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: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 ...
$ ethtool -i ens8191 | head -1
driver: atlantic
$ podman exec
9fbbed215871 ip link show ens8191
2: ens8191: <BROADCAST,MULTICAST,UP,LOWER_UP> 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 <yamato@redhat.com>
*/
#include "lsfd.h"
+#include "pidfd-utils.h"
+
+#include <sys/ioctl.h> /* ioctl */
+#include <linux/if_tun.h> /* TUNGETDEVNETNS */
static struct list_head miscdevs;
static struct list_head ttydrvs;
*/
struct tundata {
const char *iff;
+ ino_t devnetns; /* 0 implies no value given. */
};
static bool cdev_tun_probe(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;
}
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);
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,
.get_name = cdev_tun_get_name,
.fill_column = cdev_tun_fill_column,
.handle_fdinfo = cdev_tun_handle_fdinfo,
+ .attach_xinfo = cdev_tun_attach_xinfo,
};
/*
inodes=_INOTIFY.INODES_
+
misc:tun:::
-iface=_TUN.IFACE_
+iface=_TUN.IFACE_[ devnetns=_TUN.DEVNETNS_]
+
NETLINK:::
protocol=_NETLINK.PROTOCOL_[ lport=_NETLINK.LPORT_[ group=_NETLINK.GROUPS_]]
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.
[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") },
COL_TIMERFD_CLOCKID,
COL_TIMERFD_INTERVAL,
COL_TIMERFD_REMAINING,
+ COL_TUN_DEVNETNS,
COL_TUN_IFACE,
COL_TYPE,
COL_UDP_LADDR,
--- /dev/null
+TUN.DEVNETNS: 0
--- /dev/null
+3 rw- CHR misc:tun
+ASSOC,MODE,TYPE,SOURCE: 0
+NAME: 0
+TUN.IFACE: 0
--- /dev/null
+TUN.DEVNETNS: 0
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
} > $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
}
cdev_tun_test
+cdev_tun_test "$TS_CMD_UNSHARE" --net
ts_finalize