浏览代码

Add custom tls client support for std grpc

世界 2 年之前
父节点
当前提交
eb2e8a0b40

+ 3 - 3
common/tls/client.go

@@ -24,11 +24,11 @@ func NewDialerFromOptions(router adapter.Router, dialer N.Dialer, serverAddress
 
 func NewClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
 	if options.ECH != nil && options.ECH.Enabled {
-		return newECHClient(router, serverAddress, options)
+		return NewECHClient(router, serverAddress, options)
 	} else if options.UTLS != nil && options.UTLS.Enabled {
-		return newUTLSClient(router, serverAddress, options)
+		return NewUTLSClient(router, serverAddress, options)
 	} else {
-		return newStdClient(serverAddress, options)
+		return NewSTDClient(serverAddress, options)
 	}
 }
 

+ 3 - 0
common/tls/config.go

@@ -15,10 +15,13 @@ type (
 )
 
 type Config interface {
+	ServerName() string
+	SetServerName(serverName string)
 	NextProtos() []string
 	SetNextProtos(nextProto []string)
 	Config() (*STDConfig, error)
 	Client(conn net.Conn) Conn
+	Clone() Config
 }
 
 type ServerConfig interface {

+ 21 - 7
common/tls/ech_client.go

@@ -20,26 +20,40 @@ import (
 	mDNS "github.com/miekg/dns"
 )
 
-type echClientConfig struct {
+type ECHClientConfig struct {
 	config *cftls.Config
 }
 
-func (e *echClientConfig) NextProtos() []string {
+func (e *ECHClientConfig) ServerName() string {
+	return e.config.ServerName
+}
+
+func (e *ECHClientConfig) SetServerName(serverName string) {
+	e.config.ServerName = serverName
+}
+
+func (e *ECHClientConfig) NextProtos() []string {
 	return e.config.NextProtos
 }
 
-func (e *echClientConfig) SetNextProtos(nextProto []string) {
+func (e *ECHClientConfig) SetNextProtos(nextProto []string) {
 	e.config.NextProtos = nextProto
 }
 
-func (e *echClientConfig) Config() (*STDConfig, error) {
+func (e *ECHClientConfig) Config() (*STDConfig, error) {
 	return nil, E.New("unsupported usage for ECH")
 }
 
-func (e *echClientConfig) Client(conn net.Conn) Conn {
+func (e *ECHClientConfig) Client(conn net.Conn) Conn {
 	return &echConnWrapper{cftls.Client(conn, e.config)}
 }
 
+func (e *ECHClientConfig) Clone() Config {
+	return &ECHClientConfig{
+		config: e.config.Clone(),
+	}
+}
+
 type echConnWrapper struct {
 	*cftls.Conn
 }
@@ -62,7 +76,7 @@ func (c *echConnWrapper) ConnectionState() tls.ConnectionState {
 	}
 }
 
-func newECHClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
+func NewECHClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
 	var serverName string
 	if options.ServerName != "" {
 		serverName = options.ServerName
@@ -162,7 +176,7 @@ func newECHClient(router adapter.Router, serverAddress string, options option.Ou
 	} else {
 		tlsConfig.GetClientECHConfigs = fetchECHClientConfig(router)
 	}
-	return &echClientConfig{&tlsConfig}, nil
+	return &ECHClientConfig{&tlsConfig}, nil
 }
 
 func fetchECHClientConfig(router adapter.Router) func(ctx context.Context, serverName string) ([]cftls.ECHConfig, error) {

+ 1 - 1
common/tls/ech_stub.go

@@ -8,6 +8,6 @@ import (
 	E "github.com/sagernet/sing/common/exceptions"
 )
 
-func newECHClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
+func NewECHClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
 	return nil, E.New(`ECH is not included in this build, rebuild with -tags with_ech`)
 }

+ 1 - 1
common/tls/server.go

@@ -12,7 +12,7 @@ import (
 )
 
 func NewServer(ctx context.Context, logger log.Logger, options option.InboundTLSOptions) (ServerConfig, error) {
-	return newSTDServer(ctx, logger, options)
+	return NewSTDServer(ctx, logger, options)
 }
 
 func ServerHandshake(ctx context.Context, conn net.Conn, config ServerConfig) (Conn, error) {

+ 31 - 19
common/tls/std_client.go

@@ -11,11 +11,39 @@ import (
 	E "github.com/sagernet/sing/common/exceptions"
 )
 
-type stdClientConfig struct {
+type STDClientConfig struct {
 	config *tls.Config
 }
 
-func newStdClient(serverAddress string, options option.OutboundTLSOptions) (Config, error) {
+func (s *STDClientConfig) ServerName() string {
+	return s.config.ServerName
+}
+
+func (s *STDClientConfig) SetServerName(serverName string) {
+	s.config.ServerName = serverName
+}
+
+func (s *STDClientConfig) NextProtos() []string {
+	return s.config.NextProtos
+}
+
+func (s *STDClientConfig) SetNextProtos(nextProto []string) {
+	s.config.NextProtos = nextProto
+}
+
+func (s *STDClientConfig) Config() (*STDConfig, error) {
+	return s.config, nil
+}
+
+func (s *STDClientConfig) Client(conn net.Conn) Conn {
+	return tls.Client(conn, s.config)
+}
+
+func (s *STDClientConfig) Clone() Config {
+	return &STDClientConfig{s.config.Clone()}
+}
+
+func NewSTDClient(serverAddress string, options option.OutboundTLSOptions) (Config, error) {
 	var serverName string
 	if options.ServerName != "" {
 		serverName = options.ServerName
@@ -96,21 +124,5 @@ func newStdClient(serverAddress string, options option.OutboundTLSOptions) (Conf
 		}
 		tlsConfig.RootCAs = certPool
 	}
-	return &stdClientConfig{&tlsConfig}, nil
-}
-
-func (s *stdClientConfig) NextProtos() []string {
-	return s.config.NextProtos
-}
-
-func (s *stdClientConfig) SetNextProtos(nextProto []string) {
-	s.config.NextProtos = nextProto
-}
-
-func (s *stdClientConfig) Config() (*STDConfig, error) {
-	return s.config, nil
-}
-
-func (s *stdClientConfig) Client(conn net.Conn) Conn {
-	return tls.Client(conn, s.config)
+	return &STDClientConfig{&tlsConfig}, nil
 }

+ 117 - 103
common/tls/std_server.go

@@ -15,6 +15,8 @@ import (
 	"github.com/fsnotify/fsnotify"
 )
 
+var errInsecureUnused = E.New("tls: insecure unused")
+
 type STDServerConfig struct {
 	config          *tls.Config
 	logger          log.Logger
@@ -26,6 +28,14 @@ type STDServerConfig struct {
 	watcher         *fsnotify.Watcher
 }
 
+func (c *STDServerConfig) ServerName() string {
+	return c.config.ServerName
+}
+
+func (c *STDServerConfig) SetServerName(serverName string) {
+	c.config.ServerName = serverName
+}
+
 func (c *STDServerConfig) NextProtos() []string {
 	return c.config.NextProtos
 }
@@ -34,109 +44,6 @@ func (c *STDServerConfig) SetNextProtos(nextProto []string) {
 	c.config.NextProtos = nextProto
 }
 
-var errInsecureUnused = E.New("tls: insecure unused")
-
-func newSTDServer(ctx context.Context, logger log.Logger, options option.InboundTLSOptions) (ServerConfig, error) {
-	if !options.Enabled {
-		return nil, nil
-	}
-	var tlsConfig *tls.Config
-	var acmeService adapter.Service
-	var err error
-	if options.ACME != nil && len(options.ACME.Domain) > 0 {
-		tlsConfig, acmeService, err = startACME(ctx, common.PtrValueOrDefault(options.ACME))
-		//nolint:staticcheck
-		if err != nil {
-			return nil, err
-		}
-		if options.Insecure {
-			return nil, errInsecureUnused
-		}
-	} else {
-		tlsConfig = &tls.Config{}
-	}
-	if options.ServerName != "" {
-		tlsConfig.ServerName = options.ServerName
-	}
-	if len(options.ALPN) > 0 {
-		tlsConfig.NextProtos = append(tlsConfig.NextProtos, options.ALPN...)
-	}
-	if options.MinVersion != "" {
-		minVersion, err := ParseTLSVersion(options.MinVersion)
-		if err != nil {
-			return nil, E.Cause(err, "parse min_version")
-		}
-		tlsConfig.MinVersion = minVersion
-	}
-	if options.MaxVersion != "" {
-		maxVersion, err := ParseTLSVersion(options.MaxVersion)
-		if err != nil {
-			return nil, E.Cause(err, "parse max_version")
-		}
-		tlsConfig.MaxVersion = maxVersion
-	}
-	if options.CipherSuites != nil {
-	find:
-		for _, cipherSuite := range options.CipherSuites {
-			for _, tlsCipherSuite := range tls.CipherSuites() {
-				if cipherSuite == tlsCipherSuite.Name {
-					tlsConfig.CipherSuites = append(tlsConfig.CipherSuites, tlsCipherSuite.ID)
-					continue find
-				}
-			}
-			return nil, E.New("unknown cipher_suite: ", cipherSuite)
-		}
-	}
-	var certificate []byte
-	var key []byte
-	if acmeService == nil {
-		if options.Certificate != "" {
-			certificate = []byte(options.Certificate)
-		} else if options.CertificatePath != "" {
-			content, err := os.ReadFile(options.CertificatePath)
-			if err != nil {
-				return nil, E.Cause(err, "read certificate")
-			}
-			certificate = content
-		}
-		if options.Key != "" {
-			key = []byte(options.Key)
-		} else if options.KeyPath != "" {
-			content, err := os.ReadFile(options.KeyPath)
-			if err != nil {
-				return nil, E.Cause(err, "read key")
-			}
-			key = content
-		}
-		if certificate == nil && key == nil && options.Insecure {
-			tlsConfig.GetCertificate = func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
-				return GenerateKeyPair(info.ServerName)
-			}
-		} else {
-			if certificate == nil {
-				return nil, E.New("missing certificate")
-			} else if key == nil {
-				return nil, E.New("missing key")
-			}
-
-			keyPair, err := tls.X509KeyPair(certificate, key)
-			if err != nil {
-				return nil, E.Cause(err, "parse x509 key pair")
-			}
-			tlsConfig.Certificates = []tls.Certificate{keyPair}
-		}
-	}
-	return &STDServerConfig{
-		config:          tlsConfig,
-		logger:          logger,
-		acmeService:     acmeService,
-		certificate:     certificate,
-		key:             key,
-		certificatePath: options.CertificatePath,
-		keyPath:         options.KeyPath,
-	}, nil
-}
-
 func (c *STDServerConfig) Config() (*STDConfig, error) {
 	return c.config, nil
 }
@@ -149,6 +56,12 @@ func (c *STDServerConfig) Server(conn net.Conn) Conn {
 	return tls.Server(conn, c.config)
 }
 
+func (c *STDServerConfig) Clone() Config {
+	return &STDServerConfig{
+		config: c.config.Clone(),
+	}
+}
+
 func (c *STDServerConfig) Start() error {
 	if c.acmeService != nil {
 		return c.acmeService.Start()
@@ -242,3 +155,104 @@ func (c *STDServerConfig) Close() error {
 	}
 	return nil
 }
+
+func NewSTDServer(ctx context.Context, logger log.Logger, options option.InboundTLSOptions) (ServerConfig, error) {
+	if !options.Enabled {
+		return nil, nil
+	}
+	var tlsConfig *tls.Config
+	var acmeService adapter.Service
+	var err error
+	if options.ACME != nil && len(options.ACME.Domain) > 0 {
+		tlsConfig, acmeService, err = startACME(ctx, common.PtrValueOrDefault(options.ACME))
+		//nolint:staticcheck
+		if err != nil {
+			return nil, err
+		}
+		if options.Insecure {
+			return nil, errInsecureUnused
+		}
+	} else {
+		tlsConfig = &tls.Config{}
+	}
+	if options.ServerName != "" {
+		tlsConfig.ServerName = options.ServerName
+	}
+	if len(options.ALPN) > 0 {
+		tlsConfig.NextProtos = append(tlsConfig.NextProtos, options.ALPN...)
+	}
+	if options.MinVersion != "" {
+		minVersion, err := ParseTLSVersion(options.MinVersion)
+		if err != nil {
+			return nil, E.Cause(err, "parse min_version")
+		}
+		tlsConfig.MinVersion = minVersion
+	}
+	if options.MaxVersion != "" {
+		maxVersion, err := ParseTLSVersion(options.MaxVersion)
+		if err != nil {
+			return nil, E.Cause(err, "parse max_version")
+		}
+		tlsConfig.MaxVersion = maxVersion
+	}
+	if options.CipherSuites != nil {
+	find:
+		for _, cipherSuite := range options.CipherSuites {
+			for _, tlsCipherSuite := range tls.CipherSuites() {
+				if cipherSuite == tlsCipherSuite.Name {
+					tlsConfig.CipherSuites = append(tlsConfig.CipherSuites, tlsCipherSuite.ID)
+					continue find
+				}
+			}
+			return nil, E.New("unknown cipher_suite: ", cipherSuite)
+		}
+	}
+	var certificate []byte
+	var key []byte
+	if acmeService == nil {
+		if options.Certificate != "" {
+			certificate = []byte(options.Certificate)
+		} else if options.CertificatePath != "" {
+			content, err := os.ReadFile(options.CertificatePath)
+			if err != nil {
+				return nil, E.Cause(err, "read certificate")
+			}
+			certificate = content
+		}
+		if options.Key != "" {
+			key = []byte(options.Key)
+		} else if options.KeyPath != "" {
+			content, err := os.ReadFile(options.KeyPath)
+			if err != nil {
+				return nil, E.Cause(err, "read key")
+			}
+			key = content
+		}
+		if certificate == nil && key == nil && options.Insecure {
+			tlsConfig.GetCertificate = func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
+				return GenerateKeyPair(info.ServerName)
+			}
+		} else {
+			if certificate == nil {
+				return nil, E.New("missing certificate")
+			} else if key == nil {
+				return nil, E.New("missing key")
+			}
+
+			keyPair, err := tls.X509KeyPair(certificate, key)
+			if err != nil {
+				return nil, E.Cause(err, "parse x509 key pair")
+			}
+			tlsConfig.Certificates = []tls.Certificate{keyPair}
+		}
+	}
+	return &STDServerConfig{
+		config:          tlsConfig,
+		logger:          logger,
+		acmeService:     acmeService,
+		certificate:     certificate,
+		key:             key,
+		certificatePath: options.CertificatePath,
+		keyPath:         options.KeyPath,
+	}, nil
+}

+ 22 - 7
common/tls/utls_client.go

@@ -16,24 +16,32 @@ import (
 	utls "github.com/refraction-networking/utls"
 )
 
-type utlsClientConfig struct {
+type UTLSClientConfig struct {
 	config *utls.Config
 	id     utls.ClientHelloID
 }
 
-func (e *utlsClientConfig) NextProtos() []string {
+func (e *UTLSClientConfig) ServerName() string {
+	return e.config.ServerName
+}
+
+func (e *UTLSClientConfig) SetServerName(serverName string) {
+	e.config.ServerName = serverName
+}
+
+func (e *UTLSClientConfig) NextProtos() []string {
 	return e.config.NextProtos
 }
 
-func (e *utlsClientConfig) SetNextProtos(nextProto []string) {
+func (e *UTLSClientConfig) SetNextProtos(nextProto []string) {
 	e.config.NextProtos = nextProto
 }
 
-func (e *utlsClientConfig) Config() (*STDConfig, error) {
+func (e *UTLSClientConfig) Config() (*STDConfig, error) {
 	return nil, E.New("unsupported usage for uTLS")
 }
 
-func (e *utlsClientConfig) Client(conn net.Conn) Conn {
+func (e *UTLSClientConfig) Client(conn net.Conn) Conn {
 	return &utlsConnWrapper{utls.UClient(conn, e.config.Clone(), e.id)}
 }
 
@@ -59,7 +67,14 @@ func (c *utlsConnWrapper) ConnectionState() tls.ConnectionState {
 	}
 }
 
-func newUTLSClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
+func (e *UTLSClientConfig) Clone() Config {
+	return &UTLSClientConfig{
+		config: e.config.Clone(),
+		id:     e.id,
+	}
+}
+
+func NewUTLSClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
 	var serverName string
 	if options.ServerName != "" {
 		serverName = options.ServerName
@@ -152,5 +167,5 @@ func newUTLSClient(router adapter.Router, serverAddress string, options option.O
 	default:
 		return nil, E.New("unknown uTLS fingerprint: ", options.UTLS.Fingerprint)
 	}
-	return &utlsClientConfig{&tlsConfig, id}, nil
+	return &UTLSClientConfig{&tlsConfig, id}, nil
 }

+ 1 - 1
common/tls/utls_stub.go

@@ -8,6 +8,6 @@ import (
 	E "github.com/sagernet/sing/common/exceptions"
 )
 
-func newUTLSClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
+func NewUTLSClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
 	return nil, E.New(`uTLS is not included in this build, rebuild with -tags with_utls`)
 }

+ 1 - 1
include/quic_stub.go

@@ -23,7 +23,7 @@ func init() {
 		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) {
+		func(ctx context.Context, options option.V2RayQUICOptions, tlsConfig tls.ServerConfig, 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) {

+ 4 - 4
test/go.mod

@@ -61,13 +61,13 @@ require (
 	github.com/sagernet/cloudflare-tls v0.0.0-20221031050923-d70792f4c3a0 // indirect
 	github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 // indirect
 	github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 // indirect
-	github.com/sagernet/quic-go v0.0.0-20221031051350-29d8bb1c8127 // indirect
+	github.com/sagernet/quic-go v0.0.0-20221108053023-645bcc4f9b15 // indirect
 	github.com/sagernet/sing-dns v0.0.0-20221031055845-7de76401d403 // indirect
 	github.com/sagernet/sing-tun v0.0.0-20221104121441-66c48a57776f // indirect
-	github.com/sagernet/sing-vmess v0.0.0-20220925083655-063bc85ea685 // indirect
+	github.com/sagernet/sing-vmess v0.0.0-20221109021549-b446d5bdddf0 // indirect
 	github.com/sagernet/smux v0.0.0-20220831015742-e0f1988e3195 // indirect
 	github.com/sagernet/websocket v0.0.0-20220913015213-615516348b4e // indirect
-	github.com/sagernet/wireguard-go v0.0.0-20221107074148-ffff5ffac938 // indirect
+	github.com/sagernet/wireguard-go v0.0.0-20221108054404-7c2acadba17c // indirect
 	github.com/sirupsen/logrus v1.9.0 // indirect
 	github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect
 	go.etcd.io/bbolt v1.3.6 // indirect
@@ -78,7 +78,7 @@ require (
 	golang.org/x/crypto v0.1.1-0.20221024173537-a3485e174077 // indirect
 	golang.org/x/exp v0.0.0-20221028150844-83b7d23a625f // indirect
 	golang.org/x/mod v0.6.0 // indirect
-	golang.org/x/sys v0.1.1-0.20221102194838-fc697a31fa06 // indirect
+	golang.org/x/sys v0.2.0 // indirect
 	golang.org/x/text v0.4.0 // indirect
 	golang.org/x/time v0.0.0-20191024005414-555d28b269f0 // indirect
 	golang.org/x/tools v0.2.0 // indirect

+ 8 - 8
test/go.sum

@@ -146,8 +146,8 @@ github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 h1:5+m7c
 github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61/go.mod h1:QUQ4RRHD6hGGHdFMEtR8T2P6GS6R3D/CXKdaYHKKXms=
 github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 h1:iL5gZI3uFp0X6EslacyapiRz7LLSJyr4RajF/BhMVyE=
 github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
-github.com/sagernet/quic-go v0.0.0-20221031051350-29d8bb1c8127 h1:rraPfWlUy2cdZ61FLXRCFbL0lb7oocScbr4Ac0rIzTU=
-github.com/sagernet/quic-go v0.0.0-20221031051350-29d8bb1c8127/go.mod h1:oWFbojDMm85/Jbm/fyWoo8Pux6dIssxGi3q1r+5642A=
+github.com/sagernet/quic-go v0.0.0-20221108053023-645bcc4f9b15 h1:l8RQTjz5LlGEFOc49dXAr14ORbj8mTW7nX88Rbm+FiY=
+github.com/sagernet/quic-go v0.0.0-20221108053023-645bcc4f9b15/go.mod h1:oWFbojDMm85/Jbm/fyWoo8Pux6dIssxGi3q1r+5642A=
 github.com/sagernet/sing v0.0.0-20220812082120-05f9836bff8f/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY=
 github.com/sagernet/sing v0.0.0-20220817130738-ce854cda8522/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY=
 github.com/sagernet/sing v0.0.0-20221008120626-60a9910eefe4 h1:LO7xMvMGhYmjQg2vjhTzsODyzs9/WLYu5Per+/8jIeo=
@@ -158,14 +158,14 @@ github.com/sagernet/sing-shadowsocks v0.0.0-20220819002358-7461bb09a8f6 h1:JJfDe
 github.com/sagernet/sing-shadowsocks v0.0.0-20220819002358-7461bb09a8f6/go.mod h1:EX3RbZvrwAkPI2nuGa78T2iQXmrkT+/VQtskjou42xM=
 github.com/sagernet/sing-tun v0.0.0-20221104121441-66c48a57776f h1:CXF+nErOb9f7qiHingSgTa2/lJAgmEFtAQ47oVwdRGU=
 github.com/sagernet/sing-tun v0.0.0-20221104121441-66c48a57776f/go.mod h1:1u3pjXA9HmH7kRiBJqM3C/zPxrxnCLd3svmqtub/RFU=
-github.com/sagernet/sing-vmess v0.0.0-20220925083655-063bc85ea685 h1:AZzFNRR/ZwMTceUQ1b/mxx6oyKqmFymdMn/yleJmoVM=
-github.com/sagernet/sing-vmess v0.0.0-20220925083655-063bc85ea685/go.mod h1:bwhAdSNET1X+j9DOXGj9NIQR39xgcWIk1rOQ9lLD+gM=
+github.com/sagernet/sing-vmess v0.0.0-20221109021549-b446d5bdddf0 h1:z3kuD3hPNdEq7/wVy5lwE21f+8ZTazBtR81qswxJoCc=
+github.com/sagernet/sing-vmess v0.0.0-20221109021549-b446d5bdddf0/go.mod h1:bwhAdSNET1X+j9DOXGj9NIQR39xgcWIk1rOQ9lLD+gM=
 github.com/sagernet/smux v0.0.0-20220831015742-e0f1988e3195 h1:5VBIbVw9q7aKbrFdT83mjkyvQ+VaRsQ6yflTepfln38=
 github.com/sagernet/smux v0.0.0-20220831015742-e0f1988e3195/go.mod h1:yedWtra8nyGJ+SyI+ziwuaGMzBatbB10P1IOOZbbSK8=
 github.com/sagernet/websocket v0.0.0-20220913015213-615516348b4e h1:7uw2njHFGE+VpWamge6o56j2RWk4omF6uLKKxMmcWvs=
 github.com/sagernet/websocket v0.0.0-20220913015213-615516348b4e/go.mod h1:45TUl8+gH4SIKr4ykREbxKWTxkDlSzFENzctB1dVRRY=
-github.com/sagernet/wireguard-go v0.0.0-20221107074148-ffff5ffac938 h1:QBeUPiA35gP6XqSUXCdwfDfWjhBRV2m0+mtXNUzLpZ0=
-github.com/sagernet/wireguard-go v0.0.0-20221107074148-ffff5ffac938/go.mod h1:euOmN6O5kk9dQmgSS8Df4psAl3TCjxOz0NW60EWkSaI=
+github.com/sagernet/wireguard-go v0.0.0-20221108054404-7c2acadba17c h1:qP3ZOHnjZalvqbjundbXiv/YrNlo3HOgrKc+S1QGs0U=
+github.com/sagernet/wireguard-go v0.0.0-20221108054404-7c2acadba17c/go.mod h1:euOmN6O5kk9dQmgSS8Df4psAl3TCjxOz0NW60EWkSaI=
 github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
 github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
 github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
@@ -278,8 +278,8 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220731174439-a90be440212d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.1.1-0.20221102194838-fc697a31fa06 h1:E1pm64FqQa4v8dHd/bAneyMkR4hk8LTJhoSlc5mc1cM=
-golang.org/x/sys v0.1.1-0.20221102194838-fc697a31fa06/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A=
+golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.1.0 h1:g6Z6vPFA9dYBAF7DWcH6sCcOntplXsDKcliusYijMlw=

+ 1 - 1
transport/v2ray/grpc.go

@@ -15,7 +15,7 @@ import (
 	N "github.com/sagernet/sing/common/network"
 )
 
-func NewGRPCServer(ctx context.Context, options option.V2RayGRPCOptions, tlsConfig tls.Config, handler N.TCPConnectionHandler, errorHandler E.Handler) (adapter.V2RayServerTransport, error) {
+func NewGRPCServer(ctx context.Context, options option.V2RayGRPCOptions, tlsConfig tls.ServerConfig, handler N.TCPConnectionHandler, errorHandler E.Handler) (adapter.V2RayServerTransport, error) {
 	if options.ForceLite {
 		return v2raygrpclite.NewServer(ctx, options, tlsConfig, handler, errorHandler)
 	}

+ 1 - 1
transport/v2ray/grpc_lite.go

@@ -14,7 +14,7 @@ import (
 	N "github.com/sagernet/sing/common/network"
 )
 
-func NewGRPCServer(ctx context.Context, options option.V2RayGRPCOptions, tlsConfig tls.Config, handler N.TCPConnectionHandler, errorHandler E.Handler) (adapter.V2RayServerTransport, error) {
+func NewGRPCServer(ctx context.Context, options option.V2RayGRPCOptions, tlsConfig tls.ServerConfig, handler N.TCPConnectionHandler, errorHandler E.Handler) (adapter.V2RayServerTransport, error) {
 	return v2raygrpclite.NewServer(ctx, options, tlsConfig, handler, errorHandler)
 }
 

+ 1 - 1
transport/v2ray/quic.go

@@ -22,7 +22,7 @@ func RegisterQUICConstructor(server ServerConstructor[option.V2RayQUICOptions],
 	quicClientConstructor = client
 }
 
-func NewQUICServer(ctx context.Context, options option.V2RayQUICOptions, tlsConfig tls.Config, handler N.TCPConnectionHandler, errorHandler E.Handler) (adapter.V2RayServerTransport, error) {
+func NewQUICServer(ctx context.Context, options option.V2RayQUICOptions, tlsConfig tls.ServerConfig, handler N.TCPConnectionHandler, errorHandler E.Handler) (adapter.V2RayServerTransport, error) {
 	if quicServerConstructor == nil {
 		return nil, os.ErrInvalid
 	}

+ 2 - 2
transport/v2ray/transport.go

@@ -15,11 +15,11 @@ import (
 )
 
 type (
-	ServerConstructor[O any] func(ctx context.Context, options O, tlsConfig tls.Config, handler N.TCPConnectionHandler, errorHandler E.Handler) (adapter.V2RayServerTransport, error)
+	ServerConstructor[O any] func(ctx context.Context, options O, tlsConfig tls.ServerConfig, 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) {
+func NewServerTransport(ctx context.Context, options option.V2RayTransportOptions, tlsConfig tls.ServerConfig, handler N.TCPConnectionHandler, errorHandler E.Handler) (adapter.V2RayServerTransport, error) {
 	if options.Type == "" {
 		return nil, nil
 	}

+ 1 - 6
transport/v2raygrpc/client.go

@@ -16,7 +16,6 @@ import (
 	"google.golang.org/grpc"
 	"google.golang.org/grpc/backoff"
 	"google.golang.org/grpc/connectivity"
-	"google.golang.org/grpc/credentials"
 	"google.golang.org/grpc/credentials/insecure"
 )
 
@@ -35,11 +34,7 @@ type Client struct {
 func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, options option.V2RayGRPCOptions, tlsConfig tls.Config) (adapter.V2RayClientTransport, error) {
 	var dialOptions []grpc.DialOption
 	if tlsConfig != nil {
-		stdConfig, err := tlsConfig.Config()
-		if err != nil {
-			return nil, err
-		}
-		dialOptions = append(dialOptions, grpc.WithTransportCredentials(credentials.NewTLS(stdConfig)))
+		dialOptions = append(dialOptions, grpc.WithTransportCredentials(NewTLSTransportCredentials(tlsConfig)))
 	} else {
 		dialOptions = append(dialOptions, grpc.WithTransportCredentials(insecure.NewCredentials()))
 	}

+ 49 - 0
transport/v2raygrpc/credentials/credentials.go

@@ -0,0 +1,49 @@
+/*
+ * Copyright 2021 gRPC authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package credentials
+
+import (
+	"context"
+)
+
+// requestInfoKey is a struct to be used as the key to store RequestInfo in a
+// context.
+type requestInfoKey struct{}
+
+// NewRequestInfoContext creates a context with ri.
+func NewRequestInfoContext(ctx context.Context, ri interface{}) context.Context {
+	return context.WithValue(ctx, requestInfoKey{}, ri)
+}
+
+// RequestInfoFromContext extracts the RequestInfo from ctx.
+func RequestInfoFromContext(ctx context.Context) interface{} {
+	return ctx.Value(requestInfoKey{})
+}
+
+// clientHandshakeInfoKey is a struct used as the key to store
+// ClientHandshakeInfo in a context.
+type clientHandshakeInfoKey struct{}
+
+// ClientHandshakeInfoFromContext extracts the ClientHandshakeInfo from ctx.
+func ClientHandshakeInfoFromContext(ctx context.Context) interface{} {
+	return ctx.Value(clientHandshakeInfoKey{})
+}
+
+// NewClientHandshakeInfoContext creates a context with chi.
+func NewClientHandshakeInfoContext(ctx context.Context, chi interface{}) context.Context {
+	return context.WithValue(ctx, clientHandshakeInfoKey{}, chi)
+}

+ 75 - 0
transport/v2raygrpc/credentials/spiffe.go

@@ -0,0 +1,75 @@
+/*
+ *
+ * Copyright 2020 gRPC authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+// Package credentials defines APIs for parsing SPIFFE ID.
+//
+// All APIs in this package are experimental.
+package credentials
+
+import (
+	"crypto/tls"
+	"crypto/x509"
+	"net/url"
+
+	"google.golang.org/grpc/grpclog"
+)
+
+var logger = grpclog.Component("credentials")
+
+// SPIFFEIDFromState parses the SPIFFE ID from State. If the SPIFFE ID format
+// is invalid, return nil with warning.
+func SPIFFEIDFromState(state tls.ConnectionState) *url.URL {
+	if len(state.PeerCertificates) == 0 || len(state.PeerCertificates[0].URIs) == 0 {
+		return nil
+	}
+	return SPIFFEIDFromCert(state.PeerCertificates[0])
+}
+
+// SPIFFEIDFromCert parses the SPIFFE ID from x509.Certificate. If the SPIFFE
+// ID format is invalid, return nil with warning.
+func SPIFFEIDFromCert(cert *x509.Certificate) *url.URL {
+	if cert == nil || cert.URIs == nil {
+		return nil
+	}
+	var spiffeID *url.URL
+	for _, uri := range cert.URIs {
+		if uri == nil || uri.Scheme != "spiffe" || uri.Opaque != "" || (uri.User != nil && uri.User.Username() != "") {
+			continue
+		}
+		// From this point, we assume the uri is intended for a SPIFFE ID.
+		if len(uri.String()) > 2048 {
+			logger.Warning("invalid SPIFFE ID: total ID length larger than 2048 bytes")
+			return nil
+		}
+		if len(uri.Host) == 0 || len(uri.Path) == 0 {
+			logger.Warning("invalid SPIFFE ID: domain or workload ID is empty")
+			return nil
+		}
+		if len(uri.Host) > 255 {
+			logger.Warning("invalid SPIFFE ID: domain length larger than 255 characters")
+			return nil
+		}
+		// A valid SPIFFE certificate can only have exactly one URI SAN field.
+		if len(cert.URIs) > 1 {
+			logger.Warning("invalid SPIFFE ID: multiple URI SANs")
+			return nil
+		}
+		spiffeID = uri
+	}
+	return spiffeID
+}

+ 58 - 0
transport/v2raygrpc/credentials/syscallconn.go

@@ -0,0 +1,58 @@
+/*
+ *
+ * Copyright 2018 gRPC authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package credentials
+
+import (
+	"net"
+	"syscall"
+)
+
+type sysConn = syscall.Conn
+
+// syscallConn keeps reference of rawConn to support syscall.Conn for channelz.
+// SyscallConn() (the method in interface syscall.Conn) is explicitly
+// implemented on this type,
+//
+// Interface syscall.Conn is implemented by most net.Conn implementations (e.g.
+// TCPConn, UnixConn), but is not part of net.Conn interface. So wrapper conns
+// that embed net.Conn don't implement syscall.Conn. (Side note: tls.Conn
+// doesn't embed net.Conn, so even if syscall.Conn is part of net.Conn, it won't
+// help here).
+type syscallConn struct {
+	net.Conn
+	// sysConn is a type alias of syscall.Conn. It's necessary because the name
+	// `Conn` collides with `net.Conn`.
+	sysConn
+}
+
+// WrapSyscallConn tries to wrap rawConn and newConn into a net.Conn that
+// implements syscall.Conn. rawConn will be used to support syscall, and newConn
+// will be used for read/write.
+//
+// This function returns newConn if rawConn doesn't implement syscall.Conn.
+func WrapSyscallConn(rawConn, newConn net.Conn) net.Conn {
+	sysConn, ok := rawConn.(syscall.Conn)
+	if !ok {
+		return newConn
+	}
+	return &syscallConn{
+		Conn:    newConn,
+		sysConn: sysConn,
+	}
+}

+ 52 - 0
transport/v2raygrpc/credentials/util.go

@@ -0,0 +1,52 @@
+/*
+ *
+ * Copyright 2020 gRPC authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package credentials
+
+import (
+	"crypto/tls"
+)
+
+const alpnProtoStrH2 = "h2"
+
+// AppendH2ToNextProtos appends h2 to next protos.
+func AppendH2ToNextProtos(ps []string) []string {
+	for _, p := range ps {
+		if p == alpnProtoStrH2 {
+			return ps
+		}
+	}
+	ret := make([]string, 0, len(ps)+1)
+	ret = append(ret, ps...)
+	return append(ret, alpnProtoStrH2)
+}
+
+// CloneTLSConfig returns a shallow clone of the exported
+// fields of cfg, ignoring the unexported sync.Once, which
+// contains a mutex and must not be copied.
+//
+// If cfg is nil, a new zero tls.Config is returned.
+//
+// TODO: inline this function if possible.
+func CloneTLSConfig(cfg *tls.Config) *tls.Config {
+	if cfg == nil {
+		return &tls.Config{}
+	}
+
+	return cfg.Clone()
+}

+ 3 - 8
transport/v2raygrpc/server.go

@@ -13,7 +13,6 @@ import (
 	N "github.com/sagernet/sing/common/network"
 
 	"google.golang.org/grpc"
-	"google.golang.org/grpc/credentials"
 	gM "google.golang.org/grpc/metadata"
 	"google.golang.org/grpc/peer"
 )
@@ -26,15 +25,11 @@ type Server struct {
 	server  *grpc.Server
 }
 
-func NewServer(ctx context.Context, options option.V2RayGRPCOptions, tlsConfig tls.Config, handler N.TCPConnectionHandler) (*Server, error) {
+func NewServer(ctx context.Context, options option.V2RayGRPCOptions, tlsConfig tls.ServerConfig, handler N.TCPConnectionHandler) (*Server, error) {
 	var serverOptions []grpc.ServerOption
 	if tlsConfig != nil {
-		stdConfig, err := tlsConfig.Config()
-		if err != nil {
-			return nil, err
-		}
-		stdConfig.NextProtos = []string{"h2"}
-		serverOptions = append(serverOptions, grpc.Creds(credentials.NewTLS(stdConfig)))
+		tlsConfig.SetNextProtos([]string{"h2"})
+		serverOptions = append(serverOptions, grpc.Creds(NewTLSTransportCredentials(tlsConfig)))
 	}
 	server := &Server{ctx, handler, grpc.NewServer(serverOptions...)}
 	RegisterGunServiceCustomNameServer(server.server, server, options.ServiceName)

+ 86 - 0
transport/v2raygrpc/tls_credentials.go

@@ -0,0 +1,86 @@
+package v2raygrpc
+
+import (
+	"context"
+	"net"
+	"os"
+
+	"github.com/sagernet/sing-box/common/tls"
+	internal_credentials "github.com/sagernet/sing-box/transport/v2raygrpc/credentials"
+
+	"google.golang.org/grpc/credentials"
+)
+
+type TLSTransportCredentials struct {
+	config tls.Config
+}
+
+func NewTLSTransportCredentials(config tls.Config) credentials.TransportCredentials {
+	return &TLSTransportCredentials{config}
+}
+
+func (c *TLSTransportCredentials) Info() credentials.ProtocolInfo {
+	return credentials.ProtocolInfo{
+		SecurityProtocol: "tls",
+		SecurityVersion:  "1.2",
+		ServerName:       c.config.ServerName(),
+	}
+}
+
+func (c *TLSTransportCredentials) ClientHandshake(ctx context.Context, authority string, rawConn net.Conn) (net.Conn, credentials.AuthInfo, error) {
+	cfg := c.config.Clone()
+	if cfg.ServerName() == "" {
+		serverName, _, err := net.SplitHostPort(authority)
+		if err != nil {
+			serverName = authority
+		}
+		cfg.SetServerName(serverName)
+	}
+	conn, err := tls.ClientHandshake(ctx, rawConn, cfg)
+	if err != nil {
+		return nil, nil, err
+	}
+	tlsInfo := credentials.TLSInfo{
+		State: conn.ConnectionState(),
+		CommonAuthInfo: credentials.CommonAuthInfo{
+			SecurityLevel: credentials.PrivacyAndIntegrity,
+		},
+	}
+	id := internal_credentials.SPIFFEIDFromState(conn.ConnectionState())
+	if id != nil {
+		tlsInfo.SPIFFEID = id
+	}
+	return internal_credentials.WrapSyscallConn(rawConn, conn), tlsInfo, nil
+}
+
+func (c *TLSTransportCredentials) ServerHandshake(rawConn net.Conn) (net.Conn, credentials.AuthInfo, error) {
+	serverConfig, isServer := c.config.(tls.ServerConfig)
+	if !isServer {
+		return nil, nil, os.ErrInvalid
+	}
+	conn, err := tls.ServerHandshake(context.Background(), rawConn, serverConfig)
+	if err != nil {
+		rawConn.Close()
+		return nil, nil, err
+	}
+	tlsInfo := credentials.TLSInfo{
+		State: conn.ConnectionState(),
+		CommonAuthInfo: credentials.CommonAuthInfo{
+			SecurityLevel: credentials.PrivacyAndIntegrity,
+		},
+	}
+	id := internal_credentials.SPIFFEIDFromState(conn.ConnectionState())
+	if id != nil {
+		tlsInfo.SPIFFEID = id
+	}
+	return internal_credentials.WrapSyscallConn(rawConn, conn), tlsInfo, nil
+}
+
+func (c *TLSTransportCredentials) Clone() credentials.TransportCredentials {
+	return NewTLSTransportCredentials(c.config)
+}
+
+func (c *TLSTransportCredentials) OverrideServerName(serverNameOverride string) error {
+	c.config.SetServerName(serverNameOverride)
+	return nil
+}

+ 1 - 1
transport/v2raygrpclite/server.go

@@ -34,7 +34,7 @@ func (s *Server) Network() []string {
 	return []string{N.NetworkTCP}
 }
 
-func NewServer(ctx context.Context, options option.V2RayGRPCOptions, tlsConfig tls.Config, handler N.TCPConnectionHandler, errorHandler E.Handler) (*Server, error) {
+func NewServer(ctx context.Context, options option.V2RayGRPCOptions, tlsConfig tls.ServerConfig, handler N.TCPConnectionHandler, errorHandler E.Handler) (*Server, error) {
 	server := &Server{
 		handler:      handler,
 		errorHandler: errorHandler,

+ 1 - 1
transport/v2rayhttp/server.go

@@ -37,7 +37,7 @@ func (s *Server) Network() []string {
 	return []string{N.NetworkTCP}
 }
 
-func NewServer(ctx context.Context, options option.V2RayHTTPOptions, tlsConfig tls.Config, handler N.TCPConnectionHandler, errorHandler E.Handler) (*Server, error) {
+func NewServer(ctx context.Context, options option.V2RayHTTPOptions, tlsConfig tls.ServerConfig, handler N.TCPConnectionHandler, errorHandler E.Handler) (*Server, error) {
 	server := &Server{
 		ctx:          ctx,
 		handler:      handler,

+ 1 - 1
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) (adapter.V2RayServerTransport, error) {
+func NewServer(ctx context.Context, options option.V2RayQUICOptions, tlsConfig tls.ServerConfig, handler N.TCPConnectionHandler, errorHandler E.Handler) (adapter.V2RayServerTransport, error) {
 	quicConfig := &quic.Config{
 		DisablePathMTUDiscovery: !C.IsLinux && !C.IsWindows,
 	}

+ 1 - 1
transport/v2raywebsocket/server.go

@@ -34,7 +34,7 @@ type Server struct {
 	earlyDataHeaderName string
 }
 
-func NewServer(ctx context.Context, options option.V2RayWebsocketOptions, tlsConfig tls.Config, handler N.TCPConnectionHandler, errorHandler E.Handler) (*Server, error) {
+func NewServer(ctx context.Context, options option.V2RayWebsocketOptions, tlsConfig tls.ServerConfig, handler N.TCPConnectionHandler, errorHandler E.Handler) (*Server, error) {
 	server := &Server{
 		ctx:                 ctx,
 		handler:             handler,