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_service_client_stub.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 22 using captive_portal::CaptivePortalDetector; 23 24 namespace chromeos { 25 26 namespace { 27 28 // Maximum number of portal detections for the same default network 29 // after network change. 30 const int kMaxRequestAttempts = 3; 31 32 // Minimum timeout between consecutive portal checks for the same 33 // network. 34 const int kMinTimeBetweenAttemptsSec = 3; 35 36 // Delay before portal detection caused by changes in proxy settings. 37 const int kProxyChangeDelaySec = 1; 38 39 // Delay between consecutive portal checks for a network in lazy mode. 40 const int kLazyCheckIntervalSec = 5; 41 42 std::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 72 NetworkPortalDetectorImpl::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 96 NetworkPortalDetectorImpl::~NetworkPortalDetectorImpl() { 97 } 98 99 void NetworkPortalDetectorImpl::Init() { 100 DCHECK(CalledOnValidThread()); 101 102 state_ = STATE_IDLE; 103 NetworkHandler::Get()->network_state_handler()->AddObserver( 104 this, FROM_HERE); 105 } 106 107 void 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 122 void NetworkPortalDetectorImpl::AddObserver(Observer* observer) { 123 DCHECK(CalledOnValidThread()); 124 if (!observer || observers_.HasObserver(observer)) 125 return; 126 observers_.AddObserver(observer); 127 } 128 129 void 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 139 void NetworkPortalDetectorImpl::RemoveObserver(Observer* observer) { 140 DCHECK(CalledOnValidThread()); 141 if (observer) 142 observers_.RemoveObserver(observer); 143 } 144 145 bool NetworkPortalDetectorImpl::IsEnabled() { 146 return enabled_; 147 } 148 149 void 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 170 NetworkPortalDetectorImpl::CaptivePortalState 171 NetworkPortalDetectorImpl::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 182 bool 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 192 void 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 200 void NetworkPortalDetectorImpl::DisableLazyDetection() { 201 if (!lazy_detection_enabled()) 202 return; 203 lazy_detection_enabled_ = false; 204 if (attempt_count_ == kMaxRequestAttempts && IsPortalCheckPending()) 205 CancelPortalDetection(); 206 VLOG(1) << "Lazy detection mode disabled."; 207 } 208 209 void NetworkPortalDetectorImpl::NetworkManagerChanged() { 210 DCHECK(CalledOnValidThread()); 211 const NetworkState* default_network = 212 NetworkHandler::Get()->network_state_handler()->DefaultNetwork(); 213 if (!default_network) { 214 default_network_id_.clear(); 215 return; 216 } 217 218 default_network_id_ = default_network->guid(); 219 220 bool network_changed = (default_service_path_ != default_network->path()); 221 default_service_path_ = default_network->path(); 222 223 bool connection_state_changed = (default_connection_state_ != 224 default_network->connection_state()); 225 default_connection_state_ = default_network->connection_state(); 226 227 if (network_changed || connection_state_changed) { 228 attempt_count_ = 0; 229 CancelPortalDetection(); 230 } 231 232 if (CanPerformDetection() && 233 NetworkState::StateIsConnected(default_connection_state_)) { 234 // Initiate Captive Portal detection if network's captive 235 // portal state is unknown (e.g. for freshly created networks), 236 // offline or if network connection state was changed. 237 CaptivePortalState state = GetCaptivePortalState(default_network); 238 if (state.status == CAPTIVE_PORTAL_STATUS_UNKNOWN || 239 state.status == CAPTIVE_PORTAL_STATUS_OFFLINE || 240 (!network_changed && connection_state_changed)) { 241 DetectCaptivePortal(base::TimeDelta()); 242 } 243 } 244 } 245 246 void NetworkPortalDetectorImpl::DefaultNetworkChanged( 247 const NetworkState* network) { 248 NetworkManagerChanged(); 249 } 250 251 //////////////////////////////////////////////////////////////////////////////// 252 // NetworkPortalDetectorImpl, private: 253 254 bool NetworkPortalDetectorImpl::CanPerformDetection() const { 255 if (IsPortalCheckPending() || IsCheckingForPortal()) 256 return false; 257 return attempt_count_ < kMaxRequestAttempts || lazy_detection_enabled(); 258 } 259 260 void NetworkPortalDetectorImpl::DetectCaptivePortal( 261 const base::TimeDelta& delay) { 262 DCHECK(CanPerformDetection()); 263 264 if (!IsEnabled()) 265 return; 266 267 detection_task_.Cancel(); 268 detection_timeout_.Cancel(); 269 state_ = STATE_PORTAL_CHECK_PENDING; 270 271 next_attempt_delay_ = delay; 272 if (attempt_count_ > 0) { 273 base::TimeTicks now = GetCurrentTimeTicks(); 274 base::TimeDelta elapsed_time; 275 276 base::TimeDelta delay_between_attempts = min_time_between_attempts_; 277 if (attempt_count_ == kMaxRequestAttempts) { 278 DCHECK(lazy_detection_enabled()); 279 delay_between_attempts = lazy_check_interval_; 280 } 281 if (now > attempt_start_time_) 282 elapsed_time = now - attempt_start_time_; 283 if (elapsed_time < delay_between_attempts && 284 delay_between_attempts - elapsed_time > next_attempt_delay_) { 285 next_attempt_delay_ = delay_between_attempts - elapsed_time; 286 } 287 } else { 288 detection_start_time_ = GetCurrentTimeTicks(); 289 } 290 detection_task_.Reset( 291 base::Bind(&NetworkPortalDetectorImpl::DetectCaptivePortalTask, 292 weak_ptr_factory_.GetWeakPtr())); 293 base::MessageLoop::current()->PostDelayedTask( 294 FROM_HERE, detection_task_.callback(), next_attempt_delay_); 295 } 296 297 void NetworkPortalDetectorImpl::DetectCaptivePortalTask() { 298 DCHECK(IsPortalCheckPending()); 299 300 state_ = STATE_CHECKING_FOR_PORTAL; 301 attempt_start_time_ = GetCurrentTimeTicks(); 302 303 if (attempt_count_ < kMaxRequestAttempts) { 304 ++attempt_count_; 305 VLOG(1) << "Portal detection started: " 306 << "network=" << default_network_id_ << ", " 307 << "attempt=" << attempt_count_ << " of " << kMaxRequestAttempts; 308 } else { 309 DCHECK(lazy_detection_enabled()); 310 VLOG(1) << "Lazy portal detection attempt started"; 311 } 312 313 captive_portal_detector_->DetectCaptivePortal( 314 test_url_, 315 base::Bind(&NetworkPortalDetectorImpl::OnPortalDetectionCompleted, 316 weak_ptr_factory_.GetWeakPtr())); 317 detection_timeout_.Reset( 318 base::Bind(&NetworkPortalDetectorImpl::PortalDetectionTimeout, 319 weak_ptr_factory_.GetWeakPtr())); 320 base::TimeDelta request_timeout; 321 322 // For easier unit testing check for testing state is performed here 323 // and not in the GetRequestTimeoutSec(). 324 if (request_timeout_for_testing_initialized_) 325 request_timeout = request_timeout_for_testing_; 326 else 327 request_timeout = base::TimeDelta::FromSeconds(GetRequestTimeoutSec()); 328 base::MessageLoop::current()->PostDelayedTask( 329 FROM_HERE, detection_timeout_.callback(), request_timeout); 330 } 331 332 void NetworkPortalDetectorImpl::PortalDetectionTimeout() { 333 DCHECK(CalledOnValidThread()); 334 DCHECK(IsCheckingForPortal()); 335 336 VLOG(1) << "Portal detection timeout: network=" << default_network_id_; 337 338 captive_portal_detector_->Cancel(); 339 CaptivePortalDetector::Results results; 340 results.result = captive_portal::RESULT_NO_RESPONSE; 341 OnPortalDetectionCompleted(results); 342 } 343 344 void NetworkPortalDetectorImpl::CancelPortalDetection() { 345 if (IsPortalCheckPending()) 346 detection_task_.Cancel(); 347 else if (IsCheckingForPortal()) 348 captive_portal_detector_->Cancel(); 349 detection_timeout_.Cancel(); 350 state_ = STATE_IDLE; 351 } 352 353 void NetworkPortalDetectorImpl::OnPortalDetectionCompleted( 354 const CaptivePortalDetector::Results& results) { 355 captive_portal::Result result = results.result; 356 int response_code = results.response_code; 357 358 if (ShillServiceClientStub::IsStubPortalledWifiEnabled( 359 default_service_path_)) { 360 result = captive_portal::RESULT_BEHIND_CAPTIVE_PORTAL; 361 response_code = 200; 362 } 363 364 DCHECK(CalledOnValidThread()); 365 DCHECK(IsCheckingForPortal()); 366 367 VLOG(1) << "Portal detection completed: " 368 << "network=" << default_network_id_ << ", " 369 << "result=" << CaptivePortalDetector::CaptivePortalResultToString( 370 results.result) << ", " 371 << "response_code=" << results.response_code; 372 373 state_ = STATE_IDLE; 374 detection_timeout_.Cancel(); 375 376 const NetworkState* default_network = 377 NetworkHandler::Get()->network_state_handler()->DefaultNetwork(); 378 379 CaptivePortalState state; 380 state.response_code = response_code; 381 switch (result) { 382 case captive_portal::RESULT_NO_RESPONSE: 383 if (attempt_count_ >= kMaxRequestAttempts) { 384 if (state.response_code == net::HTTP_PROXY_AUTHENTICATION_REQUIRED) { 385 state.status = CAPTIVE_PORTAL_STATUS_PROXY_AUTH_REQUIRED; 386 } else if (default_network && (default_network->connection_state() == 387 flimflam::kStatePortal)) { 388 // Take into account shill's detection results. 389 state.status = CAPTIVE_PORTAL_STATUS_PORTAL; 390 LOG(WARNING) << "Network " << default_network->guid() << " " 391 << "is marked as " 392 << CaptivePortalStatusString(state.status) << " " 393 << "despite the fact that CaptivePortalDetector " 394 << "received no response"; 395 } else { 396 state.status = CAPTIVE_PORTAL_STATUS_OFFLINE; 397 } 398 SetCaptivePortalState(default_network, state); 399 } else { 400 DCHECK(CanPerformDetection()); 401 DetectCaptivePortal(results.retry_after_delta); 402 } 403 break; 404 case captive_portal::RESULT_INTERNET_CONNECTED: 405 state.status = CAPTIVE_PORTAL_STATUS_ONLINE; 406 SetCaptivePortalState(default_network, state); 407 break; 408 case captive_portal::RESULT_BEHIND_CAPTIVE_PORTAL: 409 state.status = CAPTIVE_PORTAL_STATUS_PORTAL; 410 SetCaptivePortalState(default_network, state); 411 break; 412 default: 413 break; 414 } 415 416 TryLazyDetection(); 417 } 418 419 void NetworkPortalDetectorImpl::TryLazyDetection() { 420 if (lazy_detection_enabled() && CanPerformDetection()) 421 DetectCaptivePortal(base::TimeDelta()); 422 } 423 424 void NetworkPortalDetectorImpl::Observe( 425 int type, 426 const content::NotificationSource& source, 427 const content::NotificationDetails& details) { 428 if (type == chrome::NOTIFICATION_LOGIN_PROXY_CHANGED || 429 type == chrome::NOTIFICATION_AUTH_SUPPLIED || 430 type == chrome::NOTIFICATION_AUTH_CANCELLED) { 431 VLOG(1) << "Restarting portal detection due to proxy change."; 432 attempt_count_ = 0; 433 if (IsPortalCheckPending()) 434 return; 435 CancelPortalDetection(); 436 DCHECK(CanPerformDetection()); 437 DetectCaptivePortal(base::TimeDelta::FromSeconds(kProxyChangeDelaySec)); 438 } 439 } 440 441 bool NetworkPortalDetectorImpl::IsPortalCheckPending() const { 442 return state_ == STATE_PORTAL_CHECK_PENDING; 443 } 444 445 bool NetworkPortalDetectorImpl::IsCheckingForPortal() const { 446 return state_ == STATE_CHECKING_FOR_PORTAL; 447 } 448 449 void NetworkPortalDetectorImpl::SetCaptivePortalState( 450 const NetworkState* network, 451 const CaptivePortalState& state) { 452 if (!detection_start_time_.is_null()) { 453 UMA_HISTOGRAM_TIMES("CaptivePortal.OOBE.DetectionDuration", 454 GetCurrentTimeTicks() - detection_start_time_); 455 } 456 457 if (!network) { 458 NotifyPortalDetectionCompleted(network, state); 459 return; 460 } 461 462 CaptivePortalStateMap::const_iterator it = 463 portal_state_map_.find(network->path()); 464 if (it == portal_state_map_.end() || 465 it->second.status != state.status || 466 it->second.response_code != state.response_code) { 467 VLOG(1) << "Updating Chrome Captive Portal state: " 468 << "network=" << network->guid() << ", " 469 << "status=" << CaptivePortalStatusString(state.status) << ", " 470 << "response_code=" << state.response_code; 471 portal_state_map_[network->path()] = state; 472 } 473 NotifyPortalDetectionCompleted(network, state); 474 } 475 476 void NetworkPortalDetectorImpl::NotifyPortalDetectionCompleted( 477 const NetworkState* network, 478 const CaptivePortalState& state) { 479 FOR_EACH_OBSERVER(Observer, observers_, 480 OnPortalDetectionCompleted(network, state)); 481 } 482 483 base::TimeTicks NetworkPortalDetectorImpl::GetCurrentTimeTicks() const { 484 if (time_ticks_for_testing_.is_null()) 485 return base::TimeTicks::Now(); 486 return time_ticks_for_testing_; 487 } 488 489 bool NetworkPortalDetectorImpl::DetectionTimeoutIsCancelledForTesting() const { 490 return detection_timeout_.IsCancelled(); 491 } 492 493 int NetworkPortalDetectorImpl::GetRequestTimeoutSec() const { 494 DCHECK_LE(0, attempt_count_); 495 const NetworkState* network = 496 NetworkHandler::Get()->network_state_handler()->DefaultNetwork(); 497 if (!network) 498 return kBaseRequestTimeoutSec; 499 if (lazy_detection_enabled_) 500 return kLazyRequestTimeoutSec; 501 return attempt_count_ * kBaseRequestTimeoutSec; 502 } 503 504 } // namespace chromeos 505