fix handling of ACK frames serialized after CRYPTO frames (#4018)

This commit is contained in:
Marten Seemann 2023-08-05 18:25:04 -04:00 committed by Gaukas Wang
parent fba8d784a8
commit 62b3f22a77
2 changed files with 37 additions and 51 deletions

View file

@ -724,20 +724,22 @@ func (s *connection) idleTimeoutStartTime() time.Time {
} }
func (s *connection) handleHandshakeComplete() error { func (s *connection) handleHandshakeComplete() error {
s.handshakeComplete = true
defer s.handshakeCtxCancel() defer s.handshakeCtxCancel()
// Once the handshake completes, we have derived 1-RTT keys. // Once the handshake completes, we have derived 1-RTT keys.
// There's no point in queueing undecryptable packets for later decryption any more. // There's no point in queueing undecryptable packets for later decryption anymore.
s.undecryptablePackets = nil s.undecryptablePackets = nil
s.connIDManager.SetHandshakeComplete() s.connIDManager.SetHandshakeComplete()
s.connIDGenerator.SetHandshakeComplete() s.connIDGenerator.SetHandshakeComplete()
// The server applies transport parameters right away, but the client side has to wait for handshake completion.
// During a 0-RTT connection, the client is only allowed to use the new transport parameters for 1-RTT packets.
if s.perspective == protocol.PerspectiveClient { if s.perspective == protocol.PerspectiveClient {
s.applyTransportParameters() s.applyTransportParameters()
return nil return nil
} }
// All these only apply to the server side.
if err := s.handleHandshakeConfirmed(); err != nil { if err := s.handleHandshakeConfirmed(); err != nil {
return err return err
} }
@ -1235,6 +1237,7 @@ func (s *connection) handleFrames(
if log != nil { if log != nil {
frames = make([]logging.Frame, 0, 4) frames = make([]logging.Frame, 0, 4)
} }
handshakeWasComplete := s.handshakeComplete
var handleErr error var handleErr error
for len(data) > 0 { for len(data) > 0 {
l, frame, err := s.frameParser.ParseNext(data, encLevel, s.version) l, frame, err := s.frameParser.ParseNext(data, encLevel, s.version)
@ -1271,6 +1274,17 @@ func (s *connection) handleFrames(
return false, handleErr return false, handleErr
} }
} }
// Handle completion of the handshake after processing all the frames.
// This ensures that we correctly handle the following case on the server side:
// We receive a Handshake packet that contains the CRYPTO frame that allows us to complete the handshake,
// and an ACK serialized after that CRYPTO frame. In this case, we still want to process the ACK frame.
if !handshakeWasComplete && s.handshakeComplete {
if err := s.handleHandshakeComplete(); err != nil {
return false, err
}
}
return return
} }
@ -1366,7 +1380,9 @@ func (s *connection) handleHandshakeEvents() error {
case handshake.EventNoEvent: case handshake.EventNoEvent:
return nil return nil
case handshake.EventHandshakeComplete: case handshake.EventHandshakeComplete:
err = s.handleHandshakeComplete() // Don't call handleHandshakeComplete yet.
// It's advantageous to process ACK frames that might be serialized after the CRYPTO frame first.
s.handshakeComplete = true
case handshake.EventReceivedTransportParameters: case handshake.EventReceivedTransportParameters:
err = s.handleTransportParameters(ev.TransportParameters) err = s.handleTransportParameters(ev.TransportParameters)
case handshake.EventRestoredTransportParameters: case handshake.EventRestoredTransportParameters:
@ -1494,6 +1510,9 @@ func (s *connection) handleAckFrame(frame *wire.AckFrame, encLevel protocol.Encr
if !acked1RTTPacket { if !acked1RTTPacket {
return nil return nil
} }
// On the client side: If the packet acknowledged a 1-RTT packet, this confirms the handshake.
// This is only possible if the ACK was sent in a 1-RTT packet.
// This is an optimization over simply waiting for a HANDSHAKE_DONE frame, see section 4.1.2 of RFC 9001.
if s.perspective == protocol.PerspectiveClient && !s.handshakeConfirmed { if s.perspective == protocol.PerspectiveClient && !s.handshakeConfirmed {
if err := s.handleHandshakeConfirmed(); err != nil { if err := s.handleHandshakeConfirmed(); err != nil {
return err return err
@ -1665,6 +1684,9 @@ func (s *connection) restoreTransportParameters(params *wire.TransportParameters
} }
func (s *connection) handleTransportParameters(params *wire.TransportParameters) error { func (s *connection) handleTransportParameters(params *wire.TransportParameters) error {
if s.tracer != nil {
s.tracer.ReceivedTransportParameters(params)
}
if err := s.checkTransportParameters(params); err != nil { if err := s.checkTransportParameters(params); err != nil {
return &qerr.TransportError{ return &qerr.TransportError{
ErrorCode: qerr.TransportParameterError, ErrorCode: qerr.TransportParameterError,
@ -1699,9 +1721,6 @@ func (s *connection) checkTransportParameters(params *wire.TransportParameters)
if s.logger.Debug() { if s.logger.Debug() {
s.logger.Debugf("Processed Transport Parameters: %s", params) s.logger.Debugf("Processed Transport Parameters: %s", params)
} }
if s.tracer != nil {
s.tracer.ReceivedTransportParameters(params)
}
// check the initial_source_connection_id // check the initial_source_connection_id
if params.InitialSourceConnectionID != s.handshakeDestConnID { if params.InitialSourceConnectionID != s.handshakeDestConnID {

View file

@ -1892,7 +1892,6 @@ var _ = Describe("Connection", func() {
It("cancels the HandshakeComplete context when the handshake completes", func() { It("cancels the HandshakeComplete context when the handshake completes", func() {
packer.EXPECT().PackCoalescedPacket(false, gomock.Any(), conn.version).AnyTimes() packer.EXPECT().PackCoalescedPacket(false, gomock.Any(), conn.version).AnyTimes()
finishHandshake := make(chan struct{})
sph := mockackhandler.NewMockSentPacketHandler(mockCtrl) sph := mockackhandler.NewMockSentPacketHandler(mockCtrl)
conn.sentPacketHandler = sph conn.sentPacketHandler = sph
tracer.EXPECT().DroppedEncryptionLevel(protocol.EncryptionHandshake) tracer.EXPECT().DroppedEncryptionLevel(protocol.EncryptionHandshake)
@ -1902,53 +1901,26 @@ var _ = Describe("Connection", func() {
sph.EXPECT().DropPackets(protocol.EncryptionHandshake) sph.EXPECT().DropPackets(protocol.EncryptionHandshake)
sph.EXPECT().SetHandshakeConfirmed() sph.EXPECT().SetHandshakeConfirmed()
connRunner.EXPECT().Retire(clientDestConnID) connRunner.EXPECT().Retire(clientDestConnID)
go func() { cryptoSetup.EXPECT().SetHandshakeConfirmed()
defer GinkgoRecover() cryptoSetup.EXPECT().GetSessionTicket()
<-finishHandshake
cryptoSetup.EXPECT().StartHandshake()
cryptoSetup.EXPECT().NextEvent().Return(handshake.Event{Kind: handshake.EventHandshakeComplete})
cryptoSetup.EXPECT().NextEvent().Return(handshake.Event{Kind: handshake.EventNoEvent})
cryptoSetup.EXPECT().SetHandshakeConfirmed()
cryptoSetup.EXPECT().GetSessionTicket()
conn.run()
}()
handshakeCtx := conn.HandshakeComplete() handshakeCtx := conn.HandshakeComplete()
Consistently(handshakeCtx).ShouldNot(BeClosed()) Consistently(handshakeCtx).ShouldNot(BeClosed())
close(finishHandshake) Expect(conn.handleHandshakeComplete()).To(Succeed())
Eventually(handshakeCtx).Should(BeClosed()) Eventually(handshakeCtx).Should(BeClosed())
// make sure the go routine returns
streamManager.EXPECT().CloseWithError(gomock.Any())
expectReplaceWithClosed()
packer.EXPECT().PackApplicationClose(gomock.Any(), gomock.Any(), conn.version).Return(&coalescedPacket{buffer: getPacketBuffer()}, nil)
cryptoSetup.EXPECT().Close()
mconn.EXPECT().Write(gomock.Any(), gomock.Any())
tracer.EXPECT().ClosedConnection(gomock.Any())
tracer.EXPECT().Close()
conn.shutdown()
Eventually(conn.Context().Done()).Should(BeClosed())
}) })
It("sends a session ticket when the handshake completes", func() { It("sends a session ticket when the handshake completes", func() {
const size = protocol.MaxPostHandshakeCryptoFrameSize * 3 / 2 const size = protocol.MaxPostHandshakeCryptoFrameSize * 3 / 2
packer.EXPECT().PackCoalescedPacket(false, gomock.Any(), conn.version).AnyTimes() packer.EXPECT().PackCoalescedPacket(false, gomock.Any(), conn.version).AnyTimes()
finishHandshake := make(chan struct{})
connRunner.EXPECT().Retire(clientDestConnID) connRunner.EXPECT().Retire(clientDestConnID)
conn.sentPacketHandler.DropPackets(protocol.EncryptionInitial) conn.sentPacketHandler.DropPackets(protocol.EncryptionInitial)
tracer.EXPECT().DroppedEncryptionLevel(protocol.EncryptionHandshake) tracer.EXPECT().DroppedEncryptionLevel(protocol.EncryptionHandshake)
go func() { cryptoSetup.EXPECT().SetHandshakeConfirmed()
defer GinkgoRecover() cryptoSetup.EXPECT().GetSessionTicket().Return(make([]byte, size), nil)
<-finishHandshake
cryptoSetup.EXPECT().StartHandshake()
cryptoSetup.EXPECT().NextEvent().Return(handshake.Event{Kind: handshake.EventHandshakeComplete})
cryptoSetup.EXPECT().NextEvent().Return(handshake.Event{Kind: handshake.EventNoEvent})
cryptoSetup.EXPECT().SetHandshakeConfirmed()
cryptoSetup.EXPECT().GetSessionTicket().Return(make([]byte, size), nil)
conn.run()
}()
handshakeCtx := conn.HandshakeComplete() handshakeCtx := conn.HandshakeComplete()
Consistently(handshakeCtx).ShouldNot(BeClosed()) Consistently(handshakeCtx).ShouldNot(BeClosed())
close(finishHandshake) Expect(conn.handleHandshakeComplete()).To(Succeed())
var frames []ackhandler.Frame var frames []ackhandler.Frame
Eventually(func() []ackhandler.Frame { Eventually(func() []ackhandler.Frame {
frames, _ = conn.framer.AppendControlFrames(nil, protocol.MaxByteCount, protocol.Version1) frames, _ = conn.framer.AppendControlFrames(nil, protocol.MaxByteCount, protocol.Version1)
@ -1964,16 +1936,6 @@ var _ = Describe("Connection", func() {
} }
} }
Expect(size).To(BeEquivalentTo(s)) Expect(size).To(BeEquivalentTo(s))
// make sure the go routine returns
streamManager.EXPECT().CloseWithError(gomock.Any())
expectReplaceWithClosed()
packer.EXPECT().PackApplicationClose(gomock.Any(), gomock.Any(), conn.version).Return(&coalescedPacket{buffer: getPacketBuffer()}, nil)
cryptoSetup.EXPECT().Close()
mconn.EXPECT().Write(gomock.Any(), gomock.Any())
tracer.EXPECT().ClosedConnection(gomock.Any())
tracer.EXPECT().Close()
conn.shutdown()
Eventually(conn.Context().Done()).Should(BeClosed())
}) })
It("doesn't cancel the HandshakeComplete context when the handshake fails", func() { It("doesn't cancel the HandshakeComplete context when the handshake fails", func() {
@ -2028,6 +1990,7 @@ var _ = Describe("Connection", func() {
cryptoSetup.EXPECT().SetHandshakeConfirmed() cryptoSetup.EXPECT().SetHandshakeConfirmed()
cryptoSetup.EXPECT().GetSessionTicket() cryptoSetup.EXPECT().GetSessionTicket()
mconn.EXPECT().Write(gomock.Any(), gomock.Any()) mconn.EXPECT().Write(gomock.Any(), gomock.Any())
Expect(conn.handleHandshakeComplete()).To(Succeed())
conn.run() conn.run()
}() }()
Eventually(done).Should(BeClosed()) Eventually(done).Should(BeClosed())
@ -2351,6 +2314,7 @@ var _ = Describe("Connection", func() {
cryptoSetup.EXPECT().NextEvent().Return(handshake.Event{Kind: handshake.EventNoEvent}) cryptoSetup.EXPECT().NextEvent().Return(handshake.Event{Kind: handshake.EventNoEvent})
cryptoSetup.EXPECT().GetSessionTicket().MaxTimes(1) cryptoSetup.EXPECT().GetSessionTicket().MaxTimes(1)
cryptoSetup.EXPECT().SetHandshakeConfirmed().MaxTimes(1) cryptoSetup.EXPECT().SetHandshakeConfirmed().MaxTimes(1)
Expect(conn.handleHandshakeComplete()).To(Succeed())
err := conn.run() err := conn.run()
nerr, ok := err.(net.Error) nerr, ok := err.(net.Error)
Expect(ok).To(BeTrue()) Expect(ok).To(BeTrue())
@ -2868,7 +2832,10 @@ var _ = Describe("Client Connection", func() {
TransportParameters: params, TransportParameters: params,
}) })
cryptoSetup.EXPECT().NextEvent().Return(handshake.Event{Kind: handshake.EventHandshakeComplete}).MaxTimes(1) cryptoSetup.EXPECT().NextEvent().Return(handshake.Event{Kind: handshake.EventHandshakeComplete}).MaxTimes(1)
cryptoSetup.EXPECT().NextEvent().Return(handshake.Event{Kind: handshake.EventNoEvent}).MaxTimes(1) cryptoSetup.EXPECT().NextEvent().Return(handshake.Event{Kind: handshake.EventNoEvent}).MaxTimes(1).Do(func() {
defer GinkgoRecover()
Expect(conn.handleHandshakeComplete()).To(Succeed())
})
errChan <- conn.run() errChan <- conn.run()
close(errChan) close(errChan)
}() }()