2

Is it possible to unambiguously identify that a given network interface, such as eth1, is actually a type veth network interface? Please note that in containers, their network interface names typically start with eth* instead of veth* and one cannot be sure that there's not a real eth in the set. Can this be detected through /sys/class/net?

I'm under the impression that the iflink element in /sys/class/net/... does not unambigously identify veth network interfaces, but instead is used in other situations as well. If there is no way to solve my question using the /sys/class/net/... filesystem, is there a socket call that can give me this information, preferrably usable in Python?

1 Answer 1

2

After even more research I've come to the conclusion that my goal isn't achievable using /sys/class/net/.... Luckily, it is achievable.

Mounting the Correctly Namespace'd /sys/class/net

Thanks to Danila Kiver's answer to "Switching into a network namespace does not change /sys/class/net?" all I need to do is to mount sysfs in some place in order to get the correctly (network) namespace'd view to sysfs and its class/net/ branch.

The following Python example scans for network namespaces, and then lists all network interfaces in a specific network namespace, marking each physical network interface with [PHY]. Please note that this script needs root/admin cap, especially due to the mount.

import psutil
import nsenter
from ctypes import c_char_p, c_ulong
import ctypes.util
import os
import tempfile

# https://stackoverflow.com/a/29156997
libc = ctypes.CDLL(ctypes.util.find_library('c'), use_errno=True)
libc.mount.argtypes = (c_char_p, c_char_p, c_char_p, c_ulong, c_char_p)
libc.umount.argtypes = (c_char_p, )

netns_index = dict()
for process in psutil.process_iter():
    netns_ref = '/proc/%d/ns/net' % process.pid
    try:
        netns_id = os.stat(netns_ref).st_ino
        if netns_id not in netns_index:
            netns_index[netns_id] = netns_ref
    except PermissionError:
        pass

with tempfile.TemporaryDirectory() as temp_mnt:
    for netns_id, netns_ref in netns_index.items():
        with nsenter.Namespace(netns_ref, 'net'):
            print('net:[%d]' % netns_id)
            if libc.mount('sysfs'.encode('ascii'),
                          temp_mnt.encode('ascii'),
                          'sysfs'.encode('ascii'),
                          0,
                          ''.encode('ascii')) >= 0:
                for nif_name in sorted(os.listdir('%s/class/net' % temp_mnt)):
                    nif_path = os.readlink('%s/class/net/%s' % (temp_mnt, nif_name))
                    phys_nif = not nif_path.startswith('../../devices/virtual/')
                    print('  %s %s' % (nif_name, '[PHY]' if phys_nif else ''))
                libc.umount(temp_mnt.encode('ascii'))

Workaround Without /sys/class/net

However, the NETLINK interface to the Linux kernel provides the required information, otherwise the ip link command wouldn't be able to tell the kind of interface.

The key here is the IFLA_LINKINFO attribute that is returned when asking the kernel for a list of network links (that is, network interfaces). Inside it is another attribute called IFLA_INFO_KIND, it is veth in case of an veth network interface, or bridge in case of a Linux Kernel bridge.

Please note that IFLA_LINKINFO is an optional attribute; for instance, the loopback, ethernet, and wifi network interfaces don't offer IFLA_LINKINFO.

This information can be easily gotten in Python using the famous pyroute2 netlink library. pyroute2 deals with all the nasty NETLINK stuff, install it easily via pip3. This example simply iterates over all network interfaces visible in the current network namespace, giving their names, interface index, and the IFLA_LINKINFO value, if present.

from pyroute2 import IPRoute

netw = IPRoute()
for link in netw.get_links():
    ifindex = link['index']
    ifname = link.get_attr('IFLA_IFNAME')
    linkinfo = link.get_attr('IFLA_LINKINFO')
    if linkinfo is not None:
        linktype = linkinfo.get_attr('IFLA_INFO_KIND')
    else:
        linktype = 'None'

    print('{0}: #{1} {2}'.format(ifname, ifindex, linktype))

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.