| // 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 <algorithm> |
| #include <optional> |
| |
| #include <base/check.h> |
| #include <base/containers/contains.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 <dbus/object_path.h> |
| #include <net-base/ip_address.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::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::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"; |
| } |
| |
| 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.type != ShillClient::Device::Type::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; |
| } |
| |
| ShillClient::ShillClient(const scoped_refptr<dbus::Bus>& bus, System* system) |
| : bus_(bus), system_(system) { |
| 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())); |
| // Shill client needs to know about the current default devices in case the |
| // default devices are available prior to the client. |
| UpdateDefaultDevices(); |
| } |
| |
| 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->type != ShillClient::Device::Type::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; |
| } |
| |
| Device device = {}; |
| if (!GetDeviceProperties(device_path, &device)) { |
| return std::nullopt; |
| } |
| return 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. |
| 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); |
| } |
| |
| 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)) { |
| ShillClient::Device new_device; |
| if (!GetDeviceProperties(device_path, &new_device)) { |
| 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; |
| } |
| |
| bool ShillClient::GetDeviceProperties(const dbus::ObjectPath& device_path, |
| Device* output) { |
| DCHECK(output); |
| |
| 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 false; |
| } |
| |
| 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 false; |
| } |
| const std::string& type_str = type_it->second.TryGet<std::string>(); |
| output->type = ParseDeviceType(type_str); |
| if (output->type == Device::Type::kUnknown) { |
| LOG(ERROR) << "Unknown shill Device type " << type_str << " for " |
| << device_path.value(); |
| return false; |
| } |
| |
| 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 false; |
| } |
| 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->type == Device::Type::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->type == Device::Type::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 false; |
| } |
| } |
| |
| 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 false; |
| } |
| 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 true; |
| } |
| |
| const ShillClient::Device* ShillClient::GetDevice( |
| 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; |
| } |
| |
| 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; |
| } |
| if (!GetDeviceProperties(device_path, &device_it->second)) { |
| LOG(ERROR) << "Failed to update properties of Device " |
| << device_path.value(); |
| return; |
| } |
| 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. |
| if (!GetDeviceProperties(device_path, &device_it->second)) { |
| LOG(ERROR) << "Failed to update properties of Device " |
| << device_path.value(); |
| return; |
| } |
| |
| // 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); |
| } |
| } |
| |
| std::ostream& operator<<(std::ostream& stream, const ShillClient::Device& dev) { |
| stream << "{shill_device: " << dev.shill_device_interface_property |
| << ", type: " << DeviceTypeName(dev.type); |
| if (dev.type == ShillClient::Device::Type::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::Device::Type type) { |
| return stream << DeviceTypeName(type); |
| } |
| |
| 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 |