diff --git a/common/tls/client.go b/common/tls/client.go index 28826124..a0dd288d 100644 --- a/common/tls/client.go +++ b/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) } } diff --git a/common/tls/config.go b/common/tls/config.go index 8777c82f..0885f6c5 100644 --- a/common/tls/config.go +++ b/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 { diff --git a/common/tls/ech_client.go b/common/tls/ech_client.go index 9dc962f8..97903caa 100644 --- a/common/tls/ech_client.go +++ b/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) { diff --git a/common/tls/ech_stub.go b/common/tls/ech_stub.go index 785e401f..a24a5ff7 100644 --- a/common/tls/ech_stub.go +++ b/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`) } diff --git a/common/tls/server.go b/common/tls/server.go index f8044199..4a17b3f7 100644 --- a/common/tls/server.go +++ b/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) { diff --git a/common/tls/std_client.go b/common/tls/std_client.go index 1a7c57e1..571602f3 100644 --- a/common/tls/std_client.go +++ b/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 } diff --git a/common/tls/std_server.go b/common/tls/std_server.go index 3734cbf2..03730952 100644 --- a/common/tls/std_server.go +++ b/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,9 +44,119 @@ func (c *STDServerConfig) SetNextProtos(nextProto []string) { c.config.NextProtos = nextProto } -var errInsecureUnused = E.New("tls: insecure unused") +func (c *STDServerConfig) Config() (*STDConfig, error) { + return c.config, nil +} -func newSTDServer(ctx context.Context, logger log.Logger, options option.InboundTLSOptions) (ServerConfig, error) { +func (c *STDServerConfig) Client(conn net.Conn) Conn { + return tls.Client(conn, c.config) +} + +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() + } else { + if c.certificatePath == "" && c.keyPath == "" { + return nil + } + err := c.startWatcher() + if err != nil { + c.logger.Warn("create fsnotify watcher: ", err) + } + return nil + } +} + +func (c *STDServerConfig) startWatcher() error { + watcher, err := fsnotify.NewWatcher() + if err != nil { + return err + } + if c.certificatePath != "" { + err = watcher.Add(c.certificatePath) + if err != nil { + return err + } + } + if c.keyPath != "" { + err = watcher.Add(c.keyPath) + if err != nil { + return err + } + } + c.watcher = watcher + go c.loopUpdate() + return nil +} + +func (c *STDServerConfig) loopUpdate() { + for { + select { + case event, ok := <-c.watcher.Events: + if !ok { + return + } + if event.Op&fsnotify.Write != fsnotify.Write { + continue + } + err := c.reloadKeyPair() + if err != nil { + c.logger.Error(E.Cause(err, "reload TLS key pair")) + } + case err, ok := <-c.watcher.Errors: + if !ok { + return + } + c.logger.Error(E.Cause(err, "fsnotify error")) + } + } +} + +func (c *STDServerConfig) reloadKeyPair() error { + if c.certificatePath != "" { + certificate, err := os.ReadFile(c.certificatePath) + if err != nil { + return E.Cause(err, "reload certificate from ", c.certificatePath) + } + c.certificate = certificate + } + if c.keyPath != "" { + key, err := os.ReadFile(c.keyPath) + if err != nil { + return E.Cause(err, "reload key from ", c.keyPath) + } + c.key = key + } + keyPair, err := tls.X509KeyPair(c.certificate, c.key) + if err != nil { + return E.Cause(err, "reload key pair") + } + c.config.Certificates = []tls.Certificate{keyPair} + c.logger.Info("reloaded TLS certificate") + return nil +} + +func (c *STDServerConfig) Close() error { + if c.acmeService != nil { + return c.acmeService.Close() + } + if c.watcher != nil { + return c.watcher.Close() + } + return nil +} + +func NewSTDServer(ctx context.Context, logger log.Logger, options option.InboundTLSOptions) (ServerConfig, error) { if !options.Enabled { return nil, nil } @@ -136,109 +256,3 @@ func newSTDServer(ctx context.Context, logger log.Logger, options option.Inbound keyPath: options.KeyPath, }, nil } - -func (c *STDServerConfig) Config() (*STDConfig, error) { - return c.config, nil -} - -func (c *STDServerConfig) Client(conn net.Conn) Conn { - return tls.Client(conn, c.config) -} - -func (c *STDServerConfig) Server(conn net.Conn) Conn { - return tls.Server(conn, c.config) -} - -func (c *STDServerConfig) Start() error { - if c.acmeService != nil { - return c.acmeService.Start() - } else { - if c.certificatePath == "" && c.keyPath == "" { - return nil - } - err := c.startWatcher() - if err != nil { - c.logger.Warn("create fsnotify watcher: ", err) - } - return nil - } -} - -func (c *STDServerConfig) startWatcher() error { - watcher, err := fsnotify.NewWatcher() - if err != nil { - return err - } - if c.certificatePath != "" { - err = watcher.Add(c.certificatePath) - if err != nil { - return err - } - } - if c.keyPath != "" { - err = watcher.Add(c.keyPath) - if err != nil { - return err - } - } - c.watcher = watcher - go c.loopUpdate() - return nil -} - -func (c *STDServerConfig) loopUpdate() { - for { - select { - case event, ok := <-c.watcher.Events: - if !ok { - return - } - if event.Op&fsnotify.Write != fsnotify.Write { - continue - } - err := c.reloadKeyPair() - if err != nil { - c.logger.Error(E.Cause(err, "reload TLS key pair")) - } - case err, ok := <-c.watcher.Errors: - if !ok { - return - } - c.logger.Error(E.Cause(err, "fsnotify error")) - } - } -} - -func (c *STDServerConfig) reloadKeyPair() error { - if c.certificatePath != "" { - certificate, err := os.ReadFile(c.certificatePath) - if err != nil { - return E.Cause(err, "reload certificate from ", c.certificatePath) - } - c.certificate = certificate - } - if c.keyPath != "" { - key, err := os.ReadFile(c.keyPath) - if err != nil { - return E.Cause(err, "reload key from ", c.keyPath) - } - c.key = key - } - keyPair, err := tls.X509KeyPair(c.certificate, c.key) - if err != nil { - return E.Cause(err, "reload key pair") - } - c.config.Certificates = []tls.Certificate{keyPair} - c.logger.Info("reloaded TLS certificate") - return nil -} - -func (c *STDServerConfig) Close() error { - if c.acmeService != nil { - return c.acmeService.Close() - } - if c.watcher != nil { - return c.watcher.Close() - } - return nil -} diff --git a/common/tls/utls_client.go b/common/tls/utls_client.go index ba3b2824..d54dd023 100644 --- a/common/tls/utls_client.go +++ b/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 } diff --git a/common/tls/utls_stub.go b/common/tls/utls_stub.go index 035978ad..f9dbec69 100644 --- a/common/tls/utls_stub.go +++ b/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`) } diff --git a/include/quic_stub.go b/include/quic_stub.go index be314956..6557565a 100644 --- a/include/quic_stub.go +++ b/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) { diff --git a/test/go.mod b/test/go.mod index 3ab26916..a428b49c 100644 --- a/test/go.mod +++ b/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 diff --git a/test/go.sum b/test/go.sum index a96a924d..b42798e4 100644 --- a/test/go.sum +++ b/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= diff --git a/transport/v2ray/grpc.go b/transport/v2ray/grpc.go index 961c2bbb..64865c2c 100644 --- a/transport/v2ray/grpc.go +++ b/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) } diff --git a/transport/v2ray/grpc_lite.go b/transport/v2ray/grpc_lite.go index 3ea9b8b8..589e9fbe 100644 --- a/transport/v2ray/grpc_lite.go +++ b/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) } diff --git a/transport/v2ray/quic.go b/transport/v2ray/quic.go index e9a0142c..02fcd974 100644 --- a/transport/v2ray/quic.go +++ b/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 } diff --git a/transport/v2ray/transport.go b/transport/v2ray/transport.go index a8beda57..28a98d52 100644 --- a/transport/v2ray/transport.go +++ b/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 } diff --git a/transport/v2raygrpc/client.go b/transport/v2raygrpc/client.go index cc89c1df..8724a0d9 100644 --- a/transport/v2raygrpc/client.go +++ b/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())) } diff --git a/transport/v2raygrpc/credentials/credentials.go b/transport/v2raygrpc/credentials/credentials.go new file mode 100644 index 00000000..32c9b590 --- /dev/null +++ b/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) +} diff --git a/transport/v2raygrpc/credentials/spiffe.go b/transport/v2raygrpc/credentials/spiffe.go new file mode 100644 index 00000000..25ade623 --- /dev/null +++ b/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 +} diff --git a/transport/v2raygrpc/credentials/syscallconn.go b/transport/v2raygrpc/credentials/syscallconn.go new file mode 100644 index 00000000..2919632d --- /dev/null +++ b/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, + } +} diff --git a/transport/v2raygrpc/credentials/util.go b/transport/v2raygrpc/credentials/util.go new file mode 100644 index 00000000..f792fd22 --- /dev/null +++ b/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() +} diff --git a/transport/v2raygrpc/server.go b/transport/v2raygrpc/server.go index c852a9fa..66e06a91 100644 --- a/transport/v2raygrpc/server.go +++ b/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) diff --git a/transport/v2raygrpc/tls_credentials.go b/transport/v2raygrpc/tls_credentials.go new file mode 100644 index 00000000..53a3b7ab --- /dev/null +++ b/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 +} diff --git a/transport/v2raygrpclite/server.go b/transport/v2raygrpclite/server.go index c90aa43a..8c1d59b8 100644 --- a/transport/v2raygrpclite/server.go +++ b/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, diff --git a/transport/v2rayhttp/server.go b/transport/v2rayhttp/server.go index cba5c57c..3336be1e 100644 --- a/transport/v2rayhttp/server.go +++ b/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, diff --git a/transport/v2rayquic/server.go b/transport/v2rayquic/server.go index 3642d648..e9e635b4 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) (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, } diff --git a/transport/v2raywebsocket/server.go b/transport/v2raywebsocket/server.go index 32a01bf8..6f1c5146 100644 --- a/transport/v2raywebsocket/server.go +++ b/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,