network_portal_detector_impl.cc revision c2e0dbddbe15c98d52c4786dac06cb8952a8ae6d
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 "base/bind.h" 8#include "base/command_line.h" 9#include "base/logging.h" 10#include "base/message_loop.h" 11#include "base/metrics/histogram.h" 12#include "chrome/browser/chromeos/cros/cros_library.h" 13#include "chrome/common/chrome_notification_types.h" 14#include "chrome/common/chrome_switches.h" 15#include "content/public/browser/notification_service.h" 16#include "grit/generated_resources.h" 17#include "net/http/http_status_code.h" 18#include "ui/base/l10n/l10n_util.h" 19 20using captive_portal::CaptivePortalDetector; 21 22namespace chromeos { 23 24namespace { 25 26// Maximum number of portal detections for the same active network 27// after network change. 28const int kMaxRequestAttempts = 3; 29 30// Minimum timeout between consecutive portal checks for the same 31// network. 32const int kMinTimeBetweenAttemptsSec = 3; 33 34// Timeout for a portal check. 35const int kRequestTimeoutSec = 5; 36 37// Delay before portal detection caused by changes in proxy settings. 38const int kProxyChangeDelaySec = 1; 39 40// Delay between consecutive portal checks for a network in lazy mode. 41const int kLazyCheckIntervalSec = 5; 42 43std::string CaptivePortalStatusString( 44 NetworkPortalDetectorImpl::CaptivePortalStatus status) { 45 switch (status) { 46 case NetworkPortalDetectorImpl::CAPTIVE_PORTAL_STATUS_UNKNOWN: 47 return l10n_util::GetStringUTF8( 48 IDS_CHROMEOS_CAPTIVE_PORTAL_STATUS_UNKNOWN); 49 case NetworkPortalDetectorImpl::CAPTIVE_PORTAL_STATUS_OFFLINE: 50 return l10n_util::GetStringUTF8( 51 IDS_CHROMEOS_CAPTIVE_PORTAL_STATUS_OFFLINE); 52 case NetworkPortalDetectorImpl::CAPTIVE_PORTAL_STATUS_ONLINE: 53 return l10n_util::GetStringUTF8( 54 IDS_CHROMEOS_CAPTIVE_PORTAL_STATUS_ONLINE); 55 case NetworkPortalDetectorImpl::CAPTIVE_PORTAL_STATUS_PORTAL: 56 return l10n_util::GetStringUTF8( 57 IDS_CHROMEOS_CAPTIVE_PORTAL_STATUS_PORTAL); 58 case NetworkPortalDetectorImpl::CAPTIVE_PORTAL_STATUS_PROXY_AUTH_REQUIRED: 59 return l10n_util::GetStringUTF8( 60 IDS_CHROMEOS_CAPTIVE_PORTAL_STATUS_PROXY_AUTH_REQUIRED); 61 case NetworkPortalDetectorImpl::CAPTIVE_PORTAL_STATUS_COUNT: 62 NOTREACHED(); 63 } 64 return l10n_util::GetStringUTF8( 65 IDS_CHROMEOS_CAPTIVE_PORTAL_STATUS_UNRECOGNIZED); 66} 67 68NetworkLibrary* GetNetworkLibrary() { 69 CHECK(CrosLibrary::Get()); 70 return CrosLibrary::Get()->GetNetworkLibrary(); 71} 72 73const Network* GetActiveNetwork() { 74 NetworkLibrary* cros = GetNetworkLibrary(); 75 if (!cros) 76 return NULL; 77 return cros->active_network(); 78} 79 80const Network* FindNetworkByPath(const std::string& service_path) { 81 NetworkLibrary* cros = GetNetworkLibrary(); 82 if (!cros) 83 return NULL; 84 return cros->FindNetworkByPath(service_path); 85} 86 87} // namespace 88 89NetworkPortalDetectorImpl::NetworkPortalDetectorImpl( 90 const scoped_refptr<net::URLRequestContextGetter>& request_context) 91 : active_connection_state_(STATE_UNKNOWN), 92 test_url_(CaptivePortalDetector::kDefaultURL), 93 enabled_(false), 94 weak_ptr_factory_(this), 95 attempt_count_(0), 96 lazy_detection_enabled_(false), 97 lazy_check_interval_(base::TimeDelta::FromSeconds(kLazyCheckIntervalSec)), 98 min_time_between_attempts_( 99 base::TimeDelta::FromSeconds(kMinTimeBetweenAttemptsSec)), 100 request_timeout_(base::TimeDelta::FromSeconds(kRequestTimeoutSec)) { 101 captive_portal_detector_.reset(new CaptivePortalDetector(request_context)); 102 103 registrar_.Add(this, 104 chrome::NOTIFICATION_LOGIN_PROXY_CHANGED, 105 content::NotificationService::AllSources()); 106 registrar_.Add(this, 107 chrome::NOTIFICATION_AUTH_SUPPLIED, 108 content::NotificationService::AllSources()); 109 registrar_.Add(this, 110 chrome::NOTIFICATION_AUTH_CANCELLED, 111 content::NotificationService::AllSources()); 112} 113 114NetworkPortalDetectorImpl::~NetworkPortalDetectorImpl() { 115} 116 117void NetworkPortalDetectorImpl::Init() { 118 DCHECK(CalledOnValidThread()); 119 120 state_ = STATE_IDLE; 121 chromeos::NetworkLibrary* network_library = GetNetworkLibrary(); 122 DCHECK(network_library); 123 network_library->AddNetworkManagerObserver(this); 124 network_library->RemoveObserverForAllNetworks(this); 125} 126 127void NetworkPortalDetectorImpl::Shutdown() { 128 DCHECK(CalledOnValidThread()); 129 130 detection_task_.Cancel(); 131 detection_timeout_.Cancel(); 132 133 captive_portal_detector_->Cancel(); 134 captive_portal_detector_.reset(); 135 observers_.Clear(); 136 chromeos::NetworkLibrary* network_library = GetNetworkLibrary(); 137 if (network_library) 138 network_library->RemoveNetworkManagerObserver(this); 139} 140 141void NetworkPortalDetectorImpl::AddObserver(Observer* observer) { 142 DCHECK(CalledOnValidThread()); 143 if (!observer || observers_.HasObserver(observer)) 144 return; 145 observers_.AddObserver(observer); 146} 147 148void NetworkPortalDetectorImpl::AddAndFireObserver(Observer* observer) { 149 DCHECK(CalledOnValidThread()); 150 if (!observer) 151 return; 152 AddObserver(observer); 153 const Network* network = GetActiveNetwork(); 154 observer->OnPortalDetectionCompleted(network, GetCaptivePortalState(network)); 155} 156 157void NetworkPortalDetectorImpl::RemoveObserver(Observer* observer) { 158 DCHECK(CalledOnValidThread()); 159 if (observer) 160 observers_.RemoveObserver(observer); 161} 162 163bool NetworkPortalDetectorImpl::IsEnabled() { 164 return enabled_; 165} 166 167void NetworkPortalDetectorImpl::Enable(bool start_detection) { 168 DCHECK(CalledOnValidThread()); 169 if (enabled_) 170 return; 171 enabled_ = true; 172 DCHECK(!IsPortalCheckPending()); 173 DCHECK(!IsCheckingForPortal()); 174 DCHECK(!lazy_detection_enabled_); 175 if (!start_detection) 176 return; 177 state_ = STATE_IDLE; 178 attempt_count_ = 0; 179 const Network* active_network = GetActiveNetwork(); 180 if (!active_network) 181 return; 182 portal_state_map_.erase(active_network->service_path()); 183 DetectCaptivePortal(base::TimeDelta()); 184} 185 186NetworkPortalDetectorImpl::CaptivePortalState 187NetworkPortalDetectorImpl::GetCaptivePortalState(const Network* network) { 188 DCHECK(CalledOnValidThread()); 189 if (!network) 190 return CaptivePortalState(); 191 CaptivePortalStateMap::const_iterator it = 192 portal_state_map_.find(network->service_path()); 193 if (it == portal_state_map_.end()) 194 return CaptivePortalState(); 195 return it->second; 196} 197 198void NetworkPortalDetectorImpl::EnableLazyDetection() { 199 if (lazy_detection_enabled_) 200 return; 201 VLOG(1) << "Lazy detection mode enabled."; 202 lazy_detection_enabled_ = true; 203 if (!IsPortalCheckPending() && !IsCheckingForPortal()) 204 DetectCaptivePortal(base::TimeDelta()); 205} 206 207void NetworkPortalDetectorImpl::DisableLazyDetection() { 208 if (!lazy_detection_enabled_) 209 return; 210 VLOG(1) << "Lazy detection mode disabled."; 211 lazy_detection_enabled_ = false; 212} 213 214void NetworkPortalDetectorImpl::OnNetworkManagerChanged(NetworkLibrary* cros) { 215 DCHECK(CalledOnValidThread()); 216 CHECK(cros); 217 const Network* active_network = cros->active_network(); 218 if (!active_network) 219 return; 220 221 active_network_id_ = active_network->unique_id(); 222 223 bool network_changed = 224 (active_service_path_ != active_network->service_path()); 225 if (network_changed) { 226 if (!active_service_path_.empty()) 227 cros->RemoveNetworkObserver(active_service_path_, this); 228 active_service_path_ = active_network->service_path(); 229 cros->AddNetworkObserver(active_service_path_, this); 230 } 231 232 bool connection_state_changed = 233 (active_connection_state_ != active_network->connection_state()); 234 active_connection_state_ = active_network->connection_state(); 235 236 if (network_changed || connection_state_changed) { 237 attempt_count_ = 0; 238 CancelPortalDetection(); 239 } 240 241 if (!IsCheckingForPortal() && !IsPortalCheckPending() && 242 Network::IsConnectedState(active_connection_state_) && 243 (attempt_count_ < kMaxRequestAttempts || lazy_detection_enabled_)) { 244 DCHECK(active_network); 245 246 // Initiate Captive Portal detection if network's captive 247 // portal state is unknown (e.g. for freshly created networks), 248 // offline or if network connection state was changed. 249 CaptivePortalState state = GetCaptivePortalState(active_network); 250 if (state.status == CAPTIVE_PORTAL_STATUS_UNKNOWN || 251 state.status == CAPTIVE_PORTAL_STATUS_OFFLINE || 252 (!network_changed && connection_state_changed)) { 253 DetectCaptivePortal(base::TimeDelta()); 254 } 255 } 256} 257 258void NetworkPortalDetectorImpl::OnNetworkChanged( 259 chromeos::NetworkLibrary* cros, 260 const chromeos::Network* network) { 261 DCHECK(CalledOnValidThread()); 262 OnNetworkManagerChanged(cros); 263} 264 265void NetworkPortalDetectorImpl::DetectCaptivePortal( 266 const base::TimeDelta& delay) { 267 DCHECK(!IsPortalCheckPending()); 268 DCHECK(!IsCheckingForPortal()); 269 DCHECK(attempt_count_ < kMaxRequestAttempts || lazy_detection_enabled_); 270 271 if (!IsEnabled()) 272 return; 273 274 detection_task_.Cancel(); 275 detection_timeout_.Cancel(); 276 state_ = STATE_PORTAL_CHECK_PENDING; 277 278 next_attempt_delay_ = delay; 279 if (attempt_count_ > 0) { 280 base::TimeTicks now = GetCurrentTimeTicks(); 281 base::TimeDelta elapsed_time; 282 283 base::TimeDelta delay_between_attempts = min_time_between_attempts_; 284 if (attempt_count_ == kMaxRequestAttempts) { 285 DCHECK(lazy_detection_enabled_); 286 delay_between_attempts = lazy_check_interval_; 287 } 288 if (now > attempt_start_time_) 289 elapsed_time = now - attempt_start_time_; 290 if (elapsed_time < delay_between_attempts && 291 delay_between_attempts - elapsed_time > next_attempt_delay_) { 292 next_attempt_delay_ = delay_between_attempts - elapsed_time; 293 } 294 } else { 295 detection_start_time_ = GetCurrentTimeTicks(); 296 } 297 detection_task_.Reset( 298 base::Bind(&NetworkPortalDetectorImpl::DetectCaptivePortalTask, 299 weak_ptr_factory_.GetWeakPtr())); 300 MessageLoop::current()->PostDelayedTask(FROM_HERE, 301 detection_task_.callback(), 302 next_attempt_delay_); 303} 304 305void NetworkPortalDetectorImpl::DetectCaptivePortalTask() { 306 DCHECK(IsPortalCheckPending()); 307 308 state_ = STATE_CHECKING_FOR_PORTAL; 309 attempt_start_time_ = GetCurrentTimeTicks(); 310 311 if (attempt_count_ < kMaxRequestAttempts) { 312 ++attempt_count_; 313 VLOG(1) << "Portal detection started: " 314 << "network=" << active_network_id_ << ", " 315 << "attempt=" << attempt_count_ << " of " << kMaxRequestAttempts; 316 } else { 317 DCHECK(lazy_detection_enabled_); 318 VLOG(1) << "Lazy portal detection attempt started"; 319 } 320 321 captive_portal_detector_->DetectCaptivePortal( 322 test_url_, 323 base::Bind(&NetworkPortalDetectorImpl::OnPortalDetectionCompleted, 324 weak_ptr_factory_.GetWeakPtr())); 325 detection_timeout_.Reset( 326 base::Bind(&NetworkPortalDetectorImpl::PortalDetectionTimeout, 327 weak_ptr_factory_.GetWeakPtr())); 328 MessageLoop::current()->PostDelayedTask(FROM_HERE, 329 detection_timeout_.callback(), 330 request_timeout_); 331} 332 333void NetworkPortalDetectorImpl::PortalDetectionTimeout() { 334 DCHECK(CalledOnValidThread()); 335 DCHECK(IsCheckingForPortal()); 336 337 VLOG(1) << "Portal detection timeout: network=" << active_network_id_; 338 339 captive_portal_detector_->Cancel(); 340 CaptivePortalDetector::Results results; 341 results.result = captive_portal::RESULT_NO_RESPONSE; 342 OnPortalDetectionCompleted(results); 343} 344 345void NetworkPortalDetectorImpl::CancelPortalDetection() { 346 if (IsPortalCheckPending()) 347 detection_task_.Cancel(); 348 else if (IsCheckingForPortal()) 349 captive_portal_detector_->Cancel(); 350 detection_timeout_.Cancel(); 351 state_ = STATE_IDLE; 352} 353 354void NetworkPortalDetectorImpl::OnPortalDetectionCompleted( 355 const CaptivePortalDetector::Results& results) { 356 DCHECK(CalledOnValidThread()); 357 DCHECK(IsCheckingForPortal()); 358 359 VLOG(1) << "Portal detection completed: " 360 << "network=" << active_network_id_ << ", " 361 << "result=" << CaptivePortalDetector::CaptivePortalResultToString( 362 results.result) << ", " 363 << "response_code=" << results.response_code; 364 365 state_ = STATE_IDLE; 366 detection_timeout_.Cancel(); 367 368 NetworkLibrary* cros = GetNetworkLibrary(); 369 const Network* active_network = cros->active_network(); 370 if (!active_network) { 371 TryLazyDetection(); 372 return; 373 } 374 375 CaptivePortalState state; 376 state.response_code = results.response_code; 377 switch (results.result) { 378 case captive_portal::RESULT_NO_RESPONSE: 379 if (attempt_count_ >= kMaxRequestAttempts) { 380 if (state.response_code == net::HTTP_PROXY_AUTHENTICATION_REQUIRED) { 381 state.status = CAPTIVE_PORTAL_STATUS_PROXY_AUTH_REQUIRED; 382 } else if (active_network->restricted_pool()) { 383 // Take into account shill's detection results. 384 state.status = CAPTIVE_PORTAL_STATUS_PORTAL; 385 LOG(WARNING) << "Network " << active_network->unique_id() << " " 386 << "is marked as " 387 << CaptivePortalStatusString(state.status) << " " 388 << "despite the fact that CaptivePortalDetector " 389 << "received no response"; 390 } else { 391 state.status = CAPTIVE_PORTAL_STATUS_OFFLINE; 392 } 393 SetCaptivePortalState(active_network, state); 394 } else { 395 DetectCaptivePortal(results.retry_after_delta); 396 } 397 break; 398 case captive_portal::RESULT_INTERNET_CONNECTED: 399 state.status = CAPTIVE_PORTAL_STATUS_ONLINE; 400 SetCaptivePortalState(active_network, state); 401 break; 402 case captive_portal::RESULT_BEHIND_CAPTIVE_PORTAL: 403 state.status = CAPTIVE_PORTAL_STATUS_PORTAL; 404 SetCaptivePortalState(active_network, state); 405 break; 406 default: 407 break; 408 } 409 410 TryLazyDetection(); 411} 412 413void NetworkPortalDetectorImpl::TryLazyDetection() { 414 if (!IsPortalCheckPending() && !IsCheckingForPortal() && 415 lazy_detection_enabled_) { 416 DetectCaptivePortal(base::TimeDelta()); 417 } 418} 419 420void NetworkPortalDetectorImpl::Observe( 421 int type, 422 const content::NotificationSource& source, 423 const content::NotificationDetails& details) { 424 if (type == chrome::NOTIFICATION_LOGIN_PROXY_CHANGED || 425 type == chrome::NOTIFICATION_AUTH_SUPPLIED || 426 type == chrome::NOTIFICATION_AUTH_CANCELLED) { 427 VLOG(1) << "Restarting portal detection due to proxy change."; 428 attempt_count_ = 0; 429 if (IsPortalCheckPending()) 430 return; 431 CancelPortalDetection(); 432 DetectCaptivePortal(base::TimeDelta::FromSeconds(kProxyChangeDelaySec)); 433 } 434} 435 436bool NetworkPortalDetectorImpl::IsPortalCheckPending() const { 437 return state_ == STATE_PORTAL_CHECK_PENDING; 438} 439 440bool NetworkPortalDetectorImpl::IsCheckingForPortal() const { 441 return state_ == STATE_CHECKING_FOR_PORTAL; 442} 443 444void NetworkPortalDetectorImpl::SetCaptivePortalState( 445 const Network* network, 446 const CaptivePortalState& state) { 447 DCHECK(network); 448 449 if (!detection_start_time_.is_null()) { 450 UMA_HISTOGRAM_TIMES("CaptivePortal.OOBE.DetectionDuration", 451 GetCurrentTimeTicks() - detection_start_time_); 452 } 453 454 CaptivePortalStateMap::const_iterator it = 455 portal_state_map_.find(network->service_path()); 456 if (it == portal_state_map_.end() || 457 it->second.status != state.status || 458 it->second.response_code != state.response_code) { 459 VLOG(1) << "Updating Chrome Captive Portal state: " 460 << "network=" << network->unique_id() << ", " 461 << "status=" << CaptivePortalStatusString(state.status) << ", " 462 << "response_code=" << state.response_code; 463 portal_state_map_[network->service_path()] = state; 464 } 465 NotifyPortalDetectionCompleted(network, state); 466} 467 468void NetworkPortalDetectorImpl::NotifyPortalDetectionCompleted( 469 const Network* network, 470 const CaptivePortalState& state) { 471 FOR_EACH_OBSERVER(Observer, observers_, 472 OnPortalDetectionCompleted(network, state)); 473} 474 475base::TimeTicks NetworkPortalDetectorImpl::GetCurrentTimeTicks() const { 476 if (time_ticks_for_testing_.is_null()) 477 return base::TimeTicks::Now(); 478 else 479 return time_ticks_for_testing_; 480} 481 482bool NetworkPortalDetectorImpl::DetectionTimeoutIsCancelledForTesting() const { 483 return detection_timeout_.IsCancelled(); 484} 485 486} // namespace chromeos 487