diff --git a/Changelog.md b/Changelog.md index 82b56ea6..05812a4d 100644 --- a/Changelog.md +++ b/Changelog.md @@ -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) diff --git a/integrationtests/self/handshake_test.go b/integrationtests/self/handshake_test.go index f62fa182..c8ee6348 100644 --- a/integrationtests/self/handshake_test.go +++ b/integrationtests/self/handshake_test.go @@ -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) diff --git a/interface.go b/interface.go index d533df24..768e5fe1 100644 --- a/interface.go +++ b/interface.go @@ -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. diff --git a/internal/handshake/token_generator.go b/internal/handshake/token_generator.go index 95776768..7c36e47b 100644 --- a/internal/handshake/token_generator.go +++ b/internal/handshake/token_generator.go @@ -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) diff --git a/internal/protocol/params.go b/internal/protocol/params.go index ececd97e..73c13d07 100644 --- a/internal/protocol/params.go +++ b/internal/protocol/params.go @@ -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 diff --git a/server.go b/server.go index 4878d155..eb780ac6 100644 --- a/server.go +++ b/server.go @@ -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, ) diff --git a/server_test.go b/server_test.go index 6d491a86..00c0c78d 100644 --- a/server_test.go +++ b/server_test.go @@ -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()) + }) }) diff --git a/session.go b/session.go index 9223ba7a..8d7bdcbf 100644 --- a/session.go +++ b/session.go @@ -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}) } } diff --git a/session_test.go b/session_test.go index 0b0aa376..69aaabca 100644 --- a/session_test.go +++ b/session_test.go @@ -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, )