/* -*- mode: c; c-file-style: "openbsd" -*- */ /* * Copyright (c) 2008 Vincent Bernat * * 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 #include #include #include #include #if defined(__clang__) # pragma clang diagnostic push # pragma clang diagnostic ignored "-Wdocumentation" #endif #include #include #include #include #include #include #include #include #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); }