Skip to content

Commit 65916e5

Browse files
authored
Merge pull request #20 from chipmk/fix/broadcast-multiple-interfaces
Fixes UDP broadcast on multiple interfaces
2 parents ddad6cf + 4ebbf19 commit 65916e5

File tree

2 files changed

+270
-16
lines changed

2 files changed

+270
-16
lines changed

packages/tcpip/src/stack.test.ts

Lines changed: 252 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -342,7 +342,7 @@ describe('bridge interface', () => {
342342
}
343343
});
344344

345-
test('bridge interface itself can send and receive frames', async () => {
345+
test('bridge interface can receive tcp packets', async () => {
346346
// Create a network of two devices connected to a router
347347
const device = await createStack();
348348
const router = await createStack();
@@ -392,6 +392,185 @@ describe('bridge interface', () => {
392392
}
393393
});
394394

395+
test('bridge interface can send tcp packets', async () => {
396+
const device = await createStack();
397+
const router = await createStack();
398+
399+
const deviceTap = await device.createTapInterface({
400+
ip: '192.168.1.2/24',
401+
});
402+
403+
const port = await router.createTapInterface();
404+
405+
// Create bridge and connect device
406+
await router.createBridgeInterface({
407+
ports: [port],
408+
ip: '192.168.1.1/24',
409+
});
410+
411+
// Connect device to router port
412+
deviceTap.readable.pipeTo(port.writable);
413+
port.readable.pipeTo(deviceTap.writable);
414+
415+
// Listen on device
416+
const listener = await device.listenTcp({
417+
port: 8080,
418+
});
419+
420+
// Connect from router bridge to device
421+
const connection = await router.connectTcp({
422+
host: '192.168.1.2',
423+
port: 8080,
424+
});
425+
426+
// Write data to confirm communication
427+
const outboundWriter = connection.writable.getWriter();
428+
const data = new Uint8Array([0x01, 0x02, 0x03, 0x04]);
429+
outboundWriter.write(data);
430+
431+
for await (const inbound of listener) {
432+
const reader = inbound.readable.getReader();
433+
const received = await reader.read();
434+
435+
if (received.done) {
436+
throw new Error('expected value');
437+
}
438+
439+
expect(received.value).toStrictEqual(data);
440+
break;
441+
}
442+
});
443+
444+
test('bridge interface can send and receive udp datagrams', async () => {
445+
const device = await createStack();
446+
const router = await createStack();
447+
448+
const deviceTap = await device.createTapInterface({
449+
ip: '192.168.1.2/24',
450+
});
451+
452+
const port = await router.createTapInterface();
453+
454+
// Create bridge
455+
await router.createBridgeInterface({
456+
ports: [port],
457+
ip: '192.168.1.1/24',
458+
});
459+
460+
// Connect device to port
461+
deviceTap.readable.pipeTo(port.writable);
462+
port.readable.pipeTo(deviceTap.writable);
463+
464+
// Open UDP sockets
465+
const deviceSocket = await device.openUdp({ port: 8080 });
466+
const routerSocket = await router.openUdp({ port: 8080 });
467+
468+
// Test device to router
469+
{
470+
const reader = routerSocket.readable.getReader();
471+
const writer = deviceSocket.writable.getWriter();
472+
473+
const data = new Uint8Array([0x01, 0x02, 0x03, 0x04]);
474+
await writer.write({ host: '192.168.1.1', port: 8080, data });
475+
476+
const received = await reader.read();
477+
if (received.done) {
478+
throw new Error('expected value');
479+
}
480+
481+
expect(received.value.host).toBe('192.168.1.2');
482+
expect(received.value.port).toBe(8080);
483+
expect(received.value.data).toStrictEqual(data);
484+
}
485+
486+
// Test router to device
487+
{
488+
const reader = deviceSocket.readable.getReader();
489+
const writer = routerSocket.writable.getWriter();
490+
491+
const data = new Uint8Array([0x04, 0x03, 0x02, 0x01]);
492+
await writer.write({ host: '192.168.1.2', port: 8080, data });
493+
494+
const received = await reader.read();
495+
if (received.done) {
496+
throw new Error('expected value');
497+
}
498+
499+
expect(received.value.host).toBe('192.168.1.1');
500+
expect(received.value.port).toBe(8080);
501+
expect(received.value.data).toStrictEqual(data);
502+
}
503+
});
504+
505+
test('bridge interface can send and receive broadcast udp datagrams', async () => {
506+
const device = await createStack();
507+
const router = await createStack();
508+
509+
const deviceTap = await device.createTapInterface({
510+
ip: '192.168.1.2/24',
511+
});
512+
513+
const port = await router.createTapInterface();
514+
515+
// Create bridge
516+
await router.createBridgeInterface({
517+
ports: [port],
518+
ip: '192.168.1.1/24',
519+
});
520+
521+
// Connect device to port
522+
deviceTap.readable.pipeTo(port.writable);
523+
port.readable.pipeTo(deviceTap.writable);
524+
525+
// Open UDP sockets
526+
const deviceSocket = await device.openUdp({ port: 8080 });
527+
const routerSocket = await router.openUdp({ port: 8081 });
528+
529+
// Test router to device broadcast
530+
{
531+
const deviceReader = deviceSocket.readable.getReader();
532+
const routerWriter = routerSocket.writable.getWriter();
533+
534+
const data = new Uint8Array([0x01, 0x02, 0x03, 0x04]);
535+
await routerWriter.write({
536+
host: '255.255.255.255',
537+
port: 8080,
538+
data,
539+
});
540+
541+
const received = await deviceReader.read();
542+
if (received.done) {
543+
throw new Error('expected value');
544+
}
545+
546+
expect(received.value.host).toBe('192.168.1.1');
547+
expect(received.value.port).toBe(8081);
548+
expect(received.value.data).toStrictEqual(data);
549+
}
550+
551+
// Test device to router broadcast
552+
{
553+
const routerReader = routerSocket.readable.getReader();
554+
const deviceWriter = deviceSocket.writable.getWriter();
555+
556+
const data = new Uint8Array([0x04, 0x03, 0x02, 0x01]);
557+
await deviceWriter.write({
558+
host: '255.255.255.255',
559+
port: 8081,
560+
data,
561+
});
562+
563+
const received = await routerReader.read();
564+
if (received.done) {
565+
throw new Error('expected value');
566+
}
567+
568+
expect(received.value.host).toBe('192.168.1.2');
569+
expect(received.value.port).toBe(8080);
570+
expect(received.value.data).toStrictEqual(data);
571+
}
572+
});
573+
395574
test('can get mac, ip, and netmask', async () => {
396575
const stack = await createStack();
397576

@@ -854,6 +1033,78 @@ describe('udp', () => {
8541033
expect(parsedPacket.payload.destinationPort).toBe(8080);
8551034
expect(parsedPacket.payload.payload).toStrictEqual(data);
8561035
});
1036+
1037+
test('udp broadcast can be sent over multiple interfaces', async () => {
1038+
const stack = await createStack();
1039+
1040+
// Create two interfaces
1041+
const tap1 = await stack.createTapInterface({
1042+
ip: '192.168.1.1/24',
1043+
mac: '00:1a:2b:3c:4d:01',
1044+
});
1045+
1046+
const tap2 = await stack.createTapInterface({
1047+
ip: '192.168.2.1/24',
1048+
mac: '00:1a:2b:3c:4d:02',
1049+
});
1050+
1051+
// Listen on both interfaces
1052+
const tap1Listener = tap1.listen();
1053+
const tap2Listener = tap2.listen();
1054+
1055+
// Create a socket for broadcasting
1056+
const socket = await stack.openUdp({ port: 8080 });
1057+
const writer = socket.writable.getWriter();
1058+
1059+
// Send broadcast
1060+
const data = new Uint8Array([0x01, 0x02, 0x03, 0x04]);
1061+
await writer.write({
1062+
host: '255.255.255.255',
1063+
port: 8080,
1064+
data,
1065+
});
1066+
1067+
// Check that both interfaces received the broadcast
1068+
const received1 = await waitFor(tap1Listener, (frame) => {
1069+
const parsedFrame = parseEthernetFrame(frame);
1070+
return parsedFrame.type === 'ipv4';
1071+
});
1072+
1073+
const received2 = await waitFor(tap2Listener, (frame) => {
1074+
const parsedFrame = parseEthernetFrame(frame);
1075+
return parsedFrame.type === 'ipv4';
1076+
});
1077+
1078+
// Verify the first frame
1079+
const parsedFrame1 = parseEthernetFrame(received1);
1080+
if (parsedFrame1.type !== 'ipv4') {
1081+
throw new Error('expected ipv4 packet');
1082+
}
1083+
const parsedPacket1 = parsedFrame1.payload;
1084+
if (parsedPacket1.protocol !== 'udp') {
1085+
throw new Error('expected udp packet');
1086+
}
1087+
expect(parsedPacket1.sourceIP).toBe('192.168.1.1');
1088+
expect(parsedPacket1.destinationIP).toBe('255.255.255.255');
1089+
expect(parsedPacket1.payload.sourcePort).toBe(8080);
1090+
expect(parsedPacket1.payload.destinationPort).toBe(8080);
1091+
expect(parsedPacket1.payload.payload).toStrictEqual(data);
1092+
1093+
// Verify the second frame
1094+
const parsedFrame2 = parseEthernetFrame(received2);
1095+
if (parsedFrame2.type !== 'ipv4') {
1096+
throw new Error('expected ipv4 packet');
1097+
}
1098+
const parsedPacket2 = parsedFrame2.payload;
1099+
if (parsedPacket2.protocol !== 'udp') {
1100+
throw new Error('expected udp packet');
1101+
}
1102+
expect(parsedPacket2.sourceIP).toBe('192.168.2.1');
1103+
expect(parsedPacket2.destinationIP).toBe('255.255.255.255');
1104+
expect(parsedPacket2.payload.sourcePort).toBe(8080);
1105+
expect(parsedPacket2.payload.destinationPort).toBe(8080);
1106+
expect(parsedPacket2.payload.payload).toStrictEqual(data);
1107+
});
8571108
});
8581109

8591110
describe('dns', () => {

packages/tcpip/wasm/udp.c

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,38 +11,41 @@ extern void receive_udp_datagram(struct udp_pcb *socket, const uint8_t *addr, ui
1111

1212
EXPORT("send_udp_datagram")
1313
err_t send_udp_datagram(struct udp_pcb *socket, const uint8_t *addr, uint16_t port, uint8_t *datagram, uint16_t length) {
14-
struct pbuf *p = pbuf_alloc(PBUF_TRANSPORT, length, PBUF_RAM);
15-
if (p == NULL) {
16-
return ERR_MEM;
17-
}
18-
pbuf_take(p, datagram, length);
19-
2014
ip4_addr_t ipaddr;
2115
IP4_ADDR(&ipaddr, addr[0], addr[1], addr[2], addr[3]);
2216

2317
err_t code = ERR_OK;
2418

2519
// If the destination IP is the limited broadcast address (255.255.255.255),
26-
// send on all interfaces that are up and have the broadcast flag set
20+
// send on all interfaces that are up, support ARP, and have the broadcast flag set
2721
if (ipaddr.addr == PP_HTONL(IPADDR_BROADCAST)) {
2822
struct netif *netif;
23+
2924
NETIF_FOREACH(netif) {
30-
if (netif_is_up(netif) && netif_is_flag_set(netif, NETIF_FLAG_BROADCAST)) {
31-
code = udp_sendto_if(socket, p, IP_ADDR_BROADCAST, port, netif);
32-
if (code != ERR_OK) {
33-
pbuf_free(p);
34-
return code;
25+
if (netif_is_up(netif) && netif_is_flag_set(netif, NETIF_FLAG_ETHARP) && netif_is_flag_set(netif, NETIF_FLAG_BROADCAST)) {
26+
struct pbuf *p = pbuf_alloc(PBUF_TRANSPORT, length, PBUF_RAM);
27+
if (p == NULL) {
28+
return ERR_MEM;
3529
}
30+
pbuf_take(p, datagram, length);
31+
err_t err = udp_sendto_if(socket, p, IP_ADDR_BROADCAST, port, netif);
32+
pbuf_free(p);
3633
}
3734
}
35+
36+
return ERR_OK;
3837
}
3938
// Otherwise, send to the specified IP address using lwIP's automatic routing
4039
else {
40+
struct pbuf *p = pbuf_alloc(PBUF_TRANSPORT, length, PBUF_RAM);
41+
if (p == NULL) {
42+
return ERR_MEM;
43+
}
44+
pbuf_take(p, datagram, length);
4145
code = udp_sendto(socket, p, &ipaddr, port);
46+
pbuf_free(p);
47+
return code;
4248
}
43-
44-
pbuf_free(p);
45-
return code;
4649
}
4750

4851
EXPORT("close_udp_socket")

0 commit comments

Comments
 (0)