【lwip】09-IPv4协议&超全源码实现分析( 三 )


需要注意的是,由于IP数据报首部的TTL字段每结果应该路由器都会-1,所以IP数据报首部检验和字段每经过一个路由器都要重新计算赋值 。
参考:关于wireshark的Header checksum出问题解决方案:https://www.it610.com/article/1290714377560858624.htm
检验和计算可能由网络网络驱动,协议驱动,甚至是硬件完成 。
高层校验通常是由协议执行,并将完成后的包转交给硬件 。
比较新的网络硬件可以执行一些高级功能,如IP检验和计算,这被成为checksum offloading 。网络驱动不会计算校验和,只是简单将校验和字段留空或填入无效信息,交给硬件计算 。
发送数据时,首部校验和计算:二进制反码求和 。

  • 把IP数据包的校验和字段置为全0 。
  • 将首部中的每 2 个字节当作一个数,依次求和 。
  • 把结果取反码 。
  • 把得到的结果存入校验和字段中 。
接收数据时,首部校验和验证过程:
  • 首部中的每 2 个字节当作一个数 , 依次进行求和,包括校验和字段 。
  • 检查计算出的校验和的结果是否全为1(反码应为16个0) 。
  • 如果等于零,说明被整除,校验和正确 。否则,校验和就是错误的,协议栈要抛弃这个数据包 。
为什么计算出的校验和结果全为1?
因为如果校验依时次求和 , 不包含校验和字段的话,得出的值就是校验和字段的反码 。
校验和的反码和校验和求和 , 当然是全1啦 。
9.3.11 二进制反码求和IP/ICMP/IGMP/TCP/UDP等协议的校验和算法都是相同的 。
二进制反码求和:(求和再反码,结果一致)
  • 对两个二进制数进行加法运算 。
  • 加法规则:0+0=0,0+1=1,1+1=10(进位1加到下一bit) 。
  • 若最高两位相加仍然有进位 , 则在最后的结果中+1即可 。
  • 对最终结果取反码 。
相关源码参考LwIP\core\inet_chksum.c中的lwip_standard_chksum()
这里提取版本3分析:
  • 前期先确保4字节对齐,如果不是4字节对齐,就补到4字节对齐 。
  • 后面采用32 bit累加 。溢出后 , 在低位+1 。
    • 为什么?:这里读者可能会有个疑问,IP数据包的校验和不是要求16 bit求和的吗?这里为什么能用32 bit求和?
    • 答:起始要求是16 bit,但是实际计算时只要大于16 bit即可 , 因为到最后,可以把高位折叠加到低位 。
    • 例子:按32bit累加 , 溢出就在低位+1 。其实就是两组两个(高、低)16 bit对应累加,低16 bit累加的进位给高16 bit里加回1了 。而高16 bit累加的进位在底16 bit里加回1了(手动) 。这样,累加到最后剩下32bit 。把高16bit和低16bit进行累加,进位再加1即可快速得到16bit的校验和 。
  • 数据后部分可能不是8字节对齐 , 所以剩余的字节也需要16bit校验和处理 。
思路图:
【lwip】09-IPv4协议&超全源码实现分析

文章插图
由于目的是16 bit的校验和 。其实可以看成两组2个8bit对应相加,低8bit组进位给高8bit组,高8bit组进位给低8bit组 。所以相加值是对应高、低8bit相互独立的 。
而下面函数就是利用这个特性 , 如果首字节为奇地址,先单独取出来放到t的高地址,因为后续的统计字节顺序是返的 。等待全部统计完毕后,再把两个字节顺序调换即可 。
如果是偶地址开始,那符合校验和规则,最后不需要调换字节顺序 。
#if (LWIP_CHKSUM_ALGORITHM == 3) /* Alternative version #3 *//** * An optimized checksum routine. Basically, it uses loop-unrolling on * the checksum loop, treating the head and tail bytes specially, whereas * the inner loop acts on 8 bytes at a time. * * @arg start of buffer to be checksummed. May be an odd byte address. * @len number of bytes in the buffer to be checksummed. * @return host order (!) lwip checksum (non-inverted Internet sum) * * by Curt McDowell, Broadcom Corp. December 8th, 2005 */u16_tlwip_standard_chksum(const void *dataptr, int len){const u8_t *pb = (const u8_t *)dataptr; /* 取数据的地址 */const u16_t *ps;u16_t t = 0;const u32_t *pl;u32_t sum = 0, tmp;int odd = ((mem_ptr_t)pb & 1); /* 判断是否为奇地址 */if (odd && len > 0) { /* 如果不是2直接对齐 *//* 缓存奇地址上的字节,存于 t 的高位 。数据地址偏移为偶,2字节对齐 。*//* 存到高位是为了和后面字节序保持一致 , 方便在最后一次性更正 。*/((u8_t *)&t)[1] = *pb++;len--; /* 字节数-1 */}/* 2字节对齐的数据起始地址 */ps = (const u16_t *)(const void *)pb;if (((mem_ptr_t)ps & 3) && len > 1) {/* 如果不是4字节对齐 *//* 把多出来的两字节保存到sum */sum += *ps++;len -= 2;}/* 4字节对齐的数据起始地址 */pl = (const u32_t *)(const void *)ps;while (len > 7){tmp = sum + *pl++;/* ping */if (tmp < sum) {tmp++;/* 溢出,手动+1 */}sum = tmp + *pl++;/* pong */if (sum < tmp) {sum++;/* 溢出,手动+1 */}len -= 8;}/* 折叠高、低16bit */sum = FOLD_U32T(sum);/* 处理剩余的字节 */ps = (const u16_t *)pl;/* 2字节处理 */while (len > 1) {sum += *ps++;len -= 2;}/* 剩余单字节 */if (len > 0) {/* 补到前面t的低位 */((u8_t *)&t)[0] = *(const u8_t *)ps;}sum += t;/* 把t也一起16bit校验和 *//* 两次折叠高、低16bit */sum = FOLD_U32T(sum);sum = FOLD_U32T(sum);if (odd) { /* 因为前面是从第二个字节和第三个字节开始进行统计的,字节序反了,这里在结果更正 。*/sum = SWAP_BYTES_IN_WORD(sum);}return (u16_t)sum; /* 返回校验和 */}#endif

推荐阅读