Is it safe to set net.ifnames=0 in RHEL7, RHEL8 and RHEL9?

Issue:

Is it safe to disable Consistent Device Naming by setting net.ifnames=0 on the kernel command line in RHEL7, RHEL8 and RHEL9?

Why can I no longer use the 70-persistent-net.rules file to force persistent ethX names like I could in earlier versions of Red Hat Enterprise Linux?

Resolution:

No. Red Hat strongly recommend that the new RHEL7, RHEL8 and RHEL9 naming conventions are used.

It is safe to set net.ifnames=0 only under a few specific circumstances:

If the alternate naming scheme, such as biosdevname, is enabled and able to identify the needed interface properties. biosdevname is enabled by default only on systems runningRHEL7+ on Dell hardware. In all other cases it must be enabled by setting biosdevname=1 and ensuring the biosdevname package is installed. Non-Dell hardware may not provide the necessary information needed for biosdevname to work.

If the system only has a single interface and will never have more than a single interface.

KVM guests (libvirt only, not RHV or OpenStack) exclusively using virtio-net type interfaces can safely set net.ifnames=0.

If the system is configured to use unique, non ethX style names using either udev rules or relying on the functionality of the udev properties in the /usr/lib/udev/rules.d/60-net.rules rule file.

Root Cause:

Red Hat Enterprise Linux 7 is the first release of RHEL which assigns network interface names using systemd’s Predictable Network Interface naming scheme. This is described in detail in:

  • the Consistent network device naming section in the RHEL7 Networking Guide.
  • the Consistent network interface device naming section in the RHEL8 Configuring and managing networking guide.

The freedesktop.org website has further details about the new naming behavior and the move away from ethX device naming:

  • Predictable Network Interface Names

How are interface names assigned?

The kernel itself is inherently stateless and so by itself has no functionality to ensure drivers or network interfaces are enumerated in some predictable order at boot or whenever they appear. At boot time (or whenever a network driver is loaded) Ethernet interfaces are always assigned an ethX style name by the kernel, where X is the lowest currently unused number starting with 0 (zero).

As the kernel is inherently stateless and its ethX assignments are not predictable or consistent, some user space component is involved so that after a specific network interface appears it will be renamed as needed to some consistent name. Typically some stable property such as MAC address or PCI bus address is consulted and some renaming decisions made. In RHEL 5 and RHEL 6 that userspace component is udev; in RHEL 7+it is systemd-udev.

Overview of RHEL 6 naming behavior

In RHEL 6, the typical udev behavior is to compare the MAC address of interfaces as they appear against a rule file that specifies which ethX name to assign for an interface with the given MAC address. If the kernel-assigned name does not match what is listed in the rule file, then udev renames the interface to match. As long as an interface’s MAC address does not change and the associations in the udev rule file are not altered, udev should ensure naming consistency across reboots.

Overview of RHEL 7+ naming behavior

In RHEL 7 and above, the default systemd-udev behavior is significantly different than prior releases. systemd-udev no longer considers the MAC address of an interface nor does it maintain a rule file with MAC-to-name associations. Instead, udev renames all interfaces in a predictable way based upon stable properties such as PCI Slot Number or PCI bus address. As long as these properties do not change for the interface, systemd-udev should always derive the same name across reboots. Deliberately, the new names that udev assigns do not follow the kernel’s ethX pattern. systemd refers to this new scheme as its Predictable Network Interface feature.

Why is disabling the Predictable Network Interface feature not recommended (setting net.ifnames=0)?

In RHEL 7 and above, setting the kernel command line parameter net.ifnames=0 disables the Predictable Network Interface renaming behavior. If no alternate method is employed to rename interfaces then all network interfaces will remain with their original ethX kernel-assigned names which, as discussed above, are inherently unreliable. This can cause any number of problems.

Why does renaming to ethX sometimes fail in RHEL 7+?

In RHEL 7+ renaming will fail if the new name is already in use by some other interface. If the new name to be assigned is some ethX name then the likelihood of it already being in use is high, especially at boot time. For a more technical explanation please see the below Diagnostic Steps section of this article.

Because of the above changes, the upstream developers of systemd/udev have removed the 70-persistent-net.rules functionality and replaced it with the default naming scheme present in Red Hat Enterprise Linux 7 and above.

Why only RHEL KVM, and not RHV or OpenStack?

RHEL KVM persists virtio device address on the virtual PCI bus. We believe that KVM virtualization operates in such a way that the virtual hardware bus is enumerated the same way each time, resulting in a persistent presentation of devices to the kernel for naming with the ethX naming scheme.

The bus location is stored in the <address> parameter of the VM XML as follows, where # are unique parameters per VM:

<interface type='bridge'> 
  <mac address='52:54:00:##:##:##'/>
  <source bridge='br0'/> 
  <model type='virtio'/> 
  <address type='pci' domain='0x0000' bus='0x00' slot='0x0#' function='0x0'/>
</interface>

RHV and OpenStack do not store any such device information. The VM XML is built dynamically each time the VM starts, and the bus location may change with hardware changes or updates to the VM management software. There is no method to persist device order, hence no way to ensure persistent device enumeration or kernel ordering.

Diagnostic Steps

With the move to systemd, providing consistent ethX style naming is no longer a non-trivial task for the user. Older versions of udev include a much more complicated rename_netif()function which tried to handle collisions during an interface name change. That is to say, if are quest is made to rename interface A to eth2 while interface B is already using the nameeth2, udev would detect the issue and rename interface B to some other name (rename X where X was the netdev index number) and then rename interface A to eth2. This happened inside the udev function rename_netif(), note the section highlighted in red:

static int rename_netif(struct udev_event *event)
{ 
	struct udev_device *dev = event->dev; 
	int sk; 
	struct ifreq ifr; 
	int err; 
	
	info(event->udev, "changing net interface name from '%s' to '%s'\n", 			
		udev_device_get_sysname(dev), event->name);	
	
	sk = socket(PF_INET, SOCK_DGRAM, 0); 
	if (sk < 0) { 
		err(event->udev, "error opening socket: %m\n");
		return -1;
	} 
	memset(&ifr, 0x00, sizeof(struct ifreq));
	util_strscpy(ifr.ifr_name, IFNAMSIZ, udev_device_get_sysname(dev)); util_strscpy(ifr.ifr_newname, IFNAMSIZ, event->name);
	err = ioctl(sk, SIOCSIFNAME, &ifr); 
	if (err == 0) 
		rename_netif_kernel_log(ifr); 
	else {
		int loop;

	/* see if the destination interface name already exists */ 
	if (errno != EEXIST) { 
		err(event->udev, "error changing netif name '%s' to '%s': %m\n", ifr.ifr_name,     	
		    ifr.ifr_newname); 
		goto exit; 
		}
		/* free our own name, another process may wait for us */ 
		snprintf(ifr.ifr_newname, IFNAMSIZ, "rename%s", 
udev_device_get_sysattr_value(dev, "ifindex")); 
		err = ioctl(sk, SIOCSIFNAME, &ifr); 
		if (err != 0) {
			err(event->udev, "error changing netif name '%s' to '%s': %m\n", ifr.ifr_name, 	
			    ifr.ifr_newname); 
			goto exit;
		}
		rename_netif_kernel_log(ifr); 
		wait 90 seconds for our target to become available */ 
		util_strscpy(ifr.ifr_name, IFNAMSIZ, ifr.ifr_newname);
		util_strscpy(ifr.ifr_newname, IFNAMSIZ, event->name); 
		loop = 90 * 20; 
		while (loop--) { 
				const struct timespec duration = { 0, 1000 * 1000 * 1000 / 20 };
				err = ioctl(sk, SIOCSIFNAME, &ifr); 
				if (err == 0) { 
						rename_netif_kernel_log(ifr);
						break; 
				} if (errno != EEXIST) { 
						err(event->udev, "error changing net interface name '%s'
to '%s': %m\n", 
							ifr.ifr_name, ifr.ifr_newname); 
						break; 
					} 
					dbg(event->udev, "wait for netif '%s' to become free, loop=%i\n", 		
						event->name, (90 * 20) - loop); 
					nanosleep(&duration, NULL);
				} 
			}
exit: 
			close(sk); 
			return err;
}

With RHEL 7+ and systemd, the rename_netif() function has been greatly simplified. It no longer handles naming collisions so if there is an attempt to rename an interface to a name which is already in use, it will simply fail.

static int rename_netif(struct udev_event *event) { 
		struct udev_device *dev = event->dev; 
		char name[IFNAMSIZ];
		const char *oldname;
		int r; 
		
		oldname = udev_device_get_sysname(dev); 
		
		strscpy(name, IFNAMSIZ, event->name); 
		
		r = rtnl_set_link_name(&event->rtnl, udev_device_get_ifindex(dev), name); 
		if (r < 0) 
				return log_error_errno(r, "Error changing net interface name '%s' to '%s':
%m", oldname, name); 
		log_debug("renamed network interface '%s' to '%s'", oldname, name); 
		
		return 0;
}

Leave a Comment