如何获取 /proc/[PID]/fdinfo/[FD] 中引用的 tap/tun 设备的 Linux 网络命名空间?

如何获取 /proc/[PID]/fdinfo/[FD] 中引用的 tap/tun 设备的 Linux 网络命名空间?

我想要将 TAP/TUN 设备与它们正在使用的进程正确匹配,并且我想从以下位置执行此操作外部这些进程使用 TAP/TUN 设备(也就是说,我无法发出任何ioctl()s,因为我无权访问其进程本身内部的特定文件描述符)。

我知道以下问题的答案如何找到tap接口与其文件描述符之间的联系?,即:/proc/[PID]/fdinfo/[FD]有一个额外的iff:键值对,给出相应的 TAP/TUN 网络接口的名称。

然而,网络命名空间存在一个问题,特别是当 TAP/TUN 网络接口在用户空间进程附加到网络命名空间后在网络命名空间中移动时;例如(这里tapclient是一个简单的变体a34729t 的tunclient.c,它接受分接网络名称并附加到它):

$ 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

...然后给出:iff: tap123-- 但不是网络tap123接口当前所在的网络名称空间。

当然,tap123可以通过迭代所有网络命名空间并在其中之一查找匹配的网络接口来定位。不幸的是,可能存在重复的名称,例如在tap123我们将该名称的第一个移动到fooz上面的网络命名空间后在主机命名空间中创建另一个名称时:

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

所以我们现在有 tap123s 在单独的网络命名空间中,并且fdinfo只给我们一个不明确的iff: tap123.

不幸的是,查看/proc/$PID/ns/netTapclient 的网络命名空间也无济于事,因为它与当前的网络命名空间不再匹配tap123

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

例如,这给出了net:[4026532591]vs net:[4026531993]

有没有一种方法可以明确地将进程与其所连接的tapclient正确网络接口实例相匹配?tap123

答案1

您可以尝试通过硬件地址而不是名称来匹配 tun 接口(您可以通过ioctl(SIOCGIFHWADDR)tun/tap 文件描述符获取该地址)。

我不认为有什么更简单的,否则最近的变化就像(这增加了通过 tun fd 检索接口的网络命名空间的可能性)不会被需要和接受。

答案2

不幸的是,虽然 user313992 的提示SIOCGSKNS非常对于套接字很有用SIOCGSKNS, TAP/TUN 文件描述符的实现是...奇怪的:它返回最初创建 TAP/TUN 的网络命名空间的 fd,但是不适用于当前其netdev的网络命名空间。

环顾更多__tun_chr_ioctl在哪里SIOCGSKNS实施,揭示了一个非常有前途的TUNGETDEVNETNSioctl 操作:最终获取并返回 TAP/TUN 设备的网络命名空间。

以下单元测试代码在初始网络命名空间中创建一个 TAP 设备,创建一个新的网络命名空间,然后将 TAP netdev 移动到这个新的网络命名空间中。然后, ioctlTUNGETDEVNETNS正确返回引用新网络命名空间的 fd,TAP netdev 已移至该命名空间。

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("finds namespace of TAP/TUN netdev", func() {
        runtime.LockOSThread()
        defer runtime.UnlockOSThread()

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

        By("creating a new transient network namespace")
        newnetnsfd := netns.NewTransient()

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

        By("querying the network namespace of the TAP netdev")
        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)))
    })

})

相关内容