|
|
@@ -2,6 +2,7 @@ package quic
|
|
|
|
|
|
import (
|
|
|
"bytes"
|
|
|
+ "context"
|
|
|
"crypto/tls"
|
|
|
"errors"
|
|
|
"fmt"
|
|
|
@@ -23,6 +24,8 @@ type client struct {
|
|
|
conn connection
|
|
|
hostname string
|
|
|
|
|
|
+ receivedRetry bool
|
|
|
+
|
|
|
versionNegotiated bool // has the server accepted our version
|
|
|
receivedVersionNegotiationPacket bool
|
|
|
negotiatedVersions []protocol.VersionNumber // the list of versions from the version negotiation packet
|
|
|
@@ -39,11 +42,13 @@ type client struct {
|
|
|
|
|
|
handshakeChan chan struct{}
|
|
|
|
|
|
- session packetHandler
|
|
|
+ session quicSession
|
|
|
|
|
|
logger utils.Logger
|
|
|
}
|
|
|
|
|
|
+var _ packetHandler = &client{}
|
|
|
+
|
|
|
var (
|
|
|
// make it possible to mock connection ID generation in the tests
|
|
|
generateConnectionID = protocol.GenerateConnectionID
|
|
|
@@ -52,7 +57,22 @@ var (
|
|
|
|
|
|
// 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) {
|
|
|
+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
|
|
|
@@ -61,7 +81,15 @@ func DialAddr(addr string, tlsConf *tls.Config, config *Config) (Session, error)
|
|
|
if err != nil {
|
|
|
return nil, err
|
|
|
}
|
|
|
- return Dial(udpConn, udpAddr, addr, tlsConf, config)
|
|
|
+ c, err := newClient(udpConn, udpAddr, config, tlsConf, addr)
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ go c.listen()
|
|
|
+ if err := c.dial(ctx); err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ return c.session, nil
|
|
|
}
|
|
|
|
|
|
// Dial establishes a new QUIC connection to a server using a net.PacketConn.
|
|
|
@@ -73,6 +101,31 @@ func Dial(
|
|
|
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) {
|
|
|
+ c, err := newClient(pconn, remoteAddr, config, tlsConf, host)
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ getClientMultiplexer().Add(pconn, c.srcConnID, c)
|
|
|
+ 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) (*client, error) {
|
|
|
clientConfig := populateClientConfig(config)
|
|
|
version := clientConfig.Versions[0]
|
|
|
srcConnID, err := generateConnectionID()
|
|
|
@@ -106,7 +159,7 @@ func Dial(
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
- c := &client{
|
|
|
+ return &client{
|
|
|
conn: &conn{pconn: pconn, currentAddr: remoteAddr},
|
|
|
srcConnID: srcConnID,
|
|
|
destConnID: destConnID,
|
|
|
@@ -116,14 +169,7 @@ func Dial(
|
|
|
version: version,
|
|
|
handshakeChan: make(chan struct{}),
|
|
|
logger: utils.DefaultLogger.WithPrefix("client"),
|
|
|
- }
|
|
|
-
|
|
|
- c.logger.Infof("Starting new connection to %s (%s -> %s), source connection ID %s, destination connection ID %s, version %s", hostname, c.conn.LocalAddr(), c.conn.RemoteAddr(), c.srcConnID, c.destConnID, c.version)
|
|
|
-
|
|
|
- if err := c.dial(); err != nil {
|
|
|
- return nil, err
|
|
|
- }
|
|
|
- return c.session, nil
|
|
|
+ }, nil
|
|
|
}
|
|
|
|
|
|
// populateClientConfig populates fields in the quic.Config with their default values, if none are set
|
|
|
@@ -180,28 +226,29 @@ func populateClientConfig(config *Config) *Config {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-func (c *client) dial() error {
|
|
|
+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()
|
|
|
+ err = c.dialTLS(ctx)
|
|
|
} else {
|
|
|
- err = c.dialGQUIC()
|
|
|
+ err = c.dialGQUIC(ctx)
|
|
|
}
|
|
|
if err == errCloseSessionForNewVersion {
|
|
|
- return c.dial()
|
|
|
+ return c.dial(ctx)
|
|
|
}
|
|
|
return err
|
|
|
}
|
|
|
|
|
|
-func (c *client) dialGQUIC() error {
|
|
|
+func (c *client) dialGQUIC(ctx context.Context) error {
|
|
|
if err := c.createNewGQUICSession(); err != nil {
|
|
|
return err
|
|
|
}
|
|
|
- go c.listen()
|
|
|
- return c.establishSecureConnection()
|
|
|
+ return c.establishSecureConnection(ctx)
|
|
|
}
|
|
|
|
|
|
-func (c *client) dialTLS() error {
|
|
|
+func (c *client) dialTLS(ctx context.Context) error {
|
|
|
params := &handshake.TransportParameters{
|
|
|
StreamFlowControlWindow: protocol.ReceiveStreamFlowControlWindow,
|
|
|
ConnectionFlowControlWindow: protocol.ReceiveConnectionFlowControlWindow,
|
|
|
@@ -223,16 +270,18 @@ func (c *client) dialTLS() error {
|
|
|
if err := c.createNewTLSSession(extHandler.GetPeerParams(), c.version); err != nil {
|
|
|
return err
|
|
|
}
|
|
|
- go c.listen()
|
|
|
- if err := c.establishSecureConnection(); err != nil {
|
|
|
+ if err := c.establishSecureConnection(ctx); err != nil {
|
|
|
if err != handshake.ErrCloseSessionForRetry {
|
|
|
return err
|
|
|
}
|
|
|
c.logger.Infof("Received a Retry packet. Recreating session.")
|
|
|
+ c.mutex.Lock()
|
|
|
+ c.receivedRetry = true
|
|
|
+ c.mutex.Unlock()
|
|
|
if err := c.createNewTLSSession(extHandler.GetPeerParams(), c.version); err != nil {
|
|
|
return err
|
|
|
}
|
|
|
- if err := c.establishSecureConnection(); err != nil {
|
|
|
+ if err := c.establishSecureConnection(ctx); err != nil {
|
|
|
return err
|
|
|
}
|
|
|
}
|
|
|
@@ -245,7 +294,7 @@ func (c *client) dialTLS() error {
|
|
|
// - 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() error {
|
|
|
+func (c *client) establishSecureConnection(ctx context.Context) error {
|
|
|
errorChan := make(chan error, 1)
|
|
|
|
|
|
go func() {
|
|
|
@@ -254,6 +303,10 @@ func (c *client) establishSecureConnection() error {
|
|
|
}()
|
|
|
|
|
|
select {
|
|
|
+ case <-ctx.Done():
|
|
|
+ // The session sending a PeerGoingAway error to the server.
|
|
|
+ c.session.Close(nil)
|
|
|
+ return ctx.Err()
|
|
|
case err := <-errorChan:
|
|
|
return err
|
|
|
case <-c.handshakeChan:
|
|
|
@@ -285,65 +338,79 @@ func (c *client) listen() {
|
|
|
}
|
|
|
break
|
|
|
}
|
|
|
- if err := c.handlePacket(addr, data[:n]); err != nil {
|
|
|
- c.logger.Errorf("error handling packet: %s", err.Error())
|
|
|
- }
|
|
|
+ c.handleRead(addr, data[:n])
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-func (c *client) handlePacket(remoteAddr net.Addr, packet []byte) error {
|
|
|
+func (c *client) handleRead(remoteAddr net.Addr, packet []byte) {
|
|
|
rcvTime := time.Now()
|
|
|
|
|
|
r := bytes.NewReader(packet)
|
|
|
hdr, err := wire.ParseHeaderSentByServer(r)
|
|
|
// drop the packet if we can't parse the header
|
|
|
if err != nil {
|
|
|
- return fmt.Errorf("error parsing packet from %s: %s", remoteAddr.String(), err.Error())
|
|
|
- }
|
|
|
- // reject packets with truncated connection id if we didn't request truncation
|
|
|
- if hdr.OmitConnectionID && !c.config.RequestConnectionIDOmission {
|
|
|
- return errors.New("received packet with truncated connection ID, but didn't request truncation")
|
|
|
+ c.logger.Errorf("error handling packet: %s", err)
|
|
|
+ return
|
|
|
}
|
|
|
hdr.Raw = packet[:len(packet)-r.Len()]
|
|
|
packetData := packet[len(packet)-r.Len():]
|
|
|
+ c.handlePacket(&receivedPacket{
|
|
|
+ remoteAddr: remoteAddr,
|
|
|
+ header: hdr,
|
|
|
+ data: packetData,
|
|
|
+ rcvTime: rcvTime,
|
|
|
+ })
|
|
|
+}
|
|
|
|
|
|
+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 hdr.IsVersionNegotiation {
|
|
|
+ if p.header.IsVersionNegotiation {
|
|
|
// ignore delayed / duplicated version negotiation packets
|
|
|
if c.receivedVersionNegotiationPacket || c.versionNegotiated {
|
|
|
return errors.New("received a delayed Version Negotiation Packet")
|
|
|
}
|
|
|
|
|
|
// version negotiation packets have no payload
|
|
|
- if err := c.handleVersionNegotiationPacket(hdr); err != nil {
|
|
|
+ if err := c.handleVersionNegotiationPacket(p.header); err != nil {
|
|
|
c.session.Close(err)
|
|
|
}
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
- if hdr.IsPublicHeader {
|
|
|
- return c.handleGQUICPacket(hdr, r, packetData, remoteAddr, rcvTime)
|
|
|
+ if p.header.IsPublicHeader {
|
|
|
+ return c.handleGQUICPacket(p)
|
|
|
}
|
|
|
- return c.handleIETFQUICPacket(hdr, packetData, remoteAddr, rcvTime)
|
|
|
+ return c.handleIETFQUICPacket(p)
|
|
|
}
|
|
|
|
|
|
-func (c *client) handleIETFQUICPacket(hdr *wire.Header, packetData []byte, remoteAddr net.Addr, rcvTime time.Time) error {
|
|
|
+func (c *client) handleIETFQUICPacket(p *receivedPacket) error {
|
|
|
// reject packets with the wrong connection ID
|
|
|
- if !hdr.DestConnectionID.Equal(c.srcConnID) {
|
|
|
- return fmt.Errorf("received a packet with an unexpected connection ID (%s, expected %s)", hdr.DestConnectionID, c.srcConnID)
|
|
|
- }
|
|
|
- if hdr.IsLongHeader {
|
|
|
- if hdr.Type != protocol.PacketTypeRetry && hdr.Type != protocol.PacketTypeHandshake {
|
|
|
- return fmt.Errorf("Received unsupported packet type: %s", hdr.Type)
|
|
|
+ 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:
|
|
|
+ if c.receivedRetry {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ case protocol.PacketTypeHandshake:
|
|
|
+ default:
|
|
|
+ return fmt.Errorf("Received unsupported packet type: %s", p.header.Type)
|
|
|
}
|
|
|
- c.logger.Debugf("len(packet data): %d, payloadLen: %d", len(packetData), hdr.PayloadLen)
|
|
|
- if protocol.ByteCount(len(packetData)) < hdr.PayloadLen {
|
|
|
- return fmt.Errorf("packet payload (%d bytes) is smaller than the expected payload length (%d bytes)", len(packetData), hdr.PayloadLen)
|
|
|
+ if protocol.ByteCount(len(p.data)) < p.header.PayloadLen {
|
|
|
+ return fmt.Errorf("packet payload (%d bytes) is smaller than the expected payload length (%d bytes)", len(p.data), p.header.PayloadLen)
|
|
|
}
|
|
|
- packetData = packetData[:int(hdr.PayloadLen)]
|
|
|
+ p.data = p.data[:int(p.header.PayloadLen)]
|
|
|
// TODO(#1312): implement parsing of compound packets
|
|
|
}
|
|
|
|
|
|
@@ -353,29 +420,29 @@ func (c *client) handleIETFQUICPacket(hdr *wire.Header, packetData []byte, remot
|
|
|
c.versionNegotiated = true
|
|
|
}
|
|
|
|
|
|
- c.session.handlePacket(&receivedPacket{
|
|
|
- remoteAddr: remoteAddr,
|
|
|
- header: hdr,
|
|
|
- data: packetData,
|
|
|
- rcvTime: rcvTime,
|
|
|
- })
|
|
|
+ c.session.handlePacket(p)
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
-func (c *client) handleGQUICPacket(hdr *wire.Header, r *bytes.Reader, packetData []byte, remoteAddr net.Addr, rcvTime time.Time) error {
|
|
|
+func (c *client) handleGQUICPacket(p *receivedPacket) error {
|
|
|
+ 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 !hdr.OmitConnectionID && !hdr.DestConnectionID.Equal(c.srcConnID) {
|
|
|
- return fmt.Errorf("received a packet with an unexpected connection ID (%s, expected %s)", hdr.DestConnectionID, c.srcConnID)
|
|
|
+ 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 hdr.ResetFlag {
|
|
|
+ if p.header.ResetFlag {
|
|
|
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() != remoteAddr.Network() || cr.String() != remoteAddr.String() || !hdr.DestConnectionID.Equal(c.srcConnID) {
|
|
|
+ if cr.Network() != p.remoteAddr.Network() || cr.String() != p.remoteAddr.String() || !connID.Equal(c.srcConnID) {
|
|
|
return errors.New("Received a spoofed Public Reset")
|
|
|
}
|
|
|
- pr, err := wire.ParsePublicReset(r)
|
|
|
+ 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)
|
|
|
}
|
|
|
@@ -390,12 +457,7 @@ func (c *client) handleGQUICPacket(hdr *wire.Header, r *bytes.Reader, packetData
|
|
|
c.versionNegotiated = true
|
|
|
}
|
|
|
|
|
|
- c.session.handlePacket(&receivedPacket{
|
|
|
- remoteAddr: remoteAddr,
|
|
|
- header: hdr,
|
|
|
- data: packetData,
|
|
|
- rcvTime: rcvTime,
|
|
|
- })
|
|
|
+ c.session.handlePacket(p)
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
@@ -439,7 +501,7 @@ func (c *client) createNewGQUICSession() (err error) {
|
|
|
c.mutex.Lock()
|
|
|
defer c.mutex.Unlock()
|
|
|
runner := &runner{
|
|
|
- onHandshakeCompleteImpl: func(_ packetHandler) { close(c.handshakeChan) },
|
|
|
+ onHandshakeCompleteImpl: func(_ Session) { close(c.handshakeChan) },
|
|
|
removeConnectionIDImpl: func(protocol.ConnectionID) {},
|
|
|
}
|
|
|
c.session, err = newClientSession(
|
|
|
@@ -464,7 +526,7 @@ func (c *client) createNewTLSSession(
|
|
|
c.mutex.Lock()
|
|
|
defer c.mutex.Unlock()
|
|
|
runner := &runner{
|
|
|
- onHandshakeCompleteImpl: func(_ packetHandler) { close(c.handshakeChan) },
|
|
|
+ onHandshakeCompleteImpl: func(_ Session) { close(c.handshakeChan) },
|
|
|
removeConnectionIDImpl: func(protocol.ConnectionID) {},
|
|
|
}
|
|
|
c.session, err = newTLSClientSession(
|
|
|
@@ -482,3 +544,12 @@ func (c *client) createNewTLSSession(
|
|
|
)
|
|
|
return err
|
|
|
}
|
|
|
+
|
|
|
+func (c *client) Close(err error) error {
|
|
|
+ c.mutex.Lock()
|
|
|
+ defer c.mutex.Unlock()
|
|
|
+ if c.session == nil {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ return c.session.Close(err)
|
|
|
+}
|