| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595 |
- package quic
- import (
- "bytes"
- "context"
- "crypto/tls"
- "errors"
- "fmt"
- "net"
- "sync"
- "github.com/bifurcation/mint"
- "github.com/lucas-clemente/quic-go/internal/handshake"
- "github.com/lucas-clemente/quic-go/internal/protocol"
- "github.com/lucas-clemente/quic-go/internal/utils"
- "github.com/lucas-clemente/quic-go/internal/wire"
- "github.com/lucas-clemente/quic-go/qerr"
- )
- type client struct {
- mutex sync.Mutex
- conn connection
- // If the client is created with DialAddr, we create a packet conn.
- // If it is started with Dial, we take a packet conn as a parameter.
- createdPacketConn bool
- hostname string
- packetHandlers packetHandlerManager
- token []byte
- numRetries int
- versionNegotiated bool // has the server accepted our version
- receivedVersionNegotiationPacket bool
- negotiatedVersions []protocol.VersionNumber // the list of versions from the version negotiation packet
- tlsConf *tls.Config
- mintConf *mint.Config
- config *Config
- srcConnID protocol.ConnectionID
- destConnID protocol.ConnectionID
- initialVersion protocol.VersionNumber
- version protocol.VersionNumber
- handshakeChan chan struct{}
- closeCallback func(protocol.ConnectionID)
- session quicSession
- logger utils.Logger
- }
- var _ packetHandler = &client{}
- var (
- // make it possible to mock connection ID generation in the tests
- generateConnectionID = protocol.GenerateConnectionID
- generateConnectionIDForInitial = protocol.GenerateConnectionIDForInitial
- errCloseSessionForNewVersion = errors.New("closing session in order to recreate it with a new version")
- errCloseSessionForRetry = errors.New("closing session in response to a stateless retry")
- )
- // DialAddr establishes a new QUIC connection to a server.
- // The hostname for SNI is taken from the given address.
- func DialAddr(
- addr string,
- tlsConf *tls.Config,
- config *Config,
- ) (Session, error) {
- return DialAddrContext(context.Background(), addr, tlsConf, config)
- }
- // DialAddrContext establishes a new QUIC connection to a server using the provided context.
- // The hostname for SNI is taken from the given address.
- func DialAddrContext(
- ctx context.Context,
- addr string,
- tlsConf *tls.Config,
- config *Config,
- ) (Session, error) {
- udpAddr, err := net.ResolveUDPAddr("udp", addr)
- if err != nil {
- return nil, err
- }
- udpConn, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4zero, Port: 0})
- if err != nil {
- return nil, err
- }
- return dialContext(ctx, udpConn, udpAddr, addr, tlsConf, config, true)
- }
- // Dial establishes a new QUIC connection to a server using a net.PacketConn.
- // The host parameter is used for SNI.
- func Dial(
- pconn net.PacketConn,
- remoteAddr net.Addr,
- host string,
- tlsConf *tls.Config,
- config *Config,
- ) (Session, error) {
- return DialContext(context.Background(), pconn, remoteAddr, host, tlsConf, config)
- }
- // DialContext establishes a new QUIC connection to a server using a net.PacketConn using the provided context.
- // The host parameter is used for SNI.
- func DialContext(
- ctx context.Context,
- pconn net.PacketConn,
- remoteAddr net.Addr,
- host string,
- tlsConf *tls.Config,
- config *Config,
- ) (Session, error) {
- return dialContext(ctx, pconn, remoteAddr, host, tlsConf, config, false)
- }
- func dialContext(
- ctx context.Context,
- pconn net.PacketConn,
- remoteAddr net.Addr,
- host string,
- tlsConf *tls.Config,
- config *Config,
- createdPacketConn bool,
- ) (Session, error) {
- config = populateClientConfig(config, createdPacketConn)
- if !createdPacketConn {
- for _, v := range config.Versions {
- if v == protocol.Version44 {
- return nil, errors.New("Cannot multiplex connections using gQUIC 44, see https://groups.google.com/a/chromium.org/forum/#!topic/proto-quic/pE9NlLLjizE. Please disable gQUIC 44 in the quic.Config, or use DialAddr")
- }
- }
- }
- packetHandlers, err := getMultiplexer().AddConn(pconn, config.ConnectionIDLength)
- if err != nil {
- return nil, err
- }
- c, err := newClient(pconn, remoteAddr, config, tlsConf, host, packetHandlers.Remove, createdPacketConn)
- if err != nil {
- return nil, err
- }
- c.packetHandlers = packetHandlers
- if err := c.dial(ctx); err != nil {
- return nil, err
- }
- return c.session, nil
- }
- func newClient(
- pconn net.PacketConn,
- remoteAddr net.Addr,
- config *Config,
- tlsConf *tls.Config,
- host string,
- closeCallback func(protocol.ConnectionID),
- createdPacketConn bool,
- ) (*client, error) {
- var hostname string
- if tlsConf != nil {
- hostname = tlsConf.ServerName
- }
- if hostname == "" {
- var err error
- hostname, _, err = net.SplitHostPort(host)
- if err != nil {
- return nil, err
- }
- }
- // check that all versions are actually supported
- if config != nil {
- for _, v := range config.Versions {
- if !protocol.IsValidVersion(v) {
- return nil, fmt.Errorf("%s is not a valid QUIC version", v)
- }
- }
- }
- onClose := func(protocol.ConnectionID) {}
- if closeCallback != nil {
- onClose = closeCallback
- }
- c := &client{
- conn: &conn{pconn: pconn, currentAddr: remoteAddr},
- createdPacketConn: createdPacketConn,
- hostname: hostname,
- tlsConf: tlsConf,
- config: config,
- version: config.Versions[0],
- handshakeChan: make(chan struct{}),
- closeCallback: onClose,
- logger: utils.DefaultLogger.WithPrefix("client"),
- }
- return c, c.generateConnectionIDs()
- }
- // populateClientConfig populates fields in the quic.Config with their default values, if none are set
- // it may be called with nil
- func populateClientConfig(config *Config, createdPacketConn bool) *Config {
- if config == nil {
- config = &Config{}
- }
- versions := config.Versions
- if len(versions) == 0 {
- versions = protocol.SupportedVersions
- }
- handshakeTimeout := protocol.DefaultHandshakeTimeout
- if config.HandshakeTimeout != 0 {
- handshakeTimeout = config.HandshakeTimeout
- }
- idleTimeout := protocol.DefaultIdleTimeout
- if config.IdleTimeout != 0 {
- idleTimeout = config.IdleTimeout
- }
- maxReceiveStreamFlowControlWindow := config.MaxReceiveStreamFlowControlWindow
- if maxReceiveStreamFlowControlWindow == 0 {
- maxReceiveStreamFlowControlWindow = protocol.DefaultMaxReceiveStreamFlowControlWindowClient
- }
- maxReceiveConnectionFlowControlWindow := config.MaxReceiveConnectionFlowControlWindow
- if maxReceiveConnectionFlowControlWindow == 0 {
- maxReceiveConnectionFlowControlWindow = protocol.DefaultMaxReceiveConnectionFlowControlWindowClient
- }
- maxIncomingStreams := config.MaxIncomingStreams
- if maxIncomingStreams == 0 {
- maxIncomingStreams = protocol.DefaultMaxIncomingStreams
- } else if maxIncomingStreams < 0 {
- maxIncomingStreams = 0
- }
- maxIncomingUniStreams := config.MaxIncomingUniStreams
- if maxIncomingUniStreams == 0 {
- maxIncomingUniStreams = protocol.DefaultMaxIncomingUniStreams
- } else if maxIncomingUniStreams < 0 {
- maxIncomingUniStreams = 0
- }
- connIDLen := config.ConnectionIDLength
- if connIDLen == 0 && !createdPacketConn {
- connIDLen = protocol.DefaultConnectionIDLength
- }
- for _, v := range versions {
- if v == protocol.Version44 {
- connIDLen = 0
- }
- }
- return &Config{
- Versions: versions,
- HandshakeTimeout: handshakeTimeout,
- IdleTimeout: idleTimeout,
- RequestConnectionIDOmission: config.RequestConnectionIDOmission,
- ConnectionIDLength: connIDLen,
- MaxReceiveStreamFlowControlWindow: maxReceiveStreamFlowControlWindow,
- MaxReceiveConnectionFlowControlWindow: maxReceiveConnectionFlowControlWindow,
- MaxIncomingStreams: maxIncomingStreams,
- MaxIncomingUniStreams: maxIncomingUniStreams,
- KeepAlive: config.KeepAlive,
- }
- }
- func (c *client) generateConnectionIDs() error {
- connIDLen := protocol.ConnectionIDLenGQUIC
- if c.version.UsesTLS() {
- connIDLen = c.config.ConnectionIDLength
- }
- srcConnID, err := generateConnectionID(connIDLen)
- if err != nil {
- return err
- }
- destConnID := srcConnID
- if c.version.UsesTLS() {
- destConnID, err = generateConnectionIDForInitial()
- if err != nil {
- return err
- }
- }
- c.srcConnID = srcConnID
- c.destConnID = destConnID
- if c.version == protocol.Version44 {
- c.srcConnID = nil
- }
- return nil
- }
- func (c *client) dial(ctx context.Context) error {
- c.logger.Infof("Starting new connection to %s (%s -> %s), source connection ID %s, destination connection ID %s, version %s", c.hostname, c.conn.LocalAddr(), c.conn.RemoteAddr(), c.srcConnID, c.destConnID, c.version)
- var err error
- if c.version.UsesTLS() {
- err = c.dialTLS(ctx)
- } else {
- err = c.dialGQUIC(ctx)
- }
- return err
- }
- func (c *client) dialGQUIC(ctx context.Context) error {
- if err := c.createNewGQUICSession(); err != nil {
- return err
- }
- err := c.establishSecureConnection(ctx)
- if err == errCloseSessionForNewVersion {
- return c.dial(ctx)
- }
- return err
- }
- func (c *client) dialTLS(ctx context.Context) error {
- params := &handshake.TransportParameters{
- StreamFlowControlWindow: protocol.ReceiveStreamFlowControlWindow,
- ConnectionFlowControlWindow: protocol.ReceiveConnectionFlowControlWindow,
- IdleTimeout: c.config.IdleTimeout,
- OmitConnectionID: c.config.RequestConnectionIDOmission,
- MaxBidiStreams: uint16(c.config.MaxIncomingStreams),
- MaxUniStreams: uint16(c.config.MaxIncomingUniStreams),
- DisableMigration: true,
- }
- extHandler := handshake.NewExtensionHandlerClient(params, c.initialVersion, c.config.Versions, c.version, c.logger)
- mintConf, err := tlsToMintConfig(c.tlsConf, protocol.PerspectiveClient)
- if err != nil {
- return err
- }
- mintConf.ExtensionHandler = extHandler
- mintConf.ServerName = c.hostname
- c.mintConf = mintConf
- if err := c.createNewTLSSession(extHandler.GetPeerParams(), c.version); err != nil {
- return err
- }
- err = c.establishSecureConnection(ctx)
- if err == errCloseSessionForRetry || err == errCloseSessionForNewVersion {
- return c.dial(ctx)
- }
- return err
- }
- // establishSecureConnection runs the session, and tries to establish a secure connection
- // It returns:
- // - errCloseSessionForNewVersion when the server sends a version negotiation packet
- // - handshake.ErrCloseSessionForRetry when the server performs a stateless retry (for IETF QUIC)
- // - any other error that might occur
- // - when the connection is secure (for gQUIC), or forward-secure (for IETF QUIC)
- func (c *client) establishSecureConnection(ctx context.Context) error {
- errorChan := make(chan error, 1)
- go func() {
- err := c.session.run() // returns as soon as the session is closed
- if err != errCloseSessionForRetry && err != errCloseSessionForNewVersion && c.createdPacketConn {
- c.conn.Close()
- }
- errorChan <- err
- }()
- select {
- case <-ctx.Done():
- // The session will send a PeerGoingAway error to the server.
- c.session.Close()
- return ctx.Err()
- case err := <-errorChan:
- return err
- case <-c.handshakeChan:
- // handshake successfully completed
- return nil
- }
- }
- func (c *client) handlePacket(p *receivedPacket) {
- if err := c.handlePacketImpl(p); err != nil {
- c.logger.Errorf("error handling packet: %s", err)
- }
- }
- func (c *client) handlePacketImpl(p *receivedPacket) error {
- c.mutex.Lock()
- defer c.mutex.Unlock()
- // handle Version Negotiation Packets
- if p.header.IsVersionNegotiation {
- err := c.handleVersionNegotiationPacket(p.header)
- if err != nil {
- c.session.destroy(err)
- }
- // version negotiation packets have no payload
- return err
- }
- if !c.version.UsesIETFHeaderFormat() {
- connID := p.header.DestConnectionID
- // reject packets with truncated connection id if we didn't request truncation
- if !c.config.RequestConnectionIDOmission && connID.Len() == 0 {
- return errors.New("received packet with truncated connection ID, but didn't request truncation")
- }
- // reject packets with the wrong connection ID
- if connID.Len() > 0 && !connID.Equal(c.srcConnID) {
- return fmt.Errorf("received a packet with an unexpected connection ID (%s, expected %s)", connID, c.srcConnID)
- }
- if p.header.ResetFlag {
- return c.handlePublicReset(p)
- }
- } else {
- // reject packets with the wrong connection ID
- if !p.header.DestConnectionID.Equal(c.srcConnID) {
- return fmt.Errorf("received a packet with an unexpected connection ID (%s, expected %s)", p.header.DestConnectionID, c.srcConnID)
- }
- }
- if p.header.IsLongHeader {
- switch p.header.Type {
- case protocol.PacketTypeRetry:
- c.handleRetryPacket(p.header)
- return nil
- case protocol.PacketTypeHandshake, protocol.PacketType0RTT:
- default:
- return fmt.Errorf("Received unsupported packet type: %s", p.header.Type)
- }
- }
- // this is the first packet we are receiving
- // since it is not a Version Negotiation Packet, this means the server supports the suggested version
- if !c.versionNegotiated {
- c.versionNegotiated = true
- }
- c.session.handlePacket(p)
- return nil
- }
- func (c *client) handlePublicReset(p *receivedPacket) error {
- cr := c.conn.RemoteAddr()
- // check if the remote address and the connection ID match
- // otherwise this might be an attacker trying to inject a PUBLIC_RESET to kill the connection
- if cr.Network() != p.remoteAddr.Network() || cr.String() != p.remoteAddr.String() || !p.header.DestConnectionID.Equal(c.srcConnID) {
- return errors.New("Received a spoofed Public Reset")
- }
- pr, err := wire.ParsePublicReset(bytes.NewReader(p.data))
- if err != nil {
- return fmt.Errorf("Received a Public Reset. An error occurred parsing the packet: %s", err)
- }
- c.session.closeRemote(qerr.Error(qerr.PublicReset, fmt.Sprintf("Received a Public Reset for packet number %#x", pr.RejectedPacketNumber)))
- c.logger.Infof("Received Public Reset, rejected packet number: %#x", pr.RejectedPacketNumber)
- return nil
- }
- func (c *client) handleVersionNegotiationPacket(hdr *wire.Header) error {
- // ignore delayed / duplicated version negotiation packets
- if c.receivedVersionNegotiationPacket || c.versionNegotiated {
- c.logger.Debugf("Received a delayed Version Negotiation Packet.")
- return nil
- }
- for _, v := range hdr.SupportedVersions {
- if v == c.version {
- // the version negotiation packet contains the version that we offered
- // this might be a packet sent by an attacker (or by a terribly broken server implementation)
- // ignore it
- return nil
- }
- }
- c.logger.Infof("Received a Version Negotiation Packet. Supported Versions: %s", hdr.SupportedVersions)
- newVersion, ok := protocol.ChooseSupportedVersion(c.config.Versions, hdr.SupportedVersions)
- if !ok {
- return qerr.InvalidVersion
- }
- c.receivedVersionNegotiationPacket = true
- c.negotiatedVersions = hdr.SupportedVersions
- // switch to negotiated version
- c.initialVersion = c.version
- c.version = newVersion
- if err := c.generateConnectionIDs(); err != nil {
- return err
- }
- c.logger.Infof("Switching to QUIC version %s. New connection ID: %s", newVersion, c.destConnID)
- c.session.destroy(errCloseSessionForNewVersion)
- return nil
- }
- func (c *client) handleRetryPacket(hdr *wire.Header) {
- c.logger.Debugf("<- Received Retry")
- hdr.Log(c.logger)
- // A server that performs multiple retries must use a source connection ID of at least 8 bytes.
- // Only a server that won't send additional Retries can use shorter connection IDs.
- if hdr.OrigDestConnectionID.Len() < protocol.MinConnectionIDLenInitial {
- c.logger.Debugf("Received a Retry with a too short Original Destination Connection ID: %d bytes, must have at least %d bytes.", hdr.OrigDestConnectionID.Len(), protocol.MinConnectionIDLenInitial)
- return
- }
- if !hdr.OrigDestConnectionID.Equal(c.destConnID) {
- c.logger.Debugf("Received spoofed Retry. Original Destination Connection ID: %s, expected: %s", hdr.OrigDestConnectionID, c.destConnID)
- return
- }
- c.numRetries++
- if c.numRetries > protocol.MaxRetries {
- c.session.destroy(qerr.CryptoTooManyRejects)
- return
- }
- c.destConnID = hdr.SrcConnectionID
- c.token = hdr.Token
- c.session.destroy(errCloseSessionForRetry)
- }
- func (c *client) createNewGQUICSession() error {
- c.mutex.Lock()
- defer c.mutex.Unlock()
- runner := &runner{
- onHandshakeCompleteImpl: func(_ Session) { close(c.handshakeChan) },
- removeConnectionIDImpl: c.closeCallback,
- }
- sess, err := newClientSession(
- c.conn,
- runner,
- c.hostname,
- c.version,
- c.destConnID,
- c.srcConnID,
- c.tlsConf,
- c.config,
- c.initialVersion,
- c.negotiatedVersions,
- c.logger,
- )
- if err != nil {
- return err
- }
- c.session = sess
- c.packetHandlers.Add(c.srcConnID, c)
- if c.config.RequestConnectionIDOmission {
- c.packetHandlers.Add(protocol.ConnectionID{}, c)
- }
- return nil
- }
- func (c *client) createNewTLSSession(
- paramsChan <-chan handshake.TransportParameters,
- version protocol.VersionNumber,
- ) error {
- c.mutex.Lock()
- defer c.mutex.Unlock()
- runner := &runner{
- onHandshakeCompleteImpl: func(_ Session) { close(c.handshakeChan) },
- removeConnectionIDImpl: c.closeCallback,
- }
- sess, err := newTLSClientSession(
- c.conn,
- runner,
- c.token,
- c.destConnID,
- c.srcConnID,
- c.config,
- c.mintConf,
- paramsChan,
- 1,
- c.logger,
- c.version,
- )
- if err != nil {
- return err
- }
- c.session = sess
- c.packetHandlers.Add(c.srcConnID, c)
- return nil
- }
- func (c *client) Close() error {
- c.mutex.Lock()
- defer c.mutex.Unlock()
- if c.session == nil {
- return nil
- }
- return c.session.Close()
- }
- func (c *client) destroy(e error) {
- c.mutex.Lock()
- defer c.mutex.Unlock()
- if c.session == nil {
- return
- }
- c.session.destroy(e)
- }
- func (c *client) GetVersion() protocol.VersionNumber {
- c.mutex.Lock()
- v := c.version
- c.mutex.Unlock()
- return v
- }
- func (c *client) GetPerspective() protocol.Perspective {
- return protocol.PerspectiveClient
- }
|