From d64e844053cdb641ba9fc14a0faf1bea6d29bcf9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Gr=C3=BCner?= <47506558+MegaRedHand@users.noreply.github.com> Date: Sat, 11 Jan 2025 17:28:59 -0300 Subject: [PATCH 01/23] docs: add some comments in IpAddress --- src/packets/ip.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/packets/ip.ts b/src/packets/ip.ts index 48037bfa..363c3a3c 100644 --- a/src/packets/ip.ts +++ b/src/packets/ip.ts @@ -11,7 +11,10 @@ export class EmptyPayload implements IpPayload { } } +/// Internet Protocol (IP) address +// TODO: support IPv6? export class IpAddress { + // 4 bytes octets: Uint8Array; constructor(octets: Uint8Array) { From df98f8244176a388e7a11a6eaacfdedec8dec407 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Gr=C3=BCner?= <47506558+MegaRedHand@users.noreply.github.com> Date: Sat, 11 Jan 2025 17:29:31 -0300 Subject: [PATCH 02/23] feat: add MacAddress and EthernetFrame --- src/packets/ethernet.ts | 56 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 src/packets/ethernet.ts diff --git a/src/packets/ethernet.ts b/src/packets/ethernet.ts new file mode 100644 index 00000000..7d5d8040 --- /dev/null +++ b/src/packets/ethernet.ts @@ -0,0 +1,56 @@ +/// Medium Access Control (MAC) address +export class MacAddress { + // 6 bytes + octets: Uint8Array; + + constructor(octets: Uint8Array) { + if (octets.length !== 6) { + throw new Error("Invalid MAC address"); + } + this.octets = octets; + } + + // Parse MAC address from a string representation (00:1b:63:84:45:e6) + static parse(addrString: string): MacAddress { + const octets = new Uint8Array(6); + addrString.split(":").forEach((octet, i) => { + const octetInt = parseInt(octet, 16); + if (isNaN(octetInt) || octetInt < 0 || octetInt > 255) { + throw new Error(`Invalid MAC address: ${addrString}`); + } + octets[i] = octetInt; + }); + return new this(octets); + } + + // Turn to string + toString(): string { + return Array.from(this.octets) + .map((d) => d.toString(16)) + .join(":"); + } + + // Check if two MAC addresses are equal. + equals(other: MacAddress): boolean { + return this.octets.every((octet, index) => octet === other.octets[index]); + } +} + +export class EthernetFrame { + // Info taken from wikipedia/wireshark wiki + // 7 bytes preamble and 1 byte start of frame delimiter + readonly preamble = new Uint8Array([ + 0b10101010, 0b10101010, 0b10101010, 0b10101010, 0b10101010, 0b10101010, + 0b10101010, 0b10101011, + ]); + // 6 bytes + destination: MacAddress; + // 6 bytes + source: MacAddress; + // 2 bytes + length: number; + // 46-1500 bytes + userData: Uint8Array; + // 4 bytes + fcs: number; +} From fdf4924e4c99409cca50b5f396e36947b9189d83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Gr=C3=BCner?= <47506558+MegaRedHand@users.noreply.github.com> Date: Sat, 11 Jan 2025 17:30:11 -0300 Subject: [PATCH 03/23] test: MacAddress.parse --- test/ethernet.test.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 test/ethernet.test.ts diff --git a/test/ethernet.test.ts b/test/ethernet.test.ts new file mode 100644 index 00000000..0a9595d0 --- /dev/null +++ b/test/ethernet.test.ts @@ -0,0 +1,18 @@ +import * as eth from "../src/packets/ethernet"; + +function expectStringToParseAsMacAddress( + str: string, + expectedOctets: number[], +) { + const expected = new eth.MacAddress(Uint8Array.from(expectedOctets)); + expect(eth.MacAddress.parse(str)).toEqual(expected); +} + +describe("Ethernet module", () => { + test("parsing MAC address works", () => { + expectStringToParseAsMacAddress( + "00:1b:63:84:45:e6", + [0x00, 0x1b, 0x63, 0x84, 0x45, 0xe6], + ); + }); +}); From 85cb1793c1277d15ccd2423226bedb350ef90c61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Gr=C3=BCner?= <47506558+MegaRedHand@users.noreply.github.com> Date: Sat, 11 Jan 2025 17:32:16 -0300 Subject: [PATCH 04/23] test: IpAddress.parse --- test/ip.test.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test/ip.test.ts b/test/ip.test.ts index 78ef4920..f1f87d33 100644 --- a/test/ip.test.ts +++ b/test/ip.test.ts @@ -1,6 +1,16 @@ import * as ip from "../src/packets/ip"; +function expectStringToParseAsIpAddress(str: string, expectedOctets: number[]) { + const expected = new ip.IpAddress(Uint8Array.from(expectedOctets)); + expect(ip.IpAddress.parse(str)).toEqual(expected); +} + describe("IP module", () => { + test("parsing IPv4 address works", () => { + expectStringToParseAsIpAddress("192.168.1.1", [192, 168, 1, 1]); + expectStringToParseAsIpAddress("0.0.0.0", [0, 0, 0, 0]); + expectStringToParseAsIpAddress("255.255.255.255", [255, 255, 255, 255]); + }); test("computing checksum results in valid checksum", () => { const packet = new ip.IPv4Packet( new ip.IpAddress(new Uint8Array([192, 168, 1, 1])), From d7dd17185e9965a1e267699e4a9c822dd1d6ead9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Gr=C3=BCner?= <47506558+MegaRedHand@users.noreply.github.com> Date: Sat, 11 Jan 2025 17:43:02 -0300 Subject: [PATCH 05/23] docs: rename some fields and add more docs --- src/packets/ethernet.ts | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/packets/ethernet.ts b/src/packets/ethernet.ts index 7d5d8040..c222432a 100644 --- a/src/packets/ethernet.ts +++ b/src/packets/ethernet.ts @@ -1,3 +1,8 @@ +// From https://en.wikipedia.org/wiki/EtherType +export const IP_PROTOCOL_TYPE = 0x0800; +export const ARP_PROTOCOL_TYPE = 0x0806; +export const IPV6_PROTOCOL_NUMBER = 0x86dd; + /// Medium Access Control (MAC) address export class MacAddress { // 6 bytes @@ -37,20 +42,29 @@ export class MacAddress { } export class EthernetFrame { - // Info taken from wikipedia/wireshark wiki + // Info taken from Computer Networking: A Top-Down Approach + // 8 bytes // 7 bytes preamble and 1 byte start of frame delimiter + /// Used to synchronize the communication readonly preamble = new Uint8Array([ 0b10101010, 0b10101010, 0b10101010, 0b10101010, 0b10101010, 0b10101010, 0b10101010, 0b10101011, ]); // 6 bytes + // Destination MAC address destination: MacAddress; // 6 bytes + // Source MAC address source: MacAddress; // 2 bytes - length: number; + // The payload's type + type: number; // 46-1500 bytes - userData: Uint8Array; + // If the payload is smaller than 46 bytes, it is padded. + // TODO: make this an interface + // The payload + payload: Uint8Array; // 4 bytes - fcs: number; + // Cyclic Redundancy Check (CRC) + crc: number; } From 93a08215927a225ccfe1388140188a0ab31f0d4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Gr=C3=BCner?= <47506558+MegaRedHand@users.noreply.github.com> Date: Sun, 12 Jan 2025 14:15:30 -0300 Subject: [PATCH 06/23] feat: compute CRC for frame --- package-lock.json | 7 +++++++ package.json | 1 + src/packets/ethernet.ts | 42 +++++++++++++++++++++++++++++++++++++++-- src/packets/ip.ts | 2 +- 4 files changed, 49 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 28b3d3bd..922d26e3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "license": "ISC", "dependencies": { "@pixi/filter-outline": "^5.2.0", + "@tsxper/crc32": "^2.1.3", "pixi-viewport": "^5.0.3", "pixi.js": "^8.4.1" }, @@ -1619,6 +1620,12 @@ "@sinonjs/commons": "^3.0.0" } }, + "node_modules/@tsxper/crc32": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@tsxper/crc32/-/crc32-2.1.3.tgz", + "integrity": "sha512-rXl9x70a+UXsB0wPUQIl2T7rXrPzqe30l3X268geYZj3DPBah+aB3WilY7UJB9+HUNXzhv5RBQ6jq1R08qThZw==", + "license": "MIT" + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", diff --git a/package.json b/package.json index c9ec7623..ed875a8d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,7 @@ { "dependencies": { "@pixi/filter-outline": "^5.2.0", + "@tsxper/crc32": "^2.1.3", "pixi-viewport": "^5.0.3", "pixi.js": "^8.4.1" }, diff --git a/src/packets/ethernet.ts b/src/packets/ethernet.ts index c222432a..1269097b 100644 --- a/src/packets/ethernet.ts +++ b/src/packets/ethernet.ts @@ -1,3 +1,5 @@ +import { CRC32 } from "@tsxper/crc32"; + // From https://en.wikipedia.org/wiki/EtherType export const IP_PROTOCOL_TYPE = 0x0800; export const ARP_PROTOCOL_TYPE = 0x0806; @@ -41,6 +43,8 @@ export class MacAddress { } } +const crc32 = new CRC32(); + export class EthernetFrame { // Info taken from Computer Networking: A Top-Down Approach // 8 bytes @@ -63,8 +67,42 @@ export class EthernetFrame { // If the payload is smaller than 46 bytes, it is padded. // TODO: make this an interface // The payload - payload: Uint8Array; + payload: FramePayload; // 4 bytes // Cyclic Redundancy Check (CRC) - crc: number; + get crc(): number { + return crc32.forBytes(this.toBytes({ withChecksum: false })); + } + + constructor( + source: MacAddress, + destination: MacAddress, + payload: FramePayload, + ) { + this.destination = destination; + this.source = source; + this.type = payload.type(); + this.payload = payload; + } + + toBytes({ withChecksum = true }: { withChecksum?: boolean } = {}) { + let checksum = []; + if (withChecksum) { + const crc = this.crc; + checksum = [crc >> 24, (crc >> 16) & 0xff, (crc >> 8) & 0xff, crc & 0xff]; + } + return Uint8Array.from([ + ...this.preamble, + ...this.destination.octets, + ...this.source.octets, + this.type >> 8, + this.type & 0xff, + ...this.payload.toBytes(), + ]); + } +} + +export interface FramePayload { + toBytes(): Uint8Array; + type(): number; } diff --git a/src/packets/ip.ts b/src/packets/ip.ts index 363c3a3c..663a9b57 100644 --- a/src/packets/ip.ts +++ b/src/packets/ip.ts @@ -192,7 +192,7 @@ export class IPv4Packet { }: { withChecksum?: boolean; withPayload?: boolean; - }) { + } = {}) { let checksum = 0; if (withChecksum) { checksum = this.headerChecksum; From 326a26ed2993d9ee88b2583d7f25ae2be9c32c9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Gr=C3=BCner?= <47506558+MegaRedHand@users.noreply.github.com> Date: Sun, 12 Jan 2025 14:16:46 -0300 Subject: [PATCH 07/23] fix: use checksum --- package-lock.json | 1 - src/packets/ethernet.ts | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 922d26e3..1b96682f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,7 +18,6 @@ "@eslint/js": "^9.14.0", "@types/eslint__js": "^8.42.3", "@types/jest": "^29.5.14", - "@types/pixi.js": "^5.0.0", "css-loader": "7.1.2", "eslint": "^9.14.0", "html-webpack-plugin": "^5.6.0", diff --git a/src/packets/ethernet.ts b/src/packets/ethernet.ts index 1269097b..62d38477 100644 --- a/src/packets/ethernet.ts +++ b/src/packets/ethernet.ts @@ -86,7 +86,7 @@ export class EthernetFrame { } toBytes({ withChecksum = true }: { withChecksum?: boolean } = {}) { - let checksum = []; + let checksum: number[] = []; if (withChecksum) { const crc = this.crc; checksum = [crc >> 24, (crc >> 16) & 0xff, (crc >> 8) & 0xff, crc & 0xff]; @@ -98,6 +98,7 @@ export class EthernetFrame { this.type >> 8, this.type & 0xff, ...this.payload.toBytes(), + ...checksum, ]); } } From ef80148be9af63498956a269d8414410c2cdfe07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Gr=C3=BCner?= <47506558+MegaRedHand@users.noreply.github.com> Date: Sun, 12 Jan 2025 15:57:21 -0300 Subject: [PATCH 08/23] test: add some tests and fix errors --- src/packets/ethernet.ts | 33 +++++++++++++------ src/packets/ip.ts | 8 ++++- test/ethernet.test.ts | 73 +++++++++++++++++++++++++++++++++++++++-- 3 files changed, 101 insertions(+), 13 deletions(-) diff --git a/src/packets/ethernet.ts b/src/packets/ethernet.ts index 62d38477..4ed3d7d5 100644 --- a/src/packets/ethernet.ts +++ b/src/packets/ethernet.ts @@ -3,7 +3,7 @@ import { CRC32 } from "@tsxper/crc32"; // From https://en.wikipedia.org/wiki/EtherType export const IP_PROTOCOL_TYPE = 0x0800; export const ARP_PROTOCOL_TYPE = 0x0806; -export const IPV6_PROTOCOL_NUMBER = 0x86dd; +export const IPV6_PROTOCOL_TYPE = 0x86dd; /// Medium Access Control (MAC) address export class MacAddress { @@ -45,15 +45,20 @@ export class MacAddress { const crc32 = new CRC32(); +const MINIMUM_PAYLOAD_SIZE = 46; + export class EthernetFrame { // Info taken from Computer Networking: A Top-Down Approach + // 8 bytes // 7 bytes preamble and 1 byte start of frame delimiter - /// Used to synchronize the communication - readonly preamble = new Uint8Array([ - 0b10101010, 0b10101010, 0b10101010, 0b10101010, 0b10101010, 0b10101010, - 0b10101010, 0b10101011, - ]); + // Used to synchronize the communication + // TODO: should we mention this somewhere? + // readonly preamble = new Uint8Array([ + // 0b10101010, 0b10101010, 0b10101010, 0b10101010, 0b10101010, 0b10101010, + // 0b10101010, 0b10101011, + // ]); + // 6 bytes // Destination MAC address destination: MacAddress; @@ -71,7 +76,11 @@ export class EthernetFrame { // 4 bytes // Cyclic Redundancy Check (CRC) get crc(): number { - return crc32.forBytes(this.toBytes({ withChecksum: false })); + // Computation doesn't include preamble + const frameBytes = this.toBytes({ + withChecksum: false, + }); + return crc32.forBytes(frameBytes); } constructor( @@ -89,15 +98,19 @@ export class EthernetFrame { let checksum: number[] = []; if (withChecksum) { const crc = this.crc; - checksum = [crc >> 24, (crc >> 16) & 0xff, (crc >> 8) & 0xff, crc & 0xff]; + checksum = [crc & 0xff, (crc >> 8) & 0xff, (crc >> 16) & 0xff, crc >> 24]; + } + let payload = this.payload.toBytes(); + if (payload.length < MINIMUM_PAYLOAD_SIZE) { + const padding = new Array(MINIMUM_PAYLOAD_SIZE - payload.length); + payload = Uint8Array.from([...payload, ...padding]); } return Uint8Array.from([ - ...this.preamble, ...this.destination.octets, ...this.source.octets, this.type >> 8, this.type & 0xff, - ...this.payload.toBytes(), + ...payload, ...checksum, ]); } diff --git a/src/packets/ip.ts b/src/packets/ip.ts index 663a9b57..41810f33 100644 --- a/src/packets/ip.ts +++ b/src/packets/ip.ts @@ -1,3 +1,5 @@ +import { FramePayload, IP_PROTOCOL_TYPE } from "./ethernet"; + export const ICMP_PROTOCOL_NUMBER = 1; export const TCP_PROTOCOL_NUMBER = 6; export const UDP_PROTOCOL_NUMBER = 17; @@ -120,7 +122,7 @@ export interface IpPayload { // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | Options | Padding | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -export class IPv4Packet { +export class IPv4Packet implements FramePayload { // IP version field // 4 bits readonly version = 4; @@ -231,6 +233,10 @@ export class IPv4Packet { const result = computeIpChecksum(octets); return result === 0; } + + type(): number { + return IP_PROTOCOL_TYPE; + } } export function computeIpChecksum(octets: Uint8Array): number { diff --git a/test/ethernet.test.ts b/test/ethernet.test.ts index 0a9595d0..0f9f1e27 100644 --- a/test/ethernet.test.ts +++ b/test/ethernet.test.ts @@ -1,4 +1,5 @@ import * as eth from "../src/packets/ethernet"; +import { EmptyPayload, IpAddress, IPv4Packet } from "../src/packets/ip"; function expectStringToParseAsMacAddress( str: string, @@ -8,11 +9,79 @@ function expectStringToParseAsMacAddress( expect(eth.MacAddress.parse(str)).toEqual(expected); } -describe("Ethernet module", () => { - test("parsing MAC address works", () => { +describe("MacAddress", () => { + test("parsing works", () => { expectStringToParseAsMacAddress( "00:1b:63:84:45:e6", [0x00, 0x1b, 0x63, 0x84, 0x45, 0xe6], ); }); }); + +describe("EthernetFrame", () => { + test("crc is correct", () => { + // Values from https://stackoverflow.com/a/60793979 + const dst = eth.MacAddress.parse("20:cf:30:1a:ce:a1"); + const src = eth.MacAddress.parse("62:38:e0:c2:bd:30"); + + // TODO: clean up this + const ipPayload = new EmptyPayload(); + const ipSrc = IpAddress.parse("0.0.0.0"); + const ipDst = IpAddress.parse("0.0.0.0"); + const framePayload = new IPv4Packet(ipSrc, ipDst, ipPayload); + + // Hack the expected payload into the frame payload + const expectedPayload = Uint8Array.from([ + 0x00, 0x01, 0x08, 0x00, 0x06, 0x04, 0x00, 0x01, 0x62, 0x38, 0xe0, 0xc2, + 0xbd, 0x30, 0x0a, 0x2a, 0x2a, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0a, 0x2a, 0x2a, 0x02, + ]); + framePayload.toBytes = () => expectedPayload; + framePayload.type = () => eth.ARP_PROTOCOL_TYPE; + + const frame = new eth.EthernetFrame(src, dst, framePayload); + + const expectedCrc = "6026b722"; + expect(frame.crc.toString(16)).toEqual(expectedCrc); + }); + test("toBytes is correct", () => { + // Values from https://stackoverflow.com/a/60793979 + const expected = Uint8Array.from([ + // Destination + 0x20, 0xcf, 0x30, 0x1a, 0xce, 0xa1, + // Source + 0x62, 0x38, 0xe0, 0xc2, 0xbd, 0x30, + // Type (ARP) + 0x08, 0x06, + // Payload + 0x00, 0x01, 0x08, 0x00, 0x06, 0x04, 0x00, 0x01, 0x62, 0x38, 0xe0, 0xc2, + 0xbd, 0x30, 0x0a, 0x2a, 0x2a, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0a, 0x2a, 0x2a, 0x02, + // Padding + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // CRC + 0x22, 0xb7, 0x26, 0x60, + ]); + const dst = eth.MacAddress.parse("20:cf:30:1a:ce:a1"); + const src = eth.MacAddress.parse("62:38:e0:c2:bd:30"); + + // TODO: clean up this + const ipPayload = new EmptyPayload(); + const ipSrc = IpAddress.parse("0.0.0.0"); + const ipDst = IpAddress.parse("0.0.0.0"); + const framePayload = new IPv4Packet(ipSrc, ipDst, ipPayload); + + // Hack the expected payload into the frame payload + const expectedPayload = Uint8Array.from([ + 0x00, 0x01, 0x08, 0x00, 0x06, 0x04, 0x00, 0x01, 0x62, 0x38, 0xe0, 0xc2, + 0xbd, 0x30, 0x0a, 0x2a, 0x2a, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0a, 0x2a, 0x2a, 0x02, + ]); + framePayload.toBytes = () => expectedPayload; + framePayload.type = () => eth.ARP_PROTOCOL_TYPE; + + const frame = new eth.EthernetFrame(src, dst, framePayload); + expect(frame.toBytes().toString()).toEqual(expected.toString()); + }); +}); From 93e3e93ba8284d9d3da88c610cd9fd009e87eb3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Gr=C3=BCner?= <47506558+MegaRedHand@users.noreply.github.com> Date: Sun, 12 Jan 2025 16:28:23 -0300 Subject: [PATCH 09/23] fix: 'fs' not found error https://github.com/webpack-contrib/css-loader/issues/447#issuecomment-761853289 --- package-lock.json | 10 ---------- package.json | 1 - webpack.config.js | 1 + 3 files changed, 1 insertion(+), 11 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1b96682f..84673b3d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1868,16 +1868,6 @@ "@types/node": "*" } }, - "node_modules/@types/pixi.js": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@types/pixi.js/-/pixi.js-5.0.0.tgz", - "integrity": "sha512-yZqQBR043lRBlBZci2cx6hgmX0fvBfYIqFm6VThlnueXEjitxd3coy+BGsqsZ7+ary7O//+ks4aJRhC5MJoHqA==", - "deprecated": "This is a stub types definition. pixi.js provides its own type definitions, so you do not need this installed.", - "dev": true, - "dependencies": { - "pixi.js": "*" - } - }, "node_modules/@types/qs": { "version": "6.9.16", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.16.tgz", diff --git a/package.json b/package.json index ed875a8d..5e83a20a 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,6 @@ "@eslint/js": "^9.14.0", "@types/eslint__js": "^8.42.3", "@types/jest": "^29.5.14", - "@types/pixi.js": "^5.0.0", "css-loader": "7.1.2", "eslint": "^9.14.0", "html-webpack-plugin": "^5.6.0", diff --git a/webpack.config.js b/webpack.config.js index ea07871e..9ae2de6b 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -37,5 +37,6 @@ module.exports = { }, resolve: { extensions: [".tsx", ".ts", ".js"], + fallback: { fs: false }, }, }; From 881bd28674b543e3735674c5e048d97daeca41e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Gr=C3=BCner?= <47506558+MegaRedHand@users.noreply.github.com> Date: Sun, 12 Jan 2025 16:29:54 -0300 Subject: [PATCH 10/23] chore: npm audit fix --- package-lock.json | 74 +++++++++++++++++++++++++++++++++-------------- package.json | 2 +- 2 files changed, 54 insertions(+), 22 deletions(-) diff --git a/package-lock.json b/package-lock.json index 84673b3d..49708b82 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,7 @@ "pixi.js": "^8.4.1" }, "devDependencies": { - "@eslint/js": "^9.14.0", + "@eslint/js": "9.18.0", "@types/eslint__js": "^8.42.3", "@types/jest": "^29.5.14", "css-loader": "7.1.2", @@ -891,10 +891,11 @@ "license": "MIT" }, "node_modules/@eslint/js": { - "version": "9.14.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.14.0.tgz", - "integrity": "sha512-pFoEtFWCPyDOl+C6Ift+wC7Ro89otjigCf5vcuWqWgqNSQbRrpjSvdeE6ofLz4dHmyxD5f7gIdGT4+p36L6Twg==", + "version": "9.18.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.18.0.tgz", + "integrity": "sha512-fK6L7rxcq6/z+AaQMtiFTkvbHkBLNlwyRxHpKawP0x3u9+NC6MQTnFW+AdpwC6gfHTW0051cokQgtTN2FqlxQA==", "dev": true, + "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } @@ -910,18 +911,32 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.0.tgz", - "integrity": "sha512-vH9PiIMMwvhCx31Af3HiGzsVNULDbyVkHXwlemn/B0TFj/00ho3y55efXrUZTfQipxoHC5u4xq6zblww1zm1Ig==", + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.5.tgz", + "integrity": "sha512-lB05FkqEdUg2AA0xEbUz0SnkXT1LcCTa438W4IWTUh4hdOnVbQyOJ81OrDXsJk/LSiJHubgGEFoR5EHq1NsH1A==", "dev": true, "license": "Apache-2.0", "dependencies": { + "@eslint/core": "^0.10.0", "levn": "^0.4.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@eslint/plugin-kit/node_modules/@eslint/core": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.10.0.tgz", + "integrity": "sha512-gFHJ+xBOo4G3WRlR1e/3G8A6/KZAH6zcE/hkLRCZTi/B9avAG365QhFA8uOGzTMqgTghpn7/fSnscW++dpMSAw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -3342,10 +3357,11 @@ } }, "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, + "license": "MIT", "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -3903,6 +3919,16 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint/node_modules/@eslint/js": { + "version": "9.14.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.14.0.tgz", + "integrity": "sha512-pFoEtFWCPyDOl+C6Ift+wC7Ro89otjigCf5vcuWqWgqNSQbRrpjSvdeE6ofLz4dHmyxD5f7gIdGT4+p36L6Twg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, "node_modules/eslint/node_modules/debug": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", @@ -4199,9 +4225,9 @@ } }, "node_modules/express": { - "version": "4.21.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", - "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", "dev": true, "license": "MIT", "dependencies": { @@ -4224,7 +4250,7 @@ "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.10", + "path-to-regexp": "0.1.12", "proxy-addr": "~2.0.7", "qs": "6.13.0", "range-parser": "~1.2.1", @@ -4239,6 +4265,10 @@ }, "engines": { "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/fast-deep-equal": { @@ -6475,9 +6505,9 @@ } }, "node_modules/nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", "dev": true, "funding": [ { @@ -6485,6 +6515,7 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -6834,10 +6865,11 @@ "dev": true }, "node_modules/path-to-regexp": { - "version": "0.1.10", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", - "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==", - "dev": true + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "dev": true, + "license": "MIT" }, "node_modules/picocolors": { "version": "1.1.0", diff --git a/package.json b/package.json index 5e83a20a..fe56ae07 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "version": "1.0.0", "private": true, "devDependencies": { - "@eslint/js": "^9.14.0", + "@eslint/js": "9.18.0", "@types/eslint__js": "^8.42.3", "@types/jest": "^29.5.14", "css-loader": "7.1.2", From 85782d8df59f346c780f846e62e0fccce363054e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Gr=C3=BCner?= <47506558+MegaRedHand@users.noreply.github.com> Date: Sun, 12 Jan 2025 20:44:42 -0300 Subject: [PATCH 11/23] feat: add TcpSegment --- src/packets/ip.ts | 4 ++ src/packets/tcp.ts | 95 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 99 insertions(+) create mode 100644 src/packets/tcp.ts diff --git a/src/packets/ip.ts b/src/packets/ip.ts index 41810f33..b9249fc7 100644 --- a/src/packets/ip.ts +++ b/src/packets/ip.ts @@ -1,5 +1,6 @@ import { FramePayload, IP_PROTOCOL_TYPE } from "./ethernet"; +// Taken from here: https://en.wikipedia.org/wiki/List_of_IP_protocol_numbers export const ICMP_PROTOCOL_NUMBER = 1; export const TCP_PROTOCOL_NUMBER = 6; export const UDP_PROTOCOL_NUMBER = 17; @@ -9,6 +10,7 @@ export class EmptyPayload implements IpPayload { return new Uint8Array(0); } protocol() { + // This number is reserved for experimental protocols return 0xfd; } } @@ -102,7 +104,9 @@ export class IpAddressGenerator { } export interface IpPayload { + // The bytes equivalent of the payload toBytes(): Uint8Array; + // The number of the protocol protocol(): number; } diff --git a/src/packets/tcp.ts b/src/packets/tcp.ts new file mode 100644 index 00000000..b7e70d23 --- /dev/null +++ b/src/packets/tcp.ts @@ -0,0 +1,95 @@ +import { IpPayload, TCP_PROTOCOL_NUMBER } from "./ip"; + +export class TcpSegment implements IpPayload { + // Info taken from the original RFC: https://www.ietf.org/rfc/rfc793.txt + // 0 1 2 3 + // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | Source Port | Destination Port | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | Sequence Number | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | Acknowledgment Number | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | Data | |U|A|P|R|S|F| | + // | Offset| Reserved |R|C|S|S|Y|I| Window | + // | | |G|K|H|T|N|N| | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | Checksum | Urgent Pointer | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | Options | Padding | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | data | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // + // 2 bytes + // The source port number. + sourcePort: number; + // 2 bytes + // The destination port number. + destinationPort: number; + // 4 bytes + // The sequence number of the first data octet in this segment (except + // when SYN is present). If SYN is present the sequence number is the + // initial sequence number (ISN) and the first data octet is ISN+1. + sequenceNumber: number; + // 4 bytes + // If the ACK control bit is set this field contains the value of the + // next sequence number the sender of the segment is expecting to + // receive. Once a connection is established this is always sent. + acknowledgementNumber: number; + + // 4 bits + // 4-byte offset from the start of the TCP segment to the start of the data + dataOffset: number; + + // 6 bits + // Reserved for future use. Must be zero. + // reserved: number = 0; + + // Flags + // 6 bits + // Urgent Pointer field significant + urg: boolean; + // Acknowledgment field significant + ack: boolean; + // Push function + psh: boolean; + // Reset the connection + rst: boolean; + // Synchronize sequence numbers + syn: boolean; + // No more data from sender + fin: boolean; + + // 2 bytes + // The number of data octets beginning with the one indicated in the + // acknowledgment field which the sender of this segment is willing to + // accept. + window: number; + + // 2 bytes + get checksum(): number { + return 0; + } + + // 2 bytes + urgentPointer: number; + + // 0-40 bytes + // TODO: implement options + // options: Option[]; + + // Variable size + data: Uint8Array; + + // ### IpPayload ### + toBytes(): Uint8Array { + return new Uint8Array(0); + } + + protocol(): number { + return TCP_PROTOCOL_NUMBER; + } + // ### IpPayload ### +} From f44be9750b3f570e2f8c85a3ee1a920bb572824f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Gr=C3=BCner?= <47506558+MegaRedHand@users.noreply.github.com> Date: Sun, 12 Jan 2025 21:10:44 -0300 Subject: [PATCH 12/23] feat: implement TcpSegment.toBytes() --- src/packets/tcp.ts | 99 +++++++++++++++++++++++++++++++++++++--------- 1 file changed, 81 insertions(+), 18 deletions(-) diff --git a/src/packets/tcp.ts b/src/packets/tcp.ts index b7e70d23..3fba46e7 100644 --- a/src/packets/tcp.ts +++ b/src/packets/tcp.ts @@ -1,5 +1,34 @@ import { IpPayload, TCP_PROTOCOL_NUMBER } from "./ip"; +class Flags { + // Urgent Pointer field significant + readonly urg = false; + // Acknowledgment field significant + public ack: boolean; + // Push function + readonly psh = false; + // Reset the connection + readonly rst = false; + // Synchronize sequence numbers + public syn: boolean; + // No more data from sender + public fin: boolean; + + // 6 bits + toByte(): number { + return [this.urg, this.ack, this.psh, this.rst, this.syn, this.fin].reduce( + (acc, flag, index) => { + return acc | bitSet(flag, 5 - index); + }, + 0, + ); + } +} + +function bitSet(value: boolean, bit: number): number { + return value ? 1 << bit : 0; +} + export class TcpSegment implements IpPayload { // Info taken from the original RFC: https://www.ietf.org/rfc/rfc793.txt // 0 1 2 3 @@ -41,32 +70,21 @@ export class TcpSegment implements IpPayload { // 4 bits // 4-byte offset from the start of the TCP segment to the start of the data - dataOffset: number; + readonly dataOffset: number = 5; // 6 bits // Reserved for future use. Must be zero. - // reserved: number = 0; + readonly reserved: number = 0; - // Flags + // Control bits // 6 bits - // Urgent Pointer field significant - urg: boolean; - // Acknowledgment field significant - ack: boolean; - // Push function - psh: boolean; - // Reset the connection - rst: boolean; - // Synchronize sequence numbers - syn: boolean; - // No more data from sender - fin: boolean; + flags: Flags; // 2 bytes // The number of data octets beginning with the one indicated in the // acknowledgment field which the sender of this segment is willing to // accept. - window: number; + readonly window: number = 0xffff; // 2 bytes get checksum(): number { @@ -74,7 +92,7 @@ export class TcpSegment implements IpPayload { } // 2 bytes - urgentPointer: number; + readonly urgentPointer = 0; // 0-40 bytes // TODO: implement options @@ -83,9 +101,39 @@ export class TcpSegment implements IpPayload { // Variable size data: Uint8Array; + constructor( + srcPort: number, + dstPort: number, + seqNum: number, + ackNum: number, + flags: Flags, + data: Uint8Array, + ) { + checkPort(srcPort); + checkPort(dstPort); + this.sourcePort = srcPort; + this.destinationPort = dstPort; + this.sequenceNumber = seqNum; + this.acknowledgementNumber = ackNum; + this.flags = flags; + this.data = data; + } + // ### IpPayload ### toBytes(): Uint8Array { - return new Uint8Array(0); + const checksum = this.checksum; + return Uint8Array.from([ + ...numberTobytes(this.sourcePort, 2), + ...numberTobytes(this.destinationPort, 2), + ...numberTobytes(this.sequenceNumber, 4), + ...numberTobytes(this.acknowledgementNumber, 4), + (this.dataOffset << 4) | this.reserved, + ((this.reserved & 0b11) << 6) | this.flags.toByte(), + ...numberTobytes(this.window, 2), + ...numberTobytes(checksum, 2), + ...numberTobytes(this.urgentPointer, 2), + ...this.data, + ]); } protocol(): number { @@ -93,3 +141,18 @@ export class TcpSegment implements IpPayload { } // ### IpPayload ### } + +function checkPort(port: number): void { + if (port < 0 || port > 0xffff) { + throw new Error("Invalid port"); + } +} + +function numberTobytes(n: number, numBytes: number): Uint8Array { + const bytes = new Uint8Array(numBytes); + for (let i = 0; i < numBytes; i++) { + bytes[numBytes - i - 1] = n & 0xff; + n >>= 8; + } + return bytes; +} From 3d765588fd5b76214596a9ce336242545e346dc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Gr=C3=BCner?= <47506558+MegaRedHand@users.noreply.github.com> Date: Sun, 12 Jan 2025 21:43:15 -0300 Subject: [PATCH 13/23] test: TcpSegment constructor --- src/packets/tcp.ts | 41 +++++++++++++++++++++++++++-------------- test/tcp.test.ts | 27 +++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 14 deletions(-) create mode 100644 test/tcp.test.ts diff --git a/src/packets/tcp.ts b/src/packets/tcp.ts index 3fba46e7..f52a3ad7 100644 --- a/src/packets/tcp.ts +++ b/src/packets/tcp.ts @@ -1,6 +1,6 @@ import { IpPayload, TCP_PROTOCOL_NUMBER } from "./ip"; -class Flags { +export class Flags { // Urgent Pointer field significant readonly urg = false; // Acknowledgment field significant @@ -109,8 +109,11 @@ export class TcpSegment implements IpPayload { flags: Flags, data: Uint8Array, ) { - checkPort(srcPort); - checkPort(dstPort); + checkUint(srcPort, 16); + checkUint(dstPort, 16); + checkUint(seqNum, 32); + checkUint(ackNum, 32); + this.sourcePort = srcPort; this.destinationPort = dstPort; this.sequenceNumber = seqNum; @@ -123,15 +126,15 @@ export class TcpSegment implements IpPayload { toBytes(): Uint8Array { const checksum = this.checksum; return Uint8Array.from([ - ...numberTobytes(this.sourcePort, 2), - ...numberTobytes(this.destinationPort, 2), - ...numberTobytes(this.sequenceNumber, 4), - ...numberTobytes(this.acknowledgementNumber, 4), + ...uintToBytes(this.sourcePort, 2), + ...uintToBytes(this.destinationPort, 2), + ...uintToBytes(this.sequenceNumber, 4), + ...uintToBytes(this.acknowledgementNumber, 4), (this.dataOffset << 4) | this.reserved, ((this.reserved & 0b11) << 6) | this.flags.toByte(), - ...numberTobytes(this.window, 2), - ...numberTobytes(checksum, 2), - ...numberTobytes(this.urgentPointer, 2), + ...uintToBytes(this.window, 2), + ...uintToBytes(checksum, 2), + ...uintToBytes(this.urgentPointer, 2), ...this.data, ]); } @@ -142,17 +145,27 @@ export class TcpSegment implements IpPayload { // ### IpPayload ### } -function checkPort(port: number): void { - if (port < 0 || port > 0xffff) { - throw new Error("Invalid port"); +function checkUint(n: number, numBits: number): void { + if (numBits > 32) { + throw new Error("Bitwidth more than 32 not supported"); + } + // >>> 0 is to turn this into an unsigned integer again + // https://stackoverflow.com/a/34897012 + const max = numBits === 32 ? 0xffffffff : ((1 << numBits) >>> 0) - 1; + if (n < 0 || n > max) { + throw new Error("Invalid value for uint" + numBits + ": " + n); } } -function numberTobytes(n: number, numBytes: number): Uint8Array { +function uintToBytes(n: number, numBytes: number): Uint8Array { + const original = n; const bytes = new Uint8Array(numBytes); for (let i = 0; i < numBytes; i++) { bytes[numBytes - i - 1] = n & 0xff; n >>= 8; } + if (n !== 0) { + throw new Error("Value too large for " + numBytes + " bytes: " + original); + } return bytes; } diff --git a/test/tcp.test.ts b/test/tcp.test.ts new file mode 100644 index 00000000..ba88965d --- /dev/null +++ b/test/tcp.test.ts @@ -0,0 +1,27 @@ +import { Flags, TcpSegment } from "../src/packets/tcp"; + +describe("TCP module", () => { + test("constructor works", () => { + new TcpSegment(0, 0, 0, 0, new Flags(), Uint8Array.of()); + new TcpSegment( + 0xffff, + 0xffff, + 0xffffffff, + 0xffffffff, + new Flags(), + Uint8Array.of(), + ); + }); + + test("constructor works", () => { + new TcpSegment(0, 0, 0, 0, new Flags(), Uint8Array.of()); + new TcpSegment( + 0xffff, + 0xffff, + 0xffffffff, + 0xffffffff, + new Flags(), + Uint8Array.of(), + ); + }); +}); From c034aa2af7e630776b60e2277ec18040096f0a32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Gr=C3=BCner?= <47506558+MegaRedHand@users.noreply.github.com> Date: Sun, 12 Jan 2025 21:44:16 -0300 Subject: [PATCH 14/23] add TODO --- test/tcp.test.ts | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/test/tcp.test.ts b/test/tcp.test.ts index ba88965d..9ad8d7cd 100644 --- a/test/tcp.test.ts +++ b/test/tcp.test.ts @@ -13,15 +13,8 @@ describe("TCP module", () => { ); }); - test("constructor works", () => { - new TcpSegment(0, 0, 0, 0, new Flags(), Uint8Array.of()); - new TcpSegment( - 0xffff, - 0xffff, - 0xffffffff, - 0xffffffff, - new Flags(), - Uint8Array.of(), - ); + test("toBytes works", () => { + const segment = new TcpSegment(0, 0, 0, 0, new Flags(), Uint8Array.of()); + // TODO: use wireshark to get some examples }); }); From 116ca159052a2ce5d85d0ce5348e8484f2f7628c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Gr=C3=BCner?= <47506558+MegaRedHand@users.noreply.github.com> Date: Sun, 12 Jan 2025 21:45:32 -0300 Subject: [PATCH 15/23] fix: lint --- src/packets/tcp.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/packets/tcp.ts b/src/packets/tcp.ts index f52a3ad7..eff2609d 100644 --- a/src/packets/tcp.ts +++ b/src/packets/tcp.ts @@ -88,7 +88,8 @@ export class TcpSegment implements IpPayload { // 2 bytes get checksum(): number { - return 0; + // TODO: compute + return Math.random(); } // 2 bytes From 0e92134e1605588ceb8ade8b2820c70014cb650a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Gr=C3=BCner?= <47506558+MegaRedHand@users.noreply.github.com> Date: Thu, 16 Jan 2025 20:47:20 -0300 Subject: [PATCH 16/23] test: add toBytes test --- test/tcp.test.ts | 36 ++++++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/test/tcp.test.ts b/test/tcp.test.ts index 9ad8d7cd..eaf416cd 100644 --- a/test/tcp.test.ts +++ b/test/tcp.test.ts @@ -14,7 +14,39 @@ describe("TCP module", () => { }); test("toBytes works", () => { - const segment = new TcpSegment(0, 0, 0, 0, new Flags(), Uint8Array.of()); - // TODO: use wireshark to get some examples + let flags = new Flags(); + flags.ack = true; + flags.rst = true; + + const segment = new TcpSegment( + 0x4e0, + 0x9690, + 0, + 0xe4d3ebe2, + flags, + Uint8Array.of(), + ); + const bytes = Uint8Array.from([ + // Source port + 0x04, 0xe0, + // Destination port + 0x96, 0x90, + // Sequence Number + 0x00, 0x00, 0x00, 0x00, + // Acknowledgment Number + 0xe4, 0xd3, 0xeb, 0xe2, + // Data offset (4 bits) = 5 + // Reserved (4 bits) = 0 + // Flags (RST+ACK) = 0x14 + 0x50, 0x14, + // Window + 0x00, 0x00, + // Checksum + 0x45, 0xa7, + // Urgent pointer + 0x00, 0x00, + // No data + ]); + expect(segment.toBytes().toString()).toBe(bytes.toString()); }); }); From a82c441a2df91080276891c03817cd8d19f7a99f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Gr=C3=BCner?= <47506558+MegaRedHand@users.noreply.github.com> Date: Thu, 16 Jan 2025 20:47:31 -0300 Subject: [PATCH 17/23] fix: use >>> unsigned shift left --- src/packets/tcp.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/packets/tcp.ts b/src/packets/tcp.ts index eff2609d..b1fc6a3e 100644 --- a/src/packets/tcp.ts +++ b/src/packets/tcp.ts @@ -2,17 +2,17 @@ import { IpPayload, TCP_PROTOCOL_NUMBER } from "./ip"; export class Flags { // Urgent Pointer field significant - readonly urg = false; + public urg = false; // Acknowledgment field significant - public ack: boolean; + public ack = false; // Push function - readonly psh = false; + public psh = false; // Reset the connection - readonly rst = false; + public rst = false; // Synchronize sequence numbers - public syn: boolean; + public syn = false; // No more data from sender - public fin: boolean; + public fin = false; // 6 bits toByte(): number { @@ -163,7 +163,7 @@ function uintToBytes(n: number, numBytes: number): Uint8Array { const bytes = new Uint8Array(numBytes); for (let i = 0; i < numBytes; i++) { bytes[numBytes - i - 1] = n & 0xff; - n >>= 8; + n >>>= 8; } if (n !== 0) { throw new Error("Value too large for " + numBytes + " bytes: " + original); From 185a750edf65ade18e623f1c2f077232c7ccdb25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Gr=C3=BCner?= <47506558+MegaRedHand@users.noreply.github.com> Date: Thu, 16 Jan 2025 20:50:21 -0300 Subject: [PATCH 18/23] refactor: add builder for flags --- src/packets/tcp.ts | 30 ++++++++++++++++++++++++++++++ test/tcp.test.ts | 25 ++++++++++++------------- 2 files changed, 42 insertions(+), 13 deletions(-) diff --git a/src/packets/tcp.ts b/src/packets/tcp.ts index b1fc6a3e..a70b515e 100644 --- a/src/packets/tcp.ts +++ b/src/packets/tcp.ts @@ -23,6 +23,36 @@ export class Flags { 0, ); } + + withUrg(urg: boolean = true): Flags { + this.urg = urg; + return this; + } + + withAck(ack: boolean = true): Flags { + this.ack = ack; + return this; + } + + withPsh(psh: boolean = true): Flags { + this.psh = psh; + return this; + } + + withRst(rst: boolean = true): Flags { + this.rst = rst; + return this; + } + + withSyn(syn: boolean = true): Flags { + this.syn = syn; + return this; + } + + withFin(fin: boolean = true): Flags { + this.fin = fin; + return this; + } } function bitSet(value: boolean, bit: number): number { diff --git a/test/tcp.test.ts b/test/tcp.test.ts index eaf416cd..7c599afa 100644 --- a/test/tcp.test.ts +++ b/test/tcp.test.ts @@ -13,19 +13,18 @@ describe("TCP module", () => { ); }); - test("toBytes works", () => { - let flags = new Flags(); - flags.ack = true; - flags.rst = true; + const flags = new Flags().withAck().withRst(); - const segment = new TcpSegment( - 0x4e0, - 0x9690, - 0, - 0xe4d3ebe2, - flags, - Uint8Array.of(), - ); + const testSegment = new TcpSegment( + 0x4e0, + 0x9690, + 0, + 0xe4d3ebe2, + flags, + Uint8Array.of(), + ); + + test("toBytes works", () => { const bytes = Uint8Array.from([ // Source port 0x04, 0xe0, @@ -47,6 +46,6 @@ describe("TCP module", () => { 0x00, 0x00, // No data ]); - expect(segment.toBytes().toString()).toBe(bytes.toString()); + expect(testSegment.toBytes().toString()).toBe(bytes.toString()); }); }); From 604a06edf0f17f1a5bb626a9e20bff4fc3f809a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Gr=C3=BCner?= <47506558+MegaRedHand@users.noreply.github.com> Date: Thu, 16 Jan 2025 20:52:00 -0300 Subject: [PATCH 19/23] test: add checksum test --- test/tcp.test.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/tcp.test.ts b/test/tcp.test.ts index 7c599afa..95705c42 100644 --- a/test/tcp.test.ts +++ b/test/tcp.test.ts @@ -24,6 +24,11 @@ describe("TCP module", () => { Uint8Array.of(), ); + test("Checksum works", () => { + const expectedChecksum = 0x45a7; + expect(testSegment.checksum).toBe(expectedChecksum); + }); + test("toBytes works", () => { const bytes = Uint8Array.from([ // Source port From 19150ae3669620fa2063d1c1a39a16c68eba35d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Gr=C3=BCner?= <47506558+MegaRedHand@users.noreply.github.com> Date: Thu, 16 Jan 2025 20:58:27 -0300 Subject: [PATCH 20/23] feat: compute checksum --- src/packets/tcp.ts | 18 ++++++++++++------ test/tcp.test.ts | 3 ++- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/packets/tcp.ts b/src/packets/tcp.ts index a70b515e..b3413b58 100644 --- a/src/packets/tcp.ts +++ b/src/packets/tcp.ts @@ -1,4 +1,4 @@ -import { IpPayload, TCP_PROTOCOL_NUMBER } from "./ip"; +import { computeIpChecksum, IpPayload, TCP_PROTOCOL_NUMBER } from "./ip"; export class Flags { // Urgent Pointer field significant @@ -114,12 +114,11 @@ export class TcpSegment implements IpPayload { // The number of data octets beginning with the one indicated in the // acknowledgment field which the sender of this segment is willing to // accept. - readonly window: number = 0xffff; + public window: number = 0xffff; // 2 bytes get checksum(): number { - // TODO: compute - return Math.random(); + return this.computeChecksum(); } // 2 bytes @@ -153,9 +152,16 @@ export class TcpSegment implements IpPayload { this.data = data; } + computeChecksum(): number { + const octets = this.toBytes({ withChecksum: false }); + return computeIpChecksum(octets); + } + // ### IpPayload ### - toBytes(): Uint8Array { - const checksum = this.checksum; + toBytes({ + withChecksum = true, + }: { withChecksum?: boolean } = {}): Uint8Array { + let checksum = withChecksum ? this.checksum : 0; return Uint8Array.from([ ...uintToBytes(this.sourcePort, 2), ...uintToBytes(this.destinationPort, 2), diff --git a/test/tcp.test.ts b/test/tcp.test.ts index 95705c42..1242cc6d 100644 --- a/test/tcp.test.ts +++ b/test/tcp.test.ts @@ -15,7 +15,7 @@ describe("TCP module", () => { const flags = new Flags().withAck().withRst(); - const testSegment = new TcpSegment( + let testSegment = new TcpSegment( 0x4e0, 0x9690, 0, @@ -23,6 +23,7 @@ describe("TCP module", () => { flags, Uint8Array.of(), ); + testSegment.window = 0; test("Checksum works", () => { const expectedChecksum = 0x45a7; From a659e6df9790817c8b9edc183170ffef78838165 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Gr=C3=BCner?= <47506558+MegaRedHand@users.noreply.github.com> Date: Thu, 16 Jan 2025 21:45:57 -0300 Subject: [PATCH 21/23] fix: add initial accumulator to reduce call --- src/packets/ip.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/packets/ip.ts b/src/packets/ip.ts index b9249fc7..0f3c4f39 100644 --- a/src/packets/ip.ts +++ b/src/packets/ip.ts @@ -246,7 +246,7 @@ export class IPv4Packet implements FramePayload { export function computeIpChecksum(octets: Uint8Array): number { const sum = octets.reduce((acc, octet, i) => { return acc + (octet << (8 * (1 - (i % 2)))); - }); + }, 0); const checksum = sum & 0xffff; const carry = sum >> 16; return 0xffff ^ (checksum + carry); From afbc3376b3400136ee3b9ab089328b53e2ad3f19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Gr=C3=BCner?= <47506558+MegaRedHand@users.noreply.github.com> Date: Thu, 16 Jan 2025 21:49:37 -0300 Subject: [PATCH 22/23] feat: expose srcIpAddress and dstIpAddress on TcpSegment --- src/packets/tcp.ts | 24 +++++++++++++++++++++--- test/tcp.test.ts | 15 +++++++++------ 2 files changed, 30 insertions(+), 9 deletions(-) diff --git a/src/packets/tcp.ts b/src/packets/tcp.ts index b3413b58..8c5660df 100644 --- a/src/packets/tcp.ts +++ b/src/packets/tcp.ts @@ -1,4 +1,9 @@ -import { computeIpChecksum, IpPayload, TCP_PROTOCOL_NUMBER } from "./ip"; +import { + computeIpChecksum, + IpAddress, + IpPayload, + TCP_PROTOCOL_NUMBER, +} from "./ip"; export class Flags { // Urgent Pointer field significant @@ -131,6 +136,10 @@ export class TcpSegment implements IpPayload { // Variable size data: Uint8Array; + // Used for the checksum calculation + srcIpAddress: IpAddress; + dstIpAddress: IpAddress; + constructor( srcPort: number, dstPort: number, @@ -153,8 +162,17 @@ export class TcpSegment implements IpPayload { } computeChecksum(): number { - const octets = this.toBytes({ withChecksum: false }); - return computeIpChecksum(octets); + const segmentBytes = this.toBytes({ withChecksum: false }); + + const pseudoHeaderBytes = Uint8Array.from([ + ...this.srcIpAddress.octets, + ...this.dstIpAddress.octets, + 0, + TCP_PROTOCOL_NUMBER, + ...uintToBytes(segmentBytes.length, 2), + ]); + const totalBytes = Uint8Array.from([...pseudoHeaderBytes, ...segmentBytes]); + return computeIpChecksum(totalBytes); } // ### IpPayload ### diff --git a/test/tcp.test.ts b/test/tcp.test.ts index 1242cc6d..f0d08b3d 100644 --- a/test/tcp.test.ts +++ b/test/tcp.test.ts @@ -1,3 +1,4 @@ +import { IpAddress, TCP_PROTOCOL_NUMBER } from "../src/packets/ip"; import { Flags, TcpSegment } from "../src/packets/tcp"; describe("TCP module", () => { @@ -17,16 +18,18 @@ describe("TCP module", () => { let testSegment = new TcpSegment( 0x4e0, - 0x9690, + 0xbf08, 0, - 0xe4d3ebe2, + 0x9b84d209, flags, Uint8Array.of(), ); testSegment.window = 0; + testSegment.srcIpAddress = IpAddress.parse("127.0.0.1"); + testSegment.dstIpAddress = IpAddress.parse("127.0.0.1"); test("Checksum works", () => { - const expectedChecksum = 0x45a7; + const expectedChecksum = 0x8057; expect(testSegment.checksum).toBe(expectedChecksum); }); @@ -35,11 +38,11 @@ describe("TCP module", () => { // Source port 0x04, 0xe0, // Destination port - 0x96, 0x90, + 0xbf, 0x08, // Sequence Number 0x00, 0x00, 0x00, 0x00, // Acknowledgment Number - 0xe4, 0xd3, 0xeb, 0xe2, + 0x9b, 0x84, 0xd2, 0x09, // Data offset (4 bits) = 5 // Reserved (4 bits) = 0 // Flags (RST+ACK) = 0x14 @@ -47,7 +50,7 @@ describe("TCP module", () => { // Window 0x00, 0x00, // Checksum - 0x45, 0xa7, + 0x80, 0x57, // Urgent pointer 0x00, 0x00, // No data From 8278b1d3b9d0ac3768775817bc8b3091884bddee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Gr=C3=BCner?= <47506558+MegaRedHand@users.noreply.github.com> Date: Thu, 16 Jan 2025 21:52:36 -0300 Subject: [PATCH 23/23] chore: fix lints --- src/packets/tcp.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/packets/tcp.ts b/src/packets/tcp.ts index 8c5660df..cb6b9a5f 100644 --- a/src/packets/tcp.ts +++ b/src/packets/tcp.ts @@ -29,32 +29,32 @@ export class Flags { ); } - withUrg(urg: boolean = true): Flags { + withUrg(urg = true): Flags { this.urg = urg; return this; } - withAck(ack: boolean = true): Flags { + withAck(ack = true): Flags { this.ack = ack; return this; } - withPsh(psh: boolean = true): Flags { + withPsh(psh = true): Flags { this.psh = psh; return this; } - withRst(rst: boolean = true): Flags { + withRst(rst = true): Flags { this.rst = rst; return this; } - withSyn(syn: boolean = true): Flags { + withSyn(syn = true): Flags { this.syn = syn; return this; } - withFin(fin: boolean = true): Flags { + withFin(fin = true): Flags { this.fin = fin; return this; } @@ -119,7 +119,7 @@ export class TcpSegment implements IpPayload { // The number of data octets beginning with the one indicated in the // acknowledgment field which the sender of this segment is willing to // accept. - public window: number = 0xffff; + public window = 0xffff; // 2 bytes get checksum(): number { @@ -179,7 +179,7 @@ export class TcpSegment implements IpPayload { toBytes({ withChecksum = true, }: { withChecksum?: boolean } = {}): Uint8Array { - let checksum = withChecksum ? this.checksum : 0; + const checksum = withChecksum ? this.checksum : 0; return Uint8Array.from([ ...uintToBytes(this.sourcePort, 2), ...uintToBytes(this.destinationPort, 2),