想要修改一个pcap包,除了对于提取后的每个特征进行修改以外,想要保证数据包的功能,能够重传,还要保证包头的校验和是计算正确的,这就需要按照数据包的具体内容对于包头校验和进行更改。
那么,都有哪些包头需要计算校验和,校验和又覆盖了哪些内容呢? 首先给出一个总结:
IP、ICMP、UDP和TCP报文头都有检验和字段,大小都是16bit。
(1)IP校验和:IP首部。
(2)ICMP校验和:ICMP首部+ICMP数据;
(3)UDP、TCP校验和:首部+数据+12个字节伪首部(源IP地址、目的IP地址、协议、TCP/UDP包长)。
发送数据包的一端会首先根据内容,计算校验和并填充数据包头,这里上述3种校验和的计算方法是基本一致的,包括:
(1)把校验和字段设置为0。
(2)把需要校验的数据看成以16位为单位的数字组成,依次进行二进制反码求和。
(3)把得到的结果存入校验和字段中。
而上述计算方法对于不同数据包包头的作用范围也不同,区别在于:
(1)IP校验和:只校验20字节的IP报头。
(2)ICMP校验和:覆盖整个报文(ICMP报头+ICMP数据)。
(3)UDP和TCP校验和:不仅覆盖整个报文,而且还有12个字节的IP伪首部,包括源IP地址(4字节)、目的IP地址(4字节)、协议(2字节)、TCP/UDP包长(2字节)。
另外,需要注意的是,UDP、TCP数据报的长度可以为奇数字节。因为计算时是16位为单位,此时计算校验和时需要在最后增加一个填充字节0(只用于计算校验和,不发送出去)。 而在UDP传输协议中,校验和是可选的,当校验和字段为0时,表明该UDP报文未使用校验和,接收方就不需要校验和检查了。那如果UDP校验和的计算结果是0时怎么办?这里给出答案,如果校验和的计算结果为0,则存入的值为全1(65535),这在二进制反码计算中是等效的。
接收数据包的一端,则会根据收到的实际内容,与发送端给出的校验和作比较,进行校验,其步骤为:
(1)把首部看成以16位为单位的数字组成,依次进行二进制反码求和,包括校验和字段;
(2)检查计算出的校验和的结果是否为0;
(3)如果等于0,说明被整除,校验和正确。否则,校验和就是错误的,协议栈要抛弃这个数据包。
这里给出二进制反码求和操作的原理:
(1)二进制反码求和,就是先把两个数取反,然后求和,如果最高位有进位,则向低位进1。
(2)另外,先取反后相加与先相加后取反,得到的结果是一样的。因此,通常实现代码都是先相加,最后再取反。
【一个例子】二进制反码求和
对一个无符号的数,先求其反码,然后从低位到高位,按位相加,有溢出则向高位进1(和一般的二进制法则一样),若最高位有进位,则向最低位进1。
这里的反码和有符号反码不同,不分正负,直接按位取反。此外,进位方式也与我们学习的加法法则不同,最高位有进位,需要向最低位进1。为什么要这样呢?
对此分析一下,上面的这种操作,使得在发送加法进位溢出时,溢出值并不是10000,而是1111,也就是当相加结果满1111时溢出,这样也可以说明为什么0000和1111都表示0了。
下面是两种二进制反码求和的运算:
原码加法运算:3(0011)+5(0101)=8(1000)
8(1000)+9(1001)=1(0001)
反码加法运算:3(1100)+5(1010)=8(0111)
8(0111)+9(0110)=2(1101)
从上面的例子中,当加法未发生溢出时,原码与反码加法运算结果一样;当有溢出时,结果就不一样了,原码是满10000溢出,而反码是满1111溢出,所以相差正好是1。
此外,还有需要清楚的问题,为什么我们使用的是反码和,而不是直接求和呢?或者是补码和【在计算机里面数据是以补码的形式存在】呢?
这里给出学习过程中了解到的,在TCP/IP校验和中使用反码求和的一些优点:
(1)不依赖系统是大端小端。即无论你是发送方计算机或者接收方检查校验和时,都不要调用htons或者ntohs,直接通过算法就可以得到正确的结果。用反码求和时,交换16位数的字节顺序,得到的结果相同,只是字节顺序相应地也交换了;而如果使用原码或者补码求和,得到的结果可能就不同。
(2)计算和验证校验和比较简单、快速。