Add NetworkIsolationKey to SpdySessionKey.

This will allow us to experiment with separating out connections by the
key, which has privacy benefits, but also performance costs. The key
has already been added to the socket pools themselves, but
SpdySessionKey bypasses socket pool GroupIds.

Bug: 963480
Change-Id: I54b75c2a299561c7d40ad790c23a33c191062a81
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1627243
Commit-Queue: Matt Menke <mmenke@chromium.org>
Reviewed-by: Ryan Hamilton <rch@chromium.org>
Cr-Commit-Position: refs/heads/master@{#663258}
diff --git a/net/http/http_network_transaction_unittest.cc b/net/http/http_network_transaction_unittest.cc
index ed84ac42..510b0e8 100644
--- a/net/http/http_network_transaction_unittest.cc
+++ b/net/http/http_network_transaction_unittest.cc
@@ -20893,4 +20893,220 @@
   }
 }
 
+TEST_F(HttpNetworkTransactionTest, NetworkIsolationH2) {
+  NetworkIsolationKey network_isolation_key1(
+      url::Origin::Create(GURL("http://origin1/")));
+  NetworkIsolationKey network_isolation_key2(
+      url::Origin::Create(GURL("http://origin2/")));
+
+  // Whether to use an H2 proxy. When false, uses HTTPS H2 requests without a
+  // proxy, when true, uses HTTP requests over an H2 proxy. It's unnecessary to
+  // test tunneled HTTPS over an H2 proxy, since that path sets up H2 sessions
+  // the same way as the HTTP over H2 proxy case.
+  for (bool use_proxy : {false, true}) {
+    SCOPED_TRACE(use_proxy);
+    if (use_proxy) {
+      session_deps_.proxy_resolution_service =
+          ProxyResolutionService::CreateFixedFromPacResult(
+              "HTTPS proxy:443", TRAFFIC_ANNOTATION_FOR_TESTS);
+    } else {
+      session_deps_.proxy_resolution_service =
+          ProxyResolutionService::CreateDirect();
+    }
+    const char* url1 = nullptr;
+    const char* url2 = nullptr;
+    const char* url3 = nullptr;
+    if (use_proxy) {
+      url1 = "http://foo.test/1";
+      url2 = "http://foo.test/2";
+      url3 = "http://foo.test/3";
+    } else {
+      url1 = "https://foo.test/1";
+      url2 = "https://foo.test/2";
+      url3 = "https://foo.test/3";
+    }
+
+    for (bool partition_connections : {false, true}) {
+      SCOPED_TRACE(partition_connections);
+
+      base::test::ScopedFeatureList feature_list;
+      if (partition_connections) {
+        feature_list.InitAndEnableFeature(
+            features::kPartitionConnectionsByNetworkIsolationKey);
+      } else {
+        feature_list.InitAndDisableFeature(
+            features::kPartitionConnectionsByNetworkIsolationKey);
+      }
+
+      std::unique_ptr<HttpNetworkSession> session(
+          CreateSession(&session_deps_));
+
+      // Reads and writes for the unpartitioned case, where only one socket is
+      // used.
+
+      SpdyTestUtil spdy_util;
+      spdy::SpdySerializedFrame unpartitioned_req1(
+          spdy_util.ConstructSpdyGet(url1, 1, LOWEST));
+      spdy::SpdySerializedFrame unpartitioned_response1(
+          spdy_util.ConstructSpdyGetReply(nullptr, 0, 1));
+      spdy::SpdySerializedFrame unpartitioned_body1(
+          spdy_util.ConstructSpdyDataFrame(1, "1", true));
+      spdy_util.UpdateWithStreamDestruction(1);
+
+      spdy::SpdySerializedFrame unpartitioned_req2(
+          spdy_util.ConstructSpdyGet(url2, 3, LOWEST));
+      spdy::SpdySerializedFrame unpartitioned_response2(
+          spdy_util.ConstructSpdyGetReply(nullptr, 0, 3));
+      spdy::SpdySerializedFrame unpartitioned_body2(
+          spdy_util.ConstructSpdyDataFrame(3, "2", true));
+      spdy_util.UpdateWithStreamDestruction(3);
+
+      spdy::SpdySerializedFrame unpartitioned_req3(
+          spdy_util.ConstructSpdyGet(url3, 5, LOWEST));
+      spdy::SpdySerializedFrame unpartitioned_response3(
+          spdy_util.ConstructSpdyGetReply(nullptr, 0, 5));
+      spdy::SpdySerializedFrame unpartitioned_body3(
+          spdy_util.ConstructSpdyDataFrame(5, "3", true));
+
+      const MockWrite kUnpartitionedWrites[] = {
+          CreateMockWrite(unpartitioned_req1, 0),
+          CreateMockWrite(unpartitioned_req2, 3),
+          CreateMockWrite(unpartitioned_req3, 6),
+      };
+
+      const MockRead kUnpartitionedReads[] = {
+          CreateMockRead(unpartitioned_response1, 1),
+          CreateMockRead(unpartitioned_body1, 2),
+          CreateMockRead(unpartitioned_response2, 4),
+          CreateMockRead(unpartitioned_body2, 5),
+          CreateMockRead(unpartitioned_response3, 7),
+          CreateMockRead(unpartitioned_body3, 8),
+          MockRead(SYNCHRONOUS, ERR_IO_PENDING, 9),
+      };
+
+      SequencedSocketData unpartitioned_data(kUnpartitionedReads,
+                                             kUnpartitionedWrites);
+
+      // Reads and writes for the partitioned case, where two sockets are used.
+
+      SpdyTestUtil spdy_util2;
+      spdy::SpdySerializedFrame partitioned_req1(
+          spdy_util2.ConstructSpdyGet(url1, 1, LOWEST));
+      spdy::SpdySerializedFrame partitioned_response1(
+          spdy_util2.ConstructSpdyGetReply(nullptr, 0, 1));
+      spdy::SpdySerializedFrame partitioned_body1(
+          spdy_util2.ConstructSpdyDataFrame(1, "1", true));
+      spdy_util2.UpdateWithStreamDestruction(1);
+
+      spdy::SpdySerializedFrame partitioned_req3(
+          spdy_util2.ConstructSpdyGet(url3, 3, LOWEST));
+      spdy::SpdySerializedFrame partitioned_response3(
+          spdy_util2.ConstructSpdyGetReply(nullptr, 0, 3));
+      spdy::SpdySerializedFrame partitioned_body3(
+          spdy_util2.ConstructSpdyDataFrame(3, "3", true));
+
+      const MockWrite kPartitionedWrites1[] = {
+          CreateMockWrite(partitioned_req1, 0),
+          CreateMockWrite(partitioned_req3, 3),
+      };
+
+      const MockRead kPartitionedReads1[] = {
+          CreateMockRead(partitioned_response1, 1),
+          CreateMockRead(partitioned_body1, 2),
+          CreateMockRead(partitioned_response3, 4),
+          CreateMockRead(partitioned_body3, 5),
+          MockRead(SYNCHRONOUS, ERR_IO_PENDING, 6),
+      };
+
+      SpdyTestUtil spdy_util3;
+      spdy::SpdySerializedFrame partitioned_req2(
+          spdy_util3.ConstructSpdyGet(url2, 1, LOWEST));
+      spdy::SpdySerializedFrame partitioned_response2(
+          spdy_util3.ConstructSpdyGetReply(nullptr, 0, 1));
+      spdy::SpdySerializedFrame partitioned_body2(
+          spdy_util3.ConstructSpdyDataFrame(1, "2", true));
+
+      const MockWrite kPartitionedWrites2[] = {
+          CreateMockWrite(partitioned_req2, 0),
+      };
+
+      const MockRead kPartitionedReads2[] = {
+          CreateMockRead(partitioned_response2, 1),
+          CreateMockRead(partitioned_body2, 2),
+          MockRead(SYNCHRONOUS, ERR_IO_PENDING, 3),
+      };
+
+      SequencedSocketData partitioned_data1(kPartitionedReads1,
+                                            kPartitionedWrites1);
+      SequencedSocketData partitioned_data2(kPartitionedReads2,
+                                            kPartitionedWrites2);
+
+      // No need to segment SSLDataProviders by whether or not partitioning is
+      // enabled.
+      SSLSocketDataProvider ssl_data1(ASYNC, OK);
+      ssl_data1.next_proto = kProtoHTTP2;
+      SSLSocketDataProvider ssl_data2(ASYNC, OK);
+      ssl_data2.next_proto = kProtoHTTP2;
+
+      if (partition_connections) {
+        session_deps_.socket_factory->AddSocketDataProvider(&partitioned_data1);
+        session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data1);
+        session_deps_.socket_factory->AddSocketDataProvider(&partitioned_data2);
+        session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data2);
+      } else {
+        session_deps_.socket_factory->AddSocketDataProvider(
+            &unpartitioned_data);
+        session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data1);
+      }
+
+      TestCompletionCallback callback;
+      HttpRequestInfo request1;
+      request1.method = "GET";
+      request1.url = GURL(url1);
+      request1.traffic_annotation =
+          net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS);
+      request1.network_isolation_key = network_isolation_key1;
+      auto trans1 =
+          std::make_unique<HttpNetworkTransaction>(LOWEST, session.get());
+      int rv =
+          trans1->Start(&request1, callback.callback(), NetLogWithSource());
+      EXPECT_THAT(callback.GetResult(rv), IsOk());
+      std::string response_data1;
+      EXPECT_THAT(ReadTransaction(trans1.get(), &response_data1), IsOk());
+      EXPECT_EQ("1", response_data1);
+      trans1.reset();
+
+      HttpRequestInfo request2;
+      request2.method = "GET";
+      request2.url = GURL(url2);
+      request2.traffic_annotation =
+          net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS);
+      request2.network_isolation_key = network_isolation_key2;
+      auto trans2 =
+          std::make_unique<HttpNetworkTransaction>(LOWEST, session.get());
+      rv = trans2->Start(&request2, callback.callback(), NetLogWithSource());
+      EXPECT_THAT(callback.GetResult(rv), IsOk());
+      std::string response_data2;
+      EXPECT_THAT(ReadTransaction(trans2.get(), &response_data2), IsOk());
+      EXPECT_EQ("2", response_data2);
+      trans2.reset();
+
+      HttpRequestInfo request3;
+      request3.method = "GET";
+      request3.url = GURL(url3);
+      request3.traffic_annotation =
+          net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS);
+      request3.network_isolation_key = network_isolation_key1;
+      auto trans3 =
+          std::make_unique<HttpNetworkTransaction>(LOWEST, session.get());
+      rv = trans3->Start(&request3, callback.callback(), NetLogWithSource());
+      EXPECT_THAT(callback.GetResult(rv), IsOk());
+      std::string response_data3;
+      EXPECT_THAT(ReadTransaction(trans3.get(), &response_data3), IsOk());
+      EXPECT_EQ("3", response_data3);
+      trans3.reset();
+    }
+  }
+}
+
 }  // namespace net
diff --git a/net/http/http_proxy_connect_job.cc b/net/http/http_proxy_connect_job.cc
index 4bc16b74..d488656 100644
--- a/net/http/http_proxy_connect_job.cc
+++ b/net/http/http_proxy_connect_job.cc
@@ -136,13 +136,15 @@
     const HostPortPair& endpoint,
     bool is_trusted_proxy,
     bool tunnel,
-    const NetworkTrafficAnnotationTag traffic_annotation)
+    const NetworkTrafficAnnotationTag traffic_annotation,
+    const NetworkIsolationKey& network_isolation_key)
     : transport_params_(std::move(transport_params)),
       ssl_params_(std::move(ssl_params)),
       is_quic_(is_quic),
       endpoint_(endpoint),
       is_trusted_proxy_(is_trusted_proxy),
       tunnel_(tunnel),
+      network_isolation_key_(network_isolation_key),
       traffic_annotation_(traffic_annotation) {
   // This is either a connection to an HTTP proxy or an SSL/QUIC proxy.
   DCHECK(transport_params_ || ssl_params_);
@@ -460,12 +462,8 @@
 int HttpProxyConnectJob::DoSSLConnect() {
   DCHECK(params_->ssl_params());
   if (params_->tunnel()) {
-    SpdySessionKey key(
-        params_->ssl_params()->GetDirectConnectionParams()->destination(),
-        ProxyServer::Direct(), PRIVACY_MODE_DISABLED,
-        SpdySessionKey::IsProxySession::kTrue, socket_tag());
     if (common_connect_job_params()->spdy_session_pool->FindAvailableSession(
-            key, /* enable_ip_based_pooling = */ false,
+            CreateSpdySessionKey(), /* enable_ip_based_pooling = */ false,
             /* is_websocket = */ false, net_log())) {
       using_spdy_ = true;
       next_state_ = STATE_SPDY_PROXY_CREATE_STREAM;
@@ -587,10 +585,7 @@
   // longer to timeout than it should.
   ResetTimer(kHttpProxyConnectJobTunnelTimeout);
 
-  SpdySessionKey key(
-      params_->ssl_params()->GetDirectConnectionParams()->destination(),
-      ProxyServer::Direct(), PRIVACY_MODE_DISABLED,
-      SpdySessionKey::IsProxySession::kTrue, socket_tag());
+  SpdySessionKey key = CreateSpdySessionKey();
   base::WeakPtr<SpdySession> spdy_session =
       common_connect_job_params()->spdy_session_pool->FindAvailableSession(
           key, /* enable_ip_based_pooling = */ false,
@@ -820,4 +815,12 @@
   return http_user_agent_settings()->GetUserAgent();
 }
 
+SpdySessionKey HttpProxyConnectJob::CreateSpdySessionKey() const {
+  return SpdySessionKey(
+      params_->ssl_params()->GetDirectConnectionParams()->destination(),
+      ProxyServer::Direct(), PRIVACY_MODE_DISABLED,
+      SpdySessionKey::IsProxySession::kTrue, socket_tag(),
+      params_->network_isolation_key());
+}
+
 }  // namespace net
diff --git a/net/http/http_proxy_connect_job.h b/net/http/http_proxy_connect_job.h
index f5c11df..ef12a1e 100644
--- a/net/http/http_proxy_connect_job.h
+++ b/net/http/http_proxy_connect_job.h
@@ -14,11 +14,13 @@
 #include "base/time/time.h"
 #include "net/base/host_port_pair.h"
 #include "net/base/net_export.h"
+#include "net/base/network_isolation_key.h"
 #include "net/base/request_priority.h"
 #include "net/http/http_auth.h"
 #include "net/quic/quic_chromium_client_session.h"
 #include "net/socket/connect_job.h"
 #include "net/socket/ssl_client_socket.h"
+#include "net/spdy/spdy_session_key.h"
 #include "net/ssl/ssl_cert_request_info.h"
 #include "net/traffic_annotation/network_traffic_annotation.h"
 
@@ -48,7 +50,8 @@
                         const HostPortPair& endpoint,
                         bool is_trusted_proxy,
                         bool tunnel,
-                        const NetworkTrafficAnnotationTag traffic_annotation);
+                        const NetworkTrafficAnnotationTag traffic_annotation,
+                        const NetworkIsolationKey& network_isolation_key);
 
   const scoped_refptr<TransportSocketParams>& transport_params() const {
     return transport_params_;
@@ -60,6 +63,9 @@
   const HostPortPair& endpoint() const { return endpoint_; }
   bool is_trusted_proxy() const { return is_trusted_proxy_; }
   bool tunnel() const { return tunnel_; }
+  const NetworkIsolationKey& network_isolation_key() const {
+    return network_isolation_key_;
+  }
   const NetworkTrafficAnnotationTag traffic_annotation() const {
     return traffic_annotation_;
   }
@@ -74,6 +80,7 @@
   const HostPortPair endpoint_;
   const bool is_trusted_proxy_;
   const bool tunnel_;
+  const NetworkIsolationKey network_isolation_key_;
   const NetworkTrafficAnnotationTag traffic_annotation_;
 
   DISALLOW_COPY_AND_ASSIGN(HttpProxySocketParams);
@@ -200,6 +207,8 @@
 
   std::string GetUserAgent() const;
 
+  SpdySessionKey CreateSpdySessionKey() const;
+
   scoped_refptr<HttpProxySocketParams> params_;
 
   scoped_refptr<SSLCertRequestInfo> ssl_cert_request_info_;
diff --git a/net/http/http_proxy_connect_job_unittest.cc b/net/http/http_proxy_connect_job_unittest.cc
index 46453a0..f555b9f1 100644
--- a/net/http/http_proxy_connect_job_unittest.cc
+++ b/net/http/http_proxy_connect_job_unittest.cc
@@ -21,6 +21,7 @@
 #include "base/test/scoped_task_environment.h"
 #include "build/build_config.h"
 #include "net/base/host_port_pair.h"
+#include "net/base/network_isolation_key.h"
 #include "net/base/test_proxy_delegate.h"
 #include "net/dns/mock_host_resolver.h"
 #include "net/http/http_network_session.h"
@@ -133,7 +134,8 @@
     return base::MakeRefCounted<HttpProxySocketParams>(
         CreateHttpProxyParams(), CreateHttpsProxyParams(), false /* is_quic */,
         HostPortPair(kEndpointHost, tunnel ? 443 : 80),
-        /*is_trusted_proxy=*/false, tunnel, TRAFFIC_ANNOTATION_FOR_TESTS);
+        /*is_trusted_proxy=*/false, tunnel, TRAFFIC_ANNOTATION_FOR_TESTS,
+        NetworkIsolationKey());
   }
 
   std::unique_ptr<HttpProxyConnectJob> CreateConnectJobForHttpRequest(
diff --git a/net/http/http_stream_factory_job.cc b/net/http/http_stream_factory_job.cc
index 0d905b5..ababbde0 100644
--- a/net/http/http_stream_factory_job.cc
+++ b/net/http/http_stream_factory_job.cc
@@ -160,13 +160,14 @@
       pushed_stream_id_(kNoPushedStreamFound),
       spdy_session_direct_(
           !(proxy_info.is_https() && origin_url_.SchemeIs(url::kHttpScheme))),
-      spdy_session_key_(using_quic_
-                            ? SpdySessionKey()
-                            : GetSpdySessionKey(spdy_session_direct_,
-                                                proxy_info_.proxy_server(),
-                                                origin_url_,
-                                                request_info_.privacy_mode,
-                                                request_info_.socket_tag)),
+      spdy_session_key_(
+          using_quic_ ? SpdySessionKey()
+                      : GetSpdySessionKey(spdy_session_direct_,
+                                          proxy_info_.proxy_server(),
+                                          origin_url_,
+                                          request_info_.privacy_mode,
+                                          request_info_.socket_tag,
+                                          request_info_.network_isolation_key)),
       stream_type_(HttpStreamRequest::BIDIRECTIONAL_STREAM),
       init_connection_already_resumed_(false),
       ptr_factory_(this) {
@@ -374,17 +375,19 @@
     const ProxyServer& proxy_server,
     const GURL& origin_url,
     PrivacyMode privacy_mode,
-    const SocketTag& socket_tag) {
+    const SocketTag& socket_tag,
+    const NetworkIsolationKey& network_isolation_key) {
   // In the case that we're using an HTTPS proxy for an HTTP url, look for a
   // HTTP/2 proxy session *to* the proxy, instead of to the  origin server.
   if (!spdy_session_direct) {
     return SpdySessionKey(proxy_server.host_port_pair(), ProxyServer::Direct(),
                           PRIVACY_MODE_DISABLED,
-                          SpdySessionKey::IsProxySession::kTrue, socket_tag);
+                          SpdySessionKey::IsProxySession::kTrue, socket_tag,
+                          network_isolation_key);
   }
   return SpdySessionKey(HostPortPair::FromURL(origin_url), proxy_server,
                         privacy_mode, SpdySessionKey::IsProxySession::kFalse,
-                        socket_tag);
+                        socket_tag, network_isolation_key);
 }
 
 bool HttpStreamFactory::Job::CanUseExistingSpdySession() const {
diff --git a/net/http/http_stream_factory_job.h b/net/http/http_stream_factory_job.h
index 5f09a7f..3a5488e 100644
--- a/net/http/http_stream_factory_job.h
+++ b/net/http/http_stream_factory_job.h
@@ -334,11 +334,13 @@
                               bool using_ssl);
 
   // Called in Job constructor. Use |spdy_session_key_| after construction.
-  static SpdySessionKey GetSpdySessionKey(bool spdy_session_direct,
-                                          const ProxyServer& proxy_server,
-                                          const GURL& origin_url,
-                                          PrivacyMode privacy_mode,
-                                          const SocketTag& socket_tag);
+  static SpdySessionKey GetSpdySessionKey(
+      bool spdy_session_direct,
+      const ProxyServer& proxy_server,
+      const GURL& origin_url,
+      PrivacyMode privacy_mode,
+      const SocketTag& socket_tag,
+      const NetworkIsolationKey& network_isolation_key);
 
   // Returns true if the current request can use an existing spdy session.
   bool CanUseExistingSpdySession() const;
diff --git a/net/socket/client_socket_pool.cc b/net/socket/client_socket_pool.cc
index 162a63cf..62eb8f4 100644
--- a/net/socket/client_socket_pool.cc
+++ b/net/socket/client_socket_pool.cc
@@ -168,14 +168,16 @@
         &OnHostResolution, common_connect_job_params->spdy_session_pool,
         SpdySessionKey(group_id.destination(), proxy_server,
                        group_id.privacy_mode(),
-                       SpdySessionKey::IsProxySession::kFalse, socket_tag),
+                       SpdySessionKey::IsProxySession::kFalse, socket_tag,
+                       group_id.network_isolation_key()),
         is_for_websockets);
   } else if (proxy_server.is_https()) {
     resolution_callback = base::BindRepeating(
         &OnHostResolution, common_connect_job_params->spdy_session_pool,
         SpdySessionKey(proxy_server.host_port_pair(), ProxyServer::Direct(),
                        group_id.privacy_mode(),
-                       SpdySessionKey::IsProxySession::kTrue, socket_tag),
+                       SpdySessionKey::IsProxySession::kTrue, socket_tag,
+                       group_id.network_isolation_key()),
         is_for_websockets);
   }
 
@@ -184,7 +186,8 @@
       socket_params->ssl_config_for_origin(),
       socket_params->ssl_config_for_proxy(), is_for_websockets,
       group_id.privacy_mode(), resolution_callback, request_priority,
-      socket_tag, common_connect_job_params, delegate);
+      socket_tag, group_id.network_isolation_key(), common_connect_job_params,
+      delegate);
 }
 
 }  // namespace net
diff --git a/net/socket/connect_job.cc b/net/socket/connect_job.cc
index 97756b1..871d0f5 100644
--- a/net/socket/connect_job.cc
+++ b/net/socket/connect_job.cc
@@ -110,6 +110,7 @@
     const OnHostResolutionCallback& resolution_callback,
     RequestPriority request_priority,
     SocketTag socket_tag,
+    const NetworkIsolationKey& network_isolation_key,
     const CommonConnectJobParams* common_connect_job_params,
     ConnectJob::Delegate* delegate) {
   scoped_refptr<HttpProxySocketParams> http_proxy_params;
@@ -135,7 +136,8 @@
       http_proxy_params = base::MakeRefCounted<HttpProxySocketParams>(
           std::move(proxy_tcp_params), std::move(ssl_params),
           proxy_server.is_quic(), endpoint, proxy_server.is_trusted_proxy(),
-          force_tunnel || using_ssl, *proxy_annotation_tag);
+          force_tunnel || using_ssl, *proxy_annotation_tag,
+          network_isolation_key);
     } else {
       DCHECK(proxy_server.is_socks());
       socks_params = base::MakeRefCounted<SOCKSSocketParams>(
diff --git a/net/socket/connect_job.h b/net/socket/connect_job.h
index ced8ed3..6a267fa 100644
--- a/net/socket/connect_job.h
+++ b/net/socket/connect_job.h
@@ -38,6 +38,7 @@
 class HttpUserAgentSettings;
 class NetLog;
 class NetLogWithSource;
+class NetworkIsolationKey;
 class NetworkQualityEstimator;
 struct NetworkTrafficAnnotationTag;
 class ProxyDelegate;
@@ -188,6 +189,7 @@
       const OnHostResolutionCallback& resolution_callback,
       RequestPriority request_priority,
       SocketTag socket_tag,
+      const NetworkIsolationKey& network_isolation_key,
       const CommonConnectJobParams* common_connect_job_params,
       ConnectJob::Delegate* delegate);
 
diff --git a/net/socket/ssl_connect_job_unittest.cc b/net/socket/ssl_connect_job_unittest.cc
index 801dda7..799d232 100644
--- a/net/socket/ssl_connect_job_unittest.cc
+++ b/net/socket/ssl_connect_job_unittest.cc
@@ -17,6 +17,7 @@
 #include "net/base/auth.h"
 #include "net/base/load_timing_info.h"
 #include "net/base/net_errors.h"
+#include "net/base/network_isolation_key.h"
 #include "net/cert/ct_policy_enforcer.h"
 #include "net/cert/mock_cert_verifier.h"
 #include "net/cert/multi_log_ct_verifier.h"
@@ -107,7 +108,8 @@
                                       HostPortPair("host", 80),
                                       /*is_trusted_proxy=*/false,
                                       /*tunnel=*/true,
-                                      TRAFFIC_ANNOTATION_FOR_TESTS)),
+                                      TRAFFIC_ANNOTATION_FOR_TESTS,
+                                      NetworkIsolationKey())),
         common_connect_job_params_(session_->CreateCommonConnectJobParams()) {
     ssl_config_service_->GetSSLConfig(&ssl_config_);
 
diff --git a/net/spdy/spdy_session.cc b/net/spdy/spdy_session.cc
index 0294562..6e71a07 100644
--- a/net/spdy/spdy_session.cc
+++ b/net/spdy/spdy_session.cc
@@ -1643,10 +1643,10 @@
 
   socket_->ApplySocketTag(new_tag);
 
-  SpdySessionKey new_key(spdy_session_key_.host_port_pair(),
-                         spdy_session_key_.proxy_server(),
-                         spdy_session_key_.privacy_mode(),
-                         spdy_session_key_.is_proxy_session(), new_tag);
+  SpdySessionKey new_key(
+      spdy_session_key_.host_port_pair(), spdy_session_key_.proxy_server(),
+      spdy_session_key_.privacy_mode(), spdy_session_key_.is_proxy_session(),
+      new_tag, spdy_session_key_.network_isolation_key());
   spdy_session_key_ = new_key;
 
   return true;
diff --git a/net/spdy/spdy_session_key.cc b/net/spdy/spdy_session_key.cc
index 9593864..dd83efc 100644
--- a/net/spdy/spdy_session_key.cc
+++ b/net/spdy/spdy_session_key.cc
@@ -6,8 +6,10 @@
 
 #include <tuple>
 
+#include "base/feature_list.h"
 #include "base/logging.h"
 #include "base/trace_event/memory_usage_estimator.h"
+#include "net/base/features.h"
 #include "net/base/host_port_pair.h"
 
 namespace net {
@@ -18,11 +20,17 @@
                                const ProxyServer& proxy_server,
                                PrivacyMode privacy_mode,
                                IsProxySession is_proxy_session,
-                               const SocketTag& socket_tag)
+                               const SocketTag& socket_tag,
+                               const NetworkIsolationKey& network_isolation_key)
     : host_port_proxy_pair_(host_port_pair, proxy_server),
       privacy_mode_(privacy_mode),
       is_proxy_session_(is_proxy_session),
-      socket_tag_(socket_tag) {
+      socket_tag_(socket_tag),
+      network_isolation_key_(
+          base::FeatureList::IsEnabled(
+              features::kPartitionConnectionsByNetworkIsolationKey)
+              ? network_isolation_key
+              : NetworkIsolationKey()) {
   // IsProxySession::kTrue should only be used with direct connections, since
   // using multiple layers of proxies on top of each other isn't supported.
   DCHECK(is_proxy_session != IsProxySession::kTrue || proxy_server.is_direct());
@@ -38,10 +46,10 @@
 bool SpdySessionKey::operator<(const SpdySessionKey& other) const {
   return std::tie(privacy_mode_, host_port_proxy_pair_.first,
                   host_port_proxy_pair_.second, is_proxy_session_,
-                  socket_tag_) <
+                  network_isolation_key_, socket_tag_) <
          std::tie(other.privacy_mode_, other.host_port_proxy_pair_.first,
                   other.host_port_proxy_pair_.second, other.is_proxy_session_,
-                  other.socket_tag_);
+                  other.network_isolation_key_, other.socket_tag_);
 }
 
 bool SpdySessionKey::operator==(const SpdySessionKey& other) const {
@@ -50,6 +58,7 @@
              other.host_port_proxy_pair_.first) &&
          host_port_proxy_pair_.second == other.host_port_proxy_pair_.second &&
          is_proxy_session_ == other.is_proxy_session_ &&
+         network_isolation_key_ == other.network_isolation_key_ &&
          socket_tag_ == other.socket_tag_;
 }
 
diff --git a/net/spdy/spdy_session_key.h b/net/spdy/spdy_session_key.h
index cc50639..22e9204 100644
--- a/net/spdy/spdy_session_key.h
+++ b/net/spdy/spdy_session_key.h
@@ -6,6 +6,7 @@
 #define NET_SPDY_SPDY_SESSION_KEY_H_
 
 #include "net/base/net_export.h"
+#include "net/base/network_isolation_key.h"
 #include "net/base/privacy_mode.h"
 #include "net/base/proxy_server.h"
 #include "net/socket/socket_tag.h"
@@ -26,11 +27,16 @@
     kTrue,
   };
   SpdySessionKey();
-  SpdySessionKey(const HostPortPair& host_port_pair,
-                 const ProxyServer& proxy_server,
-                 PrivacyMode privacy_mode,
-                 IsProxySession is_proxy_session,
-                 const SocketTag& socket_tag);
+
+  // TODO(mmenke): Remove default |network_isolation_key| value (only used by
+  // tests).
+  SpdySessionKey(
+      const HostPortPair& host_port_pair,
+      const ProxyServer& proxy_server,
+      PrivacyMode privacy_mode,
+      IsProxySession is_proxy_session,
+      const SocketTag& socket_tag,
+      const NetworkIsolationKey& network_isolation_key = NetworkIsolationKey());
 
   SpdySessionKey(const SpdySessionKey& other);
 
@@ -63,6 +69,10 @@
 
   const SocketTag& socket_tag() const { return socket_tag_; }
 
+  const NetworkIsolationKey& network_isolation_key() const {
+    return network_isolation_key_;
+  }
+
   // Returns the estimate of dynamically allocated memory in bytes.
   size_t EstimateMemoryUsage() const;
 
@@ -72,6 +82,8 @@
   PrivacyMode privacy_mode_ = PRIVACY_MODE_DISABLED;
   IsProxySession is_proxy_session_;
   SocketTag socket_tag_;
+  // Used to separate requests made in different contexts.
+  NetworkIsolationKey network_isolation_key_;
 };
 
 }  // namespace net
diff --git a/net/spdy/spdy_session_pool.cc b/net/spdy/spdy_session_pool.cc
index 42ca6bb2..c2a935f 100644
--- a/net/spdy/spdy_session_pool.cc
+++ b/net/spdy/spdy_session_pool.cc
@@ -266,10 +266,11 @@
       DCHECK(available_session_it != available_sessions_.end());
 
       // This session can be reused only if the proxy and privacy settings
-      // match.
+      // match, as well as the NetworkIsolationKey.
       if (!(alias_key.proxy_server() == key.proxy_server()) ||
           !(alias_key.privacy_mode() == key.privacy_mode()) ||
-          !(alias_key.is_proxy_session() == key.is_proxy_session())) {
+          !(alias_key.is_proxy_session() == key.is_proxy_session()) ||
+          !(alias_key.network_isolation_key() == key.network_isolation_key())) {
         continue;
       }
 
@@ -297,7 +298,8 @@
         SpdySessionKey old_key = available_session->spdy_session_key();
         SpdySessionKey new_key(old_key.host_port_pair(), old_key.proxy_server(),
                                old_key.privacy_mode(),
-                               old_key.is_proxy_session(), key.socket_tag());
+                               old_key.is_proxy_session(), key.socket_tag(),
+                               old_key.network_isolation_key());
 
         // If there is already a session with |new_key|, skip this one.
         // It will be found in |aliases_| in a future iteration.
@@ -331,10 +333,12 @@
             ++it;
             continue;
           }
+
           UnmapKey(*it);
-          SpdySessionKey new_pool_alias_key = SpdySessionKey(
-              it->host_port_pair(), it->proxy_server(), it->privacy_mode(),
-              it->is_proxy_session(), key.socket_tag());
+          SpdySessionKey new_pool_alias_key =
+              SpdySessionKey(it->host_port_pair(), it->proxy_server(),
+                             it->privacy_mode(), it->is_proxy_session(),
+                             key.socket_tag(), it->network_isolation_key());
           MapKeyToAvailableSession(new_pool_alias_key, available_session);
           auto old_it = it;
           ++it;
diff --git a/services/network/proxy_resolving_client_socket.cc b/services/network/proxy_resolving_client_socket.cc
index 79d1a065..bd163c1 100644
--- a/services/network/proxy_resolving_client_socket.cc
+++ b/services/network/proxy_resolving_client_socket.cc
@@ -17,6 +17,7 @@
 #include "net/base/ip_address.h"
 #include "net/base/load_flags.h"
 #include "net/base/net_errors.h"
+#include "net/base/network_isolation_key.h"
 #include "net/base/privacy_mode.h"
 #include "net/http/http_auth_controller.h"
 #include "net/http/http_network_session.h"
@@ -282,13 +283,18 @@
           : base::Optional<net::NetworkTrafficAnnotationTag>(
                 proxy_info_.traffic_annotation());
 
-  // Now that the proxy is resolved, create and start a ConnectJob.
+  // Now that the proxy is resolved, create and start a ConnectJob. Using an
+  // empty NetworkIsolationKey means that tunnels over H2 or QUIC proxies will
+  // be shared, which may result in privacy leaks, depending on the nature of
+  // the consumer.
+  //
+  // TODO(mmenke): Investigate that.
   connect_job_ = net::ConnectJob::CreateConnectJob(
       use_tls_, net::HostPortPair::FromURL(url_), proxy_info_.proxy_server(),
       proxy_annotation_tag, &ssl_config_, &ssl_config_, true /* force_tunnel */,
       net::PRIVACY_MODE_DISABLED, net::OnHostResolutionCallback(),
-      net::MAXIMUM_PRIORITY, net::SocketTag(), common_connect_job_params_,
-      this);
+      net::MAXIMUM_PRIORITY, net::SocketTag(), net::NetworkIsolationKey(),
+      common_connect_job_params_, this);
   return connect_job_->Connect();
 }