diff --git a/proxy/tun/README.md b/proxy/tun/README.md index 6441bddb6..0ac786dd3 100644 --- a/proxy/tun/README.md +++ b/proxy/tun/README.md @@ -236,7 +236,7 @@ With `autoSystemWfpBlockLeak`, which needs `autoSystemRoutingTable` (the config Whatever is blocked, loopback, which cannot leave the system, and what Windows itself needs on the local link stay allowed: DHCP, DHCPv6, and IPv6 neighbor and multicast listener discovery. -Keeping the system's DNS inside the TUN, with `"all"`, `"allexceptlan"`, `"dns"`, `"windnsclient"`, or anything covering TCP or UDP port 53, needs `dns` (the config is rejected otherwise). The `dns` servers then 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. +Keeping the system's DNS inside the TUN, with `"all"`, `"allexceptlan"`, `"dns"`, `"windnsclient"`, or anything covering TCP or UDP port 53, needs `dns` (the config is rejected otherwise). The `dns` servers then have to lie within `gateway` or `autoSystemRoutingTable`, or be loopback addresses like `127.0.0.1` for a local resolver (a warning is logged otherwise), and DNS servers that should be reached directly belong in Xray's own `dns` settings. 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. diff --git a/proxy/tun/tun_windows_wfp.go b/proxy/tun/tun_windows_wfp.go index 5ad4757f9..f58c80574 100644 --- a/proxy/tun/tun_windows_wfp.go +++ b/proxy/tun/tun_windows_wfp.go @@ -618,13 +618,15 @@ func addFilter(engine windows.Handle, sublayer, layer *windows.GUID, name string return nil } -// dnsOutsideTUN returns the servers outside all of prefixes, the TUN's own -// subnets and routes: queries to them cannot go through the TUN. +// dnsOutsideTUN returns the servers whose queries the filters block: those +// outside all of prefixes, the TUN's own subnets and routes, as queries to +// them cannot go through the TUN. Loopback ones, local resolvers, are not +// among them, as loopback stays allowed. func dnsOutsideTUN(servers []netip.Addr, prefixes []netip.Prefix) []netip.Addr { var outside []netip.Addr for _, server := range servers { server = server.Unmap() - if !slices.ContainsFunc(prefixes, func(p netip.Prefix) bool { return p.Contains(server) }) { + if !server.IsLoopback() && !slices.ContainsFunc(prefixes, func(p netip.Prefix) bool { return p.Contains(server) }) { outside = append(outside, server) } } diff --git a/proxy/tun/tun_windows_wfp_test.go b/proxy/tun/tun_windows_wfp_test.go index 8ed850813..288164699 100644 --- a/proxy/tun/tun_windows_wfp_test.go +++ b/proxy/tun/tun_windows_wfp_test.go @@ -214,6 +214,10 @@ func TestDNSOutsideTUN(t *testing.T) { netip.MustParseAddr("::ffff:203.0.113.54"), netip.MustParseAddr("8.8.8.8"), netip.MustParseAddr("2001:db8::53"), + // Local resolvers, reachable over loopback whatever is blocked. + netip.MustParseAddr("127.0.0.1"), + netip.MustParseAddr("::ffff:127.0.0.53"), + netip.MustParseAddr("::1"), } want := []netip.Addr{netip.MustParseAddr("8.8.8.8"), netip.MustParseAddr("2001:db8::53")} if got := dnsOutsideTUN(servers, prefixes); !slices.Equal(got, want) {