In IETF quic, start peer migration by switching peer address as soon as detecting a non-probing frame in the incoming packet rather than after finishing parsing the whole packet. Protected by --gfe2_reloadable_flag_quic_start_peer_migration_earlier. Doing so will fix the bug in IETF QUIC that a server may send in-cache response to old address while the end of current packet processing isn't reached PiperOrigin-RevId: 328429809 Change-Id: Ib07274bdef3944240d43defc4183ac03cdd47bba
diff --git a/quic/core/quic_connection.cc b/quic/core/quic_connection.cc index f0769fd..c44e5cf 100644 --- a/quic/core/quic_connection.cc +++ b/quic/core/quic_connection.cc
@@ -1775,7 +1775,8 @@ } // Server starts to migrate connection upon receiving of non-probing packet // from a new peer address. - if (last_header_.packet_number == GetLargestReceivedPacket()) { + if (!start_peer_migration_earlier_ && + last_header_.packet_number == GetLargestReceivedPacket()) { direct_peer_address_ = last_packet_source_address_; if (current_effective_peer_migration_type_ != NO_CHANGE) { // TODO(fayang): When multiple packet number spaces is supported, only @@ -4125,14 +4126,14 @@ } void QuicConnection::UpdatePacketContent(QuicFrameType type) { + if (version().HasIetfQuicFrames()) { + MaybeStartIetfPeerMigration(type); + return; + } // Packet content is tracked to identify connectivity probe in non-IETF // version, where a connectivity probe is defined as // - a padded PING packet with peer address change received by server, // - a padded PING packet on new path received by client. - if (version().HasIetfQuicFrames()) { - // TODO(b/149315151) detect IETF non-probing packet. - return; - } if (current_packet_content_ == NOT_PADDED_PING) { // We have already learned the current packet is not a connectivity @@ -4184,6 +4185,24 @@ if (current_effective_peer_migration_type_ != NO_CHANGE) { // Start effective peer migration immediately when the current packet is // confirmed not a connectivity probing packet. + StartEffectivePeerMigration(current_effective_peer_migration_type_); + } + } + current_effective_peer_migration_type_ = NO_CHANGE; +} + +void QuicConnection::MaybeStartIetfPeerMigration(QuicFrameType type) { + DCHECK(version().HasIetfQuicFrames()); + if (!start_peer_migration_earlier_ || QuicUtils::IsProbingFrame(type)) { + return; + } + QUIC_CODE_COUNT(quic_start_peer_migration_earlier); + if (GetLargestReceivedPacket().IsInitialized() && + last_header_.packet_number == GetLargestReceivedPacket()) { + direct_peer_address_ = last_packet_source_address_; + if (current_effective_peer_migration_type_ != NO_CHANGE) { + // Start effective peer migration when the current packet contains a + // non-probing frame. // TODO(fayang): When multiple packet number spaces is supported, only // start peer migration for the application data. StartEffectivePeerMigration(current_effective_peer_migration_type_);
diff --git a/quic/core/quic_connection.h b/quic/core/quic_connection.h index 30d91d1..f6584ff 100644 --- a/quic/core/quic_connection.h +++ b/quic/core/quic_connection.h
@@ -1368,6 +1368,10 @@ // deprecated. void MaybeRespondToConnectivityProbingOrMigration(); + // Called in IETF QUIC. Start peer migration if a non-probing frame is + // received and the current packet number is largest received so far. + void MaybeStartIetfPeerMigration(QuicFrameType type); + QuicFramer framer_; // Contents received in the current packet, especially used to identify @@ -1723,6 +1727,9 @@ size_t anti_amplification_factor_ = GetQuicFlag(FLAGS_quic_anti_amplification_factor); + + bool start_peer_migration_earlier_ = + GetQuicReloadableFlag(quic_start_peer_migration_earlier); }; } // namespace quic
diff --git a/quic/core/quic_connection_test.cc b/quic/core/quic_connection_test.cc index 0f1a8dc..e4cc987 100644 --- a/quic/core/quic_connection_test.cc +++ b/quic/core/quic_connection_test.cc
@@ -375,8 +375,9 @@ WriteResult WritePacket(const char* buffer, size_t buf_len, const QuicIpAddress& /*self_address*/, - const QuicSocketAddress& /*peer_address*/, + const QuicSocketAddress& peer_address, PerPacketOptions* /*options*/) override { + last_write_peer_address_ = peer_address; // If the buffer is allocated from the pool, return it back to the pool. // Note the buffer content doesn't change. if (packet_buffer_pool_index_.find(const_cast<char*>(buffer)) != @@ -621,6 +622,10 @@ SimpleQuicFramer* framer() { return &framer_; } + QuicSocketAddress last_write_peer_address() const { + return last_write_peer_address_; + } + private: char* AllocPacketBuffer() { PacketBuffer* p = packet_buffer_free_list_.front(); @@ -673,6 +678,8 @@ QuicHashMap<char*, PacketBuffer*> packet_buffer_pool_index_; // Indices in packet_buffer_pool_ that are not allocated. std::list<PacketBuffer*> packet_buffer_free_list_; + // The peer address passed into WritePacket(). + QuicSocketAddress last_write_peer_address_; }; class TestConnection : public QuicConnection { @@ -1958,6 +1965,11 @@ set_perspective(Perspective::IS_SERVER); QuicPacketCreatorPeer::SetSendVersionInPacket(creator_, false); EXPECT_EQ(Perspective::IS_SERVER, connection_.perspective()); + connection_.SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE); + peer_creator_.set_encryption_level(ENCRYPTION_FORWARD_SECURE); + // Prevent packets from being coalesced. + EXPECT_CALL(visitor_, GetHandshakeState()) + .WillRepeatedly(Return(HANDSHAKE_CONFIRMED)); // Clear direct_peer_address. QuicConnectionPeer::SetDirectPeerAddress(&connection_, QuicSocketAddress()); @@ -1967,23 +1979,30 @@ QuicSocketAddress()); EXPECT_FALSE(connection_.effective_peer_address().IsInitialized()); - if (QuicVersionUsesCryptoFrames(connection_.transport_version())) { - EXPECT_CALL(visitor_, OnCryptoFrame(_)).Times(AnyNumber()); - } else { - EXPECT_CALL(visitor_, OnStreamFrame(_)).Times(AnyNumber()); - } - ProcessFramePacketWithAddresses(MakeCryptoFrame(), kSelfAddress, - kPeerAddress); + const QuicSocketAddress kNewPeerAddress = + QuicSocketAddress(QuicIpAddress::Loopback6(), /*port=*/23456); + EXPECT_CALL(visitor_, OnStreamFrame(_)) + .WillOnce(Invoke( + [=]() { EXPECT_EQ(kPeerAddress, connection_.peer_address()); })) + .WillOnce(Invoke([=]() { + EXPECT_EQ((GetQuicReloadableFlag(quic_start_peer_migration_earlier) || + !GetParam().version.HasIetfQuicFrames() + ? kNewPeerAddress + : kPeerAddress), + connection_.peer_address()); + })); + QuicFrames frames; + frames.push_back(QuicFrame(frame1_)); + ProcessFramesPacketWithAddresses(frames, kSelfAddress, kPeerAddress); EXPECT_EQ(kPeerAddress, connection_.peer_address()); EXPECT_EQ(kPeerAddress, connection_.effective_peer_address()); // Process another packet with a different peer address on server side will // start connection migration. - const QuicSocketAddress kNewPeerAddress = - QuicSocketAddress(QuicIpAddress::Loopback6(), /*port=*/23456); EXPECT_CALL(visitor_, OnConnectionMigration(PORT_CHANGE)).Times(1); - ProcessFramePacketWithAddresses(MakeCryptoFrame(), kSelfAddress, - kNewPeerAddress); + QuicFrames frames2; + frames2.push_back(QuicFrame(frame2_)); + ProcessFramesPacketWithAddresses(frames2, kSelfAddress, kNewPeerAddress); EXPECT_EQ(kNewPeerAddress, connection_.peer_address()); EXPECT_EQ(kNewPeerAddress, connection_.effective_peer_address()); }
diff --git a/quic/core/quic_utils.cc b/quic/core/quic_utils.cc index 367db32..13f6a2a 100644 --- a/quic/core/quic_utils.cc +++ b/quic/core/quic_utils.cc
@@ -654,5 +654,18 @@ } } +// static +bool QuicUtils::IsProbingFrame(QuicFrameType type) { + switch (type) { + case PATH_CHALLENGE_FRAME: + case PATH_RESPONSE_FRAME: + case NEW_CONNECTION_ID_FRAME: + case PADDING_FRAME: + return true; + default: + return false; + } +} + #undef RETURN_STRING_LITERAL // undef for jumbo builds } // namespace quic
diff --git a/quic/core/quic_utils.h b/quic/core/quic_utils.h index 9e190e0..05c7124 100644 --- a/quic/core/quic_utils.h +++ b/quic/core/quic_utils.h
@@ -230,6 +230,9 @@ // exceeds this value, it will result in a stream ID that exceeds the // implementation limit on stream ID size. static QuicStreamCount GetMaxStreamCount(); + + // Return true if this frame is an IETF probing frame. + static bool IsProbingFrame(QuicFrameType type); }; template <typename Mask>