如何验证TCP校验和

4
由于某种奇怪的原因,我无法正确验证TCP校验和。我有检查IP和UDP校验和的代码,并且它完全正常运行,但是对于TCP,我的逻辑有些问题。这些头文件的结构定义都很好,因为我可以完美地读取数据(从wireshark中验证)。我唯一遇到的问题是,在TCP校验和方面,我无法验证校验和是否正确。你有什么想法,我在哪里做错了吗?
非常感谢。 校验函数
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;
    }

    if (nleft == 1) {
            *(u_char *)(&answer) = *(u_char *)w ;
            sum += answer;
    }

    sum = (sum >> 16) + (sum & 0xffff);
    sum += (sum >> 16);                
    answer = ~sum;                     
    return(answer);
}

读取TCP函数(旧版,请查看编辑后的版本)

/* packets are read using the pcap libraries */
void readTCP(const u_char *packets) {
   struct TCP_Header *tcp = (struct TCP_Header*) (packets + sizeof(struct Ethernet_Header) + sizeof(struct IP_Header));
   struct IP_Header *ip = (struct IP_Header*) (packets + sizeof(struct Ethernet_Header));
   struct TCP_Pseudo tcpPseudo;
   char tcpcsumblock[sizeof(struct TCP_Pseudo) + sizeof(struct TCP_Header)];

   /* tcp pseudo header */
   memset(&tcpPseudo, 0, sizeof(struct TCP_Pseudo));
   tcpPseudo.source_ip = ip->source_ip.s_addr;
   tcpPseudo.destination_ip = ip->destination_ip.s_addr;
   tcpPseudo.zero = 0;
   tcpPseudo.protocol = 6;
   tcpPseudo.length = htons(sizeof(struct TCP_Header));

   /* grab tcp checksum and reset it */
   int tcpCheckSum = htons(tcp->tcp_checksum);
   tcp->tcp_checksum = 0;

   /* place the data from the tcp pseudo infront of the tcp header */
   memcpy(tcpcsumblock, &tcpPseudo, sizeof(TCPPseudoHeader));   
   memcpy(tcpcsumblock+sizeof(TCPPseudoHeader),tcp, sizeof(TCPHeader));

   /* here is the issue, the checksum that i'm calculating isn't the correct checksum (i checked this by examing the packets from wireshark */
   u_short checksum = in_cksum((unsigned short *)tcpcsumblock, sizeof(tcpcsumblock));
}

==编辑==

新的TCP函数

/* packets are read using the pcap libraries */
void readTCP(const u_char *packets) {
   struct TCP_Header *tcp = (struct TCP_Header*) (packets + sizeof(struct Ethernet_Header) + sizeof(struct IP_Header));
   struct IP_Header *ip = (struct IP_Header*) (packets + sizeof(struct Ethernet_Header));
   struct TCP_Pseudo tcpPseudo;

   /* tcp pseudo header */
   memset(&tcpPseudo, 0, sizeof(struct TCP_Pseudo));
   tcpPseudo.source_ip = ip->source_ip;
   tcpPseudo.destination_ip = ip->destination_ip;
   tcpPseudo.zero = 0;
   tcpPseudo.protocol = 6;
   tcpPseudo.len = htons(ip->ip_len - (ip->ip_hdr_len * 4));

   int len = sizeof(struct TCP_Pseudo) + tcpPseudo.len;
   u_char tcpcsumblock[len];

   memcpy(tcpcsumblock, &tcpPseudo, sizeof(struct TCP_Pseudo));
   memcpy(tcpcsumblock + sizeof(struct TCP_Pseudo), (packets + sizeof(struct Ethernet_Header) + sizeof(struct IP_Header)), tcpPseudo.len);

   /* here is the issue, the checksum that i'm calculating isn't the correct checksum (i checked this by examing the packets from wireshark */
   u_short checksum = in_cksum((unsigned short *)ps_tcp, len);
   char *cs = checksum ? "Invalid Checksum!" : "Valid!";
}

IP标头

typedef struct IP_Header {
#if __BYTE_ORDER__ == __LITTLE_ENDIAN__
   uint8_t ip_hdr_len:4;   /* header length */
   uint8_t ip_version:4;   /* ip version */
#else
   uint8_t ip_version:4;   /* ip version */
   uint8_t ip_hdr_len:4;   /* The IP header length */
#endif

   uint8_t ip_tos;      /* type of service */
   uint16_t ip_len;     /* total length */
   uint16_t ip_id;      /* identification */
   uint16_t ip_off;     /* fragment offset field */
#define IP_DF 0x4000            /* dont fragment flag */
#define IP_MF 0x2000            /* more fragments flag */
#define IP_OFFMASK 0x1fff       /* mask for fragmenting bits */
   uint8_t  ip_ttl;     /* time to live */
   uint8_t  ip_p;       /* protocol */
   uint16_t ip_sum;     /* checksum */
   struct in_addr ip_src, ip_dst;   /* source and dest address */
} __attribute__ ((packed));

TCP头部

typedef struct TCP_Header {
  uint16_t tcp_source_port; /* source port */
  uint16_t tcp_dest_port; /* destination port */
  uint32_t tcp_seq; /* sequence */   
  uint32_t tcp_ack; /* acknowledgement number */
  uint8_t tcp_offest; /* data offset */
#define TH_OFF(th)      (((th)->th_offx2 & 0xf0) >> 4)
  uint8_t tcp_flags; /* flags */
#define TH_FIN      0x01
#define TH_SYN      0x02
#define TH_RST      0x04
#define TH_PUSH     0x08
#define TH_ACK      0x10
#define TH_URG      0x20
#define TH_ECE      0x40
#define TH_CWR      0x80

#define TH_NS       0x100
#define TH_RS       0xE00

   uint16_t tcp_window; /* window */
   uint16_t tcp_sum; /* checksum */
   uint16_t tcp_urp; /* urgent pointer */
} __attribute__ ((packed));

TCP伪头

typedef struct TCP_Pseudo {
   struct in_addr src_ip; /* source ip */
   struct in_addr dest_ip; /* destination ip */
   uint8_t zeroes; /* = 0 */
   uint8_t protocol; /* = 6 */
   uint16_t len; /* length of TCPHeader */
} __attribute__ ((packed));

您现在在编辑的代码中两次定义了 tcpcsumblock。一次是 char tcpcsumblock[sizeof(struct TCP_Pseudo) + sizeof(struct TCP_Header)],另一次是 u_char tcpcsumblock[len]。我怀疑这甚至无法编译通过。您能否展示一下 struct IP_Headerstruct TCP_HeaderTCP_Pseudo 的实际样子? - Mecki
糟糕,我的错,忘记删除那一行了。我已经更新了代码以包含头结构。 - theStig
另外需要注意的是:删除那些typedef。你没有将结构体定义为任何东西。我不知道为什么你的编译器没有问题,我的编译器立即指出这些typedef语句是无意义的,它是正确的。通常你会使用typedef来定义一个结构体,这样你就可以在代码中不使用“struct”关键字。但由于你在代码中到处都使用了“struct”,因此删除typedef不会以任何方式破坏你的代码。 - Mecki
啊,是的,抱歉忘了删掉那些。由于涉及一些机密信息,我不得不更改一些名称,然后忘记删除了typedef。 - theStig
1个回答

11
问题在于这一行:
tcpPseudo.length = htons(sizeof(struct TCP_Header));

根据RFC 793:
TCP长度是TCP头长度加上以八位字节计算的数据长度(这不是一个显式传输量,但是被计算出来),它不包括伪首部的12个八位字节。
你只设置了TCP头的长度,但应该是TCP头长度+数据长度。
数据长度是IP头报告的“总长度”减去IP头长度(IP头中的字段名为“IHL”,必须乘以4才能得到字节长度)减去TCP头的大小。

enter image description here

然而,由于您想要将TCP头的长度添加到数据长度中,所以您只需从总数据包长度中减去IP头的长度,剩下的就是TCP头和数据长度的总和。

tcpPseudo.length = htons(ntohs(ip->total_length) - (ip->ihl * 4));

根据RFC的规定: 校验和字段是头部和文本中所有16位字的一位补码和的16位补码。 "文本"表示TCP头部后的所有数据也会进行校验和。否则,TCP无法保证数据已正确传输。请记住,TCP是可靠的协议,将重新传输损坏的数据,但同时它也必须识别何时数据已损坏。 因此,整个数据包减去IP头必须添加到tcpcsumblock中。因此,tcpcsumblock必须足够大,以适应整个数据包(在TCP的情况下,通常1500字节已足够,尽管理论上IP数据包可能达到64 KB并且如果需要则会被分段),然后您必须将伪标头、tcp标头和所有内容添加到数据包的末尾。
我为您编写了可工作的代码。我通过输入一些真实数据来验证其正确性。此外,该实现对IP头执行了一些合理性检查,包括验证其校验和(如果校验和不匹配,则所有标头字段可能包含伪造内容,因为标头最有可能已损坏),并且它也不需要分配任何动态内存或复制任何数据,所以它应该非常快速。特别是对于最后一个功能,我不得不更改in_cksum函数以接受第三个参数。如果此参数为零,则它将像您代码中的版本一样运行,但通过提供正确的值,您可以使用此函数更新已经计算出的校验和,就好像您要校验的数据直接跟随您之前已经进行校验的数据一样(很聪明,对吧?)
uint16_t in_cksum (const void * addr, unsigned len, uint16_t init) {
  uint32_t sum;
  const uint16_t * word;

  sum = init;
  word = addr;

  /*
   * 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 (len >= 2) {
    sum += *(word++);
    len -= 2;
  }

  if (len > 0) {
    uint16_t tmp;

    *(uint8_t *)(&tmp) = *(uint8_t *)word;
    sum += tmp;
  }

  sum = (sum >> 16) + (sum & 0xffff);
  sum += (sum >> 16);
  return ((uint16_t)~sum);
}


void readTCP (const u_char *packets) {
  uint16_t csum;
  unsigned ipHdrLen;
  unsigned ipPacketLen;
  unsigned ipPayloadLen;
  struct TCP_Pseudo pseudo;
  const struct IP_Header * ip;
  const struct TCP_Header * tcp;

  // Verify IP header and calculate IP payload length
  ip = (const struct IP_Header *)(packets + sizeof(struct Ethernet_Header));
  ipHdrLen = ip->ip_hdr_len * 4;
  if (ipHdrLen < sizeof(struct IP_Header)) {
    // Packet is broken!
    // IP packets must not be smaller than the mandatory IP header.
    return;
  }
  if (in_cksum(ip, ipHdrLen, 0) != 0) {
    // Packet is broken!
    // Checksum of IP header does not verify, thus header is corrupt.
    return;
  }
  ipPacketLen = ntohs(ip->ip_len);
  if (ipPacketLen < ipHdrLen) {
    // Packet is broken!
    // The overall packet cannot be smaller than the header.
    return;
  }
  ipPayloadLen = ipPacketLen - ipHdrLen;

  // Verify that there really is a TCP header following the IP header
  if (ip->ip_p != 6) {
      // No TCP Packet!
      return;
  }
  if (ipPayloadLen < sizeof(struct TCP_Header)) {
    // Packet is broken!
    // A TCP header doesn't even fit into the data that follows the IP header.
    return;
  }

  // TCP header starts directly after IP header
  tcp = (const struct TCP_Header *)((const u_char *)ip + ipHdrLen);

  // Build the pseudo header and checksum it
  pseudo.src_ip = ip->ip_src;
  pseudo.dest_ip = ip->ip_dst;
  pseudo.zeroes = 0;
  pseudo.protocol = 6;
  pseudo.len = htons(ipPayloadLen);
  csum = in_cksum(&pseudo, (unsigned)sizeof(pseudo), 0);

  // Update the checksum by checksumming the TCP header
  // and data as if those had directly followed the pseudo header
  csum = in_cksum(tcp, ipPayloadLen, (uint16_t)~csum);

  char * cs = csum ? "Invalid Checksum!" : "Valid!";
  printf("%s\n", cs);
}

@theStig:我更新了问题。是的,数据长度是TCP头后所有数据字节数之和。 - Mecki
这很奇怪。我仍然无法得到正确的校验和。我已经更新了我的代码。我感觉我离成功很近了,一定有什么明显的地方我忽略了! - theStig
@theStig 我想我找到了问题所在:读取 ip->total_length 时必须使用 ntohs(),因为它是网络字节序。我添加了一个完整的源代码重写(未经测试,因为我没有结构声明就无法编译它)。 - Mecki
这看起来有一个错误。in_cksum没有使用创建的tmp变量。这可能会在具有奇数字节的流量上出错。 - useSticks
1
@useSticks 你说得对,确实缺少了一行代码,感谢指出。我已经修复了,请查看更新后的答案。 - Mecki
显示剩余8条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接