summaryrefslogtreecommitdiff
path: root/docs/research/linux-tun-xray.md
diff options
context:
space:
mode:
authorAndrew Guschin <guschin@altlinux.org>2026-10-30 01:59:28 +0400
committerAndrew Guschin <guschin@altlinux.org>2026-10-30 01:59:28 +0400
commitda2e0eef42d5d611b0125de17e467dbddf3d8d71 (patch)
treebdbbf6b7190157fc2325ccb7dd42cf6bb62cdcd1 /docs/research/linux-tun-xray.md
parent170f05697812b2f8b29226b4aa07b1b8db38873e (diff)
Document subscription formats and ALT runtime integrationresearch
- Add the Roentgen domain glossary - Record research on 3x-ui subscriptions, runtime integration, and defaults
Diffstat (limited to 'docs/research/linux-tun-xray.md')
-rw-r--r--docs/research/linux-tun-xray.md132
1 files changed, 132 insertions, 0 deletions
diff --git a/docs/research/linux-tun-xray.md b/docs/research/linux-tun-xray.md
new file mode 100644
index 0000000..eb59d4e
--- /dev/null
+++ b/docs/research/linux-tun-xray.md
@@ -0,0 +1,132 @@
+# How Roentgen can route Linux host traffic through Xray TUN
+
+Researched 2026-10-29. Planning only. No application code or host network changes were made. Sources are official project documentation, release metadata, and source code. Recommendations below are design inferences, not results of a live networking experiment.
+
+## Recommendation
+
+Use native Xray-core TUN as the preferred first-release backend. A companion is **not required** by current Xray. Native TUN shipped in v26.1.23; the latest official release observed is v26.10.29, published at 08:43:27 UTC on the research date. The v26.10.29 tag resolves to `0afec66085a85191e9dd2a4a6646c7c9a94688d1`, which was also the inspected main revision. Pin and validate that release for a prototype rather than assuming every version with TUN has today's automation. The original release explicitly required manual routes. [First TUN release](https://github.com/XTLS/Xray-core/releases/tag/v26.1.23), [current release](https://github.com/XTLS/Xray-core/releases/tag/v26.10.29), [official release API](https://api.github.com/repos/XTLS/Xray-core/releases/latest), [tag reference](https://api.github.com/repos/XTLS/Xray-core/git/ref/tags/v26.10.29).
+
+For a small supported platform set, use one Xray process plus a Roentgen system service that owns activation and recovery. Prefer Roentgen-managed policy routing over blindly installing Xray's automatic default routes when SSH access must survive. Restrict the initial platform to Linux hosts with systemd, working TUN, and a known DNS manager. Support for other init systems or resolver arrangements is a separate packaging decision.
+
+The user requirements remain distinct: adding or updating a 3x-ui subscription stores configurations; adding a subscription selects its first configuration if one exists; selection does not enable traffic capture. Whether that means the first received entry or the first usable connection remains a product decision. Only explicit proxy-on activates TUN. Proxy-off restores ordinary networking. Closing the terminal or SSH client leaves an already enabled system service running. Reboot auto-start and failure behavior still need a product decision.
+
+## What current Xray actually provides
+
+The official inbound is `protocol: "tun"`. It accepts traffic through a network interface rather than a listening proxy port. The normal distribution imports the TUN package. Its current stack supports TCP, UDP, IPv4, and IPv6. This removes the historical need to put tun2socks or sing-box in front of Xray. [TUN documentation](https://xtls.github.io/en/config/inbounds/tun.html), [distribution imports](https://github.com/XTLS/Xray-core/blob/v26.10.29/main/distro/all/all.go), [stack implementation](https://github.com/XTLS/Xray-core/blob/v26.10.29/proxy/tun/stack.go).
+
+Native TUN is still a proxy conversion, not an arbitrary IP VPN. ICMP echo replies are generated locally; other ICMP types are ignored. A successful TCP handshake or ping therefore does not prove that the remote destination is reachable. Define "all traffic" as host TCP/UDP through TUN, with necessary management and link-control exceptions. If arbitrary IP protocols, real remote ICMP, or transparent inbound port forwarding are required, this architecture does not satisfy that meaning. [Pinned TUN limitations](https://github.com/XTLS/Xray-core/blob/v26.10.29/proxy/tun/README.md#limitation).
+
+On Linux, `gateway` assigns interface prefixes. `autoSystemRoutingTable` adds destination routes; `autoOutboundsInterface` binds Xray-created sockets away from TUN and defaults to `"auto"` when automatic routes are supplied. An explicit empty value disables that default. The `dns` array in the TUN settings does not configure Linux DNS. `autoSystemDnsToGateway` is a separate, opt-in Linux/FreeBSD field present in the current source but absent from the English inbound page's field list. Source must resolve this documentation gap. [Pinned config parser](https://github.com/XTLS/Xray-core/blob/v26.10.29/infra/conf/tun.go), [inbound field documentation](https://xtls.github.io/en/config/inbounds/tun.html).
+
+## Minimal architecture options
+
+| Option | Data path | Work Roentgen still owns | Assessment |
+| --- | --- | --- | --- |
+| Native Xray with automatic setup | Host TUN enters Xray directly, then the selected outbound | SSH exceptions, DNS policy, preflight, supervised lifecycle and recovery | Fewest processes. Suitable for a constrained single-uplink host after route validation. |
+| Native Xray with managed policy routes | Same data path; Roentgen owns route tables, rules and exceptions | More Linux integration, but one owner for host routing and cleanup | Recommended for SSH-safe first release. Keep Xray's automatic route list empty and configure loop prevention explicitly. |
+| tun2socks plus Xray | TUN enters tun2socks, then a loopback SOCKS5 inbound in Xray | Route management, resolver integration, loop prevention for both processes, coupled lifecycle | Useful compatibility option for older Xray. No current requirement justifies the extra process. |
+| sing-box plus Xray | sing-box TUN and routing feed Xray through loopback SOCKS5 | Two configurations, both processes' bypass paths, SSH and crash recovery | Viable if Roentgen deliberately adopts sing-box's Linux integration. Not needed merely to obtain TUN. |
+
+These are architecture judgments. Companion capabilities are verified below; no throughput comparison was measured.
+
+### tun2socks
+
+The current observed release is v2.7.0. The official project supports TCP/UDP and IPv4/IPv6 through its userspace stack. Its SOCKS5 source implements UDP ASSOCIATE; a loopback TCP SOCKS5 endpoint is suitable, whereas its Unix-domain SOCKS mode does not support UDP. Xray's SOCKS inbound defaults UDP off, so this option requires `udp: true`. Do not replace SOCKS5 with HTTP CONNECT and assume UDP survives. [Release](https://github.com/xjasonlyu/tun2socks/releases/tag/v2.7.0), [project capabilities](https://github.com/xjasonlyu/tun2socks), [pinned SOCKS5 implementation](https://github.com/xjasonlyu/tun2socks/blob/v2.7.0/proxy/socks5/socks5.go), [Xray SOCKS settings](https://xtls.github.io/en/config/inbounds/socks.html).
+
+tun2socks explicitly leaves DNS setup to the user. Its Linux example creates a TUN and changes routes manually; the documentation warns that a local proxy can loop. Binding tun2socks's SOCKS connection does not bind the separate Xray process's remote sockets. Both must have an escape path, while loopback remains reachable. The example's persistent `ip tuntap add` device also requires explicit teardown after a crash. Do not copy its default-route deletion into an SSH-controlled application. [DNS responsibilities](https://github.com/xjasonlyu/tun2socks/wiki/DNS-Configuration), [Linux example and loop warning](https://github.com/xjasonlyu/tun2socks/wiki/Examples).
+
+### sing-box
+
+The current observed release is v1.14.4. Its TUN supports dual-stack addresses, automatic routes, DNS integration, and Linux nftables-based `auto_redirect`. The docs recommend the latter for Docker coexistence. DNS mode fields are marked since 1.14.0; the live page also includes fields marked since 1.15.0, so do not copy the entire current example into a 1.14.4 config. [Release](https://github.com/SagerNet/sing-box/releases/tag/v1.14.4), [TUN options and version annotations](https://sing-box.sagernet.org/configuration/inbound/tun/).
+
+Its SOCKS outbound supports TCP and UDP. `route.auto_detect_interface`, `route.default_interface`, or outbound binding prevents its own loops, but Roentgen must separately protect Xray's uplink sockets. DNS interception and native resolver configuration still have platform limits. This option adds nftables marks, table/rule identifiers, and potentially NFQUEUE requirements to the compatibility contract. It should be chosen for those routing capabilities after validation, not treated as an invisible packet adapter. [SOCKS outbound](https://sing-box.sagernet.org/configuration/outbound/socks/), [route options](https://sing-box.sagernet.org/configuration/route/), [Linux TUN integration](https://sing-box.sagernet.org/configuration/inbound/tun/).
+
+## Routing, endpoint loops, and SSH
+
+Current Xray Linux automatic routes are ordinary destination routes with metric 1. The code does not create a separate policy table or SSH exception. Existing more-specific LAN routes can bypass a default route; an existing lower-metric default can also defeat capture. Automatic uplink selection prefers an IPv4 default before IPv6 and chooses one interface. It watches route/link changes but is not a general policy-routing or multi-uplink controller. [Pinned Linux routing source](https://github.com/XTLS/Xray-core/blob/v26.10.29/proxy/tun/tun_linux.go).
+
+Xray's dial controller binds non-loopback sockets to the selected interface and returns an error if binding fails. It covers Xray's own dials, not sshd's reply packets. A proxy endpoint specified by a hostname also needs an independent bootstrap path. Merely exempting one currently resolved IP is fragile when DNS rotates, multiple addresses exist, or transport settings name additional endpoints. [Pinned dial controller](https://github.com/XTLS/Xray-core/blob/v26.10.29/proxy/tun/handler.go).
+
+Recommended routing design, inferred from Linux policy-routing and connection-tracking facilities:
+
+1. Leave the existing main-table uplink routes in place. Allocate collision-checked Roentgen table, rule priorities, and mark bits. Keep the local-table lookup ahead of capture.
+2. Install higher-priority exceptions for Xray's dedicated service UID or socket marks, plus management reply traffic. Then direct remaining eligible host traffic into a TUN table for both address families. Never exempt all root traffic.
+3. Preserve the current SSH peer's pre-enable route before capture. For robust handling of new connections, mark inbound SSH connections and restore the connection mark on replies before policy route selection. Match the actual listening addresses/ports and incoming path, including custom SSH ports. `SSH_CONNECTION` can seed the current peer but is not a complete service-wide policy.
+4. Preserve required loopback, on-link gateway/control traffic, and explicitly supported LAN paths. Show these exceptions to the operator. They are necessary system behavior, even while custom domain/subnet rules remain deferred.
+
+Linux rules support destination/source, UID ranges, marks, and port selectors. nftables provides connection tracking marks and route-type output chains. The exact priorities, mark masks, and ingress classification need an implementation design and isolated network tests; this report does not prescribe a ready-to-run firewall script. [ip-rule manual](https://man7.org/linux/man-pages/man8/ip-rule.8.html), [nftables manual](https://netfilter.org/projects/nftables/manpage.html).
+
+Preserving only packets with destination port 22 does not preserve sshd replies, whose destination is the client's ephemeral port. Preserving only existing conntrack flows also misses new SSH sessions. A current-peer host route is a simpler first-release alternative, but it bypasses every connection to that address and cannot guarantee reconnects from a new peer. That limitation must be an explicit user decision.
+
+## DNS and address families
+
+Recommended first platform uses active systemd-resolved, version 240 or newer, with Roentgen verifying that applications actually use it. Xray's native `autoSystemDnsToGateway` points resolved to the next address after the first IPv4 gateway, or first IPv6 gateway if there is no IPv4 one. It applies a per-link `~.` domain and DNS default-route flag through `resolvectl`. This is not an instruction to put a public resolver into the Linux TUN `dns` array. [Pinned DNS takeover implementation](https://github.com/XTLS/Xray-core/blob/v26.10.29/proxy/tun/tun_linux.go), [resolvectl semantics](https://www.freedesktop.org/software/systemd/man/latest/resolvectl.html).
+
+The takeover checks that the TUN's synthetic resolver address reaches a DNS outbound. It refuses a missing or empty built-in DNS configuration and upstreams that may use the system resolver. This check is a routing probe, not a successful real DNS query. An indirect hostname bootstrap loop can still escape it. [Pinned DNS preflight](https://github.com/XTLS/Xray-core/blob/v26.10.29/proxy/tun/tun_system_dns.go).
+
+Use separate policies for endpoint bootstrap and ordinary user DNS. Bootstrap must work before the proxy exists; ordinary DNS should traverse the selected proxy after activation. Configure independent upstream addresses/bootstrap and explicitly route their queries. Xray's DNS module distinguishes routed transports from local transports that bypass its routing. Do not use a local/direct DNS mode for user queries while claiming all DNS goes through the proxy. [Xray DNS transport behavior](https://xtls.github.io/en/config/dns.html).
+
+Port-53 queries can enter a DNS outbound for processing; DoH/DoT/DoQ are ordinary encrypted connections and must follow the general traffic policy. Decide whether to forward full DNS queries over the proxy or use built-in resolution, and test non-A/AAAA records. DNS outbound behavior must match the pinned core, not older examples. [DNS outbound](https://xtls.github.io/en/config/outbounds/dns.html).
+
+`~.` does not erase another interface's more-specific split DNS domains. Applications may also bypass the system resolver, and systemd-resolved has multiple `/etc/resolv.conf` modes. A LAN DNS server can evade a plain TUN default route through its connected subnet. For strict capture, cover such port-53 traffic with routing/interception or reject the conflicting setup. NetworkManager can itself bind DNS to a physical connection. [resolved routing and resolver-file modes](https://www.freedesktop.org/software/systemd/man/latest/systemd-resolved.service.html), [NetworkManager DNS management](https://www.networkmanager.dev/docs/api/latest/NetworkManager.conf.html).
+
+Route both IPv4 and IPv6 or explicitly block unsupported egress for the missing family while enabled. IPv4-only capture with working native IPv6 is a leak. IPv6 destinations can be carried through an IPv4 proxy transport if the selected protocol/server supports them; this must be checked end to end. DHCP, IPv6 neighbor discovery, and management traffic require narrow exceptions. UDP support in the TUN stack does not prove that every imported subscription outbound supports UDP or every remote server can reach IPv6.
+
+## Privileges, service lifetime, and recovery
+
+Linux TUN needs `/dev/net/tun` and permission to create/configure the device. Route and firewall operations require network administration privileges. A dedicated system service should own those operations; keep subscription fetching and the terminal UI unprivileged where practical. Bound capability sets and a controlled local command interface are preferable to running the entire TUI as root. Socket binding/mark operations can need additional capabilities depending on kernel and ownership, so do not promise that CAP_NET_ADMIN alone covers every configuration. [Kernel TUN documentation](https://docs.kernel.org/networking/tuntap.html), [Linux capabilities](https://man7.org/linux/man-pages/man7/capabilities.7.html), [systemd capability controls](https://www.freedesktop.org/software/systemd/man/latest/systemd.exec.html).
+
+Xray supports a supplied Linux TUN file descriptor through `XRAY_TUN_FD`. In that mode it does not perform the normal owned-device setup. This permits a privileged helper to configure a device and pass its FD to a less privileged core, but endpoint socket binding and DNS permissions remain separate issues. Treat that split as a later hardening option, not a requirement for the smallest release. [Linux FD handling and ownership](https://github.com/XTLS/Xray-core/blob/v26.10.29/proxy/tun/tun_linux.go), [FD documentation](https://xtls.github.io/en/config/inbounds/tun.html).
+
+Use a system-level service independent of the SSH login session. Explicit start/stop commands should change desired proxy state; TUI exit must not stop it. systemd restart policy can recover a failed process, and `ExecStopPost` runs after abnormal exits and failed starts as well as ordinary stops. It is the appropriate place for idempotent cleanup. A hung but living core needs health supervision beyond `Restart=on-failure`. [Service restart and cleanup semantics](https://www.freedesktop.org/software/systemd/man/latest/systemd.service.html).
+
+Proposed activation transaction:
+
+1. Validate the selected profile and generated config without network changes. Record the current resolver manager, routes, SSH peers and existing policy owners. Refuse unsupported conflicts.
+2. Prepare loop/management exceptions and recovery ownership records. Start Xray and verify an application exchange through its remote outbound. Establish the resolver path, then activate host capture. Do not use synthetic ping or a TCP handshake alone as the health check.
+3. If any step fails, undo only Roentgen-owned changes. Report enabled only after actual capture and DNS checks succeed.
+4. On disable, disarm automatic restart of the requested proxy state, remove capture, restore DNS, stop the core, then remove owned exceptions. Order the transition according to the chosen fail-open/fail-closed policy.
+
+A nonpersistent TUN and its routes normally disappear when the last FD closes, including after SIGKILL. Policy rules and nftables objects are independent and need cleanup. Xray's README explicitly warns that SIGKILL cannot run DNS revert; the source notes that per-link DNS state goes away with a removed link. Do not rely on either alone for externally supplied or persistent devices. The service must reconcile leftovers on failure and before its next activation. A persistent helper-held FD can keep a dead core's interface alive. [Kernel device lifetime](https://docs.kernel.org/networking/tuntap.html), [Xray DNS cleanup caveat](https://github.com/XTLS/Xray-core/blob/v26.10.29/proxy/tun/README.md#system-dns-on-linux-and-freebsd-autosystemdnstogateway), [Linux Close implementation](https://github.com/XTLS/Xray-core/blob/v26.10.29/proxy/tun/tun_linux.go).
+
+A process or machine failure cannot guarantee immediate cleanup by application code. Avoid permanent manager configuration for transient capture; keep an ownership journal, idempotent recovery, and an emergency local disable command. Never flush all firewall rules or restore a stale snapshot of the whole host route table. Strict fail-closed behavior needs a separately owned egress policy that survives core failure while allowing management access. Automatic route removal alone tends toward fail-open and is not a kill switch.
+
+## Coexistence and supported platform boundary
+
+Support a conventional host network namespace first. Container-local TUN affects that namespace, not automatically the host; rootless containers and restricted VPS kernels may lack the required device/capabilities. Non-systemd distributions cannot use the proposed system service unchanged. Xray's automatic DNS takeover cannot configure dnsmasq, unbound, static resolver files, or OpenRC arrangements just because a TUN exists. [Xray platform restrictions](https://github.com/XTLS/Xray-core/blob/v26.10.29/proxy/tun/README.md), [kernel device requirements](https://docs.kernel.org/networking/tuntap.html).
+
+Treat NetworkManager, systemd-networkd, existing VPNs, Docker/Podman bridges, firewalld/UFW, VRFs, and policy-routing rules as preflight inputs. Choose one owner for each Roentgen interface, DNS setting, route and firewall object. Detect address/table/mark/name collisions. Do not disable network managers or replace their physical defaults. Evaluate reverse-path filtering against the resulting routes; prefer consistent tables or a narrowly justified setting over globally disabling it. The kernel documents loose mode for asymmetric routing and `src_valid_mark` for marked reverse-path checks. [Kernel reverse-path settings](https://www.kernel.org/doc/html/latest/networking/ip-sysctl.html), [NetworkManager configuration](https://www.networkmanager.dev/docs/api/latest/NetworkManager.conf.html).
+
+VPN coexistence needs an explicit policy: reject concurrent full-tunnel VPNs initially, route the proxy transport over the VPN, or bypass it using a named physical uplink. Automatic Xray interface selection does not establish which policy the user wants. Docker forwarding/NAT and traffic in other network namespaces should remain outside the initial host-only guarantee unless tested and deliberately included.
+
+## Subscription boundary and later routing
+
+Keep the subscription URL and imported server profiles separate from the privileged host configuration. Current 3x-ui source handles share-link subscription lists, including encoded content. This confirms that subscription handling is application work, not a reason for a TUN companion. Support the chosen exported formats explicitly and validate each imported protocol/transport against the pinned Xray release. Do not run arbitrary downloaded inbounds, routing rules, API listeners, or filesystem paths as root. [3x-ui subscription handling](https://github.com/MHSanaei/3x-ui/blob/c621ba9014a6aa21fd7c9ecf45655e44e24ebf13/internal/sub/external_subscription.go).
+
+On refresh, preserve the selected profile if it still exists. A failed refresh must not replace the last valid configuration or change off to on. If the active profile disappears or changes, the application needs a documented policy before it switches a live host connection.
+
+Future domain/subnet policy can live in Xray routing while the Linux layer keeps capturing eligible host traffic. Raw TUN packets contain IP addresses; domain rules need DNS-derived information, FakeDNS, or sniffing, with different reliability and privacy behavior. Reserve the configuration boundary now, but defer the user-facing rules editor. System exceptions for bootstrap and SSH are required in the first release and should not depend on that later feature. [Xray routing](https://xtls.github.io/en/config/routing.html), [FakeDNS](https://xtls.github.io/en/config/fakedns.html).
+
+## Open user decisions
+
+- Approve native Xray as the backend and pin v26.10.29 for initial validation, or deliberately support an older installed core through a companion.
+- Choose the first supported distributions and resolver arrangement. Recommendation is systemd plus active systemd-resolved; other setups should fail preflight until supported.
+- Define "all traffic" as TCP/UDP with visible management/link exceptions. Decide whether LAN destinations remain direct, enter TUN, or are blocked.
+- Choose failure behavior. Should proxy failure restore direct internet or block ordinary egress until recovery? Explicit off should restore ordinary networking in either case.
+- Choose SSH protection scope. Recommendation is existing and new inbound management connections; a current-peer host route is simpler but narrower.
+- Decide whether a previously enabled proxy resumes after reboot. Terminal persistence alone does not answer this.
+- Decide whether missing UDP/IPv6 support in a selected server blocks activation or permits an explicit degraded mode that blocks unsupported traffic.
+- Decide concurrent VPN policy, DNS upstream/bootstrap policy, and what happens when an active subscription profile changes or disappears.
+
+## Validation required before implementation is accepted
+
+No network experiments were run for this research. Validate the pinned binaries in disposable VMs/network namespaces before enabling a remotely administered host:
+
+- Confirm generated config acceptance and native TUN availability for packaged amd64/arm64 binaries. Test a non-root UI and service privilege boundary.
+- Exercise TCP payload exchange, UDP request/reply and QUIC, DNS over UDP/TCP including non-address records, IPv4-only, IPv6-only and dual-stack destinations. Observe physical egress for DNS/IPv6 leakage and endpoint recursion.
+- Keep an SSH session active across on/off, reconnect from another peer, use a nonstandard listening port, and test IPv6 SSH. Repeat after core SIGKILL and startup failure.
+- Verify cleanup after ordinary stop, SIGKILL, failed DNS takeover, process hang, reboot, and supplied/persistent FD cases. Confirm unrelated routes and firewall objects remain intact.
+- Test Wi-Fi/Ethernet changes, DHCP renewals, LAN DNS, resolved split domains, Docker bridges and a conflicting VPN. Either demonstrate the declared coexistence policy or reject activation with a useful reason.
+- Verify import/select/refresh while off makes no networking changes; terminal exit preserves on; explicit off wins over restart; a bad refresh leaves the running profile intact.
+
+These tests decide whether native automatic routes suffice for the declared platform set or whether Roentgen must own policy routing from its first release. The source review establishes that a companion is optional; it does not establish production readiness for any particular host.