Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 14 additions & 11 deletions common/src/nx_icmpv6_DAD_failure.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand All @@ -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 */
Expand Down
1 change: 1 addition & 0 deletions test/cmake/netxduo/regression/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
232 changes: 232 additions & 0 deletions test/regression/netxduo_test/netx_ipv6_dad_failure_notify_send_test.c
Original file line number Diff line number Diff line change
@@ -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
Loading