]> git.ipfire.org Git - thirdparty/util-linux.git/commitdiff
lsfd: re-fill unix socket paths with sockdiag netlink interface
authorMasatake YAMATO <yamato@redhat.com>
Fri, 21 Jul 2023 19:02:38 +0000 (04:02 +0900)
committerMasatake YAMATO <yamato@redhat.com>
Mon, 24 Jul 2023 21:33:01 +0000 (06:33 +0900)
This commit is mostly based on Thomas Weißschuh's work,
https://github.com/t-8ch/util-linux/commit/06030390a5e6a16cc4b914bbf5fcedd3b6d83608.

Unlike the original work, this commit keeps /proc related code.

Unlike /proc interface, the sockdiag information source doesn't provide
enough information for filling struct unix_xinfo::st member. So this
commit uses /proc interface for filling the most of all unix_xinfo
members as before.

On the other hande, as discussed in
https://github.com/util-linux/util-linux/pull/2067, the /proc
interface in unreliable if a unix path name includes newline
characters. This commit uses the sockdiag interface to compensate
for the weakness of the /proc interface.

misc-utils/lsfd-sock-xinfo.c

index 0aa5c191ced5eaf81041ec56dca2f1a2af674131..65dc1ec46795b267bc5c94d2ac331e8da25b1c19 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * lsfd-sock-xinfo.c - read various information from files under /proc/net/
+ * lsfd-sock-xinfo.c - read various information from files under /proc/net/ and NETLINK_SOCK_DIAG
  *
  * Copyright (C) 2022 Red Hat, Inc. All rights reserved.
  * Written by Masatake YAMATO <yamato@redhat.com>
 #include <net/if.h>            /* if_nametoindex */
 #include <linux/if_ether.h>    /* ETH_P_* */
 #include <linux/net.h>         /* SS_* */
-#include <linux/netlink.h>     /* NETLINK_* */
+#include <linux/netlink.h>     /* NETLINK_*, NLMSG_* */
+#include <linux/rtnetlink.h>   /* RTA_*, struct rtattr,  */
+#include <linux/sock_diag.h>   /* SOCK_DIAG_BY_FAMILY */
 #include <linux/un.h>          /* UNIX_PATH_MAX */
+#include <linux/unix_diag.h>   /* UNIX_DIAG_*, UDIAG_SHOW_*,
+                                  struct unix_diag_req */
 #include <sched.h>             /* for setns(2) */
 #include <search.h>            /* tfind, tsearch */
+#include <stdalign.h>          /* alignas */
 #include <stdint.h>
 #include <string.h>
 #include <sys/socket.h>                /* SOCK_* */
@@ -54,6 +59,8 @@ static void load_xinfo_from_proc_raw6(ino_t netns_inode);
 static void load_xinfo_from_proc_netlink(ino_t netns_inode);
 static void load_xinfo_from_proc_packet(ino_t netns_inode);
 
+static void load_xinfo_from_diag_unix(int diag, ino_t netns_inode);
+
 static int self_netns_fd = -1;
 static struct stat self_netns_sb;
 
@@ -153,6 +160,7 @@ static struct netns *mark_sock_xinfo_loaded(ino_t ino)
 static void load_sock_xinfo_no_nsswitch(struct netns *nsobj)
 {
        ino_t netns = nsobj? nsobj->inode: 0;
+       int diagsd;
 
        load_xinfo_from_proc_unix(netns);
        load_xinfo_from_proc_tcp(netns);
@@ -168,6 +176,12 @@ static void load_sock_xinfo_no_nsswitch(struct netns *nsobj)
        load_xinfo_from_proc_netlink(netns);
        load_xinfo_from_proc_packet(netns);
 
+       diagsd = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_SOCK_DIAG);
+       if (diagsd >= 0) {
+               load_xinfo_from_diag_unix(diagsd, netns);
+               close(diagsd);
+       }
+
        if (nsobj)
                load_ifaces_from_getifaddrs(nsobj);
 }
@@ -316,6 +330,61 @@ static const char *sock_decode_type(uint16_t type)
        }
 }
 
+static void send_diag_request(int diagsd, void *req, size_t req_size,
+                             bool (*cb)(ino_t, size_t, void *),
+                             ino_t netns)
+{
+       struct sockaddr_nl nladdr = {
+               .nl_family = AF_NETLINK,
+       };
+
+       struct nlmsghdr nlh = {
+               .nlmsg_len = sizeof(nlh) + req_size,
+               .nlmsg_type = SOCK_DIAG_BY_FAMILY,
+               .nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP,
+       };
+
+       struct iovec iovecs[] = {
+               { &nlh, sizeof(nlh) },
+               { req, req_size },
+       };
+
+       const struct msghdr mhd = {
+               .msg_namelen = sizeof(nladdr),
+               .msg_name = &nladdr,
+               .msg_iovlen = ARRAY_SIZE(iovecs),
+               .msg_iov = iovecs,
+       };
+
+       alignas(void *) uint8_t buf[8192];
+
+       if (sendmsg(diagsd, &mhd, 0) < 0)
+               return;
+
+       for (;;) {
+               const struct nlmsghdr *h;
+               int r = recvfrom(diagsd, buf, sizeof(buf), 0, NULL, NULL);
+               if (r < 0)
+                       return;
+
+               h = (void *) buf;
+               if (!NLMSG_OK(h, (size_t)r))
+                       return;
+
+               for (; NLMSG_OK(h, (size_t)r); h = NLMSG_NEXT(h, r)) {
+                       if (h->nlmsg_type == NLMSG_DONE)
+                               return;
+                       if (h->nlmsg_type == NLMSG_ERROR)
+                               return;
+
+                       if (h->nlmsg_type == SOCK_DIAG_BY_FAMILY) {
+                               if (!cb(netns, h->nlmsg_len, NLMSG_DATA(h)))
+                                       return;
+                       }
+               }
+       }
+}
+
 /*
  * Protocol specific code
  */
@@ -492,6 +561,76 @@ static void load_xinfo_from_proc_unix(ino_t netns_inode)
        fclose(unix_fp);
 }
 
+/* The path name extracted from /proc/net/unix is unreliable; the line oriented interface cannot
+ * represent a file name including newlines. With unix_refill_name(), we patch the path
+ * member of unix_xinfos with information received via netlink diag interface. */
+static void unix_refill_name(struct sock_xinfo *xinfo, const char *name, size_t len)
+{
+       struct unix_xinfo *ux = (struct unix_xinfo *)xinfo;
+       size_t min_len;
+
+       if (len == 0)
+               return;
+
+       min_len = min(sizeof(ux->path) - 1, len);
+       memcpy(ux->path, name, min_len);
+       if (ux->path[0] == '\0') {
+               ux->path[0] = '@';
+       }
+       ux->path[min_len] = '\0';
+}
+
+static bool handle_diag_unix(ino_t netns __attribute__((__unused__)),
+                            size_t nlmsg_len, void *nlmsg_data)
+{
+       const struct unix_diag_msg *diag = nlmsg_data;
+       size_t rta_len;
+       ino_t inode;
+       struct sock_xinfo *xinfo;
+
+       if (diag->udiag_family != AF_UNIX)
+               return false;
+
+       if (nlmsg_len < NLMSG_LENGTH(sizeof(*diag)))
+               return false;
+
+       inode = (ino_t)diag->udiag_ino;
+       xinfo = get_sock_xinfo(inode);
+
+       if (xinfo == NULL)
+               /* The socket is found in the diag response
+                  but not in the proc fs. */
+               return true;
+
+       if (xinfo->class != &unix_xinfo_class)
+               return true;
+
+       rta_len = nlmsg_len - NLMSG_LENGTH(sizeof(*diag));
+       for (struct rtattr *attr = (struct rtattr *)(diag + 1);
+            RTA_OK(attr, rta_len);
+            attr = RTA_NEXT(attr, rta_len)) {
+               size_t len = RTA_PAYLOAD(attr);
+
+               switch (attr->rta_type) {
+               case UNIX_DIAG_NAME:
+                       unix_refill_name(xinfo, RTA_DATA(attr), len);
+                       break;
+               }
+       }
+       return true;
+}
+
+static void load_xinfo_from_diag_unix(int diagsd, ino_t netns)
+{
+       struct unix_diag_req udr = {
+               .sdiag_family = AF_UNIX,
+               .udiag_states = -1, /* set the all bits. */
+               .udiag_show = UDIAG_SHOW_NAME,
+       };
+
+       send_diag_request(diagsd, &udr, sizeof(udr), handle_diag_unix, netns);
+}
+
 /*
  * AF_INET
  */