// Copyright 2014 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "components/data_reduction_proxy/browser/data_reduction_proxy_settings.h" #include "base/bind.h" #include "base/command_line.h" #include "base/metrics/field_trial.h" #include "base/metrics/histogram.h" #include "base/prefs/pref_member.h" #include "base/prefs/pref_service.h" #include "base/prefs/scoped_user_pref_update.h" #include "base/strings/string_number_conversions.h" #include "base/strings/string_util.h" #include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" #include "components/data_reduction_proxy/browser/data_reduction_proxy_auth_request_handler.h" #include "components/data_reduction_proxy/browser/data_reduction_proxy_configurator.h" #include "components/data_reduction_proxy/browser/data_reduction_proxy_params.h" #include "components/data_reduction_proxy/browser/data_reduction_proxy_usage_stats.h" #include "components/data_reduction_proxy/common/data_reduction_proxy_pref_names.h" #include "components/data_reduction_proxy/common/data_reduction_proxy_switches.h" #include "net/base/host_port_pair.h" #include "net/base/load_flags.h" #include "net/base/net_errors.h" #include "net/http/http_network_session.h" #include "net/http/http_response_headers.h" #include "net/url_request/url_fetcher.h" #include "net/url_request/url_fetcher_delegate.h" #include "net/url_request/url_request_context_getter.h" #include "net/url_request/url_request_status.h" #include "url/gurl.h" using base::StringPrintf; namespace { // Key of the UMA DataReductionProxy.StartupState histogram. const char kUMAProxyStartupStateHistogram[] = "DataReductionProxy.StartupState"; // Key of the UMA DataReductionProxy.ProbeURL histogram. const char kUMAProxyProbeURL[] = "DataReductionProxy.ProbeURL"; int64 GetInt64PrefValue(const base::ListValue& list_value, size_t index) { int64 val = 0; std::string pref_value; bool rv = list_value.GetString(index, &pref_value); DCHECK(rv); if (rv) { rv = base::StringToInt64(pref_value, &val); DCHECK(rv); } return val; } bool IsEnabledOnCommandLine() { const CommandLine& command_line = *CommandLine::ForCurrentProcess(); return command_line.HasSwitch( data_reduction_proxy::switches::kEnableDataReductionProxy); } } // namespace namespace data_reduction_proxy { DataReductionProxySettings::DataReductionProxySettings( DataReductionProxyParams* params) : restricted_by_carrier_(false), enabled_by_user_(false), prefs_(NULL), local_state_prefs_(NULL), url_request_context_getter_(NULL) { DCHECK(params); params_.reset(params); } DataReductionProxySettings::~DataReductionProxySettings() { if (params_->allowed()) spdy_proxy_auth_enabled_.Destroy(); } void DataReductionProxySettings::InitPrefMembers() { DCHECK(thread_checker_.CalledOnValidThread()); spdy_proxy_auth_enabled_.Init( prefs::kDataReductionProxyEnabled, GetOriginalProfilePrefs(), base::Bind(&DataReductionProxySettings::OnProxyEnabledPrefChange, base::Unretained(this))); data_reduction_proxy_alternative_enabled_.Init( prefs::kDataReductionProxyAltEnabled, GetOriginalProfilePrefs(), base::Bind( &DataReductionProxySettings::OnProxyAlternativeEnabledPrefChange, base::Unretained(this))); } void DataReductionProxySettings::InitDataReductionProxySettings( PrefService* prefs, PrefService* local_state_prefs, net::URLRequestContextGetter* url_request_context_getter) { DCHECK(thread_checker_.CalledOnValidThread()); DCHECK(prefs); DCHECK(local_state_prefs); DCHECK(url_request_context_getter); prefs_ = prefs; local_state_prefs_ = local_state_prefs; url_request_context_getter_ = url_request_context_getter; InitPrefMembers(); RecordDataReductionInit(); // Disable the proxy if it is not allowed to be used. if (!params_->allowed()) return; AddDefaultProxyBypassRules(); net::NetworkChangeNotifier::AddIPAddressObserver(this); // We set or reset the proxy pref at startup. MaybeActivateDataReductionProxy(true); } void DataReductionProxySettings::InitDataReductionProxySettings( PrefService* prefs, PrefService* local_state_prefs, net::URLRequestContextGetter* url_request_context_getter, scoped_ptr configurator) { InitDataReductionProxySettings(prefs, local_state_prefs, url_request_context_getter); SetProxyConfigurator(configurator.Pass()); } void DataReductionProxySettings::SetProxyConfigurator( scoped_ptr configurator) { DCHECK(configurator); configurator_ = configurator.Pass(); } bool DataReductionProxySettings::IsDataReductionProxyEnabled() { return spdy_proxy_auth_enabled_.GetValue() || IsEnabledOnCommandLine(); } bool DataReductionProxySettings::IsDataReductionProxyAlternativeEnabled() const { return data_reduction_proxy_alternative_enabled_.GetValue(); } bool DataReductionProxySettings::IsDataReductionProxyManaged() { return spdy_proxy_auth_enabled_.IsManaged(); } void DataReductionProxySettings::SetDataReductionProxyEnabled(bool enabled) { DCHECK(thread_checker_.CalledOnValidThread()); // Prevent configuring the proxy when it is not allowed to be used. if (!params_->allowed()) return; if (spdy_proxy_auth_enabled_.GetValue() != enabled) { spdy_proxy_auth_enabled_.SetValue(enabled); OnProxyEnabledPrefChange(); } } void DataReductionProxySettings::SetDataReductionProxyAlternativeEnabled( bool enabled) { DCHECK(thread_checker_.CalledOnValidThread()); // Prevent configuring the proxy when it is not allowed to be used. if (!params_->alternative_allowed()) return; if (data_reduction_proxy_alternative_enabled_.GetValue() != enabled) { data_reduction_proxy_alternative_enabled_.SetValue(enabled); OnProxyAlternativeEnabledPrefChange(); } } int64 DataReductionProxySettings::GetDataReductionLastUpdateTime() { DCHECK(thread_checker_.CalledOnValidThread()); PrefService* local_state = GetLocalStatePrefs(); int64 last_update_internal = local_state->GetInt64(prefs::kDailyHttpContentLengthLastUpdateDate); base::Time last_update = base::Time::FromInternalValue(last_update_internal); return static_cast(last_update.ToJsTime()); } DataReductionProxySettings::ContentLengthList DataReductionProxySettings::GetDailyOriginalContentLengths() { DCHECK(thread_checker_.CalledOnValidThread()); return GetDailyContentLengths(prefs::kDailyHttpOriginalContentLength); } bool DataReductionProxySettings::IsDataReductionProxyUnreachable() { DCHECK(thread_checker_.CalledOnValidThread()); return usage_stats_->isDataReductionProxyUnreachable(); } void DataReductionProxySettings::SetDataReductionProxyUsageStats( DataReductionProxyUsageStats* usage_stats) { usage_stats_ = usage_stats; } DataReductionProxySettings::ContentLengthList DataReductionProxySettings::GetDailyReceivedContentLengths() { DCHECK(thread_checker_.CalledOnValidThread()); return GetDailyContentLengths(prefs::kDailyHttpReceivedContentLength); } void DataReductionProxySettings::OnURLFetchComplete( const net::URLFetcher* source) { DCHECK(thread_checker_.CalledOnValidThread()); // The purpose of sending a request for the warmup URL is to warm the // connection to the data_reduction_proxy. The result is ignored. if (source == warmup_fetcher_.get()) return; DCHECK(source == fetcher_.get()); net::URLRequestStatus status = source->GetStatus(); if (status.status() == net::URLRequestStatus::FAILED && status.error() == net::ERR_INTERNET_DISCONNECTED) { RecordProbeURLFetchResult(INTERNET_DISCONNECTED); return; } std::string response; source->GetResponseAsString(&response); if ("OK" == response.substr(0, 2)) { DVLOG(1) << "The data reduction proxy is unrestricted."; if (enabled_by_user_) { if (restricted_by_carrier_) { // The user enabled the proxy, but sometime previously in the session, // the network operator had blocked the canary and restricted the user. // The current network doesn't block the canary, so don't restrict the // proxy configurations. SetProxyConfigs(true /* enabled */, IsDataReductionProxyAlternativeEnabled(), false /* restricted */, false /* at_startup */); RecordProbeURLFetchResult(SUCCEEDED_PROXY_ENABLED); } else { RecordProbeURLFetchResult(SUCCEEDED_PROXY_ALREADY_ENABLED); } } restricted_by_carrier_ = false; return; } DVLOG(1) << "The data reduction proxy is restricted to the configured " << "fallback proxy."; if (enabled_by_user_) { if (!restricted_by_carrier_) { // Restrict the proxy. SetProxyConfigs(true /* enabled */, IsDataReductionProxyAlternativeEnabled(), true /* restricted */, false /* at_startup */); RecordProbeURLFetchResult(FAILED_PROXY_DISABLED); } else { RecordProbeURLFetchResult(FAILED_PROXY_ALREADY_DISABLED); } } restricted_by_carrier_ = true; } PrefService* DataReductionProxySettings::GetOriginalProfilePrefs() { DCHECK(thread_checker_.CalledOnValidThread()); return prefs_; } PrefService* DataReductionProxySettings::GetLocalStatePrefs() { DCHECK(thread_checker_.CalledOnValidThread()); return local_state_prefs_; } void DataReductionProxySettings::AddDefaultProxyBypassRules() { // localhost configurator_->AddHostPatternToBypass(""); // RFC1918 private addresses. configurator_->AddHostPatternToBypass("10.0.0.0/8"); configurator_->AddHostPatternToBypass("172.16.0.0/12"); configurator_->AddHostPatternToBypass("192.168.0.0/16"); // RFC4193 private addresses. configurator_->AddHostPatternToBypass("fc00::/7"); // IPV6 probe addresses. configurator_->AddHostPatternToBypass("*-ds.metric.gstatic.com"); configurator_->AddHostPatternToBypass("*-v4.metric.gstatic.com"); } void DataReductionProxySettings::LogProxyState( bool enabled, bool restricted, bool at_startup) { // This must stay a LOG(WARNING); the output is used in processing customer // feedback. const char kAtStartup[] = "at startup"; const char kByUser[] = "by user action"; const char kOn[] = "ON"; const char kOff[] = "OFF"; const char kRestricted[] = "(Restricted)"; const char kUnrestricted[] = "(Unrestricted)"; std::string annotated_on = kOn + std::string(" ") + (restricted ? kRestricted : kUnrestricted); LOG(WARNING) << "SPDY proxy " << (enabled ? annotated_on : kOff) << " " << (at_startup ? kAtStartup : kByUser); } void DataReductionProxySettings::OnIPAddressChanged() { DCHECK(thread_checker_.CalledOnValidThread()); if (enabled_by_user_) { DCHECK(params_->allowed()); ProbeWhetherDataReductionProxyIsAvailable(); WarmProxyConnection(); } } void DataReductionProxySettings::OnProxyEnabledPrefChange() { DCHECK(thread_checker_.CalledOnValidThread()); if (!params_->allowed()) return; MaybeActivateDataReductionProxy(false); } void DataReductionProxySettings::OnProxyAlternativeEnabledPrefChange() { DCHECK(thread_checker_.CalledOnValidThread()); if (!params_->alternative_allowed()) return; MaybeActivateDataReductionProxy(false); } void DataReductionProxySettings::ResetDataReductionStatistics() { DCHECK(thread_checker_.CalledOnValidThread()); PrefService* prefs = GetLocalStatePrefs(); if (!prefs) return; ListPrefUpdate original_update(prefs, prefs::kDailyHttpOriginalContentLength); ListPrefUpdate received_update(prefs, prefs::kDailyHttpReceivedContentLength); original_update->Clear(); received_update->Clear(); for (size_t i = 0; i < kNumDaysInHistory; ++i) { original_update->AppendString(base::Int64ToString(0)); received_update->AppendString(base::Int64ToString(0)); } } void DataReductionProxySettings::MaybeActivateDataReductionProxy( bool at_startup) { DCHECK(thread_checker_.CalledOnValidThread()); PrefService* prefs = GetOriginalProfilePrefs(); // TODO(marq): Consider moving this so stats are wiped the first time the // proxy settings are actually (not maybe) turned on. if (spdy_proxy_auth_enabled_.GetValue() && !prefs->GetBoolean(prefs::kDataReductionProxyWasEnabledBefore)) { prefs->SetBoolean(prefs::kDataReductionProxyWasEnabledBefore, true); ResetDataReductionStatistics(); } // Configure use of the data reduction proxy if it is enabled. enabled_by_user_= IsDataReductionProxyEnabled(); SetProxyConfigs(enabled_by_user_, IsDataReductionProxyAlternativeEnabled(), restricted_by_carrier_, at_startup); // Check if the proxy has been restricted explicitly by the carrier. if (enabled_by_user_) { ProbeWhetherDataReductionProxyIsAvailable(); WarmProxyConnection(); } } void DataReductionProxySettings::SetProxyConfigs(bool enabled, bool alternative_enabled, bool restricted, bool at_startup) { DCHECK(thread_checker_.CalledOnValidThread()); LogProxyState(enabled, restricted, at_startup); // The alternative is only configured if the standard configuration is // is enabled. if (enabled) { if (alternative_enabled) { configurator_->Enable(restricted, !params_->fallback_allowed(), params_->alt_origin().spec(), params_->alt_fallback_origin().spec(), params_->ssl_origin().spec()); } else { configurator_->Enable(restricted, !params_->fallback_allowed(), params_->origin().spec(), params_->fallback_origin().spec(), std::string()); } } else { configurator_->Disable(); } } // Metrics methods void DataReductionProxySettings::RecordDataReductionInit() { DCHECK(thread_checker_.CalledOnValidThread()); ProxyStartupState state = PROXY_NOT_AVAILABLE; if (params_->allowed()) { if (IsDataReductionProxyEnabled()) state = PROXY_ENABLED; else state = PROXY_DISABLED; } RecordStartupState(state); } void DataReductionProxySettings::RecordProbeURLFetchResult( ProbeURLFetchResult result) { UMA_HISTOGRAM_ENUMERATION(kUMAProxyProbeURL, result, PROBE_URL_FETCH_RESULT_COUNT); } void DataReductionProxySettings::RecordStartupState(ProxyStartupState state) { UMA_HISTOGRAM_ENUMERATION(kUMAProxyStartupStateHistogram, state, PROXY_STARTUP_STATE_COUNT); } void DataReductionProxySettings::ResetParamsForTest( DataReductionProxyParams* params) { params_.reset(params); } DataReductionProxySettings::ContentLengthList DataReductionProxySettings::GetDailyContentLengths(const char* pref_name) { DCHECK(thread_checker_.CalledOnValidThread()); DataReductionProxySettings::ContentLengthList content_lengths; const base::ListValue* list_value = GetLocalStatePrefs()->GetList(pref_name); if (list_value->GetSize() == kNumDaysInHistory) { for (size_t i = 0; i < kNumDaysInHistory; ++i) { content_lengths.push_back(GetInt64PrefValue(*list_value, i)); } } return content_lengths; } void DataReductionProxySettings::GetContentLengths( unsigned int days, int64* original_content_length, int64* received_content_length, int64* last_update_time) { DCHECK(thread_checker_.CalledOnValidThread()); DCHECK_LE(days, kNumDaysInHistory); PrefService* local_state = GetLocalStatePrefs(); if (!local_state) { *original_content_length = 0L; *received_content_length = 0L; *last_update_time = 0L; return; } const base::ListValue* original_list = local_state->GetList(prefs::kDailyHttpOriginalContentLength); const base::ListValue* received_list = local_state->GetList(prefs::kDailyHttpReceivedContentLength); if (original_list->GetSize() != kNumDaysInHistory || received_list->GetSize() != kNumDaysInHistory) { *original_content_length = 0L; *received_content_length = 0L; *last_update_time = 0L; return; } int64 orig = 0L; int64 recv = 0L; // Include days from the end of the list going backwards. for (size_t i = kNumDaysInHistory - days; i < kNumDaysInHistory; ++i) { orig += GetInt64PrefValue(*original_list, i); recv += GetInt64PrefValue(*received_list, i); } *original_content_length = orig; *received_content_length = recv; *last_update_time = local_state->GetInt64(prefs::kDailyHttpContentLengthLastUpdateDate); } // static base::string16 DataReductionProxySettings::AuthHashForSalt( int64 salt, const std::string& key) { std::string salted_key = base::StringPrintf("%lld%s%lld", static_cast(salt), key.c_str(), static_cast(salt)); return base::UTF8ToUTF16(base::MD5String(salted_key)); } net::URLFetcher* DataReductionProxySettings::GetBaseURLFetcher( const GURL& gurl, int load_flags) { net::URLFetcher* fetcher = net::URLFetcher::Create(gurl, net::URLFetcher::GET, this); fetcher->SetLoadFlags(load_flags); DCHECK(url_request_context_getter_); fetcher->SetRequestContext(url_request_context_getter_); // Configure max retries to be at most kMaxRetries times for 5xx errors. static const int kMaxRetries = 5; fetcher->SetMaxRetriesOn5xx(kMaxRetries); fetcher->SetAutomaticallyRetryOnNetworkChanges(kMaxRetries); return fetcher; } net::URLFetcher* DataReductionProxySettings::GetURLFetcherForAvailabilityCheck() { return GetBaseURLFetcher(params_->probe_url(), net::LOAD_DISABLE_CACHE | net::LOAD_BYPASS_PROXY); } void DataReductionProxySettings::ProbeWhetherDataReductionProxyIsAvailable() { net::URLFetcher* fetcher = GetURLFetcherForAvailabilityCheck(); if (!fetcher) return; fetcher_.reset(fetcher); fetcher_->Start(); } net::URLFetcher* DataReductionProxySettings::GetURLFetcherForWarmup() { return GetBaseURLFetcher(params_->warmup_url(), net::LOAD_DISABLE_CACHE); } void DataReductionProxySettings::WarmProxyConnection() { net::URLFetcher* fetcher = GetURLFetcherForWarmup(); if (!fetcher) return; warmup_fetcher_.reset(fetcher); warmup_fetcher_->Start(); } } // namespace data_reduction_proxy