Как проверить контрольную сумму ICMPv6? (Почему я продолжаю получать контрольную сумму 0x3fff?)

Я работаю над программой пользовательского пространства Linux, которая получает пакеты рекламы маршрутизатора IPv6. В рамках RFC4861 мне нужно проверить контрольную сумму ICMPv6. Основываясь на моем исследовании, большинство из которых относится к контрольной сумме IP в целом, если вы вычислите контрольную сумму дополнений псевдозаголовка IPv6 и содержимого пакета, результат должен быть 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;
}

person dlundquist    schedule 17.09.2011    source источник
comment
это работает на машине с большим или маленьким порядком байтов?   -  person Alnitak    schedule 18.09.2011
comment
Little endian (x86_64), но из того, что я прочитал, проверка контрольной суммы должна быть независимой от endian.   -  person dlundquist    schedule 18.09.2011
comment
+1, просто за На основе моих исследований   -  person Flexo    schedule 18.09.2011


Ответы (3)


Цикл 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];

Это не нужно. Константа всегда 00 00 00 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];

В противном случае ваш алгоритм выглядит правильно, за исключением того, как вы обрабатываете порядок байтов целых чисел и последний байт с нечетным номером. Судя по тому, как я прочитал протокол, байты должны суммироваться в порядке с обратным порядком байтов, то есть байты 0xAB 0xCD должны интерпретироваться как 16-битный 0xABCD. Ваш код зависит от заказа вашей машины.

Порядок построения целых чисел повлияет на количество переносов, которые вы правильно добавляете в контрольную сумму. Но если ваш код соответствует вашей целевой машине, то последний байт с нечетным номером неверен. 0xAB приведет к 0xAB00, а не 0x00AB, как написано.

person uncleO    schedule 18.09.2011
comment
Согласен со всеми вашими предложениями по коду, это была моя четвертая попытка, и я пытался максимально точно следовать примеру RFC 1071 4.1. Я думал, что последний байт был неправильным, но в моем случае длина пакета ровно 64 байта. - person dlundquist; 18.09.2011

Если это работает на машине с прямым порядком байтов, то я думаю, что вам нужно (намного) больше подкачки байтов при накоплении контрольной суммы.

Например, на машине с прямым порядком байтов элемент s6_addr16[0] типичного адреса IPv6, начинающийся с 2001:, будет содержать 0x0120, а не 0x2001. Это поставит ваши переносные биты в неправильное место.

Код длины выглядит нормально, так как вы используете там htonl(), но 0x00 0x00 0x00 0x58 и последующая логика накопления сообщений - нет. Я думаю, что любые оставшиеся биты также должны оказаться в старшем байте, а не в младшем байте, как это происходит в вашем коде.

Кроме того, использование 0x0000 для байтов контрольной суммы псевдозаголовка — это то, что вы должны делать при генерировании контрольной суммы. Чтобы проверить контрольную сумму, используйте фактические байты контрольной суммы, полученные в RA IPv6, и тогда вы должны получить 0xffff в качестве конечного значения.

person Alnitak    schedule 17.09.2011
comment
Насколько я понимаю, контрольная сумма составляется из данных в псевдозаголовке IPv6, в котором адреса находятся в фиксированной конечности сети и не зависят от хоста. Только поле длины уже преобразовано в конечность хоста. Также я получаю рекламу маршрутизаторов от трех маршрутизаторов, и этот алгоритм контрольной суммы вычисляет одну и ту же контрольную сумму для всех полученных рекламных объявлений. - person dlundquist; 18.09.2011

Я нашел свою ошибку: у меня был 256-байтовый входной буфер, и я предположил, что элемент iov_len msg_iov на recvmsg() был изменен, чтобы возвращать длину полученных данных. Поскольку длина моих рекламных объявлений маршрутизатора была постоянной, равной 64 байтам, разница между этими длинами приводила к постоянной ошибке в контрольной сумме. Мне не нужно было менять порядок байтов для проверки контрольной суммы (хотя у меня не было ICMPv6-пакета нечетной длины для проверки моей обработки последнего байта в случае нечетной длины.

Кроме того, конечное НЕ контрольной суммы необходимо только для вычисления контрольной суммы, а не для ее проверки. С приведенным выше кодом checksum() вернет 0, если контрольная сумма верна.

person dlundquist    schedule 18.09.2011