diff --git a/app/version/config.pb.go b/app/version/config.pb.go new file mode 100644 index 00000000..97607a95 --- /dev/null +++ b/app/version/config.pb.go @@ -0,0 +1,152 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.35.1 +// protoc v5.28.2 +// source: app/version/config.proto + +package version + +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 + + CoreVersion string `protobuf:"bytes,1,opt,name=core_version,json=coreVersion,proto3" json:"core_version,omitempty"` + MinVersion string `protobuf:"bytes,2,opt,name=min_version,json=minVersion,proto3" json:"min_version,omitempty"` + MaxVersion string `protobuf:"bytes,3,opt,name=max_version,json=maxVersion,proto3" json:"max_version,omitempty"` +} + +func (x *Config) Reset() { + *x = Config{} + mi := &file_app_version_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_version_config_proto_msgTypes[0] + if 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_version_config_proto_rawDescGZIP(), []int{0} +} + +func (x *Config) GetCoreVersion() string { + if x != nil { + return x.CoreVersion + } + return "" +} + +func (x *Config) GetMinVersion() string { + if x != nil { + return x.MinVersion + } + return "" +} + +func (x *Config) GetMaxVersion() string { + if x != nil { + return x.MaxVersion + } + return "" +} + +var File_app_version_config_proto protoreflect.FileDescriptor + +var file_app_version_config_proto_rawDesc = []byte{ + 0x0a, 0x18, 0x61, 0x70, 0x70, 0x2f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x2f, 0x63, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x10, 0x78, 0x72, 0x61, 0x79, + 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x6d, 0x0a, 0x06, + 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x72, 0x65, 0x5f, 0x76, + 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, + 0x72, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x6d, 0x69, 0x6e, + 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, + 0x6d, 0x69, 0x6e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x6d, 0x61, + 0x78, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0a, 0x6d, 0x61, 0x78, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x42, 0x52, 0x0a, 0x14, 0x63, + 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x76, 0x65, 0x72, 0x73, + 0x69, 0x6f, 0x6e, 0x50, 0x01, 0x5a, 0x25, 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, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0xaa, 0x02, 0x10, 0x58, + 0x72, 0x61, 0x79, 0x2e, 0x41, 0x70, 0x70, 0x2e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x62, + 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_app_version_config_proto_rawDescOnce sync.Once + file_app_version_config_proto_rawDescData = file_app_version_config_proto_rawDesc +) + +func file_app_version_config_proto_rawDescGZIP() []byte { + file_app_version_config_proto_rawDescOnce.Do(func() { + file_app_version_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_app_version_config_proto_rawDescData) + }) + return file_app_version_config_proto_rawDescData +} + +var file_app_version_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_app_version_config_proto_goTypes = []any{ + (*Config)(nil), // 0: xray.app.version.Config +} +var file_app_version_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_version_config_proto_init() } +func file_app_version_config_proto_init() { + if File_app_version_config_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_app_version_config_proto_rawDesc, + NumEnums: 0, + NumMessages: 1, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_app_version_config_proto_goTypes, + DependencyIndexes: file_app_version_config_proto_depIdxs, + MessageInfos: file_app_version_config_proto_msgTypes, + }.Build() + File_app_version_config_proto = out.File + file_app_version_config_proto_rawDesc = nil + file_app_version_config_proto_goTypes = nil + file_app_version_config_proto_depIdxs = nil +} diff --git a/app/version/config.proto b/app/version/config.proto new file mode 100644 index 00000000..8e804838 --- /dev/null +++ b/app/version/config.proto @@ -0,0 +1,14 @@ +syntax = "proto3"; + +package xray.app.version; +option csharp_namespace = "Xray.App.Version"; +option go_package = "github.com/xtls/xray-core/app/version"; +option java_package = "com.xray.app.version"; +option java_multiple_files = true; + + +message Config { + string core_version = 1; + string min_version = 2; + string max_version = 3; +} diff --git a/app/version/version.go b/app/version/version.go new file mode 100644 index 00000000..25d7e6ff --- /dev/null +++ b/app/version/version.go @@ -0,0 +1,77 @@ +package version + +import ( + "context" + "github.com/xtls/xray-core/common" + "github.com/xtls/xray-core/common/errors" + "strconv" + "strings" +) + +type Version struct { + config *Config + ctx context.Context +} + +func New(ctx context.Context, config *Config) (*Version, error) { + if config.MinVersion != "" { + result, err := compareVersions(config.MinVersion, config.CoreVersion) + if err != nil { + return nil, err + } + if result > 0 { + return nil, errors.New("this config must be run on version ", config.MinVersion, " or higher") + } + } + if config.MaxVersion != "" { + result, err := compareVersions(config.MaxVersion, config.CoreVersion) + if err != nil { + return nil, err + } + if result < 0 { + return nil, errors.New("this config should be run on version ", config.MaxVersion, " or lower") + } + } + return &Version{config: config, ctx: ctx}, nil +} + +func compareVersions(v1, v2 string) (int, error) { + // Split version strings into components + v1Parts := strings.Split(v1, ".") + v2Parts := strings.Split(v2, ".") + + // Pad shorter versions with zeros + for len(v1Parts) < len(v2Parts) { + v1Parts = append(v1Parts, "0") + } + for len(v2Parts) < len(v1Parts) { + v2Parts = append(v2Parts, "0") + } + + // Compare each part + for i := 0; i < len(v1Parts); i++ { + // Convert parts to integers + n1, err := strconv.Atoi(v1Parts[i]) + if err != nil { + return 0, errors.New("invalid version component ", v1Parts[i], " in ", v1) + } + n2, err := strconv.Atoi(v2Parts[i]) + if err != nil { + return 0, errors.New("invalid version component ", v2Parts[i], " in ", v2) + } + + if n1 < n2 { + return -1, nil // v1 < v2 + } + if n1 > n2 { + return 1, nil // v1 > v2 + } + } + return 0, nil // v1 == v2 +} + +func init() { + common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) { + return New(ctx, config.(*Config)) + })) +} diff --git a/infra/conf/version.go b/infra/conf/version.go new file mode 100644 index 00000000..5fedeb08 --- /dev/null +++ b/infra/conf/version.go @@ -0,0 +1,22 @@ +package conf + +import ( + "github.com/xtls/xray-core/app/version" + "github.com/xtls/xray-core/core" + "strconv" +) + +type VersionConfig struct { + MinVersion string `json:"min"` + MaxVersion string `json:"max"` +} + +func (c *VersionConfig) Build() (*version.Config, error) { + coreVersion := strconv.Itoa(int(core.Version_x)) + "." + strconv.Itoa(int(core.Version_y)) + "." + strconv.Itoa(int(core.Version_z)) + + return &version.Config{ + CoreVersion: coreVersion, + MinVersion: c.MinVersion, + MaxVersion: c.MaxVersion, + }, nil +} diff --git a/infra/conf/xray.go b/infra/conf/xray.go index 0e9ec3eb..0810dd80 100644 --- a/infra/conf/xray.go +++ b/infra/conf/xray.go @@ -383,6 +383,7 @@ type Config struct { FakeDNS *FakeDNSConfig `json:"fakeDns"` Observatory *ObservatoryConfig `json:"observatory"` BurstObservatory *BurstObservatoryConfig `json:"burstObservatory"` + Version *VersionConfig `json:"version"` } func (c *Config) findInboundTag(tag string) int { @@ -451,6 +452,10 @@ func (c *Config) Override(o *Config, fn string) { c.BurstObservatory = o.BurstObservatory } + if o.Version != nil { + c.Version = o.Version + } + // update the Inbound in slice if the only one in override config has same tag if len(o.InboundConfigs) > 0 { for i := range o.InboundConfigs { @@ -591,6 +596,14 @@ func (c *Config) Build() (*core.Config, error) { config.App = append(config.App, serial.ToTypedMessage(r)) } + if c.Version != nil { + r, err := c.Version.Build() + if err != nil { + return nil, errors.New("failed to build version configuration").Base(err) + } + config.App = append(config.App, serial.ToTypedMessage(r)) + } + var inbounds []InboundDetourConfig if len(c.InboundConfigs) > 0 {