mirror of
https://github.com/XTLS/Xray-core.git
synced 2026-11-03 00:46:59 +00:00
"allexceptlan" is "all" but for the local network: traffic to and from private (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, fc00::/7), link-local (169.254.0.0/16, fe80::/10), multicast (224.0.0.0/4, ff00::/8) and broadcast (255.255.255.255) addresses stays allowed, so that printers, the router's settings page, or casting to a TV keep working while nothing else leaves outside the TUN. It includes "dns": a resolver on the local network, like the router's, would forward the queries outside the TUN, so DNS stays blocked there. The LAN permit sits below the other values, like the permit through the TUN, so they still apply on the local network, e.g. "tcp/445" blocks Windows file sharing with it. The DNS Client service keeps mDNS and LLMNR, to the local network's addresses only. "all" overrides it. The addresses are matched with FWP_V4_ADDR_MASK and FWP_V6_ADDR_MASK conditions on the remote address, which match when any of them does. 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 "all", "allexceptlan", "dns", "windnsclient", or TCP or UDP port 53, 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
|
|
}
|