So Linux has adopted Persistent Device Naming, which is a really great thing for most systems. Unlike the old days where we just had eth0 and eth1 and eth2 etc (which at least has no spaces unlike Local Area Connection 6 that another OS uses), whose order depended on driver initialization in the kernel. Most people just had eth0 and were happy, and most people will still just have one Ethernet interface and will still be happy.

But with more complex setups, this persistent naming can be a problem, especially as PCIe devices come and go and change the bus layout. I’d really like truly persistent naming, tied to the specific MAC address or something, that won’t ever change no matter what else I do with the system.

To be fair to persistent device names, for most people who are not making regular hardware changes, they are a good thing. The hierarchy of naming prefers onboard naming when possible (i.e. provided by the UEFI / HW), followed by the device path. This device path is consistent across reboots, but adding or removing hardware can cause items in the list to move up and down, changing their path ID (i.e. enp4 becomes enp5 after adding another PCIe card). But at least replacing hardware with identical hardware will keep the path IDs the same, so we can replace NICs or really anything else in the system without messing up our network configuration.

Solution

The solution is to rename these devices on our own terms instead of the udev methodology. We can match hardware based on IDs such as the MAC address, which identifies a singular NIC, and use this to assign a name of our own. Now it’s up to us to modify this file if we ever replace a NIC, but at least other things in the system won’t change our name.

Here’s the documentation for systemd link units, it’s incredible really.

So given that information, I constructed the absolute minimal link file for my Proxmox system. We can’t use any name that the system might assign automatically (such as ethX, enoX, enpX, …) or we are in for trouble. I was originally going to name them geX for gigabit ethernet and xgX for 10/25Gig, but Proxmox’s web UI was not a fan of this and refused to do any networking administration since it couldn’t figure out what type those were by parsing the /etc/network/interfaces file. So, I settled on enge0 for my onboard gigabit Ethernet and enxg1 for the port on the 25 gig card that I’m using (enxg0 is the other port on the card, since these used to be function 0 and function 1 of the device). Remember that enx is used by devices identified by their MAC address and nothing else (usually USB NICs), so don’t use that. eno, enp, and ens are all used by udev as well.

We now need to write a unit file for each interface we want to have a really permanent name, and it needs to start with a number less than 99, since 99-default.link will set the interface to regular persistent naming.

That results in a file like /etc/systemd/network/10-ge0.link:

[Match]
MACAddress=12:34:56:78:9a:bc
[Link]
Name=enge0

Add one for each, in my case I added 3. Make sure you get the MAC right. Now go into /etc/network/interfaces and replace the old name with the new one. On reboot, udev should do its thing, call systemd-link, which will do it’s thing, and everything should magically come up.

My /etc/network/interfaces on Proxmox now looks like this:

# network interface settings; autogenerated
# Please do NOT modify this file directly, unless you know what
# you're doing.
#
# I know what I'm doing so I edited it manually

auto lo
iface lo inet loopback

#Real gigabit NIC
auto enge0
iface enge0 inet6 manual

#Dual port Mellanox NIC
auto enxg0
iface enxg0 inet6 static
	address fd69:beef:cafe:6::70/64

#Dual port Mellanox NIC
auto enxg1
iface enxg1 inet6 manual

#Bond primary LAN NICs for failover
auto bond0
iface bond0 inet manual
	bond-slaves enge0 enxg1
	bond-miimon 100
	bond-mode active-backup
	bond-primary enxg1

#Primary VM bridge to LAN
auto vmbr0
iface vmbr0 inet static
	address 172.27.1.70/20
	gateway 172.27.0.1
	bridge-ports bond0
	bridge-stp off
	bridge-fd 0
	bridge-vlan-aware yes
	bridge-vids 2-4094

iface vmbr0 inet6 static
	address 2001:db8:6969:420::70/64
	gateway 2001:db8:6969:420::1

And they show up Proxmox UI too! Proxmox UI

And of course ip a shows the interfaces as expected!

3: enge0: <NO-CARRIER,BROADCAST,MULTICAST,SLAVE,UP> mtu 1500 qdisc pfifo_fast master bond0 state DOWN group default qlen 1000
    link/ether 26:a9:1e:5e:7f:5d brd ff:ff:ff:ff:ff:ff permaddr a8:a1:59:22:48:32
7: enxg0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether b8:ce:f6:ae:38:e4 brd ff:ff:ff:ff:ff:ff
    inet6 fd69:beef:cafe:6::70/64 scope global 
       valid_lft forever preferred_lft forever
    inet6 fe80::bace:f6ff:feae:38e4/64 scope link 
       valid_lft forever preferred_lft forever
8: enxg1: <BROADCAST,MULTICAST,SLAVE,UP,LOWER_UP> mtu 1500 qdisc mq master bond0 state UP group default qlen 1000
    link/ether 26:a9:1e:5e:7f:5d brd ff:ff:ff:ff:ff:ff permaddr b8:ce:f6:ae:38:e5
9: bond0: <BROADCAST,MULTICAST,MASTER,UP,LOWER_UP> mtu 1500 qdisc noqueue master vmbr0 state UP group default qlen 1000
    link/ether 26:a9:1e:5e:7f:5d brd ff:ff:ff:ff:ff:ff
10: vmbr0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether 26:a9:1e:5e:7f:5d brd ff:ff:ff:ff:ff:ff
    inet 172.27.1.70/20 scope global vmbr0
       valid_lft forever preferred_lft forever
    inet6 2001:db8:6969:420::70/64 scope global 
       valid_lft forever preferred_lft forever
    inet6 fe80::24a9:1eff:fe5e:7f5d/64 scope link 
       valid_lft forever preferred_lft forever

So now I’m free to add/remove PCIe cards at will (this system gets used for a lot of testing random devices) and not worry about what that will do to my main NIC.