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 35 const NetworkState* DefaultNetwork() { 36 return NetworkHandler::Get()->network_state_handler()->DefaultNetwork(); 37 } 38 39 bool InSession() { 40 return UserManager::IsInitialized() && UserManager::Get()->IsUserLoggedIn(); 41 } 42 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 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 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 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 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 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 192 void NetworkPortalDetectorImpl::AddObserver(Observer* observer) { 193 DCHECK(CalledOnValidThread()); 194 if (observer && !observers_.HasObserver(observer)) 195 observers_.AddObserver(observer); 196 } 197 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 210 void NetworkPortalDetectorImpl::RemoveObserver(Observer* observer) { 211 DCHECK(CalledOnValidThread()); 212 if (observer) 213 observers_.RemoveObserver(observer); 214 } 215 216 bool NetworkPortalDetectorImpl::IsEnabled() { return enabled_; } 217 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 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 244 bool NetworkPortalDetectorImpl::StartDetectionIfIdle() { 245 if (!is_idle()) 246 return false; 247 StartDetection(); 248 return true; 249 } 250 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 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 304 int NetworkPortalDetectorImpl::AttemptCount() { return attempt_count_; } 305 306 base::TimeTicks NetworkPortalDetectorImpl::AttemptStartTime() { 307 return attempt_start_time_; 308 } 309 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 320 void NetworkPortalDetectorImpl::StartDetection() { 321 attempt_count_ = 0; 322 DCHECK(CanPerformAttempt()); 323 detection_start_time_ = GetCurrentTimeTicks(); 324 ScheduleAttempt(base::TimeDelta()); 325 } 326 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 335 bool NetworkPortalDetectorImpl::CanPerformAttempt() const { 336 return is_idle() && strategy_->CanPerformAttempt(); 337 } 338 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 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 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 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 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 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 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 516 bool NetworkPortalDetectorImpl::AttemptTimeoutIsCancelledForTesting() const { 517 return attempt_timeout_.IsCancelled(); 518 } 519 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