slackhq/nebula · error
error closing tun file: %w
Error message
error closing tun file: %w
What it means
tun.Close() wraps any error returned by closing the underlying os.File wrapping the /dev/tunN descriptor. It means the OS refused or failed the close, typically because the descriptor is already invalid/closed or was interrupted, so cleanup of the TUN device could not complete cleanly.
Source
Thrown at overlay/tun_netbsd.go:125
err = t.reload(c, true)
if err != nil {
return nil, err
}
c.RegisterReloadCallback(func(c *config.C) {
err := t.reload(c, false)
if err != nil {
util.LogWithContextIfNeeded("failed to reload tun device", err, t.l)
}
})
return t, nil
}
func (t *tun) Close() error {
if t.f != nil {
if err := t.f.Close(); err != nil {
return fmt.Errorf("error closing tun file: %w", err)
}
// t.f.Close should have handled it for us but let's be extra sure
_ = unix.Close(t.fd)
s, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_DGRAM, syscall.IPPROTO_IP)
if err != nil {
return err
}
defer syscall.Close(s)
ifr := ifreq{Name: t.deviceBytes()}
err = ioctl(uintptr(s), syscall.SIOCIFDESTROY, uintptr(unsafe.Pointer(&ifr)))
return err
}
return nil
}
View on GitHub (pinned to dd8f660c0a)
Solutions
- Ensure Close() is called only once per tun (use sync.Once or a closed flag)
- Check that no other code path closes the same *os.File concurrently
- Log the wrapped %w error to identify the underlying errno (e.g. EBADF)
Defensive patterns
Strategy: try-catch
Validate before calling
var closeOnce sync.Once
func safeClose(t *tun) error {
var err error
closeOnce.Do(func() { err = t.Close() })
return err
} Try / catch
if err := tun.Close(); err != nil {
var se *os.SyscallError
if errors.As(err, &se) && errors.Is(se.Err, os.ErrClosed) { return nil }
log.Printf("tun close: %v", err)
} Prevention
- Close the tun exactly once (sync.Once)
- Never close the underlying file from other goroutines
- Sequence shutdown: stop readers, then Close
When it happens
Trigger: Calling Close() on a tun whose t.f was already closed elsewhere (double Close), or a race where another goroutine closed the file, or an I/O error during close on the device.
Common situations: Application shutdown code calling Close() twice; concurrent readers/writers closing the tun; descriptors inherited or clobbered by exec.
Related errors
- failed to get syscall conn for tun: %w
- error closing tun file: %w
- newTunFromFd not supported in FreeBSD
- failed to set tun device as nonblocking: %w
- failed to create shutdown pipe: %w
AI-assisted analysis of slackhq/nebula@dd8f660c0a (2026-09-03).
Data as JSON: /api/errors/4e5b401d5c9371f7.
Report an issue: GitHub.