• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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;
ObjectID(Connection * c)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 
PortalDetector(ConnectionRefPtr connection,EventDispatcher * dispatcher,const Callback<void (const PortalDetector::Result &)> & callback)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 
~PortalDetector()75 PortalDetector::~PortalDetector() {
76   Stop();
77 }
78 
Start(const string & url_string)79 bool PortalDetector::Start(const string& url_string) {
80   return StartAfterDelay(url_string, 0);
81 }
82 
StartAfterDelay(const string & url_string,int delay_seconds)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 
Stop()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.
IsInProgress()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 
CompleteAttempt(ConnectivityTrial::Result trial_result)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 
UpdateAttemptTime(int delay_seconds)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 
AdjustStartDelay(int init_delay_seconds)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