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

  1. Ensure Close() is called only once per tun (use sync.Once or a closed flag)
  2. Check that no other code path closes the same *os.File concurrently
  3. 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

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


AI-assisted analysis of slackhq/nebula@dd8f660c0a (2026-09-03). Data as JSON: /api/errors/4e5b401d5c9371f7. Report an issue: GitHub.