/* SPDX-License-Identifier: LGPL-2.1-or-later */ #include #include #include "device-private.h" #include "device-util.h" #include "networkd-manager.h" #include "networkd-wiphy.h" #include "parse-util.h" #include "path-util.h" #include "udev-util.h" #include "wifi-util.h" Wiphy *wiphy_free(Wiphy *w) { if (!w) return NULL; if (w->manager) { hashmap_remove_value(w->manager->wiphy_by_index, UINT32_TO_PTR(w->index), w); if (w->name) hashmap_remove_value(w->manager->wiphy_by_name, w->name, w); } sd_device_unref(w->dev); sd_device_unref(w->rfkill); free(w->name); return mfree(w); } static int wiphy_new(Manager *manager, sd_netlink_message *message, Wiphy **ret) { _cleanup_(wiphy_freep) Wiphy *w = NULL; _cleanup_free_ char *name = NULL; uint32_t index; int r; assert(manager); assert(message); r = sd_netlink_message_read_u32(message, NL80211_ATTR_WIPHY, &index); if (r < 0) return r; r = sd_netlink_message_read_string_strdup(message, NL80211_ATTR_WIPHY_NAME, &name); if (r < 0) return r; w = new(Wiphy, 1); if (!w) return -ENOMEM; *w = (Wiphy) { .manager = manager, .index = index, .name = TAKE_PTR(name), }; r = hashmap_ensure_put(&manager->wiphy_by_index, NULL, UINT32_TO_PTR(w->index), w); if (r < 0) return r; r = hashmap_ensure_put(&w->manager->wiphy_by_name, &string_hash_ops, w->name, w); if (r < 0) return r; log_wiphy_debug(w, "Saved new wiphy: index=%"PRIu32, w->index); if (ret) *ret = w; TAKE_PTR(w); return 0; } int wiphy_get_by_index(Manager *manager, uint32_t index, Wiphy **ret) { Wiphy *w; assert(manager); w = hashmap_get(manager->wiphy_by_index, UINT32_TO_PTR(index)); if (!w) return -ENODEV; if (ret) *ret = w; return 0; } int wiphy_get_by_name(Manager *manager, const char *name, Wiphy **ret) { Wiphy *w; assert(manager); assert(name); w = hashmap_get(manager->wiphy_by_name, name); if (!w) return -ENODEV; if (ret) *ret = w; return 0; } static int link_get_wiphy(Link *link, Wiphy **ret) { _cleanup_(sd_device_unrefp) sd_device *phy = NULL; const char *s; int r; assert(link); assert(link->manager); if (link->iftype != ARPHRD_ETHER) return -EOPNOTSUPP; if (!link->dev) return -ENODEV; r = sd_device_get_devtype(link->dev, &s); if (r < 0) return r; if (!streq_ptr(s, "wlan")) return -EOPNOTSUPP; r = sd_device_new_child(&phy, link->dev, "phy80211"); if (r < 0) return r; r = sd_device_get_sysname(phy, &s); if (r < 0) return r; /* TODO: * Maybe, it is better to cache the found Wiphy object in the Link object. * To support that, we need to investigate what happens when the _phy_ is renamed. */ return wiphy_get_by_name(link->manager, s, ret); } static int rfkill_get_state(sd_device *dev) { int r; assert(dev); /* The previous values may be outdated. Let's clear cache and re-read the values. */ device_clear_sysattr_cache(dev); r = device_get_sysattr_bool(dev, "soft"); if (r < 0 && r != -ENOENT) return r; if (r > 0) return RFKILL_SOFT; r = device_get_sysattr_bool(dev, "hard"); if (r < 0 && r != -ENOENT) return r; if (r > 0) return RFKILL_HARD; return RFKILL_UNBLOCKED; } static int wiphy_rfkilled(Wiphy *w) { int r; assert(w); if (!udev_available()) { if (w->rfkill_state != RFKILL_UNBLOCKED) { log_wiphy_debug(w, "Running in container, assuming the radio transmitter is unblocked."); w->rfkill_state = RFKILL_UNBLOCKED; /* To suppress the above log message, cache the state. */ } return false; } if (!w->rfkill) { if (w->rfkill_state != RFKILL_UNBLOCKED) { log_wiphy_debug(w, "No rfkill device found, assuming the radio transmitter is unblocked."); w->rfkill_state = RFKILL_UNBLOCKED; /* To suppress the above log message, cache the state. */ } return false; } r = rfkill_get_state(w->rfkill); if (r < 0) return log_wiphy_debug_errno(w, r, "Could not get rfkill state: %m"); if (w->rfkill_state != r) switch (r) { case RFKILL_UNBLOCKED: log_wiphy_debug(w, "The radio transmitter is unblocked."); break; case RFKILL_SOFT: log_wiphy_debug(w, "The radio transmitter is turned off by software."); break; case RFKILL_HARD: log_wiphy_debug(w, "The radio transmitter is forced off by something outside of the driver's control."); break; default: assert_not_reached(); } w->rfkill_state = r; /* Cache the state to suppress the above log messages. */ return r != RFKILL_UNBLOCKED; } int link_rfkilled(Link *link) { Wiphy *w; int r; assert(link); r = link_get_wiphy(link, &w); if (r < 0) { if (ERRNO_IS_NOT_SUPPORTED(r) || ERRNO_IS_DEVICE_ABSENT(r)) return false; /* Typically, non-wifi interface or running in container */ return log_link_debug_errno(link, r, "Could not get phy: %m"); } return wiphy_rfkilled(w); } static int wiphy_update_name(Wiphy *w, sd_netlink_message *message) { const char *name; int r; assert(w); assert(w->manager); assert(message); r = sd_netlink_message_read_string(message, NL80211_ATTR_WIPHY_NAME, &name); if (r == -ENODATA) return 0; if (r < 0) return r; if (streq(w->name, name)) return 0; log_wiphy_debug(w, "Wiphy name change detected, renamed to %s.", name); hashmap_remove_value(w->manager->wiphy_by_name, w->name, w); r = free_and_strdup(&w->name, name); if (r < 0) return r; r = hashmap_ensure_put(&w->manager->wiphy_by_name, &string_hash_ops, w->name, w); if (r < 0) return r; return 1; /* updated */ } static int wiphy_update_device(Wiphy *w) { _cleanup_(sd_device_unrefp) sd_device *dev = NULL; int r; assert(w); assert(w->name); if (!udev_available()) return 0; w->dev = sd_device_unref(w->dev); r = sd_device_new_from_subsystem_sysname(&dev, "ieee80211", w->name); if (r < 0) return r; if (DEBUG_LOGGING) { const char *s = NULL; (void) sd_device_get_syspath(dev, &s); log_wiphy_debug(w, "Found device: %s", strna(s)); } w->dev = TAKE_PTR(dev); return 0; } static int wiphy_update_rfkill(Wiphy *w) { _cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *e = NULL; sd_device *rfkill; int r; assert(w); if (!udev_available()) return 0; w->rfkill = sd_device_unref(w->rfkill); if (!w->dev) return 0; r = sd_device_enumerator_new(&e); if (r < 0) return r; r = sd_device_enumerator_allow_uninitialized(e); if (r < 0) return r; r = sd_device_enumerator_add_match_subsystem(e, "rfkill", true); if (r < 0) return r; r = sd_device_enumerator_add_match_parent(e, w->dev); if (r < 0) return r; rfkill = sd_device_enumerator_get_device_first(e); if (!rfkill) /* rfkill device may not detected by the kernel yet, and may appear later. */ return -ENODEV; if (sd_device_enumerator_get_device_next(e)) return -ENXIO; /* multiple devices found */ w->rfkill = sd_device_ref(rfkill); if (DEBUG_LOGGING) { const char *s = NULL; (void) sd_device_get_syspath(rfkill, &s); log_wiphy_debug(w, "Found rfkill device: %s", strna(s)); } return 0; } static int wiphy_update(Wiphy *w) { int r; assert(w); r = wiphy_update_device(w); if (r < 0) { if (ERRNO_IS_DEVICE_ABSENT(r)) log_wiphy_debug_errno(w, r, "Failed to update wiphy device, ignoring: %m"); else return log_wiphy_warning_errno(w, r, "Failed to update wiphy device: %m"); } r = wiphy_update_rfkill(w); if (r < 0) { if (ERRNO_IS_DEVICE_ABSENT(r)) log_wiphy_debug_errno(w, r, "Failed to update rfkill device, ignoring: %m"); else return log_wiphy_warning_errno(w, r, "Failed to update rfkill device: %m"); } return 0; } int manager_genl_process_nl80211_wiphy(sd_netlink *genl, sd_netlink_message *message, Manager *manager) { const char *family; uint32_t index; uint8_t cmd; Wiphy *w = NULL; int r; assert(genl); assert(message); assert(manager); if (sd_netlink_message_is_error(message)) { r = sd_netlink_message_get_errno(message); if (r < 0) log_message_warning_errno(message, r, "nl80211: received error message, ignoring"); return 0; } r = sd_genl_message_get_family_name(genl, message, &family); if (r < 0) { log_debug_errno(r, "nl80211: failed to determine genl family, ignoring: %m"); return 0; } if (!streq(family, NL80211_GENL_NAME)) { log_debug("nl80211: Received message of unexpected genl family '%s', ignoring.", family); return 0; } r = sd_genl_message_get_command(genl, message, &cmd); if (r < 0) { log_debug_errno(r, "nl80211: failed to determine genl message command, ignoring: %m"); return 0; } r = sd_netlink_message_read_u32(message, NL80211_ATTR_WIPHY, &index); if (r < 0) { log_debug_errno(r, "nl80211: received %s(%u) message without valid index, ignoring: %m", strna(nl80211_cmd_to_string(cmd)), cmd); return 0; } (void) wiphy_get_by_index(manager, index, &w); switch (cmd) { case NL80211_CMD_NEW_WIPHY: { if (!w) { r = wiphy_new(manager, message, &w); if (r < 0) { log_warning_errno(r, "Failed to save new wiphy, ignoring: %m"); return 0; } } else { r = wiphy_update_name(w, message); if (r < 0) { log_wiphy_warning_errno(w, r, "Failed to update wiphy name, ignoring: %m"); return 0; } if (r == 0) return 0; } r = wiphy_update(w); if (r < 0) log_wiphy_warning_errno(w, r, "Failed to update wiphy, ignoring: %m"); break; } case NL80211_CMD_DEL_WIPHY: if (!w) { log_debug("The kernel removes wiphy we do not know, ignoring: %m"); return 0; } log_wiphy_debug(w, "Removed."); wiphy_free(w); break; default: log_wiphy_debug(w, "nl80211: received %s(%u) message.", strna(nl80211_cmd_to_string(cmd)), cmd); } return 0; } int manager_udev_process_wiphy(Manager *m, sd_device *device, sd_device_action_t action) { const char *name; Wiphy *w; int r; assert(m); assert(device); r = sd_device_get_sysname(device, &name); if (r < 0) return log_device_debug_errno(device, r, "Failed to get sysname: %m"); r = wiphy_get_by_name(m, name, &w); if (r < 0) { /* This error is not critical, as the corresponding genl message may be received later. */ log_device_debug_errno(device, r, "Failed to get Wiphy object, ignoring: %m"); return 0; } return device_unref_and_replace(w->dev, action == SD_DEVICE_REMOVE ? NULL : device); } int manager_udev_process_rfkill(Manager *m, sd_device *device, sd_device_action_t action) { _cleanup_free_ char *parent_path = NULL, *parent_name = NULL; const char *s; Wiphy *w; int r; assert(m); assert(device); r = sd_device_get_syspath(device, &s); if (r < 0) return log_device_debug_errno(device, r, "Failed to get syspath: %m"); /* Do not use sd_device_get_parent() here, as this might be a 'remove' uevent. */ r = path_extract_directory(s, &parent_path); if (r < 0) return log_device_debug_errno(device, r, "Failed to get parent syspath: %m"); r = path_extract_filename(parent_path, &parent_name); if (r < 0) return log_device_debug_errno(device, r, "Failed to get parent name: %m"); r = wiphy_get_by_name(m, parent_name, &w); if (r < 0) { /* This error is not critical, as the corresponding genl message may be received later. */ log_device_debug_errno(device, r, "Failed to get Wiphy object: %m"); return 0; } return device_unref_and_replace(w->rfkill, action == SD_DEVICE_REMOVE ? NULL : device); }