blob: bafff52d1d5aa3d799ba8b5530b43dee34c9a5a5 [file] [log] [blame]
// 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,
};
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()
: has_turned_on_(false),
has_turned_off_(false),
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<int64>(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) {
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_ && 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);
}
disabled_by_carrier_ = false;
return;
}
DVLOG(1) << "The data reduction proxy is blocked.";
if (enabled_by_user_ && !disabled_by_carrier_) {
// Disable the proxy.
SetProxyConfigs(false, false);
}
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("<local>");
// 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();
}