github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/pkg/tcpserver/tcp_server.go (about) 1 // Copyright 2021 PingCAP, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 package tcpserver 15 16 import ( 17 "context" 18 "crypto/tls" 19 "net" 20 "strings" 21 "time" 22 23 "github.com/pingcap/errors" 24 "github.com/pingcap/log" 25 cerror "github.com/pingcap/tiflow/pkg/errors" 26 "github.com/pingcap/tiflow/pkg/security" 27 "github.com/soheilhy/cmux" 28 "go.uber.org/atomic" 29 "go.uber.org/zap" 30 "golang.org/x/sync/errgroup" 31 ) 32 33 var cmuxReadTimeout = 10 * time.Second 34 35 // TCPServer provides a muxed socket that can 36 // serve both plain HTTP and gRPC at the same time. 37 type TCPServer interface { 38 // Run runs the TCPServer. 39 // For a given instance of TCPServer, Run is expected 40 // to be called only once. 41 Run(ctx context.Context) error 42 // GrpcListener returns the gRPC listener that 43 // can be listened on by a gRPC server. 44 GrpcListener() net.Listener 45 // HTTP1Listener returns a plain HTTP listener. 46 HTTP1Listener() net.Listener 47 // IsTLSEnabled returns whether TLS has been enabled. 48 IsTLSEnabled() bool 49 // Close closed the TCPServer. 50 // The listeners returned by GrpcListener and HTTP1Listener 51 // will be closed, which will force the consumers of these 52 // listeners to stop. This provides a reliable mechanism to 53 // cancel all related components. 54 Close() error 55 } 56 57 type tcpServerImpl struct { 58 mux cmux.CMux 59 60 rootListener net.Listener 61 // grpc lisener, service as p2p gRPC server. 62 grpcListener net.Listener 63 // used for HTTP server, service for restful open API. 64 http1Listener net.Listener 65 66 isClosed atomic.Bool 67 68 credentials *security.Credential 69 isTLSEnabled bool // read only 70 } 71 72 // NewTCPServer creates a new TCPServer 73 func NewTCPServer(address string, credentials *security.Credential) (TCPServer, error) { 74 lis, err := net.Listen("tcp", address) 75 if err != nil { 76 return nil, errors.Trace(err) 77 } 78 79 server := &tcpServerImpl{ 80 credentials: credentials, 81 } 82 83 if credentials.IsTLSEnabled() { 84 tlsLis, err := wrapTLSListener(lis, credentials) 85 if err != nil { 86 return nil, errors.Trace(err) 87 } 88 server.rootListener = tlsLis 89 server.isTLSEnabled = true 90 } else { 91 server.rootListener = lis 92 } 93 94 server.mux = cmux.New(server.rootListener) 95 // We must set a read timeout for cmux, otherwise irresponsive clients 96 // may block the server from exiting. 97 // ref: https://github.com/pingcap/tidb-binlog/pull/352 98 server.mux.SetReadTimeout(cmuxReadTimeout) 99 100 server.grpcListener = server.mux.MatchWithWriters( 101 cmux.HTTP2MatchHeaderFieldSendSettings("content-type", "application/grpc")) 102 server.http1Listener = server.mux.Match(cmux.HTTP1Fast(), cmux.HTTP2()) 103 104 return server, nil 105 } 106 107 // Run runs the mux. The mux has to be running to accept connections. 108 func (s *tcpServerImpl) Run(ctx context.Context) error { 109 if s.isClosed.Load() { 110 return cerror.ErrTCPServerClosed.GenWithStackByArgs() 111 } 112 113 defer func() { 114 s.isClosed.Store(true) 115 // Closing the rootListener provides a reliable way 116 // for telling downstream components to exit. 117 _ = s.rootListener.Close() 118 }() 119 errg, ctx := errgroup.WithContext(ctx) 120 121 errg.Go(func() error { 122 err := s.mux.Serve() 123 if err == cmux.ErrServerClosed { 124 return cerror.ErrTCPServerClosed.GenWithStackByArgs() 125 } 126 if err != nil && strings.Contains(err.Error(), "use of closed network connection") { 127 return cerror.ErrTCPServerClosed.GenWithStackByArgs() 128 } 129 return errors.Trace(err) 130 }) 131 132 errg.Go(func() error { 133 <-ctx.Done() 134 log.Debug("cmux has been canceled", zap.Error(ctx.Err())) 135 s.mux.Close() 136 return nil 137 }) 138 139 return errg.Wait() 140 } 141 142 func (s *tcpServerImpl) GrpcListener() net.Listener { 143 return s.grpcListener 144 } 145 146 func (s *tcpServerImpl) HTTP1Listener() net.Listener { 147 return s.http1Listener 148 } 149 150 func (s *tcpServerImpl) IsTLSEnabled() bool { 151 return s.isTLSEnabled 152 } 153 154 func (s *tcpServerImpl) Close() error { 155 if s.isClosed.Swap(true) { 156 // ignore double closing 157 return nil 158 } 159 // Closing the rootListener provides a reliable way 160 // for telling downstream components to exit. 161 return errors.Trace(s.rootListener.Close()) 162 } 163 164 // wrapTLSListener takes a plain Listener and security credentials, 165 // and returns a listener that handles TLS connections. 166 func wrapTLSListener(inner net.Listener, credentials *security.Credential) (net.Listener, error) { 167 config, err := credentials.ToTLSConfigWithVerify() 168 if err != nil { 169 return nil, errors.Trace(err) 170 } 171 // This is a hack to make `ToTLSConfigWithVerify` work with cmux, 172 // since cmux does not support ALPN. 173 config.NextProtos = nil 174 175 return tls.NewListener(inner, config), nil 176 }