Skip to content

mipv6: route optimization rewrites the IPv6 header without refreshing NetworkProtocolInd, so transport checksums use the old addresses #1192

Description

@adamgeorge309

Summary

Mipv6::datagramLocalOutHook() rewrites the IPv6 header of an outbound packet and
re-inserts it with a plain insertAtFront(), bypassing insertNetworkProtocolHeader().
The NetworkProtocolInd tag therefore keeps pointing at the header as it was before
the rewrite, and every later hook that reads the tag sees the old addresses.

Route optimization has two such paths, both in
src/inet/networklayer/mipv6/Mipv6.cc. The Type 2 Routing Header path
(Mipv6.cc:2019-2039) rewrites the destination:

auto header = datagram->removeAtFront<Ipv6Header>();
...
header->setDestAddress(ro.exit);
...
datagram->insertAtFront(t2RH);
datagram->insertAtFront(header);   // NetworkProtocolInd still holds the old header

The Home Address Option path (Mipv6.cc:2048-2070) rewrites the source the same way
(header->setSrcAddress(ro.entry); followed by datagram->insertAtFront(header);).

The same file gets it right on the receive side: Mipv6.cc:1894 and Mipv6.cc:1959 both
call insertNetworkProtocolHeader(), which updates the tag.

What the contract says

insertNetworkProtocolHeader() (src/inet/networklayer/common/L3Tools.cc:70-76) exists
precisely to keep the tag and the packet in step:

void insertNetworkProtocolHeader(Packet *packet, const Protocol& protocol, const Ptr<NetworkHeaderBase>& header)
{
    auto networkProtocolInd = packet->addTagIfAbsent<NetworkProtocolInd>();
    networkProtocolInd->setProtocol(&protocol);
    networkProtocolInd->setNetworkProtocolHeader(header);
    insertProtocolHeader(packet, protocol, header);
}

Four outbound netfilter hooks in the tree depend on that tag being current, all of them to
build a transport pseudo-header from the network-layer addresses:

  • src/inet/transportlayer/tcp_common/TcpChecksumInsertionHook.cc:32
  • src/inet/transportlayer/sctp/SctpChecksumInsertionHook.cc:32
  • src/inet/transportlayer/sctp/SctpUdpHook.cc:33
  • src/inet/transportlayer/udp/Udp.cc:1398 (UdpChecksumInsertionHook::datagramPostRoutingHook())

Mipv6 registers its hook on the same IPv6 netfilter (Mipv6.cc:128,
ipv6->registerHook(0, this)), and datagramLocalOutHook runs before
datagramPostRoutingHook. So the rewrite always happens before the checksum hook reads the
tag.

The User Datagram Protocol (UDP) checksum is defined over a pseudo-header containing the
source and destination addresses (RFC 768, and RFC 8200 Section 8.1 for IPv6), so a
checksum built from superseded addresses is wrong by construction.

Why it matters

The damage is not latent. Measured on origin/master 7aef79d5c0, with
examples/ipv6/mipv6, configuration RouteOptimizationTwoCNs, two correspondent nodes
sending User Datagram Protocol (UDP) traffic to the mobile node:

Route optimization udp.checksumMode datagrams delivered to the application
on declared 28
on computed 20
off declared 28
off computed 28

The loss appears only when route optimization and computed checksums are both on. At the
receiver, the User Datagram Protocol module logs the reason:

30.001205  MN[0].udp: Packet UdpBasicAppData-0 received from network, dest port 5000
30.001205  MN[0].udp: Sending payload up to socket sockId=0
32.001087  MN[0].udp: Packet has bit error, discarding
32.002440  MN[0].udp: Packet has bit error, discarding

28 datagrams reach the module and 20 are delivered; the 8 discarded ones start at t = 32 s,
which is when the return-routability procedure completes and the binding is installed. The
receiver rebuilds NetworkProtocolInd from the header it actually received, so its
verification uses the correct addresses and correctly rejects a checksum the sender computed
from the pre-rewrite ones.

Reproduction:

cd examples/ipv6/mipv6
opp_run -m -u Cmdenv -f omnetpp.ini -c RouteOptimizationTwoCNs -r 0 --sim-time-limit=60s \
  --*.MN[0].numApps=1 --*.MN[0].app[0].typename='"UdpSink"' --*.MN[0].app[0].localPort=5000 \
  --*.CN[*].app[0].typename='"UdpBasicApp"' \
  --*.CN[*].app[0].destAddresses='"2001:db8:0:2:8aa:ff:fe00:8"' --*.CN[*].app[0].destPort=5000 \
  --*.CN[*].app[0].messageLength=100B --*.CN[*].app[0].sendInterval=2s --*.CN[*].app[0].startTime=30s \
  --**.udp.checksumMode='"computed"'

Transmission Control Protocol (TCP) and Stream Control Transmission Protocol (SCTP) traffic
over a route-optimized path is affected the same way, through the other three hooks listed
above.

The fix is to route both rewrites through insertNetworkProtocolHeader(), as the receive-side
paths in the same file already do.

Relationship to #1169

#1169 moves the Internet Control Message Protocol version 6 (ICMPv6) checksum into an
outbound hook that reads the same tag, so it meets this defect as well. The two are
independent: this one is observable on master today with User Datagram Protocol (UDP)
traffic and no new code, and #1169's fix is not required to reproduce it.

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