// Copyright 2013 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 "chrome/browser/net/spdyproxy/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 "chrome/browser/browser_process.h" #include "chrome/browser/prefs/proxy_prefs.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/profiles/profile_manager.h" #include "chrome/common/chrome_switches.h" #include "chrome/common/pref_names.h" #include "crypto/random.h" #include "net/base/auth.h" #include "net/base/host_port_pair.h" #include "net/base/load_flags.h" #include "net/base/net_errors.h" #include "net/http/http_auth.h" #include "net/http/http_auth_cache.h" #include "net/http/http_network_session.h" #include "net/url_request/url_fetcher.h" #include "net/url_request/url_fetcher_delegate.h" #include "net/url_request/url_request_status.h" #include "url/gurl.h" using base::FieldTrialList; using base::StringPrintf; namespace { // Key of the UMA DataReductionProxy.StartupState histogram. const char kUMAProxyStartupStateHistogram[] = "DataReductionProxy.StartupState"; // Values of the UMA DataReductionProxy.StartupState histogram. enum ProxyStartupState { PROXY_NOT_AVAILABLE = 0, PROXY_DISABLED, PROXY_ENABLED, PROXY_STARTUP_STATE_COUNT, }; // Key of the UMA DataReductionProxy.ProbeURL histogram. const char kUMAProxyProbeURL[] = "DataReductionProxy.ProbeURL"; // Values of the UMA DataReductionProxy.ProbeURL histogram. // This enum must remain synchronized with DataReductionProxyProbeURLFetchResult // in metrics/histograms/histograms.xml. enum ProbeURLFetchResult { // The probe failed because the internet was disconnected. INTERNET_DISCONNECTED = 0, // The probe failed for any other reason, and as a result, the proxy was // disabled. FAILED_PROXY_DISABLED, // The probe failed, but the proxy was already disabled. FAILED_PROXY_ALREADY_DISABLED, // THe probe succeeded, and as a result the proxy was enabled. SUCCEEDED_PROXY_ENABLED, // The probe succeeded, but the proxy was already enabled. SUCCEEDED_PROXY_ALREADY_ENABLED, // This must always be last. FETCH_RESULT_COUNT }; void RecordProbeURLFetchResult(ProbeURLFetchResult result) { UMA_HISTOGRAM_ENUMERATION(kUMAProxyProbeURL, result, FETCH_RESULT_COUNT); } const char kEnabled[] = "Enabled"; // TODO(marq): Factor this string out into a constant here and in // http_auth_handler_spdyproxy. const char kAuthenticationRealmName[] = "SpdyProxy"; int64 GetInt64PrefValue(const 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 IsProxyOriginSetOnCommandLine() { const CommandLine& command_line = *CommandLine::ForCurrentProcess(); return command_line.HasSwitch(switches::kSpdyProxyAuthOrigin); } } // namespace DataReductionProxySettings::DataReductionProxySettings() : disabled_by_carrier_(false), enabled_by_user_(false) { } DataReductionProxySettings::~DataReductionProxySettings() { if (IsDataReductionProxyAllowed()) spdy_proxy_auth_enabled_.Destroy(); } void DataReductionProxySettings::InitPrefMembers() { spdy_proxy_auth_enabled_.Init( prefs::kSpdyProxyAuthEnabled, GetOriginalProfilePrefs(), base::Bind(&DataReductionProxySettings::OnProxyEnabledPrefChange, base::Unretained(this))); } void DataReductionProxySettings::InitDataReductionProxySettings() { InitPrefMembers(); // Disable the proxy if it is not allowed to be used. if (!IsDataReductionProxyAllowed()) return; AddDefaultProxyBypassRules(); net::NetworkChangeNotifier::AddIPAddressObserver(this); const CommandLine& command_line = *CommandLine::ForCurrentProcess(); // Setting the kEnableSpdyProxyAuth switch has the same effect as enabling // the feature via settings, in that once set, the preference will be sticky // across instances of Chrome. Disabling the feature can only be done through // the settings menu. RecordDataReductionInit(); if (spdy_proxy_auth_enabled_.GetValue() || command_line.HasSwitch(switches::kEnableSpdyProxyAuth)) { MaybeActivateDataReductionProxy(true); } else { // This is logged so we can use this information in user feedback. LogProxyState(false /* enabled */, true /* at startup */); } } void DataReductionProxySettings::InitDataReductionProxySession( net::HttpNetworkSession* session) { // This is a no-op unless the authentication parameters are compiled in. // (even though values for them may be specified on the command line). // Authentication will still work if the command line parameters are used, // however there will be a round-trip overhead for each challenge/response // (typically once per session). #if defined(SPDY_PROXY_AUTH_ORIGIN) && defined(SPDY_PROXY_AUTH_VALUE) DCHECK(session); net::HttpAuthCache* auth_cache = session->http_auth_cache(); DCHECK(auth_cache); InitDataReductionAuthentication(auth_cache); #endif // defined(SPDY_PROXY_AUTH_ORIGIN) && defined(SPDY_PROXY_AUTH_VALUE) } void DataReductionProxySettings::InitDataReductionAuthentication( net::HttpAuthCache* auth_cache) { DCHECK(auth_cache); int64 timestamp = (base::Time::Now() - base::Time::UnixEpoch()).InMilliseconds() / 1000; DataReductionProxyList proxies = GetDataReductionProxies(); for (DataReductionProxyList::iterator it = proxies.begin(); it != proxies.end(); ++it) { GURL auth_origin = (*it).GetOrigin(); int32 rand[3]; crypto::RandBytes(rand, 3 * sizeof(rand[0])); std::string realm = base::StringPrintf("%s%lld", kAuthenticationRealmName, timestamp); std::string challenge = base::StringPrintf( "%s realm=\"%s\", ps=\"%lld-%u-%u-%u\"", kAuthenticationRealmName, realm.data(), timestamp, rand[0], rand[1], rand[2]); base::string16 password = AuthHashForSalt(timestamp); DVLOG(1) << "origin: [" << auth_origin << "] realm: [" << realm << "] challenge: [" << challenge << "] password: [" << password << "]"; net::AuthCredentials credentials(base::string16(), password); auth_cache->Add(auth_origin, realm, net::HttpAuth::AUTH_SCHEME_SPDYPROXY, challenge, credentials, std::string()); // Proxy auth uses an empty path for lookup. } } void DataReductionProxySettings::AddHostPatternToBypass( const std::string& pattern) { bypass_rules_.push_back(pattern); } void DataReductionProxySettings::AddURLPatternToBypass( const std::string& pattern) { size_t pos = pattern.find("/"); if (pattern.find("/", pos + 1) == pos + 1) pos = pattern.find("/", pos + 2); std::string host_pattern; if (pos != std::string::npos) host_pattern = pattern.substr(0, pos); else host_pattern = pattern; AddHostPatternToBypass(host_pattern); } bool DataReductionProxySettings::IsDataReductionProxyAllowed() { return IsProxyOriginSetOnCommandLine() || (FieldTrialList::FindFullName("DataCompressionProxyRollout") == kEnabled); } bool DataReductionProxySettings::IsDataReductionProxyPromoAllowed() { return IsProxyOriginSetOnCommandLine() || (IsDataReductionProxyAllowed() && FieldTrialList::FindFullName("DataCompressionProxyPromoVisibility") == kEnabled); } std::string DataReductionProxySettings::GetDataReductionProxyOrigin() { const CommandLine& command_line = *CommandLine::ForCurrentProcess(); if (command_line.HasSwitch(switches::kSpdyProxyAuthOrigin)) return command_line.GetSwitchValueASCII(switches::kSpdyProxyAuthOrigin); #if defined(SPDY_PROXY_AUTH_ORIGIN) return SPDY_PROXY_AUTH_ORIGIN; #else return std::string(); #endif } std::string DataReductionProxySettings::GetDataReductionProxyFallback() { // Regardless of what else is defined, only return a value if the main proxy // origin is defined. if (GetDataReductionProxyOrigin().empty()) return std::string(); const CommandLine& command_line = *CommandLine::ForCurrentProcess(); if (command_line.HasSwitch(switches::kSpdyProxyAuthFallback)) return command_line.GetSwitchValueASCII(switches::kSpdyProxyAuthFallback); #if defined(DATA_REDUCTION_FALLBACK_HOST) return DATA_REDUCTION_FALLBACK_HOST; #else return std::string(); #endif } bool DataReductionProxySettings::IsAcceptableAuthChallenge( net::AuthChallengeInfo* auth_info) { // Challenge realm must start with the authentication realm name. std::string realm_prefix = auth_info->realm.substr(0, strlen(kAuthenticationRealmName)); if (realm_prefix != kAuthenticationRealmName) return false; // The challenger must be one of the configured proxies. DataReductionProxyList proxies = GetDataReductionProxies(); for (DataReductionProxyList::iterator it = proxies.begin(); it != proxies.end(); ++it) { net::HostPortPair origin_host = net::HostPortPair::FromURL(*it); if (origin_host.Equals(auth_info->challenger)) return true; } return false; } base::string16 DataReductionProxySettings::GetTokenForAuthChallenge( net::AuthChallengeInfo* auth_info) { if (auth_info->realm.length() > strlen(kAuthenticationRealmName)) { int64 salt; std::string realm_suffix = auth_info->realm.substr(strlen(kAuthenticationRealmName)); if (base::StringToInt64(realm_suffix, &salt)) { return AuthHashForSalt(salt); } else { DVLOG(1) << "Unable to parse realm name " << auth_info->realm << "into an int for salting."; return base::string16(); } } else { return base::string16(); } } bool DataReductionProxySettings::IsDataReductionProxyEnabled() { return spdy_proxy_auth_enabled_.GetValue(); } bool DataReductionProxySettings::IsDataReductionProxyManaged() { return spdy_proxy_auth_enabled_.IsManaged(); } DataReductionProxySettings::DataReductionProxyList DataReductionProxySettings::GetDataReductionProxies() { DataReductionProxyList proxies; std::string proxy = GetDataReductionProxyOrigin(); std::string fallback = GetDataReductionProxyFallback(); if (!proxy.empty()) proxies.push_back(GURL(proxy)); if (!fallback.empty()) { // Sanity check: fallback isn't the only proxy. DCHECK(!proxies.empty()); proxies.push_back(GURL(fallback)); } return proxies; } void DataReductionProxySettings::SetDataReductionProxyEnabled(bool enabled) { // Prevent configuring the proxy when it is not allowed to be used. if (!IsDataReductionProxyAllowed()) return; spdy_proxy_auth_enabled_.SetValue(enabled); OnProxyEnabledPrefChange(); } int64 DataReductionProxySettings::GetDataReductionLastUpdateTime() { 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() { return GetDailyContentLengths(prefs::kDailyHttpOriginalContentLength); } DataReductionProxySettings::ContentLengthList DataReductionProxySettings::GetDailyReceivedContentLengths() { return GetDailyContentLengths(prefs::kDailyHttpReceivedContentLength); } void DataReductionProxySettings::OnURLFetchComplete( const net::URLFetcher* source) { 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 not blocked."; if (enabled_by_user_) { if (disabled_by_carrier_) { // The user enabled the proxy, but sometime previously in the session, // the network operator had blocked the proxy. Now that the network // operator is unblocking it, configure it to the user's desires. SetProxyConfigs(true, false); RecordProbeURLFetchResult(SUCCEEDED_PROXY_ENABLED); } else { RecordProbeURLFetchResult(SUCCEEDED_PROXY_ALREADY_ENABLED); } } disabled_by_carrier_ = false; return; } DVLOG(1) << "The data reduction proxy is blocked."; if (enabled_by_user_) { if (!disabled_by_carrier_) { // Disable the proxy. SetProxyConfigs(false, false); RecordProbeURLFetchResult(FAILED_PROXY_DISABLED); } else { RecordProbeURLFetchResult(FAILED_PROXY_ALREADY_DISABLED); } } disabled_by_carrier_ = true; } void DataReductionProxySettings::OnIPAddressChanged() { if (enabled_by_user_) { DCHECK(IsDataReductionProxyAllowed()); ProbeWhetherDataReductionProxyIsAvailable(); } } void DataReductionProxySettings::OnProxyEnabledPrefChange() { if (!DataReductionProxySettings::IsDataReductionProxyAllowed()) return; MaybeActivateDataReductionProxy(false); } void DataReductionProxySettings::AddDefaultProxyBypassRules() { // localhost AddHostPatternToBypass(""); // RFC1918 private addresses. AddHostPatternToBypass("10.0.0.0/8"); AddHostPatternToBypass("172.16.0.0/12"); AddHostPatternToBypass("192.168.0.0/16"); // RFC4193 private addresses. AddHostPatternToBypass("fc00::/7"); } void DataReductionProxySettings::LogProxyState(bool enabled, 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"; LOG(WARNING) << "SPDY proxy " << (enabled ? kOn : kOff) << " " << (at_startup ? kAtStartup : kByUser); } PrefService* DataReductionProxySettings::GetOriginalProfilePrefs() { return g_browser_process->profile_manager()->GetLastUsedProfile()-> GetOriginalProfile()->GetPrefs(); } PrefService* DataReductionProxySettings::GetLocalStatePrefs() { return g_browser_process->local_state(); } void DataReductionProxySettings::ResetDataReductionStatistics() { 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 < spdyproxy::kNumDaysInHistory; ++i) { original_update->AppendString(base::Int64ToString(0)); received_update->AppendString(base::Int64ToString(0)); } } void DataReductionProxySettings::MaybeActivateDataReductionProxy( bool at_startup) { 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::kSpdyProxyAuthWasEnabledBefore)) { prefs->SetBoolean(prefs::kSpdyProxyAuthWasEnabledBefore, true); ResetDataReductionStatistics(); } std::string proxy = GetDataReductionProxyOrigin(); // Configure use of the data reduction proxy if it is enabled and the proxy // origin is non-empty. enabled_by_user_= spdy_proxy_auth_enabled_.GetValue() && !proxy.empty(); SetProxyConfigs(enabled_by_user_ && !disabled_by_carrier_, at_startup); // Check if the proxy has been disabled explicitly by the carrier. if (enabled_by_user_) ProbeWhetherDataReductionProxyIsAvailable(); } void DataReductionProxySettings::SetProxyConfigs(bool enabled, bool at_startup) { LogProxyState(enabled, at_startup); PrefService* prefs = GetOriginalProfilePrefs(); DCHECK(prefs); DictionaryPrefUpdate update(prefs, prefs::kProxy); base::DictionaryValue* dict = update.Get(); if (enabled) { std::string fallback = GetDataReductionProxyFallback(); std::string proxy_server_config = "http=" + GetDataReductionProxyOrigin() + (fallback.empty() ? "" : "," + fallback) + ",direct://;"; dict->SetString("server", proxy_server_config); dict->SetString("mode", ProxyModeToString(ProxyPrefs::MODE_FIXED_SERVERS)); dict->SetString("bypass_list", JoinString(bypass_rules_, ", ")); } else { dict->SetString("mode", ProxyModeToString(ProxyPrefs::MODE_SYSTEM)); dict->SetString("server", ""); dict->SetString("bypass_list", ""); } } // Metrics methods void DataReductionProxySettings::RecordDataReductionInit() { ProxyStartupState state = PROXY_NOT_AVAILABLE; if (IsDataReductionProxyAllowed()) state = IsDataReductionProxyEnabled() ? PROXY_ENABLED : PROXY_DISABLED; UMA_HISTOGRAM_ENUMERATION(kUMAProxyStartupStateHistogram, state, PROXY_STARTUP_STATE_COUNT); } DataReductionProxySettings::ContentLengthList DataReductionProxySettings::GetDailyContentLengths(const char* pref_name) { DataReductionProxySettings::ContentLengthList content_lengths; const ListValue* list_value = GetLocalStatePrefs()->GetList(pref_name); if (list_value->GetSize() == spdyproxy::kNumDaysInHistory) { for (size_t i = 0; i < spdyproxy::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_LE(days, spdyproxy::kNumDaysInHistory); PrefService* local_state = GetLocalStatePrefs(); if (!local_state) { *original_content_length = 0L; *received_content_length = 0L; *last_update_time = 0L; return; } const ListValue* original_list = local_state->GetList(prefs::kDailyHttpOriginalContentLength); const ListValue* received_list = local_state->GetList(prefs::kDailyHttpReceivedContentLength); if (original_list->GetSize() != spdyproxy::kNumDaysInHistory || received_list->GetSize() != spdyproxy::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 = spdyproxy::kNumDaysInHistory - days; i < spdyproxy::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); } std::string DataReductionProxySettings::GetProxyCheckURL() { if (!IsDataReductionProxyAllowed()) return std::string(); const CommandLine& command_line = *CommandLine::ForCurrentProcess(); if (command_line.HasSwitch(switches::kDataReductionProxyProbeURL)) { return command_line.GetSwitchValueASCII( switches::kDataReductionProxyProbeURL); } #if defined(DATA_REDUCTION_PROXY_PROBE_URL) return DATA_REDUCTION_PROXY_PROBE_URL; #else return std::string(); #endif } base::string16 DataReductionProxySettings::AuthHashForSalt(int64 salt) { if (!IsDataReductionProxyAllowed()) return base::string16(); std::string key; const CommandLine& command_line = *CommandLine::ForCurrentProcess(); if (command_line.HasSwitch(switches::kSpdyProxyAuthOrigin)) { // If an origin is provided via a switch, then only consider the value // that is provided by a switch. Do not use the preprocessor constant. // Don't expose SPDY_PROXY_AUTH_VALUE to a proxy passed in via the command // line. if (!command_line.HasSwitch(switches::kSpdyProxyAuthValue)) return base::string16(); key = command_line.GetSwitchValueASCII(switches::kSpdyProxyAuthValue); } else { #if defined(SPDY_PROXY_AUTH_VALUE) key = SPDY_PROXY_AUTH_VALUE; #else return base::string16(); #endif } DCHECK(!key.empty()); std::string salted_key = base::StringPrintf("%lld%s%lld", salt, key.c_str(), salt); return UTF8ToUTF16(base::MD5String(salted_key)); } net::URLFetcher* DataReductionProxySettings::GetURLFetcher() { std::string url = GetProxyCheckURL(); if (url.empty()) return NULL; net::URLFetcher* fetcher = net::URLFetcher::Create(GURL(url), net::URLFetcher::GET, this); fetcher->SetLoadFlags(net::LOAD_DISABLE_CACHE | net::LOAD_BYPASS_PROXY); Profile* profile = g_browser_process->profile_manager()-> GetDefaultProfile(); fetcher->SetRequestContext(profile->GetRequestContext()); // Configure max retries to be at most kMaxRetries times for 5xx errors. static const int kMaxRetries = 5; fetcher->SetMaxRetriesOn5xx(kMaxRetries); return fetcher; } void DataReductionProxySettings::ProbeWhetherDataReductionProxyIsAvailable() { net::URLFetcher* fetcher = GetURLFetcher(); if (!fetcher) return; fetcher_.reset(fetcher); fetcher_->Start(); }