Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
45df1a3
F-14179: relay transit IP fragments in the forwarding path
danielinux Sep 20, 2026
2f48f0d
F-14169: guard flush_tcp_tx fifo_pop to the FIFO tail
danielinux Sep 20, 2026
3790531
F-14186: drop UDP datagrams addressed to non-local destinations
danielinux Sep 20, 2026
0c7345c
F-14170: keep armed persist timer deadline across polls
danielinux Sep 20, 2026
0e7308f
F-14186: scope UDP DHCP exception to an active client
danielinux Sep 21, 2026
299980c
F-14169: keep RTT sample when a zero-length desc is parked
danielinux Sep 21, 2026
e138a32
F-14179: no ICMP errors or L4 filter matches for non-first frags
danielinux Sep 21, 2026
d88f5de
unit: forwarding-path test module (router build)
danielinux Sep 21, 2026
cf069ae
unit: drive ip_recv loopback tests through real frames
danielinux Sep 21, 2026
313896b
F-14187: deliver wildcard-bound UDP after the address arrives
danielinux Sep 21, 2026
dc6e906
F-14174: gem.h: state the -1 vs TRM error code contract
danielinux Sep 21, 2026
4a049f0
F-14171: tcp_persist_start: do not set persist_active on timer insert…
danielinux Sep 21, 2026
87a74b6
F-8566: tcp_ctrl_rto_stop: reinitialize base RTO after a control timeout
danielinux Sep 21, 2026
a967115
F-14172: dns_schedule_timer: drop the dead overflow guard
danielinux Sep 21, 2026
32875e7
F-14188: wolfIP_send_port_unreachable: clamp quote to the datagram
danielinux Sep 21, 2026
416eecd
tcp_ack: reclaim ACKed descriptors parked behind a pure ACK
danielinux Sep 21, 2026
32526ca
tcp: sample RTT from ACKED data, not a zero-length desc
danielinux Sep 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -978,7 +978,8 @@ UNIT_TEST_SRCS:=src/test/unit/unit.c \
src/test/unit/unit_tests_arp_regression.c \
src/test/unit/unit_tests_dns_edges.c \
src/test/unit/unit_tests_misc_edges.c \
src/test/unit/unit_tests_vlan.c
src/test/unit/unit_tests_vlan.c \
src/test/unit/unit_tests_forwarding.c

unit: build/test/unit

Expand Down
3 changes: 2 additions & 1 deletion src/port/amd/common/gem.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@

/* Initialize the GEM, its clock + reset, the PHY, and populate the wolfIP
* link-layer device. Returns:
* < 0 on error (negated TRM code)
* < 0 on error (driver-local sentinel, not a TRM code:
* -10 = no PHY responded on MDIO, -11 = gem_phy_init failed)
* bits [7:0] PHY MDIO address used
* bit [8] link_up flag (1 = link is up at end of init)
*/
Expand Down
27 changes: 26 additions & 1 deletion src/test/unit/unit.c
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
#include "unit_tests_dns_edges.c"
#include "unit_tests_misc_edges.c"
#include "unit_tests_vlan.c"
#include "unit_tests_forwarding.c"

Suite *wolf_suite(void)
{
Expand Down Expand Up @@ -163,6 +164,7 @@ Suite *wolf_suite(void)
tcase_add_test(tc_utils, test_wolfip_send_port_unreachable_ignores_missing_link_sender);
tcase_add_test(tc_utils, test_wolfip_send_port_unreachable_non_ethernet_skips_eth_filter);
tcase_add_test(tc_utils, test_wolfip_send_port_unreachable_sets_df);
tcase_add_test(tc_utils, test_wolfip_send_port_unreachable_quotes_no_more_than_datagram);
tcase_add_test(tc_utils, test_tcp_adv_win_clamps_and_applies_window_scale);
tcase_add_test(tc_utils, test_tcp_segment_acceptable_zero_window_and_overlap_cases);
tcase_add_test(tc_utils, test_tcp_segment_acceptable_counts_syn_in_segment_length);
Expand Down Expand Up @@ -263,6 +265,7 @@ Suite *wolf_suite(void)
tcase_add_test(tc_utils, test_sock_accept_negative_fd);
tcase_add_test(tc_utils, test_sock_accept_invalid_tcp_fd);
tcase_add_test(tc_utils, test_sock_accept_success_sets_addr);
tcase_add_test(tc_utils, test_tcp_listen_before_ipconfig_accepts_after);
tcase_add_test(tc_utils, test_sock_accept_listener_resets_paws_state);
tcase_add_test(tc_utils, test_syn_rcvd_rst_listener_resets_paws_state);
tcase_add_test(tc_utils, test_sock_accept_no_available_socket);
Expand Down Expand Up @@ -419,6 +422,8 @@ 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_start_stops_when_window_reopens_or_no_unsent_payload);
tcase_add_test(tc_utils, test_tcp_persist_start_no_active_flag_when_timer_heap_full);
tcase_add_test(tc_utils, test_tcp_ctrl_rto_stop_resets_base_rto_after_control_timeout);
tcase_add_test(tc_utils, test_tcp_persist_helpers_ignore_non_tcp_and_null_inputs);
tcase_add_test(tc_utils, test_tcp_has_pending_unsent_payload_ignores_zero_ip_len_ack_only_desc);
tcase_add_test(tc_utils, test_tcp_initial_cwnd_caps_to_iw10_and_half_rwnd);
Expand Down Expand Up @@ -547,7 +552,7 @@ Suite *wolf_suite(void)
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_schedule_timer_initial_jitter_and_cancel);
tcase_add_test(tc_utils, test_dns_schedule_timer_caps_large_retry_shift);
tcase_add_test(tc_utils, test_dns_schedule_timer_caps_retry_shift);
tcase_add_test(tc_utils, test_dns_send_query_schedules_timeout);
tcase_add_test(tc_utils, test_dns_send_query_timer_heap_full_aborts);
tcase_add_test(tc_utils, test_dns_send_query_send_failure_clears_outstanding_state);
Expand Down Expand Up @@ -751,6 +756,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_store_ooo_overlap_does_not_exhaust_cache);
tcase_add_test(tc_utils, test_tcp_ack_sack_early_retransmit_before_three_dupack);
tcase_add_test(tc_utils, test_tcp_ack_reclaims_data_acked_behind_pure_ack);
tcase_add_test(tc_utils, test_tcp_ack_forward_ack_after_retransmit_not_duplicate);
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);
Expand All @@ -768,6 +774,8 @@ Suite *wolf_suite(void)
tcase_add_test(tc_utils, test_tcp_mark_unsacked_retransmits_partially_acked_segment);
tcase_add_test(tc_utils, test_tcp_mark_unsacked_rescans_after_clearing_stale_sack);
tcase_add_test(tc_utils, test_tcp_mark_unsacked_ignores_zero_ip_len_unsent_ack_only_desc);
tcase_add_test(tc_utils, test_flush_tcp_tx_pure_ack_keeps_unacked_data_desc);
tcase_add_test(tc_utils, test_tcp_ack_parked_zero_desc_keeps_rtt_sample);
tcase_add_test(tc_utils, test_tcp_ack_sack_blocks_clamped_and_dropped);
tcase_add_test(tc_utils, test_tcp_recv_ooo_capacity_limit);
tcase_add_test(tc_utils, test_tcp_recv_overlapping_ooo_segments_coalesce_on_consume);
Expand Down Expand Up @@ -945,6 +953,8 @@ Suite *wolf_suite(void)
tcase_add_test(tc_proto, test_ip_recv_forward_arp_queue_and_flush);
tcase_add_test(tc_proto, test_arp_flush_pending_ttl_expired);
tcase_add_test(tc_proto, test_wolfip_forwarding_basic);
tcase_add_test(tc_proto, test_ip_recv_forwarding_relays_transit_fragments);
tcase_add_test(tc_proto, test_ip_recv_forwarding_drops_local_fragment);
tcase_add_test(tc_proto, test_wolfip_forwarding_ttl_expired);
tcase_add_test(tc_proto, test_regression_forwarding_no_ttl_exceeded_for_icmp_error);
tcase_add_test(tc_proto, test_forward_packet_ip_filter_drop);
Expand Down Expand Up @@ -990,6 +1000,9 @@ Suite *wolf_suite(void)
tcase_add_test(tc_proto, test_udp_sendto_and_recvfrom);
tcase_add_test(tc_proto, test_udp_sendto_unbound_socket_receives_reply);
tcase_add_test(tc_proto, test_udp_wildcard_bind_receives_all_local_addrs);
tcase_add_test(tc_proto, test_udp_wildcard_bind_before_ipconfig_receives_after);
tcase_add_test(tc_proto, test_udp_wildcard_bind_drops_third_party_dst);
tcase_add_test(tc_proto, test_udp_dhcp_exchange_only_reaches_dhcp_socket);
tcase_add_test(tc_proto, test_udp_sendto_respects_mtu_api);
tcase_add_test(tc_proto, test_udp_recvfrom_sets_remote_ip);
tcase_add_test(tc_proto, test_udp_recvfrom_null_src_addr_len);
Expand Down Expand Up @@ -1439,6 +1452,7 @@ Suite *wolf_suite(void)
tcase_add_test(tc_core, test_poll_tx_tcp_filter_tcp_blocks_send);
tcase_add_test(tc_core, test_poll_tx_tcp_send_eagain_breaks_loop);
tcase_add_test(tc_core, test_poll_tx_tcp_zero_window_starts_persist);
tcase_add_test(tc_core, test_poll_tx_tcp_zero_window_probe_fires_under_fast_poll);
tcase_add_test(tc_core, test_poll_tx_tcp_retransmit_replay);
tcase_add_test(tc_core, test_poll_tx_tcp_loopback_path);
tcase_add_test(tc_core, test_poll_tx_udp_sends_on_arp_hit);
Expand Down Expand Up @@ -1791,6 +1805,17 @@ Suite *wolf_suite(void)
#endif
#endif /* WOLFIP_VLAN */

#if WOLFIP_ENABLE_FORWARDING
/* --- unit_tests_forwarding.c (router build) --- */
tcase_add_test(tc_proto, test_fwd_nonfirst_frag_ttl1_silent_drop);
tcase_add_test(tc_proto, test_fwd_first_frag_ttl1_sends_ttl_exceeded);
tcase_add_test(tc_proto, test_fwd_nonfirst_frag_df_oversize_silent_drop);
tcase_add_test(tc_proto, test_fwd_first_frag_df_oversize_sends_frag_needed);
tcase_add_test(tc_proto, test_fwd_nonfirst_frag_bad_option_silent_drop);
tcase_add_test(tc_proto, test_fwd_multicast_dest_bad_option_silent_drop);
tcase_add_test(tc_proto, test_fwd_nonfirst_frag_l4_filter_not_notified);
#endif /* WOLFIP_ENABLE_FORWARDING */

suite_add_tcase(s, tc_core);
suite_add_tcase(s, tc_utils);
suite_add_tcase(s, tc_proto);
Expand Down
196 changes: 196 additions & 0 deletions src/test/unit/unit_tests_api.c
Original file line number Diff line number Diff line change
Expand Up @@ -1013,6 +1013,202 @@ START_TEST(test_udp_wildcard_bind_receives_all_local_addrs)
}
END_TEST

/* Embedded start-up order: the socket is bound before the interface has
* any address. The bind snapshot latches local_ip == 0; the stack must
* still deliver once the address arrives, not answer port-unreachable
* for the life of the socket. */
START_TEST(test_udp_wildcard_bind_before_ipconfig_receives_after)
{
struct wolfIP s;
int sd;
struct wolfIP_sockaddr_in sin;
struct wolfIP_sockaddr_in from;
socklen_t from_len = sizeof(from);
uint8_t payload[4] = {1, 2, 3, 4};
uint8_t rxbuf[8];
ip4 local_ip = 0x0A000001U;
int ret;

wolfIP_init(&s);
mock_link_init(&s);

sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP);
ck_assert_int_gt(sd, 0);
memset(&sin, 0, sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = ee16(5353);
sin.sin_addr.s_addr = 0U; /* INADDR_ANY */
ck_assert_int_eq(wolfIP_sock_bind(&s, sd, (struct wolfIP_sockaddr *)&sin,
sizeof(sin)), 0);
/* No interface address existed at bind time: the snapshot is 0. */
ck_assert_uint_eq(s.udpsockets[SOCKET_UNMARK(sd)].local_ip, 0);

wolfIP_ipconfig_set(&s, local_ip, 0xFFFFFF00U, 0);

inject_udp_datagram(&s, TEST_PRIMARY_IF, 0x0A000064U, local_ip,
60000, 5353, payload, sizeof(payload));

memset(&from, 0, sizeof(from));
ret = wolfIP_sock_recvfrom(&s, sd, rxbuf, sizeof(rxbuf), 0,
(struct wolfIP_sockaddr *)&from, &from_len);
ck_assert_int_eq(ret, (int)sizeof(payload));
ck_assert_mem_eq(rxbuf, payload, sizeof(payload));
/* Delivered, not bounced with ICMP Port Unreachable. */
ck_assert_uint_eq(last_frame_sent_count, 0);
}
END_TEST

/* Same start-up order on the TCP side: bind + listen before the address
* is configured. A SYN addressed to the just-configured address must be
* taken by the wildcard listener and accepted. */
START_TEST(test_tcp_listen_before_ipconfig_accepts_after)
{
struct wolfIP s;
int listen_sd;
int client_sd;
struct tsocket *listener;
struct wolfIP_sockaddr_in sin;
socklen_t alen = sizeof(sin);
ip4 local_ip = 0x0A000001U;

wolfIP_init(&s);
mock_link_init(&s);

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(8080);
sin.sin_addr.s_addr = 0U; /* INADDR_ANY */
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);
ck_assert_uint_eq(s.tcpsockets[SOCKET_UNMARK(listen_sd)].local_ip, 0);

wolfIP_ipconfig_set(&s, local_ip, 0xFFFFFF00U, 0);

inject_tcp_syn(&s, TEST_PRIMARY_IF, local_ip, 8080);
listener = &s.tcpsockets[SOCKET_UNMARK(listen_sd)];
ck_assert_int_eq(listener->sock.tcp.state, TCP_SYN_RCVD);

client_sd = wolfIP_sock_accept(&s, listen_sd, (struct wolfIP_sockaddr *)&sin,
&alen);
ck_assert_int_gt(client_sd, 0);
}
END_TEST

START_TEST(test_udp_wildcard_bind_drops_third_party_dst)
{
struct wolfIP s;
int sd;
struct wolfIP_sockaddr_in sin;
uint8_t payload[4] = {1, 2, 3, 4};
uint8_t rxbuf[LINK_MTU];
int ret;

setup_stack_with_two_ifaces(&s, 0x0A000001U, 0x0A010001U);

sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP);
ck_assert_int_gt(sd, 0);
memset(&sin, 0, sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = ee16(5353);
sin.sin_addr.s_addr = 0U; /* INADDR_ANY */
ck_assert_int_eq(wolfIP_sock_bind(&s, sd, (struct wolfIP_sockaddr *)&sin,
sizeof(sin)), 0);

/* A datagram addressed to a third-party IP is not for this host: RFC
* 1122 requires silent discard. Pre-fix the wildcard bind matched any
* destination, so an L2-adjacent attacker could inject application
* traffic addressed to someone else. */
inject_udp_datagram(&s, TEST_PRIMARY_IF, 0x0A000064U, 0xC0A80164U,
60000, 5353, payload, sizeof(payload));
ret = wolfIP_sock_recvfrom(&s, sd, rxbuf, sizeof(rxbuf), 0,
(struct wolfIP_sockaddr *)NULL, NULL);
ck_assert_int_eq(ret, -WOLFIP_EAGAIN);

/* The gate must not over-reject: a limited broadcast is still
* delivered to the wildcard bind. */
inject_udp_datagram(&s, TEST_PRIMARY_IF, 0x0A000064U, 0xFFFFFFFFU,
60002, 5353, payload, sizeof(payload));
ret = wolfIP_sock_recvfrom(&s, sd, rxbuf, sizeof(rxbuf), 0,
(struct wolfIP_sockaddr *)NULL, NULL);
ck_assert_int_eq(ret, (int)sizeof(payload));
ck_assert_mem_eq(rxbuf, payload, sizeof(payload));
}
END_TEST

/* Regression for the DHCP exception in the udp_try_recv() gate: a
* third-party-addressed 67->68 datagram (the pre-address RFC 2131
* OFFER/ACK window) must reach only the DHCP client's own socket - never
* a plain wildcard bind on port 68 - and never at all when DHCP is off.
* The phases reuse the single wildcard :68 bind (the stack rejects a
* second wildcard bind on the same port). */
START_TEST(test_udp_dhcp_exchange_only_reaches_dhcp_socket)
{
struct wolfIP s;
int sd;
struct wolfIP_sockaddr_in sin;
uint8_t payload[4] = {1, 2, 3, 4};
uint8_t rxbuf[LINK_MTU];
int ret;
ip4 third_party = 0xC0A80164U; /* 192.168.1.100, not ours */

setup_stack_with_two_ifaces(&s, 0x0A000001U, 0x0A010001U);

sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM,
WI_IPPROTO_UDP);
ck_assert_int_gt(sd, 0);
memset(&sin, 0, sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = ee16(DHCP_CLIENT_PORT);
sin.sin_addr.s_addr = 0U;
ck_assert_int_eq(wolfIP_sock_bind(&s, sd, (struct wolfIP_sockaddr *)&sin,
sizeof(sin)), 0);

/* Phase 1: a plain application socket, wildcard bound on port 68,
* DHCP exchanging: the third-party addressed datagram must NOT be
* delivered to it. */
s.dhcp_state = DHCP_DISCOVER_SENT;
s.dhcp_udp_sd = 0; /* no DHCP socket */
inject_udp_datagram(&s, TEST_PRIMARY_IF, 0x0A000064U, third_party,
DHCP_SERVER_PORT, DHCP_CLIENT_PORT, payload, sizeof(payload));
ret = wolfIP_sock_recvfrom(&s, sd, rxbuf, sizeof(rxbuf), 0,
(struct wolfIP_sockaddr *)NULL, NULL);
ck_assert_int_eq(ret, -WOLFIP_EAGAIN);

wolfIP_sock_close(&s, sd);

/* Phase 2: the same socket registered as the DHCP client socket:
* the legitimate pre-address exchange is delivered. */
sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM,
WI_IPPROTO_UDP);
ck_assert_int_gt(sd, 0);
memset(&sin, 0, sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = ee16(DHCP_CLIENT_PORT);
sin.sin_addr.s_addr = 0U;
ck_assert_int_eq(wolfIP_sock_bind(&s, sd, (struct wolfIP_sockaddr *)&sin,
sizeof(sin)), 0);
s.dhcp_udp_sd = sd;
inject_udp_datagram(&s, TEST_PRIMARY_IF, 0x0A000064U, third_party,
DHCP_SERVER_PORT, DHCP_CLIENT_PORT, payload, sizeof(payload));
ret = wolfIP_sock_recvfrom(&s, sd, rxbuf, sizeof(rxbuf), 0,
(struct wolfIP_sockaddr *)NULL, NULL);
ck_assert_int_eq(ret, (int)sizeof(payload));
ck_assert_mem_eq(rxbuf, payload, sizeof(payload));

/* Phase 3: DHCP off: the same datagram is dropped at the gate. */
s.dhcp_state = DHCP_OFF;
inject_udp_datagram(&s, TEST_PRIMARY_IF, 0x0A000064U, third_party,
DHCP_SERVER_PORT, DHCP_CLIENT_PORT, payload, sizeof(payload));
ret = wolfIP_sock_recvfrom(&s, sd, rxbuf, sizeof(rxbuf), 0,
(struct wolfIP_sockaddr *)NULL, NULL);
ck_assert_int_eq(ret, -WOLFIP_EAGAIN);
}
END_TEST

START_TEST(test_udp_sendto_respects_mtu_api)
{
struct wolfIP s;
Expand Down
15 changes: 11 additions & 4 deletions src/test/unit/unit_tests_dns_dhcp.c
Original file line number Diff line number Diff line change
Expand Up @@ -3367,17 +3367,20 @@ START_TEST(test_dns_schedule_timer_initial_jitter_and_cancel)
}
END_TEST

START_TEST(test_dns_schedule_timer_caps_large_retry_shift)
START_TEST(test_dns_schedule_timer_caps_retry_shift)
{
struct wolfIP s;

wolfIP_init(&s);
s.last_tick = 100U;
s.dns_retry_count = 64U;
/* The largest shift reachable: the single increment site caps the
* count at DNS_QUERY_RETRIES. */
s.dns_retry_count = DNS_QUERY_RETRIES;

dns_schedule_timer(&s);
ck_assert_int_ne(s.dns_timer, NO_TIMER);
ck_assert_uint_eq(find_timer_expiry(&s, s.dns_timer), UINT64_MAX);
ck_assert_uint_eq(find_timer_expiry(&s, s.dns_timer),
100U + (DNS_QUERY_TIMEOUT << DNS_QUERY_RETRIES));
}
END_TEST

Expand Down Expand Up @@ -6641,7 +6644,11 @@ START_TEST(test_udp_try_recv_conf_null)
udp->dst_port = ee16(1234);
udp->len = ee16(UDP_HEADER_LEN + 4);
udp_try_recv(&s, TEST_PRIMARY_IF, udp, (uint32_t)(ETH_HEADER_LEN + IP_HEADER_LEN + UDP_HEADER_LEN + 4));
ck_assert_ptr_nonnull(fifo_peek(&ts->sock.udp.rxbuf));
/* With no configured interfaces no destination is local, so the
* datagram is dropped even though the socket's manually set local_ip
* matches the destination (RFC 1122: a host consumes only traffic
* addressed to its own addresses). */
ck_assert_ptr_eq(fifo_peek(&ts->sock.udp.rxbuf), NULL);
}
END_TEST

Expand Down
Loading
Loading