Refactoring server preferred address (SPA) validation interfaces. And add sanity checks in QuicConnection client code to prevent client from kicking off connection migration while validating the SPA. Change QuicConnection::ValidateServerPreferredAddress() to an abstract interface in QuicSession. The change is client-side-only and behind connection option "SPAD". PiperOrigin-RevId: 500024246
diff --git a/quiche/quic/core/quic_connection.cc b/quiche/quic/core/quic_connection.cc index a929e3a..e340897 100644 --- a/quiche/quic/core/quic_connection.cc +++ b/quiche/quic/core/quic_connection.cc
@@ -242,43 +242,6 @@ return false; } -// Client migrates to server preferred address on path validation suceeds. -// Otherwise, client cleans up alternative path. -class ServerPreferredAddressResultDelegate - : public QuicPathValidator::ResultDelegate { - public: - explicit ServerPreferredAddressResultDelegate(QuicConnection* connection) - : connection_(connection) {} - void OnPathValidationSuccess( - std::unique_ptr<QuicPathValidationContext> context, - QuicTime /*start_time*/) override { - QUIC_DLOG(INFO) << "Server preferred address: " << context->peer_address() - << " validated. Migrating path, self_address: " - << context->self_address() - << ", peer_address: " << context->peer_address(); - connection_->mutable_stats().server_preferred_address_validated = true; - const bool success = connection_->MigratePath(context->self_address(), - context->peer_address(), - context->WriterToUse(), - /*owns_writer*/ false); - QUIC_BUG_IF(failed to migrate to server preferred address, !success) - << "Failed to migrate to server preferred address: " - << context->peer_address() << " after successful validation"; - } - - void OnPathValidationFailure( - std::unique_ptr<QuicPathValidationContext> context) override { - QUIC_DLOG(INFO) << "Failed to validate server preferred address : " - << context->peer_address(); - connection_->mutable_stats().failed_to_validate_server_preferred_address = - true; - connection_->OnPathValidationFailureAtClient(/*is_multi_port=*/false); - } - - private: - QuicConnection* connection_; -}; - } // namespace #define ENDPOINT \ @@ -706,13 +669,12 @@ config.HasReceivedIPv6AlternateServerAddress()) { server_preferred_address_ = config.ReceivedIPv6AlternateServerAddress(); } - AddKnownServerAddress(server_preferred_address_); if (server_preferred_address_.IsInitialized()) { QUICHE_DLOG(INFO) << ENDPOINT << "Received server preferred address: " << server_preferred_address_; if (config.HasClientRequestedIndependentOption(kSPA2, perspective_)) { accelerated_server_preferred_address_ = true; - ValidateServerPreferredAddress(); + visitor_->OnServerPreferredAddressAvailable(server_preferred_address_); } } } @@ -4005,7 +3967,7 @@ if (!accelerated_server_preferred_address_ && server_preferred_address_.IsInitialized()) { QUICHE_DCHECK_EQ(Perspective::IS_CLIENT, perspective_); - ValidateServerPreferredAddress(); + visitor_->OnServerPreferredAddressAvailable(server_preferred_address_); } } @@ -6428,21 +6390,6 @@ ->MaybeIssueNewConnectionIdForPreferredAddress(); } -void QuicConnection::ValidateServerPreferredAddress() { - QUICHE_DCHECK(server_preferred_address_.IsInitialized()); - // Validate received server preferred address. - auto context = visitor_->CreatePathValidationContextForServerPreferredAddress( - server_preferred_address_); - if (context == nullptr) { - return; - } - QUICHE_DLOG(INFO) << ENDPOINT << "Start validating server preferred address: " - << server_preferred_address_; - auto result_delegate = - std::make_unique<ServerPreferredAddressResultDelegate>(this); - ValidatePath(std::move(context), std::move(result_delegate)); -} - bool QuicConnection::ShouldDetectBlackhole() const { if (!connected_ || blackhole_detection_disabled_) { return false; @@ -6564,6 +6511,13 @@ default_path_.stateless_reset_token); } if (path_validator_.HasPendingPathValidation()) { + if (perspective_ == Perspective::IS_CLIENT && + IsValidatingServerPreferredAddress()) { + QUIC_CLIENT_HISTOGRAM_BOOL( + "QuicSession.ServerPreferredAddressValidationCancelled", true, + "How often the caller kicked off another validation while there is " + "an on-going server preferred address validation."); + } // Cancel and fail any earlier validation. path_validator_.CancelPathValidation(); } @@ -6603,6 +6557,10 @@ } path_validator_.StartPathValidation(std::move(context), std::move(result_delegate)); + if (perspective_ == Perspective::IS_CLIENT && + IsValidatingServerPreferredAddress()) { + AddKnownServerAddress(server_preferred_address_); + } } bool QuicConnection::SendPathResponse( @@ -6682,7 +6640,7 @@ path_validator_.CancelPathValidation(); } -bool QuicConnection::UpdateConnectionIdsOnClientMigration( +bool QuicConnection::UpdateConnectionIdsOnMigration( const QuicSocketAddress& self_address, const QuicSocketAddress& peer_address) { QUICHE_DCHECK(perspective_ == Perspective::IS_CLIENT); @@ -6765,7 +6723,7 @@ QUICHE_DCHECK(!version().UsesHttp3() || IsHandshakeConfirmed()); if (connection_migration_use_new_cid_) { - if (!UpdateConnectionIdsOnClientMigration(self_address, peer_address)) { + if (!UpdateConnectionIdsOnMigration(self_address, peer_address)) { if (owns_writer) { delete writer; } @@ -6800,7 +6758,8 @@ return true; } -void QuicConnection::OnPathValidationFailureAtClient(bool is_multi_port) { +void QuicConnection::OnPathValidationFailureAtClient( + bool is_multi_port, const QuicPathValidationContext& context) { if (connection_migration_use_new_cid_) { QUICHE_DCHECK(perspective_ == Perspective::IS_CLIENT); alternative_path_.Clear(); @@ -6815,6 +6774,13 @@ } } + if (context.peer_address() == server_preferred_address_ && + server_preferred_address_ != default_path_.peer_address) { + QUIC_DLOG(INFO) << "Failed to validate server preferred address : " + << server_preferred_address_; + mutable_stats().failed_to_validate_server_preferred_address = true; + } + RetirePeerIssuedConnectionIdsOnPathValidationFailure(); } @@ -7072,8 +7038,9 @@ void QuicConnection::MultiPortPathValidationResultDelegate:: OnPathValidationFailure( - std::unique_ptr<QuicPathValidationContext> /*context*/) { - connection_->OnPathValidationFailureAtClient(/*is_multi_port=*/true); + std::unique_ptr<QuicPathValidationContext> context) { + connection_->OnPathValidationFailureAtClient(/*is_multi_port=*/true, + *context); } QuicConnection::ReversePathValidationResultDelegate:: @@ -7231,5 +7198,29 @@ retransmittable_on_wire_timeout); } +bool QuicConnection::IsValidatingServerPreferredAddress() const { + QUICHE_DCHECK_EQ(perspective_, Perspective::IS_CLIENT); + return server_preferred_address_.IsInitialized() && + server_preferred_address_ != default_path_.peer_address && + path_validator_.HasPendingPathValidation() && + path_validator_.GetContext()->peer_address() == + server_preferred_address_; +} + +void QuicConnection::OnServerPreferredAddressValidated( + QuicPathValidationContext& context, bool owns_writer) { + QUIC_DLOG(INFO) << "Server preferred address: " << context.peer_address() + << " validated. Migrating path, self_address: " + << context.self_address() + << ", peer_address: " << context.peer_address(); + mutable_stats().server_preferred_address_validated = true; + const bool success = + MigratePath(context.self_address(), context.peer_address(), + context.WriterToUse(), owns_writer); + QUIC_BUG_IF(failed to migrate to server preferred address, !success) + << "Failed to migrate to server preferred address: " + << context.peer_address() << " after successful validation"; +} + #undef ENDPOINT // undef for jumbo builds } // namespace quic
diff --git a/quiche/quic/core/quic_connection.h b/quiche/quic/core/quic_connection.h index ca25805..5c06949 100644 --- a/quiche/quic/core/quic_connection.h +++ b/quiche/quic/core/quic_connection.h
@@ -241,9 +241,8 @@ virtual std::unique_ptr<QuicPathValidationContext> CreateContextForMultiPortPath() = 0; - // Creates context to validate server preferred address. - virtual std::unique_ptr<QuicPathValidationContext> - CreatePathValidationContextForServerPreferredAddress( + // Called when the client receives a preferred address from its peer. + virtual void OnServerPreferredAddressAvailable( const QuicSocketAddress& server_preferred_address) = 0; }; @@ -1219,7 +1218,8 @@ // Called to clear the alternative_path_ when path validation failed on the // client side. - void OnPathValidationFailureAtClient(bool is_multi_port); + void OnPathValidationFailureAtClient( + bool is_multi_port, const QuicPathValidationContext& context); void SetSourceAddressTokenToSend(absl::string_view token); @@ -1275,6 +1275,16 @@ // Kicks off validation of received server preferred address. void ValidateServerPreferredAddress(); + // Returns true if the client is validating the server preferred address which + // hasn't been used before. + bool IsValidatingServerPreferredAddress() const; + + // Called by client to start sending packets to the preferred address. + // If |owns_writer| is true, the ownership of the writer in the |context| is + // also passed in. + void OnServerPreferredAddressValidated(QuicPathValidationContext& context, + bool owns_writer); + protected: // Calls cancel() on all the alarms owned by this connection. void CancelAllAlarms(); @@ -1604,11 +1614,11 @@ // Returns true if header contains valid server connection ID. bool ValidateServerConnectionId(const QuicPacketHeader& header) const; - // Update the connection IDs when client migrates with/without validation. + // Update the connection IDs when client migrates its own address + // (with/without validation) or switches to server preferred address. // Returns false if required connection ID is not available. - bool UpdateConnectionIdsOnClientMigration( - const QuicSocketAddress& self_address, - const QuicSocketAddress& peer_address); + bool UpdateConnectionIdsOnMigration(const QuicSocketAddress& self_address, + const QuicSocketAddress& peer_address); // Retire active peer issued connection IDs after they are no longer used on // any path.
diff --git a/quiche/quic/core/quic_connection_test.cc b/quiche/quic/core/quic_connection_test.cc index 874eeb1..e1e318b 100644 --- a/quiche/quic/core/quic_connection_test.cc +++ b/quiche/quic/core/quic_connection_test.cc
@@ -102,6 +102,13 @@ const QuicSocketAddress kSelfAddress = QuicSocketAddress(QuicIpAddress::Loopback6(), /*port=*/443); +const QuicSocketAddress kServerPreferredAddress = QuicSocketAddress( + []() { + QuicIpAddress address; + address.FromString("2604:31c0::"); + return address; + }(), + /*port=*/443); QuicStreamId GetNthClientInitiatedStreamId(int n, QuicTransportVersion version) { @@ -1468,9 +1475,6 @@ ASSERT_TRUE(version().HasIetfQuicFrames()); ASSERT_TRUE(connection_.self_address().host().IsIPv6()); SetQuicReloadableFlag(quic_connection_migration_use_new_cid_v2, true); - QuicIpAddress host; - host.FromString("2604:31c0::"); - QuicSocketAddress server_preferred_address(host, 443); const QuicConnectionId connection_id = TestConnectionId(17); const StatelessResetToken reset_token = QuicUtils::GenerateStatelessResetToken(connection_id); @@ -1491,7 +1495,7 @@ QuicConfigPeer::SetReceivedStatelessResetToken(&config, kTestStatelessResetToken); QuicConfigPeer::SetReceivedAlternateServerAddress(&config, - server_preferred_address); + kServerPreferredAddress); QuicConfigPeer::SetPreferredAddressConnectionIdAndToken( &config, connection_id, reset_token); EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _)); @@ -1499,7 +1503,7 @@ ASSERT_TRUE(QuicConnectionPeer::GetServerPreferredAddress(&connection_) .IsInitialized()); - EXPECT_EQ(server_preferred_address, + EXPECT_EQ(kServerPreferredAddress, QuicConnectionPeer::GetServerPreferredAddress(&connection_)); } @@ -2514,7 +2518,8 @@ EXPECT_EQ(expected_self_address_, context->self_address()); EXPECT_EQ(expected_peer_address_, context->peer_address()); if (connection_->perspective() == Perspective::IS_CLIENT) { - connection_->OnPathValidationFailureAtClient(/*is_multi_port=*/false); + connection_->OnPathValidationFailureAtClient(/*is_multi_port=*/false, + *context); } *success_ = false; } @@ -2526,6 +2531,32 @@ bool* success_; }; +// A test implementation which migrates to server preferred address +// on path validation suceeds. Otherwise, client cleans up alternative path. +class QUICHE_EXPORT ServerPreferredAddressTestResultDelegate + : public QuicPathValidator::ResultDelegate { + public: + explicit ServerPreferredAddressTestResultDelegate(QuicConnection* connection) + : connection_(connection) {} + void OnPathValidationSuccess( + std::unique_ptr<QuicPathValidationContext> context, + QuicTime /*start_time*/) override { + connection_->OnServerPreferredAddressValidated(*context, false); + } + + void OnPathValidationFailure( + std::unique_ptr<QuicPathValidationContext> context) override { + connection_->OnPathValidationFailureAtClient(/*is_multi_port=*/false, + *context); + } + + protected: + QuicConnection* connection() { return connection_; } + + private: + QuicConnection* connection_; +}; + // Receive a path probe request at the server side, i.e., // in non-IETF version: receive a padded PING packet with a peer addess change; // in IETF version: receive a packet contains PATH CHALLENGE with peer address @@ -2946,6 +2977,53 @@ EXPECT_EQ(kPeerAddress, connection_.effective_peer_address()); } +TEST_P(QuicConnectionTest, + PeerAddressChangesToPreferredAddressBeforeClientInitiates) { + if (!version().HasIetfQuicFrames()) { + return; + } + ASSERT_EQ(Perspective::IS_CLIENT, connection_.perspective()); + ASSERT_TRUE(connection_.self_address().host().IsIPv6()); + SetQuicReloadableFlag(quic_connection_migration_use_new_cid_v2, true); + const QuicConnectionId connection_id = TestConnectionId(17); + const StatelessResetToken reset_token = + QuicUtils::GenerateStatelessResetToken(connection_id); + + connection_.CreateConnectionIdManager(); + + connection_.SendCryptoStreamData(); + EXPECT_CALL(*loss_algorithm_, DetectLosses(_, _, _, _, _, _)); + EXPECT_CALL(*send_algorithm_, OnCongestionEvent(true, _, _, _, _)); + QuicAckFrame frame = InitAckFrame(1); + // Received ACK for packet 1. + ProcessFramePacketAtLevel(1, QuicFrame(&frame), ENCRYPTION_INITIAL); + // Discard INITIAL key. + connection_.RemoveEncrypter(ENCRYPTION_INITIAL); + connection_.SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE); + + QuicConfig config; + config.SetClientConnectionOptions(QuicTagVector{kSPAD}); + config.SetConnectionOptionsToSend(QuicTagVector{kRVCM}); + QuicConfigPeer::SetReceivedStatelessResetToken(&config, + kTestStatelessResetToken); + QuicConfigPeer::SetReceivedAlternateServerAddress(&config, + kServerPreferredAddress); + QuicConfigPeer::SetPreferredAddressConnectionIdAndToken( + &config, connection_id, reset_token); + EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _)); + connection_.SetFromConfig(config); + EXPECT_EQ(kPeerAddress, connection_.peer_address()); + EXPECT_EQ(kPeerAddress, connection_.effective_peer_address()); + ASSERT_TRUE(QuicConnectionPeer::GetServerPreferredAddress(&connection_) + .IsInitialized()); + EXPECT_EQ(kServerPreferredAddress, + QuicConnectionPeer::GetServerPreferredAddress(&connection_)); + + EXPECT_CALL(visitor_, OnCryptoFrame(_)).Times(0); + ProcessFramePacketWithAddresses(MakeCryptoFrame(), kSelfAddress, + kServerPreferredAddress, ENCRYPTION_INITIAL); +} + TEST_P(QuicConnectionTest, MaxPacketSize) { EXPECT_EQ(Perspective::IS_CLIENT, connection_.perspective()); EXPECT_EQ(1250u, connection_.max_packet_length()); @@ -15989,8 +16067,6 @@ QuicConfig config; config.SetClientConnectionOptions(QuicTagVector{kSPAD}); ServerPreferredAddressInit(config); - const QuicSocketAddress kServerPreferredAddress = - QuicConnectionPeer::GetServerPreferredAddress(&connection_); const QuicSocketAddress kNewSelfAddress = QuicSocketAddress(QuicIpAddress::Loopback6(), /*port=*/23456); TestPacketWriter new_writer(version(), &clock_, Perspective::IS_CLIENT); @@ -16001,11 +16077,15 @@ .WillRepeatedly(Return(HANDSHAKE_CONFIRMED)); // Kick off path validation of server preferred address on handshake // confirmed. - EXPECT_CALL(visitor_, CreatePathValidationContextForServerPreferredAddress( - kServerPreferredAddress)) - .WillOnce(Return( - testing::ByMove(std::make_unique<TestQuicPathValidationContext>( - kNewSelfAddress, kServerPreferredAddress, &new_writer)))); + EXPECT_CALL(visitor_, + OnServerPreferredAddressAvailable(kServerPreferredAddress)) + .WillOnce(Invoke([&]() { + connection_.ValidatePath( + std::make_unique<TestQuicPathValidationContext>( + kNewSelfAddress, kServerPreferredAddress, &new_writer), + std::make_unique<ServerPreferredAddressTestResultDelegate>( + &connection_)); + })); connection_.OnHandshakeComplete(); EXPECT_TRUE(connection_.HasPendingPathValidation()); EXPECT_TRUE(QuicConnectionPeer::IsAlternativePath( @@ -16027,7 +16107,7 @@ EXPECT_TRUE(connection_.IsValidStatelessResetToken(kTestStatelessResetToken)); EXPECT_FALSE(connection_.IsValidStatelessResetToken(kNewStatelessResetToken)); - // Receive path challenge from server preferred address. + // Receive path response from server preferred address. QuicFrames frames; frames.push_back(QuicFrame(QuicPathResponseFrame(99, payload))); // Verify send_algorithm gets reset after migration (new sent packet is not @@ -16069,8 +16149,6 @@ QuicConfig config; config.SetClientConnectionOptions(QuicTagVector{kSPAD}); ServerPreferredAddressInit(config); - const QuicSocketAddress kServerPreferredAddress = - QuicConnectionPeer::GetServerPreferredAddress(&connection_); const QuicSocketAddress kNewSelfAddress = QuicSocketAddress(QuicIpAddress::Loopback6(), /*port=*/23456); TestPacketWriter new_writer(version(), &clock_, Perspective::IS_CLIENT); @@ -16079,11 +16157,15 @@ .WillRepeatedly(Return(HANDSHAKE_CONFIRMED)); // Kick off path validation of server preferred address on handshake // confirmed. - EXPECT_CALL(visitor_, CreatePathValidationContextForServerPreferredAddress( - kServerPreferredAddress)) - .WillOnce(Return( - testing::ByMove(std::make_unique<TestQuicPathValidationContext>( - kNewSelfAddress, kServerPreferredAddress, &new_writer)))); + EXPECT_CALL(visitor_, + OnServerPreferredAddressAvailable(kServerPreferredAddress)) + .WillOnce(Invoke([&]() { + connection_.ValidatePath( + std::make_unique<TestQuicPathValidationContext>( + kNewSelfAddress, kServerPreferredAddress, &new_writer), + std::make_unique<ServerPreferredAddressTestResultDelegate>( + &connection_)); + })); connection_.OnHandshakeComplete(); EXPECT_TRUE(connection_.HasPendingPathValidation()); ASSERT_FALSE(new_writer.path_challenge_frames().empty()); @@ -16096,7 +16178,7 @@ writer_->last_packet_header().destination_connection_id); EXPECT_EQ(kPeerAddress, writer_->last_write_peer_address()); - // Receive path challenge from original server address. + // Receive path response from original server address. QuicFrames frames; frames.push_back(QuicFrame(QuicPathResponseFrame(99, payload))); ProcessFramesPacketWithAddresses(frames, kNewSelfAddress, kPeerAddress, @@ -16135,8 +16217,6 @@ QuicConfig config; config.SetClientConnectionOptions(QuicTagVector{kSPAD}); ServerPreferredAddressInit(config); - const QuicSocketAddress kServerPreferredAddress = - QuicConnectionPeer::GetServerPreferredAddress(&connection_); const QuicSocketAddress kNewSelfAddress = QuicSocketAddress(QuicIpAddress::Loopback6(), /*port=*/23456); TestPacketWriter new_writer(version(), &clock_, Perspective::IS_CLIENT); @@ -16145,13 +16225,17 @@ .WillRepeatedly(Return(HANDSHAKE_CONFIRMED)); // Kick off path validation of server preferred address on handshake // confirmed. - EXPECT_CALL(visitor_, CreatePathValidationContextForServerPreferredAddress( - kServerPreferredAddress)) - .WillOnce(Return( - testing::ByMove(std::make_unique<TestQuicPathValidationContext>( - kNewSelfAddress, kServerPreferredAddress, &new_writer)))); + EXPECT_CALL(visitor_, + OnServerPreferredAddressAvailable(kServerPreferredAddress)) + .WillOnce(Invoke([&]() { + connection_.ValidatePath( + std::make_unique<TestQuicPathValidationContext>( + kNewSelfAddress, kServerPreferredAddress, &new_writer), + std::make_unique<ServerPreferredAddressTestResultDelegate>( + &connection_)); + })); connection_.OnHandshakeComplete(); - EXPECT_TRUE(connection_.HasPendingPathValidation()); + EXPECT_TRUE(connection_.IsValidatingServerPreferredAddress()); EXPECT_TRUE(QuicConnectionPeer::IsAlternativePath( &connection_, kNewSelfAddress, kServerPreferredAddress)); ASSERT_FALSE(new_writer.path_challenge_frames().empty()); @@ -16200,17 +16284,18 @@ if (!connection_.version().HasIetfQuicFrames()) { return; } - QuicIpAddress host; - host.FromString("2604:31c0::"); - const QuicSocketAddress kServerPreferredAddress(host, 443); const QuicSocketAddress kNewSelfAddress = QuicSocketAddress(QuicIpAddress::Loopback6(), /*port=*/23456); TestPacketWriter new_writer(version(), &clock_, Perspective::IS_CLIENT); - EXPECT_CALL(visitor_, CreatePathValidationContextForServerPreferredAddress( - kServerPreferredAddress)) - .WillOnce(Return( - testing::ByMove(std::make_unique<TestQuicPathValidationContext>( - kNewSelfAddress, kServerPreferredAddress, &new_writer)))); + EXPECT_CALL(visitor_, + OnServerPreferredAddressAvailable(kServerPreferredAddress)) + .WillOnce(Invoke([&]() { + connection_.ValidatePath( + std::make_unique<TestQuicPathValidationContext>( + kNewSelfAddress, kServerPreferredAddress, &new_writer), + std::make_unique<ServerPreferredAddressTestResultDelegate>( + &connection_)); + })); QuicConfig config; config.SetClientConnectionOptions(QuicTagVector{kSPAD, kSPA2}); ServerPreferredAddressInit(config); @@ -16236,17 +16321,18 @@ if (!connection_.version().HasIetfQuicFrames()) { return; } - QuicIpAddress host; - host.FromString("2604:31c0::"); - const QuicSocketAddress kServerPreferredAddress(host, 443); const QuicSocketAddress kNewSelfAddress = QuicSocketAddress(QuicIpAddress::Loopback6(), /*port=*/23456); TestPacketWriter new_writer(version(), &clock_, Perspective::IS_CLIENT); - EXPECT_CALL(visitor_, CreatePathValidationContextForServerPreferredAddress( - kServerPreferredAddress)) - .WillOnce(Return( - testing::ByMove(std::make_unique<TestQuicPathValidationContext>( - kNewSelfAddress, kServerPreferredAddress, &new_writer)))); + EXPECT_CALL(visitor_, + OnServerPreferredAddressAvailable(kServerPreferredAddress)) + .WillOnce(Invoke([&]() { + connection_.ValidatePath( + std::make_unique<TestQuicPathValidationContext>( + kNewSelfAddress, kServerPreferredAddress, &new_writer), + std::make_unique<ServerPreferredAddressTestResultDelegate>( + &connection_)); + })); QuicConfig config; config.SetClientConnectionOptions(QuicTagVector{kSPAD, kSPA2}); ServerPreferredAddressInit(config); @@ -16278,17 +16364,18 @@ if (!connection_.version().HasIetfQuicFrames()) { return; } - QuicIpAddress host; - host.FromString("2604:31c0::"); - const QuicSocketAddress kServerPreferredAddress(host, 443); const QuicSocketAddress kNewSelfAddress = QuicSocketAddress(QuicIpAddress::Loopback6(), /*port=*/23456); TestPacketWriter new_writer(version(), &clock_, Perspective::IS_CLIENT); - EXPECT_CALL(visitor_, CreatePathValidationContextForServerPreferredAddress( - kServerPreferredAddress)) - .WillOnce(Return( - testing::ByMove(std::make_unique<TestQuicPathValidationContext>( - kNewSelfAddress, kServerPreferredAddress, &new_writer)))); + EXPECT_CALL(visitor_, + OnServerPreferredAddressAvailable(kServerPreferredAddress)) + .WillOnce(Invoke([&]() { + connection_.ValidatePath( + std::make_unique<TestQuicPathValidationContext>( + kNewSelfAddress, kServerPreferredAddress, &new_writer), + std::make_unique<ServerPreferredAddressTestResultDelegate>( + &connection_)); + })); QuicConfig config; config.SetClientConnectionOptions(QuicTagVector{kSPAD, kSPA2}); ServerPreferredAddressInit(config); @@ -16318,6 +16405,96 @@ EXPECT_TRUE(new_writer.ping_frames().empty()); } +TEST_P(QuicConnectionTest, MultiPortCreationAfterServerMigration) { + if (!GetParam().version.HasIetfQuicFrames()) { + return; + } + QuicConfig config; + config.SetClientConnectionOptions(QuicTagVector{kMPQC, kSPAD}); + ServerPreferredAddressInit(config); + if (!connection_.connection_migration_use_new_cid()) { + return; + } + connection_.SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE); + QuicConnectionId cid_for_preferred_address = TestConnectionId(17); + const QuicSocketAddress kNewSelfAddress = + QuicSocketAddress(QuicIpAddress::Loopback6(), /*port=*/23456); + TestPacketWriter new_writer(version(), &clock_, Perspective::IS_CLIENT); + EXPECT_CALL(visitor_, + OnServerPreferredAddressAvailable(kServerPreferredAddress)) + .WillOnce(Invoke([&]() { + connection_.ValidatePath( + std::make_unique<TestQuicPathValidationContext>( + kNewSelfAddress, kServerPreferredAddress, &new_writer), + std::make_unique<ServerPreferredAddressTestResultDelegate>( + &connection_)); + })); + // The connection should start probing the preferred address after handshake + // confirmed. + QuicPathFrameBuffer payload; + EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)) + .Times(testing::AtLeast(1u)) + .WillOnce(Invoke([&]() { + EXPECT_EQ(1u, new_writer.path_challenge_frames().size()); + payload = new_writer.path_challenge_frames().front().data_buffer; + EXPECT_EQ(kServerPreferredAddress, + new_writer.last_write_peer_address()); + })); + EXPECT_CALL(visitor_, GetHandshakeState()) + .WillRepeatedly(Return(HANDSHAKE_CONFIRMED)); + connection_.OnHandshakeComplete(); + EXPECT_TRUE(connection_.IsValidatingServerPreferredAddress()); + + // Receiving PATH_RESPONSE should cause the connection to migrate to the + // preferred address. + QuicFrames frames; + frames.push_back(QuicFrame(QuicPathResponseFrame(99, payload))); + ProcessFramesPacketWithAddresses(frames, kNewSelfAddress, kPeerAddress, + ENCRYPTION_FORWARD_SECURE); + EXPECT_FALSE(connection_.IsValidatingServerPreferredAddress()); + EXPECT_EQ(kServerPreferredAddress, connection_.effective_peer_address()); + EXPECT_EQ(kNewSelfAddress, connection_.self_address()); + EXPECT_EQ(connection_.connection_id(), cid_for_preferred_address); + + // As the default path changed, the server issued CID 1 should be retired. + auto* retire_peer_issued_cid_alarm = + connection_.GetRetirePeerIssuedConnectionIdAlarm(); + ASSERT_TRUE(retire_peer_issued_cid_alarm->IsSet()); + EXPECT_CALL(visitor_, SendRetireConnectionId(/*sequence_number=*/0u)); + retire_peer_issued_cid_alarm->Fire(); + + const QuicSocketAddress kNewSelfAddress2(kNewSelfAddress.host(), + kNewSelfAddress.port() + 1); + EXPECT_NE(kNewSelfAddress2, kNewSelfAddress); + TestPacketWriter new_writer2(version(), &clock_, Perspective::IS_CLIENT); + QuicNewConnectionIdFrame frame; + frame.connection_id = TestConnectionId(789); + ASSERT_NE(frame.connection_id, connection_.connection_id()); + frame.stateless_reset_token = + QuicUtils::GenerateStatelessResetToken(frame.connection_id); + frame.retire_prior_to = 0u; + frame.sequence_number = 2u; + EXPECT_CALL(visitor_, CreateContextForMultiPortPath()) + .WillOnce(Return( + testing::ByMove(std::make_unique<TestQuicPathValidationContext>( + kNewSelfAddress2, connection_.peer_address(), &new_writer2)))); + connection_.OnNewConnectionIdFrame(frame); + EXPECT_TRUE(connection_.HasPendingPathValidation()); + EXPECT_EQ(1u, new_writer.path_challenge_frames().size()); + payload = new_writer.path_challenge_frames().front().data_buffer; + EXPECT_EQ(kServerPreferredAddress, new_writer.last_write_peer_address()); + EXPECT_EQ(kNewSelfAddress2.host(), new_writer.last_write_source_address()); + EXPECT_TRUE(QuicConnectionPeer::IsAlternativePath( + &connection_, kNewSelfAddress2, connection_.peer_address())); + auto* alt_path = QuicConnectionPeer::GetAlternativePath(&connection_); + EXPECT_FALSE(alt_path->validated); + QuicFrames frames2; + frames2.push_back(QuicFrame(QuicPathResponseFrame(99, payload))); + ProcessFramesPacketWithAddresses(frames2, kNewSelfAddress2, kPeerAddress, + ENCRYPTION_FORWARD_SECURE); + EXPECT_TRUE(alt_path->validated); +} + } // namespace } // namespace test } // namespace quic
diff --git a/quiche/quic/core/quic_session.h b/quiche/quic/core/quic_session.h index 6576325..bf9531a 100644 --- a/quiche/quic/core/quic_session.h +++ b/quiche/quic/core/quic_session.h
@@ -179,12 +179,8 @@ override { return nullptr; } - - std::unique_ptr<QuicPathValidationContext> - CreatePathValidationContextForServerPreferredAddress( - const QuicSocketAddress& /*server_preferred_address*/) override { - return nullptr; - } + void OnServerPreferredAddressAvailable( + const QuicSocketAddress& /*server_preferred_address*/) override {} // QuicStreamFrameDataProducer WriteStreamDataResult WriteStreamData(QuicStreamId id,
diff --git a/quiche/quic/test_tools/quic_test_utils.h b/quiche/quic/test_tools/quic_test_utils.h index f2e38f5..d8271e1 100644 --- a/quiche/quic/test_tools/quic_test_utils.h +++ b/quiche/quic/test_tools/quic_test_utils.h
@@ -502,10 +502,8 @@ MOCK_METHOD(bool, MaybeSendAddressToken, (), (override)); MOCK_METHOD(std::unique_ptr<QuicPathValidationContext>, CreateContextForMultiPortPath, (), (override)); - MOCK_METHOD(std::unique_ptr<QuicPathValidationContext>, - CreatePathValidationContextForServerPreferredAddress, + MOCK_METHOD(void, OnServerPreferredAddressAvailable, (const QuicSocketAddress&), (override)); - void OnBandwidthUpdateTimeout() override {} };
diff --git a/quiche/quic/test_tools/simulator/quic_endpoint.h b/quiche/quic/test_tools/simulator/quic_endpoint.h index d41bef3..1bb9ce0 100644 --- a/quiche/quic/test_tools/simulator/quic_endpoint.h +++ b/quiche/quic/test_tools/simulator/quic_endpoint.h
@@ -109,11 +109,8 @@ override { return nullptr; } - std::unique_ptr<QuicPathValidationContext> - CreatePathValidationContextForServerPreferredAddress( - const QuicSocketAddress& /*server_preferred_address*/) override { - return nullptr; - } + void OnServerPreferredAddressAvailable( + const QuicSocketAddress& /*server_preferred_address*/) override {} // End QuicConnectionVisitorInterface implementation.
diff --git a/quiche/quic/tools/quic_client_base.cc b/quiche/quic/tools/quic_client_base.cc index 948707a..de77089 100644 --- a/quiche/quic/tools/quic_client_base.cc +++ b/quiche/quic/tools/quic_client_base.cc
@@ -50,7 +50,7 @@ QUIC_LOG(WARNING) << "Fail to validate path " << *context << ", stop migrating."; client_->session()->connection()->OnPathValidationFailureAtClient( - /*is_multi_port=*/false); + /*is_multi_port=*/false, *context); } private: @@ -469,7 +469,7 @@ QUIC_LOG(WARNING) << "Fail to validate path " << *context << ", stop migrating."; client_->session()->connection()->OnPathValidationFailureAtClient( - /*is_multi_port=*/false); + /*is_multi_port=*/false, *context); } private:
diff --git a/quiche/quic/tools/quic_simple_client_session.cc b/quiche/quic/tools/quic_simple_client_session.cc index c1e1b2a..9de95d8 100644 --- a/quiche/quic/tools/quic_simple_client_session.cc +++ b/quiche/quic/tools/quic_simple_client_session.cc
@@ -6,8 +6,39 @@ #include <utility> +#include "quiche/quic/core/quic_path_validator.h" + namespace quic { +namespace { + +class QUICHE_EXPORT ServerPreferredAddressResultDelegateWithWriter + : public QuicPathValidator::ResultDelegate { + public: + explicit ServerPreferredAddressResultDelegateWithWriter( + QuicConnection* connection) + : connection_(connection) {} + + // Overridden to transfer the ownership of the new writer. + void OnPathValidationSuccess( + std::unique_ptr<QuicPathValidationContext> /*context*/, + QuicTime /*start_time*/) override { + // TODO(danzh) hand over the ownership of the released writer to client and + // call the connection to migrate. + } + + void OnPathValidationFailure( + std::unique_ptr<QuicPathValidationContext> context) override { + connection_->OnPathValidationFailureAtClient(/*is_multi_port=*/false, + *context); + } + + private: + QuicConnection* connection_ = nullptr; +}; + +} // namespace + QuicSimpleClientSession::QuicSimpleClientSession( const QuicConfig& config, const ParsedQuicVersionVector& supported_versions, QuicConnection* connection, QuicClientBase::NetworkHelper* network_helper, @@ -56,22 +87,24 @@ network_helper_->GetLatestClientAddress(), peer_address()); } -std::unique_ptr<QuicPathValidationContext> -QuicSimpleClientSession::CreatePathValidationContextForServerPreferredAddress( +void QuicSimpleClientSession::OnServerPreferredAddressAvailable( const QuicSocketAddress& server_preferred_address) { const auto self_address = connection()->self_address(); if (network_helper_ == nullptr || !network_helper_->CreateUDPSocketAndBind(server_preferred_address, self_address.host(), 0)) { - return nullptr; + return; } QuicPacketWriter* writer = network_helper_->CreateQuicPacketWriter(); if (writer == nullptr) { - return nullptr; + return; } - return std::make_unique<PathMigrationContext>( - std::unique_ptr<QuicPacketWriter>(writer), - network_helper_->GetLatestClientAddress(), server_preferred_address); + connection()->ValidatePath( + std::make_unique<PathMigrationContext>( + std::unique_ptr<QuicPacketWriter>(writer), + network_helper_->GetLatestClientAddress(), server_preferred_address), + std::make_unique<ServerPreferredAddressResultDelegateWithWriter>( + connection())); } } // namespace quic
diff --git a/quiche/quic/tools/quic_simple_client_session.h b/quiche/quic/tools/quic_simple_client_session.h index 631b36b..4e06cc2 100644 --- a/quiche/quic/tools/quic_simple_client_session.h +++ b/quiche/quic/tools/quic_simple_client_session.h
@@ -27,8 +27,7 @@ HttpDatagramSupport LocalHttpDatagramSupport() override; std::unique_ptr<QuicPathValidationContext> CreateContextForMultiPortPath() override; - std::unique_ptr<QuicPathValidationContext> - CreatePathValidationContextForServerPreferredAddress( + void OnServerPreferredAddressAvailable( const QuicSocketAddress& server_preferred_address) override; bool drop_response_body() const { return drop_response_body_; }