mirror of
https://github.com/cloudflare/cloudflared.git
synced 2025-05-28 23:56:35 +00:00

The current supervisor serves the quic connection by performing all of the following in one method: 1. Dial QUIC edge connection 2. Initialize datagram muxer for UDP sessions and ICMP 3. Wrap all together in a single struct to serve the process loops In an effort to better support modularity, each of these steps were broken out into their own separate methods that the supervisor will compose together to create the TunnelConnection and run its `Serve` method. This also provides us with the capability to better interchange the functionality supported by the datagram session manager in the future with a new mechanism. Closes TUN-8661
102 lines
2.7 KiB
Go
102 lines
2.7 KiB
Go
package connection
|
|
|
|
import (
|
|
"context"
|
|
"crypto/tls"
|
|
"fmt"
|
|
"net"
|
|
"net/netip"
|
|
"runtime"
|
|
"sync"
|
|
|
|
"github.com/quic-go/quic-go"
|
|
"github.com/rs/zerolog"
|
|
)
|
|
|
|
var (
|
|
portForConnIndex = make(map[uint8]int, 0)
|
|
portMapMutex sync.Mutex
|
|
)
|
|
|
|
func DialQuic(
|
|
ctx context.Context,
|
|
quicConfig *quic.Config,
|
|
tlsConfig *tls.Config,
|
|
edgeAddr netip.AddrPort,
|
|
localAddr net.IP,
|
|
connIndex uint8,
|
|
logger *zerolog.Logger,
|
|
) (quic.Connection, error) {
|
|
udpConn, err := createUDPConnForConnIndex(connIndex, localAddr, edgeAddr, logger)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
conn, err := quic.Dial(ctx, udpConn, net.UDPAddrFromAddrPort(edgeAddr), tlsConfig, quicConfig)
|
|
if err != nil {
|
|
// close the udp server socket in case of error connecting to the edge
|
|
udpConn.Close()
|
|
return nil, &EdgeQuicDialError{Cause: err}
|
|
}
|
|
|
|
// wrap the session, so that the UDPConn is closed after session is closed.
|
|
conn = &wrapCloseableConnQuicConnection{
|
|
conn,
|
|
udpConn,
|
|
}
|
|
return conn, nil
|
|
}
|
|
|
|
func createUDPConnForConnIndex(connIndex uint8, localIP net.IP, edgeIP netip.AddrPort, logger *zerolog.Logger) (*net.UDPConn, error) {
|
|
portMapMutex.Lock()
|
|
defer portMapMutex.Unlock()
|
|
|
|
listenNetwork := "udp"
|
|
// https://github.com/quic-go/quic-go/issues/3793 DF bit cannot be set for dual stack listener ("udp") on macOS,
|
|
// to set the DF bit properly, the network string needs to be specific to the IP family.
|
|
if runtime.GOOS == "darwin" {
|
|
if edgeIP.Addr().Is4() {
|
|
listenNetwork = "udp4"
|
|
} else {
|
|
listenNetwork = "udp6"
|
|
}
|
|
}
|
|
|
|
// if port was not set yet, it will be zero, so bind will randomly allocate one.
|
|
if port, ok := portForConnIndex[connIndex]; ok {
|
|
udpConn, err := net.ListenUDP(listenNetwork, &net.UDPAddr{IP: localIP, Port: port})
|
|
// if there wasn't an error, or if port was 0 (independently of error or not, just return)
|
|
if err == nil {
|
|
return udpConn, nil
|
|
} else {
|
|
logger.Debug().Err(err).Msgf("Unable to reuse port %d for connIndex %d. Falling back to random allocation.", port, connIndex)
|
|
}
|
|
}
|
|
|
|
// if we reached here, then there was an error or port as not been allocated it.
|
|
udpConn, err := net.ListenUDP(listenNetwork, &net.UDPAddr{IP: localIP, Port: 0})
|
|
if err == nil {
|
|
udpAddr, ok := (udpConn.LocalAddr()).(*net.UDPAddr)
|
|
if !ok {
|
|
return nil, fmt.Errorf("unable to cast to udpConn")
|
|
}
|
|
portForConnIndex[connIndex] = udpAddr.Port
|
|
} else {
|
|
delete(portForConnIndex, connIndex)
|
|
}
|
|
|
|
return udpConn, err
|
|
}
|
|
|
|
type wrapCloseableConnQuicConnection struct {
|
|
quic.Connection
|
|
udpConn *net.UDPConn
|
|
}
|
|
|
|
func (w *wrapCloseableConnQuicConnection) CloseWithError(errorCode quic.ApplicationErrorCode, reason string) error {
|
|
err := w.Connection.CloseWithError(errorCode, reason)
|
|
w.udpConn.Close()
|
|
|
|
return err
|
|
}
|