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

@Anand-240 Anand-240 commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

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 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 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

  • 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().

How was this tested?

  • 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
    cannot be built or tested directly on non-Linux)
  • Existing pkg/network unit tests don't exercise networkSetup()
    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

  • I have read the contribution guide.
  • The linter passes locally (make lint). Not run locally, no
    golangci-lint setup available in this environment; deferring to CI.
  • The e2e tests of at least one tool pass locally. Not run
    locally; deferring to CI.
  • If LLMs were used: I have read the llm policy.

@netlify

netlify Bot commented Jul 31, 2026

Copy link
Copy Markdown

Deploy Preview for urunc ready!

Name Link
🔨 Latest commit a9763d1
🔍 Latest deploy log https://app.netlify.com/projects/urunc/deploys/6a79f71ccf0a18000897dfc7
😎 Deploy Preview https://deploy-preview-875--urunc.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

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
Anand-240 force-pushed the fix/tap-leak-on-network-setup-failure branch from f2e9c82 to a9763d1 Compare August 10, 2026 16:06
@Anand-240

Copy link
Copy Markdown
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 Fixes: trailer, etc.). Could you let me know what specifically made this invalid, so I can address it or close it out properly if it's a duplicate/out of scope?

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