3
0
mirror of https://git.kernel.org/pub/scm/network/wireless/iwd.git synced 2024-11-16 17:09:24 +01:00
iwd/src/udev.c

286 lines
5.7 KiB
C

/*
*
* 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);