@@ -265,6 +265,7 @@ src_iwd_SOURCES = src/main.c linux/nl80211.h src/iwd.h src/missing.h \
src/dpp-util.h src/dpp-util.c \
src/json.h src/json.c \
src/dpp.c \
+ src/udev.c \
$(eap_sources) \
$(builtin_sources)
new file mode 100644
@@ -0,0 +1,285 @@
+/*
+ *
+ * Wireless daemon for Linux
+ *
+ * Copyright (C) 2024 Intel Corporation. All rights reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <errno.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <linux/netlink.h>
+#include <linux/filter.h>
+#include <ell/ell.h>
+
+#include "src/module.h"
+
+static void receive_props(const void *buf, uint32_t len)
+{
+ const char *action = NULL;
+ const char *interface = NULL;
+ const char *ifindex = NULL;
+
+ while (len > 0) {
+ const char *s = buf;
+ size_t l = strlen(s);
+ const char *t;
+
+ if (l < 2)
+ break;
+
+ t = strchr(s, '=');
+ if (t) {
+ size_t p = t - s;
+
+ if (!strncmp(s, "ACTION", p))
+ action = t + 1;
+ else if (!strncmp(s, "INTERFACE", p))
+ interface = t + 1;
+ else if (!strncmp(s, "IFINDEX", p))
+ ifindex = t + 1;
+ }
+
+ buf += l + 1;
+ len -= l + 1;
+ }
+
+ if (action && !strcmp(action, "add"))
+ l_info("udev interface=%s ifindex=%s", interface, ifindex);
+}
+
+static struct l_io *udev_io;
+
+static int create_socket(uint32_t *pid)
+{
+ struct sockaddr_nl addr;
+ socklen_t addrlen = sizeof(addr);
+ int fd, pktinfo = 1;
+
+ fd = socket(PF_NETLINK, SOCK_RAW | SOCK_CLOEXEC | SOCK_NONBLOCK,
+ NETLINK_KOBJECT_UEVENT);
+ if (fd < 0)
+ return -1;
+
+ memset(&addr, 0, sizeof(addr));
+ addr.nl_family = AF_NETLINK;
+
+ if (bind(fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+ close(fd);
+ return -1;
+ }
+
+ if (getsockname(fd, (struct sockaddr *) &addr, &addrlen) < 0) {
+ close(fd);
+ return -1;
+ }
+
+ if (setsockopt(fd, SOL_NETLINK, NETLINK_PKTINFO,
+ &pktinfo, sizeof(pktinfo)) < 0) {
+ close(fd);
+ return -1;
+ }
+
+ if (pid)
+ *pid = addr.nl_pid;
+
+ return fd;
+}
+
+static struct sock_filter subsys_filter[] = {
+ { 0x20, 0, 0, 0x00000008 }, /* ldw #magic */
+ { 0x15, 0, 3, 0xfeedcafe }, /* jne #feedcafe, drop */
+ { 0x20, 0, 0, 0x00000018 }, /* ldw #subsys_hash */
+ { 0x15, 0, 1, 0xa74d3cc8 }, /* jne #net, drop */
+ { 0x06, 0, 0, 0xffffffff }, /* keep: ret #-1 */
+ { 0x06, 0, 0, 0000000000 }, /* drop: ret #0 */
+};
+
+static const struct sock_fprog subsys_fprog = { .len = 6,
+ .filter = subsys_filter };
+
+static bool attach_filter(struct l_io *io)
+{
+ int fd;
+
+ fd = l_io_get_fd(io);
+ if (fd < 0)
+ return false;
+
+ if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER,
+ &subsys_fprog, sizeof(subsys_fprog)) < 0)
+ return false;
+
+ return true;
+}
+
+static bool add_membership(struct l_io *io, uint32_t group)
+{
+ int fd, value = group;
+
+ fd = l_io_get_fd(io);
+ if (fd < 0)
+ return false;
+
+ if (setsockopt(fd, SOL_NETLINK, NETLINK_ADD_MEMBERSHIP,
+ &value, sizeof(value)) < 0)
+ return false;
+
+ return true;
+}
+
+static bool drop_membership(struct l_io *io, uint32_t group)
+{
+ int fd, value = group;
+
+ fd = l_io_get_fd(io);
+ if (fd < 0)
+ return false;
+
+ if (setsockopt(fd, SOL_NETLINK, NETLINK_DROP_MEMBERSHIP,
+ &value, sizeof(value)) < 0)
+ return false;
+
+ return true;
+}
+
+static const uint8_t LIBUDEV_PREFIX[8] = { "libudev" };
+static uint32_t LIBUDEV_MAGIC = 0xfeedcafe;
+
+struct udev_hdr {
+ char prefix[8];
+ uint32_t magic;
+ uint32_t hdr_size;
+ uint32_t props_off;
+ uint32_t props_len;
+ uint32_t flt_subsys_hash;
+ uint32_t flt_devtype_hash;
+ uint32_t flt_tag_bloom_hi;
+ uint32_t flt_tag_bloom_lo;
+};
+
+static void receive_msg(const void *buf, uint32_t len)
+{
+ const struct udev_hdr *hdr = buf;
+
+ if (len < sizeof(struct udev_hdr))
+ return;
+
+ if (memcmp(hdr->prefix, LIBUDEV_PREFIX, 8))
+ return;
+
+ if (L_BE32_TO_CPU(hdr->magic) != LIBUDEV_MAGIC)
+ return;
+
+ if (hdr->hdr_size > len)
+ return;
+
+ if (hdr->props_off + hdr->props_len != len)
+ return;
+
+ receive_props(buf + hdr->props_off, hdr->props_len);
+}
+
+enum {
+ GROUP_NONE,
+ GROUP_KERNEL,
+ GROUP_UDEV,
+};
+
+static bool can_read_data(struct l_io *io, void *user_data)
+{
+ struct cmsghdr *cmsg;
+ struct msghdr msg;
+ struct iovec iov;
+ unsigned char buffer[4096];
+ unsigned char control[32];
+ uint32_t group = GROUP_NONE;
+ ssize_t len;
+ int fd;
+
+ memset(buffer, 0, sizeof(buffer));
+ memset(control, 0, sizeof(control));
+
+ fd = l_io_get_fd(io);
+
+ iov.iov_base = buffer;
+ iov.iov_len = sizeof(buffer);
+
+ memset(&msg, 0, sizeof(msg));
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+ msg.msg_control = control;
+ msg.msg_controllen = sizeof(control);
+
+ len = recvmsg(fd, &msg, 0);
+ if (len < 0)
+ return false;
+
+ for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL;
+ cmsg = CMSG_NXTHDR(&msg, cmsg)) {
+ struct nl_pktinfo *pktinfo;
+
+ if (cmsg->cmsg_level != SOL_NETLINK)
+ continue;
+
+ if (cmsg->cmsg_type != NETLINK_PKTINFO)
+ continue;
+
+ pktinfo = (void *) CMSG_DATA(cmsg);
+ group = pktinfo->group;
+ }
+
+ if (group == GROUP_UDEV)
+ receive_msg(buffer, len);
+
+ return true;
+}
+
+static int udev_init(void)
+{
+ int fd;
+
+ l_debug("");
+
+ fd = create_socket(NULL);
+ if (fd < 0)
+ return -EIO;
+
+ udev_io = l_io_new(fd);
+ l_io_set_close_on_destroy(udev_io, true);
+ l_io_set_read_handler(udev_io, can_read_data, NULL, NULL);
+ attach_filter(udev_io);
+ add_membership(udev_io, GROUP_UDEV);
+
+ return 0;
+}
+
+static void udev_exit(void)
+{
+ l_debug("");
+
+ drop_membership(udev_io, GROUP_UDEV);
+ l_io_destroy(udev_io);
+}
+
+IWD_MODULE(udev, udev_init, udev_exit);
+IWD_MODULE_DEPENDS(udev, netdev);