blob: 8f913b015a6f08c1cfe76b2aee8081b2f79e8f21 [file] [log] [blame]
// Copyright 2018 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "shill/cellular/modem.h"
#include <limits>
#include <tuple>
#include <base/bind.h>
#include <base/logging.h>
#include <base/strings/stringprintf.h>
#include <ModemManager/ModemManager.h>
#include "shill/cellular/cellular.h"
#include "shill/control_interface.h"
#include "shill/device_info.h"
#include "shill/logging.h"
#include "shill/manager.h"
#include "shill/net/rtnl_handler.h"
namespace shill {
namespace Logging {
static auto kModuleLogScope = ScopeLogger::kModem;
static std::string ObjectID(const Modem* m) {
return m->path().value().c_str();
}
} // namespace Logging
namespace {
// TODO(b/175305412): Remove kRmnetIpa0 and kRmnetData0.
constexpr char kRmnetIpa0[] = "rmnet_ipa0";
constexpr char kRmnetData0[] = "rmnet_data0";
} // namespace
// statics
constexpr char Modem::kFakeDevNameFormat[];
const char Modem::kFakeDevAddress[] = "000000000000";
const int Modem::kFakeDevInterfaceIndex = -1;
size_t Modem::fake_dev_serial_ = 0;
Modem::Modem(const std::string& service,
const RpcIdentifier& path,
DeviceInfo* device_info)
: service_(service),
path_(path),
device_info_(device_info),
type_(Cellular::kTypeInvalid),
rtnl_handler_(RTNLHandler::GetInstance()) {
SLOG(this, 1) << "Modem() Path: " << path.value();
}
Modem::~Modem() {
SLOG(this, 1) << "~Modem() Path: " << path_.value();
if (!interface_index_.has_value())
return;
// Note: The Cellular Device |device_| is owned by DeviceInfo. It will not
// be destroyed here, instead it will be kept around until/unless an RTNL
// link delete message is received. If/when a new Modem instance is
// constructed (e.g. after modemmanager restarts), the call to
// GetOrCreateCellularDevice will return the existing device for the
// interface.
CellularRefPtr cellular = GetExistingCellularDevice(interface_index_.value());
if (cellular)
cellular->OnModemDestroyed();
}
void Modem::CreateDevice(const InterfaceToProperties& properties) {
SLOG(this, 1) << __func__;
uint32_t capabilities = std::numeric_limits<uint32_t>::max();
const auto iter = properties.find(MM_DBUS_INTERFACE_MODEM);
if (iter == properties.end()) {
LOG(ERROR) << "Cellular device with no modem properties";
return;
}
const KeyValueStore& modem_props = iter->second;
if (modem_props.Contains<uint32_t>(MM_MODEM_PROPERTY_CURRENTCAPABILITIES)) {
capabilities =
modem_props.Get<uint32_t>(MM_MODEM_PROPERTY_CURRENTCAPABILITIES);
}
if (capabilities & (MM_MODEM_CAPABILITY_GSM_UMTS | MM_MODEM_CAPABILITY_LTE |
MM_MODEM_CAPABILITY_5GNR)) {
type_ = Cellular::kType3gpp;
} else if (capabilities & MM_MODEM_CAPABILITY_CDMA_EVDO) {
type_ = Cellular::kTypeCdma;
} else {
LOG(ERROR) << "Unsupported capabilities: " << capabilities;
return;
}
// We cannot check the IP method to make sure it's not PPP. The IP
// method will be checked later when the bearer object is fetched.
CreateDeviceFromModemProperties(properties);
}
void Modem::OnDeviceInfoAvailable(const std::string& link_name) {
SLOG(this, 1) << __func__ << ": " << link_name
<< " pending: " << has_pending_device_info_;
if (link_name_ != link_name || !has_pending_device_info_)
return;
// has_pending_device_info_ is only set if we've already been through
// CreateDeviceFromModemProperties() and saved our initial properties.
has_pending_device_info_ = false;
CreateDeviceFromModemProperties(initial_properties_);
}
bool Modem::GetLinkName(const KeyValueStore& modem_props,
std::string* name) const {
if (!modem_props.ContainsVariant(MM_MODEM_PROPERTY_PORTS)) {
LOG(ERROR) << "Device missing property: " << MM_MODEM_PROPERTY_PORTS;
return false;
}
auto ports = modem_props.GetVariant(MM_MODEM_PROPERTY_PORTS)
.Get<std::vector<std::tuple<std::string, uint32_t>>>();
std::string net_port;
for (const auto& port_pair : ports) {
if (std::get<1>(port_pair) == MM_MODEM_PORT_TYPE_NET) {
net_port = std::get<0>(port_pair);
// TODO(b/175305412): Remove the special handling of `kRmnetIpa0`.
// Now that ModemManager supports multiplexing, it reports the parent
// interface name when the modem is created and the child link
// `kRmnetData0` when the bearer is created. Shill only cares about the
// child link. Until shill can handle this correctly, we override the
// parent name with the child name.
if (std::get<0>(port_pair) == kRmnetIpa0)
net_port = kRmnetData0;
break;
}
}
if (net_port.empty()) {
LOG(ERROR) << "Could not find net port used by the device.";
return false;
}
*name = net_port;
return true;
}
void Modem::CreateDeviceFromModemProperties(
const InterfaceToProperties& properties) {
SLOG(this, 1) << __func__;
const auto iter = properties.find(std::string(MM_DBUS_INTERFACE_MODEM));
if (iter == properties.end()) {
LOG(ERROR) << "Unable to find modem interface properties.";
return;
}
const KeyValueStore& modem_props = iter->second;
std::string mac_address;
if (GetLinkName(modem_props, &link_name_)) {
interface_index_ = GetDeviceParams(&mac_address);
if (!interface_index_.has_value()) {
LOG(ERROR) << "Unable to create cellular device -- no interface index.";
return;
}
if (mac_address.empty()) {
// Save our properties, wait for OnDeviceInfoAvailable to be called.
LOG(WARNING)
<< __func__
<< ": No hardware address, device creation pending device info.";
initial_properties_ = properties;
has_pending_device_info_ = true;
return;
}
// Got the interface index and MAC address. Fall-through to actually
// creating the Cellular object.
} else {
// Probably a PPP dongle.
LOG(INFO) << "Cellular device without link name; assuming PPP dongle.";
link_name_ = base::StringPrintf(kFakeDevNameFormat, fake_dev_serial_++);
mac_address = kFakeDevAddress;
interface_index_ = kFakeDevInterfaceIndex;
}
if (device_info_->IsDeviceBlocked(link_name_)) {
LOG(INFO) << "Not creating cellular device for blocked interface "
<< link_name_ << ".";
return;
}
CellularRefPtr device =
GetOrCreateCellularDevice(interface_index_.value(), mac_address);
device->SetInitialProperties(properties);
SLOG(this, 1) << "Cellular device created: " << device->link_name()
<< " Enabled: " << device->enabled();
}
base::Optional<int> Modem::GetDeviceParams(std::string* mac_address) {
// TODO(petkov): Get the interface index from DeviceInfo, similar to the MAC
// address below.
int interface_index = rtnl_handler_->GetInterfaceIndex(link_name_);
if (interface_index < 0) {
return base::nullopt;
}
ByteString address_bytes;
if (device_info_->GetMacAddress(interface_index, &address_bytes)) {
*mac_address = address_bytes.HexEncode();
}
return interface_index;
}
CellularRefPtr Modem::GetOrCreateCellularDevice(
int interface_index, const std::string& mac_address) {
LOG(INFO) << __func__ << " Index: " << interface_index;
CellularRefPtr cellular = GetExistingCellularDevice(interface_index);
if (cellular && cellular->link_name() != link_name_) {
SLOG(this, 1) << "Cellular link name changed: " << link_name_;
cellular = nullptr;
device_info_->DeregisterDevice(interface_index);
}
if (cellular &&
(cellular->type() != type_ || cellular->dbus_service() != service_)) {
SLOG(this, 1) << "Cellular service changed: " << service_;
cellular = nullptr;
device_info_->DeregisterDevice(interface_index);
}
if (cellular) {
LOG(INFO) << "Using existing Cellular Device: " << cellular->enabled();
// Update the Cellular dbus path and mac address to match the new Modem.
cellular->UpdateModemProperties(path_, mac_address);
return cellular;
}
cellular = new Cellular(device_info_->manager(), link_name_, mac_address,
interface_index, type_, service_, path_);
device_info_->RegisterDevice(cellular);
return cellular;
}
CellularRefPtr Modem::GetExistingCellularDevice(int interface_index) const {
DeviceRefPtr device = device_info_->GetDevice(interface_index);
if (!device)
return nullptr;
DCHECK_EQ(device->technology(), Technology::kCellular);
return static_cast<Cellular*>(device.get());
}
} // namespace shill