From dedf2d0054eb3150697b1540bf9733cba9e881fd Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Tue, 15 Feb 2022 02:09:21 +0900 Subject: [PATCH] network: add support to create wlan virtual interface Closes #18977. --- man/systemd.netdev.xml | 40 +++ src/network/meson.build | 2 + src/network/netdev/netdev-gperf.gperf | 4 + src/network/netdev/netdev.c | 3 + src/network/netdev/netdev.h | 2 + src/network/netdev/wlan.c | 260 ++++++++++++++++++ src/network/netdev/wlan.h | 22 ++ .../fuzz/fuzz-netdev-parser/directives.netdev | 4 + 8 files changed, 337 insertions(+) create mode 100644 src/network/netdev/wlan.c create mode 100644 src/network/netdev/wlan.h diff --git a/man/systemd.netdev.xml b/man/systemd.netdev.xml index 2207852ae30..1f94e3d599f 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 1b8aa80cfe2..ca48acae91c 100644 --- a/src/network/meson.build +++ b/src/network/meson.build @@ -53,6 +53,8 @@ sources = files(''' netdev/vxcan.h netdev/wireguard.c netdev/wireguard.h + netdev/wlan.c + netdev/wlan.h netdev/xfrm.c netdev/xfrm.h networkd-address-generation.c diff --git a/src/network/netdev/netdev-gperf.gperf b/src/network/netdev/netdev-gperf.gperf index 0b87e350870..2fec1da06bd 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 48b263eaa63..8f68a50297f 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", }; diff --git a/src/network/netdev/netdev.h b/src/network/netdev/netdev.h index 6382d4e620b..be26d1969dd 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() */ diff --git a/src/network/netdev/wlan.c b/src/network/netdev/wlan.c new file mode 100644 index 00000000000..17fff4d4826 --- /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 00000000000..bcc2dbcfd0c --- /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/test/fuzz/fuzz-netdev-parser/directives.netdev b/test/fuzz/fuzz-netdev-parser/directives.netdev index 584c1c21361..1ba273232c4 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=