Disable view source for Developer Tools.
[chromium-blink-merge.git] / chrome / browser / chromeos / net / network_portal_detector_impl.cc
blob86b2a6ea58c0db087461c9a1062fd9c120facb53
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"
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"
22 using captive_portal::CaptivePortalDetector;
24 namespace chromeos {
26 namespace {
28 // Maximum number of portal detections for the same default network
29 // after network change.
30 const int kMaxRequestAttempts = 3;
32 // Minimum timeout between consecutive portal checks for the same
33 // network.
34 const int kMinTimeBetweenAttemptsSec = 3;
36 // Delay before portal detection caused by changes in proxy settings.
37 const int kProxyChangeDelaySec = 1;
39 // Delay between consecutive portal checks for a network in lazy mode.
40 const int kLazyCheckIntervalSec = 5;
42 void RecordDiscrepancyWithShill(
43 const NetworkState* network,
44 const NetworkPortalDetector::CaptivePortalStatus status) {
45 if (network->connection_state() == shill::kStateOnline) {
46 UMA_HISTOGRAM_ENUMERATION(
47 NetworkPortalDetectorImpl::kShillOnlineHistogram,
48 status,
49 NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_COUNT);
50 } else if (network->connection_state() == shill::kStatePortal) {
51 UMA_HISTOGRAM_ENUMERATION(
52 NetworkPortalDetectorImpl::kShillPortalHistogram,
53 status,
54 NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_COUNT);
55 } else if (network->connection_state() == shill::kStateOffline) {
56 UMA_HISTOGRAM_ENUMERATION(
57 NetworkPortalDetectorImpl::kShillOfflineHistogram,
58 status,
59 NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_COUNT);
63 } // namespace
65 ////////////////////////////////////////////////////////////////////////////////
66 // NetworkPortalDetectorImpl, public:
68 const char NetworkPortalDetectorImpl::kDetectionResultHistogram[] =
69 "CaptivePortal.OOBE.DetectionResult";
70 const char NetworkPortalDetectorImpl::kDetectionDurationHistogram[] =
71 "CaptivePortal.OOBE.DetectionDuration";
72 const char NetworkPortalDetectorImpl::kShillOnlineHistogram[] =
73 "CaptivePortal.OOBE.DiscrepancyWithShill_Online";
74 const char NetworkPortalDetectorImpl::kShillPortalHistogram[] =
75 "CaptivePortal.OOBE.DiscrepancyWithShill_RestrictedPool";
76 const char NetworkPortalDetectorImpl::kShillOfflineHistogram[] =
77 "CaptivePortal.OOBE.DiscrepancyWithShill_Offline";
79 NetworkPortalDetectorImpl::NetworkPortalDetectorImpl(
80 const scoped_refptr<net::URLRequestContextGetter>& request_context)
81 : state_(STATE_IDLE),
82 test_url_(CaptivePortalDetector::kDefaultURL),
83 enabled_(false),
84 weak_ptr_factory_(this),
85 attempt_count_(0),
86 lazy_detection_enabled_(false),
87 lazy_check_interval_(base::TimeDelta::FromSeconds(kLazyCheckIntervalSec)),
88 min_time_between_attempts_(
89 base::TimeDelta::FromSeconds(kMinTimeBetweenAttemptsSec)),
90 request_timeout_for_testing_initialized_(false) {
91 captive_portal_detector_.reset(new CaptivePortalDetector(request_context));
93 registrar_.Add(this,
94 chrome::NOTIFICATION_LOGIN_PROXY_CHANGED,
95 content::NotificationService::AllSources());
96 registrar_.Add(this,
97 chrome::NOTIFICATION_AUTH_SUPPLIED,
98 content::NotificationService::AllSources());
99 registrar_.Add(this,
100 chrome::NOTIFICATION_AUTH_CANCELLED,
101 content::NotificationService::AllSources());
103 NetworkHandler::Get()->network_state_handler()->AddObserver(
104 this, FROM_HERE);
107 NetworkPortalDetectorImpl::~NetworkPortalDetectorImpl() {
108 DCHECK(CalledOnValidThread());
110 detection_task_.Cancel();
111 detection_timeout_.Cancel();
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);
122 void NetworkPortalDetectorImpl::AddObserver(Observer* observer) {
123 DCHECK(CalledOnValidThread());
124 if (!observer || observers_.HasObserver(observer))
125 return;
126 observers_.AddObserver(observer);
129 void 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));
139 void NetworkPortalDetectorImpl::RemoveObserver(Observer* observer) {
140 DCHECK(CalledOnValidThread());
141 if (observer)
142 observers_.RemoveObserver(observer);
145 bool NetworkPortalDetectorImpl::IsEnabled() {
146 return enabled_;
149 void 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());
170 NetworkPortalDetectorImpl::CaptivePortalState
171 NetworkPortalDetectorImpl::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;
182 bool 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;
192 void NetworkPortalDetectorImpl::EnableLazyDetection() {
193 if (lazy_detection_enabled())
194 return;
195 lazy_detection_enabled_ = true;
196 VLOG(1) << "Lazy detection mode enabled.";
197 StartDetectionIfIdle();
200 void NetworkPortalDetectorImpl::DisableLazyDetection() {
201 if (!lazy_detection_enabled())
202 return;
203 lazy_detection_enabled_ = false;
204 if (attempt_count_ == kMaxRequestAttempts && IsPortalCheckPending())
205 CancelPortalDetection();
206 VLOG(1) << "Lazy detection mode disabled.";
209 void NetworkPortalDetectorImpl::DefaultNetworkChanged(
210 const NetworkState* default_network) {
211 DCHECK(CalledOnValidThread());
213 if (!default_network) {
214 default_network_name_.clear();
215 default_network_id_.clear();
217 CancelPortalDetection();
219 CaptivePortalState state;
220 state.status = CAPTIVE_PORTAL_STATUS_OFFLINE;
221 SetCaptivePortalState(NULL, state);
222 return;
225 default_network_name_ = default_network->name();
226 default_network_id_ = default_network->guid();
228 bool network_changed = (default_service_path_ != default_network->path());
229 default_service_path_ = default_network->path();
231 bool connection_state_changed = (default_connection_state_ !=
232 default_network->connection_state());
233 default_connection_state_ = default_network->connection_state();
235 if (network_changed || connection_state_changed) {
236 attempt_count_ = 0;
237 CancelPortalDetection();
240 if (CanPerformDetection() &&
241 NetworkState::StateIsConnected(default_connection_state_)) {
242 // Initiate Captive Portal detection if network's captive
243 // portal state is unknown (e.g. for freshly created networks),
244 // offline or if network connection state was changed.
245 CaptivePortalState state = GetCaptivePortalState(default_network);
246 if (state.status == CAPTIVE_PORTAL_STATUS_UNKNOWN ||
247 state.status == CAPTIVE_PORTAL_STATUS_OFFLINE ||
248 (!network_changed && connection_state_changed)) {
249 DetectCaptivePortal(base::TimeDelta());
254 ////////////////////////////////////////////////////////////////////////////////
255 // NetworkPortalDetectorImpl, private:
257 bool NetworkPortalDetectorImpl::CanPerformDetection() const {
258 if (IsPortalCheckPending() || IsCheckingForPortal())
259 return false;
260 return attempt_count_ < kMaxRequestAttempts || lazy_detection_enabled();
263 void NetworkPortalDetectorImpl::DetectCaptivePortal(
264 const base::TimeDelta& delay) {
265 DCHECK(CanPerformDetection());
267 if (!IsEnabled())
268 return;
270 detection_task_.Cancel();
271 detection_timeout_.Cancel();
272 state_ = STATE_PORTAL_CHECK_PENDING;
274 next_attempt_delay_ = delay;
275 if (attempt_count_ > 0) {
276 base::TimeTicks now = GetCurrentTimeTicks();
277 base::TimeDelta elapsed_time;
279 base::TimeDelta delay_between_attempts = min_time_between_attempts_;
280 if (attempt_count_ == kMaxRequestAttempts) {
281 DCHECK(lazy_detection_enabled());
282 delay_between_attempts = lazy_check_interval_;
284 if (now > attempt_start_time_)
285 elapsed_time = now - attempt_start_time_;
286 if (elapsed_time < delay_between_attempts &&
287 delay_between_attempts - elapsed_time > next_attempt_delay_) {
288 next_attempt_delay_ = delay_between_attempts - elapsed_time;
290 } else {
291 detection_start_time_ = GetCurrentTimeTicks();
293 detection_task_.Reset(
294 base::Bind(&NetworkPortalDetectorImpl::DetectCaptivePortalTask,
295 weak_ptr_factory_.GetWeakPtr()));
296 base::MessageLoop::current()->PostDelayedTask(
297 FROM_HERE, detection_task_.callback(), next_attempt_delay_);
300 void NetworkPortalDetectorImpl::DetectCaptivePortalTask() {
301 DCHECK(IsPortalCheckPending());
303 state_ = STATE_CHECKING_FOR_PORTAL;
304 attempt_start_time_ = GetCurrentTimeTicks();
306 if (attempt_count_ < kMaxRequestAttempts) {
307 ++attempt_count_;
308 VLOG(1) << "Portal detection started: "
309 << "name=" << default_network_name_ << ", "
310 << "id=" << default_network_id_ << ", "
311 << "attempt=" << attempt_count_ << " of " << kMaxRequestAttempts;
312 } else {
313 DCHECK(lazy_detection_enabled());
314 VLOG(1) << "Lazy portal detection attempt started";
317 captive_portal_detector_->DetectCaptivePortal(
318 test_url_,
319 base::Bind(&NetworkPortalDetectorImpl::OnPortalDetectionCompleted,
320 weak_ptr_factory_.GetWeakPtr()));
321 detection_timeout_.Reset(
322 base::Bind(&NetworkPortalDetectorImpl::PortalDetectionTimeout,
323 weak_ptr_factory_.GetWeakPtr()));
324 base::TimeDelta request_timeout;
326 // For easier unit testing check for testing state is performed here
327 // and not in the GetRequestTimeoutSec().
328 if (request_timeout_for_testing_initialized_)
329 request_timeout = request_timeout_for_testing_;
330 else
331 request_timeout = base::TimeDelta::FromSeconds(GetRequestTimeoutSec());
332 base::MessageLoop::current()->PostDelayedTask(
333 FROM_HERE, detection_timeout_.callback(), request_timeout);
336 void NetworkPortalDetectorImpl::PortalDetectionTimeout() {
337 DCHECK(CalledOnValidThread());
338 DCHECK(IsCheckingForPortal());
340 VLOG(1) << "Portal detection timeout: name=" << default_network_name_ << ", "
341 << "id=" << default_network_id_;
343 captive_portal_detector_->Cancel();
344 CaptivePortalDetector::Results results;
345 results.result = captive_portal::RESULT_NO_RESPONSE;
346 OnPortalDetectionCompleted(results);
349 void NetworkPortalDetectorImpl::CancelPortalDetection() {
350 if (IsPortalCheckPending())
351 detection_task_.Cancel();
352 else if (IsCheckingForPortal())
353 captive_portal_detector_->Cancel();
354 detection_timeout_.Cancel();
355 state_ = STATE_IDLE;
358 void NetworkPortalDetectorImpl::OnPortalDetectionCompleted(
359 const CaptivePortalDetector::Results& results) {
360 captive_portal::Result result = results.result;
361 int response_code = results.response_code;
363 if (shill_stub_helper::IsStubPortalledWifiEnabled(default_service_path_)) {
364 result = captive_portal::RESULT_BEHIND_CAPTIVE_PORTAL;
365 response_code = 200;
368 DCHECK(CalledOnValidThread());
369 DCHECK(IsCheckingForPortal());
371 VLOG(1) << "Portal detection completed: "
372 << "name=" << default_network_name_ << ", "
373 << "id=" << default_network_id_ << ", "
374 << "result="
375 << CaptivePortalDetector::CaptivePortalResultToString(results.result)
376 << ", "
377 << "response_code=" << results.response_code;
379 state_ = STATE_IDLE;
380 detection_timeout_.Cancel();
382 const NetworkState* default_network =
383 NetworkHandler::Get()->network_state_handler()->DefaultNetwork();
385 CaptivePortalState state;
386 state.response_code = response_code;
387 switch (result) {
388 case captive_portal::RESULT_NO_RESPONSE:
389 if (state.response_code == net::HTTP_PROXY_AUTHENTICATION_REQUIRED) {
390 state.status = CAPTIVE_PORTAL_STATUS_PROXY_AUTH_REQUIRED;
391 SetCaptivePortalState(default_network, state);
392 } else if (attempt_count_ >= kMaxRequestAttempts) {
393 if (default_network &&
394 (default_network->connection_state() == shill::kStatePortal)) {
395 // Take into account shill's detection results.
396 state.status = CAPTIVE_PORTAL_STATUS_PORTAL;
397 LOG(WARNING) << "Network name=" << default_network->name() << ", "
398 << "id=" << default_network->guid() << " "
399 << "is marked as "
400 << CaptivePortalStatusString(state.status) << " "
401 << "despite the fact that CaptivePortalDetector "
402 << "received no response";
403 } else {
404 state.status = CAPTIVE_PORTAL_STATUS_OFFLINE;
406 SetCaptivePortalState(default_network, state);
407 } else {
408 DCHECK(CanPerformDetection());
409 DetectCaptivePortal(results.retry_after_delta);
411 break;
412 case captive_portal::RESULT_INTERNET_CONNECTED:
413 state.status = CAPTIVE_PORTAL_STATUS_ONLINE;
414 SetCaptivePortalState(default_network, state);
415 break;
416 case captive_portal::RESULT_BEHIND_CAPTIVE_PORTAL:
417 state.status = CAPTIVE_PORTAL_STATUS_PORTAL;
418 SetCaptivePortalState(default_network, state);
419 break;
420 default:
421 break;
424 TryLazyDetection();
427 void NetworkPortalDetectorImpl::TryLazyDetection() {
428 if (lazy_detection_enabled() && CanPerformDetection())
429 DetectCaptivePortal(base::TimeDelta());
432 void NetworkPortalDetectorImpl::Observe(
433 int type,
434 const content::NotificationSource& source,
435 const content::NotificationDetails& details) {
436 if (type == chrome::NOTIFICATION_LOGIN_PROXY_CHANGED ||
437 type == chrome::NOTIFICATION_AUTH_SUPPLIED ||
438 type == chrome::NOTIFICATION_AUTH_CANCELLED) {
439 VLOG(1) << "Restarting portal detection due to proxy change.";
440 attempt_count_ = 0;
441 if (IsPortalCheckPending())
442 return;
443 CancelPortalDetection();
444 DCHECK(CanPerformDetection());
445 DetectCaptivePortal(base::TimeDelta::FromSeconds(kProxyChangeDelaySec));
449 bool NetworkPortalDetectorImpl::IsPortalCheckPending() const {
450 return state_ == STATE_PORTAL_CHECK_PENDING;
453 bool NetworkPortalDetectorImpl::IsCheckingForPortal() const {
454 return state_ == STATE_CHECKING_FOR_PORTAL;
457 void NetworkPortalDetectorImpl::SetCaptivePortalState(
458 const NetworkState* network,
459 const CaptivePortalState& state) {
460 if (!network) {
461 NotifyPortalDetectionCompleted(network, state);
462 return;
465 CaptivePortalStateMap::const_iterator it =
466 portal_state_map_.find(network->path());
467 if (it == portal_state_map_.end() ||
468 it->second.status != state.status ||
469 it->second.response_code != state.response_code) {
470 VLOG(1) << "Updating Chrome Captive Portal state: "
471 << "name=" << network->name() << ", "
472 << "id=" << network->guid() << ", "
473 << "status=" << CaptivePortalStatusString(state.status) << ", "
474 << "response_code=" << state.response_code;
476 // Record detection duration iff detection result differs from the
477 // previous one for this network. The reason is to record all stats
478 // only when network changes it's state.
479 RecordDetectionStats(network, state.status);
481 portal_state_map_[network->path()] = state;
483 NotifyPortalDetectionCompleted(network, state);
486 void NetworkPortalDetectorImpl::NotifyPortalDetectionCompleted(
487 const NetworkState* network,
488 const CaptivePortalState& state) {
489 FOR_EACH_OBSERVER(Observer, observers_,
490 OnPortalDetectionCompleted(network, state));
493 base::TimeTicks NetworkPortalDetectorImpl::GetCurrentTimeTicks() const {
494 if (time_ticks_for_testing_.is_null())
495 return base::TimeTicks::Now();
496 return time_ticks_for_testing_;
499 bool NetworkPortalDetectorImpl::DetectionTimeoutIsCancelledForTesting() const {
500 return detection_timeout_.IsCancelled();
503 int NetworkPortalDetectorImpl::GetRequestTimeoutSec() const {
504 DCHECK_LE(0, attempt_count_);
505 const NetworkState* network =
506 NetworkHandler::Get()->network_state_handler()->DefaultNetwork();
507 if (!network)
508 return kBaseRequestTimeoutSec;
509 if (lazy_detection_enabled_)
510 return kLazyRequestTimeoutSec;
511 return attempt_count_ * kBaseRequestTimeoutSec;
514 void NetworkPortalDetectorImpl::RecordDetectionStats(
515 const NetworkState* network,
516 CaptivePortalStatus status) {
517 // Don't record stats for offline state.
518 if (!network)
519 return;
521 if (!detection_start_time_.is_null()) {
522 UMA_HISTOGRAM_MEDIUM_TIMES(kDetectionDurationHistogram,
523 GetCurrentTimeTicks() - detection_start_time_);
525 UMA_HISTOGRAM_ENUMERATION(kDetectionResultHistogram,
526 status,
527 NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_COUNT);
528 switch (status) {
529 case NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_UNKNOWN:
530 NOTREACHED();
531 break;
532 case NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_OFFLINE:
533 if (network->connection_state() == shill::kStateOnline ||
534 network->connection_state() == shill::kStatePortal) {
535 RecordDiscrepancyWithShill(network, status);
537 break;
538 case NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_ONLINE:
539 if (network->connection_state() != shill::kStateOnline)
540 RecordDiscrepancyWithShill(network, status);
541 break;
542 case NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_PORTAL:
543 if (network->connection_state() != shill::kStatePortal)
544 RecordDiscrepancyWithShill(network, status);
545 break;
546 case NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_PROXY_AUTH_REQUIRED:
547 if (network->connection_state() != shill::kStateOnline)
548 RecordDiscrepancyWithShill(network, status);
549 break;
550 case NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_COUNT:
551 NOTREACHED();
552 break;
556 } // namespace chromeos