implement stateless handling of Initial packets for the TLS server

This commit is contained in:
Marten Seemann
2017-11-11 08:38:27 +08:00
parent 57c6f3ceb5
commit 25a6dc9654
36 changed files with 1617 additions and 724 deletions

156
client.go
View File

@@ -10,6 +10,7 @@ import (
"sync"
"time"
"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"
@@ -23,14 +24,18 @@ type client struct {
hostname string
versionNegotiationChan chan struct{} // the versionNegotiationChan is closed as soon as the server accepted the suggested version
versionNegotiated bool // has version negotiation completed yet
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
config *Config
tls handshake.MintTLS // only used when using TLS
connectionID protocol.ConnectionID
version protocol.VersionNumber
initialVersion protocol.VersionNumber
version protocol.VersionNumber
session packetHandler
}
@@ -91,7 +96,6 @@ func DialNonFWSecure(
if tlsConf != nil {
hostname = tlsConf.ServerName
}
if hostname == "" {
hostname, _, err = net.SplitHostPort(host)
if err != nil {
@@ -111,8 +115,9 @@ func DialNonFWSecure(
}
utils.Infof("Starting new connection to %s (%s -> %s), connectionID %x, version %s", hostname, c.conn.LocalAddr().String(), c.conn.RemoteAddr().String(), c.connectionID, c.version)
go c.listen()
if err := c.establishSecureConnection(); err != nil {
if err := c.dial(); err != nil {
return nil, err
}
return c.session.(NonFWSession), nil
@@ -177,25 +182,79 @@ func populateClientConfig(config *Config) *Config {
}
}
// establishSecureConnection returns as soon as the connection is secure (as opposed to forward-secure)
func (c *client) establishSecureConnection() error {
if err := c.createNewSession(c.version, nil); err != nil {
func (c *client) dial() error {
var err error
if c.version.UsesTLS() {
err = c.dialTLS()
} else {
err = c.dialGQUIC()
}
if err == errCloseSessionForNewVersion {
return c.dial()
}
return err
}
func (c *client) dialGQUIC() error {
if err := c.createNewGQUICSession(); err != nil {
return err
}
go c.listen()
return c.establishSecureConnection()
}
func (c *client) dialTLS() error {
csc := handshake.NewCryptoStreamConn(nil)
mintConf, err := tlsToMintConfig(c.tlsConf, protocol.PerspectiveClient)
if err != nil {
return err
}
mintConf.ServerName = c.hostname
c.tls = newMintController(csc, mintConf, protocol.PerspectiveClient)
params := &handshake.TransportParameters{
StreamFlowControlWindow: protocol.ReceiveStreamFlowControlWindow,
ConnectionFlowControlWindow: protocol.ReceiveConnectionFlowControlWindow,
MaxStreams: protocol.MaxIncomingStreams,
IdleTimeout: c.config.IdleTimeout,
OmitConnectionID: c.config.RequestConnectionIDOmission,
}
eh := handshake.NewExtensionHandlerClient(params, c.initialVersion, c.config.Versions, c.version)
if err := c.tls.SetExtensionHandler(eh); err != nil {
return err
}
if err := c.createNewTLSSession(eh.GetPeerParams(), c.version); err != nil {
return err
}
if err := c.establishSecureConnection(); err != nil {
if err != handshake.ErrCloseSessionForRetry {
return err
}
utils.Infof("Received a Retry packet. Recreating session.")
if err := c.createNewTLSSession(eh.GetPeerParams(), c.version); err != nil {
return err
}
if err := c.establishSecureConnection(); err != nil {
return err
}
}
return nil
}
// 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() error {
var runErr error
errorChan := make(chan struct{})
go func() {
// session.run() returns as soon as the session is closed
runErr = c.session.run()
if runErr == errCloseSessionForNewVersion {
// run the new session
runErr = c.session.run()
}
runErr = c.session.run() // returns as soon as the session is closed
close(errorChan)
utils.Infof("Connection %x closed.", c.connectionID)
c.conn.Close()
if runErr != handshake.ErrCloseSessionForRetry && runErr != errCloseSessionForNewVersion {
c.conn.Close()
}
}()
// wait until the server accepts the QUIC version (or an error occurs)
@@ -219,7 +278,8 @@ func (c *client) establishSecureConnection() error {
}
}
// Listen listens
// Listen listens on the underlying connection and passes packets on for handling.
// It returns when the connection is closed.
func (c *client) listen() {
var err error
@@ -233,13 +293,15 @@ func (c *client) listen() {
n, addr, err = c.conn.Read(data)
if err != nil {
if !strings.HasSuffix(err.Error(), "use of closed network connection") {
c.session.Close(err)
c.mutex.Lock()
if c.session != nil {
c.session.Close(err)
}
c.mutex.Unlock()
}
break
}
data = data[:n]
c.handlePacket(addr, data)
c.handlePacket(addr, data[:n])
}
}
@@ -257,15 +319,16 @@ func (c *client) handlePacket(remoteAddr net.Addr, packet []byte) {
if hdr.OmitConnectionID && !c.config.RequestConnectionIDOmission {
return
}
// reject packets with the wrong connection ID
if !hdr.OmitConnectionID && hdr.ConnectionID != c.connectionID {
return
}
hdr.Raw = packet[:len(packet)-r.Len()]
c.mutex.Lock()
defer c.mutex.Unlock()
// reject packets with the wrong connection ID
if !hdr.OmitConnectionID && hdr.ConnectionID != c.connectionID {
return
}
if hdr.ResetFlag {
cr := c.conn.RemoteAddr()
// check if the remote address and the connection ID match
@@ -305,6 +368,8 @@ func (c *client) handlePacket(remoteAddr net.Addr, packet []byte) {
close(c.versionNegotiationChan)
}
// TODO: validate packet number and connection ID on Retry packets (for IETF QUIC)
c.session.handlePacket(&receivedPacket{
remoteAddr: remoteAddr,
header: hdr,
@@ -323,15 +388,15 @@ func (c *client) handleVersionNegotiationPacket(hdr *wire.Header) error {
}
}
c.receivedVersionNegotiationPacket = true
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
initialVersion := c.version
c.initialVersion = c.version
c.version = newVersion
var err error
c.connectionID, err = utils.GenerateConnectionID()
@@ -339,17 +404,13 @@ func (c *client) handleVersionNegotiationPacket(hdr *wire.Header) error {
return err
}
utils.Infof("Switching to QUIC version %s. New connection ID: %x", newVersion, c.connectionID)
// create a new session and close the old one
// the new session must be created first to update client member variables
oldSession := c.session
defer oldSession.Close(errCloseSessionForNewVersion)
return c.createNewSession(initialVersion, hdr.SupportedVersions)
c.session.Close(errCloseSessionForNewVersion)
return nil
}
func (c *client) createNewSession(initialVersion protocol.VersionNumber, negotiatedVersions []protocol.VersionNumber) error {
var err error
utils.Debugf("createNewSession with initial version %s", initialVersion)
func (c *client) createNewGQUICSession() (err error) {
c.mutex.Lock()
defer c.mutex.Unlock()
c.session, err = newClientSession(
c.conn,
c.hostname,
@@ -357,8 +418,27 @@ func (c *client) createNewSession(initialVersion protocol.VersionNumber, negotia
c.connectionID,
c.tlsConf,
c.config,
initialVersion,
negotiatedVersions,
c.initialVersion,
c.negotiatedVersions,
)
return err
}
func (c *client) createNewTLSSession(
paramsChan <-chan handshake.TransportParameters,
version protocol.VersionNumber,
) (err error) {
c.mutex.Lock()
defer c.mutex.Unlock()
c.session, err = newTLSClientSession(
c.conn,
c.hostname,
c.version,
c.connectionID,
c.config,
c.tls,
paramsChan,
1,
)
return err
}