TUN inbound: Add gateway, dns, autoSystemRoutingTable, autoOutboundsInterface for Windows (#5887)

And refactor `mtu` to support setting IPv4/v6 separately

Example: https://github.com/XTLS/Xray-core/pull/5887#issue-4198837696
This commit is contained in:
LjhAUMEM
2026-04-13 21:38:10 +08:00
committed by GitHub
parent f27edc3172
commit 806b8dc27d
16 changed files with 544 additions and 109 deletions

View File

@@ -1,12 +1,21 @@
package localdns package localdns
import ( import (
"context"
"syscall"
"time"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/features/dns" "github.com/xtls/xray-core/features/dns"
"github.com/xtls/xray-core/transport/internet"
) )
// Client is an implementation of dns.Client, which queries localhost for DNS. // Client is an implementation of dns.Client, which queries localhost for DNS.
type Client struct{} type Client struct {
d *net.Dialer
r *net.Resolver
}
// Type implements common.HasType. // Type implements common.HasType.
func (*Client) Type() interface{} { func (*Client) Type() interface{} {
@@ -20,8 +29,14 @@ func (*Client) Start() error { return nil }
func (*Client) Close() error { return nil } func (*Client) Close() error { return nil }
// LookupIP implements Client. // LookupIP implements Client.
func (*Client) LookupIP(host string, option dns.IPOption) ([]net.IP, uint32, error) { func (c *Client) LookupIP(host string, option dns.IPOption) ([]net.IP, uint32, error) {
ips, err := net.LookupIP(host) var ips []net.IP
var err error
if len(internet.Controllers) > 0 {
ips, err = c.r.LookupIP(context.Background(), "ip", host)
} else {
ips, err = net.LookupIP(host)
}
if err != nil { if err != nil {
return nil, 0, err return nil, 0, err
} }
@@ -62,5 +77,28 @@ func (*Client) LookupIP(host string, option dns.IPOption) ([]net.IP, uint32, err
// New create a new dns.Client that queries localhost for DNS. // New create a new dns.Client that queries localhost for DNS.
func New() *Client { func New() *Client {
return &Client{} d := &net.Dialer{
Timeout: time.Second * 16,
Control: func(network, address string, c syscall.RawConn) error {
for _, ctl := range internet.Controllers {
if err := ctl(network, address, c); err != nil {
errors.LogInfoInner(context.Background(), err, "failed to apply external controller")
return err
}
}
return nil
},
}
r := &net.Resolver{
PreferGo: true,
Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
return d.DialContext(ctx, network, address)
},
}
return &Client{
d: d,
r: r,
}
} }

1
go.mod
View File

@@ -27,6 +27,7 @@ require (
golang.org/x/sys v0.43.0 golang.org/x/sys v0.43.0
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb
golang.zx2c4.com/wireguard/windows v0.5.3
google.golang.org/grpc v1.80.0 google.golang.org/grpc v1.80.0
google.golang.org/protobuf v1.36.11 google.golang.org/protobuf v1.36.11
gvisor.dev/gvisor v0.0.0-20260122175437-89a5d21be8f0 gvisor.dev/gvisor v0.0.0-20260122175437-89a5d21be8f0

2
go.sum
View File

@@ -131,6 +131,8 @@ golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeu
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb h1:whnFRlWMcXI9d+ZbWg+4sHnLp52d5yiIPUxMBSt4X9A= golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb h1:whnFRlWMcXI9d+ZbWg+4sHnLp52d5yiIPUxMBSt4X9A=
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb/go.mod h1:rpwXGsirqLqN2L0JDJQlwOboGHmptD5ZD6T2VmcqhTw= golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb/go.mod h1:rpwXGsirqLqN2L0JDJQlwOboGHmptD5ZD6T2VmcqhTw=
golang.zx2c4.com/wireguard/windows v0.5.3 h1:On6j2Rpn3OEMXqBq00QEDC7bWSZrPIHKIus8eIuExIE=
golang.zx2c4.com/wireguard/windows v0.5.3/go.mod h1:9TEe8TJmtwyQebdFwAkEWOPr3prrtqm+REGFifP60hI=
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260120221211-b8f7ae30c516 h1:sNrWoksmOyF5bvJUcnmbeAmQi8baNhqg5IWaI3llQqU= google.golang.org/genproto/googleapis/rpc v0.0.0-20260120221211-b8f7ae30c516 h1:sNrWoksmOyF5bvJUcnmbeAmQi8baNhqg5IWaI3llQqU=

View File

@@ -6,25 +6,39 @@ import (
) )
type TunConfig struct { type TunConfig struct {
Name string `json:"name"` Name string `json:"name"`
MTU uint32 `json:"MTU"` MTU []uint32 `json:"mtu"`
UserLevel uint32 `json:"userLevel"` Gateway []string `json:"gateway"`
DNS []string `json:"dns"`
UserLevel uint32 `json:"userLevel"`
AutoSystemRoutingTable []string `json:"autoSystemRoutingTable"`
AutoOutboundsInterface *string `json:"autoOutboundsInterface"`
} }
func (v *TunConfig) Build() (proto.Message, error) { func (v *TunConfig) Build() (proto.Message, error) {
config := &tun.Config{ config := &tun.Config{
Name: v.Name, Name: v.Name,
MTU: v.MTU, MTU: v.MTU,
UserLevel: v.UserLevel, Gateway: v.Gateway,
DNS: v.DNS,
UserLevel: v.UserLevel,
AutoSystemRoutingTable: v.AutoSystemRoutingTable,
}
if v.AutoOutboundsInterface != nil {
config.AutoOutboundsInterface = *v.AutoOutboundsInterface
}
if len(v.AutoSystemRoutingTable) > 0 && v.AutoOutboundsInterface == nil {
config.AutoOutboundsInterface = "auto"
} }
if v.Name == "" { if config.Name == "" {
config.Name = "xray0" config.Name = "xray0"
} }
if len(config.MTU) == 0 {
if v.MTU == 0 { config.MTU = []uint32{1500, 1280}
config.MTU = 1500 }
if len(config.MTU) == 1 {
config.MTU = append(config.MTU, config.MTU[0])
} }
return config, nil return config, nil
} }

View File

@@ -1 +1,78 @@
package tun package tun
import (
"context"
"net"
"sync"
"github.com/xtls/xray-core/common/errors"
)
type InterfaceUpdater struct {
sync.Mutex
tunIndex int
fixedName string
iface *net.Interface
}
var updater *InterfaceUpdater
func (updater *InterfaceUpdater) Get() *net.Interface {
updater.Lock()
defer updater.Unlock()
return updater.iface
}
func (updater *InterfaceUpdater) Update() {
updater.Lock()
defer updater.Unlock()
if updater.iface != nil {
iface, err := net.InterfaceByIndex(updater.iface.Index)
if err == nil && iface.Name == updater.iface.Name {
return
}
}
updater.iface = nil
interfaces, err := net.Interfaces()
if err != nil {
errors.LogInfoInner(context.Background(), err, "[tun] failed to update interface")
return
}
var got *net.Interface
for _, iface := range interfaces {
if iface.Index == updater.tunIndex {
continue
}
if updater.fixedName != "" {
if iface.Name == updater.fixedName {
got = &iface
break
}
} else {
addrs, err := iface.Addrs()
if err != nil {
continue
}
if (iface.Flags&net.FlagUp != 0) &&
(iface.Flags&net.FlagLoopback == 0) &&
len(addrs) > 0 {
got = &iface
break
}
}
}
if got == nil {
errors.LogInfo(context.Background(), "[tun] failed to update interface > got == nil")
return
}
updater.iface = got
errors.LogInfo(context.Background(), "[tun] update interface ", got.Name, " ", got.Index)
}

View File

@@ -22,12 +22,16 @@ const (
) )
type Config struct { type Config struct {
state protoimpl.MessageState `protogen:"open.v1"` state protoimpl.MessageState `protogen:"open.v1"`
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
MTU uint32 `protobuf:"varint,2,opt,name=MTU,proto3" json:"MTU,omitempty"` MTU []uint32 `protobuf:"varint,2,rep,packed,name=MTU,proto3" json:"MTU,omitempty"`
UserLevel uint32 `protobuf:"varint,3,opt,name=user_level,json=userLevel,proto3" json:"user_level,omitempty"` Gateway []string `protobuf:"bytes,3,rep,name=gateway,proto3" json:"gateway,omitempty"`
unknownFields protoimpl.UnknownFields DNS []string `protobuf:"bytes,4,rep,name=DNS,proto3" json:"DNS,omitempty"`
sizeCache protoimpl.SizeCache UserLevel uint32 `protobuf:"varint,5,opt,name=user_level,json=userLevel,proto3" json:"user_level,omitempty"`
AutoSystemRoutingTable []string `protobuf:"bytes,6,rep,name=auto_system_routing_table,json=autoSystemRoutingTable,proto3" json:"auto_system_routing_table,omitempty"`
AutoOutboundsInterface string `protobuf:"bytes,7,opt,name=auto_outbounds_interface,json=autoOutboundsInterface,proto3" json:"auto_outbounds_interface,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
} }
func (x *Config) Reset() { func (x *Config) Reset() {
@@ -67,11 +71,25 @@ func (x *Config) GetName() string {
return "" return ""
} }
func (x *Config) GetMTU() uint32 { func (x *Config) GetMTU() []uint32 {
if x != nil { if x != nil {
return x.MTU return x.MTU
} }
return 0 return nil
}
func (x *Config) GetGateway() []string {
if x != nil {
return x.Gateway
}
return nil
}
func (x *Config) GetDNS() []string {
if x != nil {
return x.DNS
}
return nil
} }
func (x *Config) GetUserLevel() uint32 { func (x *Config) GetUserLevel() uint32 {
@@ -81,16 +99,34 @@ func (x *Config) GetUserLevel() uint32 {
return 0 return 0
} }
func (x *Config) GetAutoSystemRoutingTable() []string {
if x != nil {
return x.AutoSystemRoutingTable
}
return nil
}
func (x *Config) GetAutoOutboundsInterface() string {
if x != nil {
return x.AutoOutboundsInterface
}
return ""
}
var File_proxy_tun_config_proto protoreflect.FileDescriptor var File_proxy_tun_config_proto protoreflect.FileDescriptor
const file_proxy_tun_config_proto_rawDesc = "" + const file_proxy_tun_config_proto_rawDesc = "" +
"\n" + "\n" +
"\x16proxy/tun/config.proto\x12\x0exray.proxy.tun\"M\n" + "\x16proxy/tun/config.proto\x12\x0exray.proxy.tun\"\xee\x01\n" +
"\x06Config\x12\x12\n" + "\x06Config\x12\x12\n" +
"\x04name\x18\x01 \x01(\tR\x04name\x12\x10\n" + "\x04name\x18\x01 \x01(\tR\x04name\x12\x10\n" +
"\x03MTU\x18\x02 \x01(\rR\x03MTU\x12\x1d\n" + "\x03MTU\x18\x02 \x03(\rR\x03MTU\x12\x18\n" +
"\agateway\x18\x03 \x03(\tR\agateway\x12\x10\n" +
"\x03DNS\x18\x04 \x03(\tR\x03DNS\x12\x1d\n" +
"\n" + "\n" +
"user_level\x18\x03 \x01(\rR\tuserLevelBL\n" + "user_level\x18\x05 \x01(\rR\tuserLevel\x129\n" +
"\x19auto_system_routing_table\x18\x06 \x03(\tR\x16autoSystemRoutingTable\x128\n" +
"\x18auto_outbounds_interface\x18\a \x01(\tR\x16autoOutboundsInterfaceBL\n" +
"\x12com.xray.proxy.tunP\x01Z#github.com/xtls/xray-core/proxy/tun\xaa\x02\x0eXray.Proxy.Tunb\x06proto3" "\x12com.xray.proxy.tunP\x01Z#github.com/xtls/xray-core/proxy/tun\xaa\x02\x0eXray.Proxy.Tunb\x06proto3"
var ( var (

View File

@@ -8,6 +8,10 @@ option java_multiple_files = true;
message Config { message Config {
string name = 1; string name = 1;
uint32 MTU = 2; repeated uint32 MTU = 2;
uint32 user_level = 3; repeated string gateway = 3;
repeated string DNS = 4;
uint32 user_level = 5;
repeated string auto_system_routing_table = 6;
string auto_outbounds_interface = 7;
} }

View File

@@ -2,6 +2,7 @@ package tun
import ( import (
"context" "context"
"syscall"
"github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/buf" "github.com/xtls/xray-core/common/buf"
@@ -15,6 +16,7 @@ import (
"github.com/xtls/xray-core/features/policy" "github.com/xtls/xray-core/features/policy"
"github.com/xtls/xray-core/features/routing" "github.com/xtls/xray-core/features/routing"
"github.com/xtls/xray-core/transport" "github.com/xtls/xray-core/transport"
"github.com/xtls/xray-core/transport/internet"
"github.com/xtls/xray-core/transport/internet/stat" "github.com/xtls/xray-core/transport/internet/stat"
) )
@@ -38,11 +40,6 @@ type ConnectionHandler interface {
// Handler implements ConnectionHandler // Handler implements ConnectionHandler
var _ ConnectionHandler = (*Handler)(nil) var _ ConnectionHandler = (*Handler)(nil)
func (t *Handler) policy() policy.Session {
p := t.policyManager.ForLevel(t.config.UserLevel)
return p
}
// Init the Handler instance with necessary parameters // Init the Handler instance with necessary parameters
func (t *Handler) Init(ctx context.Context, pm policy.Manager, dispatcher routing.Dispatcher) error { func (t *Handler) Init(ctx context.Context, pm policy.Manager, dispatcher routing.Dispatcher) error {
var err error var err error
@@ -60,15 +57,37 @@ func (t *Handler) Init(ctx context.Context, pm policy.Manager, dispatcher routin
t.dispatcher = dispatcher t.dispatcher = dispatcher
tunName := t.config.Name tunName := t.config.Name
tunOptions := TunOptions{ tunInterface, err := NewTun(t.config)
Name: tunName,
MTU: t.config.MTU,
}
tunInterface, err := NewTun(tunOptions)
if err != nil { if err != nil {
return err return err
} }
if t.config.AutoOutboundsInterface != "" {
tunIndex, err := tunInterface.Index()
if err != nil {
_ = tunInterface.Close()
return err
}
if t.config.AutoOutboundsInterface == "auto" {
t.config.AutoOutboundsInterface = ""
}
updater = &InterfaceUpdater{tunIndex: tunIndex, fixedName: t.config.AutoOutboundsInterface}
updater.Update()
internet.RegisterDialerController(func(network, address string, c syscall.RawConn) error {
iface := updater.Get()
if iface == nil {
errors.LogInfo(context.Background(), "[tun] falied to set interface > iface == nil")
return nil
}
return c.Control(func(fd uintptr) {
err := setinterface(network, address, fd, iface)
if err != nil {
errors.LogInfoInner(context.Background(), err, "[tun] falied to set interface")
}
})
})
}
errors.LogInfo(t.ctx, tunName, " created") errors.LogInfo(t.ctx, tunName, " created")
tunStackOptions := StackOptions{ tunStackOptions := StackOptions{

View File

@@ -34,23 +34,18 @@ const (
// stackGVisor is ip stack implemented by gVisor package // stackGVisor is ip stack implemented by gVisor package
type stackGVisor struct { type stackGVisor struct {
ctx context.Context ctx context.Context
tun GVisorTun tun Tun
idleTimeout time.Duration idleTimeout time.Duration
handler *Handler handler *Handler
stack *stack.Stack stack *stack.Stack
endpoint stack.LinkEndpoint endpoint stack.LinkEndpoint
} }
// GVisorTun implements a bridge to connect gVisor ip stack to tun interface
type GVisorTun interface {
newEndpoint() (stack.LinkEndpoint, error)
}
// NewStack builds new ip stack (using gVisor) // NewStack builds new ip stack (using gVisor)
func NewStack(ctx context.Context, options StackOptions, handler *Handler) (Stack, error) { func NewStack(ctx context.Context, options StackOptions, handler *Handler) (Stack, error) {
gStack := &stackGVisor{ gStack := &stackGVisor{
ctx: ctx, ctx: ctx,
tun: options.Tun.(GVisorTun), tun: options.Tun,
idleTimeout: options.IdleTimeout, idleTimeout: options.IdleTimeout,
handler: handler, handler: handler,
} }

View File

@@ -1,13 +1,12 @@
package tun package tun
import "gvisor.dev/gvisor/pkg/tcpip/stack"
// Tun interface implements tun interface interaction // Tun interface implements tun interface interaction
type Tun interface { type Tun interface {
Start() error Start() error
Close() error Close() error
} Name() (string, error)
Index() (int, error)
// TunOptions for tun interface implementation newEndpoint() (stack.LinkEndpoint, error)
type TunOptions struct {
Name string
MTU uint32
} }

View File

@@ -4,6 +4,7 @@ package tun
import ( import (
"context" "context"
"net"
"strconv" "strconv"
"github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/errors"
@@ -15,17 +16,14 @@ import (
type AndroidTun struct { type AndroidTun struct {
tunFd int tunFd int
options TunOptions options *Config
} }
// DefaultTun implements Tun // DefaultTun implements Tun
var _ Tun = (*AndroidTun)(nil) var _ Tun = (*AndroidTun)(nil)
// DefaultTun implements GVisorTun
var _ GVisorTun = (*AndroidTun)(nil)
// NewTun builds new tun interface handler // NewTun builds new tun interface handler
func NewTun(options TunOptions) (Tun, error) { func NewTun(options *Config) (Tun, error) {
fd, err := strconv.Atoi(platform.NewEnvFlag(platform.TunFdKey).GetValue(func() string { return "0" })) fd, err := strconv.Atoi(platform.NewEnvFlag(platform.TunFdKey).GetValue(func() string { return "0" }))
errors.LogInfo(context.Background(), "read Android Tun Fd ", fd, err) errors.LogInfo(context.Background(), "read Android Tun Fd ", fd, err)
@@ -49,10 +47,37 @@ func (t *AndroidTun) Close() error {
return nil return nil
} }
func (t *AndroidTun) Name() (string, error) {
ifr, err := unix.NewIfreq("")
if err != nil {
return "", err
}
if err = unix.IoctlIfreq(t.tunFd, unix.TUNGETIFF, ifr); err != nil {
return "", err
}
return ifr.Name(), nil
}
func (t *AndroidTun) Index() (int, error) {
name, err := t.Name()
if err != nil {
return 0, err
}
iface, err := net.InterfaceByName(name)
if err != nil {
return 0, err
}
return iface.Index, nil
}
func (t *AndroidTun) newEndpoint() (stack.LinkEndpoint, error) { func (t *AndroidTun) newEndpoint() (stack.LinkEndpoint, error) {
return fdbased.New(&fdbased.Options{ return fdbased.New(&fdbased.Options{
FDs: []int{t.tunFd}, FDs: []int{t.tunFd},
MTU: t.options.MTU, MTU: t.options.MTU[0],
RXChecksumOffload: true, RXChecksumOffload: true,
}) })
} }
func setinterface(network, address string, fd uintptr, iface *net.Interface) error {
return unix.BindToDevice(int(fd), iface.Name)
}

View File

@@ -3,16 +3,16 @@
package tun package tun
import ( import (
"errors" go_errors "errors"
"fmt" "fmt"
"net" "net"
"net/netip" "net/netip"
"os" "os"
"strconv" "strconv"
"syscall"
"unsafe" "unsafe"
"github.com/xtls/xray-core/common/buf" "github.com/xtls/xray-core/common/buf"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/platform" "github.com/xtls/xray-core/common/platform"
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/buffer" "gvisor.dev/gvisor/pkg/buffer"
@@ -25,6 +25,7 @@ const (
sysprotoControl = 2 sysprotoControl = 2
gateway = "169.254.10.1/30" gateway = "169.254.10.1/30"
utunHeaderSize = 4 utunHeaderSize = 4
UTUN_OPT_IFNAME = 2
) )
const ( const (
@@ -39,15 +40,15 @@ func procyield(cycles uint32)
type DarwinTun struct { type DarwinTun struct {
tunFile *os.File tunFile *os.File
options TunOptions options *Config
tunFd int
ownsFd bool // true for macOS (we created the fd), false for iOS (fd from system) ownsFd bool // true for macOS (we created the fd), false for iOS (fd from system)
} }
var _ Tun = (*DarwinTun)(nil) var _ Tun = (*DarwinTun)(nil)
var _ GVisorTun = (*DarwinTun)(nil)
var _ GVisorDevice = (*DarwinTun)(nil) var _ GVisorDevice = (*DarwinTun)(nil)
func NewTun(options TunOptions) (Tun, error) { func NewTun(options *Config) (Tun, error) {
// Check if fd is provided via environment (iOS mode) // Check if fd is provided via environment (iOS mode)
fdStr := platform.NewEnvFlag(platform.TunFdKey).GetValue(func() string { return "" }) fdStr := platform.NewEnvFlag(platform.TunFdKey).GetValue(func() string { return "" })
if fdStr != "" { if fdStr != "" {
@@ -64,6 +65,7 @@ func NewTun(options TunOptions) (Tun, error) {
return &DarwinTun{ return &DarwinTun{
tunFile: os.NewFile(uintptr(fd), "utun"), tunFile: os.NewFile(uintptr(fd), "utun"),
options: options, options: options,
tunFd: fd,
ownsFd: false, ownsFd: false,
}, nil }, nil
} }
@@ -74,7 +76,7 @@ func NewTun(options TunOptions) (Tun, error) {
return nil, err return nil, err
} }
err = setup(options.Name, options.MTU) err = setup(options.Name, options.MTU[0])
if err != nil { if err != nil {
_ = tunFile.Close() _ = tunFile.Close()
return nil, err return nil, err
@@ -83,6 +85,7 @@ func NewTun(options TunOptions) (Tun, error) {
return &DarwinTun{ return &DarwinTun{
tunFile: tunFile, tunFile: tunFile,
options: options, options: options,
tunFd: int(tunFile.Fd()),
ownsFd: true, ownsFd: true,
}, nil }, nil
} }
@@ -99,10 +102,26 @@ func (t *DarwinTun) Close() error {
return nil return nil
} }
func (t *DarwinTun) Name() (string, error) {
return unix.GetsockoptString(t.tunFd, sysprotoControl, UTUN_OPT_IFNAME)
}
func (t *DarwinTun) Index() (int, error) {
name, err := t.Name()
if err != nil {
return 0, err
}
iface, err := net.InterfaceByName(name)
if err != nil {
return 0, err
}
return iface.Index, nil
}
// WritePacket implements GVisorDevice method to write one packet to the tun device // WritePacket implements GVisorDevice method to write one packet to the tun device
func (t *DarwinTun) WritePacket(packet *stack.PacketBuffer) tcpip.Error { func (t *DarwinTun) WritePacket(packet *stack.PacketBuffer) tcpip.Error {
// request memory to write from reusable buffer pool // request memory to write from reusable buffer pool
b := buf.NewWithSize(int32(t.options.MTU) + utunHeaderSize) b := buf.NewWithSize(int32(t.options.MTU[0]) + utunHeaderSize)
defer b.Release() defer b.Release()
// prepare Darwin specific packet header // prepare Darwin specific packet header
@@ -124,7 +143,7 @@ func (t *DarwinTun) WritePacket(packet *stack.PacketBuffer) tcpip.Error {
b.SetByte(3, family) b.SetByte(3, family)
if _, err := t.tunFile.Write(b.Bytes()); err != nil { if _, err := t.tunFile.Write(b.Bytes()); err != nil {
if errors.Is(err, unix.EAGAIN) { if go_errors.Is(err, unix.EAGAIN) {
return &tcpip.ErrWouldBlock{} return &tcpip.ErrWouldBlock{}
} }
return &tcpip.ErrAborted{} return &tcpip.ErrAborted{}
@@ -137,11 +156,11 @@ func (t *DarwinTun) WritePacket(packet *stack.PacketBuffer) tcpip.Error {
// which will make the stack call Wait which should implement desired push-back // which will make the stack call Wait which should implement desired push-back
func (t *DarwinTun) ReadPacket() (byte, *stack.PacketBuffer, error) { func (t *DarwinTun) ReadPacket() (byte, *stack.PacketBuffer, error) {
// request memory to write from reusable buffer pool // request memory to write from reusable buffer pool
b := buf.NewWithSize(int32(t.options.MTU) + utunHeaderSize) b := buf.NewWithSize(int32(t.options.MTU[0]) + utunHeaderSize)
// read the bytes to the interface file // read the bytes to the interface file
n, err := b.ReadFrom(t.tunFile) n, err := b.ReadFrom(t.tunFile)
if errors.Is(err, unix.EAGAIN) || errors.Is(err, unix.EINTR) { if go_errors.Is(err, unix.EAGAIN) || go_errors.Is(err, unix.EINTR) {
b.Release() b.Release()
return 0, nil, ErrQueueEmpty return 0, nil, ErrQueueEmpty
} }
@@ -174,7 +193,7 @@ func (t *DarwinTun) Wait() {
} }
func (t *DarwinTun) newEndpoint() (stack.LinkEndpoint, error) { func (t *DarwinTun) newEndpoint() (stack.LinkEndpoint, error) {
return &LinkEndpoint{deviceMTU: t.options.MTU, device: t}, nil return &LinkEndpoint{deviceMTU: t.options.MTU[0], device: t}, nil
} }
// open the interface, by creating new utunN if in the system and returning its file descriptor // open the interface, by creating new utunN if in the system and returning its file descriptor
@@ -346,9 +365,20 @@ func setIPAddress(name string, gateway netip.Prefix) error {
} }
func ioctlPtr(fd int, req uint, arg unsafe.Pointer) error { func ioctlPtr(fd int, req uint, arg unsafe.Pointer) error {
_, _, errno := unix.Syscall(syscall.SYS_IOCTL, uintptr(fd), uintptr(req), uintptr(arg)) _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(fd), uintptr(req), uintptr(arg))
if errno != 0 { if errno != 0 {
return errno return errno
} }
return nil return nil
} }
func setinterface(network, address string, fd uintptr, iface *net.Interface) error {
switch network {
case "tcp4", "udp4", "ip4":
return unix.SetsockoptInt(int(fd), unix.IPPROTO_IP, unix.IP_BOUND_IF, iface.Index)
case "tcp6", "udp6", "ip6":
return unix.SetsockoptInt(int(fd), unix.IPPROTO_IPV6, unix.IPV6_BOUND_IF, iface.Index)
default:
return errors.New("unknown network ", network)
}
}

View File

@@ -3,6 +3,8 @@
package tun package tun
import ( import (
"net"
"github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/errors"
"gvisor.dev/gvisor/pkg/tcpip/stack" "gvisor.dev/gvisor/pkg/tcpip/stack"
) )
@@ -13,11 +15,8 @@ type DefaultTun struct {
// DefaultTun implements Tun // DefaultTun implements Tun
var _ Tun = (*DefaultTun)(nil) var _ Tun = (*DefaultTun)(nil)
// DefaultTun implements GVisorTun
var _ GVisorTun = (*DefaultTun)(nil)
// NewTun builds new tun interface handler // NewTun builds new tun interface handler
func NewTun(options TunOptions) (Tun, error) { func NewTun(options *Config) (Tun, error) {
return nil, errors.New("Tun is not supported on your platform") return nil, errors.New("Tun is not supported on your platform")
} }
@@ -29,6 +28,18 @@ func (t *DefaultTun) Close() error {
return errors.New("Tun is not supported on your platform") return errors.New("Tun is not supported on your platform")
} }
func (t *DefaultTun) Name() (string, error) {
return "", errors.New("Tun is not supported on your platform")
}
func (t *DefaultTun) Index() (int, error) {
return 0, errors.New("Tun is not supported on your platform")
}
func (t *DefaultTun) newEndpoint() (stack.LinkEndpoint, error) { func (t *DefaultTun) newEndpoint() (stack.LinkEndpoint, error) {
return nil, errors.New("Tun is not supported on your platform") return nil, errors.New("Tun is not supported on your platform")
} }
func setinterface(string, string, uintptr, *net.Interface) error {
return errors.New("Tun is not supported on your platform")
}

View File

@@ -3,6 +3,8 @@
package tun package tun
import ( import (
"net"
"github.com/vishvananda/netlink" "github.com/vishvananda/netlink"
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/tcpip/link/fdbased" "gvisor.dev/gvisor/pkg/tcpip/link/fdbased"
@@ -15,23 +17,20 @@ import (
type LinuxTun struct { type LinuxTun struct {
tunFd int tunFd int
tunLink netlink.Link tunLink netlink.Link
options TunOptions options *Config
} }
// LinuxTun implements Tun // LinuxTun implements Tun
var _ Tun = (*LinuxTun)(nil) var _ Tun = (*LinuxTun)(nil)
// LinuxTun implements GVisorTun
var _ GVisorTun = (*LinuxTun)(nil)
// NewTun builds new tun interface handler (linux specific) // NewTun builds new tun interface handler (linux specific)
func NewTun(options TunOptions) (Tun, error) { func NewTun(options *Config) (Tun, error) {
tunFd, err := open(options.Name) tunFd, err := open(options.Name)
if err != nil { if err != nil {
return nil, err return nil, err
} }
tunLink, err := setup(options.Name, int(options.MTU)) tunLink, err := setup(options.Name, int(options.MTU[0]))
if err != nil { if err != nil {
_ = unix.Close(tunFd) _ = unix.Close(tunFd)
return nil, err return nil, err
@@ -110,11 +109,23 @@ func (t *LinuxTun) Close() error {
return nil return nil
} }
func (t *LinuxTun) Name() (string, error) {
return t.tunLink.Attrs().Name, nil
}
func (t *LinuxTun) Index() (int, error) {
return t.tunLink.Attrs().Index, nil
}
// newEndpoint builds new gVisor stack.LinkEndpoint from the tun interface file descriptor // newEndpoint builds new gVisor stack.LinkEndpoint from the tun interface file descriptor
func (t *LinuxTun) newEndpoint() (stack.LinkEndpoint, error) { func (t *LinuxTun) newEndpoint() (stack.LinkEndpoint, error) {
return fdbased.New(&fdbased.Options{ return fdbased.New(&fdbased.Options{
FDs: []int{t.tunFd}, FDs: []int{t.tunFd},
MTU: t.options.MTU, MTU: t.options.MTU[0],
RXChecksumOffload: true, RXChecksumOffload: true,
}) })
} }
func setinterface(network, address string, fd uintptr, iface *net.Interface) error {
return unix.BindToDevice(int(fd), iface.Name)
}

View File

@@ -4,11 +4,17 @@ package tun
import ( import (
"crypto/md5" "crypto/md5"
"errors" "encoding/binary"
go_errors "errors"
"net"
"net/netip"
"sync"
"unsafe" "unsafe"
"github.com/xtls/xray-core/common/errors"
"golang.org/x/sys/windows" "golang.org/x/sys/windows"
"golang.zx2c4.com/wintun" "golang.zx2c4.com/wintun"
"golang.zx2c4.com/wireguard/windows/tunnel/winipcfg"
"gvisor.dev/gvisor/pkg/buffer" "gvisor.dev/gvisor/pkg/buffer"
"gvisor.dev/gvisor/pkg/tcpip" "gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/stack" "gvisor.dev/gvisor/pkg/tcpip/stack"
@@ -21,25 +27,26 @@ func procyield(cycles uint32)
// current version is heavily stripped to do nothing more, // current version is heavily stripped to do nothing more,
// then create a network interface, to be provided as endpoint to gVisor ip stack // then create a network interface, to be provided as endpoint to gVisor ip stack
type WindowsTun struct { type WindowsTun struct {
options TunOptions sync.RWMutex
adapter *wintun.Adapter
session wintun.Session options *Config
readWait windows.Handle adapter *wintun.Adapter
MTU uint32 session wintun.Session
readWait windows.Handle
luid winipcfg.LUID
changeCallback winipcfg.ChangeCallback
closed bool
} }
// WindowsTun implements Tun // WindowsTun implements Tun
var _ Tun = (*WindowsTun)(nil) var _ Tun = (*WindowsTun)(nil)
// WindowsTun implements GVisorTun
var _ GVisorTun = (*WindowsTun)(nil)
// WindowsTun implements GVisorDevice // WindowsTun implements GVisorDevice
var _ GVisorDevice = (*WindowsTun)(nil) var _ GVisorDevice = (*WindowsTun)(nil)
// NewTun creates a Wintun interface with the given name. Should a Wintun // NewTun creates a Wintun interface with the given name. Should a Wintun
// interface with the same name exist, it tried to be reused. // interface with the same name exist, it tried to be reused.
func NewTun(options TunOptions) (Tun, error) { func NewTun(options *Config) (Tun, error) {
// instantiate wintun adapter // instantiate wintun adapter
adapter, err := open(options.Name) adapter, err := open(options.Name)
if err != nil { if err != nil {
@@ -58,9 +65,7 @@ func NewTun(options TunOptions) (Tun, error) {
adapter: adapter, adapter: adapter,
session: session, session: session,
readWait: session.ReadWaitEvent(), readWait: session.ReadWaitEvent(),
// there is currently no iphndl.dll support, which is the netlink library for windows luid: winipcfg.LUID(adapter.LUID()),
// so there is nowhere to change MTU for the Wintun interface, and we take its default value
MTU: wintun.PacketSizeMax,
} }
return tun, nil return tun, nil
@@ -70,13 +75,8 @@ func open(name string) (*wintun.Adapter, error) {
// generate a deterministic GUID from the adapter name // generate a deterministic GUID from the adapter name
id := md5.Sum([]byte(name)) id := md5.Sum([]byte(name))
guid := (*windows.GUID)(unsafe.Pointer(&id[0])) guid := (*windows.GUID)(unsafe.Pointer(&id[0]))
// try to open existing adapter by name
adapter, err := wintun.OpenAdapter(name)
if err == nil {
return adapter, nil
}
// try to create adapter anew // try to create adapter anew
adapter, err = wintun.CreateAdapter(name, "Xray", guid) adapter, err := wintun.CreateAdapter(name, "Xray", guid)
if err == nil { if err == nil {
return adapter, nil return adapter, nil
} }
@@ -84,18 +84,153 @@ func open(name string) (*wintun.Adapter, error) {
} }
func (t *WindowsTun) Start() error { func (t *WindowsTun) Start() error {
var has4, has6 bool
allowedIPs := make([]netip.Prefix, 0, len(t.options.AutoSystemRoutingTable))
for _, route := range t.options.AutoSystemRoutingTable {
allowedIPs = append(allowedIPs, netip.MustParsePrefix(route))
}
routesMap := make(map[winipcfg.RouteData]struct{})
for _, ip := range allowedIPs {
route := winipcfg.RouteData{
Destination: ip.Masked(),
Metric: 0,
}
if ip.Addr().Is4() {
has4 = true
route.NextHop = netip.IPv4Unspecified()
} else {
has6 = true
route.NextHop = netip.IPv6Unspecified()
}
routesMap[route] = struct{}{}
}
routesData := make([]*winipcfg.RouteData, 0, len(routesMap))
for route := range routesMap {
r := route
routesData = append(routesData, &r)
}
err := t.luid.SetRoutes(routesData)
if err != nil {
return errors.New("unable to set routes").Base(err)
}
if len(t.options.Gateway) > 0 {
addresses := make([]netip.Prefix, 0, len(t.options.Gateway))
for _, address := range t.options.Gateway {
addresses = append(addresses, netip.MustParsePrefix(address))
}
err := t.luid.SetIPAddresses(addresses)
if err != nil {
return errors.New("unable to set ips").Base(err)
}
}
if has4 {
ipif, err := t.luid.IPInterface(windows.AF_INET)
if err != nil {
return err
}
ipif.RouterDiscoveryBehavior = winipcfg.RouterDiscoveryDisabled
ipif.DadTransmits = 0
ipif.ManagedAddressConfigurationSupported = false
ipif.OtherStatefulConfigurationSupported = false
ipif.NLMTU = t.options.MTU[0]
ipif.UseAutomaticMetric = false
ipif.Metric = 0
err = ipif.Set()
if err != nil {
return err
}
}
if has6 {
ipif, err := t.luid.IPInterface(windows.AF_INET6)
if err != nil {
return err
}
ipif.RouterDiscoveryBehavior = winipcfg.RouterDiscoveryDisabled
ipif.DadTransmits = 0
ipif.ManagedAddressConfigurationSupported = false
ipif.OtherStatefulConfigurationSupported = false
ipif.NLMTU = t.options.MTU[1]
ipif.UseAutomaticMetric = false
ipif.Metric = 0
err = ipif.Set()
if err != nil {
return err
}
}
if len(t.options.DNS) > 0 {
dns := make([]netip.Addr, 0, len(t.options.DNS))
for _, ip := range t.options.DNS {
dns = append(dns, netip.MustParseAddr(ip))
}
err := t.luid.SetDNS(windows.AF_INET, dns, nil)
if err != nil {
return err
}
err = t.luid.SetDNS(windows.AF_INET6, dns, nil)
if err != nil {
return err
}
}
if updater != nil {
t.changeCallback, err = winipcfg.RegisterInterfaceChangeCallback(func(notificationType winipcfg.MibNotificationType, iface *winipcfg.MibIPInterfaceRow) {
if notificationType != winipcfg.MibDeleteInstance {
return
}
updater.Update()
})
if err != nil {
return err
}
}
return nil return nil
} }
func (t *WindowsTun) Close() error { func (t *WindowsTun) Close() error {
t.Lock()
defer t.Unlock()
if t.closed {
return nil
}
t.closed = true
if t.changeCallback != nil {
t.changeCallback.Unregister()
}
t.session.End() t.session.End()
_ = t.adapter.Close() _ = t.adapter.Close()
return nil return nil
} }
func (t *WindowsTun) Name() (string, error) {
row, err := t.luid.Interface()
if err != nil {
return "", err
}
return row.Alias(), nil
}
func (t *WindowsTun) Index() (int, error) {
row, err := t.luid.Interface()
if err != nil {
return 0, err
}
return int(row.InterfaceIndex), nil
}
// WritePacket implements GVisorDevice method to write one packet to the tun device // WritePacket implements GVisorDevice method to write one packet to the tun device
func (t *WindowsTun) WritePacket(packetBuffer *stack.PacketBuffer) tcpip.Error { func (t *WindowsTun) WritePacket(packetBuffer *stack.PacketBuffer) tcpip.Error {
t.RLock()
defer t.RUnlock()
if t.closed {
return &tcpip.ErrClosedForSend{}
}
// request buffer from Wintun // request buffer from Wintun
packet, err := t.session.AllocateSendPacket(packetBuffer.Size()) packet, err := t.session.AllocateSendPacket(packetBuffer.Size())
if err != nil { if err != nil {
@@ -119,7 +254,7 @@ func (t *WindowsTun) WritePacket(packetBuffer *stack.PacketBuffer) tcpip.Error {
// which will make the stack call Wait which should implement desired push-back // which will make the stack call Wait which should implement desired push-back
func (t *WindowsTun) ReadPacket() (byte, *stack.PacketBuffer, error) { func (t *WindowsTun) ReadPacket() (byte, *stack.PacketBuffer, error) {
packet, err := t.session.ReceivePacket() packet, err := t.session.ReceivePacket()
if errors.Is(err, windows.ERROR_NO_MORE_ITEMS) { if go_errors.Is(err, windows.ERROR_NO_MORE_ITEMS) {
return 0, nil, ErrQueueEmpty return 0, nil, ErrQueueEmpty
} }
if err != nil { if err != nil {
@@ -143,5 +278,38 @@ func (t *WindowsTun) Wait() {
} }
func (t *WindowsTun) newEndpoint() (stack.LinkEndpoint, error) { func (t *WindowsTun) newEndpoint() (stack.LinkEndpoint, error) {
return &LinkEndpoint{deviceMTU: t.options.MTU, device: t}, nil return &LinkEndpoint{deviceMTU: t.options.MTU[0], device: t}, nil
}
const (
IP_UNICAST_IF = 31
IPV6_UNICAST_IF = 31
)
func setinterface(network, address string, fd uintptr, iface *net.Interface) error {
var index [4]byte
binary.BigEndian.PutUint32(index[:], uint32(iface.Index))
switch network {
case "tcp4", "udp4", "ip4":
err := windows.SetsockoptInt(windows.Handle(fd), windows.IPPROTO_IP, IP_UNICAST_IF, *(*int)(unsafe.Pointer(&index[0])))
if err != nil {
return err
}
if network == "udp4" {
return windows.SetsockoptInt(windows.Handle(fd), windows.IPPROTO_IP, windows.IP_MULTICAST_IF, *(*int)(unsafe.Pointer(&index[0])))
}
case "tcp6", "udp6", "ip6":
err := windows.SetsockoptInt(windows.Handle(fd), windows.IPPROTO_IPV6, IPV6_UNICAST_IF, iface.Index)
if err != nil {
return err
}
if network == "udp6" {
return windows.SetsockoptInt(windows.Handle(fd), windows.IPPROTO_IPV6, windows.IPV6_MULTICAST_IF, iface.Index)
}
default:
return errors.New("unknown network ", network)
}
return nil
} }

View File

@@ -2,6 +2,7 @@ package internet
import ( import (
"context" "context"
"sync"
"syscall" "syscall"
"time" "time"
@@ -11,6 +12,8 @@ import (
"github.com/xtls/xray-core/features/outbound" "github.com/xtls/xray-core/features/outbound"
) )
var Controllers []func(network, address string, c syscall.RawConn) error
var ControllersLock sync.Mutex
var effectiveSystemDialer SystemDialer = &DefaultSystemDialer{} var effectiveSystemDialer SystemDialer = &DefaultSystemDialer{}
type SystemDialer interface { type SystemDialer interface {
@@ -19,9 +22,8 @@ type SystemDialer interface {
} }
type DefaultSystemDialer struct { type DefaultSystemDialer struct {
controllers []func(network, address string, c syscall.RawConn) error dns dns.Client
dns dns.Client obm outbound.Manager
obm outbound.Manager
} }
func resolveSrcAddr(network net.Network, src net.Address) net.Addr { func resolveSrcAddr(network net.Network, src net.Address) net.Addr {
@@ -63,7 +65,7 @@ func (d *DefaultSystemDialer) Dial(ctx context.Context, src net.Address, dest ne
return nil, err return nil, err
} }
lc.Control = func(network, address string, c syscall.RawConn) error { lc.Control = func(network, address string, c syscall.RawConn) error {
for _, ctl := range d.controllers { for _, ctl := range Controllers {
if err := ctl(network, address, c); err != nil { if err := ctl(network, address, c); err != nil {
errors.LogInfoInner(ctx, err, "failed to apply external controller") errors.LogInfoInner(ctx, err, "failed to apply external controller")
} }
@@ -115,12 +117,12 @@ func (d *DefaultSystemDialer) Dial(ctx context.Context, src net.Address, dest ne
KeepAliveConfig: keepAliveConfig, KeepAliveConfig: keepAliveConfig,
} }
if sockopt != nil || len(d.controllers) > 0 { if sockopt != nil || len(Controllers) > 0 {
if sockopt != nil && sockopt.TcpMptcp { if sockopt != nil && sockopt.TcpMptcp {
dialer.SetMultipathTCP(true) dialer.SetMultipathTCP(true)
} }
dialer.Control = func(network, address string, c syscall.RawConn) error { dialer.Control = func(network, address string, c syscall.RawConn) error {
for _, ctl := range d.controllers { for _, ctl := range Controllers {
if err := ctl(network, address, c); err != nil { if err := ctl(network, address, c); err != nil {
errors.LogInfoInner(ctx, err, "failed to apply external controller") errors.LogInfoInner(ctx, err, "failed to apply external controller")
} }
@@ -208,12 +210,15 @@ func RegisterDialerController(ctl func(network, address string, c syscall.RawCon
return errors.New("nil listener controller") return errors.New("nil listener controller")
} }
dialer, ok := effectiveSystemDialer.(*DefaultSystemDialer) ControllersLock.Lock()
Controllers = append(Controllers, ctl)
ControllersLock.Unlock()
_, ok := effectiveSystemDialer.(*DefaultSystemDialer)
if !ok { if !ok {
return errors.New("RegisterListenerController not supported in custom dialer") return errors.New("RegisterListenerController not supported in custom dialer")
} }
dialer.controllers = append(dialer.controllers, ctl)
return nil return nil
} }