世界 3 лет назад
Родитель
Сommit
603264017a

+ 2 - 2
.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'

+ 347 - 0
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
+}

+ 32 - 0
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;
+}

+ 9 - 0
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{})
+}

+ 50 - 0
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
+}

+ 42 - 0
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
+}

+ 296 - 0
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
+}

+ 11 - 0
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)
+}

+ 5 - 0
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

+ 14 - 0
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=

+ 113 - 0
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)
+}

+ 13 - 0
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 {

+ 1 - 0
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"