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