diff --git a/features/dns/localdns/client.go b/features/dns/localdns/client.go index 48e740ee962d..5ba8185940e0 100644 --- a/features/dns/localdns/client.go +++ b/features/dns/localdns/client.go @@ -1,12 +1,21 @@ package localdns import ( + "context" + "syscall" + "time" + + "github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/net" "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. -type Client struct{} +type Client struct { + d *net.Dialer + r *net.Resolver +} // Type implements common.HasType. func (*Client) Type() interface{} { @@ -20,8 +29,14 @@ func (*Client) Start() error { return nil } func (*Client) Close() error { return nil } // LookupIP implements Client. -func (*Client) LookupIP(host string, option dns.IPOption) ([]net.IP, uint32, error) { - ips, err := net.LookupIP(host) +func (c *Client) LookupIP(host string, option dns.IPOption) ([]net.IP, uint32, error) { + 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 { 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. 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, + } } diff --git a/go.mod b/go.mod index c9dcff8cfd28..36d626f54efb 100644 --- a/go.mod +++ b/go.mod @@ -27,6 +27,7 @@ require ( golang.org/x/sys v0.42.0 golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 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/protobuf v1.36.11 gvisor.dev/gvisor v0.0.0-20260122175437-89a5d21be8f0 diff --git a/go.sum b/go.sum index da6e58fe1cb1..bdf3e57f0fb5 100644 --- a/go.sum +++ b/go.sum @@ -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/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/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/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= google.golang.org/genproto/googleapis/rpc v0.0.0-20260120221211-b8f7ae30c516 h1:sNrWoksmOyF5bvJUcnmbeAmQi8baNhqg5IWaI3llQqU= diff --git a/infra/conf/tun.go b/infra/conf/tun.go index 2d95e3cd93cd..7e89cd720d2e 100644 --- a/infra/conf/tun.go +++ b/infra/conf/tun.go @@ -6,24 +6,42 @@ import ( ) type TunConfig struct { - Name string `json:"name"` - MTU uint32 `json:"MTU"` - UserLevel uint32 `json:"userLevel"` + Name string `json:"name"` + MTU []uint32 `json:"mtu"` + Gateway []string `json:"gateway"` + DNS []string `json:"dns"` + UserLevel uint32 `json:"userLevel"` + AutoRoutingTable []string `json:"autoRoutingTable"` + AutoOutboundsInterface *string `json:"autoOutboundsInterface"` } func (v *TunConfig) Build() (proto.Message, error) { config := &tun.Config{ - Name: v.Name, - MTU: v.MTU, - UserLevel: v.UserLevel, + Name: v.Name, + MTU: v.MTU, + Gateway: v.Gateway, + DNS: v.DNS, + UserLevel: v.UserLevel, + AutoRoutingTable: v.AutoRoutingTable, + } + + if v.AutoOutboundsInterface != nil { + config.AutoOutboundsInterface = *v.AutoOutboundsInterface } if v.Name == "" { config.Name = "xray0" } - if v.MTU == 0 { - config.MTU = 1500 + if len(v.MTU) == 1 { + v.MTU = append(v.MTU, v.MTU[0]) + } + if len(v.MTU) == 0 { + v.MTU = []uint32{1500, 1280} + } + + if len(config.AutoRoutingTable) > 0 && v.AutoOutboundsInterface == nil { + config.AutoOutboundsInterface = "auto" } return config, nil diff --git a/proxy/tun/config.go b/proxy/tun/config.go index 75e8485f7cdd..cef14b5310cc 100644 --- a/proxy/tun/config.go +++ b/proxy/tun/config.go @@ -1 +1,78 @@ 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) +} diff --git a/proxy/tun/config.pb.go b/proxy/tun/config.pb.go index 0c2b2e89af87..ad6abeeac0ed 100644 --- a/proxy/tun/config.pb.go +++ b/proxy/tun/config.pb.go @@ -22,12 +22,16 @@ const ( ) type Config struct { - state protoimpl.MessageState `protogen:"open.v1"` - Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` - MTU uint32 `protobuf:"varint,2,opt,name=MTU,proto3" json:"MTU,omitempty"` - UserLevel uint32 `protobuf:"varint,3,opt,name=user_level,json=userLevel,proto3" json:"user_level,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + MTU []uint32 `protobuf:"varint,2,rep,packed,name=MTU,proto3" json:"MTU,omitempty"` + Gateway []string `protobuf:"bytes,3,rep,name=gateway,proto3" json:"gateway,omitempty"` + DNS []string `protobuf:"bytes,4,rep,name=DNS,proto3" json:"DNS,omitempty"` + UserLevel uint32 `protobuf:"varint,5,opt,name=user_level,json=userLevel,proto3" json:"user_level,omitempty"` + AutoRoutingTable []string `protobuf:"bytes,6,rep,name=auto_routing_table,json=autoRoutingTable,proto3" json:"auto_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() { @@ -67,11 +71,25 @@ func (x *Config) GetName() string { return "" } -func (x *Config) GetMTU() uint32 { +func (x *Config) GetMTU() []uint32 { if x != nil { 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 { @@ -81,16 +99,34 @@ func (x *Config) GetUserLevel() uint32 { return 0 } +func (x *Config) GetAutoRoutingTable() []string { + if x != nil { + return x.AutoRoutingTable + } + return nil +} + +func (x *Config) GetAutoOutboundsInterface() string { + if x != nil { + return x.AutoOutboundsInterface + } + return "" +} + var File_proxy_tun_config_proto protoreflect.FileDescriptor const file_proxy_tun_config_proto_rawDesc = "" + "\n" + - "\x16proxy/tun/config.proto\x12\x0exray.proxy.tun\"M\n" + + "\x16proxy/tun/config.proto\x12\x0exray.proxy.tun\"\xe1\x01\n" + "\x06Config\x12\x12\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" + - "user_level\x18\x03 \x01(\rR\tuserLevelBL\n" + + "user_level\x18\x05 \x01(\rR\tuserLevel\x12,\n" + + "\x12auto_routing_table\x18\x06 \x03(\tR\x10autoRoutingTable\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" var ( diff --git a/proxy/tun/config.proto b/proxy/tun/config.proto index 24e5e527cb6c..c79dd464cf0e 100644 --- a/proxy/tun/config.proto +++ b/proxy/tun/config.proto @@ -8,6 +8,10 @@ option java_multiple_files = true; message Config { string name = 1; - uint32 MTU = 2; - uint32 user_level = 3; + repeated uint32 MTU = 2; + repeated string gateway = 3; + repeated string DNS = 4; + uint32 user_level = 5; + repeated string auto_routing_table = 6; + string auto_outbounds_interface = 7; } diff --git a/proxy/tun/handler.go b/proxy/tun/handler.go index 8a7fb25382c3..1e8e030e6e56 100644 --- a/proxy/tun/handler.go +++ b/proxy/tun/handler.go @@ -2,6 +2,7 @@ package tun import ( "context" + "syscall" "github.com/xtls/xray-core/common" "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/routing" "github.com/xtls/xray-core/transport" + "github.com/xtls/xray-core/transport/internet" "github.com/xtls/xray-core/transport/internet/stat" ) @@ -38,11 +40,6 @@ type ConnectionHandler interface { // Handler implements ConnectionHandler 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 func (t *Handler) Init(ctx context.Context, pm policy.Manager, dispatcher routing.Dispatcher) error { var err error @@ -60,15 +57,37 @@ func (t *Handler) Init(ctx context.Context, pm policy.Manager, dispatcher routin t.dispatcher = dispatcher tunName := t.config.Name - tunOptions := TunOptions{ - Name: tunName, - MTU: t.config.MTU, - } - tunInterface, err := NewTun(tunOptions) + tunInterface, err := NewTun(t.config) if err != nil { 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") tunStackOptions := StackOptions{ diff --git a/proxy/tun/stack_gvisor.go b/proxy/tun/stack_gvisor.go index 8bcc4ebef21b..0e9db8ffe90c 100644 --- a/proxy/tun/stack_gvisor.go +++ b/proxy/tun/stack_gvisor.go @@ -34,23 +34,18 @@ const ( // stackGVisor is ip stack implemented by gVisor package type stackGVisor struct { ctx context.Context - tun GVisorTun + tun Tun idleTimeout time.Duration handler *Handler stack *stack.Stack 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) func NewStack(ctx context.Context, options StackOptions, handler *Handler) (Stack, error) { gStack := &stackGVisor{ ctx: ctx, - tun: options.Tun.(GVisorTun), + tun: options.Tun, idleTimeout: options.IdleTimeout, handler: handler, } diff --git a/proxy/tun/tun.go b/proxy/tun/tun.go index deb8511faf7e..48592b7de3a9 100644 --- a/proxy/tun/tun.go +++ b/proxy/tun/tun.go @@ -1,13 +1,12 @@ package tun +import "gvisor.dev/gvisor/pkg/tcpip/stack" + // Tun interface implements tun interface interaction type Tun interface { Start() error Close() error -} - -// TunOptions for tun interface implementation -type TunOptions struct { - Name string - MTU uint32 + Name() (string, error) + Index() (int, error) + newEndpoint() (stack.LinkEndpoint, error) } diff --git a/proxy/tun/tun_android.go b/proxy/tun/tun_android.go index eaa9339f92a6..9d7923b6d125 100644 --- a/proxy/tun/tun_android.go +++ b/proxy/tun/tun_android.go @@ -4,6 +4,7 @@ package tun import ( "context" + "net" "strconv" "github.com/xtls/xray-core/common/errors" @@ -15,17 +16,14 @@ import ( type AndroidTun struct { tunFd int - options TunOptions + options *Config } // DefaultTun implements Tun var _ Tun = (*AndroidTun)(nil) -// DefaultTun implements GVisorTun -var _ GVisorTun = (*AndroidTun)(nil) - // 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" })) errors.LogInfo(context.Background(), "read Android Tun Fd ", fd, err) @@ -49,10 +47,37 @@ func (t *AndroidTun) Close() error { 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) { return fdbased.New(&fdbased.Options{ FDs: []int{t.tunFd}, - MTU: t.options.MTU, + MTU: t.options.MTU[0], RXChecksumOffload: true, }) } + +func setinterface(network, address string, fd uintptr, iface *net.Interface) error { + return unix.BindToDevice(int(fd), iface.Name) +} diff --git a/proxy/tun/tun_darwin.go b/proxy/tun/tun_darwin.go index ad9f4783a18e..5025a4704bf1 100644 --- a/proxy/tun/tun_darwin.go +++ b/proxy/tun/tun_darwin.go @@ -3,16 +3,16 @@ package tun import ( - "errors" + go_errors "errors" "fmt" "net" "net/netip" "os" "strconv" - "syscall" "unsafe" "github.com/xtls/xray-core/common/buf" + "github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/platform" "golang.org/x/sys/unix" "gvisor.dev/gvisor/pkg/buffer" @@ -25,6 +25,7 @@ const ( sysprotoControl = 2 gateway = "169.254.10.1/30" utunHeaderSize = 4 + UTUN_OPT_IFNAME = 2 ) const ( @@ -39,15 +40,15 @@ func procyield(cycles uint32) type DarwinTun struct { tunFile *os.File - options TunOptions + options *Config + tunFd int ownsFd bool // true for macOS (we created the fd), false for iOS (fd from system) } var _ Tun = (*DarwinTun)(nil) -var _ GVisorTun = (*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) fdStr := platform.NewEnvFlag(platform.TunFdKey).GetValue(func() string { return "" }) if fdStr != "" { @@ -64,6 +65,7 @@ func NewTun(options TunOptions) (Tun, error) { return &DarwinTun{ tunFile: os.NewFile(uintptr(fd), "utun"), options: options, + tunFd: fd, ownsFd: false, }, nil } @@ -74,7 +76,7 @@ func NewTun(options TunOptions) (Tun, error) { return nil, err } - err = setup(options.Name, options.MTU) + err = setup(options.Name, options.MTU[0]) if err != nil { _ = tunFile.Close() return nil, err @@ -83,6 +85,7 @@ func NewTun(options TunOptions) (Tun, error) { return &DarwinTun{ tunFile: tunFile, options: options, + tunFd: int(tunFile.Fd()), ownsFd: true, }, nil } @@ -99,10 +102,26 @@ func (t *DarwinTun) Close() error { 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 func (t *DarwinTun) WritePacket(packet *stack.PacketBuffer) tcpip.Error { // 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() // prepare Darwin specific packet header @@ -124,7 +143,7 @@ func (t *DarwinTun) WritePacket(packet *stack.PacketBuffer) tcpip.Error { b.SetByte(3, family) 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.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 func (t *DarwinTun) ReadPacket() (byte, *stack.PacketBuffer, error) { // 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 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() return 0, nil, ErrQueueEmpty } @@ -174,7 +193,7 @@ func (t *DarwinTun) Wait() { } 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 @@ -346,9 +365,20 @@ func setIPAddress(name string, gateway netip.Prefix) 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 { return errno } 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) + } +} diff --git a/proxy/tun/tun_default.go b/proxy/tun/tun_default.go index ee061934c1b5..9c5c8e83b6a8 100644 --- a/proxy/tun/tun_default.go +++ b/proxy/tun/tun_default.go @@ -3,6 +3,8 @@ package tun import ( + "net" + "github.com/xtls/xray-core/common/errors" "gvisor.dev/gvisor/pkg/tcpip/stack" ) @@ -13,11 +15,8 @@ type DefaultTun struct { // DefaultTun implements Tun var _ Tun = (*DefaultTun)(nil) -// DefaultTun implements GVisorTun -var _ GVisorTun = (*DefaultTun)(nil) - // 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") } @@ -29,6 +28,18 @@ func (t *DefaultTun) Close() error { 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) { 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") +} diff --git a/proxy/tun/tun_linux.go b/proxy/tun/tun_linux.go index 0813a21633cc..24d0fdba4bd1 100644 --- a/proxy/tun/tun_linux.go +++ b/proxy/tun/tun_linux.go @@ -3,6 +3,8 @@ package tun import ( + "net" + "github.com/vishvananda/netlink" "golang.org/x/sys/unix" "gvisor.dev/gvisor/pkg/tcpip/link/fdbased" @@ -15,23 +17,20 @@ import ( type LinuxTun struct { tunFd int tunLink netlink.Link - options TunOptions + options *Config } // LinuxTun implements Tun var _ Tun = (*LinuxTun)(nil) -// LinuxTun implements GVisorTun -var _ GVisorTun = (*LinuxTun)(nil) - // 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) if err != nil { return nil, err } - tunLink, err := setup(options.Name, int(options.MTU)) + tunLink, err := setup(options.Name, int(options.MTU[0])) if err != nil { _ = unix.Close(tunFd) return nil, err @@ -110,11 +109,23 @@ func (t *LinuxTun) Close() error { 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 func (t *LinuxTun) newEndpoint() (stack.LinkEndpoint, error) { return fdbased.New(&fdbased.Options{ FDs: []int{t.tunFd}, - MTU: t.options.MTU, + MTU: t.options.MTU[0], RXChecksumOffload: true, }) } + +func setinterface(network, address string, fd uintptr, iface *net.Interface) error { + return unix.BindToDevice(int(fd), iface.Name) +} diff --git a/proxy/tun/tun_windows.go b/proxy/tun/tun_windows.go index 8b5a09fae371..cb052751b58f 100644 --- a/proxy/tun/tun_windows.go +++ b/proxy/tun/tun_windows.go @@ -4,11 +4,17 @@ package tun import ( "crypto/md5" - "errors" + "encoding/binary" + go_errors "errors" + "net" + "net/netip" + "sync" "unsafe" + "github.com/xtls/xray-core/common/errors" "golang.org/x/sys/windows" "golang.zx2c4.com/wintun" + "golang.zx2c4.com/wireguard/windows/tunnel/winipcfg" "gvisor.dev/gvisor/pkg/buffer" "gvisor.dev/gvisor/pkg/tcpip" "gvisor.dev/gvisor/pkg/tcpip/stack" @@ -21,25 +27,26 @@ func procyield(cycles uint32) // current version is heavily stripped to do nothing more, // then create a network interface, to be provided as endpoint to gVisor ip stack type WindowsTun struct { - options TunOptions - adapter *wintun.Adapter - session wintun.Session - readWait windows.Handle - MTU uint32 + sync.RWMutex + + options *Config + adapter *wintun.Adapter + session wintun.Session + readWait windows.Handle + luid winipcfg.LUID + changeCallback winipcfg.ChangeCallback + closed bool } // WindowsTun implements Tun var _ Tun = (*WindowsTun)(nil) -// WindowsTun implements GVisorTun -var _ GVisorTun = (*WindowsTun)(nil) - // WindowsTun implements GVisorDevice var _ GVisorDevice = (*WindowsTun)(nil) // NewTun creates a Wintun interface with the given name. Should a Wintun // 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 adapter, err := open(options.Name) if err != nil { @@ -58,9 +65,7 @@ func NewTun(options TunOptions) (Tun, error) { adapter: adapter, session: session, readWait: session.ReadWaitEvent(), - // there is currently no iphndl.dll support, which is the netlink library for windows - // so there is nowhere to change MTU for the Wintun interface, and we take its default value - MTU: wintun.PacketSizeMax, + luid: winipcfg.LUID(adapter.LUID()), } return tun, nil @@ -70,13 +75,8 @@ func open(name string) (*wintun.Adapter, error) { // generate a deterministic GUID from the adapter name id := md5.Sum([]byte(name)) 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 - adapter, err = wintun.CreateAdapter(name, "Xray", guid) + adapter, err := wintun.CreateAdapter(name, "Xray", guid) if err == nil { return adapter, nil } @@ -84,18 +84,153 @@ func open(name string) (*wintun.Adapter, error) { } func (t *WindowsTun) Start() error { + var has4, has6 bool + allowedIPs := make([]netip.Prefix, 0, len(t.options.AutoRoutingTable)) + for _, route := range t.options.AutoRoutingTable { + 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 } 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.adapter.Close() 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 func (t *WindowsTun) WritePacket(packetBuffer *stack.PacketBuffer) tcpip.Error { + t.RLock() + defer t.RUnlock() + if t.closed { + return &tcpip.ErrClosedForSend{} + } + // request buffer from Wintun packet, err := t.session.AllocateSendPacket(packetBuffer.Size()) 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 func (t *WindowsTun) ReadPacket() (byte, *stack.PacketBuffer, error) { 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 } if err != nil { @@ -143,5 +278,38 @@ func (t *WindowsTun) Wait() { } 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 } diff --git a/transport/internet/system_dialer.go b/transport/internet/system_dialer.go index 2d604481e940..41c832dcc90b 100644 --- a/transport/internet/system_dialer.go +++ b/transport/internet/system_dialer.go @@ -2,6 +2,7 @@ package internet import ( "context" + "sync" "syscall" "time" @@ -11,6 +12,8 @@ import ( "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{} type SystemDialer interface { @@ -19,9 +22,8 @@ type SystemDialer interface { } type DefaultSystemDialer struct { - controllers []func(network, address string, c syscall.RawConn) error - dns dns.Client - obm outbound.Manager + dns dns.Client + obm outbound.Manager } 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 } 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 { 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, } - if sockopt != nil || len(d.controllers) > 0 { + if sockopt != nil || len(Controllers) > 0 { if sockopt != nil && sockopt.TcpMptcp { dialer.SetMultipathTCP(true) } 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 { 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") } - dialer, ok := effectiveSystemDialer.(*DefaultSystemDialer) + ControllersLock.Lock() + Controllers = append(Controllers, ctl) + ControllersLock.Unlock() + + _, ok := effectiveSystemDialer.(*DefaultSystemDialer) if !ok { return errors.New("RegisterListenerController not supported in custom dialer") } - dialer.controllers = append(dialer.controllers, ctl) return nil }