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"
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
;
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
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
,
49 NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_COUNT
);
50 } else if (network
->connection_state() == shill::kStatePortal
) {
51 UMA_HISTOGRAM_ENUMERATION(
52 NetworkPortalDetectorImpl::kShillPortalHistogram
,
54 NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_COUNT
);
55 } else if (network
->connection_state() == shill::kStateOffline
) {
56 UMA_HISTOGRAM_ENUMERATION(
57 NetworkPortalDetectorImpl::kShillOfflineHistogram
,
59 NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_COUNT
);
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
)
82 test_url_(CaptivePortalDetector::kDefaultURL
),
84 weak_ptr_factory_(this),
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
));
94 chrome::NOTIFICATION_LOGIN_PROXY_CHANGED
,
95 content::NotificationService::AllSources());
97 chrome::NOTIFICATION_AUTH_SUPPLIED
,
98 content::NotificationService::AllSources());
100 chrome::NOTIFICATION_AUTH_CANCELLED
,
101 content::NotificationService::AllSources());
103 NetworkHandler::Get()->network_state_handler()->AddObserver(
107 NetworkPortalDetectorImpl::~NetworkPortalDetectorImpl() {
108 DCHECK(CalledOnValidThread());
110 detection_task_
.Cancel();
111 detection_timeout_
.Cancel();
113 captive_portal_detector_
->Cancel();
114 captive_portal_detector_
.reset();
116 if (NetworkHandler::IsInitialized()) {
117 NetworkHandler::Get()->network_state_handler()->RemoveObserver(
122 void NetworkPortalDetectorImpl::AddObserver(Observer
* observer
) {
123 DCHECK(CalledOnValidThread());
124 if (!observer
|| observers_
.HasObserver(observer
))
126 observers_
.AddObserver(observer
);
129 void NetworkPortalDetectorImpl::AddAndFireObserver(Observer
* observer
) {
130 DCHECK(CalledOnValidThread());
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());
142 observers_
.RemoveObserver(observer
);
145 bool NetworkPortalDetectorImpl::IsEnabled() {
149 void NetworkPortalDetectorImpl::Enable(bool start_detection
) {
150 DCHECK(CalledOnValidThread());
154 DCHECK(!IsPortalCheckPending());
155 DCHECK(!IsCheckingForPortal());
156 DCHECK(!lazy_detection_enabled());
157 if (!start_detection
)
161 const NetworkState
* default_network
=
162 NetworkHandler::Get()->network_state_handler()->DefaultNetwork();
163 if (!default_network
)
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());
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();
182 bool NetworkPortalDetectorImpl::StartDetectionIfIdle() {
183 if (IsPortalCheckPending() || IsCheckingForPortal())
185 if (!CanPerformDetection())
187 DCHECK(CanPerformDetection());
188 DetectCaptivePortal(base::TimeDelta());
192 void NetworkPortalDetectorImpl::EnableLazyDetection() {
193 if (lazy_detection_enabled())
195 lazy_detection_enabled_
= true;
196 VLOG(1) << "Lazy detection mode enabled.";
197 StartDetectionIfIdle();
200 void NetworkPortalDetectorImpl::DisableLazyDetection() {
201 if (!lazy_detection_enabled())
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
);
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
) {
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())
260 return attempt_count_
< kMaxRequestAttempts
|| lazy_detection_enabled();
263 void NetworkPortalDetectorImpl::DetectCaptivePortal(
264 const base::TimeDelta
& delay
) {
265 DCHECK(CanPerformDetection());
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
;
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
) {
308 VLOG(1) << "Portal detection started: "
309 << "name=" << default_network_name_
<< ", "
310 << "id=" << default_network_id_
<< ", "
311 << "attempt=" << attempt_count_
<< " of " << kMaxRequestAttempts
;
313 DCHECK(lazy_detection_enabled());
314 VLOG(1) << "Lazy portal detection attempt started";
317 captive_portal_detector_
->DetectCaptivePortal(
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_
;
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();
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
;
368 DCHECK(CalledOnValidThread());
369 DCHECK(IsCheckingForPortal());
371 VLOG(1) << "Portal detection completed: "
372 << "name=" << default_network_name_
<< ", "
373 << "id=" << default_network_id_
<< ", "
375 << CaptivePortalDetector::CaptivePortalResultToString(results
.result
)
377 << "response_code=" << results
.response_code
;
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
;
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() << " "
400 << CaptivePortalStatusString(state
.status
) << " "
401 << "despite the fact that CaptivePortalDetector "
402 << "received no response";
404 state
.status
= CAPTIVE_PORTAL_STATUS_OFFLINE
;
406 SetCaptivePortalState(default_network
, state
);
408 DCHECK(CanPerformDetection());
409 DetectCaptivePortal(results
.retry_after_delta
);
412 case captive_portal::RESULT_INTERNET_CONNECTED
:
413 state
.status
= CAPTIVE_PORTAL_STATUS_ONLINE
;
414 SetCaptivePortalState(default_network
, state
);
416 case captive_portal::RESULT_BEHIND_CAPTIVE_PORTAL
:
417 state
.status
= CAPTIVE_PORTAL_STATUS_PORTAL
;
418 SetCaptivePortalState(default_network
, state
);
427 void NetworkPortalDetectorImpl::TryLazyDetection() {
428 if (lazy_detection_enabled() && CanPerformDetection())
429 DetectCaptivePortal(base::TimeDelta());
432 void NetworkPortalDetectorImpl::Observe(
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.";
441 if (IsPortalCheckPending())
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
) {
461 NotifyPortalDetectionCompleted(network
, state
);
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();
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.
521 if (!detection_start_time_
.is_null()) {
522 UMA_HISTOGRAM_MEDIUM_TIMES(kDetectionDurationHistogram
,
523 GetCurrentTimeTicks() - detection_start_time_
);
525 UMA_HISTOGRAM_ENUMERATION(kDetectionResultHistogram
,
527 NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_COUNT
);
529 case NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_UNKNOWN
:
532 case NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_OFFLINE
:
533 if (network
->connection_state() == shill::kStateOnline
||
534 network
->connection_state() == shill::kStatePortal
) {
535 RecordDiscrepancyWithShill(network
, status
);
538 case NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_ONLINE
:
539 if (network
->connection_state() != shill::kStateOnline
)
540 RecordDiscrepancyWithShill(network
, status
);
542 case NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_PORTAL
:
543 if (network
->connection_state() != shill::kStatePortal
)
544 RecordDiscrepancyWithShill(network
, status
);
546 case NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_PROXY_AUTH_REQUIRED
:
547 if (network
->connection_state() != shill::kStateOnline
)
548 RecordDiscrepancyWithShill(network
, status
);
550 case NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_COUNT
:
556 } // namespace chromeos