TUN inbound: Support "tcp/port" and "udp/port" in autoSystemWfpBlockLeak on Windows; "dns" covers DoT and DoQ as well

"dns" in autoSystemWfpBlockLeak only blocked port 53 outside the TUN, plus
the DNS Client service on Windows 11 and Server 2022 and later, so other
programs' DNS over TLS (TCP 853) and DNS over QUIC (UDP 853) could still
leave outside the TUN.

autoSystemWfpBlockLeak now takes:

- "tcp/<port>", "udp/<port>": connections to that port may only go through
  the TUN (or come from Xray itself).
- "windnsclient": Windows' DNS Client service may only connect through the
  TUN, except for mDNS and LLMNR. As before, this only applies from Windows
  11 and Server 2022 on, where the service may use DoH or DoT to any port;
  earlier versions only query port 53.
- "dns": stands for "udp/53", "tcp/53", "windnsclient", "tcp/853" and
  "udp/853".
- "misconfigtun": unchanged.

Values are case-insensitive, deduplicated, and anything else, such as
"tcp/0" or "icmp/1", is rejected. Keeping the system's DNS inside the TUN,
with "udp/53", "tcp/53" or "windnsclient", needs dns to be set, as "dns"
did; ports like 853 alone do not. Each port gets its own permit-through-TUN
and block filters with a protocol condition, where port 53 used to have one
for both protocols, and the log lists what is blocked, e.g. "[tun] blocked
outside the TUN: udp/53, tcp/53, tcp/853, udp/853, windnsclient".

Tested on Windows 11 with a Wintun adapter as the outside interface: with
"tcp/853", "udp/853", "tcp/7215" and "udp/7216", those are blocked outside
the TUN over IPv4 and IPv6, while tcp/7216, udp/7215 and 443 are not, port
853 works through the TUN, Xray's own connections to port 853 still leave
outside it, and nothing is blocked after Xray stops.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
patterniha
2026-10-29 14:52:11 +03:30
co-authored by Claude Opus 5.5
parent 0afec66085
commit f2f292c90c
8 changed files with 308 additions and 89 deletions
+4 -2
View File
@@ -219,8 +219,10 @@ 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 `autoSystemWfpBlockLeak`, which needs `autoSystemRoutingTable` (the config is rejected otherwise), Xray also adds Windows Filtering Platform filters that keep two kinds of traffic of every program but Xray itself from leaving outside the TUN, each chosen by a value in the list, e.g. `"autoSystemWfpBlockLeak": ["dns", "misconfigtun"]`:
- `"dns"` (needs `dns`, the config is rejected otherwise): 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.
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/<port>"`, `"udp/<port>"`: connections to that TCP or UDP port only go through the TUN, e.g. `"tcp/853"`.
- `"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. Keeping the system's DNS inside the TUN like this, with `"dns"`, `"udp/53"`, `"tcp/53"` or `"windnsclient"`, 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.
- `"misconfigtun"`: an IP version without routes in `autoSystemRoutingTable`, IPv4 or IPv6, is blocked entirely, in both directions, as it would bypass the TUN. Only loopback and what Windows itself needs on the local link (DHCP, and for IPv6 neighbor and multicast listener discovery) remain allowed. 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.
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.
+24 -23
View File
@@ -247,30 +247,31 @@ startOver:
}
// With autoSystemWfpBlockLeak, once the system routes lead to the TUN,
// keep DNS ("dns", if dns is set), and an IP version no route of which
// leads to the TUN ("misconfigtun"), from leaving through the other
// interfaces. Addresses do not matter: without one of a version in
// gateway, Windows gives the TUN a link-local one.
leaks := t.options.AutoSystemWfpBlockLeak
blockDNS := slices.Contains(leaks, "dns") && len(dns) > 0
blockIPv4 := slices.Contains(leaks, "misconfigtun") && !route4
blockIPv6 := slices.Contains(leaks, "misconfigtun") && !route6
if (route4 || route6) && (blockDNS || blockIPv4 || blockIPv6) {
if t.wfp, err = blockLeaks(t.luid, blockDNS, blockIPv4, blockIPv6); err != nil {
var blocked []string
for _, b := range []struct {
on bool
what string
}{{blockDNS, "DNS"}, {blockIPv4, "IPv4"}, {blockIPv6, "IPv6"}} {
if b.on {
blocked = append(blocked, b.what)
}
}
// Rather no TUN than a leaking one.
return errors.New("unable to block ", strings.Join(blocked, " and "), " outside the TUN (remove autoSystemWfpBlockLeak to run without)").Base(err)
// keep connections to the ports given ("dns" stands for those of DNS),
// Windows' DNS Client service, and an IP version no route of which leads
// to the TUN ("misconfigtun"), from leaving through the other interfaces.
// Addresses do not matter: without one of a version in gateway, Windows
// gives the TUN a link-local one.
leaks, err := ExpandWfpBlockLeak(t.options.AutoSystemWfpBlockLeak)
if err != nil {
return err
}
block := newLeakFilters(leaks, route4, route6)
if route4 || route6 {
if slices.Contains(leaks, "windnsclient") && !block.dnsClient {
errors.LogInfo(context.Background(), "[tun] windnsclient skipped: before Windows 11 and Server 2022, the DNS Client service only queries port 53")
}
errors.LogInfo(context.Background(), "[tun] outside the TUN, blocked DNS: ", blockDNS, ", blocked IPv4: ", blockIPv4, ", blocked IPv6: ", blockIPv6)
if blockDNS {
if block.systemDNS() && len(dns) == 0 {
return errors.New("keeping the system's DNS inside the TUN needs dns to be set")
}
}
if blocked := block.String(); (route4 || route6) && blocked != "" {
if t.wfp, err = blockLeaks(t.luid, block); err != nil {
// Rather no TUN than a leaking one.
return errors.New("unable to block ", blocked, " outside the TUN (remove autoSystemWfpBlockLeak to run without)").Base(err)
}
errors.LogInfo(context.Background(), "[tun] blocked outside the TUN: ", blocked)
if block.systemDNS() {
covered := slices.Clone(addresses)
for _, route := range routesData {
covered = append(covered, route.Destination)
+105 -40
View File
@@ -7,6 +7,7 @@ import (
"os"
"runtime"
"slices"
"strings"
"unsafe"
"github.com/xtls/xray-core/common/errors"
@@ -170,19 +171,79 @@ func condition(field *windows.GUID, typ uint32, value uintptr) fwpmFilterConditi
}
}
// leakFilters is what blockLeaks keeps from leaving outside the TUN.
type leakFilters struct {
ports []leakPort // connections to these ports
dnsClient bool // Windows' DNS Client service
ipv4, ipv6 bool // an IP version altogether
}
// newLeakFilters turns expanded autoSystemWfpBlockLeak values into the filters
// for a TUN that the routes of route4 (IPv4) and route6 (IPv6) lead to.
func newLeakFilters(leaks []string, route4, route6 bool) leakFilters {
var block leakFilters
for _, leak := range leaks {
switch leak {
case "windnsclient":
block.dnsClient = dnsClientUsesAnyPort()
case "misconfigtun":
block.ipv4, block.ipv6 = !route4, !route6
default:
if port, ok := parseLeakPort(leak); ok {
block.ports = append(block.ports, port)
}
}
}
return block
}
// String lists what block keeps inside the TUN, or is empty for nothing.
func (block leakFilters) String() string {
var blocked []string
for _, port := range block.ports {
blocked = append(blocked, port.String())
}
for _, b := range []struct {
on bool
what string
}{{block.dnsClient, "windnsclient"}, {block.ipv4, "IPv4"}, {block.ipv6, "IPv6"}} {
if b.on {
blocked = append(blocked, b.what)
}
}
return strings.Join(blocked, ", ")
}
// systemDNS tells whether block keeps the system's DNS inside the TUN.
func (block leakFilters) systemDNS() bool {
return block.dnsClient || slices.ContainsFunc(block.ports, func(port leakPort) bool { return port.port == 53 })
}
// dnsClientUsesAnyPort tells whether Windows' DNS Client service may send its
// queries over HTTPS or TLS, and so to any port, which it does since Windows 11
// and Server 2022 (build 20348). Earlier versions only query port 53, and may
// run the service in one process with others, which its filters would catch
// as well.
func dnsClientUsesAnyPort() bool {
_, _, build := windows.RtlGetNtVersionNumbers()
return build >= 20348
}
// blockLeaks keeps traffic from leaving through interfaces other than tun,
// for every program but Xray itself, whose outbounds (DNS included) use the
// other interfaces on purpose:
//
// - dns: DNS (port 53) may only go through the TUN. 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. Since Windows 11 and Server 2022, Windows may also send its
// queries over HTTPS or TLS, so there its DNS Client service may not
// - ports: connections to these TCP or UDP ports 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
// link (mDNS, LLMNR).
// - ipv4, ipv6: no IPv4, or no IPv6, at all, in either direction, for a TUN
@@ -196,7 +257,7 @@ func condition(field *windows.GUID, typ uint32, value uintptr) fwpmFilterConditi
//
// The filters live in a dynamic WFP session: closing the returned engine handle
// with closeWFPEngine deletes them, and so does Windows when the process dies.
func blockLeaks(tun winipcfg.LUID, dns, ipv4, ipv6 bool) (windows.Handle, error) {
func blockLeaks(tun winipcfg.LUID, block leakFilters) (windows.Handle, error) {
engine, err := openWFPEngine()
if err != nil {
return 0, err
@@ -205,7 +266,7 @@ func blockLeaks(tun winipcfg.LUID, dns, ipv4, ipv6 bool) (windows.Handle, error)
closeWFPEngine(engine)
return 0, errors.New("FwpmTransactionBegin0 failed").Base(err)
}
err = addLeakFilters(engine, tun, dns, ipv4, ipv6)
err = addLeakFilters(engine, tun, block)
if err == nil {
if err = fwpmResult(procFwpmTransactionCommit0.Call(uintptr(engine))); err != nil {
err = errors.New("FwpmTransactionCommit0 failed").Base(err)
@@ -242,7 +303,7 @@ func closeWFPEngine(engine windows.Handle) {
// addLeakFilters adds the filters of blockLeaks in a sublayer of their own.
// blockLeaks runs it in a transaction, so that they take effect all at once.
func addLeakFilters(engine windows.Handle, tun winipcfg.LUID, dns, ipv4, ipv6 bool) error {
func addLeakFilters(engine windows.Handle, tun winipcfg.LUID, block leakFilters) error {
exe, err := os.Executable()
if err != nil {
return err
@@ -285,51 +346,55 @@ func addLeakFilters(engine windows.Handle, tun winipcfg.LUID, dns, ipv4, ipv6 bo
// hard permit. Connections from outside to Xray get an ordinary one, so
// that firewalls keep guarding its inbounds.
self := condition(&fwpmConditionALEAppID, fwpByteBlobType, uintptr(unsafe.Pointer(appID)))
dns53 := condition(&fwpmConditionIPRemotePort, fwpUint16, 53)
// DNS goes through the TUN when its local address is the TUN's, and it
// also leaves, or arrives, through the TUN. The local address alone
// A connection goes through the TUN when its local address is the TUN's,
// and it also leaves, or arrives, through the TUN. The local address alone
// decides by default, but with weak host sending or receiving enabled,
// packets of the TUN's address can use other interfaces. (The next hop,
// the interface replies would leave by, is not known for arriving ones.)
onTUN := func(field *windows.GUID) fwpmFilterCondition0 {
return condition(field, fwpUint64, uintptr(unsafe.Pointer(tunLUID)))
}
out := []fwpmFilterCondition0{dns53, onTUN(&fwpmConditionIPLocalInterface), onTUN(&fwpmConditionIPNexthopInterface)}
in := []fwpmFilterCondition0{dns53, onTUN(&fwpmConditionIPLocalInterface), onTUN(&fwpmConditionIPArrivalInterface)}
for _, layer := range []struct {
key *windows.GUID
selfFlags uint32
throughTUN []fwpmFilterCondition0
key *windows.GUID
selfFlags uint32
via *windows.GUID // the interface that has to be the TUN as well
}{
{&fwpmLayerALEAuthConnectV4, fwpmFilterFlagClearActionRight, out},
{&fwpmLayerALEAuthRecvAcceptV4, 0, in},
{&fwpmLayerALEAuthConnectV6, fwpmFilterFlagClearActionRight, out},
{&fwpmLayerALEAuthRecvAcceptV6, 0, in},
{&fwpmLayerALEAuthConnectV4, fwpmFilterFlagClearActionRight, &fwpmConditionIPNexthopInterface},
{&fwpmLayerALEAuthRecvAcceptV4, 0, &fwpmConditionIPArrivalInterface},
{&fwpmLayerALEAuthConnectV6, fwpmFilterFlagClearActionRight, &fwpmConditionIPNexthopInterface},
{&fwpmLayerALEAuthRecvAcceptV6, 0, &fwpmConditionIPArrivalInterface},
} {
if err := add(layer.key, "permit Xray", layer.selfFlags, fwpActionPermit, 4, self); err != nil {
return err
}
if dns {
if err := add(layer.key, "permit DNS through the TUN", 0, fwpActionPermit, 3, layer.throughTUN...); err != nil {
for _, port := range block.ports {
protocol := uintptr(windows.IPPROTO_UDP)
if port.tcp {
protocol = windows.IPPROTO_TCP
}
toPort := []fwpmFilterCondition0{
condition(&fwpmConditionIPProtocol, fwpUint8, protocol),
condition(&fwpmConditionIPRemotePort, fwpUint16, uintptr(port.port)),
}
throughTUN := slices.Concat(toPort, []fwpmFilterCondition0{onTUN(&fwpmConditionIPLocalInterface), onTUN(layer.via)})
if err := add(layer.key, "permit "+port.String()+" through the TUN", 0, fwpActionPermit, 3, throughTUN...); err != nil {
return err
}
if err := add(layer.key, "block DNS", 0, fwpActionBlock, 2, dns53); err != nil {
if err := add(layer.key, "block "+port.String(), 0, fwpActionBlock, 2, toPort...); err != nil {
return err
}
}
}
// Since Windows 11 and Server 2022 (build 20348), the DNS Client service
// may also send the queries for an interface's servers over HTTPS or TLS,
// out through that interface and to any port. So there it may only
// connect through the TUN, except for mDNS and LLMNR, which stay on the
// local link (over an IP version only while it is not blocked altogether).
// Earlier versions only query port 53, and may run the service in one
// process with others, which the filters would catch as well. Like
// Windows Firewall's rules for it, they recognize the service by its SID,
// Since Windows 11 and Server 2022, the DNS Client service may also send
// the queries for an interface's servers over HTTPS or TLS, out through
// that interface and to any port. So there it may only connect through
// the TUN, except for mDNS and LLMNR, which stay on the local link (over
// an IP version only while it is not blocked altogether). Like Windows
// Firewall's rules for it, the filters recognize the service by its SID,
// which Windows puts in the token of its process: the security descriptor
// grants that SID the right to match (FWP_ACTRL_MATCH_FILTER, CC in SDDL).
if _, _, build := windows.RtlGetNtVersionNumbers(); dns && build >= 20348 {
if block.dnsClient {
sd, err := windows.SecurityDescriptorFromString("O:SYG:SYD:(A;;CCRC;;;" + dnsClientSID + ")")
if err != nil {
return err
@@ -343,8 +408,8 @@ func addLeakFilters(engine windows.Handle, tun winipcfg.LUID, dns, ipv4, ipv6 bo
key *windows.GUID
localLink bool
}{
{&fwpmLayerALEAuthConnectV4, !ipv4},
{&fwpmLayerALEAuthConnectV6, !ipv6},
{&fwpmLayerALEAuthConnectV4, !block.ipv4},
{&fwpmLayerALEAuthConnectV6, !block.ipv6},
} {
if err := add(layer.key, "permit the DNS Client service through the TUN", 0, fwpActionPermit, 3, dnsClient, onTUN(&fwpmConditionIPLocalInterface), onTUN(&fwpmConditionIPNexthopInterface)); err != nil {
return err
@@ -367,7 +432,7 @@ func addLeakFilters(engine windows.Handle, tun winipcfg.LUID, dns, ipv4, ipv6 bo
matchType: fwpMatchFlagsAllSet,
conditionValue: fwpValue0{typ: fwpUint32, value: fwpConditionFlagIsLoopback},
}
if ipv4 {
if block.ipv4 {
// DHCP keeps the addresses of the other interfaces, which Xray's own
// connections use.
dhcp := []fwpmFilterCondition0{
@@ -387,7 +452,7 @@ func addLeakFilters(engine windows.Handle, tun winipcfg.LUID, dns, ipv4, ipv6 bo
}
}
}
if ipv6 {
if block.ipv6 {
// Neighbor and multicast listener discovery, ICMPv6 130-137 and 143,
// whose type and code sit where the local and remote port are.
discovery := []fwpmFilterCondition0{condition(&fwpmConditionIPProtocol, fwpUint8, windows.IPPROTO_ICMPV6)}
+28 -1
View File
@@ -114,11 +114,38 @@ func TestLeakFiltersAccepted(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if err := addLeakFilters(engine, loopback, true, true, true); err != nil {
leaks, err := ExpandWfpBlockLeak([]string{"dns", "misconfigtun", "tcp/443"})
if err != nil {
t.Fatal(err)
}
block := newLeakFilters(leaks, false, false)
block.dnsClient = true // whatever the Windows version
if err := addLeakFilters(engine, loopback, block); err != nil {
skipUnlessElevated(err)
}
}
func TestNewLeakFilters(t *testing.T) {
leaks, err := ExpandWfpBlockLeak([]string{"dns", "misconfigtun"})
if err != nil {
t.Fatal(err)
}
block := newLeakFilters(leaks, true, false)
want := "udp/53, tcp/53, tcp/853, udp/853, IPv6"
if dnsClientUsesAnyPort() {
want = "udp/53, tcp/53, tcp/853, udp/853, windnsclient, IPv6"
}
if got := block.String(); got != want {
t.Errorf("String() = %q, want %q", got, want)
}
if !block.systemDNS() {
t.Error("systemDNS() = false with dns")
}
if block := newLeakFilters([]string{"tcp/853", "udp/853", "misconfigtun"}, true, true); block.String() != "tcp/853, udp/853" || block.systemDNS() {
t.Errorf("tcp/853, udp/853: %q, systemDNS() = %v", block.String(), block.systemDNS())
}
}
func TestDNSClientSID(t *testing.T) {
sid, _, _, err := windows.LookupSID("", `NT SERVICE\Dnscache`)
if err != nil {
+79
View File
@@ -0,0 +1,79 @@
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"}
// leakPort is a "tcp/<port>" or "udp/<port>" value of autoSystemWfpBlockLeak:
// connections to that port may only go through the TUN.
type leakPort struct {
tcp bool
port uint16
}
func (p leakPort) String() string {
if p.tcp {
return "tcp/" + strconv.Itoa(int(p.port))
}
return "udp/" + strconv.Itoa(int(p.port))
}
// parseLeakPort parses a lower case "tcp/<port>" or "udp/<port>" value.
func parseLeakPort(value string) (leakPort, bool) {
protocol, port, ok := strings.Cut(value, "/")
if !ok || protocol != "tcp" && protocol != "udp" {
return leakPort{}, false
}
n, err := strconv.ParseUint(port, 10, 16)
if err != nil || n == 0 {
return leakPort{}, false
}
return leakPort{tcp: protocol == "tcp", port: uint16(n)}, 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.
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":
add(value)
default:
port, ok := parseLeakPort(value)
if !ok {
return nil, errors.New("unknown autoSystemWfpBlockLeak value: ", value)
}
add(port.String())
}
}
return leaks, nil
}
// BlocksSystemDNS tells whether expanded autoSystemWfpBlockLeak values keep the
// system's DNS (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 {
return leak == "udp/53" || leak == "tcp/53" || leak == "windnsclient"
})
}
+40
View File
@@ -0,0 +1,40 @@
package tun
import (
"slices"
"testing"
)
func TestExpandWfpBlockLeak(t *testing.T) {
for _, c := range []struct {
values []string
leaks []string
systemDNS bool
}{
{nil, nil, false},
{[]string{"dns"}, []string{"udp/53", "tcp/53", "windnsclient", "tcp/853", "udp/853"}, true},
{[]string{"DNS", "MisconfigTUN"}, []string{"udp/53", "tcp/53", "windnsclient", "tcp/853", "udp/853", "misconfigtun"}, true},
{[]string{"tcp/853", "dns", "TCP/0853"}, []string{"tcp/853", "udp/53", "tcp/53", "windnsclient", "udp/853"}, true},
{[]string{"tcp/853", "udp/853", "misconfigtun"}, []string{"tcp/853", "udp/853", "misconfigtun"}, false},
{[]string{"udp/65535", "tcp/1"}, []string{"udp/65535", "tcp/1"}, false},
{[]string{"tcp/53"}, []string{"tcp/53"}, true},
{[]string{"windnsclient"}, []string{"windnsclient"}, true},
} {
leaks, err := ExpandWfpBlockLeak(c.values)
if err != nil {
t.Errorf("%v: %v", c.values, err)
continue
}
if !slices.Equal(leaks, c.leaks) {
t.Errorf("%v: got %v, want %v", c.values, leaks, c.leaks)
}
if got := BlocksSystemDNS(leaks); got != c.systemDNS {
t.Errorf("%v: BlocksSystemDNS() = %v", c.values, got)
}
}
for _, value := range []string{"", "ip", "tcp", "tcp/", "tcp/0", "udp/65536", "tcp/-1", "tcp/+53", "tcp/53/1", "udp/abc", "icmp/1", "tcp:53", " dns"} {
if _, err := ExpandWfpBlockLeak([]string{value}); err == nil {
t.Errorf("%q was accepted", value)
}
}
}