network_portal_detector_impl.cc revision effb81e5f8246d0db0270817048dc992db66e9fb
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/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 26using captive_portal::CaptivePortalDetector; 27 28namespace chromeos { 29 30namespace { 31 32// Delay before portal detection caused by changes in proxy settings. 33const int kProxyChangeDelaySec = 1; 34 35const NetworkState* DefaultNetwork() { 36 return NetworkHandler::Get()->network_state_handler()->DefaultNetwork(); 37} 38 39bool InSession() { 40 return UserManager::IsInitialized() && UserManager::Get()->IsUserLoggedIn(); 41} 42 43void 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 57void 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 68void 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} // namespace 109 110//////////////////////////////////////////////////////////////////////////////// 111// NetworkPortalDetectorImpl, public: 112 113const char NetworkPortalDetectorImpl::kOobeDetectionResultHistogram[] = 114 "CaptivePortal.OOBE.DetectionResult"; 115const char NetworkPortalDetectorImpl::kOobeDetectionDurationHistogram[] = 116 "CaptivePortal.OOBE.DetectionDuration"; 117const char NetworkPortalDetectorImpl::kOobeShillOnlineHistogram[] = 118 "CaptivePortal.OOBE.DiscrepancyWithShill_Online"; 119const char NetworkPortalDetectorImpl::kOobeShillPortalHistogram[] = 120 "CaptivePortal.OOBE.DiscrepancyWithShill_RestrictedPool"; 121const char NetworkPortalDetectorImpl::kOobeShillOfflineHistogram[] = 122 "CaptivePortal.OOBE.DiscrepancyWithShill_Offline"; 123 124const char NetworkPortalDetectorImpl::kSessionDetectionResultHistogram[] = 125 "CaptivePortal.Session.DetectionResult"; 126const char NetworkPortalDetectorImpl::kSessionDetectionDurationHistogram[] = 127 "CaptivePortal.Session.DetectionDuration"; 128const char NetworkPortalDetectorImpl::kSessionShillOnlineHistogram[] = 129 "CaptivePortal.Session.DiscrepancyWithShill_Online"; 130const char NetworkPortalDetectorImpl::kSessionShillPortalHistogram[] = 131 "CaptivePortal.Session.DiscrepancyWithShill_RestrictedPool"; 132const char NetworkPortalDetectorImpl::kSessionShillOfflineHistogram[] = 133 "CaptivePortal.Session.DiscrepancyWithShill_Offline"; 134 135NetworkPortalDetectorImpl::NetworkPortalDetectorImpl( 136 const scoped_refptr<net::URLRequestContextGetter>& request_context) 137 : state_(STATE_IDLE), 138 test_url_(CaptivePortalDetector::kDefaultURL), 139 enabled_(false), 140 weak_factory_(this), 141 attempt_count_(0), 142 strategy_(PortalDetectorStrategy::CreateById( 143 PortalDetectorStrategy::STRATEGY_ID_LOGIN_SCREEN)), 144 error_screen_displayed_(false) { 145 captive_portal_detector_.reset(new CaptivePortalDetector(request_context)); 146 strategy_->set_delegate(this); 147 148 registrar_.Add(this, 149 chrome::NOTIFICATION_LOGIN_PROXY_CHANGED, 150 content::NotificationService::AllSources()); 151 registrar_.Add(this, 152 chrome::NOTIFICATION_AUTH_SUPPLIED, 153 content::NotificationService::AllSources()); 154 registrar_.Add(this, 155 chrome::NOTIFICATION_AUTH_CANCELLED, 156 content::NotificationService::AllSources()); 157 registrar_.Add(this, 158 chrome::NOTIFICATION_LOGIN_USER_CHANGED, 159 content::NotificationService::AllSources()); 160 161 NetworkHandler::Get()->network_state_handler()->AddObserver(this, FROM_HERE); 162 UpdateCurrentStrategy(); 163} 164 165NetworkPortalDetectorImpl::~NetworkPortalDetectorImpl() { 166 DCHECK(CalledOnValidThread()); 167 168 attempt_task_.Cancel(); 169 attempt_timeout_.Cancel(); 170 171 captive_portal_detector_->Cancel(); 172 captive_portal_detector_.reset(); 173 observers_.Clear(); 174 if (NetworkHandler::IsInitialized()) { 175 NetworkHandler::Get()->network_state_handler()->RemoveObserver(this, 176 FROM_HERE); 177 } 178} 179 180void NetworkPortalDetectorImpl::AddObserver(Observer* observer) { 181 DCHECK(CalledOnValidThread()); 182 if (observer && !observers_.HasObserver(observer)) 183 observers_.AddObserver(observer); 184} 185 186void NetworkPortalDetectorImpl::AddAndFireObserver(Observer* observer) { 187 DCHECK(CalledOnValidThread()); 188 if (!observer) 189 return; 190 AddObserver(observer); 191 CaptivePortalState portal_state; 192 const NetworkState* network = DefaultNetwork(); 193 if (network) 194 portal_state = GetCaptivePortalState(network->path()); 195 observer->OnPortalDetectionCompleted(network, portal_state); 196} 197 198void NetworkPortalDetectorImpl::RemoveObserver(Observer* observer) { 199 DCHECK(CalledOnValidThread()); 200 if (observer) 201 observers_.RemoveObserver(observer); 202} 203 204bool NetworkPortalDetectorImpl::IsEnabled() { return enabled_; } 205 206void NetworkPortalDetectorImpl::Enable(bool start_detection) { 207 DCHECK(CalledOnValidThread()); 208 if (enabled_) 209 return; 210 211 DCHECK(is_idle()); 212 enabled_ = true; 213 214 const NetworkState* network = DefaultNetwork(); 215 if (!start_detection || !network) 216 return; 217 portal_state_map_.erase(network->path()); 218 StartDetection(); 219} 220 221NetworkPortalDetectorImpl::CaptivePortalState 222NetworkPortalDetectorImpl::GetCaptivePortalState( 223 const std::string& service_path) { 224 DCHECK(CalledOnValidThread()); 225 CaptivePortalStateMap::const_iterator it = 226 portal_state_map_.find(service_path); 227 if (it == portal_state_map_.end()) 228 return CaptivePortalState(); 229 return it->second; 230} 231 232bool NetworkPortalDetectorImpl::StartDetectionIfIdle() { 233 if (!is_idle()) 234 return false; 235 StartDetection(); 236 return true; 237} 238 239void NetworkPortalDetectorImpl::DefaultNetworkChanged( 240 const NetworkState* default_network) { 241 DCHECK(CalledOnValidThread()); 242 243 if (!default_network) { 244 default_network_name_.clear(); 245 default_network_id_.clear(); 246 247 StopDetection(); 248 249 CaptivePortalState state; 250 state.status = CAPTIVE_PORTAL_STATUS_OFFLINE; 251 OnDetectionCompleted(NULL, state); 252 return; 253 } 254 255 default_network_name_ = default_network->name(); 256 default_network_id_ = default_network->guid(); 257 258 bool network_changed = (default_service_path_ != default_network->path()); 259 default_service_path_ = default_network->path(); 260 261 bool connection_state_changed = 262 (default_connection_state_ != default_network->connection_state()); 263 default_connection_state_ = default_network->connection_state(); 264 265 if (network_changed || connection_state_changed) 266 StopDetection(); 267 268 if (CanPerformAttempt() && 269 NetworkState::StateIsConnected(default_connection_state_)) { 270 // Initiate Captive Portal detection if network's captive 271 // portal state is unknown (e.g. for freshly created networks), 272 // offline or if network connection state was changed. 273 CaptivePortalState state = GetCaptivePortalState(default_network->path()); 274 if (state.status == CAPTIVE_PORTAL_STATUS_UNKNOWN || 275 state.status == CAPTIVE_PORTAL_STATUS_OFFLINE || 276 (!network_changed && connection_state_changed)) { 277 ScheduleAttempt(base::TimeDelta()); 278 } 279 } 280} 281 282int NetworkPortalDetectorImpl::AttemptCount() { return attempt_count_; } 283 284base::TimeTicks NetworkPortalDetectorImpl::AttemptStartTime() { 285 return attempt_start_time_; 286} 287 288base::TimeTicks NetworkPortalDetectorImpl::GetCurrentTimeTicks() { 289 if (time_ticks_for_testing_.is_null()) 290 return base::TimeTicks::Now(); 291 return time_ticks_for_testing_; 292} 293 294void NetworkPortalDetectorImpl::OnErrorScreenShow() { 295 error_screen_displayed_ = true; 296 UpdateCurrentStrategy(); 297} 298 299void NetworkPortalDetectorImpl::OnErrorScreenHide() { 300 error_screen_displayed_ = false; 301 UpdateCurrentStrategy(); 302} 303 304//////////////////////////////////////////////////////////////////////////////// 305// NetworkPortalDetectorImpl, private: 306 307void NetworkPortalDetectorImpl::StartDetection() { 308 attempt_count_ = 0; 309 DCHECK(CanPerformAttempt()); 310 detection_start_time_ = GetCurrentTimeTicks(); 311 ScheduleAttempt(base::TimeDelta()); 312} 313 314void NetworkPortalDetectorImpl::StopDetection() { 315 attempt_task_.Cancel(); 316 attempt_timeout_.Cancel(); 317 captive_portal_detector_->Cancel(); 318 state_ = STATE_IDLE; 319 attempt_count_ = 0; 320} 321 322bool NetworkPortalDetectorImpl::CanPerformAttempt() const { 323 return is_idle() && strategy_->CanPerformAttempt(); 324} 325 326void NetworkPortalDetectorImpl::ScheduleAttempt(const base::TimeDelta& delay) { 327 DCHECK(CanPerformAttempt()); 328 329 if (!IsEnabled()) 330 return; 331 332 attempt_task_.Cancel(); 333 attempt_timeout_.Cancel(); 334 state_ = STATE_PORTAL_CHECK_PENDING; 335 336 next_attempt_delay_ = std::max(delay, strategy_->GetDelayTillNextAttempt()); 337 attempt_task_.Reset(base::Bind(&NetworkPortalDetectorImpl::StartAttempt, 338 weak_factory_.GetWeakPtr())); 339 base::MessageLoop::current()->PostDelayedTask( 340 FROM_HERE, attempt_task_.callback(), next_attempt_delay_); 341} 342 343void NetworkPortalDetectorImpl::StartAttempt() { 344 DCHECK(is_portal_check_pending()); 345 346 state_ = STATE_CHECKING_FOR_PORTAL; 347 attempt_start_time_ = GetCurrentTimeTicks(); 348 349 captive_portal_detector_->DetectCaptivePortal( 350 test_url_, 351 base::Bind(&NetworkPortalDetectorImpl::OnAttemptCompleted, 352 weak_factory_.GetWeakPtr())); 353 attempt_timeout_.Reset( 354 base::Bind(&NetworkPortalDetectorImpl::OnAttemptTimeout, 355 weak_factory_.GetWeakPtr())); 356 357 base::MessageLoop::current()->PostDelayedTask( 358 FROM_HERE, 359 attempt_timeout_.callback(), 360 strategy_->GetNextAttemptTimeout()); 361} 362 363void NetworkPortalDetectorImpl::OnAttemptTimeout() { 364 DCHECK(CalledOnValidThread()); 365 DCHECK(is_checking_for_portal()); 366 367 VLOG(1) << "Portal detection timeout: name=" << default_network_name_ << ", " 368 << "id=" << default_network_id_; 369 370 captive_portal_detector_->Cancel(); 371 CaptivePortalDetector::Results results; 372 results.result = captive_portal::RESULT_NO_RESPONSE; 373 OnAttemptCompleted(results); 374} 375 376void NetworkPortalDetectorImpl::OnAttemptCompleted( 377 const CaptivePortalDetector::Results& results) { 378 captive_portal::Result result = results.result; 379 int response_code = results.response_code; 380 381 DCHECK(CalledOnValidThread()); 382 DCHECK(is_checking_for_portal()); 383 384 VLOG(1) << "Detection attempt completed: " 385 << "name=" << default_network_name_ << ", " 386 << "id=" << default_network_id_ << ", " 387 << "result=" 388 << CaptivePortalDetector::CaptivePortalResultToString(results.result) 389 << ", " 390 << "response_code=" << results.response_code; 391 392 state_ = STATE_IDLE; 393 attempt_timeout_.Cancel(); 394 ++attempt_count_; 395 396 const NetworkState* network = DefaultNetwork(); 397 398 // If using a fake profile client, also fake being behind a captive portal 399 // if the default network is in portal state. 400 if (result != captive_portal::RESULT_NO_RESPONSE && 401 DBusThreadManager::Get()->GetShillProfileClient()->GetTestInterface() && 402 network && network->connection_state() == shill::kStatePortal) { 403 result = captive_portal::RESULT_BEHIND_CAPTIVE_PORTAL; 404 response_code = 200; 405 } 406 407 CaptivePortalState state; 408 state.response_code = response_code; 409 switch (result) { 410 case captive_portal::RESULT_NO_RESPONSE: 411 if (state.response_code == net::HTTP_PROXY_AUTHENTICATION_REQUIRED) { 412 state.status = CAPTIVE_PORTAL_STATUS_PROXY_AUTH_REQUIRED; 413 } else if (CanPerformAttempt()) { 414 ScheduleAttempt(results.retry_after_delta); 415 return; 416 } else if (network && 417 (network->connection_state() == shill::kStatePortal)) { 418 // Take into account shill's detection results. 419 state.status = CAPTIVE_PORTAL_STATUS_PORTAL; 420 LOG(WARNING) << "Network name=" << network->name() << ", " 421 << "id=" << network->guid() << " " 422 << "is marked as " 423 << CaptivePortalStatusString(state.status) << " " 424 << "despite the fact that CaptivePortalDetector " 425 << "received no response"; 426 } else { 427 state.status = CAPTIVE_PORTAL_STATUS_OFFLINE; 428 } 429 break; 430 case captive_portal::RESULT_INTERNET_CONNECTED: 431 state.status = CAPTIVE_PORTAL_STATUS_ONLINE; 432 break; 433 case captive_portal::RESULT_BEHIND_CAPTIVE_PORTAL: 434 state.status = CAPTIVE_PORTAL_STATUS_PORTAL; 435 break; 436 default: 437 break; 438 } 439 440 OnDetectionCompleted(network, state); 441 if (CanPerformAttempt() && strategy_->CanPerformAttemptAfterDetection()) 442 ScheduleAttempt(base::TimeDelta()); 443} 444 445void NetworkPortalDetectorImpl::Observe( 446 int type, 447 const content::NotificationSource& source, 448 const content::NotificationDetails& details) { 449 if (type == chrome::NOTIFICATION_LOGIN_PROXY_CHANGED || 450 type == chrome::NOTIFICATION_AUTH_SUPPLIED || 451 type == chrome::NOTIFICATION_AUTH_CANCELLED) { 452 VLOG(1) << "Restarting portal detection due to proxy change."; 453 attempt_count_ = 0; 454 if (is_portal_check_pending()) 455 return; 456 StopDetection(); 457 ScheduleAttempt(base::TimeDelta::FromSeconds(kProxyChangeDelaySec)); 458 } else if (type == chrome::NOTIFICATION_LOGIN_USER_CHANGED) { 459 UpdateCurrentStrategy(); 460 } 461} 462 463void NetworkPortalDetectorImpl::OnDetectionCompleted( 464 const NetworkState* network, 465 const CaptivePortalState& state) { 466 if (!network) { 467 NotifyDetectionCompleted(network, state); 468 return; 469 } 470 471 CaptivePortalStateMap::const_iterator it = 472 portal_state_map_.find(network->path()); 473 if (it == portal_state_map_.end() || it->second.status != state.status || 474 it->second.response_code != state.response_code) { 475 VLOG(1) << "Updating Chrome Captive Portal state: " 476 << "name=" << network->name() << ", " 477 << "id=" << network->guid() << ", " 478 << "status=" << CaptivePortalStatusString(state.status) << ", " 479 << "response_code=" << state.response_code; 480 481 // Record detection duration iff detection result differs from the 482 // previous one for this network. The reason is to record all stats 483 // only when network changes it's state. 484 RecordDetectionStats(network, state.status); 485 486 portal_state_map_[network->path()] = state; 487 } 488 NotifyDetectionCompleted(network, state); 489} 490 491void NetworkPortalDetectorImpl::NotifyDetectionCompleted( 492 const NetworkState* network, 493 const CaptivePortalState& state) { 494 FOR_EACH_OBSERVER( 495 Observer, observers_, OnPortalDetectionCompleted(network, state)); 496 notification_controller_.OnPortalDetectionCompleted(network, state); 497} 498 499bool NetworkPortalDetectorImpl::AttemptTimeoutIsCancelledForTesting() const { 500 return attempt_timeout_.IsCancelled(); 501} 502 503void NetworkPortalDetectorImpl::RecordDetectionStats( 504 const NetworkState* network, 505 CaptivePortalStatus status) { 506 // Don't record stats for offline state. 507 if (!network) 508 return; 509 510 if (!detection_start_time_.is_null()) 511 RecordDetectionDuration(GetCurrentTimeTicks() - detection_start_time_); 512 RecordDetectionResult(status); 513 514 switch (status) { 515 case NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_UNKNOWN: 516 NOTREACHED(); 517 break; 518 case NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_OFFLINE: 519 if (network->connection_state() == shill::kStateOnline || 520 network->connection_state() == shill::kStatePortal) { 521 RecordDiscrepancyWithShill(network, status); 522 } 523 break; 524 case NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_ONLINE: 525 if (network->connection_state() != shill::kStateOnline) 526 RecordDiscrepancyWithShill(network, status); 527 break; 528 case NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_PORTAL: 529 if (network->connection_state() != shill::kStatePortal) 530 RecordDiscrepancyWithShill(network, status); 531 break; 532 case NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_PROXY_AUTH_REQUIRED: 533 if (network->connection_state() != shill::kStateOnline) 534 RecordDiscrepancyWithShill(network, status); 535 break; 536 case NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_COUNT: 537 NOTREACHED(); 538 break; 539 } 540} 541 542void NetworkPortalDetectorImpl::UpdateCurrentStrategy() { 543 if (InSession()) { 544 SetStrategy(PortalDetectorStrategy::STRATEGY_ID_SESSION); 545 return; 546 } 547 if (error_screen_displayed_) { 548 SetStrategy(PortalDetectorStrategy::STRATEGY_ID_ERROR_SCREEN); 549 return; 550 } 551 SetStrategy(PortalDetectorStrategy::STRATEGY_ID_LOGIN_SCREEN); 552} 553 554void NetworkPortalDetectorImpl::SetStrategy( 555 PortalDetectorStrategy::StrategyId id) { 556 if (id == strategy_->Id()) 557 return; 558 strategy_.reset(PortalDetectorStrategy::CreateById(id).release()); 559 strategy_->set_delegate(this); 560 StopDetection(); 561 StartDetectionIfIdle(); 562} 563 564} // namespace chromeos 565