ICMP首部 与 IP首部 校验和的计算

本篇文章详细讲解了计算 ICMP 首部IP 首部 的计算方法,以及其校验方法

开头

以下内容是一段 IPv4 的示例首部数据包

4500 0073 0000 4000 4011 *B861* C0A8 0001
C0A8 00C7

注:被*引起的部分为需要计算的校验和

既然是要计算校验和,那开始肯定是不知道的,所以我们默认为0000,所以,我么就是要对下面这一串数据进行校验和操作。

4500 0073 0000 4000 4011 0000 C0A8 0001
C0A8 00C7

第一步

16字节相加求和,于是我们就有了

$$\mathtt{0x4500}+\mathtt{0x0073}+\mathtt{0x0000}+…+\mathtt{0x00C7}=\mathtt{0x2479C}$$

Go代码实现

注:传入data []byte,大端编码

var	sum    uint32
var	length = len(data)
var	index  int
for length > 1 {
	sum += uint32(data[index])<<8 + uint32(data[index+1])
	index += 2
	length -= 2
}
// 如果len(data)为奇数
if length > 0 {
	sum += uint32(data[index])
}

第二步

由于我们的校验码只有16bit,所以我们就要把上面的$\mathtt{0x2479C}$转为16bit
2479C可以转化为

0002 479C

那我们可以将两个相加,即

$$\mathtt{0x0002}+\mathtt{0x479C}=\mathtt{0x479E}$$

如果这样算下来还是超过16bit,则需再次进行此运算。 那么最多需要几次运算呢,我们可以算一下。

极端情况

由谷歌可知,TCP包最大为65535 bytes,以16bit 为一个小节的话,也就是32767.5个小节。 假设每个小节都是$\mathtt{0xFFFF}$(剩余的半个小节以$\mathtt{0x00FF}$计算)。
已知32767的十六进制为$\mathtt{0x7FFF}$,那求和最大是

$$\mathtt{0x7FFF}\times{}\mathtt{0xFFFF}+\mathtt{0x00FF}=\mathtt{0x7FFE8100} $$

第一次进行第二步运算后结果为$\mathtt{0x100FE }$,第二次为$\mathtt{0x00FF}$
所以不难看出,最多需要两次第二步的运算即可。

Go代码实现

sum = (sum >> 16) + (sum & 0xFFFF)
// 与上一行代码作用相同,
// 超过16bit的部分会在最后被舍弃掉
sum += sum >> 16

第三步

取反运算,这一步的目的是为了验证的方便。

$$\overline{\mathtt{0x479E}} = \mathtt{0xB861}$$

Go代码实现

return uint16(^sum)

总结

Go代码实现

func Checksum(data []byte) uint16 {
    var	sum    uint32
    var	length = len(data)
    var	index  int
    for length > 1 {
    	sum += uint32(data[index])<<8 + uint32(data[index+1])
    	index += 2
    	length -= 2
    }
    // 如果len(data)为奇数
    if length > 0 {
    	sum += uint32(data[index])
    }

    sum = (sum >> 16) + (sum & 0xFFFF)
    // 与上一行代码作用相同,
    // 超过16bit的部分会在最后被舍弃掉
    sum += sum >> 16
    return uint16(^sum)
}
AUTHOR  :  Richard Chen
ARTICLE LICENSE  :  CC BY-SA 4.0
CODE LICENSE  :  MIT

Next: Tomoyo After

Prev: 在 Go 中获取 GOPATH 的最佳方案