From: Vincent Bernat Date: Wed, 15 Oct 2008 07:27:31 +0000 (+0200) Subject: Initial import X-Git-Tag: 0.2~38 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=43c02e7ba89fe166c818dc3c2e3ab17131cb5a8f;p=thirdparty%2Flldpd.git Initial import --- 43c02e7ba89fe166c818dc3c2e3ab17131cb5a8f diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 00000000..c28b8c16 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,2 @@ +SUBDIRS = src man +dist_doc_DATA = README diff --git a/README b/README new file mode 100644 index 00000000..d2c14e29 --- /dev/null +++ b/README @@ -0,0 +1,68 @@ +lldpd: implementation of IEEE 802.1ab (LLDP) +-------------------------------------------- + +LLDP (Link Layer Discovery Protocol) is an industry standard protocol +designed to supplant proprietary Link-Layer protocols such as +Extreme's EDP (Extreme Discovery Protocol) and CDP (Cisco Discovery +Protocol). The goal of LLDP is to provide an inter-vendor compatible +mechanism to deliver Link-Layer notifications to adjacent network +devices. + +lldpd implements both reception and sending. It also implements an +SNMP subagent for net-snmp to get local and remote LLDP +information. The LLDP MIB is partially implemented but the most useful +tables are here. + +lldpd supports bridge, vlan and bonding. bonding need to be done on +real physical devices, not on bridges, vlans, etc. However, vlans can +be mapped on the bonding device. You can bridge vlan but not add vlans +on bridges. More complex setups may give false results. + +lldpctl allows to query information collected through the command line. + +lldpd also implements CDP (Cisco Discovery Protocol), SONMP (Nortel +Discovery Protocol) and EDP (Extreme Discovery Protocol). However, +recent versions of IOS should support LLDP and most Extreme stuff +support LLDP. When a EDP, CDP or SONMP frame is received on a given +interface, lldpd starts sending EDP, CDP or SONMP frame on this +interface. Informations collected through EDP/CDP/SONMP are integrated +with other informations and can be queried with lldpctl or through +SNMP. + +For bonding, you need 2.6.24 (in previous version, PACKET_ORIGDEV +affected only non multicast packets). See: + http://git.kernel.org/?p=linux/kernel/git/torvalds/linux-2.6.git;a=commitdiff;h=80feaacb8a6400a9540a961b6743c69a5896b937 + http://git.kernel.org/?p=linux/kernel/git/torvalds/linux-2.6.git;a=commitdiff;h=8032b46489e50ef8f3992159abd0349b5b8e476c + +Otherwise, real device will be choosen randomly. + +On 2.6.27, we are able to receive packets on real interface for bonded +devices. This allows to get neighbor information on active/backup +bonds. Without the 2.6.27, lldpd won't receive any information on +inactive slaves. Here are the patchs (thanks to Joe Eykholt): + http://git.kernel.org/?p=linux/kernel/git/torvalds/linux-2.6.git;a=commit;h=0d7a3681232f545c6a59f77e60f7667673ef0e93 + http://git.kernel.org/?p=linux/kernel/git/torvalds/linux-2.6.git;a=commit;h=cc9bd5cebc0825e0fabc0186ab85806a0891104f + http://git.kernel.org/?p=linux/kernel/git/torvalds/linux-2.6.git;a=commit;h=f982307f22db96201e41540295f24e8dcc10c78f + +More information: + http://en.wikipedia.org/wiki/LLDP + http://standards.ieee.org/getieee802/download/802.1AB-2005.pdf + http://wiki.wireshark.org/LinkLayerDiscoveryProtocol + +lldpd is distributed under the following license: + * Permission to use, copy, modify, and 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. + +OpenLLDP is another implementation of LLDP. It supports several OS +(Windows, Mac OS X, *BSD) and LLDP-MED extensions but does not include +an SNMP subagent or the support for CDP/SONMP. + http://openlldp.sourceforge.net/ diff --git a/configure.ac b/configure.ac new file mode 100644 index 00000000..bd3f9563 --- /dev/null +++ b/configure.ac @@ -0,0 +1,66 @@ +# -*- Autoconf -*- +# Process this file with autoconf to produce a configure script. + +AC_PREREQ(2.61) +AC_INIT(lldpd, 0.1, bernat@luffy.cx) +AM_INIT_AUTOMAKE([foreign]) +AC_CONFIG_SRCDIR([src/lldpd.c]) +AC_CONFIG_HEADER([config.h]) +AC_CONFIG_FILES([Makefile src/Makefile man/Makefile]) + +# Checks for programs. +AC_PROG_CC + +# Checks for libraries. +AC_ARG_WITH(snmp, + AC_HELP_STRING( + [--with-snmp], + [Enable the use of SNMP] + ), + [], + [with_snmp=no] +) +AM_CONDITIONAL([USE_SNMP], [test "${with_snmp}" != "no"]) + +# Checks for header files. +AC_CHECK_DECLS([TAILQ_FIRST, TAILQ_NEXT, TAILQ_FOREACH, TAILQ_EMPTY],[],[],[[#include ]]) +AC_CHECK_DECL([PACKET_ORIGDEV],[],[],[[#include ]]) +AC_CHECK_DECL([ADVERTISED_2500baseX_Full],[],[],[[#include ]]) + +# Checks for typedefs, structures, and compiler characteristics. +AC_C_CONST +AC_CHECK_TYPES([int16_t, u_int16_t, int8_t, u_int8_t, int32_t, u_int32_t],[],[AC_MSG_ERROR([mandatory type not found])]) + +# Checks for library functions. +AC_REPLACE_FUNCS([strlcpy]) + +AC_PROG_GCC_TRADITIONAL + +## NetSNMP +NETSNMP_CONFIG=No +if test "${with_snmp}" != "no"; then + AC_PATH_TOOL([NETSNMP_CONFIG], [net-snmp-config], No) +fi +if test x"${NETSNMP_CONFIG}" != xNo; then + NETSNMP_libs=`${NETSNMP_CONFIG} --agent-libs` + + AC_CHECK_LIB([netsnmp], [snmp_register_callback], + AC_DEFINE_UNQUOTED([HAVE_NETSNMP], 1, [Define to indicate the Net-SNMP library]) + AC_DEFINE_UNQUOTED([USE_SNMP], 1, [Define to indicate to enable SNMP support]), + [], ${NETSNMP_libs}) + + if test "${ac_cv_lib_netsnmp_snmp_register_callback}" = "yes"; then + AC_SUBST([NETSNMP_LIB],"${NETSNMP_libs}") + fi +fi +if test "${with_snmp}" != "no"; then + if test "${ac_cv_lib_netsnmp_snmp_register_callback}" != "yes"; then + AC_MSG_NOTICE([***]) + AC_MSG_NOTICE([*** net-snmp libraries not found]) + AC_MSG_NOTICE([*** Either correct the installation, or run configure]) + AC_MSG_NOTICE([*** including --without-snmp]) + exit 1 + fi +fi + +AC_OUTPUT diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 00000000..e2a2dfa5 --- /dev/null +++ b/debian/changelog @@ -0,0 +1,5 @@ +lldpd (0+20080718cvs) UNRELEASED; urgency=low + + * Initial release + + -- Vincent Bernat Fri, 18 Jul 2008 11:20:34 +0200 diff --git a/debian/compat b/debian/compat new file mode 100644 index 00000000..7ed6ff82 --- /dev/null +++ b/debian/compat @@ -0,0 +1 @@ +5 diff --git a/debian/control b/debian/control new file mode 100644 index 00000000..70292a31 --- /dev/null +++ b/debian/control @@ -0,0 +1,22 @@ +Source: lldpd +Section: net +Priority: optional +Maintainer: Vincent Bernat +Build-Depends: debhelper (>= 5), cdbs, autotools-dev, libsnmp15-dev | libsnmp9-dev | libsnmp-dev +Standards-Version: 3.8.0 + +Package: lldpd +Architecture: any +Depends: ${shlibs:Depends}, ${misc:Depends} +Description: implementation of IEEE 802.1ab (LLDP) + This implementation provides LLDP sending and reception, supports + VLAN and includes an SNMP subagent that can interface to an SNMP + agent through AgentX protocol. + . + LLDP is an industry standard protocol designed to supplant + proprietary Link-Layer protocols such as Extreme's EDP (Extreme + Discovery Protocol) and CDP (Cisco Discovery Protocol). The goal of + LLDP is to provide an inter-vendor compatible mechanism to deliver + Link-Layer notifications to adjacent network devices. + . + This daemon is also able to deal with CDP, SONMP and EDP protocol. diff --git a/debian/copyright b/debian/copyright new file mode 100644 index 00000000..ae18ad4e --- /dev/null +++ b/debian/copyright @@ -0,0 +1,19 @@ +Files: * +Copyright: © 2008 Vincent Bernat + © 2003, 2004 Henning Brauer + © 2006 Pierre-Yves Ritschard +License: MIT + Permission to use, copy, modify, and 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. + +On Debian systems, the complete text of the MIT can be found in +`/usr/share/common-licenses/MIT`. diff --git a/debian/lldpd.default b/debian/lldpd.default new file mode 100644 index 00000000..3acacfd9 --- /dev/null +++ b/debian/lldpd.default @@ -0,0 +1,2 @@ +# Uncomment to start SNMP subagent and enable CDP, SONMP and EDP protocol +#DAEMON_ARGS="-x -c -s -e" diff --git a/debian/lldpd.init.d b/debian/lldpd.init.d new file mode 100644 index 00000000..3ece7f1e --- /dev/null +++ b/debian/lldpd.init.d @@ -0,0 +1,102 @@ +#! /bin/sh +### BEGIN INIT INFO +# Provides: skeleton +# Required-Start: $remote_fs $network +# Required-Stop: $network $remote_fs +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: LLDP daemon +### END INIT INFO + +# Do NOT "set -e" + +# PATH should only include /usr/* if it runs after the mountnfs.sh script +PATH=/sbin:/usr/sbin:/bin:/usr/bin +DESC="LLDP daemon" +NAME=lldpd +DAEMON=/usr/sbin/$NAME +DAEMON_ARGS="" +PIDFILE=/var/run/$NAME.pid +SCRIPTNAME=/etc/init.d/$NAME + +# Exit if the package is not installed +[ -x "$DAEMON" ] || exit 0 + +# Read configuration variable file if it is present +[ -r /etc/default/$NAME ] && . /etc/default/$NAME + +[ -f /lib/init/vars.sh ] && . /lib/init/vars.sh +. /lib/lsb/init-functions + +do_start() +{ + start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \ + || return 1 + start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON -- \ + $DAEMON_ARGS \ + || return 2 +} + +do_stop() +{ + start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE --name $NAME + RETVAL="$?" + [ "$RETVAL" = 2 ] && return 2 + start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON + [ "$?" = 2 ] && return 2 + rm -f $PIDFILE + return "$RETVAL" +} + +do_reload() { + start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE --name $NAME + return 0 +} + +case "$1" in + start) + [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME" + do_start + case "$?" in + 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; + 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; + esac + ;; + stop) + [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME" + do_stop + case "$?" in + 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; + 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; + esac + ;; + reload) + log_daemon_msg "Reloading $DESC" "$NAME" + do_reload + log_end_msg $? + ;; + restart|force-reload) + log_daemon_msg "Restarting $DESC" "$NAME" + do_stop + case "$?" in + 0|1) + do_start + case "$?" in + 0) log_end_msg 0 ;; + 1) log_end_msg 1 ;; # Old process is still running + *) log_end_msg 1 ;; # Failed to start + esac + ;; + *) + # Failed to stop + log_end_msg 1 + ;; + esac + ;; + *) + echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2 + exit 3 + ;; +esac + +: diff --git a/debian/rules b/debian/rules new file mode 100755 index 00000000..dcc6c38c --- /dev/null +++ b/debian/rules @@ -0,0 +1,6 @@ +#!/usr/bin/make -f + +include /usr/share/cdbs/1/rules/debhelper.mk +include /usr/share/cdbs/1/class/autotools.mk + +DEB_CONFIGURE_EXTRA_FLAGS = --with-snmp diff --git a/man/Makefile.am b/man/Makefile.am new file mode 100644 index 00000000..cda81b37 --- /dev/null +++ b/man/Makefile.am @@ -0,0 +1 @@ +man_MANS = lldpd.8 lldpctl.8 diff --git a/man/lldpctl.8 b/man/lldpctl.8 new file mode 100644 index 00000000..cd0a6e7a --- /dev/null +++ b/man/lldpctl.8 @@ -0,0 +1,55 @@ +.\" Copyright (c) 2006 Pierre-Yves Ritschard +.\" Copyright (c) 2008 Vincent Bernat +.\" +.\" Permission to use, copy, modify, and 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. +.\" +.Dd $Mdocdate: July 16 2008 $ +.Dt LLDPCTL 8 +.Os +.Sh NAME +.Nm lldpctl +.Nd control the LLDP daemon +.Sh SYNOPSIS +.Nm +.Op Fl d +.Sh DESCRIPTION +The +.Nm +program controls +.Xr lldpd 8 +daemon. +.Pp +Currently, +.Nm +is only able to display the list of discovered neighbors along with +some of their advertised capabilities. +.Pp +The options are as follows: +.Bl -tag -width Ds +.It Fl d +Enable more debugging information. +.El +.Sh FILES +.Bl -tag width "/var/run/lldpd.socketXX" -compact +.It /var/run/lldpd.socket +Unix-domain socket used for communication with +.Xr lldpd 8 . +.El +.Sh SEE ALSO +.Xr lldpd 8 +.Sh AUTHORS +.An -nosplit +The +.Nm +program was written by +.An Vincent Bernat Aq bernat@luffy.cx . diff --git a/man/lldpd.8 b/man/lldpd.8 new file mode 100644 index 00000000..3f21fc93 --- /dev/null +++ b/man/lldpd.8 @@ -0,0 +1,106 @@ +.\" Copyright (c) 2006 Pierre-Yves Ritschard +.\" Copyright (c) 2008 Vincent Bernat +.\" +.\" Permission to use, copy, modify, and 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. +.\" +.Dd $Mdocdate: August 21 2008 $ +.Dt LLDPD 8 +.Os +.Sh NAME +.Nm lldpd +.Nd LLDP daemon +.Sh SYNOPSIS +.Nm +.Op Fl dxcse +.Op Fl m Ar management +.Op Fl p Ar probe time +.Sh DESCRIPTION +.Nm +is a daemon able to receive and send +.Em LLDP +frames. The Link Layer Discovery Protocol is a vendor-neutral Layer 2 +protocol that allows a network device to advertise its identity and +capabilities on the local network. +.Pp +.Nm +also implements an SNMP subagent using AgentX protocol to interface to +a regular SNMP agent like Net-SNMP. To enable this subagent, you need +something like that in your +.Xr snmpd.conf 5 : +.Bd -literal -offset indent +master agentx +.Ed +.Pp +This daemon implements both reception and sending. It will collect +various information to send LLDP frames to all Ethernet interfaces, +including management address, speed and VLAN names. +.Pp +The options are as follows: +.Bl -tag -width Ds +.It Fl d +Do not daemonize. +If this option is specified, +.Nm +will run in the foreground and log to +.Em stderr . +This option can be specified many times to increase verbosity. +.It Fl x +Enable SNMP subagent +With this option, +.Nm +will enable an SNMP subagent using AgentX protocol. This allows to get +information about local system and remote systems through SNMP. +.It Fl c +Enable the support of CDP protocol to deal with Cisco routers that do +not speak LLDP. +.It Fl s +Enable the support of SONMP protocol to deal with Nortel routers and +switches that do not speak LLDP. +.It Fl e +Enable the support of EDP protocol to deal with Extreme routers and +switches that do not speak LLDP. +.It Fl m Ar management +Specify the management address of this system. +.Nm +only sends one management address. It will use the first one that it +finds or the one that you specify with this option. This option can +use wildcards. +.It Fl p Ar probe time +Specify the time to wait (in seconds) before accepting a given +protocol. This time will be used by +.Nm +to detect false positives like SONMP frames running through a switch +only supporting CDP. This value is only used when multiple protocols +are enabled. +.El +.Sh FILES +.Bl -tag width "/var/run/lldpd.socketXX" -compact +.It /var/run/lldpd.socket +Unix-domain socket used for communication with +.Xr lldpctl 8 . +.El +.Sh SEE ALSO +.Xr lldpctl 8 , +.Xr snmpd 8 +.Sh HISTORY +The +.Nm +program is inspired from a preliminary work of Reyk Floeter. +.Sh AUTHORS +.An -nosplit +The +.Nm +program was written by +.An Pierre-Yves Ritschard Aq pyr@openbsd.org , +and +.An Vincent Bernat Aq bernat@luffy.cx . diff --git a/misc/test-addmulti.c b/misc/test-addmulti.c new file mode 100644 index 00000000..4b290eee --- /dev/null +++ b/misc/test-addmulti.c @@ -0,0 +1,25 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int main() { + int s; + struct ifreq ifr; + const unsigned char lldpaddr[] = {0x01,0x80,0xC2,0x00,0x00,0x0E}; + + if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) + err(1, "Unable to open socket"); + bzero(&ifr, sizeof(ifr)); + strncpy(ifr.ifr_name, "lan", IFNAMSIZ); + bcopy(lldpaddr, ifr.ifr_hwaddr.sa_data, ETH_ALEN); + + if (ioctl(s, SIOCADDMULTI, &ifr) < 0) + err(1, "Unable to ioctl"); + return 0; +} diff --git a/misc/test-align.c b/misc/test-align.c new file mode 100644 index 00000000..f3e9a260 --- /dev/null +++ b/misc/test-align.c @@ -0,0 +1,54 @@ +/* gcc -c test-align.c -S -o test-align.s */ + +#include + +struct test1 { + char a; + char b; +} ; + +struct test2 { + char a; + u_int16_t b; +} ; + +struct test3 { + char a; + u_int32_t b; +} ; + +struct test4 { + char a; + u_int64_t b; +} ; + +struct test5 { + char a; + void *b; +} ; + +struct test6 { + char a; + u_int16_t b; + char c; + char d; + char e; + char f; + char g; +} ; + + +struct test1 test_1 = { 10, 20}; +struct test2 test_2 = { 10, 20}; +struct test3 test_3 = { 10, 20}; +struct test4 test_4 = { 10, 20}; +struct test5 test_5 = { 10, (void*)&test_4}; +struct test6 test_6 = { 10, 15, 20, 30, 40, 50, 60}; + +int word_align = sizeof(struct test2) - sizeof(u_int16_t); +int long_align = sizeof(struct test3) - sizeof(u_int32_t); +int long_long_align = sizeof(struct test4) - sizeof(u_int64_t); +int pointer_align = sizeof(struct test5) - sizeof(void*); + +int size = sizeof(struct test6); + diff --git a/misc/test-parselldp.c b/misc/test-parselldp.c new file mode 100644 index 00000000..b2503091 --- /dev/null +++ b/misc/test-parselldp.c @@ -0,0 +1,356 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../lldp.h" + +#define IF "lan" +#define LLDPMAC {0x01,0x80,0xC2,0x00,0x00,0x0E} +#define MTU 1500 + +int set_multi(int add) { + int s; + struct ifreq ifr; + const char lldpaddr[] = LLDPMAC; + + if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) + return -1; + bzero(&ifr, sizeof(ifr)); + strncpy(ifr.ifr_name, IF, IFNAMSIZ); + bcopy(lldpaddr, ifr.ifr_hwaddr.sa_data, ETH_ALEN); + + if (ioctl(s, add ? SIOCADDMULTI : SIOCDELMULTI, &ifr) < 0) + return -1; + return 0; +} + +void dump(const char *frame, int s) { + int i = 0; + while (s != i) { + printf("%02hhx ", frame[i++]); + if (i % 20 == 0) + printf("\n"); + } + printf("\n"); +} + +int check_mac(const char *frame, int s) { + const char lldpaddr[] = LLDPMAC; + if (s < sizeof(lldpaddr)) + return -1; + if (memcmp(frame, lldpaddr, sizeof(lldpaddr)) == 0) { + return 1; + } + return -1; +} + +int check_protocol(const char *frame, int s) { + const char proto[] = { 0x88, 0xcc }; + if (s < 2 * ETH_ALEN + 2) + return -1; + if (memcmp(frame + 2 * ETH_ALEN, proto, sizeof(proto)) == 0) { + return 1; + } + return -1; +} + +int check_tlv_end(const char *frame, int s) { + if (s != 0) { + warnx("End of LLDPDU is too large (%d > 0)", s); + return -1; + } + return 0; +} + +int check_tlv_chassisid(const char *frame, int s) { + u_int8_t subtype; + if (s < 2) { + warnx("Chassis ID TLV too small (%d < 2)", s); + return -1; + } + subtype = *(u_int8_t*)frame; + switch (subtype) { + case LLDP_CHASSISID_SUBTYPE_CHASSIS: + case LLDP_CHASSISID_SUBTYPE_IFALIAS: + case LLDP_CHASSISID_SUBTYPE_PORT: + case LLDP_CHASSISID_SUBTYPE_ADDR: + case LLDP_CHASSISID_SUBTYPE_IFNAME: + case LLDP_CHASSISID_SUBTYPE_LOCAL: + printf("Unhandled chassis ID type (%x)\n", subtype); + break; + case LLDP_CHASSISID_SUBTYPE_LLADDR: + if (s != 7) { + warnx("Incorrect MAC address size (%d != 6)", s-1); + return -1; + } + printf("Chassis mac address:\n %02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx\n", + *(char *)(frame + 1), + *(char *)(frame + 2), + *(char *)(frame + 3), + *(char *)(frame + 4), + *(char *)(frame + 5), + *(char *)(frame + 6)); + break; + default: + warnx("Unknown Chassis ID subtype (%x)", subtype); + } + return 0; +} + +int check_tlv_portid(const char *frame, int s) { + u_int8_t subtype; + if (s < 2) { + warnx("Port ID TLV too small (%d < 2)", s); + return -1; + } + subtype = *(u_int8_t*)frame; + switch (subtype) { + case LLDP_PORTID_SUBTYPE_IFALIAS: + case LLDP_PORTID_SUBTYPE_PORT: + case LLDP_PORTID_SUBTYPE_ADDR: + case LLDP_PORTID_SUBTYPE_IFNAME: + case LLDP_PORTID_SUBTYPE_LOCAL: + printf("Unhandled Port ID type (%x)\n", subtype); + break; + case LLDP_PORTID_SUBTYPE_LLADDR: + if (s != 7) { + warnx("Incorrect MAC address size (%d != 6)", s-1); + return -1; + } + printf("Port mac address:\n %02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx\n", + *(char *)(frame + 1), + *(char *)(frame + 2), + *(char *)(frame + 3), + *(char *)(frame + 4), + *(char *)(frame + 5), + *(char *)(frame + 6)); + break; + default: + warnx("Unknown Port ID subtype (%x)", subtype); + } + return 0; +} + +int check_tlv_ttl(const char *frame, int s) { + if (s != 2) { + warnx("Incorrect TTL length (%d != 2)", s); + return -1; + } + printf("TTL:\n %d\n", ntohs(*(u_int16_t*)frame)); + return 0; +} + +int check_tlv_simplestring(const char *frame, int s, char *what) { + char *desc; + if (s < 1) { + warnx("Incorrect %s length (%d < 1)", what, s); + return -1; + } + if (!(desc = (char *)malloc(s+1))) { + warnx("Not able to allocate memory"); + return -1; + } + strncpy(desc, frame, s); + desc[s] = 0; + printf("%s:\n %s\n", what, desc); + free(desc); + return 0; +} + +int check_tlv_portdescr(const char *frame, int s) { + return check_tlv_simplestring(frame, s, "Port description"); +} + +int check_tlv_systemname(const char *frame, int s) { + return check_tlv_simplestring(frame, s, "System name"); +} + +int check_tlv_systemdescr(const char *frame, int s) { + return check_tlv_simplestring(frame, s, "System description"); +} + +int check_tlv_systemcap(const char *frame, int s) { + if (s != 4) { + warnx("Incorrect system capabilities length (%d != 4)", s); + return -1; + } + printf("System capabilities (available/enabled):\n %x %x\n", + ntohs(*(u_int16_t*)frame), + ntohs(*(u_int16_t*)(frame+2))); + return 0; +} + +int check_tlv_manaddr(const char *frame, int s) { + return 0; +} + +int check_tlv_dot1(const char *frame, int s) { + int subtype; + int l; + char *vlanname; + if (s < 1) { + warnx("DOT1 frame too short (%d < 1)", s); + return -1; + } + subtype = *(u_int8_t*)frame; + switch (subtype) { + case LLDP_TLV_DOT1_PPVID: + case LLDP_TLV_DOT1_PI: + warnx("Unhandled dot1 subtype"); + break; + case LLDP_TLV_DOT1_PVID: + if (s < 3) { + warnx("DOT1 PVID frame too short (%d < 3)", s); + return -1; + } + printf("PVID:\n %d\n", ntohs(*(u_int16_t*)(frame + 1))); + break; + case LLDP_TLV_DOT1_VLANNAME: + if (s < 4) { + warnx("DOT1 VLAN name frame too short (%d < 4)", s); + return -1; + } + l = *(u_int8_t*)(frame + 3); + if (s < 4 + l) { + warnx("DOT1 VLAN name frame too short (%d < 4 + %d)", s, l); + return -1; + } + vlanname = (char *)malloc(l + 1); + strncpy(vlanname, frame+4, l); + vlanname[l] = 0; + printf("VLAN name/id:\n %s/%d\n", vlanname, ntohs(*(u_int16_t*)(frame + 1))); + break; + default: + warnx("Unknown dot1 subtype (%d)", subtype); + return -1; + } + return 0; +} + +int check_tlv_dot3(const char *frame, int s) { + warnx("Do nothing for dot3"); + return 0; +} + +int check_tlv_org(const char *frame, int s) { + char dot1[] = LLDP_TLV_ORG_DOT1; + char dot3[] = LLDP_TLV_ORG_DOT3; + if (s < 3) { + warnx("Frame too short (3)"); + return -1; + } + if (memcmp(dot1, frame, 3) == 0) + return check_tlv_dot1(frame + 3, s - 3); + if (memcmp(dot3, frame, 3) == 0) + return check_tlv_dot3(frame + 3, s - 3); + + warnx("Unknown org code"); + return -1; +} + +int check_tlv(const char *frame, int offset, int s) { + int (*sub_tlv[])(const char *frame, int s) = { + check_tlv_end, + check_tlv_chassisid, + check_tlv_portid, + check_tlv_ttl, + check_tlv_portdescr, + check_tlv_systemname, + check_tlv_systemdescr, + check_tlv_systemcap, + check_tlv_manaddr, + NULL }; + int size; + int type; + int i = 0; + int rc = 0; + + if (offset + 2 > s) { + warnx("Frame too short (1)"); + return -1; + } + size = ntohs(*(u_int16_t*)(frame + offset)) & 0x1ff; + type = ntohs(*(u_int16_t*)(frame + offset)) >> 9; + if (offset + size > s) { + warnx("Frame too short (2)"); + return -1; + } + + switch (type) { + case LLDP_TLV_ORG: + rc = check_tlv_org(frame + offset + 2, size); + break; + default: + while (sub_tlv[i] != NULL) { + if (type == i) { + rc = (*sub_tlv[i])(frame + offset + 2, size); + break; + } else i++; + } + if (sub_tlv[i] == NULL) { + warnx("Unknown TLV type (%x)", type); + return -1; + } + } + + if (rc < 0) + return rc; + return offset + size + 2; +} + +int main() { + int s, l, i; + struct sockaddr_ll sa; + char frame[MTU]; + + if (set_multi(1) < 0) + err(1, "Unable to set multicast address"); + + if ((s = socket(PF_PACKET, SOCK_RAW, htons(0x88cc))) < 0) { + warn("Unable to create socket"); + goto end; + } + + bzero(&sa, sizeof(sa)); + sa.sll_family = AF_PACKET; + sa.sll_protocol = 0; + sa.sll_ifindex = if_nametoindex(IF); + if (bind(s, (struct sockaddr*)&sa, sizeof(sa)) < 0) { + warn("Unable to bind"); + goto end; + } + + while (1) { + l = recv(s, frame, MTU, 0); + dump(frame, l); + if (check_mac(frame, l) < 0) { + warnx("Not LLDP MAC address"); + continue; + } + if (check_protocol(frame, l) < 0) { + warnx("Not LLDP protocol"); + continue; + } + i = 2 * ETH_ALEN + 2; + while (i < l) { + i = check_tlv(frame, i, l); + if (i < 0) + break; + } + } + + + end: + if (set_multi(0) < 0) + err(1, "Unable to unset multicast address"); + + return 0; +} diff --git a/misc/test-snmp.c b/misc/test-snmp.c new file mode 100644 index 00000000..9ae78f19 --- /dev/null +++ b/misc/test-snmp.c @@ -0,0 +1,463 @@ +/* gcc -Wall $(net-snmp-config --base-cflags) test-snmp.c $(net-snmp-config --agent-libs) -o test-snmp */ + +#include + +#define USING_AGENTX_SUBAGENT_MODULE 1 + +#include +#include +#include +#include + +#include "../lldp.h" + +typedef struct lldpGlobal { + int32_t messageTxInterval; + int32_t messageTxHoldMultiplier; + int32_t reinitDelay; + int32_t txDelay; + int32_t notificationInterval; +} lldpGlobal; + +struct lldpGlobal global = { + .messageTxInterval = 30, + .messageTxHoldMultiplier = 4, + .reinitDelay = 2, + .txDelay = 5, + .notificationInterval = 5 +}; + +oid messageTxInterval_oid[] = {1, 0, 8802, 1, 1, 2, 1, 1, 1, 0}; +oid messageTxHoldMultiplier_oid[] = {1, 0, 8802, 1, 1, 2, 1, 1, 2, 0}; +oid reinitDelay_oid[] = {1, 0, 8802, 1, 1, 2, 1, 1, 3, 0}; +oid txDelay_oid[] = {1, 0, 8802, 1, 1, 2, 1, 1, 4, 0}; +oid notificationInterval_oid[] = {1, 0, 8802, 1, 1, 2, 1, 1, 5, 0}; + +typedef struct lldpStats { + u_int32_t lastChangeTime; + u_int32_t inserts; + u_int32_t deletes; + u_int32_t drops; + u_int32_t ageouts; +} lldpStats; + +struct lldpStats stats = { + .lastChangeTime = 4575120, + .inserts = 1451, + .deletes = 12, + .drops = 0, + .ageouts = 2 +}; + +oid lastChangeTime_oid[] = {1, 0, 8802, 1, 1, 2, 1, 2, 1, 0}; +oid inserts_oid[] = {1, 0, 8802, 1, 1, 2, 1, 2, 2, 0}; +oid deletes_oid[] = {1, 0, 8802, 1, 1, 2, 1, 2, 3, 0}; +oid drops_oid[] = {1, 0, 8802, 1, 1, 2, 1, 2, 4, 0}; +oid ageouts_oid[] = {1, 0, 8802, 1, 1, 2, 1, 2, 5, 0}; + +typedef struct lldpChassis { + int chassisIdSubtype; + u_int8_t chassisId[256]; + int chassisId_len; + char sysName[256]; + char sysDesc[256]; + u_int8_t sysCapSupported; + u_int8_t sysCapEnabled; +} lldpChassis; + +struct lldpChassis local = { + .chassisIdSubtype = LLDP_CHASSISID_SUBTYPE_LLADDR, + .chassisId = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05 }, + .chassisId_len = 6, + .sysName = "neo.luffy.cx", + .sysDesc = "Linux neo 2.6.25-2-amd64 #1 SMP Thu Jun 12 15:38:32 UTC 2008 x86_64 GNU/Linux", + .sysCapSupported = LLDP_CAP_BRIDGE | LLDP_CAP_WLAN | LLDP_CAP_ROUTER, + .sysCapEnabled = LLDP_CAP_ROUTER +}; + +oid chassisIdSubtype_oid[] = {1, 0, 8802, 1, 1, 2, 1, 3, 1, 0}; +oid chassisId_oid[] = {1, 0, 8802, 1, 1, 2, 1, 3, 2, 0}; +oid sysName_oid[] = {1, 0, 8802, 1, 1, 2, 1, 3, 3, 0}; +oid sysDesc_oid[] = {1, 0, 8802, 1, 1, 2, 1, 3, 4, 0}; +oid sysCapSupported_oid[] = {1, 0, 8802, 1, 1, 2, 1, 3, 5, 0}; +oid sysCapEnabled_oid[] = {1, 0, 8802, 1, 1, 2, 1, 3, 6, 0}; + +typedef struct snmp_type { + u_char *value; + int *value_size; + int type; +} snmp_type; + +struct lldpPort { + int portIdSubtype; + u_int8_t portId[256]; + int portId_len; + char portDesc[256]; +} lldpPort; + +struct lldpRemote { + /* Index values */ + u_int32_t lldpRemTimeMark; + int lldpRemLocalPortNum; + int lldpRemIndex; + + struct lldpPort port; + struct lldpChassis remote; + + TAILQ_ENTRY(lldpRemote) next; +} lldpRemote; + +TAILQ_HEAD(, lldpRemote) r_entries; + +struct lldpRemote r1 = { + .lldpRemTimeMark = 4121, + .lldpRemLocalPortNum = 1, + .lldpRemIndex = 1, + .port = { + .portIdSubtype = LLDP_PORTID_SUBTYPE_LLADDR, + .portId = { 0x00, 0x05, 0x06, 0x07, 0x0a, 0x01 }, + .portId_len = 6, + .portDesc = "eth0" + }, + .remote = { + .chassisIdSubtype = LLDP_CHASSISID_SUBTYPE_LLADDR, + .chassisId = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x06 }, + .chassisId_len = 6, + .sysName = "titi.luffy.cx", + .sysDesc = "Linux titi 2.6.25-2-amd64 #1 SMP Thu Jun 12 15:38:32 UTC 2008 x86_64 GNU/Linux", + .sysCapSupported = LLDP_CAP_BRIDGE | LLDP_CAP_WLAN | LLDP_CAP_ROUTER, + .sysCapEnabled = LLDP_CAP_ROUTER + } +}; + +struct lldpRemote r2 = { + .lldpRemTimeMark = 4127, + .lldpRemLocalPortNum = 3, + .lldpRemIndex = 2, + .port = { + .portIdSubtype = LLDP_PORTID_SUBTYPE_LLADDR, + .portId = { 0x00, 0x05, 0x06, 0x07, 0x0a, 0x03 }, + .portId_len = 6, + .portDesc = "en4" + }, + .remote = { + .chassisIdSubtype = LLDP_CHASSISID_SUBTYPE_LLADDR, + .chassisId = { 0x07, 0x01, 0x02, 0x03, 0x04, 0x06 }, + .chassisId_len = 6, + .sysName = "tito.luffy.cx", + .sysDesc = "Linux tito 2.6.25-2-amd64 #1 SMP Thu Jun 12 15:38:32 UTC 2008 x86_64 GNU/Linux", + .sysCapSupported = LLDP_CAP_BRIDGE | LLDP_CAP_WLAN | LLDP_CAP_ROUTER, + .sysCapEnabled = LLDP_CAP_ROUTER + } +}; + +int lldpRemTable_handler( + netsnmp_mib_handler *handler, + netsnmp_handler_registration *reginfo, + netsnmp_agent_request_info *reqinfo, + netsnmp_request_info *requests) { + + netsnmp_request_info *request; + netsnmp_table_request_info *table_info; + struct lldpRemote *table_entry; + + switch (reqinfo->mode) { + case MODE_GET: + for (request=requests; request; request=request->next) { + table_entry = (struct lldpRemote *) + netsnmp_extract_iterator_context(request); + table_info = netsnmp_extract_table_info(request); + + if (!table_entry) { + netsnmp_set_request_error(reqinfo, request, + SNMP_NOSUCHINSTANCE); + continue; + } + + switch (table_info->colnum) { + case 4: + snmp_set_var_typed_integer(request->requestvb, ASN_INTEGER, + table_entry->remote.chassisIdSubtype); + break; + case 5: + snmp_set_var_typed_value(request->requestvb, ASN_OCTET_STR, + (u_char*)table_entry->remote.chassisId, + table_entry->remote.chassisId_len); + break; + case 6: + snmp_set_var_typed_integer(request->requestvb, ASN_INTEGER, + table_entry->port.portIdSubtype); + break; + case 7: + snmp_set_var_typed_value(request->requestvb, ASN_OCTET_STR, + (u_char*)table_entry->port.portId, + table_entry->port.portId_len); + break; + case 8: + snmp_set_var_typed_value(request->requestvb, ASN_OCTET_STR, + (u_char*)table_entry->port.portDesc, + strlen(table_entry->port.portDesc)); + break; + case 9: + snmp_set_var_typed_value(request->requestvb, ASN_OCTET_STR, + (u_char*)table_entry->remote.sysName, + strlen(table_entry->remote.sysName)); + break; + case 10: + snmp_set_var_typed_value(request->requestvb, ASN_OCTET_STR, + (u_char*)table_entry->remote.sysDesc, + strlen(table_entry->remote.sysDesc)); + break; + case 11: + snmp_set_var_typed_value( request->requestvb, ASN_OCTET_STR, + (u_char*)&(table_entry->remote.sysCapSupported), + 1); + break; + case 12: + snmp_set_var_typed_value( request->requestvb, ASN_OCTET_STR, + (u_char*)&(table_entry->remote.sysCapEnabled), + 1); + break; + default: + netsnmp_set_request_error(reqinfo, request, + SNMP_NOSUCHOBJECT); + break; + } + } + break; + + } + return SNMP_ERR_NOERROR; +} + +netsnmp_variable_list *lldpRemTable_get_next_data_point(void **my_loop_context, + void **my_data_context, + netsnmp_variable_list *put_index_data, + netsnmp_iterator_info *mydata) { + struct lldpRemote *entry = (struct lldpRemote*)*my_loop_context; + netsnmp_variable_list *idx = put_index_data; + + if (entry) { + snmp_set_var_typed_integer( idx, ASN_TIMETICKS, entry->lldpRemTimeMark ); + idx = idx->next_variable; + snmp_set_var_typed_integer( idx, ASN_INTEGER, entry->lldpRemLocalPortNum ); + idx = idx->next_variable; + snmp_set_var_typed_integer( idx, ASN_INTEGER, entry->lldpRemIndex ); + idx = idx->next_variable; + *my_data_context = (void *)entry; + *my_loop_context = TAILQ_NEXT(entry, next); + return put_index_data; + } else { + return NULL; + } +} + +netsnmp_variable_list *lldpRemTable_get_first_data_point(void **my_loop_context, + void **my_data_context, + netsnmp_variable_list *put_index_data, + netsnmp_iterator_info *mydata) { + *my_loop_context = TAILQ_FIRST(&r_entries); + return lldpRemTable_get_next_data_point(my_loop_context, my_data_context, + put_index_data, mydata); +} + +void populate_r_entries() { + static oid lldpRemTable_oid[] = {1,0,8802,1,1,2,1,4,1}; + size_t lldpRemTable_oid_len = OID_LENGTH(lldpRemTable_oid); + netsnmp_handler_registration *reg; + netsnmp_iterator_info *iinfo; + netsnmp_table_registration_info *table_info; + + reg = netsnmp_create_handler_registration( + "lldpRemTable", lldpRemTable_handler, + lldpRemTable_oid, lldpRemTable_oid_len, + HANDLER_CAN_RONLY + ); + + table_info = SNMP_MALLOC_TYPEDEF( netsnmp_table_registration_info ); + netsnmp_table_helper_add_indexes(table_info, + ASN_TIMETICKS, /* index: lldpRemTimeMark */ + ASN_INTEGER, /* index: lldpRemLocalPortNum */ + ASN_INTEGER, /* index: lldpRemIndex */ + 0); + table_info->min_column = 1; + table_info->max_column = 12; + + iinfo = SNMP_MALLOC_TYPEDEF( netsnmp_iterator_info ); + iinfo->get_first_data_point = lldpRemTable_get_first_data_point; + iinfo->get_next_data_point = lldpRemTable_get_next_data_point; + iinfo->table_reginfo = table_info; + + netsnmp_register_table_iterator( reg, iinfo ); + + TAILQ_INIT(&r_entries); + TAILQ_INSERT_TAIL(&r_entries, &r1, next); + TAILQ_INSERT_TAIL(&r_entries, &r2, next); +} + +int netsnmp_instance_universal_handler(netsnmp_mib_handler *handler, + netsnmp_handler_registration *reginfo, + netsnmp_agent_request_info *reqinfo, + netsnmp_request_info *requests) { + struct snmp_type *st = (struct snmp_type *) handler->myvoid; + int size; + + switch (reqinfo->mode) { + case MODE_GET: + if (st->value_size == NULL) { + /* Try to guess */ + switch (st->type) { + case ASN_COUNTER: + case ASN_TIMETICKS: + case ASN_INTEGER: + case ASN_GAUGE: + size = 4; + break; + case ASN_OCTET_STR: + size = strlen((char*)st->value); + break; + default: + size = 1; + } + } else + size = *(st->value_size); + snmp_set_var_typed_value(requests->requestvb, st->type, + st->value, size); + break; + default: + snmp_log(LOG_ERR, + "netsnmp_instance_universal_handler: illegal mode\n"); + netsnmp_set_request_error(reqinfo, requests, SNMP_ERR_GENERR); + return SNMP_ERR_NOERROR; + } + if (handler->next && handler->next->access_method) + return netsnmp_call_next_handler(handler, reginfo, reqinfo, + requests); + return SNMP_ERR_NOERROR; +} + +int netsnmp_register_read_only_universal_instance(const char *name, + oid * reg_oid, + size_t reg_oid_len, + void *value, + int *value_size, + int type) { + netsnmp_handler_registration *myreg; + struct snmp_type *st; + + /* We will leak memory... */ + st = (struct snmp_type *)malloc(sizeof(struct snmp_type)); + st->value = value; + st->value_size = value_size; + st->type = type; + + myreg = + netsnmp_create_handler_registration(name, + netsnmp_instance_universal_handler, + reg_oid, reg_oid_len, + HANDLER_CAN_RONLY); + myreg->handler->myvoid = (void *) st; + + return netsnmp_register_read_only_instance(myreg); +} + +#define REGISTER(variable, name, type) \ + netsnmp_register_read_only_universal_instance(#name, \ + name ## _oid, \ + OID_LENGTH(name ## _oid), \ + &variable.name, NULL, type) + +#define REGISTER_S(variable, name, type) \ + netsnmp_register_read_only_universal_instance(#name, \ + name ## _oid, \ + OID_LENGTH(name ## _oid), \ + &variable.name, &variable.name ## _len, type) + +#define REGISTER_FS(variable, name, size, type) \ + netsnmp_register_read_only_universal_instance(#name, \ + name ## _oid, \ + OID_LENGTH(name ## _oid), \ + &variable.name, &size, type) + +int one = 1; +int two = 2; +int three = 3; +int interfaces[] = {1, 2, 3, 4}; +char stuff[] = { 0xf0 }; + +void register_lldpPortConfig() { + int i; + static oid lldpPortConfigTable_oid[] = {1,0,8802,1,1,2,1,1,6}; + size_t lldpPortConfigTable_oid_len = OID_LENGTH(lldpPortConfigTable_oid); + netsnmp_table_data_set *table_set; + netsnmp_table_row *row; + table_set = netsnmp_create_table_data_set("lldpPortConfigTable"); + netsnmp_table_set_add_indexes(table_set, + ASN_INTEGER, + 0); + netsnmp_table_set_multi_add_default_row(table_set, + 2, ASN_INTEGER, 0, NULL, 0, + 3, ASN_INTEGER, 0, NULL, 0, + 4, ASN_OCTET_STR, 0, NULL, 0, + 0); + netsnmp_register_table_data_set( + netsnmp_create_handler_registration("lldpPortConfigTable", NULL, + lldpPortConfigTable_oid, + lldpPortConfigTable_oid_len, + HANDLER_CAN_RONLY), + table_set, NULL); + for (i=0; i < sizeof(interfaces)/sizeof(int); i++) { + row = netsnmp_create_table_data_row(); + netsnmp_table_row_add_index(row, ASN_INTEGER, (u_char*)(&interfaces[i]), sizeof(int)); + netsnmp_set_row_column(row, 2, ASN_INTEGER, (char*)&three, sizeof(three)); + netsnmp_set_row_column(row, 3, ASN_INTEGER, (char*)&two, sizeof(two)); + netsnmp_set_row_column(row, 4, ASN_OCTET_STR, stuff, 1); + + netsnmp_table_dataset_add_row(table_set, row); + } + netsnmp_register_auto_data_table(table_set, NULL); + +} + +int main (int argc, char **argv) { + + netsnmp_enable_subagent(); + snmp_disable_log(); + snmp_enable_stderrlog(); + + init_agent("lldpAgent"); + + REGISTER(global, messageTxInterval, ASN_INTEGER); + REGISTER(global, messageTxHoldMultiplier, ASN_INTEGER); + REGISTER(global, reinitDelay, ASN_INTEGER); + REGISTER(global, txDelay, ASN_INTEGER); + REGISTER(global, notificationInterval, ASN_INTEGER); + + REGISTER(stats, lastChangeTime, ASN_TIMETICKS); + REGISTER(stats, inserts, ASN_GAUGE); + REGISTER(stats, deletes, ASN_GAUGE); + REGISTER(stats, drops, ASN_GAUGE); + REGISTER(stats, ageouts, ASN_GAUGE); + + REGISTER(local, chassisIdSubtype, ASN_INTEGER); + REGISTER_S(local, chassisId, ASN_OCTET_STR); + REGISTER(local, sysName, ASN_OCTET_STR); + REGISTER(local, sysDesc, ASN_OCTET_STR); + REGISTER_FS(local, sysCapSupported, one, ASN_OCTET_STR); + REGISTER_FS(local, sysCapEnabled, one, ASN_OCTET_STR); + + register_lldpPortConfig(); + + populate_r_entries(); + + init_snmp("lldpAgent"); + + while(1) + agent_check_and_process(1); + + snmp_shutdown("lldpAgent"); + + return 0; +} + + diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 00000000..e84f5bf1 --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,13 @@ +sbin_PROGRAMS = lldpd lldpctl + +COMMON = log.c ctl.c lldpd.h lldp.h cdp.h compat.h sonmp.h llc.h edp.h +lldpd_SOURCES = lldpd.c lldp.c cdp.c sonmp.c edp.c iov.c features.c $(COMMON) +lldpctl_SOURCES = lldpctl.c $(COMMON) + +lldpd_LDADD = @LIBOBJS@ +lldpctl_LDADD = @LIBOBJS@ + +if USE_SNMP +lldpd_SOURCES += agent.c +lldpd_LDADD += @NETSNMP_LIB@ +endif diff --git a/src/agent.c b/src/agent.c new file mode 100644 index 00000000..44f5aea2 --- /dev/null +++ b/src/agent.c @@ -0,0 +1,814 @@ +/* + * Copyright (c) 2008 Vincent Bernat + * + * Permission to use, copy, modify, and 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 +#include +#include +#include +#include + +static oid lldp_oid[] = {1, 0, 8802, 1, 1, 2}; + +/* For net-snmp */ +extern int register_sysORTable(oid *, size_t, const char *); +extern int unregister_sysORTable(oid *, size_t); +extern struct timeval starttime; + +/* Global variable because no way to pass it as argument. Should not be used + * elsewhere. */ +struct lldpd *scfg; + +static inline uint8_t +swap_bits(uint8_t n) +{ + n = ((n&0xF0) >>4 ) | ( (n&0x0F) <<4); + n = ((n&0xCC) >>2 ) | ( (n&0x33) <<2); + n = ((n&0xAA) >>1 ) | ( (n&0x55) <<1); + + return n; +}; + +struct lldpd_hardware* +header_portindexed_table(struct variable *vp, oid *name, size_t *length, + int exact, size_t *var_len, WriteMethod **write_method) +{ + struct lldpd_hardware *hardware, *phardware = NULL; + unsigned int port, aport = 0, distance; + + if (header_simple_table(vp, name, length, exact, var_len, write_method, -1)) + return NULL; + + port = name[*length - 1]; + distance = -1; + TAILQ_FOREACH(hardware, &scfg->g_hardware, h_entries) { + if (INTERFACE_OPENED(hardware)) { + aport = if_nametoindex(hardware->h_ifname); + if (aport == port) { + /* Exact match */ + return hardware; + } + if (aport < port) + continue; + if (aport - port < distance) { + phardware = hardware; + distance = aport - port; + } + } + } + if (phardware == NULL) + return NULL; + if (exact) + return NULL; + if (distance == -1) + return NULL; + aport = distance + port; + name[*length - 1] = aport; + return phardware; +} + +struct lldpd_hardware* +header_tprindexed_table(struct variable *vp, oid *name, size_t *length, + int exact, size_t *var_len, WriteMethod **write_method, int withip) +{ + struct lldpd_hardware *hardware, *phardware = NULL; + oid *target, current[9], best[9]; + int result, target_len, oid_len; + int i; + + if ((result = snmp_oid_compare(name, *length, vp->name, vp->namelen)) < 0) { + memcpy(name, vp->name, sizeof(oid) * vp->namelen); + *length = vp->namelen; + } + + *write_method = 0; + *var_len = sizeof(long); + + oid_len = (withip) ? 9:3; + for (i = 0; i < oid_len; i++) best[i] = MAX_SUBID; + target = &name[vp->namelen]; + target_len = *length - vp->namelen; + TAILQ_FOREACH(hardware, &scfg->g_hardware, h_entries) { + if ((INTERFACE_OPENED(hardware)) && (hardware->h_rchassis != NULL)) { + if (withip && (hardware->h_rchassis->c_mgmt.s_addr == INADDR_ANY)) + continue; + current[0] = (hardware->h_rlastchange - starttime.tv_sec)*100; + current[1] = if_nametoindex(hardware->h_ifname); + current[2] = hardware->h_rid; + if (withip) { + current[3] = 1; + current[4] = 4; + current[8] = hardware->h_rchassis->c_mgmt.s_addr >> 24; + current[7] = (hardware->h_rchassis->c_mgmt.s_addr & 0xffffff) >> 16; + current[6] = (hardware->h_rchassis->c_mgmt.s_addr & 0xffff) >> 8; + current[5] = hardware->h_rchassis->c_mgmt.s_addr & 0xff; + } + if ((result = snmp_oid_compare(current, oid_len, target, + target_len)) < 0) + continue; + if ((result == 0) && !exact) + continue; + if (result == 0) + return hardware; + if (snmp_oid_compare(current, oid_len, best, oid_len) < 0) { + memcpy(best, current, sizeof(oid) * oid_len); + phardware = hardware; + } + } + } + if (phardware == NULL) + return NULL; + if (exact) + return NULL; + for (i = 0; i < oid_len; i++) + if (best[i] != MAX_SUBID) break; + if (i == oid_len) + return NULL; + memcpy(target, best, sizeof(oid) * oid_len); + *length = vp->namelen + oid_len; + + return phardware; +} + +struct lldpd_vlan* +header_pvindexed_table(struct variable *vp, oid *name, size_t *length, + int exact, size_t *var_len, WriteMethod **write_method) +{ + struct lldpd_hardware *hardware; + struct lldpd_vlan *vlan, *pvlan = NULL; + oid *target, current[2], best[2]; + int result, target_len; + + if ((result = snmp_oid_compare(name, *length, vp->name, vp->namelen)) < 0) { + memcpy(name, vp->name, sizeof(oid) * vp->namelen); + *length = vp->namelen; + } + + *write_method = 0; + *var_len = sizeof(long); + + best[0] = best[1] = MAX_SUBID; + target = &name[vp->namelen]; + target_len = *length - vp->namelen; + TAILQ_FOREACH(hardware, &scfg->g_hardware, h_entries) { + if (INTERFACE_OPENED(hardware)) { + TAILQ_FOREACH(vlan, &hardware->h_lport.p_vlans, v_entries) { + current[0] = if_nametoindex(hardware->h_ifname); + current[1] = vlan->v_vid; + if ((result = snmp_oid_compare(current, 2, target, + target_len)) < 0) + continue; + if ((result == 0) && !exact) + continue; + if (result == 0) + return vlan; + if (snmp_oid_compare(current, 2, best, 2) < 0) { + memcpy(best, current, sizeof(oid) * 2); + pvlan = vlan; + } + } + } + } + if (pvlan == NULL) + return NULL; + if (exact) + return NULL; + if ((best[0] == best[1]) && + (best[0] == MAX_SUBID)) + return NULL; + memcpy(target, best, sizeof(oid) * 2); + *length = vp->namelen + 2; + + return pvlan; +} + +struct lldpd_vlan* +header_tprvindexed_table(struct variable *vp, oid *name, size_t *length, + int exact, size_t *var_len, WriteMethod **write_method) +{ + struct lldpd_hardware *hardware; + struct lldpd_vlan *vlan, *pvlan = NULL; + oid *target, current[4], best[4]; + int result, target_len; + + if ((result = snmp_oid_compare(name, *length, vp->name, vp->namelen)) < 0) { + memcpy(name, vp->name, sizeof(oid) * vp->namelen); + *length = vp->namelen; + } + + *write_method = 0; + *var_len = sizeof(long); + + best[0] = best[1] = best[2] = best[3] = MAX_SUBID; + target = &name[vp->namelen]; + target_len = *length - vp->namelen; + TAILQ_FOREACH(hardware, &scfg->g_hardware, h_entries) { + if ((INTERFACE_OPENED(hardware)) && (hardware->h_rport != NULL)) { + TAILQ_FOREACH(vlan, &hardware->h_rport->p_vlans, v_entries) { + current[0] = (hardware->h_rlastchange - starttime.tv_sec)*100; + current[1] = if_nametoindex(hardware->h_ifname); + current[2] = hardware->h_rid; + current[3] = vlan->v_vid; + if ((result = snmp_oid_compare(current, 4, target, + target_len)) < 0) + continue; + if ((result == 0) && !exact) + continue; + if (result == 0) + return vlan; + if (snmp_oid_compare(current, 4, best, 4) < 0) { + memcpy(best, current, sizeof(oid) * 4); + pvlan = vlan; + } + } + } + } + if (pvlan == NULL) + return NULL; + if (exact) + return NULL; + if ((best[0] == best[1]) && (best[1] == best[2]) && + (best[2] == best[3]) && (best[0] == MAX_SUBID)) + return NULL; + memcpy(target, best, sizeof(oid) * 4); + *length = vp->namelen + 4; + + return pvlan; +} + +/* Scalars */ +#define LLDP_SNMP_TXINTERVAL 1 +#define LLDP_SNMP_TXMULTIPLIER 2 +#define LLDP_SNMP_REINITDELAY 3 +#define LLDP_SNMP_TXDELAY 4 +#define LLDP_SNMP_NOTIFICATION 5 +#define LLDP_SNMP_LASTUPDATE 6 +#define LLDP_SNMP_STATS_INSERTS 7 +#define LLDP_SNMP_STATS_DELETES 8 +#define LLDP_SNMP_STATS_DROPS 9 +#define LLDP_SNMP_STATS_AGEOUTS 10 +/* Local chassis */ +#define LLDP_SNMP_LOCAL_CIDSUBTYPE 1 +#define LLDP_SNMP_LOCAL_CID 2 +#define LLDP_SNMP_LOCAL_SYSNAME 3 +#define LLDP_SNMP_LOCAL_SYSDESCR 4 +#define LLDP_SNMP_LOCAL_SYSCAP_SUP 5 +#define LLDP_SNMP_LOCAL_SYSCAP_ENA 6 +/* Stats */ +#define LLDP_SNMP_STATS_TX_PORTNUM 1 +#define LLDP_SNMP_STATS_TX 2 +#define LLDP_SNMP_STATS_RX_PORTNUM 3 +#define LLDP_SNMP_STATS_RX_DISCARDED 4 +#define LLDP_SNMP_STATS_RX_ERRORS 5 +#define LLDP_SNMP_STATS_RX 6 +#define LLDP_SNMP_STATS_RX_TLVDISCARDED 7 +#define LLDP_SNMP_STATS_RX_TLVUNRECOGNIZED 8 +#define LLDP_SNMP_STATS_RX_AGEOUTS 9 +/* Local ports */ +#define LLDP_SNMP_LOCAL_PORTNUM 1 +#define LLDP_SNMP_LOCAL_PIDSUBTYPE 2 +#define LLDP_SNMP_LOCAL_PID 3 +#define LLDP_SNMP_LOCAL_PORTDESC 4 +#define LLDP_SNMP_LOCAL_DOT3_AUTONEG_SUPPORT 5 +#define LLDP_SNMP_LOCAL_DOT3_AUTONEG_ENABLED 6 +#define LLDP_SNMP_LOCAL_DOT3_AUTONEG_ADVERTISED 7 +#define LLDP_SNMP_LOCAL_DOT3_AUTONEG_MAU 8 +#define LLDP_SNMP_LOCAL_DOT3_AGG_STATUS 9 +#define LLDP_SNMP_LOCAL_DOT3_AGG_ID 10 +/* Remote ports */ +#define LLDP_SNMP_REMOTE_CIDSUBTYPE 1 +#define LLDP_SNMP_REMOTE_CID 2 +#define LLDP_SNMP_REMOTE_PIDSUBTYPE 3 +#define LLDP_SNMP_REMOTE_PID 4 +#define LLDP_SNMP_REMOTE_PORTDESC 5 +#define LLDP_SNMP_REMOTE_SYSNAME 6 +#define LLDP_SNMP_REMOTE_SYSDESC 7 +#define LLDP_SNMP_REMOTE_SYSCAP_SUP 8 +#define LLDP_SNMP_REMOTE_SYSCAP_ENA 9 +#define LLDP_SNMP_REMOTE_DOT3_AUTONEG_SUPPORT 10 +#define LLDP_SNMP_REMOTE_DOT3_AUTONEG_ENABLED 11 +#define LLDP_SNMP_REMOTE_DOT3_AUTONEG_ADVERTISED 12 +#define LLDP_SNMP_REMOTE_DOT3_AUTONEG_MAU 13 +#define LLDP_SNMP_REMOTE_DOT3_AGG_STATUS 14 +#define LLDP_SNMP_REMOTE_DOT3_AGG_ID 15 +/* Local vlans */ +#define LLDP_SNMP_LOCAL_DOT1_VLANNAME 1 +/* Remote vlans */ +#define LLDP_SNMP_REMOTE_DOT1_VLANNAME 1 +/* Management address */ +#define LLDP_SNMP_LOCAL_ADDR_LEN 1 +#define LLDP_SNMP_LOCAL_ADDR_IFSUBTYPE 2 +#define LLDP_SNMP_LOCAL_ADDR_IFID 3 +#define LLDP_SNMP_LOCAL_ADDR_OID 4 +#define LLDP_SNMP_REMOTE_ADDR_IFSUBTYPE 5 +#define LLDP_SNMP_REMOTE_ADDR_IFID 6 +#define LLDP_SNMP_REMOTE_ADDR_OID 7 + +static u_char* +agent_h_scalars(struct variable *vp, oid *name, size_t *length, + int exact, size_t *var_len, WriteMethod **write_method) +{ + static unsigned long long_ret; + struct lldpd_hardware *hardware; + + if (header_generic(vp, name, length, exact, var_len, write_method)) + return NULL; + + switch (vp->magic) { + case LLDP_SNMP_TXINTERVAL: + long_ret = scfg->g_delay; + return (u_char *)&long_ret; + case LLDP_SNMP_TXMULTIPLIER: + long_ret = scfg->g_lchassis.c_ttl / scfg->g_delay; + return (u_char *)&long_ret; + case LLDP_SNMP_REINITDELAY: + long_ret = 1; + return (u_char *)&long_ret; + case LLDP_SNMP_TXDELAY: + long_ret = LLDPD_TX_MSGDELAY; + return (u_char *)&long_ret; + case LLDP_SNMP_NOTIFICATION: + long_ret = 5; + return (u_char *)&long_ret; + case LLDP_SNMP_LASTUPDATE: + long_ret = 0; + TAILQ_FOREACH(hardware, &scfg->g_hardware, h_entries) + if (hardware->h_rlastchange > long_ret) + long_ret = hardware->h_rlastchange; + if (long_ret) + long_ret = (long_ret - starttime.tv_sec) * 100; + return (u_char *)&long_ret; + case LLDP_SNMP_STATS_INSERTS: + /* We assume this is equal to valid frames received on all ports */ + long_ret = 0; + TAILQ_FOREACH(hardware, &scfg->g_hardware, h_entries) + long_ret += hardware->h_rx_cnt; + return (u_char *)&long_ret; + case LLDP_SNMP_STATS_AGEOUTS: + long_ret = 0; + TAILQ_FOREACH(hardware, &scfg->g_hardware, h_entries) + long_ret += hardware->h_rx_ageout_cnt; + return (u_char *)&long_ret; + case LLDP_SNMP_STATS_DELETES: + long_ret = 0; + TAILQ_FOREACH(hardware, &scfg->g_hardware, h_entries) + long_ret += hardware->h_rx_ageout_cnt + + hardware->h_rx_cnt?(hardware->h_rx_cnt - 1):0; + return (u_char *)&long_ret; + case LLDP_SNMP_STATS_DROPS: + /* We assume that we never have insufficient resources */ + long_ret = 0; + return (u_char *)&long_ret; + default: + break; + } + return NULL; +} + +static u_char* +agent_h_local_chassis(struct variable *vp, oid *name, size_t *length, + int exact, size_t *var_len, WriteMethod **write_method) +{ + static uint8_t bit; + static unsigned long long_ret; + + if (header_generic(vp, name, length, exact, var_len, write_method)) + return NULL; + + switch (vp->magic) { + case LLDP_SNMP_LOCAL_CIDSUBTYPE: + long_ret = scfg->g_lchassis.c_id_subtype; + return (u_char *)&long_ret; + case LLDP_SNMP_LOCAL_CID: + *var_len = scfg->g_lchassis.c_id_len; + return (u_char *)scfg->g_lchassis.c_id; + case LLDP_SNMP_LOCAL_SYSNAME: + *var_len = strlen(scfg->g_lchassis.c_name); + return (u_char *)scfg->g_lchassis.c_name; + case LLDP_SNMP_LOCAL_SYSDESCR: + *var_len = strlen(scfg->g_lchassis.c_descr); + return (u_char *)scfg->g_lchassis.c_descr; + case LLDP_SNMP_LOCAL_SYSCAP_SUP: + *var_len = 1; + bit = swap_bits(scfg->g_lchassis.c_cap_available); + return (u_char *)&bit; + case LLDP_SNMP_LOCAL_SYSCAP_ENA: + *var_len = 1; + bit = swap_bits(scfg->g_lchassis.c_cap_enabled); + return (u_char *)&bit; + default: + break; + } + return NULL; +} + +static u_char* +agent_h_stats(struct variable *vp, oid *name, size_t *length, + int exact, size_t *var_len, WriteMethod **write_method) +{ + static unsigned long long_ret; + struct lldpd_hardware *hardware; + + if ((hardware = header_portindexed_table(vp, name, length, + exact, var_len, write_method)) == NULL) + return NULL; + + switch (vp->magic) { + case LLDP_SNMP_STATS_TX: + long_ret = hardware->h_tx_cnt; + return (u_char *)&long_ret; + case LLDP_SNMP_STATS_RX: + long_ret = hardware->h_rx_cnt; + return (u_char *)&long_ret; + case LLDP_SNMP_STATS_RX_DISCARDED: + case LLDP_SNMP_STATS_RX_ERRORS: + long_ret = hardware->h_rx_discarded_cnt; + return (u_char *)&long_ret; + case LLDP_SNMP_STATS_RX_TLVDISCARDED: + case LLDP_SNMP_STATS_RX_TLVUNRECOGNIZED: + /* Not really handled */ + long_ret = 0; + return (u_char *)&long_ret; + case LLDP_SNMP_STATS_RX_AGEOUTS: + long_ret = hardware->h_rx_ageout_cnt; + return (u_char *)&long_ret; + default: + break; + } + return NULL; +} + +static u_char* +agent_h_local_port(struct variable *vp, oid *name, size_t *length, + int exact, size_t *var_len, WriteMethod **write_method) +{ + static uint8_t bit; + struct lldpd_hardware *hardware; + static unsigned long long_ret; + + if ((hardware = header_portindexed_table(vp, name, length, + exact, var_len, write_method)) == NULL) + return NULL; + + switch (vp->magic) { + case LLDP_SNMP_LOCAL_PIDSUBTYPE: + long_ret = hardware->h_lport.p_id_subtype; + return (u_char *)&long_ret; + case LLDP_SNMP_LOCAL_PID: + *var_len = hardware->h_lport.p_id_len; + return (u_char *)hardware->h_lport.p_id; + case LLDP_SNMP_LOCAL_PORTDESC: + *var_len = strlen(hardware->h_lport.p_descr); + return (u_char *)hardware->h_lport.p_descr; + case LLDP_SNMP_LOCAL_DOT3_AUTONEG_SUPPORT: + long_ret = 2 - hardware->h_lport.p_autoneg_support; + return (u_char *)&long_ret; + case LLDP_SNMP_LOCAL_DOT3_AUTONEG_ENABLED: + long_ret = 2 - hardware->h_lport.p_autoneg_enabled; + return (u_char *)&long_ret; + case LLDP_SNMP_LOCAL_DOT3_AUTONEG_ADVERTISED: + *var_len = 2; + return (u_char *)&hardware->h_lport.p_autoneg_advertised; + case LLDP_SNMP_LOCAL_DOT3_AUTONEG_MAU: + long_ret = hardware->h_lport.p_mau_type; + return (u_char *)&long_ret; + case LLDP_SNMP_LOCAL_DOT3_AGG_STATUS: + bit = swap_bits((hardware->h_lport.p_aggregid > 0) ? 3 : 0); + *var_len = 1; + return (u_char *)&bit; + case LLDP_SNMP_LOCAL_DOT3_AGG_ID: + long_ret = hardware->h_lport.p_aggregid; + return (u_char *)&long_ret; + default: + break; + } + return NULL; +} + +static u_char* +agent_h_local_vlan(struct variable *vp, oid *name, size_t *length, + int exact, size_t *var_len, WriteMethod **write_method) +{ + struct lldpd_vlan *vlan; + + if ((vlan = header_pvindexed_table(vp, name, length, + exact, var_len, write_method)) == NULL) + return NULL; + + switch (vp->magic) { + case LLDP_SNMP_LOCAL_DOT1_VLANNAME: + *var_len = strlen(vlan->v_name); + return (u_char *)vlan->v_name; + default: + break; + } + return NULL; +} + +static u_char* +agent_h_remote_vlan(struct variable *vp, oid *name, size_t *length, + int exact, size_t *var_len, WriteMethod **write_method) +{ + struct lldpd_vlan *vlan; + + if ((vlan = header_tprvindexed_table(vp, name, length, + exact, var_len, write_method)) == NULL) + return NULL; + + switch (vp->magic) { + case LLDP_SNMP_REMOTE_DOT1_VLANNAME: + *var_len = strlen(vlan->v_name); + return (u_char *)vlan->v_name; + default: + break; + } + return NULL; +} + +static u_char* +agent_h_remote_port(struct variable *vp, oid *name, size_t *length, + int exact, size_t *var_len, WriteMethod **write_method) +{ + struct lldpd_hardware *hardware; + static uint8_t bit; + static unsigned long long_ret; + + if ((hardware = header_tprindexed_table(vp, name, length, + exact, var_len, write_method, 0)) == NULL) + return NULL; + + switch (vp->magic) { + case LLDP_SNMP_REMOTE_CIDSUBTYPE: + long_ret = hardware->h_rchassis->c_id_subtype; + return (u_char *)&long_ret; + case LLDP_SNMP_REMOTE_CID: + *var_len = hardware->h_rchassis->c_id_len; + return (u_char *)hardware->h_rchassis->c_id; + case LLDP_SNMP_REMOTE_PIDSUBTYPE: + long_ret = hardware->h_rport->p_id_subtype; + return (u_char *)&long_ret; + case LLDP_SNMP_REMOTE_PID: + *var_len = hardware->h_rport->p_id_len; + return (u_char *)hardware->h_rport->p_id; + case LLDP_SNMP_REMOTE_PORTDESC: + *var_len = strlen(hardware->h_rport->p_descr); + return (u_char *)hardware->h_rport->p_descr; + case LLDP_SNMP_REMOTE_SYSNAME: + *var_len = strlen(hardware->h_rchassis->c_name); + return (u_char *)hardware->h_rchassis->c_name; + case LLDP_SNMP_REMOTE_SYSDESC: + *var_len = strlen(hardware->h_rchassis->c_descr); + return (u_char *)hardware->h_rchassis->c_descr; + case LLDP_SNMP_REMOTE_SYSCAP_SUP: + *var_len = 1; + bit = swap_bits(hardware->h_rchassis->c_cap_available); + return (u_char *)&bit; + case LLDP_SNMP_REMOTE_SYSCAP_ENA: + *var_len = 1; + bit = swap_bits(hardware->h_rchassis->c_cap_enabled); + return (u_char *)&bit; + case LLDP_SNMP_REMOTE_DOT3_AUTONEG_SUPPORT: + long_ret = 2 - hardware->h_rport->p_autoneg_support; + return (u_char *)&long_ret; + case LLDP_SNMP_REMOTE_DOT3_AUTONEG_ENABLED: + long_ret = 2 - hardware->h_rport->p_autoneg_enabled; + return (u_char *)&long_ret; + case LLDP_SNMP_REMOTE_DOT3_AUTONEG_ADVERTISED: + *var_len = 2; + return (u_char *)&hardware->h_rport->p_autoneg_advertised; + case LLDP_SNMP_REMOTE_DOT3_AUTONEG_MAU: + long_ret = hardware->h_rport->p_mau_type; + return (u_char *)&long_ret; + case LLDP_SNMP_REMOTE_DOT3_AGG_STATUS: + bit = swap_bits((hardware->h_rport->p_aggregid > 0) ? 3 : 0); + *var_len = 1; + return (u_char *)&bit; + case LLDP_SNMP_REMOTE_DOT3_AGG_ID: + long_ret = hardware->h_rport->p_aggregid; + return (u_char *)&long_ret; + default: + break; + } + return NULL; +} + +static u_char* +agent_management(struct variable *vp, size_t *var_len, struct lldpd_chassis *chassis) +{ + static unsigned long int long_ret; + static oid zeroDotZero[2] = {0, 0}; + + switch (vp->magic) { + case LLDP_SNMP_LOCAL_ADDR_LEN: + long_ret = 5; + return (u_char*)&long_ret; + case LLDP_SNMP_LOCAL_ADDR_IFSUBTYPE: + case LLDP_SNMP_REMOTE_ADDR_IFSUBTYPE: + if (chassis->c_mgmt_if != 0) + long_ret = LLDP_MGMT_IFACE_IFINDEX; + else + long_ret = 1; + return (u_char*)&long_ret; + case LLDP_SNMP_LOCAL_ADDR_IFID: + case LLDP_SNMP_REMOTE_ADDR_IFID: + long_ret = chassis->c_mgmt_if; + return (u_char*)&long_ret; + case LLDP_SNMP_LOCAL_ADDR_OID: + case LLDP_SNMP_REMOTE_ADDR_OID: + *var_len = sizeof(zeroDotZero); + return (u_char*)zeroDotZero; + default: + break; + } + return NULL; +} + +static u_char* +agent_h_local_management(struct variable *vp, oid *name, size_t *length, + int exact, size_t *var_len, WriteMethod **write_method) +{ + oid *target, best[6]; + int result, target_len; + + if (scfg->g_lchassis.c_mgmt.s_addr == INADDR_ANY) + return NULL; + + if ((result = snmp_oid_compare(name, *length, vp->name, vp->namelen)) < 0) { + memcpy(name, vp->name, sizeof(oid) * vp->namelen); + *length = vp->namelen; + } + + *write_method = 0; + *var_len = sizeof(long); + + target = &name[vp->namelen]; + target_len = *length - vp->namelen; + + best[0] = 1; + best[1] = 4; + best[5] = scfg->g_lchassis.c_mgmt.s_addr >> 24; + best[4] = (scfg->g_lchassis.c_mgmt.s_addr & 0xffffff) >> 16; + best[3] = (scfg->g_lchassis.c_mgmt.s_addr & 0xffff) >> 8; + best[2] = scfg->g_lchassis.c_mgmt.s_addr & 0xff; + + if ((result = snmp_oid_compare(target, 6, best, 6)) < 0) { + if (exact) + return NULL; + memcpy(target, best, sizeof(oid) * 6); + *length = vp->namelen + 6; + } else if (exact && (result != 0)) + return NULL; + else if (!exact && result == 0) + return NULL; + + return agent_management(vp, var_len, &scfg->g_lchassis); +} + +static u_char* +agent_h_remote_management(struct variable *vp, oid *name, size_t *length, + int exact, size_t *var_len, WriteMethod **write_method) +{ + struct lldpd_hardware *hardware; + + if ((hardware = header_tprindexed_table(vp, name, length, + exact, var_len, write_method, 1)) == NULL) + return NULL; + + return agent_management(vp, var_len, hardware->h_rchassis); +} + +static struct variable8 lldp_vars[] = { + /* Scalars */ + {LLDP_SNMP_TXINTERVAL, ASN_INTEGER, RONLY, agent_h_scalars, 3, {1, 1, 1}}, + {LLDP_SNMP_TXMULTIPLIER, ASN_INTEGER, RONLY, agent_h_scalars, 3, {1, 1, 2}}, + {LLDP_SNMP_REINITDELAY, ASN_INTEGER, RONLY, agent_h_scalars, 3, {1, 1, 3}}, + {LLDP_SNMP_TXDELAY, ASN_INTEGER, RONLY, agent_h_scalars, 3, {1, 1, 4}}, + {LLDP_SNMP_NOTIFICATION, ASN_INTEGER, RONLY, agent_h_scalars, 3, {1, 1, 5}}, + {LLDP_SNMP_LASTUPDATE, ASN_TIMETICKS, RONLY, agent_h_scalars, 3, {1, 2, 1}}, + {LLDP_SNMP_STATS_INSERTS, ASN_GAUGE, RONLY, agent_h_scalars, 3, {1, 2, 2}}, + {LLDP_SNMP_STATS_DELETES, ASN_GAUGE, RONLY, agent_h_scalars, 3, {1, 2, 3}}, + {LLDP_SNMP_STATS_DROPS, ASN_GAUGE, RONLY, agent_h_scalars, 3, {1, 2, 4}}, + {LLDP_SNMP_STATS_AGEOUTS, ASN_GAUGE, RONLY, agent_h_scalars, 3, {1, 2, 5}}, + /* Local chassis */ + {LLDP_SNMP_LOCAL_CIDSUBTYPE, ASN_INTEGER, RONLY, agent_h_local_chassis, 3, {1, 3, 1}}, + {LLDP_SNMP_LOCAL_CID, ASN_OCTET_STR, RONLY, agent_h_local_chassis, 3, {1, 3, 2}}, + {LLDP_SNMP_LOCAL_SYSNAME, ASN_OCTET_STR, RONLY, agent_h_local_chassis, 3, {1, 3, 3}}, + {LLDP_SNMP_LOCAL_SYSDESCR, ASN_OCTET_STR, RONLY, agent_h_local_chassis, 3, {1, 3, 4}}, + {LLDP_SNMP_LOCAL_SYSCAP_SUP, ASN_OCTET_STR, RONLY, agent_h_local_chassis, 3, {1, 3, 5}}, + {LLDP_SNMP_LOCAL_SYSCAP_ENA, ASN_OCTET_STR, RONLY, agent_h_local_chassis, 3, {1, 3, 6}}, + /* Stats */ + {LLDP_SNMP_STATS_TX, ASN_COUNTER, RONLY, agent_h_stats, 5, {1, 2, 6, 1, 2}}, + {LLDP_SNMP_STATS_RX_DISCARDED, ASN_COUNTER, RONLY, agent_h_stats, 5, {1, 2, 7, 1, 2}}, + {LLDP_SNMP_STATS_RX_ERRORS, ASN_COUNTER, RONLY, agent_h_stats, 5, {1, 2, 7, 1, 3}}, + {LLDP_SNMP_STATS_RX, ASN_COUNTER, RONLY, agent_h_stats, 5, {1, 2, 7, 1, 4}}, + {LLDP_SNMP_STATS_RX_TLVDISCARDED, ASN_COUNTER, RONLY, agent_h_stats, 5, {1, 2, 7, 1, 5}}, + {LLDP_SNMP_STATS_RX_TLVUNRECOGNIZED, ASN_COUNTER, RONLY, agent_h_stats, 5, {1, 2, 7, 1, 6}}, + {LLDP_SNMP_STATS_RX_AGEOUTS, ASN_GAUGE, RONLY, agent_h_stats, 5, {1, 2, 7, 1, 7}}, + /* Local ports */ + {LLDP_SNMP_LOCAL_PIDSUBTYPE, ASN_INTEGER, RONLY, agent_h_local_port, 5, {1, 3, 7, 1, 2}}, + {LLDP_SNMP_LOCAL_PID, ASN_OCTET_STR, RONLY, agent_h_local_port, 5, {1, 3, 7, 1, 3}}, + {LLDP_SNMP_LOCAL_PORTDESC, ASN_OCTET_STR, RONLY, agent_h_local_port, 5, {1, 3, 7, 1, 4}}, + {LLDP_SNMP_LOCAL_DOT3_AUTONEG_SUPPORT, ASN_INTEGER, RONLY, agent_h_local_port, 8, + {1, 5, 4623, 1, 2, 1, 1, 1}}, + {LLDP_SNMP_LOCAL_DOT3_AUTONEG_ENABLED, ASN_INTEGER, RONLY, agent_h_local_port, 8, + {1, 5, 4623, 1, 2, 1, 1, 2}}, + {LLDP_SNMP_LOCAL_DOT3_AUTONEG_ADVERTISED, ASN_OCTET_STR, RONLY, agent_h_local_port, 8, + {1, 5, 4623, 1, 2, 1, 1, 3}}, + {LLDP_SNMP_LOCAL_DOT3_AUTONEG_MAU, ASN_INTEGER, RONLY, agent_h_local_port, 8, + {1, 5, 4623, 1, 2, 1, 1, 4}}, + {LLDP_SNMP_LOCAL_DOT3_AGG_STATUS, ASN_OCTET_STR, RONLY, agent_h_local_port, 8, + {1, 5, 4623, 1, 2, 3, 1, 1}}, + {LLDP_SNMP_LOCAL_DOT3_AGG_ID, ASN_INTEGER, RONLY, agent_h_local_port, 8, + {1, 5, 4623, 1, 2, 3, 1, 2}}, + /* Remote ports */ + {LLDP_SNMP_REMOTE_CIDSUBTYPE, ASN_INTEGER, RONLY, agent_h_remote_port, 5, {1, 4, 1, 1, 4}}, + {LLDP_SNMP_REMOTE_CID, ASN_OCTET_STR, RONLY, agent_h_remote_port, 5, {1, 4, 1, 1, 5}}, + {LLDP_SNMP_REMOTE_PIDSUBTYPE, ASN_INTEGER, RONLY, agent_h_remote_port, 5, {1, 4, 1, 1, 6}}, + {LLDP_SNMP_REMOTE_PID, ASN_OCTET_STR, RONLY, agent_h_remote_port, 5, {1, 4, 1, 1, 7}}, + {LLDP_SNMP_REMOTE_PORTDESC, ASN_OCTET_STR, RONLY, agent_h_remote_port, 5, {1, 4, 1, 1, 8}}, + {LLDP_SNMP_REMOTE_SYSNAME, ASN_OCTET_STR, RONLY, agent_h_remote_port, 5, {1, 4, 1, 1, 9}}, + {LLDP_SNMP_REMOTE_SYSDESC, ASN_OCTET_STR, RONLY, agent_h_remote_port, 5, {1, 4, 1, 1, 10}}, + {LLDP_SNMP_REMOTE_SYSCAP_SUP, ASN_OCTET_STR, RONLY, agent_h_remote_port, 5, {1, 4, 1, 1, 11}}, + {LLDP_SNMP_REMOTE_SYSCAP_ENA, ASN_OCTET_STR, RONLY, agent_h_remote_port, 5, {1, 4, 1, 1, 12}}, + {LLDP_SNMP_REMOTE_DOT3_AUTONEG_SUPPORT, ASN_INTEGER, RONLY, agent_h_remote_port, 8, + {1, 5, 4623, 1, 3, 1, 1, 1}}, + {LLDP_SNMP_REMOTE_DOT3_AUTONEG_ENABLED, ASN_INTEGER, RONLY, agent_h_remote_port, 8, + {1, 5, 4623, 1, 3, 1, 1, 2}}, + {LLDP_SNMP_REMOTE_DOT3_AUTONEG_ADVERTISED, ASN_OCTET_STR, RONLY, agent_h_remote_port, 8, + {1, 5, 4623, 1, 3, 1, 1, 3}}, + {LLDP_SNMP_REMOTE_DOT3_AUTONEG_MAU, ASN_INTEGER, RONLY, agent_h_remote_port, 8, + {1, 5, 4623, 1, 3, 1, 1, 4}}, + {LLDP_SNMP_REMOTE_DOT3_AGG_STATUS, ASN_OCTET_STR, RONLY, agent_h_remote_port, 8, + {1, 5, 4623, 1, 3, 3, 1, 1}}, + {LLDP_SNMP_REMOTE_DOT3_AGG_ID, ASN_INTEGER, RONLY, agent_h_remote_port, 8, + {1, 5, 4623, 1, 3, 3, 1, 2}}, + /* Local vlans */ + {LLDP_SNMP_LOCAL_DOT1_VLANNAME, ASN_OCTET_STR, RONLY, agent_h_local_vlan, 8, + {1, 5, 32962, 1, 2, 3, 1, 2}}, + /* Remote vlans */ + {LLDP_SNMP_REMOTE_DOT1_VLANNAME, ASN_OCTET_STR, RONLY, agent_h_remote_vlan, 8, + {1, 5, 32962, 1, 3, 3, 1, 2}}, + /* Management address */ + {LLDP_SNMP_LOCAL_ADDR_LEN, ASN_INTEGER, RONLY, agent_h_local_management, 5, + {1, 3, 8, 1, 3}}, + {LLDP_SNMP_LOCAL_ADDR_IFSUBTYPE, ASN_INTEGER, RONLY, agent_h_local_management, 5, + {1, 3, 8, 1, 4}}, + {LLDP_SNMP_LOCAL_ADDR_IFID, ASN_INTEGER, RONLY, agent_h_local_management, 5, + {1, 3, 8, 1, 5}}, + {LLDP_SNMP_LOCAL_ADDR_OID, ASN_OBJECT_ID, RONLY, agent_h_local_management, 5, + {1, 3, 8, 1, 6}}, + {LLDP_SNMP_REMOTE_ADDR_IFSUBTYPE, ASN_INTEGER, RONLY, agent_h_remote_management, 5, + {1, 4, 2, 1, 3}}, + {LLDP_SNMP_REMOTE_ADDR_IFID, ASN_INTEGER, RONLY, agent_h_remote_management, 5, + {1, 4, 2, 1, 4}}, + {LLDP_SNMP_REMOTE_ADDR_OID, ASN_OBJECT_ID, RONLY, agent_h_remote_management, 5, + {1, 4, 2, 1, 5}}, +}; + +void +agent_init(struct lldpd *cfg, int debug) +{ + int rc; + extern char *__progname; + + LLOG_INFO("Enable SNMP subagent"); + netsnmp_enable_subagent(); + snmp_disable_log(); + if (debug) + snmp_enable_stderrlog(); + else + snmp_enable_syslog_ident(__progname, LOG_DAEMON); + + scfg = cfg; + + init_agent("lldpAgent"); + + REGISTER_MIB("lldp", lldp_vars, variable8, lldp_oid); + + init_snmp("lldpAgent"); + + if ((rc = register_sysORTable(lldp_oid, OID_LENGTH(lldp_oid), + "lldpMIB implementation by lldpd")) != 0) + LLOG_WARNX("Unable to register to sysORTable (%d)", rc); +} + +void +agent_shutdown() +{ + unregister_sysORTable(lldp_oid, OID_LENGTH(lldp_oid)); + snmp_shutdown("lldpAgent"); +} diff --git a/src/cdp.c b/src/cdp.c new file mode 100644 index 00000000..8d134890 --- /dev/null +++ b/src/cdp.c @@ -0,0 +1,454 @@ +/* + * Copyright (c) 2008 Vincent Bernat + * + * Permission to use, copy, modify, and 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 +#include + +int +cdp_send(struct lldpd *global, struct lldpd_chassis *chassis, + struct lldpd_hardware *hardware, int version) +{ + struct cdp_header ch; + struct ethllc llc; + const u_int8_t mcastaddr[] = CDP_MULTICAST_ADDR; + const u_int8_t llcorg[] = LLC_ORG_CISCO; + struct iovec *iov = NULL; + struct cdp_tlv_head device; + struct cdp_tlv_head port; + struct cdp_tlv_head soft; + struct cdp_tlv_head platform; + struct cdp_tlv_address_head ah; + struct cdp_tlv_address_one ao; + struct cdp_tlv_capabilities cap; + unsigned int c = -1, i, len; + +#define IOV_NEW \ + if ((iov = (struct iovec*)realloc(iov, (++c + 1) * \ + sizeof(struct iovec))) == NULL) \ + fatal(NULL); + + /* Ether + LLC */ + memset(&llc, 0, sizeof(llc)); + memcpy(&llc.ether.shost, &hardware->h_lladdr, + sizeof(llc.ether.shost)); + memcpy(&llc.ether.dhost, &mcastaddr, + sizeof(llc.ether.dhost)); + llc.dsap = llc.ssap = 0xaa; + llc.control = 0x03; + memcpy(llc.org, llcorg, sizeof(llc.org)); + llc.protoid = htons(LLC_PID_CDP); + IOV_NEW; + iov[c].iov_base = &llc; + iov[c].iov_len = sizeof(llc); + + /* CDP header */ + memset(&ch, 0, sizeof(ch)); + ch.version = version; + ch.ttl = chassis->c_ttl; + IOV_NEW; + iov[c].iov_base = &ch; + iov[c].iov_len = sizeof(struct cdp_header); + + /* Chassis ID */ + memset(&device, 0, sizeof(device)); + device.tlv_type = htons(CDP_TLV_CHASSIS); + device.tlv_len = htons(sizeof(device) + strlen(chassis->c_name)); + IOV_NEW; + iov[c].iov_base = &device; + iov[c].iov_len = sizeof(device); + IOV_NEW; + iov[c].iov_base = chassis->c_name; + iov[c].iov_len = strlen(chassis->c_name); + + /* Adresses */ + memset(&ah, 0, sizeof(ah)); + ah.head.tlv_type = htons(CDP_TLV_ADDRESSES); + ah.head.tlv_len = htons(sizeof(ah) + sizeof(ao)); + ah.nb = htonl(1); + IOV_NEW; + iov[c].iov_base = &ah; + iov[c].iov_len = sizeof(ah); + memset(&ao, 0, sizeof(ao)); + ao.ptype = 1; + ao.plen = 1; + ao.proto = CDP_ADDRESS_PROTO_IP; + ao.alen = htons(sizeof(struct in_addr)); + memcpy(&ao.addr, &chassis->c_mgmt, sizeof(struct in_addr)); + IOV_NEW; + iov[c].iov_base = &ao; + iov[c].iov_len = sizeof(ao); + + /* Port ID */ + memset(&port, 0, sizeof(port)); + port.tlv_type = htons(CDP_TLV_PORT); + port.tlv_len = htons(sizeof(port) + strlen(hardware->h_lport.p_descr)); + IOV_NEW; + iov[c].iov_base = &port; + iov[c].iov_len = sizeof(port); + IOV_NEW; + iov[c].iov_base = hardware->h_lport.p_descr; + iov[c].iov_len = strlen(hardware->h_lport.p_descr); + + /* Capaibilities */ + memset(&cap, 0, sizeof(cap)); + cap.head.tlv_type = htons(CDP_TLV_CAPABILITIES); + cap.head.tlv_len = htons(sizeof(cap)); + cap.cap = 0; + if (chassis->c_cap_enabled & LLDP_CAP_ROUTER) + cap.cap |= CDP_CAP_ROUTER; + if (chassis->c_cap_enabled & LLDP_CAP_BRIDGE) + cap.cap |= CDP_CAP_BRIDGE; + cap.cap = htonl(cap.cap); + IOV_NEW; + iov[c].iov_base = ∩ + iov[c].iov_len = sizeof(cap); + + /* Software version */ + memset(&soft, 0, sizeof(soft)); + soft.tlv_type = htons(CDP_TLV_SOFTWARE); + soft.tlv_len = htons(sizeof(soft) + strlen(chassis->c_descr)); + IOV_NEW; + iov[c].iov_base = &soft; + iov[c].iov_len = sizeof(soft); + IOV_NEW; + iov[c].iov_base = chassis->c_descr; + iov[c].iov_len = strlen(chassis->c_descr); + + /* Platform */ + memset(&platform, 0, sizeof(platform)); + platform.tlv_type = htons(CDP_TLV_PLATFORM); + platform.tlv_len = htons(sizeof(platform) + strlen("Linux")); + IOV_NEW; + iov[c].iov_base = &platform; + iov[c].iov_len = sizeof(platform); + IOV_NEW; + iov[c].iov_base = "Linux"; + iov[c].iov_len = strlen("Linux"); + + c++; + + /* Compute len and checksum */ + len = 0; + for (i = 0; i < c; i++) { + len += iov[i].iov_len; + } + len -= sizeof(struct ieee8023); + llc.ether.size = htons(len); + ch.checksum = iov_checksum(&iov[1], c - 1, 1); + + if (writev((hardware->h_raw_real > 0) ? hardware->h_raw_real : + hardware->h_raw, iov, c) == -1) { + LLOG_WARN("unable to send packet on real device for %s", + hardware->h_ifname); + free(iov); + return ENETDOWN; + } + + hardware->h_tx_cnt++; + + free(iov); + return 0; +} + +int +cdp_decode(struct lldpd *cfg, char *frame, int s, + struct lldpd_hardware *hardware, + struct lldpd_chassis **newchassis, struct lldpd_port **newport) +{ + struct lldpd_chassis *chassis; + struct lldpd_port *port; + struct ethllc *llc; + struct cdp_header *ch; + struct cdp_tlv_head *tlv; + struct cdp_tlv_address_head *ah; + struct cdp_tlv_address_one *ao; + struct iovec iov; + u_int16_t cksum; + char *software = NULL, *platform = NULL; + int software_len = 0, platform_len = 0; + const unsigned char cdpaddr[] = CDP_MULTICAST_ADDR; + int i, f, len, rlen; + + if ((chassis = calloc(1, sizeof(struct lldpd_chassis))) == NULL) { + LLOG_WARN("failed to allocate remote chassis"); + return -1; + } + if ((port = calloc(1, sizeof(struct lldpd_port))) == NULL) { + LLOG_WARN("failed to allocate remote port"); + free(chassis); + return -1; + } + TAILQ_INIT(&port->p_vlans); + + if (s < sizeof(struct ethllc) + sizeof(struct cdp_header)) { + LLOG_WARNX("too short frame received on %s", hardware->h_ifname); + goto malformed; + } + + llc = (struct ethllc *)frame; + if (memcmp(&llc->ether.dhost, cdpaddr, sizeof(cdpaddr)) != 0) { + LLOG_INFO("frame not targeted at CDP multicast address received on %s", + hardware->h_ifname); + goto malformed; + } + if (ntohs(llc->ether.size) > s - sizeof(struct ieee8023)) { + LLOG_WARNX("incorrect 802.3 frame size reported on %s", + hardware->h_ifname); + goto malformed; + } + if (llc->protoid != htons(LLC_PID_CDP)) { + LLOG_DEBUG("incorrect LLC protocol ID received on %s", + hardware->h_ifname); + goto malformed; + } + f = sizeof(struct ethllc); + ch = (struct cdp_header *)(frame + f); + if ((ch->version != 1) && (ch->version != 2)) { + LLOG_WARNX("incorrect CDP version (%d) for frame received on %s", + ch->version, hardware->h_ifname); + goto malformed; + } + chassis->c_ttl = ntohs(ch->ttl); + iov.iov_len = s - f; + iov.iov_base = frame + f; + cksum = iov_checksum(&iov, 1, 1); + /* An off-by-one error may happen. Just ignore it */ + if ((cksum != 0) && (cksum != 0xfffe)) { + LLOG_INFO("incorrect CDP checksum for frame received on %s (%d)", + hardware->h_ifname, cksum); + goto malformed; + } + + f += sizeof(struct cdp_header); + while (f < s) { + if (f + sizeof(struct cdp_tlv_head) > s) { + LLOG_WARNX("CDP TLV header is too large for " + "frame received on %s", + hardware->h_ifname); + goto malformed; + } + tlv = (struct cdp_tlv_head *)(frame + f); + len = ntohs(tlv->tlv_len) - sizeof(struct cdp_tlv_head); + if ((len < 0) || (f + sizeof(struct cdp_tlv_head) + len > s)) { + LLOG_WARNX("incorrect size in CDP TLV header for frame " + "received on %s", + hardware->h_ifname); + goto malformed; + } + switch (ntohs(tlv->tlv_type)) { + case CDP_TLV_CHASSIS: + f += sizeof(struct cdp_tlv_head); + if ((chassis->c_name = (char *)calloc(1, len + 1)) == NULL) { + LLOG_WARN("unable to allocate memory for chassis name"); + goto malformed; + } + memcpy(chassis->c_name, frame + f, len); + chassis->c_id_subtype = LLDP_CHASSISID_SUBTYPE_LOCAL; + if ((chassis->c_id = (char *)malloc(len)) == NULL) { + LLOG_WARN("unable to allocate memory for chassis ID"); + goto malformed; + } + memcpy(chassis->c_id, frame + f, len); + chassis->c_id_len = len; + f += len; + break; + case CDP_TLV_ADDRESSES: + if (len < 4) { + LLOG_WARNX("incorrect size in CDP TLV header for frame " + "received on %s", + hardware->h_ifname); + goto malformed; + } + ah = (struct cdp_tlv_address_head *)(frame + f); + f += sizeof(struct cdp_tlv_address_head); + len -= 4; + for (i = 0; i < ntohl(ah->nb); i++) { + if (len < sizeof(struct cdp_tlv_address_one) - + sizeof(struct in_addr)) { + LLOG_WARNX("incorrect size for address TLV in " + "frame received from %s", + hardware->h_ifname); + goto malformed; + } + ao = (struct cdp_tlv_address_one *)(frame + f); + rlen = 2 + ao->plen + 2 + ntohs(ao->alen); + if (len < rlen) { + LLOG_WARNX("incorrect address size in TLV " + "received from %s", + hardware->h_ifname); + goto malformed; + } + if ((ao->ptype == 1) && (ao->plen == 1) && + (ao->proto == CDP_ADDRESS_PROTO_IP) && + (ntohs(ao->alen) == sizeof(struct in_addr)) && + (chassis->c_mgmt.s_addr == INADDR_ANY)) + chassis->c_mgmt.s_addr = ao->addr.s_addr; + f += rlen; + len -= rlen; + } + if (len != 0) { + LLOG_WARNX("not enough addresses found in TLV " + "received from %s", + hardware->h_ifname); + goto malformed; + } + break; + case CDP_TLV_PORT: + f += sizeof(struct cdp_tlv_head); + if ((port->p_descr = (char *)calloc(1, len + 1)) == NULL) { + LLOG_WARN("unable to allocate memory for port description"); + goto malformed; + } + memcpy(port->p_descr, frame + f, len); + port->p_id_subtype = LLDP_PORTID_SUBTYPE_LLADDR; + if ((port->p_id = (char *)malloc(ETH_ALEN)) == NULL) { + LLOG_WARN("unable to allocate memory for port ID"); + goto malformed; + } + memcpy(port->p_id, llc->ether.shost, ETH_ALEN); + port->p_id_len = ETH_ALEN; + f += len; + break; + case CDP_TLV_CAPABILITIES: + f += sizeof(struct cdp_tlv_head); + if (len != 4) { + LLOG_WARNX("incorrect size for capabilities TLV " + "on frame received from %s", + hardware->h_ifname); + goto malformed; + } + if (ntohl(*(u_int32_t*)(frame + f)) & CDP_CAP_ROUTER) + chassis->c_cap_enabled |= LLDP_CAP_ROUTER; + if (ntohl(*(u_int32_t*)(frame + f)) & 0x0e) + chassis->c_cap_enabled |= LLDP_CAP_BRIDGE; + if (chassis->c_cap_enabled == 0) + chassis->c_cap_enabled = LLDP_CAP_STATION; + chassis->c_cap_available = chassis->c_cap_enabled; + f += 4; + break; + case CDP_TLV_SOFTWARE: + f += sizeof(struct cdp_tlv_head); + software_len = len; + software = (char *)(frame + f); + f += len; + break; + case CDP_TLV_PLATFORM: + f += sizeof(struct cdp_tlv_head); + platform_len = len; + platform = (char *)(frame + f); + f += len; + break; + default: + LLOG_DEBUG("unknown CDP TLV type (%d) received on %s", + ntohs(tlv->tlv_type), hardware->h_ifname); + f += sizeof(struct cdp_tlv_head) + len; + } + } + if (!software && platform) { + if ((chassis->c_descr = (char *)calloc(1, + platform_len + 1)) == NULL) { + LLOG_WARN("unable to allocate memory for chassis description"); + goto malformed; + } + memcpy(chassis->c_descr, platform, platform_len); + } else if (software && !platform) { + if ((chassis->c_descr = (char *)calloc(1, + software_len + 1)) == NULL) { + LLOG_WARN("unable to allocate memory for chassis description"); + goto malformed; + } + memcpy(chassis->c_descr, software, software_len); + } else if (software && platform) { +#define CONCAT_PLATFORM " running on\n" + if ((chassis->c_descr = (char *)calloc(1, + software_len + platform_len + + strlen(CONCAT_PLATFORM) + 1)) == NULL) { + LLOG_WARN("unable to allocate memory for chassis description"); + goto malformed; + } + memcpy(chassis->c_descr, platform, platform_len); + memcpy(chassis->c_descr + platform_len, + CONCAT_PLATFORM, strlen(CONCAT_PLATFORM)); + memcpy(chassis->c_descr + platform_len + strlen(CONCAT_PLATFORM), + software, software_len); + } + if ((chassis->c_id == NULL) || + (port->p_id == NULL) || + (chassis->c_name == NULL) || + (chassis->c_descr == NULL) || + (port->p_descr == NULL) || + (chassis->c_ttl == 0) || + (chassis->c_cap_enabled == 0)) { + LLOG_WARNX("some mandatory tlv are missing for frame received on %s", + hardware->h_ifname); + goto malformed; + } + *newchassis = chassis; + *newport = port; + return 1; + +malformed: + free(chassis->c_name); + free(chassis->c_id); + free(chassis->c_descr); + free(chassis); + free(port->p_id); + free(port->p_descr); + lldpd_vlan_cleanup(port); + free(port); + return -1; +} + +int +cdpv1_send(struct lldpd *global, struct lldpd_chassis *chassis, + struct lldpd_hardware *hardware) +{ + return cdp_send(global, chassis, hardware, 1); +} + +int +cdpv2_send(struct lldpd *global, struct lldpd_chassis *chassis, + struct lldpd_hardware *hardware) +{ + return cdp_send(global, chassis, hardware, 2); +} + +int +cdp_guess(char *frame, int len, int version) +{ + const u_int8_t mcastaddr[] = CDP_MULTICAST_ADDR; + struct cdp_header *ch; + if (len < sizeof(struct ethllc) + sizeof(struct cdp_header)) + return 0; + if (memcmp(frame, mcastaddr, ETH_ALEN) != 0) + return 0; + ch = (struct cdp_header *)(frame + sizeof(struct ethllc)); + return (ch->version == version); +} + +int +cdpv1_guess(char *frame, int len) +{ + return cdp_guess(frame, len, 1); +} + +int +cdpv2_guess(char *frame, int len) +{ + return cdp_guess(frame, len, 2); +} diff --git a/src/cdp.h b/src/cdp.h new file mode 100644 index 00000000..634655cf --- /dev/null +++ b/src/cdp.h @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2008 Vincent Bernat + * + * Permission to use, copy, modify, and 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. + */ + +#ifndef _CDP_H +#define _CDP_H + +#define CDP_MULTICAST_ADDR { \ + 0x01, 0x00, 0x0c, 0xcc, 0xcc, 0xcc \ +} +#define LLC_ORG_CISCO { 0x00, 0x00, 0x0c } +#define LLC_PID_CDP 0x2000 + +struct cdp_header { + u_int8_t version; + u_int8_t ttl; + u_int16_t checksum; +} __attribute__ ((__packed__)); + +struct cdp_tlv_head { + u_int16_t tlv_type; + u_int16_t tlv_len; +} __attribute__ ((__packed__)); + +enum { + CDP_TLV_CHASSIS = 1, + CDP_TLV_ADDRESSES = 2, + CDP_TLV_PORT = 3, + CDP_TLV_CAPABILITIES = 4, + CDP_TLV_SOFTWARE = 5, + CDP_TLV_PLATFORM = 6 +}; + +struct cdp_tlv_address_head { + struct cdp_tlv_head head; + u_int32_t nb; +} __attribute__ ((__packed__)); + +struct cdp_tlv_address_one { + u_int8_t ptype; /* Should be 1 */ + u_int8_t plen; /* Should be 1 */ +#define CDP_ADDRESS_PROTO_IP 0xcc + u_int8_t proto; /* 0xcc for IP */ + u_int16_t alen; /* Should be 4 */ + struct in_addr addr; +} __attribute__ ((__packed__)); + +struct cdp_tlv_capabilities { + struct cdp_tlv_head head; + u_int32_t cap; +} __attribute__ ((__packed__)); + +#define CDP_CAP_ROUTER 1 +#define CDP_CAP_BRIDGE 8 + +#endif /* _CDP_H */ + diff --git a/src/compat.h b/src/compat.h new file mode 100644 index 00000000..8e1528f9 --- /dev/null +++ b/src/compat.h @@ -0,0 +1,57 @@ +/* + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)queue.h 8.5 (Berkeley) 8/20/94 + */ + +#if !HAVE_DECL_TAILQ_FIRST +#define TAILQ_FIRST(head) ((head)->tqh_first) +#endif + +#if !HAVE_DECL_TAILQ_NEXT +#define TAILQ_NEXT(elm, field) ((elm)->field.tqe_next) +#endif + +#if !HAVE_DECL_TAILQ_FOREACH +#define TAILQ_FOREACH(var, head, field) \ + for ((var) = ((head)->tqh_first); \ + (var); \ + (var) = ((var)->field.tqe_next)) +#endif + +#if !HAVE_DECL_TAILQ_EMPTY +#define TAILQ_EMPTY(head) ((head)->tqh_first == NULL) +#endif + +#if !HAVE_DECL_ADVERTISED_2500BASEX_Full +#define ADVERTISED_2500baseX_Full (1 << 15) +#endif + +#if !HAVE_DECL_PACKET_ORIGDEV +#define PACKET_ORIGDEV 9 +#endif diff --git a/src/ctl.c b/src/ctl.c new file mode 100644 index 00000000..0c74d8d9 --- /dev/null +++ b/src/ctl.c @@ -0,0 +1,539 @@ +/* + * Copyright (c) 2008 Vincent Bernat + * + * Permission to use, copy, modify, and 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 +#include +#include +#include +#include + +int +ctl_create(struct lldpd *cfg, char *name) +{ + int s; + struct sockaddr_un su; + int rc; + + if ((s = socket(PF_UNIX, SOCK_STREAM, 0)) == -1) + return -1; + su.sun_family = AF_UNIX; + strlcpy(su.sun_path, name, UNIX_PATH_MAX); + if (bind(s, (struct sockaddr *)&su, sizeof(struct sockaddr_un)) == -1) { + rc = errno; close(s); errno = rc; + return -1; + } + if (listen(s, 5) == -1) { + rc = errno; close(s); errno = rc; + return -1; + } + TAILQ_INIT(&cfg->g_clients); + return s; +} + +int +ctl_connect(char *name) +{ + int s; + struct sockaddr_un su; + int rc; + + if ((s = socket(PF_UNIX, SOCK_STREAM, 0)) == -1) + return -1; + su.sun_family = AF_UNIX; + strlcpy(su.sun_path, name, UNIX_PATH_MAX); + if (connect(s, (struct sockaddr *)&su, sizeof(struct sockaddr_un)) == -1) { + rc = errno; + LLOG_WARN("unable to connect to socket " LLDPD_CTL_SOCKET); + errno = rc; return -1; + } + return s; +} + +int +ctl_accept(struct lldpd *cfg, int c) +{ + int s; + struct lldpd_client *lc; + if ((s = accept(c, NULL, NULL)) == -1) { + LLOG_WARN("unable to accept connection from socket"); + return -1; + } + if ((lc = (struct lldpd_client *)malloc(sizeof( + struct lldpd_client))) == NULL) { + LLOG_WARN("failed to allocate memory for new client"); + close(s); + return -1; + } + lc->fd = s; + TAILQ_INSERT_TAIL(&cfg->g_clients, lc, next); + return 1; +} + +void +ctl_msg_init(struct hmsg *t, enum hmsg_type type) +{ + t->hdr.type = type; + t->hdr.len = 0; + t->hdr.pid = getpid(); +} + +int +ctl_msg_send(int fd, struct hmsg *t) +{ + return write(fd, t, t->hdr.len + sizeof(struct hmsg_hdr)); +} + +int +ctl_msg_recv(int fd, struct hmsg *t) +{ + int n; + if ((n = read(fd, t, MAX_HMSGSIZE)) == -1) { + return -1; + } + if (n < sizeof(struct hmsg_hdr)) { + LLOG_WARNX("message received too short"); + errno = 0; + return -1; + } + if (n != sizeof(struct hmsg_hdr) + t->hdr.len) { + LLOG_WARNX("message from %d seems to be truncated (or too large)", + t->hdr.pid); + errno = 0; + return -1; + } + return 1; +} + +int +ctl_close(struct lldpd *cfg, int c) +{ + struct lldpd_client *client, *client_next; + for (client = TAILQ_FIRST(&cfg->g_clients); + client != NULL; + client = client_next) { + client_next = TAILQ_NEXT(client, next); + if (client->fd == c) { + close(client->fd); + TAILQ_REMOVE(&cfg->g_clients, client, next); + free(client); + return 1; + } + } + /* Not found */ + return -1; +} + +void +ctl_cleanup(int c, char *name) +{ + close(c); + if (unlink(name) == -1) + LLOG_WARN("unable to unlink %s", name); +} + +/* Packing/unpacking */ + +/* This structure is used to track memory allocation when unpacking */ +struct gc { + TAILQ_ENTRY(gc) next; + void *pointer; +}; +TAILQ_HEAD(gc_l, gc); + +typedef struct { char c; int16_t x; } st_int16; +typedef struct { char c; int32_t x; } st_int32; +typedef struct { char c; void *x; } st_void_p; + +#define INT16_ALIGN (sizeof(st_int16) - sizeof(int16_t)) +#define INT32_ALIGN (sizeof(st_int32) - sizeof(int32_t)) +#define VOID_P_ALIGN (sizeof(st_void_p) - sizeof(void *)) + +struct formatdef { + char format; + int size; + int alignment; + int (*pack)(struct hmsg*, void **, void *, + const struct formatdef *); + int (*unpack)(struct hmsg*, void **, void *, + const struct formatdef *, struct gc_l *); +}; + +/* void** is a pointer to a pointer to the end of struct hmsg*. It should be + * updated. void* is a pointer to the entity to pack */ + +int +_ctl_alloc_pointer(struct gc_l *pointers, void *pointer) +{ + struct gc *gpointer; + if (pointers != NULL) { + if ((gpointer = (struct gc *)calloc(1, + sizeof(struct gc))) == NULL) { + LLOG_WARN("unable to allocate memory for garbage collector"); + return -1; + } + gpointer->pointer = pointer; + TAILQ_INSERT_TAIL(pointers, gpointer, next); + } + return 0; +} + +void +_ctl_free_pointers(struct gc_l *pointers, int listonly) +{ + struct gc *pointer, *pointer_next; + for (pointer = TAILQ_FIRST(pointers); + pointer != NULL; + pointer = pointer_next) { + pointer_next = TAILQ_NEXT(pointer, next); + TAILQ_REMOVE(pointers, pointer, next); + if (!listonly) + free(pointer->pointer); + free(pointer); + } +} + +int +pack_copy(struct hmsg *h, void **p, void *s, + const struct formatdef *ct) +{ + if (h->hdr.len + ct->size > MAX_HMSGSIZE - sizeof(struct hmsg_hdr)) { + LLOG_WARNX("message became too large"); + return -1; + } + memcpy(*p, s, ct->size); + *p += ct->size; + h->hdr.len += ct->size; + return ct->size; +} + +int +unpack_copy(struct hmsg *h, void **p, void *s, + const struct formatdef *ct, struct gc_l *pointers) +{ + memcpy(s, *p, ct->size); + *p += ct->size; + return ct->size; +} + +int +pack_string(struct hmsg *h, void **p, void *s, + const struct formatdef *ct) +{ + int len = strlen(*(char**)s); + if (h->hdr.len + len + sizeof(int) > MAX_HMSGSIZE - + sizeof(struct hmsg_hdr)) { + LLOG_WARNX("message became too large"); + return -1; + } + memcpy(*p, &len, sizeof(int)); + *p += sizeof(int); + memcpy(*p, *(char **)s, len); + *p += len; + h->hdr.len += sizeof(int) + len; + return sizeof(int) + len; +} + +int +unpack_string(struct hmsg *h, void **p, void *s, + const struct formatdef *ct, struct gc_l *pointers) +{ + char *string; + int len = *(int*)*p; + *p += sizeof(int); + if ((string = (char *)calloc(1, len + 1)) == NULL) { + LLOG_WARNX("unable to allocate new string"); + return -1; + } + if (_ctl_alloc_pointer(pointers, string) == -1) { + free(string); + return -1; + } + memcpy(string, *p, len); + *p += len; + memcpy(s, &string, sizeof(char *)); + return sizeof(char*); +} + +int +pack_chars(struct hmsg *h, void **p, void *s, + const struct formatdef *ct) +{ + char *string; + int string_len; + string = *(char **)s; + s += sizeof(char *); + string_len = *(int *)s; + + if (h->hdr.len + string_len + sizeof(int) > MAX_HMSGSIZE - + sizeof(struct hmsg_hdr)) { + LLOG_WARNX("message became too large"); + return -1; + } + memcpy(*p, &string_len, sizeof(int)); + *p += sizeof(int); + memcpy(*p, string, string_len); + *p += string_len; + h->hdr.len += sizeof(int) + string_len; + return sizeof(int) + string_len; +} + +int +unpack_chars(struct hmsg *h, void **p, void *s, + const struct formatdef *ct, struct gc_l *pointers) +{ + char *string; + struct { + char *string; + int len; + } reals __attribute__ ((__packed__)); + int len = *(int*)*p; + *p += sizeof(int); + if ((string = (char *)malloc(len)) == NULL) { + LLOG_WARN("unable to allocate new string"); + return -1; + } + if (_ctl_alloc_pointer(pointers, string) == -1) { + free(string); + return -1; + } + memcpy(string, *p, len); + *p += len; + reals.string = string; + reals.len = len; + memcpy(s, &reals, sizeof(reals)); + return sizeof(char*); +} + +int +pack_zero(struct hmsg *h, void **p, void *s, + const struct formatdef *ct) +{ + if (h->hdr.len + ct->size > MAX_HMSGSIZE - sizeof(struct hmsg_hdr)) { + LLOG_WARNX("message became too large"); + return -1; + } + memset(*p, 0, ct->size); + *p += ct->size; + h->hdr.len += ct->size; + return ct->size; +} + +static struct formatdef conv_table[] = { + {'b', 1, 0, + pack_copy, unpack_copy}, + {'w', 2, INT16_ALIGN, + pack_copy, unpack_copy}, + {'l', 4, INT32_ALIGN, + pack_copy, unpack_copy}, + /* Null terminated string */ + {'s', sizeof(void*), VOID_P_ALIGN, + pack_string, unpack_string}, + /* Pointer (is packed with 0) */ + {'P', sizeof(void*), VOID_P_ALIGN, + pack_zero, unpack_copy}, + /* A list (same as pointer), should be at the beginning */ + {'L', sizeof(void*)*2, VOID_P_ALIGN, + pack_zero, unpack_copy}, + /* Non null terminated string, followed by an int for the size */ + {'C', sizeof(void*) + sizeof(int), VOID_P_ALIGN, + pack_chars, unpack_chars}, + {0} +}; + +/* Lists can be packed only if the "next" member is the first one of the + * structure! No check is done for this. */ +struct fakelist_m { + TAILQ_ENTRY(fakelist_m) next; + void *data; +}; +TAILQ_HEAD(fakelist_l, fakelist_m); + +int +ctl_msg_pack_structure(char *format, void *structure, unsigned int size, + struct hmsg *h, void **p) +{ + char *f; + struct formatdef *ce; + unsigned int csize = 0; + uintptr_t offset; + int maxalign = 0; + + for (f = format; *f != 0; f++) { + for (ce = conv_table; + ce->format != 0; + ce++) { + if (ce->format == *f) + break; + } + if (ce->format != *f) { + LLOG_WARNX("unknown format char %c in %s", *f, + format); + return -1; + } + if (ce->alignment != 0) { + maxalign = (maxalign>ce->alignment)?maxalign:ce->alignment; + offset = (uintptr_t)structure % ce->alignment; + if (offset != 0) { + structure = structure + + (ce->alignment - offset); + csize += ce->alignment - offset; + } + } + if (ce->pack(h, p, structure, ce) == -1) { + LLOG_WARNX("error while packing %c in %s", *f, + format); + return -1; + } + structure += ce->size; + csize += ce->size; + } + + /* End padding */ + if ((maxalign > 0) && (csize % maxalign != 0)) + csize = csize + maxalign - (csize % maxalign); + if (size != csize) { + LLOG_WARNX("size of structure does not match its " + "declaration (%d vs %d)", size, csize); + return -1; + } + return 0; +} + +int +_ctl_msg_unpack_structure(char *format, void *structure, unsigned int size, + struct hmsg *h, void **p, struct gc_l *pointers) +{ + char *f; + struct formatdef *ce; + unsigned int csize = 0; + uintptr_t offset; + int maxalign = 0; + + for (f = format; *f != 0; f++) { + for (ce = conv_table; + ce->format != 0; + ce++) { + if (ce->format == *f) + break; + } + if (ce->format != *f) { + LLOG_WARNX("unknown format char %c", *f); + return -1; + } + if (ce->alignment != 0) { + maxalign = (maxalign>ce->alignment)?maxalign:ce->alignment; + offset = (uintptr_t)structure % ce->alignment; + if (offset != 0) { + structure = structure + + (ce->alignment - offset); + csize += ce->alignment - offset; + } + } + csize += ce->size; + if (csize > size) { + LLOG_WARNX("size of structure is too small for given " + "format (%d vs %d)", size, csize); + return -1; + } + + if (ce->unpack(h, p, structure, ce, pointers) == -1) { + LLOG_WARNX("error while unpacking %c", *f); + return -1; + } + structure += ce->size; + } + + /* End padding */ + if ((maxalign > 0) && (csize % maxalign != 0)) + csize = csize + maxalign - (csize % maxalign); + if (size < csize) { + LLOG_WARNX("size of structure does not match its " + "declaration (%d vs %d)", size, csize); + return -1; + } + return 0; +} + +int +ctl_msg_unpack_structure(char *format, void *structure, unsigned int size, + struct hmsg *h, void **p) +{ + struct gc_l pointers; + int rc; + TAILQ_INIT(&pointers); + if ((rc = _ctl_msg_unpack_structure(format, structure, size, + h, p, &pointers)) == -1) { + LLOG_WARNX("unable to unpack structure, freeing"); + _ctl_free_pointers(&pointers, 0); + return -1; + } + _ctl_free_pointers(&pointers, 1); + return rc; +} + +int +ctl_msg_pack_list(char *format, void *list, unsigned int size, struct hmsg *h, void **p) +{ + struct fakelist_m *member; + struct fakelist_l *flist = (struct fakelist_l *)list; + TAILQ_FOREACH(member, flist, next) { + if (ctl_msg_pack_structure(format, member, size, h, p) == -1) { + LLOG_WARNX("error while packing list, aborting"); + return -1; + } + } + return 0; +} + +int +ctl_msg_unpack_list(char *format, void *list, unsigned int size, struct hmsg *h, void **p) +{ + struct fakelist_m *member, *member_next; + struct gc_l pointers; + struct fakelist_l *flist = (struct fakelist_l *)list; + if (format[0] != 'L') { + LLOG_WARNX("to unpack a list, format should start with 'L'"); + return -1; + } + TAILQ_INIT(flist); + TAILQ_INIT(&pointers); + while (*p - (void *)h - sizeof(struct hmsg_hdr) < h->hdr.len) { + if ((member = calloc(1, size)) == NULL) { + LLOG_WARN("unable to allocate memory for structure"); + return -1; + } + if (_ctl_msg_unpack_structure(format, member, size, + h, p, &pointers) == -1) { + LLOG_WARNX("unable to unpack list, aborting"); + free(member); + /* Free each list member */ + for (member = TAILQ_FIRST(flist); + member != NULL; + member = member_next) { + member_next = TAILQ_NEXT(member, next); + TAILQ_REMOVE(flist, member, next); + free(member); + } + _ctl_free_pointers(&pointers, 0); + return -1; + } + TAILQ_INSERT_TAIL(flist, member, next); + } + _ctl_free_pointers(&pointers, 1); + return 0; +} diff --git a/src/edp.c b/src/edp.c new file mode 100644 index 00000000..0275074d --- /dev/null +++ b/src/edp.c @@ -0,0 +1,463 @@ +/* + * Copyright (c) 2008 Vincent Bernat + * + * Permission to use, copy, modify, and 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 +#include +#include +#include + +int seq = 0; + +int +edp_send(struct lldpd *global, struct lldpd_chassis *chassis, + struct lldpd_hardware *hardware) +{ + struct edp_header eh; + struct ethllc llc; + const u_int8_t mcastaddr[] = EDP_MULTICAST_ADDR; + const u_int8_t llcorg[] = LLC_ORG_EXTREME; + struct iovec *iov = NULL; + struct edp_tlv_vlan *ovlan = NULL; + struct lldpd_vlan *vlan; + struct edp_tlv_head device; + struct edp_tlv_head null; + struct edp_tlv_info info; + u_int8_t edp_fakeversion[] = {7, 6, 4, 99}; + unsigned int i, c, v, len, state = 0; + /* Subsequent XXX can be replaced by other values. We place + them here to ensure the position of "" to be a bit + invariant with version changes. */ + char *deviceslot[] = { "eth", "veth", "XXX", "XXX", "XXX", "XXX", "XXX", "XXX", "", NULL }; + +#define IOV_NEW \ + if ((iov = (struct iovec*)realloc(iov, (++c + 1) * \ + sizeof(struct iovec))) == NULL) \ + fatal(NULL); + + while (state != 2) { + free(iov); iov = NULL; + free(ovlan); ovlan = NULL; + c = v = -1; + + /* Ether + LLC */ + memset(&llc, 0, sizeof(llc)); + memcpy(&llc.ether.shost, &hardware->h_lladdr, + sizeof(llc.ether.shost)); + memcpy(&llc.ether.dhost, &mcastaddr, + sizeof(llc.ether.dhost)); + llc.dsap = llc.ssap = 0xaa; + llc.control = 0x03; + memcpy(llc.org, llcorg, sizeof(llc.org)); + llc.protoid = htons(LLC_PID_EDP); + IOV_NEW; + iov[c].iov_base = &llc; + iov[c].iov_len = sizeof(llc); + + /* EDP header */ + memset(&eh, 0, sizeof(eh)); + eh.version = 1; + eh.sequence = htons(seq++); + if ((chassis->c_id_len != ETH_ALEN) || + (chassis->c_id_subtype != LLDP_CHASSISID_SUBTYPE_LLADDR)) { + LLOG_WARNX("local chassis does not use MAC address as chassis ID!?"); + return EINVAL; + } + memcpy(&eh.mac, chassis->c_id, ETH_ALEN); + IOV_NEW; + iov[c].iov_base = &eh; + iov[c].iov_len = sizeof(eh); + + switch (state) { + case 0: + /* Display TLV */ + memset(&device, 0, sizeof(device)); + device.tlv_marker = EDP_TLV_MARKER; + device.tlv_type = EDP_TLV_DISPLAY; + device.tlv_len = htons(sizeof(device) + strlen(chassis->c_name) + 1); + IOV_NEW; + iov[c].iov_base = &device; + iov[c].iov_len = sizeof(device); + IOV_NEW; + iov[c].iov_base = chassis->c_name; + iov[c].iov_len = strlen(chassis->c_name) + 1; + + /* Info TLV */ + memset(&info, 0, sizeof(info)); + info.head.tlv_marker = EDP_TLV_MARKER; + info.head.tlv_type = EDP_TLV_INFO; + info.head.tlv_len = htons(sizeof(info)); + for (i=0; deviceslot[i] != NULL; i++) { + if (strncmp(hardware->h_ifname, deviceslot[i], + strlen(deviceslot[i])) == 0) { + info.slot = htons(i); + info.port = htons(atoi(hardware->h_ifname + + strlen(deviceslot[i]))); + break; + } + } + if (info.port == -1) { + info.slot = htons(8); + info.port = htons(if_nametoindex(hardware->h_ifname)); + } + memcpy(info.version, edp_fakeversion, sizeof(info.version)); + info.connections[0] = info.connections[1] = 0xff; + IOV_NEW; + iov[c].iov_base = &info; + iov[c].iov_len = sizeof(info); + break; + case 1: + v = 0; + TAILQ_FOREACH(vlan, &hardware->h_lport.p_vlans, + v_entries) + v++; + if (v == 0) { + v = -1; + break; + } + if ((ovlan = (struct edp_tlv_vlan*)malloc( + v*sizeof(struct edp_tlv_vlan))) == NULL) { + LLOG_WARN("no room for vlans"); + v = -1; + } + TAILQ_FOREACH(vlan, &hardware->h_lport.p_vlans, + v_entries) { + v--; + memset(&ovlan[v], 0, sizeof(ovlan[v])); + ovlan[v].head.tlv_marker = EDP_TLV_MARKER; + ovlan[v].head.tlv_type = EDP_TLV_VLAN; + ovlan[v].head.tlv_len = htons(sizeof(ovlan[v]) + + strlen(vlan->v_name) + 1); + ovlan[v].vid = htons(vlan->v_vid); + IOV_NEW; + iov[c].iov_base = &ovlan[v]; + iov[c].iov_len = sizeof(ovlan[v]); + IOV_NEW; + iov[c].iov_base = vlan->v_name; + iov[c].iov_len = strlen(vlan->v_name) + 1; + } + break; + } + + if ((state == 1) && (v == -1)) /* No VLAN, no need to send another TLV */ + break; + + /* Null TLV */ + memset(&null, 0, sizeof(null)); + null.tlv_marker = EDP_TLV_MARKER; + null.tlv_type = EDP_TLV_NULL; + null.tlv_len = htons(sizeof(null)); + IOV_NEW; + iov[c].iov_base = &null; + iov[c].iov_len = sizeof(null); + + c++; + + /* Compute len and checksum */ + len = 0; + for (i = 0; i < c; i++) { + len += iov[i].iov_len; + } + len -= sizeof(struct ieee8023); + llc.ether.size = htons(len); + len = len + sizeof(struct ieee8023) - sizeof(struct ethllc); + eh.len = htons(len); + eh.checksum = iov_checksum(&iov[1], c - 1, 0); + + if (writev((hardware->h_raw_real > 0) ? hardware->h_raw_real : + hardware->h_raw, iov, c) == -1) { + LLOG_WARN("unable to send packet on real device for %s", + hardware->h_ifname); + free(ovlan); + free(iov); + return ENETDOWN; + } + + state++; + } + + hardware->h_tx_cnt++; + free(ovlan); + free(iov); + + return 0; +} + +int +edp_decode(struct lldpd *cfg, char *frame, int s, + struct lldpd_hardware *hardware, + struct lldpd_chassis **newchassis, struct lldpd_port **newport) +{ + struct lldpd_chassis *chassis; + struct lldpd_port *port; + struct ethllc *llc; + struct edp_header *eh; + struct edp_tlv_head *tlv; + struct edp_tlv_info *info; + struct edp_tlv_vlan *vlan; + struct lldpd_vlan *lvlan, *lvlan_next; + const unsigned char edpaddr[] = EDP_MULTICAST_ADDR; + struct iovec iov; + int f, len, gotend = 0, gotvlans = 0; + + if ((chassis = calloc(1, sizeof(struct lldpd_chassis))) == NULL) { + LLOG_WARN("failed to allocate remote chassis"); + return -1; + } + if ((port = calloc(1, sizeof(struct lldpd_port))) == NULL) { + LLOG_WARN("failed to allocate remote port"); + free(chassis); + return -1; + } + TAILQ_INIT(&port->p_vlans); + + if (s < sizeof(struct ethllc) + sizeof(struct edp_header)) { + LLOG_WARNX("too short frame received on %s", hardware->h_ifname); + goto malformed; + } + + llc = (struct ethllc *)frame; + if (memcmp(&llc->ether.dhost, edpaddr, sizeof(edpaddr)) != 0) { + LLOG_INFO("frame not targeted at EDP multicast address received on %s", + hardware->h_ifname); + goto malformed; + } + if (ntohs(llc->ether.size) > s - sizeof(struct ieee8023)) { + LLOG_WARNX("incorrect 802.3 frame size reported on %s", + hardware->h_ifname); + goto malformed; + } + if (llc->protoid != htons(LLC_PID_EDP)) { + LLOG_DEBUG("incorrect LLC protocol ID received on %s", + hardware->h_ifname); + goto malformed; + } + + f = sizeof(struct ethllc); + eh = (struct edp_header *)(frame + f); + if (eh->version != 1) { + LLOG_WARNX("incorrect EDP version (%d) for frame received on %s", + eh->version, hardware->h_ifname); + goto malformed; + } + if (eh->idtype != htons(0)) { + LLOG_WARNX("incorrect device id type for frame received on %s", + hardware->h_ifname); + goto malformed; + } + if (ntohs(eh->len) > s - f) { + LLOG_WARNX("incorrect size for EDP frame received on %s", + hardware->h_ifname); + goto malformed; + } + chassis->c_ttl = LLDPD_TTL; + chassis->c_id_subtype = LLDP_CHASSISID_SUBTYPE_LLADDR; + chassis->c_id_len = ETH_ALEN; + if ((chassis->c_id = (char *)malloc(ETH_ALEN)) == NULL) { + LLOG_WARN("unable to allocate memory for chassis ID"); + goto malformed; + } + memcpy(chassis->c_id, eh->mac, ETH_ALEN); + /* We ignore reserved bytes and sequence number */ + iov.iov_len = ntohs(eh->len); + iov.iov_base = frame + f; + if (iov_checksum(&iov, 1, 0) != 0) { + LLOG_WARNX("incorrect EDP checksum for frame received on %s", + hardware->h_ifname); + goto malformed; + } + + f += sizeof(struct edp_header); + while ((f < s) && !gotend) { + if (f + sizeof(struct edp_tlv_head) > s) { + LLOG_WARNX("EDP TLV header is too large for " + "frame received on %s", + hardware->h_ifname); + goto malformed; + } + tlv = (struct edp_tlv_head *)(frame + f); + len = ntohs(tlv->tlv_len) - sizeof(struct edp_tlv_head); + if ((len < 0) || (f + sizeof(struct edp_tlv_head) + len > s)) { + LLOG_DEBUG("incorrect size in EDP TLV header for frame " + "received on %s", + hardware->h_ifname); + /* Some poor old Extreme Summit are quite bogus */ + gotend = 1; + break; + } + f += sizeof(struct edp_tlv_head); + if (tlv->tlv_marker != EDP_TLV_MARKER) { + LLOG_WARNX("incorrect marker starting EDP TLV header for frame " + "received on %s", + hardware->h_ifname); + goto malformed; + } + switch (tlv->tlv_type) { + case EDP_TLV_INFO: + if (len != sizeof(struct edp_tlv_info) - + sizeof(struct edp_tlv_head)) { + LLOG_WARNX("wrong size for EDP TLV info for frame " + "received on %s (%d vs %d)", + hardware->h_ifname); + goto malformed; + } + info = (struct edp_tlv_info *)(frame + f - + sizeof(struct edp_tlv_head)); + port->p_id_subtype = LLDP_PORTID_SUBTYPE_IFNAME; + if (asprintf(&port->p_id, "%d/%d", + ntohs(info->slot) + 1, ntohs(info->port) + 1) == -1) { + LLOG_WARN("unable to allocate memory for " + "port ID"); + goto malformed; + } + port->p_id_len = strlen(port->p_id); + if (asprintf(&port->p_descr, "Slot %d / Port %d", + ntohs(info->slot) + 1, ntohs(info->port) + 1) == -1) { + LLOG_WARN("unable to allocate memory for " + "port description"); + goto malformed; + } + if (asprintf(&chassis->c_descr, + "EDP enabled device, version %d.%d.%d.%d", + info->version[0], info->version[1], + info->version[2], info->version[3]) == -1) { + LLOG_WARN("unable to allocate memory for " + "chassis description"); + goto malformed; + } + break; + case EDP_TLV_DISPLAY: + if ((chassis->c_name = (char *)calloc(1, len + 1)) == NULL) { + LLOG_WARN("unable to allocate memory for chassis " + "name"); + goto malformed; + } + /* TLV display contains a lot of garbage */ + strlcpy(chassis->c_name, frame + f, len); + break; + case EDP_TLV_NULL: + if (len != 0) { + LLOG_WARNX("null tlv with incorrect size in frame " + "received on %s", + hardware->h_ifname); + goto malformed; + } + if (f != s) + LLOG_DEBUG("extra data after edp frame on %s", + hardware->h_ifname); + gotend = 1; + break; + case EDP_TLV_VLAN: + if (len < sizeof(struct edp_tlv_vlan) - + sizeof(struct edp_tlv_head)) { + LLOG_WARNX("wrong size for EDP TLV vlan for frame " + "received on %s (%d vs %d)", + hardware->h_ifname); + goto malformed; + } + vlan = (struct edp_tlv_vlan *)(frame + f - + sizeof(struct edp_tlv_head)); + if ((lvlan = (struct lldpd_vlan *)calloc(1, + sizeof(struct lldpd_vlan))) == NULL) { + LLOG_WARN("unable to allocate vlan"); + goto malformed; + } + lvlan->v_vid = ntohs(vlan->vid); + if ((lvlan->v_name = (char *)calloc(1, len + 1 - + sizeof(struct edp_tlv_vlan) + + sizeof(struct edp_tlv_head))) == NULL) { + LLOG_WARN("unable to allocate vlan name"); + goto malformed; + } + strlcpy(lvlan->v_name, frame + f + sizeof(struct edp_tlv_vlan) - + sizeof(struct edp_tlv_head), len - + sizeof(struct edp_tlv_vlan) + + sizeof(struct edp_tlv_head)); + if (vlan->ip.s_addr != INADDR_ANY) { + if (chassis->c_mgmt.s_addr == INADDR_ANY) + chassis->c_mgmt.s_addr = vlan->ip.s_addr; + else + /* We need to guess the good one */ + if (cfg->g_mgmt_pattern != NULL) { + /* We can try to use this to prefer an address */ + char *ip; + ip = inet_ntoa(vlan->ip); + if (fnmatch(cfg->g_mgmt_pattern, + ip, 0) == 0) + chassis->c_mgmt.s_addr = vlan->ip.s_addr; + } + } + TAILQ_INSERT_TAIL(&port->p_vlans, + lvlan, v_entries); + gotvlans = 1; + break; + default: + LLOG_DEBUG("unknown EDP TLV type (%d) received on %s", + tlv->tlv_type, hardware->h_ifname); + } + f += len; + } + if ((chassis->c_id == NULL) || + (port->p_id == NULL) || + (chassis->c_name == NULL) || + (chassis->c_descr == NULL) || + (port->p_descr == NULL) || + (gotend == 0)) { + if (gotvlans && gotend) { + /* VLAN can be sent in a separate frames. We need to add + * those vlans to an existing chassis */ + if (hardware->h_rchassis && + (hardware->h_rchassis->c_id_subtype == chassis->c_id_subtype) && + (hardware->h_rchassis->c_id_len == chassis->c_id_len) && + (memcmp(hardware->h_rchassis->c_id, chassis->c_id, + chassis->c_id_len) == 0)) { + /* We attach the VLANs to current hardware */ + lldpd_vlan_cleanup(hardware->h_rport); + for (lvlan = TAILQ_FIRST(&port->p_vlans); + lvlan != NULL; + lvlan = lvlan_next) { + lvlan_next = TAILQ_NEXT(lvlan, v_entries); + TAILQ_REMOVE(&port->p_vlans, lvlan, v_entries); + TAILQ_INSERT_TAIL(&hardware->h_rport->p_vlans, + lvlan, v_entries); + } + /* And the IP address */ + hardware->h_rchassis->c_mgmt.s_addr = + chassis->c_mgmt.s_addr; + } + /* We discard the remaining frame */ + goto malformed; + } + LLOG_WARNX("some mandatory tlv are missing for frame received on %s", + hardware->h_ifname); + goto malformed; + } + *newchassis = chassis; + *newport = port; + return 1; + +malformed: + free(chassis->c_id); + free(chassis->c_descr); + free(chassis->c_name); + free(chassis); + free(port->p_id); + free(port->p_descr); + lldpd_vlan_cleanup(port); + free(port); + return -1; +} diff --git a/src/edp.h b/src/edp.h new file mode 100644 index 00000000..5e7aec28 --- /dev/null +++ b/src/edp.h @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2008 Vincent Bernat + * + * Permission to use, copy, modify, and 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. + */ + +#ifndef _EDP_H +#define _EDP_H + +#define EDP_MULTICAST_ADDR { \ + 0x00, 0xe0, 0x2b, 0x00, 0x00, 0x00 \ +} +#define LLC_ORG_EXTREME { 0x00, 0xe0, 0x2b } +#define LLC_PID_EDP 0x00bb + +#define EDP_TLV_MARKER 0x99 + +#include "llc.h" + +struct edp_header { + u_int8_t version; + u_int8_t reserved; + u_int16_t len; + u_int16_t checksum; + u_int16_t sequence; + u_int16_t idtype; /* Should be 0 for MAC */ + u_int8_t mac[ETH_ALEN]; +} __attribute__ ((__packed__)); + +struct edp_tlv_head { + u_int8_t tlv_marker; /* 0x99 */ + u_int8_t tlv_type; + u_int16_t tlv_len; +} __attribute__ ((__packed__)); + +enum { + EDP_TLV_NULL = 0, + EDP_TLV_DISPLAY = 1, + EDP_TLV_INFO = 2, + EDP_TLV_VLAN = 5, + EDP_TLV_ESRP = 8, +}; + +struct edp_tlv_info { + struct edp_tlv_head head; + u_int16_t slot; + u_int16_t port; + u_int16_t vchassis; + u_int8_t reserved[6]; + u_int8_t version[4]; + u_int8_t connections[16]; +} __attribute__ ((__packed__)); + +#define EDP_VLAN_HAS_IP (1 << 8) +struct edp_tlv_vlan { + struct edp_tlv_head head; + u_int8_t flags; + u_int8_t reserved1[1]; + u_int16_t vid; + u_int8_t reserved2[4]; + struct in_addr ip; +} __attribute__ ((__packed__)); + +#endif /* _EDP_H */ diff --git a/src/features.c b/src/features.c new file mode 100644 index 00000000..06b872d3 --- /dev/null +++ b/src/features.c @@ -0,0 +1,209 @@ +/* + * Copyright (c) 2008 Vincent Bernat + * + * Permission to use, copy, modify, and 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. + */ + +#define INCLUDE_LINUX_IF_H +#include "lldpd.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SYSFS_PATH_MAX 256 +#define SYSFS_CLASS_NET "/sys/class/net/" +#define MAX_PORTS 1024 + +/* net/if.h */ +extern unsigned int if_nametoindex (__const char *__ifname) __THROW; + +int +old_iface_is_bridge(struct lldpd *cfg, const char *name) +{ + struct ifreq ifr; + struct __bridge_info i; + unsigned long args[4] = { BRCTL_GET_BRIDGE_INFO, + (unsigned long) &i, 0, 0 }; + strlcpy(ifr.ifr_name, name, IFNAMSIZ); + ifr.ifr_data = (char *) &args; + + if (ioctl(cfg->g_sock, SIOCDEVPRIVATE, &ifr) < 0) + return 0; + return 1; +} + +int +iface_is_bridge(struct lldpd *cfg, const char *name) +{ + char path[SYSFS_PATH_MAX]; + int f; + + if ((snprintf(path, SYSFS_PATH_MAX, + SYSFS_CLASS_NET "%s/" SYSFS_BRIDGE_FDB, name)) >= SYSFS_PATH_MAX) + LLOG_WARNX("path truncated"); + if ((f = open(path, 0)) < 0) { + return old_iface_is_bridge(cfg, name); + } + close(f); + return 1; +} + +int +old_iface_is_bridged(struct lldpd *cfg, const char *name) +{ + int i; + int ifindex = if_nametoindex(name); + int ifindices[MAX_PORTS]; + unsigned long args[4] = { BRCTL_GET_PORT_LIST, + (unsigned long)ifindices, MAX_PORTS, 0 }; + struct ifreq ifr; + struct ifaddrs *ifap, *ifa; + + if (getifaddrs(&ifap) != 0) { + LLOG_WARN("unable to get interface list"); + return 0; + } + + for (ifa = ifap; ifa != NULL; ifa = ifa->ifa_next) { + memset(ifindices, 0, sizeof(ifindices)); + strncpy(ifr.ifr_name, ifa->ifa_name, IFNAMSIZ); + ifr.ifr_data = (char *)&args; + + if (ioctl(cfg->g_sock, SIOCDEVPRIVATE, &ifr) < 0) + /* Not a bridge */ + continue; + + for (i = 0; i < MAX_PORTS; i++) { + if (ifindices[i] == ifindex) { + freeifaddrs(ifap); + return 1; + } + } + } + + return 0; +} + +int +iface_is_bridged(struct lldpd *cfg, const char *name) +{ + char path[SYSFS_PATH_MAX]; + int f; + + if ((snprintf(path, SYSFS_PATH_MAX, + SYSFS_CLASS_NET "%s/" SYSFS_BRIDGE_PORT_ATTR, + name)) >= SYSFS_PATH_MAX) + LLOG_WARNX("path truncated"); + if ((f = open(path, 0)) < 0) { + return old_iface_is_bridged(cfg, name); + } + close(f); + return 1; +} + +int +iface_is_vlan(struct lldpd *cfg, const char *name) +{ + struct vlan_ioctl_args ifv; + memset(&ifv, 0, sizeof(ifv)); + ifv.cmd = GET_VLAN_REALDEV_NAME_CMD; + if ((strlcpy(ifv.device1, name, sizeof(ifv.device1))) >= + sizeof(ifv.device1)) + LLOG_WARNX("device name truncated"); + if (ioctl(cfg->g_sock, SIOCGIFVLAN, &ifv) >= 0) + return 1; + return 0; +} + +int +iface_is_wireless(struct lldpd *cfg, const char *name) +{ + struct iwreq iwr; + strlcpy(iwr.ifr_name, name, IFNAMSIZ); + if (ioctl(cfg->g_sock, SIOCGIWNAME, &iwr) >= 0) + return 1; + return 0; +} + +int +iface_is_bond(struct lldpd *cfg, const char *name) +{ + struct ifreq ifr; + struct ifbond ifb; + memset(&ifr, 0, sizeof(ifr)); + memset(&ifb, 0, sizeof(ifb)); + strlcpy(ifr.ifr_name, name, sizeof(ifr.ifr_name)); + ifr.ifr_data = &ifb; + if (ioctl(cfg->g_sock, SIOCBONDINFOQUERY, &ifr) >= 0) + return 1; + return 0; +} + +int +iface_is_bond_slave(struct lldpd *cfg, const char *slave, const char *master) +{ + struct ifreq ifr; + struct ifbond ifb; + struct ifslave ifs; + memset(&ifr, 0, sizeof(ifr)); + memset(&ifb, 0, sizeof(ifb)); + strlcpy(ifr.ifr_name, master, sizeof(ifr.ifr_name)); + ifr.ifr_data = &ifb; + if (ioctl(cfg->g_sock, SIOCBONDINFOQUERY, &ifr) >= 0) { + while (ifb.num_slaves--) { + memset(&ifr, 0, sizeof(ifr)); + memset(&ifs, 0, sizeof(ifs)); + strlcpy(ifr.ifr_name, master, sizeof(ifr.ifr_name)); + ifr.ifr_data = &ifs; + ifs.slave_id = ifb.num_slaves; + if ((ioctl(cfg->g_sock, SIOCBONDSLAVEINFOQUERY, &ifr) >= 0) && + (strncmp(ifs.slave_name, slave, sizeof(ifs.slave_name)) == 0)) + return 1; + } + } + return 0; +} + +int +iface_is_enslaved(struct lldpd *cfg, const char *name) +{ + struct ifaddrs *ifap, *ifa; + int master; + + if (getifaddrs(&ifap) != 0) { + LLOG_WARN("unable to get interface list"); + return -1; + } + for (ifa = ifap; ifa != NULL; ifa = ifa->ifa_next) { + if (iface_is_bond_slave(cfg, name, ifa->ifa_name)) { + master = if_nametoindex(ifa->ifa_name); + freeifaddrs(ifap); + return master; + } + } + freeifaddrs(ifap); + return -1; +} diff --git a/src/iov.c b/src/iov.c new file mode 100644 index 00000000..c78b6f96 --- /dev/null +++ b/src/iov.c @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2008 Vincent Bernat + * + * Permission to use, copy, modify, and 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" + +void +iov_dump(struct lldpd_frame **buffer, struct iovec *iov, int count) +{ + int i; + int len = 0; + void *p; + for (i = 0; i < count; i++) + len += iov[i].iov_len; + if ((*buffer = (struct lldpd_frame *)malloc(len + + sizeof(int))) == NULL) { + LLOG_WARN("unable to allocate buffer"); + return; + } + memcpy(*buffer, &len, sizeof(int)); + p = (*buffer)->frame; + for (i = 0; i < count; i++) { + memcpy(p, iov[i].iov_base, iov[i].iov_len); + p += iov[i].iov_len; + } +} + +u_int16_t +iov_checksum(struct iovec *iov, int count, int cisco) +{ + unsigned int sum = 0, v = 0; + int len, oddbyte = 0, i; + u_char *cp; + + /* We compute in network byte order */ + for (i = 0; i < count; i++) { + len = iov[i].iov_len; + cp = iov[i].iov_base; + if (oddbyte) { + sum += (v << 8) + *cp++; + len--; + } + while ((len -= 2) >= 0) { + sum += *cp++ << 8; + sum += *cp++; + } + if ((oddbyte = len & 1) != 0) + v = *cp; + } + /* The remaining byte seems to be handled oddly by Cisco. Any hint about + * this is welcome. */ + if (oddbyte) { + if (cisco) + sum += v; + else + sum += v << 8; + } + sum = (sum >> 16) + (sum & 0xffff); + sum += sum >> 16; + sum = ntohs(sum); + return (0xffff & ~sum); +} diff --git a/src/llc.h b/src/llc.h new file mode 100644 index 00000000..002ccf18 --- /dev/null +++ b/src/llc.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2008 Vincent Bernat + * + * Permission to use, copy, modify, and 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. + */ + +#ifndef _LLC_H +#define _LLC_H + +struct ieee8023 { + u_int8_t dhost[ETH_ALEN]; /* destination eth addr */ + u_int8_t shost[ETH_ALEN]; /* source ether addr */ + u_int16_t size; /* packet type ID field */ +} __attribute__ ((__packed__)); + +struct ethllc { + struct ieee8023 ether; + u_int8_t dsap; /* destination SAP */ + u_int8_t ssap; /* source SAP */ + u_int8_t control; /* LLC control field */ + u_int8_t org[3]; + u_int16_t protoid; +} __attribute__ ((__packed__)); + +#endif diff --git a/src/lldp.c b/src/lldp.c new file mode 100644 index 00000000..1ff5edeb --- /dev/null +++ b/src/lldp.c @@ -0,0 +1,584 @@ +/* + * Copyright (c) 2008 Vincent Bernat + * + * Permission to use, copy, modify, and 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 +#include +#include +#include +#include +#include +#include +#include + +int +lldp_send(struct lldpd *global, struct lldpd_chassis *chassis, + struct lldpd_hardware *hardware) +{ + struct ether_header eh; + const u_int8_t mcastaddr[] = LLDP_MULTICAST_ADDR; + const u_int8_t dot1[] = LLDP_TLV_ORG_DOT1; + const u_int8_t dot3[] = LLDP_TLV_ORG_DOT3; + struct iovec *iov = NULL; + struct lldp_vlan *ovlan = NULL; + struct lldp_id chid, pid; + struct lldp_ttl ttl; + struct lldp_end end; + struct lldp_string name; + struct lldp_string descr; + struct lldp_string str; + struct lldp_cap cap; + struct lldp_mgmt mgmt; + struct lldp_aggreg aggreg; + struct lldp_macphy macphy; + struct lldpd_vlan *vlan; + struct lldpd_port *port = &hardware->h_lport; + u_int c = -1, len = 0, v; + struct lldpd_frame *buffer; + +#define IOV_NEW \ + if ((iov = (struct iovec*)realloc(iov, (++c + 1) * \ + sizeof(struct iovec))) == NULL) \ + fatal(NULL); + + /* Ethernet header */ + memset(&eh, 0, sizeof(eh)); + memcpy(&eh.ether_shost, &hardware->h_lladdr, + sizeof(eh.ether_shost)); + memcpy(&eh.ether_dhost, &mcastaddr, + sizeof(eh.ether_dhost)); + eh.ether_type = htons(ETHERTYPE_LLDP); + IOV_NEW; + iov[c].iov_base = &eh; + iov[c].iov_len = sizeof(struct ether_header); + + /* Chassis ID */ + memset(&chid, 0, sizeof(chid)); + len = chassis->c_id_len + sizeof(chid); + chid.tlv_head.type_len = LLDP_TLV_HEAD(LLDP_TLV_CHASSIS_ID, + len - sizeof(struct lldp_tlv_head)); + chid.tlv_id_subtype = chassis->c_id_subtype; + IOV_NEW; + iov[c].iov_base = &chid; + iov[c].iov_len = sizeof(chid); + IOV_NEW; + iov[c].iov_base = chassis->c_id; + iov[c].iov_len = chassis->c_id_len; + + /* Port ID */ + memset(&pid, 0, sizeof(pid)); + len = port->p_id_len + sizeof(pid); + pid.tlv_head.type_len = LLDP_TLV_HEAD(LLDP_TLV_PORT_ID, + len - sizeof(struct lldp_tlv_head)); + pid.tlv_id_subtype = port->p_id_subtype; + IOV_NEW; + iov[c].iov_base = &pid; + iov[c].iov_len = sizeof(pid); + IOV_NEW; + iov[c].iov_base = port->p_id; + iov[c].iov_len = port->p_id_len; + + /* Time to live */ + memset(&ttl, 0, sizeof(ttl)); + len = sizeof(ttl); + ttl.tlv_head.type_len = LLDP_TLV_HEAD(LLDP_TLV_TTL, + len - sizeof(struct lldp_tlv_head)); + ttl.tlv_ttl = htons(chassis->c_ttl); + IOV_NEW; + iov[c].iov_base = &ttl; + iov[c].iov_len = sizeof(ttl); + + /* System name */ + memset(&name, 0, sizeof(name)); + len = sizeof(name) + strlen(chassis->c_name); + name.tlv_head.type_len = LLDP_TLV_HEAD(LLDP_TLV_SYSTEM_NAME, + len - sizeof(struct lldp_tlv_head)); + IOV_NEW; + iov[c].iov_base = &name; + iov[c].iov_len = sizeof(name); + IOV_NEW; + iov[c].iov_base = chassis->c_name; + iov[c].iov_len = strlen(chassis->c_name); + + /* System description */ + memset(&descr, 0, sizeof(descr)); + len = sizeof(descr) + strlen(chassis->c_descr); + descr.tlv_head.type_len = LLDP_TLV_HEAD(LLDP_TLV_SYSTEM_DESCR, + len - sizeof(struct lldp_tlv_head)); + IOV_NEW; + iov[c].iov_base = &descr; + iov[c].iov_len = sizeof(descr); + IOV_NEW; + iov[c].iov_base = chassis->c_descr; + iov[c].iov_len = strlen(chassis->c_descr); + + /* System capabilities */ + memset(&cap, 0, sizeof(cap)); + len = sizeof(cap); + cap.tlv_head.type_len = LLDP_TLV_HEAD(LLDP_TLV_SYSTEM_CAP, + len - sizeof(struct lldp_tlv_head)); + cap.tlv_cap_available = htons(chassis->c_cap_available); + cap.tlv_cap_enabled = htons(chassis->c_cap_enabled); + IOV_NEW; + iov[c].iov_base = ∩ + iov[c].iov_len = len; + + if (chassis->c_mgmt.s_addr != INADDR_ANY) { + /* Management address */ + memset(&mgmt, 0, sizeof(mgmt)); + len = sizeof(mgmt); + mgmt.tlv_head.type_len = LLDP_TLV_HEAD(LLDP_TLV_MGMT_ADDR, + len - sizeof(struct lldp_tlv_head)); + mgmt.mgmt_len = sizeof(struct in_addr) + sizeof(u_int8_t); + mgmt.mgmt_subtype = LLDP_MGMT_ADDR_IP4; + memcpy(&mgmt.mgmt_addr, &chassis->c_mgmt, + sizeof(struct in_addr)); + + /* Interface port type, OID */ + if (chassis->c_mgmt_if == 0) + mgmt.mgmt_iface_subtype = + LLDP_MGMT_IFACE_UNKNOWN; + else { + mgmt.mgmt_iface_subtype = + LLDP_MGMT_IFACE_IFINDEX; + mgmt.mgmt_iface_id = + htonl(chassis->c_mgmt_if); + } + IOV_NEW; + iov[c].iov_base = &mgmt; + iov[c].iov_len = len; + } + + /* Port description */ + memset(&str, 0, sizeof(str)); + len = sizeof(str) + strlen(port->p_descr); + str.tlv_head.type_len = LLDP_TLV_HEAD(LLDP_TLV_PORT_DESCR, + len - sizeof(struct lldp_tlv_head)); + IOV_NEW; + iov[c].iov_base = &str; + iov[c].iov_len = sizeof(str); + IOV_NEW; + iov[c].iov_base = port->p_descr; + iov[c].iov_len = strlen(port->p_descr); + + /* VLANs */ + v = 0; + TAILQ_FOREACH(vlan, &port->p_vlans, v_entries) + v++; + if ((v > 0) && + ((ovlan = (struct lldp_vlan*)malloc(v*sizeof(struct lldp_vlan))) == NULL)) + LLOG_WARN("no room for vlans"); + else { + TAILQ_FOREACH(vlan, &port->p_vlans, v_entries) { + v--; + memset(&ovlan[v], 0, sizeof(ovlan[v])); + ovlan[v].tlv_head.type_len = LLDP_TLV_HEAD(LLDP_TLV_ORG, + sizeof(ovlan[v].tlv_org_id) + + sizeof(ovlan[v].tlv_org_subtype) + sizeof(ovlan[v].vid) + + sizeof(ovlan[v].len) + strlen(vlan->v_name)); + memcpy(ovlan[v].tlv_org_id, dot1, sizeof(ovlan[v].tlv_org_id)); + ovlan[v].tlv_org_subtype = LLDP_TLV_DOT1_VLANNAME; + ovlan[v].vid = htons(vlan->v_vid); + ovlan[v].len = strlen(vlan->v_name); + IOV_NEW; + iov[c].iov_base = &ovlan[v]; + iov[c].iov_len = sizeof(ovlan[v]); + IOV_NEW; + iov[c].iov_base = vlan->v_name; + iov[c].iov_len = strlen(vlan->v_name); + } + } + + /* Aggregation status */ + memset(&aggreg, 0, sizeof(aggreg)); + aggreg.tlv_head.type_len = LLDP_TLV_HEAD(LLDP_TLV_ORG, + sizeof(aggreg.tlv_org_id) + + sizeof(aggreg.tlv_org_subtype) + + sizeof(aggreg.status) + sizeof(aggreg.id)); + memcpy(aggreg.tlv_org_id, dot3, sizeof(aggreg.tlv_org_id)); + aggreg.tlv_org_subtype = LLDP_TLV_DOT3_LA; + aggreg.status = (port->p_aggregid) ? 3:1; /* Bit 0 = capability ; Bit 1 = status */ + aggreg.id = htonl(port->p_aggregid); + IOV_NEW; + iov[c].iov_base = &aggreg; + iov[c].iov_len = sizeof(aggreg); + + /* MAC/PHY */ + memset(&macphy, 0, sizeof(macphy)); + macphy.tlv_head.type_len = LLDP_TLV_HEAD(LLDP_TLV_ORG, + sizeof(macphy.tlv_org_id) + + sizeof(macphy.tlv_org_subtype) + + sizeof(macphy.autoneg) + sizeof(macphy.advertised) + + sizeof(macphy.mau)); + memcpy(macphy.tlv_org_id, dot3, sizeof(macphy.tlv_org_id)); + macphy.tlv_org_subtype = LLDP_TLV_DOT3_MAC; + macphy.autoneg = port->p_autoneg_support | + (port->p_autoneg_enabled << 1); + macphy.advertised = htons(port->p_autoneg_advertised); + macphy.mau = htons(port->p_mau_type); + IOV_NEW; + iov[c].iov_base = &macphy; + iov[c].iov_len = sizeof(macphy); + + /* END */ + memset(&end, 0, sizeof(end)); + IOV_NEW; + iov[c].iov_base = &end; + iov[c].iov_len = sizeof(end); + + c++; + if (!global->g_multi || + (hardware->h_mode == LLDPD_MODE_ANY) || + (hardware->h_mode == LLDPD_MODE_LLDP)) { + + if (writev((hardware->h_raw_real > 0) ? hardware->h_raw_real : + hardware->h_raw, iov, c) == -1) { + LLOG_WARN("unable to send packet on real device for %s", + hardware->h_ifname); + free(iov); + free(ovlan); + return ENETDOWN; + } + + hardware->h_tx_cnt++; + } + + iov_dump(&buffer, iov, c); + free(iov); + free(ovlan); + if (buffer != NULL) { + + /* We assume that LLDP frame is the reference */ + if ((hardware->h_llastframe == NULL) || + (hardware->h_llastframe->size != buffer->size) || + (memcmp(hardware->h_llastframe->frame, buffer->frame, + buffer->size) != 0)) { + free(hardware->h_llastframe); + hardware->h_llastframe = buffer; + hardware->h_llastchange = time(NULL); + } else + free(buffer); + } + + return 0; +} + +int +lldp_decode(struct lldpd *cfg, char *frame, int s, + struct lldpd_hardware *hardware, + struct lldpd_chassis **newchassis, struct lldpd_port **newport) +{ + struct lldpd_chassis *chassis; + struct lldpd_port *port; + struct ether_header *ether; + const char lldpaddr[] = LLDP_MULTICAST_ADDR; + const char dot1[] = LLDP_TLV_ORG_DOT1; + const char dot3[] = LLDP_TLV_ORG_DOT3; + int f; /* Current position in frame */ + int size, type, subtype; /* TLV header */ + char *b; + int gotend = 0; + + if ((chassis = calloc(1, sizeof(struct lldpd_chassis))) == NULL) { + LLOG_WARN("failed to allocate remote chassis"); + return -1; + } + if ((port = calloc(1, sizeof(struct lldpd_port))) == NULL) { + LLOG_WARN("failed to allocate remote port"); + free(chassis); + return -1; + } + TAILQ_INIT(&port->p_vlans); + + if (s < sizeof(struct ether_header)) { + LLOG_WARNX("too short frame received on %s", hardware->h_ifname); + goto malformed; + } + ether = (struct ether_header *)frame; + if (memcmp(ether->ether_dhost, lldpaddr, sizeof(lldpaddr)) != 0) { + LLOG_INFO("frame not targeted at LLDP multicast address received on %s", + hardware->h_ifname); + goto malformed; + } + if (ETHERTYPE_LLDP != ntohs(ether->ether_type)) { + LLOG_INFO("non LLDP frame received on %s", + hardware->h_ifname); + goto malformed; + } + + f = sizeof(struct ether_header); + while ((f < s) && (!gotend)) { + if (f + 2 > s) { + LLOG_WARNX("tlv header too short received on %s", + hardware->h_ifname); + goto malformed; + } + size = ntohs(*(u_int16_t*)(frame + f)) & 0x1ff; + type = ntohs(*(u_int16_t*)(frame + f)) >> 9; + f += 2; + if (f + size > s) { + LLOG_WARNX("tlv header too short received on %s", + hardware->h_ifname); + goto malformed; + } + switch (type) { + case LLDP_TLV_END: + if (size != 0) { + LLOG_WARNX("lldp end received with size not null on %s", + hardware->h_ifname); + goto malformed; + } + if (f != s) + LLOG_DEBUG("extra data after lldp end on %s", + hardware->h_ifname); + gotend = 1; + break; + case LLDP_TLV_CHASSIS_ID: + case LLDP_TLV_PORT_ID: + if (size < 2) { + LLOG_WARNX("tlv id too small received on %s", + hardware->h_ifname); + goto malformed; + } + subtype = *(u_int8_t*)(frame + f); + f++; + if ((subtype == 0) || (subtype > 7)) { + LLOG_WARNX("unknown subtype for tlv id received on %s", + hardware->h_ifname); + goto malformed; + } + if ((b = (char *)calloc(1, size - 1)) == NULL) { + LLOG_WARN("unable to allocate memory for id tlv " + "received on %s", + hardware->h_ifname); + goto malformed; + } + memcpy(b, frame + f, size - 1); + if (type == LLDP_TLV_PORT_ID) { + port->p_id_subtype = subtype; + port->p_id = b; + port->p_id_len = size - 1; + } else { + chassis->c_id_subtype = subtype; + chassis->c_id = b; + chassis->c_id_len = size - 1; + } + f += size - 1; + break; + case LLDP_TLV_TTL: + if (size != 2) { + LLOG_WARNX("incorrect size for ttl tlv received on %s", + hardware->h_ifname); + goto malformed; + } + chassis->c_ttl = ntohs(*(u_int16_t*)(frame + f)); + f += 2; + break; + case LLDP_TLV_PORT_DESCR: + case LLDP_TLV_SYSTEM_NAME: + case LLDP_TLV_SYSTEM_DESCR: + if (size < 1) { + LLOG_WARNX("tlv string too short received on %s", + hardware->h_ifname); + goto malformed; + } + if ((b = (char *)calloc(1, size + 1)) == NULL) { + LLOG_WARN("unable to allocate memory for string tlv " + "received on %s", + hardware->h_ifname); + goto malformed; + } + memcpy(b, frame + f, size); + f += size; + if (type == LLDP_TLV_PORT_DESCR) + port->p_descr = b; + else if (type == LLDP_TLV_SYSTEM_NAME) + chassis->c_name = b; + else chassis->c_descr = b; + break; + case LLDP_TLV_SYSTEM_CAP: + if (size != 4) { + LLOG_WARNX("system cap tlv with incorrect size received on %s", + hardware->h_ifname); + goto malformed; + } + chassis->c_cap_available = ntohs(*(u_int16_t*)(frame + f)); + f += 2; + chassis->c_cap_enabled = ntohs(*(u_int16_t*)(frame + f)); + f += 2; + break; + case LLDP_TLV_MGMT_ADDR: + if (size < 11) { + LLOG_WARNX("too short management tlv received on %s", + hardware->h_ifname); + goto malformed; + } + if ((chassis->c_mgmt.s_addr == INADDR_ANY) && + (*(u_int8_t*)(frame + f) == 5) && + (*(u_int8_t*)(frame + f + 1) == 1)) { + /* We have an IPv4 address, we ignore anything else */ + memcpy(&chassis->c_mgmt, frame + f + 2, sizeof(struct in_addr)); + chassis->c_mgmt_if = 0; + /* We only handle ifIndex subtype */ + if (*(u_int8_t*)(frame + f + 6) == LLDP_MGMT_IFACE_IFINDEX) + chassis->c_mgmt_if = ntohl(*(u_int32_t*)(frame + f + 7)); + } + f += size; + break; + case LLDP_TLV_ORG: + if (size < 4) { + LLOG_WARNX("too short org tlv received on %s", + hardware->h_ifname); + goto malformed; + } + if (memcmp(dot1, frame + f, 3) == 0) { + /* Dot1 */ + if ((*(u_int8_t*)(frame + f + 3)) == + LLDP_TLV_DOT1_VLANNAME) { + struct lldpd_vlan *vlan; + int vlan_len; + + if ((size < 7) || + (size != 7 + *(u_int8_t*)(frame + f + 6))) { + LLOG_WARNX("too short vlan tlv received on %s", + hardware->h_ifname); + goto malformed; + } + f += 4; + if ((vlan = (struct lldpd_vlan *)calloc(1, + sizeof(struct lldpd_vlan))) == NULL) { + LLOG_WARN("unable to alloc vlan structure for " + "tlv received on %s", + hardware->h_ifname); + goto malformed; + } + vlan->v_vid = ntohs(*(u_int16_t*)(frame + f)); + f += 2; + vlan_len = *(u_int8_t*)(frame + f); + f += 1; + if ((vlan->v_name = + (char *)calloc(1, vlan_len + 1)) == NULL) { + LLOG_WARN("unable to alloc vlan name for " + "tlv received on %s", + hardware->h_ifname); + goto malformed; + } + memcpy(vlan->v_name, frame + f, + vlan_len); + TAILQ_INSERT_TAIL(&port->p_vlans, + vlan, v_entries); + f += vlan_len; + } else + /* Unknown Dot1 TLV, ignore it */ + f += size; + } else if (memcmp(dot3, frame + f, 3) == 0) { + /* Dot3 */ + subtype = *(u_int8_t*)(frame + f + 3); + switch (subtype) { + case LLDP_TLV_DOT3_MAC: + f += 4; + if (size != 9) { + LLOG_WARNX("too short mac/phy tlv " + "received on %s", + hardware->h_ifname); + goto malformed; + } + port->p_autoneg_support = + *(u_int8_t*)(frame + f) && 0x1; + port->p_autoneg_enabled = + *(u_int8_t*)(frame + f) && 0x2; + f += 1; + port->p_autoneg_advertised = + ntohs(*(u_int16_t*)(frame + f)); + f += 2; + port->p_mau_type = + ntohs(*(u_int16_t*)(frame + f)); + f += 2; + break; + case LLDP_TLV_DOT3_LA: + if (size != 9) { + LLOG_WARNX("too short aggreg tlv " + "received on %s", + hardware->h_ifname); + goto malformed; + } + port->p_aggregid = + ntohl(*(u_int32_t*)(frame + f + 5)); + f += 9; + break; + default: + /* Unknown Dot3 TLV, ignore it */ + f += size; + } + } else { + LLOG_WARNX("unknown org tlv received on %s", + hardware->h_ifname); + goto malformed; + } + break; + default: + LLOG_WARNX("unknown tlv (%d) received on %s", + type, hardware->h_ifname); + goto malformed; + } + } + + /* Some random check */ + if ((chassis->c_id == NULL) || + (port->p_id == NULL) || + (chassis->c_ttl == 0) || + (gotend == 0)) { + LLOG_WARNX("some mandatory tlv are missing for frame received on %s", + hardware->h_ifname); + goto malformed; + } +#define NOTRECEIVED "Not received" + if (chassis->c_name == NULL) { + if ((chassis->c_name = (char *)calloc(1, strlen(NOTRECEIVED) + 1)) == NULL) { + LLOG_WARNX("unable to allocate null chassis name"); + goto malformed; + } + memcpy(chassis->c_name, NOTRECEIVED, strlen(NOTRECEIVED)); + } + if (chassis->c_descr == NULL) { + if ((chassis->c_descr = (char *)calloc(1, strlen(NOTRECEIVED) + 1)) == NULL) { + LLOG_WARNX("unable to allocate null chassis description"); + goto malformed; + } + memcpy(chassis->c_descr, NOTRECEIVED, strlen(NOTRECEIVED)); + } + if (port->p_descr == NULL) { + if ((port->p_descr = (char *)calloc(1, strlen(NOTRECEIVED) + 1)) == NULL) { + LLOG_WARNX("unable to allocate null port description"); + goto malformed; + } + memcpy(port->p_descr, NOTRECEIVED, strlen(NOTRECEIVED)); + } + *newchassis = chassis; + *newport = port; + return 1; +malformed: + free(chassis->c_id); + free(chassis->c_name); + free(chassis->c_descr); + free(chassis); + free(port->p_id); + free(port->p_descr); + lldpd_vlan_cleanup(port); + free(port); + return -1; +} diff --git a/src/lldp.h b/src/lldp.h new file mode 100644 index 00000000..54ef45e0 --- /dev/null +++ b/src/lldp.h @@ -0,0 +1,233 @@ +/* + * Copyright (c) 2008 Vincent Bernat + * + * Permission to use, copy, modify, and 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. + */ + +#ifndef _LLDP_H +#define _LLDP_H + +/* Should be defined in net/ethertypes.h */ +#ifndef ETHERTYPE_LLDP +#define ETHERTYPE_LLDP 0x88cc +#endif + +#define LLDP_MULTICAST_ADDR { \ + 0x01, 0x80, 0xc2, 0x00, 0x00, 0x0e \ +} + +#define LLDP_TLV_HEAD(type, len) htons(((type) << 9) | (len)) +struct lldp_tlv_head { + u_int16_t type_len; +} __attribute__ ((__packed__)); + +enum { + LLDP_TLV_END = 0, + LLDP_TLV_CHASSIS_ID = 1, + LLDP_TLV_PORT_ID = 2, + LLDP_TLV_TTL = 3, + LLDP_TLV_PORT_DESCR = 4, + LLDP_TLV_SYSTEM_NAME = 5, + LLDP_TLV_SYSTEM_DESCR = 6, + LLDP_TLV_SYSTEM_CAP = 7, + LLDP_TLV_MGMT_ADDR = 8, + LLDP_TLV_ORG = 127 +}; + +#define LLDP_TLV_ORG_DOT1 {0x00, 0x80, 0xc2} +#define LLDP_TLV_ORG_DOT3 {0x00, 0x12, 0x0f} + +enum { + LLDP_TLV_DOT1_PVID = 1, + LLDP_TLV_DOT1_PPVID = 2, + LLDP_TLV_DOT1_VLANNAME = 3, + LLDP_TLV_DOT1_PI = 4 +}; + +enum { + LLDP_TLV_DOT3_MAC = 1, + LLDP_TLV_DOT3_POWER = 2, + LLDP_TLV_DOT3_LA = 3, + LLDP_TLV_DOT3_MFS = 4 +}; + +/* Chassis ID or Port ID */ +struct lldp_id { + struct lldp_tlv_head tlv_head; + u_int8_t tlv_id_subtype; +} __attribute__ ((__packed__)); + +enum { + LLDP_CHASSISID_SUBTYPE_CHASSIS = 1, + LLDP_CHASSISID_SUBTYPE_IFALIAS = 2, + LLDP_CHASSISID_SUBTYPE_PORT = 3, + LLDP_CHASSISID_SUBTYPE_LLADDR = 4, + LLDP_CHASSISID_SUBTYPE_ADDR = 5, + LLDP_CHASSISID_SUBTYPE_IFNAME = 6, + LLDP_CHASSISID_SUBTYPE_LOCAL = 7 +}; + +enum { + LLDP_PORTID_SUBTYPE_IFALIAS = 1, + LLDP_PORTID_SUBTYPE_PORT = 2, + LLDP_PORTID_SUBTYPE_LLADDR = 3, + LLDP_PORTID_SUBTYPE_ADDR = 4, + LLDP_PORTID_SUBTYPE_IFNAME = 5, + LLDP_PORTID_SUBTYPE_AGENTCID = 6, + LLDP_PORTID_SUBTYPE_LOCAL = 7 +}; + +struct lldp_ttl { + struct lldp_tlv_head tlv_head; + u_int16_t tlv_ttl; +} __attribute__ ((__packed__)); + +struct lldp_string { + struct lldp_tlv_head tlv_head; +} __attribute__ ((__packed__)); + +struct lldp_cap { + struct lldp_tlv_head tlv_head; + u_int16_t tlv_cap_available; + u_int16_t tlv_cap_enabled; +} __attribute__ ((__packed__)); + +/* Operational MAU Type field, from RFC 3636 */ +#define LLDP_DOT3_MAU_AUI 1 +#define LLDP_DOT3_MAU_10BASE5 2 +#define LLDP_DOT3_MAU_FOIRL 3 +#define LLDP_DOT3_MAU_10BASE2 4 +#define LLDP_DOT3_MAU_10BASET 5 +#define LLDP_DOT3_MAU_10BASEFP 6 +#define LLDP_DOT3_MAU_10BASEFB 7 +#define LLDP_DOT3_MAU_10BASEFL 8 +#define LLDP_DOT3_MAU_10BROAD36 9 +#define LLDP_DOT3_MAU_10BASETHD 10 +#define LLDP_DOT3_MAU_10BASETFD 11 +#define LLDP_DOT3_MAU_10BASEFLHD 12 +#define LLDP_DOT3_MAU_10BASEFLDF 13 +#define LLDP_DOT3_MAU_10BASET4 14 +#define LLDP_DOT3_MAU_100BASETXHD 15 +#define LLDP_DOT3_MAU_100BASETXFD 16 +#define LLDP_DOT3_MAU_100BASEFXHD 17 +#define LLDP_DOT3_MAU_100BASEFXFD 18 +#define LLDP_DOT3_MAU_100BASET2HD 19 +#define LLDP_DOT3_MAU_100BASET2DF 20 +#define LLDP_DOT3_MAU_1000BASEXHD 21 +#define LLDP_DOT3_MAU_1000BASEXFD 22 +#define LLDP_DOT3_MAU_1000BASELXHD 23 +#define LLDP_DOT3_MAU_1000BASELXFD 24 +#define LLDP_DOT3_MAU_1000BASESXHD 25 +#define LLDP_DOT3_MAU_1000BASESXFD 26 +#define LLDP_DOT3_MAU_1000BASECXHD 27 +#define LLDP_DOT3_MAU_1000BASECXFD 28 +#define LLDP_DOT3_MAU_1000BASETHD 29 +#define LLDP_DOT3_MAU_1000BASETFD 30 +#define LLDP_DOT3_MAU_10GIGBASEX 31 +#define LLDP_DOT3_MAU_10GIGBASELX4 32 +#define LLDP_DOT3_MAU_10GIGBASER 33 +#define LLDP_DOT3_MAU_10GIGBASEER 34 +#define LLDP_DOT3_MAU_10GIGBASELR 35 +#define LLDP_DOT3_MAU_10GIGBASESR 36 +#define LLDP_DOT3_MAU_10GIGBASEW 37 +#define LLDP_DOT3_MAU_10GIGBASEEW 38 +#define LLDP_DOT3_MAU_10GIGBASELW 39 +#define LLDP_DOT3_MAU_10GIGBASESW 40 + +/* PMD Auto-Negotiation Advertised Capability field, from RFC 3636 */ +#define LLDP_DOT3_LINK_AUTONEG_OTHER 0x8000 +#define LLDP_DOT3_LINK_AUTONEG_10BASE_T 0x4000 +#define LLDP_DOT3_LINK_AUTONEG_10BASET_FD 0x2000 +#define LLDP_DOT3_LINK_AUTONEG_100BASE_T4 0x1000 +#define LLDP_DOT3_LINK_AUTONEG_100BASE_TX 0x0800 +#define LLDP_DOT3_LINK_AUTONEG_100BASE_TXFD 0x0400 +#define LLDP_DOT3_LINK_AUTONEG_100BASE_T2 0x0200 +#define LLDP_DOT3_LINK_AUTONEG_100BASE_T2FD 0x0100 +#define LLDP_DOT3_LINK_AUTONEG_FDX_PAUSE 0x0080 +#define LLDP_DOT3_LINK_AUTONEG_FDX_APAUSE 0x0040 +#define LLDP_DOT3_LINK_AUTONEG_FDX_SPAUSE 0x0020 +#define LLDP_DOT3_LINK_AUTONEG_FDX_BPAUSE 0x0010 +#define LLDP_DOT3_LINK_AUTONEG_1000BASE_X 0x0008 +#define LLDP_DOT3_LINK_AUTONEG_1000BASE_XFD 0x0004 +#define LLDP_DOT3_LINK_AUTONEG_1000BASE_T 0x0002 +#define LLDP_DOT3_LINK_AUTONEG_1000BASE_TFD 0x0001 + +#define LLDP_CAP_OTHER 0x01 +#define LLDP_CAP_REPEATER 0x02 +#define LLDP_CAP_BRIDGE 0x04 +#define LLDP_CAP_WLAN 0x08 +#define LLDP_CAP_ROUTER 0x10 +#define LLDP_CAP_TELEPHONE 0x20 +#define LLDP_CAP_DOCSIS 0x40 +#define LLDP_CAP_STATION 0x80 + +/* see http://www.iana.org/assignments/address-family-numbers */ +enum { + LLDP_MGMT_ADDR_IP4 = 1, + LLDP_MGMT_ADDR_IP6 = 2 +}; + +enum { + LLDP_MGMT_IFACE_UNKNOWN = 1, + LLDP_MGMT_IFACE_IFINDEX = 2, + LLDP_MGMT_IFACE_SYSPORT = 3 +}; + +/* Supports only IPv4 */ +struct lldp_mgmt { + struct lldp_tlv_head tlv_head; + u_int8_t mgmt_len; + u_int8_t mgmt_subtype; /* Should be 1 */ + struct in_addr mgmt_addr; + u_int8_t mgmt_iface_subtype; + u_int32_t mgmt_iface_id; + u_int8_t mgmt_oid_len; + u_int8_t mgmt_oid[0]; +} __attribute__ ((__packed__)); + +struct lldp_org { + struct lldp_tlv_head tlv_head; + u_int8_t tlv_org_id[3]; + u_int8_t tlv_org_subtype; +} __attribute__ ((__packed__)); + +struct lldp_vlan { + struct lldp_tlv_head tlv_head; + u_int8_t tlv_org_id[3]; + u_int8_t tlv_org_subtype; + u_int16_t vid; + u_int8_t len; +} __attribute__ ((__packed__)); + +struct lldp_aggreg { + struct lldp_tlv_head tlv_head; + u_int8_t tlv_org_id[3]; + u_int8_t tlv_org_subtype; + u_int8_t status; + u_int32_t id; +} __attribute__ ((__packed__)); + +struct lldp_macphy { + struct lldp_tlv_head tlv_head; + u_int8_t tlv_org_id[3]; + u_int8_t tlv_org_subtype; + u_int8_t autoneg; + u_int16_t advertised; + u_int16_t mau; +} __attribute__ ((__packed__)); + +struct lldp_end { + struct lldp_tlv_head tlv_head; +} __attribute__ ((__packed__)); + +#endif /* _LLDP_H */ diff --git a/src/lldpctl.c b/src/lldpctl.c new file mode 100644 index 00000000..4279df79 --- /dev/null +++ b/src/lldpctl.c @@ -0,0 +1,457 @@ +/* + * Copyright (c) 2008 Vincent Bernat + * + * Permission to use, copy, modify, and 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 +#include +#include +#include +#include +#include +#include + +void usage(void); + +TAILQ_HEAD(interfaces, lldpd_interface); +TAILQ_HEAD(vlans, lldpd_vlan); + +struct value_string { + int value; + char *string; +}; + +static const struct value_string operational_mau_type_values[] = { + { 1, "AUI - no internal MAU, view from AUI" }, + { 2, "10Base5 - thick coax MAU" }, + { 3, "Foirl - FOIRL MAU" }, + { 4, "10Base2 - thin coax MAU" }, + { 5, "10BaseT - UTP MAU" }, + { 6, "10BaseFP - passive fiber MAU" }, + { 7, "10BaseFB - sync fiber MAU" }, + { 8, "10BaseFL - async fiber MAU" }, + { 9, "10Broad36 - broadband DTE MAU" }, + { 10, "10BaseTHD - UTP MAU, half duplex mode" }, + { 11, "10BaseTFD - UTP MAU, full duplex mode" }, + { 12, "10BaseFLHD - async fiber MAU, half duplex mode" }, + { 13, "10BaseFLDF - async fiber MAU, full duplex mode" }, + { 14, "10BaseT4 - 4 pair category 3 UTP" }, + { 15, "100BaseTXHD - 2 pair category 5 UTP, half duplex mode" }, + { 16, "100BaseTXFD - 2 pair category 5 UTP, full duplex mode" }, + { 17, "100BaseFXHD - X fiber over PMT, half duplex mode" }, + { 18, "100BaseFXFD - X fiber over PMT, full duplex mode" }, + { 19, "100BaseT2HD - 2 pair category 3 UTP, half duplex mode" }, + { 20, "100BaseT2DF - 2 pair category 3 UTP, full duplex mode" }, + { 21, "1000BaseXHD - PCS/PMA, unknown PMD, half duplex mode" }, + { 22, "1000BaseXFD - PCS/PMA, unknown PMD, full duplex mode" }, + { 23, "1000BaseLXHD - Fiber over long-wavelength laser, half duplex mode" }, + { 24, "1000BaseLXFD - Fiber over long-wavelength laser, full duplex mode" }, + { 25, "1000BaseSXHD - Fiber over short-wavelength laser, half duplex mode" }, + { 26, "1000BaseSXFD - Fiber over short-wavelength laser, full duplex mode" }, + { 27, "1000BaseCXHD - Copper over 150-Ohm balanced cable, half duplex mode" }, + { 28, "1000BaseCXFD - Copper over 150-Ohm balanced cable, full duplex mode" }, + { 29, "1000BaseTHD - Four-pair Category 5 UTP, half duplex mode" }, + { 30, "1000BaseTFD - Four-pair Category 5 UTP, full duplex mode" }, + { 31, "10GigBaseX - X PCS/PMA, unknown PMD." }, + { 32, "10GigBaseLX4 - X fiber over WWDM optics" }, + { 33, "10GigBaseR - R PCS/PMA, unknown PMD." }, + { 34, "10GigBaseER - R fiber over 1550 nm optics" }, + { 35, "10GigBaseLR - R fiber over 1310 nm optics" }, + { 36, "10GigBaseSR - R fiber over 850 nm optics" }, + { 37, "10GigBaseW - W PCS/PMA, unknown PMD." }, + { 38, "10GigBaseEW - W fiber over 1550 nm optics" }, + { 39, "10GigBaseLW - W fiber over 1310 nm optics" }, + { 40, "10GigBaseSW - W fiber over 850 nm optics" }, + { 0, NULL } +}; + +void +usage(void) +{ + extern const char *__progname; + + fprintf(stderr, "usage: %s [-d]\n", __progname); + exit(1); +} + +static char* +dump(void *data, int size, int max, char sep) +{ + int i; + size_t len; + static char *buffer = NULL; + static char truncation[] = "[...]"; + + free(buffer); + if (size > max) + len = max * 3 + sizeof(truncation) + 1; + else + len = size * 3; + + if ((buffer = (char *)malloc(len)) == NULL) + fatal(NULL); + + for (i = 0; (i < size) && (i < max); i++) + sprintf(buffer + i * 3, "%02x%c", *(u_int8_t*)(data + i), sep); + if (size > max) + sprintf(buffer + i * 3, "%s", truncation); + else + *(buffer + i*3 - 1) = 0; + return buffer; +} + + +void +get_interfaces(int s, struct interfaces *ifs) +{ + void *p; + struct hmsg *h; + + if ((h = (struct hmsg *)malloc(MAX_HMSGSIZE)) == NULL) + fatal(NULL); + ctl_msg_init(h, HMSG_GET_INTERFACES); + if (ctl_msg_send(s, h) == -1) + fatalx("get_interfaces: unable to send request"); + if (ctl_msg_recv(s, h) == -1) + fatalx("get_interfaces: unable to receive answer"); + if (h->hdr.type != HMSG_GET_INTERFACES) + fatalx("get_interfaces: unknown answer type received"); + p = &h->data; + if (ctl_msg_unpack_list(STRUCT_LLDPD_INTERFACE, + ifs, sizeof(struct lldpd_interface), h, &p) == -1) + fatalx("get_interfaces: unable to retrieve the list of interfaces"); +} + +int +get_vlans(int s, struct vlans *vls, char *interface) +{ + void *p; + struct hmsg *h; + + if ((h = (struct hmsg *)malloc(MAX_HMSGSIZE)) == NULL) + fatal(NULL); + ctl_msg_init(h, HMSG_GET_VLANS); + h->hdr.len += strlcpy((char *)&h->data, interface, + MAX_HMSGSIZE - sizeof(struct hmsg_hdr)) + 1; + if (ctl_msg_send(s, h) == -1) + fatalx("get_vlans: unable to send request"); + if (ctl_msg_recv(s, h) == -1) + fatalx("get_vlans: unable to receive answer"); + if (h->hdr.type != HMSG_GET_VLANS) + fatalx("get_vlans: unknown answer type received"); + p = &h->data; + if (ctl_msg_unpack_list(STRUCT_LLDPD_VLAN, + vls, sizeof(struct lldpd_vlan), h, &p) == -1) + fatalx("get_vlans: unable to retrieve the list of vlans"); + return 1; +} + +int +get_chassis(int s, struct lldpd_chassis *chassis, char *interface) +{ + struct hmsg *h; + void *p; + + if ((h = (struct hmsg *)malloc(MAX_HMSGSIZE)) == NULL) + fatal(NULL); + ctl_msg_init(h, HMSG_GET_CHASSIS); + h->hdr.len += strlcpy((char *)&h->data, interface, + MAX_HMSGSIZE - sizeof(struct hmsg_hdr)) + 1; + if (ctl_msg_send(s, h) == -1) + fatalx("get_chassis: unable to send request to get chassis"); + if (ctl_msg_recv(s, h) == -1) + fatalx("get_chassis: unable to receive answer to get chassis"); + if (h->hdr.type == HMSG_NONE) + /* No chassis */ + return -1; + p = &h->data; + if (ctl_msg_unpack_structure(STRUCT_LLDPD_CHASSIS, + chassis, sizeof(struct lldpd_chassis), h, &p) == -1) { + LLOG_WARNX("unable to retrieve chassis for %s", interface); + fatalx("get_chassis: abort"); + } + return 1; +} + +int +get_port(int s, struct lldpd_port *port, char *interface) +{ + struct hmsg *h; + void *p; + + if ((h = (struct hmsg *)malloc(MAX_HMSGSIZE)) == NULL) + fatal(NULL); + ctl_msg_init(h, HMSG_GET_PORT); + h->hdr.len += strlcpy((char *)&h->data, interface, + MAX_HMSGSIZE - sizeof(struct hmsg_hdr)) + 1; + if (ctl_msg_send(s, h) == -1) + fatalx("get_port: unable to send request to get port"); + if (ctl_msg_recv(s, h) == -1) + fatalx("get_port: unable to receive answer to get port"); + if (h->hdr.type == HMSG_NONE) + /* No port */ + return -1; + p = &h->data; + if (ctl_msg_unpack_structure(STRUCT_LLDPD_PORT, + port, sizeof(struct lldpd_port), h, &p) == -1) { + LLOG_WARNX("unable to retrieve port information for %s", + interface); + fatalx("get_chassis: abort"); + } + return 1; +} + +void +display_cap(struct lldpd_chassis *chassis, u_int8_t bit, char *symbol) +{ + if (chassis->c_cap_available & bit) + printf("%s(%c) ", symbol, + (chassis->c_cap_enabled & bit)?'E':'d'); +} + +void +pretty_print(char *string) +{ + char *s = NULL; + if (((s = index(string, '\n')) == NULL) && (strlen(string) < 60)) { + printf("%s\n", string); + return; + } else + printf("\n"); + while (s != NULL) { + *s = '\0'; + printf(" %s\n", string); + *s = '\n'; + string = s + 1; + s = index(string, '\n'); + } + printf(" %s\n", string); +} + +void +display_chassis(struct lldpd_chassis *chassis) +{ + char *cid; + if ((cid = (char *)malloc(chassis->c_id_len + 1)) == NULL) + fatal(NULL); + memcpy(cid, chassis->c_id, chassis->c_id_len); + cid[chassis->c_id_len] = 0; + switch (chassis->c_id_subtype) { + case LLDP_CHASSISID_SUBTYPE_IFNAME: + printf(" ChassisID: %s (ifName)\n", cid); + break; + case LLDP_CHASSISID_SUBTYPE_IFALIAS: + printf(" ChassisID: %s (ifAlias)\n", cid); + break; + case LLDP_CHASSISID_SUBTYPE_LOCAL: + printf(" ChassisID: %s (local)\n", cid); + break; + case LLDP_CHASSISID_SUBTYPE_LLADDR: + printf(" ChassisID: %s (MAC)\n", + dump(chassis->c_id, chassis->c_id_len, ETH_ALEN, ':')); + break; + case LLDP_CHASSISID_SUBTYPE_ADDR: + if (*(u_int8_t*)chassis->c_id == 1) { + printf(" ChassisID: %s (IP)\n", + inet_ntoa(*(struct in_addr*)(chassis->c_id + + 1))); + break; + } + case LLDP_CHASSISID_SUBTYPE_PORT: + case LLDP_CHASSISID_SUBTYPE_CHASSIS: + default: + printf(" ChassisID: %s (unhandled type)\n", + dump(chassis->c_id, chassis->c_id_len, 16, ' ')); + } + printf(" SysName: %s\n", chassis->c_name); + printf(" SysDescr: "); pretty_print(chassis->c_descr); + printf(" MgmtIP: %s\n", inet_ntoa(chassis->c_mgmt)); + printf(" Caps: "); + display_cap(chassis, LLDP_CAP_OTHER, "Other"); + display_cap(chassis, LLDP_CAP_REPEATER, "Repeater"); + display_cap(chassis, LLDP_CAP_BRIDGE, "Bridge"); + display_cap(chassis, LLDP_CAP_WLAN, "Wlan"); + display_cap(chassis, LLDP_CAP_TELEPHONE, "Tel"); + display_cap(chassis, LLDP_CAP_DOCSIS, "Docsis"); + display_cap(chassis, LLDP_CAP_STATION, "Station"); + printf("\n"); +} + +void +display_autoneg(struct lldpd_port *port, int bithd, int bitfd, char *desc) +{ + if (!((port->p_autoneg_advertised & bithd) || + (port->p_autoneg_advertised & bitfd))) + return; + printf("%s ", desc); + if (port->p_autoneg_advertised & bithd) { + printf("(HD"); + if (port->p_autoneg_advertised & bitfd) { + printf(", FD) "); + return; + } + printf(") "); + return; + } + printf("(FD) "); +} + +void +display_port(struct lldpd_port *port) +{ + char *pid; + int i; + + if ((pid = (char *)malloc(port->p_id_len + 1)) == NULL) + fatal(NULL); + memcpy(pid, port->p_id, port->p_id_len); + pid[port->p_id_len] = 0; + switch (port->p_id_subtype) { + case LLDP_PORTID_SUBTYPE_IFNAME: + printf(" PortID: %s (ifName)\n", pid); + break; + case LLDP_PORTID_SUBTYPE_IFALIAS: + printf(" PortID: %s (ifAlias)\n", pid); + break; + case LLDP_PORTID_SUBTYPE_LOCAL: + printf(" PortID: %s (local)\n", pid); + break; + case LLDP_PORTID_SUBTYPE_LLADDR: + printf(" PortID: %s (MAC)\n", + dump(port->p_id, port->p_id_len, ETH_ALEN, ':')); + break; + case LLDP_PORTID_SUBTYPE_ADDR: + if (*(u_int8_t*)port->p_id == 1) { + printf(" PortID: %s (IP)\n", + inet_ntoa(*(struct in_addr*)(port->p_id + + 1))); + break; + } + case LLDP_PORTID_SUBTYPE_PORT: + case LLDP_PORTID_SUBTYPE_AGENTCID: + default: + printf(" ChassisID: %s (unhandled type)\n", + dump(port->p_id, port->p_id_len, 16, ' ')); + } + printf(" PortDescr: "); pretty_print(port->p_descr); + if (port->p_aggregid) + printf("\n Port is aggregated. PortAggregID: %d\n", + port->p_aggregid); + + printf("\n Autoneg: %ssupported/%senabled\n", + port->p_autoneg_support?"":"not ", + port->p_autoneg_enabled?"":"not "); + if (port->p_autoneg_enabled) { + printf(" PMD autoneg: "); + display_autoneg(port, LLDP_DOT3_LINK_AUTONEG_10BASE_T, + LLDP_DOT3_LINK_AUTONEG_10BASET_FD, + "10Base-T"); + display_autoneg(port, LLDP_DOT3_LINK_AUTONEG_100BASE_TX, + LLDP_DOT3_LINK_AUTONEG_100BASE_TXFD, + "100Base-T"); + display_autoneg(port, LLDP_DOT3_LINK_AUTONEG_100BASE_T2, + LLDP_DOT3_LINK_AUTONEG_100BASE_T2FD, + "100Base-T2"); + display_autoneg(port, LLDP_DOT3_LINK_AUTONEG_1000BASE_X, + LLDP_DOT3_LINK_AUTONEG_1000BASE_XFD, + "100Base-X"); + display_autoneg(port, LLDP_DOT3_LINK_AUTONEG_1000BASE_T, + LLDP_DOT3_LINK_AUTONEG_1000BASE_TFD, + "1000Base-T"); + printf("\n"); + } + printf(" MAU oper type: "); + for (i = 0; operational_mau_type_values[i].value != 0; i++) { + if (operational_mau_type_values[i].value == + port->p_mau_type) { + printf("%s\n", operational_mau_type_values[i].string); + break; + } + } + if (operational_mau_type_values[i].value == 0) + printf("unknown (%d)\n", port->p_mau_type); +} + +void +display_vlans(struct lldpd_port *port) +{ + int i = 0; + struct lldpd_vlan *vlan; + TAILQ_FOREACH(vlan, &port->p_vlans, v_entries) + printf(" VLAN %4d: %-20s%c", vlan->v_vid, vlan->v_name, + (i++ % 2) ? '\n' : ' '); + if (i % 2) + printf("\n"); +} + +int +main(int argc, char *argv[]) +{ + int s; + int ch, debug = 1; + struct interfaces ifs; + struct vlans vls; + struct lldpd_interface *iff; + struct lldpd_chassis chassis; + struct lldpd_port port; + char sep[80]; + + /* + * Get and parse command line options + */ + while ((ch = getopt(argc, argv, "d")) != -1) { + switch (ch) { + case 'd': + debug++; + break; + default: + usage(); + } + } + + log_init(debug); + memset(sep, '-', 79); + sep[79] = 0; + + if ((s = ctl_connect(LLDPD_CTL_SOCKET)) == -1) + fatalx("unable to connect to socket " LLDPD_CTL_SOCKET); + get_interfaces(s, &ifs); + + printf("%s\n", sep); + printf(" LLDP neighbors\n"); + printf("%s\n", sep); + TAILQ_FOREACH(iff, &ifs, next) { + if ((get_chassis(s, &chassis, iff->name) != -1) && + (get_port(s, &port, iff->name) != -1)) { + printf("Interface: %s\n", iff->name); + display_chassis(&chassis); + printf("\n"); + display_port(&port); + if (get_vlans(s, &vls, iff->name) != -1) { + memcpy(&port.p_vlans, &vls, sizeof(struct vlans)); + if (!TAILQ_EMPTY(&port.p_vlans)) { + printf("\n"); + display_vlans(&port); + } + } + printf("%s\n", sep); + } + } + + close(s); + + return 0; +} diff --git a/src/lldpd.c b/src/lldpd.c new file mode 100644 index 00000000..e4161b5f --- /dev/null +++ b/src/lldpd.c @@ -0,0 +1,1474 @@ +/* + * Copyright (c) 2008 Vincent Bernat + * + * Permission to use, copy, modify, and 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef USE_SNMP +#include +#include +#include +#include +#endif /* USE_SNMP */ + +void usage(void); + +int lldpd_iface_init(struct lldpd *, struct lldpd_hardware *); +int lldpd_iface_close(struct lldpd *, struct lldpd_hardware *); +void lldpd_iface_multicast(struct lldpd *, const char *, int); + +/* "ether proto 0x88cc and ether dst 01:80:c2:00:00:0e" */ +#define LLDPD_FILTER_LLDP_F \ + { 0x28, 0, 0, 0x0000000c }, \ + { 0x15, 0, 5, 0x000088cc }, \ + { 0x20, 0, 0, 0x00000002 }, \ + { 0x15, 0, 3, 0xc200000e }, \ + { 0x28, 0, 0, 0x00000000 }, \ + { 0x15, 0, 1, 0x00000180 }, \ + { 0x6, 0, 0, 0x0000ffff }, \ + { 0x6, 0, 0, 0x00000000 }, +struct sock_filter lldpd_filter_lldp_f[] = { LLDPD_FILTER_LLDP_F }; +/* "ether dst 01:00:0c:cc:cc:cc" */ +#define LLDPD_FILTER_CDP_F \ + { 0x20, 0, 0, 0x00000002 }, \ + { 0x15, 0, 3, 0x0ccccccc }, \ + { 0x28, 0, 0, 0x00000000 }, \ + { 0x15, 0, 1, 0x00000100 }, \ + { 0x6, 0, 0, 0x0000ffff }, \ + { 0x6, 0, 0, 0x00000000 }, +struct sock_filter lldpd_filter_cdp_f[] = { LLDPD_FILTER_CDP_F }; +/* "ether dst 01:00:81:00:01:00" */ +#define LLDPD_FILTER_SONMP_F \ + { 0x20, 0, 0, 0x00000002 }, \ + { 0x15, 0, 3, 0x81000100 }, \ + { 0x28, 0, 0, 0x00000000 }, \ + { 0x15, 0, 1, 0x00000100 }, \ + { 0x6, 0, 0, 0x0000ffff }, \ + { 0x6, 0, 0, 0x00000000 }, +struct sock_filter lldpd_filter_sonmp_f[] = { LLDPD_FILTER_SONMP_F }; +/* "ether dst 00:e0:2b:00:00:00" */ +#define LLDPD_FILTER_EDP_F \ + { 0x20, 0, 0, 0x00000002 }, \ + { 0x15, 0, 3, 0x2b000000 }, \ + { 0x28, 0, 0, 0x00000000 }, \ + { 0x15, 0, 1, 0x000000e0 }, \ + { 0x6, 0, 0, 0x0000ffff }, \ + { 0x6, 0, 0, 0x00000000 }, +struct sock_filter lldpd_filter_edp_f[] = { LLDPD_FILTER_EDP_F }; +#define LLDPD_FILTER_ANY_F \ + { 0x28, 0, 0, 0x0000000c }, \ + { 0x15, 0, 4, 0x000088cc }, \ + { 0x20, 0, 0, 0x00000002 }, \ + { 0x15, 0, 2, 0xc200000e }, \ + { 0x28, 0, 0, 0x00000000 }, \ + { 0x15, 8, 9, 0x00000180 }, \ + { 0x20, 0, 0, 0x00000002 }, \ + { 0x15, 0, 2, 0x2b000000 }, \ + { 0x28, 0, 0, 0x00000000 }, \ + { 0x15, 4, 5, 0x000000e0 }, \ + { 0x15, 1, 0, 0x0ccccccc }, \ + { 0x15, 0, 3, 0x81000100 }, \ + { 0x28, 0, 0, 0x00000000 }, \ + { 0x15, 0, 1, 0x00000100 }, \ + { 0x6, 0, 0, 0x0000ffff }, \ + { 0x6, 0, 0, 0x00000000 }, +struct sock_filter lldpd_filter_any_f[] = { LLDPD_FILTER_ANY_F }; + +struct protocol protos[] = +{ + { LLDPD_MODE_LLDP, 1, "LLDP", ' ', lldp_send, lldp_decode, NULL, + LLDP_MULTICAST_ADDR, lldpd_filter_lldp_f, sizeof(lldpd_filter_lldp_f) }, + { LLDPD_MODE_CDPV1, 0, "CDPv1", 'c', cdpv1_send, cdp_decode, cdpv1_guess, + CDP_MULTICAST_ADDR, lldpd_filter_cdp_f, sizeof(lldpd_filter_cdp_f) }, + { LLDPD_MODE_CDPV2, 0, "CDPv2", 'c', cdpv2_send, cdp_decode, cdpv2_guess, + CDP_MULTICAST_ADDR, lldpd_filter_cdp_f, sizeof(lldpd_filter_cdp_f) }, + { LLDPD_MODE_SONMP, 0, "SONMP", 's', sonmp_send, sonmp_decode, NULL, + SONMP_MULTICAST_ADDR, lldpd_filter_sonmp_f, sizeof(lldpd_filter_sonmp_f) }, + { LLDPD_MODE_EDP, 0, "EDP", 'e', edp_send, edp_decode, NULL, + EDP_MULTICAST_ADDR, lldpd_filter_edp_f, sizeof(lldpd_filter_edp_f) }, + { 0, 0, "any", ' ', NULL, NULL, NULL, + {0,0,0,0,0,0}, lldpd_filter_any_f, sizeof(lldpd_filter_any_f) } +}; + +int lldpd_iface_switchto(struct lldpd *, short int, + struct lldpd_hardware *); +struct lldpd_hardware *lldpd_port_add(struct lldpd *, struct ifaddrs *); +void lldpd_loop(struct lldpd *); +void lldpd_hangup(int); +void lldpd_shutdown(int); +void lldpd_exit(); +void lldpd_send_all(struct lldpd *); +void lldpd_recv_all(struct lldpd *); +int lldpd_guess_type(struct lldpd *, char *, int); +void lldpd_decode(struct lldpd *, char *, int, + struct lldpd_hardware *, int); +void lldpd_handle_client(struct lldpd *, struct lldpd_client *, + char *, int); + +void lldpd_handle_none(struct lldpd *, struct hmsg *, + struct hmsg *); +void lldpd_handle_get_interfaces(struct lldpd *, struct hmsg *, + struct hmsg *); +void lldpd_handle_get_port_related(struct lldpd *, struct hmsg *, + struct hmsg *); +void lldpd_handle_shutdown(struct lldpd *, struct hmsg *, + struct hmsg *); + +struct client_handle { + enum hmsg_type type; + void (*handle)(struct lldpd*, struct hmsg*, struct hmsg*); +}; + +struct client_handle client_handles[] = { + { HMSG_NONE, lldpd_handle_none }, + { HMSG_GET_INTERFACES, lldpd_handle_get_interfaces }, + { HMSG_GET_CHASSIS, lldpd_handle_get_port_related }, + { HMSG_GET_PORT, lldpd_handle_get_port_related }, + { HMSG_GET_VLANS, lldpd_handle_get_port_related }, + { HMSG_SHUTDOWN, lldpd_handle_shutdown }, + { 0, NULL } }; + +char **saved_argv; + +void +usage(void) +{ + extern const char *__progname; +#ifndef USE_SNMP + fprintf(stderr, "usage: %s [-d] [-c] [-s] [-e] [-p|-P] [-m ip]\n", __progname); +#else /* USE_SNMP */ + fprintf(stderr, "usage: %s [-d] [-c] [-s] [-e] [-p|-P] [-m ip] [-x]\n", __progname); +#endif /* USE_SNMP */ + exit(1); +} + +int +lldpd_iface_init(struct lldpd *global, struct lldpd_hardware *hardware) +{ + struct sockaddr_ll sa; + struct ifreq ifr; + int master; /* Bond device */ + char if_bond[IFNAMSIZ]; + int un = 1; + short int filter; + + /* get MTU */ + memset(&ifr, 0, sizeof(ifr)); + strlcpy(ifr.ifr_name, hardware->h_ifname, sizeof(ifr.ifr_name)); + if (ioctl(global->g_sock, SIOCGIFMTU, (char*)&ifr) == -1) { + LLOG_WARN("unable to get MTU of %s, using 1500", hardware->h_ifname); + hardware->h_mtu = 1500; + } else + hardware->h_mtu = ifr.ifr_mtu; + + /* Open listening socket to receive/send frames */ + if ((hardware->h_raw = socket(PF_PACKET, SOCK_RAW, + htons(ETH_P_ALL))) < 0) + return errno; + memset(&sa, 0, sizeof(sa)); + sa.sll_family = AF_PACKET; + sa.sll_protocol = 0; + sa.sll_ifindex = if_nametoindex(hardware->h_ifname); + if (bind(hardware->h_raw, (struct sockaddr*)&sa, sizeof(sa)) < 0) + return errno; + + if ((master = iface_is_enslaved(global, hardware->h_ifname)) != -1) { + /* With bonding device, we need to listen on the bond ! */ + if (if_indextoname(master, if_bond) == NULL) { + LLOG_WARN("unable to get index for interface %d (master of %s)", + master, hardware->h_ifname); + return ENETDOWN; + } + hardware->h_raw_real = hardware->h_raw; + hardware->h_master = master; + hardware->h_raw = -1; + if ((hardware->h_raw = socket(PF_PACKET, SOCK_RAW, + htons(ETH_P_ALL))) < 0) + return errno; + memset(&sa, 0, sizeof(sa)); + sa.sll_family = AF_PACKET; + sa.sll_protocol = 0; + sa.sll_ifindex = master; + if (bind(hardware->h_raw, (struct sockaddr*)&sa, + sizeof(sa)) < 0) + return errno; + /* With bonding, we need to listen to bond device. We use + * setsockopt() PACKET_ORIGDEV to get physical device instead of + * bond device */ + if (setsockopt(hardware->h_raw, SOL_PACKET, + PACKET_ORIGDEV, &un, sizeof(un)) == -1) { + LLOG_WARN("unable to setsockopt for master bonding device of %s. " + "You will get inaccurate results", + hardware->h_ifname); + } + } + + if (global->g_multi) + filter = LLDPD_MODE_ANY; + else + filter = LLDPD_MODE_LLDP; + if (lldpd_iface_switchto(global, filter, hardware) == -1) { + LLOG_WARNX("unable to apply filter"); + return ENETDOWN; + } + + if (master != -1) + lldpd_iface_multicast(global, if_bond, 0); + lldpd_iface_multicast(global, hardware->h_ifname, 0); + + LLOG_DEBUG("interface %s initialized (fd=%d)", hardware->h_ifname, + hardware->h_raw); + return 0; +} + +void +lldpd_iface_multicast(struct lldpd *global, const char *name, int remove) +{ + struct ifreq ifr; + int i; + + for (i=0; global->g_protocols[i].mode != 0; i++) { + if (!global->g_protocols[i].enabled) continue; + memset(&ifr, 0, sizeof(ifr)); + strlcpy(ifr.ifr_name, name, sizeof(ifr.ifr_name)); + memcpy(ifr.ifr_hwaddr.sa_data, + global->g_protocols[i].mac, ETH_ALEN); + if (ioctl(global->g_sock, (remove)?SIOCDELMULTI:SIOCADDMULTI, + &ifr) < 0) { + if (errno == ENOENT) + return; + LLOG_INFO("unable to %s %s address to multicast filter for %s", + (remove)?"delete":"add", + global->g_protocols[i].name, + name); + } + } +} + +int +lldpd_iface_close(struct lldpd *global, struct lldpd_hardware *hardware) +{ + char listen[IFNAMSIZ]; + + close(hardware->h_raw); + hardware->h_raw = -1; + + if (hardware->h_raw_real > 0) { + if (if_indextoname(hardware->h_master, listen) == NULL) { + LLOG_WARN("unable to get index for interface %d", + hardware->h_master); + strlcpy(listen, hardware->h_ifname, sizeof(listen)); + } + close(hardware->h_raw_real); + lldpd_iface_multicast(global, listen, 1); + } + strlcpy(listen, hardware->h_ifname, sizeof(listen)); + lldpd_iface_multicast(global, listen, 1); + + hardware->h_raw_real = -1; + return 0; +} + +int +lldpd_iface_switchto(struct lldpd *cfg, short int filter, struct lldpd_hardware *hardware) +{ + struct sock_fprog prog; + int i; + + memset(&prog, 0, sizeof(prog)); + for (i=0; cfg->g_protocols[i].mode != 0; i++) { + if (!cfg->g_protocols[i].enabled) continue; + if (cfg->g_protocols[i].mode == filter) + break; + } + prog.filter = cfg->g_protocols[i].filter; + prog.len = cfg->g_protocols[i].filterlen / sizeof(struct sock_filter); + if (setsockopt(hardware->h_raw, SOL_SOCKET, SO_ATTACH_FILTER, + &prog, sizeof(prog)) < 0) { + LLOG_WARN("unable to change filter for %s", hardware->h_ifname); + return -1; + } + if ((hardware->h_raw_real > 0) && + (setsockopt(hardware->h_raw_real, SOL_SOCKET, SO_ATTACH_FILTER, + &prog, sizeof(prog)) < 0)) { + LLOG_WARN("unable to change filter for real device %s", hardware->h_ifname); + return -1; + } + return 0; +} + + +void +lldpd_vlan_cleanup(struct lldpd_port *port) +{ + struct lldpd_vlan *vlan, *vlan_next; + for (vlan = TAILQ_FIRST(&port->p_vlans); + vlan != NULL; + vlan = vlan_next) { + free(vlan->v_name); + vlan_next = TAILQ_NEXT(vlan, v_entries); + TAILQ_REMOVE(&port->p_vlans, vlan, v_entries); + free(vlan); + } +} + +void +lldpd_port_cleanup(struct lldpd_port *port) +{ + lldpd_vlan_cleanup(port); + free(port->p_id); + free(port->p_descr); + free(port); +} + +void +lldpd_chassis_cleanup(struct lldpd_chassis *chassis) +{ + free(chassis->c_id); + free(chassis->c_name); + free(chassis->c_descr); + free(chassis); +} + +void +lldpd_remote_cleanup(struct lldpd *cfg, struct lldpd_hardware *hardware, int reset) +{ + if (hardware->h_rport != NULL) { + lldpd_port_cleanup(hardware->h_rport); + hardware->h_rport = NULL; + } + if (hardware->h_rchassis != NULL) { + lldpd_chassis_cleanup(hardware->h_rchassis); + hardware->h_rchassis = NULL; + } + hardware->h_rlastchange = hardware->h_rlastupdate = 0; + free(hardware->h_rlastframe); + hardware->h_rlastframe = NULL; + if (reset && cfg->g_multi) { + hardware->h_mode = LLDPD_MODE_ANY; + memset(hardware->h_proto_macs, 0, ETH_ALEN*(cfg->g_multi+1)); + hardware->h_start_probe = 0; + lldpd_iface_switchto(cfg, LLDPD_MODE_ANY, hardware); + } +} + +void +lldpd_cleanup(struct lldpd *cfg) +{ + struct lldpd_hardware *hardware, *hardware_next; + + for (hardware = TAILQ_FIRST(&cfg->g_hardware); hardware != NULL; + hardware = hardware_next) { + hardware_next = TAILQ_NEXT(hardware, h_entries); + if (hardware->h_flags == 0) { + TAILQ_REMOVE(&cfg->g_hardware, hardware, h_entries); + lldpd_iface_close(cfg, hardware); + lldpd_vlan_cleanup(&hardware->h_lport); + lldpd_remote_cleanup(cfg, hardware, 1); + free(hardware->h_proto_macs); + free(hardware->h_llastframe); + free(hardware); + } else if (hardware->h_rchassis != NULL) { + if (time(NULL) - hardware->h_rlastupdate > + hardware->h_rchassis->c_ttl) { + lldpd_remote_cleanup(cfg, hardware, 1); + hardware->h_rx_ageout_cnt++; + } + } + } +} + +struct lldpd_hardware * +lldpd_port_add(struct lldpd *cfg, struct ifaddrs *ifa) +{ + struct ifaddrs *oifap, *oifa; + struct lldpd_hardware *hardware; + struct lldpd_port *port; + struct lldpd_vlan *vlan; + struct ifreq ifr; + struct vlan_ioctl_args ifv; + struct ethtool_cmd ethc; + u_int8_t *lladdr; + + TAILQ_FOREACH(hardware, &cfg->g_hardware, h_entries) { + if (strcmp(hardware->h_ifname, ifa->ifa_name) == 0) + break; + } + + if (hardware == NULL) { + if ((hardware = (struct lldpd_hardware *) + calloc(1, sizeof(struct lldpd_hardware))) == NULL) + return (NULL); + hardware->h_raw = -1; + hardware->h_raw_real = -1; + hardware->h_start_probe = 0; + hardware->h_proto_macs = (u_int8_t*)calloc(cfg->g_multi+1, ETH_ALEN); + TAILQ_INIT(&hardware->h_lport.p_vlans); + } else { + lldpd_vlan_cleanup(&hardware->h_lport); + } + + port = &hardware->h_lport; + hardware->h_flags = ifa->ifa_flags; + + strlcpy(hardware->h_ifname, ifa->ifa_name, sizeof(hardware->h_ifname)); + lladdr = (u_int8_t*)(((struct sockaddr_ll *)ifa->ifa_addr)->sll_addr); + memcpy(&hardware->h_lladdr, lladdr, sizeof(hardware->h_lladdr)); + port->p_id_subtype = LLDP_PORTID_SUBTYPE_LLADDR; + port->p_id = (char*)hardware->h_lladdr; + port->p_id_len = sizeof(hardware->h_lladdr); + port->p_descr = hardware->h_ifname; + + if (cfg->g_lchassis.c_id == NULL) { + /* Use the first port's l2 addr as the chassis ID */ + if ((cfg->g_lchassis.c_id = malloc(sizeof(hardware->h_lladdr))) == NULL) + fatal(NULL); + cfg->g_lchassis.c_id_subtype = LLDP_CHASSISID_SUBTYPE_LLADDR; + cfg->g_lchassis.c_id_len = sizeof(hardware->h_lladdr); + memcpy(cfg->g_lchassis.c_id, + hardware->h_lladdr, sizeof(hardware->h_lladdr)); + } + + /* Get VLANS and aggregation status */ + if (getifaddrs(&oifap) != 0) + fatal("lldpd_port_add: failed to get interface list"); + for (oifa = oifap; oifa != NULL; oifa = oifa->ifa_next) { + /* Check if we already have checked this one */ + int skip = 0; + TAILQ_FOREACH(vlan, &port->p_vlans, v_entries) { + if (strcmp(vlan->v_name, oifa->ifa_name) == 0) + skip = 1; + } + if (skip) continue; + + /* Aggregation check */ + if (iface_is_bond_slave(cfg, hardware->h_ifname, oifa->ifa_name)) + port->p_aggregid = if_nametoindex(oifa->ifa_name); + + /* VLAN check */ + memset(&ifv, 0, sizeof(ifv)); + ifv.cmd = GET_VLAN_REALDEV_NAME_CMD; + strlcpy(ifv.device1, oifa->ifa_name, sizeof(ifv.device1)); + if ((ioctl(cfg->g_sock, SIOCGIFVLAN, &ifv) >= 0) && + ((iface_is_bond_slave(cfg, hardware->h_ifname, ifv.u.device2)) || + (strncmp(hardware->h_ifname, ifv.u.device2, sizeof(ifv.u.device2)) == 0))) { + if ((vlan = (struct lldpd_vlan *) + calloc(1, sizeof(struct lldpd_vlan))) == NULL) + continue; + if (asprintf(&vlan->v_name, "%s", oifa->ifa_name) == -1) { + free(vlan); + continue; + } + memset(&ifv, 0, sizeof(ifv)); + ifv.cmd = GET_VLAN_VID_CMD; + strlcpy(ifv.device1, oifa->ifa_name, sizeof(ifv.device1)); + if (ioctl(cfg->g_sock, SIOCGIFVLAN, &ifv) < 0) { + /* Dunno what happened */ + free(vlan->v_name); + free(vlan); + } else { + vlan->v_vid = ifv.u.VID; + TAILQ_INSERT_TAIL(&port->p_vlans, vlan, v_entries); + } + } + } + freeifaddrs(oifap); + + /* MAC/PHY */ + memset(&ifr, 0, sizeof(ifr)); + memset(ðc, 0, sizeof(ethc)); + strlcpy(ifr.ifr_name, hardware->h_ifname, sizeof(ifr.ifr_name)); + ifr.ifr_data = (caddr_t)ðc; + ethc.cmd = ETHTOOL_GSET; + if (ioctl(cfg->g_sock, SIOCETHTOOL, &ifr) == 0) { + int j; + int advertised_ethtool_to_rfc3636[][2] = { + {ADVERTISED_10baseT_Half, LLDP_DOT3_LINK_AUTONEG_10BASE_T}, + {ADVERTISED_10baseT_Full, LLDP_DOT3_LINK_AUTONEG_10BASET_FD}, + {ADVERTISED_100baseT_Half, LLDP_DOT3_LINK_AUTONEG_100BASE_TX}, + {ADVERTISED_100baseT_Full, LLDP_DOT3_LINK_AUTONEG_100BASE_TXFD}, + {ADVERTISED_1000baseT_Half, LLDP_DOT3_LINK_AUTONEG_1000BASE_T}, + {ADVERTISED_1000baseT_Full, LLDP_DOT3_LINK_AUTONEG_1000BASE_TFD}, + {ADVERTISED_10000baseT_Full, LLDP_DOT3_LINK_AUTONEG_OTHER}, + {ADVERTISED_Pause, LLDP_DOT3_LINK_AUTONEG_FDX_PAUSE}, + {ADVERTISED_Asym_Pause, LLDP_DOT3_LINK_AUTONEG_FDX_APAUSE}, + {ADVERTISED_2500baseX_Full, LLDP_DOT3_LINK_AUTONEG_OTHER}, + {0,0}}; + + port->p_autoneg_support = (ethc.supported & SUPPORTED_Autoneg) ? 1 : 0; + port->p_autoneg_enabled = (ethc.autoneg == AUTONEG_DISABLE) ? 0 : 1; + for (j=0; advertised_ethtool_to_rfc3636[j][0]; j++) { + if (ethc.advertising & advertised_ethtool_to_rfc3636[j][0]) + port->p_autoneg_advertised |= advertised_ethtool_to_rfc3636[j][1]; + } + switch (ethc.speed) { + case SPEED_10: + port->p_mau_type = (ethc.duplex == DUPLEX_FULL) ? \ + LLDP_DOT3_MAU_10BASETFD : LLDP_DOT3_MAU_10BASETHD; + if (ethc.port == PORT_BNC) port->p_mau_type = LLDP_DOT3_MAU_10BASE2; + if (ethc.port == PORT_FIBRE) + port->p_mau_type = (ethc.duplex == DUPLEX_FULL) ? \ + LLDP_DOT3_MAU_10BASEFLDF : LLDP_DOT3_MAU_10BASEFLHD; + break; + case SPEED_100: + port->p_mau_type = (ethc.duplex == DUPLEX_FULL) ? \ + LLDP_DOT3_MAU_100BASETXFD : LLDP_DOT3_MAU_100BASETXHD; + if (ethc.port == PORT_BNC) + port->p_mau_type = (ethc.duplex == DUPLEX_FULL) ? \ + LLDP_DOT3_MAU_100BASET2DF : LLDP_DOT3_MAU_100BASET2HD; + if (ethc.port == PORT_FIBRE) + port->p_mau_type = (ethc.duplex == DUPLEX_FULL) ? \ + LLDP_DOT3_MAU_100BASEFXFD : LLDP_DOT3_MAU_100BASEFXHD; + break; + case SPEED_1000: + port->p_mau_type = (ethc.duplex == DUPLEX_FULL) ? \ + LLDP_DOT3_MAU_1000BASETFD : LLDP_DOT3_MAU_1000BASETHD; + if (ethc.port == PORT_FIBRE) + port->p_mau_type = (ethc.duplex == DUPLEX_FULL) ? \ + LLDP_DOT3_MAU_1000BASEXFD : LLDP_DOT3_MAU_1000BASEXHD; + break; + case SPEED_10000: + port->p_mau_type = (ethc.port == PORT_FIBRE) ? \ + LLDP_DOT3_MAU_10GIGBASEX : LLDP_DOT3_MAU_10GIGBASER; + break; + } + if (ethc.port == PORT_AUI) port->p_mau_type = LLDP_DOT3_MAU_AUI; + } + + if (!INTERFACE_OPENED(hardware)) { + + if (lldpd_iface_init(cfg, hardware) != 0) { + lldpd_vlan_cleanup(&hardware->h_lport); + free(hardware->h_lladdr); + free(hardware->h_proto_macs); + free(hardware); + return (NULL); + } + + TAILQ_INSERT_TAIL(&cfg->g_hardware, hardware, h_entries); + } + + return (hardware); +} + +int +lldpd_guess_type(struct lldpd *cfg, char *frame, int s) +{ + int i; + if (s < ETH_ALEN) + 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) { + if (memcmp(frame, cfg->g_protocols[i].mac, ETH_ALEN) == 0) + return cfg->g_protocols[i].mode; + } else { + if (cfg->g_protocols[i].guess(frame, s)) + return cfg->g_protocols[i].mode; + } + } + return -1; +} + +void +lldpd_decode(struct lldpd *cfg, char *frame, int s, + struct lldpd_hardware *hardware, int bond) +{ + int result = 0, i, j, candidatetonull; + u_int8_t nullmac[ETH_ALEN] = {0,0,0,0,0,0}; + u_int8_t broadcastmac[ETH_ALEN] = {0xff,0xff,0xff,0xff,0xff,0xff}; + struct lldpd_chassis *chassis; + struct lldpd_port *port; + struct lldpd_hardware *ohardware, *firstnull = NULL, *older = NULL; + int guess = LLDPD_MODE_LLDP; + + if ((hardware->h_rlastframe != NULL) && + (hardware->h_rlastframe->size == s) && + (memcmp(hardware->h_rlastframe->frame, frame, s) == 0)) { + /* Already received the same frame */ + hardware->h_rlastupdate = time(NULL); + return; + } + + if (cfg->g_multi) { + if (hardware->h_mode == LLDPD_MODE_ANY) + guess = lldpd_guess_type(cfg, frame, s); + else + guess = hardware->h_mode; + for (i=0; cfg->g_protocols[i].mode != 0; i++) { + if (!cfg->g_protocols[i].enabled) + continue; + if (cfg->g_protocols[i].mode == guess) { + if ((result = cfg->g_protocols[i].decode(cfg, frame, + s, hardware, &chassis, &port)) == -1) + return; + break; + } + } + if (cfg->g_protocols[i].mode == 0) { + LLOG_INFO("unable to guess frame type"); + return; + } + } else if (cfg->g_protocols[0].decode(cfg, frame, s, hardware, + &chassis, &port) == -1) + /* Nothing has been received */ + return; + + if (bond) { + /* Eh, wait ! The frame we just received was for a bonding + * device. We need to attach it to a real device. What is the + * best candidate? Drum rolling... */ + TAILQ_FOREACH(ohardware, &cfg->g_hardware, h_entries) { + if (ohardware->h_master == hardware->h_master) { + /* Same bond */ + if (ohardware->h_rchassis == NULL) { + candidatetonull = 1; + if (cfg->g_multi && + (ohardware->h_mode == LLDPD_MODE_ANY)) { + for (i=j=0; + cfg->g_protocols[i].mode != 0; + i++) { + if (!cfg->g_protocols[i].enabled) + continue; + if ((cfg->g_protocols[i].mode == guess) && + (memcmp(frame + ETH_ALEN, + ohardware->h_proto_macs + ETH_ALEN*j, + ETH_ALEN) == 0)) { + hardware = ohardware; + bond = 0; + break; + } + j++; + } + if (!bond) break; + if (firstnull != NULL) { + for (i=j=0; + cfg->g_protocols[i].mode != 0; + i++) { + if (!cfg->g_protocols[i].enabled) + continue; + if ((cfg->g_protocols[i].mode == guess) && + (memcmp(nullmac, + ohardware->h_proto_macs + + ETH_ALEN*j, + ETH_ALEN) != 0)) { + /* We need to + * find a better + * candidate */ + candidatetonull = 0; + break; + } + j++; + } + } + } + /* Ok, this is the first candidate if we + * don't find a matching chassis/port */ + if (candidatetonull) firstnull = ohardware; + continue; + } + if ((older == NULL) || + (older->h_rlastupdate > ohardware->h_rlastupdate)) + /* If there is no matching chassis/port + * and no free hardware, we will use + * this one. */ + older = ohardware; + if ((chassis->c_id_subtype != + ohardware->h_rchassis->c_id_subtype) || + (chassis->c_id_len != ohardware->h_rchassis->c_id_len) || + (memcmp(chassis->c_id, ohardware->h_rchassis->c_id, + chassis->c_id_len) != 0) || + (port->p_id_subtype != ohardware->h_rport->p_id_subtype) || + (port->p_id_len != ohardware->h_rport->p_id_len) || + (memcmp(port->p_id, ohardware->h_rport->p_id, + port->p_id_len) != 0)) + continue; + /* We got a match! */ + hardware = ohardware; /* We switch hardware */ + bond = 0; + break; + } + } + if (bond) { + /* No match found */ + if (firstnull != NULL) + hardware = firstnull; + else hardware = older; + } + } + + if (cfg->g_multi && + (hardware->h_mode == LLDPD_MODE_ANY)) { + u_int8_t *mac; + char *modename; + int filter; + + for (i=j=0; cfg->g_protocols[i].mode != 0; i++) { + if (!cfg->g_protocols[i].enabled) + continue; + if (cfg->g_protocols[i].mode == guess) { + mac = hardware->h_proto_macs + ETH_ALEN*j; + modename = cfg->g_protocols[i].name; + filter = cfg->g_protocols[i].mode; + break; + } + j++; + } + if (cfg->g_protocols[i].mode == 0) { + LLOG_WARNX("should not be there"); + goto cleanup; + } + + if (hardware->h_start_probe == 0) + hardware->h_start_probe = time(NULL) - 1; + /* Handle switching respecting probe time */ + if ((memcmp(mac, frame + ETH_ALEN, ETH_ALEN) == 0) && + ((time(NULL) - hardware->h_start_probe) > cfg->g_probe_time) && + /* Don't switch to this protocol if not LLDP and LLDP is + * a valid candidate */ + ((filter == LLDPD_MODE_LLDP) || + (memcmp(hardware->h_proto_macs, + broadcastmac, ETH_ALEN) == 0) || + (memcmp(hardware->h_proto_macs, + nullmac, ETH_ALEN) == 0))) { + LLOG_INFO("switching to %s on port %s", modename, + hardware->h_ifname); + hardware->h_mode = guess; + lldpd_iface_switchto(cfg, filter, hardware); + } else { + /* Wait twice probe time to be able to receive packets of all kind */ + if ((time(NULL) - hardware->h_start_probe) > cfg->g_probe_time * 2) { + LLOG_DEBUG("probe expired on %s, retry", hardware->h_ifname); + hardware->h_start_probe = 0; + memset(hardware->h_proto_macs, 0, ETH_ALEN*(cfg->g_multi+1)); + goto cleanup; + } + if (memcmp(mac, broadcastmac, ETH_ALEN) == 0) + goto cleanup; + LLOG_INFO("received a %s frame on %s but wait for %d sec", + modename, hardware->h_ifname, cfg->g_probe_time - time(NULL) + + hardware->h_start_probe); + if (memcmp(mac, frame + ETH_ALEN, ETH_ALEN) == 0) + goto cleanup; + if (memcmp(mac, nullmac, ETH_ALEN) == 0) { + memcpy(mac, frame + ETH_ALEN, ETH_ALEN); + goto cleanup; + } + LLOG_INFO("several MAC for %s on %s, discarding %s for this interface", + modename, hardware->h_ifname, modename); + memcpy(mac, broadcastmac, ETH_ALEN); + goto cleanup; + } + } + + result = 0; + if ((hardware->h_rchassis == NULL) || + (chassis->c_id_subtype != hardware->h_rchassis->c_id_subtype) || + (chassis->c_id_len != hardware->h_rchassis->c_id_len) || + (memcmp(chassis->c_id, hardware->h_rchassis->c_id, + chassis->c_id_len) != 0)) + result = 1; + + /* We have our new frame */ + lldpd_remote_cleanup(cfg, hardware, 0); + hardware->h_rport = port; + hardware->h_rchassis = chassis; + hardware->h_rlastchange = hardware->h_rlastupdate = time(NULL); + + /* We remember this frame */ + free(hardware->h_rlastframe); + if ((hardware->h_rlastframe = (struct lldpd_frame *)malloc(s + + sizeof(int))) != NULL) { + hardware->h_rlastframe->size = s; + memcpy(hardware->h_rlastframe->frame, frame, s); + } + + if (result) { + /* This is a new remote system */ + LLOG_DEBUG("we discovered a new remote system on %s", + hardware->h_ifname); + /* Do we already know this remote system? */ + TAILQ_FOREACH(ohardware, &cfg->g_hardware, h_entries) { + if ((ohardware->h_ifname != hardware->h_ifname) && + (ohardware->h_rchassis != NULL) && + (ohardware->h_rchassis->c_id_subtype == + chassis->c_id_subtype) && + (ohardware->h_rchassis->c_id_len == + chassis->c_id_len) && + (memcmp(ohardware->h_rchassis->c_id, + chassis->c_id, chassis->c_id_len) == 0)) { + LLOG_DEBUG("but it was already on %s", + ohardware->h_ifname); + hardware->h_rid = ohardware->h_rid; + return; + } + } + hardware->h_rid = ++cfg->g_lastrid; + } + return; + +cleanup: + lldpd_chassis_cleanup(chassis); + lldpd_port_cleanup(port); + return; +} + +void +lldpd_handle_client(struct lldpd *cfg, struct lldpd_client *client, + char *buffer, int n) +{ + struct hmsg *h; /* Reception */ + struct hmsg *t; /* Sending */ + struct client_handle *ch; + + if (n < sizeof(struct hmsg_hdr)) { + LLOG_WARNX("too short message request received"); + return; + } + h = (struct hmsg *)buffer; + n -= sizeof(struct hmsg_hdr); + if (n != h->hdr.len) { + LLOG_WARNX("incorrect message size received from %d", + h->hdr.pid); + return; + } + + if ((t = (struct hmsg*)calloc(1, MAX_HMSGSIZE)) == NULL) { + LLOG_WARNX("unable to allocate memory to answer to %d", + h->hdr.pid); + return; + } + ctl_msg_init(t, h->hdr.type); + for (ch = client_handles; ch->handle != NULL; ch++) { + if (ch->type == h->hdr.type) { + ch->handle(cfg, h, t); + if (t->hdr.len == -1) { + t->hdr.len = 0; + t->hdr.type = HMSG_NONE; + } + if (ctl_msg_send(client->fd, t) == -1) + LLOG_WARN("unable to send answer to client %d", + h->hdr.pid); + free(t); + return; + } + } + + LLOG_WARNX("unknown message request (%d) received from %d", + h->hdr.type, h->hdr.pid); + free(t); + return; +} + +void +lldpd_handle_shutdown(struct lldpd *cfg, struct hmsg *r, struct hmsg *s) +{ + LLOG_INFO("received shutdown request from client %d", + r->hdr.pid); + exit(0); +} + +void +lldpd_handle_none(struct lldpd *cfg, struct hmsg *r, struct hmsg *s) +{ + LLOG_INFO("received noop request from client %d", + r->hdr.pid); + s->hdr.len = -1; +} + +void +lldpd_handle_get_interfaces(struct lldpd *cfg, struct hmsg *r, struct hmsg *s) +{ + struct lldpd_interface *iff, *iff_next; + struct lldpd_hardware *hardware; + void *p; + + /* Build the list of interfaces */ + TAILQ_HEAD(, lldpd_interface) ifs; + TAILQ_INIT(&ifs); + TAILQ_FOREACH(hardware, &cfg->g_hardware, h_entries) { + if ((iff = (struct lldpd_interface*)malloc(sizeof( + struct lldpd_interface))) == NULL) + fatal(NULL); + iff->name = hardware->h_ifname; + TAILQ_INSERT_TAIL(&ifs, iff, next); + } + + p = &s->data; + if (ctl_msg_pack_list(STRUCT_LLDPD_INTERFACE, &ifs, + sizeof(struct lldpd_interface), s, &p) == -1) { + LLOG_WARNX("unable to pack list of interfaces"); + s->hdr.len = -1; + } + + /* Free the temporary list */ + for (iff = TAILQ_FIRST(&ifs); + iff != NULL; + iff = iff_next) { + iff_next = TAILQ_NEXT(iff, next); + TAILQ_REMOVE(&ifs, iff, next); + free(iff); + } +} + +void +lldpd_handle_get_port_related(struct lldpd *cfg, struct hmsg *r, struct hmsg *s) +{ + char *ifname; + struct lldpd_hardware *hardware; + void *p; + + ifname = (char*)(&r->data); + if (ifname[r->hdr.len - 1] != 0) { + LLOG_WARNX("bad message format for get port related message"); + s->hdr.len = -1; + return; + } + TAILQ_FOREACH(hardware, &cfg->g_hardware, h_entries) { + if (strncmp(ifname, hardware->h_ifname, IFNAMSIZ) == 0) { + if ((hardware->h_rport == NULL) || + (hardware->h_rchassis == NULL)) { + s->hdr.len = 0; + s->hdr.type = HMSG_NONE; + return; + } + p = &s->data; + switch (r->hdr.type) { + case HMSG_GET_VLANS: + if (ctl_msg_pack_list(STRUCT_LLDPD_VLAN, + &hardware->h_rport->p_vlans, + sizeof(struct lldpd_vlan), s, &p) == -1) { + LLOG_WARNX("unable to send vlans information for " + "interface %s for %d", ifname, r->hdr.pid); + s->hdr.len = -1; + return; + } + break; + case HMSG_GET_PORT: + if (ctl_msg_pack_structure(STRUCT_LLDPD_PORT, + hardware->h_rport, + sizeof(struct lldpd_port), s, &p) == -1) { + LLOG_WARNX("unable to send port information for " + "interface %s for %d", ifname, r->hdr.pid); + s->hdr.len = -1; + return; + } + break; + case HMSG_GET_CHASSIS: + if (ctl_msg_pack_structure(STRUCT_LLDPD_CHASSIS, + hardware->h_rchassis, + sizeof(struct lldpd_chassis), s, &p) == -1) { + LLOG_WARNX("unable to send chassis information for " + "interface %s for %d", ifname, r->hdr.pid); + s->hdr.len = -1; + return; + } + break; + default: + LLOG_WARNX("don't know what to do"); + s->hdr.len = -1; + return; + } + return; + } + } + LLOG_WARNX("requested interface %s by %d was not found", + ifname, r->hdr.pid); + s->hdr.len = -1; + return; +} + +void +lldpd_recv_all(struct lldpd *cfg) +{ + struct lldpd_hardware *hardware; + struct lldpd_client *client, *client_next; + fd_set rfds; + struct timeval tv; + struct sockaddr_ll from; + socklen_t fromlen; + int onreal; +#ifdef USE_SNMP + int fakeblock = 0; + struct timeval *tvp = &tv; +#endif + int rc, nfds, n, bond; + char *buffer; + + do { + tv.tv_sec = cfg->g_delay - (time(NULL) - cfg->g_lastsent); + if (tv.tv_sec < 0) + tv.tv_sec = LLDPD_TX_DELAY; + if (tv.tv_sec >= cfg->g_delay) + tv.tv_sec = cfg->g_delay; + tv.tv_usec = 0; + + FD_ZERO(&rfds); + nfds = -1; + + TAILQ_FOREACH(hardware, &cfg->g_hardware, h_entries) { + /* Ignore if interface is down */ + if ((hardware->h_flags & IFF_UP) == 0) + continue; + FD_SET(hardware->h_raw, &rfds); + if (nfds < hardware->h_raw) + nfds = hardware->h_raw; + /* Listen to real interface too. In 2.6.27, we can + * receive packets if this is the slave interface. */ + if (hardware->h_raw_real > 0) { + FD_SET(hardware->h_raw_real, &rfds); + if (nfds < hardware->h_raw_real) + nfds = hardware->h_raw_real; + } + } + TAILQ_FOREACH(client, &cfg->g_clients, next) { + FD_SET(client->fd, &rfds); + if (nfds < client->fd) + nfds = client->fd; + } + FD_SET(cfg->g_ctl, &rfds); + if (nfds < cfg->g_ctl) + nfds = cfg->g_ctl; + +#ifdef USE_SNMP + if (cfg->g_snmp) + snmp_select_info(&nfds, &rfds, tvp, &fakeblock); +#endif /* USE_SNMP */ + if (nfds == -1) { + sleep(cfg->g_delay); + return; + } + + rc = select(nfds + 1, &rfds, NULL, NULL, &tv); + if (rc == -1) { + if (errno == EINTR) + continue; + LLOG_WARN("failure on select"); + break; + } +#ifdef USE_SNMP + if (cfg->g_snmp) { + if (rc > 0) + snmp_read(&rfds); + else if (rc == 0) + snmp_timeout(); + } +#endif /* USE_SNMP */ + TAILQ_FOREACH(hardware, &cfg->g_hardware, h_entries) { + /* We could have received something on _real_ + * interface. However, even in this case, this could be + * just an outgoing packet. We will try to handle both + * cases, but maybe not in the same select. */ + onreal = ((hardware->h_raw_real > 0) && + (FD_ISSET(hardware->h_raw_real, &rfds))); + if (onreal || (FD_ISSET(hardware->h_raw, &rfds))) { + if ((buffer = (char *)malloc( + hardware->h_mtu)) == NULL) { + LLOG_WARN("failed to alloc reception buffer"); + continue; + } + fromlen = sizeof(from); + if ((n = recvfrom( + onreal?hardware->h_raw_real:hardware->h_raw, + buffer, + hardware->h_mtu, 0, + (struct sockaddr *)&from, + &fromlen)) == -1) { + LLOG_WARN("error while receiving frame on %s", + hardware->h_ifname); + hardware->h_rx_discarded_cnt++; + free(buffer); + continue; + } + if (from.sll_pkttype == PACKET_OUTGOING) { + free(buffer); + continue; + } + bond = 0; + /* If received on real interface, we act like if + * this is not a bond! */ + if (!onreal && (hardware->h_raw_real > 0)) { + /* Bonding. Is it for the correct + * physical interface ? */ + if (from.sll_ifindex == hardware->h_master) { + /* It seems that we don't know from + which physical interface it comes + (kernel < 2.6.24 ?) */ + bond = 1; + } else if (from.sll_ifindex != + if_nametoindex(hardware->h_ifname)) { + free(buffer); + continue; + } + } + hardware->h_rx_cnt++; + lldpd_decode(cfg, buffer, n, hardware, bond); + free(buffer); + } + + } + if (FD_ISSET(cfg->g_ctl, &rfds)) { + if (ctl_accept(cfg, cfg->g_ctl) == -1) + LLOG_WARN("unable to accept new client"); + } + for (client = TAILQ_FIRST(&cfg->g_clients); + client != NULL; + client = client_next) { + client_next = TAILQ_NEXT(client, next); + if (FD_ISSET(client->fd, &rfds)) { + /* Got a message */ + if ((buffer = (char *)malloc(MAX_HMSGSIZE)) == + NULL) { + LLOG_WARN("failed to alloc reception buffer"); + continue; + } + if ((n = recv(client->fd, buffer, + MAX_HMSGSIZE, 0)) == -1) { + LLOG_WARN("error while receiving message"); + free(buffer); + continue; + } + if (n > 0) + lldpd_handle_client(cfg, client, buffer, n); + else + ctl_close(cfg, client->fd); /* Will use TAILQ_REMOVE ! */ + free(buffer); + } + } + +#ifdef USE_SNMP + if (cfg->g_snmp) { + run_alarms(); + netsnmp_check_outstanding_agent_requests(); + } +#endif /* USE_SNMP */ + } while ((rc != 0) || (time(NULL) - cfg->g_lastsent < cfg->g_delay)); +} + +void +lldpd_send_all(struct lldpd *cfg) +{ + struct lldpd_hardware *hardware; + int i; + cfg->g_lastsent = time(NULL); + TAILQ_FOREACH(hardware, &cfg->g_hardware, h_entries) { + /* Ignore if interface is down */ + if ((hardware->h_flags & IFF_UP) == 0) + continue; + + for (i=0; cfg->g_protocols[i].mode != 0; i++) { + if (!cfg->g_protocols[i].enabled) + continue; + if ((hardware->h_mode == cfg->g_protocols[i].mode) || + (cfg->g_protocols[i].mode == LLDPD_MODE_LLDP)) + cfg->g_protocols[i].send(cfg, &cfg->g_lchassis, hardware); + } + } +} + +void +lldpd_loop(struct lldpd *cfg) +{ + struct ifaddrs *ifap, *ifa; + struct sockaddr_ll *sdl; + struct lldpd_hardware *hardware; + int f; + char status; + struct utsname *un; + struct hostent *hp; + + /* Set system name and description */ + if ((un = (struct utsname*)malloc(sizeof(struct utsname))) == NULL) + fatal(NULL); + if (uname(un) != 0) + fatal("failed to get system information"); + if ((hp = gethostbyname(un->nodename)) == NULL) + fatal("failed to get system name"); + free(cfg->g_lchassis.c_name); + free(cfg->g_lchassis.c_descr); + if (asprintf(&cfg->g_lchassis.c_name, "%s", + hp->h_name) == -1) + fatal("failed to set system name"); + if (asprintf(&cfg->g_lchassis.c_descr, "%s %s %s %s", + un->sysname, un->release, un->version, un->machine) == -1) + fatal("failed to set system description"); + free(un); + + /* Check forwarding */ + cfg->g_lchassis.c_cap_enabled = 0; + if ((f = open("/proc/sys/net/ipv4/ip_forward", 0)) >= 0) { + if ((read(f, &status, 1) == 1) && (status == '1')) + cfg->g_lchassis.c_cap_enabled = LLDP_CAP_ROUTER; + close(f); + } + + TAILQ_FOREACH(hardware, &cfg->g_hardware, h_entries) + hardware->h_flags = 0; + + if (getifaddrs(&ifap) != 0) + fatal("lldpd_loop: failed to get interface list"); + + cfg->g_lchassis.c_mgmt.s_addr = INADDR_ANY; + for (ifa = ifap; ifa != NULL; ifa = ifa->ifa_next) { + if (cfg->g_lchassis.c_mgmt.s_addr == INADDR_ANY) + /* Get management address, if available */ + if ((ifa->ifa_addr != NULL) && + (ifa->ifa_addr->sa_family == AF_INET)) { + struct sockaddr_in *sa; + sa = (struct sockaddr_in *)ifa->ifa_addr; + if ((ntohl(*(u_int32_t*)&sa->sin_addr) != INADDR_LOOPBACK) && + (cfg->g_mgmt_pattern == NULL)) { + memcpy(&cfg->g_lchassis.c_mgmt, + &sa->sin_addr, + sizeof(struct in_addr)); + cfg->g_lchassis.c_mgmt_if = if_nametoindex(ifa->ifa_name); + } + else if (cfg->g_mgmt_pattern != NULL) { + char *ip; + ip = inet_ntoa(sa->sin_addr); + if (fnmatch(cfg->g_mgmt_pattern, + ip, 0) == 0) { + memcpy(&cfg->g_lchassis.c_mgmt, + &sa->sin_addr, + sizeof(struct in_addr)); + cfg->g_lchassis.c_mgmt_if = + if_nametoindex(ifa->ifa_name); + } + } + } + + if (iface_is_bridge(cfg, ifa->ifa_name)) { + cfg->g_lchassis.c_cap_enabled |= LLDP_CAP_BRIDGE; + continue; + } + + if ((iface_is_vlan(cfg, ifa->ifa_name)) || + (iface_is_bond(cfg, ifa->ifa_name))) + continue; + + if (ifa->ifa_addr == NULL || + ifa->ifa_addr->sa_family != PF_PACKET) + continue; + + if (!(ifa->ifa_flags & IFF_MULTICAST)) + continue; + + sdl = (struct sockaddr_ll *)ifa->ifa_addr; + if (sdl->sll_hatype != ARPHRD_ETHER || !sdl->sll_halen) + continue; + + if (iface_is_wireless(cfg, ifa->ifa_name)) + cfg->g_lchassis.c_cap_enabled |= LLDP_CAP_WLAN; + + + if (lldpd_port_add(cfg, ifa) == NULL) + LLOG_WARNX("failed to allocate port %s, skip it", + ifa->ifa_name); + } + + freeifaddrs(ifap); + + lldpd_cleanup(cfg); + + lldpd_send_all(cfg); + lldpd_recv_all(cfg); +} + +void +lldpd_hangup(int sig) +{ + /* Re-execute */ + LLOG_INFO("sighup received, reloading"); + lldpd_exit(); + execv(saved_argv[0], saved_argv); +} + +void +lldpd_shutdown(int sig) +{ + LLOG_INFO("signal received, exiting"); + exit(0); +} + +/* For signal handling */ +struct lldpd *gcfg = NULL; + +void +lldpd_exit() +{ + struct lldpd_hardware *hardware; + ctl_cleanup(gcfg->g_ctl, LLDPD_CTL_SOCKET); + TAILQ_FOREACH(hardware, &gcfg->g_hardware, h_entries) { + if (INTERFACE_OPENED(hardware)) + lldpd_iface_close(gcfg, hardware); + } +#ifdef USE_SNMP + if (gcfg->g_snmp) + agent_shutdown(); +#endif /* USE_SNMP */ +} + +int +main(int argc, char *argv[]) +{ + struct lldpd *cfg; + int ch, snmp = 0, debug = 0; + char *mgmtp = NULL; + char *popt, opts[] = "dxm:p:@ "; + int probe = 0, i, found; + + saved_argv = argv; + + /* + * Get and parse command line options + */ + popt = index(opts, '@'); + for (i=0; protos[i].mode != 0; i++) { + if (protos[i].enabled == 1) continue; + *(popt++) = protos[i].arg; + } + *popt = '\0'; + while ((ch = getopt(argc, argv, opts)) != -1) { + switch (ch) { + case 'd': + debug++; + break; + case 'm': + mgmtp = optarg; + break; + case 'p': + probe = atoi(optarg); + break; + case 'x': + snmp = 1; + break; + default: + found = 0; + for (i=0; protos[i].mode != 0; i++) { + if (protos[i].enabled) continue; + if (ch == protos[i].arg) { + protos[i].enabled = 1; + found = 1; + } + } + if (!found) + usage(); + } + } + + log_init(debug); + + if (probe == 0) probe = LLDPD_TTL; + + if ((cfg = (struct lldpd *) + calloc(1, sizeof(struct lldpd))) == NULL) + fatal(NULL); + + if (mgmtp != NULL) + cfg->g_mgmt_pattern = mgmtp; + + /* 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; + + /* Set system capabilities */ + cfg->g_lchassis.c_cap_available = LLDP_CAP_BRIDGE | LLDP_CAP_WLAN | + LLDP_CAP_ROUTER; + + /* Set TTL */ + cfg->g_lchassis.c_ttl = LLDPD_TTL; + + cfg->g_protocols = protos; + cfg->g_probe_time = probe; + for (i=0; protos[i].mode != 0; i++) + if (protos[i].enabled) { + cfg->g_multi++; + LLOG_INFO("protocol %s enabled", protos[i].name); + } else + LLOG_INFO("protocol %s disabled", protos[i].name); + cfg->g_multi--; + + TAILQ_INIT(&cfg->g_hardware); + +#ifdef USE_SNMP + if (snmp) { + cfg->g_snmp = 1; + agent_init(cfg, debug); + } +#endif /* USE_SNMP */ + + /* Create socket */ + if ((cfg->g_ctl = ctl_create(cfg, LLDPD_CTL_SOCKET)) == -1) + fatal("unable to create control socket " LLDPD_CTL_SOCKET); + + if (!debug && daemon(0, 0) != 0) { + ctl_cleanup(cfg->g_ctl, LLDPD_CTL_SOCKET); + fatal("failed to detach daemon"); + } + gcfg = cfg; + if (atexit(lldpd_exit) != 0) { + ctl_cleanup(cfg->g_ctl, LLDPD_CTL_SOCKET); + fatal("unable to set exit function"); + } + if (!debug) { + int pid; + char *spid; + if ((pid = open(LLDPD_PID_FILE, + O_TRUNC | O_CREAT | O_WRONLY)) == -1) + fatal("unable to open pid file " LLDPD_PID_FILE); + if (asprintf(&spid, "%d\n", getpid()) == -1) + fatal("unable to create pid file " LLDPD_PID_FILE); + if (write(pid, spid, strlen(spid)) == -1) + fatal("unable to write pid file " LLDPD_PID_FILE); + free(spid); + close(pid); + } + + /* Signal handling */ + signal(SIGHUP, lldpd_hangup); + signal(SIGINT, lldpd_shutdown); + signal(SIGTERM, lldpd_shutdown); + + for (;;) + lldpd_loop(cfg); + + return (0); +} diff --git a/src/lldpd.h b/src/lldpd.h new file mode 100644 index 00000000..3051a900 --- /dev/null +++ b/src/lldpd.h @@ -0,0 +1,292 @@ +/* + * Copyright (c) 2008 Vincent Bernat + * + * Permission to use, copy, modify, and 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. + */ + +#ifndef _LLDPD_H +#define _LLDPD_H + +#if HAVE_CONFIG_H + #include +#endif + +#define _GNU_SOURCE 1 +#include +#include +#include +#ifndef INCLUDE_LINUX_IF_H +#include +#else +#include +#include +#endif +#include +#include + +#include "compat.h" +#include "lldp.h" +#include "cdp.h" +#include "sonmp.h" +#include "edp.h" + +#define LLDPD_TTL 120 +#define LLDPD_TX_DELAY 30 +#define LLDPD_TX_MSGDELAY 1 +#define LLDPD_CTL_SOCKET "/var/run/lldpd.socket" +#define LLDPD_PID_FILE "/var/run/lldpd.pid" + +#define UNIX_PATH_MAX 108 + +#define USING_AGENTX_SUBAGENT_MODULE 1 + +struct lldpd_vlan { + TAILQ_ENTRY(lldpd_vlan) v_entries; + char *v_name; + u_int16_t v_vid; +}; +#define STRUCT_LLDPD_VLAN "Lsw" + +struct lldpd_chassis { + u_int8_t c_id_subtype; + char *c_id; + int c_id_len; + char *c_name; + char *c_descr; + + u_int16_t c_cap_available; + u_int16_t c_cap_enabled; + + u_int16_t c_ttl; + + struct in_addr c_mgmt; + u_int32_t c_mgmt_if; +}; +#define STRUCT_LLDPD_CHASSIS "bCsswwwll" + +struct lldpd_port { + u_int8_t p_id_subtype; + char *p_id; + int p_id_len; + char *p_descr; + + /* Dot3 stuff */ + u_int32_t p_aggregid; + u_int8_t p_autoneg_support; + u_int8_t p_autoneg_enabled; + u_int16_t p_autoneg_advertised; + u_int16_t p_mau_type; + + TAILQ_HEAD(, lldpd_vlan) p_vlans; +}; +#define STRUCT_LLDPD_PORT "bCslbbwwPP" + +struct lldpd_frame { + int size; + unsigned char frame[]; +}; + +struct lldpd_hardware { + TAILQ_ENTRY(lldpd_hardware) h_entries; + +#define INTERFACE_OPENED(x) ((x)->h_raw != -1) + + int h_raw; + int h_raw_real; /* For bonding */ + int h_master; /* For bonding */ + +#define LLDPD_MODE_ANY 0 +#define LLDPD_MODE_LLDP 1 +#define LLDPD_MODE_CDPV1 2 +#define LLDPD_MODE_CDPV2 3 +#define LLDPD_MODE_SONMP 4 +#define LLDPD_MODE_EDP 5 + int h_mode; + + int h_flags; + int h_mtu; + char h_ifname[IFNAMSIZ]; + u_int8_t h_lladdr[ETHER_ADDR_LEN]; + + u_int64_t h_tx_cnt; + u_int64_t h_rx_cnt; + u_int64_t h_rx_discarded_cnt; + u_int64_t h_rx_ageout_cnt; + + u_int8_t *h_proto_macs; + time_t h_start_probe; + + struct lldpd_port h_lport; + time_t h_llastchange; + struct lldpd_frame *h_llastframe; + + time_t h_rlastchange; + time_t h_rlastupdate; + int h_rid; + struct lldpd_frame *h_rlastframe; + struct lldpd_port *h_rport; + struct lldpd_chassis *h_rchassis; +}; + +struct lldpd_interface { + TAILQ_ENTRY(lldpd_interface) next; + char *name; +}; +#define STRUCT_LLDPD_INTERFACE "Ls" + +struct lldpd_client { + TAILQ_ENTRY(lldpd_client) next; + int fd; +}; + +#define PROTO_SEND_SIG struct lldpd *, struct lldpd_chassis *, struct lldpd_hardware * +#define PROTO_DECODE_SIG struct lldpd *, char *, int, struct lldpd_hardware *, struct lldpd_chassis **, struct lldpd_port ** +#define PROTO_GUESS_SIG char *, int + +struct lldpd; +struct protocol { + int mode; /* > 0 mode identifier (unique per protocol) */ + int enabled; /* Is this protocol enabled? */ + char *name; /* Name of protocol */ + char arg; /* Argument to enable this protocol */ + int(*send)(PROTO_SEND_SIG); /* How to send a frame */ + int(*decode)(PROTO_DECODE_SIG); /* How to decode a frame */ + int(*guess)(PROTO_GUESS_SIG); /* Can be NULL, use MAC address in this case */ + u_int8_t mac[ETH_ALEN]; /* Destination MAC address used by this protocol */ + struct sock_filter *filter; /* BPF filter */ + size_t filterlen; /* Size of BPF filter */ +}; + +struct lldpd { + int g_sock; + int g_delay; + + struct protocol *g_protocols; + int g_multi; /* Set to 1 if multiple protocols */ + int g_probe_time; + + time_t g_lastsent; + int g_lastrid; +#ifdef USE_SNMP + int g_snmp; +#endif /* USE_SNMP */ + + /* Unix socket handling */ + int g_ctl; + TAILQ_HEAD(, lldpd_client) g_clients; + + char *g_mgmt_pattern; + + struct lldpd_chassis g_lchassis; + + TAILQ_HEAD(, lldpd_hardware) g_hardware; +}; + +enum hmsg_type { + HMSG_NONE, + HMSG_GET_INTERFACES, + HMSG_GET_CHASSIS, + HMSG_GET_PORT, + HMSG_GET_VLANS, + HMSG_SHUTDOWN +}; + +struct hmsg_hdr { + enum hmsg_type type; + int16_t len; + pid_t pid; +} __attribute__ ((__packed__)); + +struct hmsg { + struct hmsg_hdr hdr; + void *data; +} __attribute__ ((__packed__)); + +#define HMSG_HEADER_SIZE sizeof(struct hmsg_hdr) +#define MAX_HMSGSIZE 8192 + +/* lldpd.c */ +void lldpd_cleanup(struct lldpd *); +void lldpd_vlan_cleanup(struct lldpd_port *); +void lldpd_remote_cleanup(struct lldpd *, struct lldpd_hardware *, int); +void lldpd_port_cleanup(struct lldpd_port *); +void lldpd_chassis_cleanup(struct lldpd_chassis *); + +/* lldp.c */ +int lldp_send(PROTO_SEND_SIG); +int lldp_decode(PROTO_DECODE_SIG); + +/* cdp.c */ +int cdpv1_send(PROTO_SEND_SIG); +int cdpv2_send(PROTO_SEND_SIG); +int cdp_decode(PROTO_DECODE_SIG); +int cdpv1_guess(PROTO_GUESS_SIG); +int cdpv2_guess(PROTO_GUESS_SIG); + +/* sonmp.c */ +int sonmp_send(PROTO_SEND_SIG); +int sonmp_decode(PROTO_DECODE_SIG); + +/* edp.c */ +int edp_send(PROTO_SEND_SIG); +int edp_decode(PROTO_DECODE_SIG); + +/* ctl.c */ +int ctl_create(struct lldpd *, char *); +int ctl_connect(char *); +void ctl_cleanup(int, char *); +int ctl_accept(struct lldpd *, int); +int ctl_close(struct lldpd *, int); +void ctl_msg_init(struct hmsg *, enum hmsg_type); +int ctl_msg_send(int, struct hmsg *); +int ctl_msg_recv(int, struct hmsg *); +int ctl_msg_pack_list(char *, void *, unsigned int, struct hmsg *, void **); +int ctl_msg_unpack_list(char *, void *, unsigned int, struct hmsg *, void **); +int ctl_msg_pack_structure(char *, void *, unsigned int, struct hmsg *, void **); +int ctl_msg_unpack_structure(char *, void *, unsigned int, struct hmsg *, void **); + +/* features.c */ +int iface_is_bridge(struct lldpd *, const char *); +int iface_is_bridged(struct lldpd *, const char *); +int iface_is_wireless(struct lldpd *, const char *); +int iface_is_vlan(struct lldpd *, const char *); +int iface_is_bond(struct lldpd *, const char *); +int iface_is_bond_slave(struct lldpd *, + const char *, const char *); +int iface_is_enslaved(struct lldpd *, const char *); + +/* log.c */ +void log_init(int); +void log_warn(const char *, ...); +#define LLOG_WARN(x,...) log_warn("%s: " x, __FUNCTION__, ##__VA_ARGS__) +void log_warnx(const char *, ...); +#define LLOG_WARNX(x,...) log_warnx("%s: " x, __FUNCTION__, ##__VA_ARGS__) +void log_info(const char *, ...); +#define LLOG_INFO(x,...) log_info("%s: " x, __FUNCTION__, ##__VA_ARGS__) +void log_debug(const char *, ...); +#define LLOG_DEBUG(x,...) log_debug("%s: " x, __FUNCTION__, ##__VA_ARGS__) +void fatal(const char *); +void fatalx(const char *); + +/* agent.c */ +void agent_shutdown(); +void agent_init(struct lldpd *, int); + +/* strlcpy.c */ +size_t strlcpy(char *, const char *, size_t); + +/* iov.c */ +void iov_dump(struct lldpd_frame **, struct iovec *, int); +u_int16_t iov_checksum(struct iovec *, int, int); + +#endif /* _LLDPD_H */ diff --git a/src/log.c b/src/log.c new file mode 100644 index 00000000..7fa90bbf --- /dev/null +++ b/src/log.c @@ -0,0 +1,161 @@ +/* $OpenBSD: log.c,v 1.11 2007/12/07 17:17:00 reyk Exp $ */ + +/* + * Copyright (c) 2003, 2004 Henning Brauer + * + * Permission to use, copy, modify, and 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 MIND, 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 + +#include +#include +#include +#include +#include + +void log_init(int); +void log_warn(const char *, ...); +void log_warnx(const char *, ...); +void log_info(const char *, ...); +void log_debug(const char *, ...); +void fatal(const char *); +void fatalx(const char *); + +int debug; + +void vlog(int, const char *, va_list); +void logit(int, const char *, ...); + +void +log_init(int n_debug) +{ + extern char *__progname; + + debug = n_debug; + + if (!debug) + openlog(__progname, LOG_PID | LOG_NDELAY, LOG_DAEMON); + + tzset(); +} + +void +logit(int pri, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vlog(pri, fmt, ap); + va_end(ap); +} + +void +vlog(int pri, const char *fmt, va_list ap) +{ + char *nfmt; + + if (debug) { + /* best effort in out of mem situations */ + if (asprintf(&nfmt, "%s\n", fmt) == -1) { + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); + } else { + vfprintf(stderr, nfmt, ap); + free(nfmt); + } + fflush(stderr); + } else + vsyslog(pri, fmt, ap); +} + + +void +log_warn(const char *emsg, ...) +{ + char *nfmt; + va_list ap; + + /* best effort to even work in out of memory situations */ + if (emsg == NULL) + logit(LOG_CRIT, "%s", strerror(errno)); + else { + va_start(ap, emsg); + + if (asprintf(&nfmt, "%s: %s", emsg, strerror(errno)) == -1) { + /* we tried it... */ + vlog(LOG_CRIT, emsg, ap); + logit(LOG_CRIT, "%s", strerror(errno)); + } else { + vlog(LOG_CRIT, nfmt, ap); + free(nfmt); + } + va_end(ap); + } +} + +void +log_warnx(const char *emsg, ...) +{ + va_list ap; + + va_start(ap, emsg); + vlog(LOG_CRIT, emsg, ap); + va_end(ap); +} + +void +log_info(const char *emsg, ...) +{ + va_list ap; + + va_start(ap, emsg); + vlog(LOG_INFO, emsg, ap); + va_end(ap); +} + +void +log_debug(const char *emsg, ...) +{ + va_list ap; + + if (debug > 1) { + va_start(ap, emsg); + vlog(LOG_DEBUG, emsg, ap); + va_end(ap); + } +} + +void +fatal(const char *emsg) +{ + if (emsg == NULL) + logit(LOG_CRIT, "fatal: %s", strerror(errno)); + else + if (errno) + logit(LOG_CRIT, "fatal: %s: %s", + emsg, strerror(errno)); + else + logit(LOG_CRIT, "fatal: %s", emsg); + + exit(1); +} + +void +fatalx(const char *emsg) +{ + errno = 0; + fatal(emsg); +} diff --git a/src/sonmp.c b/src/sonmp.c new file mode 100644 index 00000000..19067c70 --- /dev/null +++ b/src/sonmp.c @@ -0,0 +1,336 @@ +/* + * Copyright (c) 2008 Vincent Bernat + * + * Permission to use, copy, modify, and 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 +#include +#include +#include + +struct sonmp_chassis sonmp_chassis_types[] = { + {1, "unknown (via SONMP)"}, + {2, "Nortel 3000"}, + {3, "Nortel 3030"}, + {4, "Nortel 2310"}, + {5, "Nortel 2810"}, + {6, "Nortel 2912"}, + {7, "Nortel 2914"}, + {8, "Nortel 271x"}, + {9, "Nortel 2813"}, + {10, "Nortel 2814"}, + {11, "Nortel 2915"}, + {12, "Nortel 5000"}, + {13, "Nortel 2813SA"}, + {14, "Nortel 2814SA"}, + {15, "Nortel 810M"}, + {16, "Nortel EtherCell"}, + {17, "Nortel 5005"}, + {18, "Alcatel Ethernet workgroup conc."}, + {20, "Nortel 2715SA"}, + {21, "Nortel 2486"}, + {22, "Nortel 28000 series"}, + {23, "Nortel 23000 series"}, + {24, "Nortel 5DN00x series"}, + {25, "BayStack Ethernet"}, + {26, "Nortel 23100 series"}, + {27, "Nortel 100Base-T Hub"}, + {28, "Nortel 3000 Fast Ethernet"}, + {29, "Nortel Orion switch"}, + {30, "unknown"}, + {31, "Nortel DDS "}, + {32, "Nortel Centillion"}, + {33, "Nortel Centillion"}, + {34, "Nortel Centillion"}, + {35, "BayStack 301"}, + {36, "BayStack TokenRing Hub"}, + {37, "Nortel FVC Multimedia Switch"}, + {38, "Nortel Switch Node"}, + {39, "BayStack 302 Switch"}, + {40, "BayStack 350 Switch"}, + {41, "BayStack 150 Ethernet Hub"}, + {42, "Nortel Centillion 50N switch"}, + {43, "Nortel Centillion 50T switch"}, + {44, "BayStack 303 and 304 Switches"}, + {45, "BayStack 200 Ethernet Hub"}, + {46, "BayStack 250 10/100 Ethernet Hub"}, + {48, "BayStack 450 10/100/1000 Switches"}, + {49, "BayStack 410 10/100 Switches"}, + {50, "Nortel Ethernet Routing 1200 L3 Switch"}, + {51, "Nortel Ethernet Routing 1250 L3 Switch"}, + {52, "Nortel Ethernet Routing 1100 L3 Switch"}, + {53, "Nortel Ethernet Routing 1150 L3 Switch"}, + {54, "Nortel Ethernet Routing 1050 L3 Switch"}, + {55, "Nortel Ethernet Routing 1051 L3 Switch"}, + {56, "Nortel Ethernet Routing 8610 L3 Switch"}, + {57, "Nortel Ethernet Routing 8606 L3 Switch"}, + {58, "Nortel Ethernet Routing Switch 8010"}, + {59, "Nortel Ethernet Routing Switch 8006"}, + {60, "BayStack 670 wireless access point"}, + {61, "Nortel Ethernet Routing Switch 740 "}, + {62, "Nortel Ethernet Routing Switch 750 "}, + {63, "Nortel Ethernet Routing Switch 790"}, + {64, "Nortel Business Policy Switch 2000 10/100 Switches"}, + {65, "Nortel Ethernet Routing 8110 L2 Switch"}, + {66, "Nortel Ethernet Routing 8106 L2 Switch"}, + {67, "BayStack 3580 Gig Switch"}, + {68, "BayStack 10 Power Supply Unit"}, + {69, "BayStack 420 10/100 Switch"}, + {70, "OPTera Metro 1200 Ethernet Service Module"}, + {71, "Nortel Ethernet Routing Switch 8010co"}, + {72, "Nortel Ethernet Routing 8610co L3 switch"}, + {73, "Nortel Ethernet Routing 8110co L2 switch"}, + {74, "Nortel Ethernet Routing 8003"}, + {75, "Nortel Ethernet Routing 8603 L3 switch"}, + {76, "Nortel Ethernet Routing 8103 L2 switch"}, + {77, "BayStack 380 10/100/1000 Switch"}, + {78, "Nortel Ethernet Switch 470-48T"}, + {79, "OPTera Metro 1450 Ethernet Service Module"}, + {80, "OPTera Metro 1400 Ethernet Service Module"}, + {81, "Alteon Switch Family"}, + {82, "Ethernet Switch 460-24T-PWR"}, + {83, "OPTera Metro 8010 OPM L2 Switch"}, + {84, "OPTera Metro 8010co OPM L2 Switch"}, + {85, "OPTera Metro 8006 OPM L2 Switch"}, + {86, "OPTera Metro 8003 OPM L2 Switch"}, + {87, "Alteon 180e"}, + {88, "Alteon AD3"}, + {89, "Alteon 184"}, + {90, "Alteon AD4"}, + {91, "Nortel Ethernet Routing 1424 L3 switch"}, + {92, "Nortel Ethernet Routing 1648 L3 switch"}, + {93, "Nortel Ethernet Routing 1612 L3 switch"}, + {94, "Nortel Ethernet Routing 1624 L3 switch "}, + {95, "BayStack 380-24F Fiber 1000 Switch"}, + {96, "Nortel Ethernet Routing Switch 5510-24T"}, + {97, "Nortel Ethernet Routing Switch 5510-48T"}, + {98, "Nortel Ethernet Switch 470-24T"}, + {99, "Nortel Networks Wireless LAN Access Point 2220"}, + {100, "Ethernet Routing RBS 2402 L3 switch"}, + {101, "Alteon Application Switch 2424 "}, + {102, "Alteon Application Switch 2224 "}, + {103, "Alteon Application Switch 2208 "}, + {104, "Alteon Application Switch 2216"}, + {105, "Alteon Application Switch 3408"}, + {106, "Alteon Application Switch 3416"}, + {107, "Nortel Networks Wireless LAN SecuritySwitch 2250"}, + {108, "Ethernet Switch 425-48T"}, + {109, "Ethernet Switch 425-24T"}, + {110, "Nortel Networks Wireless LAN Access Point 2221"}, + {111, "Nortel Metro Ethernet Service Unit 24-T SPF switch"}, + {112, "Nortel Metro Ethernet Service Unit 24-T LX DC switch"}, + {113, "Nortel Ethernet Routing Switch 8300 10-slot chassis"}, + {114, "Nortel Ethernet Routing Switch 8300 6-slot chassis"}, + {115, "Nortel Ethernet Routing Switch 5520-24T-PWR"}, + {116, "Nortel Ethernet Routing Switch 5520-48T-PWR"}, + {117, "Nortel Networks VPN Gateway 3050"}, + {118, "Alteon SSL 310 10/100"}, + {119, "Alteon SSL 310 10/100 Fiber"}, + {120, "Alteon SSL 310 10/100 FIPS"}, + {121, "Alteon SSL 410 10/100/1000"}, + {122, "Alteon SSL 410 10/100/1000 Fiber"}, + {123, "Alteon Application Switch 2424-SSL"}, + {124, "Nortel Ethernet Switch 325-24T"}, + {125, "Nortel Ethernet Switch 325-24G"}, + {126, "Nortel Networks Wireless LAN Access Point 2225"}, + {127, "Nortel Networks Wireless LAN SecuritySwitch 2270"}, + {128, "Nortel 24-port Ethernet Switch 470-24T-PWR"}, + {129, "Nortel 48-port Ethernet Switch 470-48T-PWR"}, + {130, "Nortel Ethernet Routing Switch 5530-24TFD"}, + {131, "Nortel Ethernet Switch 3510-24T"}, + {132, "Nortel Metro Ethernet Service Unit 12G AC L3 switch"}, + {133, "Nortel Metro Ethernet Service Unit 12G DC L3 switch"}, + {134, "Nortel Secure Access Switch"}, + {135, "Networks VPN Gateway 3070"}, + {136, "OPTera Metro 3500"}, + {137, "SMB BES 1010 24T"}, + {138, "SMB BES 1010 48T"}, + {139, "SMB BES 1020 24T PWR"}, + {140, "SMB BES 1020 48T PWR"}, + {141, "SMB BES 2010 24T"}, + {142, "SMB BES 2010 48T"}, + {143, "SMB BES 2020 24T PWR"}, + {144, "SMB BES 2020 48T PWR"}, + {145, "SMB BES 110 24T"}, + {146, "SMB BES 110 48T"}, + {147, "SMB BES 120 24T PWR"}, + {148, "SMB BES 120 48T PWR"}, + {149, "SMB BES 210 24T"}, + {150, "SMB BES 210 48T"}, + {151, "SMB BES 220 24T PWR"}, + {152, "SMB BES 220 48T PWR"}, + {153, "OME 6500"}, + {0, "unknown (via SONMP)"}, +}; + +int +sonmp_send(struct lldpd *global, struct lldpd_chassis *chassis, + struct lldpd_hardware *hardware) +{ + const u_int8_t mcastaddr[] = SONMP_MULTICAST_ADDR; + const u_int8_t llcorg[] = LLC_ORG_NORTEL; + struct sonmp frame; + memset(&frame, 0, sizeof(frame)); + memcpy(&frame.llc.ether.shost, &hardware->h_lladdr, + sizeof(frame.llc.ether.shost)); + memcpy(&frame.llc.ether.dhost, &mcastaddr, + sizeof(frame.llc.ether.dhost)); + frame.llc.ether.size = htons(sizeof(struct sonmp) - + sizeof(struct ieee8023)); + frame.llc.dsap = frame.llc.ssap = 0xaa; + frame.llc.control = 0x03; + memcpy(frame.llc.org, llcorg, sizeof(frame.llc.org)); + frame.llc.protoid = htons(LLC_PID_SONMP_HELLO); + memcpy(&frame.addr, &chassis->c_mgmt, sizeof(struct in_addr)); + frame.seg[2] = if_nametoindex(hardware->h_ifname); + frame.chassis = 1; /* Other */ + frame.backplane = 12; /* Ethernet, Fast Ethernet and Gigabit */ + frame.links = 1; /* Dunno what it is */ + frame.state = SONMP_TOPOLOGY_NEW; /* Should work. We have no state */ + + if (write((hardware->h_raw_real > 0) ? hardware->h_raw_real : + hardware->h_raw, &frame, sizeof(struct sonmp)) == -1) { + LLOG_WARN("unable to send packet on real device for %s", + hardware->h_ifname); + return ENETDOWN; + } + + frame.llc.protoid = htons(LLC_PID_SONMP_FLATNET); + frame.llc.ether.dhost[ETH_ALEN-1] = 1; + + if (write((hardware->h_raw_real > 0) ? hardware->h_raw_real : + hardware->h_raw, &frame, sizeof(struct sonmp)) == -1) { + LLOG_WARN("unable to send second SONMP packet on real device for %s", + hardware->h_ifname); + return ENETDOWN; + } + + hardware->h_tx_cnt++; + return 0; +} + +int +sonmp_decode(struct lldpd *cfg, char *frame, int s, + struct lldpd_hardware *hardware, + struct lldpd_chassis **newchassis, struct lldpd_port **newport) +{ + struct sonmp *f; + const u_int8_t mcastaddr[] = SONMP_MULTICAST_ADDR; + struct lldpd_chassis *chassis; + struct lldpd_port *port; + int i; + + if ((chassis = calloc(1, sizeof(struct lldpd_chassis))) == NULL) { + LLOG_WARN("failed to allocate remote chassis"); + return -1; + } + if ((port = calloc(1, sizeof(struct lldpd_port))) == NULL) { + LLOG_WARN("failed to allocate remote port"); + free(chassis); + return -1; + } + TAILQ_INIT(&port->p_vlans); + + if (s < sizeof(struct sonmp)) { + LLOG_WARNX("too short frame received on %s", hardware->h_ifname); + goto malformed; + } + f = (struct sonmp *)frame; + if (memcmp(f->llc.ether.dhost, mcastaddr, + sizeof(mcastaddr)) != 0) { + /* There is two multicast address. We just handle only one of + * them. */ + goto malformed; + } + if (f->llc.protoid != htons(LLC_PID_SONMP_HELLO)) { + LLOG_DEBUG("incorrect LLC protocol ID received on %s", + hardware->h_ifname); + goto malformed; + } + + chassis->c_id_subtype = LLDP_CHASSISID_SUBTYPE_ADDR; + if ((chassis->c_id = calloc(1, sizeof(struct in_addr) + 1)) == NULL) { + LLOG_WARN("unable to allocate memory for chassis id on %s", + hardware->h_ifname); + goto malformed; + } + chassis->c_id_len = sizeof(struct in_addr) + 1; + chassis->c_id[0] = 1; + memcpy(chassis->c_id + 1, &f->addr, sizeof(struct in_addr)); + if (asprintf(&chassis->c_name, "%s", inet_ntoa(f->addr)) == -1) { + LLOG_WARNX("unable to write chassis name for %s", + hardware->h_ifname); + goto malformed; + } + for (i=0; sonmp_chassis_types[i].type != 0; i++) { + if (sonmp_chassis_types[i].type == f->chassis) + break; + } + if (asprintf(&chassis->c_descr, "%s", + sonmp_chassis_types[i].description) == -1) { + LLOG_WARNX("unable to write chassis description for %s", + hardware->h_ifname); + goto malformed; + } + memcpy(&chassis->c_mgmt, &f->addr, sizeof(struct in_addr)); + chassis->c_ttl = LLDPD_TTL; + + port->p_id_subtype = LLDP_PORTID_SUBTYPE_LOCAL; + if (asprintf(&port->p_id, "%02x-%02x-%02x", + f->seg[0], f->seg[1], f->seg[2]) == -1) { + LLOG_WARN("unable to allocate memory for port id on %s", + hardware->h_ifname); + goto malformed; + } + port->p_id_len = strlen(port->p_id); + + if ((f->seg[0] == 0) && (f->seg[1] == 0)) { + if (asprintf(&port->p_descr, "port %d", + *(u_int8_t *)(&f->seg[2])) == -1) { + LLOG_WARNX("unable to write port description for %s", + hardware->h_ifname); + goto malformed; + } + } else if (f->seg[0] == 0) { + if (asprintf(&port->p_descr, "port %d/%d", + *(u_int8_t *)(&f->seg[1]), + *(u_int8_t *)(&f->seg[2])) == -1) { + LLOG_WARNX("unable to write port description for %s", + hardware->h_ifname); + goto malformed; + } + } else { + if (asprintf(&port->p_descr, "port %x:%x:%x", + f->seg[0], f->seg[1], f->seg[2]) == -1) { + LLOG_WARNX("unable to write port description for %s", + hardware->h_ifname); + goto malformed; + } + } + *newchassis = chassis; + *newport = port; + return 1; + +malformed: + free(chassis->c_id); + free(chassis->c_name); + free(chassis->c_descr); + free(chassis); + free(port->p_id); + free(port->p_descr); + free(port); + return -1; +} diff --git a/src/sonmp.h b/src/sonmp.h new file mode 100644 index 00000000..0b173bc2 --- /dev/null +++ b/src/sonmp.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2008 Vincent Bernat + * + * Permission to use, copy, modify, and 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. + */ + +#ifndef _SONMP_H +#define _SONMP_H + +#define SONMP_MULTICAST_ADDR { \ + 0x01, 0x00, 0x81, 0x00, 0x01, 0x00 \ +} +#define LLC_ORG_NORTEL { 0x00, 0x00, 0x81 } +#define LLC_PID_SONMP_HELLO 0x01a2 +#define LLC_PID_SONMP_FLATNET 0x01a1 + +#include "llc.h" + +struct sonmp { + struct ethllc llc; + struct in_addr addr; + u_int8_t seg[3]; + u_int8_t chassis; + u_int8_t backplane; + u_int8_t state; + u_int8_t links; +} __attribute__ ((__packed__)); + +struct sonmp_chassis { + int type; + char *description; +}; + +#define SONMP_TOPOLOGY_CHANGED 1 +#define SONMP_TOPOLOGY_UNCHANGED 2 +#define SONMP_TOPOLOGY_NEW 3 + +#endif /* _SONMP_H */ diff --git a/src/strlcpy.c b/src/strlcpy.c new file mode 100644 index 00000000..d32b6590 --- /dev/null +++ b/src/strlcpy.c @@ -0,0 +1,51 @@ +/* $OpenBSD: strlcpy.c,v 1.11 2006/05/05 15:27:38 millert Exp $ */ + +/* + * Copyright (c) 1998 Todd C. Miller + * + * Permission to use, copy, modify, and 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 +#include + +/* + * Copy src to string dst of size siz. At most siz-1 characters + * will be copied. Always NUL terminates (unless siz == 0). + * Returns strlen(src); if retval >= siz, truncation occurred. + */ +size_t +strlcpy(char *dst, const char *src, size_t siz) +{ + char *d = dst; + const char *s = src; + size_t n = siz; + + /* Copy as many bytes as will fit */ + if (n != 0) { + while (--n != 0) { + if ((*d++ = *s++) == '\0') + break; + } + } + + /* Not enough room in dst, add NUL and traverse rest of src */ + if (n == 0) { + if (siz != 0) + *d = '\0'; /* NUL-terminate dst */ + while (*s++) + ; + } + + return(s - src - 1); /* count does not include NUL */ +}