ipv6nd: a zero Router Lifetime must not expire Route Information routes - #729
Conversation
RFC 4191 section 3.1 separates two lifetimes that a Router Advertisement carries. The Router Lifetime in the message header updates the ::/0 route and nothing else - "a type C host first updates a ::/0 route based on the Router Lifetime and Default Router Preference in the Router Advertisement message header". Each Route Information option is then processed separately and carries its own Route Lifetime, which is what the section's "if the received route's lifetime is zero, the route is removed" refers to. RFC 4861 section 4.2 defines a Router Lifetime of zero as meaning the sender is not a default router, and RFC 4191 section 2.2 specifies the preference handling for exactly that case, so such advertisements are expected rather than malformed. ipv6nd_expirera() conflates the two. A router advertising only Route Information with a zero Router Lifetime passes no test in the expiry loop: the Router Lifetime branch is skipped because the lifetime is zero, there are no addresses or prefixes to validate, and the ND option loop handles only DNSSL and RDNSS, so a Route Information option reaches the default case. `valid` stays false, the router is marked expired, and rt_build() removes its routes - although each of those routes has its own unexpired Route Lifetime, which is the only lifetime section 3.1 gives authority over them. The next Router Advertisement clears the expired flag and reinstates the routes, so the fault presents as perpetual churn rather than as lost connectivity. A Thread border router is the common case: it advertises the Thread mesh's off-mesh-routable prefix in a Route Information option, with a zero Router Lifetime because it is not a default router, and no Prefix Information. Measured against an Amazon Echo advertising one /64 with an 1800s Route Lifetime and re-advertising every 130-190s: 545 route add/delete events in 23 hours, 91% of all dhcpcd log output on that host. Keep the router while any of its route information is unexpired, and let that route's remaining lifetime schedule the next expiry run. Note that a Route Information option with a prefix length of zero is already unaffected, because it assigns rap->lifetime directly - consistent with section 3.1, where a ::/0 Route Information option overrides the header's lifetime. Only a specific-prefix route is affected. AI-Assisted: Claude Fable 5 (Claude Code) Signed-off-by: Alex Kiernan <alex.kiernan@gmail.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Advanced Run ID: 📒 Files selected for processing (1)
Included review availability: Your plan provides up to 2 included reviews per hour; 1 remains after this review. Walkthrough
ChangesIPv6 route expiry
Priority: ➖ Normal Estimated code review effort: 2 (Simple) | ~10 minutes Severity of issue fixed: Medium Merge Risk: ⚪ Minimal · up to IPv6 Route Information routes now remain available for their advertised lifetimes even when the Router Lifetime is zero, preventing route churn for route-only advertisements. The change is ready to merge with no active current-head risk identified. Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
| rap->iface->name, rinfo->sprefix); | ||
| TAILQ_REMOVE(&rap->rinfos, rinfo, next); | ||
| free(rinfo); | ||
| continue; |
There was a problem hiding this comment.
I think we also need to set expired = true here so that rt_build() is called below.
| * RFC 4191 section 3.1: the Router Lifetime in the | ||
| * advertisement header governs the ::/0 route only, | ||
| * while each Route Information option carries its own | ||
| * Route Lifetime. A router with a zero Router | ||
| * Lifetime is not a default router (RFC 4861 section | ||
| * 4.2) but its advertised routes remain valid for | ||
| * their own lifetimes, so keep it alive here - the | ||
| * Router Lifetime test above cannot see that, and no | ||
| * other test covers a Route Information option. |
There was a problem hiding this comment.
This comment is far too wordy.
The missing code here is just an oversight, not a rationale so it could live in the commit message
and this comment could just read.
/* A non expired route information option keeps the RA valid. */
RFC 4191 section 3.1 separates two lifetimes that a Router Advertisement carries. The Router Lifetime in the message header updates the ::/0 route and nothing else - "a type C host first updates a ::/0 route based on the Router Lifetime and Default Router Preference in the Router Advertisement message header". Each Route Information option is then processed separately and carries its own Route Lifetime, which is what the section's "if the received route's lifetime is zero, the route is removed" refers to.
RFC 4861 section 4.2 defines a Router Lifetime of zero as meaning the sender is not a default router, and RFC 4191 section 2.2 specifies the preference handling for exactly that case, so such advertisements are expected rather than malformed.
ipv6nd_expirera() conflates the two. A router advertising only Route Information with a zero Router Lifetime passes no test in the expiry loop: the Router Lifetime branch is skipped because the lifetime is zero, there are no addresses or prefixes to validate, and the ND option loop handles only DNSSL and RDNSS, so a Route Information option reaches the default case.
validstays false, the router is marked expired, and rt_build() removes its routes - although each of those routes has its own unexpired Route Lifetime, which is the only lifetime section 3.1 gives authority over them.The next Router Advertisement clears the expired flag and reinstates the routes, so the fault presents as perpetual churn rather than as lost connectivity.
A Thread border router is the common case: it advertises the Thread mesh's off-mesh-routable prefix in a Route Information option, with a zero Router Lifetime because it is not a default router, and no Prefix Information. Measured against an Amazon Echo advertising one /64 with an 1800s Route Lifetime and re-advertising every 130-190s: 545 route add/delete events in 23 hours, 91% of all dhcpcd log output on that host.
Keep the router while any of its route information is unexpired, and let that route's remaining lifetime schedule the next expiry run.
Note that a Route Information option with a prefix length of zero is already unaffected, because it assigns rap->lifetime directly - consistent with section 3.1, where a ::/0 Route Information option overrides the header's lifetime. Only a specific-prefix route is affected.
AI-Assisted: Claude Fable 5 (Claude Code)
Closes #728