Implement QUIC server validates the new peer address upon receiving PATH_CHALLENGE or peer migration. Protected by FLAGS_quic_reloadable_flag_quic_server_reverse_validate_new_path. PiperOrigin-RevId: 358462375 Change-Id: I3cf20ee7297cc35e784f52d4d7d474045243ef70
diff --git a/quic/core/http/end_to_end_test.cc b/quic/core/http/end_to_end_test.cc index 70fc1d9..2000733 100644 --- a/quic/core/http/end_to_end_test.cc +++ b/quic/core/http/end_to_end_test.cc
@@ -2466,6 +2466,164 @@ // Send a request using the new socket. SendSynchronousBarRequestAndCheckResponse(); + + if (!version_.HasIetfQuicFrames() || + !client_->client()->session()->connection()->validate_client_address()) { + return; + } + QuicConnection* client_connection = GetClientConnection(); + ASSERT_TRUE(client_connection); + EXPECT_EQ(1u, + client_connection->GetStats().num_connectivity_probing_received); + + // Send another request. + SendSynchronousBarRequestAndCheckResponse(); + // By the time the 2nd request is completed, the PATH_RESPONSE must have been + // received by the server. + server_thread_->Pause(); + QuicConnection* server_connection = GetServerConnection(); + if (server_connection != nullptr) { + EXPECT_FALSE(server_connection->HasPendingPathValidation()); + EXPECT_EQ(1u, server_connection->GetStats().num_validated_peer_migration); + } else { + ADD_FAILURE() << "Missing server connection"; + } + server_thread_->Resume(); +} + +TEST_P(EndToEndTest, ConnectionMigrationNewTokenForNewIp) { + ASSERT_TRUE(Initialize()); + if (!version_.HasIetfQuicFrames() || + !client_->client()->session()->connection()->validate_client_address()) { + return; + } + SendSynchronousFooRequestAndCheckResponse(); + + // Store the client IP address which was used to send the first request. + QuicIpAddress old_host = + client_->client()->network_helper()->GetLatestClientAddress().host(); + + // Migrate socket to the new IP address. + QuicIpAddress new_host = TestLoopback(2); + EXPECT_NE(old_host, new_host); + ASSERT_TRUE(client_->client()->MigrateSocket(new_host)); + + // Send a request using the new socket. + SendSynchronousBarRequestAndCheckResponse(); + QuicConnection* client_connection = GetClientConnection(); + ASSERT_TRUE(client_connection); + EXPECT_EQ(1u, + client_connection->GetStats().num_connectivity_probing_received); + + // Send another request to ensure that the server will time to finish the + // reverse path validation and send address token. + SendSynchronousBarRequestAndCheckResponse(); + + client_->Disconnect(); + // The 0-RTT handshake should succeed. + client_->Connect(); + EXPECT_TRUE(client_->client()->WaitForOneRttKeysAvailable()); + ASSERT_TRUE(client_->client()->connected()); + SendSynchronousFooRequestAndCheckResponse(); + + EXPECT_TRUE(GetClientSession()->EarlyDataAccepted()); + EXPECT_TRUE(client_->client()->EarlyDataAccepted()); + + server_thread_->Pause(); + QuicConnection* server_connection = GetServerConnection(); + if (server_connection != nullptr) { + if (GetQuicReloadableFlag(quic_enable_token_based_address_validation)) { + // Verify address is validated via validating token received in INITIAL + // packet. + EXPECT_FALSE(server_connection->GetStats() + .address_validated_via_decrypting_packet); + EXPECT_TRUE(server_connection->GetStats().address_validated_via_token); + } else { + EXPECT_TRUE(server_connection->GetStats() + .address_validated_via_decrypting_packet); + EXPECT_FALSE(server_connection->GetStats().address_validated_via_token); + } + } else { + ADD_FAILURE() << "Missing server connection"; + } + server_thread_->Resume(); + client_->Disconnect(); +} + +// A writer which copies the packet and send the copy with a specified self +// address and then send the same packet with the original self address. +class DuplicatePacketWithSpoofedSelfAddressWriter + : public QuicPacketWriterWrapper { + public: + WriteResult WritePacket(const char* buffer, + size_t buf_len, + const QuicIpAddress& self_address, + const QuicSocketAddress& peer_address, + PerPacketOptions* options) override { + if (self_address_to_overwrite_.IsInitialized()) { + // Send the same packet on the overwriting address before sending on the + // actual self address. + QuicPacketWriterWrapper::WritePacket( + buffer, buf_len, self_address_to_overwrite_, peer_address, options); + } + return QuicPacketWriterWrapper::WritePacket(buffer, buf_len, self_address, + peer_address, options); + } + + void set_self_address_to_overwrite(const QuicIpAddress& self_address) { + self_address_to_overwrite_ = self_address; + } + + private: + QuicIpAddress self_address_to_overwrite_; +}; + +TEST_P(EndToEndTest, ClientAddressSpoofedForSomePeriod) { + ASSERT_TRUE(Initialize()); + if (!version_.HasIetfQuicFrames() || + !client_->client()->session()->connection()->validate_client_address()) { + return; + } + auto writer = new DuplicatePacketWithSpoofedSelfAddressWriter(); + client_.reset(CreateQuicClient(writer)); + QuicIpAddress real_host = TestLoopback(1); + client_->MigrateSocket(real_host); + SendSynchronousFooRequestAndCheckResponse(); + EXPECT_EQ( + 0u, GetClientConnection()->GetStats().num_connectivity_probing_received); + EXPECT_EQ( + real_host, + client_->client()->network_helper()->GetLatestClientAddress().host()); + client_->WaitForDelayedAcks(); + + std::string large_body(10240, 'a'); + AddToCache("/large_response", 200, large_body); + + QuicIpAddress spoofed_host = TestLoopback(2); + writer->set_self_address_to_overwrite(spoofed_host); + + client_->SendRequest("/large_response"); + QuicConnection* client_connection = GetClientConnection(); + QuicPacketCount num_packets_received = + client_connection->GetStats().packets_received; + + while (client_->client()->WaitForEvents() && client_->connected()) { + if (client_connection->GetStats().packets_received > num_packets_received) { + // Ideally the client won't receive any packets till the server finds out + // the new client address is not working. But there are 2 corner cases: + // 1) Before the server received the packet from spoofed address, it might + // send packets to the real client address. So the client will immediately + // switch back to use the original address; + // 2) Between the server fails reverse path validation and the client + // receives packets again, the client might sent some packets with the + // spoofed address and triggers another migration. + // In both corner cases, the attempted migration should fail and fall back + // to the working path. + writer->set_self_address_to_overwrite(QuicIpAddress()); + } + } + client_->WaitForResponse(); + EXPECT_EQ(large_body, client_->response_body()); } TEST_P(EndToEndTest, AsynchronousConnectionMigrationClientIPChanged) { @@ -2491,6 +2649,10 @@ client_->client()->WaitForEvents(); } EXPECT_EQ(new_host, client_->client()->session()->self_address().host()); + QuicConnection* client_connection = GetClientConnection(); + ASSERT_TRUE(client_connection); + EXPECT_EQ(client_connection->validate_client_address() ? 1u : 0, + client_connection->GetStats().num_connectivity_probing_received); // Send a request using the new socket. SendSynchronousBarRequestAndCheckResponse(); } @@ -2546,6 +2708,21 @@ client_->client()->network_helper()->GetLatestClientAddress(); EXPECT_EQ(old_address.host(), new_address.host()); EXPECT_NE(old_address.port(), new_address.port()); + + if (!version_.HasIetfQuicFrames() || + !GetClientConnection()->validate_client_address()) { + return; + } + + server_thread_->Pause(); + QuicConnection* server_connection = GetServerConnection(); + if (server_connection != nullptr) { + EXPECT_FALSE(server_connection->HasPendingPathValidation()); + EXPECT_EQ(1u, server_connection->GetStats().num_validated_peer_migration); + } else { + ADD_FAILURE() << "Missing server connection"; + } + server_thread_->Resume(); } TEST_P(EndToEndTest, NegotiatedInitialCongestionWindow) { @@ -4564,6 +4741,46 @@ std::unique_ptr<PerPacketOptions> options_; }; +TEST_P(EndToEndTest, ClientValidateNewNetwork) { + ASSERT_TRUE(Initialize()); + if (!version_.HasIetfQuicFrames() || + !GetClientConnection()->validate_client_address()) { + return; + } + client_.reset(EndToEndTest::CreateQuicClient(nullptr)); + SendSynchronousFooRequestAndCheckResponse(); + + // Store the client IP address which was used to send the first request. + QuicIpAddress old_host = + client_->client()->network_helper()->GetLatestClientAddress().host(); + + // Migrate socket to the new IP address. + QuicIpAddress new_host = TestLoopback(2); + EXPECT_NE(old_host, new_host); + + client_->client()->ValidateNewNetwork(new_host); + // Send a request using the old socket. + EXPECT_EQ(kBarResponseBody, client_->SendSynchronousRequest("/bar")); + // Client should have received a PATH_CHALLENGE. + QuicConnection* client_connection = GetClientConnection(); + ASSERT_TRUE(client_connection); + EXPECT_EQ(1u, + client_connection->GetStats().num_connectivity_probing_received); + + // Send another request to make sure THE server will receive PATH_RESPONSE. + client_->SendSynchronousRequest("/eep"); + + server_thread_->Pause(); + QuicConnection* server_connection = GetServerConnection(); + if (server_connection != nullptr) { + EXPECT_EQ(1u, + server_connection->GetStats().num_connectivity_probing_received); + } else { + ADD_FAILURE() << "Missing server connection"; + } + server_thread_->Resume(); +} + TEST_P(EndToEndPacketReorderingTest, ReorderedPathChallenge) { ASSERT_TRUE(Initialize()); if (!version_.HasIetfQuicFrames() || @@ -4593,7 +4810,7 @@ holding_writer->HoldNextPacket(); // A packet with PATH_CHALLENGE will be held in the writer. - ASSERT_TRUE(client_->client()->ValidateAndMigrateSocket(new_host)); + client_->client()->ValidateNewNetwork(new_host); // Send (on-hold) PATH_CHALLENGE after this request. client_->SendRequest("/foo"); @@ -4608,6 +4825,12 @@ // client. EXPECT_EQ(kBarResponseBody, client_->SendSynchronousRequest("/bar")); + // Client should have received a PATH_CHALLENGE. + QuicConnection* client_connection = GetClientConnection(); + ASSERT_TRUE(client_connection); + EXPECT_EQ(client_connection->validate_client_address() ? 1u : 0, + client_connection->GetStats().num_connectivity_probing_received); + server_thread_->Pause(); QuicConnection* server_connection = GetServerConnection(); if (server_connection != nullptr) { @@ -4646,6 +4869,10 @@ while (client_->client()->HasPendingPathValidation()) { client_->client()->WaitForEvents(); } + EXPECT_EQ(old_addr, client_->client()->session()->self_address()); + server_writer_->set_fake_packet_loss_percentage(0); + EXPECT_EQ(kBarResponseBody, client_->SendSynchronousRequest("/bar")); + server_thread_->Pause(); QuicConnection* server_connection = GetServerConnection(); if (server_connection != nullptr) { @@ -4655,10 +4882,6 @@ ADD_FAILURE() << "Missing server connection"; } server_thread_->Resume(); - - EXPECT_EQ(old_addr, client_->client()->session()->self_address()); - server_writer_->set_fake_packet_loss_percentage(0); - EXPECT_EQ(kBarResponseBody, client_->SendSynchronousRequest("/bar")); } TEST_P(EndToEndPacketReorderingTest, Buffer0RttRequest) {
diff --git a/quic/core/quic_connection.cc b/quic/core/quic_connection.cc index 81a15ad..13271d6 100644 --- a/quic/core/quic_connection.cc +++ b/quic/core/quic_connection.cc
@@ -11,6 +11,7 @@ #include <iterator> #include <limits> #include <memory> +#include <optional> #include <set> #include <string> #include <utility> @@ -18,6 +19,8 @@ #include "absl/strings/escaping.h" #include "absl/strings/str_cat.h" #include "absl/strings/string_view.h" +#include "quic/core/congestion_control/rtt_stats.h" +#include "quic/core/congestion_control/send_algorithm_interface.h" #include "quic/core/crypto/crypto_protocol.h" #include "quic/core/crypto/crypto_utils.h" #include "quic/core/crypto/quic_decrypter.h" @@ -368,7 +371,12 @@ GetQuicReloadableFlag(quic_use_encryption_level_context)), path_validator_(alarm_factory_, &arena_, this, random_generator_), alternative_path_(QuicSocketAddress(), QuicSocketAddress()), - most_recent_frame_type_(NUM_FRAME_TYPES) { + most_recent_frame_type_(NUM_FRAME_TYPES), + validate_client_addresses_( + framer_.version().HasIetfQuicFrames() && use_path_validator_ && + count_bytes_on_alternative_path_separately_ && + update_packet_content_returns_connected_ && + GetQuicReloadableFlag(quic_server_reverse_validate_new_path)) { QUIC_BUG_IF(!start_peer_migration_earlier_ && send_path_response_); QUICHE_DCHECK(perspective_ == Perspective::IS_CLIENT || @@ -1192,8 +1200,8 @@ if (perspective_ == Perspective::IS_CLIENT) { if (!GetLargestReceivedPacket().IsInitialized() || header.packet_number > GetLargestReceivedPacket()) { - // Update peer_address_ and effective_peer_address_ immediately for - // client connections. + // Update direct_peer_address_ and default path peer_address immediately + // for client connections. // TODO(fayang): only change peer addresses in application data packet // number space. UpdatePeerAddress(last_packet_source_address_); @@ -1597,6 +1605,23 @@ return connected_; } +class ReversePathValidationContext : public QuicPathValidationContext { + public: + ReversePathValidationContext(const QuicSocketAddress& self_address, + const QuicSocketAddress& peer_address, + const QuicSocketAddress& effective_peer_address, + QuicConnection* connection) + : QuicPathValidationContext(self_address, + peer_address, + effective_peer_address), + connection_(connection) {} + + QuicPacketWriter* WriterToUse() override { return connection_->writer(); } + + private: + QuicConnection* connection_; +}; + bool QuicConnection::OnPathChallengeFrame(const QuicPathChallengeFrame& frame) { QUIC_BUG_IF(!connected_) << "Processing PATH_CHALLENGE frame when connection " "is closed. Last frame: " @@ -1604,9 +1629,30 @@ if (has_path_challenge_in_current_packet_) { QUICHE_DCHECK(send_path_response_); QUIC_RELOADABLE_FLAG_COUNT_N(quic_send_path_response, 2, 5); - // Only respond to the 1st PATH_CHALLENGE. + // Only respond to the 1st PATH_CHALLENGE in the packet. return true; } + if (!validate_client_addresses_) { + return OnPathChallengeFrameInternal(frame); + } + { + // UpdatePacketStateAndReplyPathChallenge() may start reverse path + // validation, if so bundle the PATH_CHALLENGE together with the + // PATH_RESPONSE. This context needs to be out of scope before returning. + // TODO(danzh) inline OnPathChallengeFrameInternal() once + // support_reverse_path_validation_ is deprecated. + QuicPacketCreator::ScopedPeerAddressContext context( + &packet_creator_, last_packet_source_address_); + if (!OnPathChallengeFrameInternal(frame)) { + return false; + } + } + return connected_; +} + +bool QuicConnection::OnPathChallengeFrameInternal( + const QuicPathChallengeFrame& frame) { + // UpdatePacketContent() may start reverse path validation. if (!UpdatePacketContent(PATH_CHALLENGE_FRAME)) { return false; } @@ -1625,9 +1671,9 @@ QUIC_RELOADABLE_FLAG_COUNT_N(quic_send_path_response, 3, 5); has_path_challenge_in_current_packet_ = true; MaybeUpdateAckTimeout(); - // Queue or send PATH_RESPONSE. No matter where the pending data are supposed - // to sent, PATH_RESPONSE should always be sent to the source address of the - // current incoming packet. + // Queue or send PATH_RESPONSE. Send PATH_RESPONSE to the source address of + // the current incoming packet, even if it's not the default path or the + // alternative path. if (!SendPathResponse(frame.data_buffer, last_packet_source_address_)) { // Queue the payloads to re-try later. pending_path_challenge_payloads_.push_back( @@ -2510,7 +2556,7 @@ const QuicSocketAddress effective_peer_addr = GetEffectivePeerAddressFromCurrentPacket(); - // effective_peer_address_ must be initialized at the beginning of the + // The default path peer_address must be initialized at the beginning of the // first packet processed(here). If effective_peer_addr is uninitialized, // just set effective_peer_address_ to the direct peer address. default_path_.peer_address = effective_peer_addr.IsInitialized() @@ -2542,7 +2588,8 @@ } time_of_last_received_packet_ = packet.receipt_time(); QUIC_DVLOG(1) << ENDPOINT << "time of last received packet: " - << packet.receipt_time().ToDebuggingValue(); + << packet.receipt_time().ToDebuggingValue() << " from peer " + << last_packet_source_address_; ScopedPacketFlusher flusher(this); if (!framer_.ProcessPacket(packet)) { @@ -2565,7 +2612,8 @@ << sent_packet_manager_.GetLargestObserved() << ", highest_packet_sent_before_effective_peer_migration_ = " << highest_packet_sent_before_effective_peer_migration_; - if (active_effective_peer_migration_type_ != NO_CHANGE && + if (!validate_client_addresses_ && + active_effective_peer_migration_type_ != NO_CHANGE && sent_packet_manager_.GetLargestObserved().IsInitialized() && (!highest_packet_sent_before_effective_peer_migration_.IsInitialized() || sent_packet_manager_.GetLargestObserved() > @@ -2909,7 +2957,10 @@ QUIC_CODE_COUNT(quic_throttled_by_amplification_limit); QUIC_DVLOG(1) << ENDPOINT << "Constrained by amplification restriction to peer address " - << direct_peer_address_; + << default_path_.peer_address << " bytes received " + << default_path_.bytes_received_before_address_validation + << ", bytes sent" + << default_path_.bytes_sent_before_address_validation; ++stats_.num_amplification_throttling; return false; } @@ -4719,30 +4770,202 @@ return; } highest_packet_sent_before_effective_peer_migration_.Clear(); + const bool send_address_token = + active_effective_peer_migration_type_ != PORT_CHANGE; active_effective_peer_migration_type_ = NO_CHANGE; + ++stats_.num_validated_peer_migration; + if (!validate_client_addresses_) { + return; + } + // Lift anti-amplification limit. + default_path_.validated = true; + alternative_path_.Clear(); + if (send_address_token) { + visitor_->MaybeSendAddressToken(); + } } void QuicConnection::StartEffectivePeerMigration(AddressChangeType type) { // TODO(fayang): Currently, all peer address change type are allowed. Need to // add a method ShouldAllowPeerAddressChange(PeerAddressChangeType type) to // determine whether |type| is allowed. + if (!validate_client_addresses_) { + if (type == NO_CHANGE) { + QUIC_BUG << "EffectivePeerMigration started without address change."; + return; + } + QUIC_DLOG(INFO) << ENDPOINT << "Effective peer's ip:port changed from " + << default_path_.peer_address.ToString() << " to " + << GetEffectivePeerAddressFromCurrentPacket().ToString() + << ", address change type is " << type + << ", migrating connection."; + + highest_packet_sent_before_effective_peer_migration_ = + sent_packet_manager_.GetLargestSentPacket(); + default_path_.peer_address = GetEffectivePeerAddressFromCurrentPacket(); + active_effective_peer_migration_type_ = type; + + OnConnectionMigration(); + return; + } + if (type == NO_CHANGE) { + UpdatePeerAddress(last_packet_source_address_); QUIC_BUG << "EffectivePeerMigration started without address change."; return; } + + // Action items: + // 1. Switch congestion controller; + // 2. Update default_path_ (addresses, validation and bytes accounting); + // 3. Save previous default path if needed; + // 4. Kick off reverse path validation if needed. + // Items 1 and 2 are must-to-do. Items 3 and 4 depends on if the new address + // is validated or not and which path the incoming packet is on. + + const QuicSocketAddress current_effective_peer_address = + GetEffectivePeerAddressFromCurrentPacket(); QUIC_DLOG(INFO) << ENDPOINT << "Effective peer's ip:port changed from " << default_path_.peer_address.ToString() << " to " - << GetEffectivePeerAddressFromCurrentPacket().ToString() + << current_effective_peer_address.ToString() << ", address change type is " << type << ", migrating connection."; - highest_packet_sent_before_effective_peer_migration_ = - sent_packet_manager_.GetLargestSentPacket(); - default_path_.peer_address = GetEffectivePeerAddressFromCurrentPacket(); + const QuicSocketAddress previous_direct_peer_address = direct_peer_address_; + PathState previous_default_path = std::move(default_path_); active_effective_peer_migration_type_ = type; - - // TODO(wub): Move these calls to OnEffectivePeerMigrationValidated. OnConnectionMigration(); + + // Update congestion controller if the address change type is not PORT_CHANGE. + if (type == PORT_CHANGE) { + QUICHE_DCHECK(previous_default_path.validated || + alternative_path_.validated && + alternative_path_.send_algorithm != nullptr); + // No need to store previous congestion controller because either the new + // default path is validated or the alternative path is validated and + // already has associated congestion controller. + } else { + previous_default_path.rtt_stats.emplace(); + previous_default_path.rtt_stats->CloneFrom( + *sent_packet_manager_.GetRttStats()); + // If the new peer address share the same IP with the alternative path, the + // connection should switch to the congestion controller of the alternative + // path. Otherwise, the connection should use a brand new one. + // In order to re-use existing code in sent_packet_manager_, reset + // congestion controller to initial state first and then change to the one + // on alternative path. + // TODO(danzh) combine these two steps into one after deprecating gQUIC. + previous_default_path.send_algorithm = + sent_packet_manager_.OnConnectionMigration( + /*reset_send_algorithm=*/true); + // OnConnectionMigration() might have marked in-flight packets to be + // retransmitted if there is any. + QUICHE_DCHECK(!sent_packet_manager_.HasInFlightPackets()); + // Stop detections in quiecense. + blackhole_detector_.StopDetection(); + + if (alternative_path_.peer_address.host() == + current_effective_peer_address.host() && + alternative_path_.send_algorithm != nullptr) { + // Update the default path with the congestion controller of the + // alternative path. + sent_packet_manager_.SetSendAlgorithm( + alternative_path_.send_algorithm.release()); + sent_packet_manager_.SetRttStats( + std::move(alternative_path_.rtt_stats).value()); + } + } + + // Update to the new peer address. + UpdatePeerAddress(last_packet_source_address_); + // Update the default path. + if (IsAlternativePath(last_packet_destination_address_, + current_effective_peer_address)) { + default_path_ = std::move(alternative_path_); + } else { + default_path_ = PathState(last_packet_destination_address_, + current_effective_peer_address); + // The path is considered validated if its peer IP address matches any + // validated path's peer IP address. + default_path_.validated = + (alternative_path_.peer_address.host() == + current_effective_peer_address.host() && + alternative_path_.validated) || + (previous_default_path.validated && type == PORT_CHANGE); + } + if (!current_incoming_packet_received_bytes_counted_) { + // Increment bytes counting on the new default path. + default_path_.bytes_received_before_address_validation += last_size_; + current_incoming_packet_received_bytes_counted_ = true; + } + + if (!previous_default_path.validated) { + // If the old address is under validation, cancel and fail it. Failing to + // validate the old path shouldn't take any effect. + QUIC_DVLOG(1) << "Cancel validation of previous peer address change to " + << previous_default_path.peer_address + << " upon peer migration to " << default_path_.peer_address; + path_validator_.CancelPathValidation(); + ++stats_.num_peer_migration_while_validating_default_path; + } + + // Clear alternative path if the new default path shares the same IP as the + // alternative path. + if (alternative_path_.peer_address.host() == + default_path_.peer_address.host()) { + alternative_path_.Clear(); + } + + if (default_path_.validated) { + QUIC_DVLOG(1) << "Peer migrated to a validated address."; + // No need to save previous default path, validate new peer address or + // update bytes sent/received. + if (!(previous_default_path.validated && type == PORT_CHANGE)) { + // The alternative path was validated because of proactive reverse path + // validation. + ++stats_.num_peer_migration_to_proactively_validated_address; + } + OnEffectivePeerMigrationValidated(); + return; + } + + // The new default address is not validated yet. Anti-amplification limit is + // enforced. + QUICHE_DCHECK(EnforceAntiAmplificationLimit()); + QUIC_DVLOG(1) << "Apply anti-amplification limit to effective peer address " + << default_path_.peer_address << " with " + << default_path_.bytes_sent_before_address_validation + << " bytes sent and " + << default_path_.bytes_received_before_address_validation + << " bytes received."; + + QUICHE_DCHECK(!alternative_path_.peer_address.IsInitialized() || + alternative_path_.peer_address.host() != + default_path_.peer_address.host()); + + // Save previous default path to the altenative path. + if (previous_default_path.validated) { + // The old path is a validated path which the connection might revert back + // to later. Store it as the alternative path. + alternative_path_ = std::move(previous_default_path); + QUICHE_DCHECK(alternative_path_.send_algorithm != nullptr); + } + + // If the new address is not validated and the connection is not already + // validating that address, a new reverse path validation is needed. + if (!path_validator_.IsValidatingPeerAddress( + current_effective_peer_address)) { + ++stats_.num_reverse_path_validtion_upon_migration; + ValidatePath(std::make_unique<ReversePathValidationContext>( + default_path_.self_address, peer_address(), + default_path_.peer_address, this), + std::make_unique<ReversePathValidationResultDelegate>( + this, previous_direct_peer_address)); + } else { + QUIC_DVLOG(1) << "Peer address " << default_path_.peer_address + << " is already under validation, wait for result."; + ++stats_.num_peer_migration_to_proactively_validated_address; + } } void QuicConnection::OnConnectionMigration() { @@ -4756,7 +4979,8 @@ } visitor_->OnConnectionMigration(active_effective_peer_migration_type_); if (active_effective_peer_migration_type_ != PORT_CHANGE && - active_effective_peer_migration_type_ != IPV4_SUBNET_CHANGE) { + active_effective_peer_migration_type_ != IPV4_SUBNET_CHANGE && + !validate_client_addresses_) { sent_packet_manager_.OnConnectionMigration(/*reset_send_algorithm=*/false); } } @@ -4863,13 +5087,45 @@ if (type == PATH_CHALLENGE_FRAME && !IsAlternativePath(last_packet_destination_address_, current_effective_peer_address)) { - // Only override the alternative path state upon a PATH_CHALLENGE. QUIC_DVLOG(1) << "The peer is probing a new path with effective peer address " << current_effective_peer_address << ", self address " << last_packet_destination_address_; - alternative_path_ = PathState(last_packet_destination_address_, - current_effective_peer_address); + if (!validate_client_addresses_) { + alternative_path_ = PathState(last_packet_destination_address_, + current_effective_peer_address); + } else if (!default_path_.validated) { + // Skip reverse path validation because either handshake hasn't + // completed or the connection is validating the default path. Using + // PATH_CHALLENGE to validate alternative client address before + // handshake gets comfirmed is meaningless because anyone can respond to + // it. If the connection is validating the default path, this + // alternative path is currently the only validated path which shouldn't + // be overridden. + QUIC_DVLOG(1) << "The connection hasn't finished handshake or is " + "validating a recent peer address change."; + QUIC_BUG_IF(IsHandshakeConfirmed() && !alternative_path_.validated) + << "No validated peer address to send after handshake comfirmed."; + } else if (!IsReceivedPeerAddressValidated()) { + // Only override alternative path state upon receiving a PATH_CHALLENGE + // from an unvalidated peer address, and the connection isn't validating + // a recent peer migration. + alternative_path_ = PathState(last_packet_destination_address_, + current_effective_peer_address); + // Conditions to proactively validate peer address: + // The perspective is server + // The PATH_CHALLENGE is received on an unvalidated alternative path. + // The connection isn't validating migrated peer address, which is of + // higher prority. + QUIC_DVLOG(1) << "Proactively validate the effective peer address " + << current_effective_peer_address; + ValidatePath( + std::make_unique<ReversePathValidationContext>( + default_path_.self_address, current_effective_peer_address, + current_effective_peer_address, this), + std::make_unique<ReversePathValidationResultDelegate>( + this, peer_address())); + } } MaybeUpdateBytesReceivedFromAlternativeAddress(last_size_); return !update_packet_content_returns_connected_ || connected_; @@ -4917,7 +5173,7 @@ << last_packet_source_address_ << ", peer_address_:" << peer_address() << ", last_packet_destination_address_:" << last_packet_destination_address_ - << ", self_address_:" << default_path_.self_address; + << ", default path self_address :" << default_path_.self_address; } return !update_packet_content_returns_connected_ || connected_; } @@ -4962,13 +5218,17 @@ if (GetLargestReceivedPacket().IsInitialized() && last_header_.packet_number == GetLargestReceivedPacket()) { - UpdatePeerAddress(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. + if (!validate_client_addresses_) { + UpdatePeerAddress(last_packet_source_address_); + } StartEffectivePeerMigration(current_effective_peer_migration_type_); + } else { + UpdatePeerAddress(last_packet_source_address_); } } current_effective_peer_migration_type_ = NO_CHANGE; @@ -5884,6 +6144,124 @@ validated = false; bytes_received_before_address_validation = 0; bytes_sent_before_address_validation = 0; + send_algorithm = nullptr; + rtt_stats = absl::nullopt; +} + +QuicConnection::PathState::PathState(PathState&& other) { + *this = std::move(other); +} + +QuicConnection::PathState& QuicConnection::PathState::operator=( + QuicConnection::PathState&& other) { + if (this != &other) { + self_address = other.self_address; + peer_address = other.peer_address; + validated = other.validated; + bytes_received_before_address_validation = + other.bytes_received_before_address_validation; + bytes_sent_before_address_validation = + other.bytes_sent_before_address_validation; + send_algorithm = std::move(other.send_algorithm); + if (other.rtt_stats.has_value()) { + rtt_stats.emplace(); + rtt_stats->CloneFrom(other.rtt_stats.value()); + } else { + rtt_stats.reset(); + } + other.Clear(); + } + return *this; +} + +bool QuicConnection::IsReceivedPeerAddressValidated() const { + QuicSocketAddress current_effective_peer_address = + GetEffectivePeerAddressFromCurrentPacket(); + QUICHE_DCHECK(current_effective_peer_address.IsInitialized()); + return (alternative_path_.peer_address.host() == + current_effective_peer_address.host() && + alternative_path_.validated) || + (default_path_.validated && default_path_.peer_address.host() == + current_effective_peer_address.host()); +} + +QuicConnection::ReversePathValidationResultDelegate:: + ReversePathValidationResultDelegate( + QuicConnection* connection, + const QuicSocketAddress& direct_peer_address) + : QuicPathValidator::ResultDelegate(), + connection_(connection), + original_direct_peer_address_(direct_peer_address) {} + +void QuicConnection::ReversePathValidationResultDelegate:: + OnPathValidationSuccess( + std::unique_ptr<QuicPathValidationContext> context) { + QUIC_DLOG(INFO) << "Successfully validated new path " << *context; + if (connection_->IsDefaultPath(context->self_address(), + context->peer_address())) { + connection_->OnEffectivePeerMigrationValidated(); + } else { + QUICHE_DCHECK(connection_->IsAlternativePath( + context->self_address(), context->effective_peer_address())); + QUIC_DVLOG(1) << "Mark alternative peer address " + << context->effective_peer_address() << " validated."; + connection_->alternative_path_.validated = true; + } +} + +void QuicConnection::ReversePathValidationResultDelegate:: + OnPathValidationFailure( + std::unique_ptr<QuicPathValidationContext> context) { + if (!connection_->connected()) { + return; + } + QUIC_DLOG(INFO) << "Fail to validate new path " << *context; + if (connection_->IsDefaultPath(context->self_address(), + context->peer_address())) { + // Only act upon validation failure on the default path. + connection_->RestoreToLastValidatedPath(original_direct_peer_address_); + } else if (connection_->IsAlternativePath( + context->self_address(), context->effective_peer_address())) { + connection_->alternative_path_.Clear(); + } +} + +void QuicConnection::RestoreToLastValidatedPath( + QuicSocketAddress original_direct_peer_address) { + QUIC_DLOG(INFO) << "Switch back to use the old peer address " + << alternative_path_.peer_address; + if (!alternative_path_.validated) { + // If not validated by now, close connection silently so that the following + // packets received will be rejected. + CloseConnection(QUIC_INTERNAL_ERROR, + "No validated peer address to use after reverse path " + "validation failure.", + ConnectionCloseBehavior::SILENT_CLOSE); + return; + } + + // Revert congestion control context to old state. + sent_packet_manager_.OnConnectionMigration(true); + QUICHE_DCHECK(!sent_packet_manager_.HasInFlightPackets()); + // Stop detections in quiecense. + blackhole_detector_.StopDetection(); + + if (alternative_path_.send_algorithm != nullptr) { + sent_packet_manager_.SetSendAlgorithm( + alternative_path_.send_algorithm.release()); + sent_packet_manager_.SetRttStats(alternative_path_.rtt_stats.value()); + } else { + QUIC_BUG << "Fail to store congestion controller before migration."; + } + + UpdatePeerAddress(original_direct_peer_address); + default_path_ = std::move(alternative_path_); + + active_effective_peer_migration_type_ = NO_CHANGE; + ++stats_.num_invalid_peer_migration; + // The reverse path validation failed because of alarm firing, flush all the + // pending writes previously throttled by anti-amplification limit. + WriteIfNotBlocked(); } #undef ENDPOINT // undef for jumbo builds
diff --git a/quic/core/quic_connection.h b/quic/core/quic_connection.h index ab111e5..33cf33b 100644 --- a/quic/core/quic_connection.h +++ b/quic/core/quic_connection.h
@@ -1149,7 +1149,9 @@ QuicPacketWriter* writer_to_use) const override; // Start vaildating the path defined by |context| asynchronously and call the - // |result_delegate| after validation finishes. + // |result_delegate| after validation finishes. If the connection is + // validating another path, cancel and fail that validation before starting + // this one. void ValidatePath( std::unique_ptr<QuicPathValidationContext> context, std::unique_ptr<QuicPathValidator::ResultDelegate> result_delegate); @@ -1191,6 +1193,8 @@ virtual std::vector<QuicConnectionId> GetActiveServerConnectionIds() const; + bool validate_client_address() const { return validate_client_addresses_; } + protected: // Calls cancel() on all the alarms owned by this connection. void CancelAllAlarms(); @@ -1257,8 +1261,8 @@ // Decides whether to send probing retransmissions, and does so if required. void MaybeSendProbingRetransmissions(); - // Notify various components(SendPacketManager, Session etc.) that this - // connection has been migrated. + // Notify various components(Session etc.) that this connection has been + // migrated. virtual void OnConnectionMigration(); // Return whether the packet being processed is a connectivity probing. @@ -1292,6 +1296,10 @@ : self_address(alternative_self_address), peer_address(alternative_peer_address) {} + PathState(PathState&& other); + + PathState& operator=(PathState&& other); + // Reset all the members. void Clear(); @@ -1307,6 +1315,10 @@ // becomes the default path if |peer_address| hasn't been validated. QuicByteCount bytes_received_before_address_validation = 0; QuicByteCount bytes_sent_before_address_validation = 0; + // Points to the send algorithm on the old default path while connection is + // validating migrated peer address. Nullptr otherwise. + std::unique_ptr<SendAlgorithmInterface> send_algorithm; + absl::optional<RttStats> rtt_stats; }; using QueuedPacketList = std::list<SerializedPacket>; @@ -1346,6 +1358,27 @@ EncryptionLevel encryption_level; }; + // Handles the reverse path validation result depending on connection state: + // whether the connection is validating a migrated peer address or is + // validating an alternative path. + class ReversePathValidationResultDelegate + : public QuicPathValidator::ResultDelegate { + public: + ReversePathValidationResultDelegate( + QuicConnection* connection, + const QuicSocketAddress& direct_peer_address); + + void OnPathValidationSuccess( + std::unique_ptr<QuicPathValidationContext> context) override; + + void OnPathValidationFailure( + std::unique_ptr<QuicPathValidationContext> context) override; + + private: + QuicConnection* connection_; + QuicSocketAddress original_direct_peer_address_; + }; + // Notifies the visitor of the close and marks the connection as disconnected. // Does not send a connection close frame to the peer. It should only be // called by CloseConnection or OnConnectionCloseFrame, OnPublicResetPacket, @@ -1620,16 +1653,33 @@ void MaybeUpdateBytesReceivedFromAlternativeAddress( QuicByteCount received_packet_size); - // Return true if the given self address and peer address is the same as the - // self address and peer address of the default path. + // TODO(danzh) pass in PathState of the incoming packet or the packet sent + // once PathState is used in packet creator. Return true if the given self + // address and peer address is the same as the self address and peer address + // of the default path. bool IsDefaultPath(const QuicSocketAddress& self_address, const QuicSocketAddress& peer_address) const; - // Return true if the given self address and peer address is the same as the + // Return true if the |self_address| and |peer_address| is the same as the // self address and peer address of the alternative path. bool IsAlternativePath(const QuicSocketAddress& self_address, const QuicSocketAddress& peer_address) const; + // Restore connection default path and congestion control state to the last + // validated path and its state. Called after fail to validate peer address + // upon detecting a peer migration. + void RestoreToLastValidatedPath( + QuicSocketAddress original_direct_peer_address); + + // Return true if the current incoming packet is from a peer address that is + // validated. + bool IsReceivedPeerAddressValidated() const; + + // Called after receiving PATH_CHALLENGE. Update packet content and + // alternative path state if the current packet is from a non-default path. + // Return true if framer should continue processing the packet. + bool OnPathChallengeFrameInternal(const QuicPathChallengeFrame& frame); + QuicFramer framer_; // Contents received in the current packet, especially used to identify @@ -2029,8 +2079,12 @@ // future. On the client side, it gets created when the client starts // validating a new path and gets cleared once it becomes the default path or // the path validation fails or replaced by a newer path of interest. On the - // server side, alternative_path gets created when server receives - // PATH_CHALLENGE on non-default path. + // server side, alternative_path gets created when server: 1) receives + // PATH_CHALLENGE on non-default path, or 2) switches to a not yet validated + // default path such that it needs to store the previous validated default + // path. + // Note that if alternative_path_ stores a validated path information (case + // 2), do not override it on receiving PATH_CHALLENGE (case 1). PathState alternative_path_; // This field is used to debug b/177312785. @@ -2043,6 +2097,9 @@ bool update_packet_content_returns_connected_ = GetQuicReloadableFlag(quic_update_packet_content_returns_connected); + + // If true, upon seeing a new client address, validate the client address. + const bool validate_client_addresses_; }; } // namespace quic
diff --git a/quic/core/quic_connection_stats.h b/quic/core/quic_connection_stats.h index a50ba83..0cf3489 100644 --- a/quic/core/quic_connection_stats.h +++ b/quic/core/quic_connection_stats.h
@@ -192,6 +192,23 @@ bool address_validated_via_token = false; size_t ping_frames_sent = 0; + + // Number of detected peer address changes which changes to a peer address + // validated by earlier path validation. + size_t num_peer_migration_to_proactively_validated_address = 0; + // Number of detected peer address changes which triggers reverse path + // validation. + size_t num_reverse_path_validtion_upon_migration = 0; + // Number of detected peer migrations which either succeed reverse path + // validation or no need to be validated. + size_t num_validated_peer_migration = 0; + // Number of detected peer migrations which triggered reverse path validation + // and failed and fell back to the old path. + size_t num_invalid_peer_migration = 0; + // Number of detected peer migrations which triggered reverse path validation + // which was canceled because the peer migrated again. Such migration is also + // counted as invalid peer migration. + size_t num_peer_migration_while_validating_default_path = 0; }; } // namespace quic
diff --git a/quic/core/quic_connection_test.cc b/quic/core/quic_connection_test.cc index 910af57..60eccb8 100644 --- a/quic/core/quic_connection_test.cc +++ b/quic/core/quic_connection_test.cc
@@ -702,6 +702,8 @@ EXPECT_CALL(*send_algorithm_, GetCongestionControlType()) .Times(AnyNumber()); EXPECT_CALL(*send_algorithm_, OnApplicationLimited(_)).Times(AnyNumber()); + EXPECT_CALL(*send_algorithm_, GetCongestionControlType()) + .Times(AnyNumber()); EXPECT_CALL(visitor_, WillingAndAbleToWrite()).Times(AnyNumber()); EXPECT_CALL(visitor_, OnPacketDecrypted(_)).Times(AnyNumber()); EXPECT_CALL(visitor_, OnCanWrite()) @@ -1622,7 +1624,7 @@ EXPECT_EQ(kNewPeerAddress, connection_.effective_peer_address()); } -TEST_P(QuicConnectionTest, PeerAddressChangeAtServer) { +TEST_P(QuicConnectionTest, PeerPortChangeAtServer) { set_perspective(Perspective::IS_SERVER); QuicPacketCreatorPeer::SetSendVersionInPacket(creator_, false); EXPECT_EQ(Perspective::IS_SERVER, connection_.perspective()); @@ -1630,6 +1632,9 @@ // Prevent packets from being coalesced. EXPECT_CALL(visitor_, GetHandshakeState()) .WillRepeatedly(Return(HANDSHAKE_CONFIRMED)); + if (version().SupportsAntiAmplificationLimit()) { + QuicConnectionPeer::SetAddressValidated(&connection_); + } // Clear direct_peer_address. QuicConnectionPeer::SetDirectPeerAddress(&connection_, QuicSocketAddress()); @@ -1681,6 +1686,149 @@ EXPECT_EQ(2 * default_init_rtt, rtt_stats->initial_rtt()); EXPECT_EQ(1u, manager_->GetConsecutiveRtoCount()); EXPECT_EQ(2u, manager_->GetConsecutiveTlpCount()); + EXPECT_EQ(manager_->GetSendAlgorithm(), send_algorithm_); + if (connection_.validate_client_address()) { + EXPECT_EQ(NO_CHANGE, connection_.active_effective_peer_migration_type()); + EXPECT_EQ(1u, connection_.GetStats().num_validated_peer_migration); + } +} + +TEST_P(QuicConnectionTest, PeerIpAddressChangeAtServer) { + if (!connection_.validate_client_address()) { + return; + } + set_perspective(Perspective::IS_SERVER); + QuicPacketCreatorPeer::SetSendVersionInPacket(creator_, false); + EXPECT_EQ(Perspective::IS_SERVER, connection_.perspective()); + connection_.SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE); + // Prevent packets from being coalesced. + EXPECT_CALL(visitor_, GetHandshakeState()) + .WillRepeatedly(Return(HANDSHAKE_CONFIRMED)); + QuicConnectionPeer::SetAddressValidated(&connection_); + + // Enable 5 RTO + QuicConfig config; + QuicTagVector connection_options; + connection_options.push_back(k5RTO); + config.SetInitialReceivedConnectionOptions(connection_options); + QuicConfigPeer::SetNegotiated(&config, true); + QuicConfigPeer::SetReceivedOriginalConnectionId(&config, + connection_.connection_id()); + QuicConfigPeer::SetReceivedInitialSourceConnectionId(&config, + QuicConnectionId()); + EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _)); + connection_.SetFromConfig(config); + + // Clear direct_peer_address. + QuicConnectionPeer::SetDirectPeerAddress(&connection_, QuicSocketAddress()); + // Clear effective_peer_address, it is the same as direct_peer_address for + // this test. + QuicConnectionPeer::SetEffectivePeerAddress(&connection_, + QuicSocketAddress()); + EXPECT_FALSE(connection_.effective_peer_address().IsInitialized()); + + const QuicSocketAddress kNewPeerAddress = + QuicSocketAddress(QuicIpAddress::Loopback4(), /*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, + ENCRYPTION_FORWARD_SECURE); + EXPECT_EQ(kPeerAddress, connection_.peer_address()); + EXPECT_EQ(kPeerAddress, connection_.effective_peer_address()); + + // Send some data to make connection has packets in flight. + connection_.SendStreamData3(); + EXPECT_EQ(1u, writer_->packets_write_attempts()); + EXPECT_TRUE(connection_.BlackholeDetectionInProgress()); + + // Process another packet with a different peer address on server side will + // start connection migration. + EXPECT_CALL(visitor_, OnConnectionMigration(IPV6_TO_IPV4_CHANGE)).Times(1); + // IETF QUIC send algorithm should be changed to a different object, so no + // OnPacketSent() called on the old send algorithm. + EXPECT_CALL(*send_algorithm_, + OnPacketSent(_, _, _, _, NO_RETRANSMITTABLE_DATA)) + .Times(0); + // Do not propagate OnCanWrite() to session notifier. + EXPECT_CALL(visitor_, OnCanWrite()).Times(AtLeast(1u)); + + QuicFrames frames2; + frames2.push_back(QuicFrame(frame2_)); + ProcessFramesPacketWithAddresses(frames2, kSelfAddress, kNewPeerAddress, + ENCRYPTION_FORWARD_SECURE); + EXPECT_EQ(kNewPeerAddress, connection_.peer_address()); + EXPECT_EQ(kNewPeerAddress, connection_.effective_peer_address()); + EXPECT_EQ(IPV6_TO_IPV4_CHANGE, + connection_.active_effective_peer_migration_type()); + EXPECT_FALSE(connection_.BlackholeDetectionInProgress()); + + EXPECT_EQ(2u, writer_->packets_write_attempts()); + EXPECT_FALSE(writer_->path_challenge_frames().empty()); + QuicPathFrameBuffer payload = + writer_->path_challenge_frames().front().data_buffer; + EXPECT_NE(connection_.sent_packet_manager().GetSendAlgorithm(), + send_algorithm_); + // Switch to use the mock send algorithm. + send_algorithm_ = new StrictMock<MockSendAlgorithm>(); + EXPECT_CALL(*send_algorithm_, CanSend(_)).WillRepeatedly(Return(true)); + EXPECT_CALL(*send_algorithm_, GetCongestionWindow()) + .WillRepeatedly(Return(kDefaultTCPMSS)); + EXPECT_CALL(*send_algorithm_, OnApplicationLimited(_)).Times(AnyNumber()); + EXPECT_CALL(*send_algorithm_, BandwidthEstimate()) + .Times(AnyNumber()) + .WillRepeatedly(Return(QuicBandwidth::Zero())); + EXPECT_CALL(*send_algorithm_, InSlowStart()).Times(AnyNumber()); + EXPECT_CALL(*send_algorithm_, InRecovery()).Times(AnyNumber()); + EXPECT_CALL(*send_algorithm_, PopulateConnectionStats(_)).Times(AnyNumber()); + connection_.SetSendAlgorithm(send_algorithm_); + + // PATH_CHALLENGE is expanded upto the max packet size which may exceeds the + // anti-amplification limit. + EXPECT_EQ(kNewPeerAddress, writer_->last_write_peer_address()); + EXPECT_EQ(kNewPeerAddress, connection_.peer_address()); + EXPECT_EQ(kNewPeerAddress, connection_.effective_peer_address()); + EXPECT_EQ(1u, + connection_.GetStats().num_reverse_path_validtion_upon_migration); + + // Verify server is throttled by anti-amplification limit. + connection_.SendCryptoDataWithString("foo", 0); + EXPECT_FALSE(connection_.GetRetransmissionAlarm()->IsSet()); + + // Receiving an ACK to the packet sent after changing peer address doesn't + // finish migration validation. + QuicAckFrame ack_frame = InitAckFrame(2); + EXPECT_CALL(*send_algorithm_, OnCongestionEvent(_, _, _, _, _)); + ProcessFramePacketWithAddresses(QuicFrame(&ack_frame), kSelfAddress, + kNewPeerAddress, ENCRYPTION_FORWARD_SECURE); + EXPECT_EQ(kNewPeerAddress, connection_.peer_address()); + EXPECT_EQ(kNewPeerAddress, connection_.effective_peer_address()); + EXPECT_EQ(IPV6_TO_IPV4_CHANGE, + connection_.active_effective_peer_migration_type()); + + // Receiving PATH_RESPONSE should lift the anti-amplification limit. + QuicFrames frames3; + frames3.push_back(QuicFrame(new QuicPathResponseFrame(99, payload))); + EXPECT_CALL(visitor_, MaybeSendAddressToken()); + EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)) + .Times(testing::AtLeast(1u)); + ProcessFramesPacketWithAddresses(frames3, kSelfAddress, kNewPeerAddress, + ENCRYPTION_FORWARD_SECURE); + EXPECT_EQ(NO_CHANGE, connection_.active_effective_peer_migration_type()); + + // Verify the anti-amplification limit is lifted by sending a packet larger + // than the anti-amplification limit. + connection_.SendCryptoDataWithString(std::string(1200, 'a'), 0); + EXPECT_EQ(1u, connection_.GetStats().num_validated_peer_migration); } TEST_P(QuicConnectionTest, EffectivePeerAddressChangeAtServer) { @@ -1723,6 +1871,11 @@ ENCRYPTION_INITIAL); EXPECT_EQ(kPeerAddress, connection_.peer_address()); EXPECT_EQ(kNewEffectivePeerAddress, connection_.effective_peer_address()); + EXPECT_EQ(kPeerAddress, writer_->last_write_peer_address()); + if (connection_.validate_client_address()) { + EXPECT_EQ(NO_CHANGE, connection_.active_effective_peer_migration_type()); + EXPECT_EQ(1u, connection_.GetStats().num_validated_peer_migration); + } // Process another packet with a different direct peer address and the same // effective peer address on server side will not start connection migration. @@ -1730,15 +1883,19 @@ QuicSocketAddress(QuicIpAddress::Loopback6(), /*port=*/23456); connection_.ReturnEffectivePeerAddressForNextPacket(kNewEffectivePeerAddress); EXPECT_CALL(visitor_, OnConnectionMigration(PORT_CHANGE)).Times(0); - // ack_frame is used to complete the migration started by the last packet, we - // need to make sure a new migration does not start after the previous one is - // completed. - QuicAckFrame ack_frame = InitAckFrame(1); - EXPECT_CALL(*send_algorithm_, OnCongestionEvent(_, _, _, _, _)); - ProcessFramePacketWithAddresses(QuicFrame(&ack_frame), kSelfAddress, - kNewPeerAddress, ENCRYPTION_INITIAL); - EXPECT_EQ(kNewPeerAddress, connection_.peer_address()); - EXPECT_EQ(kNewEffectivePeerAddress, connection_.effective_peer_address()); + + if (!connection_.validate_client_address()) { + // ack_frame is used to complete the migration started by the last packet, + // we need to make sure a new migration does not start after the previous + // one is completed. + QuicAckFrame ack_frame = InitAckFrame(1); + EXPECT_CALL(*send_algorithm_, OnCongestionEvent(_, _, _, _, _)); + ProcessFramePacketWithAddresses(QuicFrame(&ack_frame), kSelfAddress, + kNewPeerAddress, ENCRYPTION_INITIAL); + EXPECT_EQ(kNewPeerAddress, connection_.peer_address()); + EXPECT_EQ(kNewEffectivePeerAddress, connection_.effective_peer_address()); + EXPECT_EQ(NO_CHANGE, connection_.active_effective_peer_migration_type()); + } // Process another packet with different direct peer address and different // effective peer address on server side will start connection migration. @@ -1753,7 +1910,12 @@ kFinalPeerAddress, ENCRYPTION_INITIAL); EXPECT_EQ(kFinalPeerAddress, connection_.peer_address()); EXPECT_EQ(kNewerEffectivePeerAddress, connection_.effective_peer_address()); - EXPECT_EQ(PORT_CHANGE, connection_.active_effective_peer_migration_type()); + if (connection_.validate_client_address()) { + EXPECT_EQ(NO_CHANGE, connection_.active_effective_peer_migration_type()); + EXPECT_EQ(send_algorithm_, + connection_.sent_packet_manager().GetSendAlgorithm()); + EXPECT_EQ(2u, connection_.GetStats().num_validated_peer_migration); + } // While the previous migration is ongoing, process another packet with the // same direct peer address and different effective peer address on server @@ -1763,13 +1925,110 @@ connection_.ReturnEffectivePeerAddressForNextPacket( kNewestEffectivePeerAddress); EXPECT_CALL(visitor_, OnConnectionMigration(IPV6_TO_IPV4_CHANGE)).Times(1); - EXPECT_CALL(*send_algorithm_, OnConnectionMigration()).Times(1); + if (!connection_.validate_client_address()) { + EXPECT_CALL(*send_algorithm_, OnConnectionMigration()).Times(1); + } ProcessFramePacketWithAddresses(MakeCryptoFrame(), kSelfAddress, kFinalPeerAddress, ENCRYPTION_INITIAL); EXPECT_EQ(kFinalPeerAddress, connection_.peer_address()); EXPECT_EQ(kNewestEffectivePeerAddress, connection_.effective_peer_address()); EXPECT_EQ(IPV6_TO_IPV4_CHANGE, connection_.active_effective_peer_migration_type()); + if (connection_.validate_client_address()) { + EXPECT_NE(send_algorithm_, + connection_.sent_packet_manager().GetSendAlgorithm()); + EXPECT_EQ(kFinalPeerAddress, writer_->last_write_peer_address()); + EXPECT_FALSE(writer_->path_challenge_frames().empty()); + EXPECT_EQ(0u, connection_.GetStats() + .num_peer_migration_while_validating_default_path); + EXPECT_TRUE(connection_.HasPendingPathValidation()); + } +} + +TEST_P(QuicConnectionTest, ReversePathValidationFailureAtServer) { + if (!connection_.validate_client_address()) { + return; + } + set_perspective(Perspective::IS_SERVER); + QuicPacketCreatorPeer::SetSendVersionInPacket(creator_, false); + EXPECT_EQ(Perspective::IS_SERVER, connection_.perspective()); + connection_.SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE); + // Prevent packets from being coalesced. + EXPECT_CALL(visitor_, GetHandshakeState()) + .WillRepeatedly(Return(HANDSHAKE_CONFIRMED)); + QuicConnectionPeer::SetAddressValidated(&connection_); + + // Clear direct_peer_address. + QuicConnectionPeer::SetDirectPeerAddress(&connection_, QuicSocketAddress()); + // Clear effective_peer_address, it is the same as direct_peer_address for + // this test. + QuicConnectionPeer::SetEffectivePeerAddress(&connection_, + QuicSocketAddress()); + EXPECT_FALSE(connection_.effective_peer_address().IsInitialized()); + + const QuicSocketAddress kNewPeerAddress = + QuicSocketAddress(QuicIpAddress::Loopback4(), /*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, + ENCRYPTION_FORWARD_SECURE); + 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. + EXPECT_CALL(visitor_, OnConnectionMigration(IPV6_TO_IPV4_CHANGE)).Times(1); + // IETF QUIC send algorithm should be changed to a different object, so no + // OnPacketSent() called on the old send algorithm. + EXPECT_CALL(*send_algorithm_, OnConnectionMigration()).Times(0); + + QuicFrames frames2; + frames2.push_back(QuicFrame(frame2_)); + ProcessFramesPacketWithAddresses(frames2, kSelfAddress, kNewPeerAddress, + ENCRYPTION_FORWARD_SECURE); + EXPECT_EQ(kNewPeerAddress, connection_.peer_address()); + EXPECT_EQ(kNewPeerAddress, connection_.effective_peer_address()); + EXPECT_EQ(IPV6_TO_IPV4_CHANGE, + connection_.active_effective_peer_migration_type()); + EXPECT_EQ(1u, writer_->packets_write_attempts()); + EXPECT_FALSE(writer_->path_challenge_frames().empty()); + EXPECT_NE(connection_.sent_packet_manager().GetSendAlgorithm(), + send_algorithm_); + EXPECT_EQ(kNewPeerAddress, writer_->last_write_peer_address()); + EXPECT_EQ(kNewPeerAddress, connection_.peer_address()); + EXPECT_EQ(kNewPeerAddress, connection_.effective_peer_address()); + + for (size_t i = 0; i < QuicPathValidator::kMaxRetryTimes; ++i) { + clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(3 * kInitialRttMs)); + static_cast<TestAlarmFactory::TestAlarm*>( + QuicPathValidatorPeer::retry_timer( + QuicConnectionPeer::path_validator(&connection_))) + ->Fire(); + } + EXPECT_EQ(IPV6_TO_IPV4_CHANGE, + connection_.active_effective_peer_migration_type()); + + // Advance the time so that the reverse path validation times out. + clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(3 * kInitialRttMs)); + static_cast<TestAlarmFactory::TestAlarm*>( + QuicPathValidatorPeer::retry_timer( + QuicConnectionPeer::path_validator(&connection_))) + ->Fire(); + EXPECT_EQ(NO_CHANGE, connection_.active_effective_peer_migration_type()); + EXPECT_EQ(kPeerAddress, connection_.peer_address()); + EXPECT_EQ(kPeerAddress, connection_.effective_peer_address()); + EXPECT_EQ(connection_.sent_packet_manager().GetSendAlgorithm(), + send_algorithm_); } TEST_P(QuicConnectionTest, ReceivePathProbeWithNoAddressChangeAtServer) { @@ -1917,17 +2176,27 @@ PathProbeTestInit(Perspective::IS_SERVER); EXPECT_CALL(visitor_, OnConnectionMigration(PORT_CHANGE)).Times(0); + QuicPathFrameBuffer payload; if (!GetParam().version.HasIetfQuicFrames()) { EXPECT_CALL(visitor_, OnPacketReceived(_, _, /*is_connectivity_probe=*/true)) .Times(1); } else { EXPECT_CALL(visitor_, OnPacketReceived(_, _, _)).Times(0); + if (connection_.validate_client_address()) { + EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)) + .Times(AtLeast(1u)) + .WillOnce(Invoke([&]() { + EXPECT_EQ(1u, writer_->path_challenge_frames().size()); + EXPECT_EQ(1u, writer_->path_response_frames().size()); + payload = writer_->path_challenge_frames().front().data_buffer; + })); + } } - // Process a padded PING packet from a new peer address on server side + // Process a probing packet from a new peer address on server side // is effectively receiving a connectivity probing. - const QuicSocketAddress kNewPeerAddress = - QuicSocketAddress(QuicIpAddress::Loopback6(), /*port=*/23456); + const QuicSocketAddress kNewPeerAddress(QuicIpAddress::Loopback4(), + /*port=*/23456); std::unique_ptr<SerializedPacket> probing_packet = ConstructProbingPacket(); std::unique_ptr<QuicReceivedPacket> received(ConstructReceivedPacket( @@ -1966,28 +2235,45 @@ EXPECT_EQ(2 * received->length(), QuicConnectionPeer::BytesReceivedOnAlternativePath(&connection_)); - EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)) - .Times(AtLeast(1u)) - .WillOnce(Invoke( - [&]() { EXPECT_EQ(1u, writer_->path_challenge_frames().size()); })); - bool success = false; - connection_.ValidatePath( - std::make_unique<TestQuicPathValidationContext>( - connection_.self_address(), kNewPeerAddress, writer_.get()), - std::make_unique<TestValidationResultDelegate>( - connection_.self_address(), kNewPeerAddress, &success)); - EXPECT_EQ(3 * bytes_sent, - QuicConnectionPeer::BytesSentOnAlternativePath(&connection_)); + if (!connection_.validate_client_address()) { + EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)) + .Times(AtLeast(1u)) + .WillOnce(Invoke([&]() { + EXPECT_EQ(1u, writer_->path_challenge_frames().size()); + payload = writer_->path_challenge_frames().front().data_buffer; + })); + connection_.ValidatePath( + std::make_unique<TestQuicPathValidationContext>( + connection_.self_address(), kNewPeerAddress, writer_.get()), + std::make_unique<TestValidationResultDelegate>( + connection_.self_address(), kNewPeerAddress, &success)); + } + EXPECT_EQ((connection_.validate_client_address() ? 2 : 3) * bytes_sent, + QuicConnectionPeer::BytesSentOnAlternativePath(&connection_)); QuicFrames frames; - frames.push_back(QuicFrame(new QuicPathResponseFrame( - 99, writer_->path_challenge_frames().front().data_buffer))); + frames.push_back(QuicFrame(new QuicPathResponseFrame(99, payload))); ProcessFramesPacketWithAddresses(frames, connection_.self_address(), kNewPeerAddress, ENCRYPTION_FORWARD_SECURE); EXPECT_LT(2 * received->length(), QuicConnectionPeer::BytesReceivedOnAlternativePath(&connection_)); + EXPECT_TRUE(QuicConnectionPeer::IsAlternativePathValidated(&connection_)); + + // Receiving another probing packet from a newer address with a different + // port shouldn't trigger another reverse path validation. + QuicSocketAddress kNewerPeerAddress(QuicIpAddress::Loopback4(), + /*port=*/34567); + probing_packet = ConstructProbingPacket(); + received.reset(ConstructReceivedPacket( + QuicEncryptedPacket(probing_packet->encrypted_buffer, + probing_packet->encrypted_length), + clock_.Now())); + ProcessReceivedPacket(kSelfAddress, kNewerPeerAddress, *received); + EXPECT_FALSE(connection_.HasPendingPathValidation()); + EXPECT_EQ(connection_.validate_client_address(), + QuicConnectionPeer::IsAlternativePathValidated(&connection_)); } // Process another packet with the old peer address on server side will not @@ -2004,6 +2290,9 @@ set_perspective(Perspective::IS_SERVER); QuicPacketCreatorPeer::SetSendVersionInPacket(creator_, false); EXPECT_EQ(Perspective::IS_SERVER, connection_.perspective()); + if (version().SupportsAntiAmplificationLimit()) { + QuicConnectionPeer::SetAddressValidated(&connection_); + } // Clear direct_peer_address. QuicConnectionPeer::SetDirectPeerAddress(&connection_, QuicSocketAddress()); @@ -2070,12 +2359,11 @@ EXPECT_EQ(kPeerAddress, connection_.effective_peer_address()); } - // Process another packet with the old peer address on server side. if (GetParam().version.HasIetfQuicFrames()) { EXPECT_CALL(visitor_, OnConnectionMigration(PORT_CHANGE)).Times(1); - } else { - EXPECT_CALL(visitor_, OnConnectionMigration(PORT_CHANGE)).Times(0); } + // Process another packet with the old peer address on server side. gQUIC + // shouldn't regard this as a peer migration. ProcessFramePacketWithAddresses(MakeCryptoFrame(), kSelfAddress, kPeerAddress, ENCRYPTION_INITIAL); EXPECT_EQ(kPeerAddress, connection_.peer_address()); @@ -11499,12 +11787,22 @@ const QuicSocketAddress kNewPeerAddress(QuicIpAddress::Any4(), 12345); writer_->BlockOnNextWrite(); // 1st time is after writer returns WRITE_STATUS_BLOCKED. 2nd time is in - // ShouldGeneratePacket(); - EXPECT_CALL(visitor_, OnWriteBlocked()).Times(2u); - // This packet isn't sent actually, instead it is buffered in the connection. + // ShouldGeneratePacket(). + EXPECT_CALL(visitor_, OnWriteBlocked()).Times(AtLeast(2)); + QuicPathFrameBuffer path_challenge_payload{0, 1, 2, 3, 4, 5, 6, 7}; EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)) .Times(AtLeast(1u)) .WillOnce(Invoke([&]() { + // This packet isn't sent actually, instead it is buffered in the + // connection. + EXPECT_EQ(1u, writer_->packets_write_attempts()); + if (connection_.validate_client_address()) { + EXPECT_EQ(1u, writer_->path_response_frames().size()); + EXPECT_EQ(0, + memcmp(&path_challenge_payload, + &writer_->path_response_frames().front().data_buffer, + sizeof(path_challenge_payload))); + } EXPECT_EQ(1u, writer_->path_challenge_frames().size()); EXPECT_EQ(1u, writer_->padding_frames().size()); EXPECT_EQ(kNewPeerAddress, writer_->last_write_peer_address()); @@ -11514,11 +11812,22 @@ EXPECT_EQ(0u, writer_->path_challenge_frames().size()); })); bool success = false; - connection_.ValidatePath( - std::make_unique<TestQuicPathValidationContext>( - connection_.self_address(), kNewPeerAddress, writer_.get()), - std::make_unique<TestValidationResultDelegate>( - connection_.self_address(), kNewPeerAddress, &success)); + if (connection_.validate_client_address()) { + // Receiving a PATH_CHALLENGE from the new peer address should trigger + // address validation. + QuicFrames frames; + frames.push_back( + QuicFrame(new QuicPathChallengeFrame(0, path_challenge_payload))); + ProcessFramesPacketWithAddresses(frames, kSelfAddress, kNewPeerAddress, + ENCRYPTION_FORWARD_SECURE); + } else { + // Manually start to validate the new peer address. + connection_.ValidatePath( + std::make_unique<TestQuicPathValidationContext>( + connection_.self_address(), kNewPeerAddress, writer_.get()), + std::make_unique<TestValidationResultDelegate>( + connection_.self_address(), kNewPeerAddress, &success)); + } EXPECT_EQ(1u, writer_->packets_write_attempts()); // Try again with the new socket blocked from the beginning. The 2nd @@ -11735,14 +12044,16 @@ frames.push_back(QuicFrame(frame1_)); QuicPathFrameBuffer path_frame_buffer{0, 1, 2, 3, 4, 5, 6, 7}; frames.push_back(QuicFrame(new QuicPathChallengeFrame(0, path_frame_buffer))); - const QuicSocketAddress kNewPeerAddress(QuicIpAddress::Loopback6(), + const QuicSocketAddress kNewPeerAddress(QuicIpAddress::Loopback4(), /*port=*/23456); - EXPECT_CALL(visitor_, OnConnectionMigration(PORT_CHANGE)); + EXPECT_CALL(visitor_, OnConnectionMigration(IPV6_TO_IPV4_CHANGE)); + EXPECT_CALL(*send_algorithm_, OnConnectionMigration()) + .Times(connection_.validate_client_address() ? 0u : 1u); EXPECT_CALL(visitor_, OnStreamFrame(_)) .WillOnce(Invoke([=](const QuicStreamFrame& frame) { // Send some data on the stream. The STREAM_FRAME should be built into - // one packet together with the latter PATH_RESPONSE. + // one packet together with the latter PATH_RESPONSE and PATH_CHALLENGE. std::string data{"response body"}; struct iovec iov; MakeIOVector(data, &iov); @@ -11751,7 +12062,8 @@ return notifier_.WriteOrBufferData(frame.stream_id, data.length(), NO_FIN); })); - EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)); + EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)) + .Times(connection_.validate_client_address() ? 0u : 1u); ProcessFramesPacketWithAddresses(frames, kSelfAddress, kNewPeerAddress, ENCRYPTION_FORWARD_SECURE); @@ -11759,13 +12071,20 @@ // PATH_RESPONSE_FRAME. EXPECT_EQ(1u, writer_->stream_frames().size()); EXPECT_EQ(1u, writer_->path_response_frames().size()); + EXPECT_EQ(connection_.validate_client_address() ? 1u : 0u, + writer_->path_challenge_frames().size()); // The final check is to ensure that the random data in the response // matches the random data from the challenge. EXPECT_EQ(0, memcmp(path_frame_buffer.data(), &(writer_->path_response_frames().front().data_buffer), sizeof(path_frame_buffer))); + EXPECT_EQ(connection_.validate_client_address() ? 1u : 0u, + writer_->path_challenge_frames().size()); EXPECT_EQ(1u, writer_->padding_frames().size()); EXPECT_EQ(kNewPeerAddress, writer_->last_write_peer_address()); + if (connection_.validate_client_address()) { + EXPECT_TRUE(connection_.HasPendingPathValidation()); + } } TEST_P(QuicConnectionTest, ReceiveStreamFrameFollowingPathChallenge) { @@ -11780,10 +12099,12 @@ frames.push_back(QuicFrame(new QuicPathChallengeFrame(0, path_frame_buffer))); // PATH_RESPONSE should be flushed out before the rest packet is parsed. frames.push_back(QuicFrame(frame1_)); - const QuicSocketAddress kNewPeerAddress(QuicIpAddress::Loopback6(), + const QuicSocketAddress kNewPeerAddress(QuicIpAddress::Loopback4(), /*port=*/23456); + QuicByteCount received_packet_size; EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)) - .WillOnce(Invoke([=]() { + .Times(AtLeast(1u)) + .WillOnce(Invoke([=, &received_packet_size]() { // Verify that this packet contains a PATH_RESPONSE_FRAME. EXPECT_EQ(0u, writer_->stream_frames().size()); EXPECT_EQ(1u, writer_->path_response_frames().size()); @@ -11793,19 +12114,20 @@ memcmp(path_frame_buffer.data(), &(writer_->path_response_frames().front().data_buffer), sizeof(path_frame_buffer))); + EXPECT_EQ(connection_.validate_client_address() ? 1u : 0u, + writer_->path_challenge_frames().size()); EXPECT_EQ(1u, writer_->padding_frames().size()); EXPECT_EQ(kNewPeerAddress, writer_->last_write_peer_address()); - })) - .WillOnce(Invoke([=]() { - // Verify that this packet contains a STREAM_FRAME. - EXPECT_EQ(1u, writer_->stream_frames().size()); - EXPECT_EQ(kNewPeerAddress, writer_->last_write_peer_address()); + received_packet_size = + QuicConnectionPeer::BytesReceivedOnAlternativePath(&connection_); })); - EXPECT_CALL(visitor_, OnConnectionMigration(PORT_CHANGE)); + EXPECT_CALL(visitor_, OnConnectionMigration(IPV6_TO_IPV4_CHANGE)); + EXPECT_CALL(*send_algorithm_, OnConnectionMigration()) + .Times(connection_.validate_client_address() ? 0u : 1u); EXPECT_CALL(visitor_, OnStreamFrame(_)) .WillOnce(Invoke([=](const QuicStreamFrame& frame) { - // Send some data on the stream. The STREAM_FRAME should be built into - // one packet together with the latter PATH_RESPONSE. + // Send some data on the stream. The STREAM_FRAME should be built into a + // new packet but throttled by anti-amplifciation limit. std::string data{"response body"}; struct iovec iov; MakeIOVector(data, &iov); @@ -11817,6 +12139,15 @@ ProcessFramesPacketWithAddresses(frames, kSelfAddress, kNewPeerAddress, ENCRYPTION_FORWARD_SECURE); + if (!connection_.validate_client_address()) { + return; + } + EXPECT_TRUE(connection_.HasPendingPathValidation()); + EXPECT_EQ(0u, + QuicConnectionPeer::BytesReceivedOnAlternativePath(&connection_)); + EXPECT_EQ( + received_packet_size, + QuicConnectionPeer::BytesReceivedBeforeAddressValidation(&connection_)); } // Tests that a PATH_CHALLENGE is received in between other frames in an out of @@ -12974,7 +13305,7 @@ EXPECT_FALSE(connection_.IsPathDegrading()); } -TEST_P(QuicConnectionTest, MigrateToNewPathDuringValidation) { +TEST_P(QuicConnectionTest, MigrateToNewPathDuringProbing) { if (!VersionHasIetfQuicFrames(connection_.version().transport_version) || !connection_.use_path_validator()) { return; @@ -13334,6 +13665,257 @@ QuicConnectionPeer::SendPing(&connection_); } +TEST_P(QuicConnectionTest, PathChallengeBeforePeerIpAddressChangeAtServer) { + if (!connection_.validate_client_address()) { + return; + } + set_perspective(Perspective::IS_SERVER); + PathProbeTestInit(Perspective::IS_SERVER); + + const QuicSocketAddress kNewPeerAddress = + QuicSocketAddress(QuicIpAddress::Loopback4(), /*port=*/23456); + QuicPathFrameBuffer path_challenge_payload{0, 1, 2, 3, 4, 5, 6, 7}; + QuicFrames frames1; + frames1.push_back( + QuicFrame(new QuicPathChallengeFrame(0, path_challenge_payload))); + QuicPathFrameBuffer payload; + EXPECT_CALL(*send_algorithm_, + OnPacketSent(_, _, _, _, NO_RETRANSMITTABLE_DATA)) + .Times(AtLeast(1)) + .WillOnce(Invoke([&]() { + EXPECT_EQ(kNewPeerAddress, writer_->last_write_peer_address()); + EXPECT_EQ(kPeerAddress, connection_.peer_address()); + EXPECT_EQ(kPeerAddress, connection_.effective_peer_address()); + EXPECT_FALSE(writer_->path_response_frames().empty()); + EXPECT_FALSE(writer_->path_challenge_frames().empty()); + payload = writer_->path_challenge_frames().front().data_buffer; + })); + ProcessFramesPacketWithAddresses(frames1, kSelfAddress, kNewPeerAddress, + ENCRYPTION_FORWARD_SECURE); + EXPECT_EQ(kPeerAddress, connection_.peer_address()); + EXPECT_EQ(kPeerAddress, connection_.effective_peer_address()); + EXPECT_TRUE(connection_.HasPendingPathValidation()); + + // Process another packet with a different peer address on server side will + // start connection migration. + EXPECT_CALL(visitor_, OnConnectionMigration(IPV6_TO_IPV4_CHANGE)).Times(1); + EXPECT_CALL(visitor_, OnStreamFrame(_)).WillOnce(Invoke([=]() { + EXPECT_EQ(kNewPeerAddress, connection_.peer_address()); + })); + // IETF QUIC send algorithm should be changed to a different object, so no + // OnPacketSent() called on the old send algorithm. + EXPECT_CALL(*send_algorithm_, + OnPacketSent(_, _, _, _, NO_RETRANSMITTABLE_DATA)) + .Times(0); + QuicFrames frames2; + frames2.push_back(QuicFrame(frame2_)); + ProcessFramesPacketWithAddresses(frames2, kSelfAddress, kNewPeerAddress, + ENCRYPTION_FORWARD_SECURE); + EXPECT_EQ(kNewPeerAddress, connection_.peer_address()); + EXPECT_EQ(kNewPeerAddress, connection_.effective_peer_address()); + EXPECT_EQ(IPV6_TO_IPV4_CHANGE, + connection_.active_effective_peer_migration_type()); + EXPECT_TRUE(writer_->path_challenge_frames().empty()); + EXPECT_NE(connection_.sent_packet_manager().GetSendAlgorithm(), + send_algorithm_); + // Switch to use the mock send algorithm. + send_algorithm_ = new StrictMock<MockSendAlgorithm>(); + EXPECT_CALL(*send_algorithm_, CanSend(_)).WillRepeatedly(Return(true)); + EXPECT_CALL(*send_algorithm_, GetCongestionWindow()) + .WillRepeatedly(Return(kDefaultTCPMSS)); + EXPECT_CALL(*send_algorithm_, OnApplicationLimited(_)).Times(AnyNumber()); + EXPECT_CALL(*send_algorithm_, BandwidthEstimate()) + .Times(AnyNumber()) + .WillRepeatedly(Return(QuicBandwidth::Zero())); + EXPECT_CALL(*send_algorithm_, InSlowStart()).Times(AnyNumber()); + EXPECT_CALL(*send_algorithm_, InRecovery()).Times(AnyNumber()); + EXPECT_CALL(*send_algorithm_, PopulateConnectionStats(_)).Times(AnyNumber()); + connection_.SetSendAlgorithm(send_algorithm_); + + EXPECT_EQ(kNewPeerAddress, connection_.peer_address()); + EXPECT_EQ(kNewPeerAddress, connection_.effective_peer_address()); + EXPECT_EQ(IPV6_TO_IPV4_CHANGE, + connection_.active_effective_peer_migration_type()); + EXPECT_EQ(1u, connection_.GetStats() + .num_peer_migration_to_proactively_validated_address); + + // The PATH_CHALLENGE and PATH_RESPONSE is expanded upto the max packet size + // which may exceeds the anti-amplification limit. Verify server is throttled + // by anti-amplification limit. + connection_.SendCryptoDataWithString("foo", 0); + EXPECT_FALSE(connection_.GetRetransmissionAlarm()->IsSet()); + + // Receiving PATH_RESPONSE should lift the anti-amplification limit. + QuicFrames frames3; + frames3.push_back(QuicFrame(new QuicPathResponseFrame(99, payload))); + EXPECT_CALL(visitor_, MaybeSendAddressToken()); + EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)) + .Times(testing::AtLeast(1u)); + ProcessFramesPacketWithAddresses(frames3, kSelfAddress, kNewPeerAddress, + ENCRYPTION_FORWARD_SECURE); + EXPECT_EQ(NO_CHANGE, connection_.active_effective_peer_migration_type()); + + // Verify the anti-amplification limit is lifted by sending a packet larger + // than the anti-amplification limit. + EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)).Times(1); + connection_.SendCryptoDataWithString(std::string(1200, 'a'), 0); + EXPECT_EQ(1u, connection_.GetStats().num_validated_peer_migration); +} + +TEST_P(QuicConnectionTest, + PathValidationSucceedsBeforePeerIpAddressChangeAtServer) { + if (!connection_.validate_client_address()) { + return; + } + set_perspective(Perspective::IS_SERVER); + PathProbeTestInit(Perspective::IS_SERVER); + + // Receive probing packet with new peer address. + const QuicSocketAddress kNewPeerAddress(QuicIpAddress::Loopback4(), + /*port=*/23456); + QuicPathFrameBuffer payload; + EXPECT_CALL(*send_algorithm_, + OnPacketSent(_, _, _, _, NO_RETRANSMITTABLE_DATA)) + .WillOnce(Invoke([&]() { + EXPECT_EQ(kNewPeerAddress, writer_->last_write_peer_address()); + EXPECT_EQ(kPeerAddress, connection_.peer_address()); + EXPECT_EQ(kPeerAddress, connection_.effective_peer_address()); + EXPECT_FALSE(writer_->path_response_frames().empty()); + EXPECT_FALSE(writer_->path_challenge_frames().empty()); + payload = writer_->path_challenge_frames().front().data_buffer; + })) + .WillRepeatedly(Invoke([&]() { + // Only start reverse path validation once. + EXPECT_TRUE(writer_->path_challenge_frames().empty()); + })); + QuicPathFrameBuffer path_challenge_payload{0, 1, 2, 3, 4, 5, 6, 7}; + QuicFrames frames1; + frames1.push_back( + QuicFrame(new QuicPathChallengeFrame(0, path_challenge_payload))); + ProcessFramesPacketWithAddresses(frames1, kSelfAddress, kNewPeerAddress, + ENCRYPTION_FORWARD_SECURE); + EXPECT_TRUE(connection_.HasPendingPathValidation()); + + // Receive PATH_RESPONSE should mark the new peer address validated. + QuicFrames frames3; + frames3.push_back(QuicFrame(new QuicPathResponseFrame(99, payload))); + ProcessFramesPacketWithAddresses(frames3, kSelfAddress, kNewPeerAddress, + ENCRYPTION_FORWARD_SECURE); + + // Process another packet with a newer peer address with the same port will + // start connection migration. + EXPECT_CALL(visitor_, OnConnectionMigration(IPV6_TO_IPV4_CHANGE)).Times(1); + // IETF QUIC send algorithm should be changed to a different object, so no + // OnPacketSent() called on the old send algorithm. + EXPECT_CALL(*send_algorithm_, + OnPacketSent(_, _, _, _, NO_RETRANSMITTABLE_DATA)) + .Times(0); + const QuicSocketAddress kNewerPeerAddress(QuicIpAddress::Loopback4(), + /*port=*/34567); + EXPECT_CALL(visitor_, OnStreamFrame(_)).WillOnce(Invoke([=]() { + EXPECT_EQ(kNewerPeerAddress, connection_.peer_address()); + })); + EXPECT_CALL(visitor_, MaybeSendAddressToken()); + QuicFrames frames2; + frames2.push_back(QuicFrame(frame2_)); + ProcessFramesPacketWithAddresses(frames2, kSelfAddress, kNewerPeerAddress, + ENCRYPTION_FORWARD_SECURE); + EXPECT_EQ(kNewerPeerAddress, connection_.peer_address()); + EXPECT_EQ(kNewerPeerAddress, connection_.effective_peer_address()); + // Since the newer address has the same IP as the previously validated probing + // address. The peer migration becomes validated immediately. + EXPECT_EQ(NO_CHANGE, connection_.active_effective_peer_migration_type()); + EXPECT_EQ(kNewerPeerAddress, writer_->last_write_peer_address()); + EXPECT_EQ(1u, connection_.GetStats() + .num_peer_migration_to_proactively_validated_address); + EXPECT_FALSE(connection_.HasPendingPathValidation()); + EXPECT_NE(connection_.sent_packet_manager().GetSendAlgorithm(), + send_algorithm_); + + // Switch to use the mock send algorithm. + send_algorithm_ = new StrictMock<MockSendAlgorithm>(); + EXPECT_CALL(*send_algorithm_, CanSend(_)).WillRepeatedly(Return(true)); + EXPECT_CALL(*send_algorithm_, GetCongestionWindow()) + .WillRepeatedly(Return(kDefaultTCPMSS)); + EXPECT_CALL(*send_algorithm_, OnApplicationLimited(_)).Times(AnyNumber()); + EXPECT_CALL(*send_algorithm_, BandwidthEstimate()) + .Times(AnyNumber()) + .WillRepeatedly(Return(QuicBandwidth::Zero())); + EXPECT_CALL(*send_algorithm_, InSlowStart()).Times(AnyNumber()); + EXPECT_CALL(*send_algorithm_, InRecovery()).Times(AnyNumber()); + EXPECT_CALL(*send_algorithm_, PopulateConnectionStats(_)).Times(AnyNumber()); + connection_.SetSendAlgorithm(send_algorithm_); + + // Verify the server is not throttled by the anti-amplification limit by + // sending a packet larger than the anti-amplification limit. + EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)); + connection_.SendCryptoDataWithString(std::string(1200, 'a'), 0); + EXPECT_EQ(1u, connection_.GetStats().num_validated_peer_migration); +} + +TEST_P(QuicConnectionTest, + ProbedOnAnotherPathAfterPeerIpAddressChangeAtServer) { + if (!connection_.validate_client_address()) { + return; + } + PathProbeTestInit(Perspective::IS_SERVER); + + const QuicSocketAddress kNewPeerAddress(QuicIpAddress::Loopback4(), + /*port=*/23456); + + // Process a packet with a new peer address will start connection migration. + EXPECT_CALL(visitor_, OnConnectionMigration(IPV6_TO_IPV4_CHANGE)).Times(1); + // IETF QUIC send algorithm should be changed to a different object, so no + // OnPacketSent() called on the old send algorithm. + EXPECT_CALL(*send_algorithm_, + OnPacketSent(_, _, _, _, NO_RETRANSMITTABLE_DATA)) + .Times(0); + EXPECT_CALL(visitor_, OnStreamFrame(_)).WillOnce(Invoke([=]() { + EXPECT_EQ(kNewPeerAddress, connection_.peer_address()); + })); + QuicFrames frames2; + frames2.push_back(QuicFrame(frame2_)); + ProcessFramesPacketWithAddresses(frames2, kSelfAddress, kNewPeerAddress, + ENCRYPTION_FORWARD_SECURE); + EXPECT_TRUE(QuicConnectionPeer::IsAlternativePathValidated(&connection_)); + EXPECT_TRUE(connection_.HasPendingPathValidation()); + + // Switch to use the mock send algorithm. + send_algorithm_ = new StrictMock<MockSendAlgorithm>(); + EXPECT_CALL(*send_algorithm_, CanSend(_)).WillRepeatedly(Return(true)); + EXPECT_CALL(*send_algorithm_, GetCongestionWindow()) + .WillRepeatedly(Return(kDefaultTCPMSS)); + EXPECT_CALL(*send_algorithm_, OnApplicationLimited(_)).Times(AnyNumber()); + EXPECT_CALL(*send_algorithm_, BandwidthEstimate()) + .Times(AnyNumber()) + .WillRepeatedly(Return(QuicBandwidth::Zero())); + EXPECT_CALL(*send_algorithm_, InSlowStart()).Times(AnyNumber()); + EXPECT_CALL(*send_algorithm_, InRecovery()).Times(AnyNumber()); + EXPECT_CALL(*send_algorithm_, PopulateConnectionStats(_)).Times(AnyNumber()); + connection_.SetSendAlgorithm(send_algorithm_); + + // Receive probing packet with a newer peer address shouldn't override the + // on-going path validation. + const QuicSocketAddress kNewerPeerAddress(QuicIpAddress::Loopback4(), + /*port=*/34567); + EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)) + .WillOnce(Invoke([&]() { + EXPECT_EQ(kNewerPeerAddress, writer_->last_write_peer_address()); + EXPECT_FALSE(writer_->path_response_frames().empty()); + EXPECT_TRUE(writer_->path_challenge_frames().empty()); + })); + QuicPathFrameBuffer path_challenge_payload{0, 1, 2, 3, 4, 5, 6, 7}; + QuicFrames frames1; + frames1.push_back( + QuicFrame(new QuicPathChallengeFrame(0, path_challenge_payload))); + ProcessFramesPacketWithAddresses(frames1, kSelfAddress, kNewerPeerAddress, + ENCRYPTION_FORWARD_SECURE); + EXPECT_EQ(kNewPeerAddress, connection_.effective_peer_address()); + EXPECT_EQ(kNewPeerAddress, connection_.peer_address()); + EXPECT_TRUE(QuicConnectionPeer::IsAlternativePathValidated(&connection_)); + EXPECT_TRUE(connection_.HasPendingPathValidation()); +} + } // namespace } // namespace test } // namespace quic
diff --git a/quic/core/quic_flags_list.h b/quic/core/quic_flags_list.h index 6c298b8..1fc2236 100644 --- a/quic/core/quic_flags_list.h +++ b/quic/core/quic_flags_list.h
@@ -53,6 +53,7 @@ QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_send_path_response, false) QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_send_timestamps, false) QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_send_tls_crypto_error_code, true) +QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_server_reverse_validate_new_path, false) QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_single_ack_in_packet2, false) QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_start_peer_migration_earlier, true) QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_testonly_default_false, false)
diff --git a/quic/core/quic_path_validator.cc b/quic/core/quic_path_validator.cc index ac4f3ed..f2abef0 100644 --- a/quic/core/quic_path_validator.cc +++ b/quic/core/quic_path_validator.cc
@@ -136,4 +136,10 @@ path_context_->peer_address(), path_context_->WriterToUse())); } +bool QuicPathValidator::IsValidatingPeerAddress( + const QuicSocketAddress& effective_peer_address) { + return path_context_ != nullptr && + path_context_->effective_peer_address() == effective_peer_address; +} + } // namespace quic
diff --git a/quic/core/quic_path_validator.h b/quic/core/quic_path_validator.h index ffeb81f..ccd3a6c 100644 --- a/quic/core/quic_path_validator.h +++ b/quic/core/quic_path_validator.h
@@ -93,6 +93,8 @@ }; // Handles the validation result. + // TODO(danzh) consider to simplify this interface and its life time to + // outlive a validation. class QUIC_EXPORT_PRIVATE ResultDelegate { public: virtual ~ResultDelegate() = default; @@ -130,6 +132,8 @@ // |kMaxRetryTimes| times, fail the current path validation. void OnRetryTimeout(); + bool IsValidatingPeerAddress(const QuicSocketAddress& effective_peer_address); + private: friend class test::QuicPathValidatorPeer;
diff --git a/quic/core/quic_path_validator_test.cc b/quic/core/quic_path_validator_test.cc index f8fe428..e433f0c 100644 --- a/quic/core/quic_path_validator_test.cc +++ b/quic/core/quic_path_validator_test.cc
@@ -93,6 +93,7 @@ std::unique_ptr<QuicPathValidationContext>(context_), std::unique_ptr<MockQuicPathValidationResultDelegate>(result_delegate_)); EXPECT_TRUE(path_validator_.HasPendingPathValidation()); + EXPECT_TRUE(path_validator_.IsValidatingPeerAddress(effective_peer_address_)); EXPECT_CALL(*result_delegate_, OnPathValidationSuccess(_)) .WillOnce(Invoke([=](std::unique_ptr<QuicPathValidationContext> context) { EXPECT_EQ(context.get(), context_);