330 lines
9.0 KiB
Go
330 lines
9.0 KiB
Go
package quic
|
|
|
|
import (
|
|
"context"
|
|
"crypto/tls"
|
|
"errors"
|
|
"fmt"
|
|
"net"
|
|
"strings"
|
|
|
|
"github.com/lucas-clemente/quic-go/internal/protocol"
|
|
"github.com/lucas-clemente/quic-go/internal/utils"
|
|
"github.com/lucas-clemente/quic-go/logging"
|
|
)
|
|
|
|
type client struct {
|
|
conn sendConn
|
|
// 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
|
|
|
|
use0RTT bool
|
|
|
|
packetHandlers packetHandlerManager
|
|
|
|
tlsConf *tls.Config
|
|
config *Config
|
|
|
|
srcConnID protocol.ConnectionID
|
|
destConnID protocol.ConnectionID
|
|
|
|
initialPacketNumber protocol.PacketNumber
|
|
hasNegotiatedVersion bool
|
|
version protocol.VersionNumber
|
|
|
|
handshakeChan chan struct{}
|
|
|
|
session quicSession
|
|
|
|
tracer logging.ConnectionTracer
|
|
logger utils.Logger
|
|
}
|
|
|
|
var (
|
|
// make it possible to mock connection ID generation in the tests
|
|
generateConnectionID = protocol.GenerateConnectionID
|
|
generateConnectionIDForInitial = protocol.GenerateConnectionIDForInitial
|
|
)
|
|
|
|
// DialAddr establishes a new QUIC connection to a server.
|
|
// It uses a new UDP connection and closes this connection when the QUIC session is closed.
|
|
// The hostname for SNI is taken from the given address.
|
|
// The tls.Config.CipherSuites allows setting of TLS 1.3 cipher suites.
|
|
func DialAddr(
|
|
addr string,
|
|
tlsConf *tls.Config,
|
|
config *Config,
|
|
) (Session, error) {
|
|
return DialAddrContext(context.Background(), addr, tlsConf, config)
|
|
}
|
|
|
|
// DialAddrEarly establishes a new 0-RTT QUIC connection to a server.
|
|
// It uses a new UDP connection and closes this connection when the QUIC session is closed.
|
|
// The hostname for SNI is taken from the given address.
|
|
// The tls.Config.CipherSuites allows setting of TLS 1.3 cipher suites.
|
|
func DialAddrEarly(
|
|
addr string,
|
|
tlsConf *tls.Config,
|
|
config *Config,
|
|
) (EarlySession, error) {
|
|
return DialAddrEarlyContext(context.Background(), addr, tlsConf, config)
|
|
}
|
|
|
|
// DialAddrEarlyContext establishes a new 0-RTT QUIC connection to a server using provided context.
|
|
// See DialAddrEarly for details
|
|
func DialAddrEarlyContext(
|
|
ctx context.Context,
|
|
addr string,
|
|
tlsConf *tls.Config,
|
|
config *Config,
|
|
) (EarlySession, error) {
|
|
sess, err := dialAddrContext(ctx, addr, tlsConf, config, true)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
utils.Logger.WithPrefix(utils.DefaultLogger, "client").Debugf("Returning early session")
|
|
return sess, nil
|
|
}
|
|
|
|
// DialAddrContext establishes a new QUIC connection to a server using the provided context.
|
|
// See DialAddr for details.
|
|
func DialAddrContext(
|
|
ctx context.Context,
|
|
addr string,
|
|
tlsConf *tls.Config,
|
|
config *Config,
|
|
) (Session, error) {
|
|
return dialAddrContext(ctx, addr, tlsConf, config, false)
|
|
}
|
|
|
|
func dialAddrContext(
|
|
ctx context.Context,
|
|
addr string,
|
|
tlsConf *tls.Config,
|
|
config *Config,
|
|
use0RTT bool,
|
|
) (quicSession, 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, use0RTT, true)
|
|
}
|
|
|
|
// 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 same PacketConn can be used for multiple calls to Dial and
|
|
// Listen, QUIC connection IDs are used for demultiplexing the different
|
|
// connections. The host parameter is used for SNI. The tls.Config must define
|
|
// an application protocol (using NextProtos).
|
|
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, false, false)
|
|
}
|
|
|
|
// DialEarly establishes a new 0-RTT QUIC connection to a server using a net.PacketConn.
|
|
// The same PacketConn can be used for multiple calls to Dial and Listen,
|
|
// QUIC connection IDs are used for demultiplexing the different connections.
|
|
// The host parameter is used for SNI.
|
|
// The tls.Config must define an application protocol (using NextProtos).
|
|
func DialEarly(
|
|
pconn net.PacketConn,
|
|
remoteAddr net.Addr,
|
|
host string,
|
|
tlsConf *tls.Config,
|
|
config *Config,
|
|
) (EarlySession, error) {
|
|
return DialEarlyContext(context.Background(), pconn, remoteAddr, host, tlsConf, config)
|
|
}
|
|
|
|
// DialEarlyContext establishes a new 0-RTT QUIC connection to a server using a net.PacketConn using the provided context.
|
|
// See DialEarly for details.
|
|
func DialEarlyContext(
|
|
ctx context.Context,
|
|
pconn net.PacketConn,
|
|
remoteAddr net.Addr,
|
|
host string,
|
|
tlsConf *tls.Config,
|
|
config *Config,
|
|
) (EarlySession, error) {
|
|
return dialContext(ctx, pconn, remoteAddr, host, tlsConf, config, true, false)
|
|
}
|
|
|
|
// DialContext establishes a new QUIC connection to a server using a net.PacketConn using the provided context.
|
|
// See Dial for details.
|
|
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, false)
|
|
}
|
|
|
|
func dialContext(
|
|
ctx context.Context,
|
|
pconn net.PacketConn,
|
|
remoteAddr net.Addr,
|
|
host string,
|
|
tlsConf *tls.Config,
|
|
config *Config,
|
|
use0RTT bool,
|
|
createdPacketConn bool,
|
|
) (quicSession, error) {
|
|
if tlsConf == nil {
|
|
return nil, errors.New("quic: tls.Config not set")
|
|
}
|
|
if err := validateConfig(config); err != nil {
|
|
return nil, err
|
|
}
|
|
config = populateClientConfig(config, createdPacketConn)
|
|
packetHandlers, err := getMultiplexer().AddConn(pconn, config.ConnectionIDLength, config.StatelessResetKey, config.Tracer)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
c, err := newClient(pconn, remoteAddr, config, tlsConf, host, use0RTT, createdPacketConn)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
c.packetHandlers = packetHandlers
|
|
|
|
if c.config.Tracer != nil {
|
|
c.tracer = c.config.Tracer.TracerForConnection(protocol.PerspectiveClient, c.destConnID)
|
|
}
|
|
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,
|
|
use0RTT bool,
|
|
createdPacketConn bool,
|
|
) (*client, error) {
|
|
if tlsConf == nil {
|
|
tlsConf = &tls.Config{}
|
|
}
|
|
if tlsConf.ServerName == "" {
|
|
sni := host
|
|
if strings.IndexByte(sni, ':') != -1 {
|
|
var err error
|
|
sni, _, err = net.SplitHostPort(sni)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
tlsConf.ServerName = sni
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
}
|
|
}
|
|
|
|
srcConnID, err := generateConnectionID(config.ConnectionIDLength)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
destConnID, err := generateConnectionIDForInitial()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
c := &client{
|
|
srcConnID: srcConnID,
|
|
destConnID: destConnID,
|
|
conn: newSendPconn(pconn, remoteAddr),
|
|
createdPacketConn: createdPacketConn,
|
|
use0RTT: use0RTT,
|
|
tlsConf: tlsConf,
|
|
config: config,
|
|
version: config.Versions[0],
|
|
handshakeChan: make(chan struct{}),
|
|
logger: utils.DefaultLogger.WithPrefix("client"),
|
|
}
|
|
return c, 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.tlsConf.ServerName, c.conn.LocalAddr(), c.conn.RemoteAddr(), c.srcConnID, c.destConnID, c.version)
|
|
if c.tracer != nil {
|
|
c.tracer.StartedConnection(c.conn.LocalAddr(), c.conn.RemoteAddr(), c.version, c.srcConnID, c.destConnID)
|
|
}
|
|
|
|
c.session = newClientSession(
|
|
c.conn,
|
|
c.packetHandlers,
|
|
c.destConnID,
|
|
c.srcConnID,
|
|
c.config,
|
|
c.tlsConf,
|
|
c.initialPacketNumber,
|
|
c.use0RTT,
|
|
c.hasNegotiatedVersion,
|
|
c.tracer,
|
|
c.logger,
|
|
c.version,
|
|
)
|
|
c.packetHandlers.Add(c.srcConnID, c.session)
|
|
|
|
errorChan := make(chan error, 1)
|
|
go func() {
|
|
err := c.session.run() // returns as soon as the session is closed
|
|
if !errors.Is(err, errCloseForRecreating{}) && c.createdPacketConn {
|
|
c.packetHandlers.Destroy()
|
|
}
|
|
errorChan <- err
|
|
}()
|
|
|
|
// only set when we're using 0-RTT
|
|
// Otherwise, earlySessionChan will be nil. Receiving from a nil chan blocks forever.
|
|
var earlySessionChan <-chan struct{}
|
|
if c.use0RTT {
|
|
earlySessionChan = c.session.earlySessionReady()
|
|
}
|
|
|
|
select {
|
|
case <-ctx.Done():
|
|
c.session.shutdown()
|
|
return ctx.Err()
|
|
case err := <-errorChan:
|
|
var recreateErr *errCloseForRecreating
|
|
if errors.As(err, &recreateErr) {
|
|
c.initialPacketNumber = recreateErr.nextPacketNumber
|
|
c.version = recreateErr.nextVersion
|
|
c.hasNegotiatedVersion = true
|
|
return c.dial(ctx)
|
|
}
|
|
return err
|
|
case <-earlySessionChan:
|
|
// ready to send 0-RTT data
|
|
return nil
|
|
case <-c.session.HandshakeComplete().Done():
|
|
// handshake successfully completed
|
|
return nil
|
|
}
|
|
}
|