{"record":{"id":"a2b2275e87ce467c","repo":"slackhq/nebula","slug":"failed-to-make-inner-read-call-for-tun-w","errorCode":null,"errorMessage":"failed to make inner read call for tun: %w","messagePattern":"failed to make inner read call for tun: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"overlay/tun_netbsd.go","lineNumber":177,"sourceCode":"\n\t\tn, _, errno = syscall.Syscall(syscall.SYS_READV, fd, uintptr(unsafe.Pointer(&iovecs[0])), uintptr(2))\n\t\tif errno.Temporary() {\n\t\t\t// We got an EAGAIN, EINTR, or EWOULDBLOCK, go again\n\t\t\treturn false\n\t\t}\n\t\treturn true\n\t})\n\tif err != nil {\n\t\tif err == syscall.EBADF || err.Error() == \"use of closed file\" {\n\t\t\t// Go doesn't export poll.ErrFileClosing but happily reports it to us so here we are\n\t\t\t// https://github.com/golang/go/blob/master/src/internal/poll/fd_poll_runtime.go#L121\n\t\t\treturn 0, os.ErrClosed\n\t\t}\n\t\treturn 0, fmt.Errorf(\"failed to make read call for tun: %w\", err)\n\t}\n\n\tif errno != 0 {\n\t\treturn 0, fmt.Errorf(\"failed to make inner read call for tun: %w\", errno)\n\t}\n\n\t// fix bytes read number to exclude header\n\tbytesRead := int(n)\n\tif bytesRead < 0 {\n\t\treturn bytesRead, nil\n\t} else if bytesRead < 4 {\n\t\treturn 0, nil\n\t} else {\n\t\treturn bytesRead - 4, nil\n\t}\n}\n\n// Write is only valid for single threaded use\nfunc (t *tun) Write(from []byte) (int, error) {\n\tif len(from) <= 1 {\n\t\treturn 0, syscall.EIO\n\t}","sourceCodeStart":159,"sourceCodeEnd":195,"githubUrl":"https://github.com/slackhq/nebula/blob/dd8f660c0ac37903ec4080ca4d3c861ba9342ceb/overlay/tun_netbsd.go#L159-L195","documentation":"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.","triggerScenarios":"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.","commonSituations":"TUN device destroyed with ifconfig tunN destroy while reading; kernel driver errors; reading after the interface is down.","solutions":["Check the wrapped errno: EAGAIN/EWOULDBLOCK can be retried, EIO means recreate the device","Recreate the TUN device if it was destroyed (ifconfig tunN create)","Handle os.ErrClosed separately as graceful shutdown"],"exampleFix":"// before\nif err != nil { log.Fatal(err) }\n// after\nvar errno syscall.Errno\nif errors.As(err, &errno) && errors.Is(errno, syscall.EAGAIN) { continue /* retry */ }\nif err != nil { log.Fatal(err) }","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"n, err := tun.Read(buf)\nvar errno syscall.Errno\nif errors.As(err, &errno) {\n    switch {\n    case errors.Is(errno, syscall.EAGAIN), errors.Is(errno, syscall.EINTR):\n        continue // retry\n    case errors.Is(errno, syscall.EIO):\n        return recreateTun() // device gone\n    }\n}\nif err != nil { return err }","preventionTips":["Monitor the interface with ifconfig tunN before assuming code bugs","Recreate the device on persistent EIO","Differentiate transient (EAGAIN/EINTR) from fatal errnos"],"tags":["netbsd","tun","read","errno","syscall"],"backgroundTag":"tun-read-syscall-failed","analyzedSha":"dd8f660c0ac37903ec4080ca4d3c861ba9342ceb","analyzedAt":"2026-09-03T11:13:55.444Z","contentChangedAt":"2026-09-03T11:13:55.444Z","schemaVersion":2},"datasetVersion":"2026-09-10T17:17:09.494Z"}