1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "components/data_reduction_proxy/browser/data_reduction_proxy_settings.h"
8 #include "base/command_line.h"
9 #include "base/metrics/field_trial.h"
10 #include "base/metrics/histogram.h"
11 #include "base/prefs/pref_member.h"
12 #include "base/prefs/pref_service.h"
13 #include "base/prefs/scoped_user_pref_update.h"
14 #include "base/strings/string_number_conversions.h"
15 #include "base/strings/string_util.h"
16 #include "base/strings/stringprintf.h"
17 #include "base/strings/utf_string_conversions.h"
18 #include "components/data_reduction_proxy/browser/data_reduction_proxy_configurator.h"
19 #include "components/data_reduction_proxy/common/data_reduction_proxy_pref_names.h"
20 #include "components/data_reduction_proxy/common/data_reduction_proxy_switches.h"
21 #include "crypto/random.h"
22 #include "net/base/auth.h"
23 #include "net/base/host_port_pair.h"
24 #include "net/base/load_flags.h"
25 #include "net/base/net_errors.h"
26 #include "net/http/http_auth.h"
27 #include "net/http/http_auth_cache.h"
28 #include "net/http/http_network_session.h"
29 #include "net/http/http_response_headers.h"
30 #include "net/url_request/url_fetcher.h"
31 #include "net/url_request/url_fetcher_delegate.h"
32 #include "net/url_request/url_request_context_getter.h"
33 #include "net/url_request/url_request_status.h"
36 using base::FieldTrialList;
37 using base::StringPrintf;
41 // Key of the UMA DataReductionProxy.StartupState histogram.
42 const char kUMAProxyStartupStateHistogram[] =
43 "DataReductionProxy.StartupState";
45 // Key of the UMA DataReductionProxy.ProbeURL histogram.
46 const char kUMAProxyProbeURL[] = "DataReductionProxy.ProbeURL";
48 const char kEnabled[] = "Enabled";
50 // TODO(marq): Factor this string out into a constant here and in
51 // http_auth_handler_spdyproxy.
52 const char kAuthenticationRealmName[] = "SpdyProxy";
54 int64 GetInt64PrefValue(const base::ListValue& list_value, size_t index) {
56 std::string pref_value;
57 bool rv = list_value.GetString(index, &pref_value);
60 rv = base::StringToInt64(pref_value, &val);
68 namespace data_reduction_proxy {
70 bool DataReductionProxySettings::allowed_;
71 bool DataReductionProxySettings::promo_allowed_;
74 bool DataReductionProxySettings::IsProxyOriginSetOnCommandLine() {
75 const CommandLine& command_line = *CommandLine::ForCurrentProcess();
76 return command_line.HasSwitch(
77 data_reduction_proxy::switches::kDataReductionProxy);
81 bool DataReductionProxySettings::IsProxyKeySetOnCommandLine() {
82 const CommandLine& command_line = *CommandLine::ForCurrentProcess();
83 return command_line.HasSwitch(
84 data_reduction_proxy::switches::kEnableDataReductionProxy);
88 bool DataReductionProxySettings::IsIncludedInFieldTrialOrFlags() {
89 return (base::FieldTrialList::FindFullName(
90 "DataCompressionProxyRollout") == kEnabled ||
91 IsProxyOriginSetOnCommandLine());
95 void DataReductionProxySettings::SetAllowed(bool allowed) {
100 void DataReductionProxySettings::SetPromoAllowed(bool promo_allowed) {
101 promo_allowed_ = promo_allowed;
104 DataReductionProxySettings::DataReductionProxySettings()
105 : restricted_by_carrier_(false),
106 enabled_by_user_(false),
108 local_state_prefs_(NULL),
109 url_request_context_getter_(NULL),
110 fallback_allowed_(true) {
113 DataReductionProxySettings::~DataReductionProxySettings() {
114 if (IsDataReductionProxyAllowed())
115 spdy_proxy_auth_enabled_.Destroy();
118 void DataReductionProxySettings::InitPrefMembers() {
119 DCHECK(thread_checker_.CalledOnValidThread());
120 spdy_proxy_auth_enabled_.Init(
121 prefs::kDataReductionProxyEnabled,
122 GetOriginalProfilePrefs(),
123 base::Bind(&DataReductionProxySettings::OnProxyEnabledPrefChange,
124 base::Unretained(this)));
127 void DataReductionProxySettings::InitDataReductionProxySettings(
129 PrefService* local_state_prefs,
130 net::URLRequestContextGetter* url_request_context_getter) {
131 DCHECK(thread_checker_.CalledOnValidThread());
133 DCHECK(local_state_prefs);
134 DCHECK(url_request_context_getter);
136 local_state_prefs_ = local_state_prefs;
137 url_request_context_getter_ = url_request_context_getter;
139 RecordDataReductionInit();
141 // Disable the proxy if it is not allowed to be used.
142 if (!IsDataReductionProxyAllowed())
145 AddDefaultProxyBypassRules();
146 net::NetworkChangeNotifier::AddIPAddressObserver(this);
148 // We set or reset the proxy pref at startup.
149 MaybeActivateDataReductionProxy(true);
152 void DataReductionProxySettings::InitDataReductionProxySettings(
154 PrefService* local_state_prefs,
155 net::URLRequestContextGetter* url_request_context_getter,
156 scoped_ptr<DataReductionProxyConfigurator> config) {
157 InitDataReductionProxySettings(prefs,
159 url_request_context_getter);
160 SetProxyConfigurator(config.Pass());
163 void DataReductionProxySettings::SetProxyConfigurator(
164 scoped_ptr<DataReductionProxyConfigurator> configurator) {
165 DCHECK(configurator);
166 config_ = configurator.Pass();
170 void DataReductionProxySettings::InitDataReductionProxySession(
171 net::HttpNetworkSession* session,
172 const std::string& key) {
173 // This is a no-op unless the key is set. (even though values for them may be
174 // specified on the command line). Authentication will still work if the
175 // command line parameters are used, however there will be a round-trip
176 // overhead for each challenge/response (typically once per session).
177 // TODO(bengr):Pass a configuration struct into
178 // DataReductionProxyConfigurator's constructor.
182 net::HttpAuthCache* auth_cache = session->http_auth_cache();
184 InitDataReductionAuthentication(auth_cache, key);
188 void DataReductionProxySettings::InitDataReductionAuthentication(
189 net::HttpAuthCache* auth_cache,
190 const std::string& key) {
193 (base::Time::Now() - base::Time::UnixEpoch()).InMilliseconds() / 1000;
195 DataReductionProxyList proxies = GetDataReductionProxies();
196 for (DataReductionProxyList::iterator it = proxies.begin();
197 it != proxies.end(); ++it) {
198 GURL auth_origin = (*it).GetOrigin();
200 crypto::RandBytes(rand, 3 * sizeof(rand[0]));
203 base::StringPrintf("%s%lld", kAuthenticationRealmName,
204 static_cast<long long>(timestamp));
205 std::string challenge = base::StringPrintf(
206 "%s realm=\"%s\", ps=\"%lld-%u-%u-%u\"",
207 kAuthenticationRealmName,
209 static_cast<long long>(timestamp),
213 base::string16 password = AuthHashForSalt(timestamp, key);
215 DVLOG(1) << "origin: [" << auth_origin << "] realm: [" << realm
216 << "] challenge: [" << challenge << "] password: [" << password << "]";
218 net::AuthCredentials credentials(base::string16(), password);
219 // |HttpAuthController| searches this cache by origin and path, the latter
220 // being '/' in the case of the data reduction proxy.
221 auth_cache->Add(auth_origin,
223 net::HttpAuth::AUTH_SCHEME_SPDYPROXY,
230 // TODO(bengr): Use a configuration struct to carry field trial state as well.
232 bool DataReductionProxySettings::IsDataReductionProxyAllowed() {
237 bool DataReductionProxySettings::IsDataReductionProxyPromoAllowed() {
238 return IsProxyOriginSetOnCommandLine() ||
239 (IsDataReductionProxyAllowed() && promo_allowed_);
243 bool DataReductionProxySettings::IsPreconnectHintingAllowed() {
244 if (!IsDataReductionProxyAllowed())
246 return FieldTrialList::FindFullName("DataCompressionProxyPreconnectHints") ==
251 std::string DataReductionProxySettings::GetDataReductionProxyOrigin() {
252 const CommandLine& command_line = *CommandLine::ForCurrentProcess();
253 if (command_line.HasSwitch(switches::kDataReductionProxyDev))
254 return command_line.GetSwitchValueASCII(switches::kDataReductionProxyDev);
255 if (command_line.HasSwitch(switches::kDataReductionProxy))
256 return command_line.GetSwitchValueASCII(switches::kDataReductionProxy);
257 #if defined(DATA_REDUCTION_DEV_HOST)
258 if (FieldTrialList::FindFullName("DataCompressionProxyDevRollout") ==
260 return DATA_REDUCTION_DEV_HOST;
263 #if defined(SPDY_PROXY_AUTH_ORIGIN)
264 return SPDY_PROXY_AUTH_ORIGIN;
266 return std::string();
271 std::string DataReductionProxySettings::GetDataReductionProxyFallback() {
272 // Regardless of what else is defined, only return a value if the main proxy
273 // origin is defined.
274 if (GetDataReductionProxyOrigin().empty())
275 return std::string();
276 const CommandLine& command_line = *CommandLine::ForCurrentProcess();
277 if (command_line.HasSwitch(switches::kDataReductionProxyFallback)) {
278 return command_line.GetSwitchValueASCII(
279 switches::kDataReductionProxyFallback);
281 #if defined(DATA_REDUCTION_FALLBACK_HOST)
282 return DATA_REDUCTION_FALLBACK_HOST;
284 return std::string();
289 bool DataReductionProxySettings::IsAcceptableAuthChallenge(
290 net::AuthChallengeInfo* auth_info) {
291 // Challenge realm must start with the authentication realm name.
292 std::string realm_prefix =
293 auth_info->realm.substr(0, strlen(kAuthenticationRealmName));
294 if (realm_prefix != kAuthenticationRealmName)
297 // The challenger must be one of the configured proxies.
298 DataReductionProxyList proxies = GetDataReductionProxies();
299 for (DataReductionProxyList::iterator it = proxies.begin();
300 it != proxies.end(); ++it) {
301 net::HostPortPair origin_host = net::HostPortPair::FromURL(*it);
302 if (origin_host.Equals(auth_info->challenger))
308 base::string16 DataReductionProxySettings::GetTokenForAuthChallenge(
309 net::AuthChallengeInfo* auth_info) {
310 if (auth_info->realm.length() > strlen(kAuthenticationRealmName)) {
312 std::string realm_suffix =
313 auth_info->realm.substr(strlen(kAuthenticationRealmName));
314 if (base::StringToInt64(realm_suffix, &salt)) {
315 return AuthHashForSalt(salt, key_);
317 DVLOG(1) << "Unable to parse realm name " << auth_info->realm
318 << "into an int for salting.";
319 return base::string16();
322 return base::string16();
326 bool DataReductionProxySettings::IsDataReductionProxyEnabled() {
327 return spdy_proxy_auth_enabled_.GetValue() ||
328 IsProxyKeySetOnCommandLine();
331 bool DataReductionProxySettings::IsDataReductionProxyManaged() {
332 return spdy_proxy_auth_enabled_.IsManaged();
336 DataReductionProxySettings::DataReductionProxyList
337 DataReductionProxySettings::GetDataReductionProxies() {
338 DataReductionProxyList proxies;
339 std::string proxy = GetDataReductionProxyOrigin();
340 std::string fallback = GetDataReductionProxyFallback();
343 proxies.push_back(GURL(proxy));
345 if (!fallback.empty()) {
346 // Sanity check: fallback isn't the only proxy.
347 DCHECK(!proxies.empty());
348 proxies.push_back(GURL(fallback));
354 void DataReductionProxySettings::SetDataReductionProxyEnabled(bool enabled) {
355 DCHECK(thread_checker_.CalledOnValidThread());
356 // Prevent configuring the proxy when it is not allowed to be used.
357 if (!IsDataReductionProxyAllowed())
360 if (spdy_proxy_auth_enabled_.GetValue() != enabled) {
361 spdy_proxy_auth_enabled_.SetValue(enabled);
362 OnProxyEnabledPrefChange();
366 int64 DataReductionProxySettings::GetDataReductionLastUpdateTime() {
367 DCHECK(thread_checker_.CalledOnValidThread());
368 PrefService* local_state = GetLocalStatePrefs();
369 int64 last_update_internal =
370 local_state->GetInt64(prefs::kDailyHttpContentLengthLastUpdateDate);
371 base::Time last_update = base::Time::FromInternalValue(last_update_internal);
372 return static_cast<int64>(last_update.ToJsTime());
375 DataReductionProxySettings::ContentLengthList
376 DataReductionProxySettings::GetDailyOriginalContentLengths() {
377 DCHECK(thread_checker_.CalledOnValidThread());
378 return GetDailyContentLengths(prefs::kDailyHttpOriginalContentLength);
381 DataReductionProxySettings::ContentLengthList
382 DataReductionProxySettings::GetDailyReceivedContentLengths() {
383 DCHECK(thread_checker_.CalledOnValidThread());
384 return GetDailyContentLengths(prefs::kDailyHttpReceivedContentLength);
387 void DataReductionProxySettings::OnURLFetchComplete(
388 const net::URLFetcher* source) {
389 DCHECK(thread_checker_.CalledOnValidThread());
390 net::URLRequestStatus status = source->GetStatus();
391 if (status.status() == net::URLRequestStatus::FAILED &&
392 status.error() == net::ERR_INTERNET_DISCONNECTED) {
393 RecordProbeURLFetchResult(INTERNET_DISCONNECTED);
397 std::string response;
398 source->GetResponseAsString(&response);
400 if ("OK" == response.substr(0, 2)) {
401 DVLOG(1) << "The data reduction proxy is unrestricted.";
403 if (enabled_by_user_) {
404 if (restricted_by_carrier_) {
405 // The user enabled the proxy, but sometime previously in the session,
406 // the network operator had blocked the canary and restricted the user.
407 // The current network doesn't block the canary, so don't restrict the
408 // proxy configurations.
409 SetProxyConfigs(true /* enabled */,
410 false /* restricted */,
411 false /* at_startup */);
412 RecordProbeURLFetchResult(SUCCEEDED_PROXY_ENABLED);
414 RecordProbeURLFetchResult(SUCCEEDED_PROXY_ALREADY_ENABLED);
417 restricted_by_carrier_ = false;
420 DVLOG(1) << "The data reduction proxy is restricted to the configured "
421 << "fallback proxy.";
422 if (enabled_by_user_) {
423 if (!restricted_by_carrier_) {
424 // Restrict the proxy.
425 SetProxyConfigs(true /* enabled */,
426 true /* restricted */,
427 false /* at_startup */);
428 RecordProbeURLFetchResult(FAILED_PROXY_DISABLED);
430 RecordProbeURLFetchResult(FAILED_PROXY_ALREADY_DISABLED);
433 restricted_by_carrier_ = true;
436 void DataReductionProxySettings::OnIPAddressChanged() {
437 DCHECK(thread_checker_.CalledOnValidThread());
438 if (enabled_by_user_) {
439 DCHECK(IsDataReductionProxyAllowed());
440 ProbeWhetherDataReductionProxyIsAvailable();
444 void DataReductionProxySettings::OnProxyEnabledPrefChange() {
445 DCHECK(thread_checker_.CalledOnValidThread());
446 if (!DataReductionProxySettings::IsDataReductionProxyAllowed())
448 MaybeActivateDataReductionProxy(false);
451 void DataReductionProxySettings::AddDefaultProxyBypassRules() {
453 config_->AddHostPatternToBypass("<local>");
454 // RFC1918 private addresses.
455 config_->AddHostPatternToBypass("10.0.0.0/8");
456 config_->AddHostPatternToBypass("172.16.0.0/12");
457 config_->AddHostPatternToBypass("192.168.0.0/16");
458 // RFC4193 private addresses.
459 config_->AddHostPatternToBypass("fc00::/7");
460 // IPV6 probe addresses.
461 config_->AddHostPatternToBypass("*-ds.metric.gstatic.com");
462 config_->AddHostPatternToBypass("*-v4.metric.gstatic.com");
465 void DataReductionProxySettings::LogProxyState(
466 bool enabled, bool restricted, bool at_startup) {
467 // This must stay a LOG(WARNING); the output is used in processing customer
469 const char kAtStartup[] = "at startup";
470 const char kByUser[] = "by user action";
471 const char kOn[] = "ON";
472 const char kOff[] = "OFF";
473 const char kRestricted[] = "(Restricted)";
474 const char kUnrestricted[] = "(Unrestricted)";
476 std::string annotated_on =
477 kOn + std::string(" ") + (restricted ? kRestricted : kUnrestricted);
479 LOG(WARNING) << "SPDY proxy " << (enabled ? annotated_on : kOff)
480 << " " << (at_startup ? kAtStartup : kByUser);
483 PrefService* DataReductionProxySettings::GetOriginalProfilePrefs() {
484 DCHECK(thread_checker_.CalledOnValidThread());
488 PrefService* DataReductionProxySettings::GetLocalStatePrefs() {
489 DCHECK(thread_checker_.CalledOnValidThread());
490 return local_state_prefs_;
493 void DataReductionProxySettings::ResetDataReductionStatistics() {
494 DCHECK(thread_checker_.CalledOnValidThread());
495 PrefService* prefs = GetLocalStatePrefs();
498 ListPrefUpdate original_update(prefs, prefs::kDailyHttpOriginalContentLength);
499 ListPrefUpdate received_update(prefs, prefs::kDailyHttpReceivedContentLength);
500 original_update->Clear();
501 received_update->Clear();
502 for (size_t i = 0; i < kNumDaysInHistory; ++i) {
503 original_update->AppendString(base::Int64ToString(0));
504 received_update->AppendString(base::Int64ToString(0));
508 void DataReductionProxySettings::MaybeActivateDataReductionProxy(
510 DCHECK(thread_checker_.CalledOnValidThread());
511 PrefService* prefs = GetOriginalProfilePrefs();
512 // TODO(marq): Consider moving this so stats are wiped the first time the
513 // proxy settings are actually (not maybe) turned on.
514 if (spdy_proxy_auth_enabled_.GetValue() &&
515 !prefs->GetBoolean(prefs::kDataReductionProxyWasEnabledBefore)) {
516 prefs->SetBoolean(prefs::kDataReductionProxyWasEnabledBefore, true);
517 ResetDataReductionStatistics();
520 std::string proxy = GetDataReductionProxyOrigin();
521 // Configure use of the data reduction proxy if it is enabled and the proxy
522 // origin is non-empty.
523 enabled_by_user_= IsDataReductionProxyEnabled() && !proxy.empty();
524 SetProxyConfigs(enabled_by_user_, restricted_by_carrier_, at_startup);
526 // Check if the proxy has been restricted explicitly by the carrier.
527 if (enabled_by_user_)
528 ProbeWhetherDataReductionProxyIsAvailable();
531 void DataReductionProxySettings::SetProxyConfigs(
532 bool enabled, bool restricted, bool at_startup) {
533 DCHECK(thread_checker_.CalledOnValidThread());
534 // If |restricted| is true and there is no defined fallback proxy.
535 // treat this as a disable.
536 std::string fallback = GetDataReductionProxyFallback();
537 if (fallback.empty() && enabled && restricted)
540 LogProxyState(enabled, restricted, at_startup);
542 config_->Enable(restricted,
544 GetDataReductionProxyOrigin(),
552 void DataReductionProxySettings::RecordDataReductionInit() {
553 DCHECK(thread_checker_.CalledOnValidThread());
554 ProxyStartupState state = PROXY_NOT_AVAILABLE;
555 if (IsDataReductionProxyAllowed()) {
556 if (IsDataReductionProxyEnabled())
557 state = PROXY_ENABLED;
559 state = PROXY_DISABLED;
562 RecordStartupState(state);
565 void DataReductionProxySettings::RecordProbeURLFetchResult(
566 ProbeURLFetchResult result) {
567 UMA_HISTOGRAM_ENUMERATION(kUMAProxyProbeURL,
569 PROBE_URL_FETCH_RESULT_COUNT);
572 void DataReductionProxySettings::RecordStartupState(ProxyStartupState state) {
573 UMA_HISTOGRAM_ENUMERATION(kUMAProxyStartupStateHistogram,
575 PROXY_STARTUP_STATE_COUNT);
578 DataReductionProxySettings::ContentLengthList
579 DataReductionProxySettings::GetDailyContentLengths(const char* pref_name) {
580 DCHECK(thread_checker_.CalledOnValidThread());
581 DataReductionProxySettings::ContentLengthList content_lengths;
582 const base::ListValue* list_value = GetLocalStatePrefs()->GetList(pref_name);
583 if (list_value->GetSize() == kNumDaysInHistory) {
584 for (size_t i = 0; i < kNumDaysInHistory; ++i) {
585 content_lengths.push_back(GetInt64PrefValue(*list_value, i));
588 return content_lengths;
591 void DataReductionProxySettings::GetContentLengths(
593 int64* original_content_length,
594 int64* received_content_length,
595 int64* last_update_time) {
596 DCHECK(thread_checker_.CalledOnValidThread());
597 DCHECK_LE(days, kNumDaysInHistory);
598 PrefService* local_state = GetLocalStatePrefs();
600 *original_content_length = 0L;
601 *received_content_length = 0L;
602 *last_update_time = 0L;
606 const base::ListValue* original_list =
607 local_state->GetList(prefs::kDailyHttpOriginalContentLength);
608 const base::ListValue* received_list =
609 local_state->GetList(prefs::kDailyHttpReceivedContentLength);
611 if (original_list->GetSize() != kNumDaysInHistory ||
612 received_list->GetSize() != kNumDaysInHistory) {
613 *original_content_length = 0L;
614 *received_content_length = 0L;
615 *last_update_time = 0L;
621 // Include days from the end of the list going backwards.
622 for (size_t i = kNumDaysInHistory - days;
623 i < kNumDaysInHistory; ++i) {
624 orig += GetInt64PrefValue(*original_list, i);
625 recv += GetInt64PrefValue(*received_list, i);
627 *original_content_length = orig;
628 *received_content_length = recv;
630 local_state->GetInt64(prefs::kDailyHttpContentLengthLastUpdateDate);
633 std::string DataReductionProxySettings::GetProxyCheckURL() {
634 if (!IsDataReductionProxyAllowed())
635 return std::string();
636 const CommandLine& command_line = *CommandLine::ForCurrentProcess();
637 if (command_line.HasSwitch(switches::kDataReductionProxyProbeURL)) {
638 return command_line.GetSwitchValueASCII(
639 switches::kDataReductionProxyProbeURL);
641 #if defined(DATA_REDUCTION_PROXY_PROBE_URL)
642 return DATA_REDUCTION_PROXY_PROBE_URL;
644 return std::string();
649 base::string16 DataReductionProxySettings::AuthHashForSalt(
651 const std::string& key) {
652 std::string active_key;
654 const CommandLine& command_line = *CommandLine::ForCurrentProcess();
655 if (command_line.HasSwitch(switches::kDataReductionProxy)) {
656 // If an origin is provided via a switch, then only consider the value
657 // that is provided by a switch. Do not use the preprocessor constant.
658 // Don't expose |key_| to a proxy passed in via the command line.
659 if (!command_line.HasSwitch(switches::kDataReductionProxyKey))
660 return base::string16();
661 active_key = command_line.GetSwitchValueASCII(
662 switches::kDataReductionProxyKey);
666 DCHECK(!active_key.empty());
668 std::string salted_key =
669 base::StringPrintf("%lld%s%lld",
670 static_cast<long long>(salt),
672 static_cast<long long>(salt));
673 return base::UTF8ToUTF16(base::MD5String(salted_key));
676 net::URLFetcher* DataReductionProxySettings::GetURLFetcher() {
677 DCHECK(url_request_context_getter_);
678 std::string url = GetProxyCheckURL();
681 net::URLFetcher* fetcher = net::URLFetcher::Create(GURL(url),
682 net::URLFetcher::GET,
684 fetcher->SetLoadFlags(net::LOAD_DISABLE_CACHE | net::LOAD_BYPASS_PROXY);
685 fetcher->SetRequestContext(url_request_context_getter_);
686 // Configure max retries to be at most kMaxRetries times for 5xx errors.
687 static const int kMaxRetries = 5;
688 fetcher->SetMaxRetriesOn5xx(kMaxRetries);
692 void DataReductionProxySettings::ProbeWhetherDataReductionProxyIsAvailable() {
693 net::URLFetcher* fetcher = GetURLFetcher();
696 fetcher_.reset(fetcher);
700 } // namespace data_reduction_proxy