{"record":{"id":"9cf53cdf74d36083","repo":"XTLS/Xray-core","slug":"invalid-xray-tun-fd-tun-device-must-use-iff-no-pi","errorCode":null,"errorMessage":"invalid xray.tun.fd: TUN device must use IFF_NO_PI","messagePattern":"invalid xray\\.tun\\.fd: TUN device must use IFF_NO_PI","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"proxy/tun/tun_linux.go","lineNumber":100,"sourceCode":"\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}\n\n\treturn fd, tunLink, true, nil\n}","sourceCodeStart":82,"sourceCodeEnd":118,"githubUrl":"https://github.com/XTLS/Xray-core/blob/7d214f8b094f75322fa3990f8aadad1c912f24f5/proxy/tun/tun_linux.go#L82-L118","documentation":"Xray requires TUN fds to be created with the IFF_NO_PI flag, meaning the kernel must not prepend the 4-byte packet information header to each read/write. After TUNGETIFF, if flags do not contain IFF_NO_PI, this error is returned because Xray's packet framing assumes raw IP packets with no metadata prefix.","triggerScenarios":"Creating the TUN device externally (ip tuntap, a custom C/Go/Rust program, or another tool) without IFF_NO_PI, then passing its fd via xray.tun.fd. Note: `ip tuntap add` creates devices with IFF_NO_PI by default, but custom openers often forget the flag.","commonSituations":"Hand-written TUN setup code that only passes IFF_TUN; switching from a library (e.g. wireguard-go's tun crate defaults) that enables protocol info; migrating a setup that worked with another VPN client tolerant of the PI header.","solutions":["Re-create/open the TUN device with flags IFF_TUN|IFF_NO_PI in the TUNSETIFF ioctl","Delete and re-add the persistent device: `ip tuntap del mode tun name tun0` then `ip tuntap add mode tun name tun0` (mode tun defaults to no_pi)","Let Xray create the TUN itself instead of supplying xray.tun.fd"],"exampleFix":"// before\nifr.SetUint16(unix.IFF_TUN)\nunix.IoctlIfreq(fd, unix.TUNSETIFF, ifr)\n\n// after\nifr.SetUint16(unix.IFF_TUN | unix.IFF_NO_PI)\nunix.IoctlIfreq(fd, unix.TUNSETIFF, ifr)","handlingStrategy":"validation","validationCode":"func hasNoPi(fd int) bool {\n\tifr, _ := unix.NewIfreq(\"\")\n\tif err := unix.IoctlIfreq(fd, unix.TUNGETIFF, ifr); err != nil {\n\t\treturn false\n\t}\n\treturn ifr.Uint16()&unix.IFF_NO_PI != 0\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always pass IFF_TUN|IFF_NO_PI together in TUNSETIFF","Never assume an externally created device has NO_PI; verify with TUNGETIFF","Document the flag requirement for any fd provider in your setup scripts"],"tags":["tun","linux","ioctl","config","iff-no-pi"],"backgroundTag":null,"analyzedSha":"7d214f8b094f75322fa3990f8aadad1c912f24f5","analyzedAt":"2026-08-15T14:26:24.325Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}