12. 报文处理

ODP应用程序旨在处理数据包。 为了帮助处理数据包,使得应用程序能够操作数据包数据及其元数据。 数据包通过各自实现的抽象数据类型 odp_packet_t 来引用。

当报文到达源 odp_pktio_t 并且被应用程序直接或间接(通过调度队列)接收时,报文对象被创建。 当他们通过相关联的的传输队列传输到 odp_pktio_t 时,他们可能被隐式释放,或者通过调用 odp_packet_free() 来释放。

有时,应用程序可能直接发起一个数据包,或者通过从现有数据包导出新的数据包,ODP提供了对应的API以实现这些处理。 应用程序创建的数据包可以通过回环口重新送回并进行分类,或者应用程序可以根据需要进行自己的解析。

与数据包相关联的各种属性(如解析结果)存储在元数据中,ODP提供了对应的API以允许应用程序操作并修改这些信息。

12.1. 数据包结构和概念

报文是由符合诸如以太网架构格式的八位字节序组成,可以通过 ODP PKTIO抽象接口进行接收和发送。 数据包长度是指数据包的字节数。 ODP中数据包的数据是通过偏移来引用的,因为他们反映了数据包的逻辑内容和结构,而与特定的ODP实现和如何存储数据无关。

这些概念如下图所示:

../_images/odp-packet-structure.svg

ODP Packet Structure

报文数据包括0个或多个报头,0个或多个字节的payload,再接0个或多个报尾。 这里显示的是允许应用程序检查和检索数据包的各个部分并操作其结构的各种API。

为了支持数据包操作,预定义了headroom和tailroom与数据包相关联。 可以通过操作这些区域来调整数据包。 典型的数据包处理包括通过数据包接收时调用 odp_pull_head() 从数据包中剥离报头,数据包发送时调用 odp_push_head() 插入新的报头。 注意,因为headroom和tailroom表示保留的区域,因此这些区域在通过相关的push操作成为报文的一部分之前,不能被ODP应用程序寻址或直接使用。 类似的,通过pull操作删除的字节就不能被访问了。

12.2. 报文段和寻址

ODP平台使用一系列方法和技术来有效存储和处理数据包。 这些技术各个平台各不相同,因此为了保证可移植性,ODP提供一些数据包引用的约定。

ODP API通过抽象数据结构 odp_packet_t 来引用数据包对象。 描述数据包的系统元数据的各种位与数据包相关联。 通过参考元数据,ODP应用程序通过最小化检查报文数据的需求来加速报文处理。 这是因为,通过解析和分类功能来填充元数据,该功能与通过ODP调度程序呈现给应用程序之前发生的入口处理相耦合。

当ODP应用程序需要检查数据报内容时,它通过API调用来获取数据包的访问地址,该API调用可用于应用程序的一致性访问。 为了确保可移植性,ODP应用程序假定底层实现将数据包存储在预定于的实现及大小可管理的段中。 这表示应用程序可以通过正常的存储器访问来引用数据包的连续可寻址部分。 ODP提供API,允许应用程序按照需要以有效和便携的方式对数据包进行操作。 通过将这些与数据包提供的元数据结合,ODP应用程序可以完全独立于平台的方式运行,同时在支持ODP的各种平台上实现最佳性能。

报文段寻址及元数据的关系如下图所示:

../_images/odp-packet-seg.png

ODP Packet Segmentation

报文元数据在解析阶段设置,标识报文中各种报头的起始偏移量。 报文本身被存储为由ODP实现管理的区域的一系列段。 段0是数据包的第一个段,并且通常是数据包的headroom和报头所在的位置。 根据报文长度的不同,附加段可以是报文的一部分,并且包含报文剩余的有效载荷和尾部。 应用程序不需要关注段,除非当前应用程序需要对报文进行寻址。 因此,例如,如果应用程序进行类似 odp_packet_l4_ptr() 的调用来获取数据包4层头的地址时,从该调用返回的长度就是从4层头部开始的可连续寻址的长度。 这是因为以下字节占用了不同的段,并且可能存储在不同地方。 为了获得对这些字节的访问,应用程序只需要寻址到该偏移量,并且能够寻址占用下一个段的数据包字节等。 请注意,任何数据包可寻址性调用的返回长度始终是剩余数据包长度或其包含段的大小中较小的。 因此,上图中的段2的映射将返回仅扩展到分组结尾的长度,因为剩余的字节是为数据包保留的尾部的一部分,并且应用程序不可用,直到可用通过适当的API调用。

不仅PUSH/PULL API允许应用程序对当前段结构中的数据包执行高效操作,ODP还提供了允许段添加/删除的API。 odp_packet_extend_head()odp_packet_trunc_head() API允许在数据包开始处插入或删除段,而 odp_packet_extend_tail()odp_packet_trunc_tail() 允许在数据包尾部添加删除段。 通过向数据包添加一个或多个段来扩展数据包,可以允许实现自定义长度的数据包。 截断数据包会删除一个或多个数据段,以缩小数据包的大小。

12.3. 元数据处理

如上所述,作为报文接收阶段分类处理的一部分,报文元数据通常是由解析器设置。 需要注意的是,作为处理数据包的一部分,应用程序可能会更改此元数据,以反映报文内容和结构的变化。 虽然更改元数据可能会影响一些ODP API,更改元数据旨在记录应用程序对数据包的更改,但本身不会导致进行这些更改。 例如,如果应用程序通过使用 odp_packet_l3_offset_set() API来更改L3偏移量,那么后续对 odp_packet_l3_ptr() 的调用将返回一个从该更改的偏移开始的地址。 更改属性,如 odp_packet_has_udp_set() 这种操作本身不会将非UDP数据包变成有效的UDP数据包。 预估应用程序更改数据包时应该谨慎,以确保生成的元数据更改正确反映应用程序对数据包的修订。

12.4. 报文操作

ODP报文操作API主要分成两类:不改变数据包段结构的以及可能会改变数据包段结构的。 上面已经举过这样的例子。 PUSH/PULL API允许对数据包headroom/tailroom进行处理,这不会导致数据包原有分段的改变。 而EXTEND/TRUNC API提供相同的功能,但是潜在的,可能会将添加段到数据包,或者从数据包删除段。

具有执行功能类似的两类API的原因是在大多数操作实现中,不改变报文段结构的操作将比那些改变段结构的操作更加高效。 为了解决这个问题,可能涉及数据包段更改的API总是将 odp_packet_t 作为返回值。 应用程序应该使用这个新返回的指针。

为了使以这种方式操作数据包的应用程序能够最有效地运行,这些API的返回值遵循标准约定。 通常,小于零的返回值表示错误,输入数据包将不会有变化。 返回值为零表示成功,但也表示任何缓存的数据包的可寻址性仍然有效。 大于零的返回值也表示成功,但潜在地改变了数据包可寻址性。 例如,如果应用程序先前通过 odp_packet_l3_ptr() API获得了数据包的第3层头的可寻址性,则返回值为0将意味着应用程序可能会继续使用该指针来访问L3头, 而返回值大于零意味着应用程序应该重新发出该调用以重新获得可寻址性,因为报文段可能已经改变,因此旧指针可能不再有效。

12.4.1. 报文拷贝

数据包最简单的操作是制作数据包的全部或部分副本。 odp_packet_copy()odp_packet_copy_part() API用于返回包含现有数据包的整体或选定部分的新数据包。 请注意,这些操作还指定新数据包申请的报文池。

12.4.2. 报文数据拷贝及移动

ODP提供了几个API,使得数据包的部分可以复制到存储区域、另一个数据包或单个数据包中,如下所示:

../_images/odp-packet-copy-move.svg

ODP Packet Data Copying and Moving Operations

当源或目的地址是ODP数据包时,这些API提供边界检查。 这意味着,数据必须在 0..odp_packet_len()-1 的偏移范围内。 对于涉及内存区域的操作,调用方负责确保 odp_packet_copy_to/from_mem() 引用的内存区域有效。

在单个数据包中处理数据时,提供了两个类似的API:odp_packet_copy_data()odp_packet_move_data() 。 其中,移动操作更为通用,并且即使在源和目的数据区域重叠时也可以使用移动操作。 仅当调用者知道这两个区域不重叠时,才能使用复制操作,且此时复制更高效。 当处理重叠的存储区域时,odp_packet_move_data() 操作就好像是源区域首先被复制到不重叠的单独存储区域,然后再从该区域复制到目标区域。