]> git.ipfire.org Git - thirdparty/lldpd.git/blobdiff - src/daemon/lldpd.c
lldp: don't use chassis TTL anymore
[thirdparty/lldpd.git] / src / daemon / lldpd.c
index ff854c3e1f0084a43386aef2a2004ba8fb3bedf5..798bd602518ca986f70232ee0a1f0126b1c75aec 100644 (file)
@@ -16,6 +16,7 @@
  */
 
 #include "lldpd.h"
+#include "trace.h"
 
 #include <stdio.h>
 #include <unistd.h>
@@ -43,7 +44,9 @@ static void            usage(void);
 static struct protocol protos[] =
 {
        { LLDPD_MODE_LLDP, 1, "LLDP", 'l', lldp_send, lldp_decode, NULL,
-         LLDP_MULTICAST_ADDR },
+         LLDP_ADDR_NEAREST_BRIDGE,
+         LLDP_ADDR_NEAREST_NONTPMR_BRIDGE,
+         LLDP_ADDR_NEAREST_CUSTOMER_BRIDGE },
 #ifdef ENABLE_CDP
        { LLDPD_MODE_CDPV1, 0, "CDPv1", 'c', cdpv1_send, cdp_decode, cdpv1_guess,
          CDP_MULTICAST_ADDR },
@@ -126,18 +129,72 @@ usage(void)
 }
 
 struct lldpd_hardware *
-lldpd_get_hardware(struct lldpd *cfg, char *name, int index, struct lldpd_ops *ops)
+lldpd_get_hardware(struct lldpd *cfg, char *name, int index)
 {
        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)))
+                   (hardware->h_ifindex == index))
                        break;
        }
        return hardware;
 }
 
+/**
+ * Allocate the default local port. This port will be cloned each time we need a
+ * new local port.
+ */
+static void
+lldpd_alloc_default_local_port(struct lldpd *cfg)
+{
+       struct lldpd_port *port;
+
+       if ((port = (struct lldpd_port *)
+               calloc(1, sizeof(struct lldpd_port))) == NULL)
+               fatal("main", NULL);
+
+#ifdef ENABLE_DOT1
+       TAILQ_INIT(&port->p_vlans);
+       TAILQ_INIT(&port->p_ppvids);
+       TAILQ_INIT(&port->p_pids);
+#endif
+#ifdef ENABLE_CUSTOM
+       TAILQ_INIT(&port->p_custom_list);
+#endif
+       cfg->g_default_local_port = port;
+}
+
+/**
+ * Clone a given port. The destination needs to be already allocated.
+ */
+static int
+lldpd_clone_port(struct lldpd_port *destination, struct lldpd_port *source)
+{
+
+       u_int8_t *output = NULL;
+       ssize_t output_len;
+       struct lldpd_port *cloned = NULL;
+       output_len = lldpd_port_serialize(source, (void**)&output);
+       if (output_len == -1 ||
+           lldpd_port_unserialize(output, output_len, &cloned) <= 0) {
+               log_warnx("alloc", "unable to clone default port");
+               free(output);
+               return -1;
+       }
+       memcpy(destination, cloned, sizeof(struct lldpd_port));
+       free(cloned);
+       free(output);
+#ifdef ENABLE_DOT1
+       marshal_repair_tailq(lldpd_vlan, &destination->p_vlans, v_entries);
+       marshal_repair_tailq(lldpd_ppvid, &destination->p_ppvids, p_entries);
+       marshal_repair_tailq(lldpd_pi, &destination->p_pids, p_entries);
+#endif
+#ifdef ENABLE_CUSTOM
+       marshal_repair_tailq(lldpd_custom, &destination->p_custom_list, next);
+#endif
+       return 0;
+}
+
 struct lldpd_hardware *
 lldpd_alloc_hardware(struct lldpd *cfg, char *name, int index)
 {
@@ -149,6 +206,13 @@ lldpd_alloc_hardware(struct lldpd *cfg, char *name, int index)
                calloc(1, sizeof(struct lldpd_hardware))) == NULL)
                return NULL;
 
+       /* Clone default local port */
+       if (lldpd_clone_port(&hardware->h_lport, cfg->g_default_local_port) == -1) {
+               log_warnx("alloc", "unable to clone default port");
+               free(hardware);
+               return NULL;
+       }
+
        hardware->h_cfg = cfg;
        strlcpy(hardware->h_ifname, name, sizeof(hardware->h_ifname));
        hardware->h_ifindex = index;
@@ -163,11 +227,6 @@ lldpd_alloc_hardware(struct lldpd *cfg, char *name, int index)
                        hardware->h_lport.p_med_cap_enabled |= LLDP_MED_CAP_IV;
        }
 #endif
-#ifdef ENABLE_DOT1
-       TAILQ_INIT(&hardware->h_lport.p_vlans);
-       TAILQ_INIT(&hardware->h_lport.p_ppvids);
-       TAILQ_INIT(&hardware->h_lport.p_pids);
-#endif
 
        levent_hardware_init(hardware);
        return hardware;
@@ -194,7 +253,6 @@ lldpd_alloc_mgmt(int family, void *addrptr, size_t addrsize, u_int32_t iface)
                return NULL;
        }
        mgmt->m_family = family;
-       assert(addrsize <= LLDPD_MGMT_MAXADDRSIZE);
        memcpy(&mgmt->m_addr, addrptr, addrsize);
        mgmt->m_addrsize = addrsize;
        mgmt->m_iface = iface;
@@ -206,8 +264,11 @@ lldpd_hardware_cleanup(struct lldpd *cfg, struct lldpd_hardware *hardware)
 {
        log_debug("alloc", "cleanup hardware port %s", hardware->h_ifname);
 
+       free(hardware->h_lport_previous);
+       free(hardware->h_lchassis_previous_id);
+       free(hardware->h_lport_previous_id);
        lldpd_port_cleanup(&hardware->h_lport, 1);
-       if (hardware->h_ops->cleanup)
+       if (hardware->h_ops && hardware->h_ops->cleanup)
                hardware->h_ops->cleanup(cfg, hardware);
        levent_hardware_release(hardware);
        free(hardware);
@@ -230,15 +291,15 @@ lldpd_display_neighbors(struct lldpd *cfg)
                }
                if (neighbors == 0)
                        priv_iface_description(hardware->h_ifname,
-                           "lldpd: no neighbor found");
-               else if (neighbors == 1 && neighbor) {
-                       if (asprintf(&description, "lldpd: connected to %s",
+                           "");
+               else if (neighbors == 1 && neighbor && *neighbor != '\0') {
+                       if (asprintf(&description, "%s",
                                neighbor) != -1) {
                                priv_iface_description(hardware->h_ifname, description);
                                free(description);
                        }
                } else {
-                       if (asprintf(&description, "lldpd: %d neighbor%s",
+                       if (asprintf(&description, "%d neighbor%s",
                                neighbors, (neighbors > 1)?"s":"") != -1) {
                                priv_iface_description(hardware->h_ifname,
                                    description);
@@ -261,11 +322,11 @@ lldpd_count_neighbors(struct lldpd *cfg)
        }
        neighbors--;
        if (neighbors == 0)
-               setproctitle("no neighbor");
-       else if (neighbors == 1 && neighbor)
-               setproctitle("connected to %s", neighbor);
+               setproctitle("no neighbor.");
+       else if (neighbors == 1 && neighbor && *neighbor != '\0')
+               setproctitle("connected to %s.", neighbor);
        else
-               setproctitle("%d neighbor%s", neighbors,
+               setproctitle("%d neighbor%s.", neighbors,
                    (neighbors > 1)?"s":"");
 #endif
        lldpd_display_neighbors(cfg);
@@ -275,6 +336,9 @@ static void
 notify_clients_deletion(struct lldpd_hardware *hardware,
     struct lldpd_port *rport)
 {
+       TRACE(LLDPD_NEIGHBOR_DELETE(hardware->h_ifname,
+               rport->p_chassis->c_name,
+               rport->p_descr));
        levent_ctl_notify(hardware->h_ifname, NEIGHBOR_CHANGE_DELETED,
            rport);
 #ifdef USE_SNMP
@@ -288,15 +352,18 @@ lldpd_reset_timer(struct lldpd *cfg)
        /* Reset timer for ports that have been changed. */
        struct lldpd_hardware *hardware;
        TAILQ_FOREACH(hardware, &cfg->g_hardware, h_entries) {
-               /* We need to compute a checksum of the local port. To do this,
-                * we zero out fields that are not significant, marshal the
-                * port, compute the checksum, then restore. */
+               /* We keep a flat copy of the local port to see if there is any
+                * change. To do this, we zero out fields that are not
+                * significant, marshal the port, then restore. */
                struct lldpd_port *port = &hardware->h_lport;
-               u_int16_t cksum;
+               /* Take the current flags into account to detect a change. */
+               port->_p_hardware_flags = hardware->h_flags;
                u_int8_t *output = NULL;
-               size_t output_len;
-               char save[offsetof(struct lldpd_port, p_id_subtype)];
+               ssize_t output_len;
+               char save[LLDPD_PORT_START_MARKER];
                memcpy(save, port, sizeof(save));
+               /* coverity[suspicious_sizeof]
+                  We intentionally partially memset port */
                memset(port, 0, sizeof(save));
                output_len = lldpd_port_serialize(port, (void**)&output);
                memcpy(port, save, sizeof(save));
@@ -306,18 +373,40 @@ lldpd_reset_timer(struct lldpd *cfg)
                            hardware->h_ifname);
                        continue;
                }
-               cksum = frame_checksum(output, output_len, 0);
-               free(output);
-               if (cksum != hardware->h_lport_cksum) {
-                       log_info("localchassis",
-                           "change detected for port %s, resetting its timer",
+
+               /* Compare with the previous value */
+               if (hardware->h_lport_previous &&
+                   output_len == hardware->h_lport_previous_len &&
+                   !memcmp(output, hardware->h_lport_previous, output_len)) {
+                       log_debug("localchassis",
+                           "no change detected for port %s",
                            hardware->h_ifname);
-                       hardware->h_lport_cksum = cksum;
-                       levent_schedule_pdu(hardware);
                } else {
                        log_debug("localchassis",
-                           "no change detected for port %s",
+                           "change detected for port %s, resetting its timer",
                            hardware->h_ifname);
+                       levent_schedule_pdu(hardware);
+               }
+
+               /* Update the value */
+               free(hardware->h_lport_previous);
+               hardware->h_lport_previous = output;
+               hardware->h_lport_previous_len = output_len;
+       }
+}
+
+static void
+lldpd_all_chassis_cleanup(struct lldpd *cfg)
+{
+       struct lldpd_chassis *chassis, *chassis_next;
+       log_debug("localchassis", "cleanup all chassis");
+
+       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);
                }
        }
 }
@@ -326,7 +415,6 @@ void
 lldpd_cleanup(struct lldpd *cfg)
 {
        struct lldpd_hardware *hardware, *hardware_next;
-       struct lldpd_chassis *chassis, *chassis_next;
 
        log_debug("localchassis", "cleanup all ports");
 
@@ -334,26 +422,19 @@ lldpd_cleanup(struct lldpd *cfg)
             hardware = hardware_next) {
                hardware_next = TAILQ_NEXT(hardware, h_entries);
                if (!hardware->h_flags) {
+                       TRACE(LLDPD_INTERFACES_DELETE(hardware->h_ifname));
                        TAILQ_REMOVE(&cfg->g_hardware, hardware, h_entries);
-                       lldpd_remote_cleanup(hardware, NULL);
+                       lldpd_remote_cleanup(hardware, notify_clients_deletion, 1);
                        lldpd_hardware_cleanup(cfg, hardware);
-               } else
-                       lldpd_remote_cleanup(hardware, notify_clients_deletion);
-       }
-
-       log_debug("localchassis", "cleanup all chassis");
-
-       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);
+               } else {
+                       lldpd_remote_cleanup(hardware, notify_clients_deletion,
+                           !(hardware->h_flags & IFF_RUNNING));
                }
        }
 
-       lldpd_count_neighbors(cfg);
        levent_schedule_cleanup(cfg);
+       lldpd_all_chassis_cleanup(cfg);
+       lldpd_count_neighbors(cfg);
 }
 
 /* Update chassis `ochassis' with values from `chassis'. The later one is not
@@ -406,7 +487,9 @@ lldpd_guess_type(struct lldpd *cfg, char *frame, int s)
                if (!cfg->g_protocols[i].enabled)
                        continue;
                if (cfg->g_protocols[i].guess == NULL) {
-                       if (memcmp(frame, cfg->g_protocols[i].mac, ETHER_ADDR_LEN) == 0) {
+                       if (memcmp(frame, cfg->g_protocols[i].mac1, ETHER_ADDR_LEN) == 0 ||
+                           memcmp(frame, cfg->g_protocols[i].mac2, ETHER_ADDR_LEN) == 0 ||
+                           memcmp(frame, cfg->g_protocols[i].mac3, ETHER_ADDR_LEN) == 0) {
                                log_debug("decode", "guessed protocol is %s (from MAC address)",
                                    cfg->g_protocols[i].name);
                                return cfg->g_protocols[i].mode;
@@ -434,9 +517,11 @@ lldpd_decode(struct lldpd *cfg, char *frame, int s,
        log_debug("decode", "decode a received frame on %s",
            hardware->h_ifname);
 
-       if (s < sizeof(struct ether_header) + 4)
+       if (s < sizeof(struct ether_header) + 4) {
                /* Too short, just discard it */
+               hardware->h_rx_discarded_cnt++;
                return;
+       }
 
        /* Decapsulate VLAN frames */
        struct ether_header eheader;
@@ -470,6 +555,7 @@ lldpd_decode(struct lldpd *cfg, char *frame, int s,
                                s, hardware, &chassis, &port) == -1) {
                                log_debug("decode", "function for %s protocol did not decode this frame",
                                    cfg->g_protocols[i].name);
+                               hardware->h_rx_discarded_cnt++;
                                return;
                        }
                        chassis->c_protocol = port->p_protocol =
@@ -482,6 +568,11 @@ lldpd_decode(struct lldpd *cfg, char *frame, int s,
                    hardware->h_ifname);
                return;
        }
+       TRACE(LLDPD_FRAME_DECODED(
+                   hardware->h_ifname,
+                   cfg->g_protocols[i].name,
+                   chassis->c_name,
+                   port->p_descr));
 
        /* Do we already have the same MSAP somewhere? */
        int count = 0;
@@ -503,15 +594,22 @@ lldpd_decode(struct lldpd *cfg, char *frame, int s,
                }
        }
        /* Do we have room for a new MSAP? */
-       if (!oport && cfg->g_config.c_max_neighbors &&
-           count > cfg->g_config.c_max_neighbors - 1) {
-               log_info("decode",
+       if (!oport && cfg->g_config.c_max_neighbors) {
+           if (count == (cfg->g_config.c_max_neighbors - 1)) {
+               log_debug("decode",
+                   "max neighbors %d reached for port %s, "
+                   "dropping any new ones silently",
+                   cfg->g_config.c_max_neighbors,
+                   hardware->h_ifname);
+           } else if (count > cfg->g_config.c_max_neighbors - 1) {
+               log_debug("decode",
                    "too many neighbors for port %s, drop this new one",
                    hardware->h_ifname);
                lldpd_port_cleanup(port, 1);
                lldpd_chassis_cleanup(chassis, 1);
                free(port);
                return;
+           }
        }
        /* No, but do we already know the system? */
        if (!oport) {
@@ -567,6 +665,8 @@ lldpd_decode(struct lldpd *cfg, char *frame, int s,
           freed with lldpd_port_cleanup() and therefore, the refcount
           of the chassis that was attached to it is decreased.
        */
+       /* coverity[use_after_free]
+          TAILQ_REMOVE does the right thing */
        i = 0; TAILQ_FOREACH(aport, &hardware->h_rports, p_entries)
                i++;
        log_debug("decode", "%d neighbors for %s", i,
@@ -577,11 +677,25 @@ lldpd_decode(struct lldpd *cfg, char *frame, int s,
        /* Notify */
        log_debug("decode", "send notifications for changes on %s",
            hardware->h_ifname);
-       i = oport?NEIGHBOR_CHANGE_UPDATED:NEIGHBOR_CHANGE_ADDED;
-       levent_ctl_notify(hardware->h_ifname, i, port);
+       if (oport) {
+               TRACE(LLDPD_NEIGHBOR_UPDATE(hardware->h_ifname,
+                       chassis->c_name,
+                       port->p_descr,
+                       i));
+               levent_ctl_notify(hardware->h_ifname, NEIGHBOR_CHANGE_UPDATED, port);
 #ifdef USE_SNMP
-       agent_notify(hardware, i, port);
+               agent_notify(hardware, NEIGHBOR_CHANGE_UPDATED, port);
 #endif
+       } else {
+               TRACE(LLDPD_NEIGHBOR_NEW(hardware->h_ifname,
+                       chassis->c_name,
+                       port->p_descr,
+                       i));
+               levent_ctl_notify(hardware->h_ifname, NEIGHBOR_CHANGE_ADDED, port);
+#ifdef USE_SNMP
+               agent_notify(hardware, NEIGHBOR_CHANGE_ADDED, port);
+#endif
+       }
 
 #ifdef ENABLE_LLDPMED
        if (!oport && port->p_chassis->c_med_type) {
@@ -617,11 +731,11 @@ lldpd_get_lsb_release() {
                return NULL;
        }
 
-       if ((pid = fork()) < 0) {
+       pid = vfork();
+       switch (pid) {
+       case -1:
                log_warn("localchassis", "unable to fork");
                return NULL;
-       }
-       switch (pid) {
        case 0:
                /* Child, exec lsb_release */
                close(pipefd[0]);
@@ -633,7 +747,7 @@ lldpd_get_lsb_release() {
                        if (pipefd[1] > 2) close(pipefd[1]);
                        execvp("lsb_release", command);
                }
-               exit(127);
+               _exit(127);
                break;
        default:
                /* Father, read the output from the children */
@@ -683,19 +797,24 @@ lldpd_get_os_release() {
        char *key, *val;
        char *ptr1 = release;
 
-       FILE *fp = fopen("/etc/os-release", "r");
        log_debug("localchassis", "grab OS release");
+       FILE *fp = fopen("/etc/os-release", "r");
        if (!fp) {
-               log_info("localchassis", "could not open /etc/os-release");
+               log_debug("localchassis", "could not open /etc/os-release");
+               fp = fopen("/usr/lib/os-release", "r");
+       }
+       if (!fp) {
+               log_info("localchassis",
+                   "could not open either /etc/os-release or /usr/lib/os-release");
                return NULL;
        }
 
-       while ((fgets(line, 1024, fp) != NULL)) {
+       while ((fgets(line, sizeof(line), fp) != NULL)) {
                key = strtok(line, "=");
                val = strtok(NULL, "=");
 
-               if (strncmp(key, "PRETTY_NAME", 1024) == 0) {
-                       strncpy(release, val, 1024);
+               if (strncmp(key, "PRETTY_NAME", sizeof(line)) == 0) {
+                       strlcpy(release, val, sizeof(line));
                        break;
                }
        }
@@ -842,6 +961,12 @@ lldpd_recv(struct lldpd *cfg, struct lldpd_hardware *hardware, int fd)
                free(buffer);
                return;
        }
+       if (hardware->h_lport.p_disable_rx) {
+               log_debug("receive", "RX disabled, ignore the frame on %s",
+                   hardware->h_ifname);
+               free(buffer);
+               return;
+       }
        if (cfg->g_config.c_paused) {
                log_debug("receive", "paused, ignore the frame on %s",
                        hardware->h_ifname);
@@ -851,12 +976,29 @@ lldpd_recv(struct lldpd *cfg, struct lldpd_hardware *hardware, int fd)
        hardware->h_rx_cnt++;
        log_debug("receive", "decode received frame on %s",
            hardware->h_ifname);
+       TRACE(LLDPD_FRAME_RECEIVED(hardware->h_ifname, buffer, (size_t)n));
        lldpd_decode(cfg, buffer, n, hardware);
        lldpd_hide_all(cfg); /* Immediatly hide */
        lldpd_count_neighbors(cfg);
        free(buffer);
 }
 
+static void
+lldpd_send_shutdown(struct lldpd_hardware *hardware)
+{
+       struct lldpd *cfg = hardware->h_cfg;
+       if (cfg->g_config.c_receiveonly || cfg->g_config.c_paused) return;
+       if (hardware->h_lport.p_disable_tx) return;
+       if ((hardware->h_flags & IFF_RUNNING) == 0)
+               return;
+
+       /* It's safe to call `lldp_send_shutdown()` because shutdown LLDPU will
+        * only be emitted if LLDP was sent on that port. */
+       if (lldp_send_shutdown(hardware->h_cfg, hardware) != 0)
+               log_warnx("send", "unable to send shutdown LLDPDU on %s",
+                   hardware->h_ifname);
+}
+
 void
 lldpd_send(struct lldpd_hardware *hardware)
 {
@@ -865,6 +1007,7 @@ lldpd_send(struct lldpd_hardware *hardware)
        int i, sent;
 
        if (cfg->g_config.c_receiveonly || cfg->g_config.c_paused) return;
+       if (hardware->h_lport.p_disable_tx) return;
        if ((hardware->h_flags & IFF_RUNNING) == 0)
                return;
 
@@ -887,6 +1030,8 @@ lldpd_send(struct lldpd_hardware *hardware)
                                continue;
                        if (port->p_protocol ==
                            cfg->g_protocols[i].mode) {
+                               TRACE(LLDPD_FRAME_SEND(hardware->h_ifname,
+                                       cfg->g_protocols[i].name));
                                log_debug("send", "send PDU on %s with protocol %s",
                                    hardware->h_ifname,
                                    cfg->g_protocols[i].name);
@@ -903,6 +1048,8 @@ lldpd_send(struct lldpd_hardware *hardware)
                 * available protocol. */
                for (i = 0; cfg->g_protocols[i].mode != 0; i++) {
                        if (!cfg->g_protocols[i].enabled) continue;
+                       TRACE(LLDPD_FRAME_SEND(hardware->h_ifname,
+                               cfg->g_protocols[i].name));
                        log_debug("send", "fallback to protocol %s for %s",
                            cfg->g_protocols[i].name, hardware->h_ifname);
                        cfg->g_protocols[i].send(cfg,
@@ -935,6 +1082,10 @@ static int
 lldpd_routing_enabled(struct lldpd *cfg)
 {
        int routing;
+
+       if ((LOCAL_CHASSIS(cfg)->c_cap_available & LLDP_CAP_ROUTER) == 0)
+               return 0;
+
        if ((routing = interfaces_routing_enabled(cfg)) == -1) {
                log_debug("localchassis", "unable to check if routing is enabled");
                return 0;
@@ -954,8 +1105,13 @@ lldpd_update_localchassis(struct lldpd *cfg)
        /* Set system name and description */
        if (uname(&un) < 0)
                fatal("localchassis", "failed to get system information");
-       if ((hp = priv_gethostbyname()) == NULL)
-               fatal("localchassis", "failed to get system name");
+       if (cfg->g_config.c_hostname) {
+               log_debug("localchassis", "use overridden system name `%s`", cfg->g_config.c_hostname);
+               hp = cfg->g_config.c_hostname;
+       } else {
+               if ((hp = priv_gethostname()) == NULL)
+                       fatal("localchassis", "failed to get system name");
+       }
        free(LOCAL_CHASSIS(cfg)->c_name);
        free(LOCAL_CHASSIS(cfg)->c_descr);
        if ((LOCAL_CHASSIS(cfg)->c_name = strdup(hp)) == NULL)
@@ -980,6 +1136,8 @@ lldpd_update_localchassis(struct lldpd *cfg)
                                fatal("localchassis", "failed to set minimal system description");
                }
         }
+       if (cfg->g_config.c_platform == NULL)
+               cfg->g_config.c_platform = strdup(un.sysname);
 
        /* Check routing */
        if (lldpd_routing_enabled(cfg)) {
@@ -998,6 +1156,9 @@ lldpd_update_localchassis(struct lldpd *cfg)
        else
                LOCAL_CHASSIS(cfg)->c_med_sw = strdup("Unknown");
 #endif
+       if ((LOCAL_CHASSIS(cfg)->c_cap_available & LLDP_CAP_STATION) &&
+               (LOCAL_CHASSIS(cfg)->c_cap_enabled == 0))
+               LOCAL_CHASSIS(cfg)->c_cap_enabled = LLDP_CAP_STATION;
 
        /* Set chassis ID if needed. This is only done if chassis ID
           has not been set previously (with the MAC address of an
@@ -1025,6 +1186,7 @@ lldpd_update_localports(struct lldpd *cfg)
        TAILQ_FOREACH(hardware, &cfg->g_hardware, h_entries)
            hardware->h_flags = 0;
 
+       TRACE(LLDPD_INTERFACES_UPDATE());
        interfaces_update(cfg);
        lldpd_cleanup(cfg);
        lldpd_reset_timer(cfg);
@@ -1054,6 +1216,10 @@ lldpd_exit(struct lldpd *cfg)
 {
        struct lldpd_hardware *hardware, *hardware_next;
        log_debug("main", "exit lldpd");
+
+       TAILQ_FOREACH(hardware, &cfg->g_hardware, h_entries)
+               lldpd_send_shutdown(hardware);
+
        close(cfg->g_ctl);
        priv_ctl_cleanup(cfg->g_ctlname);
        log_debug("main", "cleanup hardware information");
@@ -1061,9 +1227,15 @@ lldpd_exit(struct lldpd *cfg)
             hardware = hardware_next) {
                hardware_next = TAILQ_NEXT(hardware, h_entries);
                log_debug("main", "cleanup interface %s", hardware->h_ifname);
-               lldpd_remote_cleanup(hardware, NULL);
+               lldpd_remote_cleanup(hardware, NULL, 1);
                lldpd_hardware_cleanup(cfg, hardware);
        }
+       interfaces_cleanup(cfg);
+       lldpd_port_cleanup(cfg->g_default_local_port, 1);
+       lldpd_all_chassis_cleanup(cfg);
+       free(cfg->g_default_local_port);
+       free(cfg->g_config.c_platform);
+       levent_shutdown(cfg);
 }
 
 /**
@@ -1072,11 +1244,24 @@ lldpd_exit(struct lldpd *cfg)
  * @return PID of running lldpcli or -1 if error.
  */
 static pid_t
-lldpd_configure(int debug, const char *path)
+lldpd_configure(int use_syslog, int debug, const char *path, const char *ctlname)
 {
-       pid_t lldpcli = fork();
+       pid_t lldpcli = vfork();
        int devnull;
 
+       char sdebug[debug + 4];
+       if (use_syslog)
+               strlcpy(sdebug, "-s", 3);
+       else {
+               /* debug = 0 -> -sd */
+               /* debug = 1 -> -sdd */
+               /* debug = 2 -> -sddd */
+               memset(sdebug, 'd', sizeof(sdebug));
+               sdebug[debug + 3] = '\0';
+               sdebug[0] = '-'; sdebug[1] = 's';
+       }
+       log_debug("main", "invoke %s %s", path, sdebug);
+
        switch (lldpcli) {
        case -1:
                log_warn("main", "unable to fork");
@@ -1084,26 +1269,20 @@ lldpd_configure(int debug, const char *path)
        case 0:
                /* Child, exec lldpcli */
                if ((devnull = open("/dev/null", O_RDWR, 0)) != -1) {
-                       char sdebug[debug + 3];
-                       memset(sdebug, 'd', debug + 3);
-                       sdebug[debug + 2] = '\0';
-                       sdebug[0] = '-'; sdebug[1] = 's';
-
                        dup2(devnull,   STDIN_FILENO);
                        dup2(devnull,   STDOUT_FILENO);
                        if (devnull > 2) close(devnull);
 
-                       log_debug("main", "invoke %s %s", path, sdebug);
-                       if (execl(path, "lldpcli", sdebug,
-                               "-c", SYSCONFDIR "/lldpd.conf",
-                               "-c", SYSCONFDIR "/lldpd.d",
-                               "resume",
-                               NULL) == -1) {
-                               log_warn("main", "unable to execute %s", path);
-                               log_warnx("main", "configuration is incomplete, lldpd needs to be unpaused");
-                       }
+                       execl(path, "lldpcli", sdebug,
+                           "-u", ctlname,
+                           "-C", SYSCONFDIR "/lldpd.conf",
+                           "-C", SYSCONFDIR "/lldpd.d",
+                           "resume",
+                           (char *)NULL);
+                       log_warn("main", "unable to execute %s", path);
+                       log_warnx("main", "configuration is incomplete, lldpd needs to be unpaused");
                }
-               exit(127);
+               _exit(127);
                break;
        default:
                /* Father, don't do anything stupid */
@@ -1162,6 +1341,7 @@ lldpd_started_by_upstart()
                return 0;
        log_debug("main", "running with upstart, don't fork but stop");
        raise(SIGSTOP);
+       unsetenv("UPSTART_JOB");
        return 1;
 #else
        return 0;
@@ -1203,6 +1383,7 @@ lldpd_started_by_systemd()
                .msg_iov = &iov,
                .msg_iovlen = 1
        };
+       unsetenv("NOTIFY_SOCKET");
        if (sendmsg(fd, &hdr, MSG_NOSIGNAL) < 0) {
                log_warn("main", "unable to send notification to systemd");
                close(fd);
@@ -1216,22 +1397,64 @@ lldpd_started_by_systemd()
 }
 #endif
 
+#ifdef HOST_OS_LINUX
+static void
+version_convert(const char *sversion, unsigned iversion[], size_t n)
+{
+       const char *p = sversion;
+       char *end;
+       for (size_t i = 0; i < n; i++) {
+               iversion[i] = strtol(p, &end, 10);
+               if (*end != '.') break;
+               p = end + 1;
+       }
+}
+
+static void
+version_check(void)
+{
+       struct utsname uts;
+       if (uname(&uts) == -1) return;
+       unsigned version_min[3] = {};
+       unsigned version_cur[3] = {};
+       version_convert(uts.release, version_cur, 3);
+       version_convert(MIN_LINUX_KERNEL_VERSION, version_min, 3);
+       if (version_min[0] > version_cur[0] ||
+           (version_min[0] == version_cur[0] && version_min[1] > version_cur[1]) ||
+           (version_min[0] == version_cur[0] && version_min[1] == version_cur[1] &&
+               version_min[2] > version_cur[2])) {
+               log_warnx("lldpd", "minimal kernel version required is %s, got %s",
+                   MIN_LINUX_KERNEL_VERSION, uts.release);
+               log_warnx("lldpd", "lldpd may be unable to detect bonds and bridges correctly");
+#ifndef ENABLE_OLDIES
+               log_warnx("lldpd", "consider recompiling with --enable-oldies option");
+#endif
+       }
+}
+#else
+static void version_check(void) {}
+#endif
+
 int
-lldpd_main(int argc, char *argv[])
+lldpd_main(int argc, char *argv[], char *envp[])
 {
        struct lldpd *cfg;
        struct lldpd_chassis *lchassis;
-       int ch, debug = 0;
+       int ch, debug = 0, use_syslog = 1, daemonize = 1;
+       const char *errstr;
 #ifdef USE_SNMP
        int snmp = 0;
-       char *agentx = NULL;    /* AgentX socket */
+       const char *agentx = NULL;      /* AgentX socket */
 #endif
-       char *ctlname = LLDPD_CTL_SOCKET;
+       const char *ctlname = NULL;
        char *mgmtp = NULL;
        char *cidp = NULL;
        char *interfaces = NULL;
+       /* We do not want more options here. Please add them in lldpcli instead
+        * unless there is a very good reason. Most command-line options will
+        * get deprecated at some point. */
        char *popt, opts[] =
-               "H:vhkrdD:xX:m:u:4:6:I:C:p:M:P:S:iL:@                    ";
+               "H:vhkrdD:p:xX:m:u:4:6:I:C:p:M:P:S:iL:@                    ";
        int i, found, advertise_version = 1;
 #ifdef ENABLE_LLDPMED
        int lldpmed = 0, noinventory = 0;
@@ -1241,65 +1464,99 @@ lldpd_main(int argc, char *argv[])
        char *platform_override = NULL;
        char *lsb_release = NULL;
        const char *lldpcli = LLDPCLI_PATH;
+       const char *pidfile = LLDPD_PID_FILE;
        int smart = 15;
-       int receiveonly = 0;
+       int receiveonly = 0, version = 0;
        int ctl;
 
+#ifdef ENABLE_PRIVSEP
        /* Non privileged user */
        struct passwd *user;
        struct group *group;
        uid_t uid;
        gid_t gid;
+#endif
 
        saved_argv = argv;
 
+#if HAVE_SETPROCTITLE_INIT
+       setproctitle_init(argc, argv, envp);
+#endif
+
        /*
         * Get and parse command line options
         */
-       popt = strchr(opts, '@');
-       for (i=0; protos[i].mode != 0; i++)
-               *(popt++) = protos[i].arg;
-       *popt = '\0';
+       if ((popt = strchr(opts, '@')) != NULL) {
+               for (i=0;
+                    protos[i].mode != 0 && *popt != '\0';
+                    i++)
+                       *(popt++) = protos[i].arg;
+               *popt = '\0';
+       }
        while ((ch = getopt(argc, argv, opts)) != -1) {
                switch (ch) {
                case 'h':
                        usage();
                        break;
                case 'v':
-                       fprintf(stdout, "%s\n", PACKAGE_VERSION);
-                       exit(0);
+                       version++;
                        break;
                case 'd':
-                       debug++;
+                       if (daemonize)
+                               daemonize = 0;
+                       else if (use_syslog)
+                               use_syslog = 0;
+                       else
+                               debug++;
                        break;
                case 'D':
                        log_accept(optarg);
                        break;
+               case 'p':
+                       pidfile = optarg;
+                       break;
                case 'r':
                        receiveonly = 1;
                        break;
                case 'm':
-                       mgmtp = optarg;
+                       if (mgmtp) {
+                               fprintf(stderr, "-m can only be used once\n");
+                               usage();
+                       }
+                       mgmtp = strdup(optarg);
                        break;
                case 'u':
+                       if (ctlname) {
+                               fprintf(stderr, "-u can only be used once\n");
+                               usage();
+                       }
                        ctlname = optarg;
                        break;
                case 'I':
-                       interfaces = optarg;
+                       if (interfaces) {
+                               fprintf(stderr, "-I can only be used once\n");
+                               usage();
+                       }
+                       interfaces = strdup(optarg);
                        break;
                case 'C':
-                       cidp = optarg;
+                       if (cidp) {
+                               fprintf(stderr, "-C can only be used once\n");
+                               usage();
+                       }
+                       cidp = strdup(optarg);
                        break;
                case 'L':
                        if (strlen(optarg)) lldpcli = optarg;
                        else lldpcli = NULL;
+                       break;
                case 'k':
                        advertise_version = 0;
                        break;
 #ifdef ENABLE_LLDPMED
                case 'M':
-                       lldpmed = atoi(optarg);
-                       if ((lldpmed < 1) || (lldpmed > 4)) {
+                       lldpmed = strtonum(optarg, 1, 4, &errstr);
+                       if (errstr) {
                                fprintf(stderr, "-M requires an argument between 1 and 4\n");
                                usage();
                        }
@@ -1319,6 +1576,10 @@ lldpd_main(int argc, char *argv[])
                        snmp = 1;
                        break;
                case 'X':
+                       if (agentx) {
+                               fprintf(stderr, "-X can only be used once\n");
+                               usage();
+                       }
                        snmp = 1;
                        agentx = optarg;
                        break;
@@ -1330,23 +1591,34 @@ lldpd_main(int argc, char *argv[])
 #endif
                        break;
                 case 'S':
+                       if (descr_override) {
+                               fprintf(stderr, "-S can only be used once\n");
+                               usage();
+                       }
                         descr_override = strdup(optarg);
                         break;
                case 'P':
+                       if (platform_override) {
+                               fprintf(stderr, "-P can only be used once\n");
+                               usage();
+                       }
                        platform_override = strdup(optarg);
                        break;
                case 'H':
-                       smart = atoi(optarg);
+                       smart = strtonum(optarg, 0, sizeof(filters)/sizeof(filters[0]),
+                           &errstr);
+                       if (errstr) {
+                               fprintf(stderr, "-H requires an int between 0 and %zu\n",
+                                   sizeof(filters)/sizeof(filters[0]));
+                               usage();
+                       }
                        break;
                default:
                        found = 0;
                        for (i=0; protos[i].mode != 0; i++) {
                                if (ch == protos[i].arg) {
-                                       if (protos[i].enabled < 3) {
-                                               found = 1;
-                                               if (protos[i].enabled++ == 1)
-                                                       break;
-                                       }
+                                       found = 1;
+                                       protos[i].enabled++;
                                }
                        }
                        if (!found)
@@ -1354,6 +1626,13 @@ lldpd_main(int argc, char *argv[])
                }
        }
 
+       if (version) {
+               version_display(stdout, "lldpd", version > 1);
+               exit(0);
+       }
+
+       if (ctlname == NULL) ctlname = LLDPD_CTL_SOCKET;
+
        /* Set correct smart mode */
        for (i=0; (filters[i].a != -1) && (filters[i].a != smart); i++);
        if (filters[i].a == -1) {
@@ -1362,18 +1641,31 @@ lldpd_main(int argc, char *argv[])
        }
        smart = filters[i].b;
 
-       log_init(debug, __progname);
+       log_init(use_syslog, debug, __progname);
        tzset();                /* Get timezone info before chroot */
-
-       log_debug("main", "lldpd starting...");
+       if (use_syslog && daemonize) {
+               /* So, we use syslog and we daemonize (or we are started by
+                * upstart/systemd). No need to continue writing to stdout. */
+               int fd;
+               if ((fd = open("/dev/null", O_RDWR, 0)) != -1) {
+                       dup2(fd, STDIN_FILENO);
+                       dup2(fd, STDOUT_FILENO);
+                       dup2(fd, STDERR_FILENO);
+                       if (fd > 2) close(fd);
+               }
+       }
+       log_debug("main", "lldpd " PACKAGE_VERSION " starting...");
+       version_check();
 
        /* Grab uid and gid to use for priv sep */
+#ifdef ENABLE_PRIVSEP
        if ((user = getpwnam(PRIVSEP_USER)) == NULL)
-               fatal("main", "no " PRIVSEP_USER " user for privilege separation");
+               fatalx("main", "no " PRIVSEP_USER " user for privilege separation, please create it");
        uid = user->pw_uid;
        if ((group = getgrnam(PRIVSEP_GROUP)) == NULL)
-               fatal("main", "no " PRIVSEP_GROUP " group for privilege separation");
+               fatalx("main", "no " PRIVSEP_GROUP " group for privilege separation, please create it");
        gid = group->gr_gid;
+#endif
 
        /* Create and setup socket */
        int retry = 1;
@@ -1388,7 +1680,7 @@ lldpd_main(int argc, char *argv[])
                                /* Another instance is running */
                                close(tfd);
                                log_warnx("main", "another instance is running, please stop it");
-                               fatalx("giving up");
+                               fatalx("main", "giving up");
                        } else if (errno == ECONNREFUSED) {
                                /* Nobody is listening */
                                log_info("main", "old control socket is present, clean it");
@@ -1396,49 +1688,57 @@ lldpd_main(int argc, char *argv[])
                                continue;
                        }
                        log_warn("main", "cannot determine if another daemon is already running");
-                       fatalx("giving up");
+                       fatalx("main", "giving up");
                }
-               log_warn("main", "unable to create control socket");
-               fatalx("giving up");
+               log_warn("main", "unable to create control socket at %s", ctlname);
+               fatalx("main", "giving up");
        }
+#ifdef ENABLE_PRIVSEP
        if (chown(ctlname, uid, gid) == -1)
                log_warn("main", "unable to chown control socket");
        if (chmod(ctlname,
                S_IRUSR | S_IWUSR | S_IXUSR |
                S_IRGRP | S_IWGRP | S_IXGRP) == -1)
                log_warn("main", "unable to chmod control socket");
+#endif
 
        /* Disable SIGPIPE */
        signal(SIGPIPE, SIG_IGN);
 
-       /* Configuration with lldpcli */
-       if (lldpcli) {
-               log_debug("main", "invoking lldpcli for configuration");
-               if (lldpd_configure(debug, lldpcli) == -1)
-                       fatal("main", "unable to spawn lldpcli");
-       }
+       /* Disable SIGHUP, until handlers are installed */
+       signal(SIGHUP, SIG_IGN);
 
        /* Daemonization, unless started by upstart, systemd or launchd or debug */
 #ifndef HOST_OS_OSX
-       if (!lldpd_started_by_upstart() && !lldpd_started_by_systemd() &&
-           !debug) {
+       if (daemonize &&
+           !lldpd_started_by_upstart() && !lldpd_started_by_systemd()) {
                int pid;
                char *spid;
-               log_debug("main", "daemonize");
-               if (daemon(0, 0) != 0)
+               log_info("main", "going into background");
+               if (daemon(0, 1) != 0)
                        fatal("main", "failed to detach daemon");
-               if ((pid = open(LLDPD_PID_FILE,
-                           O_TRUNC | O_CREAT | O_WRONLY, 0644)) == -1)
-                       fatal("main", "unable to open pid file " LLDPD_PID_FILE);
+               if ((pid = open(pidfile,
+                           O_TRUNC | O_CREAT | O_WRONLY, 0666)) == -1)
+                       fatal("main", "unable to open pid file " LLDPD_PID_FILE
+                           " (or the specified one)");
                if (asprintf(&spid, "%d\n", getpid()) == -1)
-                       fatal("main", "unable to create pid file " LLDPD_PID_FILE);
+                       fatal("main", "unable to create pid file " LLDPD_PID_FILE
+                           " (or the specified one)");
                if (write(pid, spid, strlen(spid)) == -1)
-                       fatal("main", "unable to write pid file " LLDPD_PID_FILE);
+                       fatal("main", "unable to write pid file " LLDPD_PID_FILE
+                           " (or the specified one)");
                free(spid);
                close(pid);
        }
 #endif
 
+       /* Configuration with lldpcli */
+       if (lldpcli) {
+               log_debug("main", "invoking lldpcli for configuration");
+               if (lldpd_configure(use_syslog, debug, lldpcli, ctlname) == -1)
+                       fatal("main", "unable to spawn lldpcli");
+       }
+
        /* Try to read system information from /etc/os-release if possible.
           Fall back to lsb_release for compatibility. */
        log_debug("main", "get OS/LSB release information");
@@ -1448,13 +1748,18 @@ lldpd_main(int argc, char *argv[])
        }
 
        log_debug("main", "initialize privilege separation");
+#ifdef ENABLE_PRIVSEP
        priv_init(PRIVSEP_CHROOT, ctl, uid, gid);
+#else
+       priv_init(PRIVSEP_CHROOT, ctl, 0, 0);
+#endif
 
        /* Initialization of global configuration */
        if ((cfg = (struct lldpd *)
            calloc(1, sizeof(struct lldpd))) == NULL)
                fatal("main", NULL);
 
+       lldpd_alloc_default_local_port(cfg);
        cfg->g_ctlname = ctlname;
        cfg->g_ctl = ctl;
        cfg->g_config.c_mgmt_pattern = mgmtp;
@@ -1466,6 +1771,7 @@ lldpd_main(int argc, char *argv[])
        cfg->g_config.c_receiveonly = receiveonly;
        cfg->g_config.c_tx_interval = LLDPD_TX_INTERVAL;
        cfg->g_config.c_tx_hold = LLDPD_TX_HOLD;
+       cfg->g_config.c_ttl = cfg->g_config.c_tx_interval * cfg->g_config.c_tx_hold;
        cfg->g_config.c_max_neighbors = LLDPD_MAX_NEIGHBORS;
 #ifdef ENABLE_LLDPMED
        cfg->g_config.c_enable_fast_start = enable_fast_start;
@@ -1476,6 +1782,8 @@ lldpd_main(int argc, char *argv[])
        cfg->g_snmp = snmp;
        cfg->g_snmp_agentx = agentx;
 #endif /* USE_SNMP */
+       cfg->g_config.c_bond_slave_src_mac_type = \
+           LLDP_BOND_SLAVE_SRC_MAC_TYPE_LOCALLY_ADMINISTERED;
 
        /* Get ioctl socket */
        log_debug("main", "get an ioctl socket");
@@ -1498,8 +1806,10 @@ lldpd_main(int argc, char *argv[])
        if ((lchassis = (struct lldpd_chassis*)
                calloc(1, sizeof(struct lldpd_chassis))) == NULL)
                fatal("localchassis", NULL);
+       cfg->g_config.c_cap_advertise = 1;
        lchassis->c_cap_available = LLDP_CAP_BRIDGE | LLDP_CAP_WLAN |
-           LLDP_CAP_ROUTER;
+           LLDP_CAP_ROUTER | LLDP_CAP_STATION;
+       cfg->g_config.c_mgmt_advertise = 1;
        TAILQ_INIT(&lchassis->c_mgmt);
 #ifdef ENABLE_LLDPMED
        if (lldpmed > 0) {
@@ -1514,18 +1824,39 @@ lldpd_main(int argc, char *argv[])
                cfg->g_config.c_noinventory = 1;
 #endif
 
-       /* Set TTL */
-       lchassis->c_ttl = cfg->g_config.c_tx_interval * cfg->g_config.c_tx_hold;
-
        log_debug("main", "initialize protocols");
        cfg->g_protocols = protos;
-       for (i=0; protos[i].mode != 0; i++)
+       for (i=0; protos[i].mode != 0; i++) {
+
+               /* With -ll, disable LLDP */
+               if (protos[i].mode == LLDPD_MODE_LLDP)
+                       protos[i].enabled %= 3;
+               /* With -ccc force CDPV2, enable CDPV1 */
+               if (protos[i].mode == LLDPD_MODE_CDPV1 && protos[i].enabled == 3) {
+                       protos[i].enabled = 1;
+               }
+               /* With -cc force CDPV1, enable CDPV2 */
+               if (protos[i].mode == LLDPD_MODE_CDPV2 && protos[i].enabled == 2) {
+                       protos[i].enabled = 1;
+               }
+
+               /* With -cccc disable CDPV1, enable CDPV2 */
+               if (protos[i].mode == LLDPD_MODE_CDPV1 && protos[i].enabled >= 4) {
+                       protos[i].enabled = 0;
+               }
+
+               /* With -cccc disable CDPV1, enable CDPV2; -ccccc will force CDPv2 */
+               if (protos[i].mode == LLDPD_MODE_CDPV2 && protos[i].enabled == 4) {
+                       protos[i].enabled = 1;
+               }
+
                if (protos[i].enabled > 1)
                        log_info("main", "protocol %s enabled and forced", protos[i].name);
                else if (protos[i].enabled)
                        log_info("main", "protocol %s enabled", protos[i].name);
                else
                        log_info("main", "protocol %s disabled", protos[i].name);
+           }
 
        TAILQ_INIT(&cfg->g_hardware);
        TAILQ_INIT(&cfg->g_chassis);
@@ -1535,7 +1866,9 @@ lldpd_main(int argc, char *argv[])
        /* Main loop */
        log_debug("main", "start main loop");
        levent_loop(cfg);
+       lchassis->c_refcount--;
        lldpd_exit(cfg);
+       free(cfg);
 
        return (0);
 }