diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 18:02:34 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 18:02:34 +0000 |
commit | fadeddfbb2aa38a980dd959b5ec1ffba7afd43cb (patch) | |
tree | a7bde6111c84ea64619656a38fba50909fa0bf60 /src/daemon/interfaces-linux.c | |
parent | Initial commit. (diff) | |
download | lldpd-upstream.tar.xz lldpd-upstream.zip |
Adding upstream version 1.0.18.upstream/1.0.18upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | src/daemon/interfaces-linux.c | 1046 |
1 files changed, 1046 insertions, 0 deletions
diff --git a/src/daemon/interfaces-linux.c b/src/daemon/interfaces-linux.c new file mode 100644 index 0000000..e764943 --- /dev/null +++ b/src/daemon/interfaces-linux.c @@ -0,0 +1,1046 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2008 Vincent Bernat <bernat@luffy.cx> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "lldpd.h" + +#include <stdio.h> +#include <unistd.h> +#include <inttypes.h> +#include <errno.h> +#include <sys/ioctl.h> +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wdocumentation" +#endif +#include <netinet/in.h> +#include <linux/if_vlan.h> +#include <linux/if_bonding.h> +#include <linux/if_bridge.h> +#include <linux/wireless.h> +#include <linux/sockios.h> +#include <linux/if_packet.h> +#include <linux/ethtool.h> +#if defined(__clang__) +# pragma clang diagnostic pop +#endif + +#define SYSFS_PATH_MAX 256 +#define MAX_PORTS 1024 +#define MAX_BRIDGES 1024 + +static int +iflinux_eth_init(struct lldpd *cfg, struct lldpd_hardware *hardware) +{ + int fd; + + log_debug("interfaces", "initialize ethernet device %s", hardware->h_ifname); + if ((fd = priv_iface_init(hardware->h_ifindex, hardware->h_ifname)) == -1) + return -1; + hardware->h_sendfd = fd; /* Send */ + + interfaces_setup_multicast(cfg, hardware->h_ifname, 0); + + levent_hardware_add_fd(hardware, fd); /* Receive */ + log_debug("interfaces", "interface %s initialized (fd=%d)", hardware->h_ifname, + fd); + return 0; +} + +/* Generic ethernet send/receive */ +static int +iflinux_eth_send(struct lldpd *cfg, struct lldpd_hardware *hardware, char *buffer, + size_t size) +{ + log_debug("interfaces", "send PDU to ethernet device %s (fd=%d)", + hardware->h_ifname, hardware->h_sendfd); + return write(hardware->h_sendfd, buffer, size); +} + +static int +iflinux_generic_recv(struct lldpd_hardware *hardware, int fd, char *buffer, size_t size, + struct sockaddr_ll *from) +{ + int n, retry = 0; + socklen_t fromlen; + +retry: + fromlen = sizeof(*from); + memset(from, 0, fromlen); + if ((n = recvfrom(fd, buffer, size, 0, (struct sockaddr *)from, &fromlen)) == + -1) { + if (errno == EAGAIN && retry == 0) { + /* There may be an error queued in the socket. Clear it and + * retry. */ + levent_recv_error(fd, hardware->h_ifname); + retry++; + goto retry; + } + if (errno == ENETDOWN) { + log_debug("interfaces", + "error while receiving frame on %s (network down)", + hardware->h_ifname); + } else { + log_warn("interfaces", + "error while receiving frame on %s (retry: %d)", + hardware->h_ifname, retry); + hardware->h_rx_discarded_cnt++; + } + return -1; + } + if (from->sll_pkttype == PACKET_OUTGOING) return -1; + return n; +} + +static int +iflinux_eth_recv(struct lldpd *cfg, struct lldpd_hardware *hardware, int fd, + char *buffer, size_t size) +{ + int n; + struct sockaddr_ll from; + + log_debug("interfaces", "receive PDU from ethernet device %s", + hardware->h_ifname); + if ((n = iflinux_generic_recv(hardware, fd, buffer, size, &from)) == -1) + return -1; + return n; +} + +static int +iflinux_eth_close(struct lldpd *cfg, struct lldpd_hardware *hardware) +{ + log_debug("interfaces", "close ethernet device %s", hardware->h_ifname); + interfaces_setup_multicast(cfg, hardware->h_ifname, 1); + return 0; +} + +static struct lldpd_ops eth_ops = { + .send = iflinux_eth_send, + .recv = iflinux_eth_recv, + .cleanup = iflinux_eth_close, +}; + +static int +iflinux_is_bridge(struct lldpd *cfg, struct interfaces_device_list *interfaces, + struct interfaces_device *iface) +{ +#ifdef ENABLE_OLDIES + struct interfaces_device *port; + char path[SYSFS_PATH_MAX]; + int f; + + if ((snprintf(path, SYSFS_PATH_MAX, SYSFS_CLASS_NET "%s/" SYSFS_BRIDGE_FDB, + iface->name)) >= SYSFS_PATH_MAX) + log_warnx("interfaces", "path truncated"); + if ((f = priv_open(path)) < 0) return 0; + close(f); + + /* Also grab all ports */ + TAILQ_FOREACH (port, interfaces, next) { + if (port->upper) continue; + if (snprintf(path, SYSFS_PATH_MAX, + SYSFS_CLASS_NET "%s/" SYSFS_BRIDGE_PORT_SUBDIR "/%s/port_no", + iface->name, port->name) >= SYSFS_PATH_MAX) + log_warnx("interfaces", "path truncated"); + if ((f = priv_open(path)) < 0) continue; + log_debug("interfaces", "port %s is bridged to %s", port->name, + iface->name); + port->upper = iface; + close(f); + } + + return 1; +#else + return 0; +#endif +} + +static int +iflinux_is_vlan(struct lldpd *cfg, struct interfaces_device_list *interfaces, + struct interfaces_device *iface) +{ +#ifdef ENABLE_OLDIES + struct vlan_ioctl_args ifv = {}; + ifv.cmd = GET_VLAN_REALDEV_NAME_CMD; + strlcpy(ifv.device1, iface->name, sizeof(ifv.device1)); + if (ioctl(cfg->g_sock, SIOCGIFVLAN, &ifv) >= 0) { + /* This is a VLAN, get the lower interface and the VID */ + struct interfaces_device *lower = + interfaces_nametointerface(interfaces, ifv.u.device2); + if (!lower) { + log_debug("interfaces", + "unable to find lower interface for VLAN %s", iface->name); + return 0; + } + + memset(&ifv, 0, sizeof(ifv)); + ifv.cmd = GET_VLAN_VID_CMD; + strlcpy(ifv.device1, iface->name, sizeof(ifv.device1)); + if (ioctl(cfg->g_sock, SIOCGIFVLAN, &ifv) < 0) { + log_debug("interfaces", "unable to find VID for VLAN %s", + iface->name); + return 0; + } + + iface->lower = lower; + bitmap_set(iface->vlan_bmap, ifv.u.VID); + return 1; + } +#endif + return 0; +} + +static int +iflinux_is_bond(struct lldpd *cfg, struct interfaces_device_list *interfaces, + struct interfaces_device *master) +{ +#ifdef ENABLE_OLDIES + /* Shortcut if we detect the new team driver. Upper and lower links + * should already be set with netlink in this case. */ + if (master->driver && !strcmp(master->driver, "team")) { + return 1; + } + + struct ifreq ifr = {}; + struct ifbond ifb = {}; + strlcpy(ifr.ifr_name, master->name, sizeof(ifr.ifr_name)); + ifr.ifr_data = (char *)&ifb; + if (ioctl(cfg->g_sock, SIOCBONDINFOQUERY, &ifr) >= 0) { + while (ifb.num_slaves--) { + struct ifslave ifs; + memset(&ifr, 0, sizeof(ifr)); + memset(&ifs, 0, sizeof(ifs)); + strlcpy(ifr.ifr_name, master->name, sizeof(ifr.ifr_name)); + ifr.ifr_data = (char *)&ifs; + ifs.slave_id = ifb.num_slaves; + if (ioctl(cfg->g_sock, SIOCBONDSLAVEINFOQUERY, &ifr) >= 0) { + struct interfaces_device *slave = + interfaces_nametointerface(interfaces, + ifs.slave_name); + if (slave == NULL) continue; + if (slave->upper) continue; + log_debug("interfaces", + "interface %s is enslaved to %s", slave->name, + master->name); + slave->upper = master; + } + } + return 1; + } +#endif + return 0; +} + +/** + * Get permanent MAC from ethtool. + * + * Return 0 on success, -1 on error. + */ +static int +iflinux_get_permanent_mac_ethtool(struct lldpd *cfg, + struct interfaces_device_list *interfaces, struct interfaces_device *iface) +{ + int ret = -1; + struct ifreq ifr = {}; + struct ethtool_perm_addr *epaddr = + calloc(sizeof(struct ethtool_perm_addr) + ETHER_ADDR_LEN, 1); + if (epaddr == NULL) goto end; + + strlcpy(ifr.ifr_name, iface->name, sizeof(ifr.ifr_name)); + epaddr->cmd = ETHTOOL_GPERMADDR; + epaddr->size = ETHER_ADDR_LEN; + ifr.ifr_data = (caddr_t)epaddr; + if (ioctl(cfg->g_sock, SIOCETHTOOL, &ifr) == -1) { + static int once = 0; + if (errno == EPERM && !once) { + log_warnx("interfaces", + "no permission to get permanent MAC address for %s (requires 2.6.19+)", + iface->name); + once = 1; + goto end; + } + if (errno != EPERM) + log_warn("interfaces", + "cannot get permanent MAC address for %s", iface->name); + goto end; + } + if (epaddr->data[0] != 0 || epaddr->data[1] != 0 || epaddr->data[2] != 0 || + epaddr->data[3] != 0 || epaddr->data[4] != 0 || epaddr->data[5] != 0) { + memcpy(iface->address, epaddr->data, ETHER_ADDR_LEN); + ret = 0; + goto end; + } + log_debug("interfaces", "cannot get permanent MAC for %s (all 0)", iface->name); +end: + free(epaddr); + return ret; +} + +/** + * Get permanent MAC address for a bond device. + */ +static void +iflinux_get_permanent_mac_bond(struct lldpd *cfg, + struct interfaces_device_list *interfaces, struct interfaces_device *iface) +{ + struct interfaces_device *master = iface->upper; + int f, state = 0; + FILE *netbond; + const char *slaveif = "Slave Interface: "; + const char *hwaddr = "Permanent HW addr: "; + u_int8_t mac[ETHER_ADDR_LEN]; + char path[SYSFS_PATH_MAX]; + char line[100]; + + /* We have a bond, we need to query it to get real MAC addresses */ + if (snprintf(path, SYSFS_PATH_MAX, "/proc/net/bonding/%s", master->name) >= + SYSFS_PATH_MAX) { + log_warnx("interfaces", "path truncated"); + return; + } + if ((f = priv_open(path)) < 0) { + if (snprintf(path, SYSFS_PATH_MAX, "/proc/self/net/bonding/%s", + master->name) >= SYSFS_PATH_MAX) { + log_warnx("interfaces", "path truncated"); + return; + } + f = priv_open(path); + } + if (f < 0) { + log_warnx("interfaces", "unable to get permanent MAC address for %s", + iface->name); + return; + } + if ((netbond = fdopen(f, "r")) == NULL) { + log_warn("interfaces", "unable to read stream from %s", path); + close(f); + return; + } + /* State 0: + We parse the file to search "Slave Interface: ". If found, go to + state 1. + State 1: + We parse the file to search "Permanent HW addr: ". If found, we get + the mac. + */ + while (fgets(line, sizeof(line), netbond)) { + switch (state) { + case 0: + if (strncmp(line, slaveif, strlen(slaveif)) == 0) { + if (line[strlen(line) - 1] == '\n') + line[strlen(line) - 1] = '\0'; + if (strcmp(iface->name, line + strlen(slaveif)) == 0) + state++; + } + break; + case 1: + if (strncmp(line, hwaddr, strlen(hwaddr)) == 0) { + if (line[strlen(line) - 1] == '\n') + line[strlen(line) - 1] = '\0'; + if (sscanf(line + strlen(hwaddr), + "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx", + &mac[0], &mac[1], &mac[2], &mac[3], &mac[4], + &mac[5]) != ETHER_ADDR_LEN) { + log_warn("interfaces", "unable to parse %s", + line + strlen(hwaddr)); + fclose(netbond); + return; + } + memcpy(iface->address, mac, ETHER_ADDR_LEN); + fclose(netbond); + return; + } + break; + } + } + log_warnx("interfaces", "unable to find real MAC address for enslaved %s", + iface->name); + fclose(netbond); +} + +/** + * Get permanent MAC. + */ +static void +iflinux_get_permanent_mac(struct lldpd *cfg, struct interfaces_device_list *interfaces, + struct interfaces_device *iface) +{ + struct interfaces_device *master = iface->upper; + + if (master == NULL || master->type != IFACE_BOND_T) return; + if (iflinux_get_permanent_mac_ethtool(cfg, interfaces, iface) == -1 && + (master->driver == NULL || !strcmp(master->driver, "bonding"))) + /* Fallback to old method for a bond */ + iflinux_get_permanent_mac_bond(cfg, interfaces, iface); +} + +#ifdef ENABLE_DOT3 +# define ETHTOOL_LINK_MODE_MASK_MAX_KERNEL_NU32 (SCHAR_MAX) +# define ETHTOOL_DECLARE_LINK_MODE_MASK(name) \ + uint32_t name[ETHTOOL_LINK_MODE_MASK_MAX_KERNEL_NU32] + +struct ethtool_link_usettings { + struct ethtool_link_settings base; + struct { + ETHTOOL_DECLARE_LINK_MODE_MASK(supported); + ETHTOOL_DECLARE_LINK_MODE_MASK(advertising); + ETHTOOL_DECLARE_LINK_MODE_MASK(lp_advertising); + } link_modes; +}; + +static int +iflinux_ethtool_link_mode_test_bit(unsigned int nr, const uint32_t *mask) +{ + if (nr >= 32 * ETHTOOL_LINK_MODE_MASK_MAX_KERNEL_NU32) return 0; + return !!(mask[nr / 32] & (1 << (nr % 32))); +} +static void +iflinux_ethtool_link_mode_unset_bit(unsigned int nr, uint32_t *mask) +{ + if (nr >= 32 * ETHTOOL_LINK_MODE_MASK_MAX_KERNEL_NU32) return; + mask[nr / 32] &= ~(1 << (nr % 32)); +} +static int +iflinux_ethtool_link_mode_is_empty(const uint32_t *mask) +{ + for (unsigned int i = 0; i < ETHTOOL_LINK_MODE_MASK_MAX_KERNEL_NU32; ++i) { + if (mask[i] != 0) return 0; + } + + return 1; +} + +static int +iflinux_ethtool_glink(struct lldpd *cfg, const char *ifname, + struct ethtool_link_usettings *uset) +{ + int rc; + + /* Try with ETHTOOL_GLINKSETTINGS first */ + struct { + struct ethtool_link_settings req; + uint32_t link_mode_data[3 * ETHTOOL_LINK_MODE_MASK_MAX_KERNEL_NU32]; + } ecmd; + static int8_t nwords = 0; + struct ifreq ifr = {}; + strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); + + if (nwords == 0) { + /* Do a handshake first. We assume that this is device-independant. */ + memset(&ecmd, 0, sizeof(ecmd)); + ecmd.req.cmd = ETHTOOL_GLINKSETTINGS; + ifr.ifr_data = (caddr_t)&ecmd; + rc = ioctl(cfg->g_sock, SIOCETHTOOL, &ifr); + if (rc == 0) { + nwords = -ecmd.req.link_mode_masks_nwords; + log_debug("interfaces", "glinksettings nwords is %" PRId8, + nwords); + } else { + static int once = 0; + if (errno == EPERM && !once) { + log_warnx("interfaces", + "cannot get ethtool link information " + "with GLINKSETTINGS (requires 4.9+). " + "25G+ speeds may be missing in MAC/PHY TLVs"); + once = 1; + } + nwords = -1; + } + } + if (nwords > 0) { + memset(&ecmd, 0, sizeof(ecmd)); + ecmd.req.cmd = ETHTOOL_GLINKSETTINGS; + ecmd.req.link_mode_masks_nwords = nwords; + ifr.ifr_data = (caddr_t)&ecmd; + rc = ioctl(cfg->g_sock, SIOCETHTOOL, &ifr); + if (rc == 0) { + log_debug("interfaces", + "got ethtool results for %s with GLINKSETTINGS", ifname); + memcpy(&uset->base, &ecmd.req, sizeof(uset->base)); + unsigned int u32_offs = 0; + memcpy(uset->link_modes.supported, + &ecmd.link_mode_data[u32_offs], + 4 * ecmd.req.link_mode_masks_nwords); + u32_offs += ecmd.req.link_mode_masks_nwords; + memcpy(uset->link_modes.advertising, + &ecmd.link_mode_data[u32_offs], + 4 * ecmd.req.link_mode_masks_nwords); + u32_offs += ecmd.req.link_mode_masks_nwords; + memcpy(uset->link_modes.lp_advertising, + &ecmd.link_mode_data[u32_offs], + 4 * ecmd.req.link_mode_masks_nwords); + goto end; + } + } + + /* Try with ETHTOOL_GSET */ + struct ethtool_cmd ethc; + memset(ðc, 0, sizeof(ethc)); + ethc.cmd = ETHTOOL_GSET; + ifr.ifr_data = (caddr_t)ðc; + rc = ioctl(cfg->g_sock, SIOCETHTOOL, &ifr); + if (rc == 0) { + /* Do a partial copy (only what we need) */ + log_debug("interfaces", "got ethtool results for %s with GSET", ifname); + memset(uset, 0, sizeof(*uset)); + uset->base.cmd = ETHTOOL_GSET; + uset->base.link_mode_masks_nwords = 1; + uset->link_modes.supported[0] = ethc.supported; + uset->link_modes.advertising[0] = ethc.advertising; + uset->link_modes.lp_advertising[0] = ethc.lp_advertising; + uset->base.speed = (ethc.speed_hi << 16) | ethc.speed; + uset->base.duplex = ethc.duplex; + uset->base.port = ethc.port; + uset->base.autoneg = ethc.autoneg; + } else { + static int once = 0; + if (errno == EPERM && !once) { + log_warnx("interfaces", + "cannot get ethtool link information " + "with GSET (requires 2.6.19+). " + "MAC/PHY TLV will be unavailable"); + once = 1; + } + } +end: + return rc; +} + +/* Fill up MAC/PHY for a given hardware port */ +static void +iflinux_macphy(struct lldpd *cfg, struct lldpd_hardware *hardware) +{ + struct ethtool_link_usettings uset = {}; + struct lldpd_port *port = &hardware->h_lport; + int j; + int advertised_ethtool_to_rfc3636[][2] = { + { ETHTOOL_LINK_MODE_10baseT_Half_BIT, LLDP_DOT3_LINK_AUTONEG_10BASE_T }, + { ETHTOOL_LINK_MODE_10baseT_Full_BIT, + LLDP_DOT3_LINK_AUTONEG_10BASET_FD }, + { ETHTOOL_LINK_MODE_100baseT_Half_BIT, + LLDP_DOT3_LINK_AUTONEG_100BASE_TX }, + { ETHTOOL_LINK_MODE_100baseT_Full_BIT, + LLDP_DOT3_LINK_AUTONEG_100BASE_TXFD }, + { ETHTOOL_LINK_MODE_1000baseT_Half_BIT, + LLDP_DOT3_LINK_AUTONEG_1000BASE_T }, + { ETHTOOL_LINK_MODE_1000baseT_Full_BIT, + LLDP_DOT3_LINK_AUTONEG_1000BASE_TFD }, + { ETHTOOL_LINK_MODE_1000baseKX_Full_BIT, + LLDP_DOT3_LINK_AUTONEG_1000BASE_XFD }, + { ETHTOOL_LINK_MODE_Pause_BIT, LLDP_DOT3_LINK_AUTONEG_FDX_PAUSE }, + { ETHTOOL_LINK_MODE_Asym_Pause_BIT, LLDP_DOT3_LINK_AUTONEG_FDX_APAUSE }, + { -1, 0 } + }; + + log_debug("interfaces", "ask ethtool for the appropriate MAC/PHY for %s", + hardware->h_ifname); + if (iflinux_ethtool_glink(cfg, hardware->h_ifname, &uset) == 0) { + port->p_macphy.autoneg_support = + iflinux_ethtool_link_mode_test_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, + uset.link_modes.supported); + port->p_macphy.autoneg_enabled = + (uset.base.autoneg == AUTONEG_DISABLE) ? 0 : 1; + for (j = 0; advertised_ethtool_to_rfc3636[j][0] >= 0; j++) { + if (iflinux_ethtool_link_mode_test_bit( + advertised_ethtool_to_rfc3636[j][0], + uset.link_modes.advertising)) { + port->p_macphy.autoneg_advertised |= + advertised_ethtool_to_rfc3636[j][1]; + iflinux_ethtool_link_mode_unset_bit( + advertised_ethtool_to_rfc3636[j][0], + uset.link_modes.advertising); + } + } + iflinux_ethtool_link_mode_unset_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, + uset.link_modes.advertising); + iflinux_ethtool_link_mode_unset_bit(ETHTOOL_LINK_MODE_TP_BIT, + uset.link_modes.advertising); + iflinux_ethtool_link_mode_unset_bit(ETHTOOL_LINK_MODE_AUI_BIT, + uset.link_modes.advertising); + iflinux_ethtool_link_mode_unset_bit(ETHTOOL_LINK_MODE_MII_BIT, + uset.link_modes.advertising); + iflinux_ethtool_link_mode_unset_bit(ETHTOOL_LINK_MODE_FIBRE_BIT, + uset.link_modes.advertising); + iflinux_ethtool_link_mode_unset_bit(ETHTOOL_LINK_MODE_BNC_BIT, + uset.link_modes.advertising); + iflinux_ethtool_link_mode_unset_bit(ETHTOOL_LINK_MODE_Pause_BIT, + uset.link_modes.advertising); + iflinux_ethtool_link_mode_unset_bit(ETHTOOL_LINK_MODE_Asym_Pause_BIT, + uset.link_modes.advertising); + iflinux_ethtool_link_mode_unset_bit(ETHTOOL_LINK_MODE_Backplane_BIT, + uset.link_modes.advertising); + if (!iflinux_ethtool_link_mode_is_empty(uset.link_modes.advertising)) { + port->p_macphy.autoneg_advertised |= + LLDP_DOT3_LINK_AUTONEG_OTHER; + } + switch (uset.base.speed) { + case SPEED_10: + port->p_macphy.mau_type = (uset.base.duplex == DUPLEX_FULL) ? + LLDP_DOT3_MAU_10BASETFD : + LLDP_DOT3_MAU_10BASETHD; + if (uset.base.port == PORT_BNC) + port->p_macphy.mau_type = LLDP_DOT3_MAU_10BASE2; + if (uset.base.port == PORT_FIBRE) + port->p_macphy.mau_type = + (uset.base.duplex == DUPLEX_FULL) ? + LLDP_DOT3_MAU_10BASEFLFD : + LLDP_DOT3_MAU_10BASEFLHD; + break; + case SPEED_100: + port->p_macphy.mau_type = (uset.base.duplex == DUPLEX_FULL) ? + LLDP_DOT3_MAU_100BASETXFD : + LLDP_DOT3_MAU_100BASETXHD; + if (uset.base.port == PORT_BNC) + port->p_macphy.mau_type = + (uset.base.duplex == DUPLEX_FULL) ? + LLDP_DOT3_MAU_100BASET2FD : + LLDP_DOT3_MAU_100BASET2HD; + if (uset.base.port == PORT_FIBRE) + port->p_macphy.mau_type = + (uset.base.duplex == DUPLEX_FULL) ? + LLDP_DOT3_MAU_100BASEFXFD : + LLDP_DOT3_MAU_100BASEFXHD; + break; + case SPEED_1000: + port->p_macphy.mau_type = (uset.base.duplex == DUPLEX_FULL) ? + LLDP_DOT3_MAU_1000BASETFD : + LLDP_DOT3_MAU_1000BASETHD; + if (uset.base.port == PORT_FIBRE) + port->p_macphy.mau_type = + (uset.base.duplex == DUPLEX_FULL) ? + LLDP_DOT3_MAU_1000BASEXFD : + LLDP_DOT3_MAU_1000BASEXHD; + break; + case SPEED_2500: + port->p_macphy.mau_type = LLDP_DOT3_MAU_2P5GIGT; + break; + case SPEED_5000: + port->p_macphy.mau_type = LLDP_DOT3_MAU_5GIGT; + break; + case SPEED_10000: + // Distinguish between RJ45 BaseT, DAC BaseCX4, or Fibre BaseLR + if (uset.base.port == PORT_TP) { + port->p_macphy.mau_type = LLDP_DOT3_MAU_10GBASET; + } else if (uset.base.port == PORT_FIBRE) { + port->p_macphy.mau_type = LLDP_DOT3_MAU_10GIGBASELR; + } else if (uset.base.port == PORT_DA) { + port->p_macphy.mau_type = LLDP_DOT3_MAU_10GIGBASECX4; + } + break; + case SPEED_25000: + // Distinguish between RJ45 BaseT, DAC BaseCR, or Fibre BaseLR + if (uset.base.port == PORT_TP) { + port->p_macphy.mau_type = LLDP_DOT3_MAU_25GBASET; + } else if (uset.base.port == PORT_FIBRE) { + port->p_macphy.mau_type = LLDP_DOT3_MAU_25GBASELR; + } else if (uset.base.port == PORT_DA) { + port->p_macphy.mau_type = LLDP_DOT3_MAU_25GBASECR; + } + break; + case SPEED_40000: + // Same kind of approximation. + port->p_macphy.mau_type = (uset.base.port == PORT_FIBRE) ? + LLDP_DOT3_MAU_40GBASELR4 : + LLDP_DOT3_MAU_40GBASECR4; + break; + case SPEED_50000: + // Same kind of approximation. + port->p_macphy.mau_type = (uset.base.port == PORT_FIBRE) ? + LLDP_DOT3_MAU_50GBASELR : + LLDP_DOT3_MAU_50GBASECR; + break; + case SPEED_100000: + // Ditto + port->p_macphy.mau_type = (uset.base.port == PORT_FIBRE) ? + LLDP_DOT3_MAU_100GBASELR4 : + LLDP_DOT3_MAU_100GBASECR4; + break; + } + if (uset.base.port == PORT_AUI) + port->p_macphy.mau_type = LLDP_DOT3_MAU_AUI; + } +} +#else /* ENABLE_DOT3 */ +static void +iflinux_macphy(struct lldpd *cfg, struct lldpd_hardware *hardware) +{ +} +#endif /* ENABLE_DOT3 */ + +#ifdef ENABLE_OLDIES +struct bond_master { + char name[IFNAMSIZ]; + int index; +}; + +static int +iface_bond_init(struct lldpd *cfg, struct lldpd_hardware *hardware) +{ + struct bond_master *master = hardware->h_data; + int fd; + int un = 1; + + if (!master) return -1; + + log_debug("interfaces", "initialize enslaved device %s", hardware->h_ifname); + + /* First, we get a socket to the raw physical interface */ + if ((fd = priv_iface_init(hardware->h_ifindex, hardware->h_ifname)) == -1) + return -1; + hardware->h_sendfd = fd; + interfaces_setup_multicast(cfg, hardware->h_ifname, 0); + + /* Then, we open a raw interface for the master */ + log_debug("interfaces", "enslaved device %s has master %s(%d)", + hardware->h_ifname, master->name, master->index); + if ((fd = priv_iface_init(master->index, master->name)) == -1) { + close(hardware->h_sendfd); + return -1; + } + /* With bonding and older kernels (< 2.6.27) we need to listen + * to bond device. We use setsockopt() PACKET_ORIGDEV to get + * physical device instead of bond device (works with >= + * 2.6.24). */ + if (setsockopt(fd, SOL_PACKET, PACKET_ORIGDEV, &un, sizeof(un)) == -1) { + log_info("interfaces", + "unable to setsockopt for master bonding device of %s. " + "You will get inaccurate results", + hardware->h_ifname); + } + interfaces_setup_multicast(cfg, master->name, 0); + + levent_hardware_add_fd(hardware, hardware->h_sendfd); + levent_hardware_add_fd(hardware, fd); + log_debug("interfaces", "interface %s initialized (fd=%d,master=%s[%d])", + hardware->h_ifname, hardware->h_sendfd, master->name, fd); + return 0; +} + +static int +iface_bond_recv(struct lldpd *cfg, struct lldpd_hardware *hardware, int fd, + char *buffer, size_t size) +{ + int n; + struct sockaddr_ll from; + struct bond_master *master = hardware->h_data; + + log_debug("interfaces", "receive PDU from enslaved device %s", + hardware->h_ifname); + if ((n = iflinux_generic_recv(hardware, fd, buffer, size, &from)) == -1) + return -1; + if (fd == hardware->h_sendfd) /* We received this on the physical interface. */ + return n; + /* We received this on the bonding interface. Is it really for us? */ + if (from.sll_ifindex == hardware->h_ifindex) /* This is for us */ + return n; + if (from.sll_ifindex == master->index) + /* We don't know from which physical interface it comes (kernel + * < 2.6.24). In doubt, this is for us. */ + return n; + return -1; /* Not for us */ +} + +static int +iface_bond_close(struct lldpd *cfg, struct lldpd_hardware *hardware) +{ + struct bond_master *master = hardware->h_data; + log_debug("interfaces", "closing enslaved device %s", hardware->h_ifname); + interfaces_setup_multicast(cfg, hardware->h_ifname, 1); + interfaces_setup_multicast(cfg, master->name, 1); + free(hardware->h_data); + hardware->h_data = NULL; + return 0; +} + +struct lldpd_ops bond_ops = { + .send = iflinux_eth_send, + .recv = iface_bond_recv, + .cleanup = iface_bond_close, +}; + +static void +iflinux_handle_bond(struct lldpd *cfg, struct interfaces_device_list *interfaces) +{ + struct interfaces_device *iface; + struct interfaces_device *master; + struct lldpd_hardware *hardware; + struct bond_master *bmaster; + int created; + TAILQ_FOREACH (iface, interfaces, next) { + if (!(iface->type & IFACE_PHYSICAL_T)) continue; + if (iface->ignore) continue; + if (!iface->upper || !(iface->upper->type & IFACE_BOND_T)) continue; + + master = iface->upper; + log_debug("interfaces", + "%s is an acceptable enslaved device (master=%s)", iface->name, + master->name); + created = 0; + if ((hardware = lldpd_get_hardware(cfg, iface->name, iface->index)) == + NULL) { + if ((hardware = lldpd_alloc_hardware(cfg, iface->name, + iface->index)) == NULL) { + log_warnx("interfaces", + "Unable to allocate space for %s", iface->name); + continue; + } + created = 1; + } + if (hardware->h_flags) continue; + if (hardware->h_ops != &bond_ops || hardware->h_ifindex_changed) { + if (!created) { + log_debug("interfaces", + "bond %s is converted from another type of interface", + hardware->h_ifname); + if (hardware->h_ops && hardware->h_ops->cleanup) + hardware->h_ops->cleanup(cfg, hardware); + levent_hardware_release(hardware); + levent_hardware_init(hardware); + } + bmaster = hardware->h_data = + calloc(1, sizeof(struct bond_master)); + if (!bmaster) { + log_warn("interfaces", "not enough memory"); + lldpd_hardware_cleanup(cfg, hardware); + continue; + } + } else + bmaster = hardware->h_data; + bmaster->index = master->index; + strlcpy(bmaster->name, master->name, IFNAMSIZ); + if (hardware->h_ops != &bond_ops || hardware->h_ifindex_changed) { + if (iface_bond_init(cfg, hardware) != 0) { + log_warn("interfaces", "unable to initialize %s", + hardware->h_ifname); + lldpd_hardware_cleanup(cfg, hardware); + continue; + } + hardware->h_ops = &bond_ops; + hardware->h_mangle = 1; + } + if (created) + interfaces_helper_add_hardware(cfg, hardware); + else + lldpd_port_cleanup(&hardware->h_lport, 0); + + hardware->h_flags = iface->flags; + iface->ignore = 1; + + /* Get local address */ + memcpy(&hardware->h_lladdr, iface->address, ETHER_ADDR_LEN); + + /* Fill information about port */ + interfaces_helper_port_name_desc(cfg, hardware, iface); + + /* Fill additional info */ +# ifdef ENABLE_DOT3 + hardware->h_lport.p_aggregid = master->index; +# endif + hardware->h_mtu = iface->mtu ? iface->mtu : 1500; + } +} +#endif + +/* Query each interface to get the appropriate driver */ +static void +iflinux_add_driver(struct lldpd *cfg, struct interfaces_device_list *interfaces) +{ + struct interfaces_device *iface; + TAILQ_FOREACH (iface, interfaces, next) { + struct ethtool_drvinfo ethc = { .cmd = ETHTOOL_GDRVINFO }; + struct ifreq ifr = { .ifr_data = (caddr_t)ðc }; + if (iface->driver) continue; + + strlcpy(ifr.ifr_name, iface->name, IFNAMSIZ); + if (ioctl(cfg->g_sock, SIOCETHTOOL, &ifr) == 0) { + iface->driver = strdup(ethc.driver); + log_debug("interfaces", "driver for %s is `%s`", iface->name, + iface->driver); + } + } +} + +/* Query each interface to see if it is a wireless one */ +static void +iflinux_add_wireless(struct lldpd *cfg, struct interfaces_device_list *interfaces) +{ + struct interfaces_device *iface; + TAILQ_FOREACH (iface, interfaces, next) { + struct iwreq iwr = {}; + strlcpy(iwr.ifr_name, iface->name, IFNAMSIZ); + if (ioctl(cfg->g_sock, SIOCGIWNAME, &iwr) >= 0) { + log_debug("interfaces", "%s is wireless", iface->name); + iface->type |= IFACE_WIRELESS_T | IFACE_PHYSICAL_T; + } + } +} + +/* Query each interface to see if it is a bridge */ +static void +iflinux_add_bridge(struct lldpd *cfg, struct interfaces_device_list *interfaces) +{ + struct interfaces_device *iface; + + TAILQ_FOREACH (iface, interfaces, next) { + if (iface->type & + (IFACE_PHYSICAL_T | IFACE_VLAN_T | IFACE_BOND_T | IFACE_BRIDGE_T)) + continue; + if (iflinux_is_bridge(cfg, interfaces, iface)) { + log_debug("interfaces", "interface %s is a bridge", + iface->name); + iface->type |= IFACE_BRIDGE_T; + } + } +} + +/* Query each interface to see if it is a bond */ +static void +iflinux_add_bond(struct lldpd *cfg, struct interfaces_device_list *interfaces) +{ + struct interfaces_device *iface; + + TAILQ_FOREACH (iface, interfaces, next) { + if (iface->type & + (IFACE_PHYSICAL_T | IFACE_VLAN_T | IFACE_BOND_T | IFACE_BRIDGE_T)) + continue; + if (iflinux_is_bond(cfg, interfaces, iface)) { + log_debug("interfaces", "interface %s is a bond", iface->name); + iface->type |= IFACE_BOND_T; + } + } +} + +/* Query each interface to see if it is a vlan */ +static void +iflinux_add_vlan(struct lldpd *cfg, struct interfaces_device_list *interfaces) +{ + struct interfaces_device *iface; + + TAILQ_FOREACH (iface, interfaces, next) { + if (iface->type & + (IFACE_PHYSICAL_T | IFACE_VLAN_T | IFACE_BOND_T | IFACE_BRIDGE_T)) + continue; + if (iflinux_is_vlan(cfg, interfaces, iface)) { + log_debug("interfaces", "interface %s is a VLAN", iface->name); + iface->type |= IFACE_VLAN_T; + } + } +} + +static void +iflinux_add_physical(struct lldpd *cfg, struct interfaces_device_list *interfaces) +{ + struct interfaces_device *iface; + /* Deny some drivers */ + const char *const *rif; + const char *const denied_drivers[] = { "cdc_mbim", "vxlan", NULL }; + + TAILQ_FOREACH (iface, interfaces, next) { + if (iface->type & (IFACE_VLAN_T | IFACE_BOND_T | IFACE_BRIDGE_T)) + continue; + + iface->type &= ~IFACE_PHYSICAL_T; + + /* We request that the interface is able to do either multicast + * or broadcast to be able to send discovery frames. */ + if (!(iface->flags & (IFF_MULTICAST | IFF_BROADCAST))) { + log_debug("interfaces", + "skip %s: not able to do multicast nor broadcast", + iface->name); + continue; + } + + /* Check if the driver is not denied */ + if (iface->driver) { + int skip = 0; + for (rif = denied_drivers; *rif; rif++) { + if (strcmp(iface->driver, *rif) == 0) { + log_debug("interfaces", + "skip %s: denied driver", iface->name); + skip = 1; + break; + } + } + if (skip) continue; + } + + /* If the interface is linked to another one, skip it too. */ + if (iface->lower && + (!iface->driver || + (strcmp(iface->driver, "veth") && + strcmp(iface->driver, "dsa")))) { + log_debug("interfaces", + "skip %s: there is a lower interface (%s)", iface->name, + iface->lower->name); + continue; + } + + /* Get the real MAC address (for example, if the interface is enslaved) + */ + iflinux_get_permanent_mac(cfg, interfaces, iface); + + log_debug("interfaces", "%s is a physical interface", iface->name); + iface->type |= IFACE_PHYSICAL_T; + } +} + +void +interfaces_update(struct lldpd *cfg) +{ + struct lldpd_hardware *hardware; + struct interfaces_device_list *interfaces; + struct interfaces_address_list *addresses; + interfaces = netlink_get_interfaces(cfg); + addresses = netlink_get_addresses(cfg); + if (interfaces == NULL || addresses == NULL) { + log_warnx("interfaces", "cannot update the list of local interfaces"); + return; + } + + /* Add missing bits to list of interfaces */ + iflinux_add_driver(cfg, interfaces); + if (LOCAL_CHASSIS(cfg)->c_cap_available & LLDP_CAP_WLAN) + iflinux_add_wireless(cfg, interfaces); + if (LOCAL_CHASSIS(cfg)->c_cap_available & LLDP_CAP_BRIDGE) + iflinux_add_bridge(cfg, interfaces); + iflinux_add_bond(cfg, interfaces); + iflinux_add_vlan(cfg, interfaces); + iflinux_add_physical(cfg, interfaces); + + interfaces_helper_allowlist(cfg, interfaces); +#ifdef ENABLE_OLDIES + iflinux_handle_bond(cfg, interfaces); +#endif + interfaces_helper_physical(cfg, interfaces, ð_ops, iflinux_eth_init); +#ifdef ENABLE_DOT1 + interfaces_helper_vlan(cfg, interfaces); +#endif + interfaces_helper_mgmt(cfg, addresses, interfaces); + interfaces_helper_chassis(cfg, interfaces); + + /* Mac/PHY */ + TAILQ_FOREACH (hardware, &cfg->g_hardware, h_entries) { + if (!hardware->h_flags) continue; + iflinux_macphy(cfg, hardware); + interfaces_helper_promisc(cfg, hardware); + } +} + +void +interfaces_cleanup(struct lldpd *cfg) +{ + netlink_cleanup(cfg); +} |