Home | History | Annotate | Download | only in shill
      1 //
      2 // Copyright (C) 2012 The Android Open Source Project
      3 //
      4 // Licensed under the Apache License, Version 2.0 (the "License");
      5 // you may not use this file except in compliance with the License.
      6 // You may obtain a copy of the License at
      7 //
      8 //      http://www.apache.org/licenses/LICENSE-2.0
      9 //
     10 // Unless required by applicable law or agreed to in writing, software
     11 // distributed under the License is distributed on an "AS IS" BASIS,
     12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13 // See the License for the specific language governing permissions and
     14 // limitations under the License.
     15 //
     16 
     17 #include "shill/portal_detector.h"
     18 
     19 #include <string>
     20 
     21 #include <base/bind.h>
     22 #include <base/strings/string_number_conversions.h>
     23 #include <base/strings/string_util.h>
     24 #include <base/strings/stringprintf.h>
     25 #if defined(__ANDROID__)
     26 #include <dbus/service_constants.h>
     27 #else
     28 #include <chromeos/dbus/service_constants.h>
     29 #endif  // __ANDROID__
     30 
     31 #include "shill/connection.h"
     32 #include "shill/connectivity_trial.h"
     33 #include "shill/logging.h"
     34 
     35 using base::Bind;
     36 using base::Callback;
     37 using base::StringPrintf;
     38 using std::string;
     39 
     40 namespace shill {
     41 
     42 namespace Logging {
     43 static auto kModuleLogScope = ScopeLogger::kPortal;
     44 static string ObjectID(Connection* c) { return c->interface_name(); }
     45 }
     46 
     47 const int PortalDetector::kDefaultCheckIntervalSeconds = 30;
     48 const char PortalDetector::kDefaultCheckPortalList[] = "ethernet,wifi,cellular";
     49 
     50 const int PortalDetector::kMaxRequestAttempts = 3;
     51 const int PortalDetector::kMinTimeBetweenAttemptsSeconds = 3;
     52 const int PortalDetector::kRequestTimeoutSeconds = 10;
     53 const int PortalDetector::kMaxFailuresInContentPhase = 2;
     54 
     55 PortalDetector::PortalDetector(
     56     ConnectionRefPtr connection,
     57     EventDispatcher* dispatcher,
     58     const Callback<void(const PortalDetector::Result&)>& callback)
     59     : attempt_count_(0),
     60       attempt_start_time_((struct timeval){0}),
     61       connection_(connection),
     62       dispatcher_(dispatcher),
     63       weak_ptr_factory_(this),
     64       portal_result_callback_(callback),
     65       connectivity_trial_callback_(Bind(&PortalDetector::CompleteAttempt,
     66                                         weak_ptr_factory_.GetWeakPtr())),
     67       time_(Time::GetInstance()),
     68       failures_in_content_phase_(0),
     69       connectivity_trial_(
     70           new ConnectivityTrial(connection_,
     71                                 dispatcher_,
     72                                 kRequestTimeoutSeconds,
     73                                 connectivity_trial_callback_)) { }
     74 
     75 PortalDetector::~PortalDetector() {
     76   Stop();
     77 }
     78 
     79 bool PortalDetector::Start(const string& url_string) {
     80   return StartAfterDelay(url_string, 0);
     81 }
     82 
     83 bool PortalDetector::StartAfterDelay(const string& url_string,
     84                                      int delay_seconds) {
     85   SLOG(connection_.get(), 3) << "In " << __func__;
     86 
     87   if (!connectivity_trial_->Start(url_string, delay_seconds * 1000)) {
     88     return false;
     89   }
     90   attempt_count_ = 1;
     91   // The attempt_start_time_ is calculated based on the current time and
     92   // |delay_seconds|.  This is used to determine if a portal detection attempt
     93   // is in progress.
     94   UpdateAttemptTime(delay_seconds);
     95   // If we're starting a new set of attempts, discard past failure history.
     96   failures_in_content_phase_ = 0;
     97   return true;
     98 }
     99 
    100 void PortalDetector::Stop() {
    101   SLOG(connection_.get(), 3) << "In " << __func__;
    102 
    103   attempt_count_ = 0;
    104   failures_in_content_phase_ = 0;
    105   if (connectivity_trial_.get())
    106     connectivity_trial_->Stop();
    107 }
    108 
    109 // IsInProgress returns true if a ConnectivityTrial is actively testing the
    110 // connection.  If Start has been called, but the trial was delayed,
    111 // IsInProgress will return false.  PortalDetector implements this by
    112 // calculating the start time of the next ConnectivityTrial.  After an initial
    113 // trial and in the case where multiple attempts may be tried, IsInProgress will
    114 // return true.
    115 bool PortalDetector::IsInProgress() {
    116   if (attempt_count_ > 1)
    117     return true;
    118   if (attempt_count_ == 1 && connectivity_trial_.get())
    119     return connectivity_trial_->IsActive();
    120   return false;
    121 }
    122 
    123 void PortalDetector::CompleteAttempt(ConnectivityTrial::Result trial_result) {
    124   Result result = Result(trial_result);
    125   if (trial_result.status == ConnectivityTrial::kStatusFailure &&
    126       trial_result.phase == ConnectivityTrial::kPhaseContent) {
    127     failures_in_content_phase_++;
    128   }
    129 
    130   LOG(INFO) << StringPrintf("Portal detection completed attempt %d with "
    131                             "phase==%s, status==%s, failures in content==%d",
    132                             attempt_count_,
    133                             ConnectivityTrial::PhaseToString(
    134                                 trial_result.phase).c_str(),
    135                             ConnectivityTrial::StatusToString(
    136                                 trial_result.status).c_str(),
    137                             failures_in_content_phase_);
    138 
    139   if (trial_result.status == ConnectivityTrial::kStatusSuccess ||
    140       attempt_count_ >= kMaxRequestAttempts ||
    141       failures_in_content_phase_ >= kMaxFailuresInContentPhase) {
    142     result.num_attempts = attempt_count_;
    143     result.final = true;
    144     Stop();
    145   } else {
    146     attempt_count_++;
    147     int retry_delay_seconds = AdjustStartDelay(0);
    148     connectivity_trial_->Retry(retry_delay_seconds * 1000);
    149     UpdateAttemptTime(retry_delay_seconds);
    150   }
    151   portal_result_callback_.Run(result);
    152 }
    153 
    154 void PortalDetector::UpdateAttemptTime(int delay_seconds) {
    155   time_->GetTimeMonotonic(&attempt_start_time_);
    156   struct timeval delay_timeval = { delay_seconds, 0 };
    157   timeradd(&attempt_start_time_, &delay_timeval, &attempt_start_time_);
    158 }
    159 
    160 
    161 int PortalDetector::AdjustStartDelay(int init_delay_seconds) {
    162   int next_attempt_delay_seconds = 0;
    163   if (attempt_count_ > 0) {
    164     // Ensure that attempts are spaced at least by a minimal interval.
    165     struct timeval now, elapsed_time;
    166     time_->GetTimeMonotonic(&now);
    167     timersub(&now, &attempt_start_time_, &elapsed_time);
    168     SLOG(connection_.get(), 4) << "Elapsed time from previous attempt is "
    169                                << elapsed_time.tv_sec << " seconds.";
    170     if (elapsed_time.tv_sec < kMinTimeBetweenAttemptsSeconds) {
    171       next_attempt_delay_seconds = kMinTimeBetweenAttemptsSeconds -
    172                                    elapsed_time.tv_sec;
    173     }
    174   } else {
    175     LOG(FATAL) << "AdjustStartDelay in PortalDetector called without "
    176                   "previous attempts";
    177   }
    178   SLOG(connection_.get(), 3) << "Adjusting trial start delay from "
    179                              << init_delay_seconds << " seconds to "
    180                              << next_attempt_delay_seconds << " seconds.";
    181   return next_attempt_delay_seconds;
    182 }
    183 
    184 }  // namespace shill
    185