Comment valider la sum de contrôle ICMPv6? (Pourquoi ai-je toujours une sum de contrôle de 0x3fff?)

Je travaille sur un programme en espace utilisateur Linux qui reçoit des paquets d’annonce de routeur IPv6. Dans le cadre de la RFC4861, je dois vérifier la sum de contrôle ICMPv6. Sur la base de mes recherches, la plupart d’entre elles font référence à la sum de contrôle IP en général, si vous calculez la sum de contrôle complémentaire du pseudo-en-tête IPv6 et que le contenu du paquet renvoie 0xffff. Mais je continue à recevoir une sum de contrôle de 0x3fff.

Y a-t-il un problème avec la mise en œuvre de ma sum de contrôle? le kernel Linux vérifie-t-il la sum de contrôle ICMPv6 avant de transmettre les paquets à l’espace utilisateur? Existe-t-il une bonne source de référence pour tester les bons paquets ICMPv6 connus?

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

La boucle while est excessive. Le corps n’arrivera qu’une fois.

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

Au lieu

 checksum += checksum >> 16; return (uint16_t)~checksum; 

C’est inutile. len est toujours 16 bits

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

C’est inutile. La constante est toujours 00 00 00 58, ajoutez donc simplement 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]; 

En règle générale, votre algorithme semble correct, à l’exception de la manière dont vous gérez la finalité des entiers et du dernier octet, l’octet impair. D’après la façon dont j’ai lu le protocole, les octets doivent être additionnés dans l’ordre big-endian, c’est-à-dire que les octets 0xAB 0xCD doivent être interprétés comme 0xABCD 16 bits. Votre code dépend de la commande de votre machine.

L’ordre dans lequel les entiers sont construits affectera le nombre de retenues que vous ajoutez correctement à la sum de contrôle. Mais si votre code correspond à votre machine cible, le dernier octet impair est incorrect. 0xAB donnerait 0xAB00, pas 0x00AB tel qu’écrit.

Si cela fonctionne sur une machine little-endian, alors je pense que vous avez besoin (beaucoup) davantage d’échange d’octets tout en accumulant la sum de contrôle.

Par exemple, sur une petite machine endian, l’ s6_addr16[0] d’une adresse IPv6 typique commençant par 2001: contiendra 0x0120 et non 0x2001 . Cela mettra vos mèches au mauvais endroit.

Le code de longueur semble correct puisque vous utilisez htonl() ici, mais la logique d’accumulation de messages 0x00 0x00 0x00 0x58 et les suivants ne le sont pas. Je pense que tous les bits restants devraient également se trouver dans l’octet de poids fort, et non dans l’octet de poids faible, comme cela se produit dans votre code.

De même, utilisez 0x0000 pour les octets de 0x0000 de contrôle de pseudo-en-tête lors de la génération du total de contrôle. Pour valider la sum de contrôle, utilisez les octets de sum de contrôle réels reçus dans le RA IPv6. Vous devez ensuite obtenir 0xffff comme valeur éventuelle.

J’ai trouvé mon bogue: j’avais un tampon d’entrée de 256 octets et iov_len supposais que l’élément msg_iov de msg_iov dans recvmsg() était modifié pour retourner la longueur des données reçues. Depuis la longueur de mes annonces de routeur où la valeur de 64 octets était constante, la différence entre ces longueurs entraînait une erreur constante dans la sum de contrôle. Je n’ai pas eu besoin de changer l’ordre des octets pour vérifier la sum de contrôle (bien que je n’ai pas eu de paquet ICMPv6 de longueur impaire pour vérifier ma gestion du dernier octet dans le cas des longueurs impaires.

De plus, le NOT final de la sum de contrôle est uniquement nécessaire pour calculer la sum de contrôle, et non pour la vérifier. Avec le code ci-dessus, la checksum() retournera 0 si la sum de contrôle est valide.