Skip to content

Commit b01006f

Browse files
committed
TUN-6853: Reuse source port when connecting to the edge for quic connections
1 parent 872cb00 commit b01006f

File tree

3 files changed

+94
-1
lines changed

3 files changed

+94
-1
lines changed

connection/quic.go

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"net/netip"
1111
"strconv"
1212
"strings"
13+
"sync"
1314
"sync/atomic"
1415
"time"
1516

@@ -42,6 +43,11 @@ const (
4243
demuxChanCapacity = 16
4344
)
4445

46+
var (
47+
portForConnIndex = make(map[uint8]int, 0)
48+
portMapMutex sync.Mutex
49+
)
50+
4551
// QUICConnection represents the type that facilitates Proxying via QUIC streams.
4652
type QUICConnection struct {
4753
session quic.Connection
@@ -60,18 +66,30 @@ type QUICConnection struct {
6066
func NewQUICConnection(
6167
quicConfig *quic.Config,
6268
edgeAddr net.Addr,
69+
connIndex uint8,
6370
tlsConfig *tls.Config,
6471
orchestrator Orchestrator,
6572
connOptions *tunnelpogs.ConnectionOptions,
6673
controlStreamHandler ControlStreamHandler,
6774
logger *zerolog.Logger,
6875
packetRouterConfig *packet.GlobalRouterConfig,
6976
) (*QUICConnection, error) {
70-
session, err := quic.DialAddr(edgeAddr.String(), tlsConfig, quicConfig)
77+
udpConn, err := createUDPConnForConnIndex(connIndex, logger)
78+
if err != nil {
79+
return nil, err
80+
}
81+
82+
session, err := quic.Dial(udpConn, edgeAddr, edgeAddr.String(), tlsConfig, quicConfig)
7183
if err != nil {
7284
return nil, &EdgeQuicDialError{Cause: err}
7385
}
7486

87+
// wrap the session, so that the UDPConn is closed after session is closed.
88+
session = &wrapCloseableConnQuicConnection{
89+
session,
90+
udpConn,
91+
}
92+
7593
sessionDemuxChan := make(chan *packet.Session, demuxChanCapacity)
7694
datagramMuxer := quicpogs.NewDatagramMuxerV2(session, logger, sessionDemuxChan)
7795
sessionManager := datagramsession.NewManager(logger, datagramMuxer.SendToSession, sessionDemuxChan)
@@ -492,3 +510,45 @@ func (rp *returnPipe) SendPacket(dst netip.Addr, pk packet.RawPacket) error {
492510
func (rp *returnPipe) Close() error {
493511
return nil
494512
}
513+
514+
func createUDPConnForConnIndex(connIndex uint8, logger *zerolog.Logger) (*net.UDPConn, error) {
515+
portMapMutex.Lock()
516+
defer portMapMutex.Unlock()
517+
518+
// if port was not set yet, it will be zero, so bind will randomly allocate one.
519+
if port, ok := portForConnIndex[connIndex]; ok {
520+
udpConn, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4zero, Port: port})
521+
// if there wasn't an error, or if port was 0 (independently of error or not, just return)
522+
if err == nil {
523+
return udpConn, nil
524+
} else {
525+
logger.Debug().Err(err).Msgf("Unable to reuse port %d for connIndex %d. Falling back to random allocation.", port, connIndex)
526+
}
527+
}
528+
529+
// if we reached here, then there was an error or port as not been allocated it.
530+
udpConn, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4zero, Port: 0})
531+
if err == nil {
532+
udpAddr, ok := (udpConn.LocalAddr()).(*net.UDPAddr)
533+
if !ok {
534+
return nil, fmt.Errorf("unable to cast to udpConn")
535+
}
536+
portForConnIndex[connIndex] = udpAddr.Port
537+
} else {
538+
delete(portForConnIndex, connIndex)
539+
}
540+
541+
return udpConn, err
542+
}
543+
544+
type wrapCloseableConnQuicConnection struct {
545+
quic.Connection
546+
udpConn *net.UDPConn
547+
}
548+
549+
func (w *wrapCloseableConnQuicConnection) CloseWithError(errorCode quic.ApplicationErrorCode, reason string) error {
550+
err := w.Connection.CloseWithError(errorCode, reason)
551+
w.udpConn.Close()
552+
553+
return err
554+
}

connection/quic_test.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -567,6 +567,37 @@ func TestNopCloserReadWriterCloseAfterEOF(t *testing.T) {
567567
require.Equal(t, err, io.EOF)
568568
}
569569

570+
func TestCreateUDPConnReuseSourcePort(t *testing.T) {
571+
logger := zerolog.Nop()
572+
conn, err := createUDPConnForConnIndex(0, &logger)
573+
require.NoError(t, err)
574+
575+
getPortFunc := func(conn *net.UDPConn) int {
576+
addr := conn.LocalAddr().(*net.UDPAddr)
577+
return addr.Port
578+
}
579+
580+
initialPort := getPortFunc(conn)
581+
582+
// close conn
583+
conn.Close()
584+
585+
// should get the same port as before.
586+
conn, err = createUDPConnForConnIndex(0, &logger)
587+
require.NoError(t, err)
588+
require.Equal(t, initialPort, getPortFunc(conn))
589+
590+
// new index, should get a different port
591+
conn1, err := createUDPConnForConnIndex(1, &logger)
592+
require.NoError(t, err)
593+
require.NotEqual(t, initialPort, getPortFunc(conn1))
594+
595+
// not closing the conn and trying to obtain a new conn for same index should give a different random port
596+
conn, err = createUDPConnForConnIndex(0, &logger)
597+
require.NoError(t, err)
598+
require.NotEqual(t, initialPort, getPortFunc(conn))
599+
}
600+
570601
func serveSession(ctx context.Context, qc *QUICConnection, edgeQUICSession quic.Connection, closeType closeReason, expectedReason string, t *testing.T) {
571602
var (
572603
payload = []byte(t.Name())
@@ -682,6 +713,7 @@ func testQUICConnection(udpListenerAddr net.Addr, t *testing.T) *QUICConnection
682713
qc, err := NewQUICConnection(
683714
testQUICConfig,
684715
udpListenerAddr,
716+
0,
685717
tlsClientConfig,
686718
&mockOrchestrator{originProxy: &mockOriginProxyWithRequest{}},
687719
&tunnelpogs.ConnectionOptions{},

supervisor/tunnel.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -657,6 +657,7 @@ func (e *EdgeTunnelServer) serveQUIC(
657657
quicConn, err := connection.NewQUICConnection(
658658
quicConfig,
659659
edgeAddr,
660+
connIndex,
660661
tlsConfig,
661662
e.orchestrator,
662663
connOptions,

0 commit comments

Comments
 (0)