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