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=