Migrate to multi-port path on path degrading
Protected by client side change only.
PiperOrigin-RevId: 516611509
diff --git a/quiche/quic/core/http/end_to_end_test.cc b/quiche/quic/core/http/end_to_end_test.cc
index 0ee3cf0..75872f4 100644
--- a/quiche/quic/core/http/end_to_end_test.cc
+++ b/quiche/quic/core/http/end_to_end_test.cc
@@ -5509,6 +5509,50 @@
stream->Reset(QuicRstStreamErrorCode::QUIC_STREAM_NO_ERROR);
}
+TEST_P(EndToEndTest, ClientMultiPortMigrationOnPathDegrading) {
+ client_config_.SetClientConnectionOptions(QuicTagVector{kMPQC});
+ ASSERT_TRUE(Initialize());
+ if (!GetClientConnection()->connection_migration_use_new_cid()) {
+ return;
+ }
+ client_.reset(EndToEndTest::CreateQuicClient(nullptr));
+ QuicConnection* client_connection = GetClientConnection();
+ QuicSpdyClientStream* stream = client_->GetOrCreateStream();
+ ASSERT_TRUE(stream);
+ // Increase the probing frequency to speed up this test.
+ client_connection->SetMultiPortProbingInterval(
+ QuicTime::Delta::FromMilliseconds(100));
+ SendSynchronousFooRequestAndCheckResponse();
+ EXPECT_TRUE(client_->WaitUntil(1000, [&]() {
+ return 1u == client_connection->GetStats().num_path_response_received;
+ }));
+ // Verify that the alternative path keeps sending probes periodically.
+ EXPECT_TRUE(client_->WaitUntil(1000, [&]() {
+ return 2u == client_connection->GetStats().num_path_response_received;
+ }));
+ server_thread_->Pause();
+ QuicConnection* server_connection = GetServerConnection();
+ // Verify that no migration has happened.
+ if (server_connection != nullptr) {
+ EXPECT_EQ(0u, server_connection->GetStats()
+ .num_peer_migration_to_proactively_validated_address);
+ }
+ server_thread_->Resume();
+
+ auto original_self_addr = client_connection->self_address();
+ // Trigger client side path degrading
+ client_connection->OnPathDegradingDetected();
+ EXPECT_NE(original_self_addr, client_connection->self_address());
+
+ // Send another request to trigger connection id retirement.
+ SendSynchronousFooRequestAndCheckResponse();
+ EXPECT_EQ(1u, client_connection->GetStats().num_retire_connection_id_sent);
+ auto new_alt_path = QuicConnectionPeer::GetAlternativePath(client_connection);
+ EXPECT_NE(client_connection->self_address(), new_alt_path->self_address);
+
+ stream->Reset(QuicRstStreamErrorCode::QUIC_STREAM_NO_ERROR);
+}
+
TEST_P(EndToEndTest, SimpleServerPreferredAddressTest) {
use_preferred_address_ = true;
ASSERT_TRUE(Initialize());
diff --git a/quiche/quic/core/quic_connection.cc b/quiche/quic/core/quic_connection.cc
index dbede25..659821a 100644
--- a/quiche/quic/core/quic_connection.cc
+++ b/quiche/quic/core/quic_connection.cc
@@ -1137,9 +1137,11 @@
void QuicConnection::OnSuccessfulMigration(bool is_port_change) {
QUICHE_DCHECK_EQ(perspective_, Perspective::IS_CLIENT);
- if (IsPathDegrading()) {
+ if (IsPathDegrading() && !multi_port_stats_) {
// If path was previously degrading, and migration is successful after
// probing, restart the path degrading and blackhole detection.
+ // In the case of multi-port, since the alt-path state is inferred from
+ // historical data, we can't trust it until we receive data on the new path.
OnForwardProgressMade();
}
if (IsAlternativePath(default_path_.self_address,
@@ -6164,10 +6166,49 @@
void QuicConnection::OnPathDegradingDetected() {
is_path_degrading_ = true;
+ visitor_->OnPathDegrading();
if (multi_port_stats_) {
multi_port_stats_->num_path_degrading++;
+ MaybeMigrateToMultiPortPath();
}
- visitor_->OnPathDegrading();
+}
+
+void QuicConnection::MaybeMigrateToMultiPortPath() {
+ if (!alternative_path_.validated) {
+ QUIC_CLIENT_HISTOGRAM_ENUM(
+ "QuicConnection.MultiPortPathStatusWhenMigrating",
+ MultiPortStatusOnMigration::kNotValidated,
+ MultiPortStatusOnMigration::kMaxValue,
+ "Status of the multi port path upon migration");
+ return;
+ }
+ std::unique_ptr<QuicPathValidationContext> context;
+ const bool has_pending_validation =
+ path_validator_.HasPendingPathValidation();
+ if (!has_pending_validation) {
+ // The multi-port path should have just finished the recent probe and
+ // waiting for the next one.
+ context = std::move(multi_port_path_context_);
+ multi_port_probing_alarm_->Cancel();
+ QUIC_CLIENT_HISTOGRAM_ENUM(
+ "QuicConnection.MultiPortPathStatusWhenMigrating",
+ MultiPortStatusOnMigration::kWaitingForRefreshValidation,
+ MultiPortStatusOnMigration::kMaxValue,
+ "Status of the multi port path upon migration");
+ } else {
+ // The multi-port path is currently under probing.
+ context = path_validator_.ReleaseContext();
+ QUIC_CLIENT_HISTOGRAM_ENUM(
+ "QuicConnection.MultiPortPathStatusWhenMigrating",
+ MultiPortStatusOnMigration::kPendingRefreshValidation,
+ MultiPortStatusOnMigration::kMaxValue,
+ "Status of the multi port path upon migration");
+ }
+ if (context == nullptr) {
+ QUICHE_BUG(quic_bug_12714_90) << "No multi-port context to migrate to";
+ return;
+ }
+ visitor_->MigrateToMultiPortPath(std::move(context));
}
void QuicConnection::OnBlackholeDetected() {
diff --git a/quiche/quic/core/quic_connection.h b/quiche/quic/core/quic_connection.h
index 8a8717b..bb6de27 100644
--- a/quiche/quic/core/quic_connection.h
+++ b/quiche/quic/core/quic_connection.h
@@ -241,6 +241,10 @@
virtual std::unique_ptr<QuicPathValidationContext>
CreateContextForMultiPortPath() = 0;
+ // Migrate to the multi-port path which is identified by |context|.
+ virtual void MigrateToMultiPortPath(
+ std::unique_ptr<QuicPathValidationContext> context) = 0;
+
// Called when the client receives a preferred address from its peer.
virtual void OnServerPreferredAddressAvailable(
const QuicSocketAddress& server_preferred_address) = 0;
@@ -1409,6 +1413,13 @@
SEND_RANDOM_BYTES // Send random bytes which is an unprocessable packet.
};
+ enum class MultiPortStatusOnMigration {
+ kNotValidated,
+ kPendingRefreshValidation,
+ kWaitingForRefreshValidation,
+ kMaxValue,
+ };
+
struct QUIC_EXPORT_PRIVATE PendingPathChallenge {
QuicPathFrameBuffer received_path_challenge;
QuicSocketAddress peer_address;
@@ -1922,6 +1933,10 @@
// Return true if framer should continue processing the packet.
bool OnPathChallengeFrameInternal(const QuicPathChallengeFrame& frame);
+ // Check the state of the multi-port alternative path and initiate path
+ // migration.
+ void MaybeMigrateToMultiPortPath();
+
std::unique_ptr<QuicSelfIssuedConnectionIdManager>
MakeSelfIssuedConnectionIdManager();
diff --git a/quiche/quic/core/quic_connection_test.cc b/quiche/quic/core/quic_connection_test.cc
index a235314..e0cd6e3 100644
--- a/quiche/quic/core/quic_connection_test.cc
+++ b/quiche/quic/core/quic_connection_test.cc
@@ -13568,6 +13568,234 @@
stats->num_multi_port_probe_failures_when_path_degrading);
}
+// Verify that when multi-port is enabled and path degrading is triggered, if
+// the alt-path is not ready, nothing happens.
+TEST_P(QuicConnectionTest, PathDegradingWhenAltPathIsNotReady) {
+ set_perspective(Perspective::IS_CLIENT);
+ QuicConfig config;
+ config.SetConnectionOptionsToSend(QuicTagVector{kRVCM});
+ config.SetClientConnectionOptions(QuicTagVector{kMPQC});
+ EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _));
+ connection_.SetFromConfig(config);
+ if (!connection_.connection_migration_use_new_cid()) {
+ return;
+ }
+ connection_.CreateConnectionIdManager();
+ connection_.SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE);
+ connection_.OnHandshakeComplete();
+
+ auto self_address = connection_.self_address();
+ const QuicSocketAddress kNewSelfAddress(self_address.host(),
+ self_address.port() + 1);
+ EXPECT_NE(kNewSelfAddress, self_address);
+ TestPacketWriter new_writer(version(), &clock_, Perspective::IS_CLIENT);
+
+ EXPECT_CALL(visitor_, ShouldKeepConnectionAlive())
+ .WillRepeatedly(Return(true));
+
+ QuicNewConnectionIdFrame frame;
+ frame.connection_id = TestConnectionId(1234);
+ ASSERT_NE(frame.connection_id, connection_.connection_id());
+ frame.stateless_reset_token =
+ QuicUtils::GenerateStatelessResetToken(frame.connection_id);
+ frame.retire_prior_to = 0u;
+ frame.sequence_number = 1u;
+ EXPECT_CALL(visitor_, CreateContextForMultiPortPath())
+ .WillRepeatedly(Return(
+ testing::ByMove(std::make_unique<TestQuicPathValidationContext>(
+ kNewSelfAddress, connection_.peer_address(), &new_writer))));
+ EXPECT_TRUE(connection_.OnNewConnectionIdFrame(frame));
+ EXPECT_TRUE(connection_.HasPendingPathValidation());
+ EXPECT_TRUE(QuicConnectionPeer::IsAlternativePath(
+ &connection_, kNewSelfAddress, connection_.peer_address()));
+ auto* alt_path = QuicConnectionPeer::GetAlternativePath(&connection_);
+ EXPECT_FALSE(alt_path->validated);
+
+ // The alt path is not ready, path degrading doesn't do anything.
+ EXPECT_CALL(visitor_, OnPathDegrading());
+ EXPECT_CALL(visitor_, MigrateToMultiPortPath(_)).Times(0);
+ connection_.OnPathDegradingDetected();
+
+ // 30ms RTT.
+ const QuicTime::Delta kTestRTT = QuicTime::Delta::FromMilliseconds(30);
+ // Fake a response delay.
+ clock_.AdvanceTime(kTestRTT);
+
+ // Even if the alt path is validated after path degrading, nothing should
+ // happen.
+ QuicFrames frames;
+ frames.push_back(QuicFrame(QuicPathResponseFrame(
+ 99, new_writer.path_challenge_frames().back().data_buffer)));
+ ProcessFramesPacketWithAddresses(frames, kNewSelfAddress, kPeerAddress,
+ ENCRYPTION_FORWARD_SECURE);
+ // No migration should happen and the alternative path should still be alive.
+ EXPECT_FALSE(connection_.HasPendingPathValidation());
+ EXPECT_TRUE(QuicConnectionPeer::IsAlternativePath(
+ &connection_, kNewSelfAddress, connection_.peer_address()));
+ EXPECT_TRUE(alt_path->validated);
+}
+
+// Verify that when multi-port is enabled and path degrading is triggered, if
+// the alt-path is ready and not probing, it should be migrated.
+TEST_P(QuicConnectionTest, PathDegradingWhenAltPathIsReadyAndNotProbing) {
+ EXPECT_CALL(visitor_, GetHandshakeState())
+ .WillRepeatedly(Return(HANDSHAKE_CONFIRMED));
+ set_perspective(Perspective::IS_CLIENT);
+ QuicConfig config;
+ config.SetConnectionOptionsToSend(QuicTagVector{kRVCM});
+ config.SetClientConnectionOptions(QuicTagVector{kMPQC});
+ EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _));
+ connection_.SetFromConfig(config);
+ if (!connection_.connection_migration_use_new_cid()) {
+ return;
+ }
+ connection_.CreateConnectionIdManager();
+ connection_.SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE);
+ connection_.OnHandshakeComplete();
+
+ auto self_address = connection_.self_address();
+ const QuicSocketAddress kNewSelfAddress(self_address.host(),
+ self_address.port() + 1);
+ EXPECT_NE(kNewSelfAddress, self_address);
+ TestPacketWriter new_writer(version(), &clock_, Perspective::IS_CLIENT);
+
+ EXPECT_CALL(visitor_, ShouldKeepConnectionAlive())
+ .WillRepeatedly(Return(true));
+
+ QuicNewConnectionIdFrame frame;
+ frame.connection_id = TestConnectionId(1234);
+ ASSERT_NE(frame.connection_id, connection_.connection_id());
+ frame.stateless_reset_token =
+ QuicUtils::GenerateStatelessResetToken(frame.connection_id);
+ frame.retire_prior_to = 0u;
+ frame.sequence_number = 1u;
+ EXPECT_CALL(visitor_, CreateContextForMultiPortPath())
+ .WillRepeatedly(Return(
+ testing::ByMove(std::make_unique<TestQuicPathValidationContext>(
+ kNewSelfAddress, connection_.peer_address(), &new_writer))));
+ EXPECT_TRUE(connection_.OnNewConnectionIdFrame(frame));
+ EXPECT_TRUE(connection_.HasPendingPathValidation());
+ EXPECT_TRUE(QuicConnectionPeer::IsAlternativePath(
+ &connection_, kNewSelfAddress, connection_.peer_address()));
+ auto* alt_path = QuicConnectionPeer::GetAlternativePath(&connection_);
+ EXPECT_FALSE(alt_path->validated);
+
+ // 30ms RTT.
+ const QuicTime::Delta kTestRTT = QuicTime::Delta::FromMilliseconds(30);
+ // Fake a response delay.
+ clock_.AdvanceTime(kTestRTT);
+
+ // Even if the alt path is validated after path degrading, nothing should
+ // happen.
+ QuicFrames frames;
+ frames.push_back(QuicFrame(QuicPathResponseFrame(
+ 99, new_writer.path_challenge_frames().back().data_buffer)));
+ ProcessFramesPacketWithAddresses(frames, kNewSelfAddress, kPeerAddress,
+ ENCRYPTION_FORWARD_SECURE);
+ // No migration should happen and the alternative path should still be alive.
+ EXPECT_FALSE(connection_.HasPendingPathValidation());
+ EXPECT_TRUE(QuicConnectionPeer::IsAlternativePath(
+ &connection_, kNewSelfAddress, connection_.peer_address()));
+ EXPECT_TRUE(alt_path->validated);
+
+ // Trigger path degrading and the connection should attempt to migrate.
+ EXPECT_CALL(visitor_, OnPathDegrading());
+ EXPECT_CALL(visitor_, OnForwardProgressMadeAfterPathDegrading()).Times(0);
+ EXPECT_CALL(visitor_, MigrateToMultiPortPath(_))
+ .WillOnce(Invoke([&](std::unique_ptr<QuicPathValidationContext> context) {
+ EXPECT_EQ(context->self_address(), kNewSelfAddress);
+ connection_.MigratePath(context->self_address(),
+ context->peer_address(), context->WriterToUse(),
+ /*owns_writer=*/false);
+ }));
+ connection_.OnPathDegradingDetected();
+}
+
+// Verify that when multi-port is enabled and path degrading is triggered, if
+// the alt-path is probing, the probing should be cancelled and the path should
+// be migrated.
+TEST_P(QuicConnectionTest, PathDegradingWhenAltPathIsReadyAndProbing) {
+ EXPECT_CALL(visitor_, GetHandshakeState())
+ .WillRepeatedly(Return(HANDSHAKE_CONFIRMED));
+ set_perspective(Perspective::IS_CLIENT);
+ QuicConfig config;
+ config.SetConnectionOptionsToSend(QuicTagVector{kRVCM});
+ config.SetClientConnectionOptions(QuicTagVector{kMPQC});
+ EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _));
+ connection_.SetFromConfig(config);
+ if (!connection_.connection_migration_use_new_cid()) {
+ return;
+ }
+ connection_.CreateConnectionIdManager();
+ connection_.SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE);
+ connection_.OnHandshakeComplete();
+
+ auto self_address = connection_.self_address();
+ const QuicSocketAddress kNewSelfAddress(self_address.host(),
+ self_address.port() + 1);
+ EXPECT_NE(kNewSelfAddress, self_address);
+ TestPacketWriter new_writer(version(), &clock_, Perspective::IS_CLIENT);
+
+ EXPECT_CALL(visitor_, ShouldKeepConnectionAlive())
+ .WillRepeatedly(Return(true));
+
+ QuicNewConnectionIdFrame frame;
+ frame.connection_id = TestConnectionId(1234);
+ ASSERT_NE(frame.connection_id, connection_.connection_id());
+ frame.stateless_reset_token =
+ QuicUtils::GenerateStatelessResetToken(frame.connection_id);
+ frame.retire_prior_to = 0u;
+ frame.sequence_number = 1u;
+ EXPECT_CALL(visitor_, CreateContextForMultiPortPath())
+ .WillRepeatedly(Return(
+ testing::ByMove(std::make_unique<TestQuicPathValidationContext>(
+ kNewSelfAddress, connection_.peer_address(), &new_writer))));
+ EXPECT_TRUE(connection_.OnNewConnectionIdFrame(frame));
+ EXPECT_TRUE(connection_.HasPendingPathValidation());
+ EXPECT_TRUE(QuicConnectionPeer::IsAlternativePath(
+ &connection_, kNewSelfAddress, connection_.peer_address()));
+ auto* alt_path = QuicConnectionPeer::GetAlternativePath(&connection_);
+ EXPECT_FALSE(alt_path->validated);
+
+ // 30ms RTT.
+ const QuicTime::Delta kTestRTT = QuicTime::Delta::FromMilliseconds(30);
+ // Fake a response delay.
+ clock_.AdvanceTime(kTestRTT);
+
+ // Even if the alt path is validated after path degrading, nothing should
+ // happen.
+ QuicFrames frames;
+ frames.push_back(QuicFrame(QuicPathResponseFrame(
+ 99, new_writer.path_challenge_frames().back().data_buffer)));
+ ProcessFramesPacketWithAddresses(frames, kNewSelfAddress, kPeerAddress,
+ ENCRYPTION_FORWARD_SECURE);
+ // No migration should happen and the alternative path should still be alive.
+ EXPECT_FALSE(connection_.HasPendingPathValidation());
+ EXPECT_TRUE(QuicConnectionPeer::IsAlternativePath(
+ &connection_, kNewSelfAddress, connection_.peer_address()));
+ EXPECT_TRUE(alt_path->validated);
+
+ random_generator_.ChangeValue();
+ connection_.GetMultiPortProbingAlarm()->Fire();
+ EXPECT_TRUE(connection_.HasPendingPathValidation());
+ EXPECT_FALSE(connection_.GetMultiPortProbingAlarm()->IsSet());
+
+ // Trigger path degrading and the connection should attempt to migrate.
+ EXPECT_CALL(visitor_, OnPathDegrading());
+ EXPECT_CALL(visitor_, OnForwardProgressMadeAfterPathDegrading()).Times(0);
+ EXPECT_CALL(visitor_, MigrateToMultiPortPath(_))
+ .WillOnce(Invoke([&](std::unique_ptr<QuicPathValidationContext> context) {
+ EXPECT_EQ(context->self_address(), kNewSelfAddress);
+ connection_.MigratePath(context->self_address(),
+ context->peer_address(), context->WriterToUse(),
+ /*owns_writer=*/false);
+ }));
+ connection_.OnPathDegradingDetected();
+ EXPECT_FALSE(connection_.HasPendingPathValidation());
+ auto* path_validator = QuicConnectionPeer::path_validator(&connection_);
+ EXPECT_FALSE(QuicPathValidatorPeer::retry_timer(path_validator)->IsSet());
+}
+
TEST_P(QuicConnectionTest, SingleAckInPacket) {
EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_));
EXPECT_CALL(visitor_, OnConnectionClosed(_, _));
diff --git a/quiche/quic/core/quic_path_validator.cc b/quiche/quic/core/quic_path_validator.cc
index da29fac..48c21d2 100644
--- a/quiche/quic/core/quic_path_validator.cc
+++ b/quiche/quic/core/quic_path_validator.cc
@@ -114,6 +114,12 @@
return path_context_.get();
}
+std::unique_ptr<QuicPathValidationContext> QuicPathValidator::ReleaseContext() {
+ auto ret = std::move(path_context_);
+ ResetPathValidation();
+ return ret;
+}
+
const QuicPathFrameBuffer& QuicPathValidator::GeneratePathChallengePayload() {
probing_data_.emplace_back(clock_->Now());
random_->RandBytes(probing_data_.back().frame_buffer.data(),
diff --git a/quiche/quic/core/quic_path_validator.h b/quiche/quic/core/quic_path_validator.h
index fe975b0..1079f21 100644
--- a/quiche/quic/core/quic_path_validator.h
+++ b/quiche/quic/core/quic_path_validator.h
@@ -5,6 +5,7 @@
#ifndef QUICHE_QUIC_CORE_QUIC_PATH_VALIDATOR_H_
#define QUICHE_QUIC_CORE_QUIC_PATH_VALIDATOR_H_
+#include <memory>
#include <ostream>
#include "absl/container/inlined_vector.h"
@@ -143,6 +144,10 @@
QuicPathValidationContext* GetContext() const;
+ // Pass the ownership of path_validation context to the caller and reset the
+ // validator.
+ std::unique_ptr<QuicPathValidationContext> ReleaseContext();
+
PathValidationReason GetPathValidationReason() const { return reason_; }
// Send another PATH_CHALLENGE on the same path. After retrying
diff --git a/quiche/quic/core/quic_session.h b/quiche/quic/core/quic_session.h
index c9931fe..e811ed4 100644
--- a/quiche/quic/core/quic_session.h
+++ b/quiche/quic/core/quic_session.h
@@ -185,6 +185,8 @@
override {
return nullptr;
}
+ void MigrateToMultiPortPath(
+ std::unique_ptr<QuicPathValidationContext> /*context*/) override {}
void OnServerPreferredAddressAvailable(
const QuicSocketAddress& /*server_preferred_address*/) override;
diff --git a/quiche/quic/test_tools/quic_test_utils.h b/quiche/quic/test_tools/quic_test_utils.h
index 2eac913..f33fe49 100644
--- a/quiche/quic/test_tools/quic_test_utils.h
+++ b/quiche/quic/test_tools/quic_test_utils.h
@@ -505,6 +505,8 @@
MOCK_METHOD(bool, MaybeSendAddressToken, (), (override));
MOCK_METHOD(std::unique_ptr<QuicPathValidationContext>,
CreateContextForMultiPortPath, (), (override));
+ MOCK_METHOD(void, MigrateToMultiPortPath,
+ (std::unique_ptr<QuicPathValidationContext>), (override));
MOCK_METHOD(void, OnServerPreferredAddressAvailable,
(const QuicSocketAddress&), (override));
void OnBandwidthUpdateTimeout() override {}
diff --git a/quiche/quic/test_tools/simulator/quic_endpoint.h b/quiche/quic/test_tools/simulator/quic_endpoint.h
index 1bb9ce0..6be7bbb 100644
--- a/quiche/quic/test_tools/simulator/quic_endpoint.h
+++ b/quiche/quic/test_tools/simulator/quic_endpoint.h
@@ -109,6 +109,8 @@
override {
return nullptr;
}
+ void MigrateToMultiPortPath(
+ std::unique_ptr<QuicPathValidationContext> /*context*/) override {}
void OnServerPreferredAddressAvailable(
const QuicSocketAddress& /*server_preferred_address*/) override {}
diff --git a/quiche/quic/tools/quic_simple_client_session.cc b/quiche/quic/tools/quic_simple_client_session.cc
index 26e368b..23601ad 100644
--- a/quiche/quic/tools/quic_simple_client_session.cc
+++ b/quiche/quic/tools/quic_simple_client_session.cc
@@ -70,4 +70,13 @@
network_helper_->GetLatestClientAddress(), peer_address());
}
+void QuicSimpleClientSession::MigrateToMultiPortPath(
+ std::unique_ptr<QuicPathValidationContext> context) {
+ auto* path_migration_context =
+ static_cast<PathMigrationContext*>(context.get());
+ MigratePath(path_migration_context->self_address(),
+ path_migration_context->peer_address(),
+ path_migration_context->ReleaseWriter(), /*owns_writer=*/true);
+}
+
} // namespace quic
diff --git a/quiche/quic/tools/quic_simple_client_session.h b/quiche/quic/tools/quic_simple_client_session.h
index 1beb062..1770c9f 100644
--- a/quiche/quic/tools/quic_simple_client_session.h
+++ b/quiche/quic/tools/quic_simple_client_session.h
@@ -37,6 +37,8 @@
HttpDatagramSupport LocalHttpDatagramSupport() override;
std::unique_ptr<QuicPathValidationContext> CreateContextForMultiPortPath()
override;
+ void MigrateToMultiPortPath(
+ std::unique_ptr<QuicPathValidationContext> context) override;
bool drop_response_body() const { return drop_response_body_; }
private: