moritetuのIT関連技術メモ

ネットワーク周りについて調べていて、IPヘッダのチェックサムの計算が気になったのでメモする。
(自分なりの解釈なので誤りあるかもしれません、記載内容については個人の責任でお願いします)

以下は、Wiresharkでpingパケットをキャプチャしたものである。

ip_checksum.png

上図から読み取れるように、IPパケットヘッダのチェックサムは 0xe814である。
今回は、この値の求め方についてである。

rfc1071によると、

とある。
16ビットのワードごとの1の補数和は聞き慣れないが、rfc1071によると、2の補数を扱うマシンでは、16ビットごとのワードを加算し、桁上がりで溢れた最上位ビットは最下位ビットに加算することで計算されるようだ。IP チェックサムの秘密のページを見ると分かりやすいかもしれない。

具体的には以下のように計算する。

   0xFF   0xFA
+  0x01   0x02
--------------
  0x100   0xFC
    ^ 桁上がりの1は、最下ビットに加算する
   0x00   0xFC
          0x01
---------------
   0x00   0xFD 

また、今は16ビットで計算したが、32ビットで計算し上位ビットと下位ビットを結合しても成り立つ。
RFC 1071 - 3. Numerical Examplesに計算例があるが、理解のため自分でも別の値を例にして計算してみる。

----------------------------------
  16bit
----------------------------------
Byte 0/1   fc   01
Byte 2/3   3e   2e
Byte 4/5   40   11
Byte 6/7   21   09
           --   --
Sum1      19b   49
          ^ 桁上がり
Carries    9b   49
                 1  <--最下位ビットに加算
           --   --
Sum2       9b   4a
~Sum2      64   b5

----------------------------------
  32bit
----------------------------------


Byte 0/3   fc01 3e2e
Byte 4/7   4011 2109
           ---------
Sum1      13c12 5f37
          ^ 桁上がり
Carries    3c12 5f37
                   1  <--最下位ビットに加算
           ---------
Sum2       3c12 5f38

   from 32-bit to 26-bit
                3c12
                5f38
                ----
Sum3            9b4a
~Sum3           64b5

以上を踏まえた上で、上記のパケットのIPチェックサムのヘッダを計算して見る。
チェックサムを0で埋めたデータは以下の通りである。

45 00 00 54 be d9 00 00 40 01 00 00 c0 a8 03 03 08 08 08 08

上記のデータを16bitのワードごとに区切って1の補数和を求めていく。

Byte0/1    45 00
Byte2/3    00 54
           -----
Sum        45 54
Byte4/5    be d9
           -----
Sum       104 2d
Carries    04 2d
               1
           -----
           04 2e
Byte6/7    00 00
           -----
Sum        04 2e
Byte8/9    40 01
           -----
Sum        44 2f
Byte10/11  00 00  <-- checksum 0 で計算
           -----
Sum        44 2f
Byte12/13  c0 a8
           -----
Sum       104 d7
Carries    04 d7
               1
           -----
Sum        04 d8
Byte14/15  03 03
           -----
Sum        07 db
Byte16/17  08 08
           -----
Sum        0f e3
Byte18/19  08 08
           -----
Sum        17 eb
Sum~       e8 14
           -----
           ff ff  <-- the result is all 1 bits

最終結果は、0xffffとなった。これは、チェックサムが正しいことを示しているはず。
RFC 1071 - 4. Implementation Examples - 4.1 "C"のCの実装例を参考に計算してみる。

// test_checksum.c
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>

u_int16_t do_checksum(u_char *data, size_t len)
{
    u_int16_t            *ptr;
    register u_int32_t    sum;
    u_int8_t             *b;
    u_int16_t             checksum;

    int count = len;
    ptr = (u_int16_t*)data;

    // (2)
    printf("=> (2) 1's complement sum\n");
    while (count > 1)  {
        printf("sum = %08x + word = %04x -> ", sum, *ptr);
        sum += *ptr++;
        printf("sum = %08x\n", sum);
        count -= 2;
    }

    // (3)
    printf("(3) count = %d, sum = %08x\n", count, sum);

    if (count > 0) {
        b = (u_int8_t*)ptr;
        sum += *b;
    }

    // (4)
    printf("=> (4) from 32-bit to 16-bit\n");
    while (sum>>16) {
        sum = (sum & 0xffff) + (sum >> 16);
        printf("sum = %08x\n", sum);
    }
    printf("sum = %08x\n", sum);

    // (5)
    printf("=> (5) 1's complement\n");
    checksum = ~sum;

    b = (u_int8_t*)&checksum;
    printf("checksum = %04x\n", checksum);
    printf("swapped  = %02x%02x\n", b[0], b[1]);

    return checksum;
}


int main(int argc, char* argv[])
{
    size_t len;
    int i;

    u_char data[] = {
        0x45, 0x00,
        0x00, 0x54,
        0xbe, 0xd9,
        0x00, 0x00,
        0x40, 0x01,
        0x00, 0x00,
        0xc0, 0xa8,
        0x03, 0x03,
        0x08, 0x08,
        0x08, 0x08
   };

   len = sizeof(data) / sizeof(char);

   // (1)
   printf("=> (1) data\n");
   printf("len = %d bytes\n", len);
   for (i = 0; i < len; i++) {
       printf("%02x ", data[i]);
   }
   printf("\n");

   do_checksum(data, len);
}

実行結果は以下の通り。実行環境は、3.2 GHz Intel Core i5。

$ gcc -o test_checksum test_checksum.c; ./test_checksum 
=> (1) data
len = 20 bytes
45 00 00 54 be d9 00 00 40 01 00 00 c0 a8 03 03 08 08 08 08 
=> (2) 1's complement sum
sum = 00000000 + word = 0045 -> sum = 00000045
sum = 00000045 + word = 5400 -> sum = 00005445
sum = 00005445 + word = d9be -> sum = 00012e03
sum = 00012e03 + word = 0000 -> sum = 00012e03
sum = 00012e03 + word = 0140 -> sum = 00012f43
sum = 00012f43 + word = 0000 -> sum = 00012f43
sum = 00012f43 + word = a8c0 -> sum = 0001d803
sum = 0001d803 + word = 0303 -> sum = 0001db06
sum = 0001db06 + word = 0808 -> sum = 0001e30e
sum = 0001e30e + word = 0808 -> sum = 0001eb16
(3) count = 0, sum = 0001eb16
=> (4) from 32-bit to 16-bit
sum = 0000eb17
sum = 0000eb17
=> (5) 1's complement
checksum = 14e8
swapped  = e814

なお、Linuxのチェックサム計算の実装は以下のようになっているようだ。
補足 linux/lib/checksum.c - on https://github.com/torvalds/linux/blob/v5.7/lib/checksum.c

参考リンク


添付ファイル: fileip_checksum.png 22件 [詳細]

トップ   差分 バックアップ リロード   一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
目次
ダブルクリックで閉じるTOP | 閉じる
GO TO TOP