From 65f6f0a43b814b2980740e119aa96f1adcb82e9a Mon Sep 17 00:00:00 2001 From: yiguodev <147401898+yiguodev@users.noreply.github.com> Date: Sat, 4 Jul 2026 10:25:15 +0800 Subject: [PATCH] TUN inbound: Refine `gateway` and `autoSystemRoutingTable` on Linux (#6398) https://github.com/XTLS/Xray-core/pull/6398#issuecomment-4880261591 --- proxy/tun/README.md | 10 ++++----- proxy/tun/tun_linux.go | 46 +++++++++++++++++++++++++++++++++++++++--- 2 files changed, 48 insertions(+), 8 deletions(-) diff --git a/proxy/tun/README.md b/proxy/tun/README.md index 2b9e0a428..8d4421ccf 100644 --- a/proxy/tun/README.md +++ b/proxy/tun/README.md @@ -14,11 +14,11 @@ Plainly enabling it in the config probably will result nothing, or lock your rou ## DETAILS -Current implementation does not contain options to configure network level addresses, routing or rules. -Enabling the feature will result only tun interface up, and that's it. \ -This is explicit decision, significantly simplifying implementation, and allowing any number of custom configurations, consumers could come up with. Network interface is OS level entity, and OS is what should manage it. \ -Working configuration, is tun enabled in Xray config with specific name (e.g. xray0), and OS level configuration to manage "xray0" interface, applying routing and rules on interface up. -This way consistency of system level routing and rules is ensured from single place of responsibility - the OS itself. \ +By default, enabling the feature will only bring the tun interface up. \ +When configured explicitly, Windows and Linux can also apply interface addresses from `gateway` and on-link routes from `autoSystemRoutingTable`. +Linux does not configure system DNS from the `dns` field; system DNS remains managed by the OS or distribution-specific network services. \ +For more advanced routing policies or rules, OS level configuration can still manage the named interface (e.g. xray0) when it appears. +This keeps complex system level routing and rules in a single place of responsibility - the OS itself. \ Examples of how to achieve this on a simple Linux system (Ubuntu with systemd-networkd) can be found at the end of this README. Due to this inbound not actually being a proxy, the configuration ignore required listen and port options, and never listen on any port. \ diff --git a/proxy/tun/tun_linux.go b/proxy/tun/tun_linux.go index 110809549..b2c5d35a1 100644 --- a/proxy/tun/tun_linux.go +++ b/proxy/tun/tun_linux.go @@ -26,9 +26,10 @@ type LinuxTun struct { options *Config ownsTun bool - systemRoutes []netlink.Route - routeMonitorStop chan struct{} - routeMonitorOnce sync.Once + interfaceAddresses []netlink.Addr + systemRoutes []netlink.Route + routeMonitorStop chan struct{} + routeMonitorOnce sync.Once } // LinuxTun implements Tun @@ -172,7 +173,14 @@ func (t *LinuxTun) Start() error { return err } + if err := t.setInterfaceAddresses(); err != nil { + _ = netlink.LinkSetDown(t.tunLink) + return err + } + if err := t.setSystemRoutes(); err != nil { + _ = t.unsetInterfaceAddresses() + _ = netlink.LinkSetDown(t.tunLink) return err } @@ -193,6 +201,7 @@ func (t *LinuxTun) Close() error { }) _ = t.unsetSystemRoutes() + _ = t.unsetInterfaceAddresses() if t.ownsTun { _ = netlink.LinkSetDown(t.tunLink) @@ -223,6 +232,37 @@ func setinterface(network, address string, fd uintptr, iface *net.Interface) err return unix.BindToDevice(int(fd), iface.Name) } +func (t *LinuxTun) setInterfaceAddresses() error { + if len(t.options.Gateway) == 0 { + return nil + } + for _, address := range t.options.Gateway { + addr, err := netlink.ParseAddr(address) + if err != nil { + _ = t.unsetInterfaceAddresses() + return errors.New("invalid interface address ", address).Base(err) + } + if err := netlink.AddrAdd(t.tunLink, addr); err != nil { + _ = t.unsetInterfaceAddresses() + return errors.New("failed to add interface address ", address).Base(err) + } + t.interfaceAddresses = append(t.interfaceAddresses, *addr) + } + return nil +} + +func (t *LinuxTun) unsetInterfaceAddresses() error { + var errs []error + for i := len(t.interfaceAddresses) - 1; i >= 0; i-- { + address := t.interfaceAddresses[i] + if err := netlink.AddrDel(t.tunLink, &address); err != nil { + errs = append(errs, errors.New("failed to delete interface address ", address.String()).Base(err)) + } + } + t.interfaceAddresses = nil + return errors.Combine(errs...) +} + func (t *LinuxTun) setSystemRoutes() error { if len(t.options.AutoSystemRoutingTable) == 0 { return nil