From b582fc3831168712ecf7b07b1ece91bcbee53091 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Wed, 15 Apr 2026 12:49:34 +0200 Subject: [PATCH 1/8] Fix loopback interface. Add new tests, remove deprecated tests. --- .github/workflows/linux.yml | 6 ++ config.h | 2 +- src/test/unit/unit.c | 6 +- src/test/unit/unit_tests_proto.c | 122 +++++++++++++++++++++++++++++-- src/wolfip.c | 48 +++++++++--- wolfip.h | 1 - 6 files changed, 167 insertions(+), 18 deletions(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 53664aff..56ca1605 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -90,6 +90,12 @@ jobs: set -euo pipefail timeout --preserve-status 2m sudo LD_PRELOAD=$PWD/libwolfip.so ping -c 5 10.10.10.1 + - name: Testing ICMP loopback smoke (no TUN/TAP) + timeout-minutes: 2 + run: | + set -euo pipefail + timeout --preserve-status 2m sudo LD_PRELOAD=$PWD/libwolfip.so ping -4 -n -c 5 127.0.0.1 + - name: Install check run: | sudo apt-get install -y check diff --git a/config.h b/config.h index e37c1f43..2c2eb040 100644 --- a/config.h +++ b/config.h @@ -40,7 +40,7 @@ #endif #ifndef WOLFIP_ENABLE_LOOPBACK -#define WOLFIP_ENABLE_LOOPBACK 0 +#define WOLFIP_ENABLE_LOOPBACK 1 #endif /* Enable HTTP server for POSIX builds */ diff --git a/src/test/unit/unit.c b/src/test/unit/unit.c index 66605062..dcf5fc08 100644 --- a/src/test/unit/unit.c +++ b/src/test/unit/unit.c @@ -115,6 +115,9 @@ Suite *wolf_suite(void) #if WOLFIP_ENABLE_LOOPBACK tcase_add_test(tc_utils, test_wolfip_loopback_defaults); tcase_add_test(tc_utils, test_wolfip_loopback_send_paths); + tcase_add_test(tc_utils, test_wolfip_loopback_poll_paths); + tcase_add_test(tc_utils, test_wolfip_loopback_poll_keeps_pending_on_short_buffer); + tcase_add_test(tc_utils, test_wolfip_loopback_poll_null_container); tcase_add_test(tc_utils, test_wolfip_loopback_send_drops_oversize); tcase_add_test(tc_utils, test_wolfip_loopback_send_null_container); tcase_add_test(tc_utils, test_wolfip_loopback_send_rejects_null_args); @@ -770,7 +773,8 @@ Suite *wolf_suite(void) tcase_add_test(tc_proto, test_regression_syn_on_established_not_silently_processed); tcase_add_test(tc_proto, test_regression_syn_on_last_ack_not_silently_processed); tcase_add_test(tc_proto, test_regression_full_txbuf_still_sends_pure_ack); - tcase_add_test(tc_proto, test_regression_loopback_immediate_pure_ack_uses_loopback_ll); + tcase_add_test(tc_proto, test_regression_loopback_pure_ack_uses_deferred_buffer_until_poll); + tcase_add_test(tc_proto, test_regression_loopback_pure_ack_drain_allows_next_send_cycle); tcase_add_test(tc_proto, test_regression_tcp_tx_desc_payload_len_keeps_descriptor_layout_sanity); tcase_add_test(tc_proto, test_regression_fast_recovery_cwnd_ssthresh_rfc5681); tcase_add_test(tc_proto, test_regression_paws_rejects_stale_timestamp); diff --git a/src/test/unit/unit_tests_proto.c b/src/test/unit/unit_tests_proto.c index 36bd9fbe..70af4a3c 100644 --- a/src/test/unit/unit_tests_proto.c +++ b/src/test/unit/unit_tests_proto.c @@ -2967,6 +2967,7 @@ START_TEST(test_wolfip_loopback_defaults) loop = wolfIP_getdev_ex(&s, TEST_LOOPBACK_IF); ck_assert_ptr_nonnull(loop); ck_assert_ptr_nonnull(loop->send); + ck_assert_ptr_eq(loop->poll, wolfIP_loopback_poll); ck_assert_uint_eq(loop->mac[0], 0x02); wolfIP_ipconfig_get_ex(&s, TEST_LOOPBACK_IF, &ip, &mask, &gw); @@ -2998,6 +2999,66 @@ START_TEST(test_wolfip_loopback_send_paths) ck_assert_int_eq(wolfIP_loopback_send(NULL, frame, sizeof(frame)), -1); ck_assert_int_eq(wolfIP_loopback_send(loop, NULL, sizeof(frame)), -1); ck_assert_int_eq(wolfIP_loopback_send(loop, frame, sizeof(frame)), (int)sizeof(frame)); + ck_assert_int_eq(wolfIP_loopback_send(loop, frame, sizeof(frame)), 0); +} +END_TEST + +START_TEST(test_wolfip_loopback_poll_paths) +{ + struct wolfIP s; + struct wolfIP_ll_dev *loop; + uint8_t tx[16]; + uint8_t rx[16]; + + wolfIP_init(&s); + loop = wolfIP_getdev_ex(&s, TEST_LOOPBACK_IF); + ck_assert_ptr_nonnull(loop); + + memset(tx, 0x5A, sizeof(tx)); + memset(rx, 0, sizeof(rx)); + + ck_assert_int_eq(wolfIP_loopback_poll(NULL, rx, sizeof(rx)), 0); + ck_assert_int_eq(wolfIP_loopback_poll(loop, rx, sizeof(rx)), 0); + + ck_assert_int_eq(wolfIP_loopback_send(loop, tx, sizeof(tx)), (int)sizeof(tx)); + ck_assert_int_eq(wolfIP_loopback_poll(loop, rx, sizeof(rx)), (int)sizeof(rx)); + ck_assert_mem_eq(rx, tx, sizeof(tx)); + ck_assert_int_eq(wolfIP_loopback_poll(loop, rx, sizeof(rx)), 0); +} +END_TEST + +START_TEST(test_wolfip_loopback_poll_keeps_pending_on_short_buffer) +{ + struct wolfIP s; + struct wolfIP_ll_dev *loop; + uint8_t tx[16]; + uint8_t rx[16]; + + wolfIP_init(&s); + loop = wolfIP_getdev_ex(&s, TEST_LOOPBACK_IF); + ck_assert_ptr_nonnull(loop); + + memset(tx, 0xC3, sizeof(tx)); + memset(rx, 0, sizeof(rx)); + + ck_assert_int_eq(wolfIP_loopback_send(loop, tx, sizeof(tx)), (int)sizeof(tx)); + ck_assert_int_eq(wolfIP_loopback_poll(loop, rx, sizeof(rx) - 1U), 0); + ck_assert_int_eq(wolfIP_loopback_poll(loop, rx, sizeof(rx)), (int)sizeof(rx)); + ck_assert_mem_eq(rx, tx, sizeof(tx)); +} +END_TEST + +START_TEST(test_wolfip_loopback_poll_null_container) +{ + uintptr_t off = (uintptr_t)offsetof(struct wolfIP, ll_dev); + struct wolfIP_ll_dev *ll; + uint8_t frame[4] = {0}; + + if (off == 0) + return; + + ll = (struct wolfIP_ll_dev *)off; + ck_assert_int_eq(wolfIP_loopback_poll(ll, frame, sizeof(frame)), 0); } END_TEST @@ -4786,17 +4847,18 @@ START_TEST(test_regression_full_txbuf_still_sends_pure_ack) } END_TEST -START_TEST(test_regression_loopback_immediate_pure_ack_uses_loopback_ll) +START_TEST(test_regression_loopback_pure_ack_uses_deferred_buffer_until_poll) { struct wolfIP s; struct tsocket *ts; struct wolfIP_ll_dev *loop; struct wolfIP_tcp_seg seg; + uint32_t expected_pending_len; wolfIP_init(&s); loop = wolfIP_getdev_ex(&s, TEST_LOOPBACK_IF); ck_assert_ptr_nonnull(loop); - loop->send = mock_send; + ck_assert_ptr_eq(loop->send, wolfIP_loopback_send); last_frame_sent_size = 0; memset(last_frame_sent, 0, sizeof(last_frame_sent)); @@ -4822,13 +4884,63 @@ START_TEST(test_regression_loopback_immediate_pure_ack_uses_loopback_ll) seg.ack = ee32(ts->sock.tcp.ack); seg.hlen = TCP_HEADER_LEN << 2; seg.flags = TCP_FLAG_ACK; + expected_pending_len = (uint32_t)sizeof(seg) - ETH_HEADER_LEN; ck_assert_int_eq(tcp_send_empty_immediate(ts, &seg, (uint32_t)sizeof(seg)), 0); ck_assert_uint_eq(ts->sock.tcp.last_ack, ts->sock.tcp.ack); - ck_assert_uint_eq(last_frame_sent_size, (uint32_t)sizeof(seg)); - ck_assert_mem_eq(seg.ip.eth.dst, loop->mac, 6); - ck_assert_mem_eq(seg.ip.eth.src, loop->mac, 6); + ck_assert_uint_eq(last_frame_sent_size, 0U); + ck_assert_uint_eq(s.loopback_pending_len, expected_pending_len); + + (void)wolfIP_poll(&s, 200); + ck_assert_uint_eq(s.loopback_pending_len, 0U); +} +END_TEST + +START_TEST(test_regression_loopback_pure_ack_drain_allows_next_send_cycle) +{ + struct wolfIP s; + struct tsocket *ts; + struct wolfIP_ll_dev *loop; + struct wolfIP_tcp_seg seg; + uint8_t rx[IP_MTU_MAX]; + uint32_t expected_pending_len = (uint32_t)sizeof(seg) - ETH_HEADER_LEN; + + wolfIP_init(&s); + loop = wolfIP_getdev_ex(&s, TEST_LOOPBACK_IF); + ck_assert_ptr_nonnull(loop); + + ts = &s.tcpsockets[0]; + memset(ts, 0, sizeof(*ts)); + ts->proto = WI_IPPROTO_TCP; + ts->S = &s; + ts->if_idx = TEST_LOOPBACK_IF; + ts->sock.tcp.state = TCP_ESTABLISHED; + ts->sock.tcp.ack = 100; + ts->sock.tcp.seq = 1000; + ts->sock.tcp.snd_una = 900; + ts->sock.tcp.cwnd = TXBUF_SIZE; + ts->sock.tcp.peer_rwnd = TXBUF_SIZE; + ts->src_port = 1234; + ts->dst_port = 4321; + ts->local_ip = 0x7F000001U; + ts->remote_ip = 0x7F000001U; + memset(&seg, 0, sizeof(seg)); + seg.src_port = ee16(ts->src_port); + seg.dst_port = ee16(ts->dst_port); + seg.seq = ee32(ts->sock.tcp.seq); + seg.ack = ee32(ts->sock.tcp.ack); + seg.hlen = TCP_HEADER_LEN << 2; + seg.flags = TCP_FLAG_ACK; + + ck_assert_int_eq(tcp_send_empty_immediate(ts, &seg, (uint32_t)sizeof(seg)), 0); + ck_assert_uint_eq(s.loopback_pending_len, expected_pending_len); + + ck_assert_int_eq(loop->poll(loop, rx, sizeof(rx)), (int)expected_pending_len); + ck_assert_uint_eq(s.loopback_pending_len, 0U); + + ck_assert_int_eq(tcp_send_empty_immediate(ts, &seg, (uint32_t)sizeof(seg)), 0); + ck_assert_uint_eq(s.loopback_pending_len, expected_pending_len); } END_TEST diff --git a/src/wolfip.c b/src/wolfip.c index 2b9dffdf..1f0373cb 100644 --- a/src/wolfip.c +++ b/src/wolfip.c @@ -22,9 +22,9 @@ #include #include #include +#ifdef WOLF_POSIX #include #include -#ifdef WOLF_POSIX #include #include #include @@ -1259,6 +1259,10 @@ struct wolfIP { } arp; struct arp_pending_entry arp_pending[WOLFIP_ARP_PENDING_MAX]; #endif +#if WOLFIP_ENABLE_LOOPBACK + uint8_t loopback_buf[IP_MTU_MAX]; + uint32_t loopback_pending_len; +#endif }; static inline int tx_has_writable_space(const struct tsocket *t) @@ -1365,18 +1369,40 @@ static inline uint32_t tcp_tx_payload_cap(const struct tsocket *t) static int wolfIP_loopback_send(struct wolfIP_ll_dev *ll, void *buf, uint32_t len) { struct wolfIP *s; - uint32_t copy = len; - uint8_t frame[LINK_MTU]; if (!ll || !buf) return -1; s = WOLFIP_CONTAINER_OF(ll, struct wolfIP, ll_dev); if (!s) return -1; - if (copy > wolfIP_ll_frame_mtu(ll)) + if (len > IP_MTU_MAX) + return 0; + if (s->loopback_pending_len > 0) + return 0; /* buffer busy, drop */ + /* buf is the IP payload (ETH header already stripped by + * wolfIP_ll_send_frame for non-ethernet devices). + * Store as-is; wolfIP_poll will re-add the ETH prefix. */ + memcpy(s->loopback_buf, buf, len); + s->loopback_pending_len = len; + return (int)len; +} + +static int wolfIP_loopback_poll(struct wolfIP_ll_dev *ll, void *buf, uint32_t len) +{ + struct wolfIP *s; + uint32_t pending; + if (!ll) + return 0; + s = WOLFIP_CONTAINER_OF(ll, struct wolfIP, ll_dev); + if (!s) + return 0; + pending = s->loopback_pending_len; + if (pending == 0) + return 0; + if (pending > len) return 0; - memcpy(frame, buf, copy); - wolfIP_recv_on(s, WOLFIP_LOOPBACK_IF_IDX, frame, copy); - return (int)copy; + s->loopback_pending_len = 0; + memcpy(buf, s->loopback_buf, pending); + return (int)pending; } #endif @@ -6634,7 +6660,9 @@ void wolfIP_init(struct wolfIP *s) memcpy(loop->mac, loop_mac, sizeof(loop_mac)); strncpy(loop->ifname, "lo", sizeof(loop->ifname) - 1); loop->ifname[sizeof(loop->ifname) - 1] = '\0'; - loop->poll = NULL; + loop->non_ethernet = 1; + loop->mtu = LINK_MTU; + loop->poll = wolfIP_loopback_poll; loop->send = wolfIP_loopback_send; } if (loop_conf) { @@ -6705,9 +6733,9 @@ size_t wolfIP_instance_size(void) return sizeof(struct wolfIP); } -#if defined(DEBUG) +#if defined(DEBUG) || defined(DEBUG_ETH) || defined(DEBUG_IP) || defined(DEBUG_UDP) #include "src/wolfip_debug.c" -#endif /* DEBUG */ +#endif /* DEBUG || DEBUG_ETH || DEBUG_IP || DEBUG_UDP */ static inline void ip_recv(struct wolfIP *s, unsigned int if_idx, struct wolfIP_ip_packet *ip, uint32_t len) diff --git a/wolfip.h b/wolfip.h index 64ad7d49..b7e6667f 100644 --- a/wolfip.h +++ b/wolfip.h @@ -48,7 +48,6 @@ typedef uint32_t ip4; #define PACKED __attribute__((packed)) #define ee16(x) __builtin_bswap16(x) #define ee32(x) __builtin_bswap32(x) -#define DEBUG #ifndef WOLFIP_EAGAIN #ifdef EAGAIN From acfc27a0f48c6c8c1a165744352e1e3c1b4c9370 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Wed, 15 Apr 2026 12:56:13 +0200 Subject: [PATCH 2/8] Adjust test_wolfguard with added lo interface --- config.h | 2 +- src/test/test_wolfguard_loopback.c | 60 +++++++++++++++++------------- 2 files changed, 35 insertions(+), 27 deletions(-) diff --git a/config.h b/config.h index 2c2eb040..d3992ee4 100644 --- a/config.h +++ b/config.h @@ -32,7 +32,7 @@ #define MAX_NEIGHBORS 16 #ifndef WOLFIP_MAX_INTERFACES -#define WOLFIP_MAX_INTERFACES 2 +#define WOLFIP_MAX_INTERFACES 3 #endif #ifndef WOLFIP_ENABLE_FORWARDING diff --git a/src/test/test_wolfguard_loopback.c b/src/test/test_wolfguard_loopback.c index 879262a1..cc1f3303 100644 --- a/src/test/test_wolfguard_loopback.c +++ b/src/test/test_wolfguard_loopback.c @@ -16,11 +16,19 @@ #endif #undef WOLFIP_MAX_INTERFACES -#define WOLFIP_MAX_INTERFACES 2 +#define WOLFIP_MAX_INTERFACES 3 #include "check.h" #include "../../config.h" +#if WOLFIP_ENABLE_LOOPBACK +#define TEST_PHYS_IF 1U +#define TEST_WG_IF 2U +#else +#define TEST_PHYS_IF 0U +#define TEST_WG_IF 1U +#endif + /* Override after config.h */ #undef MAX_UDPSOCKETS #define MAX_UDPSOCKETS 8 @@ -206,46 +214,46 @@ static void setup_loopback_stacks(uint64_t *now) wolfIP_init(&stack_a); /* Physical interface (non_ethernet, index 0) */ - ll = wolfIP_getdev_ex(&stack_a, 0); + ll = wolfIP_getdev_ex(&stack_a, TEST_PHYS_IF); ll->non_ethernet = 1; ll->poll = phys_a_poll; ll->send = phys_a_send; strncpy(ll->ifname, "eth_a", sizeof(ll->ifname) - 1); - wolfIP_ipconfig_set(&stack_a, MAKE_IP4(192,168,1,1), - MAKE_IP4(255,255,255,0), 0); + wolfIP_ipconfig_set_ex(&stack_a, TEST_PHYS_IF, MAKE_IP4(192,168,1,1), + MAKE_IP4(255,255,255,0), 0); /* wolfGuard on interface 1 (wg0) */ - ck_assert_int_eq(wolfguard_init(&wg_dev_a, &stack_a, 1, 51820), 0); + ck_assert_int_eq(wolfguard_init(&wg_dev_a, &stack_a, TEST_WG_IF, 51820), 0); /* Generate and set keys for A */ wc_RNG_GenerateBlock(&test_rng, priv_a, WG_PRIVATE_KEY_LEN); ck_assert_int_eq(wolfguard_set_private_key(&wg_dev_a, priv_a), 0); - wolfIP_ipconfig_set_ex(&stack_a, 1, MAKE_IP4(10,0,0,1), + wolfIP_ipconfig_set_ex(&stack_a, TEST_WG_IF, MAKE_IP4(10,0,0,1), MAKE_IP4(255,255,255,0), 0); /* Stack B */ wolfIP_init(&stack_b); /* Physical interface (non_ethernet, index 0) */ - ll = wolfIP_getdev_ex(&stack_b, 0); + ll = wolfIP_getdev_ex(&stack_b, TEST_PHYS_IF); ll->non_ethernet = 1; ll->poll = phys_b_poll; ll->send = phys_b_send; strncpy(ll->ifname, "eth_b", sizeof(ll->ifname) - 1); - wolfIP_ipconfig_set(&stack_b, MAKE_IP4(192,168,1,2), - MAKE_IP4(255,255,255,0), 0); + wolfIP_ipconfig_set_ex(&stack_b, TEST_PHYS_IF, MAKE_IP4(192,168,1,2), + MAKE_IP4(255,255,255,0), 0); /* wolfGuard on interface 1 (wg0) */ - ck_assert_int_eq(wolfguard_init(&wg_dev_b, &stack_b, 1, 51820), 0); + ck_assert_int_eq(wolfguard_init(&wg_dev_b, &stack_b, TEST_WG_IF, 51820), 0); /* Generate and set keys for B */ wc_RNG_GenerateBlock(&test_rng, priv_b, WG_PRIVATE_KEY_LEN); ck_assert_int_eq(wolfguard_set_private_key(&wg_dev_b, priv_b), 0); - wolfIP_ipconfig_set_ex(&stack_b, 1, MAKE_IP4(10,0,0,2), + wolfIP_ipconfig_set_ex(&stack_b, TEST_WG_IF, MAKE_IP4(10,0,0,2), MAKE_IP4(255,255,255,0), 0); /* Add peers (A knows B, B knows A) */ @@ -798,50 +806,50 @@ START_TEST(test_multi_peer) /* Stack A (hub, 2 peers) */ wolfIP_init(&stack_a); - ll = wolfIP_getdev_ex(&stack_a, 0); + ll = wolfIP_getdev_ex(&stack_a, TEST_PHYS_IF); ll->non_ethernet = 1; ll->poll = phys_a_poll_multi; ll->send = phys_a_send_multi; strncpy(ll->ifname, "eth_a", sizeof(ll->ifname) - 1); - wolfIP_ipconfig_set(&stack_a, MAKE_IP4(192,168,1,1), - MAKE_IP4(255,255,255,0), 0); + wolfIP_ipconfig_set_ex(&stack_a, TEST_PHYS_IF, MAKE_IP4(192,168,1,1), + MAKE_IP4(255,255,255,0), 0); - ck_assert_int_eq(wolfguard_init(&wg_dev_a, &stack_a, 1, 51820), 0); + ck_assert_int_eq(wolfguard_init(&wg_dev_a, &stack_a, TEST_WG_IF, 51820), 0); wc_RNG_GenerateBlock(&test_rng, priv_a, WG_PRIVATE_KEY_LEN); ck_assert_int_eq(wolfguard_set_private_key(&wg_dev_a, priv_a), 0); - wolfIP_ipconfig_set_ex(&stack_a, 1, MAKE_IP4(10,0,0,1), + wolfIP_ipconfig_set_ex(&stack_a, TEST_WG_IF, MAKE_IP4(10,0,0,1), MAKE_IP4(255,0,0,0), 0); /* Stack B */ wolfIP_init(&stack_b); - ll = wolfIP_getdev_ex(&stack_b, 0); + ll = wolfIP_getdev_ex(&stack_b, TEST_PHYS_IF); ll->non_ethernet = 1; ll->poll = phys_b_poll; ll->send = phys_b_send; strncpy(ll->ifname, "eth_b", sizeof(ll->ifname) - 1); - wolfIP_ipconfig_set(&stack_b, MAKE_IP4(192,168,1,2), - MAKE_IP4(255,255,255,0), 0); + wolfIP_ipconfig_set_ex(&stack_b, TEST_PHYS_IF, MAKE_IP4(192,168,1,2), + MAKE_IP4(255,255,255,0), 0); - ck_assert_int_eq(wolfguard_init(&wg_dev_b, &stack_b, 1, 51820), 0); + ck_assert_int_eq(wolfguard_init(&wg_dev_b, &stack_b, TEST_WG_IF, 51820), 0); wc_RNG_GenerateBlock(&test_rng, priv_b, WG_PRIVATE_KEY_LEN); ck_assert_int_eq(wolfguard_set_private_key(&wg_dev_b, priv_b), 0); - wolfIP_ipconfig_set_ex(&stack_b, 1, MAKE_IP4(10,0,1,1), + wolfIP_ipconfig_set_ex(&stack_b, TEST_WG_IF, MAKE_IP4(10,0,1,1), MAKE_IP4(255,255,255,0), 0); /* Stack C */ wolfIP_init(&stack_c); - ll = wolfIP_getdev_ex(&stack_c, 0); + ll = wolfIP_getdev_ex(&stack_c, TEST_PHYS_IF); ll->non_ethernet = 1; ll->poll = phys_c_poll; ll->send = phys_c_send; strncpy(ll->ifname, "eth_c", sizeof(ll->ifname) - 1); - wolfIP_ipconfig_set(&stack_c, MAKE_IP4(192,168,1,3), - MAKE_IP4(255,255,255,0), 0); + wolfIP_ipconfig_set_ex(&stack_c, TEST_PHYS_IF, MAKE_IP4(192,168,1,3), + MAKE_IP4(255,255,255,0), 0); - ck_assert_int_eq(wolfguard_init(&wg_dev_c, &stack_c, 1, 51820), 0); + ck_assert_int_eq(wolfguard_init(&wg_dev_c, &stack_c, TEST_WG_IF, 51820), 0); wc_RNG_GenerateBlock(&test_rng, priv_c, WG_PRIVATE_KEY_LEN); ck_assert_int_eq(wolfguard_set_private_key(&wg_dev_c, priv_c), 0); - wolfIP_ipconfig_set_ex(&stack_c, 1, MAKE_IP4(10,0,2,1), + wolfIP_ipconfig_set_ex(&stack_c, TEST_WG_IF, MAKE_IP4(10,0,2,1), MAKE_IP4(255,255,255,0), 0); /* Add peers */ From 836b05eb54389b8bed5b5b78a2c10bcb12904a2d Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Wed, 15 Apr 2026 13:08:59 +0200 Subject: [PATCH 3/8] Disabled loopback by default in config. Addressed review comments. --- .github/workflows/linux.yml | 6 +++++ Makefile | 1 + config.h | 2 +- src/test/unit/unit_esp.c | 1 + src/test/unit/unit_tests_proto.c | 24 +++++++++++++++----- src/wolfip.c | 38 ++++++++++++++++++++++---------- 6 files changed, 53 insertions(+), 19 deletions(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 56ca1605..e45a919f 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -90,6 +90,12 @@ jobs: set -euo pipefail timeout --preserve-status 2m sudo LD_PRELOAD=$PWD/libwolfip.so ping -c 5 10.10.10.1 + - name: Rebuild libwolfip.so with loopback enabled + run: | + set -euo pipefail + rm -f libwolfip.so build/pie/wolfip.o + make libwolfip.so EXTRA_CFLAGS="-DWOLFIP_ENABLE_LOOPBACK=1" + - name: Testing ICMP loopback smoke (no TUN/TAP) timeout-minutes: 2 run: | diff --git a/Makefile b/Makefile index 31ed261b..395288f1 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,7 @@ CC?=gcc CFLAGS:=-Wall -Werror -Wextra -I. -D_GNU_SOURCE CFLAGS+=-g -ggdb -Wdeclaration-after-statement +CFLAGS+=$(EXTRA_CFLAGS) LDFLAGS+=-pthread # additional debug flags: # CFLAGS+=-DDEBUG_TAP diff --git a/config.h b/config.h index d3992ee4..8b063355 100644 --- a/config.h +++ b/config.h @@ -40,7 +40,7 @@ #endif #ifndef WOLFIP_ENABLE_LOOPBACK -#define WOLFIP_ENABLE_LOOPBACK 1 +#define WOLFIP_ENABLE_LOOPBACK 0 #endif /* Enable HTTP server for POSIX builds */ diff --git a/src/test/unit/unit_esp.c b/src/test/unit/unit_esp.c index 133dd48c..3bc70ed0 100644 --- a/src/test/unit/unit_esp.c +++ b/src/test/unit/unit_esp.c @@ -28,6 +28,7 @@ #undef WOLFIP_MAX_INTERFACES #define WOLFIP_MAX_INTERFACES 1 #undef WOLFIP_ENABLE_LOOPBACK +#define WOLFIP_ENABLE_LOOPBACK 0 #include "check.h" #include "../../../config.h" diff --git a/src/test/unit/unit_tests_proto.c b/src/test/unit/unit_tests_proto.c index 70af4a3c..5eafbecf 100644 --- a/src/test/unit/unit_tests_proto.c +++ b/src/test/unit/unit_tests_proto.c @@ -2998,7 +2998,12 @@ START_TEST(test_wolfip_loopback_send_paths) ck_assert_int_eq(wolfIP_loopback_send(NULL, frame, sizeof(frame)), -1); ck_assert_int_eq(wolfIP_loopback_send(loop, NULL, sizeof(frame)), -1); - ck_assert_int_eq(wolfIP_loopback_send(loop, frame, sizeof(frame)), (int)sizeof(frame)); + /* Fill the queue, each slot enqueue returns the byte count. */ + for (unsigned int i = 0; i < WOLFIP_LOOPBACK_QUEUE_DEPTH; i++) { + ck_assert_int_eq(wolfIP_loopback_send(loop, frame, sizeof(frame)), + (int)sizeof(frame)); + } + /* Next send must be dropped because the queue is full. */ ck_assert_int_eq(wolfIP_loopback_send(loop, frame, sizeof(frame)), 0); } END_TEST @@ -3018,6 +3023,7 @@ START_TEST(test_wolfip_loopback_poll_paths) memset(rx, 0, sizeof(rx)); ck_assert_int_eq(wolfIP_loopback_poll(NULL, rx, sizeof(rx)), 0); + ck_assert_int_eq(wolfIP_loopback_poll(loop, NULL, sizeof(rx)), 0); ck_assert_int_eq(wolfIP_loopback_poll(loop, rx, sizeof(rx)), 0); ck_assert_int_eq(wolfIP_loopback_send(loop, tx, sizeof(tx)), (int)sizeof(tx)); @@ -4890,10 +4896,12 @@ START_TEST(test_regression_loopback_pure_ack_uses_deferred_buffer_until_poll) (uint32_t)sizeof(seg)), 0); ck_assert_uint_eq(ts->sock.tcp.last_ack, ts->sock.tcp.ack); ck_assert_uint_eq(last_frame_sent_size, 0U); - ck_assert_uint_eq(s.loopback_pending_len, expected_pending_len); + ck_assert_uint_eq(s.loopback_count, 1U); + ck_assert_uint_eq(s.loopback_pending_len[s.loopback_head], + expected_pending_len); (void)wolfIP_poll(&s, 200); - ck_assert_uint_eq(s.loopback_pending_len, 0U); + ck_assert_uint_eq(s.loopback_count, 0U); } END_TEST @@ -4934,13 +4942,17 @@ START_TEST(test_regression_loopback_pure_ack_drain_allows_next_send_cycle) seg.flags = TCP_FLAG_ACK; ck_assert_int_eq(tcp_send_empty_immediate(ts, &seg, (uint32_t)sizeof(seg)), 0); - ck_assert_uint_eq(s.loopback_pending_len, expected_pending_len); + ck_assert_uint_eq(s.loopback_count, 1U); + ck_assert_uint_eq(s.loopback_pending_len[s.loopback_head], + expected_pending_len); ck_assert_int_eq(loop->poll(loop, rx, sizeof(rx)), (int)expected_pending_len); - ck_assert_uint_eq(s.loopback_pending_len, 0U); + ck_assert_uint_eq(s.loopback_count, 0U); ck_assert_int_eq(tcp_send_empty_immediate(ts, &seg, (uint32_t)sizeof(seg)), 0); - ck_assert_uint_eq(s.loopback_pending_len, expected_pending_len); + ck_assert_uint_eq(s.loopback_count, 1U); + ck_assert_uint_eq(s.loopback_pending_len[s.loopback_head], + expected_pending_len); } END_TEST diff --git a/src/wolfip.c b/src/wolfip.c index 1f0373cb..3b9e83a2 100644 --- a/src/wolfip.c +++ b/src/wolfip.c @@ -1260,8 +1260,14 @@ struct wolfIP { struct arp_pending_entry arp_pending[WOLFIP_ARP_PENDING_MAX]; #endif #if WOLFIP_ENABLE_LOOPBACK - uint8_t loopback_buf[IP_MTU_MAX]; - uint32_t loopback_pending_len; +#ifndef WOLFIP_LOOPBACK_QUEUE_DEPTH +#define WOLFIP_LOOPBACK_QUEUE_DEPTH 4 +#endif + uint8_t loopback_buf[WOLFIP_LOOPBACK_QUEUE_DEPTH][IP_MTU_MAX]; + uint32_t loopback_pending_len[WOLFIP_LOOPBACK_QUEUE_DEPTH]; + uint8_t loopback_head; + uint8_t loopback_tail; + uint8_t loopback_count; #endif }; @@ -1369,39 +1375,47 @@ static inline uint32_t tcp_tx_payload_cap(const struct tsocket *t) static int wolfIP_loopback_send(struct wolfIP_ll_dev *ll, void *buf, uint32_t len) { struct wolfIP *s; + uint8_t slot; if (!ll || !buf) return -1; s = WOLFIP_CONTAINER_OF(ll, struct wolfIP, ll_dev); if (!s) return -1; - if (len > IP_MTU_MAX) + if (len == 0 || len > IP_MTU_MAX) return 0; - if (s->loopback_pending_len > 0) - return 0; /* buffer busy, drop */ + if (s->loopback_count >= WOLFIP_LOOPBACK_QUEUE_DEPTH) + return 0; /* queue full, drop */ /* buf is the IP payload (ETH header already stripped by * wolfIP_ll_send_frame for non-ethernet devices). * Store as-is; wolfIP_poll will re-add the ETH prefix. */ - memcpy(s->loopback_buf, buf, len); - s->loopback_pending_len = len; + slot = s->loopback_tail; + memcpy(s->loopback_buf[slot], buf, len); + s->loopback_pending_len[slot] = len; + s->loopback_tail = (uint8_t)((slot + 1U) % WOLFIP_LOOPBACK_QUEUE_DEPTH); + s->loopback_count++; return (int)len; } static int wolfIP_loopback_poll(struct wolfIP_ll_dev *ll, void *buf, uint32_t len) { struct wolfIP *s; + uint8_t slot; uint32_t pending; - if (!ll) + if (!ll || !buf) return 0; s = WOLFIP_CONTAINER_OF(ll, struct wolfIP, ll_dev); if (!s) return 0; - pending = s->loopback_pending_len; - if (pending == 0) + if (s->loopback_count == 0) return 0; + slot = s->loopback_head; + pending = s->loopback_pending_len[slot]; if (pending > len) return 0; - s->loopback_pending_len = 0; - memcpy(buf, s->loopback_buf, pending); + memcpy(buf, s->loopback_buf[slot], pending); + s->loopback_pending_len[slot] = 0; + s->loopback_head = (uint8_t)((slot + 1U) % WOLFIP_LOOPBACK_QUEUE_DEPTH); + s->loopback_count--; return (int)pending; } #endif From 88787f54f42c2cab3db6bbd897e11f80b6f2680e Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Wed, 15 Apr 2026 14:46:29 +0200 Subject: [PATCH 4/8] Addressed reviewer's comment --- src/test/unit/unit.c | 3 +- src/test/unit/unit_tests_proto.c | 55 ++++++++++++++++++-------------- src/wolfip.c | 2 +- 3 files changed, 33 insertions(+), 27 deletions(-) diff --git a/src/test/unit/unit.c b/src/test/unit/unit.c index dcf5fc08..42f8a425 100644 --- a/src/test/unit/unit.c +++ b/src/test/unit/unit.c @@ -117,9 +117,8 @@ Suite *wolf_suite(void) tcase_add_test(tc_utils, test_wolfip_loopback_send_paths); tcase_add_test(tc_utils, test_wolfip_loopback_poll_paths); tcase_add_test(tc_utils, test_wolfip_loopback_poll_keeps_pending_on_short_buffer); - tcase_add_test(tc_utils, test_wolfip_loopback_poll_null_container); tcase_add_test(tc_utils, test_wolfip_loopback_send_drops_oversize); - tcase_add_test(tc_utils, test_wolfip_loopback_send_null_container); + tcase_add_test(tc_utils, test_wolfip_loopback_send_queue_full_returns_eagain); tcase_add_test(tc_utils, test_wolfip_loopback_send_rejects_null_args); #endif tcase_add_test(tc_utils, test_wolfip_send_port_unreachable_ignores_missing_link_sender); diff --git a/src/test/unit/unit_tests_proto.c b/src/test/unit/unit_tests_proto.c index 5eafbecf..b4042eb8 100644 --- a/src/test/unit/unit_tests_proto.c +++ b/src/test/unit/unit_tests_proto.c @@ -3003,8 +3003,9 @@ START_TEST(test_wolfip_loopback_send_paths) ck_assert_int_eq(wolfIP_loopback_send(loop, frame, sizeof(frame)), (int)sizeof(frame)); } - /* Next send must be dropped because the queue is full. */ - ck_assert_int_eq(wolfIP_loopback_send(loop, frame, sizeof(frame)), 0); + /* Next send must return -WOLFIP_EAGAIN because the queue is full. */ + ck_assert_int_eq(wolfIP_loopback_send(loop, frame, sizeof(frame)), + -WOLFIP_EAGAIN); } END_TEST @@ -3054,20 +3055,6 @@ START_TEST(test_wolfip_loopback_poll_keeps_pending_on_short_buffer) } END_TEST -START_TEST(test_wolfip_loopback_poll_null_container) -{ - uintptr_t off = (uintptr_t)offsetof(struct wolfIP, ll_dev); - struct wolfIP_ll_dev *ll; - uint8_t frame[4] = {0}; - - if (off == 0) - return; - - ll = (struct wolfIP_ll_dev *)off; - ck_assert_int_eq(wolfIP_loopback_poll(ll, frame, sizeof(frame)), 0); -} -END_TEST - START_TEST(test_wolfip_loopback_send_drops_oversize) { struct wolfIP s; @@ -3086,17 +3073,37 @@ START_TEST(test_wolfip_loopback_send_drops_oversize) } END_TEST -START_TEST(test_wolfip_loopback_send_null_container) +START_TEST(test_wolfip_loopback_send_queue_full_returns_eagain) { - uintptr_t off = (uintptr_t)offsetof(struct wolfIP, ll_dev); - struct wolfIP_ll_dev *ll; - uint8_t frame[4] = {0}; + struct wolfIP s; + struct wolfIP_ll_dev *loop; + uint8_t frame[16] = {0}; + uint8_t rx[IP_MTU_MAX]; + + wolfIP_init(&s); + loop = wolfIP_getdev_ex(&s, TEST_LOOPBACK_IF); + ck_assert_ptr_nonnull(loop); + + /* Fill the queue completely. */ + for (unsigned int i = 0; i < WOLFIP_LOOPBACK_QUEUE_DEPTH; i++) { + frame[0] = (uint8_t)i; + ck_assert_int_eq(wolfIP_loopback_send(loop, frame, sizeof(frame)), + (int)sizeof(frame)); + } + + /* Queue-full must return -WOLFIP_EAGAIN, not 0. */ + ck_assert_int_eq(wolfIP_loopback_send(loop, frame, sizeof(frame)), + -WOLFIP_EAGAIN); - if (off == 0) - return; + /* Drain one slot and verify we can enqueue again. */ + ck_assert_int_gt(wolfIP_loopback_poll(loop, rx, sizeof(rx)), 0); + frame[0] = 0xFF; + ck_assert_int_eq(wolfIP_loopback_send(loop, frame, sizeof(frame)), + (int)sizeof(frame)); - ll = (struct wolfIP_ll_dev *)off; - ck_assert_int_eq(wolfIP_loopback_send(ll, frame, sizeof(frame)), -1); + /* Queue is full again — must get -WOLFIP_EAGAIN once more. */ + ck_assert_int_eq(wolfIP_loopback_send(loop, frame, sizeof(frame)), + -WOLFIP_EAGAIN); } END_TEST diff --git a/src/wolfip.c b/src/wolfip.c index 3b9e83a2..94bcdca4 100644 --- a/src/wolfip.c +++ b/src/wolfip.c @@ -1384,7 +1384,7 @@ static int wolfIP_loopback_send(struct wolfIP_ll_dev *ll, void *buf, uint32_t le if (len == 0 || len > IP_MTU_MAX) return 0; if (s->loopback_count >= WOLFIP_LOOPBACK_QUEUE_DEPTH) - return 0; /* queue full, drop */ + return -WOLFIP_EAGAIN; /* queue full, retry later */ /* buf is the IP payload (ETH header already stripped by * wolfIP_ll_send_frame for non-ethernet devices). * Store as-is; wolfIP_poll will re-add the ETH prefix. */ From 6e8e305fb93d664cd8f8fd76852fda115f8477cd Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Wed, 15 Apr 2026 16:54:22 +0200 Subject: [PATCH 5/8] Addressed copilot's review comments --- src/test/unit/unit.c | 2 + src/test/unit/unit_tests_proto.c | 113 +++++++++++++++++++++++++++++++ src/wolfip.c | 111 +++++++++++++++++++++++------- 3 files changed, 200 insertions(+), 26 deletions(-) diff --git a/src/test/unit/unit.c b/src/test/unit/unit.c index 42f8a425..49277b79 100644 --- a/src/test/unit/unit.c +++ b/src/test/unit/unit.c @@ -774,6 +774,8 @@ Suite *wolf_suite(void) tcase_add_test(tc_proto, test_regression_full_txbuf_still_sends_pure_ack); tcase_add_test(tc_proto, test_regression_loopback_pure_ack_uses_deferred_buffer_until_poll); tcase_add_test(tc_proto, test_regression_loopback_pure_ack_drain_allows_next_send_cycle); + tcase_add_test(tc_proto, test_regression_loopback_pure_ack_immediate_propagates_eagain_when_queue_full); + tcase_add_test(tc_proto, test_regression_loopback_udp_tx_backpressure_retries_after_queue_drain); tcase_add_test(tc_proto, test_regression_tcp_tx_desc_payload_len_keeps_descriptor_layout_sanity); tcase_add_test(tc_proto, test_regression_fast_recovery_cwnd_ssthresh_rfc5681); tcase_add_test(tc_proto, test_regression_paws_rejects_stale_timestamp); diff --git a/src/test/unit/unit_tests_proto.c b/src/test/unit/unit_tests_proto.c index b4042eb8..b33c821c 100644 --- a/src/test/unit/unit_tests_proto.c +++ b/src/test/unit/unit_tests_proto.c @@ -4963,6 +4963,119 @@ START_TEST(test_regression_loopback_pure_ack_drain_allows_next_send_cycle) } END_TEST +START_TEST(test_regression_loopback_pure_ack_immediate_propagates_eagain_when_queue_full) +{ + struct wolfIP s; + struct tsocket *ts; + struct wolfIP_ll_dev *loop; + struct wolfIP_tcp_seg seg; + uint8_t frame[16] = {0}; + unsigned int i; + + wolfIP_init(&s); + loop = wolfIP_getdev_ex(&s, TEST_LOOPBACK_IF); + ck_assert_ptr_nonnull(loop); + + for (i = 0; i < WOLFIP_LOOPBACK_QUEUE_DEPTH; i++) { + ck_assert_int_eq(wolfIP_loopback_send(loop, frame, sizeof(frame)), + (int)sizeof(frame)); + } + + ts = &s.tcpsockets[0]; + memset(ts, 0, sizeof(*ts)); + ts->proto = WI_IPPROTO_TCP; + ts->S = &s; + ts->if_idx = TEST_LOOPBACK_IF; + ts->sock.tcp.state = TCP_ESTABLISHED; + ts->sock.tcp.ack = 100; + ts->sock.tcp.seq = 1000; + ts->src_port = 1234; + ts->dst_port = 4321; + ts->local_ip = 0x7F000001U; + ts->remote_ip = 0x7F000001U; + + memset(&seg, 0, sizeof(seg)); + seg.src_port = ee16(ts->src_port); + seg.dst_port = ee16(ts->dst_port); + seg.seq = ee32(ts->sock.tcp.seq); + seg.ack = ee32(ts->sock.tcp.ack); + seg.hlen = TCP_HEADER_LEN << 2; + seg.flags = TCP_FLAG_ACK; + + ck_assert_int_eq(tcp_send_empty_immediate(ts, &seg, (uint32_t)sizeof(seg)), + -WOLFIP_EAGAIN); + ck_assert_uint_eq(s.loopback_count, WOLFIP_LOOPBACK_QUEUE_DEPTH); +} +END_TEST + +START_TEST(test_regression_loopback_udp_tx_backpressure_retries_after_queue_drain) +{ + struct wolfIP s; + struct tsocket *tx; + int tx_sd, rx_sd; + struct wolfIP_sockaddr_in bind_addr, dst_addr; + uint8_t payload = 0x5A; + uint8_t rxbuf[4]; + int ret; + int received = 0; + uint64_t now = 200; + unsigned int i; + + wolfIP_init(&s); + + rx_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + ck_assert_int_ge(rx_sd, 0); + memset(&bind_addr, 0, sizeof(bind_addr)); + bind_addr.sin_family = AF_INET; + bind_addr.sin_port = ee16(7777); + bind_addr.sin_addr.s_addr = ee32(0x7F000001U); + ck_assert_int_eq(wolfIP_sock_bind(&s, rx_sd, + (struct wolfIP_sockaddr *)&bind_addr, sizeof(bind_addr)), 0); + + tx_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + ck_assert_int_ge(tx_sd, 0); + memset(&bind_addr, 0, sizeof(bind_addr)); + bind_addr.sin_family = AF_INET; + bind_addr.sin_port = ee16(9999); + bind_addr.sin_addr.s_addr = ee32(0x7F000001U); + ck_assert_int_eq(wolfIP_sock_bind(&s, tx_sd, + (struct wolfIP_sockaddr *)&bind_addr, sizeof(bind_addr)), 0); + + memset(&dst_addr, 0, sizeof(dst_addr)); + dst_addr.sin_family = AF_INET; + dst_addr.sin_port = ee16(7777); + dst_addr.sin_addr.s_addr = ee32(0x7F000001U); + + for (i = 0; i < WOLFIP_LOOPBACK_QUEUE_DEPTH + 1U; i++) { + payload = (uint8_t)i; + ret = wolfIP_sock_sendto(&s, tx_sd, &payload, 1, 0, + (const struct wolfIP_sockaddr *)&dst_addr, sizeof(dst_addr)); + ck_assert_int_eq(ret, 1); + } + + tx = &s.udpsockets[SOCKET_UNMARK(tx_sd)]; + + (void)wolfIP_poll(&s, now++); + ck_assert_uint_eq(s.loopback_count, WOLFIP_LOOPBACK_QUEUE_DEPTH); + ck_assert_ptr_nonnull(fifo_peek(&tx->sock.udp.txbuf)); + + (void)wolfIP_poll(&s, now++); + ck_assert_uint_ne((uint32_t)(tx->events & CB_EVENT_WRITABLE), 0U); + (void)wolfIP_poll(&s, now++); + ck_assert_ptr_null(fifo_peek(&tx->sock.udp.txbuf)); + + for (;;) { + ret = wolfIP_sock_recvfrom(&s, rx_sd, rxbuf, sizeof(rxbuf), 0, NULL, NULL); + if (ret <= 0) + break; + received++; + } + + ck_assert_int_eq(ret, -WOLFIP_EAGAIN); + ck_assert_int_eq(received, (int)(WOLFIP_LOOPBACK_QUEUE_DEPTH + 1U)); +} +END_TEST + START_TEST(test_regression_tcp_tx_desc_payload_len_keeps_descriptor_layout_sanity) { struct wolfIP s; diff --git a/src/wolfip.c b/src/wolfip.c index 94bcdca4..60c154b8 100644 --- a/src/wolfip.c +++ b/src/wolfip.c @@ -1265,9 +1265,9 @@ struct wolfIP { #endif uint8_t loopback_buf[WOLFIP_LOOPBACK_QUEUE_DEPTH][IP_MTU_MAX]; uint32_t loopback_pending_len[WOLFIP_LOOPBACK_QUEUE_DEPTH]; - uint8_t loopback_head; - uint8_t loopback_tail; - uint8_t loopback_count; + uint32_t loopback_head; + uint32_t loopback_tail; + uint32_t loopback_count; #endif }; @@ -1372,10 +1372,46 @@ static inline uint32_t tcp_tx_payload_cap(const struct tsocket *t) #if WOLFIP_ENABLE_LOOPBACK +static void wolfIP_notify_loopback_space_available(struct wolfIP *s) +{ + int i; + + if (!s) + return; + + for (i = 0; i < MAX_TCPSOCKETS; i++) { + struct tsocket *t = &s->tcpsockets[i]; + if (t->proto != WI_IPPROTO_TCP) + continue; + if (wolfIP_socket_if_idx(t) != WOLFIP_LOOPBACK_IF_IDX) + continue; + if (tx_has_writable_space(t)) + t->events |= CB_EVENT_WRITABLE; + } + for (i = 0; i < MAX_UDPSOCKETS; i++) { + struct tsocket *t = &s->udpsockets[i]; + if (t->proto != WI_IPPROTO_UDP) + continue; + if (wolfIP_socket_if_idx(t) != WOLFIP_LOOPBACK_IF_IDX) + continue; + if (tx_has_writable_space(t)) + t->events |= CB_EVENT_WRITABLE; + } + for (i = 0; i < MAX_ICMPSOCKETS; i++) { + struct tsocket *t = &s->icmpsockets[i]; + if (t->proto != WI_IPPROTO_ICMP) + continue; + if (wolfIP_socket_if_idx(t) != WOLFIP_LOOPBACK_IF_IDX) + continue; + if (tx_has_writable_space(t)) + t->events |= CB_EVENT_WRITABLE; + } +} + static int wolfIP_loopback_send(struct wolfIP_ll_dev *ll, void *buf, uint32_t len) { struct wolfIP *s; - uint8_t slot; + uint32_t slot; if (!ll || !buf) return -1; s = WOLFIP_CONTAINER_OF(ll, struct wolfIP, ll_dev); @@ -1391,7 +1427,7 @@ static int wolfIP_loopback_send(struct wolfIP_ll_dev *ll, void *buf, uint32_t le slot = s->loopback_tail; memcpy(s->loopback_buf[slot], buf, len); s->loopback_pending_len[slot] = len; - s->loopback_tail = (uint8_t)((slot + 1U) % WOLFIP_LOOPBACK_QUEUE_DEPTH); + s->loopback_tail = (slot + 1U) % WOLFIP_LOOPBACK_QUEUE_DEPTH; s->loopback_count++; return (int)len; } @@ -1399,8 +1435,9 @@ static int wolfIP_loopback_send(struct wolfIP_ll_dev *ll, void *buf, uint32_t le static int wolfIP_loopback_poll(struct wolfIP_ll_dev *ll, void *buf, uint32_t len) { struct wolfIP *s; - uint8_t slot; + uint32_t slot; uint32_t pending; + int queue_was_full; if (!ll || !buf) return 0; s = WOLFIP_CONTAINER_OF(ll, struct wolfIP, ll_dev); @@ -1412,10 +1449,13 @@ static int wolfIP_loopback_poll(struct wolfIP_ll_dev *ll, void *buf, uint32_t le pending = s->loopback_pending_len[slot]; if (pending > len) return 0; + queue_was_full = (s->loopback_count >= WOLFIP_LOOPBACK_QUEUE_DEPTH) ? 1 : 0; memcpy(buf, s->loopback_buf[slot], pending); s->loopback_pending_len[slot] = 0; - s->loopback_head = (uint8_t)((slot + 1U) % WOLFIP_LOOPBACK_QUEUE_DEPTH); + s->loopback_head = (slot + 1U) % WOLFIP_LOOPBACK_QUEUE_DEPTH; s->loopback_count--; + if (queue_was_full) + wolfIP_notify_loopback_space_available(s); return (int)pending; } #endif @@ -1436,24 +1476,23 @@ static inline int wolfIP_ll_is_non_ethernet(struct wolfIP *s, unsigned int if_id return (ll && ll->non_ethernet) ? 1 : 0; } -static inline void wolfIP_ll_send_frame(struct wolfIP *s, unsigned int if_idx, - void *buf, uint32_t len) +static inline int wolfIP_ll_send_frame(struct wolfIP *s, unsigned int if_idx, + void *buf, uint32_t len) { struct wolfIP_ll_dev *ll = wolfIP_ll_at(s, if_idx); uint32_t frame_mtu; if (!ll || !ll->send) - return; + return -1; frame_mtu = wolfIP_ll_frame_mtu(ll); if (len > frame_mtu) - return; + return -1; if (ll->non_ethernet) { if (len <= ETH_HEADER_LEN) - return; - ll->send(ll, (uint8_t *)buf + ETH_HEADER_LEN, len - ETH_HEADER_LEN); - return; + return -1; + return ll->send(ll, (uint8_t *)buf + ETH_HEADER_LEN, len - ETH_HEADER_LEN); } - ll->send(ll, buf, len); + return ll->send(ll, buf, len); } static inline struct ipconf *wolfIP_ipconf_at(struct wolfIP *s, unsigned int if_idx) @@ -2605,8 +2644,10 @@ static int tcp_send_empty_immediate(struct tsocket *t, struct wolfIP_tcp_seg *tc } #endif - wolfIP_ll_send_frame(t->S, tx_if, tcp, frame_len); - return 0; + { + int send_ret = wolfIP_ll_send_frame(t->S, tx_if, tcp, frame_len); + return (send_ret < 0) ? send_ret : 0; + } } static int tcp_send_empty(struct tsocket *t, uint8_t flags) @@ -7567,6 +7608,7 @@ int wolfIP_poll(struct wolfIP *s, uint64_t now) while (desc && send_guard++ < send_budget) { unsigned int tx_if = wolfIP_socket_if_idx(ts); struct pkt_desc *next_desc = NULL; + int send_ret = 0; tcp = (struct wolfIP_tcp_seg *)(ts->txmem + desc->pos + sizeof(*desc)); if (desc->flags & PKT_FLAG_SENT) { next_desc = fifo_next(&ts->sock.tcp.txbuf, desc); @@ -7633,15 +7675,22 @@ int wolfIP_poll(struct wolfIP *s, uint64_t now) if (esp_err == 1) { /* ipsec not configured on this interface. * send plaintext. */ - wolfIP_ll_send_frame(s, tx_if, tcp, desc->len); + send_ret = wolfIP_ll_send_frame(s, tx_if, tcp, desc->len); } } else { - wolfIP_ll_send_frame(s, tx_if, tcp, desc->len); + send_ret = wolfIP_ll_send_frame(s, tx_if, tcp, desc->len); } #else - wolfIP_ll_send_frame(s, tx_if, tcp, desc->len); + send_ret = wolfIP_ll_send_frame(s, tx_if, tcp, desc->len); #endif /* WOLFIP_ESP */ } + if (send_ret == -WOLFIP_EAGAIN) { + if (tx_has_writable_space(ts)) + ts->events |= CB_EVENT_WRITABLE; + break; + } + if (send_ret < 0) + break; desc->flags |= PKT_FLAG_SENT; desc->flags &= ~PKT_FLAG_RETRANS; if (is_retrans) @@ -7697,6 +7746,7 @@ int wolfIP_poll(struct wolfIP *s, uint64_t now) while (desc) { struct wolfIP_udp_datagram *udp = (struct wolfIP_udp_datagram *)(t->txmem + desc->pos + sizeof(*desc)); unsigned int tx_if = wolfIP_socket_if_idx(t); + int send_ret = 0; #ifdef ETHERNET struct ipconf *conf = wolfIP_ipconf_at(s, tx_if); ip4 nexthop = wolfIP_select_nexthop(conf, t->remote_ip); @@ -7737,15 +7787,19 @@ int wolfIP_poll(struct wolfIP *s, uint64_t now) if (esp_send(ll, (struct wolfIP_ip_packet *)udp, len) == 1) { /* ipsec not configured on this interface. * send plaintext. */ - wolfIP_ll_send_frame(s, tx_if, udp, desc->len); + send_ret = wolfIP_ll_send_frame(s, tx_if, udp, desc->len); } } else { - wolfIP_ll_send_frame(s, tx_if, udp, desc->len); + send_ret = wolfIP_ll_send_frame(s, tx_if, udp, desc->len); } #else - wolfIP_ll_send_frame(s, tx_if, udp, desc->len); + send_ret = wolfIP_ll_send_frame(s, tx_if, udp, desc->len); #endif /* WOLFIP_ESP */ } + if (send_ret == -WOLFIP_EAGAIN) + break; + if (send_ret < 0) + break; fifo_pop(&t->sock.udp.txbuf); desc = fifo_peek(&t->sock.udp.txbuf); } @@ -7756,6 +7810,7 @@ int wolfIP_poll(struct wolfIP *s, uint64_t now) while (desc) { struct wolfIP_icmp_packet *icmp = (struct wolfIP_icmp_packet *)(t->txmem + desc->pos + sizeof(*desc)); unsigned int tx_if = wolfIP_socket_if_idx(t); + int send_ret = 0; #ifdef ETHERNET struct ipconf *conf = wolfIP_ipconf_at(s, tx_if); ip4 nexthop = wolfIP_select_nexthop(conf, t->remote_ip); @@ -7792,15 +7847,19 @@ int wolfIP_poll(struct wolfIP *s, uint64_t now) if (esp_send(ll, (struct wolfIP_ip_packet *)icmp, len) == 1) { /* ipsec not configured on this interface. * send plaintext. */ - wolfIP_ll_send_frame(s, tx_if, icmp, desc->len); + send_ret = wolfIP_ll_send_frame(s, tx_if, icmp, desc->len); } } else { - wolfIP_ll_send_frame(s, tx_if, icmp, desc->len); + send_ret = wolfIP_ll_send_frame(s, tx_if, icmp, desc->len); } #else - wolfIP_ll_send_frame(s, tx_if, icmp, desc->len); + send_ret = wolfIP_ll_send_frame(s, tx_if, icmp, desc->len); #endif /* WOLFIP_ESP */ } + if (send_ret == -WOLFIP_EAGAIN) + break; + if (send_ret < 0) + break; fifo_pop(&t->sock.udp.txbuf); desc = fifo_peek(&t->sock.udp.txbuf); } From e99e198987e0a86bb24bac29c8e62835851e7399 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Wed, 15 Apr 2026 17:41:01 +0200 Subject: [PATCH 6/8] Addressed review comments: error propagation / backpressure --- src/test/test_wolfguard_loopback.c | 4 +- src/test/unit/unit.c | 3 + src/test/unit/unit_tests_proto.c | 127 +++++++++++++++++++++++++++++ src/wolfip.c | 23 +++++- 4 files changed, 151 insertions(+), 6 deletions(-) diff --git a/src/test/test_wolfguard_loopback.c b/src/test/test_wolfguard_loopback.c index cc1f3303..dd052de5 100644 --- a/src/test/test_wolfguard_loopback.c +++ b/src/test/test_wolfguard_loopback.c @@ -213,7 +213,7 @@ static void setup_loopback_stacks(uint64_t *now) /* ---- Stack A ---- */ wolfIP_init(&stack_a); - /* Physical interface (non_ethernet, index 0) */ + /* Physical interface (non_ethernet, index TEST_PHYS_IF). */ ll = wolfIP_getdev_ex(&stack_a, TEST_PHYS_IF); ll->non_ethernet = 1; ll->poll = phys_a_poll; @@ -236,7 +236,7 @@ static void setup_loopback_stacks(uint64_t *now) /* Stack B */ wolfIP_init(&stack_b); - /* Physical interface (non_ethernet, index 0) */ + /* Physical interface (non_ethernet, index TEST_PHYS_IF). */ ll = wolfIP_getdev_ex(&stack_b, TEST_PHYS_IF); ll->non_ethernet = 1; ll->poll = phys_b_poll; diff --git a/src/test/unit/unit.c b/src/test/unit/unit.c index 49277b79..3325dc0e 100644 --- a/src/test/unit/unit.c +++ b/src/test/unit/unit.c @@ -776,6 +776,9 @@ Suite *wolf_suite(void) tcase_add_test(tc_proto, test_regression_loopback_pure_ack_drain_allows_next_send_cycle); tcase_add_test(tc_proto, test_regression_loopback_pure_ack_immediate_propagates_eagain_when_queue_full); tcase_add_test(tc_proto, test_regression_loopback_udp_tx_backpressure_retries_after_queue_drain); + tcase_add_test(tc_proto, test_regression_ll_send_frame_returns_wolfip_error_codes); + tcase_add_test(tc_proto, test_regression_loopback_ack_retry_pending_requeued_on_poll); + tcase_add_test(tc_proto, test_regression_loopback_queue_full_pure_ack_backpressure_retry); tcase_add_test(tc_proto, test_regression_tcp_tx_desc_payload_len_keeps_descriptor_layout_sanity); tcase_add_test(tc_proto, test_regression_fast_recovery_cwnd_ssthresh_rfc5681); tcase_add_test(tc_proto, test_regression_paws_rejects_stale_timestamp); diff --git a/src/test/unit/unit_tests_proto.c b/src/test/unit/unit_tests_proto.c index b33c821c..60df923b 100644 --- a/src/test/unit/unit_tests_proto.c +++ b/src/test/unit/unit_tests_proto.c @@ -5076,6 +5076,133 @@ START_TEST(test_regression_loopback_udp_tx_backpressure_retries_after_queue_drai } END_TEST +START_TEST(test_regression_ll_send_frame_returns_wolfip_error_codes) +{ + struct wolfIP s; + struct wolfIP_ll_dev *ll; + uint8_t frame[ETH_HEADER_LEN + IP_HEADER_LEN + TCP_HEADER_LEN] = {0}; + + wolfIP_init(&s); + mock_link_init(&s); + ll = wolfIP_getdev_ex(&s, TEST_PRIMARY_IF); + ck_assert_ptr_nonnull(ll); + + ck_assert_int_eq( + wolfIP_ll_send_frame(NULL, TEST_PRIMARY_IF, frame, sizeof(frame)), + -WOLFIP_EINVAL); + ck_assert_int_eq( + wolfIP_ll_send_frame(&s, WOLFIP_MAX_INTERFACES, frame, sizeof(frame)), + -WOLFIP_EINVAL); + + ll->send = NULL; + ck_assert_int_eq( + wolfIP_ll_send_frame(&s, TEST_PRIMARY_IF, frame, sizeof(frame)), + -WOLFIP_EINVAL); + + ll->send = mock_send; + ck_assert_int_eq( + wolfIP_ll_send_frame(&s, TEST_PRIMARY_IF, frame, LINK_MTU + 1U), + -WOLFIP_EINVAL); + + ll->non_ethernet = 1; + ck_assert_int_eq( + wolfIP_ll_send_frame(&s, TEST_PRIMARY_IF, frame, ETH_HEADER_LEN), + -WOLFIP_EINVAL); +} +END_TEST + +START_TEST(test_regression_loopback_ack_retry_pending_requeued_on_poll) +{ + struct wolfIP s; + struct tsocket *ts; + struct wolfIP_ll_dev *loop; + uint8_t frame[16] = {0}; + uint8_t rx[IP_MTU_MAX]; + unsigned int i; + uint64_t now = 300; + + wolfIP_init(&s); + loop = wolfIP_getdev_ex(&s, TEST_LOOPBACK_IF); + ck_assert_ptr_nonnull(loop); + + for (i = 0; i < WOLFIP_LOOPBACK_QUEUE_DEPTH; i++) { + ck_assert_int_eq(wolfIP_loopback_send(loop, frame, sizeof(frame)), + (int)sizeof(frame)); + } + + ts = &s.tcpsockets[0]; + memset(ts, 0, sizeof(*ts)); + ts->proto = WI_IPPROTO_TCP; + ts->S = &s; + ts->if_idx = TEST_LOOPBACK_IF; + ts->sock.tcp.state = TCP_ESTABLISHED; + ts->sock.tcp.ack = 100; + ts->sock.tcp.seq = 1000; + ts->src_port = 1234; + ts->dst_port = 4321; + ts->local_ip = 0x7F000001U; + ts->remote_ip = 0x7F000001U; + + tcp_send_ack(ts); + ck_assert_uint_eq(ts->sock.tcp.ack_retry_pending, 1U); + + ck_assert_int_gt(loop->poll(loop, rx, sizeof(rx)), 0); + + loop->poll = mock_poll; + (void)wolfIP_poll(&s, now); + + ck_assert_uint_eq(ts->sock.tcp.ack_retry_pending, 0U); + ck_assert_uint_eq(s.loopback_count, WOLFIP_LOOPBACK_QUEUE_DEPTH); +} +END_TEST + +START_TEST(test_regression_loopback_queue_full_pure_ack_backpressure_retry) +{ + struct wolfIP s; + struct tsocket *ts; + struct wolfIP_ll_dev *loop; + uint8_t frame[16] = {0}; + uint8_t rx[IP_MTU_MAX]; + unsigned int i; + uint64_t now = 350; + + wolfIP_init(&s); + loop = wolfIP_getdev_ex(&s, TEST_LOOPBACK_IF); + ck_assert_ptr_nonnull(loop); + + for (i = 0; i < WOLFIP_LOOPBACK_QUEUE_DEPTH; i++) { + ck_assert_int_eq(wolfIP_loopback_send(loop, frame, sizeof(frame)), + (int)sizeof(frame)); + } + + ts = &s.tcpsockets[1]; + memset(ts, 0, sizeof(*ts)); + ts->proto = WI_IPPROTO_TCP; + ts->S = &s; + ts->if_idx = TEST_LOOPBACK_IF; + ts->sock.tcp.state = TCP_ESTABLISHED; + ts->sock.tcp.ack = 200; + ts->sock.tcp.seq = 300; + ts->src_port = 5000; + ts->dst_port = 5001; + ts->local_ip = 0x7F000001U; + ts->remote_ip = 0x7F000001U; + + /* Queue full -> pure ACK send must backpressure and mark retry pending. */ + tcp_send_ack(ts); + ck_assert_uint_eq(ts->sock.tcp.ack_retry_pending, 1U); + ck_assert_uint_eq(s.loopback_count, WOLFIP_LOOPBACK_QUEUE_DEPTH); + + /* Free one slot, then poll should retry and enqueue the pending ACK. */ + ck_assert_int_gt(loop->poll(loop, rx, sizeof(rx)), 0); + loop->poll = mock_poll; + (void)wolfIP_poll(&s, now); + + ck_assert_uint_eq(ts->sock.tcp.ack_retry_pending, 0U); + ck_assert_uint_eq(s.loopback_count, WOLFIP_LOOPBACK_QUEUE_DEPTH); +} +END_TEST + START_TEST(test_regression_tcp_tx_desc_payload_len_keeps_descriptor_layout_sanity) { struct wolfIP s; diff --git a/src/wolfip.c b/src/wolfip.c index 60c154b8..69244514 100644 --- a/src/wolfip.c +++ b/src/wolfip.c @@ -1084,6 +1084,7 @@ struct tcpsocket { uint8_t ctrl_rto_active; uint8_t fin_wait_2_timeout_active; uint8_t is_listener; + uint8_t ack_retry_pending; ip4 local_ip, remote_ip; uint32_t peer_rwnd; uint16_t peer_mss; @@ -1483,13 +1484,13 @@ static inline int wolfIP_ll_send_frame(struct wolfIP *s, unsigned int if_idx, uint32_t frame_mtu; if (!ll || !ll->send) - return -1; + return -WOLFIP_EINVAL; frame_mtu = wolfIP_ll_frame_mtu(ll); if (len > frame_mtu) - return -1; + return -WOLFIP_EINVAL; if (ll->non_ethernet) { if (len <= ETH_HEADER_LEN) - return -1; + return -WOLFIP_EINVAL; return ll->send(ll, (uint8_t *)buf + ETH_HEADER_LEN, len - ETH_HEADER_LEN); } return ll->send(ll, buf, len); @@ -2681,7 +2682,14 @@ static int tcp_send_empty(struct tsocket *t, uint8_t flags) static void tcp_send_ack(struct tsocket *t) { - (void)tcp_send_empty(t, TCP_FLAG_ACK); + int ret = tcp_send_empty(t, TCP_FLAG_ACK); + + if (!t) + return; + if (ret == -WOLFIP_EAGAIN) + t->sock.tcp.ack_retry_pending = 1; + else if (ret >= 0) + t->sock.tcp.ack_retry_pending = 0; } static void tcp_send_reset_reply(struct wolfIP *s, unsigned int if_idx, @@ -7600,6 +7608,13 @@ int wolfIP_poll(struct wolfIP *s, uint64_t now) struct pkt_desc *desc; struct wolfIP_tcp_seg *tcp; tcp_resync_inflight(s, ts, now); + if (ts->sock.tcp.ack_retry_pending) { + int ack_ret = tcp_send_empty(ts, TCP_FLAG_ACK); + if (ack_ret == -WOLFIP_EAGAIN) + ts->sock.tcp.ack_retry_pending = 1; + else if (ack_ret >= 0) + ts->sock.tcp.ack_retry_pending = 0; + } in_flight = ts->sock.tcp.bytes_in_flight; if (ts->sock.tcp.persist_active && (ts->sock.tcp.peer_rwnd > 0 || !tcp_has_pending_unsent_payload(ts))) From a2a64e3a593b85a32a7c4fcac1d95e399458bd8c Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Wed, 15 Apr 2026 18:33:12 +0200 Subject: [PATCH 7/8] Fix cppcheck finding --- src/wolfip.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/wolfip.c b/src/wolfip.c index 69244514..026e3a02 100644 --- a/src/wolfip.c +++ b/src/wolfip.c @@ -2657,6 +2657,9 @@ static int tcp_send_empty(struct tsocket *t, uint8_t flags) uint8_t opt_len; uint8_t buffer[sizeof(struct wolfIP_tcp_seg) + TCP_MAX_OPTIONS_LEN]; uint32_t frame_len; + + if (!t) + return -WOLFIP_EINVAL; tcp = (struct wolfIP_tcp_seg *)buffer; memset(tcp, 0, sizeof(buffer)); opt_len = tcp_build_ack_options(t, tcp->data, TCP_MAX_OPTIONS_LEN); @@ -2682,10 +2685,9 @@ static int tcp_send_empty(struct tsocket *t, uint8_t flags) static void tcp_send_ack(struct tsocket *t) { - int ret = tcp_send_empty(t, TCP_FLAG_ACK); - if (!t) return; + int ret = tcp_send_empty(t, TCP_FLAG_ACK); if (ret == -WOLFIP_EAGAIN) t->sock.tcp.ack_retry_pending = 1; else if (ret >= 0) From 943e3706ca3af865dfa76bb683158dac43c427ff Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Wed, 15 Apr 2026 22:07:54 +0200 Subject: [PATCH 8/8] Addressed more copilot comments + fix decl-after-statement --- config.h | 2 +- src/wolfip.c | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/config.h b/config.h index 8b063355..e37c1f43 100644 --- a/config.h +++ b/config.h @@ -32,7 +32,7 @@ #define MAX_NEIGHBORS 16 #ifndef WOLFIP_MAX_INTERFACES -#define WOLFIP_MAX_INTERFACES 3 +#define WOLFIP_MAX_INTERFACES 2 #endif #ifndef WOLFIP_ENABLE_FORWARDING diff --git a/src/wolfip.c b/src/wolfip.c index 026e3a02..920cd91f 100644 --- a/src/wolfip.c +++ b/src/wolfip.c @@ -1480,9 +1480,12 @@ static inline int wolfIP_ll_is_non_ethernet(struct wolfIP *s, unsigned int if_id static inline int wolfIP_ll_send_frame(struct wolfIP *s, unsigned int if_idx, void *buf, uint32_t len) { - struct wolfIP_ll_dev *ll = wolfIP_ll_at(s, if_idx); + struct wolfIP_ll_dev *ll; uint32_t frame_mtu; + if (!s) + return -WOLFIP_EINVAL; + ll = wolfIP_ll_at(s, if_idx); if (!ll || !ll->send) return -WOLFIP_EINVAL; frame_mtu = wolfIP_ll_frame_mtu(ll); @@ -2685,9 +2688,11 @@ static int tcp_send_empty(struct tsocket *t, uint8_t flags) static void tcp_send_ack(struct tsocket *t) { + int ret; + if (!t) return; - int ret = tcp_send_empty(t, TCP_FLAG_ACK); + ret = tcp_send_empty(t, TCP_FLAG_ACK); if (ret == -WOLFIP_EAGAIN) t->sock.tcp.ack_retry_pending = 1; else if (ret >= 0)