summaryrefslogtreecommitdiffstats
path: root/src/daemon/lldpd.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/daemon/lldpd.c')
-rw-r--r--src/daemon/lldpd.c2020
1 files changed, 2020 insertions, 0 deletions
diff --git a/src/daemon/lldpd.c b/src/daemon/lldpd.c
new file mode 100644
index 0000000..4859fb8
--- /dev/null
+++ b/src/daemon/lldpd.c
@@ -0,0 +1,2020 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/*
+ * Copyright (c) 2008 Vincent Bernat <bernat@luffy.cx>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "lldpd.h"
+#include "trace.h"
+
+#include <stdio.h>
+#include <unistd.h>
+#include <errno.h>
+#include <limits.h>
+#include <signal.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <time.h>
+#include <libgen.h>
+#include <assert.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 <netinet/if_ether.h>
+#include <pwd.h>
+#include <grp.h>
+
+#if HAVE_VFORK_H
+# include <vfork.h>
+#endif
+#if HAVE_WORKING_FORK
+# define vfork fork
+#endif
+
+static void usage(void);
+
+static struct protocol protos[] = {
+ { LLDPD_MODE_LLDP, 1, "LLDP", 'l', lldp_send, lldp_decode, NULL,
+ { 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 } },
+ { LLDPD_MODE_CDPV2, 0, "CDPv2", 'c', cdpv2_send, cdp_decode, cdpv2_guess,
+ { CDP_MULTICAST_ADDR } },
+#endif
+#ifdef ENABLE_SONMP
+ { LLDPD_MODE_SONMP, 0, "SONMP", 's', sonmp_send, sonmp_decode, NULL,
+ { SONMP_MULTICAST_ADDR } },
+#endif
+#ifdef ENABLE_EDP
+ { LLDPD_MODE_EDP, 0, "EDP", 'e', edp_send, edp_decode, NULL,
+ { EDP_MULTICAST_ADDR } },
+#endif
+#ifdef ENABLE_FDP
+ { LLDPD_MODE_FDP, 0, "FDP", 'f', fdp_send, cdp_decode, NULL,
+ { FDP_MULTICAST_ADDR } },
+#endif
+ { 0, 0, "any", ' ', NULL, NULL, NULL, { { 0, 0, 0, 0, 0, 0 } } }
+};
+
+static char **saved_argv;
+#ifdef HAVE___PROGNAME
+extern const char *__progname;
+#else
+# define __progname "lldpd"
+#endif
+
+static void
+usage(void)
+{
+ fprintf(stderr, "Usage: %s [OPTIONS ...]\n", __progname);
+ fprintf(stderr, "Version: %s\n", PACKAGE_STRING);
+
+ 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, "-P name Override the default hardware platform.\n");
+ fprintf(stderr,
+ "-m IP Specify the IP management addresses of this system.\n");
+ fprintf(stderr,
+ "-u file Specify the Unix-domain socket used for communication with lldpctl(8).\n");
+ fprintf(stderr,
+ "-H mode Specify the behaviour when detecting multiple neighbors.\n");
+ fprintf(stderr, "-I iface Limit interfaces to use.\n");
+ fprintf(stderr, "-C iface Limit interfaces to use for computing chassis ID.\n");
+ fprintf(stderr, "-L path Override path for lldpcli command.\n");
+ fprintf(stderr,
+ "-O file Override default configuration locations processed by lldpcli(8) at start.\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");
+ fprintf(stderr, "-X sock Specify the SNMP subagent socket.\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);
+}
+
+struct lldpd_hardware *
+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) {
+ if (hardware->h_flags == 0) {
+ if (hardware->h_ifindex != 0 &&
+ hardware->h_ifindex != index) {
+ log_debug("interfaces",
+ "%s changed index: from %d to %d",
+ hardware->h_ifname, hardware->h_ifindex,
+ index);
+ hardware->h_ifindex_changed = 1;
+ }
+ hardware->h_ifindex = index;
+ break;
+ }
+ if (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)
+{
+ struct lldpd_hardware *hardware;
+
+ log_debug("alloc", "allocate a new local port (%s)", name);
+
+ if ((hardware = (struct lldpd_hardware *)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;
+ hardware->h_lport.p_chassis = LOCAL_CHASSIS(cfg);
+ hardware->h_lport.p_chassis->c_refcount++;
+ TAILQ_INIT(&hardware->h_rports);
+
+#ifdef ENABLE_LLDPMED
+ if (LOCAL_CHASSIS(cfg)->c_med_cap_available) {
+ hardware->h_lport.p_med_cap_enabled = LLDP_MED_CAP_CAP;
+ if (!cfg->g_config.c_noinventory)
+ hardware->h_lport.p_med_cap_enabled |= LLDP_MED_CAP_IV;
+ }
+#endif
+
+ levent_hardware_init(hardware);
+ return hardware;
+}
+
+struct lldpd_mgmt *
+lldpd_alloc_mgmt(int family, void *addrptr, size_t addrsize, u_int32_t iface)
+{
+ struct lldpd_mgmt *mgmt;
+
+ log_debug("alloc", "allocate a new management address (family: %d)", family);
+
+ if (family <= LLDPD_AF_UNSPEC || family >= LLDPD_AF_LAST) {
+ errno = EAFNOSUPPORT;
+ return NULL;
+ }
+ if (addrsize > LLDPD_MGMT_MAXADDRSIZE) {
+ errno = EOVERFLOW;
+ return NULL;
+ }
+ mgmt = calloc(1, sizeof(struct lldpd_mgmt));
+ if (mgmt == NULL) {
+ errno = ENOMEM;
+ return NULL;
+ }
+ mgmt->m_family = family;
+ memcpy(&mgmt->m_addr, addrptr, addrsize);
+ mgmt->m_addrsize = addrsize;
+ mgmt->m_iface = iface;
+ return mgmt;
+}
+
+void
+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);
+ free(hardware->h_ifdescr_previous);
+ lldpd_port_cleanup(&hardware->h_lport, 1);
+ if (hardware->h_ops && hardware->h_ops->cleanup)
+ hardware->h_ops->cleanup(cfg, hardware);
+ levent_hardware_release(hardware);
+ free(hardware);
+}
+
+static void
+lldpd_ifdescr_neighbors(struct lldpd *cfg)
+{
+ if (!cfg->g_config.c_set_ifdescr) return;
+ struct lldpd_hardware *hardware;
+ TAILQ_FOREACH (hardware, &cfg->g_hardware, h_entries) {
+ struct lldpd_port *port;
+ char *description;
+ const char *neighbor = NULL;
+ unsigned neighbors = 0;
+ TAILQ_FOREACH (port, &hardware->h_rports, p_entries) {
+ if (SMART_HIDDEN(port)) continue;
+ neighbors++;
+ neighbor = port->p_chassis->c_name;
+ }
+ if (neighbors == 0)
+ description = strdup("");
+ else if (neighbors == 1 && neighbor && *neighbor != '\0') {
+ if (asprintf(&description, "%s", neighbor) == -1) {
+ continue;
+ }
+ } else {
+ if (asprintf(&description, "%d neighbor%s", neighbors,
+ (neighbors > 1) ? "s" : "") == -1) {
+ continue;
+ }
+ }
+ if (hardware->h_ifdescr_previous == NULL ||
+ strcmp(hardware->h_ifdescr_previous, description)) {
+ priv_iface_description(hardware->h_ifname, description);
+ free(hardware->h_ifdescr_previous);
+ hardware->h_ifdescr_previous = description;
+ } else
+ free(description);
+ }
+}
+
+static void
+lldpd_count_neighbors(struct lldpd *cfg)
+{
+#if HAVE_SETPROCTITLE
+ struct lldpd_chassis *chassis;
+ const char *neighbor;
+ unsigned neighbors = 0;
+ TAILQ_FOREACH (chassis, &cfg->g_chassis, c_entries) {
+ neighbors++;
+ neighbor = chassis->c_name;
+ }
+ neighbors--;
+ if (neighbors == 0)
+ setproctitle("no neighbor.");
+ else if (neighbors == 1 && neighbor && *neighbor != '\0')
+ setproctitle("connected to %s.", neighbor);
+ else
+ setproctitle("%d neighbor%s.", neighbors, (neighbors > 1) ? "s" : "");
+#endif
+ lldpd_ifdescr_neighbors(cfg);
+}
+
+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
+ agent_notify(hardware, NEIGHBOR_CHANGE_DELETED, rport);
+#endif
+}
+
+static void
+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 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;
+ /* Take the current flags into account to detect a change. */
+ port->_p_hardware_flags = hardware->h_flags;
+ u_int8_t *output = NULL;
+ ssize_t output_len;
+ char save[LLDPD_PORT_START_MARKER];
+ memcpy(save, port, sizeof(save));
+ /* coverity[sizeof_mismatch]
+ We intentionally partially memset port */
+ memset(port, 0, sizeof(save));
+ output_len = lldpd_port_serialize(port, (void **)&output);
+ memcpy(port, save, sizeof(save));
+ if (output_len == -1) {
+ log_warnx("localchassis",
+ "unable to serialize local port %s to check for differences",
+ hardware->h_ifname);
+ continue;
+ }
+
+ /* Compare with the previous value */
+ if (!hardware->h_ifindex_changed && 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);
+ } else {
+ log_debug("localchassis",
+ "change detected for port %s, resetting its timer",
+ hardware->h_ifname);
+ hardware->h_ifindex_changed = 0;
+ 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);
+ }
+ }
+}
+
+void
+lldpd_cleanup(struct lldpd *cfg)
+{
+ struct lldpd_hardware *hardware, *hardware_next;
+
+ log_debug("localchassis", "cleanup all ports");
+
+ for (hardware = TAILQ_FIRST(&cfg->g_hardware); hardware != NULL;
+ hardware = hardware_next) {
+ hardware_next = TAILQ_NEXT(hardware, h_entries);
+ if (!hardware->h_flags) {
+ int m = cfg->g_config.c_perm_ifaces ?
+ pattern_match(hardware->h_ifname,
+ cfg->g_config.c_perm_ifaces, 0) :
+ 0;
+ switch (m) {
+ case PATTERN_MATCH_DENIED:
+ log_debug("localchassis",
+ "delete non-permanent interface %s",
+ hardware->h_ifname);
+ TRACE(LLDPD_INTERFACES_DELETE(hardware->h_ifname));
+ TAILQ_REMOVE(&cfg->g_hardware, hardware, h_entries);
+ lldpd_remote_cleanup(hardware, notify_clients_deletion,
+ 1);
+ lldpd_hardware_cleanup(cfg, hardware);
+ break;
+ case PATTERN_MATCH_ALLOWED:
+ case PATTERN_MATCH_ALLOWED_EXACT:
+ log_debug("localchassis", "do not delete %s, permanent",
+ hardware->h_ifname);
+ lldpd_remote_cleanup(hardware, notify_clients_deletion,
+ 1);
+ break;
+ }
+ } else {
+ lldpd_remote_cleanup(hardware, notify_clients_deletion,
+ !(hardware->h_flags & IFF_RUNNING));
+ }
+ }
+
+ 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
+ expected to be part of a list! It will also be wiped from memory. */
+static void
+lldpd_move_chassis(struct lldpd_chassis *ochassis, struct lldpd_chassis *chassis)
+{
+ struct lldpd_mgmt *mgmt, *mgmt_next;
+
+ /* We want to keep refcount, index and list stuff from the current
+ * chassis */
+ TAILQ_ENTRY(lldpd_chassis) entries;
+ int refcount = ochassis->c_refcount;
+ int index = ochassis->c_index;
+ memcpy(&entries, &ochassis->c_entries, sizeof(entries));
+ lldpd_chassis_cleanup(ochassis, 0);
+
+ /* Make the copy. */
+ /* WARNING: this is a kludgy hack, we need in-place copy and cannot use
+ * marshaling. */
+ memcpy(ochassis, chassis, sizeof(struct lldpd_chassis));
+ TAILQ_INIT(&ochassis->c_mgmt);
+
+ /* Copy of management addresses */
+ for (mgmt = TAILQ_FIRST(&chassis->c_mgmt); mgmt != NULL; mgmt = mgmt_next) {
+ mgmt_next = TAILQ_NEXT(mgmt, m_entries);
+ TAILQ_REMOVE(&chassis->c_mgmt, mgmt, m_entries);
+ TAILQ_INSERT_TAIL(&ochassis->c_mgmt, mgmt, m_entries);
+ }
+
+ /* Restore saved values */
+ ochassis->c_refcount = refcount;
+ ochassis->c_index = index;
+ memcpy(&ochassis->c_entries, &entries, sizeof(entries));
+
+ /* Get rid of the new chassis */
+ free(chassis);
+}
+
+static int
+lldpd_guess_type(struct lldpd *cfg, char *frame, int s)
+{
+ size_t i, j;
+ if (s < ETHER_ADDR_LEN) return -1;
+ for (i = 0; cfg->g_protocols[i].mode != 0; i++) {
+ if (!cfg->g_protocols[i].enabled) continue;
+ if (cfg->g_protocols[i].guess == NULL) {
+ for (j = 0; j < sizeof(cfg->g_protocols[0].mac) /
+ sizeof(cfg->g_protocols[0].mac[0]);
+ j++) {
+ if (memcmp(frame, cfg->g_protocols[i].mac[j],
+ 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;
+ }
+ }
+ } else {
+ if (cfg->g_protocols[i].guess(frame, s)) {
+ log_debug("decode",
+ "guessed protocol is %s (from detector function)",
+ cfg->g_protocols[i].name);
+ return cfg->g_protocols[i].mode;
+ }
+ }
+ }
+ return -1;
+}
+
+static void
+lldpd_decode(struct lldpd *cfg, char *frame, int s, struct lldpd_hardware *hardware)
+{
+ int i;
+ struct lldpd_chassis *chassis, *ochassis = NULL;
+ struct lldpd_port *port, *oport = NULL, *aport;
+ int guess = LLDPD_MODE_LLDP;
+
+ log_debug("decode", "decode a received frame on %s", hardware->h_ifname);
+
+ 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;
+ memcpy(&eheader, frame, sizeof(struct ether_header));
+ if (eheader.ether_type == htons(ETHERTYPE_VLAN)) {
+ /* VLAN decapsulation means to shift 4 bytes left the frame from
+ * offset 2*ETHER_ADDR_LEN */
+ memmove(frame + 2 * ETHER_ADDR_LEN, frame + 2 * ETHER_ADDR_LEN + 4,
+ s - 2 * ETHER_ADDR_LEN);
+ s -= 4;
+ }
+
+ TAILQ_FOREACH (oport, &hardware->h_rports, p_entries) {
+ if ((oport->p_lastframe != NULL) && (oport->p_lastframe->size == s) &&
+ (memcmp(oport->p_lastframe->frame, frame, s) == 0)) {
+ /* Already received the same frame */
+ log_debug("decode", "duplicate frame, no need to decode");
+ oport->p_lastupdate = time(NULL);
+ return;
+ }
+ }
+
+ guess = lldpd_guess_type(cfg, frame, s);
+ for (i = 0; cfg->g_protocols[i].mode != 0; i++) {
+ if (!cfg->g_protocols[i].enabled) continue;
+ if (cfg->g_protocols[i].mode == guess) {
+ log_debug("decode", "using decode function for %s protocol",
+ cfg->g_protocols[i].name);
+ if (cfg->g_protocols[i].decode(cfg, frame, 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 =
+ cfg->g_protocols[i].mode;
+ break;
+ }
+ }
+ if (cfg->g_protocols[i].mode == 0) {
+ log_debug("decode", "unable to guess frame type on %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;
+ log_debug("decode", "search for the same MSAP");
+ TAILQ_FOREACH (oport, &hardware->h_rports, p_entries) {
+ if (port->p_protocol == oport->p_protocol) {
+ count++;
+ if ((port->p_id_subtype == oport->p_id_subtype) &&
+ (port->p_id_len == oport->p_id_len) &&
+ (memcmp(port->p_id, oport->p_id, port->p_id_len) == 0) &&
+ (chassis->c_id_subtype == oport->p_chassis->c_id_subtype) &&
+ (chassis->c_id_len == oport->p_chassis->c_id_len) &&
+ (memcmp(chassis->c_id, oport->p_chassis->c_id,
+ chassis->c_id_len) == 0)) {
+ ochassis = oport->p_chassis;
+ log_debug("decode", "MSAP is already known");
+ break;
+ }
+ }
+ }
+ /* Do we have room for a new MSAP? */
+ 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) {
+ log_debug("decode", "MSAP is unknown, search for the chassis");
+ TAILQ_FOREACH (ochassis, &cfg->g_chassis, c_entries) {
+ if ((chassis->c_protocol == ochassis->c_protocol) &&
+ (chassis->c_id_subtype == ochassis->c_id_subtype) &&
+ (chassis->c_id_len == ochassis->c_id_len) &&
+ (memcmp(chassis->c_id, ochassis->c_id, chassis->c_id_len) ==
+ 0))
+ break;
+ }
+ }
+
+ 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);
+ free(oport);
+ }
+ if (ochassis) {
+ if (port->p_ttl == 0) {
+ /* Shutdown LLDPDU is special. We do not want to replace
+ * the chassis. Free the new chassis (which is mostly empty) */
+ log_debug("decode", "received a shutdown LLDPDU");
+ lldpd_chassis_cleanup(chassis, 1);
+ } else {
+ lldpd_move_chassis(ochassis, chassis);
+ }
+ chassis = ochassis;
+ } else {
+ /* Chassis not known, add it */
+ log_debug("decode", "unknown chassis, add it to the list");
+ chassis->c_index = ++cfg->g_lastrid;
+ chassis->c_refcount = 0;
+ TAILQ_INSERT_TAIL(&cfg->g_chassis, chassis, c_entries);
+ i = 0;
+ TAILQ_FOREACH (ochassis, &cfg->g_chassis, c_entries)
+ i++;
+ log_debug("decode", "%d different systems are known", i);
+ }
+ /* Add port */
+ port->p_lastchange = port->p_lastupdate = time(NULL);
+ if ((port->p_lastframe = (struct lldpd_frame *)malloc(
+ s + sizeof(struct lldpd_frame))) != NULL) {
+ port->p_lastframe->size = s;
+ memcpy(port->p_lastframe->frame, frame, s);
+ }
+ TAILQ_INSERT_TAIL(&hardware->h_rports, port, p_entries);
+ port->p_chassis = chassis;
+ port->p_chassis->c_refcount++;
+ /* 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;
+ /* coverity[use_after_free]
+ TAILQ_REMOVE does the right thing */
+ TAILQ_FOREACH (aport, &hardware->h_rports, p_entries)
+ i++;
+ log_debug("decode", "%d neighbors for %s", i, hardware->h_ifname);
+
+ if (!oport) hardware->h_insert_cnt++;
+
+ /* Notify */
+ log_debug("decode", "send notifications for changes on %s", hardware->h_ifname);
+ 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, 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) {
+ /* New neighbor, fast start */
+ if (hardware->h_cfg->g_config.c_enable_fast_start &&
+ !hardware->h_tx_fast) {
+ log_debug("decode",
+ "%s: entering fast start due to "
+ "new neighbor",
+ hardware->h_ifname);
+ hardware->h_tx_fast = hardware->h_cfg->g_config.c_tx_fast_init;
+ }
+
+ levent_schedule_pdu(hardware);
+ }
+#endif
+
+ return;
+}
+
+/* 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 cmd[][12] = { "lsb_release", "-s", "-d" };
+ char *const command[] = { cmd[0], cmd[1], cmd[2], NULL };
+ int pid, status, devnull, count;
+ int pipefd[2];
+
+ log_debug("localchassis", "grab LSB release");
+
+ if (pipe(pipefd)) {
+ log_warn("localchassis", "unable to get a pair of pipes");
+ return NULL;
+ }
+
+ pid = vfork();
+ switch (pid) {
+ case -1:
+ log_warn("localchassis", "unable to fork");
+ return NULL;
+ 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) {
+ log_info("localchassis", "unable to read from lsb_release");
+ close(pipefd[0]);
+ waitpid(pid, &status, 0);
+ return NULL;
+ }
+ close(pipefd[0]);
+ if (count >= sizeof(release)) {
+ log_info("localchassis", "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)) {
+ log_info("localchassis",
+ "lsb_release information not available");
+ return NULL;
+ }
+ if (!count) {
+ log_info("localchassis",
+ "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];
+ char line[1024];
+ char *key, *val;
+ char *ptr1 = release;
+
+ log_debug("localchassis", "grab OS release");
+ FILE *fp = fopen("/etc/os-release", "r");
+ if (!fp) {
+ 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, sizeof(line), fp) != NULL)) {
+ key = strtok(line, "=");
+ val = strtok(NULL, "=");
+
+ if (strncmp(key, "PRETTY_NAME", sizeof(line)) == 0) {
+ strlcpy(release, val, sizeof(line));
+ break;
+ }
+ }
+ fclose(fp);
+
+ /* Remove trailing newline and all " in the string. */
+ ptr1 = release + strlen(release) - 1;
+ while (ptr1 != release && ((*ptr1 == '"') || (*ptr1 == '\n'))) {
+ *ptr1 = '\0';
+ ptr1--;
+ }
+ if (release[0] == '"') return release + 1;
+ return release;
+}
+
+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;
+
+ log_debug("smartfilter", "apply smart filter for port %s", hardware->h_ifname);
+
+ /* 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_config.c_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_config.c_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])
+ strncat(buffer, ", ",
+ sizeof(buffer) - strlen(buffer) - 1);
+ strncat(buffer, cfg->g_protocols[i].name,
+ sizeof(buffer) - strlen(buffer) - 1);
+ }
+ }
+ log_debug("smartfilter", "%s: %s: %d visible neighbors (out of %d)",
+ hardware->h_ifname, (mask == SMART_OUTGOING) ? "out filter" : "in filter",
+ k, j);
+ log_debug("smartfilter", "%s: protocols: %s", hardware->h_ifname,
+ buffer[0] ? buffer : "(none)");
+}
+
+/* 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_config.c_smart) return;
+ log_debug("smartfilter", "apply smart filter results on all ports");
+ TAILQ_FOREACH (hardware, &cfg->g_hardware, h_entries) {
+ if (cfg->g_config.c_smart & SMART_INCOMING_FILTER)
+ lldpd_hide_ports(cfg, hardware, SMART_INCOMING);
+ if (cfg->g_config.c_smart & SMART_OUTGOING_FILTER)
+ lldpd_hide_ports(cfg, hardware, SMART_OUTGOING);
+ }
+}
+
+/* If PD device and PSE allocated power, echo back this change. If we have
+ * several LLDP neighbors, we use the latest updated. */
+static void
+lldpd_dot3_power_pd_pse(struct lldpd_hardware *hardware)
+{
+#ifdef ENABLE_DOT3
+ struct lldpd_port *port, *selected_port = NULL;
+ /* Are we a PD device? */
+ if (hardware->h_lport.p_power.devicetype != LLDP_DOT3_POWER_PD) return;
+ TAILQ_FOREACH (port, &hardware->h_rports, p_entries) {
+ if (port->p_hidden_in) continue;
+
+ if (port->p_protocol != LLDPD_MODE_LLDP &&
+ port->p_protocol != LLDPD_MODE_CDPV2)
+ continue;
+
+ if (port->p_power.devicetype != LLDP_DOT3_POWER_PSE) continue;
+ if (!selected_port || port->p_lastupdate > selected_port->p_lastupdate)
+ selected_port = port;
+ }
+ if (selected_port &&
+ selected_port->p_power.allocated != hardware->h_lport.p_power.allocated) {
+ log_info("receive",
+ "for %s, PSE told us allocated is now %d instead of %d",
+ hardware->h_ifname, selected_port->p_power.allocated,
+ hardware->h_lport.p_power.allocated);
+ hardware->h_lport.p_power.allocated = selected_port->p_power.allocated;
+ levent_schedule_pdu(hardware);
+ }
+
+# ifdef ENABLE_CDP
+ if (selected_port &&
+ selected_port->p_cdp_power.management_id !=
+ hardware->h_lport.p_cdp_power.management_id) {
+ hardware->h_lport.p_cdp_power.management_id =
+ selected_port->p_cdp_power.management_id;
+ }
+# endif
+
+#endif
+}
+
+void
+lldpd_recv(struct lldpd *cfg, struct lldpd_hardware *hardware, int fd)
+{
+ char *buffer = NULL;
+ int n;
+ log_debug("receive", "receive a frame on %s", hardware->h_ifname);
+ if ((buffer = (char *)malloc(hardware->h_mtu)) == NULL) {
+ log_warn("receive", "failed to alloc reception buffer");
+ return;
+ }
+ if ((n = hardware->h_ops->recv(cfg, hardware, fd, buffer, hardware->h_mtu)) ==
+ -1) {
+ log_debug("receive", "discard frame received on %s",
+ hardware->h_ifname);
+ 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);
+ free(buffer);
+ return;
+ }
+ 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_dot3_power_pd_pse(hardware);
+ 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)
+{
+ struct lldpd *cfg = hardware->h_cfg;
+ struct lldpd_port *port;
+ 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;
+
+ log_debug("send", "send PDU on %s", hardware->h_ifname);
+ 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 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) 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);
+ cfg->g_protocols[i].send(cfg, hardware);
+ hardware->h_lport.p_protocol = cfg->g_protocols[i].mode;
+ sent++;
+ break;
+ }
+ }
+ }
+
+ if (!sent) {
+ /* Nothing was sent for this port, let's speak the first
+ * 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, hardware);
+ break;
+ }
+ if (cfg->g_protocols[i].mode == 0)
+ log_warnx("send", "no protocol enabled, dunno what to send");
+ }
+}
+
+#ifdef ENABLE_LLDPMED
+static void
+lldpd_med(struct lldpd *cfg, struct utsname *un)
+{
+ static short int once = 0;
+ if (!once && cfg) {
+ LOCAL_CHASSIS(cfg)->c_med_hw = dmi_hw();
+ LOCAL_CHASSIS(cfg)->c_med_fw = dmi_fw();
+ LOCAL_CHASSIS(cfg)->c_med_sn = dmi_sn();
+ LOCAL_CHASSIS(cfg)->c_med_manuf = dmi_manuf();
+ LOCAL_CHASSIS(cfg)->c_med_model = dmi_model();
+ LOCAL_CHASSIS(cfg)->c_med_asset = dmi_asset();
+ if (un) {
+ if (LOCAL_CHASSIS(cfg)->c_med_sw)
+ free(LOCAL_CHASSIS(cfg)->c_med_sw);
+
+ if (cfg->g_config.c_advertise_version)
+ LOCAL_CHASSIS(cfg)->c_med_sw = strdup(un->release);
+ else
+ LOCAL_CHASSIS(cfg)->c_med_sw = strdup("Unknown");
+ }
+ once = 1;
+ }
+}
+#endif
+
+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;
+ }
+ return routing;
+}
+
+void
+lldpd_update_localchassis(struct lldpd *cfg)
+{
+ struct utsname un;
+ char *hp;
+
+ log_debug("localchassis", "update information for local chassis");
+ assert(LOCAL_CHASSIS(cfg) != NULL);
+
+ /* Set system name and description */
+ if (uname(&un) < 0) fatal("localchassis", "failed to get system information");
+ 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)
+ fatal("localchassis", NULL);
+ if (cfg->g_config.c_description) {
+ log_debug("localchassis", "use overridden description `%s`",
+ cfg->g_config.c_description);
+ if (asprintf(&LOCAL_CHASSIS(cfg)->c_descr, "%s",
+ cfg->g_config.c_description) == -1)
+ fatal("localchassis", "failed to set full system description");
+ } else {
+ if (cfg->g_config.c_advertise_version) {
+ log_debug("localchassis", "advertise system version");
+ if (asprintf(&LOCAL_CHASSIS(cfg)->c_descr, "%s %s %s %s %s",
+ cfg->g_lsb_release ? cfg->g_lsb_release : "",
+ un.sysname, un.release, un.version, un.machine) == -1)
+ fatal("localchassis",
+ "failed to set full system description");
+ } else {
+ log_debug("localchassis", "do not advertise system version");
+ if (asprintf(&LOCAL_CHASSIS(cfg)->c_descr, "%s",
+ cfg->g_lsb_release ? cfg->g_lsb_release : un.sysname) ==
+ -1)
+ fatal("localchassis",
+ "failed to set minimal system description");
+ }
+ }
+ if (cfg->g_config.c_platform == NULL)
+ cfg->g_config.c_platform = strdup(un.sysname);
+
+ if (!cfg->g_config.c_cap_override) {
+ /* Check routing */
+ if (lldpd_routing_enabled(cfg)) {
+ log_debug("localchassis",
+ "routing is enabled, enable router capability");
+ LOCAL_CHASSIS(cfg)->c_cap_enabled |= LLDP_CAP_ROUTER;
+ } else
+ LOCAL_CHASSIS(cfg)->c_cap_enabled &= ~LLDP_CAP_ROUTER;
+
+#ifdef ENABLE_LLDPMED
+ if (LOCAL_CHASSIS(cfg)->c_cap_available & LLDP_CAP_TELEPHONE)
+ LOCAL_CHASSIS(cfg)->c_cap_enabled |= LLDP_CAP_TELEPHONE;
+ lldpd_med(cfg, &un);
+#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;
+ else if (LOCAL_CHASSIS(cfg)->c_cap_enabled != LLDP_CAP_STATION)
+ 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
+ interface for example)
+ */
+ if (cfg->g_config.c_cid_string != NULL) {
+ log_debug("localchassis", "use specified chassis ID string");
+ free(LOCAL_CHASSIS(cfg)->c_id);
+ if (!(LOCAL_CHASSIS(cfg)->c_id = strdup(cfg->g_config.c_cid_string)))
+ fatal("localchassis", NULL);
+ LOCAL_CHASSIS(cfg)->c_id_len = strlen(cfg->g_config.c_cid_string);
+ LOCAL_CHASSIS(cfg)->c_id_subtype = LLDP_CHASSISID_SUBTYPE_LOCAL;
+ }
+ if (LOCAL_CHASSIS(cfg)->c_id == NULL) {
+ log_debug("localchassis",
+ "no chassis ID is currently set, use chassis name");
+ if (!(LOCAL_CHASSIS(cfg)->c_id = strdup(LOCAL_CHASSIS(cfg)->c_name)))
+ fatal("localchassis", NULL);
+ LOCAL_CHASSIS(cfg)->c_id_len = strlen(LOCAL_CHASSIS(cfg)->c_name);
+ LOCAL_CHASSIS(cfg)->c_id_subtype = LLDP_CHASSISID_SUBTYPE_LOCAL;
+ }
+}
+
+void
+lldpd_update_localports(struct lldpd *cfg)
+{
+ struct lldpd_hardware *hardware;
+
+ log_debug("localchassis", "update information for local ports");
+
+ /* 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;
+
+ TRACE(LLDPD_INTERFACES_UPDATE());
+ interfaces_update(cfg);
+ lldpd_cleanup(cfg);
+ lldpd_reset_timer(cfg);
+}
+
+void
+lldpd_loop(struct lldpd *cfg)
+{
+ /* Main loop.
+ 1. Update local ports information
+ 2. Update local chassis information
+ */
+ log_debug("loop", "start new loop");
+ if (!cfg->g_config.c_cap_override) LOCAL_CHASSIS(cfg)->c_cap_enabled = 0;
+ /* Information for local ports is triggered even when it is possible to
+ * update them on some other event because we want to refresh them if we
+ * missed something. */
+ log_debug("loop", "update information for local ports");
+ lldpd_update_localports(cfg);
+ log_debug("loop", "update information for local chassis");
+ lldpd_update_localchassis(cfg);
+ lldpd_count_neighbors(cfg);
+}
+
+static void
+lldpd_exit(struct lldpd *cfg)
+{
+ char *lockname = NULL;
+ struct lldpd_hardware *hardware, *hardware_next;
+ log_debug("main", "exit lldpd");
+
+ TAILQ_FOREACH (hardware, &cfg->g_hardware, h_entries)
+ lldpd_send_shutdown(hardware);
+
+ if (asprintf(&lockname, "%s.lock", cfg->g_ctlname) != -1) {
+ priv_ctl_cleanup(lockname);
+ free(lockname);
+ }
+ close(cfg->g_ctl);
+ priv_ctl_cleanup(cfg->g_ctlname);
+ log_debug("main", "cleanup hardware information");
+ for (hardware = TAILQ_FIRST(&cfg->g_hardware); hardware != NULL;
+ hardware = hardware_next) {
+ hardware_next = TAILQ_NEXT(hardware, h_entries);
+ log_debug("main", "cleanup interface %s", hardware->h_ifname);
+ 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);
+}
+
+/**
+ * Run lldpcli to configure lldpd.
+ *
+ * @return PID of running lldpcli or -1 if error.
+ */
+static pid_t
+lldpd_configure(int use_syslog, int debug, const char *path, const char *ctlname,
+ const char *config_path)
+{
+ 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");
+ return -1;
+ case 0:
+ /* Child, exec lldpcli */
+ if ((devnull = open("/dev/null", O_RDWR, 0)) != -1) {
+ dup2(devnull, STDIN_FILENO);
+ dup2(devnull, STDOUT_FILENO);
+ if (devnull > 2) close(devnull);
+
+ if (config_path) {
+ execl(path, "lldpcli", sdebug, "-u", ctlname, "-C",
+ config_path, "resume", (char *)NULL);
+ } else {
+ 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);
+ break;
+ default:
+ /* Father, don't do anything stupid */
+ return lldpcli;
+ }
+ /* Should not be here */
+ return -1;
+}
+
+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 } };
+
+#ifndef HOST_OS_OSX
+/**
+ * Tell if we have been started by systemd.
+ */
+static int
+lldpd_started_by_systemd()
+{
+# ifdef HOST_OS_LINUX
+ int fd = -1;
+ const char *notifysocket = getenv("NOTIFY_SOCKET");
+ if (!notifysocket || !strchr("@/", notifysocket[0]) || strlen(notifysocket) < 2)
+ return 0;
+
+ log_debug("main", "running with systemd, don't fork but signal ready");
+ if ((fd = socket(AF_UNIX, SOCK_DGRAM, 0)) < 0) {
+ log_warn("main", "unable to open systemd notification socket %s",
+ notifysocket);
+ return 0;
+ }
+
+ struct sockaddr_un su = { .sun_family = AF_UNIX };
+ strlcpy(su.sun_path, notifysocket, sizeof(su.sun_path));
+ if (notifysocket[0] == '@') su.sun_path[0] = 0;
+
+ char ready[] = "READY=1";
+ struct iovec iov = { .iov_base = ready, .iov_len = sizeof ready - 1 };
+ struct msghdr hdr = { .msg_name = &su,
+ .msg_namelen =
+ offsetof(struct sockaddr_un, sun_path) + strlen(notifysocket),
+ .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);
+ return 0;
+ }
+ close(fd);
+ return 1;
+# else
+ return 0;
+# endif
+}
+#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[], char *envp[])
+{
+ struct lldpd *cfg;
+ struct lldpd_chassis *lchassis;
+ int ch, debug = 0, use_syslog = 1, daemonize = 1;
+ const char *errstr;
+#ifdef USE_SNMP
+ int snmp = 0;
+ const char *agentx = NULL; /* AgentX socket */
+#endif
+ 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:p:xX:m:u:4:6:I:C:p:M:P:S:iL:O:@ ";
+ int i, found, advertise_version = 1;
+#ifdef ENABLE_LLDPMED
+ int lldpmed = 0, noinventory = 0;
+ int enable_fast_start = 1;
+#endif
+ char *descr_override = NULL;
+ 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, version = 0;
+ int ctl;
+ const char *config_file = NULL;
+
+#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
+ */
+ 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':
+ version++;
+ break;
+ case 'd':
+ 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':
+ 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':
+ if (interfaces) {
+ fprintf(stderr, "-I can only be used once\n");
+ usage();
+ }
+ interfaces = strdup(optarg);
+ break;
+ case 'C':
+ 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 = strtonum(optarg, 1, 4, &errstr);
+ if (errstr) {
+ fprintf(stderr,
+ "-M requires an argument between 1 and 4\n");
+ usage();
+ }
+ break;
+ case 'i':
+ noinventory = 1;
+ break;
+#else
+ case 'M':
+ case 'i':
+ fprintf(stderr, "LLDP-MED support is not built-in\n");
+ usage();
+ break;
+#endif
+#ifdef USE_SNMP
+ case 'x':
+ snmp = 1;
+ break;
+ case 'X':
+ if (agentx) {
+ fprintf(stderr, "-X can only be used once\n");
+ usage();
+ }
+ snmp = 1;
+ agentx = optarg;
+ break;
+#else
+ case 'x':
+ case 'X':
+ fprintf(stderr, "SNMP support is not built-in\n");
+ usage();
+#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 = 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;
+ case 'O':
+ if (config_file) {
+ fprintf(stderr, "-O can only be used once\n");
+ usage();
+ }
+ config_file = optarg;
+ break;
+ default:
+ found = 0;
+ for (i = 0; protos[i].mode != 0; i++) {
+ if (ch == protos[i].arg) {
+ found = 1;
+ protos[i].enabled++;
+ }
+ }
+ if (!found) usage();
+ }
+ }
+
+ 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) {
+ fprintf(stderr, "Incorrect mode for -H\n");
+ usage();
+ }
+ smart = filters[i].b;
+
+ log_init(use_syslog, debug, __progname);
+ tzset(); /* Get timezone info before chroot */
+ if (use_syslog && daemonize) {
+ /* So, we use syslog and we daemonize (or we are started by
+ * systemd). No need to continue writing to stdout. */
+ int fd;
+ /* coverity[resource_leak]
+ fd may be leaked if < 2, it's expected */
+ 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();
+#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
+ fatalx("main", "fuzzing enabled, unsafe for production");
+#endif
+
+ /* Grab uid and gid to use for priv sep */
+#ifdef ENABLE_PRIVSEP
+ if ((user = getpwnam(PRIVSEP_USER)) == NULL)
+ fatalx("main",
+ "no " PRIVSEP_USER
+ " user for privilege separation, please create it");
+ uid = user->pw_uid;
+ if ((group = getgrnam(PRIVSEP_GROUP)) == NULL)
+ fatalx("main",
+ "no " PRIVSEP_GROUP
+ " group for privilege separation, please create it");
+ gid = group->gr_gid;
+#endif
+
+ /* Create and setup socket */
+ int retry = 1;
+ log_debug("main", "creating control socket");
+ while ((ctl = ctl_create(ctlname)) == -1) {
+ if (retry-- && errno == EADDRINUSE) {
+ /* Check if a daemon is really listening */
+ int tfd;
+ log_info("main",
+ "unable to create control socket because it already exists");
+ log_info("main", "check if another instance is running");
+ if ((tfd = ctl_connect(ctlname)) != -1) {
+ /* Another instance is running */
+ close(tfd);
+ log_warnx("main",
+ "another instance is running, please stop it");
+ fatalx("main", "giving up");
+ } else if (errno == ECONNREFUSED) {
+ /* Nobody is listening */
+ log_info("main",
+ "old control socket is present, clean it");
+ ctl_cleanup(ctlname);
+ continue;
+ }
+ log_warn("main",
+ "cannot determine if another daemon is already running");
+ fatalx("main", "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
+
+ /* Create associated advisory lock file */
+ char *lockname = NULL;
+ int fd;
+ if (asprintf(&lockname, "%s.lock", ctlname) == -1)
+ fatal("main", "cannot build lock name");
+ if ((fd = open(lockname, O_CREAT | O_RDWR, 0000)) == -1)
+ fatal("main", "cannot create lock file for control socket");
+ close(fd);
+#ifdef ENABLE_PRIVSEP
+ if (chown(lockname, uid, gid) == -1)
+ log_warn("main", "unable to chown control socket lock");
+ if (chmod(lockname,
+ S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IWGRP | S_IXGRP) == -1)
+ log_warn("main", "unable to chmod control socket lock");
+#endif
+ free(lockname);
+
+ /* Disable SIGPIPE */
+ signal(SIGPIPE, SIG_IGN);
+
+ /* Disable SIGHUP, until handlers are installed */
+ signal(SIGHUP, SIG_IGN);
+
+ /* Daemonization, unless started by systemd or launchd or debug */
+#ifndef HOST_OS_OSX
+ if (!lldpd_started_by_systemd() && daemonize) {
+ int pid;
+ char *spid;
+ log_debug("main", "going into background");
+ if (daemon(0, 1) != 0) fatal("main", "failed to detach daemon");
+ 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
+ " (or the specified one)");
+ if (write(pid, spid, strlen(spid)) == -1)
+ fatal("main",
+ "unable to write pid file " LLDPD_PID_FILE
+ " (or the specified one)");
+ free(spid);
+ close(pid);
+ }
+#endif
+
+ /* Configuration with lldpcli */
+ if (lldpcli) {
+ if (!config_file) {
+ log_debug("main",
+ "invoking lldpcli for default configuration locations");
+ } else {
+ log_debug("main",
+ "invoking lldpcli for user supplied configuration location");
+ }
+ if (lldpd_configure(use_syslog, debug, lldpcli, ctlname, config_file) ==
+ -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");
+ lsb_release = lldpd_get_os_release();
+ if (!lsb_release) {
+ lsb_release = lldpd_get_lsb_release();
+ }
+
+ log_debug("main", "initialize privilege separation");
+#ifdef ENABLE_PRIVSEP
+ priv_init(PRIVSEP_CHROOT, ctl, uid, gid);
+#else
+ priv_init();
+#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;
+ cfg->g_config.c_cid_pattern = cidp;
+ cfg->g_config.c_iface_pattern = interfaces;
+ cfg->g_config.c_smart = smart;
+ if (lldpcli) cfg->g_config.c_paused = 1;
+ cfg->g_config.c_receiveonly = receiveonly;
+ cfg->g_config.c_tx_interval = LLDPD_TX_INTERVAL * 1000;
+ 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_ttl = (cfg->g_config.c_ttl + 999) / 1000;
+ cfg->g_config.c_max_neighbors = LLDPD_MAX_NEIGHBORS;
+#ifdef ENABLE_LLDPMED
+ cfg->g_config.c_enable_fast_start = enable_fast_start;
+ cfg->g_config.c_tx_fast_init = LLDPD_FAST_INIT;
+ cfg->g_config.c_tx_fast_interval = LLDPD_FAST_TX_INTERVAL;
+#endif
+#ifdef USE_SNMP
+ 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");
+ if ((cfg->g_sock = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
+ fatal("main", "failed to get ioctl socket");
+
+ /* Description */
+ if (!(cfg->g_config.c_advertise_version = advertise_version) && lsb_release &&
+ lsb_release[strlen(lsb_release) - 1] == '\n')
+ lsb_release[strlen(lsb_release) - 1] = '\0';
+ cfg->g_lsb_release = lsb_release;
+ if (descr_override) cfg->g_config.c_description = descr_override;
+
+ if (platform_override) cfg->g_config.c_platform = platform_override;
+
+ /* Set system capabilities */
+ log_debug("main", "set system capabilities");
+ if ((lchassis = (struct lldpd_chassis *)calloc(1,
+ sizeof(struct lldpd_chassis))) == NULL)
+ fatal("localchassis", NULL);
+ cfg->g_config.c_cap_advertise = 1;
+ cfg->g_config.c_cap_override = 0;
+ lchassis->c_cap_available =
+ LLDP_CAP_BRIDGE | LLDP_CAP_WLAN | LLDP_CAP_ROUTER | LLDP_CAP_STATION;
+ cfg->g_config.c_mgmt_advertise = 1;
+ TAILQ_INIT(&lchassis->c_mgmt);
+#ifdef ENABLE_LLDPMED
+ if (lldpmed > 0) {
+ if (lldpmed == LLDP_MED_CLASS_III)
+ lchassis->c_cap_available |= LLDP_CAP_TELEPHONE;
+ lchassis->c_med_type = lldpmed;
+ lchassis->c_med_cap_available = LLDP_MED_CAP_CAP | LLDP_MED_CAP_IV |
+ LLDP_MED_CAP_LOCATION | LLDP_MED_CAP_POLICY | LLDP_MED_CAP_MDI_PSE |
+ LLDP_MED_CAP_MDI_PD;
+ cfg->g_config.c_noinventory = noinventory;
+ } else
+ cfg->g_config.c_noinventory = 1;
+#endif
+
+ log_debug("main", "initialize protocols");
+ cfg->g_protocols = protos;
+ 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);
+ TAILQ_INSERT_TAIL(&cfg->g_chassis, lchassis, c_entries);
+ lchassis->c_refcount++; /* We should always keep a reference to local chassis */
+
+ /* Main loop */
+ log_debug("main", "start main loop");
+ levent_loop(cfg);
+ lchassis->c_refcount--;
+ lldpd_exit(cfg);
+ free(cfg);
+
+ return (0);
+}