diff --git a/common/src/nx_icmpv6_DAD_failure.c b/common/src/nx_icmpv6_DAD_failure.c index 3aec58ec..6010beaf 100644 --- a/common/src/nx_icmpv6_DAD_failure.c +++ b/common/src/nx_icmpv6_DAD_failure.c @@ -88,17 +88,6 @@ NXD_IPV6_ADDRESS *address_ptr; /* Indicate the DAD process is disabled. */ ipv6_address -> nxd_ipv6_address_DupAddrDetectTransmit = 0; -#ifdef NX_ENABLE_IPV6_ADDRESS_CHANGE_NOTIFY - if (ip_ptr -> nx_ipv6_address_change_notify) - { - ipv6_addr_index = (ULONG)ipv6_address -> nxd_ipv6_address_index; - interface_index = (ULONG)ipv6_address -> nxd_ipv6_address_attached -> nx_interface_index; - ip_ptr -> nx_ipv6_address_change_notify(ip_ptr, NX_IPV6_ADDRESS_DAD_FAILURE, interface_index, ipv6_addr_index, &ipv6_address -> nxd_ipv6_address[0]); - } -#else - NX_PARAMETER_NOT_USED(ip_ptr); -#endif /* NX_ENABLE_IPV6_ADDRESS_CHANGE_NOTIFY */ - /* Remove address from interface. */ if (ipv6_address == ipv6_address -> nxd_ipv6_address_attached -> nxd_interface_ipv6_address_list_head) { @@ -117,6 +106,20 @@ NXD_IPV6_ADDRESS *address_ptr; } } } + + /* Notify the application only now that the address is off the interface's + list: the callback runs on the IP thread and may send, and a send must + not find an invalid address at the head of that list. */ +#ifdef NX_ENABLE_IPV6_ADDRESS_CHANGE_NOTIFY + if (ip_ptr -> nx_ipv6_address_change_notify) + { + ipv6_addr_index = (ULONG)ipv6_address -> nxd_ipv6_address_index; + interface_index = (ULONG)ipv6_address -> nxd_ipv6_address_attached -> nx_interface_index; + ip_ptr -> nx_ipv6_address_change_notify(ip_ptr, NX_IPV6_ADDRESS_DAD_FAILURE, interface_index, ipv6_addr_index, &ipv6_address -> nxd_ipv6_address[0]); + } +#else + NX_PARAMETER_NOT_USED(ip_ptr); +#endif /* NX_ENABLE_IPV6_ADDRESS_CHANGE_NOTIFY */ } #endif /* NX_DISABLE_IPV6_DAD */ diff --git a/test/cmake/netxduo/regression/CMakeLists.txt b/test/cmake/netxduo/regression/CMakeLists.txt index 308026c9..6230cbf3 100644 --- a/test/cmake/netxduo/regression/CMakeLists.txt +++ b/test/cmake/netxduo/regression/CMakeLists.txt @@ -363,6 +363,7 @@ set(netxduo_test_cases ${SOURCE_DIR}/netxduo_test/netx_tcp_socket_listen_test.c ${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_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_ipv6_dad_failure_notify_send_test.c b/test/regression/netxduo_test/netx_ipv6_dad_failure_notify_send_test.c new file mode 100644 index 00000000..67e3aaad --- /dev/null +++ b/test/regression/netxduo_test/netx_ipv6_dad_failure_notify_send_test.c @@ -0,0 +1,232 @@ +/*************************************************************************** + * 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 an application's IPv6 address change + callback sending while it is told of a failed duplicate address detection. + + The callback runs on the IP thread. If it sends to a multicast group joined + on the interface whose address just failed, IP looks for a source address + on that interface's address list. The failed address must already be off + that list: found there, it would be an address no longer valid, which IP + asserts never happens, and the IP thread would stop for good. + + ip_1 already owns 2001::4, and ip_0 sets the same address, so ip_1's + defence makes ip_0's detection fail. ip_0's callback sends a datagram to a + group joined on the interface. The test then checks that the callback + came back and that ip_0's IP thread still answers a ping. The waits are + bounded: without the fix the IP thread sleeps forever, and the test reports + an error rather than hanging. */ + +#include "tx_api.h" +#include "nx_api.h" + +extern void test_control_return(UINT status); + +#if defined(FEATURE_NX_IPV6) && defined(NX_ENABLE_IPV6_ADDRESS_CHANGE_NOTIFY) && defined(NX_ENABLE_IPV6_MULTICAST) && \ + !defined(NX_DISABLE_IPV6_DAD) && !defined(NX_DISABLE_IPV4) + +#define DEMO_STACK_SIZE 2048 + +static TX_THREAD thread_0; + +static NX_PACKET_POOL pool_0; +static NX_IP ip_0; +static NX_IP ip_1; +static NX_UDP_SOCKET socket_0; + +static ULONG error_counter; +static NXD_ADDRESS group; +static volatile ULONG dad_failures; +static volatile UINT in_callback; + +static void thread_0_entry(ULONG thread_input); +static VOID address_change_notify(NX_IP *ip_ptr, UINT status, UINT interface_index, UINT address_index, + ULONG *ip_address); + +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_ipv6_dad_failure_notify_send_test_application_define(void *first_unused_memory) +#endif +{ + +CHAR *pointer; +UINT status; + + pointer = (CHAR *) first_unused_memory; + + error_counter = 0; + dad_failures = 0; + in_callback = NX_FALSE; + + 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; + + 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; + status += nx_arp_enable(&ip_0, (void *) pointer, 1024); + pointer = pointer + 1024; + status += nx_arp_enable(&ip_1, (void *) pointer, 1024); + pointer = pointer + 1024; + status += nx_udp_enable(&ip_0); + status += nxd_ipv6_enable(&ip_0); + status += nxd_ipv6_enable(&ip_1); + if (status) + error_counter++; +} + + +static void thread_0_entry(ULONG thread_input) +{ + +NXD_ADDRESS address; +NX_PACKET *packet_ptr; +UINT i; + + NX_PARAMETER_NOT_USED(thread_input); + + printf("NetX Test: IPv6 DAD Failure Notify Send Test........................"); + + /* Let the IP threads bring the links up. */ + tx_thread_sleep(NX_IP_PERIODIC_RATE); + + /* ip_1 owns 2001::4, valid at once since ICMPv6 is not yet enabled. */ + address.nxd_ip_version = NX_IP_VERSION_V6; + address.nxd_ip_address.v6[0] = 0x20010000; + address.nxd_ip_address.v6[1] = 0; + address.nxd_ip_address.v6[2] = 0; + address.nxd_ip_address.v6[3] = 4; + if (nxd_ipv6_address_set(&ip_1, 0, &address, 64, NX_NULL)) + error_counter++; + if (nxd_icmp_enable(&ip_1) || nxd_icmp_enable(&ip_0)) + error_counter++; + + /* ip_0: a socket, a group on interface 0, the callback. */ + 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)) + error_counter++; + group.nxd_ip_version = NX_IP_VERSION_V6; + group.nxd_ip_address.v6[0] = 0xFF020000; + group.nxd_ip_address.v6[1] = 0; + group.nxd_ip_address.v6[2] = 0; + group.nxd_ip_address.v6[3] = 0x00011234; + if (nxd_ipv6_multicast_interface_join(&ip_0, &group, 0)) + error_counter++; + if (nxd_ipv6_address_change_notify(&ip_0, address_change_notify)) + error_counter++; + + /* ip_0 claims ip_1's address: its duplicate address detection fails. */ + if (nxd_ipv6_address_set(&ip_0, 0, &address, 64, NX_NULL)) + error_counter++; + for (i = 0; (i < 5 * NX_IP_PERIODIC_RATE) && (dad_failures == 0); i++) + tx_thread_sleep(1); + tx_thread_sleep(NX_IP_PERIODIC_RATE / 10); + + /* The callback was told, and came back. */ + if ((dad_failures != 1) || (in_callback != NX_FALSE)) + error_counter++; + + /* The failed address is no longer on the interface. */ + if (ip_0.nx_ip_interface[0].nxd_interface_ipv6_address_list_head != NX_NULL) + error_counter++; + + /* And ip_0's IP thread is still serving: a ping to ip_1 is answered. + Only asked if the callback came back, since otherwise the IP thread + has stopped holding the IP mutex the ping would wait for. */ + if (in_callback == NX_FALSE) + { + if (nx_icmp_ping(&ip_0, IP_ADDRESS(1, 2, 3, 5), "alive", 5, &packet_ptr, NX_IP_PERIODIC_RATE)) + error_counter++; + else + nx_packet_release(packet_ptr); + } + + if (error_counter) + { + printf("ERROR!\n"); + test_control_return(1); + } + else + { + printf("SUCCESS!\n"); + test_control_return(0); + } +} + + +/* On a failed detection, sends a datagram to the group on the failed + address's interface. The interface has no other address, so the send is + refused for want of a source; what matters is that it returns. */ +static VOID address_change_notify(NX_IP *ip_ptr, UINT status, UINT interface_index, UINT address_index, + ULONG *ip_address) +{ + +NX_PACKET *packet_ptr; + + NX_PARAMETER_NOT_USED(address_index); + + if (status != NX_IPV6_ADDRESS_DAD_FAILURE) + return; + + if ((ip_ptr != &ip_0) || (interface_index != 0) || (ip_address[3] != 4)) + error_counter++; + + in_callback = NX_TRUE; + dad_failures++; + + if (nx_packet_allocate(&pool_0, &packet_ptr, NX_UDP_PACKET, NX_NO_WAIT) == NX_SUCCESS) + { + nx_packet_data_append(packet_ptr, "failed", 6, &pool_0, NX_NO_WAIT); + if (nxd_udp_socket_send(&socket_0, packet_ptr, &group, 0x99) == NX_SUCCESS) + error_counter++; + else + nx_packet_release(packet_ptr); + } + else + error_counter++; + + in_callback = NX_FALSE; +} + +#else + +#ifdef CTEST +VOID test_application_define(void *first_unused_memory) +#else +void netx_ipv6_dad_failure_notify_send_test_application_define(void *first_unused_memory) +#endif +{ + + /* Print out test information banner. */ + printf("NetX Test: IPv6 DAD Failure Notify Send Test........................N/A\n"); + test_control_return(3); +} + +#endif