Repository navigation
fix(edge): Remove stray IPv6 routes and addresses on Edge attachments - #1169
Conversation
|
Validation run locally. The lab run is at 9f0b72d, this branch on main at 74b938e with the address fix from review. The repository checks ran at 2ea8de3, and the rebase changed none of this branch's files. Real-kernel tests in
Lab run,
Repository checks at 2ea8de3: Not run: the target-platform rows, which move to #985. |
fe80f02 to
6a8921c
Compare
An attachment's pod link should carry only the default route from the pod VRF toward the host. galactic-edge pruned stray routes through the host link and the packet device, and Readback reported them, but a route added through the pod link stayed in place and Readback did not see it. A process in the provider pod with the right privilege could steer pod VRF traffic onto it and the node would never notice. Provision now removes every static route through the pod link in the pod table except the default, so the agent repairs such drift at its next pass, and Readback reports one it finds as ErrDrift. Kernel-owned and local routes stay out of scope, as for the other links. TestPodLinkRouteDrift adds a route through the pod link: Readback reports it, and Provision removes it. TestPeerNamespaceOnlyImpostor offers the host link's peer from another namespace, with the same pod link name and index and a peer at the host link's ifindex, so only the peer namespace tells them apart. Provision and Release refuse it and change neither side. Both tests fail with their check removed.
TestMASQUE gains four lab rows for the remaining packet-access checks. - M16 refused requests: a pod under the fixture's ServiceAccount cannot write an attachment for another pod. It cannot ask for packet access without a device owner. A request for a service that does not exist, or for a contract or capability version the provider does not declare, is refused before allocation. The fixture's own pod is denied EdgeService, BGPAdvertisement, route index, route status and attachment host link writes. Its token carries its pod UID and node, and its only binding is core's galactic-edge-provider RoleBinding. - M16 missing device: a fixture pod without /dev/net/tun gets its attachment programmed, reports it DataPlaneFailed and claims no route, so nothing forwards. - A06 registration: each fixture host link is a veth and is registered for delivery as one. The packet device sits behind the veth's peer in the pod VRF, so bpf_redirect_peer is the right delivery and the M07 isolation row proves both directions through it. - A16 route drift: a route added through the fixture's pod link is removed by galactic-edge at its next pass, while the attachment stays Programmed and a connected client keeps its replies. edgeprobe gains an egresskind command that reads a link's row from the pinned ifindex_egress_kind_table, since the lab nodes have no bpftool.
The provider contract said nothing about what the node does when host state drifts under a programmed attachment. It now says the node provisions every attachment again each resync, removing static routes through the attachment's links that it did not write, and that a remaining one takes the attachment out of Programmed. The MASQUE acceptance matrix's lab paragraph now lists the #989 rows the lab covers and names the issue that owns running the checks on the target platform.
The MASQUE provider adapter deletes an attachment that has had no request for 30 seconds. Rows that read the fixture's attachments assumed both tenants' attachments still existed, so a tenant left idle by the rows before them made maintenance-during-use dereference a missing attachment and panic, which ended the run. inUse connects both tenants and waits until the fixture serves their attachments again. The rows that read attachments call it first, so each holds whatever order the rows run in.
6a8921c to
ea4795a
Compare
The MASQUE lab rows and #1171's partition row each added a literal nsenter, which made three in the package and failed goconst. The harness now names it once, and the drift row uses that name.
|
Review record for head 6a82abf. Both review passes returned merge, and the fixes from the first pass were confirmed on the second. First pass at 2ea8de3: the adversarial review returned merge. It noted that it could not run the real-kernel tests or the lab, and that the contract said "every unicast route" where the check is IPv6-only. The conventions review held on three commit subjects over 72 characters, and noted the overlap with #1162, the staging and release path, and a narrating test comment. Fixes at fe80f02: the three subjects were reworded, the contract now says IPv6, the narrating comment was removed, and the body states the staging and release path. Second pass at fe80f02: the re-review returned merge and confirmed the fixes against the archived lab runs. The conventions review returned merge. Since then the branch was rebased onto main with #1162 and #1171, with the commit subjects unchanged. One further commit names nsenter once, to fix a goconst lint failure the rebase caused. A narrow re-review of that delta returned merge. Checks on 6a82abf: CI's root unit test job ran the new real-kernel tests and passed. A full lab run on the rebased branch passed every row this PR adds. Three rows that fail on main still fail, as the body says. Build, Documentation, E2E Tests, Lint, Manifests, Service API bundle, Unit Tests, Unit Tests (root) and the publish jobs are green. publish-galactic-debug-image and publish-service-api were skipped. Reviewers: none requested beyond the CODEOWNERS team, since the repository sets no prReview.humanReviewer. Auto-merge is enabled with the merge method, and the main ruleset requires one approval with code owner review, so it waits for a human approval. |
ecv
left a comment
There was a problem hiding this comment.
Request changes.
The stray-route removal is exempt for any "proto kernel" route whose prefix matches an address on the link, and the node never prunes addresses on the pod link, so a provider pod can still steer traffic past the node with one extra ip addr add.
internal/edge/hostattach/inventory.goextraRoutes/connectedPrefixes: the connected-route exemption is built from whatever IPv6 addresses the link carries now, not from the addresses the node gave it (fe80::2and the attachment address). The pod owns its namespace, so it adds, for example,fd00:dead::1/64to the pod link. The kernel adds the connected routefd00:dead::/64(proto kernel) in the pod VRF table. That prefix is inconnected, soextraRoutesskips it andReadbackstays green. A /64 or /128 connected route is more specific than atunRoutesprefix and wins over it. Nothing inconfigurePodLink/ensureAddrremoves the address, andcheckAddronly checks that the wanted ones are present. Fix: buildconnectedfrom the node's own addresses only, or prune and report extra addresses on the pod link in the same pass. This is from reading the code, not a run.TestPodLinkRouteDriftcovers a stray prefix, a second default and a forgedproto kernelroute, but not an added address.- Removal is only for routes that match
RT_FILTER_TABLE|RT_FILTER_OIFin the one table the node owns (host table orPodTable), IPv6 only. IPv4 routes, routes in the pod's main or other tables,ip ruleentries, and multipath routes whoseLinkIndexis 0 are not looked at. The doc says "IPv6 unicast route", so this is stated scope, but it is narrower than the PR title. - Window: polling only.
agent.Resyncis one minute and there is no netlink route subscription, so a stray route can carry traffic for up to a minute before the next pass removes it. A route added afterpruneRoutesin the same pass is caught byReadback, which fails the attachment out ofProgrammedrather than removing it. - No race with the node's own routes:
pruneRoutesruns before theRouteReplacecalls, andmatchesaccepts metric 0 or 1024 with the same destination and gateway, so a just-installed route is not seen as stray. The kernel'sfe80::/64, local-table andff00::/8routes are skipped by route type or by the address check, so they are not flapped. RA routes are not an issue while the pod forwards (podForwarding=1). - Route listing and the address listing go through
nldump.On(h)(the #1132 retry wrapper), not single-attempt netlink calls. - Reporting: an unremovable route returns an error naming the link, and
Readbackaddsunexpected route <dst> via <gw> metric <n>.agent.goreports that as a failure withdiag.StageProgrammingand a condition, and it clears whenReadbacknext passes. No metric is added.
Follow-ups:
- Extend the sweep to IPv4, to every table the pod link can carry routes in, to multipath routes, and to
ip rulesteering through the VRF. - A route event subscription (or a shorter pass for the pod link) would shrink the one-minute window.
- Add a drift test for an extra address on the pod link once the exemption is fixed.
|
Thanks. Taking the points in order. Connected-route exemption. Fixed in 9f0b72d, both ways you suggested:
Scope. IPv4, tables other than the one the node owns, Window. The repair runs on the one-minute resync, and the contract says so. A route or address added between passes, or after the prune in the same pass, fails Readback, and the attachment leaves No change for the points on ordering, I'll post the lab and test results for the new head here once they finish. |
…links The stray-route repair left any kernel route whose prefix matched an address the link carried. A provider pod owns its namespace, so it could add a global address to its pod link or packet device, and the kernel's connected route for that address escaped both the repair and Readback while steering pod VRF traffic past the attachment's routes. The kernel routes the node leaves are now only the link-local prefix and the connected routes of the addresses it assigns itself. It also removes every other global address from the pod link and the packet device each pass, and Readback reports one it finds. TestAddressDrift adds an address to each and fails with the address check removed.
9f0b72d to
cb5bfc2
Compare
|
Results for the fix. cb5bfc2 differs from the tested 9f0b72d only in the comment above the pod link's prune, which now covers addresses too.
|
ecv
left a comment
There was a problem hiding this comment.
Approve.
The hold is closed. The connected-route exemption is now a fixed list (fe80::/64 plus the node's own address) passed in by each caller, no longer read from the link's current addresses, and extra global addresses are pruned and reported, so ip addr add fd00:dead::1/64 on the pod link no longer buys a skipped route.
- Host link: exemption is
fe80::/64only, and the node assigns onlyfe80::1there. Pod link:fe80::/64plusa.Address, a /128 that carries no prefix route. Packet device:fe80::/64only, and the node assigns no address to it, sopruneAddrs(h, tun, nil)is correct. pruneAddrsruns after the node addsfe80::2anda.Addressand leaves link-local andownaddresses alone, so it cannot remove the node's own addresses. Local-table andff00::/8routes are still skipped by theRTN_UNICASTfilter, and thefe80::/64route is still exempt.TestAddressDriftadds a global address to the pod link and to the packet device, expectsErrDriftwith "unexpected address", then repairs and re-reads clean. The old code built the exemption from the link's addresses and had no such message, so the test fails there. It is a root test, and the root CI job runs it.
Follow-ups:
extraAddrsskips all offe80::/10, not just the node'sfe80::2. A provider addingfe80:1::1/64keeps the address while its connected route is pruned. Narrow the skip to the node's link-local addresses.- The host link has no
pruneAddrs/checkExtraAddrs. Only the pod namespace is provider-reachable today, so this is a hardening gap. - Earlier follow-ups (IPv4, other tables, ip rule, polling window) stand.
Summary
A route added through an Edge attachment's pod link stayed in place and went unreported, so a provider pod could steer its VPC traffic past what the node checks.
The node now removes any IPv6 route through an attachment's links that it did not install, and any global address it did not assign on the links inside the pod, at its next pass at most a minute later. It reports one it cannot remove.
This also adds the last lab checks for MASQUE packet access: the fixture's refused requests and permissions, a pod with no packet device, how its host links are registered, and drift repair with a client connected.
Compatibility and rollback
Nodes running this release remove IPv6 routes through an attachment's host link, pod link or packet device that differ from the ones the node installs, including routes written as kernel routes, and global addresses it did not assign on the pod link and packet device. The node never created such routes or addresses, so only hand-added or foreign ones go. Revert the commits to roll back. Merging deploys the change to staging, and it reaches production only in a later release.
Test plan
Three existing MASQUE lab rows fail on main without this change, so they are not covered here: maintenance during use, partial setup and recreation. The connector revocation row does not run, because recreation stops the suite first. Running the checks on the target platform moves to #985.
Fixes #974
Related to #989