From: Masatake YAMATO Date: Sun, 23 Nov 2025 07:18:48 +0000 (+0900) Subject: lsfd: add TUN.DEVNETNS column X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=ff2650962f09b9abef33116b9b4ecd1da2feacae;p=thirdparty%2Futil-linux.git lsfd: add TUN.DEVNETNS column lsfd can show the name of the network device behind a file descriptor pointing to a tun/tap device. # lsfd -Q 'SOURCE == "misc:tun"' COMMAND PID USER ASSOC XMODE TYPE SOURCE MNTID INODE NAME qemu-system-x86 846384 qemu 35 rw---- CHR misc:tun 36 1145 iface=vnet21 pasta.avx2 1837933 yamato 8 rw---m CHR misc:tun 2143 1145 iface=ens8191 ... This feature helps users inspect target processes, containers, and/or VMs with tools such as tcpdump, wireshark, or ip-link. However, I found a case where the device name was not sufficient. pasta (https://passt.top/) provides networking for rootless containers. It creates a tap device whose name matches the name of a network device on the host: $ ip link show ens8191 5: ens8191: mtu 1500 ... $ ethtool -i ens8191 | head -1 driver: atlantic $ podman exec 9fbbed215871 ip link show ens8191 2: ens8191: mtu 65520 ... $ podman exec 9fbbed215871 ethtool -i ens8191 | head -1 driver: tun A name alone is not enough to identify a network device on the system. With this change, lsfd reports the network namespace to which the tun/tap device belongs: # lsfd -Q 'SOURCE == "misc:tun"' -oCOMMAND,PID,SOURCE,TUN.DEVNETNS,NAME COMMAND PID SOURCE TUN.DEVNETNS NAME qemu-system-x86 846384 misc:tun 4026531840 iface=vnet21 devnetns=4026531840 pasta.avx2 1837933 misc:tun 4026536354 iface=ens8191 devnetns=4026536354 ... This change relies on the TUNGETDEVNETNS ioctl added in: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=0c3e0e3bb623c3735b8c9ab8aa8332f944f83a9f Signed-off-by: Masatake YAMATO --- diff --git a/lsfd-cmd/cdev.c b/lsfd-cmd/cdev.c index fd20e38d1..f047ef1b4 100644 --- a/lsfd-cmd/cdev.c +++ b/lsfd-cmd/cdev.c @@ -20,6 +20,10 @@ */ #include "lsfd.h" +#include "pidfd-utils.h" + +#include /* ioctl */ +#include /* TUNGETDEVNETNS */ static struct list_head miscdevs; static struct list_head ttydrvs; @@ -371,6 +375,7 @@ static struct cdev_ops cdev_misc_ops = { */ struct tundata { const char *iff; + ino_t devnetns; /* 0 implies no value given. */ }; static bool cdev_tun_probe(struct cdev *cdev) @@ -406,7 +411,12 @@ static char * cdev_tun_get_name(struct cdev *cdev) if (tundata == NULL || tundata->iff == NULL) return NULL; - xasprintf(&str, "iface=%s", tundata->iff); + if (tundata->devnetns) + xasprintf(&str, "iface=%s devnetns=%llu", + tundata->iff, (unsigned long long)tundata->devnetns); + else + xasprintf(&str, "iface=%s", tundata->iff); + return str; } @@ -426,6 +436,12 @@ static bool cdev_tun_fill_column(struct proc *proc __attribute__((__unused__)), case COL_SOURCE: *str = xstrdup("misc:tun"); return true; + case COL_TUN_DEVNETNS: + if (tundata && tundata->devnetns) { + xasprintf(str, "%llu", (unsigned long long)tundata->devnetns); + return true; + } + break; case COL_TUN_IFACE: if (tundata && tundata->iff) { *str = xstrdup(tundata->iff); @@ -447,6 +463,64 @@ static int cdev_tun_handle_fdinfo(struct cdev *cdev, const char *key, const char return 0; } +#ifdef TUNGETDEVNETNS +static int call_with_foreign_fd(pid_t target_pid, int target_fd, + int (*fn)(int, void*), void *data) +{ + int pidfd, tfd, r; + + pidfd = pidfd_open(target_pid, 0); + if (pidfd < 0) + return pidfd; + + tfd = pidfd_getfd(pidfd, target_fd, 0); + if (tfd < 0) { + close(pidfd); + return tfd; + } + + r = fn(tfd, data); + + close(tfd); + close(pidfd); + return r; +} + +static int cdev_tun_get_devnetns(int tfd, void *data) +{ + + struct tundata *tundata = data; + int nsfd = ioctl(tfd, TUNGETDEVNETNS); + struct stat sb; + + if (nsfd < 0) + return -1; + + if (fstat(nsfd, &sb) == 0) + tundata->devnetns = sb.st_ino; + + close(nsfd); + + return 0; +} +#endif + +static void cdev_tun_attach_xinfo(struct cdev *cdev) +{ + struct tundata *tundata = cdev->cdev_data; + + if (tundata->iff == NULL) + return; + +#ifdef TUNGETDEVNETNS + { + struct file *file = &cdev->file; + call_with_foreign_fd(file->proc->pid, file->association, + cdev_tun_get_devnetns, tundata); + } +#endif +} + static struct cdev_ops cdev_tun_ops = { .parent = &cdev_misc_ops, .probe = cdev_tun_probe, @@ -454,6 +528,7 @@ static struct cdev_ops cdev_tun_ops = { .get_name = cdev_tun_get_name, .fill_column = cdev_tun_fill_column, .handle_fdinfo = cdev_tun_handle_fdinfo, + .attach_xinfo = cdev_tun_attach_xinfo, }; /* diff --git a/lsfd-cmd/lsfd.1.adoc b/lsfd-cmd/lsfd.1.adoc index 7c20c00de..195e54d7e 100644 --- a/lsfd-cmd/lsfd.1.adoc +++ b/lsfd-cmd/lsfd.1.adoc @@ -307,7 +307,7 @@ inotify::: inodes=_INOTIFY.INODES_ + misc:tun::: -iface=_TUN.IFACE_ +iface=_TUN.IFACE_[ devnetns=_TUN.DEVNETNS_] + NETLINK::: protocol=_NETLINK.PROTOCOL_[ lport=_NETLINK.LPORT_[ group=_NETLINK.GROUPS_]] @@ -525,6 +525,9 @@ Remaining time. PTMX.TTY-INDEX <``number``>:: TTY index of the counterpart. +TUN.DEVNETNS <``number``>:: +Inode identifying network namespace where the device belongs. + TUN.IFACE <``string``>:: Network interface behind the tun device. diff --git a/lsfd-cmd/lsfd.c b/lsfd-cmd/lsfd.c index 64b9a27b3..18d1c12a3 100644 --- a/lsfd-cmd/lsfd.c +++ b/lsfd-cmd/lsfd.c @@ -406,6 +406,9 @@ static const struct colinfo infos[] = { [COL_TIMERFD_REMAINING]= { "TIMERFD.REMAINING", 0, SCOLS_FL_RIGHT, SCOLS_JSON_FLOAT, N_("remaining time") }, + [COL_TUN_DEVNETNS] = { "TUN.DEVNETNS", + 0, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER, + N_("inode identifying network namespace where the device belongs") }, [COL_TUN_IFACE] = { "TUN.IFACE", 0, SCOLS_FL_RIGHT, SCOLS_JSON_STRING, N_("network interface behind the tun device") }, diff --git a/lsfd-cmd/lsfd.h b/lsfd-cmd/lsfd.h index 916ad2d76..ea9bf5e70 100644 --- a/lsfd-cmd/lsfd.h +++ b/lsfd-cmd/lsfd.h @@ -131,6 +131,7 @@ enum { COL_TIMERFD_CLOCKID, COL_TIMERFD_INTERVAL, COL_TIMERFD_REMAINING, + COL_TUN_DEVNETNS, COL_TUN_IFACE, COL_TYPE, COL_UDP_LADDR, diff --git a/tests/expected/lsfd/mkfds-cdev-tun b/tests/expected/lsfd/mkfds-cdev-tun-domestic similarity index 100% rename from tests/expected/lsfd/mkfds-cdev-tun rename to tests/expected/lsfd/mkfds-cdev-tun-domestic diff --git a/tests/expected/lsfd/mkfds-cdev-tun-domestic-devnetns b/tests/expected/lsfd/mkfds-cdev-tun-domestic-devnetns new file mode 100644 index 000000000..291bc7d53 --- /dev/null +++ b/tests/expected/lsfd/mkfds-cdev-tun-domestic-devnetns @@ -0,0 +1 @@ +TUN.DEVNETNS: 0 diff --git a/tests/expected/lsfd/mkfds-cdev-tun-foreign b/tests/expected/lsfd/mkfds-cdev-tun-foreign new file mode 100644 index 000000000..5217d7939 --- /dev/null +++ b/tests/expected/lsfd/mkfds-cdev-tun-foreign @@ -0,0 +1,4 @@ +3 rw- CHR misc:tun +ASSOC,MODE,TYPE,SOURCE: 0 +NAME: 0 +TUN.IFACE: 0 diff --git a/tests/expected/lsfd/mkfds-cdev-tun-foreign-devnetns b/tests/expected/lsfd/mkfds-cdev-tun-foreign-devnetns new file mode 100644 index 000000000..291bc7d53 --- /dev/null +++ b/tests/expected/lsfd/mkfds-cdev-tun-foreign-devnetns @@ -0,0 +1 @@ +TUN.DEVNETNS: 0 diff --git a/tests/ts/lsfd/mkfds-cdev-tun b/tests/ts/lsfd/mkfds-cdev-tun index 028b42001..1079167fb 100755 --- a/tests/ts/lsfd/mkfds-cdev-tun +++ b/tests/ts/lsfd/mkfds-cdev-tun @@ -25,40 +25,65 @@ ts_skip_nonroot ts_check_test_command "$TS_CMD_LSFD" ts_check_test_command "$TS_HELPER_MKFDS" +ts_check_test_command "$TS_CMD_UNSHARE" +ts_check_test_command "$TS_CMD_LSNS" ts_cd "$TS_OUTDIR" PID= FD=3 IFNAME= +readonly MYNETNS=$($TS_CMD_LSNS -n -t net -p $$ -oNS) + +if [[ -z "$MYNETNS" ]]; then + ts_skip "the current netns is unknown" +fi cdev_tun_test() { - local tname=domestic + local unshare=$1 + local tname + local devnetns_available + local netns + + if [[ -z "$unshare" ]]; then + tname=domestic + netns=$MYNETNS + else + tname=foreign + fi ts_init_subtest "$tname" { - coproc MKFDS { "$TS_HELPER_MKFDS" cdev-tun $FD ; } + coproc MKFDS { $unshare "$TS_HELPER_MKFDS" cdev-tun $FD ; } if read -u ${MKFDS[0]} PID IFNAME; then EXPR='(FD == '"$FD"')' ${TS_CMD_LSFD} -p "${PID}" -n -o ASSOC,MODE,TYPE,SOURCE -Q "${EXPR}" echo 'ASSOC,MODE,TYPE,SOURCE': $? + if [[ -z "$netns" ]]; then + netns=$($TS_CMD_LSNS -n -t net -p $PID -oNS) + fi + output=$(${TS_CMD_LSFD} -p "${PID}" -n --raw -o NAME -Q "${EXPR}") - if [[ "$output" == "iface=$IFNAME" ]]; then + if [[ "$output" =~ "iface=$IFNAME"(\\x20devnetns=$netns)? ]]; then echo 'NAME': $? + if [[ -n "${BASH_REMATCH[1]}" ]]; then + devnetns_available=yes + fi else echo 'NAME': $? echo "expected NAME: iface=$IFNAME" echo "output NAME: $output" + echo "netns: $netns" fi output=$(${TS_CMD_LSFD} -p "${PID}" -n --raw -o TUN.IFACE -Q "${EXPR}") if [[ "$output" == "$IFNAME" ]]; then echo 'TUN.IFACE': $? else - echo 'TUN.IFAEC': $? + echo 'TUN.IFACE': $? echo "expected TUN.IFACE: $IFNAME" echo "output TUN.IFACE: $output" fi @@ -66,6 +91,24 @@ cdev_tun_test() } > $TS_OUTPUT 2>&1 ts_finalize_subtest + ts_init_subtest "$tname"-devnetns + if [[ -z "${devnetns_available}" ]]; then + ts_skip_subtest "no method to access devnetns on this platform" + else + { + output=$(${TS_CMD_LSFD} -p "${PID}" -n --raw -o TUN.DEVNETNS -Q "${EXPR}") + if [[ "$output" == "$netns" ]]; then + echo 'TUN.DEVNETNS': $? + else + echo 'TUN.DEVNETNS': $? + echo "expected TUN.DEVNETNS: $netns" + echo "output TUN.DEVNETNS: $output" + fi + } > $TS_OUTPUT 2>&1 + fi + ts_finalize_subtest + + if [[ -n "$PID" ]]; then echo DONE >&"${MKFDS[1]}" fi @@ -74,5 +117,6 @@ cdev_tun_test() } cdev_tun_test +cdev_tun_test "$TS_CMD_UNSHARE" --net ts_finalize