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