From: Masatake YAMATO Date: Sat, 29 Nov 2025 22:28:33 +0000 (+0900) Subject: lsfd: fill SOCK.NETNS even when sock_diag netlink can't report sockets X-Git-Tag: v2.43-devel~95^2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=1a99298310c7e5675d4ea45bdaf89816f8eb4d06;p=thirdparty%2Futil-linux.git lsfd: fill SOCK.NETNS even when sock_diag netlink can't report sockets 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 --- diff --git a/lsfd-cmd/lsfd.c b/lsfd-cmd/lsfd.c index 334efd328..4991ce1bf 100644 --- a/lsfd-cmd/lsfd.c +++ b/lsfd-cmd/lsfd.c @@ -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) { diff --git a/lsfd-cmd/lsfd.h b/lsfd-cmd/lsfd.h index 665cdad61..d1e382efc 100644 --- a/lsfd-cmd/lsfd.h +++ b/lsfd-cmd/lsfd.h @@ -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 diff --git a/lsfd-cmd/sock-xinfo.c b/lsfd-cmd/sock-xinfo.c index d31067009..b56b6694d 100644 --- a/lsfd-cmd/sock-xinfo.c +++ b/lsfd-cmd/sock-xinfo.c @@ -30,7 +30,6 @@ #include /* NETLINK_*, NLMSG_* */ #include /* RTA_*, struct rtattr, */ #include /* SOCK_DIAG_BY_FAMILY */ -#include /* SIOCGSKNS */ #include /* UNIX_PATH_MAX */ #include /* UNIX_DIAG_*, UDIAG_SHOW_*, struct unix_diag_req */ @@ -42,7 +41,6 @@ #include /* tfind, tsearch */ #include #include -#include #include /* 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) diff --git a/lsfd-cmd/sock.c b/lsfd-cmd/sock.c index 59824b7d0..6a1b3b994 100644 --- a/lsfd-cmd/sock.c +++ b/lsfd-cmd/sock.c @@ -21,6 +21,8 @@ #include #include +#include /* SIOCGSKNS */ +#include #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, diff --git a/lsfd-cmd/sock.h b/lsfd-cmd/sock.h index 641cf3abd..ce5862340 100644 --- a/lsfd-cmd/sock.h +++ b/lsfd-cmd/sock.h @@ -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 index 000000000..ff4e0a3dd --- /dev/null +++ b/tests/expected/lsfd/mkfds-tcp-bare @@ -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 index 000000000..e59e4fea5 --- /dev/null +++ b/tests/ts/lsfd/mkfds-tcp-bare @@ -0,0 +1,66 @@ +#!/bin/bash +# +# Copyright (C) 2026 Masatake YAMATO +# +# 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