v5: Health Check & LeastLoad Strategy (rebased from 2c5a714903)

Some changes will be necessary to integrate it into V2Ray
This commit is contained in:
Jebbs 2021-01-30 08:31:11 +08:00 committed by Shelikhoo
parent dde9463275
commit fa0cf6db26
No known key found for this signature in database
GPG Key ID: C4D5E79D22B25316
37 changed files with 3655 additions and 264 deletions

View File

@ -207,10 +207,16 @@ func (d *DefaultDispatcher) Dispatch(ctx context.Context, destination net.Destin
content = new(session.Content)
ctx = session.ContextWithContent(ctx, content)
}
handler := session.HandlerFromContext(ctx)
sniffingRequest := content.SniffingRequest
switch {
case !sniffingRequest.Enabled:
go d.routedDispatch(ctx, outbound, destination)
if handler != nil {
go d.targetedDispatch(ctx, outbound, handler.Tag)
} else {
go d.routedDispatch(ctx, outbound, destination)
}
case destination.Network != net.Network_TCP:
// Only metadata sniff will be used for non tcp connection
result, err := sniffer(ctx, nil, true)
@ -240,7 +246,11 @@ func (d *DefaultDispatcher) Dispatch(ctx context.Context, destination net.Destin
destination.Address = net.ParseAddress(domain)
ob.Target = destination
}
d.routedDispatch(ctx, outbound, destination)
if handler != nil {
d.targetedDispatch(ctx, outbound, handler.Tag)
} else {
d.routedDispatch(ctx, outbound, destination)
}
}()
}
return inbound, nil
@ -292,6 +302,25 @@ func sniffer(ctx context.Context, cReader *cachedReader, metadataOnly bool) (Sni
return contentResult, contentErr
}
//TODO Pending removal for tagged connection
func (d *DefaultDispatcher) targetedDispatch(ctx context.Context, link *transport.Link, tag string) {
handler := d.ohm.GetHandler(tag)
if handler == nil {
newError("outbound handler [", tag, "] not exist").AtError().WriteToLog(session.ExportIDToError(ctx))
common.Close(link.Writer)
common.Interrupt(link.Reader)
return
}
if accessMessage := log.AccessMessageFromContext(ctx); accessMessage != nil {
if tag := handler.Tag(); tag != "" {
accessMessage.Detour = tag
}
log.Record(accessMessage)
}
handler.Dispatch(ctx, link)
}
func (d *DefaultDispatcher) routedDispatch(ctx context.Context, link *transport.Link, destination net.Destination) {
var handler outbound.Handler

View File

@ -5,8 +5,8 @@ package router
import (
"context"
"github.com/v2fly/v2ray-core/v4/features/routing"
"github.com/v2fly/v2ray-core/v4/common/dice"
"github.com/v2fly/v2ray-core/v4/features/extension"
"github.com/v2fly/v2ray-core/v4/features/outbound"
)
@ -15,34 +15,37 @@ 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 Balancer struct {
selectors []string
strategy BalancingStrategy
ohm outbound.Manager
selectors []string
strategy routing.BalancingStrategy
ohm outbound.Manager
fallbackTag string
override overridden
}
// 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 != nil {
tag = b.strategy.Pick(o.selects)
} else {
tag = b.strategy.SelectAndPick(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
@ -53,3 +56,13 @@ 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
}

View File

@ -0,0 +1,83 @@
package router
import (
sync "sync"
"time"
"github.com/v2fly/v2ray-core/v4/features/outbound"
)
func (b *Balancer) overrideSelecting(selects []string, validity time.Duration) error {
if validity <= 0 {
b.override.Clear()
return nil
}
hs, ok := b.ohm.(outbound.HandlerSelector)
if !ok {
return newError("outbound.Manager is not a HandlerSelector")
}
tags := hs.Select(selects)
if len(tags) == 0 {
return newError("no outbound selected")
}
b.override.Put(tags, time.Now().Add(validity))
return nil
}
// OverrideSelecting implements routing.BalancingOverrider
func (r *Router) OverrideSelecting(balancer string, selects []string, validity time.Duration) 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")
}
err := b.overrideSelecting(selects, validity)
if err != nil {
return err
}
return nil
}
type overriddenSettings struct {
selects []string
until time.Time
}
type overridden struct {
access sync.RWMutex
settings overriddenSettings
}
// Get gets the overridden settings
func (o *overridden) Get() *overriddenSettings {
o.access.RLock()
defer o.access.RUnlock()
if len(o.settings.selects) == 0 || time.Now().After(o.settings.until) {
return nil
}
return &overriddenSettings{
selects: o.settings.selects,
until: o.settings.until,
}
}
// Put updates the overridden settings
func (o *overridden) Put(selects []string, until time.Time) {
o.access.Lock()
defer o.access.Unlock()
o.settings.selects = selects
o.settings.until = until
}
// Clear clears the overridden settings
func (o *overridden) Clear() {
o.access.Lock()
defer o.access.Unlock()
o.settings.selects = nil
o.settings.until = time.Time{}
}

View File

@ -8,6 +8,9 @@ import (
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
core "github.com/v2fly/v2ray-core/v4"
"github.com/v2fly/v2ray-core/v4/common"
"github.com/v2fly/v2ray-core/v4/features/routing"
@ -73,6 +76,80 @@ func (s *routingServer) SubscribeRoutingStats(request *SubscribeRoutingStatsRequ
}
}
func (s *routingServer) GetBalancers(ctx context.Context, request *GetBalancersRequest) (*GetBalancersResponse, error) {
h, ok := s.router.(routing.RouterChecker)
if !ok {
return nil, status.Errorf(codes.Unavailable, "current router is not a health checker")
}
results, err := h.GetBalancersInfo(request.BalancerTags)
if err != nil {
return nil, status.Errorf(codes.Internal, err.Error())
}
rsp := &GetBalancersResponse{
Balancers: make([]*BalancerMsg, 0),
}
for _, result := range results {
var override *OverrideSelectingMsg
if result.Override != nil {
override = &OverrideSelectingMsg{
Until: result.Override.Until.Local().String(),
Selects: result.Override.Selects,
}
}
stat := &BalancerMsg{
Tag: result.Tag,
StrategySettings: result.Strategy.Settings,
Titles: result.Strategy.ValueTitles,
Override: override,
Selects: make([]*OutboundMsg, 0),
Others: make([]*OutboundMsg, 0),
}
for _, item := range result.Strategy.Selects {
stat.Selects = append(stat.Selects, &OutboundMsg{
Tag: item.Tag,
Values: item.Values,
})
}
for _, item := range result.Strategy.Others {
stat.Others = append(stat.Others, &OutboundMsg{
Tag: item.Tag,
Values: item.Values,
})
}
rsp.Balancers = append(rsp.Balancers, stat)
}
return rsp, nil
}
func (s *routingServer) CheckBalancers(ctx context.Context, request *CheckBalancersRequest) (*CheckBalancersResponse, error) {
h, ok := s.router.(routing.RouterChecker)
if !ok {
return nil, status.Errorf(codes.Unavailable, "current router is not a health checker")
}
go func() {
err := h.CheckBalancers(request.BalancerTags)
if err != nil {
newError("CheckBalancers error:", err).AtInfo().WriteToLog()
}
}()
return &CheckBalancersResponse{}, nil
}
func (s *routingServer) OverrideSelecting(ctx context.Context, request *OverrideSelectingRequest) (*OverrideSelectingResponse, error) {
bo, ok := s.router.(routing.BalancingOverrider)
if !ok {
return nil, status.Errorf(codes.Unavailable, "current router doesn't support balancing override")
}
err := bo.OverrideSelecting(
request.BalancerTag,
request.Selectors,
time.Duration(request.Validity),
)
if err != nil {
return nil, status.Errorf(codes.InvalidArgument, err.Error())
}
return &OverrideSelectingResponse{}, nil
}
func (s *routingServer) mustEmbedUnimplementedRoutingServiceServer() {}
type service struct {

View File

@ -293,6 +293,483 @@ func (x *TestRouteRequest) GetPublishResult() bool {
return false
}
type GetBalancersRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
BalancerTags []string `protobuf:"bytes,1,rep,name=balancerTags,proto3" json:"balancerTags,omitempty"`
}
func (x *GetBalancersRequest) Reset() {
*x = GetBalancersRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_app_router_command_command_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *GetBalancersRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GetBalancersRequest) ProtoMessage() {}
func (x *GetBalancersRequest) 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 GetBalancersRequest.ProtoReflect.Descriptor instead.
func (*GetBalancersRequest) Descriptor() ([]byte, []int) {
return file_app_router_command_command_proto_rawDescGZIP(), []int{3}
}
func (x *GetBalancersRequest) GetBalancerTags() []string {
if x != nil {
return x.BalancerTags
}
return nil
}
type OutboundMsg struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Tag string `protobuf:"bytes,1,opt,name=tag,proto3" json:"tag,omitempty"`
Values []string `protobuf:"bytes,2,rep,name=values,proto3" json:"values,omitempty"`
}
func (x *OutboundMsg) Reset() {
*x = OutboundMsg{}
if protoimpl.UnsafeEnabled {
mi := &file_app_router_command_command_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *OutboundMsg) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*OutboundMsg) ProtoMessage() {}
func (x *OutboundMsg) 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 OutboundMsg.ProtoReflect.Descriptor instead.
func (*OutboundMsg) Descriptor() ([]byte, []int) {
return file_app_router_command_command_proto_rawDescGZIP(), []int{4}
}
func (x *OutboundMsg) GetTag() string {
if x != nil {
return x.Tag
}
return ""
}
func (x *OutboundMsg) GetValues() []string {
if x != nil {
return x.Values
}
return nil
}
type OverrideSelectingMsg struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Until string `protobuf:"bytes,1,opt,name=until,proto3" json:"until,omitempty"`
Selects []string `protobuf:"bytes,2,rep,name=selects,proto3" json:"selects,omitempty"`
}
func (x *OverrideSelectingMsg) Reset() {
*x = OverrideSelectingMsg{}
if protoimpl.UnsafeEnabled {
mi := &file_app_router_command_command_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *OverrideSelectingMsg) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*OverrideSelectingMsg) ProtoMessage() {}
func (x *OverrideSelectingMsg) 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 OverrideSelectingMsg.ProtoReflect.Descriptor instead.
func (*OverrideSelectingMsg) Descriptor() ([]byte, []int) {
return file_app_router_command_command_proto_rawDescGZIP(), []int{5}
}
func (x *OverrideSelectingMsg) GetUntil() string {
if x != nil {
return x.Until
}
return ""
}
func (x *OverrideSelectingMsg) GetSelects() []string {
if x != nil {
return x.Selects
}
return nil
}
type BalancerMsg struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Tag string `protobuf:"bytes,1,opt,name=tag,proto3" json:"tag,omitempty"`
StrategySettings []string `protobuf:"bytes,2,rep,name=strategySettings,proto3" json:"strategySettings,omitempty"`
Titles []string `protobuf:"bytes,4,rep,name=titles,proto3" json:"titles,omitempty"`
Override *OverrideSelectingMsg `protobuf:"bytes,5,opt,name=override,proto3" json:"override,omitempty"`
Selects []*OutboundMsg `protobuf:"bytes,6,rep,name=selects,proto3" json:"selects,omitempty"`
Others []*OutboundMsg `protobuf:"bytes,7,rep,name=others,proto3" json:"others,omitempty"`
}
func (x *BalancerMsg) Reset() {
*x = BalancerMsg{}
if protoimpl.UnsafeEnabled {
mi := &file_app_router_command_command_proto_msgTypes[6]
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[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 BalancerMsg.ProtoReflect.Descriptor instead.
func (*BalancerMsg) Descriptor() ([]byte, []int) {
return file_app_router_command_command_proto_rawDescGZIP(), []int{6}
}
func (x *BalancerMsg) GetTag() string {
if x != nil {
return x.Tag
}
return ""
}
func (x *BalancerMsg) GetStrategySettings() []string {
if x != nil {
return x.StrategySettings
}
return nil
}
func (x *BalancerMsg) GetTitles() []string {
if x != nil {
return x.Titles
}
return nil
}
func (x *BalancerMsg) GetOverride() *OverrideSelectingMsg {
if x != nil {
return x.Override
}
return nil
}
func (x *BalancerMsg) GetSelects() []*OutboundMsg {
if x != nil {
return x.Selects
}
return nil
}
func (x *BalancerMsg) GetOthers() []*OutboundMsg {
if x != nil {
return x.Others
}
return nil
}
type GetBalancersResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Balancers []*BalancerMsg `protobuf:"bytes,1,rep,name=balancers,proto3" json:"balancers,omitempty"`
}
func (x *GetBalancersResponse) Reset() {
*x = GetBalancersResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_app_router_command_command_proto_msgTypes[7]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *GetBalancersResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GetBalancersResponse) ProtoMessage() {}
func (x *GetBalancersResponse) 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 GetBalancersResponse.ProtoReflect.Descriptor instead.
func (*GetBalancersResponse) Descriptor() ([]byte, []int) {
return file_app_router_command_command_proto_rawDescGZIP(), []int{7}
}
func (x *GetBalancersResponse) GetBalancers() []*BalancerMsg {
if x != nil {
return x.Balancers
}
return nil
}
type CheckBalancersRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
BalancerTags []string `protobuf:"bytes,1,rep,name=balancerTags,proto3" json:"balancerTags,omitempty"`
}
func (x *CheckBalancersRequest) Reset() {
*x = CheckBalancersRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_app_router_command_command_proto_msgTypes[8]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *CheckBalancersRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*CheckBalancersRequest) ProtoMessage() {}
func (x *CheckBalancersRequest) 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 CheckBalancersRequest.ProtoReflect.Descriptor instead.
func (*CheckBalancersRequest) Descriptor() ([]byte, []int) {
return file_app_router_command_command_proto_rawDescGZIP(), []int{8}
}
func (x *CheckBalancersRequest) GetBalancerTags() []string {
if x != nil {
return x.BalancerTags
}
return nil
}
type CheckBalancersResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *CheckBalancersResponse) Reset() {
*x = CheckBalancersResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_app_router_command_command_proto_msgTypes[9]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *CheckBalancersResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*CheckBalancersResponse) ProtoMessage() {}
func (x *CheckBalancersResponse) 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 CheckBalancersResponse.ProtoReflect.Descriptor instead.
func (*CheckBalancersResponse) Descriptor() ([]byte, []int) {
return file_app_router_command_command_proto_rawDescGZIP(), []int{9}
}
type OverrideSelectingRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
BalancerTag string `protobuf:"bytes,1,opt,name=balancerTag,proto3" json:"balancerTag,omitempty"`
Selectors []string `protobuf:"bytes,2,rep,name=selectors,proto3" json:"selectors,omitempty"`
Validity int64 `protobuf:"varint,3,opt,name=validity,proto3" json:"validity,omitempty"`
}
func (x *OverrideSelectingRequest) Reset() {
*x = OverrideSelectingRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_app_router_command_command_proto_msgTypes[10]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *OverrideSelectingRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*OverrideSelectingRequest) ProtoMessage() {}
func (x *OverrideSelectingRequest) ProtoReflect() protoreflect.Message {
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 {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use OverrideSelectingRequest.ProtoReflect.Descriptor instead.
func (*OverrideSelectingRequest) Descriptor() ([]byte, []int) {
return file_app_router_command_command_proto_rawDescGZIP(), []int{10}
}
func (x *OverrideSelectingRequest) GetBalancerTag() string {
if x != nil {
return x.BalancerTag
}
return ""
}
func (x *OverrideSelectingRequest) GetSelectors() []string {
if x != nil {
return x.Selectors
}
return nil
}
func (x *OverrideSelectingRequest) GetValidity() int64 {
if x != nil {
return x.Validity
}
return 0
}
type OverrideSelectingResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *OverrideSelectingResponse) Reset() {
*x = OverrideSelectingResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_app_router_command_command_proto_msgTypes[11]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *OverrideSelectingResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*OverrideSelectingResponse) ProtoMessage() {}
func (x *OverrideSelectingResponse) ProtoReflect() protoreflect.Message {
mi := &file_app_router_command_command_proto_msgTypes[11]
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 OverrideSelectingResponse.ProtoReflect.Descriptor instead.
func (*OverrideSelectingResponse) Descriptor() ([]byte, []int) {
return file_app_router_command_command_proto_rawDescGZIP(), []int{11}
}
type Config struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
@ -302,7 +779,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[12]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -315,7 +792,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[12]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -328,7 +805,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{12}
}
var File_app_router_command_command_proto protoreflect.FileDescriptor
@ -390,32 +867,110 @@ var file_app_router_command_command_proto_rawDesc = []byte{
0x28, 0x09, 0x52, 0x0e, 0x46, 0x69, 0x65, 0x6c, 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, 0x89, 0x02, 0x0a, 0x0e, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x53, 0x65,
0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x87, 0x01, 0x0a, 0x15, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72,
0x69, 0x62, 0x65, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12,
0x3b, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 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, 0x2d, 0x2e, 0x76,
0x73, 0x68, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22, 0x39, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x42,
0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
0x22, 0x0a, 0x0c, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x72, 0x54, 0x61, 0x67, 0x73, 0x18,
0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x72, 0x54,
0x61, 0x67, 0x73, 0x22, 0x37, 0x0a, 0x0b, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x4d,
0x73, 0x67, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
0x03, 0x74, 0x61, 0x67, 0x12, 0x16, 0x0a, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x02,
0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x22, 0x46, 0x0a, 0x14,
0x4f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6e,
0x67, 0x4d, 0x73, 0x67, 0x12, 0x14, 0x0a, 0x05, 0x75, 0x6e, 0x74, 0x69, 0x6c, 0x18, 0x01, 0x20,
0x01, 0x28, 0x09, 0x52, 0x05, 0x75, 0x6e, 0x74, 0x69, 0x6c, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x65,
0x6c, 0x65, 0x63, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x73, 0x65, 0x6c,
0x65, 0x63, 0x74, 0x73, 0x22, 0xbe, 0x02, 0x0a, 0x0b, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65,
0x72, 0x4d, 0x73, 0x67, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28,
0x09, 0x52, 0x03, 0x74, 0x61, 0x67, 0x12, 0x2a, 0x0a, 0x10, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65,
0x67, 0x79, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09,
0x52, 0x10, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e,
0x67, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03,
0x28, 0x09, 0x52, 0x06, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x73, 0x12, 0x4f, 0x0a, 0x08, 0x6f, 0x76,
0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x33, 0x2e, 0x76,
0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 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,
0x6d, 0x0a, 0x09, 0x54, 0x65, 0x73, 0x74, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x2f, 0x2e, 0x76,
0x75, 0x74, 0x65, 0x72, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x4f, 0x76, 0x65,
0x72, 0x72, 0x69, 0x64, 0x65, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6e, 0x67, 0x4d, 0x73,
0x67, 0x52, 0x08, 0x6f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x12, 0x44, 0x0a, 0x07, 0x73,
0x65, 0x6c, 0x65, 0x63, 0x74, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x76,
0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 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, 0x2d, 0x2e,
0x75, 0x74, 0x65, 0x72, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x4f, 0x75, 0x74,
0x62, 0x6f, 0x75, 0x6e, 0x64, 0x4d, 0x73, 0x67, 0x52, 0x07, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74,
0x73, 0x12, 0x42, 0x0a, 0x06, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28,
0x0b, 0x32, 0x2a, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61,
0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e,
0x64, 0x2e, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x4d, 0x73, 0x67, 0x52, 0x06, 0x6f,
0x74, 0x68, 0x65, 0x72, 0x73, 0x22, 0x60, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61,
0x6e, 0x63, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x48, 0x0a,
0x09, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b,
0x32, 0x2a, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70,
0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64,
0x2e, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x72, 0x4d, 0x73, 0x67, 0x52, 0x09, 0x62, 0x61,
0x6c, 0x61, 0x6e, 0x63, 0x65, 0x72, 0x73, 0x22, 0x3b, 0x0a, 0x15, 0x43, 0x68, 0x65, 0x63, 0x6b,
0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x12, 0x22, 0x0a, 0x0c, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x72, 0x54, 0x61, 0x67, 0x73,
0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x72,
0x54, 0x61, 0x67, 0x73, 0x22, 0x18, 0x0a, 0x16, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x42, 0x61, 0x6c,
0x61, 0x6e, 0x63, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x76,
0x0a, 0x18, 0x4f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74,
0x69, 0x6e, 0x67, 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, 0x1c, 0x0a, 0x09,
0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52,
0x09, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x76, 0x61,
0x6c, 0x69, 0x64, 0x69, 0x74, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x76, 0x61,
0x6c, 0x69, 0x64, 0x69, 0x74, 0x79, 0x22, 0x1b, 0x0a, 0x19, 0x4f, 0x76, 0x65, 0x72, 0x72, 0x69,
0x64, 0x65, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x22, 0x08, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x32, 0x90, 0x05,
0x0a, 0x0e, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65,
0x12, 0x87, 0x01, 0x0a, 0x15, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x52, 0x6f,
0x75, 0x74, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x3b, 0x2e, 0x76, 0x32, 0x72,
0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 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, 0x2d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e,
0x63, 0x6f, 0x72, 0x65, 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, 0x6d, 0x0a, 0x09, 0x54, 0x65,
0x73, 0x74, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x2f, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e,
0x63, 0x6f, 0x72, 0x65, 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, 0x2d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79,
0x2e, 0x63, 0x6f, 0x72, 0x65, 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, 0x79, 0x0a, 0x0c, 0x47, 0x65, 0x74,
0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x72, 0x73, 0x12, 0x32, 0x2e, 0x76, 0x32, 0x72, 0x61,
0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 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, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x33, 0x2e,
0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 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, 0x78,
0x0a, 0x21, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65,
0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x63, 0x6f, 0x6d, 0x6d,
0x61, 0x6e, 0x64, 0x50, 0x01, 0x5a, 0x31, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f,
0x6d, 0x2f, 0x76, 0x32, 0x66, 0x6c, 0x79, 0x2f, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f,
0x72, 0x65, 0x2f, 0x76, 0x34, 0x2f, 0x61, 0x70, 0x70, 0x2f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72,
0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0xaa, 0x02, 0x1d, 0x56, 0x32, 0x52, 0x61, 0x79,
0x2e, 0x43, 0x6f, 0x72, 0x65, 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,
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, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x22, 0x00, 0x12, 0x7f, 0x0a, 0x0e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x42, 0x61, 0x6c,
0x61, 0x6e, 0x63, 0x65, 0x72, 0x73, 0x12, 0x34, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63,
0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x63,
0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x42, 0x61, 0x6c, 0x61,
0x6e, 0x63, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x35, 0x2e, 0x76,
0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f,
0x75, 0x74, 0x65, 0x72, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x43, 0x68, 0x65,
0x63, 0x6b, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x88, 0x01, 0x0a, 0x11, 0x4f, 0x76, 0x65, 0x72, 0x72, 0x69,
0x64, 0x65, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6e, 0x67, 0x12, 0x37, 0x2e, 0x76, 0x32,
0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 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, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x1a, 0x38, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72,
0x65, 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, 0x53, 0x65, 0x6c,
0x65, 0x63, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00,
0x42, 0x78, 0x0a, 0x21, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f,
0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x63, 0x6f,
0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x50, 0x01, 0x5a, 0x31, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e,
0x63, 0x6f, 0x6d, 0x2f, 0x76, 0x32, 0x66, 0x6c, 0x79, 0x2f, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2d,
0x63, 0x6f, 0x72, 0x65, 0x2f, 0x76, 0x34, 0x2f, 0x61, 0x70, 0x70, 0x2f, 0x72, 0x6f, 0x75, 0x74,
0x65, 0x72, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0xaa, 0x02, 0x1d, 0x56, 0x32, 0x52,
0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72, 0x65, 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 (
@ -430,28 +985,47 @@ 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, 14)
var file_app_router_command_command_proto_goTypes = []interface{}{
(*RoutingContext)(nil), // 0: v2ray.core.app.router.command.RoutingContext
(*SubscribeRoutingStatsRequest)(nil), // 1: v2ray.core.app.router.command.SubscribeRoutingStatsRequest
(*TestRouteRequest)(nil), // 2: v2ray.core.app.router.command.TestRouteRequest
(*Config)(nil), // 3: v2ray.core.app.router.command.Config
nil, // 4: v2ray.core.app.router.command.RoutingContext.AttributesEntry
(net.Network)(0), // 5: v2ray.core.common.net.Network
(*GetBalancersRequest)(nil), // 3: v2ray.core.app.router.command.GetBalancersRequest
(*OutboundMsg)(nil), // 4: v2ray.core.app.router.command.OutboundMsg
(*OverrideSelectingMsg)(nil), // 5: v2ray.core.app.router.command.OverrideSelectingMsg
(*BalancerMsg)(nil), // 6: v2ray.core.app.router.command.BalancerMsg
(*GetBalancersResponse)(nil), // 7: v2ray.core.app.router.command.GetBalancersResponse
(*CheckBalancersRequest)(nil), // 8: v2ray.core.app.router.command.CheckBalancersRequest
(*CheckBalancersResponse)(nil), // 9: v2ray.core.app.router.command.CheckBalancersResponse
(*OverrideSelectingRequest)(nil), // 10: v2ray.core.app.router.command.OverrideSelectingRequest
(*OverrideSelectingResponse)(nil), // 11: v2ray.core.app.router.command.OverrideSelectingResponse
(*Config)(nil), // 12: v2ray.core.app.router.command.Config
nil, // 13: v2ray.core.app.router.command.RoutingContext.AttributesEntry
(net.Network)(0), // 14: v2ray.core.common.net.Network
}
var file_app_router_command_command_proto_depIdxs = []int32{
5, // 0: v2ray.core.app.router.command.RoutingContext.Network:type_name -> v2ray.core.common.net.Network
4, // 1: v2ray.core.app.router.command.RoutingContext.Attributes:type_name -> v2ray.core.app.router.command.RoutingContext.AttributesEntry
0, // 2: v2ray.core.app.router.command.TestRouteRequest.RoutingContext:type_name -> v2ray.core.app.router.command.RoutingContext
1, // 3: v2ray.core.app.router.command.RoutingService.SubscribeRoutingStats:input_type -> v2ray.core.app.router.command.SubscribeRoutingStatsRequest
2, // 4: v2ray.core.app.router.command.RoutingService.TestRoute:input_type -> v2ray.core.app.router.command.TestRouteRequest
0, // 5: v2ray.core.app.router.command.RoutingService.SubscribeRoutingStats:output_type -> v2ray.core.app.router.command.RoutingContext
0, // 6: v2ray.core.app.router.command.RoutingService.TestRoute:output_type -> v2ray.core.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
14, // 0: v2ray.core.app.router.command.RoutingContext.Network:type_name -> v2ray.core.common.net.Network
13, // 1: v2ray.core.app.router.command.RoutingContext.Attributes:type_name -> v2ray.core.app.router.command.RoutingContext.AttributesEntry
0, // 2: v2ray.core.app.router.command.TestRouteRequest.RoutingContext:type_name -> v2ray.core.app.router.command.RoutingContext
5, // 3: v2ray.core.app.router.command.BalancerMsg.override:type_name -> v2ray.core.app.router.command.OverrideSelectingMsg
4, // 4: v2ray.core.app.router.command.BalancerMsg.selects:type_name -> v2ray.core.app.router.command.OutboundMsg
4, // 5: v2ray.core.app.router.command.BalancerMsg.others:type_name -> v2ray.core.app.router.command.OutboundMsg
6, // 6: v2ray.core.app.router.command.GetBalancersResponse.balancers:type_name -> v2ray.core.app.router.command.BalancerMsg
1, // 7: v2ray.core.app.router.command.RoutingService.SubscribeRoutingStats:input_type -> v2ray.core.app.router.command.SubscribeRoutingStatsRequest
2, // 8: v2ray.core.app.router.command.RoutingService.TestRoute:input_type -> v2ray.core.app.router.command.TestRouteRequest
3, // 9: v2ray.core.app.router.command.RoutingService.GetBalancers:input_type -> v2ray.core.app.router.command.GetBalancersRequest
8, // 10: v2ray.core.app.router.command.RoutingService.CheckBalancers:input_type -> v2ray.core.app.router.command.CheckBalancersRequest
10, // 11: v2ray.core.app.router.command.RoutingService.OverrideSelecting:input_type -> v2ray.core.app.router.command.OverrideSelectingRequest
0, // 12: v2ray.core.app.router.command.RoutingService.SubscribeRoutingStats:output_type -> v2ray.core.app.router.command.RoutingContext
0, // 13: v2ray.core.app.router.command.RoutingService.TestRoute:output_type -> v2ray.core.app.router.command.RoutingContext
7, // 14: v2ray.core.app.router.command.RoutingService.GetBalancers:output_type -> v2ray.core.app.router.command.GetBalancersResponse
9, // 15: v2ray.core.app.router.command.RoutingService.CheckBalancers:output_type -> v2ray.core.app.router.command.CheckBalancersResponse
11, // 16: v2ray.core.app.router.command.RoutingService.OverrideSelecting:output_type -> v2ray.core.app.router.command.OverrideSelectingResponse
12, // [12:17] is the sub-list for method output_type
7, // [7:12] is the sub-list for method input_type
7, // [7:7] is the sub-list for extension type_name
7, // [7:7] is the sub-list for extension extendee
0, // [0:7] is the sub-list for field type_name
}
func init() { file_app_router_command_command_proto_init() }
@ -497,6 +1071,114 @@ 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.(*GetBalancersRequest); 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.(*OutboundMsg); 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.(*OverrideSelectingMsg); 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.(*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[7].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*GetBalancersResponse); 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.(*CheckBalancersRequest); 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.(*CheckBalancersResponse); 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.(*OverrideSelectingRequest); 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[11].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*OverrideSelectingResponse); 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[12].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Config); i {
case 0:
return &v.state
@ -515,7 +1197,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: 14,
NumExtensions: 0,
NumServices: 1,
},

View File

@ -60,10 +60,55 @@ message TestRouteRequest {
bool PublishResult = 3;
}
message GetBalancersRequest {
repeated string balancerTags = 1;
}
message OutboundMsg {
string tag = 1;
repeated string values = 2;
}
message OverrideSelectingMsg {
string until = 1;
repeated string selects = 2;
}
message BalancerMsg {
string tag = 1;
repeated string strategySettings = 2;
repeated string titles = 4;
OverrideSelectingMsg override = 5;
repeated OutboundMsg selects = 6;
repeated OutboundMsg others = 7;
}
message GetBalancersResponse {
repeated BalancerMsg balancers = 1;
}
message CheckBalancersRequest {
repeated string balancerTags = 1;
}
message CheckBalancersResponse {}
message OverrideSelectingRequest {
string balancerTag = 1;
repeated string selectors = 2;
int64 validity = 3;
}
message OverrideSelectingResponse {}
service RoutingService {
rpc SubscribeRoutingStats(SubscribeRoutingStatsRequest)
returns (stream RoutingContext) {}
rpc TestRoute(TestRouteRequest) returns (RoutingContext) {}
rpc GetBalancers(GetBalancersRequest) returns (GetBalancersResponse) {}
rpc CheckBalancers(CheckBalancersRequest) returns (CheckBalancersResponse) {}
rpc OverrideSelecting(OverrideSelectingRequest) returns (OverrideSelectingResponse) {}
}
message Config {}

View File

@ -20,6 +20,9 @@ const _ = grpc.SupportPackageIsVersion7
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)
GetBalancers(ctx context.Context, in *GetBalancersRequest, opts ...grpc.CallOption) (*GetBalancersResponse, error)
CheckBalancers(ctx context.Context, in *CheckBalancersRequest, opts ...grpc.CallOption) (*CheckBalancersResponse, error)
OverrideSelecting(ctx context.Context, in *OverrideSelectingRequest, opts ...grpc.CallOption) (*OverrideSelectingResponse, error)
}
type routingServiceClient struct {
@ -71,12 +74,42 @@ func (c *routingServiceClient) TestRoute(ctx context.Context, in *TestRouteReque
return out, nil
}
func (c *routingServiceClient) GetBalancers(ctx context.Context, in *GetBalancersRequest, opts ...grpc.CallOption) (*GetBalancersResponse, error) {
out := new(GetBalancersResponse)
err := c.cc.Invoke(ctx, "/v2ray.core.app.router.command.RoutingService/GetBalancers", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *routingServiceClient) CheckBalancers(ctx context.Context, in *CheckBalancersRequest, opts ...grpc.CallOption) (*CheckBalancersResponse, error) {
out := new(CheckBalancersResponse)
err := c.cc.Invoke(ctx, "/v2ray.core.app.router.command.RoutingService/CheckBalancers", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *routingServiceClient) OverrideSelecting(ctx context.Context, in *OverrideSelectingRequest, opts ...grpc.CallOption) (*OverrideSelectingResponse, error) {
out := new(OverrideSelectingResponse)
err := c.cc.Invoke(ctx, "/v2ray.core.app.router.command.RoutingService/OverrideSelecting", 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)
GetBalancers(context.Context, *GetBalancersRequest) (*GetBalancersResponse, error)
CheckBalancers(context.Context, *CheckBalancersRequest) (*CheckBalancersResponse, error)
OverrideSelecting(context.Context, *OverrideSelectingRequest) (*OverrideSelectingResponse, error)
mustEmbedUnimplementedRoutingServiceServer()
}
@ -90,6 +123,15 @@ func (UnimplementedRoutingServiceServer) SubscribeRoutingStats(*SubscribeRouting
func (UnimplementedRoutingServiceServer) TestRoute(context.Context, *TestRouteRequest) (*RoutingContext, error) {
return nil, status.Errorf(codes.Unimplemented, "method TestRoute not implemented")
}
func (UnimplementedRoutingServiceServer) GetBalancers(context.Context, *GetBalancersRequest) (*GetBalancersResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetBalancers not implemented")
}
func (UnimplementedRoutingServiceServer) CheckBalancers(context.Context, *CheckBalancersRequest) (*CheckBalancersResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method CheckBalancers not implemented")
}
func (UnimplementedRoutingServiceServer) OverrideSelecting(context.Context, *OverrideSelectingRequest) (*OverrideSelectingResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method OverrideSelecting not implemented")
}
func (UnimplementedRoutingServiceServer) mustEmbedUnimplementedRoutingServiceServer() {}
// UnsafeRoutingServiceServer may be embedded to opt out of forward compatibility for this service.
@ -142,6 +184,60 @@ func _RoutingService_TestRoute_Handler(srv interface{}, ctx context.Context, dec
return interceptor(ctx, in, info, handler)
}
func _RoutingService_GetBalancers_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetBalancersRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(RoutingServiceServer).GetBalancers(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/v2ray.core.app.router.command.RoutingService/GetBalancers",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(RoutingServiceServer).GetBalancers(ctx, req.(*GetBalancersRequest))
}
return interceptor(ctx, in, info, handler)
}
func _RoutingService_CheckBalancers_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(CheckBalancersRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(RoutingServiceServer).CheckBalancers(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/v2ray.core.app.router.command.RoutingService/CheckBalancers",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(RoutingServiceServer).CheckBalancers(ctx, req.(*CheckBalancersRequest))
}
return interceptor(ctx, in, info, handler)
}
func _RoutingService_OverrideSelecting_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(OverrideSelectingRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(RoutingServiceServer).OverrideSelecting(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/v2ray.core.app.router.command.RoutingService/OverrideSelecting",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(RoutingServiceServer).OverrideSelecting(ctx, req.(*OverrideSelectingRequest))
}
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)
@ -153,6 +249,18 @@ var RoutingService_ServiceDesc = grpc.ServiceDesc{
MethodName: "TestRoute",
Handler: _RoutingService_TestRoute_Handler,
},
{
MethodName: "GetBalancers",
Handler: _RoutingService_GetBalancers_Handler,
},
{
MethodName: "CheckBalancers",
Handler: _RoutingService_CheckBalancers_Handler,
},
{
MethodName: "OverrideSelecting",
Handler: _RoutingService_OverrideSelecting_Handler,
},
},
Streams: []grpc.StreamDesc{
{

View File

@ -248,7 +248,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) {

View File

@ -157,7 +157,8 @@ func (rr *RoutingRule) BuildCondition() (Condition, error) {
return conds, nil
}
func (br *BalancingRule) Build(ohm outbound.Manager) (*Balancer, error) {
// Build builds the balancing rule
func (br *BalancingRule) Build(ohm outbound.Manager, dispatcher routing.Dispatcher) (*Balancer, error) {
switch br.Strategy {
case "leastPing":
return &Balancer{
@ -165,13 +166,28 @@ func (br *BalancingRule) Build(ohm outbound.Manager) (*Balancer, error) {
strategy: &LeastPingStrategy{},
ohm: ohm,
}, nil
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, dispatcher)
return &Balancer{
selectors: br.OutboundSelector,
ohm: ohm, fallbackTag: br.FallbackTag,
strategy: leastLoadStrategy,
}, nil
case "random":
fallthrough
default:
return &Balancer{
selectors: br.OutboundSelector,
strategy: &RandomStrategy{},
ohm: ohm,
ohm: ohm, fallbackTag: br.FallbackTag,
strategy: &RandomStrategy{},
}, nil
}
}

View File

@ -8,6 +8,7 @@ package router
import (
net "github.com/v2fly/v2ray-core/v4/common/net"
serial "github.com/v2fly/v2ray-core/v4/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{11, 0}
}
// Domain for routing decision.
@ -706,9 +707,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() {
@ -764,6 +767,260 @@ 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
HealthCheck *HealthPingConfig `protobuf:"bytes,1,opt,name=healthCheck,proto3" json:"healthCheck,omitempty"`
// 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) GetHealthCheck() *HealthPingConfig {
if x != nil {
return x.HealthCheck
}
return nil
}
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 HealthPingConfig struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// destination url, need 204 for success return
// default http://www.google.com/gen_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"`
// samplingcount 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_router_config_proto_msgTypes[10]
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_router_config_proto_msgTypes[10]
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_router_config_proto_rawDescGZIP(), []int{10}
}
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
}
type Config struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
@ -777,7 +1034,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[11]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -790,7 +1047,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[11]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -803,7 +1060,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{11}
}
func (x *Config) GetDomainStrategy() Config_DomainStrategy {
@ -842,7 +1099,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[12]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -855,7 +1112,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[12]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -921,143 +1178,187 @@ 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, 0x15, 0x76, 0x32, 0x72, 0x61, 0x79,
0x2e, 0x63, 0x6f, 0x72, 0x65, 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, 0xbf, 0x02, 0x0a, 0x06, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x36, 0x0a, 0x04,
0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x22, 0x2e, 0x76, 0x32, 0x72,
0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 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, 0x45, 0x0a, 0x09, 0x61, 0x74,
0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e,
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, 0xbf, 0x02, 0x0a, 0x06, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12,
0x36, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x22, 0x2e,
0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 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, 0x80, 0x01, 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, 0x2f, 0x0a, 0x04, 0x63, 0x69, 0x64, 0x72, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b,
0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 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, 0x3f, 0x0a, 0x09, 0x47, 0x65, 0x6f, 0x49, 0x50, 0x4c,
0x69, 0x73, 0x74, 0x12, 0x32, 0x0a, 0x05, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x18, 0x01, 0x20, 0x03,
0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 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, 0x63, 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, 0x35, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18,
0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f,
0x72, 0x65, 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, 0x43, 0x0a, 0x0b,
0x47, 0x65, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x34, 0x0a, 0x05, 0x65,
0x6e, 0x74, 0x72, 0x79, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x76, 0x32, 0x72,
0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 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, 0xf1, 0x06, 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, 0x35, 0x0a, 0x06,
0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x76,
0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 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, 0x33, 0x0a, 0x04, 0x63, 0x69, 0x64, 0x72, 0x18, 0x03, 0x20, 0x03, 0x28,
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, 0x45, 0x0a,
0x09, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b,
0x32, 0x27, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 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, 0x80, 0x01, 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, 0x2f, 0x0a, 0x04, 0x63, 0x69, 0x64, 0x72, 0x18, 0x02, 0x20, 0x03, 0x28,
0x0b, 0x32, 0x1b, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 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, 0x32, 0x0a, 0x05, 0x67, 0x65, 0x6f, 0x69,
0x70, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e,
0x63, 0x6f, 0x72, 0x65, 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, 0x43, 0x0a, 0x0a,
0x70, 0x6f, 0x72, 0x74, 0x5f, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b,
0x32, 0x20, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 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, 0x3c, 0x0a, 0x09, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x18, 0x0e,
0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72,
0x65, 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,
0x49, 0x0a, 0x0c, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x18,
0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f,
0x72, 0x65, 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, 0x3a, 0x0a, 0x08, 0x6e, 0x65,
0x74, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x18, 0x0d, 0x20, 0x03, 0x28, 0x0e, 0x32, 0x1e, 0x2e, 0x76,
0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 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, 0x40, 0x0a, 0x0b, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65,
0x5f, 0x63, 0x69, 0x64, 0x72, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x76, 0x32,
0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 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, 0x3f, 0x0a, 0x0c, 0x73, 0x6f, 0x75, 0x72,
0x63, 0x65, 0x5f, 0x67, 0x65, 0x6f, 0x69, 0x70, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c,
0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 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, 0x49, 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, 0x1f, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65,
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, 0x3f, 0x0a, 0x09, 0x47, 0x65, 0x6f,
0x49, 0x50, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x32, 0x0a, 0x05, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x18,
0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f,
0x72, 0x65, 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, 0x63, 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, 0x35, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61,
0x69, 0x6e, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79,
0x2e, 0x63, 0x6f, 0x72, 0x65, 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,
0x43, 0x0a, 0x0b, 0x47, 0x65, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x34,
0x0a, 0x05, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e,
0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 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, 0xf1, 0x06, 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,
0x35, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32,
0x1d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 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, 0x33, 0x0a, 0x04, 0x63, 0x69, 0x64, 0x72, 0x18, 0x03,
0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72,
0x65, 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, 0x32, 0x0a, 0x05, 0x67,
0x65, 0x6f, 0x69, 0x70, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x76, 0x32, 0x72,
0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 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,
0x43, 0x0a, 0x0a, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x04, 0x20,
0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65,
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, 0x1e, 0x0a, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x18, 0x0f,
0x20, 0x01, 0x28, 0x09, 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, 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, 0xad, 0x02, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x55, 0x0a, 0x0f,
0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x18,
0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2c, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f,
0x72, 0x65, 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, 0x36, 0x0a, 0x04, 0x72, 0x75, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x03, 0x28,
0x0b, 0x32, 0x22, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 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, 0x4b, 0x0a, 0x0e, 0x62,
0x61, 0x6c, 0x61, 0x6e, 0x63, 0x69, 0x6e, 0x67, 0x5f, 0x72, 0x75, 0x6c, 0x65, 0x18, 0x03, 0x20,
0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65,
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, 0x60, 0x0a, 0x19, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63,
0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x50, 0x01,
0x5a, 0x29, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x76, 0x32, 0x66,
0x6c, 0x79, 0x2f, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x76, 0x34,
0x2f, 0x61, 0x70, 0x70, 0x2f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0xaa, 0x02, 0x15, 0x56, 0x32,
0x52, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72, 0x65, 0x2e, 0x41, 0x70, 0x70, 0x2e, 0x52, 0x6f, 0x75,
0x74, 0x65, 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
0x52, 0x61, 0x6e, 0x67, 0x65, 0x42, 0x02, 0x18, 0x01, 0x52, 0x09, 0x70, 0x6f, 0x72, 0x74, 0x52,
0x61, 0x6e, 0x67, 0x65, 0x12, 0x3c, 0x0a, 0x09, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x6c, 0x69, 0x73,
0x74, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e,
0x63, 0x6f, 0x72, 0x65, 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, 0x49, 0x0a, 0x0c, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x5f, 0x6c, 0x69,
0x73, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79,
0x2e, 0x63, 0x6f, 0x72, 0x65, 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, 0x3a, 0x0a,
0x08, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x18, 0x0d, 0x20, 0x03, 0x28, 0x0e, 0x32,
0x1e, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 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, 0x40, 0x0a, 0x0b, 0x73, 0x6f, 0x75,
0x72, 0x63, 0x65, 0x5f, 0x63, 0x69, 0x64, 0x72, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b,
0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 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, 0x3f, 0x0a, 0x0c, 0x73,
0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x67, 0x65, 0x6f, 0x69, 0x70, 0x18, 0x0b, 0x20, 0x03, 0x28,
0x0b, 0x32, 0x1c, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 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, 0x49, 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, 0x1f, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63,
0x6f, 0x72, 0x65, 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, 0x1e, 0x0a, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65,
0x73, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x09, 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, 0x42, 0x0c, 0x0a, 0x0a, 0x74, 0x61,
0x72, 0x67, 0x65, 0x74, 0x5f, 0x74, 0x61, 0x67, 0x22, 0xe2, 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, 0x53, 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, 0x26, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 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, 0x91, 0x02, 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,
0x49, 0x0a, 0x0b, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x18, 0x01,
0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72,
0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x48, 0x65, 0x61,
0x6c, 0x74, 0x68, 0x50, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0b, 0x68,
0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x12, 0x3b, 0x0a, 0x05, 0x63, 0x6f,
0x73, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x76, 0x32, 0x72, 0x61,
0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 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, 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, 0x22, 0xad,
0x02, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x55, 0x0a, 0x0f, 0x64, 0x6f, 0x6d,
0x61, 0x69, 0x6e, 0x5f, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x18, 0x01, 0x20, 0x01,
0x28, 0x0e, 0x32, 0x2c, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 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, 0x36, 0x0a, 0x04, 0x72, 0x75, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22,
0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 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, 0x4b, 0x0a, 0x0e, 0x62, 0x61, 0x6c, 0x61,
0x6e, 0x63, 0x69, 0x6e, 0x67, 0x5f, 0x72, 0x75, 0x6c, 0x65, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b,
0x32, 0x24, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 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, 0x60,
0x0a, 0x19, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65,
0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x50, 0x01, 0x5a, 0x29, 0x67,
0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x76, 0x32, 0x66, 0x6c, 0x79, 0x2f,
0x76, 0x32, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x76, 0x34, 0x2f, 0x61, 0x70,
0x70, 0x2f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0xaa, 0x02, 0x15, 0x56, 0x32, 0x52, 0x61, 0x79,
0x2e, 0x43, 0x6f, 0x72, 0x65, 0x2e, 0x41, 0x70, 0x70, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x72,
0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
@ -1073,28 +1374,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, 10)
var file_app_router_config_proto_msgTypes = make([]protoimpl.MessageInfo, 13)
var file_app_router_config_proto_goTypes = []interface{}{
(Domain_Type)(0), // 0: v2ray.core.app.router.Domain.Type
(Config_DomainStrategy)(0), // 1: v2ray.core.app.router.Config.DomainStrategy
(*Domain)(nil), // 2: v2ray.core.app.router.Domain
(*CIDR)(nil), // 3: v2ray.core.app.router.CIDR
(*GeoIP)(nil), // 4: v2ray.core.app.router.GeoIP
(*GeoIPList)(nil), // 5: v2ray.core.app.router.GeoIPList
(*GeoSite)(nil), // 6: v2ray.core.app.router.GeoSite
(*GeoSiteList)(nil), // 7: v2ray.core.app.router.GeoSiteList
(*RoutingRule)(nil), // 8: v2ray.core.app.router.RoutingRule
(*BalancingRule)(nil), // 9: v2ray.core.app.router.BalancingRule
(*Config)(nil), // 10: v2ray.core.app.router.Config
(*Domain_Attribute)(nil), // 11: v2ray.core.app.router.Domain.Attribute
(*net.PortRange)(nil), // 12: v2ray.core.common.net.PortRange
(*net.PortList)(nil), // 13: v2ray.core.common.net.PortList
(*net.NetworkList)(nil), // 14: v2ray.core.common.net.NetworkList
(net.Network)(0), // 15: v2ray.core.common.net.Network
(Domain_Type)(0), // 0: v2ray.core.app.router.Domain.Type
(Config_DomainStrategy)(0), // 1: v2ray.core.app.router.Config.DomainStrategy
(*Domain)(nil), // 2: v2ray.core.app.router.Domain
(*CIDR)(nil), // 3: v2ray.core.app.router.CIDR
(*GeoIP)(nil), // 4: v2ray.core.app.router.GeoIP
(*GeoIPList)(nil), // 5: v2ray.core.app.router.GeoIPList
(*GeoSite)(nil), // 6: v2ray.core.app.router.GeoSite
(*GeoSiteList)(nil), // 7: v2ray.core.app.router.GeoSiteList
(*RoutingRule)(nil), // 8: v2ray.core.app.router.RoutingRule
(*BalancingRule)(nil), // 9: v2ray.core.app.router.BalancingRule
(*StrategyWeight)(nil), // 10: v2ray.core.app.router.StrategyWeight
(*StrategyLeastLoadConfig)(nil), // 11: v2ray.core.app.router.StrategyLeastLoadConfig
(*HealthPingConfig)(nil), // 12: v2ray.core.app.router.HealthPingConfig
(*Config)(nil), // 13: v2ray.core.app.router.Config
(*Domain_Attribute)(nil), // 14: v2ray.core.app.router.Domain.Attribute
(*net.PortRange)(nil), // 15: v2ray.core.common.net.PortRange
(*net.PortList)(nil), // 16: v2ray.core.common.net.PortList
(*net.NetworkList)(nil), // 17: v2ray.core.common.net.NetworkList
(net.Network)(0), // 18: v2ray.core.common.net.Network
(*serial.TypedMessage)(nil), // 19: v2ray.core.common.serial.TypedMessage
}
var file_app_router_config_proto_depIdxs = []int32{
0, // 0: v2ray.core.app.router.Domain.type:type_name -> v2ray.core.app.router.Domain.Type
11, // 1: v2ray.core.app.router.Domain.attribute:type_name -> v2ray.core.app.router.Domain.Attribute
14, // 1: v2ray.core.app.router.Domain.attribute:type_name -> v2ray.core.app.router.Domain.Attribute
3, // 2: v2ray.core.app.router.GeoIP.cidr:type_name -> v2ray.core.app.router.CIDR
4, // 3: v2ray.core.app.router.GeoIPList.entry:type_name -> v2ray.core.app.router.GeoIP
2, // 4: v2ray.core.app.router.GeoSite.domain:type_name -> v2ray.core.app.router.Domain
@ -1102,21 +1407,24 @@ var file_app_router_config_proto_depIdxs = []int32{
2, // 6: v2ray.core.app.router.RoutingRule.domain:type_name -> v2ray.core.app.router.Domain
3, // 7: v2ray.core.app.router.RoutingRule.cidr:type_name -> v2ray.core.app.router.CIDR
4, // 8: v2ray.core.app.router.RoutingRule.geoip:type_name -> v2ray.core.app.router.GeoIP
12, // 9: v2ray.core.app.router.RoutingRule.port_range:type_name -> v2ray.core.common.net.PortRange
13, // 10: v2ray.core.app.router.RoutingRule.port_list:type_name -> v2ray.core.common.net.PortList
14, // 11: v2ray.core.app.router.RoutingRule.network_list:type_name -> v2ray.core.common.net.NetworkList
15, // 12: v2ray.core.app.router.RoutingRule.networks:type_name -> v2ray.core.common.net.Network
15, // 9: v2ray.core.app.router.RoutingRule.port_range:type_name -> v2ray.core.common.net.PortRange
16, // 10: v2ray.core.app.router.RoutingRule.port_list:type_name -> v2ray.core.common.net.PortList
17, // 11: v2ray.core.app.router.RoutingRule.network_list:type_name -> v2ray.core.common.net.NetworkList
18, // 12: v2ray.core.app.router.RoutingRule.networks:type_name -> v2ray.core.common.net.Network
3, // 13: v2ray.core.app.router.RoutingRule.source_cidr:type_name -> v2ray.core.app.router.CIDR
4, // 14: v2ray.core.app.router.RoutingRule.source_geoip:type_name -> v2ray.core.app.router.GeoIP
13, // 15: v2ray.core.app.router.RoutingRule.source_port_list:type_name -> v2ray.core.common.net.PortList
1, // 16: v2ray.core.app.router.Config.domain_strategy:type_name -> v2ray.core.app.router.Config.DomainStrategy
8, // 17: v2ray.core.app.router.Config.rule:type_name -> v2ray.core.app.router.RoutingRule
9, // 18: v2ray.core.app.router.Config.balancing_rule:type_name -> v2ray.core.app.router.BalancingRule
19, // [19:19] is the sub-list for method output_type
19, // [19:19] is the sub-list for method input_type
19, // [19:19] is the sub-list for extension type_name
19, // [19:19] is the sub-list for extension extendee
0, // [0:19] is the sub-list for field type_name
16, // 15: v2ray.core.app.router.RoutingRule.source_port_list:type_name -> v2ray.core.common.net.PortList
19, // 16: v2ray.core.app.router.BalancingRule.strategy_settings:type_name -> v2ray.core.common.serial.TypedMessage
12, // 17: v2ray.core.app.router.StrategyLeastLoadConfig.healthCheck:type_name -> v2ray.core.app.router.HealthPingConfig
10, // 18: v2ray.core.app.router.StrategyLeastLoadConfig.costs:type_name -> v2ray.core.app.router.StrategyWeight
1, // 19: v2ray.core.app.router.Config.domain_strategy:type_name -> v2ray.core.app.router.Config.DomainStrategy
8, // 20: v2ray.core.app.router.Config.rule:type_name -> v2ray.core.app.router.RoutingRule
9, // 21: v2ray.core.app.router.Config.balancing_rule:type_name -> v2ray.core.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() }
@ -1222,7 +1530,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:
@ -1234,6 +1542,42 @@ 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.(*HealthPingConfig); 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.(*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[12].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Domain_Attribute); i {
case 0:
return &v.state
@ -1250,7 +1594,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[12].OneofWrappers = []interface{}{
(*Domain_Attribute_BoolValue)(nil),
(*Domain_Attribute_IntValue)(nil),
}
@ -1260,7 +1604,7 @@ func file_app_router_config_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_app_router_config_proto_rawDesc,
NumEnums: 2,
NumMessages: 10,
NumMessages: 13,
NumExtensions: 0,
NumServices: 0,
},

View File

@ -6,6 +6,7 @@ option go_package = "github.com/v2fly/v2ray-core/v4/app/router";
option java_package = "com.v2ray.core.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,42 @@ message BalancingRule {
string tag = 1;
repeated string outbound_selector = 2;
string strategy = 3;
v2ray.core.common.serial.TypedMessage strategy_settings = 4;
string fallback_tag = 5;
}
message StrategyWeight {
bool regexp = 1;
string match = 2;
float value =3;
}
message StrategyLeastLoadConfig {
HealthPingConfig healthCheck = 1;
// 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 HealthPingConfig {
// destination url, need 204 for success return
// default http://www.google.com/gen_204
string destination = 1;
// connectivity check url
string connectivity = 2;
// health check interval, int64 values of time.Duration
int64 interval = 3;
// samplingcount 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;
}
message Config {

231
app/router/healthping.go Normal file
View File

@ -0,0 +1,231 @@
package router
import (
"fmt"
"strings"
sync "sync"
"time"
"github.com/v2fly/v2ray-core/v4/common/dice"
"github.com/v2fly/v2ray-core/v4/features/routing"
)
// 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 {
access sync.Mutex
ticker *time.Ticker
dispatcher routing.Dispatcher
Settings *HealthPingSettings
Results map[string]*HealthPingRTTS
}
// NewHealthPing creates a new HealthPing with settings
func NewHealthPing(config *HealthPingConfig, dispatcher routing.Dispatcher) *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 == "" {
settings.Destination = "http://www.google.com/gen_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{
dispatcher: dispatcher,
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)
h.ticker = ticker
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)
}()
_, ok := <-ticker.C
if !ok {
break
}
}
}()
}
// StopScheduler implements the HealthChecker
func (h *HealthPing) StopScheduler() {
h.ticker.Stop()
h.ticker = 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)
// rtts := make(map[string][]time.Duration)
for _, tag := range tags {
handler := tag
client := newPingClient(
h.Settings.Destination,
h.Settings.Timeout,
handler,
h.dispatcher,
)
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
}

View File

@ -0,0 +1,143 @@
package router
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
}

View File

@ -0,0 +1,106 @@
package router_test
import (
"math"
reflect "reflect"
"testing"
"time"
"github.com/v2fly/v2ray-core/v4/app/router"
)
func TestHealthPingResults(t *testing.T) {
rtts := []int64{60, 140, 60, 140, 60, 60, 140, 60, 140}
hr := router.NewHealthPingResult(4, time.Hour)
for _, rtt := range rtts {
hr.Put(time.Duration(rtt))
}
rttFailed := time.Duration(math.MaxInt64)
expected := &router.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 = &router.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 := router.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 := &router.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 = &router.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 = &router.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)
}
}

81
app/router/ping.go Normal file
View File

@ -0,0 +1,81 @@
package router
import (
"context"
"net/http"
"time"
"github.com/v2fly/v2ray-core/v4/common/net"
"github.com/v2fly/v2ray-core/v4/common/session"
"github.com/v2fly/v2ray-core/v4/features/routing"
)
type pingClient struct {
destination string
httpClient *http.Client
}
func newPingClient(destination string, timeout time.Duration, handler string, dispatcher routing.Dispatcher) *pingClient {
return &pingClient{
destination: destination,
httpClient: newHTTPClient(handler, dispatcher, timeout),
}
}
func newDirectPingClient(destination string, timeout time.Duration) *pingClient {
return &pingClient{
destination: destination,
httpClient: &http.Client{Timeout: timeout},
}
}
func newHTTPClient(handler string, dispatcher routing.Dispatcher, 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
}
h := &session.Handler{
Tag: handler,
}
ctx = session.ContextWithHandler(ctx, h)
link, err := dispatcher.Dispatch(ctx, dest)
if err != nil {
return nil, err
}
return net.NewConnection(
net.ConnectionInputMulti(link.Writer),
net.ConnectionOutputMulti(link.Reader),
), nil
},
}
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
}

View File

@ -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,26 @@ func (r *Router) pickRouteInternal(ctx routing.Context) (*Rule, routing.Context,
}
// Start implements common.Runnable.
func (*Router) Start() error {
func (r *Router) Start() error {
for _, b := range r.balancers {
checker, ok := b.strategy.(routing.HealthChecker)
if !ok {
continue
}
checker.StartScheduler(b.SelectOutbounds)
}
return nil
}
// Close implements common.Closable.
func (*Router) Close() error {
func (r *Router) Close() error {
for _, b := range r.balancers {
checker, ok := b.strategy.(routing.HealthChecker)
if !ok {
continue
}
checker.StopScheduler()
}
return nil
}
@ -140,8 +154,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
}

118
app/router/router_health.go Normal file
View File

@ -0,0 +1,118 @@
package router
import (
"errors"
"strings"
"github.com/v2fly/v2ray-core/v4/features/routing"
)
// CheckHanlders implements routing.RouterChecker.
func (r *Router) CheckHanlders(tags []string) error {
errs := make([]error, 0)
for _, b := range r.balancers {
checker, ok := b.strategy.(routing.HealthChecker)
if !ok {
continue
}
all, err := b.SelectOutbounds()
if err != nil {
return err
}
ts := getCheckTags(tags, all)
err = checker.Check(ts)
if err != nil {
errs = append(errs, err)
}
}
if len(errs) == 0 {
return nil
}
return getCollectError(errs)
}
func getCheckTags(tags, all []string) []string {
ts := make([]string, 0)
for _, t := range tags {
if findSliceIndex(all, t) >= 0 && findSliceIndex(ts, t) < 0 {
ts = append(ts, t)
}
}
return ts
}
// CheckBalancers implements routing.RouterChecker.
func (r *Router) CheckBalancers(tags []string) error {
errs := make([]error, 0)
for t, b := range r.balancers {
if len(tags) > 0 && findSliceIndex(tags, t) < 0 {
continue
}
checker, ok := b.strategy.(routing.HealthChecker)
if !ok {
continue
}
tags, err := b.SelectOutbounds()
if err != nil {
errs = append(errs, err)
}
err = checker.Check(tags)
if err != nil {
errs = append(errs, err)
}
}
if len(errs) == 0 {
return nil
}
return getCollectError(errs)
}
func getCollectError(errs []error) error {
sb := new(strings.Builder)
sb.WriteString("collect errors:\n")
for _, err := range errs {
sb.WriteString(" * ")
sb.WriteString(err.Error())
sb.WriteString("\n")
}
return errors.New(sb.String())
}
// GetBalancersInfo implements routing.RouterChecker.
func (r *Router) GetBalancersInfo(tags []string) (resp []*routing.BalancerInfo, err error) {
resp = make([]*routing.BalancerInfo, 0)
for t, b := range r.balancers {
if len(tags) > 0 && findSliceIndex(tags, t) < 0 {
continue
}
all, err := b.SelectOutbounds()
if err != nil {
return nil, err
}
var override *routing.BalancingOverrideInfo
if o := b.override.Get(); o != nil {
override = &routing.BalancingOverrideInfo{
Until: o.until,
Selects: o.selects,
}
}
stat := &routing.BalancerInfo{
Tag: t,
Override: override,
Strategy: b.strategy.GetInformation(all),
}
resp = append(resp, stat)
}
return resp, nil
}
func findSliceIndex(slice []string, find string) int {
index := -1
for i, v := range slice {
if find == v {
index = i
break
}
}
return index
}

View File

@ -9,6 +9,7 @@ import (
. "github.com/v2fly/v2ray-core/v4/app/router"
"github.com/v2fly/v2ray-core/v4/common"
"github.com/v2fly/v2ray-core/v4/common/net"
serial "github.com/v2fly/v2ray-core/v4/common/serial"
"github.com/v2fly/v2ray-core/v4/common/session"
"github.com/v2fly/v2ray-core/v4/features/outbound"
routing_session "github.com/v2fly/v2ray-core/v4/features/routing/session"
@ -43,7 +44,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("v2fly.org"), 80)})
route, err := r.PickRoute(routing_session.AsRoutingContext(ctx))
@ -84,7 +85,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("v2fly.org"), 80)})
route, err := r.PickRoute(routing_session.AsRoutingContext(ctx))
@ -94,6 +95,52 @@ func TestSimpleBalancer(t *testing.T) {
}
}
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{
HealthCheck: nil,
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,
@ -119,7 +166,7 @@ func TestIPOnDemand(t *testing.T) {
mockDNS.EXPECT().LookupIP(gomock.Eq("v2fly.org")).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("v2fly.org"), 80)})
route, err := r.PickRoute(routing_session.AsRoutingContext(ctx))
@ -154,7 +201,7 @@ func TestIPIfNonMatchDomain(t *testing.T) {
mockDNS.EXPECT().LookupIP(gomock.Eq("v2fly.org")).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("v2fly.org"), 80)})
route, err := r.PickRoute(routing_session.AsRoutingContext(ctx))
@ -188,7 +235,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))

View File

@ -0,0 +1,322 @@
package router
import (
"fmt"
"math"
"sort"
"strings"
"time"
"github.com/v2fly/v2ray-core/v4/common/dice"
"github.com/v2fly/v2ray-core/v4/features/routing"
)
const (
rttFailed = time.Duration(math.MaxInt64 - iota)
rttUntested
rttUnqualified
)
// LeastLoadStrategy represents a random balancing strategy
type LeastLoadStrategy struct {
*HealthPing
settings *StrategyLeastLoadConfig
costs *WeightManager
}
// NewLeastLoadStrategy creates a new LeastLoadStrategy with settings
func NewLeastLoadStrategy(settings *StrategyLeastLoadConfig, dispatcher routing.Dispatcher) *LeastLoadStrategy {
return &LeastLoadStrategy{
HealthPing: NewHealthPing(settings.HealthCheck, dispatcher),
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
applied time.Duration
}
// GetInformation implements the routing.BalancingStrategy.
func (s *LeastLoadStrategy) GetInformation(tags []string) *routing.StrategyInfo {
qualified, others := s.getNodes(tags, time.Duration(s.settings.MaxRTT))
selects := s.selectLeastLoad(qualified)
// append qualified but not selected outbounds to others
others = append(others, qualified[len(selects):]...)
leastloadSort(others)
titles, sl := s.getNodesInfo(selects)
_, ot := s.getNodesInfo(others)
return &routing.StrategyInfo{
Settings: s.getSettings(),
ValueTitles: titles,
Selects: sl,
Others: ot,
}
}
// SelectAndPick implements the routing.BalancingStrategy.
func (s *LeastLoadStrategy) SelectAndPick(candidates []string) string {
qualified, _ := s.getNodes(candidates, time.Duration(s.settings.MaxRTT))
selects := s.selectLeastLoad(qualified)
count := len(selects)
if count == 0 {
// goes to fallbackTag
return ""
}
return selects[dice.Roll(count)].Tag
}
// Pick implements the routing.BalancingStrategy.
func (s *LeastLoadStrategy) Pick(candidates []string) string {
count := len(candidates)
if count == 0 {
// goes to fallbackTag
return ""
}
return candidates[dice.Roll(count)]
}
// selectLeastLoad selects nodes according to Baselines and Expected Count.
//
// The strategy always improves network response speed, not matter which mode below is configurated.
// 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 := 0; i < availableCount; i++ {
if nodes[i].applied > 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, []*node) {
s.access.Lock()
defer s.access.Unlock()
results := s.Results
qualified := make([]*node, 0)
unqualified := make([]*node, 0)
failed := make([]*node, 0)
untested := make([]*node, 0)
others := make([]*node, 0)
for _, tag := range candidates {
r, ok := results[tag]
if !ok {
untested = append(untested, &node{
Tag: tag,
RTTDeviationCost: 0,
RTTDeviation: 0,
RTTAverage: 0,
applied: rttUntested,
})
continue
}
stats := r.Get()
node := &node{
Tag: tag,
RTTDeviationCost: time.Duration(s.costs.Apply(tag, float64(stats.Deviation))),
RTTDeviation: stats.Deviation,
RTTAverage: stats.Average,
CountAll: stats.All,
CountFail: stats.Fail,
}
switch {
case stats.All == 0:
node.applied = rttUntested
untested = append(untested, node)
case maxRTT > 0 && stats.Average > maxRTT:
node.applied = rttUnqualified
unqualified = append(unqualified, node)
case float64(stats.Fail)/float64(stats.All) > float64(s.settings.Tolerance):
node.applied = rttFailed
if stats.All-stats.Fail == 0 {
// no good, put them after has-good nodes
node.RTTDeviationCost = rttFailed
node.RTTDeviation = rttFailed
node.RTTAverage = rttFailed
}
failed = append(failed, node)
default:
node.applied = node.RTTDeviationCost
qualified = append(qualified, node)
}
}
if len(qualified) > 0 {
leastloadSort(qualified)
others = append(others, unqualified...)
others = append(others, untested...)
others = append(others, failed...)
} else {
qualified = untested
others = append(others, unqualified...)
others = append(others, failed...)
}
return qualified, others
}
func (s *LeastLoadStrategy) getSettings() []string {
settings := make([]string, 0)
sb := new(strings.Builder)
for i, b := range s.settings.Baselines {
if i > 0 {
sb.WriteByte(' ')
}
sb.WriteString(time.Duration(b).String())
}
baselines := sb.String()
if baselines == "" {
baselines = "none"
}
maxRTT := time.Duration(s.settings.MaxRTT).String()
if s.settings.MaxRTT == 0 {
maxRTT = "none"
}
settings = append(settings, fmt.Sprintf(
"leastload, expected: %d, baselines: %s, max rtt: %s, tolerance: %.2f",
s.settings.Expected,
baselines,
maxRTT,
s.settings.Tolerance,
))
settings = append(settings, fmt.Sprintf(
"health ping, interval: %s, sampling: %d, timeout: %s, destination: %s",
s.HealthPing.Settings.Interval,
s.HealthPing.Settings.SamplingCount,
s.HealthPing.Settings.Timeout,
s.HealthPing.Settings.Destination,
))
return settings
}
func (s *LeastLoadStrategy) getNodesInfo(nodes []*node) ([]string, []*routing.OutboundInfo) {
titles := []string{" ", "RTT STD+C ", "RTT STD. ", "RTT Avg. ", "Hit ", "Cost "}
hasCost := len(s.settings.Costs) > 0
if !hasCost {
titles = []string{" ", "RTT STD. ", "RTT Avg. ", "Hit "}
}
items := make([]*routing.OutboundInfo, 0)
for _, node := range nodes {
item := &routing.OutboundInfo{
Tag: node.Tag,
}
var status string
cost := fmt.Sprintf("%.2f", s.costs.Get(node.Tag))
switch node.applied {
case rttFailed:
status = "x"
case rttUntested:
status = "?"
case rttUnqualified:
status = ">"
default:
status = "OK"
}
if hasCost {
item.Values = []string{
status,
durationString(node.RTTDeviationCost),
durationString(node.RTTDeviation),
durationString(node.RTTAverage),
fmt.Sprintf("%d/%d", node.CountAll-node.CountFail, node.CountAll),
cost,
}
} else {
item.Values = []string{
status,
durationString(node.RTTDeviation),
durationString(node.RTTAverage),
fmt.Sprintf("%d/%d", node.CountAll-node.CountFail, node.CountAll),
}
}
items = append(items, item)
}
return titles, items
}
func durationString(d time.Duration) string {
if d <= 0 || d > time.Hour {
return "-"
}
return d.String()
}
func leastloadSort(nodes []*node) {
sort.Slice(nodes, func(i, j int) bool {
left := nodes[i]
right := nodes[j]
if left.applied != right.applied {
return left.applied < right.applied
}
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
})
}

View File

@ -0,0 +1,177 @@
package router
import (
"testing"
"time"
)
func TestSelectLeastLoad(t *testing.T) {
settings := &StrategyLeastLoadConfig{
HealthCheck: &HealthPingConfig{
SamplingCount: 10,
},
Expected: 1,
MaxRTT: int64(time.Millisecond * time.Duration(800)),
}
strategy := NewLeastLoadStrategy(settings, nil)
// 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", applied: 100},
{Tag: "b", applied: 200},
{Tag: "c", applied: 300},
{Tag: "d", applied: 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", applied: 100},
{Tag: "b", applied: 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", applied: 100},
{Tag: "b", applied: 200},
{Tag: "c", applied: 250},
{Tag: "d", applied: 300},
{Tag: "e", applied: 310},
}
expected := 4
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", applied: 500},
{Tag: "b", applied: 600},
{Tag: "c", applied: 700},
{Tag: "d", applied: 800},
{Tag: "e", applied: 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", applied: 100},
{Tag: "b", applied: 200},
{Tag: "c", applied: 300},
}
expected := 2
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", applied: 800},
{Tag: "b", applied: 1000},
}
expected := 0
ns := strategy.selectLeastLoad(nodes)
if len(ns) != expected {
t.Errorf("expected: %v, actual: %v", expected, len(ns))
}
}

View File

@ -5,6 +5,7 @@ package router
import (
"context"
"github.com/v2fly/v2ray-core/v4/features/routing"
core "github.com/v2fly/v2ray-core/v4"
"github.com/v2fly/v2ray-core/v4/app/observatory"
@ -17,6 +18,20 @@ type LeastPingStrategy struct {
observatory extension.Observatory
}
// TODO Fix PlaceHolder
func (l *LeastPingStrategy) Pick(candidates []string) string {
panic("implement me")
}
func (l *LeastPingStrategy) SelectAndPick(candidates []string) string {
panic("implement me")
}
func (l *LeastPingStrategy) GetInformation(tags []string) *routing.StrategyInfo {
panic("implement me")
}
func (l *LeastPingStrategy) InjectContext(ctx context.Context) {
l.ctx = ctx
}

View File

@ -0,0 +1,38 @@
package router
import (
"github.com/v2fly/v2ray-core/v4/common/dice"
"github.com/v2fly/v2ray-core/v4/features/routing"
)
// RandomStrategy represents a random balancing strategy
type RandomStrategy struct{}
// GetInformation implements the routing.BalancingStrategy.
func (s *RandomStrategy) GetInformation(tags []string) *routing.StrategyInfo {
items := make([]*routing.OutboundInfo, 0)
for _, tag := range tags {
items = append(items, &routing.OutboundInfo{Tag: tag})
}
return &routing.StrategyInfo{
Settings: []string{"random"},
ValueTitles: nil,
Selects: items,
Others: nil,
}
}
// SelectAndPick implements the routing.BalancingStrategy.
func (s *RandomStrategy) SelectAndPick(candidates []string) string {
return s.Pick(candidates)
}
// Pick implements the routing.BalancingStrategy.
func (s *RandomStrategy) Pick(candidates []string) string {
count := len(candidates)
if count == 0 {
// goes to fallbackTag
return ""
}
return candidates[dice.Roll(count)]
}

85
app/router/weight.go Normal file
View File

@ -0,0 +1,85 @@
package router
import (
"regexp"
"strconv"
"strings"
)
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
}
// Get get the weight of specified tag
func (s *WeightManager) Get(tag string) float64 {
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)
}

60
app/router/weight_test.go Normal file
View File

@ -0,0 +1,60 @@
package router_test
import (
"reflect"
"testing"
"github.com/v2fly/v2ray-core/v4/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)
}
}

View File

@ -14,6 +14,7 @@ const (
muxPreferedSessionKey
sockoptSessionKey
trackedConnectionErrorKey
handlerSessionKey
)
// ContextWithID returns a new context with the given ID.
@ -132,3 +133,16 @@ func SubmitOutboundErrorToOriginator(ctx context.Context, err error) {
func TrackedConnectionError(ctx context.Context, tracker TrackedRequestErrorFeedback) context.Context {
return context.WithValue(ctx, trackedConnectionErrorKey, tracker)
}
// ContextWithHandler returns a new context with handler
func ContextWithHandler(ctx context.Context, handler *Handler) context.Context {
return context.WithValue(ctx, handlerSessionKey, handler)
}
// HandlerFromContext returns handler config in this context, or nil if not
func HandlerFromContext(ctx context.Context) *Handler {
if handler, ok := ctx.Value(handlerSessionKey).(*Handler); ok {
return handler
}
return nil
}

View File

@ -78,6 +78,12 @@ type Sockopt struct {
Mark int32
}
// Handler is the handler setting for dispatching.
type Handler struct {
// Tag of outbound handler.
Tag string
}
// SetAttribute attachs additional string attributes to content.
func (c *Content) SetAttribute(name string, value string) {
if c.Attributes == nil {

View File

@ -0,0 +1,51 @@
package routing
import "time"
// HealthChecker is the interface for health checkers
type HealthChecker interface {
// StartScheduler starts the check scheduler
StartScheduler(selector func() ([]string, error))
// StopScheduler stops the check scheduler
StopScheduler()
// Check start the health checking for given tags.
Check(tags []string) error
}
// OutboundInfo holds information of an outbound
type OutboundInfo struct {
Tag string // Tag of the outbound
Values []string // Information of the outbound, which can be different between strategies, like health ping RTT
}
// StrategyInfo holds strategy running information, like selected handlers and others
type StrategyInfo struct {
Settings []string // Strategy settings
ValueTitles []string // Value titles of OutboundInfo.Values
Selects []*OutboundInfo // Selects of the strategy
Others []*OutboundInfo // Other outbounds
}
// BalancingOverrideInfo holds balancing overridden information
type BalancingOverrideInfo struct {
Until time.Time
Selects []string
}
// BalancerInfo holds information of a balancer
type BalancerInfo struct {
Tag string // Tag of the balancer
Override *BalancingOverrideInfo
Strategy *StrategyInfo // Strategy and its running information
}
// RouterChecker represents a router that is able to perform checks for its balancers, and get statistics.
type RouterChecker interface {
// CheckHanlders performs a health check for specified outbound hanlders.
CheckHanlders(tags []string) error
// CheckBalancers performs health checks for specified balancers,
// if not specified, check them all.
CheckBalancers(tags []string) error
// GetBalancersInfo get health info of specific balancer, if balancer not specified, get all
GetBalancersInfo(tags []string) ([]*BalancerInfo, error)
}

View File

@ -0,0 +1,22 @@
package routing
import "time"
// BalancingStrategy is the interface for balancing strategies
type BalancingStrategy interface {
// Pick pick one outbound from candidates. Unlike the SelectAndPick(),
// it skips the select procedure (select all & pick one).
Pick(candidates []string) string
// SelectAndPick selects qualified nodes from candidates then pick one.
SelectAndPick(candidates []string) string
// GetInformation gets information of the strategy
GetInformation(tags []string) *StrategyInfo
}
// BalancingOverrider is the interface of those who can override
// the selecting of its balancers
type BalancingOverrider interface {
// OverrideSelecting overrides the selects of specified balancer, for 'validity'
// duration of time.
OverrideSelecting(balancer string, selects []string, validity time.Duration) error
}

2
go.sum
View File

@ -238,6 +238,8 @@ github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je4
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pelletier/go-toml v1.8.1 h1:1Nf83orprkJyknT6h7zbuEGUEjcyVlCxSUGTENmNCRM=
github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 h1:JhzVVoYvbOACxoUmOs6V/G4D5nPVUW73rKvXxP4XUJc=
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE=
github.com/pires/go-proxyproto v0.6.0 h1:cLJUPnuQdiNf7P/wbeOKmM1khVdaMgTFDLj8h9ZrVYk=

View File

@ -10,6 +10,7 @@ import (
loggerservice "github.com/v2fly/v2ray-core/v4/app/log/command"
observatoryservice "github.com/v2fly/v2ray-core/v4/app/observatory/command"
handlerservice "github.com/v2fly/v2ray-core/v4/app/proxyman/command"
routerservice "github.com/v2fly/v2ray-core/v4/app/router/command"
statsservice "github.com/v2fly/v2ray-core/v4/app/stats/command"
"github.com/v2fly/v2ray-core/v4/common/serial"
)
@ -37,6 +38,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{}))
default:
if !strings.HasPrefix(s, "#") {
continue

View File

@ -3,10 +3,12 @@ package conf
import (
"context"
"encoding/json"
"github.com/golang/protobuf/proto"
"strings"
"github.com/v2fly/v2ray-core/v4/app/router"
"github.com/v2fly/v2ray-core/v4/common/platform"
"github.com/v2fly/v2ray-core/v4/common/serial"
"github.com/v2fly/v2ray-core/v4/infra/conf/cfgcommon"
"github.com/v2fly/v2ray-core/v4/infra/conf/geodata"
rule2 "github.com/v2fly/v2ray-core/v4/infra/conf/rule"
@ -24,11 +26,13 @@ type StrategyConfig struct {
}
type BalancingRule struct {
Tag string `json:"tag"`
Selectors cfgcommon.StringList `json:"selector"`
Strategy StrategyConfig `json:"strategy"`
Tag string `json:"tag"`
Selectors cfgcommon.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")
@ -40,17 +44,38 @@ func (r *BalancingRule) Build() (*router.BalancingRule, error) {
var strategy string
switch strings.ToLower(r.Strategy.Type) {
case strategyRandom, "":
r.Strategy.Type = strategyRandom
strategy = strategyRandom
case strategyLeastLoad:
strategy = strategyLeastLoad
case strategyLeastPing:
strategy = "leastPing"
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{
Tag: r.Tag,
OutboundSelector: []string(r.Selectors),
Strategy: strategy,
StrategySettings: serial.ToTypedMessage(ts),
FallbackTag: r.FallbackTag,
OutboundSelector: r.Selectors,
Tag: r.Tag,
}, nil
}

View File

@ -1,6 +1,85 @@
package conf
import (
"time"
"github.com/golang/protobuf/proto"
"github.com/v2fly/v2ray-core/v4/app/router"
)
const (
strategyRandom string = "random"
strategyLeastLoad string = "leastload"
strategyLeastPing string = "leastping"
)
var (
strategyConfigLoader = NewJSONConfigLoader(ConfigCreatorCache{
strategyRandom: 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 {
// note the time values of the HealthCheck holds is not
// 'time.Duration' but plain number, sice they were parsed
// directly from json
HealthCheck *router.HealthPingSettings `json:"healthCheck,omitempty"`
// weight settings
Costs []*router.StrategyWeight `json:"costs,omitempty"`
// ping rtt baselines (ms)
Baselines []int `json:"baselines,omitempty"`
// expected nodes count to select
Expected int32 `json:"expected,omitempty"`
// max acceptable rtt (ms), filter away high delay nodes. defalut 0
MaxRTT int `json:"maxRTT,omitempty"`
// acceptable failure rate
Tolerance float64 `json:"tolerance,omitempty"`
}
// Build implements Buildable.
func (v *strategyLeastLoadConfig) Build() (proto.Message, error) {
config := &router.StrategyLeastLoadConfig{
HealthCheck: &router.HealthPingConfig{},
}
if v.HealthCheck != nil {
config.HealthCheck = &router.HealthPingConfig{
Destination: v.HealthCheck.Destination,
Connectivity: v.HealthCheck.Connectivity,
Interval: int64(v.HealthCheck.Interval * time.Second),
Timeout: int64(v.HealthCheck.Timeout * time.Second),
SamplingCount: int32(v.HealthCheck.SamplingCount),
}
}
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(time.Duration(v.MaxRTT) * time.Millisecond)
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(time.Duration(b)*time.Millisecond))
}
return config, nil
}

View File

@ -3,12 +3,14 @@ package conf_test
import (
"encoding/json"
"testing"
"time"
_ "unsafe"
"github.com/golang/protobuf/proto"
"github.com/v2fly/v2ray-core/v4/app/router"
"github.com/v2fly/v2ray-core/v4/common/net"
"github.com/v2fly/v2ray-core/v4/common/serial"
. "github.com/v2fly/v2ray-core/v4/infra/conf"
)
@ -68,6 +70,34 @@ func TestRouterConfig(t *testing.T) {
{
"tag": "b1",
"selector": ["test"]
},
{
"tag": "b2",
"selector": ["test"],
"strategy": {
"type": "leastload",
"settings": {
"healthCheck": {
"interval": 300,
"sampling": 2,
"timeout": 3,
"destination": "dest",
"connectivity": "conn"
},
"costs": [
{
"regexp": true,
"match": "\\d+(\\.\\d+)",
"value": 5
}
],
"baselines": [400, 600],
"expected": 6,
"maxRTT": 1000,
"tolerance": 0.5
}
},
"fallbackTag": "fall"
}
]
}`,
@ -80,6 +110,35 @@ func TestRouterConfig(t *testing.T) {
OutboundSelector: []string{"test"},
Strategy: "random",
},
{
Tag: "b2",
OutboundSelector: []string{"test"},
Strategy: "leastload",
StrategySettings: serial.ToTypedMessage(&router.StrategyLeastLoadConfig{
HealthCheck: &router.HealthPingConfig{
Interval: int64(time.Duration(300) * time.Second),
SamplingCount: 2,
Timeout: int64(time.Duration(3) * time.Second),
Destination: "dest",
Connectivity: "conn",
},
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{
{

View File

@ -15,6 +15,9 @@ var CmdAPI = &base.Command{
cmdGetStats,
cmdQueryStats,
cmdSysStats,
cmdBalancerCheck,
cmdBalancerInfo,
cmdBalancerOverride,
cmdAddInbounds,
cmdAddOutbounds,
cmdRemoveInbounds,

View File

@ -0,0 +1,47 @@
package api
import (
routerService "github.com/v2fly/v2ray-core/v4/app/router/command"
"github.com/v2fly/v2ray-core/v4/main/commands/base"
)
var cmdBalancerCheck = &base.Command{
CustomFlags: true,
UsageLine: "{{.Exec}} api bc [--server=127.0.0.1:8080] [balancer]...",
Short: "balancer health check",
Long: `
Perform instant health checks for specific balancers. If no
balancer tag specified, check all balancers.
> Make sure you have "RoutingService" set in "config.api.services"
of server config.
Arguments:
-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: executeBalancerCheck,
}
func executeBalancerCheck(cmd *base.Command, args []string) {
setSharedFlags(cmd)
cmd.Flag.Parse(args)
conn, ctx, close := dialAPIServer()
defer close()
client := routerService.NewRoutingServiceClient(conn)
r := &routerService.CheckBalancersRequest{BalancerTags: cmd.Flag.Args()}
_, err := client.CheckBalancers(ctx, r)
if err != nil {
base.Fatalf("failed to perform balancer health checks: %s", err)
}
}

View File

@ -0,0 +1,122 @@
package api
import (
"fmt"
"os"
"sort"
"strings"
routerService "github.com/v2fly/v2ray-core/v4/app/router/command"
"github.com/v2fly/v2ray-core/v4/main/commands/base"
)
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:
-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.GetBalancersRequest{BalancerTags: cmd.Flag.Args()}
resp, err := client.GetBalancers(ctx, r)
if err != nil {
base.Fatalf("failed to get health information: %s", err)
}
sort.Slice(resp.Balancers, func(i, j int) bool {
return resp.Balancers[i].Tag < resp.Balancers[j].Tag
})
for _, b := range resp.Balancers {
showBalancerInfo(b)
}
}
func showBalancerInfo(b *routerService.BalancerMsg) {
sb := new(strings.Builder)
// Balancer
sb.WriteString(fmt.Sprintf("Balancer: %s\n", b.Tag))
// Strategy
sb.WriteString(" - Strategy:\n")
for _, v := range b.StrategySettings {
sb.WriteString(fmt.Sprintf(" %s\n", v))
}
// Override
if b.Override != nil {
sb.WriteString(" - Selecting Override:\n")
until := fmt.Sprintf("until: %s", b.Override.Until)
writeRow(sb, 0, nil, nil, until)
for i, s := range b.Override.Selects {
writeRow(sb, i+1, nil, nil, s)
}
}
formats := getColumnFormats(b.Titles)
// Selects
sb.WriteString(" - Selects:\n")
writeRow(sb, 0, b.Titles, formats, "Tag")
for i, o := range b.Selects {
writeRow(sb, i+1, o.Values, formats, o.Tag)
}
// Others
scnt := len(b.Selects)
if len(b.Others) > 0 {
sb.WriteString(" - Others:\n")
writeRow(sb, 0, b.Titles, formats, "Tag")
for i, o := range b.Others {
writeRow(sb, scnt+i+1, o.Values, formats, o.Tag)
}
}
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, index int, values, formats []string, tag string) {
if index == 0 {
// title line
sb.WriteString(" ")
} else {
sb.WriteString(fmt.Sprintf(" %-4d", index))
}
for i, v := range values {
format := "%-14s"
if i < len(formats) {
format = formats[i]
}
sb.WriteString(fmt.Sprintf(format, v))
}
sb.WriteString(tag)
sb.WriteByte('\n')
}

View File

@ -0,0 +1,87 @@
package api
import (
"time"
routerService "github.com/v2fly/v2ray-core/v4/app/router/command"
"github.com/v2fly/v2ray-core/v4/main/commands/base"
)
var cmdBalancerOverride = &base.Command{
CustomFlags: true,
UsageLine: "{{.Exec}} api bo [--server=127.0.0.1:8080] <-b balancer> selectors...",
Short: "balancer select override",
Long: `
Override a balancer's selecting in a duration of time.
> Make sure you have "RoutingService" set in "config.api.services"
of server config.
Once a balancer's selecting is overridden:
- The selectors of the balancer won't apply.
- The strategy of the balancer stops selecting qualified nodes
according to its settings, doing only the final pick.
Arguments:
-r, -remove
Remove the overridden
-b, -balancer
Tag of the balancer. Required
-v, -validity
Time minutes of the validity of overridden. Default 60
-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 selector1 selector2
{{.Exec}} {{.LongName}} --server=127.0.0.1:8080 -b balancer -r
`,
Run: executeBalancerOverride,
}
func executeBalancerOverride(cmd *base.Command, args []string) {
var (
balancer string
validity int64
remove bool
)
cmd.Flag.StringVar(&balancer, "b", "", "")
cmd.Flag.StringVar(&balancer, "balancer", "", "")
cmd.Flag.Int64Var(&validity, "v", 60, "")
cmd.Flag.Int64Var(&validity, "validity", 60, "")
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()
v := int64(0)
if !remove {
v = int64(time.Duration(validity) * time.Minute)
}
client := routerService.NewRoutingServiceClient(conn)
r := &routerService.OverrideSelectingRequest{
BalancerTag: balancer,
Selectors: cmd.Flag.Args(),
Validity: v,
}
_, err := client.OverrideSelecting(ctx, r)
if err != nil {
base.Fatalf("failed to perform balancer health checks: %s", err)
}
}