*/
#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>
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 },
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, "-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");
}
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)
{
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_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;
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;
{
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);
if (neighbors == 0)
priv_iface_description(hardware->h_ifname,
"");
- else if (neighbors == 1 && neighbor) {
+ else if (neighbors == 1 && neighbor && *neighbor != '\0') {
if (asprintf(&description, "%s",
neighbor) != -1) {
priv_iface_description(hardware->h_ifname, description);
}
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);
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
/* 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));
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);
}
}
}
lldpd_cleanup(struct lldpd *cfg)
{
struct lldpd_hardware *hardware, *hardware_next;
- struct lldpd_chassis *chassis, *chassis_next;
log_debug("localchassis", "cleanup all ports");
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
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;
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;
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 =
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;
}
}
/* 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) {
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,
/* 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, 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, i, port);
+ agent_notify(hardware, NEIGHBOR_CHANGE_ADDED, port);
#endif
+ }
#ifdef ENABLE_LLDPMED
if (!oport && port->p_chassis->c_med_type) {
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]);
if (pipefd[1] > 2) close(pipefd[1]);
execvp("lsb_release", command);
}
- exit(127);
+ _exit(127);
break;
default:
/* Father, read the output from the children */
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;
}
}
}
}
+/* 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)
+ 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->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);
+ }
+#endif
+}
+
void
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);
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)
{
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;
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;
}
* 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,
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;
/* 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)
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)) {
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;
+ 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
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);
{
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");
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);
}
/**
* @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, const char *config_path)
{
- 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");
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");
+ 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);
+ _exit(127);
break;
default:
/* Father, don't do anything stupid */
};
#ifndef HOST_OS_OSX
-/**
- * Tell if we have been started by upstart.
- */
-static int
-lldpd_started_by_upstart()
-{
-#ifdef HOST_OS_LINUX
- const char *upstartjob = getenv("UPSTART_JOB");
- if (!(upstartjob && !strcmp(upstartjob, "lldpd")))
- return 0;
- log_debug("main", "running with upstart, don't fork but stop");
- raise(SIGSTOP);
- return 1;
-#else
- return 0;
-#endif
-}
-
/**
* Tell if we have been 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);
}
#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:O:@ ";
int i, found, advertise_version = 1;
#ifdef ENABLE_LLDPMED
int lldpmed = 0, noinventory = 0;
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;
+ 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
*/
- 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();
}
snmp = 1;
break;
case 'X':
+ if (agentx) {
+ fprintf(stderr, "-X can only be used once\n");
+ usage();
+ }
snmp = 1;
agentx = optarg;
break;
#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;
+ 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) {
- if (protos[i].enabled < 3) {
- found = 1;
- if (protos[i].enabled++ == 1)
- break;
- }
+ found = 1;
+ protos[i].enabled++;
}
}
if (!found)
}
}
+ 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) {
}
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
+ * 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;
/* 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");
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 */
+ /* Daemonization, unless started by systemd or launchd or debug */
#ifndef HOST_OS_OSX
- if (!lldpd_started_by_upstart() && !lldpd_started_by_systemd() &&
- !debug) {
+ if (daemonize &&
+ !lldpd_started_by_systemd()) {
int pid;
char *spid;
- log_debug("main", "daemonize");
- if (daemon(0, 0) != 0)
+ log_debug("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) {
+ 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");
}
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;
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;
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 ((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) {
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);
/* Main loop */
log_debug("main", "start main loop");
levent_loop(cfg);
+ lchassis->c_refcount--;
lldpd_exit(cfg);
+ free(cfg);
return (0);
}