]> 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 f810a3522d5cba1566e8c75913f18e46888e1018..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.
  *
@@ -26,6 +26,7 @@
 #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 <arpa/inet.h>
 #include <net/if_arp.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>
 #include <net-snmp/net-snmp-includes.h>
@@ -44,7 +49,7 @@ static void            usage(void);
 
 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,
@@ -73,25 +78,73 @@ 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, "Usage: %s [OPTIONS ...]\n", __progname);
+
+       fprintf(stderr, "\n");
+
+       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");
+
+#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
+
+       fprintf(stderr, "\n");
+#endif
+
        fprintf(stderr, "see manual page lldpd(8) for more information\n");
        exit(1);
 }
@@ -120,6 +173,7 @@ lldpd_alloc_hardware(struct lldpd *cfg, char *name)
 
        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);
 
 #ifdef ENABLE_LLDPMED
@@ -154,7 +208,7 @@ lldpd_vlan_cleanup(struct lldpd_port *port)
 /* If `all' is true, clear all information, including information that
    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;
@@ -169,8 +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--;
+                       port->p_chassis = NULL;
+               }
        }
 }
 
@@ -210,7 +266,7 @@ 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);
                }
        }
@@ -220,7 +276,7 @@ void
 lldpd_hardware_cleanup(struct lldpd *cfg, struct lldpd_hardware *hardware)
 {
        int i;
-       lldpd_port_cleanup(&hardware->h_lport, 1);
+       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. */
@@ -228,7 +284,7 @@ lldpd_hardware_cleanup(struct lldpd *cfg, struct lldpd_hardware *hardware)
                hardware->h_ops->cleanup(cfg, hardware);
        else {
                free(hardware->h_data);
-               for (i=0; i < FD_SETSIZE; i++)
+               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);
@@ -240,6 +296,7 @@ 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) {
@@ -251,6 +308,15 @@ lldpd_cleanup(struct lldpd *cfg)
                } else
                        lldpd_remote_cleanup(cfg, hardware, 0);
        }
+
+       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);
+               }
+       }
 }
 
 static int
@@ -282,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) &&
@@ -344,7 +416,7 @@ 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) {
@@ -354,7 +426,6 @@ lldpd_decode(struct lldpd *cfg, char *frame, int s,
        } 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++;
@@ -370,8 +441,22 @@ 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;
 }
@@ -396,6 +481,119 @@ lldpd_update_chassis(struct lldpd_chassis *ochassis,
        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)
 {
@@ -427,6 +625,109 @@ lldpd_callback_del(struct lldpd *cfg, int fd, void(*fn)(CALLBACK_SIG))
        }
 }
 
+/* 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)
 {
@@ -435,8 +736,8 @@ lldpd_recv_all(struct lldpd *cfg)
        fd_set rfds;
        struct timeval tv;
 #ifdef USE_SNMP
-       int fakeblock = 0;
-       struct timeval *tvp = &tv;
+       struct timeval snmptv;
+       int snmpblock = 0;
 #endif
        int rc, nfds, n;
        char *buffer;
@@ -458,7 +759,7 @@ lldpd_recv_all(struct lldpd *cfg)
                                continue;
                        /* This is quite expensive but we don't rely on internal
                         * structure of fd_set. */
-                       for (n = 0; n < FD_SETSIZE; n++)
+                       for (n = 0; n < LLDPD_FD_SETSIZE; n++)
                                if (FD_ISSET(n, &hardware->h_recvfds)) {
                                        FD_SET(n, &rfds);
                                        if (nfds < n)
@@ -472,8 +773,13 @@ lldpd_recv_all(struct lldpd *cfg)
                }
                
 #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);
@@ -496,10 +802,10 @@ lldpd_recv_all(struct lldpd *cfg)
                }
 #endif /* USE_SNMP */
                TAILQ_FOREACH(hardware, &cfg->g_hardware, h_entries) {
-                       for (n = 0; n < FD_SETSIZE; n++)
+                       for (n = 0; n < LLDPD_FD_SETSIZE; n++)
                                if ((FD_ISSET(n, &hardware->h_recvfds)) &&
                                    (FD_ISSET(n, &rfds))) break;
-                       if (n == FD_SETSIZE) continue;
+                       if (n == LLDPD_FD_SETSIZE) continue;
                        if ((buffer = (char *)malloc(
                                        hardware->h_mtu)) == NULL) {
                                LLOG_WARN("failed to alloc reception buffer");
@@ -512,6 +818,7 @@ lldpd_recv_all(struct lldpd *cfg)
                        }
                        hardware->h_rx_cnt++;
                        lldpd_decode(cfg, buffer, n, hardware);
+                       lldpd_hide_all(cfg); /* Immediatly hide */
                        free(buffer);
                        break;
                }
@@ -538,7 +845,7 @@ 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) {
@@ -546,17 +853,28 @@ lldpd_send_all(struct lldpd *cfg)
                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;
                                }
                        }
@@ -606,20 +924,30 @@ lldpd_update_localchassis(struct lldpd *cfg)
        free(LOCAL_CHASSIS(cfg)->c_descr);
        if ((LOCAL_CHASSIS(cfg)->c_name = strdup(hp)) == NULL)
                fatal(NULL);
-       if (cfg->g_advertise_version) {
-               if (asprintf(&LOCAL_CHASSIS(cfg)->c_descr, "%s %s %s %s",
-                       un.sysname, un.release, un.version, un.machine) == -1)
+        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 (asprintf(&LOCAL_CHASSIS(cfg)->c_descr, "%s", un.sysname) == -1)
-                       fatal("failed to set minimal 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 */
        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
@@ -695,12 +1023,14 @@ lldpd_loop(struct lldpd *cfg)
           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_update_localchassis(cfg);
-       lldpd_send_all(cfg);
+       if (!cfg->g_receiveonly)
+               lldpd_send_all(cfg);
        lldpd_recv_all(cfg);
 }
 
@@ -731,6 +1061,42 @@ 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
 lldpd_main(int argc, char *argv[])
 {
@@ -739,42 +1105,40 @@ lldpd_main(int argc, char *argv[])
        int ch, debug = 0;
 #ifdef USE_SNMP
        int snmp = 0;
+       char *agentx = NULL;    /* AgentX socket */
 #endif
        char *mgmtp = NULL;
        char *popt, opts[] = 
-#ifdef ENABLE_LISTENVLAN
-               "v"
-#endif
-               "kdxm:p:M:i@                    ";
+               "H:hkrdxX:m:p:M:S:i@                    ";
        int i, found, advertise_version = 1;
-#ifdef ENABLE_LISTENVLAN
-       int vlan = 0;
-#endif
 #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) {
-#ifdef ENABLE_LISTENVLAN
-               case 'v':
-                       vlan = 1;
+               case 'h':
+                       usage();
                        break;
-#endif
                case 'd':
                        debug++;
                        break;
+               case 'r':
+                       receiveonly = 1;
+                       break;
                case 'm':
                        mgmtp = optarg;
                        break;
@@ -800,20 +1164,37 @@ lldpd_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;
                                }
                        }
@@ -821,8 +1202,17 @@ lldpd_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;
@@ -840,6 +1230,13 @@ lldpd_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 *)
@@ -847,16 +1244,22 @@ lldpd_main(int argc, char *argv[])
                fatal(NULL);
 
        cfg->g_mgmt_pattern = mgmtp;
-       cfg->g_advertise_version = advertise_version;
-#ifdef ENABLE_LISTENVLAN
-       cfg->g_listen_vlans = vlan;
-#endif
+       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)
@@ -869,7 +1272,8 @@ lldpd_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;
@@ -880,27 +1284,29 @@ lldpd_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);
        if (lldpd_callback_add(cfg, cfg->g_ctl, ctl_accept, NULL) != 0)
                fatalx("unable to add callback for control socket");