Home | History | Annotate | Download | only in net
      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