diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-06 02:25:50 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-06 02:25:50 +0000 |
commit | 19f4f86bfed21c5326ed2acebe1163f3a83e832b (patch) | |
tree | d59b9989ce55ed23693e80974d94c856f1c2c8b1 /src/udev/net | |
parent | Initial commit. (diff) | |
download | systemd-19f4f86bfed21c5326ed2acebe1163f3a83e832b.tar.xz systemd-19f4f86bfed21c5326ed2acebe1163f3a83e832b.zip |
Adding upstream version 241.upstream/241upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | src/udev/net/ethtool-util.c | 787 | ||||
-rw-r--r-- | src/udev/net/ethtool-util.h | 108 | ||||
-rw-r--r-- | src/udev/net/link-config-gperf.gperf | 54 | ||||
-rw-r--r-- | src/udev/net/link-config.c | 535 | ||||
-rw-r--r-- | src/udev/net/link-config.h | 88 | ||||
-rw-r--r-- | src/udev/net/naming-scheme.c | 64 | ||||
-rw-r--r-- | src/udev/net/naming-scheme.h | 48 |
7 files changed, 1684 insertions, 0 deletions
diff --git a/src/udev/net/ethtool-util.c b/src/udev/net/ethtool-util.c new file mode 100644 index 0000000..0dcec03 --- /dev/null +++ b/src/udev/net/ethtool-util.c @@ -0,0 +1,787 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ + +#include <net/if.h> +#include <sys/ioctl.h> +#include <linux/ethtool.h> +#include <linux/sockios.h> + +#include "conf-parser.h" +#include "ethtool-util.h" +#include "link-config.h" +#include "log.h" +#include "missing.h" +#include "socket-util.h" +#include "string-table.h" +#include "strxcpyx.h" +#include "util.h" + +static const char* const duplex_table[_DUP_MAX] = { + [DUP_FULL] = "full", + [DUP_HALF] = "half" +}; + +DEFINE_STRING_TABLE_LOOKUP(duplex, Duplex); +DEFINE_CONFIG_PARSE_ENUM(config_parse_duplex, duplex, Duplex, "Failed to parse duplex setting"); + +static const char* const wol_table[_WOL_MAX] = { + [WOL_PHY] = "phy", + [WOL_UCAST] = "unicast", + [WOL_MCAST] = "multicast", + [WOL_BCAST] = "broadcast", + [WOL_ARP] = "arp", + [WOL_MAGIC] = "magic", + [WOL_MAGICSECURE] = "secureon", + [WOL_OFF] = "off", +}; + +DEFINE_STRING_TABLE_LOOKUP(wol, WakeOnLan); +DEFINE_CONFIG_PARSE_ENUM(config_parse_wol, wol, WakeOnLan, "Failed to parse WakeOnLan setting"); + +static const char* const port_table[] = { + [NET_DEV_PORT_TP] = "tp", + [NET_DEV_PORT_AUI] = "aui", + [NET_DEV_PORT_MII] = "mii", + [NET_DEV_PORT_FIBRE] = "fibre", + [NET_DEV_PORT_BNC] = "bnc", +}; + +DEFINE_STRING_TABLE_LOOKUP(port, NetDevPort); +DEFINE_CONFIG_PARSE_ENUM(config_parse_port, port, NetDevPort, "Failed to parse Port setting"); + +static const char* const netdev_feature_table[_NET_DEV_FEAT_MAX] = { + [NET_DEV_FEAT_GSO] = "tx-generic-segmentation", + [NET_DEV_FEAT_GRO] = "rx-gro", + [NET_DEV_FEAT_LRO] = "rx-lro", + [NET_DEV_FEAT_TSO] = "tx-tcp-segmentation", + [NET_DEV_FEAT_TSO6] = "tx-tcp6-segmentation", +}; + +static const char* const ethtool_link_mode_bit_table[] = { + [ETHTOOL_LINK_MODE_10baseT_Half_BIT] = "10baset-half", + [ETHTOOL_LINK_MODE_10baseT_Full_BIT] = "10baset-full", + [ETHTOOL_LINK_MODE_100baseT_Half_BIT] = "100baset-half", + [ETHTOOL_LINK_MODE_100baseT_Full_BIT] = "100baset-full", + [ETHTOOL_LINK_MODE_1000baseT_Half_BIT] = "1000baset-half", + [ETHTOOL_LINK_MODE_1000baseT_Full_BIT] = "1000baset-full", + [ETHTOOL_LINK_MODE_Autoneg_BIT] = "autonegotiation", + [ETHTOOL_LINK_MODE_TP_BIT] = "tp", + [ETHTOOL_LINK_MODE_AUI_BIT] = "aui", + [ETHTOOL_LINK_MODE_MII_BIT] = "mii", + [ETHTOOL_LINK_MODE_FIBRE_BIT] = "fibre", + [ETHTOOL_LINK_MODE_BNC_BIT] = "bnc", + [ETHTOOL_LINK_MODE_10000baseT_Full_BIT] = "10000baset-full", + [ETHTOOL_LINK_MODE_Pause_BIT] = "pause", + [ETHTOOL_LINK_MODE_Asym_Pause_BIT] = "asym-pause", + [ETHTOOL_LINK_MODE_2500baseX_Full_BIT] = "2500basex-full", + [ETHTOOL_LINK_MODE_Backplane_BIT] = "backplane", + [ETHTOOL_LINK_MODE_1000baseKX_Full_BIT] = "1000basekx-full", + [ETHTOOL_LINK_MODE_10000baseKX4_Full_BIT] = "10000basekx4-full", + [ETHTOOL_LINK_MODE_10000baseKR_Full_BIT] = "10000basekr-full", + [ETHTOOL_LINK_MODE_10000baseR_FEC_BIT] = "10000baser-fec", + [ETHTOOL_LINK_MODE_20000baseMLD2_Full_BIT] = "20000basemld2-full", + [ETHTOOL_LINK_MODE_20000baseKR2_Full_BIT] = "20000basekr2-full", + [ETHTOOL_LINK_MODE_40000baseKR4_Full_BIT] = "40000basekr4-full", + [ETHTOOL_LINK_MODE_40000baseCR4_Full_BIT] = "40000basecr4-full", + [ETHTOOL_LINK_MODE_40000baseSR4_Full_BIT] = "40000basesr4-full", + [ETHTOOL_LINK_MODE_40000baseLR4_Full_BIT] = "40000baselr4-full", + [ETHTOOL_LINK_MODE_56000baseKR4_Full_BIT] = "56000basekr4-full", + [ETHTOOL_LINK_MODE_56000baseCR4_Full_BIT] = "56000basecr4-full", + [ETHTOOL_LINK_MODE_56000baseSR4_Full_BIT] = "56000basesr4-full", + [ETHTOOL_LINK_MODE_56000baseLR4_Full_BIT] = "56000baselr4-full", + [ETHTOOL_LINK_MODE_25000baseCR_Full_BIT] = "25000basecr-full", + [ETHTOOL_LINK_MODE_25000baseKR_Full_BIT] = "25000basekr-full", + [ETHTOOL_LINK_MODE_25000baseSR_Full_BIT] = "25000basesr-full", + [ETHTOOL_LINK_MODE_50000baseCR2_Full_BIT] = "50000basecr2-full", + [ETHTOOL_LINK_MODE_50000baseKR2_Full_BIT] = "50000basekr2-full", + [ETHTOOL_LINK_MODE_100000baseKR4_Full_BIT] = "100000basekr4-full", + [ETHTOOL_LINK_MODE_100000baseSR4_Full_BIT] = "100000basesr4-full", + [ETHTOOL_LINK_MODE_100000baseCR4_Full_BIT] = "100000basecr4-full", + [ETHTOOL_LINK_MODE_100000baseLR4_ER4_Full_BIT] = "100000baselr4-er4-full", + [ETHTOOL_LINK_MODE_50000baseSR2_Full_BIT] = "50000basesr2-full", + [ETHTOOL_LINK_MODE_1000baseX_Full_BIT] = "1000basex-full", + [ETHTOOL_LINK_MODE_10000baseCR_Full_BIT] = "10000basecr-full", + [ETHTOOL_LINK_MODE_10000baseSR_Full_BIT] = "10000basesr-full", + [ETHTOOL_LINK_MODE_10000baseLR_Full_BIT] = "10000baselr-full", + [ETHTOOL_LINK_MODE_10000baseLRM_Full_BIT] = "10000baselrm-full", + [ETHTOOL_LINK_MODE_10000baseER_Full_BIT] = "10000baseer-full", + [ETHTOOL_LINK_MODE_2500baseT_Full_BIT] = "2500baset-full", + [ETHTOOL_LINK_MODE_5000baseT_Full_BIT] = "5000baset-full", + [ETHTOOL_LINK_MODE_FEC_NONE_BIT] = "fec-none", + [ETHTOOL_LINK_MODE_FEC_RS_BIT] = "fec-rs", + [ETHTOOL_LINK_MODE_FEC_BASER_BIT] = "fec-baser", +}; +/* Make sure the array is large enough to fit all bits */ +assert_cc((ELEMENTSOF(ethtool_link_mode_bit_table)-1) / 32 < ELEMENTSOF(((struct link_config){}).advertise)); + +DEFINE_STRING_TABLE_LOOKUP(ethtool_link_mode_bit, enum ethtool_link_mode_bit_indices); + +int ethtool_connect(int *ret) { + int fd; + + assert_return(ret, -EINVAL); + + fd = socket_ioctl_fd(); + if (fd < 0) + return fd; + + *ret = fd; + + return 0; +} + +int ethtool_get_driver(int *fd, const char *ifname, char **ret) { + struct ethtool_drvinfo ecmd = { + .cmd = ETHTOOL_GDRVINFO + }; + struct ifreq ifr = { + .ifr_data = (void*) &ecmd + }; + char *d; + int r; + + if (*fd < 0) { + r = ethtool_connect(fd); + if (r < 0) + return log_warning_errno(r, "link_config: could not connect to ethtool: %m"); + } + + strscpy(ifr.ifr_name, IFNAMSIZ, ifname); + + r = ioctl(*fd, SIOCETHTOOL, &ifr); + if (r < 0) + return -errno; + + d = strdup(ecmd.driver); + if (!d) + return -ENOMEM; + + *ret = d; + return 0; +} + +int ethtool_set_speed(int *fd, const char *ifname, unsigned speed, Duplex duplex) { + struct ethtool_cmd ecmd = { + .cmd = ETHTOOL_GSET + }; + struct ifreq ifr = { + .ifr_data = (void*) &ecmd + }; + bool need_update = false; + int r; + + if (speed == 0 && duplex == _DUP_INVALID) + return 0; + + if (*fd < 0) { + r = ethtool_connect(fd); + if (r < 0) + return log_warning_errno(r, "link_config: could not connect to ethtool: %m"); + } + + strscpy(ifr.ifr_name, IFNAMSIZ, ifname); + + r = ioctl(*fd, SIOCETHTOOL, &ifr); + if (r < 0) + return -errno; + + if (ethtool_cmd_speed(&ecmd) != speed) { + ethtool_cmd_speed_set(&ecmd, speed); + need_update = true; + } + + switch (duplex) { + case DUP_HALF: + if (ecmd.duplex != DUPLEX_HALF) { + ecmd.duplex = DUPLEX_HALF; + need_update = true; + } + break; + case DUP_FULL: + if (ecmd.duplex != DUPLEX_FULL) { + ecmd.duplex = DUPLEX_FULL; + need_update = true; + } + break; + default: + break; + } + + if (need_update) { + ecmd.cmd = ETHTOOL_SSET; + + r = ioctl(*fd, SIOCETHTOOL, &ifr); + if (r < 0) + return -errno; + } + + return 0; +} + +int ethtool_set_wol(int *fd, const char *ifname, WakeOnLan wol) { + struct ethtool_wolinfo ecmd = { + .cmd = ETHTOOL_GWOL + }; + struct ifreq ifr = { + .ifr_data = (void*) &ecmd + }; + bool need_update = false; + int r; + + if (wol == _WOL_INVALID) + return 0; + + if (*fd < 0) { + r = ethtool_connect(fd); + if (r < 0) + return log_warning_errno(r, "link_config: could not connect to ethtool: %m"); + } + + strscpy(ifr.ifr_name, IFNAMSIZ, ifname); + + r = ioctl(*fd, SIOCETHTOOL, &ifr); + if (r < 0) + return -errno; + + switch (wol) { + case WOL_PHY: + if (ecmd.wolopts != WAKE_PHY) { + ecmd.wolopts = WAKE_PHY; + need_update = true; + } + break; + case WOL_UCAST: + if (ecmd.wolopts != WAKE_UCAST) { + ecmd.wolopts = WAKE_UCAST; + need_update = true; + } + break; + case WOL_MCAST: + if (ecmd.wolopts != WAKE_MCAST) { + ecmd.wolopts = WAKE_MCAST; + need_update = true; + } + break; + case WOL_BCAST: + if (ecmd.wolopts != WAKE_BCAST) { + ecmd.wolopts = WAKE_BCAST; + need_update = true; + } + break; + case WOL_ARP: + if (ecmd.wolopts != WAKE_ARP) { + ecmd.wolopts = WAKE_ARP; + need_update = true; + } + break; + case WOL_MAGIC: + if (ecmd.wolopts != WAKE_MAGIC) { + ecmd.wolopts = WAKE_MAGIC; + need_update = true; + } + break; + case WOL_MAGICSECURE: + if (ecmd.wolopts != WAKE_MAGICSECURE) { + ecmd.wolopts = WAKE_MAGICSECURE; + need_update = true; + } + break; + case WOL_OFF: + if (ecmd.wolopts != 0) { + ecmd.wolopts = 0; + need_update = true; + } + break; + default: + break; + } + + if (need_update) { + ecmd.cmd = ETHTOOL_SWOL; + + r = ioctl(*fd, SIOCETHTOOL, &ifr); + if (r < 0) + return -errno; + } + + return 0; +} + +static int get_stringset(int fd, struct ifreq *ifr, int stringset_id, struct ethtool_gstrings **gstrings) { + _cleanup_free_ struct ethtool_gstrings *strings = NULL; + struct { + struct ethtool_sset_info info; + uint32_t space; + } buffer = { + .info = { + .cmd = ETHTOOL_GSSET_INFO, + .sset_mask = UINT64_C(1) << stringset_id, + }, + }; + unsigned len; + int r; + + ifr->ifr_data = (void *) &buffer.info; + + r = ioctl(fd, SIOCETHTOOL, ifr); + if (r < 0) + return -errno; + + if (!buffer.info.sset_mask) + return -EINVAL; + + len = buffer.info.data[0]; + + strings = malloc0(sizeof(struct ethtool_gstrings) + len * ETH_GSTRING_LEN); + if (!strings) + return -ENOMEM; + + strings->cmd = ETHTOOL_GSTRINGS; + strings->string_set = stringset_id; + strings->len = len; + + ifr->ifr_data = (void *) strings; + + r = ioctl(fd, SIOCETHTOOL, ifr); + if (r < 0) + return -errno; + + *gstrings = TAKE_PTR(strings); + + return 0; +} + +static int find_feature_index(struct ethtool_gstrings *strings, const char *feature) { + unsigned i; + + for (i = 0; i < strings->len; i++) { + if (streq((char *) &strings->data[i * ETH_GSTRING_LEN], feature)) + return i; + } + + return -1; +} + +int ethtool_set_features(int *fd, const char *ifname, int *features) { + _cleanup_free_ struct ethtool_gstrings *strings = NULL; + struct ethtool_sfeatures *sfeatures; + int block, bit, i, r; + struct ifreq ifr = {}; + + if (*fd < 0) { + r = ethtool_connect(fd); + if (r < 0) + return log_warning_errno(r, "link_config: could not connect to ethtool: %m"); + } + + strscpy(ifr.ifr_name, IFNAMSIZ, ifname); + + r = get_stringset(*fd, &ifr, ETH_SS_FEATURES, &strings); + if (r < 0) + return log_warning_errno(r, "link_config: could not get ethtool features for %s", ifname); + + sfeatures = alloca0(sizeof(struct ethtool_sfeatures) + DIV_ROUND_UP(strings->len, 32U) * sizeof(sfeatures->features[0])); + sfeatures->cmd = ETHTOOL_SFEATURES; + sfeatures->size = DIV_ROUND_UP(strings->len, 32U); + + for (i = 0; i < _NET_DEV_FEAT_MAX; i++) { + + if (features[i] != -1) { + + r = find_feature_index(strings, netdev_feature_table[i]); + if (r < 0) { + log_warning_errno(r, "link_config: could not find feature: %s", netdev_feature_table[i]); + continue; + } + + block = r / 32; + bit = r % 32; + + sfeatures->features[block].valid |= 1 << bit; + + if (features[i]) + sfeatures->features[block].requested |= 1 << bit; + else + sfeatures->features[block].requested &= ~(1 << bit); + } + } + + ifr.ifr_data = (void *) sfeatures; + + r = ioctl(*fd, SIOCETHTOOL, &ifr); + if (r < 0) + return log_warning_errno(r, "link_config: could not set ethtool features for %s", ifname); + + return 0; +} + +static int get_glinksettings(int fd, struct ifreq *ifr, struct ethtool_link_usettings **g) { + struct ecmd { + struct ethtool_link_settings req; + __u32 link_mode_data[3 * ETHTOOL_LINK_MODE_MASK_MAX_KERNEL_NU32]; + } ecmd = { + .req.cmd = ETHTOOL_GLINKSETTINGS, + }; + struct ethtool_link_usettings *u; + unsigned offset; + int r; + + /* The interaction user/kernel via the new API requires a small ETHTOOL_GLINKSETTINGS + handshake first to agree on the length of the link mode bitmaps. If kernel doesn't + agree with user, it returns the bitmap length it is expecting from user as a negative + length (and cmd field is 0). When kernel and user agree, kernel returns valid info in + all fields (ie. link mode length > 0 and cmd is ETHTOOL_GLINKSETTINGS). Based on + https://github.com/torvalds/linux/commit/3f1ac7a700d039c61d8d8b99f28d605d489a60cf + */ + + ifr->ifr_data = (void *) &ecmd; + + r = ioctl(fd, SIOCETHTOOL, ifr); + if (r < 0) + return -errno; + + if (ecmd.req.link_mode_masks_nwords >= 0 || ecmd.req.cmd != ETHTOOL_GLINKSETTINGS) + return -EOPNOTSUPP; + + ecmd.req.link_mode_masks_nwords = -ecmd.req.link_mode_masks_nwords; + + ifr->ifr_data = (void *) &ecmd; + + r = ioctl(fd, SIOCETHTOOL, ifr); + if (r < 0) + return -errno; + + if (ecmd.req.link_mode_masks_nwords <= 0 || ecmd.req.cmd != ETHTOOL_GLINKSETTINGS) + return -EOPNOTSUPP; + + u = new0(struct ethtool_link_usettings , 1); + if (!u) + return -ENOMEM; + + u->base = ecmd.req; + + offset = 0; + memcpy(u->link_modes.supported, &ecmd.link_mode_data[offset], 4 * ecmd.req.link_mode_masks_nwords); + + offset += ecmd.req.link_mode_masks_nwords; + memcpy(u->link_modes.advertising, &ecmd.link_mode_data[offset], 4 * ecmd.req.link_mode_masks_nwords); + + offset += ecmd.req.link_mode_masks_nwords; + memcpy(u->link_modes.lp_advertising, &ecmd.link_mode_data[offset], 4 * ecmd.req.link_mode_masks_nwords); + + *g = u; + + return 0; +} + +static int get_gset(int fd, struct ifreq *ifr, struct ethtool_link_usettings **u) { + struct ethtool_link_usettings *e; + struct ethtool_cmd ecmd = { + .cmd = ETHTOOL_GSET, + }; + int r; + + ifr->ifr_data = (void *) &ecmd; + + r = ioctl(fd, SIOCETHTOOL, ifr); + if (r < 0) + return -errno; + + e = new0(struct ethtool_link_usettings, 1); + if (!e) + return -ENOMEM; + + e->base.cmd = ETHTOOL_GSET; + + e->base.link_mode_masks_nwords = 1; + e->base.speed = ethtool_cmd_speed(&ecmd); + e->base.duplex = ecmd.duplex; + e->base.port = ecmd.port; + e->base.phy_address = ecmd.phy_address; + e->base.autoneg = ecmd.autoneg; + e->base.mdio_support = ecmd.mdio_support; + + e->link_modes.supported[0] = ecmd.supported; + e->link_modes.advertising[0] = ecmd.advertising; + e->link_modes.lp_advertising[0] = ecmd.lp_advertising; + + *u = e; + + return 0; +} + +static int set_slinksettings(int fd, struct ifreq *ifr, const struct ethtool_link_usettings *u) { + struct { + struct ethtool_link_settings req; + __u32 link_mode_data[3 * ETHTOOL_LINK_MODE_MASK_MAX_KERNEL_NU32]; + } ecmd = {}; + unsigned offset; + int r; + + if (u->base.cmd != ETHTOOL_GLINKSETTINGS || u->base.link_mode_masks_nwords <= 0) + return -EINVAL; + + ecmd.req = u->base; + ecmd.req.cmd = ETHTOOL_SLINKSETTINGS; + offset = 0; + memcpy(&ecmd.link_mode_data[offset], u->link_modes.supported, 4 * ecmd.req.link_mode_masks_nwords); + + offset += ecmd.req.link_mode_masks_nwords; + memcpy(&ecmd.link_mode_data[offset], u->link_modes.advertising, 4 * ecmd.req.link_mode_masks_nwords); + + offset += ecmd.req.link_mode_masks_nwords; + memcpy(&ecmd.link_mode_data[offset], u->link_modes.lp_advertising, 4 * ecmd.req.link_mode_masks_nwords); + + ifr->ifr_data = (void *) &ecmd; + + r = ioctl(fd, SIOCETHTOOL, ifr); + if (r < 0) + return -errno; + + return 0; +} + +static int set_sset(int fd, struct ifreq *ifr, const struct ethtool_link_usettings *u) { + struct ethtool_cmd ecmd = { + .cmd = ETHTOOL_SSET, + }; + int r; + + if (u->base.cmd != ETHTOOL_GSET || u->base.link_mode_masks_nwords <= 0) + return -EINVAL; + + ecmd.supported = u->link_modes.supported[0]; + ecmd.advertising = u->link_modes.advertising[0]; + ecmd.lp_advertising = u->link_modes.lp_advertising[0]; + + ethtool_cmd_speed_set(&ecmd, u->base.speed); + + ecmd.duplex = u->base.duplex; + ecmd.port = u->base.port; + ecmd.phy_address = u->base.phy_address; + ecmd.autoneg = u->base.autoneg; + ecmd.mdio_support = u->base.mdio_support; + ecmd.eth_tp_mdix = u->base.eth_tp_mdix; + ecmd.eth_tp_mdix_ctrl = u->base.eth_tp_mdix_ctrl; + + ifr->ifr_data = (void *) &ecmd; + + r = ioctl(fd, SIOCETHTOOL, ifr); + if (r < 0) + return -errno; + + return 0; +} + +/* If autonegotiation is disabled, the speed and duplex represent the fixed link + * mode and are writable if the driver supports multiple link modes. If it is + * enabled then they are read-only. If the link is up they represent the negotiated + * link mode; if the link is down, the speed is 0, %SPEED_UNKNOWN or the highest + * enabled speed and @duplex is %DUPLEX_UNKNOWN or the best enabled duplex mode. + */ +int ethtool_set_glinksettings(int *fd, const char *ifname, struct link_config *link) { + _cleanup_free_ struct ethtool_link_usettings *u = NULL; + struct ifreq ifr = {}; + int r; + + if (link->autonegotiation != AUTONEG_DISABLE && eqzero(link->advertise)) { + log_info("link_config: autonegotiation is unset or enabled, the speed and duplex are not writable."); + return 0; + } + + if (*fd < 0) { + r = ethtool_connect(fd); + if (r < 0) + return log_warning_errno(r, "link_config: could not connect to ethtool: %m"); + } + + strscpy(ifr.ifr_name, IFNAMSIZ, ifname); + + r = get_glinksettings(*fd, &ifr, &u); + if (r < 0) { + r = get_gset(*fd, &ifr, &u); + if (r < 0) + return log_warning_errno(r, "link_config: Cannot get device settings for %s : %m", ifname); + } + + if (link->speed) + u->base.speed = DIV_ROUND_UP(link->speed, 1000000); + + if (link->duplex != _DUP_INVALID) + u->base.duplex = link->duplex; + + if (link->port != _NET_DEV_PORT_INVALID) + u->base.port = link->port; + + if (link->autonegotiation >= 0) + u->base.autoneg = link->autonegotiation; + + if (!eqzero(link->advertise)) { + u->base.autoneg = AUTONEG_ENABLE; + memcpy(&u->link_modes.advertising, link->advertise, sizeof(link->advertise)); + memzero((uint8_t*) &u->link_modes.advertising + sizeof(link->advertise), + ETHTOOL_LINK_MODE_MASK_MAX_KERNEL_NBYTES - sizeof(link->advertise)); + } + + if (u->base.cmd == ETHTOOL_GLINKSETTINGS) + r = set_slinksettings(*fd, &ifr, u); + else + r = set_sset(*fd, &ifr, u); + if (r < 0) + return log_warning_errno(r, "link_config: Cannot set device settings for %s : %m", ifname); + + return r; +} + +int config_parse_channel(const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + link_config *config = data; + uint32_t k; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + r = safe_atou32(rvalue, &k); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse channel value, ignoring: %s", rvalue); + return 0; + } + + if (k < 1) { + log_syntax(unit, LOG_ERR, filename, line, -EINVAL, "Invalid %s value, ignoring: %s", lvalue, rvalue); + return 0; + } + + if (streq(lvalue, "RxChannels")) { + config->channels.rx_count = k; + config->channels.rx_count_set = true; + } else if (streq(lvalue, "TxChannels")) { + config->channels.tx_count = k; + config->channels.tx_count_set = true; + } else if (streq(lvalue, "OtherChannels")) { + config->channels.other_count = k; + config->channels.other_count_set = true; + } else if (streq(lvalue, "CombinedChannels")) { + config->channels.combined_count = k; + config->channels.combined_count_set = true; + } + + return 0; +} + +int ethtool_set_channels(int *fd, const char *ifname, netdev_channels *channels) { + struct ethtool_channels ecmd = { + .cmd = ETHTOOL_GCHANNELS + }; + struct ifreq ifr = { + .ifr_data = (void*) &ecmd + }; + + bool need_update = false; + int r; + + if (*fd < 0) { + r = ethtool_connect(fd); + if (r < 0) + return log_warning_errno(r, "link_config: could not connect to ethtool: %m"); + } + + strscpy(ifr.ifr_name, IFNAMSIZ, ifname); + + r = ioctl(*fd, SIOCETHTOOL, &ifr); + if (r < 0) + return -errno; + + if (channels->rx_count_set && ecmd.rx_count != channels->rx_count) { + ecmd.rx_count = channels->rx_count; + need_update = true; + } + + if (channels->tx_count_set && ecmd.tx_count != channels->tx_count) { + ecmd.tx_count = channels->tx_count; + need_update = true; + } + + if (channels->other_count_set && ecmd.other_count != channels->other_count) { + ecmd.other_count = channels->other_count; + need_update = true; + } + + if (channels->combined_count_set && ecmd.combined_count != channels->combined_count) { + ecmd.combined_count = channels->combined_count; + need_update = true; + } + + if (need_update) { + ecmd.cmd = ETHTOOL_SCHANNELS; + + r = ioctl(*fd, SIOCETHTOOL, &ifr); + if (r < 0) + return -errno; + } + + return 0; +} + +int config_parse_advertise(const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + link_config *config = data; + const char *p; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + if (isempty(rvalue)) { + /* Empty string resets the value. */ + zero(config->advertise); + return 0; + } + + for (p = rvalue;;) { + _cleanup_free_ char *w = NULL; + enum ethtool_link_mode_bit_indices mode; + + r = extract_first_word(&p, &w, NULL, 0); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to split advertise modes '%s', ignoring: %m", rvalue); + break; + } + if (r == 0) + break; + + mode = ethtool_link_mode_bit_from_string(w); + if (mode < 0) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse advertise mode, ignoring: %s", w); + continue; + } + + config->advertise[mode / 32] |= 1UL << (mode % 32); + } + + return 0; +} diff --git a/src/udev/net/ethtool-util.h b/src/udev/net/ethtool-util.h new file mode 100644 index 0000000..618b26b --- /dev/null +++ b/src/udev/net/ethtool-util.h @@ -0,0 +1,108 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ +#pragma once + +#include <macro.h> +#include <linux/ethtool.h> + +#include "conf-parser.h" +#include "missing_network.h" + +struct link_config; + +/* we can't use DUPLEX_ prefix, as it + * clashes with <linux/ethtool.h> */ +typedef enum Duplex { + DUP_HALF = DUPLEX_HALF, + DUP_FULL = DUPLEX_FULL, + _DUP_MAX, + _DUP_INVALID = -1 +} Duplex; + +typedef enum WakeOnLan { + WOL_PHY, + WOL_UCAST, + WOL_MCAST, + WOL_BCAST, + WOL_ARP, + WOL_MAGIC, + WOL_MAGICSECURE, + WOL_OFF, + _WOL_MAX, + _WOL_INVALID = -1 +} WakeOnLan; + +typedef enum NetDevFeature { + NET_DEV_FEAT_GSO, + NET_DEV_FEAT_GRO, + NET_DEV_FEAT_LRO, + NET_DEV_FEAT_TSO, + NET_DEV_FEAT_TSO6, + _NET_DEV_FEAT_MAX, + _NET_DEV_FEAT_INVALID = -1 +} NetDevFeature; + +typedef enum NetDevPort { + NET_DEV_PORT_TP = PORT_TP, + NET_DEV_PORT_AUI = PORT_AUI, + NET_DEV_PORT_MII = PORT_MII, + NET_DEV_PORT_FIBRE = PORT_FIBRE, + NET_DEV_PORT_BNC = PORT_BNC, + NET_DEV_PORT_DA = PORT_DA, + NET_DEV_PORT_NONE = PORT_NONE, + NET_DEV_PORT_OTHER = PORT_OTHER, + _NET_DEV_PORT_MAX, + _NET_DEV_PORT_INVALID = -1 +} NetDevPort; + +#define ETHTOOL_LINK_MODE_MASK_MAX_KERNEL_NU32 (SCHAR_MAX) +#define ETHTOOL_LINK_MODE_MASK_MAX_KERNEL_NBYTES (4 * ETHTOOL_LINK_MODE_MASK_MAX_KERNEL_NU32) + +/* layout of the struct passed from/to userland */ +struct ethtool_link_usettings { + struct ethtool_link_settings base; + + struct { + uint32_t supported[ETHTOOL_LINK_MODE_MASK_MAX_KERNEL_NU32]; + uint32_t advertising[ETHTOOL_LINK_MODE_MASK_MAX_KERNEL_NU32]; + uint32_t lp_advertising[ETHTOOL_LINK_MODE_MASK_MAX_KERNEL_NU32]; + } link_modes; +}; + +typedef struct netdev_channels { + uint32_t rx_count; + uint32_t tx_count; + uint32_t other_count; + uint32_t combined_count; + + bool rx_count_set; + bool tx_count_set; + bool other_count_set; + bool combined_count_set; +} netdev_channels; + +int ethtool_connect(int *ret); + +int ethtool_get_driver(int *fd, const char *ifname, char **ret); +int ethtool_set_speed(int *fd, const char *ifname, unsigned speed, Duplex duplex); +int ethtool_set_wol(int *fd, const char *ifname, WakeOnLan wol); +int ethtool_set_features(int *fd, const char *ifname, int *features); +int ethtool_set_glinksettings(int *fd, const char *ifname, struct link_config *link); +int ethtool_set_channels(int *fd, const char *ifname, netdev_channels *channels); + +const char *duplex_to_string(Duplex d) _const_; +Duplex duplex_from_string(const char *d) _pure_; + +const char *wol_to_string(WakeOnLan wol) _const_; +WakeOnLan wol_from_string(const char *wol) _pure_; + +const char *port_to_string(NetDevPort port) _const_; +NetDevPort port_from_string(const char *port) _pure_; + +const char *ethtool_link_mode_bit_to_string(enum ethtool_link_mode_bit_indices val) _const_; +enum ethtool_link_mode_bit_indices ethtool_link_mode_bit_from_string(const char *str) _pure_; + +CONFIG_PARSER_PROTOTYPE(config_parse_duplex); +CONFIG_PARSER_PROTOTYPE(config_parse_wol); +CONFIG_PARSER_PROTOTYPE(config_parse_port); +CONFIG_PARSER_PROTOTYPE(config_parse_channel); +CONFIG_PARSER_PROTOTYPE(config_parse_advertise); diff --git a/src/udev/net/link-config-gperf.gperf b/src/udev/net/link-config-gperf.gperf new file mode 100644 index 0000000..2bc18bf --- /dev/null +++ b/src/udev/net/link-config-gperf.gperf @@ -0,0 +1,54 @@ +%{ +#if __GNUC__ >= 7 +_Pragma("GCC diagnostic ignored \"-Wimplicit-fallthrough\"") +#endif +#include <stddef.h> +#include "conf-parser.h" +#include "ethtool-util.h" +#include "link-config.h" +#include "network-internal.h" +%} +struct ConfigPerfItem; +%null_strings +%language=ANSI-C +%define slot-name section_and_lvalue +%define hash-function-name link_config_gperf_hash +%define lookup-function-name link_config_gperf_lookup +%readonly-tables +%omit-struct-type +%struct-type +%includes +%% +Match.MACAddress, config_parse_hwaddrs, 0, offsetof(link_config, match_mac) +Match.OriginalName, config_parse_ifnames, 0, offsetof(link_config, match_name) +Match.Path, config_parse_strv, 0, offsetof(link_config, match_path) +Match.Driver, config_parse_strv, 0, offsetof(link_config, match_driver) +Match.Type, config_parse_strv, 0, offsetof(link_config, match_type) +Match.Host, config_parse_net_condition, CONDITION_HOST, offsetof(link_config, match_host) +Match.Virtualization, config_parse_net_condition, CONDITION_VIRTUALIZATION, offsetof(link_config, match_virt) +Match.KernelCommandLine, config_parse_net_condition, CONDITION_KERNEL_COMMAND_LINE, offsetof(link_config, match_kernel_cmdline) +Match.KernelVersion, config_parse_net_condition, CONDITION_KERNEL_VERSION, offsetof(link_config, match_kernel_version) +Match.Architecture, config_parse_net_condition, CONDITION_ARCHITECTURE, offsetof(link_config, match_arch) +Link.Description, config_parse_string, 0, offsetof(link_config, description) +Link.MACAddressPolicy, config_parse_mac_policy, 0, offsetof(link_config, mac_policy) +Link.MACAddress, config_parse_hwaddr, 0, offsetof(link_config, mac) +Link.NamePolicy, config_parse_name_policy, 0, offsetof(link_config, name_policy) +Link.Name, config_parse_ifname, 0, offsetof(link_config, name) +Link.Alias, config_parse_ifalias, 0, offsetof(link_config, alias) +Link.MTUBytes, config_parse_mtu, AF_UNSPEC, offsetof(link_config, mtu) +Link.BitsPerSecond, config_parse_si_size, 0, offsetof(link_config, speed) +Link.Duplex, config_parse_duplex, 0, offsetof(link_config, duplex) +Link.AutoNegotiation, config_parse_tristate, 0, offsetof(link_config, autonegotiation) +Link.WakeOnLan, config_parse_wol, 0, offsetof(link_config, wol) +Link.Port, config_parse_port, 0, offsetof(link_config, port) +Link.GenericSegmentationOffload, config_parse_tristate, 0, offsetof(link_config, features[NET_DEV_FEAT_GSO]) +Link.TCPSegmentationOffload, config_parse_tristate, 0, offsetof(link_config, features[NET_DEV_FEAT_TSO]) +Link.TCP6SegmentationOffload, config_parse_tristate, 0, offsetof(link_config, features[NET_DEV_FEAT_TSO6]) +Link.UDPSegmentationOffload, config_parse_warn_compat, DISABLED_LEGACY, 0 +Link.GenericReceiveOffload, config_parse_tristate, 0, offsetof(link_config, features[NET_DEV_FEAT_GRO]) +Link.LargeReceiveOffload, config_parse_tristate, 0, offsetof(link_config, features[NET_DEV_FEAT_LRO]) +Link.RxChannels, config_parse_channel, 0, 0 +Link.TxChannels, config_parse_channel, 0, 0 +Link.OtherChannels, config_parse_channel, 0, 0 +Link.CombinedChannels, config_parse_channel, 0, 0 +Link.Advertise, config_parse_advertise, 0, 0 diff --git a/src/udev/net/link-config.c b/src/udev/net/link-config.c new file mode 100644 index 0000000..eb2477c --- /dev/null +++ b/src/udev/net/link-config.c @@ -0,0 +1,535 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ + +#include <netinet/ether.h> + +#include "sd-device.h" +#include "sd-netlink.h" + +#include "alloc-util.h" +#include "conf-files.h" +#include "conf-parser.h" +#include "device-util.h" +#include "ethtool-util.h" +#include "fd-util.h" +#include "link-config.h" +#include "log.h" +#include "missing_network.h" +#include "naming-scheme.h" +#include "netlink-util.h" +#include "network-internal.h" +#include "parse-util.h" +#include "path-util.h" +#include "proc-cmdline.h" +#include "random-util.h" +#include "stat-util.h" +#include "string-table.h" +#include "string-util.h" +#include "strv.h" +#include "util.h" + +struct link_config_ctx { + LIST_HEAD(link_config, links); + + int ethtool_fd; + + bool enable_name_policy; + + sd_netlink *rtnl; + + usec_t link_dirs_ts_usec; +}; + +static const char* const link_dirs[] = { + "/etc/systemd/network", + "/run/systemd/network", + "/usr/lib/systemd/network", +#if HAVE_SPLIT_USR + "/lib/systemd/network", +#endif + NULL}; + +static void link_config_free(link_config *link) { + if (!link) + return; + + free(link->filename); + + set_free_free(link->match_mac); + strv_free(link->match_path); + strv_free(link->match_driver); + strv_free(link->match_type); + free(link->match_name); + free(link->match_host); + free(link->match_virt); + free(link->match_kernel_cmdline); + free(link->match_kernel_version); + free(link->match_arch); + + free(link->description); + free(link->mac); + free(link->name_policy); + free(link->name); + free(link->alias); + + free(link); +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(link_config*, link_config_free); + +static void link_configs_free(link_config_ctx *ctx) { + link_config *link, *link_next; + + if (!ctx) + return; + + LIST_FOREACH_SAFE(links, link, link_next, ctx->links) + link_config_free(link); +} + +void link_config_ctx_free(link_config_ctx *ctx) { + if (!ctx) + return; + + safe_close(ctx->ethtool_fd); + + sd_netlink_unref(ctx->rtnl); + + link_configs_free(ctx); + + free(ctx); + + return; +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(link_config_ctx*, link_config_ctx_free); + +int link_config_ctx_new(link_config_ctx **ret) { + _cleanup_(link_config_ctx_freep) link_config_ctx *ctx = NULL; + + if (!ret) + return -EINVAL; + + ctx = new0(link_config_ctx, 1); + if (!ctx) + return -ENOMEM; + + LIST_HEAD_INIT(ctx->links); + + ctx->ethtool_fd = -1; + + ctx->enable_name_policy = true; + + *ret = TAKE_PTR(ctx); + + return 0; +} + +static int load_link(link_config_ctx *ctx, const char *filename) { + _cleanup_(link_config_freep) link_config *link = NULL; + _cleanup_fclose_ FILE *file = NULL; + int i; + int r; + + assert(ctx); + assert(filename); + + file = fopen(filename, "re"); + if (!file) { + if (errno == ENOENT) + return 0; + else + return -errno; + } + + if (null_or_empty_fd(fileno(file))) { + log_debug("Skipping empty file: %s", filename); + return 0; + } + + link = new0(link_config, 1); + if (!link) + return log_oom(); + + link->mac_policy = _MACPOLICY_INVALID; + link->wol = _WOL_INVALID; + link->duplex = _DUP_INVALID; + link->port = _NET_DEV_PORT_INVALID; + link->autonegotiation = -1; + + for (i = 0; i < (int)ELEMENTSOF(link->features); i++) + link->features[i] = -1; + + r = config_parse(NULL, filename, file, + "Match\0Link\0Ethernet\0", + config_item_perf_lookup, link_config_gperf_lookup, + CONFIG_PARSE_WARN, link); + if (r < 0) + return r; + else + log_debug("Parsed configuration file %s", filename); + + if (link->speed > UINT_MAX) + return -ERANGE; + + link->filename = strdup(filename); + if (!link->filename) + return log_oom(); + + LIST_PREPEND(links, ctx->links, link); + link = NULL; + + return 0; +} + +static bool enable_name_policy(void) { + bool b; + + return proc_cmdline_get_bool("net.ifnames", &b) <= 0 || b; +} + +static int link_name_type(sd_device *device, unsigned *type) { + const char *s; + int r; + + r = sd_device_get_sysattr_value(device, "name_assign_type", &s); + if (r < 0) + return log_device_debug_errno(device, r, "Failed to query name_assign_type: %m"); + + r = safe_atou(s, type); + if (r < 0) + return log_device_warning_errno(device, r, "Failed to parse name_assign_type \"%s\": %m", s); + + log_device_debug(device, "Device has name_assign_type=%d", *type); + return 0; +} + +int link_config_load(link_config_ctx *ctx) { + _cleanup_strv_free_ char **files; + char **f; + int r; + + link_configs_free(ctx); + + if (!enable_name_policy()) { + ctx->enable_name_policy = false; + log_info("Network interface NamePolicy= disabled on kernel command line, ignoring."); + } + + /* update timestamp */ + paths_check_timestamp(link_dirs, &ctx->link_dirs_ts_usec, true); + + r = conf_files_list_strv(&files, ".link", NULL, 0, link_dirs); + if (r < 0) + return log_error_errno(r, "failed to enumerate link files: %m"); + + STRV_FOREACH_BACKWARDS(f, files) { + r = load_link(ctx, *f); + if (r < 0) + return r; + } + + return 0; +} + +bool link_config_should_reload(link_config_ctx *ctx) { + return paths_check_timestamp(link_dirs, &ctx->link_dirs_ts_usec, false); +} + +int link_config_get(link_config_ctx *ctx, sd_device *device, link_config **ret) { + link_config *link; + + assert(ctx); + assert(device); + assert(ret); + + LIST_FOREACH(links, link, ctx->links) { + const char *address = NULL, *id_path = NULL, *parent_driver = NULL, *id_net_driver = NULL, *devtype = NULL, *sysname = NULL; + sd_device *parent; + + (void) sd_device_get_sysattr_value(device, "address", &address); + (void) sd_device_get_property_value(device, "ID_PATH", &id_path); + if (sd_device_get_parent(device, &parent) >= 0) + (void) sd_device_get_driver(parent, &parent_driver); + (void) sd_device_get_property_value(device, "ID_NET_DRIVER", &id_net_driver); + (void) sd_device_get_devtype(device, &devtype); + (void) sd_device_get_sysname(device, &sysname); + + if (net_match_config(link->match_mac, link->match_path, link->match_driver, + link->match_type, link->match_name, link->match_host, + link->match_virt, link->match_kernel_cmdline, + link->match_kernel_version, link->match_arch, + address ? ether_aton(address) : NULL, + id_path, + parent_driver, + id_net_driver, + devtype, + sysname)) { + if (link->match_name) { + unsigned char name_assign_type = NET_NAME_UNKNOWN; + const char *attr_value; + + if (sd_device_get_sysattr_value(device, "name_assign_type", &attr_value) >= 0) + (void) safe_atou8(attr_value, &name_assign_type); + + if (name_assign_type == NET_NAME_ENUM) { + log_warning("Config file %s applies to device based on potentially unpredictable interface name '%s'", + link->filename, sysname); + *ret = link; + + return 0; + } else if (name_assign_type == NET_NAME_RENAMED) { + log_warning("Config file %s matches device based on renamed interface name '%s', ignoring", + link->filename, sysname); + + continue; + } + } + + log_debug("Config file %s applies to device %s", + link->filename, sysname); + + *ret = link; + return 0; + } + } + + *ret = NULL; + return -ENOENT; +} + +static bool mac_is_random(sd_device *device) { + const char *s; + unsigned type; + int r; + + /* if we can't get the assign type, assume it is not random */ + if (sd_device_get_sysattr_value(device, "addr_assign_type", &s) < 0) + return false; + + r = safe_atou(s, &type); + if (r < 0) + return false; + + return type == NET_ADDR_RANDOM; +} + +static int get_mac(sd_device *device, bool want_random, + struct ether_addr *mac) { + int r; + + if (want_random) + random_bytes(mac->ether_addr_octet, ETH_ALEN); + else { + uint64_t result; + + r = net_get_unique_predictable_data(device, &result); + if (r < 0) + return r; + + assert_cc(ETH_ALEN <= sizeof(result)); + memcpy(mac->ether_addr_octet, &result, ETH_ALEN); + } + + /* see eth_random_addr in the kernel */ + mac->ether_addr_octet[0] &= 0xfe; /* clear multicast bit */ + mac->ether_addr_octet[0] |= 0x02; /* set local assignment bit (IEEE802) */ + + return 0; +} + +int link_config_apply(link_config_ctx *ctx, link_config *config, + sd_device *device, const char **name) { + struct ether_addr generated_mac; + struct ether_addr *mac = NULL; + const char *new_name = NULL; + const char *old_name; + unsigned speed, name_type = NET_NAME_UNKNOWN; + NamePolicy policy; + int r, ifindex; + + assert(ctx); + assert(config); + assert(device); + assert(name); + + r = sd_device_get_sysname(device, &old_name); + if (r < 0) + return r; + + r = ethtool_set_glinksettings(&ctx->ethtool_fd, old_name, config); + if (r < 0) { + + if (config->port != _NET_DEV_PORT_INVALID) + log_warning_errno(r, "Could not set port (%s) of %s: %m", port_to_string(config->port), old_name); + + if (!eqzero(config->advertise)) + log_warning_errno(r, "Could not set advertise mode: %m"); /* TODO: include modes in the log message. */ + + if (config->speed) { + speed = DIV_ROUND_UP(config->speed, 1000000); + if (r == -EOPNOTSUPP) { + r = ethtool_set_speed(&ctx->ethtool_fd, old_name, speed, config->duplex); + if (r < 0) + log_warning_errno(r, "Could not set speed of %s to %u Mbps: %m", old_name, speed); + } + } + + if (config->duplex !=_DUP_INVALID) + log_warning_errno(r, "Could not set duplex of %s to (%s): %m", old_name, duplex_to_string(config->duplex)); + } + + r = ethtool_set_wol(&ctx->ethtool_fd, old_name, config->wol); + if (r < 0) + log_warning_errno(r, "Could not set WakeOnLan of %s to %s: %m", + old_name, wol_to_string(config->wol)); + + r = ethtool_set_features(&ctx->ethtool_fd, old_name, config->features); + if (r < 0) + log_warning_errno(r, "Could not set offload features of %s: %m", old_name); + + if (config->channels.rx_count_set || config->channels.tx_count_set || config->channels.other_count_set || config->channels.combined_count_set) { + r = ethtool_set_channels(&ctx->ethtool_fd, old_name, &config->channels); + if (r < 0) + log_warning_errno(r, "Could not set channels of %s: %m", old_name); + } + + r = sd_device_get_ifindex(device, &ifindex); + if (r < 0) + return log_device_warning_errno(device, r, "Could not find ifindex: %m"); + + + (void) link_name_type(device, &name_type); + + if (IN_SET(name_type, NET_NAME_USER, NET_NAME_RENAMED) + && !naming_scheme_has(NAMING_ALLOW_RERENAMES)) { + log_device_debug(device, "Device already has a name given by userspace, not renaming."); + goto no_rename; + } + + if (ctx->enable_name_policy && config->name_policy) + for (NamePolicy *p = config->name_policy; !new_name && *p != _NAMEPOLICY_INVALID; p++) { + policy = *p; + + switch (policy) { + case NAMEPOLICY_KERNEL: + if (name_type != NET_NAME_PREDICTABLE) + continue; + + /* The kernel claims to have given a predictable name, keep it. */ + log_device_debug(device, "Policy *%s*: keeping predictable kernel name", + name_policy_to_string(policy)); + goto no_rename; + case NAMEPOLICY_KEEP: + if (!IN_SET(name_type, NET_NAME_USER, NET_NAME_RENAMED)) + continue; + + log_device_debug(device, "Policy *%s*: keeping existing userspace name", + name_policy_to_string(policy)); + goto no_rename; + case NAMEPOLICY_DATABASE: + (void) sd_device_get_property_value(device, "ID_NET_NAME_FROM_DATABASE", &new_name); + break; + case NAMEPOLICY_ONBOARD: + (void) sd_device_get_property_value(device, "ID_NET_NAME_ONBOARD", &new_name); + break; + case NAMEPOLICY_SLOT: + (void) sd_device_get_property_value(device, "ID_NET_NAME_SLOT", &new_name); + break; + case NAMEPOLICY_PATH: + (void) sd_device_get_property_value(device, "ID_NET_NAME_PATH", &new_name); + break; + case NAMEPOLICY_MAC: + (void) sd_device_get_property_value(device, "ID_NET_NAME_MAC", &new_name); + break; + default: + assert_not_reached("invalid policy"); + } + } + + if (new_name) + log_device_debug(device, "Policy *%s* yields \"%s\".", name_policy_to_string(policy), new_name); + else if (config->name) { + new_name = config->name; + log_device_debug(device, "Policies didn't yield a name, using specified Name=%s.", new_name); + } else + log_device_debug(device, "Policies didn't yield a name and Name= is not given, not renaming."); + no_rename: + + switch (config->mac_policy) { + case MACPOLICY_PERSISTENT: + if (mac_is_random(device)) { + r = get_mac(device, false, &generated_mac); + if (r == -ENOENT) { + log_warning_errno(r, "Could not generate persistent MAC address for %s: %m", old_name); + break; + } else if (r < 0) + return r; + mac = &generated_mac; + } + break; + case MACPOLICY_RANDOM: + if (!mac_is_random(device)) { + r = get_mac(device, true, &generated_mac); + if (r == -ENOENT) { + log_warning_errno(r, "Could not generate random MAC address for %s: %m", old_name); + break; + } else if (r < 0) + return r; + mac = &generated_mac; + } + break; + case MACPOLICY_NONE: + default: + mac = config->mac; + } + + r = rtnl_set_link_properties(&ctx->rtnl, ifindex, config->alias, mac, config->mtu); + if (r < 0) + return log_warning_errno(r, "Could not set Alias=, MACAddress= or MTU= on %s: %m", old_name); + + *name = new_name; + + return 0; +} + +int link_get_driver(link_config_ctx *ctx, sd_device *device, char **ret) { + const char *name; + char *driver = NULL; + int r; + + r = sd_device_get_sysname(device, &name); + if (r < 0) + return r; + + r = ethtool_get_driver(&ctx->ethtool_fd, name, &driver); + if (r < 0) + return r; + + *ret = driver; + return 0; +} + +static const char* const mac_policy_table[_MACPOLICY_MAX] = { + [MACPOLICY_PERSISTENT] = "persistent", + [MACPOLICY_RANDOM] = "random", + [MACPOLICY_NONE] = "none", +}; + +DEFINE_STRING_TABLE_LOOKUP(mac_policy, MACPolicy); +DEFINE_CONFIG_PARSE_ENUM(config_parse_mac_policy, mac_policy, MACPolicy, + "Failed to parse MAC address policy"); + +static const char* const name_policy_table[_NAMEPOLICY_MAX] = { + [NAMEPOLICY_KERNEL] = "kernel", + [NAMEPOLICY_KEEP] = "keep", + [NAMEPOLICY_DATABASE] = "database", + [NAMEPOLICY_ONBOARD] = "onboard", + [NAMEPOLICY_SLOT] = "slot", + [NAMEPOLICY_PATH] = "path", + [NAMEPOLICY_MAC] = "mac", +}; + +DEFINE_STRING_TABLE_LOOKUP(name_policy, NamePolicy); +DEFINE_CONFIG_PARSE_ENUMV(config_parse_name_policy, name_policy, NamePolicy, + _NAMEPOLICY_INVALID, + "Failed to parse interface name policy"); diff --git a/src/udev/net/link-config.h b/src/udev/net/link-config.h new file mode 100644 index 0000000..1113b10 --- /dev/null +++ b/src/udev/net/link-config.h @@ -0,0 +1,88 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ +#pragma once + +#include "sd-device.h" + +#include "condition.h" +#include "conf-parser.h" +#include "ethtool-util.h" +#include "list.h" +#include "set.h" + +typedef struct link_config_ctx link_config_ctx; +typedef struct link_config link_config; + +typedef enum MACPolicy { + MACPOLICY_PERSISTENT, + MACPOLICY_RANDOM, + MACPOLICY_NONE, + _MACPOLICY_MAX, + _MACPOLICY_INVALID = -1 +} MACPolicy; + +typedef enum NamePolicy { + NAMEPOLICY_KERNEL, + NAMEPOLICY_KEEP, + NAMEPOLICY_DATABASE, + NAMEPOLICY_ONBOARD, + NAMEPOLICY_SLOT, + NAMEPOLICY_PATH, + NAMEPOLICY_MAC, + _NAMEPOLICY_MAX, + _NAMEPOLICY_INVALID = -1 +} NamePolicy; + +struct link_config { + char *filename; + + Set *match_mac; + char **match_path; + char **match_driver; + char **match_type; + char **match_name; + Condition *match_host; + Condition *match_virt; + Condition *match_kernel_cmdline; + Condition *match_kernel_version; + Condition *match_arch; + + char *description; + struct ether_addr *mac; + MACPolicy mac_policy; + NamePolicy *name_policy; + char *name; + char *alias; + uint32_t mtu; + size_t speed; + Duplex duplex; + int autonegotiation; + uint32_t advertise[2]; + WakeOnLan wol; + NetDevPort port; + int features[_NET_DEV_FEAT_MAX]; + netdev_channels channels; + + LIST_FIELDS(link_config, links); +}; + +int link_config_ctx_new(link_config_ctx **ret); +void link_config_ctx_free(link_config_ctx *ctx); + +int link_config_load(link_config_ctx *ctx); +bool link_config_should_reload(link_config_ctx *ctx); + +int link_config_get(link_config_ctx *ctx, sd_device *device, struct link_config **ret); +int link_config_apply(link_config_ctx *ctx, struct link_config *config, sd_device *device, const char **name); +int link_get_driver(link_config_ctx *ctx, sd_device *device, char **ret); + +const char *name_policy_to_string(NamePolicy p) _const_; +NamePolicy name_policy_from_string(const char *p) _pure_; + +const char *mac_policy_to_string(MACPolicy p) _const_; +MACPolicy mac_policy_from_string(const char *p) _pure_; + +/* gperf lookup function */ +const struct ConfigPerfItem* link_config_gperf_lookup(const char *key, GPERF_LEN_TYPE length); + +CONFIG_PARSER_PROTOTYPE(config_parse_mac_policy); +CONFIG_PARSER_PROTOTYPE(config_parse_name_policy); diff --git a/src/udev/net/naming-scheme.c b/src/udev/net/naming-scheme.c new file mode 100644 index 0000000..27cede5 --- /dev/null +++ b/src/udev/net/naming-scheme.c @@ -0,0 +1,64 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ +#include "alloc-util.h" +#include "naming-scheme.h" +#include "proc-cmdline.h" +#include "string-util.h" + +static const NamingScheme naming_schemes[] = { + { "v238", NAMING_V238 }, + { "v239", NAMING_V239 }, + { "v240", NAMING_V240 }, + /* … add more schemes here, as the logic to name devices is updated … */ +}; + +static const NamingScheme* naming_scheme_from_name(const char *name) { + size_t i; + + if (streq(name, "latest")) + return naming_schemes + ELEMENTSOF(naming_schemes) - 1; + + for (i = 0; i < ELEMENTSOF(naming_schemes); i++) + if (streq(naming_schemes[i].name, name)) + return naming_schemes + i; + + return NULL; +} + +const NamingScheme* naming_scheme(void) { + static const NamingScheme *cache = NULL; + _cleanup_free_ char *buffer = NULL; + const char *e, *k; + + if (cache) + return cache; + + /* Acquire setting from the kernel command line */ + (void) proc_cmdline_get_key("net.naming-scheme", 0, &buffer); + + /* Also acquire it from an env var */ + e = getenv("NET_NAMING_SCHEME"); + if (e) { + if (*e == ':') { + /* If prefixed with ':' the kernel cmdline takes precedence */ + k = buffer ?: e + 1; + } else + k = e; /* Otherwise the env var takes precedence */ + } else + k = buffer; + + if (k) { + cache = naming_scheme_from_name(k); + if (cache) { + log_info("Using interface naming scheme '%s'.", cache->name); + return cache; + } + + log_warning("Unknown interface naming scheme '%s' requested, ignoring.", k); + } + + cache = naming_scheme_from_name(DEFAULT_NET_NAMING_SCHEME); + assert(cache); + log_info("Using default interface naming scheme '%s'.", cache->name); + + return cache; +} diff --git a/src/udev/net/naming-scheme.h b/src/udev/net/naming-scheme.h new file mode 100644 index 0000000..0b3d9bf --- /dev/null +++ b/src/udev/net/naming-scheme.h @@ -0,0 +1,48 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ +#pragma once + +#include <stdbool.h> + +#include "macro.h" + +/* So here's the deal: net_id is supposed to be an excercise in providing stable names for network devices. However, we + * also want to keep updating the naming scheme used in future versions of net_id. These two goals of course are + * contradictory: on one hand we want things to not change and on the other hand we want them to improve. Our way out + * of this dilemma is to introduce the "naming scheme" concept: each time we improve the naming logic we define a new + * flag for it. Then, we keep a list of schemes, each identified by a name associated with the flags it implements. Via + * a kernel command line and environment variable we then allow the user to pick the scheme they want us to follow: + * installers could "freeze" the used scheme at the moment of installation this way. + * + * Developers: each time you tweak the naming logic here, define a new flag below, and condition the tweak with + * it. Each time we do a release we'll then add a new scheme entry and include all newly defined flags. + * + * Note that this is only half a solution to the problem though: not only udev/net_id gets updated all the time, the + * kernel gets too. And thus a kernel that previously didn't expose some sysfs attribute we look for might eventually + * do, and thus affect our naming scheme too. Thus, enforcing a naming scheme will make interfacing more stable across + * OS versions, but not fully stabilize them. */ +typedef enum NamingSchemeFlags { + /* First, the individual features */ + NAMING_SR_IOV_V = 1 << 0, /* Use "v" suffix for SR-IOV, see 609948c7043a40008b8299529c978ed8e11de8f6*/ + NAMING_NPAR_ARI = 1 << 1, /* Use NPAR "ARI", see 6bc04997b6eab35d1cb9fa73889892702c27be09 */ + NAMING_INFINIBAND = 1 << 2, /* Use "ib" prefix for infiniband, see 938d30aa98df887797c9e05074a562ddacdcdf5e */ + NAMING_ZERO_ACPI_INDEX = 1 << 3, /* Allow zero acpi_index field, see d81186ef4f6a888a70f20a1e73a812d6acb9e22f */ + NAMING_ALLOW_RERENAMES = 1 << 4, /* Allow re-renaming of devices, see #9006 */ + + /* And now the masks that combine the features above */ + NAMING_V238 = 0, + NAMING_V239 = NAMING_V238 | NAMING_SR_IOV_V | NAMING_NPAR_ARI, + NAMING_V240 = NAMING_V239 | NAMING_INFINIBAND | NAMING_ZERO_ACPI_INDEX | NAMING_ALLOW_RERENAMES, + + _NAMING_SCHEME_FLAGS_INVALID = -1, +} NamingSchemeFlags; + +typedef struct NamingScheme { + const char *name; + NamingSchemeFlags flags; +} NamingScheme; + +const NamingScheme* naming_scheme(void); + +static inline bool naming_scheme_has(NamingSchemeFlags flags) { + return FLAGS_SET(naming_scheme()->flags, flags); +} |