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));
4

1 回答 1

11

问题是这一行:

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

根据RFC 793

TCP Length 是 TCP 标头长度加上八位字节的数据长度(这不是显式传输的数量,而是计算出来的),它不计算伪标头的 12 个八位字节。

您只设置 TCP 标头长度,但它应该是 TCP 标头长度 + 数据长度。

数据长度是Total LengthIP头减去IP头长度(字段IHL在IP头中命名,必须乘以4才能得到字节长度)减去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);
}
于 2013-01-19T01:25:51.113 回答