【Linux4.1.12源码分析】IP层csum计算
2016-10-31 23:30
691 查看
从收包和发包来看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的实现版本较多,选择其中一个利于理解的进行分析)
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(低概率事件);
发包流程如下所示,在__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(低概率事件);
相关文章推荐
- 【Linux4.1.12源码分析】UDP层csum计算
- 【Linux4.1.12源码分析】IP层报文发送之ip_output
- 【Linux4.1.12源码分析】vxlan报文发送之udp_tunnel_xmit_skb
- 【Linux4.1.12源码分析】二层报文发送之报文GSO分段(IP层)
- 【Linux4.1.12源码分析】协议栈gro收包之VXLAN处理
- 【Linux4.1.12源码分析】邻居子系统实现分析 - ARP - ip_finish_output2()
- 【Linux4.1.12源码分析】协议栈gro收包之IP层处理
- 【Linux4.1.12源码分析】AF_INET raw socket实现原理分析
- 【Linux4.1.12源码分析】VXLAN之csum和remcsum实现分析(发包)
- 【Linux4.1.12源码分析】IP层报文发送之ip_local_out
- 【Linux4.1.12源码分析】二层报文发送之GSO条件判断
- 【Linux4.1.12源码分析】协议栈报文接收之IP层处理分析(ip_local_deliver)
- 【Linux4.1.12源码分析】二层报文发送之报文GSO分段(TCP)
- 【Linux4.1.12源码分析】VXLAN报文内核协议栈处理
- 【Linux4.1.12源码分析】vxlan报文发送之iptunnel_xmit
- 【Linux4.1.12源码分析】二层报文发送之报文GSO分段(MAC层)
- 【Linux4.1.12源码分析】AF_PACKET raw socket实现原理分析
- 【Linux4.1.12源码分析】邻居子系统实现分析
- 【Linux4.1.12源码分析】virtio_net之中断注册
- 【Linux4.1.12源码分析】收包软中断和NAPI