send a NEW_TOKEN from after completing the handshake (as a server)

This commit is contained in:
Marten Seemann 2019-06-01 17:12:54 +08:00
parent 7c7bcede6c
commit 34543848f0
9 changed files with 97 additions and 29 deletions

View file

@ -4,6 +4,7 @@
- Implement HTTP/3.
- Rename `quic.Cookie` to `quic.Token` and `quic.Config.AcceptCookie` to `quic.Config.AcceptToken`.
- Distinguish between Retry tokens and tokens sent in NEW_TOKEN frames.
## v0.11.0 (2019-04-05)

View file

@ -196,7 +196,12 @@ var _ = Describe("Handshake tests", func() {
}
BeforeEach(func() {
serverConfig.AcceptToken = func(net.Addr, *quic.Token) bool { return true }
serverConfig.AcceptToken = func(addr net.Addr, token *quic.Token) bool {
if token != nil {
Expect(token.IsRetryToken).To(BeFalse())
}
return true
}
var err error
// start the server, but don't call Accept
server, err = quic.ListenAddr("localhost:0", testdata.GetTLSConfig(), serverConfig)

View file

@ -18,8 +18,12 @@ type VersionNumber = protocol.VersionNumber
// A Token can be used to verify the ownership of the client address.
type Token struct {
RemoteAddr string
SentTime time.Time
// IsRetryToken encodes how the client received the token. There are two ways:
// * In a Retry packet sent when trying to establish a new connection.
// * In a NEW_TOKEN frame on a previous connection.
IsRetryToken bool
RemoteAddr string
SentTime time.Time
}
// An ErrorCode is an application-defined error code.
@ -189,7 +193,10 @@ type Config struct {
IdleTimeout time.Duration
// AcceptToken determines if a Token is accepted.
// It is called with token = nil if the client didn't send a token.
// If not set, it verifies that the address matches, and that the token was issued within the last 5 seconds.
// If not set, a default verification function is used:
// * it verifies that the address matches, and
// * if the token is a retry token, that it was issued within the last 5 seconds
// * else, that it was issued within the last 24 hours.
// This option is only valid for the server.
AcceptToken func(clientAddr net.Addr, token *Token) bool
// MaxReceiveStreamFlowControlWindow is the maximum stream-level flow control window for receiving data.

View file

@ -16,13 +16,16 @@ const (
// A Token is derived from the client address and can be used to verify the ownership of this address.
type Token struct {
RemoteAddr string
SentTime time.Time
IsRetryToken bool
RemoteAddr string
SentTime time.Time
// only set for retry tokens
OriginalDestConnectionID protocol.ConnectionID
}
// token is the struct that is used for ASN1 serialization and deserialization
type token struct {
IsRetryToken bool
RemoteAddr []byte
Timestamp int64
OriginalDestConnectionID []byte
@ -44,9 +47,10 @@ func NewTokenGenerator() (*TokenGenerator, error) {
}, nil
}
// NewToken generates a new token for a given source address
// NewRetryToken generates a new token for a Retry for a given source address
func (g *TokenGenerator) NewRetryToken(raddr net.Addr, origConnID protocol.ConnectionID) ([]byte, error) {
data, err := asn1.Marshal(token{
IsRetryToken: true,
RemoteAddr: encodeRemoteAddr(raddr),
OriginalDestConnectionID: origConnID,
Timestamp: time.Now().UnixNano(),
@ -57,6 +61,18 @@ func (g *TokenGenerator) NewRetryToken(raddr net.Addr, origConnID protocol.Conne
return g.tokenProtector.NewToken(data)
}
// NewToken generates a new token to be sent in a NEW_TOKEN frame
func (g *TokenGenerator) NewToken(raddr net.Addr) ([]byte, error) {
data, err := asn1.Marshal(token{
RemoteAddr: encodeRemoteAddr(raddr),
Timestamp: time.Now().UnixNano(),
})
if err != nil {
return nil, err
}
return g.tokenProtector.NewToken(data)
}
// DecodeToken decodes a token
func (g *TokenGenerator) DecodeToken(encrypted []byte) (*Token, error) {
// if the client didn't send any token, DecodeToken will be called with a nil-slice
@ -77,8 +93,9 @@ func (g *TokenGenerator) DecodeToken(encrypted []byte) (*Token, error) {
return nil, fmt.Errorf("rest when unpacking token: %d", len(rest))
}
token := &Token{
RemoteAddr: decodeRemoteAddr(t.RemoteAddr),
SentTime: time.Unix(0, t.Timestamp),
IsRetryToken: t.IsRetryToken,
RemoteAddr: decodeRemoteAddr(t.RemoteAddr),
SentTime: time.Unix(0, t.Timestamp),
}
if len(t.OriginalDestConnectionID) > 0 {
token.OriginalDestConnectionID = protocol.ConnectionID(t.OriginalDestConnectionID)

View file

@ -57,6 +57,9 @@ const MaxTrackedSkippedPackets = 10
// If the queue is full, new connection attempts will be rejected.
const MaxAcceptQueueSize = 32
// TokenValidity is the duration that a (non-retry) token is considered valid
const TokenValidity = 24 * time.Hour
// RetryTokenValidity is the duration that a retry token is considered valid
const RetryTokenValidity = 10 * time.Second

View file

@ -89,7 +89,7 @@ type server struct {
sessionHandler packetHandlerManager
// set as a member, so they can be set in the tests
newSession func(connection, sessionRunner, protocol.ConnectionID /* original connection ID */, protocol.ConnectionID /* destination connection ID */, protocol.ConnectionID /* source connection ID */, *Config, *tls.Config, *handshake.TransportParameters, utils.Logger, protocol.VersionNumber) (quicSession, error)
newSession func(connection, sessionRunner, protocol.ConnectionID /* original connection ID */, protocol.ConnectionID /* destination connection ID */, protocol.ConnectionID /* source connection ID */, *Config, *tls.Config, *handshake.TransportParameters, *handshake.TokenGenerator, utils.Logger, protocol.VersionNumber) (quicSession, error)
serverError error
errorChan chan struct{}
@ -198,7 +198,11 @@ var defaultAcceptToken = func(clientAddr net.Addr, token *Token) bool {
if token == nil {
return false
}
if time.Now().After(token.SentTime.Add(protocol.RetryTokenValidity)) {
validity := protocol.TokenValidity
if token.IsRetryToken {
validity = protocol.RetryTokenValidity
}
if time.Now().After(token.SentTime.Add(validity)) {
return false
}
var sourceAddr string
@ -387,8 +391,9 @@ func (s *server) handleInitialImpl(p *receivedPacket, hdr *wire.Header) (quicSes
c, err := s.tokenGenerator.DecodeToken(hdr.Token)
if err == nil {
token = &Token{
RemoteAddr: c.RemoteAddr,
SentTime: c.SentTime,
IsRetryToken: c.IsRetryToken,
RemoteAddr: c.RemoteAddr,
SentTime: c.SentTime,
}
origDestConnectionID = c.OriginalDestConnectionID
}
@ -457,6 +462,7 @@ func (s *server) createNewSession(
s.config,
s.tlsConf,
params,
s.tokenGenerator,
s.logger,
version,
)

View file

@ -292,6 +292,7 @@ var _ = Describe("Server", func() {
_ *Config,
_ *tls.Config,
_ *handshake.TransportParameters,
_ *handshake.TokenGenerator,
_ utils.Logger,
_ protocol.VersionNumber,
) (quicSession, error) {
@ -341,6 +342,7 @@ var _ = Describe("Server", func() {
_ *Config,
_ *tls.Config,
_ *handshake.TransportParameters,
_ *handshake.TokenGenerator,
_ utils.Logger,
_ protocol.VersionNumber,
) (quicSession, error) {
@ -399,6 +401,7 @@ var _ = Describe("Server", func() {
_ *Config,
_ *tls.Config,
_ *handshake.TransportParameters,
_ *handshake.TokenGenerator,
_ utils.Logger,
_ protocol.VersionNumber,
) (quicSession, error) {
@ -486,6 +489,7 @@ var _ = Describe("Server", func() {
_ *Config,
_ *tls.Config,
_ *handshake.TransportParameters,
_ *handshake.TokenGenerator,
_ utils.Logger,
_ protocol.VersionNumber,
) (quicSession, error) {
@ -518,6 +522,7 @@ var _ = Describe("Server", func() {
_ *Config,
_ *tls.Config,
_ *handshake.TransportParameters,
_ *handshake.TokenGenerator,
_ utils.Logger,
_ protocol.VersionNumber,
) (quicSession, error) {
@ -544,8 +549,9 @@ var _ = Describe("default source address verification", func() {
It("accepts a token", func() {
remoteAddr := &net.UDPAddr{IP: net.IPv4(192, 168, 0, 1)}
token := &Token{
RemoteAddr: "192.168.0.1",
SentTime: time.Now().Add(-protocol.RetryTokenValidity).Add(time.Second), // will expire in 1 second
IsRetryToken: true,
RemoteAddr: "192.168.0.1",
SentTime: time.Now().Add(-protocol.RetryTokenValidity).Add(time.Second), // will expire in 1 second
}
Expect(defaultAcceptToken(remoteAddr, token)).To(BeTrue())
})
@ -558,8 +564,9 @@ var _ = Describe("default source address verification", func() {
It("rejects a token if the address doesn't match", func() {
remoteAddr := &net.UDPAddr{IP: net.IPv4(192, 168, 0, 1)}
token := &Token{
RemoteAddr: "127.0.0.1",
SentTime: time.Now(),
IsRetryToken: true,
RemoteAddr: "127.0.0.1",
SentTime: time.Now(),
}
Expect(defaultAcceptToken(remoteAddr, token)).To(BeFalse())
})
@ -567,8 +574,9 @@ var _ = Describe("default source address verification", func() {
It("accepts a token for a remote address is not a UDP address", func() {
remoteAddr := &net.TCPAddr{IP: net.IPv4(192, 168, 0, 1), Port: 1337}
token := &Token{
RemoteAddr: "192.168.0.1:1337",
SentTime: time.Now(),
IsRetryToken: true,
RemoteAddr: "192.168.0.1:1337",
SentTime: time.Now(),
}
Expect(defaultAcceptToken(remoteAddr, token)).To(BeTrue())
})
@ -576,8 +584,9 @@ var _ = Describe("default source address verification", func() {
It("rejects an invalid token for a remote address is not a UDP address", func() {
remoteAddr := &net.TCPAddr{IP: net.IPv4(192, 168, 0, 1), Port: 1337}
token := &Token{
RemoteAddr: "192.168.0.1:7331", // mismatching port
SentTime: time.Now(),
IsRetryToken: true,
RemoteAddr: "192.168.0.1:7331", // mismatching port
SentTime: time.Now(),
}
Expect(defaultAcceptToken(remoteAddr, token)).To(BeFalse())
})
@ -585,9 +594,22 @@ var _ = Describe("default source address verification", func() {
It("rejects an expired token", func() {
remoteAddr := &net.UDPAddr{IP: net.IPv4(192, 168, 0, 1)}
token := &Token{
RemoteAddr: "192.168.0.1",
SentTime: time.Now().Add(-protocol.RetryTokenValidity).Add(-time.Second), // expired 1 second ago
IsRetryToken: true,
RemoteAddr: "192.168.0.1",
SentTime: time.Now().Add(-protocol.RetryTokenValidity).Add(-time.Second), // expired 1 second ago
}
Expect(defaultAcceptToken(remoteAddr, token)).To(BeFalse())
})
It("accepts a non-retry token", func() {
Expect(protocol.RetryTokenValidity).To(BeNumerically("<", protocol.TokenValidity))
remoteAddr := &net.UDPAddr{IP: net.IPv4(192, 168, 0, 1)}
token := &Token{
IsRetryToken: false,
RemoteAddr: "192.168.0.1",
// if this was a retry token, it would have expired one second ago
SentTime: time.Now().Add(-protocol.RetryTokenValidity).Add(-time.Second),
}
Expect(defaultAcceptToken(remoteAddr, token)).To(BeTrue())
})
})

View file

@ -116,6 +116,7 @@ type session struct {
framer framer
windowUpdateQueue *windowUpdateQueue
connFlowController flowcontrol.ConnectionFlowController
tokenGenerator *handshake.TokenGenerator // only set for the server
unpacker unpacker
frameParser wire.FrameParser
@ -175,6 +176,7 @@ var newSession = func(
conf *Config,
tlsConf *tls.Config,
params *handshake.TransportParameters,
tokenGenerator *handshake.TokenGenerator,
logger utils.Logger,
v protocol.VersionNumber,
) (quicSession, error) {
@ -184,6 +186,7 @@ var newSession = func(
config: conf,
srcConnID: srcConnID,
destConnID: destConnID,
tokenGenerator: tokenGenerator,
perspective: protocol.PerspectiveServer,
handshakeCompleteChan: make(chan struct{}),
logger: logger,
@ -495,13 +498,15 @@ func (s *session) handleHandshakeComplete() {
s.sessionRunner.OnHandshakeComplete(s)
// The client completes the handshake first (after sending the CFIN).
// We need to make sure they learn about the peer completing the handshake,
// We need to make sure it learns about the server completing the handshake,
// in order to stop retransmitting handshake packets.
// They will stop retransmitting handshake packets when receiving the first forward-secure packet.
// We need to make sure that an ack-eliciting 1-RTT packet is sent,
// independent from the application protocol.
// They will stop retransmitting handshake packets when receiving the first 1-RTT packet.
if s.perspective == protocol.PerspectiveServer {
s.queueControlFrame(&wire.PingFrame{})
token, err := s.tokenGenerator.NewToken(s.conn.RemoteAddr())
if err != nil {
s.closeLocal(err)
}
s.queueControlFrame(&wire.NewTokenFrame{Token: token})
}
}

View file

@ -77,8 +77,9 @@ var _ = Describe("Session", func() {
sessionRunner = NewMockSessionRunner(mockCtrl)
mconn = newMockConnection()
tokenGenerator, err := handshake.NewTokenGenerator()
Expect(err).ToNot(HaveOccurred())
var pSess Session
var err error
pSess, err = newSession(
mconn,
sessionRunner,
@ -88,6 +89,7 @@ var _ = Describe("Session", func() {
populateServerConfig(&Config{}),
nil, // tls.Config
&handshake.TransportParameters{},
tokenGenerator,
utils.DefaultLogger,
protocol.VersionTLS,
)