diff --git a/src/test/unit/unit.c b/src/test/unit/unit.c index 10f2f824..a9051fa1 100644 --- a/src/test/unit/unit.c +++ b/src/test/unit/unit.c @@ -126,6 +126,7 @@ Suite *wolf_suite(void) #endif tcase_add_test(tc_utils, test_wolfip_send_port_unreachable_ignores_missing_link_sender); tcase_add_test(tc_utils, test_wolfip_send_port_unreachable_non_ethernet_skips_eth_filter); + tcase_add_test(tc_utils, test_wolfip_send_port_unreachable_sets_df); tcase_add_test(tc_utils, test_tcp_adv_win_clamps_and_applies_window_scale); tcase_add_test(tc_utils, test_tcp_segment_acceptable_zero_window_and_overlap_cases); tcase_add_test(tc_utils, test_tcp_segment_acceptable_counts_syn_in_segment_length); @@ -156,6 +157,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_utils, test_sock_bind_invalid_fd); tcase_add_test(tc_utils, test_sock_bind_tcp_state_not_closed); tcase_add_test(tc_utils, test_sock_bind_tcp_filter_blocks); + tcase_add_test(tc_utils, test_sock_bind_tcp_port_collision_rejected); tcase_add_test(tc_utils, test_sock_bind_udp_src_port_nonzero); tcase_add_test(tc_utils, test_sock_bind_udp_filter_blocks); tcase_add_test(tc_utils, test_sock_bind_icmp_success); @@ -220,6 +222,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_utils, test_syn_rcvd_rst_bad_seq_dropped); tcase_add_test(tc_utils, test_ip_recv_drops_broadcast_source); tcase_add_test(tc_utils, test_ip_recv_drops_multicast_source); + tcase_add_test(tc_utils, test_ip_recv_drops_zero_source); tcase_add_test(tc_utils, test_arp_recv_rejects_broadcast_sender); tcase_add_test(tc_utils, test_arp_recv_rejects_multicast_sender); tcase_add_test(tc_utils, test_dhcp_ack_rejects_mismatched_server_id); @@ -607,6 +610,8 @@ Suite *wolf_suite(void) tcase_add_test(tc_utils, test_tcp_recv_ooo_capacity_limit); tcase_add_test(tc_utils, test_tcp_recv_overlapping_ooo_segments_coalesce_on_consume); tcase_add_test(tc_utils, test_tcp_input_syn_with_sack_option_enables_sack); + tcase_add_test(tc_utils, test_tcp_input_ignores_reserved_bits_in_hlen); + tcase_add_test(tc_utils, test_tcp_recv_ignores_reserved_bits_in_hlen); tcase_add_test(tc_utils, test_tcp_input_syn_with_sack_option_respects_local_sack_offer); tcase_add_test(tc_utils, test_tcp_input_iplen_too_big); tcase_add_test(tc_utils, test_tcp_checksum_valid_passes); @@ -653,6 +658,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_proto, test_send_ttl_exceeded_eth_filter_drop); tcase_add_test(tc_proto, test_send_ttl_exceeded_no_send); tcase_add_test(tc_proto, test_send_ttl_exceeded_non_ethernet_skips_eth_filter); + tcase_add_test(tc_proto, test_send_ttl_exceeded_sets_df); #if WOLFIP_ENABLE_FORWARDING tcase_add_test(tc_proto, test_wolfip_forward_ttl_exceeded_short_len_does_not_send); #endif @@ -770,6 +776,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_proto, test_icmp_socket_send_recv); tcase_add_test(tc_proto, test_icmp_input_echo_reply_queues); tcase_add_test(tc_proto, test_icmp_input_echo_request_reply_sent); + tcase_add_test(tc_proto, test_icmp_input_echo_reply_sets_df); tcase_add_test(tc_proto, test_icmp_input_echo_request_bad_checksum_dropped); tcase_add_test(tc_proto, test_icmp_input_echo_request_odd_len_reply_checksum); tcase_add_test(tc_proto, test_icmp_input_echo_request_dhcp_running_no_reply); @@ -782,6 +789,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_proto, test_icmp_input_filter_drop_receiving); tcase_add_test(tc_proto, test_icmp_input_dest_unreach_port_unreachable_keeps_established_tcp_socket); tcase_add_test(tc_proto, test_icmp_input_dest_unreach_frag_needed_reduces_tcp_peer_mss); + tcase_add_test(tc_proto, test_icmp_input_dest_unreach_frag_needed_below_floor_preserves_peer_mss); tcase_add_test(tc_proto, test_icmp_input_dest_unreach_port_unreachable_closes_syn_sent_tcp_socket); tcase_add_test(tc_proto, test_icmp_input_dest_unreach_port_unreachable_quoted_ip_options_keep_established_tcp_socket); tcase_add_test(tc_proto, test_udp_sendto_and_recvfrom); diff --git a/src/test/unit/unit_tests_api.c b/src/test/unit/unit_tests_api.c index 4ecb8f74..cac53498 100644 --- a/src/test/unit/unit_tests_api.c +++ b/src/test/unit/unit_tests_api.c @@ -1014,6 +1014,33 @@ START_TEST(test_sock_bind_tcp_filter_blocks) } END_TEST +START_TEST(test_sock_bind_tcp_port_collision_rejected) +{ + struct wolfIP s; + int tcp_sd1, tcp_sd2; + struct wolfIP_sockaddr_in sin; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + tcp_sd1 = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_STREAM, WI_IPPROTO_TCP); + ck_assert_int_gt(tcp_sd1, 0); + tcp_sd2 = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_STREAM, WI_IPPROTO_TCP); + ck_assert_int_gt(tcp_sd2, 0); + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = ee16(1234); + sin.sin_addr.s_addr = ee32(0x0A000001U); + + ck_assert_int_eq(wolfIP_sock_bind(&s, tcp_sd1, + (struct wolfIP_sockaddr *)&sin, sizeof(sin)), 0); + ck_assert_int_eq(wolfIP_sock_bind(&s, tcp_sd2, + (struct wolfIP_sockaddr *)&sin, sizeof(sin)), -1); +} +END_TEST + START_TEST(test_sock_bind_udp_src_port_nonzero) { struct wolfIP s; @@ -3974,6 +4001,65 @@ START_TEST(test_ip_recv_drops_multicast_source) } END_TEST +START_TEST(test_ip_recv_drops_zero_source) +{ + struct wolfIP s; + int listen_sd; + struct tsocket *listener; + struct wolfIP_sockaddr_in sin; + struct wolfIP_tcp_seg seg; + struct wolfIP_ll_dev *ll; + union transport_pseudo_header ph; + static const uint8_t src_mac[6] = {0x10, 0x20, 0x30, 0x40, 0x50, 0x60}; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + listen_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_STREAM, WI_IPPROTO_TCP); + ck_assert_int_gt(listen_sd, 0); + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = ee16(1234); + sin.sin_addr.s_addr = ee32(0x0A000001U); + ck_assert_int_eq(wolfIP_sock_bind(&s, listen_sd, (struct wolfIP_sockaddr *)&sin, sizeof(sin)), 0); + ck_assert_int_eq(wolfIP_sock_listen(&s, listen_sd, 1), 0); + + listener = &s.tcpsockets[SOCKET_UNMARK(listen_sd)]; + + ll = wolfIP_getdev_ex(&s, TEST_PRIMARY_IF); + memset(&seg, 0, sizeof(seg)); + memcpy(seg.ip.eth.dst, ll->mac, 6); + memcpy(seg.ip.eth.src, src_mac, 6); + seg.ip.eth.type = ee16(ETH_TYPE_IP); + 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(IPADDR_ANY); + seg.ip.dst = ee32(0x0A000001U); + seg.ip.csum = 0; + iphdr_set_checksum(&seg.ip); + seg.src_port = ee16(40000); + seg.dst_port = ee16(1234); + seg.seq = ee32(1); + seg.hlen = TCP_HEADER_LEN << 2; + seg.flags = TCP_FLAG_SYN; + seg.win = ee16(65535); + memset(&ph, 0, sizeof(ph)); + ph.ph.src = seg.ip.src; + ph.ph.dst = seg.ip.dst; + ph.ph.proto = WI_IPPROTO_TCP; + ph.ph.len = ee16(TCP_HEADER_LEN); + seg.csum = ee16(transport_checksum(&ph, &seg.src_port)); + + ip_recv(&s, TEST_PRIMARY_IF, (struct wolfIP_ip_packet *)&seg, + sizeof(struct wolfIP_eth_frame) + IP_HEADER_LEN + TCP_HEADER_LEN); + + ck_assert_int_eq(listener->sock.tcp.state, TCP_LISTEN); +} +END_TEST + /* Regression: arp_recv must not cache entries with broadcast, multicast, * zero, or own-IP sender addresses. Without validation, an ARP request * with a spoofed sender IP poisons the neighbor cache. */ diff --git a/src/test/unit/unit_tests_dns_dhcp.c b/src/test/unit/unit_tests_dns_dhcp.c index d081a329..af31d4ae 100644 --- a/src/test/unit/unit_tests_dns_dhcp.c +++ b/src/test/unit/unit_tests_dns_dhcp.c @@ -1758,6 +1758,37 @@ START_TEST(test_icmp_input_echo_request_reply_sent) } END_TEST +START_TEST(test_icmp_input_echo_reply_sets_df) +{ + struct wolfIP s; + struct wolfIP_icmp_packet icmp; + struct wolfIP_icmp_packet *reply; + uint32_t frame_len; + + wolfIP_init(&s); + mock_link_init(&s); + s.dhcp_state = DHCP_OFF; + wolfIP_filter_set_callback(NULL, NULL); + last_frame_sent_size = 0; + + memset(&icmp, 0, sizeof(icmp)); + icmp.ip.src = ee32(0x0A000002U); + icmp.ip.dst = ee32(0x0A000001U); + icmp.ip.ttl = 1; + icmp.ip.len = ee16(IP_HEADER_LEN + ICMP_HEADER_LEN); + icmp.ip.flags_fo = 0; + icmp.type = ICMP_ECHO_REQUEST; + icmp.csum = ee16(icmp_checksum(&icmp, ICMP_HEADER_LEN)); + frame_len = (uint32_t)(ETH_HEADER_LEN + IP_HEADER_LEN + ICMP_HEADER_LEN); + + icmp_input(&s, TEST_PRIMARY_IF, (struct wolfIP_ip_packet *)&icmp, frame_len); + ck_assert_uint_gt(last_frame_sent_size, 0); + reply = (struct wolfIP_icmp_packet *)last_frame_sent; + ck_assert_uint_eq(reply->type, ICMP_ECHO_REPLY); + ck_assert_uint_eq(ee16(reply->ip.flags_fo) & 0x4000U, 0x4000U); +} +END_TEST + START_TEST(test_icmp_input_echo_request_bad_checksum_dropped) { struct wolfIP s; @@ -2169,6 +2200,66 @@ START_TEST(test_icmp_input_dest_unreach_frag_needed_reduces_tcp_peer_mss) } END_TEST +START_TEST(test_icmp_input_dest_unreach_frag_needed_below_floor_preserves_peer_mss) +{ + struct wolfIP s; + struct tsocket *ts; + struct wolfIP_icmp_dest_unreachable_packet icmp; + struct wolfIP_tcp_wire_prefix *orig; + uint32_t frame_len; + uint16_t next_hop_mtu; + static const uint16_t below_floor[] = { 0U, 41U, 67U, 68U, 575U }; + size_t i; + + 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->local_ip = 0x0A000001U; + ts->remote_ip = 0x0A000002U; + ts->src_port = 1234; + ts->dst_port = 4321; + + frame_len = (uint32_t)(ETH_HEADER_LEN + IP_HEADER_LEN + ICMP_DEST_UNREACH_SIZE); + + for (i = 0; i < sizeof(below_floor) / sizeof(below_floor[0]); i++) { + ts->sock.tcp.peer_mss = 1460U; + + memset(&icmp, 0, sizeof(icmp)); + icmp.ip.src = ee32(0x0A0000FEU); + icmp.ip.dst = ee32(ts->local_ip); + icmp.ip.ttl = 64; + icmp.ip.proto = WI_IPPROTO_ICMP; + icmp.ip.len = ee16(IP_HEADER_LEN + ICMP_DEST_UNREACH_SIZE); + icmp.type = ICMP_DEST_UNREACH; + icmp.code = ICMP_FRAG_NEEDED; + next_hop_mtu = ee16(below_floor[i]); + memcpy(&icmp.unused[2], &next_hop_mtu, sizeof(next_hop_mtu)); + + orig = (struct wolfIP_tcp_wire_prefix *)icmp.orig_packet; + orig->ip.ver_ihl = 0x45; + orig->ip.proto = WI_IPPROTO_TCP; + orig->ip.src = ee32(ts->local_ip); + orig->ip.dst = ee32(ts->remote_ip); + orig->ip.len = ee16(IP_HEADER_LEN + 8U); + orig->src_port = ee16(ts->src_port); + orig->dst_port = ee16(ts->dst_port); + + icmp.csum = ee16(icmp_checksum((struct wolfIP_icmp_packet *)&icmp, + ICMP_DEST_UNREACH_SIZE)); + + icmp_input(&s, TEST_PRIMARY_IF, (struct wolfIP_ip_packet *)&icmp, frame_len); + + ck_assert_uint_eq(ts->sock.tcp.peer_mss, 1460U); + } +} +END_TEST + START_TEST(test_icmp_input_dest_unreach_port_unreachable_closes_syn_sent_tcp_socket) { struct wolfIP s; diff --git a/src/test/unit/unit_tests_proto.c b/src/test/unit/unit_tests_proto.c index 2fc9600e..e6f1db7a 100644 --- a/src/test/unit/unit_tests_proto.c +++ b/src/test/unit/unit_tests_proto.c @@ -3329,6 +3329,30 @@ START_TEST(test_wolfip_send_port_unreachable_non_ethernet_skips_eth_filter) } END_TEST +START_TEST(test_wolfip_send_port_unreachable_sets_df) +{ + struct wolfIP s; + uint8_t orig_buf[ETH_HEADER_LEN + TTL_EXCEEDED_ORIG_PACKET_SIZE_DEFAULT]; + struct wolfIP_ip_packet *orig = (struct wolfIP_ip_packet *)orig_buf; + struct wolfIP_icmp_dest_unreachable_packet *reply; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + last_frame_sent_size = 0; + + memset(orig_buf, 0, sizeof(orig_buf)); + orig->ver_ihl = 0x45; + orig->src = ee32(0x0A000002U); + orig->dst = ee32(0x0A000001U); + + wolfIP_send_port_unreachable(&s, TEST_PRIMARY_IF, orig); + ck_assert_uint_gt(last_frame_sent_size, 0U); + reply = (struct wolfIP_icmp_dest_unreachable_packet *)last_frame_sent; + ck_assert_uint_eq(ee16(reply->ip.flags_fo) & 0x4000U, 0x4000U); +} +END_TEST + START_TEST(test_tcp_adv_win_clamps_and_applies_window_scale) { struct tsocket ts; diff --git a/src/test/unit/unit_tests_tcp_ack.c b/src/test/unit/unit_tests_tcp_ack.c index 0fbb60eb..2c987ccf 100644 --- a/src/test/unit/unit_tests_tcp_ack.c +++ b/src/test/unit/unit_tests_tcp_ack.c @@ -2457,6 +2457,32 @@ START_TEST(test_send_ttl_exceeded_non_ethernet_skips_eth_filter) } END_TEST +START_TEST(test_send_ttl_exceeded_sets_df) +{ + struct wolfIP s; + uint8_t ip_buf[ETH_HEADER_LEN + TTL_EXCEEDED_ORIG_PACKET_SIZE_DEFAULT]; + struct wolfIP_ip_packet *ip = (struct wolfIP_ip_packet *)ip_buf; + struct wolfIP_icmp_ttl_exceeded_packet *reply; + + 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; + + memset(ip_buf, 0, sizeof(ip_buf)); + memcpy(ip->eth.src, "\x01\x02\x03\x04\x05\x06", 6); + ip->ver_ihl = 0x45; + ip->src = ee32(0x0A000002U); + ip->dst = ee32(0x0A000001U); + + wolfIP_send_ttl_exceeded(&s, TEST_PRIMARY_IF, ip); + ck_assert_uint_gt(last_frame_sent_size, 0U); + reply = (struct wolfIP_icmp_ttl_exceeded_packet *)last_frame_sent; + ck_assert_uint_eq(ee16(reply->ip.flags_fo) & 0x4000U, 0x4000U); +} +END_TEST + #if WOLFIP_ENABLE_FORWARDING START_TEST(test_wolfip_forward_ttl_exceeded_short_len_does_not_send) { diff --git a/src/test/unit/unit_tests_tcp_flow.c b/src/test/unit/unit_tests_tcp_flow.c index 596a1485..87ee0376 100644 --- a/src/test/unit/unit_tests_tcp_flow.c +++ b/src/test/unit/unit_tests_tcp_flow.c @@ -431,6 +431,68 @@ START_TEST(test_tcp_input_syn_with_sack_option_enables_sack) } END_TEST +START_TEST(test_tcp_input_ignores_reserved_bits_in_hlen) +{ + struct wolfIP s; + int listen_sd; + struct tsocket *ts; + struct wolfIP_sockaddr_in sin; + struct { + uint8_t frame[sizeof(struct wolfIP_tcp_seg) + 4]; + } pkt; + struct wolfIP_tcp_seg *syn = (struct wolfIP_tcp_seg *)pkt.frame; + struct wolfIP_ll_dev *ll; + union transport_pseudo_header ph; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + listen_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_STREAM, WI_IPPROTO_TCP); + ck_assert_int_gt(listen_sd, 0); + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = ee16(1234); + sin.sin_addr.s_addr = ee32(0x0A000001U); + ck_assert_int_eq(wolfIP_sock_bind(&s, listen_sd, (struct wolfIP_sockaddr *)&sin, sizeof(sin)), 0); + ck_assert_int_eq(wolfIP_sock_listen(&s, listen_sd, 1), 0); + ts = &s.tcpsockets[SOCKET_UNMARK(listen_sd)]; + ll = wolfIP_getdev_ex(&s, TEST_PRIMARY_IF); + ck_assert_ptr_nonnull(ll); + + memset(&pkt, 0, sizeof(pkt)); + memcpy(syn->ip.eth.dst, ll->mac, 6); + syn->ip.eth.type = ee16(ETH_TYPE_IP); + syn->ip.ver_ihl = 0x45; + syn->ip.ttl = 64; + syn->ip.proto = WI_IPPROTO_TCP; + syn->ip.len = ee16(IP_HEADER_LEN + TCP_HEADER_LEN + 4); + syn->ip.src = ee32(0x0A0000A1U); + syn->ip.dst = ee32(0x0A000001U); + iphdr_set_checksum(&syn->ip); + syn->src_port = ee16(40000); + syn->dst_port = ee16(1234); + syn->seq = ee32(1); + syn->hlen = (uint8_t)(((TCP_HEADER_LEN + 4) << 2) | 0x0C); + syn->flags = TCP_FLAG_SYN; + syn->win = ee16(65535); + syn->data[0] = TCP_OPTION_SACK_PERMITTED; + syn->data[1] = TCP_OPTION_SACK_PERMITTED_LEN; + syn->data[2] = TCP_OPTION_NOP; + syn->data[3] = TCP_OPTION_NOP; + + memset(&ph, 0, sizeof(ph)); + ph.ph.src = syn->ip.src; + ph.ph.dst = syn->ip.dst; + ph.ph.proto = WI_IPPROTO_TCP; + ph.ph.len = ee16(TCP_HEADER_LEN + 4); + syn->csum = ee16(transport_checksum(&ph, &syn->src_port)); + + tcp_input(&s, TEST_PRIMARY_IF, syn, + sizeof(struct wolfIP_eth_frame) + IP_HEADER_LEN + TCP_HEADER_LEN + 4); + ck_assert_uint_eq(ts->sock.tcp.sack_permitted, 1); +} +END_TEST + START_TEST(test_tcp_input_syn_with_sack_option_respects_local_sack_offer) { struct wolfIP s; @@ -722,7 +784,7 @@ END_TEST START_TEST(test_tcp_parse_sack_wraparound_block_accepted) { - uint8_t seg_buf[sizeof(struct wolfIP_tcp_seg) + 10]; + uint8_t seg_buf[sizeof(struct wolfIP_tcp_seg) + 12]; struct wolfIP_tcp_seg *seg = (struct wolfIP_tcp_seg *)seg_buf; struct tcp_parsed_opts po; uint32_t left = 0xFFFFFFF0U; @@ -731,7 +793,7 @@ START_TEST(test_tcp_parse_sack_wraparound_block_accepted) uint32_t frame_len; memset(seg_buf, 0, sizeof(seg_buf)); - seg->hlen = (uint8_t)((TCP_HEADER_LEN + 10) << 2); + seg->hlen = (uint8_t)((TCP_HEADER_LEN + 12) << 2); opt = seg->data; opt[0] = TCP_OPTION_SACK; opt[1] = 10; @@ -741,8 +803,10 @@ START_TEST(test_tcp_parse_sack_wraparound_block_accepted) memcpy(opt + 2, &left_be, sizeof(left_be)); memcpy(opt + 6, &right_be, sizeof(right_be)); } + opt[10] = TCP_OPTION_NOP; + opt[11] = TCP_OPTION_NOP; - frame_len = ETH_HEADER_LEN + IP_HEADER_LEN + TCP_HEADER_LEN + 10; + frame_len = ETH_HEADER_LEN + IP_HEADER_LEN + TCP_HEADER_LEN + 12; tcp_parse_options(seg, frame_len, &po); ck_assert_int_eq(po.sack_count, 1); @@ -808,7 +872,7 @@ START_TEST(test_tcp_parse_options_parses_and_clamps_mixed_options) uint32_t frame_len; memset(seg_buf, 0, sizeof(seg_buf)); - seg->hlen = (uint8_t)((TCP_HEADER_LEN + 30) << 2); + seg->hlen = (uint8_t)((TCP_HEADER_LEN + 32) << 2); opt = seg->data; opt[0] = TCP_OPTION_WS; opt[1] = TCP_OPTION_WS_LEN; @@ -840,8 +904,10 @@ START_TEST(test_tcp_parse_options_parses_and_clamps_mixed_options) memcpy(opt + 6, &right, sizeof(right)); } opt += 10; - opt[0] = TCP_OPTION_EOO; - frame_len = ETH_HEADER_LEN + IP_HEADER_LEN + TCP_HEADER_LEN + 30; + opt[0] = TCP_OPTION_NOP; + opt[1] = TCP_OPTION_NOP; + opt[2] = TCP_OPTION_EOO; + frame_len = ETH_HEADER_LEN + IP_HEADER_LEN + TCP_HEADER_LEN + 32; tcp_parse_options(seg, frame_len, &po); @@ -867,7 +933,7 @@ START_TEST(test_tcp_parse_options_parses_mss_sack_permitted_timestamp_and_two_sa uint32_t frame_len; memset(seg_buf, 0, sizeof(seg_buf)); - seg->hlen = (uint8_t)((TCP_HEADER_LEN + 34) << 2); + seg->hlen = (uint8_t)((TCP_HEADER_LEN + 36) << 2); opt = seg->data; opt[0] = TCP_OPTION_MSS; opt[1] = TCP_OPTION_MSS_LEN; @@ -897,7 +963,10 @@ START_TEST(test_tcp_parse_options_parses_mss_sack_permitted_timestamp_and_two_sa memcpy(opt + 10, &left, sizeof(left)); memcpy(opt + 14, &right, sizeof(right)); } - frame_len = ETH_HEADER_LEN + IP_HEADER_LEN + TCP_HEADER_LEN + 34; + opt += 18; + opt[0] = TCP_OPTION_NOP; + opt[1] = TCP_OPTION_NOP; + frame_len = ETH_HEADER_LEN + IP_HEADER_LEN + TCP_HEADER_LEN + 36; tcp_parse_options(seg, frame_len, &po); @@ -942,7 +1011,11 @@ END_TEST START_TEST(test_tcp_parse_options_caps_sack_block_count) { - uint8_t seg_buf[sizeof(struct wolfIP_tcp_seg) + 48]; + /* TCP_SACK_MAX_BLOCKS equals the natural ceiling of SACK blocks that fit + * in the 40-byte legal options budget (2-byte option header + 4*8 block + * bytes = 34 bytes), so we exercise the MAX path with a compliant SACK + * option carrying exactly TCP_SACK_MAX_BLOCKS blocks. */ + uint8_t seg_buf[sizeof(struct wolfIP_tcp_seg) + 36]; struct wolfIP_tcp_seg *seg = (struct wolfIP_tcp_seg *)seg_buf; struct tcp_parsed_opts po; uint8_t *opt; @@ -950,17 +1023,19 @@ START_TEST(test_tcp_parse_options_caps_sack_block_count) int i; memset(seg_buf, 0, sizeof(seg_buf)); - seg->hlen = (uint8_t)((TCP_HEADER_LEN + 42) << 2); + seg->hlen = (uint8_t)((TCP_HEADER_LEN + 36) << 2); opt = seg->data; opt[0] = TCP_OPTION_SACK; - opt[1] = 42; - for (i = 0; i < 5; i++) { + opt[1] = (uint8_t)(2 + (TCP_SACK_MAX_BLOCKS * 8)); + for (i = 0; i < TCP_SACK_MAX_BLOCKS; i++) { uint32_t left = ee32((uint32_t)(100 + (i * 20))); uint32_t right = ee32((uint32_t)(110 + (i * 20))); memcpy(opt + 2 + (i * 8), &left, sizeof(left)); memcpy(opt + 2 + (i * 8) + 4, &right, sizeof(right)); } - frame_len = ETH_HEADER_LEN + IP_HEADER_LEN + TCP_HEADER_LEN + 42; + opt[2 + (TCP_SACK_MAX_BLOCKS * 8)] = TCP_OPTION_NOP; + opt[3 + (TCP_SACK_MAX_BLOCKS * 8)] = TCP_OPTION_NOP; + frame_len = ETH_HEADER_LEN + IP_HEADER_LEN + TCP_HEADER_LEN + 36; tcp_parse_options(seg, frame_len, &po); @@ -2990,6 +3065,49 @@ START_TEST(test_tcp_recv_queues_payload_and_advances_ack) } END_TEST +START_TEST(test_tcp_recv_ignores_reserved_bits_in_hlen) +{ + /* RFC 9293 3.1: the low nibble of the Data Offset byte is Rsrvd and MUST + * be ignored. tcp_recv must therefore base its payload pointer and length + * on the masked (high-nibble) header length, otherwise a peer that sets + * reserved bits would have its delivered bytes shifted by 4*Rsrvd octets. */ + struct wolfIP s; + struct tsocket *ts; + uint8_t payload[8] = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h' }; + uint8_t seg_buf[sizeof(struct wolfIP_tcp_seg) + sizeof(payload)]; + struct wolfIP_tcp_seg *seg = (struct wolfIP_tcp_seg *)seg_buf; + uint32_t seq = 50; + uint8_t out[sizeof(payload) + 4]; + int ret; + + 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.ack = seq; + ts->sock.tcp.bytes_in_flight = 1; + queue_init(&ts->sock.tcp.rxbuf, ts->rxmem, RXBUF_SIZE, seq); + + memset(seg, 0, sizeof(seg_buf)); + seg->ip.len = ee16(IP_HEADER_LEN + TCP_HEADER_LEN + sizeof(payload)); + /* No options (hdr is the bare 20 bytes), but flip every reserved bit on. */ + seg->hlen = (uint8_t)((TCP_HEADER_LEN << 2) | 0x0F); + seg->seq = ee32(seq); + seg->flags = (TCP_FLAG_ACK | TCP_FLAG_PSH); + memcpy(seg->data, payload, sizeof(payload)); + + tcp_recv(ts, seg); + + ck_assert_uint_eq(ts->sock.tcp.ack, seq + sizeof(payload)); + memset(out, 0, sizeof(out)); + ret = queue_pop(&ts->sock.tcp.rxbuf, out, sizeof(out)); + ck_assert_int_eq(ret, (int)sizeof(payload)); + ck_assert_mem_eq(out, payload, sizeof(payload)); +} +END_TEST + START_TEST(test_tcp_recv_wrong_state_does_nothing) { struct wolfIP s; diff --git a/src/wolfip.c b/src/wolfip.c index 99ec1fa3..94b4268a 100644 --- a/src/wolfip.c +++ b/src/wolfip.c @@ -1829,6 +1829,7 @@ static void wolfIP_send_ttl_exceeded(struct wolfIP *s, unsigned int if_idx, icmp.csum = ee16(icmp_checksum((struct wolfIP_icmp_packet *)&icmp, icmp_data_len)); icmp.ip.ver_ihl = 0x45; + icmp.ip.flags_fo = ee16(0x4000U); icmp.ip.ttl = 64; icmp.ip.proto = WI_IPPROTO_ICMP; icmp.ip.id = ipcounter_next(s); @@ -1900,6 +1901,7 @@ static void wolfIP_send_port_unreachable(struct wolfIP *s, unsigned int if_idx, icmp.csum = ee16(icmp_checksum((struct wolfIP_icmp_packet *)&icmp, icmp_data_len)); icmp.ip.ver_ihl = 0x45; + icmp.ip.flags_fo = ee16(0x4000U); icmp.ip.ttl = 64; icmp.ip.proto = WI_IPPROTO_ICMP; icmp.ip.id = ipcounter_next(s); @@ -2316,7 +2318,11 @@ static void icmp_try_deliver_tcp_error(struct wolfIP *s, memcpy(&next_hop_mtu, &icmp->unused[2], sizeof(next_hop_mtu)); next_hop_mtu = ee16(next_hop_mtu); - if (next_hop_mtu > (IP_HEADER_LEN + TCP_HEADER_LEN)) { + /* RFC 879 / RFC 9293 ยง3.7.1: IPv4 default MSS is 536; ignore + * any PTB whose next-hop MTU cannot accommodate it. Rejecting + * (rather than clamping) sub-576 MTUs prevents a spoofed ICMP + * from dragging a SYN-negotiated peer_mss below 536. */ + if (next_hop_mtu >= (IP_HEADER_LEN + TCP_HEADER_LEN + TCP_DEFAULT_MSS)) { uint16_t new_mss = (uint16_t)(next_hop_mtu - (IP_HEADER_LEN + TCP_HEADER_LEN)); @@ -2535,11 +2541,19 @@ struct tcp_parsed_opts { uint32_t ts_val, ts_ecr; }; +/* RFC 9293 3.1: the low nibble of the Data Offset byte is Rsrvd and MUST be + * ignored by receivers. Mask it off before translating the 4-bit word count + * into bytes. */ +static inline uint32_t tcp_data_offset_bytes(uint8_t hlen) +{ + return (uint32_t)((hlen & 0xF0U) >> 2); +} + static void tcp_parse_options(const struct wolfIP_tcp_seg *tcp, uint32_t frame_len, struct tcp_parsed_opts *po) { const uint8_t *opt = tcp->data; - int claimed_opt_len = (tcp->hlen >> 2) - TCP_HEADER_LEN; + int claimed_opt_len = (int)tcp_data_offset_bytes(tcp->hlen) - TCP_HEADER_LEN; int available_bytes = (int)frame_len - (int)sizeof(struct wolfIP_tcp_seg); int opt_len; const uint8_t *opt_end; @@ -2960,7 +2974,7 @@ static void tcp_send_reset_reply(struct wolfIP *s, unsigned int if_idx, return; ip_len = ee16(in->ip.len); - tcp_hlen = (uint32_t)(in->hlen >> 2); + tcp_hlen = tcp_data_offset_bytes(in->hlen); if (tcp_hlen < TCP_HEADER_LEN) return; if (ip_len < (uint16_t)(IP_HEADER_LEN + tcp_hlen)) @@ -3451,9 +3465,12 @@ static inline uint32_t tcp_seq_inc(uint32_t seq, uint32_t n) /* Add a segment to the rx buffer for the application to consume */ static void tcp_recv(struct tsocket *t, struct wolfIP_tcp_seg *seg) { - uint32_t seg_len = ee16(seg->ip.len) - (IP_HEADER_LEN + (seg->hlen >> 2)); + /* RFC 9293 3.1: mask the reserved nibble before deriving header length so a + * peer that sets reserved bits cannot shift our payload pointer/length. */ + uint32_t hdr_len = tcp_data_offset_bytes(seg->hlen); + uint32_t seg_len = ee16(seg->ip.len) - (IP_HEADER_LEN + hdr_len); uint32_t seq = ee32(seg->seq); - const uint8_t *payload = (uint8_t *)seg->ip.data + (seg->hlen >> 2); + const uint8_t *payload = (uint8_t *)seg->ip.data + hdr_len; if ((t->sock.tcp.state != TCP_ESTABLISHED) && (t->sock.tcp.state != TCP_CLOSE_WAIT) && (t->sock.tcp.state != TCP_FIN_WAIT_1) && @@ -4029,7 +4046,7 @@ static void tcp_ack(struct tsocket *t, const struct wolfIP_tcp_seg *tcp) } tcp_process_sack(t, tcp, - (uint32_t)(ETH_HEADER_LEN + IP_HEADER_LEN + (tcp->hlen >> 2))); + (uint32_t)(ETH_HEADER_LEN + IP_HEADER_LEN + tcp_data_offset_bytes(tcp->hlen))); desc = fifo_peek(&t->sock.tcp.txbuf); while ((desc) && (desc->flags & PKT_FLAG_SENT)) { struct wolfIP_tcp_seg *seg = (struct wolfIP_tcp_seg *)(t->txmem + desc->pos + sizeof(*desc)); @@ -4117,7 +4134,7 @@ static void tcp_ack(struct tsocket *t, const struct wolfIP_tcp_seg *tcp) if (ack_count > 0) { struct pkt_desc *fresh_desc = NULL; uint32_t ack_ip_len = ee16(tcp->ip.len); - uint32_t ack_hdr_len = IP_HEADER_LEN + (uint32_t)(tcp->hlen >> 2); + uint32_t ack_hdr_len = IP_HEADER_LEN + tcp_data_offset_bytes(tcp->hlen); uint32_t ack_frame_len = 0; /* This ACK ackwnowledged some data. */ desc = fifo_peek(&t->sock.tcp.txbuf); @@ -4298,14 +4315,14 @@ static void tcp_input(struct wolfIP *S, unsigned int if_idx, t->if_idx = (uint8_t)if_idx; matched = 1; /* Validate minimum TCP header length (data offset). */ - if ((tcp->hlen >> 2) < TCP_HEADER_LEN) { + if (tcp_data_offset_bytes(tcp->hlen) < TCP_HEADER_LEN) { return; /* malformed: TCP header below minimum length */ } /* Validate TCP header length fits in IP payload */ - if (iplen < (uint32_t)(IP_HEADER_LEN + (tcp->hlen >> 2))) { + if (iplen < (uint32_t)(IP_HEADER_LEN + tcp_data_offset_bytes(tcp->hlen))) { return; /* malformed: TCP header exceeds IP length */ } - tcplen = iplen - (IP_HEADER_LEN + (tcp->hlen >> 2)); + tcplen = iplen - (IP_HEADER_LEN + tcp_data_offset_bytes(tcp->hlen)); if (t->sock.tcp.state == TCP_LISTEN) { /* RFC 9293 3.10.7.2: reject ACK-bearing segments before SYN handling. */ if (tcp->flags & TCP_FLAG_RST) @@ -6248,6 +6265,30 @@ int wolfIP_sock_can_write(struct wolfIP *s, int sockfd) return -WOLFIP_EINVAL; } +/* Return non-zero if any socket in arr[0..n] other than self already claims + * (local_ip, port). IPADDR_ANY on either side overlaps with any specific + * local address, matching POSIX EADDRINUSE semantics. */ +static int bind_port_in_use(const struct tsocket *arr, int n, + const struct tsocket *self, + ip4 new_local_ip, uint16_t new_port) +{ + int i; + if (new_port == 0) + return 0; + for (i = 0; i < n; i++) { + const struct tsocket *tk = &arr[i]; + if (tk == self) + continue; + if (tk->src_port != new_port) + continue; + if (tk->local_ip != IPADDR_ANY && new_local_ip != IPADDR_ANY && + tk->local_ip != new_local_ip) + continue; + return 1; + } + return 0; +} + int wolfIP_sock_bind(struct wolfIP *s, int sockfd, const struct wolfIP_sockaddr *addr, socklen_t addrlen) { @@ -6325,6 +6366,11 @@ int wolfIP_sock_bind(struct wolfIP *s, int sockfd, const struct wolfIP_sockaddr else ts->local_ip = IPADDR_ANY; } + if (bind_port_in_use(s->tcpsockets, MAX_TCPSOCKETS, ts, + ts->local_ip, new_port)) { + ts->local_ip = prev_ip; + return -1; + } if (wolfIP_filter_notify_socket_event( WOLFIP_FILT_BINDING, s, ts, ts->local_ip, new_port, IPADDR_ANY, 0) != 0) { @@ -6360,6 +6406,11 @@ int wolfIP_sock_bind(struct wolfIP *s, int sockfd, const struct wolfIP_sockaddr else ts->local_ip = IPADDR_ANY; } + if (bind_port_in_use(s->udpsockets, MAX_UDPSOCKETS, ts, + ts->local_ip, new_port)) { + ts->local_ip = prev_ip; + return -1; + } ts->src_port = new_port; if (wolfIP_filter_notify_socket_event( WOLFIP_FILT_BINDING, s, ts, @@ -6393,6 +6444,11 @@ int wolfIP_sock_bind(struct wolfIP *s, int sockfd, const struct wolfIP_sockaddr if (primary && primary->ip != IPADDR_ANY) ts->local_ip = primary->ip; } + if (bind_port_in_use(s->icmpsockets, MAX_ICMPSOCKETS, ts, + ts->local_ip, new_id)) { + ts->local_ip = prev_ip; + return -1; + } ts->src_port = new_id; if (wolfIP_filter_notify_socket_event( WOLFIP_FILT_BINDING, s, ts, @@ -6530,6 +6586,7 @@ static void icmp_input(struct wolfIP *s, unsigned int if_idx, struct wolfIP_ip_p ip->dst = tmp; ip->ttl = 64; ip->id = ipcounter_next(s); + ip->flags_fo = ee16(0x4000U); ip->csum = 0; iphdr_set_checksum(ip); #ifdef ETHERNET