/*
* Copyright (c) 2008 Vincent Bernat <bernat@luffy.cx>
*
- * Permission to use, copy, modify, and distribute this software for any
+ * Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
#include <libgen.h>
#include <sys/utsname.h>
#include <sys/types.h>
+#include <sys/wait.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <sys/time.h>
static void lldpd_exit(void);
static void lldpd_send_all(struct lldpd *);
static void lldpd_recv_all(struct lldpd *);
+static void lldpd_hide_all(struct lldpd *);
+static void lldpd_hide_ports(struct lldpd *, struct lldpd_hardware *, int);
static int lldpd_guess_type(struct lldpd *, char *, int);
static void lldpd_decode(struct lldpd *, char *, int,
struct lldpd_hardware *);
static void lldpd_update_chassis(struct lldpd_chassis *,
const struct lldpd_chassis *);
+static char *lldpd_get_lsb_release(void);
+static char *lldpd_get_os_release(void);
#ifdef ENABLE_LLDPMED
static void lldpd_med(struct lldpd_chassis *);
#endif
fprintf(stderr, "\n");
fprintf(stderr, "-d Do not daemonize.\n");
+ fprintf(stderr, "-r Receive-only mode\n");
fprintf(stderr, "-i Disable LLDP-MED inventory TLV transmission.\n");
fprintf(stderr, "-k Disable advertising of kernel release, version, machine.\n");
fprintf(stderr, "-S descr Override the default system description.\n");
fprintf(stderr, "-m IP Specify the management address of this system.\n");
+ fprintf(stderr, "-H mode Specify the behaviour when detecting multiple neighbors.\n");
#ifdef ENABLE_LLDPMED
fprintf(stderr, "-M class Enable emission of LLDP-MED frame. 'class' should be one of:\n");
fprintf(stderr, " 1 Generic Endpoint (Class I)\n");
#endif
#ifdef USE_SNMP
fprintf(stderr, "-x Enable SNMP subagent.\n");
-#endif
-#ifdef ENABLE_LISTENVLAN
- fprintf(stderr, "-v Listen on VLAN as well.\n");
#endif
fprintf(stderr, "\n");
struct lldpd_port *port, *oport = NULL;
int guess = LLDPD_MODE_LLDP;
- /* Discard VLAN frames */
- if ((s >= sizeof(struct ethhdr)) &&
- (((struct ethhdr*)frame)->h_proto == htons(ETHERTYPE_VLAN)))
+ if (s < sizeof(struct ethhdr) + 4)
+ /* Too short, just discard it */
return;
+ /* Decapsulate VLAN frames */
+ if (((struct ethhdr*)frame)->h_proto == htons(ETHERTYPE_VLAN)) {
+ /* VLAN decapsulation means to shift 4 bytes left the frame from
+ * offset 2*ETH_ALEN */
+ memmove(frame + 2*ETH_ALEN, frame + 2*ETH_ALEN + 4, s - 2*ETH_ALEN);
+ s -= 4;
+ }
TAILQ_FOREACH(oport, &hardware->h_rports, p_entries) {
if ((oport->p_lastframe != NULL) &&
freed with lldpd_port_cleanup() and therefore, the refcount
of the chassis that was attached to it is decreased.
*/
- i = 0; TAILQ_FOREACH(oport, &hardware->h_rports, p_entries) i++;
- LLOG_DEBUG("Currently, %s known %d neighbors",
+ i = 0; TAILQ_FOREACH(oport, &hardware->h_rports, p_entries)
+ i++;
+ LLOG_DEBUG("Currently, %s knows %d neighbors",
hardware->h_ifname, i);
return;
}
memcpy(&ochassis->c_entries, &entries, sizeof(entries));
}
+/* Get the output of lsb_release -s -d. This is a slow function. It should be
+ called once. It return NULL if any problem happens. Otherwise, this is a
+ statically allocated buffer. The result includes the trailing \n */
+static char *
+lldpd_get_lsb_release() {
+ static char release[1024];
+ char *const command[] = { "lsb_release", "-s", "-d", NULL };
+ int pid, status, devnull, count;
+ int pipefd[2];
+
+ if (pipe(pipefd)) {
+ LLOG_WARN("unable to get a pair of pipes");
+ return NULL;
+ }
+
+ if ((pid = fork()) < 0) {
+ LLOG_WARN("unable to fork");
+ return NULL;
+ }
+ switch (pid) {
+ case 0:
+ /* Child, exec lsb_release */
+ close(pipefd[0]);
+ if ((devnull = open("/dev/null", O_RDWR, 0)) != -1) {
+ dup2(devnull, STDIN_FILENO);
+ dup2(devnull, STDERR_FILENO);
+ dup2(pipefd[1], STDOUT_FILENO);
+ if (devnull > 2) close(devnull);
+ if (pipefd[1] > 2) close(pipefd[1]);
+ execvp("lsb_release", command);
+ }
+ exit(127);
+ break;
+ default:
+ /* Father, read the output from the children */
+ close(pipefd[1]);
+ count = 0;
+ do {
+ status = read(pipefd[0], release+count, sizeof(release)-count);
+ if ((status == -1) && (errno == EINTR)) continue;
+ if (status > 0)
+ count += status;
+ } while (count < sizeof(release) && (status > 0));
+ if (status < 0) {
+ LLOG_WARN("unable to read from lsb_release");
+ close(pipefd[0]);
+ waitpid(pid, &status, 0);
+ return NULL;
+ }
+ close(pipefd[0]);
+ if (count >= sizeof(release)) {
+ LLOG_INFO("output of lsb_release is too large");
+ waitpid(pid, &status, 0);
+ return NULL;
+ }
+ status = -1;
+ if (waitpid(pid, &status, 0) != pid)
+ return NULL;
+ if (!WIFEXITED(status) || (WEXITSTATUS(status) != 0)) {
+ LLOG_INFO("lsb_release information not available");
+ return NULL;
+ }
+ if (!count) {
+ LLOG_INFO("lsb_release returned an empty string");
+ return NULL;
+ }
+ release[count] = '\0';
+ return release;
+ }
+ /* Should not be here */
+ return NULL;
+}
+
+/* Same like lldpd_get_lsb_release but reads /etc/os-release for PRETTY_NAME=. */
+static char *
+lldpd_get_os_release() {
+ static char release[1024];
+
+ FILE *fp = fopen("/etc/os-release", "r");
+ if (!fp) {
+ LLOG_WARN("Could not open /etc/os-release to read system information");
+ return NULL;
+ }
+
+ char line[1024];
+ char *key, *val;
+
+ while ((fgets(line, 1024, fp) != NULL)) {
+ key = strtok(line, "=");
+ val = strtok(NULL, "=");
+
+ if (strncmp(key, "PRETTY_NAME", 1024) == 0) {
+ strncpy(release, val, 1024);
+ break;
+ }
+ }
+ fclose(fp);
+
+ /* Remove trailing newline and all " in the string. */
+ char *ptr1 = release;
+ char *ptr2 = release;
+ while (*ptr1 != 0) {
+ if ((*ptr1 == '"') || (*ptr1 == '\n')) {
+ ++ptr1;
+ } else {
+ *ptr2++ = *ptr1++;
+ }
+ }
+ *ptr2 = 0;
+
+ return release;
+}
+
int
lldpd_callback_add(struct lldpd *cfg, int fd, void(*fn)(CALLBACK_SIG), void *data)
{
}
}
+/* Hide unwanted ports depending on smart mode set by the user */
+static void
+lldpd_hide_all(struct lldpd *cfg)
+{
+ struct lldpd_hardware *hardware;
+
+ if (!cfg->g_smart)
+ return;
+ TAILQ_FOREACH(hardware, &cfg->g_hardware, h_entries) {
+ if (cfg->g_smart & SMART_INCOMING_FILTER)
+ lldpd_hide_ports(cfg, hardware, SMART_INCOMING);
+ if (cfg->g_smart & SMART_OUTGOING_FILTER)
+ lldpd_hide_ports(cfg, hardware, SMART_OUTGOING);
+ }
+}
+
+static void
+lldpd_hide_ports(struct lldpd *cfg, struct lldpd_hardware *hardware, int mask) {
+ struct lldpd_port *port;
+ int protocols[LLDPD_MODE_MAX+1];
+ char buffer[256];
+ int i, j, k, found;
+ unsigned int min;
+
+ /* Compute the number of occurrences of each protocol */
+ for (i = 0; i <= LLDPD_MODE_MAX; i++) protocols[i] = 0;
+ TAILQ_FOREACH(port, &hardware->h_rports, p_entries)
+ protocols[port->p_protocol]++;
+
+ /* Turn the protocols[] array into an array of
+ enabled/disabled protocols. 1 means enabled, 0
+ means disabled. */
+ min = (unsigned int)-1;
+ for (i = 0; i <= LLDPD_MODE_MAX; i++)
+ if (protocols[i] && (protocols[i] < min))
+ min = protocols[i];
+ found = 0;
+ for (i = 0; i <= LLDPD_MODE_MAX; i++)
+ if ((protocols[i] == min) && !found) {
+ /* If we need a tie breaker, we take
+ the first protocol only */
+ if (cfg->g_smart & mask &
+ (SMART_OUTGOING_ONE_PROTO | SMART_INCOMING_ONE_PROTO))
+ found = 1;
+ protocols[i] = 1;
+ } else protocols[i] = 0;
+
+ /* We set the p_hidden flag to 1 if the protocol is disabled */
+ TAILQ_FOREACH(port, &hardware->h_rports, p_entries) {
+ if (mask == SMART_OUTGOING)
+ port->p_hidden_out = protocols[port->p_protocol]?0:1;
+ else
+ port->p_hidden_in = protocols[port->p_protocol]?0:1;
+ }
+
+ /* If we want only one neighbor, we take the first one */
+ if (cfg->g_smart & mask &
+ (SMART_OUTGOING_ONE_NEIGH | SMART_INCOMING_ONE_NEIGH)) {
+ found = 0;
+ TAILQ_FOREACH(port, &hardware->h_rports, p_entries) {
+ if (mask == SMART_OUTGOING) {
+ if (found) port->p_hidden_out = 1;
+ if (!port->p_hidden_out)
+ found = 1;
+ }
+ if (mask == SMART_INCOMING) {
+ if (found) port->p_hidden_in = 1;
+ if (!port->p_hidden_in)
+ found = 1;
+ }
+ }
+ }
+
+ /* Print a debug message summarizing the operation */
+ for (i = 0; i <= LLDPD_MODE_MAX; i++) protocols[i] = 0;
+ k = j = 0;
+ TAILQ_FOREACH(port, &hardware->h_rports, p_entries) {
+ if (!(((mask == SMART_OUTGOING) && port->p_hidden_out) ||
+ ((mask == SMART_INCOMING) && port->p_hidden_in))) {
+ k++;
+ protocols[port->p_protocol] = 1;
+ }
+ j++;
+ }
+ buffer[0] = '\0';
+ for (i=0; cfg->g_protocols[i].mode != 0; i++) {
+ if (cfg->g_protocols[i].enabled && protocols[cfg->g_protocols[i].mode]) {
+ if (strlen(buffer) +
+ strlen(cfg->g_protocols[i].name) + 3 > sizeof(buffer)) {
+ /* Unlikely, our buffer is too small */
+ memcpy(buffer + sizeof(buffer) - 4, "...", 4);
+ break;
+ }
+ if (buffer[0])
+ strcat(buffer, ", ");
+ strcat(buffer, cfg->g_protocols[i].name);
+ }
+ }
+ LLOG_DEBUG("[%s] %s: %d visible neigh / %d. Protocols: %s.",
+ (mask == SMART_OUTGOING)?"out filter":"in filter",
+ hardware->h_ifname, k, j, buffer[0]?buffer:"(none)");
+}
+
static void
lldpd_recv_all(struct lldpd *cfg)
{
}
hardware->h_rx_cnt++;
lldpd_decode(cfg, buffer, n, hardware);
+ lldpd_hide_all(cfg); /* Immediatly hide */
free(buffer);
break;
}
continue;
}
TAILQ_FOREACH(port, &hardware->h_rports, p_entries) {
+ /* If this remote port is disabled, we don't
+ * consider it */
+ if (port->p_hidden_out &&
+ (cfg->g_smart & SMART_OUTGOING_FILTER))
+ continue;
if (port->p_protocol ==
cfg->g_protocols[i].mode) {
cfg->g_protocols[i].send(cfg,
} else {
if (cfg->g_advertise_version) {
if (asprintf(&LOCAL_CHASSIS(cfg)->c_descr, "%s %s %s %s",
- un.sysname, un.release, un.version, un.machine)
+ cfg->g_lsb_release?cfg->g_lsb_release:"",
+ un.sysname, un.release, un.machine)
== -1)
fatal("failed to set full system description");
} else {
if (asprintf(&LOCAL_CHASSIS(cfg)->c_descr, "%s",
- un.sysname) == -1)
+ cfg->g_lsb_release?cfg->g_lsb_release:un.sysname) == -1)
fatal("failed to set minimal system description");
}
}
/* Check forwarding */
if ((f = priv_open("/proc/sys/net/ipv4/ip_forward")) >= 0) {
- if ((read(f, &status, 1) == 1) && (status == '1')) {
- LOCAL_CHASSIS(cfg)->c_cap_enabled = LLDP_CAP_ROUTER;
- }
+ if ((read(f, &status, 1) == 1) && (status == '1'))
+ LOCAL_CHASSIS(cfg)->c_cap_enabled |= LLDP_CAP_ROUTER;
+ else
+ LOCAL_CHASSIS(cfg)->c_cap_enabled &= ~LLDP_CAP_ROUTER;
close(f);
}
#ifdef ENABLE_LLDPMED
3. Update local chassis information
4. Send packets
5. Receive packets
+ 6. Update smart mode
*/
LOCAL_CHASSIS(cfg)->c_cap_enabled = 0;
lldpd_update_localports(cfg);
lldpd_cleanup(cfg);
lldpd_update_localchassis(cfg);
- lldpd_send_all(cfg);
+ if (!cfg->g_receiveonly)
+ lldpd_send_all(cfg);
lldpd_recv_all(cfg);
}
#endif /* USE_SNMP */
}
+struct intint { int a; int b; };
+static const struct intint filters[] = {
+ { 0, 0 },
+ { 1, SMART_INCOMING_FILTER | SMART_INCOMING_ONE_PROTO |
+ SMART_OUTGOING_FILTER | SMART_OUTGOING_ONE_PROTO },
+ { 2, SMART_INCOMING_FILTER | SMART_INCOMING_ONE_PROTO },
+ { 3, SMART_OUTGOING_FILTER | SMART_OUTGOING_ONE_PROTO },
+ { 4, SMART_INCOMING_FILTER | SMART_OUTGOING_FILTER },
+ { 5, SMART_INCOMING_FILTER },
+ { 6, SMART_OUTGOING_FILTER },
+ { 7, SMART_INCOMING_FILTER | SMART_INCOMING_ONE_PROTO | SMART_INCOMING_ONE_NEIGH |
+ SMART_OUTGOING_FILTER | SMART_OUTGOING_ONE_PROTO },
+ { 8, SMART_INCOMING_FILTER | SMART_INCOMING_ONE_PROTO | SMART_INCOMING_ONE_NEIGH },
+ { 9, SMART_INCOMING_FILTER | SMART_INCOMING_ONE_NEIGH |
+ SMART_OUTGOING_FILTER | SMART_OUTGOING_ONE_PROTO },
+ { 10, SMART_OUTGOING_FILTER | SMART_OUTGOING_ONE_NEIGH },
+ { 11, SMART_INCOMING_FILTER | SMART_INCOMING_ONE_NEIGH },
+ { 12, SMART_INCOMING_FILTER | SMART_INCOMING_ONE_NEIGH |
+ SMART_OUTGOING_FILTER | SMART_OUTGOING_ONE_NEIGH },
+ { 13, SMART_INCOMING_FILTER | SMART_INCOMING_ONE_NEIGH |
+ SMART_OUTGOING_FILTER },
+ { 14, SMART_INCOMING_FILTER | SMART_INCOMING_ONE_PROTO |
+ SMART_OUTGOING_FILTER | SMART_OUTGOING_ONE_NEIGH },
+ { 15, SMART_INCOMING_FILTER | SMART_INCOMING_ONE_PROTO |
+ SMART_OUTGOING_FILTER },
+ { 16, SMART_INCOMING_FILTER | SMART_INCOMING_ONE_PROTO | SMART_INCOMING_ONE_NEIGH |
+ SMART_OUTGOING_FILTER | SMART_OUTGOING_ONE_NEIGH },
+ { 17, SMART_INCOMING_FILTER | SMART_INCOMING_ONE_PROTO | SMART_INCOMING_ONE_NEIGH |
+ SMART_OUTGOING_FILTER },
+ { 18, SMART_INCOMING_FILTER |
+ SMART_OUTGOING_FILTER | SMART_OUTGOING_ONE_NEIGH },
+ { 19, SMART_INCOMING_FILTER |
+ SMART_OUTGOING_FILTER | SMART_OUTGOING_ONE_PROTO },
+ { -1, 0 }
+};
+
int
lldpd_main(int argc, char *argv[])
{
#endif
char *mgmtp = NULL;
char *popt, opts[] =
-#ifdef ENABLE_LISTENVLAN
- "v"
-#endif
- "hkdxX:m:p:M:S:i@ ";
+ "H:hkrdxX:m:p:M:S:i@ ";
int i, found, advertise_version = 1;
-#ifdef ENABLE_LISTENVLAN
- int vlan = 0;
-#endif
#ifdef ENABLE_LLDPMED
int lldpmed = 0, noinventory = 0;
#endif
char *descr_override = NULL;
+ char *lsb_release = NULL;
+ int smart = 15;
+ int receiveonly = 0;
saved_argv = argv;
case 'h':
usage();
break;
-#ifdef ENABLE_LISTENVLAN
- case 'v':
- vlan = 1;
- break;
-#endif
case 'd':
debug++;
break;
+ case 'r':
+ receiveonly = 1;
+ break;
case 'm':
mgmtp = optarg;
break;
case 'S':
descr_override = strdup(optarg);
break;
+ case 'H':
+ smart = atoi(optarg);
+ break;
default:
found = 0;
for (i=0; protos[i].mode != 0; i++) {
usage();
}
}
+
+ /* Set correct smart mode */
+ for (i=0; (filters[i].a != -1) && (filters[i].a != smart); i++);
+ if (filters[i].a == -1) {
+ fprintf(stderr, "Incorrect mode for -H\n");
+ usage();
+ }
+ smart = filters[i].b;
log_init(debug, __progname);
tzset(); /* Get timezone info before chroot */
close(pid);
}
+ /* Try to read system information from /etc/os-release if possible.
+ Fall back to lsb_release for compatibility. */
+ lsb_release = lldpd_get_os_release();
+ if (!lsb_release) {
+ lsb_release = lldpd_get_lsb_release();
+ }
+
priv_init(PRIVSEP_CHROOT);
if ((cfg = (struct lldpd *)
fatal(NULL);
cfg->g_mgmt_pattern = mgmtp;
- cfg->g_advertise_version = advertise_version;
-#ifdef ENABLE_LISTENVLAN
- cfg->g_listen_vlans = vlan;
-#endif
+ cfg->g_smart = smart;
+ cfg->g_receiveonly = receiveonly;
/* Get ioctl socket */
if ((cfg->g_sock = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
fatal("failed to get ioctl socket");
cfg->g_delay = LLDPD_TX_DELAY;
+ /* Description */
+ if (!(cfg->g_advertise_version = advertise_version))
+ /* Remove the \n */
+ lsb_release[strlen(lsb_release) - 1] = '\0';
+ cfg->g_lsb_release = lsb_release;
if (descr_override)
cfg->g_descr_override = descr_override;