Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
bf0c0c9
F-8513: pin datagram IP header to enqueue-time destination
danielinux Aug 17, 2026
ffa9c1c
F-8514: keep accept socket seq at the ISN while in SYN_RCVD
danielinux Aug 17, 2026
64879f1
F-8557: reject TCP connect to broadcast and multicast destinations
danielinux Aug 17, 2026
a528fdf
F-8563: keep peer-advertised TCP MSS verbatim
danielinux Aug 17, 2026
72cd518
F-8565: run PAWS before the segment acceptability test
danielinux Aug 17, 2026
dc521b4
F-6472: count only pure ACKs with unchanged window as duplicate ACKs
danielinux Aug 17, 2026
07fcef0
F-6938: cap the backed-off TCP RTO at 64 seconds
danielinux Aug 17, 2026
2ae0fa2
F-8564: extend control-segment retry budget to meet the 3-minute R2 f…
danielinux Aug 17, 2026
8714895
F-8516: forward transit packets using the static route table
danielinux Aug 17, 2026
69f0bae
F-8558: drop and clamp forwarding on the declared IP total length
danielinux Aug 17, 2026
632eeaa
F-8517: recompute the IP header checksum after the IP_HDRINCL destina…
danielinux Aug 17, 2026
f97f172
F-8515: roll back the armed DNS query when name encoding fails
danielinux Aug 17, 2026
e2322a1
F-8569: reject zero-length DNS labels in the query encoder
danielinux Aug 17, 2026
792c766
F-8570: require an exact 4-byte RDATA for DNS A records
danielinux Aug 17, 2026
664cb34
F-8567: honor the DHCP option overload option (RFC 2132 §9.3)
danielinux Aug 17, 2026
c8bcb31
F-8568: schedule the first DHCPDISCOVER retry at 4 s ± 1 s
danielinux Aug 17, 2026
11ff613
F-6941: consult a freshly registered filter callback for all reasons
danielinux Aug 17, 2026
16f2eb2
F-8513: keep IP_MULTICAST_IF pinned egress at flush time
danielinux Aug 17, 2026
8c23611
F-6941: honor explicit mask configuration made before installing the …
danielinux Aug 17, 2026
36f82fd
F-8568: guard the discover retry jitter window against small bases
danielinux Aug 17, 2026
58345d8
addressed 3 skoll suggestions:
danielinux Aug 17, 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
23 changes: 22 additions & 1 deletion src/test/unit/unit.c
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ Suite *wolf_suite(void)
tcase_add_test(tc_utils, test_filter_notify_tcp_metadata);
tcase_add_test(tc_utils, test_filter_dispatch_no_callback);
tcase_add_test(tc_utils, test_filter_dispatch_mask_not_set);
tcase_add_test(tc_utils, test_filter_fresh_callback_consulted_before_mask_configured);
tcase_add_test(tc_utils, test_filter_dispatch_lock_blocks);
tcase_add_test(tc_utils, test_filter_dispatch_meta_null_initializes);
tcase_add_test(tc_utils, test_filter_socket_event_unknown_proto);
Expand Down Expand Up @@ -521,6 +522,13 @@ Suite *wolf_suite(void)
tcase_add_test(tc_utils, test_dns_abort_query_clears_timer_and_query_state);
tcase_add_test(tc_utils, test_dns_timeout_retries_then_aborts_and_allows_new_query);
tcase_add_test(tc_utils, test_dns_send_query_invalid_name);
tcase_add_test(tc_utils, test_dns_send_query_rejects_empty_labels);
tcase_add_test(tc_utils, test_dns_callback_rejects_a_record_with_wrong_rdlength);
tcase_add_test(tc_utils, test_dhcp_parse_offer_option_overload);
tcase_add_test(tc_utils,
test_dhcp_parse_offer_option_split_across_region_boundary);
tcase_add_test(tc_utils, test_dhcp_discover_first_retry_delay_rfc2131);
tcase_add_test(tc_utils, test_dhcp_discover_retry_delay_small_base_no_underflow);
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_wolfip_ip_is_multicast_variants);
Expand All @@ -534,6 +542,7 @@ Suite *wolf_suite(void)
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_syn_retransmit_duration_meets_rfc9293_r2);
tcase_add_test(tc_utils, test_tcp_input_synack_cancels_control_rto);
tcase_add_test(tc_utils, test_tcp_rto_cb_last_ack_requeues_finack_and_arms_timer);
tcase_add_test(tc_utils, test_tcp_rto_cb_last_ack_full_txbuf_keeps_retry_budget);
Expand Down Expand Up @@ -634,10 +643,14 @@ Suite *wolf_suite(void)
tcase_add_test(tc_utils, test_tcp_ack_duplicate_ssthresh_min);
tcase_add_test(tc_utils, test_tcp_ack_progress_resets_rto_recovery_state);
tcase_add_test(tc_utils, test_tcp_ack_cwnd_grows_when_payload_acked_is_mss_minus_options);
tcase_add_test(tc_utils, test_tcp_ack_data_segments_not_counted_as_dup_acks);
tcase_add_test(tc_utils, test_tcp_rto_backoff_capped_at_64s);
tcase_add_test(tc_utils, test_tcp_ack_inflight_deflate_sets_writable_without_acked_desc);
tcase_add_test(tc_utils, test_tcp_input_peer_rwnd_growth_sets_writable);
tcase_add_test(tc_utils, test_tcp_input_synack_negotiates_peer_mss);
tcase_add_test(tc_utils, test_tcp_connect_syn_advertises_interface_mss);
tcase_add_test(tc_utils, test_tcp_connect_rejects_broadcast_multicast_dest);
tcase_add_test(tc_utils, test_tcp_parse_options_keeps_sub_default_advertised_mss);
tcase_add_test(tc_utils, test_tcp_connect_syn_limits_options_to_small_mtu);
tcase_add_test(tc_utils, test_sock_sendto_tcp_respects_negotiated_peer_mss);
tcase_add_test(tc_utils, test_sock_sendto_tcp_defaults_to_rfc_mss_when_unset_by_peer);
Expand Down Expand Up @@ -940,6 +953,7 @@ Suite *wolf_suite(void)
tcase_add_test(tc_proto, test_regression_paws_rejects_stale_timestamp);
tcase_add_test(tc_proto, test_regression_paws_accepts_wrapped_newer_timestamp);
tcase_add_test(tc_proto, test_regression_paws_drops_segment_without_timestamp_option);
tcase_add_test(tc_proto, test_regression_paws_preempts_acceptability_for_replayed_segment);
tcase_add_test(tc_proto, test_regression_paws_drops_last_ack_segment_without_timestamp_option);
tcase_add_test(tc_proto, test_regression_paws_drops_time_wait_segment_without_timestamp_option);
tcase_add_test(tc_proto, test_regression_dhcp_nak_restarts_configuration);
Expand Down Expand Up @@ -1057,6 +1071,7 @@ Suite *wolf_suite(void)
tcase_add_test(tc_core, test_route_for_ip_gateway_fallback);
tcase_add_test(tc_core, test_route_for_ip_first_non_loop_fallback);
tcase_add_test(tc_core, test_sendto_udp_txbuf_full_eagain);
tcase_add_test(tc_core, test_udp_sendto_queued_datagram_keeps_enqueue_dst);
tcase_add_test(tc_core, test_sendto_icmp_no_remote_after_addr_zero);
tcase_add_test(tc_core, test_ip_recv_with_ip_options_strips_and_dispatches);
tcase_add_test(tc_core, test_ip_recv_wrong_version_dropped);
Expand Down Expand Up @@ -1152,6 +1167,7 @@ Suite *wolf_suite(void)
tcase_add_test(tc_core, test_sock_sendto_raw_null_dest_uses_stored_remote_ip);
tcase_add_test(tc_core, test_sock_sendto_raw_null_dest_no_remote_ip);
tcase_add_test(tc_core, test_sock_sendto_raw_hdrincl_dst_from_buf);
tcase_add_test(tc_core, test_raw_hdrincl_dst_override_recomputes_ip_checksum);
tcase_add_test(tc_core, test_sock_sendto_raw_invalid_fd);
tcase_add_test(tc_core, test_sock_sendto_raw_fifo_full_returns_eagain);
tcase_add_test(tc_core, test_sock_setsockopt_raw_hdrincl);
Expand Down Expand Up @@ -1218,7 +1234,7 @@ Suite *wolf_suite(void)
tcase_add_test(tc_core, test_tcp_parse_options_timestamp_parsed);
tcase_add_test(tc_core, test_tcp_parse_options_timestamp_overlong_ignored);
tcase_add_test(tc_core, test_tcp_parse_options_mss_zero_ignored);
tcase_add_test(tc_core, test_tcp_parse_options_mss_below_floor_clamped);
tcase_add_test(tc_core, test_tcp_parse_options_mss_below_default_kept_verbatim);
tcase_add_test(tc_core, test_tcp_parse_options_sack_permitted_parsed);
tcase_add_test(tc_core, test_tcp_input_syn_rcvd_rst_bad_seq_ignored);
tcase_add_test(tc_core, test_tcp_input_syn_rcvd_rst_good_seq_reverts_to_listen);
Expand All @@ -1234,6 +1250,7 @@ Suite *wolf_suite(void)
tcase_add_test(tc_core, test_tcp_input_syn_rcvd_ack_with_fin_enters_close_wait);
tcase_add_test(tc_core, test_tcp_input_window_grows_from_zero_stops_persist);
tcase_add_test(tc_core, test_tcp_rto_cb_fin_wait_2_timeout_closes_socket);
tcase_add_test(tc_core, test_accept_synack_retransmit_repeats_isn);
tcase_add_test(tc_core, test_tcp_rto_cb_fin_wait_2_wrong_state_stops_timer);
tcase_add_test(tc_core, test_tcp_rto_cb_ctrl_not_needed_stops);
tcase_add_test(tc_core, test_tcp_rto_cb_ctrl_maxretries_nonlistener_closes);
Expand Down Expand Up @@ -1398,6 +1415,10 @@ Suite *wolf_suite(void)
tcase_add_test(tc_core, test_ip_recv_directed_broadcast_dst_is_local);
tcase_add_test(tc_core, test_ip_recv_ipaddr_any_dst_is_local);
tcase_add_test(tc_core, test_ip_recv_forward_arp_hit_sends_immediately);
tcase_add_test(tc_core, test_ip_recv_forward_static_route_uses_gateway);
tcase_add_test(tc_core, test_ip_recv_forward_static_route_hairpin_dropped);
tcase_add_test(tc_core, test_ip_recv_forward_static_route_default);
tcase_add_test(tc_core, test_ip_recv_forward_declared_length_checked);
tcase_add_test(tc_core, test_ip_recv_forward_unconfigured_iface_skipped);
tcase_add_test(tc_core, test_ip_recv_forward_link_local_src_rpf_drop);
tcase_add_test(tc_core, test_ip_recv_forward_self_ip_src_dropped);
Expand Down
29 changes: 29 additions & 0 deletions src/test/unit/unit_shared.c
Original file line number Diff line number Diff line change
Expand Up @@ -81,15 +81,44 @@ static uint8_t last_frame_sent[LINK_MTU];
static uint32_t last_frame_sent_size = 0;
static uint32_t last_frame_sent_count = 0;

/* When armed, the next send() returns -WOLFIP_EAGAIN without transmitting,
* leaving any in-flight descriptor queued in the socket txbuf. */
static int mock_send_eagain_armed = 0;

/* Ring of the most recent frames actually transmitted (up to 4). */
#define MOCK_SENT_FRAMES_MAX 4
static uint8_t mock_sent_frames[MOCK_SENT_FRAMES_MAX][LINK_MTU];
static uint32_t mock_sent_frames_size[MOCK_SENT_FRAMES_MAX];
static uint32_t mock_sent_frames_count = 0;

static int mock_send(struct wolfIP_ll_dev *dev, void *frame, uint32_t len)
{
(void)dev;
if (mock_send_eagain_armed) {
mock_send_eagain_armed = 0;
return -WOLFIP_EAGAIN;
}
memcpy(last_frame_sent, frame, len);
last_frame_sent_size = len;
last_frame_sent_count++;
if (mock_sent_frames_count < MOCK_SENT_FRAMES_MAX) {
memcpy(mock_sent_frames[mock_sent_frames_count], frame, len);
mock_sent_frames_size[mock_sent_frames_count] = len;
mock_sent_frames_count++;
}
return 0;
}

void mock_link_capture_reset(void)
{
last_frame_sent_size = 0;
last_frame_sent_count = 0;
mock_sent_frames_count = 0;
/* Disarm so a test that fails before consuming the flag cannot
* poison the next transmit in a later test. */
mock_send_eagain_armed = 0;
}

static int mock_poll(struct wolfIP_ll_dev *dev, void *frame, uint32_t len)
{
(void)dev;
Expand Down
63 changes: 61 additions & 2 deletions src/test/unit/unit_tests_api.c
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,62 @@ START_TEST(test_filter_dispatch_mask_not_set)
}
END_TEST

/* A freshly installed callback must not fail open: with no explicit mask
* configuration since the last uninstall, dispatch consults the callback
* for every reason; any explicit mask configuration (even a zero mask,
* even one made before installing the callback) switches to the
* configured reasons only. */
START_TEST(test_filter_fresh_callback_consulted_before_mask_configured)
{
struct wolfIP s;
struct wolfIP_filter_metadata meta;

memset(&s, 0, sizeof(s));
/* Clear stale masks from other tests, then return to the initial
* state: uninstall resets the all-reasons default. */
wolfIP_filter_set_mask(0);
wolfIP_filter_set_eth_mask(0);
wolfIP_filter_set_ip_mask(0);
wolfIP_filter_set_tcp_mask(0);
wolfIP_filter_set_udp_mask(0);
wolfIP_filter_set_icmp_mask(0);
wolfIP_filter_set_callback(NULL, NULL);

wolfIP_filter_init_metadata(&meta);
meta.ip_proto = WOLFIP_FILTER_PROTO_TCP;

/* Fresh install, no mask configuration: consulted for all reasons. */
filter_cb_calls = 0;
wolfIP_filter_set_callback(test_filter_cb, NULL);
wolfIP_filter_dispatch(WOLFIP_FILT_RECEIVING, &s, 0, NULL, 0, &meta);
ck_assert_int_eq(filter_cb_calls, 1);

/* The first explicit mask configuration selects the reasons. */
wolfIP_filter_set_mask(WOLFIP_FILT_MASK(WOLFIP_FILT_CONNECTING));
filter_cb_calls = 0;
wolfIP_filter_dispatch(WOLFIP_FILT_RECEIVING, &s, 0, NULL, 0, &meta);
ck_assert_int_eq(filter_cb_calls, 0);

/* An explicit empty mask silences the filter. */
wolfIP_filter_set_mask(0);
filter_cb_calls = 0;
wolfIP_filter_dispatch(WOLFIP_FILT_RECEIVING, &s, 0, NULL, 0, &meta);
ck_assert_int_eq(filter_cb_calls, 0);

/* Explicit zero configured *before* installing the callback must be
* honored, not overridden by the all-reasons default. */
wolfIP_filter_set_callback(NULL, NULL);
wolfIP_filter_set_mask(0);
filter_cb_calls = 0;
wolfIP_filter_set_callback(test_filter_cb, NULL);
wolfIP_filter_dispatch(WOLFIP_FILT_RECEIVING, &s, 0, NULL, 0, &meta);
ck_assert_int_eq(filter_cb_calls, 0);

wolfIP_filter_set_callback(NULL, NULL);
wolfIP_filter_set_mask(0);
}
END_TEST

START_TEST(test_filter_dispatch_lock_blocks)
{
struct wolfIP s;
Expand Down Expand Up @@ -2425,7 +2481,9 @@ START_TEST(test_sock_accept_initializes_snd_una)
ck_assert_int_gt(client_sd, 0);

accepted = &s.tcpsockets[SOCKET_UNMARK(client_sd)];
ck_assert_uint_eq(accepted->sock.tcp.seq, (uint32_t)(0x80000000U + 1U));
/* While in SYN_RCVD the socket's seq stays at the ISN; the final ACK
* handler advances it to ISN+1 on establishment. */
ck_assert_uint_eq(accepted->sock.tcp.seq, 0x80000000U);
ck_assert_uint_eq(accepted->sock.tcp.snd_una, 0x80000000U);
ck_assert_int_eq(tcp_seq_leq(accepted->sock.tcp.snd_una, accepted->sock.tcp.seq), 1);
}
Expand Down Expand Up @@ -2739,7 +2797,8 @@ START_TEST(test_sock_accept_ack_transitions_to_established)
ack.src_port = ee16(40000); /* Remote port from inject_tcp_syn */
ack.dst_port = ee16(1234);
ack.seq = ee32(accepted->sock.tcp.ack);
ack.ack = ee32(accepted->sock.tcp.seq);
/* The client's final ACK is at the server's snd.nxt: ISN+1. */
ack.ack = ee32(tcp_seq_inc(accepted->sock.tcp.snd_una, 1));
ack.hlen = TCP_HEADER_LEN << 2;
ack.flags = TCP_FLAG_ACK;
ack.win = ee16(65535);
Expand Down
66 changes: 66 additions & 0 deletions src/test/unit/unit_tests_branches.c
Original file line number Diff line number Diff line change
Expand Up @@ -1778,6 +1778,72 @@ START_TEST(test_sendto_udp_txbuf_full_eagain)
}
END_TEST

/* Queued datagram keeps its enqueue-time destination: a descriptor left in
* the txbuf after a failed flush must not be re-targeted to the destination
* of a later sendto() on the same socket. */
START_TEST(test_udp_sendto_queued_datagram_keeps_enqueue_dst)
{
struct wolfIP s;
int udp_sd;
struct wolfIP_sockaddr_in sin;
static const uint8_t mac_a[6] = {0x02, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE};
static const uint8_t mac_b[6] = {0x03, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE};
uint8_t payload_a[8];
uint8_t payload_b[8];

wolfIP_init(&s);
mock_link_init(&s);
wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0);
udp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP);
ck_assert_int_gt(udp_sd, 0);

/* Resolve both destinations up front so the flush does not stall on ARP. */
arp_store_neighbor(&s, TEST_PRIMARY_IF, 0x0A000002U, mac_a);
arp_store_neighbor(&s, TEST_PRIMARY_IF, 0x0A000003U, mac_b);

memset(payload_a, 0xA5, sizeof(payload_a));
memset(payload_b, 0x5A, sizeof(payload_b));
mock_link_capture_reset();
mock_send_eagain_armed = 1;

memset(&sin, 0, sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = ee16(9000);
sin.sin_addr.s_addr = ee32(0x0A000002U);
ck_assert_int_eq(wolfIP_sock_sendto(&s, udp_sd, payload_a, sizeof(payload_a), 0,
(struct wolfIP_sockaddr *)&sin, sizeof(sin)), (int)sizeof(payload_a));

/* The flush fails with -EAGAIN; the first descriptor stays queued. */
wolfIP_poll(&s, 0);
ck_assert_uint_eq(mock_sent_frames_count, 0U);

/* Second datagram on the same socket, different destination. */
sin.sin_port = ee16(9001);
sin.sin_addr.s_addr = ee32(0x0A000003U);
ck_assert_int_eq(wolfIP_sock_sendto(&s, udp_sd, payload_b, sizeof(payload_b), 0,
(struct wolfIP_sockaddr *)&sin, sizeof(sin)), (int)sizeof(payload_b));

wolfIP_poll(&s, 0);
ck_assert_uint_eq(mock_sent_frames_count, 2U);

{
const struct wolfIP_ip_packet *ip0 =
(const struct wolfIP_ip_packet *)mock_sent_frames[0];
const struct wolfIP_udp_datagram *udp0 =
(const struct wolfIP_udp_datagram *)mock_sent_frames[0];
const struct wolfIP_ip_packet *ip1 =
(const struct wolfIP_ip_packet *)mock_sent_frames[1];
/* First transmitted frame is the first-enqueued datagram, carrying
* the destination and payload it had when it was queued. */
ck_assert_uint_eq(ip0->dst, ee32(0x0A000002U));
ck_assert_uint_eq(udp0->dst_port, ee16(9000));
ck_assert_mem_eq(udp0->data, payload_a, sizeof(payload_a));
/* Second transmitted frame is the second datagram. */
ck_assert_uint_eq(ip1->dst, ee32(0x0A000003U));
}
}
END_TEST

/* ---- wolfIP_sock_sendto: bound_local_ip mismatch on ICMP ---- */

START_TEST(test_sendto_icmp_no_remote_after_addr_zero)
Expand Down
Loading
Loading