From 7f6e6a42d89d9b935e987e27d355efda1d3f8756 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Thu, 19 Feb 2026 17:32:47 +0100 Subject: [PATCH 01/21] Add missing retransmissions for SYN/SYNACK/FIN segments --- src/test/unit/unit.c | 186 ++++++++++++++++++++++++++++++++++++++++++- src/wolfip.c | 104 ++++++++++++++++++++++-- 2 files changed, 280 insertions(+), 10 deletions(-) diff --git a/src/test/unit/unit.c b/src/test/unit/unit.c index 11aa14d..2bca388 100644 --- a/src/test/unit/unit.c +++ b/src/test/unit/unit.c @@ -5652,7 +5652,7 @@ START_TEST(test_tcp_rto_cb_non_established_noop) memset(ts, 0, sizeof(*ts)); ts->proto = WI_IPPROTO_TCP; ts->S = &s; - ts->sock.tcp.state = TCP_SYN_SENT; + ts->sock.tcp.state = TCP_CLOSED; ts->sock.tcp.rto_backoff = 2; tcp_rto_cb(ts); @@ -5660,6 +5660,183 @@ START_TEST(test_tcp_rto_cb_non_established_noop) } END_TEST +START_TEST(test_tcp_rto_cb_syn_sent_requeues_syn_and_arms_timer) +{ + struct wolfIP s; + struct tsocket *ts; + struct pkt_desc *desc; + struct wolfIP_tcp_seg *seg; + + 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_SYN_SENT; + ts->sock.tcp.rto = 100; + 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); + + s.last_tick = 1000; + tcp_rto_cb(ts); + + desc = fifo_peek(&ts->sock.tcp.txbuf); + ck_assert_ptr_nonnull(desc); + seg = (struct wolfIP_tcp_seg *)(ts->txmem + desc->pos + sizeof(*desc)); + ck_assert_uint_eq(seg->flags, 0x02); + ck_assert_uint_eq(ts->sock.tcp.ctrl_rto_retries, 1); + ck_assert_int_ne(ts->sock.tcp.tmr_rto, NO_TIMER); +} +END_TEST + +START_TEST(test_tcp_input_synack_cancels_control_rto) +{ + struct wolfIP s; + struct tsocket *ts; + struct wolfIP_tcp_seg synack; + struct wolfIP_timer tmr; + + 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_SYN_SENT; + ts->sock.tcp.seq = 101; + ts->sock.tcp.ctrl_rto_retries = 3; + ts->sock.tcp.ctrl_rto_active = 1; + ts->src_port = 2222; + ts->dst_port = 5001; + ts->local_ip = 0x0A000001U; + ts->remote_ip = 0x0A000002U; + + memset(&tmr, 0, sizeof(tmr)); + tmr.cb = test_timer_cb; + tmr.expires = 100; + 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); + + memset(&synack, 0, sizeof(synack)); + synack.ip.ttl = 64; + synack.ip.src = ee32(0x0A000002U); + synack.ip.dst = ee32(0x0A000001U); + synack.ip.len = ee16(IP_HEADER_LEN + TCP_HEADER_LEN); + synack.src_port = ee16(5001); + synack.dst_port = ee16(ts->src_port); + synack.seq = ee32(1000); + synack.ack = ee32(ts->sock.tcp.seq + 1); + synack.hlen = TCP_HEADER_LEN << 2; + synack.flags = 0x12; + synack.win = ee16(65535); + + tcp_input(&s, TEST_PRIMARY_IF, &synack, + (uint32_t)(ETH_HEADER_LEN + IP_HEADER_LEN + TCP_HEADER_LEN)); + + ck_assert_int_eq(ts->sock.tcp.state, TCP_ESTABLISHED); + ck_assert_int_eq(ts->sock.tcp.tmr_rto, NO_TIMER); + ck_assert_uint_eq(ts->sock.tcp.ctrl_rto_retries, 0); + ck_assert_uint_eq(ts->sock.tcp.ctrl_rto_active, 0); +} +END_TEST + +START_TEST(test_tcp_rto_cb_last_ack_requeues_finack_and_arms_timer) +{ + struct wolfIP s; + struct tsocket *ts; + struct pkt_desc *desc; + struct wolfIP_tcp_seg *seg; + + 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->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); + + s.last_tick = 1000; + tcp_rto_cb(ts); + + desc = fifo_peek(&ts->sock.tcp.txbuf); + ck_assert_ptr_nonnull(desc); + seg = (struct wolfIP_tcp_seg *)(ts->txmem + desc->pos + sizeof(*desc)); + ck_assert_uint_eq(seg->flags, 0x11); + ck_assert_uint_eq(ts->sock.tcp.ctrl_rto_retries, 1); + ck_assert_int_ne(ts->sock.tcp.tmr_rto, NO_TIMER); +} +END_TEST + +START_TEST(test_tcp_ack_fin_wait_1_ack_of_fin_moves_to_fin_wait_2_and_stops_timer) +{ + struct wolfIP s; + struct tsocket *ts; + struct wolfIP_tcp_seg ackseg; + struct wolfIP_timer tmr; + + 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.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); + + memset(&ackseg, 0, sizeof(ackseg)); + ackseg.hlen = TCP_HEADER_LEN << 2; + ackseg.flags = 0x10; + 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_eq(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); +} +END_TEST + +START_TEST(test_tcp_rto_cb_control_retry_cap_closes_socket) +{ + 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_SYN_SENT; + ts->sock.tcp.rto = 100; + ts->sock.tcp.ctrl_rto_active = 1; + ts->sock.tcp.ctrl_rto_retries = TCP_CTRL_RTO_MAXRTX; + + tcp_rto_cb(ts); + ck_assert_int_eq(ts->proto, 0); +} +END_TEST + START_TEST(test_tcp_rto_cb_cancels_existing_timer) { struct wolfIP s; @@ -9949,7 +10126,7 @@ START_TEST(test_tcp_ack_closes_last_ack_socket) desc->flags |= PKT_FLAG_SENT; memset(&ackseg, 0, sizeof(ackseg)); - ackseg.ack = ee32(seq); + ackseg.ack = ee32(seq + 1); ackseg.hlen = TCP_HEADER_LEN << 2; ackseg.flags = 0x10; @@ -14230,6 +14407,11 @@ Suite *wolf_suite(void) tcase_add_test(tc_utils, test_tcp_rto_cb_skips_unsent_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); + 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_ack_fin_wait_1_ack_of_fin_moves_to_fin_wait_2_and_stops_timer); + tcase_add_test(tc_utils, test_tcp_rto_cb_control_retry_cap_closes_socket); tcase_add_test(tc_utils, test_tcp_rto_cb_cancels_existing_timer); tcase_add_test(tc_utils, test_tcp_rto_cb_clears_sack_and_marks_lowest_only); tcase_add_test(tc_utils, test_tcp_rto_cb_ssthresh_floor_two_mss); diff --git a/src/wolfip.c b/src/wolfip.c index fd26aa0..1592980 100644 --- a/src/wolfip.c +++ b/src/wolfip.c @@ -113,6 +113,7 @@ struct wolfIP_icmp_packet; #define WI_IP_MTU 1500 #define TCP_MSS (WI_IP_MTU - (IP_HEADER_LEN + TCP_HEADER_LEN)) #define TCP_DEFAULT_MSS 536U +#define TCP_CTRL_RTO_MAXRTX 6U /* Arbitrary upper limit to avoid monopolizing the CPU during poll loops. */ #define WOLFIP_POLL_BUDGET 128 @@ -990,6 +991,8 @@ struct tcpsocket { uint32_t last_early_rexmit_ack; uint8_t dup_acks; uint8_t early_rexmit_done; + uint8_t ctrl_rto_retries; + uint8_t ctrl_rto_active; ip4 local_ip, remote_ip; uint32_t peer_rwnd; uint16_t peer_mss; @@ -1033,6 +1036,10 @@ static void close_socket(struct tsocket *ts); static inline uint32_t tcp_seq_inc(uint32_t seq, uint32_t n); static inline int tcp_seq_leq(uint32_t a, uint32_t b); static inline int tcp_seq_lt(uint32_t a, uint32_t b); +static void tcp_rto_cb(void *arg); +static void tcp_ctrl_rto_start(struct tsocket *t, uint64_t now); +static void tcp_ctrl_rto_stop(struct tsocket *t); +static int tcp_ctrl_state_needs_rto(const struct tsocket *t); #ifdef ETHERNET struct PACKED arp_packet { @@ -1661,6 +1668,8 @@ static struct tsocket *tcp_new_socket(struct wolfIP *s) t->sock.tcp.snd_una = t->sock.tcp.seq; t->sock.tcp.dup_acks = 0; t->sock.tcp.early_rexmit_done = 0; + t->sock.tcp.ctrl_rto_retries = 0; + t->sock.tcp.ctrl_rto_active = 0; t->sock.tcp.last_early_rexmit_ack = 0; t->sock.tcp.peer_rwnd = 0xFFFF; t->sock.tcp.cwnd = tcp_initial_cwnd(t->sock.tcp.peer_rwnd); @@ -2108,6 +2117,46 @@ static void tcp_send_syn(struct tsocket *t, uint8_t flags) fifo_push(&t->sock.tcp.txbuf, tcp, sizeof(struct wolfIP_tcp_seg) + opt_len); } +static int tcp_ctrl_state_needs_rto(const struct tsocket *t) +{ + if (!t || t->proto != WI_IPPROTO_TCP) + return 0; + return (t->sock.tcp.state == TCP_SYN_SENT) || + (t->sock.tcp.state == TCP_SYN_RCVD) || + (t->sock.tcp.state == TCP_FIN_WAIT_1) || + (t->sock.tcp.state == TCP_LAST_ACK); +} + +static void tcp_ctrl_rto_stop(struct tsocket *t) +{ + if (!t || t->proto != WI_IPPROTO_TCP) + return; + if (t->sock.tcp.tmr_rto != NO_TIMER) { + timer_binheap_cancel(&t->S->timers, t->sock.tcp.tmr_rto); + t->sock.tcp.tmr_rto = NO_TIMER; + } + t->sock.tcp.ctrl_rto_active = 0; + t->sock.tcp.ctrl_rto_retries = 0; +} + +static void tcp_ctrl_rto_start(struct tsocket *t, uint64_t now) +{ + struct wolfIP_timer tmr = {0}; + uint64_t shift_rto; + if (!t || t->proto != WI_IPPROTO_TCP) + return; + if (t->sock.tcp.tmr_rto != NO_TIMER) { + timer_binheap_cancel(&t->S->timers, t->sock.tcp.tmr_rto); + t->sock.tcp.tmr_rto = NO_TIMER; + } + shift_rto = (uint64_t)t->sock.tcp.rto << t->sock.tcp.ctrl_rto_retries; + tmr.expires = now + shift_rto; + tmr.arg = t; + tmr.cb = tcp_rto_cb; + t->sock.tcp.tmr_rto = timers_binheap_insert(&t->S->timers, tmr); + t->sock.tcp.ctrl_rto_active = 1; +} + /* Increment a TCP sequence number (wraps at 2^32) */ static inline uint32_t tcp_seq_inc(uint32_t seq, uint32_t n) { @@ -2564,11 +2613,23 @@ static int tcp_mark_unsacked_for_retransmit(struct tsocket *t, uint32_t ack) static void tcp_ack(struct tsocket *t, const struct wolfIP_tcp_seg *tcp) { uint32_t ack = ee32(tcp->ack); + uint32_t fin_acked = tcp_seq_inc(t->sock.tcp.last, 1); struct pkt_desc *desc; int ack_count = 0; int ack_advanced = 0; uint32_t inflight_pre = t->sock.tcp.bytes_in_flight; + if (t->sock.tcp.state == TCP_LAST_ACK && tcp_seq_leq(fin_acked, ack)) { + tcp_ctrl_rto_stop(t); + t->sock.tcp.state = TCP_CLOSED; + close_socket(t); + return; + } + if (t->sock.tcp.state == TCP_FIN_WAIT_1 && tcp_seq_leq(fin_acked, ack)) { + t->sock.tcp.state = TCP_FIN_WAIT_2; + tcp_ctrl_rto_stop(t); + } + tcp_process_sack(t, tcp, (uint32_t)(ETH_HEADER_LEN + IP_HEADER_LEN + (tcp->hlen >> 2))); desc = fifo_peek(&t->sock.tcp.txbuf); @@ -2582,13 +2643,6 @@ static void tcp_ack(struct tsocket *t, const struct wolfIP_tcp_seg *tcp) desc = fifo_peek(&t->sock.tcp.txbuf); continue; } - if (ee32(seg->seq) == t->sock.tcp.last && ee32(seg->seq) == ack) { - if (t->sock.tcp.state == TCP_LAST_ACK) { - t->sock.tcp.state = TCP_CLOSED; - close_socket(t); - return; - } - } if (tcp_seq_leq(ee32(seg->seq) + seg_len, ack)) { desc->flags |= PKT_FLAG_ACKED; desc->flags &= ~PKT_FLAG_SENT; @@ -2878,6 +2932,7 @@ static void tcp_input(struct wolfIP *S, unsigned int if_idx, } else if (t->sock.tcp.state == TCP_SYN_SENT) { if (tcp->flags == 0x12) { t->sock.tcp.state = TCP_ESTABLISHED; + tcp_ctrl_rto_stop(t); t->sock.tcp.ack = tcp_seq_inc(ee32(tcp->seq), 1); t->sock.tcp.seq = ee32(tcp->ack); t->sock.tcp.snd_una = t->sock.tcp.seq; @@ -2894,6 +2949,7 @@ static void tcp_input(struct wolfIP *S, unsigned int if_idx, if ((tcplen == 0) && (t->sock.tcp.state == TCP_SYN_RCVD)) { if (tcp->flags == 0x10) { t->sock.tcp.state = TCP_ESTABLISHED; + tcp_ctrl_rto_stop(t); t->sock.tcp.ack = ee32(tcp->seq); t->sock.tcp.seq = ee32(tcp->ack); t->sock.tcp.snd_una = t->sock.tcp.seq; @@ -2951,7 +3007,31 @@ static void tcp_rto_cb(void *arg) uint32_t budget; uint32_t first_sent_seq = 0; uint32_t prev_cwnd; - if ((ts->proto != WI_IPPROTO_TCP) || (ts->sock.tcp.state != TCP_ESTABLISHED)) + if (ts->proto != WI_IPPROTO_TCP) + return; + if (tcp_ctrl_state_needs_rto(ts) || ts->sock.tcp.ctrl_rto_active) { + if (!tcp_ctrl_state_needs_rto(ts)) { + tcp_ctrl_rto_stop(ts); + return; + } + if (ts->sock.tcp.ctrl_rto_retries >= TCP_CTRL_RTO_MAXRTX) { + tcp_ctrl_rto_stop(ts); + ts->sock.tcp.state = TCP_CLOSED; + close_socket(ts); + return; + } + ts->sock.tcp.ctrl_rto_retries++; + if (ts->sock.tcp.state == TCP_SYN_SENT) { + tcp_send_syn(ts, 0x02); + } else if (ts->sock.tcp.state == TCP_SYN_RCVD) { + tcp_send_syn(ts, 0x12); + } else if (ts->sock.tcp.state == TCP_FIN_WAIT_1 || ts->sock.tcp.state == TCP_LAST_ACK) { + tcp_send_finack(ts); + } + tcp_ctrl_rto_start(ts, ts->S->last_tick); + return; + } + if (ts->sock.tcp.state != TCP_ESTABLISHED) return; /* RFC 6675 / RFC 2018 guidance: after an RTO, SACK scoreboard must not be * trusted (receiver may renege). Fall back to cumulative-ACK driven @@ -3061,6 +3141,8 @@ static void tcp_resync_inflight(struct wolfIP *s, struct tsocket *ts, uint64_t n if (!s || !ts) return; + if (tcp_ctrl_state_needs_rto(ts) || ts->sock.tcp.ctrl_rto_active) + return; budget = fifo_desc_budget(&ts->sock.tcp.txbuf); scan = fifo_peek(&ts->sock.tcp.txbuf); while (scan && guard++ < budget) { @@ -3279,7 +3361,9 @@ int wolfIP_sock_connect(struct wolfIP *s, int sockfd, const struct wolfIP_sockad ts->sock.tcp.state = TCP_CLOSED; return -1; } + ts->sock.tcp.ctrl_rto_retries = 0; tcp_send_syn(ts, 0x02); + tcp_ctrl_rto_start(ts, s->last_tick); return -WOLFIP_EAGAIN; } return -WOLFIP_EINVAL; @@ -3727,7 +3811,9 @@ int wolfIP_sock_close(struct wolfIP *s, int sockfd) ts = &s->tcpsockets[SOCKET_UNMARK(sockfd)]; if (ts->sock.tcp.state == TCP_ESTABLISHED) { 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) { ts->sock.tcp.state = TCP_CLOSED; @@ -3738,7 +3824,9 @@ int wolfIP_sock_close(struct wolfIP *s, int sockfd) return 0; } else if (ts->sock.tcp.state == TCP_CLOSE_WAIT) { 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) { ts->sock.tcp.state = TCP_TIME_WAIT; From 2d9ef28587e2c1808a924637cb09da2a42358010 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Thu, 19 Feb 2026 17:50:41 +0100 Subject: [PATCH 02/21] Fixed RTO calculation according to RFC6298 - Fixed bug in smooth average RTT calculation - Actually assign calculated RTO value (previously fixed to 1s) - Enforce min/max RTO according to RFC - Enforce Karn to only account for new ACK in RTO calculation --- README.md | 1 + src/test/unit/unit.c | 77 +++++++++++++++++++++++++++++++++++++ src/wolfip.c | 90 ++++++++++++++++++++++++++++++++++---------- 3 files changed, 148 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 2e1a95d..5c363f9 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,7 @@ A single network interface can be associated with the device. | **Transport** | TCP | Connection management, reliable delivery | [RFC 793](https://datatracker.ietf.org/doc/html/rfc793), [RFC 9293](https://datatracker.ietf.org/doc/html/rfc9293) | | **Transport** | TCP | Maximum Segment Size negotiation | [RFC 793](https://datatracker.ietf.org/doc/html/rfc793) | | **Transport** | TCP | TCP Timestamps, RTT measurement, PAWS, Window Scaling | [RFC 7323](https://datatracker.ietf.org/doc/html/rfc7323) | +| **Transport** | TCP | Retransmission timeout (RTO) computation | [RFC 6298](https://datatracker.ietf.org/doc/html/rfc6298) | | **Transport** | TCP | TCP SACK | [RFC 2018](https://datatracker.ietf.org/doc/html/rfc2018), [RFC 2883](https://datatracker.ietf.org/doc/html/rfc2883), [RFC 6675](https://datatracker.ietf.org/doc/html/rfc6675) | | **Transport** | TCP | Congestion Control: Slow start, congestion avoidance | [RFC 5681](https://datatracker.ietf.org/doc/html/rfc5681) | | **Transport** | TCP | Fast Retransmit, triple duplicate ACK detection | [RFC 5681](https://datatracker.ietf.org/doc/html/rfc5681) | diff --git a/src/test/unit/unit.c b/src/test/unit/unit.c index 2bca388..8df6fe7 100644 --- a/src/test/unit/unit.c +++ b/src/test/unit/unit.c @@ -8593,6 +8593,29 @@ START_TEST(test_tcp_process_ts_uses_ecr) s.last_tick = 1000; ck_assert_int_eq(tcp_process_ts(ts, tcp, sizeof(buf)), 0); ck_assert_uint_eq(ts->sock.tcp.rtt, 100); + ck_assert_uint_eq(ts->sock.tcp.rto, TCP_RTO_MIN_MS); + ck_assert_uint_eq(ts->sock.tcp.rto_initialized, 1); +} +END_TEST + +START_TEST(test_tcp_rto_update_second_sample_rfc6298) +{ + 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; + + tcp_rto_update_from_sample(ts, 2000); + ck_assert_uint_eq(ts->sock.tcp.rtt, 2000); + ck_assert_uint_eq(ts->sock.tcp.rto, 6000); + + tcp_rto_update_from_sample(ts, 1000); + ck_assert_uint_eq(ts->sock.tcp.rtt, 1875); + ck_assert_uint_eq(ts->sock.tcp.rto, 5875); } END_TEST @@ -10219,6 +10242,58 @@ START_TEST(test_tcp_ack_fresh_desc_updates_rtt_existing) } END_TEST +START_TEST(test_tcp_ack_retransmitted_desc_skips_rtt_update) +{ + struct wolfIP s; + struct tsocket *ts; + struct tcp_seg_buf segbuf; + struct wolfIP_tcp_seg *seg; + struct wolfIP_tcp_seg ackseg; + struct pkt_desc *desc; + uint32_t seq = 300; + uint32_t old_rtt; + uint32_t old_rto; + + 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; + fifo_init(&ts->sock.tcp.txbuf, ts->txmem, TXBUF_SIZE); + s.last_tick = 1000; + + tcp_rto_update_from_sample(ts, 200); + old_rtt = ts->sock.tcp.rtt; + old_rto = ts->sock.tcp.rto; + + memset(&segbuf, 0, sizeof(segbuf)); + seg = &segbuf.seg; + seg->ip.len = ee16(IP_HEADER_LEN + TCP_HEADER_LEN + 1); + seg->hlen = TCP_HEADER_LEN << 2; + seg->seq = ee32(seq); + seg->data[0] = TCP_OPTION_EOO; + ck_assert_int_eq(fifo_push(&ts->sock.tcp.txbuf, &segbuf, sizeof(segbuf)), 0); + desc = fifo_peek(&ts->sock.tcp.txbuf); + ck_assert_ptr_nonnull(desc); + desc->flags |= PKT_FLAG_SENT; + desc->flags |= PKT_FLAG_WAS_RETRANS; + desc->time_sent = 800; + ts->sock.tcp.bytes_in_flight = 1; + ts->sock.tcp.snd_una = seq; + ts->sock.tcp.seq = seq + 1; + + memset(&ackseg, 0, sizeof(ackseg)); + ackseg.ack = ee32(seq + 1); + ackseg.hlen = TCP_HEADER_LEN << 2; + ackseg.flags = 0x10; + + tcp_ack(ts, &ackseg); + ck_assert_uint_eq(ts->sock.tcp.rtt, old_rtt); + ck_assert_uint_eq(ts->sock.tcp.rto, old_rto); +} +END_TEST + START_TEST(test_tcp_ack_duplicate_zero_len_segment_large_ack) { struct wolfIP s; @@ -14446,6 +14521,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_utils, test_tcp_ack_closes_last_ack_socket); tcase_add_test(tc_utils, test_tcp_ack_last_seq_match_no_close); tcase_add_test(tc_utils, test_tcp_ack_fresh_desc_updates_rtt_existing); + tcase_add_test(tc_utils, test_tcp_ack_retransmitted_desc_skips_rtt_update); tcase_add_test(tc_utils, test_tcp_ack_duplicate_zero_len_segment_large_ack); tcase_add_test(tc_utils, test_tcp_ack_duplicate_seq_match_large_seg_len); tcase_add_test(tc_utils, test_tcp_ack_duplicate_clears_sent_flag); @@ -14472,6 +14548,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_utils, test_tcp_recv_close_wait_ack_match); tcase_add_test(tc_utils, test_tcp_recv_queue_full_sends_ack); tcase_add_test(tc_utils, test_tcp_process_ts_uses_ecr); + tcase_add_test(tc_utils, test_tcp_rto_update_second_sample_rfc6298); tcase_add_test(tc_utils, test_tcp_process_ts_nop_then_ts); tcase_add_test(tc_utils, test_tcp_process_ts_skips_unknown_option); tcase_add_test(tc_utils, test_tcp_process_ts_no_ecr); diff --git a/src/wolfip.c b/src/wolfip.c index 1592980..d678158 100644 --- a/src/wolfip.c +++ b/src/wolfip.c @@ -114,6 +114,9 @@ struct wolfIP_icmp_packet; #define TCP_MSS (WI_IP_MTU - (IP_HEADER_LEN + TCP_HEADER_LEN)) #define TCP_DEFAULT_MSS 536U #define TCP_CTRL_RTO_MAXRTX 6U +#define TCP_RTO_MIN_MS 1000U +#define TCP_RTO_MAX_MS 60000U +#define TCP_RTO_G_MS 1U /* Arbitrary upper limit to avoid monopolizing the CPU during poll loops. */ #define WOLFIP_POLL_BUDGET 128 @@ -124,6 +127,8 @@ struct wolfIP_icmp_packet; #define PKT_FLAG_ACKED 0x02U #define PKT_FLAG_FIN 0x04U #define PKT_FLAG_RETRANS 0x08U +#define PKT_FLAG_WAS_RETRANS 0x10U + #define TX_WRITABLE_THRESHOLD 1 #define TCP_SACK_MAX_BLOCKS 4 @@ -988,7 +993,9 @@ struct tcpsocket { enum tcp_state state; uint32_t last_ts, rtt, rto, cwnd, cwnd_count, ssthresh, tmr_rto, rto_backoff, seq, ack, last_ack, last, bytes_in_flight, snd_una; + uint32_t srtt, rttvar; uint32_t last_early_rexmit_ack; + uint8_t rto_initialized; uint8_t dup_acks; uint8_t early_rexmit_done; uint8_t ctrl_rto_retries; @@ -1036,6 +1043,7 @@ static void close_socket(struct tsocket *ts); static inline uint32_t tcp_seq_inc(uint32_t seq, uint32_t n); static inline int tcp_seq_leq(uint32_t a, uint32_t b); static inline int tcp_seq_lt(uint32_t a, uint32_t b); +static void tcp_rto_update_from_sample(struct tsocket *t, uint32_t sample_ms); static void tcp_rto_cb(void *arg); static void tcp_ctrl_rto_start(struct tsocket *t, uint64_t now); static void tcp_ctrl_rto_stop(struct tsocket *t); @@ -1661,8 +1669,11 @@ static struct tsocket *tcp_new_socket(struct wolfIP *s) t->S = s; t->if_idx = 0; t->sock.tcp.state = TCP_CLOSED; - t->sock.tcp.rto = 1000; + t->sock.tcp.rto = TCP_RTO_MIN_MS; t->sock.tcp.rtt = 0; + t->sock.tcp.srtt = 0; + t->sock.tcp.rttvar = 0; + t->sock.tcp.rto_initialized = 0; t->sock.tcp.rto_backoff = 0; t->sock.tcp.bytes_in_flight = 0; t->sock.tcp.snd_una = t->sock.tcp.seq; @@ -2419,6 +2430,7 @@ static int tcp_process_ts(struct tsocket *t, const struct wolfIP_tcp_seg *tcp, uint32_t frame_len) { struct tcp_parsed_opts po; + uint32_t sample; tcp_parse_options(tcp, frame_len, &po); if (!po.ts_found) @@ -2428,16 +2440,51 @@ static int tcp_process_ts(struct tsocket *t, const struct wolfIP_tcp_seg *tcp, return -1; /* No echoed timestamp; fall back to coarse RTT. */ if (po.ts_ecr > t->S->last_tick) return -1; /* Echoed timestamp in the future; ignore. */ - if (t->sock.tcp.rtt == 0) - t->sock.tcp.rtt = (uint32_t)(t->S->last_tick - po.ts_ecr); - else { - uint64_t rtt_scaled = (uint64_t)t->sock.tcp.rtt << 3; - uint64_t sample_scaled = (t->S->last_tick - po.ts_ecr) << 3; - t->sock.tcp.rtt = (uint32_t)(7 * rtt_scaled + sample_scaled); - } + sample = (uint32_t)(t->S->last_tick - po.ts_ecr); + tcp_rto_update_from_sample(t, sample); return 0; } +static uint32_t tcp_rto_clamp(uint32_t rto_ms) +{ + if (rto_ms < TCP_RTO_MIN_MS) + return TCP_RTO_MIN_MS; + if (rto_ms > TCP_RTO_MAX_MS) + return TCP_RTO_MAX_MS; + return rto_ms; +} + +static void tcp_rto_update_from_sample(struct tsocket *t, uint32_t sample_ms) +{ + uint32_t srtt_ms; + uint32_t rto_ms; + uint32_t err_ms; + + if (!t || t->proto != WI_IPPROTO_TCP) + return; + if (sample_ms == 0) + sample_ms = 1; + + if (!t->sock.tcp.rto_initialized) { + t->sock.tcp.srtt = sample_ms << 3; /* SRTT in ms*8 */ + t->sock.tcp.rttvar = sample_ms << 1; /* RTTVAR in ms*4, initialized to R/2 */ + t->sock.tcp.rto_initialized = 1; + } else { + srtt_ms = t->sock.tcp.srtt >> 3; + if (srtt_ms > sample_ms) + err_ms = srtt_ms - sample_ms; + else + err_ms = sample_ms - srtt_ms; + t->sock.tcp.rttvar = (3U * t->sock.tcp.rttvar + (err_ms << 2)) >> 2; + t->sock.tcp.srtt = (7U * t->sock.tcp.srtt + (sample_ms << 3)) >> 3; + } + + srtt_ms = t->sock.tcp.srtt >> 3; + rto_ms = srtt_ms + ((t->sock.tcp.rttvar > TCP_RTO_G_MS) ? t->sock.tcp.rttvar : TCP_RTO_G_MS); + t->sock.tcp.rtt = srtt_ms; + t->sock.tcp.rto = tcp_rto_clamp(rto_ms); +} + #define SEQ_DIFF(a,b) ((a - b) > 0x7FFFFFFF) ? (b - a) : (a - b) /* Return true if a <= b @@ -2691,7 +2738,9 @@ static void tcp_ack(struct tsocket *t, const struct wolfIP_tcp_seg *tcp) } if (ack_count > 0) { struct pkt_desc *fresh_desc = NULL; - struct wolfIP_tcp_seg *seg; + uint32_t ack_ip_len = ee16(tcp->ip.len); + uint32_t ack_hdr_len = IP_HEADER_LEN + (uint32_t)(tcp->hlen >> 2); + uint32_t ack_frame_len = 0; /* This ACK ackwnowledged some data. */ desc = fifo_peek(&t->sock.tcp.txbuf); while (desc && (desc->flags & PKT_FLAG_ACKED)) { @@ -2699,17 +2748,16 @@ static void tcp_ack(struct tsocket *t, const struct wolfIP_tcp_seg *tcp) desc = fifo_peek(&t->sock.tcp.txbuf); } if (fresh_desc) { - seg = (struct wolfIP_tcp_seg *)(t->txmem + fresh_desc->pos + sizeof(*fresh_desc)); - /* Update rtt */ - if (tcp_process_ts(t, seg, fresh_desc->len) < 0) { - /* No timestamp option, use coarse RTT estimation */ - if (t->S->last_tick >= fresh_desc->time_sent) { - uint32_t rtt = (uint32_t)(t->S->last_tick - fresh_desc->time_sent); - if (t->sock.tcp.rtt == 0) { - t->sock.tcp.rtt = rtt; - } else { - uint64_t rtt_scaled = (uint64_t)t->sock.tcp.rtt << 3; - t->sock.tcp.rtt = (uint32_t)(7 * rtt_scaled + ((uint64_t)rtt << 3)); + /* Karn rule: ignore RTT samples for retransmitted segments. */ + if (!(fresh_desc->flags & PKT_FLAG_WAS_RETRANS)) { + if (ack_ip_len >= ack_hdr_len) + ack_frame_len = ETH_HEADER_LEN + ack_ip_len; + /* Prefer timestamp-based RTT sample from the incoming ACK. */ + if (ack_frame_len == 0 || tcp_process_ts(t, tcp, ack_frame_len) < 0) { + /* No usable TS echo; use coarse RTT sample from send timestamp. */ + if (t->S->last_tick >= fresh_desc->time_sent) { + uint32_t rtt = (uint32_t)(t->S->last_tick - fresh_desc->time_sent); + tcp_rto_update_from_sample(t, rtt); } } } @@ -5376,6 +5424,8 @@ int wolfIP_poll(struct wolfIP *s, uint64_t now) } desc->flags |= PKT_FLAG_SENT; desc->flags &= ~PKT_FLAG_RETRANS; + if (is_retrans) + desc->flags |= PKT_FLAG_WAS_RETRANS; desc->time_sent = now; if (size == IP_HEADER_LEN + (uint32_t)(tcp->hlen >> 2)) { desc = fifo_pop(&ts->sock.tcp.txbuf); From 9a4b13ec2045238679ef116e4a40b5533aaed6b3 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Fri, 20 Feb 2026 18:11:38 +0100 Subject: [PATCH 03/21] Added more comments to function declarations --- src/wolfip.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/wolfip.c b/src/wolfip.c index d678158..2914aa4 100644 --- a/src/wolfip.c +++ b/src/wolfip.c @@ -2128,6 +2128,8 @@ static void tcp_send_syn(struct tsocket *t, uint8_t flags) fifo_push(&t->sock.tcp.txbuf, tcp, sizeof(struct wolfIP_tcp_seg) + opt_len); } +/* Returns true when handshake/teardown control traffic is outstanding and + * should be driven by control-RTO retransmission (SYN/SYN-ACK/FIN states). */ static int tcp_ctrl_state_needs_rto(const struct tsocket *t) { if (!t || t->proto != WI_IPPROTO_TCP) @@ -2138,6 +2140,7 @@ static int tcp_ctrl_state_needs_rto(const struct tsocket *t) (t->sock.tcp.state == TCP_LAST_ACK); } +/* Stop control-RTO retransmission tracking for this socket and reset counters. */ static void tcp_ctrl_rto_stop(struct tsocket *t) { if (!t || t->proto != WI_IPPROTO_TCP) @@ -2150,6 +2153,8 @@ static void tcp_ctrl_rto_stop(struct tsocket *t) t->sock.tcp.ctrl_rto_retries = 0; } +/* Arm/re-arm control-RTO timer using exponential backoff over the current base RTO. + * This path is dedicated to SYN/SYN-ACK/FIN reliability (not data-loss recovery). */ static void tcp_ctrl_rto_start(struct tsocket *t, uint64_t now) { struct wolfIP_timer tmr = {0}; @@ -2445,6 +2450,7 @@ static int tcp_process_ts(struct tsocket *t, const struct wolfIP_tcp_seg *tcp, return 0; } +/* Apply RFC6298-style implementation bounds to computed RTO (milliseconds). */ static uint32_t tcp_rto_clamp(uint32_t rto_ms) { if (rto_ms < TCP_RTO_MIN_MS) @@ -2454,6 +2460,11 @@ static uint32_t tcp_rto_clamp(uint32_t rto_ms) return rto_ms; } +/* Update SRTT/RTTVAR/RTO from one RTT sample using RFC6298 fixed-point math. + * Internal scaling: + * - srtt in ms*8 + * - rttvar in ms*4 + * Exposed t->sock.tcp.rtt/rto remain in milliseconds. */ static void tcp_rto_update_from_sample(struct tsocket *t, uint32_t sample_ms) { uint32_t srtt_ms; From d01d5041e3c8778df8fa6786aacc5d57a44be344 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Thu, 19 Feb 2026 17:32:47 +0100 Subject: [PATCH 04/21] Add missing retransmissions for SYN/SYNACK/FIN segments --- src/wolfip.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/wolfip.c b/src/wolfip.c index 2914aa4..b93f9d8 100644 --- a/src/wolfip.c +++ b/src/wolfip.c @@ -1043,7 +1043,10 @@ static void close_socket(struct tsocket *ts); static inline uint32_t tcp_seq_inc(uint32_t seq, uint32_t n); static inline int tcp_seq_leq(uint32_t a, uint32_t b); static inline int tcp_seq_lt(uint32_t a, uint32_t b); +<<<<<<< HEAD static void tcp_rto_update_from_sample(struct tsocket *t, uint32_t sample_ms); +======= +>>>>>>> 0e6db0a (Add missing retransmissions for SYN/SYNACK/FIN segments) static void tcp_rto_cb(void *arg); static void tcp_ctrl_rto_start(struct tsocket *t, uint64_t now); static void tcp_ctrl_rto_stop(struct tsocket *t); @@ -2128,8 +2131,11 @@ static void tcp_send_syn(struct tsocket *t, uint8_t flags) fifo_push(&t->sock.tcp.txbuf, tcp, sizeof(struct wolfIP_tcp_seg) + opt_len); } +<<<<<<< HEAD /* Returns true when handshake/teardown control traffic is outstanding and * should be driven by control-RTO retransmission (SYN/SYN-ACK/FIN states). */ +======= +>>>>>>> 0e6db0a (Add missing retransmissions for SYN/SYNACK/FIN segments) static int tcp_ctrl_state_needs_rto(const struct tsocket *t) { if (!t || t->proto != WI_IPPROTO_TCP) @@ -2140,7 +2146,10 @@ static int tcp_ctrl_state_needs_rto(const struct tsocket *t) (t->sock.tcp.state == TCP_LAST_ACK); } +<<<<<<< HEAD /* Stop control-RTO retransmission tracking for this socket and reset counters. */ +======= +>>>>>>> 0e6db0a (Add missing retransmissions for SYN/SYNACK/FIN segments) static void tcp_ctrl_rto_stop(struct tsocket *t) { if (!t || t->proto != WI_IPPROTO_TCP) @@ -2153,8 +2162,11 @@ static void tcp_ctrl_rto_stop(struct tsocket *t) t->sock.tcp.ctrl_rto_retries = 0; } +<<<<<<< HEAD /* Arm/re-arm control-RTO timer using exponential backoff over the current base RTO. * This path is dedicated to SYN/SYN-ACK/FIN reliability (not data-loss recovery). */ +======= +>>>>>>> 0e6db0a (Add missing retransmissions for SYN/SYNACK/FIN segments) static void tcp_ctrl_rto_start(struct tsocket *t, uint64_t now) { struct wolfIP_timer tmr = {0}; From cf388fbf9dd1fef2859af70372ff36dbcfbdfc8d Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Thu, 19 Feb 2026 17:50:41 +0100 Subject: [PATCH 05/21] Fixed RTO calculation according to RFC6298 - Fixed bug in smooth average RTT calculation - Actually assign calculated RTO value (previously fixed to 1s) - Enforce min/max RTO according to RFC - Enforce Karn to only account for new ACK in RTO calculation --- src/wolfip.c | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/wolfip.c b/src/wolfip.c index b93f9d8..2914aa4 100644 --- a/src/wolfip.c +++ b/src/wolfip.c @@ -1043,10 +1043,7 @@ static void close_socket(struct tsocket *ts); static inline uint32_t tcp_seq_inc(uint32_t seq, uint32_t n); static inline int tcp_seq_leq(uint32_t a, uint32_t b); static inline int tcp_seq_lt(uint32_t a, uint32_t b); -<<<<<<< HEAD static void tcp_rto_update_from_sample(struct tsocket *t, uint32_t sample_ms); -======= ->>>>>>> 0e6db0a (Add missing retransmissions for SYN/SYNACK/FIN segments) static void tcp_rto_cb(void *arg); static void tcp_ctrl_rto_start(struct tsocket *t, uint64_t now); static void tcp_ctrl_rto_stop(struct tsocket *t); @@ -2131,11 +2128,8 @@ static void tcp_send_syn(struct tsocket *t, uint8_t flags) fifo_push(&t->sock.tcp.txbuf, tcp, sizeof(struct wolfIP_tcp_seg) + opt_len); } -<<<<<<< HEAD /* Returns true when handshake/teardown control traffic is outstanding and * should be driven by control-RTO retransmission (SYN/SYN-ACK/FIN states). */ -======= ->>>>>>> 0e6db0a (Add missing retransmissions for SYN/SYNACK/FIN segments) static int tcp_ctrl_state_needs_rto(const struct tsocket *t) { if (!t || t->proto != WI_IPPROTO_TCP) @@ -2146,10 +2140,7 @@ static int tcp_ctrl_state_needs_rto(const struct tsocket *t) (t->sock.tcp.state == TCP_LAST_ACK); } -<<<<<<< HEAD /* Stop control-RTO retransmission tracking for this socket and reset counters. */ -======= ->>>>>>> 0e6db0a (Add missing retransmissions for SYN/SYNACK/FIN segments) static void tcp_ctrl_rto_stop(struct tsocket *t) { if (!t || t->proto != WI_IPPROTO_TCP) @@ -2162,11 +2153,8 @@ static void tcp_ctrl_rto_stop(struct tsocket *t) t->sock.tcp.ctrl_rto_retries = 0; } -<<<<<<< HEAD /* Arm/re-arm control-RTO timer using exponential backoff over the current base RTO. * This path is dedicated to SYN/SYN-ACK/FIN reliability (not data-loss recovery). */ -======= ->>>>>>> 0e6db0a (Add missing retransmissions for SYN/SYNACK/FIN segments) static void tcp_ctrl_rto_start(struct tsocket *t, uint64_t now) { struct wolfIP_timer tmr = {0}; From e4523b1b3a2b8a55577fca8cfc2fdf289fface1a Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Thu, 19 Feb 2026 18:11:36 +0100 Subject: [PATCH 06/21] Update ssthresh calculation on RTO, per RFC5681 --- README.md | 2 +- src/test/unit/unit.c | 35 ++++++++++++++++++++++++++++++++++- src/wolfip.c | 8 +++++--- 3 files changed, 40 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 5c363f9..2cc775a 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ A single network interface can be associated with the device. | **Transport** | TCP | Connection management, reliable delivery | [RFC 793](https://datatracker.ietf.org/doc/html/rfc793), [RFC 9293](https://datatracker.ietf.org/doc/html/rfc9293) | | **Transport** | TCP | Maximum Segment Size negotiation | [RFC 793](https://datatracker.ietf.org/doc/html/rfc793) | | **Transport** | TCP | TCP Timestamps, RTT measurement, PAWS, Window Scaling | [RFC 7323](https://datatracker.ietf.org/doc/html/rfc7323) | -| **Transport** | TCP | Retransmission timeout (RTO) computation | [RFC 6298](https://datatracker.ietf.org/doc/html/rfc6298) | +| **Transport** | TCP | Retransmission timeout (RTO) computation | [RFC 6298](https://datatracker.ietf.org/doc/html/rfc6298), [RFC 5681](https://datatracker.ietf.org/doc/html/rfc5681) | | **Transport** | TCP | TCP SACK | [RFC 2018](https://datatracker.ietf.org/doc/html/rfc2018), [RFC 2883](https://datatracker.ietf.org/doc/html/rfc2883), [RFC 6675](https://datatracker.ietf.org/doc/html/rfc6675) | | **Transport** | TCP | Congestion Control: Slow start, congestion avoidance | [RFC 5681](https://datatracker.ietf.org/doc/html/rfc5681) | | **Transport** | TCP | Fast Retransmit, triple duplicate ACK detection | [RFC 5681](https://datatracker.ietf.org/doc/html/rfc5681) | diff --git a/src/test/unit/unit.c b/src/test/unit/unit.c index 8df6fe7..ec3793d 100644 --- a/src/test/unit/unit.c +++ b/src/test/unit/unit.c @@ -5900,7 +5900,39 @@ START_TEST(test_tcp_rto_cb_clears_sack_and_marks_lowest_only) ck_assert_int_ne(desc2->flags & PKT_FLAG_SENT, 0); ck_assert_int_eq(desc2->flags & PKT_FLAG_RETRANS, 0); ck_assert_uint_eq(ts->sock.tcp.cwnd, TCP_MSS); - ck_assert_uint_eq(ts->sock.tcp.ssthresh, TCP_MSS * 4); + ck_assert_uint_eq(ts->sock.tcp.ssthresh, TCP_MSS * 2); +} +END_TEST + +START_TEST(test_tcp_rto_cb_ssthresh_uses_inflight_not_cwnd) +{ + struct wolfIP s; + struct tsocket *ts; + struct pkt_desc *desc; + + 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.rto = 100; + ts->sock.tcp.cwnd = TCP_MSS * 4; + ts->sock.tcp.snd_una = 101; + ts->sock.tcp.seq = 101; + ts->sock.tcp.bytes_in_flight = TCP_MSS * 10; + fifo_init(&ts->sock.tcp.txbuf, ts->txmem, TXBUF_SIZE); + + ck_assert_int_eq(enqueue_tcp_tx(ts, 1, 0x18), 0); + desc = fifo_peek(&ts->sock.tcp.txbuf); + ck_assert_ptr_nonnull(desc); + desc->flags |= PKT_FLAG_SENT; + + s.last_tick = 1000; + tcp_rto_cb(ts); + + ck_assert_uint_eq(ts->sock.tcp.cwnd, TCP_MSS); + ck_assert_uint_eq(ts->sock.tcp.ssthresh, TCP_MSS * 5); } END_TEST @@ -14489,6 +14521,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_utils, test_tcp_rto_cb_control_retry_cap_closes_socket); tcase_add_test(tc_utils, test_tcp_rto_cb_cancels_existing_timer); tcase_add_test(tc_utils, test_tcp_rto_cb_clears_sack_and_marks_lowest_only); + tcase_add_test(tc_utils, test_tcp_rto_cb_ssthresh_uses_inflight_not_cwnd); tcase_add_test(tc_utils, test_tcp_rto_cb_ssthresh_floor_two_mss); tcase_add_test(tc_utils, test_tcp_rto_cb_fallback_marks_lowest_sent_when_no_snd_una_cover); tcase_add_test(tc_utils, test_sock_close_udp_icmp); diff --git a/src/wolfip.c b/src/wolfip.c index 2914aa4..e3bd986 100644 --- a/src/wolfip.c +++ b/src/wolfip.c @@ -3065,7 +3065,7 @@ static void tcp_rto_cb(void *arg) uint32_t guard = 0; uint32_t budget; uint32_t first_sent_seq = 0; - uint32_t prev_cwnd; + uint32_t prev_in_flight; if (ts->proto != WI_IPPROTO_TCP) return; if (tcp_ctrl_state_needs_rto(ts) || ts->sock.tcp.ctrl_rto_active) { @@ -3162,8 +3162,11 @@ static void tcp_rto_cb(void *arg) ts->events |= CB_EVENT_WRITABLE; } if (pending) { + prev_in_flight = ts->sock.tcp.bytes_in_flight; /* RTO implies all in-flight data is considered lost. */ ts->sock.tcp.bytes_in_flight = 0; + } else { + prev_in_flight = 0; } if (ts->sock.tcp.tmr_rto != NO_TIMER) { @@ -3171,10 +3174,9 @@ static void tcp_rto_cb(void *arg) ts->sock.tcp.tmr_rto = NO_TIMER; } if (pending) { - prev_cwnd = ts->sock.tcp.cwnd; ts->sock.tcp.rto_backoff++; ts->sock.tcp.cwnd = TCP_MSS; - ts->sock.tcp.ssthresh = prev_cwnd / 2; + ts->sock.tcp.ssthresh = prev_in_flight / 2; if (ts->sock.tcp.ssthresh < (2 * TCP_MSS)) ts->sock.tcp.ssthresh = 2 * TCP_MSS; From de1b75429ed33677817b300a289fde95ccbd280a Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Thu, 19 Feb 2026 18:27:51 +0100 Subject: [PATCH 07/21] Added 1-byte Zero-window probes --- src/test/unit/unit.c | 152 +++++++++++++++++++++++++++++++++ src/wolfip.c | 197 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 348 insertions(+), 1 deletion(-) diff --git a/src/test/unit/unit.c b/src/test/unit/unit.c index ec3793d..99770fd 100644 --- a/src/test/unit/unit.c +++ b/src/test/unit/unit.c @@ -12673,6 +12673,155 @@ START_TEST(test_poll_tcp_residual_window_allows_exact_fit) } END_TEST +START_TEST(test_poll_tcp_zero_window_arms_persist) +{ + struct wolfIP s; + struct tsocket *ts; + ip4 local_ip = 0x0A000001U; + ip4 remote_ip = 0x0A000002U; + uint8_t peer_mac[6] = {0x00, 0xaa, 0xbb, 0xcc, 0xdd, 0xf1}; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, local_ip, 0xFFFFFF00U, 0); + wolfIP_filter_set_callback(NULL, NULL); + last_frame_sent_size = 0; + + s.arp.neighbors[0].ip = remote_ip; + s.arp.neighbors[0].if_idx = TEST_PRIMARY_IF; + memcpy(s.arp.neighbors[0].mac, peer_mac, sizeof(peer_mac)); + + ts = &s.tcpsockets[0]; + memset(ts, 0, sizeof(*ts)); + ts->proto = WI_IPPROTO_TCP; + ts->S = &s; + ts->sock.tcp.state = TCP_ESTABLISHED; + ts->local_ip = local_ip; + ts->remote_ip = remote_ip; + ts->if_idx = TEST_PRIMARY_IF; + ts->src_port = 1111; + ts->dst_port = 2222; + ts->sock.tcp.ack = 20; + ts->sock.tcp.rto = 100; + ts->sock.tcp.cwnd = TCP_MSS * 4; + ts->sock.tcp.peer_rwnd = 0; + fifo_init(&ts->sock.tcp.txbuf, ts->txmem, TXBUF_SIZE); + + ck_assert_int_eq(enqueue_tcp_tx(ts, 8, 0x18), 0); + (void)wolfIP_poll(&s, 200); + + ck_assert_uint_eq(last_frame_sent_size, 0); + ck_assert_uint_eq(ts->sock.tcp.persist_active, 1); + ck_assert_int_ne(ts->sock.tcp.tmr_persist, NO_TIMER); +} +END_TEST + +START_TEST(test_tcp_persist_cb_sends_one_byte_probe) +{ + struct wolfIP s; + struct tsocket *ts; + ip4 local_ip = 0x0A000001U; + ip4 remote_ip = 0x0A000002U; + uint8_t peer_mac[6] = {0x00, 0xaa, 0xbb, 0xcc, 0xdd, 0xf2}; + struct wolfIP_ip_packet *ip; + struct wolfIP_tcp_seg *tcp; + uint32_t ip_len; + uint32_t tcp_hlen; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, local_ip, 0xFFFFFF00U, 0); + wolfIP_filter_set_callback(NULL, NULL); + last_frame_sent_size = 0; + + s.arp.neighbors[0].ip = remote_ip; + s.arp.neighbors[0].if_idx = TEST_PRIMARY_IF; + memcpy(s.arp.neighbors[0].mac, peer_mac, sizeof(peer_mac)); + + ts = &s.tcpsockets[0]; + memset(ts, 0, sizeof(*ts)); + ts->proto = WI_IPPROTO_TCP; + ts->S = &s; + ts->sock.tcp.state = TCP_ESTABLISHED; + ts->local_ip = local_ip; + ts->remote_ip = remote_ip; + ts->if_idx = TEST_PRIMARY_IF; + ts->src_port = 1111; + ts->dst_port = 2222; + ts->sock.tcp.ack = 20; + ts->sock.tcp.snd_una = 10; + ts->sock.tcp.rto = 100; + ts->sock.tcp.cwnd = TCP_MSS * 4; + ts->sock.tcp.peer_rwnd = 0; + fifo_init(&ts->sock.tcp.txbuf, ts->txmem, TXBUF_SIZE); + + ck_assert_int_eq(enqueue_tcp_tx(ts, 8, 0x18), 0); + s.last_tick = 500; + tcp_persist_cb(ts); + + ck_assert_uint_gt(last_frame_sent_size, 0); + ip = (struct wolfIP_ip_packet *)last_frame_sent; + tcp = (struct wolfIP_tcp_seg *)last_frame_sent; + ip_len = ee16(ip->len); + tcp_hlen = (uint32_t)(tcp->hlen >> 2); + ck_assert_uint_eq(ip_len - (IP_HEADER_LEN + tcp_hlen), 1U); + ck_assert_uint_eq(tcp->flags & 0x10, 0x10); + ck_assert_uint_eq(ts->sock.tcp.persist_active, 1); + ck_assert_int_ne(ts->sock.tcp.tmr_persist, NO_TIMER); +} +END_TEST + +START_TEST(test_tcp_input_window_reopen_stops_persist) +{ + struct wolfIP s; + struct tsocket *ts; + struct wolfIP_tcp_seg seg; + struct wolfIP_timer tmr; + ip4 local_ip = 0x0A000001U; + ip4 remote_ip = 0x0A000002U; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, local_ip, 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->sock.tcp.peer_rwnd = 0; + ts->sock.tcp.persist_active = 1; + ts->src_port = 2222; + ts->dst_port = 3333; + ts->local_ip = local_ip; + ts->remote_ip = remote_ip; + + memset(&tmr, 0, sizeof(tmr)); + tmr.cb = test_timer_cb; + tmr.expires = 100; + ts->sock.tcp.tmr_persist = timers_binheap_insert(&s.timers, tmr); + ck_assert_int_ne(ts->sock.tcp.tmr_persist, NO_TIMER); + + memset(&seg, 0, sizeof(seg)); + seg.ip.ttl = 64; + seg.ip.src = ee32(remote_ip); + seg.ip.dst = ee32(local_ip); + seg.ip.len = ee16(IP_HEADER_LEN + TCP_HEADER_LEN); + seg.src_port = ee16(ts->dst_port); + seg.dst_port = ee16(ts->src_port); + seg.hlen = TCP_HEADER_LEN << 2; + seg.flags = 0x10; + seg.win = ee16(16); + + tcp_input(&s, TEST_PRIMARY_IF, &seg, + (uint32_t)(ETH_HEADER_LEN + IP_HEADER_LEN + TCP_HEADER_LEN)); + + ck_assert_uint_gt(ts->sock.tcp.peer_rwnd, 0); + ck_assert_uint_eq(ts->sock.tcp.persist_active, 0); + ck_assert_int_eq(ts->sock.tcp.tmr_persist, NO_TIMER); +} +END_TEST + START_TEST(test_queue_insert_len_gt_size) { struct queue q; @@ -14476,6 +14625,9 @@ Suite *wolf_suite(void) tcase_add_test(tc_utils, test_poll_tcp_send_on_arp_hit); tcase_add_test(tc_utils, test_poll_tcp_residual_window_gates_data_segment); tcase_add_test(tc_utils, test_poll_tcp_residual_window_allows_exact_fit); + tcase_add_test(tc_utils, test_poll_tcp_zero_window_arms_persist); + tcase_add_test(tc_utils, test_tcp_persist_cb_sends_one_byte_probe); + tcase_add_test(tc_utils, test_tcp_input_window_reopen_stops_persist); tcase_add_test(tc_utils, test_poll_tcp_arp_request_on_miss); tcase_add_test(tc_utils, test_poll_udp_send_on_arp_hit); tcase_add_test(tc_utils, test_poll_icmp_send_on_arp_hit); diff --git a/src/wolfip.c b/src/wolfip.c index e3bd986..a092e15 100644 --- a/src/wolfip.c +++ b/src/wolfip.c @@ -117,6 +117,8 @@ struct wolfIP_icmp_packet; #define TCP_RTO_MIN_MS 1000U #define TCP_RTO_MAX_MS 60000U #define TCP_RTO_G_MS 1U +#define TCP_PERSIST_MIN_MS 1000U +#define TCP_PERSIST_MAX_MS 60000U /* Arbitrary upper limit to avoid monopolizing the CPU during poll loops. */ #define WOLFIP_POLL_BUDGET 128 @@ -992,12 +994,14 @@ enum tcp_state { struct tcpsocket { enum tcp_state state; uint32_t last_ts, rtt, rto, cwnd, cwnd_count, ssthresh, tmr_rto, rto_backoff, - seq, ack, last_ack, last, bytes_in_flight, snd_una; + tmr_persist, seq, ack, last_ack, last, bytes_in_flight, snd_una; uint32_t srtt, rttvar; uint32_t last_early_rexmit_ack; uint8_t rto_initialized; uint8_t dup_acks; uint8_t early_rexmit_done; + uint8_t persist_backoff; + uint8_t persist_active; uint8_t ctrl_rto_retries; uint8_t ctrl_rto_active; ip4 local_ip, remote_ip; @@ -1043,6 +1047,11 @@ static void close_socket(struct tsocket *ts); static inline uint32_t tcp_seq_inc(uint32_t seq, uint32_t n); static inline int tcp_seq_leq(uint32_t a, uint32_t b); static inline int tcp_seq_lt(uint32_t a, uint32_t b); +static int ip_output_add_header(struct tsocket *t, struct wolfIP_ip_packet *ip, + uint8_t proto, uint16_t len); +static void tcp_persist_cb(void *arg); +static void tcp_persist_start(struct tsocket *t, uint64_t now); +static void tcp_persist_stop(struct tsocket *t); static void tcp_rto_update_from_sample(struct tsocket *t, uint32_t sample_ms); static void tcp_rto_cb(void *arg); static void tcp_ctrl_rto_start(struct tsocket *t, uint64_t now); @@ -1078,6 +1087,7 @@ struct arp_pending_entry { }; static int arp_lookup(struct wolfIP *s, unsigned int if_idx, ip4 ip, uint8_t *mac); +static void arp_request(struct wolfIP *s, unsigned int if_idx, ip4 tip); #if WOLFIP_ENABLE_FORWARDING static void wolfIP_forward_packet(struct wolfIP *s, unsigned int out_if, struct wolfIP_ip_packet *ip, uint32_t len, @@ -1675,10 +1685,13 @@ static struct tsocket *tcp_new_socket(struct wolfIP *s) t->sock.tcp.rttvar = 0; t->sock.tcp.rto_initialized = 0; t->sock.tcp.rto_backoff = 0; + t->sock.tcp.tmr_persist = NO_TIMER; t->sock.tcp.bytes_in_flight = 0; t->sock.tcp.snd_una = t->sock.tcp.seq; t->sock.tcp.dup_acks = 0; t->sock.tcp.early_rexmit_done = 0; + t->sock.tcp.persist_backoff = 0; + t->sock.tcp.persist_active = 0; t->sock.tcp.ctrl_rto_retries = 0; t->sock.tcp.ctrl_rto_active = 0; t->sock.tcp.last_early_rexmit_ack = 0; @@ -2173,6 +2186,177 @@ static void tcp_ctrl_rto_start(struct tsocket *t, uint64_t now) t->sock.tcp.ctrl_rto_active = 1; } +static int tcp_has_pending_unsent_payload(struct tsocket *t) +{ + struct pkt_desc *desc; + uint32_t guard = 0; + uint32_t budget; + + if (!t) + return 0; + budget = fifo_desc_budget(&t->sock.tcp.txbuf); + desc = fifo_peek(&t->sock.tcp.txbuf); + while (desc && guard++ < budget) { + 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)); + if (seg_len > 0 && !(desc->flags & PKT_FLAG_SENT)) + return 1; + desc = fifo_next(&t->sock.tcp.txbuf, desc); + } + return 0; +} + +static uint32_t tcp_persist_interval_ms(const struct tsocket *t) +{ + uint64_t interval = (uint64_t)t->sock.tcp.rto << t->sock.tcp.persist_backoff; + if (interval < TCP_PERSIST_MIN_MS) + interval = TCP_PERSIST_MIN_MS; + if (interval > TCP_PERSIST_MAX_MS) + interval = TCP_PERSIST_MAX_MS; + return (uint32_t)interval; +} + +static void tcp_persist_stop(struct tsocket *t) +{ + if (!t || t->proto != WI_IPPROTO_TCP) + return; + if (t->sock.tcp.tmr_persist != NO_TIMER) { + timer_binheap_cancel(&t->S->timers, t->sock.tcp.tmr_persist); + t->sock.tcp.tmr_persist = NO_TIMER; + } + t->sock.tcp.persist_backoff = 0; + t->sock.tcp.persist_active = 0; +} + +static void tcp_persist_start(struct tsocket *t, uint64_t now) +{ + struct wolfIP_timer tmr = {0}; + uint32_t interval; + + if (!t || t->proto != WI_IPPROTO_TCP) + return; + if (t->sock.tcp.peer_rwnd > 0 || !tcp_has_pending_unsent_payload(t)) { + tcp_persist_stop(t); + return; + } + if (t->sock.tcp.tmr_persist != NO_TIMER) { + timer_binheap_cancel(&t->S->timers, t->sock.tcp.tmr_persist); + t->sock.tcp.tmr_persist = NO_TIMER; + } + interval = tcp_persist_interval_ms(t); + tmr.expires = now + interval; + tmr.arg = t; + tmr.cb = tcp_persist_cb; + t->sock.tcp.tmr_persist = timers_binheap_insert(&t->S->timers, tmr); + t->sock.tcp.persist_active = 1; +} + +static int tcp_send_zero_wnd_probe(struct tsocket *t) +{ + struct pkt_desc *desc; + uint32_t guard = 0; + uint32_t budget; + uint8_t probe_frame[ETH_HEADER_LEN + IP_HEADER_LEN + TCP_HEADER_LEN + 1]; + struct wolfIP_tcp_seg *probe = (struct wolfIP_tcp_seg *)probe_frame; + uint8_t probe_byte = 0; + uint32_t probe_seq = t->sock.tcp.snd_una; + unsigned int tx_if; +#ifdef ETHERNET + struct ipconf *conf; + ip4 nexthop; +#endif + + if (!t || t->proto != WI_IPPROTO_TCP) + return -1; + budget = fifo_desc_budget(&t->sock.tcp.txbuf); + desc = fifo_peek(&t->sock.tcp.txbuf); + 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_seq = ee32(seg->seq); + const uint8_t *payload; + if (seg_len == 0) { + desc = fifo_next(&t->sock.tcp.txbuf, desc); + continue; + } + payload = (const uint8_t *)seg->ip.data + hdr_len; + probe_byte = payload[0]; + if (tcp_seq_leq(seg_seq, t->sock.tcp.snd_una) && + tcp_seq_lt(t->sock.tcp.snd_una, tcp_seq_inc(seg_seq, seg_len))) { + probe_seq = t->sock.tcp.snd_una; + break; + } + probe_seq = seg_seq; + break; + } + if (!desc) + return -1; + + memset(probe, 0, sizeof(probe_frame)); + probe->src_port = ee16(t->src_port); + probe->dst_port = ee16(t->dst_port); + probe->seq = ee32(probe_seq); + probe->ack = ee32(t->sock.tcp.ack); + probe->hlen = TCP_HEADER_LEN << 2; + probe->flags = 0x10; + probe->win = ee16(tcp_adv_win(t)); + probe->data[0] = probe_byte; + + tx_if = wolfIP_socket_if_idx(t); +#ifdef ETHERNET + conf = wolfIP_ipconf_at(t->S, tx_if); + nexthop = wolfIP_select_nexthop(conf, t->remote_ip); + if (wolfIP_is_loopback_if(tx_if)) { + struct wolfIP_ll_dev *loop = wolfIP_ll_at(t->S, tx_if); + if (loop) + memcpy(t->nexthop_mac, loop->mac, 6); + } else if (arp_lookup(t->S, tx_if, nexthop, t->nexthop_mac) < 0) { + arp_request(t->S, tx_if, nexthop); + return -1; + } +#endif + ip_output_add_header(t, (struct wolfIP_ip_packet *)probe, WI_IPPROTO_TCP, + (uint16_t)(IP_HEADER_LEN + TCP_HEADER_LEN + 1)); + + if (wolfIP_filter_notify_tcp(WOLFIP_FILT_SENDING, t->S, tx_if, probe, sizeof(probe_frame)) != 0) + return -1; + if (wolfIP_filter_notify_ip(WOLFIP_FILT_SENDING, t->S, tx_if, &probe->ip, sizeof(probe_frame)) != 0) + return -1; +#ifdef ETHERNET + if (wolfIP_filter_notify_eth(WOLFIP_FILT_SENDING, t->S, tx_if, &probe->ip.eth, sizeof(probe_frame)) != 0) + return -1; +#endif + { + struct wolfIP_ll_dev *ll = wolfIP_ll_at(t->S, tx_if); + if (!ll || !ll->send) + return -1; + ll->send(ll, probe, sizeof(probe_frame)); + } + return 0; +} + +static void tcp_persist_cb(void *arg) +{ + struct tsocket *t = (struct tsocket *)arg; + if (!t || t->proto != WI_IPPROTO_TCP) + return; + if (t->sock.tcp.state != TCP_ESTABLISHED && t->sock.tcp.state != TCP_CLOSE_WAIT) { + tcp_persist_stop(t); + return; + } + if (t->sock.tcp.peer_rwnd > 0 || !tcp_has_pending_unsent_payload(t)) { + tcp_persist_stop(t); + return; + } + (void)tcp_send_zero_wnd_probe(t); + if (t->sock.tcp.persist_backoff < 10) + t->sock.tcp.persist_backoff++; + tcp_persist_start(t, t->S->last_tick); +} + /* Increment a TCP sequence number (wraps at 2^32) */ static inline uint32_t tcp_seq_inc(uint32_t seq, uint32_t n) { @@ -2896,6 +3080,8 @@ static void tcp_input(struct wolfIP *S, unsigned int if_idx, uint8_t ws_shift = t->sock.tcp.ws_enabled ? t->sock.tcp.snd_wscale : 0; t->sock.tcp.peer_rwnd = (uint32_t)raw_win << ws_shift; if (t->sock.tcp.peer_rwnd > prev_peer_rwnd) { + if (t->sock.tcp.persist_active) + tcp_persist_stop(t); t->events |= CB_EVENT_WRITABLE; } } @@ -3263,6 +3449,8 @@ static struct pkt_desc *tcp_find_pending_retrans(struct tsocket *ts, struct pkt_ static void close_socket(struct tsocket *ts) { + if (ts && ts->proto == WI_IPPROTO_TCP) + tcp_persist_stop(ts); memset(ts, 0, sizeof(struct tsocket)); } @@ -5362,6 +5550,9 @@ int wolfIP_poll(struct wolfIP *s, uint64_t now) struct wolfIP_tcp_seg *tcp; tcp_resync_inflight(s, ts, now); in_flight = ts->sock.tcp.bytes_in_flight; + if (ts->sock.tcp.persist_active && (ts->sock.tcp.peer_rwnd > 0 || + !tcp_has_pending_unsent_payload(ts))) + tcp_persist_stop(ts); desc = fifo_peek(&ts->sock.tcp.txbuf); while (desc && send_guard++ < send_budget) { unsigned int tx_if = wolfIP_socket_if_idx(ts); @@ -5460,9 +5651,13 @@ int wolfIP_poll(struct wolfIP *s, uint64_t now) if (next_desc == desc) break; desc = next_desc; + if (ts->sock.tcp.persist_active && ts->sock.tcp.peer_rwnd > 0) + tcp_persist_stop(ts); } } else { struct pkt_desc *rexmit_desc = NULL; + if (seg_payload_len > 0 && ts->sock.tcp.peer_rwnd == 0) + tcp_persist_start(ts, now); if (!is_retrans) { rexmit_desc = tcp_find_pending_retrans(ts, desc); if (rexmit_desc && rexmit_desc != desc) { From 35ca1dd6d9475901862ab46df2813d84df307c4d Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Thu, 19 Feb 2026 18:44:51 +0100 Subject: [PATCH 08/21] Fix TCP TS option negotiation Only send ACKs with TS option if negotiated. --- src/test/unit/unit.c | 25 +++++++++++++++ src/wolfip.c | 76 +++++++++++++++++++++++++++----------------- 2 files changed, 72 insertions(+), 29 deletions(-) diff --git a/src/test/unit/unit.c b/src/test/unit/unit.c index 99770fd..f819bbb 100644 --- a/src/test/unit/unit.c +++ b/src/test/unit/unit.c @@ -8809,6 +8809,7 @@ START_TEST(test_tcp_build_ack_options_does_not_write_past_returned_len) memset(ts, 0, sizeof(*ts)); ts->proto = WI_IPPROTO_TCP; ts->S = &s; + ts->sock.tcp.ts_enabled = 1; ts->sock.tcp.sack_permitted = 0; ts->sock.tcp.rx_sack_count = 0; @@ -8820,6 +8821,29 @@ START_TEST(test_tcp_build_ack_options_does_not_write_past_returned_len) } END_TEST +START_TEST(test_tcp_build_ack_options_omits_ts_when_not_negotiated) +{ + struct wolfIP s; + struct tsocket *ts; + uint8_t opts[TCP_MAX_OPTIONS_LEN]; + uint8_t len; + + wolfIP_init(&s); + ts = &s.tcpsockets[0]; + memset(ts, 0, sizeof(*ts)); + ts->proto = WI_IPPROTO_TCP; + ts->S = &s; + ts->sock.tcp.ts_enabled = 0; + ts->sock.tcp.sack_permitted = 0; + ts->sock.tcp.rx_sack_count = 0; + + memset(opts, 0xCC, sizeof(opts)); + len = tcp_build_ack_options(ts, opts, sizeof(opts)); + ck_assert_uint_eq(len, 0); + ck_assert_uint_eq(opts[0], 0xCC); +} +END_TEST + START_TEST(test_tcp_sort_sack_blocks_swaps_out_of_order) { struct tcp_sack_block blocks[3]; @@ -14740,6 +14764,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_utils, test_tcp_process_ts_updates_rtt_when_set); tcase_add_test(tc_utils, test_tcp_send_syn_advertises_sack_permitted); tcase_add_test(tc_utils, test_tcp_build_ack_options_does_not_write_past_returned_len); + tcase_add_test(tc_utils, test_tcp_build_ack_options_omits_ts_when_not_negotiated); tcase_add_test(tc_utils, test_tcp_sort_sack_blocks_swaps_out_of_order); tcase_add_test(tc_utils, test_tcp_merge_sack_blocks_adjacent_and_disjoint); tcase_add_test(tc_utils, test_tcp_recv_tracks_holes_and_sack_blocks); diff --git a/src/wolfip.c b/src/wolfip.c index a092e15..60afe93 100644 --- a/src/wolfip.c +++ b/src/wolfip.c @@ -1008,6 +1008,7 @@ struct tcpsocket { uint32_t peer_rwnd; uint16_t peer_mss; uint8_t snd_wscale, rcv_wscale, ws_enabled, ws_offer; + uint8_t ts_enabled, ts_offer; uint8_t sack_offer, sack_permitted; uint8_t rx_sack_count, peer_sack_count; struct tcp_sack_block rx_sack[TCP_SACK_MAX_BLOCKS]; @@ -1701,6 +1702,7 @@ static struct tsocket *tcp_new_socket(struct wolfIP *s) t->sock.tcp.peer_mss = TCP_DEFAULT_MSS; t->sock.tcp.snd_wscale = 0; t->sock.tcp.ws_enabled = 0; + t->sock.tcp.ts_enabled = 0; t->sock.tcp.sack_offer = 1; t->sock.tcp.sack_permitted = 0; t->sock.tcp.rx_sack_count = 0; @@ -1716,6 +1718,7 @@ static struct tsocket *tcp_new_socket(struct wolfIP *s) /* We always include WS in the initial SYN (shift may be 0), so * mark that we offered it to accept the peer's WS in SYN-ACK. */ t->sock.tcp.ws_offer = 1; + t->sock.tcp.ts_offer = 1; queue_init(&t->sock.tcp.rxbuf, t->rxmem, RXBUF_SIZE, 0); fifo_init(&t->sock.tcp.txbuf, t->txmem, TXBUF_SIZE); @@ -1998,17 +2001,20 @@ static void tcp_consume_ooo(struct tsocket *t) static uint8_t tcp_build_ack_options(struct tsocket *t, uint8_t *opt, uint8_t max_len) { - struct tcp_opt_ts *ts = (struct tcp_opt_ts *)opt; + struct tcp_opt_ts *ts; uint8_t len = 0; - if (max_len < TCP_OPTION_TS_LEN) - return 0; - ts->opt = TCP_OPTION_TS; - ts->len = TCP_OPTION_TS_LEN; - ts->val = ee32(t->S->last_tick & 0xFFFFFFFFU); - ts->ecr = t->sock.tcp.last_ts; - len += TCP_OPTION_TS_LEN; - opt += TCP_OPTION_TS_LEN; + if (t->sock.tcp.ts_enabled) { + if (max_len < TCP_OPTION_TS_LEN) + return 0; + ts = (struct tcp_opt_ts *)opt; + ts->opt = TCP_OPTION_TS; + ts->len = TCP_OPTION_TS_LEN; + ts->val = ee32(t->S->last_tick & 0xFFFFFFFFU); + ts->ecr = t->sock.tcp.last_ts; + len += TCP_OPTION_TS_LEN; + opt += TCP_OPTION_TS_LEN; + } /* SACK option is sent only after successful negotiation and only while we * still hold non-contiguous data above cumulative ACK. */ @@ -2082,6 +2088,7 @@ static void tcp_send_syn(struct tsocket *t, uint8_t flags) uint8_t buffer[sizeof(struct wolfIP_tcp_seg) + TCP_MAX_OPTIONS_LEN]; uint8_t include_ws = 0; uint8_t include_sack = 0; + uint8_t include_ts = 0; uint8_t opt_len = 0; tcp = (struct wolfIP_tcp_seg *)buffer; memset(tcp, 0, sizeof(buffer)); @@ -2090,10 +2097,12 @@ static void tcp_send_syn(struct tsocket *t, uint8_t flags) /* SYN-ACK: include WS only when enabled on this socket. */ include_ws = t->sock.tcp.ws_enabled; include_sack = t->sock.tcp.sack_permitted; + include_ts = t->sock.tcp.ts_enabled; } else { /* Initial SYN: always include WS to allow peer scaling. */ include_ws = 1; include_sack = t->sock.tcp.sack_offer; + include_ts = t->sock.tcp.ts_offer; } } tcp->src_port = ee16(t->src_port); @@ -2105,15 +2114,17 @@ static void tcp_send_syn(struct tsocket *t, uint8_t flags) tcp->csum = 0; tcp->urg = 0; opt = tcp->data; - ts = (struct tcp_opt_ts *)opt; - ts->opt = TCP_OPTION_TS; - ts->len = TCP_OPTION_TS_LEN; - ts->val = ee32(t->S->last_tick & 0xFFFFFFFFU); - ts->ecr = t->sock.tcp.last_ts; - ts->pad = TCP_OPTION_NOP; - ts->eoo = TCP_OPTION_NOP; - opt += sizeof(*ts); - opt_len += sizeof(*ts); + if (include_ts) { + ts = (struct tcp_opt_ts *)opt; + ts->opt = TCP_OPTION_TS; + ts->len = TCP_OPTION_TS_LEN; + ts->val = ee32(t->S->last_tick & 0xFFFFFFFFU); + ts->ecr = t->sock.tcp.last_ts; + ts->pad = TCP_OPTION_NOP; + ts->eoo = TCP_OPTION_NOP; + opt += sizeof(*ts); + opt_len += sizeof(*ts); + } mss = (struct tcp_opt_mss *)opt; mss->opt = TCP_OPTION_MSS; mss->len = TCP_OPTION_MSS_LEN; @@ -3054,6 +3065,7 @@ static void tcp_input(struct wolfIP *S, unsigned int if_idx, /* Server side: enable if peer offered WS. */ t->sock.tcp.peer_mss = po.mss_found ? po.mss : TCP_DEFAULT_MSS; t->sock.tcp.ws_enabled = po.ws_found ? 1 : 0; + t->sock.tcp.ts_enabled = po.ts_found ? 1 : 0; if (po.ws_found) t->sock.tcp.snd_wscale = po.ws_shift; t->sock.tcp.sack_permitted = @@ -3070,6 +3082,7 @@ static void tcp_input(struct wolfIP *S, unsigned int if_idx, t->sock.tcp.ws_enabled = 0; t->sock.tcp.snd_wscale = 0; } + t->sock.tcp.ts_enabled = (t->sock.tcp.ts_offer && po.ts_found) ? 1 : 0; t->sock.tcp.sack_permitted = (t->sock.tcp.sack_offer && po.sack_permitted) ? 1 : 0; } @@ -3666,6 +3679,8 @@ int wolfIP_sock_accept(struct wolfIP *s, int sockfd, struct wolfIP_sockaddr *add newts->sock.tcp.rcv_wscale = ts->sock.tcp.rcv_wscale; newts->sock.tcp.ws_enabled = ts->sock.tcp.ws_enabled; newts->sock.tcp.ws_offer = ts->sock.tcp.ws_offer; + newts->sock.tcp.ts_enabled = ts->sock.tcp.ts_enabled; + newts->sock.tcp.ts_offer = ts->sock.tcp.ts_offer; newts->sock.tcp.sack_offer = ts->sock.tcp.sack_offer; newts->sock.tcp.sack_permitted = ts->sock.tcp.sack_permitted; newts->sock.tcp.state = TCP_ESTABLISHED; @@ -3725,8 +3740,6 @@ int wolfIP_sock_sendto(struct wolfIP *s, int sockfd, const void *buf, size_t len if (IS_SOCKET_TCP(sockfd)) { size_t sent = 0; unsigned int push_iter = 0; - struct tcp_opt_ts *tsopt = (struct tcp_opt_ts *)tcp->data; - const uint32_t frame_base = (uint32_t)(sizeof(struct wolfIP_tcp_seg) + TCP_OPTIONS_LEN); if (SOCKET_UNMARK(sockfd) >= MAX_TCPSOCKETS) return -WOLFIP_EINVAL; @@ -3737,6 +3750,8 @@ int wolfIP_sock_sendto(struct wolfIP *s, int sockfd, const void *buf, size_t len while (sent < len) { uint32_t payload_len; uint32_t payload_cap = (uint32_t)(len - sent); + uint32_t opt_len = ts->sock.tcp.ts_enabled ? TCP_OPTIONS_LEN : 0; + uint32_t frame_base = (uint32_t)(sizeof(struct wolfIP_tcp_seg) + opt_len); uint32_t tx_cap = tcp_tx_payload_cap(ts); push_iter++; if (payload_cap > tx_cap) @@ -3752,20 +3767,23 @@ int wolfIP_sock_sendto(struct wolfIP *s, int sockfd, const void *buf, size_t len tcp->dst_port = ee16(ts->dst_port); tcp->seq = ee32(ts->sock.tcp.seq); tcp->ack = ee32(ts->sock.tcp.ack); - tcp->hlen = (TCP_HEADER_LEN + TCP_OPTIONS_LEN) << 2; + tcp->hlen = (uint8_t)((TCP_HEADER_LEN + opt_len) << 2); tcp->flags = 0x10 | ((sent == 0)? 0x08 : 0); /* ACK; PSH only on first */ tcp->win = ee16(tcp_adv_win(ts)); tcp->csum = 0; tcp->urg = 0; - tsopt->opt = TCP_OPTION_TS; - tsopt->len = TCP_OPTION_TS_LEN; - tsopt->val = ee32(s->last_tick & 0xFFFFFFFF); - tsopt->ecr = ts->sock.tcp.last_ts; - tsopt->pad = 0x01; - tsopt->eoo = 0x00; - memcpy((uint8_t *)tcp->data + TCP_OPTIONS_LEN, (const uint8_t *)buf + sent, payload_len); + if (ts->sock.tcp.ts_enabled) { + struct tcp_opt_ts *tsopt = (struct tcp_opt_ts *)tcp->data; + tsopt->opt = TCP_OPTION_TS; + tsopt->len = TCP_OPTION_TS_LEN; + tsopt->val = ee32(s->last_tick & 0xFFFFFFFF); + tsopt->ecr = ts->sock.tcp.last_ts; + tsopt->pad = 0x01; + tsopt->eoo = 0x00; + } + memcpy((uint8_t *)tcp->data + opt_len, (const uint8_t *)buf + sent, payload_len); if (fifo_push(&ts->sock.tcp.txbuf, tcp, - sizeof(struct wolfIP_tcp_seg) + TCP_OPTIONS_LEN + payload_len) < 0) { + sizeof(struct wolfIP_tcp_seg) + opt_len + payload_len) < 0) { break; } sent += payload_len; From f6c68e222ce870ac76d18119f53ed23d2b6265c4 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Thu, 19 Feb 2026 18:51:44 +0100 Subject: [PATCH 09/21] Fix misplaced null-check orderings (cppcheck) --- src/wolfip.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/wolfip.c b/src/wolfip.c index 60afe93..74b11c1 100644 --- a/src/wolfip.c +++ b/src/wolfip.c @@ -2272,7 +2272,7 @@ static int tcp_send_zero_wnd_probe(struct tsocket *t) uint8_t probe_frame[ETH_HEADER_LEN + IP_HEADER_LEN + TCP_HEADER_LEN + 1]; struct wolfIP_tcp_seg *probe = (struct wolfIP_tcp_seg *)probe_frame; uint8_t probe_byte = 0; - uint32_t probe_seq = t->sock.tcp.snd_una; + uint32_t probe_seq; unsigned int tx_if; #ifdef ETHERNET struct ipconf *conf; @@ -2281,6 +2281,7 @@ static int tcp_send_zero_wnd_probe(struct tsocket *t) if (!t || t->proto != WI_IPPROTO_TCP) return -1; + probe_seq = t->sock.tcp.snd_una; budget = fifo_desc_budget(&t->sock.tcp.txbuf); desc = fifo_peek(&t->sock.tcp.txbuf); while (desc && guard++ < budget) { @@ -3462,7 +3463,9 @@ static struct pkt_desc *tcp_find_pending_retrans(struct tsocket *ts, struct pkt_ static void close_socket(struct tsocket *ts) { - if (ts && ts->proto == WI_IPPROTO_TCP) + if (!ts) + return; + if (ts->proto == WI_IPPROTO_TCP) tcp_persist_stop(ts); memset(ts, 0, sizeof(struct tsocket)); } From f39e349d8309f4a4f727ac167959eca230ee024e Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Fri, 20 Feb 2026 18:23:22 +0100 Subject: [PATCH 10/21] Copilot comment from PR #35 Fix: probe_seq may be set to snd_una when snd_una falls within an existing queued segment --- src/test/unit/unit.c | 75 ++++++++++++++++++++++++++++++++++++++++++++ src/wolfip.c | 6 +++- 2 files changed, 80 insertions(+), 1 deletion(-) diff --git a/src/test/unit/unit.c b/src/test/unit/unit.c index f819bbb..8ffe12a 100644 --- a/src/test/unit/unit.c +++ b/src/test/unit/unit.c @@ -477,6 +477,31 @@ static int enqueue_tcp_tx(struct tsocket *ts, uint32_t payload_len, uint8_t flag return fifo_push(&ts->sock.tcp.txbuf, tcp, frame_len); } +static int enqueue_tcp_tx_with_payload(struct tsocket *ts, const uint8_t *payload_data, + uint32_t payload_len, uint8_t flags) +{ + uint8_t buf[ETH_HEADER_LEN + IP_HEADER_LEN + TCP_HEADER_LEN + 16]; + struct wolfIP_tcp_seg *tcp = (struct wolfIP_tcp_seg *)buf; + uint32_t total_len = IP_HEADER_LEN + TCP_HEADER_LEN + payload_len; + uint32_t frame_len = ETH_HEADER_LEN + total_len; + uint8_t *payload; + + ck_assert_uint_le(payload_len, 16); + memset(tcp, 0, sizeof(buf)); + tcp->ip.len = ee16((uint16_t)total_len); + tcp->hlen = TCP_HEADER_LEN << 2; + tcp->flags = flags; + tcp->seq = ee32(ts->sock.tcp.seq); + tcp->ack = ee32(ts->sock.tcp.ack); + tcp->src_port = ee16(ts->src_port); + tcp->dst_port = ee16(ts->dst_port); + if (payload_len > 0) { + payload = (uint8_t *)tcp->ip.data + TCP_HEADER_LEN; + memcpy(payload, payload_data, payload_len); + } + return fifo_push(&ts->sock.tcp.txbuf, tcp, frame_len); +} + static void enqueue_udp_rx(struct tsocket *ts, const void *payload, uint16_t payload_len, uint16_t src_port) { uint8_t buf[sizeof(struct wolfIP_udp_datagram) + 1024]; @@ -12795,6 +12820,55 @@ START_TEST(test_tcp_persist_cb_sends_one_byte_probe) } END_TEST +START_TEST(test_tcp_persist_probe_byte_matches_snd_una_offset) +{ + struct wolfIP s; + struct tsocket *ts; + ip4 local_ip = 0x0A000001U; + ip4 remote_ip = 0x0A000002U; + uint8_t peer_mac[6] = {0x00, 0xaa, 0xbb, 0xcc, 0xdd, 0x44}; + uint8_t payload[8] = {0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17}; + struct wolfIP_tcp_seg *tcp; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, local_ip, 0xFFFFFF00U, 0); + wolfIP_filter_set_callback(NULL, NULL); + last_frame_sent_size = 0; + + s.arp.neighbors[0].ip = remote_ip; + s.arp.neighbors[0].if_idx = TEST_PRIMARY_IF; + memcpy(s.arp.neighbors[0].mac, peer_mac, sizeof(peer_mac)); + + ts = &s.tcpsockets[0]; + memset(ts, 0, sizeof(*ts)); + ts->proto = WI_IPPROTO_TCP; + ts->S = &s; + ts->sock.tcp.state = TCP_ESTABLISHED; + ts->local_ip = local_ip; + ts->remote_ip = remote_ip; + ts->if_idx = TEST_PRIMARY_IF; + ts->src_port = 1111; + ts->dst_port = 2222; + ts->sock.tcp.seq = 100; + ts->sock.tcp.snd_una = 103; + ts->sock.tcp.ack = 20; + ts->sock.tcp.rto = 100; + ts->sock.tcp.cwnd = TCP_MSS * 4; + ts->sock.tcp.peer_rwnd = 0; + fifo_init(&ts->sock.tcp.txbuf, ts->txmem, TXBUF_SIZE); + + ck_assert_int_eq(enqueue_tcp_tx_with_payload(ts, payload, sizeof(payload), 0x18), 0); + s.last_tick = 1000; + tcp_persist_cb(ts); + + ck_assert_uint_gt(last_frame_sent_size, 0); + tcp = (struct wolfIP_tcp_seg *)last_frame_sent; + ck_assert_uint_eq(ee32(tcp->seq), 103U); + ck_assert_uint_eq(tcp->data[0], payload[3]); +} +END_TEST + START_TEST(test_tcp_input_window_reopen_stops_persist) { struct wolfIP s; @@ -14651,6 +14725,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_utils, test_poll_tcp_residual_window_allows_exact_fit); tcase_add_test(tc_utils, test_poll_tcp_zero_window_arms_persist); tcase_add_test(tc_utils, test_tcp_persist_cb_sends_one_byte_probe); + tcase_add_test(tc_utils, test_tcp_persist_probe_byte_matches_snd_una_offset); tcase_add_test(tc_utils, test_tcp_input_window_reopen_stops_persist); tcase_add_test(tc_utils, test_poll_tcp_arp_request_on_miss); tcase_add_test(tc_utils, test_poll_udp_send_on_arp_hit); diff --git a/src/wolfip.c b/src/wolfip.c index 74b11c1..47b716b 100644 --- a/src/wolfip.c +++ b/src/wolfip.c @@ -2273,6 +2273,7 @@ static int tcp_send_zero_wnd_probe(struct tsocket *t) struct wolfIP_tcp_seg *probe = (struct wolfIP_tcp_seg *)probe_frame; uint8_t probe_byte = 0; uint32_t probe_seq; + uint32_t probe_off = 0; unsigned int tx_if; #ifdef ETHERNET struct ipconf *conf; @@ -2295,13 +2296,16 @@ static int tcp_send_zero_wnd_probe(struct tsocket *t) continue; } payload = (const uint8_t *)seg->ip.data + hdr_len; - probe_byte = payload[0]; if (tcp_seq_leq(seg_seq, t->sock.tcp.snd_una) && tcp_seq_lt(t->sock.tcp.snd_una, tcp_seq_inc(seg_seq, seg_len))) { probe_seq = t->sock.tcp.snd_una; + probe_off = probe_seq - seg_seq; + probe_byte = payload[probe_off]; break; } probe_seq = seg_seq; + probe_off = 0; + probe_byte = payload[probe_off]; break; } if (!desc) From 28ee059f587b3f02bd52dfbb0919b350aa85fa62 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Fri, 20 Feb 2026 18:27:05 +0100 Subject: [PATCH 11/21] Fix copilot note (PR #35) Proper clean-up on close: Manage ACK in TCP_LAST_ACK state. --- src/test/unit/unit.c | 19 +++++++++++++++++++ src/wolfip.c | 1 + 2 files changed, 20 insertions(+) diff --git a/src/test/unit/unit.c b/src/test/unit/unit.c index 8ffe12a..eafa41d 100644 --- a/src/test/unit/unit.c +++ b/src/test/unit/unit.c @@ -8566,10 +8566,14 @@ START_TEST(test_tcp_last_ack_closes_socket) { struct wolfIP s; struct tsocket *ts; + struct wolfIP_timer tmr; ip4 local_ip = 0x0A000001U; ip4 remote_ip = 0x0A0000E1U; uint16_t local_port = 6666; uint16_t remote_port = 7777; + uint32_t ctrl_rto_id; + uint32_t i; + int found_canceled = 0; wolfIP_init(&s); mock_link_init(&s); @@ -8584,10 +8588,25 @@ START_TEST(test_tcp_last_ack_closes_socket) ts->remote_ip = remote_ip; ts->src_port = local_port; ts->dst_port = remote_port; + ts->sock.tcp.ctrl_rto_active = 1; + memset(&tmr, 0, sizeof(tmr)); + tmr.cb = test_timer_cb; + tmr.expires = 12345; + ctrl_rto_id = timers_binheap_insert(&s.timers, tmr); + ck_assert_int_ne(ctrl_rto_id, NO_TIMER); + ts->sock.tcp.tmr_rto = ctrl_rto_id; inject_tcp_segment(&s, TEST_PRIMARY_IF, remote_ip, local_ip, remote_port, local_port, 10, 0, 0x10); ck_assert_int_eq(ts->proto, 0); + for (i = 0; i < s.timers.size; i++) { + if (s.timers.timers[i].id == ctrl_rto_id) { + found_canceled = 1; + ck_assert_uint_eq(s.timers.timers[i].expires, 0); + break; + } + } + ck_assert_int_eq(found_canceled, 1); } END_TEST START_TEST(test_fifo_pop_success) { diff --git a/src/wolfip.c b/src/wolfip.c index 47b716b..f462b6c 100644 --- a/src/wolfip.c +++ b/src/wolfip.c @@ -3223,6 +3223,7 @@ static void tcp_input(struct wolfIP *S, unsigned int if_idx, } } else if (t->sock.tcp.state == TCP_LAST_ACK) { tcp_send_ack(t); + tcp_ctrl_rto_stop(t); close_socket(t); } else if ((t->sock.tcp.state == TCP_ESTABLISHED) || From 7c77cd26f9417393e5479b2eed959bfeb9a06773 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Fri, 20 Feb 2026 18:32:17 +0100 Subject: [PATCH 12/21] Improved test case as suggeste by Copilot in #35 --- src/test/unit/unit.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/test/unit/unit.c b/src/test/unit/unit.c index eafa41d..a00f7d4 100644 --- a/src/test/unit/unit.c +++ b/src/test/unit/unit.c @@ -12795,6 +12795,7 @@ START_TEST(test_tcp_persist_cb_sends_one_byte_probe) struct wolfIP_tcp_seg *tcp; uint32_t ip_len; uint32_t tcp_hlen; + uint8_t payload[8] = {0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c}; wolfIP_init(&s); mock_link_init(&s); @@ -12816,14 +12817,15 @@ START_TEST(test_tcp_persist_cb_sends_one_byte_probe) ts->if_idx = TEST_PRIMARY_IF; ts->src_port = 1111; ts->dst_port = 2222; + ts->sock.tcp.seq = 10; ts->sock.tcp.ack = 20; - ts->sock.tcp.snd_una = 10; + ts->sock.tcp.snd_una = 13; ts->sock.tcp.rto = 100; ts->sock.tcp.cwnd = TCP_MSS * 4; ts->sock.tcp.peer_rwnd = 0; fifo_init(&ts->sock.tcp.txbuf, ts->txmem, TXBUF_SIZE); - ck_assert_int_eq(enqueue_tcp_tx(ts, 8, 0x18), 0); + ck_assert_int_eq(enqueue_tcp_tx_with_payload(ts, payload, sizeof(payload), 0x18), 0); s.last_tick = 500; tcp_persist_cb(ts); @@ -12833,6 +12835,8 @@ START_TEST(test_tcp_persist_cb_sends_one_byte_probe) ip_len = ee16(ip->len); tcp_hlen = (uint32_t)(tcp->hlen >> 2); ck_assert_uint_eq(ip_len - (IP_HEADER_LEN + tcp_hlen), 1U); + ck_assert_uint_eq(ee32(tcp->seq), ts->sock.tcp.snd_una); + ck_assert_uint_eq(tcp->data[0], 0x18U); ck_assert_uint_eq(tcp->flags & 0x10, 0x10); ck_assert_uint_eq(ts->sock.tcp.persist_active, 1); ck_assert_int_ne(ts->sock.tcp.tmr_persist, NO_TIMER); From c43399c3ebd232a893ee45f2172e30822887036c Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Fri, 20 Feb 2026 18:38:18 +0100 Subject: [PATCH 13/21] Validate FIN in TCP_LAST_ACK (tcp_input->tcp_ack) TCP_LAST_ACK in tcp_input() will process ACKs via tcp_ack() This ensures socket close and control-RTO cleanup only happen when ACK actually covers FIN. --- src/test/unit/unit.c | 57 +++++++++++++++++++++++++++++++++++++++++++- src/wolfip.c | 27 +++++++++++++-------- 2 files changed, 73 insertions(+), 11 deletions(-) diff --git a/src/test/unit/unit.c b/src/test/unit/unit.c index a00f7d4..1bc5b0b 100644 --- a/src/test/unit/unit.c +++ b/src/test/unit/unit.c @@ -8584,6 +8584,7 @@ START_TEST(test_tcp_last_ack_closes_socket) ts->proto = WI_IPPROTO_TCP; ts->S = &s; ts->sock.tcp.state = TCP_LAST_ACK; + ts->sock.tcp.last = 9; ts->local_ip = local_ip; ts->remote_ip = remote_ip; ts->src_port = local_port; @@ -8597,7 +8598,7 @@ START_TEST(test_tcp_last_ack_closes_socket) ts->sock.tcp.tmr_rto = ctrl_rto_id; inject_tcp_segment(&s, TEST_PRIMARY_IF, remote_ip, local_ip, remote_port, local_port, - 10, 0, 0x10); + 10, 10, 0x10); ck_assert_int_eq(ts->proto, 0); for (i = 0; i < s.timers.size; i++) { if (s.timers.timers[i].id == ctrl_rto_id) { @@ -8609,6 +8610,59 @@ START_TEST(test_tcp_last_ack_closes_socket) ck_assert_int_eq(found_canceled, 1); } END_TEST + +START_TEST(test_tcp_last_ack_partial_ack_keeps_socket_and_timer) +{ + struct wolfIP s; + struct tsocket *ts; + struct wolfIP_timer tmr; + ip4 local_ip = 0x0A000001U; + ip4 remote_ip = 0x0A0000E2U; + uint16_t local_port = 6667; + uint16_t remote_port = 7778; + uint32_t ctrl_rto_id; + uint32_t i; + int found_active = 0; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, local_ip, 0xFFFFFF00U, 0); + + 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.last = 9; + ts->local_ip = local_ip; + ts->remote_ip = remote_ip; + ts->src_port = local_port; + ts->dst_port = remote_port; + ts->sock.tcp.ctrl_rto_active = 1; + memset(&tmr, 0, sizeof(tmr)); + tmr.cb = test_timer_cb; + tmr.expires = 12345; + ctrl_rto_id = timers_binheap_insert(&s.timers, tmr); + ck_assert_int_ne(ctrl_rto_id, NO_TIMER); + ts->sock.tcp.tmr_rto = ctrl_rto_id; + + inject_tcp_segment(&s, TEST_PRIMARY_IF, remote_ip, local_ip, remote_port, local_port, + 10, 9, 0x10); + + ck_assert_int_eq(ts->proto, WI_IPPROTO_TCP); + ck_assert_uint_eq(ts->sock.tcp.state, TCP_LAST_ACK); + ck_assert_uint_eq(ts->sock.tcp.ctrl_rto_active, 1); + ck_assert_uint_eq(ts->sock.tcp.tmr_rto, ctrl_rto_id); + for (i = 0; i < s.timers.size; i++) { + if (s.timers.timers[i].id == ctrl_rto_id) { + found_active = 1; + ck_assert_uint_eq(s.timers.timers[i].expires, 12345); + break; + } + } + ck_assert_int_eq(found_active, 1); +} +END_TEST START_TEST(test_fifo_pop_success) { struct fifo f; uint8_t data[] = {1, 2, 3, 4}; @@ -14822,6 +14876,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_utils, test_tcp_rst_syn_rcvd_returns_to_listen); tcase_add_test(tc_utils, test_tcp_fin_wait_1_to_closing); tcase_add_test(tc_utils, test_tcp_last_ack_closes_socket); + tcase_add_test(tc_utils, test_tcp_last_ack_partial_ack_keeps_socket_and_timer); tcase_add_test(tc_utils, test_tcp_ack_acks_data_and_sets_writable); tcase_add_test(tc_utils, test_tcp_ack_duplicate_resend_clears_sent); tcase_add_test(tc_utils, test_tcp_ack_discards_zero_len_segment); diff --git a/src/wolfip.c b/src/wolfip.c index f462b6c..9b0bdbf 100644 --- a/src/wolfip.c +++ b/src/wolfip.c @@ -136,6 +136,12 @@ struct wolfIP_icmp_packet; #define TCP_SACK_MAX_BLOCKS 4 #define TCP_OOO_MAX_SEGS 4 +#define TCP_FLAG_FIN 0x01U +#define TCP_FLAG_SYN 0x02U +#define TCP_FLAG_RST 0x04U +#define TCP_FLAG_PSH 0x08U +#define TCP_FLAG_ACK 0x10U + /* Random number generator, provided by the user */ //extern uint32_t wolfIP_getrandom(void); @@ -2069,7 +2075,7 @@ static void tcp_send_empty(struct tsocket *t, uint8_t flags) static void tcp_send_ack(struct tsocket *t) { - return tcp_send_empty(t, 0x10); + return tcp_send_empty(t, TCP_FLAG_ACK); } static void tcp_send_finack(struct tsocket *t) @@ -2092,8 +2098,8 @@ static void tcp_send_syn(struct tsocket *t, uint8_t flags) uint8_t opt_len = 0; tcp = (struct wolfIP_tcp_seg *)buffer; memset(tcp, 0, sizeof(buffer)); - if (flags & 0x02) { - if ((flags & 0x10) != 0) { + if (flags & TCP_FLAG_SYN) { + if ((flags & TCP_FLAG_ACK) != 0) { /* SYN-ACK: include WS only when enabled on this socket. */ include_ws = t->sock.tcp.ws_enabled; include_sack = t->sock.tcp.sack_permitted; @@ -2317,7 +2323,7 @@ static int tcp_send_zero_wnd_probe(struct tsocket *t) probe->seq = ee32(probe_seq); probe->ack = ee32(t->sock.tcp.ack); probe->hlen = TCP_HEADER_LEN << 2; - probe->flags = 0x10; + probe->flags = TCP_FLAG_ACK; probe->win = ee16(tcp_adv_win(t)); probe->data[0] = probe_byte; @@ -3210,7 +3216,7 @@ static void tcp_input(struct wolfIP *S, unsigned int if_idx, } /* Check if pure ACK to SYN-ACK */ if ((tcplen == 0) && (t->sock.tcp.state == TCP_SYN_RCVD)) { - if (tcp->flags == 0x10) { + if (tcp->flags == TCP_FLAG_ACK) { t->sock.tcp.state = TCP_ESTABLISHED; tcp_ctrl_rto_stop(t); t->sock.tcp.ack = ee32(tcp->seq); @@ -3222,9 +3228,10 @@ static void tcp_input(struct wolfIP *S, unsigned int if_idx, t->events |= CB_EVENT_WRITABLE; } } else if (t->sock.tcp.state == TCP_LAST_ACK) { - tcp_send_ack(t); - tcp_ctrl_rto_stop(t); - close_socket(t); + if (tcp->flags & TCP_FLAG_ACK) { + tcp_ack(t, tcp); + tcp_process_ts(t, tcp, frame_len); + } } else if ((t->sock.tcp.state == TCP_ESTABLISHED) || (t->sock.tcp.state == TCP_FIN_WAIT_1) || @@ -3242,7 +3249,7 @@ static void tcp_input(struct wolfIP *S, unsigned int if_idx, t->events |= CB_EVENT_CLOSED | CB_EVENT_READABLE; tcp_send_ack(t); } - if (tcp->flags & 0x10) { + if (tcp->flags & TCP_FLAG_ACK) { tcp_ack(t, tcp); tcp_process_ts(t, tcp, frame_len); } @@ -3776,7 +3783,7 @@ int wolfIP_sock_sendto(struct wolfIP *s, int sockfd, const void *buf, size_t len tcp->seq = ee32(ts->sock.tcp.seq); tcp->ack = ee32(ts->sock.tcp.ack); tcp->hlen = (uint8_t)((TCP_HEADER_LEN + opt_len) << 2); - tcp->flags = 0x10 | ((sent == 0)? 0x08 : 0); /* ACK; PSH only on first */ + tcp->flags = TCP_FLAG_ACK | ((sent == 0) ? TCP_FLAG_PSH : 0); /* ACK; PSH only on first */ tcp->win = ee16(tcp_adv_win(ts)); tcp->csum = 0; tcp->urg = 0; From 45f1c8fb0d74c1d335e066f14f98ace2557486d0 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Fri, 20 Feb 2026 18:42:45 +0100 Subject: [PATCH 14/21] Add explicit cancellation of RTO timer on close_socket() --- src/test/unit/unit.c | 40 ++++++++++++++++++++++++++++++++++++++++ src/wolfip.c | 7 ++++++- 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/src/test/unit/unit.c b/src/test/unit/unit.c index 1bc5b0b..08e99c4 100644 --- a/src/test/unit/unit.c +++ b/src/test/unit/unit.c @@ -6228,6 +6228,45 @@ START_TEST(test_sock_close_tcp_other_state_closes) } END_TEST +START_TEST(test_sock_close_tcp_cancels_rto_timer) +{ + struct wolfIP s; + struct tsocket *ts; + struct wolfIP_timer tmr; + int sd; + uint32_t rto_id; + uint32_t i; + int found_canceled = 0; + + 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_SYN_SENT; + ts->sock.tcp.ctrl_rto_active = 1; + + memset(&tmr, 0, sizeof(tmr)); + tmr.cb = test_timer_cb; + tmr.expires = 1234; + rto_id = timers_binheap_insert(&s.timers, tmr); + ck_assert_int_ne(rto_id, NO_TIMER); + ts->sock.tcp.tmr_rto = rto_id; + + ck_assert_int_eq(wolfIP_sock_close(&s, sd), 0); + ck_assert_int_eq(ts->proto, 0); + for (i = 0; i < s.timers.size; i++) { + if (s.timers.timers[i].id == rto_id) { + found_canceled = 1; + ck_assert_uint_eq(s.timers.timers[i].expires, 0); + break; + } + } + ck_assert_int_eq(found_canceled, 1); +} +END_TEST + START_TEST(test_sock_close_tcp_closed_returns_minus_one) { struct wolfIP s; @@ -14856,6 +14895,7 @@ Suite *wolf_suite(void) 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_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); tcase_add_test(tc_utils, test_tcp_syn_sent_to_established); tcase_add_test(tc_utils, test_tcp_input_syn_sent_unexpected_flags); diff --git a/src/wolfip.c b/src/wolfip.c index 9b0bdbf..742b7a3 100644 --- a/src/wolfip.c +++ b/src/wolfip.c @@ -3477,8 +3477,13 @@ static void close_socket(struct tsocket *ts) { if (!ts) return; - if (ts->proto == WI_IPPROTO_TCP) + if (ts->proto == WI_IPPROTO_TCP) { tcp_persist_stop(ts); + if (ts->sock.tcp.tmr_rto != NO_TIMER) { + timer_binheap_cancel(&ts->S->timers, ts->sock.tcp.tmr_rto); + ts->sock.tcp.tmr_rto = NO_TIMER; + } + } memset(ts, 0, sizeof(struct tsocket)); } From d5b48b7507d9d28b6d5c36a4c4efe0de086e4467 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Fri, 20 Feb 2026 18:47:49 +0100 Subject: [PATCH 15/21] Fix: Confirmed suspect missing SYN-ACK retransmission Reported by reviewer on this branch in PR #33 TCP_SYN_RCVD was entered without arming control-RTO from tcp_input() Added RTO trigger + unit tests. --- src/test/unit/unit.c | 35 +++++++++++++++++++++++++++++++++++ src/wolfip.c | 2 ++ 2 files changed, 37 insertions(+) diff --git a/src/test/unit/unit.c b/src/test/unit/unit.c index 08e99c4..86e3ebf 100644 --- a/src/test/unit/unit.c +++ b/src/test/unit/unit.c @@ -9248,6 +9248,40 @@ START_TEST(test_tcp_input_listen_syn_without_sack_disables_sack) } END_TEST +START_TEST(test_tcp_input_listen_syn_arms_control_rto) +{ + struct wolfIP s; + int listen_sd; + struct tsocket *ts; + struct wolfIP_sockaddr_in sin; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + s.last_tick = 777; + + 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)]; + ck_assert_int_eq(ts->sock.tcp.tmr_rto, NO_TIMER); + ck_assert_uint_eq(ts->sock.tcp.ctrl_rto_active, 0); + + inject_tcp_segment(&s, TEST_PRIMARY_IF, 0x0A0000A1U, 0x0A000001U, 40000, 1234, 1, 0, 0x02); + + ck_assert_int_eq(ts->sock.tcp.state, TCP_SYN_RCVD); + ck_assert_int_ne(ts->sock.tcp.tmr_rto, NO_TIMER); + ck_assert_uint_eq(ts->sock.tcp.ctrl_rto_active, 1); + ck_assert_uint_eq(ts->sock.tcp.ctrl_rto_retries, 0); +} +END_TEST + START_TEST(test_tcp_input_syn_sent_synack_without_sack_disables_sack) { struct wolfIP s; @@ -14966,6 +15000,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_utils, test_tcp_consume_ooo_wrap_drop_fully_acked); tcase_add_test(tc_utils, test_tcp_ack_sack_early_retransmit_before_three_dupack); tcase_add_test(tc_utils, test_tcp_input_listen_syn_without_sack_disables_sack); + tcase_add_test(tc_utils, test_tcp_input_listen_syn_arms_control_rto); tcase_add_test(tc_utils, test_tcp_input_syn_sent_synack_without_sack_disables_sack); tcase_add_test(tc_utils, test_tcp_recv_partial_hole_fill_consumes_stored_ooo); tcase_add_test(tc_utils, test_tcp_ack_ignores_sack_when_not_negotiated); diff --git a/src/wolfip.c b/src/wolfip.c index 742b7a3..b7eb5bc 100644 --- a/src/wolfip.c +++ b/src/wolfip.c @@ -3196,6 +3196,8 @@ static void tcp_input(struct wolfIP *S, unsigned int if_idx, t->dst_port = ee16(tcp->src_port); t->remote_ip = ee32(tcp->ip.src); t->events |= CB_EVENT_READABLE; /* Keep flag until application calls accept */ + t->sock.tcp.ctrl_rto_retries = 0; + tcp_ctrl_rto_start(t, S->last_tick); tcp_process_ts(t, tcp, frame_len); break; } else if (t->sock.tcp.state == TCP_SYN_SENT) { From e45d7b77db5ac5ee10e01c53326b296bf7b520ca Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Fri, 20 Feb 2026 19:07:38 +0100 Subject: [PATCH 16/21] Added more unit tests. - Removed duplicated/unreachable branch in ICMP bind - Enforce 100% function coverage in github actions --- .github/workflows/wolfip-autocov.yml | 61 +++++++ Makefile | 10 +- src/test/unit/unit.c | 255 +++++++++++++++++++++++++++ src/wolfip.c | 32 ---- 4 files changed, 325 insertions(+), 33 deletions(-) create mode 100644 .github/workflows/wolfip-autocov.yml diff --git a/.github/workflows/wolfip-autocov.yml b/.github/workflows/wolfip-autocov.yml new file mode 100644 index 0000000..cf1cf46 --- /dev/null +++ b/.github/workflows/wolfip-autocov.yml @@ -0,0 +1,61 @@ +name: wolfIP Autocov + +on: + push: + branches: + - "**" + pull_request: + +jobs: + autocov: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y build-essential check gcovr + + - name: Run autocov + run: make clean autocov + + - name: Generate coverage JSON + run: | + gcovr -r . --exclude "src/test/unit/unit.c" --json -o build/coverage/coverage.json + + - name: Enforce 100% function coverage for src/wolfip.c + run: | + python3 - <<'PY' + import json + import sys + + with open("build/coverage/coverage.json", "r", encoding="utf-8") as f: + data = json.load(f) + + target = None + for file_entry in data.get("files", []): + if file_entry.get("file", "").endswith("src/wolfip.c"): + target = file_entry + break + + if target is None: + print("ERROR: src/wolfip.c not found in coverage JSON") + sys.exit(1) + + functions = target.get("functions", []) + if not functions: + print("ERROR: No function coverage data for src/wolfip.c") + sys.exit(1) + + total = len(functions) + covered = sum(1 for fn in functions if fn.get("execution_count", 0) > 0) + pct = (covered * 100.0) / total + print(f"src/wolfip.c function coverage: {covered}/{total} ({pct:.2f}%)") + + if covered != total: + print("ERROR: src/wolfip.c function coverage must be 100%") + sys.exit(1) + PY diff --git a/Makefile b/Makefile index 1baaafb..d6ab7b3 100644 --- a/Makefile +++ b/Makefile @@ -355,13 +355,21 @@ cov: unit $(COV_UNIT) @gcovr -r . --exclude "src/test/unit/unit.c" --html-details -o build/coverage/index.html @$(OPEN_CMD) build/coverage/index.html +autocov: unit $(COV_UNIT) + @echo "[RUN] unit (coverage)" + @rm -f $(COV_DIR)/*.gcda + @$(COV_UNIT) + @echo "[COV] gcovr html" + @mkdir -p build/coverage + @gcovr -r . --exclude "src/test/unit/unit.c" --html-details -o build/coverage/index.html + # Install dynamic library to re-link linux applications # install: install libwolfip.so $(PREFIX)/lib ldconfig -.PHONY: clean all static cppcheck cov +.PHONY: clean all static cppcheck cov autocov cppcheck: $(CPPCHECK) $(CPPCHECK_FLAGS) src/ 2>cppcheck_results.xml diff --git a/src/test/unit/unit.c b/src/test/unit/unit.c index 86e3ebf..b74efe8 100644 --- a/src/test/unit/unit.c +++ b/src/test/unit/unit.c @@ -3861,6 +3861,55 @@ START_TEST(test_sock_setsockopt_recvttl_invalid_params) } END_TEST +START_TEST(test_sock_can_read_write_paths) +{ + struct wolfIP s; + struct tsocket *ts; + int tcp_sd; + int udp_sd; + int icmp_sd; + uint8_t payload[4] = {1, 2, 3, 4}; + + wolfIP_init(&s); + mock_link_init(&s); + + ck_assert_int_eq(wolfIP_sock_can_read(&s, -1), -WOLFIP_EINVAL); + ck_assert_int_eq(wolfIP_sock_can_write(&s, -1), -WOLFIP_EINVAL); + + 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_SYN_SENT; + ck_assert_int_eq(wolfIP_sock_can_write(&s, tcp_sd), 0); + + ts->sock.tcp.state = TCP_ESTABLISHED; + ck_assert_int_eq(wolfIP_sock_can_read(&s, tcp_sd), 0); + ck_assert_int_eq(wolfIP_sock_can_write(&s, tcp_sd), 1); + ck_assert_int_eq(queue_insert(&ts->sock.tcp.rxbuf, payload, 0, sizeof(payload)), 0); + ck_assert_int_eq(wolfIP_sock_can_read(&s, tcp_sd), 1); + + while (enqueue_tcp_tx(ts, 16, TCP_FLAG_ACK | TCP_FLAG_PSH) == 0) { + } + ck_assert_int_eq(wolfIP_sock_can_write(&s, tcp_sd), 0); + + ts->sock.tcp.state = TCP_CLOSE_WAIT; + ck_assert_int_eq(wolfIP_sock_can_read(&s, tcp_sd), 1); + + udp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + ck_assert_int_gt(udp_sd, 0); + ts = &s.udpsockets[SOCKET_UNMARK(udp_sd)]; + ck_assert_int_eq(wolfIP_sock_can_read(&s, udp_sd), 0); + enqueue_udp_rx(ts, payload, sizeof(payload), 4000); + ck_assert_int_eq(wolfIP_sock_can_read(&s, udp_sd), 1); + ck_assert_int_eq(wolfIP_sock_can_write(&s, udp_sd), 1); + + icmp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_ICMP); + ck_assert_int_gt(icmp_sd, 0); + ck_assert_int_eq(wolfIP_sock_can_write(&s, icmp_sd), 1); +} +END_TEST + START_TEST(test_sock_getsockopt_recvttl_invalid_params) { struct wolfIP s; @@ -3880,6 +3929,53 @@ START_TEST(test_sock_getsockopt_recvttl_invalid_params) ck_assert_int_eq(wolfIP_sock_getsockopt(&s, udp_sd, WOLFIP_SOL_IP, WOLFIP_IP_RECVTTL, &value, &len), -WOLFIP_EINVAL); } END_TEST + +START_TEST(test_dns_wrapper_apis) +{ + struct wolfIP s; + uint16_t id = 0; + int dns_sd; + + wolfIP_init(&s); + mock_link_init(&s); + + ck_assert_int_eq(nslookup(NULL, "example.com", &id, test_dns_lookup_cb), -22); + ck_assert_int_eq(nslookup(&s, NULL, &id, test_dns_lookup_cb), -22); + ck_assert_int_eq(nslookup(&s, "example.com", NULL, test_dns_lookup_cb), -22); + ck_assert_int_eq(nslookup(&s, "example.com", &id, NULL), -22); + + ck_assert_int_eq(wolfIP_dns_ptr_lookup(NULL, 0x01020304U, &id, test_dns_ptr_cb), -22); + ck_assert_int_eq(wolfIP_dns_ptr_lookup(&s, 0x01020304U, NULL, test_dns_ptr_cb), -22); + ck_assert_int_eq(wolfIP_dns_ptr_lookup(&s, 0x01020304U, &id, NULL), -22); + + s.dns_server = 0x08080808U; + dns_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + ck_assert_int_gt(dns_sd, 0); + s.dns_udp_sd = dns_sd; + + ck_assert_int_eq(nslookup(&s, "example.com", &id, test_dns_lookup_cb), 0); + ck_assert_ptr_eq(s.dns_lookup_cb, test_dns_lookup_cb); + ck_assert_ptr_eq(s.dns_ptr_cb, NULL); + ck_assert_uint_eq(s.dns_query_type, DNS_QUERY_TYPE_A); + + s.dns_id = 0; + ck_assert_int_eq(wolfIP_dns_ptr_lookup(&s, 0x01020304U, &id, test_dns_ptr_cb), 0); + ck_assert_ptr_eq(s.dns_ptr_cb, test_dns_ptr_cb); + ck_assert_ptr_eq(s.dns_lookup_cb, NULL); + ck_assert_uint_eq(s.dns_query_type, DNS_QUERY_TYPE_PTR); +} +END_TEST + +START_TEST(test_wolfip_static_instance_apis) +{ + struct wolfIP *s = NULL; + + wolfIP_init_static(NULL); + wolfIP_init_static(&s); + ck_assert_ptr_nonnull(s); + ck_assert_uint_gt(wolfIP_instance_size(), 0U); +} +END_TEST START_TEST(test_dhcp_parse_offer_and_ack) { struct wolfIP s; @@ -7030,6 +7126,22 @@ START_TEST(test_tcp_fin_wait_1_to_closing) } END_TEST +START_TEST(test_dhcp_callback_null_and_off_state) +{ + struct wolfIP s; + + wolfIP_init(&s); + mock_link_init(&s); + + dhcp_callback(0, 0, NULL); + + s.dhcp_state = DHCP_OFF; + s.dhcp_udp_sd = 0; + dhcp_callback(0, 0, &s); + ck_assert_int_eq(s.dhcp_state, DHCP_OFF); +} +END_TEST + #if WOLFIP_ENABLE_FORWARDING START_TEST(test_forward_prepare_paths) { @@ -8736,6 +8848,75 @@ START_TEST(test_fifo_push_full) { } END_TEST +START_TEST(test_fifo_can_push_len_null_and_oversized) +{ + struct fifo f; + uint8_t data[64]; + + ck_assert_int_eq(fifo_can_push_len(NULL, 1), 0); + fifo_init(&f, data, sizeof(data)); + ck_assert_int_eq(fifo_can_push_len(&f, (uint32_t)(sizeof(data) - sizeof(struct pkt_desc) + 1)), 0); +} +END_TEST + +START_TEST(test_fifo_can_push_len_head_tail_equal_with_wrap) +{ + struct fifo f; + uint8_t data[64]; + + fifo_init(&f, data, sizeof(data)); + f.head = 8; + f.tail = 8; + f.h_wrap = 32; + + ck_assert_int_eq(fifo_can_push_len(&f, 1), 0); +} +END_TEST + +START_TEST(test_fifo_can_push_len_wrap_tail_too_small_rejects) +{ + struct fifo f; + uint8_t data[64]; + + fifo_init(&f, data, sizeof(data)); + f.head = 60; + f.tail = 8; + f.h_wrap = 0; + + ck_assert_int_eq(fifo_can_push_len(&f, 1), 0); +} +END_TEST + +START_TEST(test_fifo_can_push_len_wrap_to_zero_then_accepts) +{ + struct fifo f; + uint8_t data[64]; + + fifo_init(&f, data, sizeof(data)); + f.head = 60; + f.tail = 40; + f.h_wrap = 0; + + ck_assert_int_eq(fifo_can_push_len(&f, 1), 1); +} +END_TEST + +START_TEST(test_fifo_can_push_len_head_at_wrap_boundary) +{ + struct fifo f; + uint8_t data[64]; + + fifo_init(&f, data, sizeof(data)); + f.head = 32; + f.tail = 48; + f.h_wrap = 32; + ck_assert_int_eq(fifo_can_push_len(&f, 0), 1); + + f.tail = 12; + ck_assert_int_eq(fifo_can_push_len(&f, 1), 0); +} +END_TEST + START_TEST(test_tcp_process_ts_uses_ecr) { struct wolfIP s; @@ -13070,6 +13251,69 @@ START_TEST(test_tcp_input_window_reopen_stops_persist) } END_TEST +START_TEST(test_tcp_persist_cb_stops_when_state_invalid) +{ + struct wolfIP s; + struct tsocket *ts; + struct wolfIP_timer tmr; + + 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_SYN_SENT; + ts->sock.tcp.persist_active = 1; + ts->sock.tcp.persist_backoff = 4; + + memset(&tmr, 0, sizeof(tmr)); + tmr.cb = test_timer_cb; + tmr.expires = 100; + ts->sock.tcp.tmr_persist = timers_binheap_insert(&s.timers, tmr); + ck_assert_int_ne(ts->sock.tcp.tmr_persist, NO_TIMER); + + tcp_persist_cb(ts); + + ck_assert_uint_eq(ts->sock.tcp.persist_active, 0); + ck_assert_uint_eq(ts->sock.tcp.persist_backoff, 0); + ck_assert_int_eq(ts->sock.tcp.tmr_persist, NO_TIMER); +} +END_TEST + +START_TEST(test_tcp_persist_cb_stops_when_window_reopens) +{ + struct wolfIP s; + struct tsocket *ts; + struct wolfIP_timer tmr; + + 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.peer_rwnd = 64; + ts->sock.tcp.persist_active = 1; + ts->sock.tcp.persist_backoff = 2; + + memset(&tmr, 0, sizeof(tmr)); + tmr.cb = test_timer_cb; + tmr.expires = 100; + ts->sock.tcp.tmr_persist = timers_binheap_insert(&s.timers, tmr); + ck_assert_int_ne(ts->sock.tcp.tmr_persist, NO_TIMER); + + tcp_persist_cb(ts); + + ck_assert_uint_eq(ts->sock.tcp.persist_active, 0); + ck_assert_uint_eq(ts->sock.tcp.persist_backoff, 0); + ck_assert_int_eq(ts->sock.tcp.tmr_persist, NO_TIMER); +} +END_TEST + START_TEST(test_queue_insert_len_gt_size) { struct queue q; @@ -14666,6 +14910,11 @@ Suite *wolf_suite(void) tcase_add_test(tc_core, test_fifo_pop_success); tcase_add_test(tc_core, test_fifo_pop_empty); tcase_add_test(tc_core, test_fifo_push_full); + tcase_add_test(tc_core, test_fifo_can_push_len_null_and_oversized); + tcase_add_test(tc_core, test_fifo_can_push_len_head_tail_equal_with_wrap); + tcase_add_test(tc_core, test_fifo_can_push_len_wrap_tail_too_small_rejects); + tcase_add_test(tc_core, test_fifo_can_push_len_wrap_to_zero_then_accepts); + tcase_add_test(tc_core, test_fifo_can_push_len_head_at_wrap_boundary); tcase_add_test(tc_core, test_fifo_push_wrap); tcase_add_test(tc_core, test_fifo_push_wrap_multiple); tcase_add_test(tc_core, test_fifo_space_wrap_sets_hwrap); @@ -14816,6 +15065,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_utils, test_sock_setsockopt_recvttl_invalid_params); tcase_add_test(tc_utils, test_sock_getsockopt_recvttl_value); tcase_add_test(tc_utils, test_sock_getsockopt_invalid_socket); + tcase_add_test(tc_utils, test_sock_can_read_write_paths); tcase_add_test(tc_utils, test_sock_getsockopt_recvttl_invalid_params); tcase_add_test(tc_utils, test_sock_get_recv_ttl_invalid_socket); tcase_add_test(tc_utils, test_sock_accept_wrong_state); @@ -14877,6 +15127,8 @@ Suite *wolf_suite(void) tcase_add_test(tc_utils, test_tcp_persist_cb_sends_one_byte_probe); tcase_add_test(tc_utils, test_tcp_persist_probe_byte_matches_snd_una_offset); tcase_add_test(tc_utils, test_tcp_input_window_reopen_stops_persist); + tcase_add_test(tc_utils, test_tcp_persist_cb_stops_when_state_invalid); + tcase_add_test(tc_utils, test_tcp_persist_cb_stops_when_window_reopens); tcase_add_test(tc_utils, test_poll_tcp_arp_request_on_miss); tcase_add_test(tc_utils, test_poll_udp_send_on_arp_hit); tcase_add_test(tc_utils, test_poll_icmp_send_on_arp_hit); @@ -14897,6 +15149,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_utils, test_dhcp_parse_offer_no_match); tcase_add_test(tc_utils, test_dhcp_parse_ack_invalid); tcase_add_test(tc_utils, test_dhcp_poll_no_data_and_wrong_state); + tcase_add_test(tc_utils, test_dhcp_callback_null_and_off_state); #if WOLFIP_ENABLE_FORWARDING tcase_add_test(tc_utils, test_forward_prepare_paths); tcase_add_test(tc_utils, test_forward_prepare_loopback_no_ll); @@ -14910,6 +15163,8 @@ Suite *wolf_suite(void) tcase_add_test(tc_utils, test_dns_skip_and_copy_name); tcase_add_test(tc_utils, test_sock_opts_and_names); tcase_add_test(tc_utils, test_dns_send_query_errors); + tcase_add_test(tc_utils, test_dns_wrapper_apis); + tcase_add_test(tc_utils, test_wolfip_static_instance_apis); 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); diff --git a/src/wolfip.c b/src/wolfip.c index b7eb5bc..6a9642a 100644 --- a/src/wolfip.c +++ b/src/wolfip.c @@ -4363,38 +4363,6 @@ int wolfIP_sock_bind(struct wolfIP *s, int sockfd, const struct wolfIP_sockaddr } } return 0; - } else if (IS_SOCKET_ICMP(sockfd)) { - if (SOCKET_UNMARK(sockfd) >= MAX_ICMPSOCKETS) - return -WOLFIP_EINVAL; - ts = &s->icmpsockets[SOCKET_UNMARK(sockfd)]; - if (ts->src_port != 0) - return -1; - if ((sin->sin_family != AF_INET) || (addrlen < sizeof(struct wolfIP_sockaddr_in))) - return -1; - { - ip4 prev_ip = ts->local_ip; - uint16_t prev_id = ts->src_port; - uint16_t new_id = ee16(sin->sin_port); - ts->if_idx = (uint8_t)if_idx; - if (bind_ip != IPADDR_ANY) - ts->local_ip = bind_ip; - else if (conf && conf->ip != IPADDR_ANY) - ts->local_ip = conf->ip; - else { - struct ipconf *primary = wolfIP_primary_ipconf(s); - if (primary && primary->ip != IPADDR_ANY) - ts->local_ip = primary->ip; - } - ts->src_port = new_id; - if (wolfIP_filter_notify_socket_event( - WOLFIP_FILT_BINDING, s, ts, - ts->local_ip, new_id, IPADDR_ANY, 0) != 0) { - ts->local_ip = prev_ip; - ts->src_port = prev_id; - return -1; - } - } - return 0; } else return -1; } From fd639d5a5444940adad889685cf24b3cad11dc4f Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Fri, 20 Feb 2026 19:13:15 +0100 Subject: [PATCH 17/21] Fix potential data loss in FIN_WAIT_1 (copilot's review) --- src/test/unit/unit.c | 95 ++++++++++++++++++++++++++++++++++++++++++++ src/wolfip.c | 22 +++++++--- 2 files changed, 111 insertions(+), 6 deletions(-) diff --git a/src/test/unit/unit.c b/src/test/unit/unit.c index b74efe8..ca579b7 100644 --- a/src/test/unit/unit.c +++ b/src/test/unit/unit.c @@ -5896,6 +5896,98 @@ START_TEST(test_tcp_rto_cb_last_ack_requeues_finack_and_arms_timer) } END_TEST +START_TEST(test_tcp_ctrl_state_needs_rto_fin_wait_1_waits_for_payload_drain) +{ + 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; + fifo_init(&ts->sock.tcp.txbuf, ts->txmem, TXBUF_SIZE); + + ts->sock.tcp.bytes_in_flight = 1; + ck_assert_int_eq(tcp_ctrl_state_needs_rto(ts), 0); + + ts->sock.tcp.bytes_in_flight = 0; + ck_assert_int_eq(enqueue_tcp_tx(ts, 1, 0x18), 0); + ck_assert_int_eq(tcp_ctrl_state_needs_rto(ts), 0); + + fifo_init(&ts->sock.tcp.txbuf, ts->txmem, TXBUF_SIZE); + ck_assert_int_eq(tcp_ctrl_state_needs_rto(ts), 1); +} +END_TEST + +START_TEST(test_tcp_rto_cb_fin_wait_1_with_data_uses_data_recovery) +{ + struct wolfIP s; + struct tsocket *ts; + struct pkt_desc *desc; + + 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.rto_backoff = 0; + ts->sock.tcp.snd_una = 101; + ts->sock.tcp.seq = 101; + ts->sock.tcp.bytes_in_flight = 1; + fifo_init(&ts->sock.tcp.txbuf, ts->txmem, TXBUF_SIZE); + + ck_assert_int_eq(enqueue_tcp_tx(ts, 1, 0x18), 0); + desc = fifo_peek(&ts->sock.tcp.txbuf); + ck_assert_ptr_nonnull(desc); + desc->flags |= PKT_FLAG_SENT; + + s.last_tick = 1000; + tcp_rto_cb(ts); + + ck_assert_int_eq(desc->flags & PKT_FLAG_SENT, 0); + ck_assert_int_ne(desc->flags & PKT_FLAG_RETRANS, 0); + ck_assert_uint_eq(ts->sock.tcp.ctrl_rto_retries, 0); + ck_assert_int_ne(ts->sock.tcp.tmr_rto, NO_TIMER); +} +END_TEST + +START_TEST(test_tcp_rto_cb_fin_wait_1_no_data_requeues_finack) +{ + struct wolfIP s; + struct tsocket *ts; + struct pkt_desc *desc; + struct wolfIP_tcp_seg *seg; + + 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->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); + + s.last_tick = 1000; + tcp_rto_cb(ts); + + desc = fifo_peek(&ts->sock.tcp.txbuf); + ck_assert_ptr_nonnull(desc); + seg = (struct wolfIP_tcp_seg *)(ts->txmem + desc->pos + sizeof(*desc)); + ck_assert_uint_eq(seg->flags, 0x11); + ck_assert_uint_eq(ts->sock.tcp.ctrl_rto_retries, 1); + ck_assert_int_ne(ts->sock.tcp.tmr_rto, NO_TIMER); +} +END_TEST + START_TEST(test_tcp_ack_fin_wait_1_ack_of_fin_moves_to_fin_wait_2_and_stops_timer) { struct wolfIP s; @@ -15173,6 +15265,9 @@ 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_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_ack_fin_wait_1_ack_of_fin_moves_to_fin_wait_2_and_stops_timer); tcase_add_test(tc_utils, test_tcp_rto_cb_control_retry_cap_closes_socket); tcase_add_test(tc_utils, test_tcp_rto_cb_cancels_existing_timer); diff --git a/src/wolfip.c b/src/wolfip.c index 6a9642a..3e7e312 100644 --- a/src/wolfip.c +++ b/src/wolfip.c @@ -1064,6 +1064,7 @@ static void tcp_rto_cb(void *arg); static void tcp_ctrl_rto_start(struct tsocket *t, uint64_t now); static void tcp_ctrl_rto_stop(struct tsocket *t); static int tcp_ctrl_state_needs_rto(const struct tsocket *t); +static int tcp_has_pending_unsent_payload(const struct tsocket *t); #ifdef ETHERNET struct PACKED arp_packet { @@ -2164,10 +2165,18 @@ static int tcp_ctrl_state_needs_rto(const struct tsocket *t) { if (!t || t->proto != WI_IPPROTO_TCP) return 0; - return (t->sock.tcp.state == TCP_SYN_SENT) || - (t->sock.tcp.state == TCP_SYN_RCVD) || - (t->sock.tcp.state == TCP_FIN_WAIT_1) || - (t->sock.tcp.state == TCP_LAST_ACK); + if ((t->sock.tcp.state == TCP_SYN_SENT) || + (t->sock.tcp.state == TCP_SYN_RCVD) || + (t->sock.tcp.state == TCP_LAST_ACK)) + return 1; + /* In FIN_WAIT_1 keep data-RTO active while payload is still outstanding. + * Switch to control-RTO only after data is fully drained and only FIN/ACK + * teardown control traffic remains. */ + if ((t->sock.tcp.state == TCP_FIN_WAIT_1) && + (t->sock.tcp.bytes_in_flight == 0) && + !tcp_has_pending_unsent_payload(t)) + return 1; + return 0; } /* Stop control-RTO retransmission tracking for this socket and reset counters. */ @@ -2203,7 +2212,7 @@ static void tcp_ctrl_rto_start(struct tsocket *t, uint64_t now) t->sock.tcp.ctrl_rto_active = 1; } -static int tcp_has_pending_unsent_payload(struct tsocket *t) +static int tcp_has_pending_unsent_payload(const struct tsocket *t) { struct pkt_desc *desc; uint32_t guard = 0; @@ -3304,7 +3313,8 @@ static void tcp_rto_cb(void *arg) tcp_ctrl_rto_start(ts, ts->S->last_tick); return; } - if (ts->sock.tcp.state != TCP_ESTABLISHED) + if (ts->sock.tcp.state != TCP_ESTABLISHED && + ts->sock.tcp.state != TCP_FIN_WAIT_1) return; /* RFC 6675 / RFC 2018 guidance: after an RTO, SACK scoreboard must not be * trusted (receiver may renege). Fall back to cumulative-ACK driven From a4951d88822c96642ce08418e2cb7366cbc8ef5f Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Fri, 20 Feb 2026 19:16:40 +0100 Subject: [PATCH 18/21] Added wolfssl dependency to the autocov test --- .github/workflows/wolfip-autocov.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/wolfip-autocov.yml b/.github/workflows/wolfip-autocov.yml index cf1cf46..6cd0ccf 100644 --- a/.github/workflows/wolfip-autocov.yml +++ b/.github/workflows/wolfip-autocov.yml @@ -13,11 +13,13 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 + with: + submodules: true - name: Install dependencies run: | sudo apt-get update - sudo apt-get install -y build-essential check gcovr + sudo apt-get install -y build-essential check gcovr libwolfssl-dev - name: Run autocov run: make clean autocov From 19f1ec8ab48796595d62b5c4fd1db4182ad45749 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Fri, 20 Feb 2026 19:27:00 +0100 Subject: [PATCH 19/21] Replace hardcoded flags numbers with TCP_FLAG_ defines --- src/wolfip.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/wolfip.c b/src/wolfip.c index 3e7e312..667d980 100644 --- a/src/wolfip.c +++ b/src/wolfip.c @@ -2081,7 +2081,7 @@ static void tcp_send_ack(struct tsocket *t) static void tcp_send_finack(struct tsocket *t) { - tcp_send_empty(t, 0x11); + tcp_send_empty(t, TCP_FLAG_FIN | TCP_FLAG_ACK); t->sock.tcp.last = t->sock.tcp.seq; } @@ -3077,7 +3077,7 @@ 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 (tcp->flags & 0x02) { + if (tcp->flags & TCP_FLAG_SYN) { struct tcp_parsed_opts po; tcp_parse_options(tcp, frame_len, &po); /* Window scale is negotiated only during SYN/SYN-ACK. */ @@ -3159,7 +3159,7 @@ static void tcp_input(struct wolfIP *S, unsigned int if_idx, } } /* Check if SYN */ - if (tcp->flags & 0x02) { + if (tcp->flags & TCP_FLAG_SYN) { if (t->sock.tcp.state == TCP_LISTEN) { ip4 syn_dst = ee32(tcp->ip.dst); int dst_match = 0; @@ -3210,7 +3210,7 @@ static void tcp_input(struct wolfIP *S, unsigned int if_idx, tcp_process_ts(t, tcp, frame_len); break; } else if (t->sock.tcp.state == TCP_SYN_SENT) { - if (tcp->flags == 0x12) { + if (tcp->flags == (TCP_FLAG_SYN | TCP_FLAG_ACK)) { t->sock.tcp.state = TCP_ESTABLISHED; tcp_ctrl_rto_stop(t); t->sock.tcp.ack = tcp_seq_inc(ee32(tcp->seq), 1); @@ -3304,9 +3304,9 @@ static void tcp_rto_cb(void *arg) } ts->sock.tcp.ctrl_rto_retries++; if (ts->sock.tcp.state == TCP_SYN_SENT) { - tcp_send_syn(ts, 0x02); + tcp_send_syn(ts, TCP_FLAG_SYN); } else if (ts->sock.tcp.state == TCP_SYN_RCVD) { - tcp_send_syn(ts, 0x12); + 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); } @@ -3656,7 +3656,7 @@ 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, 0x02); + tcp_send_syn(ts, TCP_FLAG_SYN); tcp_ctrl_rto_start(ts, s->last_tick); return -WOLFIP_EAGAIN; } @@ -3721,7 +3721,7 @@ 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, 0x12); + tcp_send_syn(newts, TCP_FLAG_SYN | TCP_FLAG_ACK); newts->sock.tcp.seq++; if (sin) { sin->sin_family = AF_INET; From ae5ea030c8f97f0446dd3168cfff6c2ea6c37699 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Fri, 20 Feb 2026 19:31:18 +0100 Subject: [PATCH 20/21] Fixed compiler warnings --- src/wolfip.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/wolfip.c b/src/wolfip.c index 667d980..ce03ce0 100644 --- a/src/wolfip.c +++ b/src/wolfip.c @@ -1064,7 +1064,7 @@ static void tcp_rto_cb(void *arg); static void tcp_ctrl_rto_start(struct tsocket *t, uint64_t now); static void tcp_ctrl_rto_stop(struct tsocket *t); static int tcp_ctrl_state_needs_rto(const struct tsocket *t); -static int tcp_has_pending_unsent_payload(const struct tsocket *t); +static int tcp_has_pending_unsent_payload(struct tsocket *t); #ifdef ETHERNET struct PACKED arp_packet { @@ -2174,7 +2174,7 @@ static int tcp_ctrl_state_needs_rto(const struct tsocket *t) * teardown control traffic remains. */ if ((t->sock.tcp.state == TCP_FIN_WAIT_1) && (t->sock.tcp.bytes_in_flight == 0) && - !tcp_has_pending_unsent_payload(t)) + !tcp_has_pending_unsent_payload((struct tsocket *)t)) return 1; return 0; } @@ -2212,7 +2212,7 @@ static void tcp_ctrl_rto_start(struct tsocket *t, uint64_t now) t->sock.tcp.ctrl_rto_active = 1; } -static int tcp_has_pending_unsent_payload(const struct tsocket *t) +static int tcp_has_pending_unsent_payload(struct tsocket *t) { struct pkt_desc *desc; uint32_t guard = 0; From 82e52fc43c3f605f0ad86c2ce6743994f8d33b71 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Fri, 20 Feb 2026 21:46:31 +0100 Subject: [PATCH 21/21] Decreased test_esp.c TEST_SIZE to 8KB --- src/test/esp/test_esp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/esp/test_esp.c b/src/test/esp/test_esp.c index f72e8cd..3f767fc 100644 --- a/src/test/esp/test_esp.c +++ b/src/test/esp/test_esp.c @@ -39,7 +39,7 @@ static void __attribute__((noreturn)) print_usage_and_die(void); -#define TEST_SIZE (12 * 1024) +#define TEST_SIZE (8 * 1024) #define BUFFER_SIZE TEST_SIZE static int disable_ipsec = 0;