Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 36 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,16 @@ build/test-evloop-tun: $(OBJ) build/test/test_eventloop_tun.o build/port/posix/l
@echo "[LD] $@"
@$(CC) $(CFLAGS) -o $@ $(BEGIN_GROUP) $(^) $(LDFLAGS) $(END_GROUP)

build/test-multicast-interop: CFLAGS+=-DIP_MULTICAST
build/test-multicast-interop: build/multicast/wolfip.o build/test/test_multicast_interop.o build/port/posix/tap_linux.o
@echo "[LD] $@"
@$(CC) $(CFLAGS) -o $@ $(BEGIN_GROUP) $(^) $(LDFLAGS) $(END_GROUP)

build/multicast/wolfip.o: src/wolfip.c
@mkdir -p `dirname $@` || true
@echo "[CC] $< (multicast)"
@$(CC) $(CFLAGS) -DIP_MULTICAST -c $< -o $@

build/test-dns: $(OBJ) build/test/test_dhcp_dns.o
@echo "[LD] $@"
@$(CC) $(CFLAGS) -o $@ $(BEGIN_GROUP) $(^) $(LDFLAGS) $(END_GROUP)
Expand Down Expand Up @@ -374,7 +384,8 @@ UNIT_TEST_SRCS:=src/test/unit/unit.c \
src/test/unit/unit_tests_dns_dhcp.c \
src/test/unit/unit_tests_tcp_ack.c \
src/test/unit/unit_tests_tcp_flow.c \
src/test/unit/unit_tests_proto.c
src/test/unit/unit_tests_proto.c \
src/test/unit/unit_tests_multicast.c

unit: build/test/unit

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

unit-multicast: CFLAGS+=-DIP_MULTICAST
unit-multicast: clean-unit unit

ESP_UNIT_CHECK_CFLAGS := $(CHECK_PKG_CFLAGS)
ifeq ($(UNAME_S),Darwin)
ifneq ($(CHECK_PREFIX),)
Expand Down Expand Up @@ -445,6 +459,8 @@ unit-leaksan: clean-unit build/test/unit
COV_DIR:=build/coverage
COV_UNIT:=$(COV_DIR)/unit
COV_UNIT_O:=$(COV_DIR)/unit.o
COV_MCAST_UNIT:=$(COV_DIR)/unit-multicast
COV_MCAST_UNIT_O:=$(COV_DIR)/unit-multicast.o

$(COV_UNIT_O): $(UNIT_TEST_SRCS)
@mkdir -p $(COV_DIR)
Expand All @@ -456,6 +472,16 @@ $(COV_UNIT): $(COV_UNIT_O)
@echo "[LD] $@"
@$(CC) $(COV_UNIT_O) -o $(COV_UNIT) $(UNIT_LDFLAGS) $(LDFLAGS)

$(COV_MCAST_UNIT_O): $(UNIT_TEST_SRCS)
@mkdir -p $(COV_DIR)
@echo "[CC] unit.c (multicast coverage)"
@$(CC) $(UNIT_CFLAGS) $(CFLAGS) -DIP_MULTICAST --coverage -c src/test/unit/unit.c -o $(COV_MCAST_UNIT_O)

$(COV_MCAST_UNIT): LDFLAGS+=--coverage $(UNIT_LIBS)
$(COV_MCAST_UNIT): $(COV_MCAST_UNIT_O)
@echo "[LD] $@"
@$(CC) $(COV_MCAST_UNIT_O) -o $(COV_MCAST_UNIT) $(UNIT_LDFLAGS) $(LDFLAGS)

cov: unit $(COV_UNIT)
@echo "[RUN] unit (coverage)"
@rm -f $(COV_DIR)/*.gcda
Expand All @@ -473,6 +499,14 @@ autocov: unit $(COV_UNIT)
@mkdir -p build/coverage
@gcovr -r . --exclude "src/test/unit/.*" --html-details -o build/coverage/index.html

autocov-multicast: unit-multicast $(COV_MCAST_UNIT)
@echo "[RUN] unit multicast (coverage)"
@rm -f $(COV_DIR)/*.gcda
@$(COV_MCAST_UNIT)
@echo "[COV] gcovr multicast html"
@mkdir -p build/coverage
@gcovr -r . --exclude "src/test/unit/.*" --html-details -o build/coverage/multicast.html

# Install dynamic library to re-link linux applications
#
install:
Expand Down Expand Up @@ -571,7 +605,7 @@ build/test/test-wolfguard-interop: src/test/test_wolfguard_interop.c src/port/po
clean-test-wolfguard-interop:
@rm -f build/test/test-wolfguard-interop build/test/test_wolfguard_interop.o build/test/linux_tun.o

.PHONY: clean all static cppcheck cov autocov unit-asan unit-ubsan unit-leaksan clean-unit \
.PHONY: clean all static cppcheck cov autocov autocov-multicast unit-multicast unit-asan unit-ubsan unit-leaksan clean-unit \
unit-esp-asan unit-esp-ubsan unit-esp-leaksan clean-unit-esp \
unit-wolfguard unit-wolfguard-asan unit-wolfguard-ubsan clean-unit-wolfguard \
test-wolfguard-loopback test-wolfguard-loopback-asan test-wolfguard-loopback-ubsan \
Expand Down
30 changes: 29 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ configured to forward traffic between multiple network interfaces.
- Pre-allocated buffers for packet processing in static memory
- Multi-interface support
- Optional IPv4-forwarding
- Optional IPv4 UDP multicast with IGMPv3 ASM membership reports

## Supported socket types

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

## Optional UDP Multicast

IPv4 UDP multicast is compiled out by default. Define `IP_MULTICAST` to enable
BSD-style multicast socket options and IGMPv3 ASM membership reports:

- `WOLFIP_IP_ADD_MEMBERSHIP`
- `WOLFIP_IP_DROP_MEMBERSHIP`
- `WOLFIP_IP_MULTICAST_IF`
- `WOLFIP_IP_MULTICAST_TTL`
- `WOLFIP_IP_MULTICAST_LOOP`

The implementation supports any-source multicast joins and leaves. Source
filter APIs such as `MCAST_JOIN_SOURCE_GROUP` are not implemented.

```sh
make unit-multicast
./build/test/unit

make build/test-multicast-interop
sudo ./build/test-multicast-interop
```

The multicast interop test creates a Linux TAP interface (`wmcast0`) and
validates both directions: Linux sending to a wolfIP multicast receiver, and
wolfIP sending to a Linux multicast receiver.

## FreeRTOS Port

wolfIP now includes a dedicated FreeRTOS wrapper port at:
Expand Down
2 changes: 1 addition & 1 deletion docs/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ wolfIP is a minimal TCP/IP stack designed for resource-constrained embedded syst
- ICMP (RFC 792) - ping replies only
- DHCP (RFC 2131) - client only
- DNS (RFC 1035) - client only
- UDP (RFC 768) - unicast only
- UDP (RFC 768) - unicast, optional IPv4 multicast with `IP_MULTICAST`
- TCP (RFC 793) with options (Timestamps, MSS)

## Core Data Structures
Expand Down
219 changes: 219 additions & 0 deletions src/test/test_multicast_interop.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
/* test_multicast_interop.c
*
* Linux TAP interop smoke tests for wolfIP IPv4 UDP multicast.
*/
#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <stdio.h>
#include <string.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <unistd.h>

#include "config.h"
#include "wolfip.h"

#ifndef IP_MULTICAST
#error "test_multicast_interop requires IP_MULTICAST"
#endif

#define MCAST_GROUP "239.1.2.9"
#define MCAST_PORT 19009
#define WOLFIP_MCAST_PORT 19010

extern int tap_init(struct wolfIP_ll_dev *dev, const char *name, uint32_t host_ip);

static uint64_t now_ms(void)
{
struct timeval tv;

gettimeofday(&tv, NULL);
return (uint64_t)tv.tv_sec * 1000U + (uint64_t)tv.tv_usec / 1000U;
}

static void poll_stack_for(struct wolfIP *s, unsigned int ms)
{
uint64_t end = now_ms() + ms;

while (now_ms() < end) {
(void)wolfIP_poll(s, now_ms());
usleep(1000);
}
}

static int host_udp_socket(void)
{
int fd = socket(AF_INET, SOCK_DGRAM, 0);
int one = 1;

if (fd < 0) {
perror("socket");
return -1;
}
(void)setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
return fd;
}

static int test_host_to_wolfip(struct wolfIP *s, uint32_t host_ip)
{
int host_fd;
int wolf_fd;
struct sockaddr_in host_if;
struct sockaddr_in dst;
struct wolfIP_sockaddr_in bind_addr;
struct wolfIP_ip_mreq mreq;
char rx[32];
const char payload[] = "linux-to-wolfip";
unsigned int i;

wolf_fd = wolfIP_sock_socket(s, AF_INET, IPSTACK_SOCK_DGRAM, 17);
if (wolf_fd < 0)
return -1;
memset(&bind_addr, 0, sizeof(bind_addr));
bind_addr.sin_family = AF_INET;
bind_addr.sin_port = htons(MCAST_PORT);
bind_addr.sin_addr.s_addr = 0;
if (wolfIP_sock_bind(s, wolf_fd, (struct wolfIP_sockaddr *)&bind_addr,
sizeof(bind_addr)) < 0)
return -1;
memset(&mreq, 0, sizeof(mreq));
inet_pton(AF_INET, MCAST_GROUP, &mreq.imr_multiaddr.s_addr);
mreq.imr_interface.s_addr = htonl(INADDR_ANY);
if (wolfIP_sock_setsockopt(s, wolf_fd, WOLFIP_SOL_IP,
WOLFIP_IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0)
return -1;

host_fd = host_udp_socket();
if (host_fd < 0)
return -1;
memset(&host_if, 0, sizeof(host_if));
host_if.sin_addr.s_addr = host_ip;
if (setsockopt(host_fd, IPPROTO_IP, IP_MULTICAST_IF,
&host_if.sin_addr, sizeof(host_if.sin_addr)) < 0) {
perror("setsockopt IP_MULTICAST_IF");
close(host_fd);
return -1;
}
memset(&dst, 0, sizeof(dst));
dst.sin_family = AF_INET;
dst.sin_port = htons(MCAST_PORT);
inet_pton(AF_INET, MCAST_GROUP, &dst.sin_addr);

if (sendto(host_fd, payload, sizeof(payload), 0,
(struct sockaddr *)&dst, sizeof(dst)) != (ssize_t)sizeof(payload)) {
perror("sendto host multicast");
close(host_fd);
return -1;
}
for (i = 0; i < 1000; i++) {
int ret;

(void)wolfIP_poll(s, now_ms());
ret = wolfIP_sock_recvfrom(s, wolf_fd, rx, sizeof(rx), 0, NULL, NULL);
if (ret == (int)sizeof(payload) && memcmp(rx, payload, sizeof(payload)) == 0) {
close(host_fd);
return 0;
}
usleep(1000);
}
close(host_fd);
fprintf(stderr, "wolfIP did not receive Linux multicast payload\n");
return -1;
}

static int test_wolfip_to_host(struct wolfIP *s, uint32_t host_ip)
{
int host_fd;
int wolf_fd;
int ttl = 3;
struct sockaddr_in bind_addr;
struct ip_mreq host_mreq;
struct wolfIP_sockaddr_in dst;
fd_set rfds;
struct timeval tv;
char rx[32];
const char payload[] = "wolfip-to-linux";

host_fd = host_udp_socket();
if (host_fd < 0)
return -1;
memset(&bind_addr, 0, sizeof(bind_addr));
bind_addr.sin_family = AF_INET;
bind_addr.sin_addr.s_addr = htonl(INADDR_ANY);
bind_addr.sin_port = htons(WOLFIP_MCAST_PORT);
if (bind(host_fd, (struct sockaddr *)&bind_addr, sizeof(bind_addr)) < 0) {
perror("bind host multicast");
close(host_fd);
return -1;
}
memset(&host_mreq, 0, sizeof(host_mreq));
inet_pton(AF_INET, MCAST_GROUP, &host_mreq.imr_multiaddr);
host_mreq.imr_interface.s_addr = host_ip;
if (setsockopt(host_fd, IPPROTO_IP, IP_ADD_MEMBERSHIP,
&host_mreq, sizeof(host_mreq)) < 0) {
perror("host IP_ADD_MEMBERSHIP");
close(host_fd);
return -1;
}

wolf_fd = wolfIP_sock_socket(s, AF_INET, IPSTACK_SOCK_DGRAM, 17);
if (wolf_fd < 0)
return -1;
if (wolfIP_sock_setsockopt(s, wolf_fd, WOLFIP_SOL_IP,
WOLFIP_IP_MULTICAST_TTL, &ttl, sizeof(ttl)) < 0)
return -1;
memset(&dst, 0, sizeof(dst));
dst.sin_family = AF_INET;
dst.sin_port = htons(WOLFIP_MCAST_PORT);
inet_pton(AF_INET, MCAST_GROUP, &dst.sin_addr.s_addr);
if (wolfIP_sock_sendto(s, wolf_fd, payload, sizeof(payload), 0,
(struct wolfIP_sockaddr *)&dst, sizeof(dst)) != (int)sizeof(payload))
return -1;
poll_stack_for(s, 50);

FD_ZERO(&rfds);
FD_SET(host_fd, &rfds);
tv.tv_sec = 2;
tv.tv_usec = 0;
if (select(host_fd + 1, &rfds, NULL, NULL, &tv) <= 0) {
fprintf(stderr, "Linux did not receive wolfIP multicast payload\n");
close(host_fd);
return -1;
}
if (recv(host_fd, rx, sizeof(rx), 0) != (ssize_t)sizeof(payload) ||
memcmp(rx, payload, sizeof(payload)) != 0) {
fprintf(stderr, "Linux received unexpected multicast payload\n");
close(host_fd);
return -1;
}
close(host_fd);
return 0;
}

int main(void)
{
struct wolfIP *s;
struct wolfIP_ll_dev *tapdev;
struct in_addr host;

wolfIP_init_static(&s);
tapdev = wolfIP_getdev(s);
if (!tapdev)
return 1;
inet_aton(HOST_STACK_IP, &host);
if (tap_init(tapdev, "wmcast0", host.s_addr) < 0)
return 2;
wolfIP_ipconfig_set(s, atoip4(WOLFIP_IP), atoip4("255.255.255.0"),
atoip4(HOST_STACK_IP));
poll_stack_for(s, 50);

if (test_host_to_wolfip(s, host.s_addr) < 0)
return 3;
if (test_wolfip_to_host(s, host.s_addr) < 0)
return 4;
printf("multicast interop ok\n");
return 0;
}
8 changes: 8 additions & 0 deletions src/test/unit/unit.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include "unit_tests_tcp_ack.c"
#include "unit_tests_tcp_flow.c"
#include "unit_tests_proto.c"
#include "unit_tests_multicast.c"

Suite *wolf_suite(void)
{
Expand Down Expand Up @@ -227,6 +228,13 @@ Suite *wolf_suite(void)
tcase_add_test(tc_utils, test_udp_no_icmp_unreachable_for_multicast_src);
tcase_add_test(tc_utils, test_udp_no_icmp_unreachable_for_broadcast_dst);
tcase_add_test(tc_utils, test_udp_no_icmp_unreachable_for_multicast_dst);
#ifdef IP_MULTICAST
tcase_add_test(tc_utils, test_multicast_join_and_drop_reports);
tcase_add_test(tc_utils, test_multicast_join_validation_and_shared_refs);
tcase_add_test(tc_utils, test_multicast_udp_receive_requires_join);
tcase_add_test(tc_utils, test_multicast_udp_send_mac_ttl_loop_and_options);
tcase_add_test(tc_utils, test_multicast_igmp_query_refreshes_report);
#endif
tcase_add_test(tc_utils, test_tcp_no_rst_for_broadcast_dst);
tcase_add_test(tc_utils, test_tcp_no_rst_for_multicast_dst);
tcase_add_test(tc_utils, test_dhcp_renewing_transitions_to_rebinding);
Expand Down
Loading
Loading