HttpServerProperties: Pass in NIK to broken service methods.

Previous CLs have added NetworkIsolationKey support to broken-
alternative-services-related HttpServerProperties methods,
but they were still being passed empty NetworkIsolationKeys
by consumers. This CL makes consumers pass in the correct NIK,
and adds tests for all callsites.

Bug: 969890
Change-Id: I5b3d7947bfe91f7fcc6a19bcf30c399dd8f49d8a
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1782666
Reviewed-by: Zhongyi Shi <[email protected]>
Commit-Queue: Matt Menke <[email protected]>
Cr-Commit-Position: refs/heads/master@{#695275}
diff --git a/net/quic/quic_network_transaction_unittest.cc b/net/quic/quic_network_transaction_unittest.cc
index 6cc402d4..c435912 100644
--- a/net/quic/quic_network_transaction_unittest.cc
+++ b/net/quic/quic_network_transaction_unittest.cc
@@ -770,13 +770,15 @@
   }
 
   void AddQuicAlternateProtocolMapping(
-      MockCryptoClientStream::HandshakeMode handshake_mode) {
+      MockCryptoClientStream::HandshakeMode handshake_mode,
+      const NetworkIsolationKey& network_isolation_key =
+          NetworkIsolationKey()) {
     crypto_client_stream_factory_.set_handshake_mode(handshake_mode);
     url::SchemeHostPort server(request_.url);
     AlternativeService alternative_service(kProtoQUIC, server.host(), 443);
     base::Time expiration = base::Time::Now() + base::TimeDelta::FromDays(1);
     http_server_properties_->SetQuicAlternativeService(
-        server, NetworkIsolationKey(), alternative_service, expiration,
+        server, network_isolation_key, alternative_service, expiration,
         supported_versions_);
   }
 
@@ -793,27 +795,33 @@
         supported_versions_);
   }
 
-  void ExpectBrokenAlternateProtocolMapping() {
+  void ExpectBrokenAlternateProtocolMapping(
+      const NetworkIsolationKey& network_isolation_key =
+          NetworkIsolationKey()) {
     const url::SchemeHostPort server(request_.url);
     const AlternativeServiceInfoVector alternative_service_info_vector =
         http_server_properties_->GetAlternativeServiceInfos(
-            server, NetworkIsolationKey());
+            server, network_isolation_key);
     EXPECT_EQ(1u, alternative_service_info_vector.size());
     EXPECT_TRUE(http_server_properties_->IsAlternativeServiceBroken(
-        alternative_service_info_vector[0].alternative_service()));
+        alternative_service_info_vector[0].alternative_service(),
+        network_isolation_key));
   }
 
-  void ExpectQuicAlternateProtocolMapping() {
+  void ExpectQuicAlternateProtocolMapping(
+      const NetworkIsolationKey& network_isolation_key =
+          NetworkIsolationKey()) {
     const url::SchemeHostPort server(request_.url);
     const AlternativeServiceInfoVector alternative_service_info_vector =
         http_server_properties_->GetAlternativeServiceInfos(
-            server, NetworkIsolationKey());
+            server, network_isolation_key);
     EXPECT_EQ(1u, alternative_service_info_vector.size());
     EXPECT_EQ(
         kProtoQUIC,
         alternative_service_info_vector[0].alternative_service().protocol);
     EXPECT_FALSE(http_server_properties_->IsAlternativeServiceBroken(
-        alternative_service_info_vector[0].alternative_service()));
+        alternative_service_info_vector[0].alternative_service(),
+        network_isolation_key));
   }
 
   void AddHangingNonAlternateProtocolSocketData() {
@@ -900,6 +908,67 @@
     SendRequestAndExpectHttpResponseFromProxy("hello from http", true, 443);
   }
 
+  // Adds a new socket data provider for an HTTP request, and runs a request,
+  // expecting it to be used.
+  void AddHttpDataAndRunRequest() {
+    MockWrite http_writes[] = {
+        MockWrite(SYNCHRONOUS, 0, "GET / HTTP/1.1\r\n"),
+        MockWrite(SYNCHRONOUS, 1, "Host: mail.example.org\r\n"),
+        MockWrite(SYNCHRONOUS, 2, "Connection: keep-alive\r\n\r\n")};
+
+    MockRead http_reads[] = {
+        MockRead(SYNCHRONOUS, 3, "HTTP/1.1 200 OK\r\n"),
+        MockRead(SYNCHRONOUS, 4, kQuicAlternativeServiceHeader),
+        MockRead(SYNCHRONOUS, 5, "http used"),
+        // Connection closed.
+        MockRead(SYNCHRONOUS, OK, 6)};
+    SequencedSocketData http_data(http_reads, http_writes);
+    socket_factory_.AddSocketDataProvider(&http_data);
+    SSLSocketDataProvider ssl_data(ASYNC, OK);
+    socket_factory_.AddSSLSocketDataProvider(&ssl_data);
+    SendRequestAndExpectHttpResponse("http used");
+    EXPECT_TRUE(http_data.AllWriteDataConsumed());
+    EXPECT_TRUE(http_data.AllReadDataConsumed());
+  }
+
+  // Adds a new socket data provider for a QUIC request, and runs a request,
+  // expecting it to be used. The new QUIC session is not closed.
+  void AddQuicDataAndRunRequest() {
+    QuicTestPacketMaker client_maker(
+        version_, quic::QuicUtils::CreateRandomConnectionId(&random_generator_),
+        &clock_, kDefaultServerHostName, quic::Perspective::IS_CLIENT,
+        client_headers_include_h2_stream_dependency_);
+    QuicTestPacketMaker server_maker(
+        version_, quic::QuicUtils::CreateRandomConnectionId(&random_generator_),
+        &clock_, kDefaultServerHostName, quic::Perspective::IS_SERVER, false);
+    MockQuicData quic_data(version_);
+    client_maker.SetEncryptionLevel(quic::ENCRYPTION_ZERO_RTT);
+    quic_data.AddWrite(
+        SYNCHRONOUS,
+        client_maker.MakeRequestHeadersPacket(
+            1, GetNthClientInitiatedBidirectionalStreamId(0), true, true,
+            ConvertRequestPriorityToQuicPriority(DEFAULT_PRIORITY),
+            GetRequestHeaders("GET", "https", "/", &client_maker), 0, nullptr));
+    client_maker.SetEncryptionLevel(quic::ENCRYPTION_FORWARD_SECURE);
+    quic_data.AddRead(
+        ASYNC, server_maker.MakeResponseHeadersPacket(
+                   1, GetNthClientInitiatedBidirectionalStreamId(0), false,
+                   false, server_maker.GetResponseHeaders("200 OK"), nullptr));
+    std::string header = ConstructDataHeader(9);
+    quic_data.AddRead(
+        ASYNC, server_maker.MakeDataPacket(
+                   2, GetNthClientInitiatedBidirectionalStreamId(0), false,
+                   true, header + "quic used"));
+    // Don't care about the final ack.
+    quic_data.AddWrite(SYNCHRONOUS, ERR_IO_PENDING);
+    // No more data to read.
+    quic_data.AddRead(SYNCHRONOUS, ERR_IO_PENDING);
+    quic_data.AddSocketDataToFactory(&socket_factory_);
+    SendRequestAndExpectQuicResponse("quic used");
+
+    EXPECT_TRUE(quic_data.AllReadDataConsumed());
+  }
+
   quic::QuicStreamId GetNthClientInitiatedBidirectionalStreamId(int n) {
     return quic::test::GetNthClientInitiatedBidirectionalStreamId(
         version_.transport_version, n);
@@ -1918,8 +1987,12 @@
 TEST_P(QuicNetworkTransactionTest,
        UseAlternativeServiceForQuicWithNetworkIsolationKey) {
   base::test::ScopedFeatureList feature_list;
-  feature_list.InitAndEnableFeature(
-      features::kPartitionHttpServerPropertiesByNetworkIsolationKey);
+  feature_list.InitWithFeatures(
+      // enabled_features
+      {features::kPartitionHttpServerPropertiesByNetworkIsolationKey,
+       features::kPartitionConnectionsByNetworkIsolationKey},
+      // disabled_features
+      {});
   // Since HttpServerProperties caches the feature value, have to create a new
   // one.
   http_server_properties_ = std::make_unique<HttpServerProperties>();
@@ -2695,6 +2768,149 @@
   ASSERT_TRUE(quic_data2.AllWriteDataConsumed());
 }
 
+// Much like above test, but verifies NetworkIsolationKeys are respected.
+TEST_P(QuicNetworkTransactionTest,
+       RetryOnAlternateNetworkWhileTCPSucceedsWithNetworkIsolationKey) {
+  const url::Origin kOrigin1 = url::Origin::Create(GURL("https://foo.test/"));
+  const net::NetworkIsolationKey kNetworkIsolationKey1(kOrigin1, kOrigin1);
+  const url::Origin kOrigin2 = url::Origin::Create(GURL("https://bar.test/"));
+  const net::NetworkIsolationKey kNetworkIsolationKey2(kOrigin2, kOrigin2);
+
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitWithFeatures(
+      // enabled_features
+      {features::kPartitionHttpServerPropertiesByNetworkIsolationKey,
+       // Need to partition connections by NetworkIsolationKey for
+       // QuicSessionAliasKey to include NetworkIsolationKeys.
+       features::kPartitionConnectionsByNetworkIsolationKey},
+      // disabled_features
+      {});
+  // Since HttpServerProperties caches the feature value, have to create a new
+  // one.
+  http_server_properties_ = std::make_unique<HttpServerProperties>();
+
+  SetUpTestForRetryConnectionOnAlternateNetwork();
+
+  client_maker_.SetEncryptionLevel(quic::ENCRYPTION_ZERO_RTT);
+
+  // The request will initially go out over QUIC.
+  MockQuicData quic_data(version_);
+  quic_data.AddRead(SYNCHRONOUS, ERR_IO_PENDING);  // Hanging read
+  int packet_num = 1;
+  quic_data.AddWrite(SYNCHRONOUS,
+                     client_maker_.MakeDummyCHLOPacket(packet_num++));  // CHLO
+  // Retranmit the handshake messages.
+  quic_data.AddWrite(SYNCHRONOUS,
+                     client_maker_.MakeDummyCHLOPacket(packet_num++));
+  quic_data.AddWrite(SYNCHRONOUS,
+                     client_maker_.MakeDummyCHLOPacket(packet_num++));
+  quic_data.AddWrite(SYNCHRONOUS,
+                     client_maker_.MakeDummyCHLOPacket(packet_num++));
+  quic_data.AddWrite(SYNCHRONOUS,
+                     client_maker_.MakeDummyCHLOPacket(packet_num++));
+  // TODO(zhongyi): remove condition check once b/115926584 is fixed.
+  if (version_.transport_version <= quic::QUIC_VERSION_39) {
+    quic_data.AddWrite(SYNCHRONOUS,
+                       client_maker_.MakeDummyCHLOPacket(packet_num++));
+  }
+  // After timeout, connection will be closed with QUIC_NETWORK_IDLE_TIMEOUT.
+  quic_data.AddWrite(SYNCHRONOUS,
+                     client_maker_.MakeConnectionClosePacket(
+                         packet_num++, true, quic::QUIC_NETWORK_IDLE_TIMEOUT,
+                         "No recent network activity."));
+  quic_data.AddSocketDataToFactory(&socket_factory_);
+
+  // Add successful TCP data so that TCP job will succeed.
+  MockWrite http_writes[] = {
+      MockWrite(SYNCHRONOUS, 0, "GET / HTTP/1.1\r\n"),
+      MockWrite(SYNCHRONOUS, 1, "Host: mail.example.org\r\n"),
+      MockWrite(SYNCHRONOUS, 2, "Connection: keep-alive\r\n\r\n")};
+
+  MockRead http_reads[] = {
+      MockRead(SYNCHRONOUS, 3, "HTTP/1.1 200 OK\r\n"),
+      MockRead(SYNCHRONOUS, 4, kQuicAlternativeServiceHeader),
+      MockRead(SYNCHRONOUS, 5, "TCP succeeds"), MockRead(SYNCHRONOUS, OK, 6)};
+  SequencedSocketData http_data(http_reads, http_writes);
+  socket_factory_.AddSocketDataProvider(&http_data);
+  socket_factory_.AddSSLSocketDataProvider(&ssl_data_);
+
+  // Quic connection will be retried on the alternate network after the initial
+  // one fails on the default network.
+  MockQuicData quic_data2(version_);
+  quic_data2.AddRead(SYNCHRONOUS, ERR_IO_PENDING);  // Handing read.
+  quic_data2.AddWrite(SYNCHRONOUS,
+                      client_maker_.MakeDummyCHLOPacket(1));  // CHLO
+
+  client_maker_.SetEncryptionLevel(quic::ENCRYPTION_FORWARD_SECURE);
+  if (VersionUsesQpack(version_.transport_version))
+    quic_data2.AddWrite(SYNCHRONOUS, ConstructInitialSettingsPacket(2));
+  quic_data2.AddSocketDataToFactory(&socket_factory_);
+
+  // Resolve the host resolution synchronously.
+  host_resolver_.set_synchronous_mode(true);
+  host_resolver_.rules()->AddIPLiteralRule("mail.example.org", "192.168.0.1",
+                                           "");
+
+  CreateSession();
+  session_->quic_stream_factory()->set_require_confirmation(true);
+  // Use a TestTaskRunner to avoid waiting in real time for timeouts.
+  QuicStreamFactoryPeer::SetAlarmFactory(
+      session_->quic_stream_factory(),
+      std::make_unique<QuicChromiumAlarmFactory>(quic_task_runner_.get(),
+                                                 &clock_));
+  // Add alternate protocol mapping to race QUIC and TCP.
+  // QUIC connection requires handshake to be confirmed and sends CHLO to the
+  // peer.
+  AddQuicAlternateProtocolMapping(
+      MockCryptoClientStream::COLD_START_WITH_CHLO_SENT, kNetworkIsolationKey1);
+  AddQuicAlternateProtocolMapping(
+      MockCryptoClientStream::COLD_START_WITH_CHLO_SENT, kNetworkIsolationKey2);
+
+  request_.network_isolation_key = kNetworkIsolationKey1;
+  HttpNetworkTransaction trans(DEFAULT_PRIORITY, session_.get());
+  TestCompletionCallback callback;
+  int rv = trans.Start(&request_, callback.callback(), net_log_.bound());
+  EXPECT_THAT(rv, IsError(ERR_IO_PENDING));
+
+  // Pump the message loop to get the request started.
+  // Request will be served with TCP job.
+  base::RunLoop().RunUntilIdle();
+  EXPECT_THAT(callback.WaitForResult(), IsOk());
+  CheckResponseData(&trans, "TCP succeeds");
+
+  // Fast forward to idle timeout the original connection. A new connection will
+  // be kicked off on the alternate network.
+  quic_task_runner_->FastForwardBy(quic::QuicTime::Delta::FromSeconds(4));
+  ASSERT_TRUE(quic_data.AllReadDataConsumed());
+  ASSERT_TRUE(quic_data.AllWriteDataConsumed());
+
+  // The second connection hasn't finish handshake, verify that QUIC is not
+  // marked as broken.
+  ExpectQuicAlternateProtocolMapping(kNetworkIsolationKey1);
+  ExpectQuicAlternateProtocolMapping(kNetworkIsolationKey2);
+  // Explicitly confirm the handshake on the second connection.
+  crypto_client_stream_factory_.last_stream()->SendOnCryptoHandshakeEvent(
+      quic::QuicSession::HANDSHAKE_CONFIRMED);
+  // Run message loop to execute posted tasks, which will notify JoController
+  // about the orphaned job status.
+  base::RunLoop().RunUntilIdle();
+
+  // Verify that QUIC is marked as broken for kNetworkIsolationKey1 only.
+  ExpectBrokenAlternateProtocolMapping(kNetworkIsolationKey1);
+  ExpectQuicAlternateProtocolMapping(kNetworkIsolationKey2);
+
+  // Deliver a message to notify the new network becomes default, the previous
+  // brokenness will be clear as the brokenness is bond with old default
+  // network.
+  scoped_mock_change_notifier_->mock_network_change_notifier()
+      ->NotifyNetworkMadeDefault(kNewNetworkForTests);
+  ExpectQuicAlternateProtocolMapping(kNetworkIsolationKey1);
+  ExpectQuicAlternateProtocolMapping(kNetworkIsolationKey2);
+
+  ASSERT_TRUE(quic_data2.AllReadDataConsumed());
+  ASSERT_TRUE(quic_data2.AllWriteDataConsumed());
+}
+
 // This test verifies that a new QUIC connection will be attempted on the
 // alternate network if the original QUIC connection fails with idle timeout
 // before handshake is confirmed. If TCP doesn't succeed but QUIC on the
@@ -3354,6 +3570,128 @@
   ASSERT_TRUE(http_data.AllReadDataConsumed());
 }
 
+// Much like above test, but verifies that NetworkIsolationKey is respected.
+TEST_P(QuicNetworkTransactionTest,
+       ProtocolErrorAfterHandshakeConfirmedThenBrokenWithNetworkIsolationKey) {
+  const url::Origin kOrigin1 = url::Origin::Create(GURL("https://foo.test/"));
+  const net::NetworkIsolationKey kNetworkIsolationKey1(kOrigin1, kOrigin1);
+  const url::Origin kOrigin2 = url::Origin::Create(GURL("https://bar.test/"));
+  const net::NetworkIsolationKey kNetworkIsolationKey2(kOrigin2, kOrigin2);
+
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitWithFeatures(
+      // enabled_features
+      {features::kPartitionHttpServerPropertiesByNetworkIsolationKey,
+       features::kPartitionConnectionsByNetworkIsolationKey},
+      // disabled_features
+      {});
+  // Since HttpServerProperties caches the feature value, have to create a new
+  // one.
+  http_server_properties_ = std::make_unique<HttpServerProperties>();
+
+  session_params_.quic_params.idle_connection_timeout =
+      base::TimeDelta::FromSeconds(5);
+
+  // The request will initially go out over QUIC.
+  MockQuicData quic_data(version_);
+  client_maker_.SetEncryptionLevel(quic::ENCRYPTION_ZERO_RTT);
+  quic_data.AddWrite(SYNCHRONOUS,
+                     ConstructClientRequestHeadersPacket(
+                         1, GetNthClientInitiatedBidirectionalStreamId(0), true,
+                         true, GetRequestHeaders("GET", "https", "/")));
+  client_maker_.SetEncryptionLevel(quic::ENCRYPTION_FORWARD_SECURE);
+  uint64_t packet_number = 2;
+  quic_data.AddRead(ASYNC, ERR_IO_PENDING);  // Pause
+
+  // Peer sending data from an non-existing stream causes this end to raise
+  // error and close connection.
+  quic_data.AddRead(
+      ASYNC, ConstructServerRstPacket(
+                 1, false, GetNthClientInitiatedBidirectionalStreamId(47),
+                 quic::QUIC_STREAM_LAST_ERROR));
+  std::string quic_error_details = "Data for nonexistent stream";
+  quic_data.AddWrite(
+      SYNCHRONOUS, ConstructClientAckAndConnectionClosePacket(
+                       packet_number++, quic::QuicTime::Delta::Zero(), 1, 1, 1,
+                       quic::QUIC_INVALID_STREAM_ID, quic_error_details,
+                       quic::IETF_RST_STREAM));
+  quic_data.AddSocketDataToFactory(&socket_factory_);
+
+  // After that fails, it will be resent via TCP.
+  MockWrite http_writes[] = {
+      MockWrite(SYNCHRONOUS, 0, "GET / HTTP/1.1\r\n"),
+      MockWrite(SYNCHRONOUS, 1, "Host: mail.example.org\r\n"),
+      MockWrite(SYNCHRONOUS, 2, "Connection: keep-alive\r\n\r\n")};
+
+  MockRead http_reads[] = {
+      MockRead(SYNCHRONOUS, 3, "HTTP/1.1 200 OK\r\n"),
+      MockRead(SYNCHRONOUS, 4, kQuicAlternativeServiceHeader),
+      MockRead(SYNCHRONOUS, 5, "hello world"), MockRead(SYNCHRONOUS, OK, 6)};
+  SequencedSocketData http_data(http_reads, http_writes);
+  socket_factory_.AddSocketDataProvider(&http_data);
+  socket_factory_.AddSSLSocketDataProvider(&ssl_data_);
+
+  // In order for a new QUIC session to be established via alternate-protocol
+  // without racing an HTTP connection, we need the host resolution to happen
+  // synchronously.  Of course, even though QUIC *could* perform a 0-RTT
+  // connection to the the server, in this test we require confirmation
+  // before encrypting so the HTTP job will still start.
+  host_resolver_.set_synchronous_mode(true);
+  host_resolver_.rules()->AddIPLiteralRule("mail.example.org", "192.168.0.1",
+                                           "");
+
+  CreateSession();
+
+  AddQuicAlternateProtocolMapping(MockCryptoClientStream::ZERO_RTT,
+                                  kNetworkIsolationKey1);
+  AddQuicAlternateProtocolMapping(MockCryptoClientStream::ZERO_RTT,
+                                  kNetworkIsolationKey2);
+
+  HttpNetworkTransaction trans(DEFAULT_PRIORITY, session_.get());
+  TestCompletionCallback callback;
+  request_.network_isolation_key = kNetworkIsolationKey1;
+  int rv = trans.Start(&request_, callback.callback(), net_log_.bound());
+  EXPECT_THAT(rv, IsError(ERR_IO_PENDING));
+
+  // Pump the message loop to get the request started.
+  base::RunLoop().RunUntilIdle();
+  // Explicitly confirm the handshake.
+  crypto_client_stream_factory_.last_stream()->SendOnCryptoHandshakeEvent(
+      quic::QuicSession::HANDSHAKE_CONFIRMED);
+  quic_data.Resume();
+
+  // Run the QUIC session to completion.
+  base::RunLoop().RunUntilIdle();
+  ASSERT_TRUE(quic_data.AllWriteDataConsumed());
+
+  // Let the transaction proceed which will result in QUIC being marked
+  // as broken and the request falling back to TCP.
+  EXPECT_THAT(callback.WaitForResult(), IsOk());
+  ASSERT_TRUE(quic_data.AllWriteDataConsumed());
+  ASSERT_FALSE(http_data.AllReadDataConsumed());
+
+  // Read the response body over TCP.
+  CheckResponseData(&trans, "hello world");
+  ASSERT_TRUE(http_data.AllWriteDataConsumed());
+  ASSERT_TRUE(http_data.AllReadDataConsumed());
+
+  // The alternative service shouldhave been marked as broken under
+  // kNetworkIsolationKey1 but not kNetworkIsolationKey2.
+  ExpectBrokenAlternateProtocolMapping(kNetworkIsolationKey1);
+  ExpectQuicAlternateProtocolMapping(kNetworkIsolationKey2);
+
+  // Subsequent requests using kNetworkIsolationKey1 should not use QUIC.
+  AddHttpDataAndRunRequest();
+  // Requests using other NetworkIsolationKeys can still use QUIC.
+  request_.network_isolation_key = kNetworkIsolationKey2;
+  AddQuicDataAndRunRequest();
+
+  // The last two requests should not have changed the alternative service
+  // mappings.
+  ExpectBrokenAlternateProtocolMapping(kNetworkIsolationKey1);
+  ExpectQuicAlternateProtocolMapping(kNetworkIsolationKey2);
+}
+
 // Verify that with retry_without_alt_svc_on_quic_errors enabled, if a QUIC
 // request is reset from, then QUIC will be marked as broken and the request
 // retried over TCP.
@@ -3509,14 +3847,15 @@
                                                   NetworkIsolationKey(),
                                                   alternative_services);
 
-  http_server_properties_->MarkAlternativeServiceBroken(local_alternative);
+  http_server_properties_->MarkAlternativeServiceBroken(local_alternative,
+                                                        NetworkIsolationKey());
 
   SendRequestAndExpectQuicResponse("hello!");
 }
 
 // Verify that with retry_without_alt_svc_on_quic_errors enabled, if a QUIC
 // request is reset from, then QUIC will be marked as broken and the request
-// retried over TCP. Then, subsequent requests will go over a new QUIC
+// retried over TCP. Then, subsequent requests will go over a new TCP
 // connection instead of going back to the broken QUIC connection.
 // This is a regression tests for crbug/731303.
 TEST_P(QuicNetworkTransactionTest,
@@ -3629,16 +3968,17 @@
 
   // Second request pools to existing connection with same destination,
   // because certificate matches, even though quic::QuicServerId is different.
-  // After it is reset, it will fail back to QUIC and mark QUIC as broken.
+  // After it is reset, it will fail back to TCP and mark QUIC as broken.
   request_.url = origin2;
   SendRequestAndExpectHttpResponse("hello world");
-  EXPECT_FALSE(
-      http_server_properties_->IsAlternativeServiceBroken(alternative1))
+  EXPECT_FALSE(http_server_properties_->IsAlternativeServiceBroken(
+      alternative1, NetworkIsolationKey()))
       << alternative1.ToString();
-  EXPECT_TRUE(http_server_properties_->IsAlternativeServiceBroken(alternative2))
+  EXPECT_TRUE(http_server_properties_->IsAlternativeServiceBroken(
+      alternative2, NetworkIsolationKey()))
       << alternative2.ToString();
 
-  // The third request should use a new QUIC connection, not the broken
+  // The third request should use a new TCP connection, not the broken
   // QUIC connection.
   SendRequestAndExpectHttpResponse("hello world");
 }
@@ -4222,9 +4562,9 @@
   AlternativeService alternative_service(kProtoQUIC,
                                          HostPortPair::FromURL(request_.url));
   http_server_properties_->MarkAlternativeServiceRecentlyBroken(
-      alternative_service);
+      alternative_service, NetworkIsolationKey());
   EXPECT_TRUE(http_server_properties_->WasAlternativeServiceRecentlyBroken(
-      alternative_service));
+      alternative_service, NetworkIsolationKey()));
 
   SendRequestAndExpectHttpResponse("hello world");
   SendRequestAndExpectQuicResponse("hello!");
@@ -4232,12 +4572,99 @@
   mock_quic_data.Resume();
 
   EXPECT_FALSE(http_server_properties_->WasAlternativeServiceRecentlyBroken(
-      alternative_service));
+      alternative_service, NetworkIsolationKey()));
   EXPECT_NE(nullptr, http_server_properties_->GetServerNetworkStats(
                          url::SchemeHostPort("https", request_.url.host(), 443),
                          NetworkIsolationKey()));
 }
 
+TEST_P(QuicNetworkTransactionTest,
+       ConfirmAlternativeServiceWithNetworkIsolationKey) {
+  const url::Origin kOrigin1 = url::Origin::Create(GURL("https://foo.test/"));
+  const net::NetworkIsolationKey kNetworkIsolationKey1(kOrigin1, kOrigin1);
+  const url::Origin kOrigin2 = url::Origin::Create(GURL("https://bar.test/"));
+  const net::NetworkIsolationKey kNetworkIsolationKey2(kOrigin2, kOrigin2);
+
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitWithFeatures(
+      // enabled_features
+      {features::kPartitionHttpServerPropertiesByNetworkIsolationKey,
+       features::kPartitionConnectionsByNetworkIsolationKey},
+      // disabled_features
+      {});
+  // Since HttpServerProperties caches the feature value, have to create a new
+  // one.
+  http_server_properties_ = std::make_unique<HttpServerProperties>();
+
+  MockRead http_reads[] = {
+      MockRead("HTTP/1.1 200 OK\r\n"), MockRead(kQuicAlternativeServiceHeader),
+      MockRead("hello world"),
+      MockRead(SYNCHRONOUS, ERR_TEST_PEER_CLOSE_AFTER_NEXT_MOCK_READ),
+      MockRead(ASYNC, OK)};
+
+  StaticSocketDataProvider http_data(http_reads, base::span<MockWrite>());
+  socket_factory_.AddSocketDataProvider(&http_data);
+  AddCertificate(&ssl_data_);
+  socket_factory_.AddSSLSocketDataProvider(&ssl_data_);
+
+  MockQuicData mock_quic_data(version_);
+  int packet_num = 1;
+  if (VersionUsesQpack(version_.transport_version)) {
+    mock_quic_data.AddWrite(SYNCHRONOUS,
+                            ConstructInitialSettingsPacket(packet_num++));
+  }
+  mock_quic_data.AddWrite(
+      SYNCHRONOUS,
+      ConstructClientRequestHeadersPacket(
+          packet_num++, GetNthClientInitiatedBidirectionalStreamId(0), true,
+          true, GetRequestHeaders("GET", "https", "/")));
+  mock_quic_data.AddRead(
+      ASYNC, ConstructServerResponseHeadersPacket(
+                 1, GetNthClientInitiatedBidirectionalStreamId(0), false, false,
+                 GetResponseHeaders("200 OK")));
+  std::string header = ConstructDataHeader(6);
+  mock_quic_data.AddRead(
+      ASYNC, ConstructServerDataPacket(
+                 2, GetNthClientInitiatedBidirectionalStreamId(0), false, true,
+                 header + "hello!"));
+  mock_quic_data.AddWrite(SYNCHRONOUS,
+                          ConstructClientAckPacket(packet_num++, 2, 1, 1));
+  mock_quic_data.AddRead(ASYNC, ERR_IO_PENDING);  // No more data to read
+  mock_quic_data.AddRead(ASYNC, 0);               // EOF
+
+  mock_quic_data.AddSocketDataToFactory(&socket_factory_);
+
+  CreateSession();
+
+  AlternativeService alternative_service(kProtoQUIC,
+                                         HostPortPair::FromURL(request_.url));
+  http_server_properties_->MarkAlternativeServiceRecentlyBroken(
+      alternative_service, kNetworkIsolationKey1);
+  http_server_properties_->MarkAlternativeServiceRecentlyBroken(
+      alternative_service, kNetworkIsolationKey2);
+  EXPECT_TRUE(http_server_properties_->WasAlternativeServiceRecentlyBroken(
+      alternative_service, kNetworkIsolationKey1));
+  EXPECT_TRUE(http_server_properties_->WasAlternativeServiceRecentlyBroken(
+      alternative_service, kNetworkIsolationKey2));
+
+  request_.network_isolation_key = kNetworkIsolationKey1;
+  SendRequestAndExpectHttpResponse("hello world");
+  SendRequestAndExpectQuicResponse("hello!");
+
+  mock_quic_data.Resume();
+
+  EXPECT_FALSE(http_server_properties_->WasAlternativeServiceRecentlyBroken(
+      alternative_service, kNetworkIsolationKey1));
+  EXPECT_NE(nullptr, http_server_properties_->GetServerNetworkStats(
+                         url::SchemeHostPort("https", request_.url.host(), 443),
+                         kNetworkIsolationKey1));
+  EXPECT_TRUE(http_server_properties_->WasAlternativeServiceRecentlyBroken(
+      alternative_service, kNetworkIsolationKey2));
+  EXPECT_EQ(nullptr, http_server_properties_->GetServerNetworkStats(
+                         url::SchemeHostPort("https", request_.url.host(), 443),
+                         kNetworkIsolationKey2));
+}
+
 TEST_P(QuicNetworkTransactionTest, UseAlternativeServiceForQuicForHttps) {
   MockRead http_reads[] = {
       MockRead("HTTP/1.1 200 OK\r\n"), MockRead(kQuicAlternativeServiceHeader),
@@ -4970,6 +5397,57 @@
   ExpectBrokenAlternateProtocolMapping();
 }
 
+TEST_P(QuicNetworkTransactionTest,
+       BrokenAlternateProtocolWithNetworkIsolationKey) {
+  const url::Origin kOrigin1 = url::Origin::Create(GURL("https://foo.test/"));
+  const net::NetworkIsolationKey kNetworkIsolationKey1(kOrigin1, kOrigin1);
+  const url::Origin kOrigin2 = url::Origin::Create(GURL("https://bar.test/"));
+  const net::NetworkIsolationKey kNetworkIsolationKey2(kOrigin2, kOrigin2);
+
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitWithFeatures(
+      // enabled_features
+      {features::kPartitionHttpServerPropertiesByNetworkIsolationKey,
+       features::kPartitionConnectionsByNetworkIsolationKey},
+      // disabled_features
+      {});
+  // Since HttpServerProperties caches the feature value, have to create a new
+  // one.
+  http_server_properties_ = std::make_unique<HttpServerProperties>();
+
+  // Alternate-protocol job
+  std::unique_ptr<quic::QuicEncryptedPacket> close(
+      ConstructServerConnectionClosePacket(1));
+  MockRead quic_reads[] = {
+      MockRead(ASYNC, close->data(), close->length()),
+      MockRead(ASYNC, ERR_IO_PENDING),  // No more data to read
+      MockRead(ASYNC, OK),              // EOF
+  };
+  StaticSocketDataProvider quic_data(quic_reads, base::span<MockWrite>());
+  socket_factory_.AddSocketDataProvider(&quic_data);
+
+  // Main job which will succeed even though the alternate job fails.
+  MockRead http_reads[] = {
+      MockRead("HTTP/1.1 200 OK\r\n\r\n"), MockRead("hello from http"),
+      MockRead(SYNCHRONOUS, ERR_TEST_PEER_CLOSE_AFTER_NEXT_MOCK_READ),
+      MockRead(ASYNC, OK)};
+
+  StaticSocketDataProvider http_data(http_reads, base::span<MockWrite>());
+  socket_factory_.AddSocketDataProvider(&http_data);
+  socket_factory_.AddSSLSocketDataProvider(&ssl_data_);
+
+  CreateSession();
+  AddQuicAlternateProtocolMapping(
+      MockCryptoClientStream::COLD_START_WITH_CHLO_SENT, kNetworkIsolationKey1);
+  AddQuicAlternateProtocolMapping(
+      MockCryptoClientStream::COLD_START_WITH_CHLO_SENT, kNetworkIsolationKey2);
+  request_.network_isolation_key = kNetworkIsolationKey1;
+  SendRequestAndExpectHttpResponse("hello from http");
+
+  ExpectBrokenAlternateProtocolMapping(kNetworkIsolationKey1);
+  ExpectQuicAlternateProtocolMapping(kNetworkIsolationKey2);
+}
+
 TEST_P(QuicNetworkTransactionTest, BrokenAlternateProtocolReadError) {
   // Alternate-protocol job
   MockRead quic_reads[] = {
@@ -5209,7 +5687,6 @@
   socket_factory_.AddSocketDataProvider(&http_data);
   socket_factory_.AddSSLSocketDataProvider(&ssl_data_);
 
-  AddHangingNonAlternateProtocolSocketData();
   CreateSession();
 
   AddQuicAlternateProtocolMapping(MockCryptoClientStream::ZERO_RTT);
@@ -5222,6 +5699,72 @@
   EXPECT_TRUE(quic_data.AllWriteDataConsumed());
 }
 
+TEST_P(QuicNetworkTransactionTest,
+       FailedZeroRttBrokenAlternateProtocolWithNetworkIsolationKey) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitWithFeatures(
+      // enabled_features
+      {features::kPartitionHttpServerPropertiesByNetworkIsolationKey,
+       features::kPartitionConnectionsByNetworkIsolationKey},
+      // disabled_features
+      {});
+  // Since HttpServerProperties caches the feature value, have to create a new
+  // one.
+  http_server_properties_ = std::make_unique<HttpServerProperties>();
+
+  const url::Origin kOrigin1 = url::Origin::Create(GURL("https://foo.test/"));
+  const net::NetworkIsolationKey kNetworkIsolationKey1(kOrigin1, kOrigin1);
+  const url::Origin kOrigin2 = url::Origin::Create(GURL("https://bar.test/"));
+  const net::NetworkIsolationKey kNetworkIsolationKey2(kOrigin2, kOrigin2);
+
+  // Alternate-protocol job
+  MockRead quic_reads[] = {
+      MockRead(ASYNC, ERR_SOCKET_NOT_CONNECTED),
+  };
+  StaticSocketDataProvider quic_data(quic_reads, base::span<MockWrite>());
+  socket_factory_.AddSocketDataProvider(&quic_data);
+
+  // Second Alternate-protocol job which will race with the TCP job.
+  StaticSocketDataProvider quic_data2(quic_reads, base::span<MockWrite>());
+  socket_factory_.AddSocketDataProvider(&quic_data2);
+
+  // Final job that will proceed when the QUIC job fails.
+  MockRead http_reads[] = {
+      MockRead("HTTP/1.1 200 OK\r\n\r\n"), MockRead("hello from http"),
+      MockRead(SYNCHRONOUS, ERR_TEST_PEER_CLOSE_AFTER_NEXT_MOCK_READ),
+      MockRead(ASYNC, OK)};
+
+  StaticSocketDataProvider http_data(http_reads, base::span<MockWrite>());
+  socket_factory_.AddSocketDataProvider(&http_data);
+  socket_factory_.AddSSLSocketDataProvider(&ssl_data_);
+
+  CreateSession();
+
+  AddQuicAlternateProtocolMapping(MockCryptoClientStream::ZERO_RTT,
+                                  kNetworkIsolationKey1);
+  AddQuicAlternateProtocolMapping(MockCryptoClientStream::ZERO_RTT,
+                                  kNetworkIsolationKey2);
+
+  request_.network_isolation_key = kNetworkIsolationKey1;
+  SendRequestAndExpectHttpResponse("hello from http");
+  EXPECT_TRUE(quic_data.AllReadDataConsumed());
+  EXPECT_TRUE(quic_data.AllWriteDataConsumed());
+
+  ExpectBrokenAlternateProtocolMapping(kNetworkIsolationKey1);
+  ExpectQuicAlternateProtocolMapping(kNetworkIsolationKey2);
+
+  // Subsequent requests using kNetworkIsolationKey1 should not use QUIC.
+  AddHttpDataAndRunRequest();
+  // Requests using other NetworkIsolationKeys can still use QUIC.
+  request_.network_isolation_key = kNetworkIsolationKey2;
+  AddQuicDataAndRunRequest();
+
+  // The last two requests should not have changed the alternative service
+  // mappings.
+  ExpectBrokenAlternateProtocolMapping(kNetworkIsolationKey1);
+  ExpectQuicAlternateProtocolMapping(kNetworkIsolationKey2);
+}
+
 TEST_P(QuicNetworkTransactionTest, DISABLED_HangingZeroRttFallback) {
   // Alternate-protocol job
   MockRead quic_reads[] = {