add DialEarly and DialAddrEarly to dial 0-RTT connections

This commit is contained in:
Marten Seemann
2019-08-15 13:49:01 +07:00
parent 951477af92
commit ce7583f813
3 changed files with 102 additions and 5 deletions

View File

@@ -22,6 +22,8 @@ type client struct {
// If it is started with Dial, we take a packet conn as a parameter.
createdPacketConn bool
use0RTT bool
packetHandlers packetHandlerManager
versionNegotiated utils.AtomicBool // has the server accepted our version
@@ -65,6 +67,18 @@ func DialAddr(
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.
func DialAddrEarly(
addr string,
tlsConf *tls.Config,
config *Config,
) (EarlySession, error) {
defer utils.Logger.WithPrefix(utils.DefaultLogger, "client").Debugf("Returning early session")
return dialAddrContext(context.Background(), addr, tlsConf, config, true)
}
// DialAddrContext establishes a new QUIC connection to a server using the provided context.
// See DialAddr for details.
func DialAddrContext(
@@ -73,6 +87,16 @@ func DialAddrContext(
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
@@ -81,7 +105,7 @@ func DialAddrContext(
if err != nil {
return nil, err
}
return dialContext(ctx, udpConn, udpAddr, addr, tlsConf, config, true)
return dialContext(ctx, udpConn, udpAddr, addr, tlsConf, config, use0RTT, true)
}
// Dial establishes a new QUIC connection to a server using a net.PacketConn.
@@ -96,7 +120,22 @@ func Dial(
tlsConf *tls.Config,
config *Config,
) (Session, error) {
return DialContext(context.Background(), pconn, remoteAddr, host, tlsConf, config)
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,
) (Session, error) {
return dialContext(context.Background(), pconn, remoteAddr, host, tlsConf, config, true, false)
}
// DialContext establishes a new QUIC connection to a server using a net.PacketConn using the provided context.
@@ -109,7 +148,7 @@ func DialContext(
tlsConf *tls.Config,
config *Config,
) (Session, error) {
return dialContext(ctx, pconn, remoteAddr, host, tlsConf, config, false)
return dialContext(ctx, pconn, remoteAddr, host, tlsConf, config, false, false)
}
func dialContext(
@@ -119,8 +158,9 @@ func dialContext(
host string,
tlsConf *tls.Config,
config *Config,
use0RTT bool,
createdPacketConn bool,
) (Session, error) {
) (quicSession, error) {
if tlsConf == nil {
return nil, errors.New("quic: tls.Config not set")
}
@@ -129,7 +169,7 @@ func dialContext(
if err != nil {
return nil, err
}
c, err := newClient(pconn, remoteAddr, config, tlsConf, host, createdPacketConn)
c, err := newClient(pconn, remoteAddr, config, tlsConf, host, use0RTT, createdPacketConn)
if err != nil {
return nil, err
}
@@ -146,6 +186,7 @@ func newClient(
config *Config,
tlsConf *tls.Config,
host string,
use0RTT bool,
createdPacketConn bool,
) (*client, error) {
if tlsConf == nil {
@@ -186,6 +227,7 @@ func newClient(
destConnID: destConnID,
conn: &conn{pconn: pconn, currentAddr: remoteAddr},
createdPacketConn: createdPacketConn,
use0RTT: use0RTT,
tlsConf: tlsConf,
config: config,
version: config.Versions[0],
@@ -231,6 +273,13 @@ func (c *client) establishSecureConnection(ctx context.Context) error {
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():
// The session will send a PeerGoingAway error to the server.
@@ -238,6 +287,9 @@ func (c *client) establishSecureConnection(ctx context.Context) error {
return ctx.Err()
case err := <-errorChan:
return err
case <-earlySessionChan:
// ready to send 0-RTT data
return nil
case <-c.session.HandshakeComplete().Done():
// handshake successfully completed
return nil

View File

@@ -257,6 +257,50 @@ var _ = Describe("Client", func() {
Eventually(run).Should(BeClosed())
})
It("returns early sessions", func() {
manager := NewMockPacketHandlerManager(mockCtrl)
manager.EXPECT().Add(gomock.Any(), gomock.Any())
mockMultiplexer.EXPECT().AddConn(packetConn, gomock.Any(), gomock.Any()).Return(manager, nil)
readyChan := make(chan struct{})
done := make(chan struct{})
newClientSession = func(
_ connection,
runner sessionRunner,
_ protocol.ConnectionID,
_ protocol.ConnectionID,
_ *Config,
_ *tls.Config,
_ protocol.PacketNumber,
_ protocol.VersionNumber,
_ utils.Logger,
_ protocol.VersionNumber,
) quicSession {
sess := NewMockQuicSession(mockCtrl)
sess.EXPECT().run().Do(func() { <-done })
sess.EXPECT().HandshakeComplete().Return(context.Background())
sess.EXPECT().earlySessionReady().Return(readyChan)
return sess
}
go func() {
defer GinkgoRecover()
defer close(done)
s, err := DialEarly(
packetConn,
addr,
"localhost:1337",
tlsConf,
&Config{},
)
Expect(err).ToNot(HaveOccurred())
Expect(s).ToNot(BeNil())
}()
Consistently(done).ShouldNot(BeClosed())
close(readyChan)
Eventually(done).Should(BeClosed())
})
It("returns an error that occurs while waiting for the handshake to complete", func() {
manager := NewMockPacketHandlerManager(mockCtrl)
manager.EXPECT().Add(gomock.Any(), gomock.Any())

View File

@@ -467,6 +467,7 @@ func (s *session) run() error {
s.scheduleSending()
if zeroRTTParams != nil {
s.processTransportParameters(zeroRTTParams)
close(s.earlySessionReadyChan)
}
case closeErr := <-s.closeChan:
// put the close error back into the channel, so that the run loop can receive it