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