mirror of
https://github.com/XTLS/Xray-core.git
synced 2026-11-02 16:36:58 +00:00
TUN inbound's autoSystemWfpBlockLeak: Add "tcpv4", "tcpv6", "udpv4", "udpv6", "icmpv4" and "icmpv6"
"v4" or "v6" after the protocol limits a value to one IP version, ports written the same way, e.g. "tcpv4/853" or "udpv6/10-20"; "icmpv4" is ICMP and "icmpv6" is ICMPv6. Their filters go to the ALE layers of that version only. "tcp", "udp" and "icmp" keep covering both. Anything covering TCP or UDP port 53 in either IP version, like "udpv4/53", still needs dns. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
9ffa0f0552
commit
2218fec173
@@ -51,6 +51,11 @@ func TestTunConfigAutoSystem(t *testing.T) {
|
||||
Parser: loadJSON(creator),
|
||||
Output: &tun.Config{Name: "xray0", Desc: "Wintun", MTU: 1500, DNS: []string{"1.1.1.1"}, AutoSystemRoutingTable: []string{"0.0.0.0/0"}, AutoOutboundsInterface: "auto", AutoSystemWfpBlockLeak: []string{"allexceptlan", "udp/53", "tcp/53", "windnsclient", "tcp/853", "udp/853"}},
|
||||
},
|
||||
{
|
||||
Input: `{"name": "xray0", "autoSystemRoutingTable": ["0.0.0.0/0"], "autoSystemWfpBlockLeak": ["TCPv4/853", "udpv6/10-20", "ICMPv6"]}`,
|
||||
Parser: loadJSON(creator),
|
||||
Output: &tun.Config{Name: "xray0", Desc: "Wintun", MTU: 1500, AutoSystemRoutingTable: []string{"0.0.0.0/0"}, AutoOutboundsInterface: "auto", AutoSystemWfpBlockLeak: []string{"tcpv4/853", "udpv6/10-20", "icmpv6"}},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -71,6 +76,8 @@ func TestTunConfigAutoSystemNeeds(t *testing.T) {
|
||||
{`{"name": "xray0", "autoSystemRoutingTable": ["0.0.0.0/0"], "autoSystemWfpBlockLeak": ["udp"]}`, []string{"windows"}},
|
||||
{`{"name": "xray0", "autoSystemRoutingTable": ["0.0.0.0/0"], "autoSystemWfpBlockLeak": ["tcp/50-60"]}`, []string{"windows"}},
|
||||
{`{"name": "xray0", "autoSystemRoutingTable": ["0.0.0.0/0"], "autoSystemWfpBlockLeak": ["tcp/54-60", "icmp"]}`, nil},
|
||||
{`{"name": "xray0", "autoSystemRoutingTable": ["0.0.0.0/0"], "autoSystemWfpBlockLeak": ["udpv6/53"]}`, []string{"windows"}},
|
||||
{`{"name": "xray0", "autoSystemRoutingTable": ["0.0.0.0/0"], "autoSystemWfpBlockLeak": ["tcpv4/853", "icmpv4"]}`, nil},
|
||||
{`{"name": "xray0", "autoSystemWfpBlockLeak": ["all"]}`, []string{"windows"}},
|
||||
{`{"name": "xray0", "autoSystemRoutingTable": ["0.0.0.0/0"], "autoSystemWfpBlockLeak": ["allexceptlan"]}`, []string{"windows"}},
|
||||
{`{"name": "xray0", "dns": ["1.1.1.1"], "autoSystemRoutingTable": ["0.0.0.0/0"], "autoSystemWfpBlockLeak": ["allexceptlan"]}`, nil},
|
||||
@@ -87,7 +94,7 @@ func TestTunConfigAutoSystemNeeds(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestTunConfigAutoSystemWfpBlockLeakUnknown(t *testing.T) {
|
||||
for _, value := range []string{"ip", "tcp/0", "tcp/0-10", "icmp/1", "udp/20-10"} {
|
||||
for _, value := range []string{"ip", "tcp/0", "tcp/0-10", "icmp/1", "udp/20-10", "tcp4", "icmpv4/1"} {
|
||||
config := new(TunConfig)
|
||||
if err := json.Unmarshal([]byte(`{"name": "xray0", "autoSystemWfpBlockLeak": ["dns", "`+value+`"]}`), config); err != nil {
|
||||
t.Fatal(err)
|
||||
|
||||
@@ -228,6 +228,7 @@ When `dns` is set, those servers are applied to the adapter. Windows is kept fro
|
||||
With `autoSystemWfpBlockLeak`, which needs `autoSystemRoutingTable` (the config is rejected otherwise), Xray also adds Windows Filtering Platform filters that keep traffic of every program but Xray itself from leaving outside the TUN, each kind chosen by a value in the list, e.g. `"autoSystemWfpBlockLeak": ["dns", "misconfigtun"]`:
|
||||
- `"tcp"`, `"udp"`: connections of that protocol only go through the TUN, to all ports, or with `/<port>` or `/<low>-<high>` to some, e.g. `"tcp/853"` or `"udp/10-20"`. Ports go from 1 to 65535, and the low end of a range is not above the high end.
|
||||
- `"icmp"`: ICMP and ICMPv6, e.g. `ping`, only go through the TUN.
|
||||
- `"tcpv4"`, `"tcpv6"`, `"udpv4"`, `"udpv6"`, `"icmpv4"`, `"icmpv6"`: the same for IPv4 or IPv6 only, with ports written the same way, e.g. `"tcpv4/853"` or `"udpv6/10-20"`. `"icmpv4"` is ICMP and `"icmpv6"` is ICMPv6.
|
||||
- `"windnsclient"`: Windows' DNS Client service cannot connect outside the TUN at all, except for name resolution on the local network (LLMNR, mDNS). On Windows 11 and Server 2022 and later, it may send name queries over HTTPS or TLS, to any port. Earlier versions only query port 53, so there it does nothing.
|
||||
- `"dns"`: stands for `"udp/53"`, `"tcp/53"`, `"windnsclient"`, `"tcp/853"` (DNS over TLS) and `"udp/853"` (DNS over QUIC). 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.
|
||||
- `"misconfigtun"`: an IP version without routes in `autoSystemRoutingTable`, IPv4 or IPv6, is blocked entirely outside the TUN, in both directions, as it would bypass the TUN. An address of that version in `gateway` is not needed: without one, Windows gives the TUN link-local addresses itself, an IPv6 one at once and an IPv4 one from `169.254.0.0/16` after some seconds (until then, IPv4 routed to the TUN is unreachable), and what is routed to the TUN goes through it with those.
|
||||
|
||||
@@ -288,15 +288,15 @@ func dnsClientUsesAnyPort() bool {
|
||||
// other interfaces on purpose:
|
||||
//
|
||||
// - traffic: connections of these protocols, TCP and UDP to some or all
|
||||
// ports, or ICMP, may only go through the TUN, those of DNS (53), DNS
|
||||
// over TLS (TCP 853) and DNS over QUIC (UDP 853) with "dns". Windows sends
|
||||
// a name query to the DNS servers of all interfaces, not only to those of
|
||||
// the TUN: to the first server of each interface, then to all of them when
|
||||
// no answer arrives within a second or two. It sends the queries for the
|
||||
// servers of an interface out through that interface, whatever the routes
|
||||
// say, and other programs reach an on-link resolver, like 192.168.1.1 from
|
||||
// DHCP, through its LAN route, which is more specific than the TUN's
|
||||
// default route.
|
||||
// ports, or ICMP, over one IP version or both, may only go through the
|
||||
// TUN, those of DNS (53), DNS over TLS (TCP 853) and DNS over QUIC (UDP
|
||||
// 853) with "dns". Windows sends a name query to the DNS servers of all
|
||||
// interfaces, not only to those of the TUN: to the first server of each
|
||||
// interface, then to all of them when no answer arrives within a second
|
||||
// or two. It sends the queries for the servers of an interface out
|
||||
// through that interface, whatever the routes say, and other programs
|
||||
// reach an on-link resolver, like 192.168.1.1 from DHCP, through its LAN
|
||||
// route, which is more specific than the TUN's default route.
|
||||
// - dnsClient: since Windows 11 and Server 2022, Windows may also send its
|
||||
// queries over HTTPS or TLS, to any port, so its DNS Client service may not
|
||||
// connect outside the TUN at all, except for name resolution on the local
|
||||
@@ -515,6 +515,9 @@ func addLeakFilters(engine windows.Handle, tun winipcfg.LUID, block leakFilters)
|
||||
}
|
||||
}
|
||||
for _, traffic := range block.traffic {
|
||||
if traffic.version == "v4" && layer.ipv6 || traffic.version == "v6" && !layer.ipv6 {
|
||||
continue // for the other IP version only
|
||||
}
|
||||
protocol := uintptr(windows.IPPROTO_UDP)
|
||||
switch {
|
||||
case traffic.protocol == "tcp":
|
||||
|
||||
@@ -129,7 +129,7 @@ func TestLeakFiltersAccepted(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
leaks, err := ExpandWfpBlockLeak([]string{"dns", "misconfigtun", "all", "tcp/443", "udp/1000-2000", "tcp", "icmp"})
|
||||
leaks, err := ExpandWfpBlockLeak([]string{"dns", "misconfigtun", "all", "tcp/443", "udp/1000-2000", "tcp", "icmp", "tcpv4/80", "udpv6/5000-6000", "icmpv6"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -170,6 +170,8 @@ func TestNewLeakFilters(t *testing.T) {
|
||||
{[]string{"misconfigtun"}, true, false, "IPv6", false},
|
||||
{[]string{"tcp/10-20", "udp", "icmp"}, true, true, "tcp/10-20, udp, icmp", true},
|
||||
{[]string{"tcp/54-60", "icmp"}, true, true, "tcp/54-60, icmp", false},
|
||||
{[]string{"TCPv4/10-20", "udpv6", "icmpv4"}, true, true, "tcpv4/10-20, udpv6, icmpv4", true},
|
||||
{[]string{"tcpv6/54-60", "icmpv6"}, true, true, "tcpv6/54-60, icmpv6", false},
|
||||
{[]string{"allexceptlan"}, true, true, "udp/53, tcp/53, tcp/853, udp/853" + windnsclient + ", IPv4 except LAN, IPv6 except LAN", true},
|
||||
{[]string{"allexceptlan", "all"}, true, true, "udp/53, tcp/53, tcp/853, udp/853" + windnsclient + ", IPv4, IPv6", true},
|
||||
{[]string{"misconfigtun", "allexceptlan"}, true, false, "udp/53, tcp/53, tcp/853, udp/853" + windnsclient + ", IPv4 except LAN, IPv6 except LAN", true},
|
||||
|
||||
+27
-17
@@ -15,44 +15,54 @@ var dnsLeaks = []string{"udp/53", "tcp/53", "windnsclient", "tcp/853", "udp/853"
|
||||
|
||||
// leakTraffic is a "tcp", "udp" or "icmp" value of autoSystemWfpBlockLeak, the
|
||||
// former two with "/<port>" or "/<low>-<high>" for some ports only, such as
|
||||
// "tcp/853" or "udp/10-20": connections of that protocol, to those ports, may
|
||||
// only go through the TUN.
|
||||
// "tcp/853" or "udp/10-20", and any of them with "v4" or "v6" after the
|
||||
// protocol for one IP version only, such as "tcpv4/853" or "icmpv6":
|
||||
// connections of that protocol, to those ports, may only go through the TUN.
|
||||
type leakTraffic struct {
|
||||
protocol string // "tcp", "udp" or "icmp"
|
||||
version string // "v4" or "v6" for one IP version only, empty for both
|
||||
low, high uint16 // the ports, both 0 for all
|
||||
}
|
||||
|
||||
func (t leakTraffic) String() string {
|
||||
name := t.protocol + t.version
|
||||
switch {
|
||||
case t.low == 0:
|
||||
return t.protocol
|
||||
return name
|
||||
case t.low == t.high:
|
||||
return t.protocol + "/" + strconv.Itoa(int(t.low))
|
||||
return name + "/" + strconv.Itoa(int(t.low))
|
||||
default:
|
||||
return t.protocol + "/" + strconv.Itoa(int(t.low)) + "-" + strconv.Itoa(int(t.high))
|
||||
return name + "/" + strconv.Itoa(int(t.low)) + "-" + strconv.Itoa(int(t.high))
|
||||
}
|
||||
}
|
||||
|
||||
// covers tells whether t includes connections of protocol to port.
|
||||
// covers tells whether t includes connections of protocol to port, over at
|
||||
// least one IP version.
|
||||
func (t leakTraffic) covers(protocol string, port uint16) bool {
|
||||
return t.protocol == protocol && (t.low == 0 || t.low <= port && port <= t.high)
|
||||
}
|
||||
|
||||
// parseLeakTraffic parses a lower case "tcp", "udp" or "icmp" value. The former
|
||||
// two may end in "/<port>" or "/<low>-<high>", with ports from 1 to 65535 and
|
||||
// low not above high, where "/1-65535" stands for all ports, as does no port
|
||||
// at all; ICMP has no ports.
|
||||
// parseLeakTraffic parses a lower case "tcp", "udp" or "icmp" value, which may
|
||||
// be followed by "v4" or "v6". TCP and UDP may end in "/<port>" or
|
||||
// "/<low>-<high>", with ports from 1 to 65535 and low not above high, where
|
||||
// "/1-65535" stands for all ports, as does no port at all; ICMP has no ports.
|
||||
func parseLeakTraffic(value string) (leakTraffic, bool) {
|
||||
protocol, ports, hasPorts := strings.Cut(value, "/")
|
||||
switch protocol {
|
||||
name, ports, hasPorts := strings.Cut(value, "/")
|
||||
t := leakTraffic{protocol: name}
|
||||
for _, version := range []string{"v4", "v6"} {
|
||||
if protocol, ok := strings.CutSuffix(name, version); ok {
|
||||
t.protocol, t.version = protocol, version
|
||||
}
|
||||
}
|
||||
switch t.protocol {
|
||||
case "tcp", "udp":
|
||||
case "icmp":
|
||||
return leakTraffic{protocol: protocol}, !hasPorts
|
||||
return t, !hasPorts
|
||||
default:
|
||||
return leakTraffic{}, false
|
||||
}
|
||||
if !hasPorts {
|
||||
return leakTraffic{protocol: protocol}, true
|
||||
return t, true
|
||||
}
|
||||
lowText, highText, isRange := strings.Cut(ports, "-")
|
||||
low, err := strconv.ParseUint(lowText, 10, 16)
|
||||
@@ -65,10 +75,10 @@ func parseLeakTraffic(value string) (leakTraffic, bool) {
|
||||
return leakTraffic{}, false
|
||||
}
|
||||
}
|
||||
if low == 1 && high == 65535 {
|
||||
return leakTraffic{protocol: protocol}, true
|
||||
if low != 1 || high != 65535 {
|
||||
t.low, t.high = uint16(low), uint16(high)
|
||||
}
|
||||
return leakTraffic{protocol: protocol, low: uint16(low), high: uint16(high)}, true
|
||||
return t, true
|
||||
}
|
||||
|
||||
// ExpandWfpBlockLeak checks the values of autoSystemWfpBlockLeak and returns
|
||||
|
||||
@@ -25,6 +25,9 @@ func TestExpandWfpBlockLeak(t *testing.T) {
|
||||
{[]string{"udp/10-20", "udp/53-53", "tcp/50-60"}, []string{"udp/10-20", "udp/53", "tcp/50-60"}, true},
|
||||
{[]string{"tcp/54-60", "udp/1-52"}, []string{"tcp/54-60", "udp/1-52"}, false},
|
||||
{[]string{"ICMP", "icmp"}, []string{"icmp"}, false},
|
||||
{[]string{"TCPv4/853", "udpv6/10-20", "ICMPv4", "icmpv6"}, []string{"tcpv4/853", "udpv6/10-20", "icmpv4", "icmpv6"}, false},
|
||||
{[]string{"tcpv6/1-65535", "tcp", "tcpv4", "udpv6/53-53"}, []string{"tcpv6", "tcp", "tcpv4", "udpv6/53"}, true},
|
||||
{[]string{"udpv4/53"}, []string{"udpv4/53"}, true},
|
||||
{[]string{"AllExceptLAN"}, []string{"allexceptlan", "udp/53", "tcp/53", "windnsclient", "tcp/853", "udp/853"}, true},
|
||||
{[]string{"dns", "allexceptlan", "all"}, []string{"udp/53", "tcp/53", "windnsclient", "tcp/853", "udp/853", "allexceptlan", "all"}, true},
|
||||
} {
|
||||
@@ -44,6 +47,7 @@ func TestExpandWfpBlockLeak(t *testing.T) {
|
||||
"", "ip", "tcp/", "udp/65536", "tcp/-1", "tcp/+53", "tcp/53/1", "udp/abc", "icmp/0", "icmp/8", "tcp:53", " dns",
|
||||
"tcp/0-10", "tcp/20-10", "tcp/5-", "tcp/-5", "tcp/1-2-3", "tcp/1-65536", "udp/ 53", "all/0",
|
||||
"tcp/0", "udp/0", "tcp/00", "udp/0-0", "udp/65535-1",
|
||||
"tcpv5", "tcp4", "udp6", "v4", "tcpv4v6", "ipv4", "dnsv4", "allv6", "tcpv4/0", "icmpv4/8", "icmpv6/0",
|
||||
} {
|
||||
if _, err := ExpandWfpBlockLeak([]string{value}); err == nil {
|
||||
t.Errorf("%q was accepted", value)
|
||||
@@ -66,6 +70,9 @@ func TestLeakTrafficCovers(t *testing.T) {
|
||||
{"tcp/10-20", "tcp", 20, true},
|
||||
{"tcp/10-20", "tcp", 21, false},
|
||||
{"tcp/10-20", "tcp", 9, false},
|
||||
{"udpv4/53", "udp", 53, true},
|
||||
{"tcpv6", "tcp", 443, true},
|
||||
{"icmpv4", "udp", 53, false},
|
||||
} {
|
||||
traffic, ok := parseLeakTraffic(c.value)
|
||||
if !ok {
|
||||
|
||||
Reference in New Issue
Block a user