| /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
| |
| #include <net/if_arp.h> |
| #include <linux/nl80211.h> |
| |
| #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); |
| } |