diff --git a/src/test/unit/unit.c b/src/test/unit/unit.c index b6a6c7d5..e0aed297 100644 --- a/src/test/unit/unit.c +++ b/src/test/unit/unit.c @@ -157,6 +157,7 @@ Suite *wolf_suite(void) 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_tx_has_writable_space_icmp_accepts_minimal_packet); + tcase_add_test(tc_utils, test_tcp_tx_payload_cap_subtracts_ts_option_from_peer_mss); 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); @@ -175,14 +176,18 @@ Suite *wolf_suite(void) 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_recv_sws_avoidance_holds_window_edge); + tcase_add_test(tc_utils, test_tcp_adv_win_scaled_right_edge_does_not_shrink); 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); tcase_add_test(tc_utils, test_wolfip_ipconfig_ex_per_interface); + tcase_add_test(tc_utils, test_wolfip_route_lookup_directed_broadcast_uses_owning_interface); #if WOLFIP_ENABLE_FORWARDING tcase_add_test(tc_utils, test_wolfip_route_table_longest_prefix_match); tcase_add_test(tc_utils, test_wolfip_route_table_default_route_delete_and_fallback); tcase_add_test(tc_utils, test_wolfip_route_table_update_replaces_gateway_without_duplication); tcase_add_test(tc_utils, test_wolfip_route_table_connected_subnet_beats_broader_static_route); + tcase_add_test(tc_utils, test_wolfip_route_table_connected_subnet_beats_equal_prefix_first_static_route); #endif tcase_add_test(tc_utils, test_wolfip_dns_server_get_returns_value_and_validates_args); tcase_add_test(tc_utils, test_wolfip_poll_executes_timers_and_callbacks); @@ -317,6 +322,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_utils, test_multicast_igmp_query_refreshes_report); tcase_add_test(tc_utils, test_multicast_igmp_query_flood_coalesced); tcase_add_test(tc_utils, test_multicast_igmp_query_report_heap_full_rearmed_on_poll); + tcase_add_test(tc_utils, test_multicast_igmp_query_does_not_starve_other_timers); tcase_add_test(tc_utils, test_multicast_igmp_query_report_rearmed_after_tick_rollback); tcase_add_test(tc_utils, test_multicast_igmp_query_bad_checksum_dropped); tcase_add_test(tc_utils, test_multicast_igmp_query_spoofed_dropped); @@ -326,6 +332,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_utils, test_multicast_recv_rejects_short_frame); tcase_add_test(tc_utils, test_multicast_setsockopt_accepts_unaligned_mreq); tcase_add_test(tc_utils, test_multicast_getsockopt_ttl_loop_accepts_uint8); + tcase_add_test(tc_utils, test_multicast_icmp_sendto_uses_multicast_mac); #endif tcase_add_test(tc_utils, test_tcp_no_rst_for_broadcast_dst); tcase_add_test(tc_utils, test_tcp_no_rst_for_multicast_dst); @@ -494,6 +501,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_utils, test_dhcp_poll_reply_wrong_chaddr_rejected); tcase_add_test(tc_utils, test_dhcp_poll_offer_zero_yiaddr_rejected); tcase_add_test(tc_utils, test_dhcp_poll_offer_defers_commit_until_ack); + tcase_add_test(tc_utils, test_dhcp_callback_drains_queued_datagrams); tcase_add_test(tc_utils, test_regression_dhcp_nak_deconfigures_address_during_renew_and_rebind); tcase_add_test(tc_utils, test_dns_callback_ptr_response); tcase_add_test(tc_utils, test_udp_try_recv_short_frame); @@ -577,6 +585,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_initial_not_below_2s); 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); @@ -589,6 +598,7 @@ Suite *wolf_suite(void) 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_send_query_rejects_256_octet_encoded_name); 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, @@ -606,6 +616,7 @@ Suite *wolf_suite(void) 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_wolfip_ip_is_broadcast_skips_slash31); 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); @@ -679,6 +690,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_utils, test_sock_abort_queued_data_rst_at_snd_una); tcase_add_test(tc_utils, test_sock_abort_preaccept_listener_stops_listening); tcase_add_test(tc_utils, test_sock_abort_after_data_rto_rst_covers_sent_data); + tcase_add_test(tc_utils, test_tcp_flush_new_data_keeps_running_rto); tcase_add_test(tc_utils, test_sock_abort_fin_behind_unacked_data_covers_fin); tcase_add_test(tc_utils, test_sock_abort_requeued_fin_covers_fin); tcase_add_test(tc_utils, test_sock_abort_close_wait_resets_peer); @@ -706,6 +718,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_utils, test_tcp_listener_preaccept_revert_drains_connection_state); tcase_add_test(tc_utils, test_tcp_listener_preaccept_close_rto_retransmits_finack); tcase_add_test(tc_utils, test_tcp_fin_in_close_wait_does_not_advance_ack); + tcase_add_test(tc_utils, test_tcp_data_after_fin_in_close_wait_ignored); tcase_add_test(tc_utils, test_tcp_listener_revert_restores_option_baseline); 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); @@ -723,6 +736,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_utils, test_tcp_input_rst_exact_seq_closes); tcase_add_test(tc_utils, test_tcp_input_syn_listen_mismatch); tcase_add_test(tc_utils, test_tcp_input_syn_rcvd_ack_established); + tcase_add_test(tc_utils, test_tcp_input_handshake_after_lost_syn_uses_one_segment_cwnd); tcase_add_test(tc_utils, test_tcp_input_syn_rcvd_ack_invalid_ack_rejected); tcase_add_test(tc_utils, test_tcp_input_syn_rcvd_high_seq_valid_ack_establishes); tcase_add_test(tc_utils, test_tcp_input_syn_rcvd_high_seq_data_held_ooo); @@ -734,6 +748,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_utils, test_tcp_input_port_mismatch_skips_socket); tcase_add_test(tc_utils, test_tcp_input_remote_ip_mismatch_skips_socket); tcase_add_test(tc_utils, test_tcp_input_local_ip_mismatch_preserves_if_idx); + tcase_add_test(tc_utils, test_tcp_input_unacceptable_segment_preserves_if_idx_and_ttl); tcase_add_test(tc_utils, test_tcp_input_unmatched_ack_sends_rst); tcase_add_test(tc_utils, test_tcp_input_unmatched_ack_nonlocal_dst_does_not_send_rst); tcase_add_test(tc_utils, test_tcp_input_unmatched_syn_sends_rst_ack); @@ -783,6 +798,7 @@ Suite *wolf_suite(void) 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_ack_fin_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); @@ -824,6 +840,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_utils, test_tcp_recv_tracks_holes_and_sack_blocks); tcase_add_test(tc_utils, test_tcp_rebuild_rx_sack_right_edge_wraps); tcase_add_test(tc_utils, test_tcp_rebuild_rx_sack_triggering_block_first); + tcase_add_test(tc_utils, test_tcp_recv_ooo_table_full_does_not_sack_discarded_segment); tcase_add_test(tc_utils, test_tcp_consume_ooo_wrap_trim_and_promote); 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); @@ -850,6 +867,8 @@ Suite *wolf_suite(void) tcase_add_test(tc_utils, test_flush_tcp_tx_popped_tail_wrap_gap_no_phantom_frames); tcase_add_test(tc_utils, test_tcp_ack_parked_zero_desc_keeps_rtt_sample); tcase_add_test(tc_utils, test_flush_tcp_tx_sends_pure_ack_behind_window_blocked_data); + tcase_add_test(tc_utils, test_flush_tcp_tx_refreshes_timestamp_option); + tcase_add_test(tc_utils, test_flush_tcp_tx_idle_restart_window); tcase_add_test(tc_utils, test_flush_tcp_tx_fin_ack_stays_behind_window_blocked_data); tcase_add_test(tc_utils, test_flush_tcp_tx_fin_held_behind_blocked_data_after_ack); tcase_add_test(tc_utils, test_tcp_first_unsent_seq_skips_pending_retransmit); @@ -883,6 +902,8 @@ Suite *wolf_suite(void) tcase_add_test(tc_utils, test_tcp_input_fin_wait_1_fin_payload_out_of_order_no_transition); tcase_add_test(tc_utils, test_tcp_input_fin_wait_1_fin_payload_ack_mismatch_no_transition); tcase_add_test(tc_utils, test_tcp_input_header_len_below_min_dropped); + tcase_add_test(tc_utils, test_tcp_input_data_offset_exceeds_ip_len_dropped); + tcase_add_test(tc_utils, test_tcp_input_listen_data_offset_exceeds_ip_len_dropped); tcase_add_test(tc_utils, test_socket_from_fd_invalid); tcase_add_test(tc_utils, test_socket_from_fd_valid); tcase_add_test(tc_utils, test_sock_close_tcp_fin_wait_1_repeated_close_keeps_fin_wait_2_path); @@ -972,6 +993,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_proto, test_raw_socket_send_builds_ip_header); tcase_add_test(tc_proto, test_regression_raw_socket_send_ip_id_network_byte_order); tcase_add_test(tc_proto, test_regression_raw_socket_tx_eagain_keeps_descriptor); + tcase_add_test(tc_proto, test_regression_raw_socket_sendto_checks_interface_mtu); tcase_add_test(tc_proto, test_raw_socket_sendto_short_addrlen_returns_einval); tcase_add_test(tc_proto, test_raw_socket_sendto_wrong_family_returns_einval); tcase_add_test(tc_proto, test_raw_socket_sendto_payload_too_large_for_ip_header_returns_einval); @@ -1129,6 +1151,8 @@ Suite *wolf_suite(void) tcase_add_test(tc_proto, test_regression_fast_recovery_cwnd_ssthresh_rfc5681); 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_tcp_input_ts_no_rtt_sample_without_snd_una_advance); + tcase_add_test(tc_proto, test_tcp_input_ts_single_rtt_sample_per_ack); 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); @@ -1140,6 +1164,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_proto, test_regression_last_ack_rejects_out_of_window_segment); tcase_add_test(tc_proto, test_regression_dns_id_never_zero); tcase_add_test(tc_proto, test_tcp_input_listen_synack_sends_rst_and_stays_listen); + tcase_add_test(tc_proto, test_tcp_input_listen_ack_non_local_dst_no_rst); tcase_add_test(tc_proto, test_tcp_input_listen_accept_final_ack_does_not_send_rst); add_tftp_tests(tc_proto); @@ -1179,6 +1204,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_utils, test_ifaddr_sendto_from_an_alias_uses_it_as_source); tcase_add_test(tc_utils, test_ifaddr_wildcard_bind_receives_traffic_to_any_local_address); tcase_add_test(tc_utils, test_ifaddr_tcp_wildcard_listener_accepts_connections_to_an_alias); + tcase_add_test(tc_utils, test_ifaddr_arp_request_for_an_alias_is_answered); #endif #if WOLFIP_IPV6 tcase_add_test(tc_utils, test_ifaddr_v6_addresses_are_independent_of_v4); @@ -1301,6 +1327,8 @@ Suite *wolf_suite(void) tcase_add_test(tc_proto, test_nd_na_resolves_an_incomplete_entry); tcase_add_test(tc_proto, test_nd_na_without_target_lla_resolves_nothing); tcase_add_test(tc_proto, test_nd_na_without_override_may_not_replace_a_known_mac); + tcase_add_test(tc_proto, test_nd_stale_entry_is_probed_and_dropped_when_unanswered); + tcase_add_test(tc_proto, test_nd_probe_answered_makes_a_stale_entry_reachable); tcase_add_test(tc_proto, test_nd_messages_with_wrong_hop_limit_are_refused); tcase_add_test(tc_proto, test_nd_na_from_unspecified_source_is_refused); tcase_add_test(tc_proto, test_nd_na_for_a_foreign_destination_is_refused); @@ -1377,6 +1405,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_proto, test_sock6_stream_and_dgram_are_created); tcase_add_test(tc_proto, test_sock6_icmpv6_socket_requires_the_icmpv6_protocol); tcase_add_test(tc_proto, test_sock6_bind_and_getsockname_roundtrip); + tcase_add_test(tc_proto, test_sock6_bind_port_zero_allocates_ephemeral_port); tcase_add_test(tc_proto, test_sock6_bind_to_a_foreign_address_is_refused); tcase_add_test(tc_proto, test_sock6_wildcard_bind_reports_the_unspecified_address); tcase_add_test(tc_proto, test_sock6_v4_mapped_bind_is_reported_as_mapped); @@ -1412,10 +1441,12 @@ Suite *wolf_suite(void) tcase_add_test(tc_proto, test_sock6_tcp_listener_accepts_an_ipv6_connection); tcase_add_test(tc_proto, test_sock6_tcp_accept_short_addrlen_keeps_the_connection); tcase_add_test(tc_proto, test_sock6_tcp_mss_accounts_for_the_40_byte_header); + tcase_add_test(tc_proto, test_sock6_tcp_packet_too_big_lowers_peer_mss); tcase_add_test(tc_proto, test_sock6_tcp_segment_to_a_dead_port_is_reset); tcase_add_test(tc_proto, test_sock6_tcp_bad_checksum_is_dropped); tcase_add_test(tc_proto, test_sock6_tcp_af_inet_listener_ignores_ipv6); tcase_add_test(tc_proto, test_sock6_tcp_no_route_aborts_the_connection); + tcase_add_test(tc_proto, test_sock6_tcp_no_route_abort_of_established_does_not_recurse); /* ICMPv6: error messages (RFC 4443) and sockets. */ tcase_add_test(tc_proto, test_icmp6_udp_port_unreachable); @@ -1440,6 +1471,8 @@ Suite *wolf_suite(void) /* Family confusion on paths shared with IPv4. */ tcase_add_test(tc_proto, test_sock6_tcp_listener_reset_is_framed_as_ipv6); tcase_add_test(tc_proto, test_sock6_tcp_syn_sent_reset_is_framed_as_ipv6); + tcase_add_test(tc_proto, test_sock6_tcp_connect_timer_heap_full_rolls_back); + tcase_add_test(tc_proto, test_sock6_tcp_connect_keeps_bound_low_port); tcase_add_test(tc_proto, test_sock6_v6only_wildcard_receives_no_ipv4); tcase_add_test(tc_proto, test_icmp6_socket_never_receives_icmpv4); @@ -1605,6 +1638,8 @@ Suite *wolf_suite(void) tcase_add_test(tc_core, test_arp_recv_reply_overwrite_blocked_when_no_pending); tcase_add_test(tc_core, test_arp_request_rate_limited); tcase_add_test(tc_core, test_arp_request_first_not_rate_limited); + tcase_add_test(tc_core, test_arp_request_throttle_per_target); + tcase_add_test(tc_core, test_arp_request_full_pending_table_keeps_throttle); tcase_add_test(tc_core, test_arp_tick_restart_resets_rate_limit); #ifdef IP_MULTICAST tcase_add_test(tc_core, test_close_releases_multicast); @@ -1791,6 +1826,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_core, test_tcp_input_established_out_of_window_sends_ack); tcase_add_test(tc_core, test_tcp_input_established_no_ack_dropped); tcase_add_test(tc_core, test_tcp_input_established_ack_beyond_snd_nxt_keeps_txqueue); + tcase_add_test(tc_core, test_tcp_input_established_ack_of_queued_unsent_data_ignored); tcase_add_test(tc_core, test_tcp_input_established_ack_beyond_snd_nxt_sends_ack); tcase_add_test(tc_core, test_tcp_input_fin_wait_1_ack_beyond_snd_nxt_no_transition); tcase_add_test(tc_core, test_tcp_input_established_fin_ooo_no_close_wait); @@ -1901,6 +1937,7 @@ Suite *wolf_suite(void) #if WOLFIP_RAWSOCKETS tcase_add_test(tc_core, test_poll_tx_raw_sends_on_arp_hit); tcase_add_test(tc_core, test_poll_tx_raw_arp_miss_emits_request); + tcase_add_test(tc_core, test_poll_tx_raw_directed_broadcast_uses_bcast_mac); tcase_add_test(tc_core, test_poll_tx_raw_dst_zero_skips_descriptor); tcase_add_test(tc_core, test_poll_tx_raw_filter_blocks_send); tcase_add_test(tc_core, test_poll_tx_raw_loopback_path); @@ -1990,6 +2027,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_core, test_dhcp_parse_ack_dns_already_set_skipped); tcase_add_test(tc_core, test_dhcp_parse_ack_inner_pad_bytes_skipped); tcase_add_test(tc_core, test_dhcp_dad_probe_wire_format); + tcase_add_test(tc_core, test_dhcp_dad_complete_announces_address); tcase_add_test(tc_core, test_dhcp_dad_conflict_releases_and_rediscover); tcase_add_test(tc_core, test_dhcp_dad_request_claiming_candidate_conflict); tcase_add_test(tc_core, test_dhcp_dad_probe_for_candidate_conflict); @@ -2091,6 +2129,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_core, test_dns_callback_zero_ancount_no_delivery); tcase_add_test(tc_core, test_dns_callback_aaaa_answer_skipped_for_a_query); tcase_add_test(tc_core, test_dns_callback_qr_without_rd_is_accepted); + tcase_add_test(tc_core, test_dns_callback_drains_queued_datagrams); tcase_add_test(tc_core, test_dns_callback_truncated_answer_delivered); tcase_add_test(tc_core, test_dns_callback_rr_rdlen_truncated_aborts_query); tcase_add_test(tc_core, test_dns_callback_bad_question_name_aborts_query); @@ -2155,6 +2194,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_core, test_tx_has_writable_space_unknown_proto); tcase_add_test(tc_core, test_tx_has_writable_space_null); tcase_add_test(tc_core, test_bind_port_in_use_port_zero); + tcase_add_test(tc_core, test_bind_port_zero_allocates_ephemeral_port); tcase_add_test(tc_core, test_arp_pending_record_refresh_existing); tcase_add_test(tc_core, test_arp_pending_record_replaces_oldest); tcase_add_test(tc_core, test_arp_pending_match_and_clear_expires_stale); @@ -2256,7 +2296,14 @@ Suite *wolf_suite(void) tcase_add_test(tc_proto, test_vlan_rx_eth_filter_deny_subif_blocks_arp_poisoning); tcase_add_test(tc_proto, test_vlan_rx_unfiltered_arp_reply_learns_neighbor); tcase_add_test(tc_proto, test_vlan_mtu_inherited); + tcase_add_test(tc_proto, test_vlan_mtu_set_applies_to_subif); tcase_add_test(tc_proto, test_vlan_delete_purges_arp_neighbor_cache); +#if WOLFIP_IF_MULTICONF + tcase_add_test(tc_proto, test_vlan_delete_purges_ifaddr_aliases); +#endif +#if WOLFIP_IPV6 + tcase_add_test(tc_proto, test_vlan_delete_purges_nd6_state); +#endif #if WOLFIP_PACKET_SOCKETS tcase_add_test(tc_proto, test_vlan_packet_socket_parent_and_sub_delivery); tcase_add_test(tc_proto, test_vlan_packet_socket_wildcard_gets_tagged_once); @@ -2272,6 +2319,8 @@ Suite *wolf_suite(void) tcase_add_test(tc_proto, test_fwd_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_multicast_dest_ttl_expired_silent_drop); + tcase_add_test(tc_proto, test_fwd_multicast_dest_df_oversize_silent_drop); tcase_add_test(tc_proto, test_fwd_fragments_dropped_before_l4_filter); tcase_add_test(tc_proto, test_fwd_frag_offset_bit12_dropped); #endif /* WOLFIP_ENABLE_FORWARDING */ diff --git a/src/test/unit/unit_tests_arp_regression.c b/src/test/unit/unit_tests_arp_regression.c index b89d7a88..44c77874 100644 --- a/src/test/unit/unit_tests_arp_regression.c +++ b/src/test/unit/unit_tests_arp_regression.c @@ -522,6 +522,67 @@ START_TEST(test_arp_request_first_not_rate_limited) } END_TEST +START_TEST(test_arp_request_throttle_per_target) +{ + struct wolfIP s; + const ip4 dead = 0x0A000002U; + const ip4 live = 0x0A000003U; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + /* A request for an unresolvable neighbor goes out. */ + s.last_tick = 1000; + last_frame_sent_size = 0; + arp_request(&s, TEST_PRIMARY_IF, dead); + ck_assert_uint_eq(last_frame_sent_size, sizeof(struct arp_packet)); + + /* Shortly after, a request for a different target must not be starved + * by the dead neighbor's window; the dead one stays throttled. */ + s.last_tick = 1200; + last_frame_sent_size = 0; + arp_request(&s, TEST_PRIMARY_IF, dead); + ck_assert_uint_eq(last_frame_sent_size, 0U); + arp_request(&s, TEST_PRIMARY_IF, live); + ck_assert_uint_eq(last_frame_sent_size, sizeof(struct arp_packet)); + ck_assert_uint_eq(((struct arp_packet *)last_frame_sent)->tip, ee32(live)); +} +END_TEST + +/* With more unresolved targets than pending slots, an entry still inside + * its 1s window must not be evicted, or it loses its throttle. */ +START_TEST(test_arp_request_full_pending_table_keeps_throttle) +{ + struct wolfIP s; + int i; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + for (i = 0; i < WOLFIP_ARP_PENDING_MAX; i++) { + s.last_tick = 1000 + (uint64_t)i * (ARP_REQUEST_MIN_GAP_MS + 1); + last_frame_sent_size = 0; + arp_request(&s, TEST_PRIMARY_IF, 0x0A000010U + (ip4)i); + ck_assert_uint_eq(last_frame_sent_size, sizeof(struct arp_packet)); + } + /* Table full, every entry still throttled: one more target waits. */ + s.last_tick += ARP_REQUEST_MIN_GAP_MS + 1; + last_frame_sent_size = 0; + arp_request(&s, TEST_PRIMARY_IF, 0x0A000020U); + ck_assert_uint_eq(last_frame_sent_size, 0U); + /* The first target is still throttled too. */ + arp_request(&s, TEST_PRIMARY_IF, 0x0A000010U); + ck_assert_uint_eq(last_frame_sent_size, 0U); + + /* Once the oldest window expires, its slot can be reused. */ + s.last_tick = 2000; + arp_request(&s, TEST_PRIMARY_IF, 0x0A000020U); + ck_assert_uint_eq(last_frame_sent_size, sizeof(struct arp_packet)); +} +END_TEST + START_TEST(test_arp_tick_restart_resets_rate_limit) { struct wolfIP s; diff --git a/src/test/unit/unit_tests_branches.c b/src/test/unit/unit_tests_branches.c index 802309f6..bc766d89 100644 --- a/src/test/unit/unit_tests_branches.c +++ b/src/test/unit/unit_tests_branches.c @@ -2221,13 +2221,13 @@ START_TEST(test_arp_request_rate_limited) s.last_tick = 5000; arp_request(&s, TEST_PRIMARY_IF, 0x0A000002U); last_frame_sent_size = 0; - /* A second request within 1000 ticks is suppressed. */ + /* A second request for the same target within 1000 ticks is suppressed. */ s.last_tick = 5500; - arp_request(&s, TEST_PRIMARY_IF, 0x0A000003U); + arp_request(&s, TEST_PRIMARY_IF, 0x0A000002U); ck_assert_uint_eq(last_frame_sent_size, 0U); /* After the rate-limit window elapses, the request fires again. */ s.last_tick = 6500; - arp_request(&s, TEST_PRIMARY_IF, 0x0A000003U); + arp_request(&s, TEST_PRIMARY_IF, 0x0A000002U); ck_assert_uint_eq(last_frame_sent_size, sizeof(struct arp_packet)); } END_TEST diff --git a/src/test/unit/unit_tests_dhcp_edges.c b/src/test/unit/unit_tests_dhcp_edges.c index 7e23facc..3d616970 100644 --- a/src/test/unit/unit_tests_dhcp_edges.c +++ b/src/test/unit/unit_tests_dhcp_edges.c @@ -1818,6 +1818,44 @@ START_TEST(test_dhcp_dad_probe_wire_format) } END_TEST +/* RFC 2131 3.2: when DAD completes the client broadcasts an ARP reply + * announcing the new address (sender IP = target IP = leased address). */ +START_TEST(test_dhcp_dad_complete_announces_address) +{ + struct wolfIP s; + struct dhcp_msg msg; + struct wolfIP_ll_dev *ll; + struct arp_packet *arp; + struct ipconf *primary; + uint32_t server_ip = 0x0A000001U; + uint32_t client_ip = 0x0A000064U; + + wolfIP_init(&s); + mock_link_init(&s); + s.dhcp_xid = 0xDA05U; + s.dhcp_state = DHCP_REQUEST_SENT; + s.last_tick = 1000U; + primary = wolfIP_primary_ipconf(&s); + ck_assert_ptr_nonnull(primary); + primary->ip = client_ip; + + build_full_ack(&s, &msg, server_ip, client_ip, 0xFFFFFF00U, + server_ip, 0x08080808U, 120U); + ll = wolfIP_getdev_ex(&s, TEST_PRIMARY_IF); + ck_assert_ptr_nonnull(ll); + ck_assert_int_eq(dhcp_parse_ack(&s, &msg, sizeof(msg)), 0); + dhcp_test_complete_dad(&s); + + 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_REPLY)); + ck_assert_mem_eq(arp->eth.dst, "\xff\xff\xff\xff\xff\xff", 6); + ck_assert_int_eq(arp->sip, ee32(client_ip)); + ck_assert_int_eq(arp->tip, ee32(client_ip)); + ck_assert_mem_eq(arp->sma, ll->mac, 6); +} +END_TEST + /* A DAD probe answered by a host other than us is a conflict: the address * is released, a DECLINE is sent (when a DHCP socket exists), and the * client restarts at DISCOVER. */ @@ -2282,8 +2320,9 @@ START_TEST(test_dhcp_dad_probe_count_len_returning_driver) dhcp_test_complete_dad(&s); - /* RFC 4331: exactly 3 probes, then BOUND. */ - ck_assert_uint_eq(dad_len_send_count, DHCP_DAD_PROBES); + /* RFC 4331: exactly 3 probes, then BOUND, then one RFC 2131 ARP + * announcement of the verified address. */ + ck_assert_uint_eq(dad_len_send_count, DHCP_DAD_PROBES + 1U); } END_TEST @@ -2401,6 +2440,55 @@ START_TEST(test_dhcp_poll_reply_wrong_chaddr_rejected) } END_TEST +/* dispatch_events() clears the READABLE event before the callback runs and + * UDP recvfrom never raises it again, so two datagrams queued in one poll + * step produce a single event. The callback must drain the queue: a stale or + * foreign datagram ahead of the real OFFER may not strand it. */ +START_TEST(test_dhcp_callback_drains_queued_datagrams) +{ + struct wolfIP s; + struct dhcp_msg msg; + struct dhcp_option *opt; + struct tsocket *ts; + uint32_t xid = 0x12345678U; + + wolfIP_init(&s); + mock_link_init(&s); + ts = dhcp_edges_rx_socket(&s); + ck_assert_ptr_nonnull(ts); + s.dhcp_xid = xid; + + memset(&msg, 0, sizeof(msg)); + msg.op = BOOT_REPLY; + msg.hlen = 6; + msg.magic = ee32(DHCP_MAGIC); + msg.xid = ee32(xid); + msg.yiaddr = ee32(0x0A000064U); + opt = (struct dhcp_option *)msg.options; + opt->code = DHCP_OPTION_MSG_TYPE; + opt->len = 1; + opt->data[0] = DHCP_OFFER; + opt = (struct dhcp_option *)((uint8_t *)opt + 3); + opt->code = DHCP_OPTION_SERVER_ID; + opt->len = 4; + opt->data[0] = 0x0A; + opt->data[3] = 0x01; + opt = (struct dhcp_option *)((uint8_t *)opt + 6); + opt->code = DHCP_OPTION_END; + + s.dhcp_state = DHCP_DISCOVER_SENT; + /* First datagram: foreign chaddr, dropped. Second: the valid OFFER. */ + enqueue_udp_rx(ts, &msg, sizeof(msg), DHCP_SERVER_PORT); + memcpy(msg.chaddr, wolfIP_ll_at(&s, WOLFIP_PRIMARY_IF_IDX)->mac, 6); + enqueue_udp_rx(ts, &msg, sizeof(msg), DHCP_SERVER_PORT); + + dhcp_callback(s.dhcp_udp_sd, CB_EVENT_READABLE, &s); + ck_assert_int_eq(s.dhcp_state, DHCP_REQUEST_SENT); + ck_assert_uint_eq(s.dhcp_ip, 0x0A000064U); + ck_assert_ptr_null(fifo_peek(&ts->sock.udp.rxbuf)); +} +END_TEST + /* An OFFER with yiaddr 0.0.0.0 must be rejected: no broken configuration * may be committed to the interface. */ START_TEST(test_dhcp_poll_offer_zero_yiaddr_rejected) diff --git a/src/test/unit/unit_tests_dns_dhcp.c b/src/test/unit/unit_tests_dns_dhcp.c index 6fa08950..e8160459 100644 --- a/src/test/unit/unit_tests_dns_dhcp.c +++ b/src/test/unit/unit_tests_dns_dhcp.c @@ -3502,7 +3502,7 @@ START_TEST(test_dns_schedule_timer_initial_jitter_and_cancel) dns_schedule_timer(&s); ck_assert_int_ne(s.dns_timer, NO_TIMER); - ck_assert_uint_eq(find_timer_expiry(&s, s.dns_timer), 2290U); + ck_assert_uint_eq(find_timer_expiry(&s, s.dns_timer), 2490U); timer_id = s.dns_timer; dns_cancel_timer(&s); @@ -3518,6 +3518,25 @@ START_TEST(test_dns_schedule_timer_initial_jitter_and_cancel) } END_TEST +/* RFC 1035 s4.2.1: the first retransmission must not fire before 2 s. */ +START_TEST(test_dns_schedule_timer_initial_not_below_2s) +{ + struct wolfIP s; + + wolfIP_init(&s); + s.last_tick = 100U; + s.dns_retry_count = 0; + test_rand_override_enabled = 1; + test_rand_override_value = 0U; + + dns_schedule_timer(&s); + ck_assert_int_ne(s.dns_timer, NO_TIMER); + ck_assert_uint_ge(find_timer_expiry(&s, s.dns_timer), 100U + 2000U); + + test_rand_override_enabled = 0; +} +END_TEST + START_TEST(test_dns_schedule_timer_caps_retry_shift) { struct wolfIP s; @@ -3551,7 +3570,7 @@ START_TEST(test_dns_send_query_schedules_timeout) ck_assert_uint_ne(id, 0U); ck_assert_uint_eq(s.dns_id, id); ck_assert_int_ne(s.dns_timer, NO_TIMER); - ck_assert_uint_eq(find_timer_expiry(&s, s.dns_timer), 2290U); + ck_assert_uint_eq(find_timer_expiry(&s, s.dns_timer), 2490U); test_rand_override_enabled = 0; } END_TEST @@ -3842,6 +3861,45 @@ START_TEST(test_dns_send_query_rejects_empty_labels) } END_TEST +/* RFC 1035 §3.1: an encoded name, including the root terminator, is at most + * 255 octets. 63+63+63+61 chars plus 3 dots encodes to 255 and is accepted; + * one more char encodes to 256 and must be rejected with state rolled back. */ +START_TEST(test_dns_send_query_rejects_256_octet_encoded_name) +{ + struct wolfIP s; + uint16_t id = 0; + char name[256]; + size_t labels[4] = { 63, 63, 63, 61 }; + size_t pos = 0; + int i; + + wolfIP_init(&s); + mock_link_init(&s); + s.dns_server = 0x08080808U; + + for (i = 0; i < 4; i++) { + memset(name + pos, 'a', labels[i]); + pos += labels[i]; + if (i < 3) + name[pos++] = '.'; + } + name[pos] = '\0'; + ck_assert_uint_eq(strlen(name), 253); + ck_assert_int_eq(dns_send_query(&s, name, &id, DNS_A), 0); + ck_assert_uint_eq(s.dns_query_len, + sizeof(struct dns_header) + 255 + sizeof(struct dns_question)); + dns_abort_query(&s); + + /* Last label one char longer: 256 octets encoded. */ + name[pos++] = 'a'; + name[pos] = '\0'; + ck_assert_uint_eq(strlen(name), 254); + ck_assert_int_eq(dns_send_query(&s, name, &id, DNS_A), -22); + ck_assert_uint_eq(s.dns_id, 0); + ck_assert_uint_eq(id, DNS_ID_NONE); +} +END_TEST + /* RFC 1035 §3.4.1: A RDATA is exactly a 32-bit address. An A RR whose * RDLENGTH is not 4 is malformed and must not be accepted as the answer; * the query stays outstanding for the retry/timeout path. */ diff --git a/src/test/unit/unit_tests_dns_edges.c b/src/test/unit/unit_tests_dns_edges.c index 3e8be7f2..afa0f3aa 100644 --- a/src/test/unit/unit_tests_dns_edges.c +++ b/src/test/unit/unit_tests_dns_edges.c @@ -247,6 +247,56 @@ START_TEST(test_dns_callback_qr_without_rd_is_accepted) } END_TEST +/* dispatch_events() clears READABLE before the callback and UDP recvfrom never + * re-raises it, so a stale reply queued ahead of the real one (e.g. the answer + * to a retransmitted query) yields a single event. The callback must drain + * the queue instead of stranding the matching reply. */ +START_TEST(test_dns_callback_drains_queued_datagrams) +{ + struct wolfIP s; + uint8_t response[128]; + int pos; + struct dns_rr *rr; + uint8_t a_rdata[4] = {0x0A, 0x00, 0x00, 0x02}; + + wolfIP_init(&s); + mock_link_init(&s); + s.dns_server = 0x0A000001U; + arm_dns_query(&s, 0x3333, dns_qname_example_com, + (int)sizeof(dns_qname_example_com), DNS_A); + dns_lookup_calls = 0; + dns_lookup_ip = 0; + s.dns_lookup_cb = test_dns_lookup_cb; + 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); + + pos = build_dns_a_response_header(response, sizeof(response), + (uint16_t)(s.dns_id + 1), 0x8180, 1, 1, NULL); + response[pos++] = 0xC0; + response[pos++] = (uint8_t)sizeof(struct dns_header); + rr = (struct dns_rr *)(response + pos); + rr->type = ee16(DNS_A); + rr->class = ee16(DNS_CLASS_IN); + rr->ttl = ee32(60); + rr->rdlength = ee16((uint16_t)sizeof(a_rdata)); + pos += (int)sizeof(struct dns_rr); + memcpy(&response[pos], a_rdata, sizeof(a_rdata)); + pos += (int)sizeof(a_rdata); + + /* Stale reply (wrong ID) first, then the matching one. */ + enqueue_udp_rx(&s.udpsockets[SOCKET_UNMARK(s.dns_udp_sd)], + response, (uint16_t)pos, DNS_PORT); + response[0] = (uint8_t)(s.dns_id >> 8); + response[1] = (uint8_t)(s.dns_id & 0xFF); + 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); + ck_assert_int_eq(dns_lookup_calls, 1); + ck_assert_uint_eq(dns_lookup_ip, 0x0A000002U); +} +END_TEST + /* A truncated response still delivers the complete A record in its answer section */ START_TEST(test_dns_callback_truncated_answer_delivered) { diff --git a/src/test/unit/unit_tests_forwarding.c b/src/test/unit/unit_tests_forwarding.c index cda854bc..3773fa6c 100644 --- a/src/test/unit/unit_tests_forwarding.c +++ b/src/test/unit/unit_tests_forwarding.c @@ -396,6 +396,72 @@ START_TEST(test_fwd_multicast_dest_bad_option_silent_drop) } END_TEST +/* ========================================================================= + * RFC 1812 4.3.2.7: multicast destination - no Time Exceeded / Frag Needed + * ========================================================================= + * No ICMP error may be sent in response to a datagram addressed to an IP + * multicast destination, so an expiring TTL or a DF datagram larger than the + * egress MTU is dropped silently, like the Parameter Problem case above. + */ +static void fwd_multicast_error_setup(struct wolfIP *s, uint8_t *frame, + uint32_t frame_len, uint16_t flags_fo, uint8_t ttl, uint16_t ip_len) +{ + struct wolfIP_ip_packet *ip = (struct wolfIP_ip_packet *)frame; + ip4 gw_ip = 0xC0A801FEU; + static const uint8_t gw_mac[6] = {0x20, 0x21, 0x22, 0x23, 0x24, 0x25}; + + setup_stack_with_two_ifaces(s, 0x0A000001U, 0xC0A80101U); + wolfIP_filter_set_callback(NULL, NULL); + ck_assert_int_eq(wolfIP_route_add(s, TEST_SECOND_IF, 0x00000000U, 0, + gw_ip), 0); + s->ll_dev[TEST_SECOND_IF].mtu = 590; + fwd_arp_store(s, TEST_SECOND_IF, gw_ip, gw_mac); + last_frame_sent_count = 0; + + memset(frame, 0, frame_len); + memcpy(ip->eth.dst, s->ll_dev[TEST_PRIMARY_IF].mac, 6); + memcpy(ip->eth.src, "\x01\x02\x03\x04\x05\x06", 6); + ip->eth.type = ee16(ETH_TYPE_IP); + ip->ver_ihl = 0x45; + ip->flags_fo = ee16(flags_fo); + ip->ttl = ttl; + ip->proto = WI_IPPROTO_UDP; + ip->len = ee16(ip_len); + ip->src = ee32(0x0A000002U); + ip->dst = ee32(0xE0000001U); /* 224.0.0.1 */ + fix_ip_checksum(ip); +} + +START_TEST(test_fwd_multicast_dest_ttl_expired_silent_drop) +{ + struct wolfIP s; + uint8_t frame[ETH_HEADER_LEN + IP_HEADER_LEN + 8]; + + fwd_multicast_error_setup(&s, frame, sizeof(frame), 0, 1, + IP_HEADER_LEN + 8); + ip_recv(&s, TEST_PRIMARY_IF, (struct wolfIP_ip_packet *)frame, + (uint32_t)sizeof(frame)); + + /* Silent: no Time Exceeded, no relay. */ + ck_assert_uint_eq(last_frame_sent_count, 0); +} +END_TEST + +START_TEST(test_fwd_multicast_dest_df_oversize_silent_drop) +{ + struct wolfIP s; + uint8_t frame[ETH_HEADER_LEN + IP_HEADER_LEN + 580]; + + fwd_multicast_error_setup(&s, frame, sizeof(frame), 0x4000U, 64, + IP_HEADER_LEN + 580); + ip_recv(&s, TEST_PRIMARY_IF, (struct wolfIP_ip_packet *)frame, + (uint32_t)sizeof(frame)); + + /* Silent: no Fragmentation Needed, no relay. */ + ck_assert_uint_eq(last_frame_sent_count, 0); +} +END_TEST + /* ========================================================================= * RFC 1858: fragments are dropped at arrival, before the SENDING L4 * filter hooks diff --git a/src/test/unit/unit_tests_ifaddr.c b/src/test/unit/unit_tests_ifaddr.c index e266441e..780e4c8a 100644 --- a/src/test/unit/unit_tests_ifaddr.c +++ b/src/test/unit/unit_tests_ifaddr.c @@ -628,6 +628,50 @@ START_TEST(test_ifaddr_tcp_wildcard_listener_accepts_connections_to_an_alias) } END_TEST +START_TEST(test_ifaddr_arp_request_for_an_alias_is_answered) +{ + struct wolfIP s; + struct arp_packet req; + struct arp_packet *rep; + uint8_t req_mac[6] = {0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF}; + + ifaddr_setup(&s); + ck_assert_int_eq(wolfIP_ifaddr_add4(&s, TEST_PRIMARY_IF, IFA_IP_B, 24), 0); + + memset(&req, 0, sizeof(req)); + req.htype = ee16(1); + req.ptype = ee16(0x0800); + req.hlen = 6; + req.plen = 4; + req.opcode = ee16(ARP_REQUEST); + req.sip = ee32(0xC0A80002U); + memcpy(req.sma, req_mac, 6); + req.tip = ee32(IFA_IP_B); + + last_frame_sent_size = 0; + arp_recv(&s, TEST_PRIMARY_IF, &req, sizeof(req)); + wolfIP_poll(&s, 1000); + + /* RFC 826: the alias is the target, so it is answered and is the sender + * protocol address of the reply. */ + ck_assert_uint_eq(last_frame_sent_size, sizeof(struct arp_packet)); + rep = (struct arp_packet *)last_frame_sent; + ck_assert_int_eq(rep->opcode, ee16(ARP_REPLY)); + ck_assert_uint_eq(rep->sip, ee32(IFA_IP_B)); + ck_assert_mem_eq(rep->sma, s.ll_dev[TEST_PRIMARY_IF].mac, 6); + ck_assert_mem_eq(rep->tma, req_mac, 6); + ck_assert_uint_eq(rep->tip, ee32(0xC0A80002U)); + + /* An address that is not configured here is still ignored. */ + last_frame_sent_size = 0; + req.opcode = ee16(ARP_REQUEST); + req.tip = ee32(IFA_IP_C); + arp_recv(&s, TEST_PRIMARY_IF, &req, sizeof(req)); + wolfIP_poll(&s, 1001); + ck_assert_uint_eq(last_frame_sent_size, 0); +} +END_TEST + #endif /* WOLFIP_IF_MULTICONF */ /* ========================================================================= diff --git a/src/test/unit/unit_tests_ipv6_nd.c b/src/test/unit/unit_tests_ipv6_nd.c index 2a578f22..4da17032 100644 --- a/src/test/unit/unit_tests_ipv6_nd.c +++ b/src/test/unit/unit_tests_ipv6_nd.c @@ -466,6 +466,83 @@ START_TEST(test_nd_na_without_override_may_not_replace_a_known_mac) } END_TEST +START_TEST(test_nd_stale_entry_is_probed_and_dropped_when_unanswered) +{ + struct wolfIP s; + struct nd6_msg *ns; + uint64_t now = 1000; + uint8_t mac[6]; + unsigned int tx_if = TEST_PRIMARY_IF; + ip6 peer; + ip6 got; + int idx; + + nd_setup(&s); + wolfIP_poll(&s, now); + ck_assert_int_eq(wolfIP_ipv6_start(&s, TEST_PRIMARY_IF), 0); + nd_advance(&s, &now, 1500); + ck_assert_int_eq(atoip6("fe80::2", &peer), 0); + (void)nd6_store_neighbor(&s, TEST_PRIMARY_IF, &peer, nd_peer_mac, + ND6_STALE, 0); + idx = nd6_neighbor_index(&s, TEST_PRIMARY_IF, &peer); + + /* RFC 4861 section 7.3.3: a STALE entry is still used, but sending to + * it starts the delay before reachability is probed. */ + ck_assert_int_eq(nd6_resolve(&s, &tx_if, &peer, mac), 0); + ck_assert_mem_eq(mac, nd_peer_mac, 6); + ck_assert_int_eq(s.nd6.neighbors[idx].state, ND6_DELAY); + + /* After DELAY_FIRST_PROBE_TIME the probe is a unicast solicitation to + * the cached address, not a multicast one. */ + last_frame_sent_size = 0; + nd_advance(&s, &now, 5500); + ck_assert_int_eq(s.nd6.neighbors[idx].state, ND6_PROBE); + ck_assert_ptr_nonnull(nd_sent()); + ns = (struct nd6_msg *)last_frame_sent; + ck_assert_uint_eq(ns->type, ICMP6_NEIGHBOR_SOLICIT); + ck_assert_mem_eq(ns->ip6.eth.dst, nd_peer_mac, 6); + ip6_hdr_get_dst(&ns->ip6, &got); + ck_assert_int_eq(ip6_cmp(&got, &peer), 0); + + /* MAX_UNICAST_SOLICIT unanswered probes delete the entry, so the next + * send resolves it afresh instead of using a dead address forever. */ + nd_advance(&s, &now, 5000); + ck_assert_int_lt(wolfIP_nd6_lookup(&s, TEST_PRIMARY_IF, &peer, mac), 0); +} +END_TEST + +START_TEST(test_nd_probe_answered_makes_a_stale_entry_reachable) +{ + struct wolfIP s; + uint64_t now = 1000; + uint8_t mac[6]; + unsigned int tx_if = TEST_PRIMARY_IF; + ip6 ll6; + ip6 peer; + int idx; + + nd_setup(&s); + wolfIP_poll(&s, now); + ck_assert_int_eq(wolfIP_ipv6_start(&s, TEST_PRIMARY_IF), 0); + nd_advance(&s, &now, 1500); + nd_our_link_local(&s, &ll6); + ck_assert_int_eq(atoip6("fe80::2", &peer), 0); + (void)nd6_store_neighbor(&s, TEST_PRIMARY_IF, &peer, nd_peer_mac, + ND6_STALE, 0); + idx = nd6_neighbor_index(&s, TEST_PRIMARY_IF, &peer); + + ck_assert_int_eq(nd6_resolve(&s, &tx_if, &peer, mac), 0); + nd_advance(&s, &now, 5500); + ck_assert_int_eq(s.nd6.neighbors[idx].state, ND6_PROBE); + + nd_send_na(&s, &peer, &ll6, &peer, ND6_NA_SOLICITED | ND6_NA_OVERRIDE, + nd_peer_mac, 255, nd_peer_mac); + ck_assert_int_eq(s.nd6.neighbors[idx].state, ND6_REACHABLE); + nd_advance(&s, &now, 5000); + ck_assert_int_eq(wolfIP_nd6_lookup(&s, TEST_PRIMARY_IF, &peer, mac), 0); +} +END_TEST + START_TEST(test_nd_messages_with_wrong_hop_limit_are_refused) { struct wolfIP s; diff --git a/src/test/unit/unit_tests_ipv6_sockets.c b/src/test/unit/unit_tests_ipv6_sockets.c index 0dd0b852..8e4b4ce7 100644 --- a/src/test/unit/unit_tests_ipv6_sockets.c +++ b/src/test/unit/unit_tests_ipv6_sockets.c @@ -142,6 +142,44 @@ START_TEST(test_sock6_bind_and_getsockname_roundtrip) } END_TEST +/* bind() to port 0 on an IPv6 address allocates an ephemeral port, as the + * IPv4 path does, instead of listening on reserved port 0. */ +START_TEST(test_sock6_bind_port_zero_allocates_ephemeral_port) +{ + struct wolfIP s; + struct wolfIP_sockaddr_in6 sin6; + struct wolfIP_sockaddr_in6 got; + socklen_t len; + int tfd, ufd; + + sock6_setup(&s); + tfd = wolfIP_sock_socket(&s, AF_INET6, IPSTACK_SOCK_STREAM, 0); + ufd = wolfIP_sock_socket(&s, AF_INET6, IPSTACK_SOCK_DGRAM, 0); + ck_assert_int_ge(tfd, 0); + ck_assert_int_ge(ufd, 0); + + sock6_addr(&sin6, S6_TEST_GLOBAL, 0); + ck_assert_int_eq(wolfIP_sock_bind(&s, tfd, (struct wolfIP_sockaddr *)&sin6, + sizeof(sin6)), 0); + ck_assert_int_eq(wolfIP_sock_listen(&s, tfd, 1), 0); + memset(&got, 0, sizeof(got)); + len = sizeof(got); + ck_assert_int_eq(wolfIP_sock_getsockname(&s, tfd, + (struct wolfIP_sockaddr *)&got, + &len), 0); + ck_assert_uint_ge(ee16(got.sin6_port), 1024); + + ck_assert_int_eq(wolfIP_sock_bind(&s, ufd, (struct wolfIP_sockaddr *)&sin6, + sizeof(sin6)), 0); + memset(&got, 0, sizeof(got)); + len = sizeof(got); + ck_assert_int_eq(wolfIP_sock_getsockname(&s, ufd, + (struct wolfIP_sockaddr *)&got, + &len), 0); + ck_assert_uint_ge(ee16(got.sin6_port), 1024); +} +END_TEST + /* An address that is not configured on any interface cannot be bound, the * same rule the IPv4 path applies. */ START_TEST(test_sock6_bind_to_a_foreign_address_is_refused) @@ -1595,6 +1633,99 @@ static void sock6_deliver_icmp6_msg(struct wolfIP *s, const ip6 *src, sock6_deliver_icmp6_msg_on(s, TEST_PRIMARY_IF, src, dst, type, code); } +/* An ICMPv6 Packet Too Big quoting a segment of an established connection, + * delivered through the ingress path. */ +static void sock6_deliver_ptb(struct wolfIP *s, const ip6 *router, + const ip6 *local, const ip6 *peer, + uint16_t local_port, uint16_t peer_port, + uint32_t mtu) +{ + uint8_t body[8 + IP6_HEADER_LEN + 8]; + uint8_t *q = &body[8]; + + memset(body, 0, sizeof(body)); + body[0] = ICMP6_PACKET_TOO_BIG; + body[4] = (uint8_t)(mtu >> 24); + body[5] = (uint8_t)(mtu >> 16); + body[6] = (uint8_t)(mtu >> 8); + body[7] = (uint8_t)mtu; + q[0] = 0x60; + q[4] = 0; + q[5] = 8; + q[6] = IP6_NEXTHDR_TCP; + q[7] = 64; + memcpy(&q[8], local->addr, 16); + memcpy(&q[24], peer->addr, 16); + q[40] = (uint8_t)(local_port >> 8); + q[41] = (uint8_t)local_port; + q[42] = (uint8_t)(peer_port >> 8); + q[43] = (uint8_t)peer_port; + sock6_deliver_icmp6_body(s, router, local, body, sizeof(body)); +} + +/* RFC 4443 section 2.4 (d): an ICMPv6 error is passed to the upper layer, and + * RFC 8201 has TCP lower its segment size on Packet Too Big, as the IPv4 + * Fragmentation Needed arm does. Without it a path narrower than the link + * drops every full-size segment for good. The reported MTU is never taken + * below the IPv6 minimum of 1280. */ +START_TEST(test_sock6_tcp_packet_too_big_lowers_peer_mss) +{ + struct wolfIP s; + struct wolfIP_sockaddr_in6 dst; + struct wolfIP_tcp6_seg *seg; + struct tsocket *t; + uint8_t staging[LINK_MTU]; + uint64_t now = 0; + ip6 peer; + ip6 local; + ip6 other; + uint32_t our_isn; + int fd; + + sock6_setup(&s); + sock6_ready(&s, &now); + fd = wolfIP_sock_socket(&s, AF_INET6, IPSTACK_SOCK_STREAM, 0); + ck_assert_int_ge(fd, 0); + t = &s.tcpsockets[SOCKET_UNMARK(fd)]; + sock6_addr(&dst, S6_PEER, 80); + ck_assert_int_eq(atoip6(S6_PEER, &peer), 0); + ck_assert_int_eq(atoip6("2001:db8:5::77", &other), 0); + + mock_link_capture_reset(); + ck_assert_int_eq(wolfIP_sock_connect(&s, fd, + (struct wolfIP_sockaddr *)&dst, + sizeof(dst)), -WOLFIP_EAGAIN); + now += 100; + wolfIP_poll(&s, now); + seg = sock6_last_tcp(staging); + ck_assert_ptr_nonnull(seg); + our_isn = ee32(seg->seq); + ip6_hdr_get_src(&seg->ip6, &local); + sock6_deliver_tcp(&s, &peer, &local, 80, t->src_port, 0x11223344, + our_isn + 1, TCP_FLAG_SYN | TCP_FLAG_ACK, NULL, 0); + now += 100; + wolfIP_poll(&s, now); + ck_assert_int_eq(t->sock.tcp.state, TCP_ESTABLISHED); + t->sock.tcp.peer_mss = 1440; + + /* A report about some other connection changes nothing. */ + sock6_deliver_ptb(&s, &other, &local, &peer, t->src_port, 81, 1400); + ck_assert_uint_eq(t->sock.tcp.peer_mss, 1440); + + /* 1400 - 40 (IPv6) - 20 (TCP). */ + sock6_deliver_ptb(&s, &other, &local, &peer, t->src_port, 80, 1400); + ck_assert_uint_eq(t->sock.tcp.peer_mss, 1340); + + /* A larger MTU never raises it again. */ + sock6_deliver_ptb(&s, &other, &local, &peer, t->src_port, 80, 1480); + ck_assert_uint_eq(t->sock.tcp.peer_mss, 1340); + + /* Below the IPv6 minimum link MTU the estimate stops at 1280. */ + sock6_deliver_ptb(&s, &other, &local, &peer, t->src_port, 80, 600); + ck_assert_uint_eq(t->sock.tcp.peer_mss, 1280 - 60); +} +END_TEST + /* An Echo Request or Reply with a chosen identifier and sequence. */ static void sock6_deliver_icmp6_echo(struct wolfIP *s, const ip6 *src, const ip6 *dst, uint8_t type, @@ -2451,6 +2582,70 @@ START_TEST(test_sock6_tcp_syn_sent_reset_is_framed_as_ipv6) } END_TEST +/* A full timer heap leaves the SYN with no retransmit timer: the IPv6 connect + * arm must roll back to CLOSED with the SYN dequeued, as the IPv4 arm does, + * rather than park the socket in SYN_SENT where every later connect() reports + * EAGAIN forever. */ +START_TEST(test_sock6_tcp_connect_timer_heap_full_rolls_back) +{ + struct wolfIP s; + struct wolfIP_sockaddr_in6 dst; + struct wolfIP_timer t = {0}; + struct tsocket *ts; + uint64_t now = 0; + int fd; + int i; + + sock6_setup(&s); + sock6_ready(&s, &now); + fd = wolfIP_sock_socket(&s, AF_INET6, IPSTACK_SOCK_STREAM, 0); + ck_assert_int_ge(fd, 0); + ts = &s.tcpsockets[SOCKET_UNMARK(fd)]; + t.cb = tcp_rto_cb; + for (i = 0; i < MAX_TIMERS; i++) { + t.arg = (void *)(intptr_t)i; + t.expires = s.last_tick + 100000 + i; + if (timers_binheap_insert(&s.timers, t) < 0) + break; + } + sock6_addr(&dst, S6_PEER, 80); + ck_assert_int_eq(wolfIP_sock_connect(&s, fd, + (struct wolfIP_sockaddr *)&dst, + sizeof(dst)), -WOLFIP_EAGAIN); + ck_assert_int_eq(ts->sock.tcp.tmr_rto, NO_TIMER); + ck_assert_int_eq(ts->sock.tcp.state, TCP_CLOSED); + ck_assert_uint_eq(fifo_len(&ts->sock.tcp.txbuf), 0); +} +END_TEST + +/* An explicitly bound source port is kept by connect(), whatever its value. + * The IPv4 arm never touches a bound port; the IPv6 arm must not shift a + * privileged one up by 1024 either. */ +START_TEST(test_sock6_tcp_connect_keeps_bound_low_port) +{ + struct wolfIP s; + struct wolfIP_sockaddr_in6 bind6; + struct wolfIP_sockaddr_in6 dst; + struct tsocket *ts; + uint64_t now = 0; + int fd; + + sock6_setup(&s); + sock6_ready(&s, &now); + fd = wolfIP_sock_socket(&s, AF_INET6, IPSTACK_SOCK_STREAM, 0); + ck_assert_int_ge(fd, 0); + ts = &s.tcpsockets[SOCKET_UNMARK(fd)]; + sock6_addr(&bind6, NULL, 600); + ck_assert_int_eq(wolfIP_sock_bind(&s, fd, (struct wolfIP_sockaddr *)&bind6, + sizeof(bind6)), 0); + sock6_addr(&dst, S6_PEER, 80); + ck_assert_int_eq(wolfIP_sock_connect(&s, fd, + (struct wolfIP_sockaddr *)&dst, + sizeof(dst)), -WOLFIP_EAGAIN); + ck_assert_uint_eq(ts->src_port, 600); +} +END_TEST + /* IPV6_V6ONLY has to hold on the receive path too. A socket that asked for * IPv6 only must not be handed IPv4 datagrams, and must not reserve the * IPv4 port either - it does not occupy that address space. */ @@ -3219,9 +3414,9 @@ START_TEST(test_sock6_specific_listener_ignores_another_local_address) ck_assert_uint_eq(listener->last_pkt_ttl, 0); ck_assert_int_eq(listener->sock.tcp.state, TCP_LISTEN); - /* The same segment to the bound address does reach it. */ - sock6_deliver_tcp(&s, &peer, &bound, 42000, 8300, 0x1000, 0x2000, - TCP_FLAG_ACK, NULL, 0); + /* A SYN to the bound address does reach it. */ + sock6_deliver_tcp(&s, &peer, &bound, 42000, 8300, 0x1000, 0, + TCP_FLAG_SYN, NULL, 0); now += 100; wolfIP_poll(&s, now); ck_assert_uint_ne(listener->last_pkt_ttl, 0); @@ -3666,4 +3861,33 @@ START_TEST(test_sock6_tcp_no_route_aborts_the_connection) } END_TEST +/* Aborting a synchronized connection sends a reset first. When the peer has + * no route that reset can never leave either, and it must not re-enter the + * abort from tcp6_send_seg: the socket is still synchronized at that point, + * so the abort would send another reset, and so on until the stack ran out. */ +START_TEST(test_sock6_tcp_no_route_abort_of_established_does_not_recurse) +{ + struct wolfIP s; + struct tsocket *ts; + uint64_t now = 0; + int fd; + + sock6_setup(&s); + sock6_ready(&s, &now); + + fd = wolfIP_sock_socket(&s, AF_INET6, IPSTACK_SOCK_STREAM, 0); + ck_assert_int_ge(fd, 0); + ts = &s.tcpsockets[SOCKET_UNMARK(fd)]; + ts->if_idx = TEST_PRIMARY_IF; + ts->peer_is_v6 = 1; + ts->src_port = 40000; + ts->dst_port = 80; + ck_assert_int_eq(atoip6("2001:db8:9::1", &ts->remote_ip6), 0); + ts->sock.tcp.state = TCP_ESTABLISHED; + + ck_assert_int_eq(wolfIP_sock_abort(&s, fd), 0); + ck_assert_uint_eq(ts->proto, 0); +} +END_TEST + #endif /* WOLFIP_IPV6 */ diff --git a/src/test/unit/unit_tests_misc_edges.c b/src/test/unit/unit_tests_misc_edges.c index 41c25e6a..37058cad 100644 --- a/src/test/unit/unit_tests_misc_edges.c +++ b/src/test/unit/unit_tests_misc_edges.c @@ -580,6 +580,42 @@ START_TEST(test_bind_port_in_use_port_zero) } END_TEST +/* bind() to port 0 must allocate an ephemeral port so that a listener never + * answers SYNs to reserved port 0 and a UDP socket can receive. */ +START_TEST(test_bind_port_zero_allocates_ephemeral_port) +{ + struct wolfIP s; + struct wolfIP_sockaddr_in sin; + struct wolfIP_sockaddr_in out; + socklen_t outlen; + int tfd, ufd; + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0a000001U, 0xffffff00U, 0); + tfd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_STREAM, 0); + ufd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + ck_assert_int_ge(tfd, 0); + ck_assert_int_ge(ufd, 0); + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + ck_assert_int_eq(wolfIP_sock_bind(&s, tfd, (struct wolfIP_sockaddr *)&sin, + sizeof(sin)), 0); + ck_assert_int_eq(wolfIP_sock_listen(&s, tfd, 1), 0); + memset(&out, 0, sizeof(out)); + outlen = sizeof(out); + ck_assert_int_eq(wolfIP_sock_getsockname(&s, tfd, + (struct wolfIP_sockaddr *)&out, &outlen), 0); + ck_assert_uint_ge(ee16(out.sin_port), 1024); + ck_assert_int_eq(wolfIP_sock_bind(&s, ufd, (struct wolfIP_sockaddr *)&sin, + sizeof(sin)), 0); + memset(&out, 0, sizeof(out)); + outlen = sizeof(out); + ck_assert_int_eq(wolfIP_sock_getsockname(&s, ufd, + (struct wolfIP_sockaddr *)&out, &outlen), 0); + ck_assert_uint_ge(ee16(out.sin_port), 1024); +} +END_TEST + /* ===================================================================== * arp_pending_record -- refresh existing entry, replace oldest * ===================================================================== */ @@ -1357,7 +1393,12 @@ START_TEST(test_tcp_connect_wildcard_zero_avoids_specific_bound_port) ck_assert_int_eq(wolfIP_sock_bind(&s, fd_a, (struct wolfIP_sockaddr *)&sin, sizeof(sin)), 0); - /* Wildcard :0: no port yet, the claim is the whole port space. */ + /* Force the allocator's first candidate to 8080. */ + test_rand_override_enabled = 1; + test_rand_override_value = 8080; + + /* Wildcard :0: bind() allocates the port now; the claim is the whole + * port space, so 8080 on 10.0.1.1 is not free. */ memset(&sin, 0, sizeof(sin)); sin.sin_family = AF_INET; sin.sin_port = 0; @@ -1366,10 +1407,6 @@ START_TEST(test_tcp_connect_wildcard_zero_avoids_specific_bound_port) sizeof(sin)), 0); tsb = &s.tcpsockets[SOCKET_UNMARK(fd_b)]; - /* Force the allocator's first candidate to 8080. */ - test_rand_override_enabled = 1; - test_rand_override_value = 8080; - /* Connect through the primary interface: the egress address is * 10.0.0.1, but the wildcard claim still covers 10.0.1.1:8080. */ memset(&sin, 0, sizeof(sin)); diff --git a/src/test/unit/unit_tests_multicast.c b/src/test/unit/unit_tests_multicast.c index 1afb7f64..d04e0107 100644 --- a/src/test/unit/unit_tests_multicast.c +++ b/src/test/unit/unit_tests_multicast.c @@ -497,6 +497,64 @@ START_TEST(test_multicast_igmp_query_report_heap_full_rearmed_on_poll) } END_TEST +/* A long-window General Query arms one report timer per membership; the shared heap must still hold + * the whole MAX_TCPSOCKETS * 3 budget the TCP, DHCP and DNS timers are sized for. */ +START_TEST(test_multicast_igmp_query_does_not_starve_other_timers) +{ + struct wolfIP s; + int sd[MAX_UDPSOCKETS]; + struct wolfIP_ip_mreq mreq; + struct wolfIP_timer t = {0}; + uint8_t frame[ETH_HEADER_LEN + IP_HEADER_LEN + IGMPV3_QUERY_MIN_LEN]; + struct wolfIP_ip_packet *ip = (struct wolfIP_ip_packet *)frame; + uint8_t *igmp = frame + ETH_HEADER_LEN + IP_HEADER_LEN; + unsigned int free_slots = 0; + int i, j; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000002U, 0xFFFFFF00U, 0); + for (i = 0; i < MAX_UDPSOCKETS; i++) { + sd[i] = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + ck_assert_int_gt(sd[i], 0); + for (j = 0; j < WOLFIP_UDP_MCAST_MEMBERSHIPS; j++) { + multicast_mreq(&mreq, 0xE9010300U + (uint32_t)(i * 16 + j), IPADDR_ANY); + ck_assert_int_eq(wolfIP_sock_setsockopt(&s, sd[i], WOLFIP_SOL_IP, + WOLFIP_IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)), 0); + } + } + /* Let every join repeat report fire so only query responses stay armed. */ + wolfIP_poll(&s, 1001); + wolfIP_poll(&s, 20000); + + /* General query, Max Resp Code 0xFF (= 3,174,400 ms). */ + memset(frame, 0, sizeof(frame)); + memcpy(ip->eth.dst, "\x01\x00\x5e\x00\x00\x01", 6); + memcpy(ip->eth.src, "\x02\x00\x00\x00\x00\x01", 6); + ip->eth.type = ee16(ETH_TYPE_IP); + ip->ver_ihl = 0x45; + ip->ttl = 1; + ip->proto = WI_IPPROTO_IGMP; + ip->len = ee16(IP_HEADER_LEN + IGMPV3_QUERY_MIN_LEN); + ip->src = ee32(0x0A000001U); + ip->dst = ee32(IGMP_ALL_HOSTS); + igmp[0] = IGMP_TYPE_MEMBERSHIP_QUERY; + igmp[1] = 0xFF; + put_be16(igmp + 2, ip_checksum_buf(igmp, IGMPV3_QUERY_MIN_LEN)); + fix_ip_checksum(ip); + wolfIP_recv_ex(&s, TEST_PRIMARY_IF, frame, sizeof(frame)); + + /* Count what is left for everything else. */ + for (i = 0; i < (int)(MAX_TIMERS + 1); i++) { + t.expires = s.last_tick + 4000000U + (uint64_t)i; + if (timers_binheap_insert(&s.timers, t) == 0) + break; + free_slots++; + } + ck_assert_uint_ge(free_slots, MAX_TCPSOCKETS * 3); +} +END_TEST + /* A deferred query response owed across a tick-source rollback is re-armed at a deadline in the new tick domain. */ START_TEST(test_multicast_igmp_query_report_rearmed_after_tick_rollback) { @@ -1045,4 +1103,41 @@ START_TEST(test_multicast_getsockopt_ttl_loop_accepts_uint8) } END_TEST +/* An ICMP datagram socket sending to a group must use the RFC 1112 multicast + * MAC, not ARP/gateway resolution (which would never resolve, or unicast the + * frame to the router). */ +START_TEST(test_multicast_icmp_sendto_uses_multicast_mac) +{ + struct wolfIP s; + int sd; + struct tsocket *ts; + struct wolfIP_sockaddr_in sin; + uint8_t payload[ICMP_HEADER_LEN] = {0}; + uint8_t exp_mac[6]; + ip4 group = 0xE0000001U; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000002U, 0xFFFFFF00U, 0); + s.last_tick = 1; + sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_ICMP); + ck_assert_int_gt(sd, 0); + ts = &s.icmpsockets[SOCKET_UNMARK(sd)]; + + payload[0] = ICMP_ECHO_REQUEST; + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = ee32(group); + ck_assert_int_eq(wolfIP_sock_sendto(&s, sd, payload, sizeof(payload), 0, + (struct wolfIP_sockaddr *)&sin, sizeof(sin)), (int)sizeof(payload)); + + last_frame_sent_size = 0; + (void)wolfIP_poll(&s, 2); + ck_assert_uint_eq(fifo_len(&ts->sock.udp.txbuf), 0U); + ck_assert_uint_gt(last_frame_sent_size, 0U); + mcast_ip_to_eth(group, exp_mac); + ck_assert_int_eq(memcmp(last_frame_sent, exp_mac, 6), 0); +} +END_TEST + #endif /* IP_MULTICAST */ diff --git a/src/test/unit/unit_tests_poll_dispatcher.c b/src/test/unit/unit_tests_poll_dispatcher.c index e40060ae..f321cb7e 100644 --- a/src/test/unit/unit_tests_poll_dispatcher.c +++ b/src/test/unit/unit_tests_poll_dispatcher.c @@ -1460,6 +1460,38 @@ START_TEST(test_poll_tx_raw_arp_miss_emits_request) } END_TEST +START_TEST(test_poll_tx_raw_directed_broadcast_uses_bcast_mac) +{ + struct wolfIP s; + int sd; + uint8_t payload[8]; + struct wolfIP_sockaddr_in sin; + static const uint8_t bcast[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + wolfIP_filter_set_callback(NULL, NULL); + last_frame_sent_size = 0; + + sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_RAW, WI_IPPROTO_UDP); + ck_assert_int_ge(sd, 0); + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = ee32(0x0A0000FFU); /* subnet-directed broadcast */ + memset(payload, 0, sizeof(payload)); + ck_assert_int_eq(wolfIP_sock_sendto(&s, sd, payload, sizeof(payload), 0, + (struct wolfIP_sockaddr *)&sin, sizeof(sin)), (int)sizeof(payload)); + + (void)wolfIP_poll(&s, 2000); + /* Must be sent as IPv4 to the broadcast MAC, not held for ARP. */ + ck_assert_uint_gt(last_frame_sent_size, 0U); + ck_assert_uint_eq(last_frame_sent[12], 0x08); + ck_assert_uint_eq(last_frame_sent[13], 0x00); + ck_assert_int_eq(memcmp(last_frame_sent, bcast, 6), 0); +} +END_TEST + START_TEST(test_poll_tx_raw_dst_zero_skips_descriptor) { struct wolfIP s; @@ -1809,9 +1841,9 @@ START_TEST(test_poll_returns_arp_retry_deadline) ck_assert_int_eq(wolfIP_sock_sendto(&s, udp_sd, payload, sizeof(payload), 0, (struct wolfIP_sockaddr *)&sin, sizeof(sin)), (int)sizeof(payload)); - /* The request goes out at 2000; the next one is allowed at 3001. */ + /* The request goes out at 2000; the next one is allowed at 3000. */ ck_assert_int_eq(wolfIP_poll(&s, 2000), WOLFIP_POLL_MAX_WAIT_MS); - ck_assert_int_eq(wolfIP_poll(&s, 2500), 501); + ck_assert_int_eq(wolfIP_poll(&s, 2500), 500); } END_TEST diff --git a/src/test/unit/unit_tests_proto.c b/src/test/unit/unit_tests_proto.c index e7a9d38f..d2e4e607 100644 --- a/src/test/unit/unit_tests_proto.c +++ b/src/test/unit/unit_tests_proto.c @@ -259,6 +259,125 @@ START_TEST(test_tcp_input_header_len_below_min_dropped) } END_TEST +/* Data offset (60 bytes) larger than the IP payload (20 bytes): the segment is + * malformed and must be dropped before ACK/SACK processing, which would + * otherwise parse option bytes lying beyond the received frame. */ +START_TEST(test_tcp_input_data_offset_exceeds_ip_len_dropped) +{ + struct wolfIP s; + struct tsocket *ts; + uint8_t buf[sizeof(struct wolfIP_tcp_seg) + 40]; + struct wolfIP_tcp_seg *seg = (struct wolfIP_tcp_seg *)buf; + uint32_t left = ee32(102); + uint32_t right = ee32(104); + uint32_t seq = 100; + uint32_t ack = 100; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + ts = &s.tcpsockets[0]; + memset(ts, 0, sizeof(*ts)); + ts->proto = WI_IPPROTO_TCP; + ts->S = &s; + ts->sock.tcp.state = TCP_ESTABLISHED; + ts->sock.tcp.ack = seq; + ts->sock.tcp.snd_una = ack; + ts->sock.tcp.seq = ack + 10; + ts->sock.tcp.bytes_in_flight = 10; + ts->sock.tcp.sack_permitted = 1; + ts->sock.tcp.cwnd = TCP_MSS * 4; + ts->sock.tcp.peer_rwnd = 1000; + ts->src_port = 1234; + ts->dst_port = 4321; + ts->local_ip = 0x0A000001U; + ts->remote_ip = 0x0A000002U; + queue_init(&ts->sock.tcp.rxbuf, ts->rxmem, RXBUF_SIZE, ts->sock.tcp.ack); + fifo_init(&ts->sock.tcp.txbuf, ts->txmem, TXBUF_SIZE); + + memset(buf, 0, sizeof(buf)); + seg->ip.ver_ihl = 0x45; + seg->ip.ttl = 64; + seg->ip.proto = WI_IPPROTO_TCP; + seg->ip.len = ee16(IP_HEADER_LEN + TCP_HEADER_LEN); + seg->ip.src = ee32(ts->remote_ip); + seg->ip.dst = ee32(ts->local_ip); + seg->dst_port = ee16(ts->src_port); + seg->src_port = ee16(ts->dst_port); + seg->seq = ee32(seq); + seg->ack = ee32(ack); + seg->flags = TCP_FLAG_ACK; + seg->hlen = 0xF0; /* 60-byte header, but ip.len covers only 20 bytes */ + seg->win = ee16(5); + /* Well-formed SACK option lying past the received frame. */ + seg->data[0] = TCP_OPTION_NOP; + seg->data[1] = TCP_OPTION_NOP; + seg->data[2] = TCP_OPTION_SACK; + seg->data[3] = 10; + memcpy(&seg->data[4], &left, sizeof(left)); + memcpy(&seg->data[8], &right, sizeof(right)); + fix_tcp_checksums(seg); + + last_frame_sent_size = 0; + tcp_input(&s, TEST_PRIMARY_IF, seg, + (uint32_t)(ETH_HEADER_LEN + IP_HEADER_LEN + TCP_HEADER_LEN)); + + ck_assert_uint_eq(last_frame_sent_size, 0); + ck_assert_uint_eq(ts->sock.tcp.peer_rwnd, 1000U); + ck_assert_uint_eq(ts->sock.tcp.snd_una, ack); + ck_assert_uint_eq(ts->sock.tcp.peer_sack_count, 0); +} +END_TEST + +START_TEST(test_tcp_input_listen_data_offset_exceeds_ip_len_dropped) +{ + struct wolfIP s; + struct tsocket *ts; + uint8_t buf[sizeof(struct wolfIP_tcp_seg) + 40]; + struct wolfIP_tcp_seg *seg = (struct wolfIP_tcp_seg *)buf; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + ts = &s.tcpsockets[0]; + memset(ts, 0, sizeof(*ts)); + ts->proto = WI_IPPROTO_TCP; + ts->S = &s; + ts->sock.tcp.state = TCP_LISTEN; + ts->src_port = 1234; + ts->local_ip = 0x0A000001U; + fifo_init(&ts->sock.tcp.txbuf, ts->txmem, TXBUF_SIZE); + queue_init(&ts->sock.tcp.rxbuf, ts->rxmem, RXBUF_SIZE, 0); + + memset(buf, 0, sizeof(buf)); + seg->ip.ver_ihl = 0x45; + seg->ip.ttl = 64; + seg->ip.proto = WI_IPPROTO_TCP; + seg->ip.len = ee16(IP_HEADER_LEN + TCP_HEADER_LEN); + seg->ip.src = ee32(0x0A000002U); + seg->ip.dst = ee32(ts->local_ip); + seg->dst_port = ee16(ts->src_port); + seg->src_port = ee16(4321); + seg->seq = ee32(100); + seg->flags = TCP_FLAG_SYN; + seg->hlen = 0xF0; + seg->data[0] = TCP_OPTION_MSS; + seg->data[1] = TCP_OPTION_MSS_LEN; + seg->data[2] = 0x02; + seg->data[3] = 0x00; + fix_tcp_checksums(seg); + + last_frame_sent_size = 0; + tcp_input(&s, TEST_PRIMARY_IF, seg, + (uint32_t)(ETH_HEADER_LEN + IP_HEADER_LEN + TCP_HEADER_LEN)); + + ck_assert_int_eq(ts->sock.tcp.state, TCP_LISTEN); + ck_assert_uint_eq(last_frame_sent_size, 0); +} +END_TEST + START_TEST(test_tcp_input_fin_wait_2_fin_with_payload_queues) { struct wolfIP s; @@ -3180,6 +3299,9 @@ START_TEST(test_arp_request_throttle) mock_link_init(&s); s.last_tick = 1000; s.arp.last_arp[TEST_PRIMARY_IF] = 880; + s.arp.pending[0].ip = target_ip; + s.arp.pending[0].if_idx = TEST_PRIMARY_IF; + s.arp.pending[0].ts = 879; last_frame_sent_size = 0; arp_request(&s, TEST_PRIMARY_IF, target_ip); ck_assert_int_eq(last_frame_sent_size, 0); @@ -3613,6 +3735,37 @@ START_TEST(test_transport_capacity_helpers_cover_guard_paths) } END_TEST +/* RFC 6691 section 2: the peer's MSS excludes TCP options, so the sender must + * shrink the data length by the options it adds (the 12-byte timestamp). */ +START_TEST(test_tcp_tx_payload_cap_subtracts_ts_option_from_peer_mss) +{ + struct wolfIP s; + struct tsocket ts; + struct wolfIP_ll_dev *ll; + + wolfIP_init(&s); + mock_link_init(&s); + ll = wolfIP_getdev_ex(&s, TEST_PRIMARY_IF); + ck_assert_ptr_nonnull(ll); + ll->mtu = LINK_MTU; + + memset(&ts, 0, sizeof(ts)); + ts.proto = WI_IPPROTO_TCP; + ts.S = &s; + ts.if_idx = TEST_PRIMARY_IF; + + ts.sock.tcp.peer_mss = 1400U; + ts.sock.tcp.ts_enabled = 0; + ck_assert_uint_eq(tcp_tx_payload_cap(&ts), 1400U); + ts.sock.tcp.ts_enabled = 1; + ck_assert_uint_eq(tcp_tx_payload_cap(&ts), 1400U - TCP_OPTIONS_LEN); + + /* PPPoE-style peer MSS 1452: options + payload must stay within it. */ + ts.sock.tcp.peer_mss = 1452U; + ck_assert_uint_le(tcp_tx_payload_cap(&ts) + TCP_OPTIONS_LEN, 1452U); +} +END_TEST + START_TEST(test_tx_has_writable_space_icmp_accepts_minimal_packet) { struct tsocket ts; @@ -3744,6 +3897,25 @@ START_TEST(test_wolfip_ip_is_broadcast_skips_zero_mask) } END_TEST +START_TEST(test_wolfip_ip_is_broadcast_skips_slash31) +{ + struct wolfIP s; + + /* RFC 3021: both addresses of a /31 are hosts, none is a broadcast. */ + wolfIP_init(&s); + s.if_count = 1; + s.ipconf[0].ip = 0x0A000000U; + s.ipconf[0].mask = 0xFFFFFFFEU; + ck_assert_int_eq(wolfIP_ip_is_broadcast(&s, 0x0A000000U), 0); + ck_assert_int_eq(wolfIP_ip_is_broadcast(&s, 0x0A000001U), 0); + + s.ipconf[0].ip = 0x0A000001U; + ck_assert_int_eq(wolfIP_ip_is_broadcast(&s, 0x0A000000U), 0); + ck_assert_int_eq(wolfIP_ip_is_broadcast(&s, 0x0A000001U), 0); + ck_assert_int_eq(wolfIP_ip_is_broadcast(&s, 0xFFFFFFFFU), 1); +} +END_TEST + #if WOLFIP_ENABLE_LOOPBACK START_TEST(test_wolfip_loopback_defaults) { @@ -4033,6 +4205,101 @@ START_TEST(test_tcp_adv_win_clamps_and_applies_window_scale) } END_TEST +/* RFC 9293 3.8.6.2.2 (MUST-39): after the receive buffer filled up and a zero + * window was advertised, small application reads must not reopen the window + * (SWS avoidance); only once min(RCV.BUFF/2, MSS) is free does it advance. */ +START_TEST(test_tcp_recv_sws_avoidance_holds_window_edge) +{ + struct wolfIP s; + struct tsocket *ts; + static uint8_t data[RXBUF_SIZE]; + uint8_t rd[2048]; + uint32_t thresh; + ip4 local_ip = 0x0A000001U; + ip4 remote_ip = 0x0A0000A1U; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, local_ip, 0xFFFFFF00U, 0); + + ts = &s.tcpsockets[0]; + memset(ts, 0, sizeof(*ts)); + ts->proto = WI_IPPROTO_TCP; + ts->S = &s; + ts->if_idx = TEST_PRIMARY_IF; + ts->sock.tcp.state = TCP_ESTABLISHED; + ts->local_ip = local_ip; + ts->remote_ip = remote_ip; + ts->src_port = 1234; + ts->dst_port = 4321; + ts->sock.tcp.ack = 1000; + ts->sock.tcp.seq = 2000; + ts->sock.tcp.snd_una = 2000; + ts->sock.tcp.peer_mss = 1460; + ts->sock.tcp.tmr_rto = NO_TIMER; + fifo_init(&ts->sock.tcp.txbuf, ts->txmem, TXBUF_SIZE); + queue_init(&ts->sock.tcp.rxbuf, ts->rxmem, RXBUF_SIZE, ts->sock.tcp.ack); + + /* Peer fills the whole receive buffer; we advertise a zero window. */ + memset(data, 0x5a, sizeof(data)); + ck_assert_int_eq(queue_insert(&ts->sock.tcp.rxbuf, data, + ts->sock.tcp.ack, RXBUF_SIZE - 1), 0); + ts->sock.tcp.ack += RXBUF_SIZE - 1; + ck_assert_uint_eq(tcp_adv_win(ts, 1), 0U); + + thresh = RXBUF_SIZE / 2; + if (thresh > 1460U) + thresh = 1460U; + + /* A 1-byte read frees no useful space: no window update. */ + ck_assert_int_eq(wolfIP_sock_recv(&s, MARK_TCP_SOCKET | 0, rd, 1, 0), 1); + ck_assert_ptr_eq(fifo_peek(&ts->sock.tcp.txbuf), NULL); + ck_assert_uint_eq(ts->sock.tcp.ack_retry_pending, 0U); + ck_assert_uint_eq(tcp_adv_win(ts, 1), 0U); + + /* Reading up to the threshold reopens the window. */ + ck_assert_int_eq(wolfIP_sock_recv(&s, MARK_TCP_SOCKET | 0, rd, + thresh - 1, 0), (int)(thresh - 1)); + ck_assert(fifo_peek(&ts->sock.tcp.txbuf) != NULL || + ts->sock.tcp.ack_retry_pending); + ck_assert_uint_eq(tcp_adv_win(ts, 1), thresh); +} +END_TEST + +/* RFC 9293 3.8.6 (SHLD-14): a receiver SHOULD NOT shrink the window. With a + * non-zero window scale, accepting a number of bytes that is not a multiple of + * 2^shift must not make the rounded advertised window pull the right edge left. */ +START_TEST(test_tcp_adv_win_scaled_right_edge_does_not_shrink) +{ + struct tsocket ts; + static uint8_t data[8]; + uint32_t edge; + + memset(&ts, 0, sizeof(ts)); + ts.sock.tcp.state = TCP_ESTABLISHED; + ts.sock.tcp.ack = 1000; + ts.sock.tcp.peer_mss = 1460; + ts.sock.tcp.ws_enabled = 1; + ts.sock.tcp.rcv_wscale = 2; + queue_init(&ts.sock.tcp.rxbuf, ts.rxmem, RXBUF_SIZE, ts.sock.tcp.ack); + + (void)tcp_adv_win(&ts, 1); + edge = ts.sock.tcp.rcv_edge; + + memset(data, 0x5a, sizeof(data)); + ck_assert_int_eq(queue_insert(&ts.sock.tcp.rxbuf, data, ts.sock.tcp.ack, 1), 0); + ts.sock.tcp.ack += 1; + (void)tcp_adv_win(&ts, 1); + ck_assert(!tcp_seq_lt(ts.sock.tcp.rcv_edge, edge)); + edge = ts.sock.tcp.rcv_edge; + + ck_assert_int_eq(queue_insert(&ts.sock.tcp.rxbuf, data, ts.sock.tcp.ack, 3), 0); + ts.sock.tcp.ack += 3; + (void)tcp_adv_win(&ts, 1); + ck_assert(!tcp_seq_lt(ts.sock.tcp.rcv_edge, edge)); +} +END_TEST + START_TEST(test_tcp_segment_acceptable_zero_window_and_overlap_cases) { struct tsocket ts; @@ -4118,6 +4385,30 @@ START_TEST(test_wolfip_ipconfig_ex_per_interface) } END_TEST +START_TEST(test_wolfip_route_lookup_directed_broadcast_uses_owning_interface) +{ + struct wolfIP s; + unsigned int if_idx = 0U; + ip4 nexthop = 0U; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set_ex(&s, TEST_PRIMARY_IF, 0x0A000001U, 0xFFFFFF00U, 0); + wolfIP_ipconfig_set_ex(&s, TEST_SECOND_IF, 0xC0A80101U, 0xFFFFFF00U, 0); + + /* Directed broadcast of the secondary subnet leaves via the secondary. */ + ck_assert_int_eq(wolfIP_route_lookup(&s, 0xC0A801FFU, &if_idx, &nexthop), 0); + ck_assert_uint_eq(if_idx, TEST_SECOND_IF); + ck_assert_uint_eq(nexthop, 0xC0A801FFU); + + /* Primary subnet broadcast and limited broadcast keep the primary. */ + ck_assert_int_eq(wolfIP_route_lookup(&s, 0x0A0000FFU, &if_idx, &nexthop), 0); + ck_assert_uint_eq(if_idx, TEST_PRIMARY_IF); + ck_assert_int_eq(wolfIP_route_lookup(&s, 0xFFFFFFFFU, &if_idx, &nexthop), 0); + ck_assert_uint_eq(if_idx, TEST_PRIMARY_IF); +} +END_TEST + #if WOLFIP_ENABLE_FORWARDING START_TEST(test_wolfip_route_table_longest_prefix_match) { @@ -4204,6 +4495,27 @@ START_TEST(test_wolfip_route_table_connected_subnet_beats_broader_static_route) ck_assert_uint_eq(nexthop, 0xC0A80163U); } END_TEST + +/* A static route with the same prefix length as a connected subnet must lose + * to it even when it is the very first route ever added and sits on a lower + * interface index. */ +START_TEST(test_wolfip_route_table_connected_subnet_beats_equal_prefix_first_static_route) +{ + struct wolfIP s; + unsigned int if_idx = 0U; + ip4 nexthop = 0U; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set_ex(&s, TEST_PRIMARY_IF, 0x0A000001U, 0xFF000000U, 0x0A0000FEU); + wolfIP_ipconfig_set_ex(&s, TEST_SECOND_IF, 0xC0A80101U, 0xFFFFFF00U, 0xC0A801FEU); + + ck_assert_int_eq(wolfIP_route_add(&s, TEST_PRIMARY_IF, 0xC0A80100U, 24U, 0x0A0000FEU), 0); + ck_assert_int_eq(wolfIP_route_lookup(&s, 0xC0A80163U, &if_idx, &nexthop), 0); + ck_assert_uint_eq(if_idx, TEST_SECOND_IF); + ck_assert_uint_eq(nexthop, 0xC0A80163U); +} +END_TEST #endif START_TEST(test_wolfip_dns_server_get_returns_value_and_validates_args) @@ -5573,9 +5885,9 @@ START_TEST(test_tcp_listen_requires_matching_local_ip) listen_port, 100, 0, 0); ck_assert_uint_eq(listener->last_pkt_ttl, ttl_before); - /* (2) A segment for the bound address still matches (not over-restricted). */ + /* (2) A SYN for the bound address still matches (not over-restricted). */ inject_tcp_segment(&s, TEST_SECOND_IF, 0x0A0000A2U, secondary_ip, 40000, - listen_port, 200, 0, 0); + listen_port, 200, 0, TCP_FLAG_SYN); ck_assert_uint_eq(listener->last_pkt_ttl, 64); } END_TEST @@ -7507,6 +7819,122 @@ START_TEST(test_regression_paws_rejects_stale_timestamp) } END_TEST +/* Build an ESTABLISHED socket and feed it one in-order peer segment that + * carries TSval/TSecr and ACKs "ack". RFC 7323 section 4.1: TSecr may update + * the RTT average only if SND.UNA is increased. */ +static struct tsocket *rttm_setup(struct wolfIP *s, uint32_t tsecr, + uint32_t ack, const uint8_t *payload, uint32_t plen) +{ + struct tsocket *ts; + uint8_t buf[sizeof(struct wolfIP_tcp_seg) + TCP_OPTIONS_LEN + 4]; + struct wolfIP_tcp_seg *seg = (struct wolfIP_tcp_seg *)buf; + struct tcp_opt_ts *tsopt; + uint32_t tcp_hlen = TCP_HEADER_LEN + TCP_OPTIONS_LEN; + + wolfIP_init(s); + mock_link_init(s); + wolfIP_ipconfig_set(s, 0x0A000001U, 0xFFFFFF00U, 0); + s->arp.neighbors[0].ip = 0x0A000002U; + s->arp.neighbors[0].if_idx = TEST_PRIMARY_IF; + memcpy(s->arp.neighbors[0].mac, + (uint8_t[]){0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF}, 6); + + ts = &s->tcpsockets[0]; + memset(ts, 0, sizeof(*ts)); + ts->proto = WI_IPPROTO_TCP; + ts->S = s; + ts->if_idx = TEST_PRIMARY_IF; + ts->sock.tcp.state = TCP_ESTABLISHED; + ts->sock.tcp.ack = 100; + ts->sock.tcp.seq = 1000; + ts->sock.tcp.snd_una = 1000; + ts->sock.tcp.rto = 200; + ts->sock.tcp.cwnd = TCP_MSS * 4; + ts->sock.tcp.peer_rwnd = TCP_MSS * 4; + ts->sock.tcp.ts_enabled = 1; + ts->src_port = 1234; + ts->dst_port = 4321; + ts->local_ip = 0x0A000001U; + ts->remote_ip = 0x0A000002U; + queue_init(&ts->sock.tcp.rxbuf, ts->rxmem, RXBUF_SIZE, ts->sock.tcp.ack); + fifo_init(&ts->sock.tcp.txbuf, ts->txmem, TXBUF_SIZE); + + if (ack != 1000) { + /* One 8-byte data segment in flight, sent at tick 90. */ + struct pkt_desc *d; + ck_assert_int_eq(enqueue_tcp_tx(ts, 8, (TCP_FLAG_ACK | TCP_FLAG_PSH)), 0); + ts->sock.tcp.seq = 1008; + (void)wolfIP_poll(s, 90); + d = fifo_peek(&ts->sock.tcp.txbuf); + ck_assert_ptr_nonnull(d); + ck_assert_int_ne(d->flags & PKT_FLAG_SENT, 0); + } + + memset(buf, 0, sizeof(buf)); + seg->ip.ver_ihl = 0x45; + seg->ip.ttl = 64; + seg->ip.proto = WI_IPPROTO_TCP; + seg->ip.len = ee16(IP_HEADER_LEN + tcp_hlen + plen); + seg->ip.src = ee32(ts->remote_ip); + seg->ip.dst = ee32(ts->local_ip); + seg->dst_port = ee16(ts->src_port); + seg->src_port = ee16(ts->dst_port); + seg->hlen = (uint8_t)(tcp_hlen << 2); + seg->flags = TCP_FLAG_ACK; + seg->seq = ee32(100); + seg->ack = ee32(ack); + seg->win = ee16(65535); + tsopt = (struct tcp_opt_ts *)seg->data; + tsopt->opt = TCP_OPTION_TS; + tsopt->len = TCP_OPTION_TS_LEN; + tsopt->val = ee32(2000); + tsopt->ecr = ee32(tsecr); + tsopt->pad = TCP_OPTION_NOP; + tsopt->eoo = TCP_OPTION_NOP; + if (plen) + memcpy(seg->data + TCP_OPTIONS_LEN, payload, plen); + fix_tcp_checksums(seg); + + s->last_tick = 100; + tcp_input(s, TEST_PRIMARY_IF, seg, + (uint32_t)(ETH_HEADER_LEN + IP_HEADER_LEN + tcp_hlen + plen)); + return ts; +} + +/* A peer data segment that does not advance SND.UNA must not feed the RTT + * estimator: its TSecr echoes our last TSval and, after an idle gap, the + * "sample" would include the gap. */ +START_TEST(test_tcp_input_ts_no_rtt_sample_without_snd_una_advance) +{ + struct wolfIP s; + struct tsocket *ts; + uint8_t payload[4] = {1, 2, 3, 4}; + + ts = rttm_setup(&s, 10, 1000, payload, sizeof(payload)); + + ck_assert_uint_eq(ts->sock.tcp.snd_una, 1000); + ck_assert_uint_eq(ts->sock.tcp.ack, 104); /* data was accepted */ + ck_assert_int_eq(ts->sock.tcp.rto_initialized, 0); +} +END_TEST + +/* An ACK that advances SND.UNA yields exactly one RTT sample from TSecr. */ +START_TEST(test_tcp_input_ts_single_rtt_sample_per_ack) +{ + struct wolfIP s; + struct tsocket *ts; + + ts = rttm_setup(&s, 90, 1008, NULL, 0); + + ck_assert_uint_eq(ts->sock.tcp.snd_una, 1008); + ck_assert_int_eq(ts->sock.tcp.rto_initialized, 1); + /* One sample of 10 ms: SRTT = R, RTTVAR = R/2 (a second identical + * sample would decay RTTVAR to 3R/8). */ + ck_assert_uint_eq(ts->sock.tcp.srtt, 10U << 3); + ck_assert_uint_eq(ts->sock.tcp.rttvar, 10U << 1); +} +END_TEST + /* RFC 7323 §5.2: TSval ordering is modulo 2^32, so after wrap a low * TSval can still be newer than TS.Recent. PAWS must not reject such * segments just because the raw unsigned value is smaller. */ @@ -8571,6 +8999,51 @@ START_TEST(test_regression_raw_socket_tx_eagain_keeps_descriptor) } END_TEST +/* F-14516: a raw sendto larger than the egress interface frame MTU must be + * rejected at admission; otherwise wolfIP_ll_xmit() fails it forever and the + * undeliverable descriptor wedges the raw TX FIFO. */ +START_TEST(test_regression_raw_socket_sendto_checks_interface_mtu) +{ + struct wolfIP s; + int sd; + uint8_t payload[1490]; + uint8_t small[6] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66}; + struct wolfIP_sockaddr_in sin; + uint32_t dst_ip = 0x0A00000CU; + uint8_t nh_mac[6] = {0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F}; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + ck_assert_int_eq(wolfIP_mtu_set(&s, TEST_PRIMARY_IF, 1514), 0); + + s.arp.neighbors[0].ip = dst_ip; + s.arp.neighbors[0].if_idx = TEST_PRIMARY_IF; + memcpy(s.arp.neighbors[0].mac, nh_mac, sizeof(nh_mac)); + + sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_RAW, WI_IPPROTO_UDP); + ck_assert_int_ge(sd, 0); + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = ee32(dst_ip); + memset(payload, 0xA5, sizeof(payload)); + + /* 14 + 20 + 1490 = 1524 > 1514: must not be admitted. */ + ck_assert_int_eq(wolfIP_sock_sendto(&s, sd, payload, sizeof(payload), 0, + (struct wolfIP_sockaddr *)&sin, sizeof(sin)), + -WOLFIP_EINVAL); + + /* A following small datagram still flows. */ + mock_link_capture_reset(); + ck_assert_int_eq(wolfIP_sock_sendto(&s, sd, small, sizeof(small), 0, + (struct wolfIP_sockaddr *)&sin, sizeof(sin)), + (int)sizeof(small)); + wolfIP_poll(&s, 0); + ck_assert_uint_eq(mock_sent_frames_count, 1U); +} +END_TEST + START_TEST(test_raw_socket_sendto_short_addrlen_returns_einval) { struct wolfIP s; diff --git a/src/test/unit/unit_tests_tcp_ack.c b/src/test/unit/unit_tests_tcp_ack.c index ea2845d6..6c80e616 100644 --- a/src/test/unit/unit_tests_tcp_ack.c +++ b/src/test/unit/unit_tests_tcp_ack.c @@ -4905,6 +4905,60 @@ START_TEST(test_tcp_rebuild_rx_sack_triggering_block_first) } END_TEST +/* A segment that cannot be cached (OOO table full) must not be SACKed: the + * sender would mark it received and wait for an RTO to resend it. */ +START_TEST(test_tcp_recv_ooo_table_full_does_not_sack_discarded_segment) +{ + struct wolfIP s; + struct tsocket *ts; + uint8_t seg_buf[sizeof(struct wolfIP_tcp_seg) + 1]; + struct wolfIP_tcp_seg *seg = (struct wolfIP_tcp_seg *)seg_buf; + struct pkt_desc *desc; + struct wolfIP_tcp_seg *ackseg; + uint32_t seqs[5] = {110, 120, 130, 140, 150}; + uint32_t left, right; + int off, i, nblk; + + wolfIP_init(&s); + ts = &s.tcpsockets[0]; + memset(ts, 0, sizeof(*ts)); + ts->proto = WI_IPPROTO_TCP; + ts->S = &s; + ts->sock.tcp.state = TCP_ESTABLISHED; + ts->sock.tcp.sack_permitted = 1; + ts->sock.tcp.ack = 100; + queue_init(&ts->sock.tcp.rxbuf, ts->rxmem, RXBUF_SIZE, 100); + fifo_init(&ts->sock.tcp.txbuf, ts->txmem, TXBUF_SIZE); + + for (i = 0; i < 5; i++) { + memset(seg, 0, sizeof(seg_buf)); + seg->ip.len = ee16(IP_HEADER_LEN + TCP_HEADER_LEN + 1); + seg->hlen = TCP_HEADER_LEN << 2; + seg->seq = ee32(seqs[i]); + seg->data[0] = 'x'; + tcp_recv(ts, seg); + if (i < 4) + (void)fifo_pop(&ts->sock.tcp.txbuf); + } + /* The fifth island did not fit the 4-slot OOO table. */ + desc = fifo_peek(&ts->sock.tcp.txbuf); + ck_assert_ptr_nonnull(desc); + ackseg = (struct wolfIP_tcp_seg *)(ts->txmem + desc->pos + sizeof(*desc)); + off = tcp_option_find(ackseg, TCP_OPTION_SACK); + ck_assert_int_ge(off, 0); + nblk = (ackseg->data[off + 1] - 2) / 8; + for (i = 0; i < nblk; i++) { + memcpy(&left, ackseg->data + off + 2 + 8 * i, sizeof(left)); + memcpy(&right, ackseg->data + off + 6 + 8 * i, sizeof(right)); + ck_assert(!(ee32(left) <= 150 && ee32(right) > 150)); + } + + /* The discarded segment is not retained for later ACKs either. */ + for (i = 0; i < ts->sock.tcp.rx_sack_count; i++) + ck_assert_uint_ne(ts->sock.tcp.rx_sack[i].left, 150); +} +END_TEST + START_TEST(test_tcp_consume_ooo_wrap_trim_and_promote) { struct wolfIP s; diff --git a/src/test/unit/unit_tests_tcp_flow.c b/src/test/unit/unit_tests_tcp_flow.c index 42607938..885aed31 100644 --- a/src/test/unit/unit_tests_tcp_flow.c +++ b/src/test/unit/unit_tests_tcp_flow.c @@ -1784,7 +1784,7 @@ START_TEST(test_regression_tcp_input_sets_last_pkt_ttl) seg.dst_port = ee16(ts->src_port); seg.src_port = ee16(ts->dst_port); seg.seq = ee32(100); - seg.ack = ee32(50); + seg.ack = ee32(0); seg.hlen = TCP_HEADER_LEN << 2; seg.flags = TCP_FLAG_ACK; fix_tcp_checksums(&seg); @@ -2685,6 +2685,61 @@ START_TEST(test_tcp_ack_data_segments_not_counted_as_dup_acks) } END_TEST +/* RFC 5681 s2: a duplicate ACK has the SYN and FIN bits both off, so + * payload-less FIN|ACK segments must not reach the fast-retransmit + * threshold. */ +START_TEST(test_tcp_ack_fin_segments_not_counted_as_dup_acks) +{ + struct wolfIP s; + struct tsocket *ts; + struct tcp_seg_buf segbuf; + struct wolfIP_tcp_seg *seg; + struct pkt_desc *desc; + struct wolfIP_tcp_seg pseg; + uint32_t seq = 1000; + int i; + + wolfIP_init(&s); + ts = &s.tcpsockets[0]; + memset(ts, 0, sizeof(*ts)); + ts->proto = WI_IPPROTO_TCP; + ts->S = &s; + ts->sock.tcp.state = TCP_ESTABLISHED; + ts->sock.tcp.cwnd = TCP_MSS * 4; + ts->sock.tcp.ssthresh = TCP_MSS * 8; + ts->sock.tcp.snd_una = seq; + ts->sock.tcp.seq = seq + TCP_MSS; + ts->sock.tcp.bytes_in_flight = TCP_MSS; + ts->sock.tcp.peer_rwnd = TCP_MSS * 8; + ts->sock.tcp.last_peer_win = 65535; + fifo_init(&ts->sock.tcp.txbuf, ts->txmem, TXBUF_SIZE); + + memset(&segbuf, 0, sizeof(segbuf)); + seg = &segbuf.seg; + seg->ip.len = ee16(IP_HEADER_LEN + TCP_HEADER_LEN + TCP_MSS); + seg->hlen = TCP_HEADER_LEN << 2; + seg->seq = ee32(seq); + ck_assert_int_eq(fifo_push(&ts->sock.tcp.txbuf, &segbuf, sizeof(segbuf)), 0); + desc = fifo_peek(&ts->sock.tcp.txbuf); + ck_assert_ptr_nonnull(desc); + desc->flags |= PKT_FLAG_SENT; + + memset(&pseg, 0, sizeof(pseg)); + pseg.ip.len = ee16(IP_HEADER_LEN + TCP_HEADER_LEN); + pseg.hlen = TCP_HEADER_LEN << 2; + pseg.flags = TCP_FLAG_ACK | TCP_FLAG_FIN; + pseg.win = ee16(65535); + pseg.ack = ee32(seq); + pseg.seq = ee32(500); + for (i = 0; i < 3; i++) + tcp_ack(ts, &pseg); + + ck_assert_uint_eq(ts->sock.tcp.dup_acks, 0); + ck_assert_int_eq(ts->sock.tcp.fast_recovery, 0); + ck_assert_uint_eq(ts->sock.tcp.snd_una, seq); +} +END_TEST + /* RFC 6298 §5.5: on a retransmission timeout the new RTO is min(2*RTO, G) * with G the maximum timer value, 64 s. With a 2 s base RTO and six * backoff doublings the uncapped interval would be 128 s; the re-armed @@ -3302,6 +3357,72 @@ START_TEST(test_tcp_input_syn_rcvd_ack_established) } END_TEST +/* RFC 5681 s3.1: if the SYN or SYN/ACK was lost, the initial window after the + * correctly transmitted SYN must be one segment of at most SMSS bytes. */ +START_TEST(test_tcp_input_handshake_after_lost_syn_uses_one_segment_cwnd) +{ + struct wolfIP s; + struct tsocket *ts; + struct wolfIP_sockaddr_in sin; + struct wolfIP_tcp_seg ackseg; + int tcp_sd; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + wolfIP_filter_set_callback(NULL, NULL); + wolfIP_filter_set_mask(0); + wolfIP_filter_set_tcp_mask(0); + + /* Active open: SYN retransmitted once before the SYN-ACK arrives. */ + tcp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_STREAM, WI_IPPROTO_TCP); + ck_assert_int_gt(tcp_sd, 0); + ts = &s.tcpsockets[SOCKET_UNMARK(tcp_sd)]; + ts->sock.tcp.state = TCP_SYN_SENT; + ts->sock.tcp.seq = 100; + ts->sock.tcp.ctrl_rto_retries = 1; + ts->src_port = 1234; + ts->dst_port = 4321; + ts->local_ip = 0x0A000001U; + ts->remote_ip = 0x0A000002U; + inject_tcp_segment(&s, TEST_PRIMARY_IF, 0x0A000002U, 0x0A000001U, 4321, 1234, 10, 101, (TCP_FLAG_SYN | TCP_FLAG_ACK)); + ck_assert_int_eq(ts->sock.tcp.state, TCP_ESTABLISHED); + ck_assert_uint_eq(ts->sock.tcp.cwnd, tcp_cc_mss(ts)); + + /* Passive open: SYN-ACK retransmitted once before the final ACK. */ + tcp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_STREAM, WI_IPPROTO_TCP); + ck_assert_int_gt(tcp_sd, 0); + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = ee16(5678); + sin.sin_addr.s_addr = ee32(0x0A000001U); + ck_assert_int_eq(wolfIP_sock_bind(&s, tcp_sd, (struct wolfIP_sockaddr *)&sin, sizeof(sin)), 0); + ck_assert_int_eq(wolfIP_sock_listen(&s, tcp_sd, 1), 0); + inject_tcp_syn(&s, TEST_PRIMARY_IF, 0x0A000001U, 5678); + ts = &s.tcpsockets[SOCKET_UNMARK(tcp_sd)]; + ck_assert_int_eq(ts->sock.tcp.state, TCP_SYN_RCVD); + ts->sock.tcp.ctrl_rto_retries = 1; + + memset(&ackseg, 0, sizeof(ackseg)); + ackseg.ip.ver_ihl = 0x45; + ackseg.ip.proto = WI_IPPROTO_TCP; + ackseg.ip.ttl = 64; + ackseg.ip.len = ee16(IP_HEADER_LEN + TCP_HEADER_LEN); + ackseg.ip.src = ee32(ts->remote_ip); + ackseg.ip.dst = ee32(ts->local_ip); + ackseg.dst_port = ee16(ts->src_port); + ackseg.src_port = ee16(ts->dst_port); + ackseg.seq = ee32(ts->sock.tcp.ack); + ackseg.ack = ee32(ts->sock.tcp.seq + 1); + ackseg.hlen = TCP_HEADER_LEN << 2; + ackseg.flags = TCP_FLAG_ACK; + fix_tcp_checksums(&ackseg); + tcp_input(&s, TEST_PRIMARY_IF, &ackseg, (uint32_t)(ETH_HEADER_LEN + IP_HEADER_LEN + TCP_HEADER_LEN)); + ck_assert_int_eq(ts->sock.tcp.state, TCP_ESTABLISHED); + ck_assert_uint_eq(ts->sock.tcp.cwnd, tcp_cc_mss(ts)); +} +END_TEST + START_TEST(test_tcp_input_syn_rcvd_ack_invalid_ack_rejected) { struct wolfIP s; @@ -4736,6 +4857,59 @@ START_TEST(test_tcp_input_local_ip_mismatch_preserves_if_idx) } END_TEST +START_TEST(test_tcp_input_unacceptable_segment_preserves_if_idx_and_ttl) +{ + struct wolfIP s; + struct tsocket *ts; + struct wolfIP_tcp_seg seg; + + setup_stack_with_two_ifaces(&s, 0x0A000001U, 0xC0A80001U); + + 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.ack = 100; + queue_init(&ts->sock.tcp.rxbuf, ts->rxmem, RXBUF_SIZE, ts->sock.tcp.ack); + fifo_init(&ts->sock.tcp.txbuf, ts->txmem, TXBUF_SIZE); + ts->src_port = 1234; + ts->dst_port = 4321; + ts->local_ip = IPADDR_ANY; + ts->remote_ip = 0x0A000002U; + ts->if_idx = TEST_SECOND_IF; + ts->last_pkt_ttl = 7; + + /* Off-path segment with the right 4-tuple arriving on another interface: + * its SEQ is far outside the receive window, so it is unacceptable. */ + memset(&seg, 0, sizeof(seg)); + seg.ip.ver_ihl = 0x45; + seg.ip.ttl = 64; + seg.ip.proto = WI_IPPROTO_TCP; + seg.ip.len = ee16(IP_HEADER_LEN + TCP_HEADER_LEN); + seg.ip.src = ee32(ts->remote_ip); + seg.ip.dst = ee32(0x0A000001U); + seg.dst_port = ee16(ts->src_port); + seg.src_port = ee16(ts->dst_port); + seg.seq = ee32(0x40000000U); + seg.hlen = TCP_HEADER_LEN << 2; + seg.flags = TCP_FLAG_ACK; + fix_tcp_checksums(&seg); + + tcp_input(&s, TEST_PRIMARY_IF, &seg, (uint32_t)(ETH_HEADER_LEN + IP_HEADER_LEN + TCP_HEADER_LEN)); + ck_assert_int_eq(ts->sock.tcp.state, TCP_ESTABLISHED); + ck_assert_uint_eq(ts->if_idx, TEST_SECOND_IF); + ck_assert_uint_eq(ts->last_pkt_ttl, 7); + + /* An acceptable segment on the same path still records it. */ + seg.seq = ee32(ts->sock.tcp.ack); + fix_tcp_checksums(&seg); + tcp_input(&s, TEST_PRIMARY_IF, &seg, (uint32_t)(ETH_HEADER_LEN + IP_HEADER_LEN + TCP_HEADER_LEN)); + ck_assert_uint_eq(ts->if_idx, TEST_PRIMARY_IF); + ck_assert_uint_eq(ts->last_pkt_ttl, 64); +} +END_TEST + START_TEST(test_tcp_input_unmatched_ack_sends_rst) { struct wolfIP s; @@ -4868,6 +5042,38 @@ START_TEST(test_tcp_input_listen_synack_sends_rst_and_stays_listen) } END_TEST +START_TEST(test_tcp_input_listen_ack_non_local_dst_no_rst) +{ + struct wolfIP s; + int listen_sd; + struct wolfIP_sockaddr_in sin; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + listen_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_STREAM, WI_IPPROTO_TCP); + ck_assert_int_gt(listen_sd, 0); + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = ee16(1234); + sin.sin_addr.s_addr = 0; + 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); + + /* ACK to a broadcast and to an address that is not ours: no reset may be + * sourced from either. */ + last_frame_sent_size = 0; + inject_tcp_segment(&s, TEST_PRIMARY_IF, 0x0A000002U, 0xFFFFFFFFU, + 4321, 1234, 77, 101, TCP_FLAG_ACK); + ck_assert_uint_eq(last_frame_sent_size, 0U); + inject_tcp_segment(&s, TEST_PRIMARY_IF, 0x0A000002U, 0xC0A80163U, + 4321, 1234, 77, 101, TCP_FLAG_ACK); + ck_assert_uint_eq(last_frame_sent_size, 0U); +} +END_TEST + START_TEST(test_tcp_input_listen_accept_final_ack_does_not_send_rst) { struct wolfIP s; @@ -6469,6 +6675,37 @@ START_TEST(test_sock_abort_after_data_rto_rst_covers_sent_data) } END_TEST +/* RFC 6298 5.1: sending new data while the RTO timer runs must not push the + * deadline of the oldest unacknowledged segment. */ +START_TEST(test_tcp_flush_new_data_keeps_running_rto) +{ + struct wolfIP s; + int sd; + struct tsocket *ts; + uint32_t armed_expires; + uint8_t payload[100]; + + sd = llk_accepted_child(&s); + ck_assert_int_ge(sd, 0); + ts = &s.tcpsockets[SOCKET_UNMARK(sd)]; + memset(payload, 0x5A, sizeof(payload)); + ck_assert_int_eq(wolfIP_sock_send(&s, sd, payload, sizeof(payload), 0), + (int)sizeof(payload)); + (void)wolfIP_poll(&s, 2); + ck_assert_int_ne(ts->sock.tcp.tmr_rto, NO_TIMER); + armed_expires = llk_timer_expires(&s, ts->sock.tcp.tmr_rto); + ck_assert_uint_ne(armed_expires, 0); + + ck_assert_int_eq(wolfIP_sock_send(&s, sd, payload, sizeof(payload), 0), + (int)sizeof(payload)); + (void)wolfIP_poll(&s, 300); + ck_assert_uint_eq(ts->sock.tcp.bytes_in_flight, 2 * sizeof(payload)); + ck_assert_int_ne(ts->sock.tcp.tmr_rto, NO_TIMER); + ck_assert_uint_eq(llk_timer_expires(&s, ts->sock.tcp.tmr_rto), + armed_expires); +} +END_TEST + /* A FIN sent behind unacked data stays in the FIFO, flagged sent. */ START_TEST(test_sock_abort_fin_behind_unacked_data_covers_fin) { @@ -7372,6 +7609,67 @@ START_TEST(test_tcp_fin_in_close_wait_does_not_advance_ack) } END_TEST +/* RFC 9293 section 3.10.7.4: in CLOSE_WAIT the peer's FIN has been + * received, so segment text is ignored. Data sent after the FIN must not + * be queued, advance the receive ACK, or raise a readable event after the + * application has already seen end-of-stream. */ +START_TEST(test_tcp_data_after_fin_in_close_wait_ignored) +{ + struct wolfIP s; + int fd; + struct tsocket *lsn; + uint8_t seg_buf[sizeof(struct wolfIP_tcp_seg) + 4]; + struct wolfIP_tcp_seg *seg = (struct wolfIP_tcp_seg *)seg_buf; + uint32_t rcv_nxt; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, LLK_LOCAL_IP, LLK_NET_MASK, 0); + fd = llk_open_listener(&s); + lsn = &s.tcpsockets[SOCKET_UNMARK(fd)]; + + llk_keep_arp_fresh(&s, LLK_ATT_IP); + + llk_attacker_syn(&s, LLK_ATT_IP, 41000, 1, 0); + llk_complete_handshake(&s, lsn, LLK_ATT_IP, 41000, 1); + ck_assert_int_eq(lsn->sock.tcp.state, TCP_ESTABLISHED); + rcv_nxt = lsn->sock.tcp.ack; + + memset(seg_buf, 0, sizeof(seg_buf)); + seg->ip.ver_ihl = 0x45; + seg->ip.proto = WI_IPPROTO_TCP; + seg->ip.ttl = 64; + seg->ip.len = ee16(IP_HEADER_LEN + TCP_HEADER_LEN); + seg->ip.src = ee32(lsn->remote_ip); + seg->ip.dst = ee32(lsn->local_ip); + seg->dst_port = ee16(lsn->src_port); + seg->src_port = ee16(lsn->dst_port); + seg->seq = ee32(rcv_nxt); + seg->ack = ee32(lsn->sock.tcp.snd_una); + seg->hlen = TCP_HEADER_LEN << 2; + seg->flags = TCP_FLAG_FIN | TCP_FLAG_ACK; + fix_tcp_checksums(seg); + tcp_input(&s, TEST_PRIMARY_IF, seg, + (uint32_t)(ETH_HEADER_LEN + IP_HEADER_LEN + TCP_HEADER_LEN)); + ck_assert_int_eq(lsn->sock.tcp.state, TCP_CLOSE_WAIT); + ck_assert_uint_eq(lsn->sock.tcp.ack, rcv_nxt + 1); + lsn->events &= ~CB_EVENT_READABLE; + + /* Data after the FIN, at the new RCV.NXT. */ + seg->ip.len = ee16(IP_HEADER_LEN + TCP_HEADER_LEN + 4); + seg->seq = ee32(rcv_nxt + 1); + seg->flags = TCP_FLAG_ACK; + memcpy(seg->data, "late", 4); + fix_tcp_checksums(seg); + tcp_input(&s, TEST_PRIMARY_IF, seg, + (uint32_t)(ETH_HEADER_LEN + IP_HEADER_LEN + TCP_HEADER_LEN + 4)); + ck_assert_int_eq(lsn->sock.tcp.state, TCP_CLOSE_WAIT); + ck_assert_uint_eq(lsn->sock.tcp.ack, rcv_nxt + 1); + ck_assert_uint_eq(queue_len(&lsn->sock.tcp.rxbuf), 0); + ck_assert_uint_eq(lsn->events & CB_EVENT_READABLE, 0); +} +END_TEST + /* When the timer heap is full, tcp_ctrl_rto_start must not mark the * control RTO active: an active flag with no timer behind it would never * fire and would suppress every other timeout. */ @@ -7719,6 +8017,153 @@ START_TEST(test_flush_tcp_tx_sends_pure_ack_behind_window_blocked_data) } END_TEST +/* A data segment queued with a Timestamp option must carry the TSval/TSecr of + * the moment it is transmitted, not those of the moment it was queued: a later + * pure ACK may already have advanced the peer's TS.Recent, and PAWS would then + * reject the stale TSval (RFC 7323 section 5). */ +START_TEST(test_flush_tcp_tx_refreshes_timestamp_option) +{ + struct wolfIP s; + struct tsocket *ts; + uint8_t buf[ETH_HEADER_LEN + IP_HEADER_LEN + TCP_HEADER_LEN + 12 + 8]; + struct wolfIP_tcp_seg *q = (struct wolfIP_tcp_seg *)buf; + struct tcp_opt_ts *qts; + struct wolfIP_tcp_seg *sent; + struct tcp_opt_ts *sts; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + s.arp.neighbors[0].ip = 0x0A000002U; + s.arp.neighbors[0].if_idx = TEST_PRIMARY_IF; + memcpy(s.arp.neighbors[0].mac, + (uint8_t[]){0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF}, 6); + + ts = &s.tcpsockets[0]; + memset(ts, 0, sizeof(*ts)); + ts->proto = WI_IPPROTO_TCP; + ts->S = &s; + ts->if_idx = TEST_PRIMARY_IF; + ts->sock.tcp.state = TCP_ESTABLISHED; + ts->sock.tcp.ack = 100; + ts->sock.tcp.seq = 1000; + ts->sock.tcp.snd_una = 1000; + ts->sock.tcp.rto = 200; + ts->sock.tcp.cwnd = TCP_MSS * 4; + ts->sock.tcp.peer_rwnd = TCP_MSS * 4; + ts->sock.tcp.ts_enabled = 1; + ts->sock.tcp.ts_recent_valid = 1; + ts->sock.tcp.last_ts = ee32(5000); /* peer's newest TSval, echoed as TSecr */ + ts->src_port = 1234; + ts->dst_port = 4321; + ts->local_ip = 0x0A000001U; + ts->remote_ip = 0x0A000002U; + queue_init(&ts->sock.tcp.rxbuf, ts->rxmem, RXBUF_SIZE, ts->sock.tcp.ack); + fifo_init(&ts->sock.tcp.txbuf, ts->txmem, TXBUF_SIZE); + + /* Data segment queued "earlier": TSval 100, TSecr 7. */ + memset(buf, 0, sizeof(buf)); + q->ip.len = ee16(IP_HEADER_LEN + TCP_HEADER_LEN + 12 + 8); + q->hlen = (TCP_HEADER_LEN + 12) << 2; + q->flags = TCP_FLAG_ACK | TCP_FLAG_PSH; + q->seq = ee32(1000); + q->ack = ee32(100); + q->src_port = ee16(1234); + q->dst_port = ee16(4321); + qts = (struct tcp_opt_ts *)q->data; + qts->opt = TCP_OPTION_TS; + qts->len = TCP_OPTION_TS_LEN; + qts->val = ee32(100); + qts->ecr = ee32(7); + qts->pad = 0x01; + qts->eoo = 0x01; + memset((uint8_t *)q->data + 12, 0xAB, 8); + ck_assert_int_eq(fifo_push(&ts->sock.tcp.txbuf, q, sizeof(buf)), 0); + ts->sock.tcp.seq = 1008; + + mock_link_capture_reset(); + (void)wolfIP_poll(&s, 4000); + + ck_assert_uint_ge(last_frame_sent_count, 1); + sent = (struct wolfIP_tcp_seg *)last_frame_sent; + ck_assert_uint_eq(ee32(sent->seq), 1000); + sts = (struct tcp_opt_ts *)sent->data; + ck_assert_uint_eq(sts->opt, TCP_OPTION_TS); + ck_assert_uint_eq(ee32(sts->val), 4000); + ck_assert_uint_eq(sts->ecr, ee32(5000)); +} +END_TEST + +/* Queue eight 1000-byte segments on an established connection whose cwnd has + * grown to the full peer window (8*SMSS) and count how many go out in one + * poll at time "now". The initial window for this peer is 4*SMSS. */ +static uint32_t flush_tcp_tx_burst_after(uint64_t now) +{ + struct wolfIP s; + struct tsocket *ts; + uint8_t buf[ETH_HEADER_LEN + IP_HEADER_LEN + TCP_HEADER_LEN + 1000]; + struct wolfIP_tcp_seg *q = (struct wolfIP_tcp_seg *)buf; + uint32_t smss; + int i; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + s.arp.neighbors[0].ip = 0x0A000002U; + s.arp.neighbors[0].if_idx = TEST_PRIMARY_IF; + memcpy(s.arp.neighbors[0].mac, + (uint8_t[]){0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF}, 6); + + ts = &s.tcpsockets[0]; + memset(ts, 0, sizeof(*ts)); + ts->proto = WI_IPPROTO_TCP; + ts->S = &s; + ts->if_idx = TEST_PRIMARY_IF; + smss = tcp_cc_mss(ts); + ts->sock.tcp.state = TCP_ESTABLISHED; + ts->sock.tcp.ack = 100; + ts->sock.tcp.seq = 1000; + ts->sock.tcp.snd_una = 1000; + ts->sock.tcp.rto = 200; + ts->sock.tcp.cwnd = smss * 8; + ts->sock.tcp.peer_rwnd = smss * 8; + ts->src_port = 1234; + ts->dst_port = 4321; + ts->local_ip = 0x0A000001U; + ts->remote_ip = 0x0A000002U; + queue_init(&ts->sock.tcp.rxbuf, ts->rxmem, RXBUF_SIZE, ts->sock.tcp.ack); + fifo_init(&ts->sock.tcp.txbuf, ts->txmem, TXBUF_SIZE); + + for (i = 0; i < 8; i++) { + memset(buf, 0, sizeof(buf)); + q->ip.len = ee16(IP_HEADER_LEN + TCP_HEADER_LEN + 1000); + q->hlen = TCP_HEADER_LEN << 2; + q->flags = TCP_FLAG_ACK | TCP_FLAG_PSH; + q->seq = ee32(1000 + (uint32_t)i * 1000); + q->ack = ee32(100); + q->src_port = ee16(1234); + q->dst_port = ee16(4321); + ck_assert_int_eq(fifo_push(&ts->sock.tcp.txbuf, q, sizeof(buf)), 0); + } + ts->sock.tcp.seq = 1000 + 8 * 1000; + + mock_link_capture_reset(); + (void)wolfIP_poll(&s, now); + return last_frame_sent_count; +} + +/* RFC 5681 s4.1: after an idle period longer than the RTO the sender should + * restart from min(IW, cwnd) rather than release a full grown cwnd at once. */ +START_TEST(test_flush_tcp_tx_idle_restart_window) +{ + /* Idle for 5000 ms >> RTO (200 ms): restart window of 4*SMSS, so only + * five of the eight 1000-byte segments fit. */ + ck_assert_uint_eq(flush_tcp_tx_burst_after(5000), 5); + /* Within one RTO of the last transmission the grown cwnd is kept. */ + ck_assert_uint_eq(flush_tcp_tx_burst_after(100), 8); +} +END_TEST + START_TEST(test_flush_tcp_tx_fin_ack_stays_behind_window_blocked_data) { struct wolfIP s; diff --git a/src/test/unit/unit_tests_tcp_state.c b/src/test/unit/unit_tests_tcp_state.c index f0890981..8506c3ef 100644 --- a/src/test/unit/unit_tests_tcp_state.c +++ b/src/test/unit/unit_tests_tcp_state.c @@ -1028,6 +1028,49 @@ START_TEST(test_tcp_input_established_ack_beyond_snd_nxt_keeps_txqueue) } END_TEST +/* F-15311: seq runs ahead by queued-but-unsent data; an ACK covering that + * data is above SND.NXT and must not advance SND.UNA. */ +START_TEST(test_tcp_input_established_ack_of_queued_unsent_data_ignored) +{ + struct wolfIP s; + struct tsocket *ts; + struct tcp_seg_buf segbuf; + struct pkt_desc *desc; + ip4 local_ip = 0x0A000001U; + ip4 remote_ip = 0x0A0000A1U; + uint16_t lport = 9013, rport = 40014; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, local_ip, 0xFFFFFF00U, 0); + + ts = setup_established_socket(&s, local_ip, remote_ip, lport, rport); + ts->sock.tcp.snd_una = 200; + ts->sock.tcp.seq = 400; + ts->sock.tcp.bytes_in_flight = 100; + + memset(&segbuf, 0, sizeof(segbuf)); + segbuf.seg.ip.len = ee16(IP_HEADER_LEN + TCP_HEADER_LEN + 100); + segbuf.seg.hlen = TCP_HEADER_LEN << 2; + segbuf.seg.seq = ee32(200); + ck_assert_int_eq(fifo_push(&ts->sock.tcp.txbuf, &segbuf, sizeof(segbuf)), 0); + desc = fifo_peek(&ts->sock.tcp.txbuf); + ck_assert_ptr_nonnull(desc); + desc->flags |= PKT_FLAG_SENT; + /* Second segment is queued but has not been transmitted. */ + segbuf.seg.seq = ee32(300); + ck_assert_int_eq(fifo_push(&ts->sock.tcp.txbuf, &segbuf, sizeof(segbuf)), 0); + + inject_tcp_segment(&s, TEST_PRIMARY_IF, remote_ip, local_ip, + rport, lport, 100, 400, TCP_FLAG_ACK); + + ck_assert_uint_eq(ts->sock.tcp.snd_una, 200U); + desc = fifo_peek(&ts->sock.tcp.txbuf); + ck_assert_ptr_nonnull(desc); + ck_assert_int_ne(desc->flags & PKT_FLAG_SENT, 0); +} +END_TEST + /* ESTABLISHED ACK above SND.NXT is answered with a challenge ACK */ START_TEST(test_tcp_input_established_ack_beyond_snd_nxt_sends_ack) { @@ -2921,6 +2964,9 @@ START_TEST(test_last_ack_final_ack_delivers_close_event) socket_cb_calls = 0; socket_cb_last_events = 0; + /* Transmit the queued FIN: an ACK can only cover transmitted data. */ + (void)wolfIP_poll(&s, 1); + /* Peer's final ACK acks our FIN (ack == 121): tcp_ack() LAST_ACK branch * transitions to TCP_CLOSED and calls close_socket(). */ inject_tcp_segment(&s, TEST_PRIMARY_IF, remote_ip, local_ip, diff --git a/src/test/unit/unit_tests_vlan.c b/src/test/unit/unit_tests_vlan.c index 76a28c70..621b5d32 100644 --- a/src/test/unit/unit_tests_vlan.c +++ b/src/test/unit/unit_tests_vlan.c @@ -1728,6 +1728,38 @@ START_TEST(test_vlan_mtu_inherited) } END_TEST +/* F-14561: wolfIP_mtu_set() on a VLAN sub-interface must lower that + * sub-interface's MTU, while never exceeding parent MTU - 4. */ +START_TEST(test_vlan_mtu_set_applies_to_subif) +{ + struct wolfIP s; + unsigned int sub_idx = 0xFFFFFFFFu; + uint32_t mtu = 0, parent_mtu = 0; + + setup_vlan_stack(&s); + ck_assert_int_eq(wolfIP_vlan_create(&s, TEST_PRIMARY_IF, 100, 0, 0, &sub_idx), 0); + ck_assert_int_eq(wolfIP_mtu_get(&s, TEST_PRIMARY_IF, &parent_mtu), 0); + + ck_assert_int_eq(wolfIP_mtu_set(&s, sub_idx, 600U), 0); + ck_assert_int_eq(wolfIP_mtu_get(&s, sub_idx, &mtu), 0); + ck_assert_uint_eq(mtu, 600U); + + /* Parent is unaffected. */ + ck_assert_int_eq(wolfIP_mtu_get(&s, TEST_PRIMARY_IF, &mtu), 0); + ck_assert_uint_eq(mtu, parent_mtu); + + /* Resetting to default falls back to the parent-derived value. */ + ck_assert_int_eq(wolfIP_mtu_set(&s, sub_idx, 0), 0); + ck_assert_int_eq(wolfIP_mtu_get(&s, sub_idx, &mtu), 0); + ck_assert_uint_eq(mtu, parent_mtu - 4U); + + /* Larger than parent allows: still capped at parent MTU - 4. */ + ck_assert_int_eq(wolfIP_mtu_set(&s, sub_idx, LINK_MTU), 0); + ck_assert_int_eq(wolfIP_mtu_get(&s, sub_idx, &mtu), 0); + ck_assert_uint_eq(mtu, parent_mtu - 4U); +} +END_TEST + /* F-4948: deleting a VLAN sub-interface must purge the ARP neighbor cache for * that slot. ARP entries are keyed only by (ip, if_idx) with no VID, and * wolfIP_vlan_create reuses the freed slot, so without a purge a new VLAN on @@ -1763,4 +1795,64 @@ START_TEST(test_vlan_delete_purges_arp_neighbor_cache) } END_TEST +#if WOLFIP_IF_MULTICONF +START_TEST(test_vlan_delete_purges_ifaddr_aliases) +{ + struct wolfIP s; + unsigned int sub100 = 0xFFFFFFFFu, sub200 = 0xFFFFFFFFu; + const ip4 primary = 0x0A0A6402U; /* 10.10.100.2 */ + const ip4 alias = 0x0A0A6403U; /* 10.10.100.3 */ + unsigned int found_if = 0; + + setup_vlan_stack(&s); + ck_assert_int_eq(wolfIP_vlan_create(&s, TEST_PRIMARY_IF, 100, 0, 0, + &sub100), 0); + ck_assert_int_eq(wolfIP_ifaddr_add4(&s, sub100, primary, 24), 0); + ck_assert_int_eq(wolfIP_ifaddr_add4(&s, sub100, alias, 24), 0); + ck_assert_int_eq(wolfIP_ifaddr_is_local4(&s, alias, &found_if), 1); + ck_assert_uint_eq(found_if, sub100); + + ck_assert_int_eq(wolfIP_vlan_delete(&s, sub100), 0); + /* The alias must not stay local on the freed index. */ + ck_assert_int_eq(wolfIP_ifaddr_is_local4(&s, alias, NULL), 0); + + /* A new VLAN reusing the slot must start with no addresses. */ + ck_assert_int_eq(wolfIP_vlan_create(&s, TEST_PRIMARY_IF, 200, 0, 0, + &sub200), 0); + ck_assert_uint_eq(sub200, sub100); + ck_assert_uint_eq(wolfIP_ifaddr_count(&s, sub200, AF_INET), 0); +} +END_TEST +#endif /* WOLFIP_IF_MULTICONF */ + +#if WOLFIP_IPV6 +START_TEST(test_vlan_delete_purges_nd6_state) +{ + struct wolfIP s; + unsigned int sub100 = 0xFFFFFFFFu, sub200 = 0xFFFFFFFFu; + static const uint8_t peer_mac[6] = {0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF}; + uint8_t mac[6]; + ip6 peer; + + setup_vlan_stack(&s); + ck_assert_int_eq(atoip6("fe80::1", &peer), 0); + ck_assert_int_eq(wolfIP_vlan_create(&s, TEST_PRIMARY_IF, 100, 0, 0, + &sub100), 0); + ck_assert_int_eq(wolfIP_ipv6_start(&s, sub100), 0); + ck_assert_int_eq(wolfIP_nd6_neighbor_add(&s, sub100, &peer, peer_mac), 0); + ck_assert_int_eq(wolfIP_nd6_lookup(&s, sub100, &peer, mac), 0); + ck_assert_uint_gt(wolfIP_ifaddr_count(&s, sub100, AF_INET6), 0); + + ck_assert_int_eq(wolfIP_vlan_delete(&s, sub100), 0); + ck_assert_int_eq(wolfIP_vlan_create(&s, TEST_PRIMARY_IF, 200, 0, 0, + &sub200), 0); + ck_assert_uint_eq(sub200, sub100); + + ck_assert_uint_eq(wolfIP_ifaddr_count(&s, sub200, AF_INET6), 0); + ck_assert_int_ne(wolfIP_nd6_lookup(&s, sub200, &peer, mac), 0); + ck_assert_uint_eq(s.nd6.started[sub200], 0); +} +END_TEST +#endif /* WOLFIP_IPV6 */ + #endif /* WOLFIP_VLAN */ diff --git a/src/wolfip.c b/src/wolfip.c index 47589112..411a5519 100644 --- a/src/wolfip.c +++ b/src/wolfip.c @@ -91,15 +91,28 @@ struct wolfIP_tcp_seg; struct wolfIP_udp_datagram; struct wolfIP_icmp_packet; +#ifdef IP_MULTICAST +#ifndef WOLFIP_UDP_MCAST_MEMBERSHIPS +#define WOLFIP_UDP_MCAST_MEMBERSHIPS 4 +#endif +#define WOLFIP_MCAST_MEMBERSHIPS (MAX_UDPSOCKETS * WOLFIP_UDP_MCAST_MEMBERSHIPS) +/* Each membership can hold a query-response timer and a join-repeat timer. + * An on-link querier can keep the former pending for up to ~53 minutes, so + * they get slots of their own instead of competing with TCP, DHCP and DNS. */ +#define WOLFIP_MCAST_TIMERS (WOLFIP_MCAST_MEMBERSHIPS * 2) +#else +#define WOLFIP_MCAST_TIMERS 0 +#endif + /* Fixed size binary heap: each element is a timer. */ #if WOLFIP_IPV6 /* Neighbor Discovery drives duplicate address detection, router * solicitation retries and every cache expiry from a single periodic tick, * so it needs one slot rather than one per address. The spare few are for * the heap not to be exactly full when DHCP, DNS and TCP are all armed. */ -#define MAX_TIMERS ((MAX_TCPSOCKETS * 3) + 4) +#define MAX_TIMERS ((MAX_TCPSOCKETS * 3) + 4 + WOLFIP_MCAST_TIMERS) #else -#define MAX_TIMERS (MAX_TCPSOCKETS * 3) +#define MAX_TIMERS ((MAX_TCPSOCKETS * 3) + WOLFIP_MCAST_TIMERS) #endif /* Constants */ @@ -214,12 +227,6 @@ struct wolfIP_icmp_packet; #endif #define TCP_RTO_MAX_BACKOFF 15U /* Max retries before closing; also clamps shift */ -#ifdef IP_MULTICAST -#ifndef WOLFIP_UDP_MCAST_MEMBERSHIPS -#define WOLFIP_UDP_MCAST_MEMBERSHIPS 4 -#endif -#define WOLFIP_MCAST_MEMBERSHIPS (MAX_UDPSOCKETS * WOLFIP_UDP_MCAST_MEMBERSHIPS) -#endif #define TCP_RTO_MIN_MS 1000U #define TCP_RTO_MAX_MS 60000U #define TCP_RTO_G_MS 1U @@ -1318,7 +1325,13 @@ struct tcpsocket { tmr_persist, seq, ack, last, bytes_in_flight, snd_una, recovery_point; uint32_t srtt, rttvar; + /* Time (ms, truncated) the last data segment was sent, for the RFC 5681 + * 4.1 restart window after an idle period. */ + uint32_t last_data_tx; uint32_t last_early_rexmit_ack; + /* Right edge of the receive window last advertised (SWS avoidance). */ + uint32_t rcv_edge; + uint8_t rcv_edge_valid; uint8_t rto_initialized; uint8_t dup_acks; uint8_t fast_recovery; @@ -1582,6 +1595,11 @@ struct arp_neighbor { uint64_t ts; }; +/* Minimum spacing between ARP requests on one interface, whatever the target. */ +#ifndef ARP_REQUEST_MIN_GAP_MS +#define ARP_REQUEST_MIN_GAP_MS 100U +#endif + #ifndef ARP_PENDING_TTL_MS #define ARP_PENDING_TTL_MS 5000U #endif @@ -1880,9 +1898,14 @@ static inline uint32_t wolfIP_ll_frame_mtu(const struct wolfIP_ll_dev *ll) #if WOLFIP_VLAN if (ll && ll->vlan_active && ll->vlan_parent) { uint32_t pmtu = wolfIP_ll_frame_mtu(ll->vlan_parent); - return (pmtu > WOLFIP_VLAN_TAG_LEN) + pmtu = (pmtu > WOLFIP_VLAN_TAG_LEN) ? (pmtu - WOLFIP_VLAN_TAG_LEN) : 0U; + /* A non-zero ll->mtu (wolfIP_mtu_set) further lowers the sub-interface + * MTU; it can never exceed what the parent can carry. */ + if (ll->mtu != 0 && ll->mtu < pmtu) + pmtu = ll->mtu; + return pmtu; } #endif if (!ll || ll->mtu == 0) @@ -1969,6 +1992,14 @@ static inline uint32_t tcp_tx_payload_cap(const struct tsocket *t) if (t && t->proto == WI_IPPROTO_TCP) { uint32_t peer_mss = (uint32_t)t->sock.tcp.peer_mss; + /* RFC 6691: the peer MSS excludes TCP options; the sender must + * reduce the data length by the options it includes. */ + if (t->sock.tcp.ts_enabled && peer_mss > 0) { + if (peer_mss > TCP_OPTIONS_LEN) + peer_mss -= TCP_OPTIONS_LEN; + else + peer_mss = 1; + } if (peer_mss > 0 && peer_mss < cap) cap = peer_mss; } @@ -2354,7 +2385,8 @@ static int wolfIP_ip_is_broadcast(const struct wolfIP *s, ip4 addr) if (conf->ip == IPADDR_ANY) continue; - if (conf->mask == 0 || conf->mask == 0xFFFFFFFFU) + /* /31 (RFC 3021) and /32 have no directed broadcast address. */ + if (conf->mask == 0 || conf->mask >= 0xFFFFFFFEU) continue; directed_bcast = (conf->ip & conf->mask) | (~conf->mask); @@ -2405,6 +2437,29 @@ static int wolfIP_route_lookup_internal(struct wolfIP *s, ip4 dest, best_if = WOLFIP_PRIMARY_IF_IDX; if (dest == IPADDR_ANY || wolfIP_ip_is_broadcast(s, dest)) { + /* Limited broadcast keeps the primary; a subnet-directed broadcast + * leaves via the interface that owns that subnet (primary wins a tie). */ + if (dest != IPADDR_ANY && dest != 0xFFFFFFFFU) { + unsigned int owner = s->if_count; + + for (i = 0; i < s->if_count; i++) { + const struct ipconf *conf = wolfIP_ipconf_at(s, i); + + if (!conf || conf->ip == IPADDR_ANY || + conf->mask == 0 || conf->mask == 0xFFFFFFFFU) + continue; + if (dest != ((conf->ip & conf->mask) | ~conf->mask)) + continue; + if (i == best_if) { + owner = i; + break; + } + if (owner == s->if_count) + owner = i; + } + if (owner < s->if_count) + best_if = owner; + } if (if_idx) *if_idx = best_if; if (nexthop) @@ -4136,16 +4191,54 @@ static struct tsocket *tcp_new_socket(struct wolfIP *s) return NULL; } -static uint16_t tcp_adv_win(const struct tsocket *t, uint8_t apply_wscale) +/* Receiver-side SWS avoidance (RFC 9293 3.8.6.2.2, MUST-39): the right window + * edge advertised last is kept fixed as the application drains the receive + * buffer, and only advances once min(RCV.BUFF/2, Eff.snd.MSS) bytes are free + * beyond it. Only tracked in states that accept data. */ +static int tcp_rcv_edge_tracked(const struct tsocket *t) +{ + return t->sock.tcp.state == TCP_ESTABLISHED || + t->sock.tcp.state == TCP_FIN_WAIT_1 || + t->sock.tcp.state == TCP_FIN_WAIT_2 || + t->sock.tcp.state == TCP_CLOSE_WAIT; +} + +static uint16_t tcp_calc_win(const struct tsocket *t, uint8_t apply_wscale) { uint32_t space = queue_space((struct queue *)&t->sock.tcp.rxbuf); uint8_t shift = (apply_wscale && t->sock.tcp.ws_enabled) ? t->sock.tcp.rcv_wscale : 0; - uint32_t win = space >> shift; + uint32_t win; + if (t->sock.tcp.rcv_edge_valid && tcp_rcv_edge_tracked(t)) { + uint32_t held = t->sock.tcp.rcv_edge - t->sock.tcp.ack; + if (held <= space) { + uint32_t thresh = t->sock.tcp.rxbuf.size / 2; + if (t->sock.tcp.peer_mss != 0 && t->sock.tcp.peer_mss < thresh) + thresh = t->sock.tcp.peer_mss; + if (space - held < thresh) { + space = held; + /* Round up to the scale granularity so the advertised right + * edge is not pulled left (RFC 9293 3.8.6, SHLD-14). */ + if (shift) + space = (space + (1U << shift) - 1U) & ~((1U << shift) - 1U); + } + } + } + win = space >> shift; if (win > 0xFFFF) win = 0xFFFF; return (uint16_t)win; } +/* Window to put in an outgoing segment; records the advertised right edge. */ +static uint16_t tcp_adv_win(struct tsocket *t, uint8_t apply_wscale) +{ + uint16_t win = tcp_calc_win(t, apply_wscale); + uint8_t shift = (apply_wscale && t->sock.tcp.ws_enabled) ? t->sock.tcp.rcv_wscale : 0; + t->sock.tcp.rcv_edge_valid = tcp_rcv_edge_tracked(t); + t->sock.tcp.rcv_edge = t->sock.tcp.ack + ((uint32_t)win << shift); + return win; +} + static int tcp_segment_acceptable(const struct tsocket *t, const struct wolfIP_tcp_seg *tcp, uint32_t tcplen) { @@ -4317,7 +4410,7 @@ static uint8_t tcp_merge_sack_blocks(struct tcp_sack_block *blocks, uint8_t coun static void tcp_rebuild_rx_sack(struct tsocket *t, uint32_t trig_seq, uint32_t trig_len) { - struct tcp_sack_block blocks[TCP_OOO_MAX_SEGS]; + struct tcp_sack_block blocks[TCP_OOO_MAX_SEGS + 1]; uint8_t i, count = 0; /* RFC 2018 model: @@ -4335,6 +4428,13 @@ static void tcp_rebuild_rx_sack(struct tsocket *t, uint32_t trig_seq, t->sock.tcp.ooo[i].len); count++; } + /* RFC 2018 sec.8: the triggering segment is reported even when it could not + * be cached. If it is cached it lies inside a block above and merges away. */ + if (trig_len != 0U) { + blocks[count].left = trig_seq; + blocks[count].right = tcp_seq_inc(trig_seq, trig_len); + count++; + } count = tcp_merge_sack_blocks(blocks, count); t->sock.tcp.rx_sack_count = 0; while (count > 0 && t->sock.tcp.rx_sack_count < TCP_SACK_MAX_BLOCKS) { @@ -5562,6 +5662,8 @@ static void tcp_recv_len(struct tsocket *t, struct wolfIP_tcp_seg *seg, } else if (tcp_seq_lt(t->sock.tcp.ack, seq)) { /* Hole detected: segment starts above ACK, so cache it as OOO and * immediately ACK with SACK blocks describing what we already have. */ + /* A segment that could not be cached is not SACKed: the sender + * would treat it as received and skip fast retransmit for it. */ (void)tcp_store_ooo_segment(t, payload, seq, seg_len); tcp_send_ack(t); } @@ -6109,17 +6211,14 @@ static int ip_output_add_header(struct tsocket *t, struct wolfIP_ip_packet *ip, #include "src/wolfip6.c" #endif /* WOLFIP_IPV6 */ -/* Process timestamp option, calculate RTT */ -static int tcp_process_ts(struct tsocket *t, const struct wolfIP_tcp_seg *tcp, - uint32_t frame_len) +/* Parse the timestamp option and update TS.Recent. Returns 0 and fills *po + * when the option is present, -1 otherwise. */ +static int tcp_update_ts_recent(struct tsocket *t, + const struct wolfIP_tcp_seg *tcp, uint32_t frame_len, + struct tcp_parsed_opts *po) { - struct tcp_parsed_opts po; - uint32_t sample; - - if (!t->S) - return -1; - tcp_parse_options(tcp, frame_len, &po); - if (!po.ts_found) + tcp_parse_options(tcp, frame_len, po); + if (!po->ts_found) return -1; /* RFC 7323 section 4.3 rule (2): TS.Recent is replaced only when the * segment's TSval is not older than the stored one and the segment's @@ -6138,11 +6237,27 @@ static int tcp_process_ts(struct tsocket *t, const struct wolfIP_tcp_seg *tcp, * so compare in host order as tcp_paws_check does: byte-swapped * values are not ordered. */ if (!t->sock.tcp.ts_recent_valid || - (!tcp_seq_lt(po.ts_val, ee32(t->sock.tcp.last_ts)) && + (!tcp_seq_lt(po->ts_val, ee32(t->sock.tcp.last_ts)) && tcp_seq_leq(ee32(tcp->seq), t->sock.tcp.ack))) { - t->sock.tcp.last_ts = ee32(po.ts_val); + t->sock.tcp.last_ts = ee32(po->ts_val); t->sock.tcp.ts_recent_valid = 1; } + return 0; +} + +/* Process timestamp option, calculate RTT. RFC 7323 section 4.1: the TSecr + * RTT sample is valid only for an ACK that advances SND.UNA, so callers must + * use it only from that path; tcp_update_ts_recent() alone is for the rest. */ +static int tcp_process_ts(struct tsocket *t, const struct wolfIP_tcp_seg *tcp, + uint32_t frame_len) +{ + struct tcp_parsed_opts po; + uint32_t sample; + + if (!t->S) + return -1; + if (tcp_update_ts_recent(t, tcp, frame_len, &po) < 0) + return -1; if (po.ts_ecr == 0) return -1; /* No echoed timestamp; fall back to coarse RTT. */ if (po.ts_ecr > t->S->last_tick) @@ -6394,20 +6509,15 @@ static int tcp_mark_unsacked_for_retransmit(struct tsocket *t, uint32_t ack) } } -/* Check whether the ack acknowledges something we have actually sent */ -static int tcp_ack_acceptable(const struct tsocket *t, uint32_t ack) +/* Check whether the ack acknowledges something we have actually sent: seq + * runs ahead of SND.NXT by queued data, so bound by the transmitted edge. */ +static int tcp_ack_acceptable(struct tsocket *t, uint32_t ack) { - uint32_t max_ack = t->sock.tcp.seq; - if (t->sock.tcp.state == TCP_FIN_WAIT_1 || - t->sock.tcp.state == TCP_FIN_WAIT_2 || - t->sock.tcp.state == TCP_CLOSING || - t->sock.tcp.state == TCP_LAST_ACK) - max_ack = tcp_seq_inc(max_ack, 1); - return tcp_seq_leq(ack, max_ack); + return tcp_seq_leq(ack, tcp_snd_nxt(t)); } /* Check whether the segment is entitled to update the peer receive window */ -static int tcp_window_update_ok(const struct tsocket *t, +static int tcp_window_update_ok(struct tsocket *t, const struct wolfIP_tcp_seg *tcp, uint32_t tcplen) { uint32_t seg_ack = ee32(tcp->ack); @@ -6695,6 +6805,9 @@ static void tcp_ack_len(struct tsocket *t, const struct wolfIP_tcp_seg *tcp, return; if (win_changed) return; + /* RFC 5681 s2: the SYN and FIN bits must both be off. */ + if (tcp->flags & (TCP_FLAG_SYN | TCP_FLAG_FIN)) + return; if (t->sock.tcp.dup_acks < 255) t->sock.tcp.dup_acks++; if (t->sock.tcp.peer_sack_count > 0 && @@ -6980,6 +7093,42 @@ static void tcp_ack(struct tsocket *t, const struct wolfIP_tcp_seg *tcp) (uint32_t)(ip_len - IP_HEADER_LEN) : 0u); } +/* Record the arrival interface and TTL only for a segment that has passed + * the acceptability checks of its state: the 4-tuple alone is not secret, so + * an off-path segment must not steer the egress/MTU choice or the reported + * received TTL. */ +static void tcp_commit_rx_path(struct tsocket *t, unsigned int if_idx, + const struct ip_flow *flow) +{ + t->if_idx = (uint8_t)if_idx; + t->last_pkt_ttl = flow->ttl; +} + +/* A reset takes its source address from the destination of the segment it + * answers, so it may only be sent when that destination is one of our own + * unicast addresses: answering anything else would make the stack a + * reflector, and the reset would carry a broadcast or forged source. */ +static int tcp_reset_dst_is_local(struct wolfIP *S, unsigned int if_idx, + const struct ip_flow *flow) +{ + int dst_match = 0; + +#if WOLFIP_IPV6 + if (flow->is_v6) { + (void)wolfIP_if_for_local_ip6(S, if_idx, &flow->dst6, &dst_match); + return dst_match && !ip6_is_multicast(&flow->dst6); + } +#else + (void)if_idx; +#endif + if ((flow->dst == IPADDR_ANY) || + wolfIP_ip_is_broadcast(S, flow->dst) || + wolfIP_ip_is_multicast(flow->dst)) + return 0; + (void)wolfIP_if_for_local_ip(S, flow->dst, &dst_match); + return dst_match; +} + static void tcp_input_flow(struct wolfIP *S, unsigned int if_idx, struct wolfIP_tcp_seg *tcp, uint32_t frame_len, const struct ip_flow *flow) @@ -7065,7 +7214,7 @@ static void tcp_input_flow(struct wolfIP *S, unsigned int if_idx, * address; a wildcard listener accepts any local address. * The SYN path already enforces this for SYNs, but without it * here a non-SYN segment for a different local IP on the same - * host overwrites the listener's if_idx/last_pkt_ttl/peer_rwnd + * host overwrites the listener's peer_rwnd * and sets matched, which corrupts listener MTU/TTL * bookkeeping and suppresses the RFC 793 unmatched RST. * bound_local_ip (not local_ip) is the discriminator: a @@ -7084,8 +7233,6 @@ static void tcp_input_flow(struct wolfIP *S, unsigned int if_idx, continue; } } - t->if_idx = (uint8_t)if_idx; - t->last_pkt_ttl = flow->ttl; matched = 1; /* Validate minimum TCP header length (data offset). */ if (tcp_data_offset_bytes(tcp->hlen) < TCP_HEADER_LEN) { @@ -7101,7 +7248,8 @@ static void tcp_input_flow(struct wolfIP *S, unsigned int if_idx, if (tcp->flags & TCP_FLAG_RST) continue; if (tcp->flags & TCP_FLAG_ACK) { - if (!tcp_listen_ack_matches_child_socket(S, t, tcp, flow)) + if (!tcp_listen_ack_matches_child_socket(S, t, tcp, flow) && + tcp_reset_dst_is_local(S, if_idx, flow)) tcp_reset_reply_flow(S, if_idx, tcp, flow); continue; } @@ -7284,6 +7432,7 @@ static void tcp_input_flow(struct wolfIP *S, unsigned int if_idx, continue; t->if_idx = (uint8_t)dst_if; + t->last_pkt_ttl = flow->ttl; t->sock.tcp.state = TCP_SYN_RCVD; t->sock.tcp.ack = tcp_seq_inc(ee32(tcp->seq), 1); t->sock.tcp.seq = wolfIP_getrandom(); @@ -7303,6 +7452,11 @@ static void tcp_input_flow(struct wolfIP *S, unsigned int if_idx, /* Only reached for a SYN-ACK whose ACK number was * acceptable (the check above RSTs the bad ones). */ if (tcp->flags == (TCP_FLAG_SYN | TCP_FLAG_ACK)) { + /* RFC 5681 s3.1: after a lost SYN the initial + * window is one segment. */ + int syn_lost = (t->sock.tcp.ctrl_rto_retries > 0); + + tcp_commit_rx_path(t, if_idx, flow); t->sock.tcp.state = TCP_ESTABLISHED; tcp_ctrl_rto_stop(t); t->sock.tcp.ack = tcp_seq_inc(ee32(tcp->seq), 1); @@ -7310,7 +7464,8 @@ static void tcp_input_flow(struct wolfIP *S, unsigned int if_idx, t->sock.tcp.snd_una = t->sock.tcp.seq; t->sock.tcp.recovery_point = t->sock.tcp.snd_una; t->sock.tcp.fast_recovery = 0; - t->sock.tcp.cwnd = tcp_initial_cwnd(t->sock.tcp.peer_rwnd, tcp_cc_mss(t)); + t->sock.tcp.cwnd = syn_lost ? tcp_cc_mss(t) : + tcp_initial_cwnd(t->sock.tcp.peer_rwnd, tcp_cc_mss(t)); t->sock.tcp.ssthresh = tcp_initial_ssthresh(t->sock.tcp.peer_rwnd); if (tx_has_writable_space(t)) t->events |= CB_EVENT_WRITABLE; @@ -7340,6 +7495,7 @@ static void tcp_input_flow(struct wolfIP *S, unsigned int if_idx, } if (tcp->flags & TCP_FLAG_ACK) { uint32_t expected_ack = tcp_seq_inc(t->sock.tcp.snd_una, 1); + int syn_lost; /* RFC 9293 section 3.10.7.4: the sequence acceptability * test (Table 6) applies before the final-ACK transition. * A segment that begins above RCV.NXT but inside the @@ -7357,6 +7513,10 @@ static void tcp_input_flow(struct wolfIP *S, unsigned int if_idx, tcp_reset_reply_flow(S, if_idx, tcp, flow); continue; } + /* RFC 5681 s3.1: after a lost SYN/ACK the initial window + * is one segment. */ + syn_lost = (t->sock.tcp.ctrl_rto_retries > 0); + tcp_commit_rx_path(t, if_idx, flow); t->sock.tcp.state = TCP_ESTABLISHED; tcp_ctrl_rto_stop(t); if (t->sock.tcp.parked) { @@ -7381,7 +7541,8 @@ static void tcp_input_flow(struct wolfIP *S, unsigned int if_idx, * filled. */ t->sock.tcp.seq = ee32(tcp->ack); t->sock.tcp.snd_una = t->sock.tcp.seq; - t->sock.tcp.cwnd = tcp_initial_cwnd(t->sock.tcp.peer_rwnd, tcp_cc_mss(t)); + t->sock.tcp.cwnd = syn_lost ? tcp_cc_mss(t) : + tcp_initial_cwnd(t->sock.tcp.peer_rwnd, tcp_cc_mss(t)); t->sock.tcp.ssthresh = tcp_initial_ssthresh(t->sock.tcp.peer_rwnd); if (tx_has_writable_space(t)) t->events |= CB_EVENT_WRITABLE; @@ -7451,14 +7612,17 @@ static void tcp_input_flow(struct wolfIP *S, unsigned int if_idx, tcp_send_ack(t); continue; } + tcp_commit_rx_path(t, if_idx, flow); if (flow->is_v6) tcp_ack_len(t, tcp, flow->transport_len); else tcp_ack(t, tcp); /* tcp_ack may have closed the socket via close_socket(); * skip timestamp processing if socket was destroyed. */ - if (t->sock.tcp.state != TCP_CLOSED) - tcp_process_ts(t, tcp, frame_len); + if (t->sock.tcp.state != TCP_CLOSED) { + struct tcp_parsed_opts po; + (void)tcp_update_ts_recent(t, tcp, frame_len, &po); + } } } else if ((t->sock.tcp.state == TCP_ESTABLISHED) || @@ -7498,18 +7662,26 @@ static void tcp_input_flow(struct wolfIP *S, unsigned int if_idx, continue; } + tcp_commit_rx_path(t, if_idx, flow); if (flow->is_v6) tcp_ack_len(t, tcp, flow->transport_len); else tcp_ack(t, tcp); if (t->sock.tcp.state == TCP_CLOSED) continue; - tcp_process_ts(t, tcp, frame_len); + { + struct tcp_parsed_opts po; + (void)tcp_update_ts_recent(t, tcp, frame_len, &po); + } if (tcplen > 0) { if ((t->sock.tcp.state == TCP_LAST_ACK) || (t->sock.tcp.state == TCP_CLOSING) || (t->sock.tcp.state == TCP_CLOSED)) return; - { + if (t->sock.tcp.state == TCP_CLOSE_WAIT) { + /* RFC 9293 section 3.10.7.4: the peer's FIN was + * already received, so segment text is ignored. */ + tcp_send_ack(t); + } else { if (flow->is_v6) tcp_recv_len(t, tcp, tcplen); else @@ -7563,26 +7735,10 @@ static void tcp_input_flow(struct wolfIP *S, unsigned int if_idx, } } if (!matched) { - int dst_match = 0; - /* A segment to a port nobody holds is answered with a reset, but - * only when the destination really is ours: answering anything else - * would make the stack a reflector. */ -#if WOLFIP_IPV6 - if (flow->is_v6) { - (void)wolfIP_if_for_local_ip6(S, if_idx, &flow->dst6, &dst_match); - if (dst_match && !ip6_is_multicast(&flow->dst6)) - tcp_reset_reply_flow(S, if_idx, tcp, flow); - return; - } -#endif - if ((flow->dst != IPADDR_ANY) && - !wolfIP_ip_is_broadcast(S, flow->dst) && - !wolfIP_ip_is_multicast(flow->dst)) { - (void)wolfIP_if_for_local_ip(S, flow->dst, &dst_match); - if (dst_match) - tcp_reset_reply_flow(S, if_idx, tcp, flow); - } + * only when the destination really is ours. */ + if (tcp_reset_dst_is_local(S, if_idx, flow)) + tcp_reset_reply_flow(S, if_idx, tcp, flow); } } @@ -9735,7 +9891,9 @@ int wolfIP_sock_sendto(struct wolfIP *s, int sockfd, const void *buf, size_t len return -WOLFIP_EINVAL; if_idx = raw_route_for_ip(s, rs, dst_ip, use_dontroute); rs->if_idx = (uint8_t)if_idx; - if (total_len > LINK_MTU) + /* wolfIP_ll_xmit() rejects frames above the egress frame MTU, which + * would leave the descriptor stuck at the FIFO head forever. */ + if (total_len > wolfIP_frame_mtu(s, if_idx)) return -WOLFIP_EINVAL; if (fifo_space(&rs->txbuf) < total_len) return -WOLFIP_EAGAIN; @@ -9845,10 +10003,10 @@ int wolfIP_sock_recvfrom(struct wolfIP *s, int sockfd, void *buf, size_t len, in if (queue_len(&ts->sock.tcp.rxbuf) == 0) return 0; { - uint16_t win_before = tcp_adv_win(ts, 1); + uint16_t win_before = tcp_calc_win(ts, 1); int ret = queue_pop(&ts->sock.tcp.rxbuf, buf, len); if (ret > 0) { - uint16_t win_after = tcp_adv_win(ts, 1); + uint16_t win_after = tcp_calc_win(ts, 1); if (queue_len(&ts->sock.tcp.rxbuf) > 0) { ts->events |= CB_EVENT_READABLE; if (ts->callback) @@ -9862,10 +10020,10 @@ int wolfIP_sock_recvfrom(struct wolfIP *s, int sockfd, void *buf, size_t len, in } else if (ts->sock.tcp.state == TCP_ESTABLISHED || ts->sock.tcp.state == TCP_FIN_WAIT_1 || ts->sock.tcp.state == TCP_FIN_WAIT_2) { - uint16_t win_before = tcp_adv_win(ts, 1); + uint16_t win_before = tcp_calc_win(ts, 1); int ret = queue_pop(&ts->sock.tcp.rxbuf, buf, len); if (ret > 0) { - uint16_t win_after = tcp_adv_win(ts, 1); + uint16_t win_after = tcp_calc_win(ts, 1); if (queue_len(&ts->sock.tcp.rxbuf) > 0) { ts->events |= CB_EVENT_READABLE; if (ts->callback) @@ -11103,6 +11261,22 @@ static int sock_bind6(struct wolfIP *s, int sockfd, struct tsocket *ts, } if (!match) return -1; /* not one of ours, or still tentative */ + /* Port 0 asks for an ephemeral port, as on the IPv4 path. */ + if (port == 0) { + uint16_t scanned; + port = (uint16_t)(wolfIP_getrandom() & 0xFFFF); + if (port < 1024) + port = (uint16_t)(1024 + (port % (0x10000 - 1024))); + for (scanned = 0; scanned < (uint16_t)(0x10000 - 1024); scanned++) { + if (!bind_port_in_use6(arr, arr_len, ts, addr, port)) + break; + port++; + if (port < 1024) + port = 1024; + } + if (scanned == (uint16_t)(0x10000 - 1024)) + return -1; + } if (bind_port_in_use6(arr, arr_len, ts, addr, port)) return -1; ts->if_idx = (uint8_t)if_idx; @@ -11275,6 +11449,16 @@ int wolfIP_sock_bind(struct wolfIP *s, int sockfd, const struct wolfIP_sockaddr else ts->local_ip = IPADDR_ANY; } + /* Port 0 asks for an ephemeral port, as connect()/sendto() do. */ + if (new_port == 0) { + new_port = port_alloc_random(s->tcpsockets, MAX_TCPSOCKETS, ts, + bind_ip, 1024); + if (new_port == 0) { + ts->local_ip = prev_ip; + ts->if_idx = prev_if_idx; + return -1; + } + } if (bind_port_in_use(s->tcpsockets, MAX_TCPSOCKETS, ts, bind_ip, new_port)) { ts->local_ip = prev_ip; @@ -11319,6 +11503,16 @@ int wolfIP_sock_bind(struct wolfIP *s, int sockfd, const struct wolfIP_sockaddr else ts->local_ip = IPADDR_ANY; } + /* Port 0 asks for an ephemeral port, as connect()/sendto() do. */ + if (new_port == 0) { + new_port = port_alloc_random(s->udpsockets, MAX_UDPSOCKETS, ts, + bind_ip, 1024); + if (new_port == 0) { + ts->local_ip = prev_ip; + ts->if_idx = prev_if_idx; + return -1; + } + } if (bind_port_in_use(s->udpsockets, MAX_UDPSOCKETS, ts, bind_ip, new_port)) { ts->local_ip = prev_ip; @@ -11620,11 +11814,22 @@ static void icmp_input(struct wolfIP *s, unsigned int if_idx, struct wolfIP_ip_p icmp_try_recv(s, if_idx, icmp, len); } +/* Head of a UDP socket's RX queue, or NULL if empty. dispatch_events() clears + * the READABLE event before invoking the callback and UDP recvfrom does not + * raise it again, so the stack's own consumers drain the queue themselves. */ +static struct pkt_desc *udp_rx_head(struct wolfIP *s, int sd) +{ + if (sd <= 0 || SOCKET_UNMARK(sd) >= MAX_UDPSOCKETS) + return NULL; + return fifo_peek(&s->udpsockets[SOCKET_UNMARK(sd)].sock.udp.rxbuf); +} + #if WOLFIP_ENABLE_DHCP static int dhcp_send_discover(struct wolfIP *s); static int dhcp_send_request(struct wolfIP *s); #ifdef ETHERNET static int dhcp_send_dad_probe(struct wolfIP *s); +static int dhcp_send_arp_announce(struct wolfIP *s); static void dhcp_dad_conflict(struct wolfIP *s); #endif static void dhcp_timer_cb(void *arg); @@ -11895,6 +12100,7 @@ static void dhcp_timer_cb(void *arg) s->dhcp_dad_probes = 0; s->dhcp_state = DHCP_BOUND; dhcp_schedule_timer_at(s, s->dhcp_renew_at); + dhcp_send_arp_announce(s); } break; #endif @@ -12614,11 +12820,17 @@ static int dhcp_send_request(struct wolfIP *s) static void dhcp_callback(int sockfd, uint16_t ev, void *arg) { struct wolfIP *s = (struct wolfIP *)arg; + struct pkt_desc *head; (void)sockfd; (void)ev; if (!s) return; - dhcp_poll(s); + /* Keep reading while each pass consumes a datagram and more remain. */ + do { + head = udp_rx_head(s, s->dhcp_udp_sd); + dhcp_poll(s); + } while (head && (udp_rx_head(s, s->dhcp_udp_sd) != NULL) && + (udp_rx_head(s, s->dhcp_udp_sd) != head)); } static int dhcp_send_discover(struct wolfIP *s) @@ -13025,6 +13237,7 @@ static void arp_pending_record(struct wolfIP *s, unsigned int if_idx, ip4 ip) static void arp_request(struct wolfIP *s, unsigned int if_idx, ip4 tip) { struct arp_packet arp; + int i; struct wolfIP_ll_dev *ll = wolfIP_ll_at(s, if_idx); struct ipconf *conf; @@ -13034,14 +13247,44 @@ static void arp_request(struct wolfIP *s, unsigned int if_idx, ip4 tip) if (!conf) return; - /* One request per second per interface. last_arp == 0 means "never - * sent" (fresh boot, or a tick-source restart that reset the limiter), - * so the first request is never held back by the window. */ + /* Throttle per target (one request per second for each tip), plus a + * short per-interface gap that bounds the aggregate rate. A single + * per-interface window would let the first unresolvable neighbor in + * transmit order win every window and starve all other targets. + * last_arp == 0 means "never sent" (fresh boot, or a tick-source + * restart that reset the limiter), so the first request is never held + * back by the window. */ if (s->arp.last_arp[if_idx] != 0 && - s->arp.last_arp[if_idx] + 1000 > s->last_tick) { - wolfIP_poll_by(s, s->arp.last_arp[if_idx] + 1000); + s->arp.last_arp[if_idx] + ARP_REQUEST_MIN_GAP_MS > s->last_tick) { + wolfIP_poll_by(s, s->arp.last_arp[if_idx] + ARP_REQUEST_MIN_GAP_MS); return; } + for (i = 0; i < WOLFIP_ARP_PENDING_MAX; i++) { + struct arp_pending_req *p = &s->arp.pending[i]; + if (p->ip == tip && p->if_idx == (uint8_t)if_idx && + s->last_tick >= p->ts && p->ts + 1000 > s->last_tick) { + wolfIP_poll_by(s, p->ts + 1000); + return; + } + } + /* Do not evict a pending entry still inside its 1s window: that would + * drop its throttle and let it be asked again after only the short + * per-interface gap. Wait until some slot frees up instead. */ + { + uint64_t next_free = (uint64_t)-1; + for (i = 0; i < WOLFIP_ARP_PENDING_MAX; i++) { + struct arp_pending_req *p = &s->arp.pending[i]; + if (p->ip == IPADDR_ANY || s->last_tick < p->ts || + p->ts + 1000 <= s->last_tick) + break; + if (p->ts + 1000 < next_free) + next_free = p->ts + 1000; + } + if (i == WOLFIP_ARP_PENDING_MAX) { + wolfIP_poll_by(s, next_free); + return; + } + } /* Store tick+1 so a request sent at tick 0 is distinguishable from * "never sent" (last_arp == 0). */ s->arp.last_arp[if_idx] = s->last_tick + 1; @@ -13098,8 +13341,60 @@ static int dhcp_send_dad_probe(struct wolfIP *s) return wolfIP_ll_send_frame(s, WOLFIP_PRIMARY_IF_IDX, &arp, sizeof(struct arp_packet)); } + +/* RFC 2131 §3.2: once the leased address is verified, broadcast an ARP + * reply (sender = target = our address) to clear stale cache entries for + * it on the subnet. Best effort; a lost announcement is not retried. */ +static int dhcp_send_arp_announce(struct wolfIP *s) +{ + struct arp_packet arp; + struct wolfIP_ll_dev *ll; + struct ipconf *primary; + + ll = wolfIP_ll_at(s, WOLFIP_PRIMARY_IF_IDX); + if (!ll || ll->non_ethernet) + return -1; + primary = wolfIP_ipconf_at(s, WOLFIP_PRIMARY_IF_IDX); + if (!primary || primary->ip == IPADDR_ANY) + return -1; + + memset(&arp, 0, sizeof(arp)); + eth_output_add_header(s, WOLFIP_PRIMARY_IF_IDX, NULL, &arp.eth, + ETH_TYPE_ARP); + arp.htype = ee16(1); /* Ethernet */ + arp.ptype = ee16(0x0800); + arp.hlen = 6; + arp.plen = 4; + arp.opcode = ee16(ARP_REPLY); + memcpy(arp.sma, ll->mac, 6); + arp.sip = ee32(primary->ip); + memset(arp.tma, 0xFF, 6); + arp.tip = ee32(primary->ip); + return wolfIP_ll_send_frame(s, WOLFIP_PRIMARY_IF_IDX, &arp, + sizeof(struct arp_packet)); +} #endif /* WOLFIP_ENABLE_DHCP */ +/* True when the (network order) ARP target is the primary address of this + * interface or, with WOLFIP_IF_MULTICONF, one of its IPv4 aliases. */ +static int arp_tip_is_ours(struct wolfIP *s, unsigned int if_idx, + const struct ipconf *conf, uint32_t tip) +{ + if (conf->ip != IPADDR_ANY && tip == ee32(conf->ip)) + return 1; +#if WOLFIP_IF_MULTICONF + { + unsigned int owner = 0; + if (wolfIP_ifaddr_is_local4(s, ee32(tip), &owner) && owner == if_idx) + return 1; + } +#else + (void)s; + (void)if_idx; +#endif + return 0; +} + static void arp_recv(struct wolfIP *s, unsigned int if_idx, void *buf, int len) { struct arp_packet *arp = (struct arp_packet *)buf; @@ -13146,16 +13441,17 @@ static void arp_recv(struct wolfIP *s, unsigned int if_idx, void *buf, int len) /* An unconfigured interface (no assigned address) must not answer * ARP requests: matching tip against a zero conf->ip would let a * request for 0.0.0.0 be answered by advertising 0.0.0.0. */ - if (arp->opcode == ee16(ARP_REQUEST) && conf->ip != IPADDR_ANY && - arp->tip == ee32(conf->ip)) { + if (arp->opcode == ee16(ARP_REQUEST) && + arp_tip_is_ours(s, if_idx, conf, arp->tip)) { uint32_t sender_ip = arp->sip; + uint32_t target_ip = arp->tip; uint8_t sender_mac[6]; memcpy(sender_mac, arp->sma, 6); arp->opcode = ee16(ARP_REPLY); memcpy(arp->tma, arp->sma, 6); memcpy(arp->sma, ll->mac, 6); arp->tip = arp->sip; - arp->sip = ee32(conf->ip); + arp->sip = target_ip; { ip4 sip = ee32(sender_ip); /* Validate sender IP before caching: reject broadcast, @@ -13473,6 +13769,45 @@ int wolfIP_vlan_delete(struct wolfIP *s, unsigned int if_idx) } s->arp.last_arp[if_idx] = 0; } +#if WOLFIP_IF_MULTICONF + /* Drop the IPv4 aliases and IPv6 addresses held on this slot, as the + * primary address was just wiped above. Otherwise they would stay local + * on the dead index and be inherited by the next VLAN created here. */ + { + int i; + for (i = 0; i < (int)WOLFIP_IFADDR_MAX; i++) { + if (s->ifaddr[i].used && + s->ifaddr[i].info.if_idx == (uint8_t)if_idx) + s->ifaddr[i].used = 0; + } + } +#endif +#if WOLFIP_IPV6 + /* Same for Neighbor Discovery state keyed by this index. */ + { + int i; + for (i = 0; i < WOLFIP_ND6_CACHE_SIZE; i++) { + if (s->nd6.neighbors[i].state != 0 && + s->nd6.neighbors[i].if_idx == (uint8_t)if_idx) + memset(&s->nd6.neighbors[i], 0, sizeof(s->nd6.neighbors[i])); + } + for (i = 0; i < WOLFIP_ND6_PREFIX_MAX; i++) { + if (s->nd6.prefixes[i].used && + s->nd6.prefixes[i].if_idx == (uint8_t)if_idx) + s->nd6.prefixes[i].used = 0; + } + for (i = 0; i < WOLFIP_ND6_ROUTER_MAX; i++) { + if (s->nd6.routers[i].used && + s->nd6.routers[i].if_idx == (uint8_t)if_idx) + s->nd6.routers[i].used = 0; + } + s->nd6.started[if_idx] = 0; + s->nd6.rs_left[if_idx] = 0; + s->nd6.rs_due[if_idx] = 0; + s->nd6.iid_valid[if_idx] = 0; + memset(s->nd6.iid[if_idx], 0, sizeof(s->nd6.iid[if_idx])); + } +#endif return 0; } @@ -13844,7 +14179,10 @@ static inline void ip_recv(struct wolfIP *s, unsigned int if_idx, return; } if (ip->ttl <= 1) { - wolfIP_send_ttl_exceeded(s, if_idx, ip); + /* RFC 1812 4.3.2.7: no ICMP error for a multicast + * destination. */ + if (!wolfIP_ip_is_multicast(dest)) + wolfIP_send_ttl_exceeded(s, if_idx, ip); return; } /* A datagram larger than the egress IP MTU cannot be relayed. @@ -13855,7 +14193,9 @@ static inline void ip_recv(struct wolfIP *s, unsigned int if_idx, if (ee16(ip->len) > (wolfIP_frame_mtu(s, (unsigned int)out_if) - ETH_HEADER_LEN) && (ee16(ip->flags_fo) & 0x4000U) != 0U) { - wolfIP_send_frag_needed(s, if_idx, (unsigned int)out_if, ip); + if (!wolfIP_ip_is_multicast(dest)) + wolfIP_send_frag_needed(s, if_idx, + (unsigned int)out_if, ip); return; } if (!wolfIP_forward_prepare(s, out_if, next_hop, mac, @@ -14228,7 +14568,7 @@ void wolfIP_recv_ex(struct wolfIP *s, unsigned int if_idx, void *buf, uint32_t l #define MAX_DNS_NAME_LEN 255 #define MAX_DNS_LABEL_LEN 63 #define DNS_QUERY_TIMEOUT 2000U -#define DNS_QUERY_TIMEOUT_INITIAL 1800U +#define DNS_QUERY_TIMEOUT_INITIAL 2000U #define DNS_QUERY_TIMEOUT_INITIAL_JITTER 391U #ifndef DNS_QUERY_RETRIES #define DNS_QUERY_RETRIES 3 @@ -14463,8 +14803,8 @@ static int dns_schedule_timer(struct wolfIP *s) if (!s) return -1; if (s->dns_retry_count == 0) { - /* RFC 1035 recommends a 2s initial retransmission interval. On embedded - * targets, add a small 0..390 ms random offset to 1800 ms so many + /* RFC 1035 recommends a 2s minimum retransmission interval. On embedded + * targets, add a small 0..390 ms random offset to 2000 ms so many * devices do not synchronize their first retry after a shared loss. */ interval = DNS_QUERY_TIMEOUT_INITIAL + (wolfIP_getrandom() % DNS_QUERY_TIMEOUT_INITIAL_JITTER); @@ -14540,7 +14880,7 @@ static void dns_timeout_cb(void *arg) } } -void dns_callback(int dns_sd, uint16_t ev, void *arg) +static void dns_callback_one(int dns_sd, uint16_t ev, void *arg) { struct wolfIP *s = (struct wolfIP *)arg; char buf[MAX_DNS_RESPONSE]; @@ -14644,6 +14984,24 @@ void dns_callback(int dns_sd, uint16_t ev, void *arg) } } +void dns_callback(int dns_sd, uint16_t ev, void *arg) +{ + struct wolfIP *s = (struct wolfIP *)arg; + struct pkt_desc *head; + + if (!s) + return; + /* Keep reading while the query is outstanding, each pass consumes a + * datagram and more remain (e.g. a stale reply ahead of the real one). */ + do { + head = udp_rx_head(s, dns_sd); + dns_callback_one(dns_sd, ev, arg); + } while ((ev & CB_EVENT_READABLE) && head && s->dns_id != 0 && + s->dns_udp_sd == dns_sd && + (udp_rx_head(s, dns_sd) != NULL) && + (udp_rx_head(s, dns_sd) != head)); +} + static int dns_send_query(struct wolfIP *s, const char *dname, uint16_t *id, uint16_t qtype) { @@ -14703,7 +15061,8 @@ static int dns_send_query(struct wolfIP *s, const char *dname, uint16_t *id, *id = DNS_ID_NONE; return -22; } - if (tok_len + label_len + 1 > MAX_DNS_NAME_LEN) { + /* +1 for the root terminator appended after the last label. */ + if (tok_len + label_len + 2 > MAX_DNS_NAME_LEN) { dns_abort_query(s); *id = DNS_ID_NONE; return -22; @@ -14983,6 +15342,46 @@ static int socket_events_pending(struct wolfIP *s) return 0; } +/* Segments are queued with the Timestamp option built at queue time. By the + * time one is sent, or retransmitted, later segments (pure ACKs jumped past a + * window-blocked data segment, earlier retransmissions) may already have moved + * the peer's TS.Recent beyond that TSval, and PAWS (RFC 7323 section 5) would + * discard it; TSecr would likewise hold the queued segment's FIFO delay in the + * peer's RTT sample. Refresh TSval/TSecr on every transmission. */ +static void tcp_tx_refresh_ts(struct tsocket *ts, struct wolfIP_tcp_seg *tcp) +{ + uint8_t *opt = (uint8_t *)tcp->data; + uint32_t optlen = ((uint32_t)(tcp->hlen >> 2) > TCP_HEADER_LEN) ? + ((uint32_t)(tcp->hlen >> 2) - TCP_HEADER_LEN) : 0; + uint32_t off = 0; + + if (!ts->sock.tcp.ts_enabled || (tcp->flags & TCP_FLAG_SYN)) + return; + while (off < optlen) { + uint8_t kind = opt[off]; + uint8_t olen; + if (kind == TCP_OPTION_EOO) + break; + if (kind == TCP_OPTION_NOP) { + off++; + continue; + } + if (off + 1 >= optlen) + break; + olen = opt[off + 1]; + if (olen < 2 || off + olen > optlen) + break; + if (kind == TCP_OPTION_TS && olen == TCP_OPTION_TS_LEN) { + uint32_t val = ee32((uint32_t)(ts->S->last_tick & 0xFFFFFFFFU)); + uint32_t ecr = ts->sock.tcp.last_ts; + memcpy(opt + off + 2, &val, sizeof(val)); + memcpy(opt + off + 6, &ecr, sizeof(ecr)); + break; + } + off += olen; + } +} + static void flush_tcp_tx(struct wolfIP *s, uint64_t now) { int i; @@ -15061,12 +15460,28 @@ static void flush_tcp_tx(struct wolfIP *s, uint64_t now) if (ack_jump && !(seg_payload_len == 0 && tcp->flags == TCP_FLAG_ACK)) break; + /* RFC 5681 4.1: after more than an RTO without sending data, + * restart from min(IW, cwnd) instead of bursting a cwnd + * grown before the idle period. */ + if (!is_retrans && seg_payload_len > 0 && in_flight == 0 && + (uint32_t)now - ts->sock.tcp.last_data_tx > + ts->sock.tcp.rto) { + uint32_t iw = tcp_initial_cwnd(ts->sock.tcp.peer_rwnd, + tcp_cc_mss(ts)); + if (ts->sock.tcp.cwnd > iw) { + ts->sock.tcp.cwnd = iw; + ts->sock.tcp.cwnd_count = 0; + if (snd_wnd > iw) + snd_wnd = iw; + } + } if (is_retrans || seg_payload_len == 0 || (in_flight < snd_wnd && seg_payload_len <= (snd_wnd - in_flight))) { struct wolfIP_timer new_tmr = {}; size = seg_ip_len; tcp->ack = ee32(ts->sock.tcp.ack); tcp->win = ee16(tcp_adv_win(ts, 1)); + tcp_tx_refresh_ts(ts, tcp); if (seg_payload_len == 0 && tcp->flags == TCP_FLAG_ACK) { /* A pure ACK sent ahead of queued data must carry the * first unsent sequence, not SND.NXT: an out-of-window @@ -15178,15 +15593,25 @@ static void flush_tcp_tx(struct wolfIP *s, uint64_t now) } } else { uint32_t payload_len = size - (IP_HEADER_LEN + (tcp->hlen >> 2)); - if (ts->sock.tcp.tmr_rto != NO_TIMER) { - timer_binheap_cancel(&s->timers, ts->sock.tcp.tmr_rto); - ts->sock.tcp.tmr_rto = NO_TIMER; + ts->sock.tcp.last_data_tx = (uint32_t)now; + /* RFC 6298 5.1: a running data RTO keeps its deadline + * for new data; only a retransmission restarts it. + * Timers shared with other roles are swapped out. */ + if (is_retrans || ts->sock.tcp.ctrl_rto_active || + ts->sock.tcp.fin_wait_2_timeout_active || + ts->sock.tcp.preaccept_timeout_active) { + if (ts->sock.tcp.tmr_rto != NO_TIMER) { + timer_binheap_cancel(&s->timers, ts->sock.tcp.tmr_rto); + ts->sock.tcp.tmr_rto = NO_TIMER; + } + } + if (ts->sock.tcp.tmr_rto == NO_TIMER) { + new_tmr.cb = tcp_rto_cb; + new_tmr.expires = now + tcp_backoff_rto_ms(ts->sock.tcp.rto, + ts->sock.tcp.rto_backoff); + new_tmr.arg = ts; + ts->sock.tcp.tmr_rto = timers_binheap_insert(&s->timers, new_tmr); } - new_tmr.cb = tcp_rto_cb; - new_tmr.expires = now + tcp_backoff_rto_ms(ts->sock.tcp.rto, - ts->sock.tcp.rto_backoff); - new_tmr.arg = ts; - ts->sock.tcp.tmr_rto = timers_binheap_insert(&s->timers, new_tmr); if (!is_retrans) { in_flight += payload_len; ts->sock.tcp.bytes_in_flight += payload_len; @@ -15306,7 +15731,7 @@ static void flush_datagram_tx(struct wolfIP *s, struct tsocket *socks, memcpy(t->nexthop_mac, loop->mac, 6); } else if (!wolfIP_ll_is_non_ethernet(s, tx_if)) { #ifdef IP_MULTICAST - if (is_udp && wolfIP_ip_is_multicast(desc_dst)) { + if (wolfIP_ip_is_multicast(desc_dst)) { mcast_ip_to_eth(desc_dst, t->nexthop_mac); } else #endif @@ -15427,13 +15852,18 @@ static void flush_raw_tx(struct wolfIP *s) if (loop) memcpy(r->nexthop_mac, loop->mac, 6); } else if (!wolfIP_ll_is_non_ethernet(s, tx_if)) { - if (!IS_IP_BCAST(nexthop) && - (arp_lookup(s, tx_if, nexthop, r->nexthop_mac) < 0)) { - arp_request(s, tx_if, nexthop); - break; - } else if (IS_IP_BCAST(nexthop)) { +#ifdef IP_MULTICAST + if (wolfIP_ip_is_multicast(dst_ip)) { + mcast_ip_to_eth(dst_ip, r->nexthop_mac); + } else +#endif + if (!wolfIP_ip_is_broadcast(s, nexthop) && + arp_lookup(s, tx_if, nexthop, r->nexthop_mac) < 0) { + arp_request(s, tx_if, nexthop); + break; + } + if (wolfIP_ip_is_broadcast(s, nexthop)) memset(r->nexthop_mac, 0xFF, 6); - } } #else nexthop = dst_ip; @@ -15761,7 +16191,8 @@ int wolfIP_route_add(struct wolfIP *s, unsigned int if_idx, ip4 prefix, free_slot->prefix_len = prefix_len; free_slot->prefix = prefix; free_slot->gateway = gateway; - free_slot->order = s->route_generation++; + /* Start at 1: order 0 is reserved for connected subnets. */ + free_slot->order = ++s->route_generation; return 0; } diff --git a/src/wolfip6.c b/src/wolfip6.c index a27bd072..8f7d5889 100644 --- a/src/wolfip6.c +++ b/src/wolfip6.c @@ -1,6 +1,7 @@ /* wolfip6.c * - * IPv6 header encapsulation and parsing for the wolfIP TCP/IP stack. + * IPv6 support for the wolfIP TCP/IP stack: IPv6 header handling, ICMPv6, + * Neighbor Discovery/SLAAC, and UDP/TCP over IPv6. * * Copyright (C) 2026 wolfSSL Inc. * @@ -26,11 +27,13 @@ * not a separate translation unit: struct wolfIP and the checksum, Ethernet * and link-layer helpers it needs are all static symbols inside wolfip.c. * - * Scope of this file today is deliberately narrow: the IPv6 header itself. - * Parsing and validation on receive, encapsulation on transmit, and the - * upper-layer checksum that both need. ICMPv6, Neighbor Discovery, SLAAC and - * DHCPv6 are not here yet; the receive path classifies what it cannot handle - * and reports a distinct reason rather than pretending to process it. + * This file holds the IPv6 side of the stack: header parsing and validation + * on receive, encapsulation on transmit and the upper-layer checksum; ICMPv6 + * (echo, error generation and relay, ICMPv6 sockets); Neighbor Discovery with + * DAD, and Router Advertisement handling with SLAAC address formation; and + * UDP and TCP over IPv6. DHCPv6 is not implemented. The receive path + * classifies what it cannot handle and reports a distinct reason rather than + * pretending to process it. */ /* ---------------------------------------------------------------------- */ @@ -717,19 +720,75 @@ static uint16_t port_alloc_random6(const struct tsocket *arr, int n, static void nd6_input(struct wolfIP *s, unsigned int if_idx, struct wolfIP_ip6_packet *pkt, uint32_t payload_len); +/* Apply a Packet Too Big to the TCP connection it quotes, the counterpart of + * the Fragmentation Needed arm of icmp_try_deliver_tcp_error(). RFC 4443 + * section 2.4 (d) has the upper layer told about an error, and RFC 8201 has + * TCP shrink its segments to the reported MTU - never below the IPv6 minimum + * of 1280, which also bounds what a spoofed message can do. The quoted + * packet must be TCP directly after the IPv6 header: no extension header + * chain is walked here. Other error types are not applied to TCP. */ +static void icmp6_try_deliver_tcp_error(struct wolfIP *s, + const struct wolfIP_icmp6_packet *icmp) +{ + uint32_t payload_len = ee16(icmp->ip6.payload_len); + const uint8_t *quote = icmp->data + 4; + uint32_t mtu; + uint16_t src_port, dst_port, new_mss; + ip6 qsrc, qdst; + int i; + + if (icmp->type != ICMP6_PACKET_TOO_BIG) + return; + /* ICMPv6 header, MTU word, the quoted IPv6 header and the TCP ports and + * sequence number. */ + if (payload_len < (8U + IP6_HEADER_LEN + 8U)) + return; + if (quote[6] != IP6_NEXTHDR_TCP) + return; + memcpy(qsrc.addr, quote + 8, sizeof(qsrc.addr)); + memcpy(qdst.addr, quote + 24, sizeof(qdst.addr)); + memcpy(&src_port, quote + IP6_HEADER_LEN, sizeof(src_port)); + memcpy(&dst_port, quote + IP6_HEADER_LEN + 2, sizeof(dst_port)); + src_port = ee16(src_port); + dst_port = ee16(dst_port); + mtu = ((uint32_t)icmp->data[0] << 24) | ((uint32_t)icmp->data[1] << 16) | + ((uint32_t)icmp->data[2] << 8) | (uint32_t)icmp->data[3]; + if (mtu < IP6_MIN_MTU) + mtu = IP6_MIN_MTU; + if (mtu > 0xFFFFU) + mtu = 0xFFFFU; + new_mss = (uint16_t)(mtu - (IP6_HEADER_LEN + TCP_HEADER_LEN)); + + for (i = 0; i < MAX_TCPSOCKETS; i++) { + struct tsocket *t = &s->tcpsockets[i]; + + if (t->proto != WI_IPPROTO_TCP || !t->peer_is_v6) + continue; + if (t->sock.tcp.state == TCP_CLOSED || t->sock.tcp.state == TCP_LISTEN) + continue; + if (ip6_cmp(&t->local_ip6, &qsrc) != 0 || + ip6_cmp(&t->remote_ip6, &qdst) != 0) + continue; + if (t->src_port != src_port || t->dst_port != dst_port) + continue; + if (t->sock.tcp.peer_mss == 0 || new_mss < t->sock.tcp.peer_mss) + t->sock.tcp.peer_mss = new_mss; + break; + } +} + /* ICMPv6 receive path. Deliberately laid out like icmp_input() above it: * length checks, then the checksum, then one arm per message type, with the * echo reply built in place over the request. * - * Only Echo is handled so far. That is useful on its own because it needs - * neither sockets nor Neighbor Discovery - the reply goes back to the source - * MAC of the request, exactly as the IPv4 path does - and it gives - * ip6_output_add_header() its first production caller. + * Echo Requests addressed to one of our unicast addresses are answered in + * place - the reply goes back to the source MAC of the request, exactly as + * the IPv4 path does. Router Solicit through Redirect go to nd6_input(), and + * ICMPv6 error messages and Echo Replies are passed to icmp6_try_recv() for + * application sockets. Errors are generated by icmp6_send_error(), not here. * - * Not handled yet, each with its requirement test already written in - * unit_tests_ipv6_pending.c: the error messages of RFC 4443 sections 3.1 to - * 3.4, and Echo Requests addressed to a multicast group, which need a - * unicast source chosen per RFC 4443 section 4.2. + * Not handled yet: Echo Requests addressed to a multicast group, which need + * a unicast source chosen per RFC 4443 section 4.2. They are dropped. * * Unlike icmp_input() there is no wolfIP_filter_notify_icmp() call: the * packet filter has no IPv6 hooks yet. It belongs here when it grows them. @@ -782,6 +841,7 @@ static void icmp6_input(struct wolfIP *s, unsigned int if_idx, * traffic it never asked for. */ if (icmp6_type_is_error(icmp->type) || (icmp->type == ICMP6_ECHO_REPLY)) { + icmp6_try_deliver_tcp_error(s, icmp); icmp6_try_recv(s, if_idx, icmp, len); return; } @@ -1014,9 +1074,9 @@ static int nd6_neighbor_index(struct wolfIP *s, unsigned int if_idx, /* Insert or refresh an entry. `mac` may be NULL to create an INCOMPLETE * entry for an address whose link-layer address is still unknown. * - * Unlike arp_store_neighbor(), which silently refuses when the table is - * full, this evicts the oldest entry. A full table that cannot be reclaimed - * means one burst of scan traffic locks out every real neighbour. */ + * When the table is full this evicts the oldest entry. A full table that + * cannot be reclaimed means one burst of scan traffic locks out every real + * neighbour. */ static int nd6_store_neighbor(struct wolfIP *s, unsigned int if_idx, const ip6 *addr, const uint8_t *mac, uint8_t state, int is_router) @@ -1081,18 +1141,23 @@ static int nd6_lookup(struct wolfIP *s, unsigned int if_idx, const ip6 *addr, /* Send a Neighbor Solicitation for `target`. * * There is no rate limit here, unlike arp_request()'s one per second per - * interface. The only caller is duplicate address detection, which is - * already paced by dad_due. When address resolution is driven from the - * transmit path it will be able to ask for the same neighbour repeatedly, - * and will need a throttle at that point. + * interface. The callers pace themselves: duplicate address detection + * (nd6_tick_cb) by dad_due, and nd6_resolve() on the transmit path by + * ND6_RETRANS_TIMER_MS and the per-entry probe count. * * `src` is the source address: a real address of ours for ordinary address * resolution, or the unspecified address during duplicate address detection. * RFC 4861 section 4.3 forbids the Source Link-Layer Address option when the * source is unspecified, which is exactly the DAD case - there is no address - * to advertise yet. */ + * to advertise yet. + * + * `unicast_mac` is NULL for address resolution, which goes to the target's + * solicited-node group. Neighbor Unreachability Detection probes pass the + * cached link-layer address instead, and the solicitation then goes to the + * target itself (RFC 4861 section 7.2.2). */ static void nd6_send_ns(struct wolfIP *s, unsigned int if_idx, - const ip6 *target, const ip6 *src) + const ip6 *target, const ip6 *src, + const uint8_t *unicast_mac) { uint8_t frame[LINK_MTU]; struct nd6_msg *ns = (struct nd6_msg *)frame; @@ -1125,8 +1190,13 @@ static void nd6_send_ns(struct wolfIP *s, unsigned int if_idx, /* Solicitations go to the target's solicited-node group, so only the * handful of nodes sharing its low 24 bits are interrupted. */ - ip6_set_solicited_node(&dst, target); - ip6_mcast_to_eth(&dst, mac); + if (unicast_mac != NULL) { + ip6_copy(&dst, target); + memcpy(mac, unicast_mac, 6); + } else { + ip6_set_solicited_node(&dst, target); + ip6_mcast_to_eth(&dst, mac); + } ip6_output_add_header(s, if_idx, &ns->ip6, src, &dst, IP6_NEXTHDR_ICMPV6, payload_len, ND6_HOP_LIMIT, mac); wolfIP_ll_send_frame(s, if_idx, frame, @@ -2194,15 +2264,25 @@ static int nd6_resolve(struct wolfIP *s, unsigned int *tx_if, const ip6 *dst, } if (nd6_select_nexthop(s, *tx_if, dst, &nexthop) != 0) return ND6_RESOLVE_NO_ROUTE; /* not on-link and no default router */ - if (nd6_lookup(s, *tx_if, &nexthop, mac) == 0) + if (nd6_lookup(s, *tx_if, &nexthop, mac) == 0) { + /* RFC 4861 section 7.3.3: the first packet sent to a STALE entry + * starts the delay before its reachability is probed. The packet + * itself still goes out with the cached address. */ + idx = nd6_neighbor_index(s, *tx_if, &nexthop); + if ((idx >= 0) && (s->nd6.neighbors[idx].state == ND6_STALE)) { + s->nd6.neighbors[idx].state = ND6_DELAY; + s->nd6.neighbors[idx].ts = s->last_tick; + nd6_arm_tick(s); + } return 0; + } if (ip6_select_source(s, *tx_if, &nexthop, &src) != 0) return ND6_RESOLVE_PENDING; /* nothing to solicit from yet */ idx = nd6_neighbor_index(s, *tx_if, &nexthop); if (idx < 0) { nd6_store_neighbor(s, *tx_if, &nexthop, NULL, ND6_INCOMPLETE, 0); - nd6_send_ns(s, *tx_if, &nexthop, &src); + nd6_send_ns(s, *tx_if, &nexthop, &src, NULL); nd6_arm_tick(s); return ND6_RESOLVE_PENDING; } @@ -2221,7 +2301,7 @@ static int nd6_resolve(struct wolfIP *s, unsigned int *tx_if, const ip6 *dst, if ((s->last_tick - s->nd6.neighbors[idx].ts) >= ND6_RETRANS_TIMER_MS) { s->nd6.neighbors[idx].ts = s->last_tick; s->nd6.neighbors[idx].probes++; - nd6_send_ns(s, *tx_if, &nexthop, &src); + nd6_send_ns(s, *tx_if, &nexthop, &src, NULL); } return ND6_RESOLVE_PENDING; } @@ -2581,7 +2661,9 @@ static uint32_t tcp6_promote(const struct tsocket *t, const void *v4frame, * destination with no route is a hard failure of a different kind: the * segment can never leave, so the connection is aborted and the caller's * socket is closed - the segment must not pin the head of the queue the way - * a held datagram does. */ + * a held datagram does. A reset needs no abort of its own: it is what + * tcp_abort() sends while tearing down, and aborting again from here would + * recurse for ever, so it is just dropped. */ static int tcp6_send_seg(struct wolfIP *s, struct tsocket *t, const void *v4frame, uint32_t frame_len) { @@ -2606,7 +2688,9 @@ static int tcp6_send_seg(struct wolfIP *s, struct tsocket *t, int rc = nd6_resolve(s, &tx_if, &t->remote_ip6, mac); if (rc == ND6_RESOLVE_NO_ROUTE) { - tcp_abort(t); + if (!(((const struct wolfIP_tcp_seg *)v4frame)->flags & + TCP_FLAG_RST)) + tcp_abort(t); return -1; } if (rc != 0) @@ -2727,8 +2811,6 @@ static int tcp6_connect(struct wolfIP *s, struct tsocket *t, const ip6 *dst, return -WOLFIP_EAGAIN; } } - if (t->src_port < 1024) - t->src_port = (uint16_t)(t->src_port + 1024); t->dst_port = dport; t->sock.tcp.seq = wolfIP_getrandom(); t->sock.tcp.snd_una = t->sock.tcp.seq; @@ -2737,7 +2819,13 @@ static int tcp6_connect(struct wolfIP *s, struct tsocket *t, const ip6 *dst, return -1; } t->sock.tcp.ctrl_rto_retries = 0; - tcp_ctrl_rto_start(t, s->last_tick); + if (tcp_ctrl_rto_start(t, s->last_tick) < 0) { + /* No timer slot: a SYN_SENT socket with no retransmit timer would + * never recover from a lost SYN. Same rollback as the IPv4 arm. */ + fifo_init(&t->sock.tcp.txbuf, t->txmem, TXBUF_SIZE); + t->sock.tcp.state = TCP_CLOSED; + return -WOLFIP_EAGAIN; + } return -WOLFIP_EAGAIN; /* in progress, as the IPv4 arm reports it */ } @@ -3343,7 +3431,8 @@ static void nd6_tick_cb(void *arg) slot->dad_probes--; ip6_set_unspecified(&unspec); - nd6_send_ns(s, slot->info.if_idx, &slot->info.v6, &unspec); + nd6_send_ns(s, slot->info.if_idx, &slot->info.v6, &unspec, + NULL); slot->dad_due = s->last_tick + ND6_RETRANS_TIMER_MS; } else { /* RFC 4862 section 5.4.4: no answer within the retransmit @@ -3413,6 +3502,27 @@ static void nd6_tick_cb(void *arg) ND6_MAX_MULTICAST_SOLICIT))) { n->state = 0; /* resolution gave up */ } + /* Neighbor Unreachability Detection (RFC 4861 section 7.3.3): a + * DELAY entry that nobody confirmed is probed with unicast + * solicitations, and one that never answers is dropped so the next + * send resolves it again from scratch. */ + if (((n->state == ND6_DELAY) && + ((s->last_tick - n->ts) >= ND6_DELAY_FIRST_PROBE_MS)) || + ((n->state == ND6_PROBE) && + ((s->last_tick - n->ts) >= ND6_RETRANS_TIMER_MS))) { + ip6 src; + + if ((n->state == ND6_PROBE) && + (n->probes >= ND6_MAX_UNICAST_SOLICIT)) { + n->state = 0; + continue; + } + n->state = ND6_PROBE; + n->ts = s->last_tick; + n->probes++; + if (ip6_select_source(s, n->if_idx, &n->addr, &src) == 0) + nd6_send_ns(s, n->if_idx, &n->addr, &src, n->mac); + } } /* Prefix and router lifetimes, both in seconds on the wire. */