{"record":{"id":"6b9a4d705530049b","repo":"slackhq/nebula","slug":"failed-to-set-tun-device-as-nonblocking-w","errorCode":null,"errorMessage":"failed to set tun device as nonblocking: %w","messagePattern":"failed to set tun device as nonblocking: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"overlay/tun_freebsd.go","lineNumber":303,"sourceCode":"func newTun(c *config.C, l *slog.Logger, vpnNetworks []netip.Prefix, _ bool) (*tun, error) {\n\t// Try to open existing tun device\n\tvar fd int\n\tvar err error\n\tdeviceName := c.GetString(\"tun.dev\", \"\")\n\tif deviceName != \"\" {\n\t\tfd, err = unix.Open(\"/dev/\"+deviceName, os.O_RDWR, 0)\n\t}\n\tif errors.Is(err, fs.ErrNotExist) || deviceName == \"\" {\n\t\t// If the device doesn't already exist, request a new one and rename it\n\t\tfd, err = unix.Open(\"/dev/tun\", os.O_RDWR, 0)\n\t}\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif err = unix.SetNonblock(fd, true); err != nil {\n\t\t_ = unix.Close(fd)\n\t\treturn nil, fmt.Errorf(\"failed to set tun device as nonblocking: %w\", err)\n\t}\n\n\t// Shutdown pipe lets Close wake any reader/writer blocked in Poll.\n\tvar pipeFds [2]int\n\tif err = unix.Pipe2(pipeFds[:], unix.O_CLOEXEC|unix.O_NONBLOCK); err != nil {\n\t\t_ = unix.Close(fd)\n\t\treturn nil, fmt.Errorf(\"failed to create shutdown pipe: %w\", err)\n\t}\n\tshutdownR, shutdownW := pipeFds[0], pipeFds[1]\n\n\tcloseOnErr := true\n\tdefer func() {\n\t\tif closeOnErr {\n\t\t\t_ = unix.Close(fd)\n\t\t\t_ = unix.Close(shutdownR)\n\t\t\t_ = unix.Close(shutdownW)\n\t\t}\n\t}()","sourceCodeStart":285,"sourceCodeEnd":321,"githubUrl":"https://github.com/slackhq/nebula/blob/dd8f660c0ac37903ec4080ca4d3c861ba9342ceb/overlay/tun_freebsd.go#L285-L321","documentation":"After opening the FreeBSD tun device fd, newTun puts it into non-blocking mode with unix.SetNonblock so reads/writes integrate with Poll. If that fcntl call fails, the fd is closed and this wrapped error is returned. The wrapped inner error (from syscall) gives the actual cause.","triggerScenarios":"unix.SetNonblock(fd, true) returns an error immediately after opening the tun device — typically an EBADF/EBUSY from the freshly opened /dev/tun fd, or exhaustion of descriptors.","commonSituations":"File descriptor table exhaustion under heavy load; opening a device path that yielded an invalid fd; sandbox/container environments restricting fcntl on device nodes; races where another process closed the fd.","solutions":["Check the wrapped errno (%w target) — EBADF means the fd from the open was invalid; EMFILE/ENFILE means fd exhaustion (raise ulimit -n)","Retry tun device creation once the fd limit issue is resolved","Verify the process has permission to open /dev/tun and that the device node exists","Ensure no other goroutine closed the fd concurrently before SetNonblock runs"],"exampleFix":"// before\nif err = unix.SetNonblock(fd, true); err != nil {\n    _ = unix.Close(fd)\n    return nil, fmt.Errorf(\"failed to set tun device as nonblocking: %w\", err)\n}\n// after (surface fd limit guidance)\nif err = unix.SetNonblock(fd, true); err != nil {\n    _ = unix.Close(fd)\n    var rl syscall.Rlimit\n    syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rl)\n    return nil, fmt.Errorf(\"failed to set tun device as nonblocking (fd=%d, nofile cur=%d): %w\", fd, rl.Cur, err)\n}","handlingStrategy":"retry","validationCode":"var rl syscall.Rlimit\nif err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rl); err == nil && rl.Cur < 1024 {\n    rl.Cur = 4096\n    _ = syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rl)\n}","typeGuard":null,"tryCatchPattern":"tunDev, err := newTun(cfg, log, prefixes, false)\nif err != nil && strings.Contains(err.Error(), \"set tun device as nonblocking\") {\n    // fd invalid or limit hit; log wrapped errno, free fds, retry once\n    log.Error(\"tun nonblock setup failed\", \"err\", err)\n    time.Sleep(500 * time.Millisecond)\n    tunDev, err = newTun(cfg, log, prefixes, false)\n}","preventionTips":["Monitor and raise RLIMIT_NOFILE for long-running daemons","Ensure the fd from device open is used immediately, not closed concurrently","Check process permissions on /dev/tun before construction","Log the wrapped errno to distinguish EBADF from EMFILE"],"tags":["freebsd","tun","nonblocking","syscall","file-descriptor"],"backgroundTag":"fcntl-nonblock-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"}