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;