network_portal_detector_impl.cc revision 7d4cd473f85ac64c3747c96c277f9e506a0d2246
1// Copyright (c) 2013 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 "chrome/browser/chromeos/net/network_portal_detector_impl.h"
6
7#include "base/bind.h"
8#include "base/command_line.h"
9#include "base/logging.h"
10#include "base/message_loop.h"
11#include "base/metrics/histogram.h"
12#include "chrome/browser/chromeos/cros/cros_library.h"
13#include "chrome/common/chrome_notification_types.h"
14#include "chrome/common/chrome_switches.h"
15#include "chromeos/network/network_state.h"
16#include "chromeos/network/network_state_handler.h"
17#include "content/public/browser/notification_service.h"
18#include "grit/generated_resources.h"
19#include "net/http/http_status_code.h"
20#include "third_party/cros_system_api/dbus/service_constants.h"
21#include "ui/base/l10n/l10n_util.h"
22
23using captive_portal::CaptivePortalDetector;
24
25namespace chromeos {
26
27namespace {
28
29// Maximum number of portal detections for the same default network
30// after network change.
31const int kMaxRequestAttempts = 3;
32
33// Minimum timeout between consecutive portal checks for the same
34// network.
35const int kMinTimeBetweenAttemptsSec = 3;
36
37// Delay before portal detection caused by changes in proxy settings.
38const int kProxyChangeDelaySec = 1;
39
40// Delay between consecutive portal checks for a network in lazy mode.
41const int kLazyCheckIntervalSec = 5;
42
43std::string CaptivePortalStatusString(
44    NetworkPortalDetectorImpl::CaptivePortalStatus status) {
45  switch (status) {
46    case NetworkPortalDetectorImpl::CAPTIVE_PORTAL_STATUS_UNKNOWN:
47      return l10n_util::GetStringUTF8(
48          IDS_CHROMEOS_CAPTIVE_PORTAL_STATUS_UNKNOWN);
49    case NetworkPortalDetectorImpl::CAPTIVE_PORTAL_STATUS_OFFLINE:
50      return l10n_util::GetStringUTF8(
51          IDS_CHROMEOS_CAPTIVE_PORTAL_STATUS_OFFLINE);
52    case NetworkPortalDetectorImpl::CAPTIVE_PORTAL_STATUS_ONLINE:
53      return l10n_util::GetStringUTF8(
54          IDS_CHROMEOS_CAPTIVE_PORTAL_STATUS_ONLINE);
55    case NetworkPortalDetectorImpl::CAPTIVE_PORTAL_STATUS_PORTAL:
56      return l10n_util::GetStringUTF8(
57          IDS_CHROMEOS_CAPTIVE_PORTAL_STATUS_PORTAL);
58    case NetworkPortalDetectorImpl::CAPTIVE_PORTAL_STATUS_PROXY_AUTH_REQUIRED:
59      return l10n_util::GetStringUTF8(
60          IDS_CHROMEOS_CAPTIVE_PORTAL_STATUS_PROXY_AUTH_REQUIRED);
61    case NetworkPortalDetectorImpl::CAPTIVE_PORTAL_STATUS_COUNT:
62      NOTREACHED();
63  }
64  return l10n_util::GetStringUTF8(
65      IDS_CHROMEOS_CAPTIVE_PORTAL_STATUS_UNRECOGNIZED);
66}
67
68}  // namespace
69
70////////////////////////////////////////////////////////////////////////////////
71// NetworkPortalDetectorImpl, public:
72
73NetworkPortalDetectorImpl::NetworkPortalDetectorImpl(
74    const scoped_refptr<net::URLRequestContextGetter>& request_context)
75    : test_url_(CaptivePortalDetector::kDefaultURL),
76      enabled_(false),
77      weak_ptr_factory_(this),
78      attempt_count_(0),
79      lazy_detection_enabled_(false),
80      lazy_check_interval_(base::TimeDelta::FromSeconds(kLazyCheckIntervalSec)),
81      min_time_between_attempts_(
82          base::TimeDelta::FromSeconds(kMinTimeBetweenAttemptsSec)),
83      request_timeout_for_testing_initialized_(false) {
84  captive_portal_detector_.reset(new CaptivePortalDetector(request_context));
85
86  registrar_.Add(this,
87                 chrome::NOTIFICATION_LOGIN_PROXY_CHANGED,
88                 content::NotificationService::AllSources());
89  registrar_.Add(this,
90                 chrome::NOTIFICATION_AUTH_SUPPLIED,
91                 content::NotificationService::AllSources());
92  registrar_.Add(this,
93                 chrome::NOTIFICATION_AUTH_CANCELLED,
94                 content::NotificationService::AllSources());
95}
96
97NetworkPortalDetectorImpl::~NetworkPortalDetectorImpl() {
98}
99
100void NetworkPortalDetectorImpl::Init() {
101  DCHECK(CalledOnValidThread());
102
103  state_ = STATE_IDLE;
104  NetworkHandler::Get()->network_state_handler()->AddObserver(
105      this, FROM_HERE);
106}
107
108void NetworkPortalDetectorImpl::Shutdown() {
109  DCHECK(CalledOnValidThread());
110
111  detection_task_.Cancel();
112  detection_timeout_.Cancel();
113
114  captive_portal_detector_->Cancel();
115  captive_portal_detector_.reset();
116  observers_.Clear();
117  if (NetworkHandler::IsInitialized()) {
118    NetworkHandler::Get()->network_state_handler()->RemoveObserver(
119        this, FROM_HERE);
120  }
121}
122
123void NetworkPortalDetectorImpl::AddObserver(Observer* observer) {
124  DCHECK(CalledOnValidThread());
125  if (!observer || observers_.HasObserver(observer))
126    return;
127  observers_.AddObserver(observer);
128}
129
130void NetworkPortalDetectorImpl::AddAndFireObserver(Observer* observer) {
131  DCHECK(CalledOnValidThread());
132  if (!observer)
133    return;
134  AddObserver(observer);
135  const NetworkState* network =
136      NetworkHandler::Get()->network_state_handler()->DefaultNetwork();
137  observer->OnPortalDetectionCompleted(network, GetCaptivePortalState(network));
138}
139
140void NetworkPortalDetectorImpl::RemoveObserver(Observer* observer) {
141  DCHECK(CalledOnValidThread());
142  if (observer)
143    observers_.RemoveObserver(observer);
144}
145
146bool NetworkPortalDetectorImpl::IsEnabled() {
147  return enabled_;
148}
149
150void NetworkPortalDetectorImpl::Enable(bool start_detection) {
151  DCHECK(CalledOnValidThread());
152  if (enabled_)
153    return;
154  enabled_ = true;
155  DCHECK(!IsPortalCheckPending());
156  DCHECK(!IsCheckingForPortal());
157  DCHECK(!lazy_detection_enabled());
158  if (!start_detection)
159    return;
160  state_ = STATE_IDLE;
161  attempt_count_ = 0;
162  const NetworkState* default_network =
163      NetworkHandler::Get()->network_state_handler()->DefaultNetwork();
164  if (!default_network)
165    return;
166  portal_state_map_.erase(default_network->path());
167  DCHECK(CanPerformDetection());
168  DetectCaptivePortal(base::TimeDelta());
169}
170
171NetworkPortalDetectorImpl::CaptivePortalState
172NetworkPortalDetectorImpl::GetCaptivePortalState(const NetworkState* network) {
173  DCHECK(CalledOnValidThread());
174  if (!network)
175    return CaptivePortalState();
176  CaptivePortalStateMap::const_iterator it =
177      portal_state_map_.find(network->path());
178  if (it == portal_state_map_.end())
179    return CaptivePortalState();
180  return it->second;
181}
182
183bool NetworkPortalDetectorImpl::StartDetectionIfIdle() {
184  if (IsPortalCheckPending() || IsCheckingForPortal())
185    return false;
186  if (!CanPerformDetection())
187    attempt_count_ = 0;
188  DCHECK(CanPerformDetection());
189  DetectCaptivePortal(base::TimeDelta());
190  return true;
191}
192
193void NetworkPortalDetectorImpl::EnableLazyDetection() {
194  if (lazy_detection_enabled())
195    return;
196  lazy_detection_enabled_ = true;
197  VLOG(1) << "Lazy detection mode enabled.";
198  StartDetectionIfIdle();
199}
200
201void NetworkPortalDetectorImpl::DisableLazyDetection() {
202  if (!lazy_detection_enabled())
203    return;
204  lazy_detection_enabled_ = false;
205  VLOG(1) << "Lazy detection mode disabled.";
206}
207
208void NetworkPortalDetectorImpl::NetworkManagerChanged() {
209  DCHECK(CalledOnValidThread());
210  const NetworkState* default_network =
211      NetworkHandler::Get()->network_state_handler()->DefaultNetwork();
212  if (!default_network) {
213    default_network_id_.clear();
214    return;
215  }
216
217  default_network_id_ = default_network->guid();
218
219  bool network_changed = (default_service_path_ != default_network->path());
220  default_service_path_ = default_network->path();
221
222  bool connection_state_changed = (default_connection_state_ !=
223                                   default_network->connection_state());
224  default_connection_state_ = default_network->connection_state();
225
226  if (network_changed || connection_state_changed) {
227    attempt_count_ = 0;
228    CancelPortalDetection();
229  }
230
231  if (CanPerformDetection() &&
232      NetworkState::StateIsConnected(default_connection_state_)) {
233    // Initiate Captive Portal detection if network's captive
234    // portal state is unknown (e.g. for freshly created networks),
235    // offline or if network connection state was changed.
236    CaptivePortalState state = GetCaptivePortalState(default_network);
237    if (state.status == CAPTIVE_PORTAL_STATUS_UNKNOWN ||
238        state.status == CAPTIVE_PORTAL_STATUS_OFFLINE ||
239        (!network_changed && connection_state_changed)) {
240      DetectCaptivePortal(base::TimeDelta());
241    }
242  }
243}
244
245void NetworkPortalDetectorImpl::DefaultNetworkChanged(
246    const NetworkState* network) {
247  NetworkManagerChanged();
248}
249
250////////////////////////////////////////////////////////////////////////////////
251// NetworkPortalDetectorImpl, private:
252
253bool NetworkPortalDetectorImpl::CanPerformDetection() const {
254  if (IsPortalCheckPending() || IsCheckingForPortal())
255    return false;
256  return attempt_count_ < kMaxRequestAttempts || lazy_detection_enabled();
257}
258
259void NetworkPortalDetectorImpl::DetectCaptivePortal(
260    const base::TimeDelta& delay) {
261  DCHECK(CanPerformDetection());
262
263  if (!IsEnabled())
264    return;
265
266  detection_task_.Cancel();
267  detection_timeout_.Cancel();
268  state_ = STATE_PORTAL_CHECK_PENDING;
269
270  next_attempt_delay_ = delay;
271  if (attempt_count_ > 0) {
272    base::TimeTicks now = GetCurrentTimeTicks();
273    base::TimeDelta elapsed_time;
274
275    base::TimeDelta delay_between_attempts = min_time_between_attempts_;
276    if (attempt_count_ == kMaxRequestAttempts) {
277      DCHECK(lazy_detection_enabled());
278      delay_between_attempts = lazy_check_interval_;
279    }
280    if (now > attempt_start_time_)
281      elapsed_time = now - attempt_start_time_;
282    if (elapsed_time < delay_between_attempts &&
283        delay_between_attempts - elapsed_time > next_attempt_delay_) {
284      next_attempt_delay_ = delay_between_attempts - elapsed_time;
285    }
286  } else {
287    detection_start_time_ = GetCurrentTimeTicks();
288  }
289  detection_task_.Reset(
290      base::Bind(&NetworkPortalDetectorImpl::DetectCaptivePortalTask,
291                 weak_ptr_factory_.GetWeakPtr()));
292  base::MessageLoop::current()->PostDelayedTask(
293      FROM_HERE, detection_task_.callback(), next_attempt_delay_);
294}
295
296void NetworkPortalDetectorImpl::DetectCaptivePortalTask() {
297  DCHECK(IsPortalCheckPending());
298
299  state_ = STATE_CHECKING_FOR_PORTAL;
300  attempt_start_time_ = GetCurrentTimeTicks();
301
302  if (attempt_count_ < kMaxRequestAttempts) {
303    ++attempt_count_;
304    VLOG(1) << "Portal detection started: "
305            << "network=" << default_network_id_ << ", "
306            << "attempt=" << attempt_count_ << " of " << kMaxRequestAttempts;
307  } else {
308    DCHECK(lazy_detection_enabled());
309    VLOG(1) << "Lazy portal detection attempt started";
310  }
311
312  captive_portal_detector_->DetectCaptivePortal(
313      test_url_,
314      base::Bind(&NetworkPortalDetectorImpl::OnPortalDetectionCompleted,
315                 weak_ptr_factory_.GetWeakPtr()));
316  detection_timeout_.Reset(
317      base::Bind(&NetworkPortalDetectorImpl::PortalDetectionTimeout,
318                 weak_ptr_factory_.GetWeakPtr()));
319  base::TimeDelta request_timeout;
320
321  // For easier unit testing check for testing state is performed here
322  // and not in the GetRequestTimeoutSec().
323  if (request_timeout_for_testing_initialized_)
324    request_timeout = request_timeout_for_testing_;
325  else
326    request_timeout = base::TimeDelta::FromSeconds(GetRequestTimeoutSec());
327  base::MessageLoop::current()->PostDelayedTask(
328      FROM_HERE, detection_timeout_.callback(), request_timeout);
329}
330
331void NetworkPortalDetectorImpl::PortalDetectionTimeout() {
332  DCHECK(CalledOnValidThread());
333  DCHECK(IsCheckingForPortal());
334
335  VLOG(1) << "Portal detection timeout: network=" << default_network_id_;
336
337  captive_portal_detector_->Cancel();
338  CaptivePortalDetector::Results results;
339  results.result = captive_portal::RESULT_NO_RESPONSE;
340  OnPortalDetectionCompleted(results);
341}
342
343void NetworkPortalDetectorImpl::CancelPortalDetection() {
344  if (IsPortalCheckPending())
345    detection_task_.Cancel();
346  else if (IsCheckingForPortal())
347    captive_portal_detector_->Cancel();
348  detection_timeout_.Cancel();
349  state_ = STATE_IDLE;
350}
351
352void NetworkPortalDetectorImpl::OnPortalDetectionCompleted(
353    const CaptivePortalDetector::Results& results) {
354  DCHECK(CalledOnValidThread());
355  DCHECK(IsCheckingForPortal());
356
357  VLOG(1) << "Portal detection completed: "
358          << "network=" << default_network_id_ << ", "
359          << "result=" << CaptivePortalDetector::CaptivePortalResultToString(
360              results.result) << ", "
361          << "response_code=" << results.response_code;
362
363  state_ = STATE_IDLE;
364  detection_timeout_.Cancel();
365
366  const NetworkState* default_network =
367      NetworkHandler::Get()->network_state_handler()->DefaultNetwork();
368
369  CaptivePortalState state;
370  state.response_code = results.response_code;
371  switch (results.result) {
372    case captive_portal::RESULT_NO_RESPONSE:
373      if (attempt_count_ >= kMaxRequestAttempts) {
374        if (state.response_code == net::HTTP_PROXY_AUTHENTICATION_REQUIRED) {
375          state.status = CAPTIVE_PORTAL_STATUS_PROXY_AUTH_REQUIRED;
376        } else if (default_network && (default_network->connection_state() ==
377                                       flimflam::kStatePortal)) {
378          // Take into account shill's detection results.
379          state.status = CAPTIVE_PORTAL_STATUS_PORTAL;
380          LOG(WARNING) << "Network " << default_network->guid() << " "
381                       << "is marked as "
382                       << CaptivePortalStatusString(state.status) << " "
383                       << "despite the fact that CaptivePortalDetector "
384                       << "received no response";
385        } else {
386          state.status = CAPTIVE_PORTAL_STATUS_OFFLINE;
387        }
388        SetCaptivePortalState(default_network, state);
389      } else {
390        DCHECK(CanPerformDetection());
391        DetectCaptivePortal(results.retry_after_delta);
392      }
393      break;
394    case captive_portal::RESULT_INTERNET_CONNECTED:
395      state.status = CAPTIVE_PORTAL_STATUS_ONLINE;
396      SetCaptivePortalState(default_network, state);
397      break;
398    case captive_portal::RESULT_BEHIND_CAPTIVE_PORTAL:
399      state.status = CAPTIVE_PORTAL_STATUS_PORTAL;
400      SetCaptivePortalState(default_network, state);
401      break;
402    default:
403      break;
404  }
405
406  TryLazyDetection();
407}
408
409void NetworkPortalDetectorImpl::TryLazyDetection() {
410  if (lazy_detection_enabled() && CanPerformDetection())
411    DetectCaptivePortal(base::TimeDelta());
412}
413
414void NetworkPortalDetectorImpl::Observe(
415    int type,
416    const content::NotificationSource& source,
417    const content::NotificationDetails& details) {
418  if (type == chrome::NOTIFICATION_LOGIN_PROXY_CHANGED ||
419      type == chrome::NOTIFICATION_AUTH_SUPPLIED ||
420      type == chrome::NOTIFICATION_AUTH_CANCELLED) {
421    VLOG(1) << "Restarting portal detection due to proxy change.";
422    attempt_count_ = 0;
423    if (IsPortalCheckPending())
424      return;
425    CancelPortalDetection();
426    DCHECK(CanPerformDetection());
427    DetectCaptivePortal(base::TimeDelta::FromSeconds(kProxyChangeDelaySec));
428  }
429}
430
431bool NetworkPortalDetectorImpl::IsPortalCheckPending() const {
432  return state_ == STATE_PORTAL_CHECK_PENDING;
433}
434
435bool NetworkPortalDetectorImpl::IsCheckingForPortal() const {
436  return state_ == STATE_CHECKING_FOR_PORTAL;
437}
438
439void NetworkPortalDetectorImpl::SetCaptivePortalState(
440    const NetworkState* network,
441    const CaptivePortalState& state) {
442  if (!detection_start_time_.is_null()) {
443    UMA_HISTOGRAM_TIMES("CaptivePortal.OOBE.DetectionDuration",
444                        GetCurrentTimeTicks() - detection_start_time_);
445  }
446
447  if (!network) {
448    NotifyPortalDetectionCompleted(network, state);
449    return;
450  }
451
452  CaptivePortalStateMap::const_iterator it =
453      portal_state_map_.find(network->path());
454  if (it == portal_state_map_.end() ||
455      it->second.status != state.status ||
456      it->second.response_code != state.response_code) {
457    VLOG(1) << "Updating Chrome Captive Portal state: "
458            << "network=" << network->guid() << ", "
459            << "status=" << CaptivePortalStatusString(state.status) << ", "
460            << "response_code=" << state.response_code;
461    portal_state_map_[network->path()] = state;
462  }
463  NotifyPortalDetectionCompleted(network, state);
464}
465
466void NetworkPortalDetectorImpl::NotifyPortalDetectionCompleted(
467    const NetworkState* network,
468    const CaptivePortalState& state) {
469  FOR_EACH_OBSERVER(Observer, observers_,
470                    OnPortalDetectionCompleted(network, state));
471}
472
473base::TimeTicks NetworkPortalDetectorImpl::GetCurrentTimeTicks() const {
474  if (time_ticks_for_testing_.is_null())
475    return base::TimeTicks::Now();
476  return time_ticks_for_testing_;
477}
478
479bool NetworkPortalDetectorImpl::DetectionTimeoutIsCancelledForTesting() const {
480  return detection_timeout_.IsCancelled();
481}
482
483int NetworkPortalDetectorImpl::GetRequestTimeoutSec() const {
484  DCHECK_LE(0, attempt_count_);
485  const NetworkState* network =
486      NetworkHandler::Get()->network_state_handler()->DefaultNetwork();
487  if (!network)
488    return kBaseRequestTimeoutSec;
489  if (lazy_detection_enabled_)
490    return kLazyRequestTimeoutSec;
491  return attempt_count_ * kBaseRequestTimeoutSec;
492}
493
494}  // namespace chromeos
495