Keep track of the platform-picked default network and migrate the connection back to the default network after a period of time if previously the connection started on non-default network or migrated off the default network. Note that migrating back to the default network requires probing the default network first and then migrate upon probing success. This is part of the chain CLs to fork Chromium's connection migration code extracted in https://chromium-review.googlesource.com/c/chromium/src/+/5980615 which is verified to work for Chromium. PiperOrigin-RevId: 796956667
diff --git a/quiche/quic/core/http/quic_connection_migration_manager.cc b/quiche/quic/core/http/quic_connection_migration_manager.cc index 45a61ba..bc30f55 100644 --- a/quiche/quic/core/http/quic_connection_migration_manager.cc +++ b/quiche/quic/core/http/quic_connection_migration_manager.cc
@@ -38,7 +38,10 @@ namespace { // Time to wait (in seconds) when no networks are available and // migrating sessions need to wait for a new network to connect. -const size_t kWaitTimeForNewNetworkSecs = 10; +constexpr size_t kWaitTimeForNewNetworkSecs = 10; +// Minimum time to wait (in seconds) when retrying to migrate back to the +// default network. +constexpr size_t kMinRetryTimeForDefaultNetworkSecs = 1; class WaitForMigrationDelegate : public QuicAlarm::Delegate { public: @@ -56,6 +59,51 @@ QuicConnectionContext* absl_nullable context_; }; +class MigrateBackToDefaultNetworkDelegate : public QuicAlarm::Delegate { + public: + MigrateBackToDefaultNetworkDelegate( + QuicConnectionMigrationManager* absl_nonnull migration_manager, + QuicConnectionContext* absl_nullable context) + : migration_manager_(migration_manager), context_(context) {} + MigrateBackToDefaultNetworkDelegate( + const MigrateBackToDefaultNetworkDelegate&) = delete; + MigrateBackToDefaultNetworkDelegate& operator=( + const MigrateBackToDefaultNetworkDelegate&) = delete; + QuicConnectionContext* GetConnectionContext() override { return context_; } + void OnAlarm() override { + migration_manager_->MaybeRetryMigrateBackToDefaultNetwork(); + } + + private: + QuicConnectionMigrationManager* absl_nonnull migration_manager_; + QuicConnectionContext* absl_nullable context_; +}; + +// This class handles path validation results associated with connection +// migration which depends on probing. +class ConnectionMigrationValidationResultDelegate + : public quic::QuicPathValidator::ResultDelegate { + public: + // `migration_manager` should out live this instance. + explicit ConnectionMigrationValidationResultDelegate( + QuicConnectionMigrationManager* absl_nonnull migration_manager) + : migration_manager_(migration_manager) {} + void OnPathValidationSuccess( + std::unique_ptr<quic::QuicPathValidationContext> context, + quic::QuicTime start_time) override { + migration_manager_->OnConnectionMigrationProbeSucceeded(std::move(context), + start_time); + } + void OnPathValidationFailure( + std::unique_ptr<quic::QuicPathValidationContext> context) override { + migration_manager_->OnProbeFailed(std::move(context)); + } + + private: + // `migration_manager_` should out live |this|. + QuicConnectionMigrationManager* absl_nonnull migration_manager_; +}; + std::string MigrationCauseToString(MigrationCause cause) { switch (cause) { case MigrationCause::UNKNOWN_CAUSE: @@ -90,20 +138,29 @@ QuicConnectionMigrationManager::QuicConnectionMigrationManager( QuicSpdyClientSessionWithMigration* absl_nonnull session, const quic::QuicClock* absl_nonnull clock, - QuicNetworkHandle /*default_network*/, QuicNetworkHandle current_network, + QuicNetworkHandle default_network, QuicNetworkHandle current_network, QuicPathContextFactory* absl_nonnull path_context_factory, const QuicConnectionMigrationConfig& config) : session_(session), connection_(session->connection()), clock_(clock), + default_network_(default_network), current_network_(current_network), path_context_factory_(path_context_factory), config_(config), + migrate_back_to_default_timer_(connection_->alarm_factory()->CreateAlarm( + new MigrateBackToDefaultNetworkDelegate(this, + connection_->context()))), wait_for_migration_alarm_(connection_->alarm_factory()->CreateAlarm( - new WaitForMigrationDelegate(this, connection_->context()))) {} + new WaitForMigrationDelegate(this, connection_->context()))) { + QUICHE_BUG_IF(gquic_session_created_on_non_default_network, + default_network_ != current_network_ && + !session_->version().HasIetfQuicFrames()); +} QuicConnectionMigrationManager::~QuicConnectionMigrationManager() { wait_for_migration_alarm_->PermanentCancel(); + migrate_back_to_default_timer_->PermanentCancel(); } void QuicConnectionMigrationManager::OnNetworkDisconnected( @@ -129,12 +186,24 @@ connection_->CancelPathValidation(); } + if (disconnected_network == default_network_) { + QUIC_DLOG(INFO) << "Default network: " << default_network_ + << " is disconnected."; + default_network_ = kInvalidNetworkHandle; + } // Ignore the signal if the current active network is not affected. if (current_network() != disconnected_network) { QUIC_DVLOG(1) << "Client's current default network is not affected by the " << "disconnected one."; return; } + if (config_.ignore_disconnect_signal_during_probing && + current_migration_cause_ == MigrationCause::ON_NETWORK_MADE_DEFAULT) { + QUIC_DVLOG(1) + << "Ignoring a network disconnection signal because a " + "connection migration is happening on the default network."; + return; + } current_migration_cause_ = MigrationCause::ON_NETWORK_DISCONNECTED; LogHandshakeStatusOnMigrationSignal(); if (!session_->OneRttKeysAvailable()) { @@ -242,6 +311,30 @@ QuicConnectionMigrationStatus::MIGRATION_STATUS_INTERNAL_ERROR, error); } +QuicConnectionMigrationManager::PathContextCreationResultDelegateForProbing:: + PathContextCreationResultDelegateForProbing( + QuicConnectionMigrationManager* absl_nonnull migration_manager, + StartProbingCallback probing_callback) + : migration_manager_(migration_manager), + probing_callback_(std::move(probing_callback)) {} + +void QuicConnectionMigrationManager:: + PathContextCreationResultDelegateForProbing::OnCreationSucceeded( + std::unique_ptr<QuicPathValidationContext> context) { + migration_manager_->FinishStartProbing(std::move(probing_callback_), + std::move(context)); +} + +void QuicConnectionMigrationManager:: + PathContextCreationResultDelegateForProbing::OnCreationFailed( + QuicNetworkHandle /*network*/, absl::string_view error) { + migration_manager_->OnMigrationFailure( + QuicConnectionMigrationStatus::MIGRATION_STATUS_INTERNAL_ERROR, error); + if (probing_callback_) { + std::move(probing_callback_)(ProbingResult::INTERNAL_ERROR); + } +} + void QuicConnectionMigrationManager::Migrate( QuicNetworkHandle network, QuicSocketAddress peer_address, bool close_session_on_error, MigrationCallback migration_callback) { @@ -290,10 +383,20 @@ } void QuicConnectionMigrationManager::FinishMigrateNetworkImmediately( - QuicNetworkHandle /*network*/, MigrationResult /*result*/) { + QuicNetworkHandle network, MigrationResult result) { pending_migrate_network_immediately_ = false; - // TODO(danzh): check whether the session is on the default network or not. If - // not, set timer to migrate back to default network. + if (result == MigrationResult::FAILURE) { + QUIC_DVLOG(1) << "Failed to migrate network immediately"; + return; + } + if (network == default_network_) { + CancelMigrateBackToDefaultNetworkTimer(); + return; + } + // We are forced to migrate to |network|, probably |default_network_| is + // not working, start to migrate back to default network after 1 secs. + StartMigrateBackToDefaultNetworkTimer( + QuicTimeDelta::FromSeconds(kMinRetryTimeForDefaultNetworkSecs)); } void QuicConnectionMigrationManager::FinishMigrate( @@ -367,6 +470,242 @@ ConnectionCloseBehavior::SILENT_CLOSE); } +void QuicConnectionMigrationManager::StartMigrateBackToDefaultNetworkTimer( + QuicTimeDelta delay) { + if (current_migration_cause_ != MigrationCause::ON_NETWORK_MADE_DEFAULT) { + current_migration_cause_ = + MigrationCause::ON_MIGRATE_BACK_TO_DEFAULT_NETWORK; + } + CancelMigrateBackToDefaultNetworkTimer(); + // Try migrate back to default network after |delay|. + migrate_back_to_default_timer_->Set(clock_->ApproximateNow() + delay); +} + +void QuicConnectionMigrationManager::CancelMigrateBackToDefaultNetworkTimer() { + retry_migrate_back_count_ = 0; + migrate_back_to_default_timer_->Cancel(); +} + +void QuicConnectionMigrationManager::MaybeRetryMigrateBackToDefaultNetwork() { + // Exponentially backoff the retry timeout. + QuicTimeDelta retry_migrate_back_timeout = + QuicTimeDelta::FromSeconds(UINT64_C(1) << retry_migrate_back_count_); + if (retry_migrate_back_timeout > config_.max_time_on_non_default_network) { + // Mark session as going away to accept no more streams. + session_->StartDraining(); + return; + } + TryMigrateBackToDefaultNetwork(retry_migrate_back_timeout); +} + +void QuicConnectionMigrationManager::TryMigrateBackToDefaultNetwork( + QuicTimeDelta next_try_timeout) { + if (default_network_ == kInvalidNetworkHandle) { + QUICHE_DVLOG(1) << "Default network is not connected"; + return; + } + if (debug_visitor_) { + debug_visitor_->OnConnectionMigrationBackToDefaultNetwork( + retry_migrate_back_count_); + } + if (!path_context_factory_) { + FinishTryMigrateBackToDefaultNetwork( + next_try_timeout, ProbingResult::DISABLED_WITH_IDLE_SESSION); + return; + } + if (MaybeCloseIdleSession( + /*has_write_error=*/false, + ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET)) { + FinishTryMigrateBackToDefaultNetwork( + next_try_timeout, ProbingResult::DISABLED_WITH_IDLE_SESSION); + return; + } + if (migration_disabled_) { + QUIC_DVLOG(1) + << "Client disables probing network with connection migration " + << "disabled by config"; + OnMigrationFailure( + QuicConnectionMigrationStatus::MIGRATION_STATUS_DISABLED_BY_CONFIG, + "Migration disabled by config"); + FinishTryMigrateBackToDefaultNetwork(next_try_timeout, + ProbingResult::DISABLED_BY_CONFIG); + return; + } + // Start probe default network immediately, if this observer is probing + // the same network, this will be a no-op. Otherwise, previous probe + // will be cancelled and it starts to probe |default_network_| + // immediately. + StartProbing( + [this, next_try_timeout](ProbingResult rv) { + FinishTryMigrateBackToDefaultNetwork(next_try_timeout, rv); + }, + default_network_, connection_->peer_address()); +} + +void QuicConnectionMigrationManager::FinishTryMigrateBackToDefaultNetwork( + QuicTimeDelta next_try_timeout, ProbingResult result) { + if (result != ProbingResult::PENDING) { + // Session is not allowed to migrate, mark session as going away, cancel + // migrate back to default timer. + session_->StartDraining(); + CancelMigrateBackToDefaultNetworkTimer(); + return; + } + ++retry_migrate_back_count_; + migrate_back_to_default_timer_->Set(clock_->ApproximateNow() + + next_try_timeout); +} + +void QuicConnectionMigrationManager::StartProbing( + StartProbingCallback probing_callback, QuicNetworkHandle network, + const quic::QuicSocketAddress& peer_address) { + // Check if probing manager is probing the same path. + QuicPathValidationContext* existing_context = + connection_->GetPathValidationContext(); + if (existing_context && existing_context->network() == network && + existing_context->peer_address() == peer_address) { + if (probing_callback) { + QUIC_DVLOG(1) << "On-going probing of peer address " << peer_address + << " on network " << network << " hasn't finished."; + std::move(probing_callback)(ProbingResult::DISABLED_BY_CONFIG); + } + return; + } + path_context_factory_->CreatePathValidationContext( + network, peer_address, + std::make_unique<PathContextCreationResultDelegateForProbing>( + this, std::move(probing_callback))); +} + +void QuicConnectionMigrationManager::FinishStartProbing( + StartProbingCallback probing_callback, + std::unique_ptr<QuicPathValidationContext> path_context) { + session_->PrepareForProbingOnPath(*path_context); + connection_->ValidatePath( + std::move(path_context), + std::make_unique<ConnectionMigrationValidationResultDelegate>(this), + quic::PathValidationReason::kConnectionMigration); + if (probing_callback) { + std::move(probing_callback)(ProbingResult::PENDING); + } +} + +void QuicConnectionMigrationManager::OnProbeFailed( + std::unique_ptr<QuicPathValidationContext> path_context) { + connection_->OnPathValidationFailureAtClient( + /*is_multi_port=*/false, *path_context); + QuicNetworkHandle network = path_context->network(); + if (debug_visitor_) { + debug_visitor_->OnProbeResult(network, connection_->peer_address(), + /*success=*/false); + } + LogProbeResultToHistogram(current_migration_cause_, false); + QuicPathValidationContext* context = connection_->GetPathValidationContext(); + if (!context) { + return; + } + if (context->network() == network && + context->peer_address() == connection_->peer_address()) { + connection_->CancelPathValidation(); + } + if (network != kInvalidNetworkHandle) { + // Probing failure can be ignored. + QUICHE_DVLOG(1) << "Connectivity probing failed on <network: " << network + << ", peer_address: " + << connection_->peer_address().ToString() << ">."; + QUICHE_DVLOG_IF( + 1, network == default_network_ && current_network() != default_network_) + << "Client probing failed on the default network, still using " + "non-default network."; + } +} + +void QuicConnectionMigrationManager::OnConnectionMigrationProbeSucceeded( + std::unique_ptr<QuicPathValidationContext> path_context, + quic::QuicTime /*start_time*/) { + QuicNetworkHandle network = path_context->network(); + if (debug_visitor_) { + debug_visitor_->OnProbeResult(network, connection_->peer_address(), + /*success*/ true); + } + LogProbeResultToHistogram(current_migration_cause_, true); + // Close streams that are not migratable to the probed |network|. + session_->ResetNonMigratableStreams(); + if (MaybeCloseIdleSession( + /*has_write_error=*/false, + ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET)) { + return; + } + // Migrate to the probed socket immediately. + if (!session_->MigrateToNewPath(std::move(path_context))) { + if (debug_visitor_) { + debug_visitor_->OnConnectionMigrationFailedAfterProbe(); + } + return; + } + OnMigrationSuccess(); + if (debug_visitor_) { + debug_visitor_->OnConnectionMigrationSucceededAfterProbe(network); + } + current_network_ = network; + if (network == default_network_) { + QUIC_DVLOG(1) << "Client successfully migrated to default network: " + << default_network_; + CancelMigrateBackToDefaultNetworkTimer(); + return; + } + QUIC_DVLOG(1) << "Client successfully got off default network after " + << "successful probing network: " << network << "."; + if (!migrate_back_to_default_timer_->IsSet()) { + current_migration_cause_ = + MigrationCause::ON_MIGRATE_BACK_TO_DEFAULT_NETWORK; + // Session gets off the |default_network|, stay on |network| for now but + // try to migrate back to default network after 1 second. + StartMigrateBackToDefaultNetworkTimer( + QuicTimeDelta::FromSeconds(kMinRetryTimeForDefaultNetworkSecs)); + } +} + +void QuicConnectionMigrationManager::OnNetworkMadeDefault( + QuicNetworkHandle new_network) { + if (!session_->version().HasIetfQuicFrames()) { + return; + } + if (debug_visitor_) { + debug_visitor_->OnNetworkMadeDefault(new_network); + } + if (!config_.migrate_session_on_network_change || + session_->IsSessionProxied()) { + return; + } + QUICHE_DCHECK_NE(kInvalidNetworkHandle, new_network); + if (debug_visitor_) { + debug_visitor_->OnConnectionMigrationAfterNewDefaultNetwork(new_network); + } + if (new_network == default_network_) { + return; + } + QUICHE_DVLOG(1) << "Network: " << new_network + << " becomes default, old default: " << default_network_ + << " current_network " << current_network(); + default_network_ = new_network; + current_migration_cause_ = MigrationCause::ON_NETWORK_MADE_DEFAULT; + // Simply cancel the timer to migrate back to the default network if session + // is already on the default network. + if (current_network() == new_network) { + CancelMigrateBackToDefaultNetworkTimer(); + OnMigrationFailure( + QuicConnectionMigrationStatus::MIGRATION_STATUS_ALREADY_MIGRATED, + "Already migrated on the new network"); + return; + } + LogHandshakeStatusOnMigrationSignal(); + // Stay on the current network. Try to migrate back to default network + // without any delay, which will start probing the new default network and + // migrate to the new network immediately on success. + StartMigrateBackToDefaultNetworkTimer(QuicTimeDelta::Zero()); +} + void QuicConnectionMigrationManager::LogMetricsOnNetworkDisconnected() { most_recent_network_disconnected_timestamp_ = clock_->ApproximateNow(); } @@ -421,8 +760,48 @@ void QuicConnectionMigrationManager::OnHandshakeCompleted( const QuicConfig& negotiated_config) { migration_disabled_ = negotiated_config.DisableConnectionMigration(); - // TODO(danzh): attempt to migrate back to the default network after handshake - // has been completed if the session is not created on the default network. + // Attempt to migrate back to the default network after handshake has been + // completed if the session is not created on the default network. + if (config_.migrate_session_on_network_change && + default_network_ != kInvalidNetworkHandle && + current_network() != default_network_) { + QUICHE_DCHECK(session_->version().HasIetfQuicFrames()); + current_migration_cause_ = + MigrationCause::ON_MIGRATE_BACK_TO_DEFAULT_NETWORK; + StartMigrateBackToDefaultNetworkTimer( + QuicTimeDelta::FromSeconds(kMinRetryTimeForDefaultNetworkSecs)); + } +} + +// TODO(fayang): Remove this when necessary data is collected. +void QuicConnectionMigrationManager::LogProbeResultToHistogram( + MigrationCause cause, bool success) { + QUIC_CLIENT_HISTOGRAM_BOOL("Net.QuicSession.PathValidationSuccess", success, + ""); + switch (cause) { + case MigrationCause::UNKNOWN_CAUSE: + QUIC_CLIENT_HISTOGRAM_BOOL( + "Net.QuicSession.PathValidationSuccess.Unknown", success, ""); + return; + case MigrationCause::ON_NETWORK_DISCONNECTED: + QUIC_CLIENT_HISTOGRAM_BOOL( + "Net.QuicSession.PathValidationSuccess.OnNetworkDisconnected", + success, ""); + return; + case MigrationCause::ON_NETWORK_MADE_DEFAULT: + QUIC_CLIENT_HISTOGRAM_BOOL( + "Net.QuicSession.PathValidationSuccess.OnNetworkMadeDefault", success, + ""); + return; + case MigrationCause::ON_MIGRATE_BACK_TO_DEFAULT_NETWORK: + QUIC_CLIENT_HISTOGRAM_BOOL( + "Net.QuicSession.PathValidationSuccess.OnMigrateBackToDefaultNetwork", + success, ""); + return; + default: + // Other causes are not implemented yet. + return; + } } void QuicConnectionMigrationManager::ResetMigrationCauseAndLogResult(
diff --git a/quiche/quic/core/http/quic_connection_migration_manager.h b/quiche/quic/core/http/quic_connection_migration_manager.h index 2bcd4ca..898ab16 100644 --- a/quiche/quic/core/http/quic_connection_migration_manager.h +++ b/quiche/quic/core/http/quic_connection_migration_manager.h
@@ -73,17 +73,31 @@ MIGRATION_STATUS_MAX }; +// Result of a connectivity probing attempt. +enum class ProbingResult { + PENDING, // Probing started, pending result. + DISABLED_WITH_IDLE_SESSION, // Probing disabled with idle session. + DISABLED_BY_CONFIG, // Probing disabled by config. + DISABLED_BY_NON_MIGRABLE_STREAM, // Probing disabled by special stream. + INTERNAL_ERROR, // Probing failed for internal reason. +}; + struct QUICHE_NO_EXPORT QuicConnectionMigrationConfig { // Whether to migrate a session with no in-flight requests to a different // network or port. bool migrate_idle_session = false; // Session can be migrated if its idle time is within this period. QuicTimeDelta idle_migration_period = QuicTimeDelta::FromSeconds(30); + // Maximum time a connection is allowed to stay on a non-default network + // before migrating back to the default network. + QuicTimeDelta max_time_on_non_default_network = + QuicTimeDelta::FromSeconds(128); // Whether to migrate to a different network upon the underlying platform's // network change signals and write error. bool migrate_session_on_network_change = false; // Below are optional experimental features. + bool ignore_disconnect_signal_during_probing = true; bool disable_blackhole_detection_on_immediate_migrate = true; }; @@ -100,15 +114,26 @@ virtual void OnConnectionMigrationAboutToStartAfterEvent( absl::string_view event_name) = 0; virtual void OnConnectionMigrationStarted() = 0; + virtual void OnConnectionMigrationBackToDefaultNetwork( + int num_migration_back_retries) = 0; + virtual void OnProbeResult(QuicNetworkHandle probed_network, + QuicSocketAddress peer_address, bool success) = 0; + virtual void OnConnectionMigrationFailedAfterProbe() = 0; + virtual void OnConnectionMigrationSucceededAfterProbe( + QuicNetworkHandle probed_network) = 0; virtual void OnConnectionMigrationFailed(MigrationCause migration_cause, QuicConnectionId connection_id, absl::string_view details) = 0; + virtual void OnNetworkMadeDefault(QuicNetworkHandle network) = 0; + virtual void OnConnectionMigrationAfterNewDefaultNetwork( + QuicNetworkHandle network) = 0; virtual void OnConnectionMigrationSuccess(MigrationCause migration_cause, QuicConnectionId connection_id) = 0; }; using MigrationCallback = quiche::SingleUseCallback<void(QuicNetworkHandle, MigrationResult)>; +using StartProbingCallback = quiche::SingleUseCallback<void(ProbingResult)>; // This class receives network change signals from the device and events // reported by the connection, like path degrading and write error, to make @@ -128,8 +153,13 @@ // Called when the platform detects the given network to be disconnected. void OnNetworkDisconnected(QuicNetworkHandle disconnected_network); - // Called by the session when the handshake gets completed. |config| is the - // negotiated QUIC configuration. + // Called when the platform chooses the given network as the default network. + // Migrates this session to it if appropriate. + void OnNetworkMadeDefault(QuicNetworkHandle new_network); + + // Called by the session when the handshake gets completed to attempt + // switching to the platform's default network asynchronously if not on it + // yet. |config| is the negotiated QUIC configuration. void OnHandshakeCompleted(const QuicConfig& negotiated_config); void OnMigrationFailure(QuicConnectionMigrationStatus status, @@ -138,6 +168,16 @@ // Called when migration alarm fires. If migration has not occurred // since alarm was set, closes session with error. void OnMigrationTimeout(); + // Called when migrating to default network timer fires. + void MaybeRetryMigrateBackToDefaultNetwork(); + + // Called when probing alternative network for connection migration succeeds. + void OnConnectionMigrationProbeSucceeded( + std::unique_ptr<QuicPathValidationContext> path_context, + quic::QuicTime start_time); + + // Called when any type of probing failed. + void OnProbeFailed(std::unique_ptr<QuicPathValidationContext> path_context); void set_debug_visitor( QuicConnectionMigrationDebugVisitor* absl_nullable visitor) { @@ -149,6 +189,9 @@ // Returns the network interface that is currently used to send packets. QuicNetworkHandle current_network() const { return current_network_; } + // Returns the network interface that is picked as default by the platform. + QuicNetworkHandle default_network() const { return default_network_; } + bool migration_attempted() const { return migration_attempted_; } bool migration_successful() const { return migration_successful_; } @@ -176,12 +219,33 @@ MigrationCallback migration_callback_; }; - // TODO(danzh): Schedules a migration alarm to wait for a new network. + // A callback implementation for creating a path context object used for + // probing. + class PathContextCreationResultDelegateForProbing + : public QuicPathContextFactory::CreationResultDelegate { + public: + // `migration_manager` should out live this instance. + PathContextCreationResultDelegateForProbing( + QuicConnectionMigrationManager* absl_nonnull migration_manager, + StartProbingCallback probing_callback); + + void OnCreationSucceeded( + std::unique_ptr<QuicPathValidationContext> context) override; + + void OnCreationFailed(QuicNetworkHandle network, + absl::string_view error) override; + + private: + QuicConnectionMigrationManager* absl_nonnull migration_manager_; + StartProbingCallback probing_callback_; + }; + + // Schedules a migration alarm to wait for a new network. void OnNoNewNetwork(); // Called when there is only one possible working network: |network|, if any // error is encountered, this session will be closed. - // TODO(danzh): When the migration succeeds: + // When the migration succeeds: // - If no longer on the default network, set timer to migrate back to the // default network; // - If now on the default network, cancel timer to migrate back to default @@ -202,6 +266,21 @@ void FinishMigrate(std::unique_ptr<QuicPathValidationContext> path_context, bool close_session_on_error, MigrationCallback callback); + void StartMigrateBackToDefaultNetworkTimer(QuicTimeDelta delay); + void CancelMigrateBackToDefaultNetworkTimer(); + + void TryMigrateBackToDefaultNetwork(QuicTimeDelta next_try_timeout); + + void FinishTryMigrateBackToDefaultNetwork(QuicTimeDelta next_try_timeout, + ProbingResult result); + + void StartProbing(StartProbingCallback probing_callback, + QuicNetworkHandle network, + const QuicSocketAddress& peer_address); + void FinishStartProbing( + StartProbingCallback probing_callback, + std::unique_ptr<QuicPathValidationContext> path_context); + bool MaybeCloseIdleSession(bool has_write_error, ConnectionCloseBehavior close_behavior); @@ -214,6 +293,10 @@ QuicSpdyClientSessionWithMigration* absl_nonnull session_; QuicConnection* absl_nonnull connection_; const quic::QuicClock* absl_nonnull clock_; // Unowned. + // Stores the latest default network platform marks if migration is enabled. + // Otherwise, stores the network interface that is currently used by the + // connection (same as `current_network_`). + QuicNetworkHandle default_network_; // Stores the network interface that is currently used by the connection. QuicNetworkHandle current_network_; QuicPathContextFactory* absl_nonnull path_context_factory_; @@ -224,6 +307,7 @@ // True when a session migration starts from MigrateNetworkImmediately. bool pending_migrate_network_immediately_ = false; + int retry_migrate_back_count_ = 0; MigrationCause current_migration_cause_ = MigrationCause::UNKNOWN_CAUSE; // True if migration is triggered, and there is no alternate network to // migrate to. @@ -233,6 +317,7 @@ bool migration_attempted_ = false; bool migration_successful_ = false; + std::unique_ptr<QuicAlarm> migrate_back_to_default_timer_; std::unique_ptr<QuicAlarm> wait_for_migration_alarm_; };
diff --git a/quiche/quic/core/http/quic_connection_migration_manager_test.cc b/quiche/quic/core/http/quic_connection_migration_manager_test.cc index 20bfccc..107216c 100644 --- a/quiche/quic/core/http/quic_connection_migration_manager_test.cc +++ b/quiche/quic/core/http/quic_connection_migration_manager_test.cc
@@ -13,6 +13,7 @@ #include "quiche/quic/platform/api/quic_test.h" #include "quiche/quic/test_tools/quic_config_peer.h" #include "quiche/quic/test_tools/quic_connection_peer.h" +#include "quiche/quic/test_tools/quic_path_validator_peer.h" #include "quiche/quic/test_tools/quic_test_utils.h" using testing::_; @@ -31,6 +32,11 @@ QuicConnectionMigrationManager* manager) { return manager->wait_for_migration_alarm_.get(); } + + static QuicAlarm* GetMigrateBackToDefaultTimer( + QuicConnectionMigrationManager* manager) { + return manager->migrate_back_to_default_timer_.get(); + } }; class TestQuicForceBlockablePacketWriter @@ -489,6 +495,7 @@ migration_config_); session_->Initialize(); migration_manager_ = &session_->migration_manager(); + EXPECT_EQ(migration_manager_->default_network(), default_network_); EXPECT_EQ(migration_manager_->current_network(), initial_network_); connection_helper_.GetClock()->AdvanceTime(QuicTimeDelta::FromSeconds(1)); @@ -504,7 +511,8 @@ ASSERT_NE(extra_connection_id, connection_->connection_id()); const StatelessResetToken reset_token = QuicUtils::GenerateStatelessResetToken(extra_connection_id); - if (version().HasIetfQuicFrames() && received_server_preferred_address) { + if (received_server_preferred_address) { + QUICHE_DCHECK(version().HasIetfQuicFrames()); // OnHandshakeMessage() will populate the received values with these. QuicIpAddress ipv4, ipv6; ASSERT_TRUE(ipv4.FromString("127.0.0.2")); @@ -608,9 +616,10 @@ alarm_factory_.FireAlarm(migration_alarm); } -// This test verifies session migrates off the disconnected default network. +// This test verifies session migrates off the disconnected default network and +// migrates back to the default network later with probing. TEST_P(QuicConnectionMigrationManagerTest, - MigratingOffDisconnectedDefaultNetwork) { + MigratingOffDisconnectedDefaultNetworkAndMigrateBack) { migrate_idle_session_ = true; Initialize(); @@ -622,6 +631,7 @@ EXPECT_NE(alternate_self_address.host(), connection_->self_address().host()); path_context_factory_->SetSelfAddressForNetwork(alternate_network, alternate_self_address); + QuicSocketAddress self_address = connection_->self_address(); EXPECT_CALL(*session_, TimeSinceLastStreamClose()) .WillOnce(Return(QuicTimeDelta::Zero())); @@ -632,6 +642,88 @@ EXPECT_EQ(migration_manager_->current_network(), alternate_network); EXPECT_EQ(connection_->self_address(), alternate_self_address); EXPECT_EQ(path_context_factory_->num_creation_attempts(), 1u); + + // Update CIDs. + QuicConnectionPeer::RetirePeerIssuedConnectionIdsNoLongerOnPath(connection_); + QuicAlarm* retire_cid_alarm = + QuicConnectionPeer::GetRetirePeerIssuedConnectionIdAlarm(connection_); + EXPECT_TRUE(retire_cid_alarm->IsSet()); + EXPECT_CALL(*connection_, + SendControlFrame(IsFrame(RETIRE_CONNECTION_ID_FRAME))); + alarm_factory_.FireAlarm(retire_cid_alarm); + // Receive a new CID from peer. + QuicNewConnectionIdFrame frame; + frame.connection_id = test::TestConnectionId(5678); + ASSERT_NE(frame.connection_id, connection_->connection_id()); + frame.stateless_reset_token = + QuicUtils::GenerateStatelessResetToken(frame.connection_id); + frame.retire_prior_to = 1u; + frame.sequence_number = 2u; + connection_->OnNewConnectionIdFrame(frame); + + // An alarm should have been scheduled to try to migrate back to the default + // network in 1s. + QuicAlarm* migrate_back_alarm = + QuicConnectionMigrationManagerPeer::GetMigrateBackToDefaultTimer( + migration_manager_); + EXPECT_TRUE(migrate_back_alarm->IsSet()); + EXPECT_EQ( + migrate_back_alarm->deadline() - connection_helper_.GetClock()->Now(), + QuicTimeDelta::FromSeconds(1)); + EXPECT_EQ(migration_manager_->default_network(), kInvalidNetworkHandle); + + // The default network is still not connected, so migration back should not + // happen. + connection_helper_.GetClock()->AdvanceTime(QuicTimeDelta::FromSeconds(1)); + alarm_factory_.FireAlarm(migrate_back_alarm); + EXPECT_EQ(path_context_factory_->num_creation_attempts(), 1u); + + QuicSocketAddress self_address2(self_address.host(), kTestPort + 1); + path_context_factory_->SetSelfAddressForNetwork(initial_network_, + self_address2); + // The default network is now connected, migration back should be attempted + // again immediately. + migration_manager_->OnNetworkMadeDefault(initial_network_); + EXPECT_TRUE(migrate_back_alarm->IsSet()); + EXPECT_EQ(migrate_back_alarm->deadline(), + connection_helper_.GetClock()->Now()); + // Fire the alarm to migrate back to default network, starting with probing. + EXPECT_CALL(*session_, TimeSinceLastStreamClose()) + .WillOnce(Return(QuicTimeDelta::Zero())); + QuicPathFrameBuffer path_frame_payload; + EXPECT_CALL(*session_, PrepareForProbingOnPath(_)); + EXPECT_CALL(*connection_, SendPathChallenge(_, _, _, _, _)) + .WillOnce( + Invoke([&, this](const QuicPathFrameBuffer& data_buffer, + const QuicSocketAddress& new_self_address, + const QuicSocketAddress& new_peer_address, + const QuicSocketAddress& /*effective_peer_address*/, + QuicPacketWriter* writer) { + path_frame_payload = data_buffer; + EXPECT_EQ(new_peer_address, connection_->peer_address()); + // self address and writer used for probing should be for the + // alternate network. + EXPECT_EQ(new_self_address.host(), self_address2.host()); + EXPECT_NE(writer, connection_->writer()); + return true; + })); + alarm_factory_.FireAlarm(migrate_back_alarm); + EXPECT_EQ(path_context_factory_->num_creation_attempts(), 2u); + + // Make path validation succeeds and the connection should be migrated to the + // default network. + QuicConnectionPeer::SetLastPacketDestinationAddress(connection_, + self_address2); + const QuicPathResponseFrame path_response(0, path_frame_payload); + EXPECT_CALL(*session_, ResetNonMigratableStreams()); + EXPECT_CALL(*session_, TimeSinceLastStreamClose()) + .WillOnce(Return(QuicTimeDelta::Zero())); + EXPECT_CALL(*session_, PrepareForMigrationToPath(_)).WillOnce(Return(true)); + EXPECT_CALL(*session_, OnMigrationToPathDone(_, true)); + connection_->ReallyOnPathResponseFrame(path_response); + EXPECT_EQ(migration_manager_->current_network(), initial_network_); + EXPECT_EQ(connection_->self_address(), self_address2); + EXPECT_FALSE(migrate_back_alarm->IsSet()); } // This test verifies that sessions idle for longer than the configured @@ -689,6 +781,365 @@ migration_manager_->OnNetworkDisconnected(initial_network_); } +// This test verifies after session migrates off the default network, it keeps +// retrying migrate back to the default network until the default 30s idle +// migration period threshold is exceeded. +TEST_P(QuicConnectionMigrationManagerTest, + MigratingOffDisconnectedDefaultNetworkAndHitIdleMigrationPeriod) { + migrate_idle_session_ = true; + Initialize(); + + const QuicNetworkHandle alternate_network = 2; + session_->set_alternate_network(alternate_network); + EXPECT_NE(alternate_network, migration_manager_->current_network()); + QuicSocketAddress alternate_self_address = + QuicSocketAddress(QuicIpAddress::Loopback6(), /*port=*/kTestPort); + EXPECT_NE(alternate_self_address.host(), connection_->self_address().host()); + path_context_factory_->SetSelfAddressForNetwork(alternate_network, + alternate_self_address); + + EXPECT_CALL(*session_, TimeSinceLastStreamClose()) + .WillOnce(Return(QuicTimeDelta::Zero())); + EXPECT_CALL(*session_, ResetNonMigratableStreams()); + EXPECT_CALL(*session_, PrepareForMigrationToPath(_)).WillOnce(Return(true)); + EXPECT_CALL(*session_, OnMigrationToPathDone(_, true)); + migration_manager_->OnNetworkDisconnected(initial_network_); + EXPECT_EQ(migration_manager_->current_network(), alternate_network); + EXPECT_EQ(connection_->self_address(), alternate_self_address); + EXPECT_EQ(path_context_factory_->num_creation_attempts(), 1u); + EXPECT_EQ(migration_manager_->default_network(), kInvalidNetworkHandle); + // An alarm should have been scheduled to try to migrate back to the default + // network in 1s. + QuicAlarm* migrate_back_alarm = + QuicConnectionMigrationManagerPeer::GetMigrateBackToDefaultTimer( + migration_manager_); + EXPECT_TRUE(migrate_back_alarm->IsSet()); + EXPECT_EQ( + migrate_back_alarm->deadline() - connection_helper_.GetClock()->Now(), + QuicTimeDelta::FromSeconds(1)); + + // The migrate back timer will fire. Due to default network being + // disconnected, no attempt will be exercised to migrate back. + connection_helper_.GetClock()->AdvanceTime(QuicTimeDelta::FromSeconds(1)); + QuicTimeDelta time_since_last_stream_close = QuicTimeDelta::FromSeconds(1); + EXPECT_CALL(*session_, PrepareForProbingOnPath(_)).Times(0); + alarm_factory_.FireAlarm(migrate_back_alarm); + EXPECT_EQ(path_context_factory_->num_creation_attempts(), 1u); + + // Old network now backs up. Re-attempt migration back to the default network. + migration_manager_->OnNetworkMadeDefault(initial_network_); + EXPECT_TRUE(migrate_back_alarm->IsSet()); + // The 1st attempt starts immediately. + EXPECT_EQ(migrate_back_alarm->deadline(), + connection_helper_.GetClock()->Now()); + for (size_t i = 0; i < 5; ++i) { + path_context_factory_->SetSelfAddressForNetwork( + initial_network_, + QuicSocketAddress(QuicIpAddress::Loopback4(), kTestPort + i)); + EXPECT_CALL(*session_, TimeSinceLastStreamClose()) + .WillOnce(Return(time_since_last_stream_close)); + // Update CIDs. + QuicConnectionPeer::RetirePeerIssuedConnectionIdsNoLongerOnPath( + connection_); + QuicAlarm* retire_cid_alarm = + QuicConnectionPeer::GetRetirePeerIssuedConnectionIdAlarm(connection_); + EXPECT_TRUE(retire_cid_alarm->IsSet()); + // Receive a new CID from peer for the next attempt. + QuicNewConnectionIdFrame frame; + frame.connection_id = test::TestConnectionId(1234 + i + 1); + ASSERT_NE(frame.connection_id, connection_->connection_id()); + frame.stateless_reset_token = + QuicUtils::GenerateStatelessResetToken(frame.connection_id); + frame.retire_prior_to = 2 + i; + frame.sequence_number = 3 + i; + connection_->OnNewConnectionIdFrame(frame); + EXPECT_CALL(*session_, PrepareForProbingOnPath(_)); + EXPECT_CALL(*connection_, SendPathChallenge(_, _, _, _, _)) + .WillOnce(Invoke( + [&, this](const QuicPathFrameBuffer& data_buffer, + const QuicSocketAddress& self_address, + const QuicSocketAddress& peer_address, + const QuicSocketAddress& /*effective_peer_address*/, + QuicPacketWriter* writer) { + EXPECT_EQ(peer_address, connection_->peer_address()); + // self address and writer used for probing should be for the + // initial network. + EXPECT_EQ(self_address.host(), QuicIpAddress::Loopback4()); + EXPECT_NE(writer, connection_->writer()); + return true; + })); + alarm_factory_.FireAlarm(migrate_back_alarm); + EXPECT_EQ(path_context_factory_->num_creation_attempts(), 2 + i) << i; + EXPECT_TRUE(migrate_back_alarm->IsSet()); + // Fail the current path validation. + auto* path_validator = QuicConnectionPeer::path_validator(connection_); + path_validator->CancelPathValidation(); + // Following attempt should be scheduled with expotential delay. + QuicTimeDelta next_delay = QuicTimeDelta::FromSeconds(UINT64_C(1) << i); + EXPECT_EQ(migrate_back_alarm->deadline(), + connection_helper_.GetClock()->Now() + next_delay); + connection_helper_.GetClock()->AdvanceTime(next_delay); + time_since_last_stream_close = time_since_last_stream_close + next_delay; + } + + // The connection should have been idle for longer than the idle migration + // period. Next attempt to migrate back will close the connection. + EXPECT_GT(time_since_last_stream_close, + migration_config_.idle_migration_period); + EXPECT_CALL(*session_, TimeSinceLastStreamClose()) + .WillOnce(Return(time_since_last_stream_close)); + // The connection should be closed instead of attempting to migrate back. + EXPECT_CALL( + *connection_, + CloseConnection(QUIC_NETWORK_IDLE_TIMEOUT, + "Idle session exceeds configured idle migration period", + ConnectionCloseBehavior::SILENT_CLOSE)); + alarm_factory_.FireAlarm(migrate_back_alarm); +} + +// This test verifies after handshake completes on non-default network, it keeps +// retrying migrate back to the default network until the max time on +// non-default network (128s) is reached. +TEST_P( + QuicConnectionMigrationManagerTest, + MigrateBackToDefaultUponHandshakeCompleteAndHitMaxTimeOnNonDefaultNetwork) { + complete_handshake_ = false; + default_network_ = 2; + Initialize(); + EXPECT_NE(migration_manager_->current_network(), + migration_manager_->default_network()); + + // Upon handshake completion, an alarm should have been scheduled to migrate + // back to the default network in 1s. + CompleteHandshake(false); + QuicAlarm* migrate_back_alarm = + QuicConnectionMigrationManagerPeer::GetMigrateBackToDefaultTimer( + migration_manager_); + EXPECT_TRUE(migrate_back_alarm->IsSet()); + EXPECT_EQ( + migrate_back_alarm->deadline() - connection_helper_.GetClock()->Now(), + QuicTimeDelta::FromSeconds(1)); + + // Create a stream to make the session non-idle. + session_->CreateOutgoingBidirectionalStream(); + + connection_helper_.GetClock()->AdvanceTime(QuicTimeDelta::FromSeconds(1)); + // Keep failing probing on the default network, and eventually hit max time on + // non-default network (128s). + for (size_t i = 0; i < 8; ++i) { + path_context_factory_->SetSelfAddressForNetwork( + default_network_, + QuicSocketAddress(QuicIpAddress::Loopback6(), kTestPort + i)); + EXPECT_CALL(*session_, PrepareForProbingOnPath(_)); + EXPECT_CALL(*connection_, SendPathChallenge(_, _, _, _, _)) + .WillOnce(Invoke( + [&, this](const QuicPathFrameBuffer& data_buffer, + const QuicSocketAddress& self_address, + const QuicSocketAddress& peer_address, + const QuicSocketAddress& /*effective_peer_address*/, + QuicPacketWriter* writer) { + EXPECT_EQ(peer_address, connection_->peer_address()); + // self address and writer used for probing should be for the + // default network. + EXPECT_EQ(self_address.host(), QuicIpAddress::Loopback6()); + EXPECT_NE(writer, connection_->writer()); + return true; + })); + alarm_factory_.FireAlarm(migrate_back_alarm); + EXPECT_EQ(path_context_factory_->num_creation_attempts(), 1 + i); + + // Fail the current path validation. + auto* path_validator = QuicConnectionPeer::path_validator(connection_); + path_validator->CancelPathValidation(); + + EXPECT_TRUE(migrate_back_alarm->IsSet()); + QuicTimeDelta next_delay = QuicTimeDelta::FromSeconds(UINT64_C(1) << i); + EXPECT_EQ(migrate_back_alarm->deadline(), + connection_helper_.GetClock()->Now() + next_delay) + << i << ", " << next_delay; + connection_helper_.GetClock()->AdvanceTime(next_delay); + + // Update CIDs for the next attempt. + QuicConnectionPeer::RetirePeerIssuedConnectionIdsNoLongerOnPath( + connection_); + QuicAlarm* retire_cid_alarm = + QuicConnectionPeer::GetRetirePeerIssuedConnectionIdAlarm(connection_); + EXPECT_TRUE(retire_cid_alarm->IsSet()); + // Receive a new CID from peer for the next attempt. + QuicNewConnectionIdFrame frame; + frame.connection_id = test::TestConnectionId(1234 + i + 1); + ASSERT_NE(frame.connection_id, connection_->connection_id()); + frame.stateless_reset_token = + QuicUtils::GenerateStatelessResetToken(frame.connection_id); + frame.retire_prior_to = 2 + i; + frame.sequence_number = 3 + i; + connection_->OnNewConnectionIdFrame(frame); + } + EXPECT_FALSE(session_->going_away()); + + // Another attempt should exceeds 128s on non-default network timeout and the + // session should be drained. + path_context_factory_->SetSelfAddressForNetwork( + default_network_, + QuicSocketAddress(QuicIpAddress::Loopback6(), kTestPort + 8)); + alarm_factory_.FireAlarm(migrate_back_alarm); + EXPECT_TRUE(session_->going_away()); +} + +// Test that if config.migrate_session_on_network_change is false, no migration +// back to default is scheduled after handshake completes on a non-default +// network. +TEST_P(QuicConnectionMigrationManagerTest, + NoMigrateBackToDefaultWhenDisabledByConfig) { + complete_handshake_ = false; + connection_migration_on_network_change_ = false; + default_network_ = 2; + Initialize(); + EXPECT_NE(migration_manager_->current_network(), + migration_manager_->default_network()); + + QuicAlarm* migrate_back_alarm = + QuicConnectionMigrationManagerPeer::GetMigrateBackToDefaultTimer( + migration_manager_); + EXPECT_FALSE(migrate_back_alarm->IsSet()); + + CompleteHandshake(false); + + EXPECT_FALSE(migrate_back_alarm->IsSet()); +} + +// This test verifies that after receiving a signal that a new network becomes +// the default network, migration_manager attempts to probe the new default +// network, and meanwhile a signal of disconnection of the original network +// shouldn't trigger another migration attempt. +TEST_P(QuicConnectionMigrationManagerTest, + CurrentNetworkDisconnectedWhileProbingNewDefaultNetwork) { + Initialize(); + const QuicNetworkHandle new_default_network = 2; + EXPECT_NE(migration_manager_->current_network(), new_default_network); + + // Create a stream to make the session non-idle. + session_->CreateOutgoingBidirectionalStream(); + + QuicAlarm* migrate_back_alarm = + QuicConnectionMigrationManagerPeer::GetMigrateBackToDefaultTimer( + migration_manager_); + + // Signal that the initial network which is already the default network + // becomes the default. This should have no effect on migration. + migration_manager_->OnNetworkMadeDefault(initial_network_); + EXPECT_FALSE(migrate_back_alarm->IsSet()); + + // Signal the new default network. + path_context_factory_->SetSelfAddressForNetwork( + new_default_network, + QuicSocketAddress(QuicIpAddress::Loopback6(), kTestPort)); + migration_manager_->OnNetworkMadeDefault(new_default_network); + + // An alarm should have been scheduled to migrate back to the default network + // immediately. + EXPECT_TRUE(migrate_back_alarm->IsSet()); + EXPECT_EQ(migrate_back_alarm->deadline(), + connection_helper_.GetClock()->Now()); + + // Fire the alarm to migrate back to default network, starting with probing. + EXPECT_CALL(*session_, PrepareForProbingOnPath(_)); + EXPECT_CALL(*connection_, SendPathChallenge(_, _, _, _, _)) + .WillOnce( + Invoke([&, this](const QuicPathFrameBuffer& data_buffer, + const QuicSocketAddress& self_address, + const QuicSocketAddress& peer_address, + const QuicSocketAddress& /*effective_peer_address*/, + QuicPacketWriter* writer) { + EXPECT_EQ(peer_address, connection_->peer_address()); + // self address and writer used for probing should be for the + // default network. + EXPECT_EQ(self_address.host(), QuicIpAddress::Loopback6()); + EXPECT_NE(writer, connection_->writer()); + return true; + })); + alarm_factory_.FireAlarm(migrate_back_alarm); + EXPECT_EQ(path_context_factory_->num_creation_attempts(), 1u); + EXPECT_TRUE(session_->HasPendingPathValidation()); + // The alarm should be re-armed with a longer timeout. + EXPECT_TRUE(migrate_back_alarm->IsSet()); + EXPECT_EQ( + migrate_back_alarm->deadline() - connection_helper_.GetClock()->Now(), + QuicTimeDelta::FromSeconds(1)); + + // Duplicated signal of new default network shouldn't trigger another probing + // or change the migration back alarm. + migration_manager_->OnNetworkMadeDefault(new_default_network); + EXPECT_EQ(path_context_factory_->num_creation_attempts(), 1u); + EXPECT_TRUE(session_->HasPendingPathValidation()); + EXPECT_EQ( + migrate_back_alarm->deadline() - connection_helper_.GetClock()->Now(), + QuicTimeDelta::FromSeconds(1)); + + // Disconnect the current network, this should not trigger another migration + // attempt. + EXPECT_CALL(*session_, ResetNonMigratableStreams()).Times(0); + EXPECT_CALL(*session_, PrepareForMigrationToPath(_)).Times(0); + EXPECT_CALL(*session_, OnNoNewNetworkForMigration()).Times(0); + migration_manager_->OnNetworkDisconnected(initial_network_); + EXPECT_EQ(path_context_factory_->num_creation_attempts(), 1u); + EXPECT_TRUE(session_->HasPendingPathValidation()); +} + +TEST_P(QuicConnectionMigrationManagerTest, FailToProbeNewDefaultNetwork) { + Initialize(); + const QuicNetworkHandle new_default_network = 2; + EXPECT_NE(migration_manager_->current_network(), new_default_network); + + // Create a stream to make the session non-idle. + session_->CreateOutgoingBidirectionalStream(); + + QuicAlarm* migrate_back_alarm = + QuicConnectionMigrationManagerPeer::GetMigrateBackToDefaultTimer( + migration_manager_); + + // Signal the new default network. + path_context_factory_->SetSelfAddressForNetwork( + new_default_network, + QuicSocketAddress(QuicIpAddress::Loopback6(), kTestPort)); + migration_manager_->OnNetworkMadeDefault(new_default_network); + + // An alarm should have been scheduled to migrate back to the default network + // immediately. + EXPECT_TRUE(migrate_back_alarm->IsSet()); + EXPECT_EQ(migrate_back_alarm->deadline(), + connection_helper_.GetClock()->Now()); + + // Fire the alarm to migrate back to default network, starting with probing. + EXPECT_CALL(*session_, PrepareForProbingOnPath(_)); + EXPECT_CALL(*connection_, SendPathChallenge(_, _, _, _, _)) + .Times(3) + .WillRepeatedly( + Invoke([&, this](const QuicPathFrameBuffer& data_buffer, + const QuicSocketAddress& self_address, + const QuicSocketAddress& peer_address, + const QuicSocketAddress& /*effective_peer_address*/, + QuicPacketWriter* writer) { + EXPECT_EQ(peer_address, connection_->peer_address()); + // self address and writer used for probing should be for the + // default network. + EXPECT_EQ(self_address.host(), QuicIpAddress::Loopback6()); + EXPECT_NE(writer, connection_->writer()); + return true; + })); + alarm_factory_.FireAlarm(migrate_back_alarm); + EXPECT_EQ(path_context_factory_->num_creation_attempts(), 1u); + EXPECT_TRUE(session_->HasPendingPathValidation()); + + // Simulate probing failure. + auto* path_validator = QuicConnectionPeer::path_validator(connection_); + QuicAlarm* retry_timer = QuicPathValidatorPeer::retry_timer(path_validator); + alarm_factory_.FireAlarm(retry_timer); + alarm_factory_.FireAlarm(retry_timer); + alarm_factory_.FireAlarm(retry_timer); + EXPECT_FALSE(session_->HasPendingPathValidation()); +} + class QuicConnectionMigrationManagerGoogleQuicTest : public QuicConnectionMigrationManagerTest {};