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