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.
5 #include "chrome/browser/chromeos/net/network_portal_detector_impl.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 "chromeos/dbus/dbus_thread_manager.h"
16 #include "chromeos/dbus/shill_profile_client.h"
17 #include "chromeos/login/login_state.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 "content/public/common/content_switches.h"
22 #include "net/http/http_status_code.h"
23 #include "third_party/cros_system_api/dbus/service_constants.h"
25 using captive_portal::CaptivePortalDetector;
31 // Delay before portal detection caused by changes in proxy settings.
32 const int kProxyChangeDelaySec = 1;
34 // Maximum number of reports from captive portal detector about
35 // offline state in a row before notification is sent to observers.
36 const int kMaxOfflineResultsBeforeReport = 3;
38 // Delay before portal detection attempt after !ONLINE -> !ONLINE
40 const int kShortInitialDelayBetweenAttemptsMs = 600;
42 // Maximum timeout before portal detection attempts after !ONLINE ->
43 // !ONLINE transition.
44 const int kShortMaximumDelayBetweenAttemptsMs = 2 * 60 * 1000;
46 // Delay before portal detection attempt after !ONLINE -> ONLINE
48 const int kLongInitialDelayBetweenAttemptsMs = 30 * 1000;
50 // Maximum timeout before portal detection attempts after !ONLINE ->
52 const int kLongMaximumDelayBetweenAttemptsMs = 5 * 60 * 1000;
54 const NetworkState* DefaultNetwork() {
55 return NetworkHandler::Get()->network_state_handler()->DefaultNetwork();
59 return LoginState::IsInitialized() && LoginState::Get()->IsUserLoggedIn();
62 void RecordDetectionResult(NetworkPortalDetector::CaptivePortalStatus status) {
64 UMA_HISTOGRAM_ENUMERATION(
65 NetworkPortalDetectorImpl::kSessionDetectionResultHistogram,
67 NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_COUNT);
69 UMA_HISTOGRAM_ENUMERATION(
70 NetworkPortalDetectorImpl::kOobeDetectionResultHistogram,
72 NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_COUNT);
76 void RecordDetectionDuration(const base::TimeDelta& duration) {
78 UMA_HISTOGRAM_MEDIUM_TIMES(
79 NetworkPortalDetectorImpl::kSessionDetectionDurationHistogram,
82 UMA_HISTOGRAM_MEDIUM_TIMES(
83 NetworkPortalDetectorImpl::kOobeDetectionDurationHistogram, duration);
87 void RecordDiscrepancyWithShill(
88 const NetworkState* network,
89 const NetworkPortalDetector::CaptivePortalStatus status) {
91 if (network->connection_state() == shill::kStateOnline) {
92 UMA_HISTOGRAM_ENUMERATION(
93 NetworkPortalDetectorImpl::kSessionShillOnlineHistogram,
95 NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_COUNT);
96 } else if (network->connection_state() == shill::kStatePortal) {
97 UMA_HISTOGRAM_ENUMERATION(
98 NetworkPortalDetectorImpl::kSessionShillPortalHistogram,
100 NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_COUNT);
101 } else if (network->connection_state() == shill::kStateOffline) {
102 UMA_HISTOGRAM_ENUMERATION(
103 NetworkPortalDetectorImpl::kSessionShillOfflineHistogram,
105 NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_COUNT);
108 if (network->connection_state() == shill::kStateOnline) {
109 UMA_HISTOGRAM_ENUMERATION(
110 NetworkPortalDetectorImpl::kOobeShillOnlineHistogram,
112 NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_COUNT);
113 } else if (network->connection_state() == shill::kStatePortal) {
114 UMA_HISTOGRAM_ENUMERATION(
115 NetworkPortalDetectorImpl::kOobeShillPortalHistogram,
117 NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_COUNT);
118 } else if (network->connection_state() == shill::kStateOffline) {
119 UMA_HISTOGRAM_ENUMERATION(
120 NetworkPortalDetectorImpl::kOobeShillOfflineHistogram,
122 NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_COUNT);
127 void RecordPortalToOnlineTransition(const base::TimeDelta& duration) {
129 UMA_HISTOGRAM_LONG_TIMES(
130 NetworkPortalDetectorImpl::kSessionPortalToOnlineHistogram,
133 UMA_HISTOGRAM_LONG_TIMES(
134 NetworkPortalDetectorImpl::kOobePortalToOnlineHistogram,
141 ////////////////////////////////////////////////////////////////////////////////
142 // NetworkPortalDetectorImpl::DetectionAttemptCompletedLogState
144 NetworkPortalDetectorImpl::DetectionAttemptCompletedReport::
145 DetectionAttemptCompletedReport()
146 : result(captive_portal::RESULT_COUNT), response_code(-1) {
149 NetworkPortalDetectorImpl::DetectionAttemptCompletedReport::
150 DetectionAttemptCompletedReport(const std::string network_name,
151 const std::string network_id,
152 captive_portal::CaptivePortalResult result,
154 : network_name(network_name),
155 network_id(network_id),
157 response_code(response_code) {
160 void NetworkPortalDetectorImpl::DetectionAttemptCompletedReport::Report()
162 VLOG(1) << "Detection attempt completed: "
163 << "name=" << network_name << ", "
164 << "id=" << network_id << ", "
165 << "result=" << captive_portal::CaptivePortalResultToString(result)
167 << "response_code=" << response_code;
170 bool NetworkPortalDetectorImpl::DetectionAttemptCompletedReport::Equals(
171 const DetectionAttemptCompletedReport& o) const {
172 return network_name == o.network_name && network_id == o.network_id &&
173 result == o.result && response_code == o.response_code;
176 ////////////////////////////////////////////////////////////////////////////////
177 // NetworkPortalDetectorImpl, public:
179 const char NetworkPortalDetectorImpl::kOobeDetectionResultHistogram[] =
180 "CaptivePortal.OOBE.DetectionResult";
181 const char NetworkPortalDetectorImpl::kOobeDetectionDurationHistogram[] =
182 "CaptivePortal.OOBE.DetectionDuration";
183 const char NetworkPortalDetectorImpl::kOobeShillOnlineHistogram[] =
184 "CaptivePortal.OOBE.DiscrepancyWithShill_Online";
185 const char NetworkPortalDetectorImpl::kOobeShillPortalHistogram[] =
186 "CaptivePortal.OOBE.DiscrepancyWithShill_RestrictedPool";
187 const char NetworkPortalDetectorImpl::kOobeShillOfflineHistogram[] =
188 "CaptivePortal.OOBE.DiscrepancyWithShill_Offline";
189 const char NetworkPortalDetectorImpl::kOobePortalToOnlineHistogram[] =
190 "CaptivePortal.OOBE.PortalToOnlineTransition";
192 const char NetworkPortalDetectorImpl::kSessionDetectionResultHistogram[] =
193 "CaptivePortal.Session.DetectionResult";
194 const char NetworkPortalDetectorImpl::kSessionDetectionDurationHistogram[] =
195 "CaptivePortal.Session.DetectionDuration";
196 const char NetworkPortalDetectorImpl::kSessionShillOnlineHistogram[] =
197 "CaptivePortal.Session.DiscrepancyWithShill_Online";
198 const char NetworkPortalDetectorImpl::kSessionShillPortalHistogram[] =
199 "CaptivePortal.Session.DiscrepancyWithShill_RestrictedPool";
200 const char NetworkPortalDetectorImpl::kSessionShillOfflineHistogram[] =
201 "CaptivePortal.Session.DiscrepancyWithShill_Offline";
202 const char NetworkPortalDetectorImpl::kSessionPortalToOnlineHistogram[] =
203 "CaptivePortal.Session.PortalToOnlineTransition";
206 void NetworkPortalDetectorImpl::Initialize(
207 net::URLRequestContextGetter* url_context) {
208 if (NetworkPortalDetector::set_for_testing())
210 CHECK(!NetworkPortalDetector::network_portal_detector())
211 << "NetworkPortalDetector was initialized twice.";
212 if (CommandLine::ForCurrentProcess()->HasSwitch(::switches::kTestType))
213 set_network_portal_detector(new NetworkPortalDetectorStubImpl());
215 set_network_portal_detector(new NetworkPortalDetectorImpl(url_context));
218 NetworkPortalDetectorImpl::NetworkPortalDetectorImpl(
219 const scoped_refptr<net::URLRequestContextGetter>& request_context)
220 : state_(STATE_IDLE),
221 test_url_(CaptivePortalDetector::kDefaultURL),
223 strategy_(PortalDetectorStrategy::CreateById(
224 PortalDetectorStrategy::STRATEGY_ID_LOGIN_SCREEN, this)),
225 last_detection_result_(CAPTIVE_PORTAL_STATUS_UNKNOWN),
226 same_detection_result_count_(0),
227 no_response_result_count_(0),
228 weak_factory_(this) {
229 captive_portal_detector_.reset(new CaptivePortalDetector(request_context));
232 chrome::NOTIFICATION_LOGIN_PROXY_CHANGED,
233 content::NotificationService::AllSources());
235 chrome::NOTIFICATION_AUTH_SUPPLIED,
236 content::NotificationService::AllSources());
238 chrome::NOTIFICATION_AUTH_CANCELLED,
239 content::NotificationService::AllSources());
241 NetworkHandler::Get()->network_state_handler()->AddObserver(this, FROM_HERE);
242 StartDetectionIfIdle();
245 NetworkPortalDetectorImpl::~NetworkPortalDetectorImpl() {
246 DCHECK(CalledOnValidThread());
248 attempt_task_.Cancel();
249 attempt_timeout_.Cancel();
251 captive_portal_detector_->Cancel();
252 captive_portal_detector_.reset();
254 if (NetworkHandler::IsInitialized()) {
255 NetworkHandler::Get()->network_state_handler()->RemoveObserver(this,
260 void NetworkPortalDetectorImpl::AddObserver(Observer* observer) {
261 DCHECK(CalledOnValidThread());
262 if (observer && !observers_.HasObserver(observer))
263 observers_.AddObserver(observer);
266 void NetworkPortalDetectorImpl::AddAndFireObserver(Observer* observer) {
267 DCHECK(CalledOnValidThread());
270 AddObserver(observer);
271 CaptivePortalState portal_state;
272 const NetworkState* network = DefaultNetwork();
274 portal_state = GetCaptivePortalState(network->guid());
275 observer->OnPortalDetectionCompleted(network, portal_state);
278 void NetworkPortalDetectorImpl::RemoveObserver(Observer* observer) {
279 DCHECK(CalledOnValidThread());
281 observers_.RemoveObserver(observer);
284 bool NetworkPortalDetectorImpl::IsEnabled() { return enabled_; }
286 void NetworkPortalDetectorImpl::Enable(bool start_detection) {
287 DCHECK(CalledOnValidThread());
294 const NetworkState* network = DefaultNetwork();
295 if (!start_detection || !network)
297 VLOG(1) << "Starting detection for: "
298 << "name=" << network->name() << ", id=" << network->guid();
299 portal_state_map_.erase(network->guid());
303 NetworkPortalDetectorImpl::CaptivePortalState
304 NetworkPortalDetectorImpl::GetCaptivePortalState(const std::string& guid) {
305 DCHECK(CalledOnValidThread());
306 CaptivePortalStateMap::const_iterator it = portal_state_map_.find(guid);
307 if (it == portal_state_map_.end())
308 return CaptivePortalState();
312 bool NetworkPortalDetectorImpl::StartDetectionIfIdle() {
319 void NetworkPortalDetectorImpl::SetStrategy(
320 PortalDetectorStrategy::StrategyId id) {
321 if (id == strategy_->Id())
323 strategy_ = PortalDetectorStrategy::CreateById(id, this).Pass();
325 StartDetectionIfIdle();
328 void NetworkPortalDetectorImpl::DefaultNetworkChanged(
329 const NetworkState* default_network) {
330 DCHECK(CalledOnValidThread());
332 if (!default_network) {
333 VLOG(1) << "DefaultNetworkChanged: None.";
334 default_network_name_.clear();
338 CaptivePortalState state;
339 state.status = CAPTIVE_PORTAL_STATUS_OFFLINE;
340 OnDetectionCompleted(NULL, state);
344 default_network_name_ = default_network->name();
346 bool network_changed = (default_network_id_ != default_network->guid());
347 default_network_id_ = default_network->guid();
349 bool connection_state_changed =
350 (default_connection_state_ != default_network->connection_state());
351 default_connection_state_ = default_network->connection_state();
353 VLOG(1) << "DefaultNetworkChanged: "
354 << "name=" << default_network_name_ << ", "
355 << "id=" << default_network_id_ << ", "
356 << "state=" << default_connection_state_ << ", "
357 << "changed=" << network_changed << ", "
358 << "state_changed=" << connection_state_changed;
360 if (network_changed || connection_state_changed)
363 if (is_idle() && NetworkState::StateIsConnected(default_connection_state_)) {
364 // Initiate Captive Portal detection if network's captive
365 // portal state is unknown (e.g. for freshly created networks),
366 // offline or if network connection state was changed.
367 CaptivePortalState state = GetCaptivePortalState(default_network->guid());
368 if (state.status == CAPTIVE_PORTAL_STATUS_UNKNOWN ||
369 state.status == CAPTIVE_PORTAL_STATUS_OFFLINE ||
370 (!network_changed && connection_state_changed)) {
371 ScheduleAttempt(base::TimeDelta());
376 int NetworkPortalDetectorImpl::NoResponseResultCount() {
377 return no_response_result_count_;
380 base::TimeTicks NetworkPortalDetectorImpl::AttemptStartTime() {
381 return attempt_start_time_;
384 base::TimeTicks NetworkPortalDetectorImpl::GetCurrentTimeTicks() {
385 if (time_ticks_for_testing_.is_null())
386 return base::TimeTicks::Now();
387 return time_ticks_for_testing_;
391 ////////////////////////////////////////////////////////////////////////////////
392 // NetworkPortalDetectorImpl, private:
394 void NetworkPortalDetectorImpl::StartDetection() {
397 ResetStrategyAndCounters();
398 detection_start_time_ = GetCurrentTimeTicks();
399 ScheduleAttempt(base::TimeDelta());
402 void NetworkPortalDetectorImpl::StopDetection() {
403 attempt_task_.Cancel();
404 attempt_timeout_.Cancel();
405 captive_portal_detector_->Cancel();
407 ResetStrategyAndCounters();
410 void NetworkPortalDetectorImpl::ScheduleAttempt(const base::TimeDelta& delay) {
416 attempt_task_.Cancel();
417 attempt_timeout_.Cancel();
418 state_ = STATE_PORTAL_CHECK_PENDING;
420 next_attempt_delay_ = std::max(delay, strategy_->GetDelayTillNextAttempt());
421 attempt_task_.Reset(base::Bind(&NetworkPortalDetectorImpl::StartAttempt,
422 weak_factory_.GetWeakPtr()));
423 base::MessageLoop::current()->PostDelayedTask(
424 FROM_HERE, attempt_task_.callback(), next_attempt_delay_);
427 void NetworkPortalDetectorImpl::StartAttempt() {
428 DCHECK(is_portal_check_pending());
430 state_ = STATE_CHECKING_FOR_PORTAL;
431 attempt_start_time_ = GetCurrentTimeTicks();
433 captive_portal_detector_->DetectCaptivePortal(
435 base::Bind(&NetworkPortalDetectorImpl::OnAttemptCompleted,
436 weak_factory_.GetWeakPtr()));
437 attempt_timeout_.Reset(
438 base::Bind(&NetworkPortalDetectorImpl::OnAttemptTimeout,
439 weak_factory_.GetWeakPtr()));
441 base::MessageLoop::current()->PostDelayedTask(
443 attempt_timeout_.callback(),
444 strategy_->GetNextAttemptTimeout());
447 void NetworkPortalDetectorImpl::OnAttemptTimeout() {
448 DCHECK(CalledOnValidThread());
449 DCHECK(is_checking_for_portal());
451 VLOG(1) << "Portal detection timeout: name=" << default_network_name_ << ", "
452 << "id=" << default_network_id_;
454 captive_portal_detector_->Cancel();
455 CaptivePortalDetector::Results results;
456 results.result = captive_portal::RESULT_NO_RESPONSE;
457 OnAttemptCompleted(results);
460 void NetworkPortalDetectorImpl::OnAttemptCompleted(
461 const CaptivePortalDetector::Results& results) {
462 DCHECK(CalledOnValidThread());
463 DCHECK(is_checking_for_portal());
465 captive_portal::CaptivePortalResult result = results.result;
466 int response_code = results.response_code;
468 const NetworkState* network = DefaultNetwork();
470 // If using a fake profile client, also fake being behind a captive portal
471 // if the default network is in portal state.
472 if (result != captive_portal::RESULT_NO_RESPONSE &&
473 DBusThreadManager::Get()->GetShillProfileClient()->GetTestInterface() &&
474 network && network->connection_state() == shill::kStatePortal) {
475 result = captive_portal::RESULT_BEHIND_CAPTIVE_PORTAL;
479 DetectionAttemptCompletedReport attempt_completed_report(
480 default_network_name_, default_network_id_, result, response_code);
481 if (!attempt_completed_report_.Equals(attempt_completed_report)) {
482 attempt_completed_report_ = attempt_completed_report;
483 attempt_completed_report_.Report();
487 attempt_timeout_.Cancel();
489 CaptivePortalState state;
490 state.response_code = response_code;
491 state.time = GetCurrentTimeTicks();
493 case captive_portal::RESULT_NO_RESPONSE:
494 if (state.response_code == net::HTTP_PROXY_AUTHENTICATION_REQUIRED) {
495 state.status = CAPTIVE_PORTAL_STATUS_PROXY_AUTH_REQUIRED;
496 } else if (network &&
497 (network->connection_state() == shill::kStatePortal)) {
498 // Take into account shill's detection results.
499 state.status = CAPTIVE_PORTAL_STATUS_PORTAL;
501 state.status = CAPTIVE_PORTAL_STATUS_OFFLINE;
504 case captive_portal::RESULT_INTERNET_CONNECTED:
505 state.status = CAPTIVE_PORTAL_STATUS_ONLINE;
507 case captive_portal::RESULT_BEHIND_CAPTIVE_PORTAL:
508 state.status = CAPTIVE_PORTAL_STATUS_PORTAL;
514 if (last_detection_result_ != state.status) {
515 last_detection_result_ = state.status;
516 same_detection_result_count_ = 1;
517 net::BackoffEntry::Policy policy = strategy_->policy();
518 if (state.status == CAPTIVE_PORTAL_STATUS_ONLINE) {
519 policy.initial_delay_ms = kLongInitialDelayBetweenAttemptsMs;
520 policy.maximum_backoff_ms = kLongMaximumDelayBetweenAttemptsMs;
522 policy.initial_delay_ms = kShortInitialDelayBetweenAttemptsMs;
523 policy.maximum_backoff_ms = kShortMaximumDelayBetweenAttemptsMs;
525 strategy_->SetPolicyAndReset(policy);
527 ++same_detection_result_count_;
529 strategy_->OnDetectionCompleted();
531 if (result == captive_portal::RESULT_NO_RESPONSE)
532 ++no_response_result_count_;
534 no_response_result_count_ = 0;
536 if (state.status != CAPTIVE_PORTAL_STATUS_OFFLINE ||
537 same_detection_result_count_ >= kMaxOfflineResultsBeforeReport) {
538 OnDetectionCompleted(network, state);
540 ScheduleAttempt(results.retry_after_delta);
543 void NetworkPortalDetectorImpl::Observe(
545 const content::NotificationSource& source,
546 const content::NotificationDetails& details) {
547 if (type == chrome::NOTIFICATION_LOGIN_PROXY_CHANGED ||
548 type == chrome::NOTIFICATION_AUTH_SUPPLIED ||
549 type == chrome::NOTIFICATION_AUTH_CANCELLED) {
550 VLOG(1) << "Restarting portal detection due to proxy change.";
552 ScheduleAttempt(base::TimeDelta::FromSeconds(kProxyChangeDelaySec));
556 void NetworkPortalDetectorImpl::OnDetectionCompleted(
557 const NetworkState* network,
558 const CaptivePortalState& state) {
560 NotifyDetectionCompleted(network, state);
564 CaptivePortalStateMap::const_iterator it =
565 portal_state_map_.find(network->guid());
566 if (it == portal_state_map_.end() || it->second.status != state.status ||
567 it->second.response_code != state.response_code) {
568 // Record detection duration iff detection result differs from the
569 // previous one for this network. The reason is to record all stats
570 // only when network changes it's state.
571 RecordDetectionStats(network, state.status);
572 if (it != portal_state_map_.end() &&
573 it->second.status == CAPTIVE_PORTAL_STATUS_PORTAL &&
574 state.status == CAPTIVE_PORTAL_STATUS_ONLINE) {
575 RecordPortalToOnlineTransition(state.time - it->second.time);
578 portal_state_map_[network->guid()] = state;
580 NotifyDetectionCompleted(network, state);
583 void NetworkPortalDetectorImpl::NotifyDetectionCompleted(
584 const NetworkState* network,
585 const CaptivePortalState& state) {
587 Observer, observers_, OnPortalDetectionCompleted(network, state));
588 notification_controller_.OnPortalDetectionCompleted(network, state);
591 bool NetworkPortalDetectorImpl::AttemptTimeoutIsCancelledForTesting() const {
592 return attempt_timeout_.IsCancelled();
595 void NetworkPortalDetectorImpl::RecordDetectionStats(
596 const NetworkState* network,
597 CaptivePortalStatus status) {
598 // Don't record stats for offline state.
602 if (!detection_start_time_.is_null())
603 RecordDetectionDuration(GetCurrentTimeTicks() - detection_start_time_);
604 RecordDetectionResult(status);
607 case NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_UNKNOWN:
610 case NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_OFFLINE:
611 if (network->connection_state() == shill::kStateOnline ||
612 network->connection_state() == shill::kStatePortal) {
613 RecordDiscrepancyWithShill(network, status);
616 case NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_ONLINE:
617 if (network->connection_state() != shill::kStateOnline)
618 RecordDiscrepancyWithShill(network, status);
620 case NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_PORTAL:
621 if (network->connection_state() != shill::kStatePortal)
622 RecordDiscrepancyWithShill(network, status);
624 case NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_PROXY_AUTH_REQUIRED:
625 if (network->connection_state() != shill::kStateOnline)
626 RecordDiscrepancyWithShill(network, status);
628 case NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_COUNT:
634 void NetworkPortalDetectorImpl::ResetStrategyAndCounters() {
635 last_detection_result_ = CAPTIVE_PORTAL_STATUS_UNKNOWN;
636 same_detection_result_count_ = 0;
637 no_response_result_count_ = 0;
641 } // namespace chromeos