从收包和发包来看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. */ip_fast_csum函数 (ip_fast_csum的实现版本较多,选择其中一个利于理解的进行分析)
void ip_send_check(struct iphdr *iph)
{
iph->check = 0; //设置check为0
iph->check = ip_fast_csum((unsigned char *)iph, iph->ihl); //把计算csum的值保存到check字段
}
/* * 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)do_csum函数就是把ip头的数据按照16字节累加起来,累加值取反后设置到IP头的check字段。
{
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;
}
收包阶段csum的判断方法如下:
if (unlikely(ip_fast_csum((u8 *)iph, iph->ihl))) //ip头csum校验如果ip_fast_csum值不为零,则校验失败。
goto csum_error;
根据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(低概率事件);