Przeglądaj źródła

Outbound: One endpoint and at most one user only (#5144)

https://github.com/XTLS/Xray-core/pull/5124#issuecomment-3281091009

Fixes https://github.com/XTLS/Xray-core/pull/5124#pullrequestreview-3218097421
𐲓𐳛𐳪𐳂𐳐 𐲀𐳢𐳦𐳫𐳢 𐲥𐳔𐳛𐳪𐳌𐳑𐳖𐳇 1 miesiąc temu
rodzic
commit
fe57507fd9
61 zmienionych plików z 829 dodań i 2394 usunięć
  1. 0 22
      app/proxyman/config.go
  2. 143 400
      app/proxyman/config.pb.go
  3. 4 32
      app/proxyman/config.proto
  4. 3 12
      app/proxyman/inbound/always.go
  5. 0 222
      app/proxyman/inbound/dynamic.go
  6. 1 9
      app/proxyman/inbound/inbound.go
  7. 0 9
      common/protocol/headers.go
  8. 0 89
      common/protocol/server_picker.go
  9. 0 71
      common/protocol/server_picker_test.go
  10. 10 102
      common/protocol/server_spec.go
  11. 3 3
      common/protocol/server_spec.pb.go
  12. 1 1
      common/protocol/server_spec.proto
  13. 0 79
      common/protocol/server_spec_test.go
  14. 7 11
      core/xray_test.go
  15. 0 4
      features/inbound/inbound.go
  16. 14 5
      infra/conf/http.go
  17. 19 23
      infra/conf/shadowsocks.go
  18. 14 5
      infra/conf/socks.go
  19. 24 32
      infra/conf/socks_test.go
  20. 20 22
      infra/conf/trojan.go
  21. 8 8
      infra/conf/vless.go
  22. 26 34
      infra/conf/vless_test.go
  23. 10 26
      infra/conf/vmess.go
  24. 29 44
      infra/conf/vmess_test.go
  25. 0 60
      infra/conf/xray.go
  26. 0 10
      infra/conf/xray_test.go
  27. 10 16
      proxy/http/client.go
  28. 4 4
      proxy/http/config.pb.go
  29. 1 1
      proxy/http/config.proto
  30. 12 17
      proxy/shadowsocks/client.go
  31. 3 3
      proxy/shadowsocks/config.pb.go
  32. 1 1
      proxy/shadowsocks/config.proto
  33. 10 17
      proxy/socks/client.go
  34. 3 3
      proxy/socks/config.pb.go
  35. 1 1
      proxy/socks/config.proto
  36. 11 16
      proxy/trojan/client.go
  37. 3 3
      proxy/trojan/config.pb.go
  38. 1 1
      proxy/trojan/config.proto
  39. 3 3
      proxy/vless/outbound/config.pb.go
  40. 1 1
      proxy/vless/outbound/config.proto
  41. 14 17
      proxy/vless/outbound/outbound.go
  42. 0 72
      proxy/vmess/encoding/commands.go
  43. 0 55
      proxy/vmess/encoding/commands_test.go
  44. 20 34
      proxy/vmess/inbound/config.pb.go
  45. 0 2
      proxy/vmess/inbound/config.proto
  46. 1 33
      proxy/vmess/inbound/inbound.go
  47. 2 29
      proxy/vmess/outbound/command.go
  48. 3 3
      proxy/vmess/outbound/config.pb.go
  49. 1 1
      proxy/vmess/outbound/config.proto
  50. 14 17
      proxy/vmess/outbound/outbound.go
  51. 18 26
      testing/scenarios/command_test.go
  52. 14 22
      testing/scenarios/dokodemo_test.go
  53. 37 57
      testing/scenarios/feature_test.go
  54. 18 26
      testing/scenarios/policy_test.go
  55. 18 26
      testing/scenarios/reverse_test.go
  56. 25 45
      testing/scenarios/shadowsocks_test.go
  57. 27 41
      testing/scenarios/socks_test.go
  58. 70 110
      testing/scenarios/tls_test.go
  59. 7 11
      testing/scenarios/transport_test.go
  60. 30 46
      testing/scenarios/vless_test.go
  61. 110 299
      testing/scenarios/vmess_test.go

+ 0 - 22
app/proxyman/config.go

@@ -1,23 +1 @@
 package proxyman
-
-func (s *AllocationStrategy) GetConcurrencyValue() uint32 {
-	if s == nil || s.Concurrency == nil {
-		return 3
-	}
-	return s.Concurrency.Value
-}
-
-func (s *AllocationStrategy) GetRefreshValue() uint32 {
-	if s == nil || s.Refresh == nil {
-		return 5
-	}
-	return s.Refresh.Value
-}
-
-func (c *ReceiverConfig) GetEffectiveSniffingSettings() *SniffingConfig {
-	if c.SniffingSettings != nil {
-		return c.SniffingSettings
-	}
-
-	return nil
-}

+ 143 - 400
app/proxyman/config.pb.go

@@ -23,58 +23,6 @@ const (
 	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
 )
 
-type AllocationStrategy_Type int32
-
-const (
-	// Always allocate all connection handlers.
-	AllocationStrategy_Always AllocationStrategy_Type = 0
-	// Randomly allocate specific range of handlers.
-	AllocationStrategy_Random AllocationStrategy_Type = 1
-	// External. Not supported yet.
-	AllocationStrategy_External AllocationStrategy_Type = 2
-)
-
-// Enum value maps for AllocationStrategy_Type.
-var (
-	AllocationStrategy_Type_name = map[int32]string{
-		0: "Always",
-		1: "Random",
-		2: "External",
-	}
-	AllocationStrategy_Type_value = map[string]int32{
-		"Always":   0,
-		"Random":   1,
-		"External": 2,
-	}
-)
-
-func (x AllocationStrategy_Type) Enum() *AllocationStrategy_Type {
-	p := new(AllocationStrategy_Type)
-	*p = x
-	return p
-}
-
-func (x AllocationStrategy_Type) String() string {
-	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
-}
-
-func (AllocationStrategy_Type) Descriptor() protoreflect.EnumDescriptor {
-	return file_app_proxyman_config_proto_enumTypes[0].Descriptor()
-}
-
-func (AllocationStrategy_Type) Type() protoreflect.EnumType {
-	return &file_app_proxyman_config_proto_enumTypes[0]
-}
-
-func (x AllocationStrategy_Type) Number() protoreflect.EnumNumber {
-	return protoreflect.EnumNumber(x)
-}
-
-// Deprecated: Use AllocationStrategy_Type.Descriptor instead.
-func (AllocationStrategy_Type) EnumDescriptor() ([]byte, []int) {
-	return file_app_proxyman_config_proto_rawDescGZIP(), []int{1, 0}
-}
-
 type InboundConfig struct {
 	state         protoimpl.MessageState
 	sizeCache     protoimpl.SizeCache
@@ -111,71 +59,6 @@ func (*InboundConfig) Descriptor() ([]byte, []int) {
 	return file_app_proxyman_config_proto_rawDescGZIP(), []int{0}
 }
 
-type AllocationStrategy struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
-	unknownFields protoimpl.UnknownFields
-
-	Type AllocationStrategy_Type `protobuf:"varint,1,opt,name=type,proto3,enum=xray.app.proxyman.AllocationStrategy_Type" json:"type,omitempty"`
-	// Number of handlers (ports) running in parallel.
-	// Default value is 3 if unset.
-	Concurrency *AllocationStrategy_AllocationStrategyConcurrency `protobuf:"bytes,2,opt,name=concurrency,proto3" json:"concurrency,omitempty"`
-	// Number of minutes before a handler is regenerated.
-	// Default value is 5 if unset.
-	Refresh *AllocationStrategy_AllocationStrategyRefresh `protobuf:"bytes,3,opt,name=refresh,proto3" json:"refresh,omitempty"`
-}
-
-func (x *AllocationStrategy) Reset() {
-	*x = AllocationStrategy{}
-	mi := &file_app_proxyman_config_proto_msgTypes[1]
-	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-	ms.StoreMessageInfo(mi)
-}
-
-func (x *AllocationStrategy) String() string {
-	return protoimpl.X.MessageStringOf(x)
-}
-
-func (*AllocationStrategy) ProtoMessage() {}
-
-func (x *AllocationStrategy) ProtoReflect() protoreflect.Message {
-	mi := &file_app_proxyman_config_proto_msgTypes[1]
-	if x != nil {
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		if ms.LoadMessageInfo() == nil {
-			ms.StoreMessageInfo(mi)
-		}
-		return ms
-	}
-	return mi.MessageOf(x)
-}
-
-// Deprecated: Use AllocationStrategy.ProtoReflect.Descriptor instead.
-func (*AllocationStrategy) Descriptor() ([]byte, []int) {
-	return file_app_proxyman_config_proto_rawDescGZIP(), []int{1}
-}
-
-func (x *AllocationStrategy) GetType() AllocationStrategy_Type {
-	if x != nil {
-		return x.Type
-	}
-	return AllocationStrategy_Always
-}
-
-func (x *AllocationStrategy) GetConcurrency() *AllocationStrategy_AllocationStrategyConcurrency {
-	if x != nil {
-		return x.Concurrency
-	}
-	return nil
-}
-
-func (x *AllocationStrategy) GetRefresh() *AllocationStrategy_AllocationStrategyRefresh {
-	if x != nil {
-		return x.Refresh
-	}
-	return nil
-}
-
 type SniffingConfig struct {
 	state         protoimpl.MessageState
 	sizeCache     protoimpl.SizeCache
@@ -196,7 +79,7 @@ type SniffingConfig struct {
 
 func (x *SniffingConfig) Reset() {
 	*x = SniffingConfig{}
-	mi := &file_app_proxyman_config_proto_msgTypes[2]
+	mi := &file_app_proxyman_config_proto_msgTypes[1]
 	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 	ms.StoreMessageInfo(mi)
 }
@@ -208,7 +91,7 @@ func (x *SniffingConfig) String() string {
 func (*SniffingConfig) ProtoMessage() {}
 
 func (x *SniffingConfig) ProtoReflect() protoreflect.Message {
-	mi := &file_app_proxyman_config_proto_msgTypes[2]
+	mi := &file_app_proxyman_config_proto_msgTypes[1]
 	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -221,7 +104,7 @@ func (x *SniffingConfig) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use SniffingConfig.ProtoReflect.Descriptor instead.
 func (*SniffingConfig) Descriptor() ([]byte, []int) {
-	return file_app_proxyman_config_proto_rawDescGZIP(), []int{2}
+	return file_app_proxyman_config_proto_rawDescGZIP(), []int{1}
 }
 
 func (x *SniffingConfig) GetEnabled() bool {
@@ -268,15 +151,14 @@ type ReceiverConfig struct {
 	PortList *net.PortList `protobuf:"bytes,1,opt,name=port_list,json=portList,proto3" json:"port_list,omitempty"`
 	// Listen specifies the IP address that the Receiver should listen on.
 	Listen                     *net.IPOrDomain        `protobuf:"bytes,2,opt,name=listen,proto3" json:"listen,omitempty"`
-	AllocationStrategy         *AllocationStrategy    `protobuf:"bytes,3,opt,name=allocation_strategy,json=allocationStrategy,proto3" json:"allocation_strategy,omitempty"`
-	StreamSettings             *internet.StreamConfig `protobuf:"bytes,4,opt,name=stream_settings,json=streamSettings,proto3" json:"stream_settings,omitempty"`
-	ReceiveOriginalDestination bool                   `protobuf:"varint,5,opt,name=receive_original_destination,json=receiveOriginalDestination,proto3" json:"receive_original_destination,omitempty"`
-	SniffingSettings           *SniffingConfig        `protobuf:"bytes,7,opt,name=sniffing_settings,json=sniffingSettings,proto3" json:"sniffing_settings,omitempty"`
+	StreamSettings             *internet.StreamConfig `protobuf:"bytes,3,opt,name=stream_settings,json=streamSettings,proto3" json:"stream_settings,omitempty"`
+	ReceiveOriginalDestination bool                   `protobuf:"varint,4,opt,name=receive_original_destination,json=receiveOriginalDestination,proto3" json:"receive_original_destination,omitempty"`
+	SniffingSettings           *SniffingConfig        `protobuf:"bytes,6,opt,name=sniffing_settings,json=sniffingSettings,proto3" json:"sniffing_settings,omitempty"`
 }
 
 func (x *ReceiverConfig) Reset() {
 	*x = ReceiverConfig{}
-	mi := &file_app_proxyman_config_proto_msgTypes[3]
+	mi := &file_app_proxyman_config_proto_msgTypes[2]
 	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 	ms.StoreMessageInfo(mi)
 }
@@ -288,7 +170,7 @@ func (x *ReceiverConfig) String() string {
 func (*ReceiverConfig) ProtoMessage() {}
 
 func (x *ReceiverConfig) ProtoReflect() protoreflect.Message {
-	mi := &file_app_proxyman_config_proto_msgTypes[3]
+	mi := &file_app_proxyman_config_proto_msgTypes[2]
 	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -301,7 +183,7 @@ func (x *ReceiverConfig) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use ReceiverConfig.ProtoReflect.Descriptor instead.
 func (*ReceiverConfig) Descriptor() ([]byte, []int) {
-	return file_app_proxyman_config_proto_rawDescGZIP(), []int{3}
+	return file_app_proxyman_config_proto_rawDescGZIP(), []int{2}
 }
 
 func (x *ReceiverConfig) GetPortList() *net.PortList {
@@ -318,13 +200,6 @@ func (x *ReceiverConfig) GetListen() *net.IPOrDomain {
 	return nil
 }
 
-func (x *ReceiverConfig) GetAllocationStrategy() *AllocationStrategy {
-	if x != nil {
-		return x.AllocationStrategy
-	}
-	return nil
-}
-
 func (x *ReceiverConfig) GetStreamSettings() *internet.StreamConfig {
 	if x != nil {
 		return x.StreamSettings
@@ -358,7 +233,7 @@ type InboundHandlerConfig struct {
 
 func (x *InboundHandlerConfig) Reset() {
 	*x = InboundHandlerConfig{}
-	mi := &file_app_proxyman_config_proto_msgTypes[4]
+	mi := &file_app_proxyman_config_proto_msgTypes[3]
 	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 	ms.StoreMessageInfo(mi)
 }
@@ -370,7 +245,7 @@ func (x *InboundHandlerConfig) String() string {
 func (*InboundHandlerConfig) ProtoMessage() {}
 
 func (x *InboundHandlerConfig) ProtoReflect() protoreflect.Message {
-	mi := &file_app_proxyman_config_proto_msgTypes[4]
+	mi := &file_app_proxyman_config_proto_msgTypes[3]
 	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -383,7 +258,7 @@ func (x *InboundHandlerConfig) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use InboundHandlerConfig.ProtoReflect.Descriptor instead.
 func (*InboundHandlerConfig) Descriptor() ([]byte, []int) {
-	return file_app_proxyman_config_proto_rawDescGZIP(), []int{4}
+	return file_app_proxyman_config_proto_rawDescGZIP(), []int{3}
 }
 
 func (x *InboundHandlerConfig) GetTag() string {
@@ -415,7 +290,7 @@ type OutboundConfig struct {
 
 func (x *OutboundConfig) Reset() {
 	*x = OutboundConfig{}
-	mi := &file_app_proxyman_config_proto_msgTypes[5]
+	mi := &file_app_proxyman_config_proto_msgTypes[4]
 	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 	ms.StoreMessageInfo(mi)
 }
@@ -427,7 +302,7 @@ func (x *OutboundConfig) String() string {
 func (*OutboundConfig) ProtoMessage() {}
 
 func (x *OutboundConfig) ProtoReflect() protoreflect.Message {
-	mi := &file_app_proxyman_config_proto_msgTypes[5]
+	mi := &file_app_proxyman_config_proto_msgTypes[4]
 	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -440,7 +315,7 @@ func (x *OutboundConfig) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use OutboundConfig.ProtoReflect.Descriptor instead.
 func (*OutboundConfig) Descriptor() ([]byte, []int) {
-	return file_app_proxyman_config_proto_rawDescGZIP(), []int{5}
+	return file_app_proxyman_config_proto_rawDescGZIP(), []int{4}
 }
 
 type SenderConfig struct {
@@ -459,7 +334,7 @@ type SenderConfig struct {
 
 func (x *SenderConfig) Reset() {
 	*x = SenderConfig{}
-	mi := &file_app_proxyman_config_proto_msgTypes[6]
+	mi := &file_app_proxyman_config_proto_msgTypes[5]
 	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 	ms.StoreMessageInfo(mi)
 }
@@ -471,7 +346,7 @@ func (x *SenderConfig) String() string {
 func (*SenderConfig) ProtoMessage() {}
 
 func (x *SenderConfig) ProtoReflect() protoreflect.Message {
-	mi := &file_app_proxyman_config_proto_msgTypes[6]
+	mi := &file_app_proxyman_config_proto_msgTypes[5]
 	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -484,7 +359,7 @@ func (x *SenderConfig) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use SenderConfig.ProtoReflect.Descriptor instead.
 func (*SenderConfig) Descriptor() ([]byte, []int) {
-	return file_app_proxyman_config_proto_rawDescGZIP(), []int{6}
+	return file_app_proxyman_config_proto_rawDescGZIP(), []int{5}
 }
 
 func (x *SenderConfig) GetVia() *net.IPOrDomain {
@@ -546,7 +421,7 @@ type MultiplexingConfig struct {
 
 func (x *MultiplexingConfig) Reset() {
 	*x = MultiplexingConfig{}
-	mi := &file_app_proxyman_config_proto_msgTypes[7]
+	mi := &file_app_proxyman_config_proto_msgTypes[6]
 	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 	ms.StoreMessageInfo(mi)
 }
@@ -558,7 +433,7 @@ func (x *MultiplexingConfig) String() string {
 func (*MultiplexingConfig) ProtoMessage() {}
 
 func (x *MultiplexingConfig) ProtoReflect() protoreflect.Message {
-	mi := &file_app_proxyman_config_proto_msgTypes[7]
+	mi := &file_app_proxyman_config_proto_msgTypes[6]
 	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -571,7 +446,7 @@ func (x *MultiplexingConfig) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use MultiplexingConfig.ProtoReflect.Descriptor instead.
 func (*MultiplexingConfig) Descriptor() ([]byte, []int) {
-	return file_app_proxyman_config_proto_rawDescGZIP(), []int{7}
+	return file_app_proxyman_config_proto_rawDescGZIP(), []int{6}
 }
 
 func (x *MultiplexingConfig) GetEnabled() bool {
@@ -602,96 +477,6 @@ func (x *MultiplexingConfig) GetXudpProxyUDP443() string {
 	return ""
 }
 
-type AllocationStrategy_AllocationStrategyConcurrency struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
-	unknownFields protoimpl.UnknownFields
-
-	Value uint32 `protobuf:"varint,1,opt,name=value,proto3" json:"value,omitempty"`
-}
-
-func (x *AllocationStrategy_AllocationStrategyConcurrency) Reset() {
-	*x = AllocationStrategy_AllocationStrategyConcurrency{}
-	mi := &file_app_proxyman_config_proto_msgTypes[8]
-	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-	ms.StoreMessageInfo(mi)
-}
-
-func (x *AllocationStrategy_AllocationStrategyConcurrency) String() string {
-	return protoimpl.X.MessageStringOf(x)
-}
-
-func (*AllocationStrategy_AllocationStrategyConcurrency) ProtoMessage() {}
-
-func (x *AllocationStrategy_AllocationStrategyConcurrency) ProtoReflect() protoreflect.Message {
-	mi := &file_app_proxyman_config_proto_msgTypes[8]
-	if x != nil {
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		if ms.LoadMessageInfo() == nil {
-			ms.StoreMessageInfo(mi)
-		}
-		return ms
-	}
-	return mi.MessageOf(x)
-}
-
-// Deprecated: Use AllocationStrategy_AllocationStrategyConcurrency.ProtoReflect.Descriptor instead.
-func (*AllocationStrategy_AllocationStrategyConcurrency) Descriptor() ([]byte, []int) {
-	return file_app_proxyman_config_proto_rawDescGZIP(), []int{1, 0}
-}
-
-func (x *AllocationStrategy_AllocationStrategyConcurrency) GetValue() uint32 {
-	if x != nil {
-		return x.Value
-	}
-	return 0
-}
-
-type AllocationStrategy_AllocationStrategyRefresh struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
-	unknownFields protoimpl.UnknownFields
-
-	Value uint32 `protobuf:"varint,1,opt,name=value,proto3" json:"value,omitempty"`
-}
-
-func (x *AllocationStrategy_AllocationStrategyRefresh) Reset() {
-	*x = AllocationStrategy_AllocationStrategyRefresh{}
-	mi := &file_app_proxyman_config_proto_msgTypes[9]
-	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-	ms.StoreMessageInfo(mi)
-}
-
-func (x *AllocationStrategy_AllocationStrategyRefresh) String() string {
-	return protoimpl.X.MessageStringOf(x)
-}
-
-func (*AllocationStrategy_AllocationStrategyRefresh) ProtoMessage() {}
-
-func (x *AllocationStrategy_AllocationStrategyRefresh) ProtoReflect() protoreflect.Message {
-	mi := &file_app_proxyman_config_proto_msgTypes[9]
-	if x != nil {
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		if ms.LoadMessageInfo() == nil {
-			ms.StoreMessageInfo(mi)
-		}
-		return ms
-	}
-	return mi.MessageOf(x)
-}
-
-// Deprecated: Use AllocationStrategy_AllocationStrategyRefresh.ProtoReflect.Descriptor instead.
-func (*AllocationStrategy_AllocationStrategyRefresh) Descriptor() ([]byte, []int) {
-	return file_app_proxyman_config_proto_rawDescGZIP(), []int{1, 1}
-}
-
-func (x *AllocationStrategy_AllocationStrategyRefresh) GetValue() uint32 {
-	if x != nil {
-		return x.Value
-	}
-	return 0
-}
-
 var File_app_proxyman_config_proto protoreflect.FileDescriptor
 
 var file_app_proxyman_config_proto_rawDesc = []byte{
@@ -706,130 +491,98 @@ var file_app_proxyman_config_proto_rawDesc = []byte{
 	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, 0x22, 0x0f, 0x0a, 0x0d, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x43, 0x6f,
-	0x6e, 0x66, 0x69, 0x67, 0x22, 0xae, 0x03, 0x0a, 0x12, 0x41, 0x6c, 0x6c, 0x6f, 0x63, 0x61, 0x74,
-	0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x3e, 0x0a, 0x04, 0x74,
-	0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2a, 0x2e, 0x78, 0x72, 0x61, 0x79,
-	0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x2e, 0x41, 0x6c,
-	0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79,
-	0x2e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x65, 0x0a, 0x0b, 0x63,
-	0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b,
-	0x32, 0x43, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x78,
-	0x79, 0x6d, 0x61, 0x6e, 0x2e, 0x41, 0x6c, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53,
-	0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x2e, 0x41, 0x6c, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69,
-	0x6f, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x43, 0x6f, 0x6e, 0x63, 0x75, 0x72,
-	0x72, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e,
-	0x63, 0x79, 0x12, 0x59, 0x0a, 0x07, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x18, 0x03, 0x20,
-	0x01, 0x28, 0x0b, 0x32, 0x3f, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70,
-	0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x2e, 0x41, 0x6c, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69,
-	0x6f, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x2e, 0x41, 0x6c, 0x6c, 0x6f, 0x63,
-	0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x52, 0x65, 0x66,
-	0x72, 0x65, 0x73, 0x68, 0x52, 0x07, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x1a, 0x35, 0x0a,
-	0x1d, 0x41, 0x6c, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74,
-	0x65, 0x67, 0x79, 0x43, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x14,
-	0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x76,
-	0x61, 0x6c, 0x75, 0x65, 0x1a, 0x31, 0x0a, 0x19, 0x41, 0x6c, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69,
-	0x6f, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73,
-	0x68, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d,
-	0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x2c, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12,
-	0x0a, 0x0a, 0x06, 0x41, 0x6c, 0x77, 0x61, 0x79, 0x73, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x52,
-	0x61, 0x6e, 0x64, 0x6f, 0x6d, 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x45, 0x78, 0x74, 0x65, 0x72,
-	0x6e, 0x61, 0x6c, 0x10, 0x02, 0x22, 0xcc, 0x01, 0x0a, 0x0e, 0x53, 0x6e, 0x69, 0x66, 0x66, 0x69,
-	0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62,
-	0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c,
-	0x65, 0x64, 0x12, 0x31, 0x0a, 0x14, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f,
-	0x6e, 0x5f, 0x6f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09,
-	0x52, 0x13, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4f, 0x76, 0x65,
-	0x72, 0x72, 0x69, 0x64, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73,
-	0x5f, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x64, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52,
-	0x0f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x45, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x64,
-	0x12, 0x23, 0x0a, 0x0d, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x6f, 0x6e, 0x6c,
-	0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74,
-	0x61, 0x4f, 0x6e, 0x6c, 0x79, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x5f, 0x6f,
-	0x6e, 0x6c, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x72, 0x6f, 0x75, 0x74, 0x65,
-	0x4f, 0x6e, 0x6c, 0x79, 0x22, 0xbd, 0x03, 0x0a, 0x0e, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65,
-	0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x36, 0x0a, 0x09, 0x70, 0x6f, 0x72, 0x74, 0x5f,
-	0x6c, 0x69, 0x73, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x78, 0x72, 0x61,
-	0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x50, 0x6f, 0x72,
-	0x74, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x08, 0x70, 0x6f, 0x72, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x12,
-	0x33, 0x0a, 0x06, 0x6c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32,
-	0x1b, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65,
-	0x74, 0x2e, 0x49, 0x50, 0x4f, 0x72, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x06, 0x6c, 0x69,
-	0x73, 0x74, 0x65, 0x6e, 0x12, 0x56, 0x0a, 0x13, 0x61, 0x6c, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69,
-	0x6f, 0x6e, 0x5f, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28,
-	0x0b, 0x32, 0x25, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x72, 0x6f,
-	0x78, 0x79, 0x6d, 0x61, 0x6e, 0x2e, 0x41, 0x6c, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e,
-	0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x52, 0x12, 0x61, 0x6c, 0x6c, 0x6f, 0x63, 0x61,
-	0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x4e, 0x0a, 0x0f,
-	0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18,
-	0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61,
-	0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e,
-	0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0e, 0x73, 0x74,
-	0x72, 0x65, 0x61, 0x6d, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x40, 0x0a, 0x1c,
-	0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x5f, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c,
-	0x5f, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01,
-	0x28, 0x08, 0x52, 0x1a, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x4f, 0x72, 0x69, 0x67, 0x69,
-	0x6e, 0x61, 0x6c, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x4e,
-	0x0a, 0x11, 0x73, 0x6e, 0x69, 0x66, 0x66, 0x69, 0x6e, 0x67, 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69,
-	0x6e, 0x67, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x78, 0x72, 0x61, 0x79,
-	0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x2e, 0x53, 0x6e,
-	0x69, 0x66, 0x66, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x10, 0x73, 0x6e,
-	0x69, 0x66, 0x66, 0x69, 0x6e, 0x67, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x4a, 0x04,
-	0x08, 0x06, 0x10, 0x07, 0x22, 0xc0, 0x01, 0x0a, 0x14, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64,
-	0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x10, 0x0a,
-	0x03, 0x74, 0x61, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x74, 0x61, 0x67, 0x12,
-	0x4d, 0x0a, 0x11, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x5f, 0x73, 0x65, 0x74, 0x74,
-	0x69, 0x6e, 0x67, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x78, 0x72, 0x61,
-	0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x2e,
-	0x54, 0x79, 0x70, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x10, 0x72, 0x65,
-	0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x47,
-	0x0a, 0x0e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73,
-	0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f,
-	0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x2e, 0x54, 0x79, 0x70, 0x65,
-	0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x0d, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x53,
-	0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x22, 0x10, 0x0a, 0x0e, 0x4f, 0x75, 0x74, 0x62, 0x6f,
-	0x75, 0x6e, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x9d, 0x03, 0x0a, 0x0c, 0x53, 0x65,
-	0x6e, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x2d, 0x0a, 0x03, 0x76, 0x69,
-	0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63,
-	0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x49, 0x50, 0x4f, 0x72, 0x44, 0x6f,
-	0x6d, 0x61, 0x69, 0x6e, 0x52, 0x03, 0x76, 0x69, 0x61, 0x12, 0x4e, 0x0a, 0x0f, 0x73, 0x74, 0x72,
-	0x65, 0x61, 0x6d, 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x02, 0x20, 0x01,
-	0x28, 0x0b, 0x32, 0x25, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70,
-	0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x53, 0x74, 0x72,
-	0x65, 0x61, 0x6d, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0e, 0x73, 0x74, 0x72, 0x65, 0x61,
-	0x6d, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x4b, 0x0a, 0x0e, 0x70, 0x72, 0x6f,
-	0x78, 0x79, 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28,
-	0x0b, 0x32, 0x24, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f,
-	0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x50, 0x72, 0x6f, 0x78,
-	0x79, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0d, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x53, 0x65,
-	0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x54, 0x0a, 0x12, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70,
-	0x6c, 0x65, 0x78, 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x04, 0x20, 0x01,
-	0x28, 0x0b, 0x32, 0x25, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x72,
-	0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x2e, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x65, 0x78,
-	0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x11, 0x6d, 0x75, 0x6c, 0x74, 0x69,
-	0x70, 0x6c, 0x65, 0x78, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x19, 0x0a, 0x08,
-	0x76, 0x69, 0x61, 0x5f, 0x63, 0x69, 0x64, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07,
-	0x76, 0x69, 0x61, 0x43, 0x69, 0x64, 0x72, 0x12, 0x50, 0x0a, 0x0f, 0x74, 0x61, 0x72, 0x67, 0x65,
-	0x74, 0x5f, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0e,
-	0x32, 0x27, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72,
-	0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69,
-	0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x52, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65,
-	0x74, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x22, 0xa4, 0x01, 0x0a, 0x12, 0x4d, 0x75,
+	0x6e, 0x66, 0x69, 0x67, 0x22, 0xcc, 0x01, 0x0a, 0x0e, 0x53, 0x6e, 0x69, 0x66, 0x66, 0x69, 0x6e,
+	0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c,
+	0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65,
+	0x64, 0x12, 0x31, 0x0a, 0x14, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e,
+	0x5f, 0x6f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52,
+	0x13, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4f, 0x76, 0x65, 0x72,
+	0x72, 0x69, 0x64, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x5f,
+	0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x64, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0f,
+	0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x45, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x64, 0x12,
+	0x23, 0x0a, 0x0d, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x6f, 0x6e, 0x6c, 0x79,
+	0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61,
+	0x4f, 0x6e, 0x6c, 0x79, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x5f, 0x6f, 0x6e,
+	0x6c, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x4f,
+	0x6e, 0x6c, 0x79, 0x22, 0xe5, 0x02, 0x0a, 0x0e, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72,
+	0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x36, 0x0a, 0x09, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x6c,
+	0x69, 0x73, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x78, 0x72, 0x61, 0x79,
+	0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x50, 0x6f, 0x72, 0x74,
+	0x4c, 0x69, 0x73, 0x74, 0x52, 0x08, 0x70, 0x6f, 0x72, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x33,
+	0x0a, 0x06, 0x6c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b,
+	0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74,
+	0x2e, 0x49, 0x50, 0x4f, 0x72, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x06, 0x6c, 0x69, 0x73,
+	0x74, 0x65, 0x6e, 0x12, 0x4e, 0x0a, 0x0f, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5f, 0x73, 0x65,
+	0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x78,
+	0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e,
+	0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x43, 0x6f, 0x6e,
+	0x66, 0x69, 0x67, 0x52, 0x0e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x53, 0x65, 0x74, 0x74, 0x69,
+	0x6e, 0x67, 0x73, 0x12, 0x40, 0x0a, 0x1c, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x5f, 0x6f,
+	0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x5f, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74,
+	0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x1a, 0x72, 0x65, 0x63, 0x65, 0x69,
+	0x76, 0x65, 0x4f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e,
+	0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x4e, 0x0a, 0x11, 0x73, 0x6e, 0x69, 0x66, 0x66, 0x69, 0x6e,
+	0x67, 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b,
+	0x32, 0x21, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x78,
+	0x79, 0x6d, 0x61, 0x6e, 0x2e, 0x53, 0x6e, 0x69, 0x66, 0x66, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e,
+	0x66, 0x69, 0x67, 0x52, 0x10, 0x73, 0x6e, 0x69, 0x66, 0x66, 0x69, 0x6e, 0x67, 0x53, 0x65, 0x74,
+	0x74, 0x69, 0x6e, 0x67, 0x73, 0x4a, 0x04, 0x08, 0x05, 0x10, 0x06, 0x22, 0xc0, 0x01, 0x0a, 0x14,
+	0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x43, 0x6f,
+	0x6e, 0x66, 0x69, 0x67, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28,
+	0x09, 0x52, 0x03, 0x74, 0x61, 0x67, 0x12, 0x4d, 0x0a, 0x11, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76,
+	0x65, 0x72, 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28,
+	0x0b, 0x32, 0x20, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e,
+	0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73,
+	0x61, 0x67, 0x65, 0x52, 0x10, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x53, 0x65, 0x74,
+	0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x47, 0x0a, 0x0e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x5f, 0x73,
+	0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e,
+	0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x73, 0x65, 0x72, 0x69,
+	0x61, 0x6c, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52,
+	0x0d, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x22, 0x10,
+	0x0a, 0x0e, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67,
+	0x22, 0x9d, 0x03, 0x0a, 0x0c, 0x53, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69,
+	0x67, 0x12, 0x2d, 0x0a, 0x03, 0x76, 0x69, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b,
+	0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74,
+	0x2e, 0x49, 0x50, 0x4f, 0x72, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x03, 0x76, 0x69, 0x61,
+	0x12, 0x4e, 0x0a, 0x0f, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69,
+	0x6e, 0x67, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x78, 0x72, 0x61, 0x79,
+	0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72,
+	0x6e, 0x65, 0x74, 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67,
+	0x52, 0x0e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73,
+	0x12, 0x4b, 0x0a, 0x0e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e,
+	0x67, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e,
+	0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e,
+	0x65, 0x74, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0d,
+	0x70, 0x72, 0x6f, 0x78, 0x79, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x54, 0x0a,
+	0x12, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x65, 0x78, 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69,
+	0x6e, 0x67, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x78, 0x72, 0x61, 0x79,
+	0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x2e, 0x4d, 0x75,
 	0x6c, 0x74, 0x69, 0x70, 0x6c, 0x65, 0x78, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67,
-	0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28,
-	0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x20, 0x0a, 0x0b, 0x63, 0x6f,
-	0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52,
-	0x0b, 0x63, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x28, 0x0a, 0x0f,
-	0x78, 0x75, 0x64, 0x70, 0x43, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x18,
-	0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0f, 0x78, 0x75, 0x64, 0x70, 0x43, 0x6f, 0x6e, 0x63, 0x75,
-	0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x28, 0x0a, 0x0f, 0x78, 0x75, 0x64, 0x70, 0x50, 0x72,
-	0x6f, 0x78, 0x79, 0x55, 0x44, 0x50, 0x34, 0x34, 0x33, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52,
+	0x52, 0x11, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x65, 0x78, 0x53, 0x65, 0x74, 0x74, 0x69,
+	0x6e, 0x67, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x76, 0x69, 0x61, 0x5f, 0x63, 0x69, 0x64, 0x72, 0x18,
+	0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x69, 0x61, 0x43, 0x69, 0x64, 0x72, 0x12, 0x50,
+	0x0a, 0x0f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67,
+	0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x27, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74,
+	0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65,
+	0x74, 0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79,
+	0x52, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79,
+	0x22, 0xa4, 0x01, 0x0a, 0x12, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x65, 0x78, 0x69, 0x6e,
+	0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c,
+	0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65,
+	0x64, 0x12, 0x20, 0x0a, 0x0b, 0x63, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79,
+	0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65,
+	0x6e, 0x63, 0x79, 0x12, 0x28, 0x0a, 0x0f, 0x78, 0x75, 0x64, 0x70, 0x43, 0x6f, 0x6e, 0x63, 0x75,
+	0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0f, 0x78, 0x75,
+	0x64, 0x70, 0x43, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x28, 0x0a,
 	0x0f, 0x78, 0x75, 0x64, 0x70, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x55, 0x44, 0x50, 0x34, 0x34, 0x33,
-	0x42, 0x55, 0x0a, 0x15, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70,
-	0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x50, 0x01, 0x5a, 0x26, 0x67, 0x69, 0x74,
-	0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61,
-	0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x61, 0x70, 0x70, 0x2f, 0x70, 0x72, 0x6f, 0x78, 0x79,
-	0x6d, 0x61, 0x6e, 0xaa, 0x02, 0x11, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x41, 0x70, 0x70, 0x2e, 0x50,
-	0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+	0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x78, 0x75, 0x64, 0x70, 0x50, 0x72, 0x6f, 0x78,
+	0x79, 0x55, 0x44, 0x50, 0x34, 0x34, 0x33, 0x42, 0x55, 0x0a, 0x15, 0x63, 0x6f, 0x6d, 0x2e, 0x78,
+	0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e,
+	0x50, 0x01, 0x5a, 0x26, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78,
+	0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x61, 0x70,
+	0x70, 0x2f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0xaa, 0x02, 0x11, 0x58, 0x72, 0x61,
+	0x79, 0x2e, 0x41, 0x70, 0x70, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x62, 0x06,
+	0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
 }
 
 var (
@@ -844,48 +597,39 @@ func file_app_proxyman_config_proto_rawDescGZIP() []byte {
 	return file_app_proxyman_config_proto_rawDescData
 }
 
-var file_app_proxyman_config_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
-var file_app_proxyman_config_proto_msgTypes = make([]protoimpl.MessageInfo, 10)
+var file_app_proxyman_config_proto_msgTypes = make([]protoimpl.MessageInfo, 7)
 var file_app_proxyman_config_proto_goTypes = []any{
-	(AllocationStrategy_Type)(0),                             // 0: xray.app.proxyman.AllocationStrategy.Type
-	(*InboundConfig)(nil),                                    // 1: xray.app.proxyman.InboundConfig
-	(*AllocationStrategy)(nil),                               // 2: xray.app.proxyman.AllocationStrategy
-	(*SniffingConfig)(nil),                                   // 3: xray.app.proxyman.SniffingConfig
-	(*ReceiverConfig)(nil),                                   // 4: xray.app.proxyman.ReceiverConfig
-	(*InboundHandlerConfig)(nil),                             // 5: xray.app.proxyman.InboundHandlerConfig
-	(*OutboundConfig)(nil),                                   // 6: xray.app.proxyman.OutboundConfig
-	(*SenderConfig)(nil),                                     // 7: xray.app.proxyman.SenderConfig
-	(*MultiplexingConfig)(nil),                               // 8: xray.app.proxyman.MultiplexingConfig
-	(*AllocationStrategy_AllocationStrategyConcurrency)(nil), // 9: xray.app.proxyman.AllocationStrategy.AllocationStrategyConcurrency
-	(*AllocationStrategy_AllocationStrategyRefresh)(nil),     // 10: xray.app.proxyman.AllocationStrategy.AllocationStrategyRefresh
-	(*net.PortList)(nil),                                     // 11: xray.common.net.PortList
-	(*net.IPOrDomain)(nil),                                   // 12: xray.common.net.IPOrDomain
-	(*internet.StreamConfig)(nil),                            // 13: xray.transport.internet.StreamConfig
-	(*serial.TypedMessage)(nil),                              // 14: xray.common.serial.TypedMessage
-	(*internet.ProxyConfig)(nil),                             // 15: xray.transport.internet.ProxyConfig
-	(internet.DomainStrategy)(0),                             // 16: xray.transport.internet.DomainStrategy
+	(*InboundConfig)(nil),         // 0: xray.app.proxyman.InboundConfig
+	(*SniffingConfig)(nil),        // 1: xray.app.proxyman.SniffingConfig
+	(*ReceiverConfig)(nil),        // 2: xray.app.proxyman.ReceiverConfig
+	(*InboundHandlerConfig)(nil),  // 3: xray.app.proxyman.InboundHandlerConfig
+	(*OutboundConfig)(nil),        // 4: xray.app.proxyman.OutboundConfig
+	(*SenderConfig)(nil),          // 5: xray.app.proxyman.SenderConfig
+	(*MultiplexingConfig)(nil),    // 6: xray.app.proxyman.MultiplexingConfig
+	(*net.PortList)(nil),          // 7: xray.common.net.PortList
+	(*net.IPOrDomain)(nil),        // 8: xray.common.net.IPOrDomain
+	(*internet.StreamConfig)(nil), // 9: xray.transport.internet.StreamConfig
+	(*serial.TypedMessage)(nil),   // 10: xray.common.serial.TypedMessage
+	(*internet.ProxyConfig)(nil),  // 11: xray.transport.internet.ProxyConfig
+	(internet.DomainStrategy)(0),  // 12: xray.transport.internet.DomainStrategy
 }
 var file_app_proxyman_config_proto_depIdxs = []int32{
-	0,  // 0: xray.app.proxyman.AllocationStrategy.type:type_name -> xray.app.proxyman.AllocationStrategy.Type
-	9,  // 1: xray.app.proxyman.AllocationStrategy.concurrency:type_name -> xray.app.proxyman.AllocationStrategy.AllocationStrategyConcurrency
-	10, // 2: xray.app.proxyman.AllocationStrategy.refresh:type_name -> xray.app.proxyman.AllocationStrategy.AllocationStrategyRefresh
-	11, // 3: xray.app.proxyman.ReceiverConfig.port_list:type_name -> xray.common.net.PortList
-	12, // 4: xray.app.proxyman.ReceiverConfig.listen:type_name -> xray.common.net.IPOrDomain
-	2,  // 5: xray.app.proxyman.ReceiverConfig.allocation_strategy:type_name -> xray.app.proxyman.AllocationStrategy
-	13, // 6: xray.app.proxyman.ReceiverConfig.stream_settings:type_name -> xray.transport.internet.StreamConfig
-	3,  // 7: xray.app.proxyman.ReceiverConfig.sniffing_settings:type_name -> xray.app.proxyman.SniffingConfig
-	14, // 8: xray.app.proxyman.InboundHandlerConfig.receiver_settings:type_name -> xray.common.serial.TypedMessage
-	14, // 9: xray.app.proxyman.InboundHandlerConfig.proxy_settings:type_name -> xray.common.serial.TypedMessage
-	12, // 10: xray.app.proxyman.SenderConfig.via:type_name -> xray.common.net.IPOrDomain
-	13, // 11: xray.app.proxyman.SenderConfig.stream_settings:type_name -> xray.transport.internet.StreamConfig
-	15, // 12: xray.app.proxyman.SenderConfig.proxy_settings:type_name -> xray.transport.internet.ProxyConfig
-	8,  // 13: xray.app.proxyman.SenderConfig.multiplex_settings:type_name -> xray.app.proxyman.MultiplexingConfig
-	16, // 14: xray.app.proxyman.SenderConfig.target_strategy:type_name -> xray.transport.internet.DomainStrategy
-	15, // [15:15] is the sub-list for method output_type
-	15, // [15:15] is the sub-list for method input_type
-	15, // [15:15] is the sub-list for extension type_name
-	15, // [15:15] is the sub-list for extension extendee
-	0,  // [0:15] is the sub-list for field type_name
+	7,  // 0: xray.app.proxyman.ReceiverConfig.port_list:type_name -> xray.common.net.PortList
+	8,  // 1: xray.app.proxyman.ReceiverConfig.listen:type_name -> xray.common.net.IPOrDomain
+	9,  // 2: xray.app.proxyman.ReceiverConfig.stream_settings:type_name -> xray.transport.internet.StreamConfig
+	1,  // 3: xray.app.proxyman.ReceiverConfig.sniffing_settings:type_name -> xray.app.proxyman.SniffingConfig
+	10, // 4: xray.app.proxyman.InboundHandlerConfig.receiver_settings:type_name -> xray.common.serial.TypedMessage
+	10, // 5: xray.app.proxyman.InboundHandlerConfig.proxy_settings:type_name -> xray.common.serial.TypedMessage
+	8,  // 6: xray.app.proxyman.SenderConfig.via:type_name -> xray.common.net.IPOrDomain
+	9,  // 7: xray.app.proxyman.SenderConfig.stream_settings:type_name -> xray.transport.internet.StreamConfig
+	11, // 8: xray.app.proxyman.SenderConfig.proxy_settings:type_name -> xray.transport.internet.ProxyConfig
+	6,  // 9: xray.app.proxyman.SenderConfig.multiplex_settings:type_name -> xray.app.proxyman.MultiplexingConfig
+	12, // 10: xray.app.proxyman.SenderConfig.target_strategy:type_name -> xray.transport.internet.DomainStrategy
+	11, // [11:11] is the sub-list for method output_type
+	11, // [11:11] is the sub-list for method input_type
+	11, // [11:11] is the sub-list for extension type_name
+	11, // [11:11] is the sub-list for extension extendee
+	0,  // [0:11] is the sub-list for field type_name
 }
 
 func init() { file_app_proxyman_config_proto_init() }
@@ -898,14 +642,13 @@ func file_app_proxyman_config_proto_init() {
 		File: protoimpl.DescBuilder{
 			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
 			RawDescriptor: file_app_proxyman_config_proto_rawDesc,
-			NumEnums:      1,
-			NumMessages:   10,
+			NumEnums:      0,
+			NumMessages:   7,
 			NumExtensions: 0,
 			NumServices:   0,
 		},
 		GoTypes:           file_app_proxyman_config_proto_goTypes,
 		DependencyIndexes: file_app_proxyman_config_proto_depIdxs,
-		EnumInfos:         file_app_proxyman_config_proto_enumTypes,
 		MessageInfos:      file_app_proxyman_config_proto_msgTypes,
 	}.Build()
 	File_app_proxyman_config_proto = out.File

+ 4 - 32
app/proxyman/config.proto

@@ -13,33 +13,6 @@ import "common/serial/typed_message.proto";
 
 message InboundConfig {}
 
-message AllocationStrategy {
-  enum Type {
-    // Always allocate all connection handlers.
-    Always = 0;
-
-    // Randomly allocate specific range of handlers.
-    Random = 1;
-
-    // External. Not supported yet.
-    External = 2;
-  }
-
-  Type type = 1;
-
-  message AllocationStrategyConcurrency { uint32 value = 1; }
-
-  // Number of handlers (ports) running in parallel.
-  // Default value is 3 if unset.
-  AllocationStrategyConcurrency concurrency = 2;
-
-  message AllocationStrategyRefresh { uint32 value = 1; }
-
-  // Number of minutes before a handler is regenerated.
-  // Default value is 5 if unset.
-  AllocationStrategyRefresh refresh = 3;
-}
-
 message SniffingConfig {
   // Whether or not to enable content sniffing on an inbound connection.
   bool enabled = 1;
@@ -62,11 +35,10 @@ message ReceiverConfig {
   xray.common.net.PortList port_list = 1;
   // Listen specifies the IP address that the Receiver should listen on.
   xray.common.net.IPOrDomain listen = 2;
-  AllocationStrategy allocation_strategy = 3;
-  xray.transport.internet.StreamConfig stream_settings = 4;
-  bool receive_original_destination = 5;
-  reserved 6;
-  SniffingConfig sniffing_settings = 7;
+  xray.transport.internet.StreamConfig stream_settings = 3;
+  bool receive_original_destination = 4;
+  reserved 5;
+  SniffingConfig sniffing_settings = 6;
 }
 
 message InboundHandlerConfig {

+ 3 - 12
app/proxyman/inbound/always.go

@@ -5,7 +5,6 @@ import (
 
 	"github.com/xtls/xray-core/app/proxyman"
 	"github.com/xtls/xray-core/common"
-	"github.com/xtls/xray-core/common/dice"
 	"github.com/xtls/xray-core/common/errors"
 	"github.com/xtls/xray-core/common/mux"
 	"github.com/xtls/xray-core/common/net"
@@ -103,7 +102,7 @@ func NewAlwaysOnInboundHandler(ctx context.Context, tag string, receiverConfig *
 				stream:          mss,
 				tag:             tag,
 				dispatcher:      h.mux,
-				sniffingConfig:  receiverConfig.GetEffectiveSniffingSettings(),
+				sniffingConfig:  receiverConfig.SniffingSettings,
 				uplinkCounter:   uplinkCounter,
 				downlinkCounter: downlinkCounter,
 				ctx:             ctx,
@@ -125,7 +124,7 @@ func NewAlwaysOnInboundHandler(ctx context.Context, tag string, receiverConfig *
 						recvOrigDest:    receiverConfig.ReceiveOriginalDestination,
 						tag:             tag,
 						dispatcher:      h.mux,
-						sniffingConfig:  receiverConfig.GetEffectiveSniffingSettings(),
+						sniffingConfig:  receiverConfig.SniffingSettings,
 						uplinkCounter:   uplinkCounter,
 						downlinkCounter: downlinkCounter,
 						ctx:             ctx,
@@ -140,7 +139,7 @@ func NewAlwaysOnInboundHandler(ctx context.Context, tag string, receiverConfig *
 						address:         address,
 						port:            net.Port(port),
 						dispatcher:      h.mux,
-						sniffingConfig:  receiverConfig.GetEffectiveSniffingSettings(),
+						sniffingConfig:  receiverConfig.SniffingSettings,
 						uplinkCounter:   uplinkCounter,
 						downlinkCounter: downlinkCounter,
 						stream:          mss,
@@ -178,14 +177,6 @@ func (h *AlwaysOnInboundHandler) Close() error {
 	return nil
 }
 
-func (h *AlwaysOnInboundHandler) GetRandomInboundProxy() (interface{}, net.Port, int) {
-	if len(h.workers) == 0 {
-		return nil, 0, 0
-	}
-	w := h.workers[dice.Roll(len(h.workers))]
-	return w.Proxy(), w.Port(), 9999
-}
-
 func (h *AlwaysOnInboundHandler) Tag() string {
 	return h.tag
 }

+ 0 - 222
app/proxyman/inbound/dynamic.go

@@ -1,222 +0,0 @@
-package inbound
-
-import (
-	"context"
-	"sync"
-	"time"
-
-	"github.com/xtls/xray-core/app/proxyman"
-	"github.com/xtls/xray-core/common/dice"
-	"github.com/xtls/xray-core/common/errors"
-	"github.com/xtls/xray-core/common/mux"
-	"github.com/xtls/xray-core/common/net"
-	"github.com/xtls/xray-core/common/serial"
-	"github.com/xtls/xray-core/common/task"
-	"github.com/xtls/xray-core/core"
-	"github.com/xtls/xray-core/proxy"
-	"github.com/xtls/xray-core/transport/internet"
-	"google.golang.org/protobuf/proto"
-)
-
-type DynamicInboundHandler struct {
-	tag            string
-	v              *core.Instance
-	proxyConfig    interface{}
-	receiverConfig *proxyman.ReceiverConfig
-	streamSettings *internet.MemoryStreamConfig
-	portMutex      sync.Mutex
-	portsInUse     map[net.Port]struct{}
-	workerMutex    sync.RWMutex
-	worker         []worker
-	lastRefresh    time.Time
-	mux            *mux.Server
-	task           *task.Periodic
-
-	ctx context.Context
-}
-
-func NewDynamicInboundHandler(ctx context.Context, tag string, receiverConfig *proxyman.ReceiverConfig, proxyConfig interface{}) (*DynamicInboundHandler, error) {
-	v := core.MustFromContext(ctx)
-	h := &DynamicInboundHandler{
-		tag:            tag,
-		proxyConfig:    proxyConfig,
-		receiverConfig: receiverConfig,
-		portsInUse:     make(map[net.Port]struct{}),
-		mux:            mux.NewServer(ctx),
-		v:              v,
-		ctx:            ctx,
-	}
-
-	mss, err := internet.ToMemoryStreamConfig(receiverConfig.StreamSettings)
-	if err != nil {
-		return nil, errors.New("failed to parse stream settings").Base(err).AtWarning()
-	}
-	if receiverConfig.ReceiveOriginalDestination {
-		if mss.SocketSettings == nil {
-			mss.SocketSettings = &internet.SocketConfig{}
-		}
-		if mss.SocketSettings.Tproxy == internet.SocketConfig_Off {
-			mss.SocketSettings.Tproxy = internet.SocketConfig_Redirect
-		}
-		mss.SocketSettings.ReceiveOriginalDestAddress = true
-	}
-
-	h.streamSettings = mss
-
-	h.task = &task.Periodic{
-		Interval: time.Minute * time.Duration(h.receiverConfig.AllocationStrategy.GetRefreshValue()),
-		Execute:  h.refresh,
-	}
-
-	return h, nil
-}
-
-func (h *DynamicInboundHandler) allocatePort() net.Port {
-	allPorts := []int32{}
-	for _, pr := range h.receiverConfig.PortList.Range {
-		for i := pr.From; i <= pr.To; i++ {
-			allPorts = append(allPorts, int32(i))
-		}
-	}
-	h.portMutex.Lock()
-	defer h.portMutex.Unlock()
-
-	for {
-		r := dice.Roll(len(allPorts))
-		port := net.Port(allPorts[r])
-		_, used := h.portsInUse[port]
-		if !used {
-			h.portsInUse[port] = struct{}{}
-			return port
-		}
-	}
-}
-
-func (h *DynamicInboundHandler) closeWorkers(workers []worker) {
-	ports2Del := make([]net.Port, len(workers))
-	for idx, worker := range workers {
-		ports2Del[idx] = worker.Port()
-		if err := worker.Close(); err != nil {
-			errors.LogInfoInner(h.ctx, err, "failed to close worker")
-		}
-	}
-
-	h.portMutex.Lock()
-	for _, port := range ports2Del {
-		delete(h.portsInUse, port)
-	}
-	h.portMutex.Unlock()
-}
-
-func (h *DynamicInboundHandler) refresh() error {
-	h.lastRefresh = time.Now()
-
-	timeout := time.Minute * time.Duration(h.receiverConfig.AllocationStrategy.GetRefreshValue()) * 2
-	concurrency := h.receiverConfig.AllocationStrategy.GetConcurrencyValue()
-	workers := make([]worker, 0, concurrency)
-
-	address := h.receiverConfig.Listen.AsAddress()
-	if address == nil {
-		address = net.AnyIP
-	}
-
-	uplinkCounter, downlinkCounter := getStatCounter(h.v, h.tag)
-
-	for i := uint32(0); i < concurrency; i++ {
-		port := h.allocatePort()
-		rawProxy, err := core.CreateObject(h.v, h.proxyConfig)
-		if err != nil {
-			errors.LogWarningInner(h.ctx, err, "failed to create proxy instance")
-			continue
-		}
-		p := rawProxy.(proxy.Inbound)
-		nl := p.Network()
-		if net.HasNetwork(nl, net.Network_TCP) {
-			worker := &tcpWorker{
-				tag:             h.tag,
-				address:         address,
-				port:            port,
-				proxy:           p,
-				stream:          h.streamSettings,
-				recvOrigDest:    h.receiverConfig.ReceiveOriginalDestination,
-				dispatcher:      h.mux,
-				sniffingConfig:  h.receiverConfig.GetEffectiveSniffingSettings(),
-				uplinkCounter:   uplinkCounter,
-				downlinkCounter: downlinkCounter,
-				ctx:             h.ctx,
-			}
-			if err := worker.Start(); err != nil {
-				errors.LogWarningInner(h.ctx, err, "failed to create TCP worker")
-				continue
-			}
-			workers = append(workers, worker)
-		}
-
-		if net.HasNetwork(nl, net.Network_UDP) {
-			worker := &udpWorker{
-				tag:             h.tag,
-				proxy:           p,
-				address:         address,
-				port:            port,
-				dispatcher:      h.mux,
-				sniffingConfig:  h.receiverConfig.GetEffectiveSniffingSettings(),
-				uplinkCounter:   uplinkCounter,
-				downlinkCounter: downlinkCounter,
-				stream:          h.streamSettings,
-				ctx:             h.ctx,
-			}
-			if err := worker.Start(); err != nil {
-				errors.LogWarningInner(h.ctx, err, "failed to create UDP worker")
-				continue
-			}
-			workers = append(workers, worker)
-		}
-	}
-
-	h.workerMutex.Lock()
-	h.worker = workers
-	h.workerMutex.Unlock()
-
-	time.AfterFunc(timeout, func() {
-		h.closeWorkers(workers)
-	})
-
-	return nil
-}
-
-func (h *DynamicInboundHandler) Start() error {
-	return h.task.Start()
-}
-
-func (h *DynamicInboundHandler) Close() error {
-	return h.task.Close()
-}
-
-func (h *DynamicInboundHandler) GetRandomInboundProxy() (interface{}, net.Port, int) {
-	h.workerMutex.RLock()
-	defer h.workerMutex.RUnlock()
-
-	if len(h.worker) == 0 {
-		return nil, 0, 0
-	}
-	w := h.worker[dice.Roll(len(h.worker))]
-	expire := h.receiverConfig.AllocationStrategy.GetRefreshValue() - uint32(time.Since(h.lastRefresh)/time.Minute)
-	return w.Proxy(), w.Port(), int(expire)
-}
-
-func (h *DynamicInboundHandler) Tag() string {
-	return h.tag
-}
-
-// ReceiverSettings implements inbound.Handler.
-func (h *DynamicInboundHandler) ReceiverSettings() *serial.TypedMessage {
-	return serial.ToTypedMessage(h.receiverConfig)
-}
-
-// ProxySettings implements inbound.Handler.
-func (h *DynamicInboundHandler) ProxySettings() *serial.TypedMessage {
-	if v, ok := h.proxyConfig.(proto.Message); ok {
-		return serial.ToTypedMessage(v)
-	}
-	return nil
-}

+ 1 - 9
app/proxyman/inbound/inbound.go

@@ -178,15 +178,7 @@ func NewHandler(ctx context.Context, config *core.InboundHandlerConfig) (inbound
 		ctx = session.ContextWithAllowedNetwork(ctx, net.Network_UDP)
 	}
 
-	allocStrategy := receiverSettings.AllocationStrategy
-	if allocStrategy == nil || allocStrategy.Type == proxyman.AllocationStrategy_Always {
-		return NewAlwaysOnInboundHandler(ctx, tag, receiverSettings, proxySettings)
-	}
-
-	if allocStrategy.Type == proxyman.AllocationStrategy_Random {
-		return NewDynamicInboundHandler(ctx, tag, receiverSettings, proxySettings)
-	}
-	return nil, errors.New("unknown allocation strategy: ", receiverSettings.AllocationStrategy.Type).AtError()
+	return NewAlwaysOnInboundHandler(ctx, tag, receiverSettings, proxySettings)
 }
 
 func init() {

+ 0 - 9
common/protocol/headers.go

@@ -5,7 +5,6 @@ import (
 
 	"github.com/xtls/xray-core/common/bitmask"
 	"github.com/xtls/xray-core/common/net"
-	"github.com/xtls/xray-core/common/uuid"
 	"golang.org/x/sys/cpu"
 )
 
@@ -71,14 +70,6 @@ type ResponseHeader struct {
 	Command ResponseCommand
 }
 
-type CommandSwitchAccount struct {
-	Host     net.Address
-	Port     net.Port
-	ID       uuid.UUID
-	Level    uint32
-	ValidMin byte
-}
-
 var (
 	// Keep in sync with crypto/tls/cipher_suites.go.
 	hasGCMAsmAMD64 = cpu.X86.HasAES && cpu.X86.HasPCLMULQDQ && cpu.X86.HasSSE41 && cpu.X86.HasSSSE3

+ 0 - 89
common/protocol/server_picker.go

@@ -1,89 +0,0 @@
-package protocol
-
-import (
-	"sync"
-)
-
-type ServerList struct {
-	sync.RWMutex
-	servers []*ServerSpec
-}
-
-func NewServerList() *ServerList {
-	return &ServerList{}
-}
-
-func (sl *ServerList) AddServer(server *ServerSpec) {
-	sl.Lock()
-	defer sl.Unlock()
-
-	sl.servers = append(sl.servers, server)
-}
-
-func (sl *ServerList) Size() uint32 {
-	sl.RLock()
-	defer sl.RUnlock()
-
-	return uint32(len(sl.servers))
-}
-
-func (sl *ServerList) GetServer(idx uint32) *ServerSpec {
-	sl.Lock()
-	defer sl.Unlock()
-
-	for {
-		if idx >= uint32(len(sl.servers)) {
-			return nil
-		}
-
-		server := sl.servers[idx]
-		if !server.IsValid() {
-			sl.removeServer(idx)
-			continue
-		}
-
-		return server
-	}
-}
-
-func (sl *ServerList) removeServer(idx uint32) {
-	n := len(sl.servers)
-	sl.servers[idx] = sl.servers[n-1]
-	sl.servers = sl.servers[:n-1]
-}
-
-type ServerPicker interface {
-	PickServer() *ServerSpec
-}
-
-type RoundRobinServerPicker struct {
-	sync.Mutex
-	serverlist *ServerList
-	nextIndex  uint32
-}
-
-func NewRoundRobinServerPicker(serverlist *ServerList) *RoundRobinServerPicker {
-	return &RoundRobinServerPicker{
-		serverlist: serverlist,
-		nextIndex:  0,
-	}
-}
-
-func (p *RoundRobinServerPicker) PickServer() *ServerSpec {
-	p.Lock()
-	defer p.Unlock()
-
-	next := p.nextIndex
-	server := p.serverlist.GetServer(next)
-	if server == nil {
-		next = 0
-		server = p.serverlist.GetServer(0)
-	}
-	next++
-	if next >= p.serverlist.Size() {
-		next = 0
-	}
-	p.nextIndex = next
-
-	return server
-}

+ 0 - 71
common/protocol/server_picker_test.go

@@ -1,71 +0,0 @@
-package protocol_test
-
-import (
-	"testing"
-	"time"
-
-	"github.com/xtls/xray-core/common/net"
-	. "github.com/xtls/xray-core/common/protocol"
-)
-
-func TestServerList(t *testing.T) {
-	list := NewServerList()
-	list.AddServer(NewServerSpec(net.TCPDestination(net.LocalHostIP, net.Port(1)), AlwaysValid()))
-	if list.Size() != 1 {
-		t.Error("list size: ", list.Size())
-	}
-	list.AddServer(NewServerSpec(net.TCPDestination(net.LocalHostIP, net.Port(2)), BeforeTime(time.Now().Add(time.Second))))
-	if list.Size() != 2 {
-		t.Error("list.size: ", list.Size())
-	}
-
-	server := list.GetServer(1)
-	if server.Destination().Port != 2 {
-		t.Error("server: ", server.Destination())
-	}
-	time.Sleep(2 * time.Second)
-	server = list.GetServer(1)
-	if server != nil {
-		t.Error("server: ", server)
-	}
-
-	server = list.GetServer(0)
-	if server.Destination().Port != 1 {
-		t.Error("server: ", server.Destination())
-	}
-}
-
-func TestServerPicker(t *testing.T) {
-	list := NewServerList()
-	list.AddServer(NewServerSpec(net.TCPDestination(net.LocalHostIP, net.Port(1)), AlwaysValid()))
-	list.AddServer(NewServerSpec(net.TCPDestination(net.LocalHostIP, net.Port(2)), BeforeTime(time.Now().Add(time.Second))))
-	list.AddServer(NewServerSpec(net.TCPDestination(net.LocalHostIP, net.Port(3)), BeforeTime(time.Now().Add(time.Second))))
-
-	picker := NewRoundRobinServerPicker(list)
-	server := picker.PickServer()
-	if server.Destination().Port != 1 {
-		t.Error("server: ", server.Destination())
-	}
-	server = picker.PickServer()
-	if server.Destination().Port != 2 {
-		t.Error("server: ", server.Destination())
-	}
-	server = picker.PickServer()
-	if server.Destination().Port != 3 {
-		t.Error("server: ", server.Destination())
-	}
-	server = picker.PickServer()
-	if server.Destination().Port != 1 {
-		t.Error("server: ", server.Destination())
-	}
-
-	time.Sleep(2 * time.Second)
-	server = picker.PickServer()
-	if server.Destination().Port != 1 {
-		t.Error("server: ", server.Destination())
-	}
-	server = picker.PickServer()
-	if server.Destination().Port != 1 {
-		t.Error("server: ", server.Destination())
-	}
-}

+ 10 - 102
common/protocol/server_spec.go

@@ -1,122 +1,30 @@
 package protocol
 
 import (
-	"sync"
-	"time"
-
-	"github.com/xtls/xray-core/common/dice"
 	"github.com/xtls/xray-core/common/net"
 )
 
-type ValidationStrategy interface {
-	IsValid() bool
-	Invalidate()
-}
-
-type alwaysValidStrategy struct{}
-
-func AlwaysValid() ValidationStrategy {
-	return alwaysValidStrategy{}
-}
-
-func (alwaysValidStrategy) IsValid() bool {
-	return true
-}
-
-func (alwaysValidStrategy) Invalidate() {}
-
-type timeoutValidStrategy struct {
-	until time.Time
-}
-
-func BeforeTime(t time.Time) ValidationStrategy {
-	return &timeoutValidStrategy{
-		until: t,
-	}
-}
-
-func (s *timeoutValidStrategy) IsValid() bool {
-	return s.until.After(time.Now())
-}
-
-func (s *timeoutValidStrategy) Invalidate() {
-	s.until = time.Time{}
-}
-
 type ServerSpec struct {
-	sync.RWMutex
-	dest  net.Destination
-	users []*MemoryUser
-	valid ValidationStrategy
+	Destination  net.Destination
+	User         *MemoryUser
 }
 
-func NewServerSpec(dest net.Destination, valid ValidationStrategy, users ...*MemoryUser) *ServerSpec {
+func NewServerSpec(dest net.Destination, user *MemoryUser) *ServerSpec {
 	return &ServerSpec{
-		dest:  dest,
-		users: users,
-		valid: valid,
+		Destination: dest,
+		User:        user,
 	}
 }
 
 func NewServerSpecFromPB(spec *ServerEndpoint) (*ServerSpec, error) {
 	dest := net.TCPDestination(spec.Address.AsAddress(), net.Port(spec.Port))
-	mUsers := make([]*MemoryUser, len(spec.User))
-	for idx, u := range spec.User {
-		mUser, err := u.ToMemoryUser()
+	var dUser *MemoryUser
+	if spec.User != nil {
+		user, err := spec.User.ToMemoryUser()
 		if err != nil {
 			return nil, err
 		}
-		mUsers[idx] = mUser
-	}
-	return NewServerSpec(dest, AlwaysValid(), mUsers...), nil
-}
-
-func (s *ServerSpec) Destination() net.Destination {
-	return s.dest
-}
-
-func (s *ServerSpec) HasUser(user *MemoryUser) bool {
-	s.RLock()
-	defer s.RUnlock()
-
-	for _, u := range s.users {
-		if u.Account.Equals(user.Account) {
-			return true
-		}
+		dUser = user
 	}
-	return false
-}
-
-func (s *ServerSpec) AddUser(user *MemoryUser) {
-	if s.HasUser(user) {
-		return
-	}
-
-	s.Lock()
-	defer s.Unlock()
-
-	s.users = append(s.users, user)
-}
-
-func (s *ServerSpec) PickUser() *MemoryUser {
-	s.RLock()
-	defer s.RUnlock()
-
-	userCount := len(s.users)
-	switch userCount {
-	case 0:
-		return nil
-	case 1:
-		return s.users[0]
-	default:
-		return s.users[dice.Roll(userCount)]
-	}
-}
-
-func (s *ServerSpec) IsValid() bool {
-	return s.valid.IsValid()
-}
-
-func (s *ServerSpec) Invalidate() {
-	s.valid.Invalidate()
+	return NewServerSpec(dest, dUser), nil
 }

+ 3 - 3
common/protocol/server_spec.pb.go

@@ -28,7 +28,7 @@ type ServerEndpoint struct {
 
 	Address *net.IPOrDomain `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"`
 	Port    uint32          `protobuf:"varint,2,opt,name=port,proto3" json:"port,omitempty"`
-	User    []*User         `protobuf:"bytes,3,rep,name=user,proto3" json:"user,omitempty"`
+	User    *User           `protobuf:"bytes,3,opt,name=user,proto3" json:"user,omitempty"`
 }
 
 func (x *ServerEndpoint) Reset() {
@@ -75,7 +75,7 @@ func (x *ServerEndpoint) GetPort() uint32 {
 	return 0
 }
 
-func (x *ServerEndpoint) GetUser() []*User {
+func (x *ServerEndpoint) GetUser() *User {
 	if x != nil {
 		return x.User
 	}
@@ -98,7 +98,7 @@ var file_common_protocol_server_spec_proto_rawDesc = []byte{
 	0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x49, 0x50, 0x4f, 0x72, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e,
 	0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x6f, 0x72,
 	0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x2e, 0x0a,
-	0x04, 0x75, 0x73, 0x65, 0x72, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x78, 0x72,
+	0x04, 0x75, 0x73, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x78, 0x72,
 	0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63,
 	0x6f, 0x6c, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x04, 0x75, 0x73, 0x65, 0x72, 0x42, 0x5e, 0x0a,
 	0x18, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e,

+ 1 - 1
common/protocol/server_spec.proto

@@ -12,5 +12,5 @@ import "common/protocol/user.proto";
 message ServerEndpoint {
   xray.common.net.IPOrDomain address = 1;
   uint32 port = 2;
-  repeated xray.common.protocol.User user = 3;
+  xray.common.protocol.User user = 3;
 }

+ 0 - 79
common/protocol/server_spec_test.go

@@ -1,79 +0,0 @@
-package protocol_test
-
-import (
-	"strings"
-	"testing"
-	"time"
-
-	"github.com/xtls/xray-core/common"
-	"github.com/xtls/xray-core/common/net"
-	. "github.com/xtls/xray-core/common/protocol"
-	"github.com/xtls/xray-core/common/uuid"
-	"github.com/xtls/xray-core/proxy/vmess"
-)
-
-func TestAlwaysValidStrategy(t *testing.T) {
-	strategy := AlwaysValid()
-	if !strategy.IsValid() {
-		t.Error("strategy not valid")
-	}
-	strategy.Invalidate()
-	if !strategy.IsValid() {
-		t.Error("strategy not valid")
-	}
-}
-
-func TestTimeoutValidStrategy(t *testing.T) {
-	strategy := BeforeTime(time.Now().Add(2 * time.Second))
-	if !strategy.IsValid() {
-		t.Error("strategy not valid")
-	}
-	time.Sleep(3 * time.Second)
-	if strategy.IsValid() {
-		t.Error("strategy is valid")
-	}
-
-	strategy = BeforeTime(time.Now().Add(2 * time.Second))
-	strategy.Invalidate()
-	if strategy.IsValid() {
-		t.Error("strategy is valid")
-	}
-}
-
-func TestUserInServerSpec(t *testing.T) {
-	uuid1 := uuid.New()
-	uuid2 := uuid.New()
-
-	toAccount := func(a *vmess.Account) Account {
-		account, err := a.AsAccount()
-		common.Must(err)
-		return account
-	}
-
-	spec := NewServerSpec(net.Destination{}, AlwaysValid(), &MemoryUser{
-		Email:   "[email protected]",
-		Account: toAccount(&vmess.Account{Id: uuid1.String()}),
-	})
-	if spec.HasUser(&MemoryUser{
-		Email:   "[email protected]",
-		Account: toAccount(&vmess.Account{Id: uuid2.String()}),
-	}) {
-		t.Error("has user: ", uuid2)
-	}
-
-	spec.AddUser(&MemoryUser{Email: "[email protected]"})
-	if !spec.HasUser(&MemoryUser{
-		Email:   "[email protected]",
-		Account: toAccount(&vmess.Account{Id: uuid1.String()}),
-	}) {
-		t.Error("not having user: ", uuid1)
-	}
-}
-
-func TestPickUser(t *testing.T) {
-	spec := NewServerSpec(net.Destination{}, AlwaysValid(), &MemoryUser{Email: "[email protected]"}, &MemoryUser{Email: "[email protected]"}, &MemoryUser{Email: "[email protected]"})
-	user := spec.PickUser()
-	if !strings.HasSuffix(user.Email, "@example.com") {
-		t.Error("user: ", user.Email)
-	}
-}

+ 7 - 11
core/xray_test.go

@@ -63,17 +63,13 @@ func TestXrayClose(t *testing.T) {
 		Outbound: []*OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&outbound.Config{
-					Receiver: []*protocol.ServerEndpoint{
-						{
-							Address: net.NewIPOrDomain(net.LocalHostIP),
-							Port:    uint32(0),
-							User: []*protocol.User{
-								{
-									Account: serial.ToTypedMessage(&vmess.Account{
-										Id: userID.String(),
-									}),
-								},
-							},
+					Receiver: &protocol.ServerEndpoint{
+						Address: net.NewIPOrDomain(net.LocalHostIP),
+						Port:    uint32(0),
+						User:  &protocol.User{
+							Account: serial.ToTypedMessage(&vmess.Account{
+								Id: userID.String(),
+							}),
 						},
 					},
 				}),

+ 0 - 4
features/inbound/inbound.go

@@ -4,7 +4,6 @@ import (
 	"context"
 
 	"github.com/xtls/xray-core/common"
-	"github.com/xtls/xray-core/common/net"
 	"github.com/xtls/xray-core/common/serial"
 	"github.com/xtls/xray-core/features"
 )
@@ -20,9 +19,6 @@ type Handler interface {
 	ReceiverSettings() *serial.TypedMessage
 	// Returns the active proxy settings.
 	ProxySettings() *serial.TypedMessage
-
-	// Deprecated: Do not use in new code.
-	GetRandomInboundProxy() (interface{}, net.Port, int)
 }
 
 // Manager is a feature that manages InboundHandlers.

+ 14 - 5
infra/conf/http.go

@@ -68,12 +68,19 @@ func (v *HTTPClientConfig) Build() (proto.Message, error) {
 			{
 				Address: v.Address,
 				Port:    v.Port,
-				Users:   []json.RawMessage{{}},
 			},
 		}
+		if len(v.Username) > 0 {
+			v.Servers[0].Users = []json.RawMessage{{}}
+		}
+	}
+	if len(v.Servers) != 1 {
+		return nil, errors.New(`HTTP settings: "servers" should have one and only one member. Multiple endpoints in "servers" should use multiple HTTP outbounds and routing balancer instead`)
 	}
-	config.Server = make([]*protocol.ServerEndpoint, len(v.Servers))
-	for idx, serverConfig := range v.Servers {
+	for _, serverConfig := range v.Servers {
+		if len(serverConfig.Users) > 1 {
+			return nil, errors.New(`HTTP servers: "users" should have one member at most. Multiple members in "users" should use multiple HTTP outbounds and routing balancer instead`)
+		}
 		server := &protocol.ServerEndpoint{
 			Address: serverConfig.Address.Build(),
 			Port:    uint32(serverConfig.Port),
@@ -98,9 +105,11 @@ func (v *HTTPClientConfig) Build() (proto.Message, error) {
 				}
 			}
 			user.Account = serial.ToTypedMessage(account.Build())
-			server.User = append(server.User, user)
+			server.User = user
+			break
 		}
-		config.Server[idx] = server
+		config.Server = server
+		break
 	}
 	config.Header = make([]*http.Header, 0, 32)
 	for key, value := range v.Headers {

+ 19 - 23
infra/conf/shadowsocks.go

@@ -172,16 +172,16 @@ type ShadowsocksServerTarget struct {
 }
 
 type ShadowsocksClientConfig struct {
-	Address    *Address `json:"address"`
-	Port       uint16   `json:"port"`
-	Level      byte     `json:"level"`
-	Email      string   `json:"email"`
-	Cipher     string   `json:"method"`
-	Password   string   `json:"password"`
-	IVCheck    bool     `json:"ivCheck"`
-	UoT        bool     `json:"uot"`
-	UoTVersion int      `json:"uotVersion"`
-	Servers []*ShadowsocksServerTarget `json:"servers"`
+	Address    *Address                   `json:"address"`
+	Port       uint16                     `json:"port"`
+	Level      byte                       `json:"level"`
+	Email      string                     `json:"email"`
+	Cipher     string                     `json:"method"`
+	Password   string                     `json:"password"`
+	IVCheck    bool                       `json:"ivCheck"`
+	UoT        bool                       `json:"uot"`
+	UoTVersion int                        `json:"uotVersion"`
+	Servers    []*ShadowsocksServerTarget `json:"servers"`
 }
 
 func (v *ShadowsocksClientConfig) Build() (proto.Message, error) {
@@ -200,8 +200,8 @@ func (v *ShadowsocksClientConfig) Build() (proto.Message, error) {
 			},
 		}
 	}
-	if len(v.Servers) == 0 {
-		return nil, errors.New("0 Shadowsocks server configured.")
+	if len(v.Servers) != 1 {
+		return nil, errors.New(`Shadowsocks settings: "servers" should have one and only one member. Multiple endpoints in "servers" should use multiple Shadowsocks outbounds and routing balancer instead`)
 	}
 
 	if len(v.Servers) == 1 {
@@ -229,8 +229,7 @@ func (v *ShadowsocksClientConfig) Build() (proto.Message, error) {
 	}
 
 	config := new(shadowsocks.ClientConfig)
-	serverSpecs := make([]*protocol.ServerEndpoint, len(v.Servers))
-	for idx, server := range v.Servers {
+	for _, server := range v.Servers {
 		if C.Contains(shadowaead_2022.List, server.Cipher) {
 			return nil, errors.New("Shadowsocks 2022 accept no multi servers")
 		}
@@ -256,19 +255,16 @@ func (v *ShadowsocksClientConfig) Build() (proto.Message, error) {
 		ss := &protocol.ServerEndpoint{
 			Address: server.Address.Build(),
 			Port:    uint32(server.Port),
-			User: []*protocol.User{
-				{
-					Level:   uint32(server.Level),
-					Email:   server.Email,
-					Account: serial.ToTypedMessage(account),
-				},
+			User:    &protocol.User{
+				Level:   uint32(server.Level),
+				Email:   server.Email,
+				Account: serial.ToTypedMessage(account),
 			},
 		}
 
-		serverSpecs[idx] = ss
+		config.Server = ss
+		break
 	}
 
-	config.Server = serverSpecs
-
 	return config, nil
 }

+ 14 - 5
infra/conf/socks.go

@@ -86,12 +86,19 @@ func (v *SocksClientConfig) Build() (proto.Message, error) {
 			{
 				Address: v.Address,
 				Port:    v.Port,
-				Users:   []json.RawMessage{{}},
 			},
 		}
+		if len(v.Username) > 0 {
+			v.Servers[0].Users = []json.RawMessage{{}}
+		}
+	}
+	if len(v.Servers) != 1 {
+		return nil, errors.New(`SOCKS settings: "servers" should have one and only one member. Multiple endpoints in "servers" should use multiple SOCKS outbounds and routing balancer instead`)
 	}
-	config.Server = make([]*protocol.ServerEndpoint, len(v.Servers))
-	for idx, serverConfig := range v.Servers {
+	for _, serverConfig := range v.Servers {
+		if len(serverConfig.Users) > 1 {
+			return nil, errors.New(`SOCKS servers: "users" should have one member at most. Multiple members in "users" should use multiple SOCKS outbounds and routing balancer instead`)
+		}
 		server := &protocol.ServerEndpoint{
 			Address: serverConfig.Address.Build(),
 			Port:    uint32(serverConfig.Port),
@@ -116,9 +123,11 @@ func (v *SocksClientConfig) Build() (proto.Message, error) {
 				}
 			}
 			user.Account = serial.ToTypedMessage(account.Build())
-			server.User = append(server.User, user)
+			server.User = user
+			break
 		}
-		config.Server[idx] = server
+		config.Server = server
+		break
 	}
 	return config, nil
 }

+ 24 - 32
infra/conf/socks_test.go

@@ -65,24 +65,20 @@ func TestSocksOutboundConfig(t *testing.T) {
 			}`,
 			Parser: loadJSON(creator),
 			Output: &socks.ClientConfig{
-				Server: []*protocol.ServerEndpoint{
-					{
-						Address: &net.IPOrDomain{
-							Address: &net.IPOrDomain_Ip{
-								Ip: []byte{127, 0, 0, 1},
-							},
-						},
-						Port: 1234,
-						User: []*protocol.User{
-							{
-								Email: "[email protected]",
-								Account: serial.ToTypedMessage(&socks.Account{
-									Username: "test user",
-									Password: "test pass",
-								}),
-							},
+				Server: &protocol.ServerEndpoint{
+					Address: &net.IPOrDomain{
+						Address: &net.IPOrDomain_Ip{
+							Ip: []byte{127, 0, 0, 1},
 						},
 					},
+					Port: 1234,
+					User: &protocol.User{
+						Email: "[email protected]",
+						Account: serial.ToTypedMessage(&socks.Account{
+							Username: "test user",
+							Password: "test pass",
+						}),
+					},
 				},
 			},
 		},
@@ -96,24 +92,20 @@ func TestSocksOutboundConfig(t *testing.T) {
 			}`,
 			Parser: loadJSON(creator),
 			Output: &socks.ClientConfig{
-				Server: []*protocol.ServerEndpoint{
-					{
-						Address: &net.IPOrDomain{
-							Address: &net.IPOrDomain_Ip{
-								Ip: []byte{127, 0, 0, 1},
-							},
-						},
-						Port: 1234,
-						User: []*protocol.User{
-							{
-								Email: "[email protected]",
-								Account: serial.ToTypedMessage(&socks.Account{
-									Username: "test user",
-									Password: "test pass",
-								}),
-							},
+				Server: &protocol.ServerEndpoint{
+					Address: &net.IPOrDomain{
+						Address: &net.IPOrDomain_Ip{
+							Ip: []byte{127, 0, 0, 1},
 						},
 					},
+					Port: 1234,
+					User: &protocol.User{
+						Email: "[email protected]",
+						Account: serial.ToTypedMessage(&socks.Account{
+							Username: "test user",
+							Password: "test pass",
+						}),
+					},
 				},
 			},
 		},

+ 20 - 22
infra/conf/trojan.go

@@ -28,13 +28,13 @@ type TrojanServerTarget struct {
 
 // TrojanClientConfig is configuration of trojan servers
 type TrojanClientConfig struct {
-	Address  *Address `json:"address"`
-	Port     uint16   `json:"port"`
-	Level    byte     `json:"level"`
-	Email    string   `json:"email"`
-	Password string   `json:"password"`
-	Flow     string   `json:"flow"`
-	Servers []*TrojanServerTarget `json:"servers"`
+	Address  *Address              `json:"address"`
+	Port     uint16                `json:"port"`
+	Level    byte                  `json:"level"`
+	Email    string                `json:"email"`
+	Password string                `json:"password"`
+	Flow     string                `json:"flow"`
+	Servers  []*TrojanServerTarget `json:"servers"`
 }
 
 // Build implements Buildable
@@ -51,15 +51,13 @@ func (c *TrojanClientConfig) Build() (proto.Message, error) {
 			},
 		}
 	}
-	if len(c.Servers) == 0 {
-		return nil, errors.New("0 Trojan server configured.")
+	if len(c.Servers) != 1 {
+		return nil, errors.New(`Trojan settings: "servers" should have one and only one member. Multiple endpoints in "servers" should use multiple Trojan outbounds and routing balancer instead`)
 	}
 
-	config := &trojan.ClientConfig{
-		Server: make([]*protocol.ServerEndpoint, len(c.Servers)),
-	}
+	config := &trojan.ClientConfig{}
 
-	for idx, rec := range c.Servers {
+	for _, rec := range c.Servers {
 		if rec.Address == nil {
 			return nil, errors.New("Trojan server address is not set.")
 		}
@@ -73,19 +71,19 @@ func (c *TrojanClientConfig) Build() (proto.Message, error) {
 			return nil, errors.PrintRemovedFeatureError(`Flow for Trojan`, ``)
 		}
 
-		config.Server[idx] = &protocol.ServerEndpoint{
+		config.Server = &protocol.ServerEndpoint{
 			Address: rec.Address.Build(),
 			Port:    uint32(rec.Port),
-			User: []*protocol.User{
-				{
-					Level: uint32(rec.Level),
-					Email: rec.Email,
-					Account: serial.ToTypedMessage(&trojan.Account{
-						Password: rec.Password,
-					}),
-				},
+			User:    &protocol.User{
+				Level: uint32(rec.Level),
+				Email: rec.Email,
+				Account: serial.ToTypedMessage(&trojan.Account{
+					Password: rec.Password,
+				}),
 			},
 		}
+
+		break
 	}
 
 	return config, nil

+ 8 - 8
infra/conf/vless.go

@@ -228,22 +228,20 @@ func (c *VLessOutboundConfig) Build() (proto.Message, error) {
 		}
 	}
 	if len(c.Vnext) != 1 {
-		return nil, errors.New(`VLESS settings: "vnext" should have one and only one member`)
+		return nil, errors.New(`VLESS settings: "vnext" should have one and only one member. Multiple endpoints in "vnext" should use multiple VLESS outbounds and routing balancer instead`)
 	}
-	config.Vnext = make([]*protocol.ServerEndpoint, len(c.Vnext))
-	for idx, rec := range c.Vnext {
+	for _, rec := range c.Vnext {
 		if rec.Address == nil {
 			return nil, errors.New(`VLESS vnext: "address" is not set`)
 		}
 		if len(rec.Users) != 1 {
-			return nil, errors.New(`VLESS vnext: "users" should have one and only one member`)
+			return nil, errors.New(`VLESS vnext: "users" should have one and only one member. Multiple members in "users" should use multiple VLESS outbounds and routing balancer instead`)
 		}
 		spec := &protocol.ServerEndpoint{
 			Address: rec.Address.Build(),
 			Port:    uint32(rec.Port),
-			User:    make([]*protocol.User, len(rec.Users)),
 		}
-		for idx, rawUser := range rec.Users {
+		for _, rawUser := range rec.Users {
 			user := new(protocol.User)
 			if c.Address != nil {
 				user.Level = c.Level
@@ -327,9 +325,11 @@ func (c *VLessOutboundConfig) Build() (proto.Message, error) {
 			}
 
 			user.Account = serial.ToTypedMessage(account)
-			spec.User[idx] = user
+			spec.User = user
+			break
 		}
-		config.Vnext[idx] = spec
+		config.Vnext = spec
+		break
 	}
 
 	return config, nil

+ 26 - 34
infra/conf/vless_test.go

@@ -35,25 +35,21 @@ func TestVLessOutbound(t *testing.T) {
 			}`,
 			Parser: loadJSON(creator),
 			Output: &outbound.Config{
-				Vnext: []*protocol.ServerEndpoint{
-					{
-						Address: &net.IPOrDomain{
-							Address: &net.IPOrDomain_Domain{
-								Domain: "example.com",
-							},
-						},
-						Port: 443,
-						User: []*protocol.User{
-							{
-								Account: serial.ToTypedMessage(&vless.Account{
-									Id:         "27848739-7e62-4138-9fd3-098a63964b6b",
-									Flow:       "xtls-rprx-vision-udp443",
-									Encryption: "none",
-								}),
-								Level: 0,
-							},
+				Vnext: &protocol.ServerEndpoint{
+					Address: &net.IPOrDomain{
+						Address: &net.IPOrDomain_Domain{
+							Domain: "example.com",
 						},
 					},
+					Port: 443,
+					User: &protocol.User{
+						Account: serial.ToTypedMessage(&vless.Account{
+							Id:         "27848739-7e62-4138-9fd3-098a63964b6b",
+							Flow:       "xtls-rprx-vision-udp443",
+							Encryption: "none",
+						}),
+						Level: 0,
+					},
 				},
 			},
 		},
@@ -68,25 +64,21 @@ func TestVLessOutbound(t *testing.T) {
 			}`,
 			Parser: loadJSON(creator),
 			Output: &outbound.Config{
-				Vnext: []*protocol.ServerEndpoint{
-					{
-						Address: &net.IPOrDomain{
-							Address: &net.IPOrDomain_Domain{
-								Domain: "example.com",
-							},
-						},
-						Port: 443,
-						User: []*protocol.User{
-							{
-								Account: serial.ToTypedMessage(&vless.Account{
-									Id:         "27848739-7e62-4138-9fd3-098a63964b6b",
-									Flow:       "xtls-rprx-vision-udp443",
-									Encryption: "none",
-								}),
-								Level: 0,
-							},
+				Vnext: &protocol.ServerEndpoint{
+					Address: &net.IPOrDomain{
+						Address: &net.IPOrDomain_Domain{
+							Domain: "example.com",
 						},
 					},
+					Port: 443,
+					User: &protocol.User{
+						Account: serial.ToTypedMessage(&vless.Account{
+							Id:         "27848739-7e62-4138-9fd3-098a63964b6b",
+							Flow:       "xtls-rprx-vision-udp443",
+							Encryption: "none",
+						}),
+						Level: 0,
+					},
 				},
 			},
 		},

+ 10 - 26
infra/conf/vmess.go

@@ -46,17 +46,6 @@ func (a *VMessAccount) Build() *vmess.Account {
 	}
 }
 
-type VMessDetourConfig struct {
-	ToTag string `json:"to"`
-}
-
-// Build implements Buildable
-func (c *VMessDetourConfig) Build() *inbound.DetourConfig {
-	return &inbound.DetourConfig{
-		To: c.ToTag,
-	}
-}
-
 type VMessDefaultConfig struct {
 	Level byte `json:"level"`
 }
@@ -71,7 +60,6 @@ func (c *VMessDefaultConfig) Build() *inbound.DefaultConfig {
 type VMessInboundConfig struct {
 	Users        []json.RawMessage   `json:"clients"`
 	Defaults     *VMessDefaultConfig `json:"default"`
-	DetourConfig *VMessDetourConfig  `json:"detour"`
 }
 
 // Build implements Buildable
@@ -82,10 +70,6 @@ func (c *VMessInboundConfig) Build() (proto.Message, error) {
 		config.Default = c.Defaults.Build()
 	}
 
-	if c.DetourConfig != nil {
-		config.Detour = c.DetourConfig.Build()
-	}
-
 	config.User = make([]*protocol.User, len(c.Users))
 	for idx, rawData := range c.Users {
 		user := new(protocol.User)
@@ -139,16 +123,15 @@ func (c *VMessOutboundConfig) Build() (proto.Message, error) {
 			},
 		}
 	}
-	if len(c.Receivers) == 0 {
-		return nil, errors.New("0 VMess receiver configured")
+	if len(c.Receivers) != 1 {
+		return nil, errors.New(`VMess settings: "vnext" should have one and only one member. Multiple endpoints in "vnext" should use multiple VMess outbounds and routing balancer instead`)
 	}
-	serverSpecs := make([]*protocol.ServerEndpoint, len(c.Receivers))
-	for idx, rec := range c.Receivers {
-		if len(rec.Users) == 0 {
-			return nil, errors.New("0 user configured for VMess outbound")
+	for _, rec := range c.Receivers {
+		if len(rec.Users) != 1 {
+			return nil, errors.New(`VMess vnext: "users" should have one and only one member. Multiple members in "users" should use multiple VMess outbounds and routing balancer instead`)
 		}
 		if rec.Address == nil {
-			return nil, errors.New("address is not set in VMess outbound config")
+			return nil, errors.New(`VMess vnext: "address" is not set`)
 		}
 		spec := &protocol.ServerEndpoint{
 			Address: rec.Address.Build(),
@@ -182,10 +165,11 @@ func (c *VMessOutboundConfig) Build() (proto.Message, error) {
 			account.ID = u.String()
 
 			user.Account = serial.ToTypedMessage(account.Build())
-			spec.User = append(spec.User, user)
+			spec.User = user
+			break
 		}
-		serverSpecs[idx] = spec
+		config.Receiver = spec
+		break
 	}
-	config.Receiver = serverSpecs
 	return config, nil
 }

+ 29 - 44
infra/conf/vmess_test.go

@@ -34,26 +34,22 @@ func TestVMessOutbound(t *testing.T) {
 			}`,
 			Parser: loadJSON(creator),
 			Output: &outbound.Config{
-				Receiver: []*protocol.ServerEndpoint{
-					{
-						Address: &net.IPOrDomain{
-							Address: &net.IPOrDomain_Ip{
-								Ip: []byte{127, 0, 0, 1},
-							},
+				Receiver: &protocol.ServerEndpoint{
+					Address: &net.IPOrDomain{
+						Address: &net.IPOrDomain_Ip{
+							Ip: []byte{127, 0, 0, 1},
 						},
-						Port: 80,
-						User: []*protocol.User{
-							{
-								Email: "[email protected]",
-								Level: 255,
-								Account: serial.ToTypedMessage(&vmess.Account{
-									Id: "e641f5ad-9397-41e3-bf1a-e8740dfed019",
-									SecuritySettings: &protocol.SecurityConfig{
-										Type: protocol.SecurityType_AUTO,
-									},
-								}),
+					},
+					Port: 80,
+					User: &protocol.User{
+						Email: "[email protected]",
+						Level: 255,
+						Account: serial.ToTypedMessage(&vmess.Account{
+							Id: "e641f5ad-9397-41e3-bf1a-e8740dfed019",
+							SecuritySettings: &protocol.SecurityConfig{
+								Type: protocol.SecurityType_AUTO,
 							},
-						},
+						}),
 					},
 				},
 			},
@@ -68,26 +64,22 @@ func TestVMessOutbound(t *testing.T) {
 			}`,
 			Parser: loadJSON(creator),
 			Output: &outbound.Config{
-				Receiver: []*protocol.ServerEndpoint{
-					{
-						Address: &net.IPOrDomain{
-							Address: &net.IPOrDomain_Ip{
-								Ip: []byte{127, 0, 0, 1},
-							},
+				Receiver: &protocol.ServerEndpoint{
+					Address: &net.IPOrDomain{
+						Address: &net.IPOrDomain_Ip{
+							Ip: []byte{127, 0, 0, 1},
 						},
-						Port: 80,
-						User: []*protocol.User{
-							{
-								Email: "[email protected]",
-								Level: 255,
-								Account: serial.ToTypedMessage(&vmess.Account{
-									Id: "e641f5ad-9397-41e3-bf1a-e8740dfed019",
-									SecuritySettings: &protocol.SecurityConfig{
-										Type: protocol.SecurityType_AUTO,
-									},
-								}),
+					},
+					Port: 80,
+					User: &protocol.User{
+						Email: "[email protected]",
+						Level: 255,
+						Account: serial.ToTypedMessage(&vmess.Account{
+							Id: "e641f5ad-9397-41e3-bf1a-e8740dfed019",
+							SecuritySettings: &protocol.SecurityConfig{
+								Type: protocol.SecurityType_AUTO,
 							},
-						},
+						}),
 					},
 				},
 			},
@@ -113,11 +105,7 @@ func TestVMessInbound(t *testing.T) {
 				],
 				"default": {
 					"level": 0
-				},
-				"detour": {
-					"to": "tag_to_detour"
-				},
-				"disableInsecureEncryption": true
+				}
 			}`,
 			Parser: loadJSON(creator),
 			Output: &inbound.Config{
@@ -136,9 +124,6 @@ func TestVMessInbound(t *testing.T) {
 				Default: &inbound.DefaultConfig{
 					Level: 0,
 				},
-				Detour: &inbound.DetourConfig{
-					To: "tag_to_detour",
-				},
 			},
 		},
 	})

+ 0 - 60
infra/conf/xray.go

@@ -3,7 +3,6 @@ package conf
 import (
 	"context"
 	"encoding/json"
-	"fmt"
 	"log"
 	"os"
 	"path/filepath"
@@ -120,47 +119,12 @@ func (m *MuxConfig) Build() (*proxyman.MultiplexingConfig, error) {
 	}, nil
 }
 
-type InboundDetourAllocationConfig struct {
-	Strategy    string  `json:"strategy"`
-	Concurrency *uint32 `json:"concurrency"`
-	RefreshMin  *uint32 `json:"refresh"`
-}
-
-// Build implements Buildable.
-func (c *InboundDetourAllocationConfig) Build() (*proxyman.AllocationStrategy, error) {
-	config := new(proxyman.AllocationStrategy)
-	switch strings.ToLower(c.Strategy) {
-	case "always":
-		config.Type = proxyman.AllocationStrategy_Always
-	case "random":
-		config.Type = proxyman.AllocationStrategy_Random
-	case "external":
-		config.Type = proxyman.AllocationStrategy_External
-	default:
-		return nil, errors.New("unknown allocation strategy: ", c.Strategy)
-	}
-	if c.Concurrency != nil {
-		config.Concurrency = &proxyman.AllocationStrategy_AllocationStrategyConcurrency{
-			Value: *c.Concurrency,
-		}
-	}
-
-	if c.RefreshMin != nil {
-		config.Refresh = &proxyman.AllocationStrategy_AllocationStrategyRefresh{
-			Value: *c.RefreshMin,
-		}
-	}
-
-	return config, nil
-}
-
 type InboundDetourConfig struct {
 	Protocol       string                         `json:"protocol"`
 	PortList       *PortList                      `json:"port"`
 	ListenOn       *Address                       `json:"listen"`
 	Settings       *json.RawMessage               `json:"settings"`
 	Tag            string                         `json:"tag"`
-	Allocation     *InboundDetourAllocationConfig `json:"allocate"`
 	StreamSetting  *StreamConfig                  `json:"streamSettings"`
 	SniffingConfig *SniffingConfig                `json:"sniffing"`
 }
@@ -197,30 +161,6 @@ func (c *InboundDetourConfig) Build() (*core.InboundHandlerConfig, error) {
 		}
 	}
 
-	if c.Allocation != nil {
-		concurrency := -1
-		if c.Allocation.Concurrency != nil && c.Allocation.Strategy == "random" {
-			concurrency = int(*c.Allocation.Concurrency)
-		}
-		portRange := 0
-
-		for _, pr := range c.PortList.Range {
-			portRange += int(pr.To - pr.From + 1)
-		}
-		if concurrency >= 0 && concurrency >= portRange {
-			var ports strings.Builder
-			for _, pr := range c.PortList.Range {
-				fmt.Fprintf(&ports, "%d-%d ", pr.From, pr.To)
-			}
-			return nil, errors.New("not enough ports. concurrency = ", concurrency, " ports: ", ports.String())
-		}
-
-		as, err := c.Allocation.Build()
-		if err != nil {
-			return nil, err
-		}
-		receiverSettings.AllocationStrategy = as
-	}
 	if c.StreamSetting != nil {
 		ss, err := c.StreamSetting.Build()
 		if err != nil {

+ 0 - 10
infra/conf/xray_test.go

@@ -58,10 +58,6 @@ func TestXrayConfig(t *testing.T) {
 					},
 					"protocol": "vmess",
 					"port": "443-500",
-					"allocate": {
-						"strategy": "random",
-						"concurrency": 3
-					},
 					"settings": {
 						"clients": [
 							{
@@ -123,12 +119,6 @@ func TestXrayConfig(t *testing.T) {
 								From: 443,
 								To:   500,
 							}}},
-							AllocationStrategy: &proxyman.AllocationStrategy{
-								Type: proxyman.AllocationStrategy_Random,
-								Concurrency: &proxyman.AllocationStrategy_AllocationStrategyConcurrency{
-									Value: 3,
-								},
-							},
 							StreamSettings: &internet.StreamConfig{
 								ProtocolName: "websocket",
 								TransportSettings: []*internet.TransportConfig{

+ 10 - 16
proxy/http/client.go

@@ -31,7 +31,7 @@ import (
 )
 
 type Client struct {
-	serverPicker  protocol.ServerPicker
+	server        *protocol.ServerSpec
 	policyManager policy.Manager
 	header        []*Header
 }
@@ -48,21 +48,17 @@ var (
 
 // NewClient create a new http client based on the given config.
 func NewClient(ctx context.Context, config *ClientConfig) (*Client, error) {
-	serverList := protocol.NewServerList()
-	for _, rec := range config.Server {
-		s, err := protocol.NewServerSpecFromPB(rec)
-		if err != nil {
-			return nil, errors.New("failed to get server spec").Base(err)
-		}
-		serverList.AddServer(s)
+	if config.Server == nil {
+		return nil, errors.New(`no target server found`)
 	}
-	if serverList.Size() == 0 {
-		return nil, errors.New("0 target server")
+	server, err := protocol.NewServerSpecFromPB(config.Server)
+	if err != nil {
+		return nil, errors.New("failed to get server spec").Base(err)
 	}
 
 	v := core.MustFromContext(ctx)
 	return &Client{
-		serverPicker:  protocol.NewRoundRobinServerPicker(serverList),
+		server:        server,
 		policyManager: v.GetFeature(policy.ManagerType()).(policy.Manager),
 		header:        config.Header,
 	}, nil
@@ -84,7 +80,9 @@ func (c *Client) Process(ctx context.Context, link *transport.Link, dialer inter
 		return errors.New("UDP is not supported by HTTP outbound")
 	}
 
-	var user *protocol.MemoryUser
+	server := c.server
+	dest := server.Destination
+	user := server.User
 	var conn stat.Connection
 
 	mbuf, _ := link.Reader.ReadMultiBuffer()
@@ -102,10 +100,6 @@ func (c *Client) Process(ctx context.Context, link *transport.Link, dialer inter
 	}
 
 	if err := retry.ExponentialBackoff(5, 100).On(func() error {
-		server := c.serverPicker.PickServer()
-		dest := server.Destination()
-		user = server.PickUser()
-
 		netConn, err := setUpHTTPTunnel(ctx, dest, targetAddr, user, dialer, header, firstPayload)
 		if netConn != nil {
 			if _, ok := netConn.(*http2Conn); !ok {

+ 4 - 4
proxy/http/config.pb.go

@@ -196,8 +196,8 @@ type ClientConfig struct {
 	unknownFields protoimpl.UnknownFields
 
 	// Sever is a list of HTTP server addresses.
-	Server []*protocol.ServerEndpoint `protobuf:"bytes,1,rep,name=server,proto3" json:"server,omitempty"`
-	Header []*Header                  `protobuf:"bytes,2,rep,name=header,proto3" json:"header,omitempty"`
+	Server *protocol.ServerEndpoint `protobuf:"bytes,1,opt,name=server,proto3" json:"server,omitempty"`
+	Header []*Header                `protobuf:"bytes,2,rep,name=header,proto3" json:"header,omitempty"`
 }
 
 func (x *ClientConfig) Reset() {
@@ -230,7 +230,7 @@ func (*ClientConfig) Descriptor() ([]byte, []int) {
 	return file_proxy_http_config_proto_rawDescGZIP(), []int{3}
 }
 
-func (x *ClientConfig) GetServer() []*protocol.ServerEndpoint {
+func (x *ClientConfig) GetServer() *protocol.ServerEndpoint {
 	if x != nil {
 		return x.Server
 	}
@@ -275,7 +275,7 @@ var file_proxy_http_config_proto_rawDesc = []byte{
 	0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05,
 	0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x7d, 0x0a, 0x0c, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43,
 	0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x3c, 0x0a, 0x06, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x18,
-	0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d,
+	0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d,
 	0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2e, 0x53, 0x65, 0x72,
 	0x76, 0x65, 0x72, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x06, 0x73, 0x65, 0x72,
 	0x76, 0x65, 0x72, 0x12, 0x2f, 0x0a, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x02, 0x20,

+ 1 - 1
proxy/http/config.proto

@@ -28,6 +28,6 @@ message Header {
 // ClientConfig is the protobuf config for HTTP proxy client.
 message ClientConfig {
   // Sever is a list of HTTP server addresses.
-  repeated xray.common.protocol.ServerEndpoint server = 1;
+  xray.common.protocol.ServerEndpoint server = 1;
   repeated Header header = 2;
 }

+ 12 - 17
proxy/shadowsocks/client.go

@@ -22,27 +22,23 @@ import (
 
 // Client is a inbound handler for Shadowsocks protocol
 type Client struct {
-	serverPicker  protocol.ServerPicker
+	server        *protocol.ServerSpec
 	policyManager policy.Manager
 }
 
 // NewClient create a new Shadowsocks client.
 func NewClient(ctx context.Context, config *ClientConfig) (*Client, error) {
-	serverList := protocol.NewServerList()
-	for _, rec := range config.Server {
-		s, err := protocol.NewServerSpecFromPB(rec)
-		if err != nil {
-			return nil, errors.New("failed to parse server spec").Base(err)
-		}
-		serverList.AddServer(s)
+	if config.Server == nil {
+		return nil, errors.New(`no target server found`)
 	}
-	if serverList.Size() == 0 {
-		return nil, errors.New("0 server")
+	server, err := protocol.NewServerSpecFromPB(config.Server)
+	if err != nil {
+		return nil, errors.New("failed to get server spec").Base(err)
 	}
 
 	v := core.MustFromContext(ctx)
 	client := &Client{
-		serverPicker:  protocol.NewRoundRobinServerPicker(serverList),
+		server:        server,
 		policyManager: v.GetFeature(policy.ManagerType()).(policy.Manager),
 	}
 	return client, nil
@@ -60,13 +56,12 @@ func (c *Client) Process(ctx context.Context, link *transport.Link, dialer inter
 	destination := ob.Target
 	network := destination.Network
 
-	var server *protocol.ServerSpec
+	server := c.server
+	dest := server.Destination
+	dest.Network = network
 	var conn stat.Connection
 
 	err := retry.ExponentialBackoff(5, 100).On(func() error {
-		server = c.serverPicker.PickServer()
-		dest := server.Destination()
-		dest.Network = network
 		rawConn, err := dialer.Dial(ctx, dest)
 		if err != nil {
 			return err
@@ -78,7 +73,7 @@ func (c *Client) Process(ctx context.Context, link *transport.Link, dialer inter
 	if err != nil {
 		return errors.New("failed to find an available destination").AtWarning().Base(err)
 	}
-	errors.LogInfo(ctx, "tunneling request to ", destination, " via ", network, ":", server.Destination().NetAddr())
+	errors.LogInfo(ctx, "tunneling request to ", destination, " via ", network, ":", server.Destination.NetAddr())
 
 	defer conn.Close()
 
@@ -93,7 +88,7 @@ func (c *Client) Process(ctx context.Context, link *transport.Link, dialer inter
 		request.Command = protocol.RequestCommandUDP
 	}
 
-	user := server.PickUser()
+	user := server.User
 	_, ok := user.Account.(*MemoryAccount)
 	if !ok {
 		return errors.New("user account is not valid")

+ 3 - 3
proxy/shadowsocks/config.pb.go

@@ -199,7 +199,7 @@ type ClientConfig struct {
 	sizeCache     protoimpl.SizeCache
 	unknownFields protoimpl.UnknownFields
 
-	Server []*protocol.ServerEndpoint `protobuf:"bytes,1,rep,name=server,proto3" json:"server,omitempty"`
+	Server *protocol.ServerEndpoint `protobuf:"bytes,1,opt,name=server,proto3" json:"server,omitempty"`
 }
 
 func (x *ClientConfig) Reset() {
@@ -232,7 +232,7 @@ func (*ClientConfig) Descriptor() ([]byte, []int) {
 	return file_proxy_shadowsocks_config_proto_rawDescGZIP(), []int{2}
 }
 
-func (x *ClientConfig) GetServer() []*protocol.ServerEndpoint {
+func (x *ClientConfig) GetServer() *protocol.ServerEndpoint {
 	if x != nil {
 		return x.Server
 	}
@@ -268,7 +268,7 @@ var file_proxy_shadowsocks_config_proto_rawDesc = []byte{
 	0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x4e,
 	0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x52, 0x07, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x22,
 	0x4c, 0x0a, 0x0c, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12,
-	0x3c, 0x0a, 0x06, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32,
+	0x3c, 0x0a, 0x06, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32,
 	0x24, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72,
 	0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x45, 0x6e, 0x64,
 	0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x06, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2a, 0x74, 0x0a,

+ 1 - 1
proxy/shadowsocks/config.proto

@@ -32,5 +32,5 @@ message ServerConfig {
 }
 
 message ClientConfig {
-  repeated xray.common.protocol.ServerEndpoint server = 1;
+  xray.common.protocol.ServerEndpoint server = 1;
 }

+ 10 - 17
proxy/socks/client.go

@@ -22,27 +22,23 @@ import (
 
 // Client is a Socks5 client.
 type Client struct {
-	serverPicker  protocol.ServerPicker
+	server        *protocol.ServerSpec
 	policyManager policy.Manager
 }
 
 // NewClient create a new Socks5 client based on the given config.
 func NewClient(ctx context.Context, config *ClientConfig) (*Client, error) {
-	serverList := protocol.NewServerList()
-	for _, rec := range config.Server {
-		s, err := protocol.NewServerSpecFromPB(rec)
-		if err != nil {
-			return nil, errors.New("failed to get server spec").Base(err)
-		}
-		serverList.AddServer(s)
+	if config.Server == nil {
+		return nil, errors.New(`no target server found`)
 	}
-	if serverList.Size() == 0 {
-		return nil, errors.New("0 target server")
+	server, err := protocol.NewServerSpecFromPB(config.Server)
+	if err != nil {
+		return nil, errors.New("failed to get server spec").Base(err)
 	}
 
 	v := core.MustFromContext(ctx)
 	c := &Client{
-		serverPicker:  protocol.NewRoundRobinServerPicker(serverList),
+		server:        server,
 		policyManager: v.GetFeature(policy.ManagerType()).(policy.Manager),
 	}
 
@@ -62,15 +58,12 @@ func (c *Client) Process(ctx context.Context, link *transport.Link, dialer inter
 	destination := ob.Target
 
 	// Outbound server.
-	var server *protocol.ServerSpec
-	// Outbound server's destination.
-	var dest net.Destination
+	server := c.server
+	dest := server.Destination
 	// Connection to the outbound server.
 	var conn stat.Connection
 
 	if err := retry.ExponentialBackoff(5, 100).On(func() error {
-		server = c.serverPicker.PickServer()
-		dest = server.Destination()
 		rawConn, err := dialer.Dial(ctx, dest)
 		if err != nil {
 			return err
@@ -101,7 +94,7 @@ func (c *Client) Process(ctx context.Context, link *transport.Link, dialer inter
 		request.Command = protocol.RequestCommandUDP
 	}
 
-	user := server.PickUser()
+	user := server.User
 	if user != nil {
 		request.User = user
 		p = c.policyManager.ForLevel(user.Level)

+ 3 - 3
proxy/socks/config.pb.go

@@ -210,7 +210,7 @@ type ClientConfig struct {
 	unknownFields protoimpl.UnknownFields
 
 	// Sever is a list of Socks server addresses.
-	Server []*protocol.ServerEndpoint `protobuf:"bytes,1,rep,name=server,proto3" json:"server,omitempty"`
+	Server *protocol.ServerEndpoint `protobuf:"bytes,1,opt,name=server,proto3" json:"server,omitempty"`
 }
 
 func (x *ClientConfig) Reset() {
@@ -243,7 +243,7 @@ func (*ClientConfig) Descriptor() ([]byte, []int) {
 	return file_proxy_socks_config_proto_rawDescGZIP(), []int{2}
 }
 
-func (x *ClientConfig) GetServer() []*protocol.ServerEndpoint {
+func (x *ClientConfig) GetServer() *protocol.ServerEndpoint {
 	if x != nil {
 		return x.Server
 	}
@@ -286,7 +286,7 @@ var file_proxy_socks_config_proto_rawDesc = []byte{
 	0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65,
 	0x3a, 0x02, 0x38, 0x01, 0x22, 0x4c, 0x0a, 0x0c, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43, 0x6f,
 	0x6e, 0x66, 0x69, 0x67, 0x12, 0x3c, 0x0a, 0x06, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x18, 0x01,
-	0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d,
+	0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d,
 	0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2e, 0x53, 0x65, 0x72, 0x76,
 	0x65, 0x72, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x06, 0x73, 0x65, 0x72, 0x76,
 	0x65, 0x72, 0x2a, 0x25, 0x0a, 0x08, 0x41, 0x75, 0x74, 0x68, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0b,

+ 1 - 1
proxy/socks/config.proto

@@ -35,5 +35,5 @@ message ServerConfig {
 // ClientConfig is the protobuf config for Socks client.
 message ClientConfig {
   // Sever is a list of Socks server addresses.
-  repeated xray.common.protocol.ServerEndpoint server = 1;
+  xray.common.protocol.ServerEndpoint server = 1;
 }

+ 11 - 16
proxy/trojan/client.go

@@ -22,27 +22,23 @@ import (
 
 // Client is a inbound handler for trojan protocol
 type Client struct {
-	serverPicker  protocol.ServerPicker
+	server        *protocol.ServerSpec
 	policyManager policy.Manager
 }
 
 // NewClient create a new trojan client.
 func NewClient(ctx context.Context, config *ClientConfig) (*Client, error) {
-	serverList := protocol.NewServerList()
-	for _, rec := range config.Server {
-		s, err := protocol.NewServerSpecFromPB(rec)
-		if err != nil {
-			return nil, errors.New("failed to parse server spec").Base(err)
-		}
-		serverList.AddServer(s)
+	if config.Server == nil {
+		return nil, errors.New(`no target server found`)
 	}
-	if serverList.Size() == 0 {
-		return nil, errors.New("0 server")
+	server, err := protocol.NewServerSpecFromPB(config.Server)
+	if err != nil {
+		return nil, errors.New("failed to get server spec").Base(err)
 	}
 
 	v := core.MustFromContext(ctx)
 	client := &Client{
-		serverPicker:  protocol.NewRoundRobinServerPicker(serverList),
+		server:        server,
 		policyManager: v.GetFeature(policy.ManagerType()).(policy.Manager),
 	}
 	return client, nil
@@ -60,12 +56,11 @@ func (c *Client) Process(ctx context.Context, link *transport.Link, dialer inter
 	destination := ob.Target
 	network := destination.Network
 
-	var server *protocol.ServerSpec
+	server := c.server
 	var conn stat.Connection
 
 	err := retry.ExponentialBackoff(5, 100).On(func() error {
-		server = c.serverPicker.PickServer()
-		rawConn, err := dialer.Dial(ctx, server.Destination())
+		rawConn, err := dialer.Dial(ctx, server.Destination)
 		if err != nil {
 			return err
 		}
@@ -76,11 +71,11 @@ func (c *Client) Process(ctx context.Context, link *transport.Link, dialer inter
 	if err != nil {
 		return errors.New("failed to find an available destination").AtWarning().Base(err)
 	}
-	errors.LogInfo(ctx, "tunneling request to ", destination, " via ", server.Destination().NetAddr())
+	errors.LogInfo(ctx, "tunneling request to ", destination, " via ", server.Destination.NetAddr())
 
 	defer conn.Close()
 
-	user := server.PickUser()
+	user := server.User
 	account, ok := user.Account.(*MemoryAccount)
 	if !ok {
 		return errors.New("user account is not valid")

+ 3 - 3
proxy/trojan/config.pb.go

@@ -156,7 +156,7 @@ type ClientConfig struct {
 	sizeCache     protoimpl.SizeCache
 	unknownFields protoimpl.UnknownFields
 
-	Server []*protocol.ServerEndpoint `protobuf:"bytes,1,rep,name=server,proto3" json:"server,omitempty"`
+	Server *protocol.ServerEndpoint `protobuf:"bytes,1,opt,name=server,proto3" json:"server,omitempty"`
 }
 
 func (x *ClientConfig) Reset() {
@@ -189,7 +189,7 @@ func (*ClientConfig) Descriptor() ([]byte, []int) {
 	return file_proxy_trojan_config_proto_rawDescGZIP(), []int{2}
 }
 
-func (x *ClientConfig) GetServer() []*protocol.ServerEndpoint {
+func (x *ClientConfig) GetServer() *protocol.ServerEndpoint {
 	if x != nil {
 		return x.Server
 	}
@@ -271,7 +271,7 @@ var file_proxy_trojan_config_proto_rawDesc = []byte{
 	0x04, 0x64, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x78, 0x76, 0x65, 0x72, 0x18, 0x06, 0x20,
 	0x01, 0x28, 0x04, 0x52, 0x04, 0x78, 0x76, 0x65, 0x72, 0x22, 0x4c, 0x0a, 0x0c, 0x43, 0x6c, 0x69,
 	0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x3c, 0x0a, 0x06, 0x73, 0x65, 0x72,
-	0x76, 0x65, 0x72, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x78, 0x72, 0x61, 0x79,
+	0x76, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x78, 0x72, 0x61, 0x79,
 	0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c,
 	0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52,
 	0x06, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x22, 0x7b, 0x0a, 0x0c, 0x53, 0x65, 0x72, 0x76, 0x65,

+ 1 - 1
proxy/trojan/config.proto

@@ -23,7 +23,7 @@ message Fallback {
 }
 
 message ClientConfig {
-  repeated xray.common.protocol.ServerEndpoint server = 1;
+  xray.common.protocol.ServerEndpoint server = 1;
 }
 
 message ServerConfig {

+ 3 - 3
proxy/vless/outbound/config.pb.go

@@ -26,7 +26,7 @@ type Config struct {
 	sizeCache     protoimpl.SizeCache
 	unknownFields protoimpl.UnknownFields
 
-	Vnext []*protocol.ServerEndpoint `protobuf:"bytes,1,rep,name=vnext,proto3" json:"vnext,omitempty"`
+	Vnext *protocol.ServerEndpoint `protobuf:"bytes,1,opt,name=vnext,proto3" json:"vnext,omitempty"`
 }
 
 func (x *Config) Reset() {
@@ -59,7 +59,7 @@ func (*Config) Descriptor() ([]byte, []int) {
 	return file_proxy_vless_outbound_config_proto_rawDescGZIP(), []int{0}
 }
 
-func (x *Config) GetVnext() []*protocol.ServerEndpoint {
+func (x *Config) GetVnext() *protocol.ServerEndpoint {
 	if x != nil {
 		return x.Vnext
 	}
@@ -76,7 +76,7 @@ var file_proxy_vless_outbound_config_proto_rawDesc = []byte{
 	0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2f,
 	0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x73, 0x70, 0x65, 0x63, 0x2e, 0x70, 0x72, 0x6f, 0x74,
 	0x6f, 0x22, 0x44, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x3a, 0x0a, 0x05, 0x76,
-	0x6e, 0x65, 0x78, 0x74, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x78, 0x72, 0x61,
+	0x6e, 0x65, 0x78, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x78, 0x72, 0x61,
 	0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f,
 	0x6c, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74,
 	0x52, 0x05, 0x76, 0x6e, 0x65, 0x78, 0x74, 0x42, 0x6d, 0x0a, 0x1d, 0x63, 0x6f, 0x6d, 0x2e, 0x78,

+ 1 - 1
proxy/vless/outbound/config.proto

@@ -9,5 +9,5 @@ option java_multiple_files = true;
 import "common/protocol/server_spec.proto";
 
 message Config {
-  repeated xray.common.protocol.ServerEndpoint vnext = 1;
+  xray.common.protocol.ServerEndpoint vnext = 1;
 }

+ 14 - 17
proxy/vless/outbound/outbound.go

@@ -47,8 +47,7 @@ func init() {
 
 // Handler is an outbound connection handler for VLess protocol.
 type Handler struct {
-	serverList    *protocol.ServerList
-	serverPicker  protocol.ServerPicker
+	server        *protocol.ServerSpec
 	policyManager policy.Manager
 	cone          bool
 	encryption    *encryption.ClientInstance
@@ -57,24 +56,22 @@ type Handler struct {
 
 // New creates a new VLess outbound handler.
 func New(ctx context.Context, config *Config) (*Handler, error) {
-	serverList := protocol.NewServerList()
-	for _, rec := range config.Vnext {
-		s, err := protocol.NewServerSpecFromPB(rec)
-		if err != nil {
-			return nil, errors.New("failed to parse server spec").Base(err).AtError()
-		}
-		serverList.AddServer(s)
+	if config.Vnext == nil {
+		return nil, errors.New(`no vnext found`)
+	}
+	server, err := protocol.NewServerSpecFromPB(config.Vnext)
+	if err != nil {
+		return nil, errors.New("failed to get server spec").Base(err).AtError()
 	}
 
 	v := core.MustFromContext(ctx)
 	handler := &Handler{
-		serverList:    serverList,
-		serverPicker:  protocol.NewRoundRobinServerPicker(serverList),
+		server:        server,
 		policyManager: v.GetFeature(policy.ManagerType()).(policy.Manager),
 		cone:          ctx.Value("cone").(bool),
 	}
 
-	a := handler.serverPicker.PickServer().PickUser().Account.(*vless.MemoryAccount)
+	a := handler.server.User.Account.(*vless.MemoryAccount)
 	if a.Encryption != "" && a.Encryption != "none" {
 		s := strings.Split(a.Encryption, ".")
 		var nfsPKeysBytes [][]byte
@@ -125,12 +122,12 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
 	}
 	ob.Name = "vless"
 
-	var rec *protocol.ServerSpec
+	rec := h.server
 	var conn stat.Connection
+
 	if err := retry.ExponentialBackoff(5, 200).On(func() error {
-		rec = h.serverPicker.PickServer()
 		var err error
-		conn, err = dialer.Dial(ctx, rec.Destination())
+		conn, err = dialer.Dial(ctx, rec.Destination)
 		if err != nil {
 			return err
 		}
@@ -145,7 +142,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
 		iConn = statConn.Connection
 	}
 	target := ob.Target
-	errors.LogInfo(ctx, "tunneling request to ", target, " via ", rec.Destination().NetAddr())
+	errors.LogInfo(ctx, "tunneling request to ", target, " via ", rec.Destination.NetAddr())
 
 	if h.encryption != nil {
 		var err error
@@ -172,7 +169,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
 
 	request := &protocol.RequestHeader{
 		Version: encoding.Version,
-		User:    rec.PickUser(),
+		User:    rec.User,
 		Command: command,
 		Address: target.Address,
 		Port:    target.Port,

+ 0 - 72
proxy/vmess/encoding/commands.go

@@ -7,10 +7,7 @@ import (
 	"github.com/xtls/xray-core/common"
 	"github.com/xtls/xray-core/common/buf"
 	"github.com/xtls/xray-core/common/errors"
-	"github.com/xtls/xray-core/common/net"
 	"github.com/xtls/xray-core/common/protocol"
-	"github.com/xtls/xray-core/common/serial"
-	"github.com/xtls/xray-core/common/uuid"
 )
 
 var (
@@ -29,9 +26,6 @@ func MarshalCommand(command interface{}, writer io.Writer) error {
 	var cmdID byte
 	var factory CommandFactory
 	switch command.(type) {
-	case *protocol.CommandSwitchAccount:
-		factory = new(CommandSwitchAccountFactory)
-		cmdID = 1
 	default:
 		return ErrUnknownCommand
 	}
@@ -67,8 +61,6 @@ func UnmarshalCommand(cmdID byte, data []byte) (protocol.ResponseCommand, error)
 
 	var factory CommandFactory
 	switch cmdID {
-	case 1:
-		factory = new(CommandSwitchAccountFactory)
 	default:
 		return nil, ErrUnknownCommand
 	}
@@ -79,67 +71,3 @@ type CommandFactory interface {
 	Marshal(command interface{}, writer io.Writer) error
 	Unmarshal(data []byte) (interface{}, error)
 }
-
-type CommandSwitchAccountFactory struct{}
-
-func (f *CommandSwitchAccountFactory) Marshal(command interface{}, writer io.Writer) error {
-	cmd, ok := command.(*protocol.CommandSwitchAccount)
-	if !ok {
-		return ErrCommandTypeMismatch
-	}
-
-	hostStr := ""
-	if cmd.Host != nil {
-		hostStr = cmd.Host.String()
-	}
-	common.Must2(writer.Write([]byte{byte(len(hostStr))}))
-
-	if len(hostStr) > 0 {
-		common.Must2(writer.Write([]byte(hostStr)))
-	}
-
-	common.Must2(serial.WriteUint16(writer, cmd.Port.Value()))
-
-	idBytes := cmd.ID.Bytes()
-	common.Must2(writer.Write(idBytes))
-	common.Must2(serial.WriteUint16(writer, 0)) // compatible with legacy alterId
-	common.Must2(writer.Write([]byte{byte(cmd.Level)}))
-
-	common.Must2(writer.Write([]byte{cmd.ValidMin}))
-	return nil
-}
-
-func (f *CommandSwitchAccountFactory) Unmarshal(data []byte) (interface{}, error) {
-	cmd := new(protocol.CommandSwitchAccount)
-	if len(data) == 0 {
-		return nil, ErrInsufficientLength
-	}
-	lenHost := int(data[0])
-	if len(data) < lenHost+1 {
-		return nil, ErrInsufficientLength
-	}
-	if lenHost > 0 {
-		cmd.Host = net.ParseAddress(string(data[1 : 1+lenHost]))
-	}
-	portStart := 1 + lenHost
-	if len(data) < portStart+2 {
-		return nil, ErrInsufficientLength
-	}
-	cmd.Port = net.PortFromBytes(data[portStart : portStart+2])
-	idStart := portStart + 2
-	if len(data) < idStart+16 {
-		return nil, ErrInsufficientLength
-	}
-	cmd.ID, _ = uuid.ParseBytes(data[idStart : idStart+16])
-	levelStart := idStart + 16 + 2
-	if len(data) < levelStart+1 {
-		return nil, ErrInsufficientLength
-	}
-	cmd.Level = uint32(data[levelStart])
-	timeStart := levelStart + 1
-	if len(data) < timeStart+1 {
-		return nil, ErrInsufficientLength
-	}
-	cmd.ValidMin = data[timeStart]
-	return cmd, nil
-}

+ 0 - 55
proxy/vmess/encoding/commands_test.go

@@ -1,55 +0,0 @@
-package encoding_test
-
-import (
-	"testing"
-
-	"github.com/google/go-cmp/cmp"
-	"github.com/stretchr/testify/assert"
-	"github.com/xtls/xray-core/common"
-	"github.com/xtls/xray-core/common/buf"
-	"github.com/xtls/xray-core/common/protocol"
-	"github.com/xtls/xray-core/common/uuid"
-	. "github.com/xtls/xray-core/proxy/vmess/encoding"
-)
-
-func TestSwitchAccount(t *testing.T) {
-	sa := &protocol.CommandSwitchAccount{
-		Port:     1234,
-		ID:       uuid.New(),
-		Level:    128,
-		ValidMin: 16,
-	}
-
-	buffer := buf.New()
-	common.Must(MarshalCommand(sa, buffer))
-
-	cmd, err := UnmarshalCommand(1, buffer.BytesFrom(2))
-	common.Must(err)
-
-	sa2, ok := cmd.(*protocol.CommandSwitchAccount)
-	if !ok {
-		t.Fatal("failed to convert command to CommandSwitchAccount")
-	}
-	if r := cmp.Diff(sa2, sa); r != "" {
-		t.Error(r)
-	}
-}
-
-func TestSwitchAccountBugOffByOne(t *testing.T) {
-	sa := &protocol.CommandSwitchAccount{
-		Port:     1234,
-		ID:       uuid.New(),
-		Level:    128,
-		ValidMin: 16,
-	}
-
-	buffer := buf.New()
-	csaf := CommandSwitchAccountFactory{}
-	common.Must(csaf.Marshal(sa, buffer))
-
-	Payload := buffer.Bytes()
-
-	cmd, err := csaf.Unmarshal(Payload[:len(Payload)-1])
-	assert.Error(t, err)
-	assert.Nil(t, cmd)
-}

+ 20 - 34
proxy/vmess/inbound/config.pb.go

@@ -118,7 +118,6 @@ type Config struct {
 
 	User    []*protocol.User `protobuf:"bytes,1,rep,name=user,proto3" json:"user,omitempty"`
 	Default *DefaultConfig   `protobuf:"bytes,2,opt,name=default,proto3" json:"default,omitempty"`
-	Detour  *DetourConfig    `protobuf:"bytes,3,opt,name=detour,proto3" json:"detour,omitempty"` // 4 is for legacy setting
 }
 
 func (x *Config) Reset() {
@@ -165,13 +164,6 @@ func (x *Config) GetDefault() *DefaultConfig {
 	return nil
 }
 
-func (x *Config) GetDetour() *DetourConfig {
-	if x != nil {
-		return x.Detour
-	}
-	return nil
-}
-
 var File_proxy_vmess_inbound_config_proto protoreflect.FileDescriptor
 
 var file_proxy_vmess_inbound_config_proto_rawDesc = []byte{
@@ -185,26 +177,21 @@ var file_proxy_vmess_inbound_config_proto_rawDesc = []byte{
 	0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x74, 0x6f, 0x22, 0x25, 0x0a, 0x0d, 0x44, 0x65, 0x66, 0x61,
 	0x75, 0x6c, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x65, 0x76,
 	0x65, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x22,
-	0xbb, 0x01, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x2e, 0x0a, 0x04, 0x75, 0x73,
-	0x65, 0x72, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e,
-	0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2e,
-	0x55, 0x73, 0x65, 0x72, 0x52, 0x04, 0x75, 0x73, 0x65, 0x72, 0x12, 0x41, 0x0a, 0x07, 0x64, 0x65,
-	0x66, 0x61, 0x75, 0x6c, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x78, 0x72,
-	0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x76, 0x6d, 0x65, 0x73, 0x73, 0x2e, 0x69,
-	0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x2e, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x43, 0x6f,
-	0x6e, 0x66, 0x69, 0x67, 0x52, 0x07, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x12, 0x3e, 0x0a,
-	0x06, 0x64, 0x65, 0x74, 0x6f, 0x75, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e,
-	0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x76, 0x6d, 0x65, 0x73, 0x73,
-	0x2e, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x2e, 0x44, 0x65, 0x74, 0x6f, 0x75, 0x72, 0x43,
-	0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x06, 0x64, 0x65, 0x74, 0x6f, 0x75, 0x72, 0x42, 0x6a, 0x0a,
-	0x1c, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e,
-	0x76, 0x6d, 0x65, 0x73, 0x73, 0x2e, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x50, 0x01, 0x5a,
-	0x2d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73,
-	0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x78, 0x79,
-	0x2f, 0x76, 0x6d, 0x65, 0x73, 0x73, 0x2f, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0xaa, 0x02,
-	0x18, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x56, 0x6d, 0x65, 0x73,
-	0x73, 0x2e, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f,
-	0x33,
+	0x7b, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x2e, 0x0a, 0x04, 0x75, 0x73, 0x65,
+	0x72, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63,
+	0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2e, 0x55,
+	0x73, 0x65, 0x72, 0x52, 0x04, 0x75, 0x73, 0x65, 0x72, 0x12, 0x41, 0x0a, 0x07, 0x64, 0x65, 0x66,
+	0x61, 0x75, 0x6c, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x78, 0x72, 0x61,
+	0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x76, 0x6d, 0x65, 0x73, 0x73, 0x2e, 0x69, 0x6e,
+	0x62, 0x6f, 0x75, 0x6e, 0x64, 0x2e, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x43, 0x6f, 0x6e,
+	0x66, 0x69, 0x67, 0x52, 0x07, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x42, 0x6a, 0x0a, 0x1c,
+	0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x76,
+	0x6d, 0x65, 0x73, 0x73, 0x2e, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x50, 0x01, 0x5a, 0x2d,
+	0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f,
+	0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f,
+	0x76, 0x6d, 0x65, 0x73, 0x73, 0x2f, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0xaa, 0x02, 0x18,
+	0x58, 0x72, 0x61, 0x79, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x56, 0x6d, 0x65, 0x73, 0x73,
+	0x2e, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
 }
 
 var (
@@ -229,12 +216,11 @@ var file_proxy_vmess_inbound_config_proto_goTypes = []any{
 var file_proxy_vmess_inbound_config_proto_depIdxs = []int32{
 	3, // 0: xray.proxy.vmess.inbound.Config.user:type_name -> xray.common.protocol.User
 	1, // 1: xray.proxy.vmess.inbound.Config.default:type_name -> xray.proxy.vmess.inbound.DefaultConfig
-	0, // 2: xray.proxy.vmess.inbound.Config.detour:type_name -> xray.proxy.vmess.inbound.DetourConfig
-	3, // [3:3] is the sub-list for method output_type
-	3, // [3:3] 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
+	2, // [2:2] is the sub-list for method output_type
+	2, // [2:2] is the sub-list for method input_type
+	2, // [2:2] is the sub-list for extension type_name
+	2, // [2:2] is the sub-list for extension extendee
+	0, // [0:2] is the sub-list for field type_name
 }
 
 func init() { file_proxy_vmess_inbound_config_proto_init() }

+ 0 - 2
proxy/vmess/inbound/config.proto

@@ -19,6 +19,4 @@ message DefaultConfig {
 message Config {
   repeated xray.common.protocol.User user = 1;
   DefaultConfig default = 2;
-  DetourConfig detour = 3;
-  // 4 is for legacy setting
 }

+ 1 - 33
proxy/vmess/inbound/inbound.go

@@ -106,7 +106,6 @@ type Handler struct {
 	inboundHandlerManager feature_inbound.Manager
 	clients               *vmess.TimedUserValidator
 	usersByEmail          *userByEmail
-	detours               *DetourConfig
 	sessionHistory        *encoding.SessionHistory
 }
 
@@ -117,7 +116,6 @@ func New(ctx context.Context, config *Config) (*Handler, error) {
 		policyManager:         v.GetFeature(policy.ManagerType()).(policy.Manager),
 		inboundHandlerManager: v.GetFeature(feature_inbound.ManagerType()).(feature_inbound.Manager),
 		clients:               vmess.NewTimedUserValidator(),
-		detours:               config.Detour,
 		usersByEmail:          newUserByEmail(config.GetDefaultValue()),
 		sessionHistory:        encoding.NewSessionHistory(),
 	}
@@ -323,38 +321,8 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection s
 	return nil
 }
 
+// Stub command generator
 func (h *Handler) generateCommand(ctx context.Context, request *protocol.RequestHeader) protocol.ResponseCommand {
-	if h.detours != nil {
-		tag := h.detours.To
-		if h.inboundHandlerManager != nil {
-			handler, err := h.inboundHandlerManager.GetHandler(ctx, tag)
-			if err != nil {
-				errors.LogWarningInner(ctx, err, "failed to get detour handler: ", tag)
-				return nil
-			}
-			proxyHandler, port, availableMin := handler.GetRandomInboundProxy()
-			inboundHandler, ok := proxyHandler.(*Handler)
-			if ok && inboundHandler != nil {
-				if availableMin > 255 {
-					availableMin = 255
-				}
-
-				errors.LogDebug(ctx, "pick detour handler for port ", port, " for ", availableMin, " minutes.")
-				user := inboundHandler.GetOrGenerateUser(request.User.Email)
-				if user == nil {
-					return nil
-				}
-				account := user.Account.(*vmess.MemoryAccount)
-				return &protocol.CommandSwitchAccount{
-					Port:     port,
-					ID:       account.ID.UUID(),
-					Level:    user.Level,
-					ValidMin: byte(availableMin),
-				}
-			}
-		}
-	}
-
 	return nil
 }
 

+ 2 - 29
proxy/vmess/outbound/command.go

@@ -1,41 +1,14 @@
 package outbound
 
 import (
-	"time"
 
-	"github.com/xtls/xray-core/common"
 	"github.com/xtls/xray-core/common/net"
 	"github.com/xtls/xray-core/common/protocol"
-	"github.com/xtls/xray-core/proxy/vmess"
 )
 
-func (h *Handler) handleSwitchAccount(cmd *protocol.CommandSwitchAccount) {
-	rawAccount := &vmess.Account{
-		Id: cmd.ID.String(),
-		SecuritySettings: &protocol.SecurityConfig{
-			Type: protocol.SecurityType_AUTO,
-		},
-	}
-
-	account, err := rawAccount.AsAccount()
-	common.Must(err)
-	user := &protocol.MemoryUser{
-		Email:   "",
-		Level:   cmd.Level,
-		Account: account,
-	}
-	dest := net.TCPDestination(cmd.Host, cmd.Port)
-	until := time.Now().Add(time.Duration(cmd.ValidMin) * time.Minute)
-	h.serverList.AddServer(protocol.NewServerSpec(dest, protocol.BeforeTime(until), user))
-}
-
+// As a stub command consumer.
 func (h *Handler) handleCommand(dest net.Destination, cmd protocol.ResponseCommand) {
-	switch typedCommand := cmd.(type) {
-	case *protocol.CommandSwitchAccount:
-		if typedCommand.Host == nil {
-			typedCommand.Host = dest.Address
-		}
-		h.handleSwitchAccount(typedCommand)
+	switch cmd.(type) {
 	default:
 	}
 }

+ 3 - 3
proxy/vmess/outbound/config.pb.go

@@ -26,7 +26,7 @@ type Config struct {
 	sizeCache     protoimpl.SizeCache
 	unknownFields protoimpl.UnknownFields
 
-	Receiver []*protocol.ServerEndpoint `protobuf:"bytes,1,rep,name=Receiver,proto3" json:"Receiver,omitempty"`
+	Receiver *protocol.ServerEndpoint `protobuf:"bytes,1,opt,name=Receiver,proto3" json:"Receiver,omitempty"`
 }
 
 func (x *Config) Reset() {
@@ -59,7 +59,7 @@ func (*Config) Descriptor() ([]byte, []int) {
 	return file_proxy_vmess_outbound_config_proto_rawDescGZIP(), []int{0}
 }
 
-func (x *Config) GetReceiver() []*protocol.ServerEndpoint {
+func (x *Config) GetReceiver() *protocol.ServerEndpoint {
 	if x != nil {
 		return x.Receiver
 	}
@@ -76,7 +76,7 @@ var file_proxy_vmess_outbound_config_proto_rawDesc = []byte{
 	0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2f,
 	0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x73, 0x70, 0x65, 0x63, 0x2e, 0x70, 0x72, 0x6f, 0x74,
 	0x6f, 0x22, 0x4a, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x40, 0x0a, 0x08, 0x52,
-	0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e,
+	0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e,
 	0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74,
 	0x6f, 0x63, 0x6f, 0x6c, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x45, 0x6e, 0x64, 0x70, 0x6f,
 	0x69, 0x6e, 0x74, 0x52, 0x08, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x42, 0x6d, 0x0a,

+ 1 - 1
proxy/vmess/outbound/config.proto

@@ -9,5 +9,5 @@ option java_multiple_files = true;
 import "common/protocol/server_spec.proto";
 
 message Config {
-  repeated xray.common.protocol.ServerEndpoint Receiver = 1;
+  xray.common.protocol.ServerEndpoint Receiver = 1;
 }

+ 14 - 17
proxy/vmess/outbound/outbound.go

@@ -29,27 +29,24 @@ import (
 
 // Handler is an outbound connection handler for VMess protocol.
 type Handler struct {
-	serverList    *protocol.ServerList
-	serverPicker  protocol.ServerPicker
+	server        *protocol.ServerSpec
 	policyManager policy.Manager
 	cone          bool
 }
 
 // New creates a new VMess outbound handler.
 func New(ctx context.Context, config *Config) (*Handler, error) {
-	serverList := protocol.NewServerList()
-	for _, rec := range config.Receiver {
-		s, err := protocol.NewServerSpecFromPB(rec)
-		if err != nil {
-			return nil, errors.New("failed to parse server spec").Base(err)
-		}
-		serverList.AddServer(s)
+	if config.Receiver == nil {
+		return nil, errors.New(`no vnext found`)
+	}
+	server, err := protocol.NewServerSpecFromPB(config.Receiver)
+	if err != nil {
+		return nil, errors.New("failed to get server spec").Base(err)
 	}
 
 	v := core.MustFromContext(ctx)
 	handler := &Handler{
-		serverList:    serverList,
-		serverPicker:  protocol.NewRoundRobinServerPicker(serverList),
+		server:        server,
 		policyManager: v.GetFeature(policy.ManagerType()).(policy.Manager),
 		cone:          ctx.Value("cone").(bool),
 	}
@@ -67,11 +64,11 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
 	ob.Name = "vmess"
 	ob.CanSpliceCopy = 3
 
-	var rec *protocol.ServerSpec
+	rec := h.server
 	var conn stat.Connection
+
 	err := retry.ExponentialBackoff(5, 200).On(func() error {
-		rec = h.serverPicker.PickServer()
-		rawConn, err := dialer.Dial(ctx, rec.Destination())
+		rawConn, err := dialer.Dial(ctx, rec.Destination)
 		if err != nil {
 			return err
 		}
@@ -85,7 +82,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
 	defer conn.Close()
 
 	target := ob.Target
-	errors.LogInfo(ctx, "tunneling request to ", target, " via ", rec.Destination().NetAddr())
+	errors.LogInfo(ctx, "tunneling request to ", target, " via ", rec.Destination.NetAddr())
 
 	command := protocol.RequestCommandTCP
 	if target.Network == net.Network_UDP {
@@ -95,7 +92,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
 		command = protocol.RequestCommandMux
 	}
 
-	user := rec.PickUser()
+	user := rec.User
 	request := &protocol.RequestHeader{
 		Version: encoding.Version,
 		User:    user,
@@ -202,7 +199,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
 		if err != nil {
 			return errors.New("failed to read header").Base(err)
 		}
-		h.handleCommand(rec.Destination(), header.Command)
+		h.handleCommand(rec.Destination, header.Command)
 
 		bodyReader, err := session.DecodeResponseBody(request, reader)
 		if err != nil {

+ 18 - 26
testing/scenarios/command_test.go

@@ -423,20 +423,16 @@ func TestCommanderAddRemoveUser(t *testing.T) {
 		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&outbound.Config{
-					Receiver: []*protocol.ServerEndpoint{
-						{
-							Address: net.NewIPOrDomain(net.LocalHostIP),
-							Port:    uint32(serverPort),
-							User: []*protocol.User{
-								{
-									Account: serial.ToTypedMessage(&vmess.Account{
-										Id: u2.String(),
-										SecuritySettings: &protocol.SecurityConfig{
-											Type: protocol.SecurityType_AES128_GCM,
-										},
-									}),
+					Receiver: &protocol.ServerEndpoint{
+						Address: net.NewIPOrDomain(net.LocalHostIP),
+						Port:    uint32(serverPort),
+						User:    &protocol.User{
+							Account: serial.ToTypedMessage(&vmess.Account{
+								Id: u2.String(),
+								SecuritySettings: &protocol.SecurityConfig{
+									Type: protocol.SecurityType_AES128_GCM,
 								},
-							},
+							}),
 						},
 					},
 				}),
@@ -600,20 +596,16 @@ func TestCommanderStats(t *testing.T) {
 		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&outbound.Config{
-					Receiver: []*protocol.ServerEndpoint{
-						{
-							Address: net.NewIPOrDomain(net.LocalHostIP),
-							Port:    uint32(serverPort),
-							User: []*protocol.User{
-								{
-									Account: serial.ToTypedMessage(&vmess.Account{
-										Id: userID.String(),
-										SecuritySettings: &protocol.SecurityConfig{
-											Type: protocol.SecurityType_AES128_GCM,
-										},
-									}),
+					Receiver: &protocol.ServerEndpoint{
+						Address: net.NewIPOrDomain(net.LocalHostIP),
+						Port:    uint32(serverPort),
+						User:    &protocol.User{
+							Account: serial.ToTypedMessage(&vmess.Account{
+								Id: userID.String(),
+								SecuritySettings: &protocol.SecurityConfig{
+									Type: protocol.SecurityType_AES128_GCM,
 								},
-							},
+							}),
 						},
 					},
 				}),

+ 14 - 22
testing/scenarios/dokodemo_test.go

@@ -94,17 +94,13 @@ func TestDokodemoTCP(t *testing.T) {
 			Outbound: []*core.OutboundHandlerConfig{
 				{
 					ProxySettings: serial.ToTypedMessage(&outbound.Config{
-						Receiver: []*protocol.ServerEndpoint{
-							{
-								Address: net.NewIPOrDomain(net.LocalHostIP),
-								Port:    uint32(serverPort),
-								User: []*protocol.User{
-									{
-										Account: serial.ToTypedMessage(&vmess.Account{
-											Id: userID.String(),
-										}),
-									},
-								},
+						Receiver: &protocol.ServerEndpoint{
+							Address: net.NewIPOrDomain(net.LocalHostIP),
+							Port:    uint32(serverPort),
+							User:    &protocol.User{
+								Account: serial.ToTypedMessage(&vmess.Account{
+									Id: userID.String(),
+								}),
 							},
 						},
 					}),
@@ -190,17 +186,13 @@ func TestDokodemoUDP(t *testing.T) {
 			Outbound: []*core.OutboundHandlerConfig{
 				{
 					ProxySettings: serial.ToTypedMessage(&outbound.Config{
-						Receiver: []*protocol.ServerEndpoint{
-							{
-								Address: net.NewIPOrDomain(net.LocalHostIP),
-								Port:    uint32(serverPort),
-								User: []*protocol.User{
-									{
-										Account: serial.ToTypedMessage(&vmess.Account{
-											Id: userID.String(),
-										}),
-									},
-								},
+						Receiver: &protocol.ServerEndpoint{
+							Address: net.NewIPOrDomain(net.LocalHostIP),
+							Port:    uint32(serverPort),
+							User:    &protocol.User{
+								Account: serial.ToTypedMessage(&vmess.Account{
+									Id: userID.String(),
+								}),
 							},
 						},
 					}),

+ 37 - 57
testing/scenarios/feature_test.go

@@ -170,17 +170,13 @@ func TestProxy(t *testing.T) {
 		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&outbound.Config{
-					Receiver: []*protocol.ServerEndpoint{
-						{
-							Address: net.NewIPOrDomain(net.LocalHostIP),
-							Port:    uint32(serverPort),
-							User: []*protocol.User{
-								{
-									Account: serial.ToTypedMessage(&vmess.Account{
-										Id: serverUserID.String(),
-									}),
-								},
-							},
+					Receiver: &protocol.ServerEndpoint{
+						Address: net.NewIPOrDomain(net.LocalHostIP),
+						Port:    uint32(serverPort),
+						User:    &protocol.User{
+							Account: serial.ToTypedMessage(&vmess.Account{
+								Id: serverUserID.String(),
+							}),
 						},
 					},
 				}),
@@ -193,17 +189,13 @@ func TestProxy(t *testing.T) {
 			{
 				Tag: "proxy",
 				ProxySettings: serial.ToTypedMessage(&outbound.Config{
-					Receiver: []*protocol.ServerEndpoint{
-						{
-							Address: net.NewIPOrDomain(net.LocalHostIP),
-							Port:    uint32(proxyPort),
-							User: []*protocol.User{
-								{
-									Account: serial.ToTypedMessage(&vmess.Account{
-										Id: proxyUserID.String(),
-									}),
-								},
-							},
+					Receiver: &protocol.ServerEndpoint{
+						Address: net.NewIPOrDomain(net.LocalHostIP),
+						Port:    uint32(proxyPort),
+						User:    &protocol.User{
+							Account: serial.ToTypedMessage(&vmess.Account{
+								Id: proxyUserID.String(),
+							}),
 						},
 					},
 				}),
@@ -308,17 +300,13 @@ func TestProxyOverKCP(t *testing.T) {
 		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&outbound.Config{
-					Receiver: []*protocol.ServerEndpoint{
-						{
-							Address: net.NewIPOrDomain(net.LocalHostIP),
-							Port:    uint32(serverPort),
-							User: []*protocol.User{
-								{
-									Account: serial.ToTypedMessage(&vmess.Account{
-										Id: serverUserID.String(),
-									}),
-								},
-							},
+					Receiver: &protocol.ServerEndpoint{
+						Address: net.NewIPOrDomain(net.LocalHostIP),
+						Port:    uint32(serverPort),
+						User:    &protocol.User{
+							Account: serial.ToTypedMessage(&vmess.Account{
+								Id: serverUserID.String(),
+							}),
 						},
 					},
 				}),
@@ -334,17 +322,13 @@ func TestProxyOverKCP(t *testing.T) {
 			{
 				Tag: "proxy",
 				ProxySettings: serial.ToTypedMessage(&outbound.Config{
-					Receiver: []*protocol.ServerEndpoint{
-						{
-							Address: net.NewIPOrDomain(net.LocalHostIP),
-							Port:    uint32(proxyPort),
-							User: []*protocol.User{
-								{
-									Account: serial.ToTypedMessage(&vmess.Account{
-										Id: proxyUserID.String(),
-									}),
-								},
-							},
+					Receiver: &protocol.ServerEndpoint{
+						Address: net.NewIPOrDomain(net.LocalHostIP),
+						Port:    uint32(proxyPort),
+						User:    &protocol.User{
+							Account: serial.ToTypedMessage(&vmess.Account{
+								Id: proxyUserID.String(),
+							}),
 						},
 					},
 				}),
@@ -685,20 +669,16 @@ func TestDialXray(t *testing.T) {
 		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&outbound.Config{
-					Receiver: []*protocol.ServerEndpoint{
-						{
-							Address: net.NewIPOrDomain(net.LocalHostIP),
-							Port:    uint32(serverPort),
-							User: []*protocol.User{
-								{
-									Account: serial.ToTypedMessage(&vmess.Account{
-										Id: userID.String(),
-										SecuritySettings: &protocol.SecurityConfig{
-											Type: protocol.SecurityType_AES128_GCM,
-										},
-									}),
+					Receiver: &protocol.ServerEndpoint{
+						Address: net.NewIPOrDomain(net.LocalHostIP),
+						Port:    uint32(serverPort),
+						User: &protocol.User{
+							Account: serial.ToTypedMessage(&vmess.Account{
+								Id: userID.String(),
+								SecuritySettings: &protocol.SecurityConfig{
+									Type: protocol.SecurityType_AES128_GCM,
 								},
-							},
+							}),
 						},
 					},
 				}),

+ 18 - 26
testing/scenarios/policy_test.go

@@ -119,20 +119,16 @@ func TestVMessClosing(t *testing.T) {
 		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&outbound.Config{
-					Receiver: []*protocol.ServerEndpoint{
-						{
-							Address: net.NewIPOrDomain(net.LocalHostIP),
-							Port:    uint32(serverPort),
-							User: []*protocol.User{
-								{
-									Account: serial.ToTypedMessage(&vmess.Account{
-										Id: userID.String(),
-										SecuritySettings: &protocol.SecurityConfig{
-											Type: protocol.SecurityType_AES128_GCM,
-										},
-									}),
+					Receiver: &protocol.ServerEndpoint{
+						Address: net.NewIPOrDomain(net.LocalHostIP),
+						Port:    uint32(serverPort),
+						User:    &protocol.User{
+							Account: serial.ToTypedMessage(&vmess.Account{
+								Id: userID.String(),
+								SecuritySettings: &protocol.SecurityConfig{
+									Type: protocol.SecurityType_AES128_GCM,
 								},
-							},
+							}),
 						},
 					},
 				}),
@@ -223,20 +219,16 @@ func TestZeroBuffer(t *testing.T) {
 		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&outbound.Config{
-					Receiver: []*protocol.ServerEndpoint{
-						{
-							Address: net.NewIPOrDomain(net.LocalHostIP),
-							Port:    uint32(serverPort),
-							User: []*protocol.User{
-								{
-									Account: serial.ToTypedMessage(&vmess.Account{
-										Id: userID.String(),
-										SecuritySettings: &protocol.SecurityConfig{
-											Type: protocol.SecurityType_AES128_GCM,
-										},
-									}),
+					Receiver: &protocol.ServerEndpoint{
+						Address: net.NewIPOrDomain(net.LocalHostIP),
+						Port:    uint32(serverPort),
+						User:    &protocol.User{
+							Account: serial.ToTypedMessage(&vmess.Account{
+								Id: userID.String(),
+								SecuritySettings: &protocol.SecurityConfig{
+									Type: protocol.SecurityType_AES128_GCM,
 								},
-							},
+							}),
 						},
 					},
 				}),

+ 18 - 26
testing/scenarios/reverse_test.go

@@ -155,20 +155,16 @@ func TestReverseProxy(t *testing.T) {
 			{
 				Tag: "reverse",
 				ProxySettings: serial.ToTypedMessage(&outbound.Config{
-					Receiver: []*protocol.ServerEndpoint{
-						{
-							Address: net.NewIPOrDomain(net.LocalHostIP),
-							Port:    uint32(reversePort),
-							User: []*protocol.User{
-								{
-									Account: serial.ToTypedMessage(&vmess.Account{
-										Id: userID.String(),
-										SecuritySettings: &protocol.SecurityConfig{
-											Type: protocol.SecurityType_AES128_GCM,
-										},
-									}),
+					Receiver: &protocol.ServerEndpoint{
+						Address: net.NewIPOrDomain(net.LocalHostIP),
+						Port:    uint32(reversePort),
+						User:    &protocol.User{
+							Account: serial.ToTypedMessage(&vmess.Account{
+								Id: userID.String(),
+								SecuritySettings: &protocol.SecurityConfig{
+									Type: protocol.SecurityType_AES128_GCM,
 								},
-							},
+							}),
 						},
 					},
 				}),
@@ -348,20 +344,16 @@ func TestReverseProxyLongRunning(t *testing.T) {
 			{
 				Tag: "reverse",
 				ProxySettings: serial.ToTypedMessage(&outbound.Config{
-					Receiver: []*protocol.ServerEndpoint{
-						{
-							Address: net.NewIPOrDomain(net.LocalHostIP),
-							Port:    uint32(reversePort),
-							User: []*protocol.User{
-								{
-									Account: serial.ToTypedMessage(&vmess.Account{
-										Id: userID.String(),
-										SecuritySettings: &protocol.SecurityConfig{
-											Type: protocol.SecurityType_AES128_GCM,
-										},
-									}),
+					Receiver: &protocol.ServerEndpoint{
+						Address: net.NewIPOrDomain(net.LocalHostIP),
+						Port:    uint32(reversePort),
+						User:    &protocol.User{
+							Account: serial.ToTypedMessage(&vmess.Account{
+								Id: userID.String(),
+								SecuritySettings: &protocol.SecurityConfig{
+									Type: protocol.SecurityType_AES128_GCM,
 								},
-							},
+							}),
 						},
 					},
 				}),

+ 25 - 45
testing/scenarios/shadowsocks_test.go

@@ -75,15 +75,11 @@ func TestShadowsocksChaCha20Poly1305TCP(t *testing.T) {
 		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&shadowsocks.ClientConfig{
-					Server: []*protocol.ServerEndpoint{
-						{
-							Address: net.NewIPOrDomain(net.LocalHostIP),
-							Port:    uint32(serverPort),
-							User: []*protocol.User{
-								{
-									Account: account,
-								},
-							},
+					Server:    &protocol.ServerEndpoint{
+						Address: net.NewIPOrDomain(net.LocalHostIP),
+						Port:    uint32(serverPort),
+						User:    &protocol.User{
+							Account: account,
 						},
 					},
 				}),
@@ -171,15 +167,11 @@ func TestShadowsocksAES256GCMTCP(t *testing.T) {
 		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&shadowsocks.ClientConfig{
-					Server: []*protocol.ServerEndpoint{
-						{
-							Address: net.NewIPOrDomain(net.LocalHostIP),
-							Port:    uint32(serverPort),
-							User: []*protocol.User{
-								{
-									Account: account,
-								},
-							},
+					Server: &protocol.ServerEndpoint{
+						Address: net.NewIPOrDomain(net.LocalHostIP),
+						Port:    uint32(serverPort),
+						User:    &protocol.User{
+							Account: account,
 						},
 					},
 				}),
@@ -268,15 +260,11 @@ func TestShadowsocksAES128GCMUDP(t *testing.T) {
 		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&shadowsocks.ClientConfig{
-					Server: []*protocol.ServerEndpoint{
-						{
-							Address: net.NewIPOrDomain(net.LocalHostIP),
-							Port:    uint32(serverPort),
-							User: []*protocol.User{
-								{
-									Account: account,
-								},
-							},
+					Server: &protocol.ServerEndpoint{
+						Address: net.NewIPOrDomain(net.LocalHostIP),
+						Port:    uint32(serverPort),
+						User:    &protocol.User{
+							Account: account,
 						},
 					},
 				}),
@@ -370,15 +358,11 @@ func TestShadowsocksAES128GCMUDPMux(t *testing.T) {
 					},
 				}),
 				ProxySettings: serial.ToTypedMessage(&shadowsocks.ClientConfig{
-					Server: []*protocol.ServerEndpoint{
-						{
-							Address: net.NewIPOrDomain(net.LocalHostIP),
-							Port:    uint32(serverPort),
-							User: []*protocol.User{
-								{
-									Account: account,
-								},
-							},
+					Server: &protocol.ServerEndpoint{
+						Address: net.NewIPOrDomain(net.LocalHostIP),
+						Port:    uint32(serverPort),
+						User:    &protocol.User{
+							Account: account,
 						},
 					},
 				}),
@@ -455,15 +439,11 @@ func TestShadowsocksNone(t *testing.T) {
 		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&shadowsocks.ClientConfig{
-					Server: []*protocol.ServerEndpoint{
-						{
-							Address: net.NewIPOrDomain(net.LocalHostIP),
-							Port:    uint32(serverPort),
-							User: []*protocol.User{
-								{
-									Account: account,
-								},
-							},
+					Server: &protocol.ServerEndpoint{
+						Address: net.NewIPOrDomain(net.LocalHostIP),
+						Port:    uint32(serverPort),
+						User:    &protocol.User{
+							Account: account,
 						},
 					},
 				}),

+ 27 - 41
testing/scenarios/socks_test.go

@@ -73,18 +73,14 @@ func TestSocksBridgeTCP(t *testing.T) {
 		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&socks.ClientConfig{
-					Server: []*protocol.ServerEndpoint{
-						{
-							Address: net.NewIPOrDomain(net.LocalHostIP),
-							Port:    uint32(serverPort),
-							User: []*protocol.User{
-								{
-									Account: serial.ToTypedMessage(&socks.Account{
-										Username: "Test Account",
-										Password: "Test Password",
-									}),
-								},
-							},
+					Server: &protocol.ServerEndpoint{
+						Address: net.NewIPOrDomain(net.LocalHostIP),
+						Port:    uint32(serverPort),
+						User:    &protocol.User{
+							Account: serial.ToTypedMessage(&socks.Account{
+								Username: "Test Account",
+								Password: "Test Password",
+							}),
 						},
 					},
 				}),
@@ -152,18 +148,14 @@ func TestSocksWithHttpRequest(t *testing.T) {
 		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&http.ClientConfig{
-					Server: []*protocol.ServerEndpoint{
-						{
-							Address: net.NewIPOrDomain(net.LocalHostIP),
-							Port:    uint32(serverPort),
-							User: []*protocol.User{
-								{
-									Account: serial.ToTypedMessage(&http.Account{
-										Username: "Test Account",
-										Password: "Test Password",
-									}),
-								},
-							},
+					Server: &protocol.ServerEndpoint{
+						Address: net.NewIPOrDomain(net.LocalHostIP),
+						Port:    uint32(serverPort),
+						User:    &protocol.User{
+							Account: serial.ToTypedMessage(&http.Account{
+								Username: "Test Account",
+								Password: "Test Password",
+							}),
 						},
 					},
 				}),
@@ -256,18 +248,14 @@ func TestSocksBridageUDP(t *testing.T) {
 		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&socks.ClientConfig{
-					Server: []*protocol.ServerEndpoint{
-						{
-							Address: net.NewIPOrDomain(net.LocalHostIP),
-							Port:    uint32(serverPort),
-							User: []*protocol.User{
-								{
-									Account: serial.ToTypedMessage(&socks.Account{
-										Username: "Test Account",
-										Password: "Test Password",
-									}),
-								},
-							},
+					Server: &protocol.ServerEndpoint{
+						Address: net.NewIPOrDomain(net.LocalHostIP),
+						Port:    uint32(serverPort),
+						User:    &protocol.User{
+							Account: serial.ToTypedMessage(&socks.Account{
+								Username: "Test Account",
+								Password: "Test Password",
+							}),
 						},
 					},
 				}),
@@ -375,11 +363,9 @@ func TestSocksBridageUDPWithRouting(t *testing.T) {
 		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&socks.ClientConfig{
-					Server: []*protocol.ServerEndpoint{
-						{
-							Address: net.NewIPOrDomain(net.LocalHostIP),
-							Port:    uint32(serverPort),
-						},
+					Server: &protocol.ServerEndpoint{
+						Address: net.NewIPOrDomain(net.LocalHostIP),
+						Port:    uint32(serverPort),
 					},
 				}),
 			},

+ 70 - 110
testing/scenarios/tls_test.go

@@ -89,17 +89,13 @@ func TestSimpleTLSConnection(t *testing.T) {
 		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&outbound.Config{
-					Receiver: []*protocol.ServerEndpoint{
-						{
-							Address: net.NewIPOrDomain(net.LocalHostIP),
-							Port:    uint32(serverPort),
-							User: []*protocol.User{
-								{
-									Account: serial.ToTypedMessage(&vmess.Account{
-										Id: userID.String(),
-									}),
-								},
-							},
+					Receiver: &protocol.ServerEndpoint{
+						Address: net.NewIPOrDomain(net.LocalHostIP),
+						Port:    uint32(serverPort),
+						User:    &protocol.User{
+							Account: serial.ToTypedMessage(&vmess.Account{
+								Id: userID.String(),
+							}),
 						},
 					},
 				}),
@@ -204,17 +200,13 @@ func TestAutoIssuingCertificate(t *testing.T) {
 		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&outbound.Config{
-					Receiver: []*protocol.ServerEndpoint{
-						{
-							Address: net.NewIPOrDomain(net.LocalHostIP),
-							Port:    uint32(serverPort),
-							User: []*protocol.User{
-								{
-									Account: serial.ToTypedMessage(&vmess.Account{
-										Id: userID.String(),
-									}),
-								},
-							},
+					Receiver: &protocol.ServerEndpoint{
+						Address: net.NewIPOrDomain(net.LocalHostIP),
+						Port:    uint32(serverPort),
+						User:    &protocol.User{
+							Account: serial.ToTypedMessage(&vmess.Account{
+								Id: userID.String(),
+							}),
 						},
 					},
 				}),
@@ -309,17 +301,13 @@ func TestTLSOverKCP(t *testing.T) {
 		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&outbound.Config{
-					Receiver: []*protocol.ServerEndpoint{
-						{
-							Address: net.NewIPOrDomain(net.LocalHostIP),
-							Port:    uint32(serverPort),
-							User: []*protocol.User{
-								{
-									Account: serial.ToTypedMessage(&vmess.Account{
-										Id: userID.String(),
-									}),
-								},
-							},
+					Receiver: &protocol.ServerEndpoint{
+						Address: net.NewIPOrDomain(net.LocalHostIP),
+						Port:    uint32(serverPort),
+						User:    &protocol.User{
+							Account: serial.ToTypedMessage(&vmess.Account{
+								Id: userID.String(),
+							}),
 						},
 					},
 				}),
@@ -409,17 +397,13 @@ func TestTLSOverWebSocket(t *testing.T) {
 		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&outbound.Config{
-					Receiver: []*protocol.ServerEndpoint{
-						{
-							Address: net.NewIPOrDomain(net.LocalHostIP),
-							Port:    uint32(serverPort),
-							User: []*protocol.User{
-								{
-									Account: serial.ToTypedMessage(&vmess.Account{
-										Id: userID.String(),
-									}),
-								},
-							},
+					Receiver: &protocol.ServerEndpoint{
+						Address: net.NewIPOrDomain(net.LocalHostIP),
+						Port:    uint32(serverPort),
+						User:    &protocol.User{
+							Account: serial.ToTypedMessage(&vmess.Account{
+								Id: userID.String(),
+							}),
 						},
 					},
 				}),
@@ -525,17 +509,13 @@ func TestGRPC(t *testing.T) {
 		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&outbound.Config{
-					Receiver: []*protocol.ServerEndpoint{
-						{
-							Address: net.NewIPOrDomain(net.LocalHostIP),
-							Port:    uint32(serverPort),
-							User: []*protocol.User{
-								{
-									Account: serial.ToTypedMessage(&vmess.Account{
-										Id: userID.String(),
-									}),
-								},
-							},
+					Receiver: &protocol.ServerEndpoint{
+						Address: net.NewIPOrDomain(net.LocalHostIP),
+						Port:    uint32(serverPort),
+						User:    &protocol.User{
+							Account: serial.ToTypedMessage(&vmess.Account{
+								Id: userID.String(),
+							}),
 						},
 					},
 				}),
@@ -641,17 +621,13 @@ func TestGRPCMultiMode(t *testing.T) {
 		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&outbound.Config{
-					Receiver: []*protocol.ServerEndpoint{
-						{
-							Address: net.NewIPOrDomain(net.LocalHostIP),
-							Port:    uint32(serverPort),
-							User: []*protocol.User{
-								{
-									Account: serial.ToTypedMessage(&vmess.Account{
-										Id: userID.String(),
-									}),
-								},
-							},
+					Receiver: &protocol.ServerEndpoint{
+						Address: net.NewIPOrDomain(net.LocalHostIP),
+						Port:    uint32(serverPort),
+						User:    &protocol.User{
+							Account: serial.ToTypedMessage(&vmess.Account{
+								Id: userID.String(),
+							}),
 						},
 					},
 				}),
@@ -752,17 +728,13 @@ func TestSimpleTLSConnectionPinned(t *testing.T) {
 		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&outbound.Config{
-					Receiver: []*protocol.ServerEndpoint{
-						{
-							Address: net.NewIPOrDomain(net.LocalHostIP),
-							Port:    uint32(serverPort),
-							User: []*protocol.User{
-								{
-									Account: serial.ToTypedMessage(&vmess.Account{
-										Id: userID.String(),
-									}),
-								},
-							},
+					Receiver: &protocol.ServerEndpoint{
+						Address: net.NewIPOrDomain(net.LocalHostIP),
+						Port:    uint32(serverPort),
+						User:    &protocol.User{
+							Account: serial.ToTypedMessage(&vmess.Account{
+								Id: userID.String(),
+							}),
 						},
 					},
 				}),
@@ -854,17 +826,13 @@ func TestSimpleTLSConnectionPinnedWrongCert(t *testing.T) {
 		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&outbound.Config{
-					Receiver: []*protocol.ServerEndpoint{
-						{
-							Address: net.NewIPOrDomain(net.LocalHostIP),
-							Port:    uint32(serverPort),
-							User: []*protocol.User{
-								{
-									Account: serial.ToTypedMessage(&vmess.Account{
-										Id: userID.String(),
-									}),
-								},
-							},
+					Receiver: &protocol.ServerEndpoint{
+						Address: net.NewIPOrDomain(net.LocalHostIP),
+						Port:    uint32(serverPort),
+						User:    &protocol.User{
+							Account: serial.ToTypedMessage(&vmess.Account{
+								Id: userID.String(),
+							}),
 						},
 					},
 				}),
@@ -955,17 +923,13 @@ func TestUTLSConnectionPinned(t *testing.T) {
 		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&outbound.Config{
-					Receiver: []*protocol.ServerEndpoint{
-						{
-							Address: net.NewIPOrDomain(net.LocalHostIP),
-							Port:    uint32(serverPort),
-							User: []*protocol.User{
-								{
-									Account: serial.ToTypedMessage(&vmess.Account{
-										Id: userID.String(),
-									}),
-								},
-							},
+					Receiver: &protocol.ServerEndpoint{
+						Address: net.NewIPOrDomain(net.LocalHostIP),
+						Port:    uint32(serverPort),
+						User:    &protocol.User{
+							Account: serial.ToTypedMessage(&vmess.Account{
+								Id: userID.String(),
+							}),
 						},
 					},
 				}),
@@ -1058,17 +1022,13 @@ func TestUTLSConnectionPinnedWrongCert(t *testing.T) {
 		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&outbound.Config{
-					Receiver: []*protocol.ServerEndpoint{
-						{
-							Address: net.NewIPOrDomain(net.LocalHostIP),
-							Port:    uint32(serverPort),
-							User: []*protocol.User{
-								{
-									Account: serial.ToTypedMessage(&vmess.Account{
-										Id: userID.String(),
-									}),
-								},
-							},
+					Receiver: &protocol.ServerEndpoint{
+						Address: net.NewIPOrDomain(net.LocalHostIP),
+						Port:    uint32(serverPort),
+						User:    &protocol.User{
+							Account: serial.ToTypedMessage(&vmess.Account{
+								Id: userID.String(),
+							}),
 						},
 					},
 				}),

+ 7 - 11
testing/scenarios/transport_test.go

@@ -85,17 +85,13 @@ func TestHTTPConnectionHeader(t *testing.T) {
 		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&outbound.Config{
-					Receiver: []*protocol.ServerEndpoint{
-						{
-							Address: net.NewIPOrDomain(net.LocalHostIP),
-							Port:    uint32(serverPort),
-							User: []*protocol.User{
-								{
-									Account: serial.ToTypedMessage(&vmess.Account{
-										Id: userID.String(),
-									}),
-								},
-							},
+					Receiver: &protocol.ServerEndpoint{
+						Address: net.NewIPOrDomain(net.LocalHostIP),
+						Port:    uint32(serverPort),
+						User:    &protocol.User{
+							Account: serial.ToTypedMessage(&vmess.Account{
+								Id: userID.String(),
+							}),
 						},
 					},
 				}),

+ 30 - 46
testing/scenarios/vless_test.go

@@ -94,17 +94,13 @@ func TestVless(t *testing.T) {
 		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&outbound.Config{
-					Vnext: []*protocol.ServerEndpoint{
-						{
-							Address: net.NewIPOrDomain(net.LocalHostIP),
-							Port:    uint32(serverPort),
-							User: []*protocol.User{
-								{
-									Account: serial.ToTypedMessage(&vless.Account{
-										Id: userID.String(),
-									}),
-								},
-							},
+					Vnext: &protocol.ServerEndpoint{
+						Address: net.NewIPOrDomain(net.LocalHostIP),
+						Port:    uint32(serverPort),
+						User:    &protocol.User{
+							Account: serial.ToTypedMessage(&vless.Account{
+								Id: userID.String(),
+							}),
 						},
 					},
 				}),
@@ -199,17 +195,13 @@ func TestVlessTls(t *testing.T) {
 		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&outbound.Config{
-					Vnext: []*protocol.ServerEndpoint{
-						{
-							Address: net.NewIPOrDomain(net.LocalHostIP),
-							Port:    uint32(serverPort),
-							User: []*protocol.User{
-								{
-									Account: serial.ToTypedMessage(&vless.Account{
-										Id: userID.String(),
-									}),
-								},
-							},
+					Vnext: &protocol.ServerEndpoint{
+						Address: net.NewIPOrDomain(net.LocalHostIP),
+						Port:    uint32(serverPort),
+						User:    &protocol.User{
+							Account: serial.ToTypedMessage(&vless.Account{
+								Id: userID.String(),
+							}),
 						},
 					},
 				}),
@@ -322,18 +314,14 @@ func TestVlessXtlsVision(t *testing.T) {
 		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&outbound.Config{
-					Vnext: []*protocol.ServerEndpoint{
-						{
-							Address: net.NewIPOrDomain(net.LocalHostIP),
-							Port:    uint32(serverPort),
-							User: []*protocol.User{
-								{
-									Account: serial.ToTypedMessage(&vless.Account{
-										Id:   userID.String(),
-										Flow: vless.XRV,
-									}),
-								},
-							},
+					Vnext: &protocol.ServerEndpoint{
+						Address: net.NewIPOrDomain(net.LocalHostIP),
+						Port:    uint32(serverPort),
+						User:    &protocol.User{
+							Account: serial.ToTypedMessage(&vless.Account{
+								Id:   userID.String(),
+								Flow: vless.XRV,
+							}),
 						},
 					},
 				}),
@@ -456,18 +444,14 @@ func TestVlessXtlsVisionReality(t *testing.T) {
 		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&outbound.Config{
-					Vnext: []*protocol.ServerEndpoint{
-						{
-							Address: net.NewIPOrDomain(net.LocalHostIP),
-							Port:    uint32(serverPort),
-							User: []*protocol.User{
-								{
-									Account: serial.ToTypedMessage(&vless.Account{
-										Id:   userID.String(),
-										Flow: vless.XRV,
-									}),
-								},
-							},
+					Vnext: &protocol.ServerEndpoint{
+						Address: net.NewIPOrDomain(net.LocalHostIP),
+						Port:    uint32(serverPort),
+						User:    &protocol.User{
+							Account: serial.ToTypedMessage(&vless.Account{
+								Id:   userID.String(),
+								Flow: vless.XRV,
+							}),
 						},
 					},
 				}),

+ 110 - 299
testing/scenarios/vmess_test.go

@@ -26,147 +26,6 @@ import (
 	"golang.org/x/sync/errgroup"
 )
 
-func TestVMessDynamicPort(t *testing.T) {
-	tcpServer := tcp.Server{
-		MsgProcessor: xor,
-	}
-	dest, err := tcpServer.Start()
-	common.Must(err)
-	defer tcpServer.Close()
-
-	userID := protocol.NewID(uuid.New())
-
-	retry := 1
-	serverPort := tcp.PickPort()
-	for {
-		serverConfig := &core.Config{
-			App: []*serial.TypedMessage{
-				serial.ToTypedMessage(&log.Config{
-					ErrorLogLevel: clog.Severity_Debug,
-					ErrorLogType:  log.LogType_Console,
-				}),
-			},
-			Inbound: []*core.InboundHandlerConfig{
-				{
-					ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
-						PortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(serverPort)}},
-						Listen:   net.NewIPOrDomain(net.LocalHostIP),
-					}),
-					ProxySettings: serial.ToTypedMessage(&inbound.Config{
-						User: []*protocol.User{
-							{
-								Account: serial.ToTypedMessage(&vmess.Account{
-									Id: userID.String(),
-								}),
-							},
-						},
-						Detour: &inbound.DetourConfig{
-							To: "detour",
-						},
-					}),
-				},
-				{
-					ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
-						PortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(serverPort + 100)}},
-						Listen:   net.NewIPOrDomain(net.LocalHostIP),
-					}),
-					ProxySettings: serial.ToTypedMessage(&dokodemo.Config{
-						Address:  net.NewIPOrDomain(dest.Address),
-						Port:     uint32(dest.Port),
-						Networks: []net.Network{net.Network_TCP},
-					}),
-				},
-				{
-					ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
-						PortList: &net.PortList{
-							Range: []*net.PortRange{{From: uint32(serverPort + 1), To: uint32(serverPort + 99)}},
-						},
-
-						Listen: net.NewIPOrDomain(net.LocalHostIP),
-						AllocationStrategy: &proxyman.AllocationStrategy{
-							Type: proxyman.AllocationStrategy_Random,
-							Concurrency: &proxyman.AllocationStrategy_AllocationStrategyConcurrency{
-								Value: 2,
-							},
-							Refresh: &proxyman.AllocationStrategy_AllocationStrategyRefresh{
-								Value: 5,
-							},
-						},
-					}),
-					ProxySettings: serial.ToTypedMessage(&inbound.Config{}),
-					Tag:           "detour",
-				},
-			},
-			Outbound: []*core.OutboundHandlerConfig{
-				{
-					ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
-				},
-			},
-		}
-
-		server, _ := InitializeServerConfig(serverConfig)
-		if server != nil && WaitConnAvailableWithTest(t, testTCPConn(serverPort+100, 1024, time.Second*2)) {
-			defer CloseServer(server)
-			break
-		}
-		retry += 1
-		if retry > 5 {
-			t.Fatal("All attempts failed to start server")
-		}
-		serverPort = tcp.PickPort()
-	}
-
-	clientPort := tcp.PickPort()
-	clientConfig := &core.Config{
-		App: []*serial.TypedMessage{
-			serial.ToTypedMessage(&log.Config{
-				ErrorLogLevel: clog.Severity_Debug,
-				ErrorLogType:  log.LogType_Console,
-			}),
-		},
-		Inbound: []*core.InboundHandlerConfig{
-			{
-				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
-					PortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(clientPort)}},
-					Listen:   net.NewIPOrDomain(net.LocalHostIP),
-				}),
-				ProxySettings: serial.ToTypedMessage(&dokodemo.Config{
-					Address:  net.NewIPOrDomain(dest.Address),
-					Port:     uint32(dest.Port),
-					Networks: []net.Network{net.Network_TCP},
-				}),
-			},
-		},
-		Outbound: []*core.OutboundHandlerConfig{
-			{
-				ProxySettings: serial.ToTypedMessage(&outbound.Config{
-					Receiver: []*protocol.ServerEndpoint{
-						{
-							Address: net.NewIPOrDomain(net.LocalHostIP),
-							Port:    uint32(serverPort),
-							User: []*protocol.User{
-								{
-									Account: serial.ToTypedMessage(&vmess.Account{
-										Id: userID.String(),
-									}),
-								},
-							},
-						},
-					},
-				}),
-			},
-		},
-	}
-
-	server, err := InitializeServerConfig(clientConfig)
-	common.Must(err)
-	defer CloseServer(server)
-
-	if !WaitConnAvailableWithTest(t, testTCPConn(clientPort, 1024, time.Second*2)) {
-		t.Fail()
-	}
-}
-
 func TestVMessGCM(t *testing.T) {
 	tcpServer := tcp.Server{
 		MsgProcessor: xor,
@@ -232,20 +91,16 @@ func TestVMessGCM(t *testing.T) {
 		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&outbound.Config{
-					Receiver: []*protocol.ServerEndpoint{
-						{
-							Address: net.NewIPOrDomain(net.LocalHostIP),
-							Port:    uint32(serverPort),
-							User: []*protocol.User{
-								{
-									Account: serial.ToTypedMessage(&vmess.Account{
-										Id: userID.String(),
-										SecuritySettings: &protocol.SecurityConfig{
-											Type: protocol.SecurityType_AES128_GCM,
-										},
-									}),
+					Receiver: &protocol.ServerEndpoint{
+						Address: net.NewIPOrDomain(net.LocalHostIP),
+						Port:    uint32(serverPort),
+						User:    &protocol.User{
+							Account: serial.ToTypedMessage(&vmess.Account{
+								Id:               userID.String(),
+								SecuritySettings: &protocol.SecurityConfig{
+									Type: protocol.SecurityType_AES128_GCM,
 								},
-							},
+							}),
 						},
 					},
 				}),
@@ -334,20 +189,16 @@ func TestVMessGCMReadv(t *testing.T) {
 		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&outbound.Config{
-					Receiver: []*protocol.ServerEndpoint{
-						{
-							Address: net.NewIPOrDomain(net.LocalHostIP),
-							Port:    uint32(serverPort),
-							User: []*protocol.User{
-								{
-									Account: serial.ToTypedMessage(&vmess.Account{
-										Id: userID.String(),
-										SecuritySettings: &protocol.SecurityConfig{
-											Type: protocol.SecurityType_AES128_GCM,
-										},
-									}),
+					Receiver: &protocol.ServerEndpoint{
+						Address: net.NewIPOrDomain(net.LocalHostIP),
+						Port:    uint32(serverPort),
+						User:    &protocol.User{
+							Account: serial.ToTypedMessage(&vmess.Account{
+								Id:               userID.String(),
+								SecuritySettings: &protocol.SecurityConfig{
+									Type: protocol.SecurityType_AES128_GCM,
 								},
-							},
+							}),
 						},
 					},
 				}),
@@ -439,20 +290,16 @@ func TestVMessGCMUDP(t *testing.T) {
 		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&outbound.Config{
-					Receiver: []*protocol.ServerEndpoint{
-						{
-							Address: net.NewIPOrDomain(net.LocalHostIP),
-							Port:    uint32(serverPort),
-							User: []*protocol.User{
-								{
-									Account: serial.ToTypedMessage(&vmess.Account{
-										Id: userID.String(),
-										SecuritySettings: &protocol.SecurityConfig{
-											Type: protocol.SecurityType_AES128_GCM,
-										},
-									}),
+					Receiver: &protocol.ServerEndpoint{
+						Address: net.NewIPOrDomain(net.LocalHostIP),
+						Port:    uint32(serverPort),
+						User:    &protocol.User{
+							Account: serial.ToTypedMessage(&vmess.Account{
+								Id:               userID.String(),
+								SecuritySettings: &protocol.SecurityConfig{
+									Type: protocol.SecurityType_AES128_GCM,
 								},
-							},
+							}),
 						},
 					},
 				}),
@@ -538,20 +385,16 @@ func TestVMessChacha20(t *testing.T) {
 		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&outbound.Config{
-					Receiver: []*protocol.ServerEndpoint{
-						{
-							Address: net.NewIPOrDomain(net.LocalHostIP),
-							Port:    uint32(serverPort),
-							User: []*protocol.User{
-								{
-									Account: serial.ToTypedMessage(&vmess.Account{
-										Id: userID.String(),
-										SecuritySettings: &protocol.SecurityConfig{
-											Type: protocol.SecurityType_CHACHA20_POLY1305,
-										},
-									}),
+					Receiver: &protocol.ServerEndpoint{
+						Address: net.NewIPOrDomain(net.LocalHostIP),
+						Port:    uint32(serverPort),
+						User:    &protocol.User{
+							Account: serial.ToTypedMessage(&vmess.Account{
+								Id:               userID.String(),
+								SecuritySettings: &protocol.SecurityConfig{
+									Type: protocol.SecurityType_CHACHA20_POLY1305,
 								},
-							},
+							}),
 						},
 					},
 				}),
@@ -638,20 +481,16 @@ func TestVMessNone(t *testing.T) {
 		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&outbound.Config{
-					Receiver: []*protocol.ServerEndpoint{
-						{
-							Address: net.NewIPOrDomain(net.LocalHostIP),
-							Port:    uint32(serverPort),
-							User: []*protocol.User{
-								{
-									Account: serial.ToTypedMessage(&vmess.Account{
-										Id: userID.String(),
-										SecuritySettings: &protocol.SecurityConfig{
-											Type: protocol.SecurityType_NONE,
-										},
-									}),
+					Receiver: &protocol.ServerEndpoint{
+						Address: net.NewIPOrDomain(net.LocalHostIP),
+						Port:    uint32(serverPort),
+						User:    &protocol.User{
+							Account: serial.ToTypedMessage(&vmess.Account{
+								Id: userID.String(),
+								SecuritySettings: &protocol.SecurityConfig{
+									Type: protocol.SecurityType_NONE,
 								},
-							},
+							}),
 						},
 					},
 				}),
@@ -740,20 +579,16 @@ func TestVMessKCP(t *testing.T) {
 		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&outbound.Config{
-					Receiver: []*protocol.ServerEndpoint{
-						{
-							Address: net.NewIPOrDomain(net.LocalHostIP),
-							Port:    uint32(serverPort),
-							User: []*protocol.User{
-								{
-									Account: serial.ToTypedMessage(&vmess.Account{
-										Id: userID.String(),
-										SecuritySettings: &protocol.SecurityConfig{
-											Type: protocol.SecurityType_AES128_GCM,
-										},
-									}),
+					Receiver: &protocol.ServerEndpoint{
+						Address: net.NewIPOrDomain(net.LocalHostIP),
+						Port:    uint32(serverPort),
+						User: &protocol.User{
+							Account: serial.ToTypedMessage(&vmess.Account{
+								Id: userID.String(),
+								SecuritySettings: &protocol.SecurityConfig{
+									Type: protocol.SecurityType_AES128_GCM,
 								},
-							},
+							}),
 						},
 					},
 				}),
@@ -866,20 +701,16 @@ func TestVMessKCPLarge(t *testing.T) {
 		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&outbound.Config{
-					Receiver: []*protocol.ServerEndpoint{
-						{
-							Address: net.NewIPOrDomain(net.LocalHostIP),
-							Port:    uint32(serverPort),
-							User: []*protocol.User{
-								{
-									Account: serial.ToTypedMessage(&vmess.Account{
-										Id: userID.String(),
-										SecuritySettings: &protocol.SecurityConfig{
-											Type: protocol.SecurityType_AES128_GCM,
-										},
-									}),
+					Receiver: &protocol.ServerEndpoint{
+						Address: net.NewIPOrDomain(net.LocalHostIP),
+						Port:    uint32(serverPort),
+						User:    &protocol.User{
+							Account: serial.ToTypedMessage(&vmess.Account{
+								Id: userID.String(),
+								SecuritySettings: &protocol.SecurityConfig{
+									Type: protocol.SecurityType_AES128_GCM,
 								},
-							},
+							}),
 						},
 					},
 				}),
@@ -999,20 +830,16 @@ func TestVMessGCMMux(t *testing.T) {
 					},
 				}),
 				ProxySettings: serial.ToTypedMessage(&outbound.Config{
-					Receiver: []*protocol.ServerEndpoint{
-						{
-							Address: net.NewIPOrDomain(net.LocalHostIP),
-							Port:    uint32(serverPort),
-							User: []*protocol.User{
-								{
-									Account: serial.ToTypedMessage(&vmess.Account{
-										Id: userID.String(),
-										SecuritySettings: &protocol.SecurityConfig{
-											Type: protocol.SecurityType_AES128_GCM,
-										},
-									}),
+					Receiver: &protocol.ServerEndpoint{
+						Address: net.NewIPOrDomain(net.LocalHostIP),
+						Port:    uint32(serverPort),
+						User:    &protocol.User{
+							Account: serial.ToTypedMessage(&vmess.Account{
+								Id: userID.String(),
+								SecuritySettings: &protocol.SecurityConfig{
+									Type: protocol.SecurityType_AES128_GCM,
 								},
-							},
+							}),
 						},
 					},
 				}),
@@ -1126,20 +953,16 @@ func TestVMessGCMMuxUDP(t *testing.T) {
 					},
 				}),
 				ProxySettings: serial.ToTypedMessage(&outbound.Config{
-					Receiver: []*protocol.ServerEndpoint{
-						{
-							Address: net.NewIPOrDomain(net.LocalHostIP),
-							Port:    uint32(serverPort),
-							User: []*protocol.User{
-								{
-									Account: serial.ToTypedMessage(&vmess.Account{
-										Id: userID.String(),
-										SecuritySettings: &protocol.SecurityConfig{
-											Type: protocol.SecurityType_AES128_GCM,
-										},
-									}),
+					Receiver: &protocol.ServerEndpoint{
+						Address: net.NewIPOrDomain(net.LocalHostIP),
+						Port:    uint32(serverPort),
+						User:    &protocol.User{
+							Account: serial.ToTypedMessage(&vmess.Account{
+								Id: userID.String(),
+								SecuritySettings: &protocol.SecurityConfig{
+									Type: protocol.SecurityType_AES128_GCM,
 								},
-							},
+							}),
 						},
 					},
 				}),
@@ -1233,20 +1056,16 @@ func TestVMessZero(t *testing.T) {
 		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&outbound.Config{
-					Receiver: []*protocol.ServerEndpoint{
-						{
-							Address: net.NewIPOrDomain(net.LocalHostIP),
-							Port:    uint32(serverPort),
-							User: []*protocol.User{
-								{
-									Account: serial.ToTypedMessage(&vmess.Account{
-										Id: userID.String(),
-										SecuritySettings: &protocol.SecurityConfig{
-											Type: protocol.SecurityType_ZERO,
-										},
-									}),
+					Receiver: &protocol.ServerEndpoint{
+						Address: net.NewIPOrDomain(net.LocalHostIP),
+						Port:    uint32(serverPort),
+						User:    &protocol.User{
+							Account: serial.ToTypedMessage(&vmess.Account{
+								Id: userID.String(),
+								SecuritySettings: &protocol.SecurityConfig{
+									Type: protocol.SecurityType_ZERO,
 								},
-							},
+							}),
 						},
 					},
 				}),
@@ -1332,21 +1151,17 @@ func TestVMessGCMLengthAuth(t *testing.T) {
 		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&outbound.Config{
-					Receiver: []*protocol.ServerEndpoint{
-						{
-							Address: net.NewIPOrDomain(net.LocalHostIP),
-							Port:    uint32(serverPort),
-							User: []*protocol.User{
-								{
-									Account: serial.ToTypedMessage(&vmess.Account{
-										Id: userID.String(),
-										SecuritySettings: &protocol.SecurityConfig{
-											Type: protocol.SecurityType_AES128_GCM,
-										},
-										TestsEnabled: "AuthenticatedLength",
-									}),
+					Receiver: &protocol.ServerEndpoint{
+						Address: net.NewIPOrDomain(net.LocalHostIP),
+						Port:    uint32(serverPort),
+						User:    &protocol.User{
+							Account: serial.ToTypedMessage(&vmess.Account{
+								Id: userID.String(),
+								SecuritySettings: &protocol.SecurityConfig{
+									Type: protocol.SecurityType_AES128_GCM,
 								},
-							},
+								TestsEnabled: "AuthenticatedLength",
+							}),
 						},
 					},
 				}),
@@ -1436,21 +1251,17 @@ func TestVMessGCMLengthAuthPlusNoTerminationSignal(t *testing.T) {
 		Outbound: []*core.OutboundHandlerConfig{
 			{
 				ProxySettings: serial.ToTypedMessage(&outbound.Config{
-					Receiver: []*protocol.ServerEndpoint{
-						{
-							Address: net.NewIPOrDomain(net.LocalHostIP),
-							Port:    uint32(serverPort),
-							User: []*protocol.User{
-								{
-									Account: serial.ToTypedMessage(&vmess.Account{
-										Id: userID.String(),
-										SecuritySettings: &protocol.SecurityConfig{
-											Type: protocol.SecurityType_AES128_GCM,
-										},
-										TestsEnabled: "AuthenticatedLength|NoTerminationSignal",
-									}),
+					Receiver: &protocol.ServerEndpoint{
+						Address: net.NewIPOrDomain(net.LocalHostIP),
+						Port:    uint32(serverPort),
+						User:    &protocol.User{
+							Account: serial.ToTypedMessage(&vmess.Account{
+								Id: userID.String(),
+								SecuritySettings: &protocol.SecurityConfig{
+									Type: protocol.SecurityType_AES128_GCM,
 								},
-							},
+								TestsEnabled: "AuthenticatedLength|NoTerminationSignal",
+							}),
 						},
 					},
 				}),