]> git.ipfire.org Git - thirdparty/util-linux.git/commitdiff
lsfd: fill SOCK.NETNS even when sock_diag netlink can't report sockets
authorMasatake YAMATO <yamato@redhat.com>
Sat, 29 Nov 2025 22:28:33 +0000 (07:28 +0900)
committerMasatake YAMATO <yamato@redhat.com>
Wed, 4 Feb 2026 08:41:41 +0000 (17:41 +0900)
lsfd could not fill SOCK.NETNS when socket netns information was not
available via sock_diag netlink. Although TCP sockets support sock_diag,
unbound/unconnected TCP sockets are not reported there, so SOCK.NETNS
remained empty for them.

lsfd already obtained socket netns information via ioctl(SIOCGSKNS) in
load_fdsk_xinfo(), called from collect_file_symlink(). That information
was only used to enumerate network namespaces on the target system and
was not associated with socket objects. Use it to fill SOCK.NETNS.

Move the netns lookup to sock_class->inspect_target_fd. The new helper
get_netns_from_socket() retrieves the netns inode via ioctl(SIOCGSKNS),
and sock_inspect_target_fd() stores it in sock->netns_inode. Then
sock_fill_column() uses sock->netns_inode for SOCK.NETNS when present.

* lsfd-cmd/sock.h (struct sock::netns_inode): new member
* lsfd-cmd/lsfd.c (collect_file_symlink): stop calling load_fdsk_xinfo()
* lsfd-cmd/sock-xinfo.c (load_fdsk_xinfo_cb): remove
* lsfd-cmd/sock.c: add get_netns_from_socket(), sock_inspect_target_fd()
and wire inspect_target_fd in sock_class; use netns_inode in
sock_fill_column() for SOCK.NETNS
* tests/ts/lsfd/mkfds-tcp-bare: add test case

Signed-off-by: Masatake YAMATO <yamato@redhat.com>
lsfd-cmd/lsfd.c
lsfd-cmd/lsfd.h
lsfd-cmd/sock-xinfo.c
lsfd-cmd/sock.c
lsfd-cmd/sock.h
tests/expected/lsfd/mkfds-tcp-bare [new file with mode: 0644]
tests/ts/lsfd/mkfds-tcp-bare [new file with mode: 0755]

index 334efd3284db279a3c9302a10a99bd57df5e9f23..4991ce1bf3fcebd358963b12d462b779f3a26e58 100644 (file)
@@ -939,7 +939,6 @@ static struct file *collect_file_symlink(struct path_cxt *pc,
 
        else if (assoc >= 0) {
                /* file-descriptor based association */
-               bool is_socket = (sb.st_mode & S_IFMT) == S_IFSOCK;
                FILE *fdinfo;
 
                if (ul_path_stat(pc, &sb, AT_SYMLINK_NOFOLLOW, name) == 0)
@@ -948,8 +947,6 @@ static struct file *collect_file_symlink(struct path_cxt *pc,
                if (is_nsfs_dev(f->stat.st_dev))
                        load_sock_xinfo(pc, name, f->stat.st_ino);
 
-               if (is_socket)
-                       load_fdsk_xinfo(proc, assoc);
 
                fdinfo = ul_path_fopenf(pc, "r", "fdinfo/%d", assoc);
                if (fdinfo) {
index 665cdad612a74e2c3074fab8d6b7564ca3c81769..d1e382efcd5f2286680a21e60769b05c12e08032 100644 (file)
@@ -326,7 +326,6 @@ void add_nodev(unsigned long minor, const char *filesystem);
  */
 void load_sock_xinfo(struct path_cxt *pc, const char *name, ino_t netns);
 bool is_nsfs_dev(dev_t dev);
-void load_fdsk_xinfo(struct proc *proc, int fd);
 
 /*
  * POSIX Mqueue
index d31067009964be3cae94c18562aa59a35f949991..b56b6694d836499f0749dc8abeeb6222c96963d2 100644 (file)
@@ -30,7 +30,6 @@
 #include <linux/netlink.h>     /* NETLINK_*, NLMSG_* */
 #include <linux/rtnetlink.h>   /* RTA_*, struct rtattr,  */
 #include <linux/sock_diag.h>   /* SOCK_DIAG_BY_FAMILY */
-#include <linux/sockios.h>     /* SIOCGSKNS */
 #include <linux/un.h>          /* UNIX_PATH_MAX */
 #include <linux/unix_diag.h>   /* UNIX_DIAG_*, UDIAG_SHOW_*,
                                   struct unix_diag_req */
@@ -42,7 +41,6 @@
 #include <search.h>            /* tfind, tsearch */
 #include <stdint.h>
 #include <string.h>
-#include <sys/ioctl.h>
 #include <sys/socket.h>                /* SOCK_* */
 
 #include "sysfs.h"
@@ -231,37 +229,12 @@ void load_sock_xinfo(struct path_cxt *pc, const char *name, ino_t netns)
        }
 }
 
-static int load_fdsk_xinfo_cb(int sk, void *data __attribute__((__unused__)))
+void load_fdsk_xinfo(ino_t netns_ino, int netns_fd)
 {
-       int nsfd;
-       struct netns *nsobj;
-       struct stat sb;
-       int r = -1;
-
-       nsfd = ioctl(sk, SIOCGSKNS);
-       if (nsfd < 0)
-               return nsfd;
-
-       if (fstat(nsfd, &sb) < 0)
-               goto out_nsfd;
-
-       if (is_sock_xinfo_loaded(sb.st_ino))
-               goto out_nsfd;
-
-       r = 0;
-       nsobj = mark_sock_xinfo_loaded(sb.st_ino);
-       load_sock_xinfo_with_fd(nsfd, nsobj);
-
-out_nsfd:
-       close(nsfd);
-       return r;
-}
-
-void load_fdsk_xinfo(struct proc *proc, int fd)
-{
-       call_with_foreign_fd(proc->pid, fd,
-                            load_fdsk_xinfo_cb, NULL);
-
+       if (!is_sock_xinfo_loaded(netns_ino)) {
+               struct netns *nsobj = mark_sock_xinfo_loaded(netns_ino);
+               load_sock_xinfo_with_fd(netns_fd, nsobj);
+       }
 }
 
 void initialize_sock_xinfos(void)
index 59824b7d0c90f9712a2de4fa9502206acd6f02c9..6a1b3b99491d609482143474a97df3926ce687e2 100644 (file)
@@ -21,6 +21,8 @@
 
 #include <sys/types.h>
 #include <sys/xattr.h>
+#include <linux/sockios.h>     /* SIOCGSKNS */
+#include <sys/ioctl.h>
 
 #include "lsfd.h"
 #include "sock.h"
@@ -29,7 +31,9 @@ static void attach_sock_xinfo(struct file *file)
 {
        struct sock *sock = (struct sock *)file;
 
-       sock->xinfo = get_sock_xinfo(file->stat.st_ino);
+       if (!sock->xinfo)
+               sock->xinfo = get_sock_xinfo(file->stat.st_ino);
+
        if (sock->xinfo) {
                struct ipc *ipc = get_ipc(file);
                if (ipc)
@@ -90,11 +94,16 @@ static bool sock_fill_column(struct proc *proc __attribute__((__unused__)),
                }
                return false;
        case COL_SOCK_NETNS:
-               if (sock->xinfo) {
+               if (sock->xinfo && sock->xinfo->netns_inode != 0) {
                        xasprintf(&str, "%llu",
                                  (unsigned long long)sock->xinfo->netns_inode);
                        break;
                }
+               if (sock->netns_inode) {
+                       xasprintf(&str, "%llu",
+                                 (unsigned long long)sock->netns_inode);
+                       break;
+               }
                return false;
        case COL_SOCK_TYPE:
                if (sock->xinfo
@@ -135,6 +144,27 @@ static bool sock_fill_column(struct proc *proc __attribute__((__unused__)),
        return true;
 }
 
+
+static ino_t get_netns_from_socket(int sk)
+{
+       int nsfd;
+       struct stat sb;
+
+       nsfd = ioctl(sk, SIOCGSKNS);
+       if (nsfd < 0)
+               return 0;
+
+       if (fstat(nsfd, &sb) < 0) {
+               close(nsfd);
+               return 0;
+       }
+
+       load_fdsk_xinfo(sb.st_ino, nsfd);
+
+       close(nsfd);
+       return sb.st_ino;
+}
+
 static void init_sock_content(struct file *file)
 {
        int fd;
@@ -169,6 +199,30 @@ static void init_sock_content(struct file *file)
        init_endpoint(&sock->endpoint);
 }
 
+static bool sock_needs_target_fd(struct file *file)
+{
+       struct sock *sock = (struct sock *)file;
+
+       /* DB behind get_sock_xinfo() is not fulfilled enough yet.
+        * However, if we are lucky enough to find an xinfo in the DB,
+        * we can avoid calling sock_inspect_target_fd().
+        *
+        * Even if get_sock_xinfo() returns NULL in this timing,
+        * eventually we call it again attach_sock_xinfo.
+        * When calling attach_sock_xinfo, the DB is completely
+        * fulfilled.
+        */
+       sock->xinfo = get_sock_xinfo(file->stat.st_ino);
+       return !(sock->xinfo && sock->xinfo->netns_inode != 0);
+}
+
+static void sock_inspect_target_fd(struct file *file, int fd)
+{
+       struct sock *sock = (struct sock *)file;
+
+       sock->netns_inode = get_netns_from_socket(fd);
+}
+
 static void free_sock_content(struct file *file)
 {
        struct sock *sock = (struct sock *)file;
@@ -194,6 +248,8 @@ const struct file_class sock_class = {
        .fill_column = sock_fill_column,
        .attach_xinfo = attach_sock_xinfo,
        .initialize_content = init_sock_content,
+       .needs_target_fd = sock_needs_target_fd,
+       .inspect_target_fd = sock_inspect_target_fd,
        .free_content = free_sock_content,
        .initialize_class = initialize_sock_class,
        .finalize_class = finalize_sock_class,
index 641cf3abd05a4cadb2e28bb7d64d705d88b55e8d..ce58623406465a073c9e09d7232060d3089618c2 100644 (file)
@@ -44,6 +44,18 @@ struct sock {
        char *protoname;
        struct sock_xinfo *xinfo;
        struct ipc_endpoint endpoint;
+
+       /*
+        * There are two netns_inode fields reachable from struct sock:
+        *
+        * - sock->xinfo->netns_inode: filled via sock_diag netlink (when available)
+        * - sock->netns_inode: filled via ioctl(SIOCGSKNS)
+        *
+        * Use sock->netns_inode as a fallback when xinfo is unavailable or
+        * does not provide the netns inode.
+        */
+       ino_t netns_inode;
+
 };
 
 struct sock_xinfo_class {
@@ -70,5 +82,6 @@ void initialize_sock_xinfos(void);
 void finalize_sock_xinfos(void);
 
 struct sock_xinfo *get_sock_xinfo(ino_t inode);
+void load_fdsk_xinfo(ino_t netns_ino, int netns_fd);
 
 #endif /* UTIL_LINUX_LSFD_SOCK_H */
diff --git a/tests/expected/lsfd/mkfds-tcp-bare b/tests/expected/lsfd/mkfds-tcp-bare
new file mode 100644 (file)
index 0000000..ff4e0a3
--- /dev/null
@@ -0,0 +1,4 @@
+ioctl(fd, SIOCGSKNS): 0
+INODE: 0
+SOCK_NETNS: 0
+SOCK_NETNS: OK
diff --git a/tests/ts/lsfd/mkfds-tcp-bare b/tests/ts/lsfd/mkfds-tcp-bare
new file mode 100755 (executable)
index 0000000..e59e4fe
--- /dev/null
@@ -0,0 +1,66 @@
+#!/bin/bash
+#
+# Copyright (C) 2026 Masatake YAMATO <yamato@redhat.com>
+#
+# This file is part of util-linux.
+#
+# This file is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This file 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 General Public License for more details.
+#
+TS_TOPDIR="${0%/*}/../.."
+TS_DESC="SOCK.NETNS of an unbound tcp socket"
+
+. "$TS_TOPDIR"/functions.sh
+ts_init "$*"
+
+ts_skip_nonroot
+ts_skip_docker
+
+. "$TS_SELF/lsfd-functions.bash"
+ts_check_test_command "$TS_CMD_LSFD"
+ts_check_test_command "$TS_HELPER_MKFDS"
+
+ts_cd "$TS_OUTDIR"
+
+PID=
+INODE=
+SOCK_NETNS=
+FD=5
+
+"$TS_HELPER_MKFDS" --quiet --dont-pause netns "$FD"
+SIOCGSKNS=$?
+if [[ "$SIOCGSKNS" == "$TS_EXIT_NOTSUPP" ]]; then
+    ts_skip "ioctl(fd, SIOCGSKNS) is not available"
+elif [[ "$SIOCGSKNS" == "$EPERM" ]]; then
+    ts_skip "ioctl(fd, SIOCGSKNS) is not usable"
+else
+    echo "ioctl(fd, SIOCGSKNS): $SIOCGSKNS" > "$TS_OUTPUT"
+fi
+
+{
+    coproc MKFDS { "$TS_HELPER_MKFDS" tcp-bare $FD; }
+    if read -r -u "${MKFDS[0]}" PID; then
+       INODE=$(${TS_CMD_LSFD} -n --raw -o INODE -p "${PID}" -Q "ASSOC == 'net'")
+       echo INODE: $?
+       SOCK_NETNS=$(${TS_CMD_LSFD} -n --raw -o SOCK.NETNS -p "${PID}" -Q "FD == $FD")
+       echo SOCK_NETNS: $?
+       if [[ -z "$SOCK_NETNS" ]]; then
+           echo "SOCK_NETNS: failed (empty, inode: $INODE)"
+       elif [[ "$SOCK_NETNS" == "$INODE" ]]; then
+           echo "SOCK_NETNS: OK"
+       else
+           echo "SOCK_NETNS: failed (inode: $INODE, sock_netns: $SOCK_NETNS)"
+       fi
+       echo DONE >&"${MKFDS[1]}"
+    fi
+    wait "${MKFDS_PID}"
+} >> "$TS_OUTPUT" 2>&1
+
+ts_finalize