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