diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 98ee485a..68dcc9dd 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -186,12 +186,12 @@ jobs: - name: Build Xray run: | mkdir -p build_assets - go build -v -o build_assets/xray -trimpath -ldflags "-s -w -buildid=" ./main + go build -v -o build_assets/xray -trimpath -ldflags "-s -w -buildid=" -tags with_gvisor ./main - name: Build background Xray on Windows if: matrix.goos == 'windows' run: | - go build -v -o build_assets/wxray.exe -trimpath -ldflags "-s -w -H windowsgui -buildid=" ./main + go build -v -o build_assets/wxray.exe -trimpath -ldflags "-s -w -H windowsgui -buildid=" -tags with_gvisor ./main - name: Build Mips softfloat Xray if: matrix.goarch == 'mips' || matrix.goarch == 'mipsle' diff --git a/app/tun/config.pb.go b/app/tun/config.pb.go new file mode 100644 index 00000000..b202344a --- /dev/null +++ b/app/tun/config.pb.go @@ -0,0 +1,347 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.28.1 +// protoc v3.21.12 +// source: app/tun/config.proto + +package tun + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type Config struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + InterfaceName string `protobuf:"bytes,1,opt,name=interface_name,json=interfaceName,proto3" json:"interface_name,omitempty"` + Inet4Address []string `protobuf:"bytes,2,rep,name=inet4_address,json=inet4Address,proto3" json:"inet4_address,omitempty"` + Inet6Address []string `protobuf:"bytes,3,rep,name=inet6_address,json=inet6Address,proto3" json:"inet6_address,omitempty"` + Mtu uint32 `protobuf:"varint,4,opt,name=mtu,proto3" json:"mtu,omitempty"` + AutoRoute bool `protobuf:"varint,5,opt,name=auto_route,json=autoRoute,proto3" json:"auto_route,omitempty"` + StrictRoute bool `protobuf:"varint,6,opt,name=strict_route,json=strictRoute,proto3" json:"strict_route,omitempty"` + Inet4RouteAddress []string `protobuf:"bytes,7,rep,name=inet4_route_address,json=inet4RouteAddress,proto3" json:"inet4_route_address,omitempty"` + Inet6RouteAddress []string `protobuf:"bytes,8,rep,name=inet6_route_address,json=inet6RouteAddress,proto3" json:"inet6_route_address,omitempty"` + EndpointIndependentNat bool `protobuf:"varint,9,opt,name=endpoint_independent_nat,json=endpointIndependentNat,proto3" json:"endpoint_independent_nat,omitempty"` + UdpTimeout int64 `protobuf:"varint,10,opt,name=udp_timeout,json=udpTimeout,proto3" json:"udp_timeout,omitempty"` + Stack string `protobuf:"bytes,11,opt,name=stack,proto3" json:"stack,omitempty"` + IncludeUid []uint32 `protobuf:"varint,12,rep,packed,name=include_uid,json=includeUid,proto3" json:"include_uid,omitempty"` + IncludeUidRange []string `protobuf:"bytes,13,rep,name=include_uid_range,json=includeUidRange,proto3" json:"include_uid_range,omitempty"` + ExcludeUid []uint32 `protobuf:"varint,14,rep,packed,name=exclude_uid,json=excludeUid,proto3" json:"exclude_uid,omitempty"` + ExcludeUidRange []string `protobuf:"bytes,15,rep,name=exclude_uid_range,json=excludeUidRange,proto3" json:"exclude_uid_range,omitempty"` + IncludeAndroidUser []int32 `protobuf:"varint,16,rep,packed,name=include_android_user,json=includeAndroidUser,proto3" json:"include_android_user,omitempty"` + IncludePackage []string `protobuf:"bytes,17,rep,name=include_package,json=includePackage,proto3" json:"include_package,omitempty"` + ExcludePackage []string `protobuf:"bytes,18,rep,name=exclude_package,json=excludePackage,proto3" json:"exclude_package,omitempty"` + // for xray + AutoDetectInterface bool `protobuf:"varint,100,opt,name=auto_detect_interface,json=autoDetectInterface,proto3" json:"auto_detect_interface,omitempty"` + OverrideAndroidVpn bool `protobuf:"varint,101,opt,name=override_android_vpn,json=overrideAndroidVpn,proto3" json:"override_android_vpn,omitempty"` +} + +func (x *Config) Reset() { + *x = Config{} + if protoimpl.UnsafeEnabled { + mi := &file_app_tun_config_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Config) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Config) ProtoMessage() {} + +func (x *Config) ProtoReflect() protoreflect.Message { + mi := &file_app_tun_config_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Config.ProtoReflect.Descriptor instead. +func (*Config) Descriptor() ([]byte, []int) { + return file_app_tun_config_proto_rawDescGZIP(), []int{0} +} + +func (x *Config) GetInterfaceName() string { + if x != nil { + return x.InterfaceName + } + return "" +} + +func (x *Config) GetInet4Address() []string { + if x != nil { + return x.Inet4Address + } + return nil +} + +func (x *Config) GetInet6Address() []string { + if x != nil { + return x.Inet6Address + } + return nil +} + +func (x *Config) GetMtu() uint32 { + if x != nil { + return x.Mtu + } + return 0 +} + +func (x *Config) GetAutoRoute() bool { + if x != nil { + return x.AutoRoute + } + return false +} + +func (x *Config) GetStrictRoute() bool { + if x != nil { + return x.StrictRoute + } + return false +} + +func (x *Config) GetInet4RouteAddress() []string { + if x != nil { + return x.Inet4RouteAddress + } + return nil +} + +func (x *Config) GetInet6RouteAddress() []string { + if x != nil { + return x.Inet6RouteAddress + } + return nil +} + +func (x *Config) GetEndpointIndependentNat() bool { + if x != nil { + return x.EndpointIndependentNat + } + return false +} + +func (x *Config) GetUdpTimeout() int64 { + if x != nil { + return x.UdpTimeout + } + return 0 +} + +func (x *Config) GetStack() string { + if x != nil { + return x.Stack + } + return "" +} + +func (x *Config) GetIncludeUid() []uint32 { + if x != nil { + return x.IncludeUid + } + return nil +} + +func (x *Config) GetIncludeUidRange() []string { + if x != nil { + return x.IncludeUidRange + } + return nil +} + +func (x *Config) GetExcludeUid() []uint32 { + if x != nil { + return x.ExcludeUid + } + return nil +} + +func (x *Config) GetExcludeUidRange() []string { + if x != nil { + return x.ExcludeUidRange + } + return nil +} + +func (x *Config) GetIncludeAndroidUser() []int32 { + if x != nil { + return x.IncludeAndroidUser + } + return nil +} + +func (x *Config) GetIncludePackage() []string { + if x != nil { + return x.IncludePackage + } + return nil +} + +func (x *Config) GetExcludePackage() []string { + if x != nil { + return x.ExcludePackage + } + return nil +} + +func (x *Config) GetAutoDetectInterface() bool { + if x != nil { + return x.AutoDetectInterface + } + return false +} + +func (x *Config) GetOverrideAndroidVpn() bool { + if x != nil { + return x.OverrideAndroidVpn + } + return false +} + +var File_app_tun_config_proto protoreflect.FileDescriptor + +var file_app_tun_config_proto_rawDesc = []byte{ + 0x0a, 0x14, 0x61, 0x70, 0x70, 0x2f, 0x74, 0x75, 0x6e, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0c, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, + 0x2e, 0x74, 0x75, 0x6e, 0x22, 0xa2, 0x06, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, + 0x25, 0x0a, 0x0e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x5f, 0x6e, 0x61, 0x6d, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, + 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x69, 0x6e, 0x65, 0x74, 0x34, 0x5f, + 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x69, + 0x6e, 0x65, 0x74, 0x34, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x69, + 0x6e, 0x65, 0x74, 0x36, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x03, 0x20, 0x03, + 0x28, 0x09, 0x52, 0x0c, 0x69, 0x6e, 0x65, 0x74, 0x36, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, + 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x74, 0x75, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x03, 0x6d, + 0x74, 0x75, 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x75, 0x74, 0x6f, 0x5f, 0x72, 0x6f, 0x75, 0x74, 0x65, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x61, 0x75, 0x74, 0x6f, 0x52, 0x6f, 0x75, 0x74, + 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x74, 0x72, 0x69, 0x63, 0x74, 0x5f, 0x72, 0x6f, 0x75, 0x74, + 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x73, 0x74, 0x72, 0x69, 0x63, 0x74, 0x52, + 0x6f, 0x75, 0x74, 0x65, 0x12, 0x2e, 0x0a, 0x13, 0x69, 0x6e, 0x65, 0x74, 0x34, 0x5f, 0x72, 0x6f, + 0x75, 0x74, 0x65, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, + 0x09, 0x52, 0x11, 0x69, 0x6e, 0x65, 0x74, 0x34, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x41, 0x64, 0x64, + 0x72, 0x65, 0x73, 0x73, 0x12, 0x2e, 0x0a, 0x13, 0x69, 0x6e, 0x65, 0x74, 0x36, 0x5f, 0x72, 0x6f, + 0x75, 0x74, 0x65, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, + 0x09, 0x52, 0x11, 0x69, 0x6e, 0x65, 0x74, 0x36, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x41, 0x64, 0x64, + 0x72, 0x65, 0x73, 0x73, 0x12, 0x38, 0x0a, 0x18, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, + 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x65, 0x6e, 0x74, 0x5f, 0x6e, 0x61, 0x74, + 0x18, 0x09, 0x20, 0x01, 0x28, 0x08, 0x52, 0x16, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, + 0x49, 0x6e, 0x64, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x65, 0x6e, 0x74, 0x4e, 0x61, 0x74, 0x12, 0x1f, + 0x0a, 0x0b, 0x75, 0x64, 0x70, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18, 0x0a, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x0a, 0x75, 0x64, 0x70, 0x54, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x12, + 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, + 0x73, 0x74, 0x61, 0x63, 0x6b, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, + 0x5f, 0x75, 0x69, 0x64, 0x18, 0x0c, 0x20, 0x03, 0x28, 0x0d, 0x52, 0x0a, 0x69, 0x6e, 0x63, 0x6c, + 0x75, 0x64, 0x65, 0x55, 0x69, 0x64, 0x12, 0x2a, 0x0a, 0x11, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, + 0x65, 0x5f, 0x75, 0x69, 0x64, 0x5f, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x0d, 0x20, 0x03, 0x28, + 0x09, 0x52, 0x0f, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x55, 0x69, 0x64, 0x52, 0x61, 0x6e, + 0x67, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x75, 0x69, + 0x64, 0x18, 0x0e, 0x20, 0x03, 0x28, 0x0d, 0x52, 0x0a, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, + 0x55, 0x69, 0x64, 0x12, 0x2a, 0x0a, 0x11, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x75, + 0x69, 0x64, 0x5f, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x0f, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0f, + 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x55, 0x69, 0x64, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x12, + 0x30, 0x0a, 0x14, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x61, 0x6e, 0x64, 0x72, 0x6f, + 0x69, 0x64, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x18, 0x10, 0x20, 0x03, 0x28, 0x05, 0x52, 0x12, 0x69, + 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x55, 0x73, 0x65, + 0x72, 0x12, 0x27, 0x0a, 0x0f, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x70, 0x61, 0x63, + 0x6b, 0x61, 0x67, 0x65, 0x18, 0x11, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0e, 0x69, 0x6e, 0x63, 0x6c, + 0x75, 0x64, 0x65, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x65, 0x78, + 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x18, 0x12, 0x20, + 0x03, 0x28, 0x09, 0x52, 0x0e, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x50, 0x61, 0x63, 0x6b, + 0x61, 0x67, 0x65, 0x12, 0x32, 0x0a, 0x15, 0x61, 0x75, 0x74, 0x6f, 0x5f, 0x64, 0x65, 0x74, 0x65, + 0x63, 0x74, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x18, 0x64, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x13, 0x61, 0x75, 0x74, 0x6f, 0x44, 0x65, 0x74, 0x65, 0x63, 0x74, 0x49, 0x6e, + 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x12, 0x30, 0x0a, 0x14, 0x6f, 0x76, 0x65, 0x72, 0x72, + 0x69, 0x64, 0x65, 0x5f, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x5f, 0x76, 0x70, 0x6e, 0x18, + 0x65, 0x20, 0x01, 0x28, 0x08, 0x52, 0x12, 0x6f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x41, + 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x56, 0x70, 0x6e, 0x42, 0x46, 0x0a, 0x10, 0x63, 0x6f, 0x6d, + 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x74, 0x75, 0x6e, 0x50, 0x01, 0x5a, + 0x21, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, + 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x61, 0x70, 0x70, 0x2f, 0x74, + 0x75, 0x6e, 0xaa, 0x02, 0x0c, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x41, 0x70, 0x70, 0x2e, 0x54, 0x75, + 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_app_tun_config_proto_rawDescOnce sync.Once + file_app_tun_config_proto_rawDescData = file_app_tun_config_proto_rawDesc +) + +func file_app_tun_config_proto_rawDescGZIP() []byte { + file_app_tun_config_proto_rawDescOnce.Do(func() { + file_app_tun_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_app_tun_config_proto_rawDescData) + }) + return file_app_tun_config_proto_rawDescData +} + +var file_app_tun_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_app_tun_config_proto_goTypes = []interface{}{ + (*Config)(nil), // 0: xray.app.tun.Config +} +var file_app_tun_config_proto_depIdxs = []int32{ + 0, // [0:0] is the sub-list for method output_type + 0, // [0:0] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_app_tun_config_proto_init() } +func file_app_tun_config_proto_init() { + if File_app_tun_config_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_app_tun_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Config); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_app_tun_config_proto_rawDesc, + NumEnums: 0, + NumMessages: 1, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_app_tun_config_proto_goTypes, + DependencyIndexes: file_app_tun_config_proto_depIdxs, + MessageInfos: file_app_tun_config_proto_msgTypes, + }.Build() + File_app_tun_config_proto = out.File + file_app_tun_config_proto_rawDesc = nil + file_app_tun_config_proto_goTypes = nil + file_app_tun_config_proto_depIdxs = nil +} diff --git a/app/tun/config.proto b/app/tun/config.proto new file mode 100644 index 00000000..55aa991c --- /dev/null +++ b/app/tun/config.proto @@ -0,0 +1,32 @@ +syntax = "proto3"; + +package xray.app.tun; +option csharp_namespace = "Xray.App.Tun"; +option go_package = "github.com/xtls/xray-core/app/tun"; +option java_package = "com.xray.app.tun"; +option java_multiple_files = true; + +message Config { + string interface_name = 1; + repeated string inet4_address = 2; + repeated string inet6_address = 3; + uint32 mtu = 4; + bool auto_route = 5; + bool strict_route = 6; + repeated string inet4_route_address = 7; + repeated string inet6_route_address = 8; + bool endpoint_independent_nat = 9; + int64 udp_timeout = 10; + string stack = 11; + repeated uint32 include_uid = 12; + repeated string include_uid_range = 13; + repeated uint32 exclude_uid = 14; + repeated string exclude_uid_range = 15; + repeated int32 include_android_user = 16; + repeated string include_package = 17; + repeated string exclude_package = 18; + + // for xray + bool auto_detect_interface = 100; + bool override_android_vpn = 101; +} diff --git a/app/tun/errors.generated.go b/app/tun/errors.generated.go new file mode 100644 index 00000000..2935e06d --- /dev/null +++ b/app/tun/errors.generated.go @@ -0,0 +1,9 @@ +package tun + +import "github.com/xtls/xray-core/common/errors" + +type errPathObjHolder struct{} + +func newError(values ...interface{}) *errors.Error { + return errors.New(values...).WithPathObj(errPathObjHolder{}) +} diff --git a/app/tun/interface_finder.go b/app/tun/interface_finder.go new file mode 100644 index 00000000..d99602b0 --- /dev/null +++ b/app/tun/interface_finder.go @@ -0,0 +1,50 @@ +package tun + +import ( + "net" + + "github.com/sagernet/sing/common/control" +) + +var _ control.InterfaceFinder = (*myInterfaceFinder)(nil) + +type myInterfaceFinder struct { + ifs []net.Interface +} + +func (f *myInterfaceFinder) update() error { + ifs, err := net.Interfaces() + if err != nil { + return err + } + f.ifs = ifs + return nil +} + +func (f *myInterfaceFinder) InterfaceIndexByName(name string) (interfaceIndex int, err error) { + for _, netInterface := range f.ifs { + if netInterface.Name == name { + return netInterface.Index, nil + } + } + netInterface, err := net.InterfaceByName(name) + if err != nil { + return + } + f.update() + return netInterface.Index, nil +} + +func (f *myInterfaceFinder) InterfaceNameByIndex(index int) (interfaceName string, err error) { + for _, netInterface := range f.ifs { + if netInterface.Index == index { + return netInterface.Name, nil + } + } + netInterface, err := net.InterfaceByIndex(index) + if err != nil { + return + } + f.update() + return netInterface.Name, nil +} diff --git a/app/tun/packet_conn.go b/app/tun/packet_conn.go new file mode 100644 index 00000000..edc39c61 --- /dev/null +++ b/app/tun/packet_conn.go @@ -0,0 +1,42 @@ +package tun + +import ( + sing_common "github.com/sagernet/sing/common" + sing_buf "github.com/sagernet/sing/common/buf" + N "github.com/sagernet/sing/common/network" + "github.com/xtls/xray-core/common/buf" + "github.com/xtls/xray-core/common/net" + "github.com/xtls/xray-core/common/singbridge" +) + +type PacketConn struct { + N.PacketConn +} + +func (p *PacketConn) ReadMultiBuffer() (buf.MultiBuffer, error) { + packet := buf.New() + packet.Extend(buf.Size) + sPacket := sing_buf.With(packet.Bytes()) + destination, err := p.ReadPacket(sPacket) + if err != nil { + packet.Release() + return nil, err + } + packet.Clear() + packet.Resize(int32(sPacket.Start()), int32(sPacket.Start()+sPacket.Len())) + destinationX := singbridge.ToDestination(destination, net.Network_UDP) + packet.UDP = &destinationX + return buf.MultiBuffer{packet}, nil +} + +func (p *PacketConn) WriteMultiBuffer(mb buf.MultiBuffer) error { + defer buf.ReleaseMulti(mb) + for _, buffer := range mb { + destination := sing_common.PtrValueOrDefault(buffer.UDP) + err := p.PacketConn.WritePacket(sing_buf.As(buffer.Bytes()), singbridge.ToSocksaddr(destination)) + if err != nil { + return err + } + } + return nil +} diff --git a/app/tun/tun.go b/app/tun/tun.go new file mode 100644 index 00000000..6899c802 --- /dev/null +++ b/app/tun/tun.go @@ -0,0 +1,296 @@ +package tun + +import ( + "context" + "net/netip" + "runtime" + "strconv" + "strings" + "time" + + "github.com/sagernet/sing-tun" + sing_common "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/control" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/common/ranges" + "github.com/xtls/xray-core/common" + "github.com/xtls/xray-core/common/net" + "github.com/xtls/xray-core/common/session" + "github.com/xtls/xray-core/common/singbridge" + "github.com/xtls/xray-core/core" + "github.com/xtls/xray-core/features/routing" + features_tun "github.com/xtls/xray-core/features/tun" + "github.com/xtls/xray-core/transport" + "github.com/xtls/xray-core/transport/internet" +) + +//go:generate go run github.com/xtls/xray-core/common/errors/errorgen + +func init() { + common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, cfg interface{}) (interface{}, error) { + return New(ctx, cfg.(*Config)) + })) +} + +var TunInitializer features_tun.Interface = (*Tun)(nil) + +type Tun struct { + ctx context.Context + dispatcher routing.Dispatcher + logger logger.ContextLogger + tunOptions tun.Options + stack string + endpointIndependentNat bool + udpTimeout int64 + tunIf tun.Tun + tunStack tun.Stack + networkMonitor tun.NetworkUpdateMonitor + interfaceMonitor tun.DefaultInterfaceMonitor + packageManager tun.PackageManager + interfaceFinder *myInterfaceFinder +} + +func New(ctx context.Context, config *Config) (*Tun, error) { + instance := core.MustFromContext(ctx) + tunInterface := &Tun{ + ctx: ctx, + dispatcher: instance.GetFeature(routing.DispatcherType()).(routing.Dispatcher), + logger: singbridge.NewLogger(newError), + stack: config.Stack, + endpointIndependentNat: config.EndpointIndependentNat, + udpTimeout: int64(5 * time.Minute.Seconds()), + interfaceFinder: new(myInterfaceFinder), + } + networkUpdateMonitor, err := tun.NewNetworkUpdateMonitor(tunInterface) + if err != nil { + return nil, err + } + defaultInterfaceMonitor, err := tun.NewDefaultInterfaceMonitor(networkUpdateMonitor, tun.DefaultInterfaceMonitorOptions{ + OverrideAndroidVPN: config.OverrideAndroidVpn, + }) + if err != nil { + return nil, err + } + defaultInterfaceMonitor.RegisterCallback(tunInterface.notifyNetworkUpdate) + if config.AutoDetectInterface { + networkUpdateMonitor.RegisterCallback(tunInterface.interfaceFinder.update) + const useInterfaceName = runtime.GOOS == "linux" || runtime.GOOS == "android" + bindFunc := control.BindToInterfaceFunc(tunInterface.interfaceFinder, func(network string, address string) (interfaceName string, interfaceIndex int) { + remoteAddr := M.ParseSocksaddr(address).Addr + if useInterfaceName { + return defaultInterfaceMonitor.DefaultInterfaceName(remoteAddr), -1 + } else { + return "", defaultInterfaceMonitor.DefaultInterfaceIndex(remoteAddr) + } + }) + internet.UseAlternativeSystemDialer(nil) + internet.RegisterDialerController(bindFunc) + internet.RegisterListenerController(bindFunc) + } + if runtime.GOOS == "android" { + packageManage, err := tun.NewPackageManager(tunInterface) + if err != nil { + return nil, err + } + tunInterface.packageManager = packageManage + } + tunInterface.networkMonitor = networkUpdateMonitor + tunInterface.interfaceMonitor = defaultInterfaceMonitor + tunName := config.InterfaceName + if tunName == "" { + tunName = tun.CalculateInterfaceName("") + } + tunMTU := config.Mtu + if tunMTU == 0 { + tunMTU = 9000 + } + includeUID := uidToRange(config.IncludeUid) + if len(config.IncludeUidRange) > 0 { + var err error + includeUID, err = parseRange(includeUID, config.IncludeUidRange) + if err != nil { + return nil, E.Cause(err, "parse include_uid_range") + } + } + excludeUID := uidToRange(config.ExcludeUid) + if len(config.ExcludeUidRange) > 0 { + var err error + excludeUID, err = parseRange(excludeUID, config.ExcludeUidRange) + if err != nil { + return nil, E.Cause(err, "parse exclude_uid_range") + } + } + if config.UdpTimeout != 0 { + tunInterface.udpTimeout = config.UdpTimeout + } + tunInterface.tunOptions = tun.Options{ + Name: tunName, + Inet4Address: sing_common.Map(config.Inet4Address, netip.MustParsePrefix), + Inet6Address: sing_common.Map(config.Inet6Address, netip.MustParsePrefix), + MTU: tunMTU, + AutoRoute: config.AutoRoute, + StrictRoute: config.StrictRoute, + Inet4RouteAddress: sing_common.Map(config.Inet4RouteAddress, netip.MustParsePrefix), + Inet6RouteAddress: sing_common.Map(config.Inet6RouteAddress, netip.MustParsePrefix), + IncludeUID: includeUID, + ExcludeUID: excludeUID, + IncludeAndroidUser: sing_common.Map(config.IncludeAndroidUser, func(it int32) int { + return int(it) + }), + IncludePackage: config.IncludePackage, + ExcludePackage: config.ExcludePackage, + InterfaceMonitor: defaultInterfaceMonitor, + TableIndex: 2022, + } + return tunInterface, nil +} + +func (t *Tun) Type() interface{} { + return features_tun.InterfaceType() +} + +func (t *Tun) Start() error { + err := t.interfaceMonitor.Start() + if err != nil { + return err + } + err = t.networkMonitor.Start() + if err != nil { + return err + } + if runtime.GOOS == "android" { + err = t.packageManager.Start() + if err != nil { + return err + } + t.tunOptions.BuildAndroidRules(t.packageManager, t) + } + tunIf, err := tun.New(t.tunOptions) + if err != nil { + return E.Cause(err, "configure tun interface") + } + t.tunIf = tunIf + t.tunStack, err = tun.NewStack(t.stack, tun.StackOptions{ + Context: t.ctx, + Tun: tunIf, + MTU: t.tunOptions.MTU, + Name: t.tunOptions.Name, + Inet4Address: t.tunOptions.Inet4Address, + Inet6Address: t.tunOptions.Inet6Address, + EndpointIndependentNat: t.endpointIndependentNat, + UDPTimeout: t.udpTimeout, + Handler: t, + Logger: t.logger, + }) + if err != nil { + return err + } + err = t.tunStack.Start() + if err != nil { + return err + } + t.logger.Info("tun started at ", t.tunOptions.Name) + return nil +} + +func (t *Tun) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error { + sid := session.NewID() + ctx = session.ContextWithID(ctx, sid) + t.logger.InfoContext(ctx, "inbound connection from ", metadata.Source) + t.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) + ctx = session.ContextWithInbound(ctx, &session.Inbound{ + Source: net.DestinationFromAddr(metadata.Source.TCPAddr()), + Conn: conn, + }) + wConn := singbridge.NewConn(conn) + _ = t.dispatcher.DispatchLink(ctx, singbridge.ToDestination(metadata.Destination, net.Network_TCP), &transport.Link{ + Reader: wConn, + Writer: wConn, + }) + conn.Close() + return nil +} + +func (t *Tun) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error { + sid := session.NewID() + ctx = session.ContextWithID(ctx, sid) + t.logger.InfoContext(ctx, "inbound packet connection from ", metadata.Source) + t.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination) + ctx = session.ContextWithInbound(ctx, &session.Inbound{ + Source: net.DestinationFromAddr(metadata.Source.UDPAddr()), + }) + pc := &PacketConn{conn} + _ = t.dispatcher.DispatchLink(ctx, singbridge.ToDestination(metadata.Destination, net.Network_UDP), &transport.Link{ + Reader: pc, + Writer: pc, + }) + conn.Close() + return nil +} + +func (t *Tun) Close() error { + return sing_common.Close( + t.packageManager, + t.interfaceMonitor, + t.networkMonitor, + t.tunStack, + t.tunIf, + ) +} + +func (t *Tun) OnPackagesUpdated(packages int, sharedUsers int) { + t.logger.Info("updated packages list: ", packages, " packages, ", sharedUsers, " shared users") +} + +func (t *Tun) NewError(ctx context.Context, err error) { +} + +func (t *Tun) notifyNetworkUpdate(int) error { + if runtime.GOOS == "android" { + var vpnStatus string + if t.interfaceMonitor.AndroidVPNEnabled() { + vpnStatus = "enabled" + } else { + vpnStatus = "disabled" + } + t.logger.Info("updated default interface ", t.interfaceMonitor.DefaultInterfaceName(netip.IPv4Unspecified()), ", index ", t.interfaceMonitor.DefaultInterfaceIndex(netip.IPv4Unspecified()), ", vpn ", vpnStatus) + } else { + t.logger.Info("updated default interface ", t.interfaceMonitor.DefaultInterfaceName(netip.IPv4Unspecified()), ", index ", t.interfaceMonitor.DefaultInterfaceIndex(netip.IPv4Unspecified())) + } + return nil +} + +func uidToRange(uidList []uint32) []ranges.Range[uint32] { + return sing_common.Map(uidList, func(uid uint32) ranges.Range[uint32] { + return ranges.NewSingle(uint32(uid)) + }) +} + +func parseRange(uidRanges []ranges.Range[uint32], rangeList []string) ([]ranges.Range[uint32], error) { + for _, uidRange := range rangeList { + if !strings.Contains(uidRange, ":") { + return nil, E.New("missing ':' in range: ", uidRange) + } + subIndex := strings.Index(uidRange, ":") + if subIndex == 0 { + return nil, E.New("missing range start: ", uidRange) + } else if subIndex == len(uidRange)-1 { + return nil, E.New("missing range end: ", uidRange) + } + var start, end uint64 + var err error + start, err = strconv.ParseUint(uidRange[:subIndex], 10, 32) + if err != nil { + return nil, E.Cause(err, "parse range start") + } + end, err = strconv.ParseUint(uidRange[subIndex+1:], 10, 32) + if err != nil { + return nil, E.Cause(err, "parse range end") + } + uidRanges = append(uidRanges, ranges.New(uint32(start), uint32(end))) + } + return uidRanges, nil +} diff --git a/features/tun/tun.go b/features/tun/tun.go new file mode 100644 index 00000000..c1164128 --- /dev/null +++ b/features/tun/tun.go @@ -0,0 +1,11 @@ +package tun + +import "github.com/xtls/xray-core/features" + +type Interface interface { + features.Feature +} + +func InterfaceType() interface{} { + return (*Interface)(nil) +} diff --git a/go.mod b/go.mod index bf6a6c3a..81d080a0 100644 --- a/go.mod +++ b/go.mod @@ -37,6 +37,7 @@ require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 // indirect github.com/francoispqt/gojay v1.2.13 // indirect + github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/gaukas/godicttls v0.0.3 // indirect github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/google/btree v1.1.2 // indirect @@ -48,6 +49,10 @@ require ( github.com/quic-go/qtls-go1-19 v0.3.2 // indirect github.com/quic-go/qtls-go1-20 v0.2.2 // indirect github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect + github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 // indirect + github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 // indirect + github.com/sagernet/sing-tun v0.1.4 // indirect + github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect go.uber.org/atomic v1.10.0 // indirect golang.org/x/exp v0.0.0-20230321023759-10a507213a29 // indirect golang.org/x/mod v0.10.0 // indirect diff --git a/go.sum b/go.sum index f8df0400..2d4eb2c2 100644 --- a/go.sum +++ b/go.sum @@ -33,6 +33,8 @@ github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk= github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/gaukas/godicttls v0.0.3 h1:YNDIf0d9adcxOijiLrEzpfZGAkNwLRzPaG6OjU7EITk= github.com/gaukas/godicttls v0.0.3/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= @@ -143,12 +145,19 @@ github.com/refraction-networking/utls v1.3.2/go.mod h1:fmoaOww2bxzzEpIKOebIsnBvj github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg= github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 h1:5+m7c6AkmAylhauulqN/c5dnh8/KssrE9c93TQrXldA= +github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61/go.mod h1:QUQ4RRHD6hGGHdFMEtR8T2P6GS6R3D/CXKdaYHKKXms= +github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 h1:iL5gZI3uFp0X6EslacyapiRz7LLSJyr4RajF/BhMVyE= +github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM= +github.com/sagernet/sing v0.0.0-20220817130738-ce854cda8522/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY= github.com/sagernet/sing v0.2.3 h1:V50MvZ4c3Iij2lYFWPlzL1PyipwSzjGeN9x+Ox89vpk= github.com/sagernet/sing v0.2.3/go.mod h1:Ta8nHnDLAwqySzKhGoKk4ZIB+vJ3GTKj7UPrWYvM+4w= github.com/sagernet/sing v0.2.4 h1:gC8BR5sglbJZX23RtMyFa8EETP9YEUADhfbEzU1yVbo= github.com/sagernet/sing v0.2.4/go.mod h1:Ta8nHnDLAwqySzKhGoKk4ZIB+vJ3GTKj7UPrWYvM+4w= github.com/sagernet/sing-shadowsocks v0.2.1 h1:FvdLQOqpvxHBJUcUe4fvgiYP2XLLwH5i1DtXQviVEPw= github.com/sagernet/sing-shadowsocks v0.2.1/go.mod h1:T/OgurSjsAe+Ug3+6PprXjmgHFmJidjOvQcjXGTKb3I= +github.com/sagernet/sing-tun v0.1.4 h1:Fa6kgvuM2fPbPu3R97S8L8NgaD5lJq3wQorNuTb5oqo= +github.com/sagernet/sing-tun v0.1.4/go.mod h1:7BrtP7NMp9FK5oVsZWg92b7yFrD+sM2+udapFurReyw= github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c h1:vK2wyt9aWYHHvNLWniwijBu/n4pySypiKRhN32u/JGo= github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c/go.mod h1:euOmN6O5kk9dQmgSS8Df4psAl3TCjxOz0NW60EWkSaI= github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb h1:XfLJSPIOUX+osiMraVgIrMR27uMXnRJWGm1+GL8/63U= @@ -192,6 +201,8 @@ github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e h1:5QefA066A1tF github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e/go.mod h1:5t19P9LBIrNamL6AcMQOncg/r10y3Pc01AbHeMhwlpU= github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU= github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM= +github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 h1:gga7acRE695APm9hlsSMoOoE65U4/TcqNj90mc69Rlg= +github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= github.com/xtls/reality v0.0.0-20230331223127-176a94313eda h1:psRJD2RrZbnI0OWyHvXfgYCPqlRM5q5SPDcjDoDBWhE= github.com/xtls/reality v0.0.0-20230331223127-176a94313eda/go.mod h1:rkuAY1S9F8eI8gDiPDYvACE8e2uwkyg8qoOTuwWov7Y= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= @@ -251,12 +262,15 @@ golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220731174439-a90be440212d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= diff --git a/infra/conf/tun.go b/infra/conf/tun.go new file mode 100644 index 00000000..b8531bc7 --- /dev/null +++ b/infra/conf/tun.go @@ -0,0 +1,113 @@ +package conf + +import ( + "encoding/json" + "net/netip" + + "github.com/sagernet/sing/common" + "github.com/xtls/xray-core/app/tun" +) + +type TunConfig struct { + InterfaceName string `json:"interface_name,omitempty"` + MTU uint32 `json:"mtu,omitempty"` + Inet4Address Listable[ListenPrefix] `json:"inet4_address,omitempty"` + Inet6Address Listable[ListenPrefix] `json:"inet6_address,omitempty"` + AutoRoute bool `json:"auto_route,omitempty"` + StrictRoute bool `json:"strict_route,omitempty"` + Inet4RouteAddress Listable[ListenPrefix] `json:"inet4_route_address,omitempty"` + Inet6RouteAddress Listable[ListenPrefix] `json:"inet6_route_address,omitempty"` + IncludeUID Listable[uint32] `json:"include_uid,omitempty"` + IncludeUIDRange Listable[string] `json:"include_uid_range,omitempty"` + ExcludeUID Listable[uint32] `json:"exclude_uid,omitempty"` + ExcludeUIDRange Listable[string] `json:"exclude_uid_range,omitempty"` + IncludeAndroidUser Listable[int] `json:"include_android_user,omitempty"` + IncludePackage Listable[string] `json:"include_package,omitempty"` + ExcludePackage Listable[string] `json:"exclude_package,omitempty"` + EndpointIndependentNat bool `json:"endpoint_independent_nat,omitempty"` + UDPTimeout int64 `json:"udp_timeout,omitempty"` + Stack string `json:"stack,omitempty"` + + AutoDetectInterface bool `json:"auto_detect_interface,omitempty"` + OverrideAndroidVPN bool `json:"override_android_vpn,omitempty"` +} + +func (f *TunConfig) Build() (*tun.Config, error) { + var config tun.Config + config.InterfaceName = f.InterfaceName + config.Mtu = f.MTU + config.Inet4Address = common.Map(common.Map(f.Inet4Address, ListenPrefix.Build), netip.Prefix.String) + config.Inet6Address = common.Map(common.Map(f.Inet6Address, ListenPrefix.Build), netip.Prefix.String) + config.AutoRoute = f.AutoRoute + config.StrictRoute = f.StrictRoute + config.Inet4RouteAddress = common.Map(common.Map(f.Inet4RouteAddress, ListenPrefix.Build), netip.Prefix.String) + config.Inet6RouteAddress = common.Map(common.Map(f.Inet6RouteAddress, ListenPrefix.Build), netip.Prefix.String) + config.IncludeUid = f.IncludeUID + config.IncludeUidRange = f.IncludeUIDRange + config.ExcludeUid = f.ExcludeUID + config.ExcludeUidRange = f.ExcludeUIDRange + config.IncludeAndroidUser = common.Map(f.IncludeAndroidUser, func(it int) int32 { + return int32(it) + }) + config.IncludePackage = f.IncludePackage + config.ExcludePackage = f.ExcludePackage + config.EndpointIndependentNat = f.EndpointIndependentNat + config.UdpTimeout = f.UDPTimeout + config.Stack = f.Stack + // for xray + config.AutoDetectInterface = f.AutoDetectInterface + config.OverrideAndroidVpn = f.OverrideAndroidVPN + return &config, nil +} + +type Listable[T comparable] []T + +func (l Listable[T]) MarshalJSON() ([]byte, error) { + arrayList := []T(l) + if len(arrayList) == 1 { + return json.Marshal(arrayList[0]) + } + return json.Marshal(arrayList) +} + +func (l *Listable[T]) UnmarshalJSON(content []byte) error { + err := json.Unmarshal(content, (*[]T)(l)) + if err == nil { + return nil + } + var singleItem T + err = json.Unmarshal(content, &singleItem) + if err != nil { + return err + } + *l = []T{singleItem} + return nil +} + +type ListenPrefix netip.Prefix + +func (p ListenPrefix) MarshalJSON() ([]byte, error) { + prefix := netip.Prefix(p) + if !prefix.IsValid() { + return json.Marshal(nil) + } + return json.Marshal(prefix.String()) +} + +func (p *ListenPrefix) UnmarshalJSON(bytes []byte) error { + var value string + err := json.Unmarshal(bytes, &value) + if err != nil { + return err + } + prefix, err := netip.ParsePrefix(value) + if err != nil { + return err + } + *p = ListenPrefix(prefix) + return nil +} + +func (p ListenPrefix) Build() netip.Prefix { + return netip.Prefix(p) +} diff --git a/infra/conf/xray.go b/infra/conf/xray.go index 8b6d05f4..8da6320c 100644 --- a/infra/conf/xray.go +++ b/infra/conf/xray.go @@ -410,6 +410,7 @@ type Config struct { Reverse *ReverseConfig `json:"reverse"` FakeDNS *FakeDNSConfig `json:"fakeDns"` Observatory *ObservatoryConfig `json:"observatory"` + Tun *TunConfig `json:"tun"` } func (c *Config) findInboundTag(tag string) int { @@ -474,6 +475,10 @@ func (c *Config) Override(o *Config, fn string) { c.Observatory = o.Observatory } + if o.Tun != nil { + c.Tun = o.Tun + } + // deprecated attrs... keep them for now if o.InboundConfig != nil { c.InboundConfig = o.InboundConfig @@ -637,6 +642,14 @@ func (c *Config) Build() (*core.Config, error) { config.App = append(config.App, serial.ToTypedMessage(r)) } + if c.Tun != nil { + r, err := c.Tun.Build() + if err != nil { + return nil, err + } + config.App = append(config.App, serial.ToTypedMessage(r)) + } + var inbounds []InboundDetourConfig if c.InboundConfig != nil { diff --git a/main/distro/all/all.go b/main/distro/all/all.go index 0e38fcf6..80a02df4 100644 --- a/main/distro/all/all.go +++ b/main/distro/all/all.go @@ -16,6 +16,7 @@ import ( // Developer preview services _ "github.com/xtls/xray-core/app/observatory/command" + _ "github.com/xtls/xray-core/app/tun" // Other optional features. _ "github.com/xtls/xray-core/app/dns"