diff --git a/box.go b/box.go index 75646de5..8d24df7e 100644 --- a/box.go +++ b/box.go @@ -176,7 +176,7 @@ func New(options Options) (*Box, error) { preServices2["clash api"] = clashServer } if needV2RayAPI { - v2rayServer, err := experimental.NewV2RayServer(logFactory.NewLogger("v2ray-api"), common.PtrValueOrDefault(experimentalOptions.V2RayAPI)) + v2rayServer, err := experimental.NewV2RayServer(ctx, logFactory.NewLogger("v2ray-api"), common.PtrValueOrDefault(experimentalOptions.V2RayAPI)) if err != nil { return nil, E.Cause(err, "create v2ray api server") } diff --git a/common/tls/listener.go b/common/tls/listener.go new file mode 100644 index 00000000..df6f002e --- /dev/null +++ b/common/tls/listener.go @@ -0,0 +1,58 @@ +package tls + +import ( + "context" + "net" + + "github.com/sagernet/sing/common/tls" +) + +var _ net.Listener = (*Listener)(nil) + +type Listener struct { + ctx context.Context + l net.Listener + tls tls.ServerConfig +} + +func (t *Listener) Addr() net.Addr { + return t.l.Addr() +} + +func (t *Listener) Accept() (net.Conn, error) { + if t.tls == nil { + return t.l.Accept() + } + for { + conn, err := t.l.Accept() + if err != nil { + return nil, err + } + conn, err = tls.ServerHandshake(t.ctx, conn, t.tls) + if err == nil { + return conn, nil + } + } +} + +func (t *Listener) Close() error { + if err := t.l.Close(); err != nil { + return err + } + if t.tls != nil { + return t.tls.Close() + } + return nil +} + +func NewListener(ctx context.Context, address string, config ServerConfig) (net.Listener, error) { + l, err := net.Listen("tcp", address) + if err != nil { + return nil, err + } + return &Listener{ + ctx: ctx, + l: l, + tls: config, + }, nil +} diff --git a/docs/configuration/experimental/clash-api.md b/docs/configuration/experimental/clash-api.md index e1ca9815..3c9ba266 100644 --- a/docs/configuration/experimental/clash-api.md +++ b/docs/configuration/experimental/clash-api.md @@ -16,6 +16,7 @@ "external_ui_download_detour": "", "secret": "", "default_mode": "", + "tls": {}, // Deprecated diff --git a/docs/configuration/experimental/clash-api.zh.md b/docs/configuration/experimental/clash-api.zh.md index 092769ac..fce7e010 100644 --- a/docs/configuration/experimental/clash-api.zh.md +++ b/docs/configuration/experimental/clash-api.zh.md @@ -16,6 +16,7 @@ "external_ui_download_detour": "", "secret": "", "default_mode": "", + "tls": {}, // Deprecated diff --git a/docs/configuration/experimental/v2ray-api.md b/docs/configuration/experimental/v2ray-api.md index 4e23dea9..d264f984 100644 --- a/docs/configuration/experimental/v2ray-api.md +++ b/docs/configuration/experimental/v2ray-api.md @@ -19,7 +19,8 @@ "users": [ "sekai" ] - } + }, + "tls": {} } ``` diff --git a/docs/configuration/experimental/v2ray-api.zh.md b/docs/configuration/experimental/v2ray-api.zh.md index 81fc8427..f72a3352 100644 --- a/docs/configuration/experimental/v2ray-api.zh.md +++ b/docs/configuration/experimental/v2ray-api.zh.md @@ -19,7 +19,8 @@ "users": [ "sekai" ] - } + }, + "tls": {} } ``` diff --git a/experimental/clashapi/server.go b/experimental/clashapi/server.go index a1152baa..aca9d7ad 100644 --- a/experimental/clashapi/server.go +++ b/experimental/clashapi/server.go @@ -11,6 +11,7 @@ import ( "time" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/tls" "github.com/sagernet/sing-box/common/urltest" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/experimental" @@ -47,6 +48,7 @@ type Server struct { mode string modeList []string modeUpdateHook chan<- struct{} + tlsConfig tls.ServerConfig externalController bool externalUI string @@ -124,6 +126,13 @@ func NewServer(ctx context.Context, router adapter.Router, logFactory log.Observ }) }) } + if options.TLS != nil { + tlsConfig, err := tls.NewServer(ctx, server.logger, common.PtrValueOrDefault(options.TLS)) + if err != nil { + return nil, err + } + server.tlsConfig = tlsConfig + } return server, nil } @@ -141,9 +150,15 @@ func (s *Server) PreStart() error { } func (s *Server) Start() error { + if s.tlsConfig != nil { + err := s.tlsConfig.Start() + if err != nil { + return E.Cause(err, "create TLS config") + } + } if s.externalController { s.checkAndDownloadExternalUI() - listener, err := net.Listen("tcp", s.httpServer.Addr) + listener, err := tls.NewListener(s.ctx, s.httpServer.Addr, s.tlsConfig) if err != nil { return E.Cause(err, "external controller listen error") } diff --git a/experimental/v2rayapi.go b/experimental/v2rayapi.go index cf479631..fb435cf0 100644 --- a/experimental/v2rayapi.go +++ b/experimental/v2rayapi.go @@ -1,6 +1,7 @@ package experimental import ( + "context" "os" "github.com/sagernet/sing-box/adapter" @@ -8,7 +9,7 @@ import ( "github.com/sagernet/sing-box/option" ) -type V2RayServerConstructor = func(logger log.Logger, options option.V2RayAPIOptions) (adapter.V2RayServer, error) +type V2RayServerConstructor = func(ctx context.Context, logger log.Logger, options option.V2RayAPIOptions) (adapter.V2RayServer, error) var v2rayServerConstructor V2RayServerConstructor @@ -16,9 +17,9 @@ func RegisterV2RayServerConstructor(constructor V2RayServerConstructor) { v2rayServerConstructor = constructor } -func NewV2RayServer(logger log.Logger, options option.V2RayAPIOptions) (adapter.V2RayServer, error) { +func NewV2RayServer(ctx context.Context, logger log.Logger, options option.V2RayAPIOptions) (adapter.V2RayServer, error) { if v2rayServerConstructor == nil { return nil, os.ErrInvalid } - return v2rayServerConstructor(logger, options) + return v2rayServerConstructor(ctx, logger, options) } diff --git a/experimental/v2rayapi/server.go b/experimental/v2rayapi/server.go index 8b4b4385..639e2c31 100644 --- a/experimental/v2rayapi/server.go +++ b/experimental/v2rayapi/server.go @@ -1,15 +1,18 @@ package v2rayapi import ( + "context" "errors" "net" "net/http" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/tls" "github.com/sagernet/sing-box/experimental" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing/common" + E "github.com/sagernet/sing/common/exceptions" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" @@ -22,30 +25,48 @@ func init() { var _ adapter.V2RayServer = (*Server)(nil) type Server struct { + ctx context.Context logger log.Logger listen string tcpListener net.Listener grpcServer *grpc.Server statsService *StatsService + tlsConfig tls.ServerConfig } -func NewServer(logger log.Logger, options option.V2RayAPIOptions) (adapter.V2RayServer, error) { +func NewServer(ctx context.Context, 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) } + var tlsConfig tls.ServerConfig + if options.TLS != nil { + var err error + tlsConfig, err = tls.NewServer(ctx, logger, common.PtrValueOrDefault(options.TLS)) + if err != nil { + return nil, err + } + } server := &Server{ + ctx: ctx, logger: logger, listen: options.Listen, grpcServer: grpcServer, statsService: statsService, + tlsConfig: tlsConfig, } return server, nil } func (s *Server) Start() error { - listener, err := net.Listen("tcp", s.listen) + if s.tlsConfig != nil { + err := s.tlsConfig.Start() + if err != nil { + return E.Cause(err, "create TLS config") + } + } + listener, err := tls.NewListener(s.ctx, s.listen, s.tlsConfig) if err != nil { return err } diff --git a/include/v2rayapi_stub.go b/include/v2rayapi_stub.go index 7f1f6b9e..5cc7948c 100644 --- a/include/v2rayapi_stub.go +++ b/include/v2rayapi_stub.go @@ -3,6 +3,8 @@ package include import ( + "context" + "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/experimental" "github.com/sagernet/sing-box/log" @@ -11,7 +13,7 @@ import ( ) func init() { - experimental.RegisterV2RayServerConstructor(func(logger log.Logger, options option.V2RayAPIOptions) (adapter.V2RayServer, error) { + experimental.RegisterV2RayServerConstructor(func(ctx context.Context, 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 9f6071ba..e03d81a1 100644 --- a/option/experimental.go +++ b/option/experimental.go @@ -25,6 +25,8 @@ type ClashAPIOptions struct { DefaultMode string `json:"default_mode,omitempty"` ModeList []string `json:"-"` + InboundTLSOptionsContainer + // Deprecated: migrated to global cache file CacheFile string `json:"cache_file,omitempty"` // Deprecated: migrated to global cache file @@ -40,6 +42,7 @@ type ClashAPIOptions struct { type V2RayAPIOptions struct { Listen string `json:"listen,omitempty"` Stats *V2RayStatsServiceOptions `json:"stats,omitempty"` + InboundTLSOptionsContainer } type V2RayStatsServiceOptions struct {