mirror of
https://github.com/XTLS/Xray-core.git
synced 2026-10-30 06:56:59 +00:00
"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>
138 lines
3.7 KiB
Go
138 lines
3.7 KiB
Go
package conf
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"fmt"
|
|
"math/big"
|
|
"net"
|
|
"runtime"
|
|
"strconv"
|
|
|
|
"github.com/xtls/xray-core/common/errors"
|
|
"github.com/xtls/xray-core/proxy/tun"
|
|
"google.golang.org/protobuf/proto"
|
|
)
|
|
|
|
type TunConfig struct {
|
|
Name string `json:"name"`
|
|
Desc string `json:"desc"`
|
|
MTU uint32 `json:"mtu"`
|
|
Gateway []string `json:"gateway"`
|
|
DNS []string `json:"dns"`
|
|
UserLevel uint32 `json:"userLevel"`
|
|
AutoSystemRoutingTable []string `json:"autoSystemRoutingTable"`
|
|
AutoOutboundsInterface *string `json:"autoOutboundsInterface"`
|
|
AutoSystemDnsToGateway bool `json:"autoSystemDnsToGateway"`
|
|
AutoSystemWfpBlockLeak []string `json:"autoSystemWfpBlockLeak"`
|
|
}
|
|
|
|
func (v *TunConfig) Build() (proto.Message, error) {
|
|
config := &tun.Config{
|
|
Name: v.Name,
|
|
Desc: v.Desc,
|
|
MTU: v.MTU,
|
|
Gateway: v.Gateway,
|
|
DNS: v.DNS,
|
|
UserLevel: v.UserLevel,
|
|
AutoSystemRoutingTable: v.AutoSystemRoutingTable,
|
|
AutoSystemDnsToGateway: v.AutoSystemDnsToGateway,
|
|
}
|
|
leaks, err := tun.ExpandWfpBlockLeak(v.AutoSystemWfpBlockLeak)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
config.AutoSystemWfpBlockLeak = leaks
|
|
// Each option needs other settings on the system it takes effect on: the
|
|
// filters go along with the routes of autoSystemRoutingTable, keeping the
|
|
// system's DNS inside the TUN needs DNS servers on it, and
|
|
// autoSystemDnsToGateway points the system DNS at the gateway.
|
|
switch runtime.GOOS {
|
|
case "windows":
|
|
if len(leaks) > 0 && len(v.AutoSystemRoutingTable) == 0 {
|
|
return nil, errors.New("autoSystemWfpBlockLeak needs autoSystemRoutingTable to be set")
|
|
}
|
|
if tun.BlocksSystemDNS(leaks) && len(v.DNS) == 0 {
|
|
return nil, errors.New(`autoSystemWfpBlockLeak "dns", "udp/53", "tcp/53" or "windnsclient" needs dns to be set`)
|
|
}
|
|
case "linux", "freebsd":
|
|
if v.AutoSystemDnsToGateway && len(v.Gateway) == 0 {
|
|
return nil, errors.New("autoSystemDnsToGateway needs gateway to be set")
|
|
}
|
|
}
|
|
if v.AutoOutboundsInterface != nil {
|
|
config.AutoOutboundsInterface = *v.AutoOutboundsInterface
|
|
}
|
|
if len(v.AutoSystemRoutingTable) > 0 && v.AutoOutboundsInterface == nil {
|
|
config.AutoOutboundsInterface = "auto"
|
|
}
|
|
|
|
if config.Name == "" {
|
|
name, err := GetAvailableTunName()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
config.Name = name
|
|
}
|
|
if config.Desc == "" {
|
|
config.Desc = "Wintun"
|
|
}
|
|
if config.MTU == 0 {
|
|
config.MTU = 1500
|
|
}
|
|
return config, nil
|
|
}
|
|
|
|
const (
|
|
tunNamePrefix = "utun"
|
|
minTunIndex = 10
|
|
maxTunIndex = 1024
|
|
)
|
|
|
|
func GetAvailableTunName() (string, error) {
|
|
interfaces, err := net.Interfaces()
|
|
if err != nil {
|
|
return "", fmt.Errorf("fail to get system interface information: %w", err)
|
|
}
|
|
|
|
usedNames := make(map[string]struct{}, len(interfaces))
|
|
for _, iface := range interfaces {
|
|
usedNames[iface.Name] = struct{}{}
|
|
}
|
|
|
|
startIndex, err := randomInt(minTunIndex, maxTunIndex)
|
|
if err != nil {
|
|
return "", fmt.Errorf("fail to generate valid tun name: %w", err)
|
|
}
|
|
|
|
rangeSize := maxTunIndex - minTunIndex + 1
|
|
|
|
for offset := 0; offset < rangeSize; offset++ {
|
|
index := minTunIndex + (startIndex-minTunIndex+offset)%rangeSize
|
|
name := tunNamePrefix + strconv.Itoa(index)
|
|
|
|
if _, exists := usedNames[name]; !exists {
|
|
return name, nil
|
|
}
|
|
}
|
|
|
|
return "", fmt.Errorf(
|
|
"no available TUN interface name in range %s%d-%s%d",
|
|
tunNamePrefix,
|
|
minTunIndex,
|
|
tunNamePrefix,
|
|
maxTunIndex,
|
|
)
|
|
}
|
|
|
|
func randomInt(min, max int) (int, error) {
|
|
value, err := rand.Int(
|
|
rand.Reader,
|
|
big.NewInt(int64(max-min+1)),
|
|
)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
return min + int(value.Int64()), nil
|
|
}
|