文章目录
协议设计上,UDP的校验和功能是可选的,Linux实现时,UDP的校验和功能默认是开启的,不过应用程序可以通过选项SO_NO_CHECK设置该能力。
校验和的计算本身是协议自己的事情,和硬件无关,但是往往为了更加的高效,可能硬件提供了部分或者全部的校验和功能,这就导致代码实现中,校验和相关的逻辑显得有些复杂。这篇笔记分析了UDP的校验和实现细节。
关于校验和API的使用见笔记linux网络校验和计算API。
数据结构
在sk_buff和net_device两个结构中,为校验和的计算增加了特定的字段。
sk_buff校验和字段
#define CHECKSUM_NONE 0
#define CHECKSUM_UNNECESSARY 1
#define CHECKSUM_COMPLETE 2
#define CHECKSUM_PARTIAL 3
struct sk_buff
{
union {
__wsum csum;
struct {
__u16 csum_start;
__u16 csum_offset;
};
};
__u8 ip_summed:2,
}
联合体中哪个成员有效取决于ip_summed的值,ip_summed共两个bit,可取四个值,它们在发送和接收过程中表示的含义还有所不同。
接收过程中,ip_summed字段包含了网络设备硬件告诉L4软件当前校验和的状态,各值含义如下:
- CHECKSUM_NONE:硬件没有提供校验和,可能是硬件不支持,也可能是硬件校验出错但是并未丢弃数据包,这时L4软件需要自己进行校验和计算;
- CHECKSUM_UNNECESSARY:硬件已经进行了完整的校验,软件无需再进行检查。这时L4软件会跳过校验和检查;
- CHECKSUM_COMPLETE:硬件已经计算了L4报头和其payload部分的校验和,并将计算结果保存在了skb->csum中,L4软件只需要再计算伪报头即可;
发送过程中,ip_summed字段记录了L4软件想要告诉网络设备硬件关于当前数据包的校验和状态信心。各值含义如下:
- CHECKSUM_NONE:L4软件已经对数据包进行了完整的校验,或者该数据包不需要校验。总之这种情况下网络设备硬件无需做任何校验和计算;
- CHECKSUM_PARTIAL:L4软件计算了伪报头的校验和,并且将值保存在了数据报的L4层首部的check字段中,网络设备硬件需要计算其余部分的校验和(报文首部+数据部分)。硬件需要计算的报文范围是从skb->csum_start到报文最后一个字节,计算结果需要填写到(skb->csum_start + skb->csum_offset)处。
net_device校验和字段
net_device的feature字段定义了如下和校验和相关的标记,这些标记表明了硬件计算校验和的能力。
feature | 值 | 含义 |
---|---|---|
NETIF_F_IP_CSUM | 2 | 网络设备可以提供对基于IPv4的TCP和UDP数据包进行校验,其它协议报文不支持 |
NETIF_F_NO_CSUM | 4 | 网络设备的传输非常可靠,无需L4执行任何校验,环回设备一般设置该标记 |
NETIF_F_HW_CSUM | 8 | 网络设备可以对任何L4协议的数据包进行校验,基本很少有硬件能够实现 |
NETIF_F_IPV6_CSUM | 16 | 网络设备可以对基于IPv6的TCP和UDP数据包进行校验,其它协议报文不支持 |
根据上述基础值重新定义了如下几个flag:
#define NETIF_F_GEN_CSUM (NETIF_F_NO_CSUM | NETIF_F_HW_CSUM)
#define NETIF_F_V4_CSUM (NETIF_F_GEN_CSUM | NETIF_F_IP_CSUM)
#define NETIF_F_V6_CSUM (NETIF_F_GEN_CSUM | NETIF_F_IPV6_CSUM)
#define NETIF_F_ALL_CSUM (NETIF_F_V4_CSUM | NETIF_F_V6_CSUM)
注:这些概念和字段的含义同样适用于TCP校验和处理过程
接收报文的校验和计算
udp4_csum_init()
UDP接收到报文后,首先会调用该函数进行校验和检查。
/* Initialize UDP checksum. If exited with zero value (success),
* CHECKSUM_UNNECESSARY means, that no more checks are required.
* Otherwise, csum completion requires chacksumming packet body,
* including udp header and folding it to skb->csum.
*/
static inline int udp4_csum_init(struct sk_buff *skb, struct udphdr *uh, int proto)
{
const struct iphdr *iph;
int err;
// 这两个字段用于指示对报文的哪些部分进行校验,cov指coverage,
// 只有UDPLite使用,对于UDP,会对整个报文进行校验
UDP_SKB_CB(skb)->partial_cov = 0;
UDP_SKB_CB(skb)->cscov = skb->len;
// UDPLITE,忽略
if (proto == IPPROTO_UDPLITE) {
err = udplite_checksum_init(skb, uh)<