diff --git a/src/graphics/renderables/packet_info.ts b/src/graphics/renderables/packet_info.ts index a8c3005b..a119bf57 100644 --- a/src/graphics/renderables/packet_info.ts +++ b/src/graphics/renderables/packet_info.ts @@ -108,7 +108,6 @@ export class PacketInfo extends BaseInfo { private addToggleInfo(): void { const packetDetails = this.packet.getPacketDetails( this.packet.viewgraph.getLayer(), - this.packet.rawPacket, ); const toggleInfo = new ToggleInfo({ diff --git a/src/packets/arp.ts b/src/packets/arp.ts index 1ef91762..62b46c19 100644 --- a/src/packets/arp.ts +++ b/src/packets/arp.ts @@ -1,6 +1,7 @@ import { IpAddress } from "./ip"; import { ARP_PROTOCOL_TYPE, FramePayload, MacAddress } from "./ethernet"; import { TOOLTIP_KEYS } from "../utils/constants/tooltips_constants"; +import { Layer } from "../types/layer"; const ETHERNET_HTYPE = 1; const IPv4_PTYPE = 0x0800; @@ -92,6 +93,21 @@ export abstract class ArpPacket implements FramePayload { return `ARP-${this.type}`; } + // eslint-disable-next-line @typescript-eslint/no-unused-vars + getPayload(layer: Layer): Record { + return { + [TOOLTIP_KEYS.HTYPE]: this.htype, + [TOOLTIP_KEYS.PTYPE]: this.ptype, + [TOOLTIP_KEYS.HLEN]: this.hlen, + [TOOLTIP_KEYS.PLEN]: this.plen, + [TOOLTIP_KEYS.OP]: this.op, + [TOOLTIP_KEYS.SHA]: this.sha.toString(), + [TOOLTIP_KEYS.SPA]: this.spa.toString(), + [TOOLTIP_KEYS.THA]: this.tha.toString(), + [TOOLTIP_KEYS.TPA]: this.tpa.toString(), + }; + } + // eslint-disable-next-line getDetails(_layer: number): Record { return { diff --git a/src/packets/ethernet.ts b/src/packets/ethernet.ts index a6fc2124..f9bbc37a 100644 --- a/src/packets/ethernet.ts +++ b/src/packets/ethernet.ts @@ -1,5 +1,6 @@ import { CRC32 } from "@tsxper/crc32"; import { Layer } from "../types/layer"; +import { TOOLTIP_KEYS } from "../utils/constants/tooltips_constants"; // From https://en.wikipedia.org/wiki/EtherType export const IP_PROTOCOL_TYPE = 0x0800; @@ -176,12 +177,15 @@ export class EthernetFrame { getDetails(layer: Layer) { if (layer == Layer.Link) { const ethernetDetails = { - EtherType: this.type.toString(), + [TOOLTIP_KEYS.ETHERTYPE]: this.type.toString(), + [TOOLTIP_KEYS.SOURCE_MAC_ADDRESS]: this.source.toString(), + [TOOLTIP_KEYS.DESTINATION_MAC_ADDRESS]: this.destination.toString(), + [TOOLTIP_KEYS.CRC]: this.crc.toString(16).padStart(8, "0"), }; // Merge Ethernet details with payload details return { ...ethernetDetails, - ...this.payload.getDetails(layer), + Payload: this.payload.getPayload(layer), }; } else { return this.payload.getDetails(layer); @@ -196,6 +200,8 @@ export interface FramePayload { type(): number; // Get details of the payload getDetails(layer: Layer): Record; + // Get payload data for Link layer + getPayload(layer: Layer): Record | string; } export function compareMacs(mac1: MacAddress, mac2: MacAddress): number { diff --git a/src/packets/ip.ts b/src/packets/ip.ts index 5396a1af..d565b54c 100644 --- a/src/packets/ip.ts +++ b/src/packets/ip.ts @@ -1,5 +1,7 @@ import { FramePayload, IP_PROTOCOL_TYPE } from "./ethernet"; import { Layer } from "../types/layer"; +import { TOOLTIP_KEYS } from "../utils/constants/tooltips_constants"; +import { TcpSegment, TcpSegmentToBytesProps } from "./tcp"; // Taken from here: https://en.wikipedia.org/wiki/List_of_IP_protocol_numbers export const ICMP_PROTOCOL_NUMBER = 1; @@ -175,7 +177,7 @@ export interface IpPayload { // Length of the payload in bytes byteLength(): number; // The bytes equivalent of the payload - toBytes(): Uint8Array; + toBytes(tcpToBytesProps?: TcpSegmentToBytesProps): Uint8Array; // The number of the protocol protocol(): number; // Packet protocol name @@ -284,7 +286,13 @@ export class IPv4Packet implements FramePayload { } let payload = new Uint8Array(0); if (withPayload) { - payload = this.payload.toBytes(); + payload = + this.payload instanceof TcpSegment + ? this.payload.toBytes({ + srcIpAddress: this.sourceAddress, + dstIpAddress: this.destinationAddress, + }) + : this.payload.toBytes(); } return Uint8Array.from([ (this.version << 4) | this.internetHeaderLength, @@ -321,6 +329,11 @@ export class IPv4Packet implements FramePayload { return IP_PROTOCOL_TYPE; } + // eslint-disable-next-line @typescript-eslint/no-unused-vars + getPayload(layer: Layer): Record | string { + return "IPv4 Datagram"; + } + getDetails(layer: Layer) { if (layer == Layer.Network) { return { @@ -333,6 +346,9 @@ export class IPv4Packet implements FramePayload { "Time to Live": this.timeToLive, Protocol: this.protocol, "Header Checksum": this.headerChecksum, + [TOOLTIP_KEYS.SOURCE_IP_ADDRESS]: this.sourceAddress.toString(), + [TOOLTIP_KEYS.DESTINATION_IP_ADDRESS]: + this.destinationAddress.toString(), Payload: this.payload.getPayload(), }; } else { diff --git a/src/packets/tcp.ts b/src/packets/tcp.ts index 1a0bb1e7..63cff900 100644 --- a/src/packets/tcp.ts +++ b/src/packets/tcp.ts @@ -71,6 +71,12 @@ function bitSet(value: boolean, bit: number): number { return value ? 1 << bit : 0; } +export interface TcpSegmentToBytesProps { + withChecksum?: boolean; + srcIpAddress?: IpAddress; + dstIpAddress?: IpAddress; +} + export class TcpSegment implements IpPayload { // Info taken from the original RFC: https://www.ietf.org/rfc/rfc793.txt // 0 1 2 3 @@ -129,8 +135,8 @@ export class TcpSegment implements IpPayload { public window = 0xffff; // 2 bytes - get checksum(): number { - return this.computeChecksum(); + checksum(srcIpAddress: IpAddress, dstIpAddress: IpAddress): number { + return this.computeChecksum(srcIpAddress, dstIpAddress); } // 2 bytes @@ -178,12 +184,12 @@ export class TcpSegment implements IpPayload { return this; } - computeChecksum(): number { + computeChecksum(srcIpAddress: IpAddress, dstIpAddress: IpAddress): number { const segmentBytes = this.toBytes({ withChecksum: false }); const pseudoHeaderBytes = Uint8Array.from([ - ...this.srcIpAddress.octets, - ...this.dstIpAddress.octets, + ...srcIpAddress.octets, + ...dstIpAddress.octets, 0, TCP_PROTOCOL_NUMBER, ...uintToBytes(segmentBytes.length, 2), @@ -203,8 +209,12 @@ export class TcpSegment implements IpPayload { toBytes({ withChecksum = true, - }: { withChecksum?: boolean } = {}): Uint8Array { - const checksum = withChecksum ? this.checksum : 0; + srcIpAddress, + dstIpAddress, + }: TcpSegmentToBytesProps = {}): Uint8Array { + const checksum = withChecksum + ? this.checksum(srcIpAddress, dstIpAddress) + : 0; return Uint8Array.from([ ...uintToBytes(this.sourcePort, 2), ...uintToBytes(this.destinationPort, 2), diff --git a/src/programs/echo_sender.ts b/src/programs/echo_sender.ts index 65943d94..31d13f5a 100644 --- a/src/programs/echo_sender.ts +++ b/src/programs/echo_sender.ts @@ -5,9 +5,9 @@ import { ProgramBase } from "./program_base"; import { ViewGraph } from "../types/graphs/viewgraph"; import { ProgramInfo } from "../graphics/renderables/device_info"; import { EchoRequest } from "../packets/icmp"; -import { IpAddress, IPv4Packet } from "../packets/ip"; +import { IPv4Packet } from "../packets/ip"; import { ViewNetworkDevice } from "../types/view-devices/vNetworkDevice"; -import { EthernetFrame, MacAddress } from "../packets/ethernet"; +import { EthernetFrame } from "../packets/ethernet"; import { TOOLTIP_KEYS } from "../utils/constants/tooltips_constants"; import { Layer } from "../types/layer"; @@ -57,40 +57,27 @@ export class SingleEcho extends ProgramBase { this.dstId, this.viewgraph, ); - let src: { ip: IpAddress; mac: MacAddress }, - dst: { ip: IpAddress; mac: MacAddress }, - sendingIface: number; if (!forwardingData) { console.warn( `Device ${this.srcId} could not send ping to device ${this.dstId}`, ); - src = { - mac: srcDevice.interfaces[0].mac, - ip: srcDevice.interfaces[0].ip, - }; - dst = { - mac: dstDevice.interfaces[0].mac, - ip: dstDevice.interfaces[0].ip, - }; - sendingIface = 0; - } else { - ({ src, dst, sendingIface } = forwardingData); + return; } + const { src, dst, nextHop, sendingIface } = forwardingData; const echoRequest = new EchoRequest(0); // Wrap in IP datagram const ipPacket = new IPv4Packet(src.ip, dst.ip, echoRequest); - // Resolve destination MAC address - const dstMac = srcDevice.resolveAddress(dst.ip); - if (!dstMac) { + // Resolve next hop MAC address + const nextHopMac = srcDevice.resolveAddress(nextHop.ip); + if (!nextHopMac || !nextHopMac.mac) { console.debug( - `Device ${this.srcId} couldn't resolve MAC address for device with IP ${dst.ip.toString()}. Program cancelled`, + `Device ${this.srcId} couldn't resolve next hop MAC address for device with IP ${nextHop.ip.toString()}. Program cancelled`, ); - return; } // Wrap in Ethernet frame - const ethernetFrame = new EthernetFrame(src.mac, dst.mac, ipPacket); + const ethernetFrame = new EthernetFrame(src.mac, nextHopMac.mac, ipPacket); sendViewPacket(this.viewgraph, this.srcId, ethernetFrame, sendingIface); } diff --git a/src/programs/http_client.ts b/src/programs/http_client.ts index f72668ef..c625f6cd 100644 --- a/src/programs/http_client.ts +++ b/src/programs/http_client.ts @@ -90,7 +90,10 @@ export class HttpClient extends ProgramBase { // Write request const socket = await this.runner.tcpConnect(this.dstId); if (!socket) { - console.warn("HttpClient failed to connect"); + console.error("HttpClient failed to connect"); + showError( + "Failed to connect to HTTP server. Make sure the forwarding table is set up correctly.", + ); return; } if (this.stopped) { diff --git a/src/styles/info.css b/src/styles/info.css index 9e3ef6c1..af1ac4e3 100644 --- a/src/styles/info.css +++ b/src/styles/info.css @@ -10,7 +10,7 @@ padding: 0; /* Removes default padding */ margin: 0; /* Removes default margin */ list-style: none; /* Removes list bullets */ - overflow: auto; /* Enables scrolling if the content is too large */ + overflow-x: auto; } /* Ensures each item takes up the full width */ @@ -101,7 +101,6 @@ border: 1px solid #bfbfbf; /* Adds a subtle border */ width: 100%; /* Ensures the container occupies the full width */ max-width: 100%; /* Prevents it from exceeding the sidebar width */ - overflow-x: auto; /* Enables horizontal scrolling if needed */ box-sizing: border-box; /* Ensures padding and border do not affect width */ display: block; } @@ -113,9 +112,9 @@ background-color: #111; /* Dark background for better contrast */ padding: 10px; /* Adds spacing inside */ border-radius: 6px; /* Rounds the corners */ - width: 100%; /* Ensures it takes the full width of the container */ max-width: 100%; /* Prevents it from exceeding the container */ display: block; + overflow-x: auto; } /* TCP Flags Table Styles */ diff --git a/src/types/data-devices/dNetworkDevice.ts b/src/types/data-devices/dNetworkDevice.ts index 953f8271..40327fce 100644 --- a/src/types/data-devices/dNetworkDevice.ts +++ b/src/types/data-devices/dNetworkDevice.ts @@ -53,7 +53,7 @@ export abstract class DataNetworkDevice extends DataDevice { ): { mac: MacAddress; edited: boolean } | undefined { const entry = this.arpTable.get(ip.toString()); if (!entry) { - // Buscar el dispositivo y la MAC real si no está en la tabla + // Search for the device and the real MAC if it is not in the table const device = this.datagraph.getDeviceByIP(ip); if (!device) { console.warn(`Device with ip ${ip.toString()} not found in DataGraph`); @@ -62,8 +62,13 @@ export abstract class DataNetworkDevice extends DataDevice { const iface = device.interfaces.find((iface) => iface.ip?.equals(ip)); return iface ? { mac: iface.mac, edited: false } : undefined; } - // Si la entrada existe pero la MAC es "", se considera eliminada - if (entry.mac === "") return undefined; + // If the entry exists but the MAC is "", it is considered deleted + if (entry.mac === "") { + console.debug( + `Interface ${ip.toString()} from device ${this.id} is deleted`, + ); + return undefined; + } return { mac: MacAddress.parse(entry.mac), edited: entry.edited }; } diff --git a/src/types/network-modules/tables/arp_table.ts b/src/types/network-modules/tables/arp_table.ts index 2a5c9044..0026edd5 100644 --- a/src/types/network-modules/tables/arp_table.ts +++ b/src/types/network-modules/tables/arp_table.ts @@ -52,7 +52,7 @@ export function removeArpTableEntry( ): void { const device = dataGraph.getDevice(deviceId); if (!device || !(device instanceof DataNetworkDevice)) { - console.warn(`Device with ID ${deviceId} is not a network device.`); + console.error(`Network Device with ID ${deviceId} not found.`); return; } device.arpTable.add({ ip, mac: "", edited: false }); diff --git a/src/types/network-modules/tcp/tcpState.ts b/src/types/network-modules/tcp/tcpState.ts index 22976d7f..abe1f14d 100644 --- a/src/types/network-modules/tcp/tcpState.ts +++ b/src/types/network-modules/tcp/tcpState.ts @@ -57,23 +57,23 @@ function sendIpPacket( console.warn(`Device ${dst.id} is not reachable from device ${src.id}`); return false; } - const [srcData, dstData, sendingIface] = [ + const [srcData, nextHopData, dstData, sendingIface] = [ forwardingData.src, + forwardingData.nextHop, forwardingData.dst, forwardingData.sendingIface, ]; - // Resolve destination MAC address - const dstMac = src.resolveAddress(dstData.ip); - if (!dstMac) { - console.warn( - `Device ${src.id} couldn't resolve MAC address for device with IP ${dstData.ip.toString()}. Program cancelled`, + // Resolve next hop MAC address + const nextHopMac = src.resolveAddress(nextHopData.ip); + if (!nextHopMac || !nextHopMac.mac) { + console.debug( + `Device ${this.srcId} couldn't resolve next hop MAC address for device with IP ${nextHopData.ip.toString()}. Program cancelled`, ); - return false; } const ipPacket = new IPv4Packet(srcData.ip, dstData.ip, payload); - const frame = new EthernetFrame(srcData.mac, dstData.mac, ipPacket); + const frame = new EthernetFrame(srcData.mac, nextHopData.mac, ipPacket); sendViewPacket(src.viewgraph, src.id, frame, sendingIface); return true; diff --git a/src/types/packet.ts b/src/types/packet.ts index 9b0b6fc0..caa4060e 100644 --- a/src/types/packet.ts +++ b/src/types/packet.ts @@ -143,8 +143,8 @@ export class Packet extends Graphics { this.removeHighlight(); } - getPacketDetails(layer: Layer, rawPacket: EthernetFrame) { - return rawPacket.getDetails(layer); + getPacketDetails(layer: Layer) { + return this.rawPacket.getDetails(layer); } isVisible(): boolean { diff --git a/src/types/view-devices/vNetworkDevice.ts b/src/types/view-devices/vNetworkDevice.ts index 18ae51f4..892be03c 100644 --- a/src/types/view-devices/vNetworkDevice.ts +++ b/src/types/view-devices/vNetworkDevice.ts @@ -1,10 +1,5 @@ import { Texture } from "pixi.js"; -import { - ICMP_PROTOCOL_NUMBER, - IpAddress, - IPv4Packet, - TCP_PROTOCOL_NUMBER, -} from "../../packets/ip"; +import { ICMP_PROTOCOL_NUMBER, IpAddress, IPv4Packet } from "../../packets/ip"; import { DeviceId, NetworkInterfaceData } from "../graphs/datagraph"; import { ViewDevice } from "./vDevice"; import { ViewGraph } from "../graphs/viewgraph"; @@ -31,6 +26,10 @@ interface ForwardingData { ip: IpAddress; mac: MacAddress; }; + nextHop: { + ip: IpAddress; + mac: MacAddress; + }; dst: { ip: IpAddress; mac: MacAddress; @@ -83,21 +82,27 @@ export abstract class ViewNetworkDevice extends ViewDevice { const receivingIface = lastEdge.getDeviceInterface(dstId); const dstDevice = viewgraph.getDevice(dstId); const dstIface = dstDevice.interfaces[receivingIface]; - // Get dstMac - let dstMac: MacAddress = dstIface.mac; + // Get nextHop + let nextHop: { mac: MacAddress; ip: IpAddress } = { + mac: dstIface.mac, + ip: dstIface.ip, + }; for (const idx of path.slice(1).keys()) { const [sendingId, receivingId] = [path[idx], path[idx + 1]]; const receivingDevice = viewgraph.getDevice(receivingId); if (receivingDevice instanceof ViewNetworkDevice) { const edge = viewgraph.getEdge(sendingId, receivingId); const receivingIface = edge.getDeviceInterface(receivingId); - dstMac = receivingDevice.interfaces[receivingIface].mac; + // ip should be defined + const { mac, ip } = receivingDevice.interfaces[receivingIface]; + nextHop = { mac, ip }; break; } } const forwardingData = { src: { mac: srcIface.mac, ip: srcIface.ip }, - dst: { mac: dstMac, ip: dstIface.ip }, + nextHop: nextHop, + dst: { mac: dstIface.mac, ip: dstIface.ip }, sendingIface, }; return forwardingData; @@ -169,7 +174,7 @@ export abstract class ViewNetworkDevice extends ViewDevice { const dstDevice = this.viewgraph.getDeviceByIP(datagram.sourceAddress); if (!(dstDevice instanceof ViewNetworkDevice)) { console.warn( - `Device with IP ${datagram.sourceAddress.toString} was not found or was not a Network Device`, + `Network Device with IP ${datagram.sourceAddress.toString()} was not found.`, ); return; } @@ -177,25 +182,19 @@ export abstract class ViewNetworkDevice extends ViewDevice { case ICMP_PROTOCOL_NUMBER: { const request: EchoRequest = datagram.payload as EchoRequest; if (dstDevice && request.type === ICMP_REQUEST_TYPE_NUMBER) { - const { src, dst } = ViewNetworkDevice.getForwardingData( + const { src, nextHop, dst } = ViewNetworkDevice.getForwardingData( this.id, dstDevice.id, this.viewgraph, ); - const [srcMac, srcIp] = [src.mac, src.ip]; - const [dstMac, dstIp] = [dst.mac, dst.ip]; const echoReply = new EchoReply(0); - const ipPacket = new IPv4Packet(srcIp, dstIp, echoReply); - const frame = new EthernetFrame(srcMac, dstMac, ipPacket); + const ipPacket = new IPv4Packet(src.ip, dst.ip, echoReply); + const frame = new EthernetFrame(src.mac, nextHop.mac, ipPacket); console.debug(`Sending EchoReply to ${dstDevice}`); sendViewPacket(this.viewgraph, this.id, frame, iface); } break; } - case TCP_PROTOCOL_NUMBER: { - // For the moment - return; - } default: } } diff --git a/src/types/view-devices/vRouter.ts b/src/types/view-devices/vRouter.ts index f4273ace..f8ca52d6 100644 --- a/src/types/view-devices/vRouter.ts +++ b/src/types/view-devices/vRouter.ts @@ -206,9 +206,9 @@ export class ViewRouter extends ViewNetworkDevice { this.viewgraph, ); if (forwardingData && forwardingData.sendingIface === iface) { - const { src, dst } = forwardingData; + const { src, nextHop } = forwardingData; - const newFrame = new EthernetFrame(src.mac, dst.mac, datagram); + const newFrame = new EthernetFrame(src.mac, nextHop.mac, datagram); sendViewPacket(this.viewgraph, this.id, newFrame, iface); } else console.debug(`Router ${this.id} could not forward packet.`); diff --git a/src/types/view-devices/vSwitch.ts b/src/types/view-devices/vSwitch.ts index 87a9fbad..9b8c2324 100644 --- a/src/types/view-devices/vSwitch.ts +++ b/src/types/view-devices/vSwitch.ts @@ -87,7 +87,7 @@ export class ViewSwitch extends ViewDevice { sendingIface: number, iface: number, ) { - if (sendingIface === iface) { + if (sendingIface === iface || sendingIface >= this.interfaces.length) { // Packet would be sent to the interface where it came, discard it return; } @@ -103,6 +103,8 @@ export class ViewSwitch extends ViewDevice { } receiveFrame(frame: EthernetFrame, iface: number): void { + // Update the forwarding table with the source MAC address + this.updateForwardingTable(frame.source, iface); if (frame.payload instanceof ArpRequest) { const { sha, spa, tha, tpa } = frame.payload; this.interfaces.forEach((sendingIface, idx) => { @@ -116,8 +118,6 @@ export class ViewSwitch extends ViewDevice { }); return; } - // Update the forwarding table with the source MAC address - this.updateForwardingTable(frame.source, iface); // If the destination MAC address is in the forwarding table, send the frame // to the corresponding device diff --git a/src/utils/constants/tooltips_constants.ts b/src/utils/constants/tooltips_constants.ts index bd242201..b005d61e 100644 --- a/src/utils/constants/tooltips_constants.ts +++ b/src/utils/constants/tooltips_constants.ts @@ -74,7 +74,8 @@ export const TOOLTIP_KEYS = { PORT: "Port", FORWARDING_TABLE: "Forwarding Table", MULTI_EDGE_CONNECTED_DEVICES: "Multi Edge Connected Devices", - ETHERTYPE: "EtherType", + ETHERTYPE: "Ether Type", + CRC: "CRC", // ARP Details HTYPE: "Hardware Type", PTYPE: "Protocol Type", @@ -261,7 +262,9 @@ export const TOOLTIP_CONTENT = { [TOOLTIP_KEYS.TPA]: "Target protocol address (IP). The IP address of the target.", [TOOLTIP_KEYS.ETHERTYPE]: - "EtherType field. Specifies the protocol encapsulated in the payload of the frame.", + "Ether Type field. Specifies the protocol encapsulated in the payload of the frame.", + [TOOLTIP_KEYS.CRC]: + "Cyclic Redundancy Check (CRC). A checksum used for error-checking the frame. It helps detect errors in the transmitted data.", [TOOLTIP_KEYS.SEQ]: "Sequence number. Used to identify the order of packets in a TCP stream.", [TOOLTIP_KEYS.ACK]: diff --git a/test/tcp.test.ts b/test/tcp.test.ts index 375d4669..ec0438f2 100644 --- a/test/tcp.test.ts +++ b/test/tcp.test.ts @@ -25,12 +25,11 @@ describe("TCP module", () => { Uint8Array.of(), ); testSegment.window = 0; - testSegment.srcIpAddress = IpAddress.parse("127.0.0.1"); - testSegment.dstIpAddress = IpAddress.parse("127.0.0.1"); + const ipAddress = IpAddress.parse("127.0.0.1"); test("Checksum works", () => { const expectedChecksum = 0x8057; - expect(testSegment.checksum).toBe(expectedChecksum); + expect(testSegment.checksum(ipAddress, ipAddress)).toBe(expectedChecksum); }); test("toBytes works", () => { @@ -55,6 +54,13 @@ describe("TCP module", () => { 0x00, 0x00, // No data ]); - expect(testSegment.toBytes().toString()).toBe(bytes.toString()); + expect( + testSegment + .toBytes({ + srcIpAddress: IpAddress.parse("127.0.0.1"), + dstIpAddress: IpAddress.parse("127.0.0.1"), + }) + .toString(), + ).toBe(bytes.toString()); }); });