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