diff options
Diffstat (limited to 'src/udev/udev-builtin-net_id.c')
-rw-r--r-- | src/udev/udev-builtin-net_id.c | 961 |
1 files changed, 961 insertions, 0 deletions
diff --git a/src/udev/udev-builtin-net_id.c b/src/udev/udev-builtin-net_id.c new file mode 100644 index 0000000..d06a8c7 --- /dev/null +++ b/src/udev/udev-builtin-net_id.c @@ -0,0 +1,961 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +/* + * 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 + * + * https://systemd.io/PREDICTABLE_INTERFACE_NAMES + * + * When the code here is changed, man/systemd.net-naming-scheme.xml must be updated too. + */ + +#include <errno.h> +#include <fcntl.h> +#include <net/if.h> +#include <net/if_arp.h> +#include <stdarg.h> +#include <stdlib.h> +#include <unistd.h> +#include <linux/if.h> +#include <linux/pci_regs.h> + +#include "alloc-util.h" +#include "dirent-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "fs-util.h" +#include "netif-naming-scheme.h" +#include "parse-util.h" +#include "proc-cmdline.h" +#include "stdio-util.h" +#include "string-util.h" +#include "strv.h" +#include "strxcpyx.h" +#include "udev-builtin.h" + +#define ONBOARD_INDEX_MAX (16*1024-1) + +enum netname_type{ + NET_UNDEF, + NET_PCI, + NET_USB, + NET_BCMA, + NET_VIRTIO, + NET_CCW, + NET_VIO, + NET_PLATFORM, + NET_NETDEVSIM, +}; + +struct netnames { + enum netname_type type; + + uint8_t mac[6]; + bool mac_valid; + + sd_device *pcidev; + char pci_slot[ALTIFNAMSIZ]; + char pci_path[ALTIFNAMSIZ]; + char pci_onboard[ALTIFNAMSIZ]; + const char *pci_onboard_label; + + char usb_ports[ALTIFNAMSIZ]; + char bcma_core[ALTIFNAMSIZ]; + char ccw_busid[ALTIFNAMSIZ]; + char vio_slot[ALTIFNAMSIZ]; + char platform_path[ALTIFNAMSIZ]; + char netdevsim_path[ALTIFNAMSIZ]; +}; + +struct virtfn_info { + sd_device *physfn_pcidev; + char suffix[ALTIFNAMSIZ]; +}; + +/* skip intermediate virtio devices */ +static sd_device *skip_virtio(sd_device *dev) { + sd_device *parent; + + /* there can only ever be one virtio bus per parent device, so we can + * safely ignore any virtio buses. see + * http://lists.linuxfoundation.org/pipermail/virtualization/2015-August/030331.html */ + for (parent = dev; parent; ) { + const char *subsystem; + + if (sd_device_get_subsystem(parent, &subsystem) < 0) + break; + + if (!streq(subsystem, "virtio")) + break; + + if (sd_device_get_parent(parent, &parent) < 0) + return NULL; + } + + return parent; +} + +static int get_virtfn_info(sd_device *dev, struct netnames *names, struct virtfn_info *ret) { + _cleanup_(sd_device_unrefp) sd_device *physfn_pcidev = NULL; + const char *physfn_link_file, *syspath; + _cleanup_free_ char *physfn_pci_syspath = NULL; + _cleanup_free_ char *virtfn_pci_syspath = NULL; + struct dirent *dent; + _cleanup_closedir_ DIR *dir = NULL; + char suffix[ALTIFNAMSIZ]; + int r; + + assert(dev); + assert(names); + assert(ret); + + r = sd_device_get_syspath(names->pcidev, &syspath); + if (r < 0) + return r; + + /* Check if this is a virtual function. */ + physfn_link_file = strjoina(syspath, "/physfn"); + r = chase_symlinks(physfn_link_file, NULL, 0, &physfn_pci_syspath, NULL); + if (r < 0) + return r; + + /* Get physical function's pci device. */ + r = sd_device_new_from_syspath(&physfn_pcidev, physfn_pci_syspath); + if (r < 0) + return r; + + /* Find the virtual function number by finding the right virtfn link. */ + dir = opendir(physfn_pci_syspath); + if (!dir) + return -errno; + + FOREACH_DIRENT_ALL(dent, dir, break) { + _cleanup_free_ char *virtfn_link_file = NULL; + + if (!startswith(dent->d_name, "virtfn")) + continue; + + virtfn_link_file = path_join(physfn_pci_syspath, dent->d_name); + if (!virtfn_link_file) + return -ENOMEM; + + if (chase_symlinks(virtfn_link_file, NULL, 0, &virtfn_pci_syspath, NULL) < 0) + continue; + + if (streq(syspath, virtfn_pci_syspath)) { + if (!snprintf_ok(suffix, sizeof(suffix), "v%s", &dent->d_name[6])) + return -ENOENT; + + break; + } + } + if (isempty(suffix)) + return -ENOENT; + + ret->physfn_pcidev = TAKE_PTR(physfn_pcidev); + strncpy(ret->suffix, suffix, sizeof(ret->suffix)); + + return 0; +} + +/* retrieve on-board index number and label from firmware */ +static int dev_pci_onboard(sd_device *dev, struct netnames *names) { + unsigned long idx, dev_port = 0; + const char *attr, *port_name = NULL; + size_t l; + char *s; + int r; + + /* ACPI _DSM — device specific method for naming a PCI or PCI Express device */ + if (sd_device_get_sysattr_value(names->pcidev, "acpi_index", &attr) < 0) { + /* SMBIOS type 41 — Onboard Devices Extended Information */ + r = sd_device_get_sysattr_value(names->pcidev, "index", &attr); + if (r < 0) + return r; + } + + r = safe_atolu(attr, &idx); + if (r < 0) + return r; + if (idx == 0 && !naming_scheme_has(NAMING_ZERO_ACPI_INDEX)) + return -EINVAL; + + /* Some BIOSes report rubbish indexes that are excessively high (2^24-1 is an index VMware likes to + * report for example). Let's define a cut-off where we don't consider the index reliable anymore. We + * pick some arbitrary cut-off, which is somewhere beyond the realistic number of physical network + * interface a system might have. Ideally the kernel would already filter this crap for us, but it + * doesn't currently. */ + if (idx > ONBOARD_INDEX_MAX) + return -ENOENT; + + /* kernel provided port index for multiple ports on a single PCI function */ + if (sd_device_get_sysattr_value(dev, "dev_port", &attr) >= 0) + dev_port = strtoul(attr, NULL, 10); + + /* kernel provided front panel port name for multiple port PCI device */ + (void) sd_device_get_sysattr_value(dev, "phys_port_name", &port_name); + + s = names->pci_onboard; + l = sizeof(names->pci_onboard); + l = strpcpyf(&s, l, "o%lu", idx); + if (port_name) + l = strpcpyf(&s, l, "n%s", port_name); + else if (dev_port > 0) + l = strpcpyf(&s, l, "d%lu", dev_port); + if (l == 0) + names->pci_onboard[0] = '\0'; + + if (sd_device_get_sysattr_value(names->pcidev, "label", &names->pci_onboard_label) < 0) + names->pci_onboard_label = NULL; + + return 0; +} + +/* read the 256 bytes PCI configuration space to check the multi-function bit */ +static bool is_pci_multifunction(sd_device *dev) { + _cleanup_close_ int fd = -1; + const char *filename, *syspath; + uint8_t config[64]; + + if (sd_device_get_syspath(dev, &syspath) < 0) + return false; + + filename = strjoina(syspath, "/config"); + fd = open(filename, O_RDONLY | O_CLOEXEC); + if (fd < 0) + return false; + if (read(fd, &config, sizeof(config)) != sizeof(config)) + return false; + + /* bit 0-6 header type, bit 7 multi/single function device */ + return config[PCI_HEADER_TYPE] & 0x80; +} + +static bool is_pci_ari_enabled(sd_device *dev) { + const char *a; + + if (sd_device_get_sysattr_value(dev, "ari_enabled", &a) < 0) + return false; + + return streq(a, "1"); +} + +static bool is_pci_bridge(sd_device *dev) { + const char *v, *p; + + if (sd_device_get_sysattr_value(dev, "modalias", &v) < 0) + return false; + + if (!startswith(v, "pci:")) + return false; + + p = strrchr(v, 's'); + if (!p) + return false; + if (p[1] != 'c') + return false; + + /* PCI device subclass 04 corresponds to PCI bridge */ + return strneq(p + 2, "04", 2); +} + +static int dev_pci_slot(sd_device *dev, struct netnames *names) { + unsigned long dev_port = 0; + unsigned domain, bus, slot, func; + int hotplug_slot = -1; + size_t l; + char *s; + const char *sysname, *attr, *port_name = NULL, *syspath; + _cleanup_(sd_device_unrefp) sd_device *pci = NULL; + sd_device *hotplug_slot_dev; + char slots[PATH_MAX]; + _cleanup_closedir_ DIR *dir = NULL; + struct dirent *dent; + int r; + + r = sd_device_get_sysname(names->pcidev, &sysname); + if (r < 0) + return r; + + if (sscanf(sysname, "%x:%x:%x.%u", &domain, &bus, &slot, &func) != 4) + return -ENOENT; + + if (naming_scheme_has(NAMING_NPAR_ARI) && + is_pci_ari_enabled(names->pcidev)) + /* ARI devices support up to 256 functions on a single device ("slot"), and interpret the + * traditional 5-bit slot and 3-bit function number as a single 8-bit function number, + * where the slot makes up the upper 5 bits. */ + func += slot * 8; + + /* kernel provided port index for multiple ports on a single PCI function */ + if (sd_device_get_sysattr_value(dev, "dev_port", &attr) >= 0) { + dev_port = strtoul(attr, NULL, 10); + /* With older kernels IP-over-InfiniBand network interfaces sometimes erroneously + * provide the port number in the 'dev_id' sysfs attribute instead of 'dev_port', + * which thus stays initialized as 0. */ + if (dev_port == 0 && + sd_device_get_sysattr_value(dev, "type", &attr) >= 0) { + unsigned long type; + + type = strtoul(attr, NULL, 10); + if (type == ARPHRD_INFINIBAND && + sd_device_get_sysattr_value(dev, "dev_id", &attr) >= 0) + dev_port = strtoul(attr, NULL, 16); + } + } + + /* kernel provided front panel port name for multi-port PCI device */ + (void) sd_device_get_sysattr_value(dev, "phys_port_name", &port_name); + + /* compose a name based on the raw kernel's PCI bus, slot numbers */ + s = names->pci_path; + l = sizeof(names->pci_path); + if (domain > 0) + l = strpcpyf(&s, l, "P%u", domain); + l = strpcpyf(&s, l, "p%us%u", bus, slot); + if (func > 0 || is_pci_multifunction(names->pcidev)) + l = strpcpyf(&s, l, "f%u", func); + if (port_name) + l = strpcpyf(&s, l, "n%s", port_name); + else if (dev_port > 0) + l = strpcpyf(&s, l, "d%lu", dev_port); + if (l == 0) + names->pci_path[0] = '\0'; + + /* ACPI _SUN — slot user number */ + r = sd_device_new_from_subsystem_sysname(&pci, "subsystem", "pci"); + if (r < 0) + return r; + + r = sd_device_get_syspath(pci, &syspath); + if (r < 0) + return r; + if (!snprintf_ok(slots, sizeof slots, "%s/slots", syspath)) + return -ENAMETOOLONG; + + dir = opendir(slots); + if (!dir) + return -errno; + + hotplug_slot_dev = names->pcidev; + while (hotplug_slot_dev) { + if (sd_device_get_sysname(hotplug_slot_dev, &sysname) < 0) + continue; + + FOREACH_DIRENT_ALL(dent, dir, break) { + int i; + char str[PATH_MAX]; + _cleanup_free_ char *address = NULL; + + if (dot_or_dot_dot(dent->d_name)) + continue; + + r = safe_atoi(dent->d_name, &i); + if (r < 0 || i <= 0) + continue; + + /* match slot address with device by stripping the function */ + if (snprintf_ok(str, sizeof str, "%s/%s/address", slots, dent->d_name) && + read_one_line_file(str, &address) >= 0 && + startswith(sysname, address)) { + hotplug_slot = i; + + /* We found the match between PCI device and slot. However, we won't use the + * slot index if the device is a PCI bridge, because it can have other child + * devices that will try to claim the same index and that would create name + * collision. */ + if (naming_scheme_has(NAMING_BRIDGE_NO_SLOT) && is_pci_bridge(hotplug_slot_dev)) + hotplug_slot = 0; + + break; + } + } + if (hotplug_slot >= 0) + break; + if (sd_device_get_parent_with_subsystem_devtype(hotplug_slot_dev, "pci", NULL, &hotplug_slot_dev) < 0) + break; + rewinddir(dir); + } + + if (hotplug_slot > 0) { + s = names->pci_slot; + l = sizeof(names->pci_slot); + if (domain > 0) + l = strpcpyf(&s, l, "P%d", domain); + l = strpcpyf(&s, l, "s%d", hotplug_slot); + if (func > 0 || is_pci_multifunction(names->pcidev)) + l = strpcpyf(&s, l, "f%d", func); + if (port_name) + l = strpcpyf(&s, l, "n%s", port_name); + else if (dev_port > 0) + l = strpcpyf(&s, l, "d%lu", dev_port); + if (l == 0) + names->pci_slot[0] = '\0'; + } + + return 0; +} + +static int names_vio(sd_device *dev, struct netnames *names) { + sd_device *parent; + unsigned busid, slotid, ethid; + const char *syspath, *subsystem; + int r; + + /* check if our direct parent is a VIO device with no other bus in-between */ + r = sd_device_get_parent(dev, &parent); + if (r < 0) + return r; + + r = sd_device_get_subsystem(parent, &subsystem); + if (r < 0) + return r; + if (!streq("vio", subsystem)) + return -ENOENT; + + /* The devices' $DEVPATH number is tied to (virtual) hardware (slot id + * selected in the HMC), thus this provides a reliable naming (e.g. + * "/devices/vio/30000002/net/eth1"); we ignore the bus number, as + * there should only ever be one bus, and then remove leading zeros. */ + r = sd_device_get_syspath(dev, &syspath); + if (r < 0) + return r; + + if (sscanf(syspath, "/sys/devices/vio/%4x%4x/net/eth%u", &busid, &slotid, ðid) != 3) + return -EINVAL; + + xsprintf(names->vio_slot, "v%u", slotid); + names->type = NET_VIO; + return 0; +} + +#define _PLATFORM_TEST "/sys/devices/platform/vvvvPPPP" +#define _PLATFORM_PATTERN4 "/sys/devices/platform/%4s%4x:%2x/net/eth%u" +#define _PLATFORM_PATTERN3 "/sys/devices/platform/%3s%4x:%2x/net/eth%u" + +static int names_platform(sd_device *dev, struct netnames *names, bool test) { + sd_device *parent; + char vendor[5]; + unsigned model, instance, ethid; + const char *syspath, *pattern, *validchars, *subsystem; + int r; + + /* check if our direct parent is a platform device with no other bus in-between */ + r = sd_device_get_parent(dev, &parent); + if (r < 0) + return r; + + r = sd_device_get_subsystem(parent, &subsystem); + if (r < 0) + return r; + + if (!streq("platform", subsystem)) + return -ENOENT; + + r = sd_device_get_syspath(dev, &syspath); + if (r < 0) + return r; + + /* syspath is too short, to have a valid ACPI instance */ + if (strlen(syspath) < sizeof _PLATFORM_TEST) + return -EINVAL; + + /* Vendor ID can be either PNP ID (3 chars A-Z) or ACPI ID (4 chars A-Z and numerals) */ + if (syspath[sizeof _PLATFORM_TEST - 1] == ':') { + pattern = _PLATFORM_PATTERN4; + validchars = UPPERCASE_LETTERS DIGITS; + } else { + pattern = _PLATFORM_PATTERN3; + validchars = UPPERCASE_LETTERS; + } + + /* Platform devices are named after ACPI table match, and instance id + * eg. "/sys/devices/platform/HISI00C2:00"); + * The Vendor (3 or 4 char), followed by hexdecimal model number : instance id. + */ + + DISABLE_WARNING_FORMAT_NONLITERAL; + if (sscanf(syspath, pattern, vendor, &model, &instance, ðid) != 4) + return -EINVAL; + REENABLE_WARNING; + + if (!in_charset(vendor, validchars)) + return -ENOENT; + + ascii_strlower(vendor); + + xsprintf(names->platform_path, "a%s%xi%u", vendor, model, instance); + names->type = NET_PLATFORM; + return 0; +} + +static int names_pci(sd_device *dev, struct netnames *names) { + sd_device *parent; + struct netnames vf_names = {}; + struct virtfn_info vf_info = {}; + const char *subsystem; + int r; + + assert(dev); + assert(names); + + r = sd_device_get_parent(dev, &parent); + if (r < 0) + return r; + /* skip virtio subsystem if present */ + parent = skip_virtio(parent); + + if (!parent) + return -ENOENT; + + /* check if our direct parent is a PCI device with no other bus in-between */ + if (sd_device_get_subsystem(parent, &subsystem) >= 0 && + streq("pci", subsystem)) { + names->type = NET_PCI; + names->pcidev = parent; + } else { + r = sd_device_get_parent_with_subsystem_devtype(dev, "pci", NULL, &names->pcidev); + if (r < 0) + return r; + } + + if (naming_scheme_has(NAMING_SR_IOV_V) && + get_virtfn_info(dev, names, &vf_info) >= 0) { + /* If this is an SR-IOV virtual device, get base name using physical device and add virtfn suffix. */ + vf_names.pcidev = vf_info.physfn_pcidev; + dev_pci_onboard(dev, &vf_names); + dev_pci_slot(dev, &vf_names); + if (vf_names.pci_onboard[0]) + if (strlen(vf_names.pci_onboard) + strlen(vf_info.suffix) < sizeof(names->pci_onboard)) + strscpyl(names->pci_onboard, sizeof(names->pci_onboard), + vf_names.pci_onboard, vf_info.suffix, NULL); + if (vf_names.pci_slot[0]) + if (strlen(vf_names.pci_slot) + strlen(vf_info.suffix) < sizeof(names->pci_slot)) + strscpyl(names->pci_slot, sizeof(names->pci_slot), + vf_names.pci_slot, vf_info.suffix, NULL); + if (vf_names.pci_path[0]) + if (strlen(vf_names.pci_path) + strlen(vf_info.suffix) < sizeof(names->pci_path)) + strscpyl(names->pci_path, sizeof(names->pci_path), + vf_names.pci_path, vf_info.suffix, NULL); + sd_device_unref(vf_info.physfn_pcidev); + } else { + dev_pci_onboard(dev, names); + dev_pci_slot(dev, names); + } + + return 0; +} + +static int names_usb(sd_device *dev, struct netnames *names) { + sd_device *usbdev; + char name[256], *ports, *config, *interf, *s; + const char *sysname; + size_t l; + int r; + + assert(dev); + assert(names); + + r = sd_device_get_parent_with_subsystem_devtype(dev, "usb", "usb_interface", &usbdev); + if (r < 0) + return r; + + r = sd_device_get_sysname(usbdev, &sysname); + if (r < 0) + return r; + + /* get USB port number chain, configuration, interface */ + strscpy(name, sizeof(name), sysname); + 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 = strpcpyl(&s, sizeof(names->usb_ports), "u", ports, NULL); + + /* append USB config number, suppress the common config == 1 */ + if (!streq(config, "1")) + l = strpcpyl(&s, sizeof(names->usb_ports), "c", config, NULL); + + /* append USB interface number, suppress the interface == 0 */ + if (!streq(interf, "0")) + l = strpcpyl(&s, sizeof(names->usb_ports), "i", interf, NULL); + if (l == 0) + return -ENAMETOOLONG; + + names->type = NET_USB; + return 0; +} + +static int names_bcma(sd_device *dev, struct netnames *names) { + sd_device *bcmadev; + unsigned core; + const char *sysname; + int r; + + assert(dev); + assert(names); + + r = sd_device_get_parent_with_subsystem_devtype(dev, "bcma", NULL, &bcmadev); + if (r < 0) + return r; + + r = sd_device_get_sysname(bcmadev, &sysname); + if (r < 0) + return r; + + /* bus num:core num */ + if (sscanf(sysname, "bcma%*u:%u", &core) != 1) + return -EINVAL; + /* suppress the common core == 0 */ + if (core > 0) + xsprintf(names->bcma_core, "b%u", core); + + names->type = NET_BCMA; + return 0; +} + +static int names_ccw(sd_device *dev, struct netnames *names) { + sd_device *cdev; + const char *bus_id, *subsys; + size_t bus_id_len; + size_t bus_id_start; + int r; + + assert(dev); + assert(names); + + /* Retrieve the associated CCW device */ + r = sd_device_get_parent(dev, &cdev); + if (r < 0) + return r; + + /* skip virtio subsystem if present */ + cdev = skip_virtio(cdev); + if (!cdev) + return -ENOENT; + + r = sd_device_get_subsystem(cdev, &subsys); + if (r < 0) + return r; + + /* Network devices are either single or grouped CCW devices */ + if (!STR_IN_SET(subsys, "ccwgroup", "ccw")) + return -ENOENT; + + /* Retrieve bus-ID of the CCW device. The bus-ID uniquely + * identifies the network device on the Linux on System z channel + * subsystem. Note that the bus-ID contains lowercase characters. + */ + r = sd_device_get_sysname(cdev, &bus_id); + if (r < 0) + return r; + + /* Check the length of the bus-ID. Rely on the fact that the kernel provides a correct bus-ID; + * alternatively, improve this check and parse and verify each bus-ID part... + */ + bus_id_len = strlen(bus_id); + if (!IN_SET(bus_id_len, 8, 9)) + return -EINVAL; + + /* Strip leading zeros from the bus id for aesthetic purposes. This + * keeps the ccw names stable, yet much shorter in general case of + * bus_id 0.0.0600 -> 600. This is similar to e.g. how PCI domain is + * not prepended when it is zero. Preserve the last 0 for 0.0.0000. + */ + bus_id_start = strspn(bus_id, ".0"); + bus_id += bus_id_start < bus_id_len ? bus_id_start : bus_id_len - 1; + + /* Store the CCW bus-ID for use as network device name */ + if (snprintf_ok(names->ccw_busid, sizeof(names->ccw_busid), "c%s", bus_id)) + names->type = NET_CCW; + + return 0; +} + +static int names_mac(sd_device *dev, struct netnames *names) { + const char *s; + unsigned long i; + unsigned a1, a2, a3, a4, a5, a6; + int r; + + /* Some kinds of devices tend to have hardware addresses + * that are impossible to use in an iface name. + */ + r = sd_device_get_sysattr_value(dev, "type", &s); + if (r < 0) + return r; + + i = strtoul(s, NULL, 0); + switch (i) { + /* The persistent part of a hardware address of an InfiniBand NIC + * is 8 bytes long. We cannot fit this much in an iface name. + */ + case ARPHRD_INFINIBAND: + return -EINVAL; + default: + break; + } + + /* check for NET_ADDR_PERM, skip random MAC addresses */ + r = sd_device_get_sysattr_value(dev, "addr_assign_type", &s); + if (r < 0) + return r; + i = strtoul(s, NULL, 0); + if (i != 0) + return 0; + + r = sd_device_get_sysattr_value(dev, "address", &s); + if (r < 0) + return r; + 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; +} + +static int names_netdevsim(sd_device *dev, struct netnames *names) { + sd_device *netdevsimdev; + const char *sysname; + unsigned addr; + const char *port_name = NULL; + int r; + bool ok; + + if (!naming_scheme_has(NAMING_NETDEVSIM)) + return 0; + + assert(dev); + assert(names); + + r = sd_device_get_parent_with_subsystem_devtype(dev, "netdevsim", NULL, &netdevsimdev); + if (r < 0) + return r; + r = sd_device_get_sysname(netdevsimdev, &sysname); + if (r < 0) + return r; + + if (sscanf(sysname, "netdevsim%u", &addr) != 1) + return -EINVAL; + + r = sd_device_get_sysattr_value(dev, "phys_port_name", &port_name); + if (r < 0) + return r; + + ok = snprintf_ok(names->netdevsim_path, sizeof(names->netdevsim_path), "i%un%s", addr, port_name); + if (!ok) + return -ENOBUFS; + + names->type = NET_NETDEVSIM; + + return 0; +} + +/* IEEE Organizationally Unique Identifier vendor string */ +static int ieee_oui(sd_device *dev, struct netnames *names, bool test) { + char str[32]; + + 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; + xsprintf(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, NULL, str, NULL, test); + return 0; +} + +static int builtin_net_id(sd_device *dev, int argc, char *argv[], bool test) { + const char *s, *p, *devtype, *prefix = "en"; + struct netnames names = {}; + unsigned long i; + int r; + + /* handle only ARPHRD_ETHER, ARPHRD_SLIP and ARPHRD_INFINIBAND devices */ + r = sd_device_get_sysattr_value(dev, "type", &s); + if (r < 0) + return r; + + i = strtoul(s, NULL, 0); + switch (i) { + case ARPHRD_ETHER: + prefix = "en"; + break; + case ARPHRD_INFINIBAND: + if (naming_scheme_has(NAMING_INFINIBAND)) + prefix = "ib"; + else + return 0; + break; + case ARPHRD_SLIP: + prefix = "sl"; + break; + default: + return 0; + } + + /* skip stacked devices, like VLANs, ... */ + r = sd_device_get_sysattr_value(dev, "ifindex", &s); + if (r < 0) + return r; + r = sd_device_get_sysattr_value(dev, "iflink", &p); + if (r < 0) + return r; + if (!streq(s, p)) + return 0; + + if (sd_device_get_devtype(dev, &devtype) >= 0) { + if (streq("wlan", devtype)) + prefix = "wl"; + else if (streq("wwan", devtype)) + prefix = "ww"; + } + + udev_builtin_add_property(dev, test, "ID_NET_NAMING_SCHEME", naming_scheme()->name); + + r = names_mac(dev, &names); + if (r >= 0 && names.mac_valid) { + char str[ALTIFNAMSIZ]; + + xsprintf(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 path names for Linux on System z network devices */ + if (names_ccw(dev, &names) >= 0 && names.type == NET_CCW) { + char str[ALTIFNAMSIZ]; + + if (snprintf_ok(str, sizeof str, "%s%s", prefix, names.ccw_busid)) + udev_builtin_add_property(dev, test, "ID_NET_NAME_PATH", str); + return 0; + } + + /* get ibmveth/ibmvnic slot-based names. */ + if (names_vio(dev, &names) >= 0 && names.type == NET_VIO) { + char str[ALTIFNAMSIZ]; + + if (snprintf_ok(str, sizeof str, "%s%s", prefix, names.vio_slot)) + udev_builtin_add_property(dev, test, "ID_NET_NAME_SLOT", str); + return 0; + } + + /* get ACPI path names for ARM64 platform devices */ + if (names_platform(dev, &names, test) >= 0 && names.type == NET_PLATFORM) { + char str[ALTIFNAMSIZ]; + + if (snprintf_ok(str, sizeof str, "%s%s", prefix, names.platform_path)) + udev_builtin_add_property(dev, test, "ID_NET_NAME_PATH", str); + return 0; + } + + /* get netdevsim path names */ + if (names_netdevsim(dev, &names) >= 0 && names.type == NET_NETDEVSIM) { + char str[ALTIFNAMSIZ]; + + if (snprintf_ok(str, sizeof str, "%s%s", prefix, names.netdevsim_path)) + udev_builtin_add_property(dev, test, "ID_NET_NAME_PATH", str); + + return 0; + } + + /* get PCI based path names, we compose only PCI based paths */ + if (names_pci(dev, &names) < 0) + return 0; + + /* plain PCI device */ + if (names.type == NET_PCI) { + char str[ALTIFNAMSIZ]; + + if (names.pci_onboard[0] && + snprintf_ok(str, sizeof str, "%s%s", prefix, names.pci_onboard)) + udev_builtin_add_property(dev, test, "ID_NET_NAME_ONBOARD", str); + + if (names.pci_onboard_label && + snprintf_ok(str, sizeof str, "%s%s", + naming_scheme_has(NAMING_LABEL_NOPREFIX) ? "" : prefix, + names.pci_onboard_label)) + udev_builtin_add_property(dev, test, "ID_NET_LABEL_ONBOARD", str); + + if (names.pci_path[0] && + snprintf_ok(str, sizeof str, "%s%s", prefix, names.pci_path)) + udev_builtin_add_property(dev, test, "ID_NET_NAME_PATH", str); + + if (names.pci_slot[0] && + snprintf_ok(str, sizeof str, "%s%s", prefix, names.pci_slot)) + udev_builtin_add_property(dev, test, "ID_NET_NAME_SLOT", str); + return 0; + } + + /* USB device */ + if (names_usb(dev, &names) >= 0 && names.type == NET_USB) { + char str[ALTIFNAMSIZ]; + + if (names.pci_path[0] && + snprintf_ok(str, sizeof str, "%s%s%s", prefix, names.pci_path, names.usb_ports)) + udev_builtin_add_property(dev, test, "ID_NET_NAME_PATH", str); + + if (names.pci_slot[0] && + snprintf_ok(str, sizeof str, "%s%s%s", prefix, names.pci_slot, names.usb_ports)) + udev_builtin_add_property(dev, test, "ID_NET_NAME_SLOT", str); + return 0; + } + + /* Broadcom bus */ + if (names_bcma(dev, &names) >= 0 && names.type == NET_BCMA) { + char str[ALTIFNAMSIZ]; + + if (names.pci_path[0] && + snprintf_ok(str, sizeof str, "%s%s%s", prefix, names.pci_path, names.bcma_core)) + udev_builtin_add_property(dev, test, "ID_NET_NAME_PATH", str); + + if (names.pci_slot[0] && + snprintf(str, sizeof str, "%s%s%s", prefix, names.pci_slot, names.bcma_core)) + udev_builtin_add_property(dev, test, "ID_NET_NAME_SLOT", str); + return 0; + } + + return 0; +} + +const UdevBuiltin udev_builtin_net_id = { + .name = "net_id", + .cmd = builtin_net_id, + .help = "Network device properties", +}; |