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 ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
Anand-240
force-pushed
the
fix/tap-leak-on-network-setup-failure
branch
from
July 31, 2026 23:10
5c274f4 to
f2e9c82
Compare
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. Fixes: urunc-dev#874 Signed-off-by: Anand-240 <anandprakashsrivastava68@gmail.com>
Anand-240
force-pushed
the
fix/tap-leak-on-network-setup-failure
branch
from
August 10, 2026 16:06
f2e9c82 to
a9763d1
Compare
Contributor
Author
|
Hi @cmainas, thanks for taking a look. I've cleaned up the commit/PR to follow the contributing guide more closely (fixed the missing |
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.
Description
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() and StaticNetwork.NetworkSetup() each
call networkSetup() 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
from Kill(), which only runs on
urunc delete --force. A plaincreate/start failure therefore leaves a stray tapN_urunc device (and
any TC rules on it) in the netns. Since getTapIndex() treats the mere
presence of a tap as a sign that a unikernel is already running there,
every subsequent setup attempt in that same netns then fails
permanently until something explicitly force-deletes the container.
Related issues
Changes
removeTap()inpkg/network/network.go, which deletes thetap 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 afterthe tap device is created.
DynamicNetwork.NetworkSetup()now callsremoveTap()ifgetInterfaceInfo()fails after a successfulnetworkSetup().StaticNetwork.NetworkSetup()now callsremoveTap()ifsetNATRule()fails after a successfulnetworkSetup().How was this tested?
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
cannot be built or tested directly on non-Linux)
pkg/networkunit tests don't exercisenetworkSetup()itself since it needs real netlink/root/netns. Verified instead 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.
LLM usage
Claude (Anthropic, model: claude-sonnet-5) was used to trace the
leaked tap device path and help put this fix together. Reviewed and
tested by me before opening this PR, per the project's LLM policy.
Checklist
make lint). Not run locally, nogolangci-lint setup available in this environment; deferring to CI.
locally; deferring to CI.