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.
4
5#include "components/data_reduction_proxy/browser/data_reduction_proxy_settings.h"
6
7#include "base/bind.h"
8#include "base/command_line.h"
9#include "base/metrics/field_trial.h"
10#include "base/metrics/histogram.h"
11#include "base/metrics/sparse_histogram.h"
12#include "base/prefs/pref_member.h"
13#include "base/prefs/pref_service.h"
14#include "base/prefs/scoped_user_pref_update.h"
15#include "base/strings/string_number_conversions.h"
16#include "base/strings/string_util.h"
17#include "base/strings/stringprintf.h"
18#include "base/strings/utf_string_conversions.h"
19#include "components/data_reduction_proxy/browser/data_reduction_proxy_auth_request_handler.h"
20#include "components/data_reduction_proxy/browser/data_reduction_proxy_configurator.h"
21#include "components/data_reduction_proxy/browser/data_reduction_proxy_params.h"
22#include "components/data_reduction_proxy/browser/data_reduction_proxy_usage_stats.h"
23#include "components/data_reduction_proxy/common/data_reduction_proxy_pref_names.h"
24#include "components/data_reduction_proxy/common/data_reduction_proxy_switches.h"
25#include "net/base/host_port_pair.h"
26#include "net/base/load_flags.h"
27#include "net/base/net_errors.h"
28#include "net/base/net_util.h"
29#include "net/http/http_network_session.h"
30#include "net/http/http_response_headers.h"
31#include "net/url_request/url_fetcher.h"
32#include "net/url_request/url_fetcher_delegate.h"
33#include "net/url_request/url_request_context_getter.h"
34#include "net/url_request/url_request_status.h"
35#include "url/gurl.h"
36
37
38using base::StringPrintf;
39
40namespace {
41// Values of the UMA DataReductionProxy.NetworkChangeEvents histograms.
42// This enum must remain synchronized with the enum of the same
43// name in metrics/histograms/histograms.xml.
44enum DataReductionProxyNetworkChangeEvent {
45  IP_CHANGED = 0, // The client IP address changed.
46  DISABLED_ON_VPN = 1, // The proxy is disabled because a VPN is running.
47  CHANGE_EVENT_COUNT = 2 // This must always be last.
48};
49
50// Key of the UMA DataReductionProxy.StartupState histogram.
51const char kUMAProxyStartupStateHistogram[] =
52    "DataReductionProxy.StartupState";
53
54// Key of the UMA DataReductionProxy.ProbeURL histogram.
55const char kUMAProxyProbeURL[] = "DataReductionProxy.ProbeURL";
56
57// Key of the UMA DataReductionProxy.ProbeURLNetError histogram.
58const char kUMAProxyProbeURLNetError[] = "DataReductionProxy.ProbeURLNetError";
59
60// Record a network change event.
61void RecordNetworkChangeEvent(DataReductionProxyNetworkChangeEvent event) {
62  UMA_HISTOGRAM_ENUMERATION("DataReductionProxy.NetworkChangeEvents",
63                            event,
64                            CHANGE_EVENT_COUNT);
65}
66
67int64 GetInt64PrefValue(const base::ListValue& list_value, size_t index) {
68  int64 val = 0;
69  std::string pref_value;
70  bool rv = list_value.GetString(index, &pref_value);
71  DCHECK(rv);
72  if (rv) {
73    rv = base::StringToInt64(pref_value, &val);
74    DCHECK(rv);
75  }
76  return val;
77}
78
79bool IsEnabledOnCommandLine() {
80  const CommandLine& command_line = *CommandLine::ForCurrentProcess();
81  return command_line.HasSwitch(
82      data_reduction_proxy::switches::kEnableDataReductionProxy);
83}
84
85}  // namespace
86
87namespace data_reduction_proxy {
88
89DataReductionProxySettings::DataReductionProxySettings(
90    DataReductionProxyParams* params)
91    : restricted_by_carrier_(false),
92      enabled_by_user_(false),
93      disabled_on_vpn_(false),
94      unreachable_(false),
95      prefs_(NULL),
96      url_request_context_getter_(NULL),
97      configurator_(NULL) {
98  DCHECK(params);
99  params_.reset(params);
100}
101
102DataReductionProxySettings::~DataReductionProxySettings() {
103  if (params_->allowed())
104    spdy_proxy_auth_enabled_.Destroy();
105  net::NetworkChangeNotifier::RemoveIPAddressObserver(this);
106}
107
108void DataReductionProxySettings::InitPrefMembers() {
109  DCHECK(thread_checker_.CalledOnValidThread());
110  spdy_proxy_auth_enabled_.Init(
111      prefs::kDataReductionProxyEnabled,
112      GetOriginalProfilePrefs(),
113      base::Bind(&DataReductionProxySettings::OnProxyEnabledPrefChange,
114                 base::Unretained(this)));
115  data_reduction_proxy_alternative_enabled_.Init(
116      prefs::kDataReductionProxyAltEnabled,
117      GetOriginalProfilePrefs(),
118      base::Bind(
119          &DataReductionProxySettings::OnProxyAlternativeEnabledPrefChange,
120          base::Unretained(this)));
121}
122
123void DataReductionProxySettings::InitDataReductionProxySettings(
124    PrefService* prefs,
125    net::URLRequestContextGetter* url_request_context_getter) {
126  DCHECK(thread_checker_.CalledOnValidThread());
127  DCHECK(prefs);
128  DCHECK(url_request_context_getter);
129  prefs_ = prefs;
130  url_request_context_getter_ = url_request_context_getter;
131  InitPrefMembers();
132  RecordDataReductionInit();
133
134  // Disable the proxy if it is not allowed to be used.
135  if (!params_->allowed())
136    return;
137
138  AddDefaultProxyBypassRules();
139  net::NetworkChangeNotifier::AddIPAddressObserver(this);
140
141  // We set or reset the proxy pref at startup.
142  MaybeActivateDataReductionProxy(true);
143}
144
145void DataReductionProxySettings::InitDataReductionProxySettings(
146    PrefService* prefs,
147    net::URLRequestContextGetter* url_request_context_getter,
148    DataReductionProxyConfigurator* configurator) {
149  InitDataReductionProxySettings(prefs,
150                                 url_request_context_getter);
151  SetProxyConfigurator(configurator);
152}
153
154void DataReductionProxySettings::SetDataReductionProxyStatisticsPrefs(
155    DataReductionProxyStatisticsPrefs* statistics_prefs) {
156  statistics_prefs_ = statistics_prefs;
157}
158
159void DataReductionProxySettings::SetOnDataReductionEnabledCallback(
160    const base::Callback<void(bool)>& on_data_reduction_proxy_enabled) {
161  on_data_reduction_proxy_enabled_ = on_data_reduction_proxy_enabled;
162  on_data_reduction_proxy_enabled_.Run(IsDataReductionProxyEnabled());
163}
164
165void DataReductionProxySettings::SetProxyConfigurator(
166    DataReductionProxyConfigurator* configurator) {
167  DCHECK(configurator);
168  configurator_ = configurator;
169}
170
171bool DataReductionProxySettings::IsDataReductionProxyEnabled() {
172  return spdy_proxy_auth_enabled_.GetValue() || IsEnabledOnCommandLine();
173}
174
175bool
176DataReductionProxySettings::IsDataReductionProxyAlternativeEnabled() const {
177  return data_reduction_proxy_alternative_enabled_.GetValue();
178}
179
180bool DataReductionProxySettings::IsDataReductionProxyManaged() {
181  return spdy_proxy_auth_enabled_.IsManaged();
182}
183
184void DataReductionProxySettings::SetDataReductionProxyEnabled(bool enabled) {
185  DCHECK(thread_checker_.CalledOnValidThread());
186  // Prevent configuring the proxy when it is not allowed to be used.
187  if (!params_->allowed())
188    return;
189
190  if (spdy_proxy_auth_enabled_.GetValue() != enabled) {
191    spdy_proxy_auth_enabled_.SetValue(enabled);
192    OnProxyEnabledPrefChange();
193  }
194}
195
196void DataReductionProxySettings::SetDataReductionProxyAlternativeEnabled(
197    bool enabled) {
198  DCHECK(thread_checker_.CalledOnValidThread());
199  // Prevent configuring the proxy when it is not allowed to be used.
200  if (!params_->alternative_allowed())
201    return;
202  if (data_reduction_proxy_alternative_enabled_.GetValue() != enabled) {
203    data_reduction_proxy_alternative_enabled_.SetValue(enabled);
204    OnProxyAlternativeEnabledPrefChange();
205  }
206}
207
208int64 DataReductionProxySettings::GetDataReductionLastUpdateTime() {
209  DCHECK(thread_checker_.CalledOnValidThread());
210  DCHECK(statistics_prefs_);
211  int64 last_update_internal =
212      statistics_prefs_->GetInt64(prefs::kDailyHttpContentLengthLastUpdateDate);
213  base::Time last_update = base::Time::FromInternalValue(last_update_internal);
214  return static_cast<int64>(last_update.ToJsTime());
215}
216
217DataReductionProxySettings::ContentLengthList
218DataReductionProxySettings::GetDailyOriginalContentLengths() {
219  DCHECK(thread_checker_.CalledOnValidThread());
220  return GetDailyContentLengths(prefs::kDailyHttpOriginalContentLength);
221}
222
223void DataReductionProxySettings::SetUnreachable(bool unreachable) {
224  unreachable_ = unreachable;
225}
226
227bool DataReductionProxySettings::IsDataReductionProxyUnreachable() {
228  DCHECK(thread_checker_.CalledOnValidThread());
229  return unreachable_;
230}
231
232DataReductionProxySettings::ContentLengthList
233DataReductionProxySettings::GetDailyReceivedContentLengths() {
234  DCHECK(thread_checker_.CalledOnValidThread());
235  return GetDailyContentLengths(prefs::kDailyHttpReceivedContentLength);
236}
237
238void DataReductionProxySettings::OnURLFetchComplete(
239    const net::URLFetcher* source) {
240  DCHECK(thread_checker_.CalledOnValidThread());
241
242  DCHECK(source == fetcher_.get());
243  net::URLRequestStatus status = source->GetStatus();
244  if (status.status() == net::URLRequestStatus::FAILED) {
245    if (status.error() == net::ERR_INTERNET_DISCONNECTED) {
246      RecordProbeURLFetchResult(INTERNET_DISCONNECTED);
247      return;
248    }
249    // TODO(bengr): Remove once we understand the reasons probes are failing.
250    // Probe errors are either due to fetcher-level errors or modified
251    // responses. This only tracks the former.
252    UMA_HISTOGRAM_SPARSE_SLOWLY(
253        kUMAProxyProbeURLNetError, std::abs(status.error()));
254  }
255
256  std::string response;
257  source->GetResponseAsString(&response);
258
259  if ("OK" == response.substr(0, 2)) {
260    DVLOG(1) << "The data reduction proxy is unrestricted.";
261
262    if (enabled_by_user_) {
263      if (restricted_by_carrier_) {
264        // The user enabled the proxy, but sometime previously in the session,
265        // the network operator had blocked the canary and restricted the user.
266        // The current network doesn't block the canary, so don't restrict the
267        // proxy configurations.
268        SetProxyConfigs(true /* enabled */,
269                        IsDataReductionProxyAlternativeEnabled(),
270                        false /* restricted */,
271                        false /* at_startup */);
272        RecordProbeURLFetchResult(SUCCEEDED_PROXY_ENABLED);
273      } else {
274        RecordProbeURLFetchResult(SUCCEEDED_PROXY_ALREADY_ENABLED);
275      }
276    }
277    restricted_by_carrier_ = false;
278    return;
279  }
280  DVLOG(1) << "The data reduction proxy is restricted to the configured "
281           << "fallback proxy.";
282  if (enabled_by_user_) {
283    if (!restricted_by_carrier_) {
284      // Restrict the proxy.
285      SetProxyConfigs(true /* enabled */,
286                      IsDataReductionProxyAlternativeEnabled(),
287                      true /* restricted */,
288                      false /* at_startup */);
289      RecordProbeURLFetchResult(FAILED_PROXY_DISABLED);
290    } else {
291      RecordProbeURLFetchResult(FAILED_PROXY_ALREADY_DISABLED);
292    }
293  }
294  restricted_by_carrier_ = true;
295}
296
297PrefService* DataReductionProxySettings::GetOriginalProfilePrefs() {
298  DCHECK(thread_checker_.CalledOnValidThread());
299  return prefs_;
300}
301
302void DataReductionProxySettings::AddDefaultProxyBypassRules() {
303  // localhost
304  DCHECK(configurator_);
305  configurator_->AddHostPatternToBypass("<local>");
306  // RFC1918 private addresses.
307  configurator_->AddHostPatternToBypass("10.0.0.0/8");
308  configurator_->AddHostPatternToBypass("172.16.0.0/12");
309  configurator_->AddHostPatternToBypass("192.168.0.0/16");
310  // RFC4193 private addresses.
311  configurator_->AddHostPatternToBypass("fc00::/7");
312  // IPV6 probe addresses.
313  configurator_->AddHostPatternToBypass("*-ds.metric.gstatic.com");
314  configurator_->AddHostPatternToBypass("*-v4.metric.gstatic.com");
315}
316
317void DataReductionProxySettings::LogProxyState(
318    bool enabled, bool restricted, bool at_startup) {
319  // This must stay a LOG(WARNING); the output is used in processing customer
320  // feedback.
321  const char kAtStartup[] = "at startup";
322  const char kByUser[] = "by user action";
323  const char kOn[] = "ON";
324  const char kOff[] = "OFF";
325  const char kRestricted[] = "(Restricted)";
326  const char kUnrestricted[] = "(Unrestricted)";
327
328  std::string annotated_on =
329      kOn + std::string(" ") + (restricted ? kRestricted : kUnrestricted);
330
331  LOG(WARNING) << "SPDY proxy " << (enabled ? annotated_on : kOff)
332               << " " << (at_startup ? kAtStartup : kByUser);
333}
334
335void DataReductionProxySettings::OnIPAddressChanged() {
336  DCHECK(thread_checker_.CalledOnValidThread());
337  if (enabled_by_user_) {
338    DCHECK(params_->allowed());
339    RecordNetworkChangeEvent(IP_CHANGED);
340    if (DisableIfVPN())
341      return;
342    if (IsDataReductionProxyAlternativeEnabled() &&
343        !params_->alternative_fallback_allowed()) {
344      return;
345    }
346    ProbeWhetherDataReductionProxyIsAvailable();
347  }
348}
349
350void DataReductionProxySettings::OnProxyEnabledPrefChange() {
351  DCHECK(thread_checker_.CalledOnValidThread());
352  if (!on_data_reduction_proxy_enabled_.is_null())
353    on_data_reduction_proxy_enabled_.Run(IsDataReductionProxyEnabled());
354  if (!params_->allowed())
355    return;
356  MaybeActivateDataReductionProxy(false);
357}
358
359void DataReductionProxySettings::OnProxyAlternativeEnabledPrefChange() {
360  DCHECK(thread_checker_.CalledOnValidThread());
361  if (!params_->alternative_allowed())
362    return;
363  MaybeActivateDataReductionProxy(false);
364}
365
366void DataReductionProxySettings::ResetDataReductionStatistics() {
367  DCHECK(thread_checker_.CalledOnValidThread());
368  DCHECK(statistics_prefs_);
369  base::ListValue* original_update =
370      statistics_prefs_->GetList(prefs::kDailyHttpOriginalContentLength);
371  base::ListValue* received_update =
372      statistics_prefs_->GetList(prefs::kDailyHttpReceivedContentLength);
373  original_update->Clear();
374  received_update->Clear();
375  for (size_t i = 0; i < kNumDaysInHistory; ++i) {
376    original_update->AppendString(base::Int64ToString(0));
377    received_update->AppendString(base::Int64ToString(0));
378  }
379}
380
381void DataReductionProxySettings::MaybeActivateDataReductionProxy(
382    bool at_startup) {
383  DCHECK(thread_checker_.CalledOnValidThread());
384  PrefService* prefs = GetOriginalProfilePrefs();
385  // TODO(marq): Consider moving this so stats are wiped the first time the
386  // proxy settings are actually (not maybe) turned on.
387  if (spdy_proxy_auth_enabled_.GetValue() &&
388      !prefs->GetBoolean(prefs::kDataReductionProxyWasEnabledBefore)) {
389    prefs->SetBoolean(prefs::kDataReductionProxyWasEnabledBefore, true);
390    ResetDataReductionStatistics();
391  }
392  // Configure use of the data reduction proxy if it is enabled.
393  enabled_by_user_= IsDataReductionProxyEnabled();
394  SetProxyConfigs(enabled_by_user_ && !disabled_on_vpn_,
395                  IsDataReductionProxyAlternativeEnabled(),
396                  restricted_by_carrier_,
397                  at_startup);
398
399  // Check if the proxy has been restricted explicitly by the carrier.
400  if (enabled_by_user_ && !disabled_on_vpn_ &&
401      !(IsDataReductionProxyAlternativeEnabled() &&
402        !params_->alternative_fallback_allowed())) {
403    ProbeWhetherDataReductionProxyIsAvailable();
404  }
405}
406
407void DataReductionProxySettings::SetProxyConfigs(bool enabled,
408                                                 bool alternative_enabled,
409                                                 bool restricted,
410                                                 bool at_startup) {
411  DCHECK(thread_checker_.CalledOnValidThread());
412  DCHECK(configurator_);
413
414  LogProxyState(enabled, restricted, at_startup);
415  // The alternative is only configured if the standard configuration is
416  // is enabled.
417  if (enabled & !params_->holdback()) {
418    if (alternative_enabled) {
419      configurator_->Enable(restricted,
420                            !params_->alternative_fallback_allowed(),
421                            params_->alt_origin().spec(),
422                            std::string(),
423                            params_->ssl_origin().spec());
424    } else {
425      configurator_->Enable(restricted,
426                            !params_->fallback_allowed(),
427                            params_->origin().spec(),
428                            params_->fallback_origin().spec(),
429                            std::string());
430    }
431  } else {
432    configurator_->Disable();
433  }
434}
435
436// Metrics methods
437void DataReductionProxySettings::RecordDataReductionInit() {
438  DCHECK(thread_checker_.CalledOnValidThread());
439  ProxyStartupState state = PROXY_NOT_AVAILABLE;
440  if (params_->allowed()) {
441    if (IsDataReductionProxyEnabled())
442      state = PROXY_ENABLED;
443    else
444      state = PROXY_DISABLED;
445  }
446
447  RecordStartupState(state);
448}
449
450void DataReductionProxySettings::RecordProbeURLFetchResult(
451    ProbeURLFetchResult result) {
452  UMA_HISTOGRAM_ENUMERATION(kUMAProxyProbeURL,
453                            result,
454                            PROBE_URL_FETCH_RESULT_COUNT);
455}
456
457void DataReductionProxySettings::RecordStartupState(ProxyStartupState state) {
458  UMA_HISTOGRAM_ENUMERATION(kUMAProxyStartupStateHistogram,
459                            state,
460                            PROXY_STARTUP_STATE_COUNT);
461}
462
463void DataReductionProxySettings::GetNetworkList(
464    net::NetworkInterfaceList* interfaces,
465    int policy) {
466  net::GetNetworkList(interfaces, policy);
467}
468
469void DataReductionProxySettings::ResetParamsForTest(
470    DataReductionProxyParams* params) {
471  params_.reset(params);
472}
473
474DataReductionProxySettings::ContentLengthList
475DataReductionProxySettings::GetDailyContentLengths(const char* pref_name) {
476  DCHECK(thread_checker_.CalledOnValidThread());
477  DataReductionProxySettings::ContentLengthList content_lengths;
478  DCHECK(statistics_prefs_);
479  const base::ListValue* list_value = statistics_prefs_->GetList(pref_name);
480  if (list_value->GetSize() == kNumDaysInHistory) {
481    for (size_t i = 0; i < kNumDaysInHistory; ++i) {
482      content_lengths.push_back(GetInt64PrefValue(*list_value, i));
483    }
484  }
485  return content_lengths;
486}
487
488void DataReductionProxySettings::GetContentLengths(
489    unsigned int days,
490    int64* original_content_length,
491    int64* received_content_length,
492    int64* last_update_time) {
493  DCHECK(thread_checker_.CalledOnValidThread());
494  DCHECK_LE(days, kNumDaysInHistory);
495  DCHECK(statistics_prefs_);
496
497  const base::ListValue* original_list =
498      statistics_prefs_->GetList(prefs::kDailyHttpOriginalContentLength);
499  const base::ListValue* received_list =
500      statistics_prefs_->GetList(prefs::kDailyHttpReceivedContentLength);
501
502  if (original_list->GetSize() != kNumDaysInHistory ||
503      received_list->GetSize() != kNumDaysInHistory) {
504    *original_content_length = 0L;
505    *received_content_length = 0L;
506    *last_update_time = 0L;
507    return;
508  }
509
510  int64 orig = 0L;
511  int64 recv = 0L;
512  // Include days from the end of the list going backwards.
513  for (size_t i = kNumDaysInHistory - days;
514       i < kNumDaysInHistory; ++i) {
515    orig += GetInt64PrefValue(*original_list, i);
516    recv += GetInt64PrefValue(*received_list, i);
517  }
518  *original_content_length = orig;
519  *received_content_length = recv;
520  *last_update_time =
521      statistics_prefs_->GetInt64(prefs::kDailyHttpContentLengthLastUpdateDate);
522}
523
524net::URLFetcher* DataReductionProxySettings::GetBaseURLFetcher(
525    const GURL& gurl,
526    int load_flags) {
527
528  net::URLFetcher* fetcher = net::URLFetcher::Create(gurl,
529                                                     net::URLFetcher::GET,
530                                                     this);
531  fetcher->SetLoadFlags(load_flags);
532  DCHECK(url_request_context_getter_);
533  fetcher->SetRequestContext(url_request_context_getter_);
534  // Configure max retries to be at most kMaxRetries times for 5xx errors.
535  static const int kMaxRetries = 5;
536  fetcher->SetMaxRetriesOn5xx(kMaxRetries);
537  fetcher->SetAutomaticallyRetryOnNetworkChanges(kMaxRetries);
538  return fetcher;
539}
540
541
542net::URLFetcher*
543DataReductionProxySettings::GetURLFetcherForAvailabilityCheck() {
544  return GetBaseURLFetcher(params_->probe_url(),
545                           net::LOAD_DISABLE_CACHE | net::LOAD_BYPASS_PROXY);
546}
547
548
549void DataReductionProxySettings::ProbeWhetherDataReductionProxyIsAvailable() {
550  net::URLFetcher* fetcher = GetURLFetcherForAvailabilityCheck();
551  if (!fetcher)
552    return;
553  fetcher_.reset(fetcher);
554  fetcher_->Start();
555}
556
557bool DataReductionProxySettings::DisableIfVPN() {
558  net::NetworkInterfaceList network_interfaces;
559  GetNetworkList(&network_interfaces, 0);
560  // VPNs use a "tun" interface, so the presence of a "tun" interface indicates
561  // a VPN is in use.
562  // TODO(kundaji): Verify this works on Windows.
563  const std::string vpn_interface_name_prefix = "tun";
564  for (size_t i = 0; i < network_interfaces.size(); ++i) {
565    std::string interface_name = network_interfaces[i].name;
566    if (LowerCaseEqualsASCII(
567        interface_name.begin(),
568        interface_name.begin() + vpn_interface_name_prefix.size(),
569        vpn_interface_name_prefix.c_str())) {
570      SetProxyConfigs(false,
571                      IsDataReductionProxyAlternativeEnabled(),
572                      false,
573                      false);
574      disabled_on_vpn_ = true;
575      RecordNetworkChangeEvent(DISABLED_ON_VPN);
576      return true;
577    }
578  }
579  if (disabled_on_vpn_) {
580    SetProxyConfigs(enabled_by_user_,
581                    IsDataReductionProxyAlternativeEnabled(),
582                    restricted_by_carrier_,
583                    false);
584  }
585  disabled_on_vpn_ = false;
586  return false;
587}
588
589}  // namespace data_reduction_proxy
590