diff --git a/src/http/httpd.h b/src/http/httpd.h index cbadc1aa..7630ef64 100644 --- a/src/http/httpd.h +++ b/src/http/httpd.h @@ -43,7 +43,7 @@ struct http_request { char path[HTTP_PATH_LEN]; // URL path char query[HTTP_QUERY_LEN]; // URL query string, if present in the target char headers[HTTP_HEADERS_LEN]; // HTTP headers - char body[HTTP_BODY_LEN]; // HTTP body (for POST requests) + char body[HTTP_BODY_LEN]; // HTTP body, if present in the request size_t body_len; }; diff --git a/src/port/rp2350_cyw43439/cyw43439_wifi.c b/src/port/rp2350_cyw43439/cyw43439_wifi.c index 0f05d9c4..c8833aac 100644 --- a/src/port/rp2350_cyw43439/cyw43439_wifi.c +++ b/src/port/rp2350_cyw43439/cyw43439_wifi.c @@ -127,7 +127,7 @@ static int op_connect(struct wolfIP_ll_dev *ll, const uint8_t bssid[6]) { (void)ll; - /* For WPA2/WPA3 the supplicant owns the keying material; the radio + /* For WPA2-PSK the supplicant owns the keying material; the radio * does 802.11 open auth + assoc carrying the RSN IE (open_auth = 0 * selects the WPA2-PSK/CCMP path), then EAPOL flows through the * 0x888E TX/RX path for the host-run 4-way. */ diff --git a/src/test/unit/unit.c b/src/test/unit/unit.c index 70b5f099..5e8c6dc0 100644 --- a/src/test/unit/unit.c +++ b/src/test/unit/unit.c @@ -210,6 +210,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_utils, test_sock_bind_tcp_state_not_closed); tcase_add_test(tc_utils, test_sock_bind_tcp_filter_blocks); tcase_add_test(tc_utils, test_sock_bind_tcp_port_collision_rejected); + tcase_add_test(tc_utils, test_sock_connect_tcp_keeps_bound_low_port); tcase_add_test(tc_utils, test_sock_bind_udp_src_port_nonzero); tcase_add_test(tc_utils, test_udp_auto_port_skips_in_use); tcase_add_test(tc_utils, test_port_alloc_walks_past_long_collision_run); @@ -262,6 +263,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_utils, test_sock_accept_invalid_tcp_fd); tcase_add_test(tc_utils, test_sock_accept_success_sets_addr); tcase_add_test(tc_utils, test_sock_accept_listener_resets_paws_state); + tcase_add_test(tc_utils, test_syn_rcvd_rst_listener_resets_paws_state); tcase_add_test(tc_utils, test_sock_accept_no_available_socket); tcase_add_test(tc_utils, test_sock_accept_no_free_socket_syn_rcvd); tcase_add_test(tc_utils, test_sock_accept_listen_no_connection); @@ -292,11 +294,14 @@ Suite *wolf_suite(void) #ifdef IP_MULTICAST tcase_add_test(tc_utils, test_multicast_join_and_drop_reports); tcase_add_test(tc_utils, test_multicast_join_report_repeated); + tcase_add_test(tc_utils, test_multicast_join_report_repeat_heap_full_rearmed_on_poll); tcase_add_test(tc_utils, test_multicast_join_validation_and_shared_refs); tcase_add_test(tc_utils, test_multicast_udp_receive_requires_join); tcase_add_test(tc_utils, test_multicast_udp_send_mac_ttl_loop_and_options); tcase_add_test(tc_utils, test_multicast_igmp_query_refreshes_report); tcase_add_test(tc_utils, test_multicast_igmp_query_flood_coalesced); + tcase_add_test(tc_utils, test_multicast_igmp_query_report_heap_full_rearmed_on_poll); + tcase_add_test(tc_utils, test_multicast_igmp_query_report_rearmed_after_tick_rollback); tcase_add_test(tc_utils, test_multicast_igmp_query_bad_checksum_dropped); tcase_add_test(tc_utils, test_multicast_igmp_query_spoofed_dropped); tcase_add_test(tc_utils, test_multicast_join_requires_configured_ip); @@ -1305,7 +1310,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_core, test_notify_loopback_null_stack_no_crash); /* === Branch-coverage tests from fleet ===*/ - /* --- unit_tests_tcp_state.c (65 tests) --- */ + /* --- unit_tests_tcp_state.c (72 tests) --- */ tcase_add_test(tc_core, test_tcp_send_reset_reply_ignores_rst_input); tcase_add_test(tc_core, test_tcp_send_reset_reply_ack_in_uses_ack_seq); tcase_add_test(tc_core, test_tcp_send_reset_reply_syn_no_ack_sets_rst_ack); @@ -1322,6 +1327,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_core, test_tcp_input_syn_rcvd_rst_good_seq_reverts_to_listen); tcase_add_test(tc_core, test_tcp_input_syn_rcvd_rst_good_seq_nonlistener_closes); tcase_add_test(tc_core, test_tcp_input_syn_rcvd_rst_nullcb_recv_reports_eof); + tcase_add_test(tc_core, test_tcp_input_closed_bound_rst_ignored); tcase_add_test(tc_core, test_tcp_input_time_wait_sends_ack_on_any_segment); tcase_add_test(tc_core, test_tcp_input_last_ack_unacceptable_sends_ack); tcase_add_test(tc_core, test_tcp_input_last_ack_syn_sends_challenge_ack); @@ -1344,6 +1350,12 @@ Suite *wolf_suite(void) tcase_add_test(tc_core, test_tcp_rto_cb_fin_wait_2_wrong_state_stops_timer); tcase_add_test(tc_core, test_tcp_rto_cb_ctrl_not_needed_stops); tcase_add_test(tc_core, test_tcp_rto_cb_ctrl_maxretries_nonlistener_closes); + tcase_add_test(tc_core, test_tcp_rto_cb_ctrl_rearm_heap_full_closes); + tcase_add_test(tc_core, test_tcp_fin_wait_2_timeout_start_heap_full_leaves_flag_clear); + tcase_add_test(tc_core, test_tcp_preaccept_timeout_start_heap_full_leaves_flag_clear); + tcase_add_test(tc_core, test_sock_connect_tcp_heap_full_does_not_pin_syn_sent); + tcase_add_test(tc_core, test_sock_connect_tcp_heap_full_drops_queued_syn); + tcase_add_test(tc_core, test_sock_accept_heap_full_keeps_listener_readable); tcase_add_test(tc_core, test_tcp_ack_duplicate_zero_inflight_early_return); tcase_add_test(tc_core, test_tcp_ack_duplicate_ack_ne_snd_una_returns); tcase_add_test(tc_core, test_tcp_ack_fourth_dupack_inflates_cwnd); @@ -1449,7 +1461,7 @@ Suite *wolf_suite(void) #ifdef IP_MULTICAST tcase_add_test(tc_core, test_poll_tx_udp_multicast_arp_skipped_uses_mcast_mac); #endif /* IP_MULTICAST */ - /* --- unit_tests_dhcp_edges.c (52 tests) --- */ + /* --- unit_tests_dhcp_edges.c (53 tests) --- */ tcase_add_test(tc_core, test_dhcp_schedule_lease_timer_zero_lease_noop); tcase_add_test(tc_core, test_dhcp_schedule_lease_timer_null_noop); tcase_add_test(tc_core, test_dhcp_schedule_lease_timer_renew_gt_lease_clamped); @@ -1458,6 +1470,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_core, test_dhcp_schedule_lease_timer_rebind_gt_lease_clamped); tcase_add_test(tc_core, test_dhcp_schedule_lease_timer_explicit_t1_t2); tcase_add_test(tc_core, test_dhcp_schedule_lease_timer_t1_t2_equal_lease_resets_defaults); + tcase_add_test(tc_core, test_dhcp_schedule_lease_timer_heap_full_rearmed_on_poll); tcase_add_test(tc_core, test_dhcp_msg_type_returns_offer); tcase_add_test(tc_core, test_dhcp_msg_type_returns_nak); tcase_add_test(tc_core, test_dhcp_msg_type_returns_ack); diff --git a/src/test/unit/unit_tests_api.c b/src/test/unit/unit_tests_api.c index 47a559fe..c9a01e95 100644 --- a/src/test/unit/unit_tests_api.c +++ b/src/test/unit/unit_tests_api.c @@ -1533,6 +1533,39 @@ START_TEST(test_sock_bind_tcp_port_collision_rejected) } END_TEST +/* A TCP socket explicitly bound to a port below 1024 keeps that source port + * across connect. */ +START_TEST(test_sock_connect_tcp_keeps_bound_low_port) +{ + struct wolfIP s; + int tcp_sd; + struct tsocket *ts; + struct wolfIP_sockaddr_in sin; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + tcp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_STREAM, WI_IPPROTO_TCP); + ck_assert_int_gt(tcp_sd, 0); + ts = &s.tcpsockets[SOCKET_UNMARK(tcp_sd)]; + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = ee16(20); + sin.sin_addr.s_addr = ee32(0x0A000001U); + ck_assert_int_eq(wolfIP_sock_bind(&s, tcp_sd, + (struct wolfIP_sockaddr *)&sin, sizeof(sin)), 0); + + sin.sin_port = ee16(5001); + sin.sin_addr.s_addr = ee32(0x0A000002U); + ck_assert_int_eq(wolfIP_sock_connect(&s, tcp_sd, + (struct wolfIP_sockaddr *)&sin, sizeof(sin)), -WOLFIP_EAGAIN); + + ck_assert_uint_eq(ts->src_port, 20); +} +END_TEST + START_TEST(test_sock_bind_udp_src_port_nonzero) { struct wolfIP s; @@ -2844,6 +2877,59 @@ START_TEST(test_sock_accept_listener_resets_paws_state) } END_TEST +/* A RST that reverts a half-open listener to LISTEN clears the aborted peer's + * PAWS state, so the next connection seeds a fresh TS.Recent. */ +START_TEST(test_syn_rcvd_rst_listener_resets_paws_state) +{ + struct wolfIP s; + int listen_sd; + struct tsocket *listener; + struct wolfIP_sockaddr_in sin; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + listen_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_STREAM, WI_IPPROTO_TCP); + ck_assert_int_gt(listen_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, listen_sd, (struct wolfIP_sockaddr *)&sin, sizeof(sin)), 0); + ck_assert_int_eq(wolfIP_sock_listen(&s, listen_sd, 1), 0); + + listener = &s.tcpsockets[SOCKET_UNMARK(listen_sd)]; + + /* Connection 1: the timestamped SYN seeds the half-open listener's + * TS.Recent (last_ts/ts_recent_valid) and enables PAWS. */ + inject_tcp_syn_ts(&s, TEST_PRIMARY_IF, 0x0A0000A1U, 0x0A000001U, 1234, + 0x10000000U); + ck_assert_int_eq(listener->sock.tcp.state, TCP_SYN_RCVD); + ck_assert_uint_eq(listener->sock.tcp.ts_enabled, 1); + ck_assert_uint_eq(listener->sock.tcp.last_ts, ee32(0x10000000U)); + ck_assert_uint_eq(listener->sock.tcp.ts_recent_valid, 1); + + /* Connection 1 is aborted in-sequence: the listener goes back to LISTEN + * and must be at the same fresh baseline the accept() revert leaves. */ + inject_tcp_segment(&s, TEST_PRIMARY_IF, 0x0A0000A1U, 0x0A000001U, + 40000, 1234, listener->sock.tcp.ack, 0, TCP_FLAG_RST); + ck_assert_int_eq(listener->sock.tcp.state, TCP_LISTEN); + ck_assert_uint_eq(listener->sock.tcp.ts_enabled, 0); + ck_assert_uint_eq(listener->sock.tcp.ts_recent_valid, 0); + ck_assert_uint_eq(listener->sock.tcp.last_ts, 0U); + + /* Connection 2, from a client with a lower timestamp epoch, must seed + * a fresh TS.Recent instead of inheriting connection 1's. */ + inject_tcp_syn_ts(&s, TEST_PRIMARY_IF, 0x0A0000A2U, 0x0A000001U, 1234, + 100U); + ck_assert_int_eq(listener->sock.tcp.state, TCP_SYN_RCVD); + ck_assert_uint_eq(listener->sock.tcp.ts_enabled, 1); + ck_assert_uint_eq(listener->sock.tcp.last_ts, ee32(100U)); + ck_assert_uint_eq(listener->sock.tcp.ts_recent_valid, 1); +} +END_TEST + START_TEST(test_sock_accept_no_available_socket) { struct wolfIP s; diff --git a/src/test/unit/unit_tests_dhcp_edges.c b/src/test/unit/unit_tests_dhcp_edges.c index 37c1c411..ce524bb7 100644 --- a/src/test/unit/unit_tests_dhcp_edges.c +++ b/src/test/unit/unit_tests_dhcp_edges.c @@ -219,6 +219,38 @@ START_TEST(test_dhcp_schedule_lease_timer_explicit_t1_t2) } END_TEST +/* A lease whose renewal timer could not be armed is re-armed by the poll loop once a slot frees. */ +START_TEST(test_dhcp_schedule_lease_timer_heap_full_rearmed_on_poll) +{ + struct wolfIP s; + struct wolfIP_timer t = {0}; + uint32_t filler[MAX_TIMERS]; + int i; + + wolfIP_init(&s); + mock_link_init(&s); + s.last_tick = 1000U; + s.dhcp_state = DHCP_BOUND; + + /* Fill the timer heap so the renewal insert fails. */ + for (i = 0; i < MAX_TIMERS; i++) { + t.expires = s.last_tick + 1000U + (uint64_t)i; + t.arg = NULL; + t.cb = NULL; + filler[i] = (uint32_t)timers_binheap_insert(&s.timers, t); + } + + dhcp_schedule_lease_timer(&s, 3600U, 1800U, 3150U); + ck_assert_int_eq(s.dhcp_timer, NO_TIMER); + + timer_binheap_cancel(&s.timers, filler[0]); + (void)wolfIP_poll(&s, s.last_tick); + + ck_assert_int_ne(s.dhcp_timer, NO_TIMER); + ck_assert_uint_eq(find_timer_expiry(&s, s.dhcp_timer), s.dhcp_renew_at); +} +END_TEST + /* ------------------------------------------------------------------------- * dhcp_msg_type — return each message type and validate * ---------------------------------------------------------------------- */ diff --git a/src/test/unit/unit_tests_dns_dhcp.c b/src/test/unit/unit_tests_dns_dhcp.c index 52e0edc5..b5f3eef4 100644 --- a/src/test/unit/unit_tests_dns_dhcp.c +++ b/src/test/unit/unit_tests_dns_dhcp.c @@ -1373,7 +1373,7 @@ START_TEST(test_sock_connect_tcp_src_port_low) sin.sin_addr.s_addr = ee32(0x0A000002U); ck_assert_int_eq(wolfIP_sock_connect(&s, tcp_sd, (struct wolfIP_sockaddr *)&sin, sizeof(sin)), -WOLFIP_EAGAIN); - ck_assert_uint_eq(ts->src_port, 1025); + ck_assert_uint_eq(ts->src_port, 1); } END_TEST diff --git a/src/test/unit/unit_tests_multicast.c b/src/test/unit/unit_tests_multicast.c index ca60bcb9..a8cfbad4 100644 --- a/src/test/unit/unit_tests_multicast.c +++ b/src/test/unit/unit_tests_multicast.c @@ -128,6 +128,45 @@ START_TEST(test_multicast_join_report_repeated) } END_TEST +/* A join repeat report the full timer heap rejected is re-armed by the poll loop once a slot frees. */ +START_TEST(test_multicast_join_report_repeat_heap_full_rearmed_on_poll) +{ + struct wolfIP s; + int sd; + struct wolfIP_ip_mreq mreq; + struct wolfIP_timer t = {0}; + uint32_t filler[MAX_TIMERS]; + ip4 group = 0xE9010212U; + int i; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000002U, 0xFFFFFF00U, 0); + sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + ck_assert_int_gt(sd, 0); + + for (i = 0; i < MAX_TIMERS; i++) { + t.expires = s.last_tick + 1000000U + (uint64_t)i; + filler[i] = (uint32_t)timers_binheap_insert(&s.timers, t); + } + + multicast_mreq(&mreq, group, IPADDR_ANY); + last_frame_sent_size = 0; + ck_assert_int_eq(wolfIP_sock_setsockopt(&s, sd, WOLFIP_SOL_IP, + WOLFIP_IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)), 0); + ck_assert_uint_gt(last_frame_sent_size, 0); + ck_assert_uint_eq(s.mcast[0].tmr_unsol, NO_TIMER); + + timer_binheap_cancel(&s.timers, filler[0]); + last_frame_sent_size = 0; + wolfIP_poll(&s, 1); + wolfIP_poll(&s, 2000); + ck_assert_uint_gt(last_frame_sent_size, 0); + ck_assert_uint_eq(last_igmp_payload()[8], IGMPV3_REC_MODE_IS_EXCLUDE); + ck_assert_uint_eq(get_be32(last_igmp_payload() + 12), group); +} +END_TEST + START_TEST(test_multicast_join_validation_and_shared_refs) { struct wolfIP s; @@ -371,6 +410,126 @@ START_TEST(test_multicast_igmp_query_flood_coalesced) } END_TEST +/* A deferred query response the full timer heap rejected is re-armed by the poll loop once a slot frees. */ +START_TEST(test_multicast_igmp_query_report_heap_full_rearmed_on_poll) +{ + struct wolfIP s; + int sd; + struct wolfIP_ip_mreq mreq; + struct wolfIP_timer t = {0}; + uint32_t filler[MAX_TIMERS]; + uint8_t frame[ETH_HEADER_LEN + IP_HEADER_LEN + IGMPV3_QUERY_MIN_LEN]; + struct wolfIP_ip_packet *ip = (struct wolfIP_ip_packet *)frame; + uint8_t *igmp = frame + ETH_HEADER_LEN + IP_HEADER_LEN; + ip4 group = 0xE9010213U; + int i; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000002U, 0xFFFFFF00U, 0); + sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + ck_assert_int_gt(sd, 0); + multicast_mreq(&mreq, group, IPADDR_ANY); + ck_assert_int_eq(wolfIP_sock_setsockopt(&s, sd, WOLFIP_SOL_IP, + WOLFIP_IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)), 0); + + /* Let the join repeat fire first, then fill the heap the query response needs. */ + wolfIP_poll(&s, 1001); + for (i = 0; i < MAX_TIMERS; i++) { + t.expires = s.last_tick + 1000000U + (uint64_t)i; + filler[i] = (uint32_t)timers_binheap_insert(&s.timers, t); + } + + memset(frame, 0, sizeof(frame)); + memcpy(ip->eth.dst, "\x01\x00\x5e\x00\x00\x01", 6); + memcpy(ip->eth.src, "\x02\x00\x00\x00\x00\x01", 6); + ip->eth.type = ee16(ETH_TYPE_IP); + ip->ver_ihl = 0x45; + ip->ttl = 1; + ip->proto = WI_IPPROTO_IGMP; + ip->len = ee16(IP_HEADER_LEN + IGMPV3_QUERY_MIN_LEN); + ip->src = ee32(0x0A000001U); + ip->dst = ee32(IGMP_ALL_HOSTS); + igmp[0] = IGMP_TYPE_MEMBERSHIP_QUERY; + put_be32(igmp + 4, group); + put_be16(igmp + 2, ip_checksum_buf(igmp, IGMPV3_QUERY_MIN_LEN)); + fix_ip_checksum(ip); + + wolfIP_recv_ex(&s, TEST_PRIMARY_IF, frame, sizeof(frame)); + ck_assert_uint_eq(s.mcast[0].tmr_report, NO_TIMER); + + timer_binheap_cancel(&s.timers, filler[0]); + last_frame_sent_size = 0; + wolfIP_poll(&s, 1002); + wolfIP_poll(&s, 1100); + ck_assert_uint_gt(last_frame_sent_size, 0); + ck_assert_uint_eq(last_igmp_payload()[8], IGMPV3_REC_MODE_IS_EXCLUDE); + ck_assert_uint_eq(get_be32(last_igmp_payload() + 12), group); +} +END_TEST + +/* A deferred query response owed across a tick-source rollback is re-armed at a deadline in the new tick domain. */ +START_TEST(test_multicast_igmp_query_report_rearmed_after_tick_rollback) +{ + struct wolfIP s; + int sd; + struct wolfIP_ip_mreq mreq; + struct wolfIP_timer t = {0}; + uint32_t filler[MAX_TIMERS]; + uint8_t frame[ETH_HEADER_LEN + IP_HEADER_LEN + IGMPV3_QUERY_MIN_LEN]; + struct wolfIP_ip_packet *ip = (struct wolfIP_ip_packet *)frame; + uint8_t *igmp = frame + ETH_HEADER_LEN + IP_HEADER_LEN; + ip4 group = 0xE9010214U; + int i; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000002U, 0xFFFFFF00U, 0); + sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + ck_assert_int_gt(sd, 0); + multicast_mreq(&mreq, group, IPADDR_ANY); + ck_assert_int_eq(wolfIP_sock_setsockopt(&s, sd, WOLFIP_SOL_IP, + WOLFIP_IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)), 0); + + /* Let the join repeat fire, take the tick close to the 32-bit wrap, then + * fill the heap the query response needs. */ + wolfIP_poll(&s, 1001); + wolfIP_poll(&s, 0xFFFFFF00U); + for (i = 0; i < MAX_TIMERS; i++) { + t.expires = s.last_tick + 1000000U + (uint64_t)i; + filler[i] = (uint32_t)timers_binheap_insert(&s.timers, t); + } + + memset(frame, 0, sizeof(frame)); + memcpy(ip->eth.dst, "\x01\x00\x5e\x00\x00\x01", 6); + memcpy(ip->eth.src, "\x02\x00\x00\x00\x00\x01", 6); + ip->eth.type = ee16(ETH_TYPE_IP); + ip->ver_ihl = 0x45; + ip->ttl = 1; + ip->proto = WI_IPPROTO_IGMP; + ip->len = ee16(IP_HEADER_LEN + IGMPV3_QUERY_MIN_LEN); + ip->src = ee32(0x0A000001U); + ip->dst = ee32(IGMP_ALL_HOSTS); + igmp[0] = IGMP_TYPE_MEMBERSHIP_QUERY; + put_be32(igmp + 4, group); + put_be16(igmp + 2, ip_checksum_buf(igmp, IGMPV3_QUERY_MIN_LEN)); + fix_ip_checksum(ip); + + wolfIP_recv_ex(&s, TEST_PRIMARY_IF, frame, sizeof(frame)); + ck_assert_uint_eq(s.mcast[0].tmr_report, NO_TIMER); + ck_assert_uint_ne(s.mcast[0].report_at, 0); + + /* The tick source restarts below the old domain, and a slot frees. */ + timer_binheap_cancel(&s.timers, filler[0]); + wolfIP_poll(&s, 1000); + last_frame_sent_size = 0; + wolfIP_poll(&s, 1100); + ck_assert_uint_gt(last_frame_sent_size, 0); + ck_assert_uint_eq(last_igmp_payload()[8], IGMPV3_REC_MODE_IS_EXCLUDE); + ck_assert_uint_eq(get_be32(last_igmp_payload() + 12), group); +} +END_TEST + START_TEST(test_multicast_igmp_query_bad_checksum_dropped) { struct wolfIP s; diff --git a/src/test/unit/unit_tests_tcp_state.c b/src/test/unit/unit_tests_tcp_state.c index 0212b1ce..e1ca17bd 100644 --- a/src/test/unit/unit_tests_tcp_state.c +++ b/src/test/unit/unit_tests_tcp_state.c @@ -709,6 +709,43 @@ START_TEST(test_tcp_input_syn_rcvd_rst_nullcb_recv_reports_eof) } END_TEST +/* A RST aimed at a bound socket still in TCP_CLOSED is ignored, leaving the + * socket and its port reservation intact. */ +START_TEST(test_tcp_input_closed_bound_rst_ignored) +{ + struct wolfIP s; + struct tsocket *ts; + struct wolfIP_sockaddr_in sin; + int sd; + ip4 local_ip = 0x0A000001U; + ip4 remote_ip = 0x0A0000A1U; + uint16_t lport = 8080, rport = 40000; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, local_ip, 0xFFFFFF00U, 0); + + sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_STREAM, 0); + ck_assert_int_ge(sd, 0); + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = ee32(IPADDR_ANY); + sin.sin_port = ee16(lport); + ck_assert_int_eq(wolfIP_sock_bind(&s, sd, (struct wolfIP_sockaddr *)&sin, + sizeof(sin)), 0); + + ts = &s.tcpsockets[SOCKET_UNMARK(sd)]; + ck_assert_int_eq(ts->sock.tcp.state, TCP_CLOSED); + + inject_tcp_segment(&s, TEST_PRIMARY_IF, remote_ip, local_ip, + rport, lport, 0, 0, TCP_FLAG_RST); + + ck_assert_int_ne(ts->proto, 0); + ck_assert_uint_eq(ts->src_port, lport); +} +END_TEST + /* Time-wait state re-ACKs any incoming segment */ START_TEST(test_tcp_input_time_wait_sends_ack_on_any_segment) { @@ -1243,6 +1280,198 @@ START_TEST(test_tcp_rto_cb_ctrl_maxretries_nonlistener_closes) } END_TEST +static void fill_timer_heap(struct wolfIP *s) +{ + struct wolfIP_timer tmr; + + memset(&tmr, 0, sizeof(tmr)); + tmr.expires = 0x7FFFFFFFU; + while (timers_binheap_insert(&s->timers, tmr) != NO_TIMER) + ; +} + +/* a control RTO re-arm that finds the timer heap full releases a non-listener socket */ +START_TEST(test_tcp_rto_cb_ctrl_rearm_heap_full_closes) +{ + struct wolfIP s; + struct tsocket *ts; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + ts = &s.tcpsockets[0]; + memset(ts, 0, sizeof(*ts)); + ts->proto = WI_IPPROTO_TCP; + ts->S = &s; + ts->sock.tcp.state = TCP_SYN_RCVD; + ts->sock.tcp.ctrl_rto_active = 1; + ts->sock.tcp.ctrl_rto_retries = 0; /* budget left: the re-arm is attempted */ + ts->sock.tcp.is_listener = 0; + ts->sock.tcp.tmr_rto = NO_TIMER; + fifo_init(&ts->sock.tcp.txbuf, ts->txmem, TXBUF_SIZE); + fill_timer_heap(&s); + + tcp_rto_cb(ts); + + ck_assert_int_eq(ts->proto, 0); +} +END_TEST + +/* fin_wait_2 timeout is not marked active when the timer heap is full */ +START_TEST(test_tcp_fin_wait_2_timeout_start_heap_full_leaves_flag_clear) +{ + struct wolfIP s; + struct tsocket *ts; + + wolfIP_init(&s); + mock_link_init(&s); + + ts = &s.tcpsockets[0]; + memset(ts, 0, sizeof(*ts)); + ts->proto = WI_IPPROTO_TCP; + ts->S = &s; + ts->sock.tcp.state = TCP_FIN_WAIT_2; + ts->sock.tcp.tmr_rto = NO_TIMER; + fifo_init(&ts->sock.tcp.txbuf, ts->txmem, TXBUF_SIZE); + fill_timer_heap(&s); + + tcp_fin_wait_2_timeout_start(ts, 0); + + ck_assert_uint_eq(ts->sock.tcp.tmr_rto, NO_TIMER); + ck_assert_int_eq(ts->sock.tcp.fin_wait_2_timeout_active, 0); +} +END_TEST + +/* pre-accept timeout is not marked active when the timer heap is full */ +START_TEST(test_tcp_preaccept_timeout_start_heap_full_leaves_flag_clear) +{ + struct wolfIP s; + struct tsocket *ts; + + wolfIP_init(&s); + mock_link_init(&s); + + ts = &s.tcpsockets[0]; + memset(ts, 0, sizeof(*ts)); + ts->proto = WI_IPPROTO_TCP; + ts->S = &s; + ts->sock.tcp.state = TCP_ESTABLISHED; + ts->sock.tcp.is_listener = 1; + ts->sock.tcp.tmr_rto = NO_TIMER; + fifo_init(&ts->sock.tcp.txbuf, ts->txmem, TXBUF_SIZE); + fill_timer_heap(&s); + + tcp_preaccept_timeout_start(ts, 0); + + ck_assert_uint_eq(ts->sock.tcp.tmr_rto, NO_TIMER); + ck_assert_int_eq(ts->sock.tcp.preaccept_timeout_active, 0); +} +END_TEST + +/* connect does not leave a socket in SYN_SENT when the control RTO cannot be armed */ +START_TEST(test_sock_connect_tcp_heap_full_does_not_pin_syn_sent) +{ + struct wolfIP s; + int tcp_sd; + struct tsocket *ts; + struct wolfIP_sockaddr_in sin; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + tcp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_STREAM, WI_IPPROTO_TCP); + ck_assert_int_gt(tcp_sd, 0); + ts = &s.tcpsockets[SOCKET_UNMARK(tcp_sd)]; + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = ee16(5001); + sin.sin_addr.s_addr = ee32(0x0A000002U); + fill_timer_heap(&s); + + ck_assert_int_eq(wolfIP_sock_connect(&s, tcp_sd, + (struct wolfIP_sockaddr *)&sin, sizeof(sin)), -WOLFIP_EAGAIN); + + ck_assert_uint_eq(ts->sock.tcp.tmr_rto, NO_TIMER); + ck_assert_int_eq(ts->sock.tcp.state, TCP_CLOSED); +} +END_TEST + +/* connect leaves no SYN queued for transmission when the control RTO cannot be armed */ +START_TEST(test_sock_connect_tcp_heap_full_drops_queued_syn) +{ + struct wolfIP s; + int tcp_sd; + struct tsocket *ts; + struct wolfIP_sockaddr_in sin; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + tcp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_STREAM, WI_IPPROTO_TCP); + ck_assert_int_gt(tcp_sd, 0); + ts = &s.tcpsockets[SOCKET_UNMARK(tcp_sd)]; + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = ee16(5001); + sin.sin_addr.s_addr = ee32(0x0A000002U); + fill_timer_heap(&s); + + ck_assert_int_eq(wolfIP_sock_connect(&s, tcp_sd, + (struct wolfIP_sockaddr *)&sin, sizeof(sin)), -WOLFIP_EAGAIN); + + ck_assert_int_eq(ts->sock.tcp.state, TCP_CLOSED); + ck_assert_ptr_null(fifo_peek(&ts->sock.tcp.txbuf)); +} +END_TEST + +/* accept keeps the listener readable and raises no close event for the discarded clone when the control RTO cannot be armed */ +START_TEST(test_sock_accept_heap_full_keeps_listener_readable) +{ + struct wolfIP s; + int listen_sd; + int i; + int callback_arg = 0; + struct tsocket *listener; + struct wolfIP_sockaddr_in sin; + socklen_t alen = sizeof(sin); + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + listen_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_STREAM, WI_IPPROTO_TCP); + ck_assert_int_gt(listen_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, listen_sd, + (struct wolfIP_sockaddr *)&sin, sizeof(sin)), 0); + ck_assert_int_eq(wolfIP_sock_listen(&s, listen_sd, 1), 0); + wolfIP_register_callback(&s, listen_sd, test_socket_cb, &callback_arg); + + listener = &s.tcpsockets[SOCKET_UNMARK(listen_sd)]; + inject_tcp_syn(&s, TEST_PRIMARY_IF, 0x0A000001U, 1234); + ck_assert_int_eq(listener->sock.tcp.state, TCP_SYN_RCVD); + ck_assert_uint_eq(listener->events & CB_EVENT_READABLE, CB_EVENT_READABLE); + fill_timer_heap(&s); + + ck_assert_int_eq(wolfIP_sock_accept(&s, listen_sd, + (struct wolfIP_sockaddr *)&sin, &alen), -WOLFIP_EAGAIN); + + ck_assert_uint_eq(listener->events & CB_EVENT_READABLE, CB_EVENT_READABLE); + for (i = 0; i < MAX_TCPSOCKETS; i++) { + if (&s.tcpsockets[i] != listener) + ck_assert_uint_eq(s.tcpsockets[i].close_notify_pending, 0); + } +} +END_TEST + /* =================================================================== * tcp_ack — additional missing branches * =================================================================== */ diff --git a/src/wolfip.c b/src/wolfip.c index d6e86a78..62898cb5 100644 --- a/src/wolfip.c +++ b/src/wolfip.c @@ -841,6 +841,11 @@ struct wolfIP_mcast_membership { * after a random delay; tmr_unsol is that pending repeat, kept apart from * tmr_report so a join does not suppress a query response. */ uint32_t tmr_unsol; + /* Deadline each report is owed at, or 0 when none is owed. A full timer + * heap leaves the deadline set with no timer behind it, and the poll loop + * re-arms from it once a slot frees. */ + uint64_t report_at; + uint64_t unsol_at; struct wolfIP *S; }; #endif @@ -1355,9 +1360,10 @@ static void tcp_rto_update_from_sample(struct tsocket *t, uint32_t sample_ms); static void tcp_rto_cb(void *arg); static int tcp_ctrl_rto_start(struct tsocket *t, uint64_t now); static void tcp_ctrl_rto_stop(struct tsocket *t); -static void tcp_fin_wait_2_timeout_start(struct tsocket *t, uint64_t now); +static void tcp_ctrl_rto_give_up(struct tsocket *t); +static int tcp_fin_wait_2_timeout_start(struct tsocket *t, uint64_t now); static void tcp_fin_wait_2_timeout_stop(struct tsocket *t); -static void tcp_preaccept_timeout_start(struct tsocket *t, uint64_t now); +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 int tcp_ctrl_state_needs_rto(const struct tsocket *t); @@ -4173,12 +4179,28 @@ static int tcp_ctrl_rto_start(struct tsocket *t, uint64_t now) return 0; } -static void tcp_fin_wait_2_timeout_start(struct tsocket *t, uint64_t now) +/* Same disposal the control-RTO retry budget uses when it runs out. */ +static void tcp_ctrl_rto_give_up(struct tsocket *t) +{ + if (!t || t->proto != WI_IPPROTO_TCP) + return; + tcp_ctrl_rto_stop(t); + if (t->sock.tcp.is_listener && t->sock.tcp.state == TCP_SYN_RCVD) { + tcp_listener_revert_to_listen(t); + } else { + t->sock.tcp.state = TCP_CLOSED; + close_socket(t); + } +} + +static int tcp_fin_wait_2_timeout_start(struct tsocket *t, uint64_t now) { struct wolfIP_timer tmr = {0}; if (!t || t->proto != WI_IPPROTO_TCP) - return; + return 0; + /* Cleared up front so a failed insert cannot leave it set. */ + t->sock.tcp.fin_wait_2_timeout_active = 0; if (t->sock.tcp.tmr_rto != NO_TIMER) { timer_binheap_cancel(&t->S->timers, t->sock.tcp.tmr_rto); t->sock.tcp.tmr_rto = NO_TIMER; @@ -4187,7 +4209,10 @@ static void tcp_fin_wait_2_timeout_start(struct tsocket *t, uint64_t now) tmr.arg = t; tmr.cb = tcp_rto_cb; t->sock.tcp.tmr_rto = timers_binheap_insert(&t->S->timers, tmr); + if (t->sock.tcp.tmr_rto == NO_TIMER) + return -1; t->sock.tcp.fin_wait_2_timeout_active = 1; + return 0; } static void tcp_fin_wait_2_timeout_stop(struct tsocket *t) @@ -4201,12 +4226,14 @@ static void tcp_fin_wait_2_timeout_stop(struct tsocket *t) t->sock.tcp.fin_wait_2_timeout_active = 0; } -static void tcp_preaccept_timeout_start(struct tsocket *t, uint64_t now) +static int tcp_preaccept_timeout_start(struct tsocket *t, uint64_t now) { struct wolfIP_timer tmr = {0}; if (!t || t->proto != WI_IPPROTO_TCP) - return; + return 0; + /* Cleared up front so a failed insert cannot leave it set. */ + t->sock.tcp.preaccept_timeout_active = 0; if (t->sock.tcp.tmr_rto != NO_TIMER) { timer_binheap_cancel(&t->S->timers, t->sock.tcp.tmr_rto); t->sock.tcp.tmr_rto = NO_TIMER; @@ -4215,7 +4242,10 @@ static void tcp_preaccept_timeout_start(struct tsocket *t, uint64_t now) tmr.arg = t; tmr.cb = tcp_rto_cb; t->sock.tcp.tmr_rto = timers_binheap_insert(&t->S->timers, tmr); + if (t->sock.tcp.tmr_rto == NO_TIMER) + return -1; t->sock.tcp.preaccept_timeout_active = 1; + return 0; } static void tcp_preaccept_timeout_stop(struct tsocket *t) @@ -4792,6 +4822,18 @@ static uint32_t igmp_max_resp_ms(uint8_t code) return tenths * 100U; } +/* Arms a report timer for a membership at the given deadline, returning NO_TIMER when the heap is full. */ +static uint32_t igmp_arm_report(struct wolfIP *s, struct wolfIP_mcast_membership *m, + uint64_t when, void (*cb)(void *)) +{ + struct wolfIP_timer tmr = {0}; + + tmr.expires = (when > s->last_tick) ? when : (s->last_tick + 1U); + tmr.arg = m; + tmr.cb = cb; + return (uint32_t)timers_binheap_insert(&s->timers, tmr); +} + /* Timer callback: the random response delay for a membership has elapsed, so * emit the deferred Current-State Report. arg is the membership; it carries a * back-pointer to the owning stack because the timer API passes only one arg. @@ -4804,6 +4846,7 @@ static void igmp_report_timer_cb(void *arg) if (!m) return; m->tmr_report = NO_TIMER; + m->report_at = 0; if (!m->S || m->refs == 0) return; (void)igmp_send_report(m->S, m->if_idx, m->group, IGMPV3_REC_MODE_IS_EXCLUDE); @@ -4816,6 +4859,7 @@ static void igmp_unsolicited_timer_cb(void *arg) if (!m) return; m->tmr_unsol = NO_TIMER; + m->unsol_at = 0; if (!m->S || m->refs == 0) return; (void)igmp_send_report(m->S, m->if_idx, m->group, IGMPV3_REC_MODE_IS_EXCLUDE); @@ -4872,7 +4916,6 @@ static void igmp_input(struct wolfIP *s, unsigned int if_idx, uint32_t max_ms = igmp_max_resp_ms(igmp[1]); for (i = 0; i < WOLFIP_MCAST_MEMBERSHIPS; i++) { - struct wolfIP_timer tmr = {0}; uint32_t delay; if (s->mcast[i].refs == 0 || s->mcast[i].if_idx != if_idx) @@ -4881,20 +4924,41 @@ static void igmp_input(struct wolfIP *s, unsigned int if_idx, continue; /* §5.2 rule 1: a query arriving while a response is already pending * for this membership schedules nothing further. This coalesces a - * query flood into a single deferred report per group. */ - if (s->mcast[i].tmr_report != NO_TIMER) + * query flood into a single deferred report per group, a response + * still owed but not yet armed included. */ + if (s->mcast[i].tmr_report != NO_TIMER || s->mcast[i].report_at != 0) continue; /* Floor at 1 ms: a zero window (IGMPv1 query) still fires on the * next poll, and expires must stay non-zero because the timer heap * treats expires == 0 as a cancelled slot. */ delay = max_ms ? (wolfIP_getrandom() % max_ms) + 1U : 1U; - tmr.expires = s->last_tick + delay; - tmr.arg = &s->mcast[i]; - tmr.cb = igmp_report_timer_cb; - s->mcast[i].tmr_report = timers_binheap_insert(&s->timers, tmr); + s->mcast[i].report_at = s->last_tick + delay; + s->mcast[i].tmr_report = igmp_arm_report(s, &s->mcast[i], + s->mcast[i].report_at, igmp_report_timer_cb); } } } + +/* Re-arms the deferred query response and the join repeat report for any membership left owing one. */ +static void igmp_timer_recover(struct wolfIP *s) +{ + unsigned int i; + + if (!s) + return; + for (i = 0; i < WOLFIP_MCAST_MEMBERSHIPS; i++) { + struct wolfIP_mcast_membership *m = &s->mcast[i]; + + if (m->refs == 0) + continue; + if (m->report_at != 0 && m->tmr_report == NO_TIMER) + m->tmr_report = igmp_arm_report(s, m, m->report_at, + igmp_report_timer_cb); + if (m->unsol_at != 0 && m->tmr_unsol == NO_TIMER) + m->tmr_unsol = igmp_arm_report(s, m, m->unsol_at, + igmp_unsolicited_timer_cb); + } +} #endif #ifdef WOLFIP_ESP @@ -5410,7 +5474,15 @@ static void tcp_ack(struct tsocket *t, const struct wolfIP_tcp_seg *tcp) if (t->sock.tcp.state == TCP_FIN_WAIT_1 && tcp_seq_leq(fin_acked, ack)) { t->sock.tcp.state = TCP_FIN_WAIT_2; tcp_ctrl_rto_stop(t); - tcp_fin_wait_2_timeout_start(t, t->S->last_tick); + if (tcp_fin_wait_2_timeout_start(t, t->S->last_tick) < 0) { + /* Nothing would bound the wait: close now, deferred as above. */ + t->sock.tcp.state = TCP_CLOSED; + if (t->callback) + t->events |= CB_EVENT_CLOSED; + else + close_socket(t); + return; + } } if (t->sock.tcp.state == TCP_CLOSING && tcp_seq_leq(fin_acked, ack)) { t->sock.tcp.state = TCP_TIME_WAIT; @@ -5678,11 +5750,9 @@ static void tcp_input(struct wolfIP *S, unsigned int if_idx, struct tsocket *t = &S->tcpsockets[i]; if (t->proto == 0 || t->S == NULL) continue; - /* A socket moved to TCP_CLOSED by the RX path with CB_EVENT_CLOSED - * still pending has only deferred its teardown to wolfIP_poll() - * Step 3 (so the close callback runs on a shallow stack). Ignore any - * further input for it until Step 3 delivers the event and reaps it. */ - if (t->sock.tcp.state == TCP_CLOSED && (t->events & CB_EVENT_CLOSED)) + /* RFC 9293 3.10.7.1: a socket in TCP_CLOSED holds no connection, so its + * segments are discarded and only the unmatched path below answers. */ + if (t->sock.tcp.state == TCP_CLOSED) continue; if (t->src_port == ee16(tcp->dst_port)) { /* TCP segment sanity checks (the ip.len vs frame_len bound is @@ -5799,16 +5869,11 @@ static void tcp_input(struct wolfIP *S, unsigned int if_idx, continue; if (t->sock.tcp.is_listener) { /* RST on a half-open connection of a listening socket: - * fall back to LISTEN to keep the server open. */ - t->sock.tcp.state = TCP_LISTEN; - t->events &= ~CB_EVENT_READABLE; - t->remote_ip = IPADDR_ANY; - t->dst_port = 0; - t->sock.tcp.ack = 0; - /* Drop the RST'd connection's parked SYN-ACK; it must + * fall back to LISTEN to keep the server open. + * Drop the RST'd connection's parked SYN-ACK; it must * not be retransmitted for the next connection (see * the accept() revert for why). */ - fifo_init(&t->sock.tcp.txbuf, t->txmem, TXBUF_SIZE); + tcp_listener_revert_to_listen(t); continue; } /* An accepted (cloned) connection has no listen role: a peer @@ -5923,7 +5988,10 @@ static void tcp_input(struct wolfIP *S, unsigned int if_idx, tcp_process_ts(t, tcp, frame_len); tcp_send_syn(t, TCP_FLAG_SYN | TCP_FLAG_ACK); t->sock.tcp.ctrl_rto_retries = 0; - tcp_ctrl_rto_start(t, S->last_tick); + if (tcp_ctrl_rto_start(t, S->last_tick) < 0) { + /* No SYN-ACK retransmit is possible: free the port. */ + tcp_listener_revert_to_listen(t); + } break; } else if (t->sock.tcp.state == TCP_SYN_SENT) { /* Only reached for a SYN-ACK whose ACK number was @@ -5980,8 +6048,12 @@ 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.is_listener) - tcp_preaccept_timeout_start(t, t->S->last_tick); + 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. */ + tcp_listener_revert_to_listen(t); + continue; + } /* t->sock.tcp.ack (RCV.NXT) is left as-is: when the * accepted segment begins above RCV.NXT, tcp_recv caches * it as OOO and advances RCV.NXT only once the hole is @@ -6239,12 +6311,14 @@ static void tcp_rto_cb(void *arg) queued = (tcp_send_finack(ts) == 0); if (queued) ts->sock.tcp.ctrl_rto_retries++; - tcp_ctrl_rto_start(ts, ts->S->last_tick); + if (tcp_ctrl_rto_start(ts, ts->S->last_tick) < 0) + tcp_ctrl_rto_give_up(ts); return; } if (queued) ts->sock.tcp.ctrl_rto_retries++; - tcp_ctrl_rto_start(ts, ts->S->last_tick); + if (tcp_ctrl_rto_start(ts, ts->S->last_tick) < 0) + tcp_ctrl_rto_give_up(ts); return; } } @@ -6816,8 +6890,6 @@ int wolfIP_sock_connect(struct wolfIP *s, int sockfd, const struct wolfIP_sockad return -WOLFIP_EAGAIN; } } - if (ts->src_port < 1024) - ts->src_port += 1024; ts->dst_port = ee16(sin->sin_port); ts->sock.tcp.seq = wolfIP_getrandom(); ts->sock.tcp.snd_una = ts->sock.tcp.seq; @@ -6832,7 +6904,11 @@ int wolfIP_sock_connect(struct wolfIP *s, int sockfd, const struct wolfIP_sockad ts->sock.tcp.state = TCP_CLOSED; return -WOLFIP_EAGAIN; } - tcp_ctrl_rto_start(ts, s->last_tick); + if (tcp_ctrl_rto_start(ts, s->last_tick) < 0) { + fifo_init(&ts->sock.tcp.txbuf, ts->txmem, TXBUF_SIZE); + ts->sock.tcp.state = TCP_CLOSED; + return -WOLFIP_EAGAIN; + } return -WOLFIP_EAGAIN; } return -WOLFIP_EINVAL; @@ -6975,13 +7051,17 @@ int wolfIP_sock_accept(struct wolfIP *s, int sockfd, struct wolfIP_sockaddr *add close_socket(newts); return -WOLFIP_EAGAIN; } - ts->events &= ~CB_EVENT_READABLE; /* Keep seq at the ISN while in SYN_RCVD: control RTO * retransmits rebuild the SYN-ACK from seq, and a retransmitted * SYN-ACK must repeat the original ISN. The final ACK handler * advances seq to ISN+1 when the connection is established. */ newts->sock.tcp.ctrl_rto_retries = 0; - tcp_ctrl_rto_start(newts, s->last_tick); + 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); @@ -7738,6 +7818,8 @@ static int udp_mcast_join(struct wolfIP *s, struct tsocket *ts, ip4 group, m->if_idx = (uint8_t)if_idx; m->tmr_report = NO_TIMER; m->tmr_unsol = NO_TIMER; + m->report_at = 0; + m->unsol_at = 0; m->S = s; break; } @@ -7751,13 +7833,11 @@ static int udp_mcast_join(struct wolfIP *s, struct tsocket *ts, ip4 group, if (m->refs != 0xff) m->refs++; if (m->refs == 1) { - struct wolfIP_timer tmr = {0}; (void)igmp_send_report(s, if_idx, group, IGMPV3_REC_MODE_IS_EXCLUDE); - tmr.expires = s->last_tick + + m->unsol_at = s->last_tick + (wolfIP_getrandom() % IGMP_UNSOLICITED_REPORT_MS) + 1U; - tmr.arg = m; - tmr.cb = igmp_unsolicited_timer_cb; - m->tmr_unsol = timers_binheap_insert(&s->timers, tmr); + m->tmr_unsol = igmp_arm_report(s, m, m->unsol_at, + igmp_unsolicited_timer_cb); } return 0; } @@ -8117,9 +8197,14 @@ int wolfIP_sock_close(struct wolfIP *s, int sockfd) return -WOLFIP_EAGAIN; ts->sock.tcp.state = TCP_FIN_WAIT_1; ts->sock.tcp.ctrl_rto_retries = 0; - tcp_ctrl_rto_start(ts, s->last_tick); ts->callback = NULL; ts->callback_arg = NULL; + if (tcp_ctrl_rto_start(ts, s->last_tick) < 0) { + /* No FIN retransmit is possible: release the socket. */ + ts->sock.tcp.state = TCP_CLOSED; + close_socket(ts); + return 0; + } return -WOLFIP_EAGAIN; } else if (ts->sock.tcp.state == TCP_LISTEN) { ts->sock.tcp.state = TCP_CLOSED; @@ -8135,9 +8220,13 @@ int wolfIP_sock_close(struct wolfIP *s, int sockfd) return -WOLFIP_EAGAIN; ts->sock.tcp.state = TCP_LAST_ACK; ts->sock.tcp.ctrl_rto_retries = 0; - tcp_ctrl_rto_start(ts, s->last_tick); ts->callback = NULL; ts->callback_arg = NULL; + 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; @@ -8792,16 +8881,18 @@ static void dhcp_timer_cb(void *arg); static void dhcp_cancel_timer(struct wolfIP *s); static void dhcp_deconfigure_lease(struct wolfIP *s); -static void dhcp_schedule_timer_at(struct wolfIP *s, uint64_t when) +/* Returns -1 when the shared timer heap had no slot for the DHCP timer. */ +static int dhcp_schedule_timer_at(struct wolfIP *s, uint64_t when) { struct wolfIP_timer tmr = { }; if (!s) - return; + return -1; tmr.expires = (when > s->last_tick) ? when : (s->last_tick + 1U); tmr.arg = s; tmr.cb = dhcp_timer_cb; s->dhcp_timer = timers_binheap_insert(&s->timers, tmr); + return (s->dhcp_timer == NO_TIMER) ? -1 : 0; } /* Exponential-backoff retransmission delay: double the base timeout for each @@ -9057,6 +9148,39 @@ static void dhcp_cancel_timer(struct wolfIP *s) s->dhcp_lease_expires = 0; } +/* Re-arms the DHCP timer from the current state's own deadline when an active client has none. */ +static void dhcp_timer_recover(struct wolfIP *s) +{ + if (!s || s->dhcp_state == DHCP_OFF || s->dhcp_timer != NO_TIMER) + return; + switch (s->dhcp_state) { + case DHCP_DISCOVER_SENT: + dhcp_schedule_timer_at(s, s->last_tick + + dhcp_discover_retry_delay(s, DHCP_DISCOVER_TIMEOUT)); + break; + case DHCP_REQUEST_SENT: + dhcp_schedule_retry_timer(s, 0); + break; + case DHCP_BOUND: + if (s->dhcp_renew_at != 0) + dhcp_schedule_timer_at(s, s->dhcp_renew_at); + break; + case DHCP_RENEWING: + dhcp_schedule_renew_rebind_retry(s, s->dhcp_rebind_at); + break; + case DHCP_REBINDING: + dhcp_schedule_renew_rebind_retry(s, s->dhcp_lease_expires); + break; +#ifdef ETHERNET + case DHCP_DAD: + dhcp_schedule_timer_at(s, s->last_tick + DHCP_DAD_INTERVAL_MS); + break; +#endif + default: + break; + } +} + static void dhcp_deconfigure_lease(struct wolfIP *s) { wolfIP_ipconfig_set(s, 0, 0, 0); @@ -12338,6 +12462,20 @@ int wolfIP_poll(struct wolfIP *s, uint64_t now) /* The in-flight acquisition start is re-timed in the new domain * so elapsed-time math never compares across domains. */ s->dhcp_start_tick = now; +#ifdef IP_MULTICAST + { + unsigned int i; + + for (i = 0; i < WOLFIP_MCAST_MEMBERSHIPS; i++) { + if (s->mcast[i].report_at != 0) + s->mcast[i].report_at = + tick_rebase(s->mcast[i].report_at, now); + if (s->mcast[i].unsol_at != 0) + s->mcast[i].unsol_at = + tick_rebase(s->mcast[i].unsol_at, now); + } + } +#endif #ifdef ETHERNET { unsigned int i; @@ -12358,6 +12496,10 @@ int wolfIP_poll(struct wolfIP *s, uint64_t now) /* Handle timers */ handle_timers(s, now); + dhcp_timer_recover(s); +#ifdef IP_MULTICAST + igmp_timer_recover(s); +#endif /* Handle socket callbacks */ handle_socket_callbacks(s);