From f84199ddf6c94318b059ca26b6edaf4e85c2b60d Mon Sep 17 00:00:00 2001 From: Vincent Bernat Date: Fri, 30 May 2014 15:21:34 +0200 Subject: [PATCH] lldpcli: add an option to enable promisc mode on managed interfaces This allows LLDP frames to be received even when they are not in a configured VLAN, like this may be the case with Cisco 2960. --- NEWS | 5 +++++ README.md | 4 ++-- src/client/conf-system.c | 38 +++++++++++++++++++++++++++++++++ src/client/display.c | 3 +++ src/client/lldpcli.8.in | 27 +++++++++++++++++++++++ src/daemon/client.c | 6 ++++++ src/daemon/interfaces-bsd.c | 1 + src/daemon/interfaces-linux.c | 1 + src/daemon/interfaces-solaris.c | 8 +++++++ src/daemon/interfaces.c | 12 +++++++++++ src/daemon/lldpd.h | 5 +++++ src/daemon/priv-bsd.c | 9 ++++++++ src/daemon/priv-linux.c | 37 ++++++++++++++++++++++++++++++++ src/daemon/priv.c | 25 ++++++++++++++++++++++ src/lib/atom-private.c | 5 +++++ src/lib/lldpctl.h | 1 + src/lldpd-structs.h | 1 + 17 files changed, 186 insertions(+), 2 deletions(-) diff --git a/NEWS b/NEWS index 3d9fd7bb..f5b2f661 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,8 @@ +lldpd (0.7.10) + * Features: + + Ability to set promiscuous mode to work around bugs of some + switches encapsulating LLDP frames inside 802.1Q frames. + lldpd (0.7.9) * Changes: + Default location for chroot, socket and PID are now configurable diff --git a/README.md b/README.md index c558e4e4..b35845bf 100644 --- a/README.md +++ b/README.md @@ -185,8 +185,8 @@ other solutions: 1. Disable VLAN acceleration on the receive side (`ethtool -K eth0 rxvlan off`) but this may or may not work. Check if there are similar properties that could apply with `ethtool -k eth0`. - 2. Put the interface in promiscuous mode with `ip link eth0 set - promisc on`. + 2. Put the interface in promiscuous mode with `ip link set + promisc on dev eth0`. On modern networks, the performance impact should be nonexistent. diff --git a/src/client/conf-system.c b/src/client/conf-system.c index dba48c01..31e60662 100644 --- a/src/client/conf-system.c +++ b/src/client/conf-system.c @@ -45,6 +45,31 @@ cmd_iface_pattern(struct lldpctl_conn_t *conn, struct writer *w, return 1; } +static int +cmd_iface_promisc(struct lldpctl_conn_t *conn, struct writer *w, + struct cmd_env *env, void *arg) +{ + lldpctl_atom_t *config = lldpctl_get_configuration(conn); + if (config == NULL) { + log_warnx("lldpctl", "unable to get configuration from lldpd. %s", + lldpctl_last_strerror(conn)); + return 0; + } + if (lldpctl_atom_set_int(config, + lldpctl_k_config_iface_promisc, + arg?1:0) == NULL) { + log_warnx("lldpctl", "unable to %s promiscuous mode: %s", + arg?"enable":"disable", + lldpctl_last_strerror(conn)); + lldpctl_atom_dec_ref(config); + return 0; + } + log_info("lldpctl", "interface promiscuous mode %s", + arg?"enabled":"disabled"); + lldpctl_atom_dec_ref(config); + return 1; +} + static int cmd_system_description(struct lldpctl_conn_t *conn, struct writer *w, struct cmd_env *env, void *arg) @@ -347,6 +372,19 @@ register_commands_configure_system(struct cmd_node *configure, NEWLINE, "Don't update interface descriptions with neighbor name", NULL, cmd_update_descriptions, NULL); + commands_new( + commands_new(configure_interface, + "promiscuous", "Enable promiscuous mode on managed interfaces", + NULL, NULL, NULL), + NEWLINE, "Enable promiscuous mode on managed interfaces", + NULL, cmd_iface_promisc, "enable"); + commands_new( + commands_new(unconfigure_interface, + "promiscuous", "Don't enable promiscuous mode on managed interfaces", + NULL, NULL, NULL), + NEWLINE, "Don't enable promiscuous mode on managed interfaces", + NULL, cmd_iface_promisc, NULL); + register_commands_srcmac_type(configure_system); } diff --git a/src/client/display.c b/src/client/display.c index fb221bff..85defbb0 100644 --- a/src/client/display.c +++ b/src/client/display.c @@ -731,6 +731,9 @@ display_configuration(lldpctl_conn_t *conn, struct writer *w) tag_datatag(w, "ifdescr-update", "Update interface descriptions", lldpctl_atom_get_int(configuration, lldpctl_k_config_ifdescr_update)? "yes":"no"); + tag_datatag(w, "iface-promisc", "Promiscuous mode on managed interfaces", + lldpctl_atom_get_int(configuration, lldpctl_k_config_iface_promisc)? + "yes":"no"); tag_datatag(w, "lldpmed-no-inventory", "Disable LLDP-MED inventory", (lldpctl_atom_get_int(configuration, lldpctl_k_config_lldpmed_noinventory) == 0)? "no":"yes"); diff --git a/src/client/lldpcli.8.in b/src/client/lldpcli.8.in index 013b4dc5..d137613d 100644 --- a/src/client/lldpcli.8.in +++ b/src/client/lldpcli.8.in @@ -218,6 +218,33 @@ to override this description with the name of the peer neighbor if one is found or with the number of neighbors found. .Ed +.Cd configure +.Cd system interface promiscuous +.Bd -ragged -offset XXXXXX +Enable promiscuous mode on managed interfaces. This option is only +needed if you don't receive LLDP frames from the remote switch. The +most plausible explanation for this is the frame is tagged with some +VLAN (usually VLAN 1) and your network card is filtering VLAN. This is +not the only available solution to work-around this problem. If you +are concerned about performance issues, you can also tag the VLAN 1 on +each interface. +.Pp +When the interface is not managed any more (or when quitting +.Nm ) , +the interface is left in promiscuous mode as it is difficult to know +if someone else also put the interface in promiscuous mode. +.Pp +This option is known to be useful when the remote switch is a Cisco +2960. +.Ed + +.Cd unconfigure +.Cd system interface promiscuous +.Bd -ragged -offset XXXXXX +Do not set promiscuous mode on managed interfaces. This option does +not disable promiscuous mode on interfaces already using this mode. +.Ed + .Cd configure .Cd system ip management pattern Ar pattern .Bd -ragged -offset XXXXXX diff --git a/src/daemon/client.c b/src/daemon/client.c index 6407ad69..ef16874b 100644 --- a/src/daemon/client.c +++ b/src/daemon/client.c @@ -137,6 +137,12 @@ client_handle_set_configuration(struct lldpd *cfg, enum hmsg_type *type, cfg->g_config.c_set_ifdescr = config->c_set_ifdescr; levent_update_now(cfg); } + if (config->c_promisc != cfg->g_config.c_promisc) { + log_debug("rpc", "%s promiscuous mode on managed interfaces", + config->c_promisc?"enable":"disable"); + cfg->g_config.c_promisc = config->c_promisc; + levent_update_now(cfg); + } if (config->c_bond_slave_src_mac_type != 0) { if (config->c_bond_slave_src_mac_type > LLDP_BOND_SLAVE_SRC_MAC_TYPE_UNKNOWN && diff --git a/src/daemon/interfaces-bsd.c b/src/daemon/interfaces-bsd.c index 0e9d5524..7db99cf7 100644 --- a/src/daemon/interfaces-bsd.c +++ b/src/daemon/interfaces-bsd.c @@ -657,6 +657,7 @@ interfaces_update(struct lldpd *cfg) TAILQ_FOREACH(hardware, &cfg->g_hardware, h_entries) { if (!hardware->h_flags) continue; ifbsd_macphy(cfg, hardware); + interfaces_helper_promisc(cfg, hardware); } if (cfg->g_iface_event == NULL) { diff --git a/src/daemon/interfaces-linux.c b/src/daemon/interfaces-linux.c index c3b4f28a..fcf359fb 100644 --- a/src/daemon/interfaces-linux.c +++ b/src/daemon/interfaces-linux.c @@ -795,6 +795,7 @@ interfaces_update(struct lldpd *cfg) TAILQ_FOREACH(hardware, &cfg->g_hardware, h_entries) { if (!hardware->h_flags) continue; iflinux_macphy(hardware); + interfaces_helper_promisc(cfg, hardware); } if (cfg->g_iface_event == NULL) { diff --git a/src/daemon/interfaces-solaris.c b/src/daemon/interfaces-solaris.c index 304f71c9..9a4f0049 100644 --- a/src/daemon/interfaces-solaris.c +++ b/src/daemon/interfaces-solaris.c @@ -119,6 +119,7 @@ ifsolaris_extract(struct lldpd *cfg, extern struct lldpd_ops bpf_ops; void interfaces_update(struct lldpd *cfg) { + struct lldpd_hardware *hardware; caddr_t buffer = NULL; struct interfaces_device_list *interfaces; struct interfaces_address_list *addresses; @@ -171,6 +172,13 @@ interfaces_update(struct lldpd *cfg) { interfaces_helper_mgmt(cfg, addresses); interfaces_helper_chassis(cfg, interfaces); + /* Mac/PHY */ + TAILQ_FOREACH(hardware, &cfg->g_hardware, h_entries) { + if (!hardware->h_flags) continue; + /* TODO: mac/phy for Solaris */ + interfaces_helper_promisc(cfg, hardware); + } + end: free(buffer); interfaces_free_devices(interfaces); diff --git a/src/daemon/interfaces.c b/src/daemon/interfaces.c index 2bd7e1b9..9c9d024f 100644 --- a/src/daemon/interfaces.c +++ b/src/daemon/interfaces.c @@ -563,6 +563,18 @@ interfaces_helper_physical(struct lldpd *cfg, } } +void +interfaces_helper_promisc(struct lldpd *cfg, + struct lldpd_hardware *hardware) +{ + if (!cfg->g_config.c_promisc) return; + if (priv_iface_promisc(hardware->h_ifname) != 0) { + log_warnx("interfaces", + "unable to enable promiscuous mode for %s", + hardware->h_ifname); + } +} + /** * Send the packet using the hardware function. Optionnaly mangle the MAC address. * diff --git a/src/daemon/lldpd.h b/src/daemon/lldpd.h index fcf2a7bf..797623c8 100644 --- a/src/daemon/lldpd.h +++ b/src/daemon/lldpd.h @@ -235,6 +235,8 @@ int asroot_iface_init_os(int, char *, int *); int priv_iface_multicast(const char *, u_int8_t *, int); int priv_iface_description(const char *, const char *); int asroot_iface_description_os(const char *, const char *); +int priv_iface_promisc(const char*); +int asroot_iface_promisc_os(const char *); int priv_snmp_socket(struct sockaddr_un *); enum priv_cmd { @@ -246,6 +248,7 @@ enum priv_cmd { PRIV_IFACE_INIT, PRIV_IFACE_MULTICAST, PRIV_IFACE_DESCRIPTION, + PRIV_IFACE_PROMISC, PRIV_SNMP_SOCKET, }; @@ -367,6 +370,8 @@ struct interfaces_device* interfaces_nametointerface( struct interfaces_device_list *, const char *); +void interfaces_helper_promisc(struct lldpd *, + struct lldpd_hardware *); void interfaces_helper_whitelist(struct lldpd *, struct interfaces_device_list *); void interfaces_helper_chassis(struct lldpd *, diff --git a/src/daemon/priv-bsd.c b/src/daemon/priv-bsd.c index 0537f25c..f32f990c 100644 --- a/src/daemon/priv-bsd.c +++ b/src/daemon/priv-bsd.c @@ -209,3 +209,12 @@ asroot_iface_description_os(const char *name, const char *description) return 0; } +int +asroot_iface_promisc_os(const char *name) +{ + /* The promiscuous mode can be set when setting BPF + (BIOCPROMISC). Unfortunately, the interface is locked down and we + cannot change that without reopening a new socket. Let's do nothing + for now. */ + return 0; +} diff --git a/src/daemon/priv-linux.c b/src/daemon/priv-linux.c index add31136..62eaf4ab 100644 --- a/src/daemon/priv-linux.c +++ b/src/daemon/priv-linux.c @@ -258,3 +258,40 @@ asroot_iface_description_os(const char *name, const char *description) fclose(fp); return 0; } + +int +asroot_iface_promisc_os(const char *name) +{ + int s, rc; + if ((s = socket(PF_PACKET, SOCK_RAW, + htons(ETH_P_ALL))) < 0) { + rc = errno; + log_warn("privsep", "unable to open raw socket"); + return rc; + } + + struct ifreq ifr = {}; + strlcpy(ifr.ifr_name, name, sizeof(ifr.ifr_name)); + + if (ioctl(s, SIOCGIFFLAGS, &ifr) == -1) { + rc = errno; + log_warn("privsep", "unable to get interface flags for %s", + name); + close(s); + return rc; + } + + if (ifr.ifr_flags & IFF_PROMISC) + return 0; + ifr.ifr_flags |= IFF_PROMISC; + if (ioctl(s, SIOCSIFFLAGS, &ifr) == -1) { + rc = errno; + log_warn("privsep", "unable to set promisc mode for %s", + name); + close(s); + return rc; + } + log_info("privsep", "promiscuous mode enabled for %s", name); + close(s); + return 0; +} diff --git a/src/daemon/priv.c b/src/daemon/priv.c index 8dfd9d76..b6341e44 100644 --- a/src/daemon/priv.c +++ b/src/daemon/priv.c @@ -157,6 +157,19 @@ priv_iface_description(const char *name, const char *description) return rc; } +/* Proxy to set interface in promiscuous mode */ +int +priv_iface_promisc(const char *ifname) +{ + int rc; + enum priv_cmd cmd = PRIV_IFACE_PROMISC; + must_write(PRIV_UNPRIVILEGED, &cmd, sizeof(enum priv_cmd)); + must_write(PRIV_UNPRIVILEGED, ifname, IFNAMSIZ); + priv_wait(); + must_read(PRIV_UNPRIVILEGED, &rc, sizeof(int)); + return rc; +} + int priv_snmp_socket(struct sockaddr_un *addr) { @@ -298,6 +311,17 @@ asroot_iface_description() free(description); } +static void +asroot_iface_promisc() +{ + char name[IFNAMSIZ]; + int rc; + must_read(PRIV_PRIVILEGED, &name, sizeof(name)); + name[sizeof(name) - 1] = '\0'; + rc = asroot_iface_promisc_os(name); + must_write(PRIV_PRIVILEGED, &rc, sizeof(rc)); +} + static void asroot_snmp_socket() { @@ -351,6 +375,7 @@ static struct dispatch_actions actions[] = { {PRIV_IFACE_INIT, asroot_iface_init}, {PRIV_IFACE_MULTICAST, asroot_iface_multicast}, {PRIV_IFACE_DESCRIPTION, asroot_iface_description}, + {PRIV_IFACE_PROMISC, asroot_iface_promisc}, {PRIV_SNMP_SOCKET, asroot_snmp_socket}, {-1, NULL} }; diff --git a/src/lib/atom-private.c b/src/lib/atom-private.c index 936b00d9..c1688726 100644 --- a/src/lib/atom-private.c +++ b/src/lib/atom-private.c @@ -480,6 +480,8 @@ _lldpctl_atom_get_int_config(lldpctl_atom_t *atom, lldpctl_key_t key) return c->config->c_advertise_version; case lldpctl_k_config_ifdescr_update: return c->config->c_set_ifdescr; + case lldpctl_k_config_iface_promisc: + return c->config->c_promisc; #ifdef ENABLE_LLDPMED case lldpctl_k_config_lldpmed_noinventory: return c->config->c_noinventory; @@ -515,6 +517,9 @@ _lldpctl_atom_set_int_config(lldpctl_atom_t *atom, lldpctl_key_t key, case lldpctl_k_config_ifdescr_update: config.c_set_ifdescr = c->config->c_set_ifdescr = value; break; + case lldpctl_k_config_iface_promisc: + config.c_promisc = c->config->c_promisc = value; + break; #ifdef ENABLE_LLDPMED case lldpctl_k_config_fast_start_enabled: config.c_enable_fast_start = value?1:2; diff --git a/src/lib/lldpctl.h b/src/lib/lldpctl.h index 6e6eb9bd..fa787c5f 100644 --- a/src/lib/lldpctl.h +++ b/src/lib/lldpctl.h @@ -596,6 +596,7 @@ typedef enum { lldpctl_k_config_fast_start_enabled, /**< `(I,WO)` Is fast start enabled */ lldpctl_k_config_fast_start_interval, /**< `(I,WO)` Start fast transmit interval */ lldpctl_k_config_ifdescr_update, /**< `(I,WO)` Enable or disable setting interface description */ + lldpctl_k_config_iface_promisc, /**< `(I,WO)` Enable or disable promiscuous mode on interfaces */ lldpctl_k_interface_name = 1000, /**< `(S)` The interface name. */ diff --git a/src/lldpd-structs.h b/src/lldpd-structs.h index ac4e6339..de134dbf 100644 --- a/src/lldpd-structs.h +++ b/src/lldpd-structs.h @@ -326,6 +326,7 @@ struct lldpd_config { char *c_hostname; /* Override system name */ int c_advertise_version; /* Should the precise version be advertised? */ int c_set_ifdescr; /* Set interface description */ + int c_promisc; /* Interfaces should be in promiscuous mode */ #ifdef ENABLE_LLDPMED int c_noinventory; /* Don't send inventory with LLDP-MED */ -- 2.39.5