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_);