network_portal_detector_impl.cc revision a1401311d1ab56c4ed0a474bd38c108f75cb0cd9
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 <algorithm>
8
9#include "base/bind.h"
10#include "base/command_line.h"
11#include "base/logging.h"
12#include "base/message_loop/message_loop.h"
13#include "base/metrics/histogram.h"
14#include "chrome/browser/chrome_notification_types.h"
15#include "chrome/browser/chromeos/login/user_manager.h"
16#include "chromeos/dbus/dbus_thread_manager.h"
17#include "chromeos/dbus/shill_profile_client.h"
18#include "chromeos/network/network_state.h"
19#include "chromeos/network/network_state_handler.h"
20#include "content/public/browser/notification_service.h"
21#include "grit/generated_resources.h"
22#include "net/http/http_status_code.h"
23#include "third_party/cros_system_api/dbus/service_constants.h"
24#include "ui/base/l10n/l10n_util.h"
25
26using captive_portal::CaptivePortalDetector;
27
28namespace chromeos {
29
30namespace {
31
32// Delay before portal detection caused by changes in proxy settings.
33const int kProxyChangeDelaySec = 1;
34
35const NetworkState* DefaultNetwork() {
36  return NetworkHandler::Get()->network_state_handler()->DefaultNetwork();
37}
38
39void RecordDiscrepancyWithShill(
40    const NetworkState* network,
41    const NetworkPortalDetector::CaptivePortalStatus status) {
42  if (network->connection_state() == shill::kStateOnline) {
43    UMA_HISTOGRAM_ENUMERATION(
44        NetworkPortalDetectorImpl::kShillOnlineHistogram,
45        status,
46        NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_COUNT);
47  } else if (network->connection_state() == shill::kStatePortal) {
48    UMA_HISTOGRAM_ENUMERATION(
49        NetworkPortalDetectorImpl::kShillPortalHistogram,
50        status,
51        NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_COUNT);
52  } else if (network->connection_state() == shill::kStateOffline) {
53    UMA_HISTOGRAM_ENUMERATION(
54        NetworkPortalDetectorImpl::kShillOfflineHistogram,
55        status,
56        NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_COUNT);
57  }
58}
59
60}  // namespace
61
62////////////////////////////////////////////////////////////////////////////////
63// NetworkPortalDetectorImpl, public:
64
65const char NetworkPortalDetectorImpl::kDetectionResultHistogram[] =
66    "CaptivePortal.OOBE.DetectionResult";
67const char NetworkPortalDetectorImpl::kDetectionDurationHistogram[] =
68    "CaptivePortal.OOBE.DetectionDuration";
69const char NetworkPortalDetectorImpl::kShillOnlineHistogram[] =
70    "CaptivePortal.OOBE.DiscrepancyWithShill_Online";
71const char NetworkPortalDetectorImpl::kShillPortalHistogram[] =
72    "CaptivePortal.OOBE.DiscrepancyWithShill_RestrictedPool";
73const char NetworkPortalDetectorImpl::kShillOfflineHistogram[] =
74    "CaptivePortal.OOBE.DiscrepancyWithShill_Offline";
75
76NetworkPortalDetectorImpl::NetworkPortalDetectorImpl(
77    const scoped_refptr<net::URLRequestContextGetter>& request_context)
78    : state_(STATE_IDLE),
79      test_url_(CaptivePortalDetector::kDefaultURL),
80      enabled_(false),
81      weak_factory_(this),
82      attempt_count_(0),
83      strategy_(PortalDetectorStrategy::CreateById(
84          PortalDetectorStrategy::STRATEGY_ID_LOGIN_SCREEN)),
85      error_screen_displayed_(false) {
86  captive_portal_detector_.reset(new CaptivePortalDetector(request_context));
87  strategy_->set_delegate(this);
88
89  registrar_.Add(this,
90                 chrome::NOTIFICATION_LOGIN_PROXY_CHANGED,
91                 content::NotificationService::AllSources());
92  registrar_.Add(this,
93                 chrome::NOTIFICATION_AUTH_SUPPLIED,
94                 content::NotificationService::AllSources());
95  registrar_.Add(this,
96                 chrome::NOTIFICATION_AUTH_CANCELLED,
97                 content::NotificationService::AllSources());
98  registrar_.Add(this,
99                 chrome::NOTIFICATION_LOGIN_USER_CHANGED,
100                 content::NotificationService::AllSources());
101
102  NetworkHandler::Get()->network_state_handler()->AddObserver(this, FROM_HERE);
103  UpdateCurrentStrategy();
104}
105
106NetworkPortalDetectorImpl::~NetworkPortalDetectorImpl() {
107  DCHECK(CalledOnValidThread());
108
109  attempt_task_.Cancel();
110  attempt_timeout_.Cancel();
111
112  captive_portal_detector_->Cancel();
113  captive_portal_detector_.reset();
114  observers_.Clear();
115  if (NetworkHandler::IsInitialized()) {
116    NetworkHandler::Get()->network_state_handler()->RemoveObserver(this,
117                                                                   FROM_HERE);
118  }
119}
120
121void NetworkPortalDetectorImpl::AddObserver(Observer* observer) {
122  DCHECK(CalledOnValidThread());
123  if (observer && !observers_.HasObserver(observer))
124    observers_.AddObserver(observer);
125}
126
127void NetworkPortalDetectorImpl::AddAndFireObserver(Observer* observer) {
128  DCHECK(CalledOnValidThread());
129  if (!observer)
130    return;
131  AddObserver(observer);
132  CaptivePortalState portal_state;
133  const NetworkState* network = DefaultNetwork();
134  if (network)
135    portal_state = GetCaptivePortalState(network->path());
136  observer->OnPortalDetectionCompleted(network, portal_state);
137}
138
139void NetworkPortalDetectorImpl::RemoveObserver(Observer* observer) {
140  DCHECK(CalledOnValidThread());
141  if (observer)
142    observers_.RemoveObserver(observer);
143}
144
145bool NetworkPortalDetectorImpl::IsEnabled() { return enabled_; }
146
147void NetworkPortalDetectorImpl::Enable(bool start_detection) {
148  DCHECK(CalledOnValidThread());
149  if (enabled_)
150    return;
151
152  DCHECK(is_idle());
153  enabled_ = true;
154
155  const NetworkState* network = DefaultNetwork();
156  if (!start_detection || !network)
157    return;
158  portal_state_map_.erase(network->path());
159  StartDetection();
160}
161
162NetworkPortalDetectorImpl::CaptivePortalState
163NetworkPortalDetectorImpl::GetCaptivePortalState(
164    const std::string& service_path) {
165  DCHECK(CalledOnValidThread());
166  CaptivePortalStateMap::const_iterator it =
167      portal_state_map_.find(service_path);
168  if (it == portal_state_map_.end())
169    return CaptivePortalState();
170  return it->second;
171}
172
173bool NetworkPortalDetectorImpl::StartDetectionIfIdle() {
174  if (!is_idle())
175    return false;
176  StartDetection();
177  return true;
178}
179
180void NetworkPortalDetectorImpl::DefaultNetworkChanged(
181    const NetworkState* default_network) {
182  DCHECK(CalledOnValidThread());
183
184  if (!default_network) {
185    default_network_name_.clear();
186    default_network_id_.clear();
187
188    StopDetection();
189
190    CaptivePortalState state;
191    state.status = CAPTIVE_PORTAL_STATUS_OFFLINE;
192    OnDetectionCompleted(NULL, state);
193    return;
194  }
195
196  default_network_name_ = default_network->name();
197  default_network_id_ = default_network->guid();
198
199  bool network_changed = (default_service_path_ != default_network->path());
200  default_service_path_ = default_network->path();
201
202  bool connection_state_changed =
203      (default_connection_state_ != default_network->connection_state());
204  default_connection_state_ = default_network->connection_state();
205
206  if (network_changed || connection_state_changed)
207    StopDetection();
208
209  if (CanPerformAttempt() &&
210      NetworkState::StateIsConnected(default_connection_state_)) {
211    // Initiate Captive Portal detection if network's captive
212    // portal state is unknown (e.g. for freshly created networks),
213    // offline or if network connection state was changed.
214    CaptivePortalState state = GetCaptivePortalState(default_network->path());
215    if (state.status == CAPTIVE_PORTAL_STATUS_UNKNOWN ||
216        state.status == CAPTIVE_PORTAL_STATUS_OFFLINE ||
217        (!network_changed && connection_state_changed)) {
218      ScheduleAttempt(base::TimeDelta());
219    }
220  }
221}
222
223int NetworkPortalDetectorImpl::AttemptCount() { return attempt_count_; }
224
225base::TimeTicks NetworkPortalDetectorImpl::AttemptStartTime() {
226  return attempt_start_time_;
227}
228
229base::TimeTicks NetworkPortalDetectorImpl::GetCurrentTimeTicks() {
230  if (time_ticks_for_testing_.is_null())
231    return base::TimeTicks::Now();
232  return time_ticks_for_testing_;
233}
234
235void NetworkPortalDetectorImpl::OnErrorScreenShow() {
236  error_screen_displayed_ = true;
237  UpdateCurrentStrategy();
238}
239
240void NetworkPortalDetectorImpl::OnErrorScreenHide() {
241  error_screen_displayed_ = false;
242  UpdateCurrentStrategy();
243}
244
245////////////////////////////////////////////////////////////////////////////////
246// NetworkPortalDetectorImpl, private:
247
248void NetworkPortalDetectorImpl::StartDetection() {
249  attempt_count_ = 0;
250  DCHECK(CanPerformAttempt());
251  detection_start_time_ = GetCurrentTimeTicks();
252  ScheduleAttempt(base::TimeDelta());
253}
254
255void NetworkPortalDetectorImpl::StopDetection() {
256  attempt_task_.Cancel();
257  attempt_timeout_.Cancel();
258  captive_portal_detector_->Cancel();
259  state_ = STATE_IDLE;
260  attempt_count_ = 0;
261}
262
263bool NetworkPortalDetectorImpl::CanPerformAttempt() const {
264  return is_idle() && strategy_->CanPerformAttempt();
265}
266
267void NetworkPortalDetectorImpl::ScheduleAttempt(const base::TimeDelta& delay) {
268  DCHECK(CanPerformAttempt());
269
270  if (!IsEnabled())
271    return;
272
273  attempt_task_.Cancel();
274  attempt_timeout_.Cancel();
275  state_ = STATE_PORTAL_CHECK_PENDING;
276
277  next_attempt_delay_ = std::max(delay, strategy_->GetDelayTillNextAttempt());
278  attempt_task_.Reset(base::Bind(&NetworkPortalDetectorImpl::StartAttempt,
279                                 weak_factory_.GetWeakPtr()));
280  base::MessageLoop::current()->PostDelayedTask(
281      FROM_HERE, attempt_task_.callback(), next_attempt_delay_);
282}
283
284void NetworkPortalDetectorImpl::StartAttempt() {
285  DCHECK(is_portal_check_pending());
286
287  state_ = STATE_CHECKING_FOR_PORTAL;
288  attempt_start_time_ = GetCurrentTimeTicks();
289
290  captive_portal_detector_->DetectCaptivePortal(
291      test_url_,
292      base::Bind(&NetworkPortalDetectorImpl::OnAttemptCompleted,
293                 weak_factory_.GetWeakPtr()));
294  attempt_timeout_.Reset(
295      base::Bind(&NetworkPortalDetectorImpl::OnAttemptTimeout,
296                 weak_factory_.GetWeakPtr()));
297
298  base::MessageLoop::current()->PostDelayedTask(
299      FROM_HERE,
300      attempt_timeout_.callback(),
301      strategy_->GetNextAttemptTimeout());
302}
303
304void NetworkPortalDetectorImpl::OnAttemptTimeout() {
305  DCHECK(CalledOnValidThread());
306  DCHECK(is_checking_for_portal());
307
308  VLOG(1) << "Portal detection timeout: name=" << default_network_name_ << ", "
309          << "id=" << default_network_id_;
310
311  captive_portal_detector_->Cancel();
312  CaptivePortalDetector::Results results;
313  results.result = captive_portal::RESULT_NO_RESPONSE;
314  OnAttemptCompleted(results);
315}
316
317void NetworkPortalDetectorImpl::OnAttemptCompleted(
318    const CaptivePortalDetector::Results& results) {
319  captive_portal::Result result = results.result;
320  int response_code = results.response_code;
321
322  DCHECK(CalledOnValidThread());
323  DCHECK(is_checking_for_portal());
324
325  VLOG(1) << "Detection attempt completed: "
326          << "name=" << default_network_name_ << ", "
327          << "id=" << default_network_id_ << ", "
328          << "result="
329          << CaptivePortalDetector::CaptivePortalResultToString(results.result)
330          << ", "
331          << "response_code=" << results.response_code;
332
333  state_ = STATE_IDLE;
334  attempt_timeout_.Cancel();
335  ++attempt_count_;
336
337  const NetworkState* network = DefaultNetwork();
338
339  // If using a fake profile client, also fake being behind a captive portal
340  // if the default network is in portal state.
341  if (result != captive_portal::RESULT_NO_RESPONSE &&
342      DBusThreadManager::Get()->GetShillProfileClient()->GetTestInterface() &&
343      network && network->connection_state() == shill::kStatePortal) {
344    result = captive_portal::RESULT_BEHIND_CAPTIVE_PORTAL;
345    response_code = 200;
346  }
347
348  CaptivePortalState state;
349  state.response_code = response_code;
350  switch (result) {
351    case captive_portal::RESULT_NO_RESPONSE:
352      if (state.response_code == net::HTTP_PROXY_AUTHENTICATION_REQUIRED) {
353        state.status = CAPTIVE_PORTAL_STATUS_PROXY_AUTH_REQUIRED;
354      } else if (CanPerformAttempt()) {
355        ScheduleAttempt(results.retry_after_delta);
356        return;
357      } else if (network &&
358                 (network->connection_state() == shill::kStatePortal)) {
359        // Take into account shill's detection results.
360        state.status = CAPTIVE_PORTAL_STATUS_PORTAL;
361        LOG(WARNING) << "Network name=" << network->name() << ", "
362                     << "id=" << network->guid() << " "
363                     << "is marked as "
364                     << CaptivePortalStatusString(state.status) << " "
365                     << "despite the fact that CaptivePortalDetector "
366                     << "received no response";
367      } else {
368        state.status = CAPTIVE_PORTAL_STATUS_OFFLINE;
369      }
370      break;
371    case captive_portal::RESULT_INTERNET_CONNECTED:
372      state.status = CAPTIVE_PORTAL_STATUS_ONLINE;
373      break;
374    case captive_portal::RESULT_BEHIND_CAPTIVE_PORTAL:
375      state.status = CAPTIVE_PORTAL_STATUS_PORTAL;
376      break;
377    default:
378      break;
379  }
380
381  OnDetectionCompleted(network, state);
382  if (CanPerformAttempt() && strategy_->CanPerformAttemptAfterDetection())
383    ScheduleAttempt(base::TimeDelta());
384}
385
386void NetworkPortalDetectorImpl::Observe(
387    int type,
388    const content::NotificationSource& source,
389    const content::NotificationDetails& details) {
390  if (type == chrome::NOTIFICATION_LOGIN_PROXY_CHANGED ||
391      type == chrome::NOTIFICATION_AUTH_SUPPLIED ||
392      type == chrome::NOTIFICATION_AUTH_CANCELLED) {
393    VLOG(1) << "Restarting portal detection due to proxy change.";
394    attempt_count_ = 0;
395    if (is_portal_check_pending())
396      return;
397    StopDetection();
398    ScheduleAttempt(base::TimeDelta::FromSeconds(kProxyChangeDelaySec));
399  } else if (type == chrome::NOTIFICATION_LOGIN_USER_CHANGED) {
400    UpdateCurrentStrategy();
401  }
402}
403
404void NetworkPortalDetectorImpl::OnDetectionCompleted(
405    const NetworkState* network,
406    const CaptivePortalState& state) {
407  if (!network) {
408    NotifyDetectionCompleted(network, state);
409    return;
410  }
411
412  CaptivePortalStateMap::const_iterator it =
413      portal_state_map_.find(network->path());
414  if (it == portal_state_map_.end() || it->second.status != state.status ||
415      it->second.response_code != state.response_code) {
416    VLOG(1) << "Updating Chrome Captive Portal state: "
417            << "name=" << network->name() << ", "
418            << "id=" << network->guid() << ", "
419            << "status=" << CaptivePortalStatusString(state.status) << ", "
420            << "response_code=" << state.response_code;
421
422    // Record detection duration iff detection result differs from the
423    // previous one for this network. The reason is to record all stats
424    // only when network changes it's state.
425    RecordDetectionStats(network, state.status);
426
427    portal_state_map_[network->path()] = state;
428  }
429  NotifyDetectionCompleted(network, state);
430}
431
432void NetworkPortalDetectorImpl::NotifyDetectionCompleted(
433    const NetworkState* network,
434    const CaptivePortalState& state) {
435  FOR_EACH_OBSERVER(
436      Observer, observers_, OnPortalDetectionCompleted(network, state));
437  notification_controller_.OnPortalDetectionCompleted(network, state);
438}
439
440bool NetworkPortalDetectorImpl::AttemptTimeoutIsCancelledForTesting() const {
441  return attempt_timeout_.IsCancelled();
442}
443
444void NetworkPortalDetectorImpl::RecordDetectionStats(
445    const NetworkState* network,
446    CaptivePortalStatus status) {
447  // Don't record stats for offline state.
448  if (!network)
449    return;
450
451  if (!detection_start_time_.is_null()) {
452    UMA_HISTOGRAM_MEDIUM_TIMES(kDetectionDurationHistogram,
453                               GetCurrentTimeTicks() - detection_start_time_);
454  }
455  UMA_HISTOGRAM_ENUMERATION(kDetectionResultHistogram,
456                            status,
457                            NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_COUNT);
458  switch (status) {
459    case NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_UNKNOWN:
460      NOTREACHED();
461      break;
462    case NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_OFFLINE:
463      if (network->connection_state() == shill::kStateOnline ||
464          network->connection_state() == shill::kStatePortal) {
465        RecordDiscrepancyWithShill(network, status);
466      }
467      break;
468    case NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_ONLINE:
469      if (network->connection_state() != shill::kStateOnline)
470        RecordDiscrepancyWithShill(network, status);
471      break;
472    case NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_PORTAL:
473      if (network->connection_state() != shill::kStatePortal)
474        RecordDiscrepancyWithShill(network, status);
475      break;
476    case NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_PROXY_AUTH_REQUIRED:
477      if (network->connection_state() != shill::kStateOnline)
478        RecordDiscrepancyWithShill(network, status);
479      break;
480    case NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_COUNT:
481      NOTREACHED();
482      break;
483  }
484}
485
486void NetworkPortalDetectorImpl::UpdateCurrentStrategy() {
487  if (UserManager::IsInitialized() && UserManager::Get()->IsUserLoggedIn()) {
488    SetStrategy(PortalDetectorStrategy::STRATEGY_ID_SESSION);
489    return;
490  }
491  if (error_screen_displayed_) {
492    SetStrategy(PortalDetectorStrategy::STRATEGY_ID_ERROR_SCREEN);
493    return;
494  }
495  SetStrategy(PortalDetectorStrategy::STRATEGY_ID_LOGIN_SCREEN);
496}
497
498void NetworkPortalDetectorImpl::SetStrategy(
499    PortalDetectorStrategy::StrategyId id) {
500  if (id == strategy_->Id())
501    return;
502  strategy_.reset(PortalDetectorStrategy::CreateById(id).release());
503  strategy_->set_delegate(this);
504  StopDetection();
505  StartDetectionIfIdle();
506}
507
508}  // namespace chromeos
509