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
|
|
}
|