blob: 513cd6825319fc32ccd1403331fc95a79b4eeb43 [file] [log] [blame]
// Copyright 2016 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 "patchpanel/shill_client.h"
#include <base/bind.h>
#include <base/check.h>
#include <base/logging.h>
#include <base/strings/string_util.h>
#include <brillo/variant_dictionary.h>
#include <chromeos/dbus/service_constants.h>
#include "patchpanel/net_util.h"
namespace patchpanel {
namespace {
ShillClient::Device::Type ParseDeviceType(const std::string& type_str) {
static const std::map<std::string, ShillClient::Device::Type> str2enum{
{shill::kTypeCellular, ShillClient::Device::Type::kCellular},
{shill::kTypeEthernet, ShillClient::Device::Type::kEthernet},
{shill::kTypeEthernetEap, ShillClient::Device::Type::kEthernetEap},
{shill::kTypeGuestInterface, ShillClient::Device::Type::kGuestInterface},
{shill::kTypeLoopback, ShillClient::Device::Type::kLoopback},
{shill::kTypePPP, ShillClient::Device::Type::kPPP},
{shill::kTypePPPoE, ShillClient::Device::Type::kPPPoE},
{shill::kTypeTunnel, ShillClient::Device::Type::kTunnel},
{shill::kTypeWifi, ShillClient::Device::Type::kWifi},
{shill::kTypeVPN, ShillClient::Device::Type::kVPN},
};
const auto it = str2enum.find(type_str);
return it != str2enum.end() ? it->second
: ShillClient::Device::Type::kUnknown;
}
const std::string DeviceTypeName(ShillClient::Device::Type type) {
static const std::map<ShillClient::Device::Type, std::string> enum2str{
{ShillClient::Device::Type::kUnknown, "Unknown"},
{ShillClient::Device::Type::kCellular, "Cellular"},
{ShillClient::Device::Type::kEthernet, "Ethernet"},
{ShillClient::Device::Type::kEthernetEap, "EthernetEap"},
{ShillClient::Device::Type::kGuestInterface, "GuestInterface"},
{ShillClient::Device::Type::kLoopback, "Loopback"},
{ShillClient::Device::Type::kPPP, "PPP"},
{ShillClient::Device::Type::kPPPoE, "PPPoE"},
{ShillClient::Device::Type::kTunnel, "Tunnel"},
{ShillClient::Device::Type::kVPN, "VPN"},
{ShillClient::Device::Type::kWifi, "Wifi"},
};
const auto it = enum2str.find(type);
return it != enum2str.end() ? it->second : "Unknown";
}
} // namespace
ShillClient::ShillClient(const scoped_refptr<dbus::Bus>& bus) : bus_(bus) {
manager_proxy_.reset(new org::chromium::flimflam::ManagerProxy(bus_));
manager_proxy_->RegisterPropertyChangedSignalHandler(
base::BindRepeating(&ShillClient::OnManagerPropertyChange,
weak_factory_.GetWeakPtr()),
base::BindOnce(&ShillClient::OnManagerPropertyChangeRegistration,
weak_factory_.GetWeakPtr()));
}
const std::string& ShillClient::default_logical_interface() const {
return default_logical_device_.ifname;
}
const std::string& ShillClient::default_physical_interface() const {
return default_physical_device_.ifname;
}
const ShillClient::Device& ShillClient::default_logical_device() const {
return default_logical_device_;
}
const ShillClient::Device& ShillClient::default_physical_device() const {
return default_physical_device_;
}
const std::vector<std::string> ShillClient::get_interfaces() const {
std::vector<std::string> ifnames;
for (const auto& [_, ifname] : devices_) {
ifnames.push_back(ifname);
}
return ifnames;
}
bool ShillClient::has_interface(const std::string& ifname) const {
for (const auto& kv : devices_) {
if (kv.second == ifname) {
return true;
}
}
return false;
}
void ShillClient::ScanDevices() {
brillo::VariantDictionary props;
if (!manager_proxy_->GetProperties(&props, nullptr)) {
LOG(ERROR) << "Unable to get Manager properties";
return;
}
const auto it = props.find(shill::kDevicesProperty);
if (it == props.end()) {
LOG(WARNING) << "Manager properties is missing " << shill::kDevicesProperty;
return;
}
UpdateDevices(it->second);
}
std::pair<ShillClient::Device, ShillClient::Device>
ShillClient::GetDefaultDevices() {
brillo::VariantDictionary properties;
if (!manager_proxy_->GetProperties(&properties, nullptr)) {
LOG(ERROR) << "Unable to get manager properties";
return {};
}
auto services =
brillo::GetVariantValueOrDefault<std::vector<dbus::ObjectPath>>(
properties, shill::kServicesProperty);
Device default_logical_device = {};
Device default_physical_device = {};
for (const auto& service_path : services) {
properties = GetServiceProperties(service_path);
auto device_path = brillo::GetVariantValueOrDefault<dbus::ObjectPath>(
properties, shill::kDeviceProperty);
if (!device_path.IsValid()) {
LOG(WARNING) << "Failed to obtain device for service ["
<< service_path.value() << "]";
return {};
}
auto it = properties.find(shill::kIsConnectedProperty);
if (it == properties.end()) {
LOG(ERROR) << "Service " << service_path.value() << " missing property "
<< shill::kIsConnectedProperty;
return {};
}
if (!it->second.TryGet<bool>()) {
LOG(INFO) << "Ignoring non-connected Service " << service_path.value();
return {};
}
std::string service_type = brillo::GetVariantValueOrDefault<std::string>(
properties, shill::kTypeProperty);
if (service_type.empty()) {
LOG(ERROR) << "Service " << service_path.value() << " missing property "
<< shill::kTypeProperty;
return {};
}
auto device = GetDevice(service_path, device_path, service_type);
if (device.type == ShillClient::Device::Type::kVPN) {
default_logical_device = device;
} else {
default_physical_device = device;
if (default_logical_device.type == ShillClient::Device::Type::kUnknown) {
default_logical_device = device;
}
break;
}
}
return std::make_pair(default_logical_device, default_physical_device);
}
void ShillClient::OnManagerPropertyChangeRegistration(
const std::string& interface,
const std::string& signal_name,
bool success) {
if (!success)
LOG(FATAL) << "Unable to register for interface change events";
}
void ShillClient::OnManagerPropertyChange(const std::string& property_name,
const brillo::Any& property_value) {
if (property_name == shill::kDevicesProperty) {
UpdateDevices(property_value);
} else if (property_name != shill::kDefaultServiceProperty &&
property_name != shill::kServicesProperty &&
property_name != shill::kConnectionStateProperty) {
return;
}
// All registered DefaultDeviceChangeHandler objects should be called if
// the default network has changed or if shill::kDevicesProperty has changed.
SetDefaultDevices(GetDefaultDevices());
}
void ShillClient::SetDefaultDevices(const std::pair<Device, Device>& devices) {
auto default_logical_device = devices.first;
auto default_physical_device = devices.second;
if (default_logical_device_.ifname != default_logical_device.ifname) {
LOG(INFO) << "Default network changed from " << default_logical_device_
<< " to " << default_logical_device;
for (const auto& h : default_logical_device_handlers_) {
if (!h.is_null())
h.Run(default_logical_device, default_logical_device_);
}
default_logical_device_ = default_logical_device;
}
if (default_physical_device_.ifname != default_physical_device.ifname) {
LOG(INFO) << "Default physical device changed from "
<< default_physical_device_ << " to " << default_physical_device;
for (const auto& h : default_physical_device_handlers_) {
if (!h.is_null())
h.Run(default_physical_device, default_physical_device_);
}
default_physical_device_ = default_physical_device;
}
}
brillo::VariantDictionary ShillClient::GetServiceProperties(
const dbus::ObjectPath& service_path) {
brillo::ErrorPtr error;
brillo::VariantDictionary properties;
org::chromium::flimflam::ServiceProxy service_proxy(bus_, service_path);
if (!service_proxy.GetProperties(&properties, &error)) {
LOG(ERROR) << "Failed to obtain service [" << service_path.value()
<< "] properties: " << error->GetMessage();
}
return properties;
}
brillo::VariantDictionary ShillClient::GetDeviceProperties(
const dbus::ObjectPath& device_path) {
brillo::VariantDictionary properties;
org::chromium::flimflam::DeviceProxy device_proxy(bus_, device_path);
if (!device_proxy.GetProperties(&properties, nullptr)) {
LOG(ERROR) << "Can't retrieve properties for device";
}
return properties;
}
ShillClient::Device ShillClient::GetDevice(const dbus::ObjectPath& service_path,
const dbus::ObjectPath& device_path,
const std::string& service_type) {
Device device = {};
auto properties = GetDeviceProperties(device_path);
device.ifname = brillo::GetVariantValueOrDefault<std::string>(
properties, shill::kInterfaceProperty);
if (device.ifname.empty()) {
LOG(ERROR) << "Empty interface name for shill Device "
<< device_path.value();
return {};
}
device.type = ParseDeviceType(service_type);
device.service_path = service_path.value();
return device;
}
void ShillClient::RegisterDefaultLogicalDeviceChangedHandler(
const DefaultDeviceChangeHandler& handler) {
default_logical_device_handlers_.emplace_back(handler);
// Explicitly trigger the callback once to let it know of the the current
// default interface. The previous interface is left empty.
handler.Run(default_logical_device_, {});
}
void ShillClient::RegisterDefaultPhysicalDeviceChangedHandler(
const DefaultDeviceChangeHandler& handler) {
default_physical_device_handlers_.emplace_back(handler);
// Explicitly trigger the callback once to let it know of the the current
// default interface. The previous interface is left empty.
handler.Run(default_physical_device_, {});
}
void ShillClient::RegisterDevicesChangedHandler(
const DevicesChangeHandler& handler) {
device_handlers_.emplace_back(handler);
}
void ShillClient::RegisterIPConfigsChangedHandler(
const IPConfigsChangeHandler& handler) {
ipconfigs_handlers_.emplace_back(handler);
}
void ShillClient::RegisterIPv6NetworkChangedHandler(
const IPv6NetworkChangeHandler& handler) {
ipv6_network_handlers_.emplace_back(handler);
}
void ShillClient::UpdateDevices(const brillo::Any& property_value) {
std::map<std::string, std::string> new_devices;
std::vector<std::string> added, removed;
for (const auto& path :
property_value.TryGet<std::vector<dbus::ObjectPath>>()) {
std::string device = path.value();
// Strip "/device/" prefix.
device = device.substr(device.find_last_of('/') + 1);
const std::string ifname = GetIfname(path);
if (ifname.empty()) {
LOG(WARNING) << "Found empty interface name for Device " << device;
continue;
}
new_devices[device] = ifname;
if (devices_.find(device) == devices_.end()) {
added.push_back(ifname);
}
// Registers handler if we see this device for the first time.
if (known_device_paths_.insert(std::make_pair(device, path)).second) {
org::chromium::flimflam::DeviceProxy proxy(bus_, path);
proxy.RegisterPropertyChangedSignalHandler(
base::BindRepeating(&ShillClient::OnDevicePropertyChange,
weak_factory_.GetWeakPtr(), device),
base::BindOnce(&ShillClient::OnDevicePropertyChangeRegistration,
weak_factory_.GetWeakPtr()));
known_device_paths_[device] = path;
}
}
for (const auto& [d, ifname] : devices_) {
if (new_devices.find(d) == new_devices.end()) {
removed.push_back(ifname);
// Clear cached IPConfig for removed device.
device_ipconfigs_.erase(d);
}
}
devices_ = new_devices;
// This can happen if the default network switched from one device to another.
if (added.empty() && removed.empty())
return;
LOG(INFO) << "shill Devices changed: added={" << base::JoinString(added, ",")
<< "}, removed={" << base::JoinString(removed, ",") << "}";
for (const auto& h : device_handlers_)
h.Run(added, removed);
}
ShillClient::IPConfig ShillClient::ParseIPConfigsProperty(
const std::string& device, const brillo::Any& property_value) {
IPConfig ipconfig;
for (const auto& path :
property_value.TryGet<std::vector<dbus::ObjectPath>>()) {
std::unique_ptr<org::chromium::flimflam::IPConfigProxy> ipconfig_proxy(
new org::chromium::flimflam::IPConfigProxy(bus_, path));
brillo::VariantDictionary ipconfig_props;
if (!ipconfig_proxy->GetProperties(&ipconfig_props, nullptr)) {
// It is possible that an IPConfig object is removed after we know its
// path, especially when the interface is going down.
LOG(WARNING) << "[" << device << "]: "
<< "Unable to get properties for " << path.value();
continue;
}
// Detects the type of IPConfig. For ipv4 and ipv6 configurations, there
// should be at most one for each type.
auto it = ipconfig_props.find(shill::kMethodProperty);
if (it == ipconfig_props.end()) {
LOG(WARNING) << "[" << device << "]: "
<< "IPConfig properties is missing Method";
continue;
}
const std::string& method = it->second.TryGet<std::string>();
const bool is_ipv4_type =
(method == shill::kTypeIPv4 || method == shill::kTypeDHCP ||
method == shill::kTypeBOOTP || method == shill::kTypeZeroConf);
const bool is_ipv6_type = (method == shill::kTypeIPv6);
if (!is_ipv4_type && !is_ipv6_type) {
LOG(WARNING) << "[" << device << "]: "
<< "unknown type \"" << method << "\" for " << path.value();
continue;
}
if ((is_ipv4_type && !ipconfig.ipv4_address.empty()) ||
(is_ipv6_type && !ipconfig.ipv6_address.empty())) {
LOG(WARNING) << "[" << device << "]: "
<< "Duplicated ipconfig for " << method;
continue;
}
// Gets the value of address, prefix_length, gateway, and dns_servers.
it = ipconfig_props.find(shill::kAddressProperty);
if (it == ipconfig_props.end()) {
LOG(WARNING) << "[" << device << "]: "
<< "IPConfig properties is missing Address";
continue;
}
const std::string& address = it->second.TryGet<std::string>();
it = ipconfig_props.find(shill::kPrefixlenProperty);
if (it == ipconfig_props.end()) {
LOG(WARNING) << "[" << device << "]: "
<< "IPConfig properties is missing Prefixlen";
continue;
}
int prefix_length = it->second.TryGet<int>();
it = ipconfig_props.find(shill::kGatewayProperty);
if (it == ipconfig_props.end()) {
LOG(WARNING) << "[" << device << "]: "
<< "IPConfig properties is missing Gateway";
continue;
}
const std::string& gateway = it->second.TryGet<std::string>();
it = ipconfig_props.find(shill::kNameServersProperty);
if (it == ipconfig_props.end()) {
LOG(WARNING) << "[" << device << "]: "
<< "IPConfig properties is missing NameServers";
// Shill will emit this property with empty value if it has no dns for
// this device, so missing this property indicates an error.
continue;
}
const std::vector<std::string>& dns_addresses =
it->second.TryGet<std::vector<std::string>>();
// Checks if this ipconfig is valid: address, gateway, and prefix_length
// should not be empty.
if (address.empty() || gateway.empty() || prefix_length == 0) {
LOG(WARNING) << "[" << device << "]: "
<< "Skipped invalid ipconfig: "
<< "address.length()=" << address.length()
<< ", gateway.length()=" << gateway.length()
<< ", prefix_length=" << prefix_length;
continue;
}
// Fills the IPConfig struct according to the type.
if (is_ipv4_type) {
ipconfig.ipv4_prefix_length = prefix_length;
ipconfig.ipv4_address = address;
ipconfig.ipv4_gateway = gateway;
ipconfig.ipv4_dns_addresses = dns_addresses;
} else { // is_ipv6_type
ipconfig.ipv6_prefix_length = prefix_length;
ipconfig.ipv6_address = address;
ipconfig.ipv6_gateway = gateway;
ipconfig.ipv6_dns_addresses = dns_addresses;
}
}
return ipconfig;
}
bool ShillClient::GetDeviceProperties(const std::string& ifname,
Device* output) {
DCHECK(output);
std::string device = "";
for (const auto& kv : devices_) {
if (kv.second == ifname) {
device = kv.first;
break;
}
}
if (device.empty()) {
LOG(ERROR) << "Unknown interface name " << ifname;
return false;
}
const auto& device_it = known_device_paths_.find(device);
if (device_it == known_device_paths_.end()) {
LOG(ERROR) << "Unknown shill Device " << device;
return false;
}
org::chromium::flimflam::DeviceProxy proxy(bus_, device_it->second);
brillo::VariantDictionary props;
if (!proxy.GetProperties(&props, nullptr)) {
LOG(WARNING) << "Unable to get shill Device properties for " << device;
return false;
}
const auto& type_it = props.find(shill::kTypeProperty);
if (type_it == props.end()) {
LOG(WARNING) << "shill Device properties is missing Type for " << device;
return false;
}
const std::string& type_str = type_it->second.TryGet<std::string>();
output->type = ParseDeviceType(type_str);
if (output->type == Device::Type::kUnknown)
LOG(WARNING) << "Unknown shill Device type " << type_str << " for "
<< device;
const auto& interface_it = props.find(shill::kInterfaceProperty);
if (interface_it == props.end()) {
LOG(WARNING) << "shill Device properties is missing Interface for "
<< device;
return false;
}
output->ifname = interface_it->second.TryGet<std::string>();
const auto& ipconfigs_it = props.find(shill::kIPConfigsProperty);
if (ipconfigs_it == props.end()) {
LOG(WARNING) << "shill Device properties is missing IPConfigs for "
<< device;
return false;
}
output->ipconfig = ParseIPConfigsProperty(device, ipconfigs_it->second);
return true;
}
std::string ShillClient::GetIfname(const dbus::ObjectPath& device_path) {
org::chromium::flimflam::DeviceProxy device_proxy(bus_, device_path);
brillo::VariantDictionary props;
if (!device_proxy.GetProperties(&props, nullptr)) {
LOG(WARNING) << "Unable to get Device properties for "
<< device_path.value();
return "";
}
const auto& interface_it = props.find(shill::kInterfaceProperty);
if (interface_it == props.end()) {
LOG(WARNING) << "shill Device properties is missing Interface for "
<< device_path.value();
return "";
}
return interface_it->second.TryGet<std::string>();
}
void ShillClient::OnDevicePropertyChangeRegistration(
const std::string& interface,
const std::string& signal_name,
bool success) {
if (!success)
LOG(ERROR) << "[" << interface << "]: "
<< "Unable to register listener for " << signal_name;
}
void ShillClient::OnDevicePropertyChange(const std::string& device,
const std::string& property_name,
const brillo::Any& property_value) {
if (property_name != shill::kIPConfigsProperty)
return;
const auto& it = devices_.find(device);
if (it == devices_.end()) {
LOG(WARNING) << "Failed to obtain interface name for shill Device "
<< device;
return;
}
const IPConfig& ipconfig = ParseIPConfigsProperty(device, property_value);
const auto& old_ipconfig_it = device_ipconfigs_.find(device);
if (old_ipconfig_it != device_ipconfigs_.end() &&
old_ipconfig_it->second == ipconfig) {
// There is no IPConfig change, no need to run the handlers.
return;
}
auto old_ipconfig = old_ipconfig_it != device_ipconfigs_.end()
? old_ipconfig_it->second
: IPConfig{};
device_ipconfigs_[device] = ipconfig;
for (const auto& handler : ipconfigs_handlers_)
handler.Run(it->second, ipconfig);
// Compares if the new IPv6 network is the same as the old one by checking
// its prefix.
if (old_ipconfig.ipv6_prefix_length == ipconfig.ipv6_prefix_length &&
IsIPv6PrefixEqual(StringToIPv6Address(old_ipconfig.ipv6_address),
StringToIPv6Address(ipconfig.ipv6_address),
ipconfig.ipv6_prefix_length)) {
return;
}
for (const auto& handler : ipv6_network_handlers_)
handler.Run(it->second, ipconfig.ipv6_address);
}
std::ostream& operator<<(std::ostream& stream, const ShillClient::Device& dev) {
return stream << "{ifname: " << dev.ifname
<< ", type: " << DeviceTypeName(dev.type)
<< ", service: " << dev.service_path << "}";
}
std::ostream& operator<<(std::ostream& stream,
const ShillClient::Device::Type type) {
return stream << DeviceTypeName(type);
}
} // namespace patchpanel