| // Copyright (c) 2012 The Chromium OS Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #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> |
| #include <chromeos/dbus/service_constants.h> |
| |
| #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 |