diff --git a/src/test/unit/unit.c b/src/test/unit/unit.c index b487b98a..5a10c585 100644 --- a/src/test/unit/unit.c +++ b/src/test/unit/unit.c @@ -369,7 +369,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_utils, test_sock_connect_tcp_txbuf_full_does_not_enter_syn_sent); tcase_add_test(tc_utils, test_sock_sendto_more_error_paths); tcase_add_test(tc_utils, test_sock_sendto_udp_no_dest); - tcase_add_test(tc_utils, test_sock_sendto_udp_sets_dest_and_assigns); + tcase_add_test(tc_utils, test_sock_sendto_udp_encodes_dest_and_assigns_src_port); tcase_add_test(tc_utils, test_sock_sendto_udp_addrlen_short); tcase_add_test(tc_utils, test_sock_sendto_udp_len_too_large); tcase_add_test(tc_utils, test_sock_sendto_udp_fifo_full); @@ -383,6 +383,8 @@ Suite *wolf_suite(void) tcase_add_test(tc_utils, test_sock_sendto_udp_remote_ip_zero); tcase_add_test(tc_utils, test_sock_sendto_udp_primary_ip_fallback); tcase_add_test(tc_utils, test_sock_sendto_udp_zero_port_in_addr); + tcase_add_test(tc_utils, test_udp_sendto_connected_alt_dest_keeps_peer); + tcase_add_test(tc_utils, test_udp_sendto_connected_failed_sendto_keeps_peer); tcase_add_test(tc_utils, test_sock_sendto_udp_src_port_low_adjusts); tcase_add_test(tc_utils, test_sock_sendto_udp_local_ip_conf_null); tcase_add_test(tc_utils, test_sock_sendto_udp_local_ip_from_primary); @@ -973,6 +975,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_proto, test_icmp_input_dest_unreach_port_unreachable_mismatched_orig_src_port_ignored); tcase_add_test(tc_proto, test_icmp_input_dest_unreach_port_unreachable_mismatched_orig_dst_port_ignored); tcase_add_test(tc_proto, test_udp_sendto_and_recvfrom); + tcase_add_test(tc_proto, test_udp_sendto_unbound_socket_receives_reply); tcase_add_test(tc_proto, test_udp_wildcard_bind_receives_all_local_addrs); tcase_add_test(tc_proto, test_udp_sendto_respects_mtu_api); tcase_add_test(tc_proto, test_udp_recvfrom_sets_remote_ip); @@ -1194,6 +1197,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_core, test_sock_can_write_tcp_syn_sent_returns_zero); tcase_add_test(tc_core, test_sock_can_write_tcp_established_with_space); tcase_add_test(tc_core, test_sock_can_write_tcp_closed_returns_one); + tcase_add_test(tc_core, test_sock_can_write_tcp_close_wait_full_fifo_returns_zero); tcase_add_test(tc_core, test_sock_can_write_tcp_invalid_fd); #if WOLFIP_RAWSOCKETS tcase_add_test(tc_core, test_sock_can_read_raw_empty); @@ -1536,6 +1540,10 @@ Suite *wolf_suite(void) tcase_add_test(tc_core, test_ip_recv_forward_ttl1_zero_payload_icmp_not_suppressed); tcase_add_test(tc_core, test_ip_recv_forward_ttl1_partial_payload_quoted); tcase_add_test(tc_core, test_forward_ttl_exceeded_copies_orig_tos); + tcase_add_test(tc_core, test_ip_recv_forward_df_oversize_sends_frag_needed); + tcase_add_test(tc_core, test_ip_recv_forward_nodf_oversize_dropped); + tcase_add_test(tc_core, test_ip_recv_forward_df_at_mtu_forwarded); + tcase_add_test(tc_core, test_ip_recv_forward_directed_bcast_ingress_net_not_forwarded); tcase_add_test(tc_core, test_ip_recv_dest_matches_secondary_iface_ip_is_local); tcase_add_test(tc_core, test_ip_recv_multicast_dst_not_forwarded); tcase_add_test(tc_core, test_arp_recv_htype_not_ethernet_dropped); @@ -1712,6 +1720,10 @@ Suite *wolf_suite(void) tcase_add_test(tc_proto, test_vlan_api_delete_basic); tcase_add_test(tc_proto, test_vlan_api_delete_physical_rejected); tcase_add_test(tc_proto, test_vlan_api_delete_bad_ifidx_rejected); +#if WOLFIP_ENABLE_FORWARDING + tcase_add_test(tc_proto, test_vlan_delete_rejected_with_route); +#endif + tcase_add_test(tc_proto, test_vlan_delete_rejected_with_socket); tcase_add_test(tc_proto, test_vlan_api_get_null_args_rejected); tcase_add_test(tc_proto, test_vlan_api_get_dangling_parent_pointer_rejected); tcase_add_test(tc_proto, test_vlan_tx_active_without_parent_rejected); diff --git a/src/test/unit/unit_tests_api.c b/src/test/unit/unit_tests_api.c index 8f6a894f..47a559fe 100644 --- a/src/test/unit/unit_tests_api.c +++ b/src/test/unit/unit_tests_api.c @@ -873,6 +873,8 @@ START_TEST(test_udp_sendto_and_recvfrom) uint16_t local_port = 4000; uint16_t remote_port = 5000; struct tsocket *ts; + struct wolfIP_udp_datagram *udp; + struct pkt_desc *desc; wolfIP_init(&s); mock_link_init(&s); @@ -898,9 +900,14 @@ START_TEST(test_udp_sendto_and_recvfrom) ts = &s.udpsockets[SOCKET_UNMARK(sd)]; ck_assert_uint_gt(ts->src_port, 0); - ck_assert_uint_eq(ts->dst_port, remote_port); - ck_assert_uint_eq(ts->remote_ip, remote_ip); ck_assert_uint_gt(fifo_len(&ts->sock.udp.txbuf), 0); + /* The sendto destination is encoded into the queued datagram; the + * socket is not connected, so no persistent peer is set. */ + desc = fifo_peek(&ts->sock.udp.txbuf); + ck_assert_ptr_nonnull(desc); + udp = (struct wolfIP_udp_datagram *)(ts->txmem + desc->pos + sizeof(*desc)); + ck_assert_uint_eq(ee16(udp->dst_port), remote_port); + ck_assert_uint_eq(ee32(udp->ip.dst), remote_ip); inject_udp_datagram(&s, TEST_PRIMARY_IF, remote_ip, local_ip, remote_port, local_port, payload, sizeof(payload)); @@ -914,6 +921,50 @@ START_TEST(test_udp_sendto_and_recvfrom) } END_TEST +START_TEST(test_udp_sendto_unbound_socket_receives_reply) +{ + struct wolfIP s; + int sd; + struct wolfIP_sockaddr_in sin; + struct wolfIP_sockaddr_in from; + socklen_t from_len = sizeof(from); + uint8_t payload[4] = {1, 2, 3, 4}; + uint8_t rxbuf[8] = {0}; + int ret; + ip4 local_ip = 0x0A000001U; + ip4 remote_ip = 0x0A000002U; + struct tsocket *ts; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, local_ip, 0xFFFFFF00U, 0); + + sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + ck_assert_int_gt(sd, 0); + + /* No bind: a plain socket()/sendto() client. The egress state + * (local_ip/if_idx) must still be set so a reply is accepted. */ + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = ee16(5000); + sin.sin_addr.s_addr = ee32(remote_ip); + ret = wolfIP_sock_sendto(&s, sd, payload, sizeof(payload), 0, + (struct wolfIP_sockaddr *)&sin, sizeof(sin)); + ck_assert_int_eq(ret, (int)sizeof(payload)); + + ts = &s.udpsockets[SOCKET_UNMARK(sd)]; + ck_assert_uint_gt(ts->src_port, 0); + inject_udp_datagram(&s, TEST_PRIMARY_IF, remote_ip, local_ip, 5000, + ts->src_port, payload, sizeof(payload)); + + memset(&from, 0, sizeof(from)); + ret = wolfIP_sock_recvfrom(&s, sd, rxbuf, sizeof(rxbuf), 0, + (struct wolfIP_sockaddr *)&from, &from_len); + ck_assert_int_eq(ret, (int)sizeof(payload)); + ck_assert_mem_eq(rxbuf, payload, sizeof(payload)); +} +END_TEST + START_TEST(test_udp_wildcard_bind_receives_all_local_addrs) { struct wolfIP s; @@ -3438,12 +3489,14 @@ START_TEST(test_sock_sendto_tcp_close_wait_allowed) } END_TEST -START_TEST(test_sock_sendto_udp_sets_dest_and_assigns) +START_TEST(test_sock_sendto_udp_encodes_dest_and_assigns_src_port) { struct wolfIP s; int udp_sd; struct tsocket *ts; struct wolfIP_sockaddr_in sin; + struct wolfIP_udp_datagram *udp; + struct pkt_desc *desc; uint8_t buf[4] = {1,2,3,4}; ip4 local_ip = 0x0A000001U; @@ -3464,10 +3517,15 @@ START_TEST(test_sock_sendto_udp_sets_dest_and_assigns) ck_assert_int_eq(wolfIP_sock_sendto(&s, udp_sd, buf, sizeof(buf), 0, (struct wolfIP_sockaddr *)&sin, sizeof(sin)), (int)sizeof(buf)); - ck_assert_uint_eq(ts->dst_port, 9999U); - ck_assert_uint_eq(ts->remote_ip, 0x0A000002U); ck_assert_uint_ge(ts->src_port, 1024U); - ck_assert_uint_eq(ts->local_ip, local_ip); + /* The sendto destination is encoded into the queued datagram; the + * socket is not connected, so no persistent peer is set. */ + desc = fifo_peek(&ts->sock.udp.txbuf); + ck_assert_ptr_nonnull(desc); + udp = (struct wolfIP_udp_datagram *)(ts->txmem + desc->pos + sizeof(*desc)); + ck_assert_uint_eq(ee16(udp->dst_port), 9999U); + ck_assert_uint_eq(ee32(udp->ip.dst), 0x0A000002U); + ck_assert_uint_eq(ee32(udp->ip.src), local_ip); } END_TEST @@ -3563,6 +3621,8 @@ START_TEST(test_sock_sendto_udp_primary_ip_fallback) int udp_sd; struct tsocket *ts; struct wolfIP_sockaddr_in sin; + struct wolfIP_udp_datagram *udp; + struct pkt_desc *desc; uint8_t buf[4] = {1,2,3,4}; ip4 primary_ip = 0x0A000001U; ip4 secondary_ip = 0xC0A80101U; @@ -3584,7 +3644,11 @@ START_TEST(test_sock_sendto_udp_primary_ip_fallback) ck_assert_int_eq(wolfIP_sock_sendto(&s, udp_sd, buf, sizeof(buf), 0, (struct wolfIP_sockaddr *)&sin, sizeof(sin)), (int)sizeof(buf)); - ck_assert_uint_eq(ts->local_ip, primary_ip); + /* The fallback source address is encoded into the queued datagram. */ + desc = fifo_peek(&ts->sock.udp.txbuf); + ck_assert_ptr_nonnull(desc); + udp = (struct wolfIP_udp_datagram *)(ts->txmem + desc->pos + sizeof(*desc)); + ck_assert_uint_eq(ee32(udp->ip.src), primary_ip); } END_TEST @@ -3616,6 +3680,134 @@ START_TEST(test_sock_sendto_udp_zero_port_in_addr) } END_TEST +/* A sendto to an alternate destination on a connected UDP socket must + * not steal the connected peer: the queued datagram carries the + * alternate destination, while the persistent peer and + * udp_try_recv's filter stay exactly as connect() set them. */ +START_TEST(test_udp_sendto_connected_alt_dest_keeps_peer) +{ + struct wolfIP s; + int udp_sd; + struct tsocket *ts; + struct wolfIP_sockaddr_in sin; + struct wolfIP_sockaddr_in from; + socklen_t peer_len = sizeof(struct wolfIP_sockaddr_in); + struct wolfIP_udp_datagram *udp; + struct pkt_desc *desc; + uint8_t buf[4] = {1, 2, 3, 4}; + uint8_t rxbuf[16]; + ip4 local_ip = 0x0A000001U; + ip4 peer1 = 0x0A000002U; + ip4 peer2 = 0x0A000003U; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, local_ip, 0xFFFFFF00U, 0); + + udp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + ck_assert_int_gt(udp_sd, 0); + ts = &s.udpsockets[SOCKET_UNMARK(udp_sd)]; + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = ee16(5000); + sin.sin_addr.s_addr = ee32(local_ip); + ck_assert_int_eq(wolfIP_sock_bind(&s, udp_sd, + (struct wolfIP_sockaddr *)&sin, sizeof(sin)), 0); + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = ee16(6000); + sin.sin_addr.s_addr = ee32(peer1); + ck_assert_int_eq(wolfIP_sock_connect(&s, udp_sd, + (struct wolfIP_sockaddr *)&sin, sizeof(sin)), 0); + ck_assert_uint_eq(ts->remote_ip, peer1); + ck_assert_uint_eq(ts->dst_port, 6000); + + /* Alternate-destination sendto: the datagram goes to peer2 ... */ + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = ee16(7000); + sin.sin_addr.s_addr = ee32(peer2); + ck_assert_int_eq(wolfIP_sock_sendto(&s, udp_sd, buf, sizeof(buf), 0, + (struct wolfIP_sockaddr *)&sin, sizeof(sin)), (int)sizeof(buf)); + + desc = fifo_peek(&ts->sock.udp.txbuf); + ck_assert_ptr_nonnull(desc); + udp = (struct wolfIP_udp_datagram *)(ts->txmem + desc->pos + sizeof(*desc)); + ck_assert_uint_eq(ee32(udp->ip.dst), peer2); + ck_assert_uint_eq(ee16(udp->dst_port), 7000); + + /* ... but the connected peer and its receive filter are untouched. */ + ck_assert_uint_eq(ts->remote_ip, peer1); + ck_assert_uint_eq(ts->dst_port, 6000); + + /* A reply from the connected peer is still accepted ... */ + inject_udp_datagram(&s, TEST_PRIMARY_IF, peer1, local_ip, 6000, 5000, + buf, sizeof(buf)); + ck_assert_int_eq(wolfIP_sock_can_read(&s, udp_sd), 1); + memset(&from, 0, sizeof(from)); + ck_assert_int_ge(wolfIP_sock_recvfrom(&s, udp_sd, rxbuf, sizeof(rxbuf), 0, + (struct wolfIP_sockaddr *)&from, &peer_len), (int)sizeof(buf)); + + /* ... while a datagram from the alternate peer is not. */ + inject_udp_datagram(&s, TEST_PRIMARY_IF, peer2, local_ip, 7000, 5000, + buf, sizeof(buf)); + ck_assert_int_eq(wolfIP_sock_can_read(&s, udp_sd), 0); +} +END_TEST + +/* A failed sendto (payload over the MTU) must not mutate the connected + * peer either: the destination resolution must not leave the socket's + * peer and receive filter changed when validation fails. */ +START_TEST(test_udp_sendto_connected_failed_sendto_keeps_peer) +{ + struct wolfIP s; + int udp_sd; + struct tsocket *ts; + struct wolfIP_sockaddr_in sin; + uint8_t big[1600]; + ip4 local_ip = 0x0A000001U; + ip4 peer1 = 0x0A000002U; + ip4 peer2 = 0x0A000003U; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, local_ip, 0xFFFFFF00U, 0); + + udp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + ck_assert_int_gt(udp_sd, 0); + ts = &s.udpsockets[SOCKET_UNMARK(udp_sd)]; + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = ee16(5000); + sin.sin_addr.s_addr = ee32(local_ip); + ck_assert_int_eq(wolfIP_sock_bind(&s, udp_sd, + (struct wolfIP_sockaddr *)&sin, sizeof(sin)), 0); + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = ee16(6000); + sin.sin_addr.s_addr = ee32(peer1); + ck_assert_int_eq(wolfIP_sock_connect(&s, udp_sd, + (struct wolfIP_sockaddr *)&sin, sizeof(sin)), 0); + + memset(big, 0, sizeof(big)); + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = ee16(7000); + sin.sin_addr.s_addr = ee32(peer2); + /* 1600 > ip_mtu(1522) - IP/UDP headers: the send must fail ... */ + ck_assert_int_eq(wolfIP_sock_sendto(&s, udp_sd, big, sizeof(big), 0, + (struct wolfIP_sockaddr *)&sin, sizeof(sin)), -1); + /* ... and must leave the connected peer untouched. */ + ck_assert_uint_eq(ts->remote_ip, peer1); + ck_assert_uint_eq(ts->dst_port, 6000); + ck_assert_uint_eq(fifo_len(&ts->sock.udp.txbuf), 0); +} +END_TEST + START_TEST(test_sock_sendto_udp_src_port_low_adjusts) { struct wolfIP s; @@ -3685,6 +3877,8 @@ START_TEST(test_sock_sendto_udp_local_ip_from_primary) int udp_sd; struct tsocket *ts; struct wolfIP_sockaddr_in sin; + struct wolfIP_udp_datagram *udp; + struct pkt_desc *desc; uint8_t buf[4] = {1,2,3,4}; ip4 primary_ip = 0x0A000001U; @@ -3709,7 +3903,11 @@ START_TEST(test_sock_sendto_udp_local_ip_from_primary) ck_assert_int_eq(wolfIP_sock_sendto(&s, udp_sd, buf, sizeof(buf), 0, (struct wolfIP_sockaddr *)&sin, sizeof(sin)), (int)sizeof(buf)); - ck_assert_uint_eq(ts->local_ip, primary_ip); + /* The fallback source address is encoded into the queued datagram. */ + desc = fifo_peek(&ts->sock.udp.txbuf); + ck_assert_ptr_nonnull(desc); + udp = (struct wolfIP_udp_datagram *)(ts->txmem + desc->pos + sizeof(*desc)); + ck_assert_uint_eq(ee32(udp->ip.src), primary_ip); } END_TEST diff --git a/src/test/unit/unit_tests_ip_arp_recv.c b/src/test/unit/unit_tests_ip_arp_recv.c index 0bd3db23..ba28c9b7 100644 --- a/src/test/unit/unit_tests_ip_arp_recv.c +++ b/src/test/unit/unit_tests_ip_arp_recv.c @@ -1393,6 +1393,206 @@ START_TEST(test_forward_ttl_exceeded_copies_orig_tos) } END_TEST +/* ========================================================================= + * ip_recv: DF-set datagram exceeding the egress MTU - Fragmentation Needed + * ========================================================================= + * Branch: ee16(ip->len) > egress IP MTU and DF set -> wolfIP_send_frag_needed + * (ICMP 3/4 carrying the egress next-hop MTU), the datagram is not relayed. + */ +START_TEST(test_ip_recv_forward_df_oversize_sends_frag_needed) +{ + struct wolfIP s; + uint8_t frame[ETH_HEADER_LEN + IP_HEADER_LEN + 580]; + struct wolfIP_ip_packet *ip = (struct wolfIP_ip_packet *)frame; + ip4 primary_ip = 0x0A000001U; + ip4 secondary_ip = 0xC0A80101U; + ip4 dest_ip = 0xC0A80155U; + ip4 src_ip = 0x0A000002U; + static const uint8_t dest_mac[6] = {0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF}; + struct wolfIP_icmp_packet *ic; + + setup_stack_with_two_ifaces(&s, primary_ip, secondary_ip); + wolfIP_filter_set_callback(NULL, NULL); + /* Egress frame budget 590 bytes: IP MTU 576 (the IPv4 minimum). */ + s.ll_dev[TEST_SECOND_IF].mtu = 590; + + arp_store_neighbor(&s, TEST_SECOND_IF, dest_ip, dest_mac); + last_frame_sent_size = 0; + + memset(frame, 0, sizeof(frame)); + memcpy(ip->eth.dst, s.ll_dev[TEST_PRIMARY_IF].mac, 6); + memcpy(ip->eth.src, "\x01\x02\x03\x04\x05\x06", 6); + ip->eth.type = ee16(ETH_TYPE_IP); + ip->ver_ihl = 0x45; + ip->flags_fo = ee16(0x4000U); /* DF set */ + ip->ttl = 64; + ip->proto = WI_IPPROTO_UDP; + ip->len = ee16(IP_HEADER_LEN + 580); /* 600 > egress MTU 576 */ + ip->src = ee32(src_ip); + ip->dst = ee32(dest_ip); + fix_ip_checksum(ip); + + ip_recv(&s, TEST_PRIMARY_IF, ip, (uint32_t)sizeof(frame)); + + /* Only the Fragmentation Needed reply is transmitted; the datagram + * itself is not relayed. */ + ck_assert_uint_eq(last_frame_sent_count, 1); + /* 14 ETH + 20 IP + 8 ICMP + 28 quoted (20 header + 8 payload). */ + ck_assert_uint_eq(last_frame_sent_size, + (uint32_t)(ETH_HEADER_LEN + IP_HEADER_LEN + 8 + IP_HEADER_LEN + 8)); + ck_assert_uint_eq(last_frame_sent[ETH_HEADER_LEN + IP_HEADER_LEN], + ICMP_DEST_UNREACH); + ck_assert_uint_eq(last_frame_sent[ETH_HEADER_LEN + IP_HEADER_LEN + 1], + ICMP_FRAG_NEEDED); + /* Next-hop MTU (network order): 576 = 0x0240. */ + ck_assert_uint_eq(last_frame_sent[ETH_HEADER_LEN + IP_HEADER_LEN + 6], 0x02); + ck_assert_uint_eq(last_frame_sent[ETH_HEADER_LEN + IP_HEADER_LEN + 7], 0x40); + /* Reply carries DF, TTL 64, from the ingress interface to the + * datagram's source. */ + ck_assert_uint_eq(last_frame_sent[ETH_HEADER_LEN + 6], 0x40); + ck_assert_uint_eq(last_frame_sent[ETH_HEADER_LEN + 8], 64); + ck_assert_uint_eq(last_frame_sent[ETH_HEADER_LEN + 12], (primary_ip >> 24) & 0xFF); + ck_assert_uint_eq(last_frame_sent[ETH_HEADER_LEN + 16], (src_ip >> 24) & 0xFF); + /* Quoted original: version/IHL and source address. */ + ck_assert_uint_eq(last_frame_sent[ETH_HEADER_LEN + IP_HEADER_LEN + 8], 0x45); + ck_assert_uint_eq(last_frame_sent[ETH_HEADER_LEN + IP_HEADER_LEN + 8 + 12], + (src_ip >> 24) & 0xFF); + /* The ICMP checksum must cover the next-hop MTU field. Pass the frame + * start: icmp_checksum() skips the eth+ip prefix of a + * wolfIP_icmp_packet before summing. */ + ic = (struct wolfIP_icmp_packet *)(last_frame_sent + + ETH_HEADER_LEN + IP_HEADER_LEN); + ck_assert_uint_eq(ic->csum, ee16(icmp_checksum( + (struct wolfIP_icmp_packet *)last_frame_sent, + (uint16_t)(8 + IP_HEADER_LEN + 8)))); +} +END_TEST + +/* ========================================================================= + * ip_recv: DF-clear datagram exceeding the egress MTU - silent drop + * ========================================================================= + * Branch: oversized but DF clear -> no Fragmentation Needed (fragmentation + * is a separate concern); the datagram is dropped on transmit. + */ +START_TEST(test_ip_recv_forward_nodf_oversize_dropped) +{ + struct wolfIP s; + uint8_t frame[ETH_HEADER_LEN + IP_HEADER_LEN + 580]; + struct wolfIP_ip_packet *ip = (struct wolfIP_ip_packet *)frame; + ip4 primary_ip = 0x0A000001U; + ip4 secondary_ip = 0xC0A80101U; + ip4 dest_ip = 0xC0A80155U; + ip4 src_ip = 0x0A000002U; + static const uint8_t dest_mac[6] = {0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF}; + + setup_stack_with_two_ifaces(&s, primary_ip, secondary_ip); + wolfIP_filter_set_callback(NULL, NULL); + s.ll_dev[TEST_SECOND_IF].mtu = 590; + + arp_store_neighbor(&s, TEST_SECOND_IF, dest_ip, dest_mac); + last_frame_sent_size = 0; + + memset(frame, 0, sizeof(frame)); + memcpy(ip->eth.dst, s.ll_dev[TEST_PRIMARY_IF].mac, 6); + memcpy(ip->eth.src, "\x01\x02\x03\x04\x05\x06", 6); + ip->eth.type = ee16(ETH_TYPE_IP); + ip->ver_ihl = 0x45; + ip->flags_fo = 0; /* DF clear */ + ip->ttl = 64; + ip->proto = WI_IPPROTO_UDP; + ip->len = ee16(IP_HEADER_LEN + 580); + ip->src = ee32(src_ip); + ip->dst = ee32(dest_ip); + fix_ip_checksum(ip); + + ip_recv(&s, TEST_PRIMARY_IF, ip, (uint32_t)sizeof(frame)); + + /* Dropped on transmit, no ICMP of any kind. */ + ck_assert_uint_eq(last_frame_sent_count, 0); +} +END_TEST + +/* ========================================================================= + * ip_recv: DF-set datagram exactly at the egress MTU - forwarded + * ========================================================================= + * Branch: ee16(ip->len) == egress IP MTU (not >) -> normal forward, no + * Fragmentation Needed. + */ +START_TEST(test_ip_recv_forward_df_at_mtu_forwarded) +{ + struct wolfIP s; + uint8_t frame[ETH_HEADER_LEN + IP_HEADER_LEN + 556]; + struct wolfIP_ip_packet *ip = (struct wolfIP_ip_packet *)frame; + ip4 primary_ip = 0x0A000001U; + ip4 secondary_ip = 0xC0A80101U; + ip4 dest_ip = 0xC0A80155U; + ip4 src_ip = 0x0A000002U; + static const uint8_t dest_mac[6] = {0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF}; + + setup_stack_with_two_ifaces(&s, primary_ip, secondary_ip); + wolfIP_filter_set_callback(NULL, NULL); + s.ll_dev[TEST_SECOND_IF].mtu = 590; + + arp_store_neighbor(&s, TEST_SECOND_IF, dest_ip, dest_mac); + last_frame_sent_size = 0; + + memset(frame, 0, sizeof(frame)); + memcpy(ip->eth.dst, s.ll_dev[TEST_PRIMARY_IF].mac, 6); + memcpy(ip->eth.src, "\x01\x02\x03\x04\x05\x06", 6); + ip->eth.type = ee16(ETH_TYPE_IP); + ip->ver_ihl = 0x45; + ip->flags_fo = ee16(0x4000U); /* DF set, but it fits */ + ip->ttl = 64; + ip->proto = WI_IPPROTO_UDP; + ip->len = ee16(IP_HEADER_LEN + 556); /* exactly 576 */ + ip->src = ee32(src_ip); + ip->dst = ee32(dest_ip); + fix_ip_checksum(ip); + + ip_recv(&s, TEST_PRIMARY_IF, ip, (uint32_t)sizeof(frame)); + + /* Forwarded as-is (TTL decremented), no ICMP generated. */ + ck_assert_uint_eq(last_frame_sent_count, 1); + ck_assert_uint_eq(last_frame_sent_size, (uint32_t)sizeof(frame)); + ck_assert_uint_eq(last_frame_sent[ETH_HEADER_LEN + 8], 63); +} +END_TEST + +START_TEST(test_ip_recv_forward_directed_bcast_ingress_net_not_forwarded) +{ + struct wolfIP s; + uint8_t frame[ETH_HEADER_LEN + IP_HEADER_LEN + 8]; + struct wolfIP_ip_packet *ip = (struct wolfIP_ip_packet *)frame; + ip4 primary_ip = 0x0A000001U; /* 10.0.0.1/24, ingress */ + ip4 secondary_ip = 0xC0A80101U; /* 192.168.1.1/24 */ + ip4 dest_ip = 0x0A0000FFU; /* 10.0.0.255 directed bcast */ + ip4 src_ip = 0x0A000002U; /* 10.0.0.2 on the ingress net */ + + setup_stack_with_two_ifaces(&s, primary_ip, secondary_ip); + wolfIP_filter_set_callback(NULL, NULL); + last_frame_sent_size = 0; + + memset(frame, 0, sizeof(frame)); + memcpy(ip->eth.dst, s.ll_dev[TEST_PRIMARY_IF].mac, 6); + memcpy(ip->eth.src, "\x01\x02\x03\x04\x05\x06", 6); + ip->eth.type = ee16(ETH_TYPE_IP); + ip->ver_ihl = 0x45; + ip->flags_fo = ee16(0x4000U); + ip->ttl = 64; + ip->proto = WI_IPPROTO_UDP; + ip->len = ee16(IP_HEADER_LEN + 8); + ip->src = ee32(src_ip); + ip->dst = ee32(dest_ip); + fix_ip_checksum(ip); + + ip_recv(&s, TEST_PRIMARY_IF, ip, (uint32_t)sizeof(frame)); + + /* A directed broadcast for the ingress's own network has no non-ingress + * egress, so it is not relayed back out (no loop). */ + ck_assert_uint_eq(last_frame_sent_count, 0); +} +END_TEST + /* ========================================================================= * ip_recv: dest matches own IP on secondary interface → is_local=1, no fwd * ========================================================================= diff --git a/src/test/unit/unit_tests_proto.c b/src/test/unit/unit_tests_proto.c index 385dc81c..024ea2cf 100644 --- a/src/test/unit/unit_tests_proto.c +++ b/src/test/unit/unit_tests_proto.c @@ -5070,7 +5070,8 @@ START_TEST(test_ip_output_add_header) { t.S = &S; // Run the function for a TCP packet - result = ip_output_add_header(&t, ip, WI_IPPROTO_TCP, 40); + result = ip_output_add_header(&t, ip, WI_IPPROTO_TCP, t.local_ip, + t.remote_ip, 40); ck_assert_int_eq(result, 0); // Validate IP header fields @@ -5107,7 +5108,8 @@ START_TEST(test_ip_output_add_header_icmp) t.if_idx = TEST_PRIMARY_IF; mock_link_init(&S); - result = ip_output_add_header(&t, ip, WI_IPPROTO_ICMP, IP_HEADER_LEN + ICMP_HEADER_LEN); + result = ip_output_add_header(&t, ip, WI_IPPROTO_ICMP, t.local_ip, + t.remote_ip, IP_HEADER_LEN + ICMP_HEADER_LEN); ck_assert_int_eq(result, 0); icmp = (struct wolfIP_icmp_packet *)ip; @@ -7357,7 +7359,8 @@ START_TEST(test_regression_udp_checksum_zero_substituted_with_ffff) udp.csum = 0; ip_output_add_header(ts, (struct wolfIP_ip_packet *)&udp, - WI_IPPROTO_UDP, IP_HEADER_LEN + 8); + WI_IPPROTO_UDP, ts->local_ip, ts->remote_ip, + IP_HEADER_LEN + 8); /* The stored checksum must be 0xFFFF, not 0. */ ck_assert_uint_ne(udp.csum, 0); diff --git a/src/test/unit/unit_tests_socket_api_arms.c b/src/test/unit/unit_tests_socket_api_arms.c index ab4763cf..2cf5f980 100644 --- a/src/test/unit/unit_tests_socket_api_arms.c +++ b/src/test/unit/unit_tests_socket_api_arms.c @@ -206,6 +206,31 @@ START_TEST(test_sock_can_write_tcp_closed_returns_one) } END_TEST +START_TEST(test_sock_can_write_tcp_close_wait_full_fifo_returns_zero) +{ + struct wolfIP s; + int sd; + struct tsocket *ts; + + wolfIP_init(&s); + mock_link_init(&s); + sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_STREAM, 0); + ck_assert_int_ge(sd, 0); + ts = &s.tcpsockets[SOCKET_UNMARK(sd)]; + ts->sock.tcp.state = TCP_CLOSE_WAIT; + + /* Writable while the TX FIFO has space, as in ESTABLISHED. */ + ck_assert_int_eq(wolfIP_sock_can_write(&s, sd), 1); + + /* A full FIFO makes a CLOSE_WAIT send return -WOLFIP_EAGAIN, so + * can_write must agree and report 0 (callers poll it to decide + * whether to send). */ + while (enqueue_tcp_tx(ts, 16, TCP_FLAG_ACK | TCP_FLAG_PSH) == 0) { + } + ck_assert_int_eq(wolfIP_sock_can_write(&s, sd), 0); +} +END_TEST + START_TEST(test_sock_can_write_tcp_invalid_fd) { struct wolfIP s; diff --git a/src/test/unit/unit_tests_vlan.c b/src/test/unit/unit_tests_vlan.c index 5e492799..8926b478 100644 --- a/src/test/unit/unit_tests_vlan.c +++ b/src/test/unit/unit_tests_vlan.c @@ -624,6 +624,76 @@ START_TEST(test_vlan_api_delete_bad_ifidx_rejected) } END_TEST +/* Regression (F-13166): deleting a VLAN while interface-dependent state still + * references its index must be rejected. wolfIP_vlan_create reuses the freed + * slot, so a stale route/mcast membership/socket on that index would silently + * operate through a newly created VLAN (wrong-VLAN traffic and membership + * reports). Each test adds one dependency, expects -WOLFIP_EBUSY, releases it, + * and expects the delete to succeed. */ + +#if WOLFIP_ENABLE_FORWARDING +START_TEST(test_vlan_delete_rejected_with_route) +{ + struct wolfIP s; + unsigned int sub_idx = 0; + int ret; + + setup_vlan_stack(&s); + + ret = wolfIP_vlan_create(&s, TEST_PRIMARY_IF, 100, 0, 0, &sub_idx); + ck_assert_int_eq(ret, 0); + + /* A route via the VLAN keeps it busy. */ + ret = wolfIP_route_add(&s, sub_idx, 0x0A010000U, 16, 0); + ck_assert_int_eq(ret, 0); + + ret = wolfIP_vlan_delete(&s, sub_idx); + ck_assert_int_eq(ret, -WOLFIP_EBUSY); + + /* The VLAN survives a rejected delete. */ + ck_assert_uint_eq(s.ll_dev[sub_idx].vlan_active, 1U); + + /* Release the route; deletion now succeeds. */ + ret = wolfIP_route_delete(&s, sub_idx, 0x0A010000U, 16); + ck_assert_int_eq(ret, 0); + ret = wolfIP_vlan_delete(&s, sub_idx); + ck_assert_int_eq(ret, 0); +} +END_TEST +#endif /* WOLFIP_ENABLE_FORWARDING */ + +START_TEST(test_vlan_delete_rejected_with_socket) +{ + struct wolfIP s; + unsigned int sub_idx = 0; + int sd; + struct tsocket *ts; + int ret; + + setup_vlan_stack(&s); + + ret = wolfIP_vlan_create(&s, TEST_PRIMARY_IF, 100, 0, 0, &sub_idx); + ck_assert_int_eq(ret, 0); + + /* A socket bound to the VLAN keeps it busy. */ + sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + ck_assert_int_gt(sd, 0); + ts = &s.udpsockets[SOCKET_UNMARK(sd)]; + ts->if_idx = (uint8_t)sub_idx; + + ret = wolfIP_vlan_delete(&s, sub_idx); + ck_assert_int_eq(ret, -WOLFIP_EBUSY); + + /* The socket survives a rejected delete (it belongs to the app). */ + ck_assert_uint_eq(ts->if_idx, (uint8_t)sub_idx); + + /* Closing the socket releases the dependency; deletion now succeeds. */ + ck_assert_int_eq(wolfIP_sock_close(&s, sd), 0); + ret = wolfIP_vlan_delete(&s, sub_idx); + ck_assert_int_eq(ret, 0); +} +END_TEST + /* Regression: wolfIP_vlan_get used to default *parent_if_idx to 0 if the * parent pointer didn't match any slot in ll_dev[], silently reporting the * wrong parent. After the fix it must return -WOLFIP_EINVAL and leave the diff --git a/src/wolfip.c b/src/wolfip.c index 68ff59e3..b5fb3bf8 100644 --- a/src/wolfip.c +++ b/src/wolfip.c @@ -1347,7 +1347,7 @@ static inline uint32_t tcp_seq_inc(uint32_t seq, uint32_t n); static inline int tcp_seq_leq(uint32_t a, uint32_t b); static inline int tcp_seq_lt(uint32_t a, uint32_t b); static int ip_output_add_header(struct tsocket *t, struct wolfIP_ip_packet *ip, - uint8_t proto, uint16_t len); + uint8_t proto, ip4 src_ip, ip4 dst_ip, uint16_t len); static void tcp_persist_cb(void *arg); static void tcp_persist_start(struct tsocket *t, uint64_t now); static void tcp_persist_stop(struct tsocket *t); @@ -2397,6 +2397,134 @@ static void wolfIP_send_ttl_exceeded(struct wolfIP *s, unsigned int if_idx, } #endif +#if WOLFIP_ENABLE_FORWARDING && defined(ETHERNET) +/* RFC 1812 4.3.2.4: a router that cannot relay a DF-set datagram because it + * exceeds the egress MTU answers the source with Fragmentation Needed + * (code 4); the 32-bit next-hop field after the checksum carries the MTU of + * the egress link. The reply goes out the interface the datagram arrived on, + * addressed to the datagram's source (the sender is attached to that link). + */ +static void wolfIP_send_frag_needed(struct wolfIP *s, unsigned int in_if, + unsigned int out_if, + struct wolfIP_ip_packet *orig) +{ + struct wolfIP_ll_dev *ll = wolfIP_ll_at(s, in_if); + 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_total; + uint32_t orig_copy; + uint32_t icmp_data_len; + uint32_t frame_len; + uint16_t mtu_net; +#if !CONFIG_IPFILTER + (void)icmp_pkt; +#endif + if (!ll) + return; +#if WOLFIP_VLAN + /* Same interface-validity rule as wolfIP_ll_send_frame: an active VLAN + * sub-iface has a NULL send and delegates to its parent. */ + if (ll->vlan_active) { + if (!ll->vlan_parent) + return; + } else if (!ll->send) { + return; + } +#else + if (!ll->send) + return; +#endif + if (orig_ihl < IP_HEADER_LEN) + orig_ihl = IP_HEADER_LEN; + /* RFC 1812 4.3.2.7: an ICMP error MUST NOT be originated in response to + * another ICMP error (type 3, 4, 5, 11, 12). A zero-payload ICMP cannot + * be an error, so it is never suppressed. */ + if (orig->proto == WI_IPPROTO_ICMP && ee16(orig->len) > orig_ihl) { + uint8_t orig_type = *(((uint8_t *)orig) + ETH_HEADER_LEN + orig_ihl); + if (orig_type == ICMP_DEST_UNREACH || orig_type == ICMP_FRAG_NEEDED || + orig_type == 5 /* Redirect */ || orig_type == ICMP_TTL_EXCEEDED || + orig_type == 12 /* Parameter Problem */) + return; + } + /* Quote the original header plus up to 8 payload bytes, or as much of + * the datagram as exists. */ + orig_total = ee16(orig->len); + if (orig_total < orig_ihl) + return; /* malformed: IP total length smaller than the header */ + orig_copy = orig_ihl + 8; + if (orig_copy > orig_total) + orig_copy = orig_total; + 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 + quoted packet */ + /* Next-hop MTU: the IP MTU (max IP datagram size, header included) of + * the egress link the datagram was to be relayed on (network order in + * the field's two low bytes). Set before the checksum: the field lies + * inside the ICMP checksummed range. */ + mtu_net = ee16((uint16_t)(wolfIP_frame_mtu(s, out_if) - ETH_HEADER_LEN)); + memcpy(&icmp.unused[2], &mtu_net, sizeof(mtu_net)); + icmp.type = ICMP_DEST_UNREACH; + icmp.code = ICMP_FRAG_NEEDED; + /* RFC 1812 4.3.2.5: the error carries the triggering packet's TOS. */ + icmp.ip.tos = orig->tos; + memcpy(icmp.orig_packet, ((uint8_t *)orig) + ETH_HEADER_LEN, orig_copy); + icmp.csum = ee16(icmp_checksum((struct wolfIP_icmp_packet *)&icmp, + icmp_data_len)); + icmp.ip.ver_ihl = 0x45; + icmp.ip.flags_fo = ee16(0x4000U); + icmp.ip.ttl = 64; + icmp.ip.proto = WI_IPPROTO_ICMP; + icmp.ip.id = ipcounter_next(s); + icmp.ip.len = ee16((uint16_t)(IP_HEADER_LEN + icmp_data_len)); + icmp.ip.src = ee32(wolfIP_ipconf_at(s, in_if)->ip); + icmp.ip.dst = orig->src; + icmp.ip.csum = 0; + iphdr_set_checksum(&icmp.ip); + frame_len = ETH_HEADER_LEN + IP_HEADER_LEN + icmp_data_len; + if (!wolfIP_ll_is_non_ethernet(s, in_if)) { + eth_output_add_header(s, in_if, orig->eth.src, &icmp.ip.eth, ETH_TYPE_IP); + } + if (wolfIP_filter_notify_icmp(WOLFIP_FILT_SENDING, s, in_if, icmp_pkt, + frame_len, IP_HEADER_LEN) != 0) + return; + if (wolfIP_filter_notify_ip(WOLFIP_FILT_SENDING, s, in_if, &icmp.ip, frame_len) != 0) + return; + if (!wolfIP_ll_is_non_ethernet(s, in_if)) { + if (wolfIP_filter_notify_eth(WOLFIP_FILT_SENDING, s, in_if, &icmp.ip.eth, frame_len) != 0) + return; + } +#ifdef WOLFIP_ESP + if (!wolfIP_ll_is_non_ethernet(s, in_if)) { + struct wolfIP_ll_dev *esp_ll = ll; +#if WOLFIP_VLAN + /* A VLAN sub-iface has no send function of its own; esp_send needs + * the physical device's send path. */ + if (ll->vlan_active && ll->vlan_parent) + esp_ll = ll->vlan_parent; +#endif + if (esp_send(esp_ll, &icmp.ip, (uint16_t)(frame_len - ETH_HEADER_LEN)) == 1) { + wolfIP_ll_send_frame(s, in_if, &icmp, frame_len); + } + } else { + wolfIP_ll_send_frame(s, in_if, &icmp, frame_len); + } +#else + wolfIP_ll_send_frame(s, in_if, &icmp, frame_len); +#endif +} +#elif WOLFIP_ENABLE_FORWARDING +static void wolfIP_send_frag_needed(struct wolfIP *s, unsigned int in_if, + unsigned int out_if, + struct wolfIP_ip_packet *orig) +{ + (void)s; + (void)in_if; + (void)out_if; + (void)orig; +} +#endif + #ifdef ETHERNET static void wolfIP_send_port_unreachable(struct wolfIP *s, unsigned int if_idx, struct wolfIP_ip_packet *orig) @@ -3649,6 +3777,7 @@ static int tcp_send_empty_immediate(struct tsocket *t, struct wolfIP_tcp_seg *tc tcp->ack = ee32(t->sock.tcp.ack); tcp->win = ee16(tcp_adv_win(t, 1)); ip_output_add_header(t, (struct wolfIP_ip_packet *)tcp, WI_IPPROTO_TCP, + t->local_ip, t->remote_ip, (uint16_t)(frame_len - ETH_HEADER_LEN)); #ifdef ETHERNET if (!wolfIP_ll_is_non_ethernet(t->S, tx_if)) @@ -4332,6 +4461,7 @@ static int tcp_send_zero_wnd_probe(struct tsocket *t) } #endif ip_output_add_header(t, (struct wolfIP_ip_packet *)probe, WI_IPPROTO_TCP, + t->local_ip, t->remote_ip, (uint16_t)(IP_HEADER_LEN + TCP_HEADER_LEN + opt_len + 1)); #ifdef ETHERNET if (!wolfIP_ll_is_non_ethernet(t->S, tx_if)) @@ -4877,20 +5007,20 @@ static void wolfIP_forward_packet(struct wolfIP *s, unsigned int out_if, #endif static int ip_output_add_header(struct tsocket *t, struct wolfIP_ip_packet *ip, - uint8_t proto, uint16_t len) + uint8_t proto, ip4 src_ip, ip4 dst_ip, uint16_t len) { union transport_pseudo_header ph; memset(&ph, 0, sizeof(ph)); memset(ip, 0, sizeof(struct wolfIP_ip_packet)); - ip->src = ee32(t->local_ip); - ip->dst = ee32(t->remote_ip); + ip->src = ee32(src_ip); + ip->dst = ee32(dst_ip); ip->ver_ihl = 0x45; ip->tos = t->tos; ip->len = ee16(len); ip->flags_fo = (proto == WI_IPPROTO_TCP) ? ee16(0x4000U) : 0; ip->ttl = 64; #ifdef IP_MULTICAST - if (proto == WI_IPPROTO_UDP && wolfIP_ip_is_multicast(t->remote_ip)) + if (proto == WI_IPPROTO_UDP && wolfIP_ip_is_multicast(dst_ip)) ip->ttl = t->sock.udp.mcast_ttl; #endif ip->proto = proto; @@ -6927,6 +7057,9 @@ int wolfIP_sock_sendto(struct wolfIP *s, int sockfd, const void *buf, size_t len const struct wolfIP_sockaddr_in *sin = (const struct wolfIP_sockaddr_in *)dest_addr; unsigned int if_idx; struct ipconf *conf; + uint16_t dst_port; + ip4 remote_ip; + ip4 src_ip; uint32_t ip_mtu; uint32_t frame_len; if (SOCKET_UNMARK(sockfd) >= MAX_UDPSOCKETS) @@ -6936,13 +7069,19 @@ int wolfIP_sock_sendto(struct wolfIP *s, int sockfd, const void *buf, size_t len if ((ts->dst_port == 0) && (dest_addr == NULL)) return -1; memset(udp, 0, sizeof(struct wolfIP_udp_datagram)); + /* Per-datagram addressing: an explicit sendto destination applies + * to this datagram only. The connected peer and udp_try_recv's + * filter are set only by connect(), and must survive this send + * even when the validation below fails. */ + dst_port = ts->dst_port; + remote_ip = ts->remote_ip; if (sin) { if (addrlen < sizeof(struct wolfIP_sockaddr_in)) return -1; - ts->dst_port = ee16(sin->sin_port); - ts->remote_ip = ee32(sin->sin_addr.s_addr); + dst_port = ee16(sin->sin_port); + remote_ip = ee32(sin->sin_addr.s_addr); } - if ((ts->dst_port==0) || (ts->remote_ip==0)) + if ((dst_port == 0) || (remote_ip == 0)) return -1; if (ts->src_port == 0) { ts->src_port = port_alloc_random(s->udpsockets, MAX_UDPSOCKETS, @@ -6950,23 +7089,30 @@ int wolfIP_sock_sendto(struct wolfIP *s, int sockfd, const void *buf, size_t len if (ts->src_port == 0) return -WOLFIP_EAGAIN; } - if_idx = wolfIP_route_for_ip(s, ts->remote_ip); + if_idx = wolfIP_route_for_ip(s, remote_ip); #ifdef IP_MULTICAST - if (wolfIP_ip_is_multicast(ts->remote_ip) && ts->sock.udp.mcast_if_set) + if (wolfIP_ip_is_multicast(remote_ip) && ts->sock.udp.mcast_if_set) if_idx = ts->sock.udp.mcast_if_idx; #endif conf = wolfIP_ipconf_at(s, if_idx); - ts->if_idx = (uint8_t)if_idx; - if (ts->local_ip == 0) { + src_ip = ts->local_ip; + if (src_ip == 0) { if (conf && conf->ip != IPADDR_ANY) - ts->local_ip = conf->ip; + src_ip = conf->ip; else { struct ipconf *primary = wolfIP_primary_ipconf(s); if (primary && primary->ip != IPADDR_ANY) - ts->local_ip = primary->ip; + src_ip = primary->ip; } } - ip_mtu = wolfIP_socket_ip_mtu(ts); + /* Bind the socket's egress state once for any sendto (connected or + * not): an unbound socket that sends to an explicit destination must + * still get a local address + egress iface so replies are accepted + * (udp_try_recv bound_match) and getsockname is sane. */ + if (ts->local_ip == 0) + ts->local_ip = src_ip; + ts->if_idx = (uint8_t)if_idx; + ip_mtu = wolfIP_ip_mtu(s, if_idx); if (ip_mtu <= (IP_HEADER_LEN + UDP_HEADER_LEN) || len > ip_mtu - IP_HEADER_LEN - UDP_HEADER_LEN) return -1; /* Fragmentation not supported */ @@ -6976,17 +7122,24 @@ int wolfIP_sock_sendto(struct wolfIP *s, int sockfd, const void *buf, size_t len } udp->src_port = ee16(ts->src_port); - udp->dst_port = ee16(ts->dst_port); + udp->dst_port = ee16(dst_port); udp->len = ee16(len + UDP_HEADER_LEN); udp->csum = 0; memcpy(udp->data, buf, len); - /* Pin the IP header to this datagram's destination/source while the - * socket's routing state still matches it; the flush only adds the - * link-layer header. */ - ip_output_add_header(ts, &udp->ip, WI_IPPROTO_UDP, + /* Pin the IP header to this datagram's destination/source; the + * flush only adds the link-layer header. */ + ip_output_add_header(ts, &udp->ip, WI_IPPROTO_UDP, src_ip, remote_ip, (uint16_t)(frame_len - ETH_HEADER_LEN)); if (fifo_push(&ts->sock.udp.txbuf, udp, frame_len) < 0) return -WOLFIP_EAGAIN; + if (sin && !ts->sock.udp.connected) { + /* An unconnected socket adopts the explicit destination as + * its last destination for subsequent plain sends (DHCP/DNS + * rely on it). A connected socket's peer is set only by + * connect() and survives a per-datagram sendto address. */ + ts->dst_port = dst_port; + ts->remote_ip = remote_ip; + } return len; } else if (IS_SOCKET_ICMP(sockfd)) { const struct wolfIP_sockaddr_in *sin = (const struct wolfIP_sockaddr_in *)dest_addr; @@ -7050,6 +7203,7 @@ int wolfIP_sock_sendto(struct wolfIP *s, int sockfd, const void *buf, size_t len * destination/source at enqueue time; the flush only adds the * link-layer header. */ ip_output_add_header(ts, &icmp->ip, WI_IPPROTO_ICMP, + ts->local_ip, ts->remote_ip, (uint16_t)(frame_len - ETH_HEADER_LEN)); if (fifo_push(&ts->sock.udp.txbuf, icmp, frame_len) < 0) return -WOLFIP_EAGAIN; @@ -8109,7 +8263,11 @@ int wolfIP_sock_can_write(struct wolfIP *s, int sockfd) return -WOLFIP_EINVAL; if (ts->sock.tcp.state == TCP_SYN_SENT) return 0; - if (ts->sock.tcp.state != TCP_ESTABLISHED) + /* Only ESTABLISHED and CLOSE_WAIT accept data from send(), so both + * must reflect actual TX capacity; every other state keeps its + * fixed readiness. */ + if (ts->sock.tcp.state != TCP_ESTABLISHED && + ts->sock.tcp.state != TCP_CLOSE_WAIT) return 1; return tx_has_writable_space(ts) ? 1 : 0; } @@ -10212,10 +10370,45 @@ int wolfIP_vlan_create(struct wolfIP *s, unsigned int parent_if_idx, int wolfIP_vlan_delete(struct wolfIP *s, unsigned int if_idx) { struct wolfIP_ll_dev *slot; + int i; if (!s) return -WOLFIP_EINVAL; if (if_idx >= s->if_count) return -WOLFIP_EINVAL; slot = &s->ll_dev[if_idx]; if (!slot->vlan_active || !slot->vlan_parent) return -WOLFIP_EINVAL; + /* Reject deletion while interface-dependent state still references this + * index: wolfIP_vlan_create reuses the freed slot, so a surviving route, + * multicast membership, or socket on this index would silently operate + * through a newly created VLAN (wrong-VLAN traffic and membership + * reports). The caller must release those dependencies first. */ +#if WOLFIP_ENABLE_FORWARDING + for (i = 0; i < (int)WOLFIP_MAX_ROUTES; i++) { + if (s->routes[i].used && s->routes[i].if_idx == (uint8_t)if_idx) + return -WOLFIP_EBUSY; + } +#endif +#ifdef IP_MULTICAST + for (i = 0; i < WOLFIP_MCAST_MEMBERSHIPS; i++) { + if (s->mcast[i].refs != 0 && s->mcast[i].if_idx == (uint8_t)if_idx) + return -WOLFIP_EBUSY; + } +#endif + for (i = 0; i < MAX_TCPSOCKETS; i++) { + if (s->tcpsockets[i].proto != 0 && + s->tcpsockets[i].if_idx == (uint8_t)if_idx) + return -WOLFIP_EBUSY; + } + for (i = 0; i < MAX_UDPSOCKETS; i++) { + if (s->udpsockets[i].proto != 0 && + s->udpsockets[i].if_idx == (uint8_t)if_idx) + return -WOLFIP_EBUSY; + } +#if WOLFIP_RAWSOCKETS + for (i = 0; i < WOLFIP_MAX_RAWSOCKETS; i++) { + if (s->rawsockets[i].used && + s->rawsockets[i].if_idx == (uint8_t)if_idx) + return -WOLFIP_EBUSY; + } +#endif /* Wipe the slot so it can be reused. s->if_count is not changed to avoid * renumbering active sub-ifaces. */ memset(slot, 0, sizeof(*slot)); @@ -10462,7 +10655,14 @@ static inline void ip_recv(struct wolfIP *s, unsigned int if_idx, if (!wolfIP_ll_is_non_ethernet(s, if_idx) && (ip->eth.dst[0] & 0x01)) l2_group = 1; #endif - if (dest == IPADDR_ANY || wolfIP_ip_is_broadcast(s, dest)) { + if (dest == IPADDR_ANY) { + /* Limited broadcast: local-only, never forwarded. */ + is_local = 1; + } else if (wolfIP_ip_is_broadcast(s, dest)) { + /* Directed broadcast: local-only. The stack holds an address on + * the network, so local (wildcard) sockets receive it; relaying + * directed broadcasts is a smurf/amplification vector (RFC 2644) + * and is intentionally not forwarded. */ is_local = 1; } else { for (i = 0; i < s->if_count; i++) { @@ -10568,6 +10768,17 @@ static inline void ip_recv(struct wolfIP *s, unsigned int if_idx, wolfIP_send_ttl_exceeded(s, if_idx, ip); return; } + /* A datagram larger than the egress IP MTU cannot be relayed. + * With DF set, RFC 1812 4.3.2.4 requires a Fragmentation + * Needed reply carrying the egress next-hop MTU instead of a + * silent drop; a DF-clear datagram that does not fit is + * still dropped on transmit. */ + if (ee16(ip->len) > + (wolfIP_frame_mtu(s, (unsigned int)out_if) - ETH_HEADER_LEN) && + (ee16(ip->flags_fo) & 0x4000U) != 0U) { + wolfIP_send_frag_needed(s, if_idx, (unsigned int)out_if, ip); + return; + } if (!wolfIP_forward_prepare(s, out_if, next_hop, mac, &broadcast)) { arp_queue_packet(s, out_if, next_hop, ip, fwd_len); @@ -11686,7 +11897,8 @@ static void flush_tcp_tx(struct wolfIP *s, uint64_t now) ts->sock.tcp.last_ack = ts->sock.tcp.ack; tcp->ack = ee32(ts->sock.tcp.ack); tcp->win = ee16(tcp_adv_win(ts, 1)); - ip_output_add_header(ts, (struct wolfIP_ip_packet *)tcp, WI_IPPROTO_TCP, size); + ip_output_add_header(ts, (struct wolfIP_ip_packet *)tcp, WI_IPPROTO_TCP, + ts->local_ip, ts->remote_ip, size); #ifdef ETHERNET if (!wolfIP_ll_is_non_ethernet(ts->S, tx_if)) eth_output_add_header(ts->S, tx_if, ts->nexthop_mac, &tcp->ip.eth, ETH_TYPE_IP); diff --git a/wolfip.h b/wolfip.h index 2de6dad9..44c32e6e 100644 --- a/wolfip.h +++ b/wolfip.h @@ -179,6 +179,16 @@ typedef uint32_t ip4; #endif #endif +#ifndef WOLFIP_EBUSY +#ifdef EBUSY +#define WOLFIP_EBUSY EBUSY +#else +/* Fallback for targets without a system EBUSY: the POSIX value, used + * best-effort (not guaranteed to match a non-POSIX libc). */ +#define WOLFIP_EBUSY (16) +#endif +#endif + #ifdef DEBUG #include