blob: 18778f614be199ac010b7a6cc98c85e8d42008d5 [file] [log] [blame] [edit]
// Copyright 2018 The ChromiumOS Authors
// 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 <algorithm>
#include <ios>
#include <memory>
#include <optional>
#include <set>
#include <string_view>
#include <utility>
#include <base/check.h>
#include <base/check_op.h>
#include <base/containers/contains.h>
#include "base/containers/fixed_flat_map.h"
#include <base/files/file_enumerator.h>
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/functional/bind.h>
#include <base/functional/callback.h>
#include <base/logging.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 <net-base/ipv6_address.h>
#include <net-base/mac_address.h>
#include <net-base/network_config.h>
#include <net-base/process_manager.h>
#include <net-base/rtnl_handler.h>
#include <ModemManager/ModemManager.h>
#include <re2/re2.h>
#include "shill/adaptor_interfaces.h"
#include "shill/cellular/apn_list.h"
#include "shill/cellular/carrier_entitlement.h"
#include "shill/cellular/cellular_bearer.h"
#include "shill/cellular/cellular_capability_3gpp.h"
#include "shill/cellular/cellular_consts.h"
#include "shill/cellular/cellular_helpers.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.h"
#include "shill/cellular/modem_info.h"
#include "shill/control_interface.h"
#include "shill/data_types.h"
#include "shill/dbus/dbus_control.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/metrics.h"
#include "shill/network/network_manager.h"
#include "shill/network/network_monitor.h"
#include "shill/ppp_daemon.h"
#include "shill/profile.h"
#include "shill/service.h"
#include "shill/store/property_accessor.h"
#include "shill/store/store_interface.h"
#include "shill/technology.h"
#include "shill/tethering_manager.h"
#include "shill/virtual_device.h"
namespace shill {
namespace Logging {
static auto kModuleLogScope = ScopeLogger::kCellular;
} // namespace Logging
namespace {
// Maximum time to wait for Modem registration before canceling a pending
// connect attempt.
constexpr base::TimeDelta kPendingConnectCancel = base::Minutes(1);
// Prefix used by entitlement check logging messages when the entitlement
// check is not successful. This prefix is used by the anomaly detector to
// identify these events.
constexpr char kEntitlementCheckAnomalyDetectorPrefix[] =
"Entitlement check failed: ";
// Longer tethering start timeout value, used when the upstream network setup
// requires the connection of a new PDN.
static constexpr base::TimeDelta kLongTetheringStartTimeout = base::Seconds(45);
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;
}
Metrics::IPConfigMethod BearerIPConfigMethodToMetrics(
CellularBearer::IPConfigMethod method) {
using BearerType = CellularBearer::IPConfigMethod;
using MetricsType = Metrics::IPConfigMethod;
switch (method) {
case BearerType::kUnknown:
return MetricsType::kUnknown;
case BearerType::kPPP:
return MetricsType::kPPP;
case BearerType::kStatic:
return MetricsType::kStatic;
case BearerType::kDHCP:
return MetricsType::kDynamic;
}
}
std::string GetFriendlyModelId(const std::string& model_id) {
if (model_id.find("L850") != std::string::npos) {
return "L850";
}
if (model_id.find("FM101") != std::string::npos) {
return "FM101";
}
if (model_id.find("7c Compute") != std::string::npos) {
return "SC7180";
}
if (model_id.find("4D75") != std::string::npos) {
return "FM350";
}
if (model_id.find("NL668") != std::string::npos) {
return "NL668";
}
return model_id;
}
// Returns if specified modem manager error can be classified as
// subscription related error. This API should be enhanced if
// better signals become available to detect subscription error.
bool IsSubscriptionError(std::string mm_error) {
return mm_error == MM_MOBILE_EQUIPMENT_ERROR_DBUS_PREFIX
".ServiceOptionNotSubscribed";
}
void PrintApnListForDebugging(std::deque<Stringmap> apn_try_list,
ApnList::ApnType apn_type) {
// Print list for debugging
if (SLOG_IS_ON(Cellular, 3)) {
std::string log_string =
": Try list: ApnType: " + ApnList::GetApnTypeString(apn_type);
for (const auto& it : apn_try_list) {
log_string += " " + GetPrintableApnStringmap(it);
}
SLOG(3) << __func__ << log_string;
}
}
Metrics::DetailedCellularConnectionResult::ConnectionAttemptType
ConnectionAttemptTypeToMetrics(CellularServiceRefPtr service) {
using MetricsType =
Metrics::DetailedCellularConnectionResult::ConnectionAttemptType;
if (!service)
return MetricsType::kUnknown;
if (service->is_in_user_connect())
return MetricsType::kUserConnect;
return MetricsType::kAutoConnect;
}
Metrics::DetailedCellularConnectionResult::APNType ApnTypeToMetricEnum(
ApnList::ApnType apn_type) {
switch (apn_type) {
case ApnList::ApnType::kDefault:
return Metrics::DetailedCellularConnectionResult::APNType::kDefault;
case ApnList::ApnType::kAttach:
return Metrics::DetailedCellularConnectionResult::APNType::kAttach;
case ApnList::ApnType::kDun:
return Metrics::DetailedCellularConnectionResult::APNType::kDUN;
}
}
} // 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::kQ6V5SysfsBasePath[] = "/sys/class/remoteproc";
const char Cellular::kQ6V5RemoteprocPattern[] = "remoteproc*";
const char Cellular::kInternalStorageIdentifier[] = "internal_device";
// 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";
case State::kPoweredOff:
return "PoweredOff";
}
return base::StringPrintf("CellularStateUnknown-%d", static_cast<int>(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);
}
// static
void Cellular::ValidateApnTryList(std::deque<Stringmap>& apn_try_list) {
// Entries in the APN try list must have the APN property
apn_try_list.erase(
std::remove_if(
apn_try_list.begin(), apn_try_list.end(),
[](const auto& item) { return !base::Contains(item, kApnProperty); }),
apn_try_list.end());
}
// static
Stringmap Cellular::BuildFallbackEmptyApn(ApnList::ApnType apn_type) {
Stringmap apn;
apn[kApnProperty] = "";
apn[kApnTypesProperty] = ApnList::GetApnTypeString(apn_type);
apn[kApnIpTypeProperty] = kApnIpTypeV4V6;
apn[kApnSourceProperty] = cellular::kApnSourceFallback;
return apn;
}
Cellular::Cellular(Manager* manager,
const std::string& link_name,
net_base::MacAddress mac_address,
int interface_index,
const std::string& service,
const RpcIdentifier& path)
: Device(manager,
link_name,
mac_address,
interface_index,
Technology::kCellular,
/*fixed_ip_params=*/false,
/*use_implicit_network=*/false),
mobile_operator_info_(
new MobileOperatorInfo(manager->dispatcher(), "cellular")),
dbus_service_(service),
dbus_path_(path),
dbus_path_str_(path.value()),
process_manager_(net_base::ProcessManager::GetInstance()) {
RegisterProperties();
mobile_operator_info_->Init();
socket_destroyer_ = net_base::NetlinkSockDiag::Create();
if (!socket_destroyer_) {
LOG(WARNING) << LoggingTag() << ": Socket destroyer failed to initialize; "
<< "IPv6 will be unavailable.";
}
if (interface_index == Modem::kInternalInterfaceIndex) {
internal_device_ = true;
}
// Create an initial Capability.
if (!internal_device_) {
CreateCapability();
} else {
LOG(INFO) << LoggingTag() << "not creating capability for internal device";
}
// Reset networks
default_pdn_apn_type_ = std::nullopt;
default_pdn_.reset();
multiplexed_tethering_pdn_.reset();
carrier_entitlement_ = std::make_unique<CarrierEntitlement>(
this, metrics(), manager->patchpanel_client(),
base::BindRepeating(&Cellular::OnEntitlementCheckUpdated,
weak_ptr_factory_.GetWeakPtr()));
SLOG(1) << LoggingTag() << ": Cellular()";
}
Cellular::~Cellular() {
LOG(INFO) << LoggingTag() << ": ~Cellular()";
if (capability_)
DestroyCapability();
}
Network* Cellular::GetPrimaryNetwork() const {
// The default network is considered as primary always.
return default_pdn_ ? default_pdn_->network() : nullptr;
}
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 GetMacAddressHexString();
}
std::string Cellular::DeviceStorageSuffix() 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 link_name();
}
bool Cellular::Load(const StoreInterface* storage) {
std::string id = GetStorageIdentifier();
SLOG(2) << LoggingTag() << ": " << __func__ << ": Device ID: " << id;
if (!storage->ContainsGroup(id)) {
id = "device_" + GetLegacyEquipmentIdentifier();
if (!storage->ContainsGroup(id)) {
LOG(WARNING) << LoggingTag() << ": " << __func__
<< ": Device is not available in the persistent store";
return false;
}
legacy_storage_id_ = id;
}
storage->GetBool(id, kAllowRoaming, &allow_roaming_);
storage->GetBool(id, kPolicyAllowRoaming, &policy_allow_roaming_);
LOG(INFO) << LoggingTag() << ": " << __func__ << ": " << kAllowRoaming << ":"
<< allow_roaming_ << " " << kPolicyAllowRoaming << ":"
<< policy_allow_roaming_;
bool res = Device::Load(storage);
/* Check if we have persistent enabled state for internal device*/
if (!internal_device()) {
if (storage->ContainsGroup(kInternalStorageIdentifier)) {
bool enabled = enabled_persistent();
storage->GetBool(kInternalStorageIdentifier, kStoragePowered, &enabled);
set_enabled_persistent(enabled);
LOG(INFO) << LoggingTag() << ": " << __func__ << ": "
<< "Loading from internal: enabled: " << enabled;
}
}
return res;
}
bool Cellular::Save(StoreInterface* storage) {
const std::string id = GetStorageIdentifier();
storage->SetBool(id, kAllowRoaming, allow_roaming_);
storage->SetBool(id, kPolicyAllowRoaming, policy_allow_roaming_);
bool result = Device::Save(storage);
SLOG(2) << LoggingTag() << ": " << __func__ << ": Device ID: " << id;
LOG(INFO) << LoggingTag() << ": " << __func__ << ": " << result;
// TODO(b/181843251): Remove when number of users on M92 are negligible.
if (result && !legacy_storage_id_.empty() &&
storage->ContainsGroup(legacy_storage_id_)) {
SLOG(2) << LoggingTag() << ": " << __func__
<< ": Deleting legacy storage id: " << legacy_storage_id_;
storage->DeleteGroup(legacy_storage_id_);
legacy_storage_id_.clear();
}
// During Save(), update the kStoragePowered property for internal
// device also.
// This is to ensure that when a real device is de-registered and the
// internal device is re-created, it should start with the right enabled
// state (which is the enabled state of the current device).
// Hence in the Save() we also update any enabled state changes to the storage
// id of the internal device. This ensures that when the internal device is
// re-created it gets the right enabled state during Load().
if (!internal_device()) {
LOG(INFO) << LoggingTag() << "update enabled setting on internal device: "
<< kInternalStorageIdentifier;
storage->SetBool(kInternalStorageIdentifier, kStoragePowered,
enabled_persistent());
}
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::GetMultiplexSupport() {
// The device allows multiplexing support when more than one multiplexed
// bearers can be setup at a given time.
return (max_multiplexed_bearers_ > 1);
}
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 ModemType kAffectedModemTypes[] = {ModemType::kL850GL};
for (const auto& affected_modem_type : kAffectedModemTypes) {
if (modem_type_ == affected_modem_type)
return true;
}
return false;
}
void Cellular::BringNetworkInterfaceDown() {
// The IPA network interface in a Qualcomm SoC should never be brought down
// by shill, because the modem processor may get in a bad state if we power
// up radio quickly after having brought down the interface (see b/305930007,
// and b/302714371).
if (IsQ6V5Modem()) {
SLOG(2) << LoggingTag() << ": skipping IPA net interface down.";
return;
}
// The physical network interface always exists, unconditionally, so it can
// be brought down safely regardless of whether a Network exists or not.
// There is no need to bring down explicitly any additional multiplexed
// network interface managed by the device because those are fully removed
// whenever the corresponding PDN is disconnected.
rtnl_handler()->SetInterfaceFlags(interface_index(), 0, IFF_UP);
}
void Cellular::SetState(State state) {
if (state == state_)
return;
LOG(INFO) << LoggingTag() << ": " << __func__ << ": "
<< GetStateString(state_) << " -> " << GetStateString(state);
state_ = state;
UpdateScanning();
}
void Cellular::SetModemState(ModemState modem_state) {
if (modem_state == modem_state_)
return;
LOG(INFO) << LoggingTag() << ": " << __func__ << ": "
<< GetModemStateString(modem_state_) << " -> "
<< GetModemStateString(modem_state);
modem_state_ = modem_state;
UpdateScanning();
}
void Cellular::HelpRegisterDerivedBool(std::string_view 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(
std::string_view name, std::string (Cellular::*get)(Error*)) {
mutable_store()->RegisterDerivedString(
name, StringAccessor(
new CustomAccessor<Cellular, std::string>(this, get, nullptr)));
}
void Cellular::Start(EnabledStateChangedCallback callback) {
LOG(INFO) << LoggingTag() << ": " << __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) << LoggingTag() << ": " << __func__
<< ": Skipping Start (no capability).";
std::move(callback).Run(Error(Error::kSuccess));
return;
}
StartModem(std::move(callback));
}
void Cellular::Stop(EnabledStateChangedCallback callback) {
LOG(INFO) << LoggingTag() << ": Stop requested (modem "
<< GetStateString(state_) << ")";
DCHECK(!stop_step_.has_value()) << "Already stopping. Unexpected Stop call.";
stop_step_ = StopSteps::kStopModem;
StopStep(std::move(callback), Error());
}
void Cellular::StopStep(EnabledStateChangedCallback callback,
const Error& error_result) {
SLOG(1) << LoggingTag() << ": " << __func__ << ": " << GetStateString(state_);
DCHECK(stop_step_.has_value());
switch (stop_step_.value()) {
case StopSteps::kStopModem:
// 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();
if (capability_) {
LOG(INFO) << LoggingTag() << ": Stopping modem.";
SetState(State::kModemStopping);
capability_->StopModem(base::BindOnce(&Cellular::StopModemCallback,
weak_ptr_factory_.GetWeakPtr(),
std::move(callback)));
return;
}
stop_step_ = StopSteps::kModemStopped;
[[fallthrough]];
case StopSteps::kModemStopped:
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();
// 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_result.IsFailure()) {
LOG(ERROR) << LoggingTag()
<< ": Failed stopping modem: " << error_result;
}
std::move(callback).Run(error_result);
stop_step_.reset();
return;
}
}
void Cellular::StartModem(EnabledStateChangedCallback callback) {
DCHECK(capability_);
LOG(INFO) << LoggingTag() << ": " << __func__;
SetState(State::kModemStarting);
capability_->StartModem(base::BindOnce(&Cellular::StartModemCallback,
weak_ptr_factory_.GetWeakPtr(),
std::move(callback)));
}
void Cellular::StartModemCallback(EnabledStateChangedCallback callback,
const Error& error) {
LOG(INFO) << LoggingTag() << ": " << __func__
<< ": state=" << GetStateString(state_);
if (!error.IsSuccess()) {
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.
LOG(WARNING) << LoggingTag() << ": StartModem failed: " << error;
std::move(callback).Run(Error(Error::kSuccess));
} else {
LOG(ERROR) << LoggingTag() << ": StartModem failed: " << error;
std::move(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());
std::move(callback).Run(Error(Error::kSuccess));
}
void Cellular::StopModemCallback(EnabledStateChangedCallback callback,
const Error& error_result) {
LOG(INFO) << LoggingTag() << ": " << __func__ << ": "
<< GetStateString(state_) << " Error: " << error_result;
stop_step_ = StopSteps::kModemStopped;
StopStep(std::move(callback), error_result);
}
void Cellular::DestroySockets() {
if (!socket_destroyer_)
return;
if (default_pdn_) {
default_pdn_->DestroySockets();
}
if (multiplexed_tethering_pdn_) {
multiplexed_tethering_pdn_->DestroySockets();
}
}
void Cellular::CompleteActivation(Error* error) {
if (capability_)
capability_->CompleteActivation(error);
}
bool Cellular::IsUnderlyingDeviceEnabled() const {
return IsEnabledModemState(modem_state_);
}
void Cellular::Scan(Error* error,
const std::string& /*reason*/,
bool /*is_dbus_call*/) {
SLOG(2) << LoggingTag() << ": Scanning started";
CHECK(error);
if (proposed_scan_in_progress_) {
Error::PopulateAndLog(FROM_HERE, error, Error::kInProgress,
"Already scanning");
return;
}
if (!capability_)
return;
capability_->Scan(
base::BindOnce(&Cellular::OnScanStarted, weak_ptr_factory_.GetWeakPtr()),
base::BindOnce(&Cellular::OnScanReply, weak_ptr_factory_.GetWeakPtr()));
}
void Cellular::RegisterOnNetwork(const std::string& network_id,
ResultCallback callback) {
if (!capability_) {
std::move(callback).Run(Error(Error::Type::kOperationFailed));
return;
}
capability_->RegisterOnNetwork(network_id, std::move(callback));
}
void Cellular::RequirePin(const std::string& pin,
bool require,
ResultCallback callback) {
SLOG(2) << LoggingTag() << ": " << __func__ << ": " << require;
if (!capability_) {
std::move(callback).Run(Error(Error::Type::kOperationFailed));
return;
}
capability_->RequirePin(pin, require, std::move(callback));
}
void Cellular::EnterPin(const std::string& pin, ResultCallback callback) {
SLOG(2) << LoggingTag() << ": " << __func__;
if (!capability_) {
std::move(callback).Run(Error(Error::Type::kOperationFailed));
return;
}
capability_->EnterPin(pin, std::move(callback));
}
void Cellular::UnblockPin(const std::string& unblock_code,
const std::string& pin,
ResultCallback callback) {
SLOG(2) << LoggingTag() << ": " << __func__;
if (!capability_) {
std::move(callback).Run(Error(Error::Type::kOperationFailed));
return;
}
capability_->UnblockPin(unblock_code, pin, std::move(callback));
}
void Cellular::ChangePin(const std::string& old_pin,
const std::string& new_pin,
ResultCallback callback) {
SLOG(2) << LoggingTag() << ": " << __func__;
if (!capability_) {
std::move(callback).Run(Error(Error::Type::kOperationFailed));
return;
}
capability_->ChangePin(old_pin, new_pin, std::move(callback));
}
void Cellular::Reset(ResultCallback callback) {
SLOG(2) << LoggingTag() << ": " << __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()) {
std::move(callback).Run(Error(Error::Type::kOperationFailed));
} else {
std::move(callback).Run(Error(Error::Type::kSuccess));
}
return;
}
if (!capability_) {
std::move(callback).Run(Error(Error::Type::kOperationFailed));
return;
}
capability_->Reset(std::move(callback));
}
void Cellular::DropConnectionDefault() {
SetPrimaryMultiplexedInterface("");
default_pdn_apn_type_ = std::nullopt;
default_pdn_.reset();
multiplexed_tethering_pdn_.reset();
SelectService(nullptr);
}
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();
return;
}
DropConnectionDefault();
}
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) << LoggingTag() << ": State change with no Service.";
}
}
void Cellular::SetServiceFailure(Service::ConnectFailure failure_state) {
LOG(WARNING) << LoggingTag() << ": " << __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) << LoggingTag() << ": State change with no Service.";
}
}
void Cellular::SetServiceFailureSilent(Service::ConnectFailure failure_state) {
SLOG(2) << LoggingTag() << ": " << __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) << LoggingTag() << ": State change with no Service.";
}
}
void Cellular::OnConnected() {
// If state is already connected and we have a default Network setup, do
// nothing. The missing Network while connected may happen during the
// reconnection performed by the tethering logic when using the tethering
// APN as default.
if (StateIsConnected() && default_pdn_) {
SLOG(1) << LoggingTag() << ": " << __func__ << ": Already connected";
return;
}
SLOG(1) << LoggingTag() << ": " << __func__;
SetState(State::kConnected);
if (!service_) {
LOG(INFO) << LoggingTag() << ": 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) << LoggingTag() << ": Disconnecting due to roaming.";
Disconnect(nullptr, "roaming disallowed");
} else {
EstablishLink();
}
}
void Cellular::OnBeforeSuspend(ResultCallback callback) {
LOG(INFO) << LoggingTag() << ": " << __func__;
Error error;
StopPPP();
SetEnabledNonPersistent(false, std::move(callback));
}
void Cellular::OnAfterResume() {
SLOG(2) << LoggingTag() << ": " << __func__;
if (enabled_persistent()) {
LOG(INFO) << LoggingTag() << ": Restarting modem after resume.";
// TODO(b/216847428): replace this with a real toggle
SetEnabledUnchecked(true, base::BindOnce(LogRestartModemResult));
}
// 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();
}
void Cellular::OnDeregistered() {
LOG(INFO) << LoggingTag() << ": " << __func__;
if (!internal_device()) {
LOG(INFO) << LoggingTag() << ": Create internal " << __func__;
Modem::CreateInternalCellularDevice(manager()->device_info());
}
}
void Cellular::UpdateGeolocationObjects(
std::vector<GeolocationInfo>* geolocation_infos) 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.
geolocation_infos->clear();
geolocation_infos->push_back(geolocation_info);
}
void Cellular::OnConnectionUpdated(int interface_index) {
SLOG(1) << LoggingTag() << ": connection updated: " << interface_index;
// Event on the default network, propagate it to the parent.
if (default_pdn_ &&
interface_index == default_pdn_->network()->interface_index()) {
// If the requested APN was connected during a DUN as DEFAULT connect or
// disconnect operation, we can now complete the operation successfully.
// If any event happens before we have connected the APN, we should
// ignore it.
if (IsTetheringOperationDunAsDefaultOngoing()) {
if (tethering_operation_->apn_connected) {
SLOG(1) << LoggingTag() << ": tethering operation can be completed";
CompleteTetheringOperation(Error(Error::kSuccess));
} else {
SLOG(1) << LoggingTag() << ": tethering operation still ongoing";
}
}
Device::OnConnectionUpdated(interface_index);
return;
}
// Event on the tethering-specific multiplexed network.
if (multiplexed_tethering_pdn_ &&
interface_index ==
multiplexed_tethering_pdn_->network()->interface_index()) {
if (IsTetheringOperationDunMultiplexedConnectOngoing()) {
if (tethering_operation_->apn_connected) {
SLOG(1) << LoggingTag()
<< ": multiplexed tethering operation can be completed";
CompleteTetheringOperation(Error(Error::kSuccess));
} else {
SLOG(1) << LoggingTag()
<< ": multiplexed tethering operation still ongoing";
}
}
return;
}
LOG(WARNING) << LoggingTag()
<< ": Unexpected network connection update: " << interface_index;
}
void Cellular::ConfigureAttachApn(bool user_triggered) {
SLOG(1) << LoggingTag() << ": " << __func__;
if (!enabled() && !enabled_pending()) {
LOG(WARNING) << LoggingTag() << ": " << __func__
<< ": Modem not enabled, skip attach APN configuration.";
// Even though the request to configure the attach APN is ignored here, the
// same attach APN logic will be executed automatically when the modem
// reaches the Enabled state.
return;
}
capability_->ConfigureAttachApn(user_triggered);
}
void Cellular::CancelPendingConnect() {
ConnectToPendingFailed(Service::kFailureDisconnect);
}
void Cellular::OnScanStarted() {
proposed_scan_in_progress_ = true;
UpdateScanning();
}
void Cellular::OnScanReply(const Stringmaps& found_networks,
const Error& error) {
SLOG(2) << LoggingTag() << ": 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()) {
error.Log();
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(2) << LoggingTag() << ": " << __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) << LoggingTag() << ": 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(4) << LoggingTag() << ": " << __func__;
PollLocation();
poll_location_task_.Reset(base::BindOnce(&Cellular::PollLocationTask,
weak_ptr_factory_.GetWeakPtr()));
dispatcher()->PostDelayedTask(FROM_HERE, poll_location_task_.callback(),
kPollLocationInterval);
}
void Cellular::PollLocation() {
if (!capability_)
return;
capability_->GetLocation(base::BindOnce(&Cellular::GetLocationCallback,
weak_ptr_factory_.GetWeakPtr()));
}
void Cellular::HandleNewSignalQuality(uint32_t strength) {
SLOG(2) << LoggingTag() << ": Signal strength: " << strength;
if (service_) {
service_->SetStrength(strength);
}
}
void Cellular::HandleNewRegistrationState() {
SLOG(2) << LoggingTag() << ": " << __func__
<< ": state = " << GetStateString(state_);
CHECK(capability_);
if (!capability_->IsRegistered()) {
if (!explicit_disconnect_ && StateIsConnected() && service_.get()) {
// TODO(b/200584652): Remove after January 2024
if (capability_->GetNetworkTechnologyString() == "")
LOG(INFO) << LoggingTag() << ": Logging Drop connection on unknown "
<< "cellular technology";
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::kPoweredOff:
case State::kModemStarting:
case State::kModemStopping:
// Defer updating Services while disabled and during transitions.
return;
case State::kEnabled:
LOG(WARNING) << LoggingTag() << ": 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;
}
if (service_) {
service_->ResetAutoConnectCooldownTime();
}
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(2) << LoggingTag() << ": " << __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) {
// On an idle->registered reg state change while modem is connected, we may
// need to establish links both in default and tethering, but the tethering
// one will need to go always once the default one is up.
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) << LoggingTag() << ": " << __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) << LoggingTag() << ": " << __func__
<< ": Service=" << service_->log_name();
// Create or update Cellular Services for secondary SIMs.
UpdateSecondaryServices();
capability_->OnServiceCreated();
// Ensure operator properties are updated.
OnOperatorChanged();
if (service_ && manager()->power_opt())
manager()->power_opt()->AddOptInfoForNewService(service_->iccid());
}
void Cellular::DestroyAllServices() {
LOG(INFO) << LoggingTag() << ": " << __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::OnNetworkValidationResult(int interface_index,
const NetworkMonitor::Result& result) {
SLOG(1) << LoggingTag() << ": " << __func__;
Device::OnNetworkValidationResult(interface_index, result);
// TODO(b/314693271): Remove this special case once the portal detection state
// machine is entirely controlled from Network and once Device is not involved
// anymore.
if (multiplexed_tethering_pdn_ &&
multiplexed_tethering_pdn_->network()->interface_index() ==
interface_index) {
switch (result.validation_state) {
case PortalDetector::ValidationState::kInternetConnectivity:
// Nothing to do: the Network already stops the network validation loop
// when the Network reaches the kInternetConnectivity state.
break;
case PortalDetector::ValidationState::kPortalRedirect:
case PortalDetector::ValidationState::kPortalSuspected:
// b/301648519: Some Cellular carriers use portal redirection flows for
// asking the user to enable or buy a tethering data plan. This flow is
// not handled natively in ChromeOS, but the network is nonetheless
// considered ready.
// the network validation loop must be stopped explicitly.
multiplexed_tethering_pdn_->network()->StopPortalDetection();
break;
case PortalDetector::ValidationState::kNoConnectivity:
// Nothing to do: the Network already continues the network validation
// loop when the Network reaches the kInternetConnectivity state.
break;
}
}
// TODO(b/309512268) add support for tethering PDN.
if (!IsEventOnPrimaryNetwork(interface_index)) {
return;
}
if (!selected_service() || !selected_service()->IsConnected()) {
return;
}
if (!service_ || !service_->GetLastGoodApn()) {
return;
}
// Report cellular specific metrics with APN information.
NotifyNetworkValidationResult(*service_->GetLastGoodApn(),
result.probe_result_metric,
result.num_attempts);
}
void Cellular::OnModemDestroyed() {
SLOG(1) << LoggingTag() << ": " << __func__;
StopLocationPolling();
DestroyCapability();
SetForceInitEpsBearerSettings(true);
// Clear the dbus path.
SetDbusPath(shill::RpcIdentifier());
// 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(1) << LoggingTag() << ": " << __func__;
CHECK(!capability_);
capability_ = std::make_unique<CellularCapability3gpp>(
this, manager()->control_interface(), manager()->metrics(),
manager()->modem_info()->pending_activation_store());
if (initial_properties_.has_value()) {
SetInitialProperties(*initial_properties_);
initial_properties_ = std::nullopt;
}
mobile_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(base::DoNothing());
// Update device state that might have been pending
// due to the lack of |capability_| during Cellular::Start().
UpdateEnabledState();
}
void Cellular::DestroyCapability() {
SLOG(1) << LoggingTag() << ": " << __func__;
mobile_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.
mobile_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,
ApnList::ApnType apn_type) {
SLOG(3) << LoggingTag() << ": " << __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(3) << LoggingTag() << ": "
<< " Skipping repetitive failure metric. Error: "
<< error.message();
return;
}
metrics()->NotifyCellularConnectionResult(error.type(),
ApnTypeToMetricEnum(apn_type));
last_cellular_connection_results_[iccid] = error.type();
if (error.IsSuccess()) {
return;
}
// used by anomaly detector for cellular subsystem crashes
LOG(ERROR) << LoggingTag() << ": " << GetFriendlyModelId(model_id_)
<< " could not connect (trigger="
<< (is_user_triggered ? "dbus" : "auto")
<< ") to mccmnc=" << mobile_operator_info_->mccmnc() << ": "
<< error.message();
}
bool Cellular::IsSubscriptionErrorSeen() {
return service_ && subscription_error_seen_[service_->iccid()];
}
void Cellular::NotifyNetworkValidationResult(
std::map<std::string, std::string> apn_info,
int portal_detection_result,
int portal_detection_count) {
CHECK(service_);
auto ipv4 = CellularBearer::IPConfigMethod::kUnknown;
auto ipv6 = CellularBearer::IPConfigMethod::kUnknown;
uint32_t tech_used = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN;
Metrics::SimType sim_type = Metrics::SimType::kUnknown;
// TODO(b/309512268) Add support for tethering PDN.
const ApnList::ApnType apn_type = ApnList::ApnType::kDefault;
LOG(INFO) << LoggingTag() << ": " << __func__
<< ": portal_detection_result: " << portal_detection_result
<< " portal_detection_count: " << portal_detection_count;
std::string roaming_state = service_->roaming_state();
// If EID is not empty, report as eSIM else report as pSIM
if (!service_->eid().empty())
sim_type = Metrics::SimType::kEsim;
else
sim_type = Metrics::SimType::kPsim;
if (capability_) {
tech_used = capability_->GetActiveAccessTechnologies();
CellularBearer* bearer = capability_->GetActiveBearer(apn_type);
if (bearer) {
ipv4 = bearer->ipv4_config_method();
ipv6 = bearer->ipv6_config_method();
}
}
Metrics::CellularNetworkValidationResult result;
result.portal_detection_result = portal_detection_result;
result.portal_detection_count = portal_detection_count;
result.uuid = mobile_operator_info_->uuid();
result.apn_info = apn_info;
result.ipv4_config_method = BearerIPConfigMethodToMetrics(ipv4);
result.ipv6_config_method = BearerIPConfigMethodToMetrics(ipv6);
result.home_mccmnc = mobile_operator_info_->mccmnc();
result.serving_mccmnc = mobile_operator_info_->serving_mccmnc();
result.roaming_state = service_->roaming_state();
result.tech_used = tech_used;
result.sim_type = sim_type;
// TODO(b/309512268) consider adding apn_type here if APN name
// is not sufficient to identify tethering APN.
metrics()->NotifyCellularNetworkValidationResult(result);
}
void Cellular::NotifyDetailedCellularConnectionResult(
const Error& error,
ApnList::ApnType apn_type,
const shill::Stringmap& apn_info) {
CHECK(service_);
SLOG(3) << LoggingTag() << ": " << __func__ << ": Result:" << error.type();
auto ipv4 = CellularBearer::IPConfigMethod::kUnknown;
auto ipv6 = CellularBearer::IPConfigMethod::kUnknown;
uint32_t tech_used = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN;
uint32_t iccid_len = 0;
Metrics::SimType sim_type = Metrics::SimType::kUnknown;
brillo::ErrorPtr detailed_error;
std::string cellular_error;
bool use_apn_revamp_ui = false;
std::string iccid = service_->iccid();
std::string roaming_state;
if (service_) {
roaming_state = service_->roaming_state();
iccid_len = service_->iccid().length();
use_apn_revamp_ui = service_->custom_apn_list().has_value();
// If EID is not empty, report as eSIM else report as pSIM
sim_type = service_->eid().empty() ? Metrics::SimType::kPsim
: Metrics::SimType::kEsim;
}
if (capability_) {
tech_used = capability_->GetActiveAccessTechnologies();
CellularBearer* bearer = capability_->GetActiveBearer(apn_type);
if (bearer) {
ipv4 = bearer->ipv4_config_method();
ipv6 = bearer->ipv6_config_method();
}
}
error.ToDetailedError(&detailed_error);
if (detailed_error != nullptr)
cellular_error = detailed_error->GetCode();
SLOG(3) << LoggingTag() << ": Cellular Error:" << cellular_error;
if (error.IsSuccess()) {
subscription_error_seen_[iccid] = false;
}
Metrics::DetailedCellularConnectionResult result;
result.error = error.type();
result.detailed_error = cellular_error;
result.uuid = mobile_operator_info_->uuid();
result.apn_info = apn_info;
result.ipv4_config_method = BearerIPConfigMethodToMetrics(ipv4);
result.ipv6_config_method = BearerIPConfigMethodToMetrics(ipv6);
result.home_mccmnc = mobile_operator_info_->mccmnc();
result.serving_mccmnc = mobile_operator_info_->serving_mccmnc();
result.roaming_state = roaming_state;
result.use_apn_revamp_ui = use_apn_revamp_ui;
result.tech_used = tech_used;
result.iccid_length = iccid_len;
result.sim_type = sim_type;
result.gid1 = mobile_operator_info_->gid1();
result.connection_attempt_type = ConnectionAttemptTypeToMetrics(service_);
result.subscription_error_seen = subscription_error_seen_[iccid];
result.modem_state = modem_state_;
result.interface_index = interface_index();
result.last_connected = service_->GetLastConnectedProperty(nullptr);
result.last_online = service_->GetLastOnlineProperty(nullptr);
result.connection_apn_types = ConnectionApnTypesToMetrics(apn_type);
metrics()->NotifyDetailedCellularConnectionResult(result);
// Update if we reported subscription error for this card so that
// subsequent errors report subscription_error_seen=true.
// This is needed because when connection attempt fail with
// serviceOptionNotSubscribed error which in most cases indicate issues
// related to invalid APNs, subsequent connection attempts fails with
// different error codes, making analysis of metrics difficult.
if (IsSubscriptionError(cellular_error)) {
subscription_error_seen_[iccid] = true;
}
}
std::vector<Metrics::DetailedCellularConnectionResult::APNType>
Cellular::ConnectionApnTypesToMetrics(ApnList::ApnType apn_type) {
std::vector<Metrics::DetailedCellularConnectionResult::APNType>
connection_apn_types;
if (default_pdn_apn_type_.has_value() &&
default_pdn_apn_type_.value() != apn_type) {
connection_apn_types.push_back(
ApnTypeToMetricEnum(default_pdn_apn_type_.value()));
}
connection_apn_types.push_back(ApnTypeToMetricEnum(apn_type));
return connection_apn_types;
}
void Cellular::Connect(CellularService* service, Error* error) {
CHECK(service);
LOG(INFO) << LoggingTag() << ": " << __func__ << ": " << service->log_name();
const ApnList::ApnType apn_type = ApnList::ApnType::kDefault;
if (!capability_) {
Error::PopulateAndLog(FROM_HERE, error, Error::kWrongState,
"Connect Failed: Modem not available.");
NotifyCellularConnectionResult(*error, service->iccid(),
service->is_in_user_connect(), apn_type);
return;
}
if (inhibited_) {
Error::PopulateAndLog(FROM_HERE, error, Error::kWrongState,
"Connect Failed: Inhibited.");
NotifyCellularConnectionResult(*error, service->iccid(),
service->is_in_user_connect(), apn_type);
return;
}
if (!connect_pending_iccid_.empty() &&
connect_pending_iccid_ == service->iccid()) {
Error error_temp = Error(Error::kWrongState, "Connect already pending.");
LOG(WARNING) << LoggingTag() << ": " << error_temp.message();
NotifyCellularConnectionResult(error_temp, service->iccid(),
service->is_in_user_connect(), apn_type);
return;
}
bool auto_connect_disabled =
manager()->IsTechnologyAutoConnectDisabled(Technology::kCellular);
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 (!capability_->SetPrimarySimSlotForIccid(service->iccid())) {
Error::PopulateAndLog(FROM_HERE, error, Error::kOperationFailed,
"Connect Failed: ICCID not available.");
NotifyCellularConnectionResult(*error, service->iccid(),
service->is_in_user_connect(), apn_type);
return;
}
if (auto_connect_disabled) {
// This solution is not ideal, but a compromise, because shill is
// switching SIM slots, but there won't be an auto connect after that.
// The user should be able to manually connect again as soon as the modem
// registers to the network with the new SIM.
Error::PopulateAndLog(
FROM_HERE, error, Error::kOperationFailed,
"Connect Failed: Incorrect ICCID. Switching services");
NotifyCellularConnectionResult(*error, service->iccid(),
service->is_in_user_connect(), apn_type);
return;
}
SetPendingConnect(service->iccid());
return;
}
if (scanning_) {
if (auto_connect_disabled) {
Error::PopulateAndLog(
FROM_HERE, error, Error::kOperationFailed,
"Connect Failed: Cellular is scanning and auto connect is disabled");
NotifyCellularConnectionResult(*error, service->iccid(),
service->is_in_user_connect(), apn_type);
} else {
LOG(INFO) << LoggingTag() << ": "
<< "Cellular is scanning. Pending connect to: "
<< service->log_name();
SetPendingConnect(service->iccid());
}
return;
}
if (!StateIsStarted()) {
Error::PopulateAndLog(FROM_HERE, error, Error::kOperationFailed,
"Connect Failed: Modem not started.");
NotifyCellularConnectionResult(*error, service->iccid(),
service->is_in_user_connect(), apn_type);
return;
}
if (StateIsConnected()) {
Error::PopulateAndLog(FROM_HERE, error, Error::kAlreadyConnected,
"Already connected; connection request ignored.");
NotifyCellularConnectionResult(*error, service->iccid(),
service->is_in_user_connect(), apn_type);
return;
}
if (ModemIsEnabledButNotRegistered() ||
capability_->IsSetInitialEpsBearerSettingsInProgress()) {
if (auto_connect_disabled) {
LOG(WARNING)
<< LoggingTag() << ": " << __func__
<< ": Waiting for Modem registration and auto connect is disabled";
return;
}
LOG(WARNING) << LoggingTag() << ": " << __func__
<< ": Waiting for Modem registration.";
SetPendingConnect(service->iccid());
return;
}
if (state_ != State::kRegistered) {
LOG(ERROR) << LoggingTag() << ": 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(), apn_type);
// If using an attach APN, send detailed metrics since |kNotRegistered| is
// a very common error when using Attach APNs.
if (service->GetLastAttachApn())
NotifyDetailedCellularConnectionResult(*error, apn_type,
*service->GetLastAttachApn());
return;
}
if (service->IsRoamingRuleViolated()) {
Error::PopulateAndLog(FROM_HERE, error, Error::kNotOnHomeNetwork,
"Connect Failed: Roaming disallowed.");
NotifyCellularConnectionResult(*error, service->iccid(),
service->is_in_user_connect(), apn_type);
return;
}
// Build default APN list, guaranteed to never be empty.
std::deque<Stringmap> apn_try_list = BuildDefaultApnTryList();
if (apn_try_list.empty()) {
Error::PopulateAndLog(FROM_HERE, error, Error::kInvalidApn,
"Connect Failed: No APNs provided.");
NotifyCellularConnectionResult(*error, service->iccid(),
service->is_in_user_connect(), apn_type);
return;
}
OnConnecting();
capability_->Connect(
apn_type, apn_try_list,
base::BindOnce(&Cellular::OnConnectReply, weak_ptr_factory_.GetWeakPtr(),
apn_type, 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(ApnList::ApnType apn_type,
std::string iccid,
bool is_user_triggered,
const Error& error) {
NotifyCellularConnectionResult(error, iccid, is_user_triggered, apn_type);
if (!error.IsSuccess()) {
LOG(WARNING) << LoggingTag() << ": " << __func__ << ": Failed: " << error;
if (service_ && service_->iccid() == iccid) {
// The error value is a combined value from the connect round robin,
// so it will not always be the error from the last connection attempt.
switch (error.type()) {
// We should not see Error::kThrottled here because kThrottled is
// mapped to InvalidApn since it's a |RetriableConnectError|.
case Error::kInvalidApn:
service_->SetFailure(Service::kFailureInvalidAPN);
break;
case Error::kNoCarrier:
service_->SetFailure(Service::kFailureOutOfRange);
break;
default:
service_->SetFailure(Service::kFailureConnect);
break;
}
}
// If we are connecting or disconnecting DUN as DEFAULT and an error happens
// in the reconnection procedure, the operation must be aborted right away.
if (IsTetheringOperationDunAsDefaultOngoing()) {
AbortTetheringOperation(error, base::DoNothing());
}
return;
}
// If we are connecting or disconnecting DUN as DEFAULT, we can now expect to
// complete the operation once a (default) Network connection update is
// received.
if (IsTetheringOperationDunAsDefaultOngoing()) {
tethering_operation_->apn_connected = true;
}
// Successful bearer connection, so store the APN type.
default_pdn_apn_type_ = apn_type;
metrics()->NotifyDeviceConnectFinished(interface_index());
OnConnected();
}
void Cellular::OnEnabled() {
SLOG(1) << LoggingTag() << ": " << __func__;
manager()->AddTerminationAction(
link_name(), base::BindOnce(&Cellular::StartTermination,
weak_ptr_factory_.GetWeakPtr()));
if (!enabled() && !enabled_pending()) {
LOG(WARNING) << LoggingTag() << ": 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(1) << LoggingTag() << ": " << __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;
}
if (IsTetheringOperationDunAsDefaultOngoing()) {
AbortTetheringOperation(Error(Error::kOperationFailed, reason),
base::DoNothing());
}
StopPPP();
explicit_disconnect_ = true;
capability_->DisconnectAll(base::BindOnce(&Cellular::OnDisconnectReply,
weak_ptr_factory_.GetWeakPtr()));
}
void Cellular::OnDisconnectReply(const Error& error) {
explicit_disconnect_ = false;
if (!error.IsSuccess()) {
LOG(WARNING) << LoggingTag() << ": " << __func__ << ": Failed: " << error;
OnDisconnectFailed();
return;
}
OnDisconnected();
}
void Cellular::OnDisconnected() {
SLOG(1) << LoggingTag() << ": " << __func__;
// The logic to reconnect the tethering APN as default involves the
// disconnection of the currently connected default APN. We explicitly ignore
// any additional action on this case, we don't want to do a full cleanup and
// report the full device as disconnected.
if (IsTetheringOperationDunAsDefaultOngoing()) {
LOG(INFO) << LoggingTag()
<< ": Disconnected during a DUN as DEFAULT tethering operation.";
return;
}
// Abort multiplexed tethering operation if the default network gets
// disconnected for any reason. The only way to abort this operation
// is by asking MM to disconnect all bearers, as we don't know yet which is
// the DUN specific bearer (because we're using the Simple.Connect() API).
// This should be fine because OnDisconnected() happens when the default PDN
// is disconnected, so we would only have the multiplexed tethering PDN
// attempt ongoing by the time we want to DisconnectAll().
if (IsTetheringOperationDunMultiplexedConnectOngoing()) {
if (capability_) {
capability_->DisconnectAll(base::DoNothing());
}
}
if (!DisconnectCleanup()) {
LOG(WARNING) << LoggingTag() << ": Disconnect occurred while in state "
<< GetStateString(state_);
}
}
void Cellular::OnDisconnectFailed() {
SLOG(1) << LoggingTag() << ": " << __func__;
// If the modem is in the disconnecting state, then the disconnect should
// eventually succeed, so do nothing.
if (modem_state_ == kModemStateDisconnecting) {
LOG(INFO) << LoggingTag()
<< ": 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) << LoggingTag()
<< ": 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.
}
bool Cellular::IsTetheringOperationDunAsDefaultOngoing() {
return (tethering_operation_ &&
((tethering_operation_->type ==
TetheringOperationType::kConnectDunAsDefaultPdn) ||
(tethering_operation_->type ==
TetheringOperationType::kDisconnectDunAsDefaultPdn)));
}
bool Cellular::IsTetheringOperationDunMultiplexedConnectOngoing() {
return (tethering_operation_ &&
(tethering_operation_->type ==
TetheringOperationType::kConnectDunMultiplexed));
}
bool Cellular::IsTetheringOperationDunMultiplexedDisconnectOngoing() {
return (tethering_operation_ &&
(tethering_operation_->type ==
TetheringOperationType::kDisconnectDunMultiplexed));
}
void Cellular::CompleteTetheringOperation(const Error& error) {
// Steal the operation info from the private member, as there are certain
// generic actions updated to ignore events if a tethering operation is
// ongoing.
CHECK(tethering_operation_);
auto operation = std::move(tethering_operation_);
tethering_operation_.reset();
// Report error.
if (!error.IsSuccess()) {
// A failure in a DUN as DEFAULT disconnection procedure means that the
// attempt to connect back to the DEFAULT APN failed, so we must report
// the modem as disconnected.
if (operation->type == TetheringOperationType::kDisconnectDunAsDefaultPdn) {
OnDisconnected();
}
LOG(WARNING) << LoggingTag() << ": Tethering operation failed: " << error;
std::move(operation->callback).Run(error);
return;
}
// On a successful completion of any DUN as DEFAULT operation (either
// connect or disconnect, the APN must have been connected.
if (operation->type == TetheringOperationType::kConnectDunAsDefaultPdn ||
operation->type == TetheringOperationType::kDisconnectDunAsDefaultPdn) {
CHECK(operation->apn_connected);
}
// Report success.
LOG(INFO) << LoggingTag() << ": Tethering operation successful";
std::move(operation->callback).Run(Error(Error::kSuccess));
}
void Cellular::NotifyCellularConnectionResultInTetheringOperation(
TetheringOperationType type, const Error& error) {
// Do nothing if there is no service. We need the service to know the current
// ICCID to include in the metrics report.
if (!service()) {
return;
}
// Connect errors will be reported in metrics exclusively on tethering
// connection operations, which are the ones triggered for the DUN APN type.
if (type != TetheringOperationType::kConnectDunAsDefaultPdn &&
type != TetheringOperationType::kConnectDunMultiplexed) {
return;
}
// Also worth noting, because the DUN as DEFAULT logic re-uses the generic
// connection setup callbacks, as soon as the connection operation is launched
// it is not expected to notify the result to metrics via this method.
NotifyCellularConnectionResult(error, service()->iccid(),
service()->is_in_user_connect(),
ApnList::ApnType::kDun);
}
bool Cellular::InitializeTetheringOperation(TetheringOperationType type,
ResultCallback callback) {
// If an attempt is already ongoing, for whatever reason, fail right away.
if (tethering_operation_) {
Error error(Error::kWrongState, "Already ongoing.");
NotifyCellularConnectionResultInTetheringOperation(type, error);
dispatcher()->PostTask(FROM_HERE,
base::BindOnce(std::move(callback), error));
return false;
}
// A capability must always exist at this point both for connections and
// disconnections.
if (!capability_) {
Error error(Error::kWrongState, "No capability.");
NotifyCellularConnectionResultInTetheringOperation(type, error);
dispatcher()->PostTask(FROM_HERE,
base::BindOnce(std::move(callback), error));
return false;
}
// If setting up a multiplexed tethering connection and the tethering
// specific Network already exists, fail right away.
if (type == TetheringOperationType::kConnectDunMultiplexed &&
multiplexed_tethering_pdn_) {
Error error(Error::kWrongState, "Multiplexed network already available.");
NotifyCellularConnectionResultInTetheringOperation(type, error);
dispatcher()->PostTask(FROM_HERE,
base::BindOnce(std::move(callback), error));
return false;
}
// Tethering connection attempt can go on.
LOG(INFO) << LoggingTag() << ": Tethering operation started";
tethering_operation_.emplace(type, std::move(callback));
tethering_operation_->apn_connected = false;
return true;
}
void Cellular::ConnectMultiplexedTetheringPdn(
AcquireTetheringNetworkResultCallback callback) {
CHECK(!callback.is_null());
LOG(INFO) << LoggingTag() << ": Tethering operation requested: "
<< "connect multiplexed DUN network.";
if (!InitializeTetheringOperation(
TetheringOperationType::kConnectDunMultiplexed,
base::BindOnce(&Cellular::OnConnectMultiplexedTetheringPdnReply,
weak_ptr_factory_.GetWeakPtr(),
std::move(callback)))) {
return;
}
// Will disconnect DUN as multiplexed PDN.
tethering_operation_->apn_type = ApnList::ApnType::kDun;
tethering_operation_->apn_try_list = BuildTetheringApnTryList();
CHECK(!tethering_operation_->apn_try_list.empty());
capability_->Connect(
tethering_operation_->apn_type, tethering_operation_->apn_try_list,
base::BindOnce(&Cellular::OnCapabilityConnectMultiplexedTetheringReply,
weak_ptr_factory_.GetWeakPtr()));
}
void Cellular::OnCapabilityConnectMultiplexedTetheringReply(
const Error& error) {
bool bearer_connected = error.IsSuccess();
// Report multiplexed DUN connection result metrics.
NotifyCellularConnectionResultInTetheringOperation(
TetheringOperationType::kConnectDunMultiplexed, error);
// If attempt was aborted, bail out.
if (!tethering_operation_) {
if (bearer_connected) {
// TODO(b/301919183): We should be able to cancel the attempt even before
// MM has created a bearer object, otherwise we'll end up in this state.
LOG(WARNING) << LoggingTag() << ": Multiplexed DUN bearer connected but"
<< " no attempt ongoing";
RunDisconnectMultiplexedTetheringPdn();
}
return;
}
// Bearer connection failed.
if (!bearer_connected) {
LOG(WARNING) << LoggingTag() << ": Tethering operation failed.";
AbortTetheringOperation(error, base::DoNothing());
return;
}
// If the device is disconnected or the service was lost, cleanup the
// possibly connected bearer and complete.
if (state_ != State::kLinked || !service() || !default_pdn_) {
AbortTetheringOperation(
Error(Error::kWrongState, "Default PDN must be connected"),
base::DoNothing());
return;
}
// Launch multiplexed tethering Network creation
LOG(INFO) << LoggingTag() << ": Tethering connection attempt successful.";
tethering_operation_->apn_connected = true;
// Not establishing the Network link if we're testing.
if (skip_establish_link_for_testing_) {
return;
}
EstablishMultiplexedTetheringLink();
if (!multiplexed_tethering_pdn_) {
AbortTetheringOperation(
Error(Error::kOperationFailed, "Multiplexed DUN bearer setup failed"),
base::DoNothing());
return;
}
// The tethering operation will be completed once the tethering network is
// connected (or an error returned in the process).
CHECK(!multiplexed_tethering_pdn_->network()->IsConnected());
LOG(INFO) << LoggingTag()
<< ": Multiplexed tethering connection not fully setup yet.";
}
void Cellular::DisconnectMultiplexedTetheringPdn(ResultCallback callback) {
CHECK(!callback.is_null());
LOG(INFO) << LoggingTag() << ": Tethering operation requested: "
<< "disconnect multiplexed DUN network.";
if (!InitializeTetheringOperation(
TetheringOperationType::kDisconnectDunMultiplexed,
std::move(callback))) {
return;
}
RunDisconnectMultiplexedTetheringPdn();
}
// This method may be called either during a normal user initiated tethering
// network release procedure, or also as fallback when the tethering network
// acquisition fails. In both cases, CompleteTetheringOperation() would be
// called after the capability Disconnect().
void Cellular::RunDisconnectMultiplexedTetheringPdn() {
multiplexed_tethering_pdn_.reset();
LOG(INFO) << LoggingTag() << ": Disconnecting multiplexed tethering network.";
capability_->Disconnect(
ApnList::ApnType::kDun,
base::BindOnce(&Cellular::OnCapabilityDisconnectMultiplexedTetheringReply,
weak_ptr_factory_.GetWeakPtr()));
}
void Cellular::OnCapabilityDisconnectMultiplexedTetheringReply(
const Error& error) {
if (error.IsFailure()) {
LOG(WARNING) << LoggingTag()
<< ": Multiplexed tethering disconnection failed: " << error;
}
if (IsTetheringOperationDunMultiplexedDisconnectOngoing()) {
// Disconnections are not aborted, we can simply complete.
CompleteTetheringOperation(error);
}
}
void Cellular::ConnectTetheringAsDefaultPdn(
AcquireTetheringNetworkResultCallback callback) {
CHECK(!callback.is_null());
LOG(INFO) << LoggingTag() << ": Tethering operation requested: "
<< "connect DUN as DEFAULT network.";
if (!InitializeTetheringOperation(
TetheringOperationType::kConnectDunAsDefaultPdn,
base::BindOnce(&Cellular::OnConnectTetheringAsDefaultPdnReply,
weak_ptr_factory_.GetWeakPtr(),
std::move(callback)))) {
return;
}
// Will disconnect DEFAULT and connect DUN as default.
tethering_operation_->apn_type = ApnList::ApnType::kDun;
tethering_operation_->apn_try_list = BuildTetheringApnTryList();
CHECK(!tethering_operation_->apn_try_list.empty());
RunTetheringOperationDunAsDefault();
}
void Cellular::DisconnectTetheringAsDefaultPdn(ResultCallback callback) {
CHECK(!callback.is_null());
LOG(INFO) << LoggingTag() << ": Tethering operation requested: "
<< "disconnect DUN as DEFAULT network.";
if (!InitializeTetheringOperation(
TetheringOperationType::kDisconnectDunAsDefaultPdn,
std::move(callback))) {
return;
}
// Will disconnect DUN as default and connect back DEFAULT.
tethering_operation_->apn_type = ApnList::ApnType::kDefault;
tethering_operation_->apn_try_list = BuildDefaultApnTryList();
CHECK(!tethering_operation_->apn_try_list.empty());
RunTetheringOperationDunAsDefault();
}
// Both operations to connect or disconnect DUN as DEFAULT involve the same
// steps: disconnect the current default and reconnect with a new APN try list.
// This method runs the logic for both operations in the same way. The only
// notable difference is that there is no ResultCallback in the disconnect
// operation.
void Cellular::RunTetheringOperationDunAsDefault() {
// Avoid going through the Disconnect() route because that involves a lot of
// cleanups that we shouldn't be doing while reconnecting with the tethering
// specific APNs. Instead, run our own disconnection logic, starting with
// the disconnection of all bearers (there should be one only either way).
explicit_disconnect_ = true;
capability_->DisconnectAll(
base::BindOnce(&Cellular::OnCapabilityDisconnectBeforeReconnectReply,
weak_ptr_factory_.GetWeakPtr()));
}
void Cellular::OnCapabilityDisconnectBeforeReconnectReply(const Error& error) {
explicit_disconnect_ = false;
// If tethering operation was aborted already, do nothing.
if (!tethering_operation_) {
return;
}
// A failure in the disconnection is assumed fatal.
if (!error.IsSuccess()) {
AbortTetheringOperation(error, base::DoNothing());
return;
}
// If service lost while attempt ongoing, abort right away.
if (!service()) {
AbortTetheringOperation(
Error(Error::kWrongState, "Tethering operation failed: no service."),
base::DoNothing());
return;
}
// Not a full cleanup, but we do transition the Service state out of a
// connected state, so that clients can rearrange their connections.
SetPrimaryMultiplexedInterface("");
default_pdn_apn_type_ = std::nullopt;
default_pdn_.reset();
SetServiceState(Service::kStateAssociating);
// We trigger a capability connect using a specific APN try list (which may
// e.g. be the DUN-specific try list). The generic OnConnectReply() is used so
// that it is treated as a standard connection attempt. From now on, the
// tethering operation will only be completed once the newly connected Network
// has been started.
capability_->Connect(
tethering_operation_->apn_type, tethering_operation_->apn_try_list,
base::BindOnce(&Cellular::OnConnectReply, weak_ptr_factory_.GetWeakPtr(),
tethering_operation_->apn_type, service()->iccid(),
false /* is_in_user_connect */));
metrics()->NotifyDeviceConnectStarted(interface_index());
}
void Cellular::ReuseDefaultPdnForTethering(
AcquireTetheringNetworkResultCallback callback) {
CHECK(!callback.is_null());
dispatcher()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), GetPrimaryNetwork(),
Error(Error::kSuccess)));
}
Cellular::TetheringOperationType Cellular::GetTetheringOperationType(
bool experimental_tethering, Error* out_error) {
Error error(Error::kSuccess);
if (!service_) {
error = Error(Error::kWrongState, "No service.");
} else if (!capability_) {
error = Error(Error::kWrongState, "No modem.");
} else if (inhibited_) {
error = Error(Error::kWrongState, "Inhibited.");
} else if (state_ != State::kLinked) {
error = Error(Error::kWrongState, "Default connection not available.");
} else if (!mobile_operator_info_->tethering_allowed(
experimental_tethering)) {
error = Error(Error::kWrongState, "Not allowed by operator.");
}
if (error.IsFailure()) {
if (out_error)
*out_error = error;
return TetheringOperationType::kFailed;
}
std::deque<Stringmap> tethering_apn_try_list = BuildTetheringApnTryList();
// No other APN is specified for tethering, we can reuse the DEFAULT for
// tethering as fallback.
if (tethering_apn_try_list.empty()) {
// This is a database error, an operator that flags "use_dun_apn_as_default"
// must also provide a separate APN of type DUN.
if (mobile_operator_info_->use_dun_apn_as_default()) {
if (out_error) {
*out_error = Error(
Error::kWrongState,
"Operator requires DUN APN as DEFAULT but no DUN APN configured");
}
return TetheringOperationType::kFailed;
}
LOG(INFO)
<< LoggingTag()
<< ": Tethering network selection: reusing default APN for tethering "
"as there is no DUN specific APN";
return TetheringOperationType::kReuseDefaultPdn;
}
// The currently connected APN is also flagged as DUN. If this APN is also in
// the list of tethering APNs, we can reuse it. This additional check is done
// to ensure that a user-defined APN doesn't override the DUN APN explicitly
// required by the operator (i.e. is_required_by_carrier_spec).
const Stringmap* last_good_apn_info = service_->GetLastGoodApn();
if (last_good_apn_info && ApnList::IsTetheringApn(*last_good_apn_info) &&
std::find(tethering_apn_try_list.begin(), tethering_apn_try_list.end(),
*last_good_apn_info) != tethering_apn_try_list.end()) {
LOG(INFO)
<< LoggingTag()
<< ": Tethering network selection: reusing default APN for tethering.";
return TetheringOperationType::kReuseDefaultPdn;
}
// A different APN is specified for tethering, and the operator requires the
// DUN APN to be used also as DEFAULT when tethering is enabled, so we must
// disconnect DEFAULT and reconnect DUN as DEFAULT.
if (mobile_operator_info_->use_dun_apn_as_default()) {
LOG(INFO) << LoggingTag()
<< ": Tethering network selection: "
"connecting DUN APN as default as required by operator.";
return TetheringOperationType::kConnectDunAsDefaultPdn;
}
// A different APN is specified for tethering, and the modem doesn't support
// multiplexing, so we must disconnect DEFAULT and reconnect DUN as DEFAULT.
if (!GetMultiplexSupport()) {
LOG(INFO)
<< LoggingTag()
<< ": Tethering network selection: "
"connecting DUN APN as default as multiplexing is unsupported.";
return TetheringOperationType::kConnectDunAsDefaultPdn;
}
// Connect DUN APN as additional multiplexed network
LOG(INFO) << LoggingTag()
<< ": Tethering network selection: "
"connecting multiplexed DUN APN.";
return TetheringOperationType::kConnectDunMultiplexed;
}
void Cellular::OnAcquireTetheringNetworkReady(
AcquireTetheringNetworkResultCallback callback,
Network* network,
const Error& error) {
SLOG(3) << __func__;
if (!error.IsSuccess()) {
tethering_event_callback_.Reset();
}
std::move(callback).Run(network, error);
}
void Cellular::AcquireTetheringNetwork(
TetheringManager::UpdateTimeoutCallback update_timeout_callback,
AcquireTetheringNetworkResultCallback callback,
TetheringManager::CellularUpstreamEventCallback tethering_event_callback,
bool experimental_tethering) {
SLOG(1) << LoggingTag() << ": " << __func__;
CHECK(!callback.is_null());
CHECK(!tethering_event_callback.is_null());
tethering_event_callback_ = std::move(tethering_event_callback);
auto internal_callback =
base::BindOnce(&Cellular::OnAcquireTetheringNetworkReady,
weak_ptr_factory_.GetWeakPtr(), std::move(callback));
Error error;
switch (GetTetheringOperationType(experimental_tethering, &error)) {
case TetheringOperationType::kConnectDunMultiplexed:
ConnectMultiplexedTetheringPdn(std::move(internal_callback));
return;
case TetheringOperationType::kConnectDunAsDefaultPdn:
// Request a longer start timeout as we need to go through a full
// PDN connection setup sequence.
update_timeout_callback.Run(kLongTetheringStartTimeout);
ConnectTetheringAsDefaultPdn(std::move(internal_callback));
return;
case TetheringOperationType::kReuseDefaultPdn:
ReuseDefaultPdnForTethering(std::move(internal_callback));
return;
case TetheringOperationType::kFailed:
dispatcher()->PostTask(
FROM_HERE,
base::BindOnce(std::move(internal_callback), nullptr, error));
return;
case TetheringOperationType::kDisconnectDunAsDefaultPdn:
case TetheringOperationType::kDisconnectDunMultiplexed:
// Not a valid return of GetTetheringOperationType().
NOTREACHED();
}
}
void Cellular::OnConnectTetheringAsDefaultPdnReply(
AcquireTetheringNetworkResultCallback callback, const Error& error) {
if (error.IsFailure()) {
LOG(WARNING) << LoggingTag()
<< ": Tethering network selection: failed to connect DUN APN "
"as default: "
<< error;
std::move(callback).Run(nullptr, error);
return;
}
LOG(INFO) << LoggingTag()
<< ": Tethering network selection: connected DUN APN as default.";
CHECK(default_pdn_);
std::move(callback).Run(default_pdn_->network(), Error(Error::kSuccess));
}
void Cellular::OnConnectMultiplexedTetheringPdnReply(
AcquireTetheringNetworkResultCallback callback, const Error& error) {
if (error.IsFailure()) {
LOG(WARNING) << LoggingTag()
<< ": Tethering network selection: failed to connect "
"multiplexed DUN APN: "
<< error;
std::move(callback).Run(nullptr, error);
return;
}
LOG(INFO) << LoggingTag()
<< ": Tethering network selection: connected multiplexed DUN APN.";
CHECK(multiplexed_tethering_pdn_);
std::move(callback).Run(multiplexed_tethering_pdn_->network(),
Error(Error::kSuccess));
}
void Cellular::AbortTetheringOperation(const Error& error,
ResultCallback callback) {
if (!tethering_operation_) {
LOG(ERROR) << LoggingTag() << ": no ongoing tethering operation to abort.";
dispatcher()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), Error(Error::kSuccess)));
return;
}
LOG(INFO) << LoggingTag() << ": Tethering operation aborted: " << error;
// Decide whether aborting the operation will require additional procedures
// after completing it.
bool disconnect_dun_as_default = false;
bool disconnect_multiplexed_dun = false;
if (tethering_operation_->type ==
TetheringOperationType::kConnectDunAsDefaultPdn) {
SLOG(2) << LoggingTag() << ": Need to disconnect DUN as DEFAULT.";
disconnect_dun_as_default = true;
} else if (tethering_operation_->type ==
TetheringOperationType::kConnectDunMultiplexed) {
SLOG(2) << LoggingTag() << ": Need to disconnect multiplexed DUN.";
disconnect_multiplexed_dun = true;
}
// Abort and complete the original operation.
LOG(INFO) << LoggingTag()
<< ": Completing tethering operation during abort sequence.";
CompleteTetheringOperation(error);
// If needed, launch a new disconnection attempt. The disconnection logic
// needs to be able to recover the state with tethering disabled from any
// point in the logic.
if (disconnect_dun_as_default) {
LOG(INFO) << LoggingTag()
<< ": Disconnecting DUN as DEFAULT during abort sequence.";
DisconnectTetheringAsDefaultPdn(std::move(callback));
} else if (disconnect_multiplexed_dun) {
LOG(INFO) << LoggingTag()
<< ": Disconnecting multiplexed DUN during abort sequence.";
DisconnectMultiplexedTetheringPdn(std::move(callback));
}
}
void Cellular::ReleaseTetheringNetwork(Network* network,
ResultCallback callback) {
SLOG(1) << LoggingTag() << ": " << __func__;
CHECK(!callback.is_null());
tethering_event_callback_.Reset();
// No explicit network given, which means there is an ongoing tethering
// network acquisition operation that needs to be aborted.
if (!network) {
// The abort logic is exclusively used during a connection attempt, because
// it allows us to go back to the default state without tethering enabled.
// There is no point in trying to abort a tethering disconnection attempt,
// unless it is an error handled internally.
if (tethering_operation_ &&
(tethering_operation_->type ==
TetheringOperationType::kDisconnectDunMultiplexed ||
tethering_operation_->type ==
TetheringOperationType::kDisconnectDunAsDefaultPdn)) {
LOG(WARNING) << LoggingTag() << ": Ignoring tethering abort request while"
<< " disconnecting operation is ongoing.";
dispatcher()->PostTask(FROM_HERE, base::BindOnce(std::move(callback),
Error(Error::kSuccess)));
return;
}
AbortTetheringOperation(Error(Error::kOperationAborted, "Aborted."),
std::move(callback));
return;
}
// If we connected the tethering APN as default, we need to disconnect it and
// reconnect with the default APN.
if (default_pdn_apn_type_ &&
*default_pdn_apn_type_ == ApnList::ApnType::kDun) {
// Validate that the network requested to disconnect is the one we expect.
if (!default_pdn_ || default_pdn_->network() != network) {
dispatcher()->PostTask(
FROM_HERE,
base::BindOnce(std::move(callback),
Error(Error::kWrongState,
"Unexpected default network to release")));
return;
}
DisconnectTetheringAsDefaultPdn(std::move(callback));
return;
}
// If we connected a multiplexed tethering APN, disconnect it here.
if (multiplexed_tethering_pdn_) {
// Validate that the network requested to disconnect is the one we expect.
if (multiplexed_tethering_pdn_->network() != network) {
dispatcher()->PostTask(
FROM_HERE,
base::BindOnce(std::move(callback),
Error(Error::kWrongState,
"Unexpected multiplexed network to release")));
return;
}
DisconnectMultiplexedTetheringPdn(std::move(callback));
return;
}
// We had reused the default PDN, so nothing to do.
dispatcher()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), Error(Error::kSuccess)));
}
void Cellular::EstablishLink() {
if (skip_establish_link_for_testing_) {
return;
}
SLOG(2) << LoggingTag() << ": " << __func__;
CHECK_EQ(State::kConnected, state_);
CHECK(capability_);
if (!default_pdn_apn_type_) {
LOG(WARNING) << LoggingTag() << ": Disconnecting due to missing APN type.";
Disconnect(nullptr, "missing APN type");
return;
}
CellularBearer* bearer = capability_->GetActiveBearer(*default_pdn_apn_type_);
if (!bearer) {
LOG(WARNING) << LoggingTag()
<< ": Disconnecting due to missing active bearer.";
Disconnect(nullptr, "missing active bearer");
return;
}
// The APN type is ensured to be one by GetActiveBearer()
CHECK_EQ(bearer->apn_types().size(), 1UL);
CHECK_EQ(bearer->apn_types()[0], *default_pdn_apn_type_);
if (bearer->ipv4_config_method() == CellularBearer::IPConfigMethod::kPPP) {
LOG(INFO) << LoggingTag() << ": Start PPP connection on "
<< bearer->data_interface();
StartPPP(bearer->data_interface());
return;
}
// ModemManager specifies which is the network interface that has been
// connected at this point, which may be either the same interface that was
// used to reference this Cellular device, or a completely different one.
LOG(INFO) << LoggingTag() << ": Establish link on "
<< bearer->data_interface();
// Create default network
default_pdn_ = std::make_unique<NetworkInfo>(
manager()->network_manager(), this, bearer->dbus_path(),
rtnl_handler()->GetInterfaceIndex(bearer->data_interface()),
bearer->data_interface());
// Start the link listener, which will ensure the initial link state for the
// data interface is notified.
StartLinkListener();
// Set state to associating.
OnConnecting();
}
void Cellular::EstablishMultiplexedTetheringLink() {
CHECK_EQ(State::kLinked, state_);
CHECK(capability_);
// The multiplexed DUN bearer selection only works if the current default PDN
// APN type is not DUN. This should be ensured by the tethering enablement
// logic, so we can assert the assumption.
CHECK(default_pdn_apn_type_);
CHECK_NE(*default_pdn_apn_type_, ApnList::ApnType::kDun);
// Do nothing if there is no tethering bearer to setup.
CellularBearer* bearer = capability_->GetActiveBearer(ApnList::ApnType::kDun);
if (!bearer) {
return;
}
SLOG(2) << LoggingTag() << ": " << __func__;
// The APN type is ensured to be one by GetActiveBearer()
CHECK_EQ(bearer->apn_types().size(), 1UL);
CHECK_EQ(bearer->apn_types()[0], ApnList::ApnType::kDun);
if (bearer->ipv4_config_method() == CellularBearer::IPConfigMethod::kPPP) {
LOG(WARNING) << LoggingTag() << ": No PPP support for tethering link";
return;
}
LOG(INFO) << LoggingTag() << ": Establish tethering link on "
<< bearer->data_interface();
// Create multiplexed tethering network
multiplexed_tethering_pdn_ = std::make_unique<NetworkInfo>(
manager()->network_manager(), this, bearer->dbus_path(),
rtnl_handler()->GetInterfaceIndex(bearer->data_interface()),
bearer->data_interface());
// Start the link listener, which will ensure the initial link state for the
// data interface is notified.
StartLinkListener();
// Unlike with the default PDN, we don't update the device state in any way at
// this point. The multiplexed DUN acquisition operation will continue with
// the link up event.
}
void Cellular::DefaultLinkUp() {
if (default_pdn_->link_state() == LinkState::kUp) {
SLOG(3) << LoggingTag() << ": Default link is up.";
return;
}
default_pdn_->SetLinkState(LinkState::kUp);
LOG(INFO) << LoggingTag() << ": Default link is up: configuring network";
CHECK(capability_);
if (!default_pdn_apn_type_) {
LOG(INFO) << LoggingTag() << ": Default link APN type unknown";
Disconnect(nullptr, "missing default link APN type.");
return;
}
if (!default_pdn_->Configure(
capability_->GetActiveBearer(*default_pdn_apn_type_))) {
LOG(INFO) << LoggingTag() << ": Default link network configuration failed";
Disconnect(nullptr, "link configuration failed.");
return;
}
SetPrimaryMultiplexedInterface(default_pdn_->network()->interface_name());
SetState(State::kLinked);
SelectService(service_);
SetServiceState(Service::kStateConfiguring);
default_pdn_->Start();
}
void Cellular::DefaultLinkDown() {
if (explicit_disconnect_) {
SLOG(3) << LoggingTag() << ": Default link is down during disconnection";
return;
}
LinkState old_state = default_pdn_->link_state();
default_pdn_->SetLinkState(LinkState::kDown);
// LinkState::kUnknown is the initial state before the first dump
if (old_state == LinkState::kUnknown) {
LOG(INFO) << LoggingTag() << ": Default link is down, bringing up.";
rtnl_handler()->SetInterfaceFlags(
default_pdn_->network()->interface_index(), IFF_UP, IFF_UP);
return;
}
if (old_state == LinkState::kUp) {
LOG(INFO) << LoggingTag() << ": Default link is down, disconnecting.";
Disconnect(nullptr, "link is down.");
return;
}
SLOG(3) << LoggingTag() << ": Default link is down.";
}
void Cellular::DefaultLinkDeleted() {
LOG(INFO) << LoggingTag() << ": Default link is deleted.";
default_pdn_->SetLinkState(LinkState::kUnknown);
// If not multiplexing, this is an indication that the cellular device is gone
// from the system. If multiplexing, just a no-op.
if (default_pdn_->network()->interface_index() == interface_index()) {
DestroyAllServices();
}
}
void Cellular::MultiplexedTetheringLinkUp() {
if (multiplexed_tethering_pdn_->link_state() == LinkState::kUp) {
SLOG(3) << LoggingTag() << ": Multiplexed tethering link is up.";
return;
}
multiplexed_tethering_pdn_->SetLinkState(LinkState::kUp);
LOG(INFO) << LoggingTag()
<< ": Multiplexed tethering link is up: configuring network";
CHECK(capability_);
// The multiplexed DUN bearer selection only works if the current default PDN
// APN type is not DUN. This should be ensured by the tethering enablement
// logic, so we can assert the assumption.
CHECK(default_pdn_apn_type_);
CHECK_NE(*default_pdn_apn_type_, ApnList::ApnType::kDun);
if (!multiplexed_tethering_pdn_->Configure(
capability_->GetActiveBearer(ApnList::ApnType::kDun))) {
LOG(INFO) << LoggingTag()
<< ": Multiplexed tethering link network configuration failed";
if (IsTetheringOperationDunMultiplexedConnectOngoing()) {
AbortTetheringOperation(
Error(Error::kOperationFailed, "Link configuration failed."),
base::DoNothing());
return;
}
}
LOG(INFO) << LoggingTag()
<< ": Multiplexed tethering network configuration ready.";
multiplexed_tethering_pdn_->Start();
LOG(INFO) << LoggingTag() << ": Multiplexed tethering network started.";
// Network not connected yet, need to wait for OnConnectionUpdated().
CHECK(!multiplexed_tethering_pdn_->network()->IsConnected());
}
void Cellular::MultiplexedTetheringLinkDown() {
LinkState old_state = multiplexed_tethering_pdn_->link_state();
multiplexed_tethering_pdn_->SetLinkState(LinkState::kDown);
// LinkState::kUnknown is the initial state before the first dump
if (old_state == LinkState::kUnknown) {
LOG(INFO) << LoggingTag()
<< ": Multiplexed tethering link is down, bringing up.";
rtnl_handler()->SetInterfaceFlags(
multiplexed_tethering_pdn_->network()->interface_index(), IFF_UP,
IFF_UP);
return;
}
if (old_state == LinkState::kUp) {
LOG(INFO) << LoggingTag()
<< ": Multiplexed tethering link is down, disconnecting.";
RunDisconnectMultiplexedTetheringPdn();
return;
}
SLOG(3) << LoggingTag() << ": Multiplexed tethering link is down.";
}
void Cellular::MultiplexedTetheringLinkDeleted() {
LOG(INFO) << LoggingTag() << ": Multiplexed tethering link is deleted.";
multiplexed_tethering_pdn_->SetLinkState(LinkState::kUnknown);
}
void Cellular::LinkMsgHandler(const net_base::RTNLMessage& msg) {
DCHECK(msg.type() == net_base::RTNLMessage::kTypeLink);
int data_interface_index = msg.interface_index();
// Actions on the default APN Network
if (default_pdn_ &&
data_interface_index == default_pdn_->network()->interface_index()) {
if (msg.mode() == net_base::RTNLMessage::kModeDelete) {
DefaultLinkDeleted();
} else if (msg.mode() == net_base::RTNLMessage::kModeAdd) {
if (msg.link_status().flags & IFF_UP) {
DefaultLinkUp();
} else {
DefaultLinkDown();
}
} else {
LOG(WARNING) << LoggingTag()
<< ": Unexpected link message mode: " << msg.mode();
}
}
// Actions on the tethering APN Network
if (multiplexed_tethering_pdn_ &&
data_interface_index ==
multiplexed_tethering_pdn_->network()->interface_index()) {
if (msg.mode() == net_base::RTNLMessage::kModeDelete) {
MultiplexedTetheringLinkDeleted();
} else if (msg.mode() == net_base::RTNLMessage::kModeAdd) {
if (msg.link_status().flags & IFF_UP) {
MultiplexedTetheringLinkUp();
} else {
MultiplexedTetheringLinkDown();
}
} else {
LOG(WARNING) << LoggingTag()
<< ": Unexpected link message mode: " << msg.mode();
}
}
}
void Cellular::StopLinkListener() {
link_listener_.reset(nullptr);
}
void Cellular::StartLinkListener() {
SLOG(2) << LoggingTag() << ": Started RTNL listener";
if (!link_listener_) {
link_listener_ = std::make_unique<net_base::RTNLListener>(
net_base::RTNLHandler::kRequestLink,
base::BindRepeating(&Cellular::LinkMsgHandler, base::Unretained(this)));
}
rtnl_handler()->RequestDump(net_base::RTNLHandler::kRequestLink);
}
void Cellular::SetInitialProperties(const InterfaceToProperties& properties) {
if (!capability_) {
LOG(WARNING) << LoggingTag() << ": SetInitialProperties with no Capability";
initial_properties_ = properties;
return;
}
capability_->SetInitialProperties(properties);
}
void Cellular::OnModemStateChanged(ModemState new_state) {
ModemState old_modem_state = modem_state_;
if (old_modem_state == new_state) {
SLOG(3) << LoggingTag()
<< ": The new state matches the old state. Nothing to do.";
return;
}
SLOG(1) << LoggingTag() << ": " << __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) << LoggingTag()
<< ": 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();
}
// Ignore state change actions while we're reconnecting the tethering APN
// as default network.
if (IsTetheringOperationDunAsDefaultOngoing()) {
SLOG(1) << LoggingTag() << ": " << __func__
<< ": ignoring actions upon new state: tethering attempt ongoing";
return;
}
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(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:
case kModemStateConnecting:
break;
case kModemStateConnected:
// Even if the modem state transitions from Connecting to Connected here
// we don't report the cellular object as Connected yet; we require the
// actual connection attempt operation to finish.
break;
}
}
bool Cellular::IsActivating() const {
return capability_ && capability_->IsActivating();
}
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) << LoggingTag() << ": " << __func__ << ": " << policy_allow_roaming_
<< "->" << value;
policy_allow_roaming_ = value;
adaptor()->EmitBoolChanged(kCellularPolicyAllowRoamingProperty, value);
manager()->UpdateDevice(this);
if (service_ && service_->IsRoamingRuleViolated()) {
Disconnect(nullptr, "policy updated: roaming rule violated");
}
return true;
}
bool Cellular::SetUseAttachApn(const bool& value, Error* error) {
LOG(INFO) << __func__;
// |use_attach_apn_ | is deprecated. its default value should be true.
if (!value)
return false;
return true;
}
bool Cellular::GetInhibited(Error* error) {
return inhibited_;
}
bool Cellular::SetInhibited(const bool& inhibited, Error* error) {
if (inhibited == inhibited_) {
LOG(WARNING) << LoggingTag() << ": " << __func__
<< ": State already set, ignoring request.";
return false;
}
LOG(INFO) << LoggingTag() << ": " << __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(WARNING) << LoggingTag() << ": " << __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(2) << LoggingTag() << ": " << __func__;
OnBeforeSuspend(base::BindOnce(&Cellular::OnTerminationCompleted,
weak_ptr_factory_.GetWeakPtr()));
}
void Cellular::OnTerminationCompleted(const Error& error) {
LOG(INFO) << LoggingTag() << ": " << __func__ << ": " << error;
manager()->TerminationActionComplete(link_name());
manager()->RemoveTerminationAction(link_name());
}
bool Cellular::DisconnectCleanup() {
SLOG(2) << LoggingTag() << ": " << __func__;
DestroySockets();
if (!StateIsConnected())
return false;
StopLinkListener();
SetState(State::kRegistered);
SetServiceFailureSilent(Service::kFailureNone);
SetPrimaryMultiplexedInterface("");
default_pdn_apn_type_ = std::nullopt;
default_pdn_.reset();
multiplexed_tethering_pdn_.reset();
ResetCarrierEntitlement();
return true;
}
void Cellular::ResetCarrierEntitlement() {
carrier_entitlement_->Reset();
if (!entitlement_check_callback_.is_null()) {
std::move(entitlement_check_callback_)
.Run(TetheringManager::EntitlementStatus::kNotAllowed);
}
}
// 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() {
base::FilePath modem_reset_path = GetQ6V5ModemResetPath();
if (!base::PathExists(modem_reset_path)) {
PLOG(ERROR) << LoggingTag()
<< ": Unable to find sysfs file to reset modem.";
return false;
}
int fd = HANDLE_EINTR(open(modem_reset_path.value().c_str(),
O_WRONLY | O_NONBLOCK | O_CLOEXEC));
if (fd < 0) {
PLOG(ERROR) << LoggingTag()
<< ": Failed to open sysfs file to reset modem.";
return false;
}
base::ScopedFD scoped_fd(fd);
if (!base::WriteFileDescriptor(scoped_fd.get(), "stop")) {
PLOG(ERROR) << LoggingTag() << ": Failed to stop modem";
return false;
}
usleep(kModemResetTimeout.InMicroseconds());
if (!base::WriteFileDescriptor(scoped_fd.get(), "start")) {
PLOG(ERROR) << LoggingTag() << ": Failed to start modem";
return false;
}
return true;
}
base::FilePath Cellular::GetQ6V5ModemResetPath() {
base::FilePath modem_reset_path, driver_path;
base::FileEnumerator it(
base::FilePath(kQ6V5SysfsBasePath), false,
base::FileEnumerator::FILES | base::FileEnumerator::SHOW_SYM_LINKS,
kQ6V5RemoteprocPattern);
for (base::FilePath name = it.Next(); !name.empty(); name = it.Next()) {
if (base::ReadSymbolicLink(name.Append("device/driver"), &driver_path) &&
driver_path.BaseName() == base::FilePath(kQ6V5DriverName)) {
modem_reset_path = name.Append("state");
break;
}
}
return modem_reset_path;
}
bool Cellular::IsQ6V5Modem() {
// Check if manufacturer is equal to "QUALCOMM INCORPORATED" and
// if one of the remoteproc[0-9]/device/driver in sysfs links
// to "qcom-q6v5-mss".
return (manufacturer_ == kQ6V5ModemManufacturerName &&
base::PathExists(GetQ6V5ModemResetPath()));
}
void Cellular::StartPPP(const std::string& serial_device) {
SLOG(2) << LoggingTag() << ": " << __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());
DropConnectionDefault(); // Don't redirect to PPPDevice.
service_->SetState(original_state);
} else {
// There should be no regular cellular network connected
DCHECK(!default_pdn_);
}
PPPDaemon::DeathCallback death_callback(
base::BindOnce(&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, std::move(death_callback), &error));
if (new_ppp_task) {
SLOG(1) << LoggingTag() << ": Forked pppd process.";
ppp_task_ = std::move(new_ppp_task);
}
}
void Cellular::StopPPP() {
SLOG(2) << LoggingTag() << ": " << __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(2) << LoggingTag() << ": " << __func__;
if (!service()) {
LOG(ERROR) << LoggingTag() << ": " << __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(2) << LoggingTag() << ": " << __func__ << ": " << reason;
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 if (reason == kPPPReasonExit) {
// Ignore; we get its exit status by the death callback for PPPDaemon.
} else {
NOTREACHED();
}
}
void Cellular::OnPPPAuthenticated() {
SLOG(2) << LoggingTag() << ": " << __func__;
is_ppp_authenticating_ = false;
}
void Cellular::OnPPPAuthenticating() {
SLOG(2) << LoggingTag() << ": " << __func__;
is_ppp_authenticating_ = true;
}
bool Cellular::GetForceInitEpsBearerSettings() {
return force_init_eps_bearer_settings_;
}
void Cellular::SetForceInitEpsBearerSettings(bool force) {
SLOG(2) << LoggingTag() << ": " << __func__ << " force: " << std::boolalpha
<< force;
force_init_eps_bearer_settings_ = force;
}
void Cellular::OnPPPConnected(
const std::map<std::string, std::string>& params) {
SLOG(2) << LoggingTag() << ": " << __func__;
std::string interface_name = PPPDaemon::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_ = device_info->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_);
auto network_config = std::make_unique<net_base::NetworkConfig>(
PPPDaemon::ParseNetworkConfig(params));
ppp_device_->UpdateNetworkConfig(std::move(network_config));
}
void Cellular::OnPPPDied(pid_t pid, int exit) {
SLOG(1) << LoggingTag() << ": " << __func__;
ppp_task_.reset();
if (is_ppp_authenticating_) {
SetServiceFailure(Service::kFailurePPPAuth);
} else {
SetServiceFailure(PPPDaemon::ExitStatusToFailure(exit));
}
Disconnect(nullptr, "unexpected pppd exit");
}
bool Cellular::ModemIsEnabledButNotRegistered() {
// Normally the Modem becomes Registered immediately after becoming enabled.
// In cases where we have an attach APN or eSIM this may not be true. See
// b/204847937 and b/205882451 for more details.
// TODO(b/186482862): Fix this behavior in ModemManager.
return (state_ == State::kEnabled || state_ == State::kModemStarting ||
state_ == State::kModemStarted) &&
(modem_state_ == kModemStateEnabled ||
modem_state_ == kModemStateSearching);
}
void Cellular::SetPendingConnect(const std::string& iccid) {
if (iccid == connect_pending_iccid_)
return;
if (!connect_pending_iccid_.empty()) {
SLOG(1) << LoggingTag()
<< ": Cancelling pending connect to: " << connect_pending_iccid_;
ConnectToPendingFailed(Service::kFailureDisconnect);
}
connect_pending_callback_.Cancel();
connect_pending_iccid_ = iccid;
if (iccid.empty())
return;
SLOG(1) << LoggingTag() << ": 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::BindOnce(
&Cellular::ConnectToPendingCancel, weak_ptr_factory_.GetWeakPtr()));
dispatcher()->PostDelayedTask(FROM_HERE, connect_cancel_callback_.callback(),
kPendingConnectCancel);
}
void Cellular::ConnectToPending() {
if (connect_pending_iccid_.empty() ||
!connect_pending_callback_.IsCancelled()) {
return;
}
if (inhibited_) {
SLOG(1) << LoggingTag() << ": " << __func__ << ": Inhibited";
return;
}
if (scanning_) {
SLOG(1) << LoggingTag() << ": " << __func__ << ": Scanning";
return;
}
if (modem_state_ == kModemStateLocked) {
// Check the lock type and set the failure appropriately
KeyValueStore sim_lock_status = GetSimLockStatus(nullptr);
std::string lock_type =
sim_lock_status.Get<std::string>(kSIMLockTypeProperty);
if (lock_type == kSIMLockNetworkPin)
ConnectToPendingFailed(Service::kFailureSimCarrierLocked);
else
ConnectToPendingFailed(Service::kFailureSimLocked);
return;
}
if (ModemIsEnabledButNotRegistered()) {
LOG(WARNING) << LoggingTag() << ": " << __func__
<< ": Waiting for Modem registration.";
return;
}
if (!StateIsRegistered()) {
LOG(WARNING) << LoggingTag() << ": " << __func__
<< ": Cellular not registered, State: "
<< GetStateString(state_);
ConnectToPendingFailed(Service::kFailureNotRegistered);
return;
}
if (modem_state_ != kModemStateRegistered) {
LOG(WARNING) << LoggingTag() << ": " << __func__
<< ": Modem not registered, State: "
<< GetModemStateString(modem_state_);
ConnectToPendingFailed(Service::kFailureNotRegistered);
return;
}
SLOG(1) << LoggingTag() << ": " << __func__ << ": " << connect_pending_iccid_;
connect_cancel_callback_.Cancel();
connect_pending_callback_.Reset(base::BindOnce(
&Cellular::ConnectToPendingAfterDelay, weak_ptr_factory_.GetWeakPtr()));
dispatcher()->PostDelayedTask(FROM_HERE, connect_pending_callback_.callback(),
kPendingConnectDelay);
}
void Cellular::ConnectToPendingAfterDelay() {
SLOG(1) << LoggingTag() << ": " << __func__ << ": " << connect_pending_iccid_;
std::string pending_iccid;
if (connect_pending_iccid_ == kUnknownIccid) {
// Connect to the current iccid if we want to connect to an unknown
// iccid. This usually occurs when the inactive slot's iccid is unknown, but
// we want to connect to it after a slot switch.
pending_iccid = iccid_;
} else {
pending_iccid = connect_pending_iccid_;
}
// Clear pending connect request regardless of whether a service is found.
connect_pending_iccid_.clear();
CellularServiceRefPtr service =
manager()->cellular_service_provider()->FindService(pending_iccid);
if (!service) {
LOG(WARNING) << LoggingTag()
<< ": No matching service for pending connect.";
return;
}
Error error;
LOG(INFO) << LoggingTag() << ": Connecting to pending Cellular Service: "
<< service->log_name();
service->Connect(&error, "Pending connect");
if (!error.IsSuccess())
service->SetFailure(Service::kFailureDelayedConnectSetup);
}
void Cellular::ConnectToPendingFailed(Service::ConnectFailure failure) {
if (!connect_pending_iccid_.empty()) {
SLOG(1) << LoggingTag() << ": " << __func__ << ": "
<< connect_pending_iccid_
<< " Failure: " << Service::ConnectFailureToString(failure);
CellularServiceRefPtr service =
manager()->cellular_service_provider()->FindService(
connect_pending_iccid_);
bool is_user_triggered = false;
if (service) {
service->SetFailure(failure);
is_user_triggered = service->is_in_user_connect();
}
// populate the error for the sake of metrics
Error error;
switch (failure) {
case Service::kFailureNotRegistered:
error.Populate(Error::kNotRegistered);
break;
case Service::kFailureDisconnect:
error.Populate(Error::kOperationAborted);
break;
case Service::kFailureSimLocked:
error.Populate(Error::kPinRequired);
break;
default:
error.Populate(Error::kOperationFailed);
break;
}
NotifyCellularConnectionResult(std::move(error), connect_pending_iccid_,
is_user_triggered,
ApnList::ApnType::kDefault);
}
connect_cancel_callback_.Cancel();
connect_pending_callback_.Cancel();
connect_pending_iccid_.clear();
}
void Cellular::ConnectToPendingCancel() {
LOG(WARNING) << LoggingTag() << ": " << __func__;
ConnectToPendingFailed(Service::kFailureNotRegistered);
}
void Cellular::UpdateScanning() {
bool scanning;
switch (state_) {
case State::kDisabled:
scanning = false;
break;
case State::kPoweredOff:
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_);
store->RegisterConstString(kPrimaryMultiplexedInterfaceProperty,
&primary_multiplexed_interface_);
HelpRegisterConstDerivedString(kTechnologyFamilyProperty,
&Cellular::GetTechnologyFamily);
HelpRegisterConstDerivedString(kDeviceIdProperty, &Cellular::GetDeviceId);
HelpRegisterDerivedBool(kCellularPolicyAllowRoamingProperty,
&Cellular::GetPolicyAllowRoaming,
&Cellular::SetPolicyAllowRoaming);
// TODO(b/277792069): Remove when Chrome removes the attach APN code.
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,
net_base::MacAddress mac_address) {
if (dbus_path_ == dbus_path) {
SLOG(1) << LoggingTag() << ": " << __func__
<< ": Skipping update. Same dbus_path provided: "
<< dbus_path.value();
return;
}
LOG(INFO) << LoggingTag() << ": " << __func__
<< ": Modem Path: " << dbus_path.value();
SetDbusPath(dbus_path);
SetModemState(kModemStateUnknown);
set_mac_address(mac_address);
CreateCapability();
}
const std::string& Cellular::GetSimCardId() const {
if (!eid_.empty())
return eid_;
return iccid_;
}
bool Cellular::HasIccid(const std::string& iccid) const {
if (iccid == iccid_)
return true;
for (const SimProperties& sim_properties : sim_slot_properties_) {
if (sim_properties.iccid == iccid) {
return true;
}
}
return false;
}
void Cellular::SetSimProperties(
const std::vector<SimProperties>& sim_properties, size_t primary_slot) {
LOG(INFO) << LoggingTag() << ": " << __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) << LoggingTag() << ": 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();
}
void Cellular::OnProfilesChanged() {
if (!service_) {
LOG(ERROR) << LoggingTag()
<< ": 3GPP profiles were updated with no service.";
return;
}
// Rebuild the APN try list.
OnOperatorChanged();
if (!StateIsConnected()) {
return;
}
LOG(INFO) << LoggingTag() << ": Reconnecting for OTA profile update.";
Disconnect(nullptr, "OTA profile update");
SetPendingConnect(service_->iccid());
}
bool Cellular::CompareApns(const Stringmap& apn1, const Stringmap& apn2) const {
static const std::string always_ignore_keys[] = {
cellular::kApnVersionProperty, kApnNameProperty,
kApnLanguageProperty, kApnSourceProperty,
kApnLocalizedNameProperty, kApnIsRequiredByCarrierSpecProperty,
kApnProfileIdProperty};
std::set<std::string> ignore_keys{std::begin(always_ignore_keys),
std::end(always_ignore_keys)};
// Enforce the APN keys so that developers explicitly define the behavior
// for each key in this function.
static const std::string only_allowed_keys[] = {
kApnProperty, kApnTypesProperty, kApnUsernameProperty,
kApnPasswordProperty, kApnAuthenticationProperty, kApnIpTypeProperty,
kApnAttachProperty};
std::set<std::string> allowed_keys{std::begin(only_allowed_keys),
std::end(only_allowed_keys)};
for (auto const& pair : apn1) {
if (ignore_keys.count(pair.first))
continue;
DCHECK(allowed_keys.count(pair.first)) << " key: " << pair.first;
if (!base::Contains(apn2, pair.first) || pair.second != apn2.at(pair.first))
return false;
// Keys match, ignore them below.
ignore_keys.insert(pair.first);
}
// Find keys in apn2 which are not in apn1.
for (auto const& pair : apn2) {
DCHECK(allowed_keys.count(pair.first) || ignore_keys.count(pair.first))
<< " key: " << pair.first;
if (ignore_keys.count(pair.first) == 0)
return false;
}
return true;
}
std::deque<Stringmap> Cellular::BuildAttachApnTryList() const {
std::deque<Stringmap> try_list = BuildApnTryList(ApnList::ApnType::kAttach);
if (try_list.size() > 1) {
// When multiple Attach APNs are present, shill should fall back to the
// default one(first in the list) if all of them fail to register.
try_list.emplace_back(try_list.front());
PrintApnListForDebugging(try_list, ApnList::ApnType::kAttach);
}
return try_list;
}
std::deque<Stringmap> Cellular::BuildDefaultApnTryList() const {
return BuildApnTryList(ApnList::ApnType::kDefault);
}
std::deque<Stringmap> Cellular::BuildTetheringApnTryList() const {
return BuildApnTryList(ApnList::ApnType::kDun);
}
bool Cellular::IsRequiredByCarrierApn(const Stringmap& apn) const {
// Only check the property in MODB APNs to avoid getting into a situation in
// which the UI or user send the property by mistake and the UI cannot
// update the APN list because there is an existing APN with the property
// set to true.
return base::Contains(apn, kApnSourceProperty) &&
apn.at(kApnSourceProperty) == kApnSourceMoDb &&
base::Contains(apn, kApnIsRequiredByCarrierSpecProperty) &&
apn.at(kApnIsRequiredByCarrierSpecProperty) ==
kApnIsRequiredByCarrierSpecTrue;
}
bool Cellular::RequiredApnExists(ApnList::ApnType apn_type) const {
for (auto apn : apn_list_) {
if (ApnList::IsApnType(apn, apn_type) && IsRequiredByCarrierApn(apn))
return true;
}
return false;
}
std::deque<Stringmap> Cellular::BuildApnTryList(
ApnList::ApnType apn_type) const {
std::deque<Stringmap> apn_try_list;
// When a required APN exists, no other APNs of that type will be included in
// the try list.
bool modb_required_apn_exists = RequiredApnExists(apn_type);
// If a required APN exists, the last good APN is not added.
bool add_last_good_apn = !modb_required_apn_exists;
std::vector<const Stringmap*> custom_apns_info;
const Stringmap* custom_apn_info = nullptr;
const Stringmap* last_good_apn_info = nullptr;
const Stringmaps* custom_apn_list = nullptr;
// Add custom APNs(from UI or Admin)
if (!modb_required_apn_exists && service_) {
if (service_->custom_apn_list().has_value()) {
// The LastGoodAPN is no longer used in the try list when the APN Revamp
// is enabled.
add_last_good_apn = false;
custom_apn_list = &service_->custom_apn_list().value();
for (const auto& custom_apn : *custom_apn_list) {
if (ApnList::IsApnType(custom_apn, apn_type))
custom_apns_info.emplace_back(&custom_apn);
}
} else if (service_->GetUserSpecifiedApn() &&
ApnList::IsApnType(*service_->GetUserSpecifiedApn(), apn_type)) {
custom_apn_info = service_->GetUserSpecifiedApn();
custom_apns_info.emplace_back(custom_apn_info);
}
last_good_apn_info = service_->GetLastGoodApn();
for (auto custom_apn : custom_apns_info) {
apn_try_list.push_back(*custom_apn);
if (!base::Contains(apn_try_list.back(), kApnSourceProperty))
apn_try_list.back()[kApnSourceProperty] = kApnSourceUi;
SLOG(3) << LoggingTag() << ": " << __func__
<< ": Adding User Specified APN: "
<< GetPrintableApnStringmap(apn_try_list.back());
if ((last_good_apn_info &&
CompareApns(*last_good_apn_info, apn_try_list.back()))) {
add_last_good_apn = false;
}
}
}
// - With the revamp APN UI, if the user has entered an APN in the UI, only
// customs APNs are used. Return early.
// - For the old UI, the Attach APN round robin is skipped if there is a
// custom attach APN.
if ((custom_apn_list && custom_apn_list->size() > 0) ||
(!custom_apn_list && custom_apn_info &&
apn_type == ApnList::ApnType::kAttach)) {
PrintApnListForDebugging(apn_try_list, apn_type);
ValidateApnTryList(apn_try_list);
return apn_try_list;
}
// Ensure all Modem APNs are added before MODB APNs.
for (auto apn : apn_list_) {
if (!ApnList::IsApnType(apn, apn_type))
continue;
DCHECK(base::Contains(apn, kApnSourceProperty));
// Verify all APNs are either from the Modem or MODB.
DCHECK(apn[kApnSourceProperty] == kApnSourceModem ||
apn[kApnSourceProperty] == kApnSourceMoDb);
if (apn[kApnSourceProperty] != kApnSourceModem)
continue;
apn_try_list.push_back(apn);
}
// Add MODB APNs and update the origin of the custom APN(only for old UI).
int index_of_first_modb_apn = apn_try_list.size();
for (const auto& apn : apn_list_) {
if (!ApnList::IsApnType(apn, apn_type) ||
(modb_required_apn_exists && !IsRequiredByCarrierApn(apn)))
continue;
// Updating the origin of the custom APN is only needed for the old UI,
// since the APN UI revamp will include the correct APN source.
if (!custom_apn_list && custom_apn_info &&
CompareApns(*custom_apn_info, apn) &&
base::Contains(apn, kApnSourceProperty)) {
// If |custom_apn_info| is not null, it is located at the first position
// of |apn_try_list|, and we update the APN source for it.
apn_try_list[0][kApnSourceProperty] = apn.at(kApnSourceProperty);
continue;
}
bool is_same_as_last_good_apn =
last_good_apn_info && CompareApns(*last_good_apn_info, apn);
if (is_same_as_last_good_apn)
add_last_good_apn = false;
if (base::Contains(apn, kApnSourceProperty) &&
apn.at(kApnSourceProperty) == kApnSourceMoDb) {
if (is_same_as_last_good_apn) {
apn_try_list.insert(apn_try_list.begin() + index_of_first_modb_apn,
apn);
} else {
apn_try_list.push_back(apn);
}
}
}
// Add fallback empty APN as a last try for Default and Attach
if (apn_type == ApnList::ApnType::kDefault ||
apn_type == ApnList::ApnType::kAttach) {
Stringmap empty_apn = BuildFallbackEmptyApn(apn_type);
apn_try_list.push_back(empty_apn);
bool is_same_as_last_good_apn =
last_good_apn_info && CompareApns(*last_good_apn_info, empty_apn);
if (is_same_as_last_good_apn)
add_last_good_apn = false;
}
// The last good APN will be a last-ditch effort to connect in case the APN
// list is misconfigured somehow.
if (last_good_apn_info && add_last_good_apn &&
ApnList::IsApnType(*last_good_apn_info, apn_type)) {
apn_try_list.push_back(*last_good_apn_info);
LOG(INFO) << LoggingTag() << ": " << __func__ << ": Adding last good APN: "
<< GetPrintableApnStringmap(*last_good_apn_info);
}
PrintApnListForDebugging(apn_try_list, apn_type);
ValidateApnTryList(apn_try_list);
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_);
UpdateFirmwareSupportsTethering();
}
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);
if (!device_id_) {
SLOG(2) << "device_id: {}";
modem_type_ = ModemType::kUnknown;
} else {
SLOG(2) << "device_id: " << device_id_->AsString();
if (device_id_->Match(DeviceId(cellular::kL850GLBusType,
cellular::kL850GLVid,
cellular::kL850GLPid))) {
modem_type_ = ModemType::kL850GL;
} else if (device_id_->Match(DeviceId(cellular::kFM101BusType,
cellular::kFM101Vid,
cellular::kFM101Pid))) {
modem_type_ = ModemType::kFM101;
} else if (device_id_->Match(DeviceId(cellular::kFM350BusType,
cellular::kFM350Vid,
cellular::kFM350Pid))) {
modem_type_ = ModemType::kFM350;
} else {
modem_type_ = ModemType::kOther;
}
}
UpdateFirmwareSupportsTethering();
}
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(1) << LoggingTag() << ": " << __func__ << ": EID= " << sim_properties.eid
<< " ICCID= " << sim_properties.iccid
<< " IMSI= " << sim_properties.imsi
<< " OperatorId= " << sim_properties.operator_id
<< " ServiceProviderName= " << sim_properties.spn
<< " GID1= " << sim_properties.gid1;
eid_ = sim_properties.eid;
iccid_ = sim_properties.iccid;
imsi_ = sim_properties.imsi;
mobile_operator_info()->UpdateMCCMNC(sim_properties.operator_id);
mobile_operator_info()->UpdateOperatorName(sim_properties.spn);
mobile_operator_info()->UpdateICCID(iccid_);
if (!imsi_.empty()) {
mobile_operator_info()->UpdateIMSI(imsi_);
}
if (!sim_properties.gid1.empty()) {
mobile_operator_info()->UpdateGID1(sim_properties.gid1);
}
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(1) << LoggingTag() << ": " << __func__
<< ": Slots: " << slot_properties.size()
<< " Primary: " << primary_slot;
sim_slot_properties_ = slot_properties;
if (primary_sim_slot_ != primary_slot) {
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(2) << LoggingTag() << ": " << __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::SetMaxActiveMultiplexedBearers(
uint32_t max_multiplexed_bearers) {
max_multiplexed_bearers_ = max_multiplexed_bearers;
}
bool Cellular::IsModemFM350() {
SLOG(2) << LoggingTag() << ": " << __func__ << " : " << std::boolalpha
<< (modem_type_ == ModemType::kFM350);
return modem_type_ == ModemType::kFM350;
}
bool Cellular::IsModemFM101() {
SLOG(2) << LoggingTag() << ": " << __func__ << " : " << std::boolalpha
<< (modem_type_ == ModemType::kFM101);
return modem_type_ == ModemType::kFM101;
}
bool Cellular::IsModemL850GL() {
SLOG(2) << LoggingTag() << ": " << __func__ << " : " << std::boolalpha
<< (modem_type_ == ModemType::kL850GL);
return modem_type_ == ModemType::kL850GL;
}
Cellular::ModemMR Cellular::GetModemFWRevision() {
const struct {
ModemType modem_type;
ModemMR mr;
std::string_view version;
} kModemMRTable[] = {
{ModemType::kL850GL, ModemMR::kModemMR1, "^18500.5001.[0-9]{2}.00.*"},
{ModemType::kL850GL, ModemMR::kModemMR2, "^18500.5001.[0-9]{2}.01.*"},
{ModemType::kL850GL, ModemMR::kModemMR3, "^18500.5001.[0-9]{2}.02.*"},
{ModemType::kL850GL, ModemMR::kModemMR4, "^18500.5001.[0-9]{2}.03.*"},
{ModemType::kL850GL, ModemMR::kModemMR5, "^18500.5001.[0-9]{2}.04.*"},
{ModemType::kL850GL, ModemMR::kModemMR6, "^18500.5001.[0-9]{2}.05.*"},
{ModemType::kL850GL, ModemMR::kModemMR7, "^18500.5001.[0-9]{2}.06.*"},
{ModemType::kL850GL, ModemMR::kModemMR8, "^18500.5001.[0-9]{2}.07.*"},
{ModemType::kFM101, ModemMR::kModemMR1, "^1950[0-1].0000.00.01.01.52"},
{ModemType::kFM101, ModemMR::kModemMR2, "^1950[0-1].0000.00.01.02.80"}};
for (const auto& info : kModemMRTable) {
if (modem_type_ != info.modem_type) {
continue;
}
if (RE2::PartialMatch(firmware_revision_, re2::RE2(info.version))) {
return info.mr;
}
}
return ModemMR::kModemMRUnknown;
}
std::string Cellular::ModemTypeEnumToString(ModemType type) {
switch (type) {
case ModemType::kL850GL:
return "L850GL";
case ModemType::kFM101:
return "FM101";
case ModemType::kFM350:
return "FM350";
case ModemType::kOther:
return "OTHER";
case ModemType::kUnknown:
default:
return "UNKNOWN";
}
}
std::string Cellular::ModemMREnumToString(ModemMR mr) {
switch (mr) {
case ModemMR::kModemMR1:
return "MR1";
case ModemMR::kModemMR2:
return "MR2";
case ModemMR::kModemMR3:
return "MR3";
case ModemMR::kModemMR4:
return "MR4";
case ModemMR::kModemMR5:
return "MR5";
case ModemMR::kModemMR6:
return "MR6";
case ModemMR::kModemMR7:
return "MR7";
case ModemMR::kModemMR8:
return "MR8";
case ModemMR::kModemMRUnknown:
default:
return "UNKNOWN";
}
}
bool Cellular::ShouldForceInitEpsBearerSettings() {
if (IsModemFM101() && (GetModemFWRevision() > ModemMR::kModemMR1)) {
return true;
}
if (IsModemL850GL() && (GetModemFWRevision() > ModemMR::kModemMR7)) {
return true;
}
return false;
}
void Cellular::StartLocationPolling() {
CHECK(capability_);
if (!capability_->IsLocationUpdateSupported()) {
SLOG(2) << LoggingTag() << ": Location polling not enabled for "
<< mm_plugin_ << " plugin.";
return;
}
if (polling_location_)
return;
polling_location_ = true;
CHECK(poll_location_task_.IsCancelled());
SLOG(2) << LoggingTag() << ": " << __func__
<< ": Starting location polling tasks.";
// Schedule an immediate task
poll_location_task_.Reset(base::BindOnce(&Cellular::PollLocationTask,
weak_ptr_factory_.GetWeakPtr()));
dispatcher()->PostTask(FROM_HERE, poll_location_task_.callback());
}
void Cellular::StopLocationPolling() {
if (!polling_location_)
return;
polling_location_ = false;
if (!poll_location_task_.IsCancelled()) {
SLOG(2) << LoggingTag() << ": " << __func__
<< ": Cancelling outstanding timeout.";
poll_location_task_.Cancel();
}
}
void Cellular::SetDbusPath(const shill::RpcIdentifier& dbus_path) {
dbus_path_ = dbus_path;
dbus_path_str_ = dbus_path.value();
adaptor()->EmitStringChanged(kDBusObjectProperty, dbus_path_str_);
}
void Cellular::SetScanning(bool scanning) {
if (scanning_ == scanning)
return;
LOG(INFO) << LoggingTag() << ": " << __func__ << ": " << scanning
<< " State: " << GetStateString(state_)
<< " Modem State: " << GetModemStateString(modem_state_);
if (scanning) {
// Set Scanning=true immediately.
SetScanningProperty(true);
return;
}
// If the modem is disabled, set Scanning=false immediately.
// A delayed clear in this case might hit after the service is destroyed.
if (state_ == State::kDisabled) {
SetScanningProperty(false);
return;
}
// 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(2) << LoggingTag() << ": " << __func__ << ": Delaying clear";
scanning_clear_callback_.Reset(base::BindOnce(
&Cellular::SetScanningProperty, weak_ptr_factory_.GetWeakPtr(), false));
dispatcher()->PostDelayedTask(FROM_HERE, scanning_clear_callback_.callback(),
kModemResetTimeout);
}
void Cellular::SetScanningProperty(bool scanning) {
SLOG(2) << LoggingTag() << ": " << __func__ << ": " << scanning;
if (!scanning_clear_callback_.IsCancelled())
scanning_clear_callback_.Cancel();
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::SetPrimaryMultiplexedInterface(
const std::string& interface_name) {
if (primary_multiplexed_interface_ == interface_name) {
return;
}
primary_multiplexed_interface_ = interface_name;
adaptor()->EmitStringChanged(kPrimaryMultiplexedInterfaceProperty,
primary_multiplexed_interface_);
}
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();
}
PowerOpt* Cellular::power_opt() {
return manager()->power_opt();
}
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() {
SLOG(2) << LoggingTag() << ": " << __func__;
Stringmap home_provider;
auto AssignIfNotEmpty = [&](const std::string& key,
const std::string& value) {
if (!value.empty())
home_provider[key] = value;
};
if (mobile_operator_info_->IsHomeOperatorKnown()) {
AssignIfNotEmpty(kOperatorCodeKey, mobile_operator_info_->mccmnc());
AssignIfNotEmpty(kOperatorNameKey, mobile_operator_info_->operator_name());
AssignIfNotEmpty(kOperatorCountryKey, mobile_operator_info_->country());
AssignIfNotEmpty(kOperatorUuidKey, mobile_operator_info_->uuid());
} else if (mobile_operator_info_->IsServingMobileNetworkOperatorKnown()) {
SLOG(2) << "Serving provider proxying in for home provider.";
AssignIfNotEmpty(kOperatorCodeKey, mobile_operator_info_->serving_mccmnc());
AssignIfNotEmpty(kOperatorNameKey,
mobile_operator_info_->serving_operator_name());
AssignIfNotEmpty(kOperatorCountryKey,
mobile_operator_info_->serving_country());
AssignIfNotEmpty(kOperatorUuidKey, mobile_operator_info_->serving_uuid());
} else {
SLOG(2) << "Home and Serving provider are unknown, so using default info.";
AssignIfNotEmpty(kOperatorCodeKey, mobile_operator_info_->mccmnc());
AssignIfNotEmpty(kOperatorNameKey, mobile_operator_info_->operator_name());
AssignIfNotEmpty(kOperatorCountryKey, mobile_operator_info_->country());
AssignIfNotEmpty(kOperatorUuidKey, mobile_operator_info_->uuid());
}
if (home_provider != home_provider_) {
home_provider_ = home_provider;
adaptor()->EmitStringmapChanged(kHomeProviderProperty, home_provider_);
}
// On the new APN UI revamp, modem and modb APNs are not shown to
// the user and the behavior of modem APNs should not be altered.
bool merge_similar_apns =
!(service_ && service_->custom_apn_list().has_value());
ApnList apn_list(merge_similar_apns);
// 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) {
auto profiles = capability_->GetProfiles();
if (profiles) {
// When profile source given in the stored profiles, both
// MM_BEARER_PROFILE_SOURCE_OPERATOR and MM_BEARER_PROFILE_SOURCE_MODEM
// map to ApnList::ApnSource::kModem.
apn_list.AddApns(*profiles, ApnList::ApnSource::kModem);
}
}
apn_list.AddApns(mobile_operator_info_->apn_list(),
ApnList::ApnSource::kModb);
SetApnList(apn_list.GetList());
SetProviderRequiresRoaming(mobile_operator_info_->requires_roaming());
}
void Cellular::UpdateServingOperator() {
SLOG(3) << LoggingTag() << ": " << __func__;
if (!service()) {
return;
}
Stringmap serving_operator;
auto AssignIfNotEmpty = [&](const std::string& key,
const std::string& value) {
if (!value.empty())
serving_operator[key] = value;
};
if (mobile_operator_info_->IsServingMobileNetworkOperatorKnown()) {
AssignIfNotEmpty(kOperatorCodeKey, mobile_operator_info_->serving_mccmnc());
AssignIfNotEmpty(kOperatorNameKey,
mobile_operator_info_->serving_operator_name());
AssignIfNotEmpty(kOperatorCountryKey,
mobile_operator_info_->serving_country());
AssignIfNotEmpty(kOperatorUuidKey, mobile_operator_info_->serving_uuid());
} else {
AssignIfNotEmpty(kOperatorCodeKey, mobile_operator_info_->mccmnc());
AssignIfNotEmpty(kOperatorNameKey, mobile_operator_info_->operator_name());
AssignIfNotEmpty(kOperatorCountryKey, mobile_operator_info_->country());
AssignIfNotEmpty(kOperatorUuidKey, mobile_operator_info_->uuid());
}
service()->SetServingOperator(serving_operator);
// Set friendly name of service.
std::string service_name = mobile_operator_info_->friendly_operator_name(
service()->roaming_state() == kRoamingStateRoaming);
if (service_name.empty()) {
LOG(WARNING) << LoggingTag()
<< ": No properties for setting friendly name for: "
<< service()->log_name();
return;
}
SLOG(2) << LoggingTag() << ": " << __func__
<< ": Service: " << service()->log_name()
<< " Name: " << service_name;
service()->SetFriendlyName(service_name);
SetProviderRequiresRoaming(mobile_operator_info_->requires_roaming());
}
void Cellular::OnOperatorChanged() {
SLOG(2) << LoggingTag() << ": " << __func__;
CHECK(capability_);
if (service()) {
capability_->UpdateServiceOLP();
}
UpdateHomeProvider();
UpdateServingOperator();
if (mobile_operator_info_->IsHomeOperatorKnown() ||
mobile_operator_info_->IsServingMobileNetworkOperatorKnown()) {
ResetCarrierEntitlement();
}
}
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;
}
void Cellular::SetSelectedServiceForTesting(CellularServiceRefPtr service) {
SelectService(service);
}
void Cellular::EntitlementCheck(EntitlementCheckResultCallback callback,
bool experimental_tethering) {
// Only one entitlement check request should exist at any point.
DCHECK(entitlement_check_callback_.is_null());
if (!entitlement_check_callback_.is_null()) {
LOG(ERROR) << kEntitlementCheckAnomalyDetectorPrefix
<< "request received while another one is in progress";
metrics()->NotifyCellularEntitlementCheckResult(
Metrics::kCellularEntitlementCheckIllegalInProgress);
dispatcher()->PostTask(
FROM_HERE,
base::BindOnce(std::move(callback),
TetheringManager::EntitlementStatus::kNotAllowed));
return;
}
if (!mobile_operator_info_->tethering_allowed(experimental_tethering)) {
LOG(ERROR) << kEntitlementCheckAnomalyDetectorPrefix
<< "tethering is not allowed by database settings";
metrics()->NotifyCellularEntitlementCheckResult(
Metrics::kCellularEntitlementCheckNotAllowedByModb);
dispatcher()->PostTask(
FROM_HERE,
base::BindOnce(
std::move(callback),
TetheringManager::EntitlementStatus::kNotAllowedByCarrier));
return;
}
// TODO(b/270210498): remove this check when tethering is allowed by default.
if (!mobile_operator_info_->IsHomeOperatorKnown() &&
!mobile_operator_info_->IsServingMobileNetworkOperatorKnown()) {
LOG(ERROR) << kEntitlementCheckAnomalyDetectorPrefix
<< "carrier is not known.";
metrics()->NotifyCellularEntitlementCheckResult(
Metrics::kCellularEntitlementCheckUnknownCarrier);
dispatcher()->PostTask(
FROM_HERE,
base::BindOnce(std::move(callback),
TetheringManager::EntitlementStatus::kNotAllowed));
return;
}
entitlement_check_callback_ = std::move(callback);
carrier_entitlement_->Check(mobile_operator_info_->entitlement_config());
}
void Cellular::TriggerEntitlementCheckCallbacks(
TetheringManager::EntitlementStatus result) {
if (!entitlement_check_callback_.is_null()) {
std::move(entitlement_check_callback_).Run(result);
}
if (!tethering_event_callback_.is_null() &&
result != TetheringManager::EntitlementStatus::kReady) {
tethering_event_callback_.Run(
TetheringManager::CellularUpstreamEvent::kUserNoLongerEntitled);
}
}
void Cellular::OnEntitlementCheckUpdated(CarrierEntitlement::Result result) {
LOG(INFO) << "Entitlement check updated: " << static_cast<int>(result);
switch (result) {
case shill::CarrierEntitlement::Result::kAllowed:
TriggerEntitlementCheckCallbacks(
TetheringManager::EntitlementStatus::kReady);
break;
case shill::CarrierEntitlement::Result::kNetworkNotReady:
TriggerEntitlementCheckCallbacks(
TetheringManager::EntitlementStatus::kUpstreamNetworkNotAvailable);
break;
case shill::CarrierEntitlement::Result::kGenericError:
LOG(ERROR) << kEntitlementCheckAnomalyDetectorPrefix << "Generic error";
[[fallthrough]];
case shill::CarrierEntitlement::Result::kUnrecognizedUser:
case shill::CarrierEntitlement::Result::kUserNotAllowedToTether:
TriggerEntitlementCheckCallbacks(
TetheringManager::EntitlementStatus::kNotAllowedUserNotEntitled);
break;
}
}
void Cellular::UpdateFirmwareSupportsTethering() {
// This function should be called anytime one of the member variables used in
// the function changes value (i.e. SetFirmwareRevision).
// This list should only include FW versions in which hotspot was fully
// validated on.
static constexpr auto blocklist =
base::MakeFixedFlatMap<Cellular::ModemType, std::string_view>({
{Cellular::ModemType::kL850GL, "^18500.5001.[0-9]{2}.0[2-4].*"},
// 81600.0000.00.29.21* doesn't support multiple PDNs either, but we
// don't disable it because this FW is only used by Japanese carriers,
// which only use single PDNs.
{Cellular::ModemType::kFM350, "^81600.0000.00.29.19.*"},
});
firmware_supports_tethering_ = true;
const auto it = blocklist.find(modem_type_);
if (it != blocklist.end()) {
// Use PartialMatch instead of FullMatch because some modems report FW
// versions with a suffix containing \r\n characters.
if (RE2::PartialMatch(firmware_revision_, re2::RE2(it->second))) {
SLOG(2) << LoggingTag() << ": " << __func__ << " : Firmware doesn't "
<< "support tethering: " << firmware_revision_;
firmware_supports_tethering_ = false;
}
}
// We call this unconditionally because tethering manager can set its
// capabilities before a Cellular object even exists, and needs to refresh
// them the first time we get the modem properties.
manager()->RefreshTetheringCapabilities();
}
void Cellular::SetDefaultPdnForTesting(const RpcIdentifier& dbus_path,
std::unique_ptr<Network> network,
LinkState link_state) {
default_pdn_ = std::make_unique<NetworkInfo>(this, dbus_path,
std::move(network), link_state);
}
void Cellular::SetMultiplexedTetheringPdnForTesting(
const RpcIdentifier& dbus_path,
std::unique_ptr<Network> network,
LinkState link_state) {
multiplexed_tethering_pdn_ = std::make_unique<NetworkInfo>(
this, dbus_path, std::move(network), link_state);
}
Cellular::NetworkInfo::NetworkInfo(NetworkManager* network_manager,
Cellular* cellular,
const RpcIdentifier& bearer_path,
int interface_index,
const std::string& interface_name)
: cellular_(cellular), bearer_path_(bearer_path) {
network_ = network_manager->CreateNetwork(
interface_index, interface_name, Technology::kCellular, false,
cellular_->manager()->patchpanel_client());
network_->RegisterEventHandler(cellular_);
}
Cellular::NetworkInfo::NetworkInfo(Cellular* cellular,
const RpcIdentifier& bearer_path,
std::unique_ptr<Network> network,
LinkState link_state)
: cellular_(cellular),
bearer_path_(bearer_path),
network_(std::move(network)),
link_state_(link_state) {}
Cellular::NetworkInfo::~NetworkInfo() {
network_->Stop();
}
std::string Cellular::NetworkInfo::LoggingTag() {
return cellular_->LoggingTag() + " [" + network_->interface_name() + "]";
}
bool Cellular::NetworkInfo::Configure(const CellularBearer* bearer) {
if (!bearer) {
LOG(INFO) << LoggingTag()
<< ": No active bearer detected: aborting network setup.";
return false;
}
if (bearer->dbus_path() != bearer_path_) {
LOG(INFO) << LoggingTag()
<< ": Mismatched active bearer detected: aborting network setup.";
return false;
}
bool ipv6_configured = false;
bool ipv4_configured = false;
std::unique_ptr<net_base::NetworkConfig> ipv6_config;
std::unique_ptr<net_base::NetworkConfig> ipv4_config;
// If the modem has done its own SLAAC and it was able to retrieve a correct
// address and gateway it will report kMethodStatic. If the modem didn't do
// SLAAC by itself it will report kMethodDHCP and optionally include a
// link-local address to configure before running host SLAAC. In both those
// cases, the modem will receive DNS information via PCOs from the network,
// which should be considered in the setup.
if (bearer->ipv6_config_method() == CellularBearer::IPConfigMethod::kStatic) {
CHECK(bearer->ipv6_config());
SLOG(2) << LoggingTag()
<< ": Assign static IPv6 configuration from bearer.";
start_opts_.accept_ra = false;
ipv6_config =
std::make_unique<net_base::NetworkConfig>(*bearer->ipv6_config());
ipv6_configured = true;
} else if (bearer->ipv6_config_method() ==
CellularBearer::IPConfigMethod::kDHCP) {
SLOG(2) << LoggingTag()
<< ": Assign IPv6 configuration from bearer using kernel SLAAC.";
ipv6_config =
std::make_unique<net_base::NetworkConfig>(*bearer->ipv6_config());
if (ipv6_config->ipv6_addresses.empty()) {
LOG(WARNING) << LoggingTag() << ": IPv6 address is not set";
} else {
const auto& local = ipv6_config->ipv6_addresses[0].address();
ipv6_config->ipv6_addresses.clear();
start_opts_.link_local_address = local;
}
start_opts_.accept_ra = true;
ipv6_configured = true;
}
std::optional<DHCPProvider::Options> dhcp_opts;
if (bearer->ipv4_config_method() == CellularBearer::IPConfigMethod::kStatic) {
SLOG(2) << LoggingTag()
<< ": Assign static IPv4 configuration from bearer.";
CHECK(bearer->ipv4_config());
ipv4_config =
std::make_unique<net_base::NetworkConfig>(*bearer->ipv4_config());
ipv4_configured = true;
} else if (bearer->ipv4_config_method() ==
CellularBearer::IPConfigMethod::kDHCP) {
if (cellular_->IsModemL850GL()) {
LOG(WARNING) << LoggingTag()
<< ": DHCP configuration not supported on L850"
" (Ignoring kDHCP).";
} else {
SLOG(2) << LoggingTag() << ": Needs DHCP to acquire IPv4 configuration.";
dhcp_opts = cellular_->manager()->CreateDefaultDHCPOption();
dhcp_opts->use_arp_gateway = false;
dhcp_opts->use_rfc_8925 = false;
ipv4_configured = true;
}
}
if (!ipv6_configured && !ipv4_configured) {
LOG(WARNING) << LoggingTag()
<< ": No supported IP configuration found in bearer";
return false;
}
// Override the MTU with a given limit for a specific serving operator
// if the network doesn't report something lower. The setting is applied both
// in IPv4 and IPv6 settings.
if (cellular_->mobile_operator_info_) {
if (const int32_t mtu = cellular_->mobile_operator_info_->mtu();
mtu != IPConfig::kUndefinedMTU) {
if (ipv4_config &&
(!ipv4_config->mtu.has_value() || mtu < ipv4_config->mtu)) {
CHECK(mtu >= net_base::NetworkConfig::kMinIPv4MTU);
ipv4_config->mtu = mtu;
}
if (ipv6_config &&
(!ipv6_config->mtu.has_value() || mtu < ipv6_config->mtu)) {
CHECK(mtu >= net_base::NetworkConfig::kMinIPv6MTU);
ipv6_config->mtu = mtu;
}
}
}
network_config_ =
net_base::NetworkConfig::Merge(ipv4_config.get(), ipv6_config.get());
start_opts_.dhcp = dhcp_opts;
// TODO(b/234300343#comment43): Read probe URL override configuration
// from shill APN dB.
start_opts_.probing_configuration =
cellular_->manager()->GetPortalDetectorProbingConfiguration();
start_opts_.validation_mode =
cellular_->service()->GetNetworkValidationMode();
return true;
}
void Cellular::NetworkInfo::Start() {
if (network_config_.has_value()) {
network_->set_link_protocol_network_config(
std::make_unique<net_base::NetworkConfig>(*network_config_));
}
network_->Start(start_opts_);
}
void Cellular::NetworkInfo::DestroySockets() {
for (const auto& address : network_->GetAddresses()) {
SLOG(2) << LoggingTag() << ": Destroy all sockets of address: " << address;
cellular_->rtnl_handler()->RemoveInterfaceAddress(
network_->interface_index(), address);
if (!cellular_->socket_destroyer_->DestroySockets(IPPROTO_TCP,
address.address()))
SLOG(2) << LoggingTag() << ": no tcp sockets found for " << address;
// Chrome sometimes binds to UDP sockets, so lets destroy them.
if (!cellular_->socket_destroyer_->DestroySockets(IPPROTO_UDP,
address.address()))
SLOG(2) << LoggingTag() << ": no udp sockets found for " << address;
}
SLOG(2) << LoggingTag() << ": " << __func__ << " complete.";
}
} // namespace shill