2022-07-21 18:50:10 +02:00
package quic
import (
"context"
"crypto/tls"
"errors"
"net"
2023-02-02 12:42:11 +01:00
"github.com/quic-go/quic-go/internal/protocol"
"github.com/quic-go/quic-go/internal/utils"
"github.com/quic-go/quic-go/logging"
2022-07-21 18:50:10 +02:00
)
type client struct {
2023-07-03 12:37:42 +02:00
sendConn sendConn
2022-07-21 18:50:10 +02:00
use0RTT bool
packetHandlers packetHandlerManager
2023-07-03 12:37:42 +02:00
onClose func ( )
2022-07-21 18:50:10 +02:00
tlsConf * tls . Config
config * Config
2023-07-03 12:37:42 +02:00
connIDGenerator ConnectionIDGenerator
srcConnID protocol . ConnectionID
destConnID protocol . ConnectionID
2022-07-21 18:50:10 +02:00
initialPacketNumber protocol . PacketNumber
hasNegotiatedVersion bool
2024-03-26 19:56:06 +01:00
version protocol . Version
2022-07-21 18:50:10 +02:00
handshakeChan chan struct { }
conn quicConn
2023-10-12 15:53:53 +02:00
tracer * logging . ConnectionTracer
2022-07-21 18:50:10 +02:00
tracingID uint64
logger utils . Logger
}
2022-08-30 20:45:06 +02:00
// make it possible to mock connection ID for initial generation in the tests
var generateConnectionIDForInitial = protocol . GenerateConnectionIDForInitial
2022-07-21 18:50:10 +02:00
// DialAddr establishes a new QUIC connection to a server.
2023-07-03 12:37:42 +02:00
// It resolves the address, and then creates a new UDP connection to dial the QUIC server.
// When the QUIC connection is closed, this UDP connection is closed.
// See Dial for more details.
func DialAddr ( ctx context . Context , addr string , tlsConf * tls . Config , conf * Config ) ( Connection , error ) {
udpConn , err := net . ListenUDP ( "udp" , & net . UDPAddr { IP : net . IPv4zero , Port : 0 } )
if err != nil {
return nil , err
}
udpAddr , err := net . ResolveUDPAddr ( "udp" , addr )
if err != nil {
return nil , err
}
2023-08-25 16:15:45 +02:00
tr , err := setupTransport ( udpConn , tlsConf , true )
2023-07-03 12:37:42 +02:00
if err != nil {
return nil , err
}
2023-08-25 16:15:45 +02:00
return tr . dial ( ctx , udpAddr , addr , tlsConf , conf , false )
2022-07-21 18:50:10 +02:00
}
// DialAddrEarly establishes a new 0-RTT QUIC connection to a server.
2023-07-03 12:37:42 +02:00
// See DialAddr for more details.
func DialAddrEarly ( ctx context . Context , addr string , tlsConf * tls . Config , conf * Config ) ( EarlyConnection , error ) {
udpConn , err := net . ListenUDP ( "udp" , & net . UDPAddr { IP : net . IPv4zero , Port : 0 } )
2023-05-30 18:17:27 +02:00
if err != nil {
return nil , err
}
2023-06-22 11:05:44 +02:00
udpAddr , err := net . ResolveUDPAddr ( "udp" , addr )
2022-07-21 18:50:10 +02:00
if err != nil {
return nil , err
}
2023-08-25 16:15:45 +02:00
tr , err := setupTransport ( udpConn , tlsConf , true )
2022-07-21 18:50:10 +02:00
if err != nil {
return nil , err
}
2023-08-25 16:15:45 +02:00
conn , err := tr . dial ( ctx , udpAddr , addr , tlsConf , conf , true )
2023-07-03 12:37:42 +02:00
if err != nil {
2023-08-25 16:15:45 +02:00
tr . Close ( )
2023-07-03 12:37:42 +02:00
return nil , err
}
return conn , nil
2022-07-21 18:50:10 +02:00
}
2023-06-22 11:05:44 +02:00
// DialEarly establishes a new 0-RTT QUIC connection to a server using a net.PacketConn.
2023-07-03 12:37:42 +02:00
// See Dial for more details.
func DialEarly ( ctx context . Context , c net . PacketConn , addr net . Addr , tlsConf * tls . Config , conf * Config ) ( EarlyConnection , error ) {
dl , err := setupTransport ( c , tlsConf , false )
if err != nil {
return nil , err
}
conn , err := dl . DialEarly ( ctx , addr , tlsConf , conf )
if err != nil {
dl . Close ( )
return nil , err
}
return conn , nil
2022-07-21 18:50:10 +02:00
}
2023-07-03 12:37:42 +02:00
// Dial establishes a new QUIC connection to a server using a net.PacketConn.
// If the PacketConn satisfies the OOBCapablePacketConn interface (as a net.UDPConn does),
// ECN and packet info support will be enabled. In this case, ReadMsgUDP and WriteMsgUDP
// will be used instead of ReadFrom and WriteTo to read/write packets.
// The tls.Config must define an application protocol (using NextProtos).
//
// This is a convenience function. More advanced use cases should instantiate a Transport,
// which offers configuration options for a more fine-grained control of the connection establishment,
// including reusing the underlying UDP socket for multiple QUIC connections.
func Dial ( ctx context . Context , c net . PacketConn , addr net . Addr , tlsConf * tls . Config , conf * Config ) ( Connection , error ) {
dl , err := setupTransport ( c , tlsConf , false )
if err != nil {
return nil , err
}
conn , err := dl . Dial ( ctx , addr , tlsConf , conf )
if err != nil {
dl . Close ( )
return nil , err
}
return conn , nil
2022-07-21 18:50:10 +02:00
}
2023-07-03 12:37:42 +02:00
func setupTransport ( c net . PacketConn , tlsConf * tls . Config , createdPacketConn bool ) ( * Transport , error ) {
if tlsConf == nil {
return nil , errors . New ( "quic: tls.Config not set" )
}
return & Transport {
Conn : c ,
createdConn : createdPacketConn ,
isSingleUse : true ,
} , nil
2023-06-22 11:05:44 +02:00
}
2023-07-03 12:37:42 +02:00
func dial (
2023-06-22 11:05:44 +02:00
ctx context . Context ,
2023-07-03 12:37:42 +02:00
conn sendConn ,
connIDGenerator ConnectionIDGenerator ,
packetHandlers packetHandlerManager ,
2022-07-21 18:50:10 +02:00
tlsConf * tls . Config ,
config * Config ,
2023-07-03 12:37:42 +02:00
onClose func ( ) ,
2022-07-21 18:50:10 +02:00
use0RTT bool ,
) ( quicConn , error ) {
2023-07-03 12:37:42 +02:00
c , err := newClient ( conn , connIDGenerator , config , tlsConf , onClose , use0RTT )
2022-07-21 18:50:10 +02:00
if err != nil {
return nil , err
}
c . packetHandlers = packetHandlers
c . tracingID = nextConnTracingID ( )
if c . config . Tracer != nil {
2023-07-03 12:37:42 +02:00
c . tracer = c . config . Tracer ( context . WithValue ( ctx , ConnectionTracingKey , c . tracingID ) , protocol . PerspectiveClient , c . destConnID )
2022-07-21 18:50:10 +02:00
}
2023-10-12 15:53:53 +02:00
if c . tracer != nil && c . tracer . StartedConnection != nil {
2023-07-03 12:37:42 +02:00
c . tracer . StartedConnection ( c . sendConn . LocalAddr ( ) , c . sendConn . RemoteAddr ( ) , c . srcConnID , c . destConnID )
2022-07-21 18:50:10 +02:00
}
if err := c . dial ( ctx ) ; err != nil {
return nil , err
}
return c . conn , nil
}
2023-07-03 12:37:42 +02:00
func newClient ( sendConn sendConn , connIDGenerator ConnectionIDGenerator , config * Config , tlsConf * tls . Config , onClose func ( ) , use0RTT bool ) ( * client , error ) {
srcConnID , err := connIDGenerator . GenerateConnectionID ( )
2022-07-21 18:50:10 +02:00
if err != nil {
return nil , err
}
destConnID , err := generateConnectionIDForInitial ( )
if err != nil {
return nil , err
}
c := & client {
2023-07-03 12:37:42 +02:00
connIDGenerator : connIDGenerator ,
srcConnID : srcConnID ,
destConnID : destConnID ,
sendConn : sendConn ,
use0RTT : use0RTT ,
onClose : onClose ,
tlsConf : tlsConf ,
config : config ,
version : config . Versions [ 0 ] ,
handshakeChan : make ( chan struct { } ) ,
logger : utils . DefaultLogger . WithPrefix ( "client" ) ,
2022-07-21 18:50:10 +02:00
}
return c , nil
}
func ( c * client ) dial ( ctx context . Context ) error {
2023-07-03 12:37:42 +02:00
c . logger . Infof ( "Starting new connection to %s (%s -> %s), source connection ID %s, destination connection ID %s, version %s" , c . tlsConf . ServerName , c . sendConn . LocalAddr ( ) , c . sendConn . RemoteAddr ( ) , c . srcConnID , c . destConnID , c . version )
2022-07-21 18:50:10 +02:00
c . conn = newClientConnection (
2023-07-03 12:37:42 +02:00
c . sendConn ,
2022-07-21 18:50:10 +02:00
c . packetHandlers ,
c . destConnID ,
c . srcConnID ,
2023-07-03 12:37:42 +02:00
c . connIDGenerator ,
2022-07-21 18:50:10 +02:00
c . config ,
c . tlsConf ,
c . initialPacketNumber ,
c . use0RTT ,
c . hasNegotiatedVersion ,
c . tracer ,
c . tracingID ,
c . logger ,
c . version ,
)
c . packetHandlers . Add ( c . srcConnID , c . conn )
errorChan := make ( chan error , 1 )
2023-07-03 12:37:42 +02:00
recreateChan := make ( chan errCloseForRecreating )
2022-07-21 18:50:10 +02:00
go func ( ) {
2023-07-03 12:37:42 +02:00
err := c . conn . run ( )
var recreateErr * errCloseForRecreating
if errors . As ( err , & recreateErr ) {
recreateChan <- * recreateErr
return
}
if c . onClose != nil {
c . onClose ( )
2022-07-21 18:50:10 +02:00
}
2023-07-03 12:37:42 +02:00
errorChan <- err // returns as soon as the connection is closed
2022-07-21 18:50:10 +02:00
} ( )
// only set when we're using 0-RTT
// Otherwise, earlyConnChan will be nil. Receiving from a nil chan blocks forever.
var earlyConnChan <- chan struct { }
if c . use0RTT {
earlyConnChan = c . conn . earlyConnReady ( )
}
select {
case <- ctx . Done ( ) :
2024-03-26 19:56:06 +01:00
c . conn . destroy ( nil )
2023-10-12 15:53:53 +02:00
return context . Cause ( ctx )
2022-07-21 18:50:10 +02:00
case err := <- errorChan :
return err
2023-07-03 12:37:42 +02:00
case recreateErr := <- recreateChan :
c . initialPacketNumber = recreateErr . nextPacketNumber
c . version = recreateErr . nextVersion
c . hasNegotiatedVersion = true
return c . dial ( ctx )
2022-07-21 18:50:10 +02:00
case <- earlyConnChan :
// ready to send 0-RTT data
return nil
2023-05-01 16:05:02 +02:00
case <- c . conn . HandshakeComplete ( ) :
2022-07-21 18:50:10 +02:00
// handshake successfully completed
return nil
}
}