]> git.ipfire.org Git - thirdparty/lldpd.git/blobdiff - src/lldpd.c
Add support to read /etc/os-release for system information.
[thirdparty/lldpd.git] / src / lldpd.c
index 13570a70ceb8e4ffdc77ac6b4b4d496752fbd46d..1641f13da588f16dbb6284d1b0b0cc35ec159ea2 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * Copyright (c) 2008 Vincent Bernat <bernat@luffy.cx>
  *
- * Permission to use, copy, modify, and distribute this software for any
+ * 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.
  *
 #include <signal.h>
 #include <sys/stat.h>
 #include <fcntl.h>
-#include <fnmatch.h>
 #include <time.h>
 #include <libgen.h>
 #include <sys/utsname.h>
 #include <sys/types.h>
+#include <sys/wait.h>
 #include <sys/socket.h>
 #include <sys/select.h>
 #include <sys/time.h>
 #include <sys/ioctl.h>
 #include <arpa/inet.h>
-#include <ifaddrs.h>
 #include <net/if_arp.h>
-#include <linux/filter.h>
-#include <linux/if_vlan.h>
-#include <linux/if_packet.h>
-#include <linux/sockios.h>
+
+#if LLDPD_FD_SETSIZE != FD_SETSIZE
+# warning "FD_SETSIZE is set to an inconsistent value."
+#endif
 
 #ifdef USE_SNMP
 #include <net-snmp/net-snmp-config.h>
 
 static void             usage(void);
 
-static int              lldpd_iface_init(struct lldpd *, struct lldpd_hardware *);
-static void             lldpd_iface_init_mtu(struct lldpd *, struct lldpd_hardware *);
-static int              lldpd_iface_close(struct lldpd *, struct lldpd_hardware *);
-static void             lldpd_iface_multicast(struct lldpd *, const char *, int);
-
-/* LLDP: "ether proto 0x88cc and ether dst 01:80:c2:00:00:0e" */
-/* FDP: "ether dst 01:e0:52:cc:cc:cc" */
-/* CDP: "ether dst 01:00:0c:cc:cc:cc" */
-/* SONMP: "ether dst 01:00:81:00:01:00" */
-/* EDP: "ether dst 00:e0:2b:00:00:00" */
-#define LLDPD_FILTER_F                 \
-       { 0x28, 0, 0, 0x0000000c },     \
-       { 0x15, 0, 4, 0x000088cc },     \
-       { 0x20, 0, 0, 0x00000002 },     \
-       { 0x15, 0, 2, 0xc200000e },     \
-       { 0x28, 0, 0, 0x00000000 },     \
-       { 0x15, 11, 12, 0x00000180 },   \
-       { 0x20, 0, 0, 0x00000002 },     \
-       { 0x15, 0, 2, 0x2b000000 },     \
-       { 0x28, 0, 0, 0x00000000 },     \
-       { 0x15, 7, 8, 0x000000e0 },     \
-       { 0x15, 1, 0, 0x0ccccccc },     \
-       { 0x15, 0, 2, 0x81000100 },     \
-       { 0x28, 0, 0, 0x00000000 },     \
-       { 0x15, 3, 4, 0x00000100 },     \
-       { 0x15, 0, 3, 0x52cccccc },     \
-       { 0x28, 0, 0, 0x00000000 },     \
-       { 0x15, 0, 1, 0x000001e0 },     \
-       { 0x6, 0, 0, 0x0000ffff },      \
-       { 0x6, 0, 0, 0x00000000 },
-static struct sock_filter lldpd_filter_f[] = { LLDPD_FILTER_F };
-
 static struct protocol protos[] =
 {
-       { LLDPD_MODE_LLDP, 1, "LLDP", ' ', lldp_send, lldp_decode, NULL,
+       { LLDPD_MODE_LLDP, 1, "LLDP", 'l', lldp_send, lldp_decode, NULL,
          LLDP_MULTICAST_ADDR },
 #ifdef ENABLE_CDP
        { LLDPD_MODE_CDPV1, 0, "CDPv1", 'c', cdpv1_send, cdp_decode, cdpv1_guess,
@@ -106,104 +73,120 @@ static struct protocol protos[] =
          {0,0,0,0,0,0} }
 };
 
-static
-struct lldpd_hardware  *lldpd_hardware_add(struct lldpd *, struct ifaddrs *);
+static void             lldpd_update_localchassis(struct lldpd *);
+static void             lldpd_update_localports(struct lldpd *);
+static void             lldpd_cleanup(struct lldpd *);
 static void             lldpd_loop(struct lldpd *);
 static void             lldpd_shutdown(int);
-static void             lldpd_exit();
+static void             lldpd_exit(void);
 static void             lldpd_send_all(struct lldpd *);
 static void             lldpd_recv_all(struct lldpd *);
+static void             lldpd_hide_all(struct lldpd *);
+static void             lldpd_hide_ports(struct lldpd *, struct lldpd_hardware *, int);
 static int              lldpd_guess_type(struct lldpd *, char *, int);
 static void             lldpd_decode(struct lldpd *, char *, int,
                            struct lldpd_hardware *);
+static void             lldpd_update_chassis(struct lldpd_chassis *,
+                           const struct lldpd_chassis *);
+static char            *lldpd_get_lsb_release(void);
+static char            *lldpd_get_os_release(void);
 #ifdef ENABLE_LLDPMED
 static void             lldpd_med(struct lldpd_chassis *);
 #endif
 
 static char            **saved_argv;
+#ifdef HAVE___PROGNAME
+extern const char      *__progname;
+#else
+# define __progname "lldpd"
+#endif
 
 static void
 usage(void)
 {
-       extern const char       *__progname;
-       fprintf(stderr, "usage: %s [options]\n", __progname);
-       fprintf(stderr, "see manual page lldpd(8) for more information\n");
-       exit(1);
-}
+       fprintf(stderr, "Usage: %s [OPTIONS ...]\n", __progname);
 
-static void
-lldpd_iface_init_mtu(struct lldpd *global, struct lldpd_hardware *hardware)
-{
-       struct ifreq ifr;
-
-       /* get MTU */
-       memset(&ifr, 0, sizeof(ifr));
-       strlcpy(ifr.ifr_name, hardware->h_ifname, sizeof(ifr.ifr_name));
-       if (ioctl(global->g_sock, SIOCGIFMTU, (char*)&ifr) == -1) {
-               LLOG_WARN("unable to get MTU of %s, using 1500", hardware->h_ifname);
-               hardware->h_mtu = 1500;
-       } else
-               hardware->h_mtu = hardware->h_lport.p_mfs = ifr.ifr_mtu;
-}
+       fprintf(stderr, "\n");
 
-static int
-lldpd_iface_init(struct lldpd *global, struct lldpd_hardware *hardware)
-{
-       int status;
-       struct sock_fprog prog;
-
-       lldpd_iface_init_mtu(global, hardware);
-       status = priv_iface_init(hardware, -1);
-       if (status != 0)
-               return status;
-
-       /* Set filter */
-       prog.filter = lldpd_filter_f;
-       prog.len = sizeof(lldpd_filter_f) / sizeof(struct sock_filter);
-       if (setsockopt(hardware->h_raw, SOL_SOCKET, SO_ATTACH_FILTER,
-                &prog, sizeof(prog)) < 0) {
-               LLOG_WARN("unable to change filter for %s", hardware->h_ifname);
-               return ENETDOWN;
-       }
+       fprintf(stderr, "-d       Do not daemonize.\n");
+       fprintf(stderr, "-r       Receive-only mode\n");
+       fprintf(stderr, "-i       Disable LLDP-MED inventory TLV transmission.\n");
+       fprintf(stderr, "-k       Disable advertising of kernel release, version, machine.\n");
+       fprintf(stderr, "-S descr Override the default system description.\n");
+       fprintf(stderr, "-m IP    Specify the management address of this system.\n");
+       fprintf(stderr, "-H mode  Specify the behaviour when detecting multiple neighbors.\n");
+#ifdef ENABLE_LLDPMED
+       fprintf(stderr, "-M class Enable emission of LLDP-MED frame. 'class' should be one of:\n");
+       fprintf(stderr, "             1 Generic Endpoint (Class I)\n");
+       fprintf(stderr, "             2 Media Endpoint (Class II)\n");
+       fprintf(stderr, "             3 Communication Device Endpoints (Class III)\n");
+       fprintf(stderr, "             4 Network Connectivity Device\n");
+#endif
+#ifdef USE_SNMP
+       fprintf(stderr, "-x       Enable SNMP subagent.\n");
+#endif
+       fprintf(stderr, "\n");
 
-       lldpd_iface_multicast(global, hardware->h_ifname, 0);
+#if defined ENABLE_CDP || defined ENABLE_EDP || defined ENABLE_FDP || defined ENABLE_SONMP
+       fprintf(stderr, "Additional protocol support.\n");
+#ifdef ENABLE_CDP
+       fprintf(stderr, "-c       Enable the support of CDP protocol. (Cisco)\n");
+#endif
+#ifdef ENABLE_EDP
+       fprintf(stderr, "-e       Enable the support of EDP protocol. (Extreme)\n");
+#endif
+#ifdef ENABLE_FDP
+       fprintf(stderr, "-f       Enable the support of FDP protocol. (Foundry)\n");
+#endif
+#ifdef ENABLE_SONMP
+       fprintf(stderr, "-s       Enable the support of SONMP protocol. (Nortel)\n");
+#endif
 
-       LLOG_DEBUG("interface %s initialized (fd=%d)", hardware->h_ifname,
-           hardware->h_raw);
-       return 0;
+       fprintf(stderr, "\n");
+#endif
+
+       fprintf(stderr, "see manual page lldpd(8) for more information\n");
+       exit(1);
 }
 
-static void
-lldpd_iface_multicast(struct lldpd *global, const char *name, int remove)
+struct lldpd_hardware *
+lldpd_get_hardware(struct lldpd *cfg, char *name, int index, struct lldpd_ops *ops)
 {
-       int i, rc;
-
-       for (i=0; global->g_protocols[i].mode != 0; i++) {
-               if (!global->g_protocols[i].enabled) continue;
-               if ((rc = priv_iface_multicast(name,
-                           global->g_protocols[i].mac, !remove)) != 0) {
-                       errno = rc;
-                       if (errno != ENOENT)
-                               LLOG_INFO("unable to %s %s address to multicast filter for %s",
-                                   (remove)?"delete":"add",
-                                   global->g_protocols[i].name,
-                                   name);
-               }
+       struct lldpd_hardware *hardware;
+       TAILQ_FOREACH(hardware, &cfg->g_hardware, h_entries) {
+               if ((strcmp(hardware->h_ifname, name) == 0) &&
+                   (hardware->h_ifindex == index) &&
+                   ((!ops) || (ops == hardware->h_ops)))
+                       break;
        }
+       return hardware;
 }
 
-static int
-lldpd_iface_close(struct lldpd *global, struct lldpd_hardware *hardware)
+struct lldpd_hardware *
+lldpd_alloc_hardware(struct lldpd *cfg, char *name)
 {
-       char listen[IFNAMSIZ];
+       struct lldpd_hardware *hardware;
 
-       close(hardware->h_raw);
-       hardware->h_raw = -1;
+       if ((hardware = (struct lldpd_hardware *)
+               calloc(1, sizeof(struct lldpd_hardware))) == NULL)
+               return NULL;
 
-       memcpy(listen, hardware->h_ifname, IFNAMSIZ);
-       lldpd_iface_multicast(global, listen, 1);
+       strlcpy(hardware->h_ifname, name, sizeof(hardware->h_ifname));
+       hardware->h_lport.p_chassis = LOCAL_CHASSIS(cfg);
+       hardware->h_lport.p_chassis->c_refcount++;
+       TAILQ_INIT(&hardware->h_rports);
 
-       return 0;
+#ifdef ENABLE_LLDPMED
+       if (LOCAL_CHASSIS(cfg)->c_med_cap_available) {
+               hardware->h_lport.p_med_cap_enabled = LLDPMED_CAP_CAP;
+               if (!cfg->g_noinventory)
+                       hardware->h_lport.p_med_cap_enabled |= LLDPMED_CAP_IV;
+       }
+#endif
+#ifdef ENABLE_DOT1
+       TAILQ_INIT(&hardware->h_lport.p_vlans);
+#endif
+       return hardware;
 }
 
 #ifdef ENABLE_DOT1
@@ -223,10 +206,9 @@ lldpd_vlan_cleanup(struct lldpd_port *port)
 #endif
 
 /* If `all' is true, clear all information, including information that
-   are not refreshed periodically. If `all' is true, also free the
-   port. */
+   are not refreshed periodically. Port should be freed manually. */
 void
-lldpd_port_cleanup(struct lldpd_port *port, int all)
+lldpd_port_cleanup(struct lldpd *cfg, struct lldpd_port *port, int all)
 {
 #ifdef ENABLE_LLDPMED
        int i;
@@ -241,9 +223,10 @@ lldpd_port_cleanup(struct lldpd_port *port, int all)
        free(port->p_descr);
        if (all) {
                free(port->p_lastframe);
-               if (port->p_chassis) /* chassis may not have been attributed, yet */
+               if (port->p_chassis) /* chassis may not have been attributed, yet */
                        port->p_chassis->c_refcount--;
-               free(port);
+                       port->p_chassis = NULL;
+               }
        }
 }
 
@@ -283,231 +266,57 @@ lldpd_remote_cleanup(struct lldpd *cfg, struct lldpd_hardware *hardware, int all
                }
                if (del) {
                        TAILQ_REMOVE(&hardware->h_rports, port, p_entries);
-                       lldpd_port_cleanup(port, 1);
+                       lldpd_port_cleanup(cfg, port, 1);
+                       free(port);
                }
        }
 }
 
 void
-lldpd_hardware_cleanup(struct lldpd_hardware *hardware)
+lldpd_hardware_cleanup(struct lldpd *cfg, struct lldpd_hardware *hardware)
 {
-       lldpd_port_cleanup(&hardware->h_lport, 1);
+       int i;
+       lldpd_port_cleanup(cfg, &hardware->h_lport, 1);
+       /* If we have a dedicated cleanup function, use it. Otherwise,
+          we just free the hardware-dependent data and close all FD
+          in h_recvfds and h_sendfd. */
+       if (hardware->h_ops->cleanup)
+               hardware->h_ops->cleanup(cfg, hardware);
+       else {
+               free(hardware->h_data);
+               for (i=0; i < LLDPD_FD_SETSIZE; i++)
+                       if (FD_ISSET(i, &hardware->h_recvfds))
+                               close(i);
+               if (hardware->h_sendfd) close(hardware->h_sendfd);
+       }
        free(hardware);
 }
 
-void
+static void
 lldpd_cleanup(struct lldpd *cfg)
 {
        struct lldpd_hardware *hardware, *hardware_next;
+       struct lldpd_chassis *chassis, *chassis_next;
 
        for (hardware = TAILQ_FIRST(&cfg->g_hardware); hardware != NULL;
             hardware = hardware_next) {
                hardware_next = TAILQ_NEXT(hardware, h_entries);
-               if (hardware->h_flags == 0) {
+               if (!hardware->h_flags) {
                        TAILQ_REMOVE(&cfg->g_hardware, hardware, h_entries);
-                       lldpd_iface_close(cfg, hardware);
                        lldpd_remote_cleanup(cfg, hardware, 1);
-                       lldpd_hardware_cleanup(hardware);
+                       lldpd_hardware_cleanup(cfg, hardware);
                } else
                        lldpd_remote_cleanup(cfg, hardware, 0);
        }
-}
-
-static struct lldpd_hardware *
-lldpd_hardware_add(struct lldpd *cfg, struct ifaddrs *ifa)
-{
-#if defined (ENABLE_DOT1) || defined (ENABLE_DOT3)
-       struct ifaddrs *oifap, *oifa;
-#endif
-       struct lldpd_hardware *hardware;
-       struct lldpd_port *port;
-#ifdef ENABLE_DOT1
-       struct lldpd_vlan *vlan;
-       struct vlan_ioctl_args ifv;
-#endif
-#ifdef ENABLE_DOT3
-       struct ethtool_cmd ethc;
-#endif
-       u_int8_t *lladdr;
-
-       TAILQ_FOREACH(hardware, &cfg->g_hardware, h_entries) {
-               if (strcmp(hardware->h_ifname, ifa->ifa_name) == 0)
-                       break;
-       }
-
-       if (hardware == NULL) {
-               if ((hardware = (struct lldpd_hardware *)
-                   calloc(1, sizeof(struct lldpd_hardware))) == NULL)
-                       return (NULL);
-               hardware->h_raw = -1;
-               hardware->h_lport.p_chassis = LOCAL_CHASSIS(cfg);
-               TAILQ_INIT(&hardware->h_rports);
-#ifdef ENABLE_LLDPMED
-               if (LOCAL_CHASSIS(cfg)->c_med_cap_available) {
-                       hardware->h_lport.p_med_cap_enabled = LLDPMED_CAP_CAP;
-                       if (!cfg->g_noinventory)
-                               hardware->h_lport.p_med_cap_enabled |= LLDPMED_CAP_IV;
-               }
-#endif
-#ifdef ENABLE_DOT1
-               TAILQ_INIT(&hardware->h_lport.p_vlans);
-       } else {
-               lldpd_port_cleanup(&hardware->h_lport, 0);
-#endif
-       }
-
-       port = &hardware->h_lport;
-       hardware->h_flags = ifa->ifa_flags;
-
-       strlcpy(hardware->h_ifname, ifa->ifa_name, sizeof(hardware->h_ifname));
-       lladdr = (u_int8_t*)(((struct sockaddr_ll *)ifa->ifa_addr)->sll_addr);
-       memcpy(&hardware->h_lladdr, lladdr, sizeof(hardware->h_lladdr));
-       iface_get_permanent_mac(cfg, hardware);
-       port->p_id_subtype = LLDP_PORTID_SUBTYPE_LLADDR;
-       if ((port->p_id = calloc(1, sizeof(hardware->h_lladdr))) == NULL)
-               fatal(NULL);
-       memcpy(port->p_id, hardware->h_lladdr, sizeof(hardware->h_lladdr));
-       port->p_id_len = sizeof(hardware->h_lladdr);
-       port->p_descr = strdup(hardware->h_ifname);
-
-       if (LOCAL_CHASSIS(cfg)->c_id == NULL) {
-               /* Use the first port's l2 addr as the chassis ID */
-               if ((LOCAL_CHASSIS(cfg)->c_id =
-                       malloc(sizeof(hardware->h_lladdr))) == NULL)
-                       fatal(NULL);
-               LOCAL_CHASSIS(cfg)->c_id_subtype = LLDP_CHASSISID_SUBTYPE_LLADDR;
-               LOCAL_CHASSIS(cfg)->c_id_len = sizeof(hardware->h_lladdr);
-               memcpy(LOCAL_CHASSIS(cfg)->c_id,
-                   hardware->h_lladdr, sizeof(hardware->h_lladdr));
-       }
-
-       /* Get VLANS and aggregation status */
-#if defined (ENABLE_DOT3) || defined (ENABLE_DOT1)
-       if (getifaddrs(&oifap) != 0)
-               fatal("lldpd_hardware_add: failed to get interface list");
-       for (oifa = oifap; oifa != NULL; oifa = oifa->ifa_next) {
-#ifdef ENABLE_DOT1
-               /* Check if we already have checked this one */
-               int skip = 0;
-               TAILQ_FOREACH(vlan, &port->p_vlans, v_entries) {
-                       if (strcmp(vlan->v_name, oifa->ifa_name) == 0) {
-                               skip = 1;
-                               break;
-                       }
-               }
-               if (skip) continue;
-#endif
-
-               /* Aggregation check */
-#ifdef ENABLE_DOT3
-               if (iface_is_bond_slave(cfg, hardware->h_ifname, oifa->ifa_name, NULL))
-                       port->p_aggregid = if_nametoindex(oifa->ifa_name);
-#endif
-
-#ifdef ENABLE_DOT1     
-               /* VLAN check */
-               memset(&ifv, 0, sizeof(ifv));
-               ifv.cmd = GET_VLAN_REALDEV_NAME_CMD;
-               strlcpy(ifv.device1, oifa->ifa_name, sizeof(ifv.device1));
-               if ((ioctl(cfg->g_sock, SIOCGIFVLAN, &ifv) >= 0) &&
-                   ((iface_is_bond_slave(cfg, hardware->h_ifname, ifv.u.device2, NULL)) ||
-                    (iface_is_bridged_to(cfg, hardware->h_ifname, ifv.u.device2)) ||
-                    (strncmp(hardware->h_ifname, ifv.u.device2, sizeof(ifv.u.device2)) == 0))) {
-                       if ((vlan = (struct lldpd_vlan *)
-                            calloc(1, sizeof(struct lldpd_vlan))) == NULL)
-                               continue;
-                       if ((vlan->v_name = strdup(oifa->ifa_name)) == NULL) {
-                               free(vlan);
-                               continue;
-                       }
-                       memset(&ifv, 0, sizeof(ifv));
-                       ifv.cmd = GET_VLAN_VID_CMD;
-                       strlcpy(ifv.device1, oifa->ifa_name, sizeof(ifv.device1));
-                       if (ioctl(cfg->g_sock, SIOCGIFVLAN, &ifv) < 0) {
-                               /* Dunno what happened */
-                               free(vlan->v_name);
-                               free(vlan);
-                       } else {
-                               vlan->v_vid = ifv.u.VID;
-                               TAILQ_INSERT_TAIL(&port->p_vlans, vlan, v_entries);
-                       }
-               }
-#endif
-       }
-       freeifaddrs(oifap);
-#endif
-
-#ifdef ENABLE_DOT3
-       /* MAC/PHY */
-       if (priv_ethtool(hardware->h_ifname, &ethc) == 0) {
-               int j;
-               int advertised_ethtool_to_rfc3636[][2] = {
-                       {ADVERTISED_10baseT_Half, LLDP_DOT3_LINK_AUTONEG_10BASE_T},
-                       {ADVERTISED_10baseT_Full, LLDP_DOT3_LINK_AUTONEG_10BASET_FD},
-                       {ADVERTISED_100baseT_Half, LLDP_DOT3_LINK_AUTONEG_100BASE_TX},
-                       {ADVERTISED_100baseT_Full, LLDP_DOT3_LINK_AUTONEG_100BASE_TXFD},
-                       {ADVERTISED_1000baseT_Half, LLDP_DOT3_LINK_AUTONEG_1000BASE_T},
-                       {ADVERTISED_1000baseT_Full, LLDP_DOT3_LINK_AUTONEG_1000BASE_TFD},
-                       {ADVERTISED_10000baseT_Full, LLDP_DOT3_LINK_AUTONEG_OTHER},
-                       {ADVERTISED_Pause, LLDP_DOT3_LINK_AUTONEG_FDX_PAUSE},
-                       {ADVERTISED_Asym_Pause, LLDP_DOT3_LINK_AUTONEG_FDX_APAUSE},
-                       {ADVERTISED_2500baseX_Full, LLDP_DOT3_LINK_AUTONEG_OTHER},
-                       {0,0}};
-
-               port->p_autoneg_support = (ethc.supported & SUPPORTED_Autoneg) ? 1 : 0;
-               port->p_autoneg_enabled = (ethc.autoneg == AUTONEG_DISABLE) ? 0 : 1;
-               for (j=0; advertised_ethtool_to_rfc3636[j][0]; j++) {
-                       if (ethc.advertising & advertised_ethtool_to_rfc3636[j][0])
-                               port->p_autoneg_advertised |= advertised_ethtool_to_rfc3636[j][1];
-               }
-               switch (ethc.speed) {
-               case SPEED_10:
-                       port->p_mau_type = (ethc.duplex == DUPLEX_FULL) ? \
-                               LLDP_DOT3_MAU_10BASETFD : LLDP_DOT3_MAU_10BASETHD;
-                       if (ethc.port == PORT_BNC) port->p_mau_type = LLDP_DOT3_MAU_10BASE2;
-                       if (ethc.port == PORT_FIBRE)
-                               port->p_mau_type = (ethc.duplex == DUPLEX_FULL) ? \
-                                       LLDP_DOT3_MAU_10BASEFLDF : LLDP_DOT3_MAU_10BASEFLHD;
-                       break;
-               case SPEED_100:
-                       port->p_mau_type = (ethc.duplex == DUPLEX_FULL) ? \
-                               LLDP_DOT3_MAU_100BASETXFD : LLDP_DOT3_MAU_100BASETXHD;
-                       if (ethc.port == PORT_BNC)
-                               port->p_mau_type = (ethc.duplex == DUPLEX_FULL) ? \
-                                       LLDP_DOT3_MAU_100BASET2DF : LLDP_DOT3_MAU_100BASET2HD;
-                       if (ethc.port == PORT_FIBRE)
-                               port->p_mau_type = (ethc.duplex == DUPLEX_FULL) ? \
-                                       LLDP_DOT3_MAU_100BASEFXFD : LLDP_DOT3_MAU_100BASEFXHD;
-                       break;
-               case SPEED_1000:
-                       port->p_mau_type = (ethc.duplex == DUPLEX_FULL) ? \
-                               LLDP_DOT3_MAU_1000BASETFD : LLDP_DOT3_MAU_1000BASETHD;
-                       if (ethc.port == PORT_FIBRE)
-                               port->p_mau_type = (ethc.duplex == DUPLEX_FULL) ? \
-                                       LLDP_DOT3_MAU_1000BASEXFD : LLDP_DOT3_MAU_1000BASEXHD;
-                       break;
-               case SPEED_10000:
-                       port->p_mau_type = (ethc.port == PORT_FIBRE) ? \
-                                       LLDP_DOT3_MAU_10GIGBASEX : LLDP_DOT3_MAU_10GIGBASER;
-                       break;
-               }
-               if (ethc.port == PORT_AUI) port->p_mau_type = LLDP_DOT3_MAU_AUI;
-       } else
-               LLOG_DEBUG("unable to get eth info for %s", hardware->h_ifname);
-#endif
-
-       if (!INTERFACE_OPENED(hardware)) {
 
-               if (lldpd_iface_init(cfg, hardware) != 0) {
-                       LLOG_WARN("unable to initialize %s", hardware->h_ifname);
-                       lldpd_hardware_cleanup(hardware);
-                       return (NULL);
+       for (chassis = TAILQ_FIRST(&cfg->g_chassis); chassis;
+            chassis = chassis_next) {
+               chassis_next = TAILQ_NEXT(chassis, c_entries);
+               if (chassis->c_refcount == 0) {
+                       TAILQ_REMOVE(&cfg->g_chassis, chassis, c_entries);
+                       lldpd_chassis_cleanup(chassis, 1);
                }
-
-               TAILQ_INSERT_TAIL(&cfg->g_hardware, hardware, h_entries);
        }
-
-       return (hardware);
 }
 
 static int
@@ -539,10 +348,16 @@ lldpd_decode(struct lldpd *cfg, char *frame, int s,
        struct lldpd_port *port, *oport = NULL;
        int guess = LLDPD_MODE_LLDP;
 
-       /* Discard VLAN frames */
-       if ((s >= sizeof(struct ethhdr)) &&
-           (((struct ethhdr*)frame)->h_proto == htons(ETHERTYPE_VLAN)))
+       if (s < sizeof(struct ethhdr) + 4)
+               /* Too short, just discard it */
                return;
+       /* Decapsulate VLAN frames */
+       if (((struct ethhdr*)frame)->h_proto == htons(ETHERTYPE_VLAN)) {
+               /* VLAN decapsulation means to shift 4 bytes left the frame from
+                * offset 2*ETH_ALEN */
+               memmove(frame + 2*ETH_ALEN, frame + 2*ETH_ALEN + 4, s - 2*ETH_ALEN);
+               s -= 4;
+       }
 
        TAILQ_FOREACH(oport, &hardware->h_rports, p_entries) {
                if ((oport->p_lastframe != NULL) &&
@@ -601,22 +416,16 @@ lldpd_decode(struct lldpd *cfg, char *frame, int s,
        if (oport) {
                /* The port is known, remove it before adding it back */
                TAILQ_REMOVE(&hardware->h_rports, oport, p_entries);
-               lldpd_port_cleanup(oport, 1);
+               lldpd_port_cleanup(cfg, oport, 1);
+               free(oport);
        }
        if (ochassis) {
-               /* Chassis is known, replace values. Hackish */
-               chassis->c_refcount = ochassis->c_refcount;
-               chassis->c_index = ochassis->c_index;
-               memcpy(&chassis->c_entries, &ochassis->c_entries,
-                   sizeof(chassis->c_entries));
-               lldpd_chassis_cleanup(ochassis, 0);
-               memcpy(ochassis, chassis, sizeof(struct lldpd_chassis));
+               lldpd_update_chassis(ochassis, chassis);
                free(chassis);
                chassis = ochassis;
        } else {
                /* Chassis not known, add it */
                chassis->c_index = ++cfg->g_lastrid;
-               port->p_chassis = chassis;
                chassis->c_refcount = 0;
                TAILQ_INSERT_TAIL(&cfg->g_chassis, chassis, c_entries);
                i = 0; TAILQ_FOREACH(ochassis, &cfg->g_chassis, c_entries) i++;
@@ -632,24 +441,303 @@ lldpd_decode(struct lldpd *cfg, char *frame, int s,
        TAILQ_INSERT_TAIL(&hardware->h_rports, port, p_entries);
        port->p_chassis = chassis;
        port->p_chassis->c_refcount++;
-       i = 0; TAILQ_FOREACH(oport, &hardware->h_rports, p_entries) i++;
-       LLOG_DEBUG("Currently, %s known %d neighbors",
+       /* Several cases are possible :
+            1. chassis is new, its refcount was 0. It is now attached
+               to this port, its refcount is 1.
+            2. chassis already exists and was attached to another
+               port, we increase its refcount accordingly.
+            3. chassis already exists and was attached to the same
+               port, its refcount was decreased with
+               lldpd_port_cleanup() and is now increased again.
+
+          In all cases, if the port already existed, it has been
+          freed with lldpd_port_cleanup() and therefore, the refcount
+          of the chassis that was attached to it is decreased.
+       */
+       i = 0; TAILQ_FOREACH(oport, &hardware->h_rports, p_entries)
+               i++;
+       LLOG_DEBUG("Currently, %s knows %d neighbors",
            hardware->h_ifname, i);
        return;
 }
 
+/* Update chassis `ochassis' with values from `chassis'. */
+static void
+lldpd_update_chassis(struct lldpd_chassis *ochassis,
+    const struct lldpd_chassis *chassis) {
+       TAILQ_ENTRY(lldpd_chassis) entries;
+       /* We want to keep refcount, index and list stuff from the current
+        * chassis */
+       int refcount = ochassis->c_refcount;
+       int index = ochassis->c_index;
+       memcpy(&entries, &ochassis->c_entries,
+           sizeof(entries));
+       /* Make the copy */
+       lldpd_chassis_cleanup(ochassis, 0);
+       memcpy(ochassis, chassis, sizeof(struct lldpd_chassis));
+       /* Restore saved values */
+       ochassis->c_refcount = refcount;
+       ochassis->c_index = index;
+       memcpy(&ochassis->c_entries, &entries, sizeof(entries));
+}
+
+/* Get the output of lsb_release -s -d.  This is a slow function. It should be
+   called once. It return NULL if any problem happens. Otherwise, this is a
+   statically allocated buffer. The result includes the trailing \n  */
+static char *
+lldpd_get_lsb_release() {
+       static char release[1024];
+       char *const command[] = { "lsb_release", "-s", "-d", NULL };
+       int pid, status, devnull, count;
+       int pipefd[2];
+
+       if (pipe(pipefd)) {
+               LLOG_WARN("unable to get a pair of pipes");
+               return NULL;
+       }
+
+       if ((pid = fork()) < 0) {
+               LLOG_WARN("unable to fork");
+               return NULL;
+       }
+       switch (pid) {
+       case 0:
+               /* Child, exec lsb_release */
+               close(pipefd[0]);
+               if ((devnull = open("/dev/null", O_RDWR, 0)) != -1) {
+                       dup2(devnull, STDIN_FILENO);
+                       dup2(devnull, STDERR_FILENO);
+                       dup2(pipefd[1], STDOUT_FILENO);
+                       if (devnull > 2) close(devnull);
+                       if (pipefd[1] > 2) close(pipefd[1]);
+                       execvp("lsb_release", command);
+               }
+               exit(127);
+               break;
+       default:
+               /* Father, read the output from the children */
+               close(pipefd[1]);
+               count = 0;
+               do {
+                       status = read(pipefd[0], release+count, sizeof(release)-count);
+                       if ((status == -1) && (errno == EINTR)) continue;
+                       if (status > 0)
+                               count += status;
+               } while (count < sizeof(release) && (status > 0));
+               if (status < 0) {
+                       LLOG_WARN("unable to read from lsb_release");
+                       close(pipefd[0]);
+                       waitpid(pid, &status, 0);
+                       return NULL;
+               }
+               close(pipefd[0]);
+               if (count >= sizeof(release)) {
+                       LLOG_INFO("output of lsb_release is too large");
+                       waitpid(pid, &status, 0);
+                       return NULL;
+               }
+               status = -1;
+               if (waitpid(pid, &status, 0) != pid)
+                       return NULL;
+               if (!WIFEXITED(status) || (WEXITSTATUS(status) != 0)) {
+                       LLOG_INFO("lsb_release information not available");
+                       return NULL;
+               }
+               if (!count) {
+                       LLOG_INFO("lsb_release returned an empty string");
+                       return NULL;
+               }
+               release[count] = '\0';
+               return release;
+       }
+       /* Should not be here */
+       return NULL;
+}
+
+/* Same like lldpd_get_lsb_release but reads /etc/os-release for PRETTY_NAME=. */
+static char *
+lldpd_get_os_release() {
+       static char release[1024];
+
+       FILE *fp = fopen("/etc/os-release", "r");
+       if (!fp) {
+               LLOG_WARN("Could not open /etc/os-release to read system information");
+               return NULL;
+       }
+
+       char line[1024];
+       char *key, *val;
+
+       while ((fgets(line, 1024, fp) != NULL)) {
+               key = strtok(line, "=");
+               val = strtok(NULL, "=");
+
+               if (strncmp(key, "PRETTY_NAME", 1024) == 0) {
+                       strncpy(release, val, 1024);
+                       break;
+               }
+       }
+       fclose(fp);
+
+       /* Remove trailing newline and all " in the string. */
+       char *ptr1 = release;
+       char *ptr2 = release;
+       while (*ptr1 != 0) {
+               if ((*ptr1 == '"') || (*ptr1 == '\n')) {
+                       ++ptr1;
+               } else {
+                       *ptr2++ = *ptr1++;
+               }
+       }
+       *ptr2 = 0;
+
+       return release;
+}
+
+int
+lldpd_callback_add(struct lldpd *cfg, int fd, void(*fn)(CALLBACK_SIG), void *data)
+{
+       struct lldpd_callback *callback;
+       if ((callback = (struct lldpd_callback *)
+               malloc(sizeof(struct lldpd_callback))) == NULL)
+               return -1;
+       callback->fd = fd;
+       callback->function = fn;
+       callback->data = data;
+       TAILQ_INSERT_TAIL(&cfg->g_callbacks, callback, next);
+       return 0;
+}
+
+void
+lldpd_callback_del(struct lldpd *cfg, int fd, void(*fn)(CALLBACK_SIG))
+{
+       struct lldpd_callback *callback, *callback_next;
+       for (callback = TAILQ_FIRST(&cfg->g_callbacks);
+            callback;
+            callback = callback_next) {
+               callback_next = TAILQ_NEXT(callback, next);
+               if ((callback->fd == fd) &&
+                   (callback->function = fn)) {
+                       free(callback->data);
+                       TAILQ_REMOVE(&cfg->g_callbacks, callback, next);
+                       free(callback);
+               }
+       }
+}
+
+/* Hide unwanted ports depending on smart mode set by the user */
+static void
+lldpd_hide_all(struct lldpd *cfg)
+{
+       struct lldpd_hardware *hardware;
+
+       if (!cfg->g_smart)
+               return;
+       TAILQ_FOREACH(hardware, &cfg->g_hardware, h_entries) {
+               if (cfg->g_smart & SMART_INCOMING_FILTER)
+                       lldpd_hide_ports(cfg, hardware, SMART_INCOMING);
+               if (cfg->g_smart & SMART_OUTGOING_FILTER)
+                       lldpd_hide_ports(cfg, hardware, SMART_OUTGOING);
+       }
+}
+
+static void
+lldpd_hide_ports(struct lldpd *cfg, struct lldpd_hardware *hardware, int mask) {
+       struct lldpd_port *port;
+       int protocols[LLDPD_MODE_MAX+1];
+       char buffer[256];
+       int i, j, k, found;
+       unsigned int min;
+
+       /* Compute the number of occurrences of each protocol */
+       for (i = 0; i <= LLDPD_MODE_MAX; i++) protocols[i] = 0;
+       TAILQ_FOREACH(port, &hardware->h_rports, p_entries)
+               protocols[port->p_protocol]++;
+
+       /* Turn the protocols[] array into an array of
+          enabled/disabled protocols. 1 means enabled, 0
+          means disabled. */
+       min = (unsigned int)-1;
+       for (i = 0; i <= LLDPD_MODE_MAX; i++)
+               if (protocols[i] && (protocols[i] < min))
+                       min = protocols[i];
+       found = 0;
+       for (i = 0; i <= LLDPD_MODE_MAX; i++)
+               if ((protocols[i] == min) && !found) {
+                       /* If we need a tie breaker, we take
+                          the first protocol only */
+                       if (cfg->g_smart & mask &
+                           (SMART_OUTGOING_ONE_PROTO | SMART_INCOMING_ONE_PROTO))
+                               found = 1;
+                       protocols[i] = 1;
+               } else protocols[i] = 0;
+
+       /* We set the p_hidden flag to 1 if the protocol is disabled */
+       TAILQ_FOREACH(port, &hardware->h_rports, p_entries) {
+               if (mask == SMART_OUTGOING)
+                       port->p_hidden_out = protocols[port->p_protocol]?0:1;
+               else
+                       port->p_hidden_in = protocols[port->p_protocol]?0:1;
+       }
+
+       /* If we want only one neighbor, we take the first one */
+       if (cfg->g_smart & mask &
+           (SMART_OUTGOING_ONE_NEIGH | SMART_INCOMING_ONE_NEIGH)) {
+               found = 0;
+               TAILQ_FOREACH(port, &hardware->h_rports, p_entries) {
+                       if (mask == SMART_OUTGOING) {
+                               if (found) port->p_hidden_out = 1;
+                               if (!port->p_hidden_out)
+                                       found = 1;
+                       }
+                       if (mask == SMART_INCOMING) {
+                               if (found) port->p_hidden_in = 1;
+                               if (!port->p_hidden_in)
+                                       found = 1;
+                       }
+               }
+       }
+
+       /* Print a debug message summarizing the operation */
+       for (i = 0; i <= LLDPD_MODE_MAX; i++) protocols[i] = 0;
+       k = j = 0;
+       TAILQ_FOREACH(port, &hardware->h_rports, p_entries) {
+               if (!(((mask == SMART_OUTGOING) && port->p_hidden_out) ||
+                     ((mask == SMART_INCOMING) && port->p_hidden_in))) {
+                       k++;
+                       protocols[port->p_protocol] = 1;
+               }
+               j++;
+       }
+       buffer[0] = '\0';
+       for (i=0; cfg->g_protocols[i].mode != 0; i++) {
+               if (cfg->g_protocols[i].enabled && protocols[cfg->g_protocols[i].mode]) {
+                       if (strlen(buffer) +
+                           strlen(cfg->g_protocols[i].name) + 3 > sizeof(buffer)) {
+                               /* Unlikely, our buffer is too small */
+                               memcpy(buffer + sizeof(buffer) - 4, "...", 4);
+                               break;
+                       }
+                       if (buffer[0])
+                               strcat(buffer, ", ");
+                       strcat(buffer, cfg->g_protocols[i].name);
+               }
+       }
+       LLOG_DEBUG("[%s] %s: %d visible neigh / %d. Protocols: %s.",
+                  (mask == SMART_OUTGOING)?"out filter":"in filter",
+                  hardware->h_ifname, k, j, buffer[0]?buffer:"(none)");
+}
+
 static void
 lldpd_recv_all(struct lldpd *cfg)
 {
        struct lldpd_hardware *hardware;
-       struct lldpd_client *client, *client_next;
+       struct lldpd_callback *callback, *callback_next;
        fd_set rfds;
        struct timeval tv;
-       struct sockaddr_ll from;
-       socklen_t fromlen;
 #ifdef USE_SNMP
-       int fakeblock = 0;
-       struct timeval *tvp = &tv;
+       struct timeval snmptv;
+       int snmpblock = 0;
 #endif
        int rc, nfds, n;
        char *buffer;
@@ -667,25 +755,31 @@ lldpd_recv_all(struct lldpd *cfg)
                
                TAILQ_FOREACH(hardware, &cfg->g_hardware, h_entries) {
                        /* Ignore if interface is down */
-                       if (((hardware->h_flags & IFF_UP) == 0) ||
-                           ((hardware->h_flags & IFF_RUNNING) == 0))
+                       if ((hardware->h_flags & IFF_RUNNING) == 0)
                                continue;
-                       FD_SET(hardware->h_raw, &rfds);
-                       if (nfds < hardware->h_raw)
-                               nfds = hardware->h_raw;
+                       /* This is quite expensive but we don't rely on internal
+                        * structure of fd_set. */
+                       for (n = 0; n < LLDPD_FD_SETSIZE; n++)
+                               if (FD_ISSET(n, &hardware->h_recvfds)) {
+                                       FD_SET(n, &rfds);
+                                       if (nfds < n)
+                                               nfds = n;
+                               }
                }
-               TAILQ_FOREACH(client, &cfg->g_clients, next) {
-                       FD_SET(client->fd, &rfds);
-                       if (nfds < client->fd)
-                               nfds = client->fd;
+               TAILQ_FOREACH(callback, &cfg->g_callbacks, next) {
+                       FD_SET(callback->fd, &rfds);
+                       if (nfds < callback->fd)
+                               nfds = callback->fd;
                }
-               FD_SET(cfg->g_ctl, &rfds);
-               if (nfds < cfg->g_ctl)
-                       nfds = cfg->g_ctl;
                
 #ifdef USE_SNMP
-               if (cfg->g_snmp)
-                       snmp_select_info(&nfds, &rfds, tvp, &fakeblock);
+               if (cfg->g_snmp) {
+                       snmpblock = 0;
+                       memcpy(&snmptv, &tv, sizeof(struct timeval));
+                       snmp_select_info(&nfds, &rfds, &snmptv, &snmpblock);
+                       if (snmpblock == 0)
+                               memcpy(&tv, &snmptv, sizeof(struct timeval));
+               }
 #endif /* USE_SNMP */
                if (nfds == -1) {
                        sleep(cfg->g_delay);
@@ -708,66 +802,33 @@ lldpd_recv_all(struct lldpd *cfg)
                }
 #endif /* USE_SNMP */
                TAILQ_FOREACH(hardware, &cfg->g_hardware, h_entries) {
-                       /* We could have received something on _real_
-                        * interface. However, even in this case, this could be
-                        * just an outgoing packet. We will try to handle both
-                        * cases, but maybe not in the same select. */
-                       if (FD_ISSET(hardware->h_raw, &rfds)) {
-                               if ((buffer = (char *)malloc(
-                                               hardware->h_mtu)) == NULL) {
-                                       LLOG_WARN("failed to alloc reception buffer");
-                                       continue;
-                               }
-                               fromlen = sizeof(from);
-                               if ((n = recvfrom(
-                                               hardware->h_raw,
-                                                   buffer,
-                                                   hardware->h_mtu, 0,
-                                                   (struct sockaddr *)&from,
-                                                   &fromlen)) == -1) {
-                                       LLOG_WARN("error while receiving frame on %s",
-                                           hardware->h_ifname);
-                                       hardware->h_rx_discarded_cnt++;
-                                       free(buffer);
-                                       continue;
-                               }
-                               if (from.sll_pkttype == PACKET_OUTGOING) {
-                                       free(buffer);
-                                       continue;
-                               }
-                               hardware->h_rx_cnt++;
-                               lldpd_decode(cfg, buffer, n, hardware);
-                               free(buffer);
+                       for (n = 0; n < LLDPD_FD_SETSIZE; n++)
+                               if ((FD_ISSET(n, &hardware->h_recvfds)) &&
+                                   (FD_ISSET(n, &rfds))) break;
+                       if (n == LLDPD_FD_SETSIZE) continue;
+                       if ((buffer = (char *)malloc(
+                                       hardware->h_mtu)) == NULL) {
+                               LLOG_WARN("failed to alloc reception buffer");
+                               continue;
                        }
-                       
-               }
-               if (FD_ISSET(cfg->g_ctl, &rfds)) {
-                       if (ctl_accept(cfg, cfg->g_ctl) == -1)
-                               LLOG_WARN("unable to accept new client");
-               }
-               for (client = TAILQ_FIRST(&cfg->g_clients);
-                    client != NULL;
-                    client = client_next) {
-                       client_next = TAILQ_NEXT(client, next);
-                       if (FD_ISSET(client->fd, &rfds)) {
-                               /* Got a message */
-                               if ((buffer = (char *)malloc(MAX_HMSGSIZE)) ==
-                                   NULL) {
-                                       LLOG_WARN("failed to alloc reception buffer");
-                                       continue;
-                               }
-                               if ((n = recv(client->fd, buffer,
-                                           MAX_HMSGSIZE, 0)) == -1) {
-                                       LLOG_WARN("error while receiving message");
-                                       free(buffer);
-                                       continue;
-                               }
-                               if (n > 0)
-                                       client_handle_client(cfg, client, buffer, n);
-                               else
-                                       ctl_close(cfg, client->fd); /* Will use TAILQ_REMOVE ! */
+                       if ((n = hardware->h_ops->recv(cfg, hardware,
+                                   n, buffer, hardware->h_mtu)) == -1) {
                                free(buffer);
+                               continue;
                        }
+                       hardware->h_rx_cnt++;
+                       lldpd_decode(cfg, buffer, n, hardware);
+                       lldpd_hide_all(cfg); /* Immediatly hide */
+                       free(buffer);
+                       break;
+               }
+               for (callback = TAILQ_FIRST(&cfg->g_callbacks);
+                    callback;
+                    callback = callback_next) {
+                       /* Callback function can use TAILQ_REMOVE */
+                       callback_next = TAILQ_NEXT(callback, next);
+                       if (FD_ISSET(callback->fd, &rfds))
+                               callback->function(cfg, callback);
                }
 
 #ifdef USE_SNMP
@@ -784,26 +845,36 @@ lldpd_send_all(struct lldpd *cfg)
 {
        struct lldpd_hardware *hardware;
        struct lldpd_port *port;
-       int i, sent = 0;
+       int i, sent;
 
        cfg->g_lastsent = time(NULL);
        TAILQ_FOREACH(hardware, &cfg->g_hardware, h_entries) {
                /* Ignore if interface is down */
-               if (((hardware->h_flags & IFF_UP) == 0) ||
-                   ((hardware->h_flags & IFF_RUNNING) == 0))
+               if ((hardware->h_flags & IFF_RUNNING) == 0)
                        continue;
 
+               sent = 0;
                for (i=0; cfg->g_protocols[i].mode != 0; i++) {
                        if (!cfg->g_protocols[i].enabled)
                                continue;
                        /* We send only if we have at least one remote system
-                        * speaking this protocol */
+                        * speaking this protocol or if the protocol is forced */
+                       if (cfg->g_protocols[i].enabled > 1) {
+                               cfg->g_protocols[i].send(cfg, hardware);
+                               sent++;
+                               continue;
+                       }
                        TAILQ_FOREACH(port, &hardware->h_rports, p_entries) {
+                               /* If this remote port is disabled, we don't
+                                * consider it */
+                               if (port->p_hidden_out &&
+                                   (cfg->g_smart & SMART_OUTGOING_FILTER))
+                                       continue;
                                if (port->p_protocol ==
                                    cfg->g_protocols[i].mode) {
                                        cfg->g_protocols[i].send(cfg,
                                            hardware);
-                                       sent = 1;
+                                       sent++;
                                        break;
                                }
                        }
@@ -836,20 +907,16 @@ lldpd_med(struct lldpd_chassis *chassis)
 #endif
 
 static void
-lldpd_loop(struct lldpd *cfg)
+lldpd_update_localchassis(struct lldpd *cfg)
 {
-       struct ifaddrs *ifap, *ifa;
-       struct sockaddr_ll *sdl;
-       struct lldpd_hardware *hardware;
+       struct utsname un;
+       char *hp;
        int f;
        char status;
-       struct utsname *un;
-       char *hp;
+       struct lldpd_hardware *hardware;
 
        /* Set system name and description */
-       if ((un = (struct utsname*)malloc(sizeof(struct utsname))) == NULL)
-               fatal(NULL);
-       if (uname(un) != 0)
+       if (uname(&un) != 0)
                fatal("failed to get system information");
        if ((hp = priv_gethostbyname()) == NULL)
                fatal("failed to get system name");
@@ -857,16 +924,30 @@ lldpd_loop(struct lldpd *cfg)
        free(LOCAL_CHASSIS(cfg)->c_descr);
        if ((LOCAL_CHASSIS(cfg)->c_name = strdup(hp)) == NULL)
                fatal(NULL);
-       if (asprintf(&LOCAL_CHASSIS(cfg)->c_descr, "%s %s %s %s",
-               un->sysname, un->release, un->version, un->machine) == -1)
-               fatal("failed to set system description");
+        if (cfg->g_descr_override) {
+                if (asprintf(&LOCAL_CHASSIS(cfg)->c_descr, "%s",
+                       cfg->g_descr_override) == -1)
+                       fatal("failed to set full system description");
+        } else {
+               if (cfg->g_advertise_version) {
+                       if (asprintf(&LOCAL_CHASSIS(cfg)->c_descr, "%s %s %s %s",
+                               cfg->g_lsb_release?cfg->g_lsb_release:"",
+                               un.sysname, un.release, un.machine)
+                                == -1)
+                               fatal("failed to set full system description");
+               } else {
+                       if (asprintf(&LOCAL_CHASSIS(cfg)->c_descr, "%s",
+                                cfg->g_lsb_release?cfg->g_lsb_release:un.sysname) == -1)
+                               fatal("failed to set minimal system description");
+               }
+        }
 
        /* Check forwarding */
-       LOCAL_CHASSIS(cfg)->c_cap_enabled = 0;
        if ((f = priv_open("/proc/sys/net/ipv4/ip_forward")) >= 0) {
-               if ((read(f, &status, 1) == 1) && (status == '1')) {
-                       LOCAL_CHASSIS(cfg)->c_cap_enabled = LLDP_CAP_ROUTER;
-               }
+               if ((read(f, &status, 1) == 1) && (status == '1'))
+                       LOCAL_CHASSIS(cfg)->c_cap_enabled |= LLDP_CAP_ROUTER;
+               else
+                       LOCAL_CHASSIS(cfg)->c_cap_enabled &= ~LLDP_CAP_ROUTER;
                close(f);
        }
 #ifdef ENABLE_LLDPMED
@@ -874,78 +955,82 @@ lldpd_loop(struct lldpd *cfg)
                LOCAL_CHASSIS(cfg)->c_cap_enabled |= LLDP_CAP_TELEPHONE;
        lldpd_med(LOCAL_CHASSIS(cfg));
        free(LOCAL_CHASSIS(cfg)->c_med_sw);
-       LOCAL_CHASSIS(cfg)->c_med_sw = strdup(un->release);
+       if (cfg->g_advertise_version)
+               LOCAL_CHASSIS(cfg)->c_med_sw = strdup(un.release);
+       else
+               LOCAL_CHASSIS(cfg)->c_med_sw = strdup("Unknown");
 #endif
-       free(un);
 
+       /* Set chassis ID if needed */
+       if ((LOCAL_CHASSIS(cfg)->c_id == NULL) &&
+           (hardware = TAILQ_FIRST(&cfg->g_hardware))) {
+               if ((LOCAL_CHASSIS(cfg)->c_id =
+                       malloc(sizeof(hardware->h_lladdr))) == NULL)
+                       fatal(NULL);
+               LOCAL_CHASSIS(cfg)->c_id_subtype = LLDP_CHASSISID_SUBTYPE_LLADDR;
+               LOCAL_CHASSIS(cfg)->c_id_len = sizeof(hardware->h_lladdr);
+               memcpy(LOCAL_CHASSIS(cfg)->c_id,
+                   hardware->h_lladdr, sizeof(hardware->h_lladdr));
+       }
+}
+
+static void
+lldpd_update_localports(struct lldpd *cfg)
+{
+       struct ifaddrs *ifap;
+       struct lldpd_hardware *hardware;
+       lldpd_ifhandlers ifhs[] = {
+               lldpd_ifh_bond, /* Handle bond */
+               lldpd_ifh_eth,  /* Handle classic ethernet interfaces */
+#ifdef ENABLE_DOT1
+               lldpd_ifh_vlan, /* Handle VLAN */
+#endif
+               lldpd_ifh_mgmt, /* Handle management address (if not already handled) */
+               NULL
+       };
+       lldpd_ifhandlers *ifh;
+
+       /* h_flags is set to 0 for each port. If the port is updated, h_flags
+        * will be set to a non-zero value. This will allow us to clean up any
+        * non up-to-date port */
        TAILQ_FOREACH(hardware, &cfg->g_hardware, h_entries)
            hardware->h_flags = 0;
 
-       if (getifaddrs(&ifap) != 0)
-               fatal("lldpd_loop: failed to get interface list");
-
        LOCAL_CHASSIS(cfg)->c_mgmt.s_addr = INADDR_ANY;
-       for (ifa = ifap; ifa != NULL; ifa = ifa->ifa_next) {
-               if (LOCAL_CHASSIS(cfg)->c_mgmt.s_addr == INADDR_ANY)
-                       /* Get management address, if available */
-                       if ((ifa->ifa_addr != NULL) &&
-                           (ifa->ifa_addr->sa_family == AF_INET)) {
-                               struct sockaddr_in *sa;
-                               sa = (struct sockaddr_in *)ifa->ifa_addr;
-                               if ((ntohl(*(u_int32_t*)&sa->sin_addr) != INADDR_LOOPBACK) &&
-                                   (cfg->g_mgmt_pattern == NULL)) {
-                                       memcpy(&LOCAL_CHASSIS(cfg)->c_mgmt,
-                                           &sa->sin_addr,
-                                           sizeof(struct in_addr));
-                                       LOCAL_CHASSIS(cfg)->c_mgmt_if = if_nametoindex(ifa->ifa_name);
-                               }
-                               else if (cfg->g_mgmt_pattern != NULL) {
-                                       char *ip;
-                                       ip = inet_ntoa(sa->sin_addr);
-                                       if (fnmatch(cfg->g_mgmt_pattern,
-                                               ip, 0) == 0) {
-                                               memcpy(&LOCAL_CHASSIS(cfg)->c_mgmt,
-                                                   &sa->sin_addr,
-                                                   sizeof(struct in_addr));
-                                               LOCAL_CHASSIS(cfg)->c_mgmt_if =
-                                                   if_nametoindex(ifa->ifa_name);
-                                       }
-                               }
-                       }
-
-               if (ifa->ifa_addr == NULL ||
-                   ifa->ifa_addr->sa_family != PF_PACKET)
-                       continue;
-
-               sdl = (struct sockaddr_ll *)ifa->ifa_addr;
-               if (sdl->sll_hatype != ARPHRD_ETHER || !sdl->sll_halen)
-                       continue;
-
-               if (iface_is_bridge(cfg, ifa->ifa_name)) {
-                       LOCAL_CHASSIS(cfg)->c_cap_enabled |= LLDP_CAP_BRIDGE;
-                       continue;
-               }
-
-               if ((iface_is_vlan(cfg, ifa->ifa_name)) ||
-                   (iface_is_bond(cfg, ifa->ifa_name)))
-                       continue;
-
-                if (!(ifa->ifa_flags & (IFF_MULTICAST|IFF_BROADCAST)))
-                        continue;
-
-               if (iface_is_wireless(cfg, ifa->ifa_name))
-                       LOCAL_CHASSIS(cfg)->c_cap_enabled |= LLDP_CAP_WLAN;
-
-               if (lldpd_hardware_add(cfg, ifa) == NULL)
-                       LLOG_WARNX("failed to allocate port %s, skip it",
-                               ifa->ifa_name);
-       }
-
+       if (getifaddrs(&ifap) != 0)
+               fatal("lldpd_update_localports: failed to get interface list");
+
+       /* We will run the list of interfaces through a list of interface
+        * handlers. Each handler will create or update some hardware port (and
+        * will set h_flags to a non zero value. The handler can use the list of
+        * interfaces but this is not mandatory. If the interface handler
+        * handles an interface from the list, it should set ifa_flags to 0 to
+        * let know the other handlers that it took care of this interface. This
+        * means that more specific handlers should be before less specific
+        * ones. */
+       for (ifh = ifhs; *ifh != NULL; ifh++)
+               (*ifh)(cfg, ifap);
        freeifaddrs(ifap);
+}
 
+static void
+lldpd_loop(struct lldpd *cfg)
+{
+       /* Main loop.
+          
+          1. Update local ports information
+          2. Clean unwanted (removed) local ports
+          3. Update local chassis information
+          4. Send packets
+          5. Receive packets
+          6. Update smart mode
+       */
+       LOCAL_CHASSIS(cfg)->c_cap_enabled = 0;
+       lldpd_update_localports(cfg);
        lldpd_cleanup(cfg);
-
-       lldpd_send_all(cfg);
+       lldpd_update_localchassis(cfg);
+       if (!cfg->g_receiveonly)
+               lldpd_send_all(cfg);
        lldpd_recv_all(cfg);
 }
 
@@ -962,12 +1047,13 @@ static struct lldpd *gcfg = NULL;
 static void
 lldpd_exit()
 {
-       struct lldpd_hardware *hardware;
+       struct lldpd_hardware *hardware, *hardware_next;
        close(gcfg->g_ctl);
        priv_ctl_cleanup();
-       TAILQ_FOREACH(hardware, &gcfg->g_hardware, h_entries) {
-               if (INTERFACE_OPENED(hardware))
-                       lldpd_iface_close(gcfg, hardware);
+       for (hardware = TAILQ_FIRST(&gcfg->g_hardware); hardware != NULL;
+            hardware = hardware_next) {
+               hardware_next = TAILQ_NEXT(hardware, h_entries);
+               lldpd_hardware_cleanup(gcfg, hardware);
        }
 #ifdef USE_SNMP
        if (gcfg->g_snmp)
@@ -975,41 +1061,90 @@ lldpd_exit()
 #endif /* USE_SNMP */
 }
 
+struct intint { int a; int b; };
+static const struct intint filters[] = {
+       {  0, 0 },
+       {  1, SMART_INCOMING_FILTER | SMART_INCOMING_ONE_PROTO |
+             SMART_OUTGOING_FILTER | SMART_OUTGOING_ONE_PROTO },
+       {  2, SMART_INCOMING_FILTER | SMART_INCOMING_ONE_PROTO },
+       {  3, SMART_OUTGOING_FILTER | SMART_OUTGOING_ONE_PROTO },
+       {  4, SMART_INCOMING_FILTER | SMART_OUTGOING_FILTER },
+       {  5, SMART_INCOMING_FILTER },
+       {  6, SMART_OUTGOING_FILTER },
+       {  7, SMART_INCOMING_FILTER | SMART_INCOMING_ONE_PROTO | SMART_INCOMING_ONE_NEIGH |
+             SMART_OUTGOING_FILTER | SMART_OUTGOING_ONE_PROTO },
+       {  8, SMART_INCOMING_FILTER | SMART_INCOMING_ONE_PROTO | SMART_INCOMING_ONE_NEIGH },
+       {  9, SMART_INCOMING_FILTER | SMART_INCOMING_ONE_NEIGH |
+             SMART_OUTGOING_FILTER | SMART_OUTGOING_ONE_PROTO },
+       { 10, SMART_OUTGOING_FILTER | SMART_OUTGOING_ONE_NEIGH },
+       { 11, SMART_INCOMING_FILTER | SMART_INCOMING_ONE_NEIGH },
+       { 12, SMART_INCOMING_FILTER | SMART_INCOMING_ONE_NEIGH |
+             SMART_OUTGOING_FILTER | SMART_OUTGOING_ONE_NEIGH },
+       { 13, SMART_INCOMING_FILTER | SMART_INCOMING_ONE_NEIGH |
+             SMART_OUTGOING_FILTER },
+       { 14, SMART_INCOMING_FILTER | SMART_INCOMING_ONE_PROTO |
+             SMART_OUTGOING_FILTER | SMART_OUTGOING_ONE_NEIGH },
+       { 15, SMART_INCOMING_FILTER | SMART_INCOMING_ONE_PROTO |
+             SMART_OUTGOING_FILTER },
+       { 16, SMART_INCOMING_FILTER | SMART_INCOMING_ONE_PROTO | SMART_INCOMING_ONE_NEIGH |
+             SMART_OUTGOING_FILTER | SMART_OUTGOING_ONE_NEIGH },
+       { 17, SMART_INCOMING_FILTER | SMART_INCOMING_ONE_PROTO | SMART_INCOMING_ONE_NEIGH |
+             SMART_OUTGOING_FILTER },
+       { 18, SMART_INCOMING_FILTER |
+             SMART_OUTGOING_FILTER | SMART_OUTGOING_ONE_NEIGH },
+       { 19, SMART_INCOMING_FILTER |
+             SMART_OUTGOING_FILTER | SMART_OUTGOING_ONE_PROTO },
+       { -1, 0 }
+};
+
 int
-main(int argc, char *argv[])
+lldpd_main(int argc, char *argv[])
 {
        struct lldpd *cfg;
        struct lldpd_chassis *lchassis;
        int ch, debug = 0;
 #ifdef USE_SNMP
        int snmp = 0;
+       char *agentx = NULL;    /* AgentX socket */
 #endif
        char *mgmtp = NULL;
-       char *popt, opts[] = "dxm:p:M:i@                    ";
-       int i, found;
+       char *popt, opts[] = 
+               "H:hkrdxX:m:p:M:S:i@                    ";
+       int i, found, advertise_version = 1;
 #ifdef ENABLE_LLDPMED
        int lldpmed = 0, noinventory = 0;
 #endif
+        char *descr_override = NULL;
+       char *lsb_release = NULL;
+       int smart = 15;
+       int receiveonly = 0;
 
        saved_argv = argv;
 
        /*
         * Get and parse command line options
         */
-       popt = index(opts, '@');
-       for (i=0; protos[i].mode != 0; i++) {
-               if (protos[i].enabled == 1) continue;
+       popt = strchr(opts, '@');
+       for (i=0; protos[i].mode != 0; i++)
                *(popt++) = protos[i].arg;
-       }
        *popt = '\0';
        while ((ch = getopt(argc, argv, opts)) != -1) {
                switch (ch) {
+               case 'h':
+                       usage();
+                       break;
                case 'd':
                        debug++;
                        break;
+               case 'r':
+                       receiveonly = 1;
+                       break;
                case 'm':
                        mgmtp = optarg;
                        break;
+               case 'k':
+                       advertise_version = 0;
+                       break;
 #ifdef ENABLE_LLDPMED
                case 'M':
                        lldpmed = atoi(optarg);
@@ -1029,20 +1164,37 @@ main(int argc, char *argv[])
                        usage();
                        break;
 #endif
-               case 'x':
 #ifdef USE_SNMP
+               case 'x':
                        snmp = 1;
+                       break;
+               case 'X':
+                       snmp = 1;
+                       agentx = optarg;
+                       break;
 #else
+               case 'x':
+               case 'X':
                        fprintf(stderr, "SNMP support is not built-in\n");
                        usage();
 #endif
                        break;
+                case 'S':
+                        descr_override = strdup(optarg);
+                        break;
+               case 'H':
+                       smart = atoi(optarg);
+                       break;
                default:
                        found = 0;
                        for (i=0; protos[i].mode != 0; i++) {
-                               if (protos[i].enabled) continue;
                                if (ch == protos[i].arg) {
-                                       protos[i].enabled = 1;
+                                       protos[i].enabled++;
+                                       /* When an argument enable
+                                          several protocols, only the
+                                          first one can be forced. */
+                                       if (found && protos[i].enabled > 1)
+                                               protos[i].enabled = 1;
                                        found = 1;
                                }
                        }
@@ -1050,8 +1202,17 @@ main(int argc, char *argv[])
                                usage();
                }
        }
+
+       /* Set correct smart mode */
+       for (i=0; (filters[i].a != -1) && (filters[i].a != smart); i++);
+       if (filters[i].a == -1) {
+               fprintf(stderr, "Incorrect mode for -H\n");
+               usage();
+       }
+       smart = filters[i].b;
        
-       log_init(debug);
+       log_init(debug, __progname);
+       tzset();                /* Get timezone info before chroot */
 
        if (!debug) {
                int pid;
@@ -1069,6 +1230,13 @@ main(int argc, char *argv[])
                close(pid);
        }
 
+       /* Try to read system information from /etc/os-release if possible.
+          Fall back to lsb_release for compatibility. */
+       lsb_release = lldpd_get_os_release();
+       if (!lsb_release) {
+               lsb_release = lldpd_get_lsb_release();
+       }
+
        priv_init(PRIVSEP_CHROOT);
 
        if ((cfg = (struct lldpd *)
@@ -1076,12 +1244,22 @@ main(int argc, char *argv[])
                fatal(NULL);
 
        cfg->g_mgmt_pattern = mgmtp;
+       cfg->g_smart = smart;
+       cfg->g_receiveonly = receiveonly;
 
        /* Get ioctl socket */
        if ((cfg->g_sock = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
                fatal("failed to get ioctl socket");
        cfg->g_delay = LLDPD_TX_DELAY;
 
+       /* Description */
+       if (!(cfg->g_advertise_version = advertise_version))
+               /* Remove the \n */
+               lsb_release[strlen(lsb_release) - 1] = '\0';
+       cfg->g_lsb_release = lsb_release;
+        if (descr_override)
+           cfg->g_descr_override = descr_override;
+
        /* Set system capabilities */
        if ((lchassis = (struct lldpd_chassis*)
                calloc(1, sizeof(struct lldpd_chassis))) == NULL)
@@ -1094,7 +1272,8 @@ main(int argc, char *argv[])
                        lchassis->c_cap_available |= LLDP_CAP_TELEPHONE;
                lchassis->c_med_type = lldpmed;
                lchassis->c_med_cap_available = LLDPMED_CAP_CAP |
-                   LLDPMED_CAP_IV | LLDPMED_CAP_LOCATION;
+                   LLDPMED_CAP_IV | LLDPMED_CAP_LOCATION |
+                   LLDPMED_CAP_POLICY | LLDPMED_CAP_MDI_PSE | LLDPMED_CAP_MDI_PD;
                cfg->g_noinventory = noinventory;
        } else
                cfg->g_noinventory = 1;
@@ -1105,27 +1284,32 @@ main(int argc, char *argv[])
 
        cfg->g_protocols = protos;
        for (i=0; protos[i].mode != 0; i++)
-               if (protos[i].enabled) {
+               if (protos[i].enabled > 1)
+                       LLOG_INFO("protocol %s enabled and forced", protos[i].name);
+               else if (protos[i].enabled)
                        LLOG_INFO("protocol %s enabled", protos[i].name);
-               else
+               else
                        LLOG_INFO("protocol %s disabled", protos[i].name);
 
        TAILQ_INIT(&cfg->g_hardware);
        TAILQ_INIT(&cfg->g_chassis);
        TAILQ_INSERT_TAIL(&cfg->g_chassis, lchassis, c_entries);
-       lchassis->c_refcount++;
+       lchassis->c_refcount++; /* We should always keep a reference to local chassis */
+
+       TAILQ_INIT(&cfg->g_callbacks);
 
 #ifdef USE_SNMP
        if (snmp) {
                cfg->g_snmp = 1;
-               agent_init(cfg, debug);
+               agent_init(cfg, agentx, debug);
        }
 #endif /* USE_SNMP */
 
        /* Create socket */
-       if ((cfg->g_ctl = priv_ctl_create(cfg)) == -1)
+       if ((cfg->g_ctl = priv_ctl_create()) == -1)
                fatalx("unable to create control socket " LLDPD_CTL_SOCKET);
-       TAILQ_INIT(&cfg->g_clients);
+       if (lldpd_callback_add(cfg, cfg->g_ctl, ctl_accept, NULL) != 0)
+               fatalx("unable to add callback for control socket");
 
        gcfg = cfg;
        if (atexit(lldpd_exit) != 0) {