Skip to content

Commit e5615bd

Browse files
authored
feat: TCP segments (#79)
This PR adds a class that contains the TCP segment's fields.
1 parent cabd95b commit e5615bd

File tree

3 files changed

+291
-1
lines changed

3 files changed

+291
-1
lines changed

src/packets/ip.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { FramePayload, IP_PROTOCOL_TYPE } from "./ethernet";
22

3+
// Taken from here: https://en.wikipedia.org/wiki/List_of_IP_protocol_numbers
34
export const ICMP_PROTOCOL_NUMBER = 1;
45
export const TCP_PROTOCOL_NUMBER = 6;
56
export const UDP_PROTOCOL_NUMBER = 17;
@@ -9,6 +10,7 @@ export class EmptyPayload implements IpPayload {
910
return new Uint8Array(0);
1011
}
1112
protocol() {
13+
// This number is reserved for experimental protocols
1214
return 0xfd;
1315
}
1416
}
@@ -102,7 +104,9 @@ export class IpAddressGenerator {
102104
}
103105

104106
export interface IpPayload {
107+
// The bytes equivalent of the payload
105108
toBytes(): Uint8Array;
109+
// The number of the protocol
106110
protocol(): number;
107111
}
108112

@@ -242,7 +246,7 @@ export class IPv4Packet implements FramePayload {
242246
export function computeIpChecksum(octets: Uint8Array): number {
243247
const sum = octets.reduce((acc, octet, i) => {
244248
return acc + (octet << (8 * (1 - (i % 2))));
245-
});
249+
}, 0);
246250
const checksum = sum & 0xffff;
247251
const carry = sum >> 16;
248252
return 0xffff ^ (checksum + carry);

src/packets/tcp.ts

Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
import {
2+
computeIpChecksum,
3+
IpAddress,
4+
IpPayload,
5+
TCP_PROTOCOL_NUMBER,
6+
} from "./ip";
7+
8+
export class Flags {
9+
// Urgent Pointer field significant
10+
public urg = false;
11+
// Acknowledgment field significant
12+
public ack = false;
13+
// Push function
14+
public psh = false;
15+
// Reset the connection
16+
public rst = false;
17+
// Synchronize sequence numbers
18+
public syn = false;
19+
// No more data from sender
20+
public fin = false;
21+
22+
// 6 bits
23+
toByte(): number {
24+
return [this.urg, this.ack, this.psh, this.rst, this.syn, this.fin].reduce(
25+
(acc, flag, index) => {
26+
return acc | bitSet(flag, 5 - index);
27+
},
28+
0,
29+
);
30+
}
31+
32+
withUrg(urg = true): Flags {
33+
this.urg = urg;
34+
return this;
35+
}
36+
37+
withAck(ack = true): Flags {
38+
this.ack = ack;
39+
return this;
40+
}
41+
42+
withPsh(psh = true): Flags {
43+
this.psh = psh;
44+
return this;
45+
}
46+
47+
withRst(rst = true): Flags {
48+
this.rst = rst;
49+
return this;
50+
}
51+
52+
withSyn(syn = true): Flags {
53+
this.syn = syn;
54+
return this;
55+
}
56+
57+
withFin(fin = true): Flags {
58+
this.fin = fin;
59+
return this;
60+
}
61+
}
62+
63+
function bitSet(value: boolean, bit: number): number {
64+
return value ? 1 << bit : 0;
65+
}
66+
67+
export class TcpSegment implements IpPayload {
68+
// Info taken from the original RFC: https://www.ietf.org/rfc/rfc793.txt
69+
// 0 1 2 3
70+
// 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
71+
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
72+
// | Source Port | Destination Port |
73+
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
74+
// | Sequence Number |
75+
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
76+
// | Acknowledgment Number |
77+
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
78+
// | Data | |U|A|P|R|S|F| |
79+
// | Offset| Reserved |R|C|S|S|Y|I| Window |
80+
// | | |G|K|H|T|N|N| |
81+
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
82+
// | Checksum | Urgent Pointer |
83+
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
84+
// | Options | Padding |
85+
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
86+
// | data |
87+
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
88+
//
89+
// 2 bytes
90+
// The source port number.
91+
sourcePort: number;
92+
// 2 bytes
93+
// The destination port number.
94+
destinationPort: number;
95+
// 4 bytes
96+
// The sequence number of the first data octet in this segment (except
97+
// when SYN is present). If SYN is present the sequence number is the
98+
// initial sequence number (ISN) and the first data octet is ISN+1.
99+
sequenceNumber: number;
100+
// 4 bytes
101+
// If the ACK control bit is set this field contains the value of the
102+
// next sequence number the sender of the segment is expecting to
103+
// receive. Once a connection is established this is always sent.
104+
acknowledgementNumber: number;
105+
106+
// 4 bits
107+
// 4-byte offset from the start of the TCP segment to the start of the data
108+
readonly dataOffset: number = 5;
109+
110+
// 6 bits
111+
// Reserved for future use. Must be zero.
112+
readonly reserved: number = 0;
113+
114+
// Control bits
115+
// 6 bits
116+
flags: Flags;
117+
118+
// 2 bytes
119+
// The number of data octets beginning with the one indicated in the
120+
// acknowledgment field which the sender of this segment is willing to
121+
// accept.
122+
public window = 0xffff;
123+
124+
// 2 bytes
125+
get checksum(): number {
126+
return this.computeChecksum();
127+
}
128+
129+
// 2 bytes
130+
readonly urgentPointer = 0;
131+
132+
// 0-40 bytes
133+
// TODO: implement options
134+
// options: Option[];
135+
136+
// Variable size
137+
data: Uint8Array;
138+
139+
// Used for the checksum calculation
140+
srcIpAddress: IpAddress;
141+
dstIpAddress: IpAddress;
142+
143+
constructor(
144+
srcPort: number,
145+
dstPort: number,
146+
seqNum: number,
147+
ackNum: number,
148+
flags: Flags,
149+
data: Uint8Array,
150+
) {
151+
checkUint(srcPort, 16);
152+
checkUint(dstPort, 16);
153+
checkUint(seqNum, 32);
154+
checkUint(ackNum, 32);
155+
156+
this.sourcePort = srcPort;
157+
this.destinationPort = dstPort;
158+
this.sequenceNumber = seqNum;
159+
this.acknowledgementNumber = ackNum;
160+
this.flags = flags;
161+
this.data = data;
162+
}
163+
164+
computeChecksum(): number {
165+
const segmentBytes = this.toBytes({ withChecksum: false });
166+
167+
const pseudoHeaderBytes = Uint8Array.from([
168+
...this.srcIpAddress.octets,
169+
...this.dstIpAddress.octets,
170+
0,
171+
TCP_PROTOCOL_NUMBER,
172+
...uintToBytes(segmentBytes.length, 2),
173+
]);
174+
const totalBytes = Uint8Array.from([...pseudoHeaderBytes, ...segmentBytes]);
175+
return computeIpChecksum(totalBytes);
176+
}
177+
178+
// ### IpPayload ###
179+
toBytes({
180+
withChecksum = true,
181+
}: { withChecksum?: boolean } = {}): Uint8Array {
182+
const checksum = withChecksum ? this.checksum : 0;
183+
return Uint8Array.from([
184+
...uintToBytes(this.sourcePort, 2),
185+
...uintToBytes(this.destinationPort, 2),
186+
...uintToBytes(this.sequenceNumber, 4),
187+
...uintToBytes(this.acknowledgementNumber, 4),
188+
(this.dataOffset << 4) | this.reserved,
189+
((this.reserved & 0b11) << 6) | this.flags.toByte(),
190+
...uintToBytes(this.window, 2),
191+
...uintToBytes(checksum, 2),
192+
...uintToBytes(this.urgentPointer, 2),
193+
...this.data,
194+
]);
195+
}
196+
197+
protocol(): number {
198+
return TCP_PROTOCOL_NUMBER;
199+
}
200+
// ### IpPayload ###
201+
}
202+
203+
function checkUint(n: number, numBits: number): void {
204+
if (numBits > 32) {
205+
throw new Error("Bitwidth more than 32 not supported");
206+
}
207+
// >>> 0 is to turn this into an unsigned integer again
208+
// https://stackoverflow.com/a/34897012
209+
const max = numBits === 32 ? 0xffffffff : ((1 << numBits) >>> 0) - 1;
210+
if (n < 0 || n > max) {
211+
throw new Error("Invalid value for uint" + numBits + ": " + n);
212+
}
213+
}
214+
215+
function uintToBytes(n: number, numBytes: number): Uint8Array {
216+
const original = n;
217+
const bytes = new Uint8Array(numBytes);
218+
for (let i = 0; i < numBytes; i++) {
219+
bytes[numBytes - i - 1] = n & 0xff;
220+
n >>>= 8;
221+
}
222+
if (n !== 0) {
223+
throw new Error("Value too large for " + numBytes + " bytes: " + original);
224+
}
225+
return bytes;
226+
}

test/tcp.test.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { IpAddress, TCP_PROTOCOL_NUMBER } from "../src/packets/ip";
2+
import { Flags, TcpSegment } from "../src/packets/tcp";
3+
4+
describe("TCP module", () => {
5+
test("constructor works", () => {
6+
new TcpSegment(0, 0, 0, 0, new Flags(), Uint8Array.of());
7+
new TcpSegment(
8+
0xffff,
9+
0xffff,
10+
0xffffffff,
11+
0xffffffff,
12+
new Flags(),
13+
Uint8Array.of(),
14+
);
15+
});
16+
17+
const flags = new Flags().withAck().withRst();
18+
19+
let testSegment = new TcpSegment(
20+
0x4e0,
21+
0xbf08,
22+
0,
23+
0x9b84d209,
24+
flags,
25+
Uint8Array.of(),
26+
);
27+
testSegment.window = 0;
28+
testSegment.srcIpAddress = IpAddress.parse("127.0.0.1");
29+
testSegment.dstIpAddress = IpAddress.parse("127.0.0.1");
30+
31+
test("Checksum works", () => {
32+
const expectedChecksum = 0x8057;
33+
expect(testSegment.checksum).toBe(expectedChecksum);
34+
});
35+
36+
test("toBytes works", () => {
37+
const bytes = Uint8Array.from([
38+
// Source port
39+
0x04, 0xe0,
40+
// Destination port
41+
0xbf, 0x08,
42+
// Sequence Number
43+
0x00, 0x00, 0x00, 0x00,
44+
// Acknowledgment Number
45+
0x9b, 0x84, 0xd2, 0x09,
46+
// Data offset (4 bits) = 5
47+
// Reserved (4 bits) = 0
48+
// Flags (RST+ACK) = 0x14
49+
0x50, 0x14,
50+
// Window
51+
0x00, 0x00,
52+
// Checksum
53+
0x80, 0x57,
54+
// Urgent pointer
55+
0x00, 0x00,
56+
// No data
57+
]);
58+
expect(testSegment.toBytes().toString()).toBe(bytes.toString());
59+
});
60+
});

0 commit comments

Comments
 (0)