Finch protect QUIC connection migration for idle sessions with "migrate_idle_sessions".

Bug: 929351
Change-Id: I452cb2c5e393568e65ce0cca80da26a60d5dd889
Reviewed-on: https://chromium-review.googlesource.com/c/1490410
Reviewed-by: Ryan Hamilton <[email protected]>
Reviewed-by: Misha Efimov <[email protected]>
Commit-Queue: Zhongyi Shi <[email protected]>
Cr-Commit-Position: refs/heads/master@{#636221}
diff --git a/net/quic/bidirectional_stream_quic_impl_unittest.cc b/net/quic/bidirectional_stream_quic_impl_unittest.cc
index b5737a3..b90b26b 100644
--- a/net/quic/bidirectional_stream_quic_impl_unittest.cc
+++ b/net/quic/bidirectional_stream_quic_impl_unittest.cc
@@ -516,6 +516,7 @@
         /*default_network=*/NetworkChangeNotifier::kInvalidNetworkHandle,
         quic::QuicTime::Delta::FromMilliseconds(
             kDefaultRetransmittableOnWireTimeoutMillisecs),
+        /*migrate_idle_session=*/false,
         base::TimeDelta::FromSeconds(kDefaultIdleSessionMigrationPeriodSeconds),
         base::TimeDelta::FromSeconds(kMaxTimeOnNonDefaultNetworkSecs),
         kMaxMigrationsToNonDefaultNetworkOnWriteError,
diff --git a/net/quic/quic_chromium_client_session.cc b/net/quic/quic_chromium_client_session.cc
index 8f8c0ffa..592054bc8 100644
--- a/net/quic/quic_chromium_client_session.cc
+++ b/net/quic/quic_chromium_client_session.cc
@@ -679,6 +679,7 @@
     bool migrate_sessions_on_network_change_v2,
     NetworkChangeNotifier::NetworkHandle default_network,
     quic::QuicTime::Delta retransmittable_on_wire_timeout,
+    bool migrate_idle_session,
     base::TimeDelta idle_migration_period,
     base::TimeDelta max_time_on_non_default_network,
     int max_migrations_to_non_default_network_on_write_error,
@@ -708,6 +709,7 @@
       migrate_session_early_v2_(migrate_session_early_v2),
       migrate_session_on_network_change_v2_(
           migrate_sessions_on_network_change_v2),
+      migrate_idle_session_(migrate_idle_session),
       idle_migration_period_(idle_migration_period),
       max_time_on_non_default_network_(max_time_on_non_default_network),
       max_migrations_to_non_default_network_on_write_error_(
@@ -1804,9 +1806,18 @@
 
   current_connection_migration_cause_ = ON_WRITE_ERROR;
 
-  if (CheckIdleTimeExceedsIdleMigrationPeriod())
+  if (migrate_idle_session_ && CheckIdleTimeExceedsIdleMigrationPeriod())
     return;
 
+  if (!migrate_idle_session_ && GetNumActiveStreams() == 0 &&
+      GetNumDrainingStreams() == 0) {
+    // connection close packet to be sent since socket may be borked.
+    connection()->CloseConnection(quic::QUIC_PACKET_WRITE_ERROR,
+                                  "Write error for non-migratable session",
+                                  quic::ConnectionCloseBehavior::SILENT_CLOSE);
+    return;
+  }
+
   // Do not migrate if connection migration is disabled.
   if (config()->DisableConnectionMigration()) {
     HistogramAndLogMigrationFailure(
@@ -1958,7 +1969,17 @@
   // Close streams that are not migratable to the probed |network|.
   ResetNonMigratableStreams();
 
-  if (CheckIdleTimeExceedsIdleMigrationPeriod())
+  if (!migrate_idle_session_ && GetNumActiveStreams() == 0 &&
+      GetNumDrainingStreams() == 0) {
+    // If idle sessions won't be migrated, close the connection.
+    CloseSessionOnErrorLater(
+        ERR_NETWORK_CHANGED,
+        quic::QUIC_CONNECTION_MIGRATION_NO_MIGRATABLE_STREAMS,
+        quic::ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+    return;
+  }
+
+  if (migrate_idle_session_ && CheckIdleTimeExceedsIdleMigrationPeriod())
     return;
 
   // Migrate to the probed socket immediately: socket, writer and reader will
@@ -2144,7 +2165,19 @@
   // - otherwise, it's brought to default network, cancel the running timer to
   //   migrate back.
 
-  if (CheckIdleTimeExceedsIdleMigrationPeriod())
+  if (!migrate_idle_session_ && GetNumActiveStreams() == 0 &&
+      GetNumDrainingStreams() == 0) {
+    HistogramAndLogMigrationFailure(net_log_,
+                                    MIGRATION_STATUS_NO_MIGRATABLE_STREAMS,
+                                    connection_id(), "No active streams");
+    CloseSessionOnErrorLater(
+        ERR_NETWORK_CHANGED,
+        quic::QUIC_CONNECTION_MIGRATION_NO_MIGRATABLE_STREAMS,
+        quic::ConnectionCloseBehavior::SILENT_CLOSE);
+    return;
+  }
+
+  if (migrate_idle_session_ && CheckIdleTimeExceedsIdleMigrationPeriod())
     return;
 
   // Do not migrate if connection migration is disabled.
@@ -2428,7 +2461,19 @@
 
   CHECK_NE(NetworkChangeNotifier::kInvalidNetworkHandle, network);
 
-  if (CheckIdleTimeExceedsIdleMigrationPeriod())
+  if (!migrate_idle_session_ && GetNumActiveStreams() == 0 &&
+      GetNumDrainingStreams() == 0) {
+    HistogramAndLogMigrationFailure(migration_net_log,
+                                    MIGRATION_STATUS_NO_MIGRATABLE_STREAMS,
+                                    connection_id(), "No active streams");
+    CloseSessionOnErrorLater(
+        ERR_NETWORK_CHANGED,
+        quic::QUIC_CONNECTION_MIGRATION_NO_MIGRATABLE_STREAMS,
+        quic::ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+    return ProbingResult::DISABLED_WITH_IDLE_SESSION;
+  }
+
+  if (migrate_idle_session_ && CheckIdleTimeExceedsIdleMigrationPeriod())
     return ProbingResult::DISABLED_WITH_IDLE_SESSION;
 
   // Abort probing if connection migration is disabled by config.
@@ -2554,6 +2599,9 @@
 }
 
 bool QuicChromiumClientSession::CheckIdleTimeExceedsIdleMigrationPeriod() {
+  if (!migrate_idle_session_)
+    return false;
+
   if (GetNumActiveStreams() != 0 || GetNumDrainingStreams() != 0) {
     return false;
   }
@@ -2824,6 +2872,17 @@
   if (network != NetworkChangeNotifier::kInvalidNetworkHandle) {
     // This is a migration attempt from connection migration.
     ResetNonMigratableStreams();
+    if (!migrate_idle_session_ && GetNumActiveStreams() == 0 &&
+        GetNumDrainingStreams() == 0) {
+      // If idle sessions can not be migrated, close the session if needed.
+      if (close_session_on_error) {
+        CloseSessionOnErrorLater(
+            ERR_NETWORK_CHANGED,
+            quic::QUIC_CONNECTION_MIGRATION_NO_MIGRATABLE_STREAMS,
+            quic::ConnectionCloseBehavior::SILENT_CLOSE);
+      }
+      return MigrationResult::FAILURE;
+    }
   }
 
   // Create and configure socket on |network|.
diff --git a/net/quic/quic_chromium_client_session.h b/net/quic/quic_chromium_client_session.h
index bfcc5f9..658e96f 100644
--- a/net/quic/quic_chromium_client_session.h
+++ b/net/quic/quic_chromium_client_session.h
@@ -377,6 +377,7 @@
       bool migrate_session_on_network_change_v2,
       NetworkChangeNotifier::NetworkHandle default_network,
       quic::QuicTime::Delta retransmittable_on_wire_timeout,
+      bool migrate_idle_session,
       base::TimeDelta idle_migration_period,
       base::TimeDelta max_time_on_non_default_network,
       int max_migrations_to_non_default_network_on_write_error,
@@ -714,8 +715,10 @@
   void TryMigrateBackToDefaultNetwork(base::TimeDelta timeout);
   void MaybeRetryMigrateBackToDefaultNetwork();
 
-  // Returns true and post a task to close the connection if session's
-  // idle time exceeds the |idle_migration_period_|.
+  // If migrate idle session is enabled, returns true and post a task to close
+  // the connection if session's idle time exceeds the |idle_migration_period_|.
+  // If migrate idle session is not enabled, returns true and posts a task to
+  // close the connection if session doesn't have outstanding streams.
   bool CheckIdleTimeExceedsIdleMigrationPeriod();
 
   // Close non-migratable streams in both directions by sending reset stream to
@@ -750,6 +753,7 @@
   bool require_confirmation_;
   bool migrate_session_early_v2_;
   bool migrate_session_on_network_change_v2_;
+  bool migrate_idle_session_;
   // Session can be migrated if its idle time is within this period.
   base::TimeDelta idle_migration_period_;
   base::TimeDelta max_time_on_non_default_network_;
diff --git a/net/quic/quic_chromium_client_session_test.cc b/net/quic/quic_chromium_client_session_test.cc
index d3e28d6c..2031c63 100644
--- a/net/quic/quic_chromium_client_session_test.cc
+++ b/net/quic/quic_chromium_client_session_test.cc
@@ -165,6 +165,7 @@
         /*defaulet_network=*/NetworkChangeNotifier::kInvalidNetworkHandle,
         quic::QuicTime::Delta::FromMilliseconds(
             kDefaultRetransmittableOnWireTimeoutMillisecs),
+        /*migrate_idle_session=*/false,
         base::TimeDelta::FromSeconds(kDefaultIdleSessionMigrationPeriodSeconds),
         base::TimeDelta::FromSeconds(kMaxTimeOnNonDefaultNetworkSecs),
         kMaxMigrationsToNonDefaultNetworkOnWriteError,
diff --git a/net/quic/quic_http_stream_test.cc b/net/quic/quic_http_stream_test.cc
index 1d14ef04..a48f5a8 100644
--- a/net/quic/quic_http_stream_test.cc
+++ b/net/quic/quic_http_stream_test.cc
@@ -326,6 +326,7 @@
         /*default_network=*/NetworkChangeNotifier::kInvalidNetworkHandle,
         quic::QuicTime::Delta::FromMilliseconds(
             kDefaultRetransmittableOnWireTimeoutMillisecs),
+        /*migrate_idle_session=*/false,
         base::TimeDelta::FromSeconds(kDefaultIdleSessionMigrationPeriodSeconds),
         base::TimeDelta::FromSeconds(kMaxTimeOnNonDefaultNetworkSecs),
         kMaxMigrationsToNonDefaultNetworkOnWriteError,
diff --git a/net/quic/quic_proxy_client_socket_unittest.cc b/net/quic/quic_proxy_client_socket_unittest.cc
index 0cb728b..ad30d277 100644
--- a/net/quic/quic_proxy_client_socket_unittest.cc
+++ b/net/quic/quic_proxy_client_socket_unittest.cc
@@ -216,6 +216,7 @@
         /*default_network=*/NetworkChangeNotifier::kInvalidNetworkHandle,
         quic::QuicTime::Delta::FromMilliseconds(
             kDefaultRetransmittableOnWireTimeoutMillisecs),
+        /*migrate_idle_session=*/true,
         base::TimeDelta::FromSeconds(kDefaultIdleSessionMigrationPeriodSeconds),
         base::TimeDelta::FromSeconds(kMaxTimeOnNonDefaultNetworkSecs),
         kMaxMigrationsToNonDefaultNetworkOnWriteError,
diff --git a/net/quic/quic_stream_factory.cc b/net/quic/quic_stream_factory.cc
index 5abe80f..ba11def3 100644
--- a/net/quic/quic_stream_factory.cc
+++ b/net/quic/quic_stream_factory.cc
@@ -1025,6 +1025,7 @@
     bool migrate_sessions_on_network_change_v2,
     bool migrate_sessions_early_v2,
     bool retry_on_alternate_network_before_handshake,
+    bool migrate_idle_sessions,
     base::TimeDelta idle_session_migration_period,
     base::TimeDelta max_time_on_non_default_network,
     int max_migrations_to_non_default_network_on_write_error,
@@ -1086,6 +1087,8 @@
           retry_on_alternate_network_before_handshake &&
           migrate_sessions_on_network_change_v2_),
       default_network_(NetworkChangeNotifier::kInvalidNetworkHandle),
+      migrate_idle_sessions_(migrate_idle_sessions &&
+                             migrate_sessions_on_network_change_v2_),
       idle_session_migration_period_(idle_session_migration_period),
       max_time_on_non_default_network_(max_time_on_non_default_network),
       max_migrations_to_non_default_network_on_write_error_(
@@ -1842,7 +1845,8 @@
       std::move(server_info), key.session_key(), require_confirmation,
       migrate_sessions_early_v2_, migrate_sessions_on_network_change_v2_,
       default_network_, retransmittable_on_wire_timeout_,
-      idle_session_migration_period_, max_time_on_non_default_network_,
+      migrate_idle_sessions_, idle_session_migration_period_,
+      max_time_on_non_default_network_,
       max_migrations_to_non_default_network_on_write_error_,
       max_migrations_to_non_default_network_on_path_degrading_,
       yield_after_packets_, yield_after_duration_, go_away_on_path_degrading_,
diff --git a/net/quic/quic_stream_factory.h b/net/quic/quic_stream_factory.h
index bd271be..0a64637 100644
--- a/net/quic/quic_stream_factory.h
+++ b/net/quic/quic_stream_factory.h
@@ -262,6 +262,7 @@
       bool migrate_sessions_on_network_change_v2,
       bool migrate_sessions_early_v2,
       bool retry_on_alternate_network_before_handshake,
+      bool migrate_idle_sessions,
       base::TimeDelta idle_session_migration_period,
       base::TimeDelta max_time_on_non_default_network,
       int max_migrations_to_non_default_network_on_write_error,
@@ -566,6 +567,10 @@
   // Otherwise, always set to NetworkChangeNotifier::kInvalidNetwork.
   NetworkChangeNotifier::NetworkHandle default_network_;
 
+  // Set if idle sessions can be migrated within
+  // |idle_session_migration_period_| when connection migration is triggered.
+  const bool migrate_idle_sessions_;
+
   // Sessions can migrate if they have been idle for less than this period.
   const base::TimeDelta idle_session_migration_period_;
 
diff --git a/net/quic/quic_stream_factory_fuzzer.cc b/net/quic/quic_stream_factory_fuzzer.cc
index 5c741443..8835927 100644
--- a/net/quic/quic_stream_factory_fuzzer.cc
+++ b/net/quic/quic_stream_factory_fuzzer.cc
@@ -106,6 +106,7 @@
   bool migrate_sessions_early_v2 = false;
   bool migrate_sessions_on_network_change_v2 = false;
   bool retry_on_alternate_network_before_handshake = false;
+  bool migrate_idle_sessions = false;
   bool go_away_on_path_degrading = false;
 
   if (!close_sessions_on_ip_change) {
@@ -116,6 +117,7 @@
         migrate_sessions_early_v2 = data_provider.ConsumeBool();
         retry_on_alternate_network_before_handshake =
             data_provider.ConsumeBool();
+        migrate_idle_sessions = data_provider.ConsumeBool();
       }
     }
   }
@@ -138,7 +140,7 @@
           kDefaultRetransmittableOnWireTimeoutMillisecs,
           quic::kMaxTimeForCryptoHandshakeSecs, quic::kInitialIdleTimeoutSecs,
           migrate_sessions_on_network_change_v2, migrate_sessions_early_v2,
-          retry_on_alternate_network_before_handshake,
+          retry_on_alternate_network_before_handshake, migrate_idle_sessions,
           base::TimeDelta::FromSeconds(
               kDefaultIdleSessionMigrationPeriodSeconds),
           base::TimeDelta::FromSeconds(kMaxTimeOnNonDefaultNetworkSecs),
diff --git a/net/quic/quic_stream_factory_test.cc b/net/quic/quic_stream_factory_test.cc
index 87cf7f85..39ad1dd 100644
--- a/net/quic/quic_stream_factory_test.cc
+++ b/net/quic/quic_stream_factory_test.cc
@@ -282,6 +282,7 @@
         test_params_.quic_migrate_sessions_on_network_change_v2,
         test_params_.quic_migrate_sessions_early_v2,
         test_params_.quic_retry_on_alternate_network_before_handshake,
+        test_params_.quic_migrate_idle_sessions,
         test_params_.quic_idle_session_migration_period,
         test_params_.quic_max_time_on_non_default_network,
         test_params_.quic_max_migrations_to_non_default_network_on_write_error,
@@ -804,7 +805,8 @@
   void OnFailedOnDefaultNetwork(int rv) { failed_on_default_network_ = true; }
 
   // Helper methods for tests of connection migration on write error.
-  void TestMigrationOnWriteErrorNonMigratableStream(IoMode write_error_mode);
+  void TestMigrationOnWriteErrorNonMigratableStream(IoMode write_error_mode,
+                                                    bool migrate_idle_sessions);
   // Migratable stream triggers write error.
   void TestMigrationOnWriteErrorMixedStreams(IoMode write_error_mode);
   // Non-migratable stream triggers write error.
@@ -831,6 +833,11 @@
   void TestNoAlternateNetworkBeforeHandshake(quic::QuicErrorCode error);
   void TestNewConnectionOnAlternateNetworkBeforeHandshake(
       quic::QuicErrorCode error);
+  void TestOnNetworkMadeDefaultNonMigratableStream(bool migrate_idle_sessions);
+  void TestMigrateSessionEarlyNonMigratableStream(bool migrate_idle_sessions);
+  void TestOnNetworkDisconnectedNoOpenStreams(bool migrate_idle_sessions);
+  void TestOnNetworkMadeDefaultNoOpenStreams(bool migrate_idle_sessions);
+  void TestOnNetworkDisconnectedNonMigratableStream(bool migrate_idle_sessions);
 
   QuicFlagSaver flags_;  // Save/restore all QUIC flag values.
   std::unique_ptr<MockHostResolverBase> host_resolver_;
@@ -2683,7 +2690,23 @@
 // This test verifies that connectivity probes will be sent even if there is
 // a non-migratable stream. However, when connection migrates to the
 // successfully probed path, any non-migratable streams will be reset.
-TEST_P(QuicStreamFactoryTest, OnNetworkMadeDefaultNonMigratableStream) {
+TEST_P(QuicStreamFactoryTest,
+       OnNetworkMadeDefaultNonMigratableStream_MigrateIdleSessions) {
+  TestOnNetworkMadeDefaultNonMigratableStream(true);
+}
+
+// This test verifies that connectivity probes will be sent even if there is
+// a non-migratable stream. However, when connection migrates to the
+// successfully probed path, any non-migratable stream will be reset. And if
+// the connection becomes idle then, close the connection.
+TEST_P(QuicStreamFactoryTest,
+       OnNetworkMadeDefaultNonMigratableStream_DoNotMigrateIdleSessions) {
+  TestOnNetworkMadeDefaultNonMigratableStream(false);
+}
+
+void QuicStreamFactoryTestBase::TestOnNetworkMadeDefaultNonMigratableStream(
+    bool migrate_idle_sessions) {
+  test_params_.quic_migrate_idle_sessions = migrate_idle_sessions;
   InitializeConnectionMigrationV2Test(
       {kDefaultNetworkForTests, kNewNetworkForTests});
   ProofVerifyDetailsChromium verify_details = DefaultProofVerifyDetails();
@@ -2692,6 +2715,16 @@
   MockQuicData socket_data;
   socket_data.AddRead(SYNCHRONOUS, ERR_IO_PENDING);
   socket_data.AddWrite(SYNCHRONOUS, ConstructInitialSettingsPacket());
+  if (!migrate_idle_sessions) {
+    socket_data.AddWrite(
+        SYNCHRONOUS,
+        client_maker_.MakeRstAckAndConnectionClosePacket(
+            3, false, GetNthClientInitiatedBidirectionalStreamId(0),
+            quic::QUIC_STREAM_CANCELLED,
+            quic::QuicTime::Delta::FromMilliseconds(0), 1, 1, 1,
+            quic::QUIC_CONNECTION_MIGRATION_NO_MIGRATABLE_STREAMS,
+            "net error"));
+  }
 
   socket_data.AddSocketDataToFactory(socket_factory_.get());
 
@@ -2704,15 +2737,18 @@
   // Connectivity probe to receive from the server.
   quic_data1.AddRead(ASYNC,
                      server_maker_.MakeConnectivityProbingPacket(1, false));
-  quic_data1.AddRead(SYNCHRONOUS, ERR_IO_PENDING);  // Hanging read.
-  // A RESET will be sent to the peer to cancel the non-migratable stream.
-  quic_data1.AddWrite(
-      SYNCHRONOUS, client_maker_.MakeRstPacket(
-                       3, false, GetNthClientInitiatedBidirectionalStreamId(0),
-                       quic::QUIC_STREAM_CANCELLED));
-  // Ping packet to send after migration is completed.
-  quic_data1.AddWrite(SYNCHRONOUS,
-                      client_maker_.MakeAckAndPingPacket(4, false, 1, 1, 1));
+  if (migrate_idle_sessions) {
+    quic_data1.AddRead(SYNCHRONOUS, ERR_IO_PENDING);  // Hanging read.
+    // A RESET will be sent to the peer to cancel the non-migratable stream.
+    quic_data1.AddWrite(
+        SYNCHRONOUS,
+        client_maker_.MakeRstPacket(
+            3, false, GetNthClientInitiatedBidirectionalStreamId(0),
+            quic::QUIC_STREAM_CANCELLED));
+    // Ping packet to send after migration is completed.
+    quic_data1.AddWrite(SYNCHRONOUS,
+                        client_maker_.MakeAckAndPingPacket(4, false, 1, 1, 1));
+  }
   quic_data1.AddSocketDataToFactory(socket_factory_.get());
 
   // Create request and QuicHttpStream.
@@ -2754,7 +2790,7 @@
   // non-migtable streams to be closed.
   quic_data1.Resume();
   EXPECT_TRUE(QuicStreamFactoryPeer::IsLiveSession(factory_.get(), session));
-  EXPECT_TRUE(HasActiveSession(host_port_pair_));
+  EXPECT_EQ(migrate_idle_sessions, HasActiveSession(host_port_pair_));
   EXPECT_EQ(0u, session->GetNumActiveStreams());
 
   base::RunLoop().RunUntilIdle();
@@ -2824,31 +2860,53 @@
   EXPECT_TRUE(socket_data.AllWriteDataConsumed());
 }
 
-TEST_P(QuicStreamFactoryTest, OnNetworkDisconnectedNonMigratableStream) {
+TEST_P(QuicStreamFactoryTest,
+       OnNetworkDisconnectedNonMigratableStream_DoNotMigrateIdleSessions) {
+  TestOnNetworkDisconnectedNonMigratableStream(false);
+}
+
+TEST_P(QuicStreamFactoryTest,
+       OnNetworkDisconnectedNonMigratableStream_MigrateIdleSessions) {
+  TestOnNetworkDisconnectedNonMigratableStream(true);
+}
+
+void QuicStreamFactoryTestBase::TestOnNetworkDisconnectedNonMigratableStream(
+    bool migrate_idle_sessions) {
+  test_params_.quic_migrate_idle_sessions = migrate_idle_sessions;
   InitializeConnectionMigrationV2Test(
       {kDefaultNetworkForTests, kNewNetworkForTests});
   ProofVerifyDetailsChromium verify_details = DefaultProofVerifyDetails();
   crypto_client_stream_factory_.AddProofVerifyDetails(&verify_details);
 
-  quic::QuicStreamOffset header_stream_offset = 0;
   MockQuicData failed_socket_data;
-  failed_socket_data.AddRead(SYNCHRONOUS, ERR_IO_PENDING);
-  failed_socket_data.AddWrite(
-      SYNCHRONOUS, ConstructInitialSettingsPacket(1, &header_stream_offset));
-  // A RESET will be sent to the peer to cancel the non-migratable stream.
-  failed_socket_data.AddWrite(
-      SYNCHRONOUS, client_maker_.MakeRstPacket(
-                       2, true, GetNthClientInitiatedBidirectionalStreamId(0),
-                       quic::QUIC_STREAM_CANCELLED));
-  failed_socket_data.AddSocketDataToFactory(socket_factory_.get());
-
-  // Set up second socket data provider that is used after migration.
   MockQuicData socket_data;
-  socket_data.AddRead(SYNCHRONOUS, ERR_IO_PENDING);  // Hanging read.
-  // Ping packet to send after migration.
-  socket_data.AddWrite(
-      SYNCHRONOUS, client_maker_.MakePingPacket(3, /*include_version=*/true));
-  socket_data.AddSocketDataToFactory(socket_factory_.get());
+  if (migrate_idle_sessions) {
+    quic::QuicStreamOffset header_stream_offset = 0;
+    failed_socket_data.AddRead(SYNCHRONOUS, ERR_IO_PENDING);
+    failed_socket_data.AddWrite(
+        SYNCHRONOUS, ConstructInitialSettingsPacket(1, &header_stream_offset));
+    // A RESET will be sent to the peer to cancel the non-migratable stream.
+    failed_socket_data.AddWrite(
+        SYNCHRONOUS, client_maker_.MakeRstPacket(
+                         2, true, GetNthClientInitiatedBidirectionalStreamId(0),
+                         quic::QUIC_STREAM_CANCELLED));
+    failed_socket_data.AddSocketDataToFactory(socket_factory_.get());
+
+    // Set up second socket data provider that is used after migration.
+    socket_data.AddRead(SYNCHRONOUS, ERR_IO_PENDING);  // Hanging read.
+    // Ping packet to send after migration.
+    socket_data.AddWrite(
+        SYNCHRONOUS, client_maker_.MakePingPacket(3, /*include_version=*/true));
+    socket_data.AddSocketDataToFactory(socket_factory_.get());
+  } else {
+    socket_data.AddRead(SYNCHRONOUS, ERR_IO_PENDING);
+    socket_data.AddWrite(SYNCHRONOUS, ConstructInitialSettingsPacket());
+    socket_data.AddWrite(
+        SYNCHRONOUS, client_maker_.MakeRstPacket(
+                         2, true, GetNthClientInitiatedBidirectionalStreamId(0),
+                         quic::QUIC_STREAM_CANCELLED));
+    socket_data.AddSocketDataToFactory(socket_factory_.get());
+  }
 
   // Create request and QuicHttpStream.
   QuicStreamRequest request(factory_.get());
@@ -2878,18 +2936,22 @@
   // Trigger connection migration. Since there is a non-migratable stream,
   // this should cause a RST_STREAM frame to be emitted with
   // quic::QUIC_STREAM_CANCELLED error code.
-  // The connection will then be migrated to the alternate network.
+  // If migate idle session, the connection will then be migrated to the
+  // alternate network. Otherwise, the connection will be closed.
   scoped_mock_network_change_notifier_->mock_network_change_notifier()
       ->NotifyNetworkDisconnected(kDefaultNetworkForTests);
 
-  EXPECT_TRUE(QuicStreamFactoryPeer::IsLiveSession(factory_.get(), session));
-  EXPECT_TRUE(HasActiveSession(host_port_pair_));
-  EXPECT_EQ(0u, session->GetNumActiveStreams());
+  EXPECT_EQ(migrate_idle_sessions,
+            QuicStreamFactoryPeer::IsLiveSession(factory_.get(), session));
+  EXPECT_EQ(migrate_idle_sessions, HasActiveSession(host_port_pair_));
 
-  base::RunLoop().RunUntilIdle();
+  if (migrate_idle_sessions) {
+    EXPECT_EQ(0u, session->GetNumActiveStreams());
+    base::RunLoop().RunUntilIdle();
 
-  EXPECT_TRUE(failed_socket_data.AllReadDataConsumed());
-  EXPECT_TRUE(failed_socket_data.AllWriteDataConsumed());
+    EXPECT_TRUE(failed_socket_data.AllReadDataConsumed());
+    EXPECT_TRUE(failed_socket_data.AllWriteDataConsumed());
+  }
   EXPECT_TRUE(socket_data.AllReadDataConsumed());
   EXPECT_TRUE(socket_data.AllWriteDataConsumed());
 }
@@ -2952,7 +3014,19 @@
   EXPECT_TRUE(socket_data.AllWriteDataConsumed());
 }
 
-TEST_P(QuicStreamFactoryTest, OnNetworkMadeDefaultNoOpenStreams) {
+TEST_P(QuicStreamFactoryTest,
+       OnNetworkMadeDefaultNoOpenStreams_DoNotMigrateIdleSessions) {
+  TestOnNetworkMadeDefaultNoOpenStreams(false);
+}
+
+TEST_P(QuicStreamFactoryTest,
+       OnNetworkMadeDefaultNoOpenStreams_MigrateIdleSessions) {
+  TestOnNetworkMadeDefaultNoOpenStreams(true);
+}
+
+void QuicStreamFactoryTestBase::TestOnNetworkMadeDefaultNoOpenStreams(
+    bool migrate_idle_sessions) {
+  test_params_.quic_migrate_idle_sessions = migrate_idle_sessions;
   InitializeConnectionMigrationV2Test(
       {kDefaultNetworkForTests, kNewNetworkForTests});
   ProofVerifyDetailsChromium verify_details = DefaultProofVerifyDetails();
@@ -2961,22 +3035,31 @@
   MockQuicData socket_data;
   socket_data.AddRead(SYNCHRONOUS, ERR_IO_PENDING);
   socket_data.AddWrite(SYNCHRONOUS, ConstructInitialSettingsPacket());
+  if (!migrate_idle_sessions) {
+    socket_data.AddWrite(
+        SYNCHRONOUS,
+        client_maker_.MakeConnectionClosePacket(
+            2, true, quic::QUIC_CONNECTION_MIGRATION_NO_MIGRATABLE_STREAMS,
+            "net error"));
+  }
   socket_data.AddSocketDataToFactory(socket_factory_.get());
 
-  // Set up the second socket data provider that is used for probing.
   MockQuicData quic_data1;
-  // Connectivity probe to be sent on the new path.
-  quic_data1.AddWrite(SYNCHRONOUS,
-                      client_maker_.MakeConnectivityProbingPacket(2, true));
-  quic_data1.AddRead(ASYNC, ERR_IO_PENDING);  // Pause
-  // Connectivity probe to receive from the server.
-  quic_data1.AddRead(ASYNC,
-                     server_maker_.MakeConnectivityProbingPacket(1, false));
-  quic_data1.AddRead(SYNCHRONOUS, ERR_IO_PENDING);  // Hanging read.
-  // Ping packet to send after migration is completed.
-  quic_data1.AddWrite(SYNCHRONOUS,
-                      client_maker_.MakeAckAndPingPacket(3, false, 1, 1, 1));
-  quic_data1.AddSocketDataToFactory(socket_factory_.get());
+  if (migrate_idle_sessions) {
+    // Set up the second socket data provider that is used for probing.
+    // Connectivity probe to be sent on the new path.
+    quic_data1.AddWrite(SYNCHRONOUS,
+                        client_maker_.MakeConnectivityProbingPacket(2, true));
+    quic_data1.AddRead(ASYNC, ERR_IO_PENDING);  // Pause
+    // Connectivity probe to receive from the server.
+    quic_data1.AddRead(ASYNC,
+                       server_maker_.MakeConnectivityProbingPacket(1, false));
+    quic_data1.AddRead(SYNCHRONOUS, ERR_IO_PENDING);  // Hanging read.
+    // Ping packet to send after migration is completed.
+    quic_data1.AddWrite(SYNCHRONOUS,
+                        client_maker_.MakeAckAndPingPacket(3, false, 1, 1, 1));
+    quic_data1.AddSocketDataToFactory(socket_factory_.get());
+  }
 
   // Create request and QuicHttpStream.
   QuicStreamRequest request(factory_.get());
@@ -3000,19 +3083,31 @@
   // Trigger connection migration.
   scoped_mock_network_change_notifier_->mock_network_change_notifier()
       ->NotifyNetworkMadeDefault(kNewNetworkForTests);
-  // Verify that the session is still active.
-  EXPECT_TRUE(HasActiveSession(host_port_pair_));
+  EXPECT_EQ(migrate_idle_sessions, HasActiveSession(host_port_pair_));
 
-  quic_data1.Resume();
-  base::RunLoop().RunUntilIdle();
-
+  if (migrate_idle_sessions) {
+    quic_data1.Resume();
+    base::RunLoop().RunUntilIdle();
+    EXPECT_TRUE(quic_data1.AllReadDataConsumed());
+    EXPECT_TRUE(quic_data1.AllWriteDataConsumed());
+  }
   EXPECT_TRUE(socket_data.AllReadDataConsumed());
   EXPECT_TRUE(socket_data.AllWriteDataConsumed());
-  EXPECT_TRUE(quic_data1.AllReadDataConsumed());
-  EXPECT_TRUE(quic_data1.AllWriteDataConsumed());
 }
 
-TEST_P(QuicStreamFactoryTest, OnNetworkDisconnectedNoOpenStreams) {
+TEST_P(QuicStreamFactoryTest,
+       OnNetworkDisconnectedNoOpenStreams_DoNotMigateIdleSessions) {
+  TestOnNetworkDisconnectedNoOpenStreams(false);
+}
+
+TEST_P(QuicStreamFactoryTest,
+       OnNetworkDisconnectedNoOpenStreams_MigateIdleSessions) {
+  TestOnNetworkDisconnectedNoOpenStreams(true);
+}
+
+void QuicStreamFactoryTestBase::TestOnNetworkDisconnectedNoOpenStreams(
+    bool migrate_idle_sessions) {
+  test_params_.quic_migrate_idle_sessions = migrate_idle_sessions;
   InitializeConnectionMigrationV2Test(
       {kDefaultNetworkForTests, kNewNetworkForTests});
   ProofVerifyDetailsChromium verify_details = DefaultProofVerifyDetails();
@@ -3023,13 +3118,16 @@
   default_socket_data.AddWrite(SYNCHRONOUS, ConstructInitialSettingsPacket());
   default_socket_data.AddSocketDataToFactory(socket_factory_.get());
 
-  // Set up second socket data provider that is used after migration.
   MockQuicData alternate_socket_data;
-  alternate_socket_data.AddRead(SYNCHRONOUS, ERR_IO_PENDING);  // Hanging read.
-  // Ping packet to send after migration.
-  alternate_socket_data.AddWrite(
-      SYNCHRONOUS, client_maker_.MakePingPacket(2, /*include_version=*/true));
-  alternate_socket_data.AddSocketDataToFactory(socket_factory_.get());
+  if (migrate_idle_sessions) {
+    // Set up second socket data provider that is used after migration.
+    alternate_socket_data.AddRead(SYNCHRONOUS,
+                                  ERR_IO_PENDING);  // Hanging read.
+    // Ping packet to send after migration.
+    alternate_socket_data.AddWrite(
+        SYNCHRONOUS, client_maker_.MakePingPacket(2, /*include_version=*/true));
+    alternate_socket_data.AddSocketDataToFactory(socket_factory_.get());
+  }
 
   // Create request and QuicHttpStream.
   QuicStreamRequest request(factory_.get());
@@ -3051,12 +3149,14 @@
   scoped_mock_network_change_notifier_->mock_network_change_notifier()
       ->NotifyNetworkDisconnected(kDefaultNetworkForTests);
 
-  EXPECT_TRUE(HasActiveSession(host_port_pair_));
+  EXPECT_EQ(migrate_idle_sessions, HasActiveSession(host_port_pair_));
 
   EXPECT_TRUE(default_socket_data.AllReadDataConsumed());
   EXPECT_TRUE(default_socket_data.AllWriteDataConsumed());
-  EXPECT_TRUE(alternate_socket_data.AllReadDataConsumed());
-  EXPECT_TRUE(alternate_socket_data.AllWriteDataConsumed());
+  if (migrate_idle_sessions) {
+    EXPECT_TRUE(alternate_socket_data.AllReadDataConsumed());
+    EXPECT_TRUE(alternate_socket_data.AllWriteDataConsumed());
+  }
 }
 
 // This test verifies session migrates to the alternate network immediately when
@@ -4430,7 +4530,19 @@
 // This test verifies that session with non-migratable stream will probe the
 // alternate network on path degrading, and close the non-migratable streams
 // when probe is successful.
-TEST_P(QuicStreamFactoryTest, MigrateSessionEarlyNonMigratableStream) {
+TEST_P(QuicStreamFactoryTest,
+       MigrateSessionEarlyNonMigratableStream_DoNotMigrateIdleSessions) {
+  TestMigrateSessionEarlyNonMigratableStream(false);
+}
+
+TEST_P(QuicStreamFactoryTest,
+       MigrateSessionEarlyNonMigratableStream_MigrateIdleSessions) {
+  TestMigrateSessionEarlyNonMigratableStream(true);
+}
+
+void QuicStreamFactoryTestBase::TestMigrateSessionEarlyNonMigratableStream(
+    bool migrate_idle_sessions) {
+  test_params_.quic_migrate_idle_sessions = migrate_idle_sessions;
   InitializeConnectionMigrationV2Test(
       {kDefaultNetworkForTests, kNewNetworkForTests});
   ProofVerifyDetailsChromium verify_details = DefaultProofVerifyDetails();
@@ -4439,6 +4551,16 @@
   MockQuicData socket_data;
   socket_data.AddRead(SYNCHRONOUS, ERR_IO_PENDING);
   socket_data.AddWrite(SYNCHRONOUS, ConstructInitialSettingsPacket());
+  if (!migrate_idle_sessions) {
+    socket_data.AddWrite(
+        SYNCHRONOUS,
+        client_maker_.MakeRstAckAndConnectionClosePacket(
+            3, false, GetNthClientInitiatedBidirectionalStreamId(0),
+            quic::QUIC_STREAM_CANCELLED,
+            quic::QuicTime::Delta::FromMilliseconds(0), 1, 1, 1,
+            quic::QUIC_CONNECTION_MIGRATION_NO_MIGRATABLE_STREAMS,
+            "net error"));
+  }
   socket_data.AddSocketDataToFactory(socket_factory_.get());
 
   // Set up the second socket data provider that is used for probing.
@@ -4450,15 +4572,18 @@
   // Connectivity probe to receive from the server.
   quic_data1.AddRead(ASYNC,
                      server_maker_.MakeConnectivityProbingPacket(1, false));
-  quic_data1.AddRead(SYNCHRONOUS, ERR_IO_PENDING);  // Hanging read.
-  // A RESET will be sent to the peer to cancel the non-migratable stream.
-  quic_data1.AddWrite(
-      SYNCHRONOUS, client_maker_.MakeRstPacket(
-                       3, false, GetNthClientInitiatedBidirectionalStreamId(0),
-                       quic::QUIC_STREAM_CANCELLED));
-  // Ping packet to send after migration is completed.
-  quic_data1.AddWrite(SYNCHRONOUS,
-                      client_maker_.MakeAckAndPingPacket(4, false, 1, 1, 1));
+  if (migrate_idle_sessions) {
+    quic_data1.AddRead(SYNCHRONOUS, ERR_IO_PENDING);  // Hanging read.
+    // A RESET will be sent to the peer to cancel the non-migratable stream.
+    quic_data1.AddWrite(
+        SYNCHRONOUS,
+        client_maker_.MakeRstPacket(
+            3, false, GetNthClientInitiatedBidirectionalStreamId(0),
+            quic::QUIC_STREAM_CANCELLED));
+    // Ping packet to send after migration is completed.
+    quic_data1.AddWrite(SYNCHRONOUS,
+                        client_maker_.MakeAckAndPingPacket(4, false, 1, 1, 1));
+  }
   quic_data1.AddSocketDataToFactory(socket_factory_.get());
 
   // Create request and QuicHttpStream.
@@ -4500,9 +4625,10 @@
   // Resume the data to read the connectivity probing response to declare probe
   // as successful. Non-migratable streams will be closed.
   quic_data1.Resume();
-  base::RunLoop().RunUntilIdle();
+  if (migrate_idle_sessions)
+    base::RunLoop().RunUntilIdle();
 
-  EXPECT_TRUE(HasActiveSession(host_port_pair_));
+  EXPECT_EQ(migrate_idle_sessions, HasActiveSession(host_port_pair_));
   EXPECT_EQ(0u, session->GetNumActiveStreams());
 
   EXPECT_TRUE(quic_data1.AllReadDataConsumed());
@@ -5958,39 +6084,49 @@
 // This test verifies that when a connection encounters a packet write error, it
 // will cancel non-migratable streams, and migrate to the alternate network.
 void QuicStreamFactoryTestBase::TestMigrationOnWriteErrorNonMigratableStream(
-    IoMode write_error_mode) {
+    IoMode write_error_mode,
+    bool migrate_idle_sessions) {
   DVLOG(1) << "Write error mode: "
            << ((write_error_mode == SYNCHRONOUS) ? "SYNCHRONOUS" : "ASYNC");
+  DVLOG(1) << "Migrate idle sessions: " << migrate_idle_sessions;
+  test_params_.quic_migrate_idle_sessions = migrate_idle_sessions;
   InitializeConnectionMigrationV2Test(
       {kDefaultNetworkForTests, kNewNetworkForTests});
   ProofVerifyDetailsChromium verify_details = DefaultProofVerifyDetails();
   crypto_client_stream_factory_.AddProofVerifyDetails(&verify_details);
 
-  quic::QuicStreamOffset header_stream_offset = 0;
-  // The socket data provider for the original socket before migration.
   MockQuicData failed_socket_data;
-  failed_socket_data.AddRead(SYNCHRONOUS, ERR_IO_PENDING);
-  failed_socket_data.AddWrite(
-      SYNCHRONOUS, ConstructInitialSettingsPacket(1, &header_stream_offset));
-  failed_socket_data.AddWrite(write_error_mode, ERR_ADDRESS_UNREACHABLE);
-  failed_socket_data.AddSocketDataToFactory(socket_factory_.get());
-
-  // Set up second socket data provider that is used after migration.
   MockQuicData socket_data;
-  socket_data.AddRead(SYNCHRONOUS, ERR_IO_PENDING);  // Hanging read.
-  // Although the write error occurs when writing a packet for the
-  // non-migratable stream and the stream will be cancelled during migration,
-  // the packet will still be retransimitted at the connection level.
-  socket_data.AddWrite(
-      SYNCHRONOUS, ConstructGetRequestPacket(
-                       2, GetNthClientInitiatedBidirectionalStreamId(0), true,
-                       true, &header_stream_offset));
-  // A RESET will be sent to the peer to cancel the non-migratable stream.
-  socket_data.AddWrite(
-      SYNCHRONOUS, client_maker_.MakeRstPacket(
-                       3, true, GetNthClientInitiatedBidirectionalStreamId(0),
-                       quic::QUIC_STREAM_CANCELLED));
-  socket_data.AddSocketDataToFactory(socket_factory_.get());
+  if (migrate_idle_sessions) {
+    quic::QuicStreamOffset header_stream_offset = 0;
+    // The socket data provider for the original socket before migration.
+    failed_socket_data.AddRead(SYNCHRONOUS, ERR_IO_PENDING);
+    failed_socket_data.AddWrite(
+        SYNCHRONOUS, ConstructInitialSettingsPacket(1, &header_stream_offset));
+    failed_socket_data.AddWrite(write_error_mode, ERR_ADDRESS_UNREACHABLE);
+    failed_socket_data.AddSocketDataToFactory(socket_factory_.get());
+
+    // Set up second socket data provider that is used after migration.
+    socket_data.AddRead(SYNCHRONOUS, ERR_IO_PENDING);  // Hanging read.
+    // Although the write error occurs when writing a packet for the
+    // non-migratable stream and the stream will be cancelled during migration,
+    // the packet will still be retransimitted at the connection level.
+    socket_data.AddWrite(
+        SYNCHRONOUS, ConstructGetRequestPacket(
+                         2, GetNthClientInitiatedBidirectionalStreamId(0), true,
+                         true, &header_stream_offset));
+    // A RESET will be sent to the peer to cancel the non-migratable stream.
+    socket_data.AddWrite(
+        SYNCHRONOUS, client_maker_.MakeRstPacket(
+                         3, true, GetNthClientInitiatedBidirectionalStreamId(0),
+                         quic::QUIC_STREAM_CANCELLED));
+    socket_data.AddSocketDataToFactory(socket_factory_.get());
+  } else {
+    socket_data.AddRead(SYNCHRONOUS, ERR_IO_PENDING);
+    socket_data.AddWrite(SYNCHRONOUS, ConstructInitialSettingsPacket());
+    socket_data.AddWrite(write_error_mode, ERR_ADDRESS_UNREACHABLE);
+    socket_data.AddSocketDataToFactory(socket_factory_.get());
+  }
 
   // Create request and QuicHttpStream.
   QuicStreamRequest request(factory_.get());
@@ -6029,25 +6165,41 @@
   // Run message loop to execute migration attempt.
   base::RunLoop().RunUntilIdle();
 
-  // Migration closes the non-migratable stream and then migrates to the
-  // alternate network successfully..
-  EXPECT_TRUE(QuicStreamFactoryPeer::IsLiveSession(factory_.get(), session));
-  EXPECT_TRUE(HasActiveSession(host_port_pair_));
+  // Migration closes the non-migratable stream and:
+  // if migrate idle session is enabled, it migrates to the alternate network
+  // successfully; otherwise the connection is closed.
+  EXPECT_EQ(migrate_idle_sessions,
+            QuicStreamFactoryPeer::IsLiveSession(factory_.get(), session));
+  EXPECT_EQ(migrate_idle_sessions, HasActiveSession(host_port_pair_));
 
-  EXPECT_TRUE(failed_socket_data.AllReadDataConsumed());
-  EXPECT_TRUE(failed_socket_data.AllWriteDataConsumed());
+  if (migrate_idle_sessions) {
+    EXPECT_TRUE(failed_socket_data.AllReadDataConsumed());
+    EXPECT_TRUE(failed_socket_data.AllWriteDataConsumed());
+  }
   EXPECT_TRUE(socket_data.AllReadDataConsumed());
   EXPECT_TRUE(socket_data.AllWriteDataConsumed());
 }
 
-TEST_P(QuicStreamFactoryTest,
-       MigrateSessionOnWriteErrorNonMigratableStreamSynchronous) {
-  TestMigrationOnWriteErrorNonMigratableStream(SYNCHRONOUS);
+TEST_P(
+    QuicStreamFactoryTest,
+    MigrateSessionOnWriteErrorNonMigratableStreamSync_DoNotMigrateIdleSessions) {
+  TestMigrationOnWriteErrorNonMigratableStream(SYNCHRONOUS, false);
+}
+
+TEST_P(
+    QuicStreamFactoryTest,
+    MigrateSessionOnWriteErrorNonMigratableStreamAsync_DoNotMigrateIdleSessions) {
+  TestMigrationOnWriteErrorNonMigratableStream(ASYNC, false);
 }
 
 TEST_P(QuicStreamFactoryTest,
-       MigrateSessionOnWriteErrorNonMigratableStreamAsync) {
-  TestMigrationOnWriteErrorNonMigratableStream(ASYNC);
+       MigrateSessionOnWriteErrorNonMigratableStreamSync_MigrateIdleSessions) {
+  TestMigrationOnWriteErrorNonMigratableStream(SYNCHRONOUS, true);
+}
+
+TEST_P(QuicStreamFactoryTest,
+       MigrateSessionOnWriteErrorNonMigratableStreamAsync_MigrateIdleSessions) {
+  TestMigrationOnWriteErrorNonMigratableStream(ASYNC, true);
 }
 
 void QuicStreamFactoryTestBase::TestMigrationOnWriteErrorMigrationDisabled(
@@ -7329,6 +7481,7 @@
 // default network or the idle migration period threshold is exceeded.
 // The default threshold is 30s.
 TEST_P(QuicStreamFactoryTest, DefaultIdleMigrationPeriod) {
+  test_params_.quic_migrate_idle_sessions = true;
   InitializeConnectionMigrationV2Test(
       {kDefaultNetworkForTests, kNewNetworkForTests});
   ProofVerifyDetailsChromium verify_details = DefaultProofVerifyDetails();
@@ -7445,6 +7598,7 @@
 
 TEST_P(QuicStreamFactoryTest, CustomIdleMigrationPeriod) {
   // The customized threshold is 15s.
+  test_params_.quic_migrate_idle_sessions = true;
   test_params_.quic_idle_session_migration_period =
       base::TimeDelta::FromSeconds(15);
   InitializeConnectionMigrationV2Test(