校验和(checksum)算法,简单的说就是16位累加的反码运算:
计算函数如下:
我们在计算时是主机字节序,计算的结果封装成IP包时是网络字节序,注意这两者之间的区别,我们在从IP包里读取要转化为主机字节序,往IP包里存入时要转化为网络字节序在存入。
UINT32 Checksum(UINT32 cksum, VOID *pBuffer, UINT32 size)
{
2个字节累加,先取网络字节序低位左移8位(变成主机字节序高位),与(加)上 网络字节序中的高位(主机字节序地位),即网络字节序要先变成主机字节序在进行累加,
if (size > 0)
如果长度为奇数
如果总的字节数为奇数,则最后一个字节单独相加
累加完毕将结果中高16位再加到低16位上,重复这一过程直到高16位为全0
}
注意:UINT32 cksum的类型,这里是4个字节的,防止在累加的过程中,数据溢出,(例如 0xFF 累加时就会内存溢出)
详细的计算过程和原理如下
一:
ip 头 的计算:
直接对头部数据进行累加(不包括原来的checksum值):
1、ipv4包头
在ipv4 头中,版本类型和头长度加在一起是1 个字节(8位),各占4位,版本类型在前,长度在后,所以要取长度只能取低4 位,
因为不包括原来的checksum值,所以在每次计算前先把checksum的值置0,然后计算
对整个ip包头的累加
结果为计算值的反码,(别忘转化为网络字节序)
2、ipv6包头
在ipv6中已经省略了checksum 部分,但在后面的部分要有的,比如TCP/UDP包,别高兴的太早
二、
TCP/UDP 报文的计算(举例UDP):
这里的checksum包含两部分,一部分是伪头的累加,还有一部分是UDP 包的累加(不包括原来的checksum值)
伪头有分ipv4和ipv6两种,分别包含如下几部分,这里做下比较
| IPV4 | IPV6 |
目的地址 | 4字节(32位) | 16字节(128位) |
源地址 | 4字节(32位) | 16字节(128位) |
协议类型 | 1字节(8位)(Protocol) | 1字节(8位)(next header) |
(TCP/UDP)长度 | 2字节(16位) | 2字节(16位) |
| | |
1、
第一部分,伪头部分的计算:
sum
UDP的长度= UDP的包头长度 + UDP的数据长度
sum +=
或者,下面也是一样的,这里就是网络字节序和主机字节序的区别了,上面的是(主机字节序)直接累加,下面的是网络字节序,一定要变成主机字节序后累加
主机字节序转化为网络字节序,存入数据包中,一定要注意,我们做的所有累加也是网络字节序,这里一定要搞清楚,以防混淆搞错了
2个字节的累加,先取网络字节序的高位,右移8位,变成主机字节序的低位,累加
在取网络字节序的低位,左移8位,变成主机字节序的高位,累加
对4位的地址进行累加
对1位的协议类型进行累加
伪头部分计算完成
第二部分,UDP数据包的计算
对整个UDP包的累加
结果为计算值的反码,(别忘转化为网络字节序)
UDP数据包部分计算完成
2、
第一部分,伪头部分的计算:
sum
sum +=
或者
对16位的地址进行累加
伪头部分计算完成
第二部分,UDP数据包的计算
对整个UDP包的累加
结果为计算值的反码,(别忘转化为网络字节序)
UDP数据包部分计算完成