diff --git a/app/commander/config.pb.go b/app/commander/config.pb.go index 32dd608a..2c7b954e 100644 --- a/app/commander/config.pb.go +++ b/app/commander/config.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.31.0 +// protoc-gen-go v1.32.0 // protoc v4.23.1 // source: app/commander/config.proto diff --git a/app/dispatcher/config.pb.go b/app/dispatcher/config.pb.go index 1512f186..cd55bd69 100644 --- a/app/dispatcher/config.pb.go +++ b/app/dispatcher/config.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.31.0 +// protoc-gen-go v1.32.0 // protoc v4.23.1 // source: app/dispatcher/config.proto diff --git a/app/dispatcher/default.go b/app/dispatcher/default.go index bfc43608..effa2198 100644 --- a/app/dispatcher/default.go +++ b/app/dispatcher/default.go @@ -230,6 +230,7 @@ func (d *DefaultDispatcher) Dispatch(ctx context.Context, destination net.Destin content = new(session.Content) ctx = session.ContextWithContent(ctx, content) } + sniffingRequest := content.SniffingRequest inbound, outbound := d.getLink(ctx) if !sniffingRequest.Enabled { @@ -366,7 +367,6 @@ func sniffer(ctx context.Context, cReader *cachedReader, metadataOnly bool, netw } return contentResult, contentErr } - func (d *DefaultDispatcher) routedDispatch(ctx context.Context, link *transport.Link, destination net.Destination) { ob := session.OutboundFromContext(ctx) if hosts, ok := d.dns.(dns.HostsLookup); ok && destination.Address.Family().IsDomain() { diff --git a/app/dns/config.pb.go b/app/dns/config.pb.go index 2d84fe72..51638688 100644 --- a/app/dns/config.pb.go +++ b/app/dns/config.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.31.0 +// protoc-gen-go v1.32.0 // protoc v4.23.1 // source: app/dns/config.proto diff --git a/app/dns/fakedns/fakedns.pb.go b/app/dns/fakedns/fakedns.pb.go index dea59f99..c9a09a2c 100644 --- a/app/dns/fakedns/fakedns.pb.go +++ b/app/dns/fakedns/fakedns.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.31.0 +// protoc-gen-go v1.32.0 // protoc v4.23.1 // source: app/dns/fakedns/fakedns.proto diff --git a/app/log/command/config.pb.go b/app/log/command/config.pb.go index 21637a8b..258f8cb1 100644 --- a/app/log/command/config.pb.go +++ b/app/log/command/config.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.31.0 +// protoc-gen-go v1.32.0 // protoc v4.23.1 // source: app/log/command/config.proto diff --git a/app/log/config.pb.go b/app/log/config.pb.go index 7829713c..3e981674 100644 --- a/app/log/config.pb.go +++ b/app/log/config.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.31.0 +// protoc-gen-go v1.32.0 // protoc v4.23.1 // source: app/log/config.proto diff --git a/app/metrics/config.pb.go b/app/metrics/config.pb.go index a93bbb64..9bab8d20 100644 --- a/app/metrics/config.pb.go +++ b/app/metrics/config.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.31.0 +// protoc-gen-go v1.32.0 // protoc v4.23.1 // source: app/metrics/config.proto diff --git a/app/observatory/burst/burst.go b/app/observatory/burst/burst.go new file mode 100644 index 00000000..13eb0fd6 --- /dev/null +++ b/app/observatory/burst/burst.go @@ -0,0 +1,14 @@ +package burst + +import ( + "math" + "time" +) + +//go:generate go run github.com/v2fly/v2ray-core/v4/common/errors/errorgen + +const ( + rttFailed = time.Duration(math.MaxInt64 - iota) + rttUntested + rttUnqualified +) diff --git a/app/observatory/burst/burstobserver.go b/app/observatory/burst/burstobserver.go new file mode 100644 index 00000000..6b7dbc7c --- /dev/null +++ b/app/observatory/burst/burstobserver.go @@ -0,0 +1,108 @@ +package burst + +import ( + "context" + + "github.com/xtls/xray-core/core" + "github.com/xtls/xray-core/app/observatory" + "github.com/xtls/xray-core/common" + "github.com/xtls/xray-core/common/signal/done" + "github.com/xtls/xray-core/features/extension" + "github.com/xtls/xray-core/features/outbound" + "google.golang.org/protobuf/proto" + "sync" +) + +type Observer struct { + config *Config + ctx context.Context + + statusLock sync.Mutex + hp *HealthPing + + finished *done.Instance + + ohm outbound.Manager +} + +func (o *Observer) GetObservation(ctx context.Context) (proto.Message, error) { + return &observatory.ObservationResult{Status: o.createResult()}, nil +} + +func (o *Observer) createResult() []*observatory.OutboundStatus { + var result []*observatory.OutboundStatus + o.hp.access.Lock() + defer o.hp.access.Unlock() + for name, value := range o.hp.Results { + status := observatory.OutboundStatus{ + Alive: value.getStatistics().All != value.getStatistics().Fail, + Delay: value.getStatistics().Average.Milliseconds(), + LastErrorReason: "", + OutboundTag: name, + LastSeenTime: 0, + LastTryTime: 0, + HealthPing: &observatory.HealthPingMeasurementResult{ + All: int64(value.getStatistics().All), + Fail: int64(value.getStatistics().Fail), + Deviation: int64(value.getStatistics().Deviation), + Average: int64(value.getStatistics().Average), + Max: int64(value.getStatistics().Max), + Min: int64(value.getStatistics().Min), + }, + } + result = append(result, &status) + } + return result +} + +func (o *Observer) Type() interface{} { + return extension.ObservatoryType() +} + +func (o *Observer) Start() error { + if o.config != nil && len(o.config.SubjectSelector) != 0 { + o.finished = done.New() + o.hp.StartScheduler(func() ([]string, error) { + hs, ok := o.ohm.(outbound.HandlerSelector) + if !ok { + + return nil, newError("outbound.Manager is not a HandlerSelector") + } + + outbounds := hs.Select(o.config.SubjectSelector) + return outbounds, nil + }) + } + return nil +} + +func (o *Observer) Close() error { + if o.finished != nil { + o.hp.StopScheduler() + return o.finished.Close() + } + return nil +} + +func New(ctx context.Context, config *Config) (*Observer, error) { + var outboundManager outbound.Manager + err := core.RequireFeatures(ctx, func(om outbound.Manager) { + outboundManager = om + }) + if err != nil { + return nil, newError("Cannot get depended features").Base(err) + } + hp := NewHealthPing(ctx, config.PingConfig) + return &Observer{ + config: config, + ctx: ctx, + ohm: outboundManager, + hp: hp, + }, nil +} + +func init() { + common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) { + return New(ctx, config.(*Config)) + })) +} diff --git a/app/observatory/burst/config.pb.go b/app/observatory/burst/config.pb.go new file mode 100644 index 00000000..ce371583 --- /dev/null +++ b/app/observatory/burst/config.pb.go @@ -0,0 +1,276 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.32.0 +// protoc v4.23.1 +// source: app/observatory/burst/config.proto + +package burst + +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 + + // @Document The selectors for outbound under observation + SubjectSelector []string `protobuf:"bytes,2,rep,name=subject_selector,json=subjectSelector,proto3" json:"subject_selector,omitempty"` + PingConfig *HealthPingConfig `protobuf:"bytes,3,opt,name=ping_config,json=pingConfig,proto3" json:"ping_config,omitempty"` +} + +func (x *Config) Reset() { + *x = Config{} + if protoimpl.UnsafeEnabled { + mi := &file_app_observatory_burst_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_observatory_burst_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_observatory_burst_config_proto_rawDescGZIP(), []int{0} +} + +func (x *Config) GetSubjectSelector() []string { + if x != nil { + return x.SubjectSelector + } + return nil +} + +func (x *Config) GetPingConfig() *HealthPingConfig { + if x != nil { + return x.PingConfig + } + return nil +} + +type HealthPingConfig struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // destination url, need 204 for success return + // default https://connectivitycheck.gstatic.com/generate_204 + Destination string `protobuf:"bytes,1,opt,name=destination,proto3" json:"destination,omitempty"` + // connectivity check url + Connectivity string `protobuf:"bytes,2,opt,name=connectivity,proto3" json:"connectivity,omitempty"` + // health check interval, int64 values of time.Duration + Interval int64 `protobuf:"varint,3,opt,name=interval,proto3" json:"interval,omitempty"` + // sampling count is the amount of recent ping results which are kept for calculation + SamplingCount int32 `protobuf:"varint,4,opt,name=samplingCount,proto3" json:"samplingCount,omitempty"` + // ping timeout, int64 values of time.Duration + Timeout int64 `protobuf:"varint,5,opt,name=timeout,proto3" json:"timeout,omitempty"` +} + +func (x *HealthPingConfig) Reset() { + *x = HealthPingConfig{} + if protoimpl.UnsafeEnabled { + mi := &file_app_observatory_burst_config_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *HealthPingConfig) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HealthPingConfig) ProtoMessage() {} + +func (x *HealthPingConfig) ProtoReflect() protoreflect.Message { + mi := &file_app_observatory_burst_config_proto_msgTypes[1] + 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 HealthPingConfig.ProtoReflect.Descriptor instead. +func (*HealthPingConfig) Descriptor() ([]byte, []int) { + return file_app_observatory_burst_config_proto_rawDescGZIP(), []int{1} +} + +func (x *HealthPingConfig) GetDestination() string { + if x != nil { + return x.Destination + } + return "" +} + +func (x *HealthPingConfig) GetConnectivity() string { + if x != nil { + return x.Connectivity + } + return "" +} + +func (x *HealthPingConfig) GetInterval() int64 { + if x != nil { + return x.Interval + } + return 0 +} + +func (x *HealthPingConfig) GetSamplingCount() int32 { + if x != nil { + return x.SamplingCount + } + return 0 +} + +func (x *HealthPingConfig) GetTimeout() int64 { + if x != nil { + return x.Timeout + } + return 0 +} + +var File_app_observatory_burst_config_proto protoreflect.FileDescriptor + +var file_app_observatory_burst_config_proto_rawDesc = []byte{ + 0x0a, 0x22, 0x61, 0x70, 0x70, 0x2f, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x6f, 0x72, + 0x79, 0x2f, 0x62, 0x75, 0x72, 0x73, 0x74, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x1f, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, + 0x61, 0x70, 0x70, 0x2e, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x6f, 0x72, 0x79, 0x2e, + 0x62, 0x75, 0x72, 0x73, 0x74, 0x22, 0x87, 0x01, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x12, 0x29, 0x0a, 0x10, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x73, 0x65, 0x6c, 0x65, + 0x63, 0x74, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x75, 0x62, 0x6a, + 0x65, 0x63, 0x74, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x52, 0x0a, 0x0b, 0x70, + 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x31, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, + 0x2e, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x62, 0x75, 0x72, + 0x73, 0x74, 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x50, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x52, 0x0a, 0x70, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, + 0xb4, 0x01, 0x0a, 0x10, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x50, 0x69, 0x6e, 0x67, 0x43, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x74, 0x69, + 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x22, 0x0a, 0x0c, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, + 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x6f, + 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x12, 0x1a, 0x0a, 0x08, 0x69, 0x6e, + 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x69, 0x6e, + 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x12, 0x24, 0x0a, 0x0d, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x69, + 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x73, + 0x61, 0x6d, 0x70, 0x6c, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x18, 0x0a, 0x07, + 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x74, + 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x42, 0x70, 0x0a, 0x1e, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, + 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x6f, + 0x72, 0x79, 0x2e, 0x62, 0x75, 0x72, 0x73, 0x74, 0x50, 0x01, 0x5a, 0x2f, 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, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, + 0x61, 0x74, 0x6f, 0x72, 0x79, 0x2f, 0x62, 0x75, 0x72, 0x73, 0x74, 0xaa, 0x02, 0x1a, 0x58, 0x72, + 0x61, 0x79, 0x2e, 0x41, 0x70, 0x70, 0x2e, 0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x6f, + 0x72, 0x79, 0x2e, 0x42, 0x75, 0x72, 0x73, 0x74, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_app_observatory_burst_config_proto_rawDescOnce sync.Once + file_app_observatory_burst_config_proto_rawDescData = file_app_observatory_burst_config_proto_rawDesc +) + +func file_app_observatory_burst_config_proto_rawDescGZIP() []byte { + file_app_observatory_burst_config_proto_rawDescOnce.Do(func() { + file_app_observatory_burst_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_app_observatory_burst_config_proto_rawDescData) + }) + return file_app_observatory_burst_config_proto_rawDescData +} + +var file_app_observatory_burst_config_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_app_observatory_burst_config_proto_goTypes = []interface{}{ + (*Config)(nil), // 0: xray.core.app.observatory.burst.Config + (*HealthPingConfig)(nil), // 1: xray.core.app.observatory.burst.HealthPingConfig +} +var file_app_observatory_burst_config_proto_depIdxs = []int32{ + 1, // 0: xray.core.app.observatory.burst.Config.ping_config:type_name -> xray.core.app.observatory.burst.HealthPingConfig + 1, // [1:1] is the sub-list for method output_type + 1, // [1:1] is the sub-list for method input_type + 1, // [1:1] is the sub-list for extension type_name + 1, // [1:1] is the sub-list for extension extendee + 0, // [0:1] is the sub-list for field type_name +} + +func init() { file_app_observatory_burst_config_proto_init() } +func file_app_observatory_burst_config_proto_init() { + if File_app_observatory_burst_config_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_app_observatory_burst_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 + } + } + file_app_observatory_burst_config_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*HealthPingConfig); 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_observatory_burst_config_proto_rawDesc, + NumEnums: 0, + NumMessages: 2, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_app_observatory_burst_config_proto_goTypes, + DependencyIndexes: file_app_observatory_burst_config_proto_depIdxs, + MessageInfos: file_app_observatory_burst_config_proto_msgTypes, + }.Build() + File_app_observatory_burst_config_proto = out.File + file_app_observatory_burst_config_proto_rawDesc = nil + file_app_observatory_burst_config_proto_goTypes = nil + file_app_observatory_burst_config_proto_depIdxs = nil +} diff --git a/app/observatory/burst/config.proto b/app/observatory/burst/config.proto new file mode 100644 index 00000000..ead75478 --- /dev/null +++ b/app/observatory/burst/config.proto @@ -0,0 +1,29 @@ +syntax = "proto3"; + +package xray.core.app.observatory.burst; +option csharp_namespace = "Xray.App.Observatory.Burst"; +option go_package = "github.com/xtls/xray-core/app/observatory/burst"; +option java_package = "com.xray.app.observatory.burst"; +option java_multiple_files = true; + +message Config { + /* @Document The selectors for outbound under observation + */ + repeated string subject_selector = 2; + + HealthPingConfig ping_config = 3; +} + +message HealthPingConfig { + // destination url, need 204 for success return + // default https://connectivitycheck.gstatic.com/generate_204 + string destination = 1; + // connectivity check url + string connectivity = 2; + // health check interval, int64 values of time.Duration + int64 interval = 3; + // sampling count is the amount of recent ping results which are kept for calculation + int32 samplingCount = 4; + // ping timeout, int64 values of time.Duration + int64 timeout = 5; +} diff --git a/app/observatory/burst/errors.generated.go b/app/observatory/burst/errors.generated.go new file mode 100644 index 00000000..35638eec --- /dev/null +++ b/app/observatory/burst/errors.generated.go @@ -0,0 +1,9 @@ +package burst + +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/observatory/burst/healthping.go b/app/observatory/burst/healthping.go new file mode 100644 index 00000000..cde9fbc1 --- /dev/null +++ b/app/observatory/burst/healthping.go @@ -0,0 +1,244 @@ +package burst + +import ( + "context" + "fmt" + "strings" + "sync" + "time" + + "github.com/xtls/xray-core/common/dice" +) + +// HealthPingSettings holds settings for health Checker +type HealthPingSettings struct { + Destination string `json:"destination"` + Connectivity string `json:"connectivity"` + Interval time.Duration `json:"interval"` + SamplingCount int `json:"sampling"` + Timeout time.Duration `json:"timeout"` +} + +// HealthPing is the health checker for balancers +type HealthPing struct { + ctx context.Context + access sync.Mutex + ticker *time.Ticker + tickerClose chan struct{} + + Settings *HealthPingSettings + Results map[string]*HealthPingRTTS +} + +// NewHealthPing creates a new HealthPing with settings +func NewHealthPing(ctx context.Context, config *HealthPingConfig) *HealthPing { + settings := &HealthPingSettings{} + if config != nil { + settings = &HealthPingSettings{ + Connectivity: strings.TrimSpace(config.Connectivity), + Destination: strings.TrimSpace(config.Destination), + Interval: time.Duration(config.Interval), + SamplingCount: int(config.SamplingCount), + Timeout: time.Duration(config.Timeout), + } + } + if settings.Destination == "" { + // Destination URL, need 204 for success return default to chromium + // https://github.com/chromium/chromium/blob/main/components/safety_check/url_constants.cc#L10 + // https://chromium.googlesource.com/chromium/src/+/refs/heads/main/components/safety_check/url_constants.cc#10 + settings.Destination = "https://connectivitycheck.gstatic.com/generate_204" + } + if settings.Interval == 0 { + settings.Interval = time.Duration(1) * time.Minute + } else if settings.Interval < 10 { + newError("health check interval is too small, 10s is applied").AtWarning().WriteToLog() + settings.Interval = time.Duration(10) * time.Second + } + if settings.SamplingCount <= 0 { + settings.SamplingCount = 10 + } + if settings.Timeout <= 0 { + // results are saved after all health pings finish, + // a larger timeout could possibly makes checks run longer + settings.Timeout = time.Duration(5) * time.Second + } + return &HealthPing{ + ctx: ctx, + Settings: settings, + Results: nil, + } +} + +// StartScheduler implements the HealthChecker +func (h *HealthPing) StartScheduler(selector func() ([]string, error)) { + if h.ticker != nil { + return + } + interval := h.Settings.Interval * time.Duration(h.Settings.SamplingCount) + ticker := time.NewTicker(interval) + tickerClose := make(chan struct{}) + h.ticker = ticker + h.tickerClose = tickerClose + go func() { + for { + go func() { + tags, err := selector() + if err != nil { + newError("error select outbounds for scheduled health check: ", err).AtWarning().WriteToLog() + return + } + h.doCheck(tags, interval, h.Settings.SamplingCount) + h.Cleanup(tags) + }() + select { + case <-ticker.C: + continue + case <-tickerClose: + return + } + } + }() +} + +// StopScheduler implements the HealthChecker +func (h *HealthPing) StopScheduler() { + if h.ticker == nil { + return + } + h.ticker.Stop() + h.ticker = nil + close(h.tickerClose) + h.tickerClose = nil +} + +// Check implements the HealthChecker +func (h *HealthPing) Check(tags []string) error { + if len(tags) == 0 { + return nil + } + newError("perform one-time health check for tags ", tags).AtInfo().WriteToLog() + h.doCheck(tags, 0, 1) + return nil +} + +type rtt struct { + handler string + value time.Duration +} + +// doCheck performs the 'rounds' amount checks in given 'duration'. You should make +// sure all tags are valid for current balancer +func (h *HealthPing) doCheck(tags []string, duration time.Duration, rounds int) { + count := len(tags) * rounds + if count == 0 { + return + } + ch := make(chan *rtt, count) + + for _, tag := range tags { + handler := tag + client := newPingClient( + h.ctx, + h.Settings.Destination, + h.Settings.Timeout, + handler, + ) + for i := 0; i < rounds; i++ { + delay := time.Duration(0) + if duration > 0 { + delay = time.Duration(dice.Roll(int(duration))) + } + time.AfterFunc(delay, func() { + newError("checking ", handler).AtDebug().WriteToLog() + delay, err := client.MeasureDelay() + if err == nil { + ch <- &rtt{ + handler: handler, + value: delay, + } + return + } + if !h.checkConnectivity() { + newError("network is down").AtWarning().WriteToLog() + ch <- &rtt{ + handler: handler, + value: 0, + } + return + } + newError(fmt.Sprintf( + "error ping %s with %s: %s", + h.Settings.Destination, + handler, + err, + )).AtWarning().WriteToLog() + ch <- &rtt{ + handler: handler, + value: rttFailed, + } + }) + } + } + for i := 0; i < count; i++ { + rtt := <-ch + if rtt.value > 0 { + // should not put results when network is down + h.PutResult(rtt.handler, rtt.value) + } + } +} + +// PutResult put a ping rtt to results +func (h *HealthPing) PutResult(tag string, rtt time.Duration) { + h.access.Lock() + defer h.access.Unlock() + if h.Results == nil { + h.Results = make(map[string]*HealthPingRTTS) + } + r, ok := h.Results[tag] + if !ok { + // validity is 2 times to sampling period, since the check are + // distributed in the time line randomly, in extreme cases, + // previous checks are distributed on the left, and latters + // on the right + validity := h.Settings.Interval * time.Duration(h.Settings.SamplingCount) * 2 + r = NewHealthPingResult(h.Settings.SamplingCount, validity) + h.Results[tag] = r + } + r.Put(rtt) +} + +// Cleanup removes results of removed handlers, +// tags should be all valid tags of the Balancer now +func (h *HealthPing) Cleanup(tags []string) { + h.access.Lock() + defer h.access.Unlock() + for tag := range h.Results { + found := false + for _, v := range tags { + if tag == v { + found = true + break + } + } + if !found { + delete(h.Results, tag) + } + } +} + +// checkConnectivity checks the network connectivity, it returns +// true if network is good or "connectivity check url" not set +func (h *HealthPing) checkConnectivity() bool { + if h.Settings.Connectivity == "" { + return true + } + tester := newDirectPingClient( + h.Settings.Connectivity, + h.Settings.Timeout, + ) + if _, err := tester.MeasureDelay(); err != nil { + return false + } + return true +} diff --git a/app/observatory/burst/healthping_result.go b/app/observatory/burst/healthping_result.go new file mode 100644 index 00000000..f48d37b6 --- /dev/null +++ b/app/observatory/burst/healthping_result.go @@ -0,0 +1,143 @@ +package burst + +import ( + "math" + "time" +) + +// HealthPingStats is the statistics of HealthPingRTTS +type HealthPingStats struct { + All int + Fail int + Deviation time.Duration + Average time.Duration + Max time.Duration + Min time.Duration +} + +// HealthPingRTTS holds ping rtts for health Checker +type HealthPingRTTS struct { + idx int + cap int + validity time.Duration + rtts []*pingRTT + + lastUpdateAt time.Time + stats *HealthPingStats +} + +type pingRTT struct { + time time.Time + value time.Duration +} + +// NewHealthPingResult returns a *HealthPingResult with specified capacity +func NewHealthPingResult(cap int, validity time.Duration) *HealthPingRTTS { + return &HealthPingRTTS{cap: cap, validity: validity} +} + +// Get gets statistics of the HealthPingRTTS +func (h *HealthPingRTTS) Get() *HealthPingStats { + return h.getStatistics() +} + +// GetWithCache get statistics and write cache for next call +// Make sure use Mutex.Lock() before calling it, RWMutex.RLock() +// is not an option since it writes cache +func (h *HealthPingRTTS) GetWithCache() *HealthPingStats { + lastPutAt := h.rtts[h.idx].time + now := time.Now() + if h.stats == nil || h.lastUpdateAt.Before(lastPutAt) || h.findOutdated(now) >= 0 { + h.stats = h.getStatistics() + h.lastUpdateAt = now + } + return h.stats +} + +// Put puts a new rtt to the HealthPingResult +func (h *HealthPingRTTS) Put(d time.Duration) { + if h.rtts == nil { + h.rtts = make([]*pingRTT, h.cap) + for i := 0; i < h.cap; i++ { + h.rtts[i] = &pingRTT{} + } + h.idx = -1 + } + h.idx = h.calcIndex(1) + now := time.Now() + h.rtts[h.idx].time = now + h.rtts[h.idx].value = d +} + +func (h *HealthPingRTTS) calcIndex(step int) int { + idx := h.idx + idx += step + if idx >= h.cap { + idx %= h.cap + } + return idx +} + +func (h *HealthPingRTTS) getStatistics() *HealthPingStats { + stats := &HealthPingStats{} + stats.Fail = 0 + stats.Max = 0 + stats.Min = rttFailed + sum := time.Duration(0) + cnt := 0 + validRTTs := make([]time.Duration, 0) + for _, rtt := range h.rtts { + switch { + case rtt.value == 0 || time.Since(rtt.time) > h.validity: + continue + case rtt.value == rttFailed: + stats.Fail++ + continue + } + cnt++ + sum += rtt.value + validRTTs = append(validRTTs, rtt.value) + if stats.Max < rtt.value { + stats.Max = rtt.value + } + if stats.Min > rtt.value { + stats.Min = rtt.value + } + } + stats.All = cnt + stats.Fail + if cnt == 0 { + stats.Min = 0 + return stats + } + stats.Average = time.Duration(int(sum) / cnt) + var std float64 + if cnt < 2 { + // no enough data for standard deviation, we assume it's half of the average rtt + // if we don't do this, standard deviation of 1 round tested nodes is 0, will always + // selected before 2 or more rounds tested nodes + std = float64(stats.Average / 2) + } else { + variance := float64(0) + for _, rtt := range validRTTs { + variance += math.Pow(float64(rtt-stats.Average), 2) + } + std = math.Sqrt(variance / float64(cnt)) + } + stats.Deviation = time.Duration(std) + return stats +} + +func (h *HealthPingRTTS) findOutdated(now time.Time) int { + for i := h.cap - 1; i < 2*h.cap; i++ { + // from oldest to latest + idx := h.calcIndex(i) + validity := h.rtts[idx].time.Add(h.validity) + if h.lastUpdateAt.After(validity) { + return idx + } + if validity.Before(now) { + return idx + } + } + return -1 +} diff --git a/app/observatory/burst/healthping_result_test.go b/app/observatory/burst/healthping_result_test.go new file mode 100644 index 00000000..a93e22dd --- /dev/null +++ b/app/observatory/burst/healthping_result_test.go @@ -0,0 +1,106 @@ +package burst_test + +import ( + "math" + reflect "reflect" + "testing" + "time" + + "github.com/xtls/xray-core/app/observatory/burst" +) + +func TestHealthPingResults(t *testing.T) { + rtts := []int64{60, 140, 60, 140, 60, 60, 140, 60, 140} + hr := burst.NewHealthPingResult(4, time.Hour) + for _, rtt := range rtts { + hr.Put(time.Duration(rtt)) + } + rttFailed := time.Duration(math.MaxInt64) + expected := &burst.HealthPingStats{ + All: 4, + Fail: 0, + Deviation: 40, + Average: 100, + Max: 140, + Min: 60, + } + actual := hr.Get() + if !reflect.DeepEqual(expected, actual) { + t.Errorf("expected: %v, actual: %v", expected, actual) + } + hr.Put(rttFailed) + hr.Put(rttFailed) + expected.Fail = 2 + actual = hr.Get() + if !reflect.DeepEqual(expected, actual) { + t.Errorf("failed half-failures test, expected: %v, actual: %v", expected, actual) + } + hr.Put(rttFailed) + hr.Put(rttFailed) + expected = &burst.HealthPingStats{ + All: 4, + Fail: 4, + Deviation: 0, + Average: 0, + Max: 0, + Min: 0, + } + actual = hr.Get() + if !reflect.DeepEqual(expected, actual) { + t.Errorf("failed all-failures test, expected: %v, actual: %v", expected, actual) + } +} + +func TestHealthPingResultsIgnoreOutdated(t *testing.T) { + rtts := []int64{60, 140, 60, 140} + hr := burst.NewHealthPingResult(4, time.Duration(10)*time.Millisecond) + for i, rtt := range rtts { + if i == 2 { + // wait for previous 2 outdated + time.Sleep(time.Duration(10) * time.Millisecond) + } + hr.Put(time.Duration(rtt)) + } + hr.Get() + expected := &burst.HealthPingStats{ + All: 2, + Fail: 0, + Deviation: 40, + Average: 100, + Max: 140, + Min: 60, + } + actual := hr.Get() + if !reflect.DeepEqual(expected, actual) { + t.Errorf("failed 'half-outdated' test, expected: %v, actual: %v", expected, actual) + } + // wait for all outdated + time.Sleep(time.Duration(10) * time.Millisecond) + expected = &burst.HealthPingStats{ + All: 0, + Fail: 0, + Deviation: 0, + Average: 0, + Max: 0, + Min: 0, + } + actual = hr.Get() + if !reflect.DeepEqual(expected, actual) { + t.Errorf("failed 'outdated / not-tested' test, expected: %v, actual: %v", expected, actual) + } + + hr.Put(time.Duration(60)) + expected = &burst.HealthPingStats{ + All: 1, + Fail: 0, + // 1 sample, std=0.5rtt + Deviation: 30, + Average: 60, + Max: 60, + Min: 60, + } + actual = hr.Get() + if !reflect.DeepEqual(expected, actual) { + t.Errorf("expected: %v, actual: %v", expected, actual) + } +} diff --git a/app/observatory/burst/ping.go b/app/observatory/burst/ping.go new file mode 100644 index 00000000..ccc0af68 --- /dev/null +++ b/app/observatory/burst/ping.go @@ -0,0 +1,69 @@ +package burst + +import ( + "context" + "net/http" + "time" + + "github.com/xtls/xray-core/common/net" + "github.com/xtls/xray-core/transport/internet/tagged" +) + +type pingClient struct { + destination string + httpClient *http.Client +} + +func newPingClient(ctx context.Context, destination string, timeout time.Duration, handler string) *pingClient { + return &pingClient{ + destination: destination, + httpClient: newHTTPClient(ctx, handler, timeout), + } +} + +func newDirectPingClient(destination string, timeout time.Duration) *pingClient { + return &pingClient{ + destination: destination, + httpClient: &http.Client{Timeout: timeout}, + } +} + +func newHTTPClient(ctxv context.Context, handler string, timeout time.Duration) *http.Client { + tr := &http.Transport{ + DisableKeepAlives: true, + DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { + dest, err := net.ParseDestination(network + ":" + addr) + if err != nil { + return nil, err + } + return tagged.Dialer(ctxv, dest, handler) + }, + } + return &http.Client{ + Transport: tr, + Timeout: timeout, + // don't follow redirect + CheckRedirect: func(req *http.Request, via []*http.Request) error { + return http.ErrUseLastResponse + }, + } +} + +// MeasureDelay returns the delay time of the request to dest +func (s *pingClient) MeasureDelay() (time.Duration, error) { + if s.httpClient == nil { + panic("pingClient no initialized") + } + req, err := http.NewRequest(http.MethodHead, s.destination, nil) + if err != nil { + return rttFailed, err + } + start := time.Now() + resp, err := s.httpClient.Do(req) + if err != nil { + return rttFailed, err + } + // don't wait for body + resp.Body.Close() + return time.Since(start), nil +} diff --git a/app/observatory/command/command.pb.go b/app/observatory/command/command.pb.go index df531537..abf170c8 100644 --- a/app/observatory/command/command.pb.go +++ b/app/observatory/command/command.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.31.0 +// protoc-gen-go v1.32.0 // protoc v4.23.1 // source: app/observatory/command/command.proto diff --git a/app/observatory/config.pb.go b/app/observatory/config.pb.go index 43eb7196..708fcf76 100644 --- a/app/observatory/config.pb.go +++ b/app/observatory/config.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.31.0 +// protoc-gen-go v1.32.0 // protoc v4.23.1 // source: app/observatory/config.proto @@ -67,6 +67,93 @@ func (x *ObservationResult) GetStatus() []*OutboundStatus { return nil } +type HealthPingMeasurementResult struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + All int64 `protobuf:"varint,1,opt,name=all,proto3" json:"all,omitempty"` + Fail int64 `protobuf:"varint,2,opt,name=fail,proto3" json:"fail,omitempty"` + Deviation int64 `protobuf:"varint,3,opt,name=deviation,proto3" json:"deviation,omitempty"` + Average int64 `protobuf:"varint,4,opt,name=average,proto3" json:"average,omitempty"` + Max int64 `protobuf:"varint,5,opt,name=max,proto3" json:"max,omitempty"` + Min int64 `protobuf:"varint,6,opt,name=min,proto3" json:"min,omitempty"` +} + +func (x *HealthPingMeasurementResult) Reset() { + *x = HealthPingMeasurementResult{} + if protoimpl.UnsafeEnabled { + mi := &file_app_observatory_config_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *HealthPingMeasurementResult) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HealthPingMeasurementResult) ProtoMessage() {} + +func (x *HealthPingMeasurementResult) ProtoReflect() protoreflect.Message { + mi := &file_app_observatory_config_proto_msgTypes[1] + 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 HealthPingMeasurementResult.ProtoReflect.Descriptor instead. +func (*HealthPingMeasurementResult) Descriptor() ([]byte, []int) { + return file_app_observatory_config_proto_rawDescGZIP(), []int{1} +} + +func (x *HealthPingMeasurementResult) GetAll() int64 { + if x != nil { + return x.All + } + return 0 +} + +func (x *HealthPingMeasurementResult) GetFail() int64 { + if x != nil { + return x.Fail + } + return 0 +} + +func (x *HealthPingMeasurementResult) GetDeviation() int64 { + if x != nil { + return x.Deviation + } + return 0 +} + +func (x *HealthPingMeasurementResult) GetAverage() int64 { + if x != nil { + return x.Average + } + return 0 +} + +func (x *HealthPingMeasurementResult) GetMax() int64 { + if x != nil { + return x.Max + } + return 0 +} + +func (x *HealthPingMeasurementResult) GetMin() int64 { + if x != nil { + return x.Min + } + return 0 +} + type OutboundStatus struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -90,13 +177,14 @@ type OutboundStatus struct { LastSeenTime int64 `protobuf:"varint,5,opt,name=last_seen_time,json=lastSeenTime,proto3" json:"last_seen_time,omitempty"` // @Document The time this outbound is tried // @Type id.outboundTag - LastTryTime int64 `protobuf:"varint,6,opt,name=last_try_time,json=lastTryTime,proto3" json:"last_try_time,omitempty"` + LastTryTime int64 `protobuf:"varint,6,opt,name=last_try_time,json=lastTryTime,proto3" json:"last_try_time,omitempty"` + HealthPing *HealthPingMeasurementResult `protobuf:"bytes,7,opt,name=health_ping,json=healthPing,proto3" json:"health_ping,omitempty"` } func (x *OutboundStatus) Reset() { *x = OutboundStatus{} if protoimpl.UnsafeEnabled { - mi := &file_app_observatory_config_proto_msgTypes[1] + mi := &file_app_observatory_config_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -109,7 +197,7 @@ func (x *OutboundStatus) String() string { func (*OutboundStatus) ProtoMessage() {} func (x *OutboundStatus) ProtoReflect() protoreflect.Message { - mi := &file_app_observatory_config_proto_msgTypes[1] + mi := &file_app_observatory_config_proto_msgTypes[2] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -122,7 +210,7 @@ func (x *OutboundStatus) ProtoReflect() protoreflect.Message { // Deprecated: Use OutboundStatus.ProtoReflect.Descriptor instead. func (*OutboundStatus) Descriptor() ([]byte, []int) { - return file_app_observatory_config_proto_rawDescGZIP(), []int{1} + return file_app_observatory_config_proto_rawDescGZIP(), []int{2} } func (x *OutboundStatus) GetAlive() bool { @@ -167,6 +255,13 @@ func (x *OutboundStatus) GetLastTryTime() int64 { return 0 } +func (x *OutboundStatus) GetHealthPing() *HealthPingMeasurementResult { + if x != nil { + return x.HealthPing + } + return nil +} + type ProbeResult struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -187,7 +282,7 @@ type ProbeResult struct { func (x *ProbeResult) Reset() { *x = ProbeResult{} if protoimpl.UnsafeEnabled { - mi := &file_app_observatory_config_proto_msgTypes[2] + mi := &file_app_observatory_config_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -200,7 +295,7 @@ func (x *ProbeResult) String() string { func (*ProbeResult) ProtoMessage() {} func (x *ProbeResult) ProtoReflect() protoreflect.Message { - mi := &file_app_observatory_config_proto_msgTypes[2] + mi := &file_app_observatory_config_proto_msgTypes[3] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -213,7 +308,7 @@ func (x *ProbeResult) ProtoReflect() protoreflect.Message { // Deprecated: Use ProbeResult.ProtoReflect.Descriptor instead. func (*ProbeResult) Descriptor() ([]byte, []int) { - return file_app_observatory_config_proto_rawDescGZIP(), []int{2} + return file_app_observatory_config_proto_rawDescGZIP(), []int{3} } func (x *ProbeResult) GetAlive() bool { @@ -250,7 +345,7 @@ type Intensity struct { func (x *Intensity) Reset() { *x = Intensity{} if protoimpl.UnsafeEnabled { - mi := &file_app_observatory_config_proto_msgTypes[3] + mi := &file_app_observatory_config_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -263,7 +358,7 @@ func (x *Intensity) String() string { func (*Intensity) ProtoMessage() {} func (x *Intensity) ProtoReflect() protoreflect.Message { - mi := &file_app_observatory_config_proto_msgTypes[3] + mi := &file_app_observatory_config_proto_msgTypes[4] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -276,7 +371,7 @@ func (x *Intensity) ProtoReflect() protoreflect.Message { // Deprecated: Use Intensity.ProtoReflect.Descriptor instead. func (*Intensity) Descriptor() ([]byte, []int) { - return file_app_observatory_config_proto_rawDescGZIP(), []int{3} + return file_app_observatory_config_proto_rawDescGZIP(), []int{4} } func (x *Intensity) GetProbeInterval() uint32 { @@ -301,7 +396,7 @@ type Config struct { func (x *Config) Reset() { *x = Config{} if protoimpl.UnsafeEnabled { - mi := &file_app_observatory_config_proto_msgTypes[4] + mi := &file_app_observatory_config_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -314,7 +409,7 @@ func (x *Config) String() string { func (*Config) ProtoMessage() {} func (x *Config) ProtoReflect() protoreflect.Message { - mi := &file_app_observatory_config_proto_msgTypes[4] + mi := &file_app_observatory_config_proto_msgTypes[5] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -327,7 +422,7 @@ func (x *Config) ProtoReflect() protoreflect.Message { // Deprecated: Use Config.ProtoReflect.Descriptor instead. func (*Config) Descriptor() ([]byte, []int) { - return file_app_observatory_config_proto_rawDescGZIP(), []int{4} + return file_app_observatory_config_proto_rawDescGZIP(), []int{5} } func (x *Config) GetSubjectSelector() []string { @@ -370,47 +465,62 @@ var file_app_observatory_config_proto_rawDesc = []byte{ 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x22, 0xd5, 0x01, 0x0a, 0x0e, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x53, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x6c, 0x69, 0x76, 0x65, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x05, 0x61, 0x6c, 0x69, 0x76, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x64, 0x65, - 0x6c, 0x61, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x64, 0x65, 0x6c, 0x61, 0x79, - 0x12, 0x2a, 0x0a, 0x11, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x72, - 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x6c, 0x61, 0x73, - 0x74, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x21, 0x0a, 0x0c, - 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x74, 0x61, 0x67, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0b, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x54, 0x61, 0x67, 0x12, - 0x24, 0x0a, 0x0e, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x73, 0x65, 0x65, 0x6e, 0x5f, 0x74, 0x69, 0x6d, - 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x6c, 0x61, 0x73, 0x74, 0x53, 0x65, 0x65, - 0x6e, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x22, 0x0a, 0x0d, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x74, 0x72, - 0x79, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x6c, 0x61, - 0x73, 0x74, 0x54, 0x72, 0x79, 0x54, 0x69, 0x6d, 0x65, 0x22, 0x65, 0x0a, 0x0b, 0x50, 0x72, 0x6f, - 0x62, 0x65, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x6c, 0x69, 0x76, - 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x61, 0x6c, 0x69, 0x76, 0x65, 0x12, 0x14, - 0x0a, 0x05, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x64, - 0x65, 0x6c, 0x61, 0x79, 0x12, 0x2a, 0x0a, 0x11, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x65, 0x72, 0x72, - 0x6f, 0x72, 0x5f, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0f, 0x6c, 0x61, 0x73, 0x74, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, - 0x22, 0x32, 0x0a, 0x09, 0x49, 0x6e, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x74, 0x79, 0x12, 0x25, 0x0a, - 0x0e, 0x70, 0x72, 0x6f, 0x62, 0x65, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0d, 0x70, 0x72, 0x6f, 0x62, 0x65, 0x49, 0x6e, 0x74, 0x65, - 0x72, 0x76, 0x61, 0x6c, 0x22, 0xa6, 0x01, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, - 0x29, 0x0a, 0x10, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x73, 0x65, 0x6c, 0x65, 0x63, - 0x74, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x75, 0x62, 0x6a, 0x65, - 0x63, 0x74, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x72, - 0x6f, 0x62, 0x65, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, - 0x72, 0x6f, 0x62, 0x65, 0x55, 0x72, 0x6c, 0x12, 0x25, 0x0a, 0x0e, 0x70, 0x72, 0x6f, 0x62, 0x65, - 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x0d, 0x70, 0x72, 0x6f, 0x62, 0x65, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x12, 0x2d, - 0x0a, 0x12, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, - 0x65, 0x6e, 0x63, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x65, 0x6e, 0x61, 0x62, - 0x6c, 0x65, 0x43, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x42, 0x5e, 0x0a, - 0x18, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x6f, 0x62, - 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x6f, 0x72, 0x79, 0x50, 0x01, 0x5a, 0x29, 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, 0x6f, 0x62, 0x73, 0x65, 0x72, - 0x76, 0x61, 0x74, 0x6f, 0x72, 0x79, 0xaa, 0x02, 0x14, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x41, 0x70, - 0x70, 0x2e, 0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x6f, 0x72, 0x79, 0x62, 0x06, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x73, 0x22, 0x9f, 0x01, 0x0a, 0x1b, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x50, 0x69, 0x6e, 0x67, + 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x75, 0x6c, + 0x74, 0x12, 0x10, 0x0a, 0x03, 0x61, 0x6c, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x03, + 0x61, 0x6c, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x61, 0x69, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x04, 0x66, 0x61, 0x69, 0x6c, 0x12, 0x1c, 0x0a, 0x09, 0x64, 0x65, 0x76, 0x69, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x64, 0x65, 0x76, 0x69, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x76, 0x65, 0x72, 0x61, 0x67, 0x65, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x61, 0x76, 0x65, 0x72, 0x61, 0x67, 0x65, 0x12, + 0x10, 0x0a, 0x03, 0x6d, 0x61, 0x78, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x03, 0x6d, 0x61, + 0x78, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x69, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x03, + 0x6d, 0x69, 0x6e, 0x22, 0xae, 0x02, 0x0a, 0x0e, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, + 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x6c, 0x69, 0x76, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x61, 0x6c, 0x69, 0x76, 0x65, 0x12, 0x14, 0x0a, 0x05, + 0x64, 0x65, 0x6c, 0x61, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x64, 0x65, 0x6c, + 0x61, 0x79, 0x12, 0x2a, 0x0a, 0x11, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, + 0x5f, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x6c, + 0x61, 0x73, 0x74, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x21, + 0x0a, 0x0c, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x74, 0x61, 0x67, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x54, 0x61, + 0x67, 0x12, 0x24, 0x0a, 0x0e, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x73, 0x65, 0x65, 0x6e, 0x5f, 0x74, + 0x69, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x6c, 0x61, 0x73, 0x74, 0x53, + 0x65, 0x65, 0x6e, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x22, 0x0a, 0x0d, 0x6c, 0x61, 0x73, 0x74, 0x5f, + 0x74, 0x72, 0x79, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, + 0x6c, 0x61, 0x73, 0x74, 0x54, 0x72, 0x79, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x57, 0x0a, 0x0b, 0x68, + 0x65, 0x61, 0x6c, 0x74, 0x68, 0x5f, 0x70, 0x69, 0x6e, 0x67, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x36, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, + 0x2e, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x48, 0x65, 0x61, + 0x6c, 0x74, 0x68, 0x50, 0x69, 0x6e, 0x67, 0x4d, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x6d, 0x65, + 0x6e, 0x74, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x0a, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, + 0x50, 0x69, 0x6e, 0x67, 0x22, 0x65, 0x0a, 0x0b, 0x50, 0x72, 0x6f, 0x62, 0x65, 0x52, 0x65, 0x73, + 0x75, 0x6c, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x6c, 0x69, 0x76, 0x65, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x05, 0x61, 0x6c, 0x69, 0x76, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x64, 0x65, 0x6c, + 0x61, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x12, + 0x2a, 0x0a, 0x11, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x72, 0x65, + 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x6c, 0x61, 0x73, 0x74, + 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x22, 0x32, 0x0a, 0x09, 0x49, + 0x6e, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x74, 0x79, 0x12, 0x25, 0x0a, 0x0e, 0x70, 0x72, 0x6f, 0x62, + 0x65, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, + 0x52, 0x0d, 0x70, 0x72, 0x6f, 0x62, 0x65, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x22, + 0xa6, 0x01, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x29, 0x0a, 0x10, 0x73, 0x75, + 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x02, + 0x20, 0x03, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x53, 0x65, 0x6c, + 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x72, 0x6f, 0x62, 0x65, 0x5f, 0x75, + 0x72, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x62, 0x65, 0x55, + 0x72, 0x6c, 0x12, 0x25, 0x0a, 0x0e, 0x70, 0x72, 0x6f, 0x62, 0x65, 0x5f, 0x69, 0x6e, 0x74, 0x65, + 0x72, 0x76, 0x61, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x70, 0x72, 0x6f, 0x62, + 0x65, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x12, 0x2d, 0x0a, 0x12, 0x65, 0x6e, 0x61, + 0x62, 0x6c, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x6f, 0x6e, + 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x42, 0x5e, 0x0a, 0x18, 0x63, 0x6f, 0x6d, 0x2e, + 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, + 0x74, 0x6f, 0x72, 0x79, 0x50, 0x01, 0x5a, 0x29, 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, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x6f, 0x72, + 0x79, 0xaa, 0x02, 0x14, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x41, 0x70, 0x70, 0x2e, 0x4f, 0x62, 0x73, + 0x65, 0x72, 0x76, 0x61, 0x74, 0x6f, 0x72, 0x79, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -425,21 +535,23 @@ func file_app_observatory_config_proto_rawDescGZIP() []byte { return file_app_observatory_config_proto_rawDescData } -var file_app_observatory_config_proto_msgTypes = make([]protoimpl.MessageInfo, 5) +var file_app_observatory_config_proto_msgTypes = make([]protoimpl.MessageInfo, 6) var file_app_observatory_config_proto_goTypes = []interface{}{ - (*ObservationResult)(nil), // 0: xray.core.app.observatory.ObservationResult - (*OutboundStatus)(nil), // 1: xray.core.app.observatory.OutboundStatus - (*ProbeResult)(nil), // 2: xray.core.app.observatory.ProbeResult - (*Intensity)(nil), // 3: xray.core.app.observatory.Intensity - (*Config)(nil), // 4: xray.core.app.observatory.Config + (*ObservationResult)(nil), // 0: xray.core.app.observatory.ObservationResult + (*HealthPingMeasurementResult)(nil), // 1: xray.core.app.observatory.HealthPingMeasurementResult + (*OutboundStatus)(nil), // 2: xray.core.app.observatory.OutboundStatus + (*ProbeResult)(nil), // 3: xray.core.app.observatory.ProbeResult + (*Intensity)(nil), // 4: xray.core.app.observatory.Intensity + (*Config)(nil), // 5: xray.core.app.observatory.Config } var file_app_observatory_config_proto_depIdxs = []int32{ - 1, // 0: xray.core.app.observatory.ObservationResult.status:type_name -> xray.core.app.observatory.OutboundStatus - 1, // [1:1] is the sub-list for method output_type - 1, // [1:1] is the sub-list for method input_type - 1, // [1:1] is the sub-list for extension type_name - 1, // [1:1] is the sub-list for extension extendee - 0, // [0:1] is the sub-list for field type_name + 2, // 0: xray.core.app.observatory.ObservationResult.status:type_name -> xray.core.app.observatory.OutboundStatus + 1, // 1: xray.core.app.observatory.OutboundStatus.health_ping:type_name -> xray.core.app.observatory.HealthPingMeasurementResult + 2, // [2:2] is the sub-list for method output_type + 2, // [2:2] is the sub-list for method input_type + 2, // [2:2] is the sub-list for extension type_name + 2, // [2:2] is the sub-list for extension extendee + 0, // [0:2] is the sub-list for field type_name } func init() { file_app_observatory_config_proto_init() } @@ -461,7 +573,7 @@ func file_app_observatory_config_proto_init() { } } file_app_observatory_config_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*OutboundStatus); i { + switch v := v.(*HealthPingMeasurementResult); i { case 0: return &v.state case 1: @@ -473,7 +585,7 @@ func file_app_observatory_config_proto_init() { } } file_app_observatory_config_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ProbeResult); i { + switch v := v.(*OutboundStatus); i { case 0: return &v.state case 1: @@ -485,7 +597,7 @@ func file_app_observatory_config_proto_init() { } } file_app_observatory_config_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Intensity); i { + switch v := v.(*ProbeResult); i { case 0: return &v.state case 1: @@ -497,6 +609,18 @@ func file_app_observatory_config_proto_init() { } } file_app_observatory_config_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Intensity); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_app_observatory_config_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Config); i { case 0: return &v.state @@ -515,7 +639,7 @@ func file_app_observatory_config_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_app_observatory_config_proto_rawDesc, NumEnums: 0, - NumMessages: 5, + NumMessages: 6, NumExtensions: 0, NumServices: 0, }, diff --git a/app/observatory/config.proto b/app/observatory/config.proto index 9ac9c549..fbfabad6 100644 --- a/app/observatory/config.proto +++ b/app/observatory/config.proto @@ -10,6 +10,15 @@ message ObservationResult { repeated OutboundStatus status = 1; } +message HealthPingMeasurementResult { + int64 all = 1; + int64 fail = 2; + int64 deviation = 3; + int64 average = 4; + int64 max = 5; + int64 min = 6; +} + message OutboundStatus{ /* @Document Whether this outbound is usable @Restriction ReadOnlyForUser @@ -36,6 +45,8 @@ message OutboundStatus{ @Type id.outboundTag */ int64 last_try_time = 6; + + HealthPingMeasurementResult health_ping = 7; } message ProbeResult{ diff --git a/app/policy/config.pb.go b/app/policy/config.pb.go index c8b4311c..0bfef971 100644 --- a/app/policy/config.pb.go +++ b/app/policy/config.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.31.0 +// protoc-gen-go v1.32.0 // protoc v4.23.1 // source: app/policy/config.proto diff --git a/app/proxyman/command/command.pb.go b/app/proxyman/command/command.pb.go index 458179e5..a689a808 100644 --- a/app/proxyman/command/command.pb.go +++ b/app/proxyman/command/command.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.31.0 +// protoc-gen-go v1.32.0 // protoc v4.23.1 // source: app/proxyman/command/command.proto diff --git a/app/proxyman/config.pb.go b/app/proxyman/config.pb.go index db41ad75..541679ac 100644 --- a/app/proxyman/config.pb.go +++ b/app/proxyman/config.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.31.0 +// protoc-gen-go v1.32.0 // protoc v4.23.1 // source: app/proxyman/config.proto diff --git a/app/reverse/config.pb.go b/app/reverse/config.pb.go index 83c0709b..c262f47a 100644 --- a/app/reverse/config.pb.go +++ b/app/reverse/config.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.31.0 +// protoc-gen-go v1.32.0 // protoc v4.23.1 // source: app/reverse/config.proto diff --git a/app/router/balancing.go b/app/router/balancing.go index 458e6838..7eb8c5a8 100644 --- a/app/router/balancing.go +++ b/app/router/balancing.go @@ -4,7 +4,6 @@ import ( "context" sync "sync" - "github.com/xtls/xray-core/common/dice" "github.com/xtls/xray-core/features/extension" "github.com/xtls/xray-core/features/outbound" ) @@ -13,15 +12,8 @@ type BalancingStrategy interface { PickOutbound([]string) string } -type RandomStrategy struct{} - -func (s *RandomStrategy) PickOutbound(tags []string) string { - n := len(tags) - if n == 0 { - panic("0 tags") - } - - return tags[dice.Roll(n)] +type BalancingPrincipleTarget interface { + GetPrincipleTarget([]string) []string } type RoundRobinStrategy struct { @@ -43,22 +35,36 @@ func (s *RoundRobinStrategy) PickOutbound(tags []string) string { } type Balancer struct { - selectors []string - strategy BalancingStrategy - ohm outbound.Manager + selectors []string + strategy BalancingStrategy + ohm outbound.Manager + fallbackTag string + + override override } +// PickOutbound picks the tag of a outbound func (b *Balancer) PickOutbound() (string, error) { - hs, ok := b.ohm.(outbound.HandlerSelector) - if !ok { - return "", newError("outbound.Manager is not a HandlerSelector") + candidates, err := b.SelectOutbounds() + if err != nil { + if b.fallbackTag != "" { + newError("fallback to [", b.fallbackTag, "], due to error: ", err).AtInfo().WriteToLog() + return b.fallbackTag, nil + } + return "", err } - tags := hs.Select(b.selectors) - if len(tags) == 0 { - return "", newError("no available outbounds selected") + var tag string + if o := b.override.Get(); o != "" { + tag = o + } else { + tag = b.strategy.PickOutbound(candidates) } - tag := b.strategy.PickOutbound(tags) if tag == "" { + if b.fallbackTag != "" { + newError("fallback to [", b.fallbackTag, "], due to empty tag returned").AtInfo().WriteToLog() + return b.fallbackTag, nil + } + // will use default handler return "", newError("balancing strategy returns empty tag") } return tag, nil @@ -69,3 +75,45 @@ func (b *Balancer) InjectContext(ctx context.Context) { contextReceiver.InjectContext(ctx) } } + +// SelectOutbounds select outbounds with selectors of the Balancer +func (b *Balancer) SelectOutbounds() ([]string, error) { + hs, ok := b.ohm.(outbound.HandlerSelector) + if !ok { + return nil, newError("outbound.Manager is not a HandlerSelector") + } + tags := hs.Select(b.selectors) + return tags, nil +} + +// GetPrincipleTarget implements routing.BalancerPrincipleTarget +func (r *Router) GetPrincipleTarget(tag string) ([]string, error) { + if b, ok := r.balancers[tag]; ok { + if s, ok := b.strategy.(BalancingPrincipleTarget); ok { + candidates, err := b.SelectOutbounds() + if err != nil { + return nil, newError("unable to select outbounds").Base(err) + } + return s.GetPrincipleTarget(candidates), nil + } + return nil, newError("unsupported GetPrincipleTarget") + } + return nil, newError("cannot find tag") +} + +// SetOverrideTarget implements routing.BalancerOverrider +func (r *Router) SetOverrideTarget(tag, target string) error { + if b, ok := r.balancers[tag]; ok { + b.override.Put(target) + return nil + } + return newError("cannot find tag") +} + +// GetOverrideTarget implements routing.BalancerOverrider +func (r *Router) GetOverrideTarget(tag string) (string, error) { + if b, ok := r.balancers[tag]; ok { + return b.override.Get(), nil + } + return "", newError("cannot find tag") +} diff --git a/app/router/balancing_override.go b/app/router/balancing_override.go new file mode 100644 index 00000000..30769f6b --- /dev/null +++ b/app/router/balancing_override.go @@ -0,0 +1,50 @@ +package router + +import ( + sync "sync" +) + +func (r *Router) OverrideBalancer(balancer string, target string) error { + var b *Balancer + for tag, bl := range r.balancers { + if tag == balancer { + b = bl + break + } + } + if b == nil { + return newError("balancer '", balancer, "' not found") + } + b.override.Put(target) + return nil +} + +type overrideSettings struct { + target string +} + +type override struct { + access sync.RWMutex + settings overrideSettings +} + +// Get gets the override settings +func (o *override) Get() string { + o.access.RLock() + defer o.access.RUnlock() + return o.settings.target +} + +// Put updates the override settings +func (o *override) Put(target string) { + o.access.Lock() + defer o.access.Unlock() + o.settings.target = target +} + +// Clear clears the override settings +func (o *override) Clear() { + o.access.Lock() + defer o.access.Unlock() + o.settings.target = "" +} diff --git a/app/router/command/command.go b/app/router/command/command.go index 58981056..60458f14 100644 --- a/app/router/command/command.go +++ b/app/router/command/command.go @@ -19,6 +19,41 @@ type routingServer struct { routingStats stats.Channel } +func (s *routingServer) GetBalancerInfo(ctx context.Context, request *GetBalancerInfoRequest) (*GetBalancerInfoResponse, error) { + var ret GetBalancerInfoResponse + ret.Balancer = &BalancerMsg{} + if bo, ok := s.router.(routing.BalancerOverrider); ok { + { + res, err := bo.GetOverrideTarget(request.GetTag()) + if err != nil { + return nil, err + } + ret.Balancer.Override = &OverrideInfo{ + Target: res, + } + } + } + + if pt, ok := s.router.(routing.BalancerPrincipleTarget); ok { + { + res, err := pt.GetPrincipleTarget(request.GetTag()) + if err != nil { + newError("unable to obtain principle target").Base(err).AtInfo().WriteToLog() + } else { + ret.Balancer.PrincipleTarget = &PrincipleTargetInfo{Tag: res} + } + } + } + return &ret, nil +} + +func (s *routingServer) OverrideBalancerTarget(ctx context.Context, request *OverrideBalancerTargetRequest) (*OverrideBalancerTargetResponse, error) { + if bo, ok := s.router.(routing.BalancerOverrider); ok { + return &OverrideBalancerTargetResponse{}, bo.SetOverrideTarget(request.BalancerTag, request.Target) + } + return nil, newError("unsupported router implementation") +} + // NewRoutingServer creates a statistics service with statistics manager. func NewRoutingServer(router routing.Router, routingStats stats.Channel) RoutingServiceServer { return &routingServer{ diff --git a/app/router/command/command.pb.go b/app/router/command/command.pb.go index 18296ff9..5cdf5332 100644 --- a/app/router/command/command.pb.go +++ b/app/router/command/command.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.31.0 +// protoc-gen-go v1.32.0 // protoc v4.23.1 // source: app/router/command/command.proto @@ -294,6 +294,342 @@ func (x *TestRouteRequest) GetPublishResult() bool { return false } +type PrincipleTargetInfo struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Tag []string `protobuf:"bytes,1,rep,name=tag,proto3" json:"tag,omitempty"` +} + +func (x *PrincipleTargetInfo) Reset() { + *x = PrincipleTargetInfo{} + if protoimpl.UnsafeEnabled { + mi := &file_app_router_command_command_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PrincipleTargetInfo) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PrincipleTargetInfo) ProtoMessage() {} + +func (x *PrincipleTargetInfo) ProtoReflect() protoreflect.Message { + mi := &file_app_router_command_command_proto_msgTypes[3] + 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 PrincipleTargetInfo.ProtoReflect.Descriptor instead. +func (*PrincipleTargetInfo) Descriptor() ([]byte, []int) { + return file_app_router_command_command_proto_rawDescGZIP(), []int{3} +} + +func (x *PrincipleTargetInfo) GetTag() []string { + if x != nil { + return x.Tag + } + return nil +} + +type OverrideInfo struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Target string `protobuf:"bytes,2,opt,name=target,proto3" json:"target,omitempty"` +} + +func (x *OverrideInfo) Reset() { + *x = OverrideInfo{} + if protoimpl.UnsafeEnabled { + mi := &file_app_router_command_command_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *OverrideInfo) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*OverrideInfo) ProtoMessage() {} + +func (x *OverrideInfo) ProtoReflect() protoreflect.Message { + mi := &file_app_router_command_command_proto_msgTypes[4] + 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 OverrideInfo.ProtoReflect.Descriptor instead. +func (*OverrideInfo) Descriptor() ([]byte, []int) { + return file_app_router_command_command_proto_rawDescGZIP(), []int{4} +} + +func (x *OverrideInfo) GetTarget() string { + if x != nil { + return x.Target + } + return "" +} + +type BalancerMsg struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Override *OverrideInfo `protobuf:"bytes,5,opt,name=override,proto3" json:"override,omitempty"` + PrincipleTarget *PrincipleTargetInfo `protobuf:"bytes,6,opt,name=principle_target,json=principleTarget,proto3" json:"principle_target,omitempty"` +} + +func (x *BalancerMsg) Reset() { + *x = BalancerMsg{} + if protoimpl.UnsafeEnabled { + mi := &file_app_router_command_command_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *BalancerMsg) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BalancerMsg) ProtoMessage() {} + +func (x *BalancerMsg) ProtoReflect() protoreflect.Message { + mi := &file_app_router_command_command_proto_msgTypes[5] + 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 BalancerMsg.ProtoReflect.Descriptor instead. +func (*BalancerMsg) Descriptor() ([]byte, []int) { + return file_app_router_command_command_proto_rawDescGZIP(), []int{5} +} + +func (x *BalancerMsg) GetOverride() *OverrideInfo { + if x != nil { + return x.Override + } + return nil +} + +func (x *BalancerMsg) GetPrincipleTarget() *PrincipleTargetInfo { + if x != nil { + return x.PrincipleTarget + } + return nil +} + +type GetBalancerInfoRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Tag string `protobuf:"bytes,1,opt,name=tag,proto3" json:"tag,omitempty"` +} + +func (x *GetBalancerInfoRequest) Reset() { + *x = GetBalancerInfoRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_app_router_command_command_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetBalancerInfoRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetBalancerInfoRequest) ProtoMessage() {} + +func (x *GetBalancerInfoRequest) ProtoReflect() protoreflect.Message { + mi := &file_app_router_command_command_proto_msgTypes[6] + 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 GetBalancerInfoRequest.ProtoReflect.Descriptor instead. +func (*GetBalancerInfoRequest) Descriptor() ([]byte, []int) { + return file_app_router_command_command_proto_rawDescGZIP(), []int{6} +} + +func (x *GetBalancerInfoRequest) GetTag() string { + if x != nil { + return x.Tag + } + return "" +} + +type GetBalancerInfoResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Balancer *BalancerMsg `protobuf:"bytes,1,opt,name=balancer,proto3" json:"balancer,omitempty"` +} + +func (x *GetBalancerInfoResponse) Reset() { + *x = GetBalancerInfoResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_app_router_command_command_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetBalancerInfoResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetBalancerInfoResponse) ProtoMessage() {} + +func (x *GetBalancerInfoResponse) ProtoReflect() protoreflect.Message { + mi := &file_app_router_command_command_proto_msgTypes[7] + 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 GetBalancerInfoResponse.ProtoReflect.Descriptor instead. +func (*GetBalancerInfoResponse) Descriptor() ([]byte, []int) { + return file_app_router_command_command_proto_rawDescGZIP(), []int{7} +} + +func (x *GetBalancerInfoResponse) GetBalancer() *BalancerMsg { + if x != nil { + return x.Balancer + } + return nil +} + +type OverrideBalancerTargetRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + BalancerTag string `protobuf:"bytes,1,opt,name=balancerTag,proto3" json:"balancerTag,omitempty"` + Target string `protobuf:"bytes,2,opt,name=target,proto3" json:"target,omitempty"` +} + +func (x *OverrideBalancerTargetRequest) Reset() { + *x = OverrideBalancerTargetRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_app_router_command_command_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *OverrideBalancerTargetRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*OverrideBalancerTargetRequest) ProtoMessage() {} + +func (x *OverrideBalancerTargetRequest) ProtoReflect() protoreflect.Message { + mi := &file_app_router_command_command_proto_msgTypes[8] + 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 OverrideBalancerTargetRequest.ProtoReflect.Descriptor instead. +func (*OverrideBalancerTargetRequest) Descriptor() ([]byte, []int) { + return file_app_router_command_command_proto_rawDescGZIP(), []int{8} +} + +func (x *OverrideBalancerTargetRequest) GetBalancerTag() string { + if x != nil { + return x.BalancerTag + } + return "" +} + +func (x *OverrideBalancerTargetRequest) GetTarget() string { + if x != nil { + return x.Target + } + return "" +} + +type OverrideBalancerTargetResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *OverrideBalancerTargetResponse) Reset() { + *x = OverrideBalancerTargetResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_app_router_command_command_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *OverrideBalancerTargetResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*OverrideBalancerTargetResponse) ProtoMessage() {} + +func (x *OverrideBalancerTargetResponse) ProtoReflect() protoreflect.Message { + mi := &file_app_router_command_command_proto_msgTypes[9] + 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 OverrideBalancerTargetResponse.ProtoReflect.Descriptor instead. +func (*OverrideBalancerTargetResponse) Descriptor() ([]byte, []int) { + return file_app_router_command_command_proto_rawDescGZIP(), []int{9} +} + type Config struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -303,7 +639,7 @@ type Config struct { func (x *Config) Reset() { *x = Config{} if protoimpl.UnsafeEnabled { - mi := &file_app_router_command_command_proto_msgTypes[3] + mi := &file_app_router_command_command_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -316,7 +652,7 @@ func (x *Config) String() string { func (*Config) ProtoMessage() {} func (x *Config) ProtoReflect() protoreflect.Message { - mi := &file_app_router_command_command_proto_msgTypes[3] + mi := &file_app_router_command_command_proto_msgTypes[10] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -329,7 +665,7 @@ func (x *Config) ProtoReflect() protoreflect.Message { // Deprecated: Use Config.ProtoReflect.Descriptor instead. func (*Config) Descriptor() ([]byte, []int) { - return file_app_router_command_command_proto_rawDescGZIP(), []int{3} + return file_app_router_command_command_proto_rawDescGZIP(), []int{10} } var File_app_router_command_command_proto protoreflect.FileDescriptor @@ -390,29 +726,78 @@ var file_app_router_command_command_proto_rawDesc = []byte{ 0x64, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x12, 0x24, 0x0a, 0x0d, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, - 0x22, 0x08, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x32, 0xf0, 0x01, 0x0a, 0x0e, 0x52, - 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x7b, 0x0a, - 0x15, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, - 0x67, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x35, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, + 0x22, 0x27, 0x0a, 0x13, 0x50, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x6c, 0x65, 0x54, 0x61, 0x72, + 0x67, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x18, 0x01, + 0x20, 0x03, 0x28, 0x09, 0x52, 0x03, 0x74, 0x61, 0x67, 0x22, 0x26, 0x0a, 0x0c, 0x4f, 0x76, 0x65, + 0x72, 0x72, 0x69, 0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x16, 0x0a, 0x06, 0x74, 0x61, 0x72, + 0x67, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, + 0x74, 0x22, 0xa9, 0x01, 0x0a, 0x0b, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x72, 0x4d, 0x73, + 0x67, 0x12, 0x41, 0x0a, 0x08, 0x6f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, + 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x4f, 0x76, + 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x08, 0x6f, 0x76, 0x65, 0x72, + 0x72, 0x69, 0x64, 0x65, 0x12, 0x57, 0x0a, 0x10, 0x70, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x6c, + 0x65, 0x5f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2c, + 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, + 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x50, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, + 0x6c, 0x65, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x0f, 0x70, 0x72, + 0x69, 0x6e, 0x63, 0x69, 0x70, 0x6c, 0x65, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x22, 0x2a, 0x0a, + 0x16, 0x47, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x74, 0x61, 0x67, 0x22, 0x5b, 0x0a, 0x17, 0x47, 0x65, 0x74, + 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x40, 0x0a, 0x08, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x72, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, - 0x2e, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, - 0x67, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, - 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, - 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x43, - 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x22, 0x00, 0x30, 0x01, 0x12, 0x61, 0x0a, 0x09, 0x54, 0x65, - 0x73, 0x74, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x29, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, - 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, - 0x64, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, - 0x75, 0x74, 0x65, 0x72, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x52, 0x6f, 0x75, - 0x74, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x22, 0x00, 0x42, 0x67, 0x0a, - 0x1b, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, - 0x75, 0x74, 0x65, 0x72, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x50, 0x01, 0x5a, 0x2c, - 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, 0x72, 0x6f, - 0x75, 0x74, 0x65, 0x72, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0xaa, 0x02, 0x17, 0x58, - 0x72, 0x61, 0x79, 0x2e, 0x41, 0x70, 0x70, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x43, - 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x2e, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x72, 0x4d, 0x73, 0x67, 0x52, 0x08, 0x62, 0x61, + 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x72, 0x22, 0x59, 0x0a, 0x1d, 0x4f, 0x76, 0x65, 0x72, 0x72, 0x69, + 0x64, 0x65, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x72, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x20, 0x0a, 0x0b, 0x62, 0x61, 0x6c, 0x61, 0x6e, + 0x63, 0x65, 0x72, 0x54, 0x61, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x62, 0x61, + 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x72, 0x54, 0x61, 0x67, 0x12, 0x16, 0x0a, 0x06, 0x74, 0x61, 0x72, + 0x67, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, + 0x74, 0x22, 0x20, 0x0a, 0x1e, 0x4f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x42, 0x61, 0x6c, + 0x61, 0x6e, 0x63, 0x65, 0x72, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x08, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x32, 0xf6, 0x03, + 0x0a, 0x0e, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x12, 0x7b, 0x0a, 0x15, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x52, 0x6f, 0x75, + 0x74, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x35, 0x2e, 0x78, 0x72, 0x61, 0x79, + 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, + 0x61, 0x6e, 0x64, 0x2e, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x52, 0x6f, 0x75, + 0x74, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x27, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, + 0x65, 0x72, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x69, + 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x22, 0x00, 0x30, 0x01, 0x12, 0x61, 0x0a, + 0x09, 0x54, 0x65, 0x73, 0x74, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x29, 0x2e, 0x78, 0x72, 0x61, + 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x63, 0x6f, 0x6d, + 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, + 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, + 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x22, 0x00, + 0x12, 0x76, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x72, 0x49, + 0x6e, 0x66, 0x6f, 0x12, 0x2f, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, + 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x47, 0x65, + 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x30, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, + 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x47, + 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x8b, 0x01, 0x0a, 0x16, 0x4f, 0x76, 0x65, + 0x72, 0x72, 0x69, 0x64, 0x65, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x72, 0x54, 0x61, 0x72, + 0x67, 0x65, 0x74, 0x12, 0x36, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, + 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x4f, 0x76, + 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x72, 0x54, 0x61, + 0x72, 0x67, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x37, 0x2e, 0x78, 0x72, + 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x63, 0x6f, + 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x4f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x42, 0x61, + 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x72, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x67, 0x0a, 0x1b, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, + 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x63, 0x6f, + 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x50, 0x01, 0x5a, 0x2c, 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, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2f, 0x63, 0x6f, + 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0xaa, 0x02, 0x17, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x41, 0x70, 0x70, + 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x62, + 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -427,28 +812,42 @@ func file_app_router_command_command_proto_rawDescGZIP() []byte { return file_app_router_command_command_proto_rawDescData } -var file_app_router_command_command_proto_msgTypes = make([]protoimpl.MessageInfo, 5) +var file_app_router_command_command_proto_msgTypes = make([]protoimpl.MessageInfo, 12) var file_app_router_command_command_proto_goTypes = []interface{}{ - (*RoutingContext)(nil), // 0: xray.app.router.command.RoutingContext - (*SubscribeRoutingStatsRequest)(nil), // 1: xray.app.router.command.SubscribeRoutingStatsRequest - (*TestRouteRequest)(nil), // 2: xray.app.router.command.TestRouteRequest - (*Config)(nil), // 3: xray.app.router.command.Config - nil, // 4: xray.app.router.command.RoutingContext.AttributesEntry - (net.Network)(0), // 5: xray.common.net.Network + (*RoutingContext)(nil), // 0: xray.app.router.command.RoutingContext + (*SubscribeRoutingStatsRequest)(nil), // 1: xray.app.router.command.SubscribeRoutingStatsRequest + (*TestRouteRequest)(nil), // 2: xray.app.router.command.TestRouteRequest + (*PrincipleTargetInfo)(nil), // 3: xray.app.router.command.PrincipleTargetInfo + (*OverrideInfo)(nil), // 4: xray.app.router.command.OverrideInfo + (*BalancerMsg)(nil), // 5: xray.app.router.command.BalancerMsg + (*GetBalancerInfoRequest)(nil), // 6: xray.app.router.command.GetBalancerInfoRequest + (*GetBalancerInfoResponse)(nil), // 7: xray.app.router.command.GetBalancerInfoResponse + (*OverrideBalancerTargetRequest)(nil), // 8: xray.app.router.command.OverrideBalancerTargetRequest + (*OverrideBalancerTargetResponse)(nil), // 9: xray.app.router.command.OverrideBalancerTargetResponse + (*Config)(nil), // 10: xray.app.router.command.Config + nil, // 11: xray.app.router.command.RoutingContext.AttributesEntry + (net.Network)(0), // 12: xray.common.net.Network } var file_app_router_command_command_proto_depIdxs = []int32{ - 5, // 0: xray.app.router.command.RoutingContext.Network:type_name -> xray.common.net.Network - 4, // 1: xray.app.router.command.RoutingContext.Attributes:type_name -> xray.app.router.command.RoutingContext.AttributesEntry - 0, // 2: xray.app.router.command.TestRouteRequest.RoutingContext:type_name -> xray.app.router.command.RoutingContext - 1, // 3: xray.app.router.command.RoutingService.SubscribeRoutingStats:input_type -> xray.app.router.command.SubscribeRoutingStatsRequest - 2, // 4: xray.app.router.command.RoutingService.TestRoute:input_type -> xray.app.router.command.TestRouteRequest - 0, // 5: xray.app.router.command.RoutingService.SubscribeRoutingStats:output_type -> xray.app.router.command.RoutingContext - 0, // 6: xray.app.router.command.RoutingService.TestRoute:output_type -> xray.app.router.command.RoutingContext - 5, // [5:7] is the sub-list for method output_type - 3, // [3:5] is the sub-list for method input_type - 3, // [3:3] is the sub-list for extension type_name - 3, // [3:3] is the sub-list for extension extendee - 0, // [0:3] is the sub-list for field type_name + 12, // 0: xray.app.router.command.RoutingContext.Network:type_name -> xray.common.net.Network + 11, // 1: xray.app.router.command.RoutingContext.Attributes:type_name -> xray.app.router.command.RoutingContext.AttributesEntry + 0, // 2: xray.app.router.command.TestRouteRequest.RoutingContext:type_name -> xray.app.router.command.RoutingContext + 4, // 3: xray.app.router.command.BalancerMsg.override:type_name -> xray.app.router.command.OverrideInfo + 3, // 4: xray.app.router.command.BalancerMsg.principle_target:type_name -> xray.app.router.command.PrincipleTargetInfo + 5, // 5: xray.app.router.command.GetBalancerInfoResponse.balancer:type_name -> xray.app.router.command.BalancerMsg + 1, // 6: xray.app.router.command.RoutingService.SubscribeRoutingStats:input_type -> xray.app.router.command.SubscribeRoutingStatsRequest + 2, // 7: xray.app.router.command.RoutingService.TestRoute:input_type -> xray.app.router.command.TestRouteRequest + 6, // 8: xray.app.router.command.RoutingService.GetBalancerInfo:input_type -> xray.app.router.command.GetBalancerInfoRequest + 8, // 9: xray.app.router.command.RoutingService.OverrideBalancerTarget:input_type -> xray.app.router.command.OverrideBalancerTargetRequest + 0, // 10: xray.app.router.command.RoutingService.SubscribeRoutingStats:output_type -> xray.app.router.command.RoutingContext + 0, // 11: xray.app.router.command.RoutingService.TestRoute:output_type -> xray.app.router.command.RoutingContext + 7, // 12: xray.app.router.command.RoutingService.GetBalancerInfo:output_type -> xray.app.router.command.GetBalancerInfoResponse + 9, // 13: xray.app.router.command.RoutingService.OverrideBalancerTarget:output_type -> xray.app.router.command.OverrideBalancerTargetResponse + 10, // [10:14] is the sub-list for method output_type + 6, // [6:10] is the sub-list for method input_type + 6, // [6:6] is the sub-list for extension type_name + 6, // [6:6] is the sub-list for extension extendee + 0, // [0:6] is the sub-list for field type_name } func init() { file_app_router_command_command_proto_init() } @@ -494,6 +893,90 @@ func file_app_router_command_command_proto_init() { } } file_app_router_command_command_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PrincipleTargetInfo); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_app_router_command_command_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*OverrideInfo); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_app_router_command_command_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*BalancerMsg); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_app_router_command_command_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetBalancerInfoRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_app_router_command_command_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetBalancerInfoResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_app_router_command_command_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*OverrideBalancerTargetRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_app_router_command_command_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*OverrideBalancerTargetResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_app_router_command_command_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Config); i { case 0: return &v.state @@ -512,7 +995,7 @@ func file_app_router_command_command_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_app_router_command_command_proto_rawDesc, NumEnums: 0, - NumMessages: 5, + NumMessages: 12, NumExtensions: 0, NumServices: 1, }, diff --git a/app/router/command/command.proto b/app/router/command/command.proto index 5f777044..ebf8610e 100644 --- a/app/router/command/command.proto +++ b/app/router/command/command.proto @@ -60,10 +60,41 @@ message TestRouteRequest { bool PublishResult = 3; } +message PrincipleTargetInfo { + repeated string tag = 1; +} + +message OverrideInfo { + string target = 2; +} + +message BalancerMsg { + OverrideInfo override = 5; + PrincipleTargetInfo principle_target = 6; +} + +message GetBalancerInfoRequest { + string tag = 1; +} + +message GetBalancerInfoResponse { + BalancerMsg balancer = 1; +} + +message OverrideBalancerTargetRequest { + string balancerTag = 1; + string target = 2; +} + +message OverrideBalancerTargetResponse {} + service RoutingService { rpc SubscribeRoutingStats(SubscribeRoutingStatsRequest) returns (stream RoutingContext) {} rpc TestRoute(TestRouteRequest) returns (RoutingContext) {} + + rpc GetBalancerInfo(GetBalancerInfoRequest) returns (GetBalancerInfoResponse){} + rpc OverrideBalancerTarget(OverrideBalancerTargetRequest) returns (OverrideBalancerTargetResponse) {} } message Config {} diff --git a/app/router/command/command_grpc.pb.go b/app/router/command/command_grpc.pb.go index 4d3b4ab4..5710ec2b 100644 --- a/app/router/command/command_grpc.pb.go +++ b/app/router/command/command_grpc.pb.go @@ -19,8 +19,10 @@ import ( const _ = grpc.SupportPackageIsVersion7 const ( - RoutingService_SubscribeRoutingStats_FullMethodName = "/xray.app.router.command.RoutingService/SubscribeRoutingStats" - RoutingService_TestRoute_FullMethodName = "/xray.app.router.command.RoutingService/TestRoute" + RoutingService_SubscribeRoutingStats_FullMethodName = "/xray.app.router.command.RoutingService/SubscribeRoutingStats" + RoutingService_TestRoute_FullMethodName = "/xray.app.router.command.RoutingService/TestRoute" + RoutingService_GetBalancerInfo_FullMethodName = "/xray.app.router.command.RoutingService/GetBalancerInfo" + RoutingService_OverrideBalancerTarget_FullMethodName = "/xray.app.router.command.RoutingService/OverrideBalancerTarget" ) // RoutingServiceClient is the client API for RoutingService service. @@ -29,6 +31,8 @@ const ( type RoutingServiceClient interface { SubscribeRoutingStats(ctx context.Context, in *SubscribeRoutingStatsRequest, opts ...grpc.CallOption) (RoutingService_SubscribeRoutingStatsClient, error) TestRoute(ctx context.Context, in *TestRouteRequest, opts ...grpc.CallOption) (*RoutingContext, error) + GetBalancerInfo(ctx context.Context, in *GetBalancerInfoRequest, opts ...grpc.CallOption) (*GetBalancerInfoResponse, error) + OverrideBalancerTarget(ctx context.Context, in *OverrideBalancerTargetRequest, opts ...grpc.CallOption) (*OverrideBalancerTargetResponse, error) } type routingServiceClient struct { @@ -80,12 +84,32 @@ func (c *routingServiceClient) TestRoute(ctx context.Context, in *TestRouteReque return out, nil } +func (c *routingServiceClient) GetBalancerInfo(ctx context.Context, in *GetBalancerInfoRequest, opts ...grpc.CallOption) (*GetBalancerInfoResponse, error) { + out := new(GetBalancerInfoResponse) + err := c.cc.Invoke(ctx, RoutingService_GetBalancerInfo_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *routingServiceClient) OverrideBalancerTarget(ctx context.Context, in *OverrideBalancerTargetRequest, opts ...grpc.CallOption) (*OverrideBalancerTargetResponse, error) { + out := new(OverrideBalancerTargetResponse) + err := c.cc.Invoke(ctx, RoutingService_OverrideBalancerTarget_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // RoutingServiceServer is the server API for RoutingService service. // All implementations must embed UnimplementedRoutingServiceServer // for forward compatibility type RoutingServiceServer interface { SubscribeRoutingStats(*SubscribeRoutingStatsRequest, RoutingService_SubscribeRoutingStatsServer) error TestRoute(context.Context, *TestRouteRequest) (*RoutingContext, error) + GetBalancerInfo(context.Context, *GetBalancerInfoRequest) (*GetBalancerInfoResponse, error) + OverrideBalancerTarget(context.Context, *OverrideBalancerTargetRequest) (*OverrideBalancerTargetResponse, error) mustEmbedUnimplementedRoutingServiceServer() } @@ -99,6 +123,12 @@ func (UnimplementedRoutingServiceServer) SubscribeRoutingStats(*SubscribeRouting func (UnimplementedRoutingServiceServer) TestRoute(context.Context, *TestRouteRequest) (*RoutingContext, error) { return nil, status.Errorf(codes.Unimplemented, "method TestRoute not implemented") } +func (UnimplementedRoutingServiceServer) GetBalancerInfo(context.Context, *GetBalancerInfoRequest) (*GetBalancerInfoResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetBalancerInfo not implemented") +} +func (UnimplementedRoutingServiceServer) OverrideBalancerTarget(context.Context, *OverrideBalancerTargetRequest) (*OverrideBalancerTargetResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method OverrideBalancerTarget not implemented") +} func (UnimplementedRoutingServiceServer) mustEmbedUnimplementedRoutingServiceServer() {} // UnsafeRoutingServiceServer may be embedded to opt out of forward compatibility for this service. @@ -151,6 +181,42 @@ func _RoutingService_TestRoute_Handler(srv interface{}, ctx context.Context, dec return interceptor(ctx, in, info, handler) } +func _RoutingService_GetBalancerInfo_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetBalancerInfoRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(RoutingServiceServer).GetBalancerInfo(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: RoutingService_GetBalancerInfo_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(RoutingServiceServer).GetBalancerInfo(ctx, req.(*GetBalancerInfoRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _RoutingService_OverrideBalancerTarget_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(OverrideBalancerTargetRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(RoutingServiceServer).OverrideBalancerTarget(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: RoutingService_OverrideBalancerTarget_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(RoutingServiceServer).OverrideBalancerTarget(ctx, req.(*OverrideBalancerTargetRequest)) + } + return interceptor(ctx, in, info, handler) +} + // RoutingService_ServiceDesc is the grpc.ServiceDesc for RoutingService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -162,6 +228,14 @@ var RoutingService_ServiceDesc = grpc.ServiceDesc{ MethodName: "TestRoute", Handler: _RoutingService_TestRoute_Handler, }, + { + MethodName: "GetBalancerInfo", + Handler: _RoutingService_GetBalancerInfo_Handler, + }, + { + MethodName: "OverrideBalancerTarget", + Handler: _RoutingService_OverrideBalancerTarget_Handler, + }, }, Streams: []grpc.StreamDesc{ { diff --git a/app/router/command/command_test.go b/app/router/command/command_test.go index b9d252a6..fa8d48b3 100644 --- a/app/router/command/command_test.go +++ b/app/router/command/command_test.go @@ -318,7 +318,7 @@ func TestSerivceTestRoute(t *testing.T) { TargetTag: &router.RoutingRule_Tag{Tag: "out"}, }, }, - }, mocks.NewDNSClient(mockCtl), mocks.NewOutboundManager(mockCtl))) + }, mocks.NewDNSClient(mockCtl), mocks.NewOutboundManager(mockCtl), nil)) lis := bufconn.Listen(1024 * 1024) bufDialer := func(context.Context, string) (net.Conn, error) { diff --git a/app/router/config.go b/app/router/config.go index 5dc32fa8..28ba7e31 100644 --- a/app/router/config.go +++ b/app/router/config.go @@ -121,28 +121,45 @@ func (rr *RoutingRule) BuildCondition() (Condition, error) { return conds, nil } -func (br *BalancingRule) Build(ohm outbound.Manager) (*Balancer, error) { - switch br.Strategy { - case "leastPing": +// Build builds the balancing rule +func (br *BalancingRule) Build(ohm outbound.Manager, dispatcher routing.Dispatcher) (*Balancer, error) { + switch strings.ToLower(br.Strategy) { + case "leastping": return &Balancer{ selectors: br.OutboundSelector, strategy: &LeastPingStrategy{}, ohm: ohm, }, nil - case "roundRobin": + case "roundrobin": return &Balancer{ selectors: br.OutboundSelector, strategy: &RoundRobinStrategy{}, ohm: ohm, }, nil - case "random": - fallthrough - default: + case "leastload": + i, err := br.StrategySettings.GetInstance() + if err != nil { + return nil, err + } + s, ok := i.(*StrategyLeastLoadConfig) + if !ok { + return nil, newError("not a StrategyLeastLoadConfig").AtError() + } + leastLoadStrategy := NewLeastLoadStrategy(s) return &Balancer{ selectors: br.OutboundSelector, - strategy: &RandomStrategy{}, - ohm: ohm, + ohm: ohm, fallbackTag: br.FallbackTag, + strategy: leastLoadStrategy, }, nil - + case "random": + fallthrough + case "": + return &Balancer{ + selectors: br.OutboundSelector, + ohm: ohm, fallbackTag: br.FallbackTag, + strategy: &RandomStrategy{}, + }, nil + default: + return nil, newError("unrecognized balancer type") } } diff --git a/app/router/config.pb.go b/app/router/config.pb.go index cfe5a8fc..59a86a2e 100644 --- a/app/router/config.pb.go +++ b/app/router/config.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.31.0 +// protoc-gen-go v1.32.0 // protoc v4.23.1 // source: app/router/config.proto @@ -8,6 +8,7 @@ package router import ( net "github.com/xtls/xray-core/common/net" + serial "github.com/xtls/xray-core/common/serial" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" @@ -131,7 +132,7 @@ func (x Config_DomainStrategy) Number() protoreflect.EnumNumber { // Deprecated: Use Config_DomainStrategy.Descriptor instead. func (Config_DomainStrategy) EnumDescriptor() ([]byte, []int) { - return file_app_router_config_proto_rawDescGZIP(), []int{8, 0} + return file_app_router_config_proto_rawDescGZIP(), []int{10, 0} } // Domain for routing decision. @@ -707,9 +708,11 @@ type BalancingRule struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Tag string `protobuf:"bytes,1,opt,name=tag,proto3" json:"tag,omitempty"` - OutboundSelector []string `protobuf:"bytes,2,rep,name=outbound_selector,json=outboundSelector,proto3" json:"outbound_selector,omitempty"` - Strategy string `protobuf:"bytes,3,opt,name=strategy,proto3" json:"strategy,omitempty"` + Tag string `protobuf:"bytes,1,opt,name=tag,proto3" json:"tag,omitempty"` + OutboundSelector []string `protobuf:"bytes,2,rep,name=outbound_selector,json=outboundSelector,proto3" json:"outbound_selector,omitempty"` + Strategy string `protobuf:"bytes,3,opt,name=strategy,proto3" json:"strategy,omitempty"` + StrategySettings *serial.TypedMessage `protobuf:"bytes,4,opt,name=strategy_settings,json=strategySettings,proto3" json:"strategy_settings,omitempty"` + FallbackTag string `protobuf:"bytes,5,opt,name=fallback_tag,json=fallbackTag,proto3" json:"fallback_tag,omitempty"` } func (x *BalancingRule) Reset() { @@ -765,6 +768,167 @@ func (x *BalancingRule) GetStrategy() string { return "" } +func (x *BalancingRule) GetStrategySettings() *serial.TypedMessage { + if x != nil { + return x.StrategySettings + } + return nil +} + +func (x *BalancingRule) GetFallbackTag() string { + if x != nil { + return x.FallbackTag + } + return "" +} + +type StrategyWeight struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Regexp bool `protobuf:"varint,1,opt,name=regexp,proto3" json:"regexp,omitempty"` + Match string `protobuf:"bytes,2,opt,name=match,proto3" json:"match,omitempty"` + Value float32 `protobuf:"fixed32,3,opt,name=value,proto3" json:"value,omitempty"` +} + +func (x *StrategyWeight) Reset() { + *x = StrategyWeight{} + if protoimpl.UnsafeEnabled { + mi := &file_app_router_config_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *StrategyWeight) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StrategyWeight) ProtoMessage() {} + +func (x *StrategyWeight) ProtoReflect() protoreflect.Message { + mi := &file_app_router_config_proto_msgTypes[8] + 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 StrategyWeight.ProtoReflect.Descriptor instead. +func (*StrategyWeight) Descriptor() ([]byte, []int) { + return file_app_router_config_proto_rawDescGZIP(), []int{8} +} + +func (x *StrategyWeight) GetRegexp() bool { + if x != nil { + return x.Regexp + } + return false +} + +func (x *StrategyWeight) GetMatch() string { + if x != nil { + return x.Match + } + return "" +} + +func (x *StrategyWeight) GetValue() float32 { + if x != nil { + return x.Value + } + return 0 +} + +type StrategyLeastLoadConfig struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // weight settings + Costs []*StrategyWeight `protobuf:"bytes,2,rep,name=costs,proto3" json:"costs,omitempty"` + // RTT baselines for selecting, int64 values of time.Duration + Baselines []int64 `protobuf:"varint,3,rep,packed,name=baselines,proto3" json:"baselines,omitempty"` + // expected nodes count to select + Expected int32 `protobuf:"varint,4,opt,name=expected,proto3" json:"expected,omitempty"` + // max acceptable rtt, filter away high delay nodes. defalut 0 + MaxRTT int64 `protobuf:"varint,5,opt,name=maxRTT,proto3" json:"maxRTT,omitempty"` + // acceptable failure rate + Tolerance float32 `protobuf:"fixed32,6,opt,name=tolerance,proto3" json:"tolerance,omitempty"` +} + +func (x *StrategyLeastLoadConfig) Reset() { + *x = StrategyLeastLoadConfig{} + if protoimpl.UnsafeEnabled { + mi := &file_app_router_config_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *StrategyLeastLoadConfig) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StrategyLeastLoadConfig) ProtoMessage() {} + +func (x *StrategyLeastLoadConfig) ProtoReflect() protoreflect.Message { + mi := &file_app_router_config_proto_msgTypes[9] + 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 StrategyLeastLoadConfig.ProtoReflect.Descriptor instead. +func (*StrategyLeastLoadConfig) Descriptor() ([]byte, []int) { + return file_app_router_config_proto_rawDescGZIP(), []int{9} +} + +func (x *StrategyLeastLoadConfig) GetCosts() []*StrategyWeight { + if x != nil { + return x.Costs + } + return nil +} + +func (x *StrategyLeastLoadConfig) GetBaselines() []int64 { + if x != nil { + return x.Baselines + } + return nil +} + +func (x *StrategyLeastLoadConfig) GetExpected() int32 { + if x != nil { + return x.Expected + } + return 0 +} + +func (x *StrategyLeastLoadConfig) GetMaxRTT() int64 { + if x != nil { + return x.MaxRTT + } + return 0 +} + +func (x *StrategyLeastLoadConfig) GetTolerance() float32 { + if x != nil { + return x.Tolerance + } + return 0 +} + type Config struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -778,7 +942,7 @@ type Config struct { func (x *Config) Reset() { *x = Config{} if protoimpl.UnsafeEnabled { - mi := &file_app_router_config_proto_msgTypes[8] + mi := &file_app_router_config_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -791,7 +955,7 @@ func (x *Config) String() string { func (*Config) ProtoMessage() {} func (x *Config) ProtoReflect() protoreflect.Message { - mi := &file_app_router_config_proto_msgTypes[8] + mi := &file_app_router_config_proto_msgTypes[10] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -804,7 +968,7 @@ func (x *Config) ProtoReflect() protoreflect.Message { // Deprecated: Use Config.ProtoReflect.Descriptor instead. func (*Config) Descriptor() ([]byte, []int) { - return file_app_router_config_proto_rawDescGZIP(), []int{8} + return file_app_router_config_proto_rawDescGZIP(), []int{10} } func (x *Config) GetDomainStrategy() Config_DomainStrategy { @@ -844,7 +1008,7 @@ type Domain_Attribute struct { func (x *Domain_Attribute) Reset() { *x = Domain_Attribute{} if protoimpl.UnsafeEnabled { - mi := &file_app_router_config_proto_msgTypes[9] + mi := &file_app_router_config_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -857,7 +1021,7 @@ func (x *Domain_Attribute) String() string { func (*Domain_Attribute) ProtoMessage() {} func (x *Domain_Attribute) ProtoReflect() protoreflect.Message { - mi := &file_app_router_config_proto_msgTypes[9] + mi := &file_app_router_config_proto_msgTypes[11] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -922,142 +1086,169 @@ var File_app_router_config_proto protoreflect.FileDescriptor var file_app_router_config_proto_rawDesc = []byte{ 0x0a, 0x17, 0x61, 0x70, 0x70, 0x2f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0f, 0x78, 0x72, 0x61, 0x79, 0x2e, - 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x1a, 0x15, 0x63, 0x6f, 0x6d, 0x6d, - 0x6f, 0x6e, 0x2f, 0x6e, 0x65, 0x74, 0x2f, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x1a, 0x18, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x6e, 0x65, 0x74, 0x2f, 0x6e, 0x65, - 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xb3, 0x02, 0x0a, 0x06, - 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x30, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1c, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, - 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x2e, 0x54, 0x79, - 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, - 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x3f, - 0x0a, 0x09, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x18, 0x03, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x21, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, - 0x74, 0x65, 0x72, 0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x2e, 0x41, 0x74, 0x74, 0x72, 0x69, - 0x62, 0x75, 0x74, 0x65, 0x52, 0x09, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x1a, - 0x6c, 0x0a, 0x09, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x12, 0x10, 0x0a, 0x03, - 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x1f, - 0x0a, 0x0a, 0x62, 0x6f, 0x6f, 0x6c, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x08, 0x48, 0x00, 0x52, 0x09, 0x62, 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, - 0x1d, 0x0a, 0x09, 0x69, 0x6e, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x03, 0x48, 0x00, 0x52, 0x08, 0x69, 0x6e, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x0d, - 0x0a, 0x0b, 0x74, 0x79, 0x70, 0x65, 0x64, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x32, 0x0a, - 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x09, 0x0a, 0x05, 0x50, 0x6c, 0x61, 0x69, 0x6e, 0x10, 0x00, - 0x12, 0x09, 0x0a, 0x05, 0x52, 0x65, 0x67, 0x65, 0x78, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x44, - 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x10, 0x02, 0x12, 0x08, 0x0a, 0x04, 0x46, 0x75, 0x6c, 0x6c, 0x10, - 0x03, 0x22, 0x2e, 0x0a, 0x04, 0x43, 0x49, 0x44, 0x52, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x70, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x70, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x72, 0x65, - 0x66, 0x69, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x70, 0x72, 0x65, 0x66, 0x69, - 0x78, 0x22, 0x7a, 0x0a, 0x05, 0x47, 0x65, 0x6f, 0x49, 0x50, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, - 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0b, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x29, 0x0a, - 0x04, 0x63, 0x69, 0x64, 0x72, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x78, 0x72, - 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x43, 0x49, - 0x44, 0x52, 0x52, 0x04, 0x63, 0x69, 0x64, 0x72, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x76, 0x65, - 0x72, 0x73, 0x65, 0x5f, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x0c, 0x72, 0x65, 0x76, 0x65, 0x72, 0x73, 0x65, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x22, 0x39, 0x0a, - 0x09, 0x47, 0x65, 0x6f, 0x49, 0x50, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x2c, 0x0a, 0x05, 0x65, 0x6e, - 0x74, 0x72, 0x79, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x78, 0x72, 0x61, 0x79, - 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x47, 0x65, 0x6f, 0x49, - 0x50, 0x52, 0x05, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x22, 0x5d, 0x0a, 0x07, 0x47, 0x65, 0x6f, 0x53, - 0x69, 0x74, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x63, - 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x75, 0x6e, 0x74, - 0x72, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x2f, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, - 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, - 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, - 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x22, 0x3d, 0x0a, 0x0b, 0x47, 0x65, 0x6f, 0x53, 0x69, - 0x74, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x05, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x18, - 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, - 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x47, 0x65, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x52, - 0x05, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x22, 0xa2, 0x07, 0x0a, 0x0b, 0x52, 0x6f, 0x75, 0x74, 0x69, - 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x12, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x03, 0x74, 0x61, 0x67, 0x12, 0x25, 0x0a, 0x0d, 0x62, 0x61, - 0x6c, 0x61, 0x6e, 0x63, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x61, 0x67, 0x18, 0x0c, 0x20, 0x01, 0x28, - 0x09, 0x48, 0x00, 0x52, 0x0c, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x69, 0x6e, 0x67, 0x54, 0x61, - 0x67, 0x12, 0x2f, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x02, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x17, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, - 0x74, 0x65, 0x72, 0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, - 0x69, 0x6e, 0x12, 0x2d, 0x0a, 0x04, 0x63, 0x69, 0x64, 0x72, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x15, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, - 0x65, 0x72, 0x2e, 0x43, 0x49, 0x44, 0x52, 0x42, 0x02, 0x18, 0x01, 0x52, 0x04, 0x63, 0x69, 0x64, - 0x72, 0x12, 0x2c, 0x0a, 0x05, 0x67, 0x65, 0x6f, 0x69, 0x70, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x16, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, - 0x65, 0x72, 0x2e, 0x47, 0x65, 0x6f, 0x49, 0x50, 0x52, 0x05, 0x67, 0x65, 0x6f, 0x69, 0x70, 0x12, - 0x3d, 0x0a, 0x0a, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, - 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x50, 0x6f, 0x72, 0x74, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x42, - 0x02, 0x18, 0x01, 0x52, 0x09, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x36, - 0x0a, 0x09, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x18, 0x0e, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x19, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, - 0x6e, 0x65, 0x74, 0x2e, 0x50, 0x6f, 0x72, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x08, 0x70, 0x6f, - 0x72, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x43, 0x0a, 0x0c, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, - 0x6b, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x78, - 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x4e, - 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x4c, 0x69, 0x73, 0x74, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0b, - 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x34, 0x0a, 0x08, 0x6e, - 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x18, 0x0d, 0x20, 0x03, 0x28, 0x0e, 0x32, 0x18, 0x2e, - 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, - 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x52, 0x08, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, - 0x73, 0x12, 0x3a, 0x0a, 0x0b, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x63, 0x69, 0x64, 0x72, - 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, - 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x43, 0x49, 0x44, 0x52, 0x42, 0x02, 0x18, - 0x01, 0x52, 0x0a, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x43, 0x69, 0x64, 0x72, 0x12, 0x39, 0x0a, - 0x0c, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x67, 0x65, 0x6f, 0x69, 0x70, 0x18, 0x0b, 0x20, + 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x1a, 0x21, 0x63, 0x6f, 0x6d, 0x6d, + 0x6f, 0x6e, 0x2f, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x64, 0x5f, + 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x15, 0x63, + 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x6e, 0x65, 0x74, 0x2f, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x18, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x6e, 0x65, 0x74, + 0x2f, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xb3, + 0x02, 0x0a, 0x06, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x30, 0x0a, 0x04, 0x74, 0x79, 0x70, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1c, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, + 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, + 0x2e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x12, 0x3f, 0x0a, 0x09, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x18, 0x03, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, + 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x2e, 0x41, 0x74, + 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x52, 0x09, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, + 0x74, 0x65, 0x1a, 0x6c, 0x0a, 0x09, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x12, + 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, + 0x79, 0x12, 0x1f, 0x0a, 0x0a, 0x62, 0x6f, 0x6f, 0x6c, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, 0x09, 0x62, 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, + 0x75, 0x65, 0x12, 0x1d, 0x0a, 0x09, 0x69, 0x6e, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x03, 0x48, 0x00, 0x52, 0x08, 0x69, 0x6e, 0x74, 0x56, 0x61, 0x6c, 0x75, + 0x65, 0x42, 0x0d, 0x0a, 0x0b, 0x74, 0x79, 0x70, 0x65, 0x64, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x22, 0x32, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x09, 0x0a, 0x05, 0x50, 0x6c, 0x61, 0x69, + 0x6e, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x52, 0x65, 0x67, 0x65, 0x78, 0x10, 0x01, 0x12, 0x0a, + 0x0a, 0x06, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x10, 0x02, 0x12, 0x08, 0x0a, 0x04, 0x46, 0x75, + 0x6c, 0x6c, 0x10, 0x03, 0x22, 0x2e, 0x0a, 0x04, 0x43, 0x49, 0x44, 0x52, 0x12, 0x0e, 0x0a, 0x02, + 0x69, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x70, 0x12, 0x16, 0x0a, 0x06, + 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x70, 0x72, + 0x65, 0x66, 0x69, 0x78, 0x22, 0x7a, 0x0a, 0x05, 0x47, 0x65, 0x6f, 0x49, 0x50, 0x12, 0x21, 0x0a, + 0x0c, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x43, 0x6f, 0x64, 0x65, + 0x12, 0x29, 0x0a, 0x04, 0x63, 0x69, 0x64, 0x72, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, + 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, + 0x2e, 0x43, 0x49, 0x44, 0x52, 0x52, 0x04, 0x63, 0x69, 0x64, 0x72, 0x12, 0x23, 0x0a, 0x0d, 0x72, + 0x65, 0x76, 0x65, 0x72, 0x73, 0x65, 0x5f, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x0c, 0x72, 0x65, 0x76, 0x65, 0x72, 0x73, 0x65, 0x4d, 0x61, 0x74, 0x63, 0x68, + 0x22, 0x39, 0x0a, 0x09, 0x47, 0x65, 0x6f, 0x49, 0x50, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x2c, 0x0a, + 0x05, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x78, + 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x47, + 0x65, 0x6f, 0x49, 0x50, 0x52, 0x05, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x22, 0x5d, 0x0a, 0x07, 0x47, + 0x65, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, + 0x79, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, + 0x75, 0x6e, 0x74, 0x72, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x2f, 0x0a, 0x06, 0x64, 0x6f, 0x6d, + 0x61, 0x69, 0x6e, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x78, 0x72, 0x61, 0x79, + 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x44, 0x6f, 0x6d, 0x61, + 0x69, 0x6e, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x22, 0x3d, 0x0a, 0x0b, 0x47, 0x65, + 0x6f, 0x53, 0x69, 0x74, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x05, 0x65, 0x6e, 0x74, + 0x72, 0x79, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, + 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x47, 0x65, 0x6f, 0x53, 0x69, + 0x74, 0x65, 0x52, 0x05, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x22, 0xa2, 0x07, 0x0a, 0x0b, 0x52, 0x6f, + 0x75, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x12, 0x0a, 0x03, 0x74, 0x61, 0x67, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x03, 0x74, 0x61, 0x67, 0x12, 0x25, 0x0a, + 0x0d, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x61, 0x67, 0x18, 0x0c, + 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0c, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x69, 0x6e, + 0x67, 0x54, 0x61, 0x67, 0x12, 0x2f, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x02, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, + 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x06, 0x64, + 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x2d, 0x0a, 0x04, 0x63, 0x69, 0x64, 0x72, 0x18, 0x03, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, + 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x43, 0x49, 0x44, 0x52, 0x42, 0x02, 0x18, 0x01, 0x52, 0x04, + 0x63, 0x69, 0x64, 0x72, 0x12, 0x2c, 0x0a, 0x05, 0x67, 0x65, 0x6f, 0x69, 0x70, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, - 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x47, 0x65, 0x6f, 0x49, 0x50, 0x52, 0x0b, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x47, 0x65, 0x6f, 0x69, 0x70, 0x12, 0x43, 0x0a, 0x10, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x18, 0x10, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, - 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x50, 0x6f, 0x72, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x0e, 0x73, - 0x6f, 0x75, 0x72, 0x63, 0x65, 0x50, 0x6f, 0x72, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x1d, 0x0a, - 0x0a, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x07, 0x20, 0x03, 0x28, - 0x09, 0x52, 0x09, 0x75, 0x73, 0x65, 0x72, 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x1f, 0x0a, 0x0b, - 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x74, 0x61, 0x67, 0x18, 0x08, 0x20, 0x03, 0x28, - 0x09, 0x52, 0x0a, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x54, 0x61, 0x67, 0x12, 0x1a, 0x0a, - 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x09, 0x20, 0x03, 0x28, 0x09, 0x52, - 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x4c, 0x0a, 0x0a, 0x61, 0x74, 0x74, - 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x18, 0x0f, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2c, 0x2e, - 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, - 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x2e, 0x41, 0x74, 0x74, 0x72, - 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0a, 0x61, 0x74, 0x74, - 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x64, 0x6f, 0x6d, 0x61, 0x69, - 0x6e, 0x5f, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x18, 0x11, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0d, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x1a, 0x3d, - 0x0a, 0x0f, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, - 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, - 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x0c, 0x0a, - 0x0a, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x74, 0x61, 0x67, 0x22, 0x6a, 0x0a, 0x0d, 0x42, - 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x10, 0x0a, 0x03, - 0x74, 0x61, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x74, 0x61, 0x67, 0x12, 0x2b, - 0x0a, 0x11, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x73, 0x65, 0x6c, 0x65, 0x63, - 0x74, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x10, 0x6f, 0x75, 0x74, 0x62, 0x6f, - 0x75, 0x6e, 0x64, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x1a, 0x0a, 0x08, 0x73, - 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, - 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x22, 0x9b, 0x02, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, - 0x69, 0x67, 0x12, 0x4f, 0x0a, 0x0f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x73, 0x74, 0x72, - 0x61, 0x74, 0x65, 0x67, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x26, 0x2e, 0x78, 0x72, - 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x43, 0x6f, - 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, - 0x65, 0x67, 0x79, 0x52, 0x0e, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, - 0x65, 0x67, 0x79, 0x12, 0x30, 0x0a, 0x04, 0x72, 0x75, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x1c, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, - 0x74, 0x65, 0x72, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x52, - 0x04, 0x72, 0x75, 0x6c, 0x65, 0x12, 0x45, 0x0a, 0x0e, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x69, - 0x6e, 0x67, 0x5f, 0x72, 0x75, 0x6c, 0x65, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, - 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, - 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x0d, 0x62, - 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x22, 0x47, 0x0a, 0x0e, - 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x08, - 0x0a, 0x04, 0x41, 0x73, 0x49, 0x73, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x55, 0x73, 0x65, 0x49, - 0x70, 0x10, 0x01, 0x12, 0x10, 0x0a, 0x0c, 0x49, 0x70, 0x49, 0x66, 0x4e, 0x6f, 0x6e, 0x4d, 0x61, - 0x74, 0x63, 0x68, 0x10, 0x02, 0x12, 0x0e, 0x0a, 0x0a, 0x49, 0x70, 0x4f, 0x6e, 0x44, 0x65, 0x6d, - 0x61, 0x6e, 0x64, 0x10, 0x03, 0x42, 0x4f, 0x0a, 0x13, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, - 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x50, 0x01, 0x5a, 0x24, - 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, 0x72, 0x6f, - 0x75, 0x74, 0x65, 0x72, 0xaa, 0x02, 0x0f, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x41, 0x70, 0x70, 0x2e, - 0x52, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x47, 0x65, 0x6f, 0x49, 0x50, 0x52, 0x05, 0x67, 0x65, 0x6f, + 0x69, 0x70, 0x12, 0x3d, 0x0a, 0x0a, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x72, 0x61, 0x6e, 0x67, 0x65, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, + 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x50, 0x6f, 0x72, 0x74, 0x52, 0x61, 0x6e, + 0x67, 0x65, 0x42, 0x02, 0x18, 0x01, 0x52, 0x09, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x61, 0x6e, 0x67, + 0x65, 0x12, 0x36, 0x0a, 0x09, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x18, 0x0e, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, + 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x50, 0x6f, 0x72, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x52, + 0x08, 0x70, 0x6f, 0x72, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x43, 0x0a, 0x0c, 0x6e, 0x65, 0x74, + 0x77, 0x6f, 0x72, 0x6b, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x1c, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, + 0x74, 0x2e, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x4c, 0x69, 0x73, 0x74, 0x42, 0x02, 0x18, + 0x01, 0x52, 0x0b, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x34, + 0x0a, 0x08, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x18, 0x0d, 0x20, 0x03, 0x28, 0x0e, + 0x32, 0x18, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, + 0x65, 0x74, 0x2e, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x52, 0x08, 0x6e, 0x65, 0x74, 0x77, + 0x6f, 0x72, 0x6b, 0x73, 0x12, 0x3a, 0x0a, 0x0b, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x63, + 0x69, 0x64, 0x72, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x78, 0x72, 0x61, 0x79, + 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x43, 0x49, 0x44, 0x52, + 0x42, 0x02, 0x18, 0x01, 0x52, 0x0a, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x43, 0x69, 0x64, 0x72, + 0x12, 0x39, 0x0a, 0x0c, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x67, 0x65, 0x6f, 0x69, 0x70, + 0x18, 0x0b, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, + 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x47, 0x65, 0x6f, 0x49, 0x50, 0x52, 0x0b, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, 0x65, 0x6f, 0x69, 0x70, 0x12, 0x43, 0x0a, 0x10, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x18, + 0x10, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, + 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x50, 0x6f, 0x72, 0x74, 0x4c, 0x69, 0x73, 0x74, + 0x52, 0x0e, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x50, 0x6f, 0x72, 0x74, 0x4c, 0x69, 0x73, 0x74, + 0x12, 0x1d, 0x0a, 0x0a, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x07, + 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x75, 0x73, 0x65, 0x72, 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x12, + 0x1f, 0x0a, 0x0b, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x74, 0x61, 0x67, 0x18, 0x08, + 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x54, 0x61, 0x67, + 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x09, 0x20, 0x03, + 0x28, 0x09, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x4c, 0x0a, 0x0a, + 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x18, 0x0f, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x2c, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, + 0x65, 0x72, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x2e, 0x41, + 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0a, + 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x64, 0x6f, + 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x18, 0x11, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0d, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x65, + 0x72, 0x1a, 0x3d, 0x0a, 0x0f, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x45, + 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, + 0x42, 0x0c, 0x0a, 0x0a, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x74, 0x61, 0x67, 0x22, 0xdc, + 0x01, 0x0a, 0x0d, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, + 0x12, 0x10, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x74, + 0x61, 0x67, 0x12, 0x2b, 0x0a, 0x11, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x73, + 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x10, 0x6f, + 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, + 0x1a, 0x0a, 0x08, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x08, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x4d, 0x0a, 0x11, 0x73, + 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, + 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x2e, 0x54, 0x79, 0x70, 0x65, + 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x10, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, + 0x67, 0x79, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x66, 0x61, + 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x5f, 0x74, 0x61, 0x67, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0b, 0x66, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x54, 0x61, 0x67, 0x22, 0x54, 0x0a, + 0x0e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x57, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, + 0x16, 0x0a, 0x06, 0x72, 0x65, 0x67, 0x65, 0x78, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x06, 0x72, 0x65, 0x67, 0x65, 0x78, 0x70, 0x12, 0x14, 0x0a, 0x05, 0x6d, 0x61, 0x74, 0x63, 0x68, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x12, 0x14, 0x0a, + 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x02, 0x52, 0x05, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x22, 0xc0, 0x01, 0x0a, 0x17, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, + 0x4c, 0x65, 0x61, 0x73, 0x74, 0x4c, 0x6f, 0x61, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, + 0x35, 0x0a, 0x05, 0x63, 0x6f, 0x73, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, + 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, + 0x2e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x57, 0x65, 0x69, 0x67, 0x68, 0x74, 0x52, + 0x05, 0x63, 0x6f, 0x73, 0x74, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x62, 0x61, 0x73, 0x65, 0x6c, 0x69, + 0x6e, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x03, 0x52, 0x09, 0x62, 0x61, 0x73, 0x65, 0x6c, + 0x69, 0x6e, 0x65, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x70, 0x65, 0x63, 0x74, 0x65, 0x64, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x65, 0x78, 0x70, 0x65, 0x63, 0x74, 0x65, 0x64, + 0x12, 0x16, 0x0a, 0x06, 0x6d, 0x61, 0x78, 0x52, 0x54, 0x54, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x06, 0x6d, 0x61, 0x78, 0x52, 0x54, 0x54, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x6f, 0x6c, 0x65, + 0x72, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x02, 0x52, 0x09, 0x74, 0x6f, 0x6c, + 0x65, 0x72, 0x61, 0x6e, 0x63, 0x65, 0x22, 0x9b, 0x02, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x12, 0x4f, 0x0a, 0x0f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x73, 0x74, 0x72, 0x61, + 0x74, 0x65, 0x67, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x26, 0x2e, 0x78, 0x72, 0x61, + 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x43, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, + 0x67, 0x79, 0x52, 0x0e, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, + 0x67, 0x79, 0x12, 0x30, 0x0a, 0x04, 0x72, 0x75, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x1c, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, + 0x65, 0x72, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x04, + 0x72, 0x75, 0x6c, 0x65, 0x12, 0x45, 0x0a, 0x0e, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x69, 0x6e, + 0x67, 0x5f, 0x72, 0x75, 0x6c, 0x65, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x78, + 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x42, + 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x0d, 0x62, 0x61, + 0x6c, 0x61, 0x6e, 0x63, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x22, 0x47, 0x0a, 0x0e, 0x44, + 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x08, 0x0a, + 0x04, 0x41, 0x73, 0x49, 0x73, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x55, 0x73, 0x65, 0x49, 0x70, + 0x10, 0x01, 0x12, 0x10, 0x0a, 0x0c, 0x49, 0x70, 0x49, 0x66, 0x4e, 0x6f, 0x6e, 0x4d, 0x61, 0x74, + 0x63, 0x68, 0x10, 0x02, 0x12, 0x0e, 0x0a, 0x0a, 0x49, 0x70, 0x4f, 0x6e, 0x44, 0x65, 0x6d, 0x61, + 0x6e, 0x64, 0x10, 0x03, 0x42, 0x4f, 0x0a, 0x13, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, + 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x50, 0x01, 0x5a, 0x24, 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, 0x72, 0x6f, 0x75, + 0x74, 0x65, 0x72, 0xaa, 0x02, 0x0f, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x41, 0x70, 0x70, 0x2e, 0x52, + 0x6f, 0x75, 0x74, 0x65, 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -1073,29 +1264,32 @@ func file_app_router_config_proto_rawDescGZIP() []byte { } var file_app_router_config_proto_enumTypes = make([]protoimpl.EnumInfo, 2) -var file_app_router_config_proto_msgTypes = make([]protoimpl.MessageInfo, 11) +var file_app_router_config_proto_msgTypes = make([]protoimpl.MessageInfo, 13) var file_app_router_config_proto_goTypes = []interface{}{ - (Domain_Type)(0), // 0: xray.app.router.Domain.Type - (Config_DomainStrategy)(0), // 1: xray.app.router.Config.DomainStrategy - (*Domain)(nil), // 2: xray.app.router.Domain - (*CIDR)(nil), // 3: xray.app.router.CIDR - (*GeoIP)(nil), // 4: xray.app.router.GeoIP - (*GeoIPList)(nil), // 5: xray.app.router.GeoIPList - (*GeoSite)(nil), // 6: xray.app.router.GeoSite - (*GeoSiteList)(nil), // 7: xray.app.router.GeoSiteList - (*RoutingRule)(nil), // 8: xray.app.router.RoutingRule - (*BalancingRule)(nil), // 9: xray.app.router.BalancingRule - (*Config)(nil), // 10: xray.app.router.Config - (*Domain_Attribute)(nil), // 11: xray.app.router.Domain.Attribute - nil, // 12: xray.app.router.RoutingRule.AttributesEntry - (*net.PortRange)(nil), // 13: xray.common.net.PortRange - (*net.PortList)(nil), // 14: xray.common.net.PortList - (*net.NetworkList)(nil), // 15: xray.common.net.NetworkList - (net.Network)(0), // 16: xray.common.net.Network + (Domain_Type)(0), // 0: xray.app.router.Domain.Type + (Config_DomainStrategy)(0), // 1: xray.app.router.Config.DomainStrategy + (*Domain)(nil), // 2: xray.app.router.Domain + (*CIDR)(nil), // 3: xray.app.router.CIDR + (*GeoIP)(nil), // 4: xray.app.router.GeoIP + (*GeoIPList)(nil), // 5: xray.app.router.GeoIPList + (*GeoSite)(nil), // 6: xray.app.router.GeoSite + (*GeoSiteList)(nil), // 7: xray.app.router.GeoSiteList + (*RoutingRule)(nil), // 8: xray.app.router.RoutingRule + (*BalancingRule)(nil), // 9: xray.app.router.BalancingRule + (*StrategyWeight)(nil), // 10: xray.app.router.StrategyWeight + (*StrategyLeastLoadConfig)(nil), // 11: xray.app.router.StrategyLeastLoadConfig + (*Config)(nil), // 12: xray.app.router.Config + (*Domain_Attribute)(nil), // 13: xray.app.router.Domain.Attribute + nil, // 14: xray.app.router.RoutingRule.AttributesEntry + (*net.PortRange)(nil), // 15: xray.common.net.PortRange + (*net.PortList)(nil), // 16: xray.common.net.PortList + (*net.NetworkList)(nil), // 17: xray.common.net.NetworkList + (net.Network)(0), // 18: xray.common.net.Network + (*serial.TypedMessage)(nil), // 19: xray.common.serial.TypedMessage } var file_app_router_config_proto_depIdxs = []int32{ 0, // 0: xray.app.router.Domain.type:type_name -> xray.app.router.Domain.Type - 11, // 1: xray.app.router.Domain.attribute:type_name -> xray.app.router.Domain.Attribute + 13, // 1: xray.app.router.Domain.attribute:type_name -> xray.app.router.Domain.Attribute 3, // 2: xray.app.router.GeoIP.cidr:type_name -> xray.app.router.CIDR 4, // 3: xray.app.router.GeoIPList.entry:type_name -> xray.app.router.GeoIP 2, // 4: xray.app.router.GeoSite.domain:type_name -> xray.app.router.Domain @@ -1103,22 +1297,24 @@ var file_app_router_config_proto_depIdxs = []int32{ 2, // 6: xray.app.router.RoutingRule.domain:type_name -> xray.app.router.Domain 3, // 7: xray.app.router.RoutingRule.cidr:type_name -> xray.app.router.CIDR 4, // 8: xray.app.router.RoutingRule.geoip:type_name -> xray.app.router.GeoIP - 13, // 9: xray.app.router.RoutingRule.port_range:type_name -> xray.common.net.PortRange - 14, // 10: xray.app.router.RoutingRule.port_list:type_name -> xray.common.net.PortList - 15, // 11: xray.app.router.RoutingRule.network_list:type_name -> xray.common.net.NetworkList - 16, // 12: xray.app.router.RoutingRule.networks:type_name -> xray.common.net.Network + 15, // 9: xray.app.router.RoutingRule.port_range:type_name -> xray.common.net.PortRange + 16, // 10: xray.app.router.RoutingRule.port_list:type_name -> xray.common.net.PortList + 17, // 11: xray.app.router.RoutingRule.network_list:type_name -> xray.common.net.NetworkList + 18, // 12: xray.app.router.RoutingRule.networks:type_name -> xray.common.net.Network 3, // 13: xray.app.router.RoutingRule.source_cidr:type_name -> xray.app.router.CIDR 4, // 14: xray.app.router.RoutingRule.source_geoip:type_name -> xray.app.router.GeoIP - 14, // 15: xray.app.router.RoutingRule.source_port_list:type_name -> xray.common.net.PortList - 12, // 16: xray.app.router.RoutingRule.attributes:type_name -> xray.app.router.RoutingRule.AttributesEntry - 1, // 17: xray.app.router.Config.domain_strategy:type_name -> xray.app.router.Config.DomainStrategy - 8, // 18: xray.app.router.Config.rule:type_name -> xray.app.router.RoutingRule - 9, // 19: xray.app.router.Config.balancing_rule:type_name -> xray.app.router.BalancingRule - 20, // [20:20] is the sub-list for method output_type - 20, // [20:20] is the sub-list for method input_type - 20, // [20:20] is the sub-list for extension type_name - 20, // [20:20] is the sub-list for extension extendee - 0, // [0:20] is the sub-list for field type_name + 16, // 15: xray.app.router.RoutingRule.source_port_list:type_name -> xray.common.net.PortList + 14, // 16: xray.app.router.RoutingRule.attributes:type_name -> xray.app.router.RoutingRule.AttributesEntry + 19, // 17: xray.app.router.BalancingRule.strategy_settings:type_name -> xray.common.serial.TypedMessage + 10, // 18: xray.app.router.StrategyLeastLoadConfig.costs:type_name -> xray.app.router.StrategyWeight + 1, // 19: xray.app.router.Config.domain_strategy:type_name -> xray.app.router.Config.DomainStrategy + 8, // 20: xray.app.router.Config.rule:type_name -> xray.app.router.RoutingRule + 9, // 21: xray.app.router.Config.balancing_rule:type_name -> xray.app.router.BalancingRule + 22, // [22:22] is the sub-list for method output_type + 22, // [22:22] is the sub-list for method input_type + 22, // [22:22] is the sub-list for extension type_name + 22, // [22:22] is the sub-list for extension extendee + 0, // [0:22] is the sub-list for field type_name } func init() { file_app_router_config_proto_init() } @@ -1224,7 +1420,7 @@ func file_app_router_config_proto_init() { } } file_app_router_config_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Config); i { + switch v := v.(*StrategyWeight); i { case 0: return &v.state case 1: @@ -1236,6 +1432,30 @@ func file_app_router_config_proto_init() { } } file_app_router_config_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*StrategyLeastLoadConfig); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_app_router_config_proto_msgTypes[10].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 + } + } + file_app_router_config_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Domain_Attribute); i { case 0: return &v.state @@ -1252,7 +1472,7 @@ func file_app_router_config_proto_init() { (*RoutingRule_Tag)(nil), (*RoutingRule_BalancingTag)(nil), } - file_app_router_config_proto_msgTypes[9].OneofWrappers = []interface{}{ + file_app_router_config_proto_msgTypes[11].OneofWrappers = []interface{}{ (*Domain_Attribute_BoolValue)(nil), (*Domain_Attribute_IntValue)(nil), } @@ -1262,7 +1482,7 @@ func file_app_router_config_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_app_router_config_proto_rawDesc, NumEnums: 2, - NumMessages: 11, + NumMessages: 13, NumExtensions: 0, NumServices: 0, }, diff --git a/app/router/config.proto b/app/router/config.proto index ab9957f3..1cedb9e9 100644 --- a/app/router/config.proto +++ b/app/router/config.proto @@ -6,6 +6,7 @@ option go_package = "github.com/xtls/xray-core/app/router"; option java_package = "com.xray.app.router"; option java_multiple_files = true; +import "common/serial/typed_message.proto"; import "common/net/port.proto"; import "common/net/network.proto"; @@ -128,6 +129,27 @@ message BalancingRule { string tag = 1; repeated string outbound_selector = 2; string strategy = 3; + xray.common.serial.TypedMessage strategy_settings = 4; + string fallback_tag = 5; +} + +message StrategyWeight { + bool regexp = 1; + string match = 2; + float value =3; +} + +message StrategyLeastLoadConfig { + // weight settings + repeated StrategyWeight costs = 2; + // RTT baselines for selecting, int64 values of time.Duration + repeated int64 baselines = 3; + // expected nodes count to select + int32 expected = 4; + // max acceptable rtt, filter away high delay nodes. defalut 0 + int64 maxRTT = 5; + // acceptable failure rate + float tolerance = 6; } message Config { diff --git a/app/router/router.go b/app/router/router.go index 010f8d0a..88197710 100644 --- a/app/router/router.go +++ b/app/router/router.go @@ -29,13 +29,13 @@ type Route struct { } // Init initializes the Router. -func (r *Router) Init(ctx context.Context, config *Config, d dns.Client, ohm outbound.Manager) error { +func (r *Router) Init(ctx context.Context, config *Config, d dns.Client, ohm outbound.Manager, dispatcher routing.Dispatcher) error { r.domainStrategy = config.DomainStrategy r.dns = d r.balancers = make(map[string]*Balancer, len(config.BalancingRule)) for _, rule := range config.BalancingRule { - balancer, err := rule.Build(ohm) + balancer, err := rule.Build(ohm, dispatcher) if err != nil { return err } @@ -113,12 +113,12 @@ func (r *Router) pickRouteInternal(ctx routing.Context) (*Rule, routing.Context, } // Start implements common.Runnable. -func (*Router) Start() error { +func (r *Router) Start() error { return nil } // Close implements common.Closable. -func (*Router) Close() error { +func (r *Router) Close() error { return nil } @@ -140,8 +140,8 @@ func (r *Route) GetOutboundTag() string { func init() { common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) { r := new(Router) - if err := core.RequireFeatures(ctx, func(d dns.Client, ohm outbound.Manager) error { - return r.Init(ctx, config.(*Config), d, ohm) + if err := core.RequireFeatures(ctx, func(d dns.Client, ohm outbound.Manager, dispatcher routing.Dispatcher) error { + return r.Init(ctx, config.(*Config), d, ohm, dispatcher) }); err != nil { return nil, err } diff --git a/app/router/router_test.go b/app/router/router_test.go index 1b71e99b..4c6bfc63 100644 --- a/app/router/router_test.go +++ b/app/router/router_test.go @@ -43,7 +43,7 @@ func TestSimpleRouter(t *testing.T) { common.Must(r.Init(context.TODO(), config, mockDNS, &mockOutboundManager{ Manager: mockOhm, HandlerSelector: mockHs, - })) + }, nil)) ctx := session.ContextWithOutbound(context.Background(), &session.Outbound{Target: net.TCPDestination(net.DomainAddress("example.com"), 80)}) route, err := r.PickRoute(routing_session.AsRoutingContext(ctx)) @@ -84,7 +84,7 @@ func TestSimpleBalancer(t *testing.T) { common.Must(r.Init(context.TODO(), config, mockDNS, &mockOutboundManager{ Manager: mockOhm, HandlerSelector: mockHs, - })) + }, nil)) ctx := session.ContextWithOutbound(context.Background(), &session.Outbound{Target: net.TCPDestination(net.DomainAddress("example.com"), 80)}) route, err := r.PickRoute(routing_session.AsRoutingContext(ctx)) @@ -94,6 +94,55 @@ func TestSimpleBalancer(t *testing.T) { } } +/* + +Do not work right now: need a full client setup + +func TestLeastLoadBalancer(t *testing.T) { + config := &Config{ + Rule: []*RoutingRule{ + { + TargetTag: &RoutingRule_BalancingTag{ + BalancingTag: "balance", + }, + Networks: []net.Network{net.Network_TCP}, + }, + }, + BalancingRule: []*BalancingRule{ + { + Tag: "balance", + OutboundSelector: []string{"test-"}, + Strategy: "leastLoad", + StrategySettings: serial.ToTypedMessage(&StrategyLeastLoadConfig{ + Baselines: nil, + Expected: 1, + }), + }, + }, + } + + mockCtl := gomock.NewController(t) + defer mockCtl.Finish() + + mockDNS := mocks.NewDNSClient(mockCtl) + mockOhm := mocks.NewOutboundManager(mockCtl) + mockHs := mocks.NewOutboundHandlerSelector(mockCtl) + + mockHs.EXPECT().Select(gomock.Eq([]string{"test-"})).Return([]string{"test1"}) + + r := new(Router) + common.Must(r.Init(context.TODO(), config, mockDNS, &mockOutboundManager{ + Manager: mockOhm, + HandlerSelector: mockHs, + }, nil)) + ctx := session.ContextWithOutbound(context.Background(), &session.Outbound{Target: net.TCPDestination(net.DomainAddress("v2ray.com"), 80)}) + route, err := r.PickRoute(routing_session.AsRoutingContext(ctx)) + common.Must(err) + if tag := route.GetOutboundTag(); tag != "test1" { + t.Error("expect tag 'test1', bug actually ", tag) + } +}*/ + func TestIPOnDemand(t *testing.T) { config := &Config{ DomainStrategy: Config_IpOnDemand, @@ -123,7 +172,7 @@ func TestIPOnDemand(t *testing.T) { }).Return([]net.IP{{192, 168, 0, 1}}, nil).AnyTimes() r := new(Router) - common.Must(r.Init(context.TODO(), config, mockDNS, nil)) + common.Must(r.Init(context.TODO(), config, mockDNS, nil, nil)) ctx := session.ContextWithOutbound(context.Background(), &session.Outbound{Target: net.TCPDestination(net.DomainAddress("example.com"), 80)}) route, err := r.PickRoute(routing_session.AsRoutingContext(ctx)) @@ -162,7 +211,7 @@ func TestIPIfNonMatchDomain(t *testing.T) { }).Return([]net.IP{{192, 168, 0, 1}}, nil).AnyTimes() r := new(Router) - common.Must(r.Init(context.TODO(), config, mockDNS, nil)) + common.Must(r.Init(context.TODO(), config, mockDNS, nil, nil)) ctx := session.ContextWithOutbound(context.Background(), &session.Outbound{Target: net.TCPDestination(net.DomainAddress("example.com"), 80)}) route, err := r.PickRoute(routing_session.AsRoutingContext(ctx)) @@ -196,7 +245,7 @@ func TestIPIfNonMatchIP(t *testing.T) { mockDNS := mocks.NewDNSClient(mockCtl) r := new(Router) - common.Must(r.Init(context.TODO(), config, mockDNS, nil)) + common.Must(r.Init(context.TODO(), config, mockDNS, nil, nil)) ctx := session.ContextWithOutbound(context.Background(), &session.Outbound{Target: net.TCPDestination(net.LocalHostIP, 80)}) route, err := r.PickRoute(routing_session.AsRoutingContext(ctx)) diff --git a/app/router/strategy_leastload.go b/app/router/strategy_leastload.go new file mode 100644 index 00000000..e8eb5567 --- /dev/null +++ b/app/router/strategy_leastload.go @@ -0,0 +1,200 @@ +package router + +import ( + "context" + "math" + "sort" + "time" + + "github.com/xtls/xray-core/app/observatory" + "github.com/xtls/xray-core/common" + "github.com/xtls/xray-core/common/dice" + "github.com/xtls/xray-core/core" + "github.com/xtls/xray-core/features/extension" +) + +// LeastLoadStrategy represents a least load balancing strategy +type LeastLoadStrategy struct { + settings *StrategyLeastLoadConfig + costs *WeightManager + + observer extension.Observatory + + ctx context.Context +} + +func (l *LeastLoadStrategy) GetPrincipleTarget(strings []string) []string { + var ret []string + nodes := l.pickOutbounds(strings) + for _, v := range nodes { + ret = append(ret, v.Tag) + } + return ret +} + +// NewLeastLoadStrategy creates a new LeastLoadStrategy with settings +func NewLeastLoadStrategy(settings *StrategyLeastLoadConfig) *LeastLoadStrategy { + return &LeastLoadStrategy{ + settings: settings, + costs: NewWeightManager( + settings.Costs, 1, + func(value, cost float64) float64 { + return value * math.Pow(cost, 0.5) + }, + ), + } +} + +// node is a minimal copy of HealthCheckResult +// we don't use HealthCheckResult directly because +// it may change by health checker during routing +type node struct { + Tag string + CountAll int + CountFail int + RTTAverage time.Duration + RTTDeviation time.Duration + RTTDeviationCost time.Duration +} + +func (l *LeastLoadStrategy) InjectContext(ctx context.Context) { + l.ctx = ctx +} + +func (s *LeastLoadStrategy) PickOutbound(candidates []string) string { + selects := s.pickOutbounds(candidates) + count := len(selects) + if count == 0 { + // goes to fallbackTag + return "" + } + return selects[dice.Roll(count)].Tag +} + +func (s *LeastLoadStrategy) pickOutbounds(candidates []string) []*node { + qualified := s.getNodes(candidates, time.Duration(s.settings.MaxRTT)) + selects := s.selectLeastLoad(qualified) + return selects +} + +// selectLeastLoad selects nodes according to Baselines and Expected Count. +// +// The strategy always improves network response speed, not matter which mode below is configured. +// But they can still have different priorities. +// +// 1. Bandwidth priority: no Baseline + Expected Count > 0.: selects `Expected Count` of nodes. +// (one if Expected Count <= 0) +// +// 2. Bandwidth priority advanced: Baselines + Expected Count > 0. +// Select `Expected Count` amount of nodes, and also those near them according to baselines. +// In other words, it selects according to different Baselines, until one of them matches +// the Expected Count, if no Baseline matches, Expected Count applied. +// +// 3. Speed priority: Baselines + `Expected Count <= 0`. +// go through all baselines until find selects, if not, select none. Used in combination +// with 'balancer.fallbackTag', it means: selects qualified nodes or use the fallback. +func (s *LeastLoadStrategy) selectLeastLoad(nodes []*node) []*node { + if len(nodes) == 0 { + newError("least load: no qualified outbound").AtInfo().WriteToLog() + return nil + } + expected := int(s.settings.Expected) + availableCount := len(nodes) + if expected > availableCount { + return nodes + } + + if expected <= 0 { + expected = 1 + } + if len(s.settings.Baselines) == 0 { + return nodes[:expected] + } + + count := 0 + // go through all base line until find expected selects + for _, b := range s.settings.Baselines { + baseline := time.Duration(b) + for i := count; i < availableCount; i++ { + if nodes[i].RTTDeviationCost >= baseline { + break + } + count = i + 1 + } + // don't continue if find expected selects + if count >= expected { + newError("applied baseline: ", baseline).AtDebug().WriteToLog() + break + } + } + if s.settings.Expected > 0 && count < expected { + count = expected + } + return nodes[:count] +} + +func (s *LeastLoadStrategy) getNodes(candidates []string, maxRTT time.Duration) []*node { + if s.observer == nil { + common.Must(core.RequireFeatures(s.ctx, func(observatory extension.Observatory) error { + s.observer = observatory + return nil + })) + } + observeResult, err := s.observer.GetObservation(s.ctx) + if err != nil { + newError("cannot get observation").Base(err).WriteToLog() + return make([]*node, 0) + } + + results := observeResult.(*observatory.ObservationResult) + + outboundlist := outboundList(candidates) + + var ret []*node + + for _, v := range results.Status { + if v.Alive && (v.Delay < maxRTT.Milliseconds() || maxRTT == 0) && outboundlist.contains(v.OutboundTag) { + record := &node{ + Tag: v.OutboundTag, + CountAll: 1, + CountFail: 1, + RTTAverage: time.Duration(v.Delay) * time.Millisecond, + RTTDeviation: time.Duration(v.Delay) * time.Millisecond, + RTTDeviationCost: time.Duration(s.costs.Apply(v.OutboundTag, float64(time.Duration(v.Delay)*time.Millisecond))), + } + + if v.HealthPing != nil { + record.RTTAverage = time.Duration(v.HealthPing.Average) + record.RTTDeviation = time.Duration(v.HealthPing.Deviation) + record.RTTDeviationCost = time.Duration(s.costs.Apply(v.OutboundTag, float64(v.HealthPing.Deviation))) + record.CountAll = int(v.HealthPing.All) + record.CountFail = int(v.HealthPing.Fail) + + } + ret = append(ret, record) + } + } + + leastloadSort(ret) + return ret +} + +func leastloadSort(nodes []*node) { + sort.Slice(nodes, func(i, j int) bool { + left := nodes[i] + right := nodes[j] + if left.RTTDeviationCost != right.RTTDeviationCost { + return left.RTTDeviationCost < right.RTTDeviationCost + } + if left.RTTAverage != right.RTTAverage { + return left.RTTAverage < right.RTTAverage + } + if left.CountFail != right.CountFail { + return left.CountFail < right.CountFail + } + if left.CountAll != right.CountAll { + return left.CountAll > right.CountAll + } + return left.Tag < right.Tag + }) +} diff --git a/app/router/strategy_leastload_test.go b/app/router/strategy_leastload_test.go new file mode 100644 index 00000000..be95140d --- /dev/null +++ b/app/router/strategy_leastload_test.go @@ -0,0 +1,179 @@ +package router + +import ( + "testing" +) + +/* +Split into multiple package, need to be tested separately + +func TestSelectLeastLoad(t *testing.T) { + settings := &StrategyLeastLoadConfig{ + HealthCheck: &HealthPingConfig{ + SamplingCount: 10, + }, + Expected: 1, + MaxRTT: int64(time.Millisecond * time.Duration(800)), + } + strategy := NewLeastLoadStrategy(settings) + // std 40 + strategy.PutResult("a", time.Millisecond*time.Duration(60)) + strategy.PutResult("a", time.Millisecond*time.Duration(140)) + strategy.PutResult("a", time.Millisecond*time.Duration(60)) + strategy.PutResult("a", time.Millisecond*time.Duration(140)) + // std 60 + strategy.PutResult("b", time.Millisecond*time.Duration(40)) + strategy.PutResult("b", time.Millisecond*time.Duration(160)) + strategy.PutResult("b", time.Millisecond*time.Duration(40)) + strategy.PutResult("b", time.Millisecond*time.Duration(160)) + // std 0, but >MaxRTT + strategy.PutResult("c", time.Millisecond*time.Duration(1000)) + strategy.PutResult("c", time.Millisecond*time.Duration(1000)) + strategy.PutResult("c", time.Millisecond*time.Duration(1000)) + strategy.PutResult("c", time.Millisecond*time.Duration(1000)) + expected := "a" + actual := strategy.SelectAndPick([]string{"a", "b", "c", "untested"}) + if actual != expected { + t.Errorf("expected: %v, actual: %v", expected, actual) + } +} + +func TestSelectLeastLoadWithCost(t *testing.T) { + settings := &StrategyLeastLoadConfig{ + HealthCheck: &HealthPingConfig{ + SamplingCount: 10, + }, + Costs: []*StrategyWeight{ + {Match: "a", Value: 9}, + }, + Expected: 1, + } + strategy := NewLeastLoadStrategy(settings, nil) + // std 40, std+c 120 + strategy.PutResult("a", time.Millisecond*time.Duration(60)) + strategy.PutResult("a", time.Millisecond*time.Duration(140)) + strategy.PutResult("a", time.Millisecond*time.Duration(60)) + strategy.PutResult("a", time.Millisecond*time.Duration(140)) + // std 60 + strategy.PutResult("b", time.Millisecond*time.Duration(40)) + strategy.PutResult("b", time.Millisecond*time.Duration(160)) + strategy.PutResult("b", time.Millisecond*time.Duration(40)) + strategy.PutResult("b", time.Millisecond*time.Duration(160)) + expected := "b" + actual := strategy.SelectAndPick([]string{"a", "b", "untested"}) + if actual != expected { + t.Errorf("expected: %v, actual: %v", expected, actual) + } +} +*/ +func TestSelectLeastExpected(t *testing.T) { + strategy := &LeastLoadStrategy{ + settings: &StrategyLeastLoadConfig{ + Baselines: nil, + Expected: 3, + }, + } + nodes := []*node{ + {Tag: "a", RTTDeviationCost: 100}, + {Tag: "b", RTTDeviationCost: 200}, + {Tag: "c", RTTDeviationCost: 300}, + {Tag: "d", RTTDeviationCost: 350}, + } + expected := 3 + ns := strategy.selectLeastLoad(nodes) + if len(ns) != expected { + t.Errorf("expected: %v, actual: %v", expected, len(ns)) + } +} +func TestSelectLeastExpected2(t *testing.T) { + strategy := &LeastLoadStrategy{ + settings: &StrategyLeastLoadConfig{ + Baselines: nil, + Expected: 3, + }, + } + nodes := []*node{ + {Tag: "a", RTTDeviationCost: 100}, + {Tag: "b", RTTDeviationCost: 200}, + } + expected := 2 + ns := strategy.selectLeastLoad(nodes) + if len(ns) != expected { + t.Errorf("expected: %v, actual: %v", expected, len(ns)) + } +} +func TestSelectLeastExpectedAndBaselines(t *testing.T) { + strategy := &LeastLoadStrategy{ + settings: &StrategyLeastLoadConfig{ + Baselines: []int64{200, 300, 400}, + Expected: 3, + }, + } + nodes := []*node{ + {Tag: "a", RTTDeviationCost: 100}, + {Tag: "b", RTTDeviationCost: 200}, + {Tag: "c", RTTDeviationCost: 250}, + {Tag: "d", RTTDeviationCost: 300}, + {Tag: "e", RTTDeviationCost: 310}, + } + expected := 3 + ns := strategy.selectLeastLoad(nodes) + if len(ns) != expected { + t.Errorf("expected: %v, actual: %v", expected, len(ns)) + } +} +func TestSelectLeastExpectedAndBaselines2(t *testing.T) { + strategy := &LeastLoadStrategy{ + settings: &StrategyLeastLoadConfig{ + Baselines: []int64{200, 300, 400}, + Expected: 3, + }, + } + nodes := []*node{ + {Tag: "a", RTTDeviationCost: 500}, + {Tag: "b", RTTDeviationCost: 600}, + {Tag: "c", RTTDeviationCost: 700}, + {Tag: "d", RTTDeviationCost: 800}, + {Tag: "e", RTTDeviationCost: 900}, + } + expected := 3 + ns := strategy.selectLeastLoad(nodes) + if len(ns) != expected { + t.Errorf("expected: %v, actual: %v", expected, len(ns)) + } +} +func TestSelectLeastLoadBaselines(t *testing.T) { + strategy := &LeastLoadStrategy{ + settings: &StrategyLeastLoadConfig{ + Baselines: []int64{200, 400, 600}, + Expected: 0, + }, + } + nodes := []*node{ + {Tag: "a", RTTDeviationCost: 100}, + {Tag: "b", RTTDeviationCost: 200}, + {Tag: "c", RTTDeviationCost: 300}, + } + expected := 1 + ns := strategy.selectLeastLoad(nodes) + if len(ns) != expected { + t.Errorf("expected: %v, actual: %v", expected, len(ns)) + } +} +func TestSelectLeastLoadBaselinesNoQualified(t *testing.T) { + strategy := &LeastLoadStrategy{ + settings: &StrategyLeastLoadConfig{ + Baselines: []int64{200, 400, 600}, + Expected: 0, + }, + } + nodes := []*node{ + {Tag: "a", RTTDeviationCost: 800}, + {Tag: "b", RTTDeviationCost: 1000}, + } + expected := 0 + ns := strategy.selectLeastLoad(nodes) + if len(ns) != expected { + t.Errorf("expected: %v, actual: %v", expected, len(ns)) + } +} diff --git a/app/router/strategy_leastping.go b/app/router/strategy_leastping.go index e8c40aea..09ff845f 100644 --- a/app/router/strategy_leastping.go +++ b/app/router/strategy_leastping.go @@ -14,6 +14,10 @@ type LeastPingStrategy struct { observatory extension.Observatory } +func (l *LeastPingStrategy) GetPrincipleTarget(strings []string) []string { + return []string{l.PickOutbound(strings)} +} + func (l *LeastPingStrategy) InjectContext(ctx context.Context) { l.ctx = ctx } diff --git a/app/router/strategy_random.go b/app/router/strategy_random.go new file mode 100644 index 00000000..0bfe3ab6 --- /dev/null +++ b/app/router/strategy_random.go @@ -0,0 +1,21 @@ +package router + +import ( + "github.com/xtls/xray-core/common/dice" +) + +// RandomStrategy represents a random balancing strategy +type RandomStrategy struct{} + +func (s *RandomStrategy) GetPrincipleTarget(strings []string) []string { + return strings +} + +func (s *RandomStrategy) PickOutbound(candidates []string) string { + count := len(candidates) + if count == 0 { + // goes to fallbackTag + return "" + } + return candidates[dice.Roll(count)] +} diff --git a/app/router/weight.go b/app/router/weight.go new file mode 100644 index 00000000..c235740d --- /dev/null +++ b/app/router/weight.go @@ -0,0 +1,89 @@ +package router + +import ( + "regexp" + "strconv" + "strings" + "sync" +) + +type weightScaler func(value, weight float64) float64 + +var numberFinder = regexp.MustCompile(`\d+(\.\d+)?`) + +// NewWeightManager creates a new WeightManager with settings +func NewWeightManager(s []*StrategyWeight, defaultWeight float64, scaler weightScaler) *WeightManager { + return &WeightManager{ + settings: s, + cache: make(map[string]float64), + scaler: scaler, + defaultWeight: defaultWeight, + } +} + +// WeightManager manages weights for specific settings +type WeightManager struct { + settings []*StrategyWeight + cache map[string]float64 + scaler weightScaler + defaultWeight float64 + mu sync.Mutex +} + +// Get get the weight of specified tag +func (s *WeightManager) Get(tag string) float64 { + s.mu.Lock() + defer s.mu.Unlock() + weight, ok := s.cache[tag] + if ok { + return weight + } + weight = s.findValue(tag) + s.cache[tag] = weight + return weight +} + +// Apply applies weight to the value +func (s *WeightManager) Apply(tag string, value float64) float64 { + return s.scaler(value, s.Get(tag)) +} + +func (s *WeightManager) findValue(tag string) float64 { + for _, w := range s.settings { + matched := s.getMatch(tag, w.Match, w.Regexp) + if matched == "" { + continue + } + if w.Value > 0 { + return float64(w.Value) + } + // auto weight from matched + numStr := numberFinder.FindString(matched) + if numStr == "" { + return s.defaultWeight + } + weight, err := strconv.ParseFloat(numStr, 64) + if err != nil { + newError("unexpected error from ParseFloat: ", err).AtError().WriteToLog() + return s.defaultWeight + } + return weight + } + return s.defaultWeight +} + +func (s *WeightManager) getMatch(tag, find string, isRegexp bool) string { + if !isRegexp { + idx := strings.Index(tag, find) + if idx < 0 { + return "" + } + return find + } + r, err := regexp.Compile(find) + if err != nil { + newError("invalid regexp: ", find, "err: ", err).AtError().WriteToLog() + return "" + } + return r.FindString(tag) +} diff --git a/app/router/weight_test.go b/app/router/weight_test.go new file mode 100644 index 00000000..2e6a91e5 --- /dev/null +++ b/app/router/weight_test.go @@ -0,0 +1,60 @@ +package router_test + +import ( + "reflect" + "testing" + + "github.com/xtls/xray-core/app/router" +) + +func TestWeight(t *testing.T) { + manager := router.NewWeightManager( + []*router.StrategyWeight{ + { + Match: "x5", + Value: 100, + }, + { + Match: "x8", + }, + { + Regexp: true, + Match: `\bx0+(\.\d+)?\b`, + Value: 1, + }, + { + Regexp: true, + Match: `\bx\d+(\.\d+)?\b`, + }, + }, + 1, func(v, w float64) float64 { + return v * w + }, + ) + tags := []string{ + "node name, x5, and more", + "node name, x8", + "node name, x15", + "node name, x0100, and more", + "node name, x10.1", + "node name, x00.1, and more", + } + // test weight + expected := []float64{100, 8, 15, 100, 10.1, 1} + actual := make([]float64, 0) + for _, tag := range tags { + actual = append(actual, manager.Get(tag)) + } + if !reflect.DeepEqual(expected, actual) { + t.Errorf("expected: %v, actual: %v", expected, actual) + } + // test scale + expected2 := []float64{1000, 80, 150, 1000, 101, 10} + actual2 := make([]float64, 0) + for _, tag := range tags { + actual2 = append(actual2, manager.Apply(tag, 10)) + } + if !reflect.DeepEqual(expected2, actual2) { + t.Errorf("expected2: %v, actual2: %v", expected2, actual2) + } +} diff --git a/app/stats/command/command.pb.go b/app/stats/command/command.pb.go index 089dd183..1de78b96 100644 --- a/app/stats/command/command.pb.go +++ b/app/stats/command/command.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.31.0 +// protoc-gen-go v1.32.0 // protoc v4.23.1 // source: app/stats/command/command.proto diff --git a/app/stats/config.pb.go b/app/stats/config.pb.go index 7c944e9b..24730161 100644 --- a/app/stats/config.pb.go +++ b/app/stats/config.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.31.0 +// protoc-gen-go v1.32.0 // protoc v4.23.1 // source: app/stats/config.proto diff --git a/common/log/log.pb.go b/common/log/log.pb.go index 871d019b..3b0f89b0 100644 --- a/common/log/log.pb.go +++ b/common/log/log.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.31.0 +// protoc-gen-go v1.32.0 // protoc v4.23.1 // source: common/log/log.proto diff --git a/common/net/address.pb.go b/common/net/address.pb.go index c0ebb72a..169ced8d 100644 --- a/common/net/address.pb.go +++ b/common/net/address.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.31.0 +// protoc-gen-go v1.32.0 // protoc v4.23.1 // source: common/net/address.proto diff --git a/common/net/destination.pb.go b/common/net/destination.pb.go index ee775c7b..ac9fae9b 100644 --- a/common/net/destination.pb.go +++ b/common/net/destination.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.31.0 +// protoc-gen-go v1.32.0 // protoc v4.23.1 // source: common/net/destination.proto diff --git a/common/net/network.pb.go b/common/net/network.pb.go index 39a1dcd3..867e3cd6 100644 --- a/common/net/network.pb.go +++ b/common/net/network.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.31.0 +// protoc-gen-go v1.32.0 // protoc v4.23.1 // source: common/net/network.proto diff --git a/common/net/port.pb.go b/common/net/port.pb.go index 791e4e7e..1b80a81a 100644 --- a/common/net/port.pb.go +++ b/common/net/port.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.31.0 +// protoc-gen-go v1.32.0 // protoc v4.23.1 // source: common/net/port.proto diff --git a/common/protocol/headers.pb.go b/common/protocol/headers.pb.go index f733c419..39e2f16d 100644 --- a/common/protocol/headers.pb.go +++ b/common/protocol/headers.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.31.0 +// protoc-gen-go v1.32.0 // protoc v4.23.1 // source: common/protocol/headers.proto diff --git a/common/protocol/server_spec.pb.go b/common/protocol/server_spec.pb.go index 5c8ed7de..34642373 100644 --- a/common/protocol/server_spec.pb.go +++ b/common/protocol/server_spec.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.31.0 +// protoc-gen-go v1.32.0 // protoc v4.23.1 // source: common/protocol/server_spec.proto diff --git a/common/protocol/user.pb.go b/common/protocol/user.pb.go index 77c26e22..972cca84 100644 --- a/common/protocol/user.pb.go +++ b/common/protocol/user.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.31.0 +// protoc-gen-go v1.32.0 // protoc v4.23.1 // source: common/protocol/user.proto diff --git a/common/serial/typed_message.pb.go b/common/serial/typed_message.pb.go index 09c71b0d..a5846dc8 100644 --- a/common/serial/typed_message.pb.go +++ b/common/serial/typed_message.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.31.0 +// protoc-gen-go v1.32.0 // protoc v4.23.1 // source: common/serial/typed_message.proto diff --git a/common/session/context.go b/common/session/context.go index 329a5a65..87586169 100644 --- a/common/session/context.go +++ b/common/session/context.go @@ -24,6 +24,7 @@ const ( dispatcherKey timeoutOnlyKey allowedNetworkKey + handlerSessionKey ) // ContextWithID returns a new context with the given ID. diff --git a/core/config.pb.go b/core/config.pb.go index 72ba2379..de21cc56 100644 --- a/core/config.pb.go +++ b/core/config.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.31.0 +// protoc-gen-go v1.32.0 // protoc v4.23.1 // source: core/config.proto diff --git a/features/routing/balancer.go b/features/routing/balancer.go new file mode 100644 index 00000000..b1a2339f --- /dev/null +++ b/features/routing/balancer.go @@ -0,0 +1,10 @@ +package routing + +type BalancerOverrider interface { + SetOverrideTarget(tag, target string) error + GetOverrideTarget(tag string) (string, error) +} + +type BalancerPrincipleTarget interface { + GetPrincipleTarget(tag string) ([]string, error) +} diff --git a/infra/conf/api.go b/infra/conf/api.go index 879fc328..d1ec0391 100644 --- a/infra/conf/api.go +++ b/infra/conf/api.go @@ -7,6 +7,7 @@ import ( loggerservice "github.com/xtls/xray-core/app/log/command" observatoryservice "github.com/xtls/xray-core/app/observatory/command" handlerservice "github.com/xtls/xray-core/app/proxyman/command" + routerservice "github.com/xtls/xray-core/app/router/command" statsservice "github.com/xtls/xray-core/app/stats/command" "github.com/xtls/xray-core/common/serial" ) @@ -34,6 +35,8 @@ func (c *APIConfig) Build() (*commander.Config, error) { services = append(services, serial.ToTypedMessage(&statsservice.Config{})) case "observatoryservice": services = append(services, serial.ToTypedMessage(&observatoryservice.Config{})) + case "routingservice": + services = append(services, serial.ToTypedMessage(&routerservice.Config{})) } } diff --git a/infra/conf/observatory.go b/infra/conf/observatory.go index f51787a4..24c37a69 100644 --- a/infra/conf/observatory.go +++ b/infra/conf/observatory.go @@ -1,9 +1,11 @@ package conf import ( - "github.com/xtls/xray-core/app/observatory" - "github.com/xtls/xray-core/infra/conf/cfgcommon/duration" "google.golang.org/protobuf/proto" + + "github.com/xtls/xray-core/app/observatory" + "github.com/xtls/xray-core/app/observatory/burst" + "github.com/xtls/xray-core/infra/conf/cfgcommon/duration" ) type ObservatoryConfig struct { @@ -16,3 +18,17 @@ type ObservatoryConfig struct { func (o *ObservatoryConfig) Build() (proto.Message, error) { return &observatory.Config{SubjectSelector: o.SubjectSelector, ProbeUrl: o.ProbeURL, ProbeInterval: int64(o.ProbeInterval), EnableConcurrency: o.EnableConcurrency}, nil } + +type BurstObservatoryConfig struct { + SubjectSelector []string `json:"subjectSelector"` + // health check settings + HealthCheck *healthCheckSettings `json:"pingConfig,omitempty"` +} + +func (b BurstObservatoryConfig) Build() (proto.Message, error) { + if result, err := b.HealthCheck.Build(); err == nil { + return &burst.Config{SubjectSelector: b.SubjectSelector, PingConfig: result.(*burst.HealthPingConfig)}, nil + } else { + return nil, err + } +} diff --git a/infra/conf/router.go b/infra/conf/router.go index a3285e85..9c7ce789 100644 --- a/infra/conf/router.go +++ b/infra/conf/router.go @@ -8,6 +8,7 @@ import ( "github.com/xtls/xray-core/app/router" "github.com/xtls/xray-core/common/net" + "github.com/xtls/xray-core/common/serial" "github.com/xtls/xray-core/common/platform/filesystem" "google.golang.org/protobuf/proto" ) @@ -24,11 +25,13 @@ type StrategyConfig struct { } type BalancingRule struct { - Tag string `json:"tag"` - Selectors StringList `json:"selector"` - Strategy StrategyConfig `json:"strategy"` + Tag string `json:"tag"` + Selectors StringList `json:"selector"` + Strategy StrategyConfig `json:"strategy"` + FallbackTag string `json:"fallbackTag"` } +// Build builds the balancing rule func (r *BalancingRule) Build() (*router.BalancingRule, error) { if r.Tag == "" { return nil, newError("empty balancer tag") @@ -37,22 +40,37 @@ func (r *BalancingRule) Build() (*router.BalancingRule, error) { return nil, newError("empty selector list") } - var strategy string - switch strings.ToLower(r.Strategy.Type) { - case strategyRandom, "": - strategy = strategyRandom - case strategyLeastPing: - strategy = "leastPing" - case strategyRoundRobin: - strategy = "roundRobin" + r.Strategy.Type = strings.ToLower(r.Strategy.Type) + switch r.Strategy.Type { + case "": + r.Strategy.Type = strategyRandom + case strategyRandom, strategyLeastLoad, strategyLeastPing, strategyRoundRobin: default: return nil, newError("unknown balancing strategy: " + r.Strategy.Type) } + settings := []byte("{}") + if r.Strategy.Settings != nil { + settings = ([]byte)(*r.Strategy.Settings) + } + rawConfig, err := strategyConfigLoader.LoadWithID(settings, r.Strategy.Type) + if err != nil { + return nil, newError("failed to parse to strategy config.").Base(err) + } + var ts proto.Message + if builder, ok := rawConfig.(Buildable); ok { + ts, err = builder.Build() + if err != nil { + return nil, err + } + } + return &router.BalancingRule{ + Strategy: r.Strategy.Type, + StrategySettings: serial.ToTypedMessage(ts), + FallbackTag: r.FallbackTag, + OutboundSelector: r.Selectors, Tag: r.Tag, - OutboundSelector: []string(r.Selectors), - Strategy: strategy, }, nil } diff --git a/infra/conf/router_strategy.go b/infra/conf/router_strategy.go index ef2abc26..58367b4a 100644 --- a/infra/conf/router_strategy.go +++ b/infra/conf/router_strategy.go @@ -1,7 +1,93 @@ package conf +import ( + "google.golang.org/protobuf/proto" + + "github.com/xtls/xray-core/app/router" + "github.com/xtls/xray-core/app/observatory/burst" + "github.com/xtls/xray-core/infra/conf/cfgcommon/duration" +) + const ( strategyRandom string = "random" strategyLeastPing string = "leastping" strategyRoundRobin string = "roundrobin" + strategyLeastLoad string = "leastload" ) + +var ( + strategyConfigLoader = NewJSONConfigLoader(ConfigCreatorCache{ + strategyRandom: func() interface{} { return new(strategyEmptyConfig) }, + strategyLeastPing: func() interface{} { return new(strategyEmptyConfig) }, + strategyRoundRobin: func() interface{} { return new(strategyEmptyConfig) }, + strategyLeastLoad: func() interface{} { return new(strategyLeastLoadConfig) }, + }, "type", "settings") +) + +type strategyEmptyConfig struct { +} + +func (v *strategyEmptyConfig) Build() (proto.Message, error) { + return nil, nil +} + +type strategyLeastLoadConfig struct { + // weight settings + Costs []*router.StrategyWeight `json:"costs,omitempty"` + // ping rtt baselines + Baselines []duration.Duration `json:"baselines,omitempty"` + // expected nodes count to select + Expected int32 `json:"expected,omitempty"` + // max acceptable rtt, filter away high delay nodes. defalut 0 + MaxRTT duration.Duration `json:"maxRTT,omitempty"` + // acceptable failure rate + Tolerance float64 `json:"tolerance,omitempty"` +} + +// healthCheckSettings holds settings for health Checker +type healthCheckSettings struct { + Destination string `json:"destination"` + Connectivity string `json:"connectivity"` + Interval duration.Duration `json:"interval"` + SamplingCount int `json:"sampling"` + Timeout duration.Duration `json:"timeout"` +} + +func (h healthCheckSettings) Build() (proto.Message, error) { + return &burst.HealthPingConfig{ + Destination: h.Destination, + Connectivity: h.Connectivity, + Interval: int64(h.Interval), + Timeout: int64(h.Timeout), + SamplingCount: int32(h.SamplingCount), + }, nil +} + +// Build implements Buildable. +func (v *strategyLeastLoadConfig) Build() (proto.Message, error) { + config := &router.StrategyLeastLoadConfig{} + config.Costs = v.Costs + config.Tolerance = float32(v.Tolerance) + if config.Tolerance < 0 { + config.Tolerance = 0 + } + if config.Tolerance > 1 { + config.Tolerance = 1 + } + config.Expected = v.Expected + if config.Expected < 0 { + config.Expected = 0 + } + config.MaxRTT = int64(v.MaxRTT) + if config.MaxRTT < 0 { + config.MaxRTT = 0 + } + config.Baselines = make([]int64, 0) + for _, b := range v.Baselines { + if b <= 0 { + continue + } + config.Baselines = append(config.Baselines, int64(b)) + } + return config, nil +} diff --git a/infra/conf/router_test.go b/infra/conf/router_test.go index 63dc8e8d..0af1b3e3 100644 --- a/infra/conf/router_test.go +++ b/infra/conf/router_test.go @@ -5,6 +5,7 @@ import ( "os" "path/filepath" "testing" + "time" _ "unsafe" "github.com/xtls/xray-core/app/router" @@ -12,6 +13,7 @@ import ( "github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/platform" "github.com/xtls/xray-core/common/platform/filesystem" + "github.com/xtls/xray-core/common/serial" . "github.com/xtls/xray-core/infra/conf" "google.golang.org/protobuf/proto" ) @@ -96,6 +98,34 @@ func TestRouterConfig(t *testing.T) { { "tag": "b1", "selector": ["test"] + }, + { + "tag": "b2", + "selector": ["test"], + "strategy": { + "type": "leastload", + "settings": { + "healthCheck": { + "interval": "5m0s", + "sampling": 2, + "timeout": "5s", + "destination": "dest", + "connectivity": "conn" + }, + "costs": [ + { + "regexp": true, + "match": "\\d+(\\.\\d+)", + "value": 5 + } + ], + "baselines": ["400ms", "600ms"], + "expected": 6, + "maxRTT": "1000ms", + "tolerance": 0.5 + } + }, + "fallbackTag": "fall" } ] }`, @@ -108,6 +138,28 @@ func TestRouterConfig(t *testing.T) { OutboundSelector: []string{"test"}, Strategy: "random", }, + { + Tag: "b2", + OutboundSelector: []string{"test"}, + Strategy: "leastload", + StrategySettings: serial.ToTypedMessage(&router.StrategyLeastLoadConfig{ + Costs: []*router.StrategyWeight{ + { + Regexp: true, + Match: "\\d+(\\.\\d+)", + Value: 5, + }, + }, + Baselines: []int64{ + int64(time.Duration(400) * time.Millisecond), + int64(time.Duration(600) * time.Millisecond), + }, + Expected: 6, + MaxRTT: int64(time.Duration(1000) * time.Millisecond), + Tolerance: 0.5, + }), + FallbackTag: "fall", + }, }, Rule: []*router.RoutingRule{ { diff --git a/infra/conf/xray.go b/infra/conf/xray.go index 10944826..2144815a 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"` + BurstObservatory *BurstObservatoryConfig `json:"burstObservatory"` } func (c *Config) findInboundTag(tag string) int { @@ -639,6 +640,14 @@ func (c *Config) Build() (*core.Config, error) { config.App = append(config.App, serial.ToTypedMessage(r)) } + if c.BurstObservatory != nil { + r, err := c.BurstObservatory.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/commands/all/api/api.go b/main/commands/all/api/api.go index 9986329b..0ff446b7 100644 --- a/main/commands/all/api/api.go +++ b/main/commands/all/api/api.go @@ -15,6 +15,8 @@ var CmdAPI = &base.Command{ cmdGetStats, cmdQueryStats, cmdSysStats, + cmdBalancerInfo, + cmdBalancerOverride, cmdAddInbounds, cmdAddOutbounds, cmdRemoveInbounds, diff --git a/main/commands/all/api/balancer_info.go b/main/commands/all/api/balancer_info.go new file mode 100644 index 00000000..4ba33236 --- /dev/null +++ b/main/commands/all/api/balancer_info.go @@ -0,0 +1,108 @@ +package api + +import ( + "fmt" + "os" + "strings" + + routerService "github.com/xtls/xray-core/app/router/command" + "github.com/xtls/xray-core/main/commands/base" +) + +// TODO: support "-json" flag for json output +var cmdBalancerInfo = &base.Command{ + CustomFlags: true, + UsageLine: "{{.Exec}} api bi [--server=127.0.0.1:8080] [balancer]...", + Short: "balancer information", + Long: ` +Get information of specified balancers, including health, strategy +and selecting. If no balancer tag specified, get information of +all balancers. + +> Make sure you have "RoutingService" set in "config.api.services" +of server config. + +Arguments: + + -json + Use json output. + + -s, -server + The API server address. Default 127.0.0.1:8080 + + -t, -timeout + Timeout seconds to call API. Default 3 + +Example: + + {{.Exec}} {{.LongName}} --server=127.0.0.1:8080 balancer1 balancer2 +`, + Run: executeBalancerInfo, +} + +func executeBalancerInfo(cmd *base.Command, args []string) { + setSharedFlags(cmd) + cmd.Flag.Parse(args) + + conn, ctx, close := dialAPIServer() + defer close() + + client := routerService.NewRoutingServiceClient(conn) + r := &routerService.GetBalancerInfoRequest{Tag: args[0]} + resp, err := client.GetBalancerInfo(ctx, r) + if err != nil { + base.Fatalf("failed to get health information: %s", err) + } + + if apiJSON { + showJSONResponse(resp) + return + } + + showBalancerInfo(resp.Balancer) + +} + +func showBalancerInfo(b *routerService.BalancerMsg) { + const tableIndent = 4 + sb := new(strings.Builder) + // Override + if b.Override != nil { + sb.WriteString(" - Selecting Override:\n") + for i, s := range []string{b.Override.Target} { + writeRow(sb, tableIndent, i+1, []string{s}, nil) + } + } + // Selects + sb.WriteString(" - Selects:\n") + + for i, o := range b.PrincipleTarget.Tag { + writeRow(sb, tableIndent, i+1, []string{o}, nil) + } + os.Stdout.WriteString(sb.String()) +} + +func getColumnFormats(titles []string) []string { + w := make([]string, len(titles)) + for i, t := range titles { + w[i] = fmt.Sprintf("%%-%ds ", len(t)) + } + return w +} + +func writeRow(sb *strings.Builder, indent, index int, values, formats []string) { + if index == 0 { + // title line + sb.WriteString(strings.Repeat(" ", indent+4)) + } else { + sb.WriteString(fmt.Sprintf("%s%-4d", strings.Repeat(" ", indent), index)) + } + for i, v := range values { + format := "%-14s" + if i < len(formats) { + format = formats[i] + } + sb.WriteString(fmt.Sprintf(format, v)) + } + sb.WriteByte('\n') +} diff --git a/main/commands/all/api/balancer_override.go b/main/commands/all/api/balancer_override.go new file mode 100644 index 00000000..3ac013e9 --- /dev/null +++ b/main/commands/all/api/balancer_override.go @@ -0,0 +1,77 @@ +package api + +import ( + routerService "github.com/xtls/xray-core/app/router/command" + "github.com/xtls/xray-core/main/commands/base" +) + +var cmdBalancerOverride = &base.Command{ + CustomFlags: true, + UsageLine: "{{.Exec}} api bo [--server=127.0.0.1:8080] <-b balancer> outboundTag", + Short: "balancer override", + Long: ` +Override a balancer's selection. + +> Make sure you have "RoutingService" set in "config.api.services" +of server config. + +Once a balancer's selecting is overridden: + +- The balancer's selection result will always be outboundTag + +Arguments: + + -r, -remove + Remove the overridden + + -r, -remove + Remove the override + + -s, -server + The API server address. Default 127.0.0.1:8080 + + -t, -timeout + Timeout seconds to call API. Default 3 + +Example: + + {{.Exec}} {{.LongName}} --server=127.0.0.1:8080 -b balancer tag + {{.Exec}} {{.LongName}} --server=127.0.0.1:8080 -b balancer -r +`, + Run: executeBalancerOverride, +} + +func executeBalancerOverride(cmd *base.Command, args []string) { + var ( + balancer string + remove bool + ) + cmd.Flag.StringVar(&balancer, "b", "", "") + cmd.Flag.StringVar(&balancer, "balancer", "", "") + cmd.Flag.BoolVar(&remove, "r", false, "") + cmd.Flag.BoolVar(&remove, "remove", false, "") + setSharedFlags(cmd) + cmd.Flag.Parse(args) + + if balancer == "" { + base.Fatalf("balancer tag not specified") + } + + conn, ctx, close := dialAPIServer() + defer close() + + client := routerService.NewRoutingServiceClient(conn) + target := "" + if !remove { + target = cmd.Flag.Args()[0] + } + r := &routerService.OverrideBalancerTargetRequest{ + BalancerTag: balancer, + Target: target, + } + + _, err := client.OverrideBalancerTarget(ctx, r) + if err != nil { + base.Fatalf("failed to perform balancer health checks: %s", err) + } +} diff --git a/main/commands/all/api/shared.go b/main/commands/all/api/shared.go index d3aaf0e1..4d98359f 100644 --- a/main/commands/all/api/shared.go +++ b/main/commands/all/api/shared.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "fmt" + "google.golang.org/protobuf/encoding/protojson" "io" "net/http" "net/url" @@ -15,7 +16,6 @@ import ( "github.com/xtls/xray-core/common/buf" "github.com/xtls/xray-core/main/commands/base" "google.golang.org/grpc" - "google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/proto" ) @@ -24,6 +24,7 @@ type serviceHandler func(ctx context.Context, conn *grpc.ClientConn, cmd *base.C var ( apiServerAddrPtr string apiTimeout int + apiJSON bool ) func setSharedFlags(cmd *base.Command) { @@ -31,6 +32,7 @@ func setSharedFlags(cmd *base.Command) { cmd.Flag.StringVar(&apiServerAddrPtr, "server", "127.0.0.1:8080", "") cmd.Flag.IntVar(&apiTimeout, "t", 3, "") cmd.Flag.IntVar(&apiTimeout, "timeout", 3, "") + cmd.Flag.BoolVar(&apiJSON, "json", false, "") } func dialAPIServer() (conn *grpc.ClientConn, ctx context.Context, close func()) { @@ -103,13 +105,8 @@ func fetchHTTPContent(target string) ([]byte, error) { return content, nil } -func protoToJSONString(m proto.Message, _, indent string) (string, error) { - ops := protojson.MarshalOptions{ - Indent: indent, - EmitUnpopulated: true, - } - b, err := ops.Marshal(m) - return string(b), err +func protoToJSONString(m proto.Message, prefix, indent string) (string, error) { + return strings.TrimSpace(protojson.MarshalOptions{Indent: indent}.Format(m)), nil } func showJSONResponse(m proto.Message) { diff --git a/proxy/blackhole/config.pb.go b/proxy/blackhole/config.pb.go index 0cdb33a7..6b047974 100644 --- a/proxy/blackhole/config.pb.go +++ b/proxy/blackhole/config.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.31.0 +// protoc-gen-go v1.32.0 // protoc v4.23.1 // source: proxy/blackhole/config.proto diff --git a/proxy/dns/config.pb.go b/proxy/dns/config.pb.go index 767ad2ca..6b1f062a 100644 --- a/proxy/dns/config.pb.go +++ b/proxy/dns/config.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.31.0 +// protoc-gen-go v1.32.0 // protoc v4.23.1 // source: proxy/dns/config.proto diff --git a/proxy/dokodemo/config.pb.go b/proxy/dokodemo/config.pb.go index c56e74b9..206089e8 100644 --- a/proxy/dokodemo/config.pb.go +++ b/proxy/dokodemo/config.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.31.0 +// protoc-gen-go v1.32.0 // protoc v4.23.1 // source: proxy/dokodemo/config.proto diff --git a/proxy/freedom/config.pb.go b/proxy/freedom/config.pb.go index 81ce18e5..447c9cde 100644 --- a/proxy/freedom/config.pb.go +++ b/proxy/freedom/config.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.32.0 -// protoc v4.25.2 +// protoc v4.23.1 // source: proxy/freedom/config.proto package freedom diff --git a/proxy/http/config.pb.go b/proxy/http/config.pb.go index 6ae78777..4c5776e6 100644 --- a/proxy/http/config.pb.go +++ b/proxy/http/config.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.31.0 +// protoc-gen-go v1.32.0 // protoc v4.23.1 // source: proxy/http/config.proto diff --git a/proxy/loopback/config.pb.go b/proxy/loopback/config.pb.go index 3902e532..f9a89d55 100644 --- a/proxy/loopback/config.pb.go +++ b/proxy/loopback/config.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.31.0 +// protoc-gen-go v1.32.0 // protoc v4.23.1 // source: proxy/loopback/config.proto diff --git a/proxy/shadowsocks/config.pb.go b/proxy/shadowsocks/config.pb.go index 86351df8..33d96f5c 100644 --- a/proxy/shadowsocks/config.pb.go +++ b/proxy/shadowsocks/config.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.31.0 +// protoc-gen-go v1.32.0 // protoc v4.23.1 // source: proxy/shadowsocks/config.proto diff --git a/proxy/shadowsocks_2022/config.pb.go b/proxy/shadowsocks_2022/config.pb.go index 633f3cda..12d03625 100644 --- a/proxy/shadowsocks_2022/config.pb.go +++ b/proxy/shadowsocks_2022/config.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.31.0 +// protoc-gen-go v1.32.0 // protoc v4.23.1 // source: proxy/shadowsocks_2022/config.proto diff --git a/proxy/socks/config.pb.go b/proxy/socks/config.pb.go index c22af748..991b467e 100644 --- a/proxy/socks/config.pb.go +++ b/proxy/socks/config.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.31.0 +// protoc-gen-go v1.32.0 // protoc v4.23.1 // source: proxy/socks/config.proto diff --git a/proxy/trojan/config.pb.go b/proxy/trojan/config.pb.go index d90271d9..63003b50 100644 --- a/proxy/trojan/config.pb.go +++ b/proxy/trojan/config.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.31.0 +// protoc-gen-go v1.32.0 // protoc v4.23.1 // source: proxy/trojan/config.proto diff --git a/proxy/vless/account.pb.go b/proxy/vless/account.pb.go index 405d8811..21f1a729 100644 --- a/proxy/vless/account.pb.go +++ b/proxy/vless/account.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.31.0 +// protoc-gen-go v1.32.0 // protoc v4.23.1 // source: proxy/vless/account.proto diff --git a/proxy/vless/encoding/addons.pb.go b/proxy/vless/encoding/addons.pb.go index 8c409cbc..02dcc713 100644 --- a/proxy/vless/encoding/addons.pb.go +++ b/proxy/vless/encoding/addons.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.31.0 +// protoc-gen-go v1.32.0 // protoc v4.23.1 // source: proxy/vless/encoding/addons.proto diff --git a/proxy/vless/inbound/config.pb.go b/proxy/vless/inbound/config.pb.go index c2ddd5e5..952b3b51 100644 --- a/proxy/vless/inbound/config.pb.go +++ b/proxy/vless/inbound/config.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.31.0 +// protoc-gen-go v1.32.0 // protoc v4.23.1 // source: proxy/vless/inbound/config.proto diff --git a/proxy/vless/outbound/config.pb.go b/proxy/vless/outbound/config.pb.go index 3fb9ed75..428110d8 100644 --- a/proxy/vless/outbound/config.pb.go +++ b/proxy/vless/outbound/config.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.31.0 +// protoc-gen-go v1.32.0 // protoc v4.23.1 // source: proxy/vless/outbound/config.proto diff --git a/proxy/vmess/account.pb.go b/proxy/vmess/account.pb.go index 575ec583..0d41c368 100644 --- a/proxy/vmess/account.pb.go +++ b/proxy/vmess/account.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.31.0 +// protoc-gen-go v1.32.0 // protoc v4.23.1 // source: proxy/vmess/account.proto diff --git a/proxy/vmess/inbound/config.pb.go b/proxy/vmess/inbound/config.pb.go index 67fa6461..e7d50c6a 100644 --- a/proxy/vmess/inbound/config.pb.go +++ b/proxy/vmess/inbound/config.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.31.0 +// protoc-gen-go v1.32.0 // protoc v4.23.1 // source: proxy/vmess/inbound/config.proto diff --git a/proxy/vmess/outbound/config.pb.go b/proxy/vmess/outbound/config.pb.go index 80492e05..c08479f7 100644 --- a/proxy/vmess/outbound/config.pb.go +++ b/proxy/vmess/outbound/config.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.31.0 +// protoc-gen-go v1.32.0 // protoc v4.23.1 // source: proxy/vmess/outbound/config.proto diff --git a/proxy/wireguard/config.pb.go b/proxy/wireguard/config.pb.go index 47bd2b77..c620471e 100644 --- a/proxy/wireguard/config.pb.go +++ b/proxy/wireguard/config.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.31.0 +// protoc-gen-go v1.32.0 // protoc v4.23.1 // source: proxy/wireguard/config.proto diff --git a/transport/global/config.pb.go b/transport/global/config.pb.go index 4bd8c84f..046c39e8 100644 --- a/transport/global/config.pb.go +++ b/transport/global/config.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.31.0 +// protoc-gen-go v1.32.0 // protoc v4.23.1 // source: transport/global/config.proto diff --git a/transport/internet/config.pb.go b/transport/internet/config.pb.go index 9636bfb4..3c8e36f5 100644 --- a/transport/internet/config.pb.go +++ b/transport/internet/config.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.31.0 +// protoc-gen-go v1.32.0 // protoc v4.23.1 // source: transport/internet/config.proto diff --git a/transport/internet/domainsocket/config.pb.go b/transport/internet/domainsocket/config.pb.go index 9d3eb25c..276293f1 100644 --- a/transport/internet/domainsocket/config.pb.go +++ b/transport/internet/domainsocket/config.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.31.0 +// protoc-gen-go v1.32.0 // protoc v4.23.1 // source: transport/internet/domainsocket/config.proto diff --git a/transport/internet/grpc/config.pb.go b/transport/internet/grpc/config.pb.go index 4a794bb1..43da0199 100644 --- a/transport/internet/grpc/config.pb.go +++ b/transport/internet/grpc/config.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.31.0 +// protoc-gen-go v1.32.0 // protoc v4.23.1 // source: transport/internet/grpc/config.proto diff --git a/transport/internet/grpc/encoding/stream.pb.go b/transport/internet/grpc/encoding/stream.pb.go index f8e7c7fb..6450c7d1 100644 --- a/transport/internet/grpc/encoding/stream.pb.go +++ b/transport/internet/grpc/encoding/stream.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.31.0 +// protoc-gen-go v1.32.0 // protoc v4.23.1 // source: transport/internet/grpc/encoding/stream.proto diff --git a/transport/internet/headers/dns/config.pb.go b/transport/internet/headers/dns/config.pb.go index 34d37c33..1dc5783e 100644 --- a/transport/internet/headers/dns/config.pb.go +++ b/transport/internet/headers/dns/config.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.31.0 +// protoc-gen-go v1.32.0 // protoc v4.23.1 // source: transport/internet/headers/dns/config.proto diff --git a/transport/internet/headers/http/config.pb.go b/transport/internet/headers/http/config.pb.go index 69fefd8c..b18e4228 100644 --- a/transport/internet/headers/http/config.pb.go +++ b/transport/internet/headers/http/config.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.31.0 +// protoc-gen-go v1.32.0 // protoc v4.23.1 // source: transport/internet/headers/http/config.proto diff --git a/transport/internet/headers/noop/config.pb.go b/transport/internet/headers/noop/config.pb.go index 9777e46f..fdccb47e 100644 --- a/transport/internet/headers/noop/config.pb.go +++ b/transport/internet/headers/noop/config.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.31.0 +// protoc-gen-go v1.32.0 // protoc v4.23.1 // source: transport/internet/headers/noop/config.proto diff --git a/transport/internet/headers/srtp/config.pb.go b/transport/internet/headers/srtp/config.pb.go index 2d9c5354..ed841c38 100644 --- a/transport/internet/headers/srtp/config.pb.go +++ b/transport/internet/headers/srtp/config.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.31.0 +// protoc-gen-go v1.32.0 // protoc v4.23.1 // source: transport/internet/headers/srtp/config.proto diff --git a/transport/internet/headers/tls/config.pb.go b/transport/internet/headers/tls/config.pb.go index c0b77de4..aa026e8a 100644 --- a/transport/internet/headers/tls/config.pb.go +++ b/transport/internet/headers/tls/config.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.31.0 +// protoc-gen-go v1.32.0 // protoc v4.23.1 // source: transport/internet/headers/tls/config.proto diff --git a/transport/internet/headers/utp/config.pb.go b/transport/internet/headers/utp/config.pb.go index b3bd9749..a6e1dd1e 100644 --- a/transport/internet/headers/utp/config.pb.go +++ b/transport/internet/headers/utp/config.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.31.0 +// protoc-gen-go v1.32.0 // protoc v4.23.1 // source: transport/internet/headers/utp/config.proto diff --git a/transport/internet/headers/wechat/config.pb.go b/transport/internet/headers/wechat/config.pb.go index 8bec6fe4..edb45f28 100644 --- a/transport/internet/headers/wechat/config.pb.go +++ b/transport/internet/headers/wechat/config.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.31.0 +// protoc-gen-go v1.32.0 // protoc v4.23.1 // source: transport/internet/headers/wechat/config.proto diff --git a/transport/internet/headers/wireguard/config.pb.go b/transport/internet/headers/wireguard/config.pb.go index 4274198c..f46ba626 100644 --- a/transport/internet/headers/wireguard/config.pb.go +++ b/transport/internet/headers/wireguard/config.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.31.0 +// protoc-gen-go v1.32.0 // protoc v4.23.1 // source: transport/internet/headers/wireguard/config.proto diff --git a/transport/internet/http/config.pb.go b/transport/internet/http/config.pb.go index baaa5630..12624f02 100644 --- a/transport/internet/http/config.pb.go +++ b/transport/internet/http/config.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.31.0 +// protoc-gen-go v1.32.0 // protoc v4.23.1 // source: transport/internet/http/config.proto diff --git a/transport/internet/kcp/config.pb.go b/transport/internet/kcp/config.pb.go index 74537db7..2369dc50 100644 --- a/transport/internet/kcp/config.pb.go +++ b/transport/internet/kcp/config.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.31.0 +// protoc-gen-go v1.32.0 // protoc v4.23.1 // source: transport/internet/kcp/config.proto diff --git a/transport/internet/quic/config.pb.go b/transport/internet/quic/config.pb.go index f10998eb..b5b9b8a3 100644 --- a/transport/internet/quic/config.pb.go +++ b/transport/internet/quic/config.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.31.0 +// protoc-gen-go v1.32.0 // protoc v4.23.1 // source: transport/internet/quic/config.proto diff --git a/transport/internet/reality/config.pb.go b/transport/internet/reality/config.pb.go index 6631d0af..220d871d 100644 --- a/transport/internet/reality/config.pb.go +++ b/transport/internet/reality/config.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.32.0 -// protoc v4.25.1 +// protoc v4.23.1 // source: transport/internet/reality/config.proto package reality diff --git a/transport/internet/tcp/config.pb.go b/transport/internet/tcp/config.pb.go index e7ecd49b..1ee10034 100644 --- a/transport/internet/tcp/config.pb.go +++ b/transport/internet/tcp/config.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.31.0 +// protoc-gen-go v1.32.0 // protoc v4.23.1 // source: transport/internet/tcp/config.proto diff --git a/transport/internet/tls/config.pb.go b/transport/internet/tls/config.pb.go index 7602f3e9..daa02fd9 100644 --- a/transport/internet/tls/config.pb.go +++ b/transport/internet/tls/config.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.31.0 +// protoc-gen-go v1.32.0 // protoc v4.23.1 // source: transport/internet/tls/config.proto diff --git a/transport/internet/udp/config.pb.go b/transport/internet/udp/config.pb.go index b56c5fa4..82df7a75 100644 --- a/transport/internet/udp/config.pb.go +++ b/transport/internet/udp/config.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.31.0 +// protoc-gen-go v1.32.0 // protoc v4.23.1 // source: transport/internet/udp/config.proto diff --git a/transport/internet/websocket/config.pb.go b/transport/internet/websocket/config.pb.go index ab0aa373..490dcfa0 100644 --- a/transport/internet/websocket/config.pb.go +++ b/transport/internet/websocket/config.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.31.0 +// protoc-gen-go v1.32.0 // protoc v4.23.1 // source: transport/internet/websocket/config.proto