Files
Xray-core/proxy/tun/wfp_leak.go
T
patternihaandClaude Opus 5.5 468f745fc2 TUN inbound's autoSystemWfpBlockLeak: Add "all", "tcp", "udp", "icmp" and port ranges; Always allow loopback, DHCP and IPv6 neighbor discovery
autoSystemWfpBlockLeak now also takes:

- "all": everything outside the TUN, IPv4 and IPv6, in both directions, so
  nothing but Xray leaves except through the TUN.
- "tcp", "udp": all ports of that protocol, also written "tcp/0", "udp/0".
- "tcp/<low>-<high>", "udp/<low>-<high>": a range of ports, matched with
  FWP_MATCH_RANGE, as two conditions on the same field would match when
  either does.
- "icmp": ICMP and ICMPv6, e.g. ping. It takes no port: in ICMP filters the
  local port holds the type, and "icmp/0" would read as the echo reply.

Values are normalized: "tcp/0" and "tcp/1-65535" become "tcp", and
"tcp/53-53" becomes "tcp/53".

Whatever is blocked, loopback, which cannot leave the system, and what
Windows itself needs on the local link now always stay allowed: DHCP, DHCPv6,
and IPv6 neighbor and multicast listener discovery. These used to be allowed
only along with "misconfigtun", so "udp" or "icmp" would otherwise have cut
DHCP or IPv6 neighbor discovery, and "tcp" any local proxy, Xray's own
inbounds included. Loopback being allowed means that with "dns", a local
resolver on 127.0.0.1:53 now works for other programs (its own queries to
port 53 or 853 outside the TUN stay blocked), and the DNS Client service
may use it.

"all" reuses the filters of "misconfigtun", which now also let everything
through the TUN, for both IP versions whatever the routes.

On other TUN options: keeping the system's DNS inside the TUN, with "all",
"dns", "windnsclient", or anything covering TCP or UDP port 53 ("udp",
"tcp/50-60", ...), needs dns, as Windows can no longer reach the DNS
servers of the other interfaces; the config is rejected otherwise. Like the
other values, the new ones need autoSystemRoutingTable. The filters do not
need autoOutboundsInterface, as Xray is allowed by its app ID; it still
keeps Xray's own connections out of the TUN's routes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 11:13:10 +03:30

114 lines
3.5 KiB
Go

package tun
import (
"slices"
"strconv"
"strings"
"github.com/xtls/xray-core/common/errors"
)
// dnsLeaks is what "dns" in autoSystemWfpBlockLeak stands for: DNS over UDP and
// TCP, Windows' DNS Client service, which may also use other protocols and
// ports, DNS over TLS, and DNS over QUIC.
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.
type leakTraffic struct {
protocol string // "tcp", "udp" or "icmp"
low, high uint16 // the ports, both 0 for all
}
func (t leakTraffic) String() string {
switch {
case t.low == 0:
return t.protocol
case t.low == t.high:
return t.protocol + "/" + strconv.Itoa(int(t.low))
default:
return t.protocol + "/" + strconv.Itoa(int(t.low)) + "-" + strconv.Itoa(int(t.high))
}
}
// covers tells whether t includes connections of protocol to port.
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>", where "/0" stands for all ports,
// as do "/1-65535" and no port at all; ICMP has no ports.
func parseLeakTraffic(value string) (leakTraffic, bool) {
protocol, ports, hasPorts := strings.Cut(value, "/")
switch protocol {
case "tcp", "udp":
case "icmp":
return leakTraffic{protocol: protocol}, !hasPorts
default:
return leakTraffic{}, false
}
if !hasPorts {
return leakTraffic{protocol: protocol}, true
}
lowText, highText, isRange := strings.Cut(ports, "-")
low, err := strconv.ParseUint(lowText, 10, 16)
if err != nil {
return leakTraffic{}, false
}
high := low
if isRange {
if high, err = strconv.ParseUint(highText, 10, 16); err != nil || low == 0 || high < low {
return leakTraffic{}, false
}
}
if low == 0 || low == 1 && high == 65535 {
return leakTraffic{protocol: protocol}, true
}
return leakTraffic{protocol: protocol, low: uint16(low), high: uint16(high)}, true
}
// ExpandWfpBlockLeak checks the values of autoSystemWfpBlockLeak and returns
// them in lower case, in order and without duplicates, with "dns" replaced by
// what it stands for, and ports written the same way, "tcp/0" as "tcp" say.
func ExpandWfpBlockLeak(values []string) ([]string, error) {
var leaks []string
add := func(leak string) {
if !slices.Contains(leaks, leak) {
leaks = append(leaks, leak)
}
}
for _, value := range values {
switch value = strings.ToLower(value); value {
case "dns":
for _, leak := range dnsLeaks {
add(leak)
}
case "windnsclient", "misconfigtun", "all":
add(value)
default:
traffic, ok := parseLeakTraffic(value)
if !ok {
return nil, errors.New("unknown autoSystemWfpBlockLeak value: ", value)
}
add(traffic.String())
}
}
return leaks, nil
}
// BlocksSystemDNS tells whether expanded autoSystemWfpBlockLeak values keep the
// system's DNS (TCP or UDP port 53, or Windows' DNS Client service) inside the
// TUN, which then needs DNS servers of its own.
func BlocksSystemDNS(leaks []string) bool {
return slices.ContainsFunc(leaks, func(leak string) bool {
if leak == "windnsclient" || leak == "all" {
return true
}
traffic, ok := parseLeakTraffic(leak)
return ok && (traffic.covers("udp", 53) || traffic.covers("tcp", 53))
})
}