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