1 // Copyright 2012 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/intranet_redirect_detector.h"
11 #include "base/command_line.h"
12 #include "base/feature_list.h"
13 #include "base/functional/bind.h"
14 #include "base/location.h"
15 #include "base/rand_util.h"
16 #include "base/strings/utf_string_conversions.h"
17 #include "base/task/single_thread_task_runner.h"
18 #include "chrome/browser/browser_process.h"
19 #include "chrome/browser/net/system_network_context_manager.h"
20 #include "chrome/browser/profiles/profile.h"
21 #include "chrome/common/chrome_switches.h"
22 #include "chrome/common/pref_names.h"
23 #include "components/omnibox/browser/intranet_redirector_state.h"
24 #include "components/omnibox/browser/omnibox_prefs.h"
25 #include "components/omnibox/common/omnibox_features.h"
26 #include "components/prefs/pref_registry_simple.h"
27 #include "components/prefs/pref_service.h"
28 #include "content/public/browser/browser_context.h"
29 #include "content/public/browser/browser_thread.h"
30 #include "content/public/browser/network_service_instance.h"
31 #include "content/public/browser/storage_partition.h"
32 #include "mojo/public/cpp/bindings/remote.h"
33 #include "net/base/load_flags.h"
34 #include "net/base/net_errors.h"
35 #include "net/base/registry_controlled_domains/registry_controlled_domain.h"
36 #include "net/traffic_annotation/network_traffic_annotation.h"
37 #include "services/network/public/cpp/resource_request.h"
38 #include "services/network/public/cpp/simple_url_loader.h"
39 #include "services/network/public/mojom/network_service.mojom.h"
41 // TODO(crbug.com/181671): Write test to verify we handle the policy toggling.
42 IntranetRedirectDetector::IntranetRedirectDetector()
43 : redirect_origin_(g_browser_process->local_state()->GetString(
44 prefs::kLastKnownIntranetRedirectOrigin)) {
45 // Because this function can be called during startup, when kicking off a URL
46 // fetch can eat up 20 ms of time, we delay seven seconds, which is hopefully
47 // long enough to be after startup, but still get results back quickly.
48 // Ideally, instead of this timer, we'd do something like "check if the
49 // browser is starting up, and if so, come back later", but there is currently
50 // no function to do this.
51 static constexpr base::TimeDelta kStartFetchDelay = base::Seconds(7);
52 base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
54 base::BindOnce(&IntranetRedirectDetector::FinishSleep,
55 weak_ptr_factory_.GetWeakPtr()),
58 content::GetNetworkConnectionTracker()->AddNetworkConnectionObserver(this);
59 SetupDnsConfigClient();
62 IntranetRedirectDetector::~IntranetRedirectDetector() {
63 content::GetNetworkConnectionTracker()->RemoveNetworkConnectionObserver(this);
67 GURL IntranetRedirectDetector::RedirectOrigin() {
68 const IntranetRedirectDetector* const detector =
69 g_browser_process->intranet_redirect_detector();
70 return detector ? detector->redirect_origin_ : GURL();
74 void IntranetRedirectDetector::RegisterPrefs(PrefRegistrySimple* registry) {
75 registry->RegisterStringPref(prefs::kLastKnownIntranetRedirectOrigin,
77 registry->RegisterBooleanPref(prefs::kDNSInterceptionChecksEnabled, true);
78 registry->RegisterIntegerPref(omnibox::kIntranetRedirectBehavior, 0);
81 void IntranetRedirectDetector::Restart() {
82 if (!IsEnabledByPolicy()) {
83 if (redirect_origin_.is_valid()) {
84 g_browser_process->local_state()->SetString(
85 prefs::kLastKnownIntranetRedirectOrigin, std::string());
87 redirect_origin_ = GURL();
90 // If a request is already scheduled, do not scheduled yet another one.
94 // Since presumably many programs open connections after network changes,
95 // delay this a little bit.
97 static constexpr base::TimeDelta kRestartDelay = base::Seconds(1);
98 base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
100 base::BindOnce(&IntranetRedirectDetector::FinishSleep,
101 weak_ptr_factory_.GetWeakPtr()),
105 void IntranetRedirectDetector::FinishSleep() {
107 if (!IsEnabledByPolicy()) {
108 if (redirect_origin_.is_valid()) {
109 g_browser_process->local_state()->SetString(
110 prefs::kLastKnownIntranetRedirectOrigin, std::string());
112 redirect_origin_ = GURL();
116 // If another fetch operation is still running, cancel it.
117 simple_loaders_.clear();
118 resulting_origins_.clear();
120 const base::CommandLine* cmd_line = base::CommandLine::ForCurrentProcess();
121 if (cmd_line->HasSwitch(switches::kDisableBackgroundNetworking))
124 DCHECK(simple_loaders_.empty() && resulting_origins_.empty());
126 // Create traffic annotation tag.
127 net::NetworkTrafficAnnotationTag traffic_annotation =
128 net::DefineNetworkTrafficAnnotation("intranet_redirect_detector", R"(
130 sender: "Intranet Redirect Detector"
132 "This component sends requests to three randomly generated, and "
133 "thus likely nonexistent, hostnames. If at least two redirect to "
134 "the same hostname, this suggests the ISP is hijacking NXDOMAIN, "
135 "and the omnibox should treat similar redirected navigations as "
136 "'failed' when deciding whether to prompt the user with a 'did you "
137 "mean to navigate' infobar for certain search inputs."
138 trigger: "On startup and when IP address of the computer changes."
139 data: "None, this is just an empty request."
144 setting: "This feature cannot be disabled by settings."
145 policy_exception_justification:
146 "Not implemented, considered not useful."
149 // Start three loaders on random hostnames.
150 for (size_t i = 0; i < 3; ++i) {
151 std::string url_string("http://");
152 // We generate a random hostname with between 7 and 15 characters.
153 const int num_chars = base::RandInt(7, 15);
154 for (int j = 0; j < num_chars; ++j)
155 url_string += ('a' + base::RandInt(0, 'z' - 'a'));
156 GURL random_url(url_string + '/');
158 auto resource_request = std::make_unique<network::ResourceRequest>();
159 resource_request->url = random_url;
160 resource_request->method = "HEAD";
161 // We don't want these fetches to affect existing state in the profile.
162 resource_request->load_flags = net::LOAD_DISABLE_CACHE;
163 resource_request->credentials_mode = network::mojom::CredentialsMode::kOmit;
164 network::mojom::URLLoaderFactory* loader_factory =
165 g_browser_process->system_network_context_manager()
166 ->GetURLLoaderFactory();
167 std::unique_ptr<network::SimpleURLLoader> simple_loader =
168 network::SimpleURLLoader::Create(std::move(resource_request),
170 network::SimpleURLLoader* simple_loader_ptr = simple_loader.get();
171 simple_loader->DownloadToString(
173 base::BindOnce(&IntranetRedirectDetector::OnSimpleLoaderComplete,
174 base::Unretained(this), simple_loader_ptr),
175 /*max_body_size=*/1);
176 simple_loaders_[simple_loader_ptr] = std::move(simple_loader);
180 void IntranetRedirectDetector::OnSimpleLoaderComplete(
181 network::SimpleURLLoader* source,
182 std::unique_ptr<std::string> response_body) {
183 // Delete the loader on this function's exit.
184 auto it = simple_loaders_.find(source);
185 DCHECK(it != simple_loaders_.end());
186 std::unique_ptr<network::SimpleURLLoader> simple_loader =
187 std::move(it->second);
188 simple_loaders_.erase(it);
190 // If any two loaders result in the same domain/host, we set the redirect
191 // origin to that; otherwise we set it to nothing.
193 DCHECK(source->GetFinalURL().is_valid());
194 GURL origin(source->GetFinalURL().DeprecatedGetOriginAsURL());
195 if (resulting_origins_.empty()) {
196 resulting_origins_.push_back(origin);
199 if (net::registry_controlled_domains::SameDomainOrHost(
200 resulting_origins_.front(),
202 net::registry_controlled_domains::EXCLUDE_PRIVATE_REGISTRIES)) {
203 redirect_origin_ = origin;
204 if (!simple_loaders_.empty()) {
205 // Cancel remaining loader, we don't need it.
206 DCHECK(simple_loaders_.size() == 1);
207 simple_loaders_.clear();
210 if (resulting_origins_.size() == 1) {
211 resulting_origins_.push_back(origin);
214 DCHECK(resulting_origins_.size() == 2);
215 const bool same_domain_or_host =
216 net::registry_controlled_domains::SameDomainOrHost(
217 resulting_origins_.back(),
219 net::registry_controlled_domains::EXCLUDE_PRIVATE_REGISTRIES);
220 redirect_origin_ = same_domain_or_host ? origin : GURL();
222 if (resulting_origins_.empty() || (resulting_origins_.size() == 1 &&
223 resulting_origins_.front().is_valid())) {
224 resulting_origins_.push_back(GURL());
227 redirect_origin_ = GURL();
230 g_browser_process->local_state()->SetString(
231 prefs::kLastKnownIntranetRedirectOrigin, redirect_origin_.is_valid() ?
232 redirect_origin_.spec() : std::string());
235 void IntranetRedirectDetector::OnConnectionChanged(
236 network::mojom::ConnectionType type) {
237 if (type != network::mojom::ConnectionType::CONNECTION_NONE)
241 void IntranetRedirectDetector::OnDnsConfigChanged() {
245 void IntranetRedirectDetector::SetupDnsConfigClient() {
246 DCHECK(!dns_config_client_receiver_.is_bound());
248 mojo::Remote<network::mojom::DnsConfigChangeManager> manager_remote;
249 content::GetNetworkService()->GetDnsConfigChangeManager(
250 manager_remote.BindNewPipeAndPassReceiver());
251 manager_remote->RequestNotifications(
252 dns_config_client_receiver_.BindNewPipeAndPassRemote());
253 dns_config_client_receiver_.set_disconnect_handler(base::BindOnce(
254 &IntranetRedirectDetector::OnDnsConfigClientConnectionError,
255 base::Unretained(this)));
258 void IntranetRedirectDetector::OnDnsConfigClientConnectionError() {
259 dns_config_client_receiver_.reset();
260 SetupDnsConfigClient();
263 bool IntranetRedirectDetector::IsEnabledByPolicy() {
264 // The InterceptionChecksBehavior pref and the older
265 // DNSInterceptionChecksEnabled policy should each be able to disable
266 // interception checks. Therefore, we enable the redirect detector iff allowed
269 // Check IntranetRedirectorBehavior pref.
271 omnibox::GetInterceptionChecksBehavior(g_browser_process->local_state());
272 if (behavior == omnibox::IntranetRedirectorBehavior::DISABLE_FEATURE ||
273 behavior == omnibox::IntranetRedirectorBehavior::
274 DISABLE_INTERCEPTION_CHECKS_ENABLE_INFOBARS) {
278 // Consult previous DNSInterceptionChecksEnabled policy.
279 if (!g_browser_process->local_state()->GetBoolean(
280 prefs::kDNSInterceptionChecksEnabled))