diff --git a/app/observatory/command/command.go b/app/observatory/command/command.go new file mode 100644 index 000000000..bf774ad4c --- /dev/null +++ b/app/observatory/command/command.go @@ -0,0 +1,50 @@ +// +build !confonly + +package command + +import ( + "context" + + "google.golang.org/grpc" + + core "github.com/v2fly/v2ray-core/v4" + "github.com/v2fly/v2ray-core/v4/app/observatory" + "github.com/v2fly/v2ray-core/v4/common" + "github.com/v2fly/v2ray-core/v4/features/extension" +) + +type service struct { + UnimplementedObservatoryServiceServer + v *core.Instance + + observatory extension.Observatory +} + +func (s *service) GetOutboundStatus(ctx context.Context, request *GetOutboundStatusRequest) (*GetOutboundStatusResponse, error) { + resp, err := s.observatory.GetObservation(ctx) + if err != nil { + return nil, err + } + retdata := resp.(*observatory.ObservationResult) + return &GetOutboundStatusResponse{ + Status: retdata, + }, nil +} + +func (s *service) Register(server *grpc.Server) { + RegisterObservatoryServiceServer(server, s) +} + +func init() { + common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, cfg interface{}) (interface{}, error) { + s := core.MustFromContext(ctx) + sv := &service{v: s} + err := s.RequireFeatures(func(Observatory extension.Observatory) { + sv.observatory = Observatory + }) + if err != nil { + return nil, err + } + return sv, nil + })) +} diff --git a/app/observatory/command/command.pb.go b/app/observatory/command/command.pb.go new file mode 100644 index 000000000..900bf1a55 --- /dev/null +++ b/app/observatory/command/command.pb.go @@ -0,0 +1,279 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.26.0 +// protoc v3.15.6 +// source: app/observatory/command/command.proto + +package command + +import ( + observatory "github.com/v2fly/v2ray-core/v4/app/observatory" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type GetOutboundStatusRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *GetOutboundStatusRequest) Reset() { + *x = GetOutboundStatusRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_app_observatory_command_command_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetOutboundStatusRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetOutboundStatusRequest) ProtoMessage() {} + +func (x *GetOutboundStatusRequest) ProtoReflect() protoreflect.Message { + mi := &file_app_observatory_command_command_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetOutboundStatusRequest.ProtoReflect.Descriptor instead. +func (*GetOutboundStatusRequest) Descriptor() ([]byte, []int) { + return file_app_observatory_command_command_proto_rawDescGZIP(), []int{0} +} + +type GetOutboundStatusResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Status *observatory.ObservationResult `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"` +} + +func (x *GetOutboundStatusResponse) Reset() { + *x = GetOutboundStatusResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_app_observatory_command_command_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetOutboundStatusResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetOutboundStatusResponse) ProtoMessage() {} + +func (x *GetOutboundStatusResponse) ProtoReflect() protoreflect.Message { + mi := &file_app_observatory_command_command_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetOutboundStatusResponse.ProtoReflect.Descriptor instead. +func (*GetOutboundStatusResponse) Descriptor() ([]byte, []int) { + return file_app_observatory_command_command_proto_rawDescGZIP(), []int{1} +} + +func (x *GetOutboundStatusResponse) GetStatus() *observatory.ObservationResult { + if x != nil { + return x.Status + } + return nil +} + +type Config struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *Config) Reset() { + *x = Config{} + if protoimpl.UnsafeEnabled { + mi := &file_app_observatory_command_command_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Config) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Config) ProtoMessage() {} + +func (x *Config) ProtoReflect() protoreflect.Message { + mi := &file_app_observatory_command_command_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Config.ProtoReflect.Descriptor instead. +func (*Config) Descriptor() ([]byte, []int) { + return file_app_observatory_command_command_proto_rawDescGZIP(), []int{2} +} + +var File_app_observatory_command_command_proto protoreflect.FileDescriptor + +var file_app_observatory_command_command_proto_rawDesc = []byte{ + 0x0a, 0x25, 0x61, 0x70, 0x70, 0x2f, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x6f, 0x72, + 0x79, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, + 0x64, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x22, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, + 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, + 0x6f, 0x72, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x1a, 0x1c, 0x61, 0x70, 0x70, + 0x2f, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x6f, 0x72, 0x79, 0x2f, 0x63, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x1a, 0x0a, 0x18, 0x47, 0x65, 0x74, + 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x62, 0x0a, 0x19, 0x47, 0x65, 0x74, 0x4f, 0x75, 0x74, 0x62, + 0x6f, 0x75, 0x6e, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x45, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, + 0x61, 0x70, 0x70, 0x2e, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x6f, 0x72, 0x79, 0x2e, + 0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x75, 0x6c, + 0x74, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x08, 0x0a, 0x06, 0x43, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x32, 0xa9, 0x01, 0x0a, 0x12, 0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, + 0x6f, 0x72, 0x79, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x92, 0x01, 0x0a, 0x11, 0x47, + 0x65, 0x74, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x12, 0x3c, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, + 0x70, 0x2e, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x63, 0x6f, + 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x47, 0x65, 0x74, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, + 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x3d, + 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, + 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, + 0x61, 0x6e, 0x64, 0x2e, 0x47, 0x65, 0x74, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x53, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, + 0x87, 0x01, 0x0a, 0x26, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, + 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x6f, + 0x72, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x50, 0x01, 0x5a, 0x36, 0x67, 0x69, + 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x76, 0x32, 0x66, 0x6c, 0x79, 0x2f, 0x76, + 0x32, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x76, 0x34, 0x2f, 0x61, 0x70, 0x70, + 0x2f, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x6f, 0x72, 0x79, 0x2f, 0x63, 0x6f, 0x6d, + 0x6d, 0x61, 0x6e, 0x64, 0xaa, 0x02, 0x22, 0x56, 0x32, 0x52, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72, + 0x65, 0x2e, 0x41, 0x70, 0x70, 0x2e, 0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x6f, 0x72, + 0x79, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x33, +} + +var ( + file_app_observatory_command_command_proto_rawDescOnce sync.Once + file_app_observatory_command_command_proto_rawDescData = file_app_observatory_command_command_proto_rawDesc +) + +func file_app_observatory_command_command_proto_rawDescGZIP() []byte { + file_app_observatory_command_command_proto_rawDescOnce.Do(func() { + file_app_observatory_command_command_proto_rawDescData = protoimpl.X.CompressGZIP(file_app_observatory_command_command_proto_rawDescData) + }) + return file_app_observatory_command_command_proto_rawDescData +} + +var file_app_observatory_command_command_proto_msgTypes = make([]protoimpl.MessageInfo, 3) +var file_app_observatory_command_command_proto_goTypes = []interface{}{ + (*GetOutboundStatusRequest)(nil), // 0: v2ray.core.app.observatory.command.GetOutboundStatusRequest + (*GetOutboundStatusResponse)(nil), // 1: v2ray.core.app.observatory.command.GetOutboundStatusResponse + (*Config)(nil), // 2: v2ray.core.app.observatory.command.Config + (*observatory.ObservationResult)(nil), // 3: v2ray.core.app.observatory.ObservationResult +} +var file_app_observatory_command_command_proto_depIdxs = []int32{ + 3, // 0: v2ray.core.app.observatory.command.GetOutboundStatusResponse.status:type_name -> v2ray.core.app.observatory.ObservationResult + 0, // 1: v2ray.core.app.observatory.command.ObservatoryService.GetOutboundStatus:input_type -> v2ray.core.app.observatory.command.GetOutboundStatusRequest + 1, // 2: v2ray.core.app.observatory.command.ObservatoryService.GetOutboundStatus:output_type -> v2ray.core.app.observatory.command.GetOutboundStatusResponse + 2, // [2:3] is the sub-list for method output_type + 1, // [1:2] is the sub-list for method input_type + 1, // [1:1] is the sub-list for extension type_name + 1, // [1:1] is the sub-list for extension extendee + 0, // [0:1] is the sub-list for field type_name +} + +func init() { file_app_observatory_command_command_proto_init() } +func file_app_observatory_command_command_proto_init() { + if File_app_observatory_command_command_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_app_observatory_command_command_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetOutboundStatusRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_app_observatory_command_command_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetOutboundStatusResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_app_observatory_command_command_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Config); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_app_observatory_command_command_proto_rawDesc, + NumEnums: 0, + NumMessages: 3, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_app_observatory_command_command_proto_goTypes, + DependencyIndexes: file_app_observatory_command_command_proto_depIdxs, + MessageInfos: file_app_observatory_command_command_proto_msgTypes, + }.Build() + File_app_observatory_command_command_proto = out.File + file_app_observatory_command_command_proto_rawDesc = nil + file_app_observatory_command_command_proto_goTypes = nil + file_app_observatory_command_command_proto_depIdxs = nil +} diff --git a/app/observatory/command/command.proto b/app/observatory/command/command.proto new file mode 100644 index 000000000..74c71269a --- /dev/null +++ b/app/observatory/command/command.proto @@ -0,0 +1,24 @@ +syntax = "proto3"; + +package v2ray.core.app.observatory.command; +option csharp_namespace = "V2Ray.Core.App.Observatory.Command"; +option go_package = "github.com/v2fly/v2ray-core/v4/app/observatory/command"; +option java_package = "com.v2ray.core.app.observatory.command"; +option java_multiple_files = true; + +import "app/observatory/config.proto"; + +message GetOutboundStatusRequest { +} + +message GetOutboundStatusResponse { + v2ray.core.app.observatory.ObservationResult status = 1; +} + +service ObservatoryService { + rpc GetOutboundStatus(GetOutboundStatusRequest) + returns (GetOutboundStatusResponse) {} +} + + +message Config {} \ No newline at end of file diff --git a/app/observatory/command/command_grpc.pb.go b/app/observatory/command/command_grpc.pb.go new file mode 100644 index 000000000..b3692fae3 --- /dev/null +++ b/app/observatory/command/command_grpc.pb.go @@ -0,0 +1,101 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. + +package command + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +// ObservatoryServiceClient is the client API for ObservatoryService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type ObservatoryServiceClient interface { + GetOutboundStatus(ctx context.Context, in *GetOutboundStatusRequest, opts ...grpc.CallOption) (*GetOutboundStatusResponse, error) +} + +type observatoryServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewObservatoryServiceClient(cc grpc.ClientConnInterface) ObservatoryServiceClient { + return &observatoryServiceClient{cc} +} + +func (c *observatoryServiceClient) GetOutboundStatus(ctx context.Context, in *GetOutboundStatusRequest, opts ...grpc.CallOption) (*GetOutboundStatusResponse, error) { + out := new(GetOutboundStatusResponse) + err := c.cc.Invoke(ctx, "/v2ray.core.app.observatory.command.ObservatoryService/GetOutboundStatus", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// ObservatoryServiceServer is the server API for ObservatoryService service. +// All implementations must embed UnimplementedObservatoryServiceServer +// for forward compatibility +type ObservatoryServiceServer interface { + GetOutboundStatus(context.Context, *GetOutboundStatusRequest) (*GetOutboundStatusResponse, error) + mustEmbedUnimplementedObservatoryServiceServer() +} + +// UnimplementedObservatoryServiceServer must be embedded to have forward compatible implementations. +type UnimplementedObservatoryServiceServer struct { +} + +func (UnimplementedObservatoryServiceServer) GetOutboundStatus(context.Context, *GetOutboundStatusRequest) (*GetOutboundStatusResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetOutboundStatus not implemented") +} +func (UnimplementedObservatoryServiceServer) mustEmbedUnimplementedObservatoryServiceServer() {} + +// UnsafeObservatoryServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to ObservatoryServiceServer will +// result in compilation errors. +type UnsafeObservatoryServiceServer interface { + mustEmbedUnimplementedObservatoryServiceServer() +} + +func RegisterObservatoryServiceServer(s grpc.ServiceRegistrar, srv ObservatoryServiceServer) { + s.RegisterService(&ObservatoryService_ServiceDesc, srv) +} + +func _ObservatoryService_GetOutboundStatus_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetOutboundStatusRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ObservatoryServiceServer).GetOutboundStatus(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/v2ray.core.app.observatory.command.ObservatoryService/GetOutboundStatus", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ObservatoryServiceServer).GetOutboundStatus(ctx, req.(*GetOutboundStatusRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// ObservatoryService_ServiceDesc is the grpc.ServiceDesc for ObservatoryService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var ObservatoryService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "v2ray.core.app.observatory.command.ObservatoryService", + HandlerType: (*ObservatoryServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "GetOutboundStatus", + Handler: _ObservatoryService_GetOutboundStatus_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "app/observatory/command/command.proto", +} diff --git a/app/observatory/config.pb.go b/app/observatory/config.pb.go new file mode 100644 index 000000000..a28ae76fc --- /dev/null +++ b/app/observatory/config.pb.go @@ -0,0 +1,500 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.26.0 +// protoc v3.15.6 +// source: app/observatory/config.proto + +package observatory + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type ObservationResult struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Status []*OutboundStatus `protobuf:"bytes,1,rep,name=status,proto3" json:"status,omitempty"` +} + +func (x *ObservationResult) Reset() { + *x = ObservationResult{} + if protoimpl.UnsafeEnabled { + mi := &file_app_observatory_config_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ObservationResult) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ObservationResult) ProtoMessage() {} + +func (x *ObservationResult) ProtoReflect() protoreflect.Message { + mi := &file_app_observatory_config_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ObservationResult.ProtoReflect.Descriptor instead. +func (*ObservationResult) Descriptor() ([]byte, []int) { + return file_app_observatory_config_proto_rawDescGZIP(), []int{0} +} + +func (x *ObservationResult) GetStatus() []*OutboundStatus { + if x != nil { + return x.Status + } + return nil +} + +type OutboundStatus struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // @Document Whether this outbound is usable + //@Restriction ReadOnlyForUser + Alive bool `protobuf:"varint,1,opt,name=alive,proto3" json:"alive,omitempty"` + // @Document The time for probe request to finish. + //@Type time.ms + //@Restriction ReadOnlyForUser + Delay int64 `protobuf:"varint,2,opt,name=delay,proto3" json:"delay,omitempty"` + // @Document The last error caused this outbound failed to relay probe request + //@Restriction NotMachineReadable + LastErrorReason string `protobuf:"bytes,3,opt,name=last_error_reason,json=lastErrorReason,proto3" json:"last_error_reason,omitempty"` + // @Document The outbound tag for this Server + //@Type id.outboundTag + OutboundTag string `protobuf:"bytes,4,opt,name=outbound_tag,json=outboundTag,proto3" json:"outbound_tag,omitempty"` + // @Document The time this outbound is known to be alive + //@Type id.outboundTag + LastSeenTime int64 `protobuf:"varint,5,opt,name=last_seen_time,json=lastSeenTime,proto3" json:"last_seen_time,omitempty"` + // @Document The time this outbound is tried + //@Type id.outboundTag + LastTryTime int64 `protobuf:"varint,6,opt,name=last_try_time,json=lastTryTime,proto3" json:"last_try_time,omitempty"` +} + +func (x *OutboundStatus) Reset() { + *x = OutboundStatus{} + if protoimpl.UnsafeEnabled { + mi := &file_app_observatory_config_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *OutboundStatus) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*OutboundStatus) ProtoMessage() {} + +func (x *OutboundStatus) ProtoReflect() protoreflect.Message { + mi := &file_app_observatory_config_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use OutboundStatus.ProtoReflect.Descriptor instead. +func (*OutboundStatus) Descriptor() ([]byte, []int) { + return file_app_observatory_config_proto_rawDescGZIP(), []int{1} +} + +func (x *OutboundStatus) GetAlive() bool { + if x != nil { + return x.Alive + } + return false +} + +func (x *OutboundStatus) GetDelay() int64 { + if x != nil { + return x.Delay + } + return 0 +} + +func (x *OutboundStatus) GetLastErrorReason() string { + if x != nil { + return x.LastErrorReason + } + return "" +} + +func (x *OutboundStatus) GetOutboundTag() string { + if x != nil { + return x.OutboundTag + } + return "" +} + +func (x *OutboundStatus) GetLastSeenTime() int64 { + if x != nil { + return x.LastSeenTime + } + return 0 +} + +func (x *OutboundStatus) GetLastTryTime() int64 { + if x != nil { + return x.LastTryTime + } + return 0 +} + +type ProbeResult struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // @Document Whether this outbound is usable + //@Restriction ReadOnlyForUser + Alive bool `protobuf:"varint,1,opt,name=alive,proto3" json:"alive,omitempty"` + // @Document The time for probe request to finish. + //@Type time.ms + //@Restriction ReadOnlyForUser + Delay int64 `protobuf:"varint,2,opt,name=delay,proto3" json:"delay,omitempty"` + // @Document The error caused this outbound failed to relay probe request + //@Restriction NotMachineReadable + LastErrorReason string `protobuf:"bytes,3,opt,name=last_error_reason,json=lastErrorReason,proto3" json:"last_error_reason,omitempty"` +} + +func (x *ProbeResult) Reset() { + *x = ProbeResult{} + if protoimpl.UnsafeEnabled { + mi := &file_app_observatory_config_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ProbeResult) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ProbeResult) ProtoMessage() {} + +func (x *ProbeResult) ProtoReflect() protoreflect.Message { + mi := &file_app_observatory_config_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ProbeResult.ProtoReflect.Descriptor instead. +func (*ProbeResult) Descriptor() ([]byte, []int) { + return file_app_observatory_config_proto_rawDescGZIP(), []int{2} +} + +func (x *ProbeResult) GetAlive() bool { + if x != nil { + return x.Alive + } + return false +} + +func (x *ProbeResult) GetDelay() int64 { + if x != nil { + return x.Delay + } + return 0 +} + +func (x *ProbeResult) GetLastErrorReason() string { + if x != nil { + return x.LastErrorReason + } + return "" +} + +type Intensity struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // @Document The time interval for a probe request in ms. + //@Type time.ms + ProbeInterval uint32 `protobuf:"varint,1,opt,name=probe_interval,json=probeInterval,proto3" json:"probe_interval,omitempty"` +} + +func (x *Intensity) Reset() { + *x = Intensity{} + if protoimpl.UnsafeEnabled { + mi := &file_app_observatory_config_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Intensity) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Intensity) ProtoMessage() {} + +func (x *Intensity) ProtoReflect() protoreflect.Message { + mi := &file_app_observatory_config_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Intensity.ProtoReflect.Descriptor instead. +func (*Intensity) Descriptor() ([]byte, []int) { + return file_app_observatory_config_proto_rawDescGZIP(), []int{3} +} + +func (x *Intensity) GetProbeInterval() uint32 { + if x != nil { + return x.ProbeInterval + } + return 0 +} + +type Config struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // @Document The selectors for outbound under observation + SubjectSelector []string `protobuf:"bytes,2,rep,name=subject_selector,json=subjectSelector,proto3" json:"subject_selector,omitempty"` +} + +func (x *Config) Reset() { + *x = Config{} + if protoimpl.UnsafeEnabled { + mi := &file_app_observatory_config_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Config) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Config) ProtoMessage() {} + +func (x *Config) ProtoReflect() protoreflect.Message { + mi := &file_app_observatory_config_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Config.ProtoReflect.Descriptor instead. +func (*Config) Descriptor() ([]byte, []int) { + return file_app_observatory_config_proto_rawDescGZIP(), []int{4} +} + +func (x *Config) GetSubjectSelector() []string { + if x != nil { + return x.SubjectSelector + } + return nil +} + +var File_app_observatory_config_proto protoreflect.FileDescriptor + +var file_app_observatory_config_proto_rawDesc = []byte{ + 0x0a, 0x1c, 0x61, 0x70, 0x70, 0x2f, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x6f, 0x72, + 0x79, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x1a, + 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x6f, + 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x6f, 0x72, 0x79, 0x22, 0x57, 0x0a, 0x11, 0x4f, 0x62, + 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, + 0x42, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x2a, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, + 0x2e, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x4f, 0x75, 0x74, + 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x22, 0xd5, 0x01, 0x0a, 0x0e, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, + 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x6c, 0x69, 0x76, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x61, 0x6c, 0x69, 0x76, 0x65, 0x12, 0x14, 0x0a, 0x05, + 0x64, 0x65, 0x6c, 0x61, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x64, 0x65, 0x6c, + 0x61, 0x79, 0x12, 0x2a, 0x0a, 0x11, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, + 0x5f, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x6c, + 0x61, 0x73, 0x74, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x21, + 0x0a, 0x0c, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x74, 0x61, 0x67, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x54, 0x61, + 0x67, 0x12, 0x24, 0x0a, 0x0e, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x73, 0x65, 0x65, 0x6e, 0x5f, 0x74, + 0x69, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x6c, 0x61, 0x73, 0x74, 0x53, + 0x65, 0x65, 0x6e, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x22, 0x0a, 0x0d, 0x6c, 0x61, 0x73, 0x74, 0x5f, + 0x74, 0x72, 0x79, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, + 0x6c, 0x61, 0x73, 0x74, 0x54, 0x72, 0x79, 0x54, 0x69, 0x6d, 0x65, 0x22, 0x65, 0x0a, 0x0b, 0x50, + 0x72, 0x6f, 0x62, 0x65, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x6c, + 0x69, 0x76, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x61, 0x6c, 0x69, 0x76, 0x65, + 0x12, 0x14, 0x0a, 0x05, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x05, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x12, 0x2a, 0x0a, 0x11, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x65, + 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0f, 0x6c, 0x61, 0x73, 0x74, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x65, 0x61, 0x73, + 0x6f, 0x6e, 0x22, 0x32, 0x0a, 0x09, 0x49, 0x6e, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x74, 0x79, 0x12, + 0x25, 0x0a, 0x0e, 0x70, 0x72, 0x6f, 0x62, 0x65, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, + 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0d, 0x70, 0x72, 0x6f, 0x62, 0x65, 0x49, 0x6e, + 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x22, 0x33, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x12, 0x29, 0x0a, 0x10, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x73, 0x65, 0x6c, 0x65, + 0x63, 0x74, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x75, 0x62, 0x6a, + 0x65, 0x63, 0x74, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x42, 0x6f, 0x0a, 0x1e, 0x63, + 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, + 0x70, 0x2e, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x6f, 0x72, 0x79, 0x50, 0x01, 0x5a, + 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x76, 0x32, 0x66, 0x6c, + 0x79, 0x2f, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x76, 0x34, 0x2f, + 0x61, 0x70, 0x70, 0x2f, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x6f, 0x72, 0x79, 0xaa, + 0x02, 0x1a, 0x56, 0x32, 0x52, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72, 0x65, 0x2e, 0x41, 0x70, 0x70, + 0x2e, 0x4f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x6f, 0x72, 0x79, 0x62, 0x06, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_app_observatory_config_proto_rawDescOnce sync.Once + file_app_observatory_config_proto_rawDescData = file_app_observatory_config_proto_rawDesc +) + +func file_app_observatory_config_proto_rawDescGZIP() []byte { + file_app_observatory_config_proto_rawDescOnce.Do(func() { + file_app_observatory_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_app_observatory_config_proto_rawDescData) + }) + return file_app_observatory_config_proto_rawDescData +} + +var file_app_observatory_config_proto_msgTypes = make([]protoimpl.MessageInfo, 5) +var file_app_observatory_config_proto_goTypes = []interface{}{ + (*ObservationResult)(nil), // 0: v2ray.core.app.observatory.ObservationResult + (*OutboundStatus)(nil), // 1: v2ray.core.app.observatory.OutboundStatus + (*ProbeResult)(nil), // 2: v2ray.core.app.observatory.ProbeResult + (*Intensity)(nil), // 3: v2ray.core.app.observatory.Intensity + (*Config)(nil), // 4: v2ray.core.app.observatory.Config +} +var file_app_observatory_config_proto_depIdxs = []int32{ + 1, // 0: v2ray.core.app.observatory.ObservationResult.status:type_name -> v2ray.core.app.observatory.OutboundStatus + 1, // [1:1] is the sub-list for method output_type + 1, // [1:1] is the sub-list for method input_type + 1, // [1:1] is the sub-list for extension type_name + 1, // [1:1] is the sub-list for extension extendee + 0, // [0:1] is the sub-list for field type_name +} + +func init() { file_app_observatory_config_proto_init() } +func file_app_observatory_config_proto_init() { + if File_app_observatory_config_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_app_observatory_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ObservationResult); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_app_observatory_config_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*OutboundStatus); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_app_observatory_config_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ProbeResult); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_app_observatory_config_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Intensity); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_app_observatory_config_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Config); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_app_observatory_config_proto_rawDesc, + NumEnums: 0, + NumMessages: 5, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_app_observatory_config_proto_goTypes, + DependencyIndexes: file_app_observatory_config_proto_depIdxs, + MessageInfos: file_app_observatory_config_proto_msgTypes, + }.Build() + File_app_observatory_config_proto = out.File + file_app_observatory_config_proto_rawDesc = nil + file_app_observatory_config_proto_goTypes = nil + file_app_observatory_config_proto_depIdxs = nil +} diff --git a/app/observatory/config.proto b/app/observatory/config.proto new file mode 100644 index 000000000..e37d00fd6 --- /dev/null +++ b/app/observatory/config.proto @@ -0,0 +1,67 @@ +syntax = "proto3"; + +package v2ray.core.app.observatory; +option csharp_namespace = "V2Ray.Core.App.Observatory"; +option go_package = "github.com/v2fly/v2ray-core/v4/app/observatory"; +option java_package = "com.v2ray.core.app.observatory"; +option java_multiple_files = true; + +message ObservationResult { + repeated OutboundStatus status = 1; +} + +message OutboundStatus{ + /* @Document Whether this outbound is usable + @Restriction ReadOnlyForUser + */ + bool alive = 1; + /* @Document The time for probe request to finish. + @Type time.ms + @Restriction ReadOnlyForUser + */ + int64 delay = 2; + /* @Document The last error caused this outbound failed to relay probe request + @Restriction NotMachineReadable + */ + string last_error_reason = 3; + /* @Document The outbound tag for this Server + @Type id.outboundTag + */ + string outbound_tag = 4; + /* @Document The time this outbound is known to be alive + @Type id.outboundTag +*/ + int64 last_seen_time = 5; + /* @Document The time this outbound is tried + @Type id.outboundTag +*/ + int64 last_try_time = 6; +} + +message ProbeResult{ + /* @Document Whether this outbound is usable + @Restriction ReadOnlyForUser + */ + bool alive = 1; + /* @Document The time for probe request to finish. + @Type time.ms + @Restriction ReadOnlyForUser + */ + int64 delay = 2; + /* @Document The error caused this outbound failed to relay probe request + @Restriction NotMachineReadable +*/ + string last_error_reason = 3; +} + +message Intensity{ + /* @Document The time interval for a probe request in ms. + @Type time.ms + */ + uint32 probe_interval = 1; +} +message Config { + /* @Document The selectors for outbound under observation + */ + repeated string subject_selector = 2; +} \ No newline at end of file diff --git a/app/observatory/errors.generated.go b/app/observatory/errors.generated.go new file mode 100644 index 000000000..05169e048 --- /dev/null +++ b/app/observatory/errors.generated.go @@ -0,0 +1,9 @@ +package observatory + +import "github.com/v2fly/v2ray-core/v4/common/errors" + +type errPathObjHolder struct{} + +func newError(values ...interface{}) *errors.Error { + return errors.New(values...).WithPathObj(errPathObjHolder{}) +} diff --git a/app/observatory/explainErrors.go b/app/observatory/explainErrors.go new file mode 100644 index 000000000..00aa5261d --- /dev/null +++ b/app/observatory/explainErrors.go @@ -0,0 +1,26 @@ +package observatory + +import "github.com/v2fly/v2ray-core/v4/common/errors" + +type errorCollector struct { + errors *errors.Error +} + +func (e *errorCollector) SubmitError(err error) { + if e.errors == nil { + e.errors = newError("underlying connection error").Base(err) + return + } + e.errors = e.errors.Base(newError("underlying connection error").Base(err)) +} + +func newErrorCollector() *errorCollector { + return &errorCollector{} +} + +func (e *errorCollector) UnderlyingError() error { + if e.errors == nil { + return newError("failed to produce report") + } + return e.errors +} diff --git a/app/observatory/observatory.go b/app/observatory/observatory.go new file mode 100644 index 000000000..937bcf96f --- /dev/null +++ b/app/observatory/observatory.go @@ -0,0 +1,3 @@ +package observatory + +//go:generate go run github.com/v2fly/v2ray-core/v4/common/errors/errorgen diff --git a/app/observatory/observer.go b/app/observatory/observer.go new file mode 100644 index 000000000..18e934da7 --- /dev/null +++ b/app/observatory/observer.go @@ -0,0 +1,203 @@ +// +build !confonly + +package observatory + +import ( + "context" + "net" + "net/http" + "net/url" + "sync" + "time" + + "github.com/golang/protobuf/proto" + + core "github.com/v2fly/v2ray-core/v4" + "github.com/v2fly/v2ray-core/v4/common" + v2net "github.com/v2fly/v2ray-core/v4/common/net" + "github.com/v2fly/v2ray-core/v4/common/session" + "github.com/v2fly/v2ray-core/v4/common/signal/done" + "github.com/v2fly/v2ray-core/v4/common/task" + "github.com/v2fly/v2ray-core/v4/features/extension" + "github.com/v2fly/v2ray-core/v4/features/outbound" + "github.com/v2fly/v2ray-core/v4/transport/internet/tagged" +) + +type Observer struct { + config *Config + ctx context.Context + + statusLock sync.Mutex + status []*OutboundStatus + + finished *done.Instance + + ohm outbound.Manager +} + +func (o *Observer) GetObservation(ctx context.Context) (proto.Message, error) { + return &ObservationResult{Status: o.status}, nil +} + +func (o *Observer) Type() interface{} { + return extension.ObservatoryType() +} + +func (o *Observer) Start() error { + o.finished = done.New() + go o.background() + return nil +} + +func (o *Observer) Close() error { + return o.finished.Close() +} + +func (o *Observer) background() { + for !o.finished.Done() { + hs, ok := o.ohm.(outbound.HandlerSelector) + if !ok { + newError("outbound.Manager is not a HandlerSelector").WriteToLog() + return + } + + outbounds := hs.Select(o.config.SubjectSelector) + + o.updateStatus(outbounds) + + for _, v := range outbounds { + result := o.probe(v) + o.updateStatusForResult(v, &result) + if o.finished.Done() { + return + } + time.Sleep(time.Second * 10) + } + + } +} + +func (o *Observer) updateStatus(outbounds []string) { + o.statusLock.Lock() + defer o.statusLock.Unlock() + //TODO should remove old inbound that is removed +} + +func (o *Observer) probe(outbound string) ProbeResult { + errorCollectorForRequest := newErrorCollector() + + httpTransport := http.Transport{ + Proxy: func(*http.Request) (*url.URL, error) { + return nil, nil + }, + DialContext: func(ctx context.Context, network string, addr string) (net.Conn, error) { + var connection net.Conn + taskErr := task.Run(ctx, func() error { + //MUST use V2Fly's built in context system + dest, err := v2net.ParseDestination(network + ":" + addr) + if err != nil { + return newError("cannot understand address").Base(err) + } + trackedCtx := session.TrackedConnectionError(o.ctx, errorCollectorForRequest) + conn, err := tagged.Dialer(trackedCtx, dest, outbound) + if err != nil { + return newError("cannot dial remote address", dest).Base(err) + } + connection = conn + return nil + }) + if taskErr != nil { + return nil, newError("cannot finish connection").Base(taskErr) + } + return connection, nil + }, + TLSHandshakeTimeout: time.Duration(time.Second * 5), + } + httpClient := &http.Client{ + Transport: &httpTransport, + CheckRedirect: func(req *http.Request, via []*http.Request) error { + return http.ErrUseLastResponse + }, + Jar: nil, + Timeout: time.Duration(time.Second * 5), + } + var GETTime time.Duration + err := task.Run(o.ctx, func() error { + startTime := time.Now() + response, err := httpClient.Get("https://api.v2fly.org/checkConnection.svgz") + if err != nil { + return newError("outbound failed to relay connection").Base(err) + } + if response.Body != nil { + response.Body.Close() + } + endTime := time.Now() + GETTime = endTime.Sub(startTime) + return nil + }) + if err != nil { + fullerr := newError("underlying connection failed").Base(errorCollectorForRequest.UnderlyingError()) + fullerr = newError("with outbound handler report").Base(fullerr) + fullerr = newError("GET request failed:", err).Base(fullerr) + fullerr = newError("the outbound ", outbound, "is dead:").Base(fullerr) + fullerr = fullerr.AtInfo() + fullerr.WriteToLog() + return ProbeResult{Alive: false, LastErrorReason: fullerr.Error()} + } + newError("the outbound ", outbound, "is alive:", GETTime.Seconds()).AtInfo().WriteToLog() + return ProbeResult{Alive: true, Delay: GETTime.Milliseconds()} +} + +func (o *Observer) updateStatusForResult(outbound string, result *ProbeResult) { + o.statusLock.Lock() + defer o.statusLock.Unlock() + var status *OutboundStatus + if location := o.findStatusLocationLockHolderOnly(outbound); location != -1 { + status = o.status[location] + } else { + status = &OutboundStatus{} + o.status = append(o.status, status) + } + + status.LastTryTime = time.Now().Unix() + status.OutboundTag = outbound + status.Alive = result.Alive + if result.Alive { + status.Delay = result.Delay + status.LastSeenTime = status.LastTryTime + status.LastErrorReason = "" + } else { + status.LastErrorReason = result.LastErrorReason + status.Delay = 99999999 + } +} + +func (o *Observer) findStatusLocationLockHolderOnly(outbound string) int { + for i, v := range o.status { + if v.OutboundTag == outbound { + return i + } + } + return -1 +} + +func New(ctx context.Context, config *Config) (*Observer, error) { + var outboundManager outbound.Manager + err := core.RequireFeatures(ctx, func(om outbound.Manager) { + outboundManager = om + }) + if err != nil { + return nil, newError("Cannot get depended features").Base(err) + } + return &Observer{ + config: config, + ctx: ctx, + ohm: outboundManager, + }, nil +} + +func init() { + common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) { + return New(ctx, config.(*Config)) + })) +} diff --git a/app/proxyman/outbound/handler.go b/app/proxyman/outbound/handler.go index 73c8b3a67..1776d858e 100644 --- a/app/proxyman/outbound/handler.go +++ b/app/proxyman/outbound/handler.go @@ -134,13 +134,17 @@ func (h *Handler) Tag() string { func (h *Handler) Dispatch(ctx context.Context, link *transport.Link) { if h.mux != nil && (h.mux.Enabled || session.MuxPreferedFromContext(ctx)) { if err := h.mux.Dispatch(ctx, link); err != nil { - newError("failed to process mux outbound traffic").Base(err).WriteToLog(session.ExportIDToError(ctx)) + err := newError("failed to process mux outbound traffic").Base(err) + session.SubmitOutboundErrorToOriginator(ctx, err) + err.WriteToLog(session.ExportIDToError(ctx)) common.Interrupt(link.Writer) } } else { if err := h.proxy.Process(ctx, link, h); err != nil { // Ensure outbound ray is properly closed. - newError("failed to process outbound traffic").Base(err).WriteToLog(session.ExportIDToError(ctx)) + err := newError("failed to process outbound traffic").Base(err) + session.SubmitOutboundErrorToOriginator(ctx, err) + err.WriteToLog(session.ExportIDToError(ctx)) common.Interrupt(link.Writer) } else { common.Must(common.Close(link.Writer)) diff --git a/app/router/balancing.go b/app/router/balancing.go index 53088b6de..6b313eb09 100644 --- a/app/router/balancing.go +++ b/app/router/balancing.go @@ -3,7 +3,10 @@ package router import ( + "context" + "github.com/v2fly/v2ray-core/v4/common/dice" + "github.com/v2fly/v2ray-core/v4/features/extension" "github.com/v2fly/v2ray-core/v4/features/outbound" ) @@ -44,3 +47,8 @@ func (b *Balancer) PickOutbound() (string, error) { } return tag, nil } +func (b *Balancer) InjectContext(ctx context.Context) { + if contextReceiver, ok := b.strategy.(extension.ContextReceiver); ok { + contextReceiver.InjectContext(ctx) + } +} diff --git a/app/router/command/command_test.go b/app/router/command/command_test.go index 3b4e584bb..9c6c0a8af 100644 --- a/app/router/command/command_test.go +++ b/app/router/command/command_test.go @@ -213,7 +213,7 @@ func TestSerivceTestRoute(t *testing.T) { r := new(router.Router) mockCtl := gomock.NewController(t) defer mockCtl.Finish() - common.Must(r.Init(&router.Config{ + common.Must(r.Init(context.TODO(), &router.Config{ Rule: []*router.RoutingRule{ { InboundTag: []string{"in"}, diff --git a/app/router/config.go b/app/router/config.go index 64213603f..e4fa2e7ce 100644 --- a/app/router/config.go +++ b/app/router/config.go @@ -160,9 +160,21 @@ func (rr *RoutingRule) BuildCondition() (Condition, error) { } func (br *BalancingRule) Build(ohm outbound.Manager) (*Balancer, error) { - return &Balancer{ - selectors: br.OutboundSelector, - strategy: &RandomStrategy{}, - ohm: ohm, - }, nil + switch br.Strategy { + case "leastPing": + return &Balancer{ + selectors: br.OutboundSelector, + strategy: &LeastPingStrategy{}, + ohm: ohm, + }, nil + case "random": + fallthrough + default: + return &Balancer{ + selectors: br.OutboundSelector, + strategy: &RandomStrategy{}, + ohm: ohm, + }, nil + + } } diff --git a/app/router/config.pb.go b/app/router/config.pb.go index 0c728c372..ccddf37d5 100644 --- a/app/router/config.pb.go +++ b/app/router/config.pb.go @@ -708,6 +708,7 @@ type BalancingRule struct { Tag string `protobuf:"bytes,1,opt,name=tag,proto3" json:"tag,omitempty"` OutboundSelector []string `protobuf:"bytes,2,rep,name=outbound_selector,json=outboundSelector,proto3" json:"outbound_selector,omitempty"` + Strategy string `protobuf:"bytes,3,opt,name=strategy,proto3" json:"strategy,omitempty"` } func (x *BalancingRule) Reset() { @@ -756,6 +757,13 @@ func (x *BalancingRule) GetOutboundSelector() []string { return nil } +func (x *BalancingRule) GetStrategy() string { + if x != nil { + return x.Strategy + } + return "" +} + type Config struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -1017,37 +1025,39 @@ var file_app_router_config_proto_rawDesc = []byte{ 0x12, 0x25, 0x0a, 0x0e, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x18, 0x11, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x42, 0x0c, 0x0a, 0x0a, 0x74, 0x61, 0x72, 0x67, 0x65, - 0x74, 0x5f, 0x74, 0x61, 0x67, 0x22, 0x4e, 0x0a, 0x0d, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x69, + 0x74, 0x5f, 0x74, 0x61, 0x67, 0x22, 0x6a, 0x0a, 0x0d, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x74, 0x61, 0x67, 0x12, 0x2b, 0x0a, 0x11, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x10, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x53, 0x65, 0x6c, - 0x65, 0x63, 0x74, 0x6f, 0x72, 0x22, 0xad, 0x02, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, - 0x12, 0x55, 0x0a, 0x0f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x73, 0x74, 0x72, 0x61, 0x74, - 0x65, 0x67, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2c, 0x2e, 0x76, 0x32, 0x72, 0x61, - 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, - 0x72, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53, - 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x52, 0x0e, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53, - 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x36, 0x0a, 0x04, 0x72, 0x75, 0x6c, 0x65, 0x18, - 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, - 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x52, 0x6f, - 0x75, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x04, 0x72, 0x75, 0x6c, 0x65, 0x12, - 0x4b, 0x0a, 0x0e, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x69, 0x6e, 0x67, 0x5f, 0x72, 0x75, 0x6c, - 0x65, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, - 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, - 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x0d, 0x62, - 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x22, 0x47, 0x0a, 0x0e, - 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x08, - 0x0a, 0x04, 0x41, 0x73, 0x49, 0x73, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x55, 0x73, 0x65, 0x49, - 0x70, 0x10, 0x01, 0x12, 0x10, 0x0a, 0x0c, 0x49, 0x70, 0x49, 0x66, 0x4e, 0x6f, 0x6e, 0x4d, 0x61, - 0x74, 0x63, 0x68, 0x10, 0x02, 0x12, 0x0e, 0x0a, 0x0a, 0x49, 0x70, 0x4f, 0x6e, 0x44, 0x65, 0x6d, - 0x61, 0x6e, 0x64, 0x10, 0x03, 0x42, 0x60, 0x0a, 0x19, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, - 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, - 0x65, 0x72, 0x50, 0x01, 0x5a, 0x29, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, - 0x2f, 0x76, 0x32, 0x66, 0x6c, 0x79, 0x2f, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, - 0x65, 0x2f, 0x76, 0x34, 0x2f, 0x61, 0x70, 0x70, 0x2f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0xaa, - 0x02, 0x15, 0x56, 0x32, 0x52, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72, 0x65, 0x2e, 0x41, 0x70, 0x70, - 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, + 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, + 0x79, 0x22, 0xad, 0x02, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x55, 0x0a, 0x0f, + 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2c, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, + 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x43, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, + 0x65, 0x67, 0x79, 0x52, 0x0e, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, + 0x65, 0x67, 0x79, 0x12, 0x36, 0x0a, 0x04, 0x72, 0x75, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x22, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, + 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, + 0x67, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x04, 0x72, 0x75, 0x6c, 0x65, 0x12, 0x4b, 0x0a, 0x0e, 0x62, + 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x69, 0x6e, 0x67, 0x5f, 0x72, 0x75, 0x6c, 0x65, 0x18, 0x03, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, + 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x42, 0x61, 0x6c, 0x61, + 0x6e, 0x63, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x0d, 0x62, 0x61, 0x6c, 0x61, 0x6e, + 0x63, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x22, 0x47, 0x0a, 0x0e, 0x44, 0x6f, 0x6d, 0x61, + 0x69, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x08, 0x0a, 0x04, 0x41, 0x73, + 0x49, 0x73, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x55, 0x73, 0x65, 0x49, 0x70, 0x10, 0x01, 0x12, + 0x10, 0x0a, 0x0c, 0x49, 0x70, 0x49, 0x66, 0x4e, 0x6f, 0x6e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x10, + 0x02, 0x12, 0x0e, 0x0a, 0x0a, 0x49, 0x70, 0x4f, 0x6e, 0x44, 0x65, 0x6d, 0x61, 0x6e, 0x64, 0x10, + 0x03, 0x42, 0x60, 0x0a, 0x19, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, + 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x50, 0x01, + 0x5a, 0x29, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x76, 0x32, 0x66, + 0x6c, 0x79, 0x2f, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x76, 0x34, + 0x2f, 0x61, 0x70, 0x70, 0x2f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0xaa, 0x02, 0x15, 0x56, 0x32, + 0x52, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72, 0x65, 0x2e, 0x41, 0x70, 0x70, 0x2e, 0x52, 0x6f, 0x75, + 0x74, 0x65, 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/app/router/config.proto b/app/router/config.proto index 1d80b2294..13cc19909 100644 --- a/app/router/config.proto +++ b/app/router/config.proto @@ -127,6 +127,7 @@ message RoutingRule { message BalancingRule { string tag = 1; repeated string outbound_selector = 2; + string strategy = 3; } message Config { diff --git a/app/router/router.go b/app/router/router.go index 45626585c..351f52333 100644 --- a/app/router/router.go +++ b/app/router/router.go @@ -6,7 +6,6 @@ package router import ( "context" - core "github.com/v2fly/v2ray-core/v4" "github.com/v2fly/v2ray-core/v4/common" "github.com/v2fly/v2ray-core/v4/features/dns" @@ -31,7 +30,7 @@ type Route struct { } // Init initializes the Router. -func (r *Router) Init(config *Config, d dns.Client, ohm outbound.Manager) error { +func (r *Router) Init(ctx context.Context, config *Config, d dns.Client, ohm outbound.Manager) error { r.domainStrategy = config.DomainStrategy r.dns = d @@ -41,6 +40,7 @@ func (r *Router) Init(config *Config, d dns.Client, ohm outbound.Manager) error if err != nil { return err } + balancer.InjectContext(ctx) r.balancers[rule.Tag] = balancer } @@ -142,7 +142,7 @@ func init() { common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) { r := new(Router) if err := core.RequireFeatures(ctx, func(d dns.Client, ohm outbound.Manager) error { - return r.Init(config.(*Config), d, ohm) + return r.Init(ctx, config.(*Config), d, ohm) }); err != nil { return nil, err } diff --git a/app/router/router_test.go b/app/router/router_test.go index e78f94b99..deb76f210 100644 --- a/app/router/router_test.go +++ b/app/router/router_test.go @@ -40,7 +40,7 @@ func TestSimpleRouter(t *testing.T) { mockHs := mocks.NewOutboundHandlerSelector(mockCtl) r := new(Router) - common.Must(r.Init(config, mockDNS, &mockOutboundManager{ + common.Must(r.Init(context.TODO(), config, mockDNS, &mockOutboundManager{ Manager: mockOhm, HandlerSelector: mockHs, })) @@ -81,7 +81,7 @@ func TestSimpleBalancer(t *testing.T) { mockHs.EXPECT().Select(gomock.Eq([]string{"test-"})).Return([]string{"test"}) r := new(Router) - common.Must(r.Init(config, mockDNS, &mockOutboundManager{ + common.Must(r.Init(context.TODO(), config, mockDNS, &mockOutboundManager{ Manager: mockOhm, HandlerSelector: mockHs, })) @@ -119,7 +119,7 @@ func TestIPOnDemand(t *testing.T) { mockDNS.EXPECT().LookupIP(gomock.Eq("v2fly.org")).Return([]net.IP{{192, 168, 0, 1}}, nil).AnyTimes() r := new(Router) - common.Must(r.Init(config, mockDNS, nil)) + common.Must(r.Init(context.TODO(), config, mockDNS, nil)) ctx := session.ContextWithOutbound(context.Background(), &session.Outbound{Target: net.TCPDestination(net.DomainAddress("v2fly.org"), 80)}) route, err := r.PickRoute(routing_session.AsRoutingContext(ctx)) @@ -154,7 +154,7 @@ func TestIPIfNonMatchDomain(t *testing.T) { mockDNS.EXPECT().LookupIP(gomock.Eq("v2fly.org")).Return([]net.IP{{192, 168, 0, 1}}, nil).AnyTimes() r := new(Router) - common.Must(r.Init(config, mockDNS, nil)) + common.Must(r.Init(context.TODO(), config, mockDNS, nil)) ctx := session.ContextWithOutbound(context.Background(), &session.Outbound{Target: net.TCPDestination(net.DomainAddress("v2fly.org"), 80)}) route, err := r.PickRoute(routing_session.AsRoutingContext(ctx)) @@ -188,7 +188,7 @@ func TestIPIfNonMatchIP(t *testing.T) { mockDNS := mocks.NewDNSClient(mockCtl) r := new(Router) - common.Must(r.Init(config, mockDNS, nil)) + common.Must(r.Init(context.TODO(), config, mockDNS, nil)) ctx := session.ContextWithOutbound(context.Background(), &session.Outbound{Target: net.TCPDestination(net.LocalHostIP, 80)}) route, err := r.PickRoute(routing_session.AsRoutingContext(ctx)) diff --git a/app/router/strategy_leastping.go b/app/router/strategy_leastping.go new file mode 100644 index 000000000..1c00f024f --- /dev/null +++ b/app/router/strategy_leastping.go @@ -0,0 +1,63 @@ +// +build !confonly + +package router + +import ( + "context" + + core "github.com/v2fly/v2ray-core/v4" + "github.com/v2fly/v2ray-core/v4/common" + + "github.com/v2fly/v2ray-core/v4/app/observatory" + "github.com/v2fly/v2ray-core/v4/features/extension" +) + +type LeastPingStrategy struct { + ctx context.Context + observatory extension.Observatory +} + +func (l *LeastPingStrategy) InjectContext(ctx context.Context) { + l.ctx = ctx +} + +func (l *LeastPingStrategy) PickOutbound(strings []string) string { + if l.observatory == nil { + common.Must(core.RequireFeatures(l.ctx, func(observatory extension.Observatory) error { + l.observatory = observatory + return nil + })) + } + + observeReport, err := l.observatory.GetObservation(l.ctx) + if err != nil { + newError("cannot get observe report").Base(err).WriteToLog() + return "" + } + outboundsList := outboundList(strings) + if result, ok := observeReport.(*observatory.ObservationResult); ok { + status := result.Status + leastPing := int64(99999999) + selectedOutboundName := "" + for _, v := range status { + if outboundsList.contains(v.OutboundTag) && v.Alive && v.Delay < leastPing { + selectedOutboundName = v.OutboundTag + } + } + return selectedOutboundName + } + + //No way to understand observeReport + return "" +} + +type outboundList []string + +func (o outboundList) contains(name string) bool { + for _, v := range o { + if v == name { + return true + } + } + return false +} diff --git a/common/session/context.go b/common/session/context.go index 4300b1f6c..5dab24d6e 100644 --- a/common/session/context.go +++ b/common/session/context.go @@ -13,6 +13,7 @@ const ( contentSessionKey muxPreferedSessionKey sockoptSessionKey + trackedConnectionErrorKey ) // ContextWithID returns a new context with the given ID. @@ -116,3 +117,18 @@ func SetForcedOutboundTagToContext(ctx context.Context, tag string) context.Cont ContentFromContext(ctx).SetAttribute("forcedOutboundTag", tag) return ctx } + +type TrackedRequestErrorFeedback interface { + SubmitError(err error) +} + +func SubmitOutboundErrorToOriginator(ctx context.Context, err error) { + if errorTracker := ctx.Value(trackedConnectionErrorKey); errorTracker != nil { + errorTracker := errorTracker.(TrackedRequestErrorFeedback) + errorTracker.SubmitError(err) + } +} + +func TrackedConnectionError(ctx context.Context, tracker TrackedRequestErrorFeedback) context.Context { + return context.WithValue(ctx, trackedConnectionErrorKey, tracker) +} diff --git a/features/extension/contextreceiver.go b/features/extension/contextreceiver.go new file mode 100644 index 000000000..2d3394796 --- /dev/null +++ b/features/extension/contextreceiver.go @@ -0,0 +1,7 @@ +package extension + +import "context" + +type ContextReceiver interface { + InjectContext(ctx context.Context) +} diff --git a/features/extension/observatory.go b/features/extension/observatory.go new file mode 100644 index 000000000..3c0d1a308 --- /dev/null +++ b/features/extension/observatory.go @@ -0,0 +1,19 @@ +package extension + +import ( + "context" + + "github.com/golang/protobuf/proto" + + "github.com/v2fly/v2ray-core/v4/features" +) + +type Observatory interface { + features.Feature + + GetObservation(ctx context.Context) (proto.Message, error) +} + +func ObservatoryType() interface{} { + return (*Observatory)(nil) +} diff --git a/go.mod b/go.mod index 6efb204c4..8efe9201e 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/golang/protobuf v1.5.2 github.com/google/go-cmp v0.5.5 github.com/gorilla/websocket v1.4.2 + github.com/jhump/protoreflect v1.8.2 // indirect github.com/lucas-clemente/quic-go v0.20.1 github.com/miekg/dns v1.1.41 github.com/pires/go-proxyproto v0.5.0 diff --git a/go.sum b/go.sum index cde4bf871..ad14004eb 100644 --- a/go.sum +++ b/go.sum @@ -75,12 +75,14 @@ github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+u github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20210202160940-bed99a852dfe/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/websocket v0.0.0-20191103002815-9a42957e2b3a/go.mod h1:jd+zY81Fx2lC4bfw58+Rflg1srqmedQjbBUejKOjYNY= +github.com/gordonklaus/ineffassign v0.0.0-20200309095847-7953dde2c7bf/go.mod h1:cuNKsD1zp2v6XfE/orVX2QE1LC+i254ceGcVeDT3pTU= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= @@ -89,6 +91,8 @@ github.com/h12w/go-socks5 v0.0.0-20200522160539-76189e178364 h1:5XxdakFhqd9dnXoA github.com/h12w/go-socks5 v0.0.0-20200522160539-76189e178364/go.mod h1:eDJQioIyy4Yn3MVivT7rv/39gAJTrA7lgmYr8EW950c= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= +github.com/jhump/protoreflect v1.8.2 h1:k2xE7wcUomeqwY0LDCYA16y4WWfyTcMx5mKhk0d4ua0= +github.com/jhump/protoreflect v1.8.2/go.mod h1:7GcYQDdMU/O/BBrl/cX6PNHpXh6cenjd8pneu5yW7Tg= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= @@ -117,6 +121,7 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= +github.com/nishanths/predeclared v0.0.0-20200524104333-86fad755b4d3/go.mod h1:nt3d53pc1VYcphSCIaYAJtnPYnr3Zyn8fMq2wvPGPso= github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -141,6 +146,7 @@ github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7q github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg= github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/seiflotfy/cuckoofilter v0.0.0-20201222105146-bc6005554a0c h1:pqy40B3MQWYrza7YZXOXgl0Nf0QGFqrOC0BKae1UNAA= github.com/seiflotfy/cuckoofilter v0.0.0-20201222105146-bc6005554a0c/go.mod h1:bR6DqgcAl1zTcOX8/pE2Qkj9XO00eCNqmKb7lXP8EAg= @@ -186,6 +192,8 @@ github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49u github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM= github.com/xtaci/smux v1.5.15 h1:6hMiXswcleXj5oNfcJc+DXS8Vj36XX2LaX98udog6Kc= github.com/xtaci/smux v1.5.15/go.mod h1:OMlQbT5vcgl2gb49mFkYo6SMf+zP3rcjcwQz7ZU7IGY= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= go.starlark.net v0.0.0-20210312235212-74c10e2c17dc h1:pVkptfeOTFfx+zXZo7HEHN3d5LmhatBFvHdm/f2QnpY= go.starlark.net v0.0.0-20210312235212-74c10e2c17dc/go.mod h1:t3mmBBPzAVvK0L0n1drDmrQsJ8FoIx4INCqVMTr/Zo0= @@ -194,6 +202,7 @@ golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+ golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -204,6 +213,8 @@ golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTk golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -216,7 +227,9 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210331212208-0fccb6fa2b5c h1:KHUzaHIpjWVlVVNh65G3hhuj3KB1HnjY6Cq5cTvRQT8= @@ -232,6 +245,7 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -270,6 +284,9 @@ golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3 golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200522201501-cb1345f3a375/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200717024301-6ddee64345a6/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -308,12 +325,15 @@ google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2 google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.25.1-0.20200805231151-a709e31e5d12 h1:OwhZOOMuf7leLaSCuxtQ9FW7ui2L2L6UKOtKAUqovUQ= +google.golang.org/protobuf v1.25.1-0.20200805231151-a709e31e5d12/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= @@ -333,5 +353,6 @@ honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck= sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0= diff --git a/infra/conf/api.go b/infra/conf/api.go index 58014cc79..0749e3db3 100644 --- a/infra/conf/api.go +++ b/infra/conf/api.go @@ -3,8 +3,12 @@ package conf import ( "strings" + "github.com/jhump/protoreflect/desc" + "github.com/jhump/protoreflect/dynamic" + "github.com/v2fly/v2ray-core/v4/app/commander" loggerservice "github.com/v2fly/v2ray-core/v4/app/log/command" + observatoryservice "github.com/v2fly/v2ray-core/v4/app/observatory/command" handlerservice "github.com/v2fly/v2ray-core/v4/app/proxyman/command" statsservice "github.com/v2fly/v2ray-core/v4/app/stats/command" "github.com/v2fly/v2ray-core/v4/common/serial" @@ -31,6 +35,18 @@ func (c *APIConfig) Build() (*commander.Config, error) { services = append(services, serial.ToTypedMessage(&loggerservice.Config{})) case "statsservice": services = append(services, serial.ToTypedMessage(&statsservice.Config{})) + case "observatoryservice": + services = append(services, serial.ToTypedMessage(&observatoryservice.Config{})) + default: + if !strings.HasPrefix(s, "#") { + continue + } + message, err := desc.LoadMessageDescriptor(s[1:]) + if err != nil || message == nil { + return nil, newError("Cannot find API", s, "").Base(err) + } + serviceConfig := dynamic.NewMessage(message) + services = append(services, serial.ToTypedMessage(serviceConfig)) } } diff --git a/infra/conf/observatory.go b/infra/conf/observatory.go new file mode 100644 index 000000000..fd8038e7e --- /dev/null +++ b/infra/conf/observatory.go @@ -0,0 +1,14 @@ +package conf + +import ( + "github.com/golang/protobuf/proto" + "github.com/v2fly/v2ray-core/v4/app/observatory" +) + +type ObservatoryConfig struct { + SubjectSelector []string `json:"subjectSelector"` +} + +func (o ObservatoryConfig) Build() (proto.Message, error) { + return &observatory.Config{SubjectSelector: o.SubjectSelector}, nil +} diff --git a/infra/conf/router.go b/infra/conf/router.go index 5184702da..9cc9d5281 100644 --- a/infra/conf/router.go +++ b/infra/conf/router.go @@ -17,9 +17,16 @@ type RouterRulesConfig struct { DomainStrategy string `json:"domainStrategy"` } +// StrategyConfig represents a strategy config +type StrategyConfig struct { + Type string `json:"type"` + Settings *json.RawMessage `json:"settings"` +} + type BalancingRule struct { - Tag string `json:"tag"` - Selectors StringList `json:"selector"` + Tag string `json:"tag"` + Selectors StringList `json:"selector"` + Strategy StrategyConfig `json:"strategy"` } func (r *BalancingRule) Build() (*router.BalancingRule, error) { @@ -30,9 +37,20 @@ func (r *BalancingRule) Build() (*router.BalancingRule, error) { return nil, newError("empty selector list") } + var strategy string + switch strings.ToLower(r.Strategy.Type) { + case strategyRandom, "": + strategy = strategyRandom + case strategyLeastPing: + strategy = "leastPing" + default: + return nil, newError("unknown balancing strategy: " + r.Strategy.Type) + } + return &router.BalancingRule{ Tag: r.Tag, OutboundSelector: []string(r.Selectors), + Strategy: strategy, }, nil } diff --git a/infra/conf/router_strategy.go b/infra/conf/router_strategy.go new file mode 100644 index 000000000..b8536330c --- /dev/null +++ b/infra/conf/router_strategy.go @@ -0,0 +1,6 @@ +package conf + +const ( + strategyRandom string = "random" + strategyLeastPing string = "leastping" +) diff --git a/infra/conf/router_test.go b/infra/conf/router_test.go index b594f1ca5..932d8e8a1 100644 --- a/infra/conf/router_test.go +++ b/infra/conf/router_test.go @@ -128,6 +128,7 @@ func TestRouterConfig(t *testing.T) { { Tag: "b1", OutboundSelector: []string{"test"}, + Strategy: "random", }, }, Rule: []*router.RoutingRule{ diff --git a/infra/conf/services.go b/infra/conf/services.go new file mode 100644 index 000000000..5c0451eeb --- /dev/null +++ b/infra/conf/services.go @@ -0,0 +1,30 @@ +package conf + +import ( + "encoding/json" + + "github.com/golang/protobuf/jsonpb" + "github.com/jhump/protoreflect/desc" + "github.com/jhump/protoreflect/dynamic" + + "github.com/v2fly/v2ray-core/v4/common/serial" +) + +func (c *Config) BuildServices(service map[string]*json.RawMessage) ([]*serial.TypedMessage, error) { + var ret []*serial.TypedMessage + for k, v := range service { + message, err := desc.LoadMessageDescriptor(k) + if err != nil || message == nil { + return nil, newError("Cannot find service", k, "").Base(err) + } + + serviceConfig := dynamic.NewMessage(message) + + if err := serviceConfig.UnmarshalJSONPB(&jsonpb.Unmarshaler{AllowUnknownFields: false}, *v); err != nil { + return nil, newError("Cannot interpret service configure file", k, "").Base(err) + } + + ret = append(ret, serial.ToTypedMessage(serviceConfig)) + } + return ret, nil +} diff --git a/infra/conf/v2ray.go b/infra/conf/v2ray.go index 9a6f2e0c7..ea7e16012 100644 --- a/infra/conf/v2ray.go +++ b/infra/conf/v2ray.go @@ -353,6 +353,9 @@ type Config struct { Reverse *ReverseConfig `json:"reverse"` FakeDNS *FakeDNSConfig `json:"fakeDns"` BrowserForwarder *BrowserForwarderConfig `json:"browserForwarder"` + Observatory *ObservatoryConfig `json:"observatory"` + + Services map[string]*json.RawMessage `json:"services"` } func (c *Config) findInboundTag(tag string) int { @@ -567,6 +570,26 @@ func (c *Config) Build() (*core.Config, error) { config.App = append(config.App, serial.ToTypedMessage(r)) } + if c.Observatory != nil { + r, err := c.Observatory.Build() + if err != nil { + return nil, err + } + config.App = append(config.App, serial.ToTypedMessage(r)) + } + + //Load Additional Services that do not have a json translator + + if msg, err := c.BuildServices(c.Services); err != nil { + developererr := newError("Loading a V2Ray Features as a service is intended for developers only. " + + "This is used for developers to prototype new features or for an advanced client to use special features in V2Ray," + + " instead of allowing end user to enable it without special tool and knowledge.") + sb := strings.Builder{} + return nil, newError("Cannot load service").Base(developererr).Base(err).Base(newError(sb.String())) + } else { + config.App = append(config.App, msg...) + } + var inbounds []InboundDetourConfig if c.InboundConfig != nil { diff --git a/main/distro/all/all.go b/main/distro/all/all.go index 8e1528404..f2462915c 100644 --- a/main/distro/all/all.go +++ b/main/distro/all/all.go @@ -14,6 +14,9 @@ import ( _ "github.com/v2fly/v2ray-core/v4/app/proxyman/command" _ "github.com/v2fly/v2ray-core/v4/app/stats/command" + // Developer preview services + _ "github.com/v2fly/v2ray-core/v4/app/observatory/command" + // Other optional features. _ "github.com/v2fly/v2ray-core/v4/app/dns" _ "github.com/v2fly/v2ray-core/v4/app/dns/fakedns" @@ -26,6 +29,9 @@ import ( // Fix dependency cycle caused by core import in internet package _ "github.com/v2fly/v2ray-core/v4/transport/internet/tagged/taggedimpl" + // Developer preview features + _ "github.com/v2fly/v2ray-core/v4/app/observatory" + // Inbound and outbound proxies. _ "github.com/v2fly/v2ray-core/v4/proxy/blackhole" _ "github.com/v2fly/v2ray-core/v4/proxy/dns"