5

IPv6ルーターアドバタイズメントパケットを受信するLinuxユーザースペースプログラムに取り組んでいます。RFC4861の一部として、ICMPv6チェックサムを検証する必要があります。私の調査によると、IPv6疑似ヘッダーとパケットの内容のチェックサムを補完するものを計算すると、そのほとんどは一般にIPチェックサムを参照します。結果は0xffffになります。しかし、私は0x3fffのチェックサムを取得し続けます。

チェックサムの実装に何か問題がありますか?Linuxカーネルは、パケットをユーザースペースに渡す前にICMPv6チェックサムを検証しますか?テストするための既知の良好なICMPv6パケットの適切な参照ソースはありますか?

uint16_t
checksum(const struct in6_addr *src, const struct in6_addr *dst, const void *data, size_t len) {
    uint32_t checksum = 0;
    union {
        uint32_t dword;
        uint16_t word[2];
        uint8_t byte[4];
    } temp;

    // IPv6 Pseudo header source address, destination address, length, zeros, next header
    checksum += src->s6_addr16[0];
    checksum += src->s6_addr16[1];
    checksum += src->s6_addr16[2];
    checksum += src->s6_addr16[3];
    checksum += src->s6_addr16[4];
    checksum += src->s6_addr16[5];
    checksum += src->s6_addr16[6];
    checksum += src->s6_addr16[7];

    checksum += dst->s6_addr16[0];
    checksum += dst->s6_addr16[1];
    checksum += dst->s6_addr16[2];
    checksum += dst->s6_addr16[3];
    checksum += dst->s6_addr16[4];
    checksum += dst->s6_addr16[5];
    checksum += dst->s6_addr16[6];
    checksum += dst->s6_addr16[7];

    temp.dword = htonl(len);
    checksum += temp.word[0];
    checksum += temp.word[1];

    temp.byte[0] = 0;
    temp.byte[1] = 0;
    temp.byte[2] = 0;
    temp.byte[3] = 58; // ICMPv6
    checksum += temp.word[0];
    checksum += temp.word[1];

    while (len > 1) {
        checksum += *((const uint16_t *)data);
        data = (const uint16_t *)data + 1;
        len -= 2;
    }

    if (len > 0)
        checksum += *((const uint8_t *)data);

    printf("Checksum %x\n", checksum);

    while (checksum >> 16 != 0)
        checksum = (checksum & 0xffff) + (checksum >> 16);

    checksum = ~checksum;

    return (uint16_t)checksum;
}
4

3 に答える 3

1

これがリトルエンディアンのマシンで実行されている場合は、チェックサムを蓄積するときに(はるかに)多くのバイトスワッピングが必要になると思います。

たとえば、リトルエンディアンのマシンではs6_addr16[0]、開始する一般的なIPv6アドレスの要素には、が含まれますが、2001:は含まれ0x0120ません0x2001。これにより、キャリービットが間違った場所に配置されます。

長さコードはhtonl()そこで使用しているので問題ないように見えますが0x00 0x00 0x00 0x58、以降のメッセージ蓄積ロジックはそうではありません。残りのビットは、コードで発生するような下位バイトではなく、上位バイトで終わる必要があると思います。

また、0x0000疑似ヘッダーのチェックサムバイトに使用することは、チェックサムを生成するときに行う必要があります。チェックサムを検証するには、IPv6 RAで受信した実際のチェックサムバイトを使用し0xffff、最終的な値として取得する必要があります。

于 2011-09-17T21:44:29.927 に答える
1

whileループはやり過ぎです。体は一度だけ起こります。

while (checksum >> 16 != 0)
    checksum = (checksum & 0xffff) + (checksum >> 16);

checksum = ~checksum;

return (uint16_t)checksum;

その代わり

checksum += checksum >> 16;

return (uint16_t)~checksum;

これは不要です。lenは常に16ビットです

temp.dword = htonl(len);
checksum += temp.word[0];
checksum += temp.word[1];

これは不要です。定数は常に000000 58なので、58を追加するだけです。

temp.byte[0] = 0;
temp.byte[1] = 0;
temp.byte[2] = 0;
temp.byte[3] = 58; // ICMPv6
checksum += temp.word[0];
checksum += temp.word[1];

整数のエンディアンと最後のバイトの奇数バイトを処理する方法を除いて、アルゴリズムは一般的に正しく見えます。プロトコルの読み方から、バイトはビッグエンディアンの順序で合計されます。つまり、バイト0xAB0xCDは16ビット0xABCDとして解釈されます。コードは、マシンの順序によって異なります。

整数が作成される順序は、チェックサムに正しく追加するキャリーの数に影響します。ただし、コードがターゲットマシンと一致する場合は、最後の奇数バイトが間違っています。0xABは、記述された0x00ABではなく、0xAB00になります。

于 2011-09-18T02:32:38.050 に答える
1

バグを見つけました。256バイトの入力バッファがあり、受信したデータの長さを返すようにonのiov_len要素が変更されていると想定しました。私のルーターアドバタイズメントの長さは64バイトで一定であるため、この長さの違いにより、チェックサムで一定のエラーが発生しました。チェックサムを検証するためにバイト順序を変更する必要はありませんでした(ただし、奇数の場合の最後のバイトの処理を検証するための奇数の長さのICMPv6パケットはありませんでした。msg_iovrecvmsg()

また、チェックサムの最後のNOTは、チェックサムを計算するためにのみ必要であり、検証するためには必要ありません。上記のコードchecksum()では、チェックサムが有効な場合は0を返します。

于 2011-09-18T07:19:29.927 に答える