Home | History | Annotate | Download | only in cellular
      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