From 4ec4fb8aabdac7f3d4fa45d52f23488937cc573f Mon Sep 17 00:00:00 2001 From: patterniha <71074308+patterniha@users.noreply.github.com> Date: Wed, 30 Sep 2026 02:31:00 +0330 Subject: [PATCH] TUN inbound: Rename to `autoSystemWfpBlockLeak` and `autoSystemDnsToGateway` autoSystemWFP becomes autoSystemWfpBlockLeak, saying that the WFP filters block leaks, and autoSystemDNS becomes autoSystemDnsToGateway, saying where it points the system DNS, so that pointing the system DNS at the gateway on Windows later would fit the same name. Their config fields keep their numbers; neither was released. Co-Authored-By: Claude Opus 5.5 --- infra/conf/tun.go | 8 ++++---- infra/conf/tun_test.go | 11 ++++++++--- proxy/tun/README.md | 8 ++++---- proxy/tun/config.pb.go | 22 +++++++++++----------- proxy/tun/config.proto | 4 ++-- proxy/tun/tun_linux.go | 2 +- proxy/tun/tun_linux_dns_test.go | 8 ++++---- proxy/tun/tun_windows.go | 11 ++++++----- 8 files changed, 40 insertions(+), 34 deletions(-) diff --git a/infra/conf/tun.go b/infra/conf/tun.go index 953a9c58a..1ad75a6e9 100644 --- a/infra/conf/tun.go +++ b/infra/conf/tun.go @@ -20,8 +20,8 @@ type TunConfig struct { UserLevel uint32 `json:"userLevel"` AutoSystemRoutingTable []string `json:"autoSystemRoutingTable"` AutoOutboundsInterface *string `json:"autoOutboundsInterface"` - AutoSystemDNS bool `json:"autoSystemDNS"` - AutoSystemWFP bool `json:"autoSystemWFP"` + AutoSystemDnsToGateway bool `json:"autoSystemDnsToGateway"` + AutoSystemWfpBlockLeak bool `json:"autoSystemWfpBlockLeak"` } func (v *TunConfig) Build() (proto.Message, error) { @@ -33,8 +33,8 @@ func (v *TunConfig) Build() (proto.Message, error) { DNS: v.DNS, UserLevel: v.UserLevel, AutoSystemRoutingTable: v.AutoSystemRoutingTable, - AutoSystemDns: v.AutoSystemDNS, - AutoSystemWfp: v.AutoSystemWFP, + AutoSystemDnsToGateway: v.AutoSystemDnsToGateway, + AutoSystemWfpBlockLeak: v.AutoSystemWfpBlockLeak, } if v.AutoOutboundsInterface != nil { config.AutoOutboundsInterface = *v.AutoOutboundsInterface diff --git a/infra/conf/tun_test.go b/infra/conf/tun_test.go index 8e0a54d1d..6e20e8468 100644 --- a/infra/conf/tun_test.go +++ b/infra/conf/tun_test.go @@ -7,7 +7,7 @@ import ( "github.com/xtls/xray-core/proxy/tun" ) -func TestTunConfigAutoSystemWFP(t *testing.T) { +func TestTunConfigAutoSystem(t *testing.T) { creator := func() Buildable { return new(TunConfig) } @@ -19,9 +19,14 @@ func TestTunConfigAutoSystemWFP(t *testing.T) { Output: &tun.Config{Name: "xray0", Desc: "Wintun", MTU: 1500}, }, { - Input: `{"name": "xray0", "autoSystemWFP": true}`, + Input: `{"name": "xray0", "autoSystemDnsToGateway": true}`, Parser: loadJSON(creator), - Output: &tun.Config{Name: "xray0", Desc: "Wintun", MTU: 1500, AutoSystemWfp: true}, + Output: &tun.Config{Name: "xray0", Desc: "Wintun", MTU: 1500, AutoSystemDnsToGateway: true}, + }, + { + Input: `{"name": "xray0", "autoSystemWfpBlockLeak": true}`, + Parser: loadJSON(creator), + Output: &tun.Config{Name: "xray0", Desc: "Wintun", MTU: 1500, AutoSystemWfpBlockLeak: true}, }, }) } diff --git a/proxy/tun/README.md b/proxy/tun/README.md index 1b73b5e4c..4716bc454 100644 --- a/proxy/tun/README.md +++ b/proxy/tun/README.md @@ -22,9 +22,9 @@ For more advanced routing policies or rules, OS level configuration can still ma 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. -### SYSTEM DNS ON LINUX (`autoSystemDNS`) +### SYSTEM DNS ON LINUX (`autoSystemDnsToGateway`) -On Linux, setting `autoSystemDNS` to `true` lets the inbound point the system resolver at the tun interface, so name lookups resolve through Xray instead of going out over the physical link. It is off by default, and it is Linux-only. +On Linux, setting `autoSystemDnsToGateway` to `true` lets the inbound point the system resolver at the tun interface, so name lookups resolve through Xray instead of going out over the physical link. It is off by default, and it is Linux-only. It uses `resolvectl`, which means it applies only when all of these hold: @@ -200,7 +200,7 @@ After the start network adapter with the name you chose in the config will be cr 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 `autoSystemWFP` and `autoSystemRoutingTable` set, 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 `autoSystemWfpBlockLeak` and `autoSystemRoutingTable` set, 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, out through those interfaces whatever the routes say, and other programs reach a resolver on the local network (e.g. `192.168.1.1` handed out by DHCP) through its more specific LAN route instead of the TUN. On Windows 11 and Server 2022 and later, where those queries may also go over HTTPS or TLS, Windows' DNS Client service cannot connect outside the TUN at all, except for name resolution on the local network (LLMNR, mDNS). 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. @@ -210,7 +210,7 @@ Names that Xray resolves through the system resolver, such as an outbound's serv 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 is name resolution on the local network (LLMNR, mDNS, NetBIOS), except over IPv6 while that is blocked. -`autoSystemWFP` (Windows only) is `false` by default, as the filters break some setups: 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. Without the filters, DNS may leak as described above. +`autoSystemWfpBlockLeak` (Windows only) is `false` by default, as the filters break some setups: 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. Without the filters, DNS may 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. diff --git a/proxy/tun/config.pb.go b/proxy/tun/config.pb.go index 0c46c689a..52c305aae 100644 --- a/proxy/tun/config.pb.go +++ b/proxy/tun/config.pb.go @@ -32,8 +32,8 @@ type Config struct { AutoSystemRoutingTable []string `protobuf:"bytes,6,rep,name=auto_system_routing_table,json=autoSystemRoutingTable,proto3" json:"auto_system_routing_table,omitempty"` AutoOutboundsInterface string `protobuf:"bytes,7,opt,name=auto_outbounds_interface,json=autoOutboundsInterface,proto3" json:"auto_outbounds_interface,omitempty"` Desc string `protobuf:"bytes,8,opt,name=desc,proto3" json:"desc,omitempty"` - AutoSystemDns bool `protobuf:"varint,9,opt,name=auto_system_dns,json=autoSystemDns,proto3" json:"auto_system_dns,omitempty"` - AutoSystemWfp bool `protobuf:"varint,10,opt,name=auto_system_wfp,json=autoSystemWfp,proto3" json:"auto_system_wfp,omitempty"` + AutoSystemDnsToGateway bool `protobuf:"varint,9,opt,name=auto_system_dns_to_gateway,json=autoSystemDnsToGateway,proto3" json:"auto_system_dns_to_gateway,omitempty"` + AutoSystemWfpBlockLeak bool `protobuf:"varint,10,opt,name=auto_system_wfp_block_leak,json=autoSystemWfpBlockLeak,proto3" json:"auto_system_wfp_block_leak,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -124,16 +124,16 @@ func (x *Config) GetDesc() string { return "" } -func (x *Config) GetAutoSystemDns() bool { +func (x *Config) GetAutoSystemDnsToGateway() bool { if x != nil { - return x.AutoSystemDns + return x.AutoSystemDnsToGateway } return false } -func (x *Config) GetAutoSystemWfp() bool { +func (x *Config) GetAutoSystemWfpBlockLeak() bool { if x != nil { - return x.AutoSystemWfp + return x.AutoSystemWfpBlockLeak } return false } @@ -142,7 +142,7 @@ var File_proxy_tun_config_proto protoreflect.FileDescriptor const file_proxy_tun_config_proto_rawDesc = "" + "\n" + - "\x16proxy/tun/config.proto\x12\x0exray.proxy.tun\"\xd2\x02\n" + + "\x16proxy/tun/config.proto\x12\x0exray.proxy.tun\"\xfa\x02\n" + "\x06Config\x12\x12\n" + "\x04name\x18\x01 \x01(\tR\x04name\x12\x10\n" + "\x03MTU\x18\x02 \x01(\rR\x03MTU\x12\x18\n" + @@ -152,10 +152,10 @@ const file_proxy_tun_config_proto_rawDesc = "" + "user_level\x18\x05 \x01(\rR\tuserLevel\x129\n" + "\x19auto_system_routing_table\x18\x06 \x03(\tR\x16autoSystemRoutingTable\x128\n" + "\x18auto_outbounds_interface\x18\a \x01(\tR\x16autoOutboundsInterface\x12\x12\n" + - "\x04desc\x18\b \x01(\tR\x04desc\x12&\n" + - "\x0fauto_system_dns\x18\t \x01(\bR\rautoSystemDns\x12&\n" + - "\x0fauto_system_wfp\x18\n" + - " \x01(\bR\rautoSystemWfpBL\n" + + "\x04desc\x18\b \x01(\tR\x04desc\x12:\n" + + "\x1aauto_system_dns_to_gateway\x18\t \x01(\bR\x16autoSystemDnsToGateway\x12:\n" + + "\x1aauto_system_wfp_block_leak\x18\n" + + " \x01(\bR\x16autoSystemWfpBlockLeakBL\n" + "\x12com.xray.proxy.tunP\x01Z#github.com/xtls/xray-core/proxy/tun\xaa\x02\x0eXray.Proxy.Tunb\x06proto3" var ( diff --git a/proxy/tun/config.proto b/proxy/tun/config.proto index 9e11cc180..252f0e7c7 100644 --- a/proxy/tun/config.proto +++ b/proxy/tun/config.proto @@ -15,6 +15,6 @@ message Config { repeated string auto_system_routing_table = 6; string auto_outbounds_interface = 7; string desc = 8; - bool auto_system_dns = 9; - bool auto_system_wfp = 10; + bool auto_system_dns_to_gateway = 9; + bool auto_system_wfp_block_leak = 10; } diff --git a/proxy/tun/tun_linux.go b/proxy/tun/tun_linux.go index 5136d501a..7e1c29172 100644 --- a/proxy/tun/tun_linux.go +++ b/proxy/tun/tun_linux.go @@ -185,7 +185,7 @@ var verifyDNSRouting = func(ctx context.Context, inboundTag, source, address str // resolution is left to the OS, which is the documented default. Errors are // returned to the caller, which treats them as non-fatal. func (t *LinuxTun) ConfigureSystemDNS(ctx context.Context, inboundTag string) error { - if !t.options.AutoSystemDns { + if !t.options.AutoSystemDnsToGateway { return nil } if t.systemDNSSet { diff --git a/proxy/tun/tun_linux_dns_test.go b/proxy/tun/tun_linux_dns_test.go index 429771d8b..71f3bf4b7 100644 --- a/proxy/tun/tun_linux_dns_test.go +++ b/proxy/tun/tun_linux_dns_test.go @@ -58,9 +58,9 @@ func recorder(t *testing.T, failOn string) *[][]string { func optedInTun() *LinuxTun { return &LinuxTun{ options: &Config{ - Name: "xray_tun", - Gateway: []string{"192.168.100.1/30"}, - AutoSystemDns: true, + Name: "xray_tun", + Gateway: []string{"192.168.100.1/30"}, + AutoSystemDnsToGateway: true, }, tunLink: testLink("xray_tun"), } @@ -79,7 +79,7 @@ func TestConfigureSystemDNSDisabledByDefault(t *testing.T) { calls := recorder(t, "") t1 := optedInTun() - t1.options.AutoSystemDns = false + t1.options.AutoSystemDnsToGateway = false if err := t1.ConfigureSystemDNS(context.Background(), "tun"); err != nil { t.Fatalf("unexpected error: %v", err) diff --git a/proxy/tun/tun_windows.go b/proxy/tun/tun_windows.go index cac7d8c9b..38adb00fe 100644 --- a/proxy/tun/tun_windows.go +++ b/proxy/tun/tun_windows.go @@ -246,10 +246,11 @@ startOver: } } - // With autoSystemWFP, once the system routes lead to the TUN, keep DNS if - // dns is set, and IPv6 if the TUN cannot carry it (no IPv6 address, or no - // IPv6 route to it), from leaving through the other interfaces. - if blockDNS, blockIPv6 := len(dns) > 0, !address6 || !route6; t.options.AutoSystemWfp && (route4 || route6) && (blockDNS || blockIPv6) { + // With autoSystemWfpBlockLeak, once the system routes lead to the TUN, + // keep DNS if dns is set, and IPv6 if the TUN cannot carry it (no IPv6 + // address, or no IPv6 route to it), from leaving through the other + // interfaces. + if blockDNS, blockIPv6 := len(dns) > 0, !address6 || !route6; t.options.AutoSystemWfpBlockLeak && (route4 || route6) && (blockDNS || blockIPv6) { if t.wfp, err = blockLeaks(t.luid, blockDNS, blockIPv6); err != nil { what := "DNS and IPv6" if !blockIPv6 { @@ -261,7 +262,7 @@ startOver: // untested, and sing-box's broke its TUN there (SagerNet/sing-box#3659), // so older versions only get a warning. if major, _, _ := windows.RtlGetNtVersionNumbers(); major >= 10 { - return errors.New("unable to block ", what, " outside the TUN (set autoSystemWFP to false to run without)").Base(err) + return errors.New("unable to block ", what, " outside the TUN (set autoSystemWfpBlockLeak to false to run without)").Base(err) } errors.LogWarningInner(context.Background(), err, "[tun] unable to block ", what, " outside the TUN, leaks are possible") } else {