Skip to content

Commit 2ed0a09

Browse files
Manuel-Polpgallino
andauthored
Switch connection to viewgraph (#142)
- Devices have MAC addresses - New hierarchical order with GraphNode interfaces and Device classes. - Switches send a received frame to the right port, following the natural packet’s flow. Does not send the frame to all it’s ports yet. - Routers register network devices beyond switches in it’s routing tables. - Protocol’s codes and types now used to identify the packet in devices’ handling process. Known bugs: - Forwarding packets with switches in the middle, in transport layer, does not work (#143). - MAC addresses does repeat. Next Issues: - Resolve known bugs - Support switches to send a received frame to all its’ ports (except the one frame arrive on). - Introduce ARP protocol with it table. --------- Co-authored-by: pgallino <[email protected]>
1 parent 89a055d commit 2ed0a09

File tree

17 files changed

+413
-168
lines changed

17 files changed

+413
-168
lines changed

src/context.ts

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Viewport } from "./graphics/viewport";
2-
import { DataGraph } from "./types/graphs/datagraph";
2+
import { DataGraph, isLinkNode, isNetworkNode } from "./types/graphs/datagraph";
33
import { ViewGraph } from "./types/graphs/viewgraph";
44
import {
55
loadFromLocalStorage,
@@ -10,6 +10,7 @@ import { Layer } from "./types/devices/device";
1010
import { IpAddress, IpAddressGenerator } from "./packets/ip";
1111
import { layerFromName } from "./types/devices/layer";
1212
import { SpeedMultiplier } from "./types/devices/speedMultiplier";
13+
import { MacAddress, MacAddressGenerator } from "./packets/ethernet";
1314

1415
export class GlobalContext {
1516
private viewport: Viewport = null;
@@ -18,6 +19,7 @@ export class GlobalContext {
1819
private speedMultiplier: SpeedMultiplier;
1920
private saveIntervalId: NodeJS.Timeout | null = null;
2021
private ipGenerator: IpAddressGenerator;
22+
private macGenerator: MacAddressGenerator;
2123

2224
constructor(viewport: Viewport) {
2325
this.viewport = viewport;
@@ -26,12 +28,17 @@ export class GlobalContext {
2628
loadFromLocalStorage(this);
2729

2830
this.setIpGenerator();
31+
this.setMacGenerator();
2932
}
3033

3134
getNextIp(): { ip: string; mask: string } {
3235
return this.ipGenerator.getNextIp();
3336
}
3437

38+
getNextMac(): string {
39+
return this.macGenerator.getNextMac();
40+
}
41+
3542
private setNetwork(datagraph: DataGraph, layer: Layer) {
3643
this.datagraph = datagraph;
3744
this.viewport.clear();
@@ -40,6 +47,7 @@ export class GlobalContext {
4047
}
4148
this.viewgraph = new ViewGraph(this.datagraph, this, layer);
4249
this.setIpGenerator();
50+
this.setMacGenerator();
4351
}
4452

4553
private setSpeedMultiplier(speedMultiplier: SpeedMultiplier) {
@@ -126,9 +134,11 @@ export class GlobalContext {
126134
private setIpGenerator() {
127135
let maxIp = IpAddress.parse("10.0.0.0");
128136
this.datagraph.getDevices().forEach((device) => {
129-
const ip = IpAddress.parse(device.ip);
130-
if (maxIp.octets < ip.octets) {
131-
maxIp = ip;
137+
if (isNetworkNode(device)) {
138+
const ip = IpAddress.parse(device.ip);
139+
if (maxIp.octets < ip.octets) {
140+
maxIp = ip;
141+
}
132142
}
133143
});
134144
// TODO: we should use IpAddress instead of string here and in Datagraph
@@ -137,6 +147,21 @@ export class GlobalContext {
137147
this.ipGenerator = new IpAddressGenerator(baseIp, mask);
138148
}
139149

150+
private setMacGenerator() {
151+
let maxMac = MacAddress.parse("00:00:10:00:00:00");
152+
this.datagraph.getDevices().forEach((device) => {
153+
if (isLinkNode(device)) {
154+
const mac = MacAddress.parse(device.mac);
155+
if (maxMac.octets < mac.octets) {
156+
maxMac = mac;
157+
}
158+
}
159+
});
160+
// TODO: we should use MacAddress instead of string here and in Datagraph
161+
const baseMac = maxMac.toString();
162+
this.macGenerator = new MacAddressGenerator(baseMac);
163+
}
164+
140165
print() {
141166
console.log("VieGraph:");
142167
console.log(this.viewgraph);

src/graphics/renderables/device_info.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,9 @@ export class DeviceInfo extends StyledInfo {
2323
}
2424

2525
private addCommonInfoFields() {
26-
const { id, connections } = this.device;
26+
const { id, connections, mac } = this.device;
2727
super.addField("ID", id.toString());
28+
super.addField("MacAddress", mac.toString());
2829
super.addListField("Connected Devices", Array.from(connections.values()));
2930
}
3031

src/packets/ethernet.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { CRC32 } from "@tsxper/crc32";
2+
import { Layer } from "../types/devices/layer";
23

34
// From https://en.wikipedia.org/wiki/EtherType
45
export const IP_PROTOCOL_TYPE = 0x0800;
@@ -43,6 +44,35 @@ export class MacAddress {
4344
}
4445
}
4546

47+
export class MacAddressGenerator {
48+
private baseMac: bigint;
49+
private currentMac: bigint;
50+
51+
constructor(baseMac: string) {
52+
this.baseMac = MacAddressGenerator.macToNumber(baseMac);
53+
this.currentMac = this.baseMac + BigInt(1); // Start on first valid MAC
54+
}
55+
56+
// Generate next valid IP
57+
getNextMac(): string {
58+
const nextMac = MacAddressGenerator.numberToMac(this.currentMac);
59+
this.currentMac++;
60+
return nextMac;
61+
}
62+
63+
// Turn MAC into a number
64+
static macToNumber(mac: string): bigint {
65+
// leaves an hexadecimal string of 6 bytes, then parse it to bigint
66+
return BigInt("0x" + mac.replace(/:/g, ""));
67+
}
68+
69+
// Turn number into IP
70+
static numberToMac(num: bigint): string {
71+
const match = num.toString(16).padStart(12, "0").match(/.{2}/g);
72+
return match ? match.join(":") : "";
73+
}
74+
}
75+
4676
const crc32 = new CRC32();
4777

4878
const MINIMUM_PAYLOAD_SIZE = 46;
@@ -114,9 +144,25 @@ export class EthernetFrame {
114144
...checksum,
115145
]);
116146
}
147+
148+
getDetails(layer: Layer) {
149+
if (layer == Layer.Link) {
150+
return {
151+
"Destination MAC": this.destination.toString(),
152+
"Source MAC": this.source.toString(),
153+
EtherType: this.type.toString(),
154+
};
155+
} else {
156+
return this.payload.getDetails(layer);
157+
}
158+
}
117159
}
118160

119161
export interface FramePayload {
162+
// The bytes equivalent of the payload
120163
toBytes(): Uint8Array;
164+
// The number of the protocol
121165
type(): number;
166+
// Get details of the payload
167+
getDetails(layer: Layer): Record<string, string | number | object>;
122168
}

src/packets/icmp.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ import { Layer } from "../types/devices/layer";
44
const ICMP_WARNING =
55
"ICMP operates directly on top of IP at the Network layer, bypassing the Transport layer (TCP/UDP). This is because ICMP is primarily used for network diagnostics and error reporting, not for end-to-end data transport.";
66

7+
export const ICMP_REQUEST_TYPE_NUMBER = 8;
8+
export const ICMP_REPLY_TYPE_NUMBER = 0;
9+
710
// More info in RFC-792
811
// 0 1 2 3
912
// 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
@@ -111,9 +114,9 @@ class EchoMessage extends IcmpPacket {
111114
}
112115

113116
export class EchoRequest extends EchoMessage {
114-
type = 8;
117+
type = ICMP_REQUEST_TYPE_NUMBER;
115118
}
116119

117120
export class EchoReply extends EchoMessage {
118-
type = 0;
121+
type = ICMP_REPLY_TYPE_NUMBER;
119122
}

src/packets/ip.ts

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -270,14 +270,6 @@ export class IPv4Packet implements FramePayload {
270270
// Desired Implementation:
271271
// - Move frame-specific data to EthernetFrame class
272272
// - Implement packet sending using MAC addresses at device level
273-
if (layer == Layer.Link) {
274-
return {
275-
"Ethernet Header": "---",
276-
"Destination MAC": "---",
277-
"Source MAC": "---",
278-
EtherType: "0x0800",
279-
};
280-
}
281273

282274
if (layer == Layer.Network) {
283275
return {

src/programs/echo_sender.ts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import { ViewGraph } from "../types/graphs/viewgraph";
66
import { ProgramInfo } from "../graphics/renderables/device_info";
77
import { EchoRequest } from "../packets/icmp";
88
import { IPv4Packet } from "../packets/ip";
9+
import { NetworkDevice } from "../types/devices";
10+
import { EthernetFrame } from "../packets/ethernet";
911

1012
function adjacentDevices(viewgraph: ViewGraph, srcId: DeviceId) {
1113
const adjacentDevices = viewgraph
@@ -46,9 +48,32 @@ export class SingleEcho extends ProgramBase {
4648
console.error("Destination device not found");
4749
return;
4850
}
51+
if (
52+
!(
53+
srcDevice instanceof NetworkDevice && dstDevice instanceof NetworkDevice
54+
)
55+
) {
56+
console.log(
57+
"At least one device between source and destination is not a network device",
58+
);
59+
return;
60+
}
4961
const echoRequest = new EchoRequest(0);
5062
const ipPacket = new IPv4Packet(srcDevice.ip, dstDevice.ip, echoRequest);
51-
sendRawPacket(this.viewgraph, this.srcId, ipPacket);
63+
const path = this.viewgraph.getPathBetween(this.srcId, this.dstId);
64+
let dstMac = dstDevice.mac;
65+
if (!path) return;
66+
console.log(path);
67+
for (const id of path.slice(1)) {
68+
const device = this.viewgraph.getDevice(id);
69+
// if there’s a router in the middle, first send frame to router mac
70+
if (device instanceof NetworkDevice) {
71+
dstMac = device.mac;
72+
break;
73+
}
74+
}
75+
const ethernetFrame = new EthernetFrame(srcDevice.mac, dstMac, ipPacket);
76+
sendRawPacket(this.viewgraph, this.srcId, this.dstId, ethernetFrame);
5277
}
5378

5479
static getProgramInfo(viewgraph: ViewGraph, srcId: DeviceId): ProgramInfo {

src/types/devices/device.ts

Lines changed: 15 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,13 @@ import { RightBar } from "../../graphics/right_bar";
1818
import { Colors, ZIndexLevels } from "../../utils";
1919
import { Position } from "../common";
2020
import { DeviceInfo } from "../../graphics/renderables/device_info";
21-
import { IpAddress, IPv4Packet } from "../../packets/ip";
21+
import { IpAddress } from "../../packets/ip";
2222
import { DeviceId } from "../graphs/datagraph";
2323
import { DragDeviceMove, AddEdgeMove } from "../undo-redo";
2424
import { Layer } from "./layer";
25-
import { Packet, sendRawPacket } from "../packet";
26-
import { EchoReply } from "../../packets/icmp";
25+
import { Packet } from "../packet";
2726
import { CreateDevice } from "./utils";
27+
import { MacAddress } from "../../packets/ethernet";
2828

2929
export { Layer } from "./layer";
3030

@@ -52,6 +52,9 @@ export abstract class Device extends Container {
5252
readonly viewgraph: ViewGraph;
5353
connections = new Set<DeviceId>();
5454

55+
mac: MacAddress;
56+
arpTable: Map<IpAddress, MacAddress> = new Map<IpAddress, MacAddress>();
57+
5558
highlightMarker: Graphics | null = null; // Marker to indicate selection
5659

5760
static dragTarget: Device | null = null;
@@ -65,28 +68,27 @@ export abstract class Device extends Container {
6568
return this.sprite.height;
6669
}
6770

68-
ip: IpAddress;
69-
ipMask: IpAddress;
70-
71-
// Each type of device has different ways of handling a received packet.
72-
// Returns the DevicedId for the next device to send the packet to, or
73-
// null if there’s no next device to send the packet.
71+
/**
72+
* Each type of device has different ways of handling a received packet.
73+
* Returns the id for the next device to send the packet to, or
74+
* null if there’s no next device to send the packet.
75+
* */
76+
// TODO: Might be general for all device in the future.
7477
abstract receivePacket(packet: Packet): Promise<DeviceId | null>;
7578

7679
constructor(
7780
id: DeviceId,
7881
texture: Texture,
7982
viewgraph: ViewGraph,
8083
position: Position,
81-
ip: IpAddress,
82-
ipMask: IpAddress,
84+
mac: MacAddress,
8385
) {
8486
super();
8587

8688
this.id = id;
8789
this.viewgraph = viewgraph;
88-
this.ip = ip;
89-
this.ipMask = ipMask;
90+
91+
this.mac = mac;
9092

9193
this.sprite = new Sprite(texture);
9294

@@ -144,25 +146,6 @@ export abstract class Device extends Container {
144146
this.connections.delete(id);
145147
}
146148

147-
// TODO: Most probably it will be different for each type of device
148-
handlePacket(packet: Packet) {
149-
switch (packet.type) {
150-
case "ICMP-8": {
151-
const dstDevice = this.viewgraph.getDeviceByIP(
152-
packet.rawPacket.sourceAddress,
153-
);
154-
if (dstDevice) {
155-
const echoReply = new EchoReply(0);
156-
const ipPacket = new IPv4Packet(this.ip, dstDevice.ip, echoReply);
157-
sendRawPacket(this.viewgraph, this.id, ipPacket);
158-
}
159-
break;
160-
}
161-
default:
162-
console.warn("Packet's type unrecognized");
163-
}
164-
}
165-
166149
delete(): void {
167150
this.viewgraph.removeDevice(this.id);
168151
console.log(`Device ${this.id} deleted`);

src/types/devices/host.ts

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
import { Device, DeviceType } from "./device";
1+
import { DeviceType } from "./device";
2+
import { NetworkDevice } from "./networkDevice";
23
import { ViewGraph } from "../graphs/viewgraph";
34
import PcImage from "../../assets/pc.svg";
45
import { Position } from "../common";
5-
import { IpAddress } from "../../packets/ip";
6+
import { IpAddress, IPv4Packet } from "../../packets/ip";
67
import { DeviceInfo, RightBar } from "../../graphics/right_bar";
78
import { DeviceId } from "../graphs/datagraph";
89
import { Layer } from "./layer";
@@ -16,8 +17,9 @@ import {
1617
} from "../../programs";
1718
import { Packet } from "../packet";
1819
import { Texture } from "pixi.js";
20+
import { MacAddress } from "../../packets/ethernet";
1921

20-
export class Host extends Device {
22+
export class Host extends NetworkDevice {
2123
static DEVICE_TEXTURE: Texture;
2224

2325
static getTexture() {
@@ -34,13 +36,25 @@ export class Host extends Device {
3436
id: DeviceId,
3537
viewgraph: ViewGraph,
3638
position: Position,
39+
mac: MacAddress,
3740
ip: IpAddress,
3841
mask: IpAddress,
3942
) {
40-
super(id, Host.getTexture(), viewgraph, position, ip, mask);
43+
super(id, Host.getTexture(), viewgraph, position, mac, ip, mask);
4144
this.loadRunningPrograms();
4245
}
4346

47+
receiveDatagram(packet: Packet): Promise<DeviceId | null> {
48+
const datagram = packet.rawPacket.payload;
49+
if (!(datagram instanceof IPv4Packet)) {
50+
return null;
51+
}
52+
if (this.ip.equals(datagram.destinationAddress)) {
53+
this.handlePacket(datagram);
54+
}
55+
return null;
56+
}
57+
4458
showInfo(): void {
4559
const programList = getProgramList(this.viewgraph, this.id);
4660

@@ -58,13 +72,6 @@ export class Host extends Device {
5872
return DeviceType.Host;
5973
}
6074

61-
async receivePacket(packet: Packet): Promise<DeviceId | null> {
62-
if (this.ip.equals(packet.rawPacket.destinationAddress)) {
63-
this.handlePacket(packet);
64-
}
65-
return null;
66-
}
67-
6875
addRunningProgram(name: string, inputs: string[]) {
6976
const pid = this.getNextPid();
7077
const runningProgram = { pid, name, inputs };

0 commit comments

Comments
 (0)