@@ -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.
4652type QUICConnection struct {
4753 session quic.Connection
@@ -60,18 +66,30 @@ type QUICConnection struct {
6066func 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 {
492510func (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+ }
0 commit comments