Skip to content

fix(network): remove leaked tap device on networkSetup failure - #875

Draft
Anand-240 wants to merge 1 commit into
urunc-dev:mainfrom
Anand-240:fix/tap-leak-on-network-setup-failure
Draft

fix(network): remove leaked tap device on networkSetup failure#875
Anand-240 wants to merge 1 commit into
urunc-dev:mainfrom
Anand-240:fix/tap-leak-on-network-setup-failure

Conversation

@Anand-240

Copy link
Copy Markdown
Contributor

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() 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 plain create/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 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

  • Added removeTap() in pkg/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 calls removeTap() on every error path after the tap device is created.
  • DynamicNetwork.NetworkSetup() now calls removeTap() if getInterfaceInfo() fails after a successful networkSetup().
  • StaticNetwork.NetworkSetup() now calls removeTap() if setNATRule() fails after a successful networkSetup().

Test plan

  • gofmt -l clean on all changed files
  • GOOS=linux GOARCH=amd64 go build ./pkg/network/... and go vet pass (this package uses Linux-only netlink/unix constants, so it can't be built/tested directly on non-Linux)
  • Existing pkg/network unit tests don't exercise networkSetup() 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

@netlify

netlify Bot commented Jul 31, 2026

Copy link
Copy Markdown

Deploy Preview for urunc canceled.

Name Link
🔨 Latest commit f2e9c82
🔍 Latest deploy log https://app.netlify.com/projects/urunc/deploys/6a6d2b6ee3dabf000782963d

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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

do-not-merge invalid This doesn't seem right

Projects

None yet

2 participants