diff --git a/.github/workflows/wolfip-autocov.yml b/.github/workflows/wolfip-autocov.yml new file mode 100644 index 0000000..6cd0ccf --- /dev/null +++ b/.github/workflows/wolfip-autocov.yml @@ -0,0 +1,63 @@ +name: wolfIP Autocov + +on: + push: + branches: + - "**" + pull_request: + +jobs: + autocov: + runs-on: ubuntu-latest + + 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 libwolfssl-dev + + - 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/README.md b/README.md index 2e1a95d..2cc775a 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), [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/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; diff --git a/src/test/unit/unit.c b/src/test/unit/unit.c index 11aa14d..ca579b7 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]; @@ -3836,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; @@ -3855,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; @@ -5652,7 +5773,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,106 +5781,147 @@ START_TEST(test_tcp_rto_cb_non_established_noop) } END_TEST -START_TEST(test_tcp_rto_cb_cancels_existing_timer) +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_ESTABLISHED; - ts->sock.tcp.tmr_rto = 1; + 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); - ck_assert_int_eq(ts->sock.tcp.tmr_rto, NO_TIMER); + + 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_rto_cb_clears_sack_and_marks_lowest_only) +START_TEST(test_tcp_input_synack_cancels_control_rto) { struct wolfIP s; struct tsocket *ts; - struct pkt_desc *desc1; - struct pkt_desc *desc2; + 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_ESTABLISHED; - ts->sock.tcp.rto = 100; - ts->sock.tcp.cwnd = TCP_MSS * 8; - ts->sock.tcp.snd_una = 100; - ts->sock.tcp.seq = 100; - ts->sock.tcp.bytes_in_flight = 2; - ts->sock.tcp.peer_sack_count = 1; - ts->sock.tcp.peer_sack[0].left = 101; - ts->sock.tcp.peer_sack[0].right = 102; - fifo_init(&ts->sock.tcp.txbuf, ts->txmem, TXBUF_SIZE); - - ck_assert_int_eq(enqueue_tcp_tx(ts, 1, 0x18), 0); + ts->sock.tcp.state = TCP_SYN_SENT; ts->sock.tcp.seq = 101; - ck_assert_int_eq(enqueue_tcp_tx(ts, 1, 0x18), 0); - desc1 = fifo_peek(&ts->sock.tcp.txbuf); - ck_assert_ptr_nonnull(desc1); - desc2 = fifo_next(&ts->sock.tcp.txbuf, desc1); - ck_assert_ptr_nonnull(desc2); - ck_assert_ptr_ne(desc2, desc1); + 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; - desc1->flags |= PKT_FLAG_SENT; - desc2->flags |= PKT_FLAG_SENT; + 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); - s.last_tick = 1000; - tcp_rto_cb(ts); + 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); - ck_assert_uint_eq(ts->sock.tcp.peer_sack_count, 0); - ck_assert_int_eq(desc1->flags & PKT_FLAG_SENT, 0); - ck_assert_int_ne(desc1->flags & PKT_FLAG_RETRANS, 0); - 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); + 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_ssthresh_floor_two_mss) +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_ESTABLISHED; + ts->sock.tcp.state = TCP_LAST_ACK; ts->sock.tcp.rto = 100; - ts->sock.tcp.cwnd = TCP_MSS; - ts->sock.tcp.snd_una = 101; - ts->sock.tcp.seq = 101; - ts->sock.tcp.bytes_in_flight = 1; + 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); - ck_assert_int_eq(enqueue_tcp_tx(ts, 1, 0x18), 0); + s.last_tick = 1000; + tcp_rto_cb(ts); + desc = fifo_peek(&ts->sock.tcp.txbuf); ck_assert_ptr_nonnull(desc); - desc->flags |= PKT_FLAG_SENT; + 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 - s.last_tick = 1000; - tcp_rto_cb(ts); +START_TEST(test_tcp_ctrl_state_needs_rto_fin_wait_1_waits_for_payload_drain) +{ + struct wolfIP s; + struct tsocket *ts; - ck_assert_uint_eq(ts->sock.tcp.cwnd, TCP_MSS); - ck_assert_uint_eq(ts->sock.tcp.ssthresh, TCP_MSS * 2); + 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_fallback_marks_lowest_sent_when_no_snd_una_cover) +START_TEST(test_tcp_rto_cb_fin_wait_1_with_data_uses_data_recovery) { struct wolfIP s; struct tsocket *ts; @@ -5770,11 +5932,11 @@ START_TEST(test_tcp_rto_cb_fallback_marks_lowest_sent_when_no_snd_una_cover) memset(ts, 0, sizeof(*ts)); ts->proto = WI_IPPROTO_TCP; ts->S = &s; - ts->sock.tcp.state = TCP_ESTABLISHED; + ts->sock.tcp.state = TCP_FIN_WAIT_1; ts->sock.tcp.rto = 100; - ts->sock.tcp.cwnd = TCP_MSS * 4; - ts->sock.tcp.snd_una = 50; - ts->sock.tcp.seq = 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); @@ -5788,65 +5950,325 @@ START_TEST(test_tcp_rto_cb_fallback_marks_lowest_sent_when_no_snd_una_cover) 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.snd_una, 50U); - ck_assert_uint_eq(ts->sock.tcp.cwnd, TCP_MSS); + 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_poll_udp_send_on_arp_hit) +START_TEST(test_tcp_rto_cb_fin_wait_1_no_data_requeues_finack) { struct wolfIP s; - int udp_sd; - struct wolfIP_sockaddr_in sin; - uint8_t payload[4] = {1, 2, 3, 4}; - uint8_t peer_mac[6] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05}; - int ret; + struct tsocket *ts; + struct pkt_desc *desc; + struct wolfIP_tcp_seg *seg; wolfIP_init(&s); - mock_link_init(&s); - wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); - wolfIP_filter_set_callback(NULL, NULL); - last_frame_sent_size = 0; - - s.arp.neighbors[0].ip = 0x0A000002U; - s.arp.neighbors[0].if_idx = TEST_PRIMARY_IF; - memcpy(s.arp.neighbors[0].mac, 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_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); - udp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); - ck_assert_int_gt(udp_sd, 0); - memset(&sin, 0, sizeof(sin)); - sin.sin_family = AF_INET; - sin.sin_port = ee16(1234); - sin.sin_addr.s_addr = ee32(0x0A000002U); - ret = wolfIP_sock_sendto(&s, udp_sd, payload, sizeof(payload), 0, - (struct wolfIP_sockaddr *)&sin, sizeof(sin)); - ck_assert_int_eq(ret, (int)sizeof(payload)); + s.last_tick = 1000; + tcp_rto_cb(ts); - (void)wolfIP_poll(&s, 100); - ck_assert_uint_gt(last_frame_sent_size, 0); - ck_assert_uint_eq(last_frame_sent[12], 0x08); - ck_assert_uint_eq(last_frame_sent[13], 0x00); + 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_poll_icmp_send_on_arp_hit) +START_TEST(test_tcp_ack_fin_wait_1_ack_of_fin_moves_to_fin_wait_2_and_stops_timer) { struct wolfIP s; - int icmp_sd; - struct wolfIP_sockaddr_in sin; - uint8_t payload[ICMP_HEADER_LEN + 1]; - uint8_t peer_mac[6] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x06}; - int ret; + struct tsocket *ts; + struct wolfIP_tcp_seg ackseg; + struct wolfIP_timer tmr; wolfIP_init(&s); - mock_link_init(&s); - wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); - wolfIP_filter_set_callback(NULL, NULL); - last_frame_sent_size = 0; - - s.arp.neighbors[0].ip = 0x0A000002U; - s.arp.neighbors[0].if_idx = TEST_PRIMARY_IF; + 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; + 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_ESTABLISHED; + ts->sock.tcp.tmr_rto = 1; + fifo_init(&ts->sock.tcp.txbuf, ts->txmem, TXBUF_SIZE); + + tcp_rto_cb(ts); + ck_assert_int_eq(ts->sock.tcp.tmr_rto, NO_TIMER); +} +END_TEST + +START_TEST(test_tcp_rto_cb_clears_sack_and_marks_lowest_only) +{ + struct wolfIP s; + struct tsocket *ts; + struct pkt_desc *desc1; + struct pkt_desc *desc2; + + 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 * 8; + ts->sock.tcp.snd_una = 100; + ts->sock.tcp.seq = 100; + ts->sock.tcp.bytes_in_flight = 2; + ts->sock.tcp.peer_sack_count = 1; + ts->sock.tcp.peer_sack[0].left = 101; + ts->sock.tcp.peer_sack[0].right = 102; + fifo_init(&ts->sock.tcp.txbuf, ts->txmem, TXBUF_SIZE); + + ck_assert_int_eq(enqueue_tcp_tx(ts, 1, 0x18), 0); + ts->sock.tcp.seq = 101; + ck_assert_int_eq(enqueue_tcp_tx(ts, 1, 0x18), 0); + desc1 = fifo_peek(&ts->sock.tcp.txbuf); + ck_assert_ptr_nonnull(desc1); + desc2 = fifo_next(&ts->sock.tcp.txbuf, desc1); + ck_assert_ptr_nonnull(desc2); + ck_assert_ptr_ne(desc2, desc1); + + desc1->flags |= PKT_FLAG_SENT; + desc2->flags |= PKT_FLAG_SENT; + + s.last_tick = 1000; + tcp_rto_cb(ts); + + ck_assert_uint_eq(ts->sock.tcp.peer_sack_count, 0); + ck_assert_int_eq(desc1->flags & PKT_FLAG_SENT, 0); + ck_assert_int_ne(desc1->flags & PKT_FLAG_RETRANS, 0); + 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 * 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 + +START_TEST(test_tcp_rto_cb_ssthresh_floor_two_mss) +{ + 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; + 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_uint_eq(ts->sock.tcp.cwnd, TCP_MSS); + ck_assert_uint_eq(ts->sock.tcp.ssthresh, TCP_MSS * 2); +} +END_TEST + +START_TEST(test_tcp_rto_cb_fallback_marks_lowest_sent_when_no_snd_una_cover) +{ + 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 = 50; + ts->sock.tcp.seq = 100; + 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.snd_una, 50U); + ck_assert_uint_eq(ts->sock.tcp.cwnd, TCP_MSS); + ck_assert_int_ne(ts->sock.tcp.tmr_rto, NO_TIMER); +} +END_TEST + +START_TEST(test_poll_udp_send_on_arp_hit) +{ + struct wolfIP s; + int udp_sd; + struct wolfIP_sockaddr_in sin; + uint8_t payload[4] = {1, 2, 3, 4}; + uint8_t peer_mac[6] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05}; + int ret; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + wolfIP_filter_set_callback(NULL, NULL); + last_frame_sent_size = 0; + + s.arp.neighbors[0].ip = 0x0A000002U; + s.arp.neighbors[0].if_idx = TEST_PRIMARY_IF; + memcpy(s.arp.neighbors[0].mac, peer_mac, sizeof(peer_mac)); + + udp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + ck_assert_int_gt(udp_sd, 0); + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = ee16(1234); + sin.sin_addr.s_addr = ee32(0x0A000002U); + ret = wolfIP_sock_sendto(&s, udp_sd, payload, sizeof(payload), 0, + (struct wolfIP_sockaddr *)&sin, sizeof(sin)); + ck_assert_int_eq(ret, (int)sizeof(payload)); + + (void)wolfIP_poll(&s, 100); + ck_assert_uint_gt(last_frame_sent_size, 0); + ck_assert_uint_eq(last_frame_sent[12], 0x08); + ck_assert_uint_eq(last_frame_sent[13], 0x00); +} +END_TEST + +START_TEST(test_poll_icmp_send_on_arp_hit) +{ + struct wolfIP s; + int icmp_sd; + struct wolfIP_sockaddr_in sin; + uint8_t payload[ICMP_HEADER_LEN + 1]; + uint8_t peer_mac[6] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x06}; + int ret; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + wolfIP_filter_set_callback(NULL, NULL); + last_frame_sent_size = 0; + + s.arp.neighbors[0].ip = 0x0A000002U; + s.arp.neighbors[0].if_idx = TEST_PRIMARY_IF; memcpy(s.arp.neighbors[0].mac, peer_mac, sizeof(peer_mac)); icmp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_ICMP); @@ -5994,6 +6416,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; @@ -6757,6 +7218,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) { @@ -8332,10 +8809,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); @@ -8346,14 +8827,83 @@ 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; 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); + 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) { + 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_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) { @@ -8390,6 +8940,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; @@ -8416,6 +9035,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 @@ -8577,6 +9219,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; @@ -8588,6 +9231,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]; @@ -8826,7 +9492,36 @@ START_TEST(test_tcp_ack_sack_early_retransmit_before_three_dupack) } END_TEST -START_TEST(test_tcp_input_listen_syn_without_sack_disables_sack) +START_TEST(test_tcp_input_listen_syn_without_sack_disables_sack) +{ + 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); + + 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)]; + ts->sock.tcp.sack_permitted = 1; + + 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_uint_eq(ts->sock.tcp.sack_permitted, 0); +} +END_TEST + +START_TEST(test_tcp_input_listen_syn_arms_control_rto) { struct wolfIP s; int listen_sd; @@ -8836,6 +9531,7 @@ START_TEST(test_tcp_input_listen_syn_without_sack_disables_sack) 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); @@ -8847,11 +9543,15 @@ START_TEST(test_tcp_input_listen_syn_without_sack_disables_sack) ck_assert_int_eq(wolfIP_sock_listen(&s, listen_sd, 1), 0); ts = &s.tcpsockets[SOCKET_UNMARK(listen_sd)]; - ts->sock.tcp.sack_permitted = 1; + 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_uint_eq(ts->sock.tcp.sack_permitted, 0); + 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 @@ -9949,7 +10649,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; @@ -10042,6 +10742,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; @@ -12389,6 +13141,271 @@ 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; + uint8_t payload[8] = {0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c}; + + 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 = 10; + ts->sock.tcp.ack = 20; + 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_with_payload(ts, payload, sizeof(payload), 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(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); +} +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; + 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_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; @@ -13985,6 +15002,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); @@ -14135,6 +15157,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); @@ -14192,6 +15215,12 @@ 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_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); @@ -14212,6 +15241,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); @@ -14225,19 +15255,31 @@ 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); 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_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); 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); 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); @@ -14258,12 +15300,14 @@ 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); 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); @@ -14290,12 +15334,14 @@ 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); 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); @@ -14304,6 +15350,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 fd26aa0..ce03ce0 100644 --- a/src/wolfip.c +++ b/src/wolfip.c @@ -113,6 +113,12 @@ 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 +#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 @@ -123,11 +129,19 @@ 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 #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); @@ -986,14 +1000,21 @@ 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; 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]; @@ -1033,6 +1054,17 @@ 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); +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(struct tsocket *t); #ifdef ETHERNET struct PACKED arp_packet { @@ -1063,6 +1095,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, @@ -1654,13 +1687,21 @@ 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.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; t->sock.tcp.peer_rwnd = 0xFFFF; t->sock.tcp.cwnd = tcp_initial_cwnd(t->sock.tcp.peer_rwnd); @@ -1668,6 +1709,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; @@ -1683,6 +1725,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); @@ -1965,17 +2008,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. */ @@ -2030,12 +2076,12 @@ 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) { - tcp_send_empty(t, 0x11); + tcp_send_empty(t, TCP_FLAG_FIN | TCP_FLAG_ACK); t->sock.tcp.last = t->sock.tcp.seq; } @@ -2049,18 +2095,21 @@ 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)); - 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; + 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); @@ -2072,15 +2121,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; @@ -2108,6 +2159,235 @@ 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) + return 0; + 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((struct tsocket *)t)) + return 1; + return 0; +} + +/* 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) + 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; +} + +/* 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}; + 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; +} + +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; + uint32_t probe_off = 0; + unsigned int tx_if; +#ifdef ETHERNET + struct ipconf *conf; + ip4 nexthop; +#endif + + 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) { + 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; + 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) + 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 = TCP_FLAG_ACK; + 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) { @@ -2370,6 +2650,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) @@ -2379,16 +2660,57 @@ 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; } +/* 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) + return TCP_RTO_MIN_MS; + if (rto_ms > TCP_RTO_MAX_MS) + return TCP_RTO_MAX_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; + 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 @@ -2564,11 +2886,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 +2916,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; @@ -2637,7 +2964,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)) { @@ -2645,17 +2974,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); } } } @@ -2749,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. */ @@ -2757,6 +3085,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 = @@ -2773,6 +3102,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; } @@ -2783,6 +3113,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; } } @@ -2827,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; @@ -2873,11 +3205,14 @@ 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) { - 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); t->sock.tcp.seq = ee32(tcp->ack); t->sock.tcp.snd_una = t->sock.tcp.seq; @@ -2892,8 +3227,9 @@ 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); t->sock.tcp.seq = ee32(tcp->ack); t->sock.tcp.snd_una = t->sock.tcp.seq; @@ -2903,8 +3239,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); - 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) || @@ -2922,7 +3260,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); } @@ -2950,8 +3288,33 @@ static void tcp_rto_cb(void *arg) uint32_t guard = 0; 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)) + 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) { + 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, TCP_FLAG_SYN); + } else if (ts->sock.tcp.state == TCP_SYN_RCVD) { + tcp_send_syn(ts, TCP_FLAG_SYN | TCP_FLAG_ACK); + } else if (ts->sock.tcp.state == TCP_FIN_WAIT_1 || ts->sock.tcp.state == TCP_LAST_ACK) { + tcp_send_finack(ts); + } + tcp_ctrl_rto_start(ts, ts->S->last_tick); + return; + } + if (ts->sock.tcp.state != TCP_ESTABLISHED && + ts->sock.tcp.state != TCP_FIN_WAIT_1) return; /* RFC 6675 / RFC 2018 guidance: after an RTO, SACK scoreboard must not be * trusted (receiver may renege). Fall back to cumulative-ACK driven @@ -3023,8 +3386,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) { @@ -3032,10 +3398,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; @@ -3061,6 +3426,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) { @@ -3120,6 +3487,15 @@ static struct pkt_desc *tcp_find_pending_retrans(struct tsocket *ts, struct pkt_ static void close_socket(struct tsocket *ts) { + if (!ts) + return; + 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)); } @@ -3279,7 +3655,9 @@ int wolfIP_sock_connect(struct wolfIP *s, int sockfd, const struct wolfIP_sockad ts->sock.tcp.state = TCP_CLOSED; return -1; } - tcp_send_syn(ts, 0x02); + ts->sock.tcp.ctrl_rto_retries = 0; + tcp_send_syn(ts, TCP_FLAG_SYN); + tcp_ctrl_rto_start(ts, s->last_tick); return -WOLFIP_EAGAIN; } return -WOLFIP_EINVAL; @@ -3333,6 +3711,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; @@ -3341,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; @@ -3392,8 +3772,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; @@ -3404,6 +3782,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) @@ -3419,20 +3799,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->flags = 0x10 | ((sent == 0)? 0x08 : 0); /* ACK; PSH only on first */ + tcp->hlen = (uint8_t)((TCP_HEADER_LEN + opt_len) << 2); + 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; - 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; @@ -3727,7 +4110,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 +4123,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; @@ -3986,38 +4373,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; } @@ -5213,6 +5568,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); @@ -5288,6 +5646,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); @@ -5309,9 +5669,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) {