struct sk_buff¶
sk_buff
是表示数据包的主要网络结构。
sk_buff 的基本结构¶
struct sk_buff
本身是一个元数据结构,不保存任何数据包数据。所有数据都保存在关联的缓冲区中。
sk_buff.head
指向主“头部”缓冲区。头部缓冲区分为两部分
数据缓冲区,包含头部,有时也包含有效载荷;这是 skb 中由
skb_put()
或skb_pull()
等通用辅助函数操作的部分;共享信息 (struct skb_shared_info),其中保存一个指向只读数据的指针数组,格式为 (页,偏移量,长度)。
可选地,skb_shared_info.frag_list
可能指向另一个 skb。
基本图可能如下所示
---------------
| sk_buff |
---------------
,--------------------------- + head
/ ,----------------- + data
/ / ,----------- + tail
| | | , + end
| | | |
v v v v
-----------------------------------------------
| headroom | data | tailroom | skb_shared_info |
-----------------------------------------------
+ [page frag]
+ [page frag]
+ [page frag]
+ [page frag] ---------
+ frag_list --> | sk_buff |
---------
dataref 和无头 skb¶
传输层发送其为重传而保留的有效载荷 skb 的克隆。为了允许堆栈的较低层添加其头部,我们将 skb_shared_info.dataref
分成两半。低 16 位计算引用的总数。高 16 位指示有多少引用仅是有效载荷。 skb_header_cloned()
检查是否允许 skb 添加/写入头部。
skb 的创建者(例如 TCP)将其 skb 标记为 sk_buff.nohdr
(通过 __skb_header_release()
)。从标记的 skb 创建的任何克隆都将填充 sk_buff.hdr_len
与可用的空余空间。如果只存在一个克隆,则可以随意修改空余空间。传输层内部的调用顺序是
<alloc skb>
skb_reserve()
__skb_header_release()
skb_clone()
// send the clone down the stack
这不是一个非常通用的结构,它取决于传输层是否执行正确的操作。实际上,通常只有一个仅包含有效负载的 skb。拥有多个具有不同 hdr_len 长度的仅包含有效负载的 skb 是不可能的。仅包含有效负载的 skb 不应离开其所有者。
校验和信息¶
堆栈和网络驱动程序之间校验和卸载的接口如下...
设备接收的数据包的校验和计算¶
校验和验证的指示在 sk_buff.ip_summed
中设置。可能的值有
CHECKSUM_NONE
设备没有校验此数据包,例如由于缺少功能。数据包包含完整(但未验证)的校验和在数据包中,但不在 skb->csum 中。因此,在这种情况下,skb->csum 是未定义的。
CHECKSUM_UNNECESSARY
您正在处理的硬件不计算完整校验和(如
CHECKSUM_COMPLETE
中),但它会解析标头并验证特定协议的校验和。对于此类数据包,如果它们的校验和正确,则会设置CHECKSUM_UNNECESSARY
。但是,在这种情况下,sk_buff.csum
仍然是未定义的。即使验证了校验和,驱动程序或设备也绝不能修改数据包中的校验和字段。CHECKSUM_UNNECESSARY
适用于以下协议TCP:IPv6 和 IPv4。
UDP:IPv4 和 IPv6。设备可能会将 CHECKSUM_UNNECESSARY 应用于 IPv4 或 IPv6 的零 UDP 校验和,在这种情况下,网络堆栈可能会执行进一步的验证。
GRE:仅当标头中存在校验和时。
SCTP:指示 SCTP 标头中的 CRC 已验证。
FCOE:指示 FC 帧中的 CRC 已验证。
sk_buff.csum_level
指示在数据包中发现的连续校验和的数量减一,这些校验和已被验证为CHECKSUM_UNNECESSARY
。例如,如果设备接收到一个 IPv6->UDP->GRE->IPv4->TCP 数据包,并且设备能够验证 UDP(可能为零)、GRE(设置了校验和标志)和 TCP 的校验和,则sk_buff.csum_level
将设置为二。如果设备只能验证 UDP 校验和而不能验证 GRE,要么是因为它不支持 GRE 校验和,要么是因为 GRE 校验和错误,则 skb->csum_level 将设置为零(在这种情况下不考虑 TCP 校验和)。CHECKSUM_COMPLETE
这是最通用的方式。设备会提供由
netif_rx()
看到的 _整个_ 数据包的校验和,并将其填充到sk_buff.csum
中。这意味着硬件无需解析 L3/L4 头部即可实现此功能。备注
即使设备仅支持某些协议,但能够生成 skb->csum,它也必须使用 CHECKSUM_COMPLETE,而不是 CHECKSUM_UNNECESSARY。
CHECKSUM_COMPLETE 不适用于 SCTP 和 FCoE 协议。
CHECKSUM_PARTIAL
按照 CHECKSUM_PARTIAL 的输出描述,校验和被设置为卸载到设备。这种情况可能发生在直接从另一个 Linux 操作系统接收到的数据包上,例如,同一主机上的虚拟化 Linux 内核,或者它可能在 GRO 或远程校验和卸载的输入路径中设置。为了校验和验证的目的,由 skb->csum_start + skb->csum_offset 引用的校验和以及数据包中任何先前的校验和都被视为已验证。数据包中任何位于正在卸载的校验和之后的校验和不被视为已验证。
非 GSO 的发送校验和计算¶
堆栈在数据包的 sk_buff.ip_summed
中请求校验和卸载。值如下:
CHECKSUM_PARTIAL
驱动程序需要计算由 hard_start_xmit() 看到的、从
sk_buff.csum_start
开始到结尾的数据包的校验和,并将校验和记录/写入偏移量sk_buff.csum_start
+sk_buff.csum_offset
处。驱动程序可以验证 csum_start 和 csum_offset 的值是否在给定数据包长度和偏移量的情况下有效,但不应尝试验证校验和是否引用了合法的传输层校验和 —— 验证 csum_start 和 csum_offset 是否正确设置是堆栈的职责。当堆栈请求数据包的校验和卸载时,驱动程序必须确保正确设置校验和。驱动程序可以将校验和计算卸载到设备,或者调用 skb_checksum_help(如果设备不支持特定校验和的卸载)。
NETIF_F_IP_CSUM
和NETIF_F_IPV6_CSUM
正在被弃用,而推荐使用NETIF_F_HW_CSUM
。新设备应使用NETIF_F_HW_CSUM
来指示校验和卸载能力。可以调用 skb_csum_hwoffload_help() 来根据网络设备的校验和计算能力来解析CHECKSUM_PARTIAL
:如果数据包与它们不匹配,则调用 skb_checksum_help() 或 skb_crc32c_help()(取决于sk_buff.csum_not_inet
的值,请参阅 非 IP 校验和 (CRC) 卸载)来解析校验和。CHECKSUM_NONE
skb 已由协议进行校验和计算,或者不需要校验和。
CHECKSUM_UNNECESSARY
这与输出上的校验和卸载的 CHECKSUM_NONE 含义相同。
CHECKSUM_COMPLETE
不用于校验和输出。如果驱动程序观察到在 skbuff 中设置此值的包,则应将该包视为设置了
CHECKSUM_NONE
。
非 IP 校验和 (CRC) 卸载¶
|
此功能表示设备能够卸载数据包中的 SCTP CRC。为了执行此卸载,堆栈将相应地设置 csum_start 和 csum_offset,将 ip_summed 设置为 |
|
此功能表示设备能够卸载数据包中的 FCOE CRC。为了执行此卸载,堆栈将 ip_summed 设置为 |
GSO 的输出校验和计算¶
在 GSO 数据包(skb_is_gso() 为 true)的情况下,校验和卸载由 gso_type 中的 SKB_GSO_* 标志暗示。最明显的是,如果 gso_type 为 SKB_GSO_TCPV4
或 SKB_GSO_TCPV6
,则意味着 TCP 校验和卸载是 GSO 操作的一部分。如果校验和正在使用 GSO 进行卸载,则 ip_summed 为 CHECKSUM_PARTIAL
,并且 csum_start 和 csum_offset 都设置为引用正在卸载的最外层校验和(UDP 封装可以实现两个卸载的校验和)。