【Linux4.1.12源码分析】IP层csum计算

时间:2022-07-20 11:04:48

从收包和发包来看IP层的csum是如何计算的,是如何进行校验的。csum值为2个字节长度。

发包流程如下所示,在__ip_local_out_sk函数中调用ip_send_check函数进行csum计算。

ip_local_out->ip_local_out_sk->__ip_local_out->__ip_local_out_sk

ip_send_check函数

/* Generate a checksum for an outgoing IP datagram. */
void ip_send_check(struct iphdr *iph)
{
iph->check = 0; //设置check为0
iph->check = ip_fast_csum((unsigned char *)iph, iph->ihl); //把计算csum的值保存到check字段
}
ip_fast_csum函数 (ip_fast_csum的实现版本较多,选择其中一个利于理解的进行分析)
/* *	This is a version of ip_compute_csum() optimized for IP headers, *	which always checksum on 4 octet boundaries. */__sum16 ip_fast_csum(const void *iph, unsigned int ihl){	return (__force __sum16)~do_csum(iph, ihl*4);}
do_csum函数

static unsigned int do_csum(const unsigned char *buff, int len)
{
int odd;
unsigned int result = 0;

if (len <= 0)
goto out;
odd = 1 & (unsigned long) buff;
if (odd) { //如果地址未按照2对其,则需要按2对齐以提升性能
#ifdef __LITTLE_ENDIAN
result += (*buff << 8);
#else
result = *buff;
#endif
len--;
buff++;
}
if (len >= 2) {
if (2 & (unsigned long) buff) { //除4余2场景,先累加该16位的值,剩下4字节的整数倍了
result += *(unsigned short *) buff;
len -= 2;
buff += 2;
}
if (len >= 4) {
const unsigned char *end = buff + ((unsigned)len & ~3); //计算出按照4字节的结尾
unsigned int carry = 0;
do {
unsigned int w = *(unsigned int *) buff;
buff += 4;
result += carry; //值加进位
result += w; //值累加
carry = (w > result); //w值超过累加值,则进位为1,为什么用大于来判断
} while (buff < end);
result += carry;
result = (result & 0xffff) + (result >> 16); //32位转化为16位,此时可能有进位
}
if (len & 2) {
result += *(unsigned short *) buff; //剩余2字节的值,再累加
buff += 2;
}
}
if (len & 1) //如果还有1字节,继续累加
#ifdef __LITTLE_ENDIAN
result += *buff;
#else
result += (*buff << 8);
#endif
result = from32to16(result); //转化为16位值
if (odd)
result = ((result >> 8) & 0xff) | ((result & 0xff) << 8); //16位两个字节值互换
out:
return result;
}
do_csum函数就是把ip头的数据按照16字节累加起来,累加值取反后设置到IP头的check字段。


收包阶段csum的判断方法如下:

	if (unlikely(ip_fast_csum((u8 *)iph, iph->ihl)))	//ip头csum校验
goto csum_error;
如果ip_fast_csum值不为零,则校验失败。


根据do_sum函数定义,该函数需要满足如下两个条件(XY为16位数值):

do_sum(X + Y) == do_sum(X)  + do_sum(Y); -----> do_sum(M + Y) = do_sum(M) + do_sum(Y)     M为16位整数倍的数值

do_sum(do_sum(X)) == do_sum(X);

 

那么来计算下收包阶段的判断:

设X为IP头数据(不包括check),Y为check值;

ip_fast_csum(X + Y)= ~do_sum(X + Y) 

                                          = ~(do_sum(X)  + do_sum(Y)) 

                                          = ~(do_sum(X) + do_sum(~do_sum(X))) 

                                          = ~(do_sum(X) + do_sum(~do_sum(X)) 

                                          = ~(do_sum(X) +  ~do_sum(X) )

                                          = ~(FFFF) = 0

即数据内容不变的情况下,可以保证ip_fast_csum的值为0,但是不能保证数据改变的情况下该值不为0(低概率事件);