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

  1. Re-create/open the TUN device with flags IFF_TUN|IFF_NO_PI in the TUNSETIFF ioctl
  2. 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)
  3. 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

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


AI-assisted analysis of XTLS/Xray-core@7d214f8b09 (2026-08-15). Data as JSON: /api/errors/9cf53cdf74d36083. Report an issue: GitHub.