了解TCP校验和功能

时间:2014-03-13 09:21:40

标签: c++ c algorithm tcp checksum

我相信TCP校验和功能执行以下操作:

  1. 将伪头和TCP段头和数据分解为2个字节块。
  2. 如果长度不超过2个字节,则在最后一个块的末尾添加一个0字节的填充,以使其为2个字节。
  3. 获取总和的一个补码以获得TCP校验和。
  4. 听起来很简单。因此我编写了自己的通用checksum函数:

    #include <inttypes.h>
    #include <arpa/inet.h>
    
    uint16_t checksum(uint16_t * data, int size) {
        uint16_t sum = 0;
    
        int i = 0, length = size / 2;
    
        while (i < length) sum += data[i++];
    
        if (size % 2) sum += data[i] & 0xFF00;
    
        return htons(~sum);
    }
    

    然而,其他人编写的checksum函数似乎更复杂。例如:

    uint16_t checksum(uint16_t * addr, int len) {
        int nleft = len;
        int sum = 0;
    
        uint16_t * w = addr;
        uint16_t answer = 0;
    
        while (nleft > 1) {
            sum += *w++;
            nleft -= sizeof(uint16_t);
        }
    
        if (nleft == 1) {
            *(uint8_t *) (&answer) = *(uint8_t *) w;
            sum += answer;
        }
    
        sum = (sum >> 16) + (sum & 0xFFFF);
        sum += (sum >> 16);
        answer = ~sum;
        return (answer);
    }
    

    我对此代码有几个问题:

    1. 声明*(uint8_t *) (&answer) = *(uint8_t *) w;实际上做了什么?
    2. 为什么我们把这笔钱当作:

      sum = (sum >> 16) + (sum & 0xFFFF);
      sum += (sum >> 16);
      
    3. 计算TCP校验和的方法有变化吗?

    4. 我真的不明白为什么会这样做sum = (sum >> 16) + (sum & 0xFFFF)。考虑sum0xABCD

      0xABCD >> 16    == 0x0000
      
      0xABCD & 0xFFFF == 0xABCD
      
      0x0000 + 0xABCD == 0xABCD
      

      这似乎是一个多余的步骤。同样适用于下一个语句sum += (sum >> 16)

4 个答案:

答案 0 :(得分:2)

  

声明*(uint8_t *)(&amp; answer)= *(uint8_t *)w;   实际上呢?

这会将uint16_t转换为uint8_t,因此只有 8 最右边的位从w复制到answer。考虑:

uint16_t x = 0x1234;
uint16_t* w = &x; // *w = // 0001001000110100

*(uint16_t *) (&answer) = *(uint16_t *) w; // answer = 0001001000110100

*(uint8_t *) (&answer) = *(uint8_t *) w;   // answer = 0000000000110100

  

为什么我们把这笔钱当作:

sum = (sum >> 16) + (sum & 0xFFFF);
sum += (sum >> 16);
answer = ~sum;

sum 32 位。 65536 ≡ 1 mod 65535end-around carry expression (sum & 0xffff) + (sum >> 16)缩小sum65535。这是必要的,可以将任何(最终)结果带回到结果总和中。

答案 1 :(得分:1)

  1. *(uint8_t *) (&answer) = *(uint8_t *) w;在右侧,它会将w转换为uint8_t*并对其进行解除引用。它会截断指向最后一个字节的uint16_t*解除引用时读取的垃圾数据。在左侧,它采用answer的地址(指针)并将其转换为uint8_t*并取消引用它。因此它需要w指向的第一个字节,并将值分配给答案的第一个字节。实际上,这一行是2. Add a one byte padding of 0s to the end of the last block if it's not 2 bytes long, to make it 2 bytes.左侧的转换需要支持大端系统......我想。

答案 2 :(得分:1)

校验和功能似乎仅适用于大端处理器。

第一个while循环针对速度进行了优化。

&answer技巧将最后一个字节(如果有奇数个字节)加载到answer的高字节中,使低字节为零,类似于您的代码对{{ 1}}。它的工作方式是这个

data[i] & 0xff00

校验和应该通过重新添加的进位来计算。这里假设此代码在1) take the address of answer (&answer) 2) convert that to a byte pointer (uint8_t *) 2a) on a big endian processor the first byte of a 16-bit quantity is the high byte 3) overwrite the high byte with the last byte of the data 为32位的机器上运行。因此,int是16位校验和,(sum & 0xffff)是需要重新加入的进位(如果有的话)。因此,行

(sum >> 16)

调整总和以包含进位。但是,该行代码本身可以生成另一个进位。所以下一行sum = (sum >> 16) + (sum & 0xffff); 将携带(如果有的话)添加回校验和。

最后,采取答案的补码。请注意,未使用sum += (sum >> 16),因为整个函数隐式假定它在大端处理器上运行。

答案 3 :(得分:1)

  1. 此声明适用于案例(请参阅RFC793或RFC1701),其中数据包具有奇数个字节:[A,B] + [C,D] + ... + [Z,0]通过合并数量(answer)将2个最重要的字节设为Z,将2个最低有效字节设为0.请记住+此处始终为1&#39; s补充。

  2. sum是一个32位累加器。为了添加1的补码,我们在累加位之后添加进位。 sum的2个最重要字节包含进位位(如果有)。

  3. 如果您查看RFC1701,可以在顶部看到哪些RFC会更新它。没有一个能取代它。