Skip to content

Commit 4abab85

Browse files
committed
Add multicast UDP sockets
1 parent 7280c2d commit 4abab85

8 files changed

Lines changed: 1118 additions & 7 deletions

File tree

Makefile

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,16 @@ build/test-evloop-tun: $(OBJ) build/test/test_eventloop_tun.o build/port/posix/l
242242
@echo "[LD] $@"
243243
@$(CC) $(CFLAGS) -o $@ $(BEGIN_GROUP) $(^) $(LDFLAGS) $(END_GROUP)
244244

245+
build/test-multicast-interop: CFLAGS+=-DIP_MULTICAST
246+
build/test-multicast-interop: build/multicast/wolfip.o build/test/test_multicast_interop.o build/port/posix/tap_linux.o
247+
@echo "[LD] $@"
248+
@$(CC) $(CFLAGS) -o $@ $(BEGIN_GROUP) $(^) $(LDFLAGS) $(END_GROUP)
249+
250+
build/multicast/wolfip.o: src/wolfip.c
251+
@mkdir -p `dirname $@` || true
252+
@echo "[CC] $< (multicast)"
253+
@$(CC) $(CFLAGS) -DIP_MULTICAST -c $< -o $@
254+
245255
build/test-dns: $(OBJ) build/test/test_dhcp_dns.o
246256
@echo "[LD] $@"
247257
@$(CC) $(CFLAGS) -o $@ $(BEGIN_GROUP) $(^) $(LDFLAGS) $(END_GROUP)
@@ -374,7 +384,8 @@ UNIT_TEST_SRCS:=src/test/unit/unit.c \
374384
src/test/unit/unit_tests_dns_dhcp.c \
375385
src/test/unit/unit_tests_tcp_ack.c \
376386
src/test/unit/unit_tests_tcp_flow.c \
377-
src/test/unit/unit_tests_proto.c
387+
src/test/unit/unit_tests_proto.c \
388+
src/test/unit/unit_tests_multicast.c
378389

379390
unit: build/test/unit
380391

@@ -385,6 +396,9 @@ build/test/unit: $(UNIT_TEST_SRCS)
385396
@echo "[LD] $@"
386397
@$(CC) build/test/unit.o -o build/test/unit $(UNIT_LDFLAGS) $(LDFLAGS)
387398

399+
unit-multicast: CFLAGS+=-DIP_MULTICAST
400+
unit-multicast: clean-unit unit
401+
388402
ESP_UNIT_CHECK_CFLAGS := $(CHECK_PKG_CFLAGS)
389403
ifeq ($(UNAME_S),Darwin)
390404
ifneq ($(CHECK_PREFIX),)
@@ -445,6 +459,8 @@ unit-leaksan: clean-unit build/test/unit
445459
COV_DIR:=build/coverage
446460
COV_UNIT:=$(COV_DIR)/unit
447461
COV_UNIT_O:=$(COV_DIR)/unit.o
462+
COV_MCAST_UNIT:=$(COV_DIR)/unit-multicast
463+
COV_MCAST_UNIT_O:=$(COV_DIR)/unit-multicast.o
448464

449465
$(COV_UNIT_O): $(UNIT_TEST_SRCS)
450466
@mkdir -p $(COV_DIR)
@@ -456,6 +472,16 @@ $(COV_UNIT): $(COV_UNIT_O)
456472
@echo "[LD] $@"
457473
@$(CC) $(COV_UNIT_O) -o $(COV_UNIT) $(UNIT_LDFLAGS) $(LDFLAGS)
458474

475+
$(COV_MCAST_UNIT_O): $(UNIT_TEST_SRCS)
476+
@mkdir -p $(COV_DIR)
477+
@echo "[CC] unit.c (multicast coverage)"
478+
@$(CC) $(UNIT_CFLAGS) $(CFLAGS) -DIP_MULTICAST --coverage -c src/test/unit/unit.c -o $(COV_MCAST_UNIT_O)
479+
480+
$(COV_MCAST_UNIT): LDFLAGS+=--coverage $(UNIT_LIBS)
481+
$(COV_MCAST_UNIT): $(COV_MCAST_UNIT_O)
482+
@echo "[LD] $@"
483+
@$(CC) $(COV_MCAST_UNIT_O) -o $(COV_MCAST_UNIT) $(UNIT_LDFLAGS) $(LDFLAGS)
484+
459485
cov: unit $(COV_UNIT)
460486
@echo "[RUN] unit (coverage)"
461487
@rm -f $(COV_DIR)/*.gcda
@@ -473,6 +499,14 @@ autocov: unit $(COV_UNIT)
473499
@mkdir -p build/coverage
474500
@gcovr -r . --exclude "src/test/unit/.*" --html-details -o build/coverage/index.html
475501

502+
autocov-multicast: unit-multicast $(COV_MCAST_UNIT)
503+
@echo "[RUN] unit multicast (coverage)"
504+
@rm -f $(COV_DIR)/*.gcda
505+
@$(COV_MCAST_UNIT)
506+
@echo "[COV] gcovr multicast html"
507+
@mkdir -p build/coverage
508+
@gcovr -r . --exclude "src/test/unit/.*" --html-details -o build/coverage/multicast.html
509+
476510
# Install dynamic library to re-link linux applications
477511
#
478512
install:
@@ -571,7 +605,7 @@ build/test/test-wolfguard-interop: src/test/test_wolfguard_interop.c src/port/po
571605
clean-test-wolfguard-interop:
572606
@rm -f build/test/test-wolfguard-interop build/test/test_wolfguard_interop.o build/test/linux_tun.o
573607

574-
.PHONY: clean all static cppcheck cov autocov unit-asan unit-ubsan unit-leaksan clean-unit \
608+
.PHONY: clean all static cppcheck cov autocov autocov-multicast unit-multicast unit-asan unit-ubsan unit-leaksan clean-unit \
575609
unit-esp-asan unit-esp-ubsan unit-esp-leaksan clean-unit-esp \
576610
unit-wolfguard unit-wolfguard-asan unit-wolfguard-ubsan clean-unit-wolfguard \
577611
test-wolfguard-loopback test-wolfguard-loopback-asan test-wolfguard-loopback-ubsan \

README.md

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ configured to forward traffic between multiple network interfaces.
1717
- Pre-allocated buffers for packet processing in static memory
1818
- Multi-interface support
1919
- Optional IPv4-forwarding
20+
- Optional IPv4 UDP multicast with IGMPv3 ASM membership reports
2021

2122
## Supported socket types
2223

@@ -39,8 +40,9 @@ wolfIP exposes a BSD-like `socket(2)` API for IPv4 sockets:
3940
| **Network** | IPv4 | Datagram delivery, TTL handling | [RFC 791](https://datatracker.ietf.org/doc/html/rfc791) |
4041
| **Network** | IPv4 Forwarding | Multi-interface routing (optional) | [RFC 1812](https://datatracker.ietf.org/doc/html/rfc1812) |
4142
| **Network** | ICMP | Echo request/reply, TTL exceeded | [RFC 792](https://datatracker.ietf.org/doc/html/rfc792) |
43+
| **Network** | IGMPv3 | ASM membership reports for IPv4 multicast (optional) | [RFC 3376](https://datatracker.ietf.org/doc/html/rfc3376) |
4244
| **Network** | IPsec | ESP Transport mode | [RFC 4303](https://datatracker.ietf.org/doc/html/rfc4303) |
43-
| **Transport** | UDP | Unicast datagrams, checksum | [RFC 768](https://datatracker.ietf.org/doc/html/rfc768) |
45+
| **Transport** | UDP | Unicast datagrams, checksum, optional IPv4 multicast | [RFC 768](https://datatracker.ietf.org/doc/html/rfc768) |
4446
| **Transport** | TCP | Connection management, reliable delivery | [RFC 793](https://datatracker.ietf.org/doc/html/rfc793), [RFC 9293](https://datatracker.ietf.org/doc/html/rfc9293) |
4547
| **Transport** | TCP | Maximum Segment Size negotiation | [RFC 793](https://datatracker.ietf.org/doc/html/rfc793) |
4648
| **Transport** | TCP | TCP Timestamps, RTT measurement, PAWS, Window Scaling | [RFC 7323](https://datatracker.ietf.org/doc/html/rfc7323) |
@@ -139,6 +141,32 @@ The `-I wtcp0` flag pins the test to the injected interface and `-c5`
139141
generates five echo requests. Successful replies confirm the ICMP
140142
datagram socket support end-to-end through the tap device.
141143

144+
## Optional UDP Multicast
145+
146+
IPv4 UDP multicast is compiled out by default. Define `IP_MULTICAST` to enable
147+
BSD-style multicast socket options and IGMPv3 ASM membership reports:
148+
149+
- `WOLFIP_IP_ADD_MEMBERSHIP`
150+
- `WOLFIP_IP_DROP_MEMBERSHIP`
151+
- `WOLFIP_IP_MULTICAST_IF`
152+
- `WOLFIP_IP_MULTICAST_TTL`
153+
- `WOLFIP_IP_MULTICAST_LOOP`
154+
155+
The implementation supports any-source multicast joins and leaves. Source
156+
filter APIs such as `MCAST_JOIN_SOURCE_GROUP` are not implemented.
157+
158+
```sh
159+
make unit-multicast
160+
./build/test/unit
161+
162+
make build/test-multicast-interop
163+
sudo ./build/test-multicast-interop
164+
```
165+
166+
The multicast interop test creates a Linux TAP interface (`wmcast0`) and
167+
validates both directions: Linux sending to a wolfIP multicast receiver, and
168+
wolfIP sending to a Linux multicast receiver.
169+
142170
## FreeRTOS Port
143171

144172
wolfIP now includes a dedicated FreeRTOS wrapper port at:

docs/API.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ wolfIP is a minimal TCP/IP stack designed for resource-constrained embedded syst
1616
- ICMP (RFC 792) - ping replies only
1717
- DHCP (RFC 2131) - client only
1818
- DNS (RFC 1035) - client only
19-
- UDP (RFC 768) - unicast only
19+
- UDP (RFC 768) - unicast, optional IPv4 multicast with `IP_MULTICAST`
2020
- TCP (RFC 793) with options (Timestamps, MSS)
2121

2222
## Core Data Structures

src/test/test_multicast_interop.c

Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
/* test_multicast_interop.c
2+
*
3+
* Linux TAP interop smoke tests for wolfIP IPv4 UDP multicast.
4+
*/
5+
#include <arpa/inet.h>
6+
#include <errno.h>
7+
#include <fcntl.h>
8+
#include <netinet/in.h>
9+
#include <stdio.h>
10+
#include <string.h>
11+
#include <sys/select.h>
12+
#include <sys/socket.h>
13+
#include <sys/time.h>
14+
#include <unistd.h>
15+
16+
#include "config.h"
17+
#include "wolfip.h"
18+
19+
#ifndef IP_MULTICAST
20+
#error "test_multicast_interop requires IP_MULTICAST"
21+
#endif
22+
23+
#define MCAST_GROUP "239.1.2.9"
24+
#define MCAST_PORT 19009
25+
#define WOLFIP_MCAST_PORT 19010
26+
27+
extern int tap_init(struct wolfIP_ll_dev *dev, const char *name, uint32_t host_ip);
28+
29+
static uint64_t now_ms(void)
30+
{
31+
struct timeval tv;
32+
33+
gettimeofday(&tv, NULL);
34+
return (uint64_t)tv.tv_sec * 1000U + (uint64_t)tv.tv_usec / 1000U;
35+
}
36+
37+
static void poll_stack_for(struct wolfIP *s, unsigned int ms)
38+
{
39+
uint64_t end = now_ms() + ms;
40+
41+
while (now_ms() < end) {
42+
(void)wolfIP_poll(s, now_ms());
43+
usleep(1000);
44+
}
45+
}
46+
47+
static int host_udp_socket(void)
48+
{
49+
int fd = socket(AF_INET, SOCK_DGRAM, 0);
50+
int one = 1;
51+
52+
if (fd < 0) {
53+
perror("socket");
54+
return -1;
55+
}
56+
(void)setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
57+
return fd;
58+
}
59+
60+
static int test_host_to_wolfip(struct wolfIP *s, uint32_t host_ip)
61+
{
62+
int host_fd;
63+
int wolf_fd;
64+
struct sockaddr_in host_if;
65+
struct sockaddr_in dst;
66+
struct wolfIP_sockaddr_in bind_addr;
67+
struct wolfIP_ip_mreq mreq;
68+
char rx[32];
69+
const char payload[] = "linux-to-wolfip";
70+
unsigned int i;
71+
72+
wolf_fd = wolfIP_sock_socket(s, AF_INET, IPSTACK_SOCK_DGRAM, 17);
73+
if (wolf_fd < 0)
74+
return -1;
75+
memset(&bind_addr, 0, sizeof(bind_addr));
76+
bind_addr.sin_family = AF_INET;
77+
bind_addr.sin_port = htons(MCAST_PORT);
78+
bind_addr.sin_addr.s_addr = 0;
79+
if (wolfIP_sock_bind(s, wolf_fd, (struct wolfIP_sockaddr *)&bind_addr,
80+
sizeof(bind_addr)) < 0)
81+
return -1;
82+
memset(&mreq, 0, sizeof(mreq));
83+
inet_pton(AF_INET, MCAST_GROUP, &mreq.imr_multiaddr.s_addr);
84+
mreq.imr_interface.s_addr = htonl(INADDR_ANY);
85+
if (wolfIP_sock_setsockopt(s, wolf_fd, WOLFIP_SOL_IP,
86+
WOLFIP_IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0)
87+
return -1;
88+
89+
host_fd = host_udp_socket();
90+
if (host_fd < 0)
91+
return -1;
92+
memset(&host_if, 0, sizeof(host_if));
93+
host_if.sin_addr.s_addr = host_ip;
94+
if (setsockopt(host_fd, IPPROTO_IP, IP_MULTICAST_IF,
95+
&host_if.sin_addr, sizeof(host_if.sin_addr)) < 0) {
96+
perror("setsockopt IP_MULTICAST_IF");
97+
close(host_fd);
98+
return -1;
99+
}
100+
memset(&dst, 0, sizeof(dst));
101+
dst.sin_family = AF_INET;
102+
dst.sin_port = htons(MCAST_PORT);
103+
inet_pton(AF_INET, MCAST_GROUP, &dst.sin_addr);
104+
105+
if (sendto(host_fd, payload, sizeof(payload), 0,
106+
(struct sockaddr *)&dst, sizeof(dst)) != (ssize_t)sizeof(payload)) {
107+
perror("sendto host multicast");
108+
close(host_fd);
109+
return -1;
110+
}
111+
for (i = 0; i < 1000; i++) {
112+
int ret;
113+
114+
(void)wolfIP_poll(s, now_ms());
115+
ret = wolfIP_sock_recvfrom(s, wolf_fd, rx, sizeof(rx), 0, NULL, NULL);
116+
if (ret == (int)sizeof(payload) && memcmp(rx, payload, sizeof(payload)) == 0) {
117+
close(host_fd);
118+
return 0;
119+
}
120+
usleep(1000);
121+
}
122+
close(host_fd);
123+
fprintf(stderr, "wolfIP did not receive Linux multicast payload\n");
124+
return -1;
125+
}
126+
127+
static int test_wolfip_to_host(struct wolfIP *s, uint32_t host_ip)
128+
{
129+
int host_fd;
130+
int wolf_fd;
131+
int ttl = 3;
132+
struct sockaddr_in bind_addr;
133+
struct ip_mreq host_mreq;
134+
struct wolfIP_sockaddr_in dst;
135+
fd_set rfds;
136+
struct timeval tv;
137+
char rx[32];
138+
const char payload[] = "wolfip-to-linux";
139+
140+
host_fd = host_udp_socket();
141+
if (host_fd < 0)
142+
return -1;
143+
memset(&bind_addr, 0, sizeof(bind_addr));
144+
bind_addr.sin_family = AF_INET;
145+
bind_addr.sin_addr.s_addr = htonl(INADDR_ANY);
146+
bind_addr.sin_port = htons(WOLFIP_MCAST_PORT);
147+
if (bind(host_fd, (struct sockaddr *)&bind_addr, sizeof(bind_addr)) < 0) {
148+
perror("bind host multicast");
149+
close(host_fd);
150+
return -1;
151+
}
152+
memset(&host_mreq, 0, sizeof(host_mreq));
153+
inet_pton(AF_INET, MCAST_GROUP, &host_mreq.imr_multiaddr);
154+
host_mreq.imr_interface.s_addr = host_ip;
155+
if (setsockopt(host_fd, IPPROTO_IP, IP_ADD_MEMBERSHIP,
156+
&host_mreq, sizeof(host_mreq)) < 0) {
157+
perror("host IP_ADD_MEMBERSHIP");
158+
close(host_fd);
159+
return -1;
160+
}
161+
162+
wolf_fd = wolfIP_sock_socket(s, AF_INET, IPSTACK_SOCK_DGRAM, 17);
163+
if (wolf_fd < 0)
164+
return -1;
165+
if (wolfIP_sock_setsockopt(s, wolf_fd, WOLFIP_SOL_IP,
166+
WOLFIP_IP_MULTICAST_TTL, &ttl, sizeof(ttl)) < 0)
167+
return -1;
168+
memset(&dst, 0, sizeof(dst));
169+
dst.sin_family = AF_INET;
170+
dst.sin_port = htons(WOLFIP_MCAST_PORT);
171+
inet_pton(AF_INET, MCAST_GROUP, &dst.sin_addr.s_addr);
172+
if (wolfIP_sock_sendto(s, wolf_fd, payload, sizeof(payload), 0,
173+
(struct wolfIP_sockaddr *)&dst, sizeof(dst)) != (int)sizeof(payload))
174+
return -1;
175+
poll_stack_for(s, 50);
176+
177+
FD_ZERO(&rfds);
178+
FD_SET(host_fd, &rfds);
179+
tv.tv_sec = 2;
180+
tv.tv_usec = 0;
181+
if (select(host_fd + 1, &rfds, NULL, NULL, &tv) <= 0) {
182+
fprintf(stderr, "Linux did not receive wolfIP multicast payload\n");
183+
close(host_fd);
184+
return -1;
185+
}
186+
if (recv(host_fd, rx, sizeof(rx), 0) != (ssize_t)sizeof(payload) ||
187+
memcmp(rx, payload, sizeof(payload)) != 0) {
188+
fprintf(stderr, "Linux received unexpected multicast payload\n");
189+
close(host_fd);
190+
return -1;
191+
}
192+
close(host_fd);
193+
return 0;
194+
}
195+
196+
int main(void)
197+
{
198+
struct wolfIP *s;
199+
struct wolfIP_ll_dev *tapdev;
200+
struct in_addr host;
201+
202+
wolfIP_init_static(&s);
203+
tapdev = wolfIP_getdev(s);
204+
if (!tapdev)
205+
return 1;
206+
inet_aton(HOST_STACK_IP, &host);
207+
if (tap_init(tapdev, "wmcast0", host.s_addr) < 0)
208+
return 2;
209+
wolfIP_ipconfig_set(s, atoip4(WOLFIP_IP), atoip4("255.255.255.0"),
210+
atoip4(HOST_STACK_IP));
211+
poll_stack_for(s, 50);
212+
213+
if (test_host_to_wolfip(s, host.s_addr) < 0)
214+
return 3;
215+
if (test_wolfip_to_host(s, host.s_addr) < 0)
216+
return 4;
217+
printf("multicast interop ok\n");
218+
return 0;
219+
}

src/test/unit/unit.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include "unit_tests_tcp_ack.c"
66
#include "unit_tests_tcp_flow.c"
77
#include "unit_tests_proto.c"
8+
#include "unit_tests_multicast.c"
89

910
Suite *wolf_suite(void)
1011
{
@@ -227,6 +228,13 @@ Suite *wolf_suite(void)
227228
tcase_add_test(tc_utils, test_udp_no_icmp_unreachable_for_multicast_src);
228229
tcase_add_test(tc_utils, test_udp_no_icmp_unreachable_for_broadcast_dst);
229230
tcase_add_test(tc_utils, test_udp_no_icmp_unreachable_for_multicast_dst);
231+
#ifdef IP_MULTICAST
232+
tcase_add_test(tc_utils, test_multicast_join_and_drop_reports);
233+
tcase_add_test(tc_utils, test_multicast_join_validation_and_shared_refs);
234+
tcase_add_test(tc_utils, test_multicast_udp_receive_requires_join);
235+
tcase_add_test(tc_utils, test_multicast_udp_send_mac_ttl_loop_and_options);
236+
tcase_add_test(tc_utils, test_multicast_igmp_query_refreshes_report);
237+
#endif
230238
tcase_add_test(tc_utils, test_tcp_no_rst_for_broadcast_dst);
231239
tcase_add_test(tc_utils, test_tcp_no_rst_for_multicast_dst);
232240
tcase_add_test(tc_utils, test_dhcp_renewing_transitions_to_rebinding);

0 commit comments

Comments
 (0)