diff --git a/src/test/unit/unit.c b/src/test/unit/unit.c index 5a10c585..70b5f099 100644 --- a/src/test/unit/unit.c +++ b/src/test/unit/unit.c @@ -615,6 +615,10 @@ Suite *wolf_suite(void) tcase_add_test(tc_utils, test_tcp_listener_sustained_lock_one_syn_per_window); tcase_add_test(tc_utils, test_tcp_listener_rst_from_holding_4tuple_releases_lock); tcase_add_test(tc_utils, test_tcp_listener_preaccept_accept_reverts_port); + tcase_add_test(tc_utils, test_tcp_listener_preaccept_established_no_data_is_readable); + tcase_add_test(tc_utils, test_tcp_listener_preaccept_peer_fin_hands_off_close_wait); + tcase_add_test(tc_utils, test_tcp_listener_preaccept_handoff_reemits_queued_ack); + 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_revert_drains_connection_state); tcase_add_test(tc_utils, test_tcp_listener_preaccept_close_rto_retransmits_finack); @@ -1192,9 +1196,11 @@ Suite *wolf_suite(void) tcase_add_test(tc_core, test_register_callback_packet_oor_ignored); #endif tcase_add_test(tc_core, test_sock_can_read_tcp_established_empty); + tcase_add_test(tc_core, test_sock_can_read_tcp_listener_syn_rcvd_returns_one); tcase_add_test(tc_core, test_sock_can_read_tcp_close_wait_returns_one); tcase_add_test(tc_core, test_sock_can_read_tcp_invalid_fd); tcase_add_test(tc_core, test_sock_can_write_tcp_syn_sent_returns_zero); + tcase_add_test(tc_core, test_sock_can_write_tcp_syn_rcvd_returns_zero); tcase_add_test(tc_core, test_sock_can_write_tcp_established_with_space); tcase_add_test(tc_core, test_sock_can_write_tcp_closed_returns_one); tcase_add_test(tc_core, test_sock_can_write_tcp_close_wait_full_fifo_returns_zero); @@ -1237,6 +1243,7 @@ Suite *wolf_suite(void) #endif tcase_add_test(tc_core, test_sock_sendto_tcp_established_sends_data); tcase_add_test(tc_core, test_sock_sendto_tcp_invalid_fd); + tcase_add_test(tc_core, test_sock_sendto_tcp_syn_rcvd_returns_eagain); tcase_add_test(tc_core, test_sock_sendto_tcp_close_wait_sends_data); #if WOLFIP_RAWSOCKETS tcase_add_test(tc_core, test_sock_sendto_raw_null_dest_uses_stored_remote_ip); diff --git a/src/test/unit/unit_tests_socket_api_arms.c b/src/test/unit/unit_tests_socket_api_arms.c index 2cf5f980..720fea67 100644 --- a/src/test/unit/unit_tests_socket_api_arms.c +++ b/src/test/unit/unit_tests_socket_api_arms.c @@ -125,6 +125,26 @@ START_TEST(test_sock_can_read_tcp_established_empty) } END_TEST +START_TEST(test_sock_can_read_tcp_listener_syn_rcvd_returns_one) +{ + struct wolfIP s; + int sd; + struct tsocket *ts; + + wolfIP_init(&s); + mock_link_init(&s); + sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_STREAM, 0); + ck_assert_int_ge(sd, 0); + ts = &s.tcpsockets[SOCKET_UNMARK(sd)]; + ts->sock.tcp.state = TCP_SYN_RCVD; + ts->sock.tcp.is_listener = 1; + + /* A listening socket with a pending handshake must wake select/poll so + * the application calls accept() before the final ACK changes state. */ + ck_assert_int_eq(wolfIP_sock_can_read(&s, sd), 1); +} +END_TEST + START_TEST(test_sock_can_read_tcp_close_wait_returns_one) { struct wolfIP s; @@ -170,6 +190,23 @@ START_TEST(test_sock_can_write_tcp_syn_sent_returns_zero) } END_TEST +START_TEST(test_sock_can_write_tcp_syn_rcvd_returns_zero) +{ + struct wolfIP s; + int sd; + struct tsocket *ts; + + wolfIP_init(&s); + mock_link_init(&s); + sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_STREAM, 0); + ck_assert_int_ge(sd, 0); + ts = &s.tcpsockets[SOCKET_UNMARK(sd)]; + ts->sock.tcp.state = TCP_SYN_RCVD; + + ck_assert_int_eq(wolfIP_sock_can_write(&s, sd), 0); +} +END_TEST + START_TEST(test_sock_can_write_tcp_established_with_space) { struct wolfIP s; @@ -834,6 +871,25 @@ START_TEST(test_sock_sendto_tcp_invalid_fd) } END_TEST +START_TEST(test_sock_sendto_tcp_syn_rcvd_returns_eagain) +{ + struct wolfIP s; + int sd; + struct tsocket *ts; + uint8_t buf[8] = {0}; + + wolfIP_init(&s); + mock_link_init(&s); + sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_STREAM, 0); + ck_assert_int_ge(sd, 0); + ts = &s.tcpsockets[SOCKET_UNMARK(sd)]; + ts->sock.tcp.state = TCP_SYN_RCVD; + + ck_assert_int_eq(wolfIP_sock_sendto(&s, sd, buf, sizeof(buf), 0, NULL, 0), + -WOLFIP_EAGAIN); +} +END_TEST + START_TEST(test_sock_sendto_tcp_close_wait_sends_data) { struct wolfIP s; diff --git a/src/test/unit/unit_tests_tcp_flow.c b/src/test/unit/unit_tests_tcp_flow.c index 6838e9c4..dd7e99ef 100644 --- a/src/test/unit/unit_tests_tcp_flow.c +++ b/src/test/unit/unit_tests_tcp_flow.c @@ -5554,10 +5554,10 @@ START_TEST(test_tcp_listener_rst_from_holding_4tuple_releases_lock) } END_TEST -/* The handshake completes before accept(): the listener is ESTABLISHED - * with the pre-accept fast-fail timer armed. accept() can no longer - * clone the connection, but it reverts the port to LISTEN so it is not - * pinned, and new clients are served again. */ +/* The handshake and first application bytes may complete in the same poll + * step before the application gets scheduled to accept(). accept() must + * preserve that established connection and its receive queue while restoring + * the original descriptor as a fresh listener. */ START_TEST(test_tcp_listener_preaccept_accept_reverts_port) { struct wolfIP s; @@ -5566,6 +5566,9 @@ START_TEST(test_tcp_listener_preaccept_accept_reverts_port) struct wolfIP_sockaddr_in peer; socklen_t peer_len = sizeof(peer); const struct wolfIP_tcp_seg *out; + static const char banner[] = "SSH-2.0-test\r\n"; + char got[sizeof(banner)]; + int accepted; wolfIP_init(&s); mock_link_init(&s); @@ -5583,12 +5586,24 @@ START_TEST(test_tcp_listener_preaccept_accept_reverts_port) /* The un-accepted established listener is time-boxed. */ ck_assert_int_eq(lsn->sock.tcp.preaccept_timeout_active, 1); ck_assert_uint_ne(lsn->sock.tcp.tmr_rto, NO_TIMER); + ck_assert_int_eq(queue_insert(&lsn->sock.tcp.rxbuf, (void *)banner, + lsn->sock.tcp.ack, + (uint32_t)sizeof(banner)), 0); - /* accept() cannot clone an ESTABLISHED connection: it fails, but it - * reverts the port to LISTEN instead of leaving it pinned. */ + /* A late accept succeeds and hands the queued banner to the child. */ memset(&peer, 0, sizeof(peer)); - ck_assert_int_eq(wolfIP_sock_accept(&s, fd, - (struct wolfIP_sockaddr *)&peer, &peer_len), -1); + accepted = wolfIP_sock_accept(&s, fd, + (struct wolfIP_sockaddr *)&peer, &peer_len); + ck_assert_int_ge(accepted, 0); + memset(got, 0, sizeof(got)); + ck_assert_int_eq(wolfIP_sock_recv(&s, accepted, got, sizeof(got), 0), + (int)sizeof(banner)); + ck_assert_mem_eq(got, banner, sizeof(banner)); + /* The accepted child is a full-duplex established stream. The SSH + * server writes its identification before its first read. */ + ck_assert_int_eq(wolfIP_sock_send(&s, accepted, banner, + sizeof(banner), 0), + (int)sizeof(banner)); ck_assert_int_eq(lsn->sock.tcp.state, TCP_LISTEN); ck_assert_int_eq(lsn->sock.tcp.preaccept_timeout_active, 0); ck_assert_uint_eq(lsn->sock.tcp.tmr_rto, NO_TIMER); @@ -5603,7 +5618,209 @@ START_TEST(test_tcp_listener_preaccept_accept_reverts_port) out = llk_last_tcp(); ck_assert_ptr_nonnull(out); ck_assert(out->flags & (TCP_FLAG_SYN | TCP_FLAG_ACK)); - ck_assert_uint_eq(ee16(out->dst_port), 42000); +} +END_TEST + +/* A peer that completes the handshake and then waits for the server to + * speak queues no data, so rxbuf stays empty. A poll()/select() driver + * learns about the pending connection only through can_read(), which must + * still report the listener readable, or the application never accepts and + * the pre-accept timer discards a healthy connection. The wildcard bind + * also checks that the child inherits the address the SYN arrived on. */ +START_TEST(test_tcp_listener_preaccept_established_no_data_is_readable) +{ + struct wolfIP s; + int fd; + int accepted; + struct tsocket *lsn; + struct tsocket *child; + struct wolfIP_sockaddr_in sin; + struct wolfIP_sockaddr_in peer; + socklen_t peer_len = sizeof(peer); + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, LLK_LOCAL_IP, LLK_NET_MASK, 0); + + fd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_STREAM, WI_IPPROTO_TCP); + ck_assert_int_gt(fd, 0); + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = ee16((uint16_t)LLK_LISTEN_PORT); + sin.sin_addr.s_addr = ee32(IPADDR_ANY); + ck_assert_int_eq(wolfIP_sock_bind(&s, fd, (struct wolfIP_sockaddr *)&sin, + sizeof(sin)), 0); + ck_assert_int_eq(wolfIP_sock_listen(&s, fd, 16), 0); + lsn = &s.tcpsockets[SOCKET_UNMARK(fd)]; + + llk_keep_arp_fresh(&s, LLK_ATT_IP); + llk_attacker_syn(&s, LLK_ATT_IP, 41000, 1, 0); + ck_assert_int_eq(lsn->sock.tcp.state, TCP_SYN_RCVD); + ck_assert_int_eq(wolfIP_sock_can_read(&s, fd), 1); + + llk_complete_handshake(&s, lsn, LLK_ATT_IP, 41000, 1); + ck_assert_int_eq(lsn->sock.tcp.state, TCP_ESTABLISHED); + ck_assert_uint_eq(queue_len(&lsn->sock.tcp.rxbuf), 0); + ck_assert_int_eq(wolfIP_sock_can_read(&s, fd), 1); + + memset(&peer, 0, sizeof(peer)); + accepted = wolfIP_sock_accept(&s, fd, + (struct wolfIP_sockaddr *)&peer, &peer_len); + ck_assert_int_ge(accepted, 0); + child = &s.tcpsockets[SOCKET_UNMARK(accepted)]; + ck_assert_int_eq(child->sock.tcp.state, TCP_ESTABLISHED); + ck_assert_int_eq(child->sock.tcp.is_listener, 0); + /* The listener's timers were cancelled by the revert: the child must + * not keep a copy of their ids. */ + ck_assert_uint_eq(child->sock.tcp.tmr_rto, NO_TIMER); + ck_assert_uint_eq(child->sock.tcp.tmr_persist, NO_TIMER); + ck_assert_uint_eq(child->bound_local_ip, LLK_LOCAL_IP); + /* No queued data: the child must not inherit the listener's + * "pending accept" readable flag. */ + ck_assert_uint_eq(child->events, CB_EVENT_WRITABLE); + ck_assert_int_eq(lsn->sock.tcp.state, TCP_LISTEN); + ck_assert_int_eq(wolfIP_sock_can_read(&s, fd), 0); +} +END_TEST + +/* The peer closes before the application accepts: the listener sits in + * CLOSE_WAIT and can_read() reports it readable, so accept() must hand the + * connection over rather than fail - a failing accept() on a permanently + * readable descriptor spins the poll loop until the pre-accept timeout. */ +START_TEST(test_tcp_listener_preaccept_peer_fin_hands_off_close_wait) +{ + struct wolfIP s; + int fd; + int accepted; + struct tsocket *lsn; + struct tsocket *child; + struct wolfIP_sockaddr_in peer; + socklen_t peer_len = sizeof(peer); + char got[8]; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, LLK_LOCAL_IP, LLK_NET_MASK, 0); + fd = llk_open_listener(&s); + lsn = &s.tcpsockets[SOCKET_UNMARK(fd)]; + + llk_keep_arp_fresh(&s, LLK_ATT_IP); + llk_attacker_syn(&s, LLK_ATT_IP, 41000, 1, 0); + llk_complete_handshake(&s, lsn, LLK_ATT_IP, 41000, 1); + ck_assert_int_eq(lsn->sock.tcp.state, TCP_ESTABLISHED); + + inject_tcp_segment(&s, TEST_PRIMARY_IF, LLK_ATT_IP, LLK_LOCAL_IP, + 41000, (uint16_t)LLK_LISTEN_PORT, 2, + lsn->sock.tcp.seq, TCP_FLAG_ACK | TCP_FLAG_FIN); + (void)wolfIP_poll(&s, 2); + ck_assert_int_eq(lsn->sock.tcp.state, TCP_CLOSE_WAIT); + ck_assert_int_eq(wolfIP_sock_can_read(&s, fd), 1); + + memset(&peer, 0, sizeof(peer)); + accepted = wolfIP_sock_accept(&s, fd, + (struct wolfIP_sockaddr *)&peer, &peer_len); + ck_assert_int_ge(accepted, 0); + ck_assert_uint_eq(ee16(peer.sin_port), 41000); + child = &s.tcpsockets[SOCKET_UNMARK(accepted)]; + ck_assert_int_eq(child->sock.tcp.state, TCP_CLOSE_WAIT); + /* EOF is a read event: the child is readable even with an empty queue. */ + ck_assert_uint_eq(child->events & CB_EVENT_READABLE, CB_EVENT_READABLE); + /* Nothing was sent before the FIN: the application reads EOF. */ + ck_assert_int_eq(wolfIP_sock_recv(&s, accepted, got, sizeof(got), 0), 0); + + /* The port is a plain listener again, off the fast-fail timer. */ + ck_assert_int_eq(lsn->sock.tcp.state, TCP_LISTEN); + ck_assert_int_eq(lsn->sock.tcp.preaccept_timeout_active, 0); + ck_assert_uint_eq(lsn->sock.tcp.tmr_rto, NO_TIMER); + ck_assert_int_eq(wolfIP_sock_can_read(&s, fd), 0); + ck_assert_int_eq(wolfIP_sock_accept(&s, fd, + (struct wolfIP_sockaddr *)&peer, &peer_len), -WOLFIP_EAGAIN); +} +END_TEST + +/* accept() called from a socket callback runs before flush_tcp_tx(), so the + * TX FIFO can still hold the pure ACK that tcp_input() queued for the peer's + * FIN. An ACK occupies no sequence space and is never retransmitted, so the + * hand-off must re-arm it on the child instead of dropping it. */ +START_TEST(test_tcp_listener_preaccept_handoff_reemits_queued_ack) +{ + struct wolfIP s; + int fd; + int accepted; + 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); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, LLK_LOCAL_IP, LLK_NET_MASK, 0); + fd = llk_open_listener(&s); + lsn = &s.tcpsockets[SOCKET_UNMARK(fd)]; + + llk_keep_arp_fresh(&s, LLK_ATT_IP); + llk_attacker_syn(&s, LLK_ATT_IP, 41000, 1, 0); + llk_complete_handshake(&s, lsn, LLK_ATT_IP, 41000, 1); + ck_assert_int_eq(lsn->sock.tcp.state, TCP_ESTABLISHED); + + /* The FIN is processed but not yet flushed: its ACK sits in the FIFO. */ + inject_tcp_segment(&s, TEST_PRIMARY_IF, LLK_ATT_IP, LLK_LOCAL_IP, + 41000, (uint16_t)LLK_LISTEN_PORT, 2, + lsn->sock.tcp.seq, TCP_FLAG_ACK | TCP_FLAG_FIN); + ck_assert_int_eq(lsn->sock.tcp.state, TCP_CLOSE_WAIT); + ck_assert_int_eq(fifo_is_empty(&lsn->sock.tcp.txbuf), 0); + + memset(&peer, 0, sizeof(peer)); + frames_before = last_frame_sent_count; + accepted = wolfIP_sock_accept(&s, fd, + (struct wolfIP_sockaddr *)&peer, &peer_len); + ck_assert_int_ge(accepted, 0); + + (void)wolfIP_poll(&s, 3); + ck_assert_uint_gt(last_frame_sent_count, frames_before); + out = llk_last_tcp(); + ck_assert_ptr_nonnull(out); + ck_assert(out->flags & TCP_FLAG_ACK); + ck_assert_uint_eq(ee16(out->src_port), (uint16_t)LLK_LISTEN_PORT); + ck_assert_uint_eq(ee16(out->dst_port), 41000); + /* The peer's FIN at seq 2 consumed one sequence number. */ + ck_assert_uint_eq(ee32(out->ack), 3); +} +END_TEST + +/* A listener closed while it holds a pre-accept connection keeps is_listener + * set through the closing states, where accept() and recv() both fail. It + * must not be advertised readable, or a poll() loop spins on a descriptor + * that can make no progress. */ +START_TEST(test_tcp_listener_closed_while_pending_is_not_readable) +{ + struct wolfIP s; + int fd; + struct tsocket *lsn; + struct wolfIP_sockaddr_in peer; + socklen_t peer_len = sizeof(peer); + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, LLK_LOCAL_IP, LLK_NET_MASK, 0); + fd = llk_open_listener(&s); + lsn = &s.tcpsockets[SOCKET_UNMARK(fd)]; + + llk_keep_arp_fresh(&s, LLK_ATT_IP); + llk_attacker_syn(&s, LLK_ATT_IP, 41000, 1, 0); + llk_complete_handshake(&s, lsn, LLK_ATT_IP, 41000, 1); + 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(lsn->sock.tcp.state, TCP_FIN_WAIT_1); + ck_assert_int_eq(lsn->sock.tcp.is_listener, 1); + + ck_assert_int_eq(wolfIP_sock_can_read(&s, fd), 0); + memset(&peer, 0, sizeof(peer)); + ck_assert_int_eq(wolfIP_sock_accept(&s, fd, + (struct wolfIP_sockaddr *)&peer, &peer_len), -1); } END_TEST @@ -5688,10 +5905,10 @@ START_TEST(test_tcp_listener_preaccept_revert_drains_connection_state) * must drop, or the next connection inherits a stale segment. */ ck_assert_int_eq(fifo_is_empty(&lsn->sock.tcp.txbuf), 0); - /* accept() fails (ESTABLISHED) and reverts the port to LISTEN. */ + /* The established connection is handed off and the listener is reset. */ memset(&peer, 0, sizeof(peer)); - ck_assert_int_eq(wolfIP_sock_accept(&s, fd, - (struct wolfIP_sockaddr *)&peer, &peer_len), -1); + ck_assert_int_ge(wolfIP_sock_accept(&s, fd, + (struct wolfIP_sockaddr *)&peer, &peer_len), 0); ck_assert_int_eq(lsn->sock.tcp.state, TCP_LISTEN); /* The dead connection's transport state is gone. */ @@ -5753,10 +5970,10 @@ START_TEST(test_tcp_listener_revert_restores_option_baseline) llk_complete_handshake(&s, lsn, LLK_ATT_IP, 41000, 1); ck_assert_int_eq(lsn->sock.tcp.state, TCP_ESTABLISHED); - /* accept() fails (ESTABLISHED) and reverts the port to LISTEN. */ + /* Late accept preserves the connection and reverts the port to LISTEN. */ memset(&peer, 0, sizeof(peer)); - ck_assert_int_eq(wolfIP_sock_accept(&s, fd, - (struct wolfIP_sockaddr *)&peer, &peer_len), -1); + ck_assert_int_ge(wolfIP_sock_accept(&s, fd, + (struct wolfIP_sockaddr *)&peer, &peer_len), 0); ck_assert_int_eq(lsn->sock.tcp.state, TCP_LISTEN); /* The option baseline matches a freshly allocated socket. */ diff --git a/src/wolfip.c b/src/wolfip.c index b5fb3bf8..d6e86a78 100644 --- a/src/wolfip.c +++ b/src/wolfip.c @@ -6860,14 +6860,74 @@ 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)]; - if (ts->sock.tcp.state == TCP_ESTABLISHED && - ts->sock.tcp.is_listener) { - /* The handshake completed before accept(): the connection can - * no longer be cloned (accept() only handles SYN_RCVD), and - * without this recovery the port would be pinned in - * ESTABLISHED forever. Revert the port to LISTEN. */ + if (ts->sock.tcp.is_listener && + (ts->sock.tcp.state == TCP_ESTABLISHED || + ts->sock.tcp.state == TCP_CLOSE_WAIT)) { + /* The network task can consume SYN, final ACK and initial + * application data in one poll step before the listening task + * runs. Preserve that completed connection in a child socket, + * including its queued data, and restore this descriptor as the + * listener. A shallow struct copy alone is not enough: the FIFO + * and queue carry pointers to their owning socket's storage. + * A peer that also closed before accept() (CLOSE_WAIT) is handed + * over the same way: the application reads the queued bytes and + * 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) { + tcp_listener_revert_to_listen(ts); + return -1; + } + tcp_preaccept_timeout_stop(ts); + *newts = *ts; + newts->sock.tcp.is_listener = 0; + newts->sock.tcp.preaccept_timeout_active = 0; + newts->sock.tcp.rxbuf.data = newts->rxmem; + /* The timer ids belong to the listener: the revert below cancels + * them, so the child must not keep a copy it could cancel or + * restart later. tmr_rto is already stopped above. */ + newts->sock.tcp.tmr_rto = NO_TIMER; + newts->sock.tcp.tmr_persist = NO_TIMER; + /* A wildcard listener leaves bound_local_ip unset; the accepted + * connection is bound to the address the SYN arrived on, as in + * the SYN_RCVD clone path below. */ + newts->bound_local_ip = (ts->bound_local_ip != IPADDR_ANY) ? + ts->bound_local_ip : ts->local_ip; + /* The only retransmittable segment possible before accept is the + * SYN-ACK, which the ESTABLISHED transition proves was + * acknowledged: do not carry a stale queued copy into the + * accepted stream. The FIFO can also hold a pure ACK for the + * peer's early data or FIN that flush_tcp_tx() has not sent yet + * (accept() from a socket callback runs before the flush), and + * an ACK occupies no sequence space, so nothing would ever + * retransmit it. Re-arm it on the child instead of leaving the + * peer waiting for its own retransmission timer. */ + fifo_init(&newts->sock.tcp.txbuf, newts->txmem, TXBUF_SIZE); + newts->sock.tcp.ack_retry_pending = 1; + /* Readiness follows the child's own buffers. The listener's + * CB_EVENT_READABLE means "a connection is pending accept" and + * would otherwise dispatch a read callback on an accepted socket + * whose RX queue is empty. */ + 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; + if (sin) { + sin->sin_family = AF_INET; + sin->sin_port = ee16(newts->dst_port); + sin->sin_addr.s_addr = ee32(newts->remote_ip); + } tcp_listener_revert_to_listen(ts); - return -1; + if (wolfIP_filter_notify_socket_event( + WOLFIP_FILT_ACCEPTING, s, newts, + newts->local_ip, newts->src_port, + newts->remote_ip, newts->dst_port) != 0) { + close_socket(newts); + return -1; + } + return (newts - s->tcpsockets) | MARK_TCP_SOCKET; } if ((ts->sock.tcp.state != TCP_SYN_RCVD) && (ts->sock.tcp.state != TCP_LISTEN)) return -1; @@ -6988,6 +7048,8 @@ int wolfIP_sock_sendto(struct wolfIP *s, int sockfd, const void *buf, size_t len return -WOLFIP_EINVAL; ts = &s->tcpsockets[SOCKET_UNMARK(sockfd)]; + if (ts->sock.tcp.state == TCP_SYN_RCVD) + return -WOLFIP_EAGAIN; if (ts->sock.tcp.state != TCP_ESTABLISHED && ts->sock.tcp.state != TCP_CLOSE_WAIT) return -1; @@ -8224,6 +8286,18 @@ int wolfIP_sock_can_read(struct wolfIP *s, int sockfd) if (IS_SOCKET_TCP(sockfd)) { if (!ts) return -WOLFIP_EINVAL; + /* A listener holding a pending connection is readable until the + * application accepts it, whatever stage the handshake reached: + * poll()/select() drivers learn about the connection only here, and + * the SYN_RCVD window is too short to rely on. Only the states + * accept() can hand off qualify - a listener closed while holding a + * connection keeps is_listener set through FIN_WAIT_1/LAST_ACK and + * friends, where accept() and recv() both fail. */ + 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)) + return 1; if (queue_len(&ts->sock.tcp.rxbuf) > 0) return 1; if (ts->sock.tcp.state == TCP_CLOSE_WAIT || ts->sock.tcp.state == TCP_CLOSED) @@ -8261,7 +8335,8 @@ int wolfIP_sock_can_write(struct wolfIP *s, int sockfd) if (IS_SOCKET_TCP(sockfd)) { if (!ts) return -WOLFIP_EINVAL; - if (ts->sock.tcp.state == TCP_SYN_SENT) + if (ts->sock.tcp.state == TCP_SYN_SENT || + ts->sock.tcp.state == TCP_SYN_RCVD) return 0; /* Only ESTABLISHED and CLOSE_WAIT accept data from send(), so both * must reflect actual TX capacity; every other state keeps its