network_portal_detector_impl.cc revision f2477e01787aa58f445919b809d89e252beef54f
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/message_loop.h"
11#include "base/metrics/histogram.h"
12#include "chrome/browser/chrome_notification_types.h"
13#include "chromeos/dbus/shill_stub_helper.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
42const char kCaptivePortalStatusUnknown[] = "Unknown";
43const char kCaptivePortalStatusOffline[] = "Offline";
44const char kCaptivePortalStatusOnline[]  = "Online";
45const char kCaptivePortalStatusPortal[]  = "Portal";
46const char kCaptivePortalStatusProxyAuthRequired[] =
47    "Proxy authentication required";
48const char kCaptivePortalStatusUnrecognized[] = "Unrecognized";
49
50std::string CaptivePortalStatusString(
51    NetworkPortalDetectorImpl::CaptivePortalStatus status) {
52  switch (status) {
53    case NetworkPortalDetectorImpl::CAPTIVE_PORTAL_STATUS_UNKNOWN:
54      return kCaptivePortalStatusUnknown;
55    case NetworkPortalDetectorImpl::CAPTIVE_PORTAL_STATUS_OFFLINE:
56      return kCaptivePortalStatusOffline;
57    case NetworkPortalDetectorImpl::CAPTIVE_PORTAL_STATUS_ONLINE:
58      return kCaptivePortalStatusOnline;
59    case NetworkPortalDetectorImpl::CAPTIVE_PORTAL_STATUS_PORTAL:
60      return kCaptivePortalStatusPortal;
61    case NetworkPortalDetectorImpl::CAPTIVE_PORTAL_STATUS_PROXY_AUTH_REQUIRED:
62      return kCaptivePortalStatusProxyAuthRequired;
63    case NetworkPortalDetectorImpl::CAPTIVE_PORTAL_STATUS_COUNT:
64      NOTREACHED();
65  }
66  return kCaptivePortalStatusUnrecognized;
67}
68
69}  // namespace
70
71////////////////////////////////////////////////////////////////////////////////
72// NetworkPortalDetectorImpl, public:
73
74NetworkPortalDetectorImpl::NetworkPortalDetectorImpl(
75    const scoped_refptr<net::URLRequestContextGetter>& request_context)
76    : state_(STATE_IDLE),
77      test_url_(CaptivePortalDetector::kDefaultURL),
78      enabled_(false),
79      weak_ptr_factory_(this),
80      attempt_count_(0),
81      lazy_detection_enabled_(false),
82      lazy_check_interval_(base::TimeDelta::FromSeconds(kLazyCheckIntervalSec)),
83      min_time_between_attempts_(
84          base::TimeDelta::FromSeconds(kMinTimeBetweenAttemptsSec)),
85      request_timeout_for_testing_initialized_(false) {
86  captive_portal_detector_.reset(new CaptivePortalDetector(request_context));
87
88  registrar_.Add(this,
89                 chrome::NOTIFICATION_LOGIN_PROXY_CHANGED,
90                 content::NotificationService::AllSources());
91  registrar_.Add(this,
92                 chrome::NOTIFICATION_AUTH_SUPPLIED,
93                 content::NotificationService::AllSources());
94  registrar_.Add(this,
95                 chrome::NOTIFICATION_AUTH_CANCELLED,
96                 content::NotificationService::AllSources());
97
98  NetworkHandler::Get()->network_state_handler()->AddObserver(
99      this, FROM_HERE);
100}
101
102NetworkPortalDetectorImpl::~NetworkPortalDetectorImpl() {
103  DCHECK(CalledOnValidThread());
104
105  detection_task_.Cancel();
106  detection_timeout_.Cancel();
107
108  captive_portal_detector_->Cancel();
109  captive_portal_detector_.reset();
110  observers_.Clear();
111  if (NetworkHandler::IsInitialized()) {
112    NetworkHandler::Get()->network_state_handler()->RemoveObserver(
113        this, FROM_HERE);
114  }
115}
116
117void NetworkPortalDetectorImpl::AddObserver(Observer* observer) {
118  DCHECK(CalledOnValidThread());
119  if (!observer || observers_.HasObserver(observer))
120    return;
121  observers_.AddObserver(observer);
122}
123
124void NetworkPortalDetectorImpl::AddAndFireObserver(Observer* observer) {
125  DCHECK(CalledOnValidThread());
126  if (!observer)
127    return;
128  AddObserver(observer);
129  const NetworkState* network =
130      NetworkHandler::Get()->network_state_handler()->DefaultNetwork();
131  observer->OnPortalDetectionCompleted(network, GetCaptivePortalState(network));
132}
133
134void NetworkPortalDetectorImpl::RemoveObserver(Observer* observer) {
135  DCHECK(CalledOnValidThread());
136  if (observer)
137    observers_.RemoveObserver(observer);
138}
139
140bool NetworkPortalDetectorImpl::IsEnabled() {
141  return enabled_;
142}
143
144void NetworkPortalDetectorImpl::Enable(bool start_detection) {
145  DCHECK(CalledOnValidThread());
146  if (enabled_)
147    return;
148  enabled_ = true;
149  DCHECK(!IsPortalCheckPending());
150  DCHECK(!IsCheckingForPortal());
151  DCHECK(!lazy_detection_enabled());
152  if (!start_detection)
153    return;
154  state_ = STATE_IDLE;
155  attempt_count_ = 0;
156  const NetworkState* default_network =
157      NetworkHandler::Get()->network_state_handler()->DefaultNetwork();
158  if (!default_network)
159    return;
160  portal_state_map_.erase(default_network->path());
161  DCHECK(CanPerformDetection());
162  DetectCaptivePortal(base::TimeDelta());
163}
164
165NetworkPortalDetectorImpl::CaptivePortalState
166NetworkPortalDetectorImpl::GetCaptivePortalState(const NetworkState* network) {
167  DCHECK(CalledOnValidThread());
168  if (!network)
169    return CaptivePortalState();
170  CaptivePortalStateMap::const_iterator it =
171      portal_state_map_.find(network->path());
172  if (it == portal_state_map_.end())
173    return CaptivePortalState();
174  return it->second;
175}
176
177bool NetworkPortalDetectorImpl::StartDetectionIfIdle() {
178  if (IsPortalCheckPending() || IsCheckingForPortal())
179    return false;
180  if (!CanPerformDetection())
181    attempt_count_ = 0;
182  DCHECK(CanPerformDetection());
183  DetectCaptivePortal(base::TimeDelta());
184  return true;
185}
186
187void NetworkPortalDetectorImpl::EnableLazyDetection() {
188  if (lazy_detection_enabled())
189    return;
190  lazy_detection_enabled_ = true;
191  VLOG(1) << "Lazy detection mode enabled.";
192  StartDetectionIfIdle();
193}
194
195void NetworkPortalDetectorImpl::DisableLazyDetection() {
196  if (!lazy_detection_enabled())
197    return;
198  lazy_detection_enabled_ = false;
199  if (attempt_count_ == kMaxRequestAttempts && IsPortalCheckPending())
200    CancelPortalDetection();
201  VLOG(1) << "Lazy detection mode disabled.";
202}
203
204void NetworkPortalDetectorImpl::DefaultNetworkChanged(
205    const NetworkState* default_network) {
206  DCHECK(CalledOnValidThread());
207  if (!default_network) {
208    default_network_name_.clear();
209    default_network_id_.clear();
210    return;
211  }
212
213  default_network_name_ = default_network->name();
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
242////////////////////////////////////////////////////////////////////////////////
243// NetworkPortalDetectorImpl, private:
244
245bool NetworkPortalDetectorImpl::CanPerformDetection() const {
246  if (IsPortalCheckPending() || IsCheckingForPortal())
247    return false;
248  return attempt_count_ < kMaxRequestAttempts || lazy_detection_enabled();
249}
250
251void NetworkPortalDetectorImpl::DetectCaptivePortal(
252    const base::TimeDelta& delay) {
253  DCHECK(CanPerformDetection());
254
255  if (!IsEnabled())
256    return;
257
258  detection_task_.Cancel();
259  detection_timeout_.Cancel();
260  state_ = STATE_PORTAL_CHECK_PENDING;
261
262  next_attempt_delay_ = delay;
263  if (attempt_count_ > 0) {
264    base::TimeTicks now = GetCurrentTimeTicks();
265    base::TimeDelta elapsed_time;
266
267    base::TimeDelta delay_between_attempts = min_time_between_attempts_;
268    if (attempt_count_ == kMaxRequestAttempts) {
269      DCHECK(lazy_detection_enabled());
270      delay_between_attempts = lazy_check_interval_;
271    }
272    if (now > attempt_start_time_)
273      elapsed_time = now - attempt_start_time_;
274    if (elapsed_time < delay_between_attempts &&
275        delay_between_attempts - elapsed_time > next_attempt_delay_) {
276      next_attempt_delay_ = delay_between_attempts - elapsed_time;
277    }
278  } else {
279    detection_start_time_ = GetCurrentTimeTicks();
280  }
281  detection_task_.Reset(
282      base::Bind(&NetworkPortalDetectorImpl::DetectCaptivePortalTask,
283                 weak_ptr_factory_.GetWeakPtr()));
284  base::MessageLoop::current()->PostDelayedTask(
285      FROM_HERE, detection_task_.callback(), next_attempt_delay_);
286}
287
288void NetworkPortalDetectorImpl::DetectCaptivePortalTask() {
289  DCHECK(IsPortalCheckPending());
290
291  state_ = STATE_CHECKING_FOR_PORTAL;
292  attempt_start_time_ = GetCurrentTimeTicks();
293
294  if (attempt_count_ < kMaxRequestAttempts) {
295    ++attempt_count_;
296    VLOG(1) << "Portal detection started: "
297            << "name=" << default_network_name_ << ", "
298            << "id=" << default_network_id_ << ", "
299            << "attempt=" << attempt_count_ << " of " << kMaxRequestAttempts;
300  } else {
301    DCHECK(lazy_detection_enabled());
302    VLOG(1) << "Lazy portal detection attempt started";
303  }
304
305  captive_portal_detector_->DetectCaptivePortal(
306      test_url_,
307      base::Bind(&NetworkPortalDetectorImpl::OnPortalDetectionCompleted,
308                 weak_ptr_factory_.GetWeakPtr()));
309  detection_timeout_.Reset(
310      base::Bind(&NetworkPortalDetectorImpl::PortalDetectionTimeout,
311                 weak_ptr_factory_.GetWeakPtr()));
312  base::TimeDelta request_timeout;
313
314  // For easier unit testing check for testing state is performed here
315  // and not in the GetRequestTimeoutSec().
316  if (request_timeout_for_testing_initialized_)
317    request_timeout = request_timeout_for_testing_;
318  else
319    request_timeout = base::TimeDelta::FromSeconds(GetRequestTimeoutSec());
320  base::MessageLoop::current()->PostDelayedTask(
321      FROM_HERE, detection_timeout_.callback(), request_timeout);
322}
323
324void NetworkPortalDetectorImpl::PortalDetectionTimeout() {
325  DCHECK(CalledOnValidThread());
326  DCHECK(IsCheckingForPortal());
327
328  VLOG(1) << "Portal detection timeout: name=" << default_network_name_ << ", "
329          << "id=" << default_network_id_;
330
331  captive_portal_detector_->Cancel();
332  CaptivePortalDetector::Results results;
333  results.result = captive_portal::RESULT_NO_RESPONSE;
334  OnPortalDetectionCompleted(results);
335}
336
337void NetworkPortalDetectorImpl::CancelPortalDetection() {
338  if (IsPortalCheckPending())
339    detection_task_.Cancel();
340  else if (IsCheckingForPortal())
341    captive_portal_detector_->Cancel();
342  detection_timeout_.Cancel();
343  state_ = STATE_IDLE;
344}
345
346void NetworkPortalDetectorImpl::OnPortalDetectionCompleted(
347    const CaptivePortalDetector::Results& results) {
348  captive_portal::Result result = results.result;
349  int response_code = results.response_code;
350
351  if (shill_stub_helper::IsStubPortalledWifiEnabled(default_service_path_)) {
352    result = captive_portal::RESULT_BEHIND_CAPTIVE_PORTAL;
353    response_code = 200;
354  }
355
356  DCHECK(CalledOnValidThread());
357  DCHECK(IsCheckingForPortal());
358
359  VLOG(1) << "Portal detection completed: "
360          << "name=" << default_network_name_ << ", "
361          << "id=" << default_network_id_ << ", "
362          << "result="
363          << CaptivePortalDetector::CaptivePortalResultToString(results.result)
364          << ", "
365          << "response_code=" << results.response_code;
366
367  state_ = STATE_IDLE;
368  detection_timeout_.Cancel();
369
370  const NetworkState* default_network =
371      NetworkHandler::Get()->network_state_handler()->DefaultNetwork();
372
373  CaptivePortalState state;
374  state.response_code = response_code;
375  switch (result) {
376    case captive_portal::RESULT_NO_RESPONSE:
377      if (state.response_code == net::HTTP_PROXY_AUTHENTICATION_REQUIRED) {
378        state.status = CAPTIVE_PORTAL_STATUS_PROXY_AUTH_REQUIRED;
379        SetCaptivePortalState(default_network, state);
380      } else if (attempt_count_ >= kMaxRequestAttempts) {
381        if (default_network &&
382            (default_network->connection_state() == shill::kStatePortal)) {
383          // Take into account shill's detection results.
384          state.status = CAPTIVE_PORTAL_STATUS_PORTAL;
385          LOG(WARNING) << "Network name=" << default_network->name() << ", "
386                       << "id=" << default_network->guid() << " "
387                       << "is marked as "
388                       << CaptivePortalStatusString(state.status) << " "
389                       << "despite the fact that CaptivePortalDetector "
390                       << "received no response";
391        } else {
392          state.status = CAPTIVE_PORTAL_STATUS_OFFLINE;
393        }
394        SetCaptivePortalState(default_network, state);
395      } else {
396        DCHECK(CanPerformDetection());
397        DetectCaptivePortal(results.retry_after_delta);
398      }
399      break;
400    case captive_portal::RESULT_INTERNET_CONNECTED:
401      state.status = CAPTIVE_PORTAL_STATUS_ONLINE;
402      SetCaptivePortalState(default_network, state);
403      break;
404    case captive_portal::RESULT_BEHIND_CAPTIVE_PORTAL:
405      state.status = CAPTIVE_PORTAL_STATUS_PORTAL;
406      SetCaptivePortalState(default_network, state);
407      break;
408    default:
409      break;
410  }
411
412  TryLazyDetection();
413}
414
415void NetworkPortalDetectorImpl::TryLazyDetection() {
416  if (lazy_detection_enabled() && CanPerformDetection())
417    DetectCaptivePortal(base::TimeDelta());
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    DCHECK(CanPerformDetection());
433    DetectCaptivePortal(base::TimeDelta::FromSeconds(kProxyChangeDelaySec));
434  }
435}
436
437bool NetworkPortalDetectorImpl::IsPortalCheckPending() const {
438  return state_ == STATE_PORTAL_CHECK_PENDING;
439}
440
441bool NetworkPortalDetectorImpl::IsCheckingForPortal() const {
442  return state_ == STATE_CHECKING_FOR_PORTAL;
443}
444
445void NetworkPortalDetectorImpl::SetCaptivePortalState(
446    const NetworkState* network,
447    const CaptivePortalState& state) {
448  if (!detection_start_time_.is_null()) {
449    UMA_HISTOGRAM_TIMES("CaptivePortal.OOBE.DetectionDuration",
450                        GetCurrentTimeTicks() - detection_start_time_);
451  }
452
453  if (!network) {
454    NotifyPortalDetectionCompleted(network, state);
455    return;
456  }
457
458  CaptivePortalStateMap::const_iterator it =
459      portal_state_map_.find(network->path());
460  if (it == portal_state_map_.end() ||
461      it->second.status != state.status ||
462      it->second.response_code != state.response_code) {
463    VLOG(1) << "Updating Chrome Captive Portal state: "
464            << "name=" << network->name() << ", "
465            << "id=" << network->guid() << ", "
466            << "status=" << CaptivePortalStatusString(state.status) << ", "
467            << "response_code=" << state.response_code;
468    portal_state_map_[network->path()] = state;
469  }
470  NotifyPortalDetectionCompleted(network, state);
471}
472
473void NetworkPortalDetectorImpl::NotifyPortalDetectionCompleted(
474    const NetworkState* network,
475    const CaptivePortalState& state) {
476  FOR_EACH_OBSERVER(Observer, observers_,
477                    OnPortalDetectionCompleted(network, state));
478}
479
480base::TimeTicks NetworkPortalDetectorImpl::GetCurrentTimeTicks() const {
481  if (time_ticks_for_testing_.is_null())
482    return base::TimeTicks::Now();
483  return time_ticks_for_testing_;
484}
485
486bool NetworkPortalDetectorImpl::DetectionTimeoutIsCancelledForTesting() const {
487  return detection_timeout_.IsCancelled();
488}
489
490int NetworkPortalDetectorImpl::GetRequestTimeoutSec() const {
491  DCHECK_LE(0, attempt_count_);
492  const NetworkState* network =
493      NetworkHandler::Get()->network_state_handler()->DefaultNetwork();
494  if (!network)
495    return kBaseRequestTimeoutSec;
496  if (lazy_detection_enabled_)
497    return kLazyRequestTimeoutSec;
498  return attempt_count_ * kBaseRequestTimeoutSec;
499}
500
501}  // namespace chromeos
502