slackhq/nebula · error

failed to make read call for tun: %w

Error message

failed to make read call for tun: %w

What it means

Read() uses the RawConn's Read method to run the syscall read; if that control function reports an error (other than the recognized closed-file conditions), it is wrapped as 'failed to make read call for tun'. This indicates the outer read invocation itself failed (e.g. EBADF, EINTR patterns surfaced here, or Go's poll error text).

Source

Thrown at overlay/tun_netbsd.go:173

		iovecs := []syscall.Iovec{
			{&head[0], 4},
			{&to[0], uint64(len(to))},
		}

		n, _, errno = syscall.Syscall(syscall.SYS_READV, fd, uintptr(unsafe.Pointer(&iovecs[0])), uintptr(2))
		if errno.Temporary() {
			// We got an EAGAIN, EINTR, or EWOULDBLOCK, go again
			return false
		}
		return true
	})
	if err != nil {
		if err == syscall.EBADF || err.Error() == "use of closed file" {
			// Go doesn't export poll.ErrFileClosing but happily reports it to us so here we are
			// https://github.com/golang/go/blob/master/src/internal/poll/fd_poll_runtime.go#L121
			return 0, os.ErrClosed
		}
		return 0, fmt.Errorf("failed to make read call for tun: %w", err)
	}

	if errno != 0 {
		return 0, fmt.Errorf("failed to make inner read call for tun: %w", errno)
	}

	// fix bytes read number to exclude header
	bytesRead := int(n)
	if bytesRead < 0 {
		return bytesRead, nil
	} else if bytesRead < 4 {
		return 0, nil
	} else {
		return bytesRead - 4, nil
	}
}

// Write is only valid for single threaded use

View on GitHub (pinned to dd8f660c0a)

Solutions

  1. Inspect the wrapped errno (%w) to identify the cause; handle EINTR by retrying
  2. Ensure Close() happens after all readers exit
  3. Treat os.ErrClosed (returned for EBADF/closed-file) as expected during shutdown and re-open/re-create the tun if persistent

Example fix

// before
n, err := tun.Read(buf)
if err != nil { return err }
// after
n, err := tun.Read(buf)
if errors.Is(err, os.ErrClosed) { return nil } // expected shutdown
if err != nil { return err }
Defensive patterns

Strategy: retry

Try / catch

n, err := tun.Read(buf)
if err != nil {
    if errors.Is(err, os.ErrClosed) { return nil }
    var errno syscall.Errno
    if errors.As(err, &errno) && errors.Is(errno, syscall.EINTR) { continue }
    return fmt.Errorf("tun read: %w", err)
}

Prevention

When it happens

Trigger: The tun fd is invalid at read time (after close/finalizer), the RawConn read fn returns a non-nil error that is not EBADF or 'use of closed file', or poll errors other than file-closing.

Common situations: Read after Close (the specific EBADF/closed cases are translated to os.ErrClosed, so this usually means a different errno like EINTR cascades or an unexpected descriptor state).

Related errors


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