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