feat(core): the packet capture's table after Java's Lauscher, measured against Java's own tables
captureRows.ts builds Java's rows per frame (Lauscher.datenVorbereiten): Ethernet; ARP, ICMP or IP; TCP or UDP; the data with Java's protocol recognition; with addresses and ports or ports only. The comment stays structured; captureCommentText says it with given words and numbers. ScenarioNetwork writes Java's finished tables per card (Lauscher.getDaten, German locale); all runtime runs are recorded again, NatRun has ttlOut. expectSameCaptures compares both views for every frame. Fixed where the engine differed: an ARP request's target MAC is the broadcast address, an ICMP error's identifier and sequence number are 0 (RFC 792), and host unreachable from a router carries the packet as forwarded. Not taken: Java's lower-case broadcast address in a reply to 0.0.0.0, and the typo 'Exeeded'. RIP keeps its recordings: Java's staggered router start now gives a different startup on this machine. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,150 @@
|
||||
// The capture table's rules that no Java run of the oracle shows
|
||||
// (Lauscher.datenVorbereiten, classifyApplicationLayerProtocol); the rest is
|
||||
// compared with Java's own tables in the *Java.test.ts files (javaRun.ts).
|
||||
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import {
|
||||
captureCommentText,
|
||||
captureRows,
|
||||
icmpMessage,
|
||||
TCP_FLAG,
|
||||
type CapturedFrame,
|
||||
type CaptureRow,
|
||||
type EthernetFrame,
|
||||
type IPv4Packet,
|
||||
} from '../src/index.js';
|
||||
|
||||
const WORDS = { protocol: 'Protocol', searchForMac: 'Search for MAC', macIs: 'MAC is' };
|
||||
const plain = (value: number) => String(value);
|
||||
|
||||
function captured(packet: IPv4Packet, number = 1): CapturedFrame {
|
||||
const frame: EthernetFrame = { identity: number, sourceMacAddress: '02:00:00:00:00:01', destinationMacAddress: '02:00:00:00:00:02', payload: { kind: 'ipv4', packet } };
|
||||
return { number, timeMilliseconds: 10 * number, frame };
|
||||
}
|
||||
|
||||
function udp(text: string, sourcePort: number, destinationPort: number): IPv4Packet {
|
||||
return {
|
||||
identity: 7,
|
||||
senderIPAddress: '10.0.0.1',
|
||||
receiverIPAddress: '10.0.0.2',
|
||||
timeToLive: 64,
|
||||
protocol: 'udp',
|
||||
payload: { kind: 'udp', datagram: { sourcePort, destinationPort, payload: new TextEncoder().encode(text) } },
|
||||
};
|
||||
}
|
||||
|
||||
function tcp(text: string, sourcePort: number, destinationPort: number, flags: number = TCP_FLAG.acknowledgement): IPv4Packet {
|
||||
return {
|
||||
identity: 8,
|
||||
senderIPAddress: '10.0.0.1',
|
||||
receiverIPAddress: '10.0.0.2',
|
||||
timeToLive: 64,
|
||||
protocol: 'tcp',
|
||||
payload: {
|
||||
kind: 'tcp',
|
||||
segment: { sourcePort, destinationPort, sequenceNumber: 1000, acknowledgementNumber: 2000, flags, payload: new TextEncoder().encode(text) },
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/** The protocol of the data row, or undefined without one. */
|
||||
function applicationProtocol(packet: IPv4Packet): string | undefined {
|
||||
return captureRows([captured(packet)], { withAddresses: true }).find((row) => row.layer === 'application')?.protocol;
|
||||
}
|
||||
|
||||
const comments = (rows: CaptureRow[]) => rows.map((row) => captureCommentText(row.comment, WORDS, plain));
|
||||
|
||||
describe('capture table', () => {
|
||||
it("names the data's protocol as Java does, by markers, ports and words", () => {
|
||||
expect(applicationProtocol(tcp('x//0x40//y', 6346, 6346))).toBe('GNT');
|
||||
expect(applicationProtocol(tcp('+ok 2 messages', 110, 50000))).toBe('POP3');
|
||||
expect(applicationProtocol(tcp('retr 1', 50000, 110))).toBe('POP3');
|
||||
expect(applicationProtocol(tcp('noop', 50000, 110))).toBe('POP3');
|
||||
expect(applicationProtocol(tcp('hello', 50000, 110))).toBe('');
|
||||
expect(applicationProtocol(tcp('250 OK', 25, 50000))).toBe('SMTP');
|
||||
expect(applicationProtocol(tcp('anything', 50000, 25))).toBe('SMTP');
|
||||
expect(applicationProtocol(udp('ID=1 QR=0 RCODE=0', 50000, 53))).toBe('DNS');
|
||||
expect(applicationProtocol(udp('DHCPDISCOVER', 68, 67))).toBe('DHCP');
|
||||
expect(applicationProtocol(udp('table', 521, 520))).toBe('RIP');
|
||||
expect(applicationProtocol(udp('table', 520, 520))).toBe('');
|
||||
expect(applicationProtocol(tcp('GET / HTTP/1.1', 50000, 80))).toBe('HTTP');
|
||||
expect(applicationProtocol(tcp('GET /', 50000, 80))).toBe('');
|
||||
expect(applicationProtocol(tcp('<html>', 80, 50000))).toBe('HTTP');
|
||||
// GNT comes first, then POP3, SMTP, DNS, DHCP, RIP, HTTP.
|
||||
expect(applicationProtocol(udp('DHCP ID=1 QR=0 RCODE=0', 68, 67))).toBe('DNS');
|
||||
});
|
||||
|
||||
it('shows data only when Java trims them to something, and without ends in the layered view', () => {
|
||||
expect(applicationProtocol(udp(' \t\n', 9, 9))).toBeUndefined();
|
||||
expect(applicationProtocol(tcp('', 50000, 80))).toBeUndefined();
|
||||
const withAddresses = captureRows([captured(udp(' x ', 5000, 9))], { withAddresses: true });
|
||||
const layered = captureRows([captured(udp(' x ', 5000, 9))], { withAddresses: false });
|
||||
expect(withAddresses.map((row) => [row.source, row.destination])).toEqual([
|
||||
['02:00:00:00:00:01', '02:00:00:00:00:02'],
|
||||
['10.0.0.1', '10.0.0.2'],
|
||||
['10.0.0.1:5000', '10.0.0.2:9'],
|
||||
['10.0.0.1:5000', '10.0.0.2:9'],
|
||||
]);
|
||||
expect(layered.map((row) => [row.source, row.destination]).slice(2)).toEqual([
|
||||
['5000', '9'],
|
||||
[null, null],
|
||||
]);
|
||||
expect(comments(layered)).toEqual(['0x800', 'Protocol: 17, TTL: 64', '', ' x ']);
|
||||
});
|
||||
|
||||
it('marks a SYN, else a FIN, and an acknowledgement only with its flag', () => {
|
||||
const segmentComment = (flags: number) => comments(captureRows([captured(tcp('', 1, 2, flags))], { withAddresses: true }))[2];
|
||||
expect(segmentComment(TCP_FLAG.synchronize)).toBe('SYN, SEQ: 1000');
|
||||
expect(segmentComment(TCP_FLAG.synchronize | TCP_FLAG.finish | TCP_FLAG.acknowledgement)).toBe('SYN, SEQ: 1000, ACK: 2000');
|
||||
expect(segmentComment(TCP_FLAG.finish | TCP_FLAG.acknowledgement)).toBe('FIN, SEQ: 1000, ACK: 2000');
|
||||
expect(segmentComment(TCP_FLAG.push | TCP_FLAG.reset)).toBe('SEQ: 1000');
|
||||
expect(captureCommentText({ kind: 'tcp', flag: undefined, sequence: 1234567, acknowledgement: 1000 }, WORDS, (value) => value.toLocaleString('de-DE'))).toBe(
|
||||
'SEQ: 1.234.567, ACK: 1.000',
|
||||
);
|
||||
});
|
||||
|
||||
it('writes an ICMP error with the packet it carries, also an ICMP one inside', () => {
|
||||
const echo: IPv4Packet = {
|
||||
identity: 3,
|
||||
senderIPAddress: '10.0.0.1',
|
||||
receiverIPAddress: '10.0.0.9',
|
||||
timeToLive: 1,
|
||||
protocol: 'icmp',
|
||||
payload: { kind: 'icmp', message: icmpMessage('echoRequest', { identifier: 4, sequenceNumber: 5 }) },
|
||||
};
|
||||
const error: IPv4Packet = {
|
||||
identity: 6,
|
||||
senderIPAddress: '10.0.0.254',
|
||||
receiverIPAddress: '10.0.0.1',
|
||||
timeToLive: 64,
|
||||
protocol: 'icmp',
|
||||
payload: { kind: 'icmp', message: icmpMessage('timeExceeded', { embeddedOriginalPacket: echo }) },
|
||||
};
|
||||
const rows = captureRows([captured(error)], { withAddresses: true });
|
||||
expect(rows.map((row) => [row.protocol, row.layer])).toEqual([
|
||||
['', 'networkAccess'],
|
||||
['ICMP', 'internet'],
|
||||
]);
|
||||
expect(comments(rows)[1]).toBe(
|
||||
'ICMP Time Exceeded (poof), TTL: 64, Identifier: 0, Seq.-No.: 0, Payload: ' +
|
||||
'[id=3; ttl=1; protocol=1; sender=10.0.0.1; target=10.0.0.9; icmpType=8; icmpCode=0; identifier=4; seqNr=5]',
|
||||
);
|
||||
const unreachable = { ...error, payload: { kind: 'icmp' as const, message: icmpMessage('destinationNetworkUnreachable', { embeddedOriginalPacket: udp('x', 1, 2) }) } };
|
||||
expect(comments(captureRows([captured(unreachable)], { withAddresses: true }))[1]).toBe(
|
||||
'ICMP Network Unreachable, TTL: 64, Identifier: 0, Seq.-No.: 0, Payload: [id=7, ttl=64, protocol=17, dest=10.0.0.2, src=10.0.0.1]',
|
||||
);
|
||||
});
|
||||
|
||||
it('keeps the frame number and time on each of its rows', () => {
|
||||
const rows = captureRows([captured(udp('a', 1, 2), 4), captured(udp('', 1, 2), 9)], { withAddresses: true });
|
||||
expect(rows.map((row) => [row.number, row.timeMilliseconds])).toEqual([
|
||||
[4, 40],
|
||||
[4, 40],
|
||||
[4, 40],
|
||||
[4, 40],
|
||||
[9, 90],
|
||||
[9, 90],
|
||||
[9, 90],
|
||||
]);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user