// // Copyright (C) 2012 The Android Open Source Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // #include "shill/portal_detector.h" #include <string> #include <base/bind.h> #include <base/strings/string_number_conversions.h> #include <base/strings/string_util.h> #include <base/strings/stringprintf.h> #if defined(__ANDROID__) #include <dbus/service_constants.h> #else #include <chromeos/dbus/service_constants.h> #endif // __ANDROID__ #include "shill/connection.h" #include "shill/connectivity_trial.h" #include "shill/logging.h" using base::Bind; using base::Callback; using base::StringPrintf; using std::string; namespace shill { namespace Logging { static auto kModuleLogScope = ScopeLogger::kPortal; static string ObjectID(Connection* c) { return c->interface_name(); } } const int PortalDetector::kDefaultCheckIntervalSeconds = 30; const char PortalDetector::kDefaultCheckPortalList[] = "ethernet,wifi,cellular"; const int PortalDetector::kMaxRequestAttempts = 3; const int PortalDetector::kMinTimeBetweenAttemptsSeconds = 3; const int PortalDetector::kRequestTimeoutSeconds = 10; const int PortalDetector::kMaxFailuresInContentPhase = 2; PortalDetector::PortalDetector( ConnectionRefPtr connection, EventDispatcher* dispatcher, const Callback<void(const PortalDetector::Result&)>& callback) : attempt_count_(0), attempt_start_time_((struct timeval){0}), connection_(connection), dispatcher_(dispatcher), weak_ptr_factory_(this), portal_result_callback_(callback), connectivity_trial_callback_(Bind(&PortalDetector::CompleteAttempt, weak_ptr_factory_.GetWeakPtr())), time_(Time::GetInstance()), failures_in_content_phase_(0), connectivity_trial_( new ConnectivityTrial(connection_, dispatcher_, kRequestTimeoutSeconds, connectivity_trial_callback_)) { } PortalDetector::~PortalDetector() { Stop(); } bool PortalDetector::Start(const string& url_string) { return StartAfterDelay(url_string, 0); } bool PortalDetector::StartAfterDelay(const string& url_string, int delay_seconds) { SLOG(connection_.get(), 3) << "In " << __func__; if (!connectivity_trial_->Start(url_string, delay_seconds * 1000)) { return false; } attempt_count_ = 1; // The attempt_start_time_ is calculated based on the current time and // |delay_seconds|. This is used to determine if a portal detection attempt // is in progress. UpdateAttemptTime(delay_seconds); // If we're starting a new set of attempts, discard past failure history. failures_in_content_phase_ = 0; return true; } void PortalDetector::Stop() { SLOG(connection_.get(), 3) << "In " << __func__; attempt_count_ = 0; failures_in_content_phase_ = 0; if (connectivity_trial_.get()) connectivity_trial_->Stop(); } // IsInProgress returns true if a ConnectivityTrial is actively testing the // connection. If Start has been called, but the trial was delayed, // IsInProgress will return false. PortalDetector implements this by // calculating the start time of the next ConnectivityTrial. After an initial // trial and in the case where multiple attempts may be tried, IsInProgress will // return true. bool PortalDetector::IsInProgress() { if (attempt_count_ > 1) return true; if (attempt_count_ == 1 && connectivity_trial_.get()) return connectivity_trial_->IsActive(); return false; } void PortalDetector::CompleteAttempt(ConnectivityTrial::Result trial_result) { Result result = Result(trial_result); if (trial_result.status == ConnectivityTrial::kStatusFailure && trial_result.phase == ConnectivityTrial::kPhaseContent) { failures_in_content_phase_++; } LOG(INFO) << StringPrintf("Portal detection completed attempt %d with " "phase==%s, status==%s, failures in content==%d", attempt_count_, ConnectivityTrial::PhaseToString( trial_result.phase).c_str(), ConnectivityTrial::StatusToString( trial_result.status).c_str(), failures_in_content_phase_); if (trial_result.status == ConnectivityTrial::kStatusSuccess || attempt_count_ >= kMaxRequestAttempts || failures_in_content_phase_ >= kMaxFailuresInContentPhase) { result.num_attempts = attempt_count_; result.final = true; Stop(); } else { attempt_count_++; int retry_delay_seconds = AdjustStartDelay(0); connectivity_trial_->Retry(retry_delay_seconds * 1000); UpdateAttemptTime(retry_delay_seconds); } portal_result_callback_.Run(result); } void PortalDetector::UpdateAttemptTime(int delay_seconds) { time_->GetTimeMonotonic(&attempt_start_time_); struct timeval delay_timeval = { delay_seconds, 0 }; timeradd(&attempt_start_time_, &delay_timeval, &attempt_start_time_); } int PortalDetector::AdjustStartDelay(int init_delay_seconds) { int next_attempt_delay_seconds = 0; if (attempt_count_ > 0) { // Ensure that attempts are spaced at least by a minimal interval. struct timeval now, elapsed_time; time_->GetTimeMonotonic(&now); timersub(&now, &attempt_start_time_, &elapsed_time); SLOG(connection_.get(), 4) << "Elapsed time from previous attempt is " << elapsed_time.tv_sec << " seconds."; if (elapsed_time.tv_sec < kMinTimeBetweenAttemptsSeconds) { next_attempt_delay_seconds = kMinTimeBetweenAttemptsSeconds - elapsed_time.tv_sec; } } else { LOG(FATAL) << "AdjustStartDelay in PortalDetector called without " "previous attempts"; } SLOG(connection_.get(), 3) << "Adjusting trial start delay from " << init_delay_seconds << " seconds to " << next_attempt_delay_seconds << " seconds."; return next_attempt_delay_seconds; } } // namespace shill