blob: 043eb774c01e2c522b13b51b896f90ef0e6d1f51 [file] [log] [blame]
// Copyright 2017 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "modemfwd/modem.h"
#include <map>
#include <string>
#include <utility>
#include <vector>
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/functional/bind.h>
#include <base/logging.h>
#include <base/memory/ptr_util.h>
#include <base/strings/string_util.h>
#include <base/strings/stringprintf.h>
#include <base/time/time.h>
#include <base/unguessable_token.h>
#include <brillo/dbus/dbus_method_invoker.h>
#include <brillo/strings/string_utils.h>
#include <chromeos/dbus/service_constants.h>
#include <chromeos/switches/modemfwd_switches.h>
#include <dbus/modemfwd/dbus-constants.h>
#include <ModemManager/ModemManager.h>
#include "base/containers/contains.h"
#include "modemfwd/logging.h"
#include "modemfwd/modem_helper.h"
#include "modemfwd/modem_sandbox.h"
#include "modemmanager/dbus-proxies.h"
namespace {
constexpr base::TimeDelta kCmdKillDelay = base::Seconds(1);
class Inhibitor {
public:
Inhibitor(std::unique_ptr<org::freedesktop::ModemManager1Proxy> mm_proxy,
const std::string& physdev_uid)
: mm_proxy_(std::move(mm_proxy)), physdev_uid_(physdev_uid) {}
bool SetInhibited(bool inhibited) {
brillo::ErrorPtr error_unused;
return mm_proxy_->InhibitDevice(physdev_uid_, inhibited, &error_unused);
}
private:
std::unique_ptr<org::freedesktop::ModemManager1Proxy> mm_proxy_;
std::string physdev_uid_;
};
std::unique_ptr<Inhibitor> GetInhibitor(
scoped_refptr<dbus::Bus> bus, const dbus::ObjectPath& mm_object_path) {
// Get the MM object backing this modem, and retrieve its Device property.
// This is the mm_physdev_uid we use for inhibition during updates.
if (!mm_object_path.IsValid()) {
LOG(WARNING) << __func__ << " " << mm_object_path.value() << " is invalid";
return nullptr;
}
auto mm_device = bus->GetObjectProxy(modemmanager::kModemManager1ServiceName,
mm_object_path);
if (!mm_device)
return nullptr;
brillo::ErrorPtr error;
auto resp = brillo::dbus_utils::CallMethodAndBlock(
mm_device, dbus::kDBusPropertiesInterface, dbus::kDBusPropertiesGet,
&error, std::string(modemmanager::kModemManager1ModemInterface),
std::string(MM_MODEM_PROPERTY_DEVICE));
if (!resp)
return nullptr;
std::string mm_physdev_uid;
dbus::MessageReader reader(resp.get());
if (!reader.PopVariantOfString(&mm_physdev_uid)) {
LOG(WARNING) << "Error popping string entry from D-Bus message";
return nullptr;
}
EVLOG(1) << "Modem " << mm_object_path.value() << " has physdev UID "
<< mm_physdev_uid;
auto mm_proxy = std::make_unique<org::freedesktop::ModemManager1Proxy>(
bus, modemmanager::kModemManager1ServiceName);
return std::make_unique<Inhibitor>(std::move(mm_proxy), mm_physdev_uid);
}
class HealthChecker {
public:
virtual ~HealthChecker() = default;
virtual bool Check() = 0;
};
class MbimHealthChecker : public HealthChecker {
public:
explicit MbimHealthChecker(std::string port) : port_(port) {}
~MbimHealthChecker() override = default;
bool Check() override {
std::vector<std::string> cmd_args;
cmd_args.push_back("/usr/bin/mbimcli");
cmd_args.push_back("-d");
cmd_args.push_back("/dev/" + port_);
cmd_args.push_back("-p");
cmd_args.push_back("--query-device-caps");
const base::FilePath mbimcli_seccomp_policy_file(
base::StringPrintf("%s/modemfwd-mbimcli-seccomp.policy",
modemfwd::kSeccompPolicyDirectory));
return modemfwd::RunProcessInSandboxWithTimeout(
cmd_args, mbimcli_seccomp_policy_file, true, nullptr, nullptr,
kCmdKillDelay) == 0;
}
private:
std::string port_;
};
std::unique_ptr<HealthChecker> GetHealthChecker(
std::unique_ptr<org::freedesktop::ModemManager1::ModemProxy> modem_object) {
modem_object->InitializeProperties(base::DoNothing());
if (!modem_object->GetProperties()->primary_port.GetAndBlock()) {
LOG(ERROR) << "Could not fetch primary port property";
return nullptr;
}
std::string primary_port_name = modem_object->primary_port();
if (!modem_object->GetProperties()->ports.GetAndBlock()) {
LOG(ERROR) << "Could not fetch ports property";
return nullptr;
}
for (const auto& [name, type] : modem_object->ports()) {
if (name != primary_port_name)
continue;
switch (type) {
case MM_MODEM_PORT_TYPE_MBIM:
ELOG(INFO) << "Found MBIM port " << primary_port_name
<< " for health checks";
return std::unique_ptr<HealthChecker>(
new MbimHealthChecker(primary_port_name));
default:
continue;
}
}
ELOG(INFO) << "No suitable primary port found for health checks";
return nullptr;
}
} // namespace
namespace modemfwd {
class ModemImpl : public Modem {
public:
ModemImpl(const std::string& device_id,
const std::string& equipment_id,
const std::string& carrier_id,
std::unique_ptr<HealthChecker> health_checker,
std::unique_ptr<Inhibitor> inhibitor,
ModemHelper* helper,
FirmwareInfo installed_firmware)
: device_id_(device_id),
equipment_id_(equipment_id),
carrier_id_(carrier_id),
health_checker_(std::move(health_checker)),
inhibitor_(std::move(inhibitor)),
installed_firmware_(installed_firmware),
helper_(helper) {}
ModemImpl(const ModemImpl&) = delete;
ModemImpl& operator=(const ModemImpl&) = delete;
~ModemImpl() override = default;
// modemfwd::Modem overrides.
std::string GetDeviceId() const override { return device_id_; }
std::string GetEquipmentId() const override { return equipment_id_; }
std::string GetCarrierId() const override { return carrier_id_; }
std::string GetMainFirmwareVersion() const override {
return installed_firmware_.main_version;
}
std::string GetOemFirmwareVersion() const override {
return installed_firmware_.oem_version;
}
std::string GetCarrierFirmwareId() const override {
return installed_firmware_.carrier_uuid;
}
std::string GetCarrierFirmwareVersion() const override {
return installed_firmware_.carrier_version;
}
std::string GetAssocFirmwareVersion(std::string fw_tag) const override {
std::map<std::string, std::string>::const_iterator pos =
installed_firmware_.assoc_versions.find(fw_tag);
if (pos == installed_firmware_.assoc_versions.end())
return "";
else
return pos->second;
}
bool SetInhibited(bool inhibited) override {
if (!inhibitor_) {
EVLOG(1) << "Inhibiting unavailable on this modem";
return false;
}
return inhibitor_->SetInhibited(inhibited);
}
bool FlashFirmwares(const std::vector<FirmwareConfig>& configs) override {
return helper_->FlashFirmwares(configs);
}
bool ClearAttachAPN(const std::string& carrier_uuid) override {
// TODO(b/298680267): Revert this as part of Attach APN cleanup
// We only need to clear attach apn on L850.
if (base::Contains(device_id_, "usb:2cb7:0007")) {
return helper_->ClearAttachAPN(carrier_uuid);
}
return true;
}
bool SupportsHealthCheck() const override { return !!health_checker_; }
bool CheckHealth() override {
return health_checker_ && health_checker_->Check();
}
private:
int heartbeat_failures_;
std::string heartbeat_port_;
std::string device_id_;
std::string equipment_id_;
std::string carrier_id_;
std::unique_ptr<HealthChecker> health_checker_;
std::unique_ptr<Inhibitor> inhibitor_;
FirmwareInfo installed_firmware_;
ModemHelper* helper_;
};
std::unique_ptr<Modem> CreateModem(
scoped_refptr<dbus::Bus> bus,
std::unique_ptr<org::chromium::flimflam::DeviceProxy> device,
ModemHelperDirectory* helper_directory) {
std::string object_path = device->GetObjectPath().value();
DVLOG(1) << "Creating modem proxy for " << object_path;
brillo::ErrorPtr error;
brillo::VariantDictionary properties;
if (!device->GetProperties(&properties, &error)) {
LOG(WARNING) << "Could not get properties for modem " << object_path;
return nullptr;
}
// If we don't have a device ID, modemfwd can't do anything with this modem,
// so check it first and just return if we can't find it.
std::string device_id;
if (!properties[shill::kDeviceIdProperty].GetValue(&device_id)) {
LOG(INFO) << "Modem " << object_path << " has no device ID, ignoring";
return nullptr;
}
// Equipment ID is also pretty important since we use it as a stable
// identifier that can distinguish between modems of the same type.
std::string equipment_id;
if (!properties[shill::kEquipmentIdProperty].GetValue(&equipment_id)) {
LOG(INFO) << "Modem " << object_path << " has no equipment ID, ignoring";
return nullptr;
}
std::string firmware_revision;
if (!properties[shill::kFirmwareRevisionProperty].GetValue(
&firmware_revision)) {
LOG(INFO) << "Modem " << object_path << " has no firmware revision";
}
// This property may not exist and it's not a big deal if it doesn't.
std::map<std::string, std::string> operator_info;
std::string carrier_id;
if (properties[shill::kHomeProviderProperty].GetValue(&operator_info))
carrier_id = operator_info[shill::kOperatorUuidKey];
// Get a helper object for inhibiting the modem, if possible.
std::unique_ptr<Inhibitor> inhibitor;
std::string mm_object_path;
if (!properties[shill::kDBusObjectProperty].GetValue(&mm_object_path)) {
LOG(INFO) << "Modem " << object_path << " has no ModemManager object";
} else {
inhibitor = GetInhibitor(bus, dbus::ObjectPath(mm_object_path));
}
if (!inhibitor)
LOG(INFO) << "Inhibiting modem " << object_path << " will not be possible";
// Use the device ID to grab a helper.
ModemHelper* helper = helper_directory->GetHelperForDeviceId(device_id);
if (!helper) {
LOG(INFO) << "No helper found to update modems with ID [" << device_id
<< "]";
return nullptr;
}
FirmwareInfo installed_firmware;
if (!helper->GetFirmwareInfo(&installed_firmware, firmware_revision)) {
LOG(WARNING) << "Could not fetch installed firmware information";
return nullptr;
}
std::unique_ptr<HealthChecker> health_checker;
auto mm_object =
std::make_unique<org::freedesktop::ModemManager1::ModemProxy>(
bus, MM_DBUS_SERVICE, dbus::ObjectPath(mm_object_path));
if (!mm_object) {
LOG(WARNING) << "Could not fetch primary port information";
} else {
health_checker = GetHealthChecker(std::move(mm_object));
}
return std::make_unique<ModemImpl>(
device_id, equipment_id, carrier_id, std::move(health_checker),
std::move(inhibitor), helper, installed_firmware);
}
// StubModem acts like a modem with a particular device ID but does not
// actually talk to a real modem. This allows us to use it for force-
// flashing.
class StubModem : public Modem {
public:
StubModem(const std::string& device_id,
const std::string& carrier_uuid,
ModemHelper* helper,
FirmwareInfo installed_firmware)
: carrier_id_(carrier_uuid),
device_id_(device_id),
equipment_id_(base::UnguessableToken().Create().ToString()),
helper_(helper),
installed_firmware_(installed_firmware) {}
StubModem(const StubModem&) = delete;
StubModem& operator=(const StubModem&) = delete;
~StubModem() override = default;
// modemfwd::Modem overrides.
std::string GetDeviceId() const override { return device_id_; }
std::string GetEquipmentId() const override { return equipment_id_; }
std::string GetCarrierId() const override { return carrier_id_; }
std::string GetMainFirmwareVersion() const override {
return installed_firmware_.main_version;
}
std::string GetOemFirmwareVersion() const override {
return installed_firmware_.oem_version;
}
std::string GetCarrierFirmwareId() const override {
return installed_firmware_.carrier_uuid;
}
std::string GetCarrierFirmwareVersion() const override {
return installed_firmware_.carrier_version;
}
std::string GetAssocFirmwareVersion(std::string) const override { return ""; }
bool SetInhibited(bool inhibited) override { return true; }
bool FlashFirmwares(const std::vector<FirmwareConfig>& configs) override {
return helper_->FlashFirmwares(configs);
}
bool ClearAttachAPN(const std::string& carrier_uuid) override {
// TODO(b/298680267): Revert this as part of Attach APN cleanup
// We only need to clear attach apn on L850.
if (base::Contains(device_id_, "usb:2cb7:0007")) {
return helper_->ClearAttachAPN(carrier_uuid);
}
return true;
}
bool SupportsHealthCheck() const override { return false; }
bool CheckHealth() override { return false; }
private:
int heartbeat_failures_;
std::string heartbeat_port_;
std::string carrier_id_;
std::string device_id_;
std::string equipment_id_;
ModemHelper* helper_;
FirmwareInfo installed_firmware_;
};
std::unique_ptr<Modem> CreateStubModem(const std::string& device_id,
const std::string& carrier_uuid,
ModemHelperDirectory* helper_directory,
bool use_real_fw_info) {
// Use the device ID to grab a helper.
ModemHelper* helper = helper_directory->GetHelperForDeviceId(device_id);
if (!helper) {
LOG(INFO) << "No helper found to update modems with ID [" << device_id
<< "]";
return nullptr;
}
FirmwareInfo installed_firmware;
if (use_real_fw_info && !helper->GetFirmwareInfo(&installed_firmware, "")) {
LOG(ERROR) << "Could not fetch installed firmware information";
return nullptr;
}
return std::make_unique<StubModem>(device_id, carrier_uuid, helper,
std::move(installed_firmware));
}
} // namespace modemfwd