diff --git a/man/systemd.netdev.xml b/man/systemd.netdev.xml
index 2207852ae3..1f94e3d599 100644
--- a/man/systemd.netdev.xml
+++ b/man/systemd.netdev.xml
@@ -192,6 +192,9 @@
ipoib
An IP over Infiniband subinterface.
+
+ virtual-wlan
+ A virtual local wireless network (WLAN) interface.
@@ -2179,6 +2182,43 @@
+
+ [VirtualWLAN] Section Options
+ The [VirtualWLAN] section only applies to virtual WLAN interfaces, and accepts the following
+ keys:
+
+
+
+ PhysicalDevice=
+
+ Specifies the name or index of the WLAN physical WLAN device (e.g. 0
+ or phy0). The list of the physical WLAN devices that exist os the host can
+ be obtained by iw phy command. This option is mandatory.
+
+
+
+
+ Type=
+
+ Specifies the type of the interface. Takes one of the ad-hoc,
+ station, ap, ap-vlan,
+ wds, monitor, mesh-point,
+ p2p-client, p2p-go, p2p-device,
+ ocb, and nan. This option is mandatory.
+
+
+
+
+ WDS=
+
+ Enables the Wireless Distribution System (WDS) mode on the interface. The mode is also
+ known as the 4 address mode. Takes a boolean value. Defaults to unset, and
+ the kernel's default will be used.
+
+
+
+
+
Examples
diff --git a/src/network/meson.build b/src/network/meson.build
index 35adad42c0..ca48acae91 100644
--- a/src/network/meson.build
+++ b/src/network/meson.build
@@ -11,18 +11,26 @@ sources = files('''
netdev/bridge.h
netdev/dummy.c
netdev/dummy.h
+ netdev/fou-tunnel.c
+ netdev/fou-tunnel.h
netdev/ifb.c
netdev/ifb.h
netdev/ipoib.c
netdev/ipoib.h
netdev/ipvlan.c
netdev/ipvlan.h
+ netdev/l2tp-tunnel.c
+ netdev/l2tp-tunnel.h
+ netdev/macsec.c
+ netdev/macsec.h
netdev/macvlan.c
netdev/macvlan.h
- netdev/netdev.c
netdev/netdev-util.c
netdev/netdev-util.h
+ netdev/netdev.c
netdev/netdev.h
+ netdev/netdevsim.c
+ netdev/netdevsim.h
netdev/nlmon.c
netdev/nlmon.h
netdev/tunnel.c
@@ -45,14 +53,8 @@ sources = files('''
netdev/vxcan.h
netdev/wireguard.c
netdev/wireguard.h
- netdev/netdevsim.c
- netdev/netdevsim.h
- netdev/fou-tunnel.c
- netdev/fou-tunnel.h
- netdev/l2tp-tunnel.c
- netdev/l2tp-tunnel.h
- netdev/macsec.c
- netdev/macsec.h
+ netdev/wlan.c
+ netdev/wlan.h
netdev/xfrm.c
netdev/xfrm.h
networkd-address-generation.c
@@ -143,6 +145,8 @@ sources = files('''
networkd-util.h
networkd-wifi.c
networkd-wifi.h
+ networkd-wiphy.c
+ networkd-wiphy.h
tc/cake.c
tc/cake.h
tc/codel.c
diff --git a/src/network/netdev/netdev-gperf.gperf b/src/network/netdev/netdev-gperf.gperf
index 0b87e35087..2fec1da06b 100644
--- a/src/network/netdev/netdev-gperf.gperf
+++ b/src/network/netdev/netdev-gperf.gperf
@@ -27,6 +27,7 @@ _Pragma("GCC diagnostic ignored \"-Wimplicit-fallthrough\"")
#include "vxcan.h"
#include "vxlan.h"
#include "wireguard.h"
+#include "wlan.h"
#include "xfrm.h"
%}
struct ConfigPerfItem;
@@ -258,3 +259,6 @@ BatmanAdvanced.RoutingAlgorithm, config_parse_batadv_routing_algorithm,
IPoIB.PartitionKey, config_parse_ipoib_pkey, 0, offsetof(IPoIB, pkey)
IPoIB.Mode, config_parse_ipoib_mode, 0, offsetof(IPoIB, mode)
IPoIB.IgnoreUserspaceMulticastGroups, config_parse_tristate, 0, offsetof(IPoIB, umcast)
+VirtualWLAN.PhysicalDevice, config_parse_wiphy, 0, 0
+VirtualWLAN.Type, config_parse_wlan_iftype, 0, offsetof(WLan, iftype)
+VirtualWLAN.WDS, config_parse_tristate, 0, offsetof(WLan, wds)
diff --git a/src/network/netdev/netdev.c b/src/network/netdev/netdev.c
index 18d118e230..8f68a50297 100644
--- a/src/network/netdev/netdev.c
+++ b/src/network/netdev/netdev.c
@@ -47,6 +47,7 @@
#include "vxcan.h"
#include "vxlan.h"
#include "wireguard.h"
+#include "wlan.h"
#include "xfrm.h"
const NetDevVTable * const netdev_vtable[_NETDEV_KIND_MAX] = {
@@ -86,6 +87,7 @@ const NetDevVTable * const netdev_vtable[_NETDEV_KIND_MAX] = {
[NETDEV_KIND_VXCAN] = &vxcan_vtable,
[NETDEV_KIND_VXLAN] = &vxlan_vtable,
[NETDEV_KIND_WIREGUARD] = &wireguard_vtable,
+ [NETDEV_KIND_WLAN] = &wlan_vtable,
[NETDEV_KIND_XFRM] = &xfrm_vtable,
};
@@ -126,6 +128,7 @@ static const char* const netdev_kind_table[_NETDEV_KIND_MAX] = {
[NETDEV_KIND_VXCAN] = "vxcan",
[NETDEV_KIND_VXLAN] = "vxlan",
[NETDEV_KIND_WIREGUARD] = "wireguard",
+ [NETDEV_KIND_WLAN] = "virtual-wlan",
[NETDEV_KIND_XFRM] = "xfrm",
};
@@ -352,37 +355,40 @@ int netdev_set_ifindex(NetDev *netdev, sd_netlink_message *message) {
return -EINVAL;
}
- r = sd_netlink_message_enter_container(message, IFLA_LINKINFO);
- if (r < 0)
- return log_netdev_error_errno(netdev, r, "Could not get LINKINFO: %m");
+ if (!NETDEV_VTABLE(netdev)->skip_netdev_kind_check) {
- r = sd_netlink_message_read_string(message, IFLA_INFO_KIND, &received_kind);
- if (r < 0)
- return log_netdev_error_errno(netdev, r, "Could not get KIND: %m");
+ r = sd_netlink_message_enter_container(message, IFLA_LINKINFO);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not get LINKINFO: %m");
- r = sd_netlink_message_exit_container(message);
- if (r < 0)
- return log_netdev_error_errno(netdev, r, "Could not exit container: %m");
+ r = sd_netlink_message_read_string(message, IFLA_INFO_KIND, &received_kind);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not get KIND: %m");
- if (netdev->kind == NETDEV_KIND_TAP)
- /* the kernel does not distinguish between tun and tap */
- kind = "tun";
- else {
- kind = netdev_kind_to_string(netdev->kind);
- if (!kind) {
- log_netdev_error(netdev, "Could not get kind");
+ r = sd_netlink_message_exit_container(message);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not exit container: %m");
+
+ if (netdev->kind == NETDEV_KIND_TAP)
+ /* the kernel does not distinguish between tun and tap */
+ kind = "tun";
+ else {
+ kind = netdev_kind_to_string(netdev->kind);
+ if (!kind) {
+ log_netdev_error(netdev, "Could not get kind");
+ netdev_enter_failed(netdev);
+ return -EINVAL;
+ }
+ }
+
+ if (!streq(kind, received_kind)) {
+ log_netdev_error(netdev, "Received newlink with wrong KIND %s, expected %s",
+ received_kind, kind);
netdev_enter_failed(netdev);
return -EINVAL;
}
}
- if (!streq(kind, received_kind)) {
- log_netdev_error(netdev, "Received newlink with wrong KIND %s, expected %s",
- received_kind, kind);
- netdev_enter_failed(netdev);
- return -EINVAL;
- }
-
netdev->ifindex = ifindex;
log_netdev_debug(netdev, "netdev has index %d", netdev->ifindex);
@@ -621,22 +627,23 @@ int netdev_join(NetDev *netdev, Link *link, link_netlink_message_handler_t callb
return 0;
}
-static bool netdev_is_ready_to_create(NetDev *netdev, Link *link) {
+static int netdev_is_ready_to_create(NetDev *netdev, Link *link) {
assert(netdev);
- assert(link);
if (netdev->state != NETDEV_STATE_LOADING)
return false;
- if (!IN_SET(link->state, LINK_STATE_CONFIGURING, LINK_STATE_CONFIGURED))
- return false;
+ if (link) {
+ if (!IN_SET(link->state, LINK_STATE_CONFIGURING, LINK_STATE_CONFIGURED))
+ return false;
- if (netdev_get_create_type(netdev) == NETDEV_CREATE_AFTER_CONFIGURED &&
- link->state != LINK_STATE_CONFIGURED)
- return false;
+ if (netdev_get_create_type(netdev) == NETDEV_CREATE_AFTER_CONFIGURED &&
+ link->state != LINK_STATE_CONFIGURED)
+ return false;
- if (link->set_link_messages > 0)
- return false;
+ if (link->set_link_messages > 0)
+ return false;
+ }
if (NETDEV_VTABLE(netdev)->is_ready_to_create)
return NETDEV_VTABLE(netdev)->is_ready_to_create(netdev, link);
@@ -649,12 +656,13 @@ int request_process_stacked_netdev(Request *req) {
assert(req);
assert(req->link);
- assert(req->type == REQUEST_TYPE_STACKED_NETDEV);
+ assert(req->type == REQUEST_TYPE_NETDEV_STACKED);
assert(req->netdev);
assert(req->netlink_handler);
- if (!netdev_is_ready_to_create(req->netdev, req->link))
- return 0;
+ r = netdev_is_ready_to_create(req->netdev, req->link);
+ if (r <= 0)
+ return r;
r = netdev_join(req->netdev, req->link, req->netlink_handler);
if (r < 0)
@@ -731,13 +739,13 @@ int link_request_stacked_netdev(Link *link, NetDev *netdev) {
if (netdev_get_create_type(netdev) == NETDEV_CREATE_STACKED) {
link->stacked_netdevs_created = false;
- r = link_queue_request(link, REQUEST_TYPE_STACKED_NETDEV, netdev, false,
+ r = link_queue_request(link, REQUEST_TYPE_NETDEV_STACKED, netdev_ref(netdev), true,
&link->create_stacked_netdev_messages,
link_create_stacked_netdev_handler,
NULL);
} else {
link->stacked_netdevs_after_configured_created = false;
- r = link_queue_request(link, REQUEST_TYPE_STACKED_NETDEV, netdev, false,
+ r = link_queue_request(link, REQUEST_TYPE_NETDEV_STACKED, netdev_ref(netdev), true,
&link->create_stacked_netdev_after_configured_messages,
link_create_stacked_netdev_after_configured_handler,
NULL);
@@ -750,6 +758,44 @@ int link_request_stacked_netdev(Link *link, NetDev *netdev) {
return 0;
}
+int request_process_independent_netdev(Request *req) {
+ int r;
+
+ assert(req);
+ assert(req->type == REQUEST_TYPE_NETDEV_INDEPENDENT);
+ assert(req->netdev);
+
+ r = netdev_is_ready_to_create(req->netdev, NULL);
+ if (r <= 0)
+ return r;
+
+ r = netdev_create(req->netdev, NULL, NULL);
+ if (r < 0)
+ return r;
+
+ return 1;
+}
+
+static int netdev_request(NetDev *netdev) {
+ int r;
+
+ assert(netdev);
+
+ if (!IN_SET(netdev_get_create_type(netdev), NETDEV_CREATE_MASTER, NETDEV_CREATE_INDEPENDENT) &&
+ !netdev_is_stacked_and_independent(netdev))
+ return 0;
+
+ r = netdev_is_ready_to_create(netdev, NULL);
+ if (r < 0)
+ return r;
+ if (r > 0)
+ /* If the netdev has no dependency, then create it now. */
+ return netdev_create(netdev, NULL, NULL);
+
+ /* Otherwise, wait for the dependencies being resolved. */
+ return netdev_queue_request(netdev, NULL);
+}
+
int netdev_load_one(Manager *manager, const char *filename) {
_cleanup_(netdev_unrefp) NetDev *netdev_raw = NULL, *netdev = NULL;
const char *dropin_dirname;
@@ -860,20 +906,11 @@ int netdev_load_one(Manager *manager, const char *filename) {
log_netdev_debug(netdev, "loaded %s", netdev_kind_to_string(netdev->kind));
- if (IN_SET(netdev_get_create_type(netdev), NETDEV_CREATE_MASTER, NETDEV_CREATE_INDEPENDENT)) {
- r = netdev_create(netdev, NULL, NULL);
- if (r < 0)
- return r;
- }
-
- if (netdev_is_stacked_and_independent(netdev)) {
- r = netdev_create(netdev, NULL, NULL);
- if (r < 0)
- return r;
- }
-
- netdev = NULL;
+ r = netdev_request(netdev);
+ if (r < 0)
+ return log_netdev_warning_errno(netdev, r, "Failed to request to create: %m");
+ TAKE_PTR(netdev);
return 0;
}
diff --git a/src/network/netdev/netdev.h b/src/network/netdev/netdev.h
index b631e8e23f..be26d1969d 100644
--- a/src/network/netdev/netdev.h
+++ b/src/network/netdev/netdev.h
@@ -43,6 +43,7 @@
"-VXLAN\0" \
"-WireGuard\0" \
"-WireGuardPeer\0" \
+ "-VirtualWLAN\0" \
"-Xfrm\0"
typedef enum NetDevKind {
@@ -82,6 +83,7 @@ typedef enum NetDevKind {
NETDEV_KIND_VXCAN,
NETDEV_KIND_VXLAN,
NETDEV_KIND_WIREGUARD,
+ NETDEV_KIND_WLAN,
NETDEV_KIND_XFRM,
_NETDEV_KIND_MAX,
_NETDEV_KIND_TUNNEL, /* Used by config_parse_stacked_netdev() */
@@ -173,6 +175,9 @@ typedef struct NetDevVTable {
/* Generate MAC address when MACAddress= is not specified. */
bool generate_mac;
+
+ /* When assigning ifindex to the netdev, skip to check if the netdev kind matches. */
+ bool skip_netdev_kind_check;
} NetDevVTable;
extern const NetDevVTable * const netdev_vtable[_NETDEV_KIND_MAX];
@@ -210,6 +215,7 @@ int netdev_generate_hw_addr(NetDev *netdev, Link *link, const char *name,
const struct hw_addr_data *hw_addr, struct hw_addr_data *ret);
int netdev_join(NetDev *netdev, Link *link, link_netlink_message_handler_t cb);
+int request_process_independent_netdev(Request *req);
int request_process_stacked_netdev(Request *req);
int link_request_stacked_netdev(Link *link, NetDev *netdev);
diff --git a/src/network/netdev/tunnel.c b/src/network/netdev/tunnel.c
index bb6e4f7586..3ba4484b6b 100644
--- a/src/network/netdev/tunnel.c
+++ b/src/network/netdev/tunnel.c
@@ -657,12 +657,14 @@ static int netdev_tunnel_is_ready_to_create(NetDev *netdev, Link *link) {
Tunnel *t;
assert(netdev);
- assert(link);
t = TUNNEL(netdev);
assert(t);
+ if (t->independent)
+ return true;
+
return tunnel_get_local_address(t, link, NULL) >= 0;
}
diff --git a/src/network/netdev/vxlan.c b/src/network/netdev/vxlan.c
index bdedf3e7a9..d93084c2d4 100644
--- a/src/network/netdev/vxlan.c
+++ b/src/network/netdev/vxlan.c
@@ -427,12 +427,14 @@ static int netdev_vxlan_is_ready_to_create(NetDev *netdev, Link *link) {
VxLan *v;
assert(netdev);
- assert(link);
v = VXLAN(netdev);
assert(v);
+ if (v->independent)
+ return true;
+
return vxlan_get_local_address(v, link, NULL, NULL) >= 0;
}
diff --git a/src/network/netdev/wlan.c b/src/network/netdev/wlan.c
new file mode 100644
index 0000000000..17fff4d482
--- /dev/null
+++ b/src/network/netdev/wlan.c
@@ -0,0 +1,260 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include
+
+#include "sd-netlink.h"
+
+#include "netlink-util.h"
+#include "networkd-manager.h"
+#include "networkd-wiphy.h"
+#include "parse-util.h"
+#include "wifi-util.h"
+#include "wlan.h"
+
+static void wlan_done(NetDev *netdev) {
+ WLan *w;
+
+ assert(netdev);
+
+ w = WLAN(netdev);
+
+ assert(w);
+
+ w->wiphy_name = mfree(w->wiphy_name);
+}
+
+static void wlan_init(NetDev *netdev) {
+ WLan *w;
+
+ assert(netdev);
+
+ w = WLAN(netdev);
+
+ assert(w);
+
+ w->wiphy_index = UINT32_MAX;
+ w->wds = -1;
+}
+
+static int wlan_get_wiphy(NetDev *netdev, Wiphy **ret) {
+ WLan *w;
+
+ assert(netdev);
+
+ w = WLAN(netdev);
+
+ assert(w);
+
+ if (w->wiphy_name)
+ return wiphy_get_by_name(netdev->manager, w->wiphy_name, ret);
+
+ return wiphy_get_by_index(netdev->manager, w->wiphy_index, ret);
+}
+
+static int wlan_is_ready_to_create(NetDev *netdev, Link *link) {
+ return wlan_get_wiphy(netdev, NULL) >= 0;
+}
+
+static int wlan_fill_message(NetDev *netdev, sd_netlink_message *m) {
+ Wiphy *wiphy;
+ WLan *w;
+ int r;
+
+ assert(netdev);
+ assert(m);
+
+ w = WLAN(netdev);
+
+ assert(w);
+
+ r = wlan_get_wiphy(netdev, &wiphy);
+ if (r < 0)
+ return r;
+
+ r = sd_netlink_message_append_u32(m, NL80211_ATTR_WIPHY, wiphy->index);
+ if (r < 0)
+ return r;
+
+ r = sd_netlink_message_append_string(m, NL80211_ATTR_IFNAME, netdev->ifname);
+ if (r < 0)
+ return r;
+
+ r = sd_netlink_message_append_u32(m, NL80211_ATTR_IFTYPE, w->iftype);
+ if (r < 0)
+ return r;
+
+ if (!hw_addr_is_null(&netdev->hw_addr) && netdev->hw_addr.length == ETH_ALEN) {
+ r = sd_netlink_message_append_ether_addr(m, NL80211_ATTR_MAC, &netdev->hw_addr.ether);
+ if (r < 0)
+ return r;
+ }
+
+ if (w->wds >= 0) {
+ r = sd_netlink_message_append_u8(m, NL80211_ATTR_4ADDR, w->wds);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+static int wlan_create_handler(sd_netlink *genl, sd_netlink_message *m, NetDev *netdev) {
+ int r;
+
+ assert(netdev);
+ assert(netdev->state != _NETDEV_STATE_INVALID);
+
+ r = sd_netlink_message_get_errno(m);
+ if (IN_SET(r, -EEXIST, -ENFILE))
+ /* Unlike the other netdevs, the kernel may return -ENFILE. See dev_alloc_name(). */
+ log_netdev_info(netdev, "WLAN interface exists, using existing without changing its parameters.");
+ else if (r < 0) {
+ log_netdev_warning_errno(netdev, r, "WLAN interface could not be created: %m");
+ netdev_enter_failed(netdev);
+
+ return 1;
+ }
+
+ log_netdev_debug(netdev, "WLAN interface is created.");
+ return 1;
+}
+
+static int wlan_create(NetDev *netdev) {
+ _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL;
+ int r;
+
+ assert(netdev);
+ assert(netdev->manager);
+ assert(netdev->manager->genl);
+
+ r = sd_genl_message_new(netdev->manager->genl, NL80211_GENL_NAME, NL80211_CMD_NEW_INTERFACE, &m);
+ if (r < 0)
+ return log_netdev_warning_errno(netdev, r, "Failed to allocate netlink message: %m");
+
+ r = wlan_fill_message(netdev, m);
+ if (r < 0)
+ return log_netdev_warning_errno(netdev, r, "Failed to fill netlink message: %m");
+
+ r = netlink_call_async(netdev->manager->genl, NULL, m, wlan_create_handler,
+ netdev_destroy_callback, netdev);
+ if (r < 0)
+ return log_netdev_warning_errno(netdev, r, "Failed to send netlink message: %m");
+
+ netdev_ref(netdev);
+ return 0;
+}
+
+static int wlan_verify(NetDev *netdev, const char *filename) {
+ WLan *w;
+
+ assert(netdev);
+ assert(filename);
+
+ w = WLAN(netdev);
+
+ assert(w);
+
+ if (w->iftype == NL80211_IFTYPE_UNSPECIFIED)
+ return log_netdev_warning_errno(netdev, SYNTHETIC_ERRNO(EINVAL),
+ "%s: WLAN interface type is not specified, ignoring.",
+ filename);
+
+ if (w->wiphy_index == UINT32_MAX && isempty(w->wiphy_name))
+ return log_netdev_warning_errno(netdev, SYNTHETIC_ERRNO(EINVAL),
+ "%s: physical WLAN device is not specified, ignoring.",
+ filename);
+
+ return 0;
+}
+
+int config_parse_wiphy(
+ const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ WLan *w = userdata;
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(userdata);
+
+ if (isempty(rvalue)) {
+ w->wiphy_name = mfree(w->wiphy_name);
+ w->wiphy_index = UINT32_MAX;
+ return 0;
+ }
+
+ r = safe_atou32(rvalue, &w->wiphy_index);
+ if (r >= 0) {
+ w->wiphy_name = mfree(w->wiphy_name);
+ return 0;
+ }
+
+ r = free_and_strdup_warn(&w->wiphy_name, rvalue);
+ if (r < 0)
+ return r;
+
+ w->wiphy_index = UINT32_MAX;
+ return 0;
+}
+
+int config_parse_wlan_iftype(
+ const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ enum nl80211_iftype t, *iftype = data;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ if (isempty(rvalue)) {
+ *iftype = NL80211_IFTYPE_UNSPECIFIED;
+ return 0;
+ }
+
+ t = nl80211_iftype_from_string(rvalue);
+ /* We reuse the kernel provided enum which does not contain negative value. So, the cast
+ * below is mandatory. Otherwise, the check below always passes. */
+ if ((int) t < 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, t,
+ "Failed to parse wlan interface type, ignoring assignment: %s",
+ rvalue);
+ return 0;
+ }
+
+ *iftype = t;
+ return 0;
+}
+
+const NetDevVTable wlan_vtable = {
+ .object_size = sizeof(WLan),
+ .init = wlan_init,
+ .done = wlan_done,
+ .sections = NETDEV_COMMON_SECTIONS "VirtualWLAN\0",
+ .is_ready_to_create = wlan_is_ready_to_create,
+ .create = wlan_create,
+ .create_type = NETDEV_CREATE_INDEPENDENT,
+ .config_verify = wlan_verify,
+ .iftype = ARPHRD_ETHER,
+ .generate_mac = true,
+ .skip_netdev_kind_check = true,
+};
diff --git a/src/network/netdev/wlan.h b/src/network/netdev/wlan.h
new file mode 100644
index 0000000000..bcc2dbcfd0
--- /dev/null
+++ b/src/network/netdev/wlan.h
@@ -0,0 +1,22 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include
+
+#include "conf-parser.h"
+#include "netdev.h"
+
+typedef struct WLan {
+ NetDev meta;
+
+ char *wiphy_name;
+ uint32_t wiphy_index;
+ enum nl80211_iftype iftype;
+ int wds; /* tristate */
+} WLan;
+
+DEFINE_NETDEV_CAST(WLAN, WLan);
+extern const NetDevVTable wlan_vtable;
+
+CONFIG_PARSER_PROTOTYPE(config_parse_wiphy);
+CONFIG_PARSER_PROTOTYPE(config_parse_wlan_iftype);
diff --git a/src/network/networkd-manager.c b/src/network/networkd-manager.c
index fab0b4094c..553aa2beb1 100644
--- a/src/network/networkd-manager.c
+++ b/src/network/networkd-manager.c
@@ -42,6 +42,7 @@
#include "networkd-speed-meter.h"
#include "networkd-state-file.h"
#include "networkd-wifi.h"
+#include "networkd-wiphy.h"
#include "ordered-set.h"
#include "path-lookup.h"
#include "path-util.h"
@@ -554,6 +555,9 @@ Manager* manager_free(Manager *m) {
m->netdevs = hashmap_free_with_destructor(m->netdevs, netdev_unref);
+ m->wiphy_by_name = hashmap_free(m->wiphy_by_name);
+ m->wiphy_by_index = hashmap_free_with_destructor(m->wiphy_by_index, wiphy_free);
+
ordered_set_free_free(m->address_pools);
hashmap_free(m->route_table_names_by_number);
@@ -793,6 +797,20 @@ static int manager_enumerate_nexthop(Manager *m) {
return manager_enumerate_internal(m, m->rtnl, req, manager_rtnl_process_nexthop);
}
+static int manager_enumerate_nl80211_wiphy(Manager *m) {
+ _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL;
+ int r;
+
+ assert(m);
+ assert(m->genl);
+
+ r = sd_genl_message_new(m->genl, NL80211_GENL_NAME, NL80211_CMD_GET_WIPHY, &req);
+ if (r < 0)
+ return r;
+
+ return manager_enumerate_internal(m, m->genl, req, manager_genl_process_nl80211_wiphy);
+}
+
static int manager_enumerate_nl80211_config(Manager *m) {
_cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL;
int r;
@@ -878,6 +896,12 @@ int manager_enumerate(Manager *m) {
else if (r < 0)
return log_error_errno(r, "Could not enumerate routing policy rules: %m");
+ r = manager_enumerate_nl80211_wiphy(m);
+ if (r == -EOPNOTSUPP)
+ log_debug_errno(r, "Could not enumerate wireless LAN phy, ignoring: %m");
+ else if (r < 0)
+ return log_error_errno(r, "Could not enumerate wireless LAN phy: %m");
+
r = manager_enumerate_nl80211_config(m);
if (r == -EOPNOTSUPP)
log_debug_errno(r, "Could not enumerate wireless LAN interfaces, ignoring: %m");
diff --git a/src/network/networkd-manager.h b/src/network/networkd-manager.h
index 86de529124..fab2cfaf11 100644
--- a/src/network/networkd-manager.h
+++ b/src/network/networkd-manager.h
@@ -84,6 +84,10 @@ struct Manager {
Hashmap *route_table_numbers_by_name;
Hashmap *route_table_names_by_number;
+ /* Wiphy */
+ Hashmap *wiphy_by_index;
+ Hashmap *wiphy_by_name;
+
/* For link speed meter */
bool use_speed_meter;
sd_event_source *speed_meter_event_source;
diff --git a/src/network/networkd-queue.c b/src/network/networkd-queue.c
index d673c2f155..5d06735774 100644
--- a/src/network/networkd-queue.c
+++ b/src/network/networkd-queue.c
@@ -46,6 +46,10 @@ static void request_free_object(RequestType type, void *object) {
case REQUEST_TYPE_NEIGHBOR:
neighbor_free(object);
break;
+ case REQUEST_TYPE_NETDEV_INDEPENDENT:
+ case REQUEST_TYPE_NETDEV_STACKED:
+ netdev_unref(object);
+ break;
case REQUEST_TYPE_NEXTHOP:
nexthop_free(object);
break;
@@ -58,7 +62,6 @@ static void request_free_object(RequestType type, void *object) {
routing_policy_rule_free(object);
break;
case REQUEST_TYPE_SET_LINK:
- case REQUEST_TYPE_STACKED_NETDEV:
break;
case REQUEST_TYPE_TRAFFIC_CONTROL:
traffic_control_free(object);
@@ -99,10 +102,12 @@ void request_drop(Request *req) {
static void request_hash_func(const Request *req, struct siphash *state) {
assert(req);
- assert(req->link);
assert(state);
- siphash24_compress(&req->link->ifindex, sizeof(req->link->ifindex), state);
+ siphash24_compress_boolean(req->link, state);
+ if (req->link)
+ siphash24_compress(&req->link->ifindex, sizeof(req->link->ifindex), state);
+
siphash24_compress(&req->type, sizeof(req->type), state);
switch (req->type) {
@@ -114,7 +119,8 @@ static void request_hash_func(const Request *req, struct siphash *state) {
case REQUEST_TYPE_ADDRESS_LABEL:
case REQUEST_TYPE_BRIDGE_FDB:
case REQUEST_TYPE_BRIDGE_MDB:
- case REQUEST_TYPE_STACKED_NETDEV:
+ case REQUEST_TYPE_NETDEV_INDEPENDENT:
+ case REQUEST_TYPE_NETDEV_STACKED:
/* TODO: Currently, these types do not have any specific hash and compare functions.
* Fortunately, all these objects are 'static', thus we can use the trivial functions. */
trivial_hash_func(req->object, state);
@@ -163,13 +169,17 @@ static int request_compare_func(const struct Request *a, const struct Request *b
assert(a);
assert(b);
- assert(a->link);
- assert(b->link);
- r = CMP(a->link->ifindex, b->link->ifindex);
+ r = CMP(!!a->link, !!b->link);
if (r != 0)
return r;
+ if (a->link) {
+ r = CMP(a->link->ifindex, b->link->ifindex);
+ if (r != 0)
+ return r;
+ }
+
r = CMP(a->type, b->type);
if (r != 0)
return r;
@@ -182,7 +192,8 @@ static int request_compare_func(const struct Request *a, const struct Request *b
case REQUEST_TYPE_ADDRESS_LABEL:
case REQUEST_TYPE_BRIDGE_FDB:
case REQUEST_TYPE_BRIDGE_MDB:
- case REQUEST_TYPE_STACKED_NETDEV:
+ case REQUEST_TYPE_NETDEV_INDEPENDENT:
+ case REQUEST_TYPE_NETDEV_STACKED:
return trivial_compare_func(a->object, b->object);
case REQUEST_TYPE_DHCP_SERVER:
case REQUEST_TYPE_DHCP4_CLIENT:
@@ -220,6 +231,48 @@ DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR(
request_compare_func,
request_free);
+int netdev_queue_request(
+ NetDev *netdev,
+ Request **ret) {
+
+ _cleanup_(request_freep) Request *req = NULL;
+ Request *existing;
+ int r;
+
+ assert(netdev);
+ assert(netdev->manager);
+
+ req = new(Request, 1);
+ if (!req)
+ return -ENOMEM;
+
+ *req = (Request) {
+ .netdev = netdev_ref(netdev),
+ .type = REQUEST_TYPE_NETDEV_INDEPENDENT,
+ .consume_object = true,
+ };
+
+ existing = ordered_set_get(netdev->manager->request_queue, req);
+ if (existing) {
+ /* To prevent from removing the existing request. */
+ req->netdev = netdev_unref(req->netdev);
+
+ if (ret)
+ *ret = existing;
+ return 0;
+ }
+
+ r = ordered_set_ensure_put(&netdev->manager->request_queue, &request_hash_ops, req);
+ if (r < 0)
+ return r;
+
+ if (ret)
+ *ret = req;
+
+ TAKE_PTR(req);
+ return 1;
+}
+
int link_queue_request(
Link *link,
RequestType type,
@@ -340,6 +393,12 @@ int manager_process_requests(sd_event_source *s, void *userdata) {
case REQUEST_TYPE_NEIGHBOR:
r = request_process_neighbor(req);
break;
+ case REQUEST_TYPE_NETDEV_INDEPENDENT:
+ r = request_process_independent_netdev(req);
+ break;
+ case REQUEST_TYPE_NETDEV_STACKED:
+ r = request_process_stacked_netdev(req);
+ break;
case REQUEST_TYPE_NEXTHOP:
r = request_process_nexthop(req);
break;
@@ -355,9 +414,6 @@ int manager_process_requests(sd_event_source *s, void *userdata) {
case REQUEST_TYPE_SET_LINK:
r = request_process_set_link(req);
break;
- case REQUEST_TYPE_STACKED_NETDEV:
- r = request_process_stacked_netdev(req);
- break;
case REQUEST_TYPE_TRAFFIC_CONTROL:
r = request_process_traffic_control(req);
break;
@@ -367,9 +423,10 @@ int manager_process_requests(sd_event_source *s, void *userdata) {
default:
return -EINVAL;
}
- if (r < 0)
- link_enter_failed(req->link);
- if (r > 0) {
+ if (r < 0) {
+ if (req->link)
+ link_enter_failed(req->link);
+ } else if (r > 0) {
ordered_set_remove(manager->request_queue, req);
request_free(req);
processed = true;
diff --git a/src/network/networkd-queue.h b/src/network/networkd-queue.h
index ac02b9d815..9b2c6baf44 100644
--- a/src/network/networkd-queue.h
+++ b/src/network/networkd-queue.h
@@ -28,12 +28,13 @@ typedef enum RequestType {
REQUEST_TYPE_IPV6_PROXY_NDP,
REQUEST_TYPE_NDISC,
REQUEST_TYPE_NEIGHBOR,
+ REQUEST_TYPE_NETDEV_INDEPENDENT,
+ REQUEST_TYPE_NETDEV_STACKED,
REQUEST_TYPE_NEXTHOP,
REQUEST_TYPE_RADV,
REQUEST_TYPE_ROUTE,
REQUEST_TYPE_ROUTING_POLICY_RULE,
REQUEST_TYPE_SET_LINK,
- REQUEST_TYPE_STACKED_NETDEV,
REQUEST_TYPE_TRAFFIC_CONTROL,
REQUEST_TYPE_UP_DOWN,
_REQUEST_TYPE_MAX,
@@ -66,6 +67,10 @@ typedef struct Request {
void request_drop(Request *req);
+int netdev_queue_request(
+ NetDev *netdev,
+ Request **ret);
+
int link_queue_request(
Link *link,
RequestType type,
diff --git a/src/network/networkd-wifi.c b/src/network/networkd-wifi.c
index 996e600492..13d6734a02 100644
--- a/src/network/networkd-wifi.c
+++ b/src/network/networkd-wifi.c
@@ -8,6 +8,7 @@
#include "networkd-link.h"
#include "networkd-manager.h"
#include "networkd-wifi.h"
+#include "networkd-wiphy.h"
#include "string-util.h"
#include "wifi-util.h"
@@ -72,6 +73,8 @@ int manager_genl_process_nl80211_config(sd_netlink *genl, sd_netlink_message *me
log_debug_errno(r, "nl80211: failed to determine genl message command, ignoring: %m");
return 0;
}
+ if (IN_SET(cmd, NL80211_CMD_NEW_WIPHY, NL80211_CMD_DEL_WIPHY))
+ return manager_genl_process_nl80211_wiphy(genl, message, manager);
if (!IN_SET(cmd, NL80211_CMD_SET_INTERFACE, NL80211_CMD_NEW_INTERFACE, NL80211_CMD_DEL_INTERFACE)) {
log_debug("nl80211: ignoring nl80211 %s(%u) message.",
strna(nl80211_cmd_to_string(cmd)), cmd);
diff --git a/src/network/networkd-wiphy.c b/src/network/networkd-wiphy.c
new file mode 100644
index 0000000000..861ebe6b69
--- /dev/null
+++ b/src/network/networkd-wiphy.c
@@ -0,0 +1,205 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include
+
+#include "device-util.h"
+#include "networkd-manager.h"
+#include "networkd-wiphy.h"
+#include "parse-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);
+ }
+
+ free(w->name);
+ return mfree(w);
+}
+
+static int wiphy_new(Manager *manager, uint32_t index, Wiphy **ret) {
+ _cleanup_(wiphy_freep) Wiphy *w = NULL;
+ int r;
+
+ assert(manager);
+
+ w = new(Wiphy, 1);
+ if (!w)
+ return -ENOMEM;
+
+ *w = (Wiphy) {
+ .index = index,
+ };
+
+ r = hashmap_ensure_put(&manager->wiphy_by_index, NULL, UINT32_TO_PTR(w->index), w);
+ if (r < 0)
+ return r;
+
+ w->manager = manager;
+
+ 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 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_ptr(w->name, name))
+ return 0;
+
+ if (w->name)
+ hashmap_remove_value(w->manager->wiphy_by_name, w->name, w);
+
+ r = free_and_strdup(&w->name, name);
+ if (r < 0)
+ return r;
+
+ return hashmap_ensure_put(&w->manager->wiphy_by_name, &string_hash_ops, w->name, w);
+}
+
+static int wiphy_update(Wiphy *w, sd_netlink_message *message) {
+ int r;
+
+ assert(w);
+ assert(message);
+
+ r = wiphy_update_name(w, message);
+ if (r < 0)
+ return log_wiphy_debug_errno(w, r, "Failed to update wiphy name: %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: {
+ bool is_new = !w;
+
+ if (!w) {
+ r = wiphy_new(manager, index, &w);
+ if (r < 0) {
+ log_warning_errno(r, "Failed to allocate wiphy, ignoring: %m");
+ return 0;
+ }
+ }
+
+ r = wiphy_update(w, message);
+ if (r < 0) {
+ log_wiphy_warning_errno(w, r, "Failed to update wiphy, ignoring: %m");
+ return 0;
+ }
+
+ log_wiphy_debug(w, "Received %s phy.", is_new ? "new" : "updated");
+ 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;
+}
diff --git a/src/network/networkd-wiphy.h b/src/network/networkd-wiphy.h
new file mode 100644
index 0000000000..072a7e5fdd
--- /dev/null
+++ b/src/network/networkd-wiphy.h
@@ -0,0 +1,51 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include
+
+#include "sd-device.h"
+
+#include "macro.h"
+
+typedef struct Manager Manager;
+
+typedef struct Wiphy {
+ Manager *manager;
+
+ uint32_t index;
+ char *name;
+} Wiphy;
+
+Wiphy *wiphy_free(Wiphy *w);
+DEFINE_TRIVIAL_CLEANUP_FUNC(Wiphy*, wiphy_free);
+
+int wiphy_get_by_index(Manager *manager, uint32_t index, Wiphy **ret);
+int wiphy_get_by_name(Manager *manager, const char *name, Wiphy **ret);
+
+int manager_genl_process_nl80211_wiphy(sd_netlink *genl, sd_netlink_message *message, Manager *manager);
+
+#define log_wiphy_full_errno_zerook(w, level, error, ...) \
+ ({ \
+ const Wiphy *_w = (w); \
+ log_interface_full_errno_zerook(_w ? _w->name : NULL, level, error, __VA_ARGS__); \
+ })
+
+#define log_wiphy_full_errno(w, level, error, ...) \
+ ({ \
+ int _error = (error); \
+ ASSERT_NON_ZERO(_error); \
+ log_wiphy_full_errno_zerook(w, level, _error, __VA_ARGS__); \
+ })
+
+#define log_wiphy_full(w, level, ...) (void) log_wiphy_full_errno_zerook(w, level, 0, __VA_ARGS__)
+
+#define log_wiphy_debug(w, ...) log_wiphy_full(w, LOG_DEBUG, __VA_ARGS__)
+#define log_wiphy_info(w, ...) log_wiphy_full(w, LOG_INFO, __VA_ARGS__)
+#define log_wiphy_notice(w, ...) log_wiphy_full(w, LOG_NOTICE, __VA_ARGS__)
+#define log_wiphy_warning(w, ...) log_wiphy_full(w, LOG_WARNING, __VA_ARGS__)
+#define log_wiphy_error(w, ...) log_wiphy_full(w, LOG_ERR, __VA_ARGS__)
+
+#define log_wiphy_debug_errno(w, error, ...) log_wiphy_full_errno(w, LOG_DEBUG, error, __VA_ARGS__)
+#define log_wiphy_info_errno(w, error, ...) log_wiphy_full_errno(w, LOG_INFO, error, __VA_ARGS__)
+#define log_wiphy_notice_errno(w, error, ...) log_wiphy_full_errno(w, LOG_NOTICE, error, __VA_ARGS__)
+#define log_wiphy_warning_errno(w, error, ...) log_wiphy_full_errno(w, LOG_WARNING, error, __VA_ARGS__)
+#define log_wiphy_error_errno(w, error, ...) log_wiphy_full_errno(w, LOG_ERR, error, __VA_ARGS__)
diff --git a/src/shared/wifi-util.c b/src/shared/wifi-util.c
index cf0f562550..d32bb5d704 100644
--- a/src/shared/wifi-util.c
+++ b/src/shared/wifi-util.c
@@ -153,7 +153,7 @@ static const char * const nl80211_iftype_table[NUM_NL80211_IFTYPES] = {
[NL80211_IFTYPE_NAN] = "nan",
};
-DEFINE_STRING_TABLE_LOOKUP_TO_STRING(nl80211_iftype, enum nl80211_iftype);
+DEFINE_STRING_TABLE_LOOKUP(nl80211_iftype, enum nl80211_iftype);
static const char * const nl80211_cmd_table[__NL80211_CMD_AFTER_LAST] = {
[NL80211_CMD_GET_WIPHY] = "get_wiphy",
diff --git a/src/shared/wifi-util.h b/src/shared/wifi-util.h
index 8afd1a4ec3..a762fbcd46 100644
--- a/src/shared/wifi-util.h
+++ b/src/shared/wifi-util.h
@@ -12,4 +12,5 @@ int wifi_get_interface(sd_netlink *genl, int ifindex, enum nl80211_iftype *ret_i
int wifi_get_station(sd_netlink *genl, int ifindex, struct ether_addr *ret_bssid);
const char *nl80211_iftype_to_string(enum nl80211_iftype iftype) _const_;
+enum nl80211_iftype nl80211_iftype_from_string(const char *s) _pure_;
const char *nl80211_cmd_to_string(int cmd) _const_;
diff --git a/test/fuzz/fuzz-netdev-parser/directives.netdev b/test/fuzz/fuzz-netdev-parser/directives.netdev
index 584c1c2136..1ba273232c 100644
--- a/test/fuzz/fuzz-netdev-parser/directives.netdev
+++ b/test/fuzz/fuzz-netdev-parser/directives.netdev
@@ -246,3 +246,7 @@ RoutingAlgorithm=
PartitionKey=
Mode=
IgnoreUserspaceMulticastGroups=
+[VirtualWLAN]
+PhysicalDevice=
+Type=
+WDS=