github.com/tumi8/quic-go@v0.37.4-tum/client.go (about)

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