diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..b512c09 --- /dev/null +++ b/.dockerignore @@ -0,0 +1 @@ +node_modules \ No newline at end of file diff --git a/App/index.html b/App/index.html deleted file mode 100644 index e0d7ea3..0000000 --- a/App/index.html +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - Networks Assignment Socket App - - - - - - - - \ No newline at end of file diff --git a/Client.ts b/Client.ts new file mode 100644 index 0000000..de4c6c3 --- /dev/null +++ b/Client.ts @@ -0,0 +1,44 @@ +/** Import dependecies */ +import dgram from 'dgram'; +import { Buffer } from 'buffer'; + +const socketPort: number = 8080; +const message = Buffer.from('UDP CONNETION DATA'); + +/** Initialise UDP Socket */ +const client = dgram.createSocket('udp4'); + +/** Listen and handle incoming messages from Server */ +client.on('message', (msg, info) => { + console.log(`Data received from server: ${msg.toString()}`); + console.log(`Received ${msg.length} bytes from ${info.address}:${info.port}\n`); +}); + +/** Send message to Server */ +client.send(message, socketPort, 'localhost', (err) => { + if (err) { + console.log('Error sending data to Server') + client.close(); + } else { + console.log('Data sent to Server') + } +}); + + +/** + * Send a message to the Server in an interval of every 5 seconds + * Enable this option by uncommenting + */ + +// let intervalCount: number = 0; +// function sendMessage() { +// client.send(Buffer.from(`Connection number ${intervalCount}`), socketPort, 'localhost', (err) => { +// if (err) { +// console.log('Error sending data to Server') +// client.close(); +// } else { +// console.log('Data sent to Server') +// } +// }); +// } +// setInterval(sendMessage, 5000); diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..d3a126c --- /dev/null +++ b/Dockerfile @@ -0,0 +1,23 @@ +FROM node:16.10.0 + +WORKDIR / + +COPY package*.json ./ + +RUN npm install + +# Install Typescript dependecies +RUN npm install -g ts-node typescript + +COPY . . + +ENV PORT=8081 + +# HTTP Server Port +EXPOSE 8081 + +# UDP Datagram Server Port +EXPOSE 8080/udp + +# Start Broker Server +CMD [ "npm", "run", "server" ] \ No newline at end of file diff --git a/Misc/2ClientsConnectToServerAndServerBroadcast.pcapng b/Misc/2ClientsConnectToServerAndServerBroadcast.pcapng new file mode 100644 index 0000000..3ad9ad8 Binary files /dev/null and b/Misc/2ClientsConnectToServerAndServerBroadcast.pcapng differ diff --git a/Misc/Diagram.png b/Misc/Diagram.png new file mode 100644 index 0000000..86e7cf4 Binary files /dev/null and b/Misc/Diagram.png differ diff --git a/Misc/Dockerfile.png b/Misc/Dockerfile.png new file mode 100644 index 0000000..e4c48c9 Binary files /dev/null and b/Misc/Dockerfile.png differ diff --git a/Misc/ServerBroadcastToClients.png b/Misc/ServerBroadcastToClients.png new file mode 100644 index 0000000..55487b0 Binary files /dev/null and b/Misc/ServerBroadcastToClients.png differ diff --git a/Misc/StartClient.png b/Misc/StartClient.png new file mode 100644 index 0000000..d4bc87f Binary files /dev/null and b/Misc/StartClient.png differ diff --git a/Misc/StartServer.png b/Misc/StartServer.png new file mode 100644 index 0000000..c850bdb Binary files /dev/null and b/Misc/StartServer.png differ diff --git a/Misc/Wireshark.pdf b/Misc/Wireshark.pdf new file mode 100644 index 0000000..1483fac Binary files /dev/null and b/Misc/Wireshark.pdf differ diff --git a/README.md b/README.md new file mode 100644 index 0000000..3163c25 --- /dev/null +++ b/README.md @@ -0,0 +1,80 @@ +# UDP Network Socket Protocol + +![UDP Diagram](./Misc/Diagram.png) + +## Installation +``` +git clone https://github.com/mangledbottles/Network-Socket-Protocol.git +cd Network-Socket-Protocol +npm i +``` +## Usage + +### Server +There can only be one server +``` +npm run server +``` + +### Dockerizing Server +Build an image from a Dockerfile and run in a new container +```docker +docker build . +docker run -i -p 8080:8080/udp [IMAGE_ID] +``` +- -i parameter allows the user to **work with IO inside the Docker container** for Server Broadcasting +- -p parameter exposes the port 8080 for UDP use (see [Dockerfile](./Dockerfile)) +- [IMAGE_ID] is the build ID that is run in the second command + +> +> ![StartServer](./Misc/StartServer.png) + +### Client +There can be many clients +``` +npm run client +``` +> ![StartClient](./Misc/StartClient.png) +> Starting the Client sends a message to the Server and the Server response with the current time + +# Topology +## Dockerfile +This project has been [dockerized](./Dockerfile) +> ![Dockerfile](./Misc/Dockerfile.png) + +## Server.ts +The Server.ts file contains 4 main functionalities +1. Create UDP server with the dgram library +2. Read user IO from the CLI +3. Listen to incomining messages from Clients +4. Send messages to Client(s) + +## Broadcasting +The Server can send a message to all Clients. The user types into the CLI and the Server manages the IO and broadcasts the message to all connected Clients. To broadcast a message to all Clients, run the Docker container ([as seen above](#dockerizing-server)), then type a message into the CLI and press enter. +> ![ServerBroadcastToClients](./Misc/ServerBroadcastToClients.png) +> When a Client connects to the Server, their address and port are saved. When a broadcast is called, the message is sent to all addresses and ports connected. An Typescript interface has been created for Client and all Client's are stored in a Set to prevent duplicates. + +## Client.ts +The Client.ts file connects to the Server, sends a message to the Server and listens for messages from the Server. + +## Wireshark +[Wireshark](https://www.wireshark.org/) is a network packet analyzer. + +Sample intercepted request (taken from [Wireshark PCAP File](./Misc/2ClientsConnectToServerAndServerBroadcast.pcapng)) + + +| No. | Time | Source | Destination | Protocol | Length | Info | +| --- | ---- | ------ | ----------- | -------- | ------ | ---- | +| 2 | 0.010357778 | 127.0.0.1 | 127.0.0.1 | UDP | 137 | 8080 → 60137 Len=95 | + +--- +| Hexadecimal Payload | Base10 Decoded | +|-|-| +| 0000 48 65 6c 6c 6f 20 33 35 35 39 31 2c 20 79 6f 75 | Hello 35591, you | +| 0010 20 61 72 65 20 23 30 2c 20 74 69 6d 65 20 69 73 | are #0, time is | +| 0020 20 54 75 65 20 4f 63 74 20 32 36 20 32 30 32 31 | Tue Oct 26 2021 | +| 0030 20 31 38 3a 32 35 3a 35 34 20 47 4d 54 2b 30 30 | 18:25:54 GMT+00 | +| 0040 30 30 20 28 43 6f 6f 72 64 69 6e 61 74 65 64 20 | 00 (Coordinated | +| 0050 55 6e 69 76 65 72 73 61 6c 20 54 69 6d 65 29 | Universal Time) | + +This is one request taken from the PCAP file which intercepted a message from the Server to the Client sending the current date. The payload is send in hexadecimal format. In the Info tab, you can see the request is being sent from port 8080 (Server) to port 60137 (generated port for given Client). \ No newline at end of file diff --git a/Server.ts b/Server.ts new file mode 100644 index 0000000..1c9ce1e --- /dev/null +++ b/Server.ts @@ -0,0 +1,117 @@ + +/** Import dependecies */ +import dgram from "dgram"; +import express, { Application, Request, Response } from "express"; +import * as readline from "readline"; + +const app: Application = express(); +const socketPort: number = 8080; +const httpPort: number = 8081; + +/** HTTP Server for checking status */ +app.get('/', (req: Request, res: Response) => { + res.send({ message: 'Server is active', timestamp: new Date() }); +}); + +/** Initialise UDP Socket */ +const Server = dgram.createSocket('udp4'); + +/** Handle errors and close Socket */ +Server.on('error', (err) => { + console.log(`Server error:\n${err.stack}`); + Server.close(); +}); + +/** Setup readline functionalities */ +const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout +}) + +function readLineAsync(message: string) { + return new Promise((resolve, reject) => { + rl.question(message, (answer: string) => { + if (answer == 'exit') return process.exit(); + broadcast(answer) // broadcast user message to all listening Clients + resolve(answer); + }); + }); +} + +async function handleServerInput() { + await readLineAsync(""); + handleServerInput() +} + + +/** Receive Messages from Client */ +let messagesCount = 0; +Server.on('message', (msg, rinfo) => { + console.log(`Server got: ${msg} from ${rinfo.address}:${rinfo.port}`); + + /** Add Client to Client List for Broadcasting */ + const { address, port } = rinfo; + Clients.add(newClient({ address, port })); + + /** Repeat message back to Client */ + Server.send(Buffer.from(`Hello ${rinfo.port}, you are #${messagesCount++}, time is ${new Date()}`), port, address, function (error) { + if (error) { + console.log(`Error sending data to Client #${rinfo.port}`) + } else { + console.log(`Data sent to Client #${rinfo.port}`); + } + }); +}); + + +/** Broadcast Information from Server to all Clients */ +interface Client { + address: string; + port: number; +} + +let Clients: Set = new Set(); + +function newClient({ address, port }: Client) { + return JSON.stringify({ address, port }); +} + +function broadcast(broadcastMessage: string) { + console.log("Broadcasting message to all Clients") + + var message = Buffer.from(broadcastMessage); + const ClientList = Array.from(Clients); + if (ClientList.length == 0) console.log("No active Clients"); + + for (let clientNumber in ClientList) { + const { address, port } = JSON.parse(ClientList[clientNumber]); + + Server.send(message, 0, message.length, port, address, (error) => { + if (error) { + console.log(`Broadcast Error sending data to Client #${port}`) + } else { + console.log(`Broadcast Data sent to Client #${port}`); + } + }); + + } +} + +/** Launch UDP Socket and HTTP Servers, and listen on given port */ +try { + Server.on('listening', (): void => { + const address = Server.address(); + console.log(`Server listening ${address.address}:${address.port}`); + }); + + Server.bind(socketPort, (): void => { + handleServerInput(); // used to read IO from CLI and broadcast the message to Clients + console.log(`UDP Datagram Server is active at http://localhost:${socketPort}`); + }); + + app.listen(httpPort, (): void => { + console.log(`HTTP Server is active at http://localhost:8081`); + }); +} catch (error: any) { + console.error(`An error occurred with starting Server ${error.toString()}`); +} \ No newline at end of file diff --git a/Server/Index.ts b/Server/Index.ts deleted file mode 100644 index 4448adf..0000000 --- a/Server/Index.ts +++ /dev/null @@ -1,48 +0,0 @@ -import express, { Application, Request, Response } from "express"; -const app: Application = express(); -const port: string = '8080'; - -const server = require('http').createServer(app); -const { Server } = require("socket.io"); -const io = new Server(server); - -/** HTTP Server for checking if online */ -app.get('/', (req: Request, res: Response) => {`` - res.send({ message: 'Server is active', timestamp: new Date() }); -}); - -/** Broker listen to all subscribers and manage protocol */ -io.on('connection', (socket: any) => { - console.log('a user connected'); - socket.emit(' new user joined ') - - /** User subscribe and publish protocol */ - socket.on("user:subscribe", (resp: any) => { - socket.join("subscribers"); - socket.to("subscribers", "new user") - io.emit('publish: new user has subscribed to the protocol', { resp }); - console.log({ message: 'user subscribe', resp }) - }); - - /** Broker publish to all subscribers */ - socket.on("dashboard:publish", (resp: any) => { - // TODO: Implement Authentication - socket.emit('publish', ' message from dashboard'); - console.log({ message: 'dashboard publish', resp}) - }) - - /** Handle user disconnect */ - socket.on("disconnect", () => { - console.log("a user disconnected") - }) - -}); - -/** Launch server and listen on given port */ -try { - server.listen(port, (): void => { - console.log(`Server is active at http://localhost:${port}`); - }); -} catch (error: any) { - console.error(`An error occurred with message ${error.toString()}`); -} \ No newline at end of file diff --git a/Server/package-lock.json b/package-lock.json similarity index 100% rename from Server/package-lock.json rename to package-lock.json diff --git a/Server/package.json b/package.json similarity index 78% rename from Server/package.json rename to package.json index 287a21a..194b800 100644 --- a/Server/package.json +++ b/package.json @@ -6,8 +6,8 @@ "scripts": { "start": "npm run dev", "build": "npm run clean && tsc", - "dev": "nodemon Index.ts", - "dev-js": "nodemon Index.js", + "server": "nodemon Server.ts", + "client": "nodemon Client.ts", "clean": "rimraf ./dist", "reset": "git pull && git checkout . && npm i && npm run build" }, @@ -20,12 +20,10 @@ "devDependencies": { "@types/express": "^4.17.13", "@types/node": "^16.10.1", - "@types/socket.io": "^3.0.2", "nodemon": "^2.0.13", "typescript": "^4.4.3" }, "dependencies": { - "express": "^4.17.1", - "socket.io": "^4.2.0" + "express": "^4.17.1" } } diff --git a/Server/tsconfig.json b/tsconfig.json similarity index 100% rename from Server/tsconfig.json rename to tsconfig.json