1 //
2 // Copyright (C) 2013 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/cellular/active_passive_out_of_credits_detector.h"
18
19 #include <string>
20
21 #include "shill/cellular/cellular_service.h"
22 #include "shill/connection.h"
23 #include "shill/connection_health_checker.h"
24 #include "shill/logging.h"
25 #include "shill/manager.h"
26 #include "shill/traffic_monitor.h"
27
28 using std::string;
29
30 namespace shill {
31
32 namespace Logging {
33 static auto kModuleLogScope = ScopeLogger::kCellular;
ObjectID(ActivePassiveOutOfCreditsDetector * a)34 static string ObjectID(ActivePassiveOutOfCreditsDetector* a) {
35 return a->GetServiceRpcIdentifier();
36 }
37 }
38
39 // static
40 const int64_t
41 ActivePassiveOutOfCreditsDetector::kOutOfCreditsConnectionDropSeconds = 15;
42 const int
43 ActivePassiveOutOfCreditsDetector::kOutOfCreditsMaxConnectAttempts = 3;
44 const int64_t
45 ActivePassiveOutOfCreditsDetector::kOutOfCreditsResumeIgnoreSeconds = 5;
46
ActivePassiveOutOfCreditsDetector(EventDispatcher * dispatcher,Manager * manager,Metrics * metrics,CellularService * service)47 ActivePassiveOutOfCreditsDetector::ActivePassiveOutOfCreditsDetector(
48 EventDispatcher* dispatcher,
49 Manager* manager,
50 Metrics* metrics,
51 CellularService* service)
52 : OutOfCreditsDetector(dispatcher, manager, metrics, service),
53 weak_ptr_factory_(this),
54 traffic_monitor_(
55 new TrafficMonitor(service->cellular(), dispatcher)),
56 service_rpc_identifier_(service->GetRpcIdentifier()) {
57 ResetDetector();
58 traffic_monitor_->set_network_problem_detected_callback(
59 Bind(&ActivePassiveOutOfCreditsDetector::OnNoNetworkRouting,
60 weak_ptr_factory_.GetWeakPtr()));
61 }
62
~ActivePassiveOutOfCreditsDetector()63 ActivePassiveOutOfCreditsDetector::~ActivePassiveOutOfCreditsDetector() {
64 StopTrafficMonitor();
65 }
66
ResetDetector()67 void ActivePassiveOutOfCreditsDetector::ResetDetector() {
68 SLOG(this, 2) << "Reset out-of-credits detection";
69 out_of_credits_detection_in_progress_ = false;
70 num_connect_attempts_ = 0;
71 }
72
IsDetecting() const73 bool ActivePassiveOutOfCreditsDetector::IsDetecting() const {
74 return out_of_credits_detection_in_progress_;
75 }
76
NotifyServiceStateChanged(Service::ConnectState old_state,Service::ConnectState new_state)77 void ActivePassiveOutOfCreditsDetector::NotifyServiceStateChanged(
78 Service::ConnectState old_state, Service::ConnectState new_state) {
79 SLOG(this, 2) << __func__ << ": " << old_state << " -> " << new_state;
80 switch (new_state) {
81 case Service::kStateUnknown:
82 case Service::kStateIdle:
83 case Service::kStateFailure:
84 StopTrafficMonitor();
85 health_checker_.reset();
86 break;
87 case Service::kStateAssociating:
88 if (num_connect_attempts_ == 0)
89 ReportOutOfCredits(false);
90 if (old_state != Service::kStateAssociating) {
91 connect_start_time_ = base::Time::Now();
92 num_connect_attempts_++;
93 SLOG(this, 2) << __func__
94 << ": num_connect_attempts="
95 << num_connect_attempts_;
96 }
97 break;
98 case Service::kStateConnected:
99 StartTrafficMonitor();
100 SetupConnectionHealthChecker();
101 break;
102 case Service::kStatePortal:
103 SLOG(this, 2) << "Portal detection failed. Launching active probe "
104 << "for out-of-credit detection.";
105 RequestConnectionHealthCheck();
106 break;
107 case Service::kStateConfiguring:
108 case Service::kStateOnline:
109 break;
110 }
111 DetectConnectDisconnectLoop(old_state, new_state);
112 }
113
StartTrafficMonitor()114 bool ActivePassiveOutOfCreditsDetector::StartTrafficMonitor() {
115 SLOG(this, 2) << __func__;
116 SLOG(this, 2) << "Service " << service()->friendly_name()
117 << ": Traffic Monitor starting.";
118 traffic_monitor_->Start();
119 return true;
120 }
121
StopTrafficMonitor()122 void ActivePassiveOutOfCreditsDetector::StopTrafficMonitor() {
123 SLOG(this, 2) << __func__;
124 SLOG(this, 2) << "Service " << service()->friendly_name()
125 << ": Traffic Monitor stopping.";
126 traffic_monitor_->Stop();
127 }
128
OnNoNetworkRouting(int reason)129 void ActivePassiveOutOfCreditsDetector::OnNoNetworkRouting(int reason) {
130 SLOG(this, 2) << "Service " << service()->friendly_name()
131 << ": Traffic Monitor detected network congestion.";
132 SLOG(this, 2) << "Requesting active probe for out-of-credit detection.";
133 RequestConnectionHealthCheck();
134 }
135
SetupConnectionHealthChecker()136 void ActivePassiveOutOfCreditsDetector::SetupConnectionHealthChecker() {
137 DCHECK(service()->connection());
138 // TODO(thieule): Consider moving health_checker_remote_ips() out of manager
139 // (crbug.com/304974).
140 if (!health_checker_.get()) {
141 health_checker_.reset(
142 new ConnectionHealthChecker(
143 service()->connection(),
144 dispatcher(),
145 manager()->health_checker_remote_ips(),
146 Bind(&ActivePassiveOutOfCreditsDetector::
147 OnConnectionHealthCheckerResult,
148 weak_ptr_factory_.GetWeakPtr())));
149 } else {
150 health_checker_->SetConnection(service()->connection());
151 }
152 // Add URL in either case because a connection reset could have dropped past
153 // DNS queries.
154 health_checker_->AddRemoteURL(manager()->GetPortalCheckURL());
155 }
156
RequestConnectionHealthCheck()157 void ActivePassiveOutOfCreditsDetector::RequestConnectionHealthCheck() {
158 if (!health_checker_.get()) {
159 SLOG(this, 2) << "No health checker exists, cannot request "
160 << "health check.";
161 return;
162 }
163 if (health_checker_->health_check_in_progress()) {
164 SLOG(this, 2) << "Health check already in progress.";
165 return;
166 }
167 health_checker_->Start();
168 }
169
OnConnectionHealthCheckerResult(ConnectionHealthChecker::Result result)170 void ActivePassiveOutOfCreditsDetector::OnConnectionHealthCheckerResult(
171 ConnectionHealthChecker::Result result) {
172 SLOG(this, 2) << __func__ << "(Result = "
173 << ConnectionHealthChecker::ResultToString(result) << ")";
174
175 if (result == ConnectionHealthChecker::kResultCongestedTxQueue) {
176 LOG(WARNING) << "Active probe determined possible out-of-credits "
177 << "scenario.";
178 if (service()) {
179 Metrics::CellularOutOfCreditsReason reason =
180 (result == ConnectionHealthChecker::kResultCongestedTxQueue) ?
181 Metrics::kCellularOutOfCreditsReasonTxCongested :
182 Metrics::kCellularOutOfCreditsReasonElongatedTimeWait;
183 metrics()->NotifyCellularOutOfCredits(reason);
184
185 ReportOutOfCredits(true);
186 SLOG(this, 2) << "Disconnecting due to out-of-credit scenario.";
187 Error error;
188 service()->Disconnect(&error, "out-of-credits");
189 }
190 }
191 }
192
DetectConnectDisconnectLoop(Service::ConnectState curr_state,Service::ConnectState new_state)193 void ActivePassiveOutOfCreditsDetector::DetectConnectDisconnectLoop(
194 Service::ConnectState curr_state, Service::ConnectState new_state) {
195 // WORKAROUND:
196 // Some modems on Verizon network do not properly redirect when a SIM
197 // runs out of credits. This workaround is used to detect an out-of-credits
198 // condition by retrying a connect request if it was dropped within
199 // kOutOfCreditsConnectionDropSeconds. If the number of retries exceeds
200 // kOutOfCreditsMaxConnectAttempts, then the SIM is considered
201 // out-of-credits and the cellular service kOutOfCreditsProperty is set.
202 // This will signal Chrome to display the appropriate UX and also suppress
203 // auto-connect until the next time the user manually connects.
204 //
205 // TODO(thieule): Remove this workaround (crosbug.com/p/18169).
206 if (out_of_credits()) {
207 SLOG(this, 2) << __func__
208 << ": Already out-of-credits, skipping check";
209 return;
210 }
211 base::TimeDelta
212 time_since_resume = base::Time::Now() - service()->resume_start_time();
213 if (time_since_resume.InSeconds() < kOutOfCreditsResumeIgnoreSeconds) {
214 // On platforms that power down the modem during suspend, make sure that
215 // we do not display a false out-of-credits warning to the user
216 // due to the sequence below by skipping out-of-credits detection
217 // immediately after a resume.
218 // 1. User suspends Chromebook.
219 // 2. Hardware turns off power to modem.
220 // 3. User resumes Chromebook.
221 // 4. Hardware restores power to modem.
222 // 5. ModemManager still has instance of old modem.
223 // ModemManager does not delete this instance until udev fires a
224 // device removed event. ModemManager does not detect new modem
225 // until udev fires a new device event.
226 // 6. Shill performs auto-connect against the old modem.
227 // Make sure at this step that we do not display a false
228 // out-of-credits warning.
229 // 7. Udev fires device removed event.
230 // 8. Udev fires new device event.
231 SLOG(this, 2) <<
232 "Skipping out-of-credits detection, too soon since resume.";
233 ResetDetector();
234 return;
235 }
236 base::TimeDelta
237 time_since_connect = base::Time::Now() - connect_start_time_;
238 if (time_since_connect.InSeconds() > kOutOfCreditsConnectionDropSeconds) {
239 ResetDetector();
240 return;
241 }
242 // Verizon can drop the connection in two ways:
243 // - Denies the connect request
244 // - Allows connect request but disconnects later
245 bool connection_dropped =
246 (Service::IsConnectedState(curr_state) ||
247 Service::IsConnectingState(curr_state)) &&
248 (new_state == Service::kStateFailure ||
249 new_state == Service::kStateIdle);
250 if (!connection_dropped)
251 return;
252 if (service()->explicitly_disconnected())
253 return;
254 if (service()->roaming_state() == kRoamingStateRoaming &&
255 !service()->cellular()->allow_roaming_property())
256 return;
257 if (time_since_connect.InSeconds() <= kOutOfCreditsConnectionDropSeconds) {
258 if (num_connect_attempts_ < kOutOfCreditsMaxConnectAttempts) {
259 SLOG(this, 2) << "Out-Of-Credits detection: Reconnecting "
260 << "(retry #" << num_connect_attempts_ << ")";
261 // Prevent autoconnect logic from kicking in while we perform the
262 // out-of-credits detection.
263 out_of_credits_detection_in_progress_ = true;
264 dispatcher()->PostTask(
265 Bind(&ActivePassiveOutOfCreditsDetector::OutOfCreditsReconnect,
266 weak_ptr_factory_.GetWeakPtr()));
267 } else {
268 LOG(INFO) << "Active/Passive Out-Of-Credits detection: "
269 << "Marking service as out-of-credits";
270 metrics()->NotifyCellularOutOfCredits(
271 Metrics::kCellularOutOfCreditsReasonConnectDisconnectLoop);
272 ReportOutOfCredits(true);
273 ResetDetector();
274 }
275 }
276 }
277
OutOfCreditsReconnect()278 void ActivePassiveOutOfCreditsDetector::OutOfCreditsReconnect() {
279 Error error;
280 service()->Connect(&error, __func__);
281 }
282
set_traffic_monitor(TrafficMonitor * traffic_monitor)283 void ActivePassiveOutOfCreditsDetector::set_traffic_monitor(
284 TrafficMonitor* traffic_monitor) {
285 traffic_monitor_.reset(traffic_monitor);
286 }
287
set_connection_health_checker(ConnectionHealthChecker * health_checker)288 void ActivePassiveOutOfCreditsDetector::set_connection_health_checker(
289 ConnectionHealthChecker* health_checker) {
290 health_checker_.reset(health_checker);
291 }
292
293 } // namespace shill
294