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.
Summary
Icmpv6::insertChecksum()computes the ICMPv6 checksum over the ICMPv6 message alone. RFC 4443requires 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: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, sothe two agree with each other and nothing inside a simulation notices. The error is observable
only once the bytes leave INET: a
PcapRecordercapture, a serializer round trip, or emulation.Multicast Listener Discovery (MLD) is affected identically.
Mldv1andMldv2do not computetheir own checksums; they call this same function (
Mldv1.cc:459,475,702,Mldv2.cc:547,600, 1163,1180,1299,1325,1361,1382). So doIpv6NeighbourDiscovery(six call sites) andPingApp(
PingApp.cc:394).What the standard says
RFC 4443, Section 2.3 (Message Checksum Calculation):
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:504andTcpChecksumInsertionHook.cc:75all build aTransportPseudoHeader, andPim.cc:110builds 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 butcommand-line overrides:
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:
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:2andaaaa:0:65:0:8aa:ff:fe00:3-- all carrychecksum
0x7def. Under RFC 4443 the source address is part of the sum, so four differentsources cannot produce one checksum.
The consequences:
INET onto a real link is discarded by the receiving host, because a real stack validates the
checksum.
this was found.
"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 reachablethere:
src/inet/networklayer/icmpv6/Icmpv6.ccis byte-identical to the branch the report waswritten 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 apseudo-header there.