blob: 63874cdf98ec3c38ea1fe7a9cc14280c3e51fbe3 [file] [log] [blame]
/* 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);
}