summaryrefslogtreecommitdiffstats
path: root/src/network/networkd-wiphy.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/network/networkd-wiphy.c')
-rw-r--r--src/network/networkd-wiphy.c500
1 files changed, 500 insertions, 0 deletions
diff --git a/src/network/networkd-wiphy.c b/src/network/networkd-wiphy.c
new file mode 100644
index 0000000..63874cd
--- /dev/null
+++ b/src/network/networkd-wiphy.c
@@ -0,0 +1,500 @@
+/* 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);
+}