github.com/metacubex/quic-go@v0.44.1-0.20240520163451-20b689a59136/client.go (about)

     1  package quic
     2  
     3  import (
     4  	"context"
     5  	"crypto/tls"
     6  	"errors"
     7  	"net"
     8  
     9  	"github.com/metacubex/quic-go/internal/protocol"
    10  	"github.com/metacubex/quic-go/internal/utils"
    11  	"github.com/metacubex/quic-go/logging"
    12  )
    13  
    14  type client struct {
    15  	sendConn sendConn
    16  
    17  	use0RTT bool
    18  
    19  	packetHandlers packetHandlerManager
    20  	onClose        func()
    21  
    22  	tlsConf *tls.Config
    23  	config  *Config
    24  
    25  	connIDGenerator ConnectionIDGenerator
    26  	srcConnID       protocol.ConnectionID
    27  	destConnID      protocol.ConnectionID
    28  
    29  	initialPacketNumber  protocol.PacketNumber
    30  	hasNegotiatedVersion bool
    31  	version              protocol.Version
    32  
    33  	handshakeChan chan struct{}
    34  
    35  	conn quicConn
    36  
    37  	tracer    *logging.ConnectionTracer
    38  	tracingID ConnectionTracingID
    39  	logger    utils.Logger
    40  }
    41  
    42  // make it possible to mock connection ID for initial generation in the tests
    43  var generateConnectionIDForInitial = protocol.GenerateConnectionIDForInitial
    44  
    45  // DialAddr establishes a new QUIC connection to a server.
    46  // It resolves the address, and then creates a new UDP connection to dial the QUIC server.
    47  // When the QUIC connection is closed, this UDP connection is closed.
    48  // See Dial for more details.
    49  func DialAddr(ctx context.Context, addr string, tlsConf *tls.Config, conf *Config) (Connection, error) {
    50  	udpConn, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4zero, Port: 0})
    51  	if err != nil {
    52  		return nil, err
    53  	}
    54  	udpAddr, err := net.ResolveUDPAddr("udp", addr)
    55  	if err != nil {
    56  		return nil, err
    57  	}
    58  	tr, err := setupTransport(udpConn, tlsConf, true)
    59  	if err != nil {
    60  		return nil, err
    61  	}
    62  	return tr.dial(ctx, udpAddr, addr, tlsConf, conf, false)
    63  }
    64  
    65  // DialAddrEarly establishes a new 0-RTT QUIC connection to a server.
    66  // See DialAddr for more details.
    67  func DialAddrEarly(ctx context.Context, addr string, tlsConf *tls.Config, conf *Config) (EarlyConnection, error) {
    68  	udpConn, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4zero, Port: 0})
    69  	if err != nil {
    70  		return nil, err
    71  	}
    72  	udpAddr, err := net.ResolveUDPAddr("udp", addr)
    73  	if err != nil {
    74  		return nil, err
    75  	}
    76  	tr, err := setupTransport(udpConn, tlsConf, true)
    77  	if err != nil {
    78  		return nil, err
    79  	}
    80  	conn, err := tr.dial(ctx, udpAddr, addr, tlsConf, conf, true)
    81  	if err != nil {
    82  		tr.Close()
    83  		return nil, err
    84  	}
    85  	return conn, nil
    86  }
    87  
    88  // DialEarly establishes a new 0-RTT QUIC connection to a server using a net.PacketConn.
    89  // See Dial for more details.
    90  func DialEarly(ctx context.Context, c net.PacketConn, addr net.Addr, tlsConf *tls.Config, conf *Config) (EarlyConnection, error) {
    91  	dl, err := setupTransport(c, tlsConf, false)
    92  	if err != nil {
    93  		return nil, err
    94  	}
    95  	conn, err := dl.DialEarly(ctx, addr, tlsConf, conf)
    96  	if err != nil {
    97  		dl.Close()
    98  		return nil, err
    99  	}
   100  	return conn, nil
   101  }
   102  
   103  // Dial establishes a new QUIC connection to a server using a net.PacketConn.
   104  // If the PacketConn satisfies the OOBCapablePacketConn interface (as a net.UDPConn does),
   105  // ECN and packet info support will be enabled. In this case, ReadMsgUDP and WriteMsgUDP
   106  // will be used instead of ReadFrom and WriteTo to read/write packets.
   107  // The tls.Config must define an application protocol (using NextProtos).
   108  //
   109  // This is a convenience function. More advanced use cases should instantiate a Transport,
   110  // which offers configuration options for a more fine-grained control of the connection establishment,
   111  // including reusing the underlying UDP socket for multiple QUIC connections.
   112  func Dial(ctx context.Context, c net.PacketConn, addr net.Addr, tlsConf *tls.Config, conf *Config) (Connection, error) {
   113  	dl, err := setupTransport(c, tlsConf, false)
   114  	if err != nil {
   115  		return nil, err
   116  	}
   117  	conn, err := dl.Dial(ctx, addr, tlsConf, conf)
   118  	if err != nil {
   119  		dl.Close()
   120  		return nil, err
   121  	}
   122  	return conn, nil
   123  }
   124  
   125  func setupTransport(c net.PacketConn, tlsConf *tls.Config, createdPacketConn bool) (*Transport, error) {
   126  	if tlsConf == nil {
   127  		return nil, errors.New("quic: tls.Config not set")
   128  	}
   129  	return &Transport{
   130  		Conn:        c,
   131  		createdConn: createdPacketConn,
   132  		isSingleUse: true,
   133  	}, nil
   134  }
   135  
   136  func dial(
   137  	ctx context.Context,
   138  	conn sendConn,
   139  	connIDGenerator ConnectionIDGenerator,
   140  	packetHandlers packetHandlerManager,
   141  	tlsConf *tls.Config,
   142  	config *Config,
   143  	onClose func(),
   144  	use0RTT bool,
   145  ) (quicConn, error) {
   146  	c, err := newClient(conn, connIDGenerator, config, tlsConf, onClose, use0RTT)
   147  	if err != nil {
   148  		return nil, err
   149  	}
   150  	c.packetHandlers = packetHandlers
   151  
   152  	c.tracingID = nextConnTracingID()
   153  	if c.config.Tracer != nil {
   154  		c.tracer = c.config.Tracer(context.WithValue(ctx, ConnectionTracingKey, c.tracingID), protocol.PerspectiveClient, c.destConnID)
   155  	}
   156  	if c.tracer != nil && c.tracer.StartedConnection != nil {
   157  		c.tracer.StartedConnection(c.sendConn.LocalAddr(), c.sendConn.RemoteAddr(), c.srcConnID, c.destConnID)
   158  	}
   159  	if err := c.dial(ctx); err != nil {
   160  		return nil, err
   161  	}
   162  	return c.conn, nil
   163  }
   164  
   165  func newClient(sendConn sendConn, connIDGenerator ConnectionIDGenerator, config *Config, tlsConf *tls.Config, onClose func(), use0RTT bool) (*client, error) {
   166  	srcConnID, err := connIDGenerator.GenerateConnectionID()
   167  	if err != nil {
   168  		return nil, err
   169  	}
   170  	destConnID, err := generateConnectionIDForInitial()
   171  	if err != nil {
   172  		return nil, err
   173  	}
   174  	c := &client{
   175  		connIDGenerator: connIDGenerator,
   176  		srcConnID:       srcConnID,
   177  		destConnID:      destConnID,
   178  		sendConn:        sendConn,
   179  		use0RTT:         use0RTT,
   180  		onClose:         onClose,
   181  		tlsConf:         tlsConf,
   182  		config:          config,
   183  		version:         config.Versions[0],
   184  		handshakeChan:   make(chan struct{}),
   185  		logger:          utils.DefaultLogger.WithPrefix("client"),
   186  	}
   187  	return c, nil
   188  }
   189  
   190  func (c *client) dial(ctx context.Context) error {
   191  	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)
   192  
   193  	c.conn = newClientConnection(
   194  		c.sendConn,
   195  		c.packetHandlers,
   196  		c.destConnID,
   197  		c.srcConnID,
   198  		c.connIDGenerator,
   199  		c.config,
   200  		c.tlsConf,
   201  		c.initialPacketNumber,
   202  		c.use0RTT,
   203  		c.hasNegotiatedVersion,
   204  		c.tracer,
   205  		c.tracingID,
   206  		c.logger,
   207  		c.version,
   208  	)
   209  	c.packetHandlers.Add(c.srcConnID, c.conn)
   210  
   211  	errorChan := make(chan error, 1)
   212  	recreateChan := make(chan errCloseForRecreating)
   213  	go func() {
   214  		err := c.conn.run()
   215  		var recreateErr *errCloseForRecreating
   216  		if errors.As(err, &recreateErr) {
   217  			recreateChan <- *recreateErr
   218  			return
   219  		}
   220  		if c.onClose != nil {
   221  			c.onClose()
   222  		}
   223  		errorChan <- err // returns as soon as the connection is closed
   224  	}()
   225  
   226  	// only set when we're using 0-RTT
   227  	// Otherwise, earlyConnChan will be nil. Receiving from a nil chan blocks forever.
   228  	var earlyConnChan <-chan struct{}
   229  	if c.use0RTT {
   230  		earlyConnChan = c.conn.earlyConnReady()
   231  	}
   232  
   233  	select {
   234  	case <-ctx.Done():
   235  		c.conn.destroy(nil)
   236  		return context.Cause(ctx)
   237  	case err := <-errorChan:
   238  		return err
   239  	case recreateErr := <-recreateChan:
   240  		c.initialPacketNumber = recreateErr.nextPacketNumber
   241  		c.version = recreateErr.nextVersion
   242  		c.hasNegotiatedVersion = true
   243  		return c.dial(ctx)
   244  	case <-earlyConnChan:
   245  		// ready to send 0-RTT data
   246  		return nil
   247  	case <-c.conn.HandshakeComplete():
   248  		// handshake successfully completed
   249  		return nil
   250  	}
   251  }