network_portal_detector_impl.cc revision f2477e01787aa58f445919b809d89e252beef54f
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 42const char kCaptivePortalStatusUnknown[] = "Unknown"; 43const char kCaptivePortalStatusOffline[] = "Offline"; 44const char kCaptivePortalStatusOnline[] = "Online"; 45const char kCaptivePortalStatusPortal[] = "Portal"; 46const char kCaptivePortalStatusProxyAuthRequired[] = 47 "Proxy authentication required"; 48const char kCaptivePortalStatusUnrecognized[] = "Unrecognized"; 49 50std::string CaptivePortalStatusString( 51 NetworkPortalDetectorImpl::CaptivePortalStatus status) { 52 switch (status) { 53 case NetworkPortalDetectorImpl::CAPTIVE_PORTAL_STATUS_UNKNOWN: 54 return kCaptivePortalStatusUnknown; 55 case NetworkPortalDetectorImpl::CAPTIVE_PORTAL_STATUS_OFFLINE: 56 return kCaptivePortalStatusOffline; 57 case NetworkPortalDetectorImpl::CAPTIVE_PORTAL_STATUS_ONLINE: 58 return kCaptivePortalStatusOnline; 59 case NetworkPortalDetectorImpl::CAPTIVE_PORTAL_STATUS_PORTAL: 60 return kCaptivePortalStatusPortal; 61 case NetworkPortalDetectorImpl::CAPTIVE_PORTAL_STATUS_PROXY_AUTH_REQUIRED: 62 return kCaptivePortalStatusProxyAuthRequired; 63 case NetworkPortalDetectorImpl::CAPTIVE_PORTAL_STATUS_COUNT: 64 NOTREACHED(); 65 } 66 return kCaptivePortalStatusUnrecognized; 67} 68 69} // namespace 70 71//////////////////////////////////////////////////////////////////////////////// 72// NetworkPortalDetectorImpl, public: 73 74NetworkPortalDetectorImpl::NetworkPortalDetectorImpl( 75 const scoped_refptr<net::URLRequestContextGetter>& request_context) 76 : state_(STATE_IDLE), 77 test_url_(CaptivePortalDetector::kDefaultURL), 78 enabled_(false), 79 weak_ptr_factory_(this), 80 attempt_count_(0), 81 lazy_detection_enabled_(false), 82 lazy_check_interval_(base::TimeDelta::FromSeconds(kLazyCheckIntervalSec)), 83 min_time_between_attempts_( 84 base::TimeDelta::FromSeconds(kMinTimeBetweenAttemptsSec)), 85 request_timeout_for_testing_initialized_(false) { 86 captive_portal_detector_.reset(new CaptivePortalDetector(request_context)); 87 88 registrar_.Add(this, 89 chrome::NOTIFICATION_LOGIN_PROXY_CHANGED, 90 content::NotificationService::AllSources()); 91 registrar_.Add(this, 92 chrome::NOTIFICATION_AUTH_SUPPLIED, 93 content::NotificationService::AllSources()); 94 registrar_.Add(this, 95 chrome::NOTIFICATION_AUTH_CANCELLED, 96 content::NotificationService::AllSources()); 97 98 NetworkHandler::Get()->network_state_handler()->AddObserver( 99 this, FROM_HERE); 100} 101 102NetworkPortalDetectorImpl::~NetworkPortalDetectorImpl() { 103 DCHECK(CalledOnValidThread()); 104 105 detection_task_.Cancel(); 106 detection_timeout_.Cancel(); 107 108 captive_portal_detector_->Cancel(); 109 captive_portal_detector_.reset(); 110 observers_.Clear(); 111 if (NetworkHandler::IsInitialized()) { 112 NetworkHandler::Get()->network_state_handler()->RemoveObserver( 113 this, FROM_HERE); 114 } 115} 116 117void NetworkPortalDetectorImpl::AddObserver(Observer* observer) { 118 DCHECK(CalledOnValidThread()); 119 if (!observer || observers_.HasObserver(observer)) 120 return; 121 observers_.AddObserver(observer); 122} 123 124void NetworkPortalDetectorImpl::AddAndFireObserver(Observer* observer) { 125 DCHECK(CalledOnValidThread()); 126 if (!observer) 127 return; 128 AddObserver(observer); 129 const NetworkState* network = 130 NetworkHandler::Get()->network_state_handler()->DefaultNetwork(); 131 observer->OnPortalDetectionCompleted(network, GetCaptivePortalState(network)); 132} 133 134void NetworkPortalDetectorImpl::RemoveObserver(Observer* observer) { 135 DCHECK(CalledOnValidThread()); 136 if (observer) 137 observers_.RemoveObserver(observer); 138} 139 140bool NetworkPortalDetectorImpl::IsEnabled() { 141 return enabled_; 142} 143 144void NetworkPortalDetectorImpl::Enable(bool start_detection) { 145 DCHECK(CalledOnValidThread()); 146 if (enabled_) 147 return; 148 enabled_ = true; 149 DCHECK(!IsPortalCheckPending()); 150 DCHECK(!IsCheckingForPortal()); 151 DCHECK(!lazy_detection_enabled()); 152 if (!start_detection) 153 return; 154 state_ = STATE_IDLE; 155 attempt_count_ = 0; 156 const NetworkState* default_network = 157 NetworkHandler::Get()->network_state_handler()->DefaultNetwork(); 158 if (!default_network) 159 return; 160 portal_state_map_.erase(default_network->path()); 161 DCHECK(CanPerformDetection()); 162 DetectCaptivePortal(base::TimeDelta()); 163} 164 165NetworkPortalDetectorImpl::CaptivePortalState 166NetworkPortalDetectorImpl::GetCaptivePortalState(const NetworkState* network) { 167 DCHECK(CalledOnValidThread()); 168 if (!network) 169 return CaptivePortalState(); 170 CaptivePortalStateMap::const_iterator it = 171 portal_state_map_.find(network->path()); 172 if (it == portal_state_map_.end()) 173 return CaptivePortalState(); 174 return it->second; 175} 176 177bool NetworkPortalDetectorImpl::StartDetectionIfIdle() { 178 if (IsPortalCheckPending() || IsCheckingForPortal()) 179 return false; 180 if (!CanPerformDetection()) 181 attempt_count_ = 0; 182 DCHECK(CanPerformDetection()); 183 DetectCaptivePortal(base::TimeDelta()); 184 return true; 185} 186 187void NetworkPortalDetectorImpl::EnableLazyDetection() { 188 if (lazy_detection_enabled()) 189 return; 190 lazy_detection_enabled_ = true; 191 VLOG(1) << "Lazy detection mode enabled."; 192 StartDetectionIfIdle(); 193} 194 195void NetworkPortalDetectorImpl::DisableLazyDetection() { 196 if (!lazy_detection_enabled()) 197 return; 198 lazy_detection_enabled_ = false; 199 if (attempt_count_ == kMaxRequestAttempts && IsPortalCheckPending()) 200 CancelPortalDetection(); 201 VLOG(1) << "Lazy detection mode disabled."; 202} 203 204void NetworkPortalDetectorImpl::DefaultNetworkChanged( 205 const NetworkState* default_network) { 206 DCHECK(CalledOnValidThread()); 207 if (!default_network) { 208 default_network_name_.clear(); 209 default_network_id_.clear(); 210 return; 211 } 212 213 default_network_name_ = default_network->name(); 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 242//////////////////////////////////////////////////////////////////////////////// 243// NetworkPortalDetectorImpl, private: 244 245bool NetworkPortalDetectorImpl::CanPerformDetection() const { 246 if (IsPortalCheckPending() || IsCheckingForPortal()) 247 return false; 248 return attempt_count_ < kMaxRequestAttempts || lazy_detection_enabled(); 249} 250 251void NetworkPortalDetectorImpl::DetectCaptivePortal( 252 const base::TimeDelta& delay) { 253 DCHECK(CanPerformDetection()); 254 255 if (!IsEnabled()) 256 return; 257 258 detection_task_.Cancel(); 259 detection_timeout_.Cancel(); 260 state_ = STATE_PORTAL_CHECK_PENDING; 261 262 next_attempt_delay_ = delay; 263 if (attempt_count_ > 0) { 264 base::TimeTicks now = GetCurrentTimeTicks(); 265 base::TimeDelta elapsed_time; 266 267 base::TimeDelta delay_between_attempts = min_time_between_attempts_; 268 if (attempt_count_ == kMaxRequestAttempts) { 269 DCHECK(lazy_detection_enabled()); 270 delay_between_attempts = lazy_check_interval_; 271 } 272 if (now > attempt_start_time_) 273 elapsed_time = now - attempt_start_time_; 274 if (elapsed_time < delay_between_attempts && 275 delay_between_attempts - elapsed_time > next_attempt_delay_) { 276 next_attempt_delay_ = delay_between_attempts - elapsed_time; 277 } 278 } else { 279 detection_start_time_ = GetCurrentTimeTicks(); 280 } 281 detection_task_.Reset( 282 base::Bind(&NetworkPortalDetectorImpl::DetectCaptivePortalTask, 283 weak_ptr_factory_.GetWeakPtr())); 284 base::MessageLoop::current()->PostDelayedTask( 285 FROM_HERE, detection_task_.callback(), next_attempt_delay_); 286} 287 288void NetworkPortalDetectorImpl::DetectCaptivePortalTask() { 289 DCHECK(IsPortalCheckPending()); 290 291 state_ = STATE_CHECKING_FOR_PORTAL; 292 attempt_start_time_ = GetCurrentTimeTicks(); 293 294 if (attempt_count_ < kMaxRequestAttempts) { 295 ++attempt_count_; 296 VLOG(1) << "Portal detection started: " 297 << "name=" << default_network_name_ << ", " 298 << "id=" << default_network_id_ << ", " 299 << "attempt=" << attempt_count_ << " of " << kMaxRequestAttempts; 300 } else { 301 DCHECK(lazy_detection_enabled()); 302 VLOG(1) << "Lazy portal detection attempt started"; 303 } 304 305 captive_portal_detector_->DetectCaptivePortal( 306 test_url_, 307 base::Bind(&NetworkPortalDetectorImpl::OnPortalDetectionCompleted, 308 weak_ptr_factory_.GetWeakPtr())); 309 detection_timeout_.Reset( 310 base::Bind(&NetworkPortalDetectorImpl::PortalDetectionTimeout, 311 weak_ptr_factory_.GetWeakPtr())); 312 base::TimeDelta request_timeout; 313 314 // For easier unit testing check for testing state is performed here 315 // and not in the GetRequestTimeoutSec(). 316 if (request_timeout_for_testing_initialized_) 317 request_timeout = request_timeout_for_testing_; 318 else 319 request_timeout = base::TimeDelta::FromSeconds(GetRequestTimeoutSec()); 320 base::MessageLoop::current()->PostDelayedTask( 321 FROM_HERE, detection_timeout_.callback(), request_timeout); 322} 323 324void NetworkPortalDetectorImpl::PortalDetectionTimeout() { 325 DCHECK(CalledOnValidThread()); 326 DCHECK(IsCheckingForPortal()); 327 328 VLOG(1) << "Portal detection timeout: name=" << default_network_name_ << ", " 329 << "id=" << default_network_id_; 330 331 captive_portal_detector_->Cancel(); 332 CaptivePortalDetector::Results results; 333 results.result = captive_portal::RESULT_NO_RESPONSE; 334 OnPortalDetectionCompleted(results); 335} 336 337void NetworkPortalDetectorImpl::CancelPortalDetection() { 338 if (IsPortalCheckPending()) 339 detection_task_.Cancel(); 340 else if (IsCheckingForPortal()) 341 captive_portal_detector_->Cancel(); 342 detection_timeout_.Cancel(); 343 state_ = STATE_IDLE; 344} 345 346void NetworkPortalDetectorImpl::OnPortalDetectionCompleted( 347 const CaptivePortalDetector::Results& results) { 348 captive_portal::Result result = results.result; 349 int response_code = results.response_code; 350 351 if (shill_stub_helper::IsStubPortalledWifiEnabled(default_service_path_)) { 352 result = captive_portal::RESULT_BEHIND_CAPTIVE_PORTAL; 353 response_code = 200; 354 } 355 356 DCHECK(CalledOnValidThread()); 357 DCHECK(IsCheckingForPortal()); 358 359 VLOG(1) << "Portal detection completed: " 360 << "name=" << default_network_name_ << ", " 361 << "id=" << default_network_id_ << ", " 362 << "result=" 363 << CaptivePortalDetector::CaptivePortalResultToString(results.result) 364 << ", " 365 << "response_code=" << results.response_code; 366 367 state_ = STATE_IDLE; 368 detection_timeout_.Cancel(); 369 370 const NetworkState* default_network = 371 NetworkHandler::Get()->network_state_handler()->DefaultNetwork(); 372 373 CaptivePortalState state; 374 state.response_code = response_code; 375 switch (result) { 376 case captive_portal::RESULT_NO_RESPONSE: 377 if (state.response_code == net::HTTP_PROXY_AUTHENTICATION_REQUIRED) { 378 state.status = CAPTIVE_PORTAL_STATUS_PROXY_AUTH_REQUIRED; 379 SetCaptivePortalState(default_network, state); 380 } else if (attempt_count_ >= kMaxRequestAttempts) { 381 if (default_network && 382 (default_network->connection_state() == shill::kStatePortal)) { 383 // Take into account shill's detection results. 384 state.status = CAPTIVE_PORTAL_STATUS_PORTAL; 385 LOG(WARNING) << "Network name=" << default_network->name() << ", " 386 << "id=" << default_network->guid() << " " 387 << "is marked as " 388 << CaptivePortalStatusString(state.status) << " " 389 << "despite the fact that CaptivePortalDetector " 390 << "received no response"; 391 } else { 392 state.status = CAPTIVE_PORTAL_STATUS_OFFLINE; 393 } 394 SetCaptivePortalState(default_network, state); 395 } else { 396 DCHECK(CanPerformDetection()); 397 DetectCaptivePortal(results.retry_after_delta); 398 } 399 break; 400 case captive_portal::RESULT_INTERNET_CONNECTED: 401 state.status = CAPTIVE_PORTAL_STATUS_ONLINE; 402 SetCaptivePortalState(default_network, state); 403 break; 404 case captive_portal::RESULT_BEHIND_CAPTIVE_PORTAL: 405 state.status = CAPTIVE_PORTAL_STATUS_PORTAL; 406 SetCaptivePortalState(default_network, state); 407 break; 408 default: 409 break; 410 } 411 412 TryLazyDetection(); 413} 414 415void NetworkPortalDetectorImpl::TryLazyDetection() { 416 if (lazy_detection_enabled() && CanPerformDetection()) 417 DetectCaptivePortal(base::TimeDelta()); 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 DCHECK(CanPerformDetection()); 433 DetectCaptivePortal(base::TimeDelta::FromSeconds(kProxyChangeDelaySec)); 434 } 435} 436 437bool NetworkPortalDetectorImpl::IsPortalCheckPending() const { 438 return state_ == STATE_PORTAL_CHECK_PENDING; 439} 440 441bool NetworkPortalDetectorImpl::IsCheckingForPortal() const { 442 return state_ == STATE_CHECKING_FOR_PORTAL; 443} 444 445void NetworkPortalDetectorImpl::SetCaptivePortalState( 446 const NetworkState* network, 447 const CaptivePortalState& state) { 448 if (!detection_start_time_.is_null()) { 449 UMA_HISTOGRAM_TIMES("CaptivePortal.OOBE.DetectionDuration", 450 GetCurrentTimeTicks() - detection_start_time_); 451 } 452 453 if (!network) { 454 NotifyPortalDetectionCompleted(network, state); 455 return; 456 } 457 458 CaptivePortalStateMap::const_iterator it = 459 portal_state_map_.find(network->path()); 460 if (it == portal_state_map_.end() || 461 it->second.status != state.status || 462 it->second.response_code != state.response_code) { 463 VLOG(1) << "Updating Chrome Captive Portal state: " 464 << "name=" << network->name() << ", " 465 << "id=" << network->guid() << ", " 466 << "status=" << CaptivePortalStatusString(state.status) << ", " 467 << "response_code=" << state.response_code; 468 portal_state_map_[network->path()] = state; 469 } 470 NotifyPortalDetectionCompleted(network, state); 471} 472 473void NetworkPortalDetectorImpl::NotifyPortalDetectionCompleted( 474 const NetworkState* network, 475 const CaptivePortalState& state) { 476 FOR_EACH_OBSERVER(Observer, observers_, 477 OnPortalDetectionCompleted(network, state)); 478} 479 480base::TimeTicks NetworkPortalDetectorImpl::GetCurrentTimeTicks() const { 481 if (time_ticks_for_testing_.is_null()) 482 return base::TimeTicks::Now(); 483 return time_ticks_for_testing_; 484} 485 486bool NetworkPortalDetectorImpl::DetectionTimeoutIsCancelledForTesting() const { 487 return detection_timeout_.IsCancelled(); 488} 489 490int NetworkPortalDetectorImpl::GetRequestTimeoutSec() const { 491 DCHECK_LE(0, attempt_count_); 492 const NetworkState* network = 493 NetworkHandler::Get()->network_state_handler()->DefaultNetwork(); 494 if (!network) 495 return kBaseRequestTimeoutSec; 496 if (lazy_detection_enabled_) 497 return kLazyRequestTimeoutSec; 498 return attempt_count_ * kBaseRequestTimeoutSec; 499} 500 501} // namespace chromeos 502