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