fix(network): remove leaked tap device on networkSetup failure - #875
Draft
Anand-240 wants to merge 1 commit into
Draft
fix(network): remove leaked tap device on networkSetup failure#875Anand-240 wants to merge 1 commit into
Anand-240 wants to merge 1 commit into
Conversation
✅ Deploy Preview for urunc canceled.
|
networkSetup() creates the tap device before doing anything that can fail (bringing links up, adding TC qdiscs/filters, assigning an IP), but none of its error paths deleted the tap it had already created. The same gap existed one level up: DynamicNetwork.NetworkSetup() and StaticNetwork.NetworkSetup() each call networkSetup() successfully and then do more work (getInterfaceInfo / setNATRule) that can still fail, again leaving the tap behind. The only existing cleanup, CleanupAllUruncTaps(), only runs from Kill() on `urunc delete --force`, so a plain create/start failure leaves a stray tapN_urunc in the netns. Since getTapIndex() treats any existing tap as "a unikernel is already running here", every subsequent setup attempt in that netns then fails permanently until something explicitly force-deletes it. Track whether the ingress qdisc on the container's real interface was actually added before rolling it back, since unlike the tap it can carry TC state that predates this call. Signed-off-by: Anand-240 <anandprakashsrivastava68@gmail.com>
Anand-240
force-pushed
the
fix/tap-leak-on-network-setup-failure
branch
from
July 31, 2026 23:10
5c274f4 to
f2e9c82
Compare
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.
Summary
networkSetup()creates the tap device before doing anything that can fail (bringing the tap/redirect link up, adding TC ingress qdiscs and redirect filters, assigning an IP), but none of its error paths ever deleted the tap it had already created. The same gap existed one level up:DynamicNetwork.NetworkSetup()andStaticNetwork.NetworkSetup()each callnetworkSetup()successfully and then do more work (getInterfaceInfo,setNATRule) that can still fail, leaving the tap behind again.The only existing cleanup,
CleanupAllUruncTaps(), is only reachable fromKill(), which only runs onurunc delete --force. A plaincreate/startfailure therefore leaves a straytapN_uruncdevice (and any TC rules on it) in the netns. SincegetTapIndex()treats the mere presence of a tap as "a unikernel is already running here", every subsequent setup attempt in that same netns then fails permanently, until something explicitly force-deletes the container. In Kubernetes this is a real trap: the pod's sandbox netns outlives a failed container attempt, so one transient failure (e.g. no IPv4 on the interface, TC module unavailable, a NAT rule failing) permanently disables networking for that pod.Fixes #874
Changes
removeTap()inpkg/network/network.go, which deletes the tap device and, only if it was actually added during this attempt, the ingress qdisc it placed on the container's real interface. The container's own interface is never touched unless we know we put the TC state there ourselves, since it can carry configuration that predates this call.networkSetup()now callsremoveTap()on every error path after the tap device is created.DynamicNetwork.NetworkSetup()now callsremoveTap()ifgetInterfaceInfo()fails after a successfulnetworkSetup().StaticNetwork.NetworkSetup()now callsremoveTap()ifsetNATRule()fails after a successfulnetworkSetup().Test plan
gofmt -lclean on all changed filesGOOS=linux GOARCH=amd64 go build ./pkg/network/...andgo vetpass (this package uses Linux-only netlink/unix constants, so it can't be built/tested directly on non-Linux)pkg/networkunit tests don't exercisenetworkSetup()itself (it needs real netlink/root/netns), so this was verified by reviewing every error path and reasoning about which TC state predates vs. is created by each call; happy to add a netns-based integration test if there's an existing harness/CI job for that