| // Copyright 2018 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/cellular/cellular.h" |
| |
| #include <fcntl.h> |
| #include <netinet/in.h> |
| #include <linux/if.h> // NOLINT - Needs definitions from netinet/in.h |
| |
| #include <tuple> |
| #include <utility> |
| |
| #include <base/bind.h> |
| #include <base/callback.h> |
| #include <base/check.h> |
| #include <base/check_op.h> |
| #include <base/containers/contains.h> |
| #include <base/files/file_path.h> |
| #include <base/files/file_util.h> |
| #include <base/memory/ptr_util.h> |
| #include <base/notreached.h> |
| #include <base/strings/string_split.h> |
| #include <base/strings/stringprintf.h> |
| #include <base/time/time.h> |
| #include <chromeos/dbus/service_constants.h> |
| #include <ModemManager/ModemManager.h> |
| |
| #include "shill/adaptor_interfaces.h" |
| #include "shill/cellular/cellular_bearer.h" |
| #include "shill/cellular/cellular_capability.h" |
| #include "shill/cellular/cellular_service.h" |
| #include "shill/cellular/cellular_service_provider.h" |
| #include "shill/cellular/mobile_operator_info.h" |
| #include "shill/cellular/modem_info.h" |
| #include "shill/connection.h" |
| #include "shill/control_interface.h" |
| #include "shill/dbus/dbus_properties_proxy.h" |
| #include "shill/device.h" |
| #include "shill/device_info.h" |
| #include "shill/error.h" |
| #include "shill/event_dispatcher.h" |
| #include "shill/external_task.h" |
| #include "shill/ipconfig.h" |
| #include "shill/logging.h" |
| #include "shill/manager.h" |
| #include "shill/net/netlink_sock_diag.h" |
| #include "shill/net/rtnl_handler.h" |
| #include "shill/net/sockets.h" |
| #include "shill/ppp_daemon.h" |
| #include "shill/ppp_device.h" |
| #include "shill/ppp_device_factory.h" |
| #include "shill/process_manager.h" |
| #include "shill/profile.h" |
| #include "shill/property_accessor.h" |
| #include "shill/store_interface.h" |
| #include "shill/technology.h" |
| |
| namespace shill { |
| |
| namespace Logging { |
| static auto kModuleLogScope = ScopeLogger::kCellular; |
| static std::string ObjectID(const Cellular* c) { |
| return c->GetRpcIdentifier().value(); |
| } |
| } // namespace Logging |
| |
| namespace { |
| |
| // Maximum time to wait for Modem registration before canceling a pending |
| // connect attempt. |
| const int64_t kPendingConnectCancelMilliseconds = 60 * 1000; |
| |
| class ApnList { |
| public: |
| void AddApns( |
| const std::vector<std::unique_ptr<MobileOperatorInfo::MobileAPN>>& apns) { |
| for (const auto& mobile_apn : apns) |
| AddApn(mobile_apn); |
| } |
| |
| const Stringmaps& GetList() { return apn_dict_list_; } |
| |
| private: |
| using ApnIndexKey = |
| std::tuple<std::string, std::string, std::string, std::string>; |
| |
| ApnIndexKey GetKey( |
| const std::unique_ptr<MobileOperatorInfo::MobileAPN>& mobile_apn) { |
| return std::make_tuple(mobile_apn->apn, mobile_apn->username, |
| mobile_apn->password, mobile_apn->authentication); |
| } |
| |
| void AddApn( |
| const std::unique_ptr<MobileOperatorInfo::MobileAPN>& mobile_apn) { |
| ApnIndexKey index = GetKey(mobile_apn); |
| if (apn_index_[index] == nullptr) { |
| apn_dict_list_.emplace_back(); |
| apn_index_[index] = &apn_dict_list_.back(); |
| } |
| |
| Stringmap* props = apn_index_[index]; |
| if (!mobile_apn->apn.empty()) |
| props->emplace(kApnProperty, mobile_apn->apn); |
| if (!mobile_apn->username.empty()) |
| props->emplace(kApnUsernameProperty, mobile_apn->username); |
| if (!mobile_apn->password.empty()) |
| props->emplace(kApnPasswordProperty, mobile_apn->password); |
| if (!mobile_apn->authentication.empty()) |
| props->emplace(kApnAuthenticationProperty, mobile_apn->authentication); |
| if (mobile_apn->is_attach_apn) |
| props->emplace(kApnAttachProperty, kApnAttachProperty); |
| if (!mobile_apn->ip_type.empty()) |
| props->emplace(kApnIpTypeProperty, mobile_apn->ip_type); |
| |
| // Find the first localized and non-localized name, if any. |
| if (!mobile_apn->operator_name_list.empty()) |
| props->emplace(kApnNameProperty, mobile_apn->operator_name_list[0].name); |
| for (const auto& lname : mobile_apn->operator_name_list) { |
| if (!lname.language.empty()) |
| props->emplace(kApnLocalizedNameProperty, lname.name); |
| } |
| } |
| |
| Stringmaps apn_dict_list_; |
| std::map<ApnIndexKey, Stringmap*> apn_index_; |
| }; |
| |
| // Gets a printable value from a Stringmap without adding a value when it |
| // doesn't exist. |
| std::string GetStringmapValue(const Stringmap& string_map, |
| const std::string& key) { |
| if (!base::Contains(string_map, key)) |
| return ""; |
| |
| return string_map.at(key); |
| } |
| |
| bool IsEnabledModemState(Cellular::ModemState state) { |
| switch (state) { |
| case Cellular::kModemStateFailed: |
| case Cellular::kModemStateUnknown: |
| case Cellular::kModemStateDisabled: |
| case Cellular::kModemStateInitializing: |
| case Cellular::kModemStateLocked: |
| case Cellular::kModemStateDisabling: |
| case Cellular::kModemStateEnabling: |
| return false; |
| case Cellular::kModemStateEnabled: |
| case Cellular::kModemStateSearching: |
| case Cellular::kModemStateRegistered: |
| case Cellular::kModemStateDisconnecting: |
| case Cellular::kModemStateConnecting: |
| case Cellular::kModemStateConnected: |
| return true; |
| } |
| return false; |
| } |
| |
| } // namespace |
| |
| // static |
| const char Cellular::kAllowRoaming[] = "AllowRoaming"; |
| const char Cellular::kPolicyAllowRoaming[] = "PolicyAllowRoaming"; |
| const char Cellular::kUseAttachApn[] = "UseAttachAPN"; |
| const char Cellular::kQ6V5ModemManufacturerName[] = "QUALCOMM INCORPORATED"; |
| const char Cellular::kQ6V5DriverName[] = "qcom-q6v5-mss"; |
| const char Cellular::kModemDriverSysfsName[] = |
| "/sys/class/remoteproc/remoteproc0/device/driver"; |
| const char Cellular::kModemResetSysfsName[] = |
| "/sys/class/remoteproc/remoteproc0/state"; |
| const int64_t Cellular::kModemResetTimeoutMilliseconds = 1 * 1000; |
| const int64_t Cellular::kPollLocationIntervalMilliseconds = 5 * 60 * 1000; |
| |
| // static |
| std::string Cellular::GetStateString(State state) { |
| switch (state) { |
| case State::kDisabled: |
| return "Disabled"; |
| case State::kEnabled: |
| return "Enabled"; |
| case State::kModemStarting: |
| return "ModemStarting"; |
| case State::kModemStarted: |
| return "ModemStarted"; |
| case State::kModemStopping: |
| return "ModemStopping"; |
| case State::kRegistered: |
| return "Registered"; |
| case State::kConnected: |
| return "Connected"; |
| case State::kLinked: |
| return "Linked"; |
| default: |
| NOTREACHED(); |
| } |
| return base::StringPrintf("CellularStateUnknown-%d", state); |
| } |
| |
| // static |
| std::string Cellular::GetModemStateString(ModemState modem_state) { |
| switch (modem_state) { |
| case kModemStateFailed: |
| return "ModemStateFailed"; |
| case kModemStateUnknown: |
| return "ModemStateUnknown"; |
| case kModemStateInitializing: |
| return "ModemStateInitializing"; |
| case kModemStateLocked: |
| return "ModemStateLocked"; |
| case kModemStateDisabled: |
| return "ModemStateDisabled"; |
| case kModemStateDisabling: |
| return "ModemStateDisabling"; |
| case kModemStateEnabling: |
| return "ModemStateEnabling"; |
| case kModemStateEnabled: |
| return "ModemStateEnabled"; |
| case kModemStateSearching: |
| return "ModemStateSearching"; |
| case kModemStateRegistered: |
| return "ModemStateRegistered"; |
| case kModemStateDisconnecting: |
| return "ModemStateDisconnecting"; |
| case kModemStateConnecting: |
| return "ModemStateConnecting"; |
| case kModemStateConnected: |
| return "ModemStateConnected"; |
| default: |
| NOTREACHED(); |
| } |
| return base::StringPrintf("ModemStateUnknown-%d", modem_state); |
| } |
| |
| Cellular::Cellular(Manager* manager, |
| const std::string& link_name, |
| const std::string& address, |
| int interface_index, |
| Type type, |
| const std::string& service, |
| const RpcIdentifier& path) |
| : Device( |
| manager, link_name, address, interface_index, Technology::kCellular), |
| home_provider_info_( |
| new MobileOperatorInfo(manager->dispatcher(), "HomeProvider")), |
| serving_operator_info_( |
| new MobileOperatorInfo(manager->dispatcher(), "ServingOperator")), |
| dbus_service_(service), |
| dbus_path_(path), |
| dbus_path_str_(path.value()), |
| type_(type), |
| ppp_device_factory_(PPPDeviceFactory::GetInstance()), |
| process_manager_(ProcessManager::GetInstance()) { |
| RegisterProperties(); |
| |
| // TODO(pprabhu) Split MobileOperatorInfo into a context that stores the |
| // costly database, and lighter objects that |Cellular| can own. |
| // crbug.com/363874 |
| home_provider_info_->Init(); |
| serving_operator_info_->Init(); |
| |
| socket_destroyer_ = NetlinkSockDiag::Create(std::make_unique<Sockets>()); |
| if (!socket_destroyer_) { |
| LOG(WARNING) << "Socket destroyer failed to initialize; " |
| << "IPv6 will be unavailable."; |
| } |
| |
| // Create an initial Capability. |
| CreateCapability(); |
| |
| SLOG(this, 1) << "Cellular() " << this->link_name(); |
| } |
| |
| Cellular::~Cellular() { |
| SLOG(this, 1) << "~Cellular() " << this->link_name(); |
| if (capability_) |
| DestroyCapability(); |
| } |
| |
| std::string Cellular::GetLegacyEquipmentIdentifier() const { |
| // 3GPP devices are uniquely identified by IMEI, which has 15 decimal digits. |
| if (!imei_.empty()) |
| return imei_; |
| |
| // 3GPP2 devices are uniquely identified by MEID, which has 14 hexadecimal |
| // digits. |
| if (!meid_.empty()) |
| return meid_; |
| |
| // An equipment ID may be reported by ModemManager, which is typically the |
| // serial number of a legacy AT modem, and is either the IMEI, MEID, or ESN |
| // of a MBIM/QMI modem. This is used as a fallback in case neither IMEI nor |
| // MEID could be retrieved through ModemManager (e.g. when there is no SIM |
| // inserted, ModemManager doesn't expose modem 3GPP interface where the IMEI |
| // is reported). |
| if (!equipment_id_.empty()) |
| return equipment_id_; |
| |
| // If none of IMEI, MEID, and equipment ID is available, fall back to MAC |
| // address. |
| return mac_address(); |
| } |
| |
| std::string Cellular::GetStorageIdentifier() const { |
| // Cellular is not guaranteed to have a valid MAC address, and other unique |
| // identifiers may not be initially available. Use the link name to |
| // differentiate between internal devices and external devices. |
| return "device_" + link_name(); |
| } |
| |
| bool Cellular::Load(const StoreInterface* storage) { |
| std::string id = GetStorageIdentifier(); |
| if (!storage->ContainsGroup(id)) { |
| id = "device_" + GetLegacyEquipmentIdentifier(); |
| if (!storage->ContainsGroup(id)) { |
| LOG(WARNING) << "Device is not available in the persistent store: " << id; |
| return false; |
| } |
| legacy_storage_id_ = id; |
| } |
| storage->GetBool(id, kAllowRoaming, &allow_roaming_); |
| storage->GetBool(id, kPolicyAllowRoaming, &policy_allow_roaming_); |
| storage->GetBool(id, kUseAttachApn, &use_attach_apn_); |
| LOG(INFO) << __func__ << " id:" << id << " " << kAllowRoaming << ":" |
| << allow_roaming_ << " " << kPolicyAllowRoaming << ":" |
| << policy_allow_roaming_ << " " << kUseAttachApn << ":" |
| << use_attach_apn_ << " "; |
| return Device::Load(storage); |
| } |
| |
| bool Cellular::Save(StoreInterface* storage) { |
| const std::string id = GetStorageIdentifier(); |
| storage->SetBool(id, kAllowRoaming, allow_roaming_); |
| storage->SetBool(id, kPolicyAllowRoaming, policy_allow_roaming_); |
| storage->SetBool(id, kUseAttachApn, use_attach_apn_); |
| bool result = Device::Save(storage); |
| LOG(INFO) << __func__ << " id: " << id << ": " << result; |
| // TODO(b/181843251): Remove after M94. |
| if (result && !legacy_storage_id_.empty() && |
| storage->ContainsGroup(legacy_storage_id_)) { |
| LOG(INFO) << __func__ |
| << ": Deleting legacy storage id: " << legacy_storage_id_; |
| storage->DeleteGroup(legacy_storage_id_); |
| legacy_storage_id_.clear(); |
| } |
| return result; |
| } |
| |
| std::string Cellular::GetTechnologyFamily(Error* error) { |
| return capability_ ? capability_->GetTypeString() : ""; |
| } |
| |
| std::string Cellular::GetDeviceId(Error* error) { |
| return device_id_ ? device_id_->AsString() : ""; |
| } |
| |
| bool Cellular::ShouldBringNetworkInterfaceDownAfterDisabled() const { |
| if (!device_id_) |
| return false; |
| |
| // The cdc-mbim kernel driver stop draining the receive buffer after the |
| // network interface is brought down. However, some MBIM modem (see |
| // b:71505232) may misbehave if the host stops draining the receiver buffer |
| // before issuing a MBIM command to disconnect the modem from network. To |
| // work around the issue, shill needs to defer bringing down the network |
| // interface until after the modem is disabled. |
| // |
| // TODO(benchan): Investigate if we need to apply the workaround for other |
| // MBIM modems or revert this change once the issue is addressed by the modem |
| // firmware on Fibocom L850-GL. |
| static constexpr DeviceId kAffectedDeviceIds[] = { |
| {DeviceId::BusType::kUsb, 0x2cb7, 0x0007}, // Fibocom L850-GL |
| }; |
| for (const auto& affected_device_id : kAffectedDeviceIds) { |
| if (device_id_->Match(affected_device_id)) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| void Cellular::SetState(State state) { |
| if (state == state_) |
| return; |
| LOG(INFO) << __func__ << ": " << GetStateString(state_) << " -> " |
| << GetStateString(state); |
| state_ = state; |
| UpdateScanning(); |
| } |
| |
| void Cellular::SetModemState(ModemState modem_state) { |
| if (modem_state == modem_state_) |
| return; |
| LOG(INFO) << __func__ << ": " << GetModemStateString(modem_state_) << " -> " |
| << GetModemStateString(modem_state); |
| modem_state_ = modem_state; |
| UpdateScanning(); |
| } |
| |
| void Cellular::HelpRegisterDerivedBool(const std::string& name, |
| bool (Cellular::*get)(Error* error), |
| bool (Cellular::*set)(const bool& value, |
| Error* error)) { |
| mutable_store()->RegisterDerivedBool( |
| name, BoolAccessor(new CustomAccessor<Cellular, bool>(this, get, set))); |
| } |
| |
| void Cellular::HelpRegisterConstDerivedString( |
| const std::string& name, std::string (Cellular::*get)(Error*)) { |
| mutable_store()->RegisterDerivedString( |
| name, StringAccessor( |
| new CustomAccessor<Cellular, std::string>(this, get, nullptr))); |
| } |
| |
| void Cellular::Start(Error* error, |
| const EnabledStateChangedCallback& callback) { |
| DCHECK(error); |
| SLOG(this, 1) << __func__ << ": " << GetStateString(state_); |
| |
| if (!capability_) { |
| // Report success, even though a connection will not succeed until a Modem |
| // is instantiated and |cabability_| is created. Setting |state_| |
| // to kEnabled here will cause CreateCapability to call StartModem. |
| SetState(State::kEnabled); |
| LOG(WARNING) << __func__ << ": Skipping Start (no capability)."; |
| if (error) |
| error->Reset(); |
| return; |
| } |
| |
| StartModem(error, callback); |
| } |
| |
| void Cellular::Stop(Error* error, const EnabledStateChangedCallback& callback) { |
| SLOG(this, 1) << __func__ << ": " << GetStateString(state_); |
| if (!capability_) { |
| // Modem is already stopped (it crashed or is inhibited). Invoke the |
| // callback with no error to persist the disabled state. |
| DestroySockets(); |
| callback.Run(Error()); |
| return; |
| } |
| |
| StopModem(error, callback); |
| } |
| |
| void Cellular::StartModem(Error* error, |
| const EnabledStateChangedCallback& callback) { |
| DCHECK(capability_); |
| LOG(INFO) << __func__; |
| SetState(State::kModemStarting); |
| capability_->StartModem(error, |
| base::Bind(&Cellular::StartModemCallback, |
| weak_ptr_factory_.GetWeakPtr(), callback)); |
| } |
| |
| void Cellular::StartModemCallback(const EnabledStateChangedCallback& callback, |
| const Error& error) { |
| LOG(INFO) << __func__ << ": state=" << GetStateString(state_); |
| |
| if (!error.IsSuccess()) { |
| LOG(ERROR) << "StartModem failed: " << error; |
| SetState(State::kEnabled); |
| if (error.type() == Error::kWrongState) { |
| // If the enable operation failed with Error::kWrongState, the modem is |
| // in an unexpected state. This usually indicates a missing or locked |
| // SIM. Invoke |callback| with no error so that the enable completes. |
| // If the ModemState property later changes to 'disabled', StartModem |
| // will be called again. |
| callback.Run(Error()); |
| } else { |
| callback.Run(error); |
| } |
| return; |
| } |
| |
| SetState(State::kModemStarted); |
| |
| // Registration state updates may have been ignored while the |
| // modem was not yet marked enabled. |
| HandleNewRegistrationState(); |
| |
| metrics()->NotifyDeviceEnableFinished(interface_index()); |
| |
| callback.Run(Error()); |
| } |
| |
| void Cellular::StopModem(Error* error, |
| const EnabledStateChangedCallback& callback) { |
| DCHECK(capability_); |
| LOG(INFO) << __func__; |
| SetState(State::kModemStopping); |
| capability_->StopModem(error, |
| base::Bind(&Cellular::StopModemCallback, |
| weak_ptr_factory_.GetWeakPtr(), callback)); |
| } |
| |
| void Cellular::StopModemCallback(const EnabledStateChangedCallback& callback, |
| const Error& error) { |
| LOG(INFO) << __func__ << ": " << GetStateString(state_) |
| << " Error: " << error; |
| SetState(State::kDisabled); |
| |
| // Sockets should be destroyed here to ensure that we make new connections |
| // when we next enable Cellular. Since the carrier may assign us a new IP |
| // on reconnect and some carriers don't like it when packets are sent from |
| // this device using the old IP, we need to make sure that we prevent further |
| // packets from going out. |
| DestroySockets(); |
| |
| // Destroy any cellular services regardless of any errors that occur during |
| // the stop process since we do not know the state of the modem at this point. |
| DestroyAllServices(); |
| |
| // In case no termination action was executed (and TerminationActionComplete |
| // was not invoked) in response to a suspend request, any registered |
| // termination action needs to be removed explicitly. |
| manager()->RemoveTerminationAction(link_name()); |
| |
| UpdateScanning(); |
| |
| if (error.type() == Error::kWrongState) { |
| // ModemManager.Modem will not respond to Stop when in a failed state. Allow |
| // the callback to succeed so that Shill identifies and persists Cellular as |
| // disabled. TODO(b/184974739): StopModem should probably succeed when in a |
| // failed state. |
| LOG(ERROR) << "StopModem returned an error: " << error; |
| callback.Run(Error()); |
| } else { |
| callback.Run(error); |
| } |
| } |
| |
| void Cellular::DestroySockets() { |
| if (!socket_destroyer_) |
| return; |
| |
| StopIPv6(); |
| for (const auto& address : |
| manager()->device_info()->GetAddresses(interface_index())) { |
| rtnl_handler()->RemoveInterfaceAddress(interface_index(), address); |
| socket_destroyer_->DestroySockets(IPPROTO_TCP, address); |
| } |
| } |
| |
| void Cellular::CompleteActivation(Error* error) { |
| if (capability_) |
| capability_->CompleteActivation(error); |
| } |
| |
| bool Cellular::IsUnderlyingDeviceEnabled() const { |
| return IsEnabledModemState(modem_state_); |
| } |
| |
| void Cellular::LinkEvent(unsigned int flags, unsigned int change) { |
| Device::LinkEvent(flags, change); |
| if (ppp_task_) { |
| LOG(INFO) << "Ignoring LinkEvent on device with PPP interface."; |
| return; |
| } |
| HandleLinkEvent(flags, change); |
| } |
| |
| void Cellular::Scan(Error* error, const std::string& /*reason*/) { |
| SLOG(this, 2) << "Scanning started"; |
| CHECK(error); |
| if (proposed_scan_in_progress_) { |
| Error::PopulateAndLog(FROM_HERE, error, Error::kInProgress, |
| "Already scanning"); |
| return; |
| } |
| |
| if (!capability_) |
| return; |
| |
| ResultStringmapsCallback cb = |
| base::Bind(&Cellular::OnScanReply, weak_ptr_factory_.GetWeakPtr()); |
| capability_->Scan(error, cb); |
| // An immediate failure in |cabapility_->Scan(...)| is indicated through the |
| // |error| argument. |
| if (error->IsFailure()) |
| return; |
| |
| proposed_scan_in_progress_ = true; |
| UpdateScanning(); |
| } |
| |
| void Cellular::RegisterOnNetwork(const std::string& network_id, |
| Error* error, |
| const ResultCallback& callback) { |
| if (!capability_) |
| callback.Run(Error(Error::Type::kOperationFailed)); |
| capability_->RegisterOnNetwork(network_id, error, callback); |
| } |
| |
| void Cellular::RequirePin(const std::string& pin, |
| bool require, |
| Error* error, |
| const ResultCallback& callback) { |
| SLOG(this, 2) << __func__ << "(" << require << ")"; |
| if (!capability_) |
| callback.Run(Error(Error::Type::kOperationFailed)); |
| capability_->RequirePin(pin, require, error, callback); |
| } |
| |
| void Cellular::EnterPin(const std::string& pin, |
| Error* error, |
| const ResultCallback& callback) { |
| SLOG(this, 2) << __func__; |
| if (!capability_) |
| callback.Run(Error(Error::Type::kOperationFailed)); |
| capability_->EnterPin(pin, error, callback); |
| } |
| |
| void Cellular::UnblockPin(const std::string& unblock_code, |
| const std::string& pin, |
| Error* error, |
| const ResultCallback& callback) { |
| SLOG(this, 2) << __func__; |
| if (!capability_) |
| callback.Run(Error(Error::Type::kOperationFailed)); |
| capability_->UnblockPin(unblock_code, pin, error, callback); |
| } |
| |
| void Cellular::ChangePin(const std::string& old_pin, |
| const std::string& new_pin, |
| Error* error, |
| const ResultCallback& callback) { |
| SLOG(this, 2) << __func__; |
| if (!capability_) |
| callback.Run(Error(Error::Type::kOperationFailed)); |
| capability_->ChangePin(old_pin, new_pin, error, callback); |
| } |
| |
| void Cellular::Reset(Error* error, const ResultCallback& callback) { |
| SLOG(this, 2) << __func__; |
| |
| // Qualcomm q6v5 modems on trogdor do not support reset using qmi messages. |
| // As per QC the only way to reset the modem is to use the sysfs interface. |
| if (IsQ6V5Modem()) { |
| if (!ResetQ6V5Modem()) { |
| callback.Run(Error(Error::Type::kOperationFailed)); |
| } else { |
| callback.Run(Error(Error::Type::kSuccess)); |
| } |
| return; |
| } |
| |
| if (!capability_) |
| callback.Run(Error(Error::Type::kOperationFailed)); |
| capability_->Reset(error, callback); |
| } |
| |
| void Cellular::DropConnection() { |
| if (ppp_device_) { |
| // For PPP dongles, IP configuration is handled on the |ppp_device_|, |
| // rather than the netdev plumbed into |this|. |
| ppp_device_->DropConnection(); |
| } else { |
| Device::DropConnection(); |
| } |
| } |
| |
| void Cellular::SetServiceState(Service::ConnectState state) { |
| if (ppp_device_) { |
| ppp_device_->SetServiceState(state); |
| } else if (selected_service()) { |
| Device::SetServiceState(state); |
| } else if (service_) { |
| service_->SetState(state); |
| } else { |
| LOG(WARNING) << "State change with no Service."; |
| } |
| } |
| |
| void Cellular::SetServiceFailure(Service::ConnectFailure failure_state) { |
| LOG(WARNING) << __func__ << ": " |
| << Service::ConnectFailureToString(failure_state); |
| if (ppp_device_) { |
| ppp_device_->SetServiceFailure(failure_state); |
| } else if (selected_service()) { |
| Device::SetServiceFailure(failure_state); |
| } else if (service_) { |
| service_->SetFailure(failure_state); |
| } else { |
| LOG(WARNING) << "State change with no Service."; |
| } |
| } |
| |
| void Cellular::SetServiceFailureSilent(Service::ConnectFailure failure_state) { |
| SLOG(this, 2) << __func__ << ": " |
| << Service::ConnectFailureToString(failure_state); |
| if (ppp_device_) { |
| ppp_device_->SetServiceFailureSilent(failure_state); |
| } else if (selected_service()) { |
| Device::SetServiceFailureSilent(failure_state); |
| } else if (service_) { |
| service_->SetFailureSilent(failure_state); |
| } else { |
| LOG(WARNING) << "State change with no Service."; |
| } |
| } |
| |
| void Cellular::OnConnected() { |
| if (StateIsConnected()) { |
| SLOG(this, 1) << __func__ << ": Already connected"; |
| return; |
| } |
| SLOG(this, 1) << __func__; |
| SetState(State::kConnected); |
| if (!service_) { |
| LOG(INFO) << "Disconnecting due to no cellular service."; |
| Disconnect(nullptr, "no cellular service"); |
| } else if (service_->IsRoamingRuleViolated()) { |
| // TODO(pholla): This logic is probably unreachable since we have two gate |
| // keepers that prevent this scenario. |
| // a) Cellular::Connect prevents connects if roaming rules are violated. |
| // b) CellularCapability3gpp::FillConnectPropertyMap will not allow MM to |
| // connect to roaming networks. |
| LOG(INFO) << "Disconnecting due to roaming."; |
| Disconnect(nullptr, "roaming disallowed"); |
| } else { |
| EstablishLink(); |
| } |
| } |
| |
| void Cellular::OnBeforeSuspend(const ResultCallback& callback) { |
| LOG(INFO) << __func__; |
| Error error; |
| StopPPP(); |
| capability_->SetModemToLowPowerModeOnModemStop(true); |
| SetEnabledNonPersistent(false, &error, callback); |
| if (error.IsFailure() && error.type() != Error::kInProgress) { |
| // If we fail to disable the modem right away, proceed instead of wasting |
| // the time to wait for the suspend/termination delay to expire. |
| LOG(WARNING) << "Proceed with suspend/termination even though the modem " |
| << "is not yet disabled: " << error; |
| callback.Run(error); |
| } |
| } |
| |
| void Cellular::OnAfterResume() { |
| SLOG(this, 2) << __func__; |
| if (enabled_persistent()) { |
| LOG(INFO) << "Restarting modem after resume."; |
| |
| Error error; |
| SetEnabledUnchecked(true, &error, base::Bind(LogRestartModemResult)); |
| if (error.IsSuccess()) { |
| LOG(INFO) << "Modem restart completed immediately."; |
| } else if (error.IsOngoing()) { |
| LOG(INFO) << "Modem restart in progress."; |
| } else { |
| LOG(WARNING) << "Modem restart failed: " << error; |
| } |
| } |
| |
| // Re-enable IPv6 so we can renegotiate an IP address. |
| StartIPv6(); |
| |
| // TODO(quiche): Consider if this should be conditional. If, e.g., |
| // the device was still disabling when we suspended, will trying to |
| // renew DHCP here cause problems? |
| Device::OnAfterResume(); |
| } |
| |
| std::vector<GeolocationInfo> Cellular::GetGeolocationObjects() const { |
| const std::string& mcc = location_info_.mcc; |
| const std::string& mnc = location_info_.mnc; |
| const std::string& lac = location_info_.lac; |
| const std::string& cid = location_info_.ci; |
| |
| GeolocationInfo geolocation_info; |
| |
| if (!(mcc.empty() || mnc.empty() || lac.empty() || cid.empty())) { |
| geolocation_info[kGeoMobileCountryCodeProperty] = mcc; |
| geolocation_info[kGeoMobileNetworkCodeProperty] = mnc; |
| geolocation_info[kGeoLocationAreaCodeProperty] = lac; |
| geolocation_info[kGeoCellIdProperty] = cid; |
| // kGeoTimingAdvanceProperty currently unused in geolocation API |
| } |
| // Else we have either an incomplete location, no location yet, |
| // or some unsupported location type, so don't return something incorrect. |
| |
| return {geolocation_info}; |
| } |
| |
| void Cellular::ReAttach() { |
| SLOG(this, 1) << __func__; |
| if (!enabled() && !enabled_pending()) { |
| LOG(WARNING) << __func__ << " Modem not enabled, skipped re-attach."; |
| return; |
| } |
| |
| capability_->SetModemToLowPowerModeOnModemStop(false); |
| Error error; |
| SetEnabledNonPersistent(false, &error, |
| base::Bind(&Cellular::ReAttachOnDetachComplete, |
| weak_ptr_factory_.GetWeakPtr())); |
| if (error.IsFailure() && error.type() != Error::kInProgress) { |
| LOG(WARNING) << __func__ << " Detaching the modem failed: " << error; |
| // Reset the flag to its default value. |
| capability_->SetModemToLowPowerModeOnModemStop(true); |
| } |
| } |
| |
| void Cellular::ReAttachOnDetachComplete(const Error&) { |
| Error error; |
| SLOG(this, 2) << __func__; |
| // Reset the flag to its default value. |
| capability_->SetModemToLowPowerModeOnModemStop(true); |
| |
| SetEnabledUnchecked(true, &error, base::Bind(LogRestartModemResult)); |
| if (error.IsFailure() && !error.IsOngoing()) |
| LOG(WARNING) << "Modem restart completed immediately."; |
| } |
| |
| void Cellular::CancelPendingConnect() { |
| ConnectToPendingFailed(Service::kFailureDisconnect); |
| } |
| |
| void Cellular::OnScanReply(const Stringmaps& found_networks, |
| const Error& error) { |
| SLOG(this, 2) << "Scanning completed"; |
| proposed_scan_in_progress_ = false; |
| UpdateScanning(); |
| |
| // TODO(jglasgow): fix error handling. |
| // At present, there is no way of notifying user of this asynchronous error. |
| if (error.IsFailure()) { |
| if (!found_networks_.empty()) |
| SetFoundNetworks(Stringmaps()); |
| return; |
| } |
| |
| SetFoundNetworks(found_networks); |
| } |
| |
| // Called from an asyc D-Bus function |
| // Relies on location handler to fetch relevant value from map |
| void Cellular::GetLocationCallback(const std::string& gpp_lac_ci_string, |
| const Error& error) { |
| // Expects string of form "MCC,MNC,LAC,CI" |
| SLOG(this, 2) << __func__ << ": " << gpp_lac_ci_string; |
| std::vector<std::string> location_vec = SplitString( |
| gpp_lac_ci_string, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); |
| if (location_vec.size() < 4) { |
| LOG(ERROR) << "Unable to parse location string " << gpp_lac_ci_string; |
| return; |
| } |
| location_info_.mcc = location_vec[0]; |
| location_info_.mnc = location_vec[1]; |
| location_info_.lac = location_vec[2]; |
| location_info_.ci = location_vec[3]; |
| |
| // Alert manager that location has been updated. |
| manager()->OnDeviceGeolocationInfoUpdated(this); |
| } |
| |
| void Cellular::PollLocationTask() { |
| SLOG(this, 4) << __func__; |
| |
| PollLocation(); |
| |
| dispatcher()->PostDelayedTask(FROM_HERE, poll_location_task_.callback(), |
| kPollLocationIntervalMilliseconds); |
| } |
| |
| void Cellular::PollLocation() { |
| if (!capability_) |
| return; |
| StringCallback cb = base::Bind(&Cellular::GetLocationCallback, |
| weak_ptr_factory_.GetWeakPtr()); |
| capability_->GetLocation(cb); |
| } |
| |
| void Cellular::HandleNewSignalQuality(uint32_t strength) { |
| SLOG(this, 2) << "Signal strength: " << strength; |
| if (service_) { |
| service_->SetStrength(strength); |
| } |
| } |
| |
| void Cellular::HandleNewRegistrationState() { |
| SLOG(this, 2) << __func__ << ": state = " << GetStateString(state_); |
| |
| CHECK(capability_); |
| if (!capability_->IsRegistered()) { |
| if (!explicit_disconnect_ && StateIsConnected() && service_.get()) { |
| metrics()->NotifyCellularDeviceDrop( |
| capability_->GetNetworkTechnologyString(), service_->strength()); |
| } |
| if (StateIsRegistered()) { |
| // If the state is moving out of Connected/Linked clean up IP/networking. |
| OnDisconnected(); |
| SetState(State::kEnabled); |
| } |
| StopLocationPolling(); |
| return; |
| } |
| |
| switch (state_) { |
| case State::kDisabled: |
| case State::kModemStarting: |
| case State::kModemStopping: |
| // Defer updating Services while disabled and during transitions. |
| return; |
| case State::kEnabled: |
| LOG(WARNING) << "Capability is registered but State=Enabled. Setting to " |
| "Registered. ModemState=" |
| << GetModemStateString(modem_state_); |
| SetRegistered(); |
| break; |
| case State::kModemStarted: |
| SetRegistered(); |
| break; |
| case State::kRegistered: |
| case State::kConnected: |
| case State::kLinked: |
| // Already registered |
| break; |
| } |
| |
| UpdateServices(); |
| } |
| |
| void Cellular::SetRegistered() { |
| DCHECK(!StateIsRegistered()); |
| SetState(State::kRegistered); |
| // Once the modem becomes registered, begin polling location; registered means |
| // we've successfully connected |
| StartLocationPolling(); |
| } |
| |
| void Cellular::UpdateServices() { |
| SLOG(this, 2) << __func__; |
| // When Disabled, ensure all services are destroyed except when ModemState is: |
| // * Locked: The primary SIM is locked and the modem has not started. |
| // * Failed: No valid SIM in the primary slot. |
| // In these cases we want to create any services we know about for the UI. |
| if (state_ == State::kDisabled && modem_state_ != kModemStateLocked && |
| modem_state_ != kModemStateFailed) { |
| DestroyAllServices(); |
| return; |
| } |
| |
| // If iccid_ is empty, the primary slot is not set, so do not create a |
| // primary service. CreateSecondaryServices() will have been called in |
| // SetSimProperties(). Just ensure that the Services are updated. |
| if (iccid_.empty()) { |
| manager()->cellular_service_provider()->UpdateServices(this); |
| return; |
| } |
| |
| // Ensure that a Service matching the Device SIM Profile exists and has its |
| // |connectable_| property set correctly. |
| if (!service_ || service_->iccid() != iccid_) { |
| CreateServices(); |
| } else { |
| manager()->cellular_service_provider()->UpdateServices(this); |
| } |
| |
| if (state_ == State::kRegistered && modem_state_ == kModemStateConnected) |
| OnConnected(); |
| |
| service_->SetNetworkTechnology(capability_->GetNetworkTechnologyString()); |
| service_->SetRoamingState(capability_->GetRoamingStateString()); |
| manager()->UpdateService(service_); |
| ConnectToPending(); |
| } |
| |
| void Cellular::CreateServices() { |
| if (service_for_testing_) |
| return; |
| |
| if (service_ && service_->iccid() == iccid_) { |
| LOG(ERROR) << __func__ << ": Service already exists for ICCID."; |
| return; |
| } |
| |
| CHECK(capability_); |
| DCHECK(manager()->cellular_service_provider()); |
| |
| // Create or update Cellular Services for the primary SIM. |
| service_ = |
| manager()->cellular_service_provider()->LoadServicesForDevice(this); |
| LOG(INFO) << __func__ << ": Service=" << service_->log_name(); |
| |
| // Create or update Cellular Services for secondary SIMs. |
| UpdateSecondaryServices(); |
| |
| capability_->OnServiceCreated(); |
| |
| // Ensure operator properties are updated. |
| OnOperatorChanged(); |
| } |
| |
| void Cellular::DestroyAllServices() { |
| LOG(INFO) << __func__; |
| DropConnection(); |
| |
| if (service_for_testing_) |
| return; |
| |
| DCHECK(manager()->cellular_service_provider()); |
| manager()->cellular_service_provider()->RemoveServices(); |
| service_ = nullptr; |
| } |
| |
| void Cellular::UpdateSecondaryServices() { |
| for (const SimProperties& sim_properties : sim_slot_properties_) { |
| if (sim_properties.iccid.empty() || sim_properties.iccid == iccid_) |
| continue; |
| manager()->cellular_service_provider()->LoadServicesForSecondarySim( |
| sim_properties.eid, sim_properties.iccid, sim_properties.imsi, this); |
| } |
| |
| // Remove any Services no longer associated with a SIM slot. |
| manager()->cellular_service_provider()->RemoveNonDeviceServices(this); |
| } |
| |
| void Cellular::OnModemDestroyed() { |
| StopLocationPolling(); |
| DestroyCapability(); |
| |
| // Under certain conditions, Cellular::StopModem may not be called before |
| // the Modem object is destroyed. This happens if the dbus modem exported |
| // by the modem-manager daemon disappears soon after the modem is disabled, |
| // not giving Shill enough time to complete the disable operation. |
| // In that case, the termination action associated with this cellular object |
| // may not have been removed. |
| manager()->RemoveTerminationAction(link_name()); |
| } |
| |
| void Cellular::CreateCapability() { |
| SLOG(this, 1) << __func__; |
| CHECK(!capability_); |
| capability_ = CellularCapability::Create( |
| type_, this, manager()->control_interface(), manager()->metrics(), |
| manager()->modem_info()->pending_activation_store()); |
| |
| home_provider_info_->AddObserver(this); |
| serving_operator_info_->AddObserver(this); |
| |
| // If Cellular::Start has not been called, or Cellular::Stop has been called, |
| // we still want to create the capability, but not call StartModem. |
| if (state_ == State::kModemStopping || state_ == State::kDisabled) |
| return; |
| |
| StartModem(/*error=*/nullptr, base::DoNothing()); |
| } |
| |
| void Cellular::DestroyCapability() { |
| SLOG(this, 1) << __func__; |
| |
| home_provider_info_->RemoveObserver(this); |
| serving_operator_info_->RemoveObserver(this); |
| // When there is a SIM swap, ModemManager destroys and creates a new modem |
| // object. Reset the mobile operator info to avoid stale data. |
| home_provider_info()->Reset(); |
| serving_operator_info()->Reset(); |
| |
| // Make sure we are disconnected. |
| StopPPP(); |
| DisconnectCleanup(); |
| |
| // |service_| holds a pointer to |this|. We need to disassociate it here so |
| // that |this| will be destroyed if the interface is removed. |
| if (service_) { |
| service_->SetDevice(nullptr); |
| service_ = nullptr; |
| } |
| |
| capability_.reset(); |
| |
| if (state_ != State::kDisabled) |
| SetState(State::kEnabled); |
| SetModemState(kModemStateUnknown); |
| } |
| |
| bool Cellular::GetConnectable(CellularService* service) const { |
| // Check |iccid_| in case sim_slot_properties_ have not been set. |
| if (service->iccid() == iccid_) |
| return true; |
| // If the Service ICCID matches the ICCID in any slot, that Service can be |
| // connected to (by changing the active slot if necessary). |
| for (const SimProperties& sim_properties : sim_slot_properties_) { |
| if (sim_properties.iccid == service->iccid()) |
| return true; |
| } |
| return false; |
| } |
| |
| void Cellular::NotifyCellularConnectionResult(const Error& error, |
| const std::string& iccid, |
| bool is_user_triggered) { |
| SLOG(this, 3) << __func__ << ": Result: " << error.type(); |
| // Don't report successive failures on the same SIM when the `Connect` is |
| // triggered by `AutoConnect`, and the failures are the same. |
| if (error.type() != Error::kSuccess && !is_user_triggered && |
| last_cellular_connection_results_.count(iccid) > 0 && |
| error.type() == last_cellular_connection_results_[iccid]) { |
| SLOG(this, 3) << " Skipping repetitive failure metric. Error: " |
| << error.message(); |
| return; |
| } |
| metrics()->NotifyCellularConnectionResult(error.type()); |
| last_cellular_connection_results_[iccid] = error.type(); |
| } |
| |
| void Cellular::Connect(CellularService* service, Error* error) { |
| CHECK(service); |
| LOG(INFO) << __func__ << ": " << service->log_name(); |
| |
| if (!capability_) { |
| Error::PopulateAndLog(FROM_HERE, error, Error::kWrongState, |
| "Connect Failed: Modem not available."); |
| NotifyCellularConnectionResult(*error, service->iccid(), |
| service_->is_in_user_connect()); |
| return; |
| } |
| |
| if (inhibited_) { |
| Error::PopulateAndLog(FROM_HERE, error, Error::kWrongState, |
| "Connect Failed: Inhibited."); |
| NotifyCellularConnectionResult(*error, service->iccid(), |
| service_->is_in_user_connect()); |
| return; |
| } |
| |
| if (!connect_pending_iccid_.empty() && |
| connect_pending_iccid_ == service->iccid()) { |
| Error error_temp = Error(Error::kWrongState, "Connect Failed: Inhibited."); |
| LOG(WARNING) << error_temp.message(); |
| NotifyCellularConnectionResult(error_temp, service->iccid(), |
| service_->is_in_user_connect()); |
| return; |
| } |
| |
| if (scanning_) { |
| LOG(INFO) << "Cellular is scanning. Pending connect to: " |
| << service->log_name(); |
| SetPendingConnect(service->iccid()); |
| return; |
| } |
| |
| if (service->iccid() != iccid_) { |
| // If the Service has a different ICCID than the current one, Disconnect |
| // from the current Service if connected, switch to the correct SIM slot, |
| // and set |connect_pending_iccid_|. The Connect will be retried after the |
| // slot change completes (which may take a while). |
| if (StateIsConnected()) |
| Disconnect(nullptr, "switching service"); |
| if (!sim_slot_switch_allowed_) { |
| LOG(INFO) << "sim_slot_switch_allowed -> true"; |
| sim_slot_switch_allowed_ = true; |
| } |
| if (capability_->SetPrimarySimSlotForIccid(service->iccid())) { |
| SetPendingConnect(service->iccid()); |
| } else { |
| Error::PopulateAndLog(FROM_HERE, error, Error::kOperationFailed, |
| "Connect Failed: ICCID not available."); |
| NotifyCellularConnectionResult(*error, service->iccid(), |
| service_->is_in_user_connect()); |
| } |
| return; |
| } |
| |
| if (!StateIsStarted()) { |
| Error::PopulateAndLog(FROM_HERE, error, Error::kOperationFailed, |
| "Connect Failed: Modem not started."); |
| NotifyCellularConnectionResult(*error, service->iccid(), |
| service_->is_in_user_connect()); |
| return; |
| } |
| |
| if (StateIsConnected()) { |
| Error::PopulateAndLog(FROM_HERE, error, Error::kAlreadyConnected, |
| "Already connected; connection request ignored."); |
| NotifyCellularConnectionResult(*error, service->iccid(), |
| service_->is_in_user_connect()); |
| return; |
| } else if (state_ != State::kRegistered) { |
| LOG(ERROR) << "Connect attempted while state = " << GetStateString(state_); |
| Error::PopulateAndLog(FROM_HERE, error, Error::kNotRegistered, |
| "Connect Failed: Modem not registered."); |
| NotifyCellularConnectionResult(*error, service->iccid(), |
| service_->is_in_user_connect()); |
| return; |
| } |
| |
| if (service->IsRoamingRuleViolated()) { |
| Error::PopulateAndLog(FROM_HERE, error, Error::kNotOnHomeNetwork, |
| "Connect Failed: Roaming disallowed."); |
| NotifyCellularConnectionResult(*error, service->iccid(), |
| service_->is_in_user_connect()); |
| return; |
| } |
| |
| OnConnecting(); |
| capability_->Connect( |
| base::Bind(&Cellular::OnConnectReply, weak_ptr_factory_.GetWeakPtr(), |
| service->iccid(), service_->is_in_user_connect())); |
| |
| metrics()->NotifyDeviceConnectStarted(interface_index()); |
| } |
| |
| // Note that there's no ResultCallback argument to this since Connect() isn't |
| // yet passed one. |
| void Cellular::OnConnectReply(std::string iccid, |
| bool is_user_triggered, |
| const Error& error) { |
| NotifyCellularConnectionResult(error, iccid, is_user_triggered); |
| if (!error.IsSuccess()) { |
| LOG(WARNING) << __func__ << ": Failed: " << error; |
| if (service_ && service_->iccid() == iccid) |
| service_->SetFailure(Service::kFailureConnect); |
| return; |
| } |
| metrics()->NotifyDeviceConnectFinished(interface_index()); |
| OnConnected(); |
| } |
| |
| void Cellular::OnEnabled() { |
| SLOG(this, 1) << __func__; |
| manager()->AddTerminationAction( |
| link_name(), |
| base::Bind(&Cellular::StartTermination, weak_ptr_factory_.GetWeakPtr())); |
| if (!enabled() && !enabled_pending()) { |
| LOG(WARNING) << "OnEnabled called while not enabling, setting enabled."; |
| SetEnabled(true); |
| } |
| } |
| |
| void Cellular::OnConnecting() { |
| if (service_) |
| service_->SetState(Service::kStateAssociating); |
| } |
| |
| void Cellular::Disconnect(Error* error, const char* reason) { |
| SLOG(this, 1) << __func__ << ": " << reason; |
| if (!StateIsConnected()) { |
| Error::PopulateAndLog(FROM_HERE, error, Error::kNotConnected, |
| "Not connected; request ignored."); |
| return; |
| } |
| if (!capability_) { |
| Error::PopulateAndLog(FROM_HERE, error, Error::kOperationFailed, |
| "Modem not available."); |
| return; |
| } |
| StopPPP(); |
| explicit_disconnect_ = true; |
| ResultCallback cb = |
| base::Bind(&Cellular::OnDisconnectReply, weak_ptr_factory_.GetWeakPtr()); |
| capability_->Disconnect(cb); |
| } |
| |
| void Cellular::OnDisconnectReply(const Error& error) { |
| explicit_disconnect_ = false; |
| if (!error.IsSuccess()) { |
| LOG(WARNING) << __func__ << ": Failed: " << error; |
| OnDisconnectFailed(); |
| return; |
| } |
| OnDisconnected(); |
| } |
| |
| void Cellular::OnDisconnected() { |
| SLOG(this, 1) << __func__; |
| if (!DisconnectCleanup()) { |
| LOG(WARNING) << "Disconnect occurred while in state " |
| << GetStateString(state_); |
| } |
| } |
| |
| void Cellular::OnDisconnectFailed() { |
| SLOG(this, 1) << __func__; |
| // If the modem is in the disconnecting state, then the disconnect should |
| // eventually succeed, so do nothing. |
| if (modem_state_ == kModemStateDisconnecting) { |
| LOG(INFO) << "Ignoring failed disconnect while modem is disconnecting."; |
| return; |
| } |
| |
| // OnDisconnectFailed got called because no bearers to disconnect were found. |
| // Which means that we shouldn't really remain in the connected/linked state |
| // if we are in one of those. |
| if (!DisconnectCleanup()) { |
| // otherwise, no-op |
| LOG(WARNING) << "Ignoring failed disconnect while in state " |
| << GetStateString(state_); |
| } |
| |
| // TODO(armansito): In either case, shill ends up thinking that it's |
| // disconnected, while for some reason the underlying modem might still |
| // actually be connected. In that case the UI would be reflecting an incorrect |
| // state and a further connection request would fail. We should perhaps tear |
| // down the modem and restart it here. |
| } |
| |
| void Cellular::EstablishLink() { |
| SLOG(this, 2) << __func__; |
| CHECK_EQ(State::kConnected, state_); |
| CHECK(capability_); |
| |
| CellularBearer* bearer = capability_->GetActiveBearer(); |
| if (bearer && bearer->ipv4_config_method() == IPConfig::kMethodPPP) { |
| LOG(INFO) << "Start PPP connection on " << bearer->data_interface(); |
| StartPPP(bearer->data_interface()); |
| return; |
| } |
| |
| unsigned int flags = 0; |
| if (manager()->device_info()->GetFlags(interface_index(), &flags) && |
| (flags & IFF_UP) != 0) { |
| LinkEvent(flags, IFF_UP); |
| return; |
| } |
| // TODO(petkov): Provide a timeout for a failed link-up request. |
| rtnl_handler()->SetInterfaceFlags(interface_index(), IFF_UP, IFF_UP); |
| |
| // Set state to associating. |
| OnConnecting(); |
| } |
| |
| void Cellular::HandleLinkEvent(unsigned int flags, unsigned int change) { |
| if ((flags & IFF_UP) != 0 && state_ == State::kConnected) { |
| LOG(INFO) << link_name() << " is up."; |
| SetState(State::kLinked); |
| |
| // b/182524993, b/185750211 - Currently we only support 1 config method |
| // (either IPv4 or IPv6) per bearer. On IPv4 only and IPv6 only network, |
| // we will pick the corresponding method from the bearer. For dual stack |
| // networks, IPv4 config will be used here and Ipv6 config will be |
| // populated using the kernel path. |
| CHECK(capability_); |
| CellularBearer* bearer = capability_->GetActiveBearer(); |
| if (bearer && bearer->ipv4_config_method() == IPConfig::kMethodStatic) { |
| SLOG(this, 2) << "Assign static IP configuration from bearer."; |
| SelectService(service_); |
| SetServiceState(Service::kStateConfiguring); |
| // Override the MTU with a given limit for a specific serving operator |
| // if the network doesn't report something lower. |
| IPConfig::Properties properties = *bearer->ipv4_config_properties(); |
| if (serving_operator_info_ && |
| serving_operator_info_->mtu() != IPConfig::kUndefinedMTU && |
| (properties.mtu == IPConfig::kUndefinedMTU || |
| serving_operator_info_->mtu() < properties.mtu)) { |
| properties.mtu = serving_operator_info_->mtu(); |
| } |
| AssignIPConfig(properties); |
| return; |
| } |
| |
| if (bearer && bearer->ipv6_config_method() == IPConfig::kMethodStatic) { |
| LOG(INFO) << "Assign static IPv6 configuration from bearer."; |
| SelectService(service_); |
| SetServiceState(Service::kStateConfiguring); |
| IPConfig::Properties properties = *bearer->ipv6_config_properties(); |
| // TODO(b:176060170): Combine values from IPv6 as well.. |
| AssignIPv6Config(properties); |
| return; |
| } |
| |
| if (AcquireIPConfig()) { |
| SLOG(this, 2) << "Start DHCP to acquire IP configuration."; |
| SelectService(service_); |
| SetServiceState(Service::kStateConfiguring); |
| return; |
| } |
| |
| LOG(ERROR) << "Unable to acquire IP configuration over DHCP."; |
| return; |
| } |
| |
| if ((flags & IFF_UP) == 0 && state_ == State::kLinked) { |
| LOG(INFO) << link_name() << " is down."; |
| DestroyAllServices(); |
| } |
| } |
| |
| void Cellular::SetInitialProperties(const InterfaceToProperties& properties) { |
| CHECK(capability_); |
| capability_->SetInitialProperties(properties); |
| } |
| |
| void Cellular::OnModemStateChanged(ModemState new_state) { |
| ModemState old_modem_state = modem_state_; |
| if (old_modem_state == new_state) { |
| SLOG(this, 3) << "The new state matches the old state. Nothing to do."; |
| return; |
| } |
| |
| SLOG(this, 1) << __func__ << " State: " << GetStateString(state_) |
| << " ModemState: " << GetModemStateString(new_state); |
| SetModemState(new_state); |
| CHECK(capability_); |
| |
| if (old_modem_state >= kModemStateRegistered && |
| modem_state_ < kModemStateRegistered) { |
| if (state_ == State::kModemStarting) { |
| // Avoid un-registering the modem while the Capability is starting the |
| // Modem to prevent unexpected spurious state changes. |
| // TODO(stevenjb): Audit logs and remove or tighten this logic. |
| LOG(WARNING) << "Modem state change while capability starting, " |
| << " ModemState: " << GetModemStateString(new_state); |
| } else { |
| capability_->SetUnregistered(modem_state_ == kModemStateSearching); |
| HandleNewRegistrationState(); |
| } |
| } |
| |
| if (old_modem_state < kModemStateEnabled && |
| modem_state_ >= kModemStateEnabled) { |
| // Just became enabled, update enabled state. |
| OnEnabled(); |
| } |
| |
| switch (modem_state_) { |
| case kModemStateFailed: |
| case kModemStateUnknown: |
| case kModemStateInitializing: |
| case kModemStateLocked: |
| break; |
| case kModemStateDisabled: |
| // When the Modem becomes disabled, Cellular is not necessarily disabled. |
| // This may occur after a SIM swap or eSIM profile change. Ensure that |
| // the Modem is started. |
| if (state_ == State::kEnabled) |
| StartModem(/*error=*/nullptr, base::DoNothing()); |
| break; |
| case kModemStateDisabling: |
| case kModemStateEnabling: |
| break; |
| case kModemStateEnabled: |
| case kModemStateSearching: |
| case kModemStateRegistered: |
| if (old_modem_state == kModemStateConnected || |
| old_modem_state == kModemStateConnecting || |
| old_modem_state == kModemStateDisconnecting) { |
| OnDisconnected(); |
| } |
| break; |
| case kModemStateDisconnecting: |
| break; |
| case kModemStateConnecting: |
| OnConnecting(); |
| break; |
| case kModemStateConnected: |
| if (old_modem_state == kModemStateConnecting) |
| OnConnected(); |
| break; |
| } |
| } |
| |
| bool Cellular::IsActivating() const { |
| return capability_ && capability_->IsActivating(); |
| } |
| |
| bool Cellular::GetAllowRoaming(Error* /*error*/) { |
| return service_ ? service_->GetAllowRoaming() : allow_roaming_; |
| } |
| |
| bool Cellular::SetAllowRoaming(const bool& value, Error* error) { |
| if (allow_roaming_ == value) |
| return false; |
| |
| LOG(INFO) << __func__ << ": " << allow_roaming_ << "->" << value; |
| |
| allow_roaming_ = value; |
| adaptor()->EmitBoolChanged(kCellularAllowRoamingProperty, value); |
| manager()->UpdateDevice(this); |
| |
| if (service_ && service_->IsRoamingRuleViolated()) { |
| Error error; |
| Disconnect(&error, __func__); |
| } |
| |
| return true; |
| } |
| |
| bool Cellular::GetPolicyAllowRoaming(Error* /*error*/) { |
| return policy_allow_roaming_; |
| } |
| |
| bool Cellular::SetPolicyAllowRoaming(const bool& value, Error* error) { |
| if (policy_allow_roaming_ == value) |
| return false; |
| |
| LOG(INFO) << __func__ << ": " << policy_allow_roaming_ << "->" << value; |
| |
| policy_allow_roaming_ = value; |
| adaptor()->EmitBoolChanged(kCellularPolicyAllowRoamingProperty, value); |
| manager()->UpdateDevice(this); |
| |
| if (service_ && service_->IsRoamingRuleViolated()) { |
| Error error; |
| Disconnect(&error, __func__); |
| } |
| |
| return true; |
| } |
| |
| bool Cellular::SetUseAttachApn(const bool& value, Error* error) { |
| if (use_attach_apn_ == value) |
| return false; |
| LOG(INFO) << __func__ << ": " << use_attach_apn_ << "->" << value; |
| |
| use_attach_apn_ = value; |
| |
| if (capability_) { |
| // We need to detach and re-attach to the LTE network in order to use the |
| // attach APN. |
| ReAttach(); |
| } |
| |
| adaptor()->EmitBoolChanged(kUseAttachAPNProperty, value); |
| return true; |
| } |
| |
| bool Cellular::GetInhibited(Error* error) { |
| return inhibited_; |
| } |
| |
| bool Cellular::SetInhibited(const bool& inhibited, Error* error) { |
| if (inhibited == inhibited_) { |
| LOG(WARNING) << __func__ << ": State already set, ignoring request."; |
| return false; |
| } |
| LOG(INFO) << __func__ << ": " << inhibited; |
| |
| // Clear any pending connect when inhibited changes. |
| SetPendingConnect(std::string()); |
| |
| inhibited_ = inhibited; |
| |
| // Update and emit Scanning before Inhibited. This allows the UI to wait for |
| // Scanning to be false once Inhibit changes to know when an Inhibit operation |
| // completes. UpdateScanning will call ConnectToPending if Scanning is false. |
| UpdateScanning(); |
| adaptor()->EmitBoolChanged(kInhibitedProperty, inhibited_); |
| |
| return true; |
| } |
| |
| KeyValueStore Cellular::GetSimLockStatus(Error* error) { |
| if (!capability_) { |
| // modemmanager might be inhibited or restarting. |
| LOG(ERROR) << __func__ << " called with null capability."; |
| return KeyValueStore(); |
| } |
| return capability_->SimLockStatusToProperty(error); |
| } |
| |
| void Cellular::SetSimPresent(bool sim_present) { |
| if (sim_present_ == sim_present) |
| return; |
| |
| sim_present_ = sim_present; |
| adaptor()->EmitBoolChanged(kSIMPresentProperty, sim_present_); |
| } |
| |
| void Cellular::StartTermination() { |
| SLOG(this, 2) << __func__; |
| OnBeforeSuspend(base::Bind(&Cellular::OnTerminationCompleted, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void Cellular::OnTerminationCompleted(const Error& error) { |
| LOG(INFO) << __func__ << ": " << error; |
| manager()->TerminationActionComplete(link_name()); |
| manager()->RemoveTerminationAction(link_name()); |
| } |
| |
| bool Cellular::DisconnectCleanup() { |
| if (!StateIsConnected()) |
| return false; |
| SetState(State::kRegistered); |
| SetServiceFailureSilent(Service::kFailureNone); |
| DestroyIPConfig(); |
| return true; |
| } |
| |
| // static |
| void Cellular::LogRestartModemResult(const Error& error) { |
| if (error.IsSuccess()) { |
| LOG(INFO) << "Modem restart completed."; |
| } else { |
| LOG(WARNING) << "Attempt to restart modem failed: " << error; |
| } |
| } |
| |
| bool Cellular::ResetQ6V5Modem() { |
| // TODO(b/177375637): Check for q6v5 driver before resetting the modem. |
| int fd = HANDLE_EINTR( |
| open(kModemResetSysfsName, O_WRONLY | O_NONBLOCK | O_CLOEXEC)); |
| if (fd < 0) { |
| PLOG(ERROR) << "Failed to open sysfs file to reset modem."; |
| return false; |
| } |
| |
| base::ScopedFD scoped_fd(fd); |
| if (!base::WriteFileDescriptor(scoped_fd.get(), "stop")) { |
| PLOG(ERROR) << "Failed to stop modem"; |
| return false; |
| } |
| usleep(kModemResetTimeoutMilliseconds * 1000); |
| if (!base::WriteFileDescriptor(scoped_fd.get(), "start")) { |
| PLOG(ERROR) << "Failed to start modem"; |
| return false; |
| } |
| return true; |
| } |
| |
| bool Cellular::IsQ6V5Modem() { |
| base::FilePath driver_path, driver_name; |
| |
| // Check if manufacturer is equal to "QUALCOMM INCORPORATED" and |
| // if remoteproc0/device/driver in sysfs links to "qcom-q6v5-mss". |
| driver_path = base::FilePath(kModemDriverSysfsName); |
| return (manufacturer_ == kQ6V5ModemManufacturerName && |
| base::ReadSymbolicLink(driver_path, &driver_name) && |
| driver_name.BaseName() == base::FilePath(kQ6V5DriverName)); |
| } |
| |
| void Cellular::StartPPP(const std::string& serial_device) { |
| SLOG(PPP, this, 2) << __func__ << " on " << serial_device; |
| // Detach any SelectedService from this device. It will be grafted onto |
| // the PPPDevice after PPP is up (in Cellular::Notify). |
| // |
| // This has two important effects: 1) kills dhcpcd if it is running. |
| // 2) stops Cellular::LinkEvent from driving changes to the |
| // SelectedService. |
| if (selected_service()) { |
| CHECK_EQ(service_.get(), selected_service().get()); |
| // Save and restore |service_| state, as DropConnection calls |
| // SelectService, and SelectService will move selected_service() |
| // to State::kIdle. |
| Service::ConnectState original_state(service_->state()); |
| Device::DropConnection(); // Don't redirect to PPPDevice. |
| service_->SetState(original_state); |
| } else { |
| CHECK(!ipconfig()); // Shouldn't have ipconfig without selected_service(). |
| } |
| |
| PPPDaemon::DeathCallback death_callback( |
| base::Bind(&Cellular::OnPPPDied, weak_ptr_factory_.GetWeakPtr())); |
| |
| PPPDaemon::Options options; |
| options.no_detach = true; |
| options.no_default_route = true; |
| options.use_peer_dns = true; |
| options.max_fail = 1; |
| |
| is_ppp_authenticating_ = false; |
| |
| Error error; |
| std::unique_ptr<ExternalTask> new_ppp_task(PPPDaemon::Start( |
| control_interface(), process_manager_, weak_ptr_factory_.GetWeakPtr(), |
| options, serial_device, death_callback, &error)); |
| if (new_ppp_task) { |
| SLOG(this, 1) << "Forked pppd process."; |
| ppp_task_ = std::move(new_ppp_task); |
| } |
| } |
| |
| void Cellular::StopPPP() { |
| SLOG(PPP, this, 2) << __func__; |
| if (!ppp_device_) |
| return; |
| DropConnection(); |
| ppp_task_.reset(); |
| ppp_device_ = nullptr; |
| } |
| |
| // called by |ppp_task_| |
| void Cellular::GetLogin(std::string* user, std::string* password) { |
| SLOG(PPP, this, 2) << __func__; |
| if (!service()) { |
| LOG(ERROR) << __func__ << " with no service "; |
| return; |
| } |
| CHECK(user); |
| CHECK(password); |
| *user = service()->ppp_username(); |
| *password = service()->ppp_password(); |
| } |
| |
| // Called by |ppp_task_|. |
| void Cellular::Notify(const std::string& reason, |
| const std::map<std::string, std::string>& dict) { |
| SLOG(PPP, this, 2) << __func__ << " " << reason << " on " << link_name(); |
| |
| if (reason == kPPPReasonAuthenticating) { |
| OnPPPAuthenticating(); |
| } else if (reason == kPPPReasonAuthenticated) { |
| OnPPPAuthenticated(); |
| } else if (reason == kPPPReasonConnect) { |
| OnPPPConnected(dict); |
| } else if (reason == kPPPReasonDisconnect) { |
| // Ignore; we get disconnect information when pppd exits. |
| } else { |
| NOTREACHED(); |
| } |
| } |
| |
| void Cellular::OnPPPAuthenticated() { |
| SLOG(PPP, this, 2) << __func__; |
| is_ppp_authenticating_ = false; |
| } |
| |
| void Cellular::OnPPPAuthenticating() { |
| SLOG(PPP, this, 2) << __func__; |
| is_ppp_authenticating_ = true; |
| } |
| |
| void Cellular::OnPPPConnected( |
| const std::map<std::string, std::string>& params) { |
| SLOG(PPP, this, 2) << __func__; |
| std::string interface_name = PPPDevice::GetInterfaceName(params); |
| DeviceInfo* device_info = manager()->device_info(); |
| int interface_index = device_info->GetIndex(interface_name); |
| if (interface_index < 0) { |
| // TODO(quiche): Consider handling the race when the RTNL notification about |
| // the new PPP device has not been received yet. crbug.com/246832. |
| NOTIMPLEMENTED() << ": No device info for " << interface_name << "."; |
| return; |
| } |
| |
| if (!ppp_device_ || ppp_device_->interface_index() != interface_index) { |
| if (ppp_device_) { |
| ppp_device_->SelectService(nullptr); // No longer drives |service_|. |
| // Destroy the existing device before creating a new one to avoid the |
| // possibility of multiple DBus Objects with the same interface name. |
| // See https://crbug.com/1032030 for details. |
| ppp_device_ = nullptr; |
| } |
| ppp_device_ = ppp_device_factory_->CreatePPPDevice( |
| manager(), interface_name, interface_index); |
| device_info->RegisterDevice(ppp_device_); |
| } |
| |
| CHECK(service_); |
| // For PPP, we only SelectService on the |ppp_device_|. |
| CHECK(!selected_service()); |
| ppp_device_->SetEnabled(true); |
| ppp_device_->SelectService(service_); |
| ppp_device_->UpdateIPConfigFromPPP(params, false /* blackhole_ipv6 */); |
| } |
| |
| void Cellular::OnPPPDied(pid_t pid, int exit) { |
| SLOG(this, 1) << __func__ << " on " << link_name(); |
| ppp_task_.reset(); |
| if (is_ppp_authenticating_) { |
| SetServiceFailure(Service::kFailurePPPAuth); |
| } else { |
| SetServiceFailure(PPPDevice::ExitStatusToFailure(exit)); |
| } |
| Error error; |
| Disconnect(&error, __func__); |
| } |
| |
| void Cellular::SetPendingConnect(const std::string& iccid) { |
| if (iccid == connect_pending_iccid_) |
| return; |
| |
| if (!connect_pending_iccid_.empty()) { |
| SLOG(this, 1) << "Cancelling pending connect to: " |
| << connect_pending_iccid_; |
| ConnectToPendingFailed(Service::kFailureDisconnect); |
| } |
| |
| connect_pending_callback_.Cancel(); |
| connect_pending_iccid_ = iccid; |
| |
| if (iccid.empty()) |
| return; |
| |
| SLOG(this, 1) << "Set Pending connect: " << iccid; |
| // Pending connect requests may fail, e.g. a SIM slot change may fail or |
| // registration may fail for an inactive eSIM profile. Set a timeout to |
| // cancel the pending connect and inform the UI. |
| connect_cancel_callback_.Reset(base::Bind(&Cellular::ConnectToPendingCancel, |
| weak_ptr_factory_.GetWeakPtr())); |
| dispatcher()->PostDelayedTask(FROM_HERE, connect_cancel_callback_.callback(), |
| kPendingConnectCancelMilliseconds); |
| } |
| |
| void Cellular::ConnectToPending() { |
| if (connect_pending_iccid_.empty() || |
| !connect_pending_callback_.IsCancelled()) { |
| return; |
| } |
| |
| if (inhibited_) { |
| SLOG(this, 1) << __func__ << ": Inhibited"; |
| return; |
| } |
| if (scanning_) { |
| SLOG(this, 1) << __func__ << ": Scanning"; |
| return; |
| } |
| |
| if (modem_state_ == kModemStateLocked) { |
| LOG(WARNING) << __func__ << ": Modem locked"; |
| ConnectToPendingFailed(Service::kFailureSimLocked); |
| return; |
| } |
| |
| // Normally the Modem becomes Registered immediately after becoming enabled. |
| // For eSIM this is not always true so we need to wait for the Modem to |
| // become registered. |
| // TODO(b/186482862): Fix this behavior in ModemManager. |
| if (state_ == State::kEnabled && modem_state_ == kModemStateEnabled) { |
| LOG(WARNING) << __func__ << ": Waiting for Modem registration."; |
| return; |
| } |
| if (!StateIsRegistered()) { |
| LOG(WARNING) << __func__ << ": Cellular not registered, State: " |
| << GetStateString(state_); |
| ConnectToPendingFailed(Service::kFailureNotRegistered); |
| return; |
| } |
| if (modem_state_ != kModemStateRegistered) { |
| LOG(WARNING) << __func__ << ": Modem not registered, State: " |
| << GetModemStateString(modem_state_); |
| ConnectToPendingFailed(Service::kFailureNotRegistered); |
| return; |
| } |
| |
| SLOG(this, 1) << __func__ << ": " << connect_pending_iccid_; |
| connect_cancel_callback_.Cancel(); |
| connect_pending_callback_.Reset(base::Bind( |
| &Cellular::ConnectToPendingAfterDelay, weak_ptr_factory_.GetWeakPtr())); |
| dispatcher()->PostDelayedTask(FROM_HERE, connect_pending_callback_.callback(), |
| kPendingConnectDelay.InMilliseconds()); |
| } |
| |
| void Cellular::ConnectToPendingAfterDelay() { |
| SLOG(this, 1) << __func__ << ": " << connect_pending_iccid_; |
| |
| // Clear pending connect request regardless of whether a service is found. |
| std::string pending_iccid = connect_pending_iccid_; |
| connect_pending_iccid_.clear(); |
| |
| CellularServiceRefPtr service = |
| manager()->cellular_service_provider()->FindService(pending_iccid); |
| if (!service) { |
| LOG(WARNING) << "No matching service for pending connect."; |
| return; |
| } |
| |
| Error error; |
| LOG(INFO) << "Connecting to pending Cellular Service: " |
| << service->log_name(); |
| service->Connect(&error, "Pending connect"); |
| if (!error.IsSuccess()) |
| service->SetFailure(Service::kFailureConnect); |
| } |
| |
| void Cellular::ConnectToPendingFailed(Service::ConnectFailure failure) { |
| if (!connect_pending_iccid_.empty()) { |
| SLOG(this, 1) << __func__ << ": " << connect_pending_iccid_ |
| << " Failure: " << Service::ConnectFailureToString(failure); |
| CellularServiceRefPtr service = |
| manager()->cellular_service_provider()->FindService( |
| connect_pending_iccid_); |
| if (service) |
| service->SetFailure(failure); |
| } |
| connect_cancel_callback_.Cancel(); |
| connect_pending_callback_.Cancel(); |
| connect_pending_iccid_.clear(); |
| } |
| |
| void Cellular::ConnectToPendingCancel() { |
| LOG(WARNING) << __func__; |
| ConnectToPendingFailed(Service::kFailureNotRegistered); |
| } |
| |
| void Cellular::UpdateScanning() { |
| bool scanning; |
| switch (state_) { |
| case State::kDisabled: |
| scanning = false; |
| break; |
| case State::kEnabled: |
| // Cellular is enabled, but the Modem object has not been created, or was |
| // destroyed because the Modem is Inhibited or Locked, or StartModem |
| // failed. |
| scanning = !inhibited_ && modem_state_ != kModemStateLocked && |
| modem_state_ != kModemStateFailed; |
| break; |
| case State::kModemStarting: |
| case State::kModemStopping: |
| scanning = true; |
| break; |
| case State::kModemStarted: |
| case State::kRegistered: |
| case State::kConnected: |
| case State::kLinked: |
| // When the modem is started and enabling or searching, treat as scanning. |
| // Also set scanning if an active scan is in progress. |
| scanning = modem_state_ == kModemStateEnabling || |
| modem_state_ == kModemStateSearching || |
| proposed_scan_in_progress_; |
| break; |
| } |
| SetScanning(scanning); |
| } |
| |
| void Cellular::RegisterProperties() { |
| PropertyStore* store = this->mutable_store(); |
| |
| // These properties do not have setters, and events are not generated when |
| // they are changed. |
| store->RegisterConstString(kDBusServiceProperty, &dbus_service_); |
| store->RegisterConstString(kDBusObjectProperty, &dbus_path_str_); |
| |
| store->RegisterUint16(kScanIntervalProperty, &scan_interval_); |
| |
| // These properties have setters that should be used to change their values. |
| // Events are generated whenever the values change. |
| store->RegisterConstStringmap(kHomeProviderProperty, &home_provider_); |
| store->RegisterConstBool(kSupportNetworkScanProperty, &scanning_supported_); |
| store->RegisterConstString(kEidProperty, &eid_); |
| store->RegisterConstString(kEsnProperty, &esn_); |
| store->RegisterConstString(kFirmwareRevisionProperty, &firmware_revision_); |
| store->RegisterConstString(kHardwareRevisionProperty, &hardware_revision_); |
| store->RegisterConstString(kImeiProperty, &imei_); |
| store->RegisterConstString(kImsiProperty, &imsi_); |
| store->RegisterConstString(kMdnProperty, &mdn_); |
| store->RegisterConstString(kMeidProperty, &meid_); |
| store->RegisterConstString(kMinProperty, &min_); |
| store->RegisterConstString(kManufacturerProperty, &manufacturer_); |
| store->RegisterConstString(kModelIdProperty, &model_id_); |
| store->RegisterConstString(kEquipmentIdProperty, &equipment_id_); |
| store->RegisterConstBool(kScanningProperty, &scanning_); |
| |
| store->RegisterConstString(kSelectedNetworkProperty, &selected_network_); |
| store->RegisterConstStringmaps(kFoundNetworksProperty, &found_networks_); |
| store->RegisterConstBool(kProviderRequiresRoamingProperty, |
| &provider_requires_roaming_); |
| store->RegisterConstBool(kSIMPresentProperty, &sim_present_); |
| store->RegisterConstKeyValueStores(kSIMSlotInfoProperty, &sim_slot_info_); |
| store->RegisterConstStringmaps(kCellularApnListProperty, &apn_list_); |
| store->RegisterConstString(kIccidProperty, &iccid_); |
| |
| // TODO(pprabhu): Decide whether these need their own custom setters. |
| HelpRegisterConstDerivedString(kTechnologyFamilyProperty, |
| &Cellular::GetTechnologyFamily); |
| HelpRegisterConstDerivedString(kDeviceIdProperty, &Cellular::GetDeviceId); |
| HelpRegisterDerivedBool(kCellularAllowRoamingProperty, |
| &Cellular::GetAllowRoaming, |
| &Cellular::SetAllowRoaming); |
| HelpRegisterDerivedBool(kCellularPolicyAllowRoamingProperty, |
| &Cellular::GetPolicyAllowRoaming, |
| &Cellular::SetPolicyAllowRoaming); |
| HelpRegisterDerivedBool(kUseAttachAPNProperty, &Cellular::GetUseAttachApn, |
| &Cellular::SetUseAttachApn); |
| HelpRegisterDerivedBool(kInhibitedProperty, &Cellular::GetInhibited, |
| &Cellular::SetInhibited); |
| |
| store->RegisterDerivedKeyValueStore( |
| kSIMLockStatusProperty, |
| KeyValueStoreAccessor(new CustomAccessor<Cellular, KeyValueStore>( |
| this, &Cellular::GetSimLockStatus, /*error=*/nullptr))); |
| } |
| |
| void Cellular::UpdateModemProperties(const RpcIdentifier& dbus_path, |
| const std::string& mac_address) { |
| if (dbus_path_ == dbus_path) |
| return; |
| LOG(INFO) << __func__ << " Modem Path: " << dbus_path.value(); |
| dbus_path_ = dbus_path; |
| dbus_path_str_ = dbus_path.value(); |
| adaptor()->EmitStringChanged(kDBusObjectProperty, dbus_path_str_); |
| SetModemState(kModemStateUnknown); |
| set_mac_address(mac_address); |
| CreateCapability(); |
| } |
| |
| const std::string& Cellular::GetSimCardId() const { |
| if (!eid_.empty()) |
| return eid_; |
| return iccid_; |
| } |
| |
| bool Cellular::HasSimCardId(const std::string& sim_card_id) const { |
| if (sim_card_id == eid_ || sim_card_id == iccid_) |
| return true; |
| for (const SimProperties& sim_properties : sim_slot_properties_) { |
| if (sim_properties.iccid == sim_card_id || |
| sim_properties.eid == sim_card_id) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| void Cellular::SetSimProperties( |
| const std::vector<SimProperties>& sim_properties, size_t primary_slot) { |
| LOG(INFO) << __func__ << " Slots: " << sim_properties.size() |
| << " Primary: " << primary_slot; |
| if (sim_properties.empty()) { |
| // This might occur while the Modem is starting. |
| SetPrimarySimProperties(SimProperties()); |
| SetSimSlotProperties(sim_properties, 0); |
| return; |
| } |
| if (primary_slot >= sim_properties.size()) { |
| LOG(ERROR) << "Invalid Primary Slot Id: " << primary_slot; |
| primary_slot = 0u; |
| } |
| |
| const SimProperties& primary_sim_properties = sim_properties[primary_slot]; |
| |
| // Update SIM properties for the primary SIM slot and create or update the |
| // primary Service. |
| SetPrimarySimProperties(primary_sim_properties); |
| |
| // Update the KeyValueStore for Device.Cellular.SIMSlotInfo and emit it. |
| SetSimSlotProperties(sim_properties, static_cast<int>(primary_slot)); |
| |
| // Ensure that secondary services are created and updated. |
| UpdateSecondaryServices(); |
| |
| // If the Primary SIM does not have a SIM profile available, attempt to switch |
| // to a slot with a SIM profile available. |
| if (!inhibited_ && primary_sim_properties.iccid.empty()) { |
| if (sim_slot_switch_allowed_) { |
| LOG(INFO) << "No Primary SIM properties, attempting to switch slots."; |
| // Attempt to switch to the first valid sim slot. |
| capability_->SetPrimarySimSlotForIccid(std::string()); |
| } else { |
| LOG(INFO) << "No Primary SIM properties, slot switch disabled."; |
| } |
| } |
| } |
| |
| std::deque<Stringmap> Cellular::BuildApnTryList() const { |
| std::deque<Stringmap> apn_try_list; |
| |
| const Stringmap* custom_apn_info = nullptr; |
| const Stringmap* last_good_apn_info = nullptr; |
| if (service_) { |
| custom_apn_info = service_->GetUserSpecifiedApn(); |
| if (custom_apn_info) { |
| apn_try_list.push_back(*custom_apn_info); |
| SLOG(this, 3) << __func__ << " Adding User Specified APN:" |
| << GetStringmapValue(*custom_apn_info, kApnProperty) |
| << " Is attach:" |
| << GetStringmapValue(*custom_apn_info, kApnAttachProperty); |
| } |
| last_good_apn_info = service_->GetLastGoodApn(); |
| if (last_good_apn_info && |
| (!custom_apn_info || *last_good_apn_info != *custom_apn_info)) { |
| apn_try_list.push_back(*last_good_apn_info); |
| SLOG(this, 3) << __func__ << " Adding last good APN:" |
| << GetStringmapValue(*last_good_apn_info, kApnProperty) |
| << " Is attach:" |
| << GetStringmapValue(*last_good_apn_info, |
| kApnAttachProperty); |
| } |
| } |
| |
| for (auto apn : apn_list_) { |
| if ((custom_apn_info && *custom_apn_info == apn) || |
| (last_good_apn_info && *last_good_apn_info == apn)) { |
| continue; |
| } |
| apn_try_list.push_back(apn); |
| } |
| return apn_try_list; |
| } |
| |
| void Cellular::SetScanningSupported(bool scanning_supported) { |
| if (scanning_supported_ == scanning_supported) |
| return; |
| |
| scanning_supported_ = scanning_supported; |
| adaptor()->EmitBoolChanged(kSupportNetworkScanProperty, scanning_supported_); |
| } |
| |
| void Cellular::SetEquipmentId(const std::string& equipment_id) { |
| if (equipment_id_ == equipment_id) |
| return; |
| |
| equipment_id_ = equipment_id; |
| adaptor()->EmitStringChanged(kEquipmentIdProperty, equipment_id_); |
| } |
| |
| void Cellular::SetEsn(const std::string& esn) { |
| if (esn_ == esn) |
| return; |
| |
| esn_ = esn; |
| adaptor()->EmitStringChanged(kEsnProperty, esn_); |
| } |
| |
| void Cellular::SetFirmwareRevision(const std::string& firmware_revision) { |
| if (firmware_revision_ == firmware_revision) |
| return; |
| |
| firmware_revision_ = firmware_revision; |
| adaptor()->EmitStringChanged(kFirmwareRevisionProperty, firmware_revision_); |
| } |
| |
| void Cellular::SetHardwareRevision(const std::string& hardware_revision) { |
| if (hardware_revision_ == hardware_revision) |
| return; |
| |
| hardware_revision_ = hardware_revision; |
| adaptor()->EmitStringChanged(kHardwareRevisionProperty, hardware_revision_); |
| } |
| |
| void Cellular::SetDeviceId(std::unique_ptr<DeviceId> device_id) { |
| device_id_ = std::move(device_id); |
| } |
| |
| void Cellular::SetImei(const std::string& imei) { |
| if (imei_ == imei) |
| return; |
| |
| imei_ = imei; |
| adaptor()->EmitStringChanged(kImeiProperty, imei_); |
| } |
| |
| void Cellular::SetPrimarySimProperties(const SimProperties& sim_properties) { |
| SLOG(this, 1) << __func__ << " EID= " << sim_properties.eid |
| << " ICCID= " << sim_properties.iccid |
| << " IMSI= " << sim_properties.imsi |
| << " OperatorId= " << sim_properties.operator_id |
| << " ServiceProviderName= " << sim_properties.spn; |
| |
| eid_ = sim_properties.eid; |
| iccid_ = sim_properties.iccid; |
| imsi_ = sim_properties.imsi; |
| |
| home_provider_info()->UpdateMCCMNC(sim_properties.operator_id); |
| home_provider_info()->UpdateOperatorName(sim_properties.spn); |
| home_provider_info()->UpdateICCID(iccid_); |
| // Provide ICCID to serving operator as well to aid in MVNO identification. |
| serving_operator_info()->UpdateICCID(iccid_); |
| if (!imsi_.empty()) { |
| home_provider_info()->UpdateIMSI(imsi_); |
| // We do not obtain IMSI OTA right now. Provide the value to serving |
| // operator as well, to aid in MVNO identification. |
| serving_operator_info()->UpdateIMSI(imsi_); |
| } |
| |
| adaptor()->EmitStringChanged(kEidProperty, eid_); |
| adaptor()->EmitStringChanged(kIccidProperty, iccid_); |
| adaptor()->EmitStringChanged(kImsiProperty, imsi_); |
| SetSimPresent(!iccid_.empty()); |
| |
| // Ensure Service creation once SIM properties are set. |
| UpdateServices(); |
| } |
| |
| void Cellular::SetSimSlotProperties( |
| const std::vector<SimProperties>& slot_properties, int primary_slot) { |
| if (sim_slot_properties_ == slot_properties && |
| primary_sim_slot_ == primary_slot) { |
| return; |
| } |
| SLOG(this, 1) << __func__ << " Slots: " << slot_properties.size() |
| << " Primary: " << primary_slot; |
| sim_slot_properties_ = slot_properties; |
| if (primary_sim_slot_ != primary_slot) { |
| if (primary_sim_slot_ != -1 && sim_slot_switch_allowed_) { |
| // After a slot change, do not allow Shill to change slots until/unless |
| // an explicit connect to a Service in a different slot is requested. |
| // This helps prevent Shill from interfering with Hermes operations. |
| LOG(INFO) << "sim_slot_switch_allowed -> false"; |
| sim_slot_switch_allowed_ = false; |
| } |
| primary_sim_slot_ = primary_slot; |
| } |
| // Set |sim_slot_info_| and emit SIMSlotInfo |
| sim_slot_info_.clear(); |
| for (int i = 0; i < static_cast<int>(slot_properties.size()); ++i) { |
| const SimProperties& sim_properties = slot_properties[i]; |
| KeyValueStore properties; |
| properties.Set(kSIMSlotInfoEID, sim_properties.eid); |
| properties.Set(kSIMSlotInfoICCID, sim_properties.iccid); |
| bool is_primary = i == primary_slot; |
| properties.Set(kSIMSlotInfoPrimary, is_primary); |
| sim_slot_info_.push_back(properties); |
| SLOG(this, 2) << __func__ << " Slot: " << sim_properties.slot |
| << " EID: " << sim_properties.eid |
| << " ICCID: " << sim_properties.iccid |
| << " Primary: " << is_primary; |
| } |
| adaptor()->EmitKeyValueStoresChanged(kSIMSlotInfoProperty, sim_slot_info_); |
| } |
| |
| void Cellular::SetMdn(const std::string& mdn) { |
| if (mdn_ == mdn) |
| return; |
| |
| mdn_ = mdn; |
| adaptor()->EmitStringChanged(kMdnProperty, mdn_); |
| } |
| |
| void Cellular::SetMeid(const std::string& meid) { |
| if (meid_ == meid) |
| return; |
| |
| meid_ = meid; |
| adaptor()->EmitStringChanged(kMeidProperty, meid_); |
| } |
| |
| void Cellular::SetMin(const std::string& min) { |
| if (min_ == min) |
| return; |
| |
| min_ = min; |
| adaptor()->EmitStringChanged(kMinProperty, min_); |
| } |
| |
| void Cellular::SetManufacturer(const std::string& manufacturer) { |
| if (manufacturer_ == manufacturer) |
| return; |
| |
| manufacturer_ = manufacturer; |
| adaptor()->EmitStringChanged(kManufacturerProperty, manufacturer_); |
| } |
| |
| void Cellular::SetModelId(const std::string& model_id) { |
| if (model_id_ == model_id) |
| return; |
| |
| model_id_ = model_id; |
| adaptor()->EmitStringChanged(kModelIdProperty, model_id_); |
| } |
| |
| void Cellular::SetMMPlugin(const std::string& mm_plugin) { |
| mm_plugin_ = mm_plugin; |
| } |
| |
| void Cellular::StartLocationPolling() { |
| CHECK(capability_); |
| if (!capability_->IsLocationUpdateSupported()) { |
| SLOG(this, 2) << "Location polling not enabled for " << mm_plugin_ |
| << " plugin."; |
| return; |
| } |
| |
| if (polling_location_) |
| return; |
| |
| polling_location_ = true; |
| |
| CHECK(poll_location_task_.IsCancelled()); |
| SLOG(this, 2) << __func__ << ": " |
| << "Starting location polling tasks."; |
| poll_location_task_.Reset( |
| base::Bind(&Cellular::PollLocationTask, weak_ptr_factory_.GetWeakPtr())); |
| |
| // Schedule an immediate task |
| dispatcher()->PostTask(FROM_HERE, poll_location_task_.callback()); |
| } |
| |
| void Cellular::StopLocationPolling() { |
| if (!polling_location_) |
| return; |
| polling_location_ = false; |
| |
| if (!poll_location_task_.IsCancelled()) { |
| SLOG(this, 2) << __func__ << ": " |
| << "Cancelling outstanding timeout."; |
| poll_location_task_.Cancel(); |
| } |
| } |
| |
| void Cellular::SetScanning(bool scanning) { |
| if (scanning_ == scanning) |
| return; |
| LOG(INFO) << __func__ << ": " << scanning |
| << " State: " << GetStateString(state_) |
| << " Modem State: " << GetModemStateString(modem_state_) << ")"; |
| if (scanning) { |
| // Set Scanning=true immediately. |
| scanning_clear_callback_.Cancel(); |
| SetScanningProperty(true); |
| } else { |
| // Delay Scanning=false to delay operations while the Modem is starting. |
| // TODO(b/177588333): Make Modem and/or the MM dbus API more robust. |
| if (!scanning_clear_callback_.IsCancelled()) |
| return; |
| SLOG(this, 2) << __func__ << ": Delaying clear"; |
| scanning_clear_callback_.Reset(base::Bind( |
| &Cellular::SetScanningProperty, weak_ptr_factory_.GetWeakPtr(), false)); |
| dispatcher()->PostDelayedTask(FROM_HERE, |
| scanning_clear_callback_.callback(), |
| kModemResetTimeoutMilliseconds); |
| } |
| } |
| |
| void Cellular::SetScanningProperty(bool scanning) { |
| SLOG(this, 2) << __func__ << ": " << scanning; |
| scanning_ = scanning; |
| adaptor()->EmitBoolChanged(kScanningProperty, scanning_); |
| |
| if (scanning) |
| metrics()->NotifyDeviceScanStarted(interface_index()); |
| else |
| metrics()->NotifyDeviceScanFinished(interface_index()); |
| |
| if (!scanning_) |
| ConnectToPending(); |
| } |
| |
| void Cellular::SetSelectedNetwork(const std::string& selected_network) { |
| if (selected_network_ == selected_network) |
| return; |
| |
| selected_network_ = selected_network; |
| adaptor()->EmitStringChanged(kSelectedNetworkProperty, selected_network_); |
| } |
| |
| void Cellular::SetFoundNetworks(const Stringmaps& found_networks) { |
| // There is no canonical form of a Stringmaps value. |
| // So don't check for redundant updates. |
| found_networks_ = found_networks; |
| adaptor()->EmitStringmapsChanged(kFoundNetworksProperty, found_networks_); |
| } |
| |
| void Cellular::SetProviderRequiresRoaming(bool provider_requires_roaming) { |
| if (provider_requires_roaming_ == provider_requires_roaming) |
| return; |
| |
| provider_requires_roaming_ = provider_requires_roaming; |
| adaptor()->EmitBoolChanged(kProviderRequiresRoamingProperty, |
| provider_requires_roaming_); |
| } |
| |
| bool Cellular::IsRoamingAllowed() { |
| return service_ && service_->IsRoamingAllowed(); |
| } |
| |
| void Cellular::SetApnList(const Stringmaps& apn_list) { |
| // There is no canonical form of a Stringmaps value, so don't check for |
| // redundant updates. |
| apn_list_ = apn_list; |
| adaptor()->EmitStringmapsChanged(kCellularApnListProperty, apn_list_); |
| } |
| |
| void Cellular::UpdateHomeProvider(const MobileOperatorInfo* operator_info) { |
| SLOG(this, 2) << __func__; |
| |
| Stringmap home_provider; |
| if (!operator_info->sid().empty()) { |
| home_provider[kOperatorCodeKey] = operator_info->sid(); |
| } |
| if (!operator_info->nid().empty()) { |
| home_provider[kOperatorCodeKey] = operator_info->nid(); |
| } |
| if (!operator_info->mccmnc().empty()) { |
| home_provider[kOperatorCodeKey] = operator_info->mccmnc(); |
| } |
| if (!operator_info->operator_name().empty()) { |
| home_provider[kOperatorNameKey] = operator_info->operator_name(); |
| } |
| if (!operator_info->country().empty()) { |
| home_provider[kOperatorCountryKey] = operator_info->country(); |
| } |
| if (!operator_info->uuid().empty()) { |
| home_provider[kOperatorUuidKey] = operator_info->uuid(); |
| } |
| if (home_provider != home_provider_) { |
| home_provider_ = home_provider; |
| adaptor()->EmitStringmapChanged(kHomeProviderProperty, home_provider_); |
| } |
| |
| ApnList apn_list; |
| // TODO(b:180004055): remove this when we have captive portal checks that |
| // mark APNs as bad and can skip the null APN for data connections |
| if (manufacturer_ != kQ6V5ModemManufacturerName) |
| apn_list.AddApns(capability_->GetProfiles()); |
| apn_list.AddApns(operator_info->apn_list()); |
| SetApnList(apn_list.GetList()); |
| |
| SetProviderRequiresRoaming(operator_info->requires_roaming()); |
| } |
| |
| void Cellular::UpdateServingOperator( |
| const MobileOperatorInfo* operator_info, |
| const MobileOperatorInfo* home_provider_info) { |
| SLOG(this, 3) << __func__; |
| if (!service()) { |
| return; |
| } |
| |
| Stringmap serving_operator; |
| if (!operator_info->sid().empty()) { |
| serving_operator[kOperatorCodeKey] = operator_info->sid(); |
| } |
| if (!operator_info->nid().empty()) { |
| serving_operator[kOperatorCodeKey] = operator_info->nid(); |
| } |
| if (!operator_info->mccmnc().empty()) { |
| serving_operator[kOperatorCodeKey] = operator_info->mccmnc(); |
| } |
| if (!operator_info->operator_name().empty()) { |
| serving_operator[kOperatorNameKey] = operator_info->operator_name(); |
| } |
| if (!operator_info->country().empty()) { |
| serving_operator[kOperatorCountryKey] = operator_info->country(); |
| } |
| if (!operator_info->uuid().empty()) { |
| serving_operator[kOperatorUuidKey] = operator_info->uuid(); |
| } |
| service()->SetServingOperator(serving_operator); |
| |
| // Set friendly name of service. |
| std::string service_name; |
| if (!operator_info->operator_name().empty()) { |
| // If roaming, try to show "<home-provider> | <serving-operator>", per 3GPP |
| // rules (TS 31.102 and annex A of 122.101). |
| if (service()->roaming_state() == kRoamingStateRoaming && |
| home_provider_info && !home_provider_info->operator_name().empty() && |
| home_provider_info->operator_name() != operator_info->operator_name()) { |
| service_name += home_provider_info->operator_name() + " | "; |
| } |
| service_name += operator_info->operator_name(); |
| } else if (!operator_info->mccmnc().empty()) { |
| // We could not get a name for the operator, just use the code. |
| service_name = "cellular_" + operator_info->mccmnc(); |
| } |
| if (service_name.empty()) { |
| LOG(WARNING) << "No properties for setting friendly name for: " |
| << service()->log_name(); |
| return; |
| } |
| SLOG(this, 2) << __func__ << " Service: " << service()->log_name() |
| << " Name: " << service_name; |
| service()->SetFriendlyName(service_name); |
| } |
| |
| void Cellular::OnOperatorChanged() { |
| SLOG(this, 2) << __func__; |
| CHECK(capability_); |
| |
| if (service()) { |
| capability_->UpdateServiceOLP(); |
| } |
| |
| const bool home_provider_known = |
| home_provider_info_->IsMobileNetworkOperatorKnown(); |
| const bool serving_operator_known = |
| serving_operator_info_->IsMobileNetworkOperatorKnown(); |
| |
| if (home_provider_known) { |
| UpdateHomeProvider(home_provider_info_.get()); |
| } else if (serving_operator_known) { |
| SLOG(this, 2) << "Serving provider proxying in for home provider."; |
| UpdateHomeProvider(serving_operator_info_.get()); |
| } |
| |
| if (serving_operator_known) { |
| if (home_provider_known) { |
| UpdateServingOperator(serving_operator_info_.get(), |
| home_provider_info_.get()); |
| } else { |
| UpdateServingOperator(serving_operator_info_.get(), nullptr); |
| } |
| } else if (home_provider_known) { |
| UpdateServingOperator(home_provider_info_.get(), home_provider_info_.get()); |
| } |
| } |
| |
| bool Cellular::StateIsConnected() { |
| return state_ == State::kConnected || state_ == State::kLinked; |
| } |
| |
| bool Cellular::StateIsRegistered() { |
| return state_ == State::kRegistered || state_ == State::kConnected || |
| state_ == State::kLinked; |
| } |
| |
| bool Cellular::StateIsStarted() { |
| return state_ == State::kModemStarted || state_ == State::kRegistered || |
| state_ == State::kConnected || state_ == State::kLinked; |
| } |
| |
| void Cellular::SetServiceForTesting(CellularServiceRefPtr service) { |
| service_for_testing_ = service; |
| service_ = service; |
| } |
| |
| } // namespace shill |