From da29810f85b717c9497d80800eb311feda1c5917 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Fri, 27 Mar 2026 13:18:47 +0100 Subject: [PATCH 1/3] Added missing important verification paths in unit tests --- src/test/unit/unit.c | 14 ++ src/test/unit/unit_tests_api.c | 195 +++++++++++++++++++ src/test/unit/unit_tests_dns_dhcp.c | 222 ++++++++++++++++++++++ src/test/unit/unit_tests_proto.c | 283 ++++++++++++++++++++++++++++ 4 files changed, 714 insertions(+) diff --git a/src/test/unit/unit.c b/src/test/unit/unit.c index 7ac99111..6cb07594 100644 --- a/src/test/unit/unit.c +++ b/src/test/unit/unit.c @@ -117,7 +117,9 @@ Suite *wolf_suite(void) #endif tcase_add_test(tc_utils, test_wolfip_ipconfig_ex_per_interface); tcase_add_test(tc_utils, test_wolfip_poll_executes_timers_and_callbacks); + tcase_add_test(tc_utils, test_wolfip_poll_drains_all_expired_timers_in_one_pass); tcase_add_test(tc_utils, test_wolfip_poll_preserves_tcp_events_raised_during_callback); + tcase_add_test(tc_utils, test_wolfip_poll_limits_device_drain_to_poll_budget); 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); @@ -192,6 +194,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_utils, test_sock_accept_bound_local_ip_no_match); tcase_add_test(tc_utils, test_sock_accept_starts_rto_timer); tcase_add_test(tc_utils, test_sock_accept_initializes_snd_una); + tcase_add_test(tc_utils, test_sock_accept_clones_half_open_state_and_queues_synack); tcase_add_test(tc_utils, test_sock_accept_synack_retransmission); tcase_add_test(tc_utils, test_sock_accept_synack_window_not_scaled); tcase_add_test(tc_utils, test_sock_accept_ack_transitions_to_established); @@ -281,9 +284,15 @@ Suite *wolf_suite(void) 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_start_stops_when_window_reopens_or_no_unsent_payload); tcase_add_test(tc_utils, test_tcp_initial_cwnd_caps_to_iw10_and_half_rwnd); tcase_add_test(tc_utils, test_tcp_persist_cb_sends_one_byte_probe); + tcase_add_test(tc_utils, test_tcp_zero_wnd_probe_selects_middle_byte_at_snd_una); tcase_add_test(tc_utils, test_tcp_persist_probe_byte_matches_snd_una_offset); + tcase_add_test(tc_utils, test_tcp_zero_wnd_probe_arp_miss_requests_resolution); + tcase_add_test(tc_utils, test_tcp_rto_cb_marks_snd_una_payload_for_retransmit); + tcase_add_test(tc_utils, test_tcp_rto_cb_clears_bookkeeping_when_no_payload_pending); + tcase_add_test(tc_utils, test_tcp_rto_cb_closes_socket_when_backoff_exhausted); 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); @@ -295,6 +304,8 @@ Suite *wolf_suite(void) tcase_add_test(tc_utils, test_dhcp_send_request_renewing_sets_ciaddr_and_rebind_deadline); tcase_add_test(tc_utils, test_dhcp_send_request_rebinding_broadcasts_to_lease_expiry); tcase_add_test(tc_utils, test_dhcp_poll_offer_and_ack); + tcase_add_test(tc_utils, test_dhcp_poll_renewing_ack_binds_client); + tcase_add_test(tc_utils, test_dhcp_poll_rebinding_ack_binds_client); tcase_add_test(tc_utils, test_dns_callback_ptr_response); tcase_add_test(tc_utils, test_udp_try_recv_short_frame); tcase_add_test(tc_utils, test_udp_try_recv_filter_drop); @@ -309,6 +320,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_utils, test_dns_callback_bad_name); tcase_add_test(tc_utils, test_dns_callback_short_header_ignored); tcase_add_test(tc_utils, test_dns_callback_wrong_id_ignored); + tcase_add_test(tc_utils, test_dns_callback_malformed_compressed_name_aborts_query); tcase_add_test(tc_utils, test_dns_callback_abort_clears_query_state); tcase_add_test(tc_utils, test_dns_abort_query_null_noop); tcase_add_test(tc_utils, test_tcp_input_ttl_zero_local_ack_still_processes); @@ -356,6 +368,8 @@ Suite *wolf_suite(void) tcase_add_test(tc_utils, test_dns_schedule_timer_caps_large_retry_shift); tcase_add_test(tc_utils, test_dns_send_query_schedules_timeout); tcase_add_test(tc_utils, test_dns_resend_query_uses_stored_query_buffer); + tcase_add_test(tc_utils, test_dns_resend_query_fails_without_valid_socket); + tcase_add_test(tc_utils, test_dns_resend_query_fails_without_cached_query_buffer); 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); diff --git a/src/test/unit/unit_tests_api.c b/src/test/unit/unit_tests_api.c index b662033c..531e9180 100644 --- a/src/test/unit/unit_tests_api.c +++ b/src/test/unit/unit_tests_api.c @@ -1,6 +1,21 @@ static struct wolfIP *poll_rearm_stack; static int poll_rearm_cb_calls; static int poll_rearm_recv_len; +static int poll_budget_packets_left; +static int poll_budget_poll_calls; + +static int test_poll_budget_ll_poll(struct wolfIP_ll_dev *dev, void *frame, uint32_t len) +{ + struct wolfIP_eth_frame *eth = (struct wolfIP_eth_frame *)frame; + + (void)dev; + if (len < ETH_HEADER_LEN || poll_budget_packets_left <= 0) + return 0; + memset(eth, 0, ETH_HEADER_LEN); + poll_budget_packets_left--; + poll_budget_poll_calls++; + return ETH_HEADER_LEN; +} static void test_poll_rearm_tcp_cb(int sock_fd, uint16_t events, void *arg) { @@ -47,6 +62,34 @@ START_TEST(test_wolfip_poll_executes_timers_and_callbacks) } END_TEST +START_TEST(test_wolfip_poll_drains_all_expired_timers_in_one_pass) +{ + struct wolfIP s; + struct wolfIP_timer tmr; + + wolfIP_init(&s); + mock_link_init(&s); + timer_cb_calls = 0; + + /* Multiple expired timers should all run during the same poll iteration. */ + memset(&tmr, 0, sizeof(tmr)); + tmr.cb = test_timer_cb; + tmr.expires = 100; + timers_binheap_insert(&s.timers, tmr); + + memset(&tmr, 0, sizeof(tmr)); + tmr.cb = test_timer_cb; + tmr.expires = 90; + timers_binheap_insert(&s.timers, tmr); + + (void)wolfIP_poll(&s, 100); + + /* The timer heap should be empty once all expired callbacks have run. */ + ck_assert_int_eq(timer_cb_calls, 2); + ck_assert_uint_eq(s.timers.size, 0U); +} +END_TEST + START_TEST(test_wolfip_poll_preserves_tcp_events_raised_during_callback) { struct wolfIP s; @@ -80,6 +123,29 @@ START_TEST(test_wolfip_poll_preserves_tcp_events_raised_during_callback) } END_TEST +START_TEST(test_wolfip_poll_limits_device_drain_to_poll_budget) +{ + struct wolfIP s; + struct wolfIP_ll_dev *ll; + + wolfIP_init(&s); + mock_link_init(&s); + ll = wolfIP_ll_at(&s, TEST_PRIMARY_IF); + ck_assert_ptr_nonnull(ll); + ll->poll = test_poll_budget_ll_poll; + ll->non_ethernet = 0; + + /* Feed more frames than the scheduler budget allows in a single poll call. */ + poll_budget_packets_left = WOLFIP_POLL_BUDGET + 3; + poll_budget_poll_calls = 0; + (void)wolfIP_poll(&s, 100); + + /* Step 1 should stop after consuming exactly one poll budget worth of packets. */ + ck_assert_int_eq(poll_budget_poll_calls, WOLFIP_POLL_BUDGET); + ck_assert_int_eq(poll_budget_packets_left, 3); +} +END_TEST + START_TEST(test_filter_notify_tcp_metadata) { struct wolfIP s; @@ -2182,6 +2248,135 @@ START_TEST(test_sock_accept_initializes_snd_una) } END_TEST +START_TEST(test_sock_accept_clones_half_open_state_and_queues_synack) +{ + struct wolfIP s; + int listen_sd; + int client_sd; + struct tsocket *listener; + struct tsocket *accepted; + struct wolfIP_sockaddr_in sin; + struct pkt_desc *desc; + struct wolfIP_tcp_seg *seg; + void *cb_arg = &s; + uint32_t pre_accept_seq; + uint32_t pre_accept_ack; + uint32_t pre_accept_last_ts; + uint32_t pre_accept_local_ip; + uint32_t pre_accept_remote_ip; + uint32_t pre_accept_peer_rwnd; + uint16_t pre_accept_peer_mss; + uint16_t pre_accept_src_port; + uint16_t pre_accept_dst_port; + uint8_t pre_accept_snd_wscale; + uint8_t pre_accept_rcv_wscale; + uint8_t pre_accept_ws_enabled; + uint8_t pre_accept_ws_offer; + uint8_t pre_accept_ts_enabled; + uint8_t pre_accept_ts_offer; + uint8_t pre_accept_sack_offer; + uint8_t pre_accept_sack_permitted; + + 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); + + listener = &s.tcpsockets[SOCKET_UNMARK(listen_sd)]; + listener->callback = test_socket_cb; + listener->callback_arg = cb_arg; + + /* Drive the listener into SYN_RCVD so accept() has half-open state to fork. */ + inject_tcp_syn(&s, TEST_PRIMARY_IF, 0x0A000001U, 1234); + ck_assert_int_eq(listener->sock.tcp.state, TCP_SYN_RCVD); + + /* Seed half-open negotiation state so accept() must clone it into the child socket. */ + listener->sock.tcp.last_ts = 0x11223344U; + listener->sock.tcp.peer_rwnd = 4096; + listener->sock.tcp.peer_mss = 1200; + listener->sock.tcp.snd_wscale = 4; + listener->sock.tcp.rcv_wscale = 2; + listener->sock.tcp.ws_enabled = 1; + listener->sock.tcp.ws_offer = 1; + listener->sock.tcp.ts_enabled = 1; + listener->sock.tcp.ts_offer = 1; + listener->sock.tcp.sack_offer = 1; + listener->sock.tcp.sack_permitted = 1; + + pre_accept_seq = listener->sock.tcp.seq; + pre_accept_ack = listener->sock.tcp.ack; + pre_accept_last_ts = listener->sock.tcp.last_ts; + pre_accept_local_ip = listener->local_ip; + pre_accept_remote_ip = listener->remote_ip; + pre_accept_peer_rwnd = listener->sock.tcp.peer_rwnd; + pre_accept_peer_mss = listener->sock.tcp.peer_mss; + pre_accept_src_port = listener->src_port; + pre_accept_dst_port = listener->dst_port; + pre_accept_snd_wscale = listener->sock.tcp.snd_wscale; + pre_accept_rcv_wscale = listener->sock.tcp.rcv_wscale; + pre_accept_ws_enabled = listener->sock.tcp.ws_enabled; + pre_accept_ws_offer = listener->sock.tcp.ws_offer; + pre_accept_ts_enabled = listener->sock.tcp.ts_enabled; + pre_accept_ts_offer = listener->sock.tcp.ts_offer; + pre_accept_sack_offer = listener->sock.tcp.sack_offer; + pre_accept_sack_permitted = listener->sock.tcp.sack_permitted; + + /* Accept should fork the half-open state into a child socket and queue a SYN-ACK there. */ + client_sd = wolfIP_sock_accept(&s, listen_sd, NULL, NULL); + ck_assert_int_gt(client_sd, 0); + + accepted = &s.tcpsockets[SOCKET_UNMARK(client_sd)]; + /* The child socket should inherit the negotiated transport parameters verbatim. */ + ck_assert_int_eq(accepted->sock.tcp.state, TCP_SYN_RCVD); + ck_assert_ptr_eq(accepted->callback, test_socket_cb); + ck_assert_ptr_eq(accepted->callback_arg, cb_arg); + ck_assert_uint_eq(accepted->local_ip, pre_accept_local_ip); + ck_assert_uint_eq(accepted->bound_local_ip, listener->bound_local_ip); + ck_assert_uint_eq(accepted->if_idx, TEST_PRIMARY_IF); + ck_assert_uint_eq(accepted->remote_ip, pre_accept_remote_ip); + ck_assert_uint_eq(accepted->src_port, pre_accept_src_port); + ck_assert_uint_eq(accepted->dst_port, pre_accept_dst_port); + ck_assert_uint_eq(accepted->sock.tcp.ack, pre_accept_ack); + ck_assert_uint_eq(accepted->sock.tcp.snd_una, pre_accept_seq); + ck_assert_uint_eq(accepted->sock.tcp.last_ts, pre_accept_last_ts); + ck_assert_uint_eq(accepted->sock.tcp.peer_rwnd, pre_accept_peer_rwnd); + ck_assert_uint_eq(accepted->sock.tcp.peer_mss, pre_accept_peer_mss); + ck_assert_uint_eq(accepted->sock.tcp.snd_wscale, pre_accept_snd_wscale); + ck_assert_uint_eq(accepted->sock.tcp.rcv_wscale, pre_accept_rcv_wscale); + ck_assert_uint_eq(accepted->sock.tcp.ws_enabled, pre_accept_ws_enabled); + ck_assert_uint_eq(accepted->sock.tcp.ws_offer, pre_accept_ws_offer); + ck_assert_uint_eq(accepted->sock.tcp.ts_enabled, pre_accept_ts_enabled); + ck_assert_uint_eq(accepted->sock.tcp.ts_offer, pre_accept_ts_offer); + ck_assert_uint_eq(accepted->sock.tcp.sack_offer, pre_accept_sack_offer); + ck_assert_uint_eq(accepted->sock.tcp.sack_permitted, pre_accept_sack_permitted); + ck_assert_uint_eq(accepted->sock.tcp.cwnd, + tcp_initial_cwnd(pre_accept_peer_rwnd, tcp_cc_mss(accepted))); + ck_assert_uint_eq(accepted->sock.tcp.ssthresh, + tcp_initial_ssthresh(pre_accept_peer_rwnd)); + + desc = fifo_peek(&accepted->sock.tcp.txbuf); + ck_assert_ptr_nonnull(desc); + seg = (struct wolfIP_tcp_seg *)(accepted->txmem + desc->pos + sizeof(*desc)); + /* SYN-ACK transmission must be queued on the accepted child, not the listener. */ + ck_assert_uint_eq(seg->flags, (TCP_FLAG_SYN | TCP_FLAG_ACK)); + ck_assert_uint_eq(ee32(seg->seq), pre_accept_seq); + ck_assert_uint_eq(ee32(seg->ack), pre_accept_ack); + + /* The listener should be reset to passive-open state once the child is created. */ + ck_assert_int_eq(listener->sock.tcp.state, TCP_LISTEN); + ck_assert_uint_eq(listener->sock.tcp.ctrl_rto_active, 0); + ck_assert_uint_eq(listener->events & CB_EVENT_READABLE, 0); +} +END_TEST + START_TEST(test_sock_accept_synack_retransmission) { struct wolfIP s; diff --git a/src/test/unit/unit_tests_dns_dhcp.c b/src/test/unit/unit_tests_dns_dhcp.c index 95be3e75..b33f6136 100644 --- a/src/test/unit/unit_tests_dns_dhcp.c +++ b/src/test/unit/unit_tests_dns_dhcp.c @@ -9,6 +9,50 @@ static uint64_t find_timer_expiry(const struct wolfIP *s, uint32_t timer_id) return 0; } +static void build_dhcp_ack_msg(struct dhcp_msg *msg, uint32_t server_ip, uint32_t mask, + uint32_t router_ip, uint32_t dns_ip) +{ + struct dhcp_option *opt; + + memset(msg, 0, sizeof(*msg)); + msg->magic = ee32(DHCP_MAGIC); + opt = (struct dhcp_option *)msg->options; + opt->code = DHCP_OPTION_MSG_TYPE; + opt->len = 1; + opt->data[0] = DHCP_ACK; + opt = (struct dhcp_option *)((uint8_t *)opt + 3); + opt->code = DHCP_OPTION_SERVER_ID; + opt->len = 4; + opt->data[0] = (server_ip >> 24) & 0xFF; + opt->data[1] = (server_ip >> 16) & 0xFF; + opt->data[2] = (server_ip >> 8) & 0xFF; + opt->data[3] = (server_ip >> 0) & 0xFF; + opt = (struct dhcp_option *)((uint8_t *)opt + 6); + opt->code = DHCP_OPTION_SUBNET_MASK; + opt->len = 4; + opt->data[0] = (mask >> 24) & 0xFF; + opt->data[1] = (mask >> 16) & 0xFF; + opt->data[2] = (mask >> 8) & 0xFF; + opt->data[3] = (mask >> 0) & 0xFF; + opt = (struct dhcp_option *)((uint8_t *)opt + 6); + opt->code = DHCP_OPTION_ROUTER; + opt->len = 4; + opt->data[0] = (router_ip >> 24) & 0xFF; + opt->data[1] = (router_ip >> 16) & 0xFF; + opt->data[2] = (router_ip >> 8) & 0xFF; + opt->data[3] = (router_ip >> 0) & 0xFF; + opt = (struct dhcp_option *)((uint8_t *)opt + 6); + opt->code = DHCP_OPTION_DNS; + opt->len = 4; + opt->data[0] = (dns_ip >> 24) & 0xFF; + opt->data[1] = (dns_ip >> 16) & 0xFF; + opt->data[2] = (dns_ip >> 8) & 0xFF; + opt->data[3] = (dns_ip >> 0) & 0xFF; + opt = (struct dhcp_option *)((uint8_t *)opt + 6); + opt->code = DHCP_OPTION_END; + opt->len = 0; +} + START_TEST(test_wolfip_static_instance_apis) { struct wolfIP *s = NULL; @@ -1979,6 +2023,50 @@ START_TEST(test_dns_resend_query_uses_stored_query_buffer) } END_TEST +START_TEST(test_dns_resend_query_fails_without_valid_socket) +{ + struct wolfIP s; + uint16_t id = 0; + + wolfIP_init(&s); + mock_link_init(&s); + s.dns_server = 0x08080808U; + s.last_tick = 100U; + test_rand_override_enabled = 1; + test_rand_override_value = 2U; + + /* Seed a cached query, then invalidate the socket so resend must reject it. */ + ck_assert_int_eq(dns_send_query(&s, "example.com", &id, DNS_A), 0); + ck_assert_uint_gt(s.dns_query_len, 0U); + s.dns_udp_sd = 0; + ck_assert_int_eq(dns_resend_query(&s), -1); + + test_rand_override_enabled = 0; +} +END_TEST + +START_TEST(test_dns_resend_query_fails_without_cached_query_buffer) +{ + struct wolfIP s; + uint16_t id = 0; + + wolfIP_init(&s); + mock_link_init(&s); + s.dns_server = 0x08080808U; + s.last_tick = 100U; + test_rand_override_enabled = 1; + test_rand_override_value = 3U; + + /* A live DNS socket alone is not enough; resend needs a cached query payload as well. */ + ck_assert_int_eq(dns_send_query(&s, "example.com", &id, DNS_A), 0); + ck_assert_int_gt(s.dns_udp_sd, 0); + s.dns_query_len = 0U; + ck_assert_int_eq(dns_resend_query(&s), -1); + + test_rand_override_enabled = 0; +} +END_TEST + START_TEST(test_dns_abort_query_clears_timer_and_query_state) { struct wolfIP s; @@ -3539,6 +3627,92 @@ START_TEST(test_dhcp_poll_offer_and_ack) } END_TEST +START_TEST(test_dhcp_poll_renewing_ack_binds_client) +{ + struct wolfIP s; + struct dhcp_msg msg; + struct tsocket *ts; + struct ipconf *primary; + uint32_t server_ip = 0x0A000001U; + uint32_t client_ip = 0x0A000064U; + uint32_t router_ip = 0x0A000002U; + uint32_t dns_ip = 0x08080808U; + uint32_t mask = 0xFFFFFF00U; + int ret; + + wolfIP_init(&s); + mock_link_init(&s); + primary = wolfIP_primary_ipconf(&s); + ck_assert_ptr_nonnull(primary); + s.dhcp_udp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + ck_assert_int_gt(s.dhcp_udp_sd, 0); + ts = &s.udpsockets[SOCKET_UNMARK(s.dhcp_udp_sd)]; + + /* Simulate a renewing client that already owns an address and receives a valid ACK. */ + s.last_tick = 1000U; + s.dhcp_state = DHCP_RENEWING; + s.dhcp_xid = 0x12345678U; + s.dhcp_server_ip = server_ip; + primary->ip = client_ip; + build_dhcp_ack_msg(&msg, server_ip, mask, router_ip, dns_ip); + msg.xid = ee32(s.dhcp_xid); + + enqueue_udp_rx(ts, &msg, sizeof(msg), DHCP_SERVER_PORT); + ret = dhcp_poll(&s); + + /* The poll dispatcher should route renewing traffic through ACK parsing to BOUND. */ + ck_assert_int_eq(ret, 0); + ck_assert_int_eq(s.dhcp_state, DHCP_BOUND); + ck_assert_uint_eq(primary->ip, client_ip); + ck_assert_uint_eq(primary->mask, mask); + ck_assert_uint_eq(primary->gw, router_ip); + ck_assert_uint_eq(s.dns_server, dns_ip); +} +END_TEST + +START_TEST(test_dhcp_poll_rebinding_ack_binds_client) +{ + struct wolfIP s; + struct dhcp_msg msg; + struct tsocket *ts; + struct ipconf *primary; + uint32_t server_ip = 0x0A000001U; + uint32_t client_ip = 0x0A000064U; + uint32_t router_ip = 0x0A000002U; + uint32_t dns_ip = 0x08080808U; + uint32_t mask = 0xFFFFFF00U; + int ret; + + wolfIP_init(&s); + mock_link_init(&s); + primary = wolfIP_primary_ipconf(&s); + ck_assert_ptr_nonnull(primary); + s.dhcp_udp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + ck_assert_int_gt(s.dhcp_udp_sd, 0); + ts = &s.udpsockets[SOCKET_UNMARK(s.dhcp_udp_sd)]; + + /* Rebinding uses the same ACK branch, but arrives after the server-specific renew phase. */ + s.last_tick = 1000U; + s.dhcp_state = DHCP_REBINDING; + s.dhcp_xid = 0xABCDEF01U; + s.dhcp_server_ip = server_ip; + primary->ip = client_ip; + build_dhcp_ack_msg(&msg, server_ip, mask, router_ip, dns_ip); + msg.xid = ee32(s.dhcp_xid); + + enqueue_udp_rx(ts, &msg, sizeof(msg), DHCP_SERVER_PORT); + ret = dhcp_poll(&s); + + /* Rebinding ACKs should also drive the client back to BOUND with refreshed config. */ + ck_assert_int_eq(ret, 0); + ck_assert_int_eq(s.dhcp_state, DHCP_BOUND); + ck_assert_uint_eq(primary->ip, client_ip); + ck_assert_uint_eq(primary->mask, mask); + ck_assert_uint_eq(primary->gw, router_ip); + ck_assert_uint_eq(s.dns_server, dns_ip); +} +END_TEST + START_TEST(test_dns_callback_ptr_response) { struct wolfIP s; @@ -4043,6 +4217,54 @@ START_TEST(test_dns_callback_wrong_id_ignored) } END_TEST +START_TEST(test_dns_callback_malformed_compressed_name_aborts_query) +{ + struct wolfIP s; + uint8_t response[128]; + int pos; + struct dns_header *hdr = (struct dns_header *)response; + struct dns_question *q; + + wolfIP_init(&s); + mock_link_init(&s); + s.dns_server = 0x0A000001U; + s.dns_query_type = DNS_QUERY_TYPE_A; + s.dns_id = 0x1234; + s.dns_lookup_cb = test_dns_lookup_cb; + dns_lookup_calls = 0; + dns_lookup_ip = 0; + s.dns_udp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + ck_assert_int_gt(s.dns_udp_sd, 0); + + memset(response, 0, sizeof(response)); + hdr->id = ee16(s.dns_id); + hdr->flags = ee16(0x8100); + hdr->qdcount = ee16(1); + hdr->ancount = ee16(1); + pos = sizeof(struct dns_header); + response[pos++] = 1; + response[pos++] = 'a'; + response[pos++] = 0; + q = (struct dns_question *)(response + pos); + q->qtype = ee16(DNS_A); + q->qclass = ee16(1); + pos += sizeof(struct dns_question); + + /* Truncate the compressed owner name so dns_skip_name() hits the abort path. */ + response[pos++] = 0xC0; + + enqueue_udp_rx(&s.udpsockets[SOCKET_UNMARK(s.dns_udp_sd)], response, (uint16_t)pos, DNS_PORT); + dns_callback(s.dns_udp_sd, CB_EVENT_READABLE, &s); + + /* Malformed compressed names must abort the active query and suppress callbacks. */ + ck_assert_int_eq(dns_lookup_calls, 0); + ck_assert_uint_eq(dns_lookup_ip, 0U); + ck_assert_uint_eq(s.dns_id, 0); + ck_assert_int_eq(s.dns_query_type, DNS_QUERY_TYPE_NONE); + ck_assert_ptr_eq(s.dns_lookup_cb, NULL); +} +END_TEST + START_TEST(test_dns_callback_abort_clears_query_state) { struct wolfIP s; diff --git a/src/test/unit/unit_tests_proto.c b/src/test/unit/unit_tests_proto.c index 872e692c..edbb1309 100644 --- a/src/test/unit/unit_tests_proto.c +++ b/src/test/unit/unit_tests_proto.c @@ -1438,6 +1438,57 @@ START_TEST(test_poll_tcp_zero_window_arms_persist) } END_TEST +START_TEST(test_tcp_persist_start_stops_when_window_reopens_or_no_unsent_payload) +{ + struct wolfIP s; + struct tsocket *ts; + struct pkt_desc *desc; + + 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.rto = 100; + 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, (TCP_FLAG_ACK | TCP_FLAG_PSH)), 0); + + /* Baseline: zero peer window plus unsent payload arms persist probing. */ + tcp_persist_start(ts, 1000); + ck_assert_uint_eq(ts->sock.tcp.persist_active, 1); + ck_assert_int_ne(ts->sock.tcp.tmr_persist, NO_TIMER); + + /* Reopening the advertised window should tear persist state back down. */ + ts->sock.tcp.persist_backoff = 3; + ts->sock.tcp.peer_rwnd = 32; + tcp_persist_start(ts, 1100); + 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); + + /* Closing the window again with the same queued data should re-arm persist. */ + ts->sock.tcp.peer_rwnd = 0; + tcp_persist_start(ts, 1200); + ck_assert_uint_eq(ts->sock.tcp.persist_active, 1); + ck_assert_int_ne(ts->sock.tcp.tmr_persist, NO_TIMER); + + /* Once the queued segment is no longer unsent payload, the guard must stop probing. */ + desc = fifo_peek(&ts->sock.tcp.txbuf); + ck_assert_ptr_nonnull(desc); + desc->flags |= PKT_FLAG_SENT; + ts->sock.tcp.persist_backoff = 5; + tcp_persist_start(ts, 1300); + 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_sends_one_byte_probe) { struct wolfIP s; @@ -1497,6 +1548,53 @@ START_TEST(test_tcp_persist_cb_sends_one_byte_probe) } END_TEST +START_TEST(test_tcp_zero_wnd_probe_selects_middle_byte_at_snd_una) +{ + struct wolfIP s; + struct tsocket *ts; + ip4 local_ip = 0x0A000001U; + ip4 remote_ip = 0x0A000002U; + uint8_t peer_mac[6] = {0x00, 0xaa, 0xbb, 0xcc, 0xdd, 0x45}; + 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; + fifo_init(&ts->sock.tcp.txbuf, ts->txmem, TXBUF_SIZE); + + /* snd_una points into the middle of this payload, so the probe must reuse that byte. */ + ck_assert_int_eq(enqueue_tcp_tx_with_payload(ts, payload, sizeof(payload), + (TCP_FLAG_ACK | TCP_FLAG_PSH)), 0); + ck_assert_int_eq(tcp_send_zero_wnd_probe(ts), 0); + + 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_initial_cwnd_caps_to_iw10_and_half_rwnd) { ck_assert_uint_eq(tcp_initial_cwnd(65535U, 1460U), 14600U); @@ -1554,6 +1652,191 @@ START_TEST(test_tcp_persist_probe_byte_matches_snd_una_offset) } END_TEST +START_TEST(test_tcp_zero_wnd_probe_arp_miss_requests_resolution) +{ + struct wolfIP s; + struct tsocket *ts; + struct arp_packet *arp; + ip4 local_ip = 0x0A000001U; + ip4 remote_ip = 0x0A000002U; + uint8_t payload[4] = {0x21, 0x22, 0x23, 0x24}; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, local_ip, 0xFFFFFF00U, 0); + wolfIP_filter_set_callback(NULL, NULL); + s.last_tick = 1000; + last_frame_sent_size = 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->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.snd_una = 10; + ts->sock.tcp.ack = 20; + fifo_init(&ts->sock.tcp.txbuf, ts->txmem, TXBUF_SIZE); + + /* With no ARP cache entry for the peer, probing should trigger resolution instead of send. */ + ck_assert_int_eq(enqueue_tcp_tx_with_payload(ts, payload, sizeof(payload), + (TCP_FLAG_ACK | TCP_FLAG_PSH)), 0); + ck_assert_int_eq(tcp_send_zero_wnd_probe(ts), -1); + + ck_assert_uint_eq(last_frame_sent_size, sizeof(struct arp_packet)); + arp = (struct arp_packet *)last_frame_sent; + ck_assert_int_eq(arp->opcode, ee16(ARP_REQUEST)); + ck_assert_int_eq(arp->tip, ee32(remote_ip)); +} +END_TEST + +START_TEST(test_tcp_rto_cb_marks_snd_una_payload_for_retransmit) +{ + struct wolfIP s; + struct tsocket *ts; + struct pkt_desc *desc; + struct wolfIP_timer tmr; + uint32_t smss; + uint8_t payload[4] = {0x31, 0x32, 0x33, 0x34}; + + 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.seq = 100; + ts->sock.tcp.snd_una = 100; + ts->sock.tcp.rto = 100; + ts->sock.tcp.rto_backoff = 1; + ts->sock.tcp.bytes_in_flight = sizeof(payload); + ts->sock.tcp.cwnd = TCP_MSS * 4; + ts->sock.tcp.ssthresh = TCP_MSS * 8; + ts->sock.tcp.peer_sack_count = 2; + fifo_init(&ts->sock.tcp.txbuf, ts->txmem, TXBUF_SIZE); + + /* Seed one sent payload segment so data-RTO has a retransmission candidate. */ + ck_assert_int_eq(enqueue_tcp_tx_with_payload(ts, payload, sizeof(payload), + (TCP_FLAG_ACK | TCP_FLAG_PSH)), 0); + desc = fifo_peek(&ts->sock.tcp.txbuf); + ck_assert_ptr_nonnull(desc); + desc->flags |= PKT_FLAG_SENT; + + memset(&tmr, 0, sizeof(tmr)); + tmr.cb = test_timer_cb; + tmr.expires = 100; + ts->sock.tcp.tmr_rto = timers_binheap_insert(&s.timers, tmr); + ck_assert_int_ne(ts->sock.tcp.tmr_rto, NO_TIMER); + + /* This callback should discard stale SACK state and retransmit from snd_una. */ + s.last_tick = 500; + tcp_rto_cb(ts); + + smss = tcp_cc_mss(ts); + /* RTO recovery should restart from one MSS and leave the segment pending retransmit. */ + ck_assert_uint_eq(ts->sock.tcp.peer_sack_count, 0); + 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.bytes_in_flight, 0); + ck_assert_uint_eq(ts->sock.tcp.rto_backoff, 2); + ck_assert_uint_eq(ts->sock.tcp.cwnd, smss); + ck_assert_uint_eq(ts->sock.tcp.ssthresh, 2 * smss); + ck_assert_int_ne(ts->sock.tcp.tmr_rto, NO_TIMER); +} +END_TEST + +START_TEST(test_tcp_rto_cb_clears_bookkeeping_when_no_payload_pending) +{ + 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.rto = 100; + ts->sock.tcp.rto_backoff = 3; + ts->sock.tcp.bytes_in_flight = 64; + ts->sock.tcp.peer_sack_count = 1; + fifo_init(&ts->sock.tcp.txbuf, ts->txmem, TXBUF_SIZE); + + memset(&tmr, 0, sizeof(tmr)); + tmr.cb = test_timer_cb; + tmr.expires = 100; + ts->sock.tcp.tmr_rto = timers_binheap_insert(&s.timers, tmr); + ck_assert_int_ne(ts->sock.tcp.tmr_rto, NO_TIMER); + + /* If bookkeeping says bytes are in flight but no payload is queued, recover by clearing state. */ + tcp_rto_cb(ts); + + /* No retransmission should be armed in this recovery-only path. */ + ck_assert_uint_eq(ts->sock.tcp.peer_sack_count, 0); + ck_assert_uint_eq(ts->sock.tcp.bytes_in_flight, 0); + ck_assert_uint_eq(ts->sock.tcp.rto_backoff, 0); + ck_assert_int_eq(ts->sock.tcp.tmr_rto, NO_TIMER); + ck_assert_int_eq(ts->sock.tcp.state, TCP_ESTABLISHED); + ck_assert_int_ne(ts->events & CB_EVENT_WRITABLE, 0); +} +END_TEST + +START_TEST(test_tcp_rto_cb_closes_socket_when_backoff_exhausted) +{ + struct wolfIP s; + struct tsocket *ts; + struct pkt_desc *desc; + struct wolfIP_timer tmr; + uint8_t payload[4] = {0x41, 0x42, 0x43, 0x44}; + + 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.seq = 200; + ts->sock.tcp.snd_una = 200; + ts->sock.tcp.rto = 100; + ts->sock.tcp.rto_backoff = TCP_RTO_MAX_BACKOFF; + ts->sock.tcp.bytes_in_flight = sizeof(payload); + fifo_init(&ts->sock.tcp.txbuf, ts->txmem, TXBUF_SIZE); + + /* Present one outstanding payload so the callback takes the data-RTO close path. */ + ck_assert_int_eq(enqueue_tcp_tx_with_payload(ts, payload, sizeof(payload), + (TCP_FLAG_ACK | TCP_FLAG_PSH)), 0); + desc = fifo_peek(&ts->sock.tcp.txbuf); + ck_assert_ptr_nonnull(desc); + desc->flags |= PKT_FLAG_SENT; + + memset(&tmr, 0, sizeof(tmr)); + tmr.cb = test_timer_cb; + tmr.expires = 100; + ts->sock.tcp.tmr_rto = timers_binheap_insert(&s.timers, tmr); + ck_assert_int_ne(ts->sock.tcp.tmr_rto, NO_TIMER); + + /* Once data-RTO backoff is exhausted, the callback should abandon the TCP socket. */ + tcp_rto_cb(ts); + + /* close_socket() zeros the descriptor, so the original TCP identity should be gone. */ + ck_assert_int_eq(ts->proto, 0); + ck_assert_int_eq(ts->sock.tcp.state, 0); + ck_assert_ptr_eq(ts->S, NULL); +} +END_TEST + START_TEST(test_tcp_input_window_reopen_stops_persist) { struct wolfIP s; From 724e85d21ceeb2ce3014f026d25a445182348393 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Fri, 27 Mar 2026 13:57:42 +0100 Subject: [PATCH 2/3] More unit tests to cover argument validations, options parsing etc. --- src/test/unit/unit.c | 22 +++ src/test/unit/unit_tests_api.c | 26 +++ src/test/unit/unit_tests_dns_dhcp.c | 6 + src/test/unit/unit_tests_proto.c | 290 ++++++++++++++++++++++++++++ src/test/unit/unit_tests_tcp_ack.c | 44 +++++ src/test/unit/unit_tests_tcp_flow.c | 252 ++++++++++++++++++++++++ 6 files changed, 640 insertions(+) diff --git a/src/test/unit/unit.c b/src/test/unit/unit.c index 6cb07594..d786923a 100644 --- a/src/test/unit/unit.c +++ b/src/test/unit/unit.c @@ -106,6 +106,8 @@ Suite *wolf_suite(void) tcase_add_test(tc_utils, test_is_timer_expired_skips_zero_head); tcase_add_test(tc_utils, test_wolfip_getdev_ex_api); tcase_add_test(tc_utils, test_wolfip_ll_frame_mtu_enforces_minimum); + tcase_add_test(tc_utils, test_transport_capacity_helpers_cover_guard_paths); + tcase_add_test(tc_utils, test_wolfip_if_for_local_ip_single_interface_falls_back_to_zero); tcase_add_test(tc_utils, test_wolfip_mtu_set_get_api); tcase_add_test(tc_utils, test_wolfip_ll_at_and_ipconf_at_invalid); tcase_add_test(tc_utils, test_ip_is_local_conf_variants); @@ -114,7 +116,12 @@ Suite *wolf_suite(void) tcase_add_test(tc_utils, test_wolfip_loopback_send_paths); tcase_add_test(tc_utils, test_wolfip_loopback_send_drops_oversize); tcase_add_test(tc_utils, test_wolfip_loopback_send_null_container); + tcase_add_test(tc_utils, test_wolfip_loopback_send_rejects_null_args); #endif + 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_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_wolfip_ipconfig_ex_per_interface); tcase_add_test(tc_utils, test_wolfip_poll_executes_timers_and_callbacks); tcase_add_test(tc_utils, test_wolfip_poll_drains_all_expired_timers_in_one_pass); @@ -126,6 +133,7 @@ Suite *wolf_suite(void) 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); + tcase_add_test(tc_utils, test_filter_socket_event_null_socket_uses_primary_defaults); tcase_add_test(tc_utils, test_filter_socket_event_proto_variants); tcase_add_test(tc_utils, test_filter_setters_and_get_mask); tcase_add_test(tc_utils, test_sock_socket_errors); @@ -285,8 +293,11 @@ 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_helpers_ignore_non_tcp_and_null_inputs); tcase_add_test(tc_utils, test_tcp_initial_cwnd_caps_to_iw10_and_half_rwnd); tcase_add_test(tc_utils, test_tcp_persist_cb_sends_one_byte_probe); + tcase_add_test(tc_utils, test_tcp_zero_wnd_probe_rejects_invalid_inputs_and_empty_payload); + tcase_add_test(tc_utils, test_tcp_zero_wnd_probe_skips_ack_only_segment); tcase_add_test(tc_utils, test_tcp_zero_wnd_probe_selects_middle_byte_at_snd_una); tcase_add_test(tc_utils, test_tcp_persist_probe_byte_matches_snd_una_offset); tcase_add_test(tc_utils, test_tcp_zero_wnd_probe_arp_miss_requests_resolution); @@ -370,6 +381,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_utils, test_dns_resend_query_uses_stored_query_buffer); tcase_add_test(tc_utils, test_dns_resend_query_fails_without_valid_socket); tcase_add_test(tc_utils, test_dns_resend_query_fails_without_cached_query_buffer); + tcase_add_test(tc_utils, test_dns_resend_query_fails_with_null_stack); 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); @@ -378,6 +390,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_utils, test_wolfip_ip_is_multicast_variants); tcase_add_test(tc_utils, test_wolfip_ip_is_broadcast_variants); tcase_add_test(tc_utils, test_wolfip_ip_is_broadcast_skips_unsuitable_configs); + tcase_add_test(tc_utils, test_wolfip_ip_is_broadcast_skips_zero_mask); 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); @@ -410,6 +423,13 @@ Suite *wolf_suite(void) tcase_add_test(tc_utils, test_tcp_input_syn_listen_does_not_scale_syn_window); tcase_add_test(tc_utils, test_tcp_input_syn_sent_does_not_scale_synack_window); tcase_add_test(tc_utils, test_tcp_parse_sack_wraparound_block_accepted); + tcase_add_test(tc_utils, test_tcp_parse_options_stops_on_truncated_or_invalid_option_length); + tcase_add_test(tc_utils, test_tcp_parse_options_returns_when_frame_has_no_option_bytes); + tcase_add_test(tc_utils, test_tcp_parse_options_parses_and_clamps_mixed_options); + tcase_add_test(tc_utils, test_tcp_parse_options_parses_mss_sack_permitted_timestamp_and_two_sack_blocks); + tcase_add_test(tc_utils, test_tcp_parse_options_ignores_unknown_option_kinds); + tcase_add_test(tc_utils, test_tcp_parse_options_caps_sack_block_count); + tcase_add_test(tc_utils, test_tcp_parse_options_ignores_known_kinds_with_wrong_lengths); tcase_add_test(tc_utils, test_tcp_input_rst_bad_seq_ignored); tcase_add_test(tc_utils, test_tcp_input_rst_seq_in_window_sends_ack); tcase_add_test(tc_utils, test_tcp_input_rst_seq_in_scaled_window_sends_ack); @@ -564,6 +584,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_proto, test_send_ttl_exceeded_ip_filter_drop); tcase_add_test(tc_proto, test_send_ttl_exceeded_eth_filter_drop); tcase_add_test(tc_proto, test_send_ttl_exceeded_no_send); + tcase_add_test(tc_proto, test_send_ttl_exceeded_non_ethernet_skips_eth_filter); #if WOLFIP_ENABLE_FORWARDING tcase_add_test(tc_proto, test_wolfip_forward_ttl_exceeded_short_len_does_not_send); #endif @@ -624,6 +645,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_proto, test_select_nexthop_variants); tcase_add_test(tc_proto, test_route_for_ip_variants); tcase_add_test(tc_proto, test_route_for_ip_dest_matches_iface_ip); + tcase_add_test(tc_proto, test_route_for_ip_matches_exact_ip_when_mask_is_zero); tcase_add_test(tc_proto, test_route_for_ip_no_primary_index); tcase_add_test(tc_proto, test_route_for_ip_null_stack); tcase_add_test(tc_proto, test_route_for_ip_gw_and_nonloop_fallback); diff --git a/src/test/unit/unit_tests_api.c b/src/test/unit/unit_tests_api.c index 531e9180..5b5aeb1d 100644 --- a/src/test/unit/unit_tests_api.c +++ b/src/test/unit/unit_tests_api.c @@ -275,6 +275,32 @@ START_TEST(test_filter_socket_event_unknown_proto) } END_TEST +START_TEST(test_filter_socket_event_null_socket_uses_primary_defaults) +{ + struct wolfIP s; + ip4 local_ip = 0x0A000001U; + ip4 remote_ip = 0x0A000002U; + + wolfIP_init(&s); + wolfIP_filter_set_callback(test_filter_cb, NULL); + wolfIP_filter_set_mask(WOLFIP_FILT_MASK(WOLFIP_FILT_CONNECTING)); + filter_cb_calls = 0; + memset(&filter_last_event, 0xA5, sizeof(filter_last_event)); + + (void)wolfIP_filter_notify_socket_event(WOLFIP_FILT_CONNECTING, &s, NULL, + local_ip, 1234, remote_ip, 4321); + + ck_assert_int_eq(filter_cb_calls, 1); + ck_assert_uint_eq(filter_last_event.if_idx, WOLFIP_PRIMARY_IF_IDX); + ck_assert_uint_eq(filter_last_event.meta.ip_proto, 0); + ck_assert_uint_eq(filter_last_event.meta.src_ip, ee32(local_ip)); + ck_assert_uint_eq(filter_last_event.meta.dst_ip, ee32(remote_ip)); + + wolfIP_filter_set_callback(NULL, NULL); + wolfIP_filter_set_mask(0); +} +END_TEST + START_TEST(test_filter_socket_event_proto_variants) { struct wolfIP s; diff --git a/src/test/unit/unit_tests_dns_dhcp.c b/src/test/unit/unit_tests_dns_dhcp.c index b33f6136..556a9ca0 100644 --- a/src/test/unit/unit_tests_dns_dhcp.c +++ b/src/test/unit/unit_tests_dns_dhcp.c @@ -2067,6 +2067,12 @@ START_TEST(test_dns_resend_query_fails_without_cached_query_buffer) } END_TEST +START_TEST(test_dns_resend_query_fails_with_null_stack) +{ + ck_assert_int_eq(dns_resend_query(NULL), -1); +} +END_TEST + START_TEST(test_dns_abort_query_clears_timer_and_query_state) { struct wolfIP s; diff --git a/src/test/unit/unit_tests_proto.c b/src/test/unit/unit_tests_proto.c index edbb1309..767344d3 100644 --- a/src/test/unit/unit_tests_proto.c +++ b/src/test/unit/unit_tests_proto.c @@ -1489,6 +1489,99 @@ START_TEST(test_tcp_persist_start_stops_when_window_reopens_or_no_unsent_payload } END_TEST +START_TEST(test_tcp_persist_helpers_ignore_non_tcp_and_null_inputs) +{ + struct wolfIP s; + struct tsocket ts; + + wolfIP_init(&s); + memset(&ts, 0, sizeof(ts)); + ts.S = &s; + ts.proto = WI_IPPROTO_UDP; + ts.sock.tcp.persist_active = 1; + ts.sock.tcp.persist_backoff = 3; + ts.sock.tcp.tmr_persist = 77; + + ck_assert_int_eq(tcp_has_pending_unsent_payload(NULL), 0); + tcp_persist_stop(NULL); + tcp_persist_start(NULL, 1); + tcp_persist_stop(&ts); + ck_assert_uint_eq(ts.sock.tcp.persist_active, 1); + ck_assert_uint_eq(ts.sock.tcp.persist_backoff, 3); + ck_assert_int_eq(ts.sock.tcp.tmr_persist, 77); + tcp_persist_start(&ts, 100); + ck_assert_uint_eq(ts.sock.tcp.persist_active, 1); + ck_assert_uint_eq(ts.sock.tcp.persist_backoff, 3); + ck_assert_int_eq(ts.sock.tcp.tmr_persist, 77); +} +END_TEST + +START_TEST(test_tcp_zero_wnd_probe_rejects_invalid_inputs_and_empty_payload) +{ + struct wolfIP s; + struct tsocket ts; + + wolfIP_init(&s); + memset(&ts, 0, sizeof(ts)); + ts.S = &s; + ts.proto = WI_IPPROTO_UDP; + fifo_init(&ts.sock.tcp.txbuf, ts.txmem, TXBUF_SIZE); + + ck_assert_int_eq(tcp_send_zero_wnd_probe(NULL), -1); + ck_assert_int_eq(tcp_send_zero_wnd_probe(&ts), -1); + + ts.proto = WI_IPPROTO_TCP; + ck_assert_int_eq(tcp_send_zero_wnd_probe(&ts), -1); +} +END_TEST + +START_TEST(test_tcp_zero_wnd_probe_skips_ack_only_segment) +{ + struct wolfIP s; + struct tsocket *ts; + ip4 local_ip = 0x0A000001U; + ip4 remote_ip = 0x0A000002U; + uint8_t peer_mac[6] = {0x00, 0xaa, 0xbb, 0xcc, 0xdd, 0x46}; + uint8_t payload[4] = {0x51, 0x52, 0x53, 0x54}; + 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 = 20; + ts->sock.tcp.snd_una = 20; + ts->sock.tcp.ack = 30; + fifo_init(&ts->sock.tcp.txbuf, ts->txmem, TXBUF_SIZE); + + ck_assert_int_eq(enqueue_tcp_tx(ts, 0, TCP_FLAG_ACK), 0); + ck_assert_int_eq(enqueue_tcp_tx_with_payload(ts, payload, sizeof(payload), + (TCP_FLAG_ACK | TCP_FLAG_PSH)), 0); + ck_assert_int_eq(tcp_send_zero_wnd_probe(ts), 0); + + ck_assert_uint_gt(last_frame_sent_size, 0); + tcp = (struct wolfIP_tcp_seg *)last_frame_sent; + ck_assert_uint_eq(ee32(tcp->seq), 20U); + ck_assert_uint_eq(tcp->data[0], payload[0]); +} +END_TEST + START_TEST(test_tcp_persist_cb_sends_one_byte_probe) { struct wolfIP s; @@ -2585,6 +2678,75 @@ START_TEST(test_wolfip_ll_frame_mtu_enforces_minimum) } END_TEST +START_TEST(test_transport_capacity_helpers_cover_guard_paths) +{ + struct wolfIP s; + struct wolfIP_ll_dev *ll; + struct tsocket ts; + + wolfIP_init(&s); + mock_link_init(&s); + ll = wolfIP_getdev_ex(&s, TEST_PRIMARY_IF); + ck_assert_ptr_nonnull(ll); + + ck_assert_int_eq(tx_has_writable_space(NULL), 0); + + memset(&ts, 0, sizeof(ts)); + ts.proto = WI_IPPROTO_ICMP; + fifo_init(&ts.sock.udp.txbuf, ts.txmem, TXBUF_SIZE); + ck_assert_int_ne(tx_has_writable_space(&ts), 0); + ts.proto = 0; + ck_assert_int_eq(tx_has_writable_space(&ts), 0); + + ck_assert_uint_eq(wolfIP_ll_frame_mtu(NULL), LINK_MTU); + ll->mtu = 0; + ck_assert_uint_eq(wolfIP_ll_frame_mtu(ll), LINK_MTU); + ll->mtu = LINK_MTU + 128U; + ck_assert_uint_eq(wolfIP_ll_frame_mtu(ll), LINK_MTU); + ll->mtu = 1U; + ck_assert_uint_eq(wolfIP_ll_frame_mtu(ll), LINK_MTU_MIN); + + ck_assert_uint_eq(wolfIP_socket_ip_mtu(NULL), 0U); + ck_assert_uint_eq(wolfIP_socket_tcp_mss(NULL), 0U); + ck_assert_uint_eq(tcp_cc_mss(NULL), TCP_MSS_MAX); + ck_assert_uint_eq(tcp_tx_payload_cap(NULL), 0U); + + memset(&ts, 0, sizeof(ts)); + ts.proto = WI_IPPROTO_TCP; + ck_assert_uint_eq(wolfIP_socket_ip_mtu(&ts), 0U); + ck_assert_uint_eq(wolfIP_socket_tcp_mss(&ts), 0U); + ck_assert_uint_eq(tcp_cc_mss(&ts), TCP_MSS_MAX); + ck_assert_uint_eq(tcp_tx_payload_cap(&ts), 0U); + + ts.S = &s; + ts.if_idx = TEST_PRIMARY_IF; + ll->mtu = LINK_MTU; + ts.sock.tcp.peer_mss = 1000U; + ck_assert_uint_eq(tcp_tx_payload_cap(&ts), 1000U); + ts.proto = WI_IPPROTO_UDP; + ck_assert_uint_eq(tcp_tx_payload_cap(&ts), wolfIP_socket_tcp_mss(&ts) - TCP_OPTIONS_LEN); + ts.proto = WI_IPPROTO_TCP; + ts.sock.tcp.peer_mss = 0U; + ck_assert_uint_eq(tcp_tx_payload_cap(&ts), wolfIP_socket_tcp_mss(&ts) - TCP_OPTIONS_LEN); + ll->mtu = ETH_HEADER_LEN + IP_HEADER_LEN + TCP_HEADER_LEN + TCP_OPTIONS_LEN; + ck_assert_uint_eq(tcp_tx_payload_cap(&ts), 0U); +} +END_TEST + +START_TEST(test_wolfip_if_for_local_ip_single_interface_falls_back_to_zero) +{ + struct wolfIP s; + int found = 7; + + wolfIP_init(&s); + mock_link_init(&s); + s.if_count = 1; + + ck_assert_uint_eq(wolfIP_if_for_local_ip(&s, IPADDR_ANY, &found), 0U); + ck_assert_int_eq(found, 0); +} +END_TEST + START_TEST(test_wolfip_mtu_set_get_api) { struct wolfIP s; @@ -2667,6 +2829,20 @@ START_TEST(test_wolfip_ip_is_broadcast_skips_unsuitable_configs) } END_TEST +START_TEST(test_wolfip_ip_is_broadcast_skips_zero_mask) +{ + struct wolfIP s; + + wolfIP_init(&s); + s.if_count = 1; + s.ipconf[0].ip = 0x0A000001U; + s.ipconf[0].mask = 0U; + + ck_assert_int_eq(wolfIP_ip_is_broadcast(&s, 0xFFFFFFFFU), 1); + ck_assert_int_eq(wolfIP_ip_is_broadcast(&s, 0x0A0000FFU), 0); +} +END_TEST + #if WOLFIP_ENABLE_LOOPBACK START_TEST(test_wolfip_loopback_defaults) { @@ -2745,8 +2921,122 @@ START_TEST(test_wolfip_loopback_send_null_container) ck_assert_int_eq(wolfIP_loopback_send(ll, frame, sizeof(frame)), -1); } END_TEST + +START_TEST(test_wolfip_loopback_send_rejects_null_args) +{ + struct wolfIP s; + struct wolfIP_ll_dev *loop; + uint8_t frame[4] = {0}; + + wolfIP_init(&s); + mock_link_init(&s); + loop = wolfIP_getdev_ex(&s, TEST_LOOPBACK_IF); + ck_assert_ptr_nonnull(loop); + + ck_assert_int_eq(wolfIP_loopback_send(NULL, frame, sizeof(frame)), -1); + ck_assert_int_eq(wolfIP_loopback_send(loop, NULL, sizeof(frame)), -1); +} +END_TEST #endif +START_TEST(test_wolfip_send_port_unreachable_ignores_missing_link_sender) +{ + struct wolfIP s; + struct wolfIP_ll_dev *ll; + struct wolfIP_ip_packet orig; + + wolfIP_init(&s); + mock_link_init(&s); + ll = wolfIP_getdev_ex(&s, TEST_PRIMARY_IF); + ck_assert_ptr_nonnull(ll); + memset(&orig, 0, sizeof(orig)); + last_frame_sent_size = 0; + + wolfIP_send_port_unreachable(&s, WOLFIP_MAX_INTERFACES, &orig); + ck_assert_uint_eq(last_frame_sent_size, 0U); + + ll->send = NULL; + wolfIP_send_port_unreachable(&s, TEST_PRIMARY_IF, &orig); + ck_assert_uint_eq(last_frame_sent_size, 0U); +} +END_TEST + +START_TEST(test_wolfip_send_port_unreachable_non_ethernet_skips_eth_filter) +{ + struct wolfIP s; + struct wolfIP_ip_packet orig; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + s.ll_dev[TEST_PRIMARY_IF].non_ethernet = 1; + filter_block_reason = WOLFIP_FILT_SENDING; + wolfIP_filter_set_callback(test_filter_cb_block, NULL); + wolfIP_filter_set_eth_mask(WOLFIP_FILT_MASK(WOLFIP_FILT_SENDING)); + wolfIP_filter_set_icmp_mask(0); + wolfIP_filter_set_ip_mask(0); + last_frame_sent_size = 0; + + memset(&orig, 0, sizeof(orig)); + orig.src = ee32(0x0A000002U); + + wolfIP_send_port_unreachable(&s, TEST_PRIMARY_IF, &orig); + ck_assert_uint_eq(last_frame_sent_size, + sizeof(struct wolfIP_icmp_dest_unreachable_packet) - ETH_HEADER_LEN); + + wolfIP_filter_set_callback(NULL, NULL); + wolfIP_filter_set_eth_mask(0); +} +END_TEST + +START_TEST(test_tcp_adv_win_clamps_and_applies_window_scale) +{ + struct tsocket ts; + uint32_t space; + + memset(&ts, 0, sizeof(ts)); + queue_init(&ts.sock.tcp.rxbuf, ts.rxmem, RXBUF_SIZE, 0); + ts.sock.tcp.rxbuf.size = 70000U; + ts.sock.tcp.ws_enabled = 1; + ts.sock.tcp.rcv_wscale = 1; + space = queue_space((struct queue *)&ts.sock.tcp.rxbuf); + + ck_assert_uint_eq(tcp_adv_win(&ts, 0), 0xFFFFU); + ck_assert_uint_eq(tcp_adv_win(&ts, 1), (uint16_t)(space >> 1)); +} +END_TEST + +START_TEST(test_tcp_segment_acceptable_zero_window_and_overlap_cases) +{ + struct tsocket ts; + struct wolfIP_tcp_seg seg; + uint32_t wnd; + + memset(&ts, 0, sizeof(ts)); + memset(&seg, 0, sizeof(seg)); + queue_init(&ts.sock.tcp.rxbuf, ts.rxmem, RXBUF_SIZE, 100U); + ts.sock.tcp.ack = 100U; + + ts.sock.tcp.rxbuf.size = 0U; + seg.seq = ee32(100U); + ck_assert_int_eq(tcp_segment_acceptable(&ts, &seg, 0U), 1); + seg.seq = ee32(101U); + ck_assert_int_eq(tcp_segment_acceptable(&ts, &seg, 0U), 0); + seg.seq = ee32(100U); + ck_assert_int_eq(tcp_segment_acceptable(&ts, &seg, 1U), 0); + + ts.sock.tcp.rxbuf.size = RXBUF_SIZE; + wnd = queue_space((struct queue *)&ts.sock.tcp.rxbuf); + ts.sock.tcp.ack = 100U; + seg.seq = ee32(100U + wnd - 1U); + ck_assert_int_eq(tcp_segment_acceptable(&ts, &seg, 2U), 1); + seg.seq = ee32(99U); + ck_assert_int_eq(tcp_segment_acceptable(&ts, &seg, 2U), 1); + seg.seq = ee32(100U + wnd); + ck_assert_int_eq(tcp_segment_acceptable(&ts, &seg, 2U), 0); +} +END_TEST + START_TEST(test_wolfip_ipconfig_ex_per_interface) { struct wolfIP s; diff --git a/src/test/unit/unit_tests_tcp_ack.c b/src/test/unit/unit_tests_tcp_ack.c index 102ec416..00238bac 100644 --- a/src/test/unit/unit_tests_tcp_ack.c +++ b/src/test/unit/unit_tests_tcp_ack.c @@ -1081,6 +1081,19 @@ START_TEST(test_route_for_ip_dest_matches_iface_ip) } END_TEST +START_TEST(test_route_for_ip_matches_exact_ip_when_mask_is_zero) +{ + struct wolfIP s; + ip4 primary_ip = 0x0A000001U; + ip4 secondary_ip = 0xC0A80101U; + + setup_stack_with_two_ifaces(&s, primary_ip, secondary_ip); + s.ipconf[TEST_SECOND_IF].mask = 0U; + + ck_assert_uint_eq(wolfIP_route_for_ip(&s, secondary_ip), TEST_SECOND_IF); +} +END_TEST + START_TEST(test_route_for_ip_gw_and_nonloop_fallback) { struct wolfIP s; @@ -2160,6 +2173,37 @@ START_TEST(test_send_ttl_exceeded_no_send) } END_TEST +START_TEST(test_send_ttl_exceeded_non_ethernet_skips_eth_filter) +{ + struct wolfIP s; + uint8_t ip_buf[ETH_HEADER_LEN + TTL_EXCEEDED_ORIG_PACKET_SIZE]; + struct wolfIP_ip_packet *ip = (struct wolfIP_ip_packet *)ip_buf; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + s.ll_dev[TEST_PRIMARY_IF].non_ethernet = 1; + filter_block_reason = WOLFIP_FILT_SENDING; + wolfIP_filter_set_callback(test_filter_cb_block, NULL); + wolfIP_filter_set_eth_mask(WOLFIP_FILT_MASK(WOLFIP_FILT_SENDING)); + wolfIP_filter_set_icmp_mask(0); + wolfIP_filter_set_ip_mask(0); + last_frame_sent_size = 0; + + memset(ip_buf, 0, sizeof(ip_buf)); + memcpy(ip->eth.src, "\x01\x02\x03\x04\x05\x06", 6); + ip->src = ee32(0x0A000002U); + ip->dst = ee32(0x0A000001U); + + wolfIP_send_ttl_exceeded(&s, TEST_PRIMARY_IF, ip); + ck_assert_uint_eq(last_frame_sent_size, + sizeof(struct wolfIP_icmp_ttl_exceeded_packet) - ETH_HEADER_LEN); + + wolfIP_filter_set_callback(NULL, NULL); + wolfIP_filter_set_eth_mask(0); +} +END_TEST + #if WOLFIP_ENABLE_FORWARDING START_TEST(test_wolfip_forward_ttl_exceeded_short_len_does_not_send) { diff --git a/src/test/unit/unit_tests_tcp_flow.c b/src/test/unit/unit_tests_tcp_flow.c index 01f4f1ff..e9244826 100644 --- a/src/test/unit/unit_tests_tcp_flow.c +++ b/src/test/unit/unit_tests_tcp_flow.c @@ -710,6 +710,258 @@ START_TEST(test_tcp_parse_sack_wraparound_block_accepted) } END_TEST +START_TEST(test_tcp_parse_options_stops_on_truncated_or_invalid_option_length) +{ + uint8_t seg_buf[sizeof(struct wolfIP_tcp_seg) + 4]; + struct wolfIP_tcp_seg *seg = (struct wolfIP_tcp_seg *)seg_buf; + struct tcp_parsed_opts po; + uint32_t frame_len; + + memset(seg_buf, 0, sizeof(seg_buf)); + seg->hlen = (uint8_t)((TCP_HEADER_LEN + 1) << 2); + seg->data[0] = TCP_OPTION_WS; + frame_len = ETH_HEADER_LEN + IP_HEADER_LEN + TCP_HEADER_LEN + 1; + tcp_parse_options(seg, frame_len, &po); + ck_assert_int_eq(po.ws_found, 0); + ck_assert_int_eq(po.mss_found, 0); + + memset(seg_buf, 0, sizeof(seg_buf)); + seg->hlen = (uint8_t)((TCP_HEADER_LEN + 2) << 2); + seg->data[0] = TCP_OPTION_WS; + seg->data[1] = 1; + frame_len = ETH_HEADER_LEN + IP_HEADER_LEN + TCP_HEADER_LEN + 2; + tcp_parse_options(seg, frame_len, &po); + ck_assert_int_eq(po.ws_found, 0); + ck_assert_int_eq(po.mss_found, 0); +} +END_TEST + +START_TEST(test_tcp_parse_options_returns_when_frame_has_no_option_bytes) +{ + uint8_t seg_buf[sizeof(struct wolfIP_tcp_seg) + 4]; + struct wolfIP_tcp_seg *seg = (struct wolfIP_tcp_seg *)seg_buf; + struct tcp_parsed_opts po; + + memset(seg_buf, 0, sizeof(seg_buf)); + seg->hlen = (uint8_t)((TCP_HEADER_LEN + 4) << 2); + seg->data[0] = TCP_OPTION_WS; + seg->data[1] = TCP_OPTION_WS_LEN; + seg->data[2] = 4; + + tcp_parse_options(seg, sizeof(struct wolfIP_tcp_seg), &po); + + ck_assert_int_eq(po.ws_found, 0); + ck_assert_int_eq(po.ts_found, 0); + ck_assert_int_eq(po.sack_count, 0); +} +END_TEST + +START_TEST(test_tcp_parse_options_parses_and_clamps_mixed_options) +{ + uint8_t seg_buf[sizeof(struct wolfIP_tcp_seg) + 32]; + struct wolfIP_tcp_seg *seg = (struct wolfIP_tcp_seg *)seg_buf; + struct tcp_parsed_opts po; + uint8_t *opt; + uint32_t ts_val = 0x01020304U; + uint32_t ts_ecr = 0x05060708U; + uint32_t frame_len; + + memset(seg_buf, 0, sizeof(seg_buf)); + seg->hlen = (uint8_t)((TCP_HEADER_LEN + 30) << 2); + opt = seg->data; + opt[0] = TCP_OPTION_WS; + opt[1] = TCP_OPTION_WS_LEN; + opt[2] = 20; + opt += 3; + opt[0] = TCP_OPTION_MSS; + opt[1] = TCP_OPTION_MSS_LEN; + opt[2] = 0; + opt[3] = 0; + opt += 4; + opt[0] = TCP_OPTION_SACK_PERMITTED; + opt[1] = TCP_OPTION_SACK_PERMITTED_LEN; + opt += 2; + opt[0] = TCP_OPTION_TS; + opt[1] = TCP_OPTION_TS_LEN; + { + uint32_t be = ee32(ts_val); + memcpy(opt + 2, &be, sizeof(be)); + be = ee32(ts_ecr); + memcpy(opt + 6, &be, sizeof(be)); + } + opt += 10; + opt[0] = TCP_OPTION_SACK; + opt[1] = 10; + { + uint32_t left = ee32(100U); + uint32_t right = ee32(120U); + memcpy(opt + 2, &left, sizeof(left)); + memcpy(opt + 6, &right, sizeof(right)); + } + opt += 10; + opt[0] = TCP_OPTION_EOO; + frame_len = ETH_HEADER_LEN + IP_HEADER_LEN + TCP_HEADER_LEN + 30; + + tcp_parse_options(seg, frame_len, &po); + + ck_assert_int_eq(po.ws_found, 1); + ck_assert_uint_eq(po.ws_shift, 14); + ck_assert_int_eq(po.mss_found, 0); + ck_assert_int_eq(po.sack_permitted, 1); + ck_assert_int_eq(po.ts_found, 1); + ck_assert_uint_eq(po.ts_val, ts_val); + ck_assert_uint_eq(po.ts_ecr, ts_ecr); + ck_assert_int_eq(po.sack_count, 1); + ck_assert_uint_eq(po.sack[0].left, 100U); + ck_assert_uint_eq(po.sack[0].right, 120U); +} +END_TEST + +START_TEST(test_tcp_parse_options_parses_mss_sack_permitted_timestamp_and_two_sack_blocks) +{ + uint8_t seg_buf[sizeof(struct wolfIP_tcp_seg) + 40]; + struct wolfIP_tcp_seg *seg = (struct wolfIP_tcp_seg *)seg_buf; + struct tcp_parsed_opts po; + uint8_t *opt; + uint32_t frame_len; + + memset(seg_buf, 0, sizeof(seg_buf)); + seg->hlen = (uint8_t)((TCP_HEADER_LEN + 34) << 2); + opt = seg->data; + opt[0] = TCP_OPTION_MSS; + opt[1] = TCP_OPTION_MSS_LEN; + opt[2] = 0x05; + opt[3] = 0xB4; + opt += 4; + opt[0] = TCP_OPTION_SACK_PERMITTED; + opt[1] = TCP_OPTION_SACK_PERMITTED_LEN; + opt += 2; + opt[0] = TCP_OPTION_TS; + opt[1] = TCP_OPTION_TS_LEN; + { + uint32_t be = ee32(0x11121314U); + memcpy(opt + 2, &be, sizeof(be)); + be = ee32(0x21222324U); + memcpy(opt + 6, &be, sizeof(be)); + } + opt += 10; + opt[0] = TCP_OPTION_SACK; + opt[1] = 18; + { + uint32_t left = ee32(100U), right = ee32(120U); + memcpy(opt + 2, &left, sizeof(left)); + memcpy(opt + 6, &right, sizeof(right)); + left = ee32(140U); + right = ee32(160U); + memcpy(opt + 10, &left, sizeof(left)); + memcpy(opt + 14, &right, sizeof(right)); + } + frame_len = ETH_HEADER_LEN + IP_HEADER_LEN + TCP_HEADER_LEN + 34; + + tcp_parse_options(seg, frame_len, &po); + + ck_assert_int_eq(po.mss_found, 1); + ck_assert_uint_eq(po.mss, 1460U); + ck_assert_int_eq(po.sack_permitted, 1); + ck_assert_int_eq(po.ts_found, 1); + ck_assert_uint_eq(po.ts_val, 0x11121314U); + ck_assert_uint_eq(po.ts_ecr, 0x21222324U); + ck_assert_int_eq(po.sack_count, 2); + ck_assert_uint_eq(po.sack[0].left, 100U); + ck_assert_uint_eq(po.sack[0].right, 120U); + ck_assert_uint_eq(po.sack[1].left, 140U); + ck_assert_uint_eq(po.sack[1].right, 160U); +} +END_TEST + +START_TEST(test_tcp_parse_options_ignores_unknown_option_kinds) +{ + uint8_t seg_buf[sizeof(struct wolfIP_tcp_seg) + 8]; + struct wolfIP_tcp_seg *seg = (struct wolfIP_tcp_seg *)seg_buf; + struct tcp_parsed_opts po; + uint32_t frame_len; + + memset(seg_buf, 0, sizeof(seg_buf)); + seg->hlen = (uint8_t)((TCP_HEADER_LEN + 4) << 2); + seg->data[0] = 99; + seg->data[1] = 4; + seg->data[2] = 0xAA; + seg->data[3] = 0xBB; + frame_len = ETH_HEADER_LEN + IP_HEADER_LEN + TCP_HEADER_LEN + 4; + + tcp_parse_options(seg, frame_len, &po); + + ck_assert_int_eq(po.ws_found, 0); + ck_assert_int_eq(po.mss_found, 0); + ck_assert_int_eq(po.sack_permitted, 0); + ck_assert_int_eq(po.ts_found, 0); + ck_assert_int_eq(po.sack_count, 0); +} +END_TEST + +START_TEST(test_tcp_parse_options_caps_sack_block_count) +{ + uint8_t seg_buf[sizeof(struct wolfIP_tcp_seg) + 48]; + struct wolfIP_tcp_seg *seg = (struct wolfIP_tcp_seg *)seg_buf; + struct tcp_parsed_opts po; + uint8_t *opt; + uint32_t frame_len; + int i; + + memset(seg_buf, 0, sizeof(seg_buf)); + seg->hlen = (uint8_t)((TCP_HEADER_LEN + 42) << 2); + opt = seg->data; + opt[0] = TCP_OPTION_SACK; + opt[1] = 42; + for (i = 0; i < 5; i++) { + uint32_t left = ee32((uint32_t)(100 + (i * 20))); + uint32_t right = ee32((uint32_t)(110 + (i * 20))); + memcpy(opt + 2 + (i * 8), &left, sizeof(left)); + memcpy(opt + 2 + (i * 8) + 4, &right, sizeof(right)); + } + frame_len = ETH_HEADER_LEN + IP_HEADER_LEN + TCP_HEADER_LEN + 42; + + tcp_parse_options(seg, frame_len, &po); + + ck_assert_int_eq(po.sack_count, TCP_SACK_MAX_BLOCKS); + ck_assert_uint_eq(po.sack[0].left, 100U); + ck_assert_uint_eq(po.sack[TCP_SACK_MAX_BLOCKS - 1].right, 170U); +} +END_TEST + +START_TEST(test_tcp_parse_options_ignores_known_kinds_with_wrong_lengths) +{ + uint8_t seg_buf[sizeof(struct wolfIP_tcp_seg) + 20]; + struct wolfIP_tcp_seg *seg = (struct wolfIP_tcp_seg *)seg_buf; + struct tcp_parsed_opts po; + uint8_t *opt; + uint32_t frame_len; + + memset(seg_buf, 0, sizeof(seg_buf)); + seg->hlen = (uint8_t)((TCP_HEADER_LEN + 14) << 2); + opt = seg->data; + opt[0] = TCP_OPTION_MSS; + opt[1] = 3; + opt[2] = 0x05; + opt += 3; + opt[0] = TCP_OPTION_SACK_PERMITTED; + opt[1] = 3; + opt[2] = 0x00; + opt += 3; + opt[0] = TCP_OPTION_TS; + opt[1] = 8; + memset(opt + 2, 0, 6); + frame_len = ETH_HEADER_LEN + IP_HEADER_LEN + TCP_HEADER_LEN + 14; + + tcp_parse_options(seg, frame_len, &po); + + ck_assert_int_eq(po.mss_found, 0); + ck_assert_int_eq(po.sack_permitted, 0); + ck_assert_int_eq(po.ts_found, 0); + ck_assert_int_eq(po.sack_count, 0); +} +END_TEST + START_TEST(test_tcp_input_rst_bad_seq_ignored) { struct wolfIP s; From c79737c8cfb470e5040f8a7c2a27215d0e384526 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Fri, 27 Mar 2026 14:41:10 +0100 Subject: [PATCH 3/3] Fixed buffer overflow in test --- src/test/unit/unit_tests_proto.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/test/unit/unit_tests_proto.c b/src/test/unit/unit_tests_proto.c index 767344d3..ef493083 100644 --- a/src/test/unit/unit_tests_proto.c +++ b/src/test/unit/unit_tests_proto.c @@ -2964,7 +2964,8 @@ END_TEST START_TEST(test_wolfip_send_port_unreachable_non_ethernet_skips_eth_filter) { struct wolfIP s; - struct wolfIP_ip_packet orig; + uint8_t orig_buf[ETH_HEADER_LEN + TTL_EXCEEDED_ORIG_PACKET_SIZE]; + struct wolfIP_ip_packet *orig = (struct wolfIP_ip_packet *)orig_buf; wolfIP_init(&s); mock_link_init(&s); @@ -2977,10 +2978,10 @@ START_TEST(test_wolfip_send_port_unreachable_non_ethernet_skips_eth_filter) wolfIP_filter_set_ip_mask(0); last_frame_sent_size = 0; - memset(&orig, 0, sizeof(orig)); - orig.src = ee32(0x0A000002U); + memset(orig_buf, 0, sizeof(orig_buf)); + orig->src = ee32(0x0A000002U); - wolfIP_send_port_unreachable(&s, TEST_PRIMARY_IF, &orig); + wolfIP_send_port_unreachable(&s, TEST_PRIMARY_IF, orig); ck_assert_uint_eq(last_frame_sent_size, sizeof(struct wolfIP_icmp_dest_unreachable_packet) - ETH_HEADER_LEN);