Optimize client handling of server preferred address:
1) Start validating server preferred address on handshake complete (rather than confirmed)
2) While validation is pending, duplicate coalesced packets (up to 5) on both paths.
This is behind client_connection_option SPA2.
PiperOrigin-RevId: 498291601
diff --git a/quiche/quic/core/crypto/crypto_protocol.h b/quiche/quic/core/crypto/crypto_protocol.h
index 08dcc42..31937bc 100644
--- a/quiche/quic/core/crypto/crypto_protocol.h
+++ b/quiche/quic/core/crypto/crypto_protocol.h
@@ -285,6 +285,10 @@
// upon client address change.
const QuicTag kSPAD = TAG('S', 'P', 'A', 'D'); // Use server preferred address
+const QuicTag kSPA2 = TAG('S', 'P', 'A', '2'); // Start validating server
+ // preferred address once it is
+ // received. Send all coalesced
+ // packets to both addresses.
// Optional support of truncated Connection IDs. If sent by a peer, the value
// is the minimum number of bytes allowed for the connection ID sent to the
diff --git a/quiche/quic/core/http/end_to_end_test.cc b/quiche/quic/core/http/end_to_end_test.cc
index 091618b..f3b7f03 100644
--- a/quiche/quic/core/http/end_to_end_test.cc
+++ b/quiche/quic/core/http/end_to_end_test.cc
@@ -5511,6 +5511,45 @@
EXPECT_TRUE(client_stats.failed_to_validate_server_preferred_address);
}
+TEST_P(EndToEndTest, OptimizedServerPreferredAddress) {
+ ASSERT_TRUE(Initialize());
+ if (!GetClientConnection()->connection_migration_use_new_cid()) {
+ return;
+ }
+ client_->Disconnect();
+ StopServer();
+ // server_address_ now contains the random listening port.
+ const QuicSocketAddress kServerPreferredAddress =
+ QuicSocketAddress(TestLoopback(2), server_address_.port());
+ ASSERT_NE(server_address_, kServerPreferredAddress);
+ // Send server preferred address and let server listen on Any.
+ if (kServerPreferredAddress.host().IsIPv4()) {
+ server_listening_address_ =
+ QuicSocketAddress(QuicIpAddress::Any4(), server_address_.port());
+ server_config_.SetIPv4AlternateServerAddressToSend(kServerPreferredAddress);
+ } else {
+ server_listening_address_ =
+ QuicSocketAddress(QuicIpAddress::Any6(), server_address_.port());
+ server_config_.SetIPv6AlternateServerAddressToSend(kServerPreferredAddress);
+ }
+ // Server restarts.
+ server_writer_ = new PacketDroppingTestWriter();
+ StartServer();
+
+ client_config_.SetConnectionOptionsToSend(QuicTagVector{kRVCM});
+ client_config_.SetClientConnectionOptions(QuicTagVector{kSPAD, kSPA2});
+ client_.reset(CreateQuicClient(nullptr));
+ EXPECT_TRUE(client_->client()->WaitForHandshakeConfirmed());
+ while (client_->client()->HasPendingPathValidation()) {
+ client_->client()->WaitForEvents();
+ }
+ // TODO(b/262386897): Currently, server drops packets received on preferred
+ // address because self address change is disallowed.
+ const auto client_stats = GetClientConnection()->GetStats();
+ EXPECT_FALSE(client_stats.server_preferred_address_validated);
+ EXPECT_TRUE(client_stats.failed_to_validate_server_preferred_address);
+}
+
TEST_P(EndToEndPacketReorderingTest, ReorderedPathChallenge) {
ASSERT_TRUE(Initialize());
if (!version_.HasIetfQuicFrames()) {
diff --git a/quiche/quic/core/quic_connection.cc b/quiche/quic/core/quic_connection.cc
index 2f5f56f..a929e3a 100644
--- a/quiche/quic/core/quic_connection.cc
+++ b/quiche/quic/core/quic_connection.cc
@@ -706,10 +706,15 @@
config.HasReceivedIPv6AlternateServerAddress()) {
server_preferred_address_ = config.ReceivedIPv6AlternateServerAddress();
}
- QUIC_DLOG_IF(INFO, server_preferred_address_.IsInitialized())
- << ENDPOINT
- << "Received server preferred address: " << server_preferred_address_;
AddKnownServerAddress(server_preferred_address_);
+ if (server_preferred_address_.IsInitialized()) {
+ QUICHE_DLOG(INFO) << ENDPOINT << "Received server preferred address: "
+ << server_preferred_address_;
+ if (config.HasClientRequestedIndependentOption(kSPA2, perspective_)) {
+ accelerated_server_preferred_address_ = true;
+ ValidateServerPreferredAddress();
+ }
+ }
}
if (config.HasReceivedMaxPacketSize()) {
peer_max_packet_size_ = config.ReceivedMaxPacketSize();
@@ -3997,20 +4002,10 @@
// Re-arm ack alarm.
ack_alarm_->Update(uber_received_packet_manager_.GetEarliestAckTimeout(),
kAlarmGranularity);
- if (server_preferred_address_.IsInitialized()) {
+ if (!accelerated_server_preferred_address_ &&
+ server_preferred_address_.IsInitialized()) {
QUICHE_DCHECK_EQ(Perspective::IS_CLIENT, perspective_);
- // Validate received server preferred address.
- auto context =
- visitor_->CreatePathValidationContextForServerPreferredAddress(
- server_preferred_address_);
- if (context != nullptr) {
- QUICHE_DLOG(INFO) << ENDPOINT
- << "Start validating server preferred address: "
- << server_preferred_address_;
- auto result_delegate =
- std::make_unique<ServerPreferredAddressResultDelegate>(this);
- ValidatePath(std::move(context), std::move(result_delegate));
- }
+ ValidateServerPreferredAddress();
}
}
@@ -5924,6 +5919,16 @@
}
}
}
+ if (accelerated_server_preferred_address_ &&
+ stats_.num_duplicated_packets_sent_to_server_preferred_address <
+ kMaxDuplicatedPacketsSentToServerPreferredAddress) {
+ // Send coalesced packets to both addresses while the server preferred
+ // address validation is pending.
+ QUICHE_DCHECK(server_preferred_address_.IsInitialized());
+ path_validator_.MaybeWritePacketToAddress(buffer, length,
+ server_preferred_address_);
+ ++stats_.num_duplicated_packets_sent_to_server_preferred_address;
+ }
// Account for added padding.
if (length > coalesced_packet_.length()) {
if (IsDefaultPath(coalesced_packet_.self_address(),
@@ -6423,6 +6428,21 @@
->MaybeIssueNewConnectionIdForPreferredAddress();
}
+void QuicConnection::ValidateServerPreferredAddress() {
+ QUICHE_DCHECK(server_preferred_address_.IsInitialized());
+ // Validate received server preferred address.
+ auto context = visitor_->CreatePathValidationContextForServerPreferredAddress(
+ server_preferred_address_);
+ if (context == nullptr) {
+ return;
+ }
+ QUICHE_DLOG(INFO) << ENDPOINT << "Start validating server preferred address: "
+ << server_preferred_address_;
+ auto result_delegate =
+ std::make_unique<ServerPreferredAddressResultDelegate>(this);
+ ValidatePath(std::move(context), std::move(result_delegate));
+}
+
bool QuicConnection::ShouldDetectBlackhole() const {
if (!connected_ || blackhole_detection_disabled_) {
return false;
diff --git a/quiche/quic/core/quic_connection.h b/quiche/quic/core/quic_connection.h
index cc22c19..ca25805 100644
--- a/quiche/quic/core/quic_connection.h
+++ b/quiche/quic/core/quic_connection.h
@@ -1272,6 +1272,9 @@
absl::optional<QuicNewConnectionIdFrame>
MaybeIssueNewConnectionIdForPreferredAddress();
+ // Kicks off validation of received server preferred address.
+ void ValidateServerPreferredAddress();
+
protected:
// Calls cancel() on all the alarms owned by this connection.
void CancelAllAlarms();
@@ -2282,6 +2285,11 @@
// only.
QuicSocketAddress server_preferred_address_;
+ // If true, kicks off validation of server_preferred_address_ once it is
+ // received. Also, send all coalesced packets on both paths until handshake is
+ // confirmed.
+ bool accelerated_server_preferred_address_ = false;
+
// If true, throttle sending if next created packet will exceed amplification
// limit.
const bool enforce_strict_amplification_factor_ =
diff --git a/quiche/quic/core/quic_connection_stats.cc b/quiche/quic/core/quic_connection_stats.cc
index 0321498..fd74285 100644
--- a/quiche/quic/core/quic_connection_stats.cc
+++ b/quiche/quic/core/quic_connection_stats.cc
@@ -67,6 +67,8 @@
<< s.server_preferred_address_validated;
os << " failed_to_validate_server_preferred_address: "
<< s.failed_to_validate_server_preferred_address;
+ os << " num_duplicated_packets_sent_to_server_preferred_address: "
+ << s.num_duplicated_packets_sent_to_server_preferred_address;
os << " }";
return os;
diff --git a/quiche/quic/core/quic_connection_stats.h b/quiche/quic/core/quic_connection_stats.h
index 94acd93..82b0255 100644
--- a/quiche/quic/core/quic_connection_stats.h
+++ b/quiche/quic/core/quic_connection_stats.h
@@ -220,6 +220,9 @@
bool server_preferred_address_validated = false;
bool failed_to_validate_server_preferred_address = false;
+ // Number of duplicated packets that have been sent to server preferred
+ // address while the validation is pending.
+ size_t num_duplicated_packets_sent_to_server_preferred_address = 0;
struct QUIC_NO_EXPORT TlsServerOperationStats {
bool success = false;
diff --git a/quiche/quic/core/quic_connection_test.cc b/quiche/quic/core/quic_connection_test.cc
index 314f491..874eeb1 100644
--- a/quiche/quic/core/quic_connection_test.cc
+++ b/quiche/quic/core/quic_connection_test.cc
@@ -1463,7 +1463,7 @@
}
// Receive server preferred address.
- void ServerPreferredAddressInit() {
+ void ServerPreferredAddressInit(QuicConfig& config) {
ASSERT_EQ(Perspective::IS_CLIENT, connection_.perspective());
ASSERT_TRUE(version().HasIetfQuicFrames());
ASSERT_TRUE(connection_.self_address().host().IsIPv6());
@@ -1485,10 +1485,9 @@
ProcessFramePacketAtLevel(1, QuicFrame(&frame), ENCRYPTION_INITIAL);
// Discard INITIAL key.
connection_.RemoveEncrypter(ENCRYPTION_INITIAL);
+ connection_.SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE);
- QuicConfig config;
config.SetConnectionOptionsToSend(QuicTagVector{kRVCM});
- config.SetClientConnectionOptions(QuicTagVector{kSPAD});
QuicConfigPeer::SetReceivedStatelessResetToken(&config,
kTestStatelessResetToken);
QuicConfigPeer::SetReceivedAlternateServerAddress(&config,
@@ -15987,7 +15986,9 @@
if (!connection_.version().HasIetfQuicFrames()) {
return;
}
- ServerPreferredAddressInit();
+ QuicConfig config;
+ config.SetClientConnectionOptions(QuicTagVector{kSPAD});
+ ServerPreferredAddressInit(config);
const QuicSocketAddress kServerPreferredAddress =
QuicConnectionPeer::GetServerPreferredAddress(&connection_);
const QuicSocketAddress kNewSelfAddress =
@@ -16065,7 +16066,9 @@
if (!connection_.version().HasIetfQuicFrames()) {
return;
}
- ServerPreferredAddressInit();
+ QuicConfig config;
+ config.SetClientConnectionOptions(QuicTagVector{kSPAD});
+ ServerPreferredAddressInit(config);
const QuicSocketAddress kServerPreferredAddress =
QuicConnectionPeer::GetServerPreferredAddress(&connection_);
const QuicSocketAddress kNewSelfAddress =
@@ -16129,7 +16132,9 @@
if (!connection_.version().HasIetfQuicFrames()) {
return;
}
- ServerPreferredAddressInit();
+ QuicConfig config;
+ config.SetClientConnectionOptions(QuicTagVector{kSPAD});
+ ServerPreferredAddressInit(config);
const QuicSocketAddress kServerPreferredAddress =
QuicConnectionPeer::GetServerPreferredAddress(&connection_);
const QuicSocketAddress kNewSelfAddress =
@@ -16191,6 +16196,128 @@
connection_.GetStats().failed_to_validate_server_preferred_address);
}
+TEST_P(QuicConnectionTest, OptimizedServerPreferredAddress) {
+ if (!connection_.version().HasIetfQuicFrames()) {
+ return;
+ }
+ QuicIpAddress host;
+ host.FromString("2604:31c0::");
+ const QuicSocketAddress kServerPreferredAddress(host, 443);
+ const QuicSocketAddress kNewSelfAddress =
+ QuicSocketAddress(QuicIpAddress::Loopback6(), /*port=*/23456);
+ TestPacketWriter new_writer(version(), &clock_, Perspective::IS_CLIENT);
+ EXPECT_CALL(visitor_, CreatePathValidationContextForServerPreferredAddress(
+ kServerPreferredAddress))
+ .WillOnce(Return(
+ testing::ByMove(std::make_unique<TestQuicPathValidationContext>(
+ kNewSelfAddress, kServerPreferredAddress, &new_writer))));
+ QuicConfig config;
+ config.SetClientConnectionOptions(QuicTagVector{kSPAD, kSPA2});
+ ServerPreferredAddressInit(config);
+ EXPECT_TRUE(connection_.HasPendingPathValidation());
+ ASSERT_FALSE(new_writer.path_challenge_frames().empty());
+
+ // Send data packet while path validation is pending.
+ connection_.SendStreamDataWithString(3, "foo", 0, NO_FIN);
+ // Verify the packet is sent on both paths.
+ EXPECT_FALSE(writer_->stream_frames().empty());
+ EXPECT_FALSE(new_writer.stream_frames().empty());
+
+ // Verify packet duplication stops on handshake confirmed.
+ EXPECT_CALL(visitor_, GetHandshakeState())
+ .WillRepeatedly(Return(HANDSHAKE_CONFIRMED));
+ connection_.OnHandshakeComplete();
+ SendPing();
+ EXPECT_FALSE(writer_->ping_frames().empty());
+ EXPECT_TRUE(new_writer.ping_frames().empty());
+}
+
+TEST_P(QuicConnectionTest, OptimizedServerPreferredAddress2) {
+ if (!connection_.version().HasIetfQuicFrames()) {
+ return;
+ }
+ QuicIpAddress host;
+ host.FromString("2604:31c0::");
+ const QuicSocketAddress kServerPreferredAddress(host, 443);
+ const QuicSocketAddress kNewSelfAddress =
+ QuicSocketAddress(QuicIpAddress::Loopback6(), /*port=*/23456);
+ TestPacketWriter new_writer(version(), &clock_, Perspective::IS_CLIENT);
+ EXPECT_CALL(visitor_, CreatePathValidationContextForServerPreferredAddress(
+ kServerPreferredAddress))
+ .WillOnce(Return(
+ testing::ByMove(std::make_unique<TestQuicPathValidationContext>(
+ kNewSelfAddress, kServerPreferredAddress, &new_writer))));
+ QuicConfig config;
+ config.SetClientConnectionOptions(QuicTagVector{kSPAD, kSPA2});
+ ServerPreferredAddressInit(config);
+ EXPECT_TRUE(connection_.HasPendingPathValidation());
+ ASSERT_FALSE(new_writer.path_challenge_frames().empty());
+
+ // Send data packet while path validation is pending.
+ connection_.SendStreamDataWithString(3, "foo", 0, NO_FIN);
+ // Verify the packet is sent on both paths.
+ EXPECT_FALSE(writer_->stream_frames().empty());
+ EXPECT_FALSE(new_writer.stream_frames().empty());
+
+ // Simluate path validation times out.
+ for (size_t i = 0; i < QuicPathValidator::kMaxRetryTimes + 1; ++i) {
+ clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(3 * kInitialRttMs));
+ static_cast<TestAlarmFactory::TestAlarm*>(
+ QuicPathValidatorPeer::retry_timer(
+ QuicConnectionPeer::path_validator(&connection_)))
+ ->Fire();
+ }
+ EXPECT_FALSE(connection_.HasPendingPathValidation());
+ // Verify packet duplication stops if there is no pending validation.
+ SendPing();
+ EXPECT_FALSE(writer_->ping_frames().empty());
+ EXPECT_TRUE(new_writer.ping_frames().empty());
+}
+
+TEST_P(QuicConnectionTest, MaxDuplicatedPacketsSentToServerPreferredAddress) {
+ if (!connection_.version().HasIetfQuicFrames()) {
+ return;
+ }
+ QuicIpAddress host;
+ host.FromString("2604:31c0::");
+ const QuicSocketAddress kServerPreferredAddress(host, 443);
+ const QuicSocketAddress kNewSelfAddress =
+ QuicSocketAddress(QuicIpAddress::Loopback6(), /*port=*/23456);
+ TestPacketWriter new_writer(version(), &clock_, Perspective::IS_CLIENT);
+ EXPECT_CALL(visitor_, CreatePathValidationContextForServerPreferredAddress(
+ kServerPreferredAddress))
+ .WillOnce(Return(
+ testing::ByMove(std::make_unique<TestQuicPathValidationContext>(
+ kNewSelfAddress, kServerPreferredAddress, &new_writer))));
+ QuicConfig config;
+ config.SetClientConnectionOptions(QuicTagVector{kSPAD, kSPA2});
+ ServerPreferredAddressInit(config);
+ EXPECT_TRUE(connection_.HasPendingPathValidation());
+ ASSERT_FALSE(new_writer.path_challenge_frames().empty());
+
+ // Send data packet while path validation is pending.
+ size_t write_limit = writer_->packets_write_attempts();
+ size_t new_write_limit = new_writer.packets_write_attempts();
+ for (size_t i = 0; i < kMaxDuplicatedPacketsSentToServerPreferredAddress;
+ ++i) {
+ connection_.SendStreamDataWithString(3, "foo", i * 3, NO_FIN);
+ // Verify the packet is sent on both paths.
+ ASSERT_EQ(write_limit + 1, writer_->packets_write_attempts());
+ ASSERT_EQ(new_write_limit + 1, new_writer.packets_write_attempts());
+ ++write_limit;
+ ++new_write_limit;
+ EXPECT_FALSE(writer_->stream_frames().empty());
+ EXPECT_FALSE(new_writer.stream_frames().empty());
+ }
+
+ // Verify packet duplication stops if duplication limit is hit.
+ SendPing();
+ ASSERT_EQ(write_limit + 1, writer_->packets_write_attempts());
+ ASSERT_EQ(new_write_limit, new_writer.packets_write_attempts());
+ EXPECT_FALSE(writer_->ping_frames().empty());
+ EXPECT_TRUE(new_writer.ping_frames().empty());
+}
+
} // namespace
} // namespace test
} // namespace quic
diff --git a/quiche/quic/core/quic_constants.h b/quiche/quic/core/quic_constants.h
index 4589189..dfd908e 100644
--- a/quiche/quic/core/quic_constants.h
+++ b/quiche/quic/core/quic_constants.h
@@ -326,6 +326,8 @@
inline constexpr size_t kMaxNumMultiPortPaths = 5;
+inline constexpr size_t kMaxDuplicatedPacketsSentToServerPreferredAddress = 5;
+
} // namespace quic
#endif // QUICHE_QUIC_CORE_QUIC_CONSTANTS_H_
diff --git a/quiche/quic/core/quic_path_validator.cc b/quiche/quic/core/quic_path_validator.cc
index 61e3dad..6179fe5 100644
--- a/quiche/quic/core/quic_path_validator.cc
+++ b/quiche/quic/core/quic_path_validator.cc
@@ -149,4 +149,18 @@
path_context_->effective_peer_address() == effective_peer_address;
}
+void QuicPathValidator::MaybeWritePacketToAddress(
+ const char* buffer, size_t buf_len, const QuicSocketAddress& peer_address) {
+ if (!HasPendingPathValidation() ||
+ path_context_->peer_address() != peer_address) {
+ return;
+ }
+ QUIC_DVLOG(1) << "Path validator is sending packet of size " << buf_len
+ << " from " << path_context_->self_address() << " to "
+ << path_context_->peer_address();
+ path_context_->WriterToUse()->WritePacket(
+ buffer, buf_len, path_context_->self_address().host(),
+ path_context_->peer_address(), nullptr);
+}
+
} // namespace quic
diff --git a/quiche/quic/core/quic_path_validator.h b/quiche/quic/core/quic_path_validator.h
index 4389ce5..4b94892 100644
--- a/quiche/quic/core/quic_path_validator.h
+++ b/quiche/quic/core/quic_path_validator.h
@@ -138,6 +138,11 @@
bool IsValidatingPeerAddress(const QuicSocketAddress& effective_peer_address);
+ // Called to send packet to |peer_address| if the path validation to this
+ // address is pending.
+ void MaybeWritePacketToAddress(const char* buffer, size_t buf_len,
+ const QuicSocketAddress& peer_address);
+
private:
friend class test::QuicPathValidatorPeer;