From f9d01b7e574f3a3c1845e0ed863538302182d764 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Fri, 2 Oct 2026 14:01:26 +0200 Subject: [PATCH 01/10] amd: drop the default profile to three UDP sockets The zynq7000 OCM layout has .bss end just below 0xFFFF0000, followed by the 16 KB-aligned MMU page tables. Once .bss crosses that boundary the page tables and the DMA buffers move up a full 16 KB and the image no longer fits in the 256 KB OCM. Master had 1320 bytes left before the boundary, so any core change larger than that broke the zynq7000 default build with "region 'OCM' overflowed by 4352 bytes". The default application opens two UDP sockets on every AMD board, one for DHCP and one for the echo service, so three still leaves a spare. Dropping the fourth socket frees its receive and transmit buffers and leaves about 18 KB before the zynq7000 boundary. The profile is shared by all three boards, so zcu102 and versal get the same socket count. The SPEED_TEST profile already uses two. --- src/port/amd/common/wolfip_config.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/port/amd/common/wolfip_config.h b/src/port/amd/common/wolfip_config.h index 7fbf0a84..8d6f7228 100644 --- a/src/port/amd/common/wolfip_config.h +++ b/src/port/amd/common/wolfip_config.h @@ -58,7 +58,7 @@ #define TXBUF_SIZE (LINK_MTU * 6) #else #define MAX_TCPSOCKETS 2 -#define MAX_UDPSOCKETS 4 +#define MAX_UDPSOCKETS 3 #define MAX_ICMPSOCKETS 1 #define RXBUF_SIZE (LINK_MTU * 4) #define TXBUF_SIZE (LINK_MTU * 4) From a24f642706bfd0a6a6decec1edf4f17b73f35f95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Thu, 1 Oct 2026 14:46:27 +0200 Subject: [PATCH 02/10] core: refuse a descriptor whose socket slot was handed out again A wolfIP descriptor is a slot index with a type mark, so once the stack frees a socket the next wolfIP_sock_socket() or wolfIP_sock_accept() returns the very same number. Anything still holding the old one then acts on the new socket: a second close() closes it, a late abort() resets someone else's connection. The stack frees TCP sockets on its own (final ACK, peer RST, retransmission give-up), so a caller cannot avoid this by being careful, and every blocking wrapper had to build its own bookkeeping to detect it. API.md documented the hazard instead of removing it. Each slot now carries a 15-bit generation, kept in bits 16-30 of its descriptors, so a descriptor only matches again after 32768 reuses of its slot. It changes when the application closes the socket, and when the stack hands out a slot it freed while the application still held the descriptor; for TCP that happens in tcp_new_socket(), so a slot the stack takes for a socket of its own retires the old descriptor too. Every call that takes a descriptor checks it and returns -WOLFIP_EBADF for a stale one, and callbacks pass the current one. A slot's first generation is 0, so descriptors keep their old values until a slot is reused, and so do the literal descriptors in the tests. SOCKET_UNMARK() and the IS_SOCKET_*() marks are unchanged. Closing a descriptor whose slot the stack freed but has not reused still returns 0, as before. To keep that the common case, tcp_new_socket() takes such a slot only when no other one is free, so a peer reset is reported as a closed connection rather than -WOLFIP_EBADF unless the table is under pressure. Because such a descriptor still passes the check, a callback registered through it lands in the free slot, so tcp_new_socket() now clears the callback and pending events of every slot it claims. The POSIX layer mapped each core slot to its wrapper entry by slot index alone, so once a held slot was reused, closing the old descriptor cleared the new socket's mapping and its callbacks were lost. Its lookup now matches the whole descriptor, generation included, and a detach only clears a mapping that is still its own. --- docs/API.md | 7 +- src/port/posix/bsd_socket.c | 5 + src/test/test_posix_errno.c | 21 ++ src/test/unit/unit.c | 7 + src/test/unit/unit_shared.c | 11 ++ src/test/unit/unit_tests_poll_dispatcher.c | 4 +- src/test/unit/unit_tests_socket_api_arms.c | 164 ++++++++++++++++ src/wolfip.c | 214 +++++++++++++++++++-- wolfip.h | 8 + 9 files changed, 417 insertions(+), 24 deletions(-) diff --git a/docs/API.md b/docs/API.md index ef7f2512..647c1b7c 100644 --- a/docs/API.md +++ b/docs/API.md @@ -162,11 +162,11 @@ Accepts a connection on a listening socket. ```c int wolfIP_sock_abort(struct wolfIP *s, int sockfd); ``` -Abortive close, like `SO_LINGER` with a zero timeout: sends an RST in `SYN_RCVD`, `ESTABLISHED`, `CLOSE_WAIT`, `FIN_WAIT_1` and `FIN_WAIT_2` (other states, such as `CLOSING` and `LAST_ACK`, are released without one), and releases the socket at once instead of waiting for a FIN exchange the peer may never complete. Also valid on a socket whose `wolfIP_sock_close()` returned `-WOLFIP_EAGAIN`, as long as no socket has been created or accepted since (see the return values under Data Transfer). +Abortive close, like `SO_LINGER` with a zero timeout: sends an RST in `SYN_RCVD`, `ESTABLISHED`, `CLOSE_WAIT`, `FIN_WAIT_1` and `FIN_WAIT_2` (other states, such as `CLOSING` and `LAST_ACK`, are released without one), and releases the socket at once instead of waiting for a FIN exchange the peer may never complete. Also valid on a socket whose `wolfIP_sock_close()` returned `-WOLFIP_EAGAIN`, until the stack releases it and its slot is handed out again (see the return values under Data Transfer). - Parameters: - s: wolfIP instance - sockfd: TCP socket descriptor -- Returns: 0 on success, `-WOLFIP_EINVAL` for a bad or non-TCP descriptor +- Returns: 0 on success, `-WOLFIP_EINVAL` for a bad or non-TCP descriptor, `-WOLFIP_EBADF` for a stale one ### Data Transfer ```c @@ -197,9 +197,10 @@ wolfIP never blocks, so every call above can ask the caller to retry. On a TCP s | `0` | End of stream: the peer closed and nothing is left to read | | `-WOLFIP_EAGAIN` | Retry later: no data queued, no transmit space, or the socket is still connecting (`SYN_SENT`/`SYN_RCVD`) | | `-WOLFIP_EINVAL` | Bad descriptor or arguments | +| `-WOLFIP_EBADF` | Stale descriptor: its socket was released and the slot handed out again | | `-1` | The operation cannot succeed on this socket (a listener, or a closing state) | -`wolfIP_sock_close()` follows the same convention: on a connected socket it starts the FIN exchange and returns `-WOLFIP_EAGAIN`. The stack then releases the descriptor by itself, without notification, once the exchange completes, the peer resets, or the close times out, and the next `wolfIP_sock_socket()` or `wolfIP_sock_accept()` can hand out the same number. Calling `wolfIP_sock_close()` or `wolfIP_sock_abort()` on it again is therefore only safe while no socket has been created or accepted since; after that, the call acts on the new socket. +`wolfIP_sock_close()` follows the same convention: on a connected socket it starts the FIN exchange and returns `-WOLFIP_EAGAIN`. The stack then releases the descriptor by itself, without notification, once the exchange completes, the peer resets, or the close times out, and the next `wolfIP_sock_socket()` or `wolfIP_sock_accept()` can reuse its slot. A descriptor carries the generation of its slot in bits 16-30, so once the slot has been handed out again, every call on the old descriptor returns `-WOLFIP_EBADF` instead of acting on the new socket. The generation wraps after 32768 reuses of the same slot. A slot the stack released on its own (peer reset, retransmission timeout) while the application still holds the descriptor is reused only when no other slot is free; from then on that descriptor, too, answers `-WOLFIP_EBADF` instead of reporting the connection as closed. ## Stack Interface Functions diff --git a/src/port/posix/bsd_socket.c b/src/port/posix/bsd_socket.c index 2fc169a1..99cff2fd 100644 --- a/src/port/posix/bsd_socket.c +++ b/src/port/posix/bsd_socket.c @@ -292,6 +292,9 @@ static struct wolfip_fd_entry *wolfip_entry_from_internal(int internal_fd) return NULL; if (!wolfip_fd_entries[idx].in_use) return NULL; + /* A reused slot maps to the descriptor generation that replaced this one. */ + if (wolfip_fd_entries[idx].internal_fd != internal_fd) + return NULL; return &wolfip_fd_entries[idx]; } @@ -306,6 +309,8 @@ static struct wolfip_fd_entry *wolfip_entry_from_public(int public_fd) static void wolfip_fd_detach_internal(int internal_fd) { + if (wolfip_entry_from_internal(internal_fd) == NULL) + return; if (IS_SOCKET_TCP(internal_fd)) { int pos = SOCKET_UNMARK(internal_fd); if (pos >= 0 && pos < MAX_TCPSOCKETS) diff --git a/src/test/test_posix_errno.c b/src/test/test_posix_errno.c index 6010747a..a3e3fabb 100644 --- a/src/test/test_posix_errno.c +++ b/src/test/test_posix_errno.c @@ -191,6 +191,26 @@ static void test_fd_lookup_synchronization(void) assert(close(fd) == 0); } +/* The stack reused a TCP slot while the application still held its old + * descriptor: closing the old one must not unmap the new one. */ +static void test_reused_slot_keeps_new_mapping(void) +{ + const int old_internal = MARK_TCP_SOCKET | 1; + const int new_internal = MARK_TCP_SOCKET | 1 | (1 << 16); + int old_fd; + int new_fd; + + old_fd = wolfip_fd_alloc(old_internal, 0); + new_fd = wolfip_fd_alloc(new_internal, 0); + assert(old_fd >= 0 && new_fd >= 0 && old_fd != new_fd); + assert(wolfip_entry_from_internal(new_internal) == &wolfip_fd_entries[new_fd]); + assert(wolfip_entry_from_internal(old_internal) == NULL); + wolfip_fd_release(old_fd); + assert(wolfip_entry_from_internal(new_internal) == &wolfip_fd_entries[new_fd]); + wolfip_fd_release(new_fd); + assert(wolfip_entry_from_internal(new_internal) == NULL); +} + int main(void) { struct sockaddr_in peer; @@ -225,6 +245,7 @@ int main(void) printf("F-4950 regression test passed\n"); test_host_call_publication(); test_fd_lookup_synchronization(); + test_reused_slot_keeps_new_mapping(); printf("POSIX concurrency regression tests passed\n"); return 0; } diff --git a/src/test/unit/unit.c b/src/test/unit/unit.c index 887909fc..dda7207e 100644 --- a/src/test/unit/unit.c +++ b/src/test/unit/unit.c @@ -1386,6 +1386,13 @@ Suite *wolf_suite(void) tcase_add_test(tc_core, test_notify_loopback_tcp_sets_writable); tcase_add_test(tc_core, test_notify_loopback_tcp_non_loopback_not_notified); tcase_add_test(tc_core, test_notify_loopback_null_stack_no_crash); + tcase_add_test(tc_core, test_sock_fd_reissued_slot_rejects_old_descriptor); + tcase_add_test(tc_core, test_sock_fd_slot_freed_by_stack_gets_new_generation); + tcase_add_test(tc_core, test_sock_fd_callback_carries_generation); + tcase_add_test(tc_core, test_sock_fd_internal_reuse_retires_open_descriptor); + tcase_add_test(tc_core, test_sock_fd_held_slot_is_reused_last); + tcase_add_test(tc_core, test_sock_fd_reused_held_slot_drops_old_callback); + tcase_add_test(tc_core, test_sock_fd_generation_wraps_positive); /* === Branch-coverage tests from fleet ===*/ /* --- unit_tests_tcp_state.c (72 tests) --- */ diff --git a/src/test/unit/unit_shared.c b/src/test/unit/unit_shared.c index a6c51c09..578a98fb 100644 --- a/src/test/unit/unit_shared.c +++ b/src/test/unit/unit_shared.c @@ -496,6 +496,17 @@ static void inject_tcp_syn(struct wolfIP *s, unsigned int if_idx, ip4 dst_ip, ui tcp_input(s, if_idx, &syn, sizeof(struct wolfIP_eth_frame) + IP_HEADER_LEN + TCP_HEADER_LEN); } +/* Marks every other free TCP slot used, so the next allocation must take skip. */ +static void take_tcp_slots_except(struct wolfIP *s, int skip) +{ + int i; + + for (i = 0; i < MAX_TCPSOCKETS; i++) { + if (i != skip && s->tcpsockets[i].proto == 0) + s->tcpsockets[i].proto = WI_IPPROTO_TCP; + } +} + static void inject_tcp_segment(struct wolfIP *s, unsigned int if_idx, ip4 src_ip, ip4 dst_ip, uint16_t src_port, uint16_t dst_port, uint32_t seq, uint32_t ack, uint8_t flags) { diff --git a/src/test/unit/unit_tests_poll_dispatcher.c b/src/test/unit/unit_tests_poll_dispatcher.c index e1dce78b..74a5124e 100644 --- a/src/test/unit/unit_tests_poll_dispatcher.c +++ b/src/test/unit/unit_tests_poll_dispatcher.c @@ -530,7 +530,7 @@ START_TEST(test_poll_raw_socket_callback_reraised_event_survives) /* The reopened socket reuses slot 0; the event raised during dispatch * must not be wiped by the old iteration's clear. */ ck_assert_int_ge(f10259_reopen_fd, 0); - ck_assert_int_eq(f10259_reopen_fd, raw_sd); + ck_assert_int_eq(SOCKET_UNMARK(f10259_reopen_fd), SOCKET_UNMARK(raw_sd)); ck_assert(s.rawsockets[SOCKET_UNMARK(f10259_reopen_fd)].events & CB_EVENT_WRITABLE); @@ -582,7 +582,7 @@ START_TEST(test_poll_packet_socket_callback_reraised_event_survives) (void)wolfIP_poll(&s, 100); ck_assert_int_ge(f10259_pkt_reopen_fd, 0); - ck_assert_int_eq(f10259_pkt_reopen_fd, pkt_sd); + ck_assert_int_eq(SOCKET_UNMARK(f10259_pkt_reopen_fd), SOCKET_UNMARK(pkt_sd)); ck_assert(s.packetsockets[SOCKET_UNMARK(f10259_pkt_reopen_fd)].events & CB_EVENT_WRITABLE); diff --git a/src/test/unit/unit_tests_socket_api_arms.c b/src/test/unit/unit_tests_socket_api_arms.c index be1f441f..b6b9e4a4 100644 --- a/src/test/unit/unit_tests_socket_api_arms.c +++ b/src/test/unit/unit_tests_socket_api_arms.c @@ -2026,3 +2026,167 @@ START_TEST(test_notify_loopback_null_stack_no_crash) wolfIP_notify_loopback_space_available(NULL); } END_TEST + +START_TEST(test_sock_fd_reissued_slot_rejects_old_descriptor) +{ + struct wolfIP s; + struct wolfIP_sockaddr_in sin; + int old_sd, new_sd; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + old_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_STREAM, 0); + ck_assert_int_eq(old_sd, MARK_TCP_SOCKET | 0); + ck_assert_int_eq(wolfIP_sock_close(&s, old_sd), 0); + new_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_STREAM, 0); + ck_assert_int_eq(SOCKET_UNMARK(new_sd), SOCKET_UNMARK(old_sd)); + ck_assert_int_ne(new_sd, old_sd); + + wolfIP_register_callback(&s, new_sd, test_socket_cb, (void *)0x42); + wolfIP_register_callback(&s, old_sd, NULL, NULL); + ck_assert_ptr_eq(s.tcpsockets[0].callback, test_socket_cb); + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = ee16(8080); + ck_assert_int_eq(wolfIP_sock_bind(&s, old_sd, (struct wolfIP_sockaddr *)&sin, + sizeof(sin)), -WOLFIP_EBADF); + ck_assert_int_eq(wolfIP_sock_listen(&s, old_sd, 1), -WOLFIP_EBADF); + ck_assert_int_eq(wolfIP_sock_abort(&s, old_sd), -WOLFIP_EBADF); + ck_assert_int_eq(wolfIP_sock_close(&s, old_sd), -WOLFIP_EBADF); + ck_assert_int_eq(s.tcpsockets[0].proto, WI_IPPROTO_TCP); + + ck_assert_int_eq(wolfIP_sock_bind(&s, new_sd, (struct wolfIP_sockaddr *)&sin, + sizeof(sin)), 0); + ck_assert_int_eq(wolfIP_sock_listen(&s, new_sd, 1), 0); + ck_assert_int_eq(wolfIP_sock_close(&s, new_sd), 0); +} +END_TEST + +START_TEST(test_sock_fd_slot_freed_by_stack_gets_new_generation) +{ + struct wolfIP s; + int old_sd, new_sd; + + wolfIP_init(&s); + mock_link_init(&s); + + /* The stack frees the slot while the application still holds old_sd. */ + old_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_STREAM, 0); + ck_assert_int_ge(old_sd, 0); + close_socket(&s.tcpsockets[SOCKET_UNMARK(old_sd)]); + take_tcp_slots_except(&s, SOCKET_UNMARK(old_sd)); + new_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_STREAM, 0); + ck_assert_int_eq(SOCKET_UNMARK(new_sd), SOCKET_UNMARK(old_sd)); + ck_assert_int_ne(new_sd, old_sd); + ck_assert_int_eq(wolfIP_sock_close(&s, old_sd), -WOLFIP_EBADF); + ck_assert_int_eq(s.tcpsockets[SOCKET_UNMARK(new_sd)].proto, WI_IPPROTO_TCP); + + /* Closing a descriptor whose slot was freed but not reissued still works. */ + close_socket(&s.tcpsockets[SOCKET_UNMARK(new_sd)]); + ck_assert_int_eq(wolfIP_sock_close(&s, new_sd), 0); + ck_assert_int_eq(wolfIP_sock_close(&s, new_sd), -WOLFIP_EBADF); +} +END_TEST + +START_TEST(test_sock_fd_callback_carries_generation) +{ + struct wolfIP s; + int sd; + + wolfIP_init(&s); + mock_link_init(&s); + + sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + ck_assert_int_eq(wolfIP_sock_close(&s, sd), 0); + sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + ck_assert_int_ne(sd, MARK_UDP_SOCKET | 0); + wolfIP_register_callback(&s, sd, test_socket_cb, NULL); + s.udpsockets[SOCKET_UNMARK(sd)].events = CB_EVENT_READABLE; + socket_cb_calls = 0; + socket_cb_last_fd = -1; + (void)wolfIP_poll(&s, 100); + ck_assert_int_eq(socket_cb_calls, 1); + ck_assert_int_eq(socket_cb_last_fd, sd); +} +END_TEST + +START_TEST(test_sock_fd_internal_reuse_retires_open_descriptor) +{ + struct wolfIP s; + int sd; + struct tsocket *t; + + wolfIP_init(&s); + mock_link_init(&s); + + /* The stack frees the slot and reuses it for a socket of its own. */ + sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_STREAM, 0); + ck_assert_int_ge(sd, 0); + close_socket(&s.tcpsockets[SOCKET_UNMARK(sd)]); + take_tcp_slots_except(&s, SOCKET_UNMARK(sd)); + t = tcp_new_socket(&s); + ck_assert_ptr_eq(t, &s.tcpsockets[SOCKET_UNMARK(sd)]); + t->sock.tcp.state = TCP_ESTABLISHED; + ck_assert_int_eq(wolfIP_sock_abort(&s, sd), -WOLFIP_EBADF); + ck_assert_int_eq(wolfIP_sock_close(&s, sd), -WOLFIP_EBADF); + ck_assert_int_eq(t->proto, WI_IPPROTO_TCP); +} +END_TEST + +START_TEST(test_sock_fd_held_slot_is_reused_last) +{ + struct wolfIP s; + int held, other; + + wolfIP_init(&s); + mock_link_init(&s); + + /* The stack frees a slot the application still holds a descriptor to. */ + held = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_STREAM, 0); + close_socket(&s.tcpsockets[SOCKET_UNMARK(held)]); + other = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_STREAM, 0); + ck_assert_int_ne(SOCKET_UNMARK(other), SOCKET_UNMARK(held)); + ck_assert_int_eq(wolfIP_sock_close(&s, held), 0); + ck_assert_int_eq(wolfIP_sock_close(&s, other), 0); +} +END_TEST + +START_TEST(test_sock_fd_reused_held_slot_drops_old_callback) +{ + struct wolfIP s; + int held, sd; + + wolfIP_init(&s); + mock_link_init(&s); + + held = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_STREAM, 0); + close_socket(&s.tcpsockets[SOCKET_UNMARK(held)]); + /* The descriptor is not stale yet, so this lands in the free slot. */ + wolfIP_register_callback(&s, held, test_socket_cb, (void *)0x42); + take_tcp_slots_except(&s, SOCKET_UNMARK(held)); + sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_STREAM, 0); + ck_assert_int_eq(SOCKET_UNMARK(sd), SOCKET_UNMARK(held)); + ck_assert_ptr_null(s.tcpsockets[SOCKET_UNMARK(sd)].callback); + ck_assert_ptr_null(s.tcpsockets[SOCKET_UNMARK(sd)].callback_arg); +} +END_TEST + +START_TEST(test_sock_fd_generation_wraps_positive) +{ + struct wolfIP s; + int sd; + + wolfIP_init(&s); + mock_link_init(&s); + s.tcp_gen[0].gen = SOCKET_GEN_MASK; + sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_STREAM, 0); + ck_assert_int_gt(sd, 0); + ck_assert_uint_eq(SOCKET_GEN(sd), SOCKET_GEN_MASK); + ck_assert_int_eq(wolfIP_sock_close(&s, sd), 0); + ck_assert_uint_eq(s.tcp_gen[0].gen, 0); + ck_assert_int_eq(wolfIP_sock_close(&s, sd), -WOLFIP_EBADF); +} +END_TEST diff --git a/src/wolfip.c b/src/wolfip.c index ffb74d9a..fc147686 100644 --- a/src/wolfip.c +++ b/src/wolfip.c @@ -1304,6 +1304,16 @@ struct udpsocket { #endif }; +/* Generation of a socket slot, carried in bits 16-30 of its descriptors. */ +struct sock_gen { + uint16_t gen; + uint8_t open; +}; +#define SOCKET_GEN_SHIFT 16 +#define SOCKET_GEN_MASK 0x7FFFU +#define SOCKET_GEN(fd) \ + ((uint16_t)(((unsigned int)(fd) >> SOCKET_GEN_SHIFT) & SOCKET_GEN_MASK)) + struct tsocket { union tsocket_sock { struct tcpsocket tcp; @@ -1548,6 +1558,15 @@ struct wolfIP { #if WOLFIP_PACKET_SOCKETS struct packetsocket packetsockets[WOLFIP_MAX_PACKETSOCKETS]; #endif +#endif + struct sock_gen tcp_gen[MAX_TCPSOCKETS]; + struct sock_gen udp_gen[MAX_UDPSOCKETS]; + struct sock_gen icmp_gen[MAX_ICMPSOCKETS]; +#if WOLFIP_RAWSOCKETS + struct sock_gen raw_gen[WOLFIP_MAX_RAWSOCKETS]; +#if WOLFIP_PACKET_SOCKETS + struct sock_gen packet_gen[WOLFIP_MAX_PACKETSOCKETS]; +#endif #endif #ifdef IP_MULTICAST struct wolfIP_mcast_membership mcast[WOLFIP_MCAST_MEMBERSHIPS]; @@ -2856,6 +2875,91 @@ static void wolfIP_send_port_unreachable(struct wolfIP *s, unsigned int if_idx, } #endif +static struct sock_gen *sock_gen_slot(struct wolfIP *s, int mark, int idx) +{ + if (idx < 0) + return NULL; + if (mark == MARK_TCP_SOCKET) + return (idx < MAX_TCPSOCKETS) ? &s->tcp_gen[idx] : NULL; + if (mark == MARK_UDP_SOCKET) + return (idx < MAX_UDPSOCKETS) ? &s->udp_gen[idx] : NULL; + if (mark == MARK_ICMP_SOCKET) + return (idx < MAX_ICMPSOCKETS) ? &s->icmp_gen[idx] : NULL; +#if WOLFIP_RAWSOCKETS + if (mark == MARK_RAW_SOCKET) + return (idx < WOLFIP_MAX_RAWSOCKETS) ? &s->raw_gen[idx] : NULL; +#if WOLFIP_PACKET_SOCKETS + if (mark == MARK_PACKET_SOCKET) + return (idx < WOLFIP_MAX_PACKETSOCKETS) ? &s->packet_gen[idx] : NULL; +#endif +#endif + return NULL; +} + +static struct sock_gen *sock_gen_of_fd(struct wolfIP *s, int fd) +{ + int mark; + + if (!s || fd < 0) + return NULL; + if (IS_SOCKET_TCP(fd)) + mark = MARK_TCP_SOCKET; + else if (IS_SOCKET_UDP(fd)) + mark = MARK_UDP_SOCKET; + else if (IS_SOCKET_ICMP(fd)) + mark = MARK_ICMP_SOCKET; + else if (IS_SOCKET_RAW(fd)) + mark = MARK_RAW_SOCKET; + else if (IS_SOCKET_PACKET(fd)) + mark = MARK_PACKET_SOCKET; + else + return NULL; + return sock_gen_slot(s, mark, SOCKET_UNMARK(fd)); +} + +static void sock_gen_next(struct sock_gen *g) +{ + g->gen = (uint16_t)((g->gen + 1U) & SOCKET_GEN_MASK); + g->open = 0; +} + +static int sock_fd_make(struct wolfIP *s, int mark, int idx) +{ + struct sock_gen *g = sock_gen_slot(s, mark, idx); + int gen = g ? g->gen : 0; + + return idx | mark | (gen << SOCKET_GEN_SHIFT); +} + +/* A slot freed by the stack while its descriptor was still open gets a new + * generation when it is handed out again. */ +static int sock_fd_open(struct wolfIP *s, int mark, int idx) +{ + struct sock_gen *g = sock_gen_slot(s, mark, idx); + + if (g) { + if (g->open) + sock_gen_next(g); + g->open = 1; + } + return sock_fd_make(s, mark, idx); +} + +static int sock_fd_stale(struct wolfIP *s, int fd) +{ + struct sock_gen *g = sock_gen_of_fd(s, fd); + + return g && (g->gen != SOCKET_GEN(fd)); +} + +static void sock_fd_retire(struct wolfIP *s, int fd) +{ + struct sock_gen *g = sock_gen_of_fd(s, fd); + + if (g) + sock_gen_next(g); +} + /* User Callbacks */ void wolfIP_register_callback(struct wolfIP *s, int sock_fd, tsocket_cb cb, void *arg) @@ -2864,7 +2968,7 @@ void wolfIP_register_callback(struct wolfIP *s, int sock_fd, tsocket_cb cb, uint16_t pending = 0; if (!s) return; - if (sock_fd < 0) + if (sock_fd < 0 || sock_fd_stale(s, sock_fd)) return; if (IS_SOCKET_TCP(sock_fd)) { if (SOCKET_UNMARK(sock_fd) >= MAX_TCPSOCKETS) @@ -3243,7 +3347,7 @@ static void udp_try_recv(struct wolfIP *s, unsigned int if_idx, * for any other (e.g. connected) socket that still has no local * address while DHCP is running. */ int is_dhcp = (s->dhcp_udp_sd > 0) && - ((uint32_t)(MARK_UDP_SOCKET | i) == (uint32_t)s->dhcp_udp_sd); + (sock_fd_make(s, MARK_UDP_SOCKET, i) == s->dhcp_udp_sd); /* Ingress matching uses the bound address, not the egress address * selected into local_ip at bind time: a wildcard (INADDR_ANY) * bind must receive datagrams addressed to any local address @@ -3619,15 +3723,41 @@ static uint32_t tcp_initial_ssthresh(uint32_t peer_rwnd) return (peer_rwnd < TXBUF_SIZE) ? peer_rwnd : TXBUF_SIZE; } +/* A free slot whose descriptor the application still holds is taken last. */ +static int tcp_free_slot(struct wolfIP *s) +{ + int i; + int held = -1; + + for (i = 0; i < MAX_TCPSOCKETS; i++) { + if (s->tcpsockets[i].proto != 0) + continue; + if (!s->tcp_gen[i].open) + return i; + if (held < 0) + held = i; + } + return held; +} + static struct tsocket *tcp_new_socket(struct wolfIP *s) { struct tsocket *t; int i; + int pick; + pick = tcp_free_slot(s); for (i = 0; i < MAX_TCPSOCKETS; i++) { t = &s->tcpsockets[i]; - if (t->proto == 0) { + if (i == pick) { + /* A descriptor still open on a slot the stack freed is stale now. */ + if (s->tcp_gen[i].open) + sock_gen_next(&s->tcp_gen[i]); t->proto = WI_IPPROTO_TCP; t->S = s; + /* A callback registered through a stale descriptor may sit in a free slot. */ + t->callback = NULL; + t->callback_arg = NULL; + t->events = 0; t->if_idx = 0; t->sock.tcp.state = TCP_CLOSED; t->sock.tcp.rto = TCP_RTO_MIN_MS; @@ -7286,18 +7416,18 @@ int wolfIP_sock_socket(struct wolfIP *s, int domain, int type, int protocol) ts = tcp_new_socket(s); if (!ts) return -1; - return (ts - s->tcpsockets) | MARK_TCP_SOCKET; + return sock_fd_open(s, MARK_TCP_SOCKET, (int)(ts - s->tcpsockets)); } else if (type == IPSTACK_SOCK_DGRAM) { if (protocol == 0 || protocol == WI_IPPROTO_UDP) { ts = udp_new_socket(s); if (!ts) return -1; - return (ts - s->udpsockets) | MARK_UDP_SOCKET; + return sock_fd_open(s, MARK_UDP_SOCKET, (int)(ts - s->udpsockets)); } else if (protocol == WI_IPPROTO_ICMP) { ts = icmp_new_socket(s); if (!ts) return -1; - return (ts - s->icmpsockets) | MARK_ICMP_SOCKET; + return sock_fd_open(s, MARK_ICMP_SOCKET, (int)(ts - s->icmpsockets)); } else { return -1; } @@ -7313,7 +7443,7 @@ int wolfIP_sock_socket(struct wolfIP *s, int domain, int type, int protocol) rs = raw_new_socket(s, protocol, hdrincl); if (!rs) return -1; - return (int)((rs - s->rawsockets) | MARK_RAW_SOCKET); + return sock_fd_open(s, MARK_RAW_SOCKET, (int)(rs - s->rawsockets)); } #endif return -1; @@ -7325,7 +7455,7 @@ int wolfIP_sock_socket(struct wolfIP *s, int domain, int type, int protocol) ps = packet_new_socket(s, protocol); if (!ps) return -1; - return (int)((ps - s->packetsockets) | MARK_PACKET_SOCKET); + return sock_fd_open(s, MARK_PACKET_SOCKET, (int)(ps - s->packetsockets)); } #endif return -1; @@ -7372,6 +7502,8 @@ int wolfIP_sock_connect(struct wolfIP *s, int sockfd, const struct wolfIP_sockad struct tsocket *ts; const struct wolfIP_sockaddr_in *sin; unsigned int if_idx; + if (sock_fd_stale(s, sockfd)) + return -WOLFIP_EBADF; if ((!addr)|| (sockfd < 0)) return -WOLFIP_EINVAL; if (!s) @@ -7603,6 +7735,9 @@ int wolfIP_sock_accept(struct wolfIP *s, int sockfd, struct wolfIP_sockaddr *add if (!s) return -WOLFIP_EINVAL; + if (sock_fd_stale(s, sockfd)) + return -WOLFIP_EBADF; + if (addrlen) *addrlen = sizeof(struct wolfIP_sockaddr_in); @@ -7679,7 +7814,7 @@ int wolfIP_sock_accept(struct wolfIP *s, int sockfd, struct wolfIP_sockaddr *add abort_accept_clone(newts); return -1; } - return (newts - s->tcpsockets) | MARK_TCP_SOCKET; + return sock_fd_open(s, MARK_TCP_SOCKET, (int)(newts - s->tcpsockets)); } if ((ts->sock.tcp.state != TCP_SYN_RCVD) && (ts->sock.tcp.state != TCP_LISTEN)) return -1; @@ -7759,7 +7894,7 @@ int wolfIP_sock_accept(struct wolfIP *s, int sockfd, struct wolfIP_sockaddr *add abort_accept_clone(newts); return -1; } - return (newts - s->tcpsockets) | MARK_TCP_SOCKET; + return sock_fd_open(s, MARK_TCP_SOCKET, (int)(newts - s->tcpsockets)); } else if (ts->sock.tcp.state == TCP_LISTEN) { return -WOLFIP_EAGAIN; } @@ -7778,6 +7913,8 @@ int wolfIP_sock_sendto(struct wolfIP *s, int sockfd, const void *buf, size_t len #if WOLFIP_RAWSOCKETS struct wolfIP_ip_packet *rip; #endif + if (sock_fd_stale(s, sockfd)) + return -WOLFIP_EBADF; tcp = (struct wolfIP_tcp_seg *)frame; udp = (struct wolfIP_udp_datagram *)frame; icmp = (struct wolfIP_icmp_packet *)frame; @@ -8236,6 +8373,8 @@ int wolfIP_sock_recvfrom(struct wolfIP *s, int sockfd, void *buf, size_t len, in struct wolfIP_udp_datagram *udp; struct wolfIP_icmp_packet *icmp; struct tsocket *ts; + if (sock_fd_stale(s, sockfd)) + return -WOLFIP_EBADF; (void)flags; if (sockfd < 0) @@ -8614,6 +8753,8 @@ int wolfIP_sock_setsockopt(struct wolfIP *s, int sockfd, int level, int optname, const void *optval, socklen_t optlen) { struct tsocket *ts; + if (sock_fd_stale(s, sockfd)) + return -WOLFIP_EBADF; #if WOLFIP_RAWSOCKETS if (IS_SOCKET_RAW(sockfd)) { struct rawsocket *rs = wolfIP_rawsocket_from_fd(s, sockfd); @@ -8758,6 +8899,8 @@ int wolfIP_sock_setsockopt(struct wolfIP *s, int sockfd, int level, int optname, int wolfIP_sock_get_recv_ttl(struct wolfIP *s, int sockfd, int *ttl) { struct tsocket *ts; + if (sock_fd_stale(s, sockfd)) + return -WOLFIP_EBADF; #if WOLFIP_RAWSOCKETS if (IS_SOCKET_RAW(sockfd)) { struct rawsocket *rs = wolfIP_rawsocket_from_fd(s, sockfd); @@ -8791,6 +8934,8 @@ int wolfIP_sock_getsockopt(struct wolfIP *s, int sockfd, int level, int optname, struct packetsocket *ps = NULL; #endif + if (sock_fd_stale(s, sockfd)) + return -WOLFIP_EBADF; if (sockfd < 0) return -WOLFIP_EINVAL; #if WOLFIP_RAWSOCKETS @@ -8898,7 +9043,7 @@ int wolfIP_sock_getsockopt(struct wolfIP *s, int sockfd, int level, int optname, #endif return 0; } -int wolfIP_sock_close(struct wolfIP *s, int sockfd) +static int sock_close(struct wolfIP *s, int sockfd) { if (sockfd < 0) return -WOLFIP_EINVAL; @@ -9026,6 +9171,18 @@ int wolfIP_sock_close(struct wolfIP *s, int sockfd) return 0; } +int wolfIP_sock_close(struct wolfIP *s, int sockfd) +{ + int ret; + + if (sock_fd_stale(s, sockfd)) + return -WOLFIP_EBADF; + ret = sock_close(s, sockfd); + if (ret == 0) + sock_fd_retire(s, sockfd); + return ret; +} + int wolfIP_sock_abort(struct wolfIP *s, int sockfd) { struct tsocket *ts; @@ -9034,6 +9191,8 @@ int wolfIP_sock_abort(struct wolfIP *s, int sockfd) return -WOLFIP_EINVAL; if (SOCKET_UNMARK(sockfd) >= MAX_TCPSOCKETS) return -WOLFIP_EINVAL; + if (sock_fd_stale(s, sockfd)) + return -WOLFIP_EBADF; ts = &s->tcpsockets[SOCKET_UNMARK(sockfd)]; if (ts->sock.tcp.state == TCP_LISTEN || ts->sock.tcp.state == TCP_CLOSED) return wolfIP_sock_close(s, sockfd); @@ -9060,6 +9219,7 @@ int wolfIP_sock_abort(struct wolfIP *s, int sockfd) ts->callback = NULL; ts->callback_arg = NULL; close_socket(ts); + sock_fd_retire(s, sockfd); return 0; } @@ -9069,6 +9229,8 @@ int wolfIP_sock_getsockname(struct wolfIP *s, int sockfd, struct wolfIP_sockaddr struct tsocket *ts; struct wolfIP_sockaddr_in *sin; + if (sock_fd_stale(s, sockfd)) + return -WOLFIP_EBADF; if ((!addr) || (sockfd < 0)) return -WOLFIP_EINVAL; @@ -9132,6 +9294,8 @@ int wolfIP_sock_can_read(struct wolfIP *s, int sockfd) { struct tsocket *ts = wolfIP_socket_from_fd(s, sockfd); + if (sock_fd_stale(s, sockfd)) + return -WOLFIP_EBADF; if (IS_SOCKET_TCP(sockfd)) { if (!ts) return -WOLFIP_EINVAL; @@ -9181,6 +9345,8 @@ int wolfIP_sock_can_write(struct wolfIP *s, int sockfd) { struct tsocket *ts = wolfIP_socket_from_fd(s, sockfd); + if (sock_fd_stale(s, sockfd)) + return -WOLFIP_EBADF; if (IS_SOCKET_TCP(sockfd)) { if (!ts) return -WOLFIP_EINVAL; @@ -9269,6 +9435,8 @@ int wolfIP_sock_bind(struct wolfIP *s, int sockfd, const struct wolfIP_sockaddr return -WOLFIP_EINVAL; #endif + if (sock_fd_stale(s, sockfd)) + return -WOLFIP_EBADF; if (sockfd < 0) return -WOLFIP_EINVAL; @@ -9474,6 +9642,8 @@ int wolfIP_sock_bind(struct wolfIP *s, int sockfd, const struct wolfIP_sockaddr int wolfIP_sock_listen(struct wolfIP *s, int sockfd, int backlog) { struct tsocket *ts; + if (sock_fd_stale(s, sockfd)) + return -WOLFIP_EBADF; (void)backlog; if (sockfd < 0) return -WOLFIP_EINVAL; @@ -9505,6 +9675,8 @@ int wolfIP_sock_getpeername(struct wolfIP *s, int sockfd, struct wolfIP_sockaddr { struct tsocket *ts; struct wolfIP_sockaddr_in *sin = (struct wolfIP_sockaddr_in *)addr; + if (sock_fd_stale(s, sockfd)) + return -WOLFIP_EBADF; if (sockfd < 0) return -WOLFIP_EINVAL; if (!s) @@ -12841,7 +13013,8 @@ static void handle_timers(struct wolfIP *s, uint64_t now) } } -static void dispatch_events(struct tsocket *socks, int count, uint32_t mark) +static void dispatch_events(struct wolfIP *s, struct tsocket *socks, int count, + int mark) { int i; for (i = 0; i < count; i++) { @@ -12849,7 +13022,8 @@ static void dispatch_events(struct tsocket *socks, int count, uint32_t mark) if (ts->callback && ts->events) { uint16_t events = ts->events; ts->events = 0; - ts->callback(i | mark, events, ts->callback_arg); + ts->callback(sock_fd_make(s, mark, i), events, + ts->callback_arg); } } } @@ -12871,7 +13045,7 @@ static void handle_socket_callbacks(struct wolfIP *s) uint16_t events = ts->events; memset(ts, 0, sizeof(struct tsocket)); if (cb) - cb(i | MARK_TCP_SOCKET, events, cb_arg); + cb(sock_fd_make(s, MARK_TCP_SOCKET, i), events, cb_arg); continue; } @@ -12890,7 +13064,7 @@ static void handle_socket_callbacks(struct wolfIP *s) void *cb_arg = ts->callback_arg; uint16_t events = ts->events; ts->events = 0; - cb(i | MARK_TCP_SOCKET, events, cb_arg); + cb(sock_fd_make(s, MARK_TCP_SOCKET, i), events, cb_arg); /* Now that CB_EVENT_CLOSED has been delivered, reap the * deferred-close socket - but only if the slot still holds the @@ -12913,8 +13087,8 @@ static void handle_socket_callbacks(struct wolfIP *s) } } - dispatch_events(s->udpsockets, MAX_UDPSOCKETS, MARK_UDP_SOCKET); - dispatch_events(s->icmpsockets, MAX_ICMPSOCKETS, MARK_ICMP_SOCKET); + dispatch_events(s, s->udpsockets, MAX_UDPSOCKETS, MARK_UDP_SOCKET); + dispatch_events(s, s->icmpsockets, MAX_ICMPSOCKETS, MARK_ICMP_SOCKET); #if WOLFIP_RAWSOCKETS for (i = 0; i < WOLFIP_MAX_RAWSOCKETS; i++) { @@ -12926,7 +13100,8 @@ static void handle_socket_callbacks(struct wolfIP *s) * post-callback clear would wipe them. */ uint16_t events = r->events; r->events = 0; - r->callback(i | MARK_RAW_SOCKET, events, r->callback_arg); + r->callback(sock_fd_make(s, MARK_RAW_SOCKET, i), events, + r->callback_arg); } } #endif @@ -12940,7 +13115,8 @@ static void handle_socket_callbacks(struct wolfIP *s) * post-callback clear would wipe them. */ uint16_t events = p->events; p->events = 0; - p->callback(i | MARK_PACKET_SOCKET, events, p->callback_arg); + p->callback(sock_fd_make(s, MARK_PACKET_SOCKET, i), + events, p->callback_arg); } } #endif diff --git a/wolfip.h b/wolfip.h index a1078cb2..f02839ed 100644 --- a/wolfip.h +++ b/wolfip.h @@ -179,6 +179,14 @@ typedef uint32_t ip4; #endif #endif +#ifndef WOLFIP_EBADF +#ifdef EBADF +#define WOLFIP_EBADF EBADF +#else +#define WOLFIP_EBADF (9) +#endif +#endif + #ifndef WOLFIP_EBUSY #ifdef EBUSY #define WOLFIP_EBUSY EBUSY From 1a596a44faba49ee47a33dcc441411baf3ac4a52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Thu, 1 Oct 2026 14:46:30 +0200 Subject: [PATCH 03/10] core: let close() return once the FIN is queued wolfIP_sock_close() returned -WOLFIP_EAGAIN in two different cases: when it had queued the FIN and the stack was finishing the close on its own, and when the TX FIFO had no room for the FIN, so nothing had happened and the caller had to retry. A caller cannot tell them apart, and retrying is dangerous in the first case: in LAST_ACK a second close() fell through to the teardown branch and released the socket at once, dropping the FIN retransmission before the peer's final ACK. The POSIX layer passed the -WOLFIP_EAGAIN on, so its close() returned -1 with EAGAIN for every normal TCP close. close() now returns 0 once the FIN is queued, and -WOLFIP_EAGAIN only when it could not be queued. On a socket that is already closing (FIN_WAIT_1, FIN_WAIT_2, CLOSING, LAST_ACK, TIME_WAIT) a repeated close() returns 0 and changes nothing; RFC 9293 3.10.4 leaves those connections alone too, and a repeated close() in TIME_WAIT used to cut it short. The descriptor stays valid until the stack releases the socket, so wolfIP_sock_abort() still works on it as API.md documents; after that the generation check from the previous commit answers -WOLFIP_EBADF once the slot is reused. --- docs/API.md | 4 +- src/test/unit/unit.c | 5 +- src/test/unit/unit_tests_dns_dhcp.c | 6 +- src/test/unit/unit_tests_proto.c | 10 ++-- src/test/unit/unit_tests_tcp_flow.c | 85 +++++++++++++++++++++++++--- src/test/unit/unit_tests_tcp_state.c | 8 +-- src/wolfip.c | 21 ++++--- 7 files changed, 107 insertions(+), 32 deletions(-) diff --git a/docs/API.md b/docs/API.md index 647c1b7c..ffa86955 100644 --- a/docs/API.md +++ b/docs/API.md @@ -162,7 +162,7 @@ Accepts a connection on a listening socket. ```c int wolfIP_sock_abort(struct wolfIP *s, int sockfd); ``` -Abortive close, like `SO_LINGER` with a zero timeout: sends an RST in `SYN_RCVD`, `ESTABLISHED`, `CLOSE_WAIT`, `FIN_WAIT_1` and `FIN_WAIT_2` (other states, such as `CLOSING` and `LAST_ACK`, are released without one), and releases the socket at once instead of waiting for a FIN exchange the peer may never complete. Also valid on a socket whose `wolfIP_sock_close()` returned `-WOLFIP_EAGAIN`, until the stack releases it and its slot is handed out again (see the return values under Data Transfer). +Abortive close, like `SO_LINGER` with a zero timeout: sends an RST in `SYN_RCVD`, `ESTABLISHED`, `CLOSE_WAIT`, `FIN_WAIT_1` and `FIN_WAIT_2` (other states, such as `CLOSING` and `LAST_ACK`, are released without one), and releases the socket at once instead of waiting for a FIN exchange the peer may never complete. Also valid on a socket that `wolfIP_sock_close()` is still closing, until the stack releases it and its slot is handed out again (see the return values under Data Transfer). - Parameters: - s: wolfIP instance - sockfd: TCP socket descriptor @@ -200,7 +200,7 @@ wolfIP never blocks, so every call above can ask the caller to retry. On a TCP s | `-WOLFIP_EBADF` | Stale descriptor: its socket was released and the slot handed out again | | `-1` | The operation cannot succeed on this socket (a listener, or a closing state) | -`wolfIP_sock_close()` follows the same convention: on a connected socket it starts the FIN exchange and returns `-WOLFIP_EAGAIN`. The stack then releases the descriptor by itself, without notification, once the exchange completes, the peer resets, or the close times out, and the next `wolfIP_sock_socket()` or `wolfIP_sock_accept()` can reuse its slot. A descriptor carries the generation of its slot in bits 16-30, so once the slot has been handed out again, every call on the old descriptor returns `-WOLFIP_EBADF` instead of acting on the new socket. The generation wraps after 32768 reuses of the same slot. A slot the stack released on its own (peer reset, retransmission timeout) while the application still holds the descriptor is reused only when no other slot is free; from then on that descriptor, too, answers `-WOLFIP_EBADF` instead of reporting the connection as closed. +On a connected socket `wolfIP_sock_close()` queues a FIN and returns 0, or returns `-WOLFIP_EAGAIN` when the transmit buffer has no room for the FIN yet, in which case the caller retries. Once the FIN is queued, the stack finishes the exchange and releases the socket by itself, without notification, once the exchange completes, the peer resets, or the close times out. Until then, calling `wolfIP_sock_close()` again returns 0 without effect; after that, the next `wolfIP_sock_socket()` or `wolfIP_sock_accept()` can reuse its slot. A descriptor carries the generation of its slot in bits 16-30, so once the slot has been handed out again, every call on the old descriptor returns `-WOLFIP_EBADF` instead of acting on the new socket. The generation wraps after 32768 reuses of the same slot. A slot the stack released on its own (peer reset, retransmission timeout) while the application still holds the descriptor is reused only when no other slot is free; from then on that descriptor, too, answers `-WOLFIP_EBADF` instead of reporting the connection as closed. ## Stack Interface Functions diff --git a/src/test/unit/unit.c b/src/test/unit/unit.c index dda7207e..3349f0cc 100644 --- a/src/test/unit/unit.c +++ b/src/test/unit/unit.c @@ -661,7 +661,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_utils, test_tcp_listener_closed_preaccept_rst_frees_last_ack); tcase_add_test(tc_utils, test_tcp_listener_preaccept_close_wait_timeout_reverts_port); tcase_add_test(tc_utils, test_sock_abort_established_resets_and_frees); - tcase_add_test(tc_utils, test_sock_abort_after_close_eagain_frees); + tcase_add_test(tc_utils, test_sock_abort_after_close_frees); tcase_add_test(tc_utils, test_sock_abort_without_peer_sends_nothing); tcase_add_test(tc_utils, test_sock_abort_fin_wait_1_rst_covers_sent_fin); tcase_add_test(tc_utils, test_sock_abort_fin_wait_2_rst_covers_acked_fin); @@ -673,6 +673,9 @@ Suite *wolf_suite(void) tcase_add_test(tc_utils, test_sock_abort_requeued_fin_covers_fin); tcase_add_test(tc_utils, test_sock_abort_close_wait_resets_peer); tcase_add_test(tc_utils, test_sock_abort_last_ack_sends_nothing); + tcase_add_test(tc_utils, test_sock_close_repeated_in_last_ack_keeps_socket); + tcase_add_test(tc_utils, test_sock_close_repeated_in_time_wait_keeps_socket); + tcase_add_test(tc_utils, test_sock_abort_after_close_stale_once_slot_reused); tcase_add_test(tc_utils, test_sock_abort_syn_rcvd_listener_resets_and_stops_listening); tcase_add_test(tc_utils, test_tcp_listener_preaccept_revert_drains_connection_state); tcase_add_test(tc_utils, test_tcp_listener_preaccept_close_rto_retransmits_finack); diff --git a/src/test/unit/unit_tests_dns_dhcp.c b/src/test/unit/unit_tests_dns_dhcp.c index 8ba16055..238c95c0 100644 --- a/src/test/unit/unit_tests_dns_dhcp.c +++ b/src/test/unit/unit_tests_dns_dhcp.c @@ -6307,7 +6307,7 @@ START_TEST(test_sock_close_tcp_fin_wait_1) ts = &s.tcpsockets[SOCKET_UNMARK(sd)]; ts->sock.tcp.state = TCP_FIN_WAIT_1; - ck_assert_int_eq(wolfIP_sock_close(&s, sd), -WOLFIP_EAGAIN); + ck_assert_int_eq(wolfIP_sock_close(&s, sd), 0); ck_assert_int_eq(ts->sock.tcp.state, TCP_FIN_WAIT_1); } END_TEST @@ -6404,7 +6404,7 @@ START_TEST(test_sock_close_tcp_fin_wait_1_repeated_close_keeps_fin_wait_2_path) ts->sock.tcp.tmr_rto = timers_binheap_insert(&s.timers, tmr); ck_assert_int_ne(ts->sock.tcp.tmr_rto, NO_TIMER); - ck_assert_int_eq(wolfIP_sock_close(&s, sd), -WOLFIP_EAGAIN); + ck_assert_int_eq(wolfIP_sock_close(&s, sd), 0); ck_assert_int_eq(ts->sock.tcp.state, TCP_FIN_WAIT_1); memset(&ackseg, 0, sizeof(ackseg)); @@ -6423,7 +6423,7 @@ START_TEST(test_sock_close_tcp_fin_wait_1_repeated_close_keeps_fin_wait_2_path) timeout_at = find_timer_expiry(&s, ts->sock.tcp.tmr_rto); ck_assert_uint_eq(timeout_at, s.last_tick + TCP_FIN_WAIT_2_TIMEOUT_MS); - ck_assert_int_eq(wolfIP_sock_close(&s, sd), -WOLFIP_EAGAIN); + ck_assert_int_eq(wolfIP_sock_close(&s, sd), 0); ck_assert_int_eq(ts->sock.tcp.state, TCP_FIN_WAIT_2); ck_assert_int_ne(ts->sock.tcp.tmr_rto, NO_TIMER); ck_assert_uint_eq(ts->sock.tcp.fin_wait_2_timeout_active, 1); diff --git a/src/test/unit/unit_tests_proto.c b/src/test/unit/unit_tests_proto.c index 2b916a2e..e94d075e 100644 --- a/src/test/unit/unit_tests_proto.c +++ b/src/test/unit/unit_tests_proto.c @@ -705,19 +705,19 @@ START_TEST(test_tcp_sock_close_state_transitions) ck_assert_int_gt(sd, 0); ts = &s.tcpsockets[SOCKET_UNMARK(sd)]; ts->sock.tcp.state = TCP_ESTABLISHED; - ck_assert_int_eq(wolfIP_sock_close(&s, sd), -WOLFIP_EAGAIN); + ck_assert_int_eq(wolfIP_sock_close(&s, sd), 0); ck_assert_int_eq(ts->sock.tcp.state, TCP_FIN_WAIT_1); ts->sock.tcp.state = TCP_CLOSE_WAIT; - ck_assert_int_eq(wolfIP_sock_close(&s, sd), -WOLFIP_EAGAIN); + ck_assert_int_eq(wolfIP_sock_close(&s, sd), 0); ck_assert_int_eq(ts->sock.tcp.state, TCP_LAST_ACK); ts->sock.tcp.state = TCP_FIN_WAIT_2; - ck_assert_int_eq(wolfIP_sock_close(&s, sd), -WOLFIP_EAGAIN); + ck_assert_int_eq(wolfIP_sock_close(&s, sd), 0); ck_assert_int_eq(ts->sock.tcp.state, TCP_FIN_WAIT_2); ts->sock.tcp.state = TCP_CLOSING; - ck_assert_int_eq(wolfIP_sock_close(&s, sd), -WOLFIP_EAGAIN); + ck_assert_int_eq(wolfIP_sock_close(&s, sd), 0); ck_assert_int_eq(ts->sock.tcp.state, TCP_CLOSING); ts->sock.tcp.state = TCP_LISTEN; @@ -2132,7 +2132,7 @@ START_TEST(test_tcp_persist_close_resets_retry_budget) (TCP_FLAG_ACK | TCP_FLAG_PSH)), 0); s.last_tick = 500; - ck_assert_int_eq(wolfIP_sock_close(&s, tcp_sd), -WOLFIP_EAGAIN); + ck_assert_int_eq(wolfIP_sock_close(&s, tcp_sd), 0); ck_assert_uint_eq(ts->sock.tcp.state, TCP_FIN_WAIT_1); /* Two unanswered teardown probes must not exhaust a fresh budget. */ diff --git a/src/test/unit/unit_tests_tcp_flow.c b/src/test/unit/unit_tests_tcp_flow.c index a6119d27..ea461cf2 100644 --- a/src/test/unit/unit_tests_tcp_flow.c +++ b/src/test/unit/unit_tests_tcp_flow.c @@ -5911,7 +5911,7 @@ START_TEST(test_tcp_listener_closed_while_pending_is_not_readable) ck_assert_int_eq(lsn->sock.tcp.state, TCP_ESTABLISHED); ck_assert_int_eq(wolfIP_sock_can_read(&s, fd), 1); - ck_assert_int_eq(wolfIP_sock_close(&s, fd), -WOLFIP_EAGAIN); + ck_assert_int_eq(wolfIP_sock_close(&s, fd), 0); ck_assert_int_eq(lsn->sock.tcp.state, TCP_FIN_WAIT_1); ck_assert_int_eq(lsn->sock.tcp.is_listener, 1); @@ -6120,7 +6120,7 @@ static void llk_closed_listener_rst_frees(int peer_fin) ck_assert_int_eq(lsn->sock.tcp.state, TCP_CLOSE_WAIT); rst_seq = 3; } - ck_assert_int_eq(wolfIP_sock_close(&s, fd), -WOLFIP_EAGAIN); + ck_assert_int_eq(wolfIP_sock_close(&s, fd), 0); ck_assert_int_eq(lsn->sock.tcp.state, peer_fin ? TCP_LAST_ACK : TCP_FIN_WAIT_1); (void)wolfIP_poll(&s, 3); @@ -6229,7 +6229,7 @@ START_TEST(test_sock_abort_established_resets_and_frees) END_TEST /* The case abort exists for: a graceful close that cannot finish. */ -START_TEST(test_sock_abort_after_close_eagain_frees) +START_TEST(test_sock_abort_after_close_frees) { struct wolfIP s; int sd; @@ -6240,7 +6240,7 @@ START_TEST(test_sock_abort_after_close_eagain_frees) sd = llk_accepted_child(&s); ck_assert_int_ge(sd, 0); ts = &s.tcpsockets[SOCKET_UNMARK(sd)]; - ck_assert_int_eq(wolfIP_sock_close(&s, sd), -WOLFIP_EAGAIN); + ck_assert_int_eq(wolfIP_sock_close(&s, sd), 0); ck_assert_int_eq(ts->sock.tcp.state, TCP_FIN_WAIT_1); last = ts->sock.tcp.last; @@ -6291,7 +6291,7 @@ static int llk_child_fin_sent(struct wolfIP *s, uint32_t *last) sd = llk_accepted_child(s); ck_assert_int_ge(sd, 0); ts = &s->tcpsockets[SOCKET_UNMARK(sd)]; - ck_assert_int_eq(wolfIP_sock_close(s, sd), -WOLFIP_EAGAIN); + ck_assert_int_eq(wolfIP_sock_close(s, sd), 0); (void)wolfIP_poll(s, 3); ck_assert_ptr_null(fifo_peek(&ts->sock.tcp.txbuf)); ck_assert_int_eq(ts->sock.tcp.state, TCP_FIN_WAIT_1); @@ -6482,7 +6482,7 @@ START_TEST(test_sock_abort_fin_behind_unacked_data_covers_fin) ck_assert_int_eq(wolfIP_sock_send(&s, sd, payload, sizeof(payload), 0), (int)sizeof(payload)); (void)wolfIP_poll(&s, 2); - ck_assert_int_eq(wolfIP_sock_close(&s, sd), -WOLFIP_EAGAIN); + ck_assert_int_eq(wolfIP_sock_close(&s, sd), 0); last = ts->sock.tcp.last; ck_assert_uint_eq(last, ts->sock.tcp.snd_una + sizeof(payload)); (void)wolfIP_poll(&s, 3); @@ -6564,7 +6564,7 @@ START_TEST(test_sock_abort_last_ack_sends_nothing) uint32_t frames_before; sd = llk_child_close_wait(&s); - ck_assert_int_eq(wolfIP_sock_close(&s, sd), -WOLFIP_EAGAIN); + ck_assert_int_eq(wolfIP_sock_close(&s, sd), 0); ck_assert_int_eq(s.tcpsockets[SOCKET_UNMARK(sd)].sock.tcp.state, TCP_LAST_ACK); frames_before = last_frame_sent_count; @@ -6578,6 +6578,75 @@ START_TEST(test_sock_abort_last_ack_sends_nothing) } END_TEST +START_TEST(test_sock_close_repeated_in_last_ack_keeps_socket) +{ + struct wolfIP s; + int sd; + struct tsocket *ts; + uint32_t frames_before; + + sd = llk_child_close_wait(&s); + ts = &s.tcpsockets[SOCKET_UNMARK(sd)]; + ck_assert_int_eq(wolfIP_sock_close(&s, sd), 0); + ck_assert_int_eq(ts->sock.tcp.state, TCP_LAST_ACK); + + frames_before = last_frame_sent_count; + ck_assert_int_eq(wolfIP_sock_close(&s, sd), 0); + ck_assert_int_eq(ts->sock.tcp.state, TCP_LAST_ACK); + ck_assert_int_eq(ts->proto, WI_IPPROTO_TCP); + ck_assert_uint_ne(ts->sock.tcp.tmr_rto, NO_TIMER); + ck_assert_uint_eq(last_frame_sent_count, frames_before); +} +END_TEST + +START_TEST(test_sock_close_repeated_in_time_wait_keeps_socket) +{ + struct wolfIP s; + int sd; + struct tsocket *ts; + uint16_t gen; + + sd = llk_accepted_child(&s); + ck_assert_int_ge(sd, 0); + ts = &s.tcpsockets[SOCKET_UNMARK(sd)]; + ck_assert_int_eq(wolfIP_sock_close(&s, sd), 0); + ts->sock.tcp.state = TCP_TIME_WAIT; + gen = s.tcp_gen[SOCKET_UNMARK(sd)].gen; + + ck_assert_int_eq(wolfIP_sock_close(&s, sd), 0); + ck_assert_int_eq(ts->sock.tcp.state, TCP_TIME_WAIT); + ck_assert_int_eq(ts->proto, WI_IPPROTO_TCP); + ck_assert_uint_eq(s.tcp_gen[SOCKET_UNMARK(sd)].gen, gen); + ck_assert_int_eq(wolfIP_sock_abort(&s, sd), 0); + ck_assert_int_eq(ts->proto, 0); +} +END_TEST + +START_TEST(test_sock_abort_after_close_stale_once_slot_reused) +{ + struct wolfIP s; + int sd, new_sd; + struct tsocket *ts; + + sd = llk_accepted_child(&s); + ck_assert_int_ge(sd, 0); + ts = &s.tcpsockets[SOCKET_UNMARK(sd)]; + ck_assert_int_eq(wolfIP_sock_close(&s, sd), 0); + ck_assert_int_eq(ts->sock.tcp.state, TCP_FIN_WAIT_1); + + /* The stack finishes the close on its own and the slot is reused. */ + close_socket(ts); + take_tcp_slots_except(&s, SOCKET_UNMARK(sd)); + new_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_STREAM, WI_IPPROTO_TCP); + ck_assert_int_eq(SOCKET_UNMARK(new_sd), SOCKET_UNMARK(sd)); + ts->sock.tcp.state = TCP_ESTABLISHED; + ck_assert_int_eq(wolfIP_sock_abort(&s, sd), -WOLFIP_EBADF); + ck_assert_int_eq(wolfIP_sock_close(&s, sd), -WOLFIP_EBADF); + ck_assert_int_eq(ts->sock.tcp.state, TCP_ESTABLISHED); + ck_assert_int_eq(ts->proto, WI_IPPROTO_TCP); +} +END_TEST + START_TEST(test_sock_abort_syn_rcvd_listener_resets_and_stops_listening) { struct wolfIP s; @@ -6758,7 +6827,7 @@ START_TEST(test_tcp_listener_preaccept_close_rto_retransmits_finack) /* Close the established-but-unaccepted connection: FIN_WAIT_1, the * control RTO owns the timer slot, and the pre-accept flag must be * gone with the timer it armed. */ - ck_assert_int_eq(wolfIP_sock_close(&s, fd), -WOLFIP_EAGAIN); + ck_assert_int_eq(wolfIP_sock_close(&s, fd), 0); ck_assert_int_eq(lsn->sock.tcp.state, TCP_FIN_WAIT_1); ck_assert_int_eq(lsn->sock.tcp.preaccept_timeout_active, 0); ck_assert_int_eq(lsn->sock.tcp.ctrl_rto_active, 1); diff --git a/src/test/unit/unit_tests_tcp_state.c b/src/test/unit/unit_tests_tcp_state.c index 21187949..797a1915 100644 --- a/src/test/unit/unit_tests_tcp_state.c +++ b/src/test/unit/unit_tests_tcp_state.c @@ -2724,7 +2724,7 @@ START_TEST(test_sock_close_established_disarms_callback) wolfIP_register_callback(&s, sd, test_socket_cb, &callback_arg); /* Active close: FIN sent, FIN_WAIT_1, EAGAIN, callback disarmed. */ - ck_assert_int_eq(wolfIP_sock_close(&s, sd), -WOLFIP_EAGAIN); + ck_assert_int_eq(wolfIP_sock_close(&s, sd), 0); ck_assert_int_eq(ts->sock.tcp.state, TCP_FIN_WAIT_1); ck_assert_ptr_null(ts->callback); ck_assert_ptr_null(ts->callback_arg); @@ -2803,7 +2803,7 @@ START_TEST(test_sock_close_close_wait_disarms_callback) /* Active close from CLOSE_WAIT: FIN sent (seq 120), LAST_ACK, EAGAIN, * callback disarmed. */ - ck_assert_int_eq(wolfIP_sock_close(&s, sd), -WOLFIP_EAGAIN); + ck_assert_int_eq(wolfIP_sock_close(&s, sd), 0); ck_assert_int_eq(ts->sock.tcp.state, TCP_LAST_ACK); ck_assert_ptr_null(ts->callback); ck_assert_ptr_null(ts->callback_arg); @@ -2844,7 +2844,7 @@ START_TEST(test_rst_in_fin_wait_1_delivers_close_event) wolfIP_register_callback(&s, sd, test_socket_cb, &callback_arg); /* Active close: FIN sent, FIN_WAIT_1, EAGAIN, native callback disarmed. */ - ck_assert_int_eq(wolfIP_sock_close(&s, sd), -WOLFIP_EAGAIN); + ck_assert_int_eq(wolfIP_sock_close(&s, sd), 0); ck_assert_int_eq(ts->sock.tcp.state, TCP_FIN_WAIT_1); /* The wrapper re-arms its own callback to wait for CB_EVENT_CLOSED. */ @@ -2913,7 +2913,7 @@ START_TEST(test_last_ack_final_ack_delivers_close_event) /* Active close from CLOSE_WAIT: FIN sent (seq 120), LAST_ACK, EAGAIN, * native callback disarmed. */ - ck_assert_int_eq(wolfIP_sock_close(&s, sd), -WOLFIP_EAGAIN); + ck_assert_int_eq(wolfIP_sock_close(&s, sd), 0); ck_assert_int_eq(ts->sock.tcp.state, TCP_LAST_ACK); /* The wrapper re-arms its own callback to wait for CB_EVENT_CLOSED. */ diff --git a/src/wolfip.c b/src/wolfip.c index fc147686..8740ee57 100644 --- a/src/wolfip.c +++ b/src/wolfip.c @@ -9068,9 +9068,8 @@ static int sock_close(struct wolfIP *s, int sockfd) /* No FIN retransmit is possible: release the socket. */ ts->sock.tcp.state = TCP_CLOSED; close_socket(ts); - return 0; } - return -WOLFIP_EAGAIN; + return 0; } else if (ts->sock.tcp.state == TCP_LISTEN) { ts->sock.tcp.state = TCP_CLOSED; (void)wolfIP_filter_notify_socket_event( @@ -9093,14 +9092,15 @@ static int sock_close(struct wolfIP *s, int sockfd) if (tcp_ctrl_rto_start(ts, s->last_tick) < 0) { ts->sock.tcp.state = TCP_CLOSED; close_socket(ts); - return 0; } - return -WOLFIP_EAGAIN; - } else if (ts->sock.tcp.state == TCP_CLOSING) { - return -WOLFIP_EAGAIN; + return 0; } else if (ts->sock.tcp.state == TCP_FIN_WAIT_1 || - ts->sock.tcp.state == TCP_FIN_WAIT_2) { - return -WOLFIP_EAGAIN; + ts->sock.tcp.state == TCP_FIN_WAIT_2 || + ts->sock.tcp.state == TCP_CLOSING || + ts->sock.tcp.state == TCP_LAST_ACK || + ts->sock.tcp.state == TCP_TIME_WAIT) { + /* Our FIN is out: the stack finishes the close on its own. */ + return 0; } else if (ts->sock.tcp.state != TCP_CLOSED) { ts->sock.tcp.state = TCP_CLOSED; (void)wolfIP_filter_notify_socket_event( @@ -9178,7 +9178,10 @@ int wolfIP_sock_close(struct wolfIP *s, int sockfd) if (sock_fd_stale(s, sockfd)) return -WOLFIP_EBADF; ret = sock_close(s, sockfd); - if (ret == 0) + /* A socket still finishing its FIN exchange keeps its descriptor for + * wolfIP_sock_abort(); the stack retires it when it frees the slot. */ + if ((ret == 0) && !(IS_SOCKET_TCP(sockfd) && + (s->tcpsockets[SOCKET_UNMARK(sockfd)].proto != 0))) sock_fd_retire(s, sockfd); return ret; } From 0e25838111895370f5cb8a4436308ac73ef86a89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Thu, 1 Oct 2026 14:46:32 +0200 Subject: [PATCH 04/10] core: reclaim a closed socket's slot when the table is full A socket the application has closed keeps its slot until the FIN exchange finishes: up to TCP_FIN_WAIT_2_TIMEOUT_MS (60 s) in FIN_WAIT_2, and the full control retransmission budget in FIN_WAIT_1 or LAST_ACK when the peer has gone. On a small socket table that is an outage. With MAX_TCPSOCKETS at 2 and one listener, a single TLS client that aborts its handshake leaves an orphan behind, and every following connection is refused with an RST until it times out. When tcp_new_socket() finds no free slot it now resets one the application no longer holds, preferring TIME_WAIT, which has nothing left to deliver, then FIN_WAIT_2, whose peer has acknowledged all our data, then the remaining closing states. Sockets the application still owns are never taken. Linux does the same under orphan pressure. The reset is the abortive close wolfIP_sock_abort() already performs, moved into tcp_abort() so both paths share it. --- docs/API.md | 2 +- src/test/unit/unit.c | 5 ++ src/test/unit/unit_tests_tcp_flow.c | 119 ++++++++++++++++++++++++++++ src/wolfip.c | 75 ++++++++++++++---- 4 files changed, 186 insertions(+), 15 deletions(-) diff --git a/docs/API.md b/docs/API.md index ffa86955..15918010 100644 --- a/docs/API.md +++ b/docs/API.md @@ -200,7 +200,7 @@ wolfIP never blocks, so every call above can ask the caller to retry. On a TCP s | `-WOLFIP_EBADF` | Stale descriptor: its socket was released and the slot handed out again | | `-1` | The operation cannot succeed on this socket (a listener, or a closing state) | -On a connected socket `wolfIP_sock_close()` queues a FIN and returns 0, or returns `-WOLFIP_EAGAIN` when the transmit buffer has no room for the FIN yet, in which case the caller retries. Once the FIN is queued, the stack finishes the exchange and releases the socket by itself, without notification, once the exchange completes, the peer resets, or the close times out. Until then, calling `wolfIP_sock_close()` again returns 0 without effect; after that, the next `wolfIP_sock_socket()` or `wolfIP_sock_accept()` can reuse its slot. A descriptor carries the generation of its slot in bits 16-30, so once the slot has been handed out again, every call on the old descriptor returns `-WOLFIP_EBADF` instead of acting on the new socket. The generation wraps after 32768 reuses of the same slot. A slot the stack released on its own (peer reset, retransmission timeout) while the application still holds the descriptor is reused only when no other slot is free; from then on that descriptor, too, answers `-WOLFIP_EBADF` instead of reporting the connection as closed. +On a connected socket `wolfIP_sock_close()` queues a FIN and returns 0, or returns `-WOLFIP_EAGAIN` when the transmit buffer has no room for the FIN yet, in which case the caller retries. Once the FIN is queued, the stack finishes the exchange and releases the socket by itself, without notification, once the exchange completes, the peer resets, or the close times out. Until then, calling `wolfIP_sock_close()` again returns 0 without effect; after that, the next `wolfIP_sock_socket()` or `wolfIP_sock_accept()` can reuse its slot. When every TCP slot is taken, `wolfIP_sock_socket()` and `wolfIP_sock_accept()` take the slot of a socket the application has already closed, preferring one in `TIME_WAIT`, then `FIN_WAIT_2`, and reset its peer if the FIN exchange has not finished. A descriptor carries the generation of its slot in bits 16-30, so once the slot has been handed out again, every call on the old descriptor returns `-WOLFIP_EBADF` instead of acting on the new socket. The generation wraps after 32768 reuses of the same slot. A slot the stack released on its own (peer reset, retransmission timeout) while the application still holds the descriptor is reused only when no other slot is free; from then on that descriptor, too, answers `-WOLFIP_EBADF` instead of reporting the connection as closed. ## Stack Interface Functions diff --git a/src/test/unit/unit.c b/src/test/unit/unit.c index 3349f0cc..db5686ac 100644 --- a/src/test/unit/unit.c +++ b/src/test/unit/unit.c @@ -676,6 +676,11 @@ Suite *wolf_suite(void) tcase_add_test(tc_utils, test_sock_close_repeated_in_last_ack_keeps_socket); tcase_add_test(tc_utils, test_sock_close_repeated_in_time_wait_keeps_socket); tcase_add_test(tc_utils, test_sock_abort_after_close_stale_once_slot_reused); + tcase_add_test(tc_utils, test_tcp_full_table_reclaims_time_wait_first); + tcase_add_test(tc_utils, test_tcp_full_table_reclaims_fin_wait_2_with_rst); + tcase_add_test(tc_utils, test_tcp_full_table_keeps_open_sockets); + tcase_add_test(tc_utils, test_tcp_full_table_reclaims_fin_wait_1_with_rst); + tcase_add_test(tc_utils, test_tcp_full_table_reclaims_last_ack_silently); tcase_add_test(tc_utils, test_sock_abort_syn_rcvd_listener_resets_and_stops_listening); tcase_add_test(tc_utils, test_tcp_listener_preaccept_revert_drains_connection_state); tcase_add_test(tc_utils, test_tcp_listener_preaccept_close_rto_retransmits_finack); diff --git a/src/test/unit/unit_tests_tcp_flow.c b/src/test/unit/unit_tests_tcp_flow.c index ea461cf2..e99b8d0d 100644 --- a/src/test/unit/unit_tests_tcp_flow.c +++ b/src/test/unit/unit_tests_tcp_flow.c @@ -6647,6 +6647,125 @@ START_TEST(test_sock_abort_after_close_stale_once_slot_reused) } END_TEST +START_TEST(test_tcp_full_table_reclaims_time_wait_first) +{ + struct wolfIP s; + int sd[MAX_TCPSOCKETS]; + int new_sd; + int i; + uint32_t frames_before; + + wolfIP_init(&s); + mock_link_init(&s); + for (i = 0; i < MAX_TCPSOCKETS; i++) { + sd[i] = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_STREAM, WI_IPPROTO_TCP); + ck_assert_int_ge(sd[i], 0); + s.tcpsockets[i].sock.tcp.state = TCP_ESTABLISHED; + } + s.tcpsockets[1].sock.tcp.state = TCP_FIN_WAIT_2; + s.tcpsockets[2].sock.tcp.state = TCP_TIME_WAIT; + + frames_before = last_frame_sent_count; + new_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_STREAM, WI_IPPROTO_TCP); + ck_assert_int_eq(SOCKET_UNMARK(new_sd), 2); + ck_assert_int_eq(s.tcpsockets[2].sock.tcp.state, TCP_CLOSED); + ck_assert_int_eq(s.tcpsockets[1].sock.tcp.state, TCP_FIN_WAIT_2); + ck_assert_uint_eq(last_frame_sent_count, frames_before); + ck_assert_int_eq(wolfIP_sock_close(&s, sd[2]), -WOLFIP_EBADF); +} +END_TEST + +START_TEST(test_tcp_full_table_reclaims_fin_wait_2_with_rst) +{ + struct wolfIP s; + int sd, new_sd; + int i; + const struct wolfIP_tcp_seg *out; + + /* The listener and the accepted child take two slots. */ + sd = llk_accepted_child(&s); + ck_assert_int_ge(sd, 0); + ck_assert_int_eq(wolfIP_sock_close(&s, sd), 0); + s.tcpsockets[SOCKET_UNMARK(sd)].sock.tcp.state = TCP_FIN_WAIT_2; + for (i = 2; i < MAX_TCPSOCKETS; i++) + ck_assert_int_ge(wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_STREAM, + WI_IPPROTO_TCP), 0); + + new_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_STREAM, WI_IPPROTO_TCP); + ck_assert_int_eq(SOCKET_UNMARK(new_sd), SOCKET_UNMARK(sd)); + out = llk_last_tcp(); + ck_assert_ptr_nonnull(out); + ck_assert_uint_eq(out->flags, TCP_FLAG_RST | TCP_FLAG_ACK); + ck_assert_uint_eq(ee16(out->dst_port), 41000); + ck_assert_int_eq(wolfIP_sock_abort(&s, sd), -WOLFIP_EBADF); + new_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_STREAM, WI_IPPROTO_TCP); + ck_assert_int_eq(new_sd, -1); +} +END_TEST + +START_TEST(test_tcp_full_table_keeps_open_sockets) +{ + struct wolfIP s; + int i; + + wolfIP_init(&s); + mock_link_init(&s); + for (i = 0; i < MAX_TCPSOCKETS; i++) { + ck_assert_int_ge(wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_STREAM, + WI_IPPROTO_TCP), 0); + s.tcpsockets[i].sock.tcp.state = (i & 1) ? TCP_CLOSE_WAIT : TCP_ESTABLISHED; + } + ck_assert_int_eq(wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_STREAM, + WI_IPPROTO_TCP), -1); + for (i = 0; i < MAX_TCPSOCKETS; i++) + ck_assert_int_eq(s.tcpsockets[i].proto, WI_IPPROTO_TCP); +} +END_TEST + +/* A socket still sending its FIN goes only when nothing cheaper is left. */ +static void full_table_reclaims_closing(int peer_fin, uint8_t expect_flags) +{ + struct wolfIP s; + int sd, new_sd; + int i; + uint32_t frames_before; + const struct wolfIP_tcp_seg *out; + + sd = peer_fin ? llk_child_close_wait(&s) : llk_accepted_child(&s); + ck_assert_int_ge(sd, 0); + ck_assert_int_eq(wolfIP_sock_close(&s, sd), 0); + ck_assert_int_eq(s.tcpsockets[SOCKET_UNMARK(sd)].sock.tcp.state, + peer_fin ? TCP_LAST_ACK : TCP_FIN_WAIT_1); + for (i = 2; i < MAX_TCPSOCKETS; i++) + ck_assert_int_ge(wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_STREAM, + WI_IPPROTO_TCP), 0); + + frames_before = last_frame_sent_count; + new_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_STREAM, WI_IPPROTO_TCP); + ck_assert_int_eq(SOCKET_UNMARK(new_sd), SOCKET_UNMARK(sd)); + if (expect_flags != 0) { + out = llk_last_tcp(); + ck_assert_ptr_nonnull(out); + ck_assert_uint_eq(out->flags, expect_flags); + ck_assert_uint_eq(ee16(out->dst_port), 41000); + } + else { + ck_assert_uint_eq(last_frame_sent_count, frames_before); + } +} + +START_TEST(test_tcp_full_table_reclaims_fin_wait_1_with_rst) +{ + full_table_reclaims_closing(0, TCP_FLAG_RST | TCP_FLAG_ACK); +} +END_TEST + +START_TEST(test_tcp_full_table_reclaims_last_ack_silently) +{ + full_table_reclaims_closing(1, 0); +} +END_TEST + START_TEST(test_sock_abort_syn_rcvd_listener_resets_and_stops_listening) { struct wolfIP s; diff --git a/src/wolfip.c b/src/wolfip.c index 8740ee57..12b47281 100644 --- a/src/wolfip.c +++ b/src/wolfip.c @@ -1416,6 +1416,7 @@ static void tcp_fin_wait_2_timeout_stop(struct tsocket *t); static int tcp_preaccept_timeout_start(struct tsocket *t, uint64_t now); static void tcp_preaccept_timeout_stop(struct tsocket *t); static void tcp_listener_revert_to_listen(struct tsocket *t); +static void tcp_reclaim_if_full(struct wolfIP *s); static int tcp_ctrl_state_needs_rto(const struct tsocket *t); static int tcp_has_pending_unsent_payload(struct tsocket *t); static uint32_t tcp_snd_nxt(struct tsocket *t); @@ -3745,6 +3746,7 @@ static struct tsocket *tcp_new_socket(struct wolfIP *s) struct tsocket *t; int i; int pick; + tcp_reclaim_if_full(s); pick = tcp_free_slot(s); for (i = 0; i < MAX_TCPSOCKETS; i++) { t = &s->tcpsockets[i]; @@ -9186,19 +9188,8 @@ int wolfIP_sock_close(struct wolfIP *s, int sockfd) return ret; } -int wolfIP_sock_abort(struct wolfIP *s, int sockfd) +static void tcp_abort(struct tsocket *ts) { - struct tsocket *ts; - - if (!s || sockfd < 0 || !IS_SOCKET_TCP(sockfd)) - return -WOLFIP_EINVAL; - if (SOCKET_UNMARK(sockfd) >= MAX_TCPSOCKETS) - return -WOLFIP_EINVAL; - if (sock_fd_stale(s, sockfd)) - return -WOLFIP_EBADF; - ts = &s->tcpsockets[SOCKET_UNMARK(sockfd)]; - if (ts->sock.tcp.state == TCP_LISTEN || ts->sock.tcp.state == TCP_CLOSED) - return wolfIP_sock_close(s, sockfd); switch (ts->sock.tcp.state) { case TCP_SYN_RCVD: case TCP_ESTABLISHED: @@ -9212,20 +9203,76 @@ int wolfIP_sock_abort(struct wolfIP *s, int sockfd) } ts->sock.tcp.state = TCP_CLOSED; (void)wolfIP_filter_notify_socket_event( - WOLFIP_FILT_CLOSED, s, ts, + WOLFIP_FILT_CLOSED, ts->S, ts, ts->local_ip, ts->src_port, ts->remote_ip, ts->dst_port); if (ts->sock.tcp.is_listener) { (void)wolfIP_filter_notify_socket_event( - WOLFIP_FILT_STOP_LISTENING, s, ts, + WOLFIP_FILT_STOP_LISTENING, ts->S, ts, ts->local_ip, ts->src_port, IPADDR_ANY, 0); } ts->callback = NULL; ts->callback_arg = NULL; close_socket(ts); +} + +int wolfIP_sock_abort(struct wolfIP *s, int sockfd) +{ + struct tsocket *ts; + + if (!s || sockfd < 0 || !IS_SOCKET_TCP(sockfd)) + return -WOLFIP_EINVAL; + if (SOCKET_UNMARK(sockfd) >= MAX_TCPSOCKETS) + return -WOLFIP_EINVAL; + if (sock_fd_stale(s, sockfd)) + return -WOLFIP_EBADF; + ts = &s->tcpsockets[SOCKET_UNMARK(sockfd)]; + if (ts->sock.tcp.state == TCP_LISTEN || ts->sock.tcp.state == TCP_CLOSED) + return wolfIP_sock_close(s, sockfd); + tcp_abort(ts); sock_fd_retire(s, sockfd); return 0; } +/* Order in which sockets the application has already closed give up their + * slot when the table is full; 0 never does. */ +static int tcp_reclaim_rank(const struct tsocket *t) +{ + switch (t->sock.tcp.state) { + case TCP_TIME_WAIT: + return 3; + case TCP_FIN_WAIT_2: + return 2; + case TCP_FIN_WAIT_1: + case TCP_CLOSING: + case TCP_LAST_ACK: + return 1; + default: + return 0; + } +} + +static void tcp_reclaim_if_full(struct wolfIP *s) +{ + struct tsocket *victim = NULL; + int best = 0; + int i; + + for (i = 0; i < MAX_TCPSOCKETS; i++) { + struct tsocket *t = &s->tcpsockets[i]; + int rank; + + if (t->proto == 0) + return; + rank = tcp_reclaim_rank(t); + if (rank > best) { + best = rank; + victim = t; + } + } + if (victim) + tcp_abort(victim); +} + int wolfIP_sock_getsockname(struct wolfIP *s, int sockfd, struct wolfIP_sockaddr *addr, const socklen_t *addrlen) { From 1a3a0df3c550ad50ac2ab82b2e3f4bb701a96b99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Thu, 1 Oct 2026 14:46:35 +0200 Subject: [PATCH 05/10] examples: keep the client slot free while accept() has nothing to return The example servers store wolfIP_sock_accept()'s result straight into their client descriptor and only call it again while that descriptor is -1: if (... && (client_fd == -1)) { client_fd = wolfIP_sock_accept(s, listen_fd, NULL, NULL); if (client_fd > 0) ... Any other negative result, -WOLFIP_EAGAIN included, leaves the server waiting for a connection it can never accept again. accept() is about to return -WOLFIP_EAGAIN after CB_EVENT_READABLE while a handshake is still in progress, which POSIX allows anyway, so put the -1 back on any negative result. rtl8735b already did this. --- src/port/lpc54s018/main.c | 2 ++ src/port/pic32mz/main.c | 4 ++++ src/port/raspberry-pico-usb-server/src/main.c | 2 ++ src/port/stm32c5a3/main.c | 2 ++ src/port/stm32f439/main.c | 2 ++ src/port/stm32h563/main.c | 2 ++ src/port/stm32h753/main.c | 2 ++ src/port/stm32n6/main.c | 2 ++ src/port/va416xx/main.c | 4 ++++ src/test/esp/test_esp.c | 2 ++ src/test/ipfilter_logger.c | 2 ++ src/test/test_eventloop.c | 2 ++ src/test/test_eventloop_tun.c | 2 ++ src/test/test_native_wolfssl.c | 2 ++ src/test/test_wolfssl_forwarding.c | 2 ++ 15 files changed, 34 insertions(+) diff --git a/src/port/lpc54s018/main.c b/src/port/lpc54s018/main.c index 8bba6da1..f24301d6 100644 --- a/src/port/lpc54s018/main.c +++ b/src/port/lpc54s018/main.c @@ -259,6 +259,8 @@ static void echo_cb(int fd, uint16_t event, void *arg) if ((fd == listen_fd) && (event & CB_EVENT_READABLE) && (client_fd == -1)) { client_fd = wolfIP_sock_accept(s, listen_fd, NULL, NULL); + if (client_fd < 0) + client_fd = -1; if (client_fd > 0) wolfIP_register_callback(s, client_fd, echo_cb, s); return; diff --git a/src/port/pic32mz/main.c b/src/port/pic32mz/main.c index 5645f0d7..b85307f7 100644 --- a/src/port/pic32mz/main.c +++ b/src/port/pic32mz/main.c @@ -94,6 +94,8 @@ static void app_cb(int fd, uint16_t event, void *arg) if ((fd == listen_fd) && (event & CB_EVENT_READABLE) && (client_fd == -1)) { client_fd = wolfIP_sock_accept(s, listen_fd, NULL, NULL); + if (client_fd < 0) + client_fd = -1; if (client_fd > 0) { wolfIP_register_callback(s, client_fd, app_cb, s); rx_bytes = 0; @@ -143,6 +145,8 @@ static void app_cb(int fd, uint16_t event, void *arg) if ((fd == listen_fd) && (event & CB_EVENT_READABLE) && (client_fd == -1)) { client_fd = wolfIP_sock_accept(s, listen_fd, NULL, NULL); + if (client_fd < 0) + client_fd = -1; if (client_fd > 0) wolfIP_register_callback(s, client_fd, app_cb, s); return; diff --git a/src/port/raspberry-pico-usb-server/src/main.c b/src/port/raspberry-pico-usb-server/src/main.c index fbf177c6..d6a1a991 100644 --- a/src/port/raspberry-pico-usb-server/src/main.c +++ b/src/port/raspberry-pico-usb-server/src/main.c @@ -189,6 +189,8 @@ static void telnet_cb(int fd, uint16_t event, void *arg) char ipaddr[16]; char welcome_msg[32]; tel_c = wolfIP_sock_accept(IPStack, tel_s, (struct wolfIP_sockaddr*)&addr, &socklen); + if (tel_c < 0) + tel_c = -1; if (tel_c > 0) { iptoa(ee32(addr.sin_addr.s_addr), ipaddr); snprintf(welcome_msg, sizeof(welcome_msg), "Welcome %s!\n", ipaddr); diff --git a/src/port/stm32c5a3/main.c b/src/port/stm32c5a3/main.c index f4c53b49..73f730bf 100644 --- a/src/port/stm32c5a3/main.c +++ b/src/port/stm32c5a3/main.c @@ -453,6 +453,8 @@ static void echo_cb(int fd, uint16_t event, void *arg) if ((fd == listen_fd) && (event & CB_EVENT_READABLE) && (client_fd == -1)) { client_fd = wolfIP_sock_accept(s, listen_fd, NULL, NULL); + if (client_fd < 0) + client_fd = -1; if (client_fd > 0) { wolfIP_register_callback(s, client_fd, echo_cb, s); } diff --git a/src/port/stm32f439/main.c b/src/port/stm32f439/main.c index c3703cfd..5c115498 100644 --- a/src/port/stm32f439/main.c +++ b/src/port/stm32f439/main.c @@ -577,6 +577,8 @@ static void echo_cb(int fd, uint16_t event, void *arg) if ((fd == listen_fd) && (event & CB_EVENT_READABLE) && (client_fd == -1)) { client_fd = wolfIP_sock_accept(s, listen_fd, NULL, NULL); + if (client_fd < 0) + client_fd = -1; if (client_fd > 0) { printf("Echo: client connected (fd=%d)\n", client_fd); wolfIP_register_callback(s, client_fd, echo_cb, s); diff --git a/src/port/stm32h563/main.c b/src/port/stm32h563/main.c index 542b46b2..69bc5ab4 100644 --- a/src/port/stm32h563/main.c +++ b/src/port/stm32h563/main.c @@ -781,6 +781,8 @@ static void echo_cb(int fd, uint16_t event, void *arg) if ((fd == listen_fd) && (event & CB_EVENT_READABLE) && (client_fd == -1)) { client_fd = wolfIP_sock_accept(s, listen_fd, NULL, NULL); + if (client_fd < 0) + client_fd = -1; if (client_fd > 0) { wolfIP_register_callback(s, client_fd, echo_cb, s); } diff --git a/src/port/stm32h753/main.c b/src/port/stm32h753/main.c index b75dc475..38eb0987 100644 --- a/src/port/stm32h753/main.c +++ b/src/port/stm32h753/main.c @@ -773,6 +773,8 @@ static void echo_cb(int fd, uint16_t event, void *arg) if ((fd == listen_fd) && (event & CB_EVENT_READABLE) && (client_fd == -1)) { client_fd = wolfIP_sock_accept(s, listen_fd, NULL, NULL); + if (client_fd < 0) + client_fd = -1; if (client_fd > 0) { wolfIP_register_callback(s, client_fd, echo_cb, s); led_blue_on(); /* Client connected */ diff --git a/src/port/stm32n6/main.c b/src/port/stm32n6/main.c index 54ac0511..3c2af3ac 100644 --- a/src/port/stm32n6/main.c +++ b/src/port/stm32n6/main.c @@ -647,6 +647,8 @@ static void echo_cb(int fd, uint16_t event, void *arg) if ((fd == listen_fd) && (event & CB_EVENT_READABLE) && (client_fd == -1)) { client_fd = wolfIP_sock_accept(s, listen_fd, NULL, NULL); + if (client_fd < 0) + client_fd = -1; if (client_fd > 0) { wolfIP_register_callback(s, client_fd, echo_cb, s); } diff --git a/src/port/va416xx/main.c b/src/port/va416xx/main.c index 4d94e03e..80adf344 100644 --- a/src/port/va416xx/main.c +++ b/src/port/va416xx/main.c @@ -231,6 +231,8 @@ static void speed_cb(int fd, uint16_t event, void *arg) if ((fd == speed_listen_fd) && (event & CB_EVENT_READABLE) && (speed_client_fd == -1)) { speed_client_fd = wolfIP_sock_accept(s, speed_listen_fd, NULL, NULL); + if (speed_client_fd < 0) + speed_client_fd = -1; if (speed_client_fd > 0) { printf("Speed: client connected (fd=%d)\n", speed_client_fd); wolfIP_register_callback(s, speed_client_fd, speed_cb, s); @@ -296,6 +298,8 @@ static void echo_cb(int fd, uint16_t event, void *arg) if ((fd == listen_fd) && (event & CB_EVENT_READABLE) && (client_fd == -1)) { client_fd = wolfIP_sock_accept(s, listen_fd, NULL, NULL); + if (client_fd < 0) + client_fd = -1; if (client_fd > 0) { printf("Echo: client connected (fd=%d)\n", client_fd); wolfIP_register_callback(s, client_fd, echo_cb, s); diff --git a/src/test/esp/test_esp.c b/src/test/esp/test_esp.c index 1e23f7ae..318dc03e 100644 --- a/src/test/esp/test_esp.c +++ b/src/test/esp/test_esp.c @@ -66,6 +66,8 @@ static void server_cb(int fd, uint16_t event, void *arg) if ((fd == listen_fd) && (event & CB_EVENT_READABLE) && (client_fd == -1)) { client_fd = wolfIP_sock_accept((struct wolfIP *)arg, listen_fd, NULL, NULL); + if (client_fd < 0) + client_fd = -1; if (client_fd > 0) { printf("accept: %04x\n", client_fd); } diff --git a/src/test/ipfilter_logger.c b/src/test/ipfilter_logger.c index b1fe4b78..bcd75c4f 100644 --- a/src/test/ipfilter_logger.c +++ b/src/test/ipfilter_logger.c @@ -161,6 +161,8 @@ static void server_cb(int fd, uint16_t event, void *arg) int ret = 0; if ((fd == listen_fd) && (event & CB_EVENT_READABLE) && (client_fd == -1)) { client_fd = wolfIP_sock_accept((struct wolfIP *)arg, listen_fd, NULL, NULL); + if (client_fd < 0) + client_fd = -1; if (client_fd > 0) { printf("accept: Client FD is 0x%04x\n", client_fd); server_ssl = wolfSSL_new(server_ctx); diff --git a/src/test/test_eventloop.c b/src/test/test_eventloop.c index b584e6df..8e8a7c8b 100644 --- a/src/test/test_eventloop.c +++ b/src/test/test_eventloop.c @@ -59,6 +59,8 @@ static void server_cb(int fd, uint16_t event, void *arg) int ret = 0; if ((fd == listen_fd) && (event & CB_EVENT_READABLE) && (client_fd == -1)) { client_fd = wolfIP_sock_accept((struct wolfIP *)arg, listen_fd, NULL, NULL); + if (client_fd < 0) + client_fd = -1; if (client_fd > 0) { printf("accept: %04x\n", client_fd); } diff --git a/src/test/test_eventloop_tun.c b/src/test/test_eventloop_tun.c index 34fb90c6..4d1b0c8f 100644 --- a/src/test/test_eventloop_tun.c +++ b/src/test/test_eventloop_tun.c @@ -59,6 +59,8 @@ static void server_cb(int fd, uint16_t event, void *arg) int ret = 0; if ((fd == listen_fd) && (event & CB_EVENT_READABLE) && (client_fd == -1)) { client_fd = wolfIP_sock_accept((struct wolfIP *)arg, listen_fd, NULL, NULL); + if (client_fd < 0) + client_fd = -1; if (client_fd > 0) { printf("accept: %04x\n", client_fd); } diff --git a/src/test/test_native_wolfssl.c b/src/test/test_native_wolfssl.c index 97a97ed9..f9fabfa7 100644 --- a/src/test/test_native_wolfssl.c +++ b/src/test/test_native_wolfssl.c @@ -67,6 +67,8 @@ static void server_cb(int fd, uint16_t event, void *arg) int ret = 0; if ((fd == listen_fd) && (event & CB_EVENT_READABLE) && (client_fd == -1)) { client_fd = wolfIP_sock_accept((struct wolfIP *)arg, listen_fd, NULL, NULL); + if (client_fd < 0) + client_fd = -1; if (client_fd > 0) { printf("accept: Client FD is 0x%04x\n", client_fd); /* Create the wolfSSL object */ diff --git a/src/test/test_wolfssl_forwarding.c b/src/test/test_wolfssl_forwarding.c index 1c9c23c5..3bd0c616 100644 --- a/src/test/test_wolfssl_forwarding.c +++ b/src/test/test_wolfssl_forwarding.c @@ -232,6 +232,8 @@ static void server_cb(int fd, uint16_t events, void *arg) (void)events; if (fd == server_listen_fd && (events & CB_EVENT_READABLE) && server_client_fd == -1) { server_client_fd = wolfIP_sock_accept(s, server_listen_fd, NULL, NULL); + if (server_client_fd < 0) + server_client_fd = -1; if (server_client_fd > 0) { wolfIP_register_callback(s, server_client_fd, server_cb, s); server_ssl = wolfSSL_new(server_ctx); From b112e342b8c50bdb8984c4ddcd90416372a52be1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Thu, 1 Oct 2026 14:46:39 +0200 Subject: [PATCH 06/10] core: accept() returns a connection, not a handshake in progress wolfIP_sock_accept() on a listener in SYN_RCVD cloned the half-open connection and returned it at once, before the peer's final ACK. The application got a socket that was not connected: send() returned -WOLFIP_EAGAIN, recv() had nothing, and a TLS server that went straight for the ClientHello failed a connection that was merely young. Every other stack (Linux, the BSDs, Winsock, lwIP, FreeRTOS+TCP, Zephyr) hands out only completed connections, and the FreeRTOS wrapper had grown a parking scheme to fake it; the POSIX layer had the same bug. accept() in SYN_RCVD now clones the connection as before, so the listener is free for the next SYN at once, but keeps the child back and returns -WOLFIP_EAGAIN. The child has no callback and remembers its listener by slot and generation. When its handshake completes and its pre-accept timer is armed, the listener raises CB_EVENT_READABLE again, wolfIP_sock_can_read() reports it, and the next accept() returns it with the listener's callback, ESTABLISHED or CLOSE_WAIT. The child records the listener's full 15-bit slot generation. CB_EVENT_READABLE is an edge, so when accept() hands out a child while another is ready, or while the listener holds a handshake of its own, it raises the event again and wakes the poller; otherwise two connections completing in one poll would reach a callback-driven application as one. A held-back child that is reset, or whose SYN-ACK goes unanswered TCP_SYNACK_MAXRTX times (default 3, about 15 s instead of the 8 retries and 3 minutes a connection gets), is freed without an event, since nobody holds its descriptor. One that completes but is not accepted within TCP_PREACCEPT_TIMEOUT_MS is reset and freed, as the listener's own pre-accept timeout does for a connection it completed itself; otherwise an application that misses the second CB_EVENT_READABLE would leave it holding a slot for good. Closing or aborting the listener resets the children still held. When the table is full they are reclaimed after FIN_WAIT_2 orphans and before the other closing states, and with no slot at all the listener keeps the handshake itself and accept() returns -WOLFIP_EAGAIN instead of -1. A connection that completes before the application calls accept() still reaches it through the listener's own ESTABLISHED path, unchanged. If that path finds no slot for the child, accept() now returns -WOLFIP_EAGAIN and leaves the connection on the listener, as the SYN_RCVD path does, instead of resetting it and returning -1; the listener's pre-accept timeout still resets it if no slot frees up in time. The tests that called accept() during the handshake and inspected the returned socket now find the held-back child instead, or complete the handshake first. --- docs/API.md | 6 +- src/test/unit/unit.c | 15 +- src/test/unit/unit_shared.c | 29 ++ src/test/unit/unit_tests_api.c | 96 +++---- src/test/unit/unit_tests_dns_dhcp.c | 33 ++- src/test/unit/unit_tests_proto.c | 14 +- src/test/unit/unit_tests_tcp_flow.c | 385 +++++++++++++++++++++++++-- src/test/unit/unit_tests_tcp_state.c | 19 +- src/wolfip.c | 153 +++++++++-- 9 files changed, 605 insertions(+), 145 deletions(-) diff --git a/docs/API.md b/docs/API.md index 15918010..0f938d3c 100644 --- a/docs/API.md +++ b/docs/API.md @@ -151,13 +151,13 @@ Initiates a connection on a socket. ```c int wolfIP_sock_accept(struct wolfIP *s, int sockfd, struct wolfIP_sockaddr *addr, socklen_t *addrlen); ``` -Accepts a connection on a listening socket. +Accepts a connection on a listening socket. Only a connection whose handshake has completed (`ESTABLISHED`, or `CLOSE_WAIT` when the peer already sent its FIN) is returned. A call while the handshake is still running moves it to a child socket the stack holds back and returns `-WOLFIP_EAGAIN`; the listener reports `CB_EVENT_READABLE` again once the child is established, and the next call returns it. So `-WOLFIP_EAGAIN` can follow `CB_EVENT_READABLE`, and the caller must not treat it as a connection. A held-back child that is reset or gets no answer after `TCP_SYNACK_MAXRTX` SYN-ACK retransmissions (default 3) is released silently, and closing the listener resets the ones still held. When the socket table is full, one still in its handshake gives up its slot before a socket the application closed in `FIN_WAIT_1`, `CLOSING` or `LAST_ACK` does. One that completes but is not accepted within `TCP_PREACCEPT_TIMEOUT_MS` (default 5 s) is reset, as a connection the listener completed itself would be. - Parameters: - s: wolfIP instance - sockfd: Listening socket descriptor - addr: Address of connecting peer - addrlen: Length of address structure -- Returns: New socket descriptor or negative error code +- Returns: New socket descriptor, `-WOLFIP_EAGAIN` when no connection is ready, or another negative error code ```c int wolfIP_sock_abort(struct wolfIP *s, int sockfd); @@ -200,7 +200,7 @@ wolfIP never blocks, so every call above can ask the caller to retry. On a TCP s | `-WOLFIP_EBADF` | Stale descriptor: its socket was released and the slot handed out again | | `-1` | The operation cannot succeed on this socket (a listener, or a closing state) | -On a connected socket `wolfIP_sock_close()` queues a FIN and returns 0, or returns `-WOLFIP_EAGAIN` when the transmit buffer has no room for the FIN yet, in which case the caller retries. Once the FIN is queued, the stack finishes the exchange and releases the socket by itself, without notification, once the exchange completes, the peer resets, or the close times out. Until then, calling `wolfIP_sock_close()` again returns 0 without effect; after that, the next `wolfIP_sock_socket()` or `wolfIP_sock_accept()` can reuse its slot. When every TCP slot is taken, `wolfIP_sock_socket()` and `wolfIP_sock_accept()` take the slot of a socket the application has already closed, preferring one in `TIME_WAIT`, then `FIN_WAIT_2`, and reset its peer if the FIN exchange has not finished. A descriptor carries the generation of its slot in bits 16-30, so once the slot has been handed out again, every call on the old descriptor returns `-WOLFIP_EBADF` instead of acting on the new socket. The generation wraps after 32768 reuses of the same slot. A slot the stack released on its own (peer reset, retransmission timeout) while the application still holds the descriptor is reused only when no other slot is free; from then on that descriptor, too, answers `-WOLFIP_EBADF` instead of reporting the connection as closed. +On a connected socket `wolfIP_sock_close()` queues a FIN and returns 0, or returns `-WOLFIP_EAGAIN` when the transmit buffer has no room for the FIN yet, in which case the caller retries. Once the FIN is queued, the stack finishes the exchange and releases the socket by itself, without notification, once the exchange completes, the peer resets, or the close times out. Until then, calling `wolfIP_sock_close()` again returns 0 without effect; after that, the next `wolfIP_sock_socket()` or `wolfIP_sock_accept()` can reuse its slot. When every TCP slot is taken, `wolfIP_sock_socket()` and `wolfIP_sock_accept()` take the slot of a socket the application has already closed or has not accepted yet: one in `TIME_WAIT` first, then `FIN_WAIT_2`, then a held-back connection still in its handshake, then `FIN_WAIT_1`, `CLOSING` or `LAST_ACK`, resetting the peer where the exchange has not finished. A descriptor carries the generation of its slot in bits 16-30, so once the slot has been handed out again, every call on the old descriptor returns `-WOLFIP_EBADF` instead of acting on the new socket. The generation wraps after 32768 reuses of the same slot. A slot the stack released on its own (peer reset, retransmission timeout) while the application still holds the descriptor is reused only when no other slot is free; from then on that descriptor, too, answers `-WOLFIP_EBADF` instead of reporting the connection as closed. ## Stack Interface Functions diff --git a/src/test/unit/unit.c b/src/test/unit/unit.c index db5686ac..ba204aac 100644 --- a/src/test/unit/unit.c +++ b/src/test/unit/unit.c @@ -655,7 +655,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_utils, test_tcp_listener_closed_while_pending_is_not_readable); tcase_add_test(tc_utils, test_tcp_listener_preaccept_timeout_reverts_port); tcase_add_test(tc_utils, test_tcp_listener_preaccept_timeout_resets_peer); - tcase_add_test(tc_utils, test_tcp_listener_preaccept_accept_no_socket_resets_peer); + tcase_add_test(tc_utils, test_tcp_listener_preaccept_accept_no_socket_keeps_connection); tcase_add_test(tc_utils, test_tcp_listener_preaccept_rst_keeps_listener); tcase_add_test(tc_utils, test_tcp_listener_closed_preaccept_rst_frees_fin_wait_1); tcase_add_test(tc_utils, test_tcp_listener_closed_preaccept_rst_frees_last_ack); @@ -665,7 +665,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_utils, test_sock_abort_without_peer_sends_nothing); tcase_add_test(tc_utils, test_sock_abort_fin_wait_1_rst_covers_sent_fin); tcase_add_test(tc_utils, test_sock_abort_fin_wait_2_rst_covers_acked_fin); - tcase_add_test(tc_utils, test_sock_abort_syn_rcvd_rst_covers_syn); + tcase_add_test(tc_utils, test_listener_close_resets_parked_syn_rcvd_child); tcase_add_test(tc_utils, test_sock_abort_queued_data_rst_at_snd_una); tcase_add_test(tc_utils, test_sock_abort_preaccept_listener_stops_listening); tcase_add_test(tc_utils, test_sock_abort_after_data_rto_rst_covers_sent_data); @@ -681,6 +681,17 @@ Suite *wolf_suite(void) tcase_add_test(tc_utils, test_tcp_full_table_keeps_open_sockets); tcase_add_test(tc_utils, test_tcp_full_table_reclaims_fin_wait_1_with_rst); tcase_add_test(tc_utils, test_tcp_full_table_reclaims_last_ack_silently); + tcase_add_test(tc_utils, test_accept_parks_child_until_established); + tcase_add_test(tc_utils, test_accept_hands_out_parked_close_wait); + tcase_add_test(tc_utils, test_accept_drops_reset_parked_child_silently); + tcase_add_test(tc_utils, test_accept_parked_child_gives_up_after_synack_cap); + tcase_add_test(tc_utils, test_full_table_reclaims_parked_syn_rcvd_child); + tcase_add_test(tc_utils, test_parked_child_of_replaced_listener_not_handed_out); + tcase_add_test(tc_utils, test_parked_child_not_accepted_is_reset_after_timeout); + tcase_add_test(tc_utils, test_accept_reannounces_second_ready_child); + tcase_add_test(tc_utils, test_parked_child_handed_out_with_wide_listener_generation); + tcase_add_test(tc_utils, test_parked_child_in_freed_slot_retires_old_descriptor); + tcase_add_test(tc_utils, test_parked_child_in_held_slot_stays_silent); tcase_add_test(tc_utils, test_sock_abort_syn_rcvd_listener_resets_and_stops_listening); tcase_add_test(tc_utils, test_tcp_listener_preaccept_revert_drains_connection_state); tcase_add_test(tc_utils, test_tcp_listener_preaccept_close_rto_retransmits_finack); diff --git a/src/test/unit/unit_shared.c b/src/test/unit/unit_shared.c index 578a98fb..47ce051c 100644 --- a/src/test/unit/unit_shared.c +++ b/src/test/unit/unit_shared.c @@ -549,6 +549,35 @@ static void inject_tcp_segment(struct wolfIP *s, unsigned int if_idx, ip4 src_ip tcp_input(s, if_idx, &seg, sizeof(struct wolfIP_eth_frame) + IP_HEADER_LEN + TCP_HEADER_LEN); } +static struct tsocket *parked_child(struct wolfIP *s, int listen_sd) +{ + int i; + + for (i = 0; i < MAX_TCPSOCKETS; i++) { + struct tsocket *t = &s->tcpsockets[i]; + + if ((t->proto == WI_IPPROTO_TCP) && + (tcp_parked_parent(t) == &s->tcpsockets[SOCKET_UNMARK(listen_sd)])) + return t; + } + return NULL; +} + +/* accept() parks the child, the peer ACKs its SYN-ACK, accept() hands it out. */ +static int accept_after_handshake(struct wolfIP *s, int listen_sd, + struct wolfIP_sockaddr *addr, socklen_t *addrlen) +{ + struct tsocket *t; + + ck_assert_int_eq(wolfIP_sock_accept(s, listen_sd, addr, addrlen), -WOLFIP_EAGAIN); + t = parked_child(s, listen_sd); + ck_assert_ptr_nonnull(t); + inject_tcp_segment(s, t->if_idx, t->remote_ip, t->local_ip, t->dst_port, + t->src_port, t->sock.tcp.ack, t->sock.tcp.seq + 1, TCP_FLAG_ACK); + ck_assert_int_eq(t->sock.tcp.state, TCP_ESTABLISHED); + return wolfIP_sock_accept(s, listen_sd, addr, addrlen); +} + static int tcp_option_find(const struct wolfIP_tcp_seg *tcp, uint8_t kind) { const uint8_t *opt = tcp->data; diff --git a/src/test/unit/unit_tests_api.c b/src/test/unit/unit_tests_api.c index 1d6eec25..3dad241f 100644 --- a/src/test/unit/unit_tests_api.c +++ b/src/test/unit/unit_tests_api.c @@ -1092,7 +1092,7 @@ START_TEST(test_tcp_listen_before_ipconfig_accepts_after) listener = &s.tcpsockets[SOCKET_UNMARK(listen_sd)]; ck_assert_int_eq(listener->sock.tcp.state, TCP_SYN_RCVD); - client_sd = wolfIP_sock_accept(&s, listen_sd, (struct wolfIP_sockaddr *)&sin, + client_sd = accept_after_handshake(&s, listen_sd, (struct wolfIP_sockaddr *)&sin, &alen); ck_assert_int_gt(client_sd, 0); } @@ -3044,7 +3044,7 @@ START_TEST(test_sock_accept_success_sets_addr) listener = &s.tcpsockets[SOCKET_UNMARK(listen_sd)]; ck_assert_int_eq(listener->sock.tcp.state, TCP_SYN_RCVD); - client_sd = wolfIP_sock_accept(&s, listen_sd, (struct wolfIP_sockaddr *)&sin, &alen); + client_sd = accept_after_handshake(&s, listen_sd, (struct wolfIP_sockaddr *)&sin, &alen); ck_assert_int_gt(client_sd, 0); ck_assert_uint_eq(alen, sizeof(sin)); ck_assert_uint_eq(sin.sin_family, AF_INET); @@ -3257,7 +3257,8 @@ START_TEST(test_sock_accept_no_available_socket) ts->sock.tcp.state = TCP_ESTABLISHED; } - ck_assert_int_eq(wolfIP_sock_accept(&s, listen_sd, NULL, NULL), -1); + /* No slot for the child: the connection stays on the listener. */ + ck_assert_int_eq(wolfIP_sock_accept(&s, listen_sd, NULL, NULL), -WOLFIP_EAGAIN); } END_TEST @@ -3294,7 +3295,9 @@ START_TEST(test_sock_accept_no_free_socket_syn_rcvd) s.tcpsockets[i].sock.tcp.state = TCP_ESTABLISHED; } - ck_assert_int_eq(wolfIP_sock_accept(&s, listen_sd, NULL, NULL), -1); + /* No slot to park the child in: the listener keeps the handshake. */ + ck_assert_int_eq(wolfIP_sock_accept(&s, listen_sd, NULL, NULL), -WOLFIP_EAGAIN); + ck_assert_int_eq(ts->sock.tcp.state, TCP_SYN_RCVD); } END_TEST @@ -3325,7 +3328,6 @@ START_TEST(test_sock_accept_bound_local_ip_no_match) { struct wolfIP s; int listen_sd; - int client_sd; struct tsocket *listener; struct wolfIP_sockaddr_in sin; @@ -3349,8 +3351,8 @@ START_TEST(test_sock_accept_bound_local_ip_no_match) inject_tcp_syn(&s, TEST_PRIMARY_IF, 0x0A000001U, 1234); s.if_count = 0; - client_sd = wolfIP_sock_accept(&s, listen_sd, NULL, NULL); - ck_assert_int_gt(client_sd, 0); + ck_assert_int_eq(wolfIP_sock_accept(&s, listen_sd, NULL, NULL), -WOLFIP_EAGAIN); + ck_assert_ptr_nonnull(parked_child(&s, listen_sd)); ck_assert_uint_eq(listener->if_idx, TEST_PRIMARY_IF); } END_TEST @@ -3359,7 +3361,6 @@ START_TEST(test_sock_accept_starts_rto_timer) { struct wolfIP s; int listen_sd; - int client_sd; struct tsocket *listener; struct tsocket *accepted; struct wolfIP_sockaddr_in sin; @@ -3381,10 +3382,9 @@ START_TEST(test_sock_accept_starts_rto_timer) listener = &s.tcpsockets[SOCKET_UNMARK(listen_sd)]; ck_assert_int_eq(listener->sock.tcp.state, TCP_SYN_RCVD); - client_sd = wolfIP_sock_accept(&s, listen_sd, NULL, NULL); - ck_assert_int_gt(client_sd, 0); - - accepted = &s.tcpsockets[SOCKET_UNMARK(client_sd)]; + ck_assert_int_eq(wolfIP_sock_accept(&s, listen_sd, NULL, NULL), -WOLFIP_EAGAIN); + accepted = parked_child(&s, listen_sd); + ck_assert_ptr_nonnull(accepted); /* Accepted socket should be in SYN_RCVD state with RTO timer active */ ck_assert_int_eq(accepted->sock.tcp.state, TCP_SYN_RCVD); ck_assert_uint_eq(accepted->sock.tcp.ctrl_rto_active, 1); @@ -3401,7 +3401,6 @@ START_TEST(test_sock_accept_initializes_snd_una) { struct wolfIP s; int listen_sd; - int client_sd; struct tsocket *listener; struct tsocket *accepted; struct wolfIP_sockaddr_in sin; @@ -3430,10 +3429,9 @@ START_TEST(test_sock_accept_initializes_snd_una) listener->sock.tcp.snd_una = isn; } - client_sd = wolfIP_sock_accept(&s, listen_sd, NULL, NULL); - ck_assert_int_gt(client_sd, 0); - - accepted = &s.tcpsockets[SOCKET_UNMARK(client_sd)]; + ck_assert_int_eq(wolfIP_sock_accept(&s, listen_sd, NULL, NULL), -WOLFIP_EAGAIN); + accepted = parked_child(&s, listen_sd); + ck_assert_ptr_nonnull(accepted); /* While in SYN_RCVD the socket's seq stays at the ISN; the final ACK * handler advances it to ISN+1 on establishment. */ ck_assert_uint_eq(accepted->sock.tcp.seq, 0x80000000U); @@ -3446,7 +3444,6 @@ START_TEST(test_sock_accept_clones_half_open_state_and_queues_synack) { struct wolfIP s; int listen_sd; - int client_sd; struct tsocket *listener; struct tsocket *accepted; struct wolfIP_sockaddr_in sin; @@ -3527,14 +3524,12 @@ START_TEST(test_sock_accept_clones_half_open_state_and_queues_synack) pre_accept_sack_permitted = listener->sock.tcp.sack_permitted; /* Accept should fork the half-open state into a child socket and queue a SYN-ACK there. */ - client_sd = wolfIP_sock_accept(&s, listen_sd, NULL, NULL); - ck_assert_int_gt(client_sd, 0); - - accepted = &s.tcpsockets[SOCKET_UNMARK(client_sd)]; + ck_assert_int_eq(wolfIP_sock_accept(&s, listen_sd, NULL, NULL), -WOLFIP_EAGAIN); + accepted = parked_child(&s, listen_sd); + ck_assert_ptr_nonnull(accepted); /* The child socket should inherit the negotiated transport parameters verbatim. */ ck_assert_int_eq(accepted->sock.tcp.state, TCP_SYN_RCVD); - ck_assert_ptr_eq(accepted->callback, test_socket_cb); - ck_assert_ptr_eq(accepted->callback_arg, cb_arg); + ck_assert_ptr_null(accepted->callback); ck_assert_uint_eq(accepted->local_ip, pre_accept_local_ip); ck_assert_uint_eq(accepted->bound_local_ip, listener->bound_local_ip); ck_assert_uint_eq(accepted->if_idx, TEST_PRIMARY_IF); @@ -3580,7 +3575,6 @@ START_TEST(test_sock_accept_synack_rto_txbuf_full_does_not_consume_retry) { struct wolfIP s; int listen_sd; - int client_sd; struct tsocket *accepted; struct wolfIP_sockaddr_in sin; uint8_t tiny_txbuf[32]; @@ -3599,10 +3593,9 @@ START_TEST(test_sock_accept_synack_rto_txbuf_full_does_not_consume_retry) ck_assert_int_eq(wolfIP_sock_listen(&s, listen_sd, 1), 0); inject_tcp_syn(&s, TEST_PRIMARY_IF, 0x0A000001U, 1234); - client_sd = wolfIP_sock_accept(&s, listen_sd, NULL, NULL); - ck_assert_int_gt(client_sd, 0); - - accepted = &s.tcpsockets[SOCKET_UNMARK(client_sd)]; + ck_assert_int_eq(wolfIP_sock_accept(&s, listen_sd, NULL, NULL), -WOLFIP_EAGAIN); + accepted = parked_child(&s, listen_sd); + ck_assert_ptr_nonnull(accepted); ck_assert_int_eq(accepted->sock.tcp.state, TCP_SYN_RCVD); fifo_init(&accepted->sock.tcp.txbuf, tiny_txbuf, sizeof(tiny_txbuf)); @@ -3622,7 +3615,6 @@ START_TEST(test_sock_accept_synack_retransmission) { struct wolfIP s; int listen_sd; - int client_sd; struct tsocket *accepted; struct wolfIP_sockaddr_in sin; struct pkt_desc *desc; @@ -3642,10 +3634,9 @@ START_TEST(test_sock_accept_synack_retransmission) ck_assert_int_eq(wolfIP_sock_listen(&s, listen_sd, 1), 0); inject_tcp_syn(&s, TEST_PRIMARY_IF, 0x0A000001U, 1234); - client_sd = wolfIP_sock_accept(&s, listen_sd, NULL, NULL); - ck_assert_int_gt(client_sd, 0); - - accepted = &s.tcpsockets[SOCKET_UNMARK(client_sd)]; + ck_assert_int_eq(wolfIP_sock_accept(&s, listen_sd, NULL, NULL), -WOLFIP_EAGAIN); + accepted = parked_child(&s, listen_sd); + ck_assert_ptr_nonnull(accepted); ck_assert_int_eq(accepted->sock.tcp.state, TCP_SYN_RCVD); /* Clear tx buffer to prepare for retransmission check */ @@ -3669,7 +3660,6 @@ START_TEST(test_sock_accept_synack_window_not_scaled) { struct wolfIP s; int listen_sd; - int client_sd; struct tsocket *listener; struct tsocket *accepted; struct wolfIP_sockaddr_in sin; @@ -3695,10 +3685,9 @@ START_TEST(test_sock_accept_synack_window_not_scaled) listener->sock.tcp.ws_enabled = 1; listener->sock.tcp.rcv_wscale = 3; - client_sd = wolfIP_sock_accept(&s, listen_sd, NULL, NULL); - ck_assert_int_gt(client_sd, 0); - - accepted = &s.tcpsockets[SOCKET_UNMARK(client_sd)]; + ck_assert_int_eq(wolfIP_sock_accept(&s, listen_sd, NULL, NULL), -WOLFIP_EAGAIN); + accepted = parked_child(&s, listen_sd); + ck_assert_ptr_nonnull(accepted); desc = fifo_peek(&accepted->sock.tcp.txbuf); ck_assert_ptr_nonnull(desc); seg = (struct wolfIP_tcp_seg *)(accepted->txmem + desc->pos + sizeof(*desc)); @@ -3712,7 +3701,6 @@ START_TEST(test_sock_accept_ack_transitions_to_established) { struct wolfIP s; int listen_sd; - int client_sd; struct tsocket *accepted; struct wolfIP_sockaddr_in sin; struct wolfIP_tcp_seg ack; @@ -3732,10 +3720,9 @@ START_TEST(test_sock_accept_ack_transitions_to_established) ck_assert_int_eq(wolfIP_sock_listen(&s, listen_sd, 1), 0); inject_tcp_syn(&s, TEST_PRIMARY_IF, 0x0A000001U, 1234); - client_sd = wolfIP_sock_accept(&s, listen_sd, NULL, NULL); - ck_assert_int_gt(client_sd, 0); - - accepted = &s.tcpsockets[SOCKET_UNMARK(client_sd)]; + ck_assert_int_eq(wolfIP_sock_accept(&s, listen_sd, NULL, NULL), -WOLFIP_EAGAIN); + accepted = parked_child(&s, listen_sd); + ck_assert_ptr_nonnull(accepted); ck_assert_int_eq(accepted->sock.tcp.state, TCP_SYN_RCVD); ck_assert_uint_eq(accepted->sock.tcp.ctrl_rto_active, 1); @@ -3769,7 +3756,8 @@ START_TEST(test_sock_accept_ack_transitions_to_established) ck_assert_int_eq(accepted->sock.tcp.state, TCP_ESTABLISHED); ck_assert_uint_eq(accepted->sock.tcp.ctrl_rto_active, 0); ck_assert_uint_eq(accepted->sock.tcp.ctrl_rto_retries, 0); - ck_assert_int_eq(accepted->sock.tcp.tmr_rto, NO_TIMER); + /* Still held back from accept(), so the pre-accept timeout runs. */ + ck_assert_uint_eq(accepted->sock.tcp.preaccept_timeout_active, 1); /* Should be signaled as writable */ ck_assert(accepted->events & CB_EVENT_WRITABLE); } @@ -5018,7 +5006,7 @@ END_TEST START_TEST(test_accepted_socket_destroyed_on_synrcvd_rto_expiry) { struct wolfIP s; - int listen_sd, client_sd; + int listen_sd; struct tsocket *accepted; struct wolfIP_sockaddr_in sin; @@ -5036,10 +5024,9 @@ START_TEST(test_accepted_socket_destroyed_on_synrcvd_rto_expiry) ck_assert_int_eq(wolfIP_sock_listen(&s, listen_sd, 1), 0); inject_tcp_syn(&s, TEST_PRIMARY_IF, 0x0A000001U, 1234); - client_sd = wolfIP_sock_accept(&s, listen_sd, NULL, NULL); - ck_assert_int_gt(client_sd, 0); - - accepted = &s.tcpsockets[SOCKET_UNMARK(client_sd)]; + ck_assert_int_eq(wolfIP_sock_accept(&s, listen_sd, NULL, NULL), -WOLFIP_EAGAIN); + accepted = parked_child(&s, listen_sd); + ck_assert_ptr_nonnull(accepted); ck_assert_int_eq(accepted->sock.tcp.state, TCP_SYN_RCVD); /* Exhaust all ctrl-RTO retries */ @@ -5956,7 +5943,7 @@ END_TEST START_TEST(test_syn_rcvd_bad_ack_sends_rst) { struct wolfIP s; - int listen_sd, client_sd; + int listen_sd; struct tsocket *accepted; struct wolfIP_sockaddr_in sin; @@ -5975,10 +5962,9 @@ START_TEST(test_syn_rcvd_bad_ack_sends_rst) /* SYN puts listen socket in SYN_RCVD, accept creates new socket */ inject_tcp_syn(&s, TEST_PRIMARY_IF, 0x0A000001U, 1234); - client_sd = wolfIP_sock_accept(&s, listen_sd, NULL, NULL); - ck_assert_int_gt(client_sd, 0); - - accepted = &s.tcpsockets[SOCKET_UNMARK(client_sd)]; + ck_assert_int_eq(wolfIP_sock_accept(&s, listen_sd, NULL, NULL), -WOLFIP_EAGAIN); + accepted = parked_child(&s, listen_sd); + ck_assert_ptr_nonnull(accepted); ck_assert_int_eq(accepted->sock.tcp.state, TCP_SYN_RCVD); /* Inject ACK with wrong ack value (99 instead of snd_una+1) */ diff --git a/src/test/unit/unit_tests_dns_dhcp.c b/src/test/unit/unit_tests_dns_dhcp.c index 238c95c0..cdc4a945 100644 --- a/src/test/unit/unit_tests_dns_dhcp.c +++ b/src/test/unit/unit_tests_dns_dhcp.c @@ -4313,16 +4313,18 @@ START_TEST(test_sock_accept_success) inject_tcp_syn(&s, TEST_PRIMARY_IF, 0x0A000001U, 1234); - new_sd = wolfIP_sock_accept(&s, listen_sd, (struct wolfIP_sockaddr *)&sin, &alen); - ck_assert_int_gt(new_sd, 0); - new_ts = &s.tcpsockets[SOCKET_UNMARK(new_sd)]; + ck_assert_int_eq(wolfIP_sock_accept(&s, listen_sd, (struct wolfIP_sockaddr *)&sin, &alen), -WOLFIP_EAGAIN); + new_ts = parked_child(&s, listen_sd); + ck_assert_ptr_nonnull(new_ts); /* After accept(), socket stays in SYN_RCVD until final ACK completes * the three-way handshake (SYN-ACK retransmission fix). */ ck_assert_int_eq(new_ts->sock.tcp.state, TCP_SYN_RCVD); - ck_assert_uint_eq(sin.sin_port, ee16(new_ts->dst_port)); - listen_ts = &s.tcpsockets[SOCKET_UNMARK(listen_sd)]; ck_assert_int_eq(listen_ts->sock.tcp.state, TCP_LISTEN); + + new_sd = accept_after_handshake(&s, listen_sd, (struct wolfIP_sockaddr *)&sin, &alen); + ck_assert_int_eq(SOCKET_UNMARK(new_sd), (int)(new_ts - s.tcpsockets)); + ck_assert_uint_eq(sin.sin_port, ee16(new_ts->dst_port)); } END_TEST @@ -4330,7 +4332,6 @@ START_TEST(test_sock_accept_ack_with_payload_completes_handshake) { struct wolfIP s; int listen_sd; - int new_sd; struct wolfIP_sockaddr_in sin; socklen_t alen = sizeof(sin); struct tsocket *new_ts; @@ -4359,9 +4360,9 @@ START_TEST(test_sock_accept_ack_with_payload_completes_handshake) inject_tcp_syn(&s, TEST_PRIMARY_IF, local_ip, local_port); - new_sd = wolfIP_sock_accept(&s, listen_sd, (struct wolfIP_sockaddr *)&sin, &alen); - ck_assert_int_gt(new_sd, 0); - new_ts = &s.tcpsockets[SOCKET_UNMARK(new_sd)]; + ck_assert_int_eq(wolfIP_sock_accept(&s, listen_sd, (struct wolfIP_sockaddr *)&sin, &alen), -WOLFIP_EAGAIN); + new_ts = parked_child(&s, listen_sd); + ck_assert_ptr_nonnull(new_ts); ck_assert_int_eq(new_ts->sock.tcp.state, TCP_SYN_RCVD); memset(buf, 0, sizeof(buf)); @@ -4396,7 +4397,6 @@ START_TEST(test_sock_accept_ack_at_snd_nxt_completes_handshake) { struct wolfIP s; int listen_sd; - int new_sd; struct wolfIP_sockaddr_in sin; socklen_t alen = sizeof(sin); struct tsocket *new_ts; @@ -4421,9 +4421,9 @@ START_TEST(test_sock_accept_ack_at_snd_nxt_completes_handshake) inject_tcp_syn(&s, TEST_PRIMARY_IF, local_ip, local_port); - new_sd = wolfIP_sock_accept(&s, listen_sd, (struct wolfIP_sockaddr *)&sin, &alen); - ck_assert_int_gt(new_sd, 0); - new_ts = &s.tcpsockets[SOCKET_UNMARK(new_sd)]; + ck_assert_int_eq(wolfIP_sock_accept(&s, listen_sd, (struct wolfIP_sockaddr *)&sin, &alen), -WOLFIP_EAGAIN); + new_ts = parked_child(&s, listen_sd); + ck_assert_ptr_nonnull(new_ts); ck_assert_int_eq(new_ts->sock.tcp.state, TCP_SYN_RCVD); memset(&ackseg, 0, sizeof(ackseg)); @@ -4453,7 +4453,6 @@ START_TEST(test_sock_accept_ack_psh_with_payload_completes_handshake) { struct wolfIP s; int listen_sd; - int new_sd; struct wolfIP_sockaddr_in sin; socklen_t alen = sizeof(sin); struct tsocket *new_ts; @@ -4482,9 +4481,9 @@ START_TEST(test_sock_accept_ack_psh_with_payload_completes_handshake) inject_tcp_syn(&s, TEST_PRIMARY_IF, local_ip, local_port); - new_sd = wolfIP_sock_accept(&s, listen_sd, (struct wolfIP_sockaddr *)&sin, &alen); - ck_assert_int_gt(new_sd, 0); - new_ts = &s.tcpsockets[SOCKET_UNMARK(new_sd)]; + ck_assert_int_eq(wolfIP_sock_accept(&s, listen_sd, (struct wolfIP_sockaddr *)&sin, &alen), -WOLFIP_EAGAIN); + new_ts = parked_child(&s, listen_sd); + ck_assert_ptr_nonnull(new_ts); ck_assert_int_eq(new_ts->sock.tcp.state, TCP_SYN_RCVD); memset(buf, 0, sizeof(buf)); diff --git a/src/test/unit/unit_tests_proto.c b/src/test/unit/unit_tests_proto.c index e94d075e..81a9714d 100644 --- a/src/test/unit/unit_tests_proto.c +++ b/src/test/unit/unit_tests_proto.c @@ -5512,7 +5512,6 @@ START_TEST(test_tcp_listen_accepts_bound_interface) const ip4 secondary_ip = 0xC0A80101U; const uint16_t listen_port = 23456; int listen_fd; - int client_fd; struct wolfIP_sockaddr_in addr; struct tsocket *listener; struct tsocket *client; @@ -5536,9 +5535,9 @@ START_TEST(test_tcp_listen_accepts_bound_interface) ck_assert_uint_eq(listener->local_ip, secondary_ip); ck_assert_uint_eq(listener->if_idx, TEST_SECOND_IF); - client_fd = wolfIP_sock_accept(&s, listen_fd, NULL, NULL); - ck_assert_int_ge(client_fd, 0); - client = &s.tcpsockets[SOCKET_UNMARK(client_fd)]; + ck_assert_int_eq(wolfIP_sock_accept(&s, listen_fd, NULL, NULL), -WOLFIP_EAGAIN); + client = parked_child(&s, listen_fd); + ck_assert_ptr_nonnull(client); ck_assert_uint_eq(client->local_ip, secondary_ip); ck_assert_uint_eq(client->bound_local_ip, secondary_ip); /* After accept(), socket stays in SYN_RCVD until final ACK. */ @@ -5600,7 +5599,6 @@ START_TEST(test_tcp_listen_accepts_any_interface) const ip4 secondary_ip = 0xC0A80105U; const uint16_t listen_port = 34567; int listen_fd; - int client_fd; struct wolfIP_sockaddr_in addr; struct tsocket *listener; struct tsocket *client; @@ -5624,9 +5622,9 @@ START_TEST(test_tcp_listen_accepts_any_interface) ck_assert_uint_eq(listener->local_ip, secondary_ip); ck_assert_uint_eq(listener->if_idx, TEST_SECOND_IF); - client_fd = wolfIP_sock_accept(&s, listen_fd, NULL, NULL); - ck_assert_int_ge(client_fd, 0); - client = &s.tcpsockets[SOCKET_UNMARK(client_fd)]; + ck_assert_int_eq(wolfIP_sock_accept(&s, listen_fd, NULL, NULL), -WOLFIP_EAGAIN); + client = parked_child(&s, listen_fd); + ck_assert_ptr_nonnull(client); ck_assert_uint_eq(client->local_ip, secondary_ip); /* After accept(), socket stays in SYN_RCVD until final ACK. */ ck_assert_int_eq(client->sock.tcp.state, TCP_SYN_RCVD); diff --git a/src/test/unit/unit_tests_tcp_flow.c b/src/test/unit/unit_tests_tcp_flow.c index e99b8d0d..42607938 100644 --- a/src/test/unit/unit_tests_tcp_flow.c +++ b/src/test/unit/unit_tests_tcp_flow.c @@ -4872,7 +4872,6 @@ START_TEST(test_tcp_input_listen_accept_final_ack_does_not_send_rst) { struct wolfIP s; int listen_sd; - int client_sd; struct tsocket *listen_ts; struct tsocket *client_ts; struct wolfIP_sockaddr_in sin; @@ -4897,10 +4896,9 @@ START_TEST(test_tcp_input_listen_accept_final_ack_does_not_send_rst) listen_ts = &s.tcpsockets[SOCKET_UNMARK(listen_sd)]; ck_assert_int_eq(listen_ts->sock.tcp.state, TCP_SYN_RCVD); - client_sd = wolfIP_sock_accept(&s, listen_sd, NULL, NULL); - ck_assert_int_gt(client_sd, 0); - - client_ts = &s.tcpsockets[SOCKET_UNMARK(client_sd)]; + ck_assert_int_eq(wolfIP_sock_accept(&s, listen_sd, NULL, NULL), -WOLFIP_EAGAIN); + client_ts = parked_child(&s, listen_sd); + ck_assert_ptr_nonnull(client_ts); ck_assert_int_eq(listen_ts->sock.tcp.state, TCP_LISTEN); ck_assert_int_eq(client_ts->sock.tcp.state, TCP_SYN_RCVD); @@ -6017,14 +6015,15 @@ END_TEST /* With no socket free, accept() cannot hand the connection off and drops it; * the peer must be reset rather than left talking to a reverted listener. */ -START_TEST(test_tcp_listener_preaccept_accept_no_socket_resets_peer) +START_TEST(test_tcp_listener_preaccept_accept_no_socket_keeps_connection) { struct wolfIP s; int fd; + int sd; + int last = -1; struct tsocket *lsn; struct wolfIP_sockaddr_in peer; socklen_t peer_len = sizeof(peer); - const struct wolfIP_tcp_seg *out; uint32_t frames_before; wolfIP_init(&s); @@ -6038,19 +6037,23 @@ START_TEST(test_tcp_listener_preaccept_accept_no_socket_resets_peer) llk_complete_handshake(&s, lsn, LLK_ATT_IP, 41000, 1); ck_assert_int_eq(lsn->sock.tcp.state, TCP_ESTABLISHED); - while (wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_STREAM, - WI_IPPROTO_TCP) > 0) - ; + while ((sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_STREAM, + WI_IPPROTO_TCP)) > 0) + last = sd; + ck_assert_int_gt(last, 0); + /* No slot for the child: the connection waits on the listener. */ memset(&peer, 0, sizeof(peer)); frames_before = last_frame_sent_count; ck_assert_int_eq(wolfIP_sock_accept(&s, fd, - (struct wolfIP_sockaddr *)&peer, &peer_len), -1); - ck_assert_uint_gt(last_frame_sent_count, frames_before); - out = llk_last_tcp(); - ck_assert_ptr_nonnull(out); - ck_assert_uint_eq(out->flags, TCP_FLAG_RST | TCP_FLAG_ACK); - ck_assert_uint_eq(ee16(out->dst_port), 41000); + (struct wolfIP_sockaddr *)&peer, &peer_len), -WOLFIP_EAGAIN); + ck_assert_uint_eq(last_frame_sent_count, frames_before); + ck_assert_int_eq(lsn->sock.tcp.state, TCP_ESTABLISHED); + + ck_assert_int_eq(wolfIP_sock_close(&s, last), 0); + ck_assert_int_ge(wolfIP_sock_accept(&s, fd, + (struct wolfIP_sockaddr *)&peer, &peer_len), 0); + ck_assert_uint_eq(ee16(peer.sin_port), 41000); ck_assert_int_eq(lsn->sock.tcp.state, TCP_LISTEN); } END_TEST @@ -6340,12 +6343,12 @@ START_TEST(test_sock_abort_fin_wait_2_rst_covers_acked_fin) } END_TEST -START_TEST(test_sock_abort_syn_rcvd_rst_covers_syn) +START_TEST(test_listener_close_resets_parked_syn_rcvd_child) { struct wolfIP s; int fd; - int sd; struct tsocket *lsn; + struct tsocket *child; uint32_t isn; const struct wolfIP_tcp_seg *out; @@ -6359,12 +6362,13 @@ START_TEST(test_sock_abort_syn_rcvd_rst_covers_syn) ck_assert_int_eq(lsn->sock.tcp.state, TCP_SYN_RCVD); isn = lsn->sock.tcp.seq; - sd = wolfIP_sock_accept(&s, fd, NULL, NULL); - ck_assert_int_ge(sd, 0); - ck_assert_int_eq(s.tcpsockets[SOCKET_UNMARK(sd)].sock.tcp.state, - TCP_SYN_RCVD); + ck_assert_int_eq(wolfIP_sock_accept(&s, fd, NULL, NULL), -WOLFIP_EAGAIN); + child = parked_child(&s, fd); + ck_assert_ptr_nonnull(child); + ck_assert_int_eq(child->sock.tcp.state, TCP_SYN_RCVD); - ck_assert_int_eq(wolfIP_sock_abort(&s, sd), 0); + ck_assert_int_eq(wolfIP_sock_close(&s, fd), 0); + ck_assert_int_eq(child->proto, 0); out = llk_last_tcp(); ck_assert_ptr_nonnull(out); ck_assert_uint_eq(out->flags, TCP_FLAG_RST | TCP_FLAG_ACK); @@ -6766,6 +6770,341 @@ START_TEST(test_tcp_full_table_reclaims_last_ack_silently) } END_TEST +static int park_listener(struct wolfIP *s) +{ + struct wolfIP_sockaddr_in sin; + int sd; + + wolfIP_init(s); + mock_link_init(s); + wolfIP_ipconfig_set(s, 0x0A000001U, 0xFFFFFF00U, 0); + sd = wolfIP_sock_socket(s, AF_INET, IPSTACK_SOCK_STREAM, WI_IPPROTO_TCP); + ck_assert_int_ge(sd, 0); + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = ee16(1234); + sin.sin_addr.s_addr = ee32(0x0A000001U); + ck_assert_int_eq(wolfIP_sock_bind(s, sd, (struct wolfIP_sockaddr *)&sin, sizeof(sin)), 0); + ck_assert_int_eq(wolfIP_sock_listen(s, sd, 1), 0); + wolfIP_register_callback(s, sd, test_socket_cb, (void *)0x42); + llk_keep_arp_fresh(s, 0x0A0000A1U); + return sd; +} + +static void park_final_ack(struct wolfIP *s, struct tsocket *t, uint8_t flags) +{ + inject_tcp_segment(s, t->if_idx, t->remote_ip, t->local_ip, t->dst_port, + t->src_port, t->sock.tcp.ack, t->sock.tcp.seq + 1, flags); +} + +START_TEST(test_accept_parks_child_until_established) +{ + struct wolfIP s; + int lsd, sd; + struct tsocket *lsn, *child; + + lsd = park_listener(&s); + lsn = &s.tcpsockets[SOCKET_UNMARK(lsd)]; + inject_tcp_syn(&s, TEST_PRIMARY_IF, 0x0A000001U, 1234); + ck_assert_int_eq(wolfIP_sock_can_read(&s, lsd), 1); + + ck_assert_int_eq(wolfIP_sock_accept(&s, lsd, NULL, NULL), -WOLFIP_EAGAIN); + child = parked_child(&s, lsd); + ck_assert_ptr_nonnull(child); + ck_assert_ptr_null(child->callback); + ck_assert_int_eq(lsn->sock.tcp.state, TCP_LISTEN); + ck_assert_int_eq(wolfIP_sock_can_read(&s, lsd), 0); + ck_assert_int_eq(wolfIP_sock_accept(&s, lsd, NULL, NULL), -WOLFIP_EAGAIN); + + lsn->events = 0; + park_final_ack(&s, child, TCP_FLAG_ACK); + ck_assert_int_eq(child->sock.tcp.state, TCP_ESTABLISHED); + ck_assert_uint_ne(lsn->events & CB_EVENT_READABLE, 0); + ck_assert_int_eq(wolfIP_sock_can_read(&s, lsd), 1); + + sd = wolfIP_sock_accept(&s, lsd, NULL, NULL); + ck_assert_int_ge(sd, 0); + ck_assert_ptr_eq(&s.tcpsockets[SOCKET_UNMARK(sd)], child); + ck_assert_uint_eq(child->sock.tcp.parked, 0); + ck_assert_ptr_eq(child->callback, test_socket_cb); + ck_assert_ptr_eq(child->callback_arg, (void *)0x42); + ck_assert_uint_eq(child->events, CB_EVENT_WRITABLE); + ck_assert_int_eq(wolfIP_sock_can_read(&s, lsd), 0); + ck_assert_int_eq(wolfIP_sock_accept(&s, lsd, NULL, NULL), -WOLFIP_EAGAIN); + + /* Accepted in time: the pre-accept timeout no longer applies. */ + ck_assert_uint_eq(child->sock.tcp.preaccept_timeout_active, 0); + (void)wolfIP_poll(&s, s.last_tick + TCP_PREACCEPT_TIMEOUT_MS + 10); + ck_assert_int_eq(child->proto, WI_IPPROTO_TCP); + ck_assert_int_eq(child->sock.tcp.state, TCP_ESTABLISHED); +} +END_TEST + +START_TEST(test_accept_reannounces_second_ready_child) +{ + struct wolfIP s; + int lsd, sd; + struct tsocket *lsn, *a, *b; + + lsd = park_listener(&s); + lsn = &s.tcpsockets[SOCKET_UNMARK(lsd)]; + inject_tcp_syn(&s, TEST_PRIMARY_IF, 0x0A000001U, 1234); + ck_assert_int_eq(wolfIP_sock_accept(&s, lsd, NULL, NULL), -WOLFIP_EAGAIN); + a = parked_child(&s, lsd); + ck_assert_ptr_nonnull(a); + inject_tcp_segment(&s, TEST_PRIMARY_IF, 0x0A0000A1U, 0x0A000001U, 40001, 1234, + 7, 0, TCP_FLAG_SYN); + ck_assert_int_eq(wolfIP_sock_accept(&s, lsd, NULL, NULL), -WOLFIP_EAGAIN); + b = (a == &s.tcpsockets[2]) ? &s.tcpsockets[3] : &s.tcpsockets[2]; + ck_assert_uint_eq(b->sock.tcp.parked, 1); + + /* Both complete before the application runs: one READABLE for two. */ + park_final_ack(&s, a, TCP_FLAG_ACK); + park_final_ack(&s, b, TCP_FLAG_ACK); + lsn->events = 0; + sd = wolfIP_sock_accept(&s, lsd, NULL, NULL); + ck_assert_int_ge(sd, 0); + ck_assert_uint_ne(lsn->events & CB_EVENT_READABLE, 0); + ck_assert_uint_eq(s.tcpsockets[SOCKET_UNMARK(sd)].events, CB_EVENT_WRITABLE); + + lsn->events = 0; + sd = wolfIP_sock_accept(&s, lsd, NULL, NULL); + ck_assert_int_ge(sd, 0); + ck_assert_uint_eq(lsn->events & CB_EVENT_READABLE, 0); +} +END_TEST + +START_TEST(test_parked_child_handed_out_with_wide_listener_generation) +{ + struct wolfIP s; + struct wolfIP_sockaddr_in sin; + int lsd, sd; + struct tsocket *child; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + llk_keep_arp_fresh(&s, 0x0A0000A1U); + /* The listener's slot has been reused well past 8 bits of generation. */ + s.tcp_gen[0].gen = 0x1234; + lsd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_STREAM, WI_IPPROTO_TCP); + ck_assert_uint_eq(SOCKET_GEN(lsd), 0x1234); + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = ee16(1234); + sin.sin_addr.s_addr = ee32(0x0A000001U); + ck_assert_int_eq(wolfIP_sock_bind(&s, lsd, (struct wolfIP_sockaddr *)&sin, sizeof(sin)), 0); + ck_assert_int_eq(wolfIP_sock_listen(&s, lsd, 1), 0); + + inject_tcp_syn(&s, TEST_PRIMARY_IF, 0x0A000001U, 1234); + ck_assert_int_eq(wolfIP_sock_accept(&s, lsd, NULL, NULL), -WOLFIP_EAGAIN); + child = parked_child(&s, lsd); + ck_assert_ptr_nonnull(child); + park_final_ack(&s, child, TCP_FLAG_ACK); + sd = wolfIP_sock_accept(&s, lsd, NULL, NULL); + ck_assert_ptr_eq(&s.tcpsockets[SOCKET_UNMARK(sd)], child); +} +END_TEST + +START_TEST(test_parked_child_not_accepted_is_reset_after_timeout) +{ + struct wolfIP s; + int lsd; + struct tsocket *child; + const struct wolfIP_tcp_seg *out; + + lsd = park_listener(&s); + inject_tcp_syn(&s, TEST_PRIMARY_IF, 0x0A000001U, 1234); + ck_assert_int_eq(wolfIP_sock_accept(&s, lsd, NULL, NULL), -WOLFIP_EAGAIN); + child = parked_child(&s, lsd); + ck_assert_ptr_nonnull(child); + park_final_ack(&s, child, TCP_FLAG_ACK); + ck_assert_int_eq(child->sock.tcp.state, TCP_ESTABLISHED); + ck_assert_uint_eq(child->sock.tcp.preaccept_timeout_active, 1); + + (void)wolfIP_poll(&s, s.last_tick + TCP_PREACCEPT_TIMEOUT_MS + 10); + ck_assert_int_eq(child->proto, 0); + out = llk_last_tcp(); + ck_assert_ptr_nonnull(out); + ck_assert_uint_eq(out->flags, TCP_FLAG_RST | TCP_FLAG_ACK); + ck_assert_uint_eq(ee16(out->dst_port), 40000); + ck_assert_int_eq(wolfIP_sock_accept(&s, lsd, NULL, NULL), -WOLFIP_EAGAIN); +} +END_TEST + +static int stale_cb_calls; + +static void stale_cb(int fd, uint16_t events, void *arg) +{ + (void)fd; (void)events; (void)arg; + stale_cb_calls++; +} + +START_TEST(test_parked_child_in_held_slot_stays_silent) +{ + struct wolfIP s; + int lsd, sd; + struct tsocket *child; + + lsd = park_listener(&s); + sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_STREAM, WI_IPPROTO_TCP); + close_socket(&s.tcpsockets[SOCKET_UNMARK(sd)]); + wolfIP_register_callback(&s, sd, stale_cb, NULL); + take_tcp_slots_except(&s, SOCKET_UNMARK(sd)); + + inject_tcp_syn(&s, TEST_PRIMARY_IF, 0x0A000001U, 1234); + ck_assert_int_eq(wolfIP_sock_accept(&s, lsd, NULL, NULL), -WOLFIP_EAGAIN); + child = parked_child(&s, lsd); + ck_assert_ptr_eq(child, &s.tcpsockets[SOCKET_UNMARK(sd)]); + ck_assert_ptr_null(child->callback); + + stale_cb_calls = 0; + park_final_ack(&s, child, TCP_FLAG_RST | TCP_FLAG_ACK); + (void)wolfIP_poll(&s, 100); + ck_assert_int_eq(child->proto, 0); + ck_assert_int_eq(stale_cb_calls, 0); +} +END_TEST + +START_TEST(test_parked_child_in_freed_slot_retires_old_descriptor) +{ + struct wolfIP s; + int lsd, sd; + struct tsocket *child; + + lsd = park_listener(&s); + /* The stack frees a slot whose descriptor the application still holds. */ + sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_STREAM, WI_IPPROTO_TCP); + ck_assert_int_ge(sd, 0); + close_socket(&s.tcpsockets[SOCKET_UNMARK(sd)]); + take_tcp_slots_except(&s, SOCKET_UNMARK(sd)); + + inject_tcp_syn(&s, TEST_PRIMARY_IF, 0x0A000001U, 1234); + ck_assert_int_eq(wolfIP_sock_accept(&s, lsd, NULL, NULL), -WOLFIP_EAGAIN); + child = parked_child(&s, lsd); + ck_assert_ptr_eq(child, &s.tcpsockets[SOCKET_UNMARK(sd)]); + ck_assert_int_eq(wolfIP_sock_abort(&s, sd), -WOLFIP_EBADF); + ck_assert_int_eq(wolfIP_sock_close(&s, sd), -WOLFIP_EBADF); + ck_assert_int_eq(child->sock.tcp.state, TCP_SYN_RCVD); +} +END_TEST + +START_TEST(test_accept_hands_out_parked_close_wait) +{ + struct wolfIP s; + int lsd, sd; + struct tsocket *child; + + lsd = park_listener(&s); + inject_tcp_syn(&s, TEST_PRIMARY_IF, 0x0A000001U, 1234); + ck_assert_int_eq(wolfIP_sock_accept(&s, lsd, NULL, NULL), -WOLFIP_EAGAIN); + child = parked_child(&s, lsd); + ck_assert_ptr_nonnull(child); + park_final_ack(&s, child, TCP_FLAG_ACK | TCP_FLAG_FIN); + ck_assert_int_eq(child->sock.tcp.state, TCP_CLOSE_WAIT); + + sd = wolfIP_sock_accept(&s, lsd, NULL, NULL); + ck_assert_ptr_eq(&s.tcpsockets[SOCKET_UNMARK(sd)], child); + ck_assert_uint_ne(child->events & CB_EVENT_READABLE, 0); +} +END_TEST + +START_TEST(test_accept_drops_reset_parked_child_silently) +{ + struct wolfIP s; + int lsd; + struct tsocket *child; + + lsd = park_listener(&s); + inject_tcp_syn(&s, TEST_PRIMARY_IF, 0x0A000001U, 1234); + ck_assert_int_eq(wolfIP_sock_accept(&s, lsd, NULL, NULL), -WOLFIP_EAGAIN); + child = parked_child(&s, lsd); + ck_assert_ptr_nonnull(child); + + park_final_ack(&s, child, TCP_FLAG_RST | TCP_FLAG_ACK); + ck_assert_int_eq(child->proto, 0); + socket_cb_calls = 0; + (void)wolfIP_poll(&s, 100); + ck_assert_int_eq(socket_cb_calls, 0); + ck_assert_int_eq(wolfIP_sock_accept(&s, lsd, NULL, NULL), -WOLFIP_EAGAIN); +} +END_TEST + +START_TEST(test_accept_parked_child_gives_up_after_synack_cap) +{ + struct wolfIP s; + int lsd; + unsigned int i; + struct tsocket *child; + + lsd = park_listener(&s); + inject_tcp_syn(&s, TEST_PRIMARY_IF, 0x0A000001U, 1234); + ck_assert_int_eq(wolfIP_sock_accept(&s, lsd, NULL, NULL), -WOLFIP_EAGAIN); + child = parked_child(&s, lsd); + ck_assert_ptr_nonnull(child); + + for (i = 0; i < TCP_SYNACK_MAXRTX; i++) { + fifo_init(&child->sock.tcp.txbuf, child->txmem, TXBUF_SIZE); + tcp_rto_cb(child); + ck_assert_int_eq(child->proto, WI_IPPROTO_TCP); + } + ck_assert_uint_eq(child->sock.tcp.ctrl_rto_retries, TCP_SYNACK_MAXRTX); + tcp_rto_cb(child); + ck_assert_int_eq(child->proto, 0); +} +END_TEST + +START_TEST(test_full_table_reclaims_parked_syn_rcvd_child) +{ + struct wolfIP s; + int lsd; + int i; + struct tsocket *child; + const struct wolfIP_tcp_seg *out; + + lsd = park_listener(&s); + inject_tcp_syn(&s, TEST_PRIMARY_IF, 0x0A000001U, 1234); + ck_assert_int_eq(wolfIP_sock_accept(&s, lsd, NULL, NULL), -WOLFIP_EAGAIN); + child = parked_child(&s, lsd); + ck_assert_ptr_nonnull(child); + for (i = 2; i < MAX_TCPSOCKETS; i++) + ck_assert_int_ge(wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_STREAM, + WI_IPPROTO_TCP), 0); + + /* A second peer: its connection takes the slot of the one still handshaking. */ + inject_tcp_segment(&s, TEST_PRIMARY_IF, 0x0A0000A1U, 0x0A000001U, 40001, 1234, + 7, 0, TCP_FLAG_SYN); + ck_assert_int_eq(wolfIP_sock_accept(&s, lsd, NULL, NULL), -WOLFIP_EAGAIN); + out = llk_last_tcp(); + ck_assert_ptr_nonnull(out); + ck_assert_uint_eq(out->flags, TCP_FLAG_RST | TCP_FLAG_ACK); + ck_assert_uint_eq(ee16(out->dst_port), 40000); + ck_assert_ptr_eq(parked_child(&s, lsd), child); + ck_assert_uint_eq(child->dst_port, 40001); +} +END_TEST + +START_TEST(test_parked_child_of_replaced_listener_not_handed_out) +{ + struct wolfIP s; + int lsd; + struct tsocket *child; + + lsd = park_listener(&s); + inject_tcp_syn(&s, TEST_PRIMARY_IF, 0x0A000001U, 1234); + ck_assert_int_eq(wolfIP_sock_accept(&s, lsd, NULL, NULL), -WOLFIP_EAGAIN); + child = parked_child(&s, lsd); + ck_assert_ptr_nonnull(child); + /* The listener's slot now belongs to a later generation. */ + s.tcp_gen[SOCKET_UNMARK(lsd)].gen++; + lsd = sock_fd_make(&s, MARK_TCP_SOCKET, SOCKET_UNMARK(lsd)); + park_final_ack(&s, child, TCP_FLAG_ACK); + ck_assert_int_eq(child->sock.tcp.state, TCP_ESTABLISHED); + ck_assert_int_eq(wolfIP_sock_accept(&s, lsd, NULL, NULL), -WOLFIP_EAGAIN); + ck_assert_int_eq(tcp_reclaim_rank(child), 2); +} +END_TEST + START_TEST(test_sock_abort_syn_rcvd_listener_resets_and_stops_listening) { struct wolfIP s; diff --git a/src/test/unit/unit_tests_tcp_state.c b/src/test/unit/unit_tests_tcp_state.c index 797a1915..f0890981 100644 --- a/src/test/unit/unit_tests_tcp_state.c +++ b/src/test/unit/unit_tests_tcp_state.c @@ -2946,7 +2946,6 @@ START_TEST(test_accept_synack_retransmit_repeats_isn) struct tsocket *accepted; struct wolfIP_sockaddr_in sin; int syn_rcvd_sd; - int acc_sd; int i; uint32_t isn; @@ -3010,9 +3009,9 @@ START_TEST(test_accept_synack_retransmit_repeats_isn) /* accept() sends a duplicate SYN-ACK from the new socket, same ISN. */ syn_rcvd_sd = (int)(syn_rcvd - s.tcpsockets) | MARK_TCP_SOCKET; - acc_sd = wolfIP_sock_accept(&s, syn_rcvd_sd, NULL, 0); - ck_assert_int_gt(acc_sd, 0); - accepted = &s.tcpsockets[SOCKET_UNMARK(acc_sd)]; + ck_assert_int_eq(wolfIP_sock_accept(&s, syn_rcvd_sd, NULL, 0), -WOLFIP_EAGAIN); + accepted = parked_child(&s, syn_rcvd_sd); + ck_assert_ptr_nonnull(accepted); ck_assert_int_eq(accepted->sock.tcp.state, TCP_SYN_RCVD); /* Poll before the accept-armed control RTO (last_tick 1000 + 1000ms * default) expires, so this flushes accept's own SYN-ACK. */ @@ -3047,7 +3046,6 @@ START_TEST(test_accept_clears_listener_tx_fifo) struct tsocket *listener; struct tsocket *accepted; struct wolfIP_sockaddr_in sin; - int acc_sd; wolfIP_init(&s); mock_link_init(&s); @@ -3072,9 +3070,9 @@ START_TEST(test_accept_clears_listener_tx_fifo) /* accept() hands the handshake to the clone; the listener must revert * to LISTEN with an empty TX FIFO. */ - acc_sd = wolfIP_sock_accept(&s, listen_sd, NULL, 0); - ck_assert_int_gt(acc_sd, 0); - accepted = &s.tcpsockets[SOCKET_UNMARK(acc_sd)]; + ck_assert_int_eq(wolfIP_sock_accept(&s, listen_sd, NULL, 0), -WOLFIP_EAGAIN); + accepted = parked_child(&s, listen_sd); + ck_assert_ptr_nonnull(accepted); ck_assert_int_eq(accepted->sock.tcp.state, TCP_SYN_RCVD); /* The clone carries the connection's SYN-ACK. */ ck_assert_int_eq(fifo_is_empty(&accepted->sock.tcp.txbuf), 0); @@ -3128,7 +3126,6 @@ START_TEST(test_no_stale_synack_after_accept_new_conn) int listen_sd; struct tsocket *listener; struct wolfIP_sockaddr_in sin; - int acc_sd; uint32_t count_before; wolfIP_init(&s); @@ -3149,8 +3146,8 @@ START_TEST(test_no_stale_synack_after_accept_new_conn) inject_tcp_syn(&s, TEST_PRIMARY_IF, 0x0A000001U, 1234); wolfIP_poll(&s, 1000); ck_assert_int_eq(listener->sock.tcp.state, TCP_SYN_RCVD); - acc_sd = wolfIP_sock_accept(&s, listen_sd, NULL, 0); - ck_assert_int_gt(acc_sd, 0); + ck_assert_int_eq(wolfIP_sock_accept(&s, listen_sd, NULL, 0), -WOLFIP_EAGAIN); + ck_assert_ptr_nonnull(parked_child(&s, listen_sd)); /* Connection B: a second client port from the same peer while A's * SYN-ACK is still parked. */ diff --git a/src/wolfip.c b/src/wolfip.c index 12b47281..aa8e4e81 100644 --- a/src/wolfip.c +++ b/src/wolfip.c @@ -171,6 +171,10 @@ struct wolfIP_icmp_packet; * counter; a peer that keeps answering with a zero window still consumes * the budget - a deliberately finite patience, not infinite. */ #define TCP_PERSIST_MAXRTX 8U +/* SYN-ACK retries for a connection accept() holds back until it completes. */ +#ifndef TCP_SYNACK_MAXRTX +#define TCP_SYNACK_MAXRTX 3U +#endif #define TCP_RTO_MAX_BACKOFF 15U /* Max retries before closing; also clamps shift */ #ifdef IP_MULTICAST @@ -1266,6 +1270,10 @@ struct tcpsocket { uint8_t preaccept_timeout_active; uint8_t is_listener; uint8_t ack_retry_pending; + /* Cloned by accept() but not handed out until its handshake completes. */ + uint8_t parked; + uint8_t parent; + uint16_t parent_gen; ip4 local_ip, remote_ip; uint32_t peer_rwnd; uint16_t peer_mss; @@ -1417,6 +1425,7 @@ static int tcp_preaccept_timeout_start(struct tsocket *t, uint64_t now); static void tcp_preaccept_timeout_stop(struct tsocket *t); static void tcp_listener_revert_to_listen(struct tsocket *t); static void tcp_reclaim_if_full(struct wolfIP *s); +static void tcp_abort(struct tsocket *ts); static int tcp_ctrl_state_needs_rto(const struct tsocket *t); static int tcp_has_pending_unsent_payload(struct tsocket *t); static uint32_t tcp_snd_nxt(struct tsocket *t); @@ -2961,6 +2970,35 @@ static void sock_fd_retire(struct wolfIP *s, int fd) sock_gen_next(g); } +static struct tsocket *tcp_parked_parent(struct tsocket *t) +{ + struct wolfIP *s = t->S; + struct tsocket *lsn; + + if (!t->sock.tcp.parked || t->sock.tcp.parent >= MAX_TCPSOCKETS) + return NULL; + lsn = &s->tcpsockets[t->sock.tcp.parent]; + if ((s->tcp_gen[t->sock.tcp.parent].gen != t->sock.tcp.parent_gen) || + (lsn->proto != WI_IPPROTO_TCP) || !lsn->sock.tcp.is_listener) + return NULL; + return lsn; +} + +static struct tsocket *tcp_parked_ready(struct wolfIP *s, struct tsocket *lsn) +{ + int i; + + for (i = 0; i < MAX_TCPSOCKETS; i++) { + struct tsocket *t = &s->tcpsockets[i]; + + if ((t->proto == WI_IPPROTO_TCP) && (tcp_parked_parent(t) == lsn) && + (t->sock.tcp.state == TCP_ESTABLISHED || + t->sock.tcp.state == TCP_CLOSE_WAIT)) + return t; + } + return NULL; +} + /* User Callbacks */ void wolfIP_register_callback(struct wolfIP *s, int sock_fd, tsocket_cb cb, void *arg) @@ -6757,6 +6795,16 @@ static void tcp_input(struct wolfIP *S, unsigned int if_idx, } t->sock.tcp.state = TCP_ESTABLISHED; tcp_ctrl_rto_stop(t); + if (t->sock.tcp.parked) { + struct tsocket *lsn = tcp_parked_parent(t); + + if (tcp_preaccept_timeout_start(t, t->S->last_tick) < 0) { + tcp_abort(t); + continue; + } + if (lsn != NULL) + lsn->events |= CB_EVENT_READABLE; + } if (t->sock.tcp.is_listener && tcp_preaccept_timeout_start(t, t->S->last_tick) < 0) { /* Take the timeout's exit now rather than pin the port. */ @@ -6977,12 +7025,16 @@ static void tcp_rto_cb(void *arg) if (ts->sock.tcp.preaccept_timeout_active) { if ((ts->sock.tcp.state != TCP_ESTABLISHED && ts->sock.tcp.state != TCP_CLOSE_WAIT) || - !ts->sock.tcp.is_listener) { + (!ts->sock.tcp.is_listener && !ts->sock.tcp.parked)) { /* The socket left the pinned condition (accepted away, reset, * or closed): disarm quietly. */ tcp_preaccept_timeout_stop(ts); return; } + if (ts->sock.tcp.parked) { + tcp_abort(ts); + return; + } /* Never accepted: reclaim the port. The peer may have nothing left to * send, so it would never draw the LISTEN RST; tell it now. */ (void)tcp_send_reset_now(ts); @@ -6999,7 +7051,9 @@ static void tcp_rto_cb(void *arg) * delay the retransmit by a second RTO interval. */ tcp_ctrl_rto_stop(ts); } else { - if (ts->sock.tcp.ctrl_rto_retries >= TCP_CTRL_RTO_MAXRTX) { + if ((ts->sock.tcp.ctrl_rto_retries >= TCP_CTRL_RTO_MAXRTX) || + (ts->sock.tcp.parked && + ts->sock.tcp.ctrl_rto_retries >= TCP_SYNACK_MAXRTX)) { tcp_ctrl_rto_stop(ts); if (ts->sock.tcp.is_listener && ts->sock.tcp.state == TCP_SYN_RCVD) { @@ -7747,6 +7801,35 @@ int wolfIP_sock_accept(struct wolfIP *s, int sockfd, struct wolfIP_sockaddr *add if (SOCKET_UNMARK(sockfd) >= MAX_TCPSOCKETS) return -WOLFIP_EINVAL; ts = &s->tcpsockets[SOCKET_UNMARK(sockfd)]; + newts = ts->sock.tcp.is_listener ? tcp_parked_ready(s, ts) : NULL; + if (newts) { + newts->sock.tcp.parked = 0; + if (newts->sock.tcp.preaccept_timeout_active) + tcp_preaccept_timeout_stop(newts); + newts->callback = ts->callback; + newts->callback_arg = ts->callback_arg; + newts->events = 0; + if ((queue_len(&newts->sock.tcp.rxbuf) > 0) || + (newts->sock.tcp.state == TCP_CLOSE_WAIT)) + newts->events |= CB_EVENT_READABLE; + if (tx_has_writable_space(newts)) + newts->events |= CB_EVENT_WRITABLE; + /* The listener's READABLE is an edge: raise it again for what still waits. */ + if ((tcp_parked_ready(s, ts) != NULL) || + (ts->sock.tcp.state == TCP_SYN_RCVD) || + (ts->sock.tcp.state == TCP_ESTABLISHED) || + (ts->sock.tcp.state == TCP_CLOSE_WAIT)) { + ts->events |= CB_EVENT_READABLE; + if (ts->callback) + wolfIP_wake(s); + } + if (sin) { + sin->sin_family = AF_INET; + sin->sin_port = ee16(newts->dst_port); + sin->sin_addr.s_addr = ee32(newts->remote_ip); + } + return sock_fd_open(s, MARK_TCP_SOCKET, (int)(newts - s->tcpsockets)); + } if (ts->sock.tcp.is_listener && (ts->sock.tcp.state == TCP_ESTABLISHED || ts->sock.tcp.state == TCP_CLOSE_WAIT)) { @@ -7761,11 +7844,9 @@ int wolfIP_sock_accept(struct wolfIP *s, int sockfd, struct wolfIP_sockaddr *add * then EOF, instead of accept() failing on a readable listener * and spinning the poll loop until the pre-accept timeout. */ newts = tcp_new_socket(s); - if (!newts) { - (void)tcp_send_reset_now(ts); - tcp_listener_revert_to_listen(ts); - return -1; - } + /* No slot yet: it waits on the listener, bounded by the pre-accept timeout. */ + if (!newts) + return -WOLFIP_EAGAIN; tcp_preaccept_timeout_stop(ts); *newts = *ts; newts->sock.tcp.is_listener = 0; @@ -7822,13 +7903,13 @@ int wolfIP_sock_accept(struct wolfIP *s, int sockfd, struct wolfIP_sockaddr *add return -1; if (ts->sock.tcp.state == TCP_SYN_RCVD) { + /* With no free slot the listener completes the handshake itself. */ newts = tcp_new_socket(s); if (!newts) - return -1; - /* Don't signal writable until connection fully established */ - newts->events &= ~CB_EVENT_WRITABLE; - newts->callback = ts->callback; - newts->callback_arg = ts->callback_arg; + return -WOLFIP_EAGAIN; + newts->sock.tcp.parked = 1; + newts->sock.tcp.parent = (uint8_t)SOCKET_UNMARK(sockfd); + newts->sock.tcp.parent_gen = SOCKET_GEN(sockfd); newts->local_ip = ts->local_ip; newts->bound_local_ip = (ts->bound_local_ip != IPADDR_ANY) ? ts->bound_local_ip : ts->local_ip; newts->if_idx = ts->if_idx; @@ -7870,16 +7951,9 @@ int wolfIP_sock_accept(struct wolfIP *s, int sockfd, struct wolfIP_sockaddr *add * advances seq to ISN+1 when the connection is established. */ newts->sock.tcp.ctrl_rto_retries = 0; if (tcp_ctrl_rto_start(newts, s->last_tick) < 0) { - newts->callback = NULL; - newts->callback_arg = NULL; close_socket(newts); return -WOLFIP_EAGAIN; } - if (sin) { - sin->sin_family = AF_INET; - sin->sin_port = ee16(ts->dst_port); - sin->sin_addr.s_addr = ee32(ts->remote_ip); - } /* The accepted connection owns the handshake now (its SYN-ACK * lives in the clone's TX FIFO). Revert the listener to the * fresh-socket baseline: the old partial reset (state, RTO, @@ -7896,7 +7970,8 @@ int wolfIP_sock_accept(struct wolfIP *s, int sockfd, struct wolfIP_sockaddr *add abort_accept_clone(newts); return -1; } - return sock_fd_open(s, MARK_TCP_SOCKET, (int)(newts - s->tcpsockets)); + /* Handed out by a later accept() once the handshake completes. */ + return -WOLFIP_EAGAIN; } else if (ts->sock.tcp.state == TCP_LISTEN) { return -WOLFIP_EAGAIN; } @@ -9045,6 +9120,26 @@ int wolfIP_sock_getsockopt(struct wolfIP *s, int sockfd, int level, int optname, #endif return 0; } +/* Connections accept() never handed out die with their listener. */ +static void tcp_drop_parked(struct wolfIP *s, int sockfd) +{ + struct tsocket *lsn; + int i; + + if (!s || sockfd < 0 || !IS_SOCKET_TCP(sockfd) || + SOCKET_UNMARK(sockfd) >= MAX_TCPSOCKETS) + return; + lsn = &s->tcpsockets[SOCKET_UNMARK(sockfd)]; + if (!lsn->sock.tcp.is_listener) + return; + for (i = 0; i < MAX_TCPSOCKETS; i++) { + struct tsocket *t = &s->tcpsockets[i]; + + if ((t->proto == WI_IPPROTO_TCP) && (tcp_parked_parent(t) == lsn)) + tcp_abort(t); + } +} + static int sock_close(struct wolfIP *s, int sockfd) { if (sockfd < 0) @@ -9179,6 +9274,7 @@ int wolfIP_sock_close(struct wolfIP *s, int sockfd) if (sock_fd_stale(s, sockfd)) return -WOLFIP_EBADF; + tcp_drop_parked(s, sockfd); ret = sock_close(s, sockfd); /* A socket still finishing its FIN exchange keeps its descriptor for * wolfIP_sock_abort(); the stack retires it when it frees the slot. */ @@ -9228,20 +9324,24 @@ int wolfIP_sock_abort(struct wolfIP *s, int sockfd) ts = &s->tcpsockets[SOCKET_UNMARK(sockfd)]; if (ts->sock.tcp.state == TCP_LISTEN || ts->sock.tcp.state == TCP_CLOSED) return wolfIP_sock_close(s, sockfd); + tcp_drop_parked(s, sockfd); tcp_abort(ts); sock_fd_retire(s, sockfd); return 0; } -/* Order in which sockets the application has already closed give up their - * slot when the table is full; 0 never does. */ -static int tcp_reclaim_rank(const struct tsocket *t) +/* Order in which sockets the application has already closed, or never got, + * give up their slot when the table is full; 0 never does. */ +static int tcp_reclaim_rank(struct tsocket *t) { + if (t->sock.tcp.parked) + return ((t->sock.tcp.state == TCP_SYN_RCVD) || + (tcp_parked_parent(t) == NULL)) ? 2 : 0; switch (t->sock.tcp.state) { case TCP_TIME_WAIT: - return 3; + return 4; case TCP_FIN_WAIT_2: - return 2; + return 3; case TCP_FIN_WAIT_1: case TCP_CLOSING: case TCP_LAST_ACK: @@ -9359,7 +9459,8 @@ int wolfIP_sock_can_read(struct wolfIP *s, int sockfd) if (ts->sock.tcp.is_listener && (ts->sock.tcp.state == TCP_SYN_RCVD || ts->sock.tcp.state == TCP_ESTABLISHED || - ts->sock.tcp.state == TCP_CLOSE_WAIT)) + ts->sock.tcp.state == TCP_CLOSE_WAIT || + tcp_parked_ready(s, ts))) return 1; if (queue_len(&ts->sock.tcp.rxbuf) > 0) return 1; From 836debb8452207d12532a3258585b3b73b122f59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Thu, 1 Oct 2026 14:46:42 +0200 Subject: [PATCH 07/10] freertos: honour SO_RCVTIMEO and SO_SNDTIMEO The BSD wrapper waited portMAX_DELAY on every blocking operation, so a peer that connected and then said nothing blocked a single-threaded server for good. There was no way to bound it: neither the stack nor the wrapper had any notion of a timeout. Each descriptor now carries a receive and a send timeout, both portMAX_DELAY by default so existing callers are unaffected. setsockopt() handles the two options in the wrapper rather than forwarding them to wolfIP, because blocking belongs to the wrapper; the stack never blocks. An expired wait already reported -1 with EAGAIN, which is the POSIX behaviour for these options, so the call sites pass on what is left of the timeout, counted from the start of the call: a wake that does not let the call complete must not restart it. struct wolfIP_timeval carries the argument, since wolfIP must not depend on sys/time.h. The values behave as on Linux and in the POSIX layer: all-zero, or a timeout too long for TickType_t, waits without bound, a negative tv_sec does not wait, a tv_usec outside [0, 999999] fails with WOLFIP_EDOM, and a non-zero value shorter than a tick rounds up to one tick rather than becoming "no timeout". A connect() that runs out of time fails with WOLFIP_EINPROGRESS, new in wolfip.h, while the handshake goes on. The conversion to ticks goes through configTICK_RATE_HZ, as pdMS_TO_TICKS() does, not portTICK_PERIOD_MS, which is 0 above 1000 Hz. optlen must be exactly sizeof(struct wolfIP_timeval): a platform struct timeval can be larger (64-bit time_t on a 32-bit target) and would be misread, not rejected. getsockopt() reads both options back. Both option calls recheck the descriptor under the lock, and the blocking calls read the timeout while they still hold it. Adds test-freertos-bsd-semantics, a scripted-core harness for the wrapper's blocking behaviour, starting with the timeouts, and test-freertos-bsd-semantics-2khz, the same harness at 2000 Hz. The FreeRTOS mocks gain the option names and struct it needs, and derive their tick macros from configTICK_RATE_HZ. --- Makefile | 13 + src/port/freeRTOS/README.md | 1 + src/port/freeRTOS/bsd_socket.c | 158 +++++++++- src/test/freertos_mocks/FreeRTOS.h | 5 +- src/test/freertos_mocks/wolfip.h | 11 + src/test/test_freertos_bsd_semantics.c | 414 +++++++++++++++++++++++++ wolfip.h | 38 +++ 7 files changed, 623 insertions(+), 17 deletions(-) create mode 100644 src/test/test_freertos_bsd_semantics.c diff --git a/Makefile b/Makefile index 6a42f664..5803bc9b 100644 --- a/Makefile +++ b/Makefile @@ -207,6 +207,8 @@ EXE=build/tcpecho build/tcp_netcat_poll build/tcp_netcat_select \ build/test-http-headers \ build/test-http-close-notify \ build/test-freertos-close-last-ack \ + build/test-freertos-bsd-semantics \ + build/test-freertos-bsd-semantics-2khz \ build/test-posix-errno \ build/ipfilter-logger \ build/test-esp build/esp-server @@ -922,6 +924,17 @@ build/test-freertos-close-last-ack: src/test/test_freertos_close_last_ack.c src/ @echo "[LD] $@" @$(CC) -Isrc/test/freertos_mocks $(CFLAGS) -o $@ src/test/test_freertos_close_last_ack.c $(LDFLAGS) +build/test-freertos-bsd-semantics: src/test/test_freertos_bsd_semantics.c src/port/freeRTOS/bsd_socket.c + @mkdir -p build || true + @echo "[LD] $@" + @$(CC) -Isrc/test/freertos_mocks $(CFLAGS) -o $@ src/test/test_freertos_bsd_semantics.c $(LDFLAGS) + +# Same test above 1000 Hz, where portTICK_PERIOD_MS is 0. +build/test-freertos-bsd-semantics-2khz: src/test/test_freertos_bsd_semantics.c src/port/freeRTOS/bsd_socket.c + @mkdir -p build || true + @echo "[LD] $@" + @$(CC) -Isrc/test/freertos_mocks -DconfigTICK_RATE_HZ=2000u $(CFLAGS) -o $@ src/test/test_freertos_bsd_semantics.c $(LDFLAGS) + build/%.o: src/%.c @mkdir -p `dirname $@` || true @echo "[CC] $<" diff --git a/src/port/freeRTOS/README.md b/src/port/freeRTOS/README.md index e751d1c3..e5e708ff 100644 --- a/src/port/freeRTOS/README.md +++ b/src/port/freeRTOS/README.md @@ -119,3 +119,4 @@ CFLAGS += -DWOLFIP_FREERTOS_BSD_MAX_FDS=32 - `wolfip_freertos_socket_init()` should be called once after wolfIP/device init and before socket usage. - File descriptors returned by this layer are wrapper FDs, not raw wolfIP internal FDs. - The wrapper is intended for task context (not ISR context). +- Blocking calls wait indefinitely by default. `setsockopt(fd, WOLFIP_SOL_SOCKET, WOLFIP_SO_RCVTIMEO, &tv, sizeof(tv))` bounds `accept`, `recv` and `recvfrom`, and `WOLFIP_SO_SNDTIMEO` bounds `connect`, `send` and `sendto`, with `tv` a `struct wolfIP_timeval` and `optlen` exactly its size; each bounds the whole call, not each wait inside it. An expired wait returns -1 with `socket_last_error()` set to `WOLFIP_EAGAIN`; as on Linux, an all-zero `tv` or one too long for `TickType_t` waits without bound, a negative `tv_sec` does not wait, and a `tv_usec` outside [0, 999999] fails with `WOLFIP_EDOM`. A `connect()` that runs out of time fails with `WOLFIP_EINPROGRESS` instead, while the handshake goes on. `getsockopt()` reads the current values back. diff --git a/src/port/freeRTOS/bsd_socket.c b/src/port/freeRTOS/bsd_socket.c index f14b76ca..ab63b93e 100644 --- a/src/port/freeRTOS/bsd_socket.c +++ b/src/port/freeRTOS/bsd_socket.c @@ -24,6 +24,7 @@ #include #include #include +#include #include "FreeRTOS.h" #include "task.h" @@ -51,6 +52,9 @@ typedef struct { SemaphoreHandle_t ready_sem; volatile uint16_t wait_events; volatile uint16_t seen_events; + /* SO_RCVTIMEO / SO_SNDTIMEO in ticks; portMAX_DELAY means none. */ + TickType_t rx_timeout; + TickType_t tx_timeout; } wolfip_bsd_fd_entry; static struct wolfIP *g_ipstack; @@ -160,6 +164,8 @@ static int wolfip_bsd_fd_alloc(int internal_fd) g_fds[i].ready_sem = sem; g_fds[i].wait_events = 0; g_fds[i].seen_events = 0; + g_fds[i].rx_timeout = portMAX_DELAY; + g_fds[i].tx_timeout = portMAX_DELAY; return i; } } @@ -213,14 +219,26 @@ static void wolfip_bsd_prepare_wait_locked(wolfip_bsd_fd_entry *entry, uint16_t wolfIP_register_callback(g_ipstack, entry->internal_fd, wolfip_bsd_socket_cb, entry); } -static int wolfip_bsd_wait_unlocked(wolfip_bsd_fd_entry *entry) +static int wolfip_bsd_wait_unlocked(wolfip_bsd_fd_entry *entry, + TickType_t timeout) { - if (xSemaphoreTake(entry->ready_sem, portMAX_DELAY) != pdTRUE) { + if (xSemaphoreTake(entry->ready_sem, timeout) != pdTRUE) { return -1; } return 0; } +static TickType_t wolfip_bsd_remaining(TickType_t timeout, TickType_t start) +{ + TickType_t elapsed; + + if (timeout == portMAX_DELAY) { + return portMAX_DELAY; + } + elapsed = (TickType_t)(xTaskGetTickCount() - start); + return (elapsed < timeout) ? (TickType_t)(timeout - elapsed) : 0; +} + /* Some TCP core calls surface a temporary "not established yet" as -1 on a * freshly accepted stream socket before the final ACK promotes it to * ESTABLISHED. Allow a single wait/retry for that case without turning all @@ -362,14 +380,18 @@ int accept(int sockfd, struct wolfIP_sockaddr *addr, socklen_t *addrlen) int public_fd; int retried_minus_one = 0; wolfip_bsd_fd_entry *entry; + TickType_t start; + TickType_t left; if (!wolfip_bsd_fd_valid(sockfd)) { return -1; } entry = &g_fds[sockfd]; + start = xTaskGetTickCount(); for (;;) { xSemaphoreTake(g_lock, portMAX_DELAY); + left = wolfip_bsd_remaining(entry->rx_timeout, start); ret = wolfIP_sock_accept(g_ipstack, entry->internal_fd, addr, addrlen); if (ret >= 0) { public_fd = wolfip_bsd_fd_alloc(ret); @@ -388,7 +410,7 @@ int accept(int sockfd, struct wolfIP_sockaddr *addr, socklen_t *addrlen) wolfip_bsd_prepare_wait_locked(entry, (uint16_t)(CB_EVENT_READABLE | CB_EVENT_CLOSED)); xSemaphoreGive(g_lock); - if (wolfip_bsd_wait_unlocked(entry) < 0) { + if (wolfip_bsd_wait_unlocked(entry, left) < 0) { wolfip_bsd_set_error(WOLFIP_EAGAIN); return -1; } @@ -402,7 +424,7 @@ int accept(int sockfd, struct wolfIP_sockaddr *addr, socklen_t *addrlen) wolfip_bsd_prepare_wait_locked(entry, (uint16_t)(CB_EVENT_READABLE | CB_EVENT_CLOSED)); xSemaphoreGive(g_lock); - if (wolfip_bsd_wait_unlocked(entry) < 0) { + if (wolfip_bsd_wait_unlocked(entry, left) < 0) { wolfip_bsd_set_error(WOLFIP_EAGAIN); return -1; } @@ -413,14 +435,18 @@ int connect(int sockfd, const struct wolfIP_sockaddr *addr, socklen_t addrlen) { int ret; wolfip_bsd_fd_entry *entry; + TickType_t start; + TickType_t left; if (!wolfip_bsd_fd_valid(sockfd)) { return -1; } entry = &g_fds[sockfd]; + start = xTaskGetTickCount(); for (;;) { xSemaphoreTake(g_lock, portMAX_DELAY); + left = wolfip_bsd_remaining(entry->tx_timeout, start); ret = wolfIP_sock_connect(g_ipstack, entry->internal_fd, addr, addrlen); if (ret == 0) { xSemaphoreGive(g_lock); @@ -434,8 +460,8 @@ int connect(int sockfd, const struct wolfIP_sockaddr *addr, socklen_t addrlen) wolfip_bsd_prepare_wait_locked(entry, (uint16_t)(CB_EVENT_WRITABLE | CB_EVENT_CLOSED)); xSemaphoreGive(g_lock); - if (wolfip_bsd_wait_unlocked(entry) < 0) { - wolfip_bsd_set_error(WOLFIP_EAGAIN); + if (wolfip_bsd_wait_unlocked(entry, left) < 0) { + wolfip_bsd_set_error(WOLFIP_EINPROGRESS); return -1; } } @@ -446,14 +472,18 @@ int send(int sockfd, const void *buf, size_t len, int flags) int ret; int retried_minus_one = 0; wolfip_bsd_fd_entry *entry; + TickType_t start; + TickType_t left; if (!wolfip_bsd_fd_valid(sockfd)) { return -1; } entry = &g_fds[sockfd]; + start = xTaskGetTickCount(); for (;;) { xSemaphoreTake(g_lock, portMAX_DELAY); + left = wolfip_bsd_remaining(entry->tx_timeout, start); ret = wolfIP_sock_send(g_ipstack, entry->internal_fd, buf, len, flags); if (ret >= 0) { xSemaphoreGive(g_lock); @@ -464,7 +494,7 @@ int send(int sockfd, const void *buf, size_t len, int flags) wolfip_bsd_prepare_wait_locked(entry, (uint16_t)(CB_EVENT_WRITABLE | CB_EVENT_READABLE | CB_EVENT_CLOSED)); xSemaphoreGive(g_lock); - if (wolfip_bsd_wait_unlocked(entry) < 0) { + if (wolfip_bsd_wait_unlocked(entry, left) < 0) { wolfip_bsd_set_error(WOLFIP_EAGAIN); return -1; } @@ -478,7 +508,7 @@ int send(int sockfd, const void *buf, size_t len, int flags) wolfip_bsd_prepare_wait_locked(entry, (uint16_t)(CB_EVENT_WRITABLE | CB_EVENT_CLOSED)); xSemaphoreGive(g_lock); - if (wolfip_bsd_wait_unlocked(entry) < 0) { + if (wolfip_bsd_wait_unlocked(entry, left) < 0) { wolfip_bsd_set_error(WOLFIP_EAGAIN); return -1; } @@ -491,14 +521,18 @@ int sendto(int sockfd, const void *buf, size_t len, int flags, int ret; int retried_minus_one = 0; wolfip_bsd_fd_entry *entry; + TickType_t start; + TickType_t left; if (!wolfip_bsd_fd_valid(sockfd)) { return -1; } entry = &g_fds[sockfd]; + start = xTaskGetTickCount(); for (;;) { xSemaphoreTake(g_lock, portMAX_DELAY); + left = wolfip_bsd_remaining(entry->tx_timeout, start); ret = wolfIP_sock_sendto(g_ipstack, entry->internal_fd, buf, len, flags, dest_addr, addrlen); if (ret >= 0) { xSemaphoreGive(g_lock); @@ -509,7 +543,7 @@ int sendto(int sockfd, const void *buf, size_t len, int flags, wolfip_bsd_prepare_wait_locked(entry, (uint16_t)(CB_EVENT_WRITABLE | CB_EVENT_READABLE | CB_EVENT_CLOSED)); xSemaphoreGive(g_lock); - if (wolfip_bsd_wait_unlocked(entry) < 0) { + if (wolfip_bsd_wait_unlocked(entry, left) < 0) { wolfip_bsd_set_error(WOLFIP_EAGAIN); return -1; } @@ -523,7 +557,7 @@ int sendto(int sockfd, const void *buf, size_t len, int flags, wolfip_bsd_prepare_wait_locked(entry, (uint16_t)(CB_EVENT_WRITABLE | CB_EVENT_CLOSED)); xSemaphoreGive(g_lock); - if (wolfip_bsd_wait_unlocked(entry) < 0) { + if (wolfip_bsd_wait_unlocked(entry, left) < 0) { wolfip_bsd_set_error(WOLFIP_EAGAIN); return -1; } @@ -534,14 +568,18 @@ int recv(int sockfd, void *buf, size_t len, int flags) { int ret; wolfip_bsd_fd_entry *entry; + TickType_t start; + TickType_t left; if (!wolfip_bsd_fd_valid(sockfd)) { return -1; } entry = &g_fds[sockfd]; + start = xTaskGetTickCount(); for (;;) { xSemaphoreTake(g_lock, portMAX_DELAY); + left = wolfip_bsd_remaining(entry->rx_timeout, start); ret = wolfIP_sock_recv(g_ipstack, entry->internal_fd, buf, len, flags); if (ret >= 0) { xSemaphoreGive(g_lock); @@ -551,7 +589,7 @@ int recv(int sockfd, void *buf, size_t len, int flags) wolfip_bsd_prepare_wait_locked(entry, (uint16_t)(CB_EVENT_READABLE | CB_EVENT_WRITABLE | CB_EVENT_CLOSED)); xSemaphoreGive(g_lock); - if (wolfip_bsd_wait_unlocked(entry) < 0) { + if (wolfip_bsd_wait_unlocked(entry, left) < 0) { wolfip_bsd_set_error(WOLFIP_EAGAIN); return -1; } @@ -565,7 +603,7 @@ int recv(int sockfd, void *buf, size_t len, int flags) wolfip_bsd_prepare_wait_locked(entry, (uint16_t)(CB_EVENT_READABLE | CB_EVENT_CLOSED)); xSemaphoreGive(g_lock); - if (wolfip_bsd_wait_unlocked(entry) < 0) { + if (wolfip_bsd_wait_unlocked(entry, left) < 0) { wolfip_bsd_set_error(WOLFIP_EAGAIN); return -1; } @@ -577,14 +615,18 @@ int recvfrom(int sockfd, void *buf, size_t len, int flags, { int ret; wolfip_bsd_fd_entry *entry; + TickType_t start; + TickType_t left; if (!wolfip_bsd_fd_valid(sockfd)) { return -1; } entry = &g_fds[sockfd]; + start = xTaskGetTickCount(); for (;;) { xSemaphoreTake(g_lock, portMAX_DELAY); + left = wolfip_bsd_remaining(entry->rx_timeout, start); ret = wolfIP_sock_recvfrom(g_ipstack, entry->internal_fd, buf, len, flags, src_addr, addrlen); if (ret >= 0) { xSemaphoreGive(g_lock); @@ -594,7 +636,7 @@ int recvfrom(int sockfd, void *buf, size_t len, int flags, wolfip_bsd_prepare_wait_locked(entry, (uint16_t)(CB_EVENT_READABLE | CB_EVENT_WRITABLE | CB_EVENT_CLOSED)); xSemaphoreGive(g_lock); - if (wolfip_bsd_wait_unlocked(entry) < 0) { + if (wolfip_bsd_wait_unlocked(entry, left) < 0) { wolfip_bsd_set_error(WOLFIP_EAGAIN); return -1; } @@ -608,13 +650,47 @@ int recvfrom(int sockfd, void *buf, size_t len, int flags, wolfip_bsd_prepare_wait_locked(entry, (uint16_t)(CB_EVENT_READABLE | CB_EVENT_CLOSED)); xSemaphoreGive(g_lock); - if (wolfip_bsd_wait_unlocked(entry) < 0) { + if (wolfip_bsd_wait_unlocked(entry, left) < 0) { wolfip_bsd_set_error(WOLFIP_EAGAIN); return -1; } } } +/* As on Linux: {0, 0} or a huge value waits forever, a negative one does not + * wait; anything else rounds up to whole ticks. */ +static TickType_t wolfip_bsd_timeout_ticks(const struct wolfIP_timeval *tv) +{ + uint64_t ticks; + + if (tv->tv_sec < 0) { + return 0; + } + if (((tv->tv_sec == 0) && (tv->tv_usec == 0)) || + ((uint64_t)tv->tv_sec >= (uint64_t)portMAX_DELAY / configTICK_RATE_HZ)) { + return portMAX_DELAY; + } + ticks = ((uint64_t)tv->tv_sec * configTICK_RATE_HZ) + + (((uint64_t)tv->tv_usec * configTICK_RATE_HZ + 999999u) / 1000000u); + if (ticks >= (uint64_t)portMAX_DELAY) { + return portMAX_DELAY; + } + return (TickType_t)ticks; +} + +static void wolfip_bsd_timeout_timeval(TickType_t ticks, + struct wolfIP_timeval *tv) +{ + if (ticks == portMAX_DELAY) { + tv->tv_sec = 0; + tv->tv_usec = 0; + return; + } + tv->tv_sec = (long)(ticks / configTICK_RATE_HZ); + tv->tv_usec = (long)(((uint64_t)(ticks % configTICK_RATE_HZ) * 1000000u) / + configTICK_RATE_HZ); +} + int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen) { @@ -622,6 +698,34 @@ int setsockopt(int sockfd, int level, int optname, if (!wolfip_bsd_fd_valid(sockfd)) { return -1; } + if ((level == WOLFIP_SOL_SOCKET) && + ((optname == WOLFIP_SO_RCVTIMEO) || (optname == WOLFIP_SO_SNDTIMEO))) { + struct wolfIP_timeval tv; + + if ((optval == NULL) || (optlen != (socklen_t)sizeof(tv))) { + wolfip_bsd_set_error(WOLFIP_EINVAL); + return -1; + } + memcpy(&tv, optval, sizeof(tv)); + if ((tv.tv_usec < 0) || (tv.tv_usec >= 1000000L)) { + wolfip_bsd_set_error(WOLFIP_EDOM); + return -1; + } + xSemaphoreTake(g_lock, portMAX_DELAY); + if (!g_fds[sockfd].in_use) { + xSemaphoreGive(g_lock); + wolfip_bsd_set_error(WOLFIP_EINVAL); + return -1; + } + if (optname == WOLFIP_SO_RCVTIMEO) { + g_fds[sockfd].rx_timeout = wolfip_bsd_timeout_ticks(&tv); + } + else { + g_fds[sockfd].tx_timeout = wolfip_bsd_timeout_ticks(&tv); + } + xSemaphoreGive(g_lock); + return 0; + } xSemaphoreTake(g_lock, portMAX_DELAY); ret = wolfIP_sock_setsockopt(g_ipstack, g_fds[sockfd].internal_fd, level, optname, optval, optlen); @@ -640,6 +744,30 @@ int getsockopt(int sockfd, int level, int optname, if (!wolfip_bsd_fd_valid(sockfd)) { return -1; } + if ((level == WOLFIP_SOL_SOCKET) && + ((optname == WOLFIP_SO_RCVTIMEO) || (optname == WOLFIP_SO_SNDTIMEO))) { + struct wolfIP_timeval tv; + TickType_t ticks; + + if ((optval == NULL) || (optlen == NULL) || + (*optlen < (socklen_t)sizeof(tv))) { + wolfip_bsd_set_error(WOLFIP_EINVAL); + return -1; + } + xSemaphoreTake(g_lock, portMAX_DELAY); + if (!g_fds[sockfd].in_use) { + xSemaphoreGive(g_lock); + wolfip_bsd_set_error(WOLFIP_EINVAL); + return -1; + } + ticks = (optname == WOLFIP_SO_RCVTIMEO) ? g_fds[sockfd].rx_timeout : + g_fds[sockfd].tx_timeout; + xSemaphoreGive(g_lock); + wolfip_bsd_timeout_timeval(ticks, &tv); + memcpy(optval, &tv, sizeof(tv)); + *optlen = (socklen_t)sizeof(tv); + return 0; + } xSemaphoreTake(g_lock, portMAX_DELAY); ret = wolfIP_sock_getsockopt(g_ipstack, g_fds[sockfd].internal_fd, level, optname, optval, optlen); @@ -721,7 +849,7 @@ int close(int sockfd) wolfip_bsd_prepare_wait_locked(entry, CB_EVENT_CLOSED); xSemaphoreGive(g_lock); - if (wolfip_bsd_wait_unlocked(entry) < 0) { + if (wolfip_bsd_wait_unlocked(entry, portMAX_DELAY) < 0) { wolfip_bsd_set_error(WOLFIP_EAGAIN); return -1; } diff --git a/src/test/freertos_mocks/FreeRTOS.h b/src/test/freertos_mocks/FreeRTOS.h index 48b20d86..c10af40a 100644 --- a/src/test/freertos_mocks/FreeRTOS.h +++ b/src/test/freertos_mocks/FreeRTOS.h @@ -14,8 +14,9 @@ typedef uint32_t TickType_t; #ifndef configTICK_RATE_HZ #define configTICK_RATE_HZ 1000u #endif -#define portTICK_PERIOD_MS 1u -#define pdMS_TO_TICKS(ms) ((TickType_t)(ms)) +#define portTICK_PERIOD_MS ((TickType_t)1000u / configTICK_RATE_HZ) +#define pdMS_TO_TICKS(ms) \ + ((TickType_t)(((uint64_t)(ms) * configTICK_RATE_HZ) / 1000u)) #define portYIELD_FROM_ISR(woken) ((void)(woken)) #endif diff --git a/src/test/freertos_mocks/wolfip.h b/src/test/freertos_mocks/wolfip.h index a80a417a..a2e82998 100644 --- a/src/test/freertos_mocks/wolfip.h +++ b/src/test/freertos_mocks/wolfip.h @@ -25,6 +25,8 @@ typedef void (*wolfIP_wake_cb)(void *arg); #define WOLFIP_EAGAIN 11 #define WOLFIP_EINVAL 22 #define WOLFIP_ENOMEM 12 +#define WOLFIP_EDOM 33 +#define WOLFIP_EINPROGRESS 115 #define MARK_TCP_SOCKET 0x100 #define IS_SOCKET_TCP(fd) (((fd) & MARK_TCP_SOCKET) == MARK_TCP_SOCKET) @@ -33,6 +35,15 @@ typedef void (*wolfIP_wake_cb)(void *arg); #define CB_EVENT_WRITABLE 0x0002 #define CB_EVENT_CLOSED 0x0004 +#define WOLFIP_SOL_SOCKET 1 +#define WOLFIP_SO_RCVTIMEO 20 +#define WOLFIP_SO_SNDTIMEO 21 + +struct wolfIP_timeval { + long tv_sec; + long tv_usec; +}; + int wolfIP_poll(struct wolfIP *ipstack, uint64_t now_ms); int wolfIP_sock_socket(struct wolfIP *s, int domain, int type, int protocol); int wolfIP_sock_bind(struct wolfIP *s, int fd, const struct wolfIP_sockaddr *addr, socklen_t len); diff --git a/src/test/test_freertos_bsd_semantics.c b/src/test/test_freertos_bsd_semantics.c new file mode 100644 index 00000000..bc453f74 --- /dev/null +++ b/src/test/test_freertos_bsd_semantics.c @@ -0,0 +1,414 @@ +/* test_freertos_bsd_semantics.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfIP TCP/IP stack. + * + * wolfIP is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfIP is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +/* Blocking semantics of the FreeRTOS BSD wrapper: timeouts, accept() and + * close(), against a scripted wolfIP core. */ + +#include +#include +#include +#include + +#include "FreeRTOS.h" +#include "semphr.h" +#include "task.h" +#include "wolfip.h" + +struct MockSemaphore { + int count; +}; + +#define LISTEN_FD (MARK_TCP_SOCKET | 0) +#define CHILD_FD (MARK_TCP_SOCKET | 1) + +static int next_socket_fd = LISTEN_FD; +static TickType_t last_wait_ticks; +static int recv_ret = -WOLFIP_EAGAIN; +static int can_read_ret; +static TickType_t now_ticks; +static tsocket_cb registered_cb; +static void *registered_arg; +/* Runs once, in place of another task, when a caller first blocks. */ +static void (*wait_hook)(void); + +SemaphoreHandle_t xSemaphoreCreateBinary(void) +{ + return calloc(1, sizeof(struct MockSemaphore)); +} + +SemaphoreHandle_t xSemaphoreCreateMutex(void) +{ + struct MockSemaphore *sem = calloc(1, sizeof(*sem)); + + if (sem != NULL) + sem->count = 1; + return sem; +} + +/* Nothing else runs, so a wait that is not already satisfied times out. */ +BaseType_t xSemaphoreTake(SemaphoreHandle_t sem, TickType_t ticks) +{ + if (sem == NULL) + return pdFALSE; + if (sem->count == 0 && ticks != 0 && wait_hook != NULL) { + void (*hook)(void) = wait_hook; + + wait_hook = NULL; + hook(); + } + if (sem->count > 0) { + sem->count--; + return pdTRUE; + } + last_wait_ticks = ticks; + if (ticks != 0 && ticks != portMAX_DELAY) + now_ticks += ticks; + return pdFALSE; +} + +BaseType_t xSemaphoreGive(SemaphoreHandle_t sem) +{ + if (sem == NULL) + return pdFALSE; + sem->count++; + return pdTRUE; +} + +void vSemaphoreDelete(SemaphoreHandle_t sem) +{ + free(sem); +} + +BaseType_t xTaskCreate(TaskFunction_t task, const char *name, + uint16_t stack_words, void *arg, UBaseType_t priority, TaskHandle_t *handle) +{ + (void)task; (void)name; (void)stack_words; (void)arg; (void)priority; + (void)handle; + return pdPASS; +} + +void vTaskDelay(TickType_t ticks) +{ + now_ticks += ticks; +} + +TickType_t xTaskGetTickCount(void) +{ + return now_ticks; +} + +void vTaskDelete(TaskHandle_t handle) +{ + (void)handle; +} + +int wolfIP_poll(struct wolfIP *ipstack, uint64_t now_ms) +{ + (void)ipstack; (void)now_ms; + return 0; +} + +int wolfIP_sock_socket(struct wolfIP *s, int domain, int type, int protocol) +{ + (void)s; (void)domain; (void)type; (void)protocol; + return next_socket_fd++; +} + +int wolfIP_sock_bind(struct wolfIP *s, int fd, const struct wolfIP_sockaddr *addr, socklen_t len) +{ + (void)s; (void)fd; (void)addr; (void)len; + return 0; +} + +int wolfIP_sock_listen(struct wolfIP *s, int fd, int backlog) +{ + (void)s; (void)fd; (void)backlog; + return 0; +} + +int wolfIP_sock_accept(struct wolfIP *s, int fd, struct wolfIP_sockaddr *addr, socklen_t *len) +{ + (void)s; (void)fd; (void)addr; (void)len; + return -WOLFIP_EAGAIN; +} + +int wolfIP_sock_connect(struct wolfIP *s, int fd, const struct wolfIP_sockaddr *addr, socklen_t len) +{ + (void)s; (void)fd; (void)addr; (void)len; + return -WOLFIP_EAGAIN; +} + +int wolfIP_sock_send(struct wolfIP *s, int fd, const void *buf, size_t len, int flags) +{ + (void)s; (void)fd; (void)buf; (void)len; (void)flags; + return -WOLFIP_EAGAIN; +} + +int wolfIP_sock_sendto(struct wolfIP *s, int fd, const void *buf, size_t len, int flags, + const struct wolfIP_sockaddr *dest_addr, socklen_t len2) +{ + (void)s; (void)fd; (void)buf; (void)len; (void)flags; (void)dest_addr; (void)len2; + return -WOLFIP_EAGAIN; +} + +int wolfIP_sock_recv(struct wolfIP *s, int fd, void *buf, size_t len, int flags) +{ + (void)s; (void)fd; (void)buf; (void)len; (void)flags; + return recv_ret; +} + +int wolfIP_sock_recvfrom(struct wolfIP *s, int fd, void *buf, size_t len, int flags, + struct wolfIP_sockaddr *src_addr, socklen_t *len2) +{ + (void)s; (void)fd; (void)buf; (void)len; (void)flags; (void)src_addr; (void)len2; + return recv_ret; +} + +int wolfIP_sock_setsockopt(struct wolfIP *s, int fd, int level, int optname, + const void *optval, socklen_t optlen) +{ + (void)s; (void)fd; (void)level; (void)optname; (void)optval; (void)optlen; + return 0; +} + +int wolfIP_sock_getsockopt(struct wolfIP *s, int fd, int level, int optname, + void *optval, socklen_t *optlen) +{ + (void)s; (void)fd; (void)level; (void)optname; (void)optval; (void)optlen; + return 0; +} + +int wolfIP_sock_getsockname(struct wolfIP *s, int fd, struct wolfIP_sockaddr *addr, socklen_t *len) +{ + (void)s; (void)fd; (void)addr; (void)len; + return 0; +} + +int wolfIP_sock_getpeername(struct wolfIP *s, int fd, struct wolfIP_sockaddr *addr, socklen_t *len) +{ + (void)s; (void)fd; (void)addr; (void)len; + return 0; +} + +int wolfIP_sock_can_write(struct wolfIP *s, int fd) +{ + (void)s; (void)fd; + return 0; +} + +int wolfIP_sock_can_read(struct wolfIP *s, int fd) +{ + (void)s; (void)fd; + return can_read_ret; +} + +int wolfIP_sock_close(struct wolfIP *s, int fd) +{ + (void)s; (void)fd; + return 0; +} + +void wolfIP_register_callback(struct wolfIP *s, int fd, tsocket_cb cb, void *arg) +{ + (void)s; (void)fd; + registered_cb = cb; + registered_arg = arg; +} + +void wolfIP_set_wake_cb(struct wolfIP *s, wolfIP_wake_cb cb, void *arg) +{ + (void)s; (void)cb; (void)arg; +} + +#include "../port/freeRTOS/bsd_socket.c" + +static int failures; + +#define CHECK(cond) do { \ + if (!(cond)) { \ + printf("%s:%d: check failed: %s\n", __FILE__, __LINE__, #cond); \ + failures++; \ + } \ + } while (0) + +static int spurious_wakes; + +/* Another task's activity: time passes and a non-completing event fires. */ +static void spurious_wake(void) +{ + now_ticks += pdMS_TO_TICKS(100); + spurious_wakes++; + registered_cb(0, CB_EVENT_WRITABLE, registered_arg); + if (spurious_wakes < 10) + wait_hook = spurious_wake; +} + +static void test_timeouts(void) +{ + struct wolfIP_timeval tv; + struct wolfIP_timeval out; + socklen_t outlen; + char wide[2 * sizeof(struct wolfIP_timeval)]; + char buf[8]; + TickType_t start; + int fd; + + fd = socket(AF_INET, SOCK_STREAM, 0); + CHECK(fd >= 0); + recv_ret = -WOLFIP_EAGAIN; + + /* Default: the wait is unbounded. */ + CHECK(recv(fd, buf, sizeof(buf), 0) == -1); + CHECK(last_wait_ticks == portMAX_DELAY); + + tv.tv_sec = 1; + tv.tv_usec = 500000; + CHECK(setsockopt(fd, WOLFIP_SOL_SOCKET, WOLFIP_SO_RCVTIMEO, &tv, + sizeof(tv)) == 0); + CHECK(recv(fd, buf, sizeof(buf), 0) == -1); + CHECK(last_wait_ticks == pdMS_TO_TICKS(1500)); + CHECK(socket_last_error() == WOLFIP_EAGAIN); + + /* A sub-millisecond timeout still waits, rather than meaning "none". */ + tv.tv_sec = 0; + tv.tv_usec = 1; + CHECK(setsockopt(fd, WOLFIP_SOL_SOCKET, WOLFIP_SO_SNDTIMEO, &tv, + sizeof(tv)) == 0); + CHECK(send(fd, buf, sizeof(buf), 0) == -1); + CHECK(last_wait_ticks == 1); + + /* Zero restores "no timeout". */ + tv.tv_usec = 0; + CHECK(setsockopt(fd, WOLFIP_SOL_SOCKET, WOLFIP_SO_RCVTIMEO, &tv, + sizeof(tv)) == 0); + CHECK(recv(fd, buf, sizeof(buf), 0) == -1); + CHECK(last_wait_ticks == portMAX_DELAY); + + /* Every blocking call honours its timeout. */ + tv.tv_sec = 0; + tv.tv_usec = 250000; + CHECK(setsockopt(fd, WOLFIP_SOL_SOCKET, WOLFIP_SO_SNDTIMEO, &tv, + sizeof(tv)) == 0); + CHECK(setsockopt(fd, WOLFIP_SOL_SOCKET, WOLFIP_SO_RCVTIMEO, &tv, + sizeof(tv)) == 0); + last_wait_ticks = 0; + CHECK(connect(fd, NULL, 0) == -1); + CHECK(last_wait_ticks == pdMS_TO_TICKS(250)); + /* The handshake goes on: connect() reports it as in progress. */ + CHECK(socket_last_error() == WOLFIP_EINPROGRESS); + last_wait_ticks = 0; + CHECK(sendto(fd, buf, sizeof(buf), 0, NULL, 0) == -1); + CHECK(last_wait_ticks == pdMS_TO_TICKS(250)); + last_wait_ticks = 0; + CHECK(recvfrom(fd, buf, sizeof(buf), 0, NULL, NULL) == -1); + CHECK(last_wait_ticks == pdMS_TO_TICKS(250)); + + /* Wakes that do not complete the call spend its timeout, not restart it. */ + spurious_wakes = 0; + wait_hook = spurious_wake; + start = now_ticks; + CHECK(send(fd, buf, sizeof(buf), 0) == -1); + CHECK(socket_last_error() == WOLFIP_EAGAIN); + CHECK((TickType_t)(now_ticks - start) <= pdMS_TO_TICKS(300)); + CHECK(spurious_wakes == 3); + wait_hook = NULL; + + /* getsockopt() reads back what setsockopt() stored. */ + tv.tv_sec = 1; + tv.tv_usec = 500000; + CHECK(setsockopt(fd, WOLFIP_SOL_SOCKET, WOLFIP_SO_RCVTIMEO, &tv, + sizeof(tv)) == 0); + memset(&out, 0xff, sizeof(out)); + outlen = sizeof(out); + CHECK(getsockopt(fd, WOLFIP_SOL_SOCKET, WOLFIP_SO_RCVTIMEO, &out, + &outlen) == 0); + CHECK(outlen == sizeof(out)); + CHECK(out.tv_sec == 1 && out.tv_usec == 500000); + tv.tv_sec = 0; + tv.tv_usec = 0; + CHECK(setsockopt(fd, WOLFIP_SOL_SOCKET, WOLFIP_SO_SNDTIMEO, &tv, + sizeof(tv)) == 0); + memset(&out, 0xff, sizeof(out)); + CHECK(getsockopt(fd, WOLFIP_SOL_SOCKET, WOLFIP_SO_SNDTIMEO, &out, + &outlen) == 0); + CHECK(out.tv_sec == 0 && out.tv_usec == 0); + outlen = sizeof(out) - 1; + CHECK(getsockopt(fd, WOLFIP_SOL_SOCKET, WOLFIP_SO_SNDTIMEO, &out, + &outlen) == -1); + + /* A timeout too long for TickType_t waits forever, as on Linux. */ + tv.tv_sec = LONG_MAX; + tv.tv_usec = 999999; + CHECK(setsockopt(fd, WOLFIP_SOL_SOCKET, WOLFIP_SO_RCVTIMEO, &tv, + sizeof(tv)) == 0); + CHECK(recv(fd, buf, sizeof(buf), 0) == -1); + CHECK(last_wait_ticks == portMAX_DELAY); + + /* A negative timeout does not wait. */ + tv.tv_sec = -1; + tv.tv_usec = 0; + CHECK(setsockopt(fd, WOLFIP_SOL_SOCKET, WOLFIP_SO_RCVTIMEO, &tv, + sizeof(tv)) == 0); + last_wait_ticks = 1234; + CHECK(recv(fd, buf, sizeof(buf), 0) == -1); + CHECK(last_wait_ticks == 0); + CHECK(socket_last_error() == WOLFIP_EAGAIN); + + tv.tv_sec = 0; + tv.tv_usec = 1000000; + CHECK(setsockopt(fd, WOLFIP_SOL_SOCKET, WOLFIP_SO_RCVTIMEO, &tv, + sizeof(tv)) == -1); + CHECK(socket_last_error() == WOLFIP_EDOM); + tv.tv_usec = -1; + CHECK(setsockopt(fd, WOLFIP_SOL_SOCKET, WOLFIP_SO_RCVTIMEO, &tv, + sizeof(tv)) == -1); + CHECK(socket_last_error() == WOLFIP_EDOM); + /* A larger struct timeval (64-bit time_t on a 32-bit target) is refused. */ + memset(wide, 0, sizeof(wide)); + CHECK(setsockopt(fd, WOLFIP_SOL_SOCKET, WOLFIP_SO_RCVTIMEO, wide, + sizeof(wide)) == -1); + tv.tv_sec = 1; + tv.tv_usec = 0; + CHECK(setsockopt(fd, WOLFIP_SOL_SOCKET, WOLFIP_SO_RCVTIMEO, &tv, + sizeof(tv) - 1) == -1); + + CHECK(close(fd) == 0); +} + +int main(void) +{ + struct wolfIP stack; + + memset(&stack, 0, sizeof(stack)); + if (wolfip_freertos_socket_init(&stack, 1, 128) != 0) { + printf("init failed\n"); + return 1; + } + + test_timeouts(); + + if (failures != 0) { + printf("test_freertos_bsd_semantics: %d FAILED\n", failures); + return 1; + } + printf("test_freertos_bsd_semantics: passed\n"); + return 0; +} diff --git a/wolfip.h b/wolfip.h index f02839ed..6445d416 100644 --- a/wolfip.h +++ b/wolfip.h @@ -72,6 +72,22 @@ typedef unsigned long size_t; #endif #endif +#ifndef WOLFIP_SO_RCVTIMEO +#ifdef SO_RCVTIMEO +#define WOLFIP_SO_RCVTIMEO SO_RCVTIMEO +#else +#define WOLFIP_SO_RCVTIMEO 20 +#endif +#endif + +#ifndef WOLFIP_SO_SNDTIMEO +#ifdef SO_SNDTIMEO +#define WOLFIP_SO_SNDTIMEO SO_SNDTIMEO +#else +#define WOLFIP_SO_SNDTIMEO 21 +#endif +#endif + #ifndef WOLFIP_SO_DONTROUTE #ifdef SO_DONTROUTE #define WOLFIP_SO_DONTROUTE SO_DONTROUTE @@ -179,6 +195,22 @@ typedef uint32_t ip4; #endif #endif +#ifndef WOLFIP_EINPROGRESS +#ifdef EINPROGRESS +#define WOLFIP_EINPROGRESS EINPROGRESS +#else +#define WOLFIP_EINPROGRESS (115) +#endif +#endif + +#ifndef WOLFIP_EDOM +#ifdef EDOM +#define WOLFIP_EDOM EDOM +#else +#define WOLFIP_EDOM (33) +#endif +#endif + #ifndef WOLFIP_EBADF #ifdef EBADF #define WOLFIP_EBADF EBADF @@ -368,6 +400,12 @@ struct wolfIP_sockaddr_in { struct wolfIP_sockaddr { uint16_t sa_family; }; typedef uint32_t socklen_t; +/* struct timeval for SO_RCVTIMEO / SO_SNDTIMEO, without needing sys/time.h. */ +struct wolfIP_timeval { + long tv_sec; + long tv_usec; +}; + /* Pull in the system socket types when available, but only declare * WOLFIP_HAVE_POSIX_TYPES once BOTH AND are * confirmed present. Zephyr is a special case: its POSIX socket layer From 39d21bb2a0aa107056272fa19ba2a5ccf7fb13ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Thu, 1 Oct 2026 14:46:45 +0200 Subject: [PATCH 08/10] freertos: let accept() and close() rely on the core With the core changes before this one, the wrapper no longer has to second-guess what the stack returns. accept() gets only established connections from wolfIP_sock_accept(), so it waits on -WOLFIP_EAGAIN until the listener becomes readable or SO_RCVTIMEO runs out. Each pass first checks that its descriptor still refers to the listener it started with: another task may have closed it while this one waited, and the wrapper slot and the core slot can both be reused. The one-shot retry on a bare -1 from accept(), send() and sendto(), which covered a socket handed out before its handshake had finished, goes away with that case. close() returns as soon as wolfIP_sock_close() has queued the FIN, and the stack finishes the exchange on its own, reclaiming the slot under pressure. It waits only while the transmit buffer has no room for the FIN, woken by CB_EVENT_WRITABLE, for at most WOLFIP_BSD_CLOSE_LINGER_MS (10 s), and then resets the connection with wolfIP_sock_abort(), so a peer that stops reading cannot hold the calling task. -WOLFIP_EBADF means the stack has already released the socket and is a successful close. The special case for a -1 after CB_EVENT_CLOSED is gone: the generation check makes a released descriptor answer -WOLFIP_EBADF. The harness gains tests for both calls, and test-freertos-close-last-ack now scripts the new core: -WOLFIP_EAGAIN while the FIFO is full, then -WOLFIP_EBADF once the stack has released the socket. --- src/port/freeRTOS/README.md | 3 + src/port/freeRTOS/bsd_socket.c | 114 ++++++------------ src/test/freertos_mocks/wolfip.h | 2 + src/test/test_freertos_bsd_semantics.c | 147 +++++++++++++++++++++++- src/test/test_freertos_close_last_ack.c | 12 +- 5 files changed, 198 insertions(+), 80 deletions(-) diff --git a/src/port/freeRTOS/README.md b/src/port/freeRTOS/README.md index e5e708ff..aa496db4 100644 --- a/src/port/freeRTOS/README.md +++ b/src/port/freeRTOS/README.md @@ -107,6 +107,7 @@ Defined in `bsd_socket.c`: - `WOLFIP_FREERTOS_POLL_MIN_MS` (default: `1`) - `WOLFIP_FREERTOS_POLL_MAX_MS` (default: `5`) - `WOLFIP_BSD_DEBUG_CALLBACK` (default: `0`) - set to `1` to log socket callbacks from the poll task +- `WOLFIP_BSD_CLOSE_LINGER_MS` (default: `10000`) - how long `close()` waits for room to queue its FIN before it aborts the connection Override via compiler flags, for example: @@ -120,3 +121,5 @@ CFLAGS += -DWOLFIP_FREERTOS_BSD_MAX_FDS=32 - File descriptors returned by this layer are wrapper FDs, not raw wolfIP internal FDs. - The wrapper is intended for task context (not ISR context). - Blocking calls wait indefinitely by default. `setsockopt(fd, WOLFIP_SOL_SOCKET, WOLFIP_SO_RCVTIMEO, &tv, sizeof(tv))` bounds `accept`, `recv` and `recvfrom`, and `WOLFIP_SO_SNDTIMEO` bounds `connect`, `send` and `sendto`, with `tv` a `struct wolfIP_timeval` and `optlen` exactly its size; each bounds the whole call, not each wait inside it. An expired wait returns -1 with `socket_last_error()` set to `WOLFIP_EAGAIN`; as on Linux, an all-zero `tv` or one too long for `TickType_t` waits without bound, a negative `tv_sec` does not wait, and a `tv_usec` outside [0, 999999] fails with `WOLFIP_EDOM`. A `connect()` that runs out of time fails with `WOLFIP_EINPROGRESS` instead, while the handshake goes on. `getsockopt()` reads the current values back. +- `accept()` returns only connections whose handshake has completed; until then wolfIP holds them back (see `wolfIP_sock_accept()` in `docs/API.md`), and `accept()` waits. +- `close()` returns once its FIN is queued, and wolfIP finishes the FIN exchange on its own. It waits only while the transmit buffer has no room for the FIN, for at most `WOLFIP_BSD_CLOSE_LINGER_MS`, and then resets the connection with `wolfIP_sock_abort()`. diff --git a/src/port/freeRTOS/bsd_socket.c b/src/port/freeRTOS/bsd_socket.c index ab63b93e..5887a5ea 100644 --- a/src/port/freeRTOS/bsd_socket.c +++ b/src/port/freeRTOS/bsd_socket.c @@ -46,6 +46,10 @@ #define WOLFIP_FREERTOS_POLL_MIN_MS 1u #endif +#ifndef WOLFIP_BSD_CLOSE_LINGER_MS +#define WOLFIP_BSD_CLOSE_LINGER_MS 10000u +#endif + typedef struct { int in_use; int internal_fd; @@ -239,19 +243,6 @@ static TickType_t wolfip_bsd_remaining(TickType_t timeout, TickType_t start) return (elapsed < timeout) ? (TickType_t)(timeout - elapsed) : 0; } -/* Some TCP core calls surface a temporary "not established yet" as -1 on a - * freshly accepted stream socket before the final ACK promotes it to - * ESTABLISHED. Allow a single wait/retry for that case without turning all - * bare -1 returns into infinite retry loops. */ -static int wolfip_bsd_tcp_stream_retryable_once(int internal_fd, int ret, int *used) -{ - if (ret != -1 || used == NULL || *used || !IS_SOCKET_TCP(internal_fd)) { - return 0; - } - *used = 1; - return 1; -} - static int wolfip_bsd_tcp_recv_should_wait_locked(int internal_fd, int ret) { if (ret != -1 || !IS_SOCKET_TCP(internal_fd)) { @@ -378,7 +369,7 @@ int accept(int sockfd, struct wolfIP_sockaddr *addr, socklen_t *addrlen) { int ret; int public_fd; - int retried_minus_one = 0; + int listen_fd; wolfip_bsd_fd_entry *entry; TickType_t start; TickType_t left; @@ -387,10 +378,17 @@ int accept(int sockfd, struct wolfIP_sockaddr *addr, socklen_t *addrlen) return -1; } entry = &g_fds[sockfd]; + listen_fd = entry->internal_fd; start = xTaskGetTickCount(); for (;;) { xSemaphoreTake(g_lock, portMAX_DELAY); + /* Another task may have closed the listener while this one waited. */ + if (!entry->in_use || (entry->internal_fd != listen_fd)) { + xSemaphoreGive(g_lock); + wolfip_bsd_set_error(WOLFIP_EBADF); + return -1; + } left = wolfip_bsd_remaining(entry->rx_timeout, start); ret = wolfIP_sock_accept(g_ipstack, entry->internal_fd, addr, addrlen); if (ret >= 0) { @@ -405,18 +403,7 @@ int accept(int sockfd, struct wolfIP_sockaddr *addr, socklen_t *addrlen) } return public_fd; } - if (wolfip_bsd_tcp_stream_retryable_once(entry->internal_fd, ret, - &retried_minus_one)) { - wolfip_bsd_prepare_wait_locked(entry, - (uint16_t)(CB_EVENT_READABLE | CB_EVENT_CLOSED)); - xSemaphoreGive(g_lock); - if (wolfip_bsd_wait_unlocked(entry, left) < 0) { - wolfip_bsd_set_error(WOLFIP_EAGAIN); - return -1; - } - continue; - } - if (ret != -WOLFIP_EAGAIN) { + if ((ret != -WOLFIP_EAGAIN) || (left == 0)) { xSemaphoreGive(g_lock); wolfip_bsd_set_error(ret); return -1; @@ -424,10 +411,7 @@ int accept(int sockfd, struct wolfIP_sockaddr *addr, socklen_t *addrlen) wolfip_bsd_prepare_wait_locked(entry, (uint16_t)(CB_EVENT_READABLE | CB_EVENT_CLOSED)); xSemaphoreGive(g_lock); - if (wolfip_bsd_wait_unlocked(entry, left) < 0) { - wolfip_bsd_set_error(WOLFIP_EAGAIN); - return -1; - } + (void)wolfip_bsd_wait_unlocked(entry, left); } } @@ -470,7 +454,6 @@ int connect(int sockfd, const struct wolfIP_sockaddr *addr, socklen_t addrlen) int send(int sockfd, const void *buf, size_t len, int flags) { int ret; - int retried_minus_one = 0; wolfip_bsd_fd_entry *entry; TickType_t start; TickType_t left; @@ -489,17 +472,6 @@ int send(int sockfd, const void *buf, size_t len, int flags) xSemaphoreGive(g_lock); return ret; } - if (wolfip_bsd_tcp_stream_retryable_once(entry->internal_fd, ret, - &retried_minus_one)) { - wolfip_bsd_prepare_wait_locked(entry, - (uint16_t)(CB_EVENT_WRITABLE | CB_EVENT_READABLE | CB_EVENT_CLOSED)); - xSemaphoreGive(g_lock); - if (wolfip_bsd_wait_unlocked(entry, left) < 0) { - wolfip_bsd_set_error(WOLFIP_EAGAIN); - return -1; - } - continue; - } if (ret != -WOLFIP_EAGAIN) { xSemaphoreGive(g_lock); wolfip_bsd_set_error(ret); @@ -519,7 +491,6 @@ int sendto(int sockfd, const void *buf, size_t len, int flags, const struct wolfIP_sockaddr *dest_addr, socklen_t addrlen) { int ret; - int retried_minus_one = 0; wolfip_bsd_fd_entry *entry; TickType_t start; TickType_t left; @@ -538,17 +509,6 @@ int sendto(int sockfd, const void *buf, size_t len, int flags, xSemaphoreGive(g_lock); return ret; } - if (wolfip_bsd_tcp_stream_retryable_once(entry->internal_fd, ret, - &retried_minus_one)) { - wolfip_bsd_prepare_wait_locked(entry, - (uint16_t)(CB_EVENT_WRITABLE | CB_EVENT_READABLE | CB_EVENT_CLOSED)); - xSemaphoreGive(g_lock); - if (wolfip_bsd_wait_unlocked(entry, left) < 0) { - wolfip_bsd_set_error(WOLFIP_EAGAIN); - return -1; - } - continue; - } if (ret != -WOLFIP_EAGAIN) { xSemaphoreGive(g_lock); wolfip_bsd_set_error(ret); @@ -814,44 +774,46 @@ int getpeername(int sockfd, struct wolfIP_sockaddr *addr, socklen_t *addrlen) int close(int sockfd) { int ret; + int internal_fd; wolfip_bsd_fd_entry *entry; + TickType_t start; + TickType_t left; if (!wolfip_bsd_fd_valid(sockfd)) { return -1; } entry = &g_fds[sockfd]; + internal_fd = entry->internal_fd; + start = xTaskGetTickCount(); for (;;) { xSemaphoreTake(g_lock, portMAX_DELAY); - ret = wolfIP_sock_close(g_ipstack, entry->internal_fd); - if (ret >= 0) { - wolfIP_register_callback(g_ipstack, entry->internal_fd, NULL, NULL); - wolfip_bsd_fd_free(sockfd); + if (!entry->in_use || (entry->internal_fd != internal_fd)) { xSemaphoreGive(g_lock); - return ret; + wolfip_bsd_set_error(WOLFIP_EBADF); + return -1; } - if ((ret == -1) && IS_SOCKET_TCP(entry->internal_fd) && - ((entry->seen_events & CB_EVENT_CLOSED) != 0u)) { - /* The TCP core can destroy the socket immediately after delivering - * CB_EVENT_CLOSED (e.g. final ACK in LAST_ACK), so the retry sees - * the already-zeroed descriptor and wolfIP_sock_close() returns -1. - * Treat that as a completed close and release the wrapper slot. */ - wolfIP_register_callback(g_ipstack, entry->internal_fd, NULL, NULL); - wolfip_bsd_fd_free(sockfd); + left = wolfip_bsd_remaining(pdMS_TO_TICKS(WOLFIP_BSD_CLOSE_LINGER_MS), start); + ret = wolfIP_sock_close(g_ipstack, internal_fd); + /* -EAGAIN: no room for the FIN yet. */ + if ((ret == -WOLFIP_EAGAIN) && (left > 0)) { + wolfip_bsd_prepare_wait_locked(entry, + (uint16_t)(CB_EVENT_WRITABLE | CB_EVENT_CLOSED)); xSemaphoreGive(g_lock); - return 0; + (void)wolfip_bsd_wait_unlocked(entry, left); + continue; } - if (ret != -WOLFIP_EAGAIN) { - xSemaphoreGive(g_lock); - wolfip_bsd_set_error(ret); - return -1; + if (ret == -WOLFIP_EAGAIN) { + (void)wolfIP_sock_abort(g_ipstack, internal_fd); } - - wolfip_bsd_prepare_wait_locked(entry, CB_EVENT_CLOSED); + wolfIP_register_callback(g_ipstack, internal_fd, NULL, NULL); + wolfip_bsd_fd_free(sockfd); xSemaphoreGive(g_lock); - if (wolfip_bsd_wait_unlocked(entry, portMAX_DELAY) < 0) { - wolfip_bsd_set_error(WOLFIP_EAGAIN); + /* -EBADF: the stack already released the socket. */ + if ((ret < 0) && (ret != -WOLFIP_EAGAIN) && (ret != -WOLFIP_EBADF)) { + wolfip_bsd_set_error(ret); return -1; } + return 0; } } diff --git a/src/test/freertos_mocks/wolfip.h b/src/test/freertos_mocks/wolfip.h index a2e82998..8d2e0470 100644 --- a/src/test/freertos_mocks/wolfip.h +++ b/src/test/freertos_mocks/wolfip.h @@ -27,6 +27,7 @@ typedef void (*wolfIP_wake_cb)(void *arg); #define WOLFIP_ENOMEM 12 #define WOLFIP_EDOM 33 #define WOLFIP_EINPROGRESS 115 +#define WOLFIP_EBADF 9 #define MARK_TCP_SOCKET 0x100 #define IS_SOCKET_TCP(fd) (((fd) & MARK_TCP_SOCKET) == MARK_TCP_SOCKET) @@ -65,6 +66,7 @@ int wolfIP_sock_getpeername(struct wolfIP *s, int fd, struct wolfIP_sockaddr *ad int wolfIP_sock_can_write(struct wolfIP *s, int fd); int wolfIP_sock_can_read(struct wolfIP *s, int fd); int wolfIP_sock_close(struct wolfIP *s, int fd); +int wolfIP_sock_abort(struct wolfIP *s, int fd); void wolfIP_register_callback(struct wolfIP *s, int fd, tsocket_cb cb, void *arg); void wolfIP_set_wake_cb(struct wolfIP *s, wolfIP_wake_cb cb, void *arg); diff --git a/src/test/test_freertos_bsd_semantics.c b/src/test/test_freertos_bsd_semantics.c index bc453f74..5a96bd86 100644 --- a/src/test/test_freertos_bsd_semantics.c +++ b/src/test/test_freertos_bsd_semantics.c @@ -144,9 +144,25 @@ int wolfIP_sock_listen(struct wolfIP *s, int fd, int backlog) return 0; } +/* Successive results of wolfIP_sock_accept() and wolfIP_sock_close(); once + * a script runs out, accept() keeps returning -EAGAIN and close() 0. */ +static int accept_script[4]; +static int accept_steps; +static int accept_calls; +static int accept_last_fd; +static int close_script[4]; +static int close_steps; +static int close_calls; +static int abort_calls; +static int abort_fd; + int wolfIP_sock_accept(struct wolfIP *s, int fd, struct wolfIP_sockaddr *addr, socklen_t *len) { - (void)s; (void)fd; (void)addr; (void)len; + (void)s; (void)addr; (void)len; + accept_last_fd = fd; + if (accept_calls < accept_steps) + return accept_script[accept_calls++]; + accept_calls++; return -WOLFIP_EAGAIN; } @@ -223,6 +239,17 @@ int wolfIP_sock_can_read(struct wolfIP *s, int fd) int wolfIP_sock_close(struct wolfIP *s, int fd) { (void)s; (void)fd; + if (close_calls < close_steps) + return close_script[close_calls++]; + close_calls++; + return 0; +} + +int wolfIP_sock_abort(struct wolfIP *s, int fd) +{ + (void)s; + abort_calls++; + abort_fd = fd; return 0; } @@ -393,6 +420,122 @@ static void test_timeouts(void) CHECK(close(fd) == 0); } +static void script_reset(void) +{ + accept_steps = accept_calls = 0; + close_steps = close_calls = 0; + abort_calls = 0; + abort_fd = -1; + wait_hook = NULL; +} + +static int hook_lfd; + +/* The listener's connection completes while accept() waits. */ +static void child_established(void) +{ + registered_cb(0, CB_EVENT_READABLE, registered_arg); +} + +/* Another task closes the listener and a third reuses its descriptor. */ +static void listener_replaced(void) +{ + CHECK(close(hook_lfd) == 0); + CHECK(socket(AF_INET, SOCK_STREAM, 0) == hook_lfd); +} + +static void test_accept(void) +{ + struct wolfIP_timeval tv; + int lfd; + int fd; + + script_reset(); + lfd = socket(AF_INET, SOCK_STREAM, 0); + CHECK(lfd >= 0); + + /* -EAGAIN while the handshake runs: wait for the listener, then retry. */ + accept_script[0] = -WOLFIP_EAGAIN; + accept_script[1] = CHILD_FD; + accept_steps = 2; + wait_hook = child_established; + fd = accept(lfd, NULL, NULL); + CHECK(fd >= 0 && fd != lfd); + CHECK(accept_calls == 2); + CHECK(close(fd) == 0); + + tv.tv_sec = 0; + tv.tv_usec = 250000; + CHECK(setsockopt(lfd, WOLFIP_SOL_SOCKET, WOLFIP_SO_RCVTIMEO, &tv, + sizeof(tv)) == 0); + script_reset(); + CHECK(accept(lfd, NULL, NULL) == -1); + CHECK(socket_last_error() == WOLFIP_EAGAIN); + CHECK(last_wait_ticks == pdMS_TO_TICKS(250)); + + script_reset(); + hook_lfd = lfd; + wait_hook = listener_replaced; + CHECK(accept(lfd, NULL, NULL) == -1); + CHECK(socket_last_error() == WOLFIP_EBADF); + CHECK(accept_calls == 1); + CHECK(close(lfd) == 0); +} + +/* Room for the FIN frees up while close() waits. */ +static void tx_space(void) +{ + registered_cb(0, CB_EVENT_WRITABLE, registered_arg); + wait_hook = tx_space; +} + +static void test_close(void) +{ + TickType_t start; + int fd; + + /* The FIN is queued at once: nothing to wait for. */ + script_reset(); + fd = socket(AF_INET, SOCK_STREAM, 0); + last_wait_ticks = 1234; + CHECK(close(fd) == 0); + CHECK(close_calls == 1); + CHECK(last_wait_ticks == 1234); + CHECK(close(fd) == -1); + + /* A full TX FIFO: retry as room frees up. */ + script_reset(); + fd = socket(AF_INET, SOCK_STREAM, 0); + close_script[0] = -WOLFIP_EAGAIN; + close_script[1] = -WOLFIP_EAGAIN; + close_steps = 2; + wait_hook = tx_space; + CHECK(close(fd) == 0); + CHECK(close_calls == 3); + CHECK(abort_calls == 0); + + /* Never any room: abort once the linger runs out. */ + script_reset(); + fd = socket(AF_INET, SOCK_STREAM, 0); + close_script[0] = close_script[1] = close_script[2] = -WOLFIP_EAGAIN; + close_steps = 3; + start = now_ticks; + CHECK(close(fd) == 0); + CHECK(abort_calls == 1); + CHECK(abort_fd == next_socket_fd - 1); + CHECK((TickType_t)(now_ticks - start) == pdMS_TO_TICKS(WOLFIP_BSD_CLOSE_LINGER_MS)); + CHECK(close(fd) == -1); + + /* The stack released the socket and reissued its descriptor. */ + script_reset(); + fd = socket(AF_INET, SOCK_STREAM, 0); + close_script[0] = -WOLFIP_EBADF; + close_steps = 1; + CHECK(close(fd) == 0); + CHECK(abort_calls == 0); + CHECK(close(fd) == -1); +} + int main(void) { struct wolfIP stack; @@ -404,6 +547,8 @@ int main(void) } test_timeouts(); + test_accept(); + test_close(); if (failures != 0) { printf("test_freertos_bsd_semantics: %d FAILED\n", failures); diff --git a/src/test/test_freertos_close_last_ack.c b/src/test/test_freertos_close_last_ack.c index 536f932d..8808d123 100644 --- a/src/test/test_freertos_close_last_ack.c +++ b/src/test/test_freertos_close_last_ack.c @@ -1,5 +1,5 @@ -/* Regression test for the FreeRTOS BSD close() wrapper when the core delivers - * CB_EVENT_CLOSED synchronously during LAST_ACK teardown. */ +/* Regression test for the FreeRTOS BSD close() wrapper when the core releases + * the socket while close() waits for room to queue its FIN. */ #include #include @@ -212,7 +212,13 @@ int wolfIP_sock_close(struct wolfIP *s, int fd) close_calls++; if (close_calls == 1) return -WOLFIP_EAGAIN; - return -1; + return -WOLFIP_EBADF; +} + +int wolfIP_sock_abort(struct wolfIP *s, int fd) +{ + (void)s; (void)fd; + return 0; } void wolfIP_register_callback(struct wolfIP *s, int fd, tsocket_cb cb, void *arg) From cde78c5bd5235e3f28bfc0e81463c940f812d6f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Thu, 1 Oct 2026 14:46:49 +0200 Subject: [PATCH 09/10] freertos: do not delete a descriptor's semaphore under a blocked task close() released the wrapper slot at once, and releasing it deletes the slot's ready semaphore. A task blocked on that descriptor in recv(), send(), accept() or connect() was still inside xSemaphoreTake() on it, so it woke on a deleted semaphore, and the next socket() or accept() could hand the slot, with a new semaphore, to an unrelated socket while the old caller still held a pointer into it. Closing a socket from one task while another reads it is how a server stops a stuck connection, so this is not a contrived case. Each slot now counts the tasks blocked on it, in wolfip_bsd_wait(), which every blocking call uses to drop the lock and wait. close() on a slot with waiters marks it closing and gives its semaphore instead of releasing it. A woken waiter that finds its slot closing passes the wake on to the next one, the last one releases the slot, and each returns -1 with WOLFIP_EBADF. A closing slot is not valid for new calls and is not reissued until it is released. A task that was woken normally drops out of the count before it relocks, and every call validates its descriptor before taking the lock, so in either window another task can still close the descriptor and a third can be handed the slot. Every call therefore remembers the internal descriptor it started with and takes the lock through wolfip_bsd_lock(), which fails with WOLFIP_EBADF when the slot is closing, released or holds another socket. A call preempted before it reads its descriptor at all can still land on a reissued one; as in POSIX, closing a descriptor while another task is about to use it is the application's race, and the README says so. The unlocked validity check now sets WOLFIP_EBADF as well, so a call on a closed descriptor never leaves an earlier EAGAIN behind for a retry loop to spin on. The README also states two limits of the wrapper that stay as they are: one socket_last_error() value for all tasks, and one wake-up per descriptor shared by every task blocked on it. The harness keeps deleted semaphores and counts any use of one, and tests a close() from another task under one and under two waiters, and a close and reissue that lands just before a woken recv(), a setsockopt() or a listen() takes the lock. The accept() test that reused the listener's slot while accept() waited now checks that the slot is not reused. --- src/port/freeRTOS/README.md | 3 + src/port/freeRTOS/bsd_socket.c | 229 +++++++++++++++++-------- src/test/test_freertos_bsd_semantics.c | 136 ++++++++++++++- 3 files changed, 294 insertions(+), 74 deletions(-) diff --git a/src/port/freeRTOS/README.md b/src/port/freeRTOS/README.md index aa496db4..9ea0a996 100644 --- a/src/port/freeRTOS/README.md +++ b/src/port/freeRTOS/README.md @@ -123,3 +123,6 @@ CFLAGS += -DWOLFIP_FREERTOS_BSD_MAX_FDS=32 - Blocking calls wait indefinitely by default. `setsockopt(fd, WOLFIP_SOL_SOCKET, WOLFIP_SO_RCVTIMEO, &tv, sizeof(tv))` bounds `accept`, `recv` and `recvfrom`, and `WOLFIP_SO_SNDTIMEO` bounds `connect`, `send` and `sendto`, with `tv` a `struct wolfIP_timeval` and `optlen` exactly its size; each bounds the whole call, not each wait inside it. An expired wait returns -1 with `socket_last_error()` set to `WOLFIP_EAGAIN`; as on Linux, an all-zero `tv` or one too long for `TickType_t` waits without bound, a negative `tv_sec` does not wait, and a `tv_usec` outside [0, 999999] fails with `WOLFIP_EDOM`. A `connect()` that runs out of time fails with `WOLFIP_EINPROGRESS` instead, while the handshake goes on. `getsockopt()` reads the current values back. - `accept()` returns only connections whose handshake has completed; until then wolfIP holds them back (see `wolfIP_sock_accept()` in `docs/API.md`), and `accept()` waits. - `close()` returns once its FIN is queued, and wolfIP finishes the FIN exchange on its own. It waits only while the transmit buffer has no room for the FIN, for at most `WOLFIP_BSD_CLOSE_LINGER_MS`, and then resets the connection with `wolfIP_sock_abort()`. +- `close()` on a descriptor that other tasks are blocked on wakes them, and their calls return -1 with `socket_last_error()` set to `WOLFIP_EBADF`. The descriptor is reissued only after the last of them has returned, and a call that finds its descriptor closed or reissued when it takes the wrapper's lock fails the same way. As in POSIX, a call made at the same moment as another task's `close()` of that descriptor is still a race in the application: until it reaches the lock, it can end up on whatever socket the descriptor names by then. +- `socket_last_error()` is one value for all tasks, so when calls in several tasks fail at the same time each may read another's error. +- Blocking calls on one descriptor share its wake-up, so do not leave another task blocked in `recv()` or `send()` on a descriptor while `close()` waits for room to queue its FIN: the close can miss its wake-up and reset the connection after `WOLFIP_BSD_CLOSE_LINGER_MS`. diff --git a/src/port/freeRTOS/bsd_socket.c b/src/port/freeRTOS/bsd_socket.c index 5887a5ea..af958b6b 100644 --- a/src/port/freeRTOS/bsd_socket.c +++ b/src/port/freeRTOS/bsd_socket.c @@ -52,6 +52,9 @@ typedef struct { int in_use; + /* close() ran while tasks were blocked on it; the last one frees the slot. */ + int closing; + int waiters; int internal_fd; SemaphoreHandle_t ready_sem; volatile uint16_t wait_events; @@ -145,10 +148,9 @@ int socket_last_error(void) static int wolfip_bsd_fd_valid(int public_fd) { - if (public_fd < 0 || public_fd >= WOLFIP_FREERTOS_BSD_MAX_FDS) { - return 0; - } - if (!g_fds[public_fd].in_use) { + if ((public_fd < 0) || (public_fd >= WOLFIP_FREERTOS_BSD_MAX_FDS) || + !g_fds[public_fd].in_use || g_fds[public_fd].closing) { + wolfip_bsd_set_error(WOLFIP_EBADF); return 0; } return 1; @@ -164,6 +166,8 @@ static int wolfip_bsd_fd_alloc(int internal_fd) return -WOLFIP_ENOMEM; } g_fds[i].in_use = 1; + g_fds[i].closing = 0; + g_fds[i].waiters = 0; g_fds[i].internal_fd = internal_fd; g_fds[i].ready_sem = sem; g_fds[i].wait_events = 0; @@ -178,18 +182,39 @@ static int wolfip_bsd_fd_alloc(int internal_fd) static void wolfip_bsd_fd_free(int public_fd) { - if (!wolfip_bsd_fd_valid(public_fd)) { + if (public_fd < 0 || public_fd >= WOLFIP_FREERTOS_BSD_MAX_FDS || + !g_fds[public_fd].in_use) { return; } vSemaphoreDelete(g_fds[public_fd].ready_sem); g_fds[public_fd].in_use = 0; + g_fds[public_fd].closing = 0; g_fds[public_fd].internal_fd = -1; g_fds[public_fd].ready_sem = NULL; g_fds[public_fd].wait_events = 0; g_fds[public_fd].seen_events = 0; } +/* With g_lock held: the descriptor was closed, or reissued to another socket. */ +static int wolfip_bsd_entry_lost(const wolfip_bsd_fd_entry *entry, int internal_fd) +{ + return !entry->in_use || entry->closing || (entry->internal_fd != internal_fd); +} + +/* Takes g_lock, or fails with WOLFIP_EBADF and leaves it free if the + * descriptor no longer names internal_fd. */ +static int wolfip_bsd_lock(int public_fd, int internal_fd) +{ + xSemaphoreTake(g_lock, portMAX_DELAY); + if (wolfip_bsd_entry_lost(&g_fds[public_fd], internal_fd)) { + xSemaphoreGive(g_lock); + wolfip_bsd_set_error(WOLFIP_EBADF); + return -1; + } + return 0; +} + static void wolfip_bsd_socket_cb(int internal_fd, uint16_t events, void *arg) { wolfip_bsd_fd_entry *entry = (wolfip_bsd_fd_entry *)arg; @@ -223,13 +248,30 @@ static void wolfip_bsd_prepare_wait_locked(wolfip_bsd_fd_entry *entry, uint16_t wolfIP_register_callback(g_ipstack, entry->internal_fd, wolfip_bsd_socket_cb, entry); } -static int wolfip_bsd_wait_unlocked(wolfip_bsd_fd_entry *entry, - TickType_t timeout) +/* Called with g_lock held, returns with it released: 0 when woken, + * -WOLFIP_EAGAIN on timeout, -WOLFIP_EBADF when the descriptor was closed. */ +static int wolfip_bsd_wait(wolfip_bsd_fd_entry *entry, int public_fd, + TickType_t timeout) { - if (xSemaphoreTake(entry->ready_sem, timeout) != pdTRUE) { - return -1; + int ret; + + entry->waiters++; + xSemaphoreGive(g_lock); + ret = (xSemaphoreTake(entry->ready_sem, timeout) == pdTRUE) ? 0 : + -WOLFIP_EAGAIN; + xSemaphoreTake(g_lock, portMAX_DELAY); + entry->waiters--; + if (entry->closing) { + if (entry->waiters > 0) { + (void)xSemaphoreGive(entry->ready_sem); + } + else { + wolfip_bsd_fd_free(public_fd); + } + ret = -WOLFIP_EBADF; } - return 0; + xSemaphoreGive(g_lock); + return ret; } static TickType_t wolfip_bsd_remaining(TickType_t timeout, TickType_t start) @@ -336,11 +378,16 @@ int socket(int domain, int type, int protocol) int bind(int sockfd, const struct wolfIP_sockaddr *addr, socklen_t addrlen) { int ret; + int internal_fd; + if (!wolfip_bsd_fd_valid(sockfd)) { return -1; } - xSemaphoreTake(g_lock, portMAX_DELAY); - ret = wolfIP_sock_bind(g_ipstack, g_fds[sockfd].internal_fd, addr, addrlen); + internal_fd = g_fds[sockfd].internal_fd; + if (wolfip_bsd_lock(sockfd, internal_fd) < 0) { + return -1; + } + ret = wolfIP_sock_bind(g_ipstack, internal_fd, addr, addrlen); xSemaphoreGive(g_lock); if (ret < 0) { wolfip_bsd_set_error(ret); @@ -352,11 +399,16 @@ int bind(int sockfd, const struct wolfIP_sockaddr *addr, socklen_t addrlen) int listen(int sockfd, int backlog) { int ret; + int internal_fd; + if (!wolfip_bsd_fd_valid(sockfd)) { return -1; } - xSemaphoreTake(g_lock, portMAX_DELAY); - ret = wolfIP_sock_listen(g_ipstack, g_fds[sockfd].internal_fd, backlog); + internal_fd = g_fds[sockfd].internal_fd; + if (wolfip_bsd_lock(sockfd, internal_fd) < 0) { + return -1; + } + ret = wolfIP_sock_listen(g_ipstack, internal_fd, backlog); xSemaphoreGive(g_lock); if (ret < 0) { wolfip_bsd_set_error(ret); @@ -382,11 +434,7 @@ int accept(int sockfd, struct wolfIP_sockaddr *addr, socklen_t *addrlen) start = xTaskGetTickCount(); for (;;) { - xSemaphoreTake(g_lock, portMAX_DELAY); - /* Another task may have closed the listener while this one waited. */ - if (!entry->in_use || (entry->internal_fd != listen_fd)) { - xSemaphoreGive(g_lock); - wolfip_bsd_set_error(WOLFIP_EBADF); + if (wolfip_bsd_lock(sockfd, listen_fd) < 0) { return -1; } left = wolfip_bsd_remaining(entry->rx_timeout, start); @@ -410,14 +458,17 @@ int accept(int sockfd, struct wolfIP_sockaddr *addr, socklen_t *addrlen) } wolfip_bsd_prepare_wait_locked(entry, (uint16_t)(CB_EVENT_READABLE | CB_EVENT_CLOSED)); - xSemaphoreGive(g_lock); - (void)wolfip_bsd_wait_unlocked(entry, left); + if (wolfip_bsd_wait(entry, sockfd, left) == -WOLFIP_EBADF) { + wolfip_bsd_set_error(WOLFIP_EBADF); + return -1; + } } } int connect(int sockfd, const struct wolfIP_sockaddr *addr, socklen_t addrlen) { int ret; + int internal_fd; wolfip_bsd_fd_entry *entry; TickType_t start; TickType_t left; @@ -426,10 +477,13 @@ int connect(int sockfd, const struct wolfIP_sockaddr *addr, socklen_t addrlen) return -1; } entry = &g_fds[sockfd]; + internal_fd = entry->internal_fd; start = xTaskGetTickCount(); for (;;) { - xSemaphoreTake(g_lock, portMAX_DELAY); + if (wolfip_bsd_lock(sockfd, internal_fd) < 0) { + return -1; + } left = wolfip_bsd_remaining(entry->tx_timeout, start); ret = wolfIP_sock_connect(g_ipstack, entry->internal_fd, addr, addrlen); if (ret == 0) { @@ -443,9 +497,9 @@ int connect(int sockfd, const struct wolfIP_sockaddr *addr, socklen_t addrlen) } wolfip_bsd_prepare_wait_locked(entry, (uint16_t)(CB_EVENT_WRITABLE | CB_EVENT_CLOSED)); - xSemaphoreGive(g_lock); - if (wolfip_bsd_wait_unlocked(entry, left) < 0) { - wolfip_bsd_set_error(WOLFIP_EINPROGRESS); + ret = wolfip_bsd_wait(entry, sockfd, left); + if (ret < 0) { + wolfip_bsd_set_error((ret == -WOLFIP_EAGAIN) ? WOLFIP_EINPROGRESS : ret); return -1; } } @@ -454,6 +508,7 @@ int connect(int sockfd, const struct wolfIP_sockaddr *addr, socklen_t addrlen) int send(int sockfd, const void *buf, size_t len, int flags) { int ret; + int internal_fd; wolfip_bsd_fd_entry *entry; TickType_t start; TickType_t left; @@ -462,10 +517,13 @@ int send(int sockfd, const void *buf, size_t len, int flags) return -1; } entry = &g_fds[sockfd]; + internal_fd = entry->internal_fd; start = xTaskGetTickCount(); for (;;) { - xSemaphoreTake(g_lock, portMAX_DELAY); + if (wolfip_bsd_lock(sockfd, internal_fd) < 0) { + return -1; + } left = wolfip_bsd_remaining(entry->tx_timeout, start); ret = wolfIP_sock_send(g_ipstack, entry->internal_fd, buf, len, flags); if (ret >= 0) { @@ -479,9 +537,9 @@ int send(int sockfd, const void *buf, size_t len, int flags) } wolfip_bsd_prepare_wait_locked(entry, (uint16_t)(CB_EVENT_WRITABLE | CB_EVENT_CLOSED)); - xSemaphoreGive(g_lock); - if (wolfip_bsd_wait_unlocked(entry, left) < 0) { - wolfip_bsd_set_error(WOLFIP_EAGAIN); + ret = wolfip_bsd_wait(entry, sockfd, left); + if (ret < 0) { + wolfip_bsd_set_error(ret); return -1; } } @@ -491,6 +549,7 @@ int sendto(int sockfd, const void *buf, size_t len, int flags, const struct wolfIP_sockaddr *dest_addr, socklen_t addrlen) { int ret; + int internal_fd; wolfip_bsd_fd_entry *entry; TickType_t start; TickType_t left; @@ -499,10 +558,13 @@ int sendto(int sockfd, const void *buf, size_t len, int flags, return -1; } entry = &g_fds[sockfd]; + internal_fd = entry->internal_fd; start = xTaskGetTickCount(); for (;;) { - xSemaphoreTake(g_lock, portMAX_DELAY); + if (wolfip_bsd_lock(sockfd, internal_fd) < 0) { + return -1; + } left = wolfip_bsd_remaining(entry->tx_timeout, start); ret = wolfIP_sock_sendto(g_ipstack, entry->internal_fd, buf, len, flags, dest_addr, addrlen); if (ret >= 0) { @@ -516,9 +578,9 @@ int sendto(int sockfd, const void *buf, size_t len, int flags, } wolfip_bsd_prepare_wait_locked(entry, (uint16_t)(CB_EVENT_WRITABLE | CB_EVENT_CLOSED)); - xSemaphoreGive(g_lock); - if (wolfip_bsd_wait_unlocked(entry, left) < 0) { - wolfip_bsd_set_error(WOLFIP_EAGAIN); + ret = wolfip_bsd_wait(entry, sockfd, left); + if (ret < 0) { + wolfip_bsd_set_error(ret); return -1; } } @@ -527,6 +589,7 @@ int sendto(int sockfd, const void *buf, size_t len, int flags, int recv(int sockfd, void *buf, size_t len, int flags) { int ret; + int internal_fd; wolfip_bsd_fd_entry *entry; TickType_t start; TickType_t left; @@ -535,10 +598,13 @@ int recv(int sockfd, void *buf, size_t len, int flags) return -1; } entry = &g_fds[sockfd]; + internal_fd = entry->internal_fd; start = xTaskGetTickCount(); for (;;) { - xSemaphoreTake(g_lock, portMAX_DELAY); + if (wolfip_bsd_lock(sockfd, internal_fd) < 0) { + return -1; + } left = wolfip_bsd_remaining(entry->rx_timeout, start); ret = wolfIP_sock_recv(g_ipstack, entry->internal_fd, buf, len, flags); if (ret >= 0) { @@ -548,9 +614,9 @@ int recv(int sockfd, void *buf, size_t len, int flags) if (wolfip_bsd_tcp_recv_should_wait_locked(entry->internal_fd, ret)) { wolfip_bsd_prepare_wait_locked(entry, (uint16_t)(CB_EVENT_READABLE | CB_EVENT_WRITABLE | CB_EVENT_CLOSED)); - xSemaphoreGive(g_lock); - if (wolfip_bsd_wait_unlocked(entry, left) < 0) { - wolfip_bsd_set_error(WOLFIP_EAGAIN); + ret = wolfip_bsd_wait(entry, sockfd, left); + if (ret < 0) { + wolfip_bsd_set_error(ret); return -1; } continue; @@ -562,9 +628,9 @@ int recv(int sockfd, void *buf, size_t len, int flags) } wolfip_bsd_prepare_wait_locked(entry, (uint16_t)(CB_EVENT_READABLE | CB_EVENT_CLOSED)); - xSemaphoreGive(g_lock); - if (wolfip_bsd_wait_unlocked(entry, left) < 0) { - wolfip_bsd_set_error(WOLFIP_EAGAIN); + ret = wolfip_bsd_wait(entry, sockfd, left); + if (ret < 0) { + wolfip_bsd_set_error(ret); return -1; } } @@ -574,6 +640,7 @@ int recvfrom(int sockfd, void *buf, size_t len, int flags, struct wolfIP_sockaddr *src_addr, socklen_t *addrlen) { int ret; + int internal_fd; wolfip_bsd_fd_entry *entry; TickType_t start; TickType_t left; @@ -582,10 +649,13 @@ int recvfrom(int sockfd, void *buf, size_t len, int flags, return -1; } entry = &g_fds[sockfd]; + internal_fd = entry->internal_fd; start = xTaskGetTickCount(); for (;;) { - xSemaphoreTake(g_lock, portMAX_DELAY); + if (wolfip_bsd_lock(sockfd, internal_fd) < 0) { + return -1; + } left = wolfip_bsd_remaining(entry->rx_timeout, start); ret = wolfIP_sock_recvfrom(g_ipstack, entry->internal_fd, buf, len, flags, src_addr, addrlen); if (ret >= 0) { @@ -595,9 +665,9 @@ int recvfrom(int sockfd, void *buf, size_t len, int flags, if (wolfip_bsd_tcp_recv_should_wait_locked(entry->internal_fd, ret)) { wolfip_bsd_prepare_wait_locked(entry, (uint16_t)(CB_EVENT_READABLE | CB_EVENT_WRITABLE | CB_EVENT_CLOSED)); - xSemaphoreGive(g_lock); - if (wolfip_bsd_wait_unlocked(entry, left) < 0) { - wolfip_bsd_set_error(WOLFIP_EAGAIN); + ret = wolfip_bsd_wait(entry, sockfd, left); + if (ret < 0) { + wolfip_bsd_set_error(ret); return -1; } continue; @@ -609,9 +679,9 @@ int recvfrom(int sockfd, void *buf, size_t len, int flags, } wolfip_bsd_prepare_wait_locked(entry, (uint16_t)(CB_EVENT_READABLE | CB_EVENT_CLOSED)); - xSemaphoreGive(g_lock); - if (wolfip_bsd_wait_unlocked(entry, left) < 0) { - wolfip_bsd_set_error(WOLFIP_EAGAIN); + ret = wolfip_bsd_wait(entry, sockfd, left); + if (ret < 0) { + wolfip_bsd_set_error(ret); return -1; } } @@ -655,9 +725,12 @@ int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen) { int ret; + int internal_fd; + if (!wolfip_bsd_fd_valid(sockfd)) { return -1; } + internal_fd = g_fds[sockfd].internal_fd; if ((level == WOLFIP_SOL_SOCKET) && ((optname == WOLFIP_SO_RCVTIMEO) || (optname == WOLFIP_SO_SNDTIMEO))) { struct wolfIP_timeval tv; @@ -671,10 +744,7 @@ int setsockopt(int sockfd, int level, int optname, wolfip_bsd_set_error(WOLFIP_EDOM); return -1; } - xSemaphoreTake(g_lock, portMAX_DELAY); - if (!g_fds[sockfd].in_use) { - xSemaphoreGive(g_lock); - wolfip_bsd_set_error(WOLFIP_EINVAL); + if (wolfip_bsd_lock(sockfd, internal_fd) < 0) { return -1; } if (optname == WOLFIP_SO_RCVTIMEO) { @@ -686,8 +756,10 @@ int setsockopt(int sockfd, int level, int optname, xSemaphoreGive(g_lock); return 0; } - xSemaphoreTake(g_lock, portMAX_DELAY); - ret = wolfIP_sock_setsockopt(g_ipstack, g_fds[sockfd].internal_fd, + if (wolfip_bsd_lock(sockfd, internal_fd) < 0) { + return -1; + } + ret = wolfIP_sock_setsockopt(g_ipstack, internal_fd, level, optname, optval, optlen); xSemaphoreGive(g_lock); if (ret < 0) { @@ -701,9 +773,12 @@ int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen) { int ret; + int internal_fd; + if (!wolfip_bsd_fd_valid(sockfd)) { return -1; } + internal_fd = g_fds[sockfd].internal_fd; if ((level == WOLFIP_SOL_SOCKET) && ((optname == WOLFIP_SO_RCVTIMEO) || (optname == WOLFIP_SO_SNDTIMEO))) { struct wolfIP_timeval tv; @@ -714,10 +789,7 @@ int getsockopt(int sockfd, int level, int optname, wolfip_bsd_set_error(WOLFIP_EINVAL); return -1; } - xSemaphoreTake(g_lock, portMAX_DELAY); - if (!g_fds[sockfd].in_use) { - xSemaphoreGive(g_lock); - wolfip_bsd_set_error(WOLFIP_EINVAL); + if (wolfip_bsd_lock(sockfd, internal_fd) < 0) { return -1; } ticks = (optname == WOLFIP_SO_RCVTIMEO) ? g_fds[sockfd].rx_timeout : @@ -728,8 +800,10 @@ int getsockopt(int sockfd, int level, int optname, *optlen = (socklen_t)sizeof(tv); return 0; } - xSemaphoreTake(g_lock, portMAX_DELAY); - ret = wolfIP_sock_getsockopt(g_ipstack, g_fds[sockfd].internal_fd, + if (wolfip_bsd_lock(sockfd, internal_fd) < 0) { + return -1; + } + ret = wolfIP_sock_getsockopt(g_ipstack, internal_fd, level, optname, optval, optlen); xSemaphoreGive(g_lock); if (ret < 0) { @@ -742,11 +816,16 @@ int getsockopt(int sockfd, int level, int optname, int getsockname(int sockfd, struct wolfIP_sockaddr *addr, socklen_t *addrlen) { int ret; + int internal_fd; + if (!wolfip_bsd_fd_valid(sockfd)) { return -1; } - xSemaphoreTake(g_lock, portMAX_DELAY); - ret = wolfIP_sock_getsockname(g_ipstack, g_fds[sockfd].internal_fd, addr, addrlen); + internal_fd = g_fds[sockfd].internal_fd; + if (wolfip_bsd_lock(sockfd, internal_fd) < 0) { + return -1; + } + ret = wolfIP_sock_getsockname(g_ipstack, internal_fd, addr, addrlen); xSemaphoreGive(g_lock); if (ret < 0) { wolfip_bsd_set_error(ret); @@ -758,11 +837,16 @@ int getsockname(int sockfd, struct wolfIP_sockaddr *addr, socklen_t *addrlen) int getpeername(int sockfd, struct wolfIP_sockaddr *addr, socklen_t *addrlen) { int ret; + int internal_fd; + if (!wolfip_bsd_fd_valid(sockfd)) { return -1; } - xSemaphoreTake(g_lock, portMAX_DELAY); - ret = wolfIP_sock_getpeername(g_ipstack, g_fds[sockfd].internal_fd, addr, addrlen); + internal_fd = g_fds[sockfd].internal_fd; + if (wolfip_bsd_lock(sockfd, internal_fd) < 0) { + return -1; + } + ret = wolfIP_sock_getpeername(g_ipstack, internal_fd, addr, addrlen); xSemaphoreGive(g_lock); if (ret < 0) { wolfip_bsd_set_error(ret); @@ -787,10 +871,7 @@ int close(int sockfd) start = xTaskGetTickCount(); for (;;) { - xSemaphoreTake(g_lock, portMAX_DELAY); - if (!entry->in_use || (entry->internal_fd != internal_fd)) { - xSemaphoreGive(g_lock); - wolfip_bsd_set_error(WOLFIP_EBADF); + if (wolfip_bsd_lock(sockfd, internal_fd) < 0) { return -1; } left = wolfip_bsd_remaining(pdMS_TO_TICKS(WOLFIP_BSD_CLOSE_LINGER_MS), start); @@ -799,15 +880,23 @@ int close(int sockfd) if ((ret == -WOLFIP_EAGAIN) && (left > 0)) { wolfip_bsd_prepare_wait_locked(entry, (uint16_t)(CB_EVENT_WRITABLE | CB_EVENT_CLOSED)); - xSemaphoreGive(g_lock); - (void)wolfip_bsd_wait_unlocked(entry, left); + if (wolfip_bsd_wait(entry, sockfd, left) == -WOLFIP_EBADF) { + wolfip_bsd_set_error(WOLFIP_EBADF); + return -1; + } continue; } if (ret == -WOLFIP_EAGAIN) { (void)wolfIP_sock_abort(g_ipstack, internal_fd); } wolfIP_register_callback(g_ipstack, internal_fd, NULL, NULL); - wolfip_bsd_fd_free(sockfd); + if (entry->waiters > 0) { + entry->closing = 1; + (void)xSemaphoreGive(entry->ready_sem); + } + else { + wolfip_bsd_fd_free(sockfd); + } xSemaphoreGive(g_lock); /* -EBADF: the stack already released the socket. */ if ((ret < 0) && (ret != -WOLFIP_EAGAIN) && (ret != -WOLFIP_EBADF)) { diff --git a/src/test/test_freertos_bsd_semantics.c b/src/test/test_freertos_bsd_semantics.c index 5a96bd86..ad996d64 100644 --- a/src/test/test_freertos_bsd_semantics.c +++ b/src/test/test_freertos_bsd_semantics.c @@ -34,8 +34,16 @@ struct MockSemaphore { int count; + int deleted; }; +/* Deleted semaphores are kept so a later use is counted, not undefined. */ +static int use_after_delete; +static SemaphoreHandle_t mock_mutex; +/* Runs, in place of another task, at the lock_countdown-th take of g_lock. */ +static void (*lock_hook)(void); +static int lock_countdown; + #define LISTEN_FD (MARK_TCP_SOCKET | 0) #define CHILD_FD (MARK_TCP_SOCKET | 1) @@ -60,6 +68,7 @@ SemaphoreHandle_t xSemaphoreCreateMutex(void) if (sem != NULL) sem->count = 1; + mock_mutex = sem; return sem; } @@ -68,12 +77,16 @@ BaseType_t xSemaphoreTake(SemaphoreHandle_t sem, TickType_t ticks) { if (sem == NULL) return pdFALSE; + if (sem == mock_mutex && lock_countdown > 0 && --lock_countdown == 0) + lock_hook(); if (sem->count == 0 && ticks != 0 && wait_hook != NULL) { void (*hook)(void) = wait_hook; wait_hook = NULL; hook(); } + if (sem->deleted) + use_after_delete++; if (sem->count > 0) { sem->count--; return pdTRUE; @@ -88,13 +101,15 @@ BaseType_t xSemaphoreGive(SemaphoreHandle_t sem) { if (sem == NULL) return pdFALSE; + if (sem->deleted) + use_after_delete++; sem->count++; return pdTRUE; } void vSemaphoreDelete(SemaphoreHandle_t sem) { - free(sem); + sem->deleted = 1; } BaseType_t xTaskCreate(TaskFunction_t task, const char *name, @@ -437,11 +452,16 @@ static void child_established(void) registered_cb(0, CB_EVENT_READABLE, registered_arg); } -/* Another task closes the listener and a third reuses its descriptor. */ +/* Another task closes the listener; its slot is not reused while accept() + * still waits on it. */ static void listener_replaced(void) { + int fd; + CHECK(close(hook_lfd) == 0); - CHECK(socket(AF_INET, SOCK_STREAM, 0) == hook_lfd); + fd = socket(AF_INET, SOCK_STREAM, 0); + CHECK(fd >= 0 && fd != hook_lfd); + CHECK(close(fd) == 0); } static void test_accept(void) @@ -479,7 +499,7 @@ static void test_accept(void) CHECK(accept(lfd, NULL, NULL) == -1); CHECK(socket_last_error() == WOLFIP_EBADF); CHECK(accept_calls == 1); - CHECK(close(lfd) == 0); + CHECK(close(lfd) == -1); } /* Room for the FIN frees up while close() waits. */ @@ -501,7 +521,9 @@ static void test_close(void) CHECK(close(fd) == 0); CHECK(close_calls == 1); CHECK(last_wait_ticks == 1234); + wolfip_bsd_set_error(WOLFIP_EAGAIN); CHECK(close(fd) == -1); + CHECK(socket_last_error() == WOLFIP_EBADF); /* A full TX FIFO: retry as room frees up. */ script_reset(); @@ -536,6 +558,110 @@ static void test_close(void) CHECK(close(fd) == -1); } +static int blocked_fd; +static int other_waiter; + +/* Another task closes the descriptor this one is blocked on. */ +static void close_under_waiter(void) +{ + int fd; + + if (other_waiter) + g_fds[blocked_fd].waiters++; + CHECK(close(blocked_fd) == 0); + /* The slot stays taken until every waiter has left it. */ + fd = socket(AF_INET, SOCK_STREAM, 0); + CHECK(fd >= 0 && fd != blocked_fd); + CHECK(close(fd) == 0); +} + +static void test_close_while_blocked(void) +{ + char buf[8]; + + script_reset(); + use_after_delete = 0; + recv_ret = -WOLFIP_EAGAIN; + blocked_fd = socket(AF_INET, SOCK_STREAM, 0); + other_waiter = 0; + wait_hook = close_under_waiter; + CHECK(recv(blocked_fd, buf, sizeof(buf), 0) == -1); + CHECK(socket_last_error() == WOLFIP_EBADF); + CHECK(use_after_delete == 0); + CHECK(!g_fds[blocked_fd].in_use); + CHECK(socket(AF_INET, SOCK_STREAM, 0) == blocked_fd); + + /* With a second waiter the first one passes the wake on, and the + * second one frees the slot. */ + other_waiter = 1; + wait_hook = close_under_waiter; + CHECK(recv(blocked_fd, buf, sizeof(buf), 0) == -1); + CHECK(g_fds[blocked_fd].in_use && g_fds[blocked_fd].closing); + /* A closing descriptor refuses new calls without blocking. */ + last_wait_ticks = 1234; + CHECK(recv(blocked_fd, buf, sizeof(buf), 0) == -1); + CHECK(close(blocked_fd) == -1); + CHECK(last_wait_ticks == 1234); + xSemaphoreTake(g_lock, portMAX_DELAY); + g_fds[blocked_fd].waiters--; + CHECK(wolfip_bsd_wait(&g_fds[blocked_fd], blocked_fd, 0) == -WOLFIP_EBADF); + CHECK(!g_fds[blocked_fd].in_use); + CHECK(use_after_delete == 0); + CHECK(recv(blocked_fd, buf, sizeof(buf), 0) == -1); +} + +static int reissued_fd; + +/* Another task closes the descriptor and a third gets its slot back. */ +static void close_and_reissue(void) +{ + CHECK(close(blocked_fd) == 0); + reissued_fd = socket(AF_INET, SOCK_STREAM, 0); + CHECK(reissued_fd == blocked_fd); +} + +/* Data arrives; the close lands before the woken task relocks. */ +static void wake_then_reissue(void) +{ + registered_cb(0, CB_EVENT_READABLE, registered_arg); + lock_hook = close_and_reissue; + lock_countdown = 2; +} + +static void test_reissue_between_calls(void) +{ + struct wolfIP_timeval tv; + char buf[8]; + + script_reset(); + recv_ret = -WOLFIP_EAGAIN; + blocked_fd = socket(AF_INET, SOCK_STREAM, 0); + wait_hook = wake_then_reissue; + CHECK(recv(blocked_fd, buf, sizeof(buf), 0) == -1); + CHECK(socket_last_error() == WOLFIP_EBADF); + CHECK(close(reissued_fd) == 0); + + /* The same for an option set between the validity check and the lock. */ + blocked_fd = socket(AF_INET, SOCK_STREAM, 0); + lock_hook = close_and_reissue; + lock_countdown = 1; + tv.tv_sec = 1; + tv.tv_usec = 0; + CHECK(setsockopt(blocked_fd, WOLFIP_SOL_SOCKET, WOLFIP_SO_RCVTIMEO, &tv, + sizeof(tv)) == -1); + CHECK(socket_last_error() == WOLFIP_EBADF); + CHECK(g_fds[reissued_fd].rx_timeout == portMAX_DELAY); + CHECK(close(reissued_fd) == 0); + + /* And for the calls that never block. */ + blocked_fd = socket(AF_INET, SOCK_STREAM, 0); + lock_hook = close_and_reissue; + lock_countdown = 1; + CHECK(listen(blocked_fd, 1) == -1); + CHECK(socket_last_error() == WOLFIP_EBADF); + CHECK(close(reissued_fd) == 0); +} + int main(void) { struct wolfIP stack; @@ -549,6 +675,8 @@ int main(void) test_timeouts(); test_accept(); test_close(); + test_close_while_blocked(); + test_reissue_between_calls(); if (failures != 0) { printf("test_freertos_bsd_semantics: %d FAILED\n", failures); From 6e9e40d07d0824fd73567e91a68519181c740c8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Thu, 1 Oct 2026 14:56:52 +0200 Subject: [PATCH 10/10] core: compare poll deadlines in the wrapped tick domain wolfIP_poll_by() kept the earliest deadline with a raw 64-bit compare, while tick_expired() and the wait wolfIP_poll() returns use the signed difference of the low 32 bits. The two disagree once pending timers hold 32-bit values in a 64-bit tick domain: timers_heap_rebase() truncates them after the clock steps back (a POSIX epoch-ms now), and timers armed before the first poll start from last_tick 0. Such a timer is small as a raw value, so it replaced the deadline set by wolfIP_poll_by(s, now) for pending events, loopback frames or TX backpressure, and wolfIP_poll() returned the timer's whole remaining time instead of 0. That can exceed WOLFIP_POLL_MAX_WAIT_MS, so the POSIX stack thread slept with work pending. Compare in the same wrapped domain as tick_expired(). Every deadline then orders the way the timers fire, and none can lie beyond the now + WOLFIP_POLL_MAX_WAIT_MS that each poll starts from. Reported in the review of #180. The new tests step a 64-bit clock back under a pending timer and check that the poll still returns at most WOLFIP_POLL_MAX_WAIT_MS, and that a rebased timer does not override a deadline at now. --- src/test/unit/unit.c | 2 ++ src/test/unit/unit_tests_poll_dispatcher.c | 32 ++++++++++++++++++++++ src/wolfip.c | 5 ++-- 3 files changed, 37 insertions(+), 2 deletions(-) diff --git a/src/test/unit/unit.c b/src/test/unit/unit.c index ba204aac..ee896ec3 100644 --- a/src/test/unit/unit.c +++ b/src/test/unit/unit.c @@ -1568,6 +1568,8 @@ Suite *wolf_suite(void) #if WOLFIP_ENABLE_LOOPBACK tcase_add_test(tc_core, test_poll_returns_zero_with_loopback_frame_queued); #endif + tcase_add_test(tc_core, test_poll_rebased_timer_stays_within_max_wait); + tcase_add_test(tc_core, test_poll_by_rebased_deadline_keeps_earlier_one); tcase_add_test(tc_core, test_poll_returns_zero_while_flush_events_undelivered); tcase_add_test(tc_core, test_wake_cb_on_socket_tx); tcase_add_test(tc_core, test_wake_cb_on_register_with_pending_events); diff --git a/src/test/unit/unit_tests_poll_dispatcher.c b/src/test/unit/unit_tests_poll_dispatcher.c index 74a5124e..e40060ae 100644 --- a/src/test/unit/unit_tests_poll_dispatcher.c +++ b/src/test/unit/unit_tests_poll_dispatcher.c @@ -1893,6 +1893,38 @@ START_TEST(test_poll_returns_zero_with_loopback_frame_queued) END_TEST #endif +START_TEST(test_poll_rebased_timer_stays_within_max_wait) +{ + struct wolfIP s; + struct wolfIP_timer tmr = {0}; + const uint64_t base = 0x100000000ULL; + + wolfIP_init(&s); + mock_link_init(&s); + (void)wolfIP_poll(&s, base + 20000); + tmr.expires = base + 25000; + tmr.cb = test_timer_cb; + ck_assert_uint_ne(timers_binheap_insert(&s.timers, tmr), 0U); + + /* The clock steps back: the timer is rebased to a 32-bit value. */ + ck_assert_int_eq(wolfIP_poll(&s, base + 10000), WOLFIP_POLL_MAX_WAIT_MS); +} +END_TEST + +START_TEST(test_poll_by_rebased_deadline_keeps_earlier_one) +{ + struct wolfIP s; + const uint64_t now = 0x100000000ULL + 10000; + + wolfIP_init(&s); + s.poll_next_at = now + WOLFIP_POLL_MAX_WAIT_MS; + wolfIP_poll_by(&s, now); + /* A rebased timer 15 s ahead is smaller as a raw 64-bit value. */ + wolfIP_poll_by(&s, 25000); + ck_assert_uint_eq(s.poll_next_at, now); +} +END_TEST + START_TEST(test_poll_returns_zero_while_flush_events_undelivered) { struct wolfIP s; diff --git a/src/wolfip.c b/src/wolfip.c index aa8e4e81..952d93ca 100644 --- a/src/wolfip.c +++ b/src/wolfip.c @@ -1841,10 +1841,11 @@ static void wolfIP_wake(struct wolfIP *s) s->wake_cb(s->wake_arg); } -/* Lowers the deadline the current wolfIP_poll() returns. */ +/* Lowers the deadline the current wolfIP_poll() returns, comparing in the + * 32-bit domain tick_expired() uses, since rebased timers are 32-bit values. */ static void wolfIP_poll_by(struct wolfIP *s, uint64_t when) { - if (when < s->poll_next_at) + if ((int32_t)((uint32_t)when - (uint32_t)s->poll_next_at) < 0) s->poll_next_at = when; }