为什么将ICMP校验和移位16位

Nic*_*ady 3 haskell checksum icmp

我正在努力理解为什么ICMP校验和总数(在补码之前)是此代码行中的总数+向右移16位:

checksum bs = let bs' = (if (BL.length bs) `mod` 2 == 0 then bs else BL.snoc bs 0)
                  ws = runGet listOfWord16 bs'
                  total = sum (map fromIntegral ws) :: Word32
              in complement (fromIntegral total + fromIntegral (total `shiftR` 16))
Run Code Online (Sandbox Code Playgroud)

RFC 792关于计算校验和的说法是这样的:

校验和

校验和是ICMP消息以ICMP类型开头的补码和的16位补码。为了计算校验和,校验和字段应为零。如果总长度为奇数,则用零的一个八位位组填充接收到的数据以计算校验和。将来可能会替换此校验和。

我理解为什么bs'要按照“如果总长度为奇数,则将接收到的数据填充一个零的八位位组以计算校验和”的要求进行计算。

我也可以理解将本行代码中完成的16位字的总和 total = sum (map fromIntegral ws) :: Word32

我只是不知道为什么在这行代码中:

complement (fromIntegral total + fromIntegral (total `shiftR` 16))
Run Code Online (Sandbox Code Playgroud)

+ fromIntegral (total `shiftR` 16)应该包括在所有。

注意:我已经用wireshark验证了校验和是正确的,仅当我total + total `shiftR` 16对链接的代码行中的进行补充时才正确。所以我知道这是正确的,我只是不明白为什么。

Cac*_*tus 5

RFC 1071详细描述了校验和定义,包括以下重要部分:

在2的补码机上,必须通过“结束进位”来计算1的补码和,即,将最高有效位的任何溢出都加到最低有效位中。

在您的代码中

total = sum (map fromIntegral ws) :: Word32
Run Code Online (Sandbox Code Playgroud)

是32位和,即其低16位是不带进位的和,高16位将包含进位之和。通过使用fromIntegral :: Word32 -> Word16截断的事实,我们可以

low = fromIntegral total :: Word16
high = fromIntegral $ total `shiftR` 16 :: Word16
Run Code Online (Sandbox Code Playgroud)

这样我们就可以计算出

eac = low + high
Run Code Online (Sandbox Code Playgroud)