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:
@@ -3716,13 +3716,13 @@ export class NetworkRuntimeEngine {
|
||||
return this.stateValue.arpCachesByNodeId.get(nodeId)?.get(ipAddress)?.macAddress;
|
||||
}
|
||||
|
||||
/** One ARP request for `targetIPAddress`, broadcast from the interface. */
|
||||
/** One ARP request for `targetIPAddress`, broadcast from the interface; its target MAC the broadcast address, as Java's (ARP.sendeARPBroadcast; Swift, and swiftArp: zeros). */
|
||||
private sendArpRequest(nodeId: Id, networkInterface: NetworkInterface, targetIPAddress: string): void {
|
||||
const request: ArpPacket = {
|
||||
operation: 'request',
|
||||
senderMacAddress: networkInterface.macAddress,
|
||||
senderIPAddress: networkInterface.ipAddress,
|
||||
targetMacAddress: UNSPECIFIED_MAC_ADDRESS,
|
||||
targetMacAddress: this.swiftArp ? UNSPECIFIED_MAC_ADDRESS : ETHERNET_BROADCAST_MAC_ADDRESS,
|
||||
targetIPAddress,
|
||||
};
|
||||
const frame: EthernetFrame = {
|
||||
@@ -4210,7 +4210,7 @@ export class NetworkRuntimeEngine {
|
||||
const answered = () =>
|
||||
(this.stateValue.icmpObservationsByNodeId.get(fromNodeId) ?? [])
|
||||
.slice(observationCount)
|
||||
.some((observation) => observation.message.identifier === identifier && observation.message.sequenceNumber === sequenceNumber);
|
||||
.some((observation) => answersEcho(observation.message, identifier, sequenceNumber));
|
||||
let sent: IPv4DeliveryResult | undefined;
|
||||
let sentAt = this.stateValue.currentTimeMilliseconds;
|
||||
const handedOn = this.sendIPv4PacketWithOptions(fromNodeId, packet, sourceInterface.portId, true, 'sent', {
|
||||
@@ -4229,9 +4229,7 @@ export class NetworkRuntimeEngine {
|
||||
}
|
||||
const result = sent ?? handedOn;
|
||||
const newObservations = (this.stateValue.icmpObservationsByNodeId.get(fromNodeId) ?? []).slice(observationCount);
|
||||
const observation = newObservations.findLast(
|
||||
(candidate) => candidate.message.identifier === identifier && candidate.message.sequenceNumber === sequenceNumber,
|
||||
);
|
||||
const observation = newObservations.findLast((candidate) => answersEcho(candidate.message, identifier, sequenceNumber));
|
||||
if (observation !== undefined) {
|
||||
switch (observation.message.kind) {
|
||||
case 'echoReply':
|
||||
@@ -4533,11 +4531,12 @@ export class NetworkRuntimeEngine {
|
||||
detail: 'TTL decremented',
|
||||
});
|
||||
// Java: a router whose ARP finds no one answers host unreachable; for
|
||||
// ICMP only an echo request (ICMP.sendUnicastToNextHop).
|
||||
// ICMP only an echo request (ICMP.sendUnicastToNextHop). The error carries
|
||||
// the packet as the router forwarded it, its TTL lowered (measured).
|
||||
const echoOrNotIcmp = packet.payload.kind !== 'icmp' || packet.payload.message.kind === 'echoRequest';
|
||||
const result = this.sendIPv4PacketWithOptions(nodeId, forwarded, undefined, false, 'forwarded', {
|
||||
onUnresolved: () => {
|
||||
if (echoOrNotIcmp) this.emitIcmpError(nodeId, networkInterface, 'destinationHostUnreachable', packet);
|
||||
if (echoOrNotIcmp) this.emitIcmpError(nodeId, networkInterface, 'destinationHostUnreachable', forwarded);
|
||||
},
|
||||
onFinished: finished,
|
||||
});
|
||||
@@ -4646,11 +4645,8 @@ export class NetworkRuntimeEngine {
|
||||
protocol: 'icmp',
|
||||
payload: {
|
||||
kind: 'icmp',
|
||||
message: icmpMessage(kind, {
|
||||
identifier: icmpIdentifier(originalPacket),
|
||||
sequenceNumber: icmpSequenceNumber(originalPacket),
|
||||
embeddedOriginalPacket: embeddedOriginal(originalPacket),
|
||||
}),
|
||||
// Identifier and sequence number unused, 0 (RFC 792, Java's sendeICMP for errors).
|
||||
message: icmpMessage(kind, { embeddedOriginalPacket: embeddedOriginal(originalPacket) }),
|
||||
},
|
||||
};
|
||||
this.recordTrace({
|
||||
@@ -4672,11 +4668,7 @@ export class NetworkRuntimeEngine {
|
||||
kind: IcmpMessageKind,
|
||||
originalPacket: IPv4Packet,
|
||||
): void {
|
||||
const message = icmpMessage(kind, {
|
||||
identifier: icmpIdentifier(originalPacket),
|
||||
sequenceNumber: icmpSequenceNumber(originalPacket),
|
||||
embeddedOriginalPacket: embeddedOriginal(originalPacket),
|
||||
});
|
||||
const message = icmpMessage(kind, { embeddedOriginalPacket: embeddedOriginal(originalPacket) });
|
||||
const packet: IPv4Packet = {
|
||||
identity: this.allocatePacketIdentity(),
|
||||
senderIPAddress: this.networkInterfaces(nodeId)[0]?.ipAddress ?? UNSPECIFIED_IP_ADDRESS,
|
||||
@@ -5010,13 +5002,13 @@ function embeddedOriginal(packet: IPv4Packet): IPv4Packet {
|
||||
return packet;
|
||||
}
|
||||
|
||||
function icmpIdentifier(packet: IPv4Packet): number {
|
||||
return packet.payload.kind === 'icmp' ? packet.payload.message.identifier : 0;
|
||||
/** The echo reply to an echo request, or an error about it (it carries the request). */
|
||||
function answersEcho(message: IcmpMessage, identifier: number, sequenceNumber: number): boolean {
|
||||
if (message.kind === 'echoReply') return message.identifier === identifier && message.sequenceNumber === sequenceNumber;
|
||||
const carried = message.embeddedOriginalPacket?.payload;
|
||||
return carried?.kind === 'icmp' && carried.message.kind === 'echoRequest' && carried.message.identifier === identifier && carried.message.sequenceNumber === sequenceNumber;
|
||||
}
|
||||
|
||||
function icmpSequenceNumber(packet: IPv4Packet): number {
|
||||
return packet.payload.kind === 'icmp' ? packet.payload.message.sequenceNumber : 0;
|
||||
}
|
||||
|
||||
export function ethernetHeaders(frame: EthernetFrame): HeaderField[] {
|
||||
return [
|
||||
|
||||
Reference in New Issue
Block a user