mirror of
https://github.com/traefik/traefik.git
synced 2026-06-18 19:38:23 +03:00
472 lines
14 KiB
Go
472 lines
14 KiB
Go
package tcp
|
|
|
|
import (
|
|
"bufio"
|
|
"context"
|
|
"crypto/tls"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"net/http"
|
|
"slices"
|
|
"time"
|
|
|
|
"github.com/go-acme/lego/v4/challenge/tlsalpn01"
|
|
"github.com/rs/zerolog/log"
|
|
tcpmuxer "github.com/traefik/traefik/v3/pkg/muxer/tcp"
|
|
"github.com/traefik/traefik/v3/pkg/tcp"
|
|
traefiktls "github.com/traefik/traefik/v3/pkg/tls"
|
|
)
|
|
|
|
// errClientHelloRead is used as a sentinel error to break the TLS handshake once we have read the ClientHello.
|
|
var errClientHelloRead = errors.New("client hello successfully read")
|
|
|
|
type tlsConfigWithOptionsName struct {
|
|
cfg *tls.Config
|
|
optionsName string
|
|
}
|
|
|
|
// Router is a TCP router.
|
|
type Router struct {
|
|
acmeTLSPassthrough bool
|
|
|
|
// Contains TCP routes.
|
|
muxerTCP tcpmuxer.Muxer
|
|
// Contains TCP TLS routes.
|
|
muxerTCPTLS tcpmuxer.Muxer
|
|
// Contains HTTPS routes.
|
|
muxerHTTPS tcpmuxer.Muxer
|
|
|
|
// Forwarder handlers.
|
|
// httpForwarder handles all HTTP requests.
|
|
httpForwarder tcp.Handler
|
|
// httpsForwarder handles (indirectly through muxerHTTPS, or directly) all HTTPS requests.
|
|
httpsForwarder tcp.Handler
|
|
|
|
// Neither is used directly, but they are held here, and recreated on config reload,
|
|
// so that they can be passed to the Switcher at the end of the config reload phase.
|
|
httpHandler http.Handler
|
|
httpsHandler http.Handler
|
|
|
|
// TLS configs.
|
|
httpsTLSConfig *tls.Config // default TLS config
|
|
// hostHTTPTLSConfig contains TLS configs keyed by SNI.
|
|
// A nil config is the hint to set up a brokenTLSRouter.
|
|
hostHTTPTLSConfig map[string]tlsConfigWithOptionsName // TLS configs keyed by SNI
|
|
}
|
|
|
|
// NewRouter returns a new TCP router.
|
|
func NewRouter(providersPrecedence []string) (*Router, error) {
|
|
muxTCP, err := tcpmuxer.NewMuxer(providersPrecedence)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
muxTCPTLS, err := tcpmuxer.NewMuxer(providersPrecedence)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
muxHTTPS, err := tcpmuxer.NewMuxer(providersPrecedence)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &Router{
|
|
muxerTCP: *muxTCP,
|
|
muxerTCPTLS: *muxTCPTLS,
|
|
muxerHTTPS: *muxHTTPS,
|
|
}, nil
|
|
}
|
|
|
|
// HTTP3TLSConfigMatcherFunc returns a matcher func for HTTP/3 which returns a tls.Config with its corresponding
|
|
// TLSOptionName matching the given HostSNI in the connection data, or the default TLS config if there is no match.
|
|
func (r *Router) HTTP3TLSConfigMatcherFunc() func(connData tcpmuxer.ConnData) (*tls.Config, string, error) {
|
|
return func(connData tcpmuxer.ConnData) (*tls.Config, string, error) {
|
|
h, _ := r.muxerHTTPS.Match(connData)
|
|
if h == nil {
|
|
return r.httpsTLSConfig, traefiktls.DefaultTLSConfigName, nil
|
|
}
|
|
|
|
if tlsHandler, ok := h.(*tcp.TLSHandler); ok {
|
|
return tlsHandler.Config, tlsHandler.TLSOptionsName, nil
|
|
}
|
|
|
|
return nil, "", errors.New("matching handler is not a TLSHandler")
|
|
}
|
|
}
|
|
|
|
// ServeTCP forwards the connection to the right TCP/HTTP handler.
|
|
func (r *Router) ServeTCP(conn tcp.WriteCloser) {
|
|
// Handling Non-TLS TCP connection early if there is neither HTTP(S) nor TLS routers on the entryPoint,
|
|
// and if there is at least one non-TLS TCP router.
|
|
// In the case of a non-TLS TCP client (that does not "send" first),
|
|
// we would block forever on clientHelloInfo,
|
|
// which is why we want to detect and handle that case first and foremost.
|
|
if r.muxerTCP.HasRoutes() && !r.muxerTCPTLS.HasRoutes() && !r.muxerHTTPS.HasRoutes() {
|
|
connData, err := tcpmuxer.NewConnData("", conn.RemoteAddr(), nil)
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("Error while reading TCP connection data")
|
|
conn.Close()
|
|
return
|
|
}
|
|
|
|
handler, _ := r.muxerTCP.Match(connData)
|
|
// If there is a handler matching the connection metadata,
|
|
// we let it handle the connection.
|
|
if handler != nil {
|
|
// Remove read/write deadline and delegate this to underlying TCP server.
|
|
if err := conn.SetDeadline(time.Time{}); err != nil {
|
|
log.Error().Err(err).Msg("Error while setting deadline")
|
|
}
|
|
|
|
handler.ServeTCP(conn)
|
|
return
|
|
}
|
|
// Otherwise, we keep going because:
|
|
// 1) we could be in the case where we have HTTP routers.
|
|
// 2) if it is an HTTPS request, even though we do not have any TLS routers,
|
|
// we still need to reply with a 404.
|
|
}
|
|
|
|
// TODO -- Check if ProxyProtocol changes the first bytes of the request
|
|
pConn := newPeekConn(conn)
|
|
|
|
postgres, err := isPostgres(pConn)
|
|
if err != nil {
|
|
var opErr *net.OpError
|
|
if !errors.Is(err, io.EOF) && (!errors.As(err, &opErr) || !opErr.Timeout()) {
|
|
log.Debug().Err(err).Msg("Error while peeking first bytes")
|
|
}
|
|
_ = pConn.Close()
|
|
return
|
|
}
|
|
|
|
if postgres {
|
|
if err := r.servePostgres(pConn); err != nil {
|
|
var opErr *net.OpError
|
|
if !errors.Is(err, io.EOF) && (!errors.As(err, &opErr) || !opErr.Timeout()) {
|
|
log.Debug().Err(err).Msg("Error while serving Postgres connection")
|
|
}
|
|
}
|
|
_ = pConn.Close()
|
|
return
|
|
}
|
|
|
|
hello, err := clientHelloInfo(pConn)
|
|
if err != nil {
|
|
var opErr *net.OpError
|
|
if !errors.Is(err, io.EOF) && (!errors.As(err, &opErr) || !opErr.Timeout()) {
|
|
log.Debug().Err(err).Msg("Error while reading client hello")
|
|
}
|
|
_ = pConn.Close()
|
|
return
|
|
}
|
|
|
|
// The deadline was set to avoid blocking on the initial read of the ClientHello,
|
|
// but now that we have it, we can remove it,
|
|
// and delegate this to underlying TCP server (for now only handled by HTTP Server).
|
|
if err := pConn.SetDeadline(time.Time{}); err != nil {
|
|
log.Error().Err(err).Msg("Error while setting deadline")
|
|
}
|
|
|
|
connData, err := tcpmuxer.NewConnData(hello.serverName, pConn.RemoteAddr(), hello.protos)
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("Error while reading TCP connection data")
|
|
_ = pConn.Close()
|
|
return
|
|
}
|
|
|
|
if !hello.isTLS {
|
|
handler, _ := r.muxerTCP.Match(connData)
|
|
switch {
|
|
case handler != nil:
|
|
handler.ServeTCP(pConn)
|
|
case r.httpForwarder != nil:
|
|
r.httpForwarder.ServeTCP(pConn)
|
|
default:
|
|
_ = pConn.Close()
|
|
}
|
|
return
|
|
}
|
|
|
|
// Handling ACME-TLS/1 challenges.
|
|
if !r.acmeTLSPassthrough && slices.Contains(hello.protos, tlsalpn01.ACMETLS1Protocol) {
|
|
r.acmeTLSALPNHandler().ServeTCP(pConn)
|
|
return
|
|
}
|
|
|
|
// For real, the handler eventually used for HTTPS is (almost) always the same:
|
|
// it is the httpsForwarder that is used for all HTTPS connections that match
|
|
// (which is also incidentally the same used in the last block below for 404s).
|
|
// The added value from doing Match is to find and use the specific TLS config
|
|
// (wrapped inside the returned handler) requested for the given HostSNI.
|
|
handlerHTTPS, catchAllHTTPS := r.muxerHTTPS.Match(connData)
|
|
if handlerHTTPS != nil && !catchAllHTTPS {
|
|
// In order not to depart from the behavior in 2.6,
|
|
// we only allow an HTTPS router to take precedence over a TCP-TLS router if it is _not_ an HostSNI(*) router
|
|
// (so basically any router that has a specific HostSNI based rule).
|
|
handlerHTTPS.ServeTCP(pConn)
|
|
return
|
|
}
|
|
|
|
// Contains also TCP TLS passthrough routes.
|
|
handlerTCPTLS, catchAllTCPTLS := r.muxerTCPTLS.Match(connData)
|
|
if handlerTCPTLS != nil && !catchAllTCPTLS {
|
|
handlerTCPTLS.ServeTCP(pConn)
|
|
return
|
|
}
|
|
|
|
// Fallback on HTTPS catchAll.
|
|
// We end up here for e.g. an HTTPS router that only has a PathPrefix rule,
|
|
// which under the scenes is counted as an HostSNI(*) rule.
|
|
if handlerHTTPS != nil {
|
|
handlerHTTPS.ServeTCP(pConn)
|
|
return
|
|
}
|
|
|
|
// Fallback on TCP TLS catchAll.
|
|
if handlerTCPTLS != nil {
|
|
handlerTCPTLS.ServeTCP(pConn)
|
|
return
|
|
}
|
|
|
|
// To handle 404s for HTTPS.
|
|
if r.httpsForwarder != nil {
|
|
r.httpsForwarder.ServeTCP(pConn)
|
|
return
|
|
}
|
|
|
|
_ = pConn.Close()
|
|
}
|
|
|
|
// AddTCPRoute defines a handler for the given rule.
|
|
func (r *Router) AddTCPRoute(rule string, priority int, providerName string, target tcp.Handler) error {
|
|
return r.muxerTCP.AddRoute(rule, "", priority, providerName, target)
|
|
}
|
|
|
|
// AddHTTPTLSConfig defines a handler for a given sniHost and sets the matching tlsConfig.
|
|
func (r *Router) AddHTTPTLSConfig(sniHost string, config *tls.Config, optionsName string) {
|
|
if r.hostHTTPTLSConfig == nil {
|
|
r.hostHTTPTLSConfig = map[string]tlsConfigWithOptionsName{}
|
|
}
|
|
|
|
r.hostHTTPTLSConfig[sniHost] = tlsConfigWithOptionsName{
|
|
cfg: config,
|
|
optionsName: optionsName,
|
|
}
|
|
}
|
|
|
|
// GetHTTPHandler gets the attached http handler.
|
|
func (r *Router) GetHTTPHandler() http.Handler {
|
|
return r.httpHandler
|
|
}
|
|
|
|
// GetHTTPSHandler gets the attached https handler.
|
|
func (r *Router) GetHTTPSHandler() http.Handler {
|
|
return r.httpsHandler
|
|
}
|
|
|
|
// SetHTTPForwarder sets the tcp handler that will forward the connections to an http handler.
|
|
func (r *Router) SetHTTPForwarder(handler tcp.Handler) {
|
|
r.httpForwarder = handler
|
|
}
|
|
|
|
// SetHTTPSForwarder sets the tcp handler that will forward the TLS connections to an HTTP handler.
|
|
// It also sets up each TLS handler (with its TLS config) for each Host(SNI) rule we previously kept track of.
|
|
// It sets up a special handler that closes the connection if a TLS config is nil.
|
|
func (r *Router) SetHTTPSForwarder(handler tcp.Handler) {
|
|
for sniHost, tlsConf := range r.hostHTTPTLSConfig {
|
|
var tcpHandler tcp.Handler
|
|
if tlsConf.cfg == nil {
|
|
tcpHandler = &brokenTLSRouter{}
|
|
} else {
|
|
tcpHandler = &tcp.TLSHandler{
|
|
Next: handler,
|
|
Config: tlsConf.cfg,
|
|
TLSOptionsName: tlsConf.optionsName,
|
|
}
|
|
}
|
|
|
|
rule := fmt.Sprintf(`HostSNI(%q)`, sniHost)
|
|
// As the hostHTTPTLSConfig contains only one TLS config per SNI,
|
|
// there is no conflict thus the provider name can be passed as empty as no tie-break is needed.
|
|
if err := r.muxerHTTPS.AddRoute(rule, "", tcpmuxer.GetRulePriority(rule), "", tcpHandler); err != nil {
|
|
log.Error().Err(err).Msg("Error while adding route for host")
|
|
}
|
|
}
|
|
|
|
if r.httpsTLSConfig == nil {
|
|
r.httpsForwarder = &brokenTLSRouter{}
|
|
return
|
|
}
|
|
|
|
r.httpsForwarder = &tcp.TLSHandler{
|
|
Next: handler,
|
|
Config: r.httpsTLSConfig,
|
|
TLSOptionsName: "default",
|
|
}
|
|
}
|
|
|
|
// SetHTTPHandler attaches http handlers on the router.
|
|
func (r *Router) SetHTTPHandler(handler http.Handler) {
|
|
r.httpHandler = handler
|
|
}
|
|
|
|
// SetHTTPSHandler attaches https handlers on the router.
|
|
func (r *Router) SetHTTPSHandler(handler http.Handler, config *tls.Config) {
|
|
r.httpsHandler = handler
|
|
r.httpsTLSConfig = config
|
|
}
|
|
|
|
func (r *Router) EnableACMETLSPassthrough() {
|
|
r.acmeTLSPassthrough = true
|
|
}
|
|
|
|
// acmeTLSALPNHandler returns a special handler to solve ACME-TLS/1 challenges.
|
|
func (r *Router) acmeTLSALPNHandler() tcp.Handler {
|
|
if r.httpsTLSConfig == nil {
|
|
return &brokenTLSRouter{}
|
|
}
|
|
|
|
return tcp.HandlerFunc(func(conn tcp.WriteCloser) {
|
|
tlsConn := tls.Server(conn, r.httpsTLSConfig)
|
|
defer tlsConn.Close()
|
|
|
|
// This avoids stale connections when validating the ACME challenge,
|
|
// as we expect a validation request to complete in a short period of time.
|
|
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
|
defer cancel()
|
|
|
|
if err := tlsConn.HandshakeContext(ctx); err != nil {
|
|
log.Debug().Err(err).Msg("Error during ACME-TLS/1 handshake")
|
|
}
|
|
})
|
|
}
|
|
|
|
// brokenTLSRouter is associated to a Host(SNI) rule for which we know the TLS conf is broken.
|
|
// It is used to make sure any attempt to connect to that hostname is closed,
|
|
// since we cannot proceed with the intended TLS conf.
|
|
type brokenTLSRouter struct{}
|
|
|
|
// ServeTCP instantly closes the connection.
|
|
func (t *brokenTLSRouter) ServeTCP(conn tcp.WriteCloser) {
|
|
_ = conn.Close()
|
|
}
|
|
|
|
// peekConn wraps a tcp.WriteCloser with a bufio.Reader for Peek operations
|
|
// and a peeked buffer that accumulates bytes consumed during protocol detection
|
|
// so they can be replayed on subsequent Read calls.
|
|
type peekConn struct {
|
|
tcp.WriteCloser
|
|
|
|
peeked []byte
|
|
reader *bufio.Reader
|
|
}
|
|
|
|
func newPeekConn(conn tcp.WriteCloser) *peekConn {
|
|
return &peekConn{
|
|
WriteCloser: conn,
|
|
reader: bufio.NewReader(conn),
|
|
}
|
|
}
|
|
|
|
// Peek allows peeking into the connection without consuming bytes, by using the bufio.Reader's Peek method.
|
|
func (c *peekConn) Peek(n int) ([]byte, error) {
|
|
return c.reader.Peek(n)
|
|
}
|
|
|
|
// PeekRead reads from the connection and accumulates the read bytes into the peeked buffer, allowing them to be replayed later.
|
|
// Note that PeekRead advances the reader, thus the next call to Peek will not return the same result as the previous one.
|
|
func (c *peekConn) PeekRead(p []byte) (int, error) {
|
|
n, err := c.reader.Read(p)
|
|
if n > 0 {
|
|
c.peeked = append(c.peeked, p[:n]...)
|
|
}
|
|
return n, err
|
|
}
|
|
|
|
// Read drains accumulated peeked bytes first, then reads from the bufio reader.
|
|
func (c *peekConn) Read(p []byte) (int, error) {
|
|
if len(c.peeked) > 0 {
|
|
n := copy(p, c.peeked)
|
|
c.peeked = c.peeked[n:]
|
|
if len(c.peeked) == 0 {
|
|
c.peeked = nil
|
|
}
|
|
return n, nil
|
|
}
|
|
|
|
return c.reader.Read(p)
|
|
}
|
|
|
|
type clientHello struct {
|
|
serverName string // SNI server name
|
|
protos []string // ALPN protocols list
|
|
isTLS bool // whether we are a TLS handshake
|
|
}
|
|
|
|
// clientHelloInfo returns various data from the clientHello handshake,
|
|
// without consuming any bytes from conn.
|
|
// It returns an error if it can't peek the first byte from the connection.
|
|
func clientHelloInfo(conn *peekConn) (*clientHello, error) {
|
|
hdr, err := conn.Peek(1)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("peeking first byte: %w", err)
|
|
}
|
|
|
|
// No valid TLS record has a type of 0x80, however SSLv2 handshakes start with an uint16 length
|
|
// where the MSB is set and the first record is always < 256 bytes long.
|
|
// Therefore, typ == 0x80 strongly suggests an SSLv2 client.
|
|
const recordTypeSSLv2 = 0x80
|
|
const recordTypeHandshake = 0x16
|
|
if hdr[0] != recordTypeHandshake {
|
|
if hdr[0] == recordTypeSSLv2 {
|
|
// we consider SSLv2 as TLS, and it will be refused by real TLS handshake.
|
|
return &clientHello{
|
|
isTLS: true,
|
|
}, nil
|
|
}
|
|
return &clientHello{}, nil // Not TLS.
|
|
}
|
|
|
|
var (
|
|
sni string
|
|
protos []string
|
|
)
|
|
server := tls.Server(readOnlyConn{conn: conn}, &tls.Config{
|
|
GetConfigForClient: func(hello *tls.ClientHelloInfo) (*tls.Config, error) {
|
|
sni = hello.ServerName
|
|
protos = hello.SupportedProtos
|
|
// This error prevents unnecessary additional steps in the TLS ClientHello message processing.
|
|
return nil, errClientHelloRead
|
|
},
|
|
})
|
|
|
|
if handshakeErr := server.Handshake(); !errors.Is(handshakeErr, errClientHelloRead) {
|
|
return nil, fmt.Errorf("reading client hello: %w", handshakeErr)
|
|
}
|
|
|
|
return &clientHello{
|
|
serverName: sni,
|
|
isTLS: true,
|
|
protos: protos,
|
|
}, nil
|
|
}
|
|
|
|
// readOnlyConn is a net.Conn that reads from conn, fails on Writes and crashes otherwise.
|
|
type readOnlyConn struct {
|
|
net.Conn // nil; crash on any unexpected use
|
|
|
|
conn *peekConn
|
|
}
|
|
|
|
// Read reads from the conn using PeekRead to keep the read bytes.
|
|
func (c readOnlyConn) Read(p []byte) (int, error) {
|
|
return c.conn.PeekRead(p)
|
|
}
|
|
|
|
// Write crashes all the time.
|
|
func (readOnlyConn) Write(_ []byte) (int, error) { return 0, io.EOF }
|