第 3 章 - Azure RTOS NetX Duo 功能组件

本章从功能角度介绍了高性能 Azure RTOS NetX Duo TCP/IP 堆栈。

执行概述

NetX Duo 应用程序包含五种程序执行:初始化、应用程序接口调用、内部 IP 线程、IP 周期性计时器和网络驱动程序。

注意

NetX Duo 假设存在 ThreadX,具体取决于其线程执行、挂起、周期性计时器和互相排斥设施。

初始化

必须首先调用 nx_system_initialize 服务,然后才能调用任何其他 NetX Duo 服务。 可以从 ThreadX tx_application_define 函数或应用程序线程调用系统初始化。

返回 nx_system_initialize 后,系统便准备就绪,可以创建数据包池和 IP 实例了。 由于创建 IP 实例需要默认数据包池,因此在创建 IP 实例之前,必须至少存在一个 NetX Duo 数据包池。 允许从 ThreadX 初始化函数 tx_application_define 和应用程序线程创建数据包池和 IP 实例。

在内部,创建 IP 实例分为两部分:第一部分在调用方的上下文中执行,可以从 tx_application_define 或应用程序线程的上下文中完成。 该部分包括设置 IP 数据结构和创建各种 IP 资源,包括内部 IP 线程。 第二部分在内部 IP 线程的初始执行过程中完成。 在这种情况下,系统将首先调用在 IP 创建的第一部分中提供的网络驱动程序。 通过从内部 IP 线程调用网络驱动程序,驱动程序可以在初始化处理过程中执行 I/O 和挂起。

网络驱动程序从初始化处理返回后,IP 创建完成。

在 NetX Duo 中初始化 IPv6 还需要其他一些 NetX Duo 服务。 有关这些服务的详细信息,请参阅本章后面的 NetX Duo 中的 IPv6 部分。

注意

可利用 NetX Duo 服务 nx_ip_status_check 获取有关 IP 实例及其主接口状态的信息。 此类状态信息包括是否初始化、启用链路,以及是否解析 IP 地址。 此信息用于同步需要使用新建 IP 实例的应用程序线程。 对于多宿主系统,请参阅多宿主支持。 可利用 nx_ip_interface_status_check 获取有关指定接口的信息。

应用程序接口调用

大部分应用程序发出的调用会由在 ThreadX RTOS 下运行的应用程序线程执行。 但是,也可从 tx_application_define 调用一些初始化、创建和启用服务。 第 4 章 - Azure RTOS NetX Duo 服务说明的“允许调用自”部分指示了可从中调用每项 NetX Duo 服务的来源。

大多数情况下,处理密集型活动(如计算校验和)在调用线程的上下文中完成,不会阻止其他线程访问 IP 实例。 例如,在传输时,系统将在调用基础 IP send 函数之前,在 nx_udp_socket_send 服务中执行 UDP 校验和计算。 对于收到的数据包,会通过 nx_udp_socket_receive 服务(在应用程序线程的上下文中执行)计算UDP 校验和。 这有助于防止由于在低优先级线程中处理大量校验和计算而导致高优先级线程的网络请求停顿。

系统将值(如 IP 地址和端口号)按主机字节顺序传递到 API。 这些值在内部也按主机字节顺序存储。 因此,开发人员可以通过调试程序轻松查看值。 将这些值编入帧进行传输时,它们会转换为网络字节顺序。

内部 IP 线程

如前所述,NetX Duo 中的每个 IP 实例都有自己的线程。 内部 IP 线程的优先级和堆栈大小在 nx_ip_create 服务中定义。 内部 IP 线程在可执行模式下创建。 如果 IP 线程的优先级高于调用线程,则可能会在 IP 创建调用内发生抢占。

内部 IP 线程的入口点位于内部函数 _nx_ip_thread_entry 中。 启动后,内部 IP 线程首先完成网络驱动程序初始化,其中包括对应用程序特定的网络驱动程序进行三次调用。 第一次调用是将网络驱动程序附加到 IP 实例,然后初始化调用,允许网络驱动程序完成初始化过程。 网络驱动程序从初始化返回后(在等待正确设置硬件时可能挂起),内部 IP 线程会再次调用网络驱动程序以启用链路。 网络驱动程序从链路启用调用返回后,内部 IP 线程将进入永久循环检查,检查需要对此 IP 实例处理的各种事件。 此循环中处理的事件包括延迟的 IP 数据包接收、IP 数据包片段汇编、ICMP ping 处理、IGMP 处理、TCP 数据包队列处理、TCP 周期性处理、IP 片段汇编超时和 IGMP 周期性处理。 事件还包括 IPv4 中的地址解析活动、ARP 数据包处理和 ARP 周期性处理,以及 IPv6 中的重复地址检测、路由器请求和邻居发现。

注意

NetX Duo 回调函数(包括侦听和断开连接回调)是从内部 IP 线程(而不是原始调用线程)中调用的。 必须注意,应用程序不得在任何 NetX Duo 回调函数中挂起。

IP 周期性计时器

每个 IP 实例都使用两个 ThreadX 周期性计时器。 第一种是 ARP、IGMP 和 TCP 超时的一秒计时器,还可驱动 IP 片段重组处理。 第二种是 100 毫秒计时器,用于驱动 TCP 重新传输超时和 IPv6 相关操作。

网络驱动程序

NetX Duo 中的每个 IP 实例都有一个主接口,该接口由在 nx_ip_create 服务中指定的设备驱动程序标识。 网络驱动程序负责处理各种 NetX Duo 请求,包括数据包传输、数据包接收及状态和控制请求。

对于多宿主系统,IP 实例具有多个接口,每个接口都有关联的网络驱动程序,可针对各自的接口执行这些任务。

网络驱动程序还必须处理介质上发生的异步事件。 介质中的异步事件包括数据包接收、数据包传输完成和状态更改。 NetX Duo 为网络驱动程序提供了多个访问函数,用于处理各种事件。 这些函数旨在从网络驱动程序的中断服务例程部分调用。 对于 IPv4 网络,网络驱动程序应将接收的所有 ARP 数据包转发给 _nx_arp_packet_deferred_receive 内部函数。 所有 RARP 数据都应包转发给 _nx_arp_packet_deferred_receive 内部函数。 IP 数据包具有两个选项。 如果需要快速分发 IP 数据包,则应将传入的 IP 数据包转发到 _nx_ip_packet_receive 以便立即进行处理。 这极大地提高了 NetX Duo 在处理 IP 数据包方面的性能。 否则,应将 IP 数据包转发到 _nx_ip_packet_deferred_receive。 此服务将 IP 数据包放置在延迟处理队列中,由内部 IP 线程处理,从而尽量缩短 ISR 处理时间。

网络驱动程序还可以推迟中断处理,以耗尽 IP 线程的上下文。 在此模式下,ISR 应保存必要的信息,调用内部函数 _nx_ip_driver_deferred_processing 并确认中断控制器。 此服务通知 IP 线程安排回调设备驱动程序,以完成导致中断的事件的处理。

某些网络控制器能够在硬件中执行 TCP/IP 标头校验和计算与验证,无需占用宝贵的 CPU 资源。 为了利用硬件功能,NetX Duo 支持选择在编译时启用或禁用各种软件校验和计算,以及在运行时开启或关闭校验和计算,但前提是设备驱动程序能够就硬件功能与 IP 层通信。 有关编写 NetX Duo 网络驱动程序的更多详细信息,请参阅第 5 章 - Azure RTOS NetX Duo 网络驱动程序

多宿主支持

NetX Duo 支持使用单个 IP 实例连接多个物理设备的系统。 系统将每个物理接口分配给 IP 实例中的接口控制块。 要使用多宿主系统的应用程序必须将 NX_MAX_PHSYCIAL_INTERFACES 值定义为附加到系统的物理设备的数量,然后重新生成 NetX Duo 库。 默认情况下,NX_MAX_PHYSICAL_INTERFACES 设置为 1,即,在 IP 实例中创建一个接口控制块。

NetX Duo 应用程序使用 nx_ip_create 服务为主要设备创建单个 IP 实例。 对于各个其他网络设备,应用程序使用 nx_ip_interface_attach 服务将设备附加到 IP 实例。

每个网络接口结构都具有 IP 控制块中包含的网络接口的网络信息子集,其中包括接口 IPv4 地址、子网掩码、IP MTU 大小和 MAC 层地址信息。

注意

具有多宿主支持的 NetX Duo 与早期版本的 NetX Duo 向后兼容。 不采用显式接口信息的服务默认为主网络设备。

主接口在 IP 实例列表中的索引为零。 系统将为附加到 IP 实例的每个后续设备分配下一个索引。

所有附加设备都可以使用已启用 IP 实例的所有上层协议服务,包括 TCP、UDP、ICMP 和 IGMP。

大多数情况下,NetX Duo 可以确定传输数据包时要使用的最佳源地址。 源地址选择基于目标地址。 NetX Duo 服务旨在允许应用程序指定要使用的特定源地址,以防目标地址无法确定最合适的源地址。 以多宿主系统为例,应用程序需要将数据包发送到 IPv4 广播或多播目标地址。

专门用于开发多宿主应用程序的服务包括:

  • nx_igmp_multicast_interface_join
  • nx_igmp_multicast_interface_leave
  • nx_ip_driver_interface_direct_command
  • nx_ip_interface_address_get
  • nx_ip_interface_address_mapping_configure
  • nx_ip_interface_address_set
  • nx_ip_interface_attach
  • nx_ip_interface_capability_get
  • nx_ip_interface_capability_set
  • nx_ip_interface_detach
  • nx_ip_interface_info_get
  • nx_ip_interface_mtu_set
  • nx_ip_interface_physical_address_get
  • nx_ip_interface_physical_address_set
  • nx_ip_interface_status_check
  • nx_ip_raw_packet_source_send
  • nx_ipv4_multicast_interface_join
  • nx_ipv4_multicast_interface_leave
  • nx_udp_socket_source_send
  • nxd_ipv6_multicast_interface_join
  • nxd_ipv6_multicast_interface_leave
  • nxd_udp_socket_source_send
  • nxd_icmp_source_ping
  • nxd_ip_raw_packet_source_send
  • nxd_udp_socket_source_send

有关这些服务的详细信息,请参阅 NetX Duo 服务说明

环回接口

环回接口是一种特殊网络接口,未附加到任何物理链路。 环回接口允许应用程序使用 IPv4 环回地址 127.0.0.1 进行通信。若要利用逻辑环回接口,请确保未设置可配置选项 NX_DISABLE_LOOPBACK_INTERFACE

接口控制块

IP 实例中的接口控制块的数量是物理接口数(由 NX_MAX_PHYSICAL_INTERFACES 定义)加上环回接口数(如果已启用)。 接口总数在 NX_MAX_IP_INTERFACES 中定义。

协议分层

NetX Duo 实现的 TCP/IP 是一种分层协议,这意味着较复杂的协议基于较简单的底层协议构建。 在 TCP/IP 中,最低层协议位于链路层,由网络驱动程序处理。 该级别通常面向以太网,但也可以是光纤、串行或几乎任何物理介质。

链路层的上面是网络层。 在 TCP/IP 中就是 IP,它基本上负责尽最大努力在网络上发送和接收简单数据包。 尽管 ICMP 和 IGMP 之类的管理类型协议依赖 IP 发送和接收数据包,但它们通常也被归为网络层。

传输层位于网络层上面。 这一层负责管理网络上主机之间的数据流。 NetX Duo 支持两种类型的传输服务:UDP 和 TCP。 UDP 服务以无连接方式在两个主机之间尽最大努力发送和接收数据,TCP 则在两个主机实体之间提供面向连接的可靠服务。

此分层体现在实际网络数据包中。 TCP/IP 中的每一层都包含名为标头的信息块。 这种用标头包围数据(可能还有协议信息)的方法通常称为数据封装。 图 1 显示了 NetX Duo 分层示例,图 2 显示了为正在发送的 UDP 数据生成的数据封装。

Protocol Layering

图 1。 协议分层

数据包池

以快速和确定的方式分配数据包是实时网络应用程序始终面临的挑战。 考虑到这一点,NetX Duo 提供了对固定大小的网络数据包创建和管理多个池的功能。

由于 NetX Duo 数据包池包含固定大小的内存块,因此不存在任何内部分段问题。 当然,分段会导致具有不确定性本质的行为。 此外,分配和释放 NetX Duo 数据包所需的时间相当于简单的链接列表操作。 数据包分配和解除分配在可用列表的开头完成。 这样可实现最快的链接列表处理。

UDP Data Encapsulation

图 2。 UDP 数据封装

固定大小的数据包池通常存在缺乏灵活性的缺点。 确定最佳的数据包有效负载大小,同时处理最坏情况传入数据包是一项困难任务。 NetX Duo 数据包利用称为“数据包链接”的可选功能解决了这一问题。 实际的网络数据包可以由一个或多个 NetX Duo 数据包链接而成。 此外,数据包标头还会保留指向数据包顶部的指针。 添加其他协议后,只需向后移动指针,就会在数据前面直接写入新标头。 如果没有灵活的数据包技术,堆栈就必须分配另一个缓冲区,并将数据复制到具有新标头的新缓冲区中,这项工作需要占用大量处理时间。

在给定数据包池中,每个数据包的有效负载大小固定,因此,超出有效负载大小的应用程序数据需要将多个数据包链接在一起。 使用用户数据填充数据包时,应用程序应使用 nx_packet_data_append 服务。 此服务会将应用程序数据移动到数据包中。 在数据包不足以容纳用户数据的情况下,系统会分配其他数据包来存储用户数据。 若要使用数据包链接,驱动程序必须能接收链式数据包或从链式数据包进行传输。

对于不需要使用数据包链接功能的嵌入式系统,可以使用 NX_DISABLE_PACKET_CHAIN 生成 NetX Duo 库,以删除数据包链接逻辑。 请注意,IP 分段和重组功能可能需要利用链式数据包功能。 因此,定义 NX_DISABLE_PACKET_CHAIN 需要同时定义 NX_DISABLE_FRAGMENTATION

每个 NetX Duo 数据包内存池都是一项公共资源。 NetX Duo 不会限制数据包池的使用方式。

数据包池内存区域

数据包池的内存区域在创建过程中指定。 与 ThreadX 和 NetX Duo 对象的其他内存区域一样,数据包池内存区域也可位于目标地址空间内的任意位置。

这项功能十分重要,因为可为应用程序提供极大的灵活性。 例如,假设某个通信产品有一个用于网络缓冲区的高速内存区域。 通过将其放入 NetX Duo 数据包内存池,可以轻松利用此内存区域。

创建数据包池

数据包池是在初始化期间或运行时由应用程序线程创建。 NetX Duo 应用程序中的数据包内存池数量没有限制。

双数据包池

通常,默认 IP 数据包池的有效负载大小足以容纳网络接口 MTU 以下的帧大小。 在正常操作期间,IP 线程需要发送诸如 ARP、TCP 控制消息、IGMP 消息、ICMPv6 消息之类的消息。 这些消息使用从 IP 实例中的默认数据包池分配的数据包。 在可用于数据包池的内存量受到限制的内存受限系统中,使用单个数据包池(有效负载大小较大,以匹配 MTU 大小)可能不是最佳解决方案。 NetX Duo 允许应用程序安装有效负载大小较小的辅助数据包池。 一旦安装了辅助数据包池,IP 帮助程序线程就会根据其传输的消息大小,从默认数据包池或辅助池中分配数据包。 对于辅助数据包池,200 字节的有效负载大小适用于 IP 帮助程序线程传输的大多数消息。

默认情况下,生成 NetX Duo 库时不会启用双数据包池。 若要启用该功能,生成库时需要定义 NX_DUAL_PACKET_POOL_ENABLE。 然后,可以通过调用 nx_ip_auxiliary_packet_pool_set 来设置辅助数据包池。

还可以选择创建多个数据包池。 例如,使用最适合预期消息大小的有效负载大小来创建传输数据包池。 由于无法预测接收的数据包的大小,因此,系统会在驱动程序中创建一个有效负载大小设置为驱动程序 MTU 的接收数据包池。

数据包标头 NX_PACKET

默认情况下,NetX Duo 会将数据包标头直接置于数据包有效负载区域之前。 数据包内存池本质上是一系列数据包:标头后面紧跟数据包有效负载。 数据包标头 (NX_PACKET) 和数据包池的布局如图 3 所示。

对于能够执行零复制操作的网络设备驱动程序,通常会将数据包有效负载区域的起始地址编入 DMA 逻辑。 某些 DMA 引擎对有效负载区域具有对齐要求。 为了使有效负载区域的起始地址针对 DMA 引擎或缓存操作正确对齐,用户可以定义 NX_PACKET_ALIGNMENT 符号。

警告

当数据包传输完成时,网络驱动程序必须调用 nx_packet_transmit_release 函数。 此函数首先检查以确保数据包不属于 TCP 输出队列,然后才会真正将其放回可用池。

Packet Header and Packet Pool Layout

图 3。 数据包标头和数据包池布局

数据包标头的字段定义如下。 请注意,此表并不是 NX_PACKET 结构中所有成员的完整列表。

数据包标头 目的
nx_packet_pool_owner 此字段指向具有此特定数据包的数据包池。 释放数据包时,会将其释放到此特定池中。 由于每个数据包内都有池所有权,因此,一个数据报有可能跨多个数据包池的多个数据包。
nx_packet_next 此字段指向同一帧的下一个数据包。 如果为 NULL,则无其他属于该帧的数据包。 此字段还用于保存零碎的数据包,直到可以重组整个数据包。 如果定义了 NX_DISABLE_PACKET_CHAIN,则会将此字段删除。
nx_packet_last 此字段指向同一网络数据包中的最后一个数据包。 如果为 NULL,则此数据包表示整个网络数据包。 如果定义了 NX_DISABLE_PACKET_CHAIN,则会删除此字段。
nx_packet_length 此字段包含整个网络数据包中的总字节数,包括由 nx_packet_next 成员链接在一起的所有数据包的总字节数。
nx_packet_ip_interface 此字段是接口控制块,当接口驱动程序收到数据包以及 NetX Duo 传出数据包时,系统会将其分配给数据包。 接口控制块描述了接口,例如网络地址、MAC 地址、IP 地址以及诸如“已启用链路”和“需要物理映射”之类的接口状态。
nx_packet_data_start 此字段指向此数据包的物理有效负载区域的开头。 它不必紧随 NX_PACKET 标头之后,但这是 nx_packet_pool_create 服务的默认值。
nx_packet_data_end 此字段指向此数据包的物理有效负载区域的结尾。 此字段与 nx_packet_data_start 字段之间的差值表示有效负载大小。
nx_packet_prepend_ptr 此字段指向数据包数据(协议标头或实际数据)的位置,添加在数据包有效负载区域的现有数据包数据(如果有)前面。 该字段必须大于或等于 nx_packet_data_start 指针位置,且小于或等于 nx_packet_append_ptr 指针。

注意

由于性能原因,NetX Duo 假定当数据包传入 NetX Duo 服务进行传输时,预置指针指向长字对齐地址。

数据包标头 目的
nx_packet_append_ptr 此字段指向当前在数据包有效负载区域中的数据的结尾。 它必须位于 nx_packet_prepend_ptr 和 nx_packet_data_end 指向的内存位置之间。此字段与 nx_packet_prepend_ptr 字段之间的差值表示此数据包中的数据量。
nx_packet_packet_pad 此字段定义 4 字节字中的填充长度,以便达到所需的对齐要求。 如果未定义 NX_PACKET_HEADER_PAD,则会删除此字段。 或者,可以使用 NX_PACKET_ALIGNMENT,而不是定义 nx_packet_header_pad。

数据包标头偏移量

定义数据包标头大小,允许足够空间容纳标头大小。 nx_packet_allocate 服务用于分配数据包,并根据指定的数据包类型调整数据包中的预置指针。 NetX Duo 可通过数据包类型了解在协议数据前面插入协议标头(例如 UDP、TCP 或 ICMP)所需的偏移量。

NetX Duo 中定义了以下类型,以考虑到数据包中的 IP 标头和物理层(以太网)标头。 考虑到所需的 4 字节对齐方式,后者假定为 16 字节。 NetX Duo 中仍定义了 IPv4 数据包,以便应用程序为 IPv4 网络分配数据包。 请注意,如果在生成 NetX Duo 库时启用了 IPv6,则会将泛型数据包类型(例如 NX_IP_PACKET)映射到 IPv6 版本。 如果生成 NetX Duo 库时未启用 IPv6,这些泛型数据包类型将映射到 IPv4 版本。

下表显示了启用 IPv6 时定义的符号:

数据包类型
NX_IPv6_PACKET (NX_IP_PACKET) 0x38
NX_UDPv6_PACKET (NX_UDP_PACKET) 0x40
NX_TCPv6_PACKET (NX_TCP_PACKET) 0x4c
NX_IPv4_PACKET 0x24
NX_IPv4_UDP_PACKET 0x2c
NX_IPv4_TCP_PACKET 0x38

下表显示了禁用 IPv6 时定义的符号:

数据包类型
NX_IPv4_PACKET (NX_IP_PACKET) 0x24
NX_IPv4_UDP_PACKET (NX_UDP_PACKET) 0x2c
NX_IPv4_TCP_PACKET (NX_TCP_PACKET) 0x38

请注意,如果定义了 NX_IPSEC_ENABLE,这些值会发生变化。 有关使用 IPsec 的应用程序,请参阅《NetX Duo IPsec 用户指南》了解详细信息。

池容量

数据包池中的数据包数量取决于有效负载大小和提供给数据包池创建服务的内存区域中的总字节数。 通过将数据包大小(包括 NX_PACKET 标头大小、有效负载大小以及适当的对齐方式)除以所提供的内存区域中的总字节数,即可计算出池容量。

有效负载区域对齐

NetX Duo 中的数据包池设计支持零复制。 在设备驱动程序级别,驱动程序能够将有效负载区域直接分配到用于数据接收的缓冲区描述符。 有时,DMA 引擎或缓存同步机制要求有效负载区域的起始地址具有一定的对齐要求。 这可以通过在 NX_PACKET_ALIGNMENT 中定义所需的对齐要求(以字节为单位)来实现。 创建数据包池时,有效负载区域的起始地址将与此值对齐。 默认情况下,起始地址为 4 字节对齐。

线程挂起

在等待空池中的数据包时,应用程序线程可能挂起。 当数据包返回池时,系统将此数据包提供给已挂起的线程并恢复线程。

如果同一数据包池上有多个线程挂起,这些线程将按挂起顺序恢复 (FIFO)。

池统计信息和错误

如果启用,NetX Duo 数据包管理软件会跟踪一些可能对应用程序有用的统计信息和错误。 系统将为数据包池维护以下统计信息和错误报告:

  • 池中的数据包总数
  • 池中的可用数据包数量
  • 数据包分配总数
  • 空池分配请求
  • 空池分配挂起
  • 无效的数据包版本

除非已定义 NX_DISABLE_PACKET_INFO,否则所有这些统计信息和错误报告(池中的数据包总数和可用数据包数除外)都内置在 NetX Duo 库中。 此数据可通过 nx_packet_pool_info_get 服务提供给应用程序。

数据包池控制块 NX_PACKET_POOL

每个数据包内存池的特性都可在其控制块中找到。 控制块包含有用的信息,例如可用数据包的链接列表、可用数据包数量以及此池中数据包的有效负载大小。 此结构在 nx_api.h 文件中定义。

数据包池控制块可以位于内存中的任意位置,但最常见的是通过在任何函数的作用域外部定义控制块来使其成为全局结构。

IPv4 协议

NetX Duo 的 Internet 协议 (IP) 组件负责通过 Internet 发送和接收 IPv4 数据包。 在 NetX Duo 中,该组件最终负责使用底层网络驱动程序发送和接收 TCP、UDP、ICMP 和 IGMP 消息。

NetX Duo 支持 IPv4 协议 (RFC 791) 和 IPv6 协议 (RFC 2460)。 本部分介绍 IPv4。 下一部分将介绍 IPv6。

IPv4 地址

Internet 上的每个主机都具有唯一的 32 位标识符,称为 IP 地址。 图 4 介绍了五类 IPv4 地址。 五类 IPv4 地址的范围如下:

范围
A 0.0.0.0 到 127.255.255.255
B 128.0.0.0 到 191.255.255.255
C 192.0.0.0 到 223.255.255.255
D 224.0.0.0 到 239.255.255.255
E 240.0.0.0 到 247.255.255.255

Diagram of the IPv4 Address Structure.

图 4. IPv4 地址结构

还有三种类型的地址规范:单播、广播和多播。 单播地址是用于标识 Internet 上特定主机的 IPv4 地址。 单播地址可以是源 IPv4 地址或目标 IPv4 地址。 广播地址可标识特定网络或子网络上的所有主机,而且只能用作目标地址。 通过将地址的主机 ID 部分设置为全 1 来指定广播地址。 多播地址(D 类)指定 Internet 上的动态主机组。 多播组的成员可以根据需要随时加入和离开。

重要

只有 UDP over IPv4 之类的无连接协议才能使用广播和多播组的有限广播功能。

重要

宏 IP_ADDRESS 在 nx_api.h 中定义。 它允许使用逗号(而非句点)轻松指定 IPv4 地址。 例如,IP_ADDRESS(128,0,0,0) 指定了图 4 中的第一个 B 类地址。

IPv4 网关地址

网络网关协助网络中的主机中继发往本地域以外的目标的数据包。 每个节点都知道要发往的下一个跃点:它的某个邻居,或者遵循预编程的静态路由表。 但是,如果这些方法失败,则节点应将数据包转发到默认网关,其中包含有关如何将数据包路由到目标的详细信息。 请注意,默认网关必须可通过附加到 IP 实例的某个物理接口直接访问。 应用程序调用 nx_ip_gateway_address_set 来配置 IPv4 默认网关地址。 使用 nx_ip_gateway_address_get 服务来检索当前的 IPv4 网关设置。 应用程序应使用 nx_ip_gateway_address_clear 服务来清除网关设置。

IPv4 标头

对于要通过 Internet 发送的任何 IPv4 数据包,其中必须有 IPv4 标头。 当较高级别的协议(UDP、TCP、ICMP 或 IGMP)调用 IP 组件来发送数据包时,IPv4 传输模块会在数据前面放置 IPv4 标头。 相反,从网络接收 IP 数据包时,IP 组件在传递到更高级别的协议之前会从数据包中删除 IPv4 标头。 图 5 显示了 IP 标头格式。

IPv4 Header Format

图 5. IPv4 标头格式

重要

TCP/IP 实现中的所有标头都应采用 big endian 格式。 在这种格式中,字的最高序字节驻留在最低序字节地址中。 例如,IP 标头的 4 位版本和 4 位标头长度必须位于标头的第一个字节中。

IPv4 标头的字段定义如下:

IPv4 标头字段 目的
4 位版本 此字段包含此标头代表的 IP 版本。 对于 NetX Duo 支持的 IP 版本 4,此字段的值为 4。
4 位标头长度 此字段指定 IP 标头中的 32 位字的数量。 如果没有任何选项字,则此字段的值为 5。
8 位服务类型(TOS) 此字段为此 IP 数据包指定请求的服务类型。 有效请求如下:
- 正常:0x00
- 最小延迟:0x00
- 最大数据:0x08
- 最大可靠性:0x04
- 最小成本:0x02
16 位总长度 此字段包含 IP 数据报(包括 IP 标头)的总长度(以字节为单位)。 IP 数据报是 TCP/IP Internet 上的基本信息单元。 除了数据之外,它还包含目标和源地址。 由于这是一个 16 位字段,IP 数据报最大为 65,535 字节。
16 位标识 此字段表示对主机发送的各个 IP 数据报进行唯一标识的数字。 此数字通常在发送 IP 数据报之后递增。 非常适合汇编接收的 IP 数据包片段。
3 位标志 此字段包含 IP 分段信息。 第 14 位是“请勿分段”位。 如果设置了此位,则不会对传出 IP 数据报分段。 第 13 位是“更多片段”位。 如果设置了此位,则会有更多片段。 如果此位是明文,则这是 IP 数据包的最后一个片段。
13 位片段偏移 此字段包含片段偏移中的前 13 位。 因此,只允许在 8 字节边界上使用片段偏移。 分段 IP 数据包的第一个片段会设置“更多片段”位,且偏移量为 0。
8 位生存时间 (TTL) 此字段包含该数据报可能经过的路由器数量,这基本上限制了数据报的生存期。
8 位协议 此字段指定哪项协议正在使用 IP 数据报。 下面列出了有效协议及其值:
- ICMP:0x01
- IGMP:0x02
- TCP:0X06
- UDP:0X11
16 位校验和 此字段包含仅覆盖 IP 标头的 16 位校验和。 在更高级别的协议中还有额外的校验和,可覆盖 IP 有效负载。
32 位源 IP 地址 此字段包含发送方的 IP 地址,而且始终是主机地址。
32 位目标 IP 地址 此字段包含接收方的 IP 地址,如果地址属于广播或多播地址,则为多个接收方的 IP 地址。

创建 IP 实例

IP 实例是在初始化期间或在运行时由应用程序线程创建的。 即使应用程序只打算使用 IPv6 网络,nx_ip_create 服务也会定义初始 IPv4 地址、网络掩码、默认数据包池、介质驱动程序以及内部 IP 线程的内存和优先级。 如果应用程序初始化 IP 实例时,将其 IPv4 地址设置为无效地址 (0.0.0.0),则假定该接口地址将在以后通过手动配置(利用 RARP、DHCP 或类似协议)进行解析。

对于具有多个网络接口的系统,将在调用 nx_ip_create 时指定主接口。 可以通过调用 nx_ip_interface_attach 将各个其他接口附加到同一 IP 实例。 此服务在接口控制块中存储有关网络接口(例如 IP 地址、网络掩码)的信息,并将驱动程序实例与 IP 实例中的接口控制块关联。 驱动程序收到数据包时,首先需要将接口信息存储在 NX_PACKET 结构中,然后将其转发到 IP 接收逻辑。 请注意,必须首先创建 IP 实例,然后才能附加任何接口。

调用 nx_ip_create 后,不会启动 IPv6 服务。 要使用 IPv6 服务的应用程序必须调用 nx_ipv6_enable 服务来启动 IPv6。

在 IPv6 网络上,IP 实例中的每个接口都可以有多个 IPv6 全局地址。 除了使用 DHCPv6 进行 IPv6 地址分配外,设备还可以使用无状态地址自动配置。 有关详细信息,请参阅本章后面的“IP 控制块”和“IPv6 地址解析”部分。

IP 发送

NetX Duo 中的 IP 发送处理非常简单。 数据包中的预置指针向后移动以容纳 IP 标头。 IP 标头已完成(其中所有选项均由调用协议层指定),IP 校验和以内联方式计算(仅适用于 IPv4 数据包),并将数据包调度到关联的网络驱动程序。 此外,传出分段也在 IP 发送处理中进行协调。

对于 IPv4,如果目标 IP 地址需要物理映射,NetX Duo 将发起 ARP 请求。 IPv6 使用邻居发现进行 IPv6 地址到物理地址的映射。

注意

对于 IPv4 连接,需要 IP 地址解析(即物理映射)的数据包会排在 ARP 队列中,直到队列中排队的数据包数超过由 NX_ARP_MAX_QUEUE_DEPTH 符号定义的 ARP 队列深度。 如果达到队列深度,NetX Duo 将删除队列中最旧的数据包,并继续等待排队的其余数据包的地址解析。 另一方面,如果未解析某个 ARP 条目,则会在 ARP 条目超时后释放该 ARP 条目上挂起的数据包。

对于具有多个网络接口的系统,NetX Duo 会基于目标 IP 地址选择一个接口。 选择接口时遵循以下过程:

  1. 如果发送方指定了一个传出接口且该接口有效,则使用该接口。
  2. 如果目标地址是 IPv4 广播或多播地址,则使用第一个启用的物理接口。
  3. 如果在静态路由表中找到了目标地址,则使用与该网关关联的接口。
  4. 如果目标在链路上,则使用链路上的接口。
  5. 如果目标地址是链路本地地址 (169.254.0.0/16),则使用第一个有效接口。
  6. 如果配置了默认网关,则使用与默认网关关联的接口传输数据包。
  7. 最后,如果某个有效接口 IP 地址是链路本地地址 (169.254.0.0/16),则将该接口用作传输的源地址。
  8. 如果上述所有操作均失败,则会删除输出数据包。

IP 接收

IP 接收处理可从网络驱动程序或内部 IP 线程调用(用于处理延迟接收的数据包队列上的数据包)。 IP 接收处理会检查协议字段,并尝试将数据包调度给合适的协议组件。 在实际调度数据包之前,系统会通过将预置指针向前移动到 IP 标头之后来删除 IP 标头。

IP 接收处理还会检测分段 IP 数据包,并在启用分段的情况下执行必要的步骤进行重组。 如果需要分段但未启用,则会删除数据包。

NetX Duo 根据数据包中指定的接口来确定适当的网络接口。 如果数据包接口为 NULL,则 NetX Duo 默认为主接口。 这样做是为了确保与旧版 NetX Duo 以太网驱动程序兼容。

Raw IP 发送

Raw IP 数据包是一个 IP 帧,其中包含 NetX Duo 不直接支持(和处理)的上层协议有效负载。 原始数据包允许开发人员定义自己的基于 IP 的应用程序。 如果使用 nx_ip_raw_packet_send 服务启用了 Raw IP 数据包处理,则应用程序可以直接使用 nxd_ip_raw_packet_send 服务发送 Raw IP 数据包。 在 IPv6 网络上传输单播数据包时,NetX Duo 会根据目标地址自动确定用于发送数据包的最佳源 IPv6 地址。 但是,如果目标地址是多播地址(或 IPv4 中的广播地址),NetX Duo 将默认使用第一个(主)接口。 因此,若要在辅助接口上发送此类数据包,应用程序必须使用 nx_ip_raw_packet_source_send 服务来指定要用于传出数据包的源地址。

Raw IP 接收

如果启用了 Raw IP 数据包处理,应用程序可以通过 nx_ip_raw_packet_receive 服务来接收 Raw IP 数据包。 所有传入数据包都根据 IP 标头中指定的协议进行处理。 如果协议指定 UDP、TCP、IGMP 或 ICMP,NetX Duo 将使用适用于数据包协议类型的处理程序来处理数据包。 如果协议不属于这些协议之一,而且已启用 Raw IP 接收,则传入数据包将被放入原始数据包队列,等待应用程序通过 nx_ip_raw_packet_receive 服务接收。 此外,在等待原始 IP 数据包时,应用程序线程可能会挂起并具有可选超时。 可以在原始数据包队列上排队的数据包数量是有限的。 最大值是在 NX_IP_RAW_MAX_QUEUE_DEPTH 中定义的,其默认值为 20。 应用程序可以通过调用 nx_ip_raw_receive_queue_max_set 服务来更改最大值。

或者,可以使用 NX_ENABLE_IP_RAW_PACKET_FILTER 来生成 NetX Duo 库。在此操作模式下,应用程序提供一个回调函数,系统每次收到具有未处理的协议类型的数据包时都会调用该函数。 IP 接收逻辑将数据包转发到用户定义的原始数据包接收筛选器例程。 筛选器例程决定是否保留原始数据包以供将来处理。 回调例程的返回值指示数据包是否已由原始数据包接收筛选器处理。 如果数据包已由回调函数处理,则应在应用程序处理完数据包后释放数据包。 否则,NetX Duo 将负责释放数据包。 有关如何使用原始数据包筛选器函数的详细信息,请参阅 nx_ip_raw_packet_filter_set。

注意

NetX Duo 的 BSD 包装器函数依赖原始数据包筛选器函数来处理 BSD 原始套接字。 因此,若要支持 BSD 包装器中的原始套接字,必须在定义了 NX_ENABLE_IP_RAW_PACKET_FILTER 的情况下生成 NetX Duo 库,并且应用程序不应使用 nx_ip_raw_packet_filter_set 来安装自己的原始数据包筛选器函数。

默认数据包池

创建过程中,将为每个 IP 实例提供默认数据包池。 此数据包池用于为 ARP、RARP、ICMP、IGMP、各种 TCP 控制数据包(SYN、ACK 等)、邻居发现、路由器发现和重复地址检测分配数据包。 如果当 NetX Duo 需要分配数据包时,默认数据包池为空,NetX Duo 可能必须中止特定操作并返回错误消息(如果可能)。

IP 帮助程序线程

每个 IP 实例都有一个帮助程序线程。 此线程负责进行所有延迟数据包处理和所有周期性处理。 IP 帮助程序线程在 nx_ip_create 中创建。这是为线程提供堆栈和优先级的位置。 请注意,IP 帮助程序线程中的第一个处理是完成与 IP 创建服务关联的网络驱动程序初始化。 完成网络驱动程序初始化后,帮助程序线程会启动一个无限循环来处理数据包和周期性请求。

重要

如果在 IP 帮助程序线程中发现了无法解释的行为,则第一个调试步骤是在 IP 创建服务期间增加堆栈大小。 如果堆栈太小,IP 帮助程序线程可能会覆盖内存,这可能导致异常问题。

线程挂起

应用程序线程在尝试接收 Raw IP 数据包时可能挂起。 收到原始数据包后,系统会向第一个挂起的线程提供新数据包,并恢复该线程。 用于接收数据包的 NetX Duo 服务都具有可选挂起超时。 收到数据包或超时过期后,应用程序线程恢复且具有适当的完成状态。

IP 统计信息和错误

如果启用,NetX Duo 会跟踪一些可能对应用程序有用的统计信息和错误。 系统将为每个 IP 实例维护以下统计信息和错误报告:

  • 发送的 IP 数据包总数
  • 发送的 IP 字节总数
  • 接收的 IP 数据包总数
  • 接收的 IP 字节总数
  • IP 无效数据包总数
  • 删除的 IP 接收数据包总数
  • IP 接收校验和错误总数
  • 删除的 IP 发送数据包总数
  • 发送的 IP 片段总数
  • 接收的 IP 片段总数

所有这些统计信息和错误报告都可通过 nx_ip_info_get 服务提供给应用程序。

IP 控制块 NX_IP

每个 IP 实例的特性都可在其控制块中找到。 控制块包含有用的信息,例如每个网络设备的 IP 地址和网络掩码,以及邻居 IP 和物理硬件地址映射表。 此结构在 nx_api.h 文件中定义。 如果启用了 IPv6,此结构还会包含一个 IPv6 地址数组,其地址数量由用户可配置选项 NX_MAX_IPV6_ADDRESSES 指定。 默认值允许每个物理网络接口具有三个 IPv6 地址。

IP 实例控制块可以位于内存中的任意位置,但最常见的是通过在任何函数的作用域外部定义控制块来使其成为全局结构。

静态 IPv4 路由

使用静态路由功能,应用程序可以为特定的网络外目标 IP 地址指定 IPv4 网络和下一个跃点地址。 如果启用静态路由,NetX Duo 将在静态路由表中搜索与待发送数据包的目标地址匹配的条目。 如果未找到匹配项,NetX Duo 将搜索物理接口列表,并根据目标 IP 地址和网络掩码选择源 IP 地址和下一个跃点地址。 如果目标与附加到 IP 实例的网络驱动程序的任何 IP 地址都不匹配,NetX Duo 会选择直接连接到默认网关的接口,使用该接口的 IP 地址作为源地址,并使用默认网关作为下一个跃点。

可以分别使用 nx_ip_static_route_add 和 nx_ip_static_route_delete 服务在静态路由表中添加和删除条目。 若要使用静态路由,主机应用程序必须通过定义 NX_ENABLE_IP_STATIC_ROUTING 来启用此功能

注意

向静态路由表添加条目时,NetX Duo 会检查表中是否存在指定目标地址的匹配条目。 如果存在,它会优先选择网络掩码中网络较小(前缀较长)的条目。

IPv4 转发

如果传入的 IPv4 数据包不是发往该节点的,并且启用了 IPv4 转发功能,NetX Duo 会尝试通过其他接口转发该数据包。

IP 分段

网络设备可能会限制传出数据包的大小。 此限制称为最大传输单元 (MTU)。 IP MTU 是链路层驱动程序能够在不对 IP 数据包分段的情况下传输的最大 IP 帧大小。 在设备驱动程序初始化阶段,驱动程序模块必须通过 nx_ip_interface_mtu_set 服务配置 IP MTU 大小。

尽管不建议,但应用程序生成的数据报可能大于设备支持的底层 IP MTU。 传输此类 IP 数据报之前,IP 层必须对这些数据包分段。 收到分段 IP 帧后,接收端必须存储具有相同分段 ID 的所有分段 IP 帧,并且按顺序重组。 如果 IP 接收逻辑无法收集所有片段以及时还原原始 IP 帧,系统将释放所有片段。 由上层协议来检测此类数据包丢失并从中恢复。

IP 分段适用于 IPv4 和 IPv6 数据包。

为了支持 IP 分段和重组操作,系统设计器必须使用 nx_ip_fragment_enable 服务在 NetX Duo 中启用 IP 分段功能。 如果未启用此功能,系统会放弃传入的分段 IP 数据包,以及超过网络驱动程序 MTU 的数据包。

注意

生成 NetX Duo 库时,可以通过定义 NX_DISABLE_FRAGMENTATION 来完全删除 IP 分段逻辑。 这样做有助于减少 NetX Duo 的代码大小。 请注意,在这种情况下,IPv4 和 IPv6 分段/重组函数均处于禁用状态。

注意

如果定义了 NX_DISABLE_CHAINED_PACKET,则必须禁用 IP 分段。

注意

在 IPv6 网络中,如果数据报的大小超过其最小 MTU 大小,则路由器不会对数据报进行分段。 因此,发送设备需要确定源与目标之间的最小 MTU,并确保 IP 数据报的大小不超过路径 MTU。 在 NetX Duo 中,可以通过在定义了符号 NX_ENABLE_IPV6_PATH_MTU_DISCOVERY 的情况下生成 NetX Duo 库来启用 IPv6 路径 MTU 发现。

IPv4 中的地址解析协议 (ARP)

地址解析协议 (ARP) 负责将 32 位 IPv4 地址动态映射到底层物理介质的 IPv4 地址 (RFC 826)。 以太网是最典型的物理介质,可支持 48 位地址。 ARP 需求取决于向 nx_ip_create 服务提供的网络驱动程序。 如果需要物理映射,网络驱动程序必须使用 nx_interface_address_mapping_needed 服务正确配置驱动程序接口。

ARP 启用

若要让 ARP 正常运行,首先必须由应用程序通过 nx_arp_enable 服务启用它。 此服务为 ARP 处理设置各种数据结构,包括从提供给 ARP 启用服务的内存创建 ARP 缓存区域。

ARP 缓存

我们可以将 ARP 缓存视为内部 ARP 映射数据结构的数组。 每个内部结构都能维持 IP 地址与物理硬件地址之间的关系。 此外,每个数据结构都具有链路指针,因此可以成为多个链接列表的一部分。

如果 ARP 表中存在映射,应用程序可以通过使用 nx_arp_ip_address_find 服务提供硬件 MAC 地址,从而在 ARP 缓存中查找 IP 地址。 同样,nx_arp_hardware_address_find 服务会为给定 IP 地址返回 MAC 地址。

ARP 动态条目

默认情况下,ARP 启用服务会将 ARP 缓存中的所有条目放入可用动态 ARP 条目列表。 当检测到对未映射 IP 地址的发送请求时,NetX Duo 会从该列表中分配动态 ARP 条目。 分配后,系统将设置 ARP 条目,并向物理介质发送 ARP 请求。

还可使用 nx_arp_dynamic_entry_set 服务创建动态条目。

重要

如果所有动态 ARP 条目都在使用中,则最近使用最少的 ARP 条目会被替换成新的映射。

ARP 静态条目

应用程序还可以使用 nx_arp_static_entry_create 服务设置静态 ARP 映射。 该服务从动态 ARP 条目列表中分配 ARP 条目,并将其与应用程序提供的映射信息一同放入静态列表。 静态 ARP 条目不会重用或老化。 应用程序可以使用 nx_arp_static_entry_delete 服务删除静态条目。 若要删除 ARP 表中的所有静态条目,应用程序可以使用 nx_arp_static_entries_delete 服务。

自动 ARP 条目

在对等节点响应 ARP 请求之后,NetX Duo 会记录对等节点的 IP/MAC 映射。 NetX Duo 还实现了自动 ARP 条目功能,它将根据网络中未经请求的 ARP 请求记录对等节点的 IP/MAC 地址映射。 借助此功能,系统可利用对等节点信息填充 ARP 表,从而减少完成 ARP 请求/响应周期所需的延迟。 但启用自动 ARP 存在不利之处,因为在繁忙的网络上,如果本地链路中存在众多节点,ARP 表往往会很快填满,最终导致 ARP 条目替换。

此功能默认启用。 若要禁用此功能,必须在定义了 NX_DISABLE_ARP_AUTO_ENTRY 符号的情况下编译 NetX Duo 库。

ARP 消息

如前所述,当 IP 任务检测到 IP 地址需要映射时,系统会发送 ARP 请求消息。 ARP 请求会定期(每 NX_ARP_UPDATE_RATE 秒)发送,直到收到相应的 ARP 响应。 在放弃 ARP 尝试之前,总共会发出 NX_ARP_MAXIMUM_RETRIES 次 ARP 请求。 收到 ARP 响应后,系统会将关联的物理地址信息存储在缓存中的 ARP 条目中。

对于多宿主系统,NetX Duo 根据指定的目标地址确定要发送 ARP 请求和响应的接口。

注意

传出 IP 数据包会在 NetX Duo 等待 ARP 响应时排队。 排队的传出 IP 数据包数量由 NX_ARP_MAX_QUEUE_DEPTH 常量定义。

NetX Duo 还会响应本地 IPv4 网络上其他节点的 ARP 请求。 当系统发出外部 ARP 请求,且该请求与接收 ARP 请求的接口的当前 IP 地址匹配时,NetX Duo 会生成包含当前物理地址的 ARP 响应消息。

图 6 显示了以太网 ARP 请求和响应格式,如下所述。

请求/响应字段 用途
以太网目标地址 此 6 字节字段包含 ARP 响应的目标地址,对于 ARP 请求则为广播地址(全为 1)。 此字段由网络驱动程序设置。
以太网源地址 此 6 字节字段包含 ARP 请求或响应发送方的地址,由网络驱动程序设置。
帧类型 此 2 字节字段包含显示的以太网帧类型,对于 ARP 请求和响应,该字段等于 0x0806。 这是网络驱动程序负责设置的最后一个字段。
硬件类型 此 2 字节字段包含硬件类型,0x0001 表示以太网。
协议类型 此 2 字节字段包含协议类型,0x0800 表示 IP 地址。
硬件大小 此 1 字节字段包含硬件地址大小,以太网地址为 6。

Diagram of the ARP Packet Format.

图 6. ARP 数据包格式

请求/响应字段 目的
协议大小 此 1 字节字段包含 IP 地址大小,IP 地址为 4。
操作代码 此 2 字节字段包含该 ARP 数据包的操作。 使用值 0x0001 指定 ARP 请求,而值 0x0002 表示 ARP 响应。
发送方以太网地址 此 6 字节字段包含发送方的以太网地址。
发送方 IP 地址 此 4 字节字段包含发送方的 IP 地址。
目标以太网地址 此 6 字节字段包含目标以太网地址。
目标 IP 地址 此 4 字节字段包含目标 IP 地址。

注意

ARP 请求和响应是以太网级别的数据包。 所有其他 TCP/IP 数据包都由 IP 数据包标头封装。

注意

TCP/IP 实现中的所有 ARP 消息都应采用 big endian 格式。 在这种格式中,字的最高序字节驻留在最低序字节地址中。

ARP 老化

NetX 支持自动完成动态 ARP 条目失效。 NX_ARP_EXPIRATION_RATE 指定已建立的 IP 地址到物理映射保持有效的秒数。 过期后,系统会从 ARP 缓存中删除 ARP 条目。 下一次尝试发送到相应的 IP 地址会导致发起新的 ARP 请求。 将 NX_ARP_EXPIRATION_RATE 设置为 0 将禁用 ARP 老化,这是默认配置。

ARP 防御

当系统收到 ARP 请求或 ARP 响应数据包,并且发送方具有相同 IP 地址(与此节点的 IP 地址冲突)时,NetX Duo 会将该地址的 ARP 请求作为防御发送。 如果在 10 秒内多次收到冲突 ARP 数据包,则 NetX Duo 不再发送更多防御数据包。 可以通过 NX_ARP_DEFEND_INTERVAL 重新定义 10 秒的默认间隔。 此行为遵循在 RFC5227 2.4 (c) 中指定的策略。 由于 Windows XP 会忽略 ARP 公告作为 ARP 探测的响应,因此,用户可以定义 NX_ARP_DEFEND_BY_REPLY,将 ARP 响应作为额外的防御发送。

ARP 统计信息和错误

如果启用,NetX Duo ARP 软件会跟踪一些可能对应用程序有用的统计信息和错误。 系统将为每个 IP 的 ARP 处理维护以下统计信息和错误报告:

  • 发送的 ARP 请求总数
  • 接收的 ARP 请求总数
  • 发送的 ARP 响应总数
  • 接收的 ARP 响应总数
  • ARP 动态条目总数
  • ARP 静态条目总数
  • ARP 过期条目总数
  • ARP 无效消息总数

所有这些统计信息和错误报告都可通过 nx_arp_info_get 服务提供给应用程序。

IPv4 中的反向地址解析协议 (RARP)

反向地址解析协议 (RARP) 是一种用于请求对主机的 32 位 IP 地址进行网络分配的协议 (RFC 903)。 这项工作可通过 RARP 请求来完成,并且会定期进行,直到网络成员在 RARP 响应中向主机网络接口分配 IP 地址为止。 应用程序通过 nx_ip_create 服务创建 IP 地址为零的 IP 实例。 如果应用程序已启用 RARP,则可以使用 RARP 协议,向可通过 IP 地址为零的接口访问的网络服务器请求 IP 地址。

RARP 启用

若要使用 RARP,应用程序必须创建 IP 地址为零的 IP 实例,然后使用 nx_rarp_enable 服务启用 RARP。 对于多宿主系统,在与 IP 实例关联的网络设备中,至少有一个设备的 IP 地址必须为零。 对于需要 IP 地址的 NetX Duo 系统,RARP 处理会定期发送 RARP 请求消息,直到收到包含网络指定 IP 地址的有效 RARP 回复。 至此,RARP 处理完成。

启用 RARP 后,系统会在解析所有接口地址后自动将其禁用。 应用程序可以使用 nx_rarp_disable 服务强制 RARP 终止。

RARP 请求

RARP 请求数据包的格式与图 6 所示的 ARP 数据包几乎完全相同。唯一的区别是“帧类型”字段为 0x8035,而“操作代码”字段为 3,指定了 RARP 请求。 如前所述,系统每隔 NX_RARP_UPDATE_RATE 秒定期发送 RARP 请求,直到收到包含网络分配 IP 地址的 RARP 回复。

注意

TCP/IP 实现中的所有 RARP 消息都应采用 big endian 格式。 在这种格式中,字的最高序字节驻留在最低序字节地址中。

RARP 回复

系统从网络收到 RARP 回复消息,其中包含此主机的网络分配 IP 地址。 RARP 回复数据包的格式与图 6 所示的 ARP 数据包几乎完全相同。 唯一的区别是“帧类型”字段为 0x8035,而“操作代码”字段为 4,指定了 RARP 回复。 收到消息后,系统会在 IP 实例中设置 IP 地址,禁用周期性 RARP 请求,而且 IP 实例现在可用于正常网络操作。

对于多宿主主机,系统会向请求网络接口应用 IP 地址。 如果还有其他网络接口仍在请求 IP 地址分配,周期性 RARP 服务将继续运行,直到解决所有接口 IP 地址请求为止。

注意

在 RARP 处理完成之前,应用程序不应使用该 IP 实例。 应用程序可以使用 nx_ip_status_check 来等待 RARP 完成。 对于多宿主系统,在请求接口上完成 RARP 处理之前,应用程序不应使用该接口。 可以使用 nx_ip_interface_status_check 服务检查辅助设备上的 IP 地址状态。

RARP 统计信息和错误

如果启用,NetX Duo RARP 软件会跟踪一些可能对应用程序有用的统计信息和错误。 系统将为每个 IP 的 RARP 处理维护以下统计信息和错误报告:

  • 发送的 RARP 请求总数
  • 接收的 RARP 响应总数
  • RARP 无效消息总数

所有这些统计信息和错误报告都可通过 nx_rarp_info_get 服务提供给应用程序。

Internet 控制消息协议 (ICMP)

IPv4 的 Internet 控制消息协议 (ICMP) 仅限于在 IP 网络成员之间传递错误和控制信息。 IPv6 的 Internet 控制消息协议 (ICMPv6) 还处理错误和控制信息,它是地址解析协议(例如重复地址检测 (DAD) 和无状态地址自动配置)所必需的。

与大多数其他应用程序层(例如,TCP/IP)消息一样,ICMP 和 ICMPv6 消息由带有 ICMP(或 ICMPv6)协议名称的 IP 标头封装。

ICMP 统计信息和错误

如果启用,NetX Duo 会跟踪一些可能对应用程序有用的 ICMP 统计信息和错误。 系统将为每个 IP 的 ICMP 处理维护以下统计信息和错误报告:

  • 发送的 ICMP Ping 总数
  • ICMP Ping 超时总数
  • 挂起的 ICMP Ping 线程总数
  • 接收的 ICMP Ping 响应总数
  • ICMP 校验和错误总数
  • ICMP 未处理消息总数

所有这些统计信息和错误报告都可通过 nx_icmp_info_get 服务提供给应用程序。

NetX Duo 中的 ICMPv4 服务

ICMPv4 启用

应用程序必须先调用 nx_icmp_enable 服务来启用 ICMPv4 处理,NetX Duo 才能处理 ICMPv4 消息。 完成此操作后,应用程序可以发出 ping 请求和字段传入 ping 数据包。

ICMPv4 回显请求

回显请求是一种 ICMPv4 消息,通常用于检查网络上是否存在特定节点,该节点由其主机 IP 地址标识。 常用的 ping 命令是使用 ICMP 回显请求/回显回复消息实现的。 如果存在特定主机,其网络堆栈将使用 ping 响应来处理 ping 请求和响应。 图 7 详细说明了 ICMPv4 ping 消息格式。

ICMPv4 Ping Message

图 7. ICMPv4 Ping 消息

注意

TCP/IP 实现中的所有 ICMPv4 消息都应采用 big endian 格式。 在这种格式中,字的最高序字节驻留在最低序字节地址中。

下面介绍了 ICMPv4 标头格式:

标头字段 目的
类型 此字段指定 ICMPv4 消息(31-24 位)。 最常见的值包括:
- 0:回显回复
- 3:目标不可访问
- 8:回显请求
- 11:超出了时间
- 12:参数问题
代码 此字段是类型字段特定的上下文(23-16 位)。 对于回显请求或回复,此代码设置为零。
校验和 此字段包含 ICMPv4 消息(包括从类型字段开始的整个 ICMPv4 标头)的 1 的补码之和的 16 位校验和。 在生成校验和之前,校验和字段已清空。
标识 此字段包含标识主机的 ID 值;主机应在回显回复中使用从回显请求中提取的 ID(31-16 位)。
序列号 此字段包含 ID 值;主机应在回显回复中使用从回显请求中提取的 ID(31-16 位)。 与标识符字段不同,在相同主机后续发出的回显请求中,该值将发生变化(15-0 位)。

ICMPv4 回显响应

ping 响应是另一种 ICMP 消息,由 ICMP 组件响应外部 ping 请求时在内部生成。 除了确认,ping 响应还包含 ping 请求中提供的用户数据的副本。

ICMPv4 错误消息

NetX Duo 支持以下 ICMPv4 错误消息:

  • 目标不可访问
  • 超出了时间
  • 参数问题

Internet 组管理协议 (IGMP)

Internet 组管理协议 (IGMP) 提供一种设备,用于与其计划接收(或加入)IPv4 多播组的邻居及其路由器通信(RFC 1112 和 RFC 2236)。 多播组基本上是网络成员的动态集合,由 D 类 IP 地址表示。 多播组的成员可以随时离开,新成员可以随时加入。 IGMP 负责协调加入和离开组的相关事宜。

注意

IGMP 仅适用于 IPv4 多播组。 它不能用于 IPv6 网络。

IGMP 启用

在 NetX Duo 中进行任何多播活动之前,应用程序必须调用 nx_igmp_enable 服务。 此服务执行基本 IGMP 初始化,以便为多播请求做准备。

多播 IPv4 寻址

如前所述,多播地址实际上是图 4 所示的 D 类 IP 地址。 D 类地址的低序 28 位对应多播组 ID。 系统有一系列预定义的多播地址;但是,“all hosts”地址 (244.0.0.1) 对于 IGMP 处理尤为重要。 路由器使用“all hosts”地址来查询所有多播成员,以报告其所属的多播组。

IPv4 中的物理地址映射

D 类多播地址直接映射到从 01.00.5e.00.00.00 到 01.00.5e.7f.ff.ff 的物理以太网地址。 IP 多播地址的 23 个低序位直接映射到以太网地址的 23 个低序位。

多播组加入

需要加入特定多播组的应用程序可以通过调用 nx_igmp_multicast_join 服务来实现此目的。 此服务会跟踪加入此多播组的请求数量。 如果这是第一个加入多播组的应用程序请求,系统将在主网络上发送 IGMP 报告,表明此主机要加入组的意图。 接下来,系统将调用网络驱动程序,以侦听具有此多播组的以太网地址的数据包。

在多宿主系统中,如果可以通过特定接口访问多播组,应用程序应使用 nx_igmp_multicast_interface_join 服务,而不是 nx_igmp_multicast_join 服务,后者仅限于主网络上的多播组。

多播组离开

如果应用程序需要离开之前加入的多播组,可以通过调用 nx_igmp_multicast_leave 服务来完成此操作。 此服务会减少与加入组次数关联的内部计数。 如果某个组没有未完成的加入请求,系统会调用网络驱动程序,以便禁止侦听具有此多播组的以太网地址的数据包。

多播环回

应用程序可能希望接收来自同一节点上某个源的多播流量。 这需要 IP 多播组件使用 nx_igmp_loopback_enable 服务来启用环回。

IGMP 报告消息

当应用程序加入多播组时,系统会通过网络发送 IGMP 报告消息,表明主机要加入特定多播组的意图。 图 8 显示了 IGMP 报告消息的格式。 IGMP 报告消息中的组消息和目标 IP 地址都使用多播组地址。

在上图(图 8)中,IGMP 标头包含版本/类型字段、最大响应

Diagram of a IGMP report message.

图 8. IGMP 报告消息

时间、校验和字段和多播组地址字段。 对于 IGMPv1 消息,“最大响应时间”字段始终设置为零,因为它不属于 IGMPv1 协议。 当主机收到查询类型 IGMP 消息时,系统会设置“最大响应时间”字段;当主机收到另一台主机由 IGMPv2 协议定义的报告类型消息时,系统会清除该字段。

下面介绍了 IGMP 标头格式:

标头字段 目的
版本 此字段指定 IGMP 版本(31-28 位)。
类型 此字段指定 IGMP 消息类型(27-24 位)。
最大响应时间 IGMP v1 中未使用。 在 IGMP v2 中,此字段用作最大响应时间。
校验和 此字段包含 IGMP 消息(从 IGMP 版本开始)的 1 的补码之和的 16 位校验和(0-15 位)
组地址 32 位 D 类组 IP 地址

为响应多播路由器发送的 IGMP 查询消息,系统还会发送 IGMP 报告消息。 多播路由器会定期发送查询消息,查看哪些主机仍需要组成员身份。 查询消息的格式与图 8 所示的 IGMP 报告消息相同。 唯一的区别是 IGMP 类型等于 1,组地址字段设置为 0。 多播路由器将 IGMP 查询消息发送到“all hosts”IP 地址。 仍希望维护组成员身份的主机会通过发送另一个 IGMP 报告消息进行响应。

注意

TCP/IP 实现中的所有消息都应采用 big endian 格式。 在这种格式中,字的最高序字节驻留在最低序字节地址中。

IGMP 统计信息和错误

如果启用,NetX Duo IGMP 软件会跟踪一些可能对应用程序有用的统计信息和错误。 系统将为每个 IP 的 IGMP 处理维护以下统计信息和错误报告:

  • 发送的 IGMP 报告总数
  • 接收的 IGMP 查询总数
  • IGMP 校验和错误总数
  • 已加入的 IGMP 当前组总数

所有这些统计信息和错误报告都可通过 nx_igmp_info_get 服务提供给应用程序。

无 IGMP 的多播

需要 IPv4 多播流量的应用程序可以通过使用 nx_ipv4_multicast_interface_join 服务来加入多播组地址,而无需调用 IGMP 消息。 此服务指示 IPv4 层和底层接口驱动程序接受来自指定 IPv4 多播地址的数据包。 但是,不会为此组发送或处理 IGMP 组管理消息。

不再希望从该组接收流量的应用程序可以使用 nx_ipv4_multicast_interface_leave 服务

NetX Duo 中的 IPv6

IPv6 地址

IPv6 地址为 128 位。 RFC 4291 中描述了 IPv6 地址的体系结构。 该地址分为包含最高有效位的前缀和包含较低位的主机地址。 前缀指示地址类型,大致相当于 IPv4 网络中的网络地址。

IPv6 具有三种类型的地址规范:单播、任意广播(NetX Duo 不支持)和多播。 单播地址是用于标识 Internet 上特定主机的 IP 地址。 单播地址可以是源 IP 地址或目标 IP 地址。 多播地址指定 Internet 上的动态主机组。 多播组的成员可以根据需要随时加入和离开。

IPv6 没有与 IPv4 广播机制等效的机制。 通过将数据包发送到链路本地“all hosts”多播组,可以将数据包发送到所有主机。

IPv6 利用多播地址来执行邻居发现、路由器发现和无状态地址自动配置过程。

IPv6 单播地址分为两种类型:链路本地地址,通常由众所周知的链路本地前缀和接口 MAC 地址组合而成;以及全局 IP 地址,也包含前缀部分和主机 ID 部分。 全局地址可以手动配置,也可以通过无状态地址自动配置或 DHCPv6 配置。 NetX Duo 支持链路本地地址和全局地址。

为了同时适应 IPv4 和 IPv6 格式,NetX Duo 提供了一种新的数据类型 NXD_ADDRESS,用于保存 IPv4 和 IPv6 地址。 此结构的定义如下所示。 地址字段是 IPv4 和 IPv6 地址的联合。

typedef struct NXD_ADDRESS_STRUCT
{
    ULONG nxd_ip_version;
    union
    {
        ULONG v4;
        ULONG v6[4];
    } nxd_ip_address;
} NXD_ADDRESS;

在 NXD_ADDRESS 结构中,第一个元素 nxd_ip_version 指示 IPv4 或 IPv6 版本。 支持的值为 NX_IP_VERSION_V4 或 NX_IP_VERSION_V6。 nxd_ip_version 指示 nxd_ip_address 联合中的哪个字段用作 IP 地址。 NetX Duo API 服务通常将指向 NXD_ADDRESS 结构的指针用作输入参数,以代替 ULONG(32 位)IP 地址。

链路本地地址仅在本地网络上有效。 在分配了有效的链路本地地址后,设备可以向同一网络上的另一台设备发送数据包,也可以从中接收数据包。 应用程序通过调用 NetX Duo 服务 nxd_ipv6_address_set 来分配链路本地地址,并将前缀长度参数设置为 10。 应用程序可以为服务提供链路本地地址,也可以简单地使用 NX_NULL 作为链路本地地址,并允许 NetX Duo 基于设备的 MAC 地址构造链路本地地址。

以下示例指示 NetX Duo 使用其 MAC 地址在主要设备(索引 0)上配置前缀长度为 10 的链路本地地址:

nxd_ipv6_address_set(ip_ptr, 0, NX_NULL, 10, NX_NULL);

在上面的示例中,如果接口的 MAC 地址为 54:32:10:1A:BC:67,则相应的链路本地地址为:

FE80::5632:10FF:FE1A:BC67

请注意,IPv6 地址 (5632:10FF:FE1A:BC67) 的主机 ID 部分由 6 字节 MAC 地址组成,并进行了以下修改:

  • 在 MAC 地址的第 3 个字节和第 4 个字节之间插入 0xFFFE
  • MAC 地址的第一个字节的第二个最低位(U/L 位)设置为 1

有关如何根据 IPv6 地址的接口 MAC 地址构造主机部分的详细信息,请参阅 RFC 2464(通过以太网传输 IPv6 数据包)。

在 IPv6 中,可通过几个特殊的多播地址将多播消息发送到一个或多个主机:

Group 地址 说明
“All nodes”组 FF02::1 本地网络上的所有主机
“All routers”组 FF02::2 本地网络上的所有路由器
Solicited-node FF02::1:FF00:0/104 解释如下

solicited-node 多播地址面向本地链路上的特定主机,而非所有 IPv6 主机。 它由前缀 FF02::1:FF00:0/104(104 位)和目标 IPv6 地址的最后 24 位组成。 例如,IPv6 地址 205B:209D:D028::F058:D1C8:1024 的 solicitednode 多播地址为 FF02::1:FFC8:1024。

重要

双冒号表示法指示中间位全部为零。 FF02::1:FF00:0/104 完全展开类似于 FF02:0000:0000:0000:0000:0001:FF00:0000

全局地址

IPv6 全局地址的示例为 2001:0123:4567:89AB:CDEF::1。NetX Duo 将 IPv6 地址存储在 NXD_ADDRESS 结构中。 在下面的示例中,NXD_ADDRESS 变量 global_ipv6_address 包含单播 IPv6 地址。 以下示例演示了 NetX Duo 设备如何为其主要设备创建特定的 IPv6 全局地址:

NXD_ADDRESS global_ipv6_address;
UINT        primary_interface_index = 0;

global_ipv6_address.nxd_ip_version = NX_IP_VERSION_V6;
global_ipv6_address.nxd_ip_address.v6[0] = 0x20010123;
global_ipv6_address.nxd_ip_address.v6[1] = 0x456789AB;
global_ipv6_address.nxd_ip_address.v6[2] = 0xCDEF0000;
global_ipv6_address.nxd_ip_address.v6[3] = 0x00000001;

status = nxd_ipv6_address_set(
            &ip_0,
            primary_interface_index,
            &global_ipv6_address,
            64,
            NX_NULL);

请注意,此 IPv6 地址的前缀为 2001:0123:4567:89AB,其长度为 64 位,这是以太网上全局单播 IPv6 地址的通用前缀长度。

NXD_ADDRESS 结构还保存 IPv4 地址。 存储在 global_ipv4_address 中的 IP 地址 192.1.168.10 (0xC001A80A) 具有以下内存布局:

字段
global_ipv4_address.nxd_ip_version NX_IP_VERSION_V4
global_ipv4_address.nxd_ip_address.v4 0xC001A80A

当应用程序将地址传递给 NetX Duo 服务时,nxd_ip_version 字段必须指定正确的 IP 版本,以进行正确的数据包处理。

为了向后兼容现有的 NetX 应用程序,NetX Duo 支持所有 NetX 服务。 在内部,NetX Duo 将 IPv4 地址类型 ULONG 转换为 NXD_ADDRESS 数据类型,然后将其转发到实际的 NetX Duo 服务。

以下示例说明了 NetX 和 NetX Duo 中服务之间的相似之处和不同之处。

/* Make a connection to the destination IPv4 address
   192.1.168.12 through an already created TCP socket bound
   to the well known HTTP port number 80. */

global_ipv4_address.nxd_ip_version = NX_IP_VERSION_V4;
global_ipv4_address.nxd_ip_address.v4 = 0xC001A80C;

nxd_tcp_client_socket_connect(&tcp_socket,
                              &global_ipv4_address,
                              port_number,
                              NX_WAIT_FOREVER);

下面是等效的 NetX API:

ULONG         server_ip = 0xC001A80C;
NX_TCP_SOCKET tcp_socket;
UINT          port_number = 80;

nx_tcp_client_socket_connect(&tcp_socket,
                             server_ip,
                             port_number,
                             NX_WAIT_FOREVER); 

重要

建议应用程序开发人员使用这些 API 的 nxd 版本。

IPv6 默认路由器

IPv6 使用默认路由器将数据包转发到链路外目标。 借助 NetX Duo 服务 nxd_ipv6_default_router_add,应用程序可以向默认路由器表中添加 IPv6 路由器。 有关 NetX Duo 提供的更多默认路由器服务,请参阅第 4 章“服务说明”。

转发 IPv6 数据包时,NetX Duo 首先检查数据包目标是否在链路上。 如果不在,NetX Duo 会在默认路由表中检查是否有有效的路由器,以将链路外数据包转发到其中。

若要从 IPv6 默认路由器表中删除路由器,应用程序应使用 nxd_ipv6_default_router_delete 服务。 若要获取 IPv6 默认路由器表的条目,请使用 nxd_ipv6_default_router_entry_get 服务。

IPv6 标头

IPv6 标头是从 IPv4 标头修改而来。 分配数据包时,调用方会指定应用程序协议(例如 UDP、TCP)、缓冲区大小(以字节为单位)和跃点限制。

图 9 显示了 IPv6 标头的格式,表中列出了标头组件。

Diagram of the IPv6 header format.

图 9. IPv6 标头格式

IP 标头 目的
版本 IP 版本的 4 位字段。 对于 IPv6 网络,此字段中的值必须为 6;对于 IPv4 网络,必须为 4。
流量类 用于存储流量类信息的 8 位字段。 NetX Duo 不使用此字段。
流标签 20 位字段,用于唯一标识与数据包关联的流(如果有)。 零值表示数据包不属于特定流。 此字段代替 IPv4 中的“TOS”字段。
有效负载长度 16 位字段,指示 IPv6 基本标头后面的 IPv6 数据包的数据量(以字节为单位)。 这包括所有封装协议标头和数据。
下一个标头 8 位字段,指示 IPv6 基本标头后面的的扩展标头的类型。 此字段代替 IPv4 中的“协议”字段。
跃点限制 8 位字段,用于限制允许数据包通过的路由器数量。 此字段代替 IPv4 中的“TTL”字段。
源地址 128 位字段,用于存储发送方的 IPv6 地址。
目标地址 128 位字段,用于存储目标的 IPv6 地址。

在 NetX Duo 中启用 IPv6

默认情况下,NetX Duo 启用了 IPv6。 如果未定义 nx_user.h 中的可配置选项 NX_DISABLE_IPV6,则会在 NetX Duo 中启用 IPv6 服务。 如果定义了 NX_DISABLE_IPV6,NetX Duo 将仅提供 IPv4 服务,所有与 IPv6 相关的模块和服务都不会内置到 NetX Duo 库中。

应用程序可使用以下服务来配置设备 IPv6 地址:nxd_ipv6_address_set

除了手动设置设备的 IPv6 地址外,系统还可以使用无状态地址自动配置。 若要使用此选项,应用程序必须调用 nxd_ipv6_enable 以在设备上启动 IPv6 服务。 另外,必须通过调用 nxd_icmp_enable 来启动 ICMPv6 服务,这样,NetX Duo 就能够执行路由器请求、邻居发现和重复地址检测等服务。 请注意,nx_icmp_enable 仅启动用于 IPv4 服务的 ICMP。 nxd_icmp_enable 则启动用于 IPv4 和 IPv6 的 ICMP 服务。 如果系统不需要 ICMPv6 服务,则可以使用 nx_icmp_enable,这样就不会将 ICMPv6 模块链接到系统中。

以下示例显示了典型的 NetX Duo IPv6 初始化过程。

/* Assume ip_0 has been created and IPv4 services (such as ARP,
   ICMP, have been enabled. */
#define SECONDARY_INTERFACE 1

/* Enable IPv6 */
status = nxd_ipv6_enable(&ip_0);

if(status != NX_SUCCESS)
{
    /* nxd_ipv6_enable failed. */
}

/* Enable ICMPv6 */
status = nxd_icmp_enable(&ip_0);
if(status != NX_SUCCESS)
{
    /* nxd_icmp_enable failed. */
}

/* Configure the link local address on the primary interface. */
status = nxd_ipv6_address_set(&ip_0, 0, NX_NULL, 10, NX_NULL);

/* Configure ip_0 primary interface global address. */
ip_address.nxd_ip_version = NX_IP_VERSION_V6
ip_address.nxd_ip_address.v6[0] = 0x20010db8;
ip_address.nxd_ip_address.v6[1] = 0x0000f101;
ip_address.nxd_ip_address.v6[2] = 0;
ip_address.nxd_ip_address.v6[3] = 0x202;

/* Configure global address of the primary interface. */
status = nxd_ipv6_address_set(&ip_0, SECONDARY_INTERFACE,
                              &ip_address, 64, NX_NULL);

可以在 IPv6 启动之前或之后启用上层协议(例如 TCP 和 UDP)。

重要

仅在初始化 IP 线程并启用设备后,IPv6 服务才可用。

启用接口后(即,接口设备驱动程序可以发送和接收数据,并且已获取有效的链路本地地址),设备可以通过以下方法之一获取全局 IPv6 地址:

  • 无状态地址自动配置;
  • 手动配置 IPv6 地址;
  • 通过 DHCPv6(带有可选的 DHCPv6 包)配置地址

下面介绍前两种方法。 DHCP 包中介绍了第三种方法 (DHCPv6)。

使用路由器请求进行无状态地址自动配置

当使用提供前缀信息的路由器连接到 IPv6 网络时,NetX Duo 设备可以自动配置其接口。 需要无状态地址自动配置的设备会发出路由器请求 (RS) 消息。 网络上的路由器以请求的路由器播发 (RA) 消息进行响应。 RA 消息播发前缀,这些前缀可标识与链路关联的网络地址。 然后,设备为附加到的网络生成唯一标识符。 地址由前缀及其唯一标识符组合而成。 收到 RA 消息后,主机以这种方式生成其 IP 地址。 路由器也可能会定期发送未经请求的 RA 消息。

警告

NetX Duo 允许应用程序在运行时启用或禁用无状态地址自动配置。 若要启用此功能,必须在定义了 NX_IPV6_STATELESS_AUTOCONFIG_CONTROL 的情况下编译 NetX Duo 库。 启用此功能后,应用程序可以使用 nxd_ipv6_stateless_address_autoconfigure_enable 和 nxd_ipv6_stateless_address_autocofigure_disable 来启用或禁用 IPv6 无状态地址自动配置。

手动配置 IPv6 地址

如果需要特定的 IPv6 地址,应用程序可以使用 nxd_ipv6_address_set 手动配置 IPv6 地址。 一个网络接口可能具有多个 IPv6 地址。 但请记住,系统中通过无状态地址自动配置或通过手动配置获得的 IPv6 地址的总数不能超过 NX_MAX_IPV6_ADDRESSES

以下示例演示如何在 ip_0 中的主接口(设备 0)上手动配置全局地址:

NXD_ADDRESS global_address;
global_address.nxd_ip_version = NX_IP_VERSION_V6;
global_address.nxd_ip_address.v6[0] = 0x20010000;
global_address.nxd_ip_address.v6[1] = 0x00000000;
global_address.nxd_ip_address.v6[2] = 0x00000000;
global_address.nxd_ip_address.v6[3] = 0x0000ABCD;

然后,主机调用以下 NetX Duo 服务,将此地址分配为全局 IP 地址:

status = nxd_ipv6_address_set(&ip_0, 0,  
                              &global_address, 64
                              NX_NULL);

重复地址检测 (DAD)

系统配置其 IPv6 地址后,该地址将标记为“暂定”。 如果启用了 RFC 4862 中所述的重复地址检测 (DAD),NetX Duo 会自动发送以该暂定地址为目标的邻居请求 (NS) 消息。 如果网络上的主机没有在给定时间段内响应这些 NS 消息,则认为该地址在本地链路上是唯一的,其状态将转换为“有效”状态。 此时,应用程序可以开始使用此 IP 地址进行通信。

DAD 功能是 ICMPv6 模块的一部分。 因此,应用程序必须启用 ICMPv6 服务,新配置的地址才能完成 DAD 过程。 或者,可以通过在 NetX Duo 库生成环境(定义为 nx_user.h)中定义 NX_DISABLE_IPV6_DAD 选项来关闭 DAD 过程。 在 DAD 过程中,NX_IPV6_DAD_TRANSMITS 参数会确定 NetX Duo 发送的 NS 消息数,而无需收到响应来确定该地址是唯一的。 默认情况下,根据 RFC 4862 的建议,NX_IPV6_DAD_TRANSMITS 设置为 3。 将此符号设置为零可有效禁用 DAD。

如果在应用程序分配 IPv6 地址时未启用 ICMPv6 或 DAD,则不会执行 DAD,并且 NetX Duo 会立即将 IPv6 地址的状态设置为“有效”。

在其链路本地地址和/或全局地址有效之前,NetX Duo 无法在 IPv6 网络上进行通信。 获取有效地址后,NetX Duo 会尝试将传入数据包的目标地址与其已配置的 IPv6 地址之一或已启用的多播地址进行匹配。 如果未找到匹配项,则删除该数据包。

警告

在 DAD 过程中,要传输的 DAD NS 数据包的数量由 NX_IPV6_DAD_TRANSMITS 定义,默认值为 3,并且默认情况下,每发送一条 DAD NS 消息后会有一秒钟的延迟。 因此,在启用了 DAD 的系统中,分配 IPv6 地址(并假定它不是重复的地址)后,在 IP 地址为“有效”状态并且可以通信之前,大约有 3 秒钟的延迟。

当系统中的 IPv6 地址发生更改时,应用程序可能希望收到通知。 若要启用 IPv6 地址更改通知功能,生成 NetX Duo 库时必须定义符号 NX_ENABLE_IPV6_ADDRESS_CHANGE_NOTIFY。 启用该功能后,应用程序可以使用 nxd_ipv6_address_change_notify 服务安装回调函数。

IPv6 地址更改或变为无效后,系统会使用以下信息调用用户提供的回调函数:

函数 说明
ip_ptr 指向 IP 实例的指针
interface_index 与此 IPv6 地址关联的网络接口的索引
ipv6_addr_index IPv6 地址表的索引
ipv6_address 指向 IPv6 地址的指针,其形式为四个 ULONG 整数组成的数组。 IPv6 地址以主机字节顺序显示。

NetX Duo 中的 IPv6 多播支持

多播地址指定 Internet 上的动态主机组。 多播组的成员可以根据需要随时加入和离开。 NetX Duo 实现了多种 ICMPv6 协议,包括重复地址检测、邻居发现和路由器发现,这些协议需要使用 IP 多播功能。 因此,NetX Duo 需要底层设备驱动程序支持多播操作。

当 NetX Duo 需要加入或离开多播组(例如 all-node 多播地址和 solicited-node 多播地址)时,它会向设备驱动程序发出驱动程序命令,以加入或离开多播 MAC 地址。 用于加入多播地址的驱动程序命令为 NX_LINK_MULTICAST_JOIN。 若要离开多播地址,NetX Duo 会发出驱动程序命令 NX_LINK_MULTICAST_LEAVE。 设备驱动程序必须实现这两个命令,ICMPv6 协议才能正常工作。

应用程序可以使用 nxd_ipv6_multicast_interface_join 服务来加入 IPv6 多播。此服务会向 IP 堆栈注册多播地址,然后将 IPv6 多播地址告知指定的设备驱动程序。 若要离开多播组,应用程序使用 nxd_ipv6_multicast_interface_leave 服务

邻居发现 (ND)

邻居发现是 IPv6 网络中的一种协议,用于将物理地址映射到 IPv6 地址(全局地址或链路本地地址)。 此映射在邻居发现缓存(ND 缓存)中维护。 ND 过程等效于 IPv4 中的 ARP 过程,ND 缓存类似于 ARP 表。 IPv6 节点可以使用邻居发现 (ND) 协议获取其邻居的 MAC 地址。 它向 all-node solicited node 多播地址发送邻居请求 (NS) 消息,并等待相应的邻居播发 (NA) 消息。 通过此过程获得的 MAC 地址存储在 ND 缓存中。

每个 IP 实例都有一个 ND 缓存。 ND 缓存作为条目数组维护。 数组大小是在编译时通过设置 nx_user.h 中的选项 NX_IPV6_NEIGHBOR_CACHE_SIZE 来定义的。 请注意,附加到某个 IP 实例的所有接口共享同一 ND 缓存。

NetX Duo 启动时,整个 ND 缓存为空。 系统运行时,NetX Duo 自动更新 ND 缓存,并根据 ND 协议添加和删除条目。 但是,应用程序也可以通过使用以下 NetX Duo 服务手动添加和删除缓存条目来更新 ND 缓存:

  • nxd_nd_cache_entry_delete
  • nxd_nd_cache_entry_set
  • nxd_nd_cache_invalidate

发送和接收 IPv6 数据包时,NetX Duo 会自动更新 ND 缓存表。

IPv6 中的 Internet 控制消息协议 (ICMPv6)

ICMPv6 在 IPv6 中的作用已得到很大扩展,它现在支持 IPv6 地址映射和路由器发现。 此外,NetX Duo ICMPv6 还支持回显请求和响应、ICMPv6 错误报告以及 ICMPv6 重定向消息。

ICMPv6 启用

应用程序必须调用 nxd_icmp_enable 服务来启用 ICMPv6 处理(如前所述),NetX Duo 才能处理 ICMPv6 消息。

ICMPv6 消息

ICMPv6 标头结构与 ICMPv4 标头结构类似。 如下所示,基本 ICMPv6 标头包含三个字段(类型、代码和校验和),以及长度可变的 ICMPv6 选项数据。

Diagram of a basic ICMPv6 header.

图 10. 基本 ICMPv6 标头

字段 大小(字节) 说明
1 标识 ICMPv6 消息类型;
1 目标不可访问
2 数据包太大
3 超出了时间
4 参数问题
128 回显请求
129 回显回复
133 路由器请求
134 路由器播发
135 邻居请求
136 邻居播发
137 重定向消息
代码 1 进一步限定 ICMPv6 消息类型。 通常与错误消息一起使用。 如果不使用,则将其设置为零。 回显请求/回复和 NS 消息不使用该字段。
校验和 2 ICMP 标头的 16 位校验和字段。 这是整个 ICMPv6 消息(包括 ICMPv6 标头)的 16 位补码。 它还包括由 IPv6 源地址、目标地址和数据包有效负载长度组成的伪标头。

下面显示了一个邻居请求标头示例。

Diagram of an example Neighbor Solicitation header.

图 11. 邻居请求消息的 ICMPv6 标头

字段 大小(字节) 说明
类型 1 标识邻居请求消息的 ICMPv6 消息类型。 值为 135。
代码 1 未使用。 设置为 0。
校验和 2 ICMPv6 标头的 16 位校验和字段。
保留 4 保留字节数设置为 0。
目标地址 16 请求目标的 IPv6 地址。 对于 IPv6 地址解析,这是需要解析其链路层地址的设备的实际单播 IP 地址。
选项 变量 邻居发现协议指定的可选信息。

ICMPv6 Ping 请求

在 NetX Duo 中,应用程序根据参数中指定的目标 IP 地址,使用 nxd_icmp_ping 发出 IPv6 或 IPv4 ping 请求。

ICMPv6 Ping 响应

ICMPv6 Ping 响应是另一种 ICMPv6 消息,由 ICMPv6 组件响应外部 ICMPv6 ping 请求时在内部生成。 除了确认,ICMPv6 ping 响应还包含 ICMPv6 ping 请求中提供的用户数据的副本。

线程挂起

应用程序线程在尝试 ping 另一个网络成员时可能挂起。 收到 ping 响应后,系统会向第一个挂起的线程提供 ping 响应消息,并恢复该线程。 与所有 NetX Duo 服务一样,针对 ping 请求挂起的线程具有可选超时。

其他 ICMPv6 消息

以下功能需要 ICMPv6 消息:

  • 邻居发现
  • 无状态地址自动配置
  • 路由器发现
  • 邻居不可访问性检测

邻居不可访问性、路由器和前缀发现

邻居不可访问性检测、路由器发现和前缀发现基于邻居发现协议,下面介绍了这些功能。

邻居不可访问性检测:IPv6 设备需要发送数据包时,会在其邻居发现 (ND) 缓存中搜索目标链路层地址。 直接目标(有时也称为“下一个跃点”)可以是同一链路上的实际目标,也可以是路由器(如果目标不在链路上)。 ND 缓存条目包含邻居的可访问性状态。

“可访问”状态表示邻居被认为是可访问的。 如果邻居最近收到“已收到发送给邻居的数据包”确认消息,则表示该邻居是可访问的。 NetX Duo 中的确认消息采用“正在接收来自邻居的 NA 消息”的形式,来响应 NetX Duo 设备发送的 NS 消息。 如果应用程序调用 NetX Duo 服务 nxd_nd_cache_entry_set 来手动输入缓存记录,NetX Duo 也会将邻居的状态更改为“可访问”

路由器发现:IPv6 设备使用路由器转发所有发往链路外目标的数据包。 它还可以使用路由器发送的信息(例如路由器播发 (RA) 消息)来配置全局 IPv6 地址。

网络上的设备可以通过向 all-router 多播地址 (FF01::2) 发送路由器请求 (RS) 消息来启动路由器发现过程。 或者,它可以在 all-node 多播地址 (FF::1) 上等待路由器的周期性 RA。

RA 消息包含用于为该网络配置 IPv6 地址的前缀信息。 在 NetX Duo 中,路由器请求默认情况下处于启用状态,并且可以通过设置 nx_user.h 中的配置选项 NX_DISABLE_ICMPV6_ROUTER_SOLICITATION 来禁用。 有关设置路由器请求参数的更多详细信息,请参阅“NetX Duo 的安装和使用”一章中的“配置选项”。

前缀发现:IPv6 设备使用前缀发现来了解可直接访问(无需通过路由器)的目标主机。 此信息可通过路由器的 RA 消息提供给 IPv6 设备。 IPv6 设备将前缀信息存储在前缀表中。 前缀发现是将 IPv6 设备前缀表中的前缀与目标地址进行匹配的过程。 如果前缀中的所有位与目标地址的最高有效位匹配,则表示前缀与目标地址匹配。 如果有多个前缀涵盖某个地址,则选择最长的前缀。

ICMPv6 错误消息

NetX Duo 支持以下 ICMPv6 错误消息:

  • 目标不可访问
  • 数据包太大
  • 超出了时间
  • 参数问题

用户数据报协议 (UDP)

用户数据报协议 (UDP) 为网络成员提供了最简单的数据传输形式 (RFC 768)。 系统会尽最大努力将 UDP 数据包从一个网络成员发送到另一个网络成员;也就是说,数据包接收方没有内置的确认机制。 此外,发送 UDP 数据包无需事先建立任何连接。 因此,UDP 数据包传输非常高效。

对于将 NetX 应用程序迁移到 NetX Duo 的开发人员,NetX 和 NetX Duo之间的 UDP 功能只有几个基本变化。 这是因为 IPv6 主要与底层 IP 层有关。 所有 NetX Duo UDP 服务都可用于 IPv4 或 IPv6 连接。

UDP 标头

在传输过程中,UDP 会在应用程序的数据前面放置一个简单的数据包标头,并在接收时从数据包中删除类似的 UDP 标头,然后再将收到的 UDP 数据包传递给应用程序。 UDP 利用 IP 协议来发送和接收数据包,这意味着当数据包处于网络中时,UDP 标头前面会有一个 IP 标头。 图 12 显示了 UDP 标头格式。

Diagram of the UDP header format.

图 12. UDP 标头

注意

UDP/IP 实现中的所有标头都应采用 big endian 格式。 在这种格式中,字的最高序字节驻留在最低序字节地址中。

下面介绍了 UDP 标头格式:

标头字段 目的
16 位源端口号 此字段包含从中发送 UDP 数据包的端口。 有效 UDP 端口范围为 1 到 0xFFFF。
16 位目标端口号 此字段包含要向其发送数据包的 UDP 端口。 有效 UDP 端口范围为 1 到 0xFFFF。
16 位 UDP 长度 此字段包含 UDP 数据包中的字节数,包括 UDP 标头大小。
16 位 UDP 校验和 此字段包含数据包的 16 位校验和,包括 UDP 标头、数据包数据区域和伪 IP 标头。

UDP 启用

应用程序必须首先通过调用 nx_udp_enable 服务来启用 UDP,然后才能进行 UDP 数据包传输。 启用后,应用程序可以随意发送和接收 UDP 数据包。

UDP 套接字创建

UDP 套接字是在初始化期间或运行时由应用程序线程创建的。 nx_udp_socket_create 服务可定义服务的初始类型、生存时间和接收队列深度。 应用程序中的 UDP 套接字数量无限制。

UDP 校验和

IPv6 协议要求对数据包数据进行 UDP 标头校验和计算,而在 IPv4 协议中,UDP 校验和是可选的。

UDP 指定了 1 的补码 16 位校验和,其中包含 IP 伪标头(由源 IP 地址、目标 IP 地址和协议/长度 IP 字组成)、UDP 标头和 UDP 数据包数据。 IPv4 和 IPv6 UDP 数据包标头校验和之间的唯一区别在于,IPv4 中的源 IP 地址和目标 IP 地址是 32 位,而 IPv6 中的源 IP 地址和目标 IP 地址是 128 位。 如果计算出的 UDP 校验和为 0,则存入的值为全 1 (0xFFFF)。 如果发送套接字禁用了 UDP 校验和逻辑,则会在“UDP 校验和”字段中放置一个零,表示未计算校验和。

如果 UDP 校验和与接收方计算得出的校验和不匹配,则会直接放弃该 UDP 数据包。

在 IPv4 网络上,UDP 校验和是可选的。 NetX Duo 允许应用程序基于每个套接字启用或禁用 UDP 校验和计算。 默认情况下,UDP 套接字校验和逻辑处于启用状态。 应用程序可以通过调用 nx_udp_socket_checksum_disable 服务来禁用特定 UDP 套接字的校验和逻辑。 但是,在 IPv6 网络上,必须计算 UDP 校验和。 因此,当通过 IPv6 网络发送数据包时,nx_udp_socket_checksum_disable 服务不会禁用 UDP 校验和逻辑。

某些以太网控制器可以动态生成 UDP 校验和。 如果系统能够使用硬件校验和计算功能,则可以生成没有校验和逻辑的 NetX Duo 库。 若要禁用 UDP 软件校验和,必须在定义了以下符号的情况下生成 NetX Duo 库:NX_DISABLE_UDP_TX_CHECKSUM 和 NX_DISABLE_UDP_RX_CHECKSUM(详见第二章)。 这些配置选项会从 NetX Duo 中完全删除 UDP 校验和逻辑,而调用 nx_udp_socket_checksum_disable 服务时,应用程序可以基于每个套接字禁用 IPv4 UDP 校验和处理。

UDP 端口和绑定

UDP 端口是 UDP 协议中的逻辑端点。 NetX Duo 的 UDP 组件包含 65,535 个有效端口,范围为 1 到 0xFFFF。 若要发送或接收 UDP 数据,应用程序必须首先创建一个 UDP 套接字,然后将其绑定到所需端口。 将 UDP 套接字绑定到端口之后,应用程序可能会在该套接字上发送和接收数据。

UDP Fast Path™

UDP Fast Path™ 是通过 NetX UDP 实现的低数据包开销路径的名称。 发送 UDP 数据包只需少量函数调用:nx_udp_socket_send、nx_ip_packet_send,以及对网络驱动程序的最终调用。 可在 NetX Duo 中对现有 NetX 应用程序使用 nx_udp_socket_send,而且仅适用于 IPv4 数据包。 不过,首选方法是使用下面讨论的 nxd_udp_socket_send 服务。 收到 UDP 数据包时,要么将 UDP 数据包置于适当的 UDP 套接字接收队列中,要么在单个函数调用中从网络驱动程序的接收中断处理传递到挂起的应用程序线程。 这种高度优化的 UDP 数据包收发逻辑是 UDP Fast Path 技术的本质。

UDP 数据包发送

通过调用 nxd_udp_socket_send 函数,即可轻松通过 IPv6 或 IPv4 网络发送 UDP 数据。 调用方必须在 NXD_ADDRESS 指针参数的 nx_ip_version 字段中设置 IP 版本。 NetX Duo 将根据目标 IPv4/IPv6 地址确定传输的 UDP 数据包的最佳源地址。 此服务将 UDP 标头置于数据包数据的前面,并使用内部 IP 发送例程将其发送到网络上。 由于所有 UDP 数据包传输都会立即得到处理,因此发送 UDP 数据包时不会出现线程挂起。

对于多播或广播目标,如果 NetX Duo 设备有多个 IP 地址可供选择,则应用程序应指定要使用的源 IP 地址。 这可以使用 nxd_udp_socket_source_send 服务来实现。

重要

如果使用 nx_udp_socket_send 传输多播或广播数据包,则会将第一个已启用接口的 IP 地址用作源地址。

注意

如果为此套接字启用了 UDP 校验和逻辑,校验和运算将在调用线程的上下文中执行,而不会阻止对 UDP 或 IP 数据结构的访问。

警告

驻留在 NX_PACKET 结构中的 UDP 有效负载数据应当驻留在长字边界上。 应用程序需要在预置指针与 NetX Duo 的数据开始指针之间留出足够的空间,以便放置 UDP、IP 和物理介质标头。

UDP 数据包接收

应用程序线程可以通过调用 nx_udp_socket_receive 来接收特定套接字的 UDP 数据包。 套接字接收函数会传递套接字接收队列中最早的数据包。 如果接收队列中没有任何数据包,调用线程可能会挂起(具有可选超时),直到数据包到达为止。

UDP 接收数据包处理(通常从网络驱动程序的接收中断处理程序中调用)负责将数据包放在 UDP 套接字的接收队列上,或将其传递到等待数据包的第一个挂起线程。 如果数据包已排队,接收处理还会检查与该套接字关联的最大接收队列深度。 如果新排队的数据包超出队列深度,系统会放弃队列中最早的数据包。

UDP 接收通知

如果应用程序线程需要处理来自多个套接字的接收数据,则应使用 nx_udp_socket_receive_notify 函数。 此函数为套接字注册接收数据包回调函数。 每当在套接字上接收数据包时,都会执行该回调函数。

该回调函数的内容特定于应用程序;但它最有可能包含一个逻辑,该逻辑通知处理线程,相应套接字上现在有可用的数据包。

对等节点的地址和端口

收到 UDP 数据包后,应用程序可以使用 nx_udp_packet_info_extract 服务查找发送方的 IP 地址和端口号。 成功返回数据后,此服务可提供有关发送方 IP 地址、发送方端口号以及从中接收数据包的本地接口的信息。

线程挂起

如前所述,应用程序线程在尝试接收特定 UDP 端口上的 UDP 数据包时可能挂起。 在该端口上收到数据包后,系统会将其提供给第一个挂起的线程,然后恢复该线程。 针对 UDP 接收数据包挂起时,可以使用可选超时,这项功能适用于大多数 NetX Duo 服务。

UDP 套接字统计信息和错误

如果启用,NetX Duo UDP 套接字软件会跟踪一些可能对应用程序有用的统计信息和错误。 系统将为每个 IP/UDP 实例维护以下统计信息和错误报告:

  • 发送的 UDP 数据包总数
  • 发送的 UDP 字节总数
  • 接收的 UDP 数据包总数
  • 接收的 UDP 字节总数
  • UDP 无效数据包总数
  • 删除的 UDP 接收数据包总数
  • UDP 接收校验和错误总数
  • 发送的 UDP 套接字数据包数
  • 发送的 UDP 套接字字节数
  • 接收的 UDP 套接字数据包数
  • 接收的 UDP 套接字字节数
  • 已排队的 UDP 套接字数据包数
  • 删除的 UDP 套接字接收数据包数
  • UDP 套接字校验和错误数

应用程序可通过 nx_udp_info_get 服务(用于获取所有 UDP 套接字上积累的 UDP 统计信息)和 nx_udp_socket_info_get 服务(用于获取指定 UDP 套接字上的 UDP 统计信息)获取所有这些统计信息和错误报告。

UDP 套接字控制块 NX_UDP_SOCKET

每个 UDP 套接字的特性都可在关联的 NX_UDP_SOCKET 控制块中找到。 该控制块包含有用的信息,例如指向 IP 数据结构的链接、发送和接收路径的网络接口、绑定的端口以及接收数据包队列。 此结构在 nx_api.h 文件中定义。

传输控制协议 (TCP)

传输控制协议 (TCP) 在两个网络成员之间提供可靠的流数据传输 (RFC 793)。 接收成员会验证并确认从一个网络成员发送的所有数据。 此外,这两个成员必须先建立连接,然后才能进行数据传输。 这一切将实现可靠的数据传输;但是,它所需的开销的确要比前述的 UDP 数据传输大得多。

除非另有说明,否则 NetX 和 NetX Duo 之间的 TCP 协议 API 服务不会有任何变化,因为 IPv6 主要与底层 IP 层有关。 所有 NetX Duo TCP 服务均可用于 IPv4 或 IPv6 连接。

TCP 标头

传输时,系统会将 TCP 标头置于用户数据的前面。 接收时,系统会从传入数据包中删除 TCP 标头,仅保留可供应用程序使用的用户数据。 TCP 利用 IP 协议来发送和接收数据包,这这意味着当数据包处于网络中时,TCP 标头前面会有一个 IP 标头。 图 13 显示了 TCP 标头格式。

Diagram of the TCP header format.

图 13. TCP 标头

下面介绍了 TCP 标头格式:

标头字段 目的
16 位源端口号 此字段包含在其中发送 TCP 数据包的端口。 有效 TCP 端口范围为 1 到 0xFFFF。
16 位目标端口 此字段包含要向其发送数据包的 TCP 端口。 有效 TCP 端口范围为 1 到 0xFFFF。
32 位序列号 此字段包含从此连接端发送的数据的序列号。 系统会在两个 TCP 节点之间的初始连接序列中建立原始序列。 每次从该点传输数据都会导致序列号按发送的字节数递增。
32 位确认编号 此字段包含一个序列号,与连接的这一端收到的最后一个字节对应。 通过此方法可确定以前发送的数据是否已由连接的另一端成功接收。
4 位标头长度 此字段包含 TCP 标头中的 32 位字数。 如果 TCP 标头中不存在任何选项,则此字段为 5。
6 位代码位 此字段包含六个不同的代码位,用于指示与连接关联的各种控制信息。 控制位的定义如下:<br > - URG (21):存在紧急数据<br > - ACK (20):确认编号有效<br > - PSH (19):立即处理此数据<br > - RST (18):重置连接<br > - SYN (17):同步序列号(用于建立连接)<br > - FIN (16):发送方已完成传输(用于关闭连接)
16 位窗口 此字段用于流控制。 其中包含套接字当前可以接收的字节数。 基本上用于流控制。 发送方负责确保要发送的数据适合接收方的播发窗口。
16 位 TCP 校验和 此字段包含数据包的 16 位校验和,包括 TCP 标头、数据包数据区域和伪 IP 标头。
16 位紧急指针 此字段包含紧急数据的最后一个字节的正偏移量。 该字段只有在标头中设置 URG 代码位时才有效。

注意

TCP/IP 实现中的所有标头都应采用 big endian 格式。 在这种格式中,字的最高序字节驻留在最低序字节地址中。

TCP 启用

应用程序必须先通过调用 nx_tcp_enable 服务来启用 TCP,然后才能进行 TCP 连接和数据包传输。 启用后,应用程序可以自由访问所有 TCP 服务。

TCP 套接字创建

TCP 套接字是在初始化期间或运行时由应用程序线程创建的。 nx_tcp_socket_create 服务可定义服务的初始类型、生存时间和窗口大小。 应用程序中的 TCP 套接字数量无限制。

TCP 校验和

TCP 指定了 1 的补码 16 位校验和,其中包含 IP 伪标头(由源 IP 地址、目标 IP 地址和协议/长度 IP 字组成)、TCP 标头和 TCP 数据包数据。 IPv4 和 IPv6 TCP 数据包标头校验和之间的唯一区别在于,IPv4 中的源 IP 地址和目标 IP 地址是 32 位,而 IPv6 中的源 IP 地址和目标 IP 地址是 128 位。

某些网络控制器能够在硬件中执行 TCP 校验和计算与验证。 对于这类系统,应用程序可能需要尽可能使用硬件校验和逻辑来降低运行时开销。 应用程序可以通过定义 NX_DISABLE_TCP_TX_CHECKSUM 和 NX_DISABLE_TCP_RX_CHECKSUM 在生成时完全禁用 NetX Duo 库中的 TCP 校验和计算逻辑。 这样,就不会编译 TCP 校验和代码。 但是,如果安装了可选的 NetX Duo IPsec 包,并且 TCP 连接可能需要遍历安全通道,则应格外小心。 在这种情况下,属于 TCP 连接的数据包中的数据已加密,并且网络驱动程序中存在的大多数硬件 TCP 校验和模块无法根据加密的 TCP 有效负载生成正确的校验和值。

若要解决此问题,应用程序应在库中启用 TCP 校验和逻辑,并使用接口功能。 启用接口功能后,如果驱动程序还能够计算校验和值,TCP 模块将知道如何正确处理 TCP 校验和:

  1. 如果 TCP 数据包不受 IPsec 进程的限制,网络接口硬件就能够计算校验和。 因此,TCP 模块不会尝试计算校验和;

  2. 如果安装了 IPsec 包,并且 TCP 数据包受 IPsec 进程的限制,TCP 模块会在将数据包发送到 IPsec 层之前在软件中计算校验和。

TCP 端口

TCP 端口是 TCP 协议中的逻辑连接点。 NetX Duo 的 TCP 组件包含 65,535 个有效端口,范围为 1 到 0xFFFF。 在 UDP 中,一个端口的数据可以发送到任何其他目标端口。与之不同,TCP 端口会连接到另一个特定 TCP 端口,而且只有建立连接后才能进行数据传输,同时只能在两个连接端口间传输数据。

重要

TCP 端口完全独立于 UDP 端口;例如,UDP 端口号 1 与 TCP 端口号 1 没有任何关系。

客户端-服务器模型

若要使用 TCP 进行数据传输,必须首先在两个 TCP 套接字之间建立连接。 需要按客户端-服务器的方式建立连接。 连接的客户端将启动连接,而服务器端只需等待客户端连接请求,然后再进行处理。

重要

对于多宿主设备,NetX Duo 会根据连接的目标 IP 地址自动确定进行连接的源地址和下一个跃点地址。 由于 TCP 仅限于将数据包发送到单播(例如,非广播)目标地址,因此 NetX Duo 不需要“提示”来选择源 IPv6 地址。

TCP 套接字状态机

两个 TCP 套接字(一个客户端和一个服务器)之间的连接十分复杂,并且以状态机方式进行管理。 每个 TCP 套接字启动时都处于 CLOSED 状态。 通过连接事件,每个套接字的状态机将变为 ESTABLISHED 状态,此时可在 TCP 中传输批量数据。 如果连接的一端不想再发送数据,它会断开连接。 当另一端断开连接后,TCP 套接字最终将恢复为 CLOSED 状态。 每当 TCP 客户端和服务器建立并关闭连接时都会重复此过程。 图 14 显示了 TCP 状态机的各种状态。

TCP 客户端连接

如前所述,TCP 连接的客户端向 TCP 服务器发起连接请求。 在发起连接请求之前,必须首先在客户端 IP 实例上启用 TCP。 此外,接下来必须使用 nx_tcp_socket_create 服务创建客户端 TCP 套接字,并通过 nx_tcp_client_socket_bind 服务将其绑定到端口。

绑定客户端套接字后,使用 nxd_tcp_client_socket_connect 服务与 TCP 服务器建立连接。 请注意,套接字在尝试启动连接时必须处于 CLOSED 状态。 建立连接后,NetX Duo 发出 SYN 数据包,然后等待服务器返回 SYN ACK 数据包,即表示接受连接请求。 收到 SYN ACK 后,NetX Duo 使用 ACK 数据包进行响应,并将客户端套接字升级到 ESTABLISHED 状态。

Diagram of the states of the TCP state machine.

图 14. TCP 状态机的状态

警告

应用程序应使用 nxd_tcp_client_socket_connect 进行 IPv4 和 IPv6 TCP 连接。 应用程序仍然可以使用 nx_tcp_client_socket_connect 进行 IPv4 TCP 连接,但建议开发人员使用 nxd_tcp_client_socket_connect,因为 nx_tcp_client_socket_connect 最终将被弃用。

同样,nxd_tcp_socket_peer_info_get 适用于 IPv4 或 IPv6 TCP 连接。 但是,nx_tcp_socket_peer_info_get 只可用于旧式应用程序。 建议开发人员今后使用 nxd_tcp_socket_peer_info_get。

TCP 客户端断开连接

系统通过调用 nx_tcp_socket_disconnect 来关闭连接。 如果未指定挂起,客户端套接字会将 RST 数据包发送到服务器套接字,并将套接字设为 CLOSED 状态。 否则,如果请求挂起,系统将执行完整的 TCP 断开连接协议,如下所示:

  • 如果服务器之前发起了断开连接请求(客户端套接字已收到一个 FIN 数据包,并通过 ACK 进行响应,现在处于 CLOSE WAIT 状态),NetX Duo 会将客户端 TCP 套接字状态升级到 LAST ACK 状态并发送 FIN 数据包。 然后,它会在等到服务器的 ACK 后,再完成断开连接操作并进入 CLOSED 状态。

  • 另一方面,如果客户端首先发起断开连接请求(服务器尚未断开连接,并且套接字仍处于 ESTABLISHED 状态),NetX Duo 会发送 FIN 数据包以启动断开连接操作,并在等到服务器的 FIN 和 ACK 后,再完成断开连接操作并将套接字设为 CLOSED 状态。

如果套接字传输队列中仍有数据包,NetX Duo 会在指定的超时时间后挂起,允许系统确认数据包。 如果超时过期,NetX Duo 将清空客户端套接字的传输队列。

若要从客户端套接字取消端口绑定,应用程序需调用 nx_tcp_client_socket_unbind。 套接字必须处于 CLOSED 状态或正在断开连接(即,处于 TIMED WAIT 状态),才能释放端口;否则将返回错误。

最后,如果应用程序不再需要客户端套接字,则会调用 nx_tcp_socket_delete 删除套接字。

TCP 服务器连接

TCP 连接的服务器端处于被动状态;也就是说,服务器需要等待客户端发起连接请求。 若要接受客户端连接,必须首先通过调用 nx_tcp_enable 服务在 IP 实例上启用 TCP。 接下来,应用程序必须使用 nx_tcp_socket_create 服务创建 TCP 套接字。

还必须将服务器套接字设置为侦听连接请求。 这可以通过 nx_tcp_server_socket_listen 服务来实现。 此服务将服务器套接字设为 LISTEN 状态,并将指定的服务器端口绑定到套接字。

注意

要设置套接字侦听回调例程,应用程序需要为 nx_tcp_server_socket_listen 服务的 tcp_listen_callback 参数指定适当的回调函数。 然后,每当此服务器端口发起新连接请求时,NetX Duo 就都会执行该应用程序回调函数。 回调中的处理由应用程序控制。

要接受客户端连接请求,应用程序需调用 nx_tcp_server_socket_accept 服务。 服务器套接字必须处于 LISTEN 或 SYN RECEIVED 状态(即,服务器处于 LISTEN 状态,并且已从请求连接的客户端收到 SYN 数据包)才能调用接受服务。 来自 nx_tcp_server_socket_accept 的成功返回状态表示,连接已设置,且服务器套接字处于 ESTABLISHED 状态。

服务器套接字具有有效连接后,其他客户端连接请求将会排队,直至达到由传入到 nx_tcp_server_socket_listen 服务的 listen_queue_size 指定的深度。 若要处理服务器端口上的后续连接,应用程序必须使用可用套接字(即,处于 CLOSED 状态的套接字)调用 nx_tcp_server_socket_relisten。 请注意,如果与套接字关联的前一个连接现在已完成,并且套接字处于 CLOSED 状态,则可以使用相同的服务器套接字。

TCP 服务器断开连接

系统通过调用 nx_tcp_socket_disconnect 来关闭连接。 如果未指定挂起,服务器套接字会将 RST 数据包发送到客户端套接字,并将套接字设为 CLOSED 状态。 否则,如果请求挂起,系统将执行完整的 TCP 断开连接协议,如下所示:

  • 如果客户端之前发起了断开连接请求(服务器套接字已收到一个 FIN 数据包,并通过 ACK 进行响应,现在处于 CLOSE WAIT 状态),NetX Duo 会将 TCP 套接字状态升级到 LAST ACK 状态并发送 FIN 数据包。 然后,系统需要等到客户端的 ACK 后才能完成断开连接并进入 CLOSED 状态。

  • 另一方面,如果服务器首先发起断开连接请求(客户端尚未断开连接,并且套接字仍处于 ESTABLISHED 状态),NetX Duo 会发送 FIN 数据包以启动断开连接操作,并在等到客户端的 FIN 和 ACK 后,再完成断开连接操作并将套接字设为 CLOSED 状态。

如果套接字传输队列中仍有数据包,NetX Duo 会在指定的超时时间后挂起,允许系统确认这些数据包。 如果超时过期,NetX Duo 将刷新服务器套接字的传输队列。

断开连接处理完成并且服务器套接字处于 CLOSED 状态后,应用程序必须调用 nx_tcp_server_socket_unaccept 服务,以结束此套接字与服务器端口的关联。 请注意,即使 nx_tcp_socket_disconnect 或 nx_tcp_server_socket_accept 返回错误状态,应用程序也必须调用此服务。 nx_tcp_server_socket_unaccept 返回后,可以将套接字用作客户端或服务器套接字,甚至还可在不再需要时删除。 如果需要接受同一服务器端口上的其他客户端连接,则应在此套接字上调用 nx_tcp_server_socket_relisten 服务。

以下代码段演示了典型 TCP 服务器使用的调用序列:

/* Set up a previously created TCP socket to
   listen on port 12 */
nx_tcp_server_socket_listen()

/* Loop to make a (another) connection. */
while(1)
{
    /* Wait for a client socket connection request
       for 100 ticks. */
    nx_tcp_server_socket_accept();

    /* (Send and receive TCP messages with the TCP
       client) */

    /* Disconnect the server socket. */
    nx_tcp_socket_disconnect();

    /* Remove this server socket from listening on
       the port. */
    nx_tcp_server_socket_unaccept(&server_socket);
    /* Set up server socket to relisten on the
       same port for the next client. */
    nx_tcp_server_socket_relisten();
}

MSS 验证

最大段大小 (MSS) 是指 TCP 主机在不被底层 IP 层分段的情况下可以接收的最大字节数。 在 TCP 连接建立阶段,两端会交换自己的 TCP MSS 值,以便发送方不会发送大于接收方 MSS 的 TCP 数据段。 在建立连接之前,NetX Duo TCP 模块可以选择验证对等节点的播发 MSS 值。 默认情况下,NetX Duo 不启用这种检查。 希望执行 MSS 验证的应用程序应在生成 NetX Duo 库时定义 NX_ENABLE_TCP_MSS_CHECK,并且应在 NX_TCP_MSS_MINIMUM 中定义最小值。 MMS 值低于 NX_TCP_MSS_MINIMUM 的传入 TCP 连接将被删除。

停止侦听服务器端口

如果应用程序不再希望侦听以前通过调用 nx_tcp_server_socket_listen 服务指定的服务器端口上的客户端连接请求,那么,应用程序只需调用 nx_tcp_server_socket_unlisten 服务即可。 此服务会将等待连接的所有套接字恢复为 CLOSED 状态,并释放所有排队的客户端连接请求数据包。

TCP 窗口大小

在连接的设置和数据传输阶段,每个端口都会报告自身可以处理的数据量(称为“窗口大小”)。 接收和处理数据时,系统将动态调整此窗口大小。 在 TCP 中,发送方只能发送适合接收方窗口的数据量。 从本质上讲,窗口大小将为各个连接方向上的数据传输提供流控制。

TCP 数据包发送

通过调用 nx_tcp_socket_send 函数,即可轻松实现发送 TCP 数据。 如果正在传输的数据大小超过套接字的 MSS 值或当前对等接收窗口大小(以较小者为准),则 TCP 内部逻辑会切分数据,以符合最低(MSS、对等接收窗口)传输要求。 然后,此服务会在数据包(包括校验和计算)的前面生成 TCP 标头。 如果接收方窗口大小不为零,调用方将发送尽可能多的数据,以便填满接收方窗口大小。 如果接收窗口变为零,调用方可能会挂起并等待接收方的窗口大小增加到足以发送此数据包。 在任意给定时间,多个线程在尝试通过同一套接字发送数据时可能会挂起。

警告

驻留在 NX_PACKET 结构中的 TCP 数据应当驻留在长字边界上。 此外,系统应在预置指针与数据开始指针之间留出足够空间,以便放置 TCP、IP 和物理介质标头。

TCP 数据包重传

之前传输的 TCP 数据包实际上存储在内部,直到从连接的另一端返回 ACK。 如果在超时期限内未确认传输的数据,系统会重新发送存储的数据包,并设置下一个超时时间。 收到 ACK 后,系统将最终释放内部传输队列中由确认编号涵盖的所有数据包。

警告

当 nx_tcp_socket_send() 返回 NX_SUCCESS 后,应用程序不得重用数据包或更改数据包内容。 另一端确认数据后,NetX Duo 内部处理将最终释放已传输数据包。

TCP 保持连接

借助 TCP 保持连接功能,套接字可以检测其对等节点是否在没有适当终止(例如,对等节点崩溃)的情况下断开连接,或防止某些网络监视工具在长时间空闲的情况下终止连接。 TCP 保持连接的工作原理是定期发送无数据的 TCP 帧,并将序列号设置为当前序列号减 1。 收到此类 TCP 保持连接帧后,接收方(如果仍处于活动状态)会对当前序列号做出 ACK 响应, 从而完成保持连接事务。

默认情况下,未启用保持连接功能。 若要使用此功能,必须在定义了 NX_ENABLE_TCP_KEEPALIVE 的情况下生成 NetX Duo 库。 NX_TCP_KEEPALIVE_INITIAL 符号指定启动保持连接帧之前处于非活动状态的秒数。

TCP 数据包接收

TCP 接收数据包处理(从 IP 帮助程序线程调用)负责处理各种连接和断开连接操作,以及传输确认处理。 此外,TCP 接收数据包处理负责将包含接收数据的数据包放入相应 TCP 套接字的接收队列,或将数据包传递到等待数据包的第一个挂起线程。

TCP 接收通知

如果应用程序线程需要处理来自多个套接字的接收数据,则应使用 nx_tcp_socket_receive_notify 函数。 此函数为套接字注册接收数据包回调函数。 每当在套接字上接收数据包时,都会执行该回调函数。

该回调函数的内容特定于应用程序;但它最有可能包含一个逻辑,该逻辑通知处理线程,相应套接字上有可用的数据包。

线程挂起

如前所述,应用程序线程在尝试接收来自特定 TCP 端口的数据时可能挂起。 在该端口上收到数据包后,系统会将其提供给第一个挂起的线程,然后恢复该线程。 针对 TCP 接收数据包挂起时,可以使用可选超时,这项功能适用于大多数 NetX Duo 服务。

线程挂起还适用于连接(客户端与服务器)、客户端绑定和断开连接服务。

TCP 套接字统计信息和错误

如果启用,NetX Duo TCP 套接字软件会跟踪一些可能对应用程序有用的统计信息和错误。 系统将为每个 IP/TCP 实例维护以下统计信息和错误报告:

  • 发送的 TCP 数据包总数
  • 发送的 TCP 字节总数
  • 接收的 TCP 数据包总数
  • 接收的 TCP 字节总数
  • TCP 无效数据包总数
  • 删除的 TCP 接收数据包总数
  • TCP 接收校验和错误总数
  • TCP 连接总数
  • TCP 断开连接总数
  • 删除的 TCP 连接总数
  • TCP 数据包重传总数
  • 发送的 TCP 套接字数据包数
  • 发送的 TCP 套接字字节数
  • 接收的 TCP 套接字数据包数
  • 接收的 TCP 套接字字节数
  • TCP 套接字数据包重传数
  • 已排队的 TCP 套接字数据包数
  • TCP 套接字校验和错误数
  • TCP 套接字状态
  • TCP 套接字传输队列深度
  • TCP 套接字传输窗口大小
  • TCP 套接字接收窗口大小

应用程序可通过 nx_tcp_info_get 服务(用于获取全部 TCP 统计信息)和 nx_tcp_socket_info_get 服务(用于获取每个套接字的 TCP 统计信息)获取所有这些统计信息和错误报告。

TCP 套接字控制块 NX_TCP_SOCKET

每个 TCP 套接字的特性都可在关联的 NX_TCP_SOCKET 控制块中找到,该控制块包含有用的信息,例如指向 IP 数据结构的链接、网络连接接口、绑定的端口以及接收数据包队列。 此结构在 nx_api.h 文件中定义。

TCP/IP 卸载

此功能使 NetX Duo 可以支持在硬件上提供 TCP/IP 服务的网络接口卡。 某些 WiFi 模块提供 TCP/IP 处理模块,而 MCU 上的应用程序通过 API 发送和接收数据包,以便访问其 TCP/IP 堆栈。 启用此功能后,开发人员可以直接运行本机 NetX Duo 应用程序。

若要启用 TCP/IP 卸载功能,必须定义 NX_ENABLE_TCPIP_OFFLOADNX_ENABLE_INTERFACE_CAPABILITY 来构建 NetX Duo。

TCP/IP 卸载处理程序

NetX Duo 通过回调函数与网络驱动程序进行通信,以处理 TCP 或 UDP 套接字操作。 回调函数在 NX_INTERFACE_STRUCT 中进行定义。 网络驱动程序需要在 NX_LINK_ENABLE 驱动程序命令期间设置 TCP/IP 回调函数。 TCP/IP 回调函数的原型如下所示。

UINT (*nx_interface_tcpip_offload_handler)(struct NX_IP_STRUCT *ip_ptr,
                                           struct NX_INTERFACE_STRUCT *interface_ptr,
                                           VOID *socket_ptr, UINT operation, NX_PACKET *packet_ptr,
                                           NXD_ADDRESS *local_ip, NXD_ADDRESS *remote_ip,
                                           UINT local_port, UINT *remote_port, UINT wait_option);

参数说明。

  • ip_ptr - 指向 IP 实例的指针
  • interface_ptr - 指向接口的指针
  • socket_ptr - 指向 NX_TCP_SOCKETNX_UDP_SOCKET 的指针,具体取决于 operation 的值
  • operation - 当前函数调用的操作。 值定义如下:
#define NX_TCPIP_OFFLOAD_TCP_CLIENT_SOCKET_CONNECT  0
#define NX_TCPIP_OFFLOAD_TCP_SERVER_SOCKET_LISTEN   1
#define NX_TCPIP_OFFLOAD_TCP_SERVER_SOCKET_ACCEPT   2
#define NX_TCPIP_OFFLOAD_TCP_SERVER_SOCKET_UNLISTEN 3
#define NX_TCPIP_OFFLOAD_TCP_SOCKET_DISCONNECT      4
#define NX_TCPIP_OFFLOAD_TCP_SOCKET_SEND            5
#define NX_TCPIP_OFFLOAD_UDP_SOCKET_BIND            6
#define NX_TCPIP_OFFLOAD_UDP_SOCKET_UNBIND          7
#define NX_TCPIP_OFFLOAD_UDP_SOCKET_SEND            8
  • packet_ptr - 指向数据包的指针。 当 operationTCP_SOCKET_SENDUDP_SOCKET_SEND 时,设置值
  • local_ip - 指向本地 IP 地址的指针。 当 operationUDP_SOCKET_SEND 时,设置值
  • remote_ip - 指向远程 IP 地址的指针。 当 operationTCP_CLIENT_SOCKET_CONNECTUDP_SOCKET_SEND 时,设置值。 当操作为 TCP_SERVER_SOCKET_ACCEPT 时,此值必须由回调函数返回
  • local_port - 本地端口。 当 operationTCP_CLIENT_SOCKET_CONNECTTCP_SERVER_SOCKET_LISTENTCP_SERVER_SOCKET_ACCEPTTCP_SERVER_SOCKET_UNLISTEN 或 UDP 时,设置值
  • remote_port - 远程端口。 当 operationTCP_CLIENT_SOCKET_CONNECTUDP_SOCKET_SEND 时,设置值。 当操作为 TCP_SERVER_SOCKET_ACCEPT 时,此值必须由回调函数返回
  • wait_option - 等待选项(以时钟周期为单位)。 为所有操作设置值

TCP/IP 卸载上下文

将指针添加到 TCP/IP 卸载驱动程序将要使用的 NX_TCP_SOCKET 结构。

typedef struct NX_TCP_SOCKET_STRUCT
{
    // ...

    /* This pointer is designed to be accessed by TCP/IP offload directly.  */
    VOID *nx_tcp_socket_tcpip_offload_context;
} NX_TCP_SOCKET;

将指针添加到 TCP/IP 卸载驱动程序将要使用的 NX_UDP_SOCKET 结构。

typedef struct NX_UDP_SOCKET_STRUCT
{
    // ...

    /* This pointer is designed to be accessed by TCP/IP offload directly.  */
    VOID *nx_udp_socket_tcpip_offload_context;
} NX_UDP_SOCKET;

TCP/IP 卸载网络驱动程序的 API

/* Invoked when TCP packet is receive or connection error.  */
VOID _nx_tcp_socket_driver_packet_receive(NX_TCP_SOCKET *socket_ptr, NX_PACKET *packet_ptr);

/* Invoked when TCP connection is establish.  */
UINT _nx_tcp_socket_driver_establish(NX_TCP_SOCKET *socket_ptr, NX_INTERFACE *interface_ptr, UINT remote_port);

/* Invoked when UDP packet is receive.  */
VOID _nx_udp_socket_driver_packet_receive(NX_UDP_SOCKET *socket_ptr, NX_PACKET *packet_ptr,
                                          NXD_ADDRESS *local_ip, NXD_ADDRESS *remote_ip, UINT remote_port);

TCP/IP 卸载驱动程序

每个 IP 接口都需要一个驱动程序函数。 如需详细了解如何开发 NetX Duo 驱动程序函数,请参阅第 5 章

TCP/IP 卸载已知限制

  • 仅支持 TCP 和 UDP 套接字
  • DHCP 通常通过底层 TCP/IP 堆栈而不是 NetX Duo 来完成
  • 底层 TCP/IP 堆栈的其他限制