diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 53664af..e45a919 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -90,6 +90,18 @@ 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: | + 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/Makefile b/Makefile index 31ed261..395288f 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/src/test/test_wolfguard_loopback.c b/src/test/test_wolfguard_loopback.c index 879262a..dd052de 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 @@ -205,47 +213,47 @@ static void setup_loopback_stacks(uint64_t *now) /* ---- Stack A ---- */ wolfIP_init(&stack_a); - /* Physical interface (non_ethernet, index 0) */ - ll = wolfIP_getdev_ex(&stack_a, 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; 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); + /* 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; 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 */ diff --git a/src/test/unit/unit.c b/src/test/unit/unit.c index 6660506..3325dc0 100644 --- a/src/test/unit/unit.c +++ b/src/test/unit/unit.c @@ -115,8 +115,10 @@ 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_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); @@ -770,7 +772,13 @@ 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_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_esp.c b/src/test/unit/unit_esp.c index 133dd48..3bc70ed 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 36bd9fb..60df923 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); @@ -2997,7 +2998,60 @@ 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 return -WOLFIP_EAGAIN because the queue is full. */ + ck_assert_int_eq(wolfIP_loopback_send(loop, frame, sizeof(frame)), + -WOLFIP_EAGAIN); +} +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, 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)); + 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 @@ -3019,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]; - if (off == 0) - return; + 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)); + } - ll = (struct wolfIP_ll_dev *)off; - ck_assert_int_eq(wolfIP_loopback_send(ll, frame, sizeof(frame)), -1); + /* Queue-full must return -WOLFIP_EAGAIN, not 0. */ + ck_assert_int_eq(wolfIP_loopback_send(loop, frame, sizeof(frame)), + -WOLFIP_EAGAIN); + + /* 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)); + + /* 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 @@ -4786,17 +4860,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 +4897,309 @@ 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_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_count, 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_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_count, 0U); + + ck_assert_int_eq(tcp_send_empty_immediate(ts, &seg, (uint32_t)sizeof(seg)), 0); + ck_assert_uint_eq(s.loopback_count, 1U); + ck_assert_uint_eq(s.loopback_pending_len[s.loopback_head], + expected_pending_len); +} +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_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 diff --git a/src/wolfip.c b/src/wolfip.c index 2b9dffd..920cd91 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 @@ -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; @@ -1259,6 +1260,16 @@ struct wolfIP { } arp; struct arp_pending_entry arp_pending[WOLFIP_ARP_PENDING_MAX]; #endif +#if WOLFIP_ENABLE_LOOPBACK +#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]; + uint32_t loopback_head; + uint32_t loopback_tail; + uint32_t loopback_count; +#endif }; static inline int tx_has_writable_space(const struct tsocket *t) @@ -1362,21 +1373,91 @@ 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; - uint32_t copy = len; - uint8_t frame[LINK_MTU]; + uint32_t slot; 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 == 0 || len > IP_MTU_MAX) return 0; - memcpy(frame, buf, copy); - wolfIP_recv_on(s, WOLFIP_LOOPBACK_IF_IDX, frame, copy); - return (int)copy; + if (s->loopback_count >= WOLFIP_LOOPBACK_QUEUE_DEPTH) + 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. */ + slot = s->loopback_tail; + memcpy(s->loopback_buf[slot], buf, len); + s->loopback_pending_len[slot] = len; + s->loopback_tail = (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; + uint32_t slot; + uint32_t pending; + int queue_was_full; + if (!ll || !buf) + return 0; + s = WOLFIP_CONTAINER_OF(ll, struct wolfIP, ll_dev); + if (!s) + return 0; + if (s->loopback_count == 0) + return 0; + slot = s->loopback_head; + 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 = (slot + 1U) % WOLFIP_LOOPBACK_QUEUE_DEPTH; + s->loopback_count--; + if (queue_was_full) + wolfIP_notify_loopback_space_available(s); + return (int)pending; } #endif @@ -1396,24 +1477,26 @@ 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); + 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; + return -WOLFIP_EINVAL; frame_mtu = wolfIP_ll_frame_mtu(ll); if (len > frame_mtu) - return; + return -WOLFIP_EINVAL; 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 -WOLFIP_EINVAL; + 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) @@ -2565,8 +2648,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) @@ -2575,6 +2660,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); @@ -2600,7 +2688,15 @@ 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; + + if (!t) + return; + ret = tcp_send_empty(t, TCP_FLAG_ACK); + 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, @@ -6634,7 +6730,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 +6803,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) @@ -7517,6 +7615,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))) @@ -7525,6 +7630,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); @@ -7591,15 +7697,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) @@ -7655,6 +7768,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); @@ -7695,15 +7809,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); } @@ -7714,6 +7832,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); @@ -7750,15 +7869,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); } diff --git a/wolfip.h b/wolfip.h index 64ad7d4..b7e6667 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