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; 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 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 63 ActivePassiveOutOfCreditsDetector::~ActivePassiveOutOfCreditsDetector() { 64 StopTrafficMonitor(); 65 } 66 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 73 bool ActivePassiveOutOfCreditsDetector::IsDetecting() const { 74 return out_of_credits_detection_in_progress_; 75 } 76 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 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 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 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 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 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 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 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 278 void ActivePassiveOutOfCreditsDetector::OutOfCreditsReconnect() { 279 Error error; 280 service()->Connect(&error, __func__); 281 } 282 283 void ActivePassiveOutOfCreditsDetector::set_traffic_monitor( 284 TrafficMonitor* traffic_monitor) { 285 traffic_monitor_.reset(traffic_monitor); 286 } 287 288 void ActivePassiveOutOfCreditsDetector::set_connection_health_checker( 289 ConnectionHealthChecker* health_checker) { 290 health_checker_.reset(health_checker); 291 } 292 293 } // namespace shill 294