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