Skip to content

Commit a3bdc25

Browse files
authored
refactor: route packets according to interface in connection (#166)
This PR continues #157 by moving routing logic to use the new interface fields. It also changes the way packets flow, making devices rewrap packets into frames and send them with `sendRawPacket`, instead of telling the `Packet` object where to go. This should simplify how we do broadcasts.
1 parent 2176e22 commit a3bdc25

File tree

8 files changed

+180
-144
lines changed

8 files changed

+180
-144
lines changed

src/programs/echo_sender.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ export class SingleEcho extends ProgramBase {
7373
}
7474
}
7575
const ethernetFrame = new EthernetFrame(srcDevice.mac, dstMac, ipPacket);
76-
sendRawPacket(this.viewgraph, this.srcId, this.dstId, ethernetFrame);
76+
sendRawPacket(this.viewgraph, this.srcId, ethernetFrame);
7777
}
7878

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

src/types/devices/device.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,7 @@ import { IpAddress } from "../../packets/ip";
2222
import { DeviceId, RemovedNodeData } from "../graphs/datagraph";
2323
import { DragDeviceMove, AddEdgeMove } from "../undo-redo";
2424
import { Layer } from "./layer";
25-
import { Packet } from "../packet";
26-
import { MacAddress } from "../../packets/ethernet";
25+
import { EthernetFrame, MacAddress } from "../../packets/ethernet";
2726
import { GlobalContext } from "../../context";
2827

2928
export { Layer } from "./layer";
@@ -73,8 +72,7 @@ export abstract class Device extends Container {
7372
* Returns the id for the next device to send the packet to, or
7473
* null if there’s no next device to send the packet.
7574
* */
76-
// TODO: Might be general for all device in the future.
77-
abstract receivePacket(packet: Packet): Promise<DeviceId | null>;
75+
abstract receiveFrame(frame: EthernetFrame): void;
7876

7977
constructor(
8078
id: DeviceId,

src/types/devices/host.ts

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import {
1515
Program,
1616
RunningProgram,
1717
} from "../../programs";
18-
import { Packet } from "../packet";
1918
import { Texture } from "pixi.js";
2019
import { MacAddress } from "../../packets/ethernet";
2120
import { GlobalContext } from "../../context";
@@ -46,13 +45,9 @@ export class Host extends NetworkDevice {
4645
this.loadRunningPrograms();
4746
}
4847

49-
receiveDatagram(packet: Packet): Promise<DeviceId | null> {
50-
const datagram = packet.rawPacket.payload;
51-
if (!(datagram instanceof IPv4Packet)) {
52-
return null;
53-
}
54-
if (this.ip.equals(datagram.destinationAddress)) {
55-
this.handlePacket(datagram);
48+
receiveDatagram(packet: IPv4Packet): Promise<DeviceId | null> {
49+
if (this.ip.equals(packet.destinationAddress)) {
50+
this.handlePacket(packet);
5651
}
5752
return null;
5853
}

src/types/devices/networkDevice.ts

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { Device } from "./device";
55
import { ViewGraph } from "../graphs/viewgraph";
66
import { Position } from "../common";
77
import { EthernetFrame, MacAddress } from "../../packets/ethernet";
8-
import { Packet, sendRawPacket } from "../packet";
8+
import { sendRawPacket } from "../packet";
99
import { EchoReply, EchoRequest } from "../../packets/icmp";
1010
import { GlobalContext } from "../../context";
1111

@@ -28,7 +28,7 @@ export abstract class NetworkDevice extends Device {
2828
this.ipMask = ipMask;
2929
}
3030

31-
abstract receiveDatagram(packet: Packet): Promise<DeviceId | null>;
31+
abstract receiveDatagram(packet: IPv4Packet): void;
3232

3333
// TODO: Most probably it will be different for each type of device
3434
handlePacket(datagram: IPv4Packet) {
@@ -54,7 +54,7 @@ export abstract class NetworkDevice extends Device {
5454
const echoReply = new EchoReply(0);
5555
const ipPacket = new IPv4Packet(this.ip, dstDevice.ip, echoReply);
5656
const ethernet = new EthernetFrame(this.mac, dstMac, ipPacket);
57-
sendRawPacket(this.viewgraph, this.id, dstDevice.id, ethernet);
57+
sendRawPacket(this.viewgraph, this.id, ethernet);
5858
}
5959
break;
6060
}
@@ -63,11 +63,15 @@ export abstract class NetworkDevice extends Device {
6363
}
6464
}
6565

66-
async receivePacket(packet: Packet): Promise<DeviceId | null> {
67-
const frame = packet.rawPacket;
68-
if (this.mac.equals(frame.destination)) {
69-
return this.receiveDatagram(packet);
66+
receiveFrame(frame: EthernetFrame): void {
67+
if (!this.mac.equals(frame.destination)) {
68+
return;
69+
}
70+
if (!(frame.payload instanceof IPv4Packet)) {
71+
console.error("Packet's type not IPv4");
72+
return;
7073
}
71-
return null;
74+
const datagram = frame.payload;
75+
this.receiveDatagram(datagram);
7276
}
7377
}

src/types/devices/router.ts

Lines changed: 80 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,22 @@ import { Position } from "../common";
66
import { DeviceInfo, RightBar } from "../../graphics/right_bar";
77
import { IpAddress, IPv4Packet } from "../../packets/ip";
88
import { DeviceId, isRouter } from "../graphs/datagraph";
9-
import { Texture } from "pixi.js";
10-
import { MacAddress } from "../../packets/ethernet";
11-
import { Packet } from "../packet";
9+
import { Texture, Ticker } from "pixi.js";
10+
import { EthernetFrame, MacAddress } from "../../packets/ethernet";
1211
import { GlobalContext } from "../../context";
12+
import { sendRawPacket } from "../packet";
1313

1414
export class Router extends NetworkDevice {
1515
static DEVICE_TEXTURE: Texture;
1616

17-
private packetQueueSize = 0;
17+
private processingPackets = false;
18+
private processingProgress = 0;
19+
// Time in ms to process a single packet
20+
private timePerPacket = 250;
21+
22+
private packetQueue: IPv4Packet[] = [];
23+
// TODO: we should count this in bytes
1824
private maxQueueSize = 5;
19-
private timePerPacket = 1000;
2025

2126
static getTexture() {
2227
if (!Router.DEVICE_TEXTURE) {
@@ -55,60 +60,90 @@ export class Router extends NetworkDevice {
5560
return DeviceType.Router;
5661
}
5762

58-
async routePacket(datagram: IPv4Packet): Promise<DeviceId | null> {
59-
const device = this.viewgraph.getDataGraph().getDevice(this.id);
60-
if (!device || !isRouter(device)) {
61-
return null;
63+
receiveDatagram(datagram: IPv4Packet) {
64+
if (this.ip.equals(datagram.destinationAddress)) {
65+
this.handlePacket(datagram);
66+
return;
6267
}
63-
if (this.packetQueueSize >= this.maxQueueSize) {
68+
this.addPacketToQueue(datagram);
69+
}
70+
71+
addPacketToQueue(datagram: IPv4Packet) {
72+
if (this.packetQueue.length >= this.maxQueueSize) {
6473
console.debug("Packet queue full, dropping packet");
65-
return null;
74+
return;
75+
}
76+
this.packetQueue.push(datagram);
77+
// Start packet processor if not already running
78+
if (!this.processingPackets) {
79+
Ticker.shared.add(this.processPacket, this);
80+
this.processingPackets = true;
81+
}
82+
}
83+
84+
processPacket(ticker: Ticker) {
85+
this.processingProgress += ticker.deltaMS;
86+
if (this.processingProgress < this.timePerPacket) {
87+
return;
88+
}
89+
this.processingProgress -= this.timePerPacket;
90+
const datagram = this.packetQueue.pop();
91+
const devices = this.routePacket(datagram);
92+
93+
if (!devices || devices.length === 0) {
94+
return;
95+
}
96+
for (const nextHopId of devices) {
97+
// Wrap the datagram in a new frame
98+
const nextHop = this.viewgraph.getDevice(nextHopId);
99+
if (!nextHop) {
100+
console.error("Next hop not found");
101+
continue;
102+
}
103+
const newFrame = new EthernetFrame(this.mac, nextHop.mac, datagram);
104+
sendRawPacket(this.viewgraph, this.id, newFrame);
105+
}
106+
107+
// Stop processing packets if queue is empty
108+
if (this.packetQueue.length === 0) {
109+
Ticker.shared.remove(this.processPacket, this);
110+
this.processingPackets = false;
111+
this.processingProgress = 0;
112+
return;
113+
}
114+
}
115+
116+
routePacket(datagram: IPv4Packet): DeviceId[] {
117+
const device = this.viewgraph.getDataGraph().getDevice(this.id);
118+
if (!device || !isRouter(device)) {
119+
return;
66120
}
67-
this.packetQueueSize += 1;
68-
console.debug("Processing packet, queue size:", this.packetQueueSize);
69-
await new Promise((resolve) => setTimeout(resolve, this.timePerPacket));
70-
this.packetQueueSize -= 1;
71121

72122
const result = device.routingTable.find((entry) => {
73123
if (entry.deleted) {
74-
console.log("Skipping deleted entry:", entry);
124+
console.debug("Skipping deleted entry:", entry);
75125
return false;
76126
}
77127
const ip = IpAddress.parse(entry.ip);
78128
const mask = IpAddress.parse(entry.mask);
79-
console.log("Considering entry:", entry);
129+
console.debug("Considering entry:", entry);
80130
return datagram.destinationAddress.isInSubnet(ip, mask);
81131
});
82-
console.log("Result:", result);
83-
return result === undefined ? null : result.iface;
84-
}
85132

86-
async receiveDatagram(packet: Packet): Promise<DeviceId | null> {
87-
const datagram = packet.rawPacket.payload;
88-
if (!(datagram instanceof IPv4Packet)) {
89-
return null;
133+
if (!result) {
134+
console.warn("No route found for", datagram.destinationAddress);
135+
return [];
90136
}
91-
if (this.ip.equals(datagram.destinationAddress)) {
92-
this.handlePacket(datagram);
93-
return null;
94-
}
95-
// a router changed forward datagram to destination, have to change current destination mac
96-
const dstDevice = this.viewgraph.getDeviceByIP(datagram.destinationAddress);
97-
if (!dstDevice) {
98-
console.error("Destination device not found");
99-
return null;
100-
}
101-
const path = this.viewgraph.getPathBetween(this.id, dstDevice.id);
102-
let dstMac = dstDevice.mac;
103-
if (!path) return null;
104-
for (const id of path.slice(1)) {
105-
const device = this.viewgraph.getDevice(id);
106-
if (device instanceof NetworkDevice) {
107-
dstMac = device.mac;
108-
break;
109-
}
137+
138+
const devices = this.viewgraph
139+
.getDataGraph()
140+
.getConnectionsInInterface(this.id, result.iface);
141+
142+
if (!devices) {
143+
console.error("Current device doesn't exist!", this.id);
144+
return [];
110145
}
111-
packet.rawPacket.destination = dstMac;
112-
return this.routePacket(datagram);
146+
147+
return devices;
113148
}
114149
}

src/types/devices/switch.ts

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ import SwitchImage from "../../assets/switch.svg";
44
import { Position } from "../common";
55
import { DeviceInfo, RightBar } from "../../graphics/right_bar";
66
import { DeviceId } from "../graphs/datagraph";
7-
import { Packet } from "../packet";
87
import { Texture } from "pixi.js";
9-
import { MacAddress } from "../../packets/ethernet";
8+
import { EthernetFrame, MacAddress } from "../../packets/ethernet";
109
import { IPv4Packet } from "../../packets/ip";
1110
import { GlobalContext } from "../../context";
11+
import { sendRawPacket } from "../packet";
1212

1313
export class Switch extends Device {
1414
static DEVICE_TEXTURE: Texture;
@@ -44,19 +44,31 @@ export class Switch extends Device {
4444
return DeviceType.Switch;
4545
}
4646

47-
receivePacket(packet: Packet): Promise<DeviceId | null> {
48-
const datagram = packet.rawPacket.payload;
49-
if (datagram instanceof IPv4Packet) {
50-
const dstDevice = this.viewgraph.getDeviceByIP(
51-
datagram.destinationAddress,
52-
);
53-
if (!dstDevice) {
54-
console.error("Destination device not found");
55-
return null;
56-
}
57-
const path = this.viewgraph.getPathBetween(this.id, dstDevice.id);
58-
return Promise.resolve(path.length > 1 ? path[1] : null);
47+
receiveFrame(frame: EthernetFrame) {
48+
const datagram = frame.payload;
49+
if (!(datagram instanceof IPv4Packet)) {
50+
return;
5951
}
60-
return null;
52+
// TODO: this should add the sender to the switching table,
53+
// try to match the packet against it to find a receiver,
54+
// and broadcast it if no receiver is found
55+
const dstDevice = this.viewgraph.getDeviceByIP(datagram.destinationAddress);
56+
if (!dstDevice) {
57+
console.error("Destination device not found");
58+
return;
59+
}
60+
const path = this.viewgraph.getPathBetween(this.id, dstDevice.id);
61+
if (path.length < 2) {
62+
console.error("Destination device is not reachable");
63+
return;
64+
}
65+
const nextHopId = path[1];
66+
const nextHop = this.viewgraph.getDevice(nextHopId);
67+
if (!nextHop) {
68+
console.error("Next hop not found");
69+
return;
70+
}
71+
const newFrame = new EthernetFrame(this.mac, nextHop.mac, datagram);
72+
sendRawPacket(this.viewgraph, this.id, newFrame);
6173
}
6274
}

src/types/graphs/datagraph.ts

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -105,8 +105,7 @@ interface SwitchDataNode extends LinkDataNode {
105105

106106
interface EdgeTip {
107107
id: DeviceId;
108-
// TODO: uncomment this
109-
// iface: number;
108+
iface: number;
110109
}
111110

112111
interface GraphEdge {
@@ -233,7 +232,10 @@ export class DataGraph {
233232
);
234233
return;
235234
}
236-
const edge = { from: { id: n1Id }, to: { id: n2Id } };
235+
const edge = {
236+
from: { id: n1Id, iface: n2Id },
237+
to: { id: n2Id, iface: n1Id },
238+
};
237239
this.deviceGraph.setEdge(n1Id, n2Id, edge);
238240

239241
console.log(
@@ -288,6 +290,26 @@ export class DataGraph {
288290
return this.deviceGraph.getNeighbors(id);
289291
}
290292

293+
// Get all connections of a device in a given interface
294+
getConnectionsInInterface(
295+
id: DeviceId,
296+
iface: number,
297+
): DeviceId[] | undefined {
298+
if (!this.deviceGraph.hasVertex(id)) {
299+
return;
300+
}
301+
const connections = [];
302+
for (const [neighborId, { from, to }] of this.deviceGraph.getEdges(id)) {
303+
if (
304+
(from.id === id && from.iface === iface) ||
305+
(to.id === id && to.iface === iface)
306+
) {
307+
connections.push(neighborId);
308+
}
309+
}
310+
return connections;
311+
}
312+
291313
// Method to remove a device and all its connections
292314
removeDevice(id: DeviceId): RemovedNodeData | undefined {
293315
if (!this.deviceGraph.hasVertex(id)) {

0 commit comments

Comments
 (0)