linux网络校验和计算API

本文介绍了Linux网络校验和的计算方法及其API,包括IP首部的专用API如ip_compute_csum()和ip_fast_csum(),以及通用API如skb_checksum()和csum_fold()等。这些API用于在网络报文的发送和接收过程中确保数据的完整性和正确性。
摘要由CSDN通过智能技术生成

校验和算法

发送方先把校验和字段置为0,然后对参与校验的数据每16bit进行二进制反码求和,然后将结果保存在校验和字段中。由于校验和计算方法是累加求和的方式,所以将校验和置为0可以将校验和字段本身排除在校验结果之外。

接收方收到报文后,带着校验和字段一起对对需要校验的数据每16bit进行二进制反码求和,如果最后的计算结果为0,那么校验通过,否则数据有损坏。

IP首部校验和计算API

由于IP首部的校验和计算范围只覆盖IP首部,所以其校验和的计算总是由软件来完成。考虑到转发过程中,往往会改变IP首部的某些字段(如TTL),所以为了更高效更新校验和,特意为IP首部的校验和计算提供了一些专用API。

ip_compute_csum()

/*
 * this routine is used for miscellaneous IP-like checksums, mainly
 * in icmp.c
 */
__sum16 ip_compute_csum(const void *buff, int len);

通用的IP首部校验和计算API。

ip_fast_csum()

/*
 *	This is a version of ip_compute_csum() optimized for IP headers,
 *	which always checksum on 4 octet boundaries.
 */
static inline __sum16 ip_fast_csum(const void *iph, unsigned int ihl);

该函数针对IP首部校验和的计算进行了优化,它的实现是体系结构相关的。

ip_send_check()

/* Generate a checksum for an outgoing IP datagram. */
__inline__ void ip_send_check(struct iphdr *iph)
{
	iph->check = 0;
	iph->check = ip_fast_csum((unsigned char *)iph, iph->ihl);
}

就是ip_fast_csum()的包裹函数,在计算之前会把IP首部校验和清0再计算,用于发送报文的IP首部校验和计算。

ip_decrease_ttl()

static inline int ip_decrease_ttl(struct iphdr *iph)
{
	u32 check = (__force u32)iph->check;
	check += (__force u32)htons(0x0100);
	iph->check = (__force __sum16)(check + (check>=0xFFFF));
	return --iph->ttl;
}

该函数用于再报文转发过程中递减TTL并且更新校验和。

通用校验和计算API

通用校验和计算API主要由L4协议使用,这些协议的校验和通常会覆盖整个报文。

skb_checksum()

__wsum skb_checksum(const struct sk_buff *skb, int offset, int len, __wsum csum);

将skb封装的数据逻辑上是一块连续内存(内部可能在frags数组和frag_list中也有数据)。该函数以入参csum为校验和计算初始值,计算从内存开头(skb->data)偏移offset字节开始的len字节数据的校验和。

增加csum入参,是为了让该调用者能够先计算一块内存校验和,然后将其计算结果累加到skb_checksum()的结果上,后续很多接口是类似的功能。

csum_fold()

/*
 *	Fold a partial checksum
 */
static inline __sum16 csum_fold(__wsum sum);

为了计算高效,校验和很多时候是以32bit为单位计算的,该函数将32位的校验和计算结果转换为16位的结果(方法就是把高16位折到低16位上)。这通常是校验和计算的最后一步。

csum_partial系列

/*
 * computes the checksum of a memory block at buff, length len,
 * and adds in "sum" (32-bit)
 *
 * returns a 32-bit number suitable for feeding into itself
 * or csum_tcpudp_magic
 *
 * this function must be called with even lengths, except
 * for the last fragment, which may be odd
 *
 * it's best to have buff aligned on a 32-bit boundary
 */
__wsum csum_partial(const void *buff, int len, __wsum sum);

/*
 * the same as csum_partial, but copies from src while it
 * checksums, and handles user-space pointer exceptions correctly, when needed.
 *
 * here even more important to align src and dst on a 32-bit (or even
 * better 64-bit) boundary
 */
__wsum csum_partial_copy_nocheck(const void *src, void *dst, int len, __wsum sum);
__wsum csum_partial_copy_from_user(const void __user *src, void *dst, int len,
    __wsum sum, int *err_ptr);

/*
 *	And now for the all-in-one: copy and checksum from a user iovec
 *	directly to a datagram
 *	Calls to csum_partial but the last must be in 32 bit chunks
 *
 *	ip_build_xmit must ensure that when fragmenting only the last
 *	call to this function will be unaligned also.
 */
int csum_partial_copy_fromiovecend(unsigned char *kdata, struct iovec *iov,
				 int offset, unsigned int len, __wsum *csump);

这组函数是最通用的校验和计算API了。它们的功能注释已经很清晰了。

csum_block_add/sub()

static inline __wsum csum_block_add(__wsum csum, __wsum csum2, int offset)
{
	u32 sum = (__force u32)csum2;
	if (offset&1)
		sum = ((sum&0xFF00FF)<<8)+((sum>>8)&0xFF00FF);
	return csum_add(csum, (__force __wsum)sum);
}

static inline __wsum csum_block_sub(__wsum csum, __wsum csum2, int offset)
{
	u32 sum = (__force u32)csum2;
	if (offset&1)
		sum = ((sum&0xFF00FF)<<8)+((sum>>8)&0xFF00FF);
	return csum_sub(csum, (__force __wsum)sum);
}

A和B两块数据的校验和分别位csum和csum2,如果现在这两块数据需要合并,那么它们的校验和也需要合并,此时可以使用csum_block_add(),它返回合并后的数据块的校验和。

类似的,如果B时A的一部分数据,现在需要把B从A中删除,它们也需要将B的校验和从A中删除,此时可以使用csum_block_sub(),它返回拆分后数据块A的校验和。

这两个函数都有个offset参数,是因为如果两块数据拆分或者合并时的偏移量不是偶数,因为校验和的计算必须是16位对齐的,所以需要特殊处理下。整体上来说,offset就是相对于数据块A开始的偏移量。

csum_add/sub()

static inline __wsum csum_add(__wsum csum, __wsum addend)
{
	u32 res = (__force u32)csum;
	res += (__force u32)addend;
	return (__force __wsum)(res + (res < (__force u32)addend));
}

static inline __wsum csum_sub(__wsum csum, __wsum addend)
{
	return csum_add(csum, ~addend);
}

这两个函数是csum_block_add/sub()的简化版,它们去掉了offset参数,所以调用者需要保证必须是16位对齐的。

skb_checksum_help()

这是设备接口层实现的一个函数,它整体的策略是让硬件校验和失效。网络协议栈底层在需要时会调用该函数。

/*
 * Invalidate hardware checksum when packet is to be mangled, and
 * complete checksum manually on outgoing path.
 */
int skb_checksum_help(struct sk_buff *skb)
{
	__wsum csum;
	int ret = 0, offset;

    // 只有接收方向才会设置CHECKSUM_COMPLETE,所以直接将skb->ip_summed
    // 设置为CHECKSUM_NONE,这样L4层软件就会自己进行校验
	if (skb->ip_summed == CHECKSUM_COMPLETE)
		goto out_set_summed;

    // 下面是针对出口数据报的处理

    // GSO会处理校验和,这里不处理
	if (unlikely(skb_shinfo(skb)->gso_size)) {
		/* Let GSO fix up the checksum. */
		goto out_set_summed;
	}

    // 软件重新进行校验和计算
	offset = skb->csum_start - skb_headroom(skb);
	BUG_ON(offset >= skb_headlen(skb));
	csum = skb_checksum(skb, offset, skb->len - offset, 0);

	offset += skb->csum_offset;
	BUG_ON(offset + sizeof(__sum16) > skb_headlen(skb));

	if (skb_cloned(skb) &&
	    !skb_clone_writable(skb, offset + sizeof(__sum16))) {
		ret = pskb_expand_head(skb, 0, 0, GFP_ATOMIC);
		if (ret)
			goto out;
	}
    // 将计算结果保存到报文指定位置
	*(__sum16 *)(skb->data + offset) = csum_fold(csum);
out_set_summed:
	skb->ip_summed = CHECKSUM_NONE;
out:
	return ret;
}

对于出口数据包,该函数重新进行校验和计算。对于入口数据包,它会使硬件校验和无效,进而触发L4层软件重新校验。

csum_tcpudp_magic()

static inline __wsum
csum_tcpudp_nofold(__be32 saddr, __be32 daddr, unsigned short len,
		   unsigned short proto, __wsum sum);
/*
 * computes the checksum of the TCP/UDP pseudo-header
 * returns a 16-bit checksum, already complemented
 */
static inline __sum16
csum_tcpudp_magic(__be32 saddr, __be32 daddr, unsigned short len,
		  unsigned short proto, __wsum sum);

这是个体系结构相关的接口,它用于帮助TCP和UDP协议在全部报文校验和(入参sum)的基础上添加伪首部的校验和。csum_tcpudp_magic()返回的是16位的校验和结果;csum_tcpudp_nofold()返回的是32位的校验和结果。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值