第 2 章 - 安装和使用 Azure RTOS NetX Duo DHCPv6 客户端

本章包含与安装、设置和使用 Azure RTOS NetX Duo DHCPv6 客户端组件相关的各种问题的说明。

产品分发

https://github.com/azure-rtos/netxduo 中提供了 NetX Duo DHCPv6 客户端。 该包包含两个源文件和一个 PDF 文件(其中包含本文档),如下所示:

  • nxd_dhcpv6_client.h:NetX Duo DHCPv6 客户端的头文件

  • nxd_dhcpv6_client.c:NetX Duo DHCPv6 客户端的源代码文件

  • demo_netxduo_dhcpv6_client.c:演示如何设置 NetX Duo DHCPv6 客户端的示例程序

  • nxd_dhcpv6_client.pdf:NetX Duo DHCPv6 客户端的 PDF 说明

NetX Duo DHCPv6 客户端安装

若要使用 NetX Duo DHCPv6 客户端 API,可将前面提到的整个分发包复制到 NetX Duo 所安装的目录中。 例如,如果 NetX Duo 安装在“\threadx\arm7\green”目录中,则可将 nxd_dhcpv6_client.h 和 nxd_dhpcv6_client.c 文件复制到该目录中 。

使用 NetX Duo DHCPv6 客户端

应用程序代码必须先包含 tx_api.h 和 nx_api.h,然后包含 nxd_dhcpv6_client.h,才能分别使用 DHCPv6 客户端、ThreadX 和 NetX Duo 服务 。 nxd_dhcpv6_client.c 必须采用与其他应用程序文件相同的方式在项目中进行编译,并且其对象窗体必须与该应用程序的文件一起链接。

客户端 DHCP 唯一标识符 (DUID)

客户端 DUID 唯一定义网络上的每个客户端。 在从服务器请求 IPv6 地址之前,应用程序必须先创建客户端 DUID。 客户端 DUID 自动包含在发往服务器的所有消息中。 若要创建 DUID,应用程序需要调用服务 nx_dhcpv6_create_client_duid:

UINT nx_dhcpv6_create_client_duid(NX_DHCPV6 *dhcpv6_ptr, 
                                  UINT duid_type, 
                                  UINT hardware_type, ULONG time);

应用程序将调用此服务,并指定 DUID 的类型(仅包括链接层,或者包括链接层和时间)。 对于包括链接层和时间的 DUID,如果未指定时间输入,则此服务将提供时间字段。

对于正在重新启动并希望使用以前分配的 IPv6 地址租约的设备,应用程序必须创建它在分配 IPv6 地址时使用的同一个客户端 DUID。 只需提供链接层就能创建链接层客户端 DUID。 如果设备有权访问链接层地址,则此操作不需要以前的非易失存储器。 对于时间类型的 DUID,应用程序必须有权访问上一次创建 DUID 时使用的相同时间数据,这确实需要非易失存储器。 没有稳定存储的客户端不得使用时间类型的 DUID。

客户端的非临时地址标识关联 (IANA)

在请求 IPv6 地址之前,应用程序必须先创建一个 IANA,并选择性地创建一个或多个 IA 地址。 为此,应用程序需调用 nx_dhcpv6_create_client_iana 服务。 若要创建 IA 地址选项,应用程序需结合请求的 IPv6 地址和生存期值(作为向服务器发出的提示)调用 nx_dhcpv6_add_client_ia 服务。

IANA 及其 IA 共同定义了客户端 IPv6 地址分配参数:

在启动 DHCPv6 客户端之前,DHCPv6 客户端应用程序将使用 nx_dhcpv6_create_client_iana 服务创建 IANA:

UINT    nx_dhcpv6_create_client_iana(NX_DHCPV6 *dhcpv6_ptr, 
									 UINT IA_ident, ULONG T1, ULONG T2);

在启动 DHCPv6 客户端之前,它还必须使用 nx_dhcpv6_create_client_ia 服务和请求的 IPv6 地址创建一个或多个 IA。

UINT    nx_dhcpv6_add_client_ia(NX_DHCPV6 *dhcpv6_ptr, 
                                NXD_ADDRESS *ipv6_address, 
                                ULONG preferred_lifetime, 
                                ULONG valid_lifetime);

注意

应用程序创建的 IA 地址数不能超过 NX_DHCPV6_MAX_IA_ADDRESS 参数,该参数的默认值为 1。

NetX Duo DHCPv6 客户端支持对旧式 DHCPv6 客户端应用程序使用 nx_dhcpv6_create_client_ia,该服务与 nx_dhcpv6_add_client_ia 相同,不过,我们建议开发人员使用 nx_dhcpv6_add_client_ia 服务 。

本章后面的“小型示例系统”中演示了这些服务。

重复使用 IANA 和 IA 时有关非易失存储器的注意事项

如果应用程序希望在重新启动时使用相同的地址,则它必须将 IANA 参数 T1、T2 和 IANA 标识符保存到非易失存储器。 应用程序还必须将其 IA(其中包含其 IPv6 地址)保存到非易失存储器。

在关闭时,应用程序还必须将已绑定到其已分配的 IPv6 地址租约的消逝时间存储到非易失存储器。 为此,它会在停止 DHCPv6 客户端之前调用 nx_dhcpv6_get_time_accrued 服务。

UINT nx_dhcpv6_get_time_accrued(NX_DHCPV6 *dhcpv6_ptr, 
                                ULONG *time_accrued);

假设应用程序中有一个独立时钟可以跟踪在重新启动后,从应用程序停止时间到 DHCPv6 客户端重启时间的时间间隔,则应用程序会将该消逝时间增加到停止之前 IPv6 租约上的累积时间。 现在,它会使用绑定到 IPv6 租约的总消逝时间(用作如下所示的 nv_time 输入)来启动客户端线程任务:

UINT nx_dhcpv6_start(NX_DHCPV6 *dhcpv6_ptr, ULONG nv_time);

从此时起,DHCPv6 客户端线程任务将接管监视 IPv6 租约上累积的时间以确定何时要续订租约的工作。

设置 DHCPv6 选项数据

在请求 IPv6 租约之前,应用程序可以请求 DNS 服务器和时间服务器等其他网络参数数据。 其中的某些参数具有特定的服务。 下面显示了其中的几个服务:

UINT  nx_dhcpv6_request_option_DNS_server(NX_DHCPV6 *dhcpv6_ptr, 
                                          UINT enable)

UINT  nx_dhcpv6_request_option_time_server(NX_DHCPV6 *dhcpv6_ptr, 
                                           UINT enable);

启动 IPv6 地址请求

应用程序结合零时间输入调用 nx_dhcpv6_start 服务来启动 DHCPv6 客户端线程。 若要启动 DHCPv6 协议来请求 IPv6 地址,应用程序需调用 nx_dhcpv6_request_solicit。

如果应用程序希望使用以前分配的 IPv6 租约,则它会结合非零时间输入调用 nx_dhcpv6_start。 它不应调用 nx_dhcpv6_request_solicit。

此后,应用程序无需执行任何其他操作,DHCPv6 客户端就会自动监视何时需要续订或重新绑定 IPv6 地址。

小型示例系统

以下小示例通过一个 DHCPv6 客户端和虚拟“RAM”驱动程序描述了 NetX Duo DHCPv6 客户端的使用是多么容易。 此演示假设使用的设备只带有一个物理网络接口。

tx_application_define 创建数据包池,使 DHCPv6 客户端能够发送 DHCPv6 消息。 它还创建应用程序线程和 IP 实例。 然后,它在第 130-148 行中针对 IP 启用 UDP 和 ICMP。 然后,在第 151 行中使用状态更改回调函数 (dhcpv6_state_change_notify) 和服务器错误回调函数 (dhcpv6_server_error_handler) 创建 DHCPv6 客户端 。

第 202-217 行的客户端线程入口函数 thread_client_entry 中,使用链接本地地址设置客户端 IP,并为 IPv6 和 ICMPv6 服务启用该 IP。 在启动 DHCPv6 客户端之前,应用程序将在第 219-303 行中创建客户端 DUID、IANA 选项和 IA 地址选项。 如果客户端希望从服务器请求 IPv6 地址以及有效和首选的生存期,则 IA 地址选项是可选的。 服务器不一定会授予请求的 IPv6 地址或租约时间。 应用程序可以添加更多 IA 选项(数目不超过 NX_DHCPV6_MAX_IA_ADDRESS)以获取多个全局地址。

最后,应用程序设置各种选项,以在其发往 DHCPv6 服务器的消息中请求网络参数。 DHCPv6 客户端任务是通过调用第 306 行中的 nx_dhcpv6_start 启动的,实际 DHCPv6 协议是通过调用第 317 行中的 nx_dhcpv6_request_solicit 以 SOLICIT 状态启动的 。 然后,DHCPv6 客户端通过 DHCPv6 协议自动处理客户端状态提升,直到已绑定到某个地址或发生错误。 在此期间,应用程序将等待协议完成;如果已配置 IP 实例(默认配置),则它还会等待重复地址检测 (DAD) 完成。

完成 tx_thread_sleep 调用之后,应用程序将检查状态更改回调中设置的全局参数,以确定用于获取 IPv6 租约的 DHCPv6 客户端任务是否成功,如果成功,则进一步确定 DAD 的唯一性检查是否成功。 这是使用状态更改回调函数和服务器错误回调函数中设置的计数器完成的。 应用程序将轮询失败地址分配的 address_not_assigned、address_expired 和 server_errors 的非零计数。 如果 bound_addresses 的计数不为零(至少成功分配了一个地址),则应用程序将检查失败 DAD 检查的非零 address_failed_dad。 下面解释了状态更改回调和服务器错误回调:

状态更改回调 dhcpv6_state_change_notify、前一种和当前的 DHCPv6 客户端状态用于确定客户端是否收到了任何有效的服务器响应:

  • dhcpv6_state_change_notify 检查状态是否直接从 SOLICIT 转换为 INIT,如果是,它将递增未从服务器收到任何响应的 DHCPv6 客户端的计数器。

接下来,dhcpv6_state_change_notify 检查客户端是否已分配(绑定)到一个或多个 IPv6 地址:

  • 如果新状态为 BOUND,则它会递增已绑定到客户端的地址的计数器。

dhcpv6_state_change_notify 还会检查失败的 DAD 检查:

  • 如果状态从 DECLINE 转换为 INIT,则表示分配给 DHCPv6 客户端的某个地址未通过 DAD 检查,此时,失败的地址分配计数将会递增。

此示例中 dhcpv6_state_change_notify 执行的最后一项检查针对通过了 DAD 检查的、但续订或重新绑定失败的已成功分配地址:

  • 如果状态从 REBIND 更改为 INIT,则表示客户端未收到其 RENEW 或 REBIND 请求的任何响应,此时,dhcpv6_state_change_notify 将递增其已过期地址的计数。

在 DHCPv6 客户端任务通知 dhcpv6_server_error_handler 已从服务器收到错误状态后,该回调将递增服务器错误的计数。

假设一切正常,应用程序将在 DHCPv6 客户端中查询地址数据,包括租约时间。 它将在第 372-392 行,通过调用 nx_dhcpv6_get_valid_ip_address_count 服务来获取有效(已成功分配的)地址计数,并通过调用 nx_dhcpv6_get_iana_lease_time 来获取 IANA 中的续订时间(适用于分配的所有 IA 地址) 。 然后,它在 DHCPv6 客户端中按地址索引查询其 IPv6 地址和租约时间的每个 IA 选项。

某些 DHCPv6 客户端服务(nx_dhcpv6_get_lease_time_data、nx_dhcpv6_get_IP_address)不需要使用地址索引作为输入,会返回主客户端全局地址的 DHCPv6 参数。 这一点适用于在第 384 行中调用 nx_dhcpv6_get_valid_ip_address_lease_time 时使用单个全局 IPv6 地址的客户端。

每次系统重新启动后,系统可以通过 DHCPv6 客户端配置 NX_DHCPV6_CLIENT_RESTORE_STATE 来还原以前以绑定状态创建的 DHCPv6 客户端。 在第 434 行调用 nx_dhcpv6_client_get_record 可以获取每次系统重新启动后的 DHCPv6 客户端记录,在第 525 行调用 nx_dhcpv6_client_restore_record 可以存储系统开机后的 DHCPv6 客户端记录 。

然后,应用程序在第 552 行中使用 nx_dhcpv6_request_release 服务释放分配的地址。 若要重启应用程序,请在第 567 行中使用 nx_dhcpv6_client_stop 服务停止 DHCPv6 客户端,并清除已注册到 IP 实例的、通过 DHCPv6 客户端配置的所有 IPv6 地址。 此操作是通过在第 578 行中调用 nx_dhcpv6_reinitialize 执行的。 然后,应用程序会像以前一样,使用 nx_dhcpv6_start 和 nx_dhcpv6_request_solicit 服务重启 DHCPv6 客户端任务 。

在第 626 行中通过调用 nx_dhcpv6_delete 删除 DHCPv6 客户端。 请注意,这不会删除应用程序为 DHCPv6 客户端创建的数据包池,因为 IP 实例也使用此数据包池。 否则,如果该数据包池对 IP 实例不再有用,则应使用 NetX Duo nx_packet_pool_delete 服务删除该池。

/* This is a small demo of the NetX Duo DHCPv6 Client for the high-performance NetX Duo stack. */

#include    <stdio.h>
#include   "tx_api.h"
#include   "nx_api.h"
#include   "nxd_dhcpv6_client.h"

#ifdef FEATURE_NX_IPV6
#define     DEMO_STACK_SIZE         2048

/* Set the client address, and request these address from DHCPv6 Server. */
/*
#define     NX_DHCPV6_REQUEST_IA_ADDRESS
*/

/* Set the list of DHCPv6 option data (timezone, DNS server, timer server, domain name)to get from the DHCPv6 server. */

#define     NX_DHCPV6_REQUEST_OPTION


/* Add the fully qualified domain name to request whether the DHCPv6 server SHOULD or SHOULD NOT perform the AAAA RR or DNS updates. */

#define     NX_DHCPV6_REQUEST_FQDN_OPTION


/* Define the ThreadX and NetX object control blocks... */

NX_PACKET_POOL          pool_0;
TX_THREAD               thread_client;
NX_IP                   client_ip;

/* Define the Client and Server instances. */

NX_DHCPV6               dhcp_client;

/* Define the error counter used in the demo application... */
ULONG                   error_counter;
CHAR                    *pointer;

/* Define thread prototypes. */
void    thread_client_entry(ULONG thread_input);

/***** Substitute your ethernet driver entry function here *********/
extern VOID    _nx_ram_network_driver(NX_IP_DRIVER *driver_req_ptr);

/* Declare DHCPv6 Client callbacks */
VOID dhcpv6_state_change_notify(NX_DHCPV6 *dhcpv6_ptr, UINT old_state, UINT new_state);
VOID dhcpv6_server_error_handler(NX_DHCPV6 *dhcpv6_ptr, UINT op_code, UINT status_code, UINT message_type);

/* Set up globals for tracking changes to DHCPv6 Client from callback services. */
UINT state_changes = 0;
UINT address_expired = 0;
UINT address_failed_dad = 0;
UINT bound_addresses = 0;
UINT address_not_assigned = 0;
UINT server_errors = 0;

/* Define some DHCPv6 parameters. */

#define DHCPV6_IANA_ID      0xC0DEDBAD
#define DHCPV6_T1           NX_DHCPV6_INFINITE_LEASE
#define DHCPV6_T2           NX_DHCPV6_INFINITE_LEASE
#define DHCPV6_RENEW_TIME   NX_DHCPV6_INFINITE_LEASE
#define DHCPV6_REBIND_TIME  NX_DHCPV6_INFINITE_LEASE
#define PACKET_PAYLOAD      500
#define PACKET_POOL_SIZE    (5*PACKET_PAYLOAD)

/* Define main entry point. */

int main()
{

    /* Enter the ThreadX kernel. */
    tx_kernel_enter();
}


/* Define what the initial system looks like. */

void    tx_application_define(void *first_unused_memory)
{

UINT    status;

    /* Setup the working pointer. */
    pointer =  (CHAR *) first_unused_memory;

    /* Create the Client thread. */
    status = tx_thread_create(&thread_client, "Client thread", thread_client_entry, 0,
                              pointer, DEMO_STACK_SIZE, 8, 8, TX_NO_TIME_SLICE, TX_AUTO_START);

    /* Check for IP create errors. */
    if (status)
    {
        error_counter++;
        return;
    }

    pointer =  pointer + DEMO_STACK_SIZE;

    /* Initialize the NetX system. */
    nx_system_initialize();

    /* Create a packet pool. */
    status =  nx_packet_pool_create(&pool_0, "NetX Main Packet Pool", 1024,  pointer, PACKET_POOL_SIZE);

    pointer = pointer + PACKET_POOL_SIZE;

    /* Check for pool creation error. */
    if (status)
    {
        error_counter++;
        return;
    }

    /* Create a Client IP instance. */
    status = nx_ip_create(&client_ip, "Client IP", IP_ADDRESS(0, 0, 0, 0),
                          0xFFFFFF00UL, &pool_0, _nx_ram_network_driver,
                          pointer, 2048, 1);

    pointer =  pointer + 2048;

    /* Check for IP create errors. */
    if (status)
    {
        error_counter++;
        return;
    }

    /* Enable UDP traffic for sending DHCPv6 messages. */
    status =  nx_udp_enable(&client_ip);

    /* Check for UDP enable errors. */
    if (status)
    {
        error_counter++;
        return;
    }

    /* Enable ICMP. */
    status =  nx_icmp_enable(&client_ip);

    /* Check for ICMP enable errors. */
    if (status)
    {
        error_counter++;
        return;
    }

    /* Create the DHCPv6 Client. */
    status =  nx_dhcpv6_client_create(&dhcp_client, &client_ip, "DHCPv6 Client",
                                      &pool_0, pointer, 2048, dhcpv6_state_change_notify,
                                      dhcpv6_server_error_handler);

    /* Check for errors. */
    if (status)
    {
        error_counter++;
        return;
    }

    /* Update the stack pointer because we need it again. */
    pointer = pointer + 2048;

    /* Yield control to DHCPv6 threads and ThreadX. */
    return;
}


/* Define the Client host application thread. */

void    thread_client_entry(ULONG thread_input)
{

UINT        status;
ULONG       T1, T2;
UINT        address_count;
UINT        address_index = 0;
NXD_ADDRESS valid_ipv6_address;
ULONG       preferred_lifetime;
ULONG       valid_lifetime;
UINT        ia_count = 1;

#ifdef NX_DHCPV6_REQUEST_IA_ADDRESS
NXD_ADDRESS ipv6_address;
#endif

#ifdef NX_DHCPV6_REQUEST_OPTION
UCHAR       buffer[200];
NXD_ADDRESS dns_server;
#endif

#ifdef NX_DHCPV6_CLIENT_RESTORE_STATE
ULONG       current_time;
ULONG       elapsed_time;
NX_DHCPV6_CLIENT_RECORD dhcpv6_client_record;
#endif


    state_changes = 0;

    /* Establish the link local address for the host. The RAM driver creates
       a virtual MAC address of 0x1122334456. */
    status = nxd_ipv6_address_set(&client_ip, 0, NX_NULL, 10, NULL);

    if (status)
    {
        error_counter++;
        return;
    }

    /* Let NetX Duo get initialized. */
    tx_thread_sleep(50);

    /* Enable the Client IP for IPv6 and ICMPv6 services. */
    nxd_ipv6_enable(&client_ip);
    nxd_icmp_enable(&client_ip);

    /* Create a Link Layer Plus Time DUID for the DHCPv6 Client. Set time ID field
       to NULL; the DHCPv6 Client API will supply one. */
    status = nx_dhcpv6_create_client_duid(&dhcp_client, NX_DHCPV6_DUID_TYPE_LINK_TIME,
                                          NX_DHCPV6_HW_TYPE_IEEE_802, 0);

    if (status != NX_SUCCESS)
    {
        error_counter++;
        return;
    }

    /* Create the DHCPv6 client's Identity Association (IA-NA) now.

       Note that if this host had already been assigned in IPv6 lease, it
       would have to use the assigned T1 and T2 values in loading the DHCPv6
       client with an IANA block.
    */
    status = nx_dhcpv6_create_client_iana(&dhcp_client, DHCPV6_IANA_ID, DHCPV6_T1,  DHCPV6_T2);

    if (status != NX_SUCCESS)
    {
        error_counter++;
        return;
    }

#ifdef NX_DHCPV6_REQUEST_IA_ADDRESS
    memset(&ipv6_address,0x0, sizeof(NXD_ADDRESS));
    ipv6_address.nxd_ip_version = NX_IP_VERSION_V6;
    ipv6_address.nxd_ip_address.v6[0] = 0x3ffe0501;
    ipv6_address.nxd_ip_address.v6[1] = 0xffff0100;
    ipv6_address.nxd_ip_address.v6[2] = 0x00000000;
    ipv6_address.nxd_ip_address.v6[3] = 0x0000abcd;

    /* Create an IA address option.
        Note that if this host had already been assigned in IPv6 lease, it
        would have to use the assigned IPv6 address, preferred and valid lifetime
        values in loading the DHCPv6 Client with an IA block.
    */
    status = nx_dhcpv6_add_client_ia(&dhcp_client, &ipv6_address,DHCPV6_RENEW_TIME, DHCPV6_REBIND_TIME);

    if (status != NX_SUCCESS)
    {
        error_counter++;
        return;
    }

    /* If the DHCPv6 Client is configured for a maximum number of IA addresses
       greater than 1, we can add another IA address if the device requires
       multiple global IPv6 addresses. */
    if(NX_DHCPV6_MAX_IA_ADDRESS >= 2)
    {
        memset(&ipv6_address,0x0, sizeof(NXD_ADDRESS));
        ipv6_address.nxd_ip_version = NX_IP_VERSION_V6;
        ipv6_address.nxd_ip_address.v6[0] = 0x3ffe0501;
        ipv6_address.nxd_ip_address.v6[1] = 0xffff0100;
        ipv6_address.nxd_ip_address.v6[2] = 0x00000000;
        ipv6_address.nxd_ip_address.v6[3] = 0x00001234;

        /* Add another  IA address option. */
        status = nx_dhcpv6_add_client_ia(&dhcp_client, &ipv6_address, DHCPV6_RENEW_TIME, DHCPV6_REBIND_TIME);

        if (status != NX_SUCCESS)
        {
            error_counter++;
            return;
        }
    }
#endif /* NX_DHCPV6_REQUEST_IA_ADDRESS  */

#ifdef NX_DHCPV6_REQUEST_OPTION
    /* Set the list of DHCPv6 option data to get from the DHCPv6 server if needed. */
    nx_dhcpv6_request_option_timezone(&dhcp_client, NX_TRUE);
    nx_dhcpv6_request_option_DNS_server(&dhcp_client, NX_TRUE);
    nx_dhcpv6_request_option_time_server(&dhcp_client, NX_TRUE);
    nx_dhcpv6_request_option_domain_name(&dhcp_client, NX_TRUE);
#endif /* NX_DHCPV6_REQUEST_OPTION */


#ifdef NX_DHCPV6_REQUEST_FQDN_OPTION
    /* Set the DHCPv6 Client FQDN option.
       operation: NX_DHCPV6_CLIENT_DESIRES_UPDATE_AAAA_RR         DHCPv6 Client choose to updating the FQDN-to-IPv6 address mapping for FQDN and address(es) used by the client.
                  NX_DHCPV6_CLIENT_DESIRES_SERVER_DO_DNS_UPDATE   DHCPv6 Client choose to updating the FQDN-to-IPv6 address mapping for FQDN and address(es) used by the client to the server.
                  NX_DHCPV6_CLIENT_DESIRES_NO_SERVER_DNS_UPDATE   DHCPv6 Client choose to request that the server perform no DNS updatest on its behalf. */
    nx_dhcpv6_request_option_FQDN(&dhcp_client, "DHCPv6-Client", NX_DHCPV6_CLIENT_DESIRES_UPDATE_AAAA_RR);
#endif /* NX_DHCPV6_REQUEST_FQDN_OPTION */

    /* Start up the NetX DHCPv6 Client thread task. */
    status =  nx_dhcpv6_start(&dhcp_client);

    /* Check for errors. */
    if (status != NX_SUCCESS)
    {

        error_counter++;
        return;
    }

    /* Start the DHCPv6 by sending a Solicit message out on the network. */
    status = nx_dhcpv6_request_solicit(&dhcp_client);

    /* Check status. */
    if (status != NX_SUCCESS)
    {

        error_counter++;
        return;
    }

    /* Is the DHCPv6 Client request for address assignment successfully started? */
    if (status == NX_SUCCESS)
    {

        /* If Duplicate Address Detection (DAD) is enabled in NetX Duo, e.g. #NXDUO_DISABLE_DAD
           not defined, allow time for NetX Duo to verify the address is unique on our network.
         */
        tx_thread_sleep(500);

        /* Check the bound address. */
        if (bound_addresses != ia_count)
        {

            /* Attempt to find out why DHCPv6 failed, where...*/

            if (server_errors > 0)
            {
                /* Actually you would compare server_error count with number of IA's added
                   to determine if any addresses were assigned. */
                printf("Server error, not all address assigned\n");
            }

            if (address_not_assigned > 0)
            {
                /* Actually you would compare address not assigned count with number of IA's added
                   to determine if any addresses were assigned. */

                printf("No servers responded to some or all of our IAs\n");
            }

        }

        /* Regardless if the DHCPv6 Client achieved a bound state, check for DAD
           failures. */
        if (address_failed_dad > 0)
        {
            /* Actually you would compare failed dad count with number of IA's added
               to determine if any addresses were assigned. */

            printf("Some or all of our IAs failed DAD\n");

        }

        /* Successfully assigned IPv6 addresses! */

        /* Get the count of valid IPv6 address obtained by DHCPv6. */
        status = nx_dhcpv6_get_valid_ip_address_count(&dhcp_client, &address_count);

        /* Check status. */
        if (status != NX_SUCCESS)
        {
            error_counter++;
        }

        /* Get the IPv6 address and related lifetimes by address index. This index is the
           index into the DHCPv6 Client address table. Not to be confused with the IP
           instance address table! */
        status = nx_dhcpv6_get_valid_ip_address_lease_time(&dhcp_client, address_index,
                                                           &valid_ipv6_address, 
                                                               &preferred_lifetime,
                                                           &valid_lifetime);

        /* Check status. */
        if (status != NX_SUCCESS)
        {
            error_counter++;
        }

        /* Get the IANA options for when to start renew/rebind requests. These time
           parameters are the same for all IPv6 addresses assigned in the Client
           e.g. IANA returned from Server. */
        status = nx_dhcpv6_get_iana_lease_time(&dhcp_client, &T1, &T2);

        /* Check status. */
        if (status != NX_SUCCESS)
        {
            error_counter++;
        }

        /*****************************************************************************/
        /* These are 'legacy' DHCPv6 services and are for the most part identical to the services
           above except they default to the primary global IPv6 address regardless if the
           Client was assigned more than one global IPv6 address. */

        /* Now check the assigned lease times. */
        status = nx_dhcpv6_get_lease_time_data(&dhcp_client, &T1, &T2,
                                               &preferred_lifetime, &valid_lifetime);

        /* Check status. */
        if (status != NX_SUCCESS)
        {
            error_counter++;
        }

        /* Get the IP address. */
        status = nx_dhcpv6_get_IP_address(&dhcp_client, &valid_ipv6_address);

        /* Check status. */
        if (status != NX_SUCCESS)
        {
            error_counter++;
        }

        /* Bound state. */

#ifdef NX_DHCPV6_CLIENT_RESTORE_STATE

        /* Get the DHCPv6 Client record. */
        nx_dhcpv6_client_get_record(&dhcp_client, &dhcpv6_client_record);

        /* Delete DHCPv6 instance. */
        nx_dhcpv6_client_delete(&dhcp_client);

        /* Delete IP instance. */
        status = nx_ip_delete(&client_ip);

        /* Check for error. */
        if (status)
        {
            error_counter++;
            return;
        }

        /* Create a Client IP instance. */
        status = nx_ip_create(&client_ip, "Client IP", IP_ADDRESS(0, 0, 0, 0),
                              0xFFFFFF00UL, &pool_0, _nx_ram_network_driver,
                              pointer, 2048, 1);

        pointer =  pointer + 2048;

        /* Check for IP create errors. */
        if (status)
        {
            error_counter++;
            return;
        }

        /* Enable UDP traffic for sending DHCPv6 messages. */
        status =  nx_udp_enable(&client_ip);

        /* Check for UDP enable errors. */
        if (status)
        {
            error_counter++;
            return;
        }

        /* Enable ICMP. */
        status =  nx_icmp_enable(&client_ip);

        /* Check for ICMP enable errors. */
        if (status)
        {
            error_counter++;
            return;
        }

        /* Enable the Client IP for IPv6 and ICMPv6 services. */
        status = nxd_ipv6_enable(&client_ip);
        status += nxd_icmp_enable(&client_ip);

        /* Check for IPv6 and ICMPv6 enable errors. */
        if (status)
        {
            error_counter++;
            return;
        }

        /* Establish the link local address for the host. The RAM driver creates
           a virtual MAC address of 0x1122334456. */
        status = nxd_ipv6_address_set(&client_ip, 0, NX_NULL, 10, NULL);

        if (status)
        {
            error_counter++;
            return;
        }

        /* If Duplicate Address Detection (DAD) is enabled in NetX Duo, e.g. #NXDUO_DISABLE_DAD
           not defined, allow time for NetX Duo to verify the address is unique on our network.
         */
        tx_thread_sleep(500);

        /* Create the DHCPv6 Client. */
        status =  nx_dhcpv6_client_create(&dhcp_client, &client_ip, "DHCPv6 Client",
                                          &pool_0, pointer, 2048, dhcpv6_state_change_notify,
                                          dhcpv6_server_error_handler);

        /* Check for errors. */
        if (status)
        {
            error_counter++;
            return;
        }

        /* Update the stack pointer because we need it again. */
        pointer = pointer + 2048;

        /* Restore the DHCPv6 record. */
        nx_dhcpv6_client_restore_record(&dhcp_client, &dhcpv6_client_record, 5);

        /* Resume the DHCPv6 service. */
        nx_dhcpv6_resume(&dhcp_client);
#endif


#ifdef NX_DHCPV6_REQUEST_OPTION

        /* Get the DNS Server address. */
        nx_dhcpv6_get_DNS_server_address(&dhcp_client, 0, &dns_server);

        /* Get the domain name. */
        memset(buffer, 0, sizeof(buffer));

        nx_dhcpv6_get_other_option_data(&dhcp_client, NX_DHCPV6_DOMAIN_NAME_OPTION, buffer, 200); // Try to get DNS info got from DHCPv6 Server

        /* Get the domain name. */
        memset(buffer, 0, sizeof(buffer));

        /* Get the time zone. */
        nx_dhcpv6_get_other_option_data(&dhcp_client, NX_DHCPV6_NEW_POSIX_TIMEZONE_OPTION, buffer, 200); // Try to get DNS info got from DHCPv6 Server
#endif

        /* At some point, we may wish to release the IPv6 address lease e.g. the device
           is leaving the network or powering down. In that case we inform the
           DHCPv6 Server that we are releasing the address lease. */
        status = nx_dhcpv6_request_release(&dhcp_client);

        /* Check status. */
        if (status != NX_SUCCESS)
        {

            error_counter++;
            return;
        }

        /* Send the release message. */
        tx_thread_sleep(100);
    }

    /* Stopping the Client task. */
    status = nx_dhcpv6_stop(&dhcp_client);

    /* Check status. */
    if (status != NX_SUCCESS)
    {

        error_counter++;
        return;
    }

    /* Clear the previously assigned IPv6 addresses from the Client and IP address table. */
    status = nx_dhcpv6_reinitialize(&dhcp_client);

    /* Check status. */
    if (status != NX_SUCCESS)
    {

        error_counter++;
        return;
    }

    /* Start up the Client task again. */
    status = nx_dhcpv6_start(&dhcp_client);

    /* Check status. */
    if (status != NX_SUCCESS)
    {

        error_counter++;
        return;
    }

    /* Begin the request process by sending a Solicit message with the IA created above
       with our preferred IPv6 address. */
    status = nx_dhcpv6_request_solicit(&dhcp_client);
    /* Check status. */
    if (status != NX_SUCCESS)
    {

        error_counter++;
        return;
    }

    /* Wait a bit before releasing the IP address and terminating the client. */
    tx_thread_sleep(500);

    /* Ok, lets stop the application. Again we DO NOT plan
       to keep the IPv6 address we were assigned and need to release it
       back to the DHCPv6 server. */
    status = nx_dhcpv6_request_release(&dhcp_client);

    /* Check for error. */
    if (status != NX_SUCCESS)
    {
        error_counter++;
    }

    /* Now delete the DHCPv6 client and release ThreadX and
       NetX Duo resources back to the system. */
    nx_dhcpv6_client_delete(&dhcp_client);


    return;

}


/* This is the notification from the DHCPv6 Client task that it has changed
   state in the DHCPv6 protocol, for example getting assigned an IPv6 lease and
   achieving the bound state or an IPv6 lease expires and being reset to
   the init state.
*/
VOID dhcpv6_state_change_notify(NX_DHCPV6 *dhcpv6_ptr, UINT old_state, UINT new_state)
{


    /* Increment state change counter. */
    state_changes++;

    /* Check if the Client attempted to request an IPv6 lease but no servers
       responded. */
    if ((old_state == NX_DHCPV6_STATE_SENDING_SOLICIT) && (new_state == NX_DHCPV6_STATE_INIT))
    {

        /* Indication that either DAD failed or IP lease expired. */
        address_not_assigned++;
    }

    /* Check if the Client has been assigned an IPv6 lease. */
    if (new_state == NX_DHCPV6_STATE_BOUND_TO_ADDRESS)
    {
        bound_addresses++;
    }

   /* Check if the Client was bound, but failed the uniqueness check
       (Duplicate Address Detection) and was reset to the INIT state. */
    if ((old_state == NX_DHCPV6_STATE_SENDING_DECLINE) && (new_state == NX_DHCPV6_STATE_INIT))
    {

        /* Indication that DAD failed on Client IA. */
        address_failed_dad++;
    }

    /* Check if the Client was bound, attempted renew the lease but the
       IPv6 address renewal/rebinding failed. */
    if ((old_state == NX_DHCPV6_STATE_SENDING_REBIND) && (new_state == NX_DHCPV6_STATE_INIT))
    {

        /* Indication that the IP lease expired. */
        address_expired++;
    }



    /* Other checks are possible. */

}

/* This is the notification from the DHCPv6 Client task that it received an error
   from the server (status code) in response to the Client's last DHCPv6 message.
*/

VOID dhcpv6_server_error_handler(NX_DHCPV6 *dhcpv6_ptr, UINT op_code, UINT status_code, UINT message_type)
{

    /* Increment the server error count. */
    server_errors++;

    /* This should distinguish between receiving a server error and no server
       available to assign the Client an IPv6 address if the Client fails
       to get assigned an address. */
}

#endif /* FEATURE_NX_IPV6 */