| // Copyright 2016 The ChromiumOS Authors |
| // 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 <memory> |
| #include <optional> |
| #include <string_view> |
| |
| #include <base/check.h> |
| #include <base/containers/contains.h> |
| #include <base/containers/fixed_flat_map.h> |
| #include <base/functional/bind.h> |
| #include <base/logging.h> |
| #include <base/memory/ptr_util.h> |
| #include <base/strings/strcat.h> |
| #include <base/strings/string_number_conversions.h> |
| #include <base/strings/string_util.h> |
| #include <brillo/variant_dictionary.h> |
| #include <chromeos/dbus/service_constants.h> |
| #include <chromeos/net-base/ip_address.h> |
| #include <chromeos/net-base/network_config.h> |
| #include <chromeos/net-base/technology.h> |
| #include <dbus/bus.h> |
| #include <dbus/object_path.h> |
| #include <shill/dbus-proxies.h> |
| |
| namespace patchpanel { |
| |
| namespace { |
| |
| constexpr std::string_view kNoService = "no_service"; |
| |
| std::optional<net_base::Technology> ParseDeviceType(std::string_view type_str) { |
| static constexpr auto str2enum = |
| base::MakeFixedFlatMap<std::string_view, net_base::Technology>({ |
| {shill::kTypeCellular, net_base::Technology::kCellular}, |
| {shill::kTypeEthernet, net_base::Technology::kEthernet}, |
| {shill::kTypeEthernetEap, net_base::Technology::kEthernet}, |
| {shill::kTypeWifi, net_base::Technology::kWiFi}, |
| {shill::kTypeVPN, net_base::Technology::kVPN}, |
| }); |
| |
| const auto iter = str2enum.find(type_str); |
| if (iter == str2enum.end()) { |
| return std::nullopt; |
| } |
| return iter->second; |
| } |
| |
| void RunDefaultNetworkListeners( |
| const std::optional<ShillClient::Device>& new_device, |
| const std::optional<ShillClient::Device>& prev_device, |
| const std::vector<ShillClient::DefaultDeviceChangeHandler>& listeners) { |
| const auto* new_p = new_device ? new_device.operator->() : nullptr; |
| const auto* prev_p = prev_device ? prev_device.operator->() : nullptr; |
| for (const auto& h : listeners) { |
| if (!h.is_null()) { |
| h.Run(new_p, prev_p); |
| } |
| } |
| } |
| |
| bool IsActiveDevice(const ShillClient::Device& device) { |
| // By default all new non-Cellular shill Devices are active. |
| if (device.technology != net_base::Technology::kCellular) { |
| return true; |
| } |
| // b/273741099: A Cellular Device is active iff it has a primary multiplexed |
| // interface. |
| return device.primary_multiplexed_interface.has_value(); |
| } |
| |
| } // namespace |
| |
| bool ShillClient::Device::IsConnected() const { |
| return network_config.ipv4_address.has_value() || |
| !network_config.ipv6_addresses.empty(); |
| } |
| |
| bool ShillClient::Device::IsIPv6Only() const { |
| return !network_config.ipv4_address.has_value() && |
| !network_config.ipv6_addresses.empty(); |
| } |
| |
| std::string_view ShillClient::Device::ActiveIfname() const { |
| return primary_multiplexed_interface ? *primary_multiplexed_interface |
| : ifname; |
| } |
| |
| std::string ShillClient::Device::SessionIDString() const { |
| return session_id ? base::NumberToString(session_id.value()) : "none"; |
| } |
| |
| std::unique_ptr<ShillClient> ShillClient::New( |
| const scoped_refptr<dbus::Bus>& bus, System* system) { |
| auto client = base::WrapUnique(new ShillClient(bus, system)); |
| client->Initialize(); |
| return client; |
| } |
| |
| ShillClient::ShillClient(const scoped_refptr<dbus::Bus>& bus, System* system) |
| : bus_(bus), system_(system) {} |
| |
| ShillClient::~ShillClient() = default; |
| |
| void ShillClient::Initialize() { |
| manager_proxy_ = |
| std::make_unique<org::chromium::flimflam::ManagerProxy>(bus_); |
| manager_proxy_->RegisterPropertyChangedSignalHandler( |
| base::BindRepeating(&ShillClient::OnManagerPropertyChange, |
| weak_factory_.GetWeakPtr()), |
| base::BindOnce(&ShillClient::OnManagerPropertyChangeRegistration, |
| weak_factory_.GetWeakPtr())); |
| // Shill client needs to know about the current default devices in case the |
| // default devices are available prior to the client. |
| UpdateDefaultDevices(); |
| |
| // Also fetch the DoH provider list. |
| brillo::VariantDictionary props; |
| if (!manager_proxy_->GetProperties(&props, nullptr)) { |
| LOG(ERROR) << "Unable to get Manager properties"; |
| return; |
| } |
| if (const auto it = props.find(shill::kDNSProxyDOHProvidersProperty); |
| it != props.end()) { |
| UpdateDoHProviders(it->second); |
| } else { |
| LOG(ERROR) << "Manager properties is missing " |
| << shill::kDNSProxyDOHProvidersProperty; |
| } |
| } |
| |
| const ShillClient::Device* ShillClient::default_logical_device() const { |
| if (!default_logical_device_) { |
| return nullptr; |
| } |
| return default_logical_device_.operator->(); |
| } |
| |
| const ShillClient::Device* ShillClient::default_physical_device() const { |
| if (!default_physical_device_) { |
| return nullptr; |
| } |
| return default_physical_device_.operator->(); |
| } |
| |
| const std::vector<ShillClient::Device> ShillClient::GetDevices() const { |
| std::vector<Device> devices; |
| devices.reserve(devices_.size()); |
| for (const auto& [_, device] : devices_) { |
| devices.push_back(device); |
| } |
| return devices; |
| } |
| |
| 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); |
| } |
| |
| void ShillClient::UpdateNetworkConfigCache( |
| int ifindex, |
| const net_base::NetworkConfig& network_config, |
| int session_id) { |
| // Currently the session ID is only used for logging, it is not needed to |
| // react to changes of the session id for an interface other than updating the |
| // corresponding Device state in ShillClient. |
| session_id_cache_[ifindex] = session_id; |
| for (auto& [_, device] : devices_) { |
| if (device.ifindex == ifindex) { |
| device.session_id = session_id; |
| } |
| } |
| if (default_physical_device_ && |
| default_physical_device_->ifindex == ifindex) { |
| default_physical_device_->session_id = session_id; |
| } |
| if (default_logical_device_ && default_logical_device_->ifindex == ifindex) { |
| default_logical_device_->session_id = session_id; |
| } |
| |
| const auto it = network_config_cache_.find(ifindex); |
| bool has_changed = false; |
| if (it == network_config_cache_.end()) { |
| network_config_cache_.insert({ifindex, network_config}); |
| has_changed = true; |
| } else { |
| has_changed = it->second != network_config; |
| if (has_changed) { |
| it->second = network_config; |
| } |
| } |
| |
| if (has_changed) { |
| OnDeviceNetworkConfigChange(ifindex); |
| } |
| } |
| |
| void ShillClient::ClearNetworkConfigCache(int ifindex) { |
| session_id_cache_.erase(ifindex); |
| if (network_config_cache_.erase(ifindex) == 1) { |
| OnDeviceNetworkConfigChange(ifindex); |
| } |
| } |
| |
| void ShillClient::UpdateDefaultDevices() { |
| // Iterate through Services listed as the shill Manager "Services" properties. |
| // This Service DBus path list is built in shill with the Manager function |
| // EnumerateAvailableServices() which uses the vector of Services with the |
| // Service::Compare() function. This guarantees that connected Services are at |
| // the front of the list. If a VPN Service is connected, it is always at the |
| // front of the list, however this relies on the following implementation |
| // details: |
| // - portal detection is not run on VPN, therefore a connected VPN should |
| // always be in the "online" state. |
| // - the shill Manager Technology order property has VPN in front |
| // (Manager.GetServiceOrder). |
| const auto services = GetServices(); |
| |
| // Complete |service_logname_cache_|. The service lognames are stable until |
| // reboot and only need to be fetched once per service. Most of the time there |
| // is no extra Service DBus property fetch. |
| for (const auto& service_path : services) { |
| if (!base::Contains(service_logname_cache_, service_path)) { |
| GetDevicePathFromServicePath(service_path); |
| } |
| } |
| |
| if (services.empty()) { |
| SetDefaultLogicalDevice(std::nullopt); |
| SetDefaultPhysicalDevice(std::nullopt); |
| return; |
| } |
| auto default_logical_device = GetDeviceFromServicePath(services[0]); |
| if (!default_logical_device) { |
| SetDefaultLogicalDevice(std::nullopt); |
| SetDefaultPhysicalDevice(std::nullopt); |
| return; |
| } |
| if (!IsActiveDevice(*default_logical_device)) { |
| SetDefaultLogicalDevice(std::nullopt); |
| SetDefaultPhysicalDevice(std::nullopt); |
| return; |
| } |
| SetDefaultLogicalDevice(default_logical_device); |
| |
| // No VPN connection, the logical and physical Devices are the same. |
| if (default_logical_device->technology != net_base::Technology::kVPN) { |
| SetDefaultPhysicalDevice(default_logical_device); |
| return; |
| } |
| |
| // In case of a VPN, also get the physical Device properties. |
| if (services.size() < 2) { |
| LOG(ERROR) << "No physical Service found"; |
| SetDefaultPhysicalDevice(std::nullopt); |
| return; |
| } |
| auto default_physical_device = GetDeviceFromServicePath(services[1]); |
| if (!default_physical_device) { |
| LOG(ERROR) << "Could not update the default physical Device"; |
| SetDefaultPhysicalDevice(std::nullopt); |
| return; |
| } |
| if (!IsActiveDevice(*default_physical_device)) { |
| LOG(ERROR) << default_physical_device << " found for Service " |
| << services[1].value() |
| << " is not active, but a VPN was a connected"; |
| SetDefaultPhysicalDevice(std::nullopt); |
| return; |
| } |
| SetDefaultPhysicalDevice(default_physical_device); |
| } |
| |
| std::vector<dbus::ObjectPath> ShillClient::GetServices() { |
| brillo::VariantDictionary manager_properties; |
| if (!manager_proxy_->GetProperties(&manager_properties, nullptr)) { |
| LOG(ERROR) << "Unable to get Manager properties"; |
| return {}; |
| } |
| return brillo::GetVariantValueOrDefault<std::vector<dbus::ObjectPath>>( |
| manager_properties, shill::kServicesProperty); |
| } |
| |
| std::optional<dbus::ObjectPath> ShillClient::GetDevicePathFromServicePath( |
| const dbus::ObjectPath& service_path) { |
| brillo::VariantDictionary service_properties; |
| org::chromium::flimflam::ServiceProxy service_proxy(bus_, service_path); |
| if (!service_proxy.GetProperties(&service_properties, nullptr)) { |
| LOG(ERROR) << "Unable to get Service properties for " |
| << service_path.value(); |
| return std::nullopt; |
| } |
| |
| // Populate |service_logname_cache_|. |
| if (const auto it = service_properties.find(shill::kLogNameProperty); |
| it != service_properties.end()) { |
| service_logname_cache_[service_path] = it->second.TryGet<std::string>(); |
| } |
| |
| // Check if there is any connected Service at the moment. |
| if (const auto it = service_properties.find(shill::kIsConnectedProperty); |
| it == service_properties.end()) { |
| LOG(ERROR) << "Service " << service_path.value() << " missing property " |
| << shill::kIsConnectedProperty; |
| return std::nullopt; |
| } else if (!it->second.TryGet<bool>()) { |
| // There is no default Device if there is no connected Service. |
| LOG(INFO) << "Service " << service_path.value() << " was not connected"; |
| return std::nullopt; |
| } |
| |
| auto device_path = brillo::GetVariantValueOrDefault<dbus::ObjectPath>( |
| service_properties, shill::kDeviceProperty); |
| if (!device_path.IsValid()) { |
| LOG(ERROR) << "Service " << service_path.value() << " missing property " |
| << shill::kDeviceProperty; |
| return std::nullopt; |
| } |
| |
| return device_path; |
| } |
| |
| std::optional<ShillClient::Device> ShillClient::GetDeviceFromServicePath( |
| const dbus::ObjectPath& service_path) { |
| const auto device_path = GetDevicePathFromServicePath(service_path); |
| if (!device_path) { |
| return std::nullopt; |
| } |
| return GetDeviceProperties(*device_path); |
| } |
| |
| 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::kDNSProxyDOHProvidersProperty) { |
| UpdateDoHProviders(property_value); |
| return; |
| } 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. |
| UpdateDefaultDevices(); |
| } |
| |
| void ShillClient::SetDefaultLogicalDevice(const std::optional<Device>& device) { |
| if (!default_logical_device_ && !device) { |
| return; |
| } |
| if (default_logical_device_ && device && |
| default_logical_device_->ifname == device->ifname) { |
| return; |
| } |
| LOG(INFO) << "Default logical device changed from " << default_logical_device_ |
| << " to " << device; |
| RunDefaultNetworkListeners(device, default_logical_device_, |
| default_logical_device_handlers_); |
| default_logical_device_ = device; |
| } |
| |
| void ShillClient::SetDefaultPhysicalDevice( |
| const std::optional<Device>& device) { |
| if (!default_physical_device_ && !device) { |
| return; |
| } |
| if (default_physical_device_ && device && |
| default_physical_device_->ifname == device->ifname) { |
| return; |
| } |
| LOG(INFO) << "Default physical device changed from " |
| << default_physical_device_ << " to " << device; |
| RunDefaultNetworkListeners(device, default_physical_device_, |
| default_physical_device_handlers_); |
| default_physical_device_ = 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. |
| if (default_logical_device_) { |
| handler.Run(default_logical_device_.operator->(), nullptr); |
| } |
| } |
| |
| 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. |
| if (default_physical_device_) { |
| handler.Run(default_physical_device_.operator->(), nullptr); |
| } |
| } |
| |
| void ShillClient::RegisterDevicesChangedHandler( |
| const DevicesChangeHandler& handler) { |
| device_handlers_.emplace_back(handler); |
| // Explicitly trigger the callback to ensure existing Devices are captured. |
| handler.Run(GetDevices(), /*removed=*/{}); |
| } |
| |
| 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) { |
| // All current shill Devices advertised by shill. This set is used |
| // for finding Devices removed by shill and contains both active and inactive |
| // Devices. |
| std::set<dbus::ObjectPath> current; |
| |
| // Find all new active shill Devices not yet tracked by patchpanel. |
| std::vector<Device> added_devices; |
| for (const auto& device_path : |
| property_value.TryGet<std::vector<dbus::ObjectPath>>()) { |
| current.insert(device_path); |
| |
| // Registers handler if we see this shill Device for the first time. |
| if (known_device_paths_.insert(device_path).second) { |
| org::chromium::flimflam::DeviceProxy proxy(bus_, device_path); |
| proxy.RegisterPropertyChangedSignalHandler( |
| base::BindRepeating(&ShillClient::OnDevicePropertyChange, |
| weak_factory_.GetWeakPtr(), device_path), |
| base::BindOnce(&ShillClient::OnDevicePropertyChangeRegistration, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| // Populate ShillClient::Device properties for any new active shill Device. |
| if (!base::Contains(devices_, device_path)) { |
| const std::optional<ShillClient::Device> new_device = |
| GetDeviceProperties(device_path); |
| if (!new_device.has_value()) { |
| LOG(WARNING) << "Failed to add properties of new Device " |
| << device_path.value(); |
| devices_.erase(device_path); |
| continue; |
| } |
| if (!IsActiveDevice(*new_device)) { |
| LOG(INFO) << "Ignoring inactive shill Device " << *new_device; |
| continue; |
| } |
| LOG(INFO) << "New shill Device " << *new_device; |
| added_devices.push_back(*new_device); |
| devices_[device_path] = *new_device; |
| } |
| } |
| |
| // Find all shill Devices removed by shill or shill Devices that became |
| // inactive and remove them from |devices_|, |
| std::vector<Device> removed_devices; |
| for (auto it = devices_.begin(); it != devices_.end();) { |
| if (!base::Contains(current, it->first) || !IsActiveDevice(it->second)) { |
| LOG(INFO) << "Removed shill Device " << it->second; |
| removed_devices.push_back(it->second); |
| it = devices_.erase(it); |
| } else { |
| it++; |
| } |
| } |
| |
| // This can happen if: |
| // - The default network switched from one device to another. |
| // - An inactive Device is removed by shill and it was already ignored by |
| // ShillClient. |
| // - A Device is added by shill but not yet considered active, and should be |
| // ignored by ShillClient. |
| if (added_devices.empty() && removed_devices.empty()) { |
| return; |
| } |
| |
| // Update DevicesChangeHandler listeners. |
| for (const auto& h : device_handlers_) { |
| h.Run(added_devices, removed_devices); |
| } |
| } |
| |
| std::optional<ShillClient::Device> ShillClient::GetDeviceProperties( |
| const dbus::ObjectPath& device_path) { |
| auto output = std::make_optional<ShillClient::Device>(); |
| |
| org::chromium::flimflam::DeviceProxy proxy(bus_, device_path); |
| brillo::VariantDictionary props; |
| if (!proxy.GetProperties(&props, nullptr)) { |
| LOG(ERROR) << "Unable to get shill Device properties for " |
| << device_path.value(); |
| return std::nullopt; |
| } |
| |
| const auto& type_it = props.find(shill::kTypeProperty); |
| if (type_it == props.end()) { |
| LOG(ERROR) << "shill Device properties is missing Type for " |
| << device_path.value(); |
| return std::nullopt; |
| } |
| const std::string& type_str = type_it->second.TryGet<std::string>(); |
| const std::optional<net_base::Technology> technology = |
| ParseDeviceType(type_str); |
| if (!technology.has_value()) { |
| LOG(ERROR) << "Unknown shill Device type " << type_str << " for " |
| << device_path.value(); |
| return std::nullopt; |
| } |
| output->technology = *technology; |
| |
| const auto& interface_it = props.find(shill::kInterfaceProperty); |
| if (interface_it == props.end()) { |
| LOG(ERROR) << "shill Device properties is missing Interface for " |
| << device_path.value(); |
| return std::nullopt; |
| } |
| output->shill_device_interface_property = |
| interface_it->second.TryGet<std::string>(); |
| output->ifname = interface_it->second.TryGet<std::string>(); |
| |
| // Ensure that |primary_multiplexed_interface| is nullopt when: |
| // - kPrimaryMultiplexedInterfaceProperty is not defined for Cellular |
| // Devices, |
| // - the Device is not a Cellular Device. |
| output->primary_multiplexed_interface = std::nullopt; |
| if (output->technology == net_base::Technology::kCellular) { |
| const auto& it = props.find(shill::kPrimaryMultiplexedInterfaceProperty); |
| if (it == props.end()) { |
| LOG(WARNING) << "shill Cellular Device properties is missing " |
| << shill::kPrimaryMultiplexedInterfaceProperty << " for " |
| << device_path.value(); |
| } else { |
| const auto& primary_multiplexed_interface = |
| it->second.TryGet<std::string>(); |
| if (!primary_multiplexed_interface.empty()) { |
| output->primary_multiplexed_interface = primary_multiplexed_interface; |
| } |
| } |
| // b/267111163: ensure for Cellular Device that the network interface |
| // |ifname| used for the datapath setup is set to the primary multiplexed |
| // interface. |
| output->ifname = output->primary_multiplexed_interface.value_or(""); |
| } |
| |
| // When the datapath interface exists and has an interface index, cache the |
| // datapath interface name |ifname| and interface index |ifindex| keyed by the |
| // shill Device property (|shill_device_interface_property|). For Cellular |
| // Devices this ensures that the name of the primary multiplexed interface is |
| // known after the network has disconnected. Knowing the datapath interface |
| // name is necessary for mutliple cleanup operations. If the interface index |
| // cannot be obtained from the kernel, look up the cache to obtain the |
| // interface name and datapath interface index from the cache. |
| output->ifindex = system_->IfNametoindex(output->ifname); |
| if (output->ifindex > 0) { |
| datapath_interface_cache_[output->shill_device_interface_property] = { |
| output->ifname, output->ifindex}; |
| } else { |
| const auto it = |
| datapath_interface_cache_.find(output->shill_device_interface_property); |
| if (it != datapath_interface_cache_.end()) { |
| output->ifname = it->second.first; |
| output->ifindex = it->second.second; |
| } else if (output->technology == net_base::Technology::kCellular) { |
| // When a Cellular shill Device is inactive, it is expected that the |
| // datapath interface name and interface index are undefined. Furthermore |
| // if the Device has never been active, there is no cache entry in |
| // |datapath_interface_cache_| yet. |
| output->ifname = ""; |
| output->ifindex = -1; |
| } else { |
| LOG(ERROR) |
| << "No datapath interface name and index entry for shill Device " |
| << output->shill_device_interface_property; |
| return std::nullopt; |
| } |
| } |
| |
| if (const auto it = network_config_cache_.find(output->ifindex); |
| it != network_config_cache_.end()) { |
| output->network_config = it->second; |
| } else { |
| output->network_config = {}; |
| } |
| |
| // Optional property: a Device does not necessarily have a selected Service at |
| // all time. |
| const auto& selected_service_it = props.find(shill::kSelectedServiceProperty); |
| if (selected_service_it != props.end()) { |
| output->service_path = |
| selected_service_it->second.TryGet<dbus::ObjectPath>().value(); |
| } |
| |
| if (const auto it = |
| service_logname_cache_.find(dbus::ObjectPath(output->service_path)); |
| it != service_logname_cache_.end()) { |
| output->service_logname = it->second; |
| } else if (output->service_path != "") { |
| output->service_logname = output->service_path; |
| } else { |
| output->service_logname = std::string(kNoService); |
| } |
| |
| if (const auto it = session_id_cache_.find(output->ifindex); |
| it != session_id_cache_.end()) { |
| output->session_id = it->second; |
| } else { |
| output->session_id = std::nullopt; |
| } |
| |
| output->logging_tag = |
| base::StrCat({output->ActiveIfname(), " ", output->service_logname, |
| " sid=", output->SessionIDString()}); |
| |
| return output; |
| } |
| |
| const ShillClient::Device* ShillClient::GetDeviceByShillDeviceName( |
| const std::string& shill_device_interface_property) const { |
| // To find the VPN Device, the default logical Device must be checked |
| // separately. |
| if (default_logical_device_ && |
| default_logical_device_->shill_device_interface_property == |
| shill_device_interface_property) { |
| return default_logical_device_.operator->(); |
| } |
| for (const auto& [_, device] : devices_) { |
| if (device.shill_device_interface_property == |
| shill_device_interface_property) { |
| return &device; |
| } |
| } |
| return nullptr; |
| } |
| |
| const ShillClient::Device* ShillClient::GetDeviceByIfindex(int ifindex) const { |
| // To find the VPN Device, the default logical Device must be checked |
| // separately. |
| if (default_logical_device_ && default_logical_device_->ifindex == ifindex) { |
| return default_logical_device_.operator->(); |
| } |
| for (const auto& [_, device] : devices_) { |
| if (device.ifindex == ifindex) { |
| return &device; |
| } |
| } |
| return nullptr; |
| } |
| |
| void ShillClient::OnDevicePropertyChangeRegistration( |
| const std::string& dbus_interface_name, |
| const std::string& signal_name, |
| bool success) { |
| if (!success) { |
| LOG(ERROR) << "Unable to register Device property listener for " |
| << signal_name; |
| } |
| } |
| |
| void ShillClient::OnDevicePropertyChange(const dbus::ObjectPath& device_path, |
| const std::string& property_name, |
| const brillo::Any& property_value) { |
| if (property_name == shill::kPrimaryMultiplexedInterfaceProperty) { |
| OnDevicePrimaryMultiplexedInterfaceChange( |
| device_path, property_value.TryGet<std::string>()); |
| } |
| } |
| |
| void ShillClient::OnDevicePrimaryMultiplexedInterfaceChange( |
| const dbus::ObjectPath& device_path, |
| const std::string& primary_multiplexed_interface) { |
| const auto& device_it = devices_.find(device_path); |
| if (device_it == devices_.end() && primary_multiplexed_interface.empty()) { |
| // b/423793120: Ignore notifications about empty primary multiplexed |
| // interface if the shill Device is already considered inactive. |
| return; |
| } |
| |
| LOG(INFO) << __func__ << ": Device " << device_path.value() |
| << " has primary multiplexed interface \"" |
| << primary_multiplexed_interface << "\""; |
| if (device_it == devices_.end() && !primary_multiplexed_interface.empty()) { |
| // If the shill Device is not found in |devices_| it is not active. If the |
| // primary multiplexed interface is now defined, that Device is active and |
| // needs to be advertised as a new Device. |
| ScanDevices(); |
| UpdateDefaultDevices(); |
| // b/294053895: If the shill Device is now active, it might already be |
| // connected. Make sure that IP configuration listeners are notified. |
| const auto& device_it = devices_.find(device_path); |
| if (device_it != devices_.end() && IsActiveDevice(device_it->second)) { |
| NotifyIPConfigChangeHandlers(device_it->second); |
| NotifyIPv6NetworkChangeHandlers(device_it->second, {}); |
| } |
| return; |
| } |
| |
| // The shill Device is already active the primary multiplexed interface is |
| // already known, this event can be ignored. |
| if (primary_multiplexed_interface == |
| device_it->second.primary_multiplexed_interface) { |
| return; |
| } |
| |
| // When the shill Device is already active and the primary multiplexed |
| // interface property changed, it should now be empty and the shill Device |
| // should not be active anymore. Refresh all properties at once and advertise |
| // it as a removed Device. |
| if (!primary_multiplexed_interface.empty()) { |
| LOG(ERROR) << __func__ << ": Device " << device_path.value() |
| << " has primary multiplexed interface \"" |
| << primary_multiplexed_interface << "\" but we had " |
| << device_it->second; |
| } |
| const std::optional<Device> updated_device = GetDeviceProperties(device_path); |
| if (!updated_device.has_value()) { |
| LOG(ERROR) << "Failed to update properties of Device " |
| << device_path.value(); |
| return; |
| } |
| device_it->second = *updated_device; |
| if (!IsActiveDevice(device_it->second)) { |
| ScanDevices(); |
| UpdateDefaultDevices(); |
| } |
| } |
| |
| void ShillClient::OnDeviceNetworkConfigChange(int ifindex) { |
| auto device_it = devices_.begin(); |
| for (; device_it != devices_.end(); device_it++) { |
| if (device_it->second.ifindex == ifindex) { |
| break; |
| } |
| } |
| if (device_it == devices_.end()) { |
| // If the Device is not found in |devices_| it is not active. Ignore IP |
| // configuration changes until the device becomes active. |
| return; |
| } |
| |
| const dbus::ObjectPath& device_path = device_it->first; |
| net_base::NetworkConfig old_ip_config = device_it->second.network_config; |
| |
| // Refresh all properties at once. |
| const std::optional<Device> updated_device = GetDeviceProperties(device_path); |
| if (!updated_device.has_value()) { |
| LOG(ERROR) << "Failed to update properties of Device " |
| << device_path.value(); |
| return; |
| } |
| device_it->second = *updated_device; |
| |
| // Do not run the IPConfigsChangeHandler and IPv6NetworkChangeHandler |
| // callbacks if there is no IPConfig change. |
| const auto& new_ip_config = device_it->second.network_config; |
| if (old_ip_config == new_ip_config) { |
| return; |
| } |
| |
| // Ensure that the cached states of the default physical Device and default |
| // logical Device are refreshed as well. |
| // TODO(b/273741099): Handle the VPN Device. Since the VPN Device is not |
| // exposed in kDevicesProperty, ShillClient never registers a signal handler |
| // for Device property changes on the VPN Device. |
| if (default_physical_device_ && |
| default_physical_device_->ifname == device_it->second.ifname) { |
| default_physical_device_ = device_it->second; |
| } |
| if (default_logical_device_ && |
| default_logical_device_->ifname == device_it->second.ifname) { |
| default_logical_device_ = device_it->second; |
| } |
| |
| LOG(INFO) << "[" << device_path.value() |
| << "]: IPConfig changed: " << new_ip_config; |
| NotifyIPConfigChangeHandlers(device_it->second); |
| NotifyIPv6NetworkChangeHandlers(device_it->second, old_ip_config); |
| } |
| |
| void ShillClient::NotifyIPConfigChangeHandlers(const Device& device) { |
| for (const auto& handler : ipconfigs_handlers_) { |
| handler.Run(device); |
| } |
| } |
| |
| void ShillClient::NotifyIPv6NetworkChangeHandlers( |
| const Device& device, const net_base::NetworkConfig& old_config) { |
| // Always trigger the callback if PD prefix changed. |
| if (old_config.ipv6_delegated_prefixes != |
| device.network_config.ipv6_delegated_prefixes) { |
| LOG(INFO) << __func__ << ": ipv6_delegated_prefixes changes: from " |
| << old_config << " to " << device.network_config; |
| for (const auto& handler : ipv6_network_handlers_) { |
| handler.Run(device); |
| } |
| return; |
| } |
| // Compares if the new IPv6 network is the same as the old one by checking its |
| // prefix. Note that we are currently only assuming all addresses are of a |
| // same prefix, and only comparing the first address. |
| const auto& old_cidr = old_config.ipv6_addresses; |
| const auto& new_cidr = device.network_config.ipv6_addresses; |
| if (old_cidr.empty() && new_cidr.empty()) { |
| return; |
| } |
| if (!old_cidr.empty() && !new_cidr.empty() && |
| old_cidr[0].GetPrefixCIDR() == new_cidr[0].GetPrefixCIDR()) { |
| return; |
| } |
| LOG(INFO) << __func__ << ": ipv6_addresses subnet changes: from " |
| << old_config << " to " << device.network_config; |
| for (const auto& handler : ipv6_network_handlers_) { |
| handler.Run(device); |
| } |
| } |
| |
| void ShillClient::RegisterDoHProvidersChangedHandler( |
| const DoHProvidersChangeHandler& handler) { |
| doh_provider_handlers_.push_back(handler); |
| handler.Run(); |
| } |
| |
| void ShillClient::UpdateDoHProviders(const brillo::Any& property_value) { |
| base::flat_set<std::string> new_doh_providers; |
| for (const auto& [key, _] : |
| property_value.TryGet<brillo::VariantDictionary>()) { |
| new_doh_providers.insert(key); |
| } |
| |
| if (new_doh_providers == doh_providers_) { |
| return; |
| } |
| |
| doh_providers_.swap(new_doh_providers); |
| for (const auto& h : doh_provider_handlers_) { |
| h.Run(); |
| } |
| } |
| |
| void ShillClient::set_doh_providers_for_testing(const DoHProviders& value) { |
| doh_providers_ = value; |
| } |
| |
| std::ostream& operator<<(std::ostream& stream, const ShillClient::Device& dev) { |
| stream << "{shill_device: " << dev.shill_device_interface_property |
| << ", type: " |
| << (dev.technology.has_value() ? ToString(*dev.technology) |
| : "Unknown"); |
| if (dev.technology == net_base::Technology::kCellular) { |
| stream << ", primary_multiplexed_interface: " |
| << dev.primary_multiplexed_interface.value_or("none"); |
| } |
| return stream << ", ifname: " << dev.ifname << ", ifindex: " << dev.ifindex |
| << ", service: " << dev.service_logname |
| << ", sid: " << dev.SessionIDString() << "}"; |
| } |
| |
| std::ostream& operator<<(std::ostream& stream, |
| const std::optional<ShillClient::Device>& dev) { |
| if (!dev) { |
| return stream << "none"; |
| } |
| return stream << *dev; |
| } |
| |
| std::ostream& operator<<(std::ostream& stream, const ShillClient::Device* dev) { |
| if (!dev) { |
| return stream << "none"; |
| } |
| return stream << *dev; |
| } |
| |
| } // namespace patchpanel |