Skip to content

pmipv6: refuse a proxy binding update older than the accepted one - #1243

Open
adamgeorge309 wants to merge 1 commit into
masterfrom
topic/gy/pmipv6-pbu-ordering
Open

adamgeorge309 wants to merge 1 commit into
masterfrom
topic/gy/pmipv6-pbu-ordering

Conversation

@adamgeorge309

@adamgeorge309 adamgeorge309 commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

The Proxy Mobile IPv6 (PMIPv6, RFC 5213) local mobility anchor (LMA) now refuses a Proxy Binding Update (PBU) whose timestamp is older than that of the last one it accepted for the mobile node, as RFC 5213 Section 5.5 requires. Before, it accepted PBUs in arrival order: a PBU from the mobile access gateway (MAG) the node had left, delayed on that MAG's backhaul, re-pointed the node's home network prefix back to that MAG.

Closes #1242

The problem

src/inet/networklayer/pmipv6/Pmipv6.cc on master:

  • :376 — the MAG writes pbu->setTimestampValue(0); // ordered by sequence number in this model;
  • :233 — the LMA stores the sequence number in the binding cache entry, and nothing reads it back;
  • :208–:236 — the registration branch re-points the prefix for any PBU, whatever its age.

RFC 5213 Section 5.5 names the consequence of an older message from the previous MAG "delivered out of order, resulting in incorrectly updating the mobile node's Binding Cache entry and creating a routing state for tunneling the mobile node's traffic to the previous mobile access gateway." Section 9.3 makes the timestamp scheme the default (TimestampBasedApproachInUse = 1).

tests/module/PMIPv6_pbu_ordering.test: the mobile node attaches to mag1 at 0.82 s and to mag2 at 3.49 s. mag1's backhaul link has a 1.4 s delay: its PBU waits one address resolution round trip (2.8 s) and then travels 1.4 s, so it reaches the LMA at 5.02 s, after mag2's. The correspondent node pings the mobile node every 0.5 s from 4 s.

3.49 s, PBU from mag2 5.02 s, PBU from mag1 Echo replies
master accepted accepted: LMA handover: re-pointing prefix 2001:db8:1::/64 toward MAG 2001:db8:0:1::2 3 of 33
this pull request accepted refused with TIMESTAMP_LOWER_THAN_PREV_ACCEPTED 32 of 33

The fix

  • The MAG writes its clock into the Timestamp option, in the Section 8.8 layout: 48 bits of seconds, 16 bits of 1/65536 s.
  • The LMA stores the timestamp of the last accepted PBU in the binding cache entry. A registration whose timestamp is lower is refused with TIMESTAMP_LOWER_THAN_PREV_ACCEPTED (Section 5.5, rule 8); an equal one fails the "greater than" test of rule 6 and is refused with TIMESTAMP_MISMATCH (rule 9). Both status codes were declared in MobilityHeader.msg and not used.
  • The Proxy Binding Acknowledgement echoes the timestamp when it accepts and carries the LMA's clock when it refuses (rules 7 to 9). A refusal carries lifetime 0: RFC 6275 Section 6.1.8 leaves the lifetime of a rejection undefined, and 0 grants nothing.
  • Deregistrations are not compared by timestamp. They keep the existing Section 5.3.5 check that only the serving MAG may deregister a node, which already refuses a deregistration from the MAG a node has left.
  • No parameter switches the ordering off, because no other ordering is implemented (see Not addressed here).

Architectural surface

  • Packet content: the Timestamp option of every PBU and Proxy Binding Acknowledgement is no longer zero, and a refused PBU is answered with status TIMESTAMP_LOWER_THAN_PREV_ACCEPTED or TIMESTAMP_MISMATCH, lifetime 0 and the LMA's clock. No message field or length changes; one comment in MobilityHeader.msg changes.
  • C++: the protected struct Pmipv6::BindingCacheEntry gains the field timestamp. No public or virtual function changes.
  • Configuration: no parameter changes. The Pmipv6.ned documentation comment gains one paragraph on the ordering.
  • No feature descriptor changes, and no sealed path is touched.

Verification

Each suite was run on unmodified master (49e1fa0945) and with this commit, on a release build (make MODE=release in the root, then in tests/module/lib and tests/serializer/lib); the test commands pass --no-build.

Command Master This commit
tests/module: inet_run_module_tests -m release --no-build -l ERROR -f MIPv6 (matches PMIPv6_* too) 10 of 11 pass, MIPv6_tcp_handover fails 11 of 12 pass: the new PMIPv6_pbu_ordering passes, MIPv6_tcp_handover fails
tests/fingerprint: ./fingerprinttest -s -F tyf 1774 run, 0 failures, 62 errors 1774 run, 0 failures, the same 62 errors, after the ~tND update
tests/serializer: inet_run_serializer_tests -m release --no-build -l ERROR 3 of 4 pass; serializer_chunk_roundtrip fails with 60 failed cases the same, the same 60 cases
inet_run_statistical_tests -m release -w examples/ipv6/pmipv6 (statistics repository 9ab4c26a45) — pass
doc/project/enforcement/check-commits.sh origin/master..HEAD, check-classification.sh origin/master..HEAD, check-source-seals.sh --base origin/master — pass
doc/project/enforcement/check-naming.sh --base origin/master fails on pre-existing directory names the same; the changed .ned and .msg files have no naming candidates
doc/project/enforcement/check-architecture.sh, check-interfaces.sh fail identical output

The commit was also built in debug mode, with make MODE=debug -j4 at the repository root and in tests/module/lib, exit 0, with no compiler warning in the changed files. On that build cd tests/module && inet_run_module_tests -m debug --no-build -l ERROR -f 'MIPv6|PMIPv6' passes 12 of 12, exit 0: the new PMIPv6_pbu_ordering and the eleven MIPv6_* tests, MIPv6_tcp_handover included.

The master row of the first table comes from the network, config.xml, movement.xml and omnetpp.ini of PMIPv6_pbu_ordering.test, extracted into a directory and run against the master library with opp_run -l <master>/src/INET -n .:<master>/src -u Cmdenv. The 62 fingerprint errors are configurations whose optional features are not built (VoipStreamSender, TcpLwip, Z3GateScheduleConfigurator, the OpenSceneGraph visualizer showcases). MIPv6_tcp_handover and serializer_chunk_roundtrip fail identically on unmodified master, so they are pre-existing.

examples/ipv6/pmipv6 behaves as before: 31117 events, 105 echo requests sent and 102 answered, identical scalars. Its fingerprint that hashes packet bytes (~tND) moves from bd46-d85c to bdf6-52b5 because the Timestamp option is no longer zero. The fingerprint of event times, module paths, message lengths and module-supplied extra data (tplx) and the one of event times, node paths and message lengths of packets between nodes (~tNl) do not move. The one that hashes display strings and canvas figures (tyf) fails on unmodified master (actual 05f8-fee3, recorded 0277-d784), gives the same actual value with this commit, and is left as it is.

Not addressed here

  • The timestamp comparison for deregistrations (Section 5.5 rule 6 applies to every PBU that carries the Timestamp option). A deregistration from a MAG other than the serving one is refused by the Section 5.3.5 check; a delayed deregistration from the serving MAG itself is not refused.
  • The sequence-number scheme of Section 5.5, which Section 9.3 selects with TimestampBasedApproachInUse = 0. It is allowed only when a MAG can learn the last sequence number another MAG sent for the node (rule 3 of the sequence-number approach), and each MAG here keeps its own counter.
  • The 1970 epoch of Section 8.8. The Timestamp option carries simulation time in the Section 8.8 layout, counted from 0; ordering needs one clock that all nodes share, and simulation time is such a clock.
  • The check of the timestamp against the LMA's clock and TimestampValidityWindow. It detects clock skew between machines; all nodes read one simulation clock, so TIMESTAMP_MISMATCH is sent only for an equal timestamp.
  • Two PBUs for one node sent less than 1/65536 s apart get equal timestamps, and the second is refused with TIMESTAMP_MISMATCH. That is the resolution of the Section 8.8 format.

Devin Review

A Proxy Mobile IPv6 (PMIPv6, RFC 5213) local mobility anchor (LMA) must be
able to tell which of two Proxy Binding Updates (PBU) for one mobile node
was sent later. RFC 5213 Section 5.5 names the consequence of not telling:
a message from the gateway the node was previously anchored at, "delivered
out of order, resulting in incorrectly updating the mobile node's Binding
Cache entry". Section 9.3 makes the timestamp scheme of Section 5.5 the
default (TimestampBasedApproachInUse = 1), with the sequence-number scheme
as the alternative.

Pmipv6 applied neither. The mobile access gateway (MAG) wrote a zero
Timestamp option, with the comment "ordered by sequence number in this
model", and the LMA stored the sequence number without comparing it. The
LMA accepted every registration in arrival order.

Now the MAG writes its clock into the Timestamp option in the RFC 5213
Section 8.8 layout (48 bits of seconds, 16 bits of 1/65536 s). The clock
is simulation time, counted from 0 rather than from the 1970 epoch that
Section 8.8 names: the ordering needs one clock that all nodes share, and
simulation time is such a clock. The LMA keeps the timestamp of the last
accepted PBU in the binding cache entry and refuses a registration whose
timestamp is not greater: an older one with
TIMESTAMP_LOWER_THAN_PREV_ACCEPTED (Section 5.5, rule 8), an equal one
with TIMESTAMP_MISMATCH (rules 6 and 9). An acceptance echoes the
timestamp, a rejection carries the LMA's clock (rules 7 to 9), and a
rejection carries lifetime 0: RFC 6275 Section 6.1.8 leaves the lifetime
of a rejection undefined, and 0 grants nothing. Deregistrations are not
compared by timestamp: they keep the existing rule that only the serving
MAG may deregister a node (Section 5.3.5), which already refuses a
deregistration from the MAG a node has left.

The sequence-number scheme, which Section 9.3 selects with
TimestampBasedApproachInUse = 0, is not implemented, and no parameter
selects it: Section 5.5 allows it only when a MAG can learn the last
sequence number another MAG sent, and each MAG here keeps its own counter.
The check against the LMA's clock and TimestampValidityWindow is not
implemented either, because all nodes read the same simulation clock.

To reproduce, copy the new module test into a master checkout and run it:

  cd tests/module
  inet_run_module_tests -m release -f PMIPv6_pbu_ordering

The mobile node attaches to mag1 at 0.82 s and to mag2 at 3.49 s, and
mag2's PBU reaches the LMA at once. mag1's backhaul link has a 1.4 s
delay: mag1's PBU waits one address resolution round trip (2.8 s) and then
travels 1.4 s, so it reaches the LMA at 5.02 s. On master the LMA logs
"LMA handover: re-pointing prefix 2001:db8:1::/64 toward MAG
2001:db8:0:1::2" at 5.02 s, and the correspondent node's PingApp reports
"sent: 33 received: 3". With this commit the LMA logs "LMA rejecting Proxy
Binding Update for MN 'mn1@example.com' from MAG 2001:db8:0:1::2: its
timestamp is older than the last one accepted" and PingApp reports "sent:
33 received: 32".

examples/ipv6/pmipv6 behaves as before: the same 31117 events, 105 echo
requests sent and 102 answered, and identical scalars. Its fingerprint
that hashes packet bytes (~tND) moves from bd46-d85c to bdf6-52b5, because
the Timestamp option of each PBU and Proxy Binding Acknowledgement is no
longer zero. The fingerprint of event times, module paths, message lengths
and module-supplied extra data (tplx) and the one of event times, node
paths and message lengths of packets between nodes (~tNl) do not move.

Change: src.pmipv6.Pmipv6 | behavior.change.fix | fingerprint test whatsnew

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 3 potential issues.

1 flag not posted on this PR by your GitHub settings — view it in Devin Review. (Configure)

Devin Review

Comment on lines +219 to +220
else if (it != bindingCache.end()
&& pbu->getTimestampValue() <= it->second.timestamp)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Delayed deregistration erases a newer binding

When processProxyBindingUpdate receives a delayed deregistration after a newer registration from the same MAG, it erases the newer binding. The lifetime-zero branch bypasses the timestamp comparison but still matches the serving MAG. Downlink traffic loses its route.

Learn more

The LMA accepts a registration by saving its timestamp in the binding cache. A deregistration enters the earlier lifetime-zero branch, which tests only whether its source address equals the serving MAG. If that deregistration was delayed behind a newer registration from the same MAG, it deletes the new route and entry. The new timestamp check does not protect this ordering even though both messages carry timestamps.

Example: MAG A sends a deregistration at 10 s, then registers the same node again at 11 s. If the 11 s PBU arrives first, the LMA accepts it. When the delayed 10 s deregistration arrives, the LMA removes the 11 s binding instead of preserving it.

Recommended fix: Apply timestamp ordering before both deregistration and registration handling in processProxyBindingUpdate. Preserve the accepted timestamp through the lifetime-zero transition as necessary, and add a test that reverses delivery order for deregistration and re-registration from one MAG.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +219 to +220
else if (it != bindingCache.end()
&& pbu->getTimestampValue() <= it->second.timestamp)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Delayed update revives a deregistered binding

When an old registration arrives after deregistration, bindingCache.find returns no entry and accepts the old timestamp. Deregistration erased the only saved timestamp. The LMA routes the departed node's traffic back to the old MAG.

Learn more

The LMA stores the most recent accepted timestamp only inside a binding cache entry. The lifetime-zero path in processProxyBindingUpdate erases that entry. When an earlier registration arrives afterward, the new check sees no entry, accepts the outdated PBU, and installs a downlink route despite the node having detached.

Example: A registration sent by MAG A at 5 s waits on a slow link. MAG B registers the node at 6 s and deregisters it at 7 s. When A's 5 s registration reaches the LMA at 8 s, it revives a binding to A instead of being rejected as older than the last accepted update.

Recommended fix: Retain the last accepted timestamp for a mobile node after removing its route, for at least the period when delayed PBUs can arrive. Compare future registrations against this history, and test an old PBU arriving after successful deregistration.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +43 to +44
uint64_t fraction = (uint64_t)((seconds - (double)wholeSeconds) * 65536.0);
return (wholeSeconds << 16) | (fraction & 0xFFFF);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Sub-tick handovers lose their registration

When two MAGs send PBUs within one 1/65536-second tick, timestampOf gives both updates the same value. The LMA rejects the later update as equal; the new MAG neither installs a route nor retries.

Learn more

The wire-format timestamp has 16 fractional bits, so this conversion discards finer simulation-time differences. The LMA's strict comparison in processProxyBindingUpdate rejects equal values, while processProxyBindingAcknowledgement simply returns on rejection. Two distinct attachment events can therefore leave the later MAG without a binding even though it sent the later update.

Example: A PBU sent at 10.000001 s and another sent at 10.000002 s both encode as 655360. Once the LMA accepts the first, it rejects the second with TIMESTAMP_MISMATCH; the later attachment gets no registration.

Recommended fix: Define handling for distinct PBUs within one timestamp tick, such as a retry with a strictly later timestamp and bounded backoff. Cover a handover occurring within one tick in a focused module test.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

pmipv6: the local mobility anchor does not order Proxy Binding Updates, so a delayed one from the previous gateway re-points the prefix

1 participant