slackhq/nebula · error

failed to make inner read call for tun: %w

Error message

failed to make inner read call for tun: %w

What it means

The inner read performed inside the RawConn control function returned a non-zero syscall.Errno; Read() wraps it as 'failed to make inner read call for tun'. This is the actual errno from reading the /dev/tunN descriptor, e.g. EIO or EAGAIN handling surfaced by the library.

Source

Thrown at overlay/tun_netbsd.go:177

		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
func (t *tun) Write(from []byte) (int, error) {
	if len(from) <= 1 {
		return 0, syscall.EIO
	}

View on GitHub (pinned to dd8f660c0a)

Solutions

  1. Check the wrapped errno: EAGAIN/EWOULDBLOCK can be retried, EIO means recreate the device
  2. Recreate the TUN device if it was destroyed (ifconfig tunN create)
  3. Handle os.ErrClosed separately as graceful shutdown

Example fix

// before
if err != nil { log.Fatal(err) }
// after
var errno syscall.Errno
if errors.As(err, &errno) && errors.Is(errno, syscall.EAGAIN) { continue /* retry */ }
if err != nil { log.Fatal(err) }
Defensive patterns

Strategy: retry

Try / catch

n, err := tun.Read(buf)
var errno syscall.Errno
if errors.As(err, &errno) {
    switch {
    case errors.Is(errno, syscall.EAGAIN), errors.Is(errno, syscall.EINTR):
        continue // retry
    case errors.Is(errno, syscall.EIO):
        return recreateTun() // device gone
    }
}
if err != nil { return err }

Prevention

When it happens

Trigger: unix.Read on the tun fd inside rc.Read returns an errno such as EIO (device detached), EINTR, or EAGAIN that the code does not swallow, or a negative/failed read result.

Common situations: TUN device destroyed with ifconfig tunN destroy while reading; kernel driver errors; reading after the interface is down.

Related errors


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