mirror of
https://github.com/gravitational/teleport.git
synced 2026-10-31 20:39:37 +08:00
185 lines
5.0 KiB
Go
185 lines
5.0 KiB
Go
/*
|
|
Copyright 2020 Gravitational, Inc.
|
|
|
|
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 multiplexer
|
|
|
|
import (
|
|
"context"
|
|
"crypto/tls"
|
|
"io"
|
|
"net"
|
|
"time"
|
|
|
|
"github.com/gravitational/trace"
|
|
"github.com/jonboulle/clockwork"
|
|
log "github.com/sirupsen/logrus"
|
|
"golang.org/x/net/http2"
|
|
|
|
"github.com/gravitational/teleport"
|
|
"github.com/gravitational/teleport/lib/defaults"
|
|
"github.com/gravitational/teleport/lib/utils"
|
|
)
|
|
|
|
// TLSListenerConfig specifies listener configuration
|
|
type TLSListenerConfig struct {
|
|
// Listener is the listener returning *tls.Conn
|
|
// connections on Accept
|
|
Listener net.Listener
|
|
// ID is an identifier used for debugging purposes
|
|
ID string
|
|
// ReadDeadline is a connection read deadline during the TLS handshake (start
|
|
// of the connection). It is set to defaults.HandshakeReadDeadline if
|
|
// unspecified.
|
|
ReadDeadline time.Duration
|
|
// Clock is a clock to override in tests, set to real time clock
|
|
// by default
|
|
Clock clockwork.Clock
|
|
}
|
|
|
|
// CheckAndSetDefaults verifies configuration and sets defaults
|
|
func (c *TLSListenerConfig) CheckAndSetDefaults() error {
|
|
if c.Listener == nil {
|
|
return trace.BadParameter("missing parameter Listener")
|
|
}
|
|
if c.ReadDeadline == 0 {
|
|
c.ReadDeadline = defaults.HandshakeReadDeadline
|
|
}
|
|
if c.Clock == nil {
|
|
c.Clock = clockwork.NewRealClock()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// NewTLSListener returns a new TLS listener
|
|
func NewTLSListener(cfg TLSListenerConfig) (*TLSListener, error) {
|
|
if err := cfg.CheckAndSetDefaults(); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
context, cancel := context.WithCancel(context.TODO())
|
|
return &TLSListener{
|
|
log: log.WithFields(log.Fields{
|
|
trace.Component: teleport.Component("mxtls", cfg.ID),
|
|
}),
|
|
cfg: cfg,
|
|
http2Listener: newListener(context, cfg.Listener.Addr()),
|
|
httpListener: newListener(context, cfg.Listener.Addr()),
|
|
cancel: cancel,
|
|
context: context,
|
|
}, nil
|
|
}
|
|
|
|
// TLSListener wraps tls.Listener and detects negotiated protocol
|
|
// (assuming it's either http/1.1 or http/2)
|
|
// and forwards the appropriate responses to either HTTP/1.1 or HTTP/2
|
|
// listeners
|
|
type TLSListener struct {
|
|
log *log.Entry
|
|
cfg TLSListenerConfig
|
|
http2Listener *Listener
|
|
httpListener *Listener
|
|
cancel context.CancelFunc
|
|
context context.Context
|
|
}
|
|
|
|
// HTTP2 returns HTTP2 listener
|
|
func (l *TLSListener) HTTP2() net.Listener {
|
|
return l.http2Listener
|
|
}
|
|
|
|
// HTTP returns HTTP listener
|
|
func (l *TLSListener) HTTP() net.Listener {
|
|
return l.httpListener
|
|
}
|
|
|
|
// Serve accepts and forwards tls.Conn connections
|
|
func (l *TLSListener) Serve() error {
|
|
for {
|
|
conn, err := l.cfg.Listener.Accept()
|
|
if err == nil {
|
|
tlsConn, ok := conn.(*tls.Conn)
|
|
if !ok {
|
|
conn.Close()
|
|
log.Errorf("Expected tls.Conn, got %T, internal usage error.", conn)
|
|
continue
|
|
}
|
|
go l.detectAndForward(tlsConn)
|
|
continue
|
|
}
|
|
if utils.IsUseOfClosedNetworkError(err) {
|
|
<-l.context.Done()
|
|
return trace.Wrap(err, "listener is closed")
|
|
}
|
|
select {
|
|
case <-l.context.Done():
|
|
return trace.Wrap(net.ErrClosed, "listener is closed")
|
|
case <-time.After(5 * time.Second):
|
|
}
|
|
}
|
|
}
|
|
|
|
func (l *TLSListener) detectAndForward(conn *tls.Conn) {
|
|
err := conn.SetReadDeadline(l.cfg.Clock.Now().Add(l.cfg.ReadDeadline))
|
|
if err != nil {
|
|
l.log.WithError(err).Debugf("Failed to set connection deadline.")
|
|
conn.Close()
|
|
return
|
|
}
|
|
|
|
start := l.cfg.Clock.Now()
|
|
if err := conn.Handshake(); err != nil {
|
|
if trace.Unwrap(err) != io.EOF {
|
|
l.log.WithError(err).Warning("Handshake failed.")
|
|
}
|
|
conn.Close()
|
|
return
|
|
}
|
|
|
|
// Log warning if TLS handshake takes more than one second to help debug
|
|
// latency issues.
|
|
if elapsed := time.Since(start); elapsed > 1*time.Second {
|
|
l.log.Warnf("Slow TLS handshake from %v, took %v.", conn.RemoteAddr(), time.Since(start))
|
|
}
|
|
|
|
err = conn.SetReadDeadline(time.Time{})
|
|
if err != nil {
|
|
l.log.WithError(err).Warning("Failed to reset read deadline")
|
|
conn.Close()
|
|
return
|
|
}
|
|
|
|
switch conn.ConnectionState().NegotiatedProtocol {
|
|
case http2.NextProtoTLS:
|
|
l.http2Listener.HandleConnection(l.context, conn)
|
|
case teleport.HTTPNextProtoTLS, "":
|
|
l.httpListener.HandleConnection(l.context, conn)
|
|
default:
|
|
conn.Close()
|
|
l.log.WithError(err).Errorf("unsupported protocol: %v", conn.ConnectionState().NegotiatedProtocol)
|
|
}
|
|
}
|
|
|
|
// Close closes the listener.
|
|
// Any blocked Accept operations will be unblocked and return errors.
|
|
func (l *TLSListener) Close() error {
|
|
defer l.cancel()
|
|
return l.cfg.Listener.Close()
|
|
}
|
|
|
|
// Addr returns the listener's network address.
|
|
func (l *TLSListener) Addr() net.Addr {
|
|
return l.cfg.Listener.Addr()
|
|
}
|