diff --git a/.github/workflows/freebsd.yml b/.github/workflows/freebsd.yml index 5e0896b..b820187 100644 --- a/.github/workflows/freebsd.yml +++ b/.github/workflows/freebsd.yml @@ -21,9 +21,16 @@ jobs: prepare: | set -ex env IGNORE_OSVERSION=yes pkg update -f - env IGNORE_OSVERSION=yes pkg install -y gmake gcc wolfssl check vim + env IGNORE_OSVERSION=yes pkg install -y gmake gcc check vim autoconf automake libtool git kldload if_tap || true sysctl net.link.tap.up_on_open=1 || true + git clone --depth=1 https://github.com/wolfssl/wolfssl --branch nightly-snapshot /tmp/wolfssl + cd /tmp/wolfssl + ./autogen.sh + ./configure --enable-all --enable-md5 + gmake -j$(sysctl -n hw.ncpu) + gmake install + ldconfig run: | set -ex cd "${GITHUB_WORKSPACE:-/root/work/github/workspace}" diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 0ab8ddb..53664af 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -16,12 +16,22 @@ jobs: with: submodules: true - - name: Update repo + - name: Install dependencies run: | sudo apt-get update - sudo apt-get install -y libwolfssl-dev + sudo apt-get install -y build-essential autoconf automake libtool pkg-config sudo modprobe tun + - name: Clone and build wolfSSL from nightly-snapshot + run: | + git clone --depth=1 https://github.com/wolfssl/wolfssl --branch nightly-snapshot /tmp/wolfssl + cd /tmp/wolfssl + ./autogen.sh + ./configure --enable-all --enable-md5 + make -j$(nproc) + sudo make install + sudo ldconfig + - name: Build linux tests run: | mkdir -p build/port diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 8cc7b06..0456355 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -18,7 +18,16 @@ jobs: - name: Install dependencies run: | brew update - brew install make wolfssl check + brew install make check autoconf automake libtool + + - name: Clone and build wolfSSL from nightly-snapshot + run: | + git clone --depth=1 https://github.com/wolfssl/wolfssl --branch nightly-snapshot /tmp/wolfssl + cd /tmp/wolfssl + ./autogen.sh + ./configure --enable-all --enable-md5 + make -j$(sysctl -n hw.ncpu) + sudo make install - name: Build tests run: | diff --git a/.github/workflows/multi-compiler.yml b/.github/workflows/multi-compiler.yml index 555bcd0..8a459ee 100644 --- a/.github/workflows/multi-compiler.yml +++ b/.github/workflows/multi-compiler.yml @@ -39,9 +39,19 @@ jobs: - name: Install dependencies run: | - sudo apt-get install -y libwolfssl-dev check + sudo apt-get install -y autoconf automake libtool pkg-config check sudo modprobe tun + - name: Clone and build wolfSSL from nightly-snapshot + run: | + git clone --depth=1 https://github.com/wolfssl/wolfssl --branch nightly-snapshot /tmp/wolfssl + cd /tmp/wolfssl + ./autogen.sh + ./configure --enable-all --enable-md5 + make -j$(nproc) + sudo make install + sudo ldconfig + - name: Build wolfIP with ${{ matrix.cc }} run: | mkdir -p build/port diff --git a/.github/workflows/sanitizers.yml b/.github/workflows/sanitizers.yml index 2ffe029..2907345 100644 --- a/.github/workflows/sanitizers.yml +++ b/.github/workflows/sanitizers.yml @@ -44,9 +44,19 @@ jobs: - name: Install dependencies run: | sudo apt-get update - sudo apt-get install -y libwolfssl-dev check + sudo apt-get install -y build-essential autoconf automake libtool pkg-config check sudo modprobe tun + - name: Clone and build wolfSSL from nightly-snapshot + run: | + git clone --depth=1 https://github.com/wolfssl/wolfssl --branch nightly-snapshot /tmp/wolfssl + cd /tmp/wolfssl + ./autogen.sh + ./configure --enable-all --enable-md5 + make -j$(nproc) + sudo make install + sudo ldconfig + - name: Build wolfIP with ${{ matrix.name }} run: | mkdir -p build/port diff --git a/src/test/unit/unit.c b/src/test/unit/unit.c index 6321466..6660506 100644 --- a/src/test/unit/unit.c +++ b/src/test/unit/unit.c @@ -208,6 +208,26 @@ Suite *wolf_suite(void) tcase_add_test(tc_utils, test_sock_accept_synack_retransmission); tcase_add_test(tc_utils, test_sock_accept_synack_window_not_scaled); tcase_add_test(tc_utils, test_sock_accept_ack_transitions_to_established); + tcase_add_test(tc_utils, test_listen_socket_survives_synrcvd_rto_expiry); + tcase_add_test(tc_utils, test_accepted_socket_destroyed_on_synrcvd_rto_expiry); + tcase_add_test(tc_utils, test_tcp_send_syn_options_aligned_small_mtu); + 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_arp_recv_rejects_broadcast_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); + tcase_add_test(tc_utils, test_udp_no_icmp_unreachable_for_broadcast_dst); + tcase_add_test(tc_utils, test_udp_no_icmp_unreachable_for_multicast_dst); + tcase_add_test(tc_utils, test_tcp_no_rst_for_broadcast_dst); + tcase_add_test(tc_utils, test_tcp_no_rst_for_multicast_dst); + tcase_add_test(tc_utils, test_dhcp_renewing_transitions_to_rebinding); + tcase_add_test(tc_utils, test_arp_recv_rejects_wrong_htype); + tcase_add_test(tc_utils, test_syn_sent_bad_ack_synack_sends_rst); + 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_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); @@ -712,6 +732,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_proto, test_icmp_input_echo_request_dhcp_running_no_reply); tcase_add_test(tc_proto, test_icmp_input_echo_request_broadcast_no_reply); tcase_add_test(tc_proto, test_icmp_input_echo_request_directed_broadcast_no_reply); + tcase_add_test(tc_proto, test_icmp_input_echo_request_multicast_no_reply); tcase_add_test(tc_proto, test_icmp_input_echo_request_filter_drop); tcase_add_test(tc_proto, test_icmp_input_echo_request_ip_filter_drop); tcase_add_test(tc_proto, test_icmp_input_echo_request_eth_filter_drop); diff --git a/src/test/unit/unit_tests_api.c b/src/test/unit/unit_tests_api.c index 11d21a7..3012cad 100644 --- a/src/test/unit/unit_tests_api.c +++ b/src/test/unit/unit_tests_api.c @@ -3638,3 +3638,798 @@ START_TEST(test_dns_wrapper_apis) ck_assert_uint_eq(s.dns_query_type, DNS_QUERY_TYPE_PTR); } END_TEST + +/* Regression: a listen socket stuck in SYN_RCVD (e.g. spoofed SYN with no + * ACK reply) must revert to TCP_LISTEN when ctrl-RTO retries are exhausted, + * not be destroyed via close_socket(). */ +START_TEST(test_listen_socket_survives_synrcvd_rto_expiry) +{ + struct wolfIP s; + int listen_sd; + struct tsocket *listener; + 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); + + listener = &s.tcpsockets[SOCKET_UNMARK(listen_sd)]; + + /* Inject a SYN, do NOT call accept(), simulating a spoofed source */ + inject_tcp_syn(&s, TEST_PRIMARY_IF, 0x0A000001U, 1234); + ck_assert_int_eq(listener->sock.tcp.state, TCP_SYN_RCVD); + + /* Exhaust all ctrl-RTO retries */ + listener->sock.tcp.ctrl_rto_retries = TCP_CTRL_RTO_MAXRTX; + s.last_tick = 100000; + tcp_rto_cb(listener); + + /* Listen socket must be alive and back in LISTEN state */ + ck_assert_uint_eq(listener->proto, WI_IPPROTO_TCP); + ck_assert_int_eq(listener->sock.tcp.state, TCP_LISTEN); + ck_assert_uint_eq(listener->sock.tcp.ctrl_rto_active, 0); + ck_assert_uint_eq(listener->remote_ip, 0); + ck_assert_uint_eq(listener->dst_port, 0); +} +END_TEST + +/* Regression: with a constrained MTU that yields max_opt_len not a multiple + * of 4, the TS option fit-check must account for final 4-byte alignment. + * Without the alignment guard, opt_len can end up unaligned and hlen encodes + * fewer bytes than actually written, corrupting the SYN segment. */ +START_TEST(test_tcp_send_syn_options_aligned_small_mtu) +{ + struct wolfIP s; + int sd; + struct tsocket *ts; + struct wolfIP_sockaddr_in sin; + struct pkt_desc *desc; + struct wolfIP_tcp_seg *seg; + uint8_t opt_len; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + /* frame MTU 75 -> ip_mtu 61 -> max_opt_len 21: triggers unaligned + * options when TS is included without the alignment guard. */ + wolfIP_mtu_set(&s, TEST_PRIMARY_IF, 75); + + sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_STREAM, WI_IPPROTO_TCP); + ck_assert_int_gt(sd, 0); + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = ee16(5000); + sin.sin_addr.s_addr = ee32(0x0A000001U); + ck_assert_int_eq(wolfIP_sock_bind(&s, sd, (struct wolfIP_sockaddr *)&sin, sizeof(sin)), 0); + + ts = &s.tcpsockets[SOCKET_UNMARK(sd)]; + ts->if_idx = TEST_PRIMARY_IF; + ts->remote_ip = 0x0A000002U; + ts->dst_port = 80; + + ck_assert_int_eq(tcp_send_syn(ts, TCP_FLAG_SYN), 0); + + desc = fifo_peek(&ts->sock.tcp.txbuf); + ck_assert_ptr_nonnull(desc); + seg = (struct wolfIP_tcp_seg *)(ts->txmem + desc->pos + sizeof(*desc)); + opt_len = (uint8_t)(desc->len - sizeof(struct wolfIP_tcp_seg)); + + /* TCP options must be 4-byte aligned */ + ck_assert_uint_eq(opt_len % 4, 0); + /* hlen must correctly encode the actual header length */ + ck_assert_uint_eq(seg->hlen >> 2, TCP_HEADER_LEN + opt_len); +} +END_TEST + +/* Non-listener sockets in SYN_RCVD (created by accept()) must still be + * destroyed when ctrl-RTO retries are exhausted. */ +START_TEST(test_accepted_socket_destroyed_on_synrcvd_rto_expiry) +{ + struct wolfIP s; + int listen_sd, client_sd; + struct tsocket *accepted; + 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); + + inject_tcp_syn(&s, TEST_PRIMARY_IF, 0x0A000001U, 1234); + client_sd = wolfIP_sock_accept(&s, listen_sd, NULL, NULL); + ck_assert_int_gt(client_sd, 0); + + accepted = &s.tcpsockets[SOCKET_UNMARK(client_sd)]; + ck_assert_int_eq(accepted->sock.tcp.state, TCP_SYN_RCVD); + + /* Exhaust all ctrl-RTO retries */ + accepted->sock.tcp.ctrl_rto_retries = TCP_CTRL_RTO_MAXRTX; + s.last_tick = 100000; + tcp_rto_cb(accepted); + + /* Non-listener socket must be destroyed (memset to zero) */ + ck_assert_uint_eq(accepted->proto, 0); +} +END_TEST + +/* Regression: per RFC 9293, a RST without ACK in SYN_SENT must be silently + * dropped. The generic RST handler checked seg_seq == rcv_nxt, which is + * trivially true (both 0) in SYN_SENT, allowing a bare RST with SEQ=0 to + * destroy an outgoing connection attempt. */ +START_TEST(test_syn_sent_bare_rst_dropped) +{ + struct wolfIP s; + int sd; + struct tsocket *ts; + struct wolfIP_sockaddr_in sin; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_STREAM, WI_IPPROTO_TCP); + ck_assert_int_gt(sd, 0); + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = ee16(80); + sin.sin_addr.s_addr = ee32(0x0A000002U); + ck_assert_int_eq(wolfIP_sock_connect(&s, sd, (struct wolfIP_sockaddr *)&sin, sizeof(sin)), + -WOLFIP_EAGAIN); + + ts = &s.tcpsockets[SOCKET_UNMARK(sd)]; + ck_assert_int_eq(ts->sock.tcp.state, TCP_SYN_SENT); + + /* Bare RST (no ACK) with SEQ=0: must be silently dropped */ + inject_tcp_segment(&s, TEST_PRIMARY_IF, + 0x0A000002U, 0x0A000001U, + 80, ts->src_port, + 0, 0, TCP_FLAG_RST); + + /* Socket must survive */ + ck_assert_uint_eq(ts->proto, WI_IPPROTO_TCP); + ck_assert_int_eq(ts->sock.tcp.state, TCP_SYN_SENT); +} +END_TEST + +/* Regression: in SYN_RCVD, a RST with a sequence number outside the receive + * window must be silently dropped per RFC 9293 §3.10.7. The SYN_RCVD branch + * bypassed the window check entirely, accepting any RST. */ +START_TEST(test_syn_rcvd_rst_bad_seq_dropped) +{ + struct wolfIP s; + int listen_sd; + struct tsocket *listener; + 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); + + /* SYN puts the listen socket into SYN_RCVD */ + inject_tcp_syn(&s, TEST_PRIMARY_IF, 0x0A000001U, 1234); + listener = &s.tcpsockets[SOCKET_UNMARK(listen_sd)]; + ck_assert_int_eq(listener->sock.tcp.state, TCP_SYN_RCVD); + + /* RST with SEQ=0 (wrong - rcv_nxt is ISN_peer+1): must be dropped */ + inject_tcp_segment(&s, TEST_PRIMARY_IF, + 0x0A0000A1U, 0x0A000001U, + listener->dst_port, listener->src_port, + 0, 0, TCP_FLAG_RST); + + /* Socket must still be in SYN_RCVD */ + ck_assert_int_eq(listener->sock.tcp.state, TCP_SYN_RCVD); +} +END_TEST + +/* Regression: ip_recv must drop packets with broadcast or multicast source + * addresses per RFC 1122 §3.2.1.3. Without the check, a TCP SYN from a + * broadcast source reaches tcp_input and transitions a listen socket to + * SYN_RCVD. */ +START_TEST(test_ip_recv_drops_broadcast_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)]; + + /* Build a TCP SYN from broadcast source 255.255.255.255 */ + 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(0xFFFFFFFFU); + 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); + + /* Socket must stay in LISTEN - broadcast source should be dropped */ + 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. */ +START_TEST(test_arp_recv_rejects_broadcast_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); + + /* Build an ARP request for our IP from broadcast sender */ + 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(0xFFFFFFFFU); /* broadcast sender */ + memset(arp.tma, 0, 6); + arp.tip = ee32(conf->ip); + + arp_recv(&s, TEST_PRIMARY_IF, &arp, sizeof(arp)); + + /* No neighbor entry should exist for 255.255.255.255 */ + ck_assert_int_lt(arp_neighbor_index(&s, TEST_PRIMARY_IF, 0xFFFFFFFFU), 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. */ +START_TEST(test_arp_recv_rejects_wrong_htype) +{ + struct wolfIP s; + struct arp_packet arp; + struct wolfIP_ll_dev *ll; + struct ipconf *conf; + ip4 sender = 0x0A000099U; + static const uint8_t fake_mac[6] = {0xDE, 0xAD, 0xBE, 0xEF, 0x00, 0x02}; + + 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(6); /* IEEE 802 - wrong for Ethernet */ + arp.ptype = ee16(0x0800); + arp.hlen = 6; + arp.plen = 4; + arp.opcode = ee16(ARP_REQUEST); + memcpy(arp.sma, fake_mac, 6); + arp.sip = ee32(sender); + memset(arp.tma, 0, 6); + arp.tip = ee32(conf->ip); + + last_frame_sent_size = 0; + arp_recv(&s, TEST_PRIMARY_IF, &arp, sizeof(arp)); + + /* No reply sent, no neighbor cached */ + ck_assert_uint_eq(last_frame_sent_size, 0); + ck_assert_int_lt(arp_neighbor_index(&s, TEST_PRIMARY_IF, sender), 0); +} +END_TEST + +/* Regression: dhcp_parse_ack must reject a DHCPACK whose Server Identifier + * differs from the one committed during the OFFER phase. Without this check, + * a rogue server can race a forged ACK with attacker-controlled config. */ +START_TEST(test_dhcp_ack_rejects_mismatched_server_id) +{ + struct wolfIP s; + struct dhcp_msg msg; + struct dhcp_option *opt; + uint32_t real_server = 0x0A000001U; + uint32_t rogue_server = 0x0A000099U; + + wolfIP_init(&s); + + /* Simulate OFFER phase: set committed server IP and XID */ + s.dhcp_server_ip = real_server; + s.dhcp_xid = 0x12345678U; + s.dhcp_state = DHCP_REQUEST_SENT; + + /* Build a DHCPACK with a different Server Identifier */ + memset(&msg, 0, sizeof(msg)); + msg.op = BOOT_REPLY; + msg.magic = ee32(DHCP_MAGIC); + msg.xid = ee32(0x12345678U); + opt = (struct dhcp_option *)msg.options; + opt->code = DHCP_OPTION_MSG_TYPE; + opt->len = 1; + opt->data[0] = DHCP_ACK; + opt = (struct dhcp_option *)((uint8_t *)opt + 3); + opt->code = DHCP_OPTION_SERVER_ID; + opt->len = 4; + opt->data[0] = (rogue_server >> 24) & 0xFF; + opt->data[1] = (rogue_server >> 16) & 0xFF; + opt->data[2] = (rogue_server >> 8) & 0xFF; + opt->data[3] = (rogue_server >> 0) & 0xFF; + opt = (struct dhcp_option *)((uint8_t *)opt + 6); + opt->code = DHCP_OPTION_END; + + /* Must be rejected - server ID doesn't match the OFFER */ + ck_assert_int_eq(dhcp_parse_ack(&s, &msg, sizeof(msg)), -1); + /* Committed server IP must not be overwritten */ + ck_assert_uint_eq(s.dhcp_server_ip, real_server); +} +END_TEST + +/* build a minimal UDP datagram for udp_try_recv injection. */ +static void build_udp_datagram(struct wolfIP_udp_datagram *udp, + struct wolfIP *s, unsigned int if_idx, + ip4 src_ip, ip4 dst_ip, + uint16_t src_port, uint16_t dst_port) +{ + struct wolfIP_ll_dev *ll = wolfIP_getdev_ex(s, if_idx); + union transport_pseudo_header ph; + static const uint8_t src_mac[6] = {0x10, 0x20, 0x30, 0x40, 0x50, 0x60}; + + memset(udp, 0, sizeof(*udp)); + memcpy(udp->ip.eth.dst, ll->mac, 6); + memcpy(udp->ip.eth.src, src_mac, 6); + udp->ip.eth.type = ee16(ETH_TYPE_IP); + udp->ip.ver_ihl = 0x45; + udp->ip.ttl = 64; + udp->ip.proto = WI_IPPROTO_UDP; + udp->ip.len = ee16(IP_HEADER_LEN + UDP_HEADER_LEN); + udp->ip.src = ee32(src_ip); + udp->ip.dst = ee32(dst_ip); + udp->ip.csum = 0; + iphdr_set_checksum(&udp->ip); + udp->src_port = ee16(src_port); + udp->dst_port = ee16(dst_port); + udp->len = ee16(UDP_HEADER_LEN); + udp->csum = 0; + memset(&ph, 0, sizeof(ph)); + ph.ph.src = udp->ip.src; + ph.ph.dst = udp->ip.dst; + ph.ph.proto = WI_IPPROTO_UDP; + ph.ph.len = udp->len; + udp->csum = ee16(transport_checksum(&ph, &udp->src_port)); +} + +/* udp_try_recv must NOT send ICMP port unreachable when the + * source IP is broadcast - prevents amplification attacks. */ +START_TEST(test_udp_no_icmp_unreachable_for_broadcast_src) +{ + struct wolfIP s; + struct wolfIP_udp_datagram udp; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + build_udp_datagram(&udp, &s, TEST_PRIMARY_IF, + 0xFFFFFFFFU, 0x0A000001U, 9999, 9999); + + last_frame_sent_size = 0; + udp_try_recv(&s, TEST_PRIMARY_IF, &udp, + sizeof(struct wolfIP_eth_frame) + IP_HEADER_LEN + UDP_HEADER_LEN); + + ck_assert_uint_eq(last_frame_sent_size, 0); +} +END_TEST + +/* udp_try_recv must NOT send ICMP port unreachable when the + * source IP is multicast - prevents amplification attacks. */ +START_TEST(test_udp_no_icmp_unreachable_for_multicast_src) +{ + struct wolfIP s; + struct wolfIP_udp_datagram udp; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + build_udp_datagram(&udp, &s, TEST_PRIMARY_IF, + 0xE0000001U, 0x0A000001U, 9999, 9999); + + last_frame_sent_size = 0; + udp_try_recv(&s, TEST_PRIMARY_IF, &udp, + sizeof(struct wolfIP_eth_frame) + IP_HEADER_LEN + UDP_HEADER_LEN); + + ck_assert_uint_eq(last_frame_sent_size, 0); +} +END_TEST + +/* udp_try_recv must NOT send ICMP port unreachable when the + * destination IP is broadcast. */ +START_TEST(test_udp_no_icmp_unreachable_for_broadcast_dst) +{ + struct wolfIP s; + struct wolfIP_udp_datagram udp; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + build_udp_datagram(&udp, &s, TEST_PRIMARY_IF, + 0x0A000099U, 0xFFFFFFFFU, 9999, 9999); + + last_frame_sent_size = 0; + udp_try_recv(&s, TEST_PRIMARY_IF, &udp, + sizeof(struct wolfIP_eth_frame) + IP_HEADER_LEN + UDP_HEADER_LEN); + + ck_assert_uint_eq(last_frame_sent_size, 0); +} +END_TEST + +/* udp_try_recv must NOT send ICMP port unreachable when the + * destination IP is multicast. */ +START_TEST(test_udp_no_icmp_unreachable_for_multicast_dst) +{ + struct wolfIP s; + struct wolfIP_udp_datagram udp; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + build_udp_datagram(&udp, &s, TEST_PRIMARY_IF, + 0x0A000099U, 0xE0000001U, 9999, 9999); + + last_frame_sent_size = 0; + udp_try_recv(&s, TEST_PRIMARY_IF, &udp, + sizeof(struct wolfIP_eth_frame) + IP_HEADER_LEN + UDP_HEADER_LEN); + + ck_assert_uint_eq(last_frame_sent_size, 0); +} +END_TEST + +/* tcp_input must NOT send RST for unmatched segments when the + * destination IP is broadcast. */ +START_TEST(test_tcp_no_rst_for_broadcast_dst) +{ + struct wolfIP s; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + last_frame_sent_size = 0; + inject_tcp_segment(&s, TEST_PRIMARY_IF, + 0x0A000099U, 0xFFFFFFFFU, + 12345, 9999, + 100, 0, TCP_FLAG_ACK); + + ck_assert_uint_eq(last_frame_sent_size, 0); +} +END_TEST + +/* tcp_input must NOT send RST for unmatched segments when the + * destination IP is multicast. */ +START_TEST(test_tcp_no_rst_for_multicast_dst) +{ + struct wolfIP s; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + last_frame_sent_size = 0; + inject_tcp_segment(&s, TEST_PRIMARY_IF, + 0x0A000099U, 0xE0000001U, + 12345, 9999, + 100, 0, TCP_FLAG_ACK); + + ck_assert_uint_eq(last_frame_sent_size, 0); +} +END_TEST + +/* dhcp_timer_cb in RENEWING state must transition to REBINDING + * when last_tick >= dhcp_rebind_at. + * last_tick == rebind_at. */ +START_TEST(test_dhcp_renewing_transitions_to_rebinding) +{ + struct wolfIP s; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + s.dhcp_state = DHCP_RENEWING; + s.dhcp_rebind_at = 5000; + s.dhcp_timeout_count = 3; + s.last_tick = 5000; + + dhcp_timer_cb(&s); + + ck_assert_int_eq(s.dhcp_state, DHCP_REBINDING); + ck_assert_uint_eq(s.dhcp_start_tick, 5000); + ck_assert_uint_eq(s.dhcp_timeout_count, 0); +} +END_TEST + +/* Regression: per RFC 9293, a SYN-ACK with invalid ACK in SYN_SENT must + * trigger a RST () to help the peer clean up stale + * half-open connections. The code silently dropped it with continue. */ +START_TEST(test_syn_sent_bad_ack_synack_sends_rst) +{ + struct wolfIP s; + int sd; + struct tsocket *ts; + struct wolfIP_sockaddr_in sin; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_STREAM, WI_IPPROTO_TCP); + ck_assert_int_gt(sd, 0); + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = ee16(80); + sin.sin_addr.s_addr = ee32(0x0A000002U); + ck_assert_int_eq(wolfIP_sock_connect(&s, sd, (struct wolfIP_sockaddr *)&sin, sizeof(sin)), + -WOLFIP_EAGAIN); + + ts = &s.tcpsockets[SOCKET_UNMARK(sd)]; + ck_assert_int_eq(ts->sock.tcp.state, TCP_SYN_SENT); + + /* Inject SYN-ACK with wrong ACK (99 instead of ISN+1) */ + last_frame_sent_size = 0; + inject_tcp_segment(&s, TEST_PRIMARY_IF, + 0x0A000002U, 0x0A000001U, + 80, ts->src_port, + 1000, 99, + TCP_FLAG_SYN | TCP_FLAG_ACK); + + /* A RST must be sent */ + ck_assert_uint_gt(last_frame_sent_size, 0); + /* Socket must remain in SYN_SENT */ + ck_assert_int_eq(ts->sock.tcp.state, TCP_SYN_SENT); +} +END_TEST + +/* Regression: per RFC 9293 §3.10.7.4, an ACK with invalid ack value in + * SYN_RCVD must trigger a RST. The code silently dropped it. */ +START_TEST(test_syn_rcvd_bad_ack_sends_rst) +{ + struct wolfIP s; + int listen_sd, client_sd; + struct tsocket *accepted; + 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); + + /* SYN puts listen socket in SYN_RCVD, accept creates new socket */ + inject_tcp_syn(&s, TEST_PRIMARY_IF, 0x0A000001U, 1234); + client_sd = wolfIP_sock_accept(&s, listen_sd, NULL, NULL); + ck_assert_int_gt(client_sd, 0); + + accepted = &s.tcpsockets[SOCKET_UNMARK(client_sd)]; + ck_assert_int_eq(accepted->sock.tcp.state, TCP_SYN_RCVD); + + /* Inject ACK with wrong ack value (99 instead of snd_una+1) */ + last_frame_sent_size = 0; + inject_tcp_segment(&s, TEST_PRIMARY_IF, + 0x0A0000A1U, 0x0A000001U, + accepted->dst_port, 1234, + accepted->sock.tcp.ack, 99, + TCP_FLAG_ACK); + + /* A RST must be sent */ + ck_assert_uint_gt(last_frame_sent_size, 0); + /* Socket must remain in SYN_RCVD */ + ck_assert_int_eq(accepted->sock.tcp.state, TCP_SYN_RCVD); +} +END_TEST + +/* Regression: per RFC 9293 §3.10.7.4 step 5, a segment without ACK in a + * synchronized state must be dropped entirely. Without the check, a FIN + * without ACK triggers a state transition in ESTABLISHED. */ +START_TEST(test_established_fin_without_ack_dropped) +{ + struct wolfIP s; + int sd; + struct tsocket *ts; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_STREAM, WI_IPPROTO_TCP); + ck_assert_int_gt(sd, 0); + ts = &s.tcpsockets[SOCKET_UNMARK(sd)]; + ts->sock.tcp.state = TCP_ESTABLISHED; + ts->src_port = 1234; + ts->dst_port = 5000; + ts->local_ip = 0x0A000001U; + ts->remote_ip = 0x0A000002U; + ts->sock.tcp.ack = 100; + ts->sock.tcp.seq = 200; + ts->sock.tcp.snd_una = 200; + queue_init(&ts->sock.tcp.rxbuf, ts->rxmem, RXBUF_SIZE, 0); + + /* Inject FIN without ACK - must be dropped */ + inject_tcp_segment(&s, TEST_PRIMARY_IF, + 0x0A000002U, 0x0A000001U, + 5000, 1234, + 100, 0, TCP_FLAG_FIN); + + /* State must remain ESTABLISHED - FIN without ACK is invalid */ + ck_assert_int_eq(ts->sock.tcp.state, TCP_ESTABLISHED); +} +END_TEST + +/* Regression: ip_recv must drop packets containing source routing options + * (LSRR 0x83, SSRR 0x89) per RFC 7126 §3.8. */ +START_TEST(test_ip_recv_drops_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)); + + /* Ethernet header */ + 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 header with IHL=6 (24 bytes) */ + 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: LSRR */ + { + uint8_t *opts = pkt + ETH_HEADER_LEN + IP_HEADER_LEN; + opts[0] = 0x83; /* LSRR */ + opts[1] = 3; + opts[2] = 4; + opts[3] = 0x00; /* End of Options */ + } + ip->csum = 0; + iphdr_set_checksum(ip); + + /* TCP header at offset ETH+24 with valid checksum */ + { + 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; /* seq=1 */ + tcp[12] = TCP_HEADER_LEN << 2; + tcp[13] = TCP_FLAG_SYN; + tcp[14] = 0xFF; tcp[15] = 0xFF; /* window */ + /* Compute TCP checksum via pseudo-header */ + 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)); + + /* Socket must stay in LISTEN - source-routed packet should be dropped */ + 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 d073a85..926b887 100644 --- a/src/test/unit/unit_tests_dns_dhcp.c +++ b/src/test/unit/unit_tests_dns_dhcp.c @@ -1919,6 +1919,32 @@ START_TEST(test_icmp_input_echo_request_directed_broadcast_no_reply) } END_TEST +/* Coverage: icmp_input must NOT reply to echo requests with multicast dst. */ +START_TEST(test_icmp_input_echo_request_multicast_no_reply) +{ + struct wolfIP s; + struct wolfIP_icmp_packet icmp; + uint32_t frame_len; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + s.dhcp_state = DHCP_OFF; + last_frame_sent_size = 0; + + memset(&icmp, 0, sizeof(icmp)); + icmp.ip.src = ee32(0x0A000002U); + icmp.ip.dst = ee32(0xE0000001U); + icmp.ip.len = ee16(IP_HEADER_LEN + ICMP_HEADER_LEN); + 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_eq(last_frame_sent_size, 0U); +} +END_TEST + START_TEST(test_icmp_input_echo_request_filter_drop) { struct wolfIP s; @@ -2875,7 +2901,7 @@ START_TEST(test_tcp_handshake_and_fin_close_wait) inject_tcp_segment(&s, TEST_PRIMARY_IF, remote_ip, local_ip, remote_port, local_port, 2, server_seq + 1, TCP_FLAG_ACK); ck_assert_int_eq(ts->sock.tcp.state, TCP_ESTABLISHED); - inject_tcp_segment(&s, TEST_PRIMARY_IF, remote_ip, local_ip, remote_port, local_port, 2, server_seq + 1, TCP_FLAG_FIN); + inject_tcp_segment(&s, TEST_PRIMARY_IF, remote_ip, local_ip, remote_port, local_port, 2, server_seq + 1, TCP_FLAG_FIN | TCP_FLAG_ACK); ck_assert_int_eq(ts->sock.tcp.state, TCP_CLOSE_WAIT); ck_assert_uint_eq(ts->events & CB_EVENT_CLOSED, CB_EVENT_CLOSED); } diff --git a/src/test/unit/unit_tests_proto.c b/src/test/unit/unit_tests_proto.c index aa25c0c..36bd9fb 100644 --- a/src/test/unit/unit_tests_proto.c +++ b/src/test/unit/unit_tests_proto.c @@ -272,7 +272,7 @@ START_TEST(test_tcp_input_fin_wait_2_fin_with_payload_queues) seg->seq = ee32(100); seg->ack = ee32(100); seg->hlen = TCP_HEADER_LEN << 2; - seg->flags = TCP_FLAG_FIN; + seg->flags = TCP_FLAG_FIN | TCP_FLAG_ACK; memcpy(seg->data, payload, sizeof(payload)); fix_tcp_checksums(seg); @@ -2441,6 +2441,10 @@ START_TEST(test_arp_request_handling) { s.ipconf[TEST_PRIMARY_IF].ip = device_ip; /* Prepare ARP request */ + arp_req.htype = ee16(1); + arp_req.ptype = ee16(0x0800); + arp_req.hlen = 6; + arp_req.plen = 4; arp_req.opcode = ee16(ARP_REQUEST); arp_req.sip = ee32(req_ip); memcpy(arp_req.sma, req_mac, 6); @@ -2478,6 +2482,10 @@ START_TEST(test_arp_reply_handling) { mock_link_init(&s); /* Prepare ARP reply */ + arp_reply.htype = ee16(1); + arp_reply.ptype = ee16(0x0800); + arp_reply.hlen = 6; + arp_reply.plen = 4; arp_reply.opcode = ee16(ARP_REPLY); arp_reply.sip = ee32(reply_ip); memcpy(arp_reply.sma, reply_mac, 6); @@ -2507,6 +2515,10 @@ START_TEST(test_arp_reply_unsolicited_does_not_overwrite_existing) s.arp.neighbors[0].if_idx = TEST_PRIMARY_IF; memcpy(s.arp.neighbors[0].mac, existing_mac, 6); + arp_reply.htype = ee16(1); + arp_reply.ptype = ee16(0x0800); + arp_reply.hlen = 6; + arp_reply.plen = 4; arp_reply.opcode = ee16(ARP_REPLY); arp_reply.sip = ee32(reply_ip); memcpy(arp_reply.sma, reply_mac, 6); @@ -2537,6 +2549,10 @@ START_TEST(test_arp_reply_with_pending_request_updates) s.arp.last_arp[TEST_PRIMARY_IF] = 0; arp_request(&s, TEST_PRIMARY_IF, reply_ip); + arp_reply.htype = ee16(1); + arp_reply.ptype = ee16(0x0800); + arp_reply.hlen = 6; + arp_reply.plen = 4; arp_reply.opcode = ee16(ARP_REPLY); arp_reply.sip = ee32(reply_ip); memcpy(arp_reply.sma, new_mac, 6); @@ -2567,6 +2583,10 @@ START_TEST(test_arp_request_refreshes_existing_entry) memcpy(s.arp.neighbors[0].mac, existing_mac, 6); s.arp.neighbors[0].ts = 10; + arp_req.htype = ee16(1); + arp_req.ptype = ee16(0x0800); + arp_req.hlen = 6; + arp_req.plen = 4; arp_req.opcode = ee16(ARP_REQUEST); arp_req.sip = ee32(req_ip); memcpy(arp_req.sma, req_mac, 6); @@ -2598,6 +2618,10 @@ START_TEST(test_arp_request_refreshes_timestamp_on_same_mac) s.arp.neighbors[0].ts = 10; s.last_tick = 42; + arp_req.htype = ee16(1); + arp_req.ptype = ee16(0x0800); + arp_req.hlen = 6; + arp_req.plen = 4; arp_req.opcode = ee16(ARP_REQUEST); arp_req.sip = ee32(req_ip); memcpy(arp_req.sma, mac, 6); @@ -2690,6 +2714,10 @@ START_TEST(test_arp_reply_updates_expired_entry) s.arp.neighbors[0].ts = 0; memcpy(s.arp.neighbors[0].mac, old_mac, 6); + arp_reply.htype = ee16(1); + arp_reply.ptype = ee16(0x0800); + arp_reply.hlen = 6; + arp_reply.plen = 4; arp_reply.opcode = ee16(ARP_REPLY); arp_reply.sip = ee32(reply_ip); memcpy(arp_reply.sma, new_mac, 6); @@ -3200,6 +3228,10 @@ START_TEST(test_wolfip_recv_ex_multi_interface_arp_reply) arp_req.ptype = ee16(ETH_TYPE_IP); arp_req.hlen = 6; arp_req.plen = 4; + arp_req.htype = ee16(1); + arp_req.ptype = ee16(0x0800); + arp_req.hlen = 6; + arp_req.plen = 4; arp_req.opcode = ee16(ARP_REQUEST); memcpy(arp_req.sma, requester_mac, 6); arp_req.sip = ee32(0xC0A80164); diff --git a/src/test/unit/unit_tests_tcp_ack.c b/src/test/unit/unit_tests_tcp_ack.c index 87845c7..9eb6dde 100644 --- a/src/test/unit/unit_tests_tcp_ack.c +++ b/src/test/unit/unit_tests_tcp_ack.c @@ -153,7 +153,7 @@ START_TEST(test_tcp_rst_syn_rcvd_returns_to_listen) ts = &s.tcpsockets[SOCKET_UNMARK(listen_sd)]; ck_assert_int_eq(ts->sock.tcp.state, TCP_SYN_RCVD); - inject_tcp_segment(&s, TEST_PRIMARY_IF, remote_ip, local_ip, remote_port, local_port, 1, 0, TCP_FLAG_RST); + inject_tcp_segment(&s, TEST_PRIMARY_IF, remote_ip, local_ip, remote_port, local_port, ts->sock.tcp.ack, 0, TCP_FLAG_RST); ck_assert_int_eq(ts->sock.tcp.state, TCP_LISTEN); ck_assert_uint_eq(ts->remote_ip, IPADDR_ANY); ck_assert_uint_eq(ts->dst_port, 0); @@ -193,7 +193,7 @@ START_TEST(test_tcp_input_fin_wait_2_fin_sets_ack) seg.dst_port = ee16(ts->src_port); seg.src_port = ee16(ts->dst_port); seg.hlen = TCP_HEADER_LEN << 2; - seg.flags = TCP_FLAG_FIN; + seg.flags = TCP_FLAG_FIN | TCP_FLAG_ACK; seg.seq = ee32(seq); fix_tcp_checksums(&seg); @@ -1077,7 +1077,7 @@ START_TEST(test_tcp_fin_wait_1_to_closing) queue_init(&ts->sock.tcp.rxbuf, ts->rxmem, RXBUF_SIZE, ts->sock.tcp.ack); inject_tcp_segment(&s, TEST_PRIMARY_IF, remote_ip, local_ip, remote_port, local_port, - 9, 0, TCP_FLAG_FIN); + 9, 0, TCP_FLAG_FIN | TCP_FLAG_ACK); ck_assert_int_eq(ts->sock.tcp.state, TCP_CLOSING); ck_assert_uint_eq(ts->events & CB_EVENT_CLOSED, CB_EVENT_CLOSED); } @@ -1528,6 +1528,10 @@ START_TEST(test_ip_recv_forward_arp_queue_and_flush) ck_assert_uint_gt(s.arp_pending[0].len, 0); memset(&arp_reply, 0, sizeof(arp_reply)); + arp_reply.htype = ee16(1); + arp_reply.ptype = ee16(0x0800); + arp_reply.hlen = 6; + arp_reply.plen = 4; arp_reply.opcode = ee16(ARP_REPLY); arp_reply.sip = ee32(dest_ip); memcpy(arp_reply.sma, dest_mac, 6); @@ -1563,6 +1567,10 @@ START_TEST(test_arp_flush_pending_ttl_expired) ck_assert_uint_eq(s.arp_pending[0].dest, dest_ip); memset(&arp_reply, 0, sizeof(arp_reply)); + arp_reply.htype = ee16(1); + arp_reply.ptype = ee16(0x0800); + arp_reply.hlen = 6; + arp_reply.plen = 4; arp_reply.opcode = ee16(ARP_REPLY); arp_reply.sip = ee32(dest_ip); memcpy(arp_reply.sma, "\x01\x02\x03\x04\x05\x06", 6); @@ -1945,6 +1953,10 @@ START_TEST(test_arp_recv_reply_updates_existing_no_duplicate) s.last_tick = 100; memset(&pkt, 0, sizeof(pkt)); + pkt.htype = ee16(1); + pkt.ptype = ee16(0x0800); + pkt.hlen = 6; + pkt.plen = 4; pkt.opcode = ee16(ARP_REPLY); pkt.sip = ee32(ip); memcpy(pkt.sma, new_mac, 6); @@ -2123,6 +2135,10 @@ START_TEST(test_arp_recv_request_other_ip_no_reply) last_frame_sent_size = 0; memset(&arp_req, 0, sizeof(arp_req)); + arp_req.htype = ee16(1); + arp_req.ptype = ee16(0x0800); + arp_req.hlen = 6; + arp_req.plen = 4; arp_req.opcode = ee16(ARP_REQUEST); arp_req.sip = ee32(0x0A000002U); memcpy(arp_req.sma, "\x01\x02\x03\x04\x05\x06", 6); @@ -2138,6 +2154,10 @@ START_TEST(test_arp_recv_null_stack) struct arp_packet arp_req; memset(&arp_req, 0, sizeof(arp_req)); + arp_req.htype = ee16(1); + arp_req.ptype = ee16(0x0800); + arp_req.hlen = 6; + arp_req.plen = 4; arp_req.opcode = ee16(ARP_REQUEST); arp_req.tip = ee32(0x0A000001U); @@ -2157,6 +2177,10 @@ START_TEST(test_arp_recv_request_no_send_fn) last_frame_sent_size = 0; memset(&arp_req, 0, sizeof(arp_req)); + arp_req.htype = ee16(1); + arp_req.ptype = ee16(0x0800); + arp_req.hlen = 6; + arp_req.plen = 4; arp_req.opcode = ee16(ARP_REQUEST); arp_req.sip = ee32(0x0A000002U); memcpy(arp_req.sma, "\x01\x02\x03\x04\x05\x06", 6); @@ -2180,6 +2204,10 @@ START_TEST(test_arp_recv_request_sends_reply) last_frame_sent_size = 0; memset(&arp_req, 0, sizeof(arp_req)); + arp_req.htype = ee16(1); + arp_req.ptype = ee16(0x0800); + arp_req.hlen = 6; + arp_req.plen = 4; arp_req.opcode = ee16(ARP_REQUEST); arp_req.sip = ee32(0x0A000002U); memcpy(arp_req.sma, "\x01\x02\x03\x04\x05\x06", 6); @@ -2208,6 +2236,10 @@ START_TEST(test_arp_recv_request_does_not_store_self_neighbor) wolfIP_filter_set_mask(0); memset(&arp_req, 0, sizeof(arp_req)); + arp_req.htype = ee16(1); + arp_req.ptype = ee16(0x0800); + arp_req.hlen = 6; + arp_req.plen = 4; arp_req.opcode = ee16(ARP_REQUEST); arp_req.sip = ee32(sender_ip); memcpy(arp_req.sma, sender_mac, sizeof(sender_mac)); @@ -2447,6 +2479,10 @@ START_TEST(test_arp_reply_filter_drop) last_frame_sent_size = 0; memset(&arp_req, 0, sizeof(arp_req)); + arp_req.htype = ee16(1); + arp_req.ptype = ee16(0x0800); + arp_req.hlen = 6; + arp_req.plen = 4; arp_req.opcode = ee16(ARP_REQUEST); arp_req.sip = ee32(0x0A000002U); memcpy(arp_req.sma, "\x01\x02\x03\x04\x05\x06", 6); @@ -2487,6 +2523,10 @@ START_TEST(test_arp_recv_invalid_iface) last_frame_sent_size = 0; memset(&arp_req, 0, sizeof(arp_req)); + arp_req.htype = ee16(1); + arp_req.ptype = ee16(0x0800); + arp_req.hlen = 6; + arp_req.plen = 4; arp_req.opcode = ee16(ARP_REQUEST); arp_req.tip = ee32(0x0A000001U); @@ -2509,6 +2549,10 @@ START_TEST(test_arp_recv_filter_drop) last_frame_sent_size = 0; memset(&arp_req, 0, sizeof(arp_req)); + arp_req.htype = ee16(1); + arp_req.ptype = ee16(0x0800); + arp_req.hlen = 6; + arp_req.plen = 4; arp_req.opcode = ee16(ARP_REQUEST); arp_req.sip = ee32(0x0A000002U); memcpy(arp_req.sma, "\x01\x02\x03\x04\x05\x06", 6); diff --git a/src/test/unit/unit_tests_tcp_flow.c b/src/test/unit/unit_tests_tcp_flow.c index 9ca63c5..0147a5a 100644 --- a/src/test/unit/unit_tests_tcp_flow.c +++ b/src/test/unit/unit_tests_tcp_flow.c @@ -4090,7 +4090,7 @@ START_TEST(test_tcp_input_established_fin_sets_close_wait) seg.dst_port = ee16(ts->src_port); seg.seq = ee32(10); seg.hlen = TCP_HEADER_LEN << 2; - seg.flags = TCP_FLAG_FIN; + 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)); diff --git a/src/wolfesp.c b/src/wolfesp.c index 4df4b7e..6f6aac6 100644 --- a/src/wolfesp.c +++ b/src/wolfesp.c @@ -66,8 +66,8 @@ int wolfIP_esp_init(void) void wolfIP_esp_sa_del_all(void) { - memset(in_sa_list, 0, sizeof(in_sa_list)); - memset(out_sa_list, 0, sizeof(out_sa_list)); + wc_ForceZero(in_sa_list, sizeof(in_sa_list)); + wc_ForceZero(out_sa_list, sizeof(out_sa_list)); return; } @@ -104,7 +104,7 @@ void wolfIP_esp_sa_del(int in, uint8_t * spi) wolfIP_esp_sa * sa = NULL; sa = esp_sa_get(in, spi); if (sa != NULL) { - memset(sa, 0, sizeof(*sa)); + wc_ForceZero(sa, sizeof(*sa)); } return; } @@ -169,7 +169,7 @@ int wolfIP_esp_sa_new_gcm(int in, uint8_t * spi, ip4 src, ip4 dst, ESP_GCM_RFC4106_IV_LEN); if (err) { ESP_LOG("error: wc_RNG_GenerateBlock: %d\n", err); - memset(new_sa, 0, sizeof(*new_sa)); + wc_ForceZero(new_sa, sizeof(*new_sa)); err = -1; } diff --git a/src/wolfip.c b/src/wolfip.c index b492417..2b9dffd 100644 --- a/src/wolfip.c +++ b/src/wolfip.c @@ -1083,6 +1083,7 @@ struct tcpsocket { uint8_t ctrl_rto_retries; uint8_t ctrl_rto_active; uint8_t fin_wait_2_timeout_active; + uint8_t is_listener; ip4 local_ip, remote_ip; uint32_t peer_rwnd; uint16_t peer_mss; @@ -1142,6 +1143,10 @@ static int tcp_ctrl_state_needs_rto(const struct tsocket *t); static int tcp_has_pending_unsent_payload(struct tsocket *t); static inline struct wolfIP_ll_dev *wolfIP_ll_at(struct wolfIP *s, unsigned int if_idx); static inline unsigned int wolfIP_socket_if_idx(const struct tsocket *t); +#ifdef WOLFIP_ESP +static int esp_send(struct wolfIP_ll_dev *ll_dev, + const struct wolfIP_ip_packet *ip, uint16_t len); +#endif #ifdef ETHERNET struct PACKED arp_packet { @@ -1669,7 +1674,19 @@ static void wolfIP_send_ttl_exceeded(struct wolfIP *s, unsigned int if_idx, if (wolfIP_filter_notify_eth(WOLFIP_FILT_SENDING, s, if_idx, &icmp.ip.eth, sizeof(icmp)) != 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)); + } + } else { + wolfIP_ll_send_frame(s, if_idx, &icmp, sizeof(icmp)); + } +#else wolfIP_ll_send_frame(s, if_idx, &icmp, sizeof(icmp)); +#endif } #elif WOLFIP_ENABLE_FORWARDING static void wolfIP_send_ttl_exceeded(struct wolfIP *s, unsigned int if_idx, @@ -1719,7 +1736,19 @@ static void wolfIP_send_port_unreachable(struct wolfIP *s, unsigned int if_idx, if (wolfIP_filter_notify_eth(WOLFIP_FILT_SENDING, s, if_idx, &icmp.ip.eth, sizeof(icmp)) != 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)); + } + } else { + wolfIP_ll_send_frame(s, if_idx, &icmp, sizeof(icmp)); + } +#else wolfIP_ll_send_frame(s, if_idx, &icmp, sizeof(icmp)); +#endif } #else static void wolfIP_send_port_unreachable(struct wolfIP *s, unsigned int if_idx, @@ -2723,7 +2752,8 @@ static int tcp_send_syn(struct tsocket *t, uint8_t flags) *opt++ = TCP_OPTION_SACK_PERMITTED_LEN; opt_len += TCP_OPTION_SACK_PERMITTED_LEN; } - if (include_ts && (uint8_t)(opt_len + sizeof(*ts)) <= max_opt_len) { + if (include_ts && + ((uint8_t)((opt_len + sizeof(*ts) + 3U) & ~3U) <= max_opt_len)) { ts = (struct tcp_opt_ts *)opt; ts->opt = TCP_OPTION_TS; ts->len = TCP_OPTION_TS_LEN; @@ -3945,6 +3975,9 @@ static void tcp_input(struct wolfIP *S, unsigned int if_idx, continue; } if (t->sock.tcp.state == TCP_SYN_RCVD) { + /* RFC 9293: only accept RST if SEQ matches rcv_nxt */ + if (seg_seq != rcv_nxt) + continue; /* RST on a half-open connection: fall back to listening state. */ t->sock.tcp.state = TCP_LISTEN; t->events &= ~CB_EVENT_READABLE; @@ -3953,6 +3986,20 @@ static void tcp_input(struct wolfIP *S, unsigned int if_idx, t->sock.tcp.ack = 0; continue; } + if (t->sock.tcp.state == TCP_SYN_SENT) { + /* RFC 9293: RST without ACK in SYN_SENT must be dropped. + * With ACK, validate SND.UNA < SEG.ACK <= SND.NXT. */ + if (!(tcp->flags & TCP_FLAG_ACK)) + continue; + { + uint32_t seg_ack = ee32(tcp->ack); + if (!tcp_seq_lt(t->sock.tcp.snd_una, seg_ack) || + tcp_seq_lt(tcp_seq_inc(t->sock.tcp.seq, 1), seg_ack)) + continue; + } + close_socket(t); + continue; + } if (seg_seq != rcv_nxt) { uint32_t rcv_wnd = queue_space((struct queue *)&t->sock.tcp.rxbuf); if (rcv_wnd != 0) { @@ -4022,8 +4069,12 @@ static void tcp_input(struct wolfIP *S, unsigned int if_idx, break; } else if (t->sock.tcp.state == TCP_SYN_SENT) { if (tcp->flags == (TCP_FLAG_SYN | TCP_FLAG_ACK)) { - if (ee32(tcp->ack) != tcp_seq_inc(t->sock.tcp.seq, 1)) + if (ee32(tcp->ack) != tcp_seq_inc(t->sock.tcp.seq, 1)) { + /* RFC 9293: invalid ACK in SYN_SENT - send RST + * to help peer clean up stale half-open state. */ + tcp_send_reset_reply(S, if_idx, tcp); continue; + } t->sock.tcp.state = TCP_ESTABLISHED; tcp_ctrl_rto_stop(t); t->sock.tcp.ack = tcp_seq_inc(ee32(tcp->seq), 1); @@ -4048,8 +4099,12 @@ static void tcp_input(struct wolfIP *S, unsigned int if_idx, } 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) + 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); @@ -4116,6 +4171,10 @@ static void tcp_input(struct wolfIP *S, unsigned int if_idx, continue; } + /* RFC 9293 §3.10.7.4 step 5: drop if ACK bit is off. */ + if (!(tcp->flags & TCP_FLAG_ACK)) + continue; + if (tcp->flags & TCP_FLAG_ACK) { tcp_ack(t, tcp); if (t->sock.tcp.state == TCP_CLOSED) @@ -4211,8 +4270,30 @@ static void tcp_rto_cb(void *arg) } if (ts->sock.tcp.ctrl_rto_retries >= TCP_CTRL_RTO_MAXRTX) { tcp_ctrl_rto_stop(ts); - ts->sock.tcp.state = TCP_CLOSED; - close_socket(ts); + if (ts->sock.tcp.is_listener && + ts->sock.tcp.state == TCP_SYN_RCVD) { + /* Revert listen socket back to LISTEN instead of + * destroying it, mirrors the accept() recovery path. */ + ts->sock.tcp.state = TCP_LISTEN; + ts->sock.tcp.seq = wolfIP_getrandom(); + ts->sock.tcp.ack = 0; + ts->sock.tcp.snd_una = 0; + ts->sock.tcp.ctrl_rto_retries = 0; + ts->remote_ip = 0; + ts->dst_port = 0; + ts->events = 0; + if (ts->bound_local_ip != IPADDR_ANY) { + int bound_match = 0; + unsigned int bound_if = wolfIP_if_for_local_ip( + ts->S, ts->bound_local_ip, &bound_match); + ts->if_idx = bound_match ? (uint8_t)bound_if + : ts->if_idx; + ts->local_ip = ts->bound_local_ip; + } + } else { + ts->sock.tcp.state = TCP_CLOSED; + close_socket(ts); + } return; } { @@ -5403,6 +5484,7 @@ int wolfIP_sock_listen(struct wolfIP *s, int sockfd, int backlog) if (ts->sock.tcp.state != TCP_CLOSED) return -1; ts->sock.tcp.state = TCP_LISTEN; + ts->sock.tcp.is_listener = 1; if (wolfIP_filter_notify_socket_event( WOLFIP_FILT_LISTENING, s, ts, ts->local_ip, ts->src_port, IPADDR_ANY, 0) != 0) { @@ -5491,7 +5573,20 @@ static void icmp_input(struct wolfIP *s, unsigned int if_idx, struct wolfIP_ip_p return; } #endif +#ifdef WOLFIP_ESP + if (!wolfIP_ll_is_non_ethernet(s, if_idx)) { + struct wolfIP_ll_dev *ll = wolfIP_ll_at(s, if_idx); + if (esp_send(ll, ip, len - ETH_HEADER_LEN) == 1) { + /* ipsec not configured on this interface. + * send plaintext. */ + wolfIP_ll_send_frame(s, if_idx, ip, len); + } + } else { + wolfIP_ll_send_frame(s, if_idx, ip, len); + } +#else wolfIP_ll_send_frame(s, if_idx, ip, len); +#endif return; } icmp_try_deliver_tcp_error(s, icmp); @@ -5902,6 +5997,10 @@ static int dhcp_parse_ack(struct wolfIP *s, struct dhcp_msg *msg, uint32_t msg_l if (len < 4) return -1; data = DHCP_OPT_data_to_u32(inner); + /* Reject ACK from a server other than the one + * we committed to during the OFFER phase. */ + if (s->dhcp_server_ip != 0 && data != s->dhcp_server_ip) + return -1; s->dhcp_server_ip = data; saw_server_id = 1; } else if (primary && code == DHCP_OPTION_OFFER_IP) { @@ -6451,6 +6550,10 @@ static void arp_recv(struct wolfIP *s, unsigned int if_idx, void *buf, int len) conf = wolfIP_ipconf_at(s, if_idx); if (!conf) return; + /* Only process Ethernet/IPv4 ARP packets. */ + if (arp->htype != ee16(1) || arp->ptype != ee16(0x0800) || + arp->hlen != 6 || arp->plen != 4) + return; if (arp->opcode == ee16(ARP_REQUEST) && arp->tip == ee32(conf->ip)) { uint32_t sender_ip = arp->sip; @@ -6463,12 +6566,18 @@ static void arp_recv(struct wolfIP *s, unsigned int if_idx, void *buf, int len) arp->sip = ee32(conf->ip); { ip4 sip = ee32(sender_ip); - int idx = arp_neighbor_index(s, if_idx, sip); - if (idx >= 0) { - if (memcmp(s->arp.neighbors[idx].mac, sender_mac, 6) == 0) - s->arp.neighbors[idx].ts = s->last_tick; - } else { - arp_store_neighbor(s, if_idx, sip, sender_mac); + /* Validate sender IP before caching: reject broadcast, + * multicast, zero, and our own address. */ + if (sip != IPADDR_ANY && sip != conf->ip && + !wolfIP_ip_is_broadcast(s, sip) && + !wolfIP_ip_is_multicast(sip)) { + int idx = arp_neighbor_index(s, if_idx, sip); + if (idx >= 0) { + if (memcmp(s->arp.neighbors[idx].mac, sender_mac, 6) == 0) + s->arp.neighbors[idx].ts = s->last_tick; + } else { + arp_store_neighbor(s, if_idx, sip, sender_mac); + } } } eth_output_add_header(s, if_idx, arp->tma, &arp->eth, ETH_TYPE_ARP); @@ -6626,6 +6735,14 @@ static inline void ip_recv(struct wolfIP *s, unsigned int if_idx, /* Fragment reassembly is not implemented; drop all fragments. */ if ((ee16(ip->flags_fo) & 0x3FFFU) != 0U) return; + /* RFC 1122 §3.2.1.3: discard packets with non-unicast source addresses. */ + { + ip4 src = ee32(ip->src); + if (wolfIP_ip_is_broadcast(s, src) || wolfIP_ip_is_multicast(src)) + return; + if (src == IPADDR_ANY && !DHCP_IS_RUNNING(s)) + return; + } #if WOLFIP_ENABLE_LOOPBACK if (!wolfIP_is_loopback_if(if_idx)) { ip4 dest = ee32(ip->dst); @@ -6709,6 +6826,26 @@ static inline void ip_recv(struct wolfIP *s, unsigned int if_idx, uint32_t opt_len = ip_hlen - IP_HEADER_LEN; uint16_t total_ip_len = ee16(ip->len); + /* RFC 7126 §3.8: drop source-routed packets. */ + { + uint8_t *opt = ((uint8_t *)ip) + ETH_HEADER_LEN + IP_HEADER_LEN; + uint8_t *opt_end = opt + opt_len; + while (opt < opt_end) { + uint8_t type = *opt; + if (type == 0) /* End of Options */ + break; + if (type == 1) { /* NOP */ + opt++; + continue; + } + if (type == 0x83 || type == 0x89) /* LSRR or SSRR */ + return; + if (opt + 1 >= opt_end || opt[1] < 2) + break; + opt += opt[1]; + } + } + if (len > LINK_MTU) return; memcpy(frame, ip, len); @@ -7607,7 +7744,20 @@ int wolfIP_poll(struct wolfIP *s, uint64_t now) } #endif { + #ifdef WOLFIP_ESP + if (!wolfIP_ll_is_non_ethernet(s, tx_if)) { + struct wolfIP_ll_dev *ll = wolfIP_ll_at(s, tx_if); + if (esp_send(ll, (struct wolfIP_ip_packet *)icmp, len) == 1) { + /* ipsec not configured on this interface. + * send plaintext. */ + wolfIP_ll_send_frame(s, tx_if, icmp, desc->len); + } + } else { + wolfIP_ll_send_frame(s, tx_if, icmp, desc->len); + } + #else wolfIP_ll_send_frame(s, tx_if, icmp, desc->len); + #endif /* WOLFIP_ESP */ } fifo_pop(&t->sock.udp.txbuf); desc = fifo_peek(&t->sock.udp.txbuf);