XTLS/Xray-core · error

invalid xray.tun.fd: TUN device name {actualName} does not m

Error message

invalid xray.tun.fd: TUN device name {actualName} does not match configured name {expectedName}

What it means

When a TUN device name is configured, Xray compares it against the actual name reported by TUNGETIFF (ifr.Name()). A mismatch means the fd belongs to a different TUN interface than the one named in the inbound config, which would make the subsequent netlink configuration (addresses, routes) target the wrong device.

Source

Thrown at proxy/tun/tun_linux.go:105

	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
}

// open the file that implements tun interface in the OS
func open(name string) (int, error) {
	fd, err := unix.Open("/dev/net/tun", unix.O_RDWR, 0)
	if err != nil {

View on GitHub (pinned to 7d214f8b09)

Solutions

  1. Align the configured TUN name with the actual interface name (check `ip link` / TUNGETIFF result)
  2. Pin the name when creating the device externally so it is deterministic (TUNSETIFF with the exact name)
  3. Omit the expected name in config if the actual name is not known ahead of time

Example fix

// before: config names tun0, fd is tun1
{ "name": "tun0", ... }  // fd actually belongs to tun1

// after
ip tuntap add mode tun name tun0  # create with fixed name, pass its fd
{ "name": "tun0", ... }
Defensive patterns

Strategy: validation

Validate before calling

// before handing over the fd, read its real name and reconcile with config
ifr, _ := unix.NewIfreq("")
_ = unix.IoctlIfreq(fd, unix.TUNGETIFF, ifr)
actual := ifr.Name()
if cfgName != "" && cfgName != actual {
	log.Fatalf("config name %q != actual tun name %q", cfgName, actual)
}

Prevention

When it happens

Trigger: Configuring name "tun0" in the tun inbound while xray.tun.fd refers to tun1; a TUN manager that renames or numbers devices dynamically; reusing a config after the OS assigned a different free tunN name.

Common situations: Persisted configs paired with an external fd provider that opens whichever tunN is free; Android VpnService where the interface name is chosen by the framework; typo in the configured name.

Related errors


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