| // Copyright (c) 2013 The Chromium OS Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "shill/active_passive_out_of_credits_detector.h" |
| |
| #include "shill/cellular_service.h" |
| #include "shill/connection.h" |
| #include "shill/connection_health_checker.h" |
| #include "shill/logging.h" |
| #include "shill/manager.h" |
| #include "shill/traffic_monitor.h" |
| |
| namespace shill { |
| |
| // static |
| const int64 |
| ActivePassiveOutOfCreditsDetector::kOutOfCreditsConnectionDropSeconds = 15; |
| const int |
| ActivePassiveOutOfCreditsDetector::kOutOfCreditsMaxConnectAttempts = 3; |
| const int64 |
| ActivePassiveOutOfCreditsDetector::kOutOfCreditsResumeIgnoreSeconds = 5; |
| |
| ActivePassiveOutOfCreditsDetector::ActivePassiveOutOfCreditsDetector( |
| EventDispatcher *dispatcher, |
| Manager *manager, |
| Metrics *metrics, |
| CellularService *service) |
| : OutOfCreditsDetector(dispatcher, manager, metrics, service), |
| weak_ptr_factory_(this), |
| traffic_monitor_( |
| new TrafficMonitor(service->cellular(), dispatcher)) { |
| ResetDetector(); |
| traffic_monitor_->set_tcp_out_traffic_not_routed_callback( |
| Bind(&ActivePassiveOutOfCreditsDetector::OnNoNetworkRouting, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| ActivePassiveOutOfCreditsDetector::~ActivePassiveOutOfCreditsDetector() { |
| StopTrafficMonitor(); |
| } |
| |
| void ActivePassiveOutOfCreditsDetector::ResetDetector() { |
| SLOG(Cellular, 2) << "Reset out-of-credits detection"; |
| out_of_credits_detection_in_progress_ = false; |
| num_connect_attempts_ = 0; |
| } |
| |
| bool ActivePassiveOutOfCreditsDetector::IsDetecting() const { |
| return out_of_credits_detection_in_progress_; |
| } |
| |
| void ActivePassiveOutOfCreditsDetector::NotifyServiceStateChanged( |
| Service::ConnectState old_state, Service::ConnectState new_state) { |
| SLOG(Cellular, 2) << __func__ << ": " << old_state << " -> " << new_state; |
| switch (new_state) { |
| case Service::kStateUnknown: |
| case Service::kStateIdle: |
| case Service::kStateFailure: |
| StopTrafficMonitor(); |
| health_checker_.reset(); |
| break; |
| case Service::kStateAssociating: |
| if (num_connect_attempts_ == 0) |
| ReportOutOfCredits(false); |
| if (old_state != Service::kStateAssociating) { |
| connect_start_time_ = base::Time::Now(); |
| num_connect_attempts_++; |
| SLOG(Cellular, 2) << __func__ |
| << ": num_connect_attempts=" |
| << num_connect_attempts_; |
| } |
| break; |
| case Service::kStateConnected: |
| StartTrafficMonitor(); |
| SetupConnectionHealthChecker(); |
| break; |
| case Service::kStatePortal: |
| SLOG(Cellular, 2) << "Portal detection failed. Launching active probe " |
| << "for out-of-credit detection."; |
| RequestConnectionHealthCheck(); |
| break; |
| case Service::kStateConfiguring: |
| case Service::kStateOnline: |
| break; |
| } |
| DetectConnectDisconnectLoop(old_state, new_state); |
| } |
| |
| bool ActivePassiveOutOfCreditsDetector::StartTrafficMonitor() { |
| SLOG(Cellular, 2) << __func__; |
| SLOG(Cellular, 2) << "Service " << service()->friendly_name() |
| << ": Traffic Monitor starting."; |
| traffic_monitor_->Start(); |
| return true; |
| } |
| |
| void ActivePassiveOutOfCreditsDetector::StopTrafficMonitor() { |
| SLOG(Cellular, 2) << __func__; |
| SLOG(Cellular, 2) << "Service " << service()->friendly_name() |
| << ": Traffic Monitor stopping."; |
| traffic_monitor_->Stop(); |
| } |
| |
| void ActivePassiveOutOfCreditsDetector::OnNoNetworkRouting() { |
| SLOG(Cellular, 2) << "Service " << service()->friendly_name() |
| << ": Traffic Monitor detected network congestion."; |
| SLOG(Cellular, 2) << "Requesting active probe for out-of-credit detection."; |
| RequestConnectionHealthCheck(); |
| } |
| |
| void ActivePassiveOutOfCreditsDetector::SetupConnectionHealthChecker() { |
| DCHECK(service()->connection()); |
| // TODO: Consider moving health_checker_remote_ips() out of manager |
| // (crbug.com/304974). |
| if (!health_checker_.get()) { |
| health_checker_.reset( |
| new ConnectionHealthChecker( |
| service()->connection(), |
| dispatcher(), |
| manager()->health_checker_remote_ips(), |
| Bind(&ActivePassiveOutOfCreditsDetector:: |
| OnConnectionHealthCheckerResult, |
| weak_ptr_factory_.GetWeakPtr()))); |
| } else { |
| health_checker_->SetConnection(service()->connection()); |
| } |
| // Add URL in either case because a connection reset could have dropped past |
| // DNS queries. |
| health_checker_->AddRemoteURL(manager()->GetPortalCheckURL()); |
| } |
| |
| void ActivePassiveOutOfCreditsDetector::RequestConnectionHealthCheck() { |
| if (!health_checker_.get()) { |
| SLOG(Cellular, 2) << "No health checker exists, cannot request " |
| << "health check."; |
| return; |
| } |
| if (health_checker_->health_check_in_progress()) { |
| SLOG(Cellular, 2) << "Health check already in progress."; |
| return; |
| } |
| health_checker_->Start(); |
| } |
| |
| void ActivePassiveOutOfCreditsDetector::OnConnectionHealthCheckerResult( |
| ConnectionHealthChecker::Result result) { |
| SLOG(Cellular, 2) << __func__ << "(Result = " |
| << ConnectionHealthChecker::ResultToString(result) << ")"; |
| |
| if (result == ConnectionHealthChecker::kResultCongestedTxQueue) { |
| LOG(WARNING) << "Active probe determined possible out-of-credits " |
| << "scenario."; |
| if (service()) { |
| Metrics::CellularOutOfCreditsReason reason = |
| (result == ConnectionHealthChecker::kResultCongestedTxQueue) ? |
| Metrics::kCellularOutOfCreditsReasonTxCongested : |
| Metrics::kCellularOutOfCreditsReasonElongatedTimeWait; |
| metrics()->NotifyCellularOutOfCredits(reason); |
| |
| ReportOutOfCredits(true); |
| SLOG(Cellular, 2) << "Disconnecting due to out-of-credit scenario."; |
| Error error; |
| service()->Disconnect(&error); |
| } |
| } |
| } |
| |
| void ActivePassiveOutOfCreditsDetector::DetectConnectDisconnectLoop( |
| Service::ConnectState curr_state, Service::ConnectState new_state) { |
| // WORKAROUND: |
| // Some modems on Verizon network do not properly redirect when a SIM |
| // runs out of credits. This workaround is used to detect an out-of-credits |
| // condition by retrying a connect request if it was dropped within |
| // kOutOfCreditsConnectionDropSeconds. If the number of retries exceeds |
| // kOutOfCreditsMaxConnectAttempts, then the SIM is considered |
| // out-of-credits and the cellular service kOutOfCreditsProperty is set. |
| // This will signal Chrome to display the appropriate UX and also suppress |
| // auto-connect until the next time the user manually connects. |
| // |
| // TODO(thieule): Remove this workaround (crosbug.com/p/18169). |
| if (out_of_credits()) { |
| SLOG(Cellular, 2) << __func__ |
| << ": Already out-of-credits, skipping check"; |
| return; |
| } |
| base::TimeDelta |
| time_since_resume = base::Time::Now() - service()->resume_start_time(); |
| if (time_since_resume.InSeconds() < kOutOfCreditsResumeIgnoreSeconds) { |
| // On platforms that power down the modem during suspend, make sure that |
| // we do not display a false out-of-credits warning to the user |
| // due to the sequence below by skipping out-of-credits detection |
| // immediately after a resume. |
| // 1. User suspends Chromebook. |
| // 2. Hardware turns off power to modem. |
| // 3. User resumes Chromebook. |
| // 4. Hardware restores power to modem. |
| // 5. ModemManager still has instance of old modem. |
| // ModemManager does not delete this instance until udev fires a |
| // device removed event. ModemManager does not detect new modem |
| // until udev fires a new device event. |
| // 6. Shill performs auto-connect against the old modem. |
| // Make sure at this step that we do not display a false |
| // out-of-credits warning. |
| // 7. Udev fires device removed event. |
| // 8. Udev fires new device event. |
| SLOG(Cellular, 2) << |
| "Skipping out-of-credits detection, too soon since resume."; |
| ResetDetector(); |
| return; |
| } |
| base::TimeDelta |
| time_since_connect = base::Time::Now() - connect_start_time_; |
| if (time_since_connect.InSeconds() > kOutOfCreditsConnectionDropSeconds) { |
| ResetDetector(); |
| return; |
| } |
| // Verizon can drop the connection in two ways: |
| // - Denies the connect request |
| // - Allows connect request but disconnects later |
| bool connection_dropped = |
| (Service::IsConnectedState(curr_state) || |
| Service::IsConnectingState(curr_state)) && |
| (new_state == Service::kStateFailure || |
| new_state == Service::kStateIdle); |
| if (!connection_dropped) |
| return; |
| if (service()->explicitly_disconnected()) |
| return; |
| if (service()->roaming_state() == kRoamingStateRoaming && |
| !service()->cellular()->allow_roaming_property()) |
| return; |
| if (time_since_connect.InSeconds() <= kOutOfCreditsConnectionDropSeconds) { |
| if (num_connect_attempts_ < kOutOfCreditsMaxConnectAttempts) { |
| SLOG(Cellular, 2) << "Out-Of-Credits detection: Reconnecting " |
| << "(retry #" << num_connect_attempts_ << ")"; |
| // Prevent autoconnect logic from kicking in while we perform the |
| // out-of-credits detection. |
| out_of_credits_detection_in_progress_ = true; |
| dispatcher()->PostTask( |
| Bind(&ActivePassiveOutOfCreditsDetector::OutOfCreditsReconnect, |
| weak_ptr_factory_.GetWeakPtr())); |
| } else { |
| LOG(INFO) << "Active/Passive Out-Of-Credits detection: " |
| << "Marking service as out-of-credits"; |
| metrics()->NotifyCellularOutOfCredits( |
| Metrics::kCellularOutOfCreditsReasonConnectDisconnectLoop); |
| ReportOutOfCredits(true); |
| ResetDetector(); |
| } |
| } |
| } |
| |
| void ActivePassiveOutOfCreditsDetector::OutOfCreditsReconnect() { |
| Error error; |
| service()->Connect(&error, __func__); |
| } |
| |
| void ActivePassiveOutOfCreditsDetector::set_traffic_monitor( |
| TrafficMonitor *traffic_monitor) { |
| traffic_monitor_.reset(traffic_monitor); |
| } |
| |
| void ActivePassiveOutOfCreditsDetector::set_connection_health_checker( |
| ConnectionHealthChecker *health_checker) { |
| health_checker_.reset(health_checker); |
| } |
| |
| } // namespace shill |