TUN inbound: Block DNS and IPv6 leaks outside the TUN on Windows; Add strictRoute

Windows sends name queries to the DNS servers of all interfaces, and a
resolver on the local network (e.g. 192.168.1.1 from DHCP) is reached
through its more specific LAN route instead of the TUN, so DNS leaks
past it. IPv6 bypasses a TUN that cannot carry it.

With autoSystemRoutingTable set, the Windows TUN now adds Windows
Filtering Platform filters, all in one transaction and in a dynamic
session, so that they are removed when Xray exits, even if it crashes:
- DNS (port 53) only goes through the TUN, in both directions: its local
  address, and the interface it leaves or arrives by, must be the TUN's.
- IPv6 is blocked in both directions when the TUN has no IPv6 address or
  no IPv6 route, except loopback, neighbor and multicast listener
  discovery, and DHCPv6.
- Xray's own traffic is exempt: its connections out with a hard permit,
  which Windows Firewall rules do not override (like sing-box's
  strict_route), connections to its inbounds with an ordinary one.
If the filters cannot be added, the TUN does not start on Windows 10 and
later (only a warning on 7/8). The new `strictRoute` option (true by
default) turns them off.

Also on Windows:
- A warning for `dns` servers outside gateway and autoSystemRoutingTable,
  as queries to them cannot go through the TUN and are blocked.
- While DNS is restricted and autoOutboundsInterface is in use, Xray
  resolves the names it would ask Windows for itself (Go's resolver on
  its own sockets). Those lookups and the `localhost` DNS server skip the
  TUN's DNS servers, unless another interface uses them too, instead of
  looping back into the TUN.
- The DNS cache is flushed when the TUN starts and stops, and DNS
  registration is turned off on the TUN (through netsh before Windows 10
  1809).
- Close no longer panics when registering the route or interface change
  callbacks failed.
The README's Windows section describes all of it.

Tested on Windows 11, elevated, amd64 and 386: the filters, DNS arriving
through a real Wintun adapter and blocked outside it, the IPv6 block,
Windows Firewall rules, and a real Xray run. Windows 7/8 and Windows 10
before 1809 are untested.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
patterniha
2026-09-28 08:15:40 +03:30
co-authored by Claude Opus 5.5
parent 7780db9bbe
commit 2db099b34b
12 changed files with 967 additions and 4 deletions
+14
View File
@@ -198,6 +198,20 @@ To make it start, wintun.dll specific for your Windows/arch must be present next
After the start network adapter with the name you chose in the config will be created in the system, and exist while Xray is running.
When `dns` is set, those servers are applied to the adapter. Windows is kept from registering the TUN's addresses in DNS, and its DNS cache is flushed when the TUN starts and stops.
With `autoSystemRoutingTable` set, and unless `strictRoute` is set to `false`, Xray also adds Windows Filtering Platform filters that keep two kinds of traffic of every program but Xray itself from leaving outside the TUN:
- With `dns` set, DNS (port 53) only goes through the TUN. Windows keeps sending name queries to the DNS servers of the other interfaces as well, and a resolver on the local network (e.g. `192.168.1.1` handed out by DHCP) is reached through its more specific LAN route instead of the TUN. The `dns` servers therefore have to lie within `gateway` or `autoSystemRoutingTable` (a warning is logged otherwise), and DNS servers that should be reached directly belong in Xray's own `dns` settings.
- Unless the TUN carries IPv6, that is, has an IPv6 address in `gateway` and IPv6 routes in `autoSystemRoutingTable`, IPv6 is blocked entirely, in both directions, as it would bypass the TUN. Only loopback and what Windows itself needs on the local link (neighbor and multicast listener discovery, DHCPv6) remain allowed.
With the filters in place, Xray's own connections out also get past Windows Firewall's block rules (other firewalls may still block them), while connections to Xray's inbounds stay subject to them.
Names that Xray resolves through the system resolver, such as an outbound's server address given as a domain with the default `AsIs` domain strategy, would be looked up by Windows on Xray's behalf, and those queries would then go into the TUN too. While DNS is restricted this way and `autoOutboundsInterface` is in use (the default with `autoSystemRoutingTable`), Xray therefore resolves them itself, with its own queries to the DNS servers of the other interfaces. That bypasses Windows' DNS cache, and its name resolution on the local network (LLMNR, mDNS): a server address given as a domain is looked up again for every connection, and a DNS server that does not answer delays each lookup. Having Xray's own `dns` resolve it, through the outbound's `sockopt.domainStrategy`, avoids that. The `localhost` DNS server queries the same servers whenever `autoOutboundsInterface` is in use. Both skip the TUN's own DNS servers, unless another interface uses them as well: queried from Xray itself, they would lead back into it, or nowhere.
If the filters cannot be added, the TUN does not start (on Windows 10 and later; older versions only log a warning). They are removed when Xray exits. Not covered are encrypted DNS that Windows may send to the servers of other interfaces (DNS over HTTPS), and name resolution on the local network over IPv4 (LLMNR, mDNS, NetBIOS).
`strictRoute` (Windows only, `true` by default) can be set to `false` to go without the filters, for setups they break: a local DNS resolver other programs use (e.g. on `127.0.0.1:53`), the DNS of another VPN on its own interface, IPv6 on the local network while the TUN has no IPv6 address, virtual machines whose NAT resolves names on the host, or signing in to a captive portal. DNS may then leak as described above.
You can give the adapter ip address manually, you can live Windows to give it autogenerated ip address (which take few seconds), it doesn't matter, the traffic going _through_ the interface will be forwarded into the app for proxying. \
Minimal configuration that will work for local machine is routing passing the traffic on-link through the interface.
You will need the interface id for that, unfortunately it is going to change with every Xray start due to implementation ambiguity between Xray and wintun driver.