text: /* -*- 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 <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;
  		iface->vlanid = 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(&ethc, 0, sizeof(ethc));
  	ethc.cmd = ETHTOOL_GSET;
  	ifr.ifr_data = (caddr_t)&ethc;
  	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_10000:
  			// For fiber, we tell 10GIGBASEX and for others,
  			// 10GIGBASER. It's not unusual to have 10GIGBASER on
  			// fiber either but we don't have 10GIGBASET for
  			// copper. No good solution.
  			port->p_macphy.mau_type = (uset.base.port == PORT_FIBRE) ?	\
  			    LLDP_DOT3_MAU_10GIGBASELR : LLDP_DOT3_MAU_10GIGBASECX4;
  			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_100000:
  			// Ditto
  			port->p_macphy.mau_type = (uset.base.port == PORT_FIBRE) ? \
  			    LLDP_DOT3_MAU_100GBASELR4 : LLDP_DOT3_MAU_100GBASECR10;
  			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) {
  			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) {
  			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)&ethc
  		};
  		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;
  	/* Blacklist some drivers */
  	const char * const *rif;
  	const char * const blacklisted_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 blacklisted */
  		if (iface->driver) {
  			int skip = 0;
  			for (rif = blacklisted_drivers; *rif; rif++) {
  				if (strcmp(iface->driver, *rif) == 0) {
  					log_debug("interfaces", "skip %s: blacklisted 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_whitelist(cfg, interfaces);
  #ifdef ENABLE_OLDIES
  	iflinux_handle_bond(cfg, interfaces);
  #endif
  	interfaces_helper_physical(cfg, interfaces,
  	    &eth_ops,
  	    iflinux_eth_init);
  #ifdef ENABLE_DOT1
  	interfaces_helper_vlan(cfg, interfaces);
  #endif
  	interfaces_helper_mgmt(cfg, addresses);
  	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);
  }
  