blob: 224b8a61e28a48a83479a8fd17a9ad5f2588a5bc [file]
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "quiche/quic/core/http/quic_connection_migration_manager.h"
#include <memory>
#include "quiche/quic/core/crypto/null_encrypter.h"
#include "quiche/quic/core/http/quic_spdy_client_session_with_migration.h"
#include "quiche/quic/core/io/socket.h"
#include "quiche/quic/core/quic_force_blockable_packet_writer.h"
#include "quiche/quic/core/quic_path_validator.h"
#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::_;
using testing::NiceMock;
using testing::Return;
using testing::StrictMock;
namespace quic::test {
MATCHER_P(IsFrame, type, "") { return arg.type == type; }
class QuicConnectionMigrationManagerPeer {
public:
static QuicAlarm* GetWaitForMigrationAlarm(
QuicConnectionMigrationManager* manager) {
return manager->wait_for_migration_alarm_.get();
}
static QuicAlarm* GetRunPendingCallbacksAlarm(
QuicConnectionMigrationManager* manager) {
return manager->run_pending_callbacks_alarm_.get();
}
static QuicAlarm* GetMigrateBackToDefaultTimer(
QuicConnectionMigrationManager* manager) {
return manager->migrate_back_to_default_timer_.get();
}
};
class TestQuicClientPathValidationContext
: public QuicClientPathValidationContext {
public:
TestQuicClientPathValidationContext(
const quic::QuicSocketAddress& self_address,
const quic::QuicSocketAddress& peer_address, QuicNetworkHandle network)
: QuicClientPathValidationContext(self_address, peer_address, network),
writer_(std::make_unique<QuicForceBlockablePacketWriter>()) {
auto* writer = new NiceMock<MockPacketWriter>();
// Owns writer.
writer_->set_writer(writer);
ON_CALL(*writer, WritePacket(_, _, _, _, _, _))
.WillByDefault(Return(WriteResult(WRITE_STATUS_OK, 0)));
ON_CALL(*writer, GetMaxPacketSize(_))
.WillByDefault(Return(kMaxOutgoingPacketSize));
ON_CALL(*writer, IsBatchMode()).WillByDefault(Return(false));
ON_CALL(*writer, GetNextWriteLocation(_, _))
.WillByDefault(Return(QuicPacketBuffer()));
ON_CALL(*writer, Flush())
.WillByDefault(Return(WriteResult(WRITE_STATUS_OK, 0)));
ON_CALL(*writer, SupportsReleaseTime()).WillByDefault(Return(false));
ON_CALL(*writer, MessageTooBigErrorCode())
.WillByDefault(Return(kSocketErrorMsgSize));
}
QuicForceBlockablePacketWriter* ForceBlockableWriterToUse() override {
return writer_.get();
}
bool ShouldConnectionOwnWriter() const override { return true; }
void ReleasePacketWriter() { writer_.release(); }
private:
std::unique_ptr<QuicForceBlockablePacketWriter> writer_;
};
class TestQuicPathContextFactory : public QuicPathContextFactory {
public:
TestQuicPathContextFactory(bool async_creation, bool has_error)
: async_creation_(async_creation), has_error_(has_error) {}
void CreatePathValidationContext(
QuicNetworkHandle network, QuicSocketAddress peer_address,
std::unique_ptr<CreationResultDelegate> result_delegate) override {
pending_result_delegate_ = std::move(result_delegate);
network_ = network;
peer_address_ = peer_address;
if (!async_creation_) {
FinishPendingCreation();
}
++num_creation_attempts_;
}
void FinishPendingCreation() {
ASSERT_NE(pending_result_delegate_, nullptr)
<< "No pending path context creation";
if (has_error_) {
pending_result_delegate_->OnCreationFailed(
network_, "path context creation failure.");
} else {
QUICHE_DCHECK(network_to_address_map_.contains(network_));
pending_result_delegate_->OnCreationSucceeded(
std::make_unique<TestQuicClientPathValidationContext>(
network_to_address_map_[network_], peer_address_, network_));
network_to_address_map_.erase(network_);
}
pending_result_delegate_ = nullptr;
}
void SetSelfAddressForNetwork(QuicNetworkHandle network,
const QuicSocketAddress& self_address) {
QUICHE_DCHECK(!network_to_address_map_.contains(network));
network_to_address_map_[network] = self_address;
}
size_t num_creation_attempts() const { return num_creation_attempts_; }
private:
bool async_creation_ = false;
bool has_error_ = false;
std::unique_ptr<CreationResultDelegate> pending_result_delegate_;
QuicNetworkHandle network_ = kInvalidNetworkHandle;
QuicSocketAddress peer_address_;
absl::flat_hash_map<QuicNetworkHandle, QuicSocketAddress>
network_to_address_map_;
size_t num_creation_attempts_ = 0;
};
class TestCryptoStream : public QuicCryptoStream, public QuicCryptoHandshaker {
public:
explicit TestCryptoStream(QuicSession* session)
: QuicCryptoStream(session),
QuicCryptoHandshaker(this, session),
params_(new QuicCryptoNegotiatedParameters) {
// Simulate a negotiated cipher_suite with a fake value.
params_->cipher_suite = 1;
}
void EstablishZeroRttEncryption() {
encryption_established_ = true;
session()->connection()->SetEncrypter(
ENCRYPTION_ZERO_RTT,
std::make_unique<NullEncrypter>(session()->perspective()));
}
void OnHandshakeMessage(const CryptoHandshakeMessage& /*message*/) override {
encryption_established_ = true;
one_rtt_keys_available_ = true;
QuicErrorCode error;
std::string error_details;
session()->config()->SetInitialStreamFlowControlWindowToSend(
test::kInitialStreamFlowControlWindowForTest);
session()->config()->SetInitialSessionFlowControlWindowToSend(
test::kInitialSessionFlowControlWindowForTest);
if (session()->version().IsIetfQuic()) {
if (session()->perspective() == Perspective::IS_CLIENT) {
session()->config()->SetOriginalConnectionIdToSend(
session()->connection()->connection_id());
session()->config()->SetInitialSourceConnectionIdToSend(
session()->connection()->connection_id());
} else {
session()->config()->SetInitialSourceConnectionIdToSend(
session()->connection()->client_connection_id());
}
TransportParameters transport_parameters;
EXPECT_TRUE(
session()->config()->FillTransportParameters(&transport_parameters));
error = session()->config()->ProcessTransportParameters(
transport_parameters, /* is_resumption = */ false, &error_details);
} else {
CryptoHandshakeMessage msg;
session()->config()->ToHandshakeMessage(&msg, transport_version());
error =
session()->config()->ProcessPeerHello(msg, CLIENT, &error_details);
}
session()->OnConfigNegotiated();
EXPECT_THAT(error, test::IsQuicNoError());
session()->OnNewEncryptionKeyAvailable(
ENCRYPTION_FORWARD_SECURE,
std::make_unique<NullEncrypter>(session()->perspective()));
if (session()->connection()->version().IsIetfQuic()) {
session()->OnTlsHandshakeComplete();
} else {
session()->SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE);
}
session()->DiscardOldEncryptionKey(ENCRYPTION_INITIAL);
}
// QuicCryptoStream implementation
ssl_early_data_reason_t EarlyDataReason() const override {
return ssl_early_data_unknown;
}
bool encryption_established() const override {
return encryption_established_;
}
bool one_rtt_keys_available() const override {
return one_rtt_keys_available_;
}
const QuicCryptoNegotiatedParameters& crypto_negotiated_params()
const override {
return *params_;
}
CryptoMessageParser* crypto_message_parser() override {
return QuicCryptoHandshaker::crypto_message_parser();
}
void OnPacketDecrypted(EncryptionLevel /*level*/) override {}
void OnOneRttPacketAcknowledged() override {}
void OnHandshakePacketSent() override {}
void OnHandshakeDoneReceived() override {}
void OnNewTokenReceived(absl::string_view /*token*/) override {}
std::string GetAddressToken(
const CachedNetworkParameters* /*cached_network_parameters*/)
const override {
return "";
}
bool ValidateAddressToken(absl::string_view /*token*/) const override {
return true;
}
const CachedNetworkParameters* PreviousCachedNetworkParams() const override {
return nullptr;
}
void SetPreviousCachedNetworkParams(
CachedNetworkParameters /*cached_network_params*/) override {}
HandshakeState GetHandshakeState() const override {
return one_rtt_keys_available() ? HANDSHAKE_CONFIRMED : HANDSHAKE_START;
}
void SetServerApplicationStateForResumption(
std::unique_ptr<ApplicationState> /*application_state*/) override {}
MOCK_METHOD(std::unique_ptr<QuicDecrypter>,
AdvanceKeysAndCreateCurrentOneRttDecrypter, (), (override));
MOCK_METHOD(std::unique_ptr<QuicEncrypter>, CreateCurrentOneRttEncrypter, (),
(override));
MOCK_METHOD(void, OnCanWrite, (), (override));
bool HasPendingCryptoRetransmission() const override { return false; }
MOCK_METHOD(bool, HasPendingRetransmission, (), (const, override));
void OnConnectionClosed(const QuicConnectionCloseFrame& /*frame*/,
ConnectionCloseSource /*source*/) override {}
bool ExportKeyingMaterial(absl::string_view /*label*/,
absl::string_view /*context*/,
size_t /*result_len*/,
std::string* /*result*/) override {
return false;
}
SSL* GetSsl() const override { return nullptr; }
bool IsCryptoFrameExpectedForEncryptionLevel(
EncryptionLevel level) const override {
return level != ENCRYPTION_ZERO_RTT;
}
EncryptionLevel GetEncryptionLevelToSendCryptoDataOfSpace(
PacketNumberSpace space) const override {
switch (space) {
case INITIAL_DATA:
return ENCRYPTION_INITIAL;
case HANDSHAKE_DATA:
return ENCRYPTION_HANDSHAKE;
case APPLICATION_DATA:
return ENCRYPTION_FORWARD_SECURE;
default:
QUICHE_DCHECK(false);
return NUM_ENCRYPTION_LEVELS;
}
}
private:
using QuicCryptoStream::session;
bool encryption_established_ = false;
bool one_rtt_keys_available_ = false;
quiche::QuicheReferenceCountedPointer<QuicCryptoNegotiatedParameters> params_;
};
class TestStream : public QuicSpdyStream {
public:
TestStream(QuicStreamId id, QuicSpdySession* session, StreamType type)
: QuicSpdyStream(id, session, type) {}
void OnBodyAvailable() override {}
MOCK_METHOD(void, OnCanWrite, (), (override));
MOCK_METHOD(bool, RetransmitStreamData,
(QuicStreamOffset, QuicByteCount, bool, TransmissionType),
(override));
MOCK_METHOD(bool, HasPendingRetransmission, (), (const, override));
protected:
bool ValidateReceivedHeaders(const QuicHeaderList& /*header_list*/) override {
return true;
}
};
class TestQuicSpdyClientSessionWithMigration
: public QuicSpdyClientSessionWithMigration {
public:
TestQuicSpdyClientSessionWithMigration(
QuicConnection* connection, QuicForceBlockablePacketWriter* writer,
QuicSession::Visitor* visitor, const QuicConfig& config,
const ParsedQuicVersionVector& supported_versions,
QuicNetworkHandle default_network, QuicNetworkHandle current_network,
std::unique_ptr<QuicPathContextFactory> path_context_factory,
QuicConnectionMigrationConfig migration_config)
: QuicSpdyClientSessionWithMigration(
connection, writer, visitor, config, supported_versions,
default_network, current_network, std::move(path_context_factory),
migration_config, QuicPriorityType::kHttp),
crypto_stream_(this) {
ON_CALL(*this, IsSessionProxied()).WillByDefault(Return(false));
ON_CALL(*this, OnMigrationToPathDone(_, _))
.WillByDefault(
[](std::unique_ptr<QuicPathValidationContext> context,
bool success) {
if (success) {
static_cast<TestQuicClientPathValidationContext&>(*context)
.ReleasePacketWriter();
}
});
}
MOCK_METHOD(void, ResetNonMigratableStreams, (), (override));
MOCK_METHOD(void, OnNoNewNetworkForMigration, (), (override));
MOCK_METHOD(void, PrepareForProbingOnPath,
(QuicPathValidationContext & context), (override));
MOCK_METHOD(bool, IsSessionProxied, (), (const));
MOCK_METHOD(bool, PrepareForMigrationToPath,
(QuicClientPathValidationContext&));
MOCK_METHOD(void, OnMigrationToPathDone,
(std::unique_ptr<QuicClientPathValidationContext>, bool));
MOCK_METHOD(void, OnConnectionToBeClosedDueToMigrationError,
(MigrationCause migration_cause, QuicErrorCode quic_error),
(override));
// QuicSpdyClientSessionWithMigration.
QuicNetworkHandle FindAlternateNetwork(QuicNetworkHandle network) override {
QUICHE_DCHECK_NE(network, alternate_network_);
return alternate_network_;
}
void StartDraining() override { going_away_ = true; }
// QuicSession.
TestCryptoStream* GetMutableCryptoStream() override {
return &crypto_stream_;
}
const TestCryptoStream* GetCryptoStream() const override {
return &crypto_stream_;
}
void OnProofValid(
const QuicCryptoClientConfig::CachedState& /*cached*/) override {}
void OnProofVerifyDetailsAvailable(
const ProofVerifyDetails& /*verify_details*/) override {}
bool OnCertificateRequested(
const std::vector<std::string>& /*cert_authorities*/) override {
return false;
}
TestStream* CreateOutgoingBidirectionalStream() override {
QuicStreamId id = GetNextOutgoingBidirectionalStreamId();
if (id ==
QuicUtils::GetInvalidStreamId(connection()->transport_version())) {
return nullptr;
}
TestStream* stream = new TestStream(id, this, BIDIRECTIONAL);
ActivateStream(absl::WrapUnique(stream));
return stream;
}
TestStream* CreateIncomingStream(QuicStreamId id) override {
// Enforce the limit on the number of open streams.
if (!VersionIsIetfQuic(connection()->transport_version()) &&
stream_id_manager().num_open_incoming_streams() + 1 >
max_open_incoming_bidirectional_streams()) {
// No need to do this test for version 99; it's done by
// QuicSession::GetOrCreateStream.
connection()->CloseConnection(
QUIC_TOO_MANY_OPEN_STREAMS, "Too many streams!",
ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
return nullptr;
}
TestStream* stream = new TestStream(
id, this,
test::DetermineStreamType(id, connection()->version(), perspective(),
/*is_incoming=*/true, BIDIRECTIONAL));
ActivateStream(absl::WrapUnique(stream));
return stream;
}
void set_alternate_network(QuicNetworkHandle network) {
alternate_network_ = network;
}
bool going_away() const { return going_away_; }
using QuicSpdyClientSessionWithMigration::migration_manager;
protected:
bool ShouldCreateIncomingStream(QuicStreamId /*id*/) override { return true; }
bool ShouldCreateOutgoingBidirectionalStream() override { return true; }
private:
QuicNetworkHandle alternate_network_ = kInvalidNetworkHandle;
bool going_away_ = false;
NiceMock<TestCryptoStream> crypto_stream_;
};
class QuicConnectionMigrationManagerTest
: public test::QuicTestWithParam<ParsedQuicVersion> {
public:
QuicConnectionMigrationManagerTest()
: versions_(ParsedQuicVersionVector{GetParam()}),
config_(test::DefaultQuicConfig()),
connection_(new StrictMock<test::MockQuicConnection>(
&connection_helper_, &alarm_factory_, Perspective::IS_CLIENT,
versions_)),
default_writer_(new QuicForceBlockablePacketWriter()) {
auto* writer = new NiceMock<MockPacketWriter>();
// Owns writer.
default_writer_->set_writer(writer);
ON_CALL(*writer, WritePacket(_, _, _, _, _, _))
.WillByDefault(Return(WriteResult(WRITE_STATUS_OK, 0)));
ON_CALL(*writer, GetMaxPacketSize(_))
.WillByDefault(Return(kMaxOutgoingPacketSize));
ON_CALL(*writer, IsBatchMode()).WillByDefault(Return(false));
ON_CALL(*writer, GetNextWriteLocation(_, _))
.WillByDefault(Return(QuicPacketBuffer()));
ON_CALL(*writer, Flush())
.WillByDefault(Return(WriteResult(WRITE_STATUS_OK, 0)));
ON_CALL(*writer, SupportsReleaseTime()).WillByDefault(Return(false));
ON_CALL(*writer, MessageTooBigErrorCode())
.WillByDefault(Return(kSocketErrorMsgSize));
connection_->SetQuicPacketWriter(default_writer_, true);
}
void Initialize() {
migration_config_.migrate_session_early =
connection_migration_on_path_degrading_ &&
connection_migration_on_network_change_;
migration_config_.migrate_session_on_network_change =
connection_migration_on_network_change_;
migration_config_.allow_port_migration = port_migration_;
migration_config_.migrate_idle_session = migrate_idle_session_;
path_context_factory_ = new TestQuicPathContextFactory(
async_path_context_creation_, /*has_error*/ false);
session_ = std::make_unique<TestQuicSpdyClientSessionWithMigration>(
connection_, default_writer_, &session_visitor_, config_, versions_,
default_network_, initial_network_,
std::unique_ptr<QuicPathContextFactory>(path_context_factory_),
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));
if (complete_handshake_) {
CompleteHandshake(/*received_server_preferred_address=*/false);
return;
}
}
void CompleteHandshake(bool received_server_preferred_address) {
const QuicConnectionId extra_connection_id = TestConnectionId(1234);
ASSERT_NE(extra_connection_id, connection_->connection_id());
const StatelessResetToken reset_token =
QuicUtils::GenerateStatelessResetToken(extra_connection_id);
if (version().IsIetfQuic() && received_server_preferred_address) {
// OnHandshakeMessage() will populate the received values with these.
QuicIpAddress ipv4, ipv6;
ASSERT_TRUE(ipv4.FromString("127.0.0.2"));
ASSERT_TRUE(ipv6.FromString("::2"));
session_->config()->SetIPv4AlternateServerAddressToSend(
QuicSocketAddress(ipv4, 12345));
session_->config()->SetIPv6AlternateServerAddressToSend(
QuicSocketAddress(ipv6, 12345));
session_->config()->SetPreferredAddressConnectionIdAndTokenToSend(
extra_connection_id, reset_token);
}
CryptoHandshakeMessage msg;
session_->GetMutableCryptoStream()->OnHandshakeMessage(msg);
EXPECT_TRUE(session_->OneRttKeysAvailable());
EXPECT_EQ(session_->GetHandshakeState(), HANDSHAKE_CONFIRMED);
if (received_server_preferred_address) {
EXPECT_TRUE(
QuicConnectionPeer::GetReceivedServerPreferredAddress(connection_)
.IsInitialized());
}
connection_->SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE);
if (version().IsIetfQuic() && !received_server_preferred_address) {
// Prepare an additional CID for future migration.
QuicNewConnectionIdFrame frame;
frame.connection_id = extra_connection_id;
frame.stateless_reset_token = reset_token;
frame.retire_prior_to = 0u;
frame.sequence_number = 1u;
connection_->OnNewConnectionIdFrame(frame);
}
connection_->OnHandshakeComplete();
}
ParsedQuicVersion version() const { return versions_[0]; }
protected:
QuicNetworkHandle default_network_ = 1;
QuicNetworkHandle initial_network_ = 1;
MockQuicConnectionHelper connection_helper_;
MockAlarmFactory alarm_factory_;
NiceMock<test::MockQuicSessionVisitor> session_visitor_;
ParsedQuicVersionVector versions_;
QuicConfig config_;
QuicConnectionMigrationConfig migration_config_;
// Owned by |session_|
TestQuicPathContextFactory* path_context_factory_ = nullptr;
// Owned by |session_|
StrictMock<test::MockQuicConnection>* connection_;
QuicForceBlockablePacketWriter* default_writer_;
std::unique_ptr<TestQuicSpdyClientSessionWithMigration> session_;
QuicConnectionMigrationManager* migration_manager_ = nullptr;
bool connection_migration_on_path_degrading_ = true;
bool port_migration_ = true;
bool connection_migration_on_network_change_ = true;
bool migrate_idle_session_ = false;
bool complete_handshake_ = true;
bool async_path_context_creation_ = false;
};
// Used by ::testing::PrintToStringParamName().
std::string PrintToString(const ParsedQuicVersion& p) {
return ParsedQuicVersionToString(p);
}
INSTANTIATE_TEST_SUITE_P(QuicConnectionMigrationManagerTests,
QuicConnectionMigrationManagerTest,
::testing::ValuesIn(CurrentSupportedHttp3Versions()),
::testing::PrintToStringParamName());
// This test verifies that session times out connection migration attempt
// with signals delivered in the following order (no alternate network is
// available):
// - default network disconnected is delivered: session attempts connection
// migration but found not alternate network. Session waits for a new network
// comes up in the next kWaitTimeForNewNetworkSecs seconds.
// - no new network is connected, migration times out. Session is closed.
TEST_P(QuicConnectionMigrationManagerTest, MigrationTimeoutWithNoNewNetwork) {
Initialize();
// Trigger a network disconnected signal to attempt migrating to a different
// network. But since there is no alternative network available, no migration
// should have happened.
EXPECT_CALL(*session_, OnNoNewNetworkForMigration());
migration_manager_->OnNetworkDisconnected(initial_network_);
EXPECT_EQ(path_context_factory_->num_creation_attempts(), 0u);
EXPECT_TRUE(default_writer_->IsWriteBlocked());
QuicAlarm* migration_alarm =
QuicConnectionMigrationManagerPeer::GetWaitForMigrationAlarm(
migration_manager_);
EXPECT_TRUE(migration_alarm->IsSet());
EXPECT_EQ(migration_alarm->deadline() - connection_helper_.GetClock()->Now(),
QuicTimeDelta::FromSeconds(10));
EXPECT_CALL(
*connection_,
CloseConnection(QUIC_CONNECTION_MIGRATION_NO_NEW_NETWORK,
"Migration for cause OnNetworkDisconnected timed out",
ConnectionCloseBehavior::SILENT_CLOSE));
connection_helper_.GetClock()->AdvanceTime(QuicTimeDelta::FromSeconds(10));
alarm_factory_.FireAlarm(migration_alarm);
}
TEST_P(QuicConnectionMigrationManagerTest,
MigrationDeferredUntilNewNetworkConnected) {
migrate_idle_session_ = true;
Initialize();
// Trigger a network disconnected signal to attempt migrating to a different
// network. But since there is no alternative network available, no migration
// should have happened.
EXPECT_CALL(*session_, OnNoNewNetworkForMigration());
migration_manager_->OnNetworkDisconnected(initial_network_);
EXPECT_EQ(path_context_factory_->num_creation_attempts(), 0u);
EXPECT_TRUE(default_writer_->IsWriteBlocked());
QuicAlarm* migration_alarm =
QuicConnectionMigrationManagerPeer::GetWaitForMigrationAlarm(
migration_manager_);
EXPECT_TRUE(migration_alarm->IsSet());
EXPECT_EQ(migration_alarm->deadline() - connection_helper_.GetClock()->Now(),
QuicTimeDelta::FromSeconds(10));
// Alternative network connected. Another migration should be attempted.
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_EQ(session_->TimeSinceLastStreamClose(),
QuicTimeDelta::FromSeconds(1));
EXPECT_CALL(*session_, ResetNonMigratableStreams());
EXPECT_CALL(*session_, PrepareForMigrationToPath(_)).WillOnce(Return(true));
EXPECT_CALL(*session_, OnMigrationToPathDone(_, true));
migration_manager_->OnNetworkConnected(alternate_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_FALSE(connection_->writer()->IsWriteBlocked());
}
// This test verifies session migrates off the disconnected default network and
// migrates back to the default network later with probing.
TEST_P(QuicConnectionMigrationManagerTest,
MigratingOffDisconnectedDefaultNetworkAndMigrateBack) {
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);
QuicSocketAddress self_address = connection_->self_address();
EXPECT_EQ(session_->TimeSinceLastStreamClose(),
QuicTimeDelta::FromSeconds(1));
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);
// 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_EQ(session_->TimeSinceLastStreamClose(),
QuicTimeDelta::FromSeconds(2));
QuicPathFrameBuffer path_frame_payload;
EXPECT_CALL(*session_, PrepareForProbingOnPath(_));
EXPECT_CALL(*connection_, SendPathChallenge(_, _, _, _, _))
.WillOnce([&, 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_EQ(session_->TimeSinceLastStreamClose(),
QuicTimeDelta::FromSeconds(2));
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 when the current network is disconnected, migration
// should be attempted immediately. Any write error during and after the path
// context creation should be ignored.
TEST_P(QuicConnectionMigrationManagerTest,
NetworkDisconnectedFollowedByWriteErrorsAsyncPathContextCreation) {
migrate_idle_session_ = true;
async_path_context_creation_ = 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);
// Receive a network disconnected signal, migration should be attempted
// immediately.
EXPECT_EQ(session_->TimeSinceLastStreamClose(),
QuicTimeDelta::FromSeconds(1));
EXPECT_CALL(*session_, ResetNonMigratableStreams());
migration_manager_->OnNetworkDisconnected(initial_network_);
EXPECT_EQ(path_context_factory_->num_creation_attempts(), 1u);
// While waiting for the path context to be created asynchronously, any write
// error shouldn't trigger another migration.
migration_manager_->MaybeStartMigrateSessionOnWriteError(/*error_code=*/111);
// An alarm should have been scheduled to run pending callbacks.
QuicAlarm* pending_callbacks_alarm =
QuicConnectionMigrationManagerPeer::GetRunPendingCallbacksAlarm(
migration_manager_);
EXPECT_TRUE(pending_callbacks_alarm->IsSet());
// Fire the alarm should not trigger another migration.
alarm_factory_.FireAlarm(pending_callbacks_alarm);
EXPECT_EQ(path_context_factory_->num_creation_attempts(), 1u);
// Another write error which will be handle asynchronously after the path
// context creation is finished should also be ignored.
migration_manager_->MaybeStartMigrateSessionOnWriteError(/*error_code=*/111);
// An alarm should have been scheduled to run pending callbacks.
pending_callbacks_alarm =
QuicConnectionMigrationManagerPeer::GetRunPendingCallbacksAlarm(
migration_manager_);
EXPECT_TRUE(pending_callbacks_alarm->IsSet());
// Finish creating the path context and continue the migration.
EXPECT_CALL(*session_, PrepareForMigrationToPath(_)).WillOnce(Return(true));
EXPECT_CALL(*session_, OnMigrationToPathDone(_, true));
path_context_factory_->FinishPendingCreation();
EXPECT_EQ(migration_manager_->current_network(), alternate_network);
EXPECT_EQ(connection_->self_address(), alternate_self_address);
// Fire the alarm to actually handle the 2nd write error, it should not
// trigger another migration.
alarm_factory_.FireAlarm(pending_callbacks_alarm);
EXPECT_EQ(path_context_factory_->num_creation_attempts(), 1u);
}
// This test verifies that sessions idle for longer than the configured
// |idle_migration_period| should not be migrated.
TEST_P(QuicConnectionMigrationManagerTest, DoNotMigrateLongIdleSession) {
migrate_idle_session_ = true;
Initialize();
const QuicNetworkHandle alternate_network = 2;
session_->set_alternate_network(alternate_network);
EXPECT_NE(alternate_network, migration_manager_->current_network());
connection_helper_.GetClock()->AdvanceTime(
migration_config_.idle_migration_period);
EXPECT_CALL(
*connection_,
CloseConnection(QUIC_NETWORK_IDLE_TIMEOUT,
"Idle session exceeds configured idle migration period",
ConnectionCloseBehavior::SILENT_CLOSE));
migration_manager_->OnNetworkDisconnected(initial_network_);
}
// This test verifies that no idle sessions should be migrated if disallowed by
// config.
TEST_P(QuicConnectionMigrationManagerTest,
DoNotMigrateIdleSessionIfDisabledByConfig) {
migrate_idle_session_ = false;
Initialize();
const QuicNetworkHandle alternate_network = 2;
session_->set_alternate_network(alternate_network);
EXPECT_NE(alternate_network, migration_manager_->current_network());
EXPECT_CALL(*connection_,
CloseConnection(QUIC_CONNECTION_MIGRATION_NO_MIGRATABLE_STREAMS,
"Migrating idle session is disabled.",
ConnectionCloseBehavior::SILENT_CLOSE));
migration_manager_->OnNetworkDisconnected(initial_network_);
}
TEST_P(QuicConnectionMigrationManagerTest,
ConnectionMigrationDisabledDuringHandshakeAndNetworkDisconnected) {
QuicConfigPeer::SetReceivedDisableConnectionMigration(&config_);
migrate_idle_session_ = true;
Initialize();
const QuicNetworkHandle alternate_network = 2;
session_->set_alternate_network(alternate_network);
EXPECT_NE(alternate_network, migration_manager_->current_network());
EXPECT_TRUE(session_->config()->DisableConnectionMigration());
EXPECT_EQ(session_->TimeSinceLastStreamClose(),
QuicTimeDelta::FromSeconds(1));
EXPECT_CALL(*connection_,
CloseConnection(QUIC_CONNECTION_MIGRATION_DISABLED_BY_CONFIG,
"Migration disabled by config",
ConnectionCloseBehavior::SILENT_CLOSE));
migration_manager_->OnNetworkDisconnected(initial_network_);
}
TEST_P(QuicConnectionMigrationManagerTest,
ConnectionMigrationDisabledDuringHandshakeAndWriteError) {
QuicConfigPeer::SetReceivedDisableConnectionMigration(&config_);
migrate_idle_session_ = true;
Initialize();
EXPECT_TRUE(session_->config()->DisableConnectionMigration());
EXPECT_EQ(session_->TimeSinceLastStreamClose(),
QuicTimeDelta::FromSeconds(1));
migration_manager_->MaybeStartMigrateSessionOnWriteError(/*error_code=*/111);
// An alarm should have been scheduled to run pending callbacks.
QuicAlarm* pending_callbacks_alarm =
QuicConnectionMigrationManagerPeer::GetRunPendingCallbacksAlarm(
migration_manager_);
EXPECT_TRUE(pending_callbacks_alarm->IsSet());
// Run pending callbacks should actually attempt migration and close the
// connection since migration is disabled.
EXPECT_CALL(*connection_,
CloseConnection(QUIC_CONNECTION_MIGRATION_DISABLED_BY_CONFIG,
"Unrecoverable write error",
ConnectionCloseBehavior::SILENT_CLOSE));
alarm_factory_.FireAlarm(pending_callbacks_alarm);
}
// 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_EQ(session_->TimeSinceLastStreamClose(),
QuicTimeDelta::FromSeconds(1));
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));
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));
// 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([&, 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(
PathValidationFailure::Reason::kUnknown);
// 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);
}
// The connection should have been idle for longer than the idle migration
// period. Next attempt to migrate back will close the connection.
EXPECT_GT(session_->TimeSinceLastStreamClose(),
migration_config_.idle_migration_period);
// 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([&, 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(
PathValidationFailure::Reason::kUnknown);
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([&, 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(
[&, 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());
}
// This test verifies that the connection migrates to the alternate network
// early when path degrading is detected.
TEST_P(QuicConnectionMigrationManagerTest, MigrateEarlyOnPathDegrading) {
Initialize();
session_->CreateOutgoingBidirectionalStream();
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);
// Upon path degrading, the migration manager should probe an alternative
// network.
QuicPathFrameBuffer path_frame_payload;
EXPECT_CALL(*session_, PrepareForProbingOnPath(_));
EXPECT_CALL(*connection_, SendPathChallenge(_, _, _, _, _))
.WillOnce([&, this](const QuicPathFrameBuffer& data_buffer,
const QuicSocketAddress& self_address,
const QuicSocketAddress& peer_address,
const QuicSocketAddress& effective_peer_address,
QuicPacketWriter* writer) {
path_frame_payload = data_buffer;
EXPECT_EQ(peer_address, connection_->peer_address());
// self address and writer used for probing should be for the
// alternate network.
EXPECT_EQ(self_address, alternate_self_address);
EXPECT_NE(writer, connection_->writer());
return true;
});
connection_->OnPathDegradingDetected();
EXPECT_EQ(path_context_factory_->num_creation_attempts(), 1u);
// Make path validation succeeds and the connection should be migrated to the
// alternate network.
QuicConnectionPeer::SetLastPacketDestinationAddress(connection_,
alternate_self_address);
const QuicPathResponseFrame path_response(0, path_frame_payload);
EXPECT_CALL(*session_, ResetNonMigratableStreams());
EXPECT_CALL(*session_, PrepareForMigrationToPath(_)).WillOnce(Return(true));
EXPECT_CALL(*session_, OnMigrationToPathDone(_, true));
connection_->ReallyOnPathResponseFrame(path_response);
EXPECT_EQ(migration_manager_->current_network(), alternate_network);
EXPECT_EQ(connection_->self_address(), alternate_self_address);
// 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));
// Notify the manager the alternate network has become default,
// this will cancel migrate back to default network timer.
migration_manager_->OnNetworkMadeDefault(alternate_network);
EXPECT_EQ(migration_manager_->default_network(), alternate_network);
EXPECT_FALSE(migrate_back_alarm->IsSet());
}
// This test verifies that the connection only migrates limited times to the
// alternate network from the default network when path degrading is detected
// for each default network.
TEST_P(QuicConnectionMigrationManagerTest,
MigrationOnPathDegradingHitMaxLimit) {
// Only allow one migration to non-default network on path degrading from the
// same default network.
migration_config_.max_migrations_to_non_default_network_on_path_degrading = 1;
Initialize();
session_->CreateOutgoingBidirectionalStream();
QuicSocketAddress self_address = connection_->self_address();
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);
// Upon path degrading, the migration manager should probe an alternative
// network.
QuicPathFrameBuffer path_frame_payload;
EXPECT_CALL(*session_, PrepareForProbingOnPath(_));
EXPECT_CALL(*connection_, SendPathChallenge(_, _, _, _, _))
.WillOnce([&, this](const QuicPathFrameBuffer& data_buffer,
const QuicSocketAddress& self_address,
const QuicSocketAddress& peer_address,
const QuicSocketAddress& effective_peer_address,
QuicPacketWriter* writer) {
path_frame_payload = data_buffer;
EXPECT_EQ(peer_address, connection_->peer_address());
// self address and writer used for probing should be for the
// alternate network.
EXPECT_EQ(self_address, alternate_self_address);
EXPECT_NE(writer, connection_->writer());
return true;
});
connection_->OnPathDegradingDetected();
EXPECT_EQ(path_context_factory_->num_creation_attempts(), 1u);
// Make path validation succeeds and the connection should be migrated to the
// alternate network.
QuicConnectionPeer::SetLastPacketDestinationAddress(connection_,
alternate_self_address);
const QuicPathResponseFrame path_response(0, path_frame_payload);
EXPECT_CALL(*session_, ResetNonMigratableStreams());
EXPECT_CALL(*session_, PrepareForMigrationToPath(_)).WillOnce(Return(true));
EXPECT_CALL(*session_, OnMigrationToPathDone(_, true));
connection_->ReallyOnPathResponseFrame(path_response);
EXPECT_EQ(migration_manager_->current_network(), alternate_network);
EXPECT_EQ(connection_->self_address(), alternate_self_address);
// 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));
// 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);
QuicSocketAddress self_address2(self_address.host(), kTestPort + 1);
path_context_factory_->SetSelfAddressForNetwork(initial_network_,
self_address2);
// Advance the clock to trigger the migrate back alarm.
QuicPathFrameBuffer path_frame_payload2;
EXPECT_CALL(*session_, PrepareForProbingOnPath(_));
EXPECT_CALL(*connection_, SendPathChallenge(_, _, _, _, _))
.WillOnce([&, this](const QuicPathFrameBuffer& data_buffer,
const QuicSocketAddress& self_address,
const QuicSocketAddress& peer_address,
const QuicSocketAddress& effective_peer_address,
QuicPacketWriter* writer) {
path_frame_payload2 = data_buffer;
EXPECT_EQ(peer_address, connection_->peer_address());
EXPECT_EQ(self_address2, self_address);
EXPECT_NE(writer, connection_->writer());
return true;
});
connection_helper_.GetClock()->AdvanceTime(QuicTimeDelta::FromSeconds(1));
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 back to
// the default network.
QuicConnectionPeer::SetLastPacketDestinationAddress(connection_,
self_address2);
const QuicPathResponseFrame path_response2(0, path_frame_payload);
EXPECT_CALL(*session_, ResetNonMigratableStreams());
EXPECT_CALL(*session_, PrepareForMigrationToPath(_)).WillOnce(Return(true));
EXPECT_CALL(*session_, OnMigrationToPathDone(_, true));
connection_->ReallyOnPathResponseFrame(path_response2);
EXPECT_EQ(migration_manager_->current_network(), initial_network_);
EXPECT_EQ(connection_->self_address(), self_address2);
EXPECT_FALSE(migrate_back_alarm->IsSet());
// Max migrations to non-default network is reached on the initial network.
// The migration manager should not start probing when path degrading
// is detected again.
EXPECT_CALL(*session_, PrepareForProbingOnPath(_)).Times(0);
connection_->OnPathDegradingDetected();
EXPECT_EQ(path_context_factory_->num_creation_attempts(), 2u);
}
// This test verifies that the connection migrates to the alternate port when
// path degrading is detected but connection migration is disabled. And such
// migration is only allowed 4 times.
TEST_P(QuicConnectionMigrationManagerTest,
MigrateToDifferentPortOnPathDegrading) {
connection_migration_on_network_change_ = false;
port_migration_ = true;
Initialize();
session_->CreateOutgoingBidirectionalStream();
// The first 4 path degrading events should trigger migration to different
// ports on the same network.
for (size_t i = 0; i < 4; ++i) {
QuicSocketAddress alternate_self_address =
QuicSocketAddress(connection_->self_address().host(),
/*port=*/connection_->self_address().port() + 1);
path_context_factory_->SetSelfAddressForNetwork(initial_network_,
alternate_self_address);
// Upon path degrading, the migration manager should probe a different port
// on the same network.
QuicPathFrameBuffer path_frame_payload;
EXPECT_CALL(*session_, PrepareForProbingOnPath(_));
EXPECT_CALL(*connection_, SendPathChallenge(_, _, _, _, _))
.WillOnce([&, this](const QuicPathFrameBuffer& data_buffer,
const QuicSocketAddress& self_address,
const QuicSocketAddress& peer_address,
const QuicSocketAddress& effective_peer_address,
QuicPacketWriter* writer) {
path_frame_payload = data_buffer;
EXPECT_EQ(peer_address, connection_->peer_address());
// self address and writer used for probing should be for a
// different port.
EXPECT_EQ(self_address, alternate_self_address);
EXPECT_NE(writer, connection_->writer());
return true;
});
connection_->OnPathDegradingDetected();
EXPECT_EQ(path_context_factory_->num_creation_attempts(), i + 1);
// Make path validation succeeds and the connection should be migrated to
// the alternate network.
QuicConnectionPeer::SetLastPacketDestinationAddress(connection_,
alternate_self_address);
const QuicPathResponseFrame path_response(0, path_frame_payload);
// No need to reset non-migratable streams before migrating to a different
// port.
EXPECT_CALL(*session_, ResetNonMigratableStreams()).Times(0);
EXPECT_CALL(*session_, PrepareForMigrationToPath(_)).WillOnce(Return(true));
EXPECT_CALL(*session_, OnMigrationToPathDone(_, true));
connection_->ReallyOnPathResponseFrame(path_response);
// The network should not change.
EXPECT_EQ(migration_manager_->current_network(), initial_network_);
EXPECT_EQ(connection_->self_address(), alternate_self_address);
QuicAlarm* migrate_back_alarm =
QuicConnectionMigrationManagerPeer::GetMigrateBackToDefaultTimer(
migration_manager_);
EXPECT_FALSE(migrate_back_alarm->IsSet());
// Retire the old CID and prepare a new CID for the next path degrading.
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)))
.Times(testing::AtMost(1));
alarm_factory_.FireAlarm(retire_cid_alarm);
QuicNewConnectionIdFrame frame;
frame.connection_id = test::TestConnectionId(5678 + i);
ASSERT_NE(frame.connection_id, connection_->connection_id());
frame.stateless_reset_token =
QuicUtils::GenerateStatelessResetToken(frame.connection_id);
frame.retire_prior_to = 1u + i;
frame.sequence_number = 2u + i;
connection_->OnNewConnectionIdFrame(frame);
}
// The 5th path degrading should not trigger migration to a different port.
connection_->OnPathDegradingDetected();
EXPECT_EQ(path_context_factory_->num_creation_attempts(), 4u);
}
// This test verifies that the connection migrates to the alternate network
// when the alternate network is connected after path has been degrading.
TEST_P(QuicConnectionMigrationManagerTest,
MigrateOnNewNetworkConnectAfterPathDegrading) {
Initialize();
session_->CreateOutgoingBidirectionalStream();
// Path degrading failed to start migration because of lack of alternative
// network.
connection_->OnPathDegradingDetected();
EXPECT_EQ(path_context_factory_->num_creation_attempts(), 0u);
EXPECT_TRUE(connection_->IsPathDegrading());
// When a new network become available, the migration manager should probe it.
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);
QuicPathFrameBuffer path_frame_payload;
EXPECT_CALL(*session_, PrepareForProbingOnPath(_));
EXPECT_CALL(*connection_, SendPathChallenge(_, _, _, _, _))
.WillOnce([&, this](const QuicPathFrameBuffer& data_buffer,
const QuicSocketAddress& self_address,
const QuicSocketAddress& peer_address,
const QuicSocketAddress& effective_peer_address,
QuicPacketWriter* writer) {
path_frame_payload = data_buffer;
EXPECT_EQ(peer_address, connection_->peer_address());
// self address and writer used for probing should be for the
// alternate network.
EXPECT_EQ(self_address, alternate_self_address);
EXPECT_NE(writer, connection_->writer());
return true;
});
migration_manager_->OnNetworkConnected(alternate_network);
EXPECT_EQ(path_context_factory_->num_creation_attempts(), 1u);
// Make path validation succeeds and the connection should be migrated to the
// alternate network.
QuicConnectionPeer::SetLastPacketDestinationAddress(connection_,
alternate_self_address);
const QuicPathResponseFrame path_response(0, path_frame_payload);
EXPECT_CALL(*session_, ResetNonMigratableStreams());
EXPECT_CALL(*session_, PrepareForMigrationToPath(_)).WillOnce(Return(true));
EXPECT_CALL(*session_, OnMigrationToPathDone(_, true));
connection_->ReallyOnPathResponseFrame(path_response);
EXPECT_EQ(migration_manager_->current_network(), alternate_network);
EXPECT_EQ(connection_->self_address(), alternate_self_address);
EXPECT_FALSE(connection_->IsPathDegrading());
// 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));
// Notify the manager the alternate network has become default,
// this will cancel migrate back to default network timer.
migration_manager_->OnNetworkMadeDefault(alternate_network);
EXPECT_EQ(migration_manager_->default_network(), alternate_network);
EXPECT_FALSE(migrate_back_alarm->IsSet());
}
// This test verifies that when a write error occurs and there is no new
// network, the migration manager will wait for a new network to become
// available and then migrate to it.
TEST_P(QuicConnectionMigrationManagerTest,
AsyncMigrationAttemptOnWriteErrorButNoNewNetwork) {
Initialize();
session_->CreateOutgoingBidirectionalStream();
// Migration attempt should be made asynchronously.
migration_manager_->MaybeStartMigrateSessionOnWriteError(123);
QuicAlarm* pending_callbacks_alarm =
QuicConnectionMigrationManagerPeer::GetRunPendingCallbacksAlarm(
migration_manager_);
EXPECT_EQ(pending_callbacks_alarm->deadline(),
connection_helper_.GetClock()->Now());
// No alternative network available, an alarm should have been scheduled to
// wait for any new network.
EXPECT_CALL(*session_, OnNoNewNetworkForMigration());
alarm_factory_.FireAlarm(pending_callbacks_alarm);
QuicAlarm* migration_alarm =
QuicConnectionMigrationManagerPeer::GetWaitForMigrationAlarm(
migration_manager_);
EXPECT_TRUE(migration_alarm->IsSet());
// Simulate a new network becomes available and migrate to it.
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_, ResetNonMigratableStreams());
EXPECT_CALL(*session_, PrepareForMigrationToPath(_)).WillOnce(Return(true));
EXPECT_CALL(*session_, OnMigrationToPathDone(_, true));
migration_manager_->OnNetworkConnected(alternate_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_FALSE(migration_alarm->IsSet());
}
// This test verifies that session is not marked as going away after connection
// migration on write error and migrate back to default network logic is applied
// to bring the migrated session back to the default network. Migration signals
// delivered in the following order (alternate network is always available):
// - session on the default network encountered a write error;
// - session successfully migrated to the non-default network;
// - session attempts to migrate back to default network post migration;
// - migration back to the default network is successful.
TEST_P(QuicConnectionMigrationManagerTest,
AsyncMigrationOnWriteErrorAndMigrateBack) {
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);
path_context_factory_->SetSelfAddressForNetwork(alternate_network,
alternate_self_address);
// Create a stream to make the session non-idle.
session_->CreateOutgoingBidirectionalStream();
// Migration attempt should be made asynchronously via an alarm scheduled for
// next event loop.
EXPECT_TRUE(migration_manager_->MaybeStartMigrateSessionOnWriteError(123));
QuicAlarm* pending_callbacks_alarm =
QuicConnectionMigrationManagerPeer::GetRunPendingCallbacksAlarm(
migration_manager_);
EXPECT_TRUE(pending_callbacks_alarm->IsSet());
QuicSocketAddress self_address = connection_->self_address();
// Migrate to alternate network immediately.
EXPECT_CALL(*session_, ResetNonMigratableStreams());
EXPECT_CALL(*session_, PrepareForMigrationToPath(_)).WillOnce(Return(true));
EXPECT_CALL(*session_, OnMigrationToPathDone(_, true));
alarm_factory_.FireAlarm(pending_callbacks_alarm);
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(), initial_network_);
QuicSocketAddress self_address2(self_address.host(), kTestPort + 1);
path_context_factory_->SetSelfAddressForNetwork(initial_network_,
self_address2);
// Fire the alarm to migrate back to default network, starting with probing.
QuicPathFrameBuffer path_frame_payload;
EXPECT_CALL(*session_, PrepareForProbingOnPath(_));
EXPECT_CALL(*connection_, SendPathChallenge(_, _, _, _, _))
.WillOnce([&, 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;
});
connection_helper_.GetClock()->AdvanceTime(QuicTimeDelta::FromSeconds(1));
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_, 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());
}
TEST_P(QuicConnectionMigrationManagerTest, MigrationToServerPreferredAddress) {
if (!version().IsIetfQuic()) {
return;
}
complete_handshake_ = false;
Initialize();
// A new port will be used to probing to the server preferred address.
QuicSocketAddress self_address2(QuicIpAddress::Loopback4(), kTestPort + 10);
path_context_factory_->SetSelfAddressForNetwork(initial_network_,
self_address2);
// Upon handshake completion, probing to the server preferred address should
// be started.
EXPECT_CALL(*session_, PrepareForProbingOnPath(_));
QuicPathFrameBuffer path_frame_payload;
EXPECT_CALL(*connection_, SendPathChallenge(_, _, _, _, _))
.WillOnce([&, this](const QuicPathFrameBuffer& data_buffer,
const QuicSocketAddress& self_address,
const QuicSocketAddress& peer_address,
const QuicSocketAddress& /*effective_peer_address*/,
QuicPacketWriter* writer) {
path_frame_payload = data_buffer;
EXPECT_NE(peer_address, connection_->peer_address());
// self address and writer used for probing should be for the
// default network.
EXPECT_EQ(self_address, QuicSocketAddress(QuicIpAddress::Loopback4(),
kTestPort + 10));
EXPECT_NE(writer, connection_->writer());
return true;
});
CompleteHandshake(/*received_server_preferred_address=*/true);
// Make path validation succeeds and the connection should start using the
// server preferred address.
QuicConnectionPeer::SetLastPacketDestinationAddress(connection_,
self_address2);
const QuicPathResponseFrame path_response(0, path_frame_payload);
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_EQ(connection_->peer_address().ToString(), "127.0.0.2:12345");
}
// This test verifies that if the max number of migrations is reached
// on write error, the session will be closed.
TEST_P(QuicConnectionMigrationManagerTest,
AsyncMigrationOnWriteErrorMaxAttemptsReached) {
migration_config_.max_migrations_to_non_default_network_on_write_error = 1;
Initialize();
session_->CreateOutgoingBidirectionalStream();
// Set up an alternate network and migrate to it on write error.
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);
path_context_factory_->SetSelfAddressForNetwork(alternate_network,
alternate_self_address);
EXPECT_TRUE(migration_manager_->MaybeStartMigrateSessionOnWriteError(123));
QuicAlarm* pending_callbacks_alarm =
QuicConnectionMigrationManagerPeer::GetRunPendingCallbacksAlarm(
migration_manager_);
EXPECT_TRUE(pending_callbacks_alarm->IsSet());
EXPECT_CALL(*session_, ResetNonMigratableStreams());
EXPECT_CALL(*session_, PrepareForMigrationToPath(_)).WillOnce(Return(true));
EXPECT_CALL(*session_, OnMigrationToPathDone(_, true));
alarm_factory_.FireAlarm(pending_callbacks_alarm);
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);
// 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());
// 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);
// Migrate back to the default network.
QuicSocketAddress self_address2(QuicIpAddress::Loopback4(), kTestPort + 1);
path_context_factory_->SetSelfAddressForNetwork(initial_network_,
self_address2);
QuicPathFrameBuffer path_frame_payload;
EXPECT_CALL(*session_, PrepareForProbingOnPath(_));
EXPECT_CALL(*connection_, SendPathChallenge(_, _, _, _, _))
.WillOnce([&, 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());
EXPECT_EQ(new_self_address.host(), self_address2.host());
EXPECT_NE(writer, connection_->writer());
return true;
});
connection_helper_.GetClock()->AdvanceTime(QuicTimeDelta::FromSeconds(1));
alarm_factory_.FireAlarm(migrate_back_alarm);
EXPECT_EQ(path_context_factory_->num_creation_attempts(), 2u);
QuicConnectionPeer::SetLastPacketDestinationAddress(connection_,
self_address2);
const QuicPathResponseFrame path_response(0, path_frame_payload);
EXPECT_CALL(*session_, ResetNonMigratableStreams());
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());
// Max migrations on write error is reached.
// The migration manager should not start migration but close the connection.
EXPECT_TRUE(migration_manager_->MaybeStartMigrateSessionOnWriteError(456));
pending_callbacks_alarm =
QuicConnectionMigrationManagerPeer::GetRunPendingCallbacksAlarm(
migration_manager_);
EXPECT_TRUE(pending_callbacks_alarm->IsSet());
EXPECT_CALL(*connection_,
CloseConnection(
QUIC_PACKET_WRITE_ERROR,
"Too many migrations for write error for the same network",
ConnectionCloseBehavior::SILENT_CLOSE));
alarm_factory_.FireAlarm(pending_callbacks_alarm);
}
class QuicConnectionMigrationManagerGoogleQuicTest
: public QuicConnectionMigrationManagerTest {};
INSTANTIATE_TEST_SUITE_P(QuicConnectionMigrationManagerGoogleQuicTests,
QuicConnectionMigrationManagerGoogleQuicTest,
::testing::ValuesIn(ParsedQuicVersionVector{
quic::ParsedQuicVersion::Q046()}),
::testing::PrintToStringParamName());
TEST_P(QuicConnectionMigrationManagerGoogleQuicTest, NoMigrationForGoogleQuic) {
Initialize();
session_->set_alternate_network(-1);
EXPECT_FALSE(migration_manager_->MaybeStartMigrateSessionOnWriteError(111));
// If the session has attempted to migrate, it would have found no alternate
// network and called OnNoNewNetworkForMigration().
EXPECT_CALL(*session_, OnNoNewNetworkForMigration()).Times(0);
migration_manager_->OnPathDegrading();
migration_manager_->OnNetworkDisconnected(initial_network_);
}
class QuicSpdyClientSessionWithMigrationTest
: public QuicConnectionMigrationManagerTest {};
INSTANTIATE_TEST_SUITE_P(QuicSpdyClientSessionWithMigrationTests,
QuicSpdyClientSessionWithMigrationTest,
::testing::ValuesIn(CurrentSupportedHttp3Versions()),
::testing::PrintToStringParamName());
TEST_P(QuicSpdyClientSessionWithMigrationTest,
SessionFailedToPrepareForMigration) {
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_EQ(session_->TimeSinceLastStreamClose(),
QuicTimeDelta::FromSeconds(1));
EXPECT_CALL(*session_, ResetNonMigratableStreams());
// Session failed to prepare for migration. Migration should not be attempted.
EXPECT_CALL(*session_, PrepareForMigrationToPath(_)).WillOnce(Return(false));
EXPECT_CALL(*connection_,
CloseConnection(QUIC_CONNECTION_MIGRATION_INTERNAL_ERROR,
"Session failed to migrate to new path.",
ConnectionCloseBehavior::SILENT_CLOSE));
migration_manager_->OnNetworkDisconnected(initial_network_);
EXPECT_EQ(migration_manager_->current_network(), default_network_);
EXPECT_EQ(path_context_factory_->num_creation_attempts(), 1u);
}
TEST_P(QuicSpdyClientSessionWithMigrationTest,
MaybeMitigateWriteErrorWithDifferentWriteStatus) {
migrate_idle_session_ = true;
Initialize();
// These write status and error code combination won't trigger migration.
EXPECT_FALSE(session_->MaybeMitigateWriteError(
WriteResult(WRITE_STATUS_MSG_TOO_BIG, 123)));
EXPECT_FALSE(session_->MaybeMitigateWriteError(
WriteResult(WRITE_STATUS_FAILED_TO_COALESCE_PACKET, 123)));
EXPECT_FALSE(session_->MaybeMitigateWriteError(
WriteResult(WRITE_STATUS_ERROR, kSocketErrorMsgSize)));
// `WRITE_STATUS_ERROR` with non-message-too-big error code should trigger
// migration.
EXPECT_TRUE(
session_->MaybeMitigateWriteError(WriteResult(WRITE_STATUS_ERROR, 123)));
QuicAlarm* pending_callbacks_alarm =
QuicConnectionMigrationManagerPeer::GetRunPendingCallbacksAlarm(
migration_manager_);
EXPECT_EQ(pending_callbacks_alarm->deadline(),
connection_helper_.GetClock()->Now());
// No alternative network available, an alarm should have been scheduled to
// wait for any new network.
EXPECT_CALL(*session_, OnNoNewNetworkForMigration());
alarm_factory_.FireAlarm(pending_callbacks_alarm);
QuicAlarm* migration_alarm =
QuicConnectionMigrationManagerPeer::GetWaitForMigrationAlarm(
migration_manager_);
EXPECT_TRUE(migration_alarm->IsSet());
}
} // namespace quic::test