{"record":{"id":"6207787d23fc5d28","repo":"XTLS/Xray-core","slug":"invalid-xray-tun-fd-file-descriptor-is-not-a-tun","errorCode":null,"errorMessage":"invalid xray.tun.fd: file descriptor is not a TUN device","messagePattern":"invalid xray\\.tun\\.fd: file descriptor is not a TUN device","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"proxy/tun/tun_linux.go","lineNumber":97,"sourceCode":"\tfd, err := strconv.Atoi(fdStr)\n\tif err != nil {\n\t\treturn -1, nil, true, errors.New(\"invalid \", platform.TunFdKey).Base(err)\n\t}\n\tif fd < 3 {\n\t\treturn -1, nil, true, errors.New(\"invalid \", platform.TunFdKey, \": file descriptor must be >= 3\")\n\t}\n\n\tifr, err := unix.NewIfreq(\"\")\n\tif err != nil {\n\t\treturn -1, nil, true, err\n\t}\n\tif err = unix.IoctlIfreq(fd, unix.TUNGETIFF, ifr); err != nil {\n\t\treturn -1, nil, true, err\n\t}\n\n\tflags := ifr.Uint16()\n\tif flags&unix.IFF_TUN == 0 {\n\t\treturn -1, nil, true, errors.New(\"invalid \", platform.TunFdKey, \": file descriptor is not a TUN device\")\n\t}\n\tif flags&unix.IFF_NO_PI == 0 {\n\t\treturn -1, nil, true, errors.New(\"invalid \", platform.TunFdKey, \": TUN device must use IFF_NO_PI\")\n\t}\n\n\tactualName := ifr.Name()\n\tif expectedName != \"\" && actualName != expectedName {\n\t\treturn -1, nil, true, errors.New(\"invalid \", platform.TunFdKey, \": TUN device name \", actualName, \" does not match configured name \", expectedName)\n\t}\n\n\ttunLink, err := netlink.LinkByName(actualName)\n\tif err != nil {\n\t\treturn -1, nil, true, err\n\t}\n\n\tif err = unix.SetNonblock(fd, true); err != nil {\n\t\treturn -1, nil, true, err\n\t}","sourceCodeStart":79,"sourceCodeEnd":115,"githubUrl":"https://github.com/XTLS/Xray-core/blob/7d214f8b094f75322fa3990f8aadad1c912f24f5/proxy/tun/tun_linux.go#L79-L115","documentation":"Xray validates a user-supplied TUN file descriptor (passed via the xray.tun.fd platform setting) by issuing the TUNGETIFF ioctl and checking the returned flags. If the IFF_TUN bit is not set, the fd is not a TUN interface (it may be a socket, pipe, or regular file), so startup aborts. This is a configuration/integration guard: the fd must come from opening /dev/net/tun and binding it with TUNSETIFF.","triggerScenarios":"Calling Xray with xray.tun.fd pointing at a UDP/TCP socket, an fd inherited from a parent process that is not /dev/net/tun, or a raw IP socket. Also opening /dev/net/tun but never calling TUNSETIFF before handing the fd over.","commonSituations":"Integration with an external TUN manager (e.g. a supervisor script or another VPN stack) that passes the wrong fd number; shell scripts that dup fds; Android/iOS handoff where the fd is actually a VpnService socket rather than a TUN; stale fd numbers after process restart.","solutions":["Verify the fd with a TUNGETIFF ioctl before passing it to Xray (see validationCode)","Create the TUN yourself via open(\"/dev/net/tun\", O_RDWR) + ioctl(TUNSETIFF, IFF_TUN|IFF_NO_PI) and pass that fd","Remove xray.tun.fd entirely and let Xray create and name the TUN device itself via the tun inbound config","Double-check the fd number actually corresponds to /dev/net/tun in /proc/<pid>/fd (it should be a symlink to /dev/net/tun)"],"exampleFix":"// before: passing an arbitrary fd\nexport XRAY_TUN_FD=5  // fd 5 is a UDP socket\n\n// after: create a real TUN fd first\nfd, _ := unix.Open(\"/dev/net/tun\", unix.O_RDWR, 0)\nifr, _ := unix.NewIfreq(\"tun0\")\nifr.SetUint16(unix.IFF_TUN | unix.IFF_NO_PI)\n_ = unix.IoctlIfreq(fd, unix.TUNSETIFF, ifr)\n// now export/hand over this fd","handlingStrategy":"validation","validationCode":"func isTunFD(fd int) bool {\n\tifr, err := unix.NewIfreq(\"\")\n\tif err != nil {\n\t\treturn false\n\t}\n\tif err := unix.IoctlIfreq(fd, unix.TUNGETIFF, ifr); err != nil {\n\t\treturn false\n\t}\n\treturn ifr.Uint16()&unix.IFF_TUN != 0\n}\n\n// before starting xray with xray.tun.fd:\nif !isTunFD(myFd) {\n\tlog.Fatal(\"fd is not a TUN device\")\n}","typeGuard":null,"tryCatchPattern":"if err := tun.Start(); err != nil {\n\tif strings.Contains(err.Error(), \"not a TUN device\") {\n\t\t// fix the fd source (open /dev/net/tun + TUNSETIFF) and retry once\n\t}\n}","preventionTips":["Always create the fd via open(\"/dev/net/tun\") + TUNSETIFF in the same process that hands it over","Confirm /proc/<pid>/fd/<n> links to /dev/net/tun before export","Prefer letting xray create the TUN itself unless fd passing is required"],"tags":["tun","linux","ioctl","config","fd"],"backgroundTag":null,"analyzedSha":"7d214f8b094f75322fa3990f8aadad1c912f24f5","analyzedAt":"2026-08-15T14:26:24.325Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}