{"record":{"id":"67d165c62ce88b93","repo":"slackhq/nebula","slug":"failed-to-set-the-tun-fd-to-non-blocking-mode-w","errorCode":null,"errorMessage":"failed to set the tun fd to non-blocking mode: %w","messagePattern":"failed to set the tun fd to non-blocking mode: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"overlay/tun_ios.go","lineNumber":41,"sourceCode":")\n\ntype tun struct {\n\tio.ReadWriteCloser\n\tvpnNetworks []netip.Prefix\n\tRoutes      atomic.Pointer[[]Route]\n\trouteTree   atomic.Pointer[bart.Table[routing.Gateways]]\n\tl           *slog.Logger\n}\n\nfunc newTun(_ *config.C, _ *slog.Logger, _ []netip.Prefix, _ bool) (*tun, error) {\n\treturn nil, fmt.Errorf(\"newTun not supported in iOS\")\n}\n\nfunc newTunFromFd(c *config.C, l *slog.Logger, deviceFd int, vpnNetworks []netip.Prefix) (*tun, error) {\n\tif err := unix.SetNonblock(deviceFd, true); err != nil {\n\t\t// We own the fd from the moment it is handed to us, same as the reload error path below\n\t\t_ = unix.Close(deviceFd)\n\t\treturn nil, fmt.Errorf(\"failed to set the tun fd to non-blocking mode: %w\", err)\n\t}\n\n\tfile := os.NewFile(uintptr(deviceFd), \"/dev/tun\")\n\tt := &tun{\n\t\tvpnNetworks:     vpnNetworks,\n\t\tReadWriteCloser: &tunReadCloser{f: file},\n\t\tl:               l,\n\t}\n\n\terr := t.reload(c, true)\n\tif err != nil {\n\t\t_ = file.Close()\n\t\treturn nil, err\n\t}\n\n\tc.RegisterReloadCallback(func(c *config.C) {\n\t\terr := t.reload(c, false)\n\t\tif err != nil {","sourceCodeStart":23,"sourceCodeEnd":59,"githubUrl":"https://github.com/slackhq/nebula/blob/dd8f660c0ac37903ec4080ca4d3c861ba9342ceb/overlay/tun_ios.go#L23-L59","documentation":"newTunFromFd (overlay/tun_ios.go:41) receives a TUN file descriptor from the iOS NetworkExtension and must put it into non-blocking mode with unix.SetNonblock. This error wraps a failure of that call, aborting tun setup; the fd is closed before returning.","triggerScenarios":"unix.SetNonblock(deviceFd, true) fails, e.g. the fd is invalid (EBADF) because the NetworkExtension handed over a closed or wrong fd, or an underlying fcntl error on the inherited descriptor.","commonSituations":"Passing an fd that was already closed or duplicated incorrectly in the Packet Tunnel Provider extension; fd inheritance issues when the provider extension reconfigures the flow; passing a non-socket/non-file integer by mistake.","solutions":["Verify the fd value passed to newTunFromFd comes directly from the NEPacketTunnelProvider packetFlow and is still open.","Check that your extension code does not close or overwrite the fd before nebula takes ownership.","Inspect the wrapped errno (%w) to distinguish EBADF (bad fd) from other fcntl failures.","Ensure the fd is handed over once, exactly at tunnel startup, matching the ownership transfer expectation in the code."],"exampleFix":"// before\nfd := 0 // placeholder/wrong fd\n// after\nfd := int(packetFlow.ValueForKeyPath(\"fileDescriptor\")) // valid fd from packet flow\nif fd <= 0 { return errors.New(\"invalid tun fd\") }","handlingStrategy":"validation","validationCode":"if deviceFd <= 0 {\n\treturn errors.New(\"invalid tun file descriptor from NetworkExtension\")\n}\n// confirm fd is live before handing to newTunFromFd\nif _, err := unix.FcntlInt(uintptr(deviceFd), unix.F_GETFD, 0); err != nil {\n\treturn fmt.Errorf(\"tun fd not usable: %w\", err)\n}","typeGuard":"func validFd(fd int) bool {\n\t_, err := unix.FcntlInt(uintptr(fd), unix.F_GETFD, 0)\n\treturn err == nil\n}","tryCatchPattern":"t, err := newTunFromFd(c, l, deviceFd, vpnNetworks)\nif err != nil && strings.Contains(err.Error(), \"non-blocking mode\") {\n\t// fd was bad or already closed; reacquire from packetFlow before retry\n}","preventionTips":["Pass the fd straight from NEPacketTunnelProvider; never close or duplicate it first.","Validate fd > 0 before calling newTunFromFd.","Hand over ownership exactly once during tunnel startup."],"tags":["ios","tun","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"}