From 1b44faed17c9865843725c852406aa09b4614cf1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 26 Sep 2022 19:37:06 +0800 Subject: [PATCH] Add v2ray stats api --- adapter/experimental.go | 10 + adapter/router.go | 5 +- box.go | 31 +- constant/err.go | 2 + experimental/clashapi.go | 20 +- experimental/clashapi/server.go | 7 + experimental/clashapi_stub.go | 14 - experimental/trackerconn/conn.go | 4 + experimental/trackerconn/packet_conn.go | 4 + experimental/v2rayapi.go | 24 + experimental/v2rayapi/server.go | 75 +++ experimental/v2rayapi/stats.go | 193 +++++++ experimental/v2rayapi/stats.pb.go | 678 ++++++++++++++++++++++++ experimental/v2rayapi/stats.proto | 53 ++ experimental/v2rayapi/stats_grpc.pb.go | 173 ++++++ inbound/hysteria_stub.go | 4 +- inbound/naive_quic_stub.go | 4 +- include/clashapi.go | 5 + include/clashapi_stub.go | 17 + include/quic.go | 5 +- include/quic_stub.go | 18 +- include/v2rayapi.go | 5 + include/v2rayapi_stub.go | 17 + option/experimental.go | 1 + option/v2ray.go | 13 + outbound/hysteria_stub.go | 4 +- route/router.go | 25 +- transport/v2ray/quic.go | 24 +- transport/v2ray/quic_stub.go | 23 - transport/v2ray/transport.go | 5 + transport/v2rayquic/init.go | 7 + transport/v2rayquic/server.go | 2 +- 32 files changed, 1408 insertions(+), 64 deletions(-) delete mode 100644 experimental/clashapi_stub.go create mode 100644 experimental/v2rayapi.go create mode 100644 experimental/v2rayapi/server.go create mode 100644 experimental/v2rayapi/stats.go create mode 100644 experimental/v2rayapi/stats.pb.go create mode 100644 experimental/v2rayapi/stats.proto create mode 100644 experimental/v2rayapi/stats_grpc.pb.go create mode 100644 include/clashapi.go create mode 100644 include/clashapi_stub.go create mode 100644 include/v2rayapi.go create mode 100644 include/v2rayapi_stub.go create mode 100644 option/v2ray.go delete mode 100644 transport/v2ray/quic_stub.go create mode 100644 transport/v2rayquic/init.go diff --git a/adapter/experimental.go b/adapter/experimental.go index 4abee333..bd7ee01b 100644 --- a/adapter/experimental.go +++ b/adapter/experimental.go @@ -38,3 +38,13 @@ func OutboundTag(detour Outbound) string { } return detour.Tag() } + +type V2RayServer interface { + Service + StatsService() V2RayStatsService +} + +type V2RayStatsService interface { + RoutedConnection(inbound string, outbound string, conn net.Conn) net.Conn + RoutedPacketConnection(inbound string, outbound string, conn N.PacketConn) N.PacketConn +} diff --git a/adapter/router.go b/adapter/router.go index 0af0a452..6bf30589 100644 --- a/adapter/router.go +++ b/adapter/router.go @@ -41,7 +41,10 @@ type Router interface { Rules() []Rule ClashServer() ClashServer - SetClashServer(controller ClashServer) + SetClashServer(server ClashServer) + + V2RayServer() V2RayServer + SetV2RayServer(server V2RayServer) } type Rule interface { diff --git a/box.go b/box.go index fe39b679..89bd30cd 100644 --- a/box.go +++ b/box.go @@ -31,6 +31,7 @@ type Box struct { logger log.ContextLogger logFile *os.File clashServer adapter.ClashServer + v2rayServer adapter.V2RayServer done chan struct{} } @@ -39,8 +40,14 @@ func New(ctx context.Context, options option.Options) (*Box, error) { logOptions := common.PtrValueOrDefault(options.Log) var needClashAPI bool - if options.Experimental != nil && options.Experimental.ClashAPI != nil && options.Experimental.ClashAPI.ExternalController != "" { - needClashAPI = true + var needV2RayAPI bool + if options.Experimental != nil { + if options.Experimental.ClashAPI != nil && options.Experimental.ClashAPI.ExternalController != "" { + needClashAPI = true + } + if options.Experimental.V2RayAPI != nil && options.Experimental.V2RayAPI.Listen != "" { + needV2RayAPI = true + } } var logFactory log.Factory @@ -149,6 +156,7 @@ func New(ctx context.Context, options option.Options) (*Box, error) { } var clashServer adapter.ClashServer + var v2rayServer adapter.V2RayServer if needClashAPI { clashServer, err = experimental.NewClashServer(router, observableLogFactory, common.PtrValueOrDefault(options.Experimental.ClashAPI)) if err != nil { @@ -156,6 +164,13 @@ func New(ctx context.Context, options option.Options) (*Box, error) { } router.SetClashServer(clashServer) } + if needV2RayAPI { + v2rayServer, err = experimental.NewV2RayServer(logFactory.NewLogger("v2ray-api"), common.PtrValueOrDefault(options.Experimental.V2RayAPI)) + if err != nil { + return nil, E.Cause(err, "create v2ray api server") + } + router.SetV2RayServer(v2rayServer) + } return &Box{ router: router, inbounds: inbounds, @@ -165,6 +180,7 @@ func New(ctx context.Context, options option.Options) (*Box, error) { logger: logFactory.NewLogger(""), logFile: logFile, clashServer: clashServer, + v2rayServer: v2rayServer, done: make(chan struct{}), }, nil } @@ -223,6 +239,12 @@ func (s *Box) start() error { return E.Cause(err, "start clash api server") } } + if s.v2rayServer != nil { + err = s.v2rayServer.Start() + if err != nil { + return E.Cause(err, "start v2ray api server") + } + } s.logger.Info("sing-box started (", F.Seconds(time.Since(s.createdAt).Seconds()), "s)") return nil } @@ -244,6 +266,11 @@ func (s *Box) Close() error { s.router, s.logFactory, s.clashServer, + s.v2rayServer, common.PtrOrNil(s.logFile), ) } + +func (s *Box) Router() adapter.Router { + return s.router +} diff --git a/constant/err.go b/constant/err.go index 881e155a..30845123 100644 --- a/constant/err.go +++ b/constant/err.go @@ -3,3 +3,5 @@ package constant import E "github.com/sagernet/sing/common/exceptions" var ErrTLSRequired = E.New("TLS required") + +var ErrQUICNotIncluded = E.New(`QUIC is not included in this build, rebuild with -tags with_quic`) diff --git a/experimental/clashapi.go b/experimental/clashapi.go index e29f518e..e71f04be 100644 --- a/experimental/clashapi.go +++ b/experimental/clashapi.go @@ -1,14 +1,24 @@ -//go:build with_clash_api - package experimental import ( + "os" + "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/experimental/clashapi" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" ) -func NewClashServer(router adapter.Router, logFactory log.ObservableFactory, options option.ClashAPIOptions) (adapter.ClashServer, error) { - return clashapi.NewServer(router, logFactory, options) +type ClashServerConstructor = func(router adapter.Router, logFactory log.ObservableFactory, options option.ClashAPIOptions) (adapter.ClashServer, error) + +var clashServerConstructor ClashServerConstructor + +func RegisterClashServerConstructor(constructor ClashServerConstructor) { + clashServerConstructor = constructor +} + +func NewClashServer(router adapter.Router, logFactory log.ObservableFactory, options option.ClashAPIOptions) (adapter.ClashServer, error) { + if clashServerConstructor == nil { + return nil, os.ErrInvalid + } + return clashServerConstructor(router, logFactory, options) } diff --git a/experimental/clashapi/server.go b/experimental/clashapi/server.go index 2d330919..d189ec6f 100644 --- a/experimental/clashapi/server.go +++ b/experimental/clashapi/server.go @@ -14,6 +14,7 @@ import ( "github.com/sagernet/sing-box/common/json" "github.com/sagernet/sing-box/common/urltest" C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/experimental" "github.com/sagernet/sing-box/experimental/clashapi/cachefile" "github.com/sagernet/sing-box/experimental/clashapi/trafficontrol" "github.com/sagernet/sing-box/log" @@ -29,6 +30,12 @@ import ( "github.com/go-chi/render" ) +func init() { + experimental.RegisterClashServerConstructor(func(router adapter.Router, logFactory log.ObservableFactory, options option.ClashAPIOptions) (adapter.ClashServer, error) { + return NewServer(router, logFactory, options) + }) +} + var _ adapter.ClashServer = (*Server)(nil) type Server struct { diff --git a/experimental/clashapi_stub.go b/experimental/clashapi_stub.go deleted file mode 100644 index 50fb89f0..00000000 --- a/experimental/clashapi_stub.go +++ /dev/null @@ -1,14 +0,0 @@ -//go:build !with_clash_api - -package experimental - -import ( - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing-box/option" - E "github.com/sagernet/sing/common/exceptions" -) - -func NewClashServer(router adapter.Router, logFactory log.ObservableFactory, options option.ClashAPIOptions) (adapter.ClashServer, error) { - return nil, E.New(`clash api is not included in this build, rebuild with -tags with_clash_api`) -} diff --git a/experimental/trackerconn/conn.go b/experimental/trackerconn/conn.go index 56c029ca..dd54b6e7 100644 --- a/experimental/trackerconn/conn.go +++ b/experimental/trackerconn/conn.go @@ -57,6 +57,10 @@ func (c *Conn) WriteBuffer(buffer *buf.Buffer) error { return nil } +func (c *Conn) Upstream() any { + return c.ExtendedConn +} + type DirectConn Conn func (c *DirectConn) WriteTo(w io.Writer) (n int64, err error) { diff --git a/experimental/trackerconn/packet_conn.go b/experimental/trackerconn/packet_conn.go index 5d9e4164..e1da2413 100644 --- a/experimental/trackerconn/packet_conn.go +++ b/experimental/trackerconn/packet_conn.go @@ -35,3 +35,7 @@ func (c *PacketConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) er c.writeCounter.Add(dataLen) return nil } + +func (c *PacketConn) Upstream() any { + return c.PacketConn +} diff --git a/experimental/v2rayapi.go b/experimental/v2rayapi.go new file mode 100644 index 00000000..cf479631 --- /dev/null +++ b/experimental/v2rayapi.go @@ -0,0 +1,24 @@ +package experimental + +import ( + "os" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" +) + +type V2RayServerConstructor = func(logger log.Logger, options option.V2RayAPIOptions) (adapter.V2RayServer, error) + +var v2rayServerConstructor V2RayServerConstructor + +func RegisterV2RayServerConstructor(constructor V2RayServerConstructor) { + v2rayServerConstructor = constructor +} + +func NewV2RayServer(logger log.Logger, options option.V2RayAPIOptions) (adapter.V2RayServer, error) { + if v2rayServerConstructor == nil { + return nil, os.ErrInvalid + } + return v2rayServerConstructor(logger, options) +} diff --git a/experimental/v2rayapi/server.go b/experimental/v2rayapi/server.go new file mode 100644 index 00000000..8b4b4385 --- /dev/null +++ b/experimental/v2rayapi/server.go @@ -0,0 +1,75 @@ +package v2rayapi + +import ( + "errors" + "net" + "net/http" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/experimental" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" + + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" +) + +func init() { + experimental.RegisterV2RayServerConstructor(NewServer) +} + +var _ adapter.V2RayServer = (*Server)(nil) + +type Server struct { + logger log.Logger + listen string + tcpListener net.Listener + grpcServer *grpc.Server + statsService *StatsService +} + +func NewServer(logger log.Logger, options option.V2RayAPIOptions) (adapter.V2RayServer, error) { + grpcServer := grpc.NewServer(grpc.Creds(insecure.NewCredentials())) + statsService := NewStatsService(common.PtrValueOrDefault(options.Stats)) + if statsService != nil { + RegisterStatsServiceServer(grpcServer, statsService) + } + server := &Server{ + logger: logger, + listen: options.Listen, + grpcServer: grpcServer, + statsService: statsService, + } + return server, nil +} + +func (s *Server) Start() error { + listener, err := net.Listen("tcp", s.listen) + if err != nil { + return err + } + s.logger.Info("grpc server started at ", listener.Addr()) + s.tcpListener = listener + go func() { + err = s.grpcServer.Serve(listener) + if err != nil && !errors.Is(err, http.ErrServerClosed) { + s.logger.Error(err) + } + }() + return nil +} + +func (s *Server) Close() error { + if s.grpcServer != nil { + s.grpcServer.Stop() + } + return common.Close( + common.PtrOrNil(s.grpcServer), + s.tcpListener, + ) +} + +func (s *Server) StatsService() adapter.V2RayStatsService { + return s.statsService +} diff --git a/experimental/v2rayapi/stats.go b/experimental/v2rayapi/stats.go new file mode 100644 index 00000000..286b0b79 --- /dev/null +++ b/experimental/v2rayapi/stats.go @@ -0,0 +1,193 @@ +package v2rayapi + +import ( + "context" + "net" + "regexp" + "runtime" + "strings" + "sync" + "time" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/experimental/trackerconn" + "github.com/sagernet/sing-box/option" + E "github.com/sagernet/sing/common/exceptions" + N "github.com/sagernet/sing/common/network" + + "go.uber.org/atomic" +) + +func init() { + StatsService_ServiceDesc.ServiceName = "v2ray.core.app.stats.command.StatsService" +} + +var ( + _ adapter.V2RayStatsService = (*StatsService)(nil) + _ StatsServiceServer = (*StatsService)(nil) +) + +type StatsService struct { + createdAt time.Time + directIO bool + inbounds map[string]bool + outbounds map[string]bool + access sync.Mutex + counters map[string]*atomic.Int64 +} + +func NewStatsService(options option.V2RayStatsServiceOptions) *StatsService { + if !options.Enabled { + return nil + } + inbounds := make(map[string]bool) + outbounds := make(map[string]bool) + for _, inbound := range options.Inbounds { + inbounds[inbound] = true + } + for _, outbound := range options.Outbounds { + outbounds[outbound] = true + } + return &StatsService{ + createdAt: time.Now(), + directIO: options.DirectIO, + inbounds: inbounds, + outbounds: outbounds, + counters: make(map[string]*atomic.Int64), + } +} + +func (s *StatsService) RoutedConnection(inbound string, outbound string, conn net.Conn) net.Conn { + var readCounter *atomic.Int64 + var writeCounter *atomic.Int64 + countInbound := inbound != "" && s.inbounds[inbound] + countOutbound := outbound != "" && s.outbounds[outbound] + if !countInbound && !countOutbound { + return conn + } + s.access.Lock() + if countInbound { + readCounter = s.loadOrCreateCounter("inbound>>>"+inbound+">>>traffic>>>uplink", readCounter) + writeCounter = s.loadOrCreateCounter("inbound>>>"+inbound+">>>traffic>>>downlink", writeCounter) + } + if countOutbound { + readCounter = s.loadOrCreateCounter("outbound>>>"+outbound+">>>traffic>>>uplink", readCounter) + writeCounter = s.loadOrCreateCounter("outbound>>>"+outbound+">>>traffic>>>downlink", writeCounter) + } + s.access.Unlock() + return trackerconn.New(conn, readCounter, writeCounter, s.directIO) +} + +func (s *StatsService) RoutedPacketConnection(inbound string, outbound string, conn N.PacketConn) N.PacketConn { + var readCounter *atomic.Int64 + var writeCounter *atomic.Int64 + countInbound := inbound != "" && s.inbounds[inbound] + countOutbound := outbound != "" && s.outbounds[outbound] + if !countInbound && !countOutbound { + return conn + } + s.access.Lock() + if countInbound { + readCounter = s.loadOrCreateCounter("inbound>>>"+inbound+">>>traffic>>>uplink", readCounter) + writeCounter = s.loadOrCreateCounter("inbound>>>"+inbound+">>>traffic>>>downlink", writeCounter) + } + if countOutbound { + readCounter = s.loadOrCreateCounter("outbound>>>"+outbound+">>>traffic>>>uplink", readCounter) + writeCounter = s.loadOrCreateCounter("outbound>>>"+outbound+">>>traffic>>>downlink", writeCounter) + } + s.access.Unlock() + return trackerconn.NewPacket(conn, readCounter, writeCounter) +} + +func (s *StatsService) GetStats(ctx context.Context, request *GetStatsRequest) (*GetStatsResponse, error) { + s.access.Lock() + counter, loaded := s.counters[request.Name] + s.access.Unlock() + if !loaded { + return nil, E.New(request.Name, " not found.") + } + var value int64 + if request.Reset_ { + value = counter.Swap(0) + } else { + value = counter.Load() + } + return &GetStatsResponse{Stat: &Stat{Name: request.Name, Value: value}}, nil +} + +func (s *StatsService) QueryStats(ctx context.Context, request *QueryStatsRequest) (*QueryStatsResponse, error) { + var response QueryStatsResponse + s.access.Lock() + defer s.access.Unlock() + if request.Regexp { + matchers := make([]*regexp.Regexp, 0, len(request.Patterns)) + for _, pattern := range request.Patterns { + matcher, err := regexp.Compile(pattern) + if err != nil { + return nil, err + } + matchers = append(matchers, matcher) + } + for name, counter := range s.counters { + for _, matcher := range matchers { + if matcher.MatchString(name) { + var value int64 + if request.Reset_ { + value = counter.Swap(0) + } else { + value = counter.Load() + } + response.Stat = append(response.Stat, &Stat{Name: name, Value: value}) + } + } + } + } else { + for name, counter := range s.counters { + for _, matcher := range request.Patterns { + if strings.Contains(name, matcher) { + var value int64 + if request.Reset_ { + value = counter.Swap(0) + } else { + value = counter.Load() + } + response.Stat = append(response.Stat, &Stat{Name: name, Value: value}) + } + } + } + } + return &response, nil +} + +func (s *StatsService) GetSysStats(ctx context.Context, request *SysStatsRequest) (*SysStatsResponse, error) { + var rtm runtime.MemStats + runtime.ReadMemStats(&rtm) + response := &SysStatsResponse{ + Uptime: uint32(time.Now().Sub(s.createdAt).Seconds()), + NumGoroutine: uint32(runtime.NumGoroutine()), + Alloc: rtm.Alloc, + TotalAlloc: rtm.TotalAlloc, + Sys: rtm.Sys, + Mallocs: rtm.Mallocs, + Frees: rtm.Frees, + LiveObjects: rtm.Mallocs - rtm.Frees, + NumGC: rtm.NumGC, + PauseTotalNs: rtm.PauseTotalNs, + } + + return response, nil +} + +func (s *StatsService) mustEmbedUnimplementedStatsServiceServer() { +} + +func (s *StatsService) loadOrCreateCounter(name string, counter *atomic.Int64) *atomic.Int64 { + counter, loaded := s.counters[name] + if !loaded { + if counter == nil { + counter = atomic.NewInt64(0) + } + s.counters[name] = counter + } + return counter +} diff --git a/experimental/v2rayapi/stats.pb.go b/experimental/v2rayapi/stats.pb.go new file mode 100644 index 00000000..45cde824 --- /dev/null +++ b/experimental/v2rayapi/stats.pb.go @@ -0,0 +1,678 @@ +package v2rayapi + +import ( + reflect "reflect" + sync "sync" + + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" +) + +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 GetStatsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Name of the stat counter. + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + // Whether or not to reset the counter to fetching its value. + Reset_ bool `protobuf:"varint,2,opt,name=reset,proto3" json:"reset,omitempty"` +} + +func (x *GetStatsRequest) Reset() { + *x = GetStatsRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_experimental_v2rayapi_stats_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetStatsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetStatsRequest) ProtoMessage() {} + +func (x *GetStatsRequest) ProtoReflect() protoreflect.Message { + mi := &file_experimental_v2rayapi_stats_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 GetStatsRequest.ProtoReflect.Descriptor instead. +func (*GetStatsRequest) Descriptor() ([]byte, []int) { + return file_experimental_v2rayapi_stats_proto_rawDescGZIP(), []int{0} +} + +func (x *GetStatsRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *GetStatsRequest) GetReset_() bool { + if x != nil { + return x.Reset_ + } + return false +} + +type Stat struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Value int64 `protobuf:"varint,2,opt,name=value,proto3" json:"value,omitempty"` +} + +func (x *Stat) Reset() { + *x = Stat{} + if protoimpl.UnsafeEnabled { + mi := &file_experimental_v2rayapi_stats_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Stat) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Stat) ProtoMessage() {} + +func (x *Stat) ProtoReflect() protoreflect.Message { + mi := &file_experimental_v2rayapi_stats_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 Stat.ProtoReflect.Descriptor instead. +func (*Stat) Descriptor() ([]byte, []int) { + return file_experimental_v2rayapi_stats_proto_rawDescGZIP(), []int{1} +} + +func (x *Stat) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *Stat) GetValue() int64 { + if x != nil { + return x.Value + } + return 0 +} + +type GetStatsResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Stat *Stat `protobuf:"bytes,1,opt,name=stat,proto3" json:"stat,omitempty"` +} + +func (x *GetStatsResponse) Reset() { + *x = GetStatsResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_experimental_v2rayapi_stats_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetStatsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetStatsResponse) ProtoMessage() {} + +func (x *GetStatsResponse) ProtoReflect() protoreflect.Message { + mi := &file_experimental_v2rayapi_stats_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 GetStatsResponse.ProtoReflect.Descriptor instead. +func (*GetStatsResponse) Descriptor() ([]byte, []int) { + return file_experimental_v2rayapi_stats_proto_rawDescGZIP(), []int{2} +} + +func (x *GetStatsResponse) GetStat() *Stat { + if x != nil { + return x.Stat + } + return nil +} + +type QueryStatsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Deprecated, use Patterns instead + Pattern string `protobuf:"bytes,1,opt,name=pattern,proto3" json:"pattern,omitempty"` + Reset_ bool `protobuf:"varint,2,opt,name=reset,proto3" json:"reset,omitempty"` + Patterns []string `protobuf:"bytes,3,rep,name=patterns,proto3" json:"patterns,omitempty"` + Regexp bool `protobuf:"varint,4,opt,name=regexp,proto3" json:"regexp,omitempty"` +} + +func (x *QueryStatsRequest) Reset() { + *x = QueryStatsRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_experimental_v2rayapi_stats_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *QueryStatsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*QueryStatsRequest) ProtoMessage() {} + +func (x *QueryStatsRequest) ProtoReflect() protoreflect.Message { + mi := &file_experimental_v2rayapi_stats_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 QueryStatsRequest.ProtoReflect.Descriptor instead. +func (*QueryStatsRequest) Descriptor() ([]byte, []int) { + return file_experimental_v2rayapi_stats_proto_rawDescGZIP(), []int{3} +} + +func (x *QueryStatsRequest) GetPattern() string { + if x != nil { + return x.Pattern + } + return "" +} + +func (x *QueryStatsRequest) GetReset_() bool { + if x != nil { + return x.Reset_ + } + return false +} + +func (x *QueryStatsRequest) GetPatterns() []string { + if x != nil { + return x.Patterns + } + return nil +} + +func (x *QueryStatsRequest) GetRegexp() bool { + if x != nil { + return x.Regexp + } + return false +} + +type QueryStatsResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Stat []*Stat `protobuf:"bytes,1,rep,name=stat,proto3" json:"stat,omitempty"` +} + +func (x *QueryStatsResponse) Reset() { + *x = QueryStatsResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_experimental_v2rayapi_stats_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *QueryStatsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*QueryStatsResponse) ProtoMessage() {} + +func (x *QueryStatsResponse) ProtoReflect() protoreflect.Message { + mi := &file_experimental_v2rayapi_stats_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 QueryStatsResponse.ProtoReflect.Descriptor instead. +func (*QueryStatsResponse) Descriptor() ([]byte, []int) { + return file_experimental_v2rayapi_stats_proto_rawDescGZIP(), []int{4} +} + +func (x *QueryStatsResponse) GetStat() []*Stat { + if x != nil { + return x.Stat + } + return nil +} + +type SysStatsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *SysStatsRequest) Reset() { + *x = SysStatsRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_experimental_v2rayapi_stats_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SysStatsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SysStatsRequest) ProtoMessage() {} + +func (x *SysStatsRequest) ProtoReflect() protoreflect.Message { + mi := &file_experimental_v2rayapi_stats_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SysStatsRequest.ProtoReflect.Descriptor instead. +func (*SysStatsRequest) Descriptor() ([]byte, []int) { + return file_experimental_v2rayapi_stats_proto_rawDescGZIP(), []int{5} +} + +type SysStatsResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + NumGoroutine uint32 `protobuf:"varint,1,opt,name=NumGoroutine,proto3" json:"NumGoroutine,omitempty"` + NumGC uint32 `protobuf:"varint,2,opt,name=NumGC,proto3" json:"NumGC,omitempty"` + Alloc uint64 `protobuf:"varint,3,opt,name=Alloc,proto3" json:"Alloc,omitempty"` + TotalAlloc uint64 `protobuf:"varint,4,opt,name=TotalAlloc,proto3" json:"TotalAlloc,omitempty"` + Sys uint64 `protobuf:"varint,5,opt,name=Sys,proto3" json:"Sys,omitempty"` + Mallocs uint64 `protobuf:"varint,6,opt,name=Mallocs,proto3" json:"Mallocs,omitempty"` + Frees uint64 `protobuf:"varint,7,opt,name=Frees,proto3" json:"Frees,omitempty"` + LiveObjects uint64 `protobuf:"varint,8,opt,name=LiveObjects,proto3" json:"LiveObjects,omitempty"` + PauseTotalNs uint64 `protobuf:"varint,9,opt,name=PauseTotalNs,proto3" json:"PauseTotalNs,omitempty"` + Uptime uint32 `protobuf:"varint,10,opt,name=Uptime,proto3" json:"Uptime,omitempty"` +} + +func (x *SysStatsResponse) Reset() { + *x = SysStatsResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_experimental_v2rayapi_stats_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SysStatsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SysStatsResponse) ProtoMessage() {} + +func (x *SysStatsResponse) ProtoReflect() protoreflect.Message { + mi := &file_experimental_v2rayapi_stats_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SysStatsResponse.ProtoReflect.Descriptor instead. +func (*SysStatsResponse) Descriptor() ([]byte, []int) { + return file_experimental_v2rayapi_stats_proto_rawDescGZIP(), []int{6} +} + +func (x *SysStatsResponse) GetNumGoroutine() uint32 { + if x != nil { + return x.NumGoroutine + } + return 0 +} + +func (x *SysStatsResponse) GetNumGC() uint32 { + if x != nil { + return x.NumGC + } + return 0 +} + +func (x *SysStatsResponse) GetAlloc() uint64 { + if x != nil { + return x.Alloc + } + return 0 +} + +func (x *SysStatsResponse) GetTotalAlloc() uint64 { + if x != nil { + return x.TotalAlloc + } + return 0 +} + +func (x *SysStatsResponse) GetSys() uint64 { + if x != nil { + return x.Sys + } + return 0 +} + +func (x *SysStatsResponse) GetMallocs() uint64 { + if x != nil { + return x.Mallocs + } + return 0 +} + +func (x *SysStatsResponse) GetFrees() uint64 { + if x != nil { + return x.Frees + } + return 0 +} + +func (x *SysStatsResponse) GetLiveObjects() uint64 { + if x != nil { + return x.LiveObjects + } + return 0 +} + +func (x *SysStatsResponse) GetPauseTotalNs() uint64 { + if x != nil { + return x.PauseTotalNs + } + return 0 +} + +func (x *SysStatsResponse) GetUptime() uint32 { + if x != nil { + return x.Uptime + } + return 0 +} + +var File_experimental_v2rayapi_stats_proto protoreflect.FileDescriptor + +var file_experimental_v2rayapi_stats_proto_rawDesc = []byte{ + 0x0a, 0x21, 0x65, 0x78, 0x70, 0x65, 0x72, 0x69, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x6c, 0x2f, 0x76, + 0x32, 0x72, 0x61, 0x79, 0x61, 0x70, 0x69, 0x2f, 0x73, 0x74, 0x61, 0x74, 0x73, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x12, 0x15, 0x65, 0x78, 0x70, 0x65, 0x72, 0x69, 0x6d, 0x65, 0x6e, 0x74, 0x61, + 0x6c, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x61, 0x70, 0x69, 0x22, 0x3b, 0x0a, 0x0f, 0x47, 0x65, + 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, + 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, + 0x65, 0x12, 0x14, 0x0a, 0x05, 0x72, 0x65, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x05, 0x72, 0x65, 0x73, 0x65, 0x74, 0x22, 0x30, 0x0a, 0x04, 0x53, 0x74, 0x61, 0x74, 0x12, + 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, + 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x43, 0x0a, 0x10, 0x47, 0x65, 0x74, + 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2f, 0x0a, + 0x04, 0x73, 0x74, 0x61, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x65, 0x78, + 0x70, 0x65, 0x72, 0x69, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x6c, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, + 0x61, 0x70, 0x69, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x52, 0x04, 0x73, 0x74, 0x61, 0x74, 0x22, 0x77, + 0x0a, 0x11, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, 0x12, 0x14, 0x0a, + 0x05, 0x72, 0x65, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x72, 0x65, + 0x73, 0x65, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, 0x73, 0x18, + 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, 0x73, 0x12, + 0x16, 0x0a, 0x06, 0x72, 0x65, 0x67, 0x65, 0x78, 0x70, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x06, 0x72, 0x65, 0x67, 0x65, 0x78, 0x70, 0x22, 0x45, 0x0a, 0x12, 0x51, 0x75, 0x65, 0x72, 0x79, + 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2f, 0x0a, + 0x04, 0x73, 0x74, 0x61, 0x74, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x65, 0x78, + 0x70, 0x65, 0x72, 0x69, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x6c, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, + 0x61, 0x70, 0x69, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x52, 0x04, 0x73, 0x74, 0x61, 0x74, 0x22, 0x11, + 0x0a, 0x0f, 0x53, 0x79, 0x73, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x22, 0xa2, 0x02, 0x0a, 0x10, 0x53, 0x79, 0x73, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x22, 0x0a, 0x0c, 0x4e, 0x75, 0x6d, 0x47, 0x6f, 0x72, + 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0c, 0x4e, 0x75, + 0x6d, 0x47, 0x6f, 0x72, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x4e, 0x75, + 0x6d, 0x47, 0x43, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x4e, 0x75, 0x6d, 0x47, 0x43, + 0x12, 0x14, 0x0a, 0x05, 0x41, 0x6c, 0x6c, 0x6f, 0x63, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, + 0x05, 0x41, 0x6c, 0x6c, 0x6f, 0x63, 0x12, 0x1e, 0x0a, 0x0a, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x41, + 0x6c, 0x6c, 0x6f, 0x63, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x54, 0x6f, 0x74, 0x61, + 0x6c, 0x41, 0x6c, 0x6c, 0x6f, 0x63, 0x12, 0x10, 0x0a, 0x03, 0x53, 0x79, 0x73, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x04, 0x52, 0x03, 0x53, 0x79, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x4d, 0x61, 0x6c, 0x6c, + 0x6f, 0x63, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x4d, 0x61, 0x6c, 0x6c, 0x6f, + 0x63, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x46, 0x72, 0x65, 0x65, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, + 0x04, 0x52, 0x05, 0x46, 0x72, 0x65, 0x65, 0x73, 0x12, 0x20, 0x0a, 0x0b, 0x4c, 0x69, 0x76, 0x65, + 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x4c, + 0x69, 0x76, 0x65, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x12, 0x22, 0x0a, 0x0c, 0x50, 0x61, + 0x75, 0x73, 0x65, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x4e, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x04, + 0x52, 0x0c, 0x50, 0x61, 0x75, 0x73, 0x65, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x4e, 0x73, 0x12, 0x16, + 0x0a, 0x06, 0x55, 0x70, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, + 0x55, 0x70, 0x74, 0x69, 0x6d, 0x65, 0x32, 0xb4, 0x02, 0x0a, 0x0c, 0x53, 0x74, 0x61, 0x74, 0x73, + 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x5d, 0x0a, 0x08, 0x47, 0x65, 0x74, 0x53, 0x74, + 0x61, 0x74, 0x73, 0x12, 0x26, 0x2e, 0x65, 0x78, 0x70, 0x65, 0x72, 0x69, 0x6d, 0x65, 0x6e, 0x74, + 0x61, 0x6c, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x61, 0x70, 0x69, 0x2e, 0x47, 0x65, 0x74, 0x53, + 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x65, 0x78, + 0x70, 0x65, 0x72, 0x69, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x6c, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, + 0x61, 0x70, 0x69, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x63, 0x0a, 0x0a, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, + 0x74, 0x61, 0x74, 0x73, 0x12, 0x28, 0x2e, 0x65, 0x78, 0x70, 0x65, 0x72, 0x69, 0x6d, 0x65, 0x6e, + 0x74, 0x61, 0x6c, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x61, 0x70, 0x69, 0x2e, 0x51, 0x75, 0x65, + 0x72, 0x79, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x29, + 0x2e, 0x65, 0x78, 0x70, 0x65, 0x72, 0x69, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x6c, 0x2e, 0x76, 0x32, + 0x72, 0x61, 0x79, 0x61, 0x70, 0x69, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x74, 0x61, 0x74, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x60, 0x0a, 0x0b, 0x47, + 0x65, 0x74, 0x53, 0x79, 0x73, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x26, 0x2e, 0x65, 0x78, 0x70, + 0x65, 0x72, 0x69, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x6c, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x61, + 0x70, 0x69, 0x2e, 0x53, 0x79, 0x73, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x65, 0x78, 0x70, 0x65, 0x72, 0x69, 0x6d, 0x65, 0x6e, 0x74, 0x61, + 0x6c, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x79, 0x73, 0x53, 0x74, + 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x34, 0x5a, + 0x32, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x61, 0x67, 0x65, + 0x72, 0x6e, 0x65, 0x74, 0x2f, 0x73, 0x69, 0x6e, 0x67, 0x2d, 0x62, 0x6f, 0x78, 0x2f, 0x65, 0x78, + 0x70, 0x65, 0x72, 0x69, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x6c, 0x2f, 0x76, 0x32, 0x72, 0x61, 0x79, + 0x61, 0x70, 0x69, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_experimental_v2rayapi_stats_proto_rawDescOnce sync.Once + file_experimental_v2rayapi_stats_proto_rawDescData = file_experimental_v2rayapi_stats_proto_rawDesc +) + +func file_experimental_v2rayapi_stats_proto_rawDescGZIP() []byte { + file_experimental_v2rayapi_stats_proto_rawDescOnce.Do(func() { + file_experimental_v2rayapi_stats_proto_rawDescData = protoimpl.X.CompressGZIP(file_experimental_v2rayapi_stats_proto_rawDescData) + }) + return file_experimental_v2rayapi_stats_proto_rawDescData +} + +var ( + file_experimental_v2rayapi_stats_proto_msgTypes = make([]protoimpl.MessageInfo, 7) + file_experimental_v2rayapi_stats_proto_goTypes = []interface{}{ + (*GetStatsRequest)(nil), // 0: experimental.v2rayapi.GetStatsRequest + (*Stat)(nil), // 1: experimental.v2rayapi.Stat + (*GetStatsResponse)(nil), // 2: experimental.v2rayapi.GetStatsResponse + (*QueryStatsRequest)(nil), // 3: experimental.v2rayapi.QueryStatsRequest + (*QueryStatsResponse)(nil), // 4: experimental.v2rayapi.QueryStatsResponse + (*SysStatsRequest)(nil), // 5: experimental.v2rayapi.SysStatsRequest + (*SysStatsResponse)(nil), // 6: experimental.v2rayapi.SysStatsResponse + } +) + +var file_experimental_v2rayapi_stats_proto_depIdxs = []int32{ + 1, // 0: experimental.v2rayapi.GetStatsResponse.stat:type_name -> experimental.v2rayapi.Stat + 1, // 1: experimental.v2rayapi.QueryStatsResponse.stat:type_name -> experimental.v2rayapi.Stat + 0, // 2: experimental.v2rayapi.StatsService.GetStats:input_type -> experimental.v2rayapi.GetStatsRequest + 3, // 3: experimental.v2rayapi.StatsService.QueryStats:input_type -> experimental.v2rayapi.QueryStatsRequest + 5, // 4: experimental.v2rayapi.StatsService.GetSysStats:input_type -> experimental.v2rayapi.SysStatsRequest + 2, // 5: experimental.v2rayapi.StatsService.GetStats:output_type -> experimental.v2rayapi.GetStatsResponse + 4, // 6: experimental.v2rayapi.StatsService.QueryStats:output_type -> experimental.v2rayapi.QueryStatsResponse + 6, // 7: experimental.v2rayapi.StatsService.GetSysStats:output_type -> experimental.v2rayapi.SysStatsResponse + 5, // [5:8] is the sub-list for method output_type + 2, // [2:5] 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_experimental_v2rayapi_stats_proto_init() } +func file_experimental_v2rayapi_stats_proto_init() { + if File_experimental_v2rayapi_stats_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_experimental_v2rayapi_stats_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetStatsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_experimental_v2rayapi_stats_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Stat); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_experimental_v2rayapi_stats_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetStatsResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_experimental_v2rayapi_stats_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*QueryStatsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_experimental_v2rayapi_stats_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*QueryStatsResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_experimental_v2rayapi_stats_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SysStatsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_experimental_v2rayapi_stats_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SysStatsResponse); 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_experimental_v2rayapi_stats_proto_rawDesc, + NumEnums: 0, + NumMessages: 7, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_experimental_v2rayapi_stats_proto_goTypes, + DependencyIndexes: file_experimental_v2rayapi_stats_proto_depIdxs, + MessageInfos: file_experimental_v2rayapi_stats_proto_msgTypes, + }.Build() + File_experimental_v2rayapi_stats_proto = out.File + file_experimental_v2rayapi_stats_proto_rawDesc = nil + file_experimental_v2rayapi_stats_proto_goTypes = nil + file_experimental_v2rayapi_stats_proto_depIdxs = nil +} diff --git a/experimental/v2rayapi/stats.proto b/experimental/v2rayapi/stats.proto new file mode 100644 index 00000000..5fc3da49 --- /dev/null +++ b/experimental/v2rayapi/stats.proto @@ -0,0 +1,53 @@ +syntax = "proto3"; + +package experimental.v2rayapi; +option go_package = "github.com/sagernet/sing-box/experimental/v2rayapi"; + +message GetStatsRequest { + // Name of the stat counter. + string name = 1; + // Whether or not to reset the counter to fetching its value. + bool reset = 2; +} + +message Stat { + string name = 1; + int64 value = 2; +} + +message GetStatsResponse { + Stat stat = 1; +} + +message QueryStatsRequest { + // Deprecated, use Patterns instead + string pattern = 1; + bool reset = 2; + repeated string patterns = 3; + bool regexp = 4; +} + +message QueryStatsResponse { + repeated Stat stat = 1; +} + +message SysStatsRequest {} + +message SysStatsResponse { + uint32 NumGoroutine = 1; + uint32 NumGC = 2; + uint64 Alloc = 3; + uint64 TotalAlloc = 4; + uint64 Sys = 5; + uint64 Mallocs = 6; + uint64 Frees = 7; + uint64 LiveObjects = 8; + uint64 PauseTotalNs = 9; + uint32 Uptime = 10; +} + +service StatsService { + rpc GetStats(GetStatsRequest) returns (GetStatsResponse) {} + rpc QueryStats(QueryStatsRequest) returns (QueryStatsResponse) {} + rpc GetSysStats(SysStatsRequest) returns (SysStatsResponse) {} +} \ No newline at end of file diff --git a/experimental/v2rayapi/stats_grpc.pb.go b/experimental/v2rayapi/stats_grpc.pb.go new file mode 100644 index 00000000..de89f88e --- /dev/null +++ b/experimental/v2rayapi/stats_grpc.pb.go @@ -0,0 +1,173 @@ +package v2rayapi + +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 + +// StatsServiceClient is the client API for StatsService 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 StatsServiceClient interface { + GetStats(ctx context.Context, in *GetStatsRequest, opts ...grpc.CallOption) (*GetStatsResponse, error) + QueryStats(ctx context.Context, in *QueryStatsRequest, opts ...grpc.CallOption) (*QueryStatsResponse, error) + GetSysStats(ctx context.Context, in *SysStatsRequest, opts ...grpc.CallOption) (*SysStatsResponse, error) +} + +type statsServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewStatsServiceClient(cc grpc.ClientConnInterface) StatsServiceClient { + return &statsServiceClient{cc} +} + +func (c *statsServiceClient) GetStats(ctx context.Context, in *GetStatsRequest, opts ...grpc.CallOption) (*GetStatsResponse, error) { + out := new(GetStatsResponse) + err := c.cc.Invoke(ctx, "/experimental.v2rayapi.StatsService/GetStats", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *statsServiceClient) QueryStats(ctx context.Context, in *QueryStatsRequest, opts ...grpc.CallOption) (*QueryStatsResponse, error) { + out := new(QueryStatsResponse) + err := c.cc.Invoke(ctx, "/experimental.v2rayapi.StatsService/QueryStats", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *statsServiceClient) GetSysStats(ctx context.Context, in *SysStatsRequest, opts ...grpc.CallOption) (*SysStatsResponse, error) { + out := new(SysStatsResponse) + err := c.cc.Invoke(ctx, "/experimental.v2rayapi.StatsService/GetSysStats", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// StatsServiceServer is the server API for StatsService service. +// All implementations must embed UnimplementedStatsServiceServer +// for forward compatibility +type StatsServiceServer interface { + GetStats(context.Context, *GetStatsRequest) (*GetStatsResponse, error) + QueryStats(context.Context, *QueryStatsRequest) (*QueryStatsResponse, error) + GetSysStats(context.Context, *SysStatsRequest) (*SysStatsResponse, error) + mustEmbedUnimplementedStatsServiceServer() +} + +// UnimplementedStatsServiceServer must be embedded to have forward compatible implementations. +type UnimplementedStatsServiceServer struct{} + +func (UnimplementedStatsServiceServer) GetStats(context.Context, *GetStatsRequest) (*GetStatsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetStats not implemented") +} + +func (UnimplementedStatsServiceServer) QueryStats(context.Context, *QueryStatsRequest) (*QueryStatsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method QueryStats not implemented") +} + +func (UnimplementedStatsServiceServer) GetSysStats(context.Context, *SysStatsRequest) (*SysStatsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetSysStats not implemented") +} +func (UnimplementedStatsServiceServer) mustEmbedUnimplementedStatsServiceServer() {} + +// UnsafeStatsServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to StatsServiceServer will +// result in compilation errors. +type UnsafeStatsServiceServer interface { + mustEmbedUnimplementedStatsServiceServer() +} + +func RegisterStatsServiceServer(s grpc.ServiceRegistrar, srv StatsServiceServer) { + s.RegisterService(&StatsService_ServiceDesc, srv) +} + +func _StatsService_GetStats_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetStatsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(StatsServiceServer).GetStats(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/experimental.v2rayapi.StatsService/GetStats", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(StatsServiceServer).GetStats(ctx, req.(*GetStatsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _StatsService_QueryStats_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryStatsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(StatsServiceServer).QueryStats(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/experimental.v2rayapi.StatsService/QueryStats", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(StatsServiceServer).QueryStats(ctx, req.(*QueryStatsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _StatsService_GetSysStats_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SysStatsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(StatsServiceServer).GetSysStats(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/experimental.v2rayapi.StatsService/GetSysStats", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(StatsServiceServer).GetSysStats(ctx, req.(*SysStatsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// StatsService_ServiceDesc is the grpc.ServiceDesc for StatsService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var StatsService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "experimental.v2rayapi.StatsService", + HandlerType: (*StatsServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "GetStats", + Handler: _StatsService_GetStats_Handler, + }, + { + MethodName: "QueryStats", + Handler: _StatsService_QueryStats_Handler, + }, + { + MethodName: "GetSysStats", + Handler: _StatsService_GetSysStats_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "experimental/v2rayapi/stats.proto", +} diff --git a/inbound/hysteria_stub.go b/inbound/hysteria_stub.go index 182b993d..1a56a5b6 100644 --- a/inbound/hysteria_stub.go +++ b/inbound/hysteria_stub.go @@ -6,11 +6,11 @@ import ( "context" "github.com/sagernet/sing-box/adapter" - I "github.com/sagernet/sing-box/include" + C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" ) func NewHysteria(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HysteriaInboundOptions) (adapter.Inbound, error) { - return nil, I.ErrQUICNotIncluded + return nil, C.ErrQUICNotIncluded } diff --git a/inbound/naive_quic_stub.go b/inbound/naive_quic_stub.go index 336f2840..90f697e4 100644 --- a/inbound/naive_quic_stub.go +++ b/inbound/naive_quic_stub.go @@ -3,9 +3,9 @@ package inbound import ( - I "github.com/sagernet/sing-box/include" + C "github.com/sagernet/sing-box/constant" ) func (n *Naive) configureHTTP3Listener() error { - return I.ErrQUICNotIncluded + return C.ErrQUICNotIncluded } diff --git a/include/clashapi.go b/include/clashapi.go new file mode 100644 index 00000000..550a5db6 --- /dev/null +++ b/include/clashapi.go @@ -0,0 +1,5 @@ +//go:build with_clash_api + +package include + +import _ "github.com/sagernet/sing-box/experimental/clashapi" diff --git a/include/clashapi_stub.go b/include/clashapi_stub.go new file mode 100644 index 00000000..5f7e5aac --- /dev/null +++ b/include/clashapi_stub.go @@ -0,0 +1,17 @@ +//go:build !with_clash_api + +package include + +import ( + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/experimental" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + E "github.com/sagernet/sing/common/exceptions" +) + +func init() { + experimental.RegisterClashServerConstructor(func(router adapter.Router, logFactory log.ObservableFactory, options option.ClashAPIOptions) (adapter.ClashServer, error) { + return nil, E.New(`clash api is not included in this build, rebuild with -tags with_clash_api`) + }) +} diff --git a/include/quic.go b/include/quic.go index 3f6ea8da..1e507f7b 100644 --- a/include/quic.go +++ b/include/quic.go @@ -2,6 +2,9 @@ package include -import _ "github.com/sagernet/sing-dns/quic" +import ( + _ "github.com/sagernet/sing-box/transport/v2rayquic" + _ "github.com/sagernet/sing-dns/quic" +) const WithQUIC = true diff --git a/include/quic_stub.go b/include/quic_stub.go index 22267c92..be314956 100644 --- a/include/quic_stub.go +++ b/include/quic_stub.go @@ -5,17 +5,29 @@ package include import ( "context" + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/tls" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing-box/transport/v2ray" "github.com/sagernet/sing-dns" E "github.com/sagernet/sing/common/exceptions" + M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" ) const WithQUIC = false -var ErrQUICNotIncluded = E.New(`QUIC is not included in this build, rebuild with -tags with_quic`) - func init() { dns.RegisterTransport([]string{"quic", "h3"}, func(ctx context.Context, dialer N.Dialer, link string) (dns.Transport, error) { - return nil, ErrQUICNotIncluded + return nil, C.ErrQUICNotIncluded }) + v2ray.RegisterQUICConstructor( + func(ctx context.Context, options option.V2RayQUICOptions, tlsConfig tls.Config, handler N.TCPConnectionHandler, errorHandler E.Handler) (adapter.V2RayServerTransport, error) { + return nil, C.ErrQUICNotIncluded + }, + func(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, options option.V2RayQUICOptions, tlsConfig tls.Config) (adapter.V2RayClientTransport, error) { + return nil, C.ErrQUICNotIncluded + }, + ) } diff --git a/include/v2rayapi.go b/include/v2rayapi.go new file mode 100644 index 00000000..acd3e9a8 --- /dev/null +++ b/include/v2rayapi.go @@ -0,0 +1,5 @@ +//go:build with_v2ray_api + +package include + +import _ "github.com/sagernet/sing-box/experimental/v2rayapi" diff --git a/include/v2rayapi_stub.go b/include/v2rayapi_stub.go new file mode 100644 index 00000000..7f1f6b9e --- /dev/null +++ b/include/v2rayapi_stub.go @@ -0,0 +1,17 @@ +//go:build !with_v2ray_api + +package include + +import ( + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/experimental" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + E "github.com/sagernet/sing/common/exceptions" +) + +func init() { + experimental.RegisterV2RayServerConstructor(func(logger log.Logger, options option.V2RayAPIOptions) (adapter.V2RayServer, error) { + return nil, E.New(`v2ray api is not included in this build, rebuild with -tags with_v2ray_api`) + }) +} diff --git a/option/experimental.go b/option/experimental.go index 458c42a4..2167ddaa 100644 --- a/option/experimental.go +++ b/option/experimental.go @@ -2,4 +2,5 @@ package option type ExperimentalOptions struct { ClashAPI *ClashAPIOptions `json:"clash_api,omitempty"` + V2RayAPI *V2RayAPIOptions `json:"v2ray_api,omitempty"` } diff --git a/option/v2ray.go b/option/v2ray.go new file mode 100644 index 00000000..25d3138a --- /dev/null +++ b/option/v2ray.go @@ -0,0 +1,13 @@ +package option + +type V2RayAPIOptions struct { + Listen string `json:"listen,omitempty"` + Stats *V2RayStatsServiceOptions `json:"stats,omitempty"` +} + +type V2RayStatsServiceOptions struct { + Enabled bool `json:"enabled,omitempty"` + DirectIO bool `json:"direct_io,omitempty"` + Inbounds []string `json:"inbounds,omitempty"` + Outbounds []string `json:"outbounds,omitempty"` +} diff --git a/outbound/hysteria_stub.go b/outbound/hysteria_stub.go index 6c333636..62fae20c 100644 --- a/outbound/hysteria_stub.go +++ b/outbound/hysteria_stub.go @@ -6,11 +6,11 @@ import ( "context" "github.com/sagernet/sing-box/adapter" - I "github.com/sagernet/sing-box/include" + C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" ) func NewHysteria(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HysteriaOutboundOptions) (adapter.Outbound, error) { - return nil, I.ErrQUICNotIncluded + return nil, C.ErrQUICNotIncluded } diff --git a/route/router.go b/route/router.go index df975db3..4b6fd438 100644 --- a/route/router.go +++ b/route/router.go @@ -93,8 +93,9 @@ type Router struct { networkMonitor tun.NetworkUpdateMonitor interfaceMonitor tun.DefaultInterfaceMonitor packageManager tun.PackageManager - clashServer adapter.ClashServer processSearcher process.Searcher + clashServer adapter.ClashServer + v2rayServer adapter.V2RayServer } func NewRouter(ctx context.Context, logger log.ContextLogger, dnsLogger log.ContextLogger, options option.RouteOptions, dnsOptions option.DNSOptions, inbounds []option.Inbound) (*Router, error) { @@ -590,6 +591,11 @@ func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata ad defer tracker.Leave() conn = trackerConn } + if r.v2rayServer != nil { + if statsService := r.v2rayServer.StatsService(); statsService != nil { + conn = statsService.RoutedConnection(metadata.Inbound, detour.Tag(), conn) + } + } return detour.NewConnection(ctx, conn, metadata) } @@ -663,6 +669,11 @@ func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, m defer tracker.Leave() conn = trackerConn } + if r.v2rayServer != nil { + if statsService := r.v2rayServer.StatsService(); statsService != nil { + conn = statsService.RoutedPacketConnection(metadata.Inbound, detour.Tag(), conn) + } + } return detour.NewPacketConnection(ctx, conn, metadata) } @@ -747,8 +758,16 @@ func (r *Router) ClashServer() adapter.ClashServer { return r.clashServer } -func (r *Router) SetClashServer(controller adapter.ClashServer) { - r.clashServer = controller +func (r *Router) SetClashServer(server adapter.ClashServer) { + r.clashServer = server +} + +func (r *Router) V2RayServer() adapter.V2RayServer { + return r.v2rayServer +} + +func (r *Router) SetV2RayServer(server adapter.V2RayServer) { + r.v2rayServer = server } func hasRule(rules []option.Rule, cond func(rule option.DefaultRule) bool) bool { diff --git a/transport/v2ray/quic.go b/transport/v2ray/quic.go index 6cbbb952..e9a0142c 100644 --- a/transport/v2ray/quic.go +++ b/transport/v2ray/quic.go @@ -1,23 +1,37 @@ -//go:build with_quic - package v2ray import ( "context" + "os" "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/tls" "github.com/sagernet/sing-box/option" - "github.com/sagernet/sing-box/transport/v2rayquic" E "github.com/sagernet/sing/common/exceptions" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" ) +var ( + quicServerConstructor ServerConstructor[option.V2RayQUICOptions] + quicClientConstructor ClientConstructor[option.V2RayQUICOptions] +) + +func RegisterQUICConstructor(server ServerConstructor[option.V2RayQUICOptions], client ClientConstructor[option.V2RayQUICOptions]) { + quicServerConstructor = server + quicClientConstructor = client +} + func NewQUICServer(ctx context.Context, options option.V2RayQUICOptions, tlsConfig tls.Config, handler N.TCPConnectionHandler, errorHandler E.Handler) (adapter.V2RayServerTransport, error) { - return v2rayquic.NewServer(ctx, options, tlsConfig, handler, errorHandler) + if quicServerConstructor == nil { + return nil, os.ErrInvalid + } + return quicServerConstructor(ctx, options, tlsConfig, handler, errorHandler) } func NewQUICClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, options option.V2RayQUICOptions, tlsConfig tls.Config) (adapter.V2RayClientTransport, error) { - return v2rayquic.NewClient(ctx, dialer, serverAddr, options, tlsConfig) + if quicClientConstructor == nil { + return nil, os.ErrInvalid + } + return quicClientConstructor(ctx, dialer, serverAddr, options, tlsConfig) } diff --git a/transport/v2ray/quic_stub.go b/transport/v2ray/quic_stub.go deleted file mode 100644 index 59775907..00000000 --- a/transport/v2ray/quic_stub.go +++ /dev/null @@ -1,23 +0,0 @@ -//go:build !with_quic - -package v2ray - -import ( - "context" - - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/common/tls" - I "github.com/sagernet/sing-box/include" - "github.com/sagernet/sing-box/option" - E "github.com/sagernet/sing/common/exceptions" - M "github.com/sagernet/sing/common/metadata" - N "github.com/sagernet/sing/common/network" -) - -func NewQUICServer(ctx context.Context, options option.V2RayQUICOptions, tlsConfig tls.Config, handler N.TCPConnectionHandler, errorHandler E.Handler) (adapter.V2RayServerTransport, error) { - return nil, I.ErrQUICNotIncluded -} - -func NewQUICClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, options option.V2RayQUICOptions, tlsConfig tls.Config) (adapter.V2RayClientTransport, error) { - return nil, I.ErrQUICNotIncluded -} diff --git a/transport/v2ray/transport.go b/transport/v2ray/transport.go index 9f26073e..a8beda57 100644 --- a/transport/v2ray/transport.go +++ b/transport/v2ray/transport.go @@ -14,6 +14,11 @@ import ( N "github.com/sagernet/sing/common/network" ) +type ( + ServerConstructor[O any] func(ctx context.Context, options O, tlsConfig tls.Config, handler N.TCPConnectionHandler, errorHandler E.Handler) (adapter.V2RayServerTransport, error) + ClientConstructor[O any] func(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, options O, tlsConfig tls.Config) (adapter.V2RayClientTransport, error) +) + func NewServerTransport(ctx context.Context, options option.V2RayTransportOptions, tlsConfig tls.Config, handler N.TCPConnectionHandler, errorHandler E.Handler) (adapter.V2RayServerTransport, error) { if options.Type == "" { return nil, nil diff --git a/transport/v2rayquic/init.go b/transport/v2rayquic/init.go new file mode 100644 index 00000000..45933ebe --- /dev/null +++ b/transport/v2rayquic/init.go @@ -0,0 +1,7 @@ +package v2rayquic + +import "github.com/sagernet/sing-box/transport/v2ray" + +func init() { + v2ray.RegisterQUICConstructor(NewServer, NewClient) +} diff --git a/transport/v2rayquic/server.go b/transport/v2rayquic/server.go index 95ee9691..3642d648 100644 --- a/transport/v2rayquic/server.go +++ b/transport/v2rayquic/server.go @@ -29,7 +29,7 @@ type Server struct { quicListener quic.Listener } -func NewServer(ctx context.Context, options option.V2RayQUICOptions, tlsConfig tls.Config, handler N.TCPConnectionHandler, errorHandler E.Handler) (*Server, error) { +func NewServer(ctx context.Context, options option.V2RayQUICOptions, tlsConfig tls.Config, handler N.TCPConnectionHandler, errorHandler E.Handler) (adapter.V2RayServerTransport, error) { quicConfig := &quic.Config{ DisablePathMTUDiscovery: !C.IsLinux && !C.IsWindows, }