diff --git a/src/test/unit/unit.c b/src/test/unit/unit.c index 3325dc0..07f2962 100644 --- a/src/test/unit/unit.c +++ b/src/test/unit/unit.c @@ -104,6 +104,9 @@ Suite *wolf_suite(void) tcase_add_test(tc_utils, test_timer_pop_right_child_swap); tcase_add_test(tc_utils, test_timer_pop_break_when_root_small); tcase_add_test(tc_utils, test_is_timer_expired_skips_zero_head); + tcase_add_test(tc_utils, test_timer_pop_siftdown_resets_after_cancelled); + tcase_add_test(tc_utils, test_tcp_reset_reply_sets_df_bit); + tcase_add_test(tc_utils, test_ipcounter_seeded_at_init); tcase_add_test(tc_utils, test_wolfip_getdev_ex_api); tcase_add_test(tc_utils, test_wolfip_ll_frame_mtu_enforces_minimum); tcase_add_test(tc_utils, test_transport_capacity_helpers_cover_guard_paths); @@ -216,7 +219,9 @@ Suite *wolf_suite(void) tcase_add_test(tc_utils, test_syn_sent_bare_rst_dropped); 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_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); tcase_add_test(tc_utils, test_udp_no_icmp_unreachable_for_broadcast_src); tcase_add_test(tc_utils, test_udp_no_icmp_unreachable_for_multicast_src); @@ -230,6 +235,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_utils, test_syn_rcvd_bad_ack_sends_rst); tcase_add_test(tc_utils, test_established_fin_without_ack_dropped); tcase_add_test(tc_utils, test_ip_recv_drops_source_routed_packet); + tcase_add_test(tc_utils, test_ip_recv_drops_ssrr_source_routed_packet); tcase_add_test(tc_utils, test_sock_sendto_error_paths); tcase_add_test(tc_utils, test_sock_sendto_null_buf_or_len_zero); tcase_add_test(tc_utils, test_sock_sendto_tcp_not_established); @@ -324,6 +330,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_utils, test_tcp_has_pending_unsent_payload_ignores_zero_ip_len_ack_only_desc); tcase_add_test(tc_utils, test_tcp_initial_cwnd_caps_to_iw10_and_half_rwnd); tcase_add_test(tc_utils, test_tcp_persist_cb_sends_one_byte_probe); + tcase_add_test(tc_utils, test_tcp_zero_wnd_probe_includes_timestamp_when_enabled); tcase_add_test(tc_utils, test_tcp_zero_wnd_probe_rejects_invalid_inputs_and_empty_payload); tcase_add_test(tc_utils, test_tcp_zero_wnd_probe_skips_ack_only_segment); tcase_add_test(tc_utils, test_tcp_zero_wnd_probe_selects_middle_byte_at_snd_una); @@ -342,6 +349,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_utils, test_poll_icmp_send_on_arp_miss_requests_arp_and_retains_queue); tcase_add_test(tc_utils, test_dhcp_timer_cb_paths); tcase_add_test(tc_utils, test_regression_dhcp_lease_expiry_deconfigures_address); + tcase_add_test(tc_utils, test_dhcp_request_retry_exhaustion_deconfigures_lease); tcase_add_test(tc_utils, test_dhcp_timer_cb_send_failure_does_not_consume_retry_budget); tcase_add_test(tc_utils, test_dhcp_client_init_and_bound); tcase_add_test(tc_utils, test_dhcp_client_init_bind_failure_closes_socket); @@ -493,6 +501,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_utils, test_tcp_input_syn_rcvd_ack_established); tcase_add_test(tc_utils, test_tcp_input_syn_rcvd_ack_invalid_ack_rejected); tcase_add_test(tc_utils, test_tcp_input_syn_rcvd_ack_invalid_seq_rejected); + tcase_add_test(tc_utils, test_tcp_input_syn_rcvd_ack_fin_transitions_to_close_wait); tcase_add_test(tc_utils, test_tcp_input_filter_drop); 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); @@ -580,6 +589,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_utils, test_tcp_ack_sack_early_retransmit_before_three_dupack); tcase_add_test(tc_utils, test_tcp_input_listen_syn_without_sack_disables_sack); tcase_add_test(tc_utils, test_tcp_input_listen_syn_arms_control_rto); + tcase_add_test(tc_utils, test_tcp_input_listen_syn_sends_synack_immediately); tcase_add_test(tc_utils, test_tcp_input_syn_sent_synack_without_sack_disables_sack); tcase_add_test(tc_utils, test_tcp_recv_partial_hole_fill_consumes_stored_ooo); tcase_add_test(tc_utils, test_tcp_ack_ignores_sack_when_not_negotiated); @@ -637,6 +647,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_proto, test_arp_reply_updates_expired_entry); tcase_add_test(tc_proto, test_wolfip_recv_ex_multi_interface_arp_reply); tcase_add_test(tc_proto, test_forward_prepare_null_args); + tcase_add_test(tc_proto, test_send_ttl_exceeded_includes_full_ip_header_with_options); tcase_add_test(tc_proto, test_send_ttl_exceeded_filter_drop); tcase_add_test(tc_proto, test_send_ttl_exceeded_ip_filter_drop); tcase_add_test(tc_proto, test_send_ttl_exceeded_eth_filter_drop); @@ -648,6 +659,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_proto, test_arp_request_filter_drop); tcase_add_test(tc_proto, test_arp_request_invalid_interface); tcase_add_test(tc_proto, test_arp_request_no_send_fn); + tcase_add_test(tc_proto, test_arp_reply_rejects_invalid_sender_ip); tcase_add_test(tc_proto, test_arp_reply_filter_drop); tcase_add_test(tc_proto, test_arp_recv_invalid_iface); tcase_add_test(tc_proto, test_arp_recv_filter_drop); diff --git a/src/test/unit/unit_esp.c b/src/test/unit/unit_esp.c index 3bc70ed..b04996d 100644 --- a/src/test/unit/unit_esp.c +++ b/src/test/unit/unit_esp.c @@ -26,9 +26,11 @@ #define WOLFSSL_WOLFIP #endif #undef WOLFIP_MAX_INTERFACES -#define WOLFIP_MAX_INTERFACES 1 +#define WOLFIP_MAX_INTERFACES 2 #undef WOLFIP_ENABLE_LOOPBACK #define WOLFIP_ENABLE_LOOPBACK 0 +#undef WOLFIP_ENABLE_FORWARDING +#define WOLFIP_ENABLE_FORWARDING 1 #include "check.h" #include "../../../config.h" @@ -1428,6 +1430,297 @@ START_TEST(test_ip_recv_esp_transport_unwrap_failure_drops_packet) } END_TEST +/* Mock send that captures the last frame sent. + * Used by tests that exercise the full TX path (tcp_send_empty_immediate). */ +static uint8_t esp_test_last_frame[LINK_MTU]; +static uint32_t esp_test_last_frame_size; + +static int esp_test_mock_send(struct wolfIP_ll_dev *dev, void *frame, uint32_t len) +{ + (void)dev; + memcpy(esp_test_last_frame, frame, len); + esp_test_last_frame_size = len; + return 0; +} + +/* Seed an ARP neighbor entry so tcp_send_empty_immediate can resolve + * the destination MAC without a pending ARP exchange. */ +static void esp_test_seed_arp(struct wolfIP *s, unsigned int if_idx, ip4 ip) +{ + static const uint8_t fake_mac[6] = {0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF}; + s->arp.neighbors[0].ip = ip; + s->arp.neighbors[0].if_idx = (uint8_t)if_idx; + s->arp.neighbors[0].ts = s->last_tick; + memcpy(s->arp.neighbors[0].mac, fake_mac, 6); +} + +/* Regression: tcp_send_empty_immediate must ESP-wrap outbound frames when + * an outbound SA exists. Without the fix the fallback path calls + * wolfIP_ll_send_frame directly, leaking plaintext TCP ACKs. */ +START_TEST(test_tcp_ack_esp_wrapped_when_txfifo_full) +{ + struct wolfIP s; + struct tsocket *ts; + struct wolfIP_ll_dev *ll; + struct wolfIP_ip_packet *sent_ip; + + /* Stack + ESP init */ + wolfIP_init(&s); + esp_setup(); + esp_add_cbc_test_sas(); + + /* Configure interface 0 with the SA's local address */ + wolfIP_ipconfig_set(&s, atoip4(T_SRC), 0xFFFFFF00U, 0); + + /* Wire up the mock link-layer device */ + ll = wolfIP_ll_at(&s, 0); + ck_assert_ptr_nonnull(ll); + memcpy(ll->mac, (uint8_t[]){0x00,0x11,0x22,0x33,0x44,0x55}, 6); + ll->send = esp_test_mock_send; + ll->poll = NULL; + + /* Seed ARP so the immediate-send path can resolve the peer MAC */ + esp_test_seed_arp(&s, 0, atoip4(T_DST)); + + /* Set up an ESTABLISHED TCP socket whose addresses match the SA */ + ts = &s.tcpsockets[0]; + memset(ts, 0, sizeof(*ts)); + ts->S = &s; + ts->proto = WI_IPPROTO_TCP; + ts->if_idx = 0; + ts->local_ip = atoip4(T_SRC); + ts->remote_ip = atoip4(T_DST); + ts->src_port = 5000; + ts->dst_port = 80; + ts->sock.tcp.state = TCP_ESTABLISHED; + ts->sock.tcp.seq = 1000; + ts->sock.tcp.ack = 2000; + ts->sock.tcp.snd_una = 1000; + fifo_init(&ts->sock.tcp.txbuf, ts->txmem, TXBUF_SIZE); + + /* Fill the TX FIFO until it cannot accept another segment, so that + * tcp_send_empty() is forced into the tcp_send_empty_immediate() fallback */ + { + uint8_t fill[ETH_HEADER_LEN + IP_HEADER_LEN + TCP_HEADER_LEN + 4]; + struct wolfIP_tcp_seg *fill_seg = (struct wolfIP_tcp_seg *)fill; + uint32_t fill_len = sizeof(fill); + + memset(fill, 0, sizeof(fill)); + fill_seg->ip.len = ee16((uint16_t)(IP_HEADER_LEN + TCP_HEADER_LEN + 4)); + fill_seg->hlen = TCP_HEADER_LEN << 2; + fill_seg->flags = TCP_FLAG_ACK; + fill_seg->seq = ee32(ts->sock.tcp.seq); + fill_seg->ack = ee32(ts->sock.tcp.ack); + fill_seg->src_port = ee16(ts->src_port); + fill_seg->dst_port = ee16(ts->dst_port); + + while (fifo_push(&ts->sock.tcp.txbuf, fill_seg, fill_len) == 0) + ; /* keep pushing until FIFO is full */ + } + + /* Clear the capture buffer */ + esp_test_last_frame_size = 0; + memset(esp_test_last_frame, 0, sizeof(esp_test_last_frame)); + + /* Send a pure ACK, FIFO is full, so this must go through + * tcp_send_empty_immediate(). */ + tcp_send_ack(ts); + + /* A frame must have been sent */ + ck_assert_uint_gt(esp_test_last_frame_size, 0); + + /* The IP protocol in the sent frame must be ESP (50 / 0x32), + * NOT plaintext TCP (6). */ + sent_ip = (struct wolfIP_ip_packet *)esp_test_last_frame; + ck_assert_uint_eq(sent_ip->proto, 0x32); +} +END_TEST + +/* Regression: tcp_send_zero_wnd_probe must ESP-wrap outbound frames when + * an outbound SA exists. Without the fix the probe (which carries 1 byte + * of application payload) is sent in plaintext via wolfIP_ll_send_frame. */ +START_TEST(test_tcp_zero_wnd_probe_esp_wrapped) +{ + struct wolfIP s; + struct tsocket *ts; + struct wolfIP_ll_dev *ll; + struct wolfIP_ip_packet *sent_ip; + + wolfIP_init(&s); + esp_setup(); + esp_add_cbc_test_sas(); + wolfIP_ipconfig_set(&s, atoip4(T_SRC), 0xFFFFFF00U, 0); + + ll = wolfIP_ll_at(&s, 0); + ck_assert_ptr_nonnull(ll); + memcpy(ll->mac, (uint8_t[]){0x00,0x11,0x22,0x33,0x44,0x55}, 6); + ll->send = esp_test_mock_send; + ll->poll = NULL; + + esp_test_seed_arp(&s, 0, atoip4(T_DST)); + + ts = &s.tcpsockets[0]; + memset(ts, 0, sizeof(*ts)); + ts->S = &s; + ts->proto = WI_IPPROTO_TCP; + ts->if_idx = 0; + ts->local_ip = atoip4(T_SRC); + ts->remote_ip = atoip4(T_DST); + ts->src_port = 5000; + ts->dst_port = 80; + ts->sock.tcp.state = TCP_ESTABLISHED; + ts->sock.tcp.seq = 1000; + ts->sock.tcp.ack = 2000; + ts->sock.tcp.snd_una = 1000; + fifo_init(&ts->sock.tcp.txbuf, ts->txmem, TXBUF_SIZE); + + /* Enqueue one segment with 4 bytes of payload so the probe has + * data to pick from. */ + { + uint8_t buf[ETH_HEADER_LEN + IP_HEADER_LEN + TCP_HEADER_LEN + 4]; + struct wolfIP_tcp_seg *seg = (struct wolfIP_tcp_seg *)buf; + uint32_t frame_len = sizeof(buf); + + memset(buf, 0, sizeof(buf)); + seg->ip.len = ee16((uint16_t)(IP_HEADER_LEN + TCP_HEADER_LEN + 4)); + seg->hlen = TCP_HEADER_LEN << 2; + seg->flags = TCP_FLAG_ACK; + seg->seq = ee32(ts->sock.tcp.seq); + seg->ack = ee32(ts->sock.tcp.ack); + seg->src_port = ee16(ts->src_port); + seg->dst_port = ee16(ts->dst_port); + /* Application payload byte that must NOT appear in plaintext */ + seg->data[0] = 0x42; + ck_assert_int_eq(fifo_push(&ts->sock.tcp.txbuf, seg, frame_len), 0); + } + + esp_test_last_frame_size = 0; + memset(esp_test_last_frame, 0, sizeof(esp_test_last_frame)); + + /* Call the zero-window probe directly */ + tcp_send_zero_wnd_probe(ts); + + ck_assert_uint_gt(esp_test_last_frame_size, 0); + + sent_ip = (struct wolfIP_ip_packet *)esp_test_last_frame; + ck_assert_uint_eq(sent_ip->proto, 0x32); +} +END_TEST + +/* Regression: tcp_send_reset_reply must ESP-wrap RST segments when the + * destination has a matching outbound ESP SA. Without the fix the RST is + * sent in plaintext via wolfIP_ll_send_frame. */ +START_TEST(test_tcp_reset_reply_esp_wrapped) +{ + struct wolfIP s; + struct wolfIP_ll_dev *ll; + struct wolfIP_ip_packet *sent_ip; + struct wolfIP_tcp_seg in_seg; + union transport_pseudo_header ph; + + wolfIP_init(&s); + esp_setup(); + esp_add_cbc_test_sas(); + /* Our IP is T_SRC so the RST reply (T_SRC->T_DST) matches the + * outbound SA direction. */ + wolfIP_ipconfig_set(&s, atoip4(T_SRC), 0xFFFFFF00U, 0); + + ll = wolfIP_ll_at(&s, 0); + ck_assert_ptr_nonnull(ll); + memcpy(ll->mac, (uint8_t[]){0x00,0x11,0x22,0x33,0x44,0x55}, 6); + ll->send = esp_test_mock_send; + ll->poll = NULL; + + /* Build an inbound SYN from T_DST destined to T_SRC (our IP) on + * a port with no listener -- this will trigger a RST reply back + * toward T_DST, which has a matching outbound ESP SA. */ + memset(&in_seg, 0, sizeof(in_seg)); + memcpy(in_seg.ip.eth.dst, ll->mac, 6); + memcpy(in_seg.ip.eth.src, (uint8_t[]){0xAA,0xBB,0xCC,0xDD,0xEE,0xFF}, 6); + in_seg.ip.eth.type = ee16(0x0800); + in_seg.ip.ver_ihl = 0x45; + in_seg.ip.ttl = 64; + in_seg.ip.proto = WI_IPPROTO_TCP; + in_seg.ip.len = ee16(IP_HEADER_LEN + TCP_HEADER_LEN); + in_seg.ip.src = ee32(atoip4(T_DST)); + in_seg.ip.dst = ee32(atoip4(T_SRC)); + in_seg.ip.csum = 0; + iphdr_set_checksum(&in_seg.ip); + + in_seg.src_port = ee16(40000); + in_seg.dst_port = ee16(9999); /* no listener */ + in_seg.seq = ee32(1); + in_seg.hlen = TCP_HEADER_LEN << 2; + in_seg.flags = TCP_FLAG_SYN; + in_seg.win = ee16(65535); + memset(&ph, 0, sizeof(ph)); + ph.ph.src = in_seg.ip.src; + ph.ph.dst = in_seg.ip.dst; + ph.ph.proto = WI_IPPROTO_TCP; + ph.ph.len = ee16(TCP_HEADER_LEN); + in_seg.csum = ee16(transport_checksum(&ph, &in_seg.src_port)); + + esp_test_last_frame_size = 0; + memset(esp_test_last_frame, 0, sizeof(esp_test_last_frame)); + + tcp_send_reset_reply(&s, 0, &in_seg); + + ck_assert_uint_gt(esp_test_last_frame_size, 0); + + sent_ip = (struct wolfIP_ip_packet *)esp_test_last_frame; + ck_assert_uint_eq(sent_ip->proto, 0x32); +} +END_TEST + +/* Regression: wolfIP_forward_packet must ESP-wrap forwarded IP packets when + * the outbound interface has an ESP SA configured. Without the fix the + * forwarding path calls wolfIP_ll_send_frame directly, sending full forwarded + * payload in plaintext. */ +START_TEST(test_forward_packet_esp_wrapped) +{ + struct wolfIP s; + struct wolfIP_ll_dev *ll; + struct wolfIP_ip_packet *sent_ip; + uint8_t peer_mac[6] = {0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF}; + uint8_t payload[] = { 'f', 'w', 'd', '!' }; + uint8_t buf[LINK_MTU]; + uint32_t frame_len; + + wolfIP_init(&s); + esp_setup(); + esp_add_cbc_test_sas(); + + /* Interface 0: ingress (source side) */ + wolfIP_ipconfig_set(&s, atoip4("192.168.0.1"), 0xFFFFFF00U, 0); + + /* Interface 1: egress -- its IP matches the SA's source address so + * esp_send will find the outbound SA. */ + ll = wolfIP_ll_at(&s, 1); + ck_assert_ptr_nonnull(ll); + memcpy(ll->mac, (uint8_t[]){0x00,0x11,0x22,0x33,0x44,0x66}, 6); + ll->send = esp_test_mock_send; + ll->poll = NULL; + wolfIP_ipconfig_set_ex(&s, 1, atoip4(T_SRC), 0xFFFFFF00U, 0); + + /* Build a UDP packet from T_SRC -> T_DST that will be "forwarded" */ + frame_len = build_udp_ip_packet(buf, sizeof(buf), + atoip4(T_SRC), atoip4(T_DST), + 1234, 5678, payload, sizeof(payload)); + + esp_test_last_frame_size = 0; + memset(esp_test_last_frame, 0, sizeof(esp_test_last_frame)); + + /* Forward the packet out interface 1 */ + wolfIP_forward_packet(&s, 1, (struct wolfIP_ip_packet *)buf, frame_len, + peer_mac, 0); + + ck_assert_uint_gt(esp_test_last_frame_size, 0); + + sent_ip = (struct wolfIP_ip_packet *)esp_test_last_frame; + ck_assert_uint_eq(sent_ip->proto, 0x32); +} +END_TEST + static Suite *esp_suite(void) { Suite *s; @@ -1513,6 +1806,14 @@ static Suite *esp_suite(void) tcase_add_test(tc, test_wrap_rejects_ip_len_below_header); suite_add_tcase(s, tc); + /* TCP immediate-send ESP regression */ + tc = tcase_create("tcp_immediate_esp"); + tcase_add_test(tc, test_tcp_ack_esp_wrapped_when_txfifo_full); + tcase_add_test(tc, test_tcp_zero_wnd_probe_esp_wrapped); + tcase_add_test(tc, test_tcp_reset_reply_esp_wrapped); + tcase_add_test(tc, test_forward_packet_esp_wrapped); + suite_add_tcase(s, tc); + return s; } diff --git a/src/test/unit/unit_tests_api.c b/src/test/unit/unit_tests_api.c index 3012cad..4ecb8f7 100644 --- a/src/test/unit/unit_tests_api.c +++ b/src/test/unit/unit_tests_api.c @@ -3913,6 +3913,67 @@ START_TEST(test_ip_recv_drops_broadcast_source) } END_TEST +/* Cover the multicast source branch of the ip_recv source address + * validation so that removing || wolfIP_ip_is_multicast(src) is detected. */ +START_TEST(test_ip_recv_drops_multicast_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(0xE0000001U); /* 224.0.0.1 — multicast */ + 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. */ @@ -3953,6 +4014,43 @@ START_TEST(test_arp_recv_rejects_broadcast_sender) } END_TEST +/* Cover the multicast branch of the ARP request sender-IP validation so + * that removing && !wolfIP_ip_is_multicast(sip) is detected. */ +START_TEST(test_arp_recv_rejects_multicast_sender) +{ + struct wolfIP s; + struct arp_packet arp; + struct wolfIP_ll_dev *ll; + struct ipconf *conf; + static const uint8_t fake_mac[6] = {0xDE, 0xAD, 0xBE, 0xEF, 0x00, 0x01}; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + ll = wolfIP_getdev_ex(&s, TEST_PRIMARY_IF); + conf = wolfIP_ipconf_at(&s, TEST_PRIMARY_IF); + + memset(&arp, 0, sizeof(arp)); + memcpy(arp.eth.dst, ll->mac, 6); + memcpy(arp.eth.src, fake_mac, 6); + arp.eth.type = ee16(ETH_TYPE_ARP); + arp.htype = ee16(1); + arp.ptype = ee16(0x0800); + arp.hlen = 6; + arp.plen = 4; + arp.opcode = ee16(ARP_REQUEST); + memcpy(arp.sma, fake_mac, 6); + arp.sip = ee32(0xE0000001U); /* multicast sender 224.0.0.1 */ + memset(arp.tma, 0, 6); + arp.tip = ee32(conf->ip); + + arp_recv(&s, TEST_PRIMARY_IF, &arp, sizeof(arp)); + + ck_assert_int_lt(arp_neighbor_index(&s, TEST_PRIMARY_IF, 0xE0000001U), 0); +} +END_TEST + /* Regression: arp_recv must reject ARP packets with incorrect hardware or * protocol type fields (htype != 1, ptype != 0x0800, hlen != 6, plen != 4). * Without validation, non-Ethernet/IPv4 ARP packets pollute the cache. */ @@ -4433,3 +4531,84 @@ START_TEST(test_ip_recv_drops_source_routed_packet) ck_assert_int_eq(listener->sock.tcp.state, TCP_LISTEN); } END_TEST + +/* Cover the SSRR (0x89) branch of the source-routing drop guard so that + * removing the || type == 0x89 check produces a test failure. */ +START_TEST(test_ip_recv_drops_ssrr_source_routed_packet) +{ + struct wolfIP s; + int listen_sd; + struct tsocket *listener; + struct wolfIP_sockaddr_in sin; + uint8_t pkt[ETH_HEADER_LEN + 24 + TCP_HEADER_LEN]; + struct wolfIP_ip_packet *ip; + struct wolfIP_ll_dev *ll; + union transport_pseudo_header ph; + uint16_t *tcp_csum_field; + 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(pkt, 0, sizeof(pkt)); + + ip = (struct wolfIP_ip_packet *)pkt; + memcpy(ip->eth.dst, ll->mac, 6); + memcpy(ip->eth.src, src_mac, 6); + ip->eth.type = ee16(ETH_TYPE_IP); + + ip->ver_ihl = 0x46; + ip->ttl = 64; + ip->proto = WI_IPPROTO_TCP; + ip->len = ee16(24 + TCP_HEADER_LEN); + ip->src = ee32(0x0A0000A1U); + ip->dst = ee32(0x0A000001U); + + /* IP options: SSRR */ + { + uint8_t *opts = pkt + ETH_HEADER_LEN + IP_HEADER_LEN; + opts[0] = 0x89; /* SSRR */ + opts[1] = 3; + opts[2] = 4; + opts[3] = 0x00; /* End of Options */ + } + ip->csum = 0; + iphdr_set_checksum(ip); + + { + uint8_t *tcp = pkt + ETH_HEADER_LEN + 24; + tcp[0] = (uint8_t)(40000 >> 8); + tcp[1] = (uint8_t)(40000 & 0xFF); + tcp[2] = (uint8_t)(1234 >> 8); + tcp[3] = (uint8_t)(1234 & 0xFF); + tcp[4] = 0; tcp[5] = 0; tcp[6] = 0; tcp[7] = 1; + tcp[12] = TCP_HEADER_LEN << 2; + tcp[13] = TCP_FLAG_SYN; + tcp[14] = 0xFF; tcp[15] = 0xFF; + tcp_csum_field = (uint16_t *)(tcp + 16); + *tcp_csum_field = 0; + memset(&ph, 0, sizeof(ph)); + ph.ph.src = ip->src; + ph.ph.dst = ip->dst; + ph.ph.proto = WI_IPPROTO_TCP; + ph.ph.len = ee16(TCP_HEADER_LEN); + *tcp_csum_field = ee16(transport_checksum(&ph, tcp)); + } + + ip_recv(&s, TEST_PRIMARY_IF, ip, sizeof(pkt)); + + ck_assert_int_eq(listener->sock.tcp.state, TCP_LISTEN); +} +END_TEST diff --git a/src/test/unit/unit_tests_dns_dhcp.c b/src/test/unit/unit_tests_dns_dhcp.c index 926b887..d081a32 100644 --- a/src/test/unit/unit_tests_dns_dhcp.c +++ b/src/test/unit/unit_tests_dns_dhcp.c @@ -3952,6 +3952,40 @@ START_TEST(test_regression_dhcp_lease_expiry_deconfigures_address) } END_TEST +/* Regression: when DHCP_REQUEST_SENT retries are exhausted, the offered IP + * (applied during dhcp_parse_offer) must be deconfigured before transitioning + * to DHCP_OFF. Without the fix the device keeps using an unconfirmed IP. */ +START_TEST(test_dhcp_request_retry_exhaustion_deconfigures_lease) +{ + struct wolfIP s; + struct ipconf *primary; + + wolfIP_init(&s); + mock_link_init(&s); + primary = wolfIP_primary_ipconf(&s); + ck_assert_ptr_nonnull(primary); + s.dhcp_udp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + ck_assert_int_gt(s.dhcp_udp_sd, 0); + s.dhcp_xid = 1U; + + /* Simulate state after dhcp_parse_offer applied the offered IP */ + wolfIP_ipconfig_set(&s, 0x0A000064U, 0xFFFFFF00U, 0x0A000001U); + s.dhcp_ip = primary->ip; + s.dhcp_server_ip = 0x0A000001U; + s.dhcp_state = DHCP_REQUEST_SENT; + s.dhcp_timeout_count = DHCP_REQUEST_RETRIES; /* exhausted */ + + dhcp_timer_cb(&s); + + ck_assert_int_eq(s.dhcp_state, DHCP_OFF); + /* The unconfirmed IP must have been removed */ + ck_assert_uint_eq(primary->ip, 0U); + ck_assert_uint_eq(primary->mask, 0U); + ck_assert_uint_eq(s.dhcp_ip, 0U); + ck_assert_uint_eq(s.dhcp_server_ip, 0U); +} +END_TEST + START_TEST(test_dhcp_timer_cb_send_failure_does_not_consume_retry_budget) { struct wolfIP s; @@ -4865,7 +4899,7 @@ START_TEST(test_udp_try_recv_unmatched_port_sends_icmp_unreachable) (uint32_t)(ETH_HEADER_LEN + IP_HEADER_LEN + UDP_HEADER_LEN + 4)); ck_assert_uint_eq(last_frame_sent_size, - sizeof(struct wolfIP_icmp_dest_unreachable_packet)); + (uint32_t)(ETH_HEADER_LEN + IP_HEADER_LEN + ICMP_DEST_UNREACH_SIZE)); icmp = (struct wolfIP_icmp_dest_unreachable_packet *)last_frame_sent; ck_assert_uint_eq(icmp->type, 3U); ck_assert_uint_eq(icmp->code, 3U); @@ -4875,7 +4909,7 @@ START_TEST(test_udp_try_recv_unmatched_port_sends_icmp_unreachable) ck_assert_uint_eq(ee32(icmp->ip.src), local_ip); ck_assert_uint_eq(ee32(icmp->ip.dst), remote_ip); ck_assert_mem_eq(icmp->orig_packet, ((uint8_t *)udp) + ETH_HEADER_LEN, - TTL_EXCEEDED_ORIG_PACKET_SIZE); + TTL_EXCEEDED_ORIG_PACKET_SIZE_DEFAULT); } END_TEST diff --git a/src/test/unit/unit_tests_proto.c b/src/test/unit/unit_tests_proto.c index 60df923..cda5db0 100644 --- a/src/test/unit/unit_tests_proto.c +++ b/src/test/unit/unit_tests_proto.c @@ -1676,6 +1676,58 @@ START_TEST(test_tcp_persist_cb_sends_one_byte_probe) } END_TEST +/* Regression: zero-window probes must include the TCP timestamp option when + * ts_enabled is set, per RFC 7323 section 3.2. Without the fix the probe buffer + * has no room for options and the timestamp is omitted. */ +START_TEST(test_tcp_zero_wnd_probe_includes_timestamp_when_enabled) +{ + struct wolfIP s; + struct tsocket *ts; + ip4 local_ip = 0x0A000001U; + ip4 remote_ip = 0x0A000002U; + uint8_t peer_mac[6] = {0x00, 0xaa, 0xbb, 0xcc, 0xdd, 0xf2}; + struct wolfIP_tcp_seg *tcp; + uint8_t payload[4] = {0x42, 0x43, 0x44, 0x45}; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, local_ip, 0xFFFFFF00U, 0); + wolfIP_filter_set_callback(NULL, NULL); + last_frame_sent_size = 0; + + s.arp.neighbors[0].ip = remote_ip; + s.arp.neighbors[0].if_idx = TEST_PRIMARY_IF; + memcpy(s.arp.neighbors[0].mac, peer_mac, sizeof(peer_mac)); + + ts = &s.tcpsockets[0]; + memset(ts, 0, sizeof(*ts)); + ts->proto = WI_IPPROTO_TCP; + ts->S = &s; + ts->sock.tcp.state = TCP_ESTABLISHED; + ts->local_ip = local_ip; + ts->remote_ip = remote_ip; + ts->if_idx = TEST_PRIMARY_IF; + ts->src_port = 1111; + ts->dst_port = 2222; + ts->sock.tcp.seq = 10; + ts->sock.tcp.ack = 20; + ts->sock.tcp.snd_una = 10; + ts->sock.tcp.rto = 100; + ts->sock.tcp.cwnd = TCP_MSS * 4; + ts->sock.tcp.peer_rwnd = 0; + ts->sock.tcp.ts_enabled = 1; + fifo_init(&ts->sock.tcp.txbuf, ts->txmem, TXBUF_SIZE); + + ck_assert_int_eq(enqueue_tcp_tx_with_payload(ts, payload, sizeof(payload), TCP_FLAG_ACK | TCP_FLAG_PSH), 0); + s.last_tick = 500; + tcp_persist_cb(ts); + + ck_assert_uint_gt(last_frame_sent_size, 0); + tcp = (struct wolfIP_tcp_seg *)last_frame_sent; + ck_assert_int_ge(tcp_option_find(tcp, TCP_OPTION_TS), 0); +} +END_TEST + START_TEST(test_tcp_zero_wnd_probe_selects_middle_byte_at_snd_una) { struct wolfIP s; @@ -2370,6 +2422,103 @@ START_TEST(test_is_timer_expired_skips_zero_head) } END_TEST +/* Regression: when timers_binheap_pop skips multiple cancelled timers + * (expires==0) in its do-while loop, the sift-down cursor must reset to 0 + * on each iteration. Without the fix the cursor stays at a leaf position + * from the previous sift-down, so the replacement element at index 0 is + * never sifted down, breaking the min-heap invariant. */ +START_TEST(test_timer_pop_siftdown_resets_after_cancelled) +{ + struct timers_binheap h; + struct wolfIP_timer popped; + int id1, id2; + + memset(&h, 0, sizeof(h)); + + /* Insert five timers: [10, 20, 50, 100, 200] */ + id1 = timers_binheap_insert(&h, (struct wolfIP_timer){ .expires = 10 }); + id2 = timers_binheap_insert(&h, (struct wolfIP_timer){ .expires = 20 }); + timers_binheap_insert(&h, (struct wolfIP_timer){ .expires = 50 }); + timers_binheap_insert(&h, (struct wolfIP_timer){ .expires = 100 }); + timers_binheap_insert(&h, (struct wolfIP_timer){ .expires = 200 }); + + /* Cancel the two smallest */ + timer_binheap_cancel(&h, id1); + timer_binheap_cancel(&h, id2); + + /* Pop must skip both cancelled timers and return 50 */ + popped = timers_binheap_pop(&h); + ck_assert_uint_eq(popped.expires, 50); + + /* Next pop must return 100 -- verifies the heap invariant held */ + popped = timers_binheap_pop(&h); + ck_assert_uint_eq(popped.expires, 100); +} +END_TEST + +/* Regression: tcp_send_reset_reply must set the DF bit on outbound RST + * segments, consistent with the normal TCP output path. Without DF, the + * sequential IP ID is observable and exploitable for idle-scan attacks. */ +START_TEST(test_tcp_reset_reply_sets_df_bit) +{ + struct wolfIP s; + struct wolfIP_tcp_seg in_seg; + union transport_pseudo_header ph; + struct wolfIP_ip_packet *sent_ip; + + 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(&in_seg, 0, sizeof(in_seg)); + { + struct wolfIP_ll_dev *ll = wolfIP_getdev_ex(&s, TEST_PRIMARY_IF); + memcpy(in_seg.ip.eth.dst, ll->mac, 6); + } + memcpy(in_seg.ip.eth.src, "\xAA\xBB\xCC\xDD\xEE\xFF", 6); + in_seg.ip.eth.type = ee16(0x0800); + in_seg.ip.ver_ihl = 0x45; + in_seg.ip.ttl = 64; + in_seg.ip.proto = WI_IPPROTO_TCP; + in_seg.ip.len = ee16(IP_HEADER_LEN + TCP_HEADER_LEN); + in_seg.ip.src = ee32(0x0A000002U); + in_seg.ip.dst = ee32(0x0A000001U); + in_seg.ip.csum = 0; + iphdr_set_checksum(&in_seg.ip); + in_seg.src_port = ee16(40000); + in_seg.dst_port = ee16(9999); + in_seg.seq = ee32(1); + in_seg.hlen = TCP_HEADER_LEN << 2; + in_seg.flags = TCP_FLAG_SYN; + in_seg.win = ee16(65535); + memset(&ph, 0, sizeof(ph)); + ph.ph.src = in_seg.ip.src; + ph.ph.dst = in_seg.ip.dst; + ph.ph.proto = WI_IPPROTO_TCP; + ph.ph.len = ee16(TCP_HEADER_LEN); + in_seg.csum = ee16(transport_checksum(&ph, &in_seg.src_port)); + + tcp_send_reset_reply(&s, TEST_PRIMARY_IF, &in_seg); + + ck_assert_uint_gt(last_frame_sent_size, 0); + sent_ip = (struct wolfIP_ip_packet *)last_frame_sent; + ck_assert_uint_eq(sent_ip->flags_fo, ee16(0x4000U)); +} +END_TEST + +/* Regression: ipcounter must be seeded with a random value at init so that + * IP identification fields on non-DF packets (UDP, ICMP) are not trivially + * predictable starting from zero. */ +START_TEST(test_ipcounter_seeded_at_init) +{ + struct wolfIP s; + + wolfIP_init(&s); + ck_assert_uint_ne(s.ipcounter, 0); +} +END_TEST /* Arp suite */ START_TEST(test_arp_request_basic) @@ -2906,8 +3055,13 @@ END_TEST START_TEST(test_wolfip_ip_is_multicast_variants) { - ck_assert_int_eq(wolfIP_ip_is_multicast(0xE0000001U), 1); - ck_assert_int_eq(wolfIP_ip_is_multicast(0x0A000001U), 0); + ck_assert_int_eq(wolfIP_ip_is_multicast(0xE0000001U), 1); /* 224.0.0.1 */ + ck_assert_int_eq(wolfIP_ip_is_multicast(0xE1000001U), 1); /* 225.0.0.1 */ + ck_assert_int_eq(wolfIP_ip_is_multicast(0xEF000001U), 1); /* 239.0.0.1 */ + ck_assert_int_eq(wolfIP_ip_is_multicast(0xEFFFFFFFU), 1); /* 239.255.255.255 */ + ck_assert_int_eq(wolfIP_ip_is_multicast(0xDFFFFFFFU), 0); /* 223.255.255.255 */ + ck_assert_int_eq(wolfIP_ip_is_multicast(0xF0000000U), 0); /* 240.0.0.0 */ + ck_assert_int_eq(wolfIP_ip_is_multicast(0x0A000001U), 0); /* 10.0.0.1 */ } END_TEST @@ -3149,7 +3303,7 @@ END_TEST START_TEST(test_wolfip_send_port_unreachable_non_ethernet_skips_eth_filter) { struct wolfIP s; - uint8_t orig_buf[ETH_HEADER_LEN + TTL_EXCEEDED_ORIG_PACKET_SIZE]; + uint8_t orig_buf[ETH_HEADER_LEN + TTL_EXCEEDED_ORIG_PACKET_SIZE_DEFAULT]; struct wolfIP_ip_packet *orig = (struct wolfIP_ip_packet *)orig_buf; wolfIP_init(&s); @@ -3168,7 +3322,7 @@ START_TEST(test_wolfip_send_port_unreachable_non_ethernet_skips_eth_filter) wolfIP_send_port_unreachable(&s, TEST_PRIMARY_IF, orig); ck_assert_uint_eq(last_frame_sent_size, - sizeof(struct wolfIP_icmp_dest_unreachable_packet) - ETH_HEADER_LEN); + (uint32_t)(IP_HEADER_LEN + ICMP_DEST_UNREACH_SIZE)); wolfIP_filter_set_callback(NULL, NULL); wolfIP_filter_set_eth_mask(0); @@ -3416,7 +3570,7 @@ START_TEST(test_wolfip_forwarding_ttl_expired) wolfIP_recv_ex(&s, TEST_PRIMARY_IF, frame, ETH_HEADER_LEN + IP_HEADER_LEN + 8); ck_assert_uint_eq(last_frame_sent_size, - sizeof(struct wolfIP_icmp_ttl_exceeded_packet)); + (uint32_t)(ETH_HEADER_LEN + IP_HEADER_LEN + ICMP_TTL_EXCEEDED_SIZE)); icmp = (struct wolfIP_icmp_ttl_exceeded_packet *)last_frame_sent; ck_assert_uint_eq(icmp->type, ICMP_TTL_EXCEEDED); ck_assert_uint_eq(icmp->code, 0); @@ -3430,8 +3584,8 @@ START_TEST(test_wolfip_forwarding_ttl_expired) ck_assert_uint_eq(ee32(icmp->ip.dst), ee32(frame->src)); ck_assert_mem_eq(icmp->orig_packet, ((uint8_t *)frame) + ETH_HEADER_LEN, - ee16(frame->len) < TTL_EXCEEDED_ORIG_PACKET_SIZE ? - ee16(frame->len) : TTL_EXCEEDED_ORIG_PACKET_SIZE); + ee16(frame->len) < TTL_EXCEEDED_ORIG_PACKET_SIZE_DEFAULT ? + ee16(frame->len) : TTL_EXCEEDED_ORIG_PACKET_SIZE_DEFAULT); ck_assert_uint_eq(frame->ttl, 1); /* original packet should remain unchanged */ } END_TEST diff --git a/src/test/unit/unit_tests_tcp_ack.c b/src/test/unit/unit_tests_tcp_ack.c index 9eb6dde..0fbb60e 100644 --- a/src/test/unit/unit_tests_tcp_ack.c +++ b/src/test/unit/unit_tests_tcp_ack.c @@ -1372,7 +1372,7 @@ END_TEST START_TEST(test_ip_recv_forward_ttl_exceeded) { struct wolfIP s; - uint8_t ip_buf[ETH_HEADER_LEN + TTL_EXCEEDED_ORIG_PACKET_SIZE]; + uint8_t ip_buf[ETH_HEADER_LEN + TTL_EXCEEDED_ORIG_PACKET_SIZE_DEFAULT]; struct wolfIP_ip_packet *ip = (struct wolfIP_ip_packet *)ip_buf; ip4 primary_ip = 0x0A000001U; ip4 secondary_ip = 0xC0A80101U; @@ -2267,7 +2267,7 @@ END_TEST START_TEST(test_send_ttl_exceeded_filter_drop) { struct wolfIP s; - uint8_t ip_buf[ETH_HEADER_LEN + TTL_EXCEEDED_ORIG_PACKET_SIZE]; + uint8_t ip_buf[ETH_HEADER_LEN + TTL_EXCEEDED_ORIG_PACKET_SIZE_DEFAULT]; struct wolfIP_ip_packet *ip = (struct wolfIP_ip_packet *)ip_buf; wolfIP_init(&s); @@ -2348,6 +2348,63 @@ START_TEST(test_send_ttl_exceeded_eth_filter_drop) } END_TEST +/* Regression: wolfIP_send_ttl_exceeded must include the full original IP + * header (including options) plus 8 bytes of transport data per RFC 792. + * With IHL=6 (24-byte header), 24+8=32 bytes must be copied, not 28. */ +START_TEST(test_send_ttl_exceeded_includes_full_ip_header_with_options) +{ + struct wolfIP s; + /* Original packet: IHL=6 (24-byte IP header) + 8 bytes of UDP ports */ + uint8_t orig_buf[ETH_HEADER_LEN + 24 + 8]; + struct wolfIP_ip_packet *orig = (struct wolfIP_ip_packet *)orig_buf; + struct wolfIP_icmp_ttl_exceeded_packet *icmp_out; + + 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(orig_buf, 0, sizeof(orig_buf)); + memcpy(orig->eth.src, "\x01\x02\x03\x04\x05\x06", 6); + orig->ver_ihl = 0x46; /* IHL=6, 24-byte header */ + orig->len = ee16(24 + 8); + orig->ttl = 1; + orig->proto = WI_IPPROTO_UDP; + orig->src = ee32(0x0A000002U); + orig->dst = ee32(0x0A000003U); + /* Fill IP option area with a recognizable pattern */ + ((uint8_t *)orig)[ETH_HEADER_LEN + IP_HEADER_LEN] = 0x01; /* NOP */ + ((uint8_t *)orig)[ETH_HEADER_LEN + IP_HEADER_LEN + 1] = 0x01; + ((uint8_t *)orig)[ETH_HEADER_LEN + IP_HEADER_LEN + 2] = 0x01; + ((uint8_t *)orig)[ETH_HEADER_LEN + IP_HEADER_LEN + 3] = 0x00; /* EOO */ + /* Fill transport data (UDP src/dst ports) with recognizable bytes */ + ((uint8_t *)orig)[ETH_HEADER_LEN + 24 + 0] = 0xAA; + ((uint8_t *)orig)[ETH_HEADER_LEN + 24 + 1] = 0xBB; + ((uint8_t *)orig)[ETH_HEADER_LEN + 24 + 2] = 0xCC; + ((uint8_t *)orig)[ETH_HEADER_LEN + 24 + 3] = 0xDD; + ((uint8_t *)orig)[ETH_HEADER_LEN + 24 + 4] = 0x11; + ((uint8_t *)orig)[ETH_HEADER_LEN + 24 + 5] = 0x22; + ((uint8_t *)orig)[ETH_HEADER_LEN + 24 + 6] = 0x33; + ((uint8_t *)orig)[ETH_HEADER_LEN + 24 + 7] = 0x44; + + wolfIP_send_ttl_exceeded(&s, TEST_PRIMARY_IF, orig); + + ck_assert_uint_gt(last_frame_sent_size, 0); + icmp_out = (struct wolfIP_icmp_ttl_exceeded_packet *)last_frame_sent; + + /* Verify all 8 bytes of transport data are present (bytes 24-31) */ + ck_assert_uint_eq(icmp_out->orig_packet[24], 0xAA); + ck_assert_uint_eq(icmp_out->orig_packet[25], 0xBB); + ck_assert_uint_eq(icmp_out->orig_packet[26], 0xCC); + ck_assert_uint_eq(icmp_out->orig_packet[27], 0xDD); + ck_assert_uint_eq(icmp_out->orig_packet[28], 0x11); + ck_assert_uint_eq(icmp_out->orig_packet[29], 0x22); + ck_assert_uint_eq(icmp_out->orig_packet[30], 0x33); + ck_assert_uint_eq(icmp_out->orig_packet[31], 0x44); +} +END_TEST + START_TEST(test_send_ttl_exceeded_no_send) { struct wolfIP s; @@ -2372,7 +2429,7 @@ END_TEST START_TEST(test_send_ttl_exceeded_non_ethernet_skips_eth_filter) { struct wolfIP s; - uint8_t ip_buf[ETH_HEADER_LEN + TTL_EXCEEDED_ORIG_PACKET_SIZE]; + uint8_t ip_buf[ETH_HEADER_LEN + TTL_EXCEEDED_ORIG_PACKET_SIZE_DEFAULT]; struct wolfIP_ip_packet *ip = (struct wolfIP_ip_packet *)ip_buf; wolfIP_init(&s); @@ -2393,7 +2450,7 @@ START_TEST(test_send_ttl_exceeded_non_ethernet_skips_eth_filter) wolfIP_send_ttl_exceeded(&s, TEST_PRIMARY_IF, ip); ck_assert_uint_eq(last_frame_sent_size, - sizeof(struct wolfIP_icmp_ttl_exceeded_packet) - ETH_HEADER_LEN); + (uint32_t)(IP_HEADER_LEN + ICMP_TTL_EXCEEDED_SIZE)); wolfIP_filter_set_callback(NULL, NULL); wolfIP_filter_set_eth_mask(0); @@ -2465,6 +2522,53 @@ START_TEST(test_arp_request_invalid_interface) } END_TEST +/* Regression: ARP reply handler must reject sender IPs that are broadcast, + * multicast, zero, or the device's own address -- same validation the request + * handler already applies. Without the check, an attacker can poison the + * cache by sending a reply with sip set to the victim's own IP. */ +START_TEST(test_arp_reply_rejects_invalid_sender_ip) +{ + struct wolfIP s; + struct arp_packet arp_rep; + struct wolfIP_ll_dev *ll; + const ip4 local_ip = 0x0A000001U; + static const uint8_t attacker_mac[6] = {0xDE, 0xAD, 0xBE, 0xEF, 0x00, 0x01}; + /* IPs that must never be cached */ + const ip4 bad_ips[] = { + IPADDR_ANY, /* 0.0.0.0 */ + local_ip, /* own IP */ + 0xFFFFFFFFU, /* broadcast */ + 0xE0000001U, /* multicast 224.0.0.1 */ + }; + int k; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, local_ip, 0xFFFFFF00U, 0); + ll = wolfIP_getdev_ex(&s, TEST_PRIMARY_IF); + + for (k = 0; k < (int)(sizeof(bad_ips)/sizeof(bad_ips[0])); k++) { + memset(&arp_rep, 0, sizeof(arp_rep)); + memcpy(arp_rep.eth.dst, ll->mac, 6); + memcpy(arp_rep.eth.src, attacker_mac, 6); + arp_rep.eth.type = ee16(ETH_TYPE_ARP); + arp_rep.htype = ee16(1); + arp_rep.ptype = ee16(0x0800); + arp_rep.hlen = 6; + arp_rep.plen = 4; + arp_rep.opcode = ee16(ARP_REPLY); + memcpy(arp_rep.sma, attacker_mac, 6); + arp_rep.sip = ee32(bad_ips[k]); + memcpy(arp_rep.tma, ll->mac, 6); + arp_rep.tip = ee32(local_ip); + + arp_recv(&s, TEST_PRIMARY_IF, &arp_rep, sizeof(arp_rep)); + + ck_assert_int_lt(arp_neighbor_index(&s, TEST_PRIMARY_IF, bad_ips[k]), 0); + } +} +END_TEST + START_TEST(test_arp_reply_filter_drop) { struct wolfIP s; @@ -4206,6 +4310,48 @@ START_TEST(test_tcp_input_listen_syn_arms_control_rto) } END_TEST +/* Regression: when a SYN arrives at a LISTEN socket, the SYN-ACK must be + * sent immediately as part of the LISTEN->SYN_RCVD transition, not deferred + * until accept() or the ctrl_rto timer fires (up to 1 second later). */ +START_TEST(test_tcp_input_listen_syn_sends_synack_immediately) +{ + struct wolfIP s; + int listen_sd; + struct tsocket *ts; + 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 = 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)]; + + inject_tcp_segment(&s, TEST_PRIMARY_IF, 0x0A0000A1U, 0x0A000001U, + 40000, 1234, 1, 0, TCP_FLAG_SYN); + + ck_assert_int_eq(ts->sock.tcp.state, TCP_SYN_RCVD); + + /* A SYN-ACK must have been queued in the TX FIFO immediately */ + ck_assert(!fifo_is_empty(&ts->sock.tcp.txbuf)); + { + struct pkt_desc *desc = fifo_peek(&ts->sock.tcp.txbuf); + struct wolfIP_tcp_seg *seg; + ck_assert_ptr_nonnull(desc); + seg = (struct wolfIP_tcp_seg *)(ts->txmem + desc->pos + sizeof(*desc)); + ck_assert_uint_eq(seg->flags, (TCP_FLAG_SYN | TCP_FLAG_ACK)); + } +} +END_TEST + START_TEST(test_tcp_input_syn_sent_synack_without_sack_disables_sack) { struct wolfIP s; diff --git a/src/test/unit/unit_tests_tcp_flow.c b/src/test/unit/unit_tests_tcp_flow.c index 0147a5a..596a148 100644 --- a/src/test/unit/unit_tests_tcp_flow.c +++ b/src/test/unit/unit_tests_tcp_flow.c @@ -2889,6 +2889,59 @@ START_TEST(test_tcp_input_syn_rcvd_ack_invalid_seq_rejected) } END_TEST +/* Regression: an ACK+FIN segment in SYN_RCVD must not be silently discarded. + * The ACK should complete the handshake (to ESTABLISHED) and the FIN should + * be processed in the same pass (to CLOSE_WAIT). Per RFC 9293 section 3.10.7.4 + * the ACK field must be processed regardless of other control flags. */ +START_TEST(test_tcp_input_syn_rcvd_ack_fin_transitions_to_close_wait) +{ + struct wolfIP s; + int listen_sd; + struct tsocket *ts; + struct wolfIP_sockaddr_in sin; + struct wolfIP_tcp_seg seg; + + 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); + + inject_tcp_syn(&s, TEST_PRIMARY_IF, 0x0A000001U, 1234); + ts = &s.tcpsockets[SOCKET_UNMARK(listen_sd)]; + ck_assert_int_eq(ts->sock.tcp.state, TCP_SYN_RCVD); + + /* Send ACK+FIN with valid seq/ack */ + memset(&seg, 0, sizeof(seg)); + 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(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(ts->sock.tcp.ack); + seg.ack = ee32(ts->sock.tcp.seq + 1); + seg.hlen = TCP_HEADER_LEN << 2; + seg.flags = TCP_FLAG_ACK | TCP_FLAG_FIN; + fix_tcp_checksums(&seg); + tcp_input(&s, TEST_PRIMARY_IF, &seg, + (uint32_t)(ETH_HEADER_LEN + IP_HEADER_LEN + TCP_HEADER_LEN)); + + /* The ACK must have completed the handshake and the FIN must have + * been processed, landing in CLOSE_WAIT. */ + ck_assert_int_eq(ts->sock.tcp.state, TCP_CLOSE_WAIT); +} +END_TEST + START_TEST(test_tcp_recv_queues_payload_and_advances_ack) { struct wolfIP s; diff --git a/src/wolfesp.c b/src/wolfesp.c index 56b1251..eb31076 100644 --- a/src/wolfesp.c +++ b/src/wolfesp.c @@ -941,6 +941,7 @@ esp_aes_rfc4106_dec(const wolfIP_esp_sa * esp_sa, uint8_t * esp_data, } rfc4106_dec_out: + wc_ForceZero(nonce, salt_len); if (inited) { wc_AesFree(&gcm_dec); inited = 0; @@ -1013,6 +1014,7 @@ esp_aes_rfc4106_enc(const wolfIP_esp_sa * esp_sa, uint8_t * esp_data, } rfc4106_enc_out: + wc_ForceZero(nonce, salt_len); if (inited) { wc_AesFree(&gcm_enc); inited = 0; @@ -1066,6 +1068,7 @@ esp_aes_rfc4543_dec(const wolfIP_esp_sa * esp_sa, uint8_t * esp_data, } rfc4543_dec_out: + wc_ForceZero(nonce, salt_len); return err; } @@ -1121,6 +1124,7 @@ esp_aes_rfc4543_enc(const wolfIP_esp_sa * esp_sa, uint8_t * esp_data, } rfc4543_enc_out: + wc_ForceZero(nonce, salt_len); if (inited) { wc_AesFree(&gmac_enc.aes); inited = 0; @@ -1660,6 +1664,7 @@ esp_transport_wrap(struct wolfIP_ip_packet *ip, uint16_t * ip_len) if (err == 0) { memcpy(icv, hash, esp_sa->icv_len); } + } break; #if defined(WOLFSSL_AESGCM_STREAM) diff --git a/src/wolfip.c b/src/wolfip.c index 920cd91..4df56cf 100644 --- a/src/wolfip.c +++ b/src/wolfip.c @@ -729,9 +729,10 @@ union transport_pseudo_header { /* ICMP */ -#define TTL_EXCEEDED_ORIG_PACKET_SIZE (28) -#define ICMP_TTL_EXCEEDED_SIZE (36) -#define ICMP_DEST_UNREACH_SIZE (36) +#define TTL_EXCEEDED_ORIG_PACKET_SIZE_MAX (68) /* max IP header (60) + 8 bytes */ +#define TTL_EXCEEDED_ORIG_PACKET_SIZE_DEFAULT (28) /* IHL=5: 20 + 8 */ +#define ICMP_TTL_EXCEEDED_SIZE (8 + TTL_EXCEEDED_ORIG_PACKET_SIZE_DEFAULT) +#define ICMP_DEST_UNREACH_SIZE (8 + TTL_EXCEEDED_ORIG_PACKET_SIZE_DEFAULT) struct PACKED wolfIP_icmp_packet { struct wolfIP_ip_packet ip; @@ -745,7 +746,7 @@ struct PACKED wolfIP_icmp_ttl_exceeded_packet { uint8_t type, code; uint16_t csum; uint8_t unused[4]; - uint8_t orig_packet[TTL_EXCEEDED_ORIG_PACKET_SIZE]; + uint8_t orig_packet[TTL_EXCEEDED_ORIG_PACKET_SIZE_MAX]; }; struct PACKED wolfIP_icmp_dest_unreachable_packet { @@ -753,7 +754,7 @@ struct PACKED wolfIP_icmp_dest_unreachable_packet { uint8_t type, code; uint16_t csum; uint8_t unused[4]; - uint8_t orig_packet[TTL_EXCEEDED_ORIG_PACKET_SIZE]; + uint8_t orig_packet[TTL_EXCEEDED_ORIG_PACKET_SIZE_MAX]; }; static uint16_t icmp_echo_id(const struct wolfIP_icmp_packet *icmp) @@ -1727,49 +1728,58 @@ static void wolfIP_send_ttl_exceeded(struct wolfIP *s, unsigned int if_idx, struct wolfIP_ll_dev *ll = wolfIP_ll_at(s, if_idx); struct wolfIP_icmp_ttl_exceeded_packet icmp = {0}; struct wolfIP_icmp_packet *icmp_pkt = (struct wolfIP_icmp_packet *)&icmp; + uint32_t orig_ihl = (orig->ver_ihl & 0x0F) * 4; + uint32_t orig_copy; + uint32_t icmp_data_len; #if !CONFIG_IPFILTER (void)icmp_pkt; #endif if (!ll || !ll->send) return; + if (orig_ihl < IP_HEADER_LEN) + orig_ihl = IP_HEADER_LEN; + orig_copy = orig_ihl + 8; + if (orig_copy > TTL_EXCEEDED_ORIG_PACKET_SIZE_MAX) + orig_copy = TTL_EXCEEDED_ORIG_PACKET_SIZE_MAX; + icmp_data_len = 8 + orig_copy; /* ICMP header (type+code+csum+unused) + quoted packet */ icmp.type = ICMP_TTL_EXCEEDED; - memcpy(icmp.orig_packet, ((uint8_t *)orig) + ETH_HEADER_LEN, - TTL_EXCEEDED_ORIG_PACKET_SIZE); + memcpy(icmp.orig_packet, ((uint8_t *)orig) + ETH_HEADER_LEN, orig_copy); icmp.csum = ee16(icmp_checksum((struct wolfIP_icmp_packet *)&icmp, - ICMP_DEST_UNREACH_SIZE)); + icmp_data_len)); icmp.ip.ver_ihl = 0x45; icmp.ip.ttl = 64; icmp.ip.proto = WI_IPPROTO_ICMP; icmp.ip.id = ipcounter_next(s); - icmp.ip.len = ee16(IP_HEADER_LEN + ICMP_DEST_UNREACH_SIZE); + icmp.ip.len = ee16((uint16_t)(IP_HEADER_LEN + icmp_data_len)); icmp.ip.src = ee32(wolfIP_ipconf_at(s, if_idx)->ip); icmp.ip.dst = orig->src; icmp.ip.csum = 0; iphdr_set_checksum(&icmp.ip); - if (!wolfIP_ll_is_non_ethernet(s, if_idx)) { - eth_output_add_header(s, if_idx, orig->eth.src, &icmp.ip.eth, ETH_TYPE_IP); - } - if (wolfIP_filter_notify_icmp(WOLFIP_FILT_SENDING, s, if_idx, icmp_pkt, sizeof(icmp)) != 0) - return; - if (wolfIP_filter_notify_ip(WOLFIP_FILT_SENDING, s, if_idx, &icmp.ip, sizeof(icmp)) != 0) - return; - if (!wolfIP_ll_is_non_ethernet(s, if_idx)) { - if (wolfIP_filter_notify_eth(WOLFIP_FILT_SENDING, s, if_idx, &icmp.ip.eth, sizeof(icmp)) != 0) + { + uint32_t frame_len = ETH_HEADER_LEN + IP_HEADER_LEN + icmp_data_len; + if (!wolfIP_ll_is_non_ethernet(s, if_idx)) { + eth_output_add_header(s, if_idx, orig->eth.src, &icmp.ip.eth, ETH_TYPE_IP); + } + if (wolfIP_filter_notify_icmp(WOLFIP_FILT_SENDING, s, if_idx, icmp_pkt, frame_len) != 0) return; - } + if (wolfIP_filter_notify_ip(WOLFIP_FILT_SENDING, s, if_idx, &icmp.ip, frame_len) != 0) + return; + if (!wolfIP_ll_is_non_ethernet(s, if_idx)) { + if (wolfIP_filter_notify_eth(WOLFIP_FILT_SENDING, s, if_idx, &icmp.ip.eth, frame_len) != 0) + return; + } #ifdef WOLFIP_ESP - if (!wolfIP_ll_is_non_ethernet(s, if_idx)) { - if (esp_send(ll, &icmp.ip, sizeof(icmp) - ETH_HEADER_LEN) == 1) { - /* ipsec not configured on this interface. - * send plaintext. */ - wolfIP_ll_send_frame(s, if_idx, &icmp, sizeof(icmp)); + if (!wolfIP_ll_is_non_ethernet(s, if_idx)) { + if (esp_send(ll, &icmp.ip, (uint16_t)(frame_len - ETH_HEADER_LEN)) == 1) { + wolfIP_ll_send_frame(s, if_idx, &icmp, frame_len); + } + } else { + wolfIP_ll_send_frame(s, if_idx, &icmp, frame_len); } - } else { - wolfIP_ll_send_frame(s, if_idx, &icmp, sizeof(icmp)); - } #else - wolfIP_ll_send_frame(s, if_idx, &icmp, sizeof(icmp)); + wolfIP_ll_send_frame(s, if_idx, &icmp, frame_len); #endif + } } #elif WOLFIP_ENABLE_FORWARDING static void wolfIP_send_ttl_exceeded(struct wolfIP *s, unsigned int if_idx, @@ -1788,50 +1798,59 @@ static void wolfIP_send_port_unreachable(struct wolfIP *s, unsigned int if_idx, struct wolfIP_ll_dev *ll = wolfIP_ll_at(s, if_idx); struct wolfIP_icmp_dest_unreachable_packet icmp = {0}; struct wolfIP_icmp_packet *icmp_pkt = (struct wolfIP_icmp_packet *)&icmp; + uint32_t orig_ihl = (orig->ver_ihl & 0x0F) * 4; + uint32_t orig_copy; + uint32_t icmp_data_len; #if !CONFIG_IPFILTER (void)icmp_pkt; #endif if (!ll || !ll->send) return; + if (orig_ihl < IP_HEADER_LEN) + orig_ihl = IP_HEADER_LEN; + orig_copy = orig_ihl + 8; + if (orig_copy > TTL_EXCEEDED_ORIG_PACKET_SIZE_MAX) + orig_copy = TTL_EXCEEDED_ORIG_PACKET_SIZE_MAX; + icmp_data_len = 8 + orig_copy; icmp.type = ICMP_DEST_UNREACH; icmp.code = ICMP_PORT_UNREACH; - memcpy(icmp.orig_packet, ((uint8_t *)orig) + ETH_HEADER_LEN, - TTL_EXCEEDED_ORIG_PACKET_SIZE); + memcpy(icmp.orig_packet, ((uint8_t *)orig) + ETH_HEADER_LEN, orig_copy); icmp.csum = ee16(icmp_checksum((struct wolfIP_icmp_packet *)&icmp, - ICMP_TTL_EXCEEDED_SIZE)); + icmp_data_len)); icmp.ip.ver_ihl = 0x45; icmp.ip.ttl = 64; icmp.ip.proto = WI_IPPROTO_ICMP; icmp.ip.id = ipcounter_next(s); - icmp.ip.len = ee16(IP_HEADER_LEN + ICMP_TTL_EXCEEDED_SIZE); + icmp.ip.len = ee16((uint16_t)(IP_HEADER_LEN + icmp_data_len)); icmp.ip.src = ee32(wolfIP_ipconf_at(s, if_idx)->ip); icmp.ip.dst = orig->src; icmp.ip.csum = 0; iphdr_set_checksum(&icmp.ip); - if (!wolfIP_ll_is_non_ethernet(s, if_idx)) { - eth_output_add_header(s, if_idx, orig->eth.src, &icmp.ip.eth, ETH_TYPE_IP); - } - if (wolfIP_filter_notify_icmp(WOLFIP_FILT_SENDING, s, if_idx, icmp_pkt, sizeof(icmp)) != 0) - return; - if (wolfIP_filter_notify_ip(WOLFIP_FILT_SENDING, s, if_idx, &icmp.ip, sizeof(icmp)) != 0) - return; - if (!wolfIP_ll_is_non_ethernet(s, if_idx)) { - if (wolfIP_filter_notify_eth(WOLFIP_FILT_SENDING, s, if_idx, &icmp.ip.eth, sizeof(icmp)) != 0) + { + uint32_t frame_len = ETH_HEADER_LEN + IP_HEADER_LEN + icmp_data_len; + if (!wolfIP_ll_is_non_ethernet(s, if_idx)) { + eth_output_add_header(s, if_idx, orig->eth.src, &icmp.ip.eth, ETH_TYPE_IP); + } + if (wolfIP_filter_notify_icmp(WOLFIP_FILT_SENDING, s, if_idx, icmp_pkt, frame_len) != 0) return; - } + if (wolfIP_filter_notify_ip(WOLFIP_FILT_SENDING, s, if_idx, &icmp.ip, frame_len) != 0) + return; + if (!wolfIP_ll_is_non_ethernet(s, if_idx)) { + if (wolfIP_filter_notify_eth(WOLFIP_FILT_SENDING, s, if_idx, &icmp.ip.eth, frame_len) != 0) + return; + } #ifdef WOLFIP_ESP - if (!wolfIP_ll_is_non_ethernet(s, if_idx)) { - if (esp_send(ll, &icmp.ip, sizeof(icmp) - ETH_HEADER_LEN) == 1) { - /* ipsec not configured on this interface. - * send plaintext. */ - wolfIP_ll_send_frame(s, if_idx, &icmp, sizeof(icmp)); + if (!wolfIP_ll_is_non_ethernet(s, if_idx)) { + if (esp_send(ll, &icmp.ip, (uint16_t)(frame_len - ETH_HEADER_LEN)) == 1) { + wolfIP_ll_send_frame(s, if_idx, &icmp, frame_len); + } + } else { + wolfIP_ll_send_frame(s, if_idx, &icmp, frame_len); } - } else { - wolfIP_ll_send_frame(s, if_idx, &icmp, sizeof(icmp)); - } #else - wolfIP_ll_send_frame(s, if_idx, &icmp, sizeof(icmp)); + wolfIP_ll_send_frame(s, if_idx, &icmp, frame_len); #endif + } } #else static void wolfIP_send_port_unreachable(struct wolfIP *s, unsigned int if_idx, @@ -1877,6 +1896,7 @@ static struct wolfIP_timer timers_binheap_pop(struct timers_binheap *heap) uint32_t i = 0; struct wolfIP_timer tmr = {0}; do { + i = 0; tmr = heap->timers[0]; heap->size--; heap->timers[0] = heap->timers[heap->size]; @@ -2649,7 +2669,21 @@ static int tcp_send_empty_immediate(struct tsocket *t, struct wolfIP_tcp_seg *tc #endif { - int send_ret = wolfIP_ll_send_frame(t->S, tx_if, tcp, frame_len); + int send_ret = 0; +#ifdef WOLFIP_ESP + if (!wolfIP_ll_is_non_ethernet(t->S, tx_if)) { + struct wolfIP_ll_dev *ll_esp = wolfIP_ll_at(t->S, tx_if); + int esp_err = esp_send(ll_esp, (struct wolfIP_ip_packet *)tcp, + (uint16_t)(frame_len - ETH_HEADER_LEN)); + if (esp_err == 1) { + send_ret = wolfIP_ll_send_frame(t->S, tx_if, tcp, frame_len); + } + } else { + send_ret = wolfIP_ll_send_frame(t->S, tx_if, tcp, frame_len); + } +#else + send_ret = wolfIP_ll_send_frame(t->S, tx_if, tcp, frame_len); +#endif return (send_ret < 0) ? send_ret : 0; } } @@ -2741,10 +2775,10 @@ static void tcp_send_reset_reply(struct wolfIP *s, unsigned int if_idx, out.ip.dst = in->ip.src; out.ip.ver_ihl = 0x45; out.ip.len = ee16(IP_HEADER_LEN + TCP_HEADER_LEN); + out.ip.flags_fo = ee16(0x4000U); out.ip.ttl = 64; out.ip.proto = WI_IPPROTO_TCP; - out.ip.id = ee16(s->ipcounter); - s->ipcounter = (uint16_t)(s->ipcounter + 1); + out.ip.id = ipcounter_next(s); iphdr_set_checksum(&out.ip); memset(&ph, 0, sizeof(ph)); @@ -2770,7 +2804,22 @@ static void tcp_send_reset_reply(struct wolfIP *s, unsigned int if_idx, return; } #endif - wolfIP_ll_send_frame(s, if_idx, &out.ip, sizeof(out)); + { +#ifdef WOLFIP_ESP + if (!wolfIP_ll_is_non_ethernet(s, if_idx)) { + struct wolfIP_ll_dev *ll_esp = wolfIP_ll_at(s, if_idx); + int esp_err = esp_send(ll_esp, &out.ip, + (uint16_t)(sizeof(out) - ETH_HEADER_LEN)); + if (esp_err == 1) { + wolfIP_ll_send_frame(s, if_idx, &out.ip, sizeof(out)); + } + } else { + wolfIP_ll_send_frame(s, if_idx, &out.ip, sizeof(out)); + } +#else + wolfIP_ll_send_frame(s, if_idx, &out.ip, sizeof(out)); +#endif + } } static int tcp_send_finack(struct tsocket *t) @@ -3054,11 +3103,14 @@ static int tcp_send_zero_wnd_probe(struct tsocket *t) struct pkt_desc *desc; uint32_t guard = 0; uint32_t budget; - uint8_t probe_frame[ETH_HEADER_LEN + IP_HEADER_LEN + TCP_HEADER_LEN + 1]; + uint8_t probe_frame[ETH_HEADER_LEN + IP_HEADER_LEN + TCP_HEADER_LEN + + TCP_MAX_OPTIONS_LEN + 1]; struct wolfIP_tcp_seg *probe = (struct wolfIP_tcp_seg *)probe_frame; uint8_t probe_byte = 0; uint32_t probe_seq; uint32_t probe_off = 0; + uint8_t opt_len; + uint32_t frame_len; unsigned int tx_if; #ifdef ETHERNET struct ipconf *conf; @@ -3097,14 +3149,16 @@ static int tcp_send_zero_wnd_probe(struct tsocket *t) return -1; memset(probe, 0, sizeof(probe_frame)); + opt_len = tcp_build_ack_options(t, probe->data, TCP_MAX_OPTIONS_LEN); probe->src_port = ee16(t->src_port); probe->dst_port = ee16(t->dst_port); probe->seq = ee32(probe_seq); probe->ack = ee32(t->sock.tcp.ack); - probe->hlen = TCP_HEADER_LEN << 2; + probe->hlen = ((20 + opt_len) << 2) & 0xF0; probe->flags = TCP_FLAG_ACK; probe->win = ee16(tcp_adv_win(t, 1)); - probe->data[0] = probe_byte; + probe->data[opt_len] = probe_byte; + frame_len = ETH_HEADER_LEN + IP_HEADER_LEN + TCP_HEADER_LEN + opt_len + 1; tx_if = wolfIP_socket_if_idx(t); #ifdef ETHERNET @@ -3122,19 +3176,34 @@ static int tcp_send_zero_wnd_probe(struct tsocket *t) } #endif ip_output_add_header(t, (struct wolfIP_ip_packet *)probe, WI_IPPROTO_TCP, - (uint16_t)(IP_HEADER_LEN + TCP_HEADER_LEN + 1)); + (uint16_t)(IP_HEADER_LEN + TCP_HEADER_LEN + opt_len + 1)); - if (wolfIP_filter_notify_tcp(WOLFIP_FILT_SENDING, t->S, tx_if, probe, sizeof(probe_frame)) != 0) + if (wolfIP_filter_notify_tcp(WOLFIP_FILT_SENDING, t->S, tx_if, probe, frame_len) != 0) return -1; - if (wolfIP_filter_notify_ip(WOLFIP_FILT_SENDING, t->S, tx_if, &probe->ip, sizeof(probe_frame)) != 0) + if (wolfIP_filter_notify_ip(WOLFIP_FILT_SENDING, t->S, tx_if, &probe->ip, frame_len) != 0) return -1; #ifdef ETHERNET if (!wolfIP_ll_is_non_ethernet(t->S, tx_if)) { - if (wolfIP_filter_notify_eth(WOLFIP_FILT_SENDING, t->S, tx_if, &probe->ip.eth, sizeof(probe_frame)) != 0) + if (wolfIP_filter_notify_eth(WOLFIP_FILT_SENDING, t->S, tx_if, &probe->ip.eth, frame_len) != 0) return -1; } #endif - wolfIP_ll_send_frame(t->S, tx_if, probe, sizeof(probe_frame)); + { +#ifdef WOLFIP_ESP + if (!wolfIP_ll_is_non_ethernet(t->S, tx_if)) { + struct wolfIP_ll_dev *ll_esp = wolfIP_ll_at(t->S, tx_if); + int esp_err = esp_send(ll_esp, (struct wolfIP_ip_packet *)probe, + (uint16_t)(frame_len - ETH_HEADER_LEN)); + if (esp_err == 1) { + wolfIP_ll_send_frame(t->S, tx_if, probe, frame_len); + } + } else { + wolfIP_ll_send_frame(t->S, tx_if, probe, frame_len); + } +#else + wolfIP_ll_send_frame(t->S, tx_if, probe, frame_len); +#endif + } return 0; } @@ -3398,7 +3467,21 @@ static void wolfIP_forward_packet(struct wolfIP *s, unsigned int out_if, if (wolfIP_filter_notify_eth(WOLFIP_FILT_SENDING, s, out_if, &ip->eth, len) != 0) return; } - wolfIP_ll_send_frame(s, out_if, ip, len); + { +#ifdef WOLFIP_ESP + if (!wolfIP_ll_is_non_ethernet(s, out_if)) { + struct wolfIP_ll_dev *ll_esp = wolfIP_ll_at(s, out_if); + int esp_err = esp_send(ll_esp, ip, (uint16_t)(len - ETH_HEADER_LEN)); + if (esp_err == 1) { + wolfIP_ll_send_frame(s, out_if, ip, len); + } + } else { + wolfIP_ll_send_frame(s, out_if, ip, len); + } +#else + wolfIP_ll_send_frame(s, out_if, ip, len); +#endif + } #else (void)s; (void)out_if; @@ -4159,9 +4242,10 @@ static void tcp_input(struct wolfIP *S, unsigned int if_idx, t->dst_port = ee16(tcp->src_port); t->remote_ip = ee32(tcp->ip.src); t->events |= CB_EVENT_READABLE; /* Keep flag until application calls accept */ + tcp_process_ts(t, tcp, frame_len); + tcp_send_syn(t, TCP_FLAG_SYN | TCP_FLAG_ACK); t->sock.tcp.ctrl_rto_retries = 0; tcp_ctrl_rto_start(t, S->last_tick); - tcp_process_ts(t, tcp, frame_len); break; } else if (t->sock.tcp.state == TCP_SYN_SENT) { if (tcp->flags == (TCP_FLAG_SYN | TCP_FLAG_ACK)) { @@ -4190,28 +4274,32 @@ static void tcp_input(struct wolfIP *S, unsigned int if_idx, /* Check if final ACK to SYN-ACK (may include payload) */ if (t->sock.tcp.state == TCP_SYN_RCVD) { if (tcp->flags & TCP_FLAG_ACK) { - if (tcplen == 0 && tcp->flags != TCP_FLAG_ACK) { - /* Ignore non-pure ACKs without payload in SYN_RCVD. */ - } else { - uint32_t expected_ack = tcp_seq_inc(t->sock.tcp.snd_una, 1); - uint32_t expected_seq = t->sock.tcp.ack; - if (ee32(tcp->ack) != expected_ack || ee32(tcp->seq) != expected_seq) { - /* RFC 9293 §3.10.7.4: unacceptable ACK in - * SYN_RCVD - send RST to peer. */ - tcp_send_reset_reply(S, if_idx, tcp); - continue; - } - t->sock.tcp.state = TCP_ESTABLISHED; - tcp_ctrl_rto_stop(t); - t->sock.tcp.ack = ee32(tcp->seq); - 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.ssthresh = tcp_initial_ssthresh(t->sock.tcp.peer_rwnd); - if (tx_has_writable_space(t)) - t->events |= CB_EVENT_WRITABLE; - if (tcplen > 0) - tcp_recv(t, tcp); + uint32_t expected_ack = tcp_seq_inc(t->sock.tcp.snd_una, 1); + uint32_t expected_seq = t->sock.tcp.ack; + if (ee32(tcp->ack) != expected_ack || ee32(tcp->seq) != expected_seq) { + /* RFC 9293 section 3.10.7.4: unacceptable ACK in + * SYN_RCVD - send RST to peer. */ + tcp_send_reset_reply(S, if_idx, tcp); + continue; + } + t->sock.tcp.state = TCP_ESTABLISHED; + tcp_ctrl_rto_stop(t); + t->sock.tcp.ack = ee32(tcp->seq); + 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.ssthresh = tcp_initial_ssthresh(t->sock.tcp.peer_rwnd); + if (tx_has_writable_space(t)) + t->events |= CB_EVENT_WRITABLE; + if (tcplen > 0) + tcp_recv(t, tcp); + /* RFC 9293 section 3.10.7.4: process FIN if present in the + * same segment that completed the handshake. */ + if (tcp->flags & TCP_FLAG_FIN) { + t->sock.tcp.ack = tcp_seq_inc(t->sock.tcp.ack, 1); + t->sock.tcp.state = TCP_CLOSE_WAIT; + t->events |= CB_EVENT_READABLE; + tcp_send_ack(t); } } } else if (t->sock.tcp.state == TCP_LAST_ACK) { @@ -5798,8 +5886,10 @@ static void dhcp_timer_cb(void *arg) ret = dhcp_send_request(s); if (ret >= 0) s->dhcp_timeout_count++; - } else + } else { + dhcp_deconfigure_lease(s); s->dhcp_state = DHCP_OFF; + } break; case DHCP_BOUND: if (s->dhcp_lease_expires != 0 && s->last_tick >= s->dhcp_lease_expires) { @@ -6685,8 +6775,15 @@ static void arp_recv(struct wolfIP *s, unsigned int if_idx, void *buf, int len) } else if (arp->opcode == ee16(ARP_REPLY)) { ip4 sip = ee32(arp->sip); - int idx = arp_neighbor_index(s, if_idx, sip); - int pending = arp_pending_match_and_clear(s, if_idx, sip); + int idx, pending; + /* Validate sender IP: reject broadcast, multicast, zero, and + * our own address -- same checks as the ARP request handler. */ + if (sip == IPADDR_ANY || sip == conf->ip || + wolfIP_ip_is_broadcast(s, sip) || + wolfIP_ip_is_multicast(sip)) + return; + idx = arp_neighbor_index(s, if_idx, sip); + pending = arp_pending_match_and_clear(s, if_idx, sip); /* Security trade-off: allow quick-path add, but block unsolicited overwrite. */ if (pending || idx < 0) { arp_store_neighbor(s, if_idx, sip, arp->sma); @@ -6716,6 +6813,7 @@ void wolfIP_init(struct wolfIP *s) if (!s) return; memset(s, 0, sizeof(struct wolfIP)); + s->ipcounter = (uint16_t)(wolfIP_getrandom() & 0xFFFF); s->if_count = WOLFIP_MAX_INTERFACES; for (i = 0; i < s->if_count; i++) { s->ll_dev[i].mtu = LINK_MTU; @@ -6876,7 +6974,7 @@ static inline void ip_recv(struct wolfIP *s, unsigned int if_idx, if (ip->ttl <= 1) { /* Need at least Ethernet header + 28 bytes of original packet. */ - if (len < (uint32_t)(ETH_HEADER_LEN + TTL_EXCEEDED_ORIG_PACKET_SIZE)) + if (len < (uint32_t)(ETH_HEADER_LEN + TTL_EXCEEDED_ORIG_PACKET_SIZE_DEFAULT)) return; wolfIP_send_ttl_exceeded(s, if_idx, ip); return;