3

I want to correctly match TAP/TUN devices with the processes they are using, and I want to do this from outside of these processes using TAP/TUN devices (that is, I cannot issue any ioctl()s because I don't have access to a particular file descriptor inside its process itself).

I'm aware of the answers to How to find the connection between tap interface and its file descriptor?, that is: /proc/[PID]/fdinfo/[FD] has an additional iff: key-value pair that gives the name of the corresponding TAP/TUN network interface.

However, there's a problem with network namespaces, especially when TAP/TUN network interfaces get moved around network namespaces after their user-space processes have attached to them; for instance (here, tapclient is a simple variation of a34729t's tunclient.c, which accepts a tap network name and attaches to it):

$ sudo ip tuntap add tap123 mode tap
$ sudo tapclient tap123 &
$ sudo ip netns add fooz
$ sudo ip link set tap123 netns fooz
$ PID=$(ps faux | grep tapclient | grep -v -e sudo -e grep | awk '{print $2}')
$ sudo cat /proc/$PID/fdinfo/3

...which then gives: iff: tap123 -- but not the network namespace where the tap123 network interface is currently located in.

Of course, tap123 can be located by iterating over all network namespaces and looking for a matching network interface inside one of them. Unfortunately, there might be duplicate names, such as when creating another tap123 in the host namespace after we've moved the first one of this name into the fooz network namespace above:

$ sudo ip tuntap add tap123 mode tap
$ ip link show tap123
$ sudo ip netns exec fooz ip link show tap123

So we now have two tap123s in separate network namespaces, and fdinfo only gives us an ambiguous iff: tap123.

Unfortunately, looking at the /proc/$PID/ns/net network namespace of the tapclient won't help either, since that doesn't match the current network namespace of tap123 any longer:

$ findmnt -t nsfs | grep /run/netns/fooz
$ sudo readlink /proc/$PID/ns/net

For instance, this gives net:[4026532591] versus net:[4026531993].

It there a way to unambiguously match the tapclient process with the correct tap123 network interface instance it is attached to?

TheDiveO
  • 1,317
  • 1
  • 11
  • 26
  • Note that there's ip netns identify $PID that should give you the net namespace of $PID without you having to look at nsfs inode numbers (and ip netns pids fooz to go the other way), but they don't seem to find anything either. Sounds like a kernel bug. – TooTea Mar 07 '19 at 10:40
  • @TooTea ip netns identify $PID is doing nothing more than taking /proc/$PID/ns/net and then trying to match only against its own list of bind-mounted symbolic network namespace names. Unfortunately, that's most of the time rather pointless as few are using iproute2's bindmount positions. My example is just using ip netns ... to give an easily reproducable example to check the underlying mechanisms, but it's not a typical deployment situation. – TheDiveO Mar 07 '19 at 14:16

2 Answers2

1

You can try matching the tun interface by its hardware address rather than by name (you can obtain that with ioctl(SIOCGIFHWADDR) on the tun/tap file descriptor).

I don't think there's anything simpler, otherwise a recent change like this (which adds the possibility of also retrieving the net namespace of the interface via the tun fd) wouldn't have been needed and accepted.

  • The new SIOCGSKNSioctl() you link to looks very promising, I'll look into its code to see if it does the job I'm asking for. At first glance it looks like it does, as I noticed already that the TUN clone device fd has the network namespace associated, but this cannot be retrieved from userspace later. What I'm unsure is whether it really works under the example conditions laid out above. But an interesting link anyway, many thanks! – TheDiveO Mar 07 '19 at 14:22
  • The problem with SIOCGIFHWADDR and also SIOCGSKNS is rather that they need to be carried out on the original file descriptor because this fd's state is crucial here. Unfortunately, as https://unix.stackexchange.com/questions/74672/cross-process-dup-on-linux points out, it's not really safely possible to duplicate the fd from the TAP/TUN user process to be used by someone else in order to issue the SIOCGKSNS. This leads me to believe that it's necessary to ask the kernel devs to be so kind as to add another fdinfo key for giving correct network namespace information. – TheDiveO Mar 07 '19 at 15:12
  • I disagree that it's not "safely possible" to attach to a process with ptrace() and run any code in the context of that process (including sending fds with SCM_RIGHTS), but I don't have any nice and fast code to show right now. Until they add that info to fdinfo, and If using gdb is an option, you can make a list of tap interfaces, and call the ioctl via gdb only when necessary (when there really are duplicate names, etc). –  Mar 07 '19 at 20:17
  • Agreed on disagree: "safely" is misleading and I meant it to be along your "nice and fast code", deployable in production. I'm under the impression that SIOCGSKNS is strange as it returns the network namespace which was originally active when /dev/net/tun was opened. When moving the TAP/TUN later as in my example, the original network namespace would be returned, but the network interface isn't there anymore. The network namespace handling in tun.c looks slightly odd to me in places, especially when the stored original netns is passed but then ignored... – TheDiveO Mar 07 '19 at 22:08
  • "SIOCGSKNS ... returns the network namespace which was originally active when /dev/net/tun was opened." that is unfortunately true ;-( wrong call, then. –  Mar 07 '19 at 23:30
  • with the ability to duplicate fds from one process to another, the socket option becomes again more useful. – TheDiveO Apr 14 '23 at 20:37
0

Unfortunately, while user313992's hint about SIOCGSKNS is extremely useful for sockets, the implementation of SIOCGSKNS for TAP/TUN file descriptors is ... strange: it returns an fd for the network namespace the TAP/TUN was initially created in, but not for the current network namespace of its netdev.

Looking around more in __tun_chr_ioctl where SIOCGSKNS is implemented, reveals a highly promising TUNGETDEVNETNS ioctl operation: this finally fetches and returns the network namespace of the TAP/TUN device.

The following unit test codes creates a TAP device in the initial network namespaces, creates a new network namespace, and then moves the TAP netdev into this new network namespace. The TUNGETDEVNETNS ioctl then correctly returns a fd referencing the new network namespace, where the TAP netdev has been moved already to.

package main

import ( "os" "runtime"

"github.com/thediveo/notwork/link"
"github.com/thediveo/notwork/netns"
"github.com/vishvananda/netlink"
"golang.org/x/sys/unix"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
. "github.com/thediveo/success"

)

const tapNamePrefix = "tap-"

// Ugly IOCTL stuff; copied from github.com/thediveo/lxkns/ops/ioctl.go const _IOC_NRBITS = 8 const _IOC_TYPEBITS = 8 const _IOC_SIZEBITS = 14

const _IOC_NRSHIFT = 0 const _IOC_TYPESHIFT = _IOC_NRSHIFT + _IOC_NRBITS const _IOC_SIZESHIFT = _IOC_TYPESHIFT + _IOC_TYPEBITS const _IOC_DIRSHIFT = _IOC_SIZESHIFT + _IOC_SIZEBITS

const _IOC_NONE = uint(0)

func _IOC(dir, ioctype, nr, size uint) uint { return (dir << _IOC_DIRSHIFT) | (ioctype << _IOC_TYPESHIFT) | (nr << _IOC_NRSHIFT) | (size << _IOC_SIZESHIFT) }

func _IO(ioctype, nr uint) uint { return _IOC(_IOC_NONE, ioctype, nr, 0) }

func getTapNetdevNetnsFd(fd int) (int, error) { return unix.IoctlRetInt(fd, _IO('T', 227)) }

var _ = Describe("TAP/TUN netns", func() {

It(&quot;finds namespace of TAP/TUN netdev&quot;, func() {
    runtime.LockOSThread()
    defer runtime.UnlockOSThread()

    By(&quot;creating a TAP netdev&quot;)
    tt := netlink.Tuntap{
        Mode:   netlink.TUNTAP_MODE_TAP,
        Queues: 1,
    }
    tap := link.NewTransient(&amp;tt, tapNamePrefix).(*netlink.Tuntap)
    Expect(tap.Fds).NotTo(BeEmpty())
    for _, fd := range tap.Fds {
        DeferCleanup(func() { fd.Close() })
    }

    By(&quot;creating a new transient network namespace&quot;)
    newnetnsfd := netns.NewTransient()

    By(&quot;moving the TAP netdev into the new network namespace&quot;)
    Expect(netlink.LinkSetNsFd(tap, newnetnsfd)).To(Succeed())
    Expect(netlink.LinkList()).NotTo(ContainElement(
        HaveField(&quot;Attrs().Name&quot;, tap.Name)))
    nlh := netns.NewNetlinkHandle(newnetnsfd)
    defer func() {
        Expect(nlh.LinkSetNsPid(tap, os.Getpid())).To(Succeed())
        nlh.Close()
    }()
    Expect(nlh.LinkList()).To(ContainElement(
        HaveField(&quot;Attrs().Name&quot;, tap.Name)))

    By(&quot;querying the network namespace of the TAP netdev&quot;)
    tapnetnsfd := Successful(getTapNetdevNetnsFd(int(tap.Fds[0].Fd())))
    defer unix.Close(tapnetnsfd)

    Expect(netns.Ino(tapnetnsfd)).NotTo(Equal(netns.CurrentIno()))
    Expect(netns.Ino(tapnetnsfd)).To(Equal(netns.Ino(newnetnsfd)))
})

})

TheDiveO
  • 1,317
  • 1
  • 11
  • 26