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

read 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 の長さは、TCP ヘッダーの長さにオクテット単位のデータ長を加えたもの (これは明示的に送信された量ではありませんが、計算されます) であり、疑似ヘッダーの 12 オクテットはカウントされません。

TCP ヘッダー長のみを設定しますが、TCP ヘッダー長 + データ長にする必要があります。

データ長は、Total LengthIP ヘッダーから IP ヘッダーの長さ (フィールドはIHLIP ヘッダーで指定され、長さをバイト単位で取得するには 4 を掛ける必要があります) を引いて TCP ヘッダーのサイズを引いたものです。

ここに画像の説明を入力

ただし、TCP ヘッダーの長さをデータ長に追加する必要があるため、パケットの合計長から IP ヘッダーの長さを差し引くだけで、TCP ヘッダーとデータの長さの合計が残ります。

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

また、RFCによると:

チェックサム フィールドは、ヘッダーとテキスト内のすべての 16 ビット ワードの 1 の補数の合計の 16 ビットの 1 の補数です。

「And text」は、TCP ヘッダーに続くすべてのデータもチェックサムされることを意味します。そうしないと、TCP はデータが正しく送信されたことを保証できません。TCP は破損したデータを再送信する信頼性の高いプロトコルですが、データがいつ破損したかを認識しなければならないことに注意してください。

したがって、パケット全体から IP ヘッダーを除いたものを に追加する必要がありますtcpcsumblock。したがってtcpcsumblock、パケット全体が収まるのに十分な大きさでなければなりません (TCP の場合、通常は 1500 バイトで十分ですが、理論的には IP パケットは 64 KB にもなり、必要に応じてフラグメント化されます)。次に、疑似ヘッダーを追加する必要があります。 、tcpヘッダー、およびパケットの最後までのすべて。


私はあなたのために実用的なコードを書きました。実際のデータを入力して、これが正しく機能することを確認しました。おまけとして、この実装は、チェックサムの検証を含む、IP ヘッダーに対していくつかのサニティ チェックを実行します (一致しない場合、ヘッダーが破損している可能性が高いため、すべてのヘッダー フィールドに偽物が含まれる可能性があるため)。動的メモリを割り当てたり、データをコピーしたりする必要がないため、かなり高速です。特に最後の機能については、in_cksum3 番目のパラメーターを受け入れる関数。このパラメーターが 0 の場合、コード内のバージョンとまったく同じように動作しますが、正しい値を入力することで、この関数を使用して、チェックサムを実行するデータがそのデータの直後にあったかのように、既に計算されたチェックサムを更新できます。すでにチェックサムを行っていました(かなり気の利いたですね?)

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 に答える