TCP校验和C代码返回错误结果

TCP checksum C code returns wrong result

在假定应该为0的情况下,我得到了一个较大的TCP校验和结果。我正在通过将TCP psuedoheader复制到数组的前12个字节中,然后将TCP标头和数据复制到接下来的几个方法中来解决此问题。数组的字节,然后将该数组及其长度传递给校验和函数。我无法弄清为什么校验和与正确值显着不同(例如,其中一个数据包数据包应为0时为9180)。

这是我的IP,TCP和伪TCP标头:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
typedef struct __attribute__((__packed__)) IPHeader {
  #if __BYTE_ORDER__ == __LITTLE_ENDIAN__
  uint8_t hdrLen:4;
  uint8_t version:4;
  #else
  uint8_t version:4;
  uint8_t hdrLen:4;
  #endif
  uint8_t TOS;
  uint16_t totLen;
  uint16_t id;
  uint16_t offset;
  #define DF 0x4            
  #define MF 0x2          
  #define OFF 0
  uint8_t TTL;
  uint8_t protocol;
  uint16_t checksum;
  struct in_addr srcIP;
  struct in_addr destIP;
}IPHeader;

typedef struct __attribute__((__packed__)) TCPHeader {
  uint16_t srcPort;
  uint16_t destPort;
  uint32_t seqNum;
  uint32_t ackNum;
  uint8_t offset:4;
  uint8_t res:4;
  uint8_t flags;
  uint16_t window;
  uint16_t checksum;
  uint16_t urg;
}TCPHeader;

typedef struct __attribute__((__packed__)) TCPPseudo {
  struct in_addr srcAddr;
  struct in_addr destAddr;
  uint8_t zeroes;
  uint8_t protocol;
  uint16_t len;
}TCPPseudo;

这是我将数据复制到数组中的方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
IPHeader *iph
TCPPseudo ts;
TCPHeader *tcp;
u_char checkData[1000];
uint16_t checkLen = 0;
---
tcp = (TCPHeader *)(packet + ETHER_SIZE + iph->hdrLen * 4);
ts.srcAddr = iph->srcIP;
ts.destAddr = iph->destIP;
ts.zeroes = 0;
ts.protocol = iph->protocol;
checkLen = ntohs(iph->totLen) - (iph->hdrLen * 4);
ts.len = checkLen;
memcpy(checkData, &ts, 12);
memcpy(checkData + 12, tcp, checkLen);
getTCPInfo(tcp, iph, checkData, checkLen);
---
unsigned short checksum = in_cksum((unsigned short *)checkData, len + 12);
printf("\\t\\tChecksum: %d, (0x%x)", checksum, htons(tcp->checksum));

这是我计算校验和的方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
unsigned short in_cksum(unsigned short *addr,int len)
{
    register int sum = 0;
    u_short answer = 0;
    register u_short *w = addr;
    register int nleft = len;

    /*
     * Our algorithm is simple, using a 32 bit accumulator (sum), we add
     * sequential 16 bit words to it, and at the end, fold back all the
     * carry bits from the top 16 bits into the lower 16 bits.
     */

    while (nleft > 1)  {
            sum += *w++;
            nleft -= 2;
    }

    /* mop up an odd byte, if necessary */
    if (nleft == 1) {
            *(u_char *)(&answer) = *(u_char *)w ;
            sum += answer;
    }

    /* add back carry outs from top 16 bits to low 16 bits */
    sum = (sum >> 16) + (sum & 0xffff);     /* add hi 16 to low 16 */
    sum += (sum >> 16);                     /* add carry */
    answer = ~sum;                          /* truncate to 16 bits */
    return(answer);
}

您应该保存接收到的数据包的TCP校验和,然后在计算伪报头和TCP段的校验和之前,在tcp报头中将校验和字段设置为零。然后将计算出的校验和与先前保存的值进行匹配。如果网络传输中没有错误,则两者应该相等。
这样做

1
2
recvPktChkSum = tcp->checksum
tcp->checksum = 0;

在计算校验和之前,还应确保长度以网络字节顺序排列

1
ts.len = htons(checkLen);

之前

1
memcpy(checkData + 12, tcp, checkLen);

然后在

之后

1
checksum = in_cksum((unsigned short *)checkData, len + 12);

比较

1
2
if(htons(recvPktChkSum) == htons(checksum))
   printf("checksum valid");