2014-03-13 63 views
1

相信TCP校验和函数执行以下操作:了解TCP校验和函数

  1. 的伪首和TCP片段报头和数据分裂成2个字节的块。
  2. 如果长度不是2个字节,则在最后一个块的末尾添加一个0字节的填充字以使其为2个字节。
  3. 取总和的补码得到TCP校验和。

听起来很简单。因此,我写我自己的通用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校验和变更的方式吗?

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

0xABCD >> 16 == 0x0000 

0xABCD & 0xFFFF == 0xABCD 

0x0000 + 0xABCD == 0xABCD 

这似乎是一个多余的一步。下一条语句sum += (sum >> 16)也是如此。

+0

看起来像'sum =(sum >> 16)+(sum&0xFFFF)'是为了将所有数据包装成16位整数,因此在第二个例子中,sum是声明为'int sum = 0;' – deimus

+0

对于问题1,按照RPC 793填充8位零的最后一个字。 – Jiminion

+0

请注意,校验和是以补码形式完成的。这不同于简单地用'sum + = ...'来包装溢出的uint16_t。 – nos

回答

0

校验和功能似乎只适用于big-endian处理器。

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

&answer招加载最后一个字节(如果有奇数个字节)进入answer高字节,其余低字节为零,类似于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 

校验应该与携带重新添加计算。它在这里假设这个代码是一台机器上运行,其中一个int是32位。因此,(sum & 0xffff)是16位校验和,以及(sum >> 16)是进位位(如果有的话)需要在被添加回去。因此,线

sum = (sum >> 16) + (sum & 0xffff); 

调整为包括承载和。但是,该行代码本身可能会生成另一个进位位。所以下一行sum += (sum >> 16)增加了(如果有的话)返回校验和。

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

+0

rfc 1071指出校验和计算与字节顺序无关。 – user666412

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

这句话是什么*(uint8_t *)(&答案)= *(uint8_t *)瓦; 居然呢?

这就引起uint16_tuint8_t,所以只有最右位从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是位。 65536 ≡ 1 mod 65535,所以end-around carry expression(sum & 0xffff) + (sum >> 16)减少sum65535。这是必要的,以增加任何(最终)结果带回到结果总和。

1
  1. 此语句容纳其中分组具有奇数个字节的情况下(见RFC793或RFC1701):通过掺入的总和与2个最显著字节的数量(answer)作为Z和2 [A,B] + [C,D] + ... + [Z,0]最低有效字节为0.记住+这里总是1的补码加法。

  2. sum是一个32位累加器。为了补充1的补码,我们在积累比特之后加入进位。 sum的2个最高有效字节包含进位位,如果有的话。

  3. 如果你看看RFC1701你可以在顶部看到哪些RFC更新它。没有任何东西取代它。