From c8791cba170a058498710392618cc70c1b51864e Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Tue, 31 Mar 2026 10:56:52 +0200 Subject: [PATCH 01/20] Fix unsent TCP TX payload length accounting F/1765 --- src/test/unit/unit.c | 3 ++ src/test/unit/unit_tests_proto.c | 67 +++++++++++++++++++++++++++++ src/test/unit/unit_tests_tcp_flow.c | 41 ++++++++++++++++++ src/wolfip.c | 31 ++++++++++--- 4 files changed, 137 insertions(+), 5 deletions(-) diff --git a/src/test/unit/unit.c b/src/test/unit/unit.c index 7c8ea50..8b4cecf 100644 --- a/src/test/unit/unit.c +++ b/src/test/unit/unit.c @@ -296,6 +296,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_utils, test_poll_tcp_zero_window_arms_persist); tcase_add_test(tc_utils, test_tcp_persist_start_stops_when_window_reopens_or_no_unsent_payload); tcase_add_test(tc_utils, test_tcp_persist_helpers_ignore_non_tcp_and_null_inputs); + tcase_add_test(tc_utils, test_tcp_has_pending_unsent_payload_ignores_zero_ip_len_ack_only_desc); tcase_add_test(tc_utils, test_tcp_initial_cwnd_caps_to_iw10_and_half_rwnd); tcase_add_test(tc_utils, test_tcp_persist_cb_sends_one_byte_probe); tcase_add_test(tc_utils, test_tcp_zero_wnd_probe_rejects_invalid_inputs_and_empty_payload); @@ -410,6 +411,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_utils, test_tcp_rto_cb_resets_flags_and_arms_timer); tcase_add_test(tc_utils, test_tcp_rto_cb_no_pending_resets_backoff); tcase_add_test(tc_utils, test_tcp_rto_cb_skips_unsent_desc); + tcase_add_test(tc_utils, test_tcp_rto_cb_does_not_signal_writable_for_zero_ip_len_ack_only_desc); tcase_add_test(tc_utils, test_tcp_rto_cb_non_tcp_noop); tcase_add_test(tc_utils, test_tcp_rto_cb_non_established_noop); tcase_add_test(tc_utils, test_tcp_rto_cb_syn_sent_requeues_syn_and_arms_timer); @@ -553,6 +555,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_utils, test_tcp_mark_unsacked_for_retransmit_wrap_seg_end); tcase_add_test(tc_utils, test_tcp_mark_unsacked_retransmits_partially_acked_segment); tcase_add_test(tc_utils, test_tcp_mark_unsacked_rescans_after_clearing_stale_sack); + tcase_add_test(tc_utils, test_tcp_mark_unsacked_ignores_zero_ip_len_unsent_ack_only_desc); tcase_add_test(tc_utils, test_tcp_ack_sack_blocks_clamped_and_dropped); tcase_add_test(tc_utils, test_tcp_recv_ooo_capacity_limit); tcase_add_test(tc_utils, test_tcp_recv_overlapping_ooo_segments_coalesce_on_consume); diff --git a/src/test/unit/unit_tests_proto.c b/src/test/unit/unit_tests_proto.c index 16e7a78..b30b62c 100644 --- a/src/test/unit/unit_tests_proto.c +++ b/src/test/unit/unit_tests_proto.c @@ -1516,6 +1516,33 @@ START_TEST(test_tcp_persist_helpers_ignore_non_tcp_and_null_inputs) } END_TEST +START_TEST(test_tcp_has_pending_unsent_payload_ignores_zero_ip_len_ack_only_desc) +{ + struct wolfIP s; + struct tsocket *ts; + struct pkt_desc *desc; + struct wolfIP_tcp_seg *seg; + + 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; + fifo_init(&ts->sock.tcp.txbuf, ts->txmem, TXBUF_SIZE); + + ck_assert_int_eq(enqueue_tcp_tx(ts, 0, TCP_FLAG_ACK), 0); + desc = fifo_peek(&ts->sock.tcp.txbuf); + ck_assert_ptr_nonnull(desc); + seg = (struct wolfIP_tcp_seg *)(ts->txmem + desc->pos + sizeof(*desc)); + seg->ip.len = 0; + + ck_assert_int_eq(tcp_has_pending_unsent_payload(ts), 0); +} +END_TEST + START_TEST(test_tcp_zero_wnd_probe_rejects_invalid_inputs_and_empty_payload) { struct wolfIP s; @@ -1539,6 +1566,8 @@ START_TEST(test_tcp_zero_wnd_probe_skips_ack_only_segment) { struct wolfIP s; struct tsocket *ts; + struct pkt_desc *ack_desc; + struct pkt_desc *data_desc; ip4 local_ip = 0x0A000001U; ip4 remote_ip = 0x0A000002U; uint8_t peer_mac[6] = {0x00, 0xaa, 0xbb, 0xcc, 0xdd, 0x46}; @@ -1573,6 +1602,12 @@ START_TEST(test_tcp_zero_wnd_probe_skips_ack_only_segment) ck_assert_int_eq(enqueue_tcp_tx(ts, 0, TCP_FLAG_ACK), 0); ck_assert_int_eq(enqueue_tcp_tx_with_payload(ts, payload, sizeof(payload), (TCP_FLAG_ACK | TCP_FLAG_PSH)), 0); + ack_desc = fifo_peek(&ts->sock.tcp.txbuf); + ck_assert_ptr_nonnull(ack_desc); + ((struct wolfIP_tcp_seg *)(ts->txmem + ack_desc->pos + sizeof(*ack_desc)))->ip.len = 0; + data_desc = fifo_next(&ts->sock.tcp.txbuf, ack_desc); + ck_assert_ptr_nonnull(data_desc); + ((struct wolfIP_tcp_seg *)(ts->txmem + data_desc->pos + sizeof(*data_desc)))->ip.len = 0; ck_assert_int_eq(tcp_send_zero_wnd_probe(ts), 0); ck_assert_uint_gt(last_frame_sent_size, 0); @@ -1884,6 +1919,38 @@ START_TEST(test_tcp_rto_cb_clears_bookkeeping_when_no_payload_pending) } END_TEST +START_TEST(test_tcp_rto_cb_does_not_signal_writable_for_zero_ip_len_ack_only_desc) +{ + struct wolfIP s; + struct tsocket *ts; + struct pkt_desc *desc; + struct wolfIP_tcp_seg *seg; + + 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.snd_una = 100; + ts->sock.tcp.seq = 100; + ts->sock.tcp.rto_backoff = 3; + fifo_init(&ts->sock.tcp.txbuf, ts->txmem, TXBUF_SIZE); + + ck_assert_int_eq(enqueue_tcp_tx(ts, 0, TCP_FLAG_ACK), 0); + desc = fifo_peek(&ts->sock.tcp.txbuf); + ck_assert_ptr_nonnull(desc); + seg = (struct wolfIP_tcp_seg *)(ts->txmem + desc->pos + sizeof(*desc)); + seg->ip.len = 0; + seg->seq = ee32(100); + + tcp_rto_cb(ts); + ck_assert_uint_eq(ts->sock.tcp.rto_backoff, 0); + ck_assert_int_eq(ts->events & CB_EVENT_WRITABLE, 0); +} +END_TEST START_TEST(test_tcp_rto_cb_closes_socket_when_backoff_exhausted) { struct wolfIP s; diff --git a/src/test/unit/unit_tests_tcp_flow.c b/src/test/unit/unit_tests_tcp_flow.c index e924482..8ea8032 100644 --- a/src/test/unit/unit_tests_tcp_flow.c +++ b/src/test/unit/unit_tests_tcp_flow.c @@ -190,6 +190,47 @@ START_TEST(test_tcp_mark_unsacked_rescans_after_clearing_stale_sack) } END_TEST +START_TEST(test_tcp_mark_unsacked_ignores_zero_ip_len_unsent_ack_only_desc) +{ + struct wolfIP s; + struct tsocket *ts; + struct pkt_desc *desc1; + struct pkt_desc *desc2; + struct wolfIP_tcp_seg *seg1; + int ret; + uint8_t payload[4] = {0x21, 0x22, 0x23, 0x24}; + + wolfIP_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.snd_una = 100; + ts->sock.tcp.seq = 100; + ts->sock.tcp.bytes_in_flight = sizeof(payload); + fifo_init(&ts->sock.tcp.txbuf, ts->txmem, TXBUF_SIZE); + + ck_assert_int_eq(enqueue_tcp_tx(ts, 0, TCP_FLAG_ACK), 0); + ck_assert_int_eq(enqueue_tcp_tx_with_payload(ts, payload, sizeof(payload), + (TCP_FLAG_ACK | TCP_FLAG_PSH)), 0); + + desc1 = fifo_peek(&ts->sock.tcp.txbuf); + ck_assert_ptr_nonnull(desc1); + seg1 = (struct wolfIP_tcp_seg *)(ts->txmem + desc1->pos + sizeof(*desc1)); + seg1->ip.len = 0; + seg1->seq = ee32(100); + + desc2 = fifo_next(&ts->sock.tcp.txbuf, desc1); + ck_assert_ptr_nonnull(desc2); + desc2->flags |= PKT_FLAG_SENT; + + ret = tcp_mark_unsacked_for_retransmit(ts, 100); + ck_assert_int_eq(ret, 1); + ck_assert_int_eq(desc2->flags & PKT_FLAG_SENT, 0); + ck_assert_int_ne(desc2->flags & PKT_FLAG_RETRANS, 0); +} +END_TEST START_TEST(test_tcp_ack_sack_blocks_clamped_and_dropped) { struct wolfIP s; diff --git a/src/wolfip.c b/src/wolfip.c index 98942fd..92231e2 100644 --- a/src/wolfip.c +++ b/src/wolfip.c @@ -2755,6 +2755,27 @@ static void tcp_fin_wait_2_timeout_stop(struct tsocket *t) t->sock.tcp.fin_wait_2_timeout_active = 0; } +static uint32_t tcp_tx_desc_payload_len(const struct pkt_desc *desc, + const struct wolfIP_tcp_seg *seg) +{ + uint32_t seg_ip_len; + uint32_t seg_hdr_len; + + if (!desc || !seg) + return 0; + seg_hdr_len = IP_HEADER_LEN + (uint32_t)(seg->hlen >> 2); + seg_ip_len = ee16(seg->ip.len); + if (seg_ip_len == 0) { + if (desc->len >= (ETH_HEADER_LEN + seg_hdr_len)) + seg_ip_len = desc->len - ETH_HEADER_LEN; + else + seg_ip_len = desc->len; + } + if (seg_ip_len <= seg_hdr_len) + return 0; + return seg_ip_len - seg_hdr_len; +} + static int tcp_has_pending_unsent_payload(struct tsocket *t) { struct pkt_desc *desc; @@ -2769,7 +2790,7 @@ static int tcp_has_pending_unsent_payload(struct tsocket *t) struct wolfIP_tcp_seg *seg; uint32_t seg_len; seg = (struct wolfIP_tcp_seg *)(t->txmem + desc->pos + sizeof(*desc)); - seg_len = ee16(seg->ip.len) - (IP_HEADER_LEN + (seg->hlen >> 2)); + seg_len = tcp_tx_desc_payload_len(desc, seg); if (seg_len > 0 && !(desc->flags & PKT_FLAG_SENT)) return 1; desc = fifo_next(&t->sock.tcp.txbuf, desc); @@ -2846,7 +2867,7 @@ static int tcp_send_zero_wnd_probe(struct tsocket *t) while (desc && guard++ < budget) { struct wolfIP_tcp_seg *seg = (struct wolfIP_tcp_seg *)(t->txmem + desc->pos + sizeof(*desc)); uint32_t hdr_len = (uint32_t)(seg->hlen >> 2); - uint32_t seg_len = ee16(seg->ip.len) - (IP_HEADER_LEN + hdr_len); + uint32_t seg_len = tcp_tx_desc_payload_len(desc, seg); uint32_t seg_seq = ee32(seg->seq); const uint8_t *payload; if (seg_len == 0) { @@ -3406,7 +3427,7 @@ static int tcp_mark_unsacked_for_retransmit(struct tsocket *t, uint32_t ack) if (guard++ >= budget) break; seg = (struct wolfIP_tcp_seg *)(t->txmem + desc->pos + sizeof(*desc)); - seg_len = ee16(seg->ip.len) - (IP_HEADER_LEN + (seg->hlen >> 2)); + seg_len = tcp_tx_desc_payload_len(desc, seg); if (seg_len == 0) { desc = fifo_next(&t->sock.tcp.txbuf, desc); continue; @@ -4095,7 +4116,7 @@ static void tcp_rto_cb(void *arg) if (desc->flags & PKT_FLAG_SENT) { struct wolfIP_tcp_seg *seg = (struct wolfIP_tcp_seg *)(ts->txmem + desc->pos + sizeof(*desc)); - uint32_t seg_len = ee16(seg->ip.len) - (IP_HEADER_LEN + (seg->hlen >> 2)); + uint32_t seg_len = tcp_tx_desc_payload_len(desc, seg); uint32_t seg_start = ee32(seg->seq); uint32_t seg_end = tcp_seq_inc(seg_start, seg_len); @@ -4117,7 +4138,7 @@ static void tcp_rto_cb(void *arg) } else { struct wolfIP_tcp_seg *seg = (struct wolfIP_tcp_seg *)(ts->txmem + desc->pos + sizeof(*desc)); - uint32_t seg_len = ee16(seg->ip.len) - (IP_HEADER_LEN + (seg->hlen >> 2)); + uint32_t seg_len = tcp_tx_desc_payload_len(desc, seg); uint32_t seg_start = ee32(seg->seq); uint32_t seg_end = tcp_seq_inc(seg_start, seg_len); if (seg_len > 0 && From f867b1664724935e4ce7f3dbc51d4f83a753bfdc Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Tue, 31 Mar 2026 11:00:35 +0200 Subject: [PATCH 02/20] Close DHCP socket on bind failure F/1766 --- src/test/unit/unit.c | 1 + src/test/unit/unit_tests_dns_dhcp.c | 31 +++++++++++++++++++++++++++++ src/wolfip.c | 2 ++ 3 files changed, 34 insertions(+) diff --git a/src/test/unit/unit.c b/src/test/unit/unit.c index 8b4cecf..12991ec 100644 --- a/src/test/unit/unit.c +++ b/src/test/unit/unit.c @@ -318,6 +318,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_utils, test_dhcp_timer_cb_paths); tcase_add_test(tc_utils, test_dhcp_timer_cb_send_failure_does_not_consume_retry_budget); tcase_add_test(tc_utils, test_dhcp_client_init_and_bound); + tcase_add_test(tc_utils, test_dhcp_client_init_bind_failure_closes_socket); tcase_add_test(tc_utils, test_dhcp_send_request_renewing_sets_ciaddr_and_rebind_deadline); tcase_add_test(tc_utils, test_dhcp_send_request_rebinding_broadcasts_to_lease_expiry); tcase_add_test(tc_utils, test_dhcp_send_request_send_failure_retries_next_tick); diff --git a/src/test/unit/unit_tests_dns_dhcp.c b/src/test/unit/unit_tests_dns_dhcp.c index 46315d6..0ff76de 100644 --- a/src/test/unit/unit_tests_dns_dhcp.c +++ b/src/test/unit/unit_tests_dns_dhcp.c @@ -3857,6 +3857,37 @@ START_TEST(test_dhcp_client_init_and_bound) ck_assert_int_eq(dhcp_client_is_running(&s), 1); } END_TEST + +START_TEST(test_dhcp_client_init_bind_failure_closes_socket) +{ + struct wolfIP s; + unsigned int i; + int ret; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + filter_block_reason = WOLFIP_FILT_BINDING; + filter_block_calls = 0; + wolfIP_filter_set_callback(test_filter_cb_block, NULL); + wolfIP_filter_set_mask(WOLFIP_FILT_MASK(WOLFIP_FILT_BINDING)); + + ret = dhcp_client_init(&s); + ck_assert_int_eq(ret, -1); + ck_assert_int_eq(s.dhcp_udp_sd, 0); + ck_assert_int_eq(s.dhcp_state, DHCP_OFF); + ck_assert_int_gt(filter_block_calls, 0); + for (i = 0; i < MAX_UDPSOCKETS; i++) { + ck_assert_int_eq(s.udpsockets[i].proto, 0); + ck_assert_uint_eq(s.udpsockets[i].src_port, 0U); + } + + wolfIP_filter_set_callback(NULL, NULL); + wolfIP_filter_set_mask(0); +} +END_TEST + START_TEST(test_sock_close_udp_icmp) { struct wolfIP s; diff --git a/src/wolfip.c b/src/wolfip.c index 92231e2..f8cdf6f 100644 --- a/src/wolfip.c +++ b/src/wolfip.c @@ -6042,6 +6042,8 @@ int dhcp_client_init(struct wolfIP *s) sin.sin_port = ee16(DHCP_CLIENT_PORT); if (wolfIP_sock_bind(s, s->dhcp_udp_sd, (struct wolfIP_sockaddr *)&sin, sizeof(struct wolfIP_sockaddr_in)) < 0) { + wolfIP_sock_close(s, s->dhcp_udp_sd); + s->dhcp_udp_sd = 0; s->dhcp_state = DHCP_OFF; return -1; } From 72c1770e0b71e5c83aac47a4a4ff19bed9454675 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Tue, 31 Mar 2026 11:03:05 +0200 Subject: [PATCH 03/20] Fix TCP socket demultiplexing by IP F/1767 --- src/test/unit/unit.c | 1 + src/test/unit/unit_tests_tcp_flow.c | 41 +++++++++++++++++++++++++++++ src/wolfip.c | 8 ++++++ 3 files changed, 50 insertions(+) diff --git a/src/test/unit/unit.c b/src/test/unit/unit.c index 12991ec..e511f30 100644 --- a/src/test/unit/unit.c +++ b/src/test/unit/unit.c @@ -461,6 +461,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_utils, test_tcp_input_syn_rcvd_ack_invalid_seq_rejected); tcase_add_test(tc_utils, test_tcp_input_filter_drop); tcase_add_test(tc_utils, test_tcp_input_port_mismatch_skips_socket); + tcase_add_test(tc_utils, test_tcp_input_remote_ip_mismatch_skips_socket); tcase_add_test(tc_utils, test_tcp_input_unmatched_ack_sends_rst); tcase_add_test(tc_utils, test_tcp_input_unmatched_ack_nonlocal_dst_does_not_send_rst); tcase_add_test(tc_utils, test_tcp_input_unmatched_syn_sends_rst_ack); diff --git a/src/test/unit/unit_tests_tcp_flow.c b/src/test/unit/unit_tests_tcp_flow.c index 8ea8032..bc1a8d8 100644 --- a/src/test/unit/unit_tests_tcp_flow.c +++ b/src/test/unit/unit_tests_tcp_flow.c @@ -3657,6 +3657,47 @@ START_TEST(test_tcp_input_port_mismatch_skips_socket) } END_TEST +START_TEST(test_tcp_input_remote_ip_mismatch_skips_socket) +{ + struct wolfIP s; + struct wolfIP_tcp_seg seg; + 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_ESTABLISHED; + ts->src_port = 1234; + ts->dst_port = 4321; + ts->local_ip = 0x0A000001U; + ts->remote_ip = 0x0A000002U; + ts->sock.tcp.peer_rwnd = 100; + + memset(&seg, 0, sizeof(seg)); + seg.ip.ver_ihl = 0x45; + seg.ip.ttl = 64; + seg.ip.proto = WI_IPPROTO_TCP; + seg.ip.len = ee16(IP_HEADER_LEN + TCP_HEADER_LEN); + seg.ip.src = ee32(0x0A000003U); + seg.ip.dst = ee32(ts->local_ip); + seg.dst_port = ee16(ts->src_port); + seg.src_port = ee16(ts->dst_port); + seg.hlen = TCP_HEADER_LEN << 2; + seg.flags = TCP_FLAG_ACK; + seg.win = ee16(777); + fix_tcp_checksums(&seg); + + tcp_input(&s, TEST_PRIMARY_IF, &seg, (uint32_t)(ETH_HEADER_LEN + IP_HEADER_LEN + TCP_HEADER_LEN)); + ck_assert_int_eq(ts->sock.tcp.state, TCP_ESTABLISHED); + ck_assert_uint_eq(ts->sock.tcp.peer_rwnd, 100); +} +END_TEST + START_TEST(test_tcp_input_unmatched_ack_sends_rst) { struct wolfIP s; diff --git a/src/wolfip.c b/src/wolfip.c index f8cdf6f..0a8fc26 100644 --- a/src/wolfip.c +++ b/src/wolfip.c @@ -3758,6 +3758,14 @@ static void tcp_input(struct wolfIP *S, unsigned int if_idx, /* Not the right socket */ continue; } + if (t->remote_ip != ee32(tcp->ip.src)) { + /* Not the right peer */ + continue; + } + if (t->local_ip != IPADDR_ANY && t->local_ip != ee32(tcp->ip.dst)) { + /* Not the right local endpoint */ + continue; + } } matched = 1; /* Validate minimum TCP header length (data offset). */ From de1c5db04392a1bc44b52be81e493eb5024780d9 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Tue, 31 Mar 2026 11:06:34 +0200 Subject: [PATCH 04/20] Reject LISTEN SYN-ACK segments F/1768 --- src/test/unit/unit.c | 1 + src/test/unit/unit_tests_tcp_flow.c | 42 +++++++++++++++++++++++++++++ src/wolfip.c | 9 +++++++ 3 files changed, 52 insertions(+) diff --git a/src/test/unit/unit.c b/src/test/unit/unit.c index e511f30..6b7f3f0 100644 --- a/src/test/unit/unit.c +++ b/src/test/unit/unit.c @@ -742,6 +742,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_proto, test_regression_udp_checksum_zero_substituted_with_ffff); tcase_add_test(tc_proto, test_regression_last_ack_rejects_out_of_window_segment); tcase_add_test(tc_proto, test_regression_dns_id_never_zero); + tcase_add_test(tc_proto, test_tcp_input_listen_synack_sends_rst_and_stays_listen); tcase_add_test(tc_utils, test_transport_checksum); tcase_add_test(tc_utils, test_iphdr_set_checksum); diff --git a/src/test/unit/unit_tests_tcp_flow.c b/src/test/unit/unit_tests_tcp_flow.c index bc1a8d8..f7139ce 100644 --- a/src/test/unit/unit_tests_tcp_flow.c +++ b/src/test/unit/unit_tests_tcp_flow.c @@ -3788,6 +3788,48 @@ START_TEST(test_tcp_input_unmatched_rst_is_discarded) } END_TEST +START_TEST(test_tcp_input_listen_synack_sends_rst_and_stays_listen) +{ + struct wolfIP s; + int listen_sd; + struct tsocket *ts; + struct wolfIP_sockaddr_in sin; + struct wolfIP_tcp_seg *rst; + + 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); + + ts = &s.tcpsockets[SOCKET_UNMARK(listen_sd)]; + last_frame_sent_size = 0; + memset(last_frame_sent, 0, sizeof(last_frame_sent)); + + inject_tcp_segment(&s, TEST_PRIMARY_IF, 0x0A000002U, 0x0A000001U, + 4321, 1234, 77, 101, (uint8_t)(TCP_FLAG_SYN | TCP_FLAG_ACK)); + + ck_assert_int_eq(ts->sock.tcp.state, TCP_LISTEN); + ck_assert_uint_eq(last_frame_sent_size, (uint32_t)sizeof(struct wolfIP_tcp_seg)); + rst = (struct wolfIP_tcp_seg *)last_frame_sent; + ck_assert_uint_eq(ee32(rst->ip.src), 0x0A000001U); + ck_assert_uint_eq(ee32(rst->ip.dst), 0x0A000002U); + ck_assert_uint_eq(ee16(rst->src_port), 1234); + ck_assert_uint_eq(ee16(rst->dst_port), 4321); + ck_assert_uint_eq(rst->flags, TCP_FLAG_RST); + ck_assert_uint_eq(ee32(rst->seq), 101U); + ck_assert_uint_eq(ee32(rst->ack), 0U); +} +END_TEST + START_TEST(test_tcp_input_syn_bound_ip_mismatch) { struct wolfIP s; diff --git a/src/wolfip.c b/src/wolfip.c index 0a8fc26..de10638 100644 --- a/src/wolfip.c +++ b/src/wolfip.c @@ -3777,6 +3777,15 @@ static void tcp_input(struct wolfIP *S, unsigned int if_idx, return; /* malformed: TCP header exceeds IP length */ } tcplen = iplen - (IP_HEADER_LEN + (tcp->hlen >> 2)); + if (t->sock.tcp.state == TCP_LISTEN) { + /* RFC 9293 3.10.7.2: reject ACK-bearing segments before SYN handling. */ + if (tcp->flags & TCP_FLAG_RST) + continue; + if (tcp->flags & TCP_FLAG_ACK) { + tcp_send_reset_reply(S, if_idx, tcp); + continue; + } + } if (tcp->flags & TCP_FLAG_SYN) { struct tcp_parsed_opts po; tcp_parse_options(tcp, frame_len, &po); From c70fb1767d8c08d2e9d293523d030cd3db2a7f96 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Tue, 31 Mar 2026 11:09:33 +0200 Subject: [PATCH 05/20] Deconfigure DHCP lease on expiry F/1769 --- src/test/unit/unit.c | 1 + src/test/unit/unit_tests_dns_dhcp.c | 42 +++++++++++++++++++++++++++++ src/wolfip.c | 7 +++++ 3 files changed, 50 insertions(+) diff --git a/src/test/unit/unit.c b/src/test/unit/unit.c index 6b7f3f0..8afd092 100644 --- a/src/test/unit/unit.c +++ b/src/test/unit/unit.c @@ -316,6 +316,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_utils, test_poll_icmp_send_on_arp_hit); tcase_add_test(tc_utils, test_poll_icmp_send_on_arp_miss_requests_arp_and_retains_queue); tcase_add_test(tc_utils, test_dhcp_timer_cb_paths); + tcase_add_test(tc_utils, test_regression_dhcp_lease_expiry_deconfigures_address); tcase_add_test(tc_utils, test_dhcp_timer_cb_send_failure_does_not_consume_retry_budget); tcase_add_test(tc_utils, test_dhcp_client_init_and_bound); tcase_add_test(tc_utils, test_dhcp_client_init_bind_failure_closes_socket); diff --git a/src/test/unit/unit_tests_dns_dhcp.c b/src/test/unit/unit_tests_dns_dhcp.c index 0ff76de..59cb713 100644 --- a/src/test/unit/unit_tests_dns_dhcp.c +++ b/src/test/unit/unit_tests_dns_dhcp.c @@ -3807,6 +3807,48 @@ START_TEST(test_dhcp_timer_cb_paths) } END_TEST +START_TEST(test_regression_dhcp_lease_expiry_deconfigures_address) +{ + struct wolfIP s; + struct ipconf *primary; + + wolfIP_init(&s); + mock_link_init(&s); + primary = wolfIP_primary_ipconf(&s); + ck_assert_ptr_nonnull(primary); + s.dhcp_udp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + ck_assert_int_gt(s.dhcp_udp_sd, 0); + s.dhcp_xid = 1U; + + wolfIP_ipconfig_set(&s, 0x0A000064U, 0xFFFFFF00U, 0x0A000001U); + s.dhcp_ip = primary->ip; + s.dhcp_server_ip = 0x0A000001U; + s.last_tick = 1000U; + s.dhcp_lease_expires = s.last_tick; + + s.dhcp_state = DHCP_BOUND; + dhcp_timer_cb(&s); + ck_assert_int_eq(s.dhcp_state, DHCP_DISCOVER_SENT); + ck_assert_uint_eq(primary->ip, 0U); + ck_assert_uint_eq(primary->mask, 0U); + ck_assert_uint_eq(primary->gw, 0U); + ck_assert_uint_ne(s.dhcp_timer, NO_TIMER); + + wolfIP_ipconfig_set(&s, 0x0A000064U, 0xFFFFFF00U, 0x0A000001U); + s.dhcp_ip = primary->ip; + s.last_tick = 2000U; + s.dhcp_lease_expires = s.last_tick; + + s.dhcp_state = DHCP_REBINDING; + dhcp_timer_cb(&s); + ck_assert_int_eq(s.dhcp_state, DHCP_DISCOVER_SENT); + ck_assert_uint_eq(primary->ip, 0U); + ck_assert_uint_eq(primary->mask, 0U); + ck_assert_uint_eq(primary->gw, 0U); + ck_assert_uint_ne(s.dhcp_timer, NO_TIMER); +} +END_TEST + START_TEST(test_dhcp_timer_cb_send_failure_does_not_consume_retry_budget) { struct wolfIP s; diff --git a/src/wolfip.c b/src/wolfip.c index de10638..3e02d0d 100644 --- a/src/wolfip.c +++ b/src/wolfip.c @@ -5382,6 +5382,7 @@ static void icmp_input(struct wolfIP *s, unsigned int if_idx, struct wolfIP_ip_p static int dhcp_send_discover(struct wolfIP *s); static int dhcp_send_request(struct wolfIP *s); static void dhcp_timer_cb(void *arg); +static void dhcp_cancel_timer(struct wolfIP *s); static void dhcp_schedule_timer_at(struct wolfIP *s, uint64_t when) { @@ -5491,7 +5492,10 @@ static void dhcp_timer_cb(void *arg) break; case DHCP_BOUND: if (s->dhcp_lease_expires != 0 && s->last_tick >= s->dhcp_lease_expires) { + dhcp_cancel_timer(s); + wolfIP_ipconfig_set(s, 0, 0, 0); s->dhcp_state = DHCP_OFF; + dhcp_send_discover(s); break; } s->dhcp_state = DHCP_RENEWING; @@ -5513,7 +5517,10 @@ static void dhcp_timer_cb(void *arg) break; case DHCP_REBINDING: if (s->dhcp_lease_expires != 0 && s->last_tick >= s->dhcp_lease_expires) { + dhcp_cancel_timer(s); + wolfIP_ipconfig_set(s, 0, 0, 0); s->dhcp_state = DHCP_OFF; + dhcp_send_discover(s); break; } ret = dhcp_send_request(s); From e0d966046e5ee1ee10040e4a272ffa1cf9be8092 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Tue, 31 Mar 2026 11:13:07 +0200 Subject: [PATCH 06/20] Validate DNS RR class in answers F/1770 --- src/test/unit/unit.c | 2 + src/test/unit/unit_tests_dns_dhcp.c | 109 ++++++++++++++++++++++++++++ src/wolfip.c | 5 +- 3 files changed, 115 insertions(+), 1 deletion(-) diff --git a/src/test/unit/unit.c b/src/test/unit/unit.c index 8afd092..f386164 100644 --- a/src/test/unit/unit.c +++ b/src/test/unit/unit.c @@ -343,6 +343,8 @@ Suite *wolf_suite(void) tcase_add_test(tc_utils, test_dns_callback_bad_name); tcase_add_test(tc_utils, test_dns_callback_short_header_ignored); tcase_add_test(tc_utils, test_dns_callback_wrong_id_ignored); + tcase_add_test(tc_utils, test_dns_callback_non_in_a_answer_ignored); + tcase_add_test(tc_utils, test_dns_callback_non_in_ptr_answer_ignored); tcase_add_test(tc_utils, test_dns_callback_malformed_compressed_name_aborts_query); tcase_add_test(tc_utils, test_dns_callback_abort_clears_query_state); tcase_add_test(tc_utils, test_dns_abort_query_null_noop); diff --git a/src/test/unit/unit_tests_dns_dhcp.c b/src/test/unit/unit_tests_dns_dhcp.c index 59cb713..a75a236 100644 --- a/src/test/unit/unit_tests_dns_dhcp.c +++ b/src/test/unit/unit_tests_dns_dhcp.c @@ -4832,6 +4832,115 @@ START_TEST(test_dns_callback_wrong_id_ignored) } END_TEST +START_TEST(test_dns_callback_non_in_a_answer_ignored) +{ + struct wolfIP s; + uint8_t response[128]; + int pos; + struct dns_header *hdr = (struct dns_header *)response; + struct dns_question *q; + struct dns_rr *rr; + const uint8_t ip_bytes[4] = {0x0A, 0x00, 0x00, 0x42}; + + wolfIP_init(&s); + mock_link_init(&s); + s.dns_server = 0x0A000001U; + s.dns_query_type = DNS_QUERY_TYPE_A; + s.dns_id = 0x1234; + s.dns_lookup_cb = test_dns_lookup_cb; + dns_lookup_calls = 0; + dns_lookup_ip = 0; + s.dns_udp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + ck_assert_int_gt(s.dns_udp_sd, 0); + + memset(response, 0, sizeof(response)); + hdr->id = ee16(s.dns_id); + hdr->flags = ee16(0x8100); + hdr->qdcount = ee16(1); + hdr->ancount = ee16(1); + pos = sizeof(struct dns_header); + response[pos++] = 7; memcpy(&response[pos], "example", 7); pos += 7; + response[pos++] = 3; memcpy(&response[pos], "com", 3); pos += 3; + response[pos++] = 0; + q = (struct dns_question *)(response + pos); + q->qtype = ee16(DNS_A); + q->qclass = ee16(1); + pos += sizeof(struct dns_question); + response[pos++] = 0xC0; + response[pos++] = (uint8_t)sizeof(struct dns_header); + rr = (struct dns_rr *)(response + pos); + rr->type = ee16(DNS_A); + rr->class = ee16(3); + rr->ttl = ee32(60); + rr->rdlength = ee16(4); + pos += sizeof(struct dns_rr); + memcpy(&response[pos], ip_bytes, sizeof(ip_bytes)); + pos += sizeof(ip_bytes); + + enqueue_udp_rx(&s.udpsockets[SOCKET_UNMARK(s.dns_udp_sd)], response, (uint16_t)pos, DNS_PORT); + dns_callback(s.dns_udp_sd, CB_EVENT_READABLE, &s); + ck_assert_int_eq(dns_lookup_calls, 0); + ck_assert_uint_eq(dns_lookup_ip, 0U); + ck_assert_uint_eq(s.dns_id, 0x1234); + ck_assert_int_eq(s.dns_query_type, DNS_QUERY_TYPE_A); +} +END_TEST + +START_TEST(test_dns_callback_non_in_ptr_answer_ignored) +{ + struct wolfIP s; + uint8_t response[192]; + int pos; + struct dns_header *hdr = (struct dns_header *)response; + struct dns_question *q; + struct dns_rr *rr; + const char *ptr_name = "1.0.0.10.in-addr.arpa"; + struct tsocket *ts; + + wolfIP_init(&s); + mock_link_init(&s); + s.dns_server = 0x0A000001U; + s.dns_query_type = DNS_QUERY_TYPE_PTR; + s.dns_id = 0x1234; + s.dns_ptr_cb = test_dns_ptr_cb; + s.dns_lookup_cb = NULL; + s.dns_udp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + ck_assert_int_gt(s.dns_udp_sd, 0); + ts = &s.udpsockets[SOCKET_UNMARK(s.dns_udp_sd)]; + + memset(response, 0, sizeof(response)); + hdr->id = ee16(s.dns_id); + hdr->flags = ee16(0x8100); + hdr->qdcount = ee16(1); + hdr->ancount = ee16(1); + pos = sizeof(struct dns_header); + response[pos++] = 1; response[pos++] = 'a'; + response[pos++] = 3; memcpy(&response[pos], "com", 3); pos += 3; + response[pos++] = 0; + q = (struct dns_question *)(response + pos); + q->qtype = ee16(DNS_PTR); + q->qclass = ee16(1); + pos += sizeof(struct dns_question); + response[pos++] = 0xC0; + response[pos++] = (uint8_t)sizeof(struct dns_header); + rr = (struct dns_rr *)(response + pos); + rr->type = ee16(DNS_PTR); + rr->class = ee16(3); + rr->ttl = ee32(60); + rr->rdlength = ee16((uint16_t)(strlen(ptr_name) + 2)); + pos += sizeof(struct dns_rr); + response[pos++] = (uint8_t)strlen(ptr_name); + memcpy(&response[pos], ptr_name, strlen(ptr_name)); + pos += (int)strlen(ptr_name); + response[pos++] = 0; + + enqueue_udp_rx(ts, response, (uint16_t)pos, DNS_PORT); + dns_callback(s.dns_udp_sd, CB_EVENT_READABLE, &s); + ck_assert_uint_eq(s.dns_id, 0x1234); + ck_assert_int_eq(s.dns_query_type, DNS_QUERY_TYPE_PTR); +} +END_TEST + START_TEST(test_dns_callback_malformed_compressed_name_aborts_query) { struct wolfIP s; diff --git a/src/wolfip.c b/src/wolfip.c index 3e02d0d..b6f5d7c 100644 --- a/src/wolfip.c +++ b/src/wolfip.c @@ -7027,6 +7027,7 @@ void dns_callback(int dns_sd, uint16_t ev, void *arg) } if (s->dns_query_type == DNS_QUERY_TYPE_A && ee16(rr->type) == DNS_A && + ee16(rr->class) == DNS_CLASS_IN && rdlen >= DNS_IPV4_RDATA_LEN) { uint32_t ip; if (pos + DNS_IPV4_RDATA_LEN > dns_len) { @@ -7041,7 +7042,9 @@ void dns_callback(int dns_sd, uint16_t ev, void *arg) s->dns_lookup_cb(ip); dns_abort_query(s); return; - } else if (s->dns_query_type == DNS_QUERY_TYPE_PTR && ee16(rr->type) == DNS_PTR) { + } else if (s->dns_query_type == DNS_QUERY_TYPE_PTR && + ee16(rr->type) == DNS_PTR && + ee16(rr->class) == DNS_CLASS_IN) { if (dns_copy_name((const uint8_t *)buf, dns_len, pos, s->dns_ptr_name, sizeof(s->dns_ptr_name)) == 0) { if (s->dns_ptr_cb) From 24420ede9719bdf859c6717e575d16d0ca410ef5 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Tue, 31 Mar 2026 11:15:07 +0200 Subject: [PATCH 07/20] Count SYN in TCP segment acceptability length F/1771 --- src/test/unit/unit.c | 1 + src/test/unit/unit_tests_proto.c | 18 ++++++++++++++++++ src/wolfip.c | 4 +++- 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/test/unit/unit.c b/src/test/unit/unit.c index f386164..ddee8d7 100644 --- a/src/test/unit/unit.c +++ b/src/test/unit/unit.c @@ -123,6 +123,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_utils, test_wolfip_send_port_unreachable_non_ethernet_skips_eth_filter); tcase_add_test(tc_utils, test_tcp_adv_win_clamps_and_applies_window_scale); tcase_add_test(tc_utils, test_tcp_segment_acceptable_zero_window_and_overlap_cases); + tcase_add_test(tc_utils, test_tcp_segment_acceptable_counts_syn_in_segment_length); tcase_add_test(tc_utils, test_wolfip_ipconfig_ex_per_interface); tcase_add_test(tc_utils, test_wolfip_poll_executes_timers_and_callbacks); tcase_add_test(tc_utils, test_wolfip_poll_drains_all_expired_timers_in_one_pass); diff --git a/src/test/unit/unit_tests_proto.c b/src/test/unit/unit_tests_proto.c index b30b62c..1077520 100644 --- a/src/test/unit/unit_tests_proto.c +++ b/src/test/unit/unit_tests_proto.c @@ -3121,6 +3121,24 @@ START_TEST(test_tcp_segment_acceptable_zero_window_and_overlap_cases) } END_TEST +START_TEST(test_tcp_segment_acceptable_counts_syn_in_segment_length) +{ + struct tsocket ts; + struct wolfIP_tcp_seg seg; + + memset(&ts, 0, sizeof(ts)); + memset(&seg, 0, sizeof(seg)); + queue_init(&ts.sock.tcp.rxbuf, ts.rxmem, RXBUF_SIZE, 100U); + ts.sock.tcp.ack = 100U; + + ts.sock.tcp.rxbuf.size = RXBUF_SIZE; + seg.seq = ee32(99U); + seg.flags = TCP_FLAG_SYN | TCP_FLAG_FIN; + + ck_assert_int_eq(tcp_segment_acceptable(&ts, &seg, 0U), 1); +} +END_TEST + START_TEST(test_wolfip_ipconfig_ex_per_interface) { struct wolfIP s; diff --git a/src/wolfip.c b/src/wolfip.c index b6f5d7c..00f3a78 100644 --- a/src/wolfip.c +++ b/src/wolfip.c @@ -2154,7 +2154,9 @@ static int tcp_segment_acceptable(const struct tsocket *t, uint32_t rcv_nxt = t->sock.tcp.ack; uint32_t rcv_wnd = queue_space((struct queue *)&t->sock.tcp.rxbuf); uint32_t seg_seq = ee32(tcp->seq); - uint32_t seg_len = tcplen + ((tcp->flags & TCP_FLAG_FIN) ? 1U : 0U); + uint32_t seg_len = tcplen + + ((tcp->flags & TCP_FLAG_SYN) ? 1U : 0U) + + ((tcp->flags & TCP_FLAG_FIN) ? 1U : 0U); if (seg_len == 0U) { if (rcv_wnd == 0U) From 07eda8778f7e3ff28e8df16f14fbb309f62b3282 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Tue, 31 Mar 2026 11:17:14 +0200 Subject: [PATCH 08/20] Clear DHCP config on NAK F/1772 --- src/test/unit/unit.c | 1 + src/test/unit/unit_tests_dns_dhcp.c | 51 +++++++++++++++++++++++++++++ src/wolfip.c | 1 + 3 files changed, 53 insertions(+) diff --git a/src/test/unit/unit.c b/src/test/unit/unit.c index ddee8d7..7b445ff 100644 --- a/src/test/unit/unit.c +++ b/src/test/unit/unit.c @@ -329,6 +329,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_utils, test_dhcp_poll_offer_and_ack); tcase_add_test(tc_utils, test_dhcp_poll_renewing_ack_binds_client); tcase_add_test(tc_utils, test_dhcp_poll_rebinding_ack_binds_client); + tcase_add_test(tc_utils, test_regression_dhcp_nak_deconfigures_address_during_renew_and_rebind); tcase_add_test(tc_utils, test_dns_callback_ptr_response); tcase_add_test(tc_utils, test_udp_try_recv_short_frame); tcase_add_test(tc_utils, test_udp_try_recv_filter_drop); diff --git a/src/test/unit/unit_tests_dns_dhcp.c b/src/test/unit/unit_tests_dns_dhcp.c index a75a236..c1ac386 100644 --- a/src/test/unit/unit_tests_dns_dhcp.c +++ b/src/test/unit/unit_tests_dns_dhcp.c @@ -4272,6 +4272,57 @@ START_TEST(test_dhcp_poll_rebinding_ack_binds_client) } END_TEST +START_TEST(test_regression_dhcp_nak_deconfigures_address_during_renew_and_rebind) +{ + struct wolfIP s; + struct dhcp_msg msg; + struct dhcp_option *opt; + struct tsocket *ts; + struct ipconf *primary; + int ret; + + wolfIP_init(&s); + mock_link_init(&s); + primary = wolfIP_primary_ipconf(&s); + ck_assert_ptr_nonnull(primary); + s.dhcp_udp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + ck_assert_int_gt(s.dhcp_udp_sd, 0); + ts = &s.udpsockets[SOCKET_UNMARK(s.dhcp_udp_sd)]; + s.dhcp_xid = 0x12345678U; + + memset(&msg, 0, sizeof(msg)); + msg.op = BOOT_REPLY; + msg.magic = ee32(DHCP_MAGIC); + msg.xid = ee32(s.dhcp_xid); + opt = (struct dhcp_option *)msg.options; + opt->code = DHCP_OPTION_MSG_TYPE; + opt->len = 1; + opt->data[0] = DHCP_NAK; + opt = (struct dhcp_option *)((uint8_t *)opt + 3); + opt->code = DHCP_OPTION_END; + + wolfIP_ipconfig_set(&s, 0x0A000064U, 0xFFFFFF00U, 0x0A000001U); + s.dhcp_state = DHCP_RENEWING; + enqueue_udp_rx(ts, &msg, sizeof(msg), DHCP_SERVER_PORT); + ret = dhcp_poll(&s); + ck_assert_int_eq(ret, 0); + ck_assert_int_eq(s.dhcp_state, DHCP_DISCOVER_SENT); + ck_assert_uint_eq(primary->ip, 0U); + ck_assert_uint_eq(primary->mask, 0U); + ck_assert_uint_eq(primary->gw, 0U); + + wolfIP_ipconfig_set(&s, 0x0A000064U, 0xFFFFFF00U, 0x0A000001U); + s.dhcp_state = DHCP_REBINDING; + enqueue_udp_rx(ts, &msg, sizeof(msg), DHCP_SERVER_PORT); + ret = dhcp_poll(&s); + ck_assert_int_eq(ret, 0); + ck_assert_int_eq(s.dhcp_state, DHCP_DISCOVER_SENT); + ck_assert_uint_eq(primary->ip, 0U); + ck_assert_uint_eq(primary->mask, 0U); + ck_assert_uint_eq(primary->gw, 0U); +} +END_TEST + START_TEST(test_dns_callback_ptr_response) { struct wolfIP s; diff --git a/src/wolfip.c b/src/wolfip.c index 00f3a78..2b4f609 100644 --- a/src/wolfip.c +++ b/src/wolfip.c @@ -5851,6 +5851,7 @@ static int dhcp_poll(struct wolfIP *s) * it must restart the configuration process. */ if (dhcp_msg_type(s, &msg, (uint32_t)len) == DHCP_NAK) { dhcp_cancel_timer(s); + wolfIP_ipconfig_set(s, 0, 0, 0); s->dhcp_state = DHCP_OFF; dhcp_send_discover(s); return 0; From dc47eff41ef16ddbc059692e850b86af9063030c Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Tue, 31 Mar 2026 11:19:30 +0200 Subject: [PATCH 09/20] Keep FIN_WAIT_1 on repeated close F/1773 --- src/test/unit/unit.c | 1 + src/test/unit/unit_tests_dns_dhcp.c | 55 ++++++++++++++++++++++++++++- src/wolfip.c | 1 - 3 files changed, 55 insertions(+), 2 deletions(-) diff --git a/src/test/unit/unit.c b/src/test/unit/unit.c index 7b445ff..5ede656 100644 --- a/src/test/unit/unit.c +++ b/src/test/unit/unit.c @@ -590,6 +590,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_utils, test_tcp_input_header_len_below_min_dropped); tcase_add_test(tc_utils, test_socket_from_fd_invalid); tcase_add_test(tc_utils, test_socket_from_fd_valid); + tcase_add_test(tc_utils, test_sock_close_tcp_fin_wait_1_repeated_close_keeps_fin_wait_2_path); tcase_add_test(tc_proto, test_arp_request_basic); tcase_add_test(tc_proto, test_arp_request_throttle); diff --git a/src/test/unit/unit_tests_dns_dhcp.c b/src/test/unit/unit_tests_dns_dhcp.c index c1ac386..c338ae9 100644 --- a/src/test/unit/unit_tests_dns_dhcp.c +++ b/src/test/unit/unit_tests_dns_dhcp.c @@ -3986,7 +3986,60 @@ START_TEST(test_sock_close_tcp_fin_wait_1) ts->sock.tcp.state = TCP_FIN_WAIT_1; ck_assert_int_eq(wolfIP_sock_close(&s, sd), -WOLFIP_EAGAIN); - ck_assert_int_eq(ts->sock.tcp.state, TCP_CLOSING); + ck_assert_int_eq(ts->sock.tcp.state, TCP_FIN_WAIT_1); +} +END_TEST + +START_TEST(test_sock_close_tcp_fin_wait_1_repeated_close_keeps_fin_wait_2_path) +{ + struct wolfIP s; + struct tsocket *ts; + struct wolfIP_tcp_seg ackseg; + struct wolfIP_timer tmr; + int sd; + uint64_t timeout_at; + + wolfIP_init(&s); + mock_link_init(&s); + s.last_tick = 1000U; + + sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_STREAM, WI_IPPROTO_TCP); + ck_assert_int_gt(sd, 0); + ts = &s.tcpsockets[SOCKET_UNMARK(sd)]; + ts->S = &s; + ts->sock.tcp.state = TCP_FIN_WAIT_1; + ts->sock.tcp.last = 100; + ts->sock.tcp.snd_una = 100; + ts->sock.tcp.seq = 1000; + ts->sock.tcp.rto = 100; + ts->sock.tcp.ctrl_rto_active = 1; + ts->sock.tcp.ctrl_rto_retries = 2; + + memset(&tmr, 0, sizeof(tmr)); + tmr.cb = test_timer_cb; + tmr.expires = 200; + tmr.arg = ts; + 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(ts->sock.tcp.state, TCP_FIN_WAIT_1); + + memset(&ackseg, 0, sizeof(ackseg)); + ackseg.hlen = TCP_HEADER_LEN << 2; + ackseg.flags = TCP_FLAG_ACK; + ackseg.ack = ee32(101); + ackseg.ip.len = ee16(IP_HEADER_LEN + TCP_HEADER_LEN); + + tcp_ack(ts, &ackseg); + + 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.ctrl_rto_active, 0); + ck_assert_uint_eq(ts->sock.tcp.ctrl_rto_retries, 0); + ck_assert_uint_eq(ts->sock.tcp.fin_wait_2_timeout_active, 1); + 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); } END_TEST diff --git a/src/wolfip.c b/src/wolfip.c index 2b4f609..f8125af 100644 --- a/src/wolfip.c +++ b/src/wolfip.c @@ -5026,7 +5026,6 @@ int wolfIP_sock_close(struct wolfIP *s, int sockfd) ts->sock.tcp.state = TCP_TIME_WAIT; return -WOLFIP_EAGAIN; } else if (ts->sock.tcp.state == TCP_FIN_WAIT_1) { - ts->sock.tcp.state = TCP_CLOSING; return -WOLFIP_EAGAIN; } else if (ts->sock.tcp.state == TCP_FIN_WAIT_2) { tcp_fin_wait_2_timeout_stop(ts); From c637f91e25a69184741bf40e2a08032208693cca Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Tue, 31 Mar 2026 11:25:32 +0200 Subject: [PATCH 10/20] Preserve pure ACK delivery under TX FIFO pressure F/1774 --- src/test/unit/unit.c | 1 + src/test/unit/unit_tests_proto.c | 77 ++++++++++++++++++++++++++++++++ src/wolfip.c | 63 +++++++++++++++++++++++++- 3 files changed, 139 insertions(+), 2 deletions(-) diff --git a/src/test/unit/unit.c b/src/test/unit/unit.c index 5ede656..5a7eb74 100644 --- a/src/test/unit/unit.c +++ b/src/test/unit/unit.c @@ -739,6 +739,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_proto, test_regression_tcp_ip_len_below_ip_header); tcase_add_test(tc_proto, test_regression_syn_on_established_not_silently_processed); tcase_add_test(tc_proto, test_regression_syn_on_last_ack_not_silently_processed); + tcase_add_test(tc_proto, test_regression_full_txbuf_still_sends_pure_ack); tcase_add_test(tc_proto, test_regression_fast_recovery_cwnd_ssthresh_rfc5681); tcase_add_test(tc_proto, test_regression_paws_rejects_stale_timestamp); tcase_add_test(tc_proto, test_regression_paws_accepts_wrapped_newer_timestamp); diff --git a/src/test/unit/unit_tests_proto.c b/src/test/unit/unit_tests_proto.c index 1077520..96216ec 100644 --- a/src/test/unit/unit_tests_proto.c +++ b/src/test/unit/unit_tests_proto.c @@ -4677,6 +4677,83 @@ START_TEST(test_regression_syn_on_last_ack_not_silently_processed) } END_TEST +/* F/1774: even if the shared TCP TX FIFO is full of sent payload waiting for + * ACK, the stack still needs to emit a pure ACK for newly received data. */ +START_TEST(test_regression_full_txbuf_still_sends_pure_ack) +{ + struct wolfIP s; + struct tsocket *ts; + uint8_t buf[sizeof(struct wolfIP_tcp_seg) + 4]; + struct wolfIP_tcp_seg *seg = (struct wolfIP_tcp_seg *)buf; + struct pkt_desc *desc; + uint32_t original_ack; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + last_frame_sent_size = 0; + + s.arp.neighbors[0].ip = 0x0A000002U; + s.arp.neighbors[0].if_idx = TEST_PRIMARY_IF; + memcpy(s.arp.neighbors[0].mac, + (uint8_t[]){0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF}, 6); + + ts = &s.tcpsockets[0]; + memset(ts, 0, sizeof(*ts)); + ts->proto = WI_IPPROTO_TCP; + ts->S = &s; + ts->if_idx = TEST_PRIMARY_IF; + ts->sock.tcp.state = TCP_ESTABLISHED; + ts->sock.tcp.ack = 100; + ts->sock.tcp.seq = 1000; + ts->sock.tcp.snd_una = 900; + ts->sock.tcp.cwnd = TXBUF_SIZE; + ts->sock.tcp.peer_rwnd = TXBUF_SIZE; + ts->src_port = 1234; + ts->dst_port = 4321; + ts->local_ip = 0x0A000001U; + ts->remote_ip = 0x0A000002U; + queue_init(&ts->sock.tcp.rxbuf, ts->rxmem, RXBUF_SIZE, ts->sock.tcp.ack); + fifo_init(&ts->sock.tcp.txbuf, ts->txmem, TXBUF_SIZE); + + while (enqueue_tcp_tx(ts, 16, (TCP_FLAG_ACK | TCP_FLAG_PSH)) == 0) { + } + desc = fifo_peek(&ts->sock.tcp.txbuf); + while (desc) { + desc->flags |= PKT_FLAG_SENT; + desc = fifo_next(&ts->sock.tcp.txbuf, desc); + } + + original_ack = ts->sock.tcp.ack; + + memset(buf, 0, sizeof(buf)); + seg->ip.ver_ihl = 0x45; + seg->ip.ttl = 64; + seg->ip.proto = WI_IPPROTO_TCP; + seg->ip.len = ee16(IP_HEADER_LEN + TCP_HEADER_LEN + 4); + seg->ip.src = ee32(ts->remote_ip); + seg->ip.dst = ee32(ts->local_ip); + seg->dst_port = ee16(ts->src_port); + seg->src_port = ee16(ts->dst_port); + seg->hlen = TCP_HEADER_LEN << 2; + seg->flags = TCP_FLAG_ACK; + seg->seq = ee32(original_ack); + seg->ack = ee32(ts->sock.tcp.seq); + seg->win = ee16(65535); + memcpy(seg->data, (uint8_t[]){0xDE, 0xAD, 0xBE, 0xEF}, 4); + fix_tcp_checksums(seg); + + tcp_input(&s, TEST_PRIMARY_IF, seg, + (uint32_t)(ETH_HEADER_LEN + IP_HEADER_LEN + TCP_HEADER_LEN + 4)); + + ck_assert_uint_eq(ts->sock.tcp.ack, original_ack + 4); + + (void)wolfIP_poll(&s, 200); + + ck_assert_uint_gt(last_frame_sent_size, 0); +} +END_TEST + /* RFC 5681 §3.2: fast recovery deviates in multiple ways. * (a) ssthresh uses cwnd/2 instead of max(FlightSize/2, 2*SMSS) diff --git a/src/wolfip.c b/src/wolfip.c index f8125af..3370eae 100644 --- a/src/wolfip.c +++ b/src/wolfip.c @@ -2483,11 +2483,64 @@ static uint8_t tcp_build_ack_options(struct tsocket *t, uint8_t *opt, uint8_t ma return len; } +static int tcp_send_empty_immediate(struct tsocket *t, struct wolfIP_tcp_seg *tcp, + uint32_t frame_len) +{ + unsigned int tx_if; + struct wolfIP_ll_dev *ll; + + if (!t || !tcp || frame_len < ETH_HEADER_LEN + IP_HEADER_LEN + TCP_HEADER_LEN) + return -1; + + tx_if = wolfIP_socket_if_idx(t); + ll = wolfIP_ll_at(t->S, tx_if); + if (!ll) + return -1; + if (wolfIP_is_loopback_if(tx_if)) + return -1; +#ifdef ETHERNET + if (!wolfIP_ll_is_non_ethernet(t->S, tx_if)) { + struct ipconf *conf = wolfIP_ipconf_at(t->S, tx_if); + ip4 nexthop; + + if (!conf) + return -1; + nexthop = wolfIP_select_nexthop(conf, t->remote_ip); + + if (arp_lookup(t->S, tx_if, nexthop, t->nexthop_mac) < 0) { + arp_request(t->S, tx_if, nexthop); + return -1; + } + } +#endif + + t->sock.tcp.last_ack = t->sock.tcp.ack; + tcp->ack = ee32(t->sock.tcp.ack); + tcp->win = ee16(tcp_adv_win(t, 1)); + ip_output_add_header(t, (struct wolfIP_ip_packet *)tcp, WI_IPPROTO_TCP, + (uint16_t)(frame_len - ETH_HEADER_LEN)); + + if (wolfIP_filter_notify_tcp(WOLFIP_FILT_SENDING, t->S, tx_if, tcp, frame_len) != 0) + return -1; + if (wolfIP_filter_notify_ip(WOLFIP_FILT_SENDING, t->S, tx_if, &tcp->ip, frame_len) != 0) + return -1; +#ifdef ETHERNET + if (!wolfIP_ll_is_non_ethernet(t->S, tx_if)) { + if (wolfIP_filter_notify_eth(WOLFIP_FILT_SENDING, t->S, tx_if, &tcp->ip.eth, frame_len) != 0) + return -1; + } +#endif + + wolfIP_ll_send_frame(t->S, tx_if, tcp, frame_len); + return 0; +} + static void tcp_send_empty(struct tsocket *t, uint8_t flags) { struct wolfIP_tcp_seg *tcp; uint8_t opt_len; uint8_t buffer[sizeof(struct wolfIP_tcp_seg) + TCP_MAX_OPTIONS_LEN]; + uint32_t frame_len; tcp = (struct wolfIP_tcp_seg *)buffer; memset(tcp, 0, sizeof(buffer)); opt_len = tcp_build_ack_options(t, tcp->data, TCP_MAX_OPTIONS_LEN); @@ -2500,8 +2553,14 @@ static void tcp_send_empty(struct tsocket *t, uint8_t flags) tcp->win = ee16(tcp_adv_win(t, 1)); tcp->csum = 0; tcp->urg = 0; - fifo_push(&t->sock.tcp.txbuf, tcp, - sizeof(struct wolfIP_tcp_seg) + opt_len); + frame_len = sizeof(struct wolfIP_tcp_seg) + opt_len; + if (fifo_push(&t->sock.tcp.txbuf, tcp, frame_len) == 0) + return; + + /* Pure ACKs have no retransmission path, so do not drop them when the + * shared data/control TX FIFO is saturated by already queued payload. */ + if (flags == TCP_FLAG_ACK) + (void)tcp_send_empty_immediate(t, tcp, frame_len); } static void tcp_send_ack(struct tsocket *t) From 50d575aa09cf40037010bf07fd974010e39ad5b1 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Tue, 31 Mar 2026 11:28:29 +0200 Subject: [PATCH 11/20] Keep FIN_WAIT_2 on repeated close F/1775 --- src/test/unit/unit_tests_dns_dhcp.c | 6 ++++++ src/test/unit/unit_tests_proto.c | 2 +- src/wolfip.c | 7 ++----- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/test/unit/unit_tests_dns_dhcp.c b/src/test/unit/unit_tests_dns_dhcp.c index c338ae9..4739b9a 100644 --- a/src/test/unit/unit_tests_dns_dhcp.c +++ b/src/test/unit/unit_tests_dns_dhcp.c @@ -4040,6 +4040,12 @@ START_TEST(test_sock_close_tcp_fin_wait_1_repeated_close_keeps_fin_wait_2_path) ck_assert_uint_eq(ts->sock.tcp.fin_wait_2_timeout_active, 1); 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(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); + ck_assert_uint_eq(find_timer_expiry(&s, ts->sock.tcp.tmr_rto), timeout_at); } END_TEST diff --git a/src/test/unit/unit_tests_proto.c b/src/test/unit/unit_tests_proto.c index 96216ec..e5c2e6d 100644 --- a/src/test/unit/unit_tests_proto.c +++ b/src/test/unit/unit_tests_proto.c @@ -617,7 +617,7 @@ START_TEST(test_tcp_sock_close_state_transitions) ts->sock.tcp.state = TCP_FIN_WAIT_2; ck_assert_int_eq(wolfIP_sock_close(&s, sd), -WOLFIP_EAGAIN); - ck_assert_int_eq(ts->sock.tcp.state, TCP_TIME_WAIT); + 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); diff --git a/src/wolfip.c b/src/wolfip.c index 3370eae..509eb89 100644 --- a/src/wolfip.c +++ b/src/wolfip.c @@ -5084,11 +5084,8 @@ int wolfIP_sock_close(struct wolfIP *s, int sockfd) } else if (ts->sock.tcp.state == TCP_CLOSING) { ts->sock.tcp.state = TCP_TIME_WAIT; return -WOLFIP_EAGAIN; - } else if (ts->sock.tcp.state == TCP_FIN_WAIT_1) { - return -WOLFIP_EAGAIN; - } else if (ts->sock.tcp.state == TCP_FIN_WAIT_2) { - tcp_fin_wait_2_timeout_stop(ts); - ts->sock.tcp.state = TCP_TIME_WAIT; + } else if (ts->sock.tcp.state == TCP_FIN_WAIT_1 || + ts->sock.tcp.state == TCP_FIN_WAIT_2) { return -WOLFIP_EAGAIN; } else if (ts->sock.tcp.state != TCP_CLOSED) { ts->sock.tcp.state = TCP_CLOSED; From 69e9a12ee1d1b2a56deae1c6be242be3f317829b Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Tue, 31 Mar 2026 11:30:13 +0200 Subject: [PATCH 12/20] tcp: keep closing sockets in CLOSING F/1776 --- src/test/unit/unit_tests_proto.c | 2 +- src/wolfip.c | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/test/unit/unit_tests_proto.c b/src/test/unit/unit_tests_proto.c index e5c2e6d..03ec324 100644 --- a/src/test/unit/unit_tests_proto.c +++ b/src/test/unit/unit_tests_proto.c @@ -621,7 +621,7 @@ START_TEST(test_tcp_sock_close_state_transitions) ts->sock.tcp.state = TCP_CLOSING; ck_assert_int_eq(wolfIP_sock_close(&s, sd), -WOLFIP_EAGAIN); - ck_assert_int_eq(ts->sock.tcp.state, TCP_TIME_WAIT); + ck_assert_int_eq(ts->sock.tcp.state, TCP_CLOSING); ts->sock.tcp.state = TCP_LISTEN; ck_assert_int_eq(wolfIP_sock_close(&s, sd), 0); diff --git a/src/wolfip.c b/src/wolfip.c index 509eb89..7f8f232 100644 --- a/src/wolfip.c +++ b/src/wolfip.c @@ -5082,7 +5082,6 @@ int wolfIP_sock_close(struct wolfIP *s, int sockfd) tcp_ctrl_rto_start(ts, s->last_tick); return -WOLFIP_EAGAIN; } else if (ts->sock.tcp.state == TCP_CLOSING) { - ts->sock.tcp.state = TCP_TIME_WAIT; return -WOLFIP_EAGAIN; } else if (ts->sock.tcp.state == TCP_FIN_WAIT_1 || ts->sock.tcp.state == TCP_FIN_WAIT_2) { From a82e5624b0c00512bb824a4c4bb777af779e2f54 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Tue, 31 Mar 2026 11:35:42 +0200 Subject: [PATCH 13/20] Guard SYN enqueue state transitions F/1777 --- src/test/unit/unit.c | 2 + src/test/unit/unit_tests_api.c | 74 ++++++++++++++++++++++++++++++++++ src/wolfip.c | 42 ++++++++++++------- 3 files changed, 104 insertions(+), 14 deletions(-) diff --git a/src/test/unit/unit.c b/src/test/unit/unit.c index 5a7eb74..b31c1f5 100644 --- a/src/test/unit/unit.c +++ b/src/test/unit/unit.c @@ -253,6 +253,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_utils, test_sock_connect_tcp_filter_drop); tcase_add_test(tc_utils, test_sock_connect_tcp_src_port_low); tcase_add_test(tc_utils, test_sock_connect_tcp_initial_seq_randomized); + tcase_add_test(tc_utils, test_sock_connect_tcp_txbuf_full_does_not_enter_syn_sent); tcase_add_test(tc_utils, test_sock_sendto_more_error_paths); tcase_add_test(tc_utils, test_sock_sendto_udp_no_dest); tcase_add_test(tc_utils, test_sock_sendto_udp_sets_dest_and_assigns); @@ -286,6 +287,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_utils, test_sock_recvfrom_udp_payload_too_long); tcase_add_test(tc_utils, test_sock_recvfrom_icmp_payload_too_long); tcase_add_test(tc_utils, test_sock_accept_success); + tcase_add_test(tc_utils, test_sock_accept_synack_rto_txbuf_full_does_not_consume_retry); tcase_add_test(tc_utils, test_sock_accept_ack_with_payload_completes_handshake); tcase_add_test(tc_utils, test_sock_accept_ack_at_snd_nxt_completes_handshake); tcase_add_test(tc_utils, test_sock_accept_ack_psh_with_payload_completes_handshake); diff --git a/src/test/unit/unit_tests_api.c b/src/test/unit/unit_tests_api.c index c9694a1..11d21a7 100644 --- a/src/test/unit/unit_tests_api.c +++ b/src/test/unit/unit_tests_api.c @@ -1937,6 +1937,38 @@ START_TEST(test_sock_connect_tcp_local_ip_from_primary) } END_TEST +START_TEST(test_sock_connect_tcp_txbuf_full_does_not_enter_syn_sent) +{ + struct wolfIP s; + int tcp_sd; + struct tsocket *ts; + struct wolfIP_sockaddr_in sin; + uint8_t tiny_txbuf[32]; + + 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)]; + ts->sock.tcp.state = TCP_CLOSED; + fifo_init(&ts->sock.tcp.txbuf, tiny_txbuf, sizeof(tiny_txbuf)); + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = ee16(80); + 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_int_eq(ts->sock.tcp.state, TCP_CLOSED); + ck_assert_uint_eq(ts->sock.tcp.ctrl_rto_active, 0); + ck_assert_int_eq(ts->sock.tcp.tmr_rto, NO_TIMER); + ck_assert_ptr_null(fifo_peek(&ts->sock.tcp.txbuf)); +} +END_TEST + START_TEST(test_sock_connect_tcp_primary_ip_fallback) { struct wolfIP s; @@ -2413,6 +2445,48 @@ START_TEST(test_sock_accept_clones_half_open_state_and_queues_synack) } END_TEST +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]; + + 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); + + 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(accepted->sock.tcp.state, TCP_SYN_RCVD); + + fifo_init(&accepted->sock.tcp.txbuf, tiny_txbuf, sizeof(tiny_txbuf)); + accepted->sock.tcp.ctrl_rto_retries = 0; + s.last_tick = 10000; + + tcp_rto_cb(accepted); + + ck_assert_uint_eq(accepted->sock.tcp.ctrl_rto_retries, 0); + ck_assert_uint_eq(accepted->sock.tcp.ctrl_rto_active, 1); + ck_assert_int_ne(accepted->sock.tcp.tmr_rto, NO_TIMER); + ck_assert_ptr_null(fifo_peek(&accepted->sock.tcp.txbuf)); +} +END_TEST + START_TEST(test_sock_accept_synack_retransmission) { struct wolfIP s; diff --git a/src/wolfip.c b/src/wolfip.c index 7f8f232..d3e90e6 100644 --- a/src/wolfip.c +++ b/src/wolfip.c @@ -2648,7 +2648,7 @@ static void tcp_send_finack(struct tsocket *t) t->sock.tcp.last = t->sock.tcp.seq; } -static void tcp_send_syn(struct tsocket *t, uint8_t flags) +static int tcp_send_syn(struct tsocket *t, uint8_t flags) { struct wolfIP_tcp_seg *tcp; struct tcp_opt_ts *ts; @@ -2731,7 +2731,7 @@ static void tcp_send_syn(struct tsocket *t, uint8_t flags) opt_len++; } tcp->hlen = ((20 + opt_len) << 2) & 0xF0; - fifo_push(&t->sock.tcp.txbuf, tcp, sizeof(struct wolfIP_tcp_seg) + opt_len); + return fifo_push(&t->sock.tcp.txbuf, tcp, sizeof(struct wolfIP_tcp_seg) + opt_len); } /* Returns true when handshake/teardown control traffic is outstanding and @@ -4169,16 +4169,24 @@ static void tcp_rto_cb(void *arg) close_socket(ts); return; } - ts->sock.tcp.ctrl_rto_retries++; - if (ts->sock.tcp.state == TCP_SYN_SENT) { - tcp_send_syn(ts, TCP_FLAG_SYN); - } else if (ts->sock.tcp.state == TCP_SYN_RCVD) { - tcp_send_syn(ts, TCP_FLAG_SYN | TCP_FLAG_ACK); - } else if (ts->sock.tcp.state == TCP_FIN_WAIT_1 || ts->sock.tcp.state == TCP_LAST_ACK) { - tcp_send_finack(ts); + { + int queued = 0; + + if (ts->sock.tcp.state == TCP_SYN_SENT) { + queued = (tcp_send_syn(ts, TCP_FLAG_SYN) == 0); + } else if (ts->sock.tcp.state == TCP_SYN_RCVD) { + queued = (tcp_send_syn(ts, TCP_FLAG_SYN | TCP_FLAG_ACK) == 0); + } else if (ts->sock.tcp.state == TCP_FIN_WAIT_1 || ts->sock.tcp.state == TCP_LAST_ACK) { + ts->sock.tcp.ctrl_rto_retries++; + tcp_send_finack(ts); + tcp_ctrl_rto_start(ts, ts->S->last_tick); + return; + } + if (queued) + ts->sock.tcp.ctrl_rto_retries++; + tcp_ctrl_rto_start(ts, ts->S->last_tick); + return; } - tcp_ctrl_rto_start(ts, ts->S->last_tick); - return; } if (ts->sock.tcp.state != TCP_ESTABLISHED && ts->sock.tcp.state != TCP_FIN_WAIT_1) @@ -4548,7 +4556,10 @@ int wolfIP_sock_connect(struct wolfIP *s, int sockfd, const struct wolfIP_sockad return -1; } ts->sock.tcp.ctrl_rto_retries = 0; - tcp_send_syn(ts, TCP_FLAG_SYN); + if (tcp_send_syn(ts, TCP_FLAG_SYN) < 0) { + ts->sock.tcp.state = TCP_CLOSED; + return -WOLFIP_EAGAIN; + } tcp_ctrl_rto_start(ts, s->last_tick); return -WOLFIP_EAGAIN; } @@ -4581,7 +4592,6 @@ int wolfIP_sock_accept(struct wolfIP *s, int sockfd, struct wolfIP_sockaddr *add newts = tcp_new_socket(s); if (!newts) return -1; - ts->events &= ~CB_EVENT_READABLE; /* Don't signal writable until connection fully established */ newts->events &= ~CB_EVENT_WRITABLE; newts->callback = ts->callback; @@ -4616,7 +4626,11 @@ int wolfIP_sock_accept(struct wolfIP *s, int sockfd, struct wolfIP_sockaddr *add * the caller could still close the listening socket * while we're still accepting. */ - tcp_send_syn(newts, TCP_FLAG_SYN | TCP_FLAG_ACK); + if (tcp_send_syn(newts, TCP_FLAG_SYN | TCP_FLAG_ACK) < 0) { + close_socket(newts); + return -WOLFIP_EAGAIN; + } + ts->events &= ~CB_EVENT_READABLE; newts->sock.tcp.seq++; newts->sock.tcp.ctrl_rto_retries = 0; tcp_ctrl_rto_start(newts, s->last_tick); From 297e7c3136e09ec93ff9b8c9a3ebb57accecc3f9 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Tue, 31 Mar 2026 11:39:50 +0200 Subject: [PATCH 14/20] Guard UDP RX FIFO enqueue state F/1778 --- src/test/unit/unit.c | 1 + src/test/unit/unit_tests_dns_dhcp.c | 69 +++++++++++++++++++++++++++++ src/wolfip.c | 7 +-- 3 files changed, 74 insertions(+), 3 deletions(-) diff --git a/src/test/unit/unit.c b/src/test/unit/unit.c index b31c1f5..218e8ec 100644 --- a/src/test/unit/unit.c +++ b/src/test/unit/unit.c @@ -342,6 +342,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_utils, test_udp_try_recv_remote_ip_matches_local_ip); tcase_add_test(tc_utils, test_udp_try_recv_unmatched_port_sends_icmp_unreachable); tcase_add_test(tc_utils, test_udp_try_recv_unmatched_nonlocal_dst_does_not_send_icmp); + tcase_add_test(tc_utils, test_udp_try_recv_full_fifo_drop_does_not_set_readable_or_suppress_icmp); tcase_add_test(tc_utils, test_dns_callback_bad_flags); tcase_add_test(tc_utils, test_dns_callback_truncated_response_aborts_query); tcase_add_test(tc_utils, test_dns_callback_bad_name); diff --git a/src/test/unit/unit_tests_dns_dhcp.c b/src/test/unit/unit_tests_dns_dhcp.c index 4739b9a..6067b9e 100644 --- a/src/test/unit/unit_tests_dns_dhcp.c +++ b/src/test/unit/unit_tests_dns_dhcp.c @@ -4751,6 +4751,75 @@ START_TEST(test_udp_try_recv_unmatched_nonlocal_dst_does_not_send_icmp) } END_TEST +START_TEST(test_udp_try_recv_full_fifo_drop_does_not_set_readable_or_suppress_icmp) +{ + struct wolfIP s; + struct tsocket *ts; + uint8_t udp_buf[sizeof(struct wolfIP_udp_datagram) + 4]; + struct wolfIP_udp_datagram *udp = (struct wolfIP_udp_datagram *)udp_buf; + struct wolfIP_icmp_dest_unreachable_packet *icmp; + uint32_t local_ip = 0x0A000001U; + uint32_t remote_ip = 0x0A000002U; + uint8_t src_mac[6] = {0x20, 0x21, 0x22, 0x23, 0x24, 0x25}; + uint32_t frame_len = (uint32_t)(ETH_HEADER_LEN + IP_HEADER_LEN + UDP_HEADER_LEN + 4); + uint32_t head_before; + uint32_t tail_before; + uint32_t h_wrap_before; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, local_ip, 0xFFFFFF00U, 0); + + ts = udp_new_socket(&s); + ck_assert_ptr_nonnull(ts); + ts->src_port = 1234; + ts->local_ip = local_ip; + + memset(udp_buf, 0, sizeof(udp_buf)); + memcpy(udp->ip.eth.src, src_mac, sizeof(src_mac)); + memcpy(udp->ip.eth.dst, s.ll_dev[TEST_PRIMARY_IF].mac, 6); + udp->ip.eth.type = ee16(ETH_TYPE_IP); + udp->ip.ver_ihl = 0x45; + udp->ip.ttl = 64; + udp->ip.proto = WI_IPPROTO_UDP; + udp->ip.len = ee16(IP_HEADER_LEN + UDP_HEADER_LEN + 4); + udp->ip.src = ee32(remote_ip); + udp->ip.dst = ee32(local_ip); + udp->src_port = ee16(4321); + udp->dst_port = ee16(1234); + udp->len = ee16(UDP_HEADER_LEN + 4); + memcpy(udp->data, "test", 4); + fix_udp_checksums(udp); + + /* Mirror the FIFO's canonical full state: head == tail with wrap set. */ + ts->sock.udp.rxbuf.head = 0; + ts->sock.udp.rxbuf.tail = 0; + ts->sock.udp.rxbuf.h_wrap = ts->sock.udp.rxbuf.size; + ck_assert_int_eq(fifo_can_push_len(&ts->sock.udp.rxbuf, frame_len), 0); + + head_before = ts->sock.udp.rxbuf.head; + tail_before = ts->sock.udp.rxbuf.tail; + h_wrap_before = ts->sock.udp.rxbuf.h_wrap; + ts->events = 0; + + memset(last_frame_sent, 0, sizeof(last_frame_sent)); + last_frame_sent_size = 0; + + udp_try_recv(&s, TEST_PRIMARY_IF, udp, frame_len); + + ck_assert_uint_eq(ts->events & CB_EVENT_READABLE, 0U); + ck_assert_uint_eq(ts->sock.udp.rxbuf.head, head_before); + ck_assert_uint_eq(ts->sock.udp.rxbuf.tail, tail_before); + ck_assert_uint_eq(ts->sock.udp.rxbuf.h_wrap, h_wrap_before); + ck_assert_uint_eq(last_frame_sent_size, + sizeof(struct wolfIP_icmp_dest_unreachable_packet)); + + icmp = (struct wolfIP_icmp_dest_unreachable_packet *)last_frame_sent; + ck_assert_uint_eq(icmp->type, 3U); + ck_assert_uint_eq(icmp->code, 3U); +} +END_TEST + START_TEST(test_dns_callback_bad_flags) { struct wolfIP s; diff --git a/src/wolfip.c b/src/wolfip.c index d3e90e6..fd8c0cb 100644 --- a/src/wolfip.c +++ b/src/wolfip.c @@ -1911,9 +1911,10 @@ static void udp_try_recv(struct wolfIP *s, unsigned int if_idx, if ((int)frame_len < (int)expected_len) return; /* Insert into socket buffer */ - fifo_push(&t->sock.udp.rxbuf, udp, frame_len); - t->events |= CB_EVENT_READABLE; - matched = 1; + if (fifo_push(&t->sock.udp.rxbuf, udp, frame_len) == 0) { + t->events |= CB_EVENT_READABLE; + matched = 1; + } } } if (!matched) { From 7eec3869778786cc0d78cf16969838dcd1dae924 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Tue, 31 Mar 2026 11:43:00 +0200 Subject: [PATCH 15/20] Guard ICMP RX state on fifo push failure F/1779 --- src/test/unit/unit.c | 1 + src/test/unit/unit_tests_tcp_ack.c | 47 ++++++++++++++++++++++++++++++ src/wolfip.c | 7 +++-- 3 files changed, 52 insertions(+), 3 deletions(-) diff --git a/src/test/unit/unit.c b/src/test/unit/unit.c index 218e8ec..4c0d5ca 100644 --- a/src/test/unit/unit.c +++ b/src/test/unit/unit.c @@ -663,6 +663,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_proto, test_icmp_try_recv_mismatch_local_ip); tcase_add_test(tc_proto, test_icmp_try_recv_mismatch_src_port); tcase_add_test(tc_proto, test_icmp_try_recv_mismatch_remote_ip); + tcase_add_test(tc_proto, test_icmp_try_recv_full_fifo_does_not_signal_readable); tcase_add_test(tc_proto, test_wolfip_recv_on_not_for_us); tcase_add_test(tc_proto, test_wolfip_recv_on_filter_drop_eth); #if WOLFIP_ENABLE_FORWARDING diff --git a/src/test/unit/unit_tests_tcp_ack.c b/src/test/unit/unit_tests_tcp_ack.c index a27833a..87845c7 100644 --- a/src/test/unit/unit_tests_tcp_ack.c +++ b/src/test/unit/unit_tests_tcp_ack.c @@ -2704,6 +2704,53 @@ START_TEST(test_icmp_try_recv_mismatch_remote_ip) } END_TEST +START_TEST(test_icmp_try_recv_full_fifo_does_not_signal_readable) +{ + struct wolfIP s; + struct tsocket *ts; + struct wolfIP_icmp_packet icmp; + uint32_t frame_len; + uint32_t fifo_used; + int ret; + + wolfIP_init(&s); + mock_link_init(&s); + + ts = icmp_new_socket(&s); + ck_assert_ptr_nonnull(ts); + ts->local_ip = 0x0A000001U; + ts->remote_ip = 0x0A000002U; + ts->src_port = 0x1234U; + ts->last_pkt_ttl = 77U; + ts->events = 0U; + + memset(&icmp, 0, sizeof(icmp)); + icmp.ip.len = ee16(IP_HEADER_LEN + ICMP_HEADER_LEN); + icmp.ip.src = ee32(ts->remote_ip); + icmp.ip.dst = ee32(ts->local_ip); + icmp.ip.ttl = 29U; + icmp.type = ICMP_ECHO_REPLY; + icmp_set_echo_id(&icmp, ts->src_port); + frame_len = (uint32_t)(ETH_HEADER_LEN + IP_HEADER_LEN + ICMP_HEADER_LEN); + + while (fifo_can_push_len(&ts->sock.udp.rxbuf, frame_len)) { + ret = fifo_push(&ts->sock.udp.rxbuf, &icmp, frame_len); + ck_assert_int_eq(ret, 0); + } + + fifo_used = fifo_len(&ts->sock.udp.rxbuf); + ck_assert_uint_gt(fifo_used, 0U); + ck_assert_int_eq(fifo_can_push_len(&ts->sock.udp.rxbuf, frame_len), 0); + + icmp.ip.ttl = 42U; + icmp_try_recv(&s, TEST_PRIMARY_IF, &icmp, frame_len); + + ck_assert_uint_eq(fifo_len(&ts->sock.udp.rxbuf), fifo_used); + ck_assert_uint_eq(ts->last_pkt_ttl, 77U); + ck_assert_uint_eq(ts->events & CB_EVENT_READABLE, 0U); +} +END_TEST + START_TEST(test_wolfip_recv_on_not_for_us) { struct wolfIP s; diff --git a/src/wolfip.c b/src/wolfip.c index fd8c0cb..614c3c3 100644 --- a/src/wolfip.c +++ b/src/wolfip.c @@ -1975,9 +1975,10 @@ static void icmp_try_recv(struct wolfIP *s, unsigned int if_idx, continue; if ((int)frame_len < ee16(icmp->ip.len) + ETH_HEADER_LEN) continue; - fifo_push(&t->sock.udp.rxbuf, icmp, frame_len); - t->last_pkt_ttl = icmp->ip.ttl; - t->events |= CB_EVENT_READABLE; + if (fifo_push(&t->sock.udp.rxbuf, icmp, frame_len) == 0) { + t->last_pkt_ttl = icmp->ip.ttl; + t->events |= CB_EVENT_READABLE; + } } } From 7578fbc12f50f86893f4c8b7afb6b9f88c667518 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Tue, 31 Mar 2026 12:01:14 +0200 Subject: [PATCH 16/20] Switched to ghcr container 'wolfboot-ci' for m33mu tests --- .github/workflows/stm32h563-m33mu-freertos.yml | 2 +- .github/workflows/stm32h563-m33mu-ssh-tzen.yml | 4 ++-- .github/workflows/stm32h563-m33mu.yml | 12 ++++++------ tools/scripts/run-m33mu-ci-in-container.sh | 2 +- tools/scripts/run-m33mu-workflow.sh | 2 +- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/stm32h563-m33mu-freertos.yml b/.github/workflows/stm32h563-m33mu-freertos.yml index f8b3adf..aae6b32 100644 --- a/.github/workflows/stm32h563-m33mu-freertos.yml +++ b/.github/workflows/stm32h563-m33mu-freertos.yml @@ -9,7 +9,7 @@ jobs: stm32h563_m33mu_echo_freertos: runs-on: ubuntu-latest container: - image: ghcr.io/danielinux/m33mu-ci:1.8 + image: ghcr.io/wolfssl/wolfboot-ci:v1.2 options: --privileged steps: - name: Checkout diff --git a/.github/workflows/stm32h563-m33mu-ssh-tzen.yml b/.github/workflows/stm32h563-m33mu-ssh-tzen.yml index 6f19b91..276caf2 100644 --- a/.github/workflows/stm32h563-m33mu-ssh-tzen.yml +++ b/.github/workflows/stm32h563-m33mu-ssh-tzen.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 25 container: - image: ghcr.io/danielinux/m33mu-ci:1.8 + image: ghcr.io/wolfssl/wolfboot-ci:v1.2 options: --privileged steps: @@ -72,7 +72,7 @@ jobs: dhcp-leasefile=/tmp/dnsmasq.leases log-dhcp CONF - sudo dnsmasq --conf-file=/tmp/dnsmasq.conf --pid-file=/tmp/dnsmasq.pid + sudo dnsmasq --no-poll --conf-file=/tmp/dnsmasq.conf --pid-file=/tmp/dnsmasq.pid sudo m33mu src/port/stm32h563/app.bin \ --cpu stm32h563 --tap:tap0 --uart-stdout --timeout 180 --quit-on-faults \ diff --git a/.github/workflows/stm32h563-m33mu.yml b/.github/workflows/stm32h563-m33mu.yml index 725cf0e..3c68fb0 100644 --- a/.github/workflows/stm32h563-m33mu.yml +++ b/.github/workflows/stm32h563-m33mu.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 20 container: - image: ghcr.io/danielinux/m33mu-ci:1.8 + image: ghcr.io/wolfssl/wolfboot-ci:v1.2 options: --privileged steps: @@ -57,7 +57,7 @@ jobs: dhcp-leasefile=/tmp/dnsmasq.leases log-dhcp EOF - sudo dnsmasq --conf-file=/tmp/dnsmasq.conf --pid-file=/tmp/dnsmasq.pid + sudo dnsmasq --no-poll --conf-file=/tmp/dnsmasq.conf --pid-file=/tmp/dnsmasq.pid sudo m33mu src/port/stm32h563/app.bin \ --cpu stm32h563 --tap:tap0 --uart-stdout --timeout 120 \ @@ -114,7 +114,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 30 container: - image: ghcr.io/danielinux/m33mu-ci:1.8 + image: ghcr.io/wolfssl/wolfboot-ci:v1.2 options: --privileged steps: @@ -173,7 +173,7 @@ jobs: dhcp-leasefile=/tmp/dnsmasq.leases log-dhcp EOF - sudo dnsmasq --conf-file=/tmp/dnsmasq.conf --pid-file=/tmp/dnsmasq.pid + sudo dnsmasq --no-poll --conf-file=/tmp/dnsmasq.conf --pid-file=/tmp/dnsmasq.pid sudo m33mu src/port/stm32h563/app.bin \ --cpu stm32h563 --tap:tap0 --uart-stdout --timeout 240 \ @@ -323,7 +323,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 25 container: - image: ghcr.io/danielinux/m33mu-ci:1.8 + image: ghcr.io/wolfssl/wolfboot-ci:v1.2 options: --privileged steps: @@ -385,7 +385,7 @@ jobs: dhcp-leasefile=/tmp/dnsmasq.leases log-dhcp CONF - sudo dnsmasq --conf-file=/tmp/dnsmasq.conf --pid-file=/tmp/dnsmasq.pid + sudo dnsmasq --no-poll --conf-file=/tmp/dnsmasq.conf --pid-file=/tmp/dnsmasq.pid sudo tcpdump -i tap0 -nn -U -w /tmp/https-test.pcap > /tmp/tcpdump.log 2>&1 & echo $! > /tmp/tcpdump.pid diff --git a/tools/scripts/run-m33mu-ci-in-container.sh b/tools/scripts/run-m33mu-ci-in-container.sh index 22478fd..539575e 100755 --- a/tools/scripts/run-m33mu-ci-in-container.sh +++ b/tools/scripts/run-m33mu-ci-in-container.sh @@ -145,7 +145,7 @@ dhcp-range=192.168.12.50,192.168.12.100,255.255.255.0,12h dhcp-leasefile=/tmp/dnsmasq.leases log-dhcp EOF - run_root dnsmasq --conf-file=/tmp/dnsmasq.conf --pid-file=/tmp/dnsmasq.pid + run_root dnsmasq --no-poll --conf-file=/tmp/dnsmasq.conf --pid-file=/tmp/dnsmasq.pid } start_m33mu() { diff --git a/tools/scripts/run-m33mu-workflow.sh b/tools/scripts/run-m33mu-workflow.sh index f712900..72972a9 100755 --- a/tools/scripts/run-m33mu-workflow.sh +++ b/tools/scripts/run-m33mu-workflow.sh @@ -75,7 +75,7 @@ script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" repo_root="$(cd "${script_dir}/../.." && pwd)" workspace_root="$(cd "${repo_root}/.." && pwd)" repo_name="$(basename "${repo_root}")" -image="${M33MU_CI_IMAGE:-ghcr.io/danielinux/m33mu-ci:1.7}" +image="${M33MU_CI_IMAGE:-ghcr.io/wolfssl/wolfboot-ci:v1.2}" podman_tty_args=(--rm --privileged --security-opt label=disable) if [ -t 0 ] && [ -t 1 ]; then From fdcb9a545c53db025ecfaadd2a4f9e9c1184b0fc Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Tue, 31 Mar 2026 12:08:25 +0200 Subject: [PATCH 17/20] Fixed regressions and addressed copilot's comments --- .../workflows/stm32h563-m33mu-freertos.yml | 2 +- .../workflows/stm32h563-m33mu-ssh-tzen.yml | 4 +- .github/workflows/stm32h563-m33mu.yml | 12 +- src/test/unit/unit.c | 9 +- src/test/unit/unit_tests_dns_dhcp.c | 135 ++++++++++++++++-- src/test/unit/unit_tests_proto.c | 46 ++++++ src/test/unit/unit_tests_tcp_flow.c | 101 +++++++++++++ src/wolfip.c | 70 ++++++--- tools/scripts/run-m33mu-workflow.sh | 2 +- 9 files changed, 345 insertions(+), 36 deletions(-) diff --git a/.github/workflows/stm32h563-m33mu-freertos.yml b/.github/workflows/stm32h563-m33mu-freertos.yml index aae6b32..9af5a5b 100644 --- a/.github/workflows/stm32h563-m33mu-freertos.yml +++ b/.github/workflows/stm32h563-m33mu-freertos.yml @@ -9,7 +9,7 @@ jobs: stm32h563_m33mu_echo_freertos: runs-on: ubuntu-latest container: - image: ghcr.io/wolfssl/wolfboot-ci:v1.2 + image: ghcr.io/wolfssl/wolfboot-ci-m33mu:v1.2 options: --privileged steps: - name: Checkout diff --git a/.github/workflows/stm32h563-m33mu-ssh-tzen.yml b/.github/workflows/stm32h563-m33mu-ssh-tzen.yml index 276caf2..10cf326 100644 --- a/.github/workflows/stm32h563-m33mu-ssh-tzen.yml +++ b/.github/workflows/stm32h563-m33mu-ssh-tzen.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 25 container: - image: ghcr.io/wolfssl/wolfboot-ci:v1.2 + image: ghcr.io/wolfssl/wolfboot-ci-m33mu:v1.2 options: --privileged steps: @@ -72,7 +72,7 @@ jobs: dhcp-leasefile=/tmp/dnsmasq.leases log-dhcp CONF - sudo dnsmasq --no-poll --conf-file=/tmp/dnsmasq.conf --pid-file=/tmp/dnsmasq.pid + sudo dnsmasq --conf-file=/tmp/dnsmasq.conf --pid-file=/tmp/dnsmasq.pid sudo m33mu src/port/stm32h563/app.bin \ --cpu stm32h563 --tap:tap0 --uart-stdout --timeout 180 --quit-on-faults \ diff --git a/.github/workflows/stm32h563-m33mu.yml b/.github/workflows/stm32h563-m33mu.yml index 3c68fb0..ad1c9f0 100644 --- a/.github/workflows/stm32h563-m33mu.yml +++ b/.github/workflows/stm32h563-m33mu.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 20 container: - image: ghcr.io/wolfssl/wolfboot-ci:v1.2 + image: ghcr.io/wolfssl/wolfboot-ci-m33mu:v1.2 options: --privileged steps: @@ -57,7 +57,7 @@ jobs: dhcp-leasefile=/tmp/dnsmasq.leases log-dhcp EOF - sudo dnsmasq --no-poll --conf-file=/tmp/dnsmasq.conf --pid-file=/tmp/dnsmasq.pid + sudo dnsmasq --conf-file=/tmp/dnsmasq.conf --pid-file=/tmp/dnsmasq.pid sudo m33mu src/port/stm32h563/app.bin \ --cpu stm32h563 --tap:tap0 --uart-stdout --timeout 120 \ @@ -114,7 +114,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 30 container: - image: ghcr.io/wolfssl/wolfboot-ci:v1.2 + image: ghcr.io/wolfssl/wolfboot-ci-m33mu:v1.2 options: --privileged steps: @@ -173,7 +173,7 @@ jobs: dhcp-leasefile=/tmp/dnsmasq.leases log-dhcp EOF - sudo dnsmasq --no-poll --conf-file=/tmp/dnsmasq.conf --pid-file=/tmp/dnsmasq.pid + sudo dnsmasq --conf-file=/tmp/dnsmasq.conf --pid-file=/tmp/dnsmasq.pid sudo m33mu src/port/stm32h563/app.bin \ --cpu stm32h563 --tap:tap0 --uart-stdout --timeout 240 \ @@ -323,7 +323,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 25 container: - image: ghcr.io/wolfssl/wolfboot-ci:v1.2 + image: ghcr.io/wolfssl/wolfboot-ci-m33mu:v1.2 options: --privileged steps: @@ -385,7 +385,7 @@ jobs: dhcp-leasefile=/tmp/dnsmasq.leases log-dhcp CONF - sudo dnsmasq --no-poll --conf-file=/tmp/dnsmasq.conf --pid-file=/tmp/dnsmasq.pid + sudo dnsmasq --conf-file=/tmp/dnsmasq.conf --pid-file=/tmp/dnsmasq.pid sudo tcpdump -i tap0 -nn -U -w /tmp/https-test.pcap > /tmp/tcpdump.log 2>&1 & echo $! > /tmp/tcpdump.pid diff --git a/src/test/unit/unit.c b/src/test/unit/unit.c index 4c0d5ca..6b48fae 100644 --- a/src/test/unit/unit.c +++ b/src/test/unit/unit.c @@ -342,7 +342,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_utils, test_udp_try_recv_remote_ip_matches_local_ip); tcase_add_test(tc_utils, test_udp_try_recv_unmatched_port_sends_icmp_unreachable); tcase_add_test(tc_utils, test_udp_try_recv_unmatched_nonlocal_dst_does_not_send_icmp); - tcase_add_test(tc_utils, test_udp_try_recv_full_fifo_drop_does_not_set_readable_or_suppress_icmp); + tcase_add_test(tc_utils, test_udp_try_recv_full_fifo_drop_does_not_set_readable_or_send_icmp); tcase_add_test(tc_utils, test_dns_callback_bad_flags); tcase_add_test(tc_utils, test_dns_callback_truncated_response_aborts_query); tcase_add_test(tc_utils, test_dns_callback_bad_name); @@ -426,9 +426,11 @@ Suite *wolf_suite(void) tcase_add_test(tc_utils, test_tcp_rto_cb_syn_sent_requeues_syn_and_arms_timer); tcase_add_test(tc_utils, test_tcp_input_synack_cancels_control_rto); tcase_add_test(tc_utils, test_tcp_rto_cb_last_ack_requeues_finack_and_arms_timer); + tcase_add_test(tc_utils, test_tcp_rto_cb_last_ack_full_txbuf_keeps_retry_budget); tcase_add_test(tc_utils, test_tcp_ctrl_state_needs_rto_fin_wait_1_waits_for_payload_drain); tcase_add_test(tc_utils, test_tcp_rto_cb_fin_wait_1_with_data_uses_data_recovery); tcase_add_test(tc_utils, test_tcp_rto_cb_fin_wait_1_no_data_requeues_finack); + tcase_add_test(tc_utils, test_tcp_rto_cb_fin_wait_1_no_data_full_txbuf_keeps_retry_budget); tcase_add_test(tc_utils, test_tcp_ack_fin_wait_1_ack_of_fin_moves_to_fin_wait_2_and_arms_timeout); tcase_add_test(tc_utils, test_tcp_ack_closing_ack_of_fin_moves_to_time_wait_and_stops_timer); tcase_add_test(tc_utils, test_tcp_rto_cb_control_retry_cap_closes_socket); @@ -440,6 +442,8 @@ Suite *wolf_suite(void) tcase_add_test(tc_utils, test_sock_close_udp_icmp); tcase_add_test(tc_utils, test_sock_close_invalid_fds); tcase_add_test(tc_utils, test_sock_close_tcp_fin_wait_1); + tcase_add_test(tc_utils, test_sock_close_tcp_established_full_txbuf_preserves_state); + tcase_add_test(tc_utils, test_sock_close_tcp_close_wait_full_txbuf_preserves_state); tcase_add_test(tc_utils, test_sock_close_tcp_other_state_closes); tcase_add_test(tc_utils, test_sock_close_tcp_cancels_rto_timer); tcase_add_test(tc_utils, test_sock_close_tcp_closed_returns_minus_one); @@ -470,6 +474,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_utils, test_tcp_input_filter_drop); tcase_add_test(tc_utils, test_tcp_input_port_mismatch_skips_socket); tcase_add_test(tc_utils, test_tcp_input_remote_ip_mismatch_skips_socket); + tcase_add_test(tc_utils, test_tcp_input_local_ip_mismatch_preserves_if_idx); tcase_add_test(tc_utils, test_tcp_input_unmatched_ack_sends_rst); tcase_add_test(tc_utils, test_tcp_input_unmatched_ack_nonlocal_dst_does_not_send_rst); tcase_add_test(tc_utils, test_tcp_input_unmatched_syn_sends_rst_ack); @@ -744,6 +749,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_proto, test_regression_syn_on_established_not_silently_processed); tcase_add_test(tc_proto, test_regression_syn_on_last_ack_not_silently_processed); tcase_add_test(tc_proto, test_regression_full_txbuf_still_sends_pure_ack); + tcase_add_test(tc_proto, test_regression_loopback_immediate_pure_ack_uses_loopback_ll); tcase_add_test(tc_proto, test_regression_fast_recovery_cwnd_ssthresh_rfc5681); tcase_add_test(tc_proto, test_regression_paws_rejects_stale_timestamp); tcase_add_test(tc_proto, test_regression_paws_accepts_wrapped_newer_timestamp); @@ -754,6 +760,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_proto, test_regression_last_ack_rejects_out_of_window_segment); tcase_add_test(tc_proto, test_regression_dns_id_never_zero); tcase_add_test(tc_proto, test_tcp_input_listen_synack_sends_rst_and_stays_listen); + tcase_add_test(tc_proto, test_tcp_input_listen_accept_final_ack_does_not_send_rst); tcase_add_test(tc_utils, test_transport_checksum); tcase_add_test(tc_utils, test_iphdr_set_checksum); diff --git a/src/test/unit/unit_tests_dns_dhcp.c b/src/test/unit/unit_tests_dns_dhcp.c index 6067b9e..0cf256c 100644 --- a/src/test/unit/unit_tests_dns_dhcp.c +++ b/src/test/unit/unit_tests_dns_dhcp.c @@ -3241,6 +3241,38 @@ START_TEST(test_tcp_rto_cb_last_ack_requeues_finack_and_arms_timer) } END_TEST +START_TEST(test_tcp_rto_cb_last_ack_full_txbuf_keeps_retry_budget) +{ + struct wolfIP s; + struct tsocket *ts; + + wolfIP_init(&s); + ts = &s.tcpsockets[0]; + memset(ts, 0, sizeof(*ts)); + ts->proto = WI_IPPROTO_TCP; + ts->S = &s; + ts->sock.tcp.state = TCP_LAST_ACK; + ts->sock.tcp.rto = 100; + ts->sock.tcp.ctrl_rto_retries = 2; + ts->src_port = 12345; + ts->dst_port = 5001; + ts->local_ip = 0x0A000001U; + ts->remote_ip = 0x0A000002U; + fifo_init(&ts->sock.tcp.txbuf, ts->txmem, TXBUF_SIZE); + ts->sock.tcp.txbuf.head = 0; + ts->sock.tcp.txbuf.tail = 0; + ts->sock.tcp.txbuf.h_wrap = ts->sock.tcp.txbuf.size; + + s.last_tick = 1000; + tcp_rto_cb(ts); + + ck_assert_uint_eq(ts->sock.tcp.ctrl_rto_retries, 2); + ck_assert_uint_eq(ts->sock.tcp.ctrl_rto_active, 1); + ck_assert_int_ne(ts->sock.tcp.tmr_rto, NO_TIMER); + ck_assert_uint_eq(find_timer_expiry(&s, ts->sock.tcp.tmr_rto), 1400U); +} +END_TEST + START_TEST(test_tcp_ctrl_state_needs_rto_fin_wait_1_waits_for_payload_drain) { struct wolfIP s; @@ -3333,6 +3365,39 @@ START_TEST(test_tcp_rto_cb_fin_wait_1_no_data_requeues_finack) } END_TEST +START_TEST(test_tcp_rto_cb_fin_wait_1_no_data_full_txbuf_keeps_retry_budget) +{ + struct wolfIP s; + struct tsocket *ts; + + wolfIP_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_1; + ts->sock.tcp.rto = 100; + ts->sock.tcp.ctrl_rto_retries = 2; + ts->src_port = 12345; + ts->dst_port = 5001; + ts->local_ip = 0x0A000001U; + ts->remote_ip = 0x0A000002U; + ts->sock.tcp.bytes_in_flight = 0; + fifo_init(&ts->sock.tcp.txbuf, ts->txmem, TXBUF_SIZE); + ts->sock.tcp.txbuf.head = 0; + ts->sock.tcp.txbuf.tail = 0; + ts->sock.tcp.txbuf.h_wrap = ts->sock.tcp.txbuf.size; + + s.last_tick = 1000; + tcp_rto_cb(ts); + + ck_assert_uint_eq(ts->sock.tcp.ctrl_rto_retries, 2); + ck_assert_uint_eq(ts->sock.tcp.ctrl_rto_active, 1); + ck_assert_int_ne(ts->sock.tcp.tmr_rto, NO_TIMER); + ck_assert_uint_eq(find_timer_expiry(&s, ts->sock.tcp.tmr_rto), 1400U); +} +END_TEST + START_TEST(test_tcp_ack_fin_wait_1_ack_of_fin_moves_to_fin_wait_2_and_arms_timeout) { struct wolfIP s; @@ -3990,6 +4055,66 @@ START_TEST(test_sock_close_tcp_fin_wait_1) } END_TEST +START_TEST(test_sock_close_tcp_established_full_txbuf_preserves_state) +{ + struct wolfIP s; + struct tsocket *ts; + int sd; + + wolfIP_init(&s); + mock_link_init(&s); + + sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_STREAM, WI_IPPROTO_TCP); + ck_assert_int_gt(sd, 0); + ts = &s.tcpsockets[SOCKET_UNMARK(sd)]; + ts->sock.tcp.state = TCP_ESTABLISHED; + ts->src_port = 12345; + ts->dst_port = 5001; + ts->local_ip = 0x0A000001U; + ts->remote_ip = 0x0A000002U; + fifo_init(&ts->sock.tcp.txbuf, ts->txmem, TXBUF_SIZE); + ts->sock.tcp.txbuf.head = 0; + ts->sock.tcp.txbuf.tail = 0; + ts->sock.tcp.txbuf.h_wrap = ts->sock.tcp.txbuf.size; + + ck_assert_int_eq(wolfIP_sock_close(&s, sd), -WOLFIP_EAGAIN); + ck_assert_int_eq(ts->sock.tcp.state, TCP_ESTABLISHED); + ck_assert_uint_eq(ts->sock.tcp.ctrl_rto_active, 0); + ck_assert_uint_eq(ts->sock.tcp.ctrl_rto_retries, 0); + ck_assert_uint_eq(ts->sock.tcp.tmr_rto, NO_TIMER); +} +END_TEST + +START_TEST(test_sock_close_tcp_close_wait_full_txbuf_preserves_state) +{ + struct wolfIP s; + struct tsocket *ts; + int sd; + + wolfIP_init(&s); + mock_link_init(&s); + + sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_STREAM, WI_IPPROTO_TCP); + ck_assert_int_gt(sd, 0); + ts = &s.tcpsockets[SOCKET_UNMARK(sd)]; + ts->sock.tcp.state = TCP_CLOSE_WAIT; + ts->src_port = 12345; + ts->dst_port = 5001; + ts->local_ip = 0x0A000001U; + ts->remote_ip = 0x0A000002U; + fifo_init(&ts->sock.tcp.txbuf, ts->txmem, TXBUF_SIZE); + ts->sock.tcp.txbuf.head = 0; + ts->sock.tcp.txbuf.tail = 0; + ts->sock.tcp.txbuf.h_wrap = ts->sock.tcp.txbuf.size; + + ck_assert_int_eq(wolfIP_sock_close(&s, sd), -WOLFIP_EAGAIN); + ck_assert_int_eq(ts->sock.tcp.state, TCP_CLOSE_WAIT); + ck_assert_uint_eq(ts->sock.tcp.ctrl_rto_active, 0); + ck_assert_uint_eq(ts->sock.tcp.ctrl_rto_retries, 0); + ck_assert_uint_eq(ts->sock.tcp.tmr_rto, NO_TIMER); +} +END_TEST + START_TEST(test_sock_close_tcp_fin_wait_1_repeated_close_keeps_fin_wait_2_path) { struct wolfIP s; @@ -4751,13 +4876,12 @@ START_TEST(test_udp_try_recv_unmatched_nonlocal_dst_does_not_send_icmp) } END_TEST -START_TEST(test_udp_try_recv_full_fifo_drop_does_not_set_readable_or_suppress_icmp) +START_TEST(test_udp_try_recv_full_fifo_drop_does_not_set_readable_or_send_icmp) { struct wolfIP s; struct tsocket *ts; uint8_t udp_buf[sizeof(struct wolfIP_udp_datagram) + 4]; struct wolfIP_udp_datagram *udp = (struct wolfIP_udp_datagram *)udp_buf; - struct wolfIP_icmp_dest_unreachable_packet *icmp; uint32_t local_ip = 0x0A000001U; uint32_t remote_ip = 0x0A000002U; uint8_t src_mac[6] = {0x20, 0x21, 0x22, 0x23, 0x24, 0x25}; @@ -4811,12 +4935,7 @@ START_TEST(test_udp_try_recv_full_fifo_drop_does_not_set_readable_or_suppress_ic ck_assert_uint_eq(ts->sock.udp.rxbuf.head, head_before); ck_assert_uint_eq(ts->sock.udp.rxbuf.tail, tail_before); ck_assert_uint_eq(ts->sock.udp.rxbuf.h_wrap, h_wrap_before); - ck_assert_uint_eq(last_frame_sent_size, - sizeof(struct wolfIP_icmp_dest_unreachable_packet)); - - icmp = (struct wolfIP_icmp_dest_unreachable_packet *)last_frame_sent; - ck_assert_uint_eq(icmp->type, 3U); - ck_assert_uint_eq(icmp->code, 3U); + ck_assert_uint_eq(last_frame_sent_size, 0U); } END_TEST diff --git a/src/test/unit/unit_tests_proto.c b/src/test/unit/unit_tests_proto.c index 03ec324..5223fba 100644 --- a/src/test/unit/unit_tests_proto.c +++ b/src/test/unit/unit_tests_proto.c @@ -4754,6 +4754,52 @@ START_TEST(test_regression_full_txbuf_still_sends_pure_ack) } END_TEST +START_TEST(test_regression_loopback_immediate_pure_ack_uses_loopback_ll) +{ + struct wolfIP s; + struct tsocket *ts; + struct wolfIP_ll_dev *loop; + struct wolfIP_tcp_seg seg; + + wolfIP_init(&s); + loop = wolfIP_getdev_ex(&s, TEST_LOOPBACK_IF); + ck_assert_ptr_nonnull(loop); + loop->send = mock_send; + last_frame_sent_size = 0; + memset(last_frame_sent, 0, sizeof(last_frame_sent)); + + ts = &s.tcpsockets[0]; + memset(ts, 0, sizeof(*ts)); + ts->proto = WI_IPPROTO_TCP; + ts->S = &s; + ts->if_idx = TEST_LOOPBACK_IF; + ts->sock.tcp.state = TCP_ESTABLISHED; + ts->sock.tcp.ack = 100; + ts->sock.tcp.seq = 1000; + ts->sock.tcp.snd_una = 900; + ts->sock.tcp.cwnd = TXBUF_SIZE; + ts->sock.tcp.peer_rwnd = TXBUF_SIZE; + ts->src_port = 1234; + ts->dst_port = 4321; + ts->local_ip = 0x7F000001U; + ts->remote_ip = 0x7F000001U; + memset(&seg, 0, sizeof(seg)); + seg.src_port = ee16(ts->src_port); + seg.dst_port = ee16(ts->dst_port); + seg.seq = ee32(ts->sock.tcp.seq); + seg.ack = ee32(ts->sock.tcp.ack); + seg.hlen = TCP_HEADER_LEN << 2; + seg.flags = TCP_FLAG_ACK; + + ck_assert_int_eq(tcp_send_empty_immediate(ts, &seg, + (uint32_t)sizeof(seg)), 0); + ck_assert_uint_eq(ts->sock.tcp.last_ack, ts->sock.tcp.ack); + ck_assert_uint_eq(last_frame_sent_size, (uint32_t)sizeof(seg)); + ck_assert_mem_eq(seg.ip.eth.dst, loop->mac, 6); + ck_assert_mem_eq(seg.ip.eth.src, loop->mac, 6); +} +END_TEST + /* RFC 5681 §3.2: fast recovery deviates in multiple ways. * (a) ssthresh uses cwnd/2 instead of max(FlightSize/2, 2*SMSS) diff --git a/src/test/unit/unit_tests_tcp_flow.c b/src/test/unit/unit_tests_tcp_flow.c index f7139ce..9ca63c5 100644 --- a/src/test/unit/unit_tests_tcp_flow.c +++ b/src/test/unit/unit_tests_tcp_flow.c @@ -3676,6 +3676,7 @@ START_TEST(test_tcp_input_remote_ip_mismatch_skips_socket) ts->dst_port = 4321; ts->local_ip = 0x0A000001U; ts->remote_ip = 0x0A000002U; + ts->if_idx = TEST_SECOND_IF; ts->sock.tcp.peer_rwnd = 100; memset(&seg, 0, sizeof(seg)); @@ -3695,6 +3696,50 @@ START_TEST(test_tcp_input_remote_ip_mismatch_skips_socket) tcp_input(&s, TEST_PRIMARY_IF, &seg, (uint32_t)(ETH_HEADER_LEN + IP_HEADER_LEN + TCP_HEADER_LEN)); ck_assert_int_eq(ts->sock.tcp.state, TCP_ESTABLISHED); ck_assert_uint_eq(ts->sock.tcp.peer_rwnd, 100); + ck_assert_uint_eq(ts->if_idx, TEST_SECOND_IF); +} +END_TEST + +START_TEST(test_tcp_input_local_ip_mismatch_preserves_if_idx) +{ + struct wolfIP s; + struct wolfIP_tcp_seg seg; + 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_ESTABLISHED; + ts->src_port = 1234; + ts->dst_port = 4321; + ts->local_ip = 0x0A000001U; + ts->remote_ip = 0x0A000002U; + ts->if_idx = TEST_SECOND_IF; + ts->sock.tcp.peer_rwnd = 100; + + memset(&seg, 0, sizeof(seg)); + seg.ip.ver_ihl = 0x45; + seg.ip.ttl = 64; + seg.ip.proto = WI_IPPROTO_TCP; + seg.ip.len = ee16(IP_HEADER_LEN + TCP_HEADER_LEN); + seg.ip.src = ee32(ts->remote_ip); + seg.ip.dst = ee32(0x0A000003U); + seg.dst_port = ee16(ts->src_port); + seg.src_port = ee16(ts->dst_port); + seg.hlen = TCP_HEADER_LEN << 2; + seg.flags = TCP_FLAG_ACK; + seg.win = ee16(777); + fix_tcp_checksums(&seg); + + tcp_input(&s, TEST_PRIMARY_IF, &seg, (uint32_t)(ETH_HEADER_LEN + IP_HEADER_LEN + TCP_HEADER_LEN)); + ck_assert_int_eq(ts->sock.tcp.state, TCP_ESTABLISHED); + ck_assert_uint_eq(ts->sock.tcp.peer_rwnd, 100); + ck_assert_uint_eq(ts->if_idx, TEST_SECOND_IF); } END_TEST @@ -3830,6 +3875,62 @@ START_TEST(test_tcp_input_listen_synack_sends_rst_and_stays_listen) } END_TEST +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; + + 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); + + inject_tcp_segment(&s, TEST_PRIMARY_IF, 0x0A000002U, 0x0A000001U, + 4321, 1234, 77, 0, TCP_FLAG_SYN); + + 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(listen_ts->sock.tcp.state, TCP_LISTEN); + ck_assert_int_eq(client_ts->sock.tcp.state, TCP_SYN_RCVD); + + last_frame_sent_size = 0; + memset(last_frame_sent, 0, sizeof(last_frame_sent)); + + inject_tcp_segment(&s, TEST_PRIMARY_IF, 0x0A000002U, 0x0A000001U, + 4321, 1234, client_ts->sock.tcp.ack, + tcp_seq_inc(client_ts->sock.tcp.snd_una, 1), TCP_FLAG_ACK); + + ck_assert_uint_eq(last_frame_sent_size, 0U); + ck_assert_int_eq(listen_ts->sock.tcp.state, TCP_LISTEN); + ck_assert_int_eq(client_ts->sock.tcp.state, TCP_ESTABLISHED); + + inject_tcp_segment(&s, TEST_PRIMARY_IF, 0x0A000002U, 0x0A000001U, + 4321, 1234, client_ts->sock.tcp.ack, client_ts->sock.tcp.seq, TCP_FLAG_ACK); + + ck_assert_uint_eq(last_frame_sent_size, 0U); + ck_assert_int_eq(listen_ts->sock.tcp.state, TCP_LISTEN); + ck_assert_int_eq(client_ts->sock.tcp.state, TCP_ESTABLISHED); +} +END_TEST + START_TEST(test_tcp_input_syn_bound_ip_mismatch) { struct wolfIP s; diff --git a/src/wolfip.c b/src/wolfip.c index 614c3c3..2c9cb93 100644 --- a/src/wolfip.c +++ b/src/wolfip.c @@ -1910,10 +1910,11 @@ static void udp_try_recv(struct wolfIP *s, unsigned int if_idx, expected_len = ee16(udp->len) + IP_HEADER_LEN + ETH_HEADER_LEN; if ((int)frame_len < (int)expected_len) return; - /* Insert into socket buffer */ + /* A bound socket matched this datagram. If the RX FIFO is full, + * drop silently instead of misreporting the port as closed. */ + matched = 1; if (fifo_push(&t->sock.udp.rxbuf, udp, frame_len) == 0) { t->events |= CB_EVENT_READABLE; - matched = 1; } } } @@ -2498,10 +2499,12 @@ static int tcp_send_empty_immediate(struct tsocket *t, struct wolfIP_tcp_seg *tc ll = wolfIP_ll_at(t->S, tx_if); if (!ll) return -1; - if (wolfIP_is_loopback_if(tx_if)) - return -1; #ifdef ETHERNET - if (!wolfIP_ll_is_non_ethernet(t->S, tx_if)) { + if (wolfIP_is_loopback_if(tx_if)) { + if (t->local_ip == IPADDR_ANY || t->remote_ip == IPADDR_ANY) + return -1; + memcpy(t->nexthop_mac, ll->mac, 6); + } else if (!wolfIP_ll_is_non_ethernet(t->S, tx_if)) { struct ipconf *conf = wolfIP_ipconf_at(t->S, tx_if); ip4 nexthop; @@ -2537,7 +2540,7 @@ static int tcp_send_empty_immediate(struct tsocket *t, struct wolfIP_tcp_seg *tc return 0; } -static void tcp_send_empty(struct tsocket *t, uint8_t flags) +static int tcp_send_empty(struct tsocket *t, uint8_t flags) { struct wolfIP_tcp_seg *tcp; uint8_t opt_len; @@ -2557,17 +2560,18 @@ static void tcp_send_empty(struct tsocket *t, uint8_t flags) tcp->urg = 0; frame_len = sizeof(struct wolfIP_tcp_seg) + opt_len; if (fifo_push(&t->sock.tcp.txbuf, tcp, frame_len) == 0) - return; + return 0; /* Pure ACKs have no retransmission path, so do not drop them when the * shared data/control TX FIFO is saturated by already queued payload. */ if (flags == TCP_FLAG_ACK) - (void)tcp_send_empty_immediate(t, tcp, frame_len); + return tcp_send_empty_immediate(t, tcp, frame_len); + return -1; } static void tcp_send_ack(struct tsocket *t) { - return tcp_send_empty(t, TCP_FLAG_ACK); + (void)tcp_send_empty(t, TCP_FLAG_ACK); } static void tcp_send_reset_reply(struct wolfIP *s, unsigned int if_idx, @@ -2644,10 +2648,12 @@ static void tcp_send_reset_reply(struct wolfIP *s, unsigned int if_idx, wolfIP_ll_send_frame(s, if_idx, &out.ip, sizeof(out)); } -static void tcp_send_finack(struct tsocket *t) +static int tcp_send_finack(struct tsocket *t) { - tcp_send_empty(t, TCP_FLAG_FIN | TCP_FLAG_ACK); + if (tcp_send_empty(t, TCP_FLAG_FIN | TCP_FLAG_ACK) < 0) + return -1; t->sock.tcp.last = t->sock.tcp.seq; + return 0; } static int tcp_send_syn(struct tsocket *t, uint8_t flags) @@ -3766,6 +3772,32 @@ static void tcp_ack(struct tsocket *t, const struct wolfIP_tcp_seg *tcp) } +static int tcp_listen_ack_matches_child_socket(struct wolfIP *S, + const struct tsocket *listener, const struct wolfIP_tcp_seg *tcp) +{ + int i; + uint16_t local_port = ee16(tcp->dst_port); + uint16_t remote_port = ee16(tcp->src_port); + ip4 local_ip = ee32(tcp->ip.dst); + ip4 remote_ip = ee32(tcp->ip.src); + + for (i = 0; i < MAX_TCPSOCKETS; i++) { + const struct tsocket *t = &S->tcpsockets[i]; + + if (t == listener || t->proto == 0 || t->S == NULL) + continue; + if (t->sock.tcp.state <= TCP_LISTEN) + continue; + if (t->src_port != local_port || t->dst_port != remote_port) + continue; + if (t->local_ip != local_ip || t->remote_ip != remote_ip) + continue; + return 1; + } + + return 0; +} + /* Preselect socket, parse options, manage handshakes, pass to application */ static void tcp_input(struct wolfIP *S, unsigned int if_idx, struct wolfIP_tcp_seg *tcp, uint32_t frame_len) @@ -3809,7 +3841,6 @@ static void tcp_input(struct wolfIP *S, unsigned int if_idx, if (t->proto == 0 || t->S == NULL) continue; if (t->src_port == ee16(tcp->dst_port)) { - t->if_idx = (uint8_t)if_idx; /* TCP segment sanity checks */ iplen = ee16(tcp->ip.len); if (iplen > frame_len - ETH_HEADER_LEN) { @@ -3830,6 +3861,7 @@ static void tcp_input(struct wolfIP *S, unsigned int if_idx, continue; } } + t->if_idx = (uint8_t)if_idx; matched = 1; /* Validate minimum TCP header length (data offset). */ if ((tcp->hlen >> 2) < TCP_HEADER_LEN) { @@ -3845,7 +3877,8 @@ static void tcp_input(struct wolfIP *S, unsigned int if_idx, if (tcp->flags & TCP_FLAG_RST) continue; if (tcp->flags & TCP_FLAG_ACK) { - tcp_send_reset_reply(S, if_idx, tcp); + if (!tcp_listen_ack_matches_child_socket(S, t, tcp)) + tcp_send_reset_reply(S, if_idx, tcp); continue; } } @@ -4179,8 +4212,9 @@ static void tcp_rto_cb(void *arg) } else if (ts->sock.tcp.state == TCP_SYN_RCVD) { queued = (tcp_send_syn(ts, TCP_FLAG_SYN | TCP_FLAG_ACK) == 0); } else if (ts->sock.tcp.state == TCP_FIN_WAIT_1 || ts->sock.tcp.state == TCP_LAST_ACK) { - ts->sock.tcp.ctrl_rto_retries++; - tcp_send_finack(ts); + queued = (tcp_send_finack(ts) == 0); + if (queued) + ts->sock.tcp.ctrl_rto_retries++; tcp_ctrl_rto_start(ts, ts->S->last_tick); return; } @@ -5079,9 +5113,10 @@ int wolfIP_sock_close(struct wolfIP *s, int sockfd) return -WOLFIP_EINVAL; ts = &s->tcpsockets[SOCKET_UNMARK(sockfd)]; if (ts->sock.tcp.state == TCP_ESTABLISHED) { + if (tcp_send_finack(ts) < 0) + return -WOLFIP_EAGAIN; ts->sock.tcp.state = TCP_FIN_WAIT_1; ts->sock.tcp.ctrl_rto_retries = 0; - tcp_send_finack(ts); tcp_ctrl_rto_start(ts, s->last_tick); return -WOLFIP_EAGAIN; } else if (ts->sock.tcp.state == TCP_LISTEN) { @@ -5092,9 +5127,10 @@ int wolfIP_sock_close(struct wolfIP *s, int sockfd) close_socket(ts); return 0; } else if (ts->sock.tcp.state == TCP_CLOSE_WAIT) { + if (tcp_send_finack(ts) < 0) + return -WOLFIP_EAGAIN; ts->sock.tcp.state = TCP_LAST_ACK; ts->sock.tcp.ctrl_rto_retries = 0; - tcp_send_finack(ts); tcp_ctrl_rto_start(ts, s->last_tick); return -WOLFIP_EAGAIN; } else if (ts->sock.tcp.state == TCP_CLOSING) { diff --git a/tools/scripts/run-m33mu-workflow.sh b/tools/scripts/run-m33mu-workflow.sh index 72972a9..4866f00 100755 --- a/tools/scripts/run-m33mu-workflow.sh +++ b/tools/scripts/run-m33mu-workflow.sh @@ -75,7 +75,7 @@ script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" repo_root="$(cd "${script_dir}/../.." && pwd)" workspace_root="$(cd "${repo_root}/.." && pwd)" repo_name="$(basename "${repo_root}")" -image="${M33MU_CI_IMAGE:-ghcr.io/wolfssl/wolfboot-ci:v1.2}" +image="${M33MU_CI_IMAGE:-ghcr.io/wolfssl/wolfboot-ci-m33mu:v1.2}" podman_tty_args=(--rm --privileged --security-opt label=disable) if [ -t 0 ] && [ -t 1 ]; then From 1cce5e7134e216f7c76d0d2c7d101919afc0e572 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Tue, 31 Mar 2026 14:29:16 +0200 Subject: [PATCH 18/20] DHCP fixes (copilot's review) --- src/test/unit/unit_tests_dns_dhcp.c | 12 ++++++++++++ src/wolfip.c | 16 ++++++++++++---- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/src/test/unit/unit_tests_dns_dhcp.c b/src/test/unit/unit_tests_dns_dhcp.c index 0cf256c..d073a85 100644 --- a/src/test/unit/unit_tests_dns_dhcp.c +++ b/src/test/unit/unit_tests_dns_dhcp.c @@ -3876,6 +3876,7 @@ START_TEST(test_regression_dhcp_lease_expiry_deconfigures_address) { struct wolfIP s; struct ipconf *primary; + uint32_t stale_timeout_count; wolfIP_init(&s); mock_link_init(&s); @@ -3890,6 +3891,8 @@ START_TEST(test_regression_dhcp_lease_expiry_deconfigures_address) s.dhcp_server_ip = 0x0A000001U; s.last_tick = 1000U; s.dhcp_lease_expires = s.last_tick; + s.dhcp_timeout_count = 3U; + stale_timeout_count = s.dhcp_timeout_count; s.dhcp_state = DHCP_BOUND; dhcp_timer_cb(&s); @@ -3897,12 +3900,18 @@ START_TEST(test_regression_dhcp_lease_expiry_deconfigures_address) ck_assert_uint_eq(primary->ip, 0U); ck_assert_uint_eq(primary->mask, 0U); ck_assert_uint_eq(primary->gw, 0U); + ck_assert_uint_eq(s.dhcp_ip, 0U); + ck_assert_uint_eq(s.dhcp_server_ip, 0U); + ck_assert_uint_ne(stale_timeout_count, 0U); + ck_assert_uint_eq(s.dhcp_timeout_count, 0U); ck_assert_uint_ne(s.dhcp_timer, NO_TIMER); wolfIP_ipconfig_set(&s, 0x0A000064U, 0xFFFFFF00U, 0x0A000001U); s.dhcp_ip = primary->ip; + s.dhcp_server_ip = 0x0A000001U; s.last_tick = 2000U; s.dhcp_lease_expires = s.last_tick; + s.dhcp_timeout_count = 2U; s.dhcp_state = DHCP_REBINDING; dhcp_timer_cb(&s); @@ -3910,6 +3919,9 @@ START_TEST(test_regression_dhcp_lease_expiry_deconfigures_address) ck_assert_uint_eq(primary->ip, 0U); ck_assert_uint_eq(primary->mask, 0U); ck_assert_uint_eq(primary->gw, 0U); + ck_assert_uint_eq(s.dhcp_ip, 0U); + ck_assert_uint_eq(s.dhcp_server_ip, 0U); + ck_assert_uint_eq(s.dhcp_timeout_count, 0U); ck_assert_uint_ne(s.dhcp_timer, NO_TIMER); } END_TEST diff --git a/src/wolfip.c b/src/wolfip.c index 2c9cb93..ece6355 100644 --- a/src/wolfip.c +++ b/src/wolfip.c @@ -5491,6 +5491,7 @@ static int dhcp_send_discover(struct wolfIP *s); static int dhcp_send_request(struct wolfIP *s); 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) { @@ -5601,7 +5602,7 @@ static void dhcp_timer_cb(void *arg) case DHCP_BOUND: if (s->dhcp_lease_expires != 0 && s->last_tick >= s->dhcp_lease_expires) { dhcp_cancel_timer(s); - wolfIP_ipconfig_set(s, 0, 0, 0); + dhcp_deconfigure_lease(s); s->dhcp_state = DHCP_OFF; dhcp_send_discover(s); break; @@ -5626,7 +5627,7 @@ static void dhcp_timer_cb(void *arg) case DHCP_REBINDING: if (s->dhcp_lease_expires != 0 && s->last_tick >= s->dhcp_lease_expires) { dhcp_cancel_timer(s); - wolfIP_ipconfig_set(s, 0, 0, 0); + dhcp_deconfigure_lease(s); s->dhcp_state = DHCP_OFF; dhcp_send_discover(s); break; @@ -5642,16 +5643,23 @@ static void dhcp_timer_cb(void *arg) static void dhcp_cancel_timer(struct wolfIP *s) { + s->dhcp_timeout_count = 0; if (s->dhcp_timer != NO_TIMER) { timer_binheap_cancel(&s->timers, s->dhcp_timer); s->dhcp_timer = NO_TIMER; - s->dhcp_timeout_count = 0; } s->dhcp_renew_at = 0; s->dhcp_rebind_at = 0; s->dhcp_lease_expires = 0; } +static void dhcp_deconfigure_lease(struct wolfIP *s) +{ + wolfIP_ipconfig_set(s, 0, 0, 0); + s->dhcp_ip = 0; + s->dhcp_server_ip = 0; +} + #define DHCP_OPT_data_to_u32(opt) \ (((uint32_t)(opt)->data[0] << 24) | \ ((uint32_t)(opt)->data[1] << 16) | \ @@ -5957,7 +5965,7 @@ static int dhcp_poll(struct wolfIP *s) * it must restart the configuration process. */ if (dhcp_msg_type(s, &msg, (uint32_t)len) == DHCP_NAK) { dhcp_cancel_timer(s); - wolfIP_ipconfig_set(s, 0, 0, 0); + dhcp_deconfigure_lease(s); s->dhcp_state = DHCP_OFF; dhcp_send_discover(s); return 0; From eeaa47addc2e6daab6e90e81d4d343b3ffb917dd Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Tue, 31 Mar 2026 17:16:43 +0200 Subject: [PATCH 19/20] Fix handling of non-ethernet packets, pass desc as argument --- src/test/unit/unit.c | 1 + src/test/unit/unit_tests_proto.c | 35 ++++++++++++++++++++++++ src/wolfip.c | 47 +++++++++++++++++++++----------- 3 files changed, 67 insertions(+), 16 deletions(-) diff --git a/src/test/unit/unit.c b/src/test/unit/unit.c index 6b48fae..6c1a3a2 100644 --- a/src/test/unit/unit.c +++ b/src/test/unit/unit.c @@ -750,6 +750,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_proto, test_regression_syn_on_last_ack_not_silently_processed); tcase_add_test(tc_proto, test_regression_full_txbuf_still_sends_pure_ack); tcase_add_test(tc_proto, test_regression_loopback_immediate_pure_ack_uses_loopback_ll); + tcase_add_test(tc_proto, test_regression_tcp_tx_desc_payload_len_uses_link_type_not_length_heuristic); tcase_add_test(tc_proto, test_regression_fast_recovery_cwnd_ssthresh_rfc5681); tcase_add_test(tc_proto, test_regression_paws_rejects_stale_timestamp); tcase_add_test(tc_proto, test_regression_paws_accepts_wrapped_newer_timestamp); diff --git a/src/test/unit/unit_tests_proto.c b/src/test/unit/unit_tests_proto.c index 5223fba..2f3e542 100644 --- a/src/test/unit/unit_tests_proto.c +++ b/src/test/unit/unit_tests_proto.c @@ -4800,6 +4800,41 @@ START_TEST(test_regression_loopback_immediate_pure_ack_uses_loopback_ll) } END_TEST +START_TEST(test_regression_tcp_tx_desc_payload_len_uses_link_type_not_length_heuristic) +{ + struct wolfIP s; + struct tsocket *ts; + struct pkt_desc desc; + struct wolfIP_tcp_seg seg; + + 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->if_idx = TEST_PRIMARY_IF; + + memset(&desc, 0, sizeof(desc)); + memset(&seg, 0, sizeof(seg)); + seg.hlen = TCP_HEADER_LEN << 2; + seg.ip.len = 0; + + /* Short Ethernet-backed descriptors must not be treated as if desc->len + * were already an IP length. */ + desc.len = IP_HEADER_LEN + TCP_HEADER_LEN + 4; + ck_assert_uint_eq(tcp_tx_desc_ip_len(ts, &desc, &seg), 0U); + ck_assert_uint_eq(tcp_tx_desc_payload_len(ts, &desc, &seg), 0U); + + /* The same descriptor length is valid on non-Ethernet/L3 links. */ + s.ll_dev[TEST_PRIMARY_IF].non_ethernet = 1; + ck_assert_uint_eq(tcp_tx_desc_ip_len(ts, &desc, &seg), + (uint32_t)desc.len); + ck_assert_uint_eq(tcp_tx_desc_payload_len(ts, &desc, &seg), 4U); +} +END_TEST + /* RFC 5681 §3.2: fast recovery deviates in multiple ways. * (a) ssthresh uses cwnd/2 instead of max(FlightSize/2, 2*SMSS) diff --git a/src/wolfip.c b/src/wolfip.c index ece6355..c98b30b 100644 --- a/src/wolfip.c +++ b/src/wolfip.c @@ -2824,22 +2824,37 @@ static void tcp_fin_wait_2_timeout_stop(struct tsocket *t) t->sock.tcp.fin_wait_2_timeout_active = 0; } -static uint32_t tcp_tx_desc_payload_len(const struct pkt_desc *desc, - const struct wolfIP_tcp_seg *seg) +static uint32_t tcp_tx_desc_ip_len(const struct tsocket *t, + const struct pkt_desc *desc, const struct wolfIP_tcp_seg *seg) { uint32_t seg_ip_len; uint32_t seg_hdr_len; + unsigned int tx_if; - if (!desc || !seg) + if (!t || !desc || !seg) return 0; seg_hdr_len = IP_HEADER_LEN + (uint32_t)(seg->hlen >> 2); seg_ip_len = ee16(seg->ip.len); - if (seg_ip_len == 0) { - if (desc->len >= (ETH_HEADER_LEN + seg_hdr_len)) - seg_ip_len = desc->len - ETH_HEADER_LEN; - else - seg_ip_len = desc->len; - } + if (seg_ip_len != 0) + return seg_ip_len; + tx_if = wolfIP_socket_if_idx(t); + if (wolfIP_ll_is_non_ethernet(t->S, tx_if)) + return desc->len; + if (desc->len < (ETH_HEADER_LEN + seg_hdr_len)) + return 0; + return desc->len - ETH_HEADER_LEN; +} + +static uint32_t tcp_tx_desc_payload_len(const struct tsocket *t, + const struct pkt_desc *desc, const struct wolfIP_tcp_seg *seg) +{ + uint32_t seg_ip_len; + uint32_t seg_hdr_len; + + if (!t || !desc || !seg) + return 0; + seg_ip_len = tcp_tx_desc_ip_len(t, desc, seg); + seg_hdr_len = IP_HEADER_LEN + (uint32_t)(seg->hlen >> 2); if (seg_ip_len <= seg_hdr_len) return 0; return seg_ip_len - seg_hdr_len; @@ -2859,7 +2874,7 @@ static int tcp_has_pending_unsent_payload(struct tsocket *t) struct wolfIP_tcp_seg *seg; uint32_t seg_len; seg = (struct wolfIP_tcp_seg *)(t->txmem + desc->pos + sizeof(*desc)); - seg_len = tcp_tx_desc_payload_len(desc, seg); + seg_len = tcp_tx_desc_payload_len(t, desc, seg); if (seg_len > 0 && !(desc->flags & PKT_FLAG_SENT)) return 1; desc = fifo_next(&t->sock.tcp.txbuf, desc); @@ -2936,7 +2951,7 @@ static int tcp_send_zero_wnd_probe(struct tsocket *t) while (desc && guard++ < budget) { struct wolfIP_tcp_seg *seg = (struct wolfIP_tcp_seg *)(t->txmem + desc->pos + sizeof(*desc)); uint32_t hdr_len = (uint32_t)(seg->hlen >> 2); - uint32_t seg_len = tcp_tx_desc_payload_len(desc, seg); + uint32_t seg_len = tcp_tx_desc_payload_len(t, desc, seg); uint32_t seg_seq = ee32(seg->seq); const uint8_t *payload; if (seg_len == 0) { @@ -3496,7 +3511,7 @@ static int tcp_mark_unsacked_for_retransmit(struct tsocket *t, uint32_t ack) if (guard++ >= budget) break; seg = (struct wolfIP_tcp_seg *)(t->txmem + desc->pos + sizeof(*desc)); - seg_len = tcp_tx_desc_payload_len(desc, seg); + seg_len = tcp_tx_desc_payload_len(t, desc, seg); if (seg_len == 0) { desc = fifo_next(&t->sock.tcp.txbuf, desc); continue; @@ -4238,7 +4253,7 @@ static void tcp_rto_cb(void *arg) if (desc->flags & PKT_FLAG_SENT) { struct wolfIP_tcp_seg *seg = (struct wolfIP_tcp_seg *)(ts->txmem + desc->pos + sizeof(*desc)); - uint32_t seg_len = tcp_tx_desc_payload_len(desc, seg); + uint32_t seg_len = tcp_tx_desc_payload_len(ts, desc, seg); uint32_t seg_start = ee32(seg->seq); uint32_t seg_end = tcp_seq_inc(seg_start, seg_len); @@ -4260,7 +4275,7 @@ static void tcp_rto_cb(void *arg) } else { struct wolfIP_tcp_seg *seg = (struct wolfIP_tcp_seg *)(ts->txmem + desc->pos + sizeof(*desc)); - uint32_t seg_len = tcp_tx_desc_payload_len(desc, seg); + uint32_t seg_len = tcp_tx_desc_payload_len(ts, desc, seg); uint32_t seg_start = ee32(seg->seq); uint32_t seg_end = tcp_seq_inc(seg_start, seg_len); if (seg_len > 0 && @@ -7409,13 +7424,13 @@ int wolfIP_poll(struct wolfIP *s, uint64_t now) if (ts->sock.tcp.peer_rwnd < snd_wnd) snd_wnd = ts->sock.tcp.peer_rwnd; is_retrans = (desc->flags & PKT_FLAG_RETRANS) ? 1 : 0; - seg_ip_len = desc->len - ETH_HEADER_LEN; + seg_ip_len = tcp_tx_desc_ip_len(ts, desc, tcp); seg_hdr_len = IP_HEADER_LEN + (uint32_t)(tcp->hlen >> 2); seg_payload_len = (seg_ip_len > seg_hdr_len) ? (seg_ip_len - seg_hdr_len) : 0; if (is_retrans || seg_payload_len == 0 || (in_flight < snd_wnd && seg_payload_len <= (snd_wnd - in_flight))) { struct wolfIP_timer new_tmr = {}; - size = desc->len - ETH_HEADER_LEN; + size = seg_ip_len; tcp = (struct wolfIP_tcp_seg *)(ts->txmem + desc->pos + sizeof(*desc)); /* Refresh ack counter */ ts->sock.tcp.last_ack = ts->sock.tcp.ack; From d9a1437cafc6dcdf5574b380924e1a8df566731c Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Tue, 31 Mar 2026 17:58:26 +0200 Subject: [PATCH 20/20] Fix regression in tcp_tx_desc_ip_len for non-ethernet devices --- src/test/unit/unit.c | 2 +- src/test/unit/unit_tests_proto.c | 18 +++++++++++++++--- src/wolfip.c | 4 ---- 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/test/unit/unit.c b/src/test/unit/unit.c index 6c1a3a2..6321466 100644 --- a/src/test/unit/unit.c +++ b/src/test/unit/unit.c @@ -750,7 +750,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_proto, test_regression_syn_on_last_ack_not_silently_processed); tcase_add_test(tc_proto, test_regression_full_txbuf_still_sends_pure_ack); tcase_add_test(tc_proto, test_regression_loopback_immediate_pure_ack_uses_loopback_ll); - tcase_add_test(tc_proto, test_regression_tcp_tx_desc_payload_len_uses_link_type_not_length_heuristic); + tcase_add_test(tc_proto, test_regression_tcp_tx_desc_payload_len_keeps_descriptor_layout_sanity); tcase_add_test(tc_proto, test_regression_fast_recovery_cwnd_ssthresh_rfc5681); tcase_add_test(tc_proto, test_regression_paws_rejects_stale_timestamp); tcase_add_test(tc_proto, test_regression_paws_accepts_wrapped_newer_timestamp); diff --git a/src/test/unit/unit_tests_proto.c b/src/test/unit/unit_tests_proto.c index 2f3e542..aa25c0c 100644 --- a/src/test/unit/unit_tests_proto.c +++ b/src/test/unit/unit_tests_proto.c @@ -4800,7 +4800,7 @@ START_TEST(test_regression_loopback_immediate_pure_ack_uses_loopback_ll) } END_TEST -START_TEST(test_regression_tcp_tx_desc_payload_len_uses_link_type_not_length_heuristic) +START_TEST(test_regression_tcp_tx_desc_payload_len_keeps_descriptor_layout_sanity) { struct wolfIP s; struct tsocket *ts; @@ -4827,10 +4827,22 @@ START_TEST(test_regression_tcp_tx_desc_payload_len_uses_link_type_not_length_heu ck_assert_uint_eq(tcp_tx_desc_ip_len(ts, &desc, &seg), 0U); ck_assert_uint_eq(tcp_tx_desc_payload_len(ts, &desc, &seg), 0U); - /* The same descriptor length is valid on non-Ethernet/L3 links. */ + /* Non-Ethernet links still use the same queued descriptor layout. */ + s.ll_dev[TEST_PRIMARY_IF].non_ethernet = 1; + ck_assert_uint_eq(tcp_tx_desc_ip_len(ts, &desc, &seg), 0U); + ck_assert_uint_eq(tcp_tx_desc_payload_len(ts, &desc, &seg), 0U); + + /* Once the descriptor includes stored link headroom, both paths decode + * the same IP and payload lengths. */ + desc.len = ETH_HEADER_LEN + IP_HEADER_LEN + TCP_HEADER_LEN + 4; + s.ll_dev[TEST_PRIMARY_IF].non_ethernet = 0; + ck_assert_uint_eq(tcp_tx_desc_ip_len(ts, &desc, &seg), + IP_HEADER_LEN + TCP_HEADER_LEN + 4U); + ck_assert_uint_eq(tcp_tx_desc_payload_len(ts, &desc, &seg), 4U); + s.ll_dev[TEST_PRIMARY_IF].non_ethernet = 1; ck_assert_uint_eq(tcp_tx_desc_ip_len(ts, &desc, &seg), - (uint32_t)desc.len); + IP_HEADER_LEN + TCP_HEADER_LEN + 4U); ck_assert_uint_eq(tcp_tx_desc_payload_len(ts, &desc, &seg), 4U); } END_TEST diff --git a/src/wolfip.c b/src/wolfip.c index c98b30b..b492417 100644 --- a/src/wolfip.c +++ b/src/wolfip.c @@ -2829,7 +2829,6 @@ static uint32_t tcp_tx_desc_ip_len(const struct tsocket *t, { uint32_t seg_ip_len; uint32_t seg_hdr_len; - unsigned int tx_if; if (!t || !desc || !seg) return 0; @@ -2837,9 +2836,6 @@ static uint32_t tcp_tx_desc_ip_len(const struct tsocket *t, seg_ip_len = ee16(seg->ip.len); if (seg_ip_len != 0) return seg_ip_len; - tx_if = wolfIP_socket_if_idx(t); - if (wolfIP_ll_is_non_ethernet(t->S, tx_if)) - return desc->len; if (desc->len < (ETH_HEADER_LEN + seg_hdr_len)) return 0; return desc->len - ETH_HEADER_LEN;