Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DNS Prefetching on HTTP sites #548

Closed
TestPolygon opened this issue Apr 27, 2019 · 14 comments
Closed

DNS Prefetching on HTTP sites #548

TestPolygon opened this issue Apr 27, 2019 · 14 comments
Labels
Chromium specific to Chromium/Chrome enhancement New feature or request fixed issue has been addressed

Comments

@TestPolygon
Copy link

TestPolygon commented Apr 27, 2019

Description

This issue is on bugs.chromium.org: https://bugs.chromium.org/p/chromium/issues/detail?id=955588

Visiting of HTTP hosted sites causes DNS Prefetching of all links inside the HTML document: all href of <a> and <link> tags.

Though HTTP is non-encrypted connection, I would prefer do not have such behavior of the browser.
In case, if you use HTTPS proxy for connection to a HTTP hosted site (and block downloading of all third party elements) you can have the private channal between you and the proxy:
[You - ISP - Internet - Proxy] - Internet - Host

But behavior of the browser performs DNS Prefetching of all links inside the document even you does not connect to them. And it breaks privacy of this channel.

But: In incognito mode this does not happens, except <link rel="prerender" href="...">

I (and you) can disable DNS Prefetching of <a> tags by inserting HTTP Response Header "x-dns-prefetch-control": "off". It works. Can you add this functional? I have tested it via ModHeader extension.

uBO allow to block DNS Prefetchinf of the next:
<link rel="preload" as="..." href="...">
<link rel="prefetch" href="...">
when "href" of <link> is domain that blocks by uBO.

But these:
<link rel="preconnect" href="...">
<link rel="dns-prefetch" href="...">
<link rel="prerender" href="..."> (this "works" even in incognito mode)
cause DNS Prefetching even uBO blocks the domain containing in "href" attribute of these <link>.
Is it possible to work around it?

The useful userscript for testing:

const suffix = Math.floor(Math.random()*1000);    
function insertHTML(html) {
    document.querySelector("head").insertAdjacentHTML("afterBegin", html);
}

insertHTML(`<a                              href="https://0-just-anchor--${suffix}-test.org/">`);          // OK, disables by HTTP Header
insertHTML(`<link rel="preload" as="script" href="https://1-preload------${suffix}-test.org/script.js">`); // OK, net::ERR_BLOCKED_BY_CLIENT
insertHTML(`<link rel="prefetch"            href="https://2-prefetch-----${suffix}-test.org/">`);          // OK, net::ERR_BLOCKED_BY_CLIENT
insertHTML(`<link rel="preconnect"          href="https://3-preconnect---${suffix}-test.org/">`); // BAD, but does not work in incognito mode
insertHTML(`<link rel="dns-prefetch"        href="https://4-dns-prefetch-${suffix}-test.org/">`); // BAD, but does not work in incognito mode
insertHTML(`<link rel="prerender"           href="https://5-prerender----${suffix}-test.org/">`); // VERY BAD, works even in incognito mode

A specific URLs where the issue occurs

http://*/*
For example: http://example.com

Steps to Reproduce

  1. Enable Disable pre-fetching (to prevent any connection for blocked network requests) in uBO
  2. Open DNSQuerySniffer
  3. Open any HTTP site (not HTTPS), for example: http://joyre{}actor.cc/tag/котэ/best (remove {})
  4. See, that the browser prefetched all links in the page. Links (href) in <a>, <link> tags was DNS resolved.

Expected behavior:

DNS resolving should happen only for domains of resources that were downloaded.

Actual behavior:

All href in <a>, <link> tags was DNS resolved.

Your environment

  • uBlock Origin version: v1.18.16
  • Browser: Chromium 73 based browser.
  • Operating System: Windows 10
@gwarser

This comment has been minimized.

@gwarser gwarser closed this as completed Apr 27, 2019
@gwarser gwarser reopened this Apr 27, 2019
@gorhill
Copy link
Member

gorhill commented Apr 27, 2019

by inserting HTTP Response Header "x-dns-prefetch-control": "off"

Sounds like a good idea to me.

@gwarser
Copy link

gwarser commented Apr 27, 2019

Weird. In Chrome Version 74.0.3729.108 in my Manjaro this option does not exist in chrome://flags even with different name as in https://www.ghacks.net/2019/04/23/missing-chromes-use-a-prediction-service-setting/

Changing uBO setting does nothing, http/https does not matter. I need to check this again

@gwarser gwarser added enhancement New feature or request Chromium specific to Chromium/Chrome labels Apr 27, 2019
@gorhill
Copy link
Member

gorhill commented Apr 27, 2019

It does help, but this does not address rel="preconnect" and rel="preload" as far as I can tell (using Chromium's chrome://net-export/) -- which is also used on the web page given in repro steps.

I blocked all 3rd-party scripts/frames for the test, to minimize variability across page loads. This is the results with uBO's "Disable prefetch" enabled:

without header with header blocked
anime.reactor.cc ?
avatars.mds.yandex.net avatars.mds.yandex.net ?
fonts.w.tools fonts.w.tools
img0.joyreactor.cc img0.joyreactor.cc
img1.joyreactor.cc img1.joyreactor.cc
joyreactor.cc joyreactor.cc
joyreactor.com ?
marketgid.com yes
polit.reactor.cc ?
show.ctrmanager.com show.ctrmanager.com yes
storage.mds.yandex.net storage.mds.yandex.net ?
twitter.com ?
vkontakte.ru ?
www .facebook.com ?
www .liveinternet.ru ?
ya.ru ?
yastatic.net yastatic.net ?
ysa-static.passport.yandex.ru ysa-static.passport.yandex.ru ?

The ? in the "blocked" column means that the entry does not appear in uBO's popup panel, while "yes" means it is shown as blocked in the panel.

Observations from above:

  • the header X-DNS-Prefetch-Control will be disregarded whenever a rel="preconnect"/rel="preload" is used
  • when rel="preconnect"/rel="preload" is not used, Chromium still ignores the "Disable prefetch" setting -- and this is when the header X-DNS-Prefetch-Control is most useful.

gorhill added a commit to gorhill/uBlock that referenced this issue Apr 27, 2019
Related issue:
- uBlockOrigin/uBlock-issues#548

The fix applies only to Chromium-based browsers -- a
`X-DNS-Prefetch-Control` header[1] will be unconditionally
injected when uBO's "Disable pre-fetching" setting is
enabled (it is by default).

This is a mitigation, this does not completely fix the issue
of the setting "Disable pre-fetching" being disregarded on
Chromium-based browsers when sites use
`preconnect`/`preload`.

[1] https://developer.mozilla.org/docs/Web/HTTP/Headers/X-DNS-Prefetch-Control
@uBlock-user uBlock-user added the fixed issue has been addressed label Apr 27, 2019
@uBlock-user
Copy link
Contributor

For preconnect/prerender -- https://bugs.chromium.org/p/chromium/issues/detail?id=786038

@gwarser
Copy link

gwarser commented Apr 27, 2019

Easier to watch on linux by:

script -q -c "sudo tcpdump -l port 53 2>/dev/null | grep --line-buffered ' A? ' | cut -d' ' -f8" | tee dns.log
# or 
sudo sh -c "tcpdump -l port 53 2>/dev/null | grep --line-buffered ' A? ' | cut -d' ' -f8"

from https://unix.stackexchange.com/questions/43716/how-to-log-all-my-dns-queries


Using OP scriptlet on example.com in Chrome 74.0.3729.108:

In uBO from master "0-just-anchor" does not appear in both http and https.

In 1.18.16, I see "0-just-anchor" only on http: page.

@uBlock-user
Copy link
Contributor

So this is limited to http sites only ? I use CloudFlare D-O-H instead of normal unencrypted DNS services.

@gorhill
Copy link
Member

gorhill commented Apr 27, 2019

@gwarser Not sure I understand what you are reporting. Is the fix good or bad?

Note that I was using chrome://net-internals/#dns to clear the resolver cache before each test.

@gorhill
Copy link
Member

gorhill commented Apr 27, 2019

I tried to use CSP's prefetch-src 'none' to disable prefetching but this does not work, I get the following message in the console:

The Content-Security-Policy directive 'prefetch-src' is implemented behind a flag which is currently disabled.

I couldn't spot any obvious flag related to this.

@gwarser
Copy link

gwarser commented Apr 27, 2019

It works. DNS prefetching for <a> no longer appears.

OP script uses random domains - no need to clear DNS cache.

DNS requests for preload, prefetch, preconnect, dns-prefetch, prerender happen no mater what.

@uBlock-user
Copy link
Contributor

I couldn't spot any obvious flag related to this.

chrome://flags/#enable-experimental-web-platform-features

@TestPolygon
Copy link
Author

So this is limited to http sites only ?

https://www.chromium.org/developers/design-documents/dns-prefetching
DNS Prefetch Control section

OP script uses random domains - no need to clear DNS cache.

Yes, with this script you can easy test the behavior on http://example.com without problem related to DNS caching. Just inject it like a usercript|contenscript (I use User JavaScript and CSS extension for it). Check the comments at the end of lines (insertHTML(...)) too.

Cases to test:

  • Normal mode
  • Add "x-dns-prefetch-control": "off" header
  • Add blocking of all third party content
  • In incognito mode

@h1z1
Copy link

h1z1 commented May 7, 2019

How does this not have more public coverage? What a nasty little "bug" (in chrome).

@ghost
Copy link

ghost commented Aug 26, 2019

works for https too, try https://www.pcmag.com/
I'm not using any DNS servers, so I'm unsure if it will only DNS-lookup or also load content, but it tries these:

product.pcmag.com
product.pcmag.com
product.pcmag.com
product.pcmag.com
zdbb.net
www.googleadservices.com
www.googleadservices.com
api.bounceexchange.com
www.google-analytics.com
www.google-analytics.com
static.pcmag.com
assets.pcmag.com
pagead2.googlesyndication.com
api.bounceexchange.com
partner.googleadservices.com
pagead2.googlesyndication.com
a.zdbb.net
partner.googleadservices.com
assets.pcmag.com
a.zdbb.net

hmm, ok it's not supposed to be on for 'http' sites, by default:

    is_dns_prefetch_enabled_ = settings && settings->GetDNSPrefetchingEnabled()   &&
                               GetSecurityOrigin()->Protocol() == "http";
  

unless, I guess the site itself sets it in headers.

well, I tried this and had no effect:

https://github.com/Eloston/ungoogled-chromium/issues/815

--- a/third_party/blink/renderer/core/dom/document.cc	2019-08-25 10:07:09.672360957 +0200
+++ b/third_party/blink/renderer/core/dom/document.cc	2019-08-26 10:31:41.923726592 +0200
@@ -6790,17 +6790,17 @@ void Document::DetachRange(Range* range)
 }
 
 void Document::InitDNSPrefetch() {
-  Settings* settings = GetSettings();
+  //Settings* settings = GetSettings();
 
-  have_explicitly_disabled_dns_prefetch_ = false;
-  is_dns_prefetch_enabled_ = settings && settings->GetDNSPrefetchingEnabled() &&
+  have_explicitly_disabled_dns_prefetch_ = true;
+/*  is_dns_prefetch_enabled_ = settings && settings->GetDNSPrefetchingEnabled() &&
                              GetSecurityOrigin()->Protocol() == "http";
 
   // Inherit DNS prefetch opt-out from parent frame
   if (Document* parent = ParentDocument()) {
     if (!parent->IsDNSPrefetchEnabled())
-      is_dns_prefetch_enabled_ = false;
-  }
+*/      is_dns_prefetch_enabled_ = false;
+  //}
 }
 
 void Document::ParseDNSPrefetchControlHeader(
--- a/third_party/blink/renderer/core/loader/preload_helper.cc	2019-08-09 16:48:15.000000000 +0200
+++ b/third_party/blink/renderer/core/loader/preload_helper.cc	2019-08-26 10:32:18.751263607 +0200
@@ -135,7 +135,7 @@ void PreloadHelper::DnsPrefetchIfNeeded(
     // <https://bugs.webkit.org/show_bug.cgi?id=48857>.
     if (settings && settings->GetDNSPrefetchingEnabled() &&
         params.href.IsValid() && !params.href.IsEmpty()) {
-      if (settings->GetLogDnsPrefetchAndPreconnect()) {
+/*      if (settings->GetLogDnsPrefetchAndPreconnect()) */{
         SendMessageToConsoleForPossiblyNullDocument(
             ConsoleMessage::Create(
                 mojom::ConsoleMessageSource::kOther,

so that tells me, that x-dns-prefetch-control is useless here, but re-reading this made me realize that since it's preconnect, it's something else happening and dns prefetch is ignored.

what teh?

    bool has_preconnect_prediction = false;
    PreconnectPrediction prediction;
    has_preconnect_prediction =
        resource_prefetch_predictor_->PredictPreconnectOrigins(url, &prediction)  ;
    // Try to preconnect to the |url| even if the predictor has no              
    // prediction.
    has_preconnect_prediction =
        AddInitialUrlToPreconnectPrediction(url, &prediction);
  
    if (!has_preconnect_prediction)
      return;
  

chrome/browser/predictors/loading_predictor.cc
well, that ^ had no effect, also this neither:

assuming chromium is ran with --enable-experimental-web-platform-features
then this is supposed to replace any specified prefetch-src values with 'none'

todo: always set this! which is why isn't working for preconnect on www.pcmag.com as per: https://github.com/uBlockOrigin/uBlock-issues/issues/548#issuecomment-524763679

--- a/third_party/blink/renderer/core/frame/csp/csp_directive_list.cc	2019-08-26 11:27:13.200846767 +0200
+++ b/third_party/blink/renderer/core/frame/csp/csp_directive_list.cc	2019-08-26 11:36:06.356144097 +0200
@@ -1355,7 +1355,8 @@ void CSPDirectiveList::AddDirective(cons
     if (type == ContentSecurityPolicy::DirectiveType::kRequireSRIFor) {
       ParseRequireSRIFor(name, value);
     } else if (type == ContentSecurityPolicy::DirectiveType::kPrefetchSrc) {
-      SetCSPDirective<SourceListDirective>(name, value, prefetch_src_);
+      static const String none=* new String("none");
+      SetCSPDirective<SourceListDirective>(name, none, prefetch_src_);
     } else {
       policy_->ReportUnsupportedDirective(name);
     }

and tried other stuff but nothing.
Also tried to do it in uBlock but no effect:

tried "'none'" too!

diff --git a/src/js/traffic.js b/src/js/traffic.js
index ac77e0e0..00a98a4a 100644
--- a/src/js/traffic.js
+++ b/src/js/traffic.js
@@ -836,6 +836,8 @@ const injectCSP = function(fctxt, pageStore, responseHeaders) {
         }
     }
 
+  builtinDirectives.push("prefetch-src none");
+
     if ( builtinDirectives.length !== 0 ) {
         cspSubsets[0] = builtinDirectives.join(', ');
     }

ok we're getting somewhere, by starting chromium with --v=2 arg:

[1:1:0826/134543.639661:VERBOSE2:prescient_networking_dispatcher.cc(29)] Preconnect: https://product.pcmag.com/
[1:1:0826/134543.640152:VERBOSE2:prescient_networking_dispatcher.cc(29)] Preconnect: https://zdbb.net/
[1:1:0826/134543.640342:VERBOSE2:prescient_networking_dispatcher.cc(29)] Preconnect: https://a.zdbb.net/
[1:1:0826/134543.640563:VERBOSE2:prescient_networking_dispatcher.cc(29)] Preconnect: https://partner.googleadservices.com/
[1:1:0826/134543.640923:VERBOSE2:prescient_networking_dispatcher.cc(29)] Preconnect: https://pagead2.googlesyndication.com/
[1:1:0826/134543.641052:VERBOSE2:prescient_networking_dispatcher.cc(29)] Preconnect: https://www.pcmag.com/https//securepubads.g.doubleclick.net
[1:1:0826/134543.641130:VERBOSE2:prescient_networking_dispatcher.cc(29)] Preconnect: https://www.googleadservices.com/
[1:1:0826/134543.641284:VERBOSE2:prescient_networking_dispatcher.cc(29)] Preconnect: https://assets.pcmag.com/
[1:1:0826/134543.641362:VERBOSE2:prescient_networking_dispatcher.cc(29)] Preconnect: https://static.pcmag.com/
[1:1:0826/134543.641430:VERBOSE2:prescient_networking_dispatcher.cc(29)] Preconnect: https://www.google-analytics.com/

ok well, fixed by:

--- a/chrome/browser/predictors/preconnect_manager.cc	2019-08-26 14:08:51.054928230 +0200
+++ b/chrome/browser/predictors/preconnect_manager.cc	2019-08-26 14:09:00.778805985 +0200
@@ -105,6 +105,7 @@ void PreconnectManager::StartPreresolveH
 void PreconnectManager::StartPreresolveHosts(
     const std::vector<std::string>& hostnames) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  return;
   // Push jobs in front of the queue due to higher priority.
   for (auto it = hostnames.rbegin(); it != hostnames.rend(); ++it) {
     PreresolveJobId job_id =
@@ -120,7 +121,7 @@ void PreconnectManager::StartPreresolveH
 void PreconnectManager::StartPreconnectUrl(const GURL& url,
                                            bool allow_credentials) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-  if (!url.SchemeIsHTTPOrHTTPS())
+//  if (!url.SchemeIsHTTPOrHTTPS())
     return;
   PreresolveJobId job_id = preresolve_jobs_.Add(std::make_unique<PreresolveJob>(
       url.GetOrigin(), 1, allow_credentials, nullptr));

hopefully nothing else that I forgot.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Chromium specific to Chromium/Chrome enhancement New feature or request fixed issue has been addressed
Projects
None yet
Development

No branches or pull requests

5 participants