udev-builtin-net_id.c revision ad37f393fa97f4274cc3bf97a0d8c388a429037e
cfb5b3805759e63dc5e0cae6e92e1df885b5c5b6Tom Gundersen/***
cfb5b3805759e63dc5e0cae6e92e1df885b5c5b6Tom Gundersen This file is part of systemd.
cfb5b3805759e63dc5e0cae6e92e1df885b5c5b6Tom Gundersen
cfb5b3805759e63dc5e0cae6e92e1df885b5c5b6Tom Gundersen Copyright 2012 Kay Sievers <kay@vrfy.org>
cfb5b3805759e63dc5e0cae6e92e1df885b5c5b6Tom Gundersen
cfb5b3805759e63dc5e0cae6e92e1df885b5c5b6Tom Gundersen systemd is free software; you can redistribute it and/or modify it
cfb5b3805759e63dc5e0cae6e92e1df885b5c5b6Tom Gundersen under the terms of the GNU Lesser General Public License as published by
cfb5b3805759e63dc5e0cae6e92e1df885b5c5b6Tom Gundersen the Free Software Foundation; either version 2.1 of the License, or
cfb5b3805759e63dc5e0cae6e92e1df885b5c5b6Tom Gundersen (at your option) any later version.
cfb5b3805759e63dc5e0cae6e92e1df885b5c5b6Tom Gundersen
cfb5b3805759e63dc5e0cae6e92e1df885b5c5b6Tom Gundersen systemd is distributed in the hope that it will be useful, but
cfb5b3805759e63dc5e0cae6e92e1df885b5c5b6Tom Gundersen WITHOUT ANY WARRANTY; without even the implied warranty of
cfb5b3805759e63dc5e0cae6e92e1df885b5c5b6Tom Gundersen MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
cfb5b3805759e63dc5e0cae6e92e1df885b5c5b6Tom Gundersen Lesser General Public License for more details.
cfb5b3805759e63dc5e0cae6e92e1df885b5c5b6Tom Gundersen
cfb5b3805759e63dc5e0cae6e92e1df885b5c5b6Tom Gundersen You should have received a copy of the GNU Lesser General Public License
cfb5b3805759e63dc5e0cae6e92e1df885b5c5b6Tom Gundersen along with systemd; If not, see <http://www.gnu.org/licenses/>.
cfb5b3805759e63dc5e0cae6e92e1df885b5c5b6Tom Gundersen***/
cfb5b3805759e63dc5e0cae6e92e1df885b5c5b6Tom Gundersen
cfb5b3805759e63dc5e0cae6e92e1df885b5c5b6Tom Gundersen/*
cfb5b3805759e63dc5e0cae6e92e1df885b5c5b6Tom Gundersen * Predictable network interface device names based on:
cfb5b3805759e63dc5e0cae6e92e1df885b5c5b6Tom Gundersen * - firmware/bios-provided index numbers for on-board devices
07630cea1f3a845c09309f197ac7c4f11edd3b62Lennart Poettering * - firmware-provided pci-express hotplug slot index number
cfb5b3805759e63dc5e0cae6e92e1df885b5c5b6Tom Gundersen * - physical/geographical location of the hardware
cfb5b3805759e63dc5e0cae6e92e1df885b5c5b6Tom Gundersen * - the interface's MAC address
07630cea1f3a845c09309f197ac7c4f11edd3b62Lennart Poettering *
cfb5b3805759e63dc5e0cae6e92e1df885b5c5b6Tom Gundersen * Two character prefixes based on the type of interface:
07630cea1f3a845c09309f197ac7c4f11edd3b62Lennart Poettering * en -- ethernet
07630cea1f3a845c09309f197ac7c4f11edd3b62Lennart Poettering * wl -- wlan
07630cea1f3a845c09309f197ac7c4f11edd3b62Lennart Poettering * ww -- wwan
07630cea1f3a845c09309f197ac7c4f11edd3b62Lennart Poettering *
cfb5b3805759e63dc5e0cae6e92e1df885b5c5b6Tom Gundersen * Type of names:
cfb5b3805759e63dc5e0cae6e92e1df885b5c5b6Tom Gundersen * o<index> -- on-board device index number
cfb5b3805759e63dc5e0cae6e92e1df885b5c5b6Tom Gundersen * s<slot>[f<function>] -- hotplug slot index number
cfb5b3805759e63dc5e0cae6e92e1df885b5c5b6Tom Gundersen * x<MAC> -- MAC address
cfb5b3805759e63dc5e0cae6e92e1df885b5c5b6Tom Gundersen * p<bus>s<slot>[f<function>] -- PCI geographical location
cfb5b3805759e63dc5e0cae6e92e1df885b5c5b6Tom Gundersen * p<bus>s<slot>[f<function>][u<port>][...][c<config>][i<interface>]
dbe81cbd2a93088236a2e4e41eeb33378940f7b9Martin Pitt * -- USB port number chain
cfb5b3805759e63dc5e0cae6e92e1df885b5c5b6Tom Gundersen *
cfb5b3805759e63dc5e0cae6e92e1df885b5c5b6Tom Gundersen * All multi-function PCI devices will carry the [f<function>] number in the
cfb5b3805759e63dc5e0cae6e92e1df885b5c5b6Tom Gundersen * device name, including the function 0 device.
cfb5b3805759e63dc5e0cae6e92e1df885b5c5b6Tom Gundersen *
cfb5b3805759e63dc5e0cae6e92e1df885b5c5b6Tom Gundersen * For USB devices the fill chain of port numbers of hubs is composed. If the
cfb5b3805759e63dc5e0cae6e92e1df885b5c5b6Tom Gundersen * string would gt longer than the maximum of 15 characters, the name is not
cfb5b3805759e63dc5e0cae6e92e1df885b5c5b6Tom Gundersen * exported. The usual USB configuration == 1 and interface == 0 values are
cfb5b3805759e63dc5e0cae6e92e1df885b5c5b6Tom Gundersen * suppressed.
cfb5b3805759e63dc5e0cae6e92e1df885b5c5b6Tom Gundersen *
9ee18af3a036074c4021c82ae2e67f5ccaa9ea9dTom Gundersen * PCI ethernet card with firmware index
9ee18af3a036074c4021c82ae2e67f5ccaa9ea9dTom Gundersen * ID_NET_NAME_ONBOARD=eno1
9ee18af3a036074c4021c82ae2e67f5ccaa9ea9dTom Gundersen * ID_NET_NAME_ONBOARD_LABEL=Ethernet Port 1
cfb5b3805759e63dc5e0cae6e92e1df885b5c5b6Tom Gundersen *
cfb5b3805759e63dc5e0cae6e92e1df885b5c5b6Tom Gundersen * PCI ethernet card
cfb5b3805759e63dc5e0cae6e92e1df885b5c5b6Tom Gundersen * /sys/devices/pci0000:00/0000:00:1c.3/0000:05:00.0/net/ens1
dbe81cbd2a93088236a2e4e41eeb33378940f7b9Martin Pitt * ID_NET_NAME_MAC=enx000000000466
933f9caeeb2b3c1b951d330e04beb04226e5a890Daniel Mack * ID_NET_NAME_PATH=enp5s0
dbe81cbd2a93088236a2e4e41eeb33378940f7b9Martin Pitt * ID_NET_NAME_SLOT=ens1
cfb5b3805759e63dc5e0cae6e92e1df885b5c5b6Tom Gundersen *
cfb5b3805759e63dc5e0cae6e92e1df885b5c5b6Tom Gundersen * PCI ethernet card in hotplug slot with firmware index number:
cfb5b3805759e63dc5e0cae6e92e1df885b5c5b6Tom Gundersen * /sys/devices/pci0000:00/0000:00:1c.3/0000:05:00.0/net/ens1
cfb5b3805759e63dc5e0cae6e92e1df885b5c5b6Tom Gundersen * ID_NET_NAME_MAC=enx000000000466
9ee18af3a036074c4021c82ae2e67f5ccaa9ea9dTom Gundersen * ID_NET_NAME_PATH=enp5s0
cfb5b3805759e63dc5e0cae6e92e1df885b5c5b6Tom Gundersen * ID_NET_NAME_SLOT=ens1
cfb5b3805759e63dc5e0cae6e92e1df885b5c5b6Tom Gundersen *
cfb5b3805759e63dc5e0cae6e92e1df885b5c5b6Tom Gundersen * PCI wlan card:
cfb5b3805759e63dc5e0cae6e92e1df885b5c5b6Tom Gundersen * /sys/devices/pci0000:00/0000:00:1c.1/0000:03:00.0/net/wlp3s0
cfb5b3805759e63dc5e0cae6e92e1df885b5c5b6Tom Gundersen * ID_NET_NAME_MAC=wlx0024d7e31130
cfb5b3805759e63dc5e0cae6e92e1df885b5c5b6Tom Gundersen * ID_NET_NAME_PATH=wlp3s0
75f86906c52735c98dc0aa7e24b773edb42ee814Lennart Poettering *
cfb5b3805759e63dc5e0cae6e92e1df885b5c5b6Tom Gundersen * USB built-in 3G modem:
cfb5b3805759e63dc5e0cae6e92e1df885b5c5b6Tom Gundersen * /sys/devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.4/2-1.4:1.6/net/wwp0s29u1u4i6
cfb5b3805759e63dc5e0cae6e92e1df885b5c5b6Tom Gundersen * ID_NET_NAME_MAC=wwx028037ec0200
cfb5b3805759e63dc5e0cae6e92e1df885b5c5b6Tom Gundersen * ID_NET_NAME_PATH=wwp0s29u1u4i6
cfb5b3805759e63dc5e0cae6e92e1df885b5c5b6Tom Gundersen *
cfb5b3805759e63dc5e0cae6e92e1df885b5c5b6Tom Gundersen * USB Android phone:
cfb5b3805759e63dc5e0cae6e92e1df885b5c5b6Tom Gundersen * /sys/devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.2/2-1.2:1.0/net/enp0s29u1u2
cfb5b3805759e63dc5e0cae6e92e1df885b5c5b6Tom Gundersen * ID_NET_NAME_MAC=enxd626b3450fb5
cfb5b3805759e63dc5e0cae6e92e1df885b5c5b6Tom Gundersen * ID_NET_NAME_PATH=enp0s29u1u2
cfb5b3805759e63dc5e0cae6e92e1df885b5c5b6Tom Gundersen */
40862866417265ac8c20422cb44f14a8f141ce0dTom Gundersen
40862866417265ac8c20422cb44f14a8f141ce0dTom Gundersen#include <stdio.h>
40862866417265ac8c20422cb44f14a8f141ce0dTom Gundersen#include <stdlib.h>
40862866417265ac8c20422cb44f14a8f141ce0dTom Gundersen#include <stdarg.h>
cfb5b3805759e63dc5e0cae6e92e1df885b5c5b6Tom Gundersen#include <unistd.h>
40862866417265ac8c20422cb44f14a8f141ce0dTom Gundersen#include <string.h>
40862866417265ac8c20422cb44f14a8f141ce0dTom Gundersen#include <errno.h>
cfb5b3805759e63dc5e0cae6e92e1df885b5c5b6Tom Gundersen#include <net/if.h>
cfb5b3805759e63dc5e0cae6e92e1df885b5c5b6Tom Gundersen#include <linux/pci_regs.h>
cfb5b3805759e63dc5e0cae6e92e1df885b5c5b6Tom Gundersen
933f9caeeb2b3c1b951d330e04beb04226e5a890Daniel Mack#include "udev.h"
cfb5b3805759e63dc5e0cae6e92e1df885b5c5b6Tom Gundersen
e2acdb6b0f68d9b4152708a9f21bf9e11f8b9e7eTorstein Husebøenum netname_type{
933f9caeeb2b3c1b951d330e04beb04226e5a890Daniel Mack NET_UNDEF,
933f9caeeb2b3c1b951d330e04beb04226e5a890Daniel Mack NET_PCI,
933f9caeeb2b3c1b951d330e04beb04226e5a890Daniel Mack NET_USB,
cfb5b3805759e63dc5e0cae6e92e1df885b5c5b6Tom Gundersen};
cfb5b3805759e63dc5e0cae6e92e1df885b5c5b6Tom Gundersen
9ee18af3a036074c4021c82ae2e67f5ccaa9ea9dTom Gundersenstruct netnames {
cfb5b3805759e63dc5e0cae6e92e1df885b5c5b6Tom Gundersen enum netname_type type;
cfb5b3805759e63dc5e0cae6e92e1df885b5c5b6Tom Gundersen
cfb5b3805759e63dc5e0cae6e92e1df885b5c5b6Tom Gundersen uint8_t mac[6];
bool mac_valid;
struct udev_device *pcidev;
char pci_slot[IFNAMSIZ];
char pci_path[IFNAMSIZ];
char pci_onboard[IFNAMSIZ];
const char *pci_onboard_label;
struct udev_device *usbdev;
char usb_ports[IFNAMSIZ];
};
/* retrieve on-board index number and label from firmware */
static int dev_pci_onboard(struct udev_device *dev, struct netnames *names) {
const char *index;
int idx;
/* ACPI _DSM -- device specific method for naming a PCI or PCI Express device */
index = udev_device_get_sysattr_value(names->pcidev, "acpi_index");
/* SMBIOS type 41 -- Onboard Devices Extended Information */
if (!index)
index = udev_device_get_sysattr_value(names->pcidev, "index");
if (!index)
return -ENOENT;
idx = strtoul(index, NULL, 0);
if (idx <= 0)
return -EINVAL;
snprintf(names->pci_onboard, sizeof(names->pci_onboard), "o%d", idx);
names->pci_onboard_label = udev_device_get_sysattr_value(names->pcidev, "label");
return 0;
}
/* read the 256 bytes PCI configuration space to check the multi-function bit */
static bool is_pci_singlefunction(struct udev_device *dev) {
char filename[256];
FILE *f;
char config[64];
bool single = false;
snprintf(filename, sizeof(filename), "%s/config", udev_device_get_syspath(dev));
f = fopen(filename, "re");
if (!f)
goto out;
if (fread(&config, sizeof(config), 1, f) != 1)
goto out;
/* bit 0-6 header type, bit 7 multi/single function device */
if ((config[PCI_HEADER_TYPE] & 0x80) == 0)
single = true;
out:
fclose(f);
return single;
}
static int dev_pci_slot(struct udev_device *dev, struct netnames *names) {
struct udev *udev = udev_device_get_udev(names->pcidev);
unsigned int bus;
unsigned int slot;
unsigned int func;
struct udev_device *pci = NULL;
char slots[256];
DIR *dir;
struct dirent *dent;
char str[256];
int hotplug_slot = 0;
int err = 0;
/* compose a name based on the raw kernel's PCI bus, slot numbers */
if (sscanf(udev_device_get_sysname(names->pcidev), "0000:%x:%x.%d", &bus, &slot, &func) != 3)
return -ENOENT;
if (func == 0 && is_pci_singlefunction(names->pcidev))
snprintf(names->pci_path, sizeof(names->pci_path), "p%ds%d", bus, slot);
else
snprintf(names->pci_path, sizeof(names->pci_path), "p%ds%df%d", bus, slot, func);
/* ACPI _SUN -- slot user number */
pci = udev_device_new_from_subsystem_sysname(udev, "subsystem", "pci");
if (!pci) {
err = -ENOENT;
goto out;
}
snprintf(slots, sizeof(slots), "%s/slots", udev_device_get_syspath(pci));
dir = opendir(slots);
if (!dir) {
err = -errno;
goto out;
}
for (dent = readdir(dir); dent != NULL; dent = readdir(dir)) {
int i;
char *rest;
char *address;
if (dent->d_name[0] == '.')
continue;
i = strtol(dent->d_name, &rest, 10);
if (rest[0] != '\0')
continue;
if (i < 1)
continue;
snprintf(str, sizeof(str), "%s/%s/address", slots, dent->d_name);
if (read_one_line_file(str, &address) >= 0) {
/* match slot address with device by stripping the function */
if (strncmp(address, udev_device_get_sysname(names->pcidev), strlen(address)) == 0)
hotplug_slot = i;
free(address);
}
if (hotplug_slot > 0)
break;
}
closedir(dir);
if (hotplug_slot > 0) {
if (func == 0 && is_pci_singlefunction(names->pcidev))
snprintf(names->pci_slot, sizeof(names->pci_slot), "s%d", hotplug_slot);
else
snprintf(names->pci_slot, sizeof(names->pci_slot), "s%df%d", hotplug_slot, func);
}
out:
udev_device_unref(pci);
return err;
}
static int names_pci(struct udev_device *dev, struct netnames *names) {
struct udev_device *parent;
parent = udev_device_get_parent(dev);
if (!parent)
return -ENOENT;
/* check if our direct parent is a PCI device with no other bus in-between */
if (streq("pci", udev_device_get_subsystem(parent))) {
names->type = NET_PCI;
names->pcidev = parent;
} else {
names->pcidev = udev_device_get_parent_with_subsystem_devtype(dev, "pci", NULL);
if (!names->pcidev)
return -ENOENT;
}
dev_pci_onboard(dev, names);
dev_pci_slot(dev, names);
return 0;
}
static int names_usb(struct udev_device *dev, struct netnames *names) {
char name[256];
char *ports;
char *config;
char *interf;
size_t l;
char *s;
names->usbdev = udev_device_get_parent_with_subsystem_devtype(dev, "usb", "usb_interface");
if (!names->usbdev)
return -ENOENT;
/* get USB port number chain, configuration, interface */
util_strscpy(name, sizeof(name), udev_device_get_sysname(names->usbdev));
s = strchr(name, '-');
if (!s)
return -EINVAL;
ports = s+1;
s = strchr(ports, ':');
if (!s)
return -EINVAL;
s[0] = '\0';
config = s+1;
s = strchr(config, '.');
if (!s)
return -EINVAL;
s[0] = '\0';
interf = s+1;
/* prefix every port number in the chain with "u"*/
s = ports;
while ((s = strchr(s, '.')))
s[0] = 'u';
s = names->usb_ports;
l = util_strpcpyl(&s, sizeof(names->usb_ports), "u", ports, NULL);
/* append USB config number, suppress the common config == 1 */
if (!streq(config, "1"))
l = util_strpcpyl(&s, sizeof(names->usb_ports), "c", config, NULL);
/* append USB interface number, suppress the interface == 0 */
if (!streq(interf, "0"))
l = util_strpcpyl(&s, sizeof(names->usb_ports), "i", interf, NULL);
if (l == 0)
return -ENAMETOOLONG;
names->type = NET_USB;
return 0;
}
static int names_mac(struct udev_device *dev, struct netnames *names) {
const char *s;
unsigned int i;
unsigned int a1, a2, a3, a4, a5, a6;
/* check for NET_ADDR_PERM, skip random MAC addresses */
s = udev_device_get_sysattr_value(dev, "addr_assign_type");
if (!s)
return EXIT_FAILURE;
i = strtoul(s, NULL, 0);
if (i != 0)
return 0;
s = udev_device_get_sysattr_value(dev, "address");
if (!s)
return -ENOENT;
if (sscanf(s, "%x:%x:%x:%x:%x:%x", &a1, &a2, &a3, &a4, &a5, &a6) != 6)
return -EINVAL;
/* skip empty MAC addresses */
if (a1 + a2 + a3 + a4 + a5 + a6 == 0)
return -EINVAL;
names->mac[0] = a1;
names->mac[1] = a2;
names->mac[2] = a3;
names->mac[3] = a4;
names->mac[4] = a5;
names->mac[5] = a6;
names->mac_valid = true;
return 0;
}
/* IEEE Organizationally Unique Identifier vendor string */
static int ieee_oui(struct udev_device *dev, struct netnames *names, bool test) {
char str[IFNAMSIZ];
if (names->mac_valid)
return -ENOENT;
/* skip commonly misused 00:00:00 (Xerox) prefix */
if (memcmp(names->mac, "\0\0\0", 3) == 0)
return -EINVAL;
snprintf(str, sizeof(str), "OUI:%02X%02X%02X%02X%02X%02X",
names->mac[0], names->mac[1], names->mac[2],
names->mac[3], names->mac[4], names->mac[5]);
udev_builtin_hwdb_lookup(dev, str, test);
return 0;
}
static int builtin_net_id(struct udev_device *dev, int argc, char *argv[], bool test) {
const char *s;
unsigned int i;
const char *devtype;
const char *prefix = "en";
struct netnames names;
int err;
/* handle only ARPHRD_ETHER devices */
s = udev_device_get_sysattr_value(dev, "type");
if (!s)
return EXIT_FAILURE;
i = strtoul(s, NULL, 0);
if (i != 1)
return 0;
devtype = udev_device_get_devtype(dev);
if (devtype) {
if (streq("wlan", devtype))
prefix = "wl";
else if (streq("wwan", devtype))
prefix = "ww";
}
zero(names);
err = names_mac(dev, &names);
if (err >= 0 && names.mac_valid) {
char str[IFNAMSIZ];
snprintf(str, sizeof(str), "%sx%02x%02x%02x%02x%02x%02x", prefix,
names.mac[0], names.mac[1], names.mac[2],
names.mac[3], names.mac[4], names.mac[5]);
udev_builtin_add_property(dev, test, "ID_NET_NAME_MAC", str);
ieee_oui(dev, &names, test);
}
/* get PCI based path names, we compose only PCI based paths */
err = names_pci(dev, &names);
if (err < 0)
goto out;
/* plain PCI device */
if (names.type == NET_PCI) {
char str[IFNAMSIZ];
if (names.pci_onboard[0])
if (snprintf(str, sizeof(str), "%s%s", prefix, names.pci_onboard) < (int)sizeof(str))
udev_builtin_add_property(dev, test, "ID_NET_NAME_ONBOARD", str);
if (names.pci_onboard_label)
if (snprintf(str, sizeof(str), "%s%s", prefix, names.pci_onboard_label) < (int)sizeof(str))
udev_builtin_add_property(dev, test, "ID_NET_LABEL_ONBOARD", str);
if (names.pci_path[0])
if (snprintf(str, sizeof(str), "%s%s", prefix, names.pci_path) < (int)sizeof(str))
udev_builtin_add_property(dev, test, "ID_NET_NAME_PATH", str);
if (names.pci_slot[0])
if (snprintf(str, sizeof(str), "%s%s", prefix, names.pci_slot) < (int)sizeof(str))
udev_builtin_add_property(dev, test, "ID_NET_NAME_SLOT", str);
goto out;
}
/* USB device */
err = names_usb(dev, &names);
if (err >= 0 && names.type == NET_USB) {
char str[IFNAMSIZ];
if (names.pci_path[0])
if (snprintf(str, sizeof(str), "%s%s%s", prefix, names.pci_path, names.usb_ports) < (int)sizeof(str))
udev_builtin_add_property(dev, test, "ID_NET_NAME_PATH", str);
if (names.pci_slot[0])
if (snprintf(str, sizeof(str), "%s%s%s", prefix, names.pci_slot, names.usb_ports) < (int)sizeof(str))
udev_builtin_add_property(dev, test, "ID_NET_NAME_SLOT", str);
}
out:
return EXIT_SUCCESS;
}
const struct udev_builtin udev_builtin_net_id = {
.name = "net_id",
.cmd = builtin_net_id,
.help = "network device properties",
};