Network Device Naming In Linux
A free Linux kernel development course lecture on how network interfaces get identified without major and minor numbers
Every driver class you have studied so far in this free Linux device drivers course — character and block — is reached through a device node under /dev, keyed by a major/minor pair. Network devices break that pattern entirely. There is no /dev/eth0 on a modern Linux system, and no minor number to look up. This lecture, part of our free embedded Linux course, explains how the kernel names network interfaces instead, how you interact with them from user space, and how to read hardware details directly through a raw socket ioctl.
What You Will Learn
Prerequisites
This lecture assumes you have completed the character and block device lectures earlier in this free Linux kernel development course, and that you are comfortable building and loading a kernel module.
Why No Device Node?
Character and block drivers exist to move bytes or blocks in and out of a file-like object, so a filesystem node is a natural interface: open it, read it, write it, close it. A network interface does not work that way. Data does not flow through a single file descriptor tied to the interface itself; instead, the networking stack routes individual packets to whichever interface a socket’s routing decision selects. Because there is no one file to open, the kernel never allocates a major/minor pair or a /dev entry for a network device at all. Instead, it allocates the interface a name.
How a Driver Registers an Interface Name
A network driver asks the core networking code for a net_device structure using an allocation call, supplying a name template rather than a fixed name. On a current mainline kernel this is done with alloc_netdev() (or one of its wrappers such as alloc_etherdev() for Ethernet-style hardware), followed by register_netdev() to make the interface live.
The %d in the template is filled in by the kernel with the next free instance number for that prefix, starting from zero. This is only the starting name, though. A user-space device manager such as udev, driven by rules keyed on bus location, MAC address, or firmware description, is free to rename the interface to something more predictable — which is exactly why modern distributions show names like enp3s0 or wlp2s0 rather than plain eth0, even though the driver itself only ever asked for eth%d.
The Normal Path: ip and Sockets
Once an interface exists, its name is normally used for exactly one thing from a human’s perspective: configuring it with a tool like ip or the older ifconfig, to assign an address and bring up routes.
$ ip link show
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 ...
$ sudo ip addr add 192.168.1.50/24 dev eth0
$ sudo ip link set eth0 up
After that point, applications never mention the interface name again. They open a socket, and the network layer inside the kernel decides — based on the routing table, not on any file path — which interface actually carries each packet.
The Direct Path: Talking to a Driver with ioctl
It is also possible to reach a network driver directly from user space, bypassing the routing layer, by creating a socket purely as a handle and issuing one of the ioctl commands the networking core defines for driver queries and configuration. A common example is asking for a device’s hardware (MAC) address with SIOCGIFHWADDR.
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <linux/sockios.h>
#include <net/if.h>
int main(int argc, char *argv[])
{
struct ifreq ifr;
int sock, i;
if (argc != 2) {
fprintf(stderr, "usage: %s \n", argv[0]);
return 1;
}
sock = socket(AF_INET, SOCK_DGRAM, 0);
if (sock < 0) {
perror("socket");
return 1;
}
memset(&ifr, 0, sizeof(ifr));
strncpy(ifr.ifr_name, argv[1], IFNAMSIZ - 1);
if (ioctl(sock, SIOCGIFHWADDR, &ifr) < 0) {
perror("ioctl(SIOCGIFHWADDR)");
close(sock);
return 1;
}
printf("%s hwaddr: ", argv[1]);
for (i = 0; i < 6; i++)
printf("%02x%s", (unsigned char)ifr.ifr_hwaddr.sa_data[i],
i < 5 ? ":" : "\n");
close(sock);
return 0;
}
Build and run it against a real interface:
$ gcc -o ep_getmac ep_getmac.c
$ ./ep_getmac eth0
eth0 hwaddr: 02:42:ac:11:00:02
The socket here is only ever used as a handle for the ioctl call — no packet is actually sent. This is a standard ioctl the generic network layer handles on the driver’s behalf, but drivers are free to define their own private ioctl numbers for hardware-specific configuration that has no generic equivalent.
Registering a Minimal Network Interface
You can see the naming mechanism in isolation with a tiny driver that registers an interface but implements no real transmit/receive path.
#include <linux/module.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
static struct net_device *ep_netdev;
static netdev_tx_t ep_start_xmit(struct sk_buff *skb, struct net_device *dev)
{
/* Demo only: drop every packet, just prove registration/naming works */
dev_kfree_skb(skb);
return NETDEV_TX_OK;
}
static const struct net_device_ops ep_netdev_ops = {
.ndo_start_xmit = ep_start_xmit,
};
static int __init ep_netdemo_init(void)
{
int ret;
ep_netdev = alloc_netdev(0, "ep_net%d", NET_NAME_UNKNOWN, ether_setup);
if (!ep_netdev)
return -ENOMEM;
ep_netdev->netdev_ops = &ep_netdev_ops;
eth_hw_addr_random(ep_netdev);
ret = register_netdev(ep_netdev);
if (ret) {
free_netdev(ep_netdev);
return ret;
}
pr_info("ep_netdemo: registered as %s\n", ep_netdev->name);
return 0;
}
static void __exit ep_netdemo_exit(void)
{
unregister_netdev(ep_netdev);
free_netdev(ep_netdev);
}
module_init(ep_netdemo_init);
module_exit(ep_netdemo_exit);
MODULE_LICENSE("GPL");
$ sudo insmod ep_netdemo.ko
$ dmesg | tail -1
[ 987.654321] ep_netdemo: registered as ep_net0
$ ip link show ep_net0
5: ep_net0: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN
link/ether 4a:2f:91:0c:88:7e brd ff:ff:ff:ff:ff:ff
$ sudo rmmod ep_netdemo
Notice there is no corresponding entry under /dev at any point, and no major/minor pair shows up anywhere — the interface is only ever reachable by the name ep_net0 that the kernel handed back from the %d template.
Comparison With Character and Block Devices
| Aspect | Character/Block Devices | Network Devices |
|---|---|---|
| Identifier | Major + minor number | Interface name (eth0, wlan0, …) |
| Access point | /dev node | None — accessed via sockets |
| Registration call | register_chrdev_region / register_blkdev | alloc_netdev + register_netdev |
| Renaming | Not applicable (node path can be symlinked) | udev/systemd may rename after registration |
| Direct user-space control | open/read/write/ioctl on the node | ioctl on a throwaway socket |
Common Mistakes
- Looking for a
/deventry for a network interface — it does not exist and never will. - Hardcoding an interface name like
eth0in scripts, when udev naming rules can and do change it on real hardware. - Forgetting that the driver’s requested name (
eth%d,wlan%d) is only a starting point, not a guarantee. - Using a full
AF_PACKETraw socket when a simpleAF_INET/SOCK_DGRAMsocket is enough just to issue a driverioctl.
Best Practices
- Prefer generic
ioctlnumbers and the standard netlink/iptooling over private driverioctls whenever the functionality already exists generically. - Use stable identifiers (MAC address matching, systemd link files) rather than the raw kernel-assigned name if your application must survive interface renaming.
- Always free the
net_deviceand unregister cleanly in your module’s exit path to avoid leaving a stale interface behind.
Summary
Network devices are the odd one out among the driver classes in this free Linux kernel development course: no major/minor pair, no /dev node, and no open/read/write model. Instead, the kernel hands out a name built from a driver-supplied template and an auto-incrementing instance number, and user space normally talks to the interface only indirectly, through routing and sockets — though a direct ioctl path remains available for querying or configuring the driver when needed.
Frequently Asked Questions
Why don’t network devices have major and minor numbers?
Because there is no single file-like object to open for a network interface — packets are routed by the networking stack rather than read or written through one file descriptor, so the kernel assigns a name instead of a device node.
What determines whether an interface is called eth0 or something else?
The driver supplies a name template such as “eth%d” when it calls alloc_netdev(), and the kernel fills in the next free instance number; a device manager like udev may rename it afterward based on its own rules.
Can user space talk to a network driver without going through the routing layer?
Yes, by creating a socket purely as a handle and issuing an ioctl such as SIOCGIFHWADDR, which the network layer forwards to the driver on the caller’s behalf.
Why did my eth0 interface get renamed to something like enp3s0?
Modern distributions use udev/systemd predictable network interface naming rules based on bus location or firmware information, which override the driver’s original eth%d name.
Do custom ioctl numbers work the same way for network drivers as for character drivers?
Yes, a network driver can define private ioctl numbers for functionality with no generic equivalent, handled the same way a character driver would handle its own custom ioctl commands.
Is this lecture part of a free Linux kernel development course I can follow from the start?
Yes, this is one lecture in an ongoing free Linux kernel development course and free Linux device drivers course covering driver types in order, starting from character devices.
Continue Your Free Embedded Linux Course
Next up: inspecting drivers already loaded on a running system through /proc and /sys.
Next Lecture Back to Course Index
4 Comments