XTLS/Xray-core · error
invalid xray.tun.fd: TUN device must use IFF_NO_PI
Error message
invalid xray.tun.fd: TUN device must use IFF_NO_PI
What it means
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.
Source
Thrown at proxy/tun/tun_linux.go:100
}
if fd < 3 {
return -1, nil, true, errors.New("invalid ", platform.TunFdKey, ": file descriptor must be >= 3")
}
ifr, err := unix.NewIfreq("")
if err != nil {
return -1, nil, true, err
}
if err = unix.IoctlIfreq(fd, unix.TUNGETIFF, ifr); err != nil {
return -1, nil, true, err
}
flags := ifr.Uint16()
if flags&unix.IFF_TUN == 0 {
return -1, nil, true, errors.New("invalid ", platform.TunFdKey, ": file descriptor is not a TUN device")
}
if flags&unix.IFF_NO_PI == 0 {
return -1, nil, true, errors.New("invalid ", platform.TunFdKey, ": TUN device must use IFF_NO_PI")
}
actualName := ifr.Name()
if expectedName != "" && actualName != expectedName {
return -1, nil, true, errors.New("invalid ", platform.TunFdKey, ": TUN device name ", actualName, " does not match configured name ", expectedName)
}
tunLink, err := netlink.LinkByName(actualName)
if err != nil {
return -1, nil, true, err
}
if err = unix.SetNonblock(fd, true); err != nil {
return -1, nil, true, err
}
return fd, tunLink, true, nil
}View on GitHub (pinned to 7d214f8b09)
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
Example fix
// before ifr.SetUint16(unix.IFF_TUN) unix.IoctlIfreq(fd, unix.TUNSETIFF, ifr) // after ifr.SetUint16(unix.IFF_TUN | unix.IFF_NO_PI) unix.IoctlIfreq(fd, unix.TUNSETIFF, ifr)
Defensive patterns
Strategy: validation
Validate before calling
func hasNoPi(fd int) bool {
ifr, _ := unix.NewIfreq("")
if err := unix.IoctlIfreq(fd, unix.TUNGETIFF, ifr); err != nil {
return false
}
return ifr.Uint16()&unix.IFF_NO_PI != 0
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- invalid xray.tun.fd: file descriptor is not a TUN device
- invalid xray.tun.fd: TUN device name {actualName} does not m
- invalid interface address {address}
- invalid system route {cidr}
- outbound interface cannot be the TUN interface
AI-assisted analysis of XTLS/Xray-core@7d214f8b09 (2026-08-15).
Data as JSON: /api/errors/9cf53cdf74d36083.
Report an issue: GitHub.