diff --git a/common/src/nxd_udp_socket_send.c b/common/src/nxd_udp_socket_send.c index afb12691..8dc8fe93 100644 --- a/common/src/nxd_udp_socket_send.c +++ b/common/src/nxd_udp_socket_send.c @@ -400,6 +400,12 @@ UINT compute_checksum = 1; return(status); } } + else if (packet_ptr -> nx_packet_address.nx_packet_ipv6_address_ptr -> nxd_ipv6_address_state != NX_IPV6_ADDR_STATE_VALID) + { + + /* The packet carries the address it was received on, which is no longer valid. */ + return(NX_NO_INTERFACE_ADDRESS); + } /* Fill in the IP src/dest address */ ip_dest_addr = &ip_address -> nxd_ip_address.v6[0]; diff --git a/test/cmake/netxduo/regression/CMakeLists.txt b/test/cmake/netxduo/regression/CMakeLists.txt index 6230cbf3..8693fe53 100644 --- a/test/cmake/netxduo/regression/CMakeLists.txt +++ b/test/cmake/netxduo/regression/CMakeLists.txt @@ -364,6 +364,7 @@ set(netxduo_test_cases ${SOURCE_DIR}/netxduo_test/netx_ipv6_disable_test.c ${SOURCE_DIR}/netxduo_test/netx_ipv6_disable_tcp_test.c ${SOURCE_DIR}/netxduo_test/netx_ipv6_dad_failure_notify_send_test.c + ${SOURCE_DIR}/netxduo_test/netx_udp_ipv6_reply_invalid_address_test.c ${SOURCE_DIR}/netxduo_test/netx_icmp_multiple_ping6_test1.c ${SOURCE_DIR}/netxduo_test/netx_ip_address_get_test.c ${SOURCE_DIR}/netxduo_test/netx_10_24_02_test.c diff --git a/test/regression/netxduo_test/netx_udp_ipv6_reply_invalid_address_test.c b/test/regression/netxduo_test/netx_udp_ipv6_reply_invalid_address_test.c new file mode 100644 index 00000000..a683e384 --- /dev/null +++ b/test/regression/netxduo_test/netx_udp_ipv6_reply_invalid_address_test.c @@ -0,0 +1,360 @@ +/*************************************************************************** + * Copyright (c) 2026 Eclipse ThreadX contributors + * + * This program and the accompanying materials are made available under the + * terms of the MIT License which is available at + * https://opensource.org/licenses/MIT. + * + * AI Disclosure: This file was largely AI-generated by Claude Code (Opus 5.5). + * The AI-generated portions may be considered public domain (CC0-1.0) + * and not subject to the project's licence. The human contributor has + * reviewed and verified that the code is correct. + * + * SPDX-License-Identifier: MIT and CC0-1.0 + **************************************************************************/ + +/* This NetX test concentrates on sending back a received IPv6 UDP datagram + once the address it arrived on is no longer valid. + + A received datagram carries the address it arrived on, and + nxd_udp_socket_send uses that address as the source of a reply instead of + looking one up. If the address has been deleted, or IPv6 disabled, the + entry is no longer attached to an interface; if it has been set again it is + still being checked for duplicates. In each case the send must be refused + with NX_NO_INTERFACE_ADDRESS, the packet left to the caller, and the IP + instance must carry on. A deleted entry used as a source would stop the IP + thread for good, with the IP mutex held. + + ip_1 sends to ip_0's 2001::4, and ip_0 replies with the packet it received. + The reply is sent by a helper thread and waited for with a bound, so the + test reports an error rather than hanging if the send never returns. After + each refusal ip_0 gets its address back, and a datagram and its reply cross + both ways again. */ + +#include "tx_api.h" +#include "nx_api.h" + +extern void test_control_return(UINT status); + +#ifdef FEATURE_NX_IPV6 + +#define DEMO_STACK_SIZE 2048 + +static TX_THREAD thread_0; +static TX_THREAD thread_1; + +static NX_PACKET_POOL pool_0; +static NX_IP ip_0; +static NX_IP ip_1; +static NX_UDP_SOCKET socket_0; +static NX_UDP_SOCKET socket_1; + +static ULONG error_counter; +static NXD_ADDRESS address_0; +static NXD_ADDRESS address_1; + +/* The reply handed to thread 1, and what came of it. */ +static NX_PACKET *reply_ptr; +static UINT reply_status; +static volatile UINT reply_done; + +static void thread_0_entry(ULONG thread_input); +static void thread_1_entry(ULONG thread_input); +static UINT datagram_receive(NX_PACKET **packet_ptr); +static UINT reply_send(NX_PACKET *packet_ptr); +static UINT address_restore(VOID); +static VOID round_trip(VOID); + +extern void _nx_ram_network_driver_1500(struct NX_IP_DRIVER_STRUCT *driver_req); + +#ifdef CTEST +VOID test_application_define(void *first_unused_memory) +#else +void netx_udp_ipv6_reply_invalid_address_test_application_define(void *first_unused_memory) +#endif +{ + +CHAR *pointer; +UINT status; + + pointer = (CHAR *) first_unused_memory; + + error_counter = 0; + + tx_thread_create(&thread_0, "thread 0", thread_0_entry, 0, + pointer, DEMO_STACK_SIZE, + 4, 4, TX_NO_TIME_SLICE, TX_AUTO_START); + pointer = pointer + DEMO_STACK_SIZE; + + /* Sends the replies; higher priority, so it runs as soon as it is resumed. */ + tx_thread_create(&thread_1, "thread 1", thread_1_entry, 0, + pointer, DEMO_STACK_SIZE, + 3, 3, TX_NO_TIME_SLICE, TX_DONT_START); + pointer = pointer + DEMO_STACK_SIZE; + + nx_system_initialize(); + + status = nx_packet_pool_create(&pool_0, "NetX Main Packet Pool", 1536, pointer, 1536 * 12); + pointer = pointer + 1536 * 12; + if (status) + error_counter++; + + status = nx_ip_create(&ip_0, "NetX IP Instance 0", IP_ADDRESS(1, 2, 3, 4), 0xFFFFFF00UL, &pool_0, + _nx_ram_network_driver_1500, pointer, 2048, 1); + pointer = pointer + 2048; + status += nx_ip_create(&ip_1, "NetX IP Instance 1", IP_ADDRESS(1, 2, 3, 5), 0xFFFFFF00UL, &pool_0, + _nx_ram_network_driver_1500, pointer, 2048, 1); + pointer = pointer + 2048; +#ifndef NX_DISABLE_IPV4 + status += nx_arp_enable(&ip_0, (void *) pointer, 1024); + pointer = pointer + 1024; + status += nx_arp_enable(&ip_1, (void *) pointer, 1024); + pointer = pointer + 1024; +#endif /* NX_DISABLE_IPV4 */ + status += nx_udp_enable(&ip_0); + status += nx_udp_enable(&ip_1); + status += nxd_ipv6_enable(&ip_0); + status += nxd_ipv6_enable(&ip_1); + if (status) + error_counter++; +} + + +static void thread_0_entry(ULONG thread_input) +{ + +NX_PACKET *packet_ptr; + + NX_PARAMETER_NOT_USED(thread_input); + + printf("NetX Test: UDP IPv6 Reply Invalid Address Test......................"); + + /* Let the IP threads bring the links up. */ + tx_thread_sleep(NX_IP_PERIODIC_RATE); + + /* The addresses are valid at once, since ICMPv6 is not yet enabled. */ + address_0.nxd_ip_version = NX_IP_VERSION_V6; + address_0.nxd_ip_address.v6[0] = 0x20010000; + address_0.nxd_ip_address.v6[1] = 0; + address_0.nxd_ip_address.v6[2] = 0; + address_0.nxd_ip_address.v6[3] = 4; + address_1 = address_0; + address_1.nxd_ip_address.v6[3] = 5; + if (nxd_ipv6_address_set(&ip_0, 0, &address_0, 64, NX_NULL) || + nxd_ipv6_address_set(&ip_1, 0, &address_1, 64, NX_NULL) || + nxd_icmp_enable(&ip_0) || nxd_icmp_enable(&ip_1)) + error_counter++; + + if (nx_udp_socket_create(&ip_0, &socket_0, "Socket 0", NX_IP_NORMAL, NX_FRAGMENT_OKAY, 0x80, 5) || + nx_udp_socket_bind(&socket_0, 0x88, NX_NO_WAIT) || + nx_udp_socket_create(&ip_1, &socket_1, "Socket 1", NX_IP_NORMAL, NX_FRAGMENT_OKAY, 0x80, 5) || + nx_udp_socket_bind(&socket_1, 0x89, NX_NO_WAIT)) + error_counter++; + + /* Both ways work to begin with. */ + round_trip(); + + /* 1. The address deleted. */ + if (datagram_receive(&packet_ptr) == NX_SUCCESS) + { + if (nxd_ipv6_address_delete(&ip_0, 0)) + error_counter++; + if (reply_send(packet_ptr) != NX_NO_INTERFACE_ADDRESS) + error_counter++; + nx_packet_release(packet_ptr); + if (address_restore() == NX_SUCCESS) + round_trip(); + } + + /* 2. IPv6 disabled. */ + if (datagram_receive(&packet_ptr) == NX_SUCCESS) + { + if (nxd_ipv6_disable(&ip_0)) + error_counter++; + if (reply_send(packet_ptr) != NX_NO_INTERFACE_ADDRESS) + error_counter++; + nx_packet_release(packet_ptr); + + /* Disabling IPv6 also stopped ICMPv6. */ + if (nxd_ipv6_enable(&ip_0) || nxd_icmp_enable(&ip_0)) + error_counter++; + if (address_restore() == NX_SUCCESS) + round_trip(); + } + +#ifndef NX_DISABLE_IPV6_DAD + /* 3. The address deleted and set again: it is checked for duplicates + before it is valid. Once it is, the refused packet goes out. */ + if (datagram_receive(&packet_ptr) == NX_SUCCESS) + { + if (nxd_ipv6_address_delete(&ip_0, 0) || + nxd_ipv6_address_set(&ip_0, 0, &address_0, 64, NX_NULL)) + error_counter++; + if (ip_0.nx_ipv6_address[0].nxd_ipv6_address_state != NX_IPV6_ADDR_STATE_TENTATIVE) + error_counter++; + if (reply_send(packet_ptr) != NX_NO_INTERFACE_ADDRESS) + { + error_counter++; + } + else if (address_restore() == NX_SUCCESS) + { + if (reply_send(packet_ptr) != NX_SUCCESS) + error_counter++; + else if (nx_udp_socket_receive(&socket_1, &packet_ptr, NX_IP_PERIODIC_RATE)) + error_counter++; + else + nx_packet_release(packet_ptr); + } + } +#endif /* NX_DISABLE_IPV6_DAD */ + + if (error_counter) + { + printf("ERROR!\n"); + test_control_return(1); + } + else + { + printf("SUCCESS!\n"); + test_control_return(0); + } +} + + +/* Sends each reply it is given back to ip_1, then waits for the next. */ +static void thread_1_entry(ULONG thread_input) +{ + + NX_PARAMETER_NOT_USED(thread_input); + + while (1) + { + reply_status = nxd_udp_socket_send(&socket_0, reply_ptr, &address_1, 0x89); + reply_done = NX_TRUE; + tx_thread_suspend(&thread_1); + } +} + + +/* ip_1 sends a datagram to ip_0, which receives it. */ +static UINT datagram_receive(NX_PACKET **packet_ptr) +{ + +NX_PACKET *send_ptr; + + if (nx_packet_allocate(&pool_0, &send_ptr, NX_IPv6_UDP_PACKET, NX_NO_WAIT) || + nx_packet_data_append(send_ptr, "request", 7, &pool_0, NX_NO_WAIT)) + { + error_counter++; + return(NX_NOT_SUCCESSFUL); + } + if (nxd_udp_socket_send(&socket_1, send_ptr, &address_0, 0x88)) + { + nx_packet_release(send_ptr); + error_counter++; + return(NX_NOT_SUCCESSFUL); + } + if (nx_udp_socket_receive(&socket_0, packet_ptr, NX_IP_PERIODIC_RATE)) + { + error_counter++; + return(NX_NOT_SUCCESSFUL); + } + return(NX_SUCCESS); +} + + +/* Has thread 1 send the packet as a reply. If the send has not returned + within a second, the IP instance is stuck: the test stops there. A refused + packet is still the caller's; a sent one is not, so the pointer is cleared. */ +static UINT reply_send(NX_PACKET *packet_ptr) +{ + +UINT i; + + reply_ptr = packet_ptr; + reply_done = NX_FALSE; + tx_thread_resume(&thread_1); + for (i = 0; (i < NX_IP_PERIODIC_RATE) && (reply_done == NX_FALSE); i++) + tx_thread_sleep(1); + + if (reply_done == NX_FALSE) + { + printf("ERROR!\n"); + test_control_return(1); + } + + return(reply_status); +} + + +/* Gives ip_0 its address back and waits, bounded, until it is valid. */ +static UINT address_restore(VOID) +{ + +UINT i; + + if ((ip_0.nx_ipv6_address[0].nxd_ipv6_address_state == NX_IPV6_ADDR_STATE_UNKNOWN) && + nxd_ipv6_address_set(&ip_0, 0, &address_0, 64, NX_NULL)) + { + error_counter++; + return(NX_NOT_SUCCESSFUL); + } + for (i = 0; (i < 10 * NX_IP_PERIODIC_RATE) && + (ip_0.nx_ipv6_address[0].nxd_ipv6_address_state != NX_IPV6_ADDR_STATE_VALID); i++) + tx_thread_sleep(1); + if (ip_0.nx_ipv6_address[0].nxd_ipv6_address_state != NX_IPV6_ADDR_STATE_VALID) + { + error_counter++; + return(NX_NOT_SUCCESSFUL); + } + return(NX_SUCCESS); +} + + +/* A datagram from ip_1 to ip_0, and the received packet sent back as the + reply, from the address it arrived on. */ +static VOID round_trip(VOID) +{ + +NX_PACKET *packet_ptr; +NXD_ADDRESS source; +UINT port; + + if (datagram_receive(&packet_ptr)) + return; + + if (reply_send(packet_ptr) != NX_SUCCESS) + { + nx_packet_release(packet_ptr); + error_counter++; + return; + } + + if (nx_udp_socket_receive(&socket_1, &packet_ptr, NX_IP_PERIODIC_RATE)) + { + error_counter++; + return; + } + if (nxd_udp_packet_info_extract(packet_ptr, &source, NX_NULL, &port, NX_NULL) || + (source.nxd_ip_version != NX_IP_VERSION_V6) || + (source.nxd_ip_address.v6[0] != 0x20010000) || (source.nxd_ip_address.v6[3] != 4) || + (port != 0x88)) + error_counter++; + nx_packet_release(packet_ptr); +} + +#else + +#ifdef CTEST +VOID test_application_define(void *first_unused_memory) +#else +void netx_udp_ipv6_reply_invalid_address_test_application_define(void *first_unused_memory) +#endif +{ + + /* Print out test information banner. */ + printf("NetX Test: UDP IPv6 Reply Invalid Address Test......................N/A\n"); + test_control_return(3); +} + +#endif