| // 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/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/technology.h> | |
| #include <dbus/bus.h> | |
| #include <dbus/object_path.h> | |
| #include <shill/dbus-proxies.h> | |
| namespace patchpanel { | |
| namespace { | |
| 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 ipconfig.ipv4_cidr || ipconfig.ipv6_cidr; | |
| } | |
| bool ShillClient::Device::IsIPv6Only() const { | |
| return !ipconfig.ipv4_cidr && ipconfig.ipv6_cidr; | |
| } | |
| ShillClient::ShillClient(const scoped_refptr<dbus::Bus>& bus, System* system) | |
| : bus_(bus), system_(system) { | |
| 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; | |
| } | |
| } | |
| ShillClient::~ShillClient() = default; | |
| 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::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(); | |
| 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<ShillClient::Device> ShillClient::GetDeviceFromServicePath( | |
| 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; | |
| } | |
| // 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 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); | |
| } | |
| } | |
| ShillClient::IPConfig ShillClient::ParseIPConfigsProperty( | |
| const dbus::ObjectPath& device, const brillo::Any& ipconfig_paths) { | |
| IPConfig ipconfig; | |
| for (const auto& path : | |
| ipconfig_paths.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.value() | |
| << "]: Unable to get properties for " << path.value(); | |
| continue; | |
| } | |
| // Gets the value of address, prefix_length, gateway, and dns_servers. | |
| auto it = ipconfig_props.find(shill::kAddressProperty); | |
| if (it == ipconfig_props.end()) { | |
| LOG(WARNING) << "[" << device.value() | |
| << "]: IPConfig properties is missing Address"; | |
| continue; | |
| } | |
| const std::string& address_str = it->second.TryGet<std::string>(); | |
| if (address_str.empty()) { | |
| // On IPv6 only networks, dhcp is expected to fail, nevertheless shill | |
| // will still expose a mostly empty IPConfig object. On dual stack | |
| // networks, the IPv6 configuration may be available before dhcp has | |
| // finished. Avoid logging spurious WARNING messages in these two cases. | |
| continue; | |
| } | |
| it = ipconfig_props.find(shill::kPrefixlenProperty); | |
| if (it == ipconfig_props.end()) { | |
| LOG(WARNING) << "[" << device.value() | |
| << "]: IPConfig properties is missing Prefixlen"; | |
| continue; | |
| } | |
| int prefix_length = it->second.TryGet<int>(); | |
| if (prefix_length == 0) { | |
| LOG(WARNING) | |
| << "[" << device.value() << "]: " | |
| << " IPConfig Prefixlen property is 0, may be an invalid setup"; | |
| } | |
| const auto cidr = | |
| net_base::IPCIDR::CreateFromStringAndPrefix(address_str, prefix_length); | |
| if (!cidr) { | |
| LOG(WARNING) << "[" << device.value() | |
| << "]: IPConfig Address and Prefixlen property was invalid: " | |
| << address_str << "/" << prefix_length; | |
| continue; | |
| } | |
| const bool is_ipv4 = (cidr->GetFamily() == net_base::IPFamily::kIPv4); | |
| const std::string method = is_ipv4 ? "IPv4" : "IPv6"; | |
| if ((is_ipv4 && ipconfig.ipv4_cidr) || (!is_ipv4 && ipconfig.ipv6_cidr)) { | |
| LOG(WARNING) << "[" << device.value() << "]: Duplicated IPconfig for " | |
| << method; | |
| continue; | |
| } | |
| it = ipconfig_props.find(shill::kGatewayProperty); | |
| if (it == ipconfig_props.end()) { | |
| LOG(WARNING) << "[" << device.value() << "]: " << method | |
| << " IPConfig properties is missing Gateway"; | |
| continue; | |
| } | |
| const std::string& gateway = it->second.TryGet<std::string>(); | |
| if (gateway.empty()) { | |
| LOG(WARNING) << "[" << device.value() << "]: " << method | |
| << " IPConfig Gateway property was empty."; | |
| continue; | |
| } | |
| it = ipconfig_props.find(shill::kNameServersProperty); | |
| if (it == ipconfig_props.end()) { | |
| LOG(WARNING) << "[" << device.value() << "]: " << method | |
| << " 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>>(); | |
| // Fills the IPConfig struct according to the type. | |
| if (is_ipv4) { | |
| ipconfig.ipv4_cidr = cidr->ToIPv4CIDR(); | |
| ipconfig.ipv4_gateway = net_base::IPv4Address::CreateFromString(gateway); | |
| if (!ipconfig.ipv4_gateway) { | |
| LOG(WARNING) << "[" << device.value() << "]: " << method | |
| << " IPConfig Gateway property was not valid IPv4Address: " | |
| << gateway; | |
| } | |
| ipconfig.ipv4_dns_addresses = dns_addresses; | |
| } else { // AF_INET6 | |
| ipconfig.ipv6_cidr = cidr->ToIPv6CIDR(); | |
| ipconfig.ipv6_gateway = net_base::IPv6Address::CreateFromString(gateway); | |
| if (!ipconfig.ipv6_gateway) { | |
| LOG(WARNING) << "[" << device.value() << "]: " << method | |
| << " IPConfig Gateway property was not valid IPv6Address: " | |
| << gateway; | |
| } | |
| ipconfig.ipv6_dns_addresses = dns_addresses; | |
| } | |
| } | |
| return ipconfig; | |
| } | |
| 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; | |
| } | |
| } | |
| const auto& ipconfigs_it = props.find(shill::kIPConfigsProperty); | |
| if (ipconfigs_it == props.end()) { | |
| LOG(ERROR) << "shill Device properties is missing IPConfigs for " | |
| << device_path.value(); | |
| return std::nullopt; | |
| } | |
| output->ipconfig = ParseIPConfigsProperty(device_path, ipconfigs_it->second); | |
| // 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(); | |
| } | |
| 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::kIPConfigsProperty) { | |
| OnDeviceIPConfigChange(device_path); | |
| } else 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) { | |
| LOG(INFO) << __func__ << ": Device " << device_path.value() | |
| << " has primary multiplexed interface \"" | |
| << primary_multiplexed_interface << "\""; | |
| const auto& device_it = devices_.find(device_path); | |
| 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, std::nullopt); | |
| } | |
| 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::OnDeviceIPConfigChange(const dbus::ObjectPath& device_path) { | |
| const auto& device_it = devices_.find(device_path); | |
| 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; | |
| } | |
| IPConfig old_ip_config = device_it->second.ipconfig; | |
| // 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 IPConfig& new_ip_config = device_it->second.ipconfig; | |
| 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.ipv6_cidr); | |
| } | |
| void ShillClient::NotifyIPConfigChangeHandlers(const Device& device) { | |
| for (const auto& handler : ipconfigs_handlers_) { | |
| handler.Run(device); | |
| } | |
| } | |
| void ShillClient::NotifyIPv6NetworkChangeHandlers( | |
| const Device& device, const std::optional<net_base::IPv6CIDR>& old_cidr) { | |
| // Compares if the new IPv6 network is the same as the old one by checking its | |
| // prefix. | |
| const auto& new_cidr = device.ipconfig.ipv6_cidr; | |
| if (!old_cidr && !new_cidr) { | |
| return; | |
| } | |
| if (old_cidr && new_cidr && | |
| old_cidr->GetPrefixCIDR() == new_cidr->GetPrefixCIDR()) { | |
| return; | |
| } | |
| for (const auto& handler : ipv6_network_handlers_) { | |
| handler.Run(device); | |
| } | |
| } | |
| void ShillClient::RegisterDoHProvidersChangedHandler( | |
| const DoHProvidersChangeHandler& handler) { | |
| doh_provider_handlers_.push_back(handler); | |
| handler.Run(doh_providers_); | |
| } | |
| 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(doh_providers_); | |
| } | |
| } | |
| 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_path << "}"; | |
| } | |
| 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; | |
| } | |
| std::ostream& operator<<(std::ostream& stream, | |
| const ShillClient::IPConfig& ipconfig) { | |
| return stream << "{ ipv4_cidr: " | |
| << ipconfig.ipv4_cidr.value_or(net_base::IPv4CIDR()) | |
| << ", ipv4_gateway: " | |
| << ipconfig.ipv4_gateway.value_or(net_base::IPv4Address()) | |
| << ", ipv4_dns: [" | |
| << base::JoinString(ipconfig.ipv4_dns_addresses, ",") | |
| << "], ipv6_cidr: " | |
| << ipconfig.ipv6_cidr.value_or(net_base::IPv6CIDR()) | |
| << ", ipv6_gateway: " | |
| << ipconfig.ipv6_gateway.value_or(net_base::IPv6Address()) | |
| << ", ipv6_dns: [" | |
| << base::JoinString(ipconfig.ipv6_dns_addresses, ",") << "]}"; | |
| } | |
| } // namespace patchpanel |