interface naming schemes

created onAugust 21, 2025

there are several naming schemes in Linux:

  • the “traditional” or “classic” naming scheme of the nineties with iface names eth0, eth1, wlan0, wlan1, etc. Interfaces get their names assigned by the kernel on a first-come, first-serve policy as they are probed by the network interface drivers. With nowadays complex hardware, this way of naming is no longer predictable if more than one interface of a kind (ethernet, wlan, …) exists in the machine.
  • some custom schemes working towards alleviating the problem mentioned above. Programs like and (which uses udev) used config files like and assigned interface names based on the MAC address – which comes with another set of problems. These custom schemes seem to be no longer in use.
  • the predictable network interface devices names introduced in udev. Note that udev is developed as a part of systemd but can be used completely without systemd (at least at the time of writing).

The authoritative documentation on the persistent naming on freedesktop.org is marked as obsolete and refers to the systemd.net-naming-scheme man page. Unfortunately, I can’t wrap my head around that man page completely.

Luckily, the same page, at the end, refers to some source code of udev, . The header comment in this C source is promising. I suspected that this comment had been reduced over time and dug out an ancient revision with a more verbose header comment:

/* * Predictable network interface device names based on: * - firmware/bios-provided index numbers for on-board devices * - firmware-provided pci-express hotplug slot index number * - physical/geographical location of the hardware * - the interface's MAC address * * http://www.freedesktop.org/wiki/Software/systemd/PredictableNetworkInterfaceNames * * Two character prefixes based on the type of interface: * en — Ethernet * sl — serial line IP (slip) * wl — wlan * ww — wwan * * Type of names: * b<number> — BCMA bus core number * c<bus_id> — bus id of a grouped CCW or CCW device, * with all leading zeros stripped [s390] * o<index>[n<phys_port_name>|d<dev_port>] * — on-board device index number * s<slot>[f<function>][n<phys_port_name>|d<dev_port>] * — hotplug slot index number * x<MAC> — MAC address * [P<domain>]p<bus>s<slot>[f<function>][n<phys_port_name>|d<dev_port>] * — PCI geographical location * [P<domain>]p<bus>s<slot>[f<function>][u<port>][..][c<config>][i<interface>] * — USB port number chain * v<slot> - VIO slot number (IBM PowerVM) * a<vendor><model>i<instance> — Platform bus ACPI instance id * * * All multi-function PCI devices will carry the [f<function>] number in the * device name, including the function 0 device. * * When using PCI geography, The PCI domain is only prepended when it is not 0. * * For USB devices the full chain of port numbers of hubs is composed. If the * name gets longer than the maximum number of 15 characters, the name is not * exported. * The usual USB configuration == 1 and interface == 0 values are suppressed. * * PCI Ethernet card with firmware index "1": * ID_NET_NAME_ONBOARD=eno1 * ID_NET_NAME_ONBOARD_LABEL=Ethernet Port 1 * * PCI Ethernet card in hotplug slot with firmware index number: * /sys/devices/pci0000:00/0000:00:1c.3/0000:05:00.0/net/ens1 * ID_NET_NAME_MAC=enx000000000466 * ID_NET_NAME_PATH=enp5s0 * ID_NET_NAME_SLOT=ens1 * * PCI Ethernet multi-function card with 2 ports: * /sys/devices/pci0000:00/0000:00:1c.0/0000:02:00.0/net/enp2s0f0 * ID_NET_NAME_MAC=enx78e7d1ea46da * ID_NET_NAME_PATH=enp2s0f0 * /sys/devices/pci0000:00/0000:00:1c.0/0000:02:00.1/net/enp2s0f1 * ID_NET_NAME_MAC=enx78e7d1ea46dc * ID_NET_NAME_PATH=enp2s0f1 * * PCI wlan card: * /sys/devices/pci0000:00/0000:00:1c.1/0000:03:00.0/net/wlp3s0 * ID_NET_NAME_MAC=wlx0024d7e31130 * ID_NET_NAME_PATH=wlp3s0 * * USB built-in 3G modem: * /sys/devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.4/2-1.4:1.6/net/wwp0s29u1u4i6 * ID_NET_NAME_MAC=wwx028037ec0200 * ID_NET_NAME_PATH=wwp0s29u1u4i6 * * USB Android phone: * /sys/devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.2/2-1.2:1.0/net/enp0s29u1u2 * ID_NET_NAME_MAC=enxd626b3450fb5 * ID_NET_NAME_PATH=enp0s29u1u2 * * s390 grouped CCW interface: * /sys/devices/css0/0.0.0007/0.0.f5f0/group_device/net/encf5f0 * ID_NET_NAME_MAC=enx026d3c00000a * ID_NET_NAME_PATH=encf5f0 */

Still, I can’t figure out completely how the persistent naming works. Poking around in the code in the vicinity of I hit on the relevant code in (the revision current at the time of writing):

if (enable_name_policy() && config->name_policy) for (NamePolicy *policy = config->name_policy; *policy != _NAMEPOLICY_INVALID; policy++) { const char *new_name = NULL; switch (*policy) { case NAMEPOLICY_KERNEL: if (link->name_assign_type != NET_NAME_PREDICTABLE) continue; /* The kernel claims to have given a predictable name, keep it. */ log_link_debug(link, "Policy *%s*: keeping predictable kernel name", name_policy_to_string(*policy)); goto no_rename; case NAMEPOLICY_KEEP: if (!IN_SET(link->name_assign_type, NET_NAME_USER, NET_NAME_RENAMED)) continue; log_link_debug(link, "Policy *%s*: keeping existing userspace name", name_policy_to_string(*policy)); goto no_rename; case NAMEPOLICY_DATABASE: (void) sd_device_get_property_value(device, "ID_NET_NAME_FROM_DATABASE", &new_name); break; case NAMEPOLICY_ONBOARD: (void) sd_device_get_property_value(device, "ID_NET_NAME_ONBOARD", &new_name); break; case NAMEPOLICY_SLOT: (void) sd_device_get_property_value(device, "ID_NET_NAME_SLOT", &new_name); break; case NAMEPOLICY_PATH: (void) sd_device_get_property_value(device, "ID_NET_NAME_PATH", &new_name); break; case NAMEPOLICY_MAC: (void) sd_device_get_property_value(device, "ID_NET_NAME_MAC", &new_name); break; default: assert_not_reached(); } if (ifname_valid(new_name)) { log_link_debug(link, "Policy *%s* yields \"%s\".", name_policy_to_string(*policy), new_name); link->new_name = new_name; return 0; } } if (link->config->name) { log_link_debug(link, "Policies didn't yield a name, using specified Name=%s.", link->config->name); link->new_name = link->config->name; return 0; } log_link_debug(link, "Policies didn't yield a name and Name= is not given, not renaming."); no_rename: if (!naming_scheme_has(NAMING_USE_INTERFACE_PROPERTY)) return sd_device_get_sysname(device, &link->new_name); link->new_name = link->ifname; return 0; }

The code shows that the first match in the switch statement wins, with the following cases:

  • NAMEPOLICY_KERNEL
  • NAMEPOLICY_KEEP
  • ID_NET_NAME_FROM_DATABASE
  • ID_NET_NAME_ONBOARD
  • ID_NET_NAME_SLOT
  • ID_NET_NAME_PATH
  • ID_NET_NAME_MAC

With the device info can be retrieved from the udev database. I.e., I know that I have one ethernet controller in my laptop, so I can list its properties with :

root@idoru:/$ udevadm info -e | grep -A 20 -i ^P.*eth P: /devices/pci0000:00/0000:00:1c.6/0000:6d:00.0/net/eth0 E: DEVPATH=/devices/pci0000:00/0000:00:1c.6/0000:6d:00.0/net/eth0 E: ID_BUS=pci E: ID_MODEL_FROM_DATABASE=Killer E2500 Gigabit Ethernet Controller E: ID_MODEL_ID=0xe0b1 E: ID_NET_NAME_MAC=enx80fa5b64d3b6 E: ID_NET_NAME_PATH=enp109s0 E: ID_OUI_FROM_DATABASE=CLEVO CO. E: ID_PCI_CLASS_FROM_DATABASE=Network controller E: ID_PCI_SUBCLASS_FROM_DATABASE=Ethernet controller E: ID_VENDOR_FROM_DATABASE=Qualcomm Atheros E: ID_VENDOR_ID=0x1969 E: IFINDEX=2 E: INTERFACE=eth0 E: SUBSYSTEM=net E: USEC_INITIALIZED=4933553 E: net.ifnames=0

I haven’t checked the code of thoroughly, but I suspect that causes the switch statement to branch into , where it jumps out of the switch in , which is why my ethernet interface is assigned the interface name .

Without , would be the first hit, resulting in the interface name .

references

Debian wiki, NetworkInterfaceNames

freedesktop.org, Predictable Network Interface Names

freedesktop.org, systemd.net-naming-scheme man page

github systemd/systemd, udev-builtin-net_id.c, revision v239-13

github systemd/systemd, link_config.c

x