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