Bug 1658362 - Add a pref to set alt-svc mapping artificially for testing. r=JuniorHsu,necko-reviewers
authorDragana Damjanovic <dd.mozilla@gmail.com>
Thu, 13 Aug 2020 12:12:49 +0000
changeset 544534 7b70cffeab0ed4bebdea63c8b7117ae6a2cb9787
parent 544533 6debe70da6a65ce3746090e7c631dfa69c5c548a
child 544535 f6038d6a0bb159691462f91ff4dba3c051e7f057
push id37697
push usernerli@mozilla.com
push dateThu, 13 Aug 2020 21:39:42 +0000
treeherdermozilla-central@f46205a42fae [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersJuniorHsu, necko-reviewers
bugs1658362
milestone81.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1658362 - Add a pref to set alt-svc mapping artificially for testing. r=JuniorHsu,necko-reviewers Differential Revision: https://phabricator.services.mozilla.com/D86589
modules/libpref/init/all.js
netwerk/protocol/http/AlternateServices.cpp
netwerk/protocol/http/AlternateServices.h
netwerk/protocol/http/nsHttpChannel.cpp
netwerk/protocol/http/nsHttpHandler.cpp
netwerk/protocol/http/nsHttpHandler.h
netwerk/test/unit/test_altsvc_pref.js
netwerk/test/unit/xpcshell.ini
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -1442,16 +1442,22 @@ pref("network.http.spdy.enable-hpack-dum
 pref("network.http.http3.enabled", false);
 
 // Http3 qpack table size.
 pref("network.http.http3.default-qpack-table-size", 65536); // 64k
 // Maximal number of streams that can be blocked on waiting for qpack
 // instructions.
 pref("network.http.http3.default-max-stream-blocked", 20);
 
+
+// This is only for testing!
+// This adds alt-svc mapping and it has a form of <host-name>;<alt-svc-header>
+// Example: example1.com;h3-29=":443",example2.com;h3-29=":443"
+pref("network.http.http3.alt-svc-mapping-for-testing", "");
+
 // alt-svc allows separation of transport routing from
 // the origin host without using a proxy.
 pref("network.http.altsvc.enabled", true);
 pref("network.http.altsvc.oe", true);
 
 // Turn on 0RTT data for TLS 1.3
 pref("security.tls.enable_0rtt_data", true);
 
--- a/netwerk/protocol/http/AlternateServices.cpp
+++ b/netwerk/protocol/http/AlternateServices.cpp
@@ -54,17 +54,19 @@ bool AltSvcMapping::AcceptableProxy(nsPr
   return !proxyInfo || proxyInfo->IsDirect() || proxyInfo->IsSOCKS();
 }
 
 void AltSvcMapping::ProcessHeader(
     const nsCString& buf, const nsCString& originScheme,
     const nsCString& originHost, int32_t originPort, const nsACString& username,
     const nsACString& topWindowOrigin, bool privateBrowsing, bool isolated,
     nsIInterfaceRequestor* callbacks, nsProxyInfo* proxyInfo, uint32_t caps,
-    const OriginAttributes& originAttributes) {
+    const OriginAttributes& originAttributes,
+    bool aDontValidate /* = false */) {  // aDontValidate is only used for
+                                         // testing
   MOZ_ASSERT(NS_IsMainThread());
   LOG(("AltSvcMapping::ProcessHeader: %s\n", buf.get()));
 
   if (StaticPrefs::network_http_altsvc_proxy_checks() &&
       !AcceptableProxy(proxyInfo)) {
     LOG(("AltSvcMapping::ProcessHeader ignoring due to proxy\n"));
     return;
   }
@@ -173,19 +175,22 @@ void AltSvcMapping::ProcessHeader(
         originAttributes, isHttp3);
     if (mapping->TTL() <= 0) {
       LOG(("Alt Svc invalid map"));
       mapping = nullptr;
       // since this isn't a parse error, let's clear any existing mapping
       // as that would have happened if we had accepted the parameters.
       gHttpHandler->AltServiceCache()->ClearHostMapping(
           originHost, originPort, originAttributes, topWindowOrigin);
-    } else {
+    } else if (!aDontValidate) {
       gHttpHandler->UpdateAltServiceMapping(mapping, proxyInfo, callbacks, caps,
                                             originAttributes);
+    } else {
+      gHttpHandler->UpdateAltServiceMappingWithoutValidation(
+          mapping, proxyInfo, callbacks, caps, originAttributes);
     }
   }
 
   if (numEntriesInHeader) {  // Ignore headers that were just "alt-svc: clear"
     Telemetry::Accumulate(Telemetry::HTTP_ALTSVC_ENTRIES_PER_HEADER,
                           numEntriesInHeader);
   }
 }
@@ -949,16 +954,35 @@ already_AddRefed<AltSvcMapping> AltSvcCa
     return nullptr;
   }
 
   MOZ_ASSERT(rv->Private() == privateBrowsing);
   LOG(("AltSvcCache::LookupMapping %p HIT %p\n", this, rv.get()));
   return rv.forget();
 }
 
+// This is only used for testing!
+void AltSvcCache::UpdateAltServiceMappingWithoutValidation(
+    AltSvcMapping* map, nsProxyInfo* pi, nsIInterfaceRequestor* aCallbacks,
+    uint32_t caps, const OriginAttributes& originAttributes) {
+  MOZ_ASSERT(NS_IsMainThread());
+  if (!mStorage) {
+    return;
+  }
+  RefPtr<AltSvcMapping> existing =
+      LookupMapping(map->HashKey(), map->Private());
+  LOG(
+      ("AltSvcCache::UpdateAltServiceMappingWithoutValidation %p map %p "
+       "existing %p %s",
+       this, map, existing.get(), map->AlternateHost().get()));
+  if (!existing) {
+    map->SetValidated(true);
+  }
+}
+
 void AltSvcCache::UpdateAltServiceMapping(
     AltSvcMapping* map, nsProxyInfo* pi, nsIInterfaceRequestor* aCallbacks,
     uint32_t caps, const OriginAttributes& originAttributes) {
   MOZ_ASSERT(NS_IsMainThread());
   if (!mStorage) {
     return;
   }
   RefPtr<AltSvcMapping> existing =
--- a/netwerk/protocol/http/AlternateServices.h
+++ b/netwerk/protocol/http/AlternateServices.h
@@ -55,24 +55,24 @@ class AltSvcMapping {
                 const nsACString& alternateHost, int32_t alternatePort,
                 const nsACString& npnToken,
                 const OriginAttributes& originAttributes, bool aIsHttp3);
 
  public:
   AltSvcMapping(DataStorage* storage, int32_t storageEpoch,
                 const nsCString& serialized);
 
-  static void ProcessHeader(const nsCString& buf, const nsCString& originScheme,
-                            const nsCString& originHost, int32_t originPort,
-                            const nsACString& username,
-                            const nsACString& topWindowOrigin,
-                            bool privateBrowsing, bool isolated,
-                            nsIInterfaceRequestor* callbacks,
-                            nsProxyInfo* proxyInfo, uint32_t caps,
-                            const OriginAttributes& originAttributes);
+  static void ProcessHeader(
+      const nsCString& buf, const nsCString& originScheme,
+      const nsCString& originHost, int32_t originPort,
+      const nsACString& username, const nsACString& topWindowOrigin,
+      bool privateBrowsing, bool isolated, nsIInterfaceRequestor* callbacks,
+      nsProxyInfo* proxyInfo, uint32_t caps,
+      const OriginAttributes& originAttributes,
+      bool aDontValidate = false);  // aDontValidate is only used for testing!
 
   // AcceptableProxy() decides whether a particular proxy configuration (pi) is
   // suitable for use with Alt-Svc. No proxy (including a null pi) is suitable.
   static bool AcceptableProxy(nsProxyInfo* pi);
 
   const nsCString& AlternateHost() const { return mAlternateHost; }
   const nsCString& OriginHost() const { return mOriginHost; }
   uint32_t OriginPort() const { return mOriginPort; }
@@ -186,16 +186,20 @@ class TransactionObserver final : public
 class AltSvcCache {
  public:
   AltSvcCache() : mStorageEpoch(0) {}
   virtual ~AltSvcCache() = default;
   void UpdateAltServiceMapping(
       AltSvcMapping* map, nsProxyInfo* pi, nsIInterfaceRequestor*,
       uint32_t caps,
       const OriginAttributes& originAttributes);  // main thread
+  void UpdateAltServiceMappingWithoutValidation(
+      AltSvcMapping* map, nsProxyInfo* pi, nsIInterfaceRequestor*,
+      uint32_t caps,
+      const OriginAttributes& originAttributes);  // main thread
   already_AddRefed<AltSvcMapping> GetAltServiceMapping(
       const nsACString& scheme, const nsACString& host, int32_t port, bool pb,
       bool isolated, const nsACString& topWindowOrigin,
       const OriginAttributes& originAttributes, bool aHttp3Allowed);
   void ClearAltServiceMappings();
   void ClearHostMapping(const nsACString& host, int32_t port,
                         const OriginAttributes& originAttributes,
                         const nsACString& topWindowOrigin);
--- a/netwerk/protocol/http/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -6801,16 +6801,20 @@ nsresult nsHttpChannel::BeginConnect() {
       !StaticPrefs::privacy_partition_network_state_connection_with_proxy()) {
     StoragePrincipalHelper::GetOriginAttributes(
         this, originAttributes, StoragePrincipalHelper::eRegularPrincipal);
   } else {
     StoragePrincipalHelper::GetOriginAttributesForNetworkState(
         this, originAttributes);
   }
 
+  gHttpHandler->MaybeAddAltSvcForTesting(mURI, mUsername, GetTopWindowOrigin(),
+                                         mPrivateBrowsing, IsIsolated(),
+                                         mCallbacks, originAttributes);
+
   RefPtr<nsHttpConnectionInfo> connInfo = new nsHttpConnectionInfo(
       host, port, EmptyCString(), mUsername, GetTopWindowOrigin(), proxyInfo,
       originAttributes, isHttps);
   mAllowAltSvc = (mAllowAltSvc && !gHttpHandler->IsSpdyBlacklisted(connInfo));
   bool http3Allowed = !mUpgradeProtocolCallback && !mProxyInfo &&
                       !(mCaps & NS_HTTP_BE_CONSERVATIVE) && !mBeConservative;
 
   RefPtr<AltSvcMapping> mapping;
--- a/netwerk/protocol/http/nsHttpHandler.cpp
+++ b/netwerk/protocol/http/nsHttpHandler.cpp
@@ -1988,16 +1988,33 @@ void nsHttpHandler::PrefsChanged(const c
 
     if (userSetImageAcceptHeader.IsEmpty()) {
       mImageAcceptHeader.Assign(ImageAcceptHeader());
     } else {
       mImageAcceptHeader.Assign(userSetImageAcceptHeader);
     }
   }
 
+  if (PREF_CHANGED(HTTP_PREF("http3.alt-svc-mapping-for-testing"))) {
+    nsAutoCString altSvcMappings;
+    rv = Preferences::GetCString(HTTP_PREF("http3.alt-svc-mapping-for-testing"),
+                                 altSvcMappings);
+    if (NS_SUCCEEDED(rv)) {
+      nsCCharSeparatedTokenizer tokenizer(altSvcMappings, ',');
+      while (tokenizer.hasMoreTokens()) {
+        nsAutoCString token(tokenizer.nextToken());
+        int32_t index = token.Find(";");
+        if (index != kNotFound) {
+          auto* map = new nsCString(Substring(token, index + 1));
+          mAltSvcMappingTemptativeMap.Put(Substring(token, 0, index), map);
+        }
+      }
+    }
+  }
+
   // Enable HTTP response timeout if TCP Keepalives are disabled.
   mResponseTimeoutEnabled =
       !mTCPKeepaliveShortLivedEnabled && !mTCPKeepaliveLongLivedEnabled;
 
 #undef PREF_CHANGED
 #undef MULTI_PREF_CHANGED
 }
 
@@ -2909,9 +2926,42 @@ void nsHttpHandler::SetHttpHandlerInitAr
   mDeviceModelId = aArgs.mDeviceModelId();
 }
 
 void nsHttpHandler::SetDeviceModelId(const nsCString& aModelId) {
   MOZ_ASSERT(XRE_IsSocketProcess());
   mDeviceModelId = aModelId;
 }
 
+void nsHttpHandler::MaybeAddAltSvcForTesting(
+    nsIURI* aUri, const nsACString& aUsername,
+    const nsACString& aTopWindowOrigin, bool aPrivateBrowsing, bool aIsolated,
+    nsIInterfaceRequestor* aCallbacks,
+    const OriginAttributes& aOriginAttributes) {
+  if (!IsHttp3Enabled() || mAltSvcMappingTemptativeMap.IsEmpty()) {
+    return;
+  }
+
+  bool isHttps = false;
+  if (NS_FAILED(aUri->SchemeIs("https", &isHttps)) || !isHttps) {
+    // Only set forr HTTPS.
+    return;
+  }
+
+  nsAutoCString originHost;
+  if (NS_FAILED(aUri->GetAsciiHost(originHost))) {
+    return;
+  }
+
+  nsCString* map = mAltSvcMappingTemptativeMap.Get(originHost);
+  if (map) {
+    int32_t originPort = 80;
+    aUri->GetPort(&originPort);
+    LOG(("nsHttpHandler::MaybeAddAltSvcForTesting for %s map: %s",
+         originHost.get(), PromiseFlatCString(*map).get()));
+    AltSvcMapping::ProcessHeader(*map, nsCString("https"), originHost,
+                                 originPort, aUsername, aTopWindowOrigin,
+                                 aPrivateBrowsing, aIsolated, aCallbacks,
+                                 nullptr, 0, aOriginAttributes, true);
+  }
+}
+
 }  // namespace mozilla::net
--- a/netwerk/protocol/http/nsHttpHandler.h
+++ b/netwerk/protocol/http/nsHttpHandler.h
@@ -343,16 +343,24 @@ class nsHttpHandler final : public nsIHt
   // Alternate Services Maps are main thread only
   void UpdateAltServiceMapping(AltSvcMapping* map, nsProxyInfo* proxyInfo,
                                nsIInterfaceRequestor* callbacks, uint32_t caps,
                                const OriginAttributes& originAttributes) {
     mAltSvcCache->UpdateAltServiceMapping(map, proxyInfo, callbacks, caps,
                                           originAttributes);
   }
 
+  void UpdateAltServiceMappingWithoutValidation(
+      AltSvcMapping* map, nsProxyInfo* proxyInfo,
+      nsIInterfaceRequestor* callbacks, uint32_t caps,
+      const OriginAttributes& originAttributes) {
+    mAltSvcCache->UpdateAltServiceMappingWithoutValidation(
+        map, proxyInfo, callbacks, caps, originAttributes);
+  }
+
   already_AddRefed<AltSvcMapping> GetAltServiceMapping(
       const nsACString& scheme, const nsACString& host, int32_t port, bool pb,
       bool isolated, const nsACString& topWindowOrigin,
       const OriginAttributes& originAttributes, bool aHttp3Allowed) {
     return mAltSvcCache->GetAltServiceMapping(scheme, host, port, pb, isolated,
                                               topWindowOrigin, originAttributes,
                                               aHttp3Allowed);
   }
@@ -483,16 +491,22 @@ class nsHttpHandler final : public nsIHt
   bool GetThroughCaptivePortal() { return mThroughCaptivePortal; }
 
   nsresult CompleteUpgrade(HttpTransactionShell* aTrans,
                            nsIHttpUpgradeListener* aUpgradeListener);
 
   nsresult DoShiftReloadConnectionCleanupWithConnInfo(
       nsHttpConnectionInfo* aCI);
 
+  void MaybeAddAltSvcForTesting(nsIURI* aUri, const nsACString& aUsername,
+                                const nsACString& aTopWindowOrigin,
+                                bool aPrivateBrowsing, bool aIsolated,
+                                nsIInterfaceRequestor* aCallbacks,
+                                const OriginAttributes& aOriginAttributes);
+
  private:
   nsHttpHandler();
 
   virtual ~nsHttpHandler();
 
   [[nodiscard]] nsresult Init();
 
   //
@@ -830,16 +844,21 @@ class nsHttpHandler final : public nsIHt
 
  private:
   nsTHashtable<nsCStringHashKey> mBlacklistedSpdyOrigins;
 
   bool mThroughCaptivePortal;
 
   // The mapping of channel id and the weak pointer of nsHttpChannel.
   nsDataHashtable<nsUint64HashKey, nsWeakPtr> mIDToHttpChannelMap;
+
+  // This is parsed pref network.http.http3.alt-svc-mapping-for-testing.
+  // The pref set artificial altSvc-s for origin for testing.
+  // This maps an origin to an altSvc.
+  nsClassHashtable<nsCStringHashKey, nsCString> mAltSvcMappingTemptativeMap;
 };
 
 extern StaticRefPtr<nsHttpHandler> gHttpHandler;
 
 //-----------------------------------------------------------------------------
 // nsHttpsHandler - thin wrapper to distinguish the HTTP handler from the
 //                  HTTPS handler (even though they share the same impl).
 //-----------------------------------------------------------------------------
new file mode 100644
--- /dev/null
+++ b/netwerk/test/unit/test_altsvc_pref.js
@@ -0,0 +1,140 @@
+"use strict";
+
+let h3Port;
+let h3Route;
+let h3AltSvc;
+let prefs;
+let httpsOrigin;
+
+let tests = [
+  // The altSvc storage may not be up imediately, therefore run test_no_altsvc_pref
+  // for a couple times to wait for the storage.
+  test_no_altsvc_pref,
+  test_no_altsvc_pref,
+  test_no_altsvc_pref,
+  test_altsvc_pref,
+  testsDone,
+];
+
+let current_test = 0;
+
+function run_next_test() {
+  if (current_test < tests.length) {
+    dump("starting test number " + current_test + "\n");
+    tests[current_test]();
+    current_test++;
+  }
+}
+
+function run_test() {
+  let env = Cc["@mozilla.org/process/environment;1"].getService(
+    Ci.nsIEnvironment
+  );
+  h3Port = env.get("MOZHTTP3_PORT");
+  Assert.notEqual(h3Port, null);
+  Assert.notEqual(h3Port, "");
+  h3AltSvc = ":" + h3Port;
+
+  h3Route = "foo.example.com:" + h3Port;
+  do_get_profile();
+  prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
+
+  prefs.setBoolPref("network.http.http3.enabled", true);
+  prefs.setCharPref("network.dns.localDomains", "foo.example.com");
+  prefs.setBoolPref("network.dns.disableIPv6", true);
+
+  // The certificate for the http3server server is for foo.example.com and
+  // is signed by http2-ca.pem so add that cert to the trust list as a
+  // signing cert.
+  let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
+    Ci.nsIX509CertDB
+  );
+  addCertFromFile(certdb, "http2-ca.pem", "CTu,u,u");
+  httpsOrigin = "https://foo.example.com/";
+
+  run_next_test();
+}
+
+let Http3CheckListener = function() {};
+
+Http3CheckListener.prototype = {
+  expectedRoute: "",
+  expectedStatus: Cr.NS_OK,
+
+  onStartRequest: function testOnStartRequest(request) {
+    Assert.ok(request instanceof Ci.nsIHttpChannel);
+    Assert.equal(request.status, this.expectedStatus);
+    if (Components.isSuccessCode(this.expectedStatus)) {
+      Assert.equal(request.responseStatus, 200);
+    }
+  },
+
+  onDataAvailable: function testOnDataAvailable(request, stream, off, cnt) {
+    read_stream(stream, cnt);
+  },
+
+  onStopRequest: function testOnStopRequest(request, status) {
+    Assert.equal(status, this.expectedStatus);
+    if (Components.isSuccessCode(this.expectedStatus)) {
+      Assert.equal(request.responseStatus, 200);
+      let routed = "NA";
+      try {
+        routed = request.getRequestHeader("Alt-Used");
+      } catch (e) {}
+      dump("routed is " + routed + "\n");
+
+      Assert.equal(routed, this.expectedRoute);
+
+      let httpVersion = "";
+      try {
+        httpVersion = request.protocolVersion;
+      } catch (e) {}
+      Assert.equal(httpVersion, "h3");
+    }
+
+    run_next_test();
+    do_test_finished();
+  },
+};
+
+function makeChan(uri) {
+  let chan = NetUtil.newChannel({
+    uri,
+    loadUsingSystemPrincipal: true,
+  }).QueryInterface(Ci.nsIHttpChannel);
+  chan.loadFlags = Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI;
+  return chan;
+}
+
+function test_no_altsvc_pref() {
+  dump("test_no_altsvc_pref");
+  do_test_pending();
+
+  let chan = makeChan(httpsOrigin + "http3-test");
+  let listener = new Http3CheckListener();
+  listener.expectedStatus = Cr.NS_ERROR_CONNECTION_REFUSED;
+  chan.asyncOpen(listener);
+}
+
+function test_altsvc_pref() {
+  dump("test_altsvc_pref");
+  do_test_pending();
+
+  prefs.setCharPref(
+    "network.http.http3.alt-svc-mapping-for-testing",
+    "foo.example.com;h3-27=" + h3AltSvc
+  );
+
+  let chan = makeChan(httpsOrigin + "http3-test");
+  let listener = new Http3CheckListener();
+  listener.expectedRoute = h3Route;
+  chan.asyncOpen(listener);
+}
+
+function testsDone() {
+  prefs.clearUserPref("network.http.http3.enabled");
+  prefs.clearUserPref("network.dns.localDomains");
+  prefs.clearUserPref("network.dns.disableIPv6");
+  prefs.clearUserPref("network.http.http3.alt-svc-mapping-for-testing");
+  dump("testDone\n");
+}
--- a/netwerk/test/unit/xpcshell.ini
+++ b/netwerk/test/unit/xpcshell.ini
@@ -440,8 +440,10 @@ skip-if = asan || tsan || os == 'win' ||
 skip-if = os == "android"
 [test_trr_case_sensitivity.js]
 skip-if = os == "android"
 [test_trr_proxy.js]
 [test_trr_cname_chain.js]
 skip-if = os == "android"
 [test_http_sfv.js]
 [test_blob_channelname.js]
+[test_altsvc_pref.js]
+skip-if = asan || tsan || os == 'win' || os =='android'