network_portal_detector_impl.cc revision 90dce4d38c5ff5333bea97d859d4e484e27edf0c
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(this);
105}
106
107void NetworkPortalDetectorImpl::Shutdown() {
108  DCHECK(CalledOnValidThread());
109
110  detection_task_.Cancel();
111  detection_timeout_.Cancel();
112
113  captive_portal_detector_->Cancel();
114  captive_portal_detector_.reset();
115  observers_.Clear();
116  if (NetworkHandler::IsInitialized())
117    NetworkHandler::Get()->network_state_handler()->RemoveObserver(this);
118}
119
120void NetworkPortalDetectorImpl::AddObserver(Observer* observer) {
121  DCHECK(CalledOnValidThread());
122  if (!observer || observers_.HasObserver(observer))
123    return;
124  observers_.AddObserver(observer);
125}
126
127void NetworkPortalDetectorImpl::AddAndFireObserver(Observer* observer) {
128  DCHECK(CalledOnValidThread());
129  if (!observer)
130    return;
131  AddObserver(observer);
132  const NetworkState* network =
133      NetworkHandler::Get()->network_state_handler()->DefaultNetwork();
134  observer->OnPortalDetectionCompleted(network, GetCaptivePortalState(network));
135}
136
137void NetworkPortalDetectorImpl::RemoveObserver(Observer* observer) {
138  DCHECK(CalledOnValidThread());
139  if (observer)
140    observers_.RemoveObserver(observer);
141}
142
143bool NetworkPortalDetectorImpl::IsEnabled() {
144  return enabled_;
145}
146
147void NetworkPortalDetectorImpl::Enable(bool start_detection) {
148  DCHECK(CalledOnValidThread());
149  if (enabled_)
150    return;
151  enabled_ = true;
152  DCHECK(!IsPortalCheckPending());
153  DCHECK(!IsCheckingForPortal());
154  DCHECK(!lazy_detection_enabled());
155  if (!start_detection)
156    return;
157  state_ = STATE_IDLE;
158  attempt_count_ = 0;
159  const NetworkState* default_network =
160      NetworkHandler::Get()->network_state_handler()->DefaultNetwork();
161  if (!default_network)
162    return;
163  portal_state_map_.erase(default_network->path());
164  DCHECK(CanPerformDetection());
165  DetectCaptivePortal(base::TimeDelta());
166}
167
168NetworkPortalDetectorImpl::CaptivePortalState
169NetworkPortalDetectorImpl::GetCaptivePortalState(const NetworkState* network) {
170  DCHECK(CalledOnValidThread());
171  if (!network)
172    return CaptivePortalState();
173  CaptivePortalStateMap::const_iterator it =
174      portal_state_map_.find(network->path());
175  if (it == portal_state_map_.end())
176    return CaptivePortalState();
177  return it->second;
178}
179
180bool NetworkPortalDetectorImpl::StartDetectionIfIdle() {
181  if (IsPortalCheckPending() || IsCheckingForPortal())
182    return false;
183  if (!CanPerformDetection())
184    attempt_count_ = 0;
185  DCHECK(CanPerformDetection());
186  DetectCaptivePortal(base::TimeDelta());
187  return true;
188}
189
190void NetworkPortalDetectorImpl::EnableLazyDetection() {
191  if (lazy_detection_enabled())
192    return;
193  lazy_detection_enabled_ = true;
194  VLOG(1) << "Lazy detection mode enabled.";
195  StartDetectionIfIdle();
196}
197
198void NetworkPortalDetectorImpl::DisableLazyDetection() {
199  if (!lazy_detection_enabled())
200    return;
201  lazy_detection_enabled_ = false;
202  VLOG(1) << "Lazy detection mode disabled.";
203}
204
205void NetworkPortalDetectorImpl::NetworkManagerChanged() {
206  DCHECK(CalledOnValidThread());
207  const NetworkState* default_network =
208      NetworkHandler::Get()->network_state_handler()->DefaultNetwork();
209  if (!default_network) {
210    default_network_id_.clear();
211    return;
212  }
213
214  default_network_id_ = default_network->guid();
215
216  bool network_changed = (default_service_path_ != default_network->path());
217  default_service_path_ = default_network->path();
218
219  bool connection_state_changed = (default_connection_state_ !=
220                                   default_network->connection_state());
221  default_connection_state_ = default_network->connection_state();
222
223  if (network_changed || connection_state_changed) {
224    attempt_count_ = 0;
225    CancelPortalDetection();
226  }
227
228  if (CanPerformDetection() &&
229      NetworkState::StateIsConnected(default_connection_state_)) {
230    // Initiate Captive Portal detection if network's captive
231    // portal state is unknown (e.g. for freshly created networks),
232    // offline or if network connection state was changed.
233    CaptivePortalState state = GetCaptivePortalState(default_network);
234    if (state.status == CAPTIVE_PORTAL_STATUS_UNKNOWN ||
235        state.status == CAPTIVE_PORTAL_STATUS_OFFLINE ||
236        (!network_changed && connection_state_changed)) {
237      DetectCaptivePortal(base::TimeDelta());
238    }
239  }
240}
241
242void NetworkPortalDetectorImpl::DefaultNetworkChanged(
243    const NetworkState* network) {
244  NetworkManagerChanged();
245}
246
247////////////////////////////////////////////////////////////////////////////////
248// NetworkPortalDetectorImpl, private:
249
250bool NetworkPortalDetectorImpl::CanPerformDetection() const {
251  if (IsPortalCheckPending() || IsCheckingForPortal())
252    return false;
253  return attempt_count_ < kMaxRequestAttempts || lazy_detection_enabled();
254}
255
256void NetworkPortalDetectorImpl::DetectCaptivePortal(
257    const base::TimeDelta& delay) {
258  DCHECK(CanPerformDetection());
259
260  if (!IsEnabled())
261    return;
262
263  detection_task_.Cancel();
264  detection_timeout_.Cancel();
265  state_ = STATE_PORTAL_CHECK_PENDING;
266
267  next_attempt_delay_ = delay;
268  if (attempt_count_ > 0) {
269    base::TimeTicks now = GetCurrentTimeTicks();
270    base::TimeDelta elapsed_time;
271
272    base::TimeDelta delay_between_attempts = min_time_between_attempts_;
273    if (attempt_count_ == kMaxRequestAttempts) {
274      DCHECK(lazy_detection_enabled());
275      delay_between_attempts = lazy_check_interval_;
276    }
277    if (now > attempt_start_time_)
278      elapsed_time = now - attempt_start_time_;
279    if (elapsed_time < delay_between_attempts &&
280        delay_between_attempts - elapsed_time > next_attempt_delay_) {
281      next_attempt_delay_ = delay_between_attempts - elapsed_time;
282    }
283  } else {
284    detection_start_time_ = GetCurrentTimeTicks();
285  }
286  detection_task_.Reset(
287      base::Bind(&NetworkPortalDetectorImpl::DetectCaptivePortalTask,
288                 weak_ptr_factory_.GetWeakPtr()));
289  base::MessageLoop::current()->PostDelayedTask(
290      FROM_HERE, detection_task_.callback(), next_attempt_delay_);
291}
292
293void NetworkPortalDetectorImpl::DetectCaptivePortalTask() {
294  DCHECK(IsPortalCheckPending());
295
296  state_ = STATE_CHECKING_FOR_PORTAL;
297  attempt_start_time_ = GetCurrentTimeTicks();
298
299  if (attempt_count_ < kMaxRequestAttempts) {
300    ++attempt_count_;
301    VLOG(1) << "Portal detection started: "
302            << "network=" << default_network_id_ << ", "
303            << "attempt=" << attempt_count_ << " of " << kMaxRequestAttempts;
304  } else {
305    DCHECK(lazy_detection_enabled());
306    VLOG(1) << "Lazy portal detection attempt started";
307  }
308
309  captive_portal_detector_->DetectCaptivePortal(
310      test_url_,
311      base::Bind(&NetworkPortalDetectorImpl::OnPortalDetectionCompleted,
312                 weak_ptr_factory_.GetWeakPtr()));
313  detection_timeout_.Reset(
314      base::Bind(&NetworkPortalDetectorImpl::PortalDetectionTimeout,
315                 weak_ptr_factory_.GetWeakPtr()));
316  base::TimeDelta request_timeout;
317
318  // For easier unit testing check for testing state is performed here
319  // and not in the GetRequestTimeoutSec().
320  if (request_timeout_for_testing_initialized_)
321    request_timeout = request_timeout_for_testing_;
322  else
323    request_timeout = base::TimeDelta::FromSeconds(GetRequestTimeoutSec());
324  base::MessageLoop::current()->PostDelayedTask(
325      FROM_HERE, detection_timeout_.callback(), request_timeout);
326}
327
328void NetworkPortalDetectorImpl::PortalDetectionTimeout() {
329  DCHECK(CalledOnValidThread());
330  DCHECK(IsCheckingForPortal());
331
332  VLOG(1) << "Portal detection timeout: network=" << default_network_id_;
333
334  captive_portal_detector_->Cancel();
335  CaptivePortalDetector::Results results;
336  results.result = captive_portal::RESULT_NO_RESPONSE;
337  OnPortalDetectionCompleted(results);
338}
339
340void NetworkPortalDetectorImpl::CancelPortalDetection() {
341  if (IsPortalCheckPending())
342    detection_task_.Cancel();
343  else if (IsCheckingForPortal())
344    captive_portal_detector_->Cancel();
345  detection_timeout_.Cancel();
346  state_ = STATE_IDLE;
347}
348
349void NetworkPortalDetectorImpl::OnPortalDetectionCompleted(
350    const CaptivePortalDetector::Results& results) {
351  DCHECK(CalledOnValidThread());
352  DCHECK(IsCheckingForPortal());
353
354  VLOG(1) << "Portal detection completed: "
355          << "network=" << default_network_id_ << ", "
356          << "result=" << CaptivePortalDetector::CaptivePortalResultToString(
357              results.result) << ", "
358          << "response_code=" << results.response_code;
359
360  state_ = STATE_IDLE;
361  detection_timeout_.Cancel();
362
363  const NetworkState* default_network =
364      NetworkHandler::Get()->network_state_handler()->DefaultNetwork();
365
366  CaptivePortalState state;
367  state.response_code = results.response_code;
368  switch (results.result) {
369    case captive_portal::RESULT_NO_RESPONSE:
370      if (attempt_count_ >= kMaxRequestAttempts) {
371        if (state.response_code == net::HTTP_PROXY_AUTHENTICATION_REQUIRED) {
372          state.status = CAPTIVE_PORTAL_STATUS_PROXY_AUTH_REQUIRED;
373        } else if (default_network && (default_network->connection_state() ==
374                                       flimflam::kStatePortal)) {
375          // Take into account shill's detection results.
376          state.status = CAPTIVE_PORTAL_STATUS_PORTAL;
377          LOG(WARNING) << "Network " << default_network->guid() << " "
378                       << "is marked as "
379                       << CaptivePortalStatusString(state.status) << " "
380                       << "despite the fact that CaptivePortalDetector "
381                       << "received no response";
382        } else {
383          state.status = CAPTIVE_PORTAL_STATUS_OFFLINE;
384        }
385        SetCaptivePortalState(default_network, state);
386      } else {
387        DCHECK(CanPerformDetection());
388        DetectCaptivePortal(results.retry_after_delta);
389      }
390      break;
391    case captive_portal::RESULT_INTERNET_CONNECTED:
392      state.status = CAPTIVE_PORTAL_STATUS_ONLINE;
393      SetCaptivePortalState(default_network, state);
394      break;
395    case captive_portal::RESULT_BEHIND_CAPTIVE_PORTAL:
396      state.status = CAPTIVE_PORTAL_STATUS_PORTAL;
397      SetCaptivePortalState(default_network, state);
398      break;
399    default:
400      break;
401  }
402
403  TryLazyDetection();
404}
405
406void NetworkPortalDetectorImpl::TryLazyDetection() {
407  if (lazy_detection_enabled() && CanPerformDetection())
408    DetectCaptivePortal(base::TimeDelta());
409}
410
411void NetworkPortalDetectorImpl::Observe(
412    int type,
413    const content::NotificationSource& source,
414    const content::NotificationDetails& details) {
415  if (type == chrome::NOTIFICATION_LOGIN_PROXY_CHANGED ||
416      type == chrome::NOTIFICATION_AUTH_SUPPLIED ||
417      type == chrome::NOTIFICATION_AUTH_CANCELLED) {
418    VLOG(1) << "Restarting portal detection due to proxy change.";
419    attempt_count_ = 0;
420    if (IsPortalCheckPending())
421      return;
422    CancelPortalDetection();
423    DCHECK(CanPerformDetection());
424    DetectCaptivePortal(base::TimeDelta::FromSeconds(kProxyChangeDelaySec));
425  }
426}
427
428bool NetworkPortalDetectorImpl::IsPortalCheckPending() const {
429  return state_ == STATE_PORTAL_CHECK_PENDING;
430}
431
432bool NetworkPortalDetectorImpl::IsCheckingForPortal() const {
433  return state_ == STATE_CHECKING_FOR_PORTAL;
434}
435
436void NetworkPortalDetectorImpl::SetCaptivePortalState(
437    const NetworkState* network,
438    const CaptivePortalState& state) {
439  if (!detection_start_time_.is_null()) {
440    UMA_HISTOGRAM_TIMES("CaptivePortal.OOBE.DetectionDuration",
441                        GetCurrentTimeTicks() - detection_start_time_);
442  }
443
444  if (!network) {
445    NotifyPortalDetectionCompleted(network, state);
446    return;
447  }
448
449  CaptivePortalStateMap::const_iterator it =
450      portal_state_map_.find(network->path());
451  if (it == portal_state_map_.end() ||
452      it->second.status != state.status ||
453      it->second.response_code != state.response_code) {
454    VLOG(1) << "Updating Chrome Captive Portal state: "
455            << "network=" << network->guid() << ", "
456            << "status=" << CaptivePortalStatusString(state.status) << ", "
457            << "response_code=" << state.response_code;
458    portal_state_map_[network->path()] = state;
459  }
460  NotifyPortalDetectionCompleted(network, state);
461}
462
463void NetworkPortalDetectorImpl::NotifyPortalDetectionCompleted(
464    const NetworkState* network,
465    const CaptivePortalState& state) {
466  FOR_EACH_OBSERVER(Observer, observers_,
467                    OnPortalDetectionCompleted(network, state));
468}
469
470base::TimeTicks NetworkPortalDetectorImpl::GetCurrentTimeTicks() const {
471  if (time_ticks_for_testing_.is_null())
472    return base::TimeTicks::Now();
473  return time_ticks_for_testing_;
474}
475
476bool NetworkPortalDetectorImpl::DetectionTimeoutIsCancelledForTesting() const {
477  return detection_timeout_.IsCancelled();
478}
479
480int NetworkPortalDetectorImpl::GetRequestTimeoutSec() const {
481  DCHECK_LE(0, attempt_count_);
482  const NetworkState* network =
483      NetworkHandler::Get()->network_state_handler()->DefaultNetwork();
484  if (!network)
485    return kBaseRequestTimeoutSec;
486  if (lazy_detection_enabled_)
487    return kLazyRequestTimeoutSec;
488  return attempt_count_ * kBaseRequestTimeoutSec;
489}
490
491}  // namespace chromeos
492