Skip to content

icmpv6: the computed checksum omits the IPv6 pseudo-header RFC 4443 requires #1169

Description

@adamgeorge309

Summary

Icmpv6::insertChecksum() computes the ICMPv6 checksum over the ICMPv6 message alone. RFC 4443
requires it to be computed over an IPv6 pseudo-header followed by the message, so with
checksumMode = "computed" every ICMPv6 packet INET emits carries a wrong checksum.

src/inet/networklayer/icmpv6/Icmpv6.cc:433-441:

        case CHECKSUM_COMPUTED: {
            // if the checksum mode is computed, then compute the checksum and set it
            icmpHeader->setChksum(0x0000); // make sure that the checksum is 0 in the header before computing the checksum
            MemoryOutputStream icmpStream;
            Chunk::serialize(icmpStream, icmpHeader);
            if (packet->getByteLength() > 0)
                Chunk::serialize(icmpStream, packet->peekDataAsBytes());
            uint16_t checksum = internetChecksum(icmpStream.getData());
            icmpHeader->setChksum(checksum);
            break;
        }

No pseudo-header is built, and the function is not given the source and destination addresses
that would be needed to build one.

verifyChecksum() (src/inet/networklayer/icmpv6/Icmpv6.cc:459-464) has the same omission, so
the two agree with each other and nothing inside a simulation notices. The error is observable
only once the bytes leave INET: a PcapRecorder capture, a serializer round trip, or emulation.

Multicast Listener Discovery (MLD) is affected identically. Mldv1 and Mldv2 do not compute
their own checksums; they call this same function (Mldv1.cc:459,475,702, Mldv2.cc:547,600, 1163,1180,1299,1325,1361,1382). So do Ipv6NeighbourDiscovery (six call sites) and PingApp
(PingApp.cc:394).

What the standard says

RFC 4443, Section 2.3 (Message Checksum Calculation):

The checksum is the 16-bit one's complement of the one's complement sum of the entire ICMPv6
message, starting with the ICMPv6 message type field, and prepended with a "pseudo-header" of
IPv6 header fields, as specified in [IPv6, Section 8.1]. The Next Header value used in the
pseudo-header is 58. (The inclusion of a pseudo-header in the ICMPv6 checksum is a change from
IPv4; see [IPv6] for the rationale for this change.)

For computing the checksum, the checksum field is first set to zero.

The pseudo-header of RFC 8200 (formerly RFC 2460), Section 8.1, is the source address, the
destination address, the upper-layer packet length and the next header value.

INET already implements this correctly for the three other protocols with the same requirement:
Udp.cc:852, Tcp.cc:504 and TcpChecksumInsertionHook.cc:75 all build a
TransportPseudoHeader, and Pim.cc:110 builds one for Protocol Independent Multicast (PIM)
over IPv6, quoting the same RFC 8200 Section 8.1 rule in a comment. ICMPv6 is the one that
omits it.

Why it matters

Reproduction on origin/master (7aef79d), using the existing MLD example and nothing but
command-line overrides:

cd examples/ipv6/mld
inet -u Cmdenv -f omnetpp.ini -c MldDemo --sim-time-limit=30s \
     --**.checksumMode='"computed"' --**.fcsMode='"computed"' \
     --*.router.numPcapRecorders=1 --*.router.pcapRecorder[0].pcapFile='"router.pcap"'
tshark -r router.pcap -Y "icmpv6.checksum.status==0" | wc -l

All 15 ICMPv6 frames in the capture are flagged: Multicast Listener Query, Multicast Listener
Report, Multicast Listener Done, Router Solicitation (RS) and Router Advertisement (RA).
Recomputing both candidate values over the captured bytes settles which one INET produced:

frames matching
checksum over the ICMPv6 message alone 15 of 15
checksum including the IPv6 pseudo-header (RFC 4443) 0 of 15

A shorter demonstration is visible in the capture without any arithmetic: Multicast Listener
Reports sent from four different source addresses -- fe80::8aa:ff:fe00:2,
fe80::8aa:ff:fe00:3, aaaa:0:65:0:8aa:ff:fe00:2 and aaaa:0:65:0:8aa:ff:fe00:3 -- all carry
checksum 0x7def. Under RFC 4443 the source address is part of the sum, so four different
sources cannot produce one checksum.

The consequences:

  • Emulation is where this bites hardest. ICMPv6 or Neighbour Discovery (ND) traffic sent from
    INET onto a real link is discarded by the receiving host, because a real stack validates the
    checksum.
  • Captures exported for teaching or documentation show every ICMPv6 packet as bad, which is how
    this was found.
  • The "declared" default (Icmpv6.ned:29) is unaffected, so most simulations do not see it.

The defect is present in origin/master (7aef79d) verbatim, and the trigger is reachable
there: src/inet/networklayer/icmpv6/Icmpv6.cc is byte-identical to the branch the report was
written on.

Not a regression

The omission appears to be original to the ICMPv6 checksum code. git log --all -G 'PseudoHeader' -- src/inet/networklayer/icmpv6/ finds no commit on any branch that ever added or removed a
pseudo-header there.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions