Repository navigation
fix(vpp-offload): follow the router's own addresses while the daemon runs - #355
Open
lunarthegrey wants to merge 1 commit into
Open
lunarthegrey wants to merge 1 commit into
lunarthegrey wants to merge 1 commit into
Conversation
…runs The engine read the router's addresses and subnets once, at attach, and judged every route via the router against them for the life of the process. A subnet addressed later, such as a new IX LAN on a new bridge whose connected route FRR redistributes with the router as next hop, fell through to the kernel FIB check. That check refused it, because the connected route leaves through a bridge VPP can reach, so the route read unresolvable until a restart. With it, fib-synced read Degraded, every verify came back incomplete and parked the module in Ready, and the first-steer FIB gate refused both the steer retry and the lever, even with a covering steer-exempt in place. The kernel topology's background thread now re-reads the addresses every 2 s, alongside the FDB and port VLANs, and the runtime's placement tick hands any change to the engine. The engine hands back to the source every route whose judgement the change can flip: routes through an address that came or went (requeue_via), and routes inside a subnet that came or went or naming such an address (the new requeue_within, a range over the feed's ordered mirror). The delta path then judges them again, both ways. A resync re-reads too, so its walk starts from the addresses held now. The addresses are polled rather than subscribed to: a full read every 2 s is a few dozen entries and has no notifications to lose, so there is no overrun to recover from. A dump taken during churn can still skip a live entry, so an address leaves only once two reads in a row lack it, the rule neigh-snoop applies to its neighbour rows since #332. A failed read keeps the last good sets, rather than reading an empty table as every address gone, and the reason of every unresolvable route via the router names the failure. Startup and the refresh share one getifaddrs walk, so they cannot disagree about what an address is. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
vpp-offload's
ConvergenceEngineread the router's own addresses and subnets once, at attach (with_self_networksand its v6 twin), and judged every route via the router against them for the life of the process.On 2026-10-11 a new IX LAN was addressed on a new bridge after the daemon started. FRR redistributes the connected /24 with the router's own address as next hop. Because the engine's subnet set predated the address, the route fell through to shape 3 (
kernel_owns). The kernel's connected route leaves through a bridge VPP can reach over avlans alltrunk, so shape 3 refused it, and the route readunresolvable (mapping): … the kernel sends this prefix out <bridge>, which VPP can reach (a transit route under next-hop-self?). A coveringsteer-exemptwas already configured. While the route stayed unresolvable:fib-syncedread Degraded.VerifyIncomplete, so the supervisor parked in Ready.fib_fit_to_steer(), which refuses while anything is unresolvable, so steering was never re-asserted and the lever was refused. Only a daemon restart rebuilt the engine.What changes
The kernel topology re-reads the addresses every 2 s, on the existing
pf-vpp-fdbthread next to the FDB and port VLANs (Topology::self_networks,topology::SelfNetworks). This is a poll, not an RTM_NEWADDR subscription. A full read is a few dozen entries and has no notifications to lose, so there is no overrun to recover from. That is why fix: netlink consumers recover from lost notifications and never wait forever on a reply #332's subscribe-before-dump pattern does not apply. The only existing address subscription is the hand-back path's (v6 only, and only underv6 on).Its rule from fix: netlink consumers recover from lost notifications and never wait forever on a reply #332: an address leaves only once two reads in a row lack it, because a dump taken during churn can skip a live entry (
publish_self_networks). A new address is published at once, and a failed read publishes the failure.The engine follows (
ConvergenceEngine::refresh_self_networks). The runtime calls it on the placement tick (PLACEMENT_EVERY, 2 s) beforeapply_changes. It diffs the new sets against the ones in force, both families (v6 only underv6 on, as at bring-up), and hands back to the source every route whose judgement the change can flip:requeue_via(shapes 1 and 3 need every next hop to be the router's);RouteSource::requeue_within. OnRouteFeedthat is a range scan over the ordered mirror, so its cost is the routes inside the subnet, not the table.The delta path then judges each route again, either way: kernel-delivered and withdrawn, or back to resolution and the kernel's word. A resync re-reads too, so its walk starts from the addresses held now.
A failed read keeps the last good sets. An empty table would otherwise read as every address gone. The reason of every unresolvable route via the router names the failure until a read succeeds.
Startup and the refresh share one
getifaddrswalk (bringup::kernel_ifaddrs, fallible). The attach-time readers still degrade open, as before.Once the route is kernel-delivered, nothing new is needed:
poll_reverifyre-runs the incomplete verdict once the table is clean, and the steer retry follows.vpp_runtime.rsproves that chain through the real loop.Tests
engine.rsunit tests, with a fake topology and a fake feed that serves handed-back routes:v6 ononly.tests/vpp_engine.rs, against the fake VPP: the unresolvable count drops from 1 to 0, VPP is told to delete the prefix and nothing is installed, the first-steer gate opens with thesteer-exempt, verify goes from incomplete to passed, and removing the address brings the count back with the same name.tests/vpp_runtime.rs, throughDriver+Runtime+ the realRouteFeed: a module parked in Ready on an incomplete verdict recovers by itself after the address appears, with no resync. The test waits ~2 s of real time, because placement is paced on the real clock.feed.rs:requeue_withinqueues exactly the routes inside the nets (both families, the subnet's own route included, a newer queued withdrawal wins), through theArcthe loader boxes.topology.rs: the two-reads publish rule.Mutation checks: removing
requeue_withinfrom the refresh fails the unit tests and the fake-VPP test, which then reports the production symptom string verbatim. Removing the runtime call fails the runtime test. Removing the resync re-read fails its unit test.Local gates:
cargo fmt --check; host clippy and clippy for all four Linux targets with--workspace --all-targets --all-features -D warnings; hostcargo test --workspace; and fmt, clippy andcargo test --workspaceon Linux arm64 in Docker. Docs: CHANGELOG, and the runbook's kernel-delivered section, the new-VLAN bullet and the unresolvable-reason table.🤖 Generated with Claude Code