Unix & Linux
linux-kernel qemu tap
Updated Wed, 21 Sep 2022 10:57:55 GMT

How to find the connection between tap interface and its file descriptor?


I have a qemu VM started by an orchestration script, which creates transient tap interfaces. When I inspect the command line arguments of the qemu-system-x86_64 process, then I can see, that the process connects to already opened tap interface with the file descriptor 27:

-netdev tap,fd=27,id=hostnet1,vhost=on,vhostfd=28

According to ls -l /proc/<qemu-system-x86_64_PID>/fd/27 it points to /dev/net/tun.

Does it work somehow in a way that tap interface name (for example vnet99) is passed to /dev/net/tun with ioctl() and this returns the correct fd? Or in general, how can I find out which tap interface in my host machine has file descriptor 27?




Solution

The iff: entry which could have given an answer was added in kernel 3.14 with the commit tun: add device name(iff) field to proc fdinfo entry, so is not available on kernel 3.13 or before, eg with Ubuntu 14.04LTS.

In this case, while it's not possible to ask the kernel to give the information, it's still possible to ask the actual process to give this information, by tracing it with a debugger.

There's an ioctl available since Linux 2.6.27 to ask informations about a configured tuntap interface: TUNGETIFF. Its use is for a process inheriting an fd to be able to query the fd to receive interface's name and type (ifr_name and ifr_flags) or to know the tuntap device was not configured yet (EBADFD) and that it should do it.

So while it's possible using gdb, it's a bit tricky because if no development environment is available, a few needed parameter and values have to be known or adjusted (and might change in the future or with architectures). These informations and adjustments where needed here:

  • defining $malloc for 64bits systems to handle the correct size for the returned memory address: credit goes to this comment from SO.
  • $malloc(64): struct ifreq appears to be 40 bytes on 64bits, let's use 64 to stay safe.
  • 0x800454d2 == TUNGETIFF.
  • result ifr_name is at offset 0.

Here are a shell script preparing the way for each tuntap fd found to call gdb, along with gdb's own script:

tungetiff.sh:

#!/bin/sh
[ $# -gt 0 ] || exit 1
SCRIPTGDB="$1"; shift
for pid in "$@"; do
    for procfd in /proc/$pid/fd/*; do
        if [ "$(readlink $procfd)" = "/dev/net/tun" ]; then
            fd=$(basename $procfd)
            printf 'pid=%d fd=%d ifname=' $pid $fd
            gdb -batch-silent --pid=$pid -ex 'set $fd'=$fd -x "$SCRIPTGDB"
        fi
    done
done

tungetiff.gdb:

set $malloc=(void *(*)(long long)) malloc
p $malloc(64)
p ioctl($fd, 0x800454d2, $1)
set *((char *)($1+16))=0
set logging file /dev/stdout
set logging on
printf "%s\n",$1
set logging off
call free($1)
quit

Typical example of execution (will probably only work as root, even user libvirt-qemu doesn't appear to be able to ptrace qemu-system):

# ./tungetiff.sh tungetiff.gdb $(pgrep qemu-system-)
pid=22281 fd=26 ifname=vnet1
pid=22281 fd=30 ifname=vnet2
pid=27109 fd=26 ifname=vnet0