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/users/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
26 using captive_portal::CaptivePortalDetector;
27
28 namespace chromeos {
29
30 namespace {
31
32 // Delay before portal detection caused by changes in proxy settings.
33 const int kProxyChangeDelaySec = 1;
34
DefaultNetwork()35 const NetworkState* DefaultNetwork() {
36 return NetworkHandler::Get()->network_state_handler()->DefaultNetwork();
37 }
38
InSession()39 bool InSession() {
40 return UserManager::IsInitialized() && UserManager::Get()->IsUserLoggedIn();
41 }
42
RecordDetectionResult(NetworkPortalDetector::CaptivePortalStatus status)43 void 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
RecordDetectionDuration(const base::TimeDelta & duration)57 void 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
RecordDiscrepancyWithShill(const NetworkState * network,const NetworkPortalDetector::CaptivePortalStatus status)68 void 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
RecordPortalToOnlineTransition(const base::TimeDelta & duration)108 void 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
125 const char NetworkPortalDetectorImpl::kOobeDetectionResultHistogram[] =
126 "CaptivePortal.OOBE.DetectionResult";
127 const char NetworkPortalDetectorImpl::kOobeDetectionDurationHistogram[] =
128 "CaptivePortal.OOBE.DetectionDuration";
129 const char NetworkPortalDetectorImpl::kOobeShillOnlineHistogram[] =
130 "CaptivePortal.OOBE.DiscrepancyWithShill_Online";
131 const char NetworkPortalDetectorImpl::kOobeShillPortalHistogram[] =
132 "CaptivePortal.OOBE.DiscrepancyWithShill_RestrictedPool";
133 const char NetworkPortalDetectorImpl::kOobeShillOfflineHistogram[] =
134 "CaptivePortal.OOBE.DiscrepancyWithShill_Offline";
135 const char NetworkPortalDetectorImpl::kOobePortalToOnlineHistogram[] =
136 "CaptivePortal.OOBE.PortalToOnlineTransition";
137
138 const char NetworkPortalDetectorImpl::kSessionDetectionResultHistogram[] =
139 "CaptivePortal.Session.DetectionResult";
140 const char NetworkPortalDetectorImpl::kSessionDetectionDurationHistogram[] =
141 "CaptivePortal.Session.DetectionDuration";
142 const char NetworkPortalDetectorImpl::kSessionShillOnlineHistogram[] =
143 "CaptivePortal.Session.DiscrepancyWithShill_Online";
144 const char NetworkPortalDetectorImpl::kSessionShillPortalHistogram[] =
145 "CaptivePortal.Session.DiscrepancyWithShill_RestrictedPool";
146 const char NetworkPortalDetectorImpl::kSessionShillOfflineHistogram[] =
147 "CaptivePortal.Session.DiscrepancyWithShill_Offline";
148 const char NetworkPortalDetectorImpl::kSessionPortalToOnlineHistogram[] =
149 "CaptivePortal.Session.PortalToOnlineTransition";
150
NetworkPortalDetectorImpl(const scoped_refptr<net::URLRequestContextGetter> & request_context)151 NetworkPortalDetectorImpl::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
~NetworkPortalDetectorImpl()177 NetworkPortalDetectorImpl::~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
AddObserver(Observer * observer)192 void NetworkPortalDetectorImpl::AddObserver(Observer* observer) {
193 DCHECK(CalledOnValidThread());
194 if (observer && !observers_.HasObserver(observer))
195 observers_.AddObserver(observer);
196 }
197
AddAndFireObserver(Observer * observer)198 void 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
RemoveObserver(Observer * observer)210 void NetworkPortalDetectorImpl::RemoveObserver(Observer* observer) {
211 DCHECK(CalledOnValidThread());
212 if (observer)
213 observers_.RemoveObserver(observer);
214 }
215
IsEnabled()216 bool NetworkPortalDetectorImpl::IsEnabled() { return enabled_; }
217
Enable(bool start_detection)218 void 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
233 NetworkPortalDetectorImpl::CaptivePortalState
GetCaptivePortalState(const std::string & service_path)234 NetworkPortalDetectorImpl::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
StartDetectionIfIdle()244 bool NetworkPortalDetectorImpl::StartDetectionIfIdle() {
245 if (!is_idle())
246 return false;
247 StartDetection();
248 return true;
249 }
250
SetStrategy(PortalDetectorStrategy::StrategyId id)251 void 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
DefaultNetworkChanged(const NetworkState * default_network)261 void 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
AttemptCount()304 int NetworkPortalDetectorImpl::AttemptCount() { return attempt_count_; }
305
AttemptStartTime()306 base::TimeTicks NetworkPortalDetectorImpl::AttemptStartTime() {
307 return attempt_start_time_;
308 }
309
GetCurrentTimeTicks()310 base::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
StartDetection()320 void NetworkPortalDetectorImpl::StartDetection() {
321 attempt_count_ = 0;
322 DCHECK(CanPerformAttempt());
323 detection_start_time_ = GetCurrentTimeTicks();
324 ScheduleAttempt(base::TimeDelta());
325 }
326
StopDetection()327 void 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
CanPerformAttempt() const335 bool NetworkPortalDetectorImpl::CanPerformAttempt() const {
336 return is_idle() && strategy_->CanPerformAttempt();
337 }
338
ScheduleAttempt(const base::TimeDelta & delay)339 void 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
StartAttempt()356 void 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
OnAttemptTimeout()376 void 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
OnAttemptCompleted(const CaptivePortalDetector::Results & results)389 void 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
Observe(int type,const content::NotificationSource & source,const content::NotificationDetails & details)459 void 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
OnDetectionCompleted(const NetworkState * network,const CaptivePortalState & state)475 void 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
NotifyDetectionCompleted(const NetworkState * network,const CaptivePortalState & state)508 void 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
AttemptTimeoutIsCancelledForTesting() const516 bool NetworkPortalDetectorImpl::AttemptTimeoutIsCancelledForTesting() const {
517 return attempt_timeout_.IsCancelled();
518 }
519
RecordDetectionStats(const NetworkState * network,CaptivePortalStatus status)520 void 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