【问题标题】:how to verify tcp checksum [closed]如何验证 tcp 校验和 [关闭]
【发布时间】:2013-01-02 19:47:41
【问题描述】:

出于某种奇怪的原因,我无法正确验证 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 的实际外观吗?
  • 糟糕,忘记删除那行,我更新了代码以包含标题结构
  • 附加说明:删除那些 typedef。您没有将结构定义为任何内容。我想知道为什么您的编译器对此没有问题,我的编译器立即大喊那些 typedef 语句毫无意义而且是正确的。您通常 typedef 一个结构,因此您可以在没有 struct 关键字的情况下使用它。但是由于您在代码中的任何地方都使用struct,因此删除 typedef 不会以任何方式破坏您的代码。
  • 啊,是的,抱歉忘记删除这些。由于一些机密信息,我不得不更改一些名称并忘记删除 typedefs

标签: c linux tcp network-programming pcap


【解决方案1】:

问题出在这一行:

tcpPseudo.length = htons(sizeof(struct TCP_Header));

根据RFC 793

TCP 长度是 TCP 标头长度加上数据长度 八位字节(这不是一个明确传输的数量,而是 计算),并且它不计算伪的 12 个八位字节 标题。

你只设置了TCP头长度,但应该是TCP头长度+数据长度。

数据长度为IP头上报的Total Length减去IP头长度(IP头中字段名为IHL,必须乘以4才能得到长度,单位为字节)减去IP头的大小TCP 标头。

然而,由于您想将 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);
}

【讨论】:

  • 你会不会碰巧知道如何计算数据的位置?更重要的是,我将如何计算数据长度?是 TCP Header 之后的一切吗?
  • @theStig:我更新了问题。是的,数据长度是TCP头之后所有数据的字节数。
  • 我已经尝试了您的建议,但正确 TCP 的校验和仍然非零。我是否还需要将 tcpcsumblock 的大小更改为 sizeof(pseudoheader) + sizeof(tcpheader) + tcpDataLength?
  • @theStig TCP 数据也必须进行校验和,而不仅仅是标头。我又更新了回复。
  • @useSticks 你说得对,少了一行代码,谢谢指出。我修复了它,请查看更新的答案。
猜你喜欢
  • 2020-08-11
  • 1970-01-01
  • 2014-03-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-05-16
  • 1970-01-01
相关资源
最近更新 更多