第 2 章 - 安装和使用 Azure RTOS NetX Duo DHCP 服务器

本章描述与安装、设置和使用 NetX Duo DHCP 组件相关的各种问题。

产品分发

NetX Duo DHCP 服务器在 https://github.com/azure-rtos/netxduo 上提供。 该包包含两个源文件和一个 PDF 文件(其中包含本文档),如下所示:

  • nx_dhcp_server.h:NetX Duo DHCP 服务器的头文件
  • nx_dhcp_server.c:NetX Duo DHCP 服务器的 C 源文件
  • nxd_dhcp_server.pdf:NetX Duo DHCP 服务器用户指南
  • demo_netxduo_dhcp.c:NetX Duo DHCP 服务器演示

DHCP 安装

若要使用 NetX DHCP Duo 服务器,应将之前提到的全部分发文件复制到安装了 NetX Duo 的同一目录中。 例如,如果 NetX Duo 安装在“\threadx\arm7\green”目录中,则应将 nxd_dhcp_server.h 和 nxd_dhpc_server.c 文件复制到该目录中 。

使用 NetX Duo DHCP 服务器

NetX Duo DHCP 服务器易于使用。 总体上,应用程序代码必须先包含 tx_api.h 和 nx_api.h,然后包含 nx_dhcp_server.h,才能分别使用 ThreadX 和 NetX Duo 。 在包含 nxd_dhcp_server.h 之后,应用程序代码即可发出本指南后文所指定的 DHCP 函数调用。 在生成过程中,应用程序还必须包含 nxd_dhcp_server.c。 此文件必须采用与其他应用程序文件相同的方式进行编译,并且其对象窗体必须与该应用程序的文件一起链接。 有关使用 NetX Duo DHCP 服务器的更多详细信息,请参阅以下部分:“NetX Duo DHCP 服务器的要求”和“NetX Duo DHCP 服务器的限制”。

请注意,由于 DHCP 利用 NetX Duo UDP 服务,因此在使用 DHCP 之前,必须通过 nx_udp_enable 调用启用 UDP。

NetX Duo DHCP 服务器的要求

NetX Duo DHCP 服务器要求将 UDP 套接字端口分配给已知的 DHCP 端口 67。 若要创建 DHCP 服务器,应用程序必须创建一个数据包池,且其数据包有效负载至少为 548 字节加上 IP 头、UDP 头和以太网头(总计 44 字节,4 字节对齐)。

假定服务器和客户端都使用以太网硬件地址设置:

  • 硬件类型 1
  • 硬件长度 6
  • 跃点数 0

多个客户端会话

NetX Duo DHCP 服务器可以处理多个客户端会话,具体方法是维护一个表并在其中记录处于活动状态的 DHCP 客户端以及客户端所处的“状态”(例如,DHCP 指示 INIT、BOOT、SELECTING、REQUESTING 或 RENEWING 等)。如果会话超时在收到下一条客户端消息之前过期,除非客户端绑定到某个 IP 租约,否则服务器将清除客户端会话数据并将分配的 IP 地址返回给可用池。 如果服务器从同一个客户端收到多条 DISCOVER 消息,则服务器会重置会话超时,并保留为客户端预留的 IP 地址以接受后续 REQUEST 消息。

NetX Duo DHCP 服务器还接受单一状态客户端 DHCP 请求,例如,客户端只发送 REQUEST 消息。 这假定之前已为客户端分配了来自 DHCP 服务器的 IP 租约。

设置特定于接口的网络参数服务器响应

应用程序可以使用 nx_dhcp_set_interface_network_parameters 服务为它处理 DHCP 客户端请求的每个接口设置路由器、子网掩码和 DNS 服务器参数。 否则,这些参数将分别默认为服务器主接口的 IP 网关、其 DHCP 网络子网和 DHCP 服务器 IP 地址。

DHCP 服务器将这些参数包含在其发送给 DHCP 客户端的 DHCP 消息的选项数据中。

为客户端分配 IP 地址

如果客户端 DISCOVER 消息未指定所请求的 IP 地址,则 DHCP 服务器可以使用自己的池中的 IP 地址。 如果服务器没有可用的 IP 地址,则会向客户端发送 NACK 消息。

只要所请求的 IP 地址可用并且可以在服务器 IP 地址数据库中找到,NetX Duo DHCP 服务器就会在客户端 REQUEST 消息中授予该 IP 地址。 应用程序将使用 nx_dhcp_create_server_ip_address_list 服务来创建服务器可分配给 DHCP 客户端的 IP 地址的列表。 如果服务器没有所请求的 IP 地址或者该 IP 地址已分配给另一个主机,则会向客户端发送 NACK 消息。

当 DHCP 服务器收到客户端请求时,它将使用 DHCP 消息的“客户端 MAC 地址”字段中的客户端 MAC 地址来唯一标识该客户端。 如果客户端更改其 MAC 地址或移动到另一个子网,则应向服务器发送 RELEASE 消息以将 IP 地址返回给可用池,并请求处于 INIT 状态的新 IP 地址。

有关详细信息,请参阅“小型示例系统”部分中的图 1.1。 保存到 DHCP 服务器实例的 IP 地址数限制为 DHCP 服务器控制块中服务器地址阵列的大小,并由可配置选项 NX_DHCP_IP_ADDRESS_MAX_LIST_SIZE 定义。

IP 地址租约时间

如果所请求的客户端租约时间小于可配置选项 NX_DHCP_DEFAULT_LEASE_TIME 中定义的服务器默认租约时间,则 DHCP 服务器还会接受该租约时间。 分配给客户端的续订时间和重新绑定时间分别为租约时间的 50% 和 85%,除非租约时间为无限期 (0xFFFFFFFF),在这种情况下,续订时间和重新绑定时间也设置为无限期。

DHCP 服务器超时

DHCP 服务器具有用户可配置的会话超时(中 NX_DHCP_CLIENT_SESSION_TIMEOUT 选项中定义),在此超时期限内,除非会话已完成,否则服务器会等待下一条 DHCP 客户端消息。 当服务器从客户端收到下一条消息时,无论该消息是否与先前发送的消息相同,都会重置超时。

内部错误处理

DHCP 服务器使用 nx_dhcp_listen_for_messages 函数来接收和处理 DHCP 客户端数据包。 如果数据包无效,或者 DHCP 服务器遇到内部错误,则此函数将停止处理当前的 DHCP 客户端数据包。 nx_dhcp_listen_for_messages 将返回错误状态。 在调用此函数以接收下一条 DHCP 客户端消息之前,DHCP 服务器线程会短暂让出对 ThreadX 计划程序的控制权。 在当前版本中,对 nx_dhcp_listen_for_messages 返回的错误状态没有日志记录支持。

选项 55:参数请求列表

必须为 NetX Duo DHCP 服务器配置一组选项,这些选项将加载到由服务器传回给客户端的 OFFER 消息和 DHCPACK 消息中的参数请求选项 (55) 列表。 这些选项应包括客户端网络的关键网络配置数据,并且默认情况下定义为路由器 IP 地址、子网掩码和 DNS 服务器。 该选项列表是一个空格分隔的列表,并在用户可配置选项 NX_DHCP_DEFAULT_SERVER_OPTION_LIST 中定义。 请注意,该列表中指定的选项个数必须等于 NX_DHCP_DEFAULT_OPTION_LIST_SIZE,此选项也由用户定义。

NetX Duo DHCP 服务器的限制

DHCP 消息

在向客户端授予 IP 地址之前,NetX Duo DHCP 服务器不会验证该 IP 地址是否已分配给网络上的其他客户端。 如果有多个 DHCP 服务器,则确实是这样。 根据 RFC 2131,客户端负责验证 IP 地址在其网络上是否唯一(例如,对该地址执行 ping)。 如果不是,则服务器应该会从客户端收到带有 IP 地址的 DECLINE 消息,提示其更新数据库。

NetX Duo DHCP 服务器不会发出 FORCE_RENEW 消息。 IP 地址租约由 DHCP 客户端续订。 但是,DHCP 服务器会监视其数据库中所有已分配的 IP 地址的剩余时间。 IP 地址租约过期后,该 IP 地址将返回给可用的 IP 地址池。 因此,由客户端主动续订/重新绑定其 IP 地址租约。

只要为客户端授予(“绑定”)了 IP 地址租约(或续订了现有 IP 地址租约),就会清除会话数据。 如果客户端数据包经证实为虚假,或者客户端在两次响应之间超时,则会清除会话数据。

每次重启时保存数据

NetX Duo DHCP 服务器将客户端数据(包括 DHCP 请求参数)保存在客户端记录表中。 此表不存储在非易失存储器中,因此,如果 DHCP 服务器主机必须重新启动,则重新启动期间不会保存这些信息。

NetX Duo DHCP 服务器将 IP 地址租约数据保存在 IP 地址表中。 此表不存储在非易失存储器中,因此,如果 DHCP 服务器主机必须重新启动,则重新启动期间不会保存这些信息。

中继代理

NetX Duo DHCP 服务器没有为“中继代理”字段配置任何 IP 地址,因为它不支持网络外 DHCP 请求。

小型示例系统

下面的图 1.1 举例说明了 NetX Duo DHCP 服务器是多么易于使用。 在此示例中,DHCP include 文件 nxd_dhcp_server.h 在第 5 行引入。 DHCP 服务器线程堆栈大小、IP 线程堆栈大小和测试线程堆栈大小都在第 7 到第 13 行中定义。

首先,在第 57 行使用“test_thread_entry”函数创建用于停止、重启和最终删除 DHCP 服务器的可选测试线程任务。 DHCP 服务器控制块“dhcp_server”在第 20 行定义为全局变量。 请注意,将创建服务器数据包池,其数据包有效负载至少与标准 DHCP 消息一样大(548 字节加上 IP 头字节数和 UDP 头字节数)。 成功创建 DHCP 服务器的 IP 实例后,应用程序将在第 96 行创建 DHCP 服务器。 接下来,应用程序为服务器 IP 实例启用 UDP。 在启动 DHCP 服务器之前,应用程序将使用 nx_dhcp_create_server_ip_address_list 服务在第 137 行创建可用 IP 地址列表。 接下来,应用程序使用 nx_dhcp_set_interface_network_parameters 服务在第 138 行设置网络配置参数。当应用程序在第 141 行调用 nx_dhcp_server_start 时,DHCP 服务器将变为可用。 测试线程任务演示了如何停止和重启 DHCP 服务器。

/* This is a small demo of NetX Duo DHCP Server for the high-performance NetX Duo TCP/IP stack.  */

#include   "tx_api.h"
#include   "nx_api.h"
#include   "nxd_dhcp_server.h"

#define     DEMO_TEST_STACK_SIZE         2048
#define     DEMO_SERVER_STACK_SIZE  2048
#define     SERVER_IP_ADDRESS_LIST  "192.168.2.10 192.168.2.11 192.168.2.12"
#define     PACKET_PAYLOAD          1000
#define     PACKET_POOL_SIZE        (PACKET_PAYLOAD * 10)
#define     SERVER_IP_THREAD_STACK    2048


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

TX_THREAD test_thread;
NX_PACKET_POOL server_pool;
NX_IP server_ip;
NX_DHCP_SERVER dhcp_server;


/* Define the counters used in the demo application...  */

ULONG state_changes;


/* Define thread prototypes.  */

void test_thread_entry(ULONG thread_input);
void nx_etherDriver_mcf5485(struct NX_IP_DRIVER_STRUCT *driver_req);


/* 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)
{
    CHAR    *pointer;
    UINT    status;


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

    /* Create the test thread.  */
    status = tx_thread_create(&test_thread, "test thread", test_thread_entry, 0,
            pointer, TEST_STACK_SIZE,  1, 1, TX_NO_TIME_SLICE, TX_DONT_START);

    if (status)
    {
        printf("Error with DHCP test thread create. Status 0x%x\r\n", status);
        return;
    }

    pointer =  pointer + DEMO_STACK_SIZE;

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

    /* Create the DHCP Server packet pool.  */
    status =  nx_packet_pool_create(&server_pool, "NetX Main Packet Pool", PACKET_PAYLOAD,
        pointer, PACKET_POOL_SIZE);
    pointer = pointer + PACKET_POOL_SIZE;

    /* Check for pool creation error.  */
    if (status)
    {
        printf("Error with DHCP server packet pool create. Status 0x%x\r\n", status);
        return;
    }

    /* Create the DHCP Server IP instance.  */
    status = nx_ip_create(&server_ip, "NetX DHCP Server IP", NX_DHCP_SERVER_IP_ADDRESS,
        0xFFFFFF00UL,  &server_pool, nx_etherDriver_mcf5485, pointer,
        R_IP_THREAD_STACK, 1);

    pointer =  pointer + DEMO_IP_THREAD_STACK;

    /* Check for IP create errors.  */
    if (status)
    {
        printf("Error with DHCP server IP task create. Status 0x%x\r\n", status);
        return;
    }

    /* Create the DHCP Server instance.  */
    status =  nx_dhcp_server_create(&dhcp_server, &server_ip, pointer,
                                     DEMO_SERVER_STACK_SIZE,"DHCP Server", &server_pool);

    if (status)
    {
        printf("Error with DHCP server create. Status 0x%x\r\n", status);
        return;
    }

    pointer = pointer + DEMO_SERVER_STACK_SIZE;

    /* Enable ARP and supply ARP cache memory for IP Instance 0.  */
    status =  nx_arp_enable(&server_ip, (void *) pointer, 1024);
    pointer = pointer + 1024;

    /* Check for ARP enable errors.  */
    if (status)
    {
        printf("Error with ARP enable. Status 0x%x\r\n", status);
        return;
    }

    /* Enable UDP traffic.  */
    status =  nx_udp_enable(&server_ip);

    /* Check for UDP enable errors.  */
    if (status)
    {
        printf("Error with ICMP enable. Status 0x%x\r\n", status);
        return;
    }

    /* Enable ICMP to enable the ping utility.  */
    status =  nx_icmp_enable(&server_ip);

    /* Check for errors.  */
    if (status)
    {
        printf("Error with ICMP enable. Status 0x%x\r\n", status);
    }

   status = nx_dhcp_create_server_ip_address_list(&dhcp_server, iface_index,
                                 START_IP_ADDRESS_LIST, END_IP_ADDRESS_LIST, &addresses_added);

   status = nx_dhcp_set_interface_network_parameters(&dhcp_server, iface_index,
                               NX_DHCP_SUBNET_MASK, IP_ADDRESS(10,0,0,1),
                               IP_ADDRESS(10,0,0,1));

    /* Start the DHCP Server.  */
   status =  nx_dhcp_server_start(&dhcp_server);

    tx_thread_resume(&test_thread);
}

/* Define the test thread.  */
void    test_thread_entry(ULONG thread_input)
{
    UINT status;
    UINT keep_spinning;


    /* Just let the test thread be idle till we're ready to shut things down. */
    keep_spinning = 1;
    while(keep_spinning)
    {
        tx_thread_sleep(300);
    }

    printf("Stopping the server...\n");
    status = nx_dhcp_server_stop(&dhcp_server);
    if (status)
    {
        printf("Error with DHCP server stop. Status 0x%x\r\n", status);
        return;
    }

    tx_thread_sleep(500);

    printf("Starting the server...\n");
    status = nx_dhcp_server_start(&dhcp_server);
    if (status)
    {
        printf("Error with DHPC server start. Status 0x%x\r\n", status);
        return;
    }


    tx_thread_sleep(600);

    printf("Stopping the server for good...\n");
    status = nx_dhcp_server_stop(&dhcp_server);
    if (status)
    {
        printf("Error with DHCP server stop. Status 0x%x\r\n", status);
        return;
    }

    tx_thread_sleep(200);


    printf("Deleting the server...\n");
    status = nx_dhcp_server_delete(&dhcp_server);
    if (status)
    {
        printf("Error with DHCP server delete. Status 0x%x\r\n", status);
        return;
    }
}

图 1.1 示例 NetX Duo DHCP 服务器应用程序

配置选项

有多个配置选项用于生成 NetX Duo DHCP 服务器。 以下列表详细介绍了每个配置选项:

  • NX_DISABLE_ERROR_CHECKING:此选项用于禁用基本 DHCP 错误检查。 在调试应用程序后,通常会使用此选项。
  • NX_DHCP_SERVER_THREAD_PRIORITY:此选项用于指定 DHCP 服务器线程的优先级。 默认情况下,此值指定 DHCP 线程以优先级 2 运行。
  • NX_DHCP_TYPE_OF_SERVICE:此选项用于指定 DHCP UDP 请求所需的服务类型。 默认情况下,此值定义为 NX_IP_NORMAL,表示正常的 IP 数据包服务。
  • NX_DHCP_FRAGMENT_OPTION:为 DHCP UDP 请求启用分段。 默认情况下,此值设置为 NX_DONT_FRAGMENT,表示禁用 UDP 分段。
  • NX_DHCP_TIME_TO_LIVE:指定数据包在被丢弃之前可通过的路由器数目。 默认值为 0x80。
  • NX_DHCP_QUEUE_DEPTH:指定 DHCP 服务器套接字在刷新队列之前保留的数据包数。 默认值为 5。
  • NX_DHCP_PACKET_ALLOCATE_TIMEOUT:指定 NetX DHCP Server 等待分配数据包池中的数据包的超时(以计时器时钟周期为单位)。 默认值设置为 NX_IP_PERIODIC_RATE。
  • NX_DHCP_SUBNET_MASK:这是应为 DHCP 客户端配置的子网掩码。 默认值设置为 0xFFFFFF00。
  • NX_DHCP_FAST_PERIODIC_TIME_INTERVAL:这是 DHCP 服务器快速计时器检查会话剩余时间并处理已超时的会话的超时期限(以计时器时钟周期为单位)。
  • NX_DHCP_SLOW_PERIODIC_TIME_INTERVAL:这是 DHCP 服务器慢速计时器检查 IP 地址租约剩余时间并处理已过期的租约的超时期限(以计时器时钟周期为单位)。
  • NX_DHCP_CLIENT_SESSION_TIMEOUT:这是 DHCP 服务器等待接收下一条 DHCP 客户端消息的超时期限(以计时器时钟周期为单位)。
  • NX_DHCP_DEFAULT_LEASE_TIME:这是分配给 DHCP 客户端的 IP 地址租约时间(以秒为单位),并用作计算分配给客户端的续订时间和重新绑定时间的基准。 默认值设置为 0xFFFFFFFF(无限期)。
  • NX_DHCP_IP_ADDRESS_MAX_LIST_SIZE:这是用于保存可分配给客户端的 IP 地址的 DHCP 服务器阵列的大小。 默认值为 20。
  • NX_DHCP_CLIENT_RECORD_TABLE_SIZE:这是用于保存客户端记录的 DHCP 服务器阵列的大小。 默认值为 50。
  • NX_DHCP_CLIENT_OPTIONS_MAX:这是 DHCP 客户端实例用于保存当前会话的参数请求列表中所有所请求选项的阵列的大小。 默认值为 12。
  • NX_DHCP_OPTIONAL_SERVER_OPTION_LIST:这是用于保存 DHCP 服务器在参数请求列表中提供给当前 DHCP 客户端的默认选项列表的缓冲区。 默认值为“1 3 6”。
  • NX_DHCP_OPTIONAL_SERVER_OPTION_SIZE:这是用于保存 DHCP 服务器的默认选项列表的阵列的大小。 默认值为 3。
  • NX_DHCP_SERVER_HOSTNAME_MAX:这是用于保存服务器主机名的缓冲区的大小。 默认值为 32。
  • NX_DHCP_CLIENT_HOSTNAME_MAX:这是用于保存当前 DHCP 服务器客户端会话中的客户端主机名的缓冲区的大小。 默认值为 32。