I talked a bit about systemd’s network device name in my earlier post about systemd-networkd and bonding and I received some questions about how systemd rolls through the possible names of network devices to choose the final name. These predictable network device names threw me a curveball last summer when I couldn’t figure out how the names were constructed.
Let’s walk through this process.
What’s in a name?
Back in the systemd-networkd bonding post, I dug into a dual port Intel network card that showed up in a hotplug slot:
|
1 2 3 4 5 6 7 8 9 10 11 |
# udevadm info -e | grep -A 9 ^P.*eth0 P: /devices/pci0000:00/0000:00:03.2/0000:08:00.0/net/eth0 E: DEVPATH=/devices/pci0000:00/0000:00:03.2/0000:08:00.0/net/eth0 E: ID_BUS=pci E: ID_MODEL_FROM_DATABASE=82599ES 10-Gigabit SFI/SFP+ Network Connection (Ethernet OCP Server Adapter X520-2) E: ID_MODEL_ID=0x10fb E: ID_NET_DRIVER=ixgbe E: ID_NET_LINK_FILE=/usr/lib/systemd/network/99-default.link E: ID_NET_NAME_MAC=enxa0369f2cec90 E: ID_NET_NAME_PATH=enp8s0f0 E: ID_NET_NAME_SLOT=ens9f0 |
This udev database dump shows that it came up with a few different names for the network interface:
ID_NET_NAME_MAC=enxa0369f2cec90ID_NET_NAME_PATH=enp8s0f0ID_NET_NAME_SLOT=ens9f0
Where do these names come from? We can dig into systemd’s source code to figure out the origin of the names and which one is selected as the final choice.
Down the udev rabbit hole
Let’s take a look at src/udev/udev-builtin-net_id.c:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
/* * 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 * ccw<name> -- CCW bus group name * o<index>[d<dev_port>] -- on-board device index number * s<slot>[f<function>][d<dev_port>] -- hotplug slot index number * x<MAC> -- MAC address * [P<domain>]p<bus>s<slot>[f<function>][d<dev_port>] * -- PCI geographical location * [P<domain>]p<bus>s<slot>[f<function>][u<port>][..][c<config>][i<interface>] * -- USB port number chain |
So here’s where our names actually begin. Ethernet cards will always start with en, but they might be followed by a p (for PCI slots), a s (for hotplug PCI-E slots), and o (for onboard cards). Scroll down just a bit more for some examples starting at line 56.
Real-world examples
We already looked at the hotplug slot naming from Rackspace’s OnMetal servers. They show up as ens9f0 and ens9f1. That means they’re on a hotplug slot which happens to be slot 9. The function indexes are 0 and 1 (for both ports on the Intel 82599ES).
Linux firewall with a dual-port PCI card
Here’s an example of my Linux firewall at home. It’s a Dell Optiplex 3020 with an Intel I350-T2 (dual port):
|
1 2 3 4 5 6 7 8 9 10 11 12 |
# udevadm info -e | grep -A 10 ^P.*enp1s0f1 P: /devices/pci0000:00/0000:00:01.0/0000:01:00.1/net/enp1s0f1 E: DEVPATH=/devices/pci0000:00/0000:00:01.0/0000:01:00.1/net/enp1s0f1 E: ID_BUS=pci E: ID_MODEL_FROM_DATABASE=I350 Gigabit Network Connection (Ethernet Server Adapter I350-T2) E: ID_MODEL_ID=0x1521 E: ID_NET_DRIVER=igb E: ID_NET_LINK_FILE=/usr/lib/systemd/network/99-default.link E: ID_NET_NAME=enp1s0f1 E: ID_NET_NAME_MAC=enxa0369f6e5227 E: ID_NET_NAME_PATH=enp1s0f1 E: ID_OUI_FROM_DATABASE=Intel Corporate |
And the output from lspci:
|
1 2 3 |
# lspci -s 01:00 01:00.0 Ethernet controller: Intel Corporation I350 Gigabit Network Connection (rev 01) 01:00.1 Ethernet controller: Intel Corporation I350 Gigabit Network Connection (rev 01) |
This card happens to sit on PCI bus 1 (enp1), slot 0 (s0). Since it’s a dual-port card, it has two function indexes (f0 and f1). That leaves me with two predictable names: enp1s0f1 and enp1s0f0.
1U server with four ethernet ports
Let’s grab another example. Here’s a SuperMicro 1U X9SCA server with four onboard PCI ethernet cards:
|
1 2 3 4 5 6 7 8 9 10 11 12 |
# udevadm info -e | grep -A 10 ^P.*enp2s0 P: /devices/pci0000:00/0000:00:1c.4/0000:02:00.0/net/enp2s0 E: DEVPATH=/devices/pci0000:00/0000:00:1c.4/0000:02:00.0/net/enp2s0 E: ID_BUS=pci E: ID_MODEL_FROM_DATABASE=82574L Gigabit Network Connection E: ID_MODEL_ID=0x10d3 E: ID_NET_DRIVER=e1000e E: ID_NET_LINK_FILE=/usr/lib/systemd/network/99-default.link E: ID_NET_NAME=enp2s0 E: ID_NET_NAME_MAC=enx00259025963a E: ID_NET_NAME_PATH=enp2s0 E: ID_OUI_FROM_DATABASE=Super Micro Computer, Inc. |
And here’s all four ports in lspci:
|
1 2 3 4 5 |
# for i in `seq 2 5`; do lspci -s 0${i}:; done 02:00.0 Ethernet controller: Intel Corporation 82574L Gigabit Network Connection 03:00.0 Ethernet controller: Intel Corporation 82574L Gigabit Network Connection 04:00.0 Ethernet controller: Intel Corporation 82574L Gigabit Network Connection 05:00.0 Ethernet controller: Intel Corporation 82574L Gigabit Network Connection |
These are interesting because they’re not all on the same PCI bus. They sit on buses 2-5 in slot 0. There are no function indexes here, so they’re named enp2s0 through enp5s0. These aren’t true onboard cards, so they’re named based on their locations.
Storage server with onboard ethernet
Here’s an example of a server with a true inboard ethernet card:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
$ udevadm info -e | grep -A 11 ^P.*eno1 P: /devices/pci0000:00/0000:00:19.0/net/eno1 E: DEVPATH=/devices/pci0000:00/0000:00:19.0/net/eno1 E: ID_BUS=pci E: ID_MODEL_FROM_DATABASE=Ethernet Connection I217-V E: ID_MODEL_ID=0x153b E: ID_NET_DRIVER=e1000e E: ID_NET_LABEL_ONBOARD=en Onboard LAN E: ID_NET_LINK_FILE=/usr/lib/systemd/network/99-default.link E: ID_NET_NAME_MAC=enxe03f49b159c0 E: ID_NET_NAME_ONBOARD=eno1 E: ID_NET_NAME_PATH=enp0s25 E: ID_OUI_FROM_DATABASE=ASUSTek COMPUTER INC. |
And the lspci output:
|
1 2 |
$ lspci -s 00:19.0 00:19.0 Ethernet controller: Intel Corporation Ethernet Connection I217-V (rev 05) |
This card has a new name showing up in udev: ID_NET_NAME_ONBOARD. The systemd udev code has some special handling for onboard cards because they usually sit on the main bus. The naming can get a bit ugly because that 19 would need to be converted into hex for the name.
If systemd didn’t handle onboard cards differently, this card might be named something ugly like enp0s13 (since 19 in decimal becomes 13 in hex). That’s really confusing.
Picking the final name
As we’ve seen above, udev makes a big list of names in the udev database. However, there can only be one name in the OS when you try to use the network card.
Let’s wander back into the code. this time we’re going to take a look in src/udev/net/link-config.c starting at around line 403:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
if (ctx->enable_name_policy && config->name_policy) { NamePolicy *policy; for (policy = config->name_policy; !new_name && *policy != _NAMEPOLICY_INVALID; policy++) { switch (*policy) { case NAMEPOLICY_KERNEL: respect_predictable = true; break; case NAMEPOLICY_DATABASE: new_name = udev_device_get_property_value(device, "ID_NET_NAME_FROM_DATABASE"); break; case NAMEPOLICY_ONBOARD: new_name = udev_device_get_property_value(device, "ID_NET_NAME_ONBOARD"); break; case NAMEPOLICY_SLOT: new_name = udev_device_get_property_value(device, "ID_NET_NAME_SLOT"); break; case NAMEPOLICY_PATH: new_name = udev_device_get_property_value(device, "ID_NET_NAME_PATH"); break; case NAMEPOLICY_MAC: new_name = udev_device_get_property_value(device, "ID_NET_NAME_MAC"); break; default: break; } } } |
If we look at the overall case statement, you can see that the first match is the one that takes precedence. Working from top to bottom, udev takes the first match of:
- ID_NET_NAME_FROM_DATABASE
- ID_NET_NAME_ONBOARD
- ID_NET_NAME_SLOT
- ID_NET_NAME_PATH
- ID_NET_NAME_MAC
If we go back to our OnMetal example way at the top of the post, we can follow the logic. The udev database contained the following:
|
1 2 3 |
E: ID_NET_NAME_MAC=enxa0369f2cec90 E: ID_NET_NAME_PATH=enp8s0f0 E: ID_NET_NAME_SLOT=ens9f0 |
The udev daemon would start with ID_NET_NAME_FROM_DATABASE, but that doesn’t exist for this card. Next, it would move to ID_NET_NAME_ONBOARD, but that’s not present. Next comes ID_NET_NAME_SLOT, and we have a match! The ID_NET_NAME_SLOT entry has ens9f0 and that’s the final name for the network device.
This loop also handles some special cases. The first check is to see if someone requested for udev to not use predictable naming. We saw this in the systemd-networkd bonding post when the bootloader configuration contained net.ifnames=0. If that kernel command line parameter is present, predictable naming logic is skipped.
Another special case is ID_NET_NAME_FROM_DATABASE. Those ports come from udev’s internal hardware database. That file only has one item at the moment and it’s for a particular Dell iDRAC network interface.
Perplexed by hex
If the PCI slot numbers don’t seem to line up, be sure to read my post from last summer. I ran into a peculiar Dell server with a dual port Intel card on PCI bus 42. The interface ended up with a name of enp66s0f0 and I was stumped.
The name enp66s0f0 seems to say that we have a card on PCI bus 66, in slot 0, with multiple function index numbers (for multiple ports). However, systemd does a conversion of PCI slot numbers into hex. That means that decimal 66 becomes 42 in hex.
Most servers won’t be this complicated, but it’s key to remember the hex conversion.
Feedback
Are these systemd-related posts interesting? Let me know. I’m a huge fan of systemd and I enjoy writing about it.
Photo credit: University of Michigan Library

> Are these systemd-related posts interesting?
YES! as a linux guy since 0.99pl4 i’m find the move to systemd to be an “interesting” one, and your posts and explainers are helping to keep my sanity. you are performing good work sir, thank you.
Great article!
Minor correction:
“If we look at the overall case statement, you can see that the first match is the one that takes precedence. Working from top to bottom, udev takes the first match of:”
The order comes from the “NamePolicy” attribute of the systemd network link configuration file. This is best explained here: https://github.com/systemd/systemd/blob/7d19344a99f23b1375efc36fa5d04d4d341833ac/NEWS#L2347
The default NamePolicy (https://github.com/systemd/systemd/blob/7d19344a99f23b1375efc36fa5d04d4d341833ac/network/99-default.link#L2) happens to use the same order as the switch, so it’s not too wrong ;-)
Thanks for the useful background article. One quick point that you might want to take into account:
You appear to have conversion from hex to decimal that happens between lspci and systemd crossed over in your article. The hex values come from lspci (the manpage says “All numbers are hexadecimal.”), and it appears systemd is using decimal. 0x42 (hex) is 66 decimal (4 * 16 = 64; 64 + 2 = 66). Similarly 0x19 (hex) is 25 decimal (1 * 16 = 16; 16 + 9 = 25).
So “00:19.0 Ethernet controller: Intel Corporation Ethernet Connection I217-V (rev 05)” becomes “ID_NET_NAME_PATH=enp0s25” by converting 0x19 from hex to decimal (25); and the “dual port Intel card on PCI bus 42” becomes “enp66s0f0” by converting 0x42 from hex to decimal (66). As far as I can tell this solves the entire mystery about hex. (And in particular it means the bit in your article which says “this card might be named something ugly like enp0s13 (since 19 in decimal becomes 13 in hex)” is misleading; as the udev output above shows, systemd/udev is translating it to 25 — decimal.)
Ewen
Nice. Thanks a bunch Major! invigorating!
Ive been trying to understand where the names comes for a while. Thanks for your post. Keep doing that great job.
Great article. I think you reversed the relationship in the end, though: it’s 42 hex that’s 66 decimal.
Thanks for catching that. ;)
thanks :)
Nice !
This is illuminating background, but for me, my problem is I have some (Ansible) scripts that used to assume ‘eth0’ was usable. Does anyone have any clues how I should modify those scripts to obtain the new network device name?
Hey Jonathan — You could check the ansible_default_ipv4 fact and then loop through the available network interface facts until you find the interface which has the default IPv4 address configured. It’s not ideal, but it works. This sounds like it might be a nice RFE for Ansible. ;)
ansible_default_ipv4 is a good option but not if you are looking for the equivalent of ansible_eth1, in which case this might help: https://serverfault.com/a/852093/209018
Nice explanation, but the problem is “predictable interface names” is the baddest idea ever. Nothing is more predicable as “eth0”.
Just type “sudo ln -s /dev/null /etc/udev/rules.d/00-net-setup-link.rules” and reboot. You’ll be happy. I predict what your interface name after the reboot: eth0 for ethernet and wlan0 for Wifi. more than 99,99% sure. There is nothing wrong with eth0 eth1 and eth2.
The names are plenty predictable. What device they’ll be assigned to if you have multiple ethernet cards and make a system change of one sort or another? Entirely unpredictable. That’s the point of this.
once a MAC address assigned to a interface through ifcfg-*, Any reason to change that?
Great article. Didn’t know this.
The naming thus has the main benefit of identifying ports. With servers having so many ports, one is saved from plugging cables in and out while issuing ifconfig commands to figure out which ports is the one having issues.
nice post, and great documentation about new network device names
nice post and great documentation for net network device names
Thanks for the explanation! Not only was it educational, but it helped me predict what my Ethernet device name would end up being prior to upgrading my server from Ubuntu 14.04 to 16.04.
when using Fuel Openstack during the os env deployment and error is kicked out because the logical ethernet name is too long when assigning a vlan to the interface. so, my question is how can one reassign the predictable name to be something less than 16 characters so there is no error generated during the OS env deploymnet process.
Follow-up to the previous post – here is an example of the error message that is kicked out when assigning a vlan to an interface that has more that 16 characters to the name.
2017-05-02 21:13:30 ERR Error: argument “enx8cae4cfe75f2.101” is wrong: “name” too long
2017-05-02 21:13:30 ERR Command ‘ip link add link enx8cae4cfe75f2 name enx8cae4cfe75f2.101 type vlan id 101’ has been failed with exit_code=255.
Dan I think this would be a case where you could request overriding the naming or explicitly defining a mapping.
See these lines from the post.
We saw this in the systemd-networkd bonding post when the bootloader configuration contained net.ifnames=0. If that kernel command line parameter is present, predictable naming logic is skipped.
Another special case is ID_NET_NAME_FROM_DATABASE. Those ports come from udev’s internal hardware database. That file only has one item at the moment and it’s for a particular Dell iDRAC network interface.
Great article.Thanks for this great job.
You explain a very annoying ‘feature’ very well. Nice job.