summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/Makefile.am16
-rw-r--r--src/Makefile.in773
-rw-r--r--src/client/Makefile.am49
-rw-r--r--src/client/Makefile.in1276
-rw-r--r--src/client/README.conf8
-rw-r--r--src/client/client.h148
-rw-r--r--src/client/commands.c855
-rw-r--r--src/client/completion/_lldpcli48
-rwxr-xr-xsrc/client/completion/lldpcli26
-rw-r--r--src/client/conf-dot3.c35
-rw-r--r--src/client/conf-inv.c161
-rw-r--r--src/client/conf-lldp.c762
-rw-r--r--src/client/conf-med.c499
-rw-r--r--src/client/conf-power.c389
-rw-r--r--src/client/conf-system.c602
-rw-r--r--src/client/conf.c44
-rw-r--r--src/client/display.c1039
-rw-r--r--src/client/json_writer.c374
-rw-r--r--src/client/kv_writer.c133
-rw-r--r--src/client/lldpcli.8.in1151
-rw-r--r--src/client/lldpcli.c611
-rw-r--r--src/client/lldpctl.81
-rw-r--r--src/client/misc.c73
-rw-r--r--src/client/show.c364
-rw-r--r--src/client/text_writer.c183
-rw-r--r--src/client/tokenizer.c116
-rw-r--r--src/client/utf8.c90
-rw-r--r--src/client/writer.h57
-rw-r--r--src/client/xml_writer.c158
-rw-r--r--src/compat/Makefile.am8
-rw-r--r--src/compat/Makefile.in725
-rw-r--r--src/compat/asprintf.c85
-rw-r--r--src/compat/compat.h93
-rw-r--r--src/compat/daemon.c66
-rw-r--r--src/compat/empty.c21
-rw-r--r--src/compat/getline.c106
-rw-r--r--src/compat/malloc.c16
-rw-r--r--src/compat/realloc.c17
-rw-r--r--src/compat/setproctitle.c9
-rw-r--r--src/compat/strlcpy.c51
-rw-r--r--src/compat/strndup.c24
-rw-r--r--src/compat/strnlen.c15
-rw-r--r--src/compat/strtonum.c65
-rw-r--r--src/compat/vsyslog.c17
-rw-r--r--src/ctl.c261
-rw-r--r--src/ctl.h64
-rw-r--r--src/daemon/Makefile.am180
-rw-r--r--src/daemon/Makefile.in1545
-rw-r--r--src/daemon/agent.c1939
-rw-r--r--src/daemon/agent.h35
-rw-r--r--src/daemon/agent_priv.c243
-rw-r--r--src/daemon/bitmap.c63
-rw-r--r--src/daemon/client.c700
-rw-r--r--src/daemon/dmi-dummy.c57
-rw-r--r--src/daemon/dmi-freebsd.c82
-rw-r--r--src/daemon/dmi-linux.c90
-rw-r--r--src/daemon/dmi-openbsd.c73
-rw-r--r--src/daemon/dmi-osx.c109
-rw-r--r--src/daemon/dtrace2systemtap.awk25
-rw-r--r--src/daemon/event.c911
-rw-r--r--src/daemon/forward-bsd.c31
-rw-r--r--src/daemon/forward-linux.c59
-rw-r--r--src/daemon/forward-solaris.c27
-rw-r--r--src/daemon/frame.c66
-rw-r--r--src/daemon/frame.h102
-rw-r--r--src/daemon/interfaces-bpf.c117
-rw-r--r--src/daemon/interfaces-bsd.c641
-rw-r--r--src/daemon/interfaces-linux.c1046
-rw-r--r--src/daemon/interfaces-solaris.c174
-rw-r--r--src/daemon/interfaces.c764
-rw-r--r--src/daemon/lldp-tlv.h83
-rw-r--r--src/daemon/lldpd.8.in424
-rw-r--r--src/daemon/lldpd.c2020
-rw-r--r--src/daemon/lldpd.h429
-rw-r--r--src/daemon/lldpd.service.in22
-rw-r--r--src/daemon/lldpd.sysusers.conf.in6
-rw-r--r--src/daemon/main.c17
-rw-r--r--src/daemon/netlink.c993
-rw-r--r--src/daemon/pattern.c80
-rw-r--r--src/daemon/priv-bsd.c202
-rw-r--r--src/daemon/priv-linux.c337
-rw-r--r--src/daemon/priv-seccomp.c210
-rw-r--r--src/daemon/priv.c761
-rw-r--r--src/daemon/privsep.c26
-rw-r--r--src/daemon/privsep_fd.c129
-rw-r--r--src/daemon/privsep_io.c100
-rw-r--r--src/daemon/probes.d115
-rw-r--r--src/daemon/protocols/cdp.c711
-rw-r--r--src/daemon/protocols/cdp.h70
-rw-r--r--src/daemon/protocols/edp.c514
-rw-r--r--src/daemon/protocols/edp.h41
-rw-r--r--src/daemon/protocols/lldp.c1315
-rw-r--r--src/daemon/protocols/sonmp.c410
-rw-r--r--src/daemon/protocols/sonmp.h42
-rw-r--r--src/daemon/trace.h8
-rw-r--r--src/daemon/usr.sbin.lldpd.in65
-rw-r--r--src/lib/Makefile.am75
-rw-r--r--src/lib/Makefile.in978
-rw-r--r--src/lib/atom.c655
-rw-r--r--src/lib/atom.h344
-rw-r--r--src/lib/atoms/chassis.c326
-rw-r--r--src/lib/atoms/config.c338
-rw-r--r--src/lib/atoms/custom.c226
-rw-r--r--src/lib/atoms/dot1.c250
-rw-r--r--src/lib/atoms/dot3.c474
-rw-r--r--src/lib/atoms/interface.c118
-rw-r--r--src/lib/atoms/med.c1123
-rw-r--r--src/lib/atoms/mgmt.c149
-rw-r--r--src/lib/atoms/port.c862
-rw-r--r--src/lib/connection.c306
-rw-r--r--src/lib/errors.c75
-rw-r--r--src/lib/fixedpoint.c255
-rw-r--r--src/lib/fixedpoint.h42
-rw-r--r--src/lib/helpers.c78
-rw-r--r--src/lib/helpers.h24
-rw-r--r--src/lib/lldpctl.h1186
-rw-r--r--src/lib/lldpctl.map53
-rw-r--r--src/lib/lldpctl.pc.in6
-rw-r--r--src/lldp-const.h352
-rw-r--r--src/lldpd-structs.c232
-rw-r--r--src/lldpd-structs.h568
-rw-r--r--src/log.c279
-rw-r--r--src/log.h39
-rw-r--r--src/marshal.c380
-rw-r--r--src/marshal.h158
-rw-r--r--src/version.c138
126 files changed, 39982 insertions, 0 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644
index 0000000..19644f3
--- /dev/null
+++ b/src/Makefile.am
@@ -0,0 +1,16 @@
+AM_CFLAGS = -I $(top_srcdir)/include $(LLDP_CFLAGS)
+AM_CPPFLAGS = $(LLDP_CPPFLAGS) -DSYSCONFDIR='"$(sysconfdir)"'
+AM_LDFLAGS = $(LLDP_LDFLAGS)
+
+noinst_LTLIBRARIES = libcommon-daemon-lib.la libcommon-daemon-client.la
+include_HEADERS = lldp-const.h
+
+libcommon_daemon_lib_la_SOURCES = \
+ log.c log.h version.c \
+ marshal.c marshal.h \
+ ctl.c ctl.h \
+ lldpd-structs.c lldpd-structs.h lldp-const.h
+libcommon_daemon_lib_la_LIBADD = compat/libcompat.la
+
+libcommon_daemon_client_la_SOURCES = log.c log.h version.c lldp-const.h
+libcommon_daemon_client_la_LIBADD = compat/libcompat.la
diff --git a/src/Makefile.in b/src/Makefile.in
new file mode 100644
index 0000000..c04901f
--- /dev/null
+++ b/src/Makefile.in
@@ -0,0 +1,773 @@
+# Makefile.in generated by automake 1.16.5 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2021 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/alignof.m4 \
+ $(top_srcdir)/m4/args.m4 \
+ $(top_srcdir)/m4/ax_build_date_epoch.m4 \
+ $(top_srcdir)/m4/ax_cflags_gcc_option.m4 \
+ $(top_srcdir)/m4/ax_ld_check_flag.m4 \
+ $(top_srcdir)/m4/ax_lib_readline.m4 \
+ $(top_srcdir)/m4/ax_prog_doxygen.m4 \
+ $(top_srcdir)/m4/config_subdirs.m4 \
+ $(top_srcdir)/m4/ld-version-script.m4 \
+ $(top_srcdir)/m4/libcap.m4 $(top_srcdir)/m4/libevent.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/os.m4 \
+ $(top_srcdir)/m4/progname.m4 $(top_srcdir)/m4/seccomp.m4 \
+ $(top_srcdir)/m4/snmp.m4 $(top_srcdir)/m4/stdint.m4 \
+ $(top_srcdir)/m4/systemtap.m4 $(top_srcdir)/m4/xml2.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(include_HEADERS) \
+ $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+libcommon_daemon_client_la_DEPENDENCIES = compat/libcompat.la
+am_libcommon_daemon_client_la_OBJECTS = log.lo version.lo
+libcommon_daemon_client_la_OBJECTS = \
+ $(am_libcommon_daemon_client_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+libcommon_daemon_lib_la_DEPENDENCIES = compat/libcompat.la
+am_libcommon_daemon_lib_la_OBJECTS = log.lo version.lo marshal.lo \
+ ctl.lo lldpd-structs.lo
+libcommon_daemon_lib_la_OBJECTS = \
+ $(am_libcommon_daemon_lib_la_OBJECTS)
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/ctl.Plo \
+ ./$(DEPDIR)/lldpd-structs.Plo ./$(DEPDIR)/log.Plo \
+ ./$(DEPDIR)/marshal.Plo ./$(DEPDIR)/version.Plo
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libcommon_daemon_client_la_SOURCES) \
+ $(libcommon_daemon_lib_la_SOURCES)
+DIST_SOURCES = $(libcommon_daemon_client_la_SOURCES) \
+ $(libcommon_daemon_lib_la_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+am__installdirs = "$(DESTDIR)$(includedir)"
+HEADERS = $(include_HEADERS)
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+APPARMORDIR = @APPARMORDIR@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CONFIGURE_ARGS = @CONFIGURE_ARGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CSCOPE = @CSCOPE@
+CTAGS = @CTAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DLLTOOL = @DLLTOOL@
+DOXYGEN_PAPER_SIZE = @DOXYGEN_PAPER_SIZE@
+DSYMUTIL = @DSYMUTIL@
+DTRACE = @DTRACE@
+DUMPBIN = @DUMPBIN@
+DX_CONFIG = @DX_CONFIG@
+DX_DOCDIR = @DX_DOCDIR@
+DX_DOT = @DX_DOT@
+DX_DOXYGEN = @DX_DOXYGEN@
+DX_DVIPS = @DX_DVIPS@
+DX_EGREP = @DX_EGREP@
+DX_ENV = @DX_ENV@
+DX_FLAG_chi = @DX_FLAG_chi@
+DX_FLAG_chm = @DX_FLAG_chm@
+DX_FLAG_doc = @DX_FLAG_doc@
+DX_FLAG_dot = @DX_FLAG_dot@
+DX_FLAG_html = @DX_FLAG_html@
+DX_FLAG_man = @DX_FLAG_man@
+DX_FLAG_pdf = @DX_FLAG_pdf@
+DX_FLAG_ps = @DX_FLAG_ps@
+DX_FLAG_rtf = @DX_FLAG_rtf@
+DX_FLAG_xml = @DX_FLAG_xml@
+DX_HHC = @DX_HHC@
+DX_LATEX = @DX_LATEX@
+DX_MAKEINDEX = @DX_MAKEINDEX@
+DX_PDFLATEX = @DX_PDFLATEX@
+DX_PERL = @DX_PERL@
+DX_PROJECT = @DX_PROJECT@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+ETAGS = @ETAGS@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+FILECMD = @FILECMD@
+FUZZ_DECODE_ENGINE = @FUZZ_DECODE_ENGINE@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LAUNCHDDAEMONSDIR = @LAUNCHDDAEMONSDIR@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LLDPD_CTL_SOCKET = @LLDPD_CTL_SOCKET@
+LLDPD_PID_FILE = @LLDPD_PID_FILE@
+LLDP_BIN_LDFLAGS = @LLDP_BIN_LDFLAGS@
+LLDP_CFLAGS = @LLDP_CFLAGS@
+LLDP_CPPFLAGS = @LLDP_CPPFLAGS@
+LLDP_LDFLAGS = @LLDP_LDFLAGS@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+NETLINK_MAX_RECEIVE_BUFSIZE = @NETLINK_MAX_RECEIVE_BUFSIZE@
+NETLINK_RECEIVE_BUFSIZE = @NETLINK_RECEIVE_BUFSIZE@
+NETLINK_SEND_BUFSIZE = @NETLINK_SEND_BUFSIZE@
+NETSNMP_CFLAGS = @NETSNMP_CFLAGS@
+NETSNMP_CONFIG = @NETSNMP_CONFIG@
+NETSNMP_LIBS = @NETSNMP_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+PRIVSEP_CHROOT = @PRIVSEP_CHROOT@
+PRIVSEP_GROUP = @PRIVSEP_GROUP@
+PRIVSEP_USER = @PRIVSEP_USER@
+RANLIB = @RANLIB@
+READLINE_LIBS = @READLINE_LIBS@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+SYSTEMDSYSTEMUNITDIR = @SYSTEMDSYSTEMUNITDIR@
+SYSUSERSDIR = @SYSUSERSDIR@
+VERSION = @VERSION@
+XML2_CONFIG = @XML2_CONFIG@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+apparmordir = @apparmordir@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+check_CFLAGS = @check_CFLAGS@
+check_LIBS = @check_LIBS@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+launchddaemonsdir = @launchddaemonsdir@
+libbsd_CFLAGS = @libbsd_CFLAGS@
+libbsd_LIBS = @libbsd_LIBS@
+libcap_CFLAGS = @libcap_CFLAGS@
+libcap_LIBS = @libcap_LIBS@
+libdir = @libdir@
+libevent_CFLAGS = @libevent_CFLAGS@
+libevent_LDFLAGS = @libevent_LDFLAGS@
+libevent_LIBS = @libevent_LIBS@
+libexecdir = @libexecdir@
+libseccomp_CFLAGS = @libseccomp_CFLAGS@
+libseccomp_LIBS = @libseccomp_LIBS@
+libxml2_CFLAGS = @libxml2_CFLAGS@
+libxml2_LIBS = @libxml2_LIBS@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+pkgconfigdir = @pkgconfigdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+systemdsystemunitdir = @systemdsystemunitdir@
+sysusersdir = @sysusersdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+AM_CFLAGS = -I $(top_srcdir)/include $(LLDP_CFLAGS)
+AM_CPPFLAGS = $(LLDP_CPPFLAGS) -DSYSCONFDIR='"$(sysconfdir)"'
+AM_LDFLAGS = $(LLDP_LDFLAGS)
+noinst_LTLIBRARIES = libcommon-daemon-lib.la libcommon-daemon-client.la
+include_HEADERS = lldp-const.h
+libcommon_daemon_lib_la_SOURCES = \
+ log.c log.h version.c \
+ marshal.c marshal.h \
+ ctl.c ctl.h \
+ lldpd-structs.c lldpd-structs.h lldp-const.h
+
+libcommon_daemon_lib_la_LIBADD = compat/libcompat.la
+libcommon_daemon_client_la_SOURCES = log.c log.h version.c lldp-const.h
+libcommon_daemon_client_la_LIBADD = compat/libcompat.la
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstLTLIBRARIES:
+ -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES)
+ @list='$(noinst_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+libcommon-daemon-client.la: $(libcommon_daemon_client_la_OBJECTS) $(libcommon_daemon_client_la_DEPENDENCIES) $(EXTRA_libcommon_daemon_client_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(libcommon_daemon_client_la_OBJECTS) $(libcommon_daemon_client_la_LIBADD) $(LIBS)
+
+libcommon-daemon-lib.la: $(libcommon_daemon_lib_la_OBJECTS) $(libcommon_daemon_lib_la_DEPENDENCIES) $(EXTRA_libcommon_daemon_lib_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(libcommon_daemon_lib_la_OBJECTS) $(libcommon_daemon_lib_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ctl.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/lldpd-structs.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/log.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/marshal.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/version.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\
+@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.obj$$||'`;\
+@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ `$(CYGPATH_W) '$<'` &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.lo$$||'`;\
+@am__fastdepCC_TRUE@ $(LTCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+install-includeHEADERS: $(include_HEADERS)
+ @$(NORMAL_INSTALL)
+ @list='$(include_HEADERS)'; test -n "$(includedir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(includedir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(includedir)" || exit 1; \
+ fi; \
+ for p in $$list; do \
+ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+ echo "$$d$$p"; \
+ done | $(am__base_list) | \
+ while read files; do \
+ echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(includedir)'"; \
+ $(INSTALL_HEADER) $$files "$(DESTDIR)$(includedir)" || exit $$?; \
+ done
+
+uninstall-includeHEADERS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(include_HEADERS)'; test -n "$(includedir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(includedir)'; $(am__uninstall_files_from_dir)
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LTLIBRARIES) $(HEADERS)
+installdirs:
+ for dir in "$(DESTDIR)$(includedir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/ctl.Plo
+ -rm -f ./$(DEPDIR)/lldpd-structs.Plo
+ -rm -f ./$(DEPDIR)/log.Plo
+ -rm -f ./$(DEPDIR)/marshal.Plo
+ -rm -f ./$(DEPDIR)/version.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am: install-includeHEADERS
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/ctl.Plo
+ -rm -f ./$(DEPDIR)/lldpd-structs.Plo
+ -rm -f ./$(DEPDIR)/log.Plo
+ -rm -f ./$(DEPDIR)/marshal.Plo
+ -rm -f ./$(DEPDIR)/version.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-includeHEADERS
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-includeHEADERS install-info install-info-am \
+ install-man install-pdf install-pdf-am install-ps \
+ install-ps-am install-strip installcheck installcheck-am \
+ installdirs maintainer-clean maintainer-clean-generic \
+ mostlyclean mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool pdf pdf-am ps ps-am tags tags-am uninstall \
+ uninstall-am uninstall-includeHEADERS
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/client/Makefile.am b/src/client/Makefile.am
new file mode 100644
index 0000000..23a3225
--- /dev/null
+++ b/src/client/Makefile.am
@@ -0,0 +1,49 @@
+AM_CFLAGS = -I $(top_srcdir)/include $(LLDP_CFLAGS)
+AM_CPPFLAGS = $(LLDP_CPPFLAGS)
+AM_LDFLAGS = $(LLDP_LDFLAGS)
+
+sbin_PROGRAMS = lldpcli
+man_MANS = lldpcli.8
+dist_man_MANS = lldpctl.8
+
+install-exec-local: lldpcli
+ cd $(DESTDIR)$(sbindir) && rm -f lldpctl
+ cd $(DESTDIR)$(sbindir) && $(LN_S) lldpcli lldpctl
+uninstall-local:
+ cd $(DESTDIR)$(sbindir) && rm -f lldpctl
+
+lldpcli_SOURCES = client.h lldpcli.c display.c \
+ conf.c conf-med.c conf-inv.c conf-dot3.c conf-power.c \
+ conf-lldp.c conf-system.c \
+ commands.c show.c \
+ misc.c tokenizer.c \
+ utf8.c \
+ writer.h text_writer.c kv_writer.c json_writer.c
+lldpcli_LDADD = \
+ $(top_builddir)/src/libcommon-daemon-client.la \
+ $(top_builddir)/src/lib/liblldpctl.la \
+ @READLINE_LIBS@
+lldpcli_CFLAGS = $(AM_CFLAGS)
+lldpcli_LDFLAGS = $(AM_LDFLAGS) $(LLDP_BIN_LDFLAGS)
+
+if USE_XML
+lldpcli_SOURCES += xml_writer.c
+lldpcli_CFLAGS += @libxml2_CFLAGS@
+lldpcli_LDADD += @libxml2_LIBS@
+endif
+
+# Completions
+bashcompletiondir = $(datadir)/bash-completion/completions
+dist_bashcompletion_DATA = completion/lldpcli
+zshcompletiondir = $(datadir)/zsh/site-functions
+dist_zshcompletion_DATA = completion/_lldpcli
+
+# Default configuration
+lldpdconfdir = $(sysconfdir)/lldpd.d
+dist_lldpdconf_DATA = README.conf
+
+TEMPLATES = lldpcli.8
+EXTRA_DIST = lldpcli.8.in
+CLEANFILES = $(TEMPLATES)
+lldpcli.8: lldpcli.8.in
+include $(top_srcdir)/edit.am
diff --git a/src/client/Makefile.in b/src/client/Makefile.in
new file mode 100644
index 0000000..c179404
--- /dev/null
+++ b/src/client/Makefile.in
@@ -0,0 +1,1276 @@
+# Makefile.in generated by automake 1.16.5 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2021 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+sbin_PROGRAMS = lldpcli$(EXEEXT)
+@USE_XML_TRUE@am__append_1 = xml_writer.c
+@USE_XML_TRUE@am__append_2 = @libxml2_CFLAGS@
+@USE_XML_TRUE@am__append_3 = @libxml2_LIBS@
+subdir = src/client
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/alignof.m4 \
+ $(top_srcdir)/m4/args.m4 \
+ $(top_srcdir)/m4/ax_build_date_epoch.m4 \
+ $(top_srcdir)/m4/ax_cflags_gcc_option.m4 \
+ $(top_srcdir)/m4/ax_ld_check_flag.m4 \
+ $(top_srcdir)/m4/ax_lib_readline.m4 \
+ $(top_srcdir)/m4/ax_prog_doxygen.m4 \
+ $(top_srcdir)/m4/config_subdirs.m4 \
+ $(top_srcdir)/m4/ld-version-script.m4 \
+ $(top_srcdir)/m4/libcap.m4 $(top_srcdir)/m4/libevent.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/os.m4 \
+ $(top_srcdir)/m4/progname.m4 $(top_srcdir)/m4/seccomp.m4 \
+ $(top_srcdir)/m4/snmp.m4 $(top_srcdir)/m4/stdint.m4 \
+ $(top_srcdir)/m4/systemtap.m4 $(top_srcdir)/m4/xml2.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(dist_bashcompletion_DATA) \
+ $(dist_lldpdconf_DATA) $(dist_zshcompletion_DATA) \
+ $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+am__installdirs = "$(DESTDIR)$(sbindir)" "$(DESTDIR)$(man8dir)" \
+ "$(DESTDIR)$(bashcompletiondir)" "$(DESTDIR)$(lldpdconfdir)" \
+ "$(DESTDIR)$(zshcompletiondir)"
+PROGRAMS = $(sbin_PROGRAMS)
+am__lldpcli_SOURCES_DIST = client.h lldpcli.c display.c conf.c \
+ conf-med.c conf-inv.c conf-dot3.c conf-power.c conf-lldp.c \
+ conf-system.c commands.c show.c misc.c tokenizer.c utf8.c \
+ writer.h text_writer.c kv_writer.c json_writer.c xml_writer.c
+@USE_XML_TRUE@am__objects_1 = lldpcli-xml_writer.$(OBJEXT)
+am_lldpcli_OBJECTS = lldpcli-lldpcli.$(OBJEXT) \
+ lldpcli-display.$(OBJEXT) lldpcli-conf.$(OBJEXT) \
+ lldpcli-conf-med.$(OBJEXT) lldpcli-conf-inv.$(OBJEXT) \
+ lldpcli-conf-dot3.$(OBJEXT) lldpcli-conf-power.$(OBJEXT) \
+ lldpcli-conf-lldp.$(OBJEXT) lldpcli-conf-system.$(OBJEXT) \
+ lldpcli-commands.$(OBJEXT) lldpcli-show.$(OBJEXT) \
+ lldpcli-misc.$(OBJEXT) lldpcli-tokenizer.$(OBJEXT) \
+ lldpcli-utf8.$(OBJEXT) lldpcli-text_writer.$(OBJEXT) \
+ lldpcli-kv_writer.$(OBJEXT) lldpcli-json_writer.$(OBJEXT) \
+ $(am__objects_1)
+lldpcli_OBJECTS = $(am_lldpcli_OBJECTS)
+am__DEPENDENCIES_1 =
+lldpcli_DEPENDENCIES = $(top_builddir)/src/libcommon-daemon-client.la \
+ $(top_builddir)/src/lib/liblldpctl.la $(am__DEPENDENCIES_1)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+lldpcli_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(lldpcli_CFLAGS) \
+ $(CFLAGS) $(lldpcli_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/lldpcli-commands.Po \
+ ./$(DEPDIR)/lldpcli-conf-dot3.Po \
+ ./$(DEPDIR)/lldpcli-conf-inv.Po \
+ ./$(DEPDIR)/lldpcli-conf-lldp.Po \
+ ./$(DEPDIR)/lldpcli-conf-med.Po \
+ ./$(DEPDIR)/lldpcli-conf-power.Po \
+ ./$(DEPDIR)/lldpcli-conf-system.Po ./$(DEPDIR)/lldpcli-conf.Po \
+ ./$(DEPDIR)/lldpcli-display.Po \
+ ./$(DEPDIR)/lldpcli-json_writer.Po \
+ ./$(DEPDIR)/lldpcli-kv_writer.Po \
+ ./$(DEPDIR)/lldpcli-lldpcli.Po ./$(DEPDIR)/lldpcli-misc.Po \
+ ./$(DEPDIR)/lldpcli-show.Po ./$(DEPDIR)/lldpcli-text_writer.Po \
+ ./$(DEPDIR)/lldpcli-tokenizer.Po ./$(DEPDIR)/lldpcli-utf8.Po \
+ ./$(DEPDIR)/lldpcli-xml_writer.Po
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(lldpcli_SOURCES)
+DIST_SOURCES = $(am__lldpcli_SOURCES_DIST)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+man8dir = $(mandir)/man8
+NROFF = nroff
+MANS = $(dist_man_MANS) $(man_MANS)
+DATA = $(dist_bashcompletion_DATA) $(dist_lldpdconf_DATA) \
+ $(dist_zshcompletion_DATA)
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+am__DIST_COMMON = $(dist_man_MANS) $(srcdir)/Makefile.in \
+ $(top_srcdir)/depcomp $(top_srcdir)/edit.am
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+APPARMORDIR = @APPARMORDIR@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CONFIGURE_ARGS = @CONFIGURE_ARGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CSCOPE = @CSCOPE@
+CTAGS = @CTAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DLLTOOL = @DLLTOOL@
+DOXYGEN_PAPER_SIZE = @DOXYGEN_PAPER_SIZE@
+DSYMUTIL = @DSYMUTIL@
+DTRACE = @DTRACE@
+DUMPBIN = @DUMPBIN@
+DX_CONFIG = @DX_CONFIG@
+DX_DOCDIR = @DX_DOCDIR@
+DX_DOT = @DX_DOT@
+DX_DOXYGEN = @DX_DOXYGEN@
+DX_DVIPS = @DX_DVIPS@
+DX_EGREP = @DX_EGREP@
+DX_ENV = @DX_ENV@
+DX_FLAG_chi = @DX_FLAG_chi@
+DX_FLAG_chm = @DX_FLAG_chm@
+DX_FLAG_doc = @DX_FLAG_doc@
+DX_FLAG_dot = @DX_FLAG_dot@
+DX_FLAG_html = @DX_FLAG_html@
+DX_FLAG_man = @DX_FLAG_man@
+DX_FLAG_pdf = @DX_FLAG_pdf@
+DX_FLAG_ps = @DX_FLAG_ps@
+DX_FLAG_rtf = @DX_FLAG_rtf@
+DX_FLAG_xml = @DX_FLAG_xml@
+DX_HHC = @DX_HHC@
+DX_LATEX = @DX_LATEX@
+DX_MAKEINDEX = @DX_MAKEINDEX@
+DX_PDFLATEX = @DX_PDFLATEX@
+DX_PERL = @DX_PERL@
+DX_PROJECT = @DX_PROJECT@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+ETAGS = @ETAGS@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+FILECMD = @FILECMD@
+FUZZ_DECODE_ENGINE = @FUZZ_DECODE_ENGINE@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LAUNCHDDAEMONSDIR = @LAUNCHDDAEMONSDIR@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LLDPD_CTL_SOCKET = @LLDPD_CTL_SOCKET@
+LLDPD_PID_FILE = @LLDPD_PID_FILE@
+LLDP_BIN_LDFLAGS = @LLDP_BIN_LDFLAGS@
+LLDP_CFLAGS = @LLDP_CFLAGS@
+LLDP_CPPFLAGS = @LLDP_CPPFLAGS@
+LLDP_LDFLAGS = @LLDP_LDFLAGS@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+NETLINK_MAX_RECEIVE_BUFSIZE = @NETLINK_MAX_RECEIVE_BUFSIZE@
+NETLINK_RECEIVE_BUFSIZE = @NETLINK_RECEIVE_BUFSIZE@
+NETLINK_SEND_BUFSIZE = @NETLINK_SEND_BUFSIZE@
+NETSNMP_CFLAGS = @NETSNMP_CFLAGS@
+NETSNMP_CONFIG = @NETSNMP_CONFIG@
+NETSNMP_LIBS = @NETSNMP_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+PRIVSEP_CHROOT = @PRIVSEP_CHROOT@
+PRIVSEP_GROUP = @PRIVSEP_GROUP@
+PRIVSEP_USER = @PRIVSEP_USER@
+RANLIB = @RANLIB@
+READLINE_LIBS = @READLINE_LIBS@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+SYSTEMDSYSTEMUNITDIR = @SYSTEMDSYSTEMUNITDIR@
+SYSUSERSDIR = @SYSUSERSDIR@
+VERSION = @VERSION@
+XML2_CONFIG = @XML2_CONFIG@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+apparmordir = @apparmordir@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+check_CFLAGS = @check_CFLAGS@
+check_LIBS = @check_LIBS@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+launchddaemonsdir = @launchddaemonsdir@
+libbsd_CFLAGS = @libbsd_CFLAGS@
+libbsd_LIBS = @libbsd_LIBS@
+libcap_CFLAGS = @libcap_CFLAGS@
+libcap_LIBS = @libcap_LIBS@
+libdir = @libdir@
+libevent_CFLAGS = @libevent_CFLAGS@
+libevent_LDFLAGS = @libevent_LDFLAGS@
+libevent_LIBS = @libevent_LIBS@
+libexecdir = @libexecdir@
+libseccomp_CFLAGS = @libseccomp_CFLAGS@
+libseccomp_LIBS = @libseccomp_LIBS@
+libxml2_CFLAGS = @libxml2_CFLAGS@
+libxml2_LIBS = @libxml2_LIBS@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+pkgconfigdir = @pkgconfigdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+systemdsystemunitdir = @systemdsystemunitdir@
+sysusersdir = @sysusersdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+AM_CFLAGS = -I $(top_srcdir)/include $(LLDP_CFLAGS)
+AM_CPPFLAGS = $(LLDP_CPPFLAGS)
+AM_LDFLAGS = $(LLDP_LDFLAGS)
+man_MANS = lldpcli.8
+dist_man_MANS = lldpctl.8
+lldpcli_SOURCES = client.h lldpcli.c display.c conf.c conf-med.c \
+ conf-inv.c conf-dot3.c conf-power.c conf-lldp.c conf-system.c \
+ commands.c show.c misc.c tokenizer.c utf8.c writer.h \
+ text_writer.c kv_writer.c json_writer.c $(am__append_1)
+lldpcli_LDADD = $(top_builddir)/src/libcommon-daemon-client.la \
+ $(top_builddir)/src/lib/liblldpctl.la @READLINE_LIBS@ \
+ $(am__append_3)
+lldpcli_CFLAGS = $(AM_CFLAGS) $(am__append_2)
+lldpcli_LDFLAGS = $(AM_LDFLAGS) $(LLDP_BIN_LDFLAGS)
+
+# Completions
+bashcompletiondir = $(datadir)/bash-completion/completions
+dist_bashcompletion_DATA = completion/lldpcli
+zshcompletiondir = $(datadir)/zsh/site-functions
+dist_zshcompletion_DATA = completion/_lldpcli
+
+# Default configuration
+lldpdconfdir = $(sysconfdir)/lldpd.d
+dist_lldpdconf_DATA = README.conf
+TEMPLATES = lldpcli.8
+EXTRA_DIST = lldpcli.8.in
+CLEANFILES = $(TEMPLATES)
+edit = $(SED) \
+ -e 's|@bindir[@]|$(bindir)|g' \
+ -e 's|@sbindir[@]|$(sbindir)|g' \
+ -e 's|@sysconfdir[@]|$(sysconfdir)|g' \
+ -e 's|@pkgdatadir[@]|$(pkgdatadir)|g' \
+ -e 's|@libdir[@]|$(libdir)|g' \
+ -e 's|@srcdir[@]|$(srcdir)|g' \
+ -e 's|@top_builddir[@]|$(top_builddir)|g' \
+ -e 's|@includedir[@]|$(includedir)|g' \
+ -e 's|@exec_prefix[@]|$(exec_prefix)|g' \
+ -e 's|@prefix[@]|$(prefix)|g' \
+ -e 's|@VERSION[@]|$(VERSION)|g' \
+ -e 's|@PACKAGE[@]|$(PACKAGE)|g' \
+ -e 's|@PACKAGE_NAME[@]|$(PACKAGE_NAME)|g' \
+ -e 's|@PACKAGE_URL[@]|$(PACKAGE_URL)|g' \
+ -e 's|@PRIVSEP_USER[@]|$(PRIVSEP_USER)|g' \
+ -e 's|@PRIVSEP_GROUP[@]|$(PRIVSEP_GROUP)|g' \
+ -e 's|@PRIVSEP_CHROOT[@]|$(PRIVSEP_CHROOT)|g' \
+ -e 's|@LLDPD_PID_FILE[@]|$(LLDPD_PID_FILE)|g' \
+ -e 's|@LLDPD_CTL_SOCKET[@]|$(LLDPD_CTL_SOCKET)|g' \
+ -e 's|@PRIVSEP_CHROOT[@]|$(PRIVSEP_CHROOT)|g'
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(top_srcdir)/edit.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/client/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/client/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+$(top_srcdir)/edit.am $(am__empty):
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+install-sbinPROGRAMS: $(sbin_PROGRAMS)
+ @$(NORMAL_INSTALL)
+ @list='$(sbin_PROGRAMS)'; test -n "$(sbindir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(sbindir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(sbindir)" || exit 1; \
+ fi; \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed 's/$(EXEEXT)$$//' | \
+ while read p p1; do if test -f $$p \
+ || test -f $$p1 \
+ ; then echo "$$p"; echo "$$p"; else :; fi; \
+ done | \
+ sed -e 'p;s,.*/,,;n;h' \
+ -e 's|.*|.|' \
+ -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \
+ sed 'N;N;N;s,\n, ,g' | \
+ $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \
+ { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \
+ if ($$2 == $$4) files[d] = files[d] " " $$1; \
+ else { print "f", $$3 "/" $$4, $$1; } } \
+ END { for (d in files) print "f", d, files[d] }' | \
+ while read type dir files; do \
+ if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \
+ test -z "$$files" || { \
+ echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(sbindir)$$dir'"; \
+ $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(sbindir)$$dir" || exit $$?; \
+ } \
+ ; done
+
+uninstall-sbinPROGRAMS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(sbin_PROGRAMS)'; test -n "$(sbindir)" || list=; \
+ files=`for p in $$list; do echo "$$p"; done | \
+ sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \
+ -e 's/$$/$(EXEEXT)/' \
+ `; \
+ test -n "$$list" || exit 0; \
+ echo " ( cd '$(DESTDIR)$(sbindir)' && rm -f" $$files ")"; \
+ cd "$(DESTDIR)$(sbindir)" && rm -f $$files
+
+clean-sbinPROGRAMS:
+ @list='$(sbin_PROGRAMS)'; test -n "$$list" || exit 0; \
+ echo " rm -f" $$list; \
+ rm -f $$list || exit $$?; \
+ test -n "$(EXEEXT)" || exit 0; \
+ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \
+ echo " rm -f" $$list; \
+ rm -f $$list
+
+lldpcli$(EXEEXT): $(lldpcli_OBJECTS) $(lldpcli_DEPENDENCIES) $(EXTRA_lldpcli_DEPENDENCIES)
+ @rm -f lldpcli$(EXEEXT)
+ $(AM_V_CCLD)$(lldpcli_LINK) $(lldpcli_OBJECTS) $(lldpcli_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/lldpcli-commands.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/lldpcli-conf-dot3.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/lldpcli-conf-inv.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/lldpcli-conf-lldp.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/lldpcli-conf-med.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/lldpcli-conf-power.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/lldpcli-conf-system.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/lldpcli-conf.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/lldpcli-display.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/lldpcli-json_writer.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/lldpcli-kv_writer.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/lldpcli-lldpcli.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/lldpcli-misc.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/lldpcli-show.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/lldpcli-text_writer.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/lldpcli-tokenizer.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/lldpcli-utf8.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/lldpcli-xml_writer.Po@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\
+@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.obj$$||'`;\
+@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ `$(CYGPATH_W) '$<'` &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.lo$$||'`;\
+@am__fastdepCC_TRUE@ $(LTCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+lldpcli-lldpcli.o: lldpcli.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(lldpcli_CFLAGS) $(CFLAGS) -MT lldpcli-lldpcli.o -MD -MP -MF $(DEPDIR)/lldpcli-lldpcli.Tpo -c -o lldpcli-lldpcli.o `test -f 'lldpcli.c' || echo '$(srcdir)/'`lldpcli.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/lldpcli-lldpcli.Tpo $(DEPDIR)/lldpcli-lldpcli.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='lldpcli.c' object='lldpcli-lldpcli.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(lldpcli_CFLAGS) $(CFLAGS) -c -o lldpcli-lldpcli.o `test -f 'lldpcli.c' || echo '$(srcdir)/'`lldpcli.c
+
+lldpcli-lldpcli.obj: lldpcli.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(lldpcli_CFLAGS) $(CFLAGS) -MT lldpcli-lldpcli.obj -MD -MP -MF $(DEPDIR)/lldpcli-lldpcli.Tpo -c -o lldpcli-lldpcli.obj `if test -f 'lldpcli.c'; then $(CYGPATH_W) 'lldpcli.c'; else $(CYGPATH_W) '$(srcdir)/lldpcli.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/lldpcli-lldpcli.Tpo $(DEPDIR)/lldpcli-lldpcli.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='lldpcli.c' object='lldpcli-lldpcli.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(lldpcli_CFLAGS) $(CFLAGS) -c -o lldpcli-lldpcli.obj `if test -f 'lldpcli.c'; then $(CYGPATH_W) 'lldpcli.c'; else $(CYGPATH_W) '$(srcdir)/lldpcli.c'; fi`
+
+lldpcli-display.o: display.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(lldpcli_CFLAGS) $(CFLAGS) -MT lldpcli-display.o -MD -MP -MF $(DEPDIR)/lldpcli-display.Tpo -c -o lldpcli-display.o `test -f 'display.c' || echo '$(srcdir)/'`display.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/lldpcli-display.Tpo $(DEPDIR)/lldpcli-display.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='display.c' object='lldpcli-display.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(lldpcli_CFLAGS) $(CFLAGS) -c -o lldpcli-display.o `test -f 'display.c' || echo '$(srcdir)/'`display.c
+
+lldpcli-display.obj: display.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(lldpcli_CFLAGS) $(CFLAGS) -MT lldpcli-display.obj -MD -MP -MF $(DEPDIR)/lldpcli-display.Tpo -c -o lldpcli-display.obj `if test -f 'display.c'; then $(CYGPATH_W) 'display.c'; else $(CYGPATH_W) '$(srcdir)/display.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/lldpcli-display.Tpo $(DEPDIR)/lldpcli-display.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='display.c' object='lldpcli-display.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(lldpcli_CFLAGS) $(CFLAGS) -c -o lldpcli-display.obj `if test -f 'display.c'; then $(CYGPATH_W) 'display.c'; else $(CYGPATH_W) '$(srcdir)/display.c'; fi`
+
+lldpcli-conf.o: conf.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(lldpcli_CFLAGS) $(CFLAGS) -MT lldpcli-conf.o -MD -MP -MF $(DEPDIR)/lldpcli-conf.Tpo -c -o lldpcli-conf.o `test -f 'conf.c' || echo '$(srcdir)/'`conf.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/lldpcli-conf.Tpo $(DEPDIR)/lldpcli-conf.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='conf.c' object='lldpcli-conf.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(lldpcli_CFLAGS) $(CFLAGS) -c -o lldpcli-conf.o `test -f 'conf.c' || echo '$(srcdir)/'`conf.c
+
+lldpcli-conf.obj: conf.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(lldpcli_CFLAGS) $(CFLAGS) -MT lldpcli-conf.obj -MD -MP -MF $(DEPDIR)/lldpcli-conf.Tpo -c -o lldpcli-conf.obj `if test -f 'conf.c'; then $(CYGPATH_W) 'conf.c'; else $(CYGPATH_W) '$(srcdir)/conf.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/lldpcli-conf.Tpo $(DEPDIR)/lldpcli-conf.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='conf.c' object='lldpcli-conf.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(lldpcli_CFLAGS) $(CFLAGS) -c -o lldpcli-conf.obj `if test -f 'conf.c'; then $(CYGPATH_W) 'conf.c'; else $(CYGPATH_W) '$(srcdir)/conf.c'; fi`
+
+lldpcli-conf-med.o: conf-med.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(lldpcli_CFLAGS) $(CFLAGS) -MT lldpcli-conf-med.o -MD -MP -MF $(DEPDIR)/lldpcli-conf-med.Tpo -c -o lldpcli-conf-med.o `test -f 'conf-med.c' || echo '$(srcdir)/'`conf-med.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/lldpcli-conf-med.Tpo $(DEPDIR)/lldpcli-conf-med.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='conf-med.c' object='lldpcli-conf-med.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(lldpcli_CFLAGS) $(CFLAGS) -c -o lldpcli-conf-med.o `test -f 'conf-med.c' || echo '$(srcdir)/'`conf-med.c
+
+lldpcli-conf-med.obj: conf-med.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(lldpcli_CFLAGS) $(CFLAGS) -MT lldpcli-conf-med.obj -MD -MP -MF $(DEPDIR)/lldpcli-conf-med.Tpo -c -o lldpcli-conf-med.obj `if test -f 'conf-med.c'; then $(CYGPATH_W) 'conf-med.c'; else $(CYGPATH_W) '$(srcdir)/conf-med.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/lldpcli-conf-med.Tpo $(DEPDIR)/lldpcli-conf-med.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='conf-med.c' object='lldpcli-conf-med.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(lldpcli_CFLAGS) $(CFLAGS) -c -o lldpcli-conf-med.obj `if test -f 'conf-med.c'; then $(CYGPATH_W) 'conf-med.c'; else $(CYGPATH_W) '$(srcdir)/conf-med.c'; fi`
+
+lldpcli-conf-inv.o: conf-inv.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(lldpcli_CFLAGS) $(CFLAGS) -MT lldpcli-conf-inv.o -MD -MP -MF $(DEPDIR)/lldpcli-conf-inv.Tpo -c -o lldpcli-conf-inv.o `test -f 'conf-inv.c' || echo '$(srcdir)/'`conf-inv.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/lldpcli-conf-inv.Tpo $(DEPDIR)/lldpcli-conf-inv.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='conf-inv.c' object='lldpcli-conf-inv.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(lldpcli_CFLAGS) $(CFLAGS) -c -o lldpcli-conf-inv.o `test -f 'conf-inv.c' || echo '$(srcdir)/'`conf-inv.c
+
+lldpcli-conf-inv.obj: conf-inv.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(lldpcli_CFLAGS) $(CFLAGS) -MT lldpcli-conf-inv.obj -MD -MP -MF $(DEPDIR)/lldpcli-conf-inv.Tpo -c -o lldpcli-conf-inv.obj `if test -f 'conf-inv.c'; then $(CYGPATH_W) 'conf-inv.c'; else $(CYGPATH_W) '$(srcdir)/conf-inv.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/lldpcli-conf-inv.Tpo $(DEPDIR)/lldpcli-conf-inv.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='conf-inv.c' object='lldpcli-conf-inv.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(lldpcli_CFLAGS) $(CFLAGS) -c -o lldpcli-conf-inv.obj `if test -f 'conf-inv.c'; then $(CYGPATH_W) 'conf-inv.c'; else $(CYGPATH_W) '$(srcdir)/conf-inv.c'; fi`
+
+lldpcli-conf-dot3.o: conf-dot3.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(lldpcli_CFLAGS) $(CFLAGS) -MT lldpcli-conf-dot3.o -MD -MP -MF $(DEPDIR)/lldpcli-conf-dot3.Tpo -c -o lldpcli-conf-dot3.o `test -f 'conf-dot3.c' || echo '$(srcdir)/'`conf-dot3.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/lldpcli-conf-dot3.Tpo $(DEPDIR)/lldpcli-conf-dot3.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='conf-dot3.c' object='lldpcli-conf-dot3.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(lldpcli_CFLAGS) $(CFLAGS) -c -o lldpcli-conf-dot3.o `test -f 'conf-dot3.c' || echo '$(srcdir)/'`conf-dot3.c
+
+lldpcli-conf-dot3.obj: conf-dot3.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(lldpcli_CFLAGS) $(CFLAGS) -MT lldpcli-conf-dot3.obj -MD -MP -MF $(DEPDIR)/lldpcli-conf-dot3.Tpo -c -o lldpcli-conf-dot3.obj `if test -f 'conf-dot3.c'; then $(CYGPATH_W) 'conf-dot3.c'; else $(CYGPATH_W) '$(srcdir)/conf-dot3.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/lldpcli-conf-dot3.Tpo $(DEPDIR)/lldpcli-conf-dot3.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='conf-dot3.c' object='lldpcli-conf-dot3.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(lldpcli_CFLAGS) $(CFLAGS) -c -o lldpcli-conf-dot3.obj `if test -f 'conf-dot3.c'; then $(CYGPATH_W) 'conf-dot3.c'; else $(CYGPATH_W) '$(srcdir)/conf-dot3.c'; fi`
+
+lldpcli-conf-power.o: conf-power.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(lldpcli_CFLAGS) $(CFLAGS) -MT lldpcli-conf-power.o -MD -MP -MF $(DEPDIR)/lldpcli-conf-power.Tpo -c -o lldpcli-conf-power.o `test -f 'conf-power.c' || echo '$(srcdir)/'`conf-power.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/lldpcli-conf-power.Tpo $(DEPDIR)/lldpcli-conf-power.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='conf-power.c' object='lldpcli-conf-power.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(lldpcli_CFLAGS) $(CFLAGS) -c -o lldpcli-conf-power.o `test -f 'conf-power.c' || echo '$(srcdir)/'`conf-power.c
+
+lldpcli-conf-power.obj: conf-power.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(lldpcli_CFLAGS) $(CFLAGS) -MT lldpcli-conf-power.obj -MD -MP -MF $(DEPDIR)/lldpcli-conf-power.Tpo -c -o lldpcli-conf-power.obj `if test -f 'conf-power.c'; then $(CYGPATH_W) 'conf-power.c'; else $(CYGPATH_W) '$(srcdir)/conf-power.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/lldpcli-conf-power.Tpo $(DEPDIR)/lldpcli-conf-power.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='conf-power.c' object='lldpcli-conf-power.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(lldpcli_CFLAGS) $(CFLAGS) -c -o lldpcli-conf-power.obj `if test -f 'conf-power.c'; then $(CYGPATH_W) 'conf-power.c'; else $(CYGPATH_W) '$(srcdir)/conf-power.c'; fi`
+
+lldpcli-conf-lldp.o: conf-lldp.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(lldpcli_CFLAGS) $(CFLAGS) -MT lldpcli-conf-lldp.o -MD -MP -MF $(DEPDIR)/lldpcli-conf-lldp.Tpo -c -o lldpcli-conf-lldp.o `test -f 'conf-lldp.c' || echo '$(srcdir)/'`conf-lldp.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/lldpcli-conf-lldp.Tpo $(DEPDIR)/lldpcli-conf-lldp.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='conf-lldp.c' object='lldpcli-conf-lldp.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(lldpcli_CFLAGS) $(CFLAGS) -c -o lldpcli-conf-lldp.o `test -f 'conf-lldp.c' || echo '$(srcdir)/'`conf-lldp.c
+
+lldpcli-conf-lldp.obj: conf-lldp.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(lldpcli_CFLAGS) $(CFLAGS) -MT lldpcli-conf-lldp.obj -MD -MP -MF $(DEPDIR)/lldpcli-conf-lldp.Tpo -c -o lldpcli-conf-lldp.obj `if test -f 'conf-lldp.c'; then $(CYGPATH_W) 'conf-lldp.c'; else $(CYGPATH_W) '$(srcdir)/conf-lldp.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/lldpcli-conf-lldp.Tpo $(DEPDIR)/lldpcli-conf-lldp.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='conf-lldp.c' object='lldpcli-conf-lldp.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(lldpcli_CFLAGS) $(CFLAGS) -c -o lldpcli-conf-lldp.obj `if test -f 'conf-lldp.c'; then $(CYGPATH_W) 'conf-lldp.c'; else $(CYGPATH_W) '$(srcdir)/conf-lldp.c'; fi`
+
+lldpcli-conf-system.o: conf-system.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(lldpcli_CFLAGS) $(CFLAGS) -MT lldpcli-conf-system.o -MD -MP -MF $(DEPDIR)/lldpcli-conf-system.Tpo -c -o lldpcli-conf-system.o `test -f 'conf-system.c' || echo '$(srcdir)/'`conf-system.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/lldpcli-conf-system.Tpo $(DEPDIR)/lldpcli-conf-system.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='conf-system.c' object='lldpcli-conf-system.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(lldpcli_CFLAGS) $(CFLAGS) -c -o lldpcli-conf-system.o `test -f 'conf-system.c' || echo '$(srcdir)/'`conf-system.c
+
+lldpcli-conf-system.obj: conf-system.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(lldpcli_CFLAGS) $(CFLAGS) -MT lldpcli-conf-system.obj -MD -MP -MF $(DEPDIR)/lldpcli-conf-system.Tpo -c -o lldpcli-conf-system.obj `if test -f 'conf-system.c'; then $(CYGPATH_W) 'conf-system.c'; else $(CYGPATH_W) '$(srcdir)/conf-system.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/lldpcli-conf-system.Tpo $(DEPDIR)/lldpcli-conf-system.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='conf-system.c' object='lldpcli-conf-system.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(lldpcli_CFLAGS) $(CFLAGS) -c -o lldpcli-conf-system.obj `if test -f 'conf-system.c'; then $(CYGPATH_W) 'conf-system.c'; else $(CYGPATH_W) '$(srcdir)/conf-system.c'; fi`
+
+lldpcli-commands.o: commands.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(lldpcli_CFLAGS) $(CFLAGS) -MT lldpcli-commands.o -MD -MP -MF $(DEPDIR)/lldpcli-commands.Tpo -c -o lldpcli-commands.o `test -f 'commands.c' || echo '$(srcdir)/'`commands.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/lldpcli-commands.Tpo $(DEPDIR)/lldpcli-commands.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='commands.c' object='lldpcli-commands.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(lldpcli_CFLAGS) $(CFLAGS) -c -o lldpcli-commands.o `test -f 'commands.c' || echo '$(srcdir)/'`commands.c
+
+lldpcli-commands.obj: commands.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(lldpcli_CFLAGS) $(CFLAGS) -MT lldpcli-commands.obj -MD -MP -MF $(DEPDIR)/lldpcli-commands.Tpo -c -o lldpcli-commands.obj `if test -f 'commands.c'; then $(CYGPATH_W) 'commands.c'; else $(CYGPATH_W) '$(srcdir)/commands.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/lldpcli-commands.Tpo $(DEPDIR)/lldpcli-commands.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='commands.c' object='lldpcli-commands.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(lldpcli_CFLAGS) $(CFLAGS) -c -o lldpcli-commands.obj `if test -f 'commands.c'; then $(CYGPATH_W) 'commands.c'; else $(CYGPATH_W) '$(srcdir)/commands.c'; fi`
+
+lldpcli-show.o: show.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(lldpcli_CFLAGS) $(CFLAGS) -MT lldpcli-show.o -MD -MP -MF $(DEPDIR)/lldpcli-show.Tpo -c -o lldpcli-show.o `test -f 'show.c' || echo '$(srcdir)/'`show.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/lldpcli-show.Tpo $(DEPDIR)/lldpcli-show.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='show.c' object='lldpcli-show.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(lldpcli_CFLAGS) $(CFLAGS) -c -o lldpcli-show.o `test -f 'show.c' || echo '$(srcdir)/'`show.c
+
+lldpcli-show.obj: show.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(lldpcli_CFLAGS) $(CFLAGS) -MT lldpcli-show.obj -MD -MP -MF $(DEPDIR)/lldpcli-show.Tpo -c -o lldpcli-show.obj `if test -f 'show.c'; then $(CYGPATH_W) 'show.c'; else $(CYGPATH_W) '$(srcdir)/show.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/lldpcli-show.Tpo $(DEPDIR)/lldpcli-show.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='show.c' object='lldpcli-show.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(lldpcli_CFLAGS) $(CFLAGS) -c -o lldpcli-show.obj `if test -f 'show.c'; then $(CYGPATH_W) 'show.c'; else $(CYGPATH_W) '$(srcdir)/show.c'; fi`
+
+lldpcli-misc.o: misc.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(lldpcli_CFLAGS) $(CFLAGS) -MT lldpcli-misc.o -MD -MP -MF $(DEPDIR)/lldpcli-misc.Tpo -c -o lldpcli-misc.o `test -f 'misc.c' || echo '$(srcdir)/'`misc.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/lldpcli-misc.Tpo $(DEPDIR)/lldpcli-misc.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='misc.c' object='lldpcli-misc.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(lldpcli_CFLAGS) $(CFLAGS) -c -o lldpcli-misc.o `test -f 'misc.c' || echo '$(srcdir)/'`misc.c
+
+lldpcli-misc.obj: misc.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(lldpcli_CFLAGS) $(CFLAGS) -MT lldpcli-misc.obj -MD -MP -MF $(DEPDIR)/lldpcli-misc.Tpo -c -o lldpcli-misc.obj `if test -f 'misc.c'; then $(CYGPATH_W) 'misc.c'; else $(CYGPATH_W) '$(srcdir)/misc.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/lldpcli-misc.Tpo $(DEPDIR)/lldpcli-misc.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='misc.c' object='lldpcli-misc.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(lldpcli_CFLAGS) $(CFLAGS) -c -o lldpcli-misc.obj `if test -f 'misc.c'; then $(CYGPATH_W) 'misc.c'; else $(CYGPATH_W) '$(srcdir)/misc.c'; fi`
+
+lldpcli-tokenizer.o: tokenizer.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(lldpcli_CFLAGS) $(CFLAGS) -MT lldpcli-tokenizer.o -MD -MP -MF $(DEPDIR)/lldpcli-tokenizer.Tpo -c -o lldpcli-tokenizer.o `test -f 'tokenizer.c' || echo '$(srcdir)/'`tokenizer.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/lldpcli-tokenizer.Tpo $(DEPDIR)/lldpcli-tokenizer.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tokenizer.c' object='lldpcli-tokenizer.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(lldpcli_CFLAGS) $(CFLAGS) -c -o lldpcli-tokenizer.o `test -f 'tokenizer.c' || echo '$(srcdir)/'`tokenizer.c
+
+lldpcli-tokenizer.obj: tokenizer.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(lldpcli_CFLAGS) $(CFLAGS) -MT lldpcli-tokenizer.obj -MD -MP -MF $(DEPDIR)/lldpcli-tokenizer.Tpo -c -o lldpcli-tokenizer.obj `if test -f 'tokenizer.c'; then $(CYGPATH_W) 'tokenizer.c'; else $(CYGPATH_W) '$(srcdir)/tokenizer.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/lldpcli-tokenizer.Tpo $(DEPDIR)/lldpcli-tokenizer.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tokenizer.c' object='lldpcli-tokenizer.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(lldpcli_CFLAGS) $(CFLAGS) -c -o lldpcli-tokenizer.obj `if test -f 'tokenizer.c'; then $(CYGPATH_W) 'tokenizer.c'; else $(CYGPATH_W) '$(srcdir)/tokenizer.c'; fi`
+
+lldpcli-utf8.o: utf8.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(lldpcli_CFLAGS) $(CFLAGS) -MT lldpcli-utf8.o -MD -MP -MF $(DEPDIR)/lldpcli-utf8.Tpo -c -o lldpcli-utf8.o `test -f 'utf8.c' || echo '$(srcdir)/'`utf8.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/lldpcli-utf8.Tpo $(DEPDIR)/lldpcli-utf8.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='utf8.c' object='lldpcli-utf8.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(lldpcli_CFLAGS) $(CFLAGS) -c -o lldpcli-utf8.o `test -f 'utf8.c' || echo '$(srcdir)/'`utf8.c
+
+lldpcli-utf8.obj: utf8.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(lldpcli_CFLAGS) $(CFLAGS) -MT lldpcli-utf8.obj -MD -MP -MF $(DEPDIR)/lldpcli-utf8.Tpo -c -o lldpcli-utf8.obj `if test -f 'utf8.c'; then $(CYGPATH_W) 'utf8.c'; else $(CYGPATH_W) '$(srcdir)/utf8.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/lldpcli-utf8.Tpo $(DEPDIR)/lldpcli-utf8.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='utf8.c' object='lldpcli-utf8.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(lldpcli_CFLAGS) $(CFLAGS) -c -o lldpcli-utf8.obj `if test -f 'utf8.c'; then $(CYGPATH_W) 'utf8.c'; else $(CYGPATH_W) '$(srcdir)/utf8.c'; fi`
+
+lldpcli-text_writer.o: text_writer.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(lldpcli_CFLAGS) $(CFLAGS) -MT lldpcli-text_writer.o -MD -MP -MF $(DEPDIR)/lldpcli-text_writer.Tpo -c -o lldpcli-text_writer.o `test -f 'text_writer.c' || echo '$(srcdir)/'`text_writer.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/lldpcli-text_writer.Tpo $(DEPDIR)/lldpcli-text_writer.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='text_writer.c' object='lldpcli-text_writer.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(lldpcli_CFLAGS) $(CFLAGS) -c -o lldpcli-text_writer.o `test -f 'text_writer.c' || echo '$(srcdir)/'`text_writer.c
+
+lldpcli-text_writer.obj: text_writer.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(lldpcli_CFLAGS) $(CFLAGS) -MT lldpcli-text_writer.obj -MD -MP -MF $(DEPDIR)/lldpcli-text_writer.Tpo -c -o lldpcli-text_writer.obj `if test -f 'text_writer.c'; then $(CYGPATH_W) 'text_writer.c'; else $(CYGPATH_W) '$(srcdir)/text_writer.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/lldpcli-text_writer.Tpo $(DEPDIR)/lldpcli-text_writer.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='text_writer.c' object='lldpcli-text_writer.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(lldpcli_CFLAGS) $(CFLAGS) -c -o lldpcli-text_writer.obj `if test -f 'text_writer.c'; then $(CYGPATH_W) 'text_writer.c'; else $(CYGPATH_W) '$(srcdir)/text_writer.c'; fi`
+
+lldpcli-kv_writer.o: kv_writer.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(lldpcli_CFLAGS) $(CFLAGS) -MT lldpcli-kv_writer.o -MD -MP -MF $(DEPDIR)/lldpcli-kv_writer.Tpo -c -o lldpcli-kv_writer.o `test -f 'kv_writer.c' || echo '$(srcdir)/'`kv_writer.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/lldpcli-kv_writer.Tpo $(DEPDIR)/lldpcli-kv_writer.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='kv_writer.c' object='lldpcli-kv_writer.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(lldpcli_CFLAGS) $(CFLAGS) -c -o lldpcli-kv_writer.o `test -f 'kv_writer.c' || echo '$(srcdir)/'`kv_writer.c
+
+lldpcli-kv_writer.obj: kv_writer.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(lldpcli_CFLAGS) $(CFLAGS) -MT lldpcli-kv_writer.obj -MD -MP -MF $(DEPDIR)/lldpcli-kv_writer.Tpo -c -o lldpcli-kv_writer.obj `if test -f 'kv_writer.c'; then $(CYGPATH_W) 'kv_writer.c'; else $(CYGPATH_W) '$(srcdir)/kv_writer.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/lldpcli-kv_writer.Tpo $(DEPDIR)/lldpcli-kv_writer.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='kv_writer.c' object='lldpcli-kv_writer.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(lldpcli_CFLAGS) $(CFLAGS) -c -o lldpcli-kv_writer.obj `if test -f 'kv_writer.c'; then $(CYGPATH_W) 'kv_writer.c'; else $(CYGPATH_W) '$(srcdir)/kv_writer.c'; fi`
+
+lldpcli-json_writer.o: json_writer.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(lldpcli_CFLAGS) $(CFLAGS) -MT lldpcli-json_writer.o -MD -MP -MF $(DEPDIR)/lldpcli-json_writer.Tpo -c -o lldpcli-json_writer.o `test -f 'json_writer.c' || echo '$(srcdir)/'`json_writer.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/lldpcli-json_writer.Tpo $(DEPDIR)/lldpcli-json_writer.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='json_writer.c' object='lldpcli-json_writer.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(lldpcli_CFLAGS) $(CFLAGS) -c -o lldpcli-json_writer.o `test -f 'json_writer.c' || echo '$(srcdir)/'`json_writer.c
+
+lldpcli-json_writer.obj: json_writer.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(lldpcli_CFLAGS) $(CFLAGS) -MT lldpcli-json_writer.obj -MD -MP -MF $(DEPDIR)/lldpcli-json_writer.Tpo -c -o lldpcli-json_writer.obj `if test -f 'json_writer.c'; then $(CYGPATH_W) 'json_writer.c'; else $(CYGPATH_W) '$(srcdir)/json_writer.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/lldpcli-json_writer.Tpo $(DEPDIR)/lldpcli-json_writer.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='json_writer.c' object='lldpcli-json_writer.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(lldpcli_CFLAGS) $(CFLAGS) -c -o lldpcli-json_writer.obj `if test -f 'json_writer.c'; then $(CYGPATH_W) 'json_writer.c'; else $(CYGPATH_W) '$(srcdir)/json_writer.c'; fi`
+
+lldpcli-xml_writer.o: xml_writer.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(lldpcli_CFLAGS) $(CFLAGS) -MT lldpcli-xml_writer.o -MD -MP -MF $(DEPDIR)/lldpcli-xml_writer.Tpo -c -o lldpcli-xml_writer.o `test -f 'xml_writer.c' || echo '$(srcdir)/'`xml_writer.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/lldpcli-xml_writer.Tpo $(DEPDIR)/lldpcli-xml_writer.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='xml_writer.c' object='lldpcli-xml_writer.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(lldpcli_CFLAGS) $(CFLAGS) -c -o lldpcli-xml_writer.o `test -f 'xml_writer.c' || echo '$(srcdir)/'`xml_writer.c
+
+lldpcli-xml_writer.obj: xml_writer.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(lldpcli_CFLAGS) $(CFLAGS) -MT lldpcli-xml_writer.obj -MD -MP -MF $(DEPDIR)/lldpcli-xml_writer.Tpo -c -o lldpcli-xml_writer.obj `if test -f 'xml_writer.c'; then $(CYGPATH_W) 'xml_writer.c'; else $(CYGPATH_W) '$(srcdir)/xml_writer.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/lldpcli-xml_writer.Tpo $(DEPDIR)/lldpcli-xml_writer.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='xml_writer.c' object='lldpcli-xml_writer.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(lldpcli_CFLAGS) $(CFLAGS) -c -o lldpcli-xml_writer.obj `if test -f 'xml_writer.c'; then $(CYGPATH_W) 'xml_writer.c'; else $(CYGPATH_W) '$(srcdir)/xml_writer.c'; fi`
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+install-man8: $(dist_man_MANS) $(man_MANS)
+ @$(NORMAL_INSTALL)
+ @list1=''; \
+ list2='$(dist_man_MANS) $(man_MANS)'; \
+ test -n "$(man8dir)" \
+ && test -n "`echo $$list1$$list2`" \
+ || exit 0; \
+ echo " $(MKDIR_P) '$(DESTDIR)$(man8dir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(man8dir)" || exit 1; \
+ { for i in $$list1; do echo "$$i"; done; \
+ if test -n "$$list2"; then \
+ for i in $$list2; do echo "$$i"; done \
+ | sed -n '/\.8[a-z]*$$/p'; \
+ fi; \
+ } | while read p; do \
+ if test -f $$p; then d=; else d="$(srcdir)/"; fi; \
+ echo "$$d$$p"; echo "$$p"; \
+ done | \
+ sed -e 'n;s,.*/,,;p;h;s,.*\.,,;s,^[^8][0-9a-z]*$$,8,;x' \
+ -e 's,\.[0-9a-z]*$$,,;$(transform);G;s,\n,.,' | \
+ sed 'N;N;s,\n, ,g' | { \
+ list=; while read file base inst; do \
+ if test "$$base" = "$$inst"; then list="$$list $$file"; else \
+ echo " $(INSTALL_DATA) '$$file' '$(DESTDIR)$(man8dir)/$$inst'"; \
+ $(INSTALL_DATA) "$$file" "$(DESTDIR)$(man8dir)/$$inst" || exit $$?; \
+ fi; \
+ done; \
+ for i in $$list; do echo "$$i"; done | $(am__base_list) | \
+ while read files; do \
+ test -z "$$files" || { \
+ echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(man8dir)'"; \
+ $(INSTALL_DATA) $$files "$(DESTDIR)$(man8dir)" || exit $$?; }; \
+ done; }
+
+uninstall-man8:
+ @$(NORMAL_UNINSTALL)
+ @list=''; test -n "$(man8dir)" || exit 0; \
+ files=`{ for i in $$list; do echo "$$i"; done; \
+ l2='$(dist_man_MANS) $(man_MANS)'; for i in $$l2; do echo "$$i"; done | \
+ sed -n '/\.8[a-z]*$$/p'; \
+ } | sed -e 's,.*/,,;h;s,.*\.,,;s,^[^8][0-9a-z]*$$,8,;x' \
+ -e 's,\.[0-9a-z]*$$,,;$(transform);G;s,\n,.,'`; \
+ dir='$(DESTDIR)$(man8dir)'; $(am__uninstall_files_from_dir)
+install-dist_bashcompletionDATA: $(dist_bashcompletion_DATA)
+ @$(NORMAL_INSTALL)
+ @list='$(dist_bashcompletion_DATA)'; test -n "$(bashcompletiondir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(bashcompletiondir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(bashcompletiondir)" || exit 1; \
+ fi; \
+ for p in $$list; do \
+ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+ echo "$$d$$p"; \
+ done | $(am__base_list) | \
+ while read files; do \
+ echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(bashcompletiondir)'"; \
+ $(INSTALL_DATA) $$files "$(DESTDIR)$(bashcompletiondir)" || exit $$?; \
+ done
+
+uninstall-dist_bashcompletionDATA:
+ @$(NORMAL_UNINSTALL)
+ @list='$(dist_bashcompletion_DATA)'; test -n "$(bashcompletiondir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(bashcompletiondir)'; $(am__uninstall_files_from_dir)
+install-dist_lldpdconfDATA: $(dist_lldpdconf_DATA)
+ @$(NORMAL_INSTALL)
+ @list='$(dist_lldpdconf_DATA)'; test -n "$(lldpdconfdir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(lldpdconfdir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(lldpdconfdir)" || exit 1; \
+ fi; \
+ for p in $$list; do \
+ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+ echo "$$d$$p"; \
+ done | $(am__base_list) | \
+ while read files; do \
+ echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(lldpdconfdir)'"; \
+ $(INSTALL_DATA) $$files "$(DESTDIR)$(lldpdconfdir)" || exit $$?; \
+ done
+
+uninstall-dist_lldpdconfDATA:
+ @$(NORMAL_UNINSTALL)
+ @list='$(dist_lldpdconf_DATA)'; test -n "$(lldpdconfdir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(lldpdconfdir)'; $(am__uninstall_files_from_dir)
+install-dist_zshcompletionDATA: $(dist_zshcompletion_DATA)
+ @$(NORMAL_INSTALL)
+ @list='$(dist_zshcompletion_DATA)'; test -n "$(zshcompletiondir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(zshcompletiondir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(zshcompletiondir)" || exit 1; \
+ fi; \
+ for p in $$list; do \
+ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+ echo "$$d$$p"; \
+ done | $(am__base_list) | \
+ while read files; do \
+ echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(zshcompletiondir)'"; \
+ $(INSTALL_DATA) $$files "$(DESTDIR)$(zshcompletiondir)" || exit $$?; \
+ done
+
+uninstall-dist_zshcompletionDATA:
+ @$(NORMAL_UNINSTALL)
+ @list='$(dist_zshcompletion_DATA)'; test -n "$(zshcompletiondir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(zshcompletiondir)'; $(am__uninstall_files_from_dir)
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(PROGRAMS) $(MANS) $(DATA)
+installdirs:
+ for dir in "$(DESTDIR)$(sbindir)" "$(DESTDIR)$(man8dir)" "$(DESTDIR)$(bashcompletiondir)" "$(DESTDIR)$(lldpdconfdir)" "$(DESTDIR)$(zshcompletiondir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+ -test -z "$(CLEANFILES)" || rm -f $(CLEANFILES)
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-sbinPROGRAMS \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/lldpcli-commands.Po
+ -rm -f ./$(DEPDIR)/lldpcli-conf-dot3.Po
+ -rm -f ./$(DEPDIR)/lldpcli-conf-inv.Po
+ -rm -f ./$(DEPDIR)/lldpcli-conf-lldp.Po
+ -rm -f ./$(DEPDIR)/lldpcli-conf-med.Po
+ -rm -f ./$(DEPDIR)/lldpcli-conf-power.Po
+ -rm -f ./$(DEPDIR)/lldpcli-conf-system.Po
+ -rm -f ./$(DEPDIR)/lldpcli-conf.Po
+ -rm -f ./$(DEPDIR)/lldpcli-display.Po
+ -rm -f ./$(DEPDIR)/lldpcli-json_writer.Po
+ -rm -f ./$(DEPDIR)/lldpcli-kv_writer.Po
+ -rm -f ./$(DEPDIR)/lldpcli-lldpcli.Po
+ -rm -f ./$(DEPDIR)/lldpcli-misc.Po
+ -rm -f ./$(DEPDIR)/lldpcli-show.Po
+ -rm -f ./$(DEPDIR)/lldpcli-text_writer.Po
+ -rm -f ./$(DEPDIR)/lldpcli-tokenizer.Po
+ -rm -f ./$(DEPDIR)/lldpcli-utf8.Po
+ -rm -f ./$(DEPDIR)/lldpcli-xml_writer.Po
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am: install-dist_bashcompletionDATA \
+ install-dist_lldpdconfDATA install-dist_zshcompletionDATA \
+ install-man
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am: install-exec-local install-sbinPROGRAMS
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man: install-man8
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/lldpcli-commands.Po
+ -rm -f ./$(DEPDIR)/lldpcli-conf-dot3.Po
+ -rm -f ./$(DEPDIR)/lldpcli-conf-inv.Po
+ -rm -f ./$(DEPDIR)/lldpcli-conf-lldp.Po
+ -rm -f ./$(DEPDIR)/lldpcli-conf-med.Po
+ -rm -f ./$(DEPDIR)/lldpcli-conf-power.Po
+ -rm -f ./$(DEPDIR)/lldpcli-conf-system.Po
+ -rm -f ./$(DEPDIR)/lldpcli-conf.Po
+ -rm -f ./$(DEPDIR)/lldpcli-display.Po
+ -rm -f ./$(DEPDIR)/lldpcli-json_writer.Po
+ -rm -f ./$(DEPDIR)/lldpcli-kv_writer.Po
+ -rm -f ./$(DEPDIR)/lldpcli-lldpcli.Po
+ -rm -f ./$(DEPDIR)/lldpcli-misc.Po
+ -rm -f ./$(DEPDIR)/lldpcli-show.Po
+ -rm -f ./$(DEPDIR)/lldpcli-text_writer.Po
+ -rm -f ./$(DEPDIR)/lldpcli-tokenizer.Po
+ -rm -f ./$(DEPDIR)/lldpcli-utf8.Po
+ -rm -f ./$(DEPDIR)/lldpcli-xml_writer.Po
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-dist_bashcompletionDATA \
+ uninstall-dist_lldpdconfDATA uninstall-dist_zshcompletionDATA \
+ uninstall-local uninstall-man uninstall-sbinPROGRAMS
+
+uninstall-man: uninstall-man8
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool clean-sbinPROGRAMS cscopelist-am \
+ ctags ctags-am distclean distclean-compile distclean-generic \
+ distclean-libtool distclean-tags distdir dvi dvi-am html \
+ html-am info info-am install install-am install-data \
+ install-data-am install-dist_bashcompletionDATA \
+ install-dist_lldpdconfDATA install-dist_zshcompletionDATA \
+ install-dvi install-dvi-am install-exec install-exec-am \
+ install-exec-local install-html install-html-am install-info \
+ install-info-am install-man install-man8 install-pdf \
+ install-pdf-am install-ps install-ps-am install-sbinPROGRAMS \
+ install-strip installcheck installcheck-am installdirs \
+ maintainer-clean maintainer-clean-generic mostlyclean \
+ mostlyclean-compile mostlyclean-generic mostlyclean-libtool \
+ pdf pdf-am ps ps-am tags tags-am uninstall uninstall-am \
+ uninstall-dist_bashcompletionDATA uninstall-dist_lldpdconfDATA \
+ uninstall-dist_zshcompletionDATA uninstall-local uninstall-man \
+ uninstall-man8 uninstall-sbinPROGRAMS
+
+.PRECIOUS: Makefile
+
+
+install-exec-local: lldpcli
+ cd $(DESTDIR)$(sbindir) && rm -f lldpctl
+ cd $(DESTDIR)$(sbindir) && $(LN_S) lldpcli lldpctl
+uninstall-local:
+ cd $(DESTDIR)$(sbindir) && rm -f lldpctl
+lldpcli.8: lldpcli.8.in
+
+$(TEMPLATES): Makefile
+ $(AM_V_GEN)$(MKDIR_P) $(@D) && $(edit) $(srcdir)/$@.in > $@.tmp && mv $@.tmp $@
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/client/README.conf b/src/client/README.conf
new file mode 100644
index 0000000..4982e9e
--- /dev/null
+++ b/src/client/README.conf
@@ -0,0 +1,8 @@
+# You can put lldpd configuration snippets into this directory.
+# Upon start, lldpd will read each files in this directory and execute content
+# as if they were passed as arguments to lldpcli
+#
+# Files should be suffixed by .conf and have content like:
+# configure system description 'my little server'
+#
+# See lldpcli(8) for more details.
diff --git a/src/client/client.h b/src/client/client.h
new file mode 100644
index 0000000..04fca94
--- /dev/null
+++ b/src/client/client.h
@@ -0,0 +1,148 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/*
+ * Copyright (c) 2012 Vincent Bernat <bernat@luffy.cx>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef _CLIENT_H
+#define _CLIENT_H
+
+#if HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include "../lib/lldpctl.h"
+#include "../lldp-const.h"
+#include "../log.h"
+#include "../ctl.h"
+#include "../compat/compat.h"
+#include "writer.h"
+
+#ifdef HAVE_ADDRESS_SANITIZER
+# include <sanitizer/lsan_interface.h>
+# define SUPPRESS_LEAK(x) __lsan_ignore_object(x)
+#else
+# define SUPPRESS_LEAK(x)
+#endif
+
+/* Readline stuff */
+#ifdef HAVE_LIBREADLINE
+# if defined(HAVE_READLINE_READLINE_H)
+# include <readline/readline.h>
+# elif defined(HAVE_READLINE_H)
+# include <readline.h>
+# else
+extern char *readline();
+extern char *rl_line_buffer;
+extern int rl_point;
+extern int rl_insert_text(const char *);
+extern void rl_forced_update_display(void);
+extern int rl_bind_key(int, int (*f)(int, int));
+# endif
+#endif
+#ifdef HAVE_READLINE_HISTORY
+# if defined(HAVE_READLINE_HISTORY_H)
+# include <readline/history.h>
+# elif defined(HAVE_HISTORY_H)
+# include <history.h>
+# else
+extern void add_history();
+# endif
+#endif
+#undef NEWLINE
+
+extern const char *ctlname;
+
+/* commands.c */
+#define NEWLINE "<CR>"
+struct cmd_node;
+struct cmd_env;
+struct cmd_node *commands_root(void);
+struct cmd_node *commands_new(struct cmd_node *, const char *, const char *,
+ int (*validate)(struct cmd_env *, const void *),
+ int (*execute)(struct lldpctl_conn_t *, struct writer *, struct cmd_env *,
+ const void *),
+ const void *);
+struct cmd_node *commands_privileged(struct cmd_node *);
+struct cmd_node *commands_lock(struct cmd_node *);
+struct cmd_node *commands_hidden(struct cmd_node *);
+void commands_free(struct cmd_node *);
+const char *cmdenv_arg(struct cmd_env *);
+const char *cmdenv_get(struct cmd_env *, const char *);
+int cmdenv_put(struct cmd_env *, const char *, const char *);
+int cmdenv_pop(struct cmd_env *, int);
+int commands_execute(struct lldpctl_conn_t *, struct writer *, struct cmd_node *, int,
+ const char **, int);
+char *commands_complete(struct cmd_node *, int, const char **, int, int);
+/* helpers */
+int cmd_check_no_env(struct cmd_env *, const void *);
+int cmd_check_env(struct cmd_env *, const void *);
+int cmd_store_env(struct lldpctl_conn_t *, struct writer *, struct cmd_env *,
+ const void *);
+int cmd_store_env_and_pop(struct lldpctl_conn_t *, struct writer *, struct cmd_env *,
+ const void *);
+int cmd_store_env_value(struct lldpctl_conn_t *, struct writer *, struct cmd_env *,
+ const void *);
+int cmd_store_env_value_and_pop(struct lldpctl_conn_t *, struct writer *,
+ struct cmd_env *, const void *);
+int cmd_store_env_value_and_pop2(struct lldpctl_conn_t *, struct writer *,
+ struct cmd_env *, const void *);
+int cmd_store_env_value_and_pop3(struct lldpctl_conn_t *, struct writer *,
+ struct cmd_env *, const void *);
+int cmd_store_something_env_value_and_pop2(const char *, struct cmd_env *,
+ const void *);
+int cmd_store_something_env_value(const char *, struct cmd_env *, const void *);
+lldpctl_atom_t *cmd_iterate_on_interfaces(struct lldpctl_conn_t *, struct cmd_env *);
+lldpctl_atom_t *cmd_iterate_on_ports(struct lldpctl_conn_t *, struct cmd_env *,
+ const char **);
+void cmd_restrict_ports(struct cmd_node *);
+void cmd_restrict_protocol(struct cmd_node *);
+
+/* misc.c */
+int contains(const char *, const char *);
+const char *totag(const char *);
+
+/* display.c */
+#define DISPLAY_BRIEF 1
+#define DISPLAY_NORMAL 2
+#define DISPLAY_DETAILS 3
+void display_interfaces(lldpctl_conn_t *, struct writer *, struct cmd_env *, int, int);
+void display_interface(lldpctl_conn_t *, struct writer *, int, lldpctl_atom_t *,
+ lldpctl_atom_t *, int, int);
+void display_local_chassis(lldpctl_conn_t *, struct writer *, struct cmd_env *, int);
+void display_configuration(lldpctl_conn_t *, struct writer *);
+void display_interfaces_stats(lldpctl_conn_t *, struct writer *, struct cmd_env *);
+void display_interface_stats(lldpctl_conn_t *, struct writer *, lldpctl_atom_t *);
+void display_local_interfaces(lldpctl_conn_t *, struct writer *, struct cmd_env *, int,
+ int);
+
+/* show.c */
+void register_commands_show(struct cmd_node *);
+void register_commands_watch(struct cmd_node *);
+
+/* conf*.c */
+void register_commands_configure(struct cmd_node *);
+void register_commands_configure_system(struct cmd_node *, struct cmd_node *);
+void register_commands_configure_lldp(struct cmd_node *, struct cmd_node *);
+void register_commands_configure_med(struct cmd_node *, struct cmd_node *);
+void register_commands_configure_inventory(struct cmd_node *, struct cmd_node *);
+void register_commands_configure_dot3(struct cmd_node *);
+void register_commands_medpow(struct cmd_node *);
+void register_commands_dot3pow(struct cmd_node *);
+
+/* tokenizer.c */
+int tokenize_line(const char *, int *, char ***);
+void tokenize_free(int, char **);
+
+#endif
diff --git a/src/client/commands.c b/src/client/commands.c
new file mode 100644
index 0000000..804789e
--- /dev/null
+++ b/src/client/commands.c
@@ -0,0 +1,855 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/*
+ * Copyright (c) 2012 Vincent Bernat <bernat@luffy.cx>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "client.h"
+#include <string.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <libgen.h>
+
+/**
+ * An element of the environment (a key and a value).
+ */
+struct cmd_env_el {
+ TAILQ_ENTRY(cmd_env_el) next; /**< Next environment element */
+ const char *key; /**< Key for this element */
+ const char *value; /**< Value for this element */
+};
+
+/**
+ * A stack element.
+ */
+struct cmd_env_stack {
+ TAILQ_ENTRY(cmd_env_stack) next; /**< Next element, down the stack */
+ struct cmd_node *el; /**< Stored element */
+};
+
+/**
+ * Structure representing an environment for the current command.
+ *
+ * An environment is a list of values stored for use for the function executing
+ * as well as the current command, the current position in the command and a
+ * stack for cmd_node
+ */
+struct cmd_env {
+ TAILQ_HEAD(, cmd_env_el) elements; /**< List of environment variables */
+ TAILQ_HEAD(, cmd_env_stack) stack; /**< Stack */
+ int argc; /**< Number of argument in the command */
+ int argp; /**< Current argument */
+ const char **argv; /**< Arguments */
+};
+
+/**
+ * Structure representing a command node.
+ *
+ * Such a node contains a token accepted to enter the node (or @c NULL if there
+ * is no token needed), a documentation string to present the user, a function
+ * to validate the user input (or @c NULL if no function is needed) and a
+ * function to execute when entering the node. Because we can enter a node just
+ * by completing, the execution part should have no other effect than modifying
+ * the environment, with the exception of execution on @c NEWLINE (which cannot
+ * happen when completing).
+ */
+struct cmd_node {
+ TAILQ_ENTRY(cmd_node) next; /**< Next sibling */
+
+ const char *token; /**< Token to enter this cnode */
+ const char *doc; /**< Documentation string */
+ int privileged; /**< Privileged command? */
+ int lock; /**< Lock required for execution? */
+ int hidden; /**< Hidden command? */
+
+ /**
+ * Function validating entry in this node. Can be @c NULL.
+ */
+ int (*validate)(struct cmd_env *, const void *);
+ /**
+ * Function to execute when entering this node. May be @c NULL.
+ *
+ * This function can alter the environment
+ */
+ int (*execute)(struct lldpctl_conn_t *, struct writer *, struct cmd_env *,
+ const void *);
+ const void *arg; /**< Magic argument for the previous two functions */
+
+ /* List of possible subentries */
+ TAILQ_HEAD(, cmd_node) subentries; /* List of subnodes */
+};
+
+/**
+ * Create a root node.
+ *
+ * @return the root node.
+ */
+struct cmd_node *
+commands_root(void)
+{
+ struct cmd_node *new = calloc(1, sizeof(struct cmd_node));
+ if (new == NULL) fatalx("lldpctl", "out of memory");
+ TAILQ_INIT(&new->subentries);
+ return new;
+}
+
+/**
+ * Make a node accessible only to privileged users.
+ *
+ * @param node node to change privileges
+ * @return the modified node
+ *
+ * The node is modified. It is returned to ease chaining.
+ */
+struct cmd_node *
+commands_privileged(struct cmd_node *node)
+{
+ if (node) node->privileged = 1;
+ return node;
+}
+
+/**
+ * Make a node accessible only with a lock.
+ *
+ * @param node node to use lock to execute
+ * @return the modified node
+ *
+ * The node is modified. It is returned to ease chaining.
+ */
+struct cmd_node *
+commands_lock(struct cmd_node *node)
+{
+ if (node) node->lock = 1;
+ return node;
+}
+
+/**
+ * Hide a node from help or completion.
+ *
+ * @param node node to hide
+ * @return the modified node
+ *
+ * The node is modified. It is returned to ease chaining.
+ */
+struct cmd_node *
+commands_hidden(struct cmd_node *node)
+{
+ if (node) node->hidden = 1;
+ return node;
+}
+
+/**
+ * Create a new node acessible by any user.
+ *
+ * @param root The node we want to attach this node.
+ * @param token Token to enter this node. Or @c NULL if no token is needed.
+ * @param doc Documentation for this node.
+ * @param validate Function that should return 1 if we can enter the node.
+ * @param execute Function that should return 1 on successful execution of this node.
+ * @param arg Magic argument for precedent functions.
+ * @return the newly created node
+ */
+struct cmd_node *
+commands_new(struct cmd_node *root, const char *token, const char *doc,
+ int (*validate)(struct cmd_env *, const void *),
+ int (*execute)(struct lldpctl_conn_t *, struct writer *, struct cmd_env *,
+ const void *),
+ const void *arg)
+{
+ struct cmd_node *new = calloc(1, sizeof(struct cmd_node));
+ if (new == NULL) fatalx("lldpctl", "out of memory");
+ new->token = token;
+ new->doc = doc;
+ new->validate = validate;
+ new->execute = execute;
+ new->arg = arg;
+ TAILQ_INIT(&new->subentries);
+ TAILQ_INSERT_TAIL(&root->subentries, new, next);
+ return new;
+}
+
+/**
+ * Free a command tree.
+ *
+ * @param root The node we want to free.
+ */
+void
+commands_free(struct cmd_node *root)
+{
+ struct cmd_node *subcmd, *subcmd_next;
+ for (subcmd = TAILQ_FIRST(&root->subentries); subcmd != NULL;
+ subcmd = subcmd_next) {
+ subcmd_next = TAILQ_NEXT(subcmd, next);
+ TAILQ_REMOVE(&root->subentries, subcmd, next);
+ commands_free(subcmd);
+ }
+ free(root);
+}
+
+/**
+ * Return the current argument in the environment. This can be @c NEWLINE or
+ * @c NULL.
+ *
+ * @param env The environment.
+ * @return current argument.
+ */
+const char *
+cmdenv_arg(struct cmd_env *env)
+{
+ if (env->argp < env->argc) return env->argv[env->argp];
+ if (env->argp == env->argc) return NEWLINE;
+ return NULL;
+}
+
+/**
+ * Get a value from the environment.
+ *
+ * @param env The environment.
+ * @param key The key for the requested value.
+ * @return @c NULL if not found or the requested value otherwise. If no value is
+ * associated, return the key.
+ */
+const char *
+cmdenv_get(struct cmd_env *env, const char *key)
+{
+ struct cmd_env_el *el;
+ TAILQ_FOREACH (el, &env->elements, next)
+ if (!strcmp(el->key, key)) return el->value ? el->value : el->key;
+ return NULL;
+}
+
+/**
+ * Put a value in the environment.
+ *
+ * @param env The environment.
+ * @param key The key for the value.
+ * @param value The value.
+ * @return 0 on success, -1 otherwise.
+ */
+int
+cmdenv_put(struct cmd_env *env, const char *key, const char *value)
+{
+ struct cmd_env_el *el = malloc(sizeof(struct cmd_env_el));
+ if (el == NULL) {
+ log_warn("lldpctl",
+ "unable to allocate memory for new environment variable");
+ return -1;
+ }
+ el->key = key;
+ el->value = value;
+ TAILQ_INSERT_TAIL(&env->elements, el, next);
+ return 0;
+}
+
+/**
+ * Pop some node from the execution stack.
+ *
+ * This allows to resume parsing on a previous state. Useful to call after
+ * parsing optional arguments.
+ *
+ * @param env The environment.
+ * @param n How many element we want to pop.
+ * @return 0 on success, -1 otherwise.
+ */
+int
+cmdenv_pop(struct cmd_env *env, int n)
+{
+ while (n-- > 0) {
+ if (TAILQ_EMPTY(&env->stack)) {
+ log_warnx("lldpctl", "environment stack is empty");
+ return -1;
+ }
+ struct cmd_env_stack *first = TAILQ_FIRST(&env->stack);
+ TAILQ_REMOVE(&env->stack, first, next);
+ free(first);
+ }
+ return 0;
+}
+
+/**
+ * Push some node on the execution stack.
+ *
+ * @param env The environment.
+ * @param node The node to push.
+ * @return 0 on success, -1 on error.
+ */
+static int
+cmdenv_push(struct cmd_env *env, struct cmd_node *node)
+{
+ struct cmd_env_stack *el = malloc(sizeof(struct cmd_env_stack));
+ if (el == NULL) {
+ log_warn("lldpctl", "not enough memory to allocate a stack element");
+ return -1;
+ }
+ el->el = node;
+ TAILQ_INSERT_HEAD(&env->stack, el, next);
+ return 0;
+}
+
+/**
+ * Return the top of the stack, without poping it.
+ *
+ * @param env The environment.
+ * @return the top element or @c NULL is the stack is empty.
+ */
+static struct cmd_node *
+cmdenv_top(struct cmd_env *env)
+{
+ if (TAILQ_EMPTY(&env->stack)) return NULL;
+ return TAILQ_FIRST(&env->stack)->el;
+}
+
+/**
+ * Free execution environment.
+ *
+ * @param env The environment.
+ */
+static void
+cmdenv_free(struct cmd_env *env)
+{
+ while (!TAILQ_EMPTY(&env->stack))
+ cmdenv_pop(env, 1);
+
+ struct cmd_env_el *first;
+ while (!TAILQ_EMPTY(&env->elements)) {
+ first = TAILQ_FIRST(&env->elements);
+ TAILQ_REMOVE(&env->elements, first, next);
+ free(first);
+ }
+}
+
+struct candidate_word {
+ TAILQ_ENTRY(candidate_word) next;
+ const char *word;
+ const char *doc;
+ int hidden;
+};
+
+/**
+ * Execute or complete a command from the given node.
+ *
+ * @param conn Connection to lldpd.
+ * @param w Writer for output.
+ * @param root Root node we want to start from.
+ * @param argc Number of arguments.
+ * @param argv Array of arguments.
+ * @param word Completed word. Or NULL when no completion is required.
+ * @param all When completing, display possible completions even if only one choice
+ * is possible.
+ * @param priv Is the current user privileged?
+ * @return 0 on success, -1 otherwise.
+ */
+static int
+_commands_execute(struct lldpctl_conn_t *conn, struct writer *w, struct cmd_node *root,
+ int argc, const char **argv, char **word, int all, int priv)
+{
+ int n, rc = 0, completion = (word != NULL);
+ int help = 0; /* Are we asking for help? */
+ int complete = 0; /* Are we asking for possible completions? */
+ int needlock = 0; /* Do we need a lock? */
+ struct cmd_env env = { .elements = TAILQ_HEAD_INITIALIZER(env.elements),
+ .stack = TAILQ_HEAD_INITIALIZER(env.stack),
+ .argc = argc,
+ .argv = argv,
+ .argp = 0 };
+ static int lockfd = -1;
+ static char *lockname = NULL; /* Name of lockfile */
+ cmdenv_push(&env, root);
+ if (!completion)
+ for (n = 0; n < argc; n++)
+ log_debug("lldpctl", "argument %02d: `%s`", n, argv[n]);
+ if (completion) *word = NULL;
+
+#define CAN_EXECUTE(candidate) \
+ ((!candidate->privileged || priv || complete) && \
+ (!candidate->validate || candidate->validate(&env, candidate->arg) == 1))
+
+ /* When completion is in progress, we use the same algorithm than for
+ * execution until we reach the cursor position. */
+ struct cmd_node *current = NULL;
+ while ((current = cmdenv_top(&env))) {
+ if (!completion) {
+ help = !!cmdenv_get(&env, "help"); /* Are we asking for help? */
+ complete = !!cmdenv_get(&env, "complete"); /* Or completion? */
+ }
+
+ struct cmd_node *candidate, *best = NULL;
+ const char *token = (env.argp < env.argc) ? env.argv[env.argp] :
+ (env.argp == env.argc && !help && !complete) ? NEWLINE :
+ NULL;
+ if (token == NULL || (completion && env.argp == env.argc - 1)) goto end;
+ if (!completion)
+ log_debug("lldpctl", "process argument %02d: `%s`", env.argp,
+ token);
+ TAILQ_FOREACH (candidate, &current->subentries, next) {
+ if (candidate->token &&
+ !strncmp(candidate->token, token, strlen(token)) &&
+ CAN_EXECUTE(candidate)) {
+ if (candidate->token &&
+ !strcmp(candidate->token, token)) {
+ /* Exact match */
+ best = candidate;
+ needlock = needlock || candidate->lock;
+ break;
+ }
+ if (!best)
+ best = candidate;
+ else {
+ if (!completion)
+ log_warnx("lldpctl",
+ "ambiguous token: %s (%s or %s)",
+ token, candidate->token,
+ best->token);
+ rc = -1;
+ goto end;
+ }
+ }
+ }
+ if (!best) {
+ /* Take first that validate */
+ TAILQ_FOREACH (candidate, &current->subentries, next) {
+ if (!candidate->token && CAN_EXECUTE(candidate)) {
+ best = candidate;
+ needlock = needlock || candidate->lock;
+ break;
+ }
+ }
+ }
+ if (!best && env.argp == env.argc) goto end;
+ if (!best) {
+ if (!completion)
+ log_warnx("lldpctl",
+ "unknown command from argument %d: `%s`",
+ env.argp + 1, token);
+ rc = -1;
+ goto end;
+ }
+
+ /* Push and execute */
+ cmdenv_push(&env, best);
+ if (best->execute) {
+ if (needlock) {
+ if (lockfd == -1) {
+ if (lockname == NULL &&
+ asprintf(&lockname, "%s.lock", ctlname) ==
+ -1) {
+ log_warnx("lldpctl",
+ "not enough memory to build lock filename");
+ rc = -1;
+ goto end;
+ }
+ log_debug("lldpctl", "open %s for locking",
+ lockname);
+ if ((lockfd = open(lockname, O_RDWR)) == -1) {
+ log_warn("lldpctl",
+ "cannot open lock %s", lockname);
+ rc = -1;
+ goto end;
+ }
+ }
+ if (lockf(lockfd, F_LOCK, 0) == -1) {
+ log_warn("lldpctl", "cannot get lock on %s",
+ lockname);
+ rc = -1;
+ close(lockfd);
+ lockfd = -1;
+ goto end;
+ }
+ }
+ rc = best->execute(conn, w, &env, best->arg) != 1 ? -1 : rc;
+ if (needlock && lockf(lockfd, F_ULOCK, 0) == -1) {
+ log_warn("lldpctl", "cannot unlock %s", lockname);
+ close(lockfd);
+ lockfd = -1;
+ }
+ if (rc == -1) goto end;
+ }
+ env.argp++;
+ }
+end:
+ if (!completion && !help && !complete) {
+ if (rc == 0 && env.argp != env.argc + 1) {
+ log_warnx("lldpctl", "incomplete command");
+ rc = -1;
+ }
+ } else if (rc == 0 && (env.argp == env.argc - 1 || help || complete)) {
+ /* We need to complete. Let's build the list of candidate words. */
+ struct cmd_node *candidate = NULL;
+ size_t maxl = 10; /* Max length of a word */
+ TAILQ_HEAD(, candidate_word) words; /* List of subnodes */
+ TAILQ_INIT(&words);
+ current = cmdenv_top(&env);
+ if (!TAILQ_EMPTY(&current->subentries)) {
+ TAILQ_FOREACH (candidate, &current->subentries, next) {
+ if ((!candidate->token || help || complete ||
+ !strncmp(env.argv[env.argc - 1],
+ candidate->token,
+ strlen(env.argv[env.argc - 1]))) &&
+ CAN_EXECUTE(candidate)) {
+ struct candidate_word *cword =
+ malloc(sizeof(struct candidate_word));
+ if (!cword) break;
+ cword->word = candidate->token;
+ cword->doc = candidate->doc;
+ cword->hidden = candidate->hidden;
+ if (cword->word && strlen(cword->word) > maxl)
+ maxl = strlen(cword->word);
+ TAILQ_INSERT_TAIL(&words, cword, next);
+ }
+ }
+ }
+ if (!TAILQ_EMPTY(&words)) {
+ /* Search if there is a common prefix, then return it. */
+ char prefix[maxl + 2]; /* Extra space may be added at the end */
+ struct candidate_word *cword, *cword_next;
+ memset(prefix, 0, maxl + 2);
+ for (size_t n = 0; n < maxl; n++) {
+ int c = 1; /* Set to 0 to exit outer loop */
+ TAILQ_FOREACH (cword, &words, next) {
+ c = 0;
+ if (cword->hidden) continue;
+ if (cword->word == NULL) break;
+ if (!strcmp(cword->word, NEWLINE)) break;
+ if (strlen(cword->word) == n) break;
+ if (prefix[n] == '\0')
+ prefix[n] = cword->word[n];
+ else if (prefix[n] != cword->word[n])
+ break;
+ c = 1;
+ }
+ if (c == 0) {
+ prefix[n] = '\0';
+ break;
+ }
+ }
+ /* If the prefix is complete, add a space, otherwise,
+ * just return it as is. */
+ if (!all && !help && !complete && strcmp(prefix, NEWLINE) &&
+ strlen(prefix) > 0 &&
+ strlen(env.argv[env.argc - 1]) < strlen(prefix)) {
+ TAILQ_FOREACH (cword, &words, next) {
+ if (cword->word &&
+ !strcmp(prefix, cword->word)) {
+ prefix[strlen(prefix)] = ' ';
+ break;
+ }
+ }
+ *word = strdup(prefix);
+ } else {
+ /* No common prefix, print possible completions */
+ if (!complete)
+ fprintf(stderr, "\n-- \033[1;34m%s\033[0m\n",
+ current->doc ? current->doc : "Help");
+ TAILQ_FOREACH (cword, &words, next) {
+ if (cword->hidden) continue;
+
+ char fmt[100];
+ if (!complete) {
+ snprintf(fmt, sizeof(fmt),
+ "%s%%%ds%s %%s\n", "\033[1m",
+ (int)maxl, "\033[0m");
+ fprintf(stderr, fmt,
+ cword->word ? cword->word : "WORD",
+ cword->doc ? cword->doc : "...");
+ } else {
+ if (!cword->word ||
+ !strcmp(cword->word, NEWLINE))
+ continue;
+ fprintf(stdout, "%s %s\n",
+ cword->word ? cword->word : "WORD",
+ cword->doc ? cword->doc : "...");
+ }
+ }
+ }
+ for (cword = TAILQ_FIRST(&words); cword != NULL;
+ cword = cword_next) {
+ cword_next = TAILQ_NEXT(cword, next);
+ TAILQ_REMOVE(&words, cword, next);
+ free(cword);
+ }
+ }
+ }
+ cmdenv_free(&env);
+ return rc;
+}
+
+/**
+ * Complete the given command.
+ */
+char *
+commands_complete(struct cmd_node *root, int argc, const char **argv, int all,
+ int privileged)
+{
+ char *word = NULL;
+ if (_commands_execute(NULL, NULL, root, argc, argv, &word, all, privileged) ==
+ 0)
+ return word;
+ return NULL;
+}
+
+/**
+ * Execute the given commands.
+ */
+int
+commands_execute(struct lldpctl_conn_t *conn, struct writer *w, struct cmd_node *root,
+ int argc, const char **argv, int privileged)
+{
+ return _commands_execute(conn, w, root, argc, argv, NULL, 0, privileged);
+}
+
+/**
+ * Check if the environment does not contain the given key.
+ *
+ * @param env The environment.
+ * @param key The key to search for.
+ * @return 1 if the environment does not contain the key. 0 otherwise.
+ */
+int
+cmd_check_no_env(struct cmd_env *env, const void *key)
+{
+ return cmdenv_get(env, (const char *)key) == NULL;
+}
+
+/**
+ * Check if the environment does contain the given key.
+ *
+ * @param env The environment.
+ * @param key The key to search for. Can be a comma separated list.
+ * @return 1 if the environment does contain the key. 0 otherwise.
+ */
+int
+cmd_check_env(struct cmd_env *env, const void *key)
+{
+ struct cmd_env_el *el;
+ const char *list = key;
+ int count = 0;
+ TAILQ_FOREACH (el, &env->elements, next) {
+ if (contains(list, el->key)) count++;
+ }
+ while ((list = strchr(list, ','))) {
+ list++;
+ count--;
+ }
+ return (count == 1);
+}
+
+/**
+ * Store the given key in the environment.
+ *
+ * @param conn The connection.
+ * @param w The writer (not used).
+ * @param env The environment.
+ * @param key The key to store.
+ * @return 1 if the key was stored
+ */
+int
+cmd_store_env(struct lldpctl_conn_t *conn, struct writer *w, struct cmd_env *env,
+ const void *key)
+{
+ return cmdenv_put(env, key, NULL) != -1;
+}
+
+/**
+ * Store the given key in the environment and pop one element from the stack.
+ *
+ * @param conn The connection.
+ * @param w The writer (not used).
+ * @param env The environment.
+ * @param key The key to store.
+ * @return 1 if the key was stored
+ */
+int
+cmd_store_env_and_pop(struct lldpctl_conn_t *conn, struct writer *w,
+ struct cmd_env *env, const void *key)
+{
+ return (cmd_store_env(conn, w, env, key) != -1 && cmdenv_pop(env, 1) != -1);
+}
+
+/**
+ * Store the given key with a value being the current keyword in the environment
+ * and pop X elements from the stack.
+ *
+ * @param conn The connection.
+ * @param w The writer (not used).
+ * @param env The environment.
+ * @param key The key to store.
+ * @return 1 if the key was stored
+ */
+int
+cmd_store_env_value_and_pop(struct lldpctl_conn_t *conn, struct writer *w,
+ struct cmd_env *env, const void *key)
+{
+ return (
+ cmdenv_put(env, key, cmdenv_arg(env)) != -1 && cmdenv_pop(env, 1) != -1);
+}
+int
+cmd_store_env_value_and_pop2(struct lldpctl_conn_t *conn, struct writer *w,
+ struct cmd_env *env, const void *key)
+{
+ return (
+ cmdenv_put(env, key, cmdenv_arg(env)) != -1 && cmdenv_pop(env, 2) != -1);
+}
+int
+cmd_store_env_value(struct lldpctl_conn_t *conn, struct writer *w, struct cmd_env *env,
+ const void *key)
+{
+ return (cmdenv_put(env, key, cmdenv_arg(env)) != -1);
+}
+int
+cmd_store_env_value_and_pop3(struct lldpctl_conn_t *conn, struct writer *w,
+ struct cmd_env *env, const void *key)
+{
+ return (
+ cmdenv_put(env, key, cmdenv_arg(env)) != -1 && cmdenv_pop(env, 3) != -1);
+}
+int
+cmd_store_something_env_value_and_pop2(const char *what, struct cmd_env *env,
+ const void *value)
+{
+ return (cmdenv_put(env, what, value) != -1 && cmdenv_pop(env, 2) != -1);
+}
+int
+cmd_store_something_env_value(const char *what, struct cmd_env *env, const void *value)
+{
+ return (cmdenv_put(env, what, value) != -1);
+}
+
+/**
+ * Provide an iterator on all interfaces contained in "ports".
+ *
+ * @warning This function is not reentrant. It uses static variables to keep
+ * track of ports that have already been provided. Moreover, to release all
+ * resources, the iterator should be used until its end.
+ *
+ * @param conn The connection.
+ * @param env The environment.
+ * @return The next interface in the set of ports (or in all ports if no `ports`
+ * variable is present in the environment)
+ */
+lldpctl_atom_t *
+cmd_iterate_on_interfaces(struct lldpctl_conn_t *conn, struct cmd_env *env)
+{
+ static lldpctl_atom_iter_t *iter = NULL;
+ static lldpctl_atom_t *iface_list = NULL;
+ static lldpctl_atom_t *iface = NULL;
+ const char *interfaces = cmdenv_get(env, "ports");
+
+ do {
+ if (iter == NULL) {
+ iface_list = lldpctl_get_interfaces(conn);
+ if (!iface_list) {
+ log_warnx("lldpctl",
+ "not able to get the list of interfaces. %s",
+ lldpctl_last_strerror(conn));
+ return NULL;
+ }
+ iter = lldpctl_atom_iter(iface_list);
+ if (!iter) return NULL;
+ } else {
+ iter = lldpctl_atom_iter_next(iface_list, iter);
+ if (iface) {
+ lldpctl_atom_dec_ref(iface);
+ iface = NULL;
+ }
+ if (!iter) {
+ lldpctl_atom_dec_ref(iface_list);
+ return NULL;
+ }
+ }
+
+ iface = lldpctl_atom_iter_value(iface_list, iter);
+ } while (interfaces &&
+ !contains(interfaces,
+ lldpctl_atom_get_str(iface, lldpctl_k_interface_name)));
+
+ return iface;
+}
+
+/**
+ * Provide an iterator on all ports contained in "ports", as well as the
+ * default port.
+ *
+ * @warning This function is not reentrant. It uses static variables to keep
+ * track of ports that have already been provided. Moreover, to release all
+ * resources, the iterator should be used until its end.
+ *
+ * @param conn The connection.
+ * @param env The environment.
+ * @param name Name of the interface (for logging purpose)
+ * @return The next interface in the set of ports (or in all ports if no `ports`
+ * variable is present in the environment), including the default port
+ * if no `ports` variable is present in the environment.
+ */
+lldpctl_atom_t *
+cmd_iterate_on_ports(struct lldpctl_conn_t *conn, struct cmd_env *env,
+ const char **name)
+{
+ static int put_default = 0;
+ static lldpctl_atom_t *last_port = NULL;
+ const char *interfaces = cmdenv_get(env, "ports");
+
+ if (last_port) {
+ lldpctl_atom_dec_ref(last_port);
+ last_port = NULL;
+ }
+ if (!put_default) {
+ lldpctl_atom_t *iface = cmd_iterate_on_interfaces(conn, env);
+ if (iface) {
+ *name = lldpctl_atom_get_str(iface, lldpctl_k_interface_name);
+ last_port = lldpctl_get_port(iface);
+ return last_port;
+ }
+ if (!interfaces) {
+ put_default = 1;
+ *name = "(default)";
+ last_port = lldpctl_get_default_port(conn);
+ return last_port;
+ }
+ return NULL;
+ } else {
+ put_default = 0;
+ return NULL;
+ }
+}
+
+/**
+ * Restrict the command to some ports.
+ */
+void
+cmd_restrict_ports(struct cmd_node *root)
+{
+ /* Restrict to some ports. */
+ commands_new(commands_new(root, "ports", "Restrict configuration to some ports",
+ cmd_check_no_env, NULL, "ports"),
+ NULL,
+ "Restrict configuration to the specified ports (comma-separated list)",
+ NULL, cmd_store_env_value_and_pop2, "ports");
+}
+
+/**
+ * Restrict the command to specific protocol
+ */
+void
+cmd_restrict_protocol(struct cmd_node *root)
+{
+ /* Restrict to some ports. */
+ commands_new(commands_new(root, "protocol", "Restrict to specific protocol",
+ cmd_check_no_env, NULL, "protocol"),
+ NULL, "Restrict display to the specified protocol", NULL,
+ cmd_store_env_value_and_pop2, "protocol");
+}
diff --git a/src/client/completion/_lldpcli b/src/client/completion/_lldpcli
new file mode 100644
index 0000000..ff942f1
--- /dev/null
+++ b/src/client/completion/_lldpcli
@@ -0,0 +1,48 @@
+#compdef lldpcli
+#
+# zsh completion for lldpcli
+#
+# Copyright (c) 2014 Vincent Bernat <bernat@luffy.cx>
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+
+__lldpcli_command () {
+ local -a completions
+ completions=(${(f)"$(_call_program commands lldpcli complete ${words[1,$(($CURRENT-1))]})"})
+ completions=(${completions:s/ /:/})
+ _describe -t lldpcli-command "lldpcli completion" completions "$@"
+}
+
+_lldpcli () {
+ local curcontext="$curcontext" state line
+
+ _arguments -C \
+ '*-d[print more debugging information]' \
+ '(- *)-v[print version number and exit]' \
+ '-u[use an alternate socket with lldpd]:UNIX socket:_files' \
+ '-f[output format]:format:(plain xml json json0 keyvalue)' \
+ '*-c[read a configuration file]:configuration file:_files' \
+ '(-)*::lldpcli command:__lldpcli_command'
+}
+
+
+_lldpcli "$@"
+
+# Local Variables:
+# mode: Shell-Script
+# sh-indentation: 4
+# indent-tabs-mode: nil
+# sh-basic-offset: 4
+# End:
+# vim: ft=zsh sw=4 ts=4 et
diff --git a/src/client/completion/lldpcli b/src/client/completion/lldpcli
new file mode 100755
index 0000000..48476f7
--- /dev/null
+++ b/src/client/completion/lldpcli
@@ -0,0 +1,26 @@
+_lldpcli()
+{
+ COMPREPLY=()
+ COMP_WORDBREAKS=" "
+ local cur=${COMP_WORDS[COMP_CWORD]}
+ local cmd=(${COMP_WORDS[*]})
+
+ if [ "" != "$cur" ]; then
+ unset cmd[COMP_CWORD]
+ fi
+
+ local choices=$(${cmd[0]} complete ${cmd[@]:1} | \
+ cut -d " " -f 1)
+ COMPREPLY=($(compgen -W '${choices}' -- ${cur} ))
+ return 0
+}
+
+complete -F _lldpcli lldpcli
+
+# Local Variables:
+# mode: Shell-Script
+# sh-indentation: 4
+# indent-tabs-mode: nil
+# sh-basic-offset: 4
+# End:
+# vim: ft=zsh sw=4 ts=4 et
diff --git a/src/client/conf-dot3.c b/src/client/conf-dot3.c
new file mode 100644
index 0000000..e90b60d
--- /dev/null
+++ b/src/client/conf-dot3.c
@@ -0,0 +1,35 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/*
+ * Copyright (c) 2013 Vincent Bernat <bernat@luffy.cx>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <unistd.h>
+#include <string.h>
+
+#include "client.h"
+#include "../log.h"
+
+/**
+ * Commands to configure dot3
+ */
+void
+register_commands_configure_dot3(struct cmd_node *configure)
+{
+ if (lldpctl_key_get_map(lldpctl_k_dot3_power_class)[0].string == NULL) return;
+
+ struct cmd_node *configure_dot3 =
+ commands_new(configure, "dot3", "Dot3 configuration", NULL, NULL, NULL);
+ register_commands_dot3pow(configure_dot3);
+}
diff --git a/src/client/conf-inv.c b/src/client/conf-inv.c
new file mode 100644
index 0000000..b137a18
--- /dev/null
+++ b/src/client/conf-inv.c
@@ -0,0 +1,161 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/*
+ * SPDX-FileCopyrightText: 2022 Koninklijke Philips N.V.
+ * SPDX-License-Identifier: ISC
+ */
+
+#include <unistd.h>
+#include <string.h>
+
+#include "client.h"
+#include "../log.h"
+
+static int
+cmd_inventory(struct lldpctl_conn_t *conn, struct writer *w, struct cmd_env *env,
+ const void *arg)
+{
+ log_debug("lldpctl", "configure inventory information");
+
+ lldpctl_atom_t *chassis = lldpctl_get_local_chassis(conn);
+
+ if (chassis == NULL) {
+ log_warnx("lldpctl", "unable to get configuration from lldpd. %s",
+ lldpctl_last_strerror(conn));
+ return 0;
+ }
+
+ const char *action = arg;
+ if ((!strcmp(action, "hardware-revision") &&
+ (lldpctl_atom_set_str(chassis, lldpctl_k_chassis_med_inventory_hw,
+ cmdenv_get(env, "hardware-revision")) == NULL)) ||
+ (!strcmp(action, "software-revision") &&
+ (lldpctl_atom_set_str(chassis, lldpctl_k_chassis_med_inventory_sw,
+ cmdenv_get(env, "software-revision")) == NULL)) ||
+ (!strcmp(action, "firmware-revision") &&
+ (lldpctl_atom_set_str(chassis, lldpctl_k_chassis_med_inventory_fw,
+ cmdenv_get(env, "firmware-revision")) == NULL)) ||
+ (!strcmp(action, "serial-number") &&
+ (lldpctl_atom_set_str(chassis, lldpctl_k_chassis_med_inventory_sn,
+ cmdenv_get(env, "serial-number")) == NULL)) ||
+ (!strcmp(action, "manufacturer") &&
+ (lldpctl_atom_set_str(chassis, lldpctl_k_chassis_med_inventory_manuf,
+ cmdenv_get(env, "manufacturer")) == NULL)) ||
+ (!strcmp(action, "model") &&
+ (lldpctl_atom_set_str(chassis, lldpctl_k_chassis_med_inventory_model,
+ cmdenv_get(env, "model")) == NULL)) ||
+ (!strcmp(action, "asset") &&
+ (lldpctl_atom_set_str(chassis, lldpctl_k_chassis_med_inventory_asset,
+ cmdenv_get(env, "asset")) == NULL))) {
+ log_warnx("lldpctl", "Unable to setup inventory. %s",
+ lldpctl_last_strerror(conn));
+ lldpctl_atom_dec_ref(chassis);
+ return 0;
+ }
+
+ log_info("lldpctl", "Configuration for inventory is applied");
+ lldpctl_atom_dec_ref(chassis);
+ return 1;
+}
+
+/**
+ * Register `configure inventory *` commands
+ *
+ */
+static void
+register_commands_inv(struct cmd_node *configure_inv, struct cmd_node *unconfigure_inv)
+{
+ commands_new(commands_new(commands_new(configure_inv, "hardware-revision",
+ "Set hardware-revision string", NULL, NULL, NULL),
+ NULL, "Inventory hardware-revision string", NULL,
+ cmd_store_env_value, "hardware-revision"),
+ NEWLINE, "Set hardware-revision string", NULL, cmd_inventory,
+ "hardware-revision");
+
+ commands_new(commands_new(unconfigure_inv, "hardware-revision",
+ "Unset hardware-revision string", NULL, NULL, NULL),
+ NEWLINE, "Unset hardware-revision string", NULL, cmd_inventory,
+ "hardware-revision");
+
+ commands_new(commands_new(commands_new(configure_inv, "software-revision",
+ "Set software-revision string", NULL, NULL, NULL),
+ NULL, "Inventory software-revision string", NULL,
+ cmd_store_env_value, "software-revision"),
+ NEWLINE, "Set software-revision string", NULL, cmd_inventory,
+ "software-revision");
+
+ commands_new(commands_new(unconfigure_inv, "software-revision",
+ "Unset software-revision string", NULL, NULL, NULL),
+ NEWLINE, "Unset software-revision string", NULL, cmd_inventory,
+ "software-revision");
+
+ commands_new(commands_new(commands_new(configure_inv, "firmware-revision",
+ "Set firmware-revision string", NULL, NULL, NULL),
+ NULL, "Inventory firmware-revision string", NULL,
+ cmd_store_env_value, "firmware-revision"),
+ NEWLINE, "Set firmware-revision string", NULL, cmd_inventory,
+ "firmware-revision");
+
+ commands_new(commands_new(unconfigure_inv, "firmware-revision",
+ "Unset firmware-revision string", NULL, NULL, NULL),
+ NEWLINE, "Unset firmware-revision string", NULL, cmd_inventory,
+ "firmware-revision");
+
+ commands_new(commands_new(commands_new(configure_inv, "serial-number",
+ "Set serial-number string", NULL, NULL, NULL),
+ NULL, "Inventory serial-number string", NULL,
+ cmd_store_env_value, "serial-number"),
+ NEWLINE, "Set serial-number string", NULL, cmd_inventory, "serial-number");
+
+ commands_new(commands_new(unconfigure_inv, "serial-number",
+ "Unset serial-number string", NULL, NULL, NULL),
+ NEWLINE, "Unset serial-number string", NULL, cmd_inventory,
+ "serial-number");
+
+ commands_new(commands_new(commands_new(configure_inv, "manufacturer",
+ "Set manufacturer string", NULL, NULL, NULL),
+ NULL, "Inventory manufacturer string", NULL,
+ cmd_store_env_value, "manufacturer"),
+ NEWLINE, "Set manufacturer string", NULL, cmd_inventory, "manufacturer");
+
+ commands_new(commands_new(unconfigure_inv, "manufacturer",
+ "Unset manufacturer string", NULL, NULL, NULL),
+ NEWLINE, "Unset manufacturer string", NULL, cmd_inventory, "manufacturer");
+
+ commands_new(commands_new(commands_new(configure_inv, "model",
+ "Set model string", NULL, NULL, NULL),
+ NULL, "Inventory model string", NULL, cmd_store_env_value,
+ "model"),
+ NEWLINE, "Set model string", NULL, cmd_inventory, "model");
+
+ commands_new(commands_new(unconfigure_inv, "model", "Unset model string", NULL,
+ NULL, NULL),
+ NEWLINE, "Unset model string", NULL, cmd_inventory, "model");
+
+ commands_new(commands_new(commands_new(configure_inv, "asset",
+ "Set asset string", NULL, NULL, NULL),
+ NULL, "Inventory asset string", NULL, cmd_store_env_value,
+ "asset"),
+ NEWLINE, "Set asset string", NULL, cmd_inventory, "asset");
+
+ commands_new(commands_new(unconfigure_inv, "asset", "Unset asset string", NULL,
+ NULL, NULL),
+ NEWLINE, "Unset asset string", NULL, cmd_inventory, "asset");
+}
+
+/**
+ * Register `configure inventory *`
+ *
+ */
+void
+register_commands_configure_inventory(struct cmd_node *configure,
+ struct cmd_node *unconfigure)
+{
+ if (lldpctl_key_get_map(lldpctl_k_med_policy_type)[0].value <= 0) return;
+
+ struct cmd_node *configure_inv = commands_new(configure, "inventory",
+ "Inventory configuration", NULL, NULL, NULL);
+ struct cmd_node *unconfigure_inv = commands_new(unconfigure, "inventory",
+ "Inventory configuration", NULL, NULL, NULL);
+
+ register_commands_inv(configure_inv, unconfigure_inv);
+}
diff --git a/src/client/conf-lldp.c b/src/client/conf-lldp.c
new file mode 100644
index 0000000..0c5b985
--- /dev/null
+++ b/src/client/conf-lldp.c
@@ -0,0 +1,762 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/*
+ * Copyright (c) 2013 Vincent Bernat <bernat@luffy.cx>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <unistd.h>
+#include <string.h>
+#include <limits.h>
+
+#include "client.h"
+#include "../log.h"
+
+static int
+cmd_txdelay(struct lldpctl_conn_t *conn, struct writer *w, struct cmd_env *env,
+ const void *arg)
+{
+ const char *interval;
+ char interval_ms[8]; /* less than 2.5 hours */
+ lldpctl_key_t key;
+ int arglen;
+
+ log_debug("lldpctl", "set transmit delay");
+
+ lldpctl_atom_t *config = lldpctl_get_configuration(conn);
+ if (config == NULL) {
+ log_warnx("lldpctl", "unable to get configuration from lldpd. %s",
+ lldpctl_last_strerror(conn));
+ return 0;
+ }
+ interval = cmdenv_get(env, "tx-interval");
+ key = lldpctl_k_config_tx_interval;
+ /* interval is either <number> for seconds or <number>ms for milliseconds */
+ if (interval) {
+ arglen = strlen(interval);
+ /* room for "ms" in interval, room for interval in interval_ms */
+ if (arglen >= 2 && arglen - 2 < sizeof(interval_ms) &&
+ strcmp("ms", interval + arglen - 2) == 0) {
+ /* remove "ms" suffix */
+ memcpy(interval_ms, interval, arglen - 2);
+ interval_ms[arglen - 2] = '\0';
+ /* substitute key and value */
+ key = lldpctl_k_config_tx_interval_ms;
+ interval = interval_ms;
+ }
+ }
+ if (lldpctl_atom_set_str(config, key, interval) == NULL) {
+ log_warnx("lldpctl", "unable to set transmit delay. %s",
+ lldpctl_last_strerror(conn));
+ lldpctl_atom_dec_ref(config);
+ return 0;
+ }
+ log_info("lldpctl", "transmit delay set to new value");
+ lldpctl_atom_dec_ref(config);
+ return 1;
+}
+
+static int
+cmd_txhold(struct lldpctl_conn_t *conn, struct writer *w, struct cmd_env *env,
+ const void *arg)
+{
+ log_debug("lldpctl", "set transmit hold");
+
+ lldpctl_atom_t *config = lldpctl_get_configuration(conn);
+ if (config == NULL) {
+ log_warnx("lldpctl", "unable to get configuration from lldpd. %s",
+ lldpctl_last_strerror(conn));
+ return 0;
+ }
+ if (lldpctl_atom_set_str(config, lldpctl_k_config_tx_hold,
+ cmdenv_get(env, "tx-hold")) == NULL) {
+ log_warnx("lldpctl", "unable to set transmit hold. %s",
+ lldpctl_last_strerror(conn));
+ lldpctl_atom_dec_ref(config);
+ return 0;
+ }
+ log_info("lldpctl", "transmit hold set to new value %s",
+ cmdenv_get(env, "tx-hold"));
+ lldpctl_atom_dec_ref(config);
+ return 1;
+}
+
+static int
+cmd_status(struct lldpctl_conn_t *conn, struct writer *w, struct cmd_env *env,
+ const void *arg)
+{
+ lldpctl_atom_t *port;
+ const char *name;
+ const char *status = cmdenv_get(env, "status");
+
+ log_debug("lldpctl", "lldp administrative port status set to '%s'", status);
+
+ if (!status || !strlen(status)) {
+ log_warnx("lldpctl", "no status specified");
+ return 0;
+ }
+
+ while ((port = cmd_iterate_on_ports(conn, env, &name))) {
+ if (lldpctl_atom_set_str(port, lldpctl_k_port_status, status) == NULL) {
+ log_warnx("lldpctl",
+ "unable to set LLDP status for %s."
+ " %s",
+ name, lldpctl_last_strerror(conn));
+ }
+ }
+
+ return 1;
+}
+
+static int
+cmd_agent_type(struct lldpctl_conn_t *conn, struct writer *w, struct cmd_env *env,
+ const void *arg)
+{
+ const char *str = arg;
+ int value = -1;
+
+ log_debug("lldpctl", "set agent type to '%s'", str);
+
+ lldpctl_atom_t *config = lldpctl_get_configuration(conn);
+ if (config == NULL) {
+ log_warnx("lldpctl", "unable to get configuration from lldpd. %s",
+ lldpctl_last_strerror(conn));
+ return 0;
+ }
+
+ for (lldpctl_map_t *b_map =
+ lldpctl_key_get_map(lldpctl_k_config_lldp_agent_type);
+ b_map->string; b_map++) {
+ if (!strcmp(b_map->string, str)) {
+ value = b_map->value;
+ break;
+ }
+ }
+
+ if (value == -1) {
+ log_warnx("lldpctl", "invalid value");
+ lldpctl_atom_dec_ref(config);
+ return 0;
+ }
+
+ if (lldpctl_atom_set_int(config, lldpctl_k_config_lldp_agent_type, value) ==
+ NULL) {
+ log_warnx("lldpctl",
+ "unable to set LLDP agent type."
+ " %s",
+ lldpctl_last_strerror(conn));
+ lldpctl_atom_dec_ref(config);
+ return 0;
+ }
+
+ log_info("lldpctl", "agent type set to new value : %s", str);
+ lldpctl_atom_dec_ref(config);
+
+ return 1;
+}
+
+static int
+cmd_portid_type_local(struct lldpctl_conn_t *conn, struct writer *w,
+ struct cmd_env *env, const void *arg)
+{
+ lldpctl_atom_t *port;
+ const char *name;
+ const char *id = cmdenv_get(env, "port-id");
+ const char *descr = cmdenv_get(env, "port-descr");
+
+ log_debug("lldpctl",
+ "lldp PortID TLV Subtype Local port-id '%s' port-descr '%s'", id, descr);
+
+ if (!id || !strlen(id)) {
+ log_warnx("lldpctl", "no id specified");
+ return 0;
+ }
+
+ while ((port = cmd_iterate_on_ports(conn, env, &name))) {
+ if (lldpctl_atom_set_str(port, lldpctl_k_port_id, id) == NULL) {
+ log_warnx("lldpctl",
+ "unable to set LLDP PortID for %s."
+ " %s",
+ name, lldpctl_last_strerror(conn));
+ }
+ if (descr &&
+ lldpctl_atom_set_str(port, lldpctl_k_port_descr, descr) == NULL) {
+ log_warnx("lldpctl",
+ "unable to set LLDP Port Description for %s."
+ " %s",
+ name, lldpctl_last_strerror(conn));
+ }
+ }
+
+ return 1;
+}
+
+static int
+cmd_port_descr(struct lldpctl_conn_t *conn, struct writer *w, struct cmd_env *env,
+ const void *arg)
+{
+ lldpctl_atom_t *port;
+ const char *name;
+ const char *descr = cmdenv_get(env, "port-descr");
+
+ log_debug("lldpctl", "lldp port-descr '%s'", descr);
+
+ while ((port = cmd_iterate_on_ports(conn, env, &name))) {
+ if (descr &&
+ lldpctl_atom_set_str(port, lldpctl_k_port_descr, descr) == NULL) {
+ log_warnx("lldpctl",
+ "unable to set LLDP Port Description for %s."
+ " %s",
+ name, lldpctl_last_strerror(conn));
+ }
+ }
+
+ return 1;
+}
+
+static int
+cmd_portid_type(struct lldpctl_conn_t *conn, struct writer *w, struct cmd_env *env,
+ const void *arg)
+{
+ const char *value_str = 0;
+ int value = -1;
+
+ log_debug("lldpctl", "lldp PortID TLV Subtype");
+
+ lldpctl_atom_t *config = lldpctl_get_configuration(conn);
+ if (config == NULL) {
+ log_warnx("lldpctl", "unable to get configuration from lldpd. %s",
+ lldpctl_last_strerror(conn));
+ return 0;
+ }
+
+ value_str = arg;
+ for (lldpctl_map_t *b_map =
+ lldpctl_key_get_map(lldpctl_k_config_lldp_portid_type);
+ b_map->string; b_map++) {
+ if (!strcmp(b_map->string, value_str)) {
+ value = b_map->value;
+ break;
+ }
+ }
+
+ if (value == -1) {
+ log_warnx("lldpctl", "invalid value");
+ lldpctl_atom_dec_ref(config);
+ return 0;
+ }
+
+ if (lldpctl_atom_set_int(config, lldpctl_k_config_lldp_portid_type, value) ==
+ NULL) {
+ log_warnx("lldpctl",
+ "unable to set LLDP PortID type."
+ " %s",
+ lldpctl_last_strerror(conn));
+ lldpctl_atom_dec_ref(config);
+ return 0;
+ }
+
+ log_info("lldpctl", "LLDP PortID TLV type set to new value : %s", value_str);
+ lldpctl_atom_dec_ref(config);
+
+ return 1;
+}
+
+static int
+cmd_chassis_cap_advertise(struct lldpctl_conn_t *conn, struct writer *w,
+ struct cmd_env *env, const void *arg)
+{
+ log_debug("lldpctl", "lldp capabilities-advertisements %s",
+ arg ? "enable" : "disable");
+
+ lldpctl_atom_t *config = lldpctl_get_configuration(conn);
+ if (config == NULL) {
+ log_warnx("lldpctl", "unable to get configuration from lldpd. %s",
+ lldpctl_last_strerror(conn));
+ return 0;
+ }
+ if (lldpctl_atom_set_int(config, lldpctl_k_config_chassis_cap_advertise,
+ arg ? 1 : 0) == NULL) {
+ log_warnx("lldpctl",
+ "unable to %s chassis capabilities advertisement: %s",
+ arg ? "enable" : "disable", lldpctl_last_strerror(conn));
+ lldpctl_atom_dec_ref(config);
+ return 0;
+ }
+ log_info("lldpctl", "chassis capabilities advertisement %s",
+ arg ? "enabled" : "disabled");
+ lldpctl_atom_dec_ref(config);
+ return 1;
+}
+
+/* FIXME: see about compressing this with other functions */
+static int
+cmd_chassis_mgmt_advertise(struct lldpctl_conn_t *conn, struct writer *w,
+ struct cmd_env *env, const void *arg)
+{
+ log_debug("lldpctl", "lldp management-addresses-advertisements %s",
+ arg ? "enable" : "disable");
+
+ lldpctl_atom_t *config = lldpctl_get_configuration(conn);
+ if (config == NULL) {
+ log_warnx("lldpctl", "unable to get configuration from lldpd. %s",
+ lldpctl_last_strerror(conn));
+ return 0;
+ }
+ if (lldpctl_atom_set_int(config, lldpctl_k_config_chassis_mgmt_advertise,
+ arg ? 1 : 0) == NULL) {
+ log_warnx("lldpctl",
+ "unable to %s management addresses advertisement: %s",
+ arg ? "enable" : "disable", lldpctl_last_strerror(conn));
+ lldpctl_atom_dec_ref(config);
+ return 0;
+ }
+ log_info("lldpctl", "management addresses advertisement %s",
+ arg ? "enabled" : "disabled");
+ lldpctl_atom_dec_ref(config);
+ return 1;
+}
+
+static int
+cmd_vlan_tx(struct lldpctl_conn_t *conn, struct writer *w, struct cmd_env *env,
+ const void *arg)
+{
+ lldpctl_atom_t *port;
+ const char *name;
+ const char *vlan_id = cmdenv_get(env, "vlan-tx-id");
+ const char *vlan_prio = cmdenv_get(env, "vlan-tx-prio");
+ const char *vlan_dei = cmdenv_get(env, "vlan-tx-dei");
+
+ /* Default values are used to disable VLAN */
+ int vlan_id_int = -1;
+ int vlan_prio_int = -1;
+ int vlan_dei_int = -1;
+ int vlan_tag = -1;
+
+ if (!arg)
+ log_debug("lldpctl",
+ "lldp disable VLAN tagging of transmitted LLDP frames");
+ else
+ log_debug("lldpctl",
+ "lldp enable VLAN tagging of transmitted LLDP frames with VLAN ID: '%s', Priority: '%s', DEI: '%s'",
+ vlan_id, vlan_prio, vlan_dei);
+
+ if (arg) {
+ if (!vlan_id || !strlen(vlan_id)) {
+ log_warnx("lldpctl", "no VLAN id for TX specified");
+ return 0;
+ } else {
+ const char *errstr;
+ vlan_id_int = strtonum(vlan_id, 0, 4094, &errstr);
+ if (errstr != NULL) {
+ log_warnx("lldpctl", "invalid VLAN ID for TX `%s': %s",
+ vlan_id, errstr);
+ return 0;
+ }
+ }
+
+ if (!vlan_prio || !strlen(vlan_prio)) {
+ log_warnx("lldpctl",
+ "no VLAN priority for TX specified, using default (0)");
+ /* Use default priority */
+ vlan_prio_int = 0;
+ } else {
+ const char *errstr;
+ vlan_prio_int = strtonum(vlan_prio, 0, 7, &errstr);
+ if (errstr != NULL) {
+ log_warnx("lldpctl", "invalid VLAN piority `%s': %s",
+ vlan_prio, errstr);
+ return 0;
+ }
+ }
+
+ if (!vlan_dei || !strlen(vlan_dei)) {
+ log_warnx("lldpctl",
+ "no VLAN Drop eligible indicator (DEI) for TX specified, using default (0)");
+ /* Use default priority */
+ vlan_dei_int = 0;
+ } else {
+ const char *errstr;
+ vlan_dei_int = strtonum(vlan_dei, 0, 1, &errstr);
+ if (errstr != NULL) {
+ log_warnx("lldpctl",
+ "invalid VLAN Drop eligible indicator (DEI) `%s': %s",
+ vlan_dei, errstr);
+ return 0;
+ }
+ }
+ /* Priority(3bits) | DEI(1bit) | VID(12bits) */
+ vlan_tag = ((vlan_prio_int & 0x7) << 13) |
+ ((vlan_dei_int & 0x1) << 12) | (vlan_id_int & 0xfff);
+ }
+
+ while ((port = cmd_iterate_on_ports(conn, env, &name))) {
+ if (lldpctl_atom_set_int(port, lldpctl_k_port_vlan_tx, vlan_tag) ==
+ NULL) {
+ log_warnx("lldpctl",
+ "unable to set VLAN TX config on %s."
+ " %s",
+ name, lldpctl_last_strerror(conn));
+ }
+ }
+
+ return 1;
+}
+
+#ifdef ENABLE_CUSTOM
+static int
+cmd_custom_tlv_set(struct lldpctl_conn_t *conn, struct writer *w, struct cmd_env *env,
+ const void *arg)
+{
+ lldpctl_atom_t *port;
+ const char *s;
+ const char *name;
+ uint8_t oui[LLDP_TLV_ORG_OUI_LEN];
+ uint8_t oui_info[LLDP_TLV_ORG_OUI_INFO_MAXLEN];
+ int oui_info_len = 0;
+ uint16_t subtype = 0;
+ const char *op = "add";
+
+ if (!arg || !strcmp(arg, "remove")) op = "remove";
+
+ log_debug("lldpctl", "lldp custom-tlv(s) %s", op);
+
+ if (!arg) goto set;
+
+ s = cmdenv_get(env, "oui");
+ if (!s ||
+ (sscanf(s, "%02hhx,%02hhx,%02hhx", &oui[0], &oui[1], &oui[2]) != 3 &&
+ sscanf(s, "%02hhX,%02hhX,%02hhX", &oui[0], &oui[1], &oui[2]) != 3)) {
+ log_warnx("lldpctl", "invalid OUI value '%s'", s);
+ return 0;
+ }
+
+ s = cmdenv_get(env, "subtype");
+ if (!s) {
+ log_warnx("lldpctl", "no subtype specified");
+ return 0;
+ } else {
+ const char *errstr;
+ subtype = strtonum(s, 0, 255, &errstr);
+ if (errstr != NULL) {
+ log_warnx("lldpctl", "invalid subtype value `%s': %s", s,
+ errstr);
+ return 0;
+ }
+ }
+
+ s = cmdenv_get(env, "oui-info");
+ /* This info is optional */
+ if (s) {
+ const char delim[] = ",";
+ char *s_copy = strdup(s);
+ char *token = strtok(s_copy, delim);
+ while (token != NULL) {
+ if (sscanf(token, "%02hhx", &oui_info[oui_info_len]) == 1 ||
+ sscanf(token, "%02hhX", &oui_info[oui_info_len]) == 1)
+ oui_info_len++;
+ if (oui_info_len >= sizeof(oui_info)) break;
+ token = strtok(NULL, delim);
+ }
+ free(s_copy);
+ }
+
+ s = cmdenv_get(env, "replace");
+ /* This info is optional */
+ if (s) op = "replace";
+
+set:
+ while ((port = cmd_iterate_on_ports(conn, env, &name))) {
+ lldpctl_atom_t *custom_tlvs;
+ if (!arg) {
+ lldpctl_atom_set(port, lldpctl_k_custom_tlvs_clear, NULL);
+ } else if (!(custom_tlvs =
+ lldpctl_atom_get(port, lldpctl_k_custom_tlvs))) {
+ log_warnx("lldpctl", "unable to get custom TLVs for port %s",
+ name);
+ } else {
+ lldpctl_atom_t *tlv = lldpctl_atom_create(custom_tlvs);
+ if (!tlv) {
+ log_warnx("lldpctl",
+ "unable to create new custom TLV for port %s",
+ name);
+ } else {
+ /* Configure custom TLV */
+ lldpctl_atom_set_buffer(tlv, lldpctl_k_custom_tlv_oui,
+ oui, sizeof(oui));
+ lldpctl_atom_set_int(tlv,
+ lldpctl_k_custom_tlv_oui_subtype, subtype);
+ lldpctl_atom_set_buffer(tlv,
+ lldpctl_k_custom_tlv_oui_info_string, oui_info,
+ oui_info_len);
+ lldpctl_atom_set_str(tlv, lldpctl_k_custom_tlv_op, op);
+
+ /* Assign it to port */
+ lldpctl_atom_set(port, lldpctl_k_custom_tlv, tlv);
+
+ lldpctl_atom_dec_ref(tlv);
+ }
+ lldpctl_atom_dec_ref(custom_tlvs);
+ }
+ }
+
+ return 1;
+}
+
+static int
+cmd_check_no_add_env(struct cmd_env *env, const void *arg)
+{
+ const char *what = arg;
+ if (cmdenv_get(env, "add")) return 0;
+ if (cmdenv_get(env, what)) return 0;
+ return 1;
+}
+
+static int
+cmd_check_no_replace_env(struct cmd_env *env, const void *arg)
+{
+ const char *what = arg;
+ if (cmdenv_get(env, "replace")) return 0;
+ if (cmdenv_get(env, what)) return 0;
+ return 1;
+}
+
+static void
+register_commands_configure_lldp_custom_tlvs(struct cmd_node *configure_lldp,
+ struct cmd_node *unconfigure_lldp)
+{
+ struct cmd_node *configure_custom_tlvs;
+ struct cmd_node *unconfigure_custom_tlvs;
+ struct cmd_node *configure_custom_tlvs_basic;
+ struct cmd_node *unconfigure_custom_tlvs_basic;
+
+ configure_custom_tlvs = commands_new(configure_lldp, "custom-tlv",
+ "Add custom TLV(s) to be broadcast on ports", NULL, NULL, NULL);
+
+ unconfigure_custom_tlvs = commands_new(unconfigure_lldp, "custom-tlv",
+ "Remove ALL custom TLV(s)", NULL, NULL, NULL);
+
+ commands_new(unconfigure_custom_tlvs, NEWLINE, "Remove ALL custom TLV", NULL,
+ cmd_custom_tlv_set, NULL);
+
+ commands_new(configure_custom_tlvs, "add", "Add custom TLV",
+ cmd_check_no_replace_env, cmd_store_env_and_pop, "add");
+ commands_new(configure_custom_tlvs, "replace", "Replace custom TLV",
+ cmd_check_no_add_env, cmd_store_env_and_pop, "replace");
+
+ /* Basic form: 'configure lldp custom-tlv oui 11,22,33 subtype 44' */
+ configure_custom_tlvs_basic = commands_new(
+ commands_new(commands_new(commands_new(configure_custom_tlvs, "oui",
+ "Organizationally Unique Identifier", NULL,
+ NULL, NULL),
+ NULL, "Organizationally Unique Identifier", NULL,
+ cmd_store_env_value, "oui"),
+ "subtype", "Organizationally Defined Subtype", NULL, NULL, NULL),
+ NULL, "Organizationally Defined Subtype", NULL, cmd_store_env_value,
+ "subtype");
+
+ commands_new(configure_custom_tlvs_basic, NEWLINE,
+ "Add custom TLV(s) to be broadcast on ports", NULL, cmd_custom_tlv_set,
+ "enable");
+
+ /* Basic form: 'unconfigure lldp custom-tlv oui 11,22,33 subtype 44' */
+ unconfigure_custom_tlvs_basic = commands_new(
+ commands_new(commands_new(commands_new(unconfigure_custom_tlvs, "oui",
+ "Organizationally Unique Identifier", NULL,
+ NULL, NULL),
+ NULL, "Organizationally Unique Identifier", NULL,
+ cmd_store_env_value, "oui"),
+ "subtype", "Organizationally Defined Subtype", NULL, NULL, NULL),
+ NULL, "Organizationally Defined Subtype", NULL, cmd_store_env_value,
+ "subtype");
+
+ commands_new(unconfigure_custom_tlvs_basic, NEWLINE,
+ "Remove specific custom TLV", NULL, cmd_custom_tlv_set, "remove");
+
+ /* Extended form: 'configure custom-tlv lldp oui 11,22,33 subtype 44 oui-info
+ * 55,66,77,...' */
+ commands_new(commands_new(commands_new(configure_custom_tlvs_basic, "oui-info",
+ "Organizationally Unique Identifier", NULL, NULL,
+ NULL),
+ NULL, "OUI Info String", NULL, cmd_store_env_value,
+ "oui-info"),
+ NEWLINE, "Add custom TLV(s) to be broadcast on ports", NULL,
+ cmd_custom_tlv_set, "enable");
+}
+#endif /* ENABLE_CUSTOM */
+
+static int
+cmd_store_status_env_value(struct lldpctl_conn_t *conn, struct writer *w,
+ struct cmd_env *env, const void *value)
+{
+ return cmd_store_something_env_value("status", env, value);
+}
+
+/**
+ * Register `configure lldp` commands.
+ *
+ * Those are the commands that are related to the LLDP protocol but not
+ * Dot1/Dot3/MED. Commands not related to LLDP should go in system instead.
+ */
+void
+register_commands_configure_lldp(struct cmd_node *configure,
+ struct cmd_node *unconfigure)
+{
+ struct cmd_node *configure_lldp =
+ commands_new(configure, "lldp", "LLDP configuration", NULL, NULL, NULL);
+ struct cmd_node *unconfigure_lldp =
+ commands_new(unconfigure, "lldp", "LLDP configuration", NULL, NULL, NULL);
+
+ commands_new(
+ commands_new(commands_new(configure_lldp, "tx-interval",
+ "Set LLDP transmit delay", cmd_check_no_env, NULL,
+ "ports"),
+ NULL, "LLDP transmit <delay> in seconds or <delay>ms in milliseconds",
+ NULL, cmd_store_env_value, "tx-interval"),
+ NEWLINE, "Set LLDP transmit delay", NULL, cmd_txdelay, NULL);
+
+ commands_new(commands_new(commands_new(configure_lldp, "tx-hold",
+ "Set LLDP transmit hold", cmd_check_no_env, NULL,
+ "ports"),
+ NULL, "LLDP transmit hold in seconds", NULL,
+ cmd_store_env_value, "tx-hold"),
+ NEWLINE, "Set LLDP transmit hold", NULL, cmd_txhold, NULL);
+
+ struct cmd_node *status = commands_new(configure_lldp, "status",
+ "Set administrative status", NULL, NULL, NULL);
+
+ for (lldpctl_map_t *status_map = lldpctl_key_get_map(lldpctl_k_port_status);
+ status_map->string; status_map++) {
+ const char *tag = strdup(totag(status_map->string));
+ SUPPRESS_LEAK(tag);
+ commands_new(commands_new(status, tag, status_map->string, NULL,
+ cmd_store_status_env_value, status_map->string),
+ NEWLINE, "Set port administrative status", NULL, cmd_status, NULL);
+ }
+
+ /* Configure the various agent type we can configure. */
+ struct cmd_node *configure_lldp_agent_type = commands_new(configure_lldp,
+ "agent-type", "LLDP agent type", NULL, NULL, NULL);
+ for (lldpctl_map_t *b_map =
+ lldpctl_key_get_map(lldpctl_k_config_lldp_agent_type);
+ b_map->string; b_map++) {
+ const char *tag = strdup(totag(b_map->string));
+ SUPPRESS_LEAK(tag);
+ commands_new(commands_new(configure_lldp_agent_type, tag, b_map->string,
+ NULL, NULL, NULL),
+ NEWLINE, "Set LLDP agent type", NULL, cmd_agent_type,
+ b_map->string);
+ }
+
+ /* Now handle the various portid subtypes we can configure. */
+ struct cmd_node *configure_lldp_portid_type = commands_new(configure_lldp,
+ "portidsubtype", "LLDP PortID TLV Subtype", NULL, NULL, NULL);
+
+ for (lldpctl_map_t *b_map =
+ lldpctl_key_get_map(lldpctl_k_config_lldp_portid_type);
+ b_map->string; b_map++) {
+ if (!strcmp(b_map->string, "ifname")) {
+ commands_new(commands_new(configure_lldp_portid_type,
+ b_map->string, "Interface Name",
+ cmd_check_no_env, NULL, "ports"),
+ NEWLINE, NULL, NULL, cmd_portid_type, b_map->string);
+ } else if (!strcmp(b_map->string, "local")) {
+ struct cmd_node *port_id =
+ commands_new(commands_new(configure_lldp_portid_type,
+ b_map->string, "Local", NULL, NULL, NULL),
+ NULL, "Port ID", NULL, cmd_store_env_value, "port-id");
+ commands_new(port_id, NEWLINE, "Set local port ID", NULL,
+ cmd_portid_type_local, b_map->string);
+ commands_new(commands_new(commands_new(port_id, "description",
+ "Also set port description", NULL,
+ NULL, NULL),
+ NULL, "Port description", NULL,
+ cmd_store_env_value, "port-descr"),
+ NEWLINE, "Set local port ID and description", NULL,
+ cmd_portid_type_local, NULL);
+ } else if (!strcmp(b_map->string, "macaddress")) {
+ commands_new(commands_new(configure_lldp_portid_type,
+ b_map->string, "MAC Address", cmd_check_no_env,
+ NULL, "ports"),
+ NEWLINE, NULL, NULL, cmd_portid_type, b_map->string);
+ }
+ }
+
+ commands_new(commands_new(commands_new(configure_lldp, "portdescription",
+ "Port Description", NULL, NULL, NULL),
+ NULL, "Port description", NULL, cmd_store_env_value,
+ "port-descr"),
+ NEWLINE, "Set port description", NULL, cmd_port_descr, NULL);
+
+ commands_new(commands_new(configure_lldp, "capabilities-advertisements",
+ "Enable chassis capabilities advertisement", cmd_check_no_env,
+ NULL, "ports"),
+ NEWLINE, "Enable chassis capabilities advertisement", NULL,
+ cmd_chassis_cap_advertise, "enable");
+ commands_new(commands_new(unconfigure_lldp, "capabilities-advertisements",
+ "Don't enable chassis capabilities advertisement",
+ cmd_check_no_env, NULL, "ports"),
+ NEWLINE, "Don't enable chassis capabilities advertisement", NULL,
+ cmd_chassis_cap_advertise, NULL);
+
+ commands_new(commands_new(configure_lldp, "management-addresses-advertisements",
+ "Enable management addresses advertisement", cmd_check_no_env,
+ NULL, "ports"),
+ NEWLINE, "Enable management addresses advertisement", NULL,
+ cmd_chassis_mgmt_advertise, "enable");
+ commands_new(commands_new(unconfigure_lldp,
+ "management-addresses-advertisements",
+ "Don't enable management addresses advertisement",
+ cmd_check_no_env, NULL, "ports"),
+ NEWLINE, "Don't enable management addresses advertisement", NULL,
+ cmd_chassis_mgmt_advertise, NULL);
+
+ struct cmd_node *vlan_tx =
+ commands_new(commands_new(configure_lldp, "vlan-tx",
+ "Send LLDP frames with a VLAN tag", NULL, NULL, NULL),
+ NULL, "VLAN ID (0-4094)", NULL, cmd_store_env_value, "vlan-tx-id");
+
+ struct cmd_node *vlan_tx_prio =
+ commands_new(commands_new(vlan_tx, "priority",
+ "Also set a priority in a VLAN tag (default 0)", NULL,
+ NULL, NULL),
+ NULL, "Priority to be included in a VLAN tag (0-7)", NULL,
+ cmd_store_env_value, "vlan-tx-prio");
+
+ commands_new(vlan_tx, NEWLINE, "Enable VLAN tagging of transmitted LLDP frames",
+ NULL, cmd_vlan_tx, "enable");
+
+ commands_new(vlan_tx_prio, NEWLINE,
+ "Set VLAN ID and priority for transmitted frames", NULL, cmd_vlan_tx,
+ "enable");
+
+ commands_new(
+ commands_new(
+ commands_new(vlan_tx_prio, "dei",
+ "Also set a Drop eligible indicator (DEI) in a VLAN tag (default 0)",
+ NULL, NULL, NULL),
+ NULL,
+ "Drop eligible indicator (DEI) in a VLAN tag (0-don't drop; 1-drop)",
+ NULL, cmd_store_env_value, "vlan-tx-dei"),
+ NEWLINE, "Set VLAN ID, priority and DEI for transmitted frames", NULL,
+ cmd_vlan_tx, "enable");
+
+ commands_new(commands_new(unconfigure_lldp, "vlan-tx",
+ "Send LLDP frames without VLAN tag", NULL, NULL, NULL),
+ NEWLINE, "Disable VLAN tagging of transmitted LLDP frames", NULL,
+ cmd_vlan_tx, NULL);
+
+#ifdef ENABLE_CUSTOM
+ register_commands_configure_lldp_custom_tlvs(configure_lldp, unconfigure_lldp);
+#endif
+}
diff --git a/src/client/conf-med.c b/src/client/conf-med.c
new file mode 100644
index 0000000..8c03f8e
--- /dev/null
+++ b/src/client/conf-med.c
@@ -0,0 +1,499 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/*
+ * Copyright (c) 2012 Vincent Bernat <bernat@luffy.cx>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <unistd.h>
+#include <string.h>
+
+#include "client.h"
+#include "../log.h"
+
+static int
+_cmd_medlocation(struct lldpctl_conn_t *conn, struct cmd_env *env, int format)
+{
+ lldpctl_atom_t *port;
+ const char *name;
+ while ((port = cmd_iterate_on_ports(conn, env, &name))) {
+ lldpctl_atom_t *med_location = NULL, *med_locations = NULL;
+ const char *what = NULL;
+ int ok = 0;
+
+ med_locations = lldpctl_atom_get(port, lldpctl_k_port_med_locations);
+ if (med_locations == NULL) {
+ log_warnx("lldpctl",
+ "unable to set LLDP-MED location: support seems unavailable");
+ continue; /* Iterator needs to be run until end */
+ }
+
+ med_location = lldpctl_atom_iter_value(med_locations,
+ lldpctl_atom_iter_next(med_locations,
+ lldpctl_atom_iter(med_locations)));
+
+ switch (format) {
+ case LLDP_MED_LOCFORMAT_COORD:
+ if ((what = "format",
+ lldpctl_atom_set_int(med_location,
+ lldpctl_k_med_location_format, format)) == NULL ||
+ (what = "latitude",
+ lldpctl_atom_set_str(med_location,
+ lldpctl_k_med_location_latitude,
+ cmdenv_get(env, "latitude"))) == NULL ||
+ (what = "longitude",
+ lldpctl_atom_set_str(med_location,
+ lldpctl_k_med_location_longitude,
+ cmdenv_get(env, "longitude"))) == NULL ||
+ (what = "altitude",
+ lldpctl_atom_set_str(med_location,
+ lldpctl_k_med_location_altitude,
+ cmdenv_get(env, "altitude"))) == NULL ||
+ (what = "altitude unit",
+ lldpctl_atom_set_str(med_location,
+ lldpctl_k_med_location_altitude_unit,
+ cmdenv_get(env, "altitude-unit"))) == NULL ||
+ (what = "datum",
+ lldpctl_atom_set_str(med_location,
+ lldpctl_k_med_location_geoid,
+ cmdenv_get(env, "datum"))) == NULL)
+ log_warnx("lldpctl",
+ "unable to set LLDP MED location value for %s on %s. %s.",
+ what, name, lldpctl_last_strerror(conn));
+ else
+ ok = 1;
+ break;
+ case LLDP_MED_LOCFORMAT_CIVIC:
+ if ((what = "format",
+ lldpctl_atom_set_int(med_location,
+ lldpctl_k_med_location_format, format)) == NULL ||
+ (what = "country",
+ lldpctl_atom_set_str(med_location,
+ lldpctl_k_med_location_country,
+ cmdenv_get(env, "country"))) == NULL) {
+ log_warnx("lldpctl",
+ "unable to set LLDP MED location value for %s on %s. %s.",
+ what, name, lldpctl_last_strerror(conn));
+ break;
+ }
+ ok = 1;
+ for (lldpctl_map_t *addr_map =
+ lldpctl_key_get_map(lldpctl_k_med_civicaddress_type);
+ addr_map->string; addr_map++) {
+ lldpctl_atom_t *cael, *caels;
+ const char *value = cmdenv_get(env, addr_map->string);
+ if (!value) continue;
+
+ caels = lldpctl_atom_get(med_location,
+ lldpctl_k_med_location_ca_elements);
+ cael = lldpctl_atom_create(caels);
+
+ if (lldpctl_atom_set_str(cael,
+ lldpctl_k_med_civicaddress_type,
+ addr_map->string) == NULL ||
+ lldpctl_atom_set_str(cael,
+ lldpctl_k_med_civicaddress_value,
+ value) == NULL ||
+ lldpctl_atom_set(med_location,
+ lldpctl_k_med_location_ca_elements,
+ cael) == NULL) {
+ log_warnx("lldpctl",
+ "unable to add a civic address element `%s`. %s",
+ addr_map->string,
+ lldpctl_last_strerror(conn));
+ ok = 0;
+ }
+
+ lldpctl_atom_dec_ref(cael);
+ lldpctl_atom_dec_ref(caels);
+ if (!ok) break;
+ }
+ break;
+ case LLDP_MED_LOCFORMAT_ELIN:
+ if (lldpctl_atom_set_int(med_location,
+ lldpctl_k_med_location_format, format) == NULL ||
+ lldpctl_atom_set_str(med_location,
+ lldpctl_k_med_location_elin,
+ cmdenv_get(env, "elin")) == NULL)
+ log_warnx("lldpctl",
+ "unable to set LLDP MED location on %s. %s", name,
+ lldpctl_last_strerror(conn));
+ else
+ ok = 1;
+ break;
+ }
+ if (ok) {
+ if (lldpctl_atom_set(port, lldpctl_k_port_med_locations,
+ med_location) == NULL) {
+ log_warnx("lldpctl",
+ "unable to set LLDP MED location on %s. %s.", name,
+ lldpctl_last_strerror(conn));
+ } else
+ log_info("lldpctl",
+ "LLDP-MED location has been set for port %s", name);
+ }
+
+ lldpctl_atom_dec_ref(med_location);
+ lldpctl_atom_dec_ref(med_locations);
+ }
+ return 1;
+}
+
+static int
+cmd_medlocation_coordinate(struct lldpctl_conn_t *conn, struct writer *w,
+ struct cmd_env *env, const void *arg)
+{
+ log_debug("lldpctl", "set MED location coordinate");
+ return _cmd_medlocation(conn, env, LLDP_MED_LOCFORMAT_COORD);
+}
+
+static int
+cmd_medlocation_address(struct lldpctl_conn_t *conn, struct writer *w,
+ struct cmd_env *env, const void *arg)
+{
+ log_debug("lldpctl", "set MED location address");
+ return _cmd_medlocation(conn, env, LLDP_MED_LOCFORMAT_CIVIC);
+}
+
+static int
+cmd_medlocation_elin(struct lldpctl_conn_t *conn, struct writer *w, struct cmd_env *env,
+ const void *arg)
+{
+ log_debug("lldpctl", "set MED location ELIN");
+ return _cmd_medlocation(conn, env, LLDP_MED_LOCFORMAT_ELIN);
+}
+
+static int
+cmd_medpolicy(struct lldpctl_conn_t *conn, struct writer *w, struct cmd_env *env,
+ const void *arg)
+{
+ log_debug("lldpctl", "set MED policy");
+ lldpctl_atom_t *iface;
+ while ((iface = cmd_iterate_on_interfaces(conn, env))) {
+ const char *name =
+ lldpctl_atom_get_str(iface, lldpctl_k_interface_name);
+ lldpctl_atom_t *port = lldpctl_get_port(iface);
+ lldpctl_atom_t *med_policy = NULL, *med_policies = NULL;
+ const char *what = NULL;
+
+ med_policies = lldpctl_atom_get(port, lldpctl_k_port_med_policies);
+ if (med_policies == NULL) {
+ log_warnx("lldpctl",
+ "unable to set LLDP-MED policies: support seems unavailable");
+ goto end;
+ }
+
+ med_policy = lldpctl_atom_iter_value(med_policies,
+ lldpctl_atom_iter_next(med_policies,
+ lldpctl_atom_iter(med_policies)));
+
+ if ((what = "application",
+ lldpctl_atom_set_str(med_policy, lldpctl_k_med_policy_type,
+ cmdenv_get(env, "application"))) == NULL ||
+ (what = "unknown flag",
+ lldpctl_atom_set_int(med_policy, lldpctl_k_med_policy_unknown,
+ cmdenv_get(env, "unknown") ? 1 : 0)) == NULL ||
+ (what = "tagged flag",
+ lldpctl_atom_set_int(med_policy, lldpctl_k_med_policy_tagged,
+ cmdenv_get(env, "tagged") ? 1 : 0)) == NULL ||
+ (what = "vlan",
+ cmdenv_get(env, "vlan") ?
+ lldpctl_atom_set_str(med_policy, lldpctl_k_med_policy_vid,
+ cmdenv_get(env, "vlan")) :
+ lldpctl_atom_set_int(med_policy, lldpctl_k_med_policy_vid,
+ 0)) == NULL ||
+ (what = "priority",
+ cmdenv_get(env, "priority") ?
+ lldpctl_atom_set_str(med_policy,
+ lldpctl_k_med_policy_priority,
+ cmdenv_get(env, "priority")) :
+ lldpctl_atom_set_int(med_policy,
+ lldpctl_k_med_policy_priority, 0)) == NULL ||
+ (what = "dscp",
+ cmdenv_get(env, "dscp") ?
+ lldpctl_atom_set_str(med_policy, lldpctl_k_med_policy_dscp,
+ cmdenv_get(env, "dscp")) :
+ lldpctl_atom_set_int(med_policy, lldpctl_k_med_policy_dscp,
+ 0)) == NULL)
+ log_warnx("lldpctl",
+ "unable to set LLDP MED policy value for %s on %s. %s.",
+ what, name, lldpctl_last_strerror(conn));
+ else {
+ if (lldpctl_atom_set(port, lldpctl_k_port_med_policies,
+ med_policy) == NULL) {
+ log_warnx("lldpctl",
+ "unable to set LLDP MED policy on %s. %s.", name,
+ lldpctl_last_strerror(conn));
+ } else
+ log_info("lldpctl",
+ "LLDP-MED policy has been set for port %s", name);
+ }
+
+ end:
+ lldpctl_atom_dec_ref(med_policy);
+ lldpctl_atom_dec_ref(med_policies);
+ lldpctl_atom_dec_ref(port);
+ }
+ return 1;
+}
+
+/**
+ * Register `configure med location coordinate` commands.
+ */
+static void
+register_commands_medloc_coord(struct cmd_node *configure_medlocation)
+{
+ /* MED location coordinate (set) */
+ struct cmd_node *configure_medloc_coord = commands_new(configure_medlocation,
+ "coordinate", "MED location coordinate configuration", NULL, NULL, NULL);
+ commands_new(configure_medloc_coord, NEWLINE,
+ "Configure MED location coordinates", cmd_check_env,
+ cmd_medlocation_coordinate,
+ "latitude,longitude,altitude,altitude-unit,datum");
+ commands_new(commands_new(configure_medloc_coord, "latitude",
+ "Specify latitude", cmd_check_no_env, NULL, "latitude"),
+ NULL, "Latitude as xx.yyyyN or xx.yyyyS", NULL,
+ cmd_store_env_value_and_pop2, "latitude");
+ commands_new(commands_new(configure_medloc_coord, "longitude",
+ "Specify longitude", cmd_check_no_env, NULL, "longitude"),
+ NULL, "Longitude as xx.yyyyE or xx.yyyyW", NULL,
+ cmd_store_env_value_and_pop2, "longitude");
+ struct cmd_node *altitude =
+ commands_new(commands_new(configure_medloc_coord, "altitude",
+ "Specify altitude", cmd_check_no_env, NULL, "altitude"),
+ NULL, "Altitude", NULL, cmd_store_env_value, "altitude");
+ commands_new(altitude, "m", "meters", NULL, cmd_store_env_value_and_pop3,
+ "altitude-unit");
+ commands_new(altitude, "f", "floors", NULL, cmd_store_env_value_and_pop3,
+ "altitude-unit");
+
+ struct cmd_node *datum = commands_new(configure_medloc_coord, "datum",
+ "Specify datum", cmd_check_no_env, NULL, "datum");
+ for (lldpctl_map_t *datum_map =
+ lldpctl_key_get_map(lldpctl_k_med_location_geoid);
+ datum_map->string; datum_map++)
+ commands_new(datum, datum_map->string, NULL, NULL,
+ cmd_store_env_value_and_pop2, "datum");
+}
+
+/**
+ * Register `configure med location address` commands.
+ */
+static void
+register_commands_medloc_addr(struct cmd_node *configure_medlocation)
+{
+ /* MED location address (set) */
+ struct cmd_node *configure_medloc_addr = commands_new(configure_medlocation,
+ "address", "MED location address configuration", NULL, NULL, NULL);
+ commands_new(configure_medloc_addr, NEWLINE, "Configure MED location address",
+ cmd_check_env, cmd_medlocation_address, "country");
+
+ /* Country */
+ commands_new(commands_new(configure_medloc_addr, "country",
+ "Specify country (mandatory)", cmd_check_no_env, NULL,
+ "country"),
+ NULL, "Country as a two-letter code", NULL, cmd_store_env_value_and_pop2,
+ "country");
+
+ /* Other fields */
+ for (lldpctl_map_t *addr_map =
+ lldpctl_key_get_map(lldpctl_k_med_civicaddress_type);
+ addr_map->string; addr_map++) {
+ const char *tag = strdup(totag(addr_map->string));
+ SUPPRESS_LEAK(tag);
+ commands_new(commands_new(configure_medloc_addr, tag, addr_map->string,
+ cmd_check_no_env, NULL, addr_map->string),
+ NULL, addr_map->string, NULL, cmd_store_env_value_and_pop2,
+ addr_map->string);
+ }
+}
+
+/**
+ * Register `configure med location elin` commands.
+ */
+static void
+register_commands_medloc_elin(struct cmd_node *configure_medlocation)
+{
+ /* MED location elin (set) */
+ commands_new(commands_new(commands_new(configure_medlocation, "elin",
+ "MED location ELIN configuration", NULL, NULL,
+ NULL),
+ NULL, "ELIN number", NULL, cmd_store_env_value, "elin"),
+ NEWLINE, "Set MED location ELIN number", NULL, cmd_medlocation_elin, NULL);
+}
+
+/**
+ * Register `configure med location` commands.
+ */
+static void
+register_commands_medloc(struct cmd_node *configure_med)
+{
+ struct cmd_node *configure_medlocation = commands_new(configure_med, "location",
+ "MED location configuration", NULL, NULL, NULL);
+
+ register_commands_medloc_coord(configure_medlocation);
+ register_commands_medloc_addr(configure_medlocation);
+ register_commands_medloc_elin(configure_medlocation);
+}
+
+static int
+cmd_check_application_but_no(struct cmd_env *env, const void *arg)
+{
+ const char *what = arg;
+ if (!cmdenv_get(env, "application")) return 0;
+ if (cmdenv_get(env, what)) return 0;
+ return 1;
+}
+static int
+cmd_store_app_env_value_and_pop2(struct lldpctl_conn_t *conn, struct writer *w,
+ struct cmd_env *env, const void *value)
+{
+ return cmd_store_something_env_value_and_pop2("application", env, value);
+}
+static int
+cmd_store_prio_env_value_and_pop2(struct lldpctl_conn_t *conn, struct writer *w,
+ struct cmd_env *env, const void *value)
+{
+ return cmd_store_something_env_value_and_pop2("priority", env, value);
+}
+
+/**
+ * Register `configure med policy` commands.
+ */
+static void
+register_commands_medpol(struct cmd_node *configure_med)
+{
+ struct cmd_node *configure_medpolicy = commands_new(configure_med, "policy",
+ "MED policy configuration", NULL, NULL, NULL);
+
+ commands_new(configure_medpolicy, NEWLINE, "Apply new MED policy",
+ cmd_check_env, cmd_medpolicy, "application");
+
+ /* Application */
+ struct cmd_node *configure_application =
+ commands_new(configure_medpolicy, "application", "MED policy application",
+ cmd_check_no_env, NULL, "application");
+
+ for (lldpctl_map_t *pol_map = lldpctl_key_get_map(lldpctl_k_med_policy_type);
+ pol_map->string; pol_map++) {
+ char *tag = strdup(totag(pol_map->string));
+ SUPPRESS_LEAK(tag);
+ commands_new(configure_application, tag, pol_map->string, NULL,
+ cmd_store_app_env_value_and_pop2, pol_map->string);
+ }
+
+ /* Remaining keywords */
+ commands_new(configure_medpolicy, "unknown", "Set unknown flag",
+ cmd_check_application_but_no, cmd_store_env_and_pop, "unknown");
+ commands_new(configure_medpolicy, "tagged", "Set tagged flag",
+ cmd_check_application_but_no, cmd_store_env_and_pop, "tagged");
+ commands_new(commands_new(configure_medpolicy, "vlan", "VLAN advertising",
+ cmd_check_application_but_no, NULL, "vlan"),
+ NULL, "VLAN ID to advertise", NULL, cmd_store_env_value_and_pop2, "vlan");
+ commands_new(commands_new(configure_medpolicy, "dscp", "DiffServ advertising",
+ cmd_check_application_but_no, NULL, "dscp"),
+ NULL, "DSCP value to advertise (between 0 and 63)", NULL,
+ cmd_store_env_value_and_pop2, "dscp");
+ struct cmd_node *priority = commands_new(configure_medpolicy, "priority",
+ "MED policy priority", cmd_check_application_but_no, NULL, "priority");
+ for (lldpctl_map_t *prio_map =
+ lldpctl_key_get_map(lldpctl_k_med_policy_priority);
+ prio_map->string; prio_map++) {
+ char *tag = strdup(totag(prio_map->string));
+ SUPPRESS_LEAK(tag);
+ commands_new(priority, tag, prio_map->string, NULL,
+ cmd_store_prio_env_value_and_pop2, prio_map->string);
+ }
+}
+
+static int
+cmd_faststart(struct lldpctl_conn_t *conn, struct writer *w, struct cmd_env *env,
+ const void *arg)
+{
+ log_debug("lldpctl", "configure fast interval support");
+
+ lldpctl_atom_t *config = lldpctl_get_configuration(conn);
+ if (config == NULL) {
+ log_warnx("lldpctl", "unable to get configuration from lldpd. %s",
+ lldpctl_last_strerror(conn));
+ return 0;
+ }
+
+ const char *action = arg;
+ if ((!strcmp(action, "enable") &&
+ (lldpctl_atom_set_int(config, lldpctl_k_config_fast_start_enabled, 1) ==
+ NULL)) ||
+ (!strcmp(action, "disable") &&
+ (lldpctl_atom_set_int(config, lldpctl_k_config_fast_start_enabled, 0) ==
+ NULL)) ||
+ (!strcmp(action, "delay") &&
+ (lldpctl_atom_set_str(config, lldpctl_k_config_fast_start_interval,
+ cmdenv_get(env, "tx-interval")) == NULL))) {
+ log_warnx("lldpctl", "unable to setup fast start. %s",
+ lldpctl_last_strerror(conn));
+ lldpctl_atom_dec_ref(config);
+ return 0;
+ }
+ log_info("lldpctl", "configruation for fast start applied");
+ lldpctl_atom_dec_ref(config);
+ return 1;
+}
+
+/**
+ * Register "configure med fast-start *"
+ */
+static void
+register_commands_medfast(struct cmd_node *med, struct cmd_node *nomed)
+{
+ struct cmd_node *configure_fast = commands_new(med, "fast-start",
+ "Fast start configuration", cmd_check_no_env, NULL, "ports");
+ struct cmd_node *unconfigure_fast = commands_new(nomed, "fast-start",
+ "Fast start configuration", cmd_check_no_env, NULL, "ports");
+
+ /* Enable */
+ commands_new(commands_new(configure_fast, "enable", "Enable fast start", NULL,
+ NULL, NULL),
+ NEWLINE, "Enable fast start", NULL, cmd_faststart, "enable");
+
+ /* Set TX delay */
+ commands_new(commands_new(commands_new(configure_fast, "tx-interval",
+ "Set LLDP fast transmit delay", NULL, NULL, NULL),
+ NULL, "LLDP fast transmit delay in seconds", NULL,
+ cmd_store_env_value, "tx-interval"),
+ NEWLINE, "Set LLDP fast transmit delay", NULL, cmd_faststart, "delay");
+
+ /* Disable */
+ commands_new(commands_new(unconfigure_fast, NEWLINE, "Disable fast start", NULL,
+ cmd_faststart, "disable"),
+ NEWLINE, "Disable fast start", NULL, cmd_faststart, "disable");
+}
+
+/**
+ * Register "configure med *"
+ */
+void
+register_commands_configure_med(struct cmd_node *configure,
+ struct cmd_node *unconfigure)
+{
+ if (lldpctl_key_get_map(lldpctl_k_med_policy_type)[0].string == NULL) return;
+
+ struct cmd_node *configure_med =
+ commands_new(configure, "med", "MED configuration", NULL, NULL, NULL);
+ struct cmd_node *unconfigure_med =
+ commands_new(unconfigure, "med", "MED configuration", NULL, NULL, NULL);
+
+ register_commands_medloc(configure_med);
+ register_commands_medpol(configure_med);
+ register_commands_medpow(configure_med);
+ register_commands_medfast(configure_med, unconfigure_med);
+}
diff --git a/src/client/conf-power.c b/src/client/conf-power.c
new file mode 100644
index 0000000..7c33540
--- /dev/null
+++ b/src/client/conf-power.c
@@ -0,0 +1,389 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/*
+ * Copyright (c) 2013 Vincent Bernat <bernat@luffy.cx>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <unistd.h>
+#include <string.h>
+
+#include "client.h"
+#include "../log.h"
+
+static int
+cmd_medpower(struct lldpctl_conn_t *conn, struct writer *w, struct cmd_env *env,
+ const void *arg)
+{
+ log_debug("lldpctl", "set MED power");
+ lldpctl_atom_t *port;
+ const char *name;
+ while ((port = cmd_iterate_on_ports(conn, env, &name))) {
+ lldpctl_atom_t *med_power;
+ const char *what = NULL;
+
+ med_power = lldpctl_atom_get(port, lldpctl_k_port_med_power);
+ if (med_power == NULL) {
+ log_warnx("lldpctl",
+ "unable to set LLDP-MED power: support seems unavailable");
+ continue; /* Need to finish the loop */
+ }
+
+ if ((what = "device type",
+ lldpctl_atom_set_str(med_power, lldpctl_k_med_power_type,
+ cmdenv_get(env, "device-type"))) == NULL ||
+ (what = "power source",
+ lldpctl_atom_set_str(med_power, lldpctl_k_med_power_source,
+ cmdenv_get(env, "source"))) == NULL ||
+ (what = "power priority",
+ lldpctl_atom_set_str(med_power, lldpctl_k_med_power_priority,
+ cmdenv_get(env, "priority"))) == NULL ||
+ (what = "power value",
+ lldpctl_atom_set_str(med_power, lldpctl_k_med_power_val,
+ cmdenv_get(env, "value"))) == NULL)
+ log_warnx("lldpctl",
+ "unable to set LLDP MED power value for %s on %s. %s.",
+ what, name, lldpctl_last_strerror(conn));
+ else {
+ if (lldpctl_atom_set(port, lldpctl_k_port_med_power,
+ med_power) == NULL) {
+ log_warnx("lldpctl",
+ "unable to set LLDP MED power on %s. %s.", name,
+ lldpctl_last_strerror(conn));
+ } else
+ log_info("lldpctl",
+ "LLDP-MED power has been set for port %s", name);
+ }
+
+ lldpctl_atom_dec_ref(med_power);
+ }
+ return 1;
+}
+
+static int
+cmd_store_powerpairs_env_value_and_pop2(struct lldpctl_conn_t *conn, struct writer *w,
+ struct cmd_env *env, const void *value)
+{
+ return cmd_store_something_env_value_and_pop2("powerpairs", env, value);
+}
+static int
+cmd_store_class_env_value_and_pop2(struct lldpctl_conn_t *conn, struct writer *w,
+ struct cmd_env *env, const void *value)
+{
+ return cmd_store_something_env_value_and_pop2("class", env, value);
+}
+static int
+cmd_store_prio_env_value_and_pop2(struct lldpctl_conn_t *conn, struct writer *w,
+ struct cmd_env *env, const void *value)
+{
+ return cmd_store_something_env_value_and_pop2("priority", env, value);
+}
+
+static int
+cmd_dot3power(struct lldpctl_conn_t *conn, struct writer *w, struct cmd_env *env,
+ const void *arg)
+{
+ log_debug("lldpctl", "set dot3 power");
+ lldpctl_atom_t *port;
+ const char *name;
+ while ((port = cmd_iterate_on_ports(conn, env, &name))) {
+ lldpctl_atom_t *dot3_power;
+ const char *what = NULL;
+ int ok = 1;
+
+ dot3_power = lldpctl_atom_get(port, lldpctl_k_port_dot3_power);
+ if (dot3_power == NULL) {
+ log_warnx("lldpctl",
+ "unable to set Dot3 power: support seems unavailable");
+ continue; /* Need to finish the loop */
+ }
+
+ if ((what = "device type",
+ lldpctl_atom_set_str(dot3_power,
+ lldpctl_k_dot3_power_devicetype,
+ cmdenv_get(env, "device-type"))) == NULL ||
+ /* Flags */
+ (what = "supported flag",
+ lldpctl_atom_set_int(dot3_power, lldpctl_k_dot3_power_supported,
+ cmdenv_get(env, "supported") ? 1 : 0)) == NULL ||
+ (what = "enabled flag",
+ lldpctl_atom_set_int(dot3_power, lldpctl_k_dot3_power_enabled,
+ cmdenv_get(env, "enabled") ? 1 : 0)) == NULL ||
+ (what = "paircontrol flag",
+ lldpctl_atom_set_int(dot3_power,
+ lldpctl_k_dot3_power_paircontrol,
+ cmdenv_get(env, "paircontrol") ? 1 : 0)) == NULL ||
+ /* Powerpairs */
+ (what = "power pairs",
+ lldpctl_atom_set_str(dot3_power, lldpctl_k_dot3_power_pairs,
+ cmdenv_get(env, "powerpairs"))) == NULL ||
+ /* Class */
+ (what = "power class",
+ cmdenv_get(env, "class") ?
+ lldpctl_atom_set_str(dot3_power, lldpctl_k_dot3_power_class,
+ cmdenv_get(env, "class")) :
+ lldpctl_atom_set_int(dot3_power, lldpctl_k_dot3_power_class,
+ 0)) == NULL ||
+ (what = "802.3at type",
+ lldpctl_atom_set_int(dot3_power, lldpctl_k_dot3_power_type,
+ 0)) == NULL) {
+ log_warnx("lldpctl",
+ "unable to set LLDP Dot3 power value for %s on %s. %s.",
+ what, name, lldpctl_last_strerror(conn));
+ ok = 0;
+ } else if (cmdenv_get(env, "typeat")) {
+ int typeat = cmdenv_get(env, "typeat")[0] - '0';
+ const char *source = cmdenv_get(env, "source");
+ if ((what = "802.3at type",
+ lldpctl_atom_set_int(dot3_power,
+ lldpctl_k_dot3_power_type, typeat)) == NULL ||
+ (what = "source",
+ lldpctl_atom_set_int(dot3_power,
+ lldpctl_k_dot3_power_source,
+ (!strcmp(source, "primary")) ?
+ LLDP_DOT3_POWER_SOURCE_PRIMARY :
+ (!strcmp(source, "backup")) ?
+ LLDP_DOT3_POWER_SOURCE_BACKUP :
+ (!strcmp(source, "pse")) ?
+ LLDP_DOT3_POWER_SOURCE_PSE :
+ (!strcmp(source, "local")) ?
+ LLDP_DOT3_POWER_SOURCE_LOCAL :
+ (!strcmp(source, "both")) ?
+ LLDP_DOT3_POWER_SOURCE_BOTH :
+ LLDP_DOT3_POWER_SOURCE_UNKNOWN)) == NULL ||
+ (what = "priority",
+ lldpctl_atom_set_str(dot3_power,
+ lldpctl_k_dot3_power_priority,
+ cmdenv_get(env, "priority"))) == NULL ||
+ (what = "requested power",
+ lldpctl_atom_set_str(dot3_power,
+ lldpctl_k_dot3_power_requested,
+ cmdenv_get(env, "requested"))) == NULL ||
+ (what = "allocated power",
+ lldpctl_atom_set_str(dot3_power,
+ lldpctl_k_dot3_power_allocated,
+ cmdenv_get(env, "allocated"))) == NULL) {
+ log_warnx("lldpctl",
+ "unable to set LLDP Dot3 power value for %s on %s. %s.",
+ what, name, lldpctl_last_strerror(conn));
+ ok = 0;
+ }
+ }
+ if (ok) {
+ if (lldpctl_atom_set(port, lldpctl_k_port_dot3_power,
+ dot3_power) == NULL) {
+ log_warnx("lldpctl",
+ "unable to set LLDP Dot3 power on %s. %s.", name,
+ lldpctl_last_strerror(conn));
+ } else
+ log_info("lldpctl",
+ "LLDP Dot3 power has been set for port %s", name);
+ }
+
+ lldpctl_atom_dec_ref(dot3_power);
+ }
+ return 1;
+}
+
+static int
+cmd_check_type_but_no(struct cmd_env *env, const void *arg)
+{
+ const char *what = arg;
+ if (!cmdenv_get(env, "device-type")) return 0;
+ if (cmdenv_get(env, what)) return 0;
+ return 1;
+}
+static int
+cmd_check_typeat_but_no(struct cmd_env *env, const void *arg)
+{
+ const char *what = arg;
+ if (!cmdenv_get(env, "typeat")) return 0;
+ if (cmdenv_get(env, what)) return 0;
+ return 1;
+}
+static int
+cmd_check_type(struct cmd_env *env, const char *type)
+{
+ const char *etype = cmdenv_get(env, "device-type");
+ if (!etype) return 0;
+ return (!strcmp(type, etype));
+}
+static int
+cmd_check_pse(struct cmd_env *env, const void *arg)
+{
+ return cmd_check_type(env, "pse");
+}
+static int
+cmd_check_pd(struct cmd_env *env, const void *arg)
+{
+ return cmd_check_type(env, "pd");
+}
+
+static void
+register_commands_pow_source(struct cmd_node *source)
+{
+ commands_new(source, "unknown", "Unknown power source", NULL,
+ cmd_store_env_value_and_pop2, "source");
+ commands_new(source, "primary", "Primary power source", cmd_check_pse,
+ cmd_store_env_value_and_pop2, "source");
+ commands_new(source, "backup", "Backup power source", cmd_check_pse,
+ cmd_store_env_value_and_pop2, "source");
+ commands_new(source, "pse", "Power source is PSE", cmd_check_pd,
+ cmd_store_env_value_and_pop2, "source");
+ commands_new(source, "local", "Local power source", cmd_check_pd,
+ cmd_store_env_value_and_pop2, "source");
+ commands_new(source, "both", "Both PSE and local source available",
+ cmd_check_pd, cmd_store_env_value_and_pop2, "source");
+}
+
+static void
+register_commands_pow_priority(struct cmd_node *priority, int key)
+{
+ for (lldpctl_map_t *prio_map = lldpctl_key_get_map(key); prio_map->string;
+ prio_map++) {
+ char *tag = strdup(totag(prio_map->string));
+ SUPPRESS_LEAK(tag);
+ commands_new(priority, tag, prio_map->string, NULL,
+ cmd_store_prio_env_value_and_pop2, prio_map->string);
+ }
+}
+
+/**
+ * Register `configure med power` commands.
+ */
+void
+register_commands_medpow(struct cmd_node *configure_med)
+{
+ struct cmd_node *configure_medpower = commands_new(configure_med, "power",
+ "MED power configuration", NULL, NULL, NULL);
+
+ commands_new(configure_medpower, NEWLINE, "Apply new MED power configuration",
+ cmd_check_env, cmd_medpower, "device-type,source,priority,value");
+
+ /* Type: PSE or PD */
+ commands_new(configure_medpower, "pd", "MED power consumer", cmd_check_no_env,
+ cmd_store_env_value_and_pop, "device-type");
+ commands_new(configure_medpower, "pse", "MED power provider", cmd_check_no_env,
+ cmd_store_env_value_and_pop, "device-type");
+
+ /* Source */
+ struct cmd_node *source = commands_new(configure_medpower, "source",
+ "MED power source", cmd_check_type_but_no, NULL, "source");
+ register_commands_pow_source(source);
+
+ /* Priority */
+ struct cmd_node *priority = commands_new(configure_medpower, "priority",
+ "MED power priority", cmd_check_type_but_no, NULL, "priority");
+ register_commands_pow_priority(priority, lldpctl_k_med_power_priority);
+
+ /* Value */
+ commands_new(commands_new(configure_medpower, "value", "MED power value",
+ cmd_check_type_but_no, NULL, "value"),
+ NULL, "MED power value in milliwatts", NULL, cmd_store_env_value_and_pop2,
+ "value");
+}
+
+static int
+cmd_check_env_power(struct cmd_env *env, const void *nothing)
+{
+ /* We need type and powerpair but if we have typeat, we also request
+ * source, priority, requested and allocated. */
+ if (!cmdenv_get(env, "device-type")) return 0;
+ if (!cmdenv_get(env, "powerpairs")) return 0;
+ if (cmdenv_get(env, "typeat")) {
+ return (!!cmdenv_get(env, "source") && !!cmdenv_get(env, "priority") &&
+ !!cmdenv_get(env, "requested") && !!cmdenv_get(env, "allocated"));
+ }
+ return 1;
+}
+
+/**
+ * Register `configure med dot3` commands.
+ */
+void
+register_commands_dot3pow(struct cmd_node *configure_dot3)
+{
+ struct cmd_node *configure_dot3power = commands_new(configure_dot3, "power",
+ "Dot3 power configuration", NULL, NULL, NULL);
+
+ commands_new(configure_dot3power, NEWLINE, "Apply new Dot3 power configuration",
+ cmd_check_env_power, cmd_dot3power, NULL);
+
+ /* Type: PSE or PD */
+ commands_new(configure_dot3power, "pd", "Dot3 power consumer", cmd_check_no_env,
+ cmd_store_env_value_and_pop, "device-type");
+ commands_new(configure_dot3power, "pse", "Dot3 power provider",
+ cmd_check_no_env, cmd_store_env_value_and_pop, "device-type");
+
+ /* Flags */
+ commands_new(configure_dot3power, "supported", "MDI power support present",
+ cmd_check_type_but_no, cmd_store_env_and_pop, "supported");
+ commands_new(configure_dot3power, "enabled", "MDI power support enabled",
+ cmd_check_type_but_no, cmd_store_env_and_pop, "enabled");
+ commands_new(configure_dot3power, "paircontrol",
+ "MDI power pair can be selected", cmd_check_type_but_no,
+ cmd_store_env_and_pop, "paircontrol");
+
+ /* Power pairs */
+ struct cmd_node *powerpairs = commands_new(configure_dot3power, "powerpairs",
+ "Which pairs are currently used for power (mandatory)",
+ cmd_check_type_but_no, NULL, "powerpairs");
+ for (lldpctl_map_t *pp_map = lldpctl_key_get_map(lldpctl_k_dot3_power_pairs);
+ pp_map->string; pp_map++) {
+ commands_new(powerpairs, pp_map->string, pp_map->string, NULL,
+ cmd_store_powerpairs_env_value_and_pop2, pp_map->string);
+ }
+
+ /* Class */
+ struct cmd_node *class = commands_new(configure_dot3power, "class",
+ "Power class", cmd_check_type_but_no, NULL, "class");
+ for (lldpctl_map_t *class_map = lldpctl_key_get_map(lldpctl_k_dot3_power_class);
+ class_map->string; class_map++) {
+ const char *tag = strdup(totag(class_map->string));
+ SUPPRESS_LEAK(tag);
+ commands_new(class, tag, class_map->string, NULL,
+ cmd_store_class_env_value_and_pop2, class_map->string);
+ }
+
+ /* 802.3at type */
+ struct cmd_node *typeat = commands_new(configure_dot3power, "type",
+ "802.3at device type", cmd_check_type_but_no, NULL, "typeat");
+ commands_new(typeat, "1", "802.3at type 1", NULL, cmd_store_env_value_and_pop2,
+ "typeat");
+ commands_new(typeat, "2", "802.3at type 2", NULL, cmd_store_env_value_and_pop2,
+ "typeat");
+
+ /* Source */
+ struct cmd_node *source = commands_new(configure_dot3power, "source",
+ "802.3at dot3 power source (mandatory)", cmd_check_typeat_but_no, NULL,
+ "source");
+ register_commands_pow_source(source);
+
+ /* Priority */
+ struct cmd_node *priority = commands_new(configure_dot3power, "priority",
+ "802.3at dot3 power priority (mandatory)", cmd_check_typeat_but_no, NULL,
+ "priority");
+ register_commands_pow_priority(priority, lldpctl_k_dot3_power_priority);
+
+ /* Values */
+ commands_new(commands_new(configure_dot3power, "requested",
+ "802.3at dot3 power value requested (mandatory)",
+ cmd_check_typeat_but_no, NULL, "requested"),
+ NULL, "802.3at power value requested in milliwatts", NULL,
+ cmd_store_env_value_and_pop2, "requested");
+ commands_new(commands_new(configure_dot3power, "allocated",
+ "802.3at dot3 power value allocated (mandatory)",
+ cmd_check_typeat_but_no, NULL, "allocated"),
+ NULL, "802.3at power value allocated in milliwatts", NULL,
+ cmd_store_env_value_and_pop2, "allocated");
+}
diff --git a/src/client/conf-system.c b/src/client/conf-system.c
new file mode 100644
index 0000000..0700ec2
--- /dev/null
+++ b/src/client/conf-system.c
@@ -0,0 +1,602 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/*
+ * Copyright (c) 2013 Vincent Bernat <bernat@luffy.cx>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <unistd.h>
+#include <string.h>
+#include <sys/utsname.h>
+
+#include "client.h"
+#include "../log.h"
+
+static int
+cmd_iface_pattern(struct lldpctl_conn_t *conn, struct writer *w, struct cmd_env *env,
+ const void *arg)
+{
+ log_debug("lldpctl", "set iface pattern");
+
+ lldpctl_atom_t *config = lldpctl_get_configuration(conn);
+ if (config == NULL) {
+ log_warnx("lldpctl", "unable to get configuration from lldpd. %s",
+ lldpctl_last_strerror(conn));
+ return 0;
+ }
+
+ const char *value = cmdenv_get(env, "iface-pattern");
+ if (lldpctl_atom_set_str(config, lldpctl_k_config_iface_pattern, value) ==
+ NULL) {
+ log_warnx("lldpctl", "unable to set iface-pattern. %s",
+ lldpctl_last_strerror(conn));
+ lldpctl_atom_dec_ref(config);
+ return 0;
+ }
+ log_info("lldpctl", "iface-pattern set to new value %s",
+ value ? value : "(none)");
+ lldpctl_atom_dec_ref(config);
+ return 1;
+}
+
+static int
+cmd_perm_iface_pattern(struct lldpctl_conn_t *conn, struct writer *w,
+ struct cmd_env *env, const void *arg)
+{
+ log_debug("lldpctl", "set permanent iface pattern");
+
+ lldpctl_atom_t *config = lldpctl_get_configuration(conn);
+ if (config == NULL) {
+ log_warnx("lldpctl", "unable to get configuration from lldpd. %s",
+ lldpctl_last_strerror(conn));
+ return 0;
+ }
+
+ const char *value = cmdenv_get(env, "iface-pattern");
+ if (lldpctl_atom_set_str(config, lldpctl_k_config_perm_iface_pattern, value) ==
+ NULL) {
+ log_warnx("lldpctl", "unable to set permanent iface pattern. %s",
+ lldpctl_last_strerror(conn));
+ lldpctl_atom_dec_ref(config);
+ return 0;
+ }
+ log_info("lldpctl", "permanent iface pattern set to new value %s",
+ value ? value : "(none)");
+ lldpctl_atom_dec_ref(config);
+ return 1;
+}
+
+static int
+cmd_iface_promisc(struct lldpctl_conn_t *conn, struct writer *w, struct cmd_env *env,
+ const void *arg)
+{
+ lldpctl_atom_t *config = lldpctl_get_configuration(conn);
+ if (config == NULL) {
+ log_warnx("lldpctl", "unable to get configuration from lldpd. %s",
+ lldpctl_last_strerror(conn));
+ return 0;
+ }
+ if (lldpctl_atom_set_int(config, lldpctl_k_config_iface_promisc, arg ? 1 : 0) ==
+ NULL) {
+ log_warnx("lldpctl", "unable to %s promiscuous mode: %s",
+ arg ? "enable" : "disable", lldpctl_last_strerror(conn));
+ lldpctl_atom_dec_ref(config);
+ return 0;
+ }
+ log_info("lldpctl", "interface promiscuous mode %s",
+ arg ? "enabled" : "disabled");
+ lldpctl_atom_dec_ref(config);
+ return 1;
+}
+
+static int
+cmd_system_description(struct lldpctl_conn_t *conn, struct writer *w,
+ struct cmd_env *env, const void *arg)
+{
+ int platform = 0;
+ const char *what = arg;
+ const char *value;
+ if (!strcmp(what, "system")) {
+ value = cmdenv_get(env, "description");
+ } else {
+ value = cmdenv_get(env, "platform");
+ platform = 1;
+ }
+ log_debug("lldpctl", "set %s description", what);
+ lldpctl_atom_t *config = lldpctl_get_configuration(conn);
+ if (config == NULL) {
+ log_warnx("lldpctl", "unable to get configuration from lldpd. %s",
+ lldpctl_last_strerror(conn));
+ return 0;
+ }
+ if (lldpctl_atom_set_str(config,
+ platform ? lldpctl_k_config_platform : lldpctl_k_config_description,
+ value) == NULL) {
+ log_warnx("lldpctl", "unable to set description. %s",
+ lldpctl_last_strerror(conn));
+ lldpctl_atom_dec_ref(config);
+ return 0;
+ }
+ log_info("lldpctl", "description set to new value %s",
+ value ? value : "(none)");
+ lldpctl_atom_dec_ref(config);
+ return 1;
+}
+
+static int
+cmd_system_chassisid(struct lldpctl_conn_t *conn, struct writer *w, struct cmd_env *env,
+ const void *arg)
+{
+ const char *value;
+ value = cmdenv_get(env, "description");
+ log_debug("lldpctl", "set chassis ID");
+ lldpctl_atom_t *config = lldpctl_get_configuration(conn);
+ if (config == NULL) {
+ log_warnx("lldpctl", "unable to get configuration from lldpd. %s",
+ lldpctl_last_strerror(conn));
+ return 0;
+ }
+ if (lldpctl_atom_set_str(config, lldpctl_k_config_cid_string, value) == NULL) {
+ log_warnx("lldpctl", "unable to set chassis ID. %s",
+ lldpctl_last_strerror(conn));
+ lldpctl_atom_dec_ref(config);
+ return 0;
+ }
+ log_info("lldpctl", "chassis ID set to new value %s", value ? value : "(none)");
+ lldpctl_atom_dec_ref(config);
+ return 1;
+}
+
+static int
+cmd_management(struct lldpctl_conn_t *conn, struct writer *w, struct cmd_env *env,
+ const void *arg)
+{
+ log_debug("lldpctl", "set management pattern");
+
+ lldpctl_atom_t *config = lldpctl_get_configuration(conn);
+ if (config == NULL) {
+ log_warnx("lldpctl", "unable to get configuration from lldpd. %s",
+ lldpctl_last_strerror(conn));
+ return 0;
+ }
+
+ const char *value = cmdenv_get(env, "management-pattern");
+ if (lldpctl_atom_set_str(config, lldpctl_k_config_mgmt_pattern, value) ==
+ NULL) {
+ log_warnx("lldpctl", "unable to set management pattern. %s",
+ lldpctl_last_strerror(conn));
+ lldpctl_atom_dec_ref(config);
+ return 0;
+ }
+ log_info("lldpctl", "management pattern set to new value %s",
+ value ? value : "(none)");
+ lldpctl_atom_dec_ref(config);
+ return 1;
+}
+
+static int
+cmd_hostname(struct lldpctl_conn_t *conn, struct writer *w, struct cmd_env *env,
+ const void *arg)
+{
+ struct utsname un;
+ log_debug("lldpctl", "set system name");
+
+ lldpctl_atom_t *config = lldpctl_get_configuration(conn);
+ if (config == NULL) {
+ log_warnx("lldpctl", "unable to get configuration from lldpd. %s",
+ lldpctl_last_strerror(conn));
+ return 0;
+ }
+
+ const char *value = cmdenv_get(env, "hostname");
+ if (value && strlen(value) == 1 && value[0] == '.') {
+ if (uname(&un) < 0) {
+ log_warn("lldpctl", "cannot get node name");
+ lldpctl_atom_dec_ref(config);
+ return 0;
+ }
+ char *c = strchr(un.nodename, '.');
+ if (c) *c = 0;
+ value = un.nodename;
+ }
+ if (lldpctl_atom_set_str(config, lldpctl_k_config_hostname, value) == NULL) {
+ log_warnx("lldpctl", "unable to set system name. %s",
+ lldpctl_last_strerror(conn));
+ lldpctl_atom_dec_ref(config);
+ return 0;
+ }
+ log_info("lldpctl", "system name set to new value %s",
+ value ? value : "(none)");
+ lldpctl_atom_dec_ref(config);
+ return 1;
+}
+
+static int
+cmd_capability(struct lldpctl_conn_t *conn, struct writer *w, struct cmd_env *env,
+ const void *arg)
+{
+ log_debug("lldpctl", "set capabilities");
+
+ int ret = 0;
+ lldpctl_atom_t *config = lldpctl_get_configuration(conn);
+ lldpctl_atom_t *chassis = NULL;
+ if (config == NULL) {
+ log_warnx("lldpctl", "unable to get configuration from lldpd. %s",
+ lldpctl_last_strerror(conn));
+ goto cmd_capability_end;
+ }
+
+ if (!strcmp(arg, "configure")) {
+
+ const char *s = cmdenv_get(env, "capabilities");
+ if (s) {
+ chassis = lldpctl_get_local_chassis(conn);
+ if (chassis == NULL) {
+ log_warnx("lldpctl",
+ "unable to get local chassis from lldpd. %s",
+ lldpctl_last_strerror(conn));
+ goto cmd_capability_end;
+ }
+ u_int16_t value = 0;
+ const char delim[] = ",";
+ char *s_copy = strdup(s);
+ char *token = strtok(s_copy, delim);
+ while (token != NULL) {
+ if (!strcmp(token, "other")) {
+ value |= LLDP_CAP_OTHER;
+ } else if (!strcmp(token, "repeater")) {
+ value |= LLDP_CAP_REPEATER;
+ } else if (!strcmp(token, "bridge")) {
+ value |= LLDP_CAP_BRIDGE;
+ } else if (!strcmp(token, "wlan")) {
+ value |= LLDP_CAP_WLAN;
+ } else if (!strcmp(token, "router")) {
+ value |= LLDP_CAP_ROUTER;
+ } else if (!strcmp(token, "telephone")) {
+ value |= LLDP_CAP_TELEPHONE;
+ } else if (!strcmp(token, "docsis")) {
+ value |= LLDP_CAP_DOCSIS;
+ } else if (!strcmp(token, "station")) {
+ value |= LLDP_CAP_STATION;
+ } else {
+ log_warnx("lldpctl", "capability %s not found",
+ token);
+ }
+ token = strtok(NULL, delim);
+ }
+ free(s_copy);
+
+ if (lldpctl_atom_set_int(chassis, lldpctl_k_chassis_cap_enabled,
+ value) == NULL) {
+ log_warnx("lldpctl",
+ "unable to set system capabilities. %s",
+ lldpctl_last_strerror(conn));
+ goto cmd_capability_end;
+ }
+ if (lldpctl_atom_set_int(config,
+ lldpctl_k_config_chassis_cap_override, 1) == NULL) {
+ log_warnx("lldpctl",
+ "unable to set system capabilities override. %s",
+ lldpctl_last_strerror(conn));
+ goto cmd_capability_end;
+ }
+ log_debug("lldpctl", "system capabilities set to new value %d",
+ value);
+ }
+ } else {
+ if (lldpctl_atom_set_int(config, lldpctl_k_config_chassis_cap_override,
+ 0) == NULL) {
+ log_warnx("lldpctl",
+ "unable to set system capabilities to not override. %s",
+ lldpctl_last_strerror(conn));
+ goto cmd_capability_end;
+ }
+ }
+
+ ret = 1;
+cmd_capability_end:
+ lldpctl_atom_dec_ref(chassis);
+ lldpctl_atom_dec_ref(config);
+ return ret;
+}
+
+static int
+cmd_update_descriptions(struct lldpctl_conn_t *conn, struct writer *w,
+ struct cmd_env *env, const void *arg)
+{
+ lldpctl_atom_t *config = lldpctl_get_configuration(conn);
+ if (config == NULL) {
+ log_warnx("lldpctl", "unable to get configuration from lldpd. %s",
+ lldpctl_last_strerror(conn));
+ return 0;
+ }
+ if (lldpctl_atom_set_int(config, lldpctl_k_config_ifdescr_update,
+ arg ? 1 : 0) == NULL) {
+ log_warnx("lldpctl", "unable to %s interface description update: %s",
+ arg ? "enable" : "disable", lldpctl_last_strerror(conn));
+ lldpctl_atom_dec_ref(config);
+ return 0;
+ }
+ log_info("lldpctl", "interface description update %s",
+ arg ? "enabled" : "disabled");
+ lldpctl_atom_dec_ref(config);
+ return 1;
+}
+
+static int
+cmd_bondslave_srcmac_type(struct lldpctl_conn_t *conn, struct writer *w,
+ struct cmd_env *env, const void *arg)
+{
+ const char *value_str = 0;
+ int value = -1;
+
+ log_debug("lldpctl", "bond slave src mac");
+
+ lldpctl_atom_t *config = lldpctl_get_configuration(conn);
+ if (config == NULL) {
+ log_warnx("lldpctl", "unable to get configuration from lldpd. %s",
+ lldpctl_last_strerror(conn));
+ return 0;
+ }
+
+ value_str = arg;
+ for (lldpctl_map_t *b_map =
+ lldpctl_key_get_map(lldpctl_k_config_bond_slave_src_mac_type);
+ b_map->string; b_map++) {
+ if (!strcmp(b_map->string, value_str)) {
+ value = b_map->value;
+ break;
+ }
+ }
+
+ if (value == -1) {
+ log_warnx("lldpctl", "invalid value");
+ lldpctl_atom_dec_ref(config);
+ return 0;
+ }
+
+ if (lldpctl_atom_set_int(config, lldpctl_k_config_bond_slave_src_mac_type,
+ value) == NULL) {
+ log_warnx("lldpctl",
+ "unable to set bond slave src mac type."
+ " %s",
+ lldpctl_last_strerror(conn));
+ lldpctl_atom_dec_ref(config);
+ return 0;
+ }
+
+ log_info("lldpctl", "bond slave src mac set to new value: %s", value_str);
+ lldpctl_atom_dec_ref(config);
+
+ return 1;
+}
+
+static int
+cmd_maxneighs(struct lldpctl_conn_t *conn, struct writer *w, struct cmd_env *env,
+ const void *arg)
+{
+ log_debug("lldpctl", "set maximum neighbors");
+
+ lldpctl_atom_t *config = lldpctl_get_configuration(conn);
+ if (config == NULL) {
+ log_warnx("lldpctl", "unable to get configuration from lldpd. %s",
+ lldpctl_last_strerror(conn));
+ return 0;
+ }
+ if (lldpctl_atom_set_str(config, lldpctl_k_config_max_neighbors,
+ cmdenv_get(env, "max-neighbors")) == NULL) {
+ log_warnx("lldpctl", "unable to set maximum of neighbors. %s",
+ lldpctl_last_strerror(conn));
+ lldpctl_atom_dec_ref(config);
+ return 0;
+ }
+ log_info("lldpctl", "maximum neighbors set to new value %s",
+ cmdenv_get(env, "max-neighbors"));
+ lldpctl_atom_dec_ref(config);
+ return 1;
+}
+
+/**
+ * Register `configure system bond-slave-src-mac-type`
+ */
+static void
+register_commands_srcmac_type(struct cmd_node *configure)
+{
+ struct cmd_node *bond_slave_src_mac_type =
+ commands_new(configure, "bond-slave-src-mac-type",
+ "Set LLDP bond slave source MAC type", NULL, NULL, NULL);
+
+ for (lldpctl_map_t *b_map =
+ lldpctl_key_get_map(lldpctl_k_config_bond_slave_src_mac_type);
+ b_map->string; b_map++) {
+ if (!strcmp(b_map->string, "real")) {
+ commands_new(commands_new(bond_slave_src_mac_type,
+ b_map->string, "Real mac", NULL, NULL, NULL),
+ NEWLINE, NULL, NULL, cmd_bondslave_srcmac_type,
+ b_map->string);
+ } else if (!strcmp(b_map->string, "zero")) {
+ commands_new(commands_new(bond_slave_src_mac_type,
+ b_map->string, "All zero mac", NULL, NULL,
+ NULL),
+ NEWLINE, NULL, NULL, cmd_bondslave_srcmac_type,
+ b_map->string);
+ } else if (!strcmp(b_map->string, "fixed")) {
+ commands_new(commands_new(bond_slave_src_mac_type,
+ b_map->string, "Fixed value (3Com card)", NULL,
+ NULL, NULL),
+ NEWLINE, NULL, NULL, cmd_bondslave_srcmac_type,
+ b_map->string);
+ } else if (!strcmp(b_map->string, "local")) {
+ commands_new(commands_new(bond_slave_src_mac_type,
+ b_map->string,
+ "Real Mac with locally "
+ "administered bit set",
+ NULL, NULL, NULL),
+ NEWLINE, NULL, NULL, cmd_bondslave_srcmac_type,
+ b_map->string);
+ }
+ }
+}
+
+static void
+register_commands_capabilities(struct cmd_node *configure, struct cmd_node *unconfigure)
+{
+ struct cmd_node *configure_capability = commands_new(configure, "capabilities",
+ "Capabilities configuration", cmd_check_no_env, NULL, "ports");
+ struct cmd_node *unconfigure_capability =
+ commands_new(unconfigure, "capabilities", "Capabilities configuration",
+ cmd_check_no_env, NULL, "ports");
+
+ /* Override */
+ commands_new(commands_new(commands_new(configure_capability, "enabled",
+ "Override capabilities", NULL, NULL, NULL),
+ NULL, " Set of capabilities separated by commas", NULL,
+ cmd_store_env_value, "capabilities"),
+ NEWLINE, "Override capabilities", NULL, cmd_capability, "configure");
+
+ /* Do not override */
+ commands_new(commands_new(unconfigure_capability, "enabled",
+ "Do not override capabilities", NULL, NULL, NULL),
+ NEWLINE, "Do not override capabilities", NULL, cmd_capability,
+ "unconfigure");
+}
+
+/**
+ * Register `configure system` commands.
+ *
+ * Those are the commands to configure protocol-independant stuff.
+ */
+void
+register_commands_configure_system(struct cmd_node *configure,
+ struct cmd_node *unconfigure)
+{
+ struct cmd_node *configure_system = commands_new(configure, "system",
+ "System configuration", cmd_check_no_env, NULL, "ports");
+ struct cmd_node *unconfigure_system = commands_new(unconfigure, "system",
+ "System configuration", cmd_check_no_env, NULL, "ports");
+ struct cmd_node *configure_interface = commands_new(configure_system,
+ "interface", "Interface related items", NULL, NULL, NULL);
+ struct cmd_node *unconfigure_interface = commands_new(unconfigure_system,
+ "interface", "Interface related items", NULL, NULL, NULL);
+
+ commands_new(commands_new(commands_new(configure_system, "description",
+ "Override chassis description", NULL, NULL, NULL),
+ NULL, "Chassis description", NULL, cmd_store_env_value,
+ "description"),
+ NEWLINE, "Override chassis description", NULL, cmd_system_description,
+ "system");
+ commands_new(commands_new(unconfigure_system, "description",
+ "Don't override chassis description", NULL, NULL, NULL),
+ NEWLINE, "Don't override chassis description", NULL, cmd_system_description,
+ "system");
+
+ commands_new(commands_new(commands_new(configure_system, "chassisid",
+ "Override chassis ID", NULL, NULL, NULL),
+ NULL, "Chassis ID", NULL, cmd_store_env_value, "description"),
+ NEWLINE, "Override chassis ID", NULL, cmd_system_chassisid, "system");
+ commands_new(commands_new(unconfigure_system, "chassisid",
+ "Don't override chassis ID", NULL, NULL, NULL),
+ NEWLINE, "Don't override chassis ID", NULL, cmd_system_chassisid, "system");
+
+ commands_new(commands_new(commands_new(configure_system, "platform",
+ "Override platform description", NULL, NULL,
+ NULL),
+ NULL, "Platform description (CDP)", NULL, cmd_store_env_value,
+ "platform"),
+ NEWLINE, "Override platform description", NULL, cmd_system_description,
+ "platform");
+ commands_new(commands_new(unconfigure_system, "platform",
+ "Don't override platform description", NULL, NULL, NULL),
+ NEWLINE, "Don't override platform description", NULL,
+ cmd_system_description, "platform");
+
+ commands_new(commands_new(commands_new(configure_system, "hostname",
+ "Override system name", NULL, NULL, NULL),
+ NULL, "System name", NULL, cmd_store_env_value, "hostname"),
+ NEWLINE, "Override system name", NULL, cmd_hostname, NULL);
+ commands_new(commands_new(unconfigure_system, "hostname",
+ "Don't override system name", NULL, NULL, NULL),
+ NEWLINE, "Don't override system name", NULL, cmd_hostname, NULL);
+
+ commands_new(commands_new(commands_new(configure_system, "max-neighbors",
+ "Set maximum number of neighbors per port",
+ cmd_check_no_env, NULL, "ports"),
+ NULL, "Maximum number of neighbors", NULL, cmd_store_env_value,
+ "max-neighbors"),
+ NEWLINE, "Set maximum number of neighbors per port", NULL, cmd_maxneighs,
+ NULL);
+
+ commands_new(
+ commands_new(commands_new(commands_new(commands_new(configure_system, "ip",
+ "IP related options", NULL, NULL,
+ NULL),
+ "management", "IP management related options",
+ NULL, NULL, NULL),
+ "pattern", "Set IP management pattern", NULL, NULL, NULL),
+ NULL, "IP management pattern (comma-separated list of wildcards)", NULL,
+ cmd_store_env_value, "management-pattern"),
+ NEWLINE, "Set IP management pattern", NULL, cmd_management, NULL);
+ commands_new(
+ commands_new(commands_new(commands_new(unconfigure_system, "ip",
+ "IP related options", NULL, NULL, NULL),
+ "management", "IP management related options", NULL, NULL,
+ NULL),
+ "pattern", "Delete any IP management pattern", NULL, NULL, NULL),
+ NEWLINE, "Delete any IP management pattern", NULL, cmd_management, NULL);
+
+ commands_new(commands_new(commands_new(configure_interface, "pattern",
+ "Set active interface pattern", NULL, NULL, NULL),
+ NULL, "Interface pattern (comma-separated list of wildcards)",
+ NULL, cmd_store_env_value, "iface-pattern"),
+ NEWLINE, "Set active interface pattern", NULL, cmd_iface_pattern, NULL);
+ commands_new(commands_new(unconfigure_interface, "pattern",
+ "Delete any interface pattern", NULL, NULL, NULL),
+ NEWLINE, "Clear interface pattern", NULL, cmd_iface_pattern, NULL);
+
+ commands_new(
+ commands_new(commands_new(configure_interface, "permanent",
+ "Set permanent interface pattern", NULL, NULL, NULL),
+ NULL, "Permanent interface pattern (comma-separated list of wildcards)",
+ NULL, cmd_store_env_value, "iface-pattern"),
+ NEWLINE, "Set permanent interface pattern", NULL, cmd_perm_iface_pattern,
+ NULL);
+ commands_new(commands_new(unconfigure_interface, "permanent",
+ "Clear permanent interface pattern", NULL, NULL, NULL),
+ NEWLINE, "Delete any interface pattern", NULL, cmd_perm_iface_pattern,
+ NULL);
+
+ commands_new(commands_new(configure_interface, "description",
+ "Update interface descriptions with neighbor name", NULL, NULL,
+ NULL),
+ NEWLINE, "Update interface descriptions with neighbor name", NULL,
+ cmd_update_descriptions, "enable");
+ commands_new(commands_new(unconfigure_interface, "description",
+ "Don't update interface descriptions with neighbor name", NULL,
+ NULL, NULL),
+ NEWLINE, "Don't update interface descriptions with neighbor name", NULL,
+ cmd_update_descriptions, NULL);
+
+ commands_new(commands_new(configure_interface, "promiscuous",
+ "Enable promiscuous mode on managed interfaces", NULL, NULL,
+ NULL),
+ NEWLINE, "Enable promiscuous mode on managed interfaces", NULL,
+ cmd_iface_promisc, "enable");
+ commands_new(commands_new(unconfigure_interface, "promiscuous",
+ "Don't enable promiscuous mode on managed interfaces", NULL,
+ NULL, NULL),
+ NEWLINE, "Don't enable promiscuous mode on managed interfaces", NULL,
+ cmd_iface_promisc, NULL);
+
+ register_commands_capabilities(configure_system, unconfigure_system);
+ register_commands_srcmac_type(configure_system);
+}
diff --git a/src/client/conf.c b/src/client/conf.c
new file mode 100644
index 0000000..7bbb1f9
--- /dev/null
+++ b/src/client/conf.c
@@ -0,0 +1,44 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/*
+ * Copyright (c) 2013 Vincent Bernat <bernat@luffy.cx>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <unistd.h>
+#include <string.h>
+
+#include "client.h"
+#include "../log.h"
+
+/**
+ * Register `configure` and `no configure` commands.
+ */
+void
+register_commands_configure(struct cmd_node *root)
+{
+ struct cmd_node *configure =
+ commands_new(root, "configure", "Change system settings", NULL, NULL, NULL);
+ struct cmd_node *unconfigure = commands_new(root, "unconfigure",
+ "Unconfigure system settings", NULL, NULL, NULL);
+ commands_privileged(commands_lock(configure));
+ commands_privileged(commands_lock(unconfigure));
+ cmd_restrict_ports(configure);
+ cmd_restrict_ports(unconfigure);
+
+ register_commands_configure_system(configure, unconfigure);
+ register_commands_configure_lldp(configure, unconfigure);
+ register_commands_configure_med(configure, unconfigure);
+ register_commands_configure_inventory(configure, unconfigure);
+ register_commands_configure_dot3(configure);
+}
diff --git a/src/client/display.c b/src/client/display.c
new file mode 100644
index 0000000..6e3ec78
--- /dev/null
+++ b/src/client/display.c
@@ -0,0 +1,1039 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/*
+ * Copyright (c) 2008 Vincent Bernat <bernat@luffy.cx>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <time.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <arpa/inet.h>
+#include <string.h>
+
+#include "../log.h"
+#include "client.h"
+
+static void
+display_cap(struct writer *w, lldpctl_atom_t *chassis, u_int8_t bit, const char *symbol)
+{
+ if (lldpctl_atom_get_int(chassis, lldpctl_k_chassis_cap_available) & bit) {
+ tag_start(w, "capability", "Capability");
+ tag_attr(w, "type", "", symbol);
+ tag_attr(w, "enabled", "",
+ (lldpctl_atom_get_int(chassis, lldpctl_k_chassis_cap_enabled) &
+ bit) ?
+ "on" :
+ "off");
+ tag_end(w);
+ }
+}
+
+static void
+display_med_capability(struct writer *w, long int available, int cap,
+ const char *symbol)
+{
+ if (available & cap) {
+ tag_start(w, "capability", "Capability");
+ tag_attr(w, "type", "", symbol);
+ tag_attr(w, "available", "", "yes");
+ tag_end(w);
+ }
+}
+
+static void
+display_med(struct writer *w, lldpctl_atom_t *port, lldpctl_atom_t *chassis)
+{
+ lldpctl_atom_t *medpolicies, *medpolicy;
+ lldpctl_atom_t *medlocations, *medlocation;
+ lldpctl_atom_t *caelements, *caelement;
+ lldpctl_atom_t *medpower;
+ long int cap = lldpctl_atom_get_int(chassis, lldpctl_k_chassis_med_cap);
+ const char *type;
+
+ if (lldpctl_atom_get_int(chassis, lldpctl_k_chassis_med_type) <= 0) return;
+
+ tag_start(w, "lldp-med", "LLDP-MED");
+
+ tag_datatag(w, "device-type", "Device Type",
+ lldpctl_atom_get_str(chassis, lldpctl_k_chassis_med_type));
+
+ display_med_capability(w, cap, LLDP_MED_CAP_CAP, "Capabilities");
+ display_med_capability(w, cap, LLDP_MED_CAP_POLICY, "Policy");
+ display_med_capability(w, cap, LLDP_MED_CAP_LOCATION, "Location");
+ display_med_capability(w, cap, LLDP_MED_CAP_MDI_PSE, "MDI/PSE");
+ display_med_capability(w, cap, LLDP_MED_CAP_MDI_PD, "MDI/PD");
+ display_med_capability(w, cap, LLDP_MED_CAP_IV, "Inventory");
+
+ /* LLDP MED policies */
+ medpolicies = lldpctl_atom_get(port, lldpctl_k_port_med_policies);
+ lldpctl_atom_foreach(medpolicies, medpolicy)
+ {
+ if (lldpctl_atom_get_int(medpolicy, lldpctl_k_med_policy_type) <= 0)
+ continue;
+
+ tag_start(w, "policy", "LLDP-MED Network Policy for");
+ tag_attr(w, "apptype", "",
+ lldpctl_atom_get_str(medpolicy, lldpctl_k_med_policy_type));
+ tag_attr(w, "defined", "Defined",
+ (lldpctl_atom_get_int(medpolicy, lldpctl_k_med_policy_unknown) >
+ 0) ?
+ "no" :
+ "yes");
+
+ if (lldpctl_atom_get_int(medpolicy, lldpctl_k_med_policy_tagged) > 0) {
+ int vid =
+ lldpctl_atom_get_int(medpolicy, lldpctl_k_med_policy_vid);
+ tag_start(w, "vlan", "VLAN");
+ if (vid == 0) {
+ tag_attr(w, "vid", "", "priority");
+ } else if (vid == 4095) {
+ tag_attr(w, "vid", "", "reserved");
+ } else {
+ tag_attr(w, "vid", "",
+ lldpctl_atom_get_str(medpolicy,
+ lldpctl_k_med_policy_vid));
+ }
+ tag_end(w);
+ }
+
+ tag_datatag(w, "priority", "Priority",
+ lldpctl_atom_get_str(medpolicy, lldpctl_k_med_policy_priority));
+ /* Also give a numeric value */
+ int pcp =
+ lldpctl_atom_get_int(medpolicy, lldpctl_k_med_policy_priority);
+ char spcp[2] = { pcp + '0', '\0' };
+ tag_datatag(w, "pcp", "PCP", spcp);
+ tag_datatag(w, "dscp", "DSCP Value",
+ lldpctl_atom_get_str(medpolicy, lldpctl_k_med_policy_dscp));
+
+ tag_end(w);
+ }
+ lldpctl_atom_dec_ref(medpolicies);
+
+ /* LLDP MED locations */
+ medlocations = lldpctl_atom_get(port, lldpctl_k_port_med_locations);
+ lldpctl_atom_foreach(medlocations, medlocation)
+ {
+ int format =
+ lldpctl_atom_get_int(medlocation, lldpctl_k_med_location_format);
+ if (format <= 0) continue;
+ tag_start(w, "location", "LLDP-MED Location Identification");
+ tag_attr(w, "type", "Type",
+ lldpctl_atom_get_str(medlocation, lldpctl_k_med_location_format));
+
+ switch (format) {
+ case LLDP_MED_LOCFORMAT_COORD:
+ tag_attr(w, "geoid", "Geoid",
+ lldpctl_atom_get_str(medlocation,
+ lldpctl_k_med_location_geoid));
+ tag_datatag(w, "lat", "Latitude",
+ lldpctl_atom_get_str(medlocation,
+ lldpctl_k_med_location_latitude));
+ tag_datatag(w, "lon", "Longitude",
+ lldpctl_atom_get_str(medlocation,
+ lldpctl_k_med_location_longitude));
+ tag_start(w, "altitude", "Altitude");
+ tag_attr(w, "unit", "",
+ lldpctl_atom_get_str(medlocation,
+ lldpctl_k_med_location_altitude_unit));
+ tag_data(w,
+ lldpctl_atom_get_str(medlocation,
+ lldpctl_k_med_location_altitude));
+ tag_end(w);
+ break;
+ case LLDP_MED_LOCFORMAT_CIVIC:
+ tag_datatag(w, "country", "Country",
+ lldpctl_atom_get_str(medlocation,
+ lldpctl_k_med_location_country));
+ caelements = lldpctl_atom_get(medlocation,
+ lldpctl_k_med_location_ca_elements);
+ lldpctl_atom_foreach(caelements, caelement)
+ {
+ type = lldpctl_atom_get_str(caelement,
+ lldpctl_k_med_civicaddress_type);
+ tag_datatag(w, totag(type), type,
+ lldpctl_atom_get_str(caelement,
+ lldpctl_k_med_civicaddress_value));
+ }
+ lldpctl_atom_dec_ref(caelements);
+ break;
+ case LLDP_MED_LOCFORMAT_ELIN:
+ tag_datatag(w, "ecs", "ECS ELIN",
+ lldpctl_atom_get_str(medlocation,
+ lldpctl_k_med_location_elin));
+ break;
+ }
+
+ tag_end(w);
+ }
+ lldpctl_atom_dec_ref(medlocations);
+
+ /* LLDP MED power */
+ medpower = lldpctl_atom_get(port, lldpctl_k_port_med_power);
+ if (lldpctl_atom_get_int(medpower, lldpctl_k_med_power_type) > 0) {
+ tag_start(w, "poe", "Extended Power-over-Ethernet");
+
+ tag_datatag(w, "device-type", "Power Type & Source",
+ lldpctl_atom_get_str(medpower, lldpctl_k_med_power_type));
+ tag_datatag(w, "source", "Power Source",
+ lldpctl_atom_get_str(medpower, lldpctl_k_med_power_source));
+ tag_datatag(w, "priority", "Power priority",
+ lldpctl_atom_get_str(medpower, lldpctl_k_med_power_priority));
+ tag_datatag(w, "power", "Power Value",
+ lldpctl_atom_get_str(medpower, lldpctl_k_med_power_val));
+
+ tag_end(w);
+ }
+ lldpctl_atom_dec_ref(medpower);
+
+ /* LLDP MED inventory */
+ do {
+ const char *hw =
+ lldpctl_atom_get_str(chassis, lldpctl_k_chassis_med_inventory_hw);
+ const char *sw =
+ lldpctl_atom_get_str(chassis, lldpctl_k_chassis_med_inventory_sw);
+ const char *fw =
+ lldpctl_atom_get_str(chassis, lldpctl_k_chassis_med_inventory_fw);
+ const char *sn =
+ lldpctl_atom_get_str(chassis, lldpctl_k_chassis_med_inventory_sn);
+ const char *manuf = lldpctl_atom_get_str(chassis,
+ lldpctl_k_chassis_med_inventory_manuf);
+ const char *model = lldpctl_atom_get_str(chassis,
+ lldpctl_k_chassis_med_inventory_model);
+ const char *asset = lldpctl_atom_get_str(chassis,
+ lldpctl_k_chassis_med_inventory_asset);
+ if (!(hw || sw || fw || sn || manuf || model || asset)) break;
+
+ tag_start(w, "inventory", "Inventory");
+ tag_datatag(w, "hardware", "Hardware Revision", hw);
+ tag_datatag(w, "software", "Software Revision", sw);
+ tag_datatag(w, "firmware", "Firmware Revision", fw);
+ tag_datatag(w, "serial", "Serial Number", sn);
+ tag_datatag(w, "manufacturer", "Manufacturer", manuf);
+ tag_datatag(w, "model", "Model", model);
+ tag_datatag(w, "asset", "Asset ID", asset);
+ tag_end(w);
+ } while (0);
+
+ tag_end(w);
+}
+
+static void
+display_chassis(struct writer *w, lldpctl_atom_t *chassis, int details)
+{
+ lldpctl_atom_t *mgmts, *mgmt;
+
+ tag_start(w, "chassis", "Chassis");
+ tag_start(w, "id", "ChassisID");
+ tag_attr(w, "type", "",
+ lldpctl_atom_get_str(chassis, lldpctl_k_chassis_id_subtype));
+ tag_data(w, lldpctl_atom_get_str(chassis, lldpctl_k_chassis_id));
+ tag_end(w);
+ tag_datatag(w, "name", "SysName",
+ lldpctl_atom_get_str(chassis, lldpctl_k_chassis_name));
+ if (details == DISPLAY_BRIEF) {
+ tag_end(w);
+ return;
+ }
+ tag_datatag(w, "descr", "SysDescr",
+ lldpctl_atom_get_str(chassis, lldpctl_k_chassis_descr));
+
+ /* Management addresses */
+ mgmts = lldpctl_atom_get(chassis, lldpctl_k_chassis_mgmt);
+ lldpctl_atom_foreach(mgmts, mgmt)
+ {
+ tag_datatag(w, "mgmt-ip", "MgmtIP",
+ lldpctl_atom_get_str(mgmt, lldpctl_k_mgmt_ip));
+ if (lldpctl_atom_get_int(mgmt, lldpctl_k_mgmt_iface_index))
+ tag_datatag(w, "mgmt-iface", "MgmtIface",
+ lldpctl_atom_get_str(mgmt, lldpctl_k_mgmt_iface_index));
+ }
+ lldpctl_atom_dec_ref(mgmts);
+
+ /* Capabilities */
+ display_cap(w, chassis, LLDP_CAP_OTHER, "Other");
+ display_cap(w, chassis, LLDP_CAP_REPEATER, "Repeater");
+ display_cap(w, chassis, LLDP_CAP_BRIDGE, "Bridge");
+ display_cap(w, chassis, LLDP_CAP_ROUTER, "Router");
+ display_cap(w, chassis, LLDP_CAP_WLAN, "Wlan");
+ display_cap(w, chassis, LLDP_CAP_TELEPHONE, "Tel");
+ display_cap(w, chassis, LLDP_CAP_DOCSIS, "Docsis");
+ display_cap(w, chassis, LLDP_CAP_STATION, "Station");
+
+ tag_end(w);
+}
+
+static void
+display_custom_tlvs(struct writer *w, lldpctl_atom_t *neighbor)
+{
+ lldpctl_atom_t *custom_list, *custom;
+ int have_custom_tlvs = 0;
+ size_t i, len, slen;
+ const uint8_t *oui, *oui_info;
+ char buf[1600]; /* should be enough for printing */
+
+ custom_list = lldpctl_atom_get(neighbor, lldpctl_k_custom_tlvs);
+ lldpctl_atom_foreach(custom_list, custom)
+ {
+ /* This tag gets added only once, if there are any custom TLVs */
+ if (!have_custom_tlvs) {
+ tag_start(w, "unknown-tlvs", "Unknown TLVs");
+ have_custom_tlvs++;
+ }
+ len = 0;
+ oui = lldpctl_atom_get_buffer(custom, lldpctl_k_custom_tlv_oui, &len);
+ len = 0;
+ oui_info = lldpctl_atom_get_buffer(custom,
+ lldpctl_k_custom_tlv_oui_info_string, &len);
+ if (!oui) continue;
+ tag_start(w, "unknown-tlv", "TLV");
+
+ /* Add OUI as attribute */
+ snprintf(buf, sizeof(buf), "%02X,%02X,%02X", oui[0], oui[1], oui[2]);
+ tag_attr(w, "oui", "OUI", buf);
+ snprintf(buf, sizeof(buf), "%d",
+ (int)lldpctl_atom_get_int(custom,
+ lldpctl_k_custom_tlv_oui_subtype));
+ tag_attr(w, "subtype", "SubType", buf);
+ snprintf(buf, sizeof(buf), "%d", (int)len);
+ tag_attr(w, "len", "Len", buf);
+ if (len > 0) {
+ for (slen = 0, i = 0; i < len; ++i)
+ slen += snprintf(buf + slen,
+ sizeof(buf) > slen ? sizeof(buf) - slen : 0,
+ "%02X%s", oui_info[i], ((i < len - 1) ? "," : ""));
+ tag_data(w, buf);
+ }
+ tag_end(w);
+ }
+ lldpctl_atom_dec_ref(custom_list);
+
+ if (have_custom_tlvs) tag_end(w);
+}
+
+static void
+display_autoneg(struct writer *w, int advertised, int bithd, int bitfd,
+ const char *desc)
+{
+ if (!((advertised & bithd) || (advertised & bitfd))) return;
+
+ tag_start(w, "advertised", "Adv");
+ tag_attr(w, "type", "", desc);
+ if (bitfd != bithd) {
+ tag_attr(w, "hd", "HD", (advertised & bithd) ? "yes" : "no");
+ tag_attr(w, "fd", "FD", (advertised & bitfd) ? "yes" : "no");
+ }
+ tag_end(w);
+}
+
+static void
+display_port(struct writer *w, lldpctl_atom_t *port, int details)
+{
+ int vlan_tx_tag;
+ char buf[5]; /* should be enough for printing */
+
+ tag_start(w, "port", "Port");
+ tag_start(w, "id", "PortID");
+ tag_attr(w, "type", "", lldpctl_atom_get_str(port, lldpctl_k_port_id_subtype));
+ tag_data(w, lldpctl_atom_get_str(port, lldpctl_k_port_id));
+ tag_end(w);
+
+ tag_datatag(w, "descr", "PortDescr",
+ lldpctl_atom_get_str(port, lldpctl_k_port_descr));
+
+ if ((vlan_tx_tag = lldpctl_atom_get_int(port, lldpctl_k_port_vlan_tx)) != -1) {
+ tag_start(w, "vlanTX", "VlanTX");
+ snprintf(buf, sizeof(buf), "%d", vlan_tx_tag & 0xfff);
+ tag_attr(w, "id", "VID", buf);
+ snprintf(buf, sizeof(buf), "%d", (vlan_tx_tag >> 13) & 0x7);
+ tag_attr(w, "prio", "Prio", buf);
+ snprintf(buf, sizeof(buf), "%d", (vlan_tx_tag >> 12) & 0x1);
+ tag_attr(w, "dei", "DEI", buf);
+ tag_end(w);
+ }
+
+ if (details && lldpctl_atom_get_int(port, lldpctl_k_port_ttl) > 0)
+ tag_datatag(w, "ttl", "TTL",
+ lldpctl_atom_get_str(port, lldpctl_k_port_ttl));
+
+ /* Dot3 */
+ if (details == DISPLAY_DETAILS) {
+ tag_datatag(w, "mfs", "MFS",
+ lldpctl_atom_get_str(port, lldpctl_k_port_dot3_mfs));
+ tag_datatag(w, "aggregation", "Port is aggregated. PortAggregID",
+ lldpctl_atom_get_str(port, lldpctl_k_port_dot3_aggregid));
+
+ long int autoneg_support, autoneg_enabled, autoneg_advertised, mautype;
+ autoneg_support =
+ lldpctl_atom_get_int(port, lldpctl_k_port_dot3_autoneg_support);
+ autoneg_enabled =
+ lldpctl_atom_get_int(port, lldpctl_k_port_dot3_autoneg_enabled);
+ autoneg_advertised =
+ lldpctl_atom_get_int(port, lldpctl_k_port_dot3_autoneg_advertised);
+ mautype = lldpctl_atom_get_int(port, lldpctl_k_port_dot3_mautype);
+ if (autoneg_support > 0 || autoneg_enabled > 0 || mautype > 0) {
+ tag_start(w, "auto-negotiation", "PMD autoneg");
+ tag_attr(w, "supported", "supported",
+ (autoneg_support > 0) ? "yes" : "no");
+ tag_attr(w, "enabled", "enabled",
+ (autoneg_enabled > 0) ? "yes" : "no");
+
+ if (autoneg_enabled > 0) {
+ if (autoneg_advertised < 0) autoneg_advertised = 0;
+ display_autoneg(w, autoneg_advertised,
+ LLDP_DOT3_LINK_AUTONEG_10BASE_T,
+ LLDP_DOT3_LINK_AUTONEG_10BASET_FD, "10Base-T");
+ display_autoneg(w, autoneg_advertised,
+ LLDP_DOT3_LINK_AUTONEG_100BASE_TX,
+ LLDP_DOT3_LINK_AUTONEG_100BASE_TXFD, "100Base-TX");
+ display_autoneg(w, autoneg_advertised,
+ LLDP_DOT3_LINK_AUTONEG_100BASE_T2,
+ LLDP_DOT3_LINK_AUTONEG_100BASE_T2FD, "100Base-T2");
+ display_autoneg(w, autoneg_advertised,
+ LLDP_DOT3_LINK_AUTONEG_100BASE_T4,
+ LLDP_DOT3_LINK_AUTONEG_100BASE_T4, "100Base-T4");
+ display_autoneg(w, autoneg_advertised,
+ LLDP_DOT3_LINK_AUTONEG_1000BASE_X,
+ LLDP_DOT3_LINK_AUTONEG_1000BASE_XFD, "1000Base-X");
+ display_autoneg(w, autoneg_advertised,
+ LLDP_DOT3_LINK_AUTONEG_1000BASE_T,
+ LLDP_DOT3_LINK_AUTONEG_1000BASE_TFD, "1000Base-T");
+ }
+ tag_datatag(w, "current", "MAU oper type",
+ lldpctl_atom_get_str(port, lldpctl_k_port_dot3_mautype));
+ tag_end(w);
+ }
+
+ lldpctl_atom_t *dot3_power =
+ lldpctl_atom_get(port, lldpctl_k_port_dot3_power);
+ int devicetype =
+ lldpctl_atom_get_int(dot3_power, lldpctl_k_dot3_power_devicetype);
+ if (devicetype > 0) {
+ tag_start(w, "power", "MDI Power");
+ tag_attr(w, "supported", "Supported",
+ (lldpctl_atom_get_int(dot3_power,
+ lldpctl_k_dot3_power_supported) > 0) ?
+ "yes" :
+ "no");
+ tag_attr(w, "enabled", "Enabled",
+ (lldpctl_atom_get_int(dot3_power,
+ lldpctl_k_dot3_power_enabled) > 0) ?
+ "yes" :
+ "no");
+ tag_attr(w, "paircontrol", "Pair control",
+ (lldpctl_atom_get_int(dot3_power,
+ lldpctl_k_dot3_power_paircontrol) > 0) ?
+ "yes" :
+ "no");
+ tag_start(w, "device-type", "Device type");
+ tag_data(w,
+ lldpctl_atom_get_str(dot3_power,
+ lldpctl_k_dot3_power_devicetype));
+ ;
+ tag_end(w);
+ tag_start(w, "pairs", "Power pairs");
+ tag_data(w,
+ lldpctl_atom_get_str(dot3_power,
+ lldpctl_k_dot3_power_pairs));
+ tag_end(w);
+ tag_start(w, "class", "Class");
+ tag_data(w,
+ lldpctl_atom_get_str(dot3_power,
+ lldpctl_k_dot3_power_class));
+ tag_end(w);
+
+ /* 802.3at */
+ if (lldpctl_atom_get_int(dot3_power,
+ lldpctl_k_dot3_power_type) >
+ LLDP_DOT3_POWER_8023AT_OFF) {
+ tag_start(w, "power-type", "Power type");
+ tag_data(w,
+ lldpctl_atom_get_str(dot3_power,
+ lldpctl_k_dot3_power_type));
+ tag_end(w);
+
+ tag_start(w, "source", "Power Source");
+ tag_data(w,
+ lldpctl_atom_get_str(dot3_power,
+ lldpctl_k_dot3_power_source));
+ tag_end(w);
+
+ tag_start(w, "priority", "Power Priority");
+ tag_data(w,
+ lldpctl_atom_get_str(dot3_power,
+ lldpctl_k_dot3_power_priority));
+ tag_end(w);
+
+ tag_start(w, "requested", "PD requested power Value");
+ tag_data(w,
+ lldpctl_atom_get_str(dot3_power,
+ lldpctl_k_dot3_power_requested));
+ tag_end(w);
+
+ tag_start(w, "allocated", "PSE allocated power Value");
+ tag_data(w,
+ lldpctl_atom_get_str(dot3_power,
+ lldpctl_k_dot3_power_allocated));
+ tag_end(w);
+ }
+
+ /* 802.3bt */
+ if (lldpctl_atom_get_int(dot3_power,
+ lldpctl_k_dot3_power_type_ext) >
+ LLDP_DOT3_POWER_8023BT_OFF) {
+ tag_start(w, "requested-a", "Requested mode A");
+ tag_data(w,
+ lldpctl_atom_get_str(dot3_power,
+ lldpctl_k_dot3_power_requested_a));
+ tag_end(w);
+ tag_start(w, "requested-b", "Requested mode B");
+ tag_data(w,
+ lldpctl_atom_get_str(dot3_power,
+ lldpctl_k_dot3_power_requested_b));
+ tag_end(w);
+ tag_start(w, "allocated-a", "Allocated alternative A");
+ tag_data(w,
+ lldpctl_atom_get_str(dot3_power,
+ lldpctl_k_dot3_power_allocated_a));
+ tag_end(w);
+ tag_start(w, "allocated-b", "Allocated alternative B");
+ tag_data(w,
+ lldpctl_atom_get_str(dot3_power,
+ lldpctl_k_dot3_power_allocated_b));
+ tag_end(w);
+ tag_start(w, "pse-powering-status",
+ "PSE powering status");
+ tag_data(w,
+ lldpctl_atom_get_str(dot3_power,
+ lldpctl_k_dot3_power_pse_status));
+ tag_end(w);
+ tag_start(w, "pd-powering-status",
+ "PD powering status");
+ tag_data(w,
+ lldpctl_atom_get_str(dot3_power,
+ lldpctl_k_dot3_power_pd_status));
+ tag_end(w);
+ tag_start(w, "power-pairs-ext", "Power pairs extra");
+ tag_data(w,
+ lldpctl_atom_get_str(dot3_power,
+ lldpctl_k_dot3_power_pse_pairs_ext));
+ tag_end(w);
+ tag_start(w, "power-class-ext-a", "Class extra A");
+ tag_data(w,
+ lldpctl_atom_get_str(dot3_power,
+ lldpctl_k_dot3_power_class_a));
+ tag_end(w);
+ tag_start(w, "power-class-ext-b", "Class extra B");
+ tag_data(w,
+ lldpctl_atom_get_str(dot3_power,
+ lldpctl_k_dot3_power_class_b));
+ tag_end(w);
+ tag_start(w, "power-class-ext", "Class extra");
+ tag_data(w,
+ lldpctl_atom_get_str(dot3_power,
+ lldpctl_k_dot3_power_class_ext));
+ tag_end(w);
+ tag_start(w, "power-type-ext", "Power type extra");
+ tag_data(w,
+ lldpctl_atom_get_str(dot3_power,
+ lldpctl_k_dot3_power_type_ext));
+ tag_end(w);
+ tag_start(w, "pd-load", "PD load");
+ tag_data(w,
+ lldpctl_atom_get_str(dot3_power,
+ lldpctl_k_dot3_power_pd_load));
+ tag_end(w);
+ tag_start(w, "max-power",
+ "PSE maximum available power");
+ tag_data(w,
+ lldpctl_atom_get_str(dot3_power,
+ lldpctl_k_dot3_power_pse_max));
+ tag_end(w);
+ }
+
+ tag_end(w);
+ }
+ lldpctl_atom_dec_ref(dot3_power);
+ }
+
+ tag_end(w);
+}
+
+static void
+display_local_ttl(struct writer *w, lldpctl_conn_t *conn, int details)
+{
+ char *ttl;
+ long int tx_hold;
+ long int tx_interval;
+
+ lldpctl_atom_t *configuration;
+ configuration = lldpctl_get_configuration(conn);
+ if (!configuration) {
+ log_warnx("lldpctl", "not able to get configuration. %s",
+ lldpctl_last_strerror(conn));
+ return;
+ }
+
+ tx_hold = lldpctl_atom_get_int(configuration, lldpctl_k_config_tx_hold);
+ tx_interval =
+ lldpctl_atom_get_int(configuration, lldpctl_k_config_tx_interval_ms);
+
+ tx_interval = (tx_interval * tx_hold + 999) / 1000;
+
+ if (asprintf(&ttl, "%lu", tx_interval) == -1) {
+ log_warnx("lldpctl", "not enough memory to build TTL.");
+ goto end;
+ }
+
+ tag_start(w, "ttl", "TTL");
+ tag_attr(w, "ttl", "", ttl);
+ tag_end(w);
+ free(ttl);
+end:
+ lldpctl_atom_dec_ref(configuration);
+}
+
+static void
+display_vlans(struct writer *w, lldpctl_atom_t *port)
+{
+ lldpctl_atom_t *vlans, *vlan;
+ int foundpvid = 0;
+ int pvid, vid;
+
+ pvid = lldpctl_atom_get_int(port, lldpctl_k_port_vlan_pvid);
+
+ vlans = lldpctl_atom_get(port, lldpctl_k_port_vlans);
+ lldpctl_atom_foreach(vlans, vlan)
+ {
+ vid = lldpctl_atom_get_int(vlan, lldpctl_k_vlan_id);
+
+ tag_start(w, "vlan", "VLAN");
+ tag_attr(w, "vlan-id", "",
+ lldpctl_atom_get_str(vlan, lldpctl_k_vlan_id));
+ if (pvid == vid) {
+ tag_attr(w, "pvid", "pvid", "yes");
+ foundpvid = 1;
+ } else {
+ tag_attr(w, "pvid", "pvid", "no");
+ }
+ tag_data(w, lldpctl_atom_get_str(vlan, lldpctl_k_vlan_name));
+ tag_end(w);
+ }
+ lldpctl_atom_dec_ref(vlans);
+
+ if (!foundpvid && pvid > 0) {
+ tag_start(w, "vlan", "VLAN");
+ tag_attr(w, "vlan-id", "",
+ lldpctl_atom_get_str(port, lldpctl_k_port_vlan_pvid));
+ tag_attr(w, "pvid", "pvid", "yes");
+ tag_end(w);
+ }
+}
+
+static void
+display_ppvids(struct writer *w, lldpctl_atom_t *port)
+{
+ lldpctl_atom_t *ppvids, *ppvid;
+ ppvids = lldpctl_atom_get(port, lldpctl_k_port_ppvids);
+ lldpctl_atom_foreach(ppvids, ppvid)
+ {
+ int status = lldpctl_atom_get_int(ppvid, lldpctl_k_ppvid_status);
+ tag_start(w, "ppvid", "PPVID");
+ if (lldpctl_atom_get_int(ppvid, lldpctl_k_ppvid_id) > 0)
+ tag_attr(w, "value", "",
+ lldpctl_atom_get_str(ppvid, lldpctl_k_ppvid_id));
+ tag_attr(w, "supported", "supported",
+ (status & LLDP_PPVID_CAP_SUPPORTED) ? "yes" : "no");
+ tag_attr(w, "enabled", "enabled",
+ (status & LLDP_PPVID_CAP_ENABLED) ? "yes" : "no");
+ tag_end(w);
+ }
+ lldpctl_atom_dec_ref(ppvids);
+}
+
+static void
+display_pids(struct writer *w, lldpctl_atom_t *port)
+{
+ lldpctl_atom_t *pids, *pid;
+ pids = lldpctl_atom_get(port, lldpctl_k_port_pis);
+ lldpctl_atom_foreach(pids, pid)
+ {
+ const char *pi = lldpctl_atom_get_str(pid, lldpctl_k_pi_id);
+ if (pi && strlen(pi) > 0) tag_datatag(w, "pi", "PI", pi);
+ }
+ lldpctl_atom_dec_ref(pids);
+}
+
+static const char *
+display_age(time_t lastchange)
+{
+ static char sage[30];
+ int age = (int)(time(NULL) - lastchange);
+ if (snprintf(sage, sizeof(sage), "%d day%s, %02d:%02d:%02d",
+ age / (60 * 60 * 24), (age / (60 * 60 * 24) > 1) ? "s" : "",
+ (age / (60 * 60)) % 24, (age / 60) % 60, age % 60) >= sizeof(sage))
+ return "too much";
+ else
+ return sage;
+}
+
+void
+display_local_chassis(lldpctl_conn_t *conn, struct writer *w, struct cmd_env *env,
+ int details)
+{
+ tag_start(w, "local-chassis", "Local chassis");
+
+ lldpctl_atom_t *chassis = lldpctl_get_local_chassis(conn);
+ display_chassis(w, chassis, details);
+ if (details == DISPLAY_DETAILS) {
+ display_med(w, NULL, chassis);
+ }
+ lldpctl_atom_dec_ref(chassis);
+
+ tag_end(w);
+}
+
+void
+display_interface(lldpctl_conn_t *conn, struct writer *w, int hidden,
+ lldpctl_atom_t *iface, lldpctl_atom_t *port, int details, int protocol)
+{
+ int local = 0;
+
+ if (!hidden && lldpctl_atom_get_int(port, lldpctl_k_port_hidden)) return;
+
+ /* user might have specified protocol to filter on display */
+ if ((protocol != LLDPD_MODE_MAX) &&
+ (protocol != lldpctl_atom_get_int(port, lldpctl_k_port_protocol)))
+ return;
+
+ /* Infer local / remote port from the port index (remote == 0) */
+ local = lldpctl_atom_get_int(port, lldpctl_k_port_index) > 0 ? 1 : 0;
+
+ lldpctl_atom_t *chassis = lldpctl_atom_get(port, lldpctl_k_port_chassis);
+
+ tag_start(w, "interface", "Interface");
+ tag_attr(w, "name", "", lldpctl_atom_get_str(iface, lldpctl_k_interface_name));
+ if (!local) {
+ tag_attr(w, "via", "via",
+ lldpctl_atom_get_str(port, lldpctl_k_port_protocol));
+ if (details > DISPLAY_BRIEF) {
+ tag_attr(w, "rid", "RID",
+ lldpctl_atom_get_str(chassis, lldpctl_k_chassis_index));
+ tag_attr(w, "age", "Time",
+ display_age(
+ lldpctl_atom_get_int(port, lldpctl_k_port_age)));
+ }
+ } else {
+ tag_datatag(w, "status", "Administrative status",
+ lldpctl_atom_get_str(port, lldpctl_k_port_status));
+ }
+
+ display_chassis(w, chassis, details);
+ display_port(w, port, details);
+ if (details && local && conn) display_local_ttl(w, conn, details);
+ if (details == DISPLAY_DETAILS) {
+ display_vlans(w, port);
+ display_ppvids(w, port);
+ display_pids(w, port);
+ display_med(w, port, chassis);
+ }
+
+ lldpctl_atom_dec_ref(chassis);
+
+ display_custom_tlvs(w, port);
+
+ tag_end(w);
+}
+
+/**
+ * Display information about interfaces.
+ *
+ * @param conn Connection to lldpd.
+ * @param w Writer.
+ * @param env Environment from which we may find the list of ports.
+ * @param hidden Whatever to show hidden ports.
+ * @param details Level of details we need (DISPLAY_*).
+ */
+void
+display_interfaces(lldpctl_conn_t *conn, struct writer *w, struct cmd_env *env,
+ int hidden, int details)
+{
+ lldpctl_atom_t *iface;
+ int protocol = LLDPD_MODE_MAX;
+ const char *proto_str;
+
+ /* user might have specified protocol to filter display results */
+ proto_str = cmdenv_get(env, "protocol");
+
+ if (proto_str) {
+ log_debug("display", "filter protocol: %s ", proto_str);
+
+ protocol = 0;
+ for (lldpctl_map_t *protocol_map =
+ lldpctl_key_get_map(lldpctl_k_port_protocol);
+ protocol_map->string; protocol_map++) {
+ if (!strcasecmp(proto_str, protocol_map->string)) {
+ protocol = protocol_map->value;
+ break;
+ }
+ }
+ }
+
+ tag_start(w, "lldp", "LLDP neighbors");
+ while ((iface = cmd_iterate_on_interfaces(conn, env))) {
+ lldpctl_atom_t *port;
+ lldpctl_atom_t *neighbors;
+ lldpctl_atom_t *neighbor;
+ port = lldpctl_get_port(iface);
+ neighbors = lldpctl_atom_get(port, lldpctl_k_port_neighbors);
+ lldpctl_atom_foreach(neighbors, neighbor)
+ {
+ display_interface(conn, w, hidden, iface, neighbor, details,
+ protocol);
+ }
+ lldpctl_atom_dec_ref(neighbors);
+ lldpctl_atom_dec_ref(port);
+ }
+ tag_end(w);
+}
+
+/**
+ * Display information about local interfaces.
+ *
+ * @param conn Connection to lldpd.
+ * @param w Writer.
+ * @param hidden Whatever to show hidden ports.
+ * @param env Environment from which we may find the list of ports.
+ * @param details Level of details we need (DISPLAY_*).
+ */
+void
+display_local_interfaces(lldpctl_conn_t *conn, struct writer *w, struct cmd_env *env,
+ int hidden, int details)
+{
+ lldpctl_atom_t *iface;
+ int protocol = LLDPD_MODE_MAX;
+
+ tag_start(w, "lldp", "LLDP interfaces");
+ while ((iface = cmd_iterate_on_interfaces(conn, env))) {
+ lldpctl_atom_t *port;
+ port = lldpctl_get_port(iface);
+ display_interface(conn, w, hidden, iface, port, details, protocol);
+ lldpctl_atom_dec_ref(port);
+ }
+ tag_end(w);
+}
+
+static void
+display_stat(struct writer *w, const char *tag, const char *descr,
+ long unsigned int cnt)
+{
+ char buf[20] = {};
+
+ tag_start(w, tag, descr);
+ snprintf(buf, sizeof(buf), "%lu", cnt);
+ tag_attr(w, tag, "", buf);
+ tag_end(w);
+}
+
+void
+display_interface_stats(lldpctl_conn_t *conn, struct writer *w, lldpctl_atom_t *port)
+{
+ tag_start(w, "interface", "Interface");
+ tag_attr(w, "name", "", lldpctl_atom_get_str(port, lldpctl_k_port_name));
+
+ display_stat(w, "tx", "Transmitted",
+ lldpctl_atom_get_int(port, lldpctl_k_tx_cnt));
+ display_stat(w, "rx", "Received", lldpctl_atom_get_int(port, lldpctl_k_rx_cnt));
+
+ display_stat(w, "rx_discarded_cnt", "Discarded",
+ lldpctl_atom_get_int(port, lldpctl_k_rx_discarded_cnt));
+
+ display_stat(w, "rx_unrecognized_cnt", "Unrecognized",
+ lldpctl_atom_get_int(port, lldpctl_k_rx_unrecognized_cnt));
+
+ display_stat(w, "ageout_cnt", "Ageout",
+ lldpctl_atom_get_int(port, lldpctl_k_ageout_cnt));
+
+ display_stat(w, "insert_cnt", "Inserted",
+ lldpctl_atom_get_int(port, lldpctl_k_insert_cnt));
+
+ display_stat(w, "delete_cnt", "Deleted",
+ lldpctl_atom_get_int(port, lldpctl_k_delete_cnt));
+
+ tag_end(w);
+}
+
+/**
+ * Display interface stats
+ *
+ * @param conn Connection to lldpd.
+ * @param w Writer.
+ * @param env Environment from which we may find the list of ports.
+ */
+void
+display_interfaces_stats(lldpctl_conn_t *conn, struct writer *w, struct cmd_env *env)
+{
+ lldpctl_atom_t *iface;
+ int summary = 0;
+ u_int64_t h_tx_cnt = 0;
+ u_int64_t h_rx_cnt = 0;
+ u_int64_t h_rx_discarded_cnt = 0;
+ u_int64_t h_rx_unrecognized_cnt = 0;
+ u_int64_t h_ageout_cnt = 0;
+ u_int64_t h_insert_cnt = 0;
+ u_int64_t h_delete_cnt = 0;
+
+ if (cmdenv_get(env, "summary")) summary = 1;
+
+ tag_start(w, "lldp", (summary ? "LLDP Global statistics" : "LLDP statistics"));
+ while ((iface = cmd_iterate_on_interfaces(conn, env))) {
+ lldpctl_atom_t *port;
+ port = lldpctl_get_port(iface);
+ if (!summary)
+ display_interface_stats(conn, w, port);
+ else {
+ h_tx_cnt += lldpctl_atom_get_int(port, lldpctl_k_tx_cnt);
+ h_rx_cnt += lldpctl_atom_get_int(port, lldpctl_k_rx_cnt);
+ h_rx_discarded_cnt +=
+ lldpctl_atom_get_int(port, lldpctl_k_rx_discarded_cnt);
+ h_rx_unrecognized_cnt +=
+ lldpctl_atom_get_int(port, lldpctl_k_rx_unrecognized_cnt);
+ h_ageout_cnt +=
+ lldpctl_atom_get_int(port, lldpctl_k_ageout_cnt);
+ h_insert_cnt +=
+ lldpctl_atom_get_int(port, lldpctl_k_insert_cnt);
+ h_delete_cnt +=
+ lldpctl_atom_get_int(port, lldpctl_k_delete_cnt);
+ }
+ lldpctl_atom_dec_ref(port);
+ }
+
+ if (summary) {
+ tag_start(w, "summary", "Summary of stats");
+ display_stat(w, "tx", "Transmitted", h_tx_cnt);
+ display_stat(w, "rx", "Received", h_rx_cnt);
+ display_stat(w, "rx_discarded_cnt", "Discarded", h_rx_discarded_cnt);
+
+ display_stat(w, "rx_unrecognized_cnt", "Unrecognized",
+ h_rx_unrecognized_cnt);
+
+ display_stat(w, "ageout_cnt", "Ageout", h_ageout_cnt);
+
+ display_stat(w, "insert_cnt", "Inserted", h_insert_cnt);
+
+ display_stat(w, "delete_cnt", "Deleted", h_delete_cnt);
+ tag_end(w);
+ }
+ tag_end(w);
+}
+
+static const char *
+N(const char *str)
+{
+ if (str == NULL || strlen(str) == 0) return "(none)";
+ return str;
+}
+
+void
+display_configuration(lldpctl_conn_t *conn, struct writer *w)
+{
+ lldpctl_atom_t *configuration;
+
+ configuration = lldpctl_get_configuration(conn);
+ if (!configuration) {
+ log_warnx("lldpctl", "not able to get configuration. %s",
+ lldpctl_last_strerror(conn));
+ return;
+ }
+
+ tag_start(w, "configuration", "Global configuration");
+ tag_start(w, "config", "Configuration");
+
+ tag_datatag(w, "tx-delay", "Transmit delay",
+ lldpctl_atom_get_str(configuration, lldpctl_k_config_tx_interval));
+ tag_datatag(w, "tx-delay-ms", "Transmit delay in milliseconds",
+ lldpctl_atom_get_str(configuration, lldpctl_k_config_tx_interval_ms));
+ tag_datatag(w, "tx-hold", "Transmit hold",
+ lldpctl_atom_get_str(configuration, lldpctl_k_config_tx_hold));
+ tag_datatag(w, "max-neighbors", "Maximum number of neighbors",
+ lldpctl_atom_get_str(configuration, lldpctl_k_config_max_neighbors));
+ tag_datatag(w, "rx-only", "Receive mode",
+ lldpctl_atom_get_int(configuration, lldpctl_k_config_receiveonly) ? "yes" :
+ "no");
+ tag_datatag(w, "mgmt-pattern", "Pattern for management addresses",
+ N(lldpctl_atom_get_str(configuration, lldpctl_k_config_mgmt_pattern)));
+ tag_datatag(w, "iface-pattern", "Interface pattern",
+ N(lldpctl_atom_get_str(configuration, lldpctl_k_config_iface_pattern)));
+ tag_datatag(w, "perm-iface-pattern", "Permanent interface pattern",
+ N(lldpctl_atom_get_str(configuration,
+ lldpctl_k_config_perm_iface_pattern)));
+ tag_datatag(w, "cid-pattern", "Interface pattern for chassis ID",
+ N(lldpctl_atom_get_str(configuration, lldpctl_k_config_cid_pattern)));
+ tag_datatag(w, "cid-string", "Override chassis ID with",
+ N(lldpctl_atom_get_str(configuration, lldpctl_k_config_cid_string)));
+ tag_datatag(w, "description", "Override description with",
+ N(lldpctl_atom_get_str(configuration, lldpctl_k_config_description)));
+ tag_datatag(w, "platform", "Override platform with",
+ N(lldpctl_atom_get_str(configuration, lldpctl_k_config_platform)));
+ tag_datatag(w, "hostname", "Override system name with",
+ N(lldpctl_atom_get_str(configuration, lldpctl_k_config_hostname)));
+ tag_datatag(w, "capabilities", "Override system capabilities",
+ lldpctl_atom_get_int(configuration, lldpctl_k_config_chassis_cap_override) ?
+ "yes" :
+ "no");
+ tag_datatag(w, "advertise-version", "Advertise version",
+ lldpctl_atom_get_int(configuration, lldpctl_k_config_advertise_version) ?
+ "yes" :
+ "no");
+ tag_datatag(w, "ifdescr-update", "Update interface descriptions",
+ lldpctl_atom_get_int(configuration, lldpctl_k_config_ifdescr_update) ?
+ "yes" :
+ "no");
+ tag_datatag(w, "iface-promisc", "Promiscuous mode on managed interfaces",
+ lldpctl_atom_get_int(configuration, lldpctl_k_config_iface_promisc) ?
+ "yes" :
+ "no");
+ tag_datatag(w, "lldpmed-no-inventory", "Disable LLDP-MED inventory",
+ (lldpctl_atom_get_int(configuration,
+ lldpctl_k_config_lldpmed_noinventory) == 0) ?
+ "no" :
+ "yes");
+ tag_datatag(w, "lldpmed-faststart", "LLDP-MED fast start mechanism",
+ (lldpctl_atom_get_int(configuration, lldpctl_k_config_fast_start_enabled) ==
+ 0) ?
+ "no" :
+ "yes");
+ tag_datatag(w, "lldpmed-faststart-interval", "LLDP-MED fast start interval",
+ N(lldpctl_atom_get_str(configuration,
+ lldpctl_k_config_fast_start_interval)));
+ tag_datatag(w, "bond-slave-src-mac-type",
+ "Source MAC for LLDP frames on bond slaves",
+ lldpctl_atom_get_str(configuration,
+ lldpctl_k_config_bond_slave_src_mac_type));
+ tag_datatag(w, "lldp-portid-type", "Port ID TLV subtype for LLDP frames",
+ lldpctl_atom_get_str(configuration, lldpctl_k_config_lldp_portid_type));
+ tag_datatag(w, "lldp-agent-type", "Agent type",
+ lldpctl_atom_get_str(configuration, lldpctl_k_config_lldp_agent_type));
+
+ tag_end(w);
+ tag_end(w);
+
+ lldpctl_atom_dec_ref(configuration);
+}
diff --git a/src/client/json_writer.c b/src/client/json_writer.c
new file mode 100644
index 0000000..dc6665a
--- /dev/null
+++ b/src/client/json_writer.c
@@ -0,0 +1,374 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/*
+ * Copyright (c) 2017 Vincent Bernat <bernat@luffy.cx>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#if HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/queue.h>
+
+#include "writer.h"
+#include "../compat/compat.h"
+#include "../log.h"
+
+enum tag { STRING, BOOL, ARRAY, OBJECT };
+
+struct element {
+ struct element *parent; /* Parent (if any) */
+ TAILQ_ENTRY(element) next; /* Sibling (if any) */
+ char *key; /* Key if parent is an object */
+ enum tag tag; /* Kind of element */
+ union {
+ char *string; /* STRING */
+ int boolean; /* BOOL */
+ TAILQ_HEAD(, element) children; /* ARRAY or OBJECT */
+ };
+};
+
+struct json_writer_private {
+ FILE *fh;
+ int variant;
+ struct element *root;
+ struct element *current; /* should always be an object */
+};
+
+/* Create a new element. If a parent is provided, it will also be attached to
+ * the parent. */
+static struct element *
+json_element_new(struct element *parent, const char *key, enum tag tag)
+{
+ struct element *child = malloc(sizeof(*child));
+ if (child == NULL) fatal(NULL, NULL);
+ child->parent = parent;
+ child->key = key ? strdup(key) : NULL;
+ child->tag = tag;
+ TAILQ_INIT(&child->children);
+ if (parent) TAILQ_INSERT_TAIL(&parent->children, child, next);
+ return child;
+}
+
+/* Free the element content (but not the element itself) */
+static void
+json_element_free(struct element *current)
+{
+ struct element *el, *el_next;
+ switch (current->tag) {
+ case STRING:
+ free(current->string);
+ break;
+ case BOOL:
+ break;
+ case ARRAY:
+ case OBJECT:
+ for (el = TAILQ_FIRST(&current->children); el != NULL; el = el_next) {
+ el_next = TAILQ_NEXT(el, next);
+ json_element_free(el);
+ TAILQ_REMOVE(&current->children, el, next);
+ if (current->tag == OBJECT) free(el->key);
+ free(el);
+ }
+ break;
+ }
+}
+
+static void
+json_free(struct json_writer_private *p)
+{
+ json_element_free(p->root);
+ free(p->root);
+}
+
+static void
+json_string_dump(FILE *fh, const char *s)
+{
+ fprintf(fh, "\"");
+ while (*s != '\0') {
+ unsigned int c = *s;
+ size_t len;
+ switch (c) {
+ case '"':
+ fprintf(fh, "\\\"");
+ s++;
+ break;
+ case '\\':
+ fprintf(fh, "\\\\");
+ s++;
+ break;
+ case '\b':
+ fprintf(fh, "\\b");
+ s++;
+ break;
+ case '\f':
+ fprintf(fh, "\\f");
+ s++;
+ break;
+ case '\n':
+ fprintf(fh, "\\n");
+ s++;
+ break;
+ case '\r':
+ fprintf(fh, "\\r");
+ s++;
+ break;
+ case '\t':
+ fprintf(fh, "\\t");
+ s++;
+ break;
+ default:
+ len = utf8_validate_cz(s);
+ if (len == 0) {
+ /* Not a valid UTF-8 char, use a
+ * replacement character */
+ fprintf(fh, "\\uFFFD");
+ s++;
+ } else if (c < 0x1f) {
+ /* 7-bit ASCII character */
+ fprintf(fh, "\\u%04X", c);
+ s++;
+ } else {
+ /* UTF-8, write as is */
+ while (len--)
+ fprintf(fh, "%c", *s++);
+ }
+ break;
+ }
+ }
+ fprintf(fh, "\"");
+}
+
+/* Dump an element to the specified file handle. */
+static void
+json_element_dump(FILE *fh, struct element *current, int indent)
+{
+ static const char pairs[2][2] = { "{}", "[]" };
+ struct element *el;
+ switch (current->tag) {
+ case STRING:
+ json_string_dump(fh, current->string);
+ break;
+ case BOOL:
+ fprintf(fh, current->boolean ? "true" : "false");
+ break;
+ case ARRAY:
+ case OBJECT:
+ fprintf(fh, "%c\n%*s", pairs[(current->tag == ARRAY)][0], indent + 2,
+ "");
+ TAILQ_FOREACH (el, &current->children, next) {
+ if (current->tag == OBJECT) fprintf(fh, "\"%s\": ", el->key);
+ json_element_dump(fh, el, indent + 2);
+ if (TAILQ_NEXT(el, next)) fprintf(fh, ",\n%*s", indent + 2, "");
+ }
+ fprintf(fh, "\n%*c", indent + 1, pairs[(current->tag == ARRAY)][1]);
+ break;
+ }
+}
+
+static void
+json_dump(struct json_writer_private *p)
+{
+ json_element_dump(p->fh, p->root, 0);
+ fprintf(p->fh, "\n");
+}
+
+static void
+json_start(struct writer *w, const char *tag, const char *descr)
+{
+ struct json_writer_private *p = w->priv;
+ struct element *child;
+ struct element *new;
+
+ /* Look for the tag in the current object. */
+ TAILQ_FOREACH (child, &p->current->children, next) {
+ if (!strcmp(child->key, tag)) break;
+ }
+ if (!child) child = json_element_new(p->current, tag, ARRAY);
+
+ /* Queue the new element. */
+ new = json_element_new(child, NULL, OBJECT);
+ p->current = new;
+}
+
+static void
+json_attr(struct writer *w, const char *tag, const char *descr, const char *value)
+{
+ struct json_writer_private *p = w->priv;
+ struct element *new = json_element_new(p->current, tag, STRING);
+ if (value && (!strcmp(value, "yes") || !strcmp(value, "on"))) {
+ new->tag = BOOL;
+ new->boolean = 1;
+ } else if (value && (!strcmp(value, "no") || !strcmp(value, "off"))) {
+ new->tag = BOOL;
+ new->boolean = 0;
+ } else {
+ new->string = strdup(value ? value : "");
+ }
+}
+
+static void
+json_data(struct writer *w, const char *data)
+{
+ struct json_writer_private *p = w->priv;
+ struct element *new = json_element_new(p->current, "value", STRING);
+ new->string = strdup(data ? data : "");
+}
+
+/* When an array has only one member, just remove the array. When an object has
+ * `value` as the only key, remove the object. Moreover, for an object, move the
+ * `name` key outside (inside a new object). This is a recursive function. We
+ * think the depth will be limited. Also, the provided element can be
+ * destroyed. Don't use it after this function!
+ *
+ * During the cleaning process, we will generate array of 1-size objects that
+ * could be turned into an object. We don't do that since people may rely on
+ * this format. Another problem is the format is changing depending on the
+ * number of interfaces or the number of neighbors.
+ */
+static void
+json_element_cleanup(struct element *el)
+{
+#ifndef ENABLE_JSON0
+ struct element *child, *child_next;
+
+ /* If array with one element, steal the content. Object with only one
+ * value whose key is "value", steal the content. */
+ if ((el->tag == ARRAY || el->tag == OBJECT) &&
+ (child = TAILQ_FIRST(&el->children)) && !TAILQ_NEXT(child, next) &&
+ (el->tag == ARRAY || !strcmp(child->key, "value"))) {
+ free(child->key);
+ child->key = el->key;
+ child->parent = el->parent;
+ TAILQ_INSERT_BEFORE(el, child, next);
+ TAILQ_REMOVE(&el->parent->children, el, next);
+ free(el);
+ json_element_cleanup(child);
+ return;
+ }
+
+ /* Other kind of arrays, recursively clean */
+ if (el->tag == ARRAY) {
+ for (child = TAILQ_FIRST(&el->children); child; child = child_next) {
+ child_next = TAILQ_NEXT(child, next);
+ json_element_cleanup(child);
+ }
+ return;
+ }
+
+ /* Other kind of objects, recursively clean, but if one key is "name",
+ * use it's value as a key for a new object stealing the existing
+ * one. */
+ if (el->tag == OBJECT) {
+ struct element *name_child = NULL;
+ for (child = TAILQ_FIRST(&el->children); child; child = child_next) {
+ child_next = TAILQ_NEXT(child, next);
+ json_element_cleanup(child);
+ }
+ /* Redo a check to find if we have a "name" key now */
+ for (child = TAILQ_FIRST(&el->children); child; child = child_next) {
+ child_next = TAILQ_NEXT(child, next);
+ if (!strcmp(child->key, "name") && child->tag == STRING) {
+ name_child = child;
+ }
+ }
+ if (name_child) {
+ struct element *new_el = json_element_new(NULL, NULL, OBJECT);
+ /* Replace el by new_el in parent object/array */
+ new_el->parent = el->parent;
+ TAILQ_INSERT_BEFORE(el, new_el, next);
+ TAILQ_REMOVE(&el->parent->children, el, next);
+ new_el->key = el->key;
+
+ /* new_el is parent of el */
+ el->parent = new_el;
+ el->key = name_child->string; /* stolen */
+ TAILQ_INSERT_TAIL(&new_el->children, el, next);
+
+ /* Remove "name" child */
+ TAILQ_REMOVE(&el->children, name_child, next);
+ free(name_child->key);
+ free(name_child);
+ }
+ return;
+ }
+#endif
+}
+
+static void
+json_cleanup(struct json_writer_private *p)
+{
+ if (p->variant != 0) json_element_cleanup(p->root);
+}
+
+static void
+json_end(struct writer *w)
+{
+ struct json_writer_private *p = w->priv;
+ while ((p->current = p->current->parent) != NULL && p->current->tag != OBJECT)
+ ;
+ if (p->current == NULL) {
+ fatalx("lldpctl", "unbalanced tags");
+ return;
+ }
+
+ /* Display current object if last one */
+ if (p->current == p->root) {
+ json_cleanup(p);
+ json_dump(p);
+ json_free(p);
+ fprintf(p->fh, "\n");
+ fflush(p->fh);
+ p->root = p->current = json_element_new(NULL, NULL, OBJECT);
+ }
+}
+
+static void
+json_finish(struct writer *w)
+{
+ struct json_writer_private *p = w->priv;
+ if (p->current != p->root) log_warnx("lldpctl", "unbalanced tags");
+ json_free(p);
+ free(p);
+ free(w);
+}
+
+struct writer *
+json_init(FILE *fh, int variant)
+{
+ struct writer *result;
+ struct json_writer_private *priv;
+
+ priv = malloc(sizeof(*priv));
+ if (priv == NULL) fatal(NULL, NULL);
+
+ priv->fh = fh;
+ priv->root = priv->current = json_element_new(NULL, NULL, OBJECT);
+ priv->variant = variant;
+
+ result = malloc(sizeof(*result));
+ if (result == NULL) fatal(NULL, NULL);
+
+ result->priv = priv;
+ result->start = json_start;
+ result->attr = json_attr;
+ result->data = json_data;
+ result->end = json_end;
+ result->finish = json_finish;
+
+ return result;
+}
diff --git a/src/client/kv_writer.c b/src/client/kv_writer.c
new file mode 100644
index 0000000..56c9a43
--- /dev/null
+++ b/src/client/kv_writer.c
@@ -0,0 +1,133 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/*
+ * Copyright (c) 2010 Andreas Hofmeister <andi@collax.com>
+ * 2010 Vincent Bernat <bernat@luffy.cx>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "writer.h"
+#include "../log.h"
+
+#define SEP '.'
+
+struct kv_writer_private {
+ FILE *fh;
+ char *prefix;
+};
+
+static void
+kv_start(struct writer *w, const char *tag, const char *descr)
+{
+ struct kv_writer_private *p = w->priv;
+ char *newprefix;
+ int s;
+
+ s = strlen(p->prefix) + 1 + strlen(tag);
+ if ((newprefix = malloc(s + 1)) == NULL) fatal(NULL, NULL);
+ if (strlen(p->prefix) > 0)
+ snprintf(newprefix, s + 1, "%s\1%s", p->prefix, tag);
+ else
+ snprintf(newprefix, s + 1, "%s", tag);
+ free(p->prefix);
+ p->prefix = newprefix;
+}
+
+static void
+kv_data(struct writer *w, const char *data)
+{
+ struct kv_writer_private *p = w->priv;
+ char *key = strdup(p->prefix);
+ char *value = data ? strdup(data) : NULL;
+ char *dot, *nl;
+ if (!key) fatal(NULL, NULL);
+ while ((dot = strchr(key, '\1')) != NULL)
+ *dot = SEP;
+ if (value) {
+ nl = value;
+ while ((nl = strchr(nl, '\n'))) {
+ *nl = ' ';
+ nl++;
+ }
+ }
+ fprintf(p->fh, "%s=%s\n", key, value ? value : "");
+ free(key);
+ free(value);
+}
+
+static void
+kv_end(struct writer *w)
+{
+ struct kv_writer_private *p = w->priv;
+ char *dot;
+
+ if ((dot = strrchr(p->prefix, '\1')) == NULL) {
+ p->prefix[0] = '\0';
+ fflush(p->fh);
+ } else
+ *dot = '\0';
+}
+
+static void
+kv_attr(struct writer *w, const char *tag, const char *descr, const char *value)
+{
+ if (!strcmp(tag, "name") || !strcmp(tag, "type")) {
+ /* Special case for name, replace the last prefix */
+ kv_end(w);
+ kv_start(w, value, NULL);
+ } else {
+ kv_start(w, tag, NULL);
+ kv_data(w, value);
+ kv_end(w);
+ }
+}
+
+static void
+kv_finish(struct writer *w)
+{
+ struct kv_writer_private *p = w->priv;
+
+ free(p->prefix);
+ free(w->priv);
+ w->priv = NULL;
+
+ free(w);
+}
+
+struct writer *
+kv_init(FILE *fh)
+{
+
+ struct writer *result;
+ struct kv_writer_private *priv;
+
+ if ((priv = malloc(sizeof(*priv))) == NULL) fatal(NULL, NULL);
+
+ priv->fh = fh;
+ priv->prefix = strdup("");
+
+ if ((result = malloc(sizeof(struct writer))) == NULL) fatal(NULL, NULL);
+
+ result->priv = priv;
+ result->start = kv_start;
+ result->attr = kv_attr;
+ result->data = kv_data;
+ result->end = kv_end;
+ result->finish = kv_finish;
+
+ return result;
+}
diff --git a/src/client/lldpcli.8.in b/src/client/lldpcli.8.in
new file mode 100644
index 0000000..3f85da3
--- /dev/null
+++ b/src/client/lldpcli.8.in
@@ -0,0 +1,1151 @@
+.\" Copyright (c) 2006 Pierre-Yves Ritschard <pyr@openbsd.org>
+.\" Copyright (c) 2008 Vincent Bernat <bernat@luffy.cx>
+.\"
+.\" Permission to use, copy, modify, and/or distribute this software for any
+.\" purpose with or without fee is hereby granted, provided that the above
+.\" copyright notice and this permission notice appear in all copies.
+.\"
+.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+.\"
+.Dd $Mdocdate: July 16 2008 $
+.Dt LLDPCLI 8
+.Os
+.Sh NAME
+.Nm lldpcli ,
+.Nm lldpctl
+.Nd control LLDP daemon
+.Sh SYNOPSIS
+.Nm
+.Op Fl dv
+.Op Fl u Ar socket
+.Op Fl f Ar format
+.Op Fl c Ar file
+.Op Ar command ...
+.Nm lldpctl
+.Op Fl dv
+.Op Fl u Ar socket
+.Op Fl f Ar format
+.Op Ar interfaces ...
+.Sh DESCRIPTION
+The
+.Nm
+program controls
+.Xr lldpd 8
+daemon.
+.Pp
+When no command is specified,
+.Nm
+will start an interactive shell which can be used to input arbitrary
+commands as if they were specified on the command line. This
+interactive shell should provide completion and history support.
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl d
+Enable more debugging information. This flag can be repeated.
+.It Fl u Ar socket
+Specify the Unix-domain socket used for communication with
+.Xr lldpd 8 .
+.It Fl v
+Show
+.Nm
+version. When repeated, show more build information.
+.It Fl f Ar format
+Choose the output format. Currently
+.Em plain ,
+.Em xml ,
+.Em json ,
+.Em json0
+and
+.Em keyvalue
+formats are available. The default is
+.Em plain .
+.Em json0
+is more verbose than
+.Em json
+but the structure of the JSON object is not affected by the number of
+interfaces or the number of neighbors. It is therefore easier to
+parse.
+.It Fl c Ar file
+Read the given configuration file. This option may be repeated several
+times. If a directory is provided, each file contained in it will be
+read if ending by
+.Li .conf .
+Order is alphabetical.
+.El
+.Pp
+When invoked as
+.Nm lldpctl ,
+.Nm
+will display detailed information about each neighbors on the
+specified interfaces or on all interfaces if none are specified. This
+command is mostly kept for backward compatibility with older versions.
+.Pp
+The following commands are supported by
+.Nm .
+When there is no ambiguity, the keywords can be abbreviated. For
+example,
+.Cd show neighbors ports eth0 summary
+and
+.Cd sh neigh p eth0 sum
+are the same command.
+.Bd -ragged -offset XX
+.Cd exit
+.Bd -ragged -offset XXXXXX
+Quit
+.Nm .
+.Ed
+
+.Cd help Op ...
+.Bd -ragged -offset XXXXXX
+Display general help or help about a command. Also, you can get help
+using the completion or by pressing the
+.Ic ?
+key. However, completion and inline help may be unavailable if
+.Nm
+was compiled without readline support but
+.Cd help
+command is always available.
+.Ed
+
+.Cd show neighbors
+.Op ports Ar ethX Op ,...
+.Op Cd details | summary
+.Op Cd hidden
+.Bd -ragged -offset XXXXXX
+Display information about each neighbor known by
+.Xr lldpd 8
+daemon. With
+.Cd summary ,
+only the name and the port description of each remote host will be
+displayed. On the other hand, with
+.Cd details ,
+all available information will be displayed, giving a verbose
+view. When using
+.Cd hidden ,
+also display remote ports hidden by the smart filter. When specifying
+one or several ports, the information displayed is limited to the
+given list of ports.
+.Ed
+
+.Cd show interfaces
+.Op ports Ar ethX Op ,...
+.Op Cd details | summary
+.Op Cd hidden
+.Bd -ragged -offset XXXXXX
+Display information about each local interface known by
+.Xr lldpd 8
+daemon. With
+.Cd summary ,
+only the name and the port description of each local interface will be
+displayed. On the other hand, with
+.Cd details ,
+all available information will be displayed, giving a verbose
+view. When using
+.Cd hidden ,
+also display local ports hidden by the smart filter. When specifying
+one or several ports, the information displayed is limited to the
+given list of ports.
+.Ed
+
+.Cd show chassis
+.Op Cd details | summary
+.Bd -ragged -offset XXXXXX
+Display information about local chassis. With
+.Cd summary ,
+most details are skipped. On the other hand, with
+.Cd details ,
+all available information will be displayed, giving a verbose
+view.
+.Ed
+
+.Cd watch
+.Op ports Ar ethX Op ,...
+.Op Cd details | summary
+.Op Cd hidden
+.Op Cd limit Ar X
+.Bd -ragged -offset XXXXXX
+Watch for any neighbor changes and report them as soon as they
+happen. When specifying ports, the changes are only reported when
+happening on the given ports.
+.Cd hidden , summary
+and
+.Cd details
+have the same meaning than previously described. If
+.Cd limit
+is specified,
+.Nm
+will exit after receiving the specified number of events.
+.Ed
+
+.Cd show configuration
+.Bd -ragged -offset XXXXXX
+Display global configuration of
+.Xr lldpd 8
+daemon.
+.Ed
+
+.Cd show statistics
+.Op ports Ar ethX Op ,...
+.Op Cd summary
+.Bd -ragged -offset XXXXXX
+Report LLDP-related statistics, like the number of LLDPDU transmitted,
+received, discarded or unrecognized. When specifying ports, only the
+statistics from the given port are reported. With
+.Cd summary
+the statistics of each port is summed.
+.Ed
+
+.Cd update
+.Bd -ragged -offset XXXXXX
+Make
+.Xr lldpd 8
+update its information and send new LLDP PDU on all interfaces.
+.Ed
+
+.Cd configure
+.Cd system hostname Ar name
+.Bd -ragged -offset XXXXXX
+Override system hostname with the provided value. By default, the
+system name is the FQDN found from the resolved value of
+.Ic uname -n .
+As a special value, use "." (dot) to use the short hostname instead of
+a FQDN.
+.Ed
+
+.Cd unconfigure
+.Cd system hostname
+.Bd -ragged -offset XXXXXX
+Do not override system hostname and restore the use of the node name.
+.Ed
+
+.Cd configure
+.Cd system description Ar description
+.Bd -ragged -offset XXXXXX
+Override chassis description with the provided value instead of using
+kernel name, node name, kernel version, build date and architecture.
+.Ed
+
+.Cd unconfigure
+.Cd system description
+.Bd -ragged -offset XXXXXX
+Do not override chassis description and use a value computed from node
+name, kernel name, kernel version, build date and architecture instead.
+.Ed
+
+.Cd configure
+.Cd system chassisid Ar description
+.Bd -ragged -offset XXXXXX
+Override chassis ID with the provided value instead of using MAC address
+from one interface or host name.
+.Ed
+
+.Cd unconfigure
+.Cd system chassisid
+.Bd -ragged -offset XXXXXX
+Do not override chassis ID and use a value computed from one of the interface
+MAC address (or host name if none is found).
+.Ed
+
+.Cd configure
+.Cd system platform Ar description
+.Bd -ragged -offset XXXXXX
+Override platform description with the provided value instead of using
+kernel name. This value is currently only used for CDP.
+.Ed
+
+.Cd unconfigure
+.Cd system platform
+.Bd -ragged -offset XXXXXX
+Do not override platform description and use the kernel name. This
+option undoes the previous one.
+.Ed
+
+.Cd configure
+.Cd system capabilities enabled Ar capabilities
+.Bd -ragged -offset XXXXXX
+Override system capabilities with the provided value instead of using
+kernel information. Several capabilities can be specified separated by
+commas. Only available capabilities can be enabled. Valid capabilities are:
+.Bl -tag -width "XXX." -compact -offset XX
+.It Sy other
+.It Sy repeater
+.It Sy bridge
+.It Sy wlan
+.It Sy router
+.It Sy telephone
+.It Sy docsis
+.It Sy station
+.El
+Here is an example of use:
+.D1 configure system capabilities enabled bridge,router
+.Pp
+.Ed
+
+.Cd unconfigure
+.Cd system capabilities enabled
+.Bd -ragged -offset XXXXXX
+Do not override capabilities and use the kernel information. This option
+undoes the previous one.
+.Ed
+
+.Cd configure
+.Cd system interface pattern Ar pattern
+.Bd -ragged -offset XXXXXX
+Specify which interface to listen and send LLDPDU to. Without this
+option,
+.Nm lldpd
+will use all available physical interfaces. This option can use
+wildcards. Several interfaces can be specified separated by commas.
+It is also possible to remove an interface by prefixing it with an
+exclamation mark. It is possible to allow an interface by
+prefixing it with two exclamation marks. An allowed interface beats
+a forbidden interfaces which beats a simple matched interface. For
+example, with
+.Em eth*,!eth1,!eth2
+.Nm lldpd
+will only use interfaces starting by
+.Em eth
+with the exception of
+.Em eth1
+and
+.Em eth2 .
+While with
+.Em *,!eth*,!!eth1
+.Nm
+will use all interfaces, except interfaces starting by
+.Em eth
+with the exception of
+.Em eth1 .
+When an exact match is found, it will circumvent some tests. For example, if
+.Em eth0.12
+is specified, it will be accepted even if this is a VLAN interface.
+.Ed
+
+.Cd unconfigure
+.Cd system interface pattern
+.Bd -ragged -offset XXXXXX
+Remove any previously configured interface pattern and use all
+physical interfaces. This option undoes the previous one.
+.Ed
+
+.Cd configure
+.Cd system interface permanent Ar pattern
+.Bd -ragged -offset XXXXXX
+Specify interfaces whose configuration is permanently kept by
+.Nm lldpd .
+By default,
+.Nm lldpd
+disregard any data about interfaces when they are removed from the
+system (statistics, custom configuration). This option allows one to
+specify a pattern similar to the interface pattern. If an interface
+disappear but matches the pattern, its data is kept in memory and
+reused if the interface reappear at some point. For example, on Linux,
+one could use the pattern
+.Em eth*,eno*,enp* ,
+which should match fixed interfaces on most systems.
+.Ed
+
+.Cd unconfigure
+.Cd system interface permanent
+.Bd -ragged -offset XXXXXX
+Remove any previously configured permanent interface pattern. Any
+interface removed from the system will be forgotten. This option
+undoes the previous one.
+.Ed
+
+.Cd configure
+.Cd system interface description
+.Bd -ragged -offset XXXXXX
+Some OS allows the user to set a description for an interface. Setting
+this option will enable
+.Nm lldpd
+to override this description with the name of the peer neighbor if one
+is found or with the number of neighbors found.
+.Ed
+
+.Cd unconfigure
+.Cd system interface description
+.Bd -ragged -offset XXXXXX
+Do not update interface description with the name of the peer
+neighbor. This option undoes the previous one.
+.Ed
+
+.Cd configure
+.Cd system interface promiscuous
+.Bd -ragged -offset XXXXXX
+Enable promiscuous mode on managed interfaces.
+.Pp
+When the interface is not managed any more (or when quitting
+.Nm lldpd ) ,
+the interface is left in promiscuous mode as it is difficult to know
+if someone else also put the interface in promiscuous mode.
+.Pp
+This option is known to be useful when the remote switch is a Cisco
+2960 and the local network card features VLAN hardware
+acceleration. In this case, you may not receive LLDP frames from the
+remote switch. The most plausible explanation for this is the frame is
+tagged with some VLAN (usually VLAN 1) and your network card is
+filtering VLAN. This is not the only available solution to work-around
+this problem. If you are concerned about performance issues, you can
+also tag the VLAN 1 on each interface instead.
+.Pp
+Currently, this option has no effect on anything else than Linux. On
+other OS, either disable VLAN acceleration, tag VLAN 1 or enable
+promiscuous mode manually on the interface.
+.Ed
+
+.Cd unconfigure
+.Cd system interface promiscuous
+.Bd -ragged -offset XXXXXX
+Do not set promiscuous mode on managed interfaces. This option does
+not disable promiscuous mode on interfaces already using this mode.
+.Ed
+
+.Cd configure
+.Cd system ip management pattern Ar pattern
+.Bd -ragged -offset XXXXXX
+Specify the management addresses of this system. As for interfaces
+(described above), this option can use wildcards and inversions.
+Without this option, the first IPv4 and the first IPv6 are used. If an
+exact IP address is provided, it is used as a management address
+without any check. If only negative patterns are provided, only one
+IPv4 and one IPv6 addresses are chosen. Otherwise, many of them can be
+selected. If you want to remove IPv6 addresses, you can use
+.Em !*:* .
+If an interface name is matched, the first IPv4 address and the first
+IPv6 address associated to this interface will be chosen.
+.Ed
+
+.Cd unconfigure
+.Cd system ip management pattern
+.Bd -ragged -offset XXXXXX
+Unset any specific pattern for matching management addresses. This
+option undoes the previous one.
+.Ed
+
+.Cd configure
+.Cd system bond-slave-src-mac-type Ar value
+.Bd -ragged -offset XXXXXX
+Set the type of src mac in lldp frames sent on bond slaves
+
+Valid types are:
+.Bl -tag -width "XXX." -compact -offset XX
+.It Sy real
+Slave real mac
+.It Sy zero
+All zero mac
+.It Sy fixed
+An arbitrary fixed value
+.Li ( 00:60:08:69:97:ef )
+.It Sy local
+Real mac with locally administered bit set. If the real mac already
+has the locally administered bit set, fallback to the fixed value.
+.El
+.Pp
+Default value for
+.Nm bond-slave-src-mac-type
+is
+.Nm local .
+Some switches may complain when using one of the two other possible
+values (either because
+.Li 00:00:00:00:00:00
+is not a valid MAC or because the MAC address is flapping from one
+port to another). Using
+.Sy local
+might lead to a duplicate MAC address on the network (but this is
+quite unlikely).
+.Ed
+
+.Cd configure
+.Cd system max-neighbors Ar neighbors
+.Bd -ragged -offset XXXXXX
+Change the maximum number of neighbors accepted (for each protocol) on
+an interface. This is a global value. The default is 32. This setting
+only applies to future neighbors.
+.Ed
+
+.Cd configure
+.Cd lldp agent-type
+.Cd nearest-bridge | nearest-non-tpmr-bridge | nearest-customer-bridge
+.Bd -ragged -offset XXXXXX
+The destination MAC address used to send LLDPDU allows an agent to
+control the propagation of LLDPDUs. By default, the
+.Li 01:80:c2:00:00:0e
+MAC address is used and limit the propagation of the LLDPDU to the
+nearest bridge
+.Cd ( nearest-bridge ) .
+To instruct
+.Nm lldpd
+to use the
+.Li 01:80:c2:00:00:03
+MAC address instead, use
+.Cd nearest-nontpmr-bridge
+instead.
+To use the
+.Li 01:80:c2:00:00:00
+MAC address instead, use
+.Cd nearest-customer-bridge
+instead.
+.Ed
+
+.Cd configure
+.Cd lldp capabilities-advertisements
+.Pp
+.Cd unconfigure
+.Cd lldp capabilities-advertisements
+.Bd -ragged -offset XXXXXX
+Enable or disable advertisements of the chassis capabilities TLV.
+.Ed
+
+.Cd configure
+.Cd lldp management-addresses-advertisements
+.Pp
+.Cd unconfigure
+.Cd lldp management-addresses-advertisements
+.Bd -ragged -offset XXXXXX
+Enable or disable advertisements of the management address TLV.
+.Ed
+
+.Cd configure
+.Cd lldp portidsubtype
+.Cd ifname | macaddress
+.Pp
+.Cd configure
+.Op ports Ar ethX Op ,...
+.Cd lldp portidsubtype
+.Cd local Ar value
+.Bd -ragged -offset XXXXXX
+Force port ID subtype. By default,
+.Nm lldpd
+will use the MAC address as port identifier and the interface name as
+port description, unless the interface has an alias. In this case, the
+interface name will be used as port identifier and the description
+will be the interface alias. With this command, you can force the port
+identifier to be the interface name (with
+.Cd ifname ) ,
+the MAC address (with
+.Cd macaddress )
+or a local value (with
+.Cd value ) .
+In the latest case, the local value should be provided.
+.Ed
+
+.Cd configure
+.Op ports Ar ethX Op ,...
+.Cd lldp portdescription
+.Cd Ar description
+.Bd -ragged -offset XXXXXX
+Force port description to the provided string.
+.Ed
+
+.Cd configure
+.Cd lldp tx-interval Ar interval
+.Bd -ragged -offset XXXXXX
+Change transmit delay to the specified value in seconds. The transmit
+delay is the delay between two transmissions of LLDP PDU. The default
+value is 30 seconds. Note:
+.Nm lldpd
+also starts another system based refresh timer on each port to detect
+changes such as a hostname. This is the value of the tx-interval
+multiplied by 20.
+.Pp
+You can specify an
+.Cd interval
+value in milliseconds by appending a "ms" suffix to the figure (e.g.
+"configure lldp tx-interval 1500ms" is 1.5s, not 1500s). In this case
+the TTL for received and sent LLDP frames is rounded up to the next
+second. Note: the effective interval can be limited by the operating
+system capabilities and CPU speed.
+.Ed
+
+.Cd configure
+.Cd lldp tx-hold Ar hold
+.Bd -ragged -offset XXXXXX
+Change transmit hold value to the specified value. This value is used
+to compute the TTL of transmitted packets which is the product of this
+value and of the transmit delay. The default value is 4 and therefore
+the default TTL is 120 seconds.
+.Ed
+
+.Cd configure
+.Op ports Ar ethX Op ,...
+.Cd lldp
+.Cd status Ar rx-and-tx | rx-only | tx-only | disabled
+.Bd -ragged -offset XXXXXX
+Configure the administrative status of the given port. By default, all
+ports are configured to be in
+.Ar rx-and-tx
+mode. This means they can receive and transmit LLDP frames (as well as
+other protocols if needed). In
+.Ar rx-only
+mode, they won't emit any frames and in
+.Ar tx-only
+mode, they won't receive any frames. In
+.Ar disabled
+mode, no frame will be sent and any incoming frame will be
+discarded. This setting does not override the operational mode of the
+main daemon. If it is configured in receive-only mode (with the
+.Fl r
+flag), setting any transmit mode won't have any effect.
+.Ed
+
+.Cd configure
+.Op ports Ar ethX Op ,...
+.Cd lldp
+.Cd vlan-tx Ar vlan_id
+.Op Cd prio Ar priority Op Cd dei Ar dei
+.Bd -ragged -offset XXXXXX
+Configure the given port to send LLDP frames over a specified VLAN. With VLAN Identifier (VID) as
+.Ar vlan_id ,
+Priority Code Point (PCP) as
+.Ar priority ,
+and Drop Eligible Indicator (DEI) as
+.Ar dei .
+.Nm lldpd
+accepts LLDP frames on all VLANs.
+.Ed
+
+.Cd configure
+.Op ports Ar ethX Op ,...
+.Cd lldp custom-tlv
+.Op Cd add | replace
+.Cd oui Ar oui
+.Cd subtype Ar subtype
+.Op Cd oui-info Ar content
+.Bd -ragged -offset XXXXXX
+Emit a custom TLV for OUI
+.Ar oui ,
+with subtype
+.Ar subtype
+and optionally with the bytes specified in
+.Ar content .
+Both
+.Ar oui
+and
+.Ar content
+should be a comma-separated list of bytes in hex format.
+.Ar oui
+must be exactly 3-byte long.
+If
+.Ar add
+is specified then the TLV will be added. This is the default action.
+If
+.Ar replace
+is specified then all TLVs with the same
+.Ar oui
+and
+.Ar subtype
+will be replaced.
+
+.Ed
+
+.Cd unconfigure
+.Op ports Ar ethX Op ,...
+.Cd lldp custom-tlv
+.Op Cd oui Ar oui
+.Op Cd subtype Ar subtype
+.Bd -ragged -offset XXXXXX
+When no oui is specified, remove all previously configured custom TLV.
+When OUI
+.Ar oui
+and subtype
+.Ar subtype
+is specified, remove specific instances of custom TLV.
+.Ed
+
+.Cd configure med fast-start
+.Cd enable | tx-interval Ar interval
+.Bd -ragged -offset XXXXXX
+Configure LLDP-MED fast start mechanism. When a new LLDP-MED-enabled
+neighbor is detected, fast start allows
+.Nm lldpd
+to shorten the interval between two LLDPDU.
+.Cd enable
+should enable LLDP-MED fast start while
+.Cd tx-interval
+specifies the interval between two LLDPDU in seconds. The default
+interval is 1 second. Once 4 LLDPDU have been sent, the fast start
+mechanism is disabled until a new neighbor is detected.
+.Ed
+
+.Cd unconfigure med fast-start
+.Bd -ragged -offset XXXXXX
+Disable LLDP-MED fast start mechanism.
+.Ed
+
+.Cd configure
+.Op ports Ar ethX Op ,...
+.Cd med location coordinate
+.Cd latitude Ar latitude
+.Cd longitude Ar longitude
+.Cd altitude Ar altitude Ar unit
+.Cd datum Ar datum
+.Bd -ragged -offset XXXXXX
+Advertise a coordinate based location on the given ports (or on all
+ports if no port is specified). The format of
+.Ar latitude
+is a decimal floating point number followed either by
+.Em N
+or
+.Em S .
+The format of
+.Ar longitude
+is a decimal floating point number followed either by
+.Em E
+or
+.Em W .
+.Ar altitude
+is a decimal floating point number followed either by
+.Em m
+when expressed in meters or
+.Em f
+when expressed in floors. A space is expected between the floating
+point number and the unit.
+.Ar datum
+is one of those values:
+.Bl -bullet -compact -offset XXXXXXXX
+.It
+WGS84
+.It
+NAD83
+.It
+NAD83/MLLW
+.El
+.Pp
+A valid use of this command is:
+.D1 configure ports eth0 med location coordinate latitude 48.85667N longitude 2.2014E altitude 117.47 m datum WGS84
+.Ed
+
+.Cd configure
+.Op ports Ar ethX Op ,...
+.Cd med location address
+.Cd country Ar country
+.Cd Op Ar type value Op ...
+.Bd -ragged -offset XXXXXX
+Advertise a civic address on the given ports (or on all ports if no
+port is specified).
+.Ar country
+is the two-letter code representing the country. The remaining
+arguments should be paired to form the address. The first member of
+each pair indicates the type of the second member which is a free-form
+text. Here is the list of valid types:
+.Bl -bullet -compact -offset XXXXXXXX
+.It
+language
+.It
+country-subdivision
+.It
+county
+.It
+city
+.It
+city-division
+.It
+block
+.It
+street
+.It
+direction
+.It
+trailing-street-suffix
+.It
+street-suffix
+.It
+number
+.It
+number-suffix
+.It
+landmark
+.It
+additional
+.It
+name
+.It
+zip
+.It
+building
+.It
+unit
+.It
+floor
+.It
+room
+.It
+place-type
+.It
+script
+.El
+.Pp
+A valid use of this command is:
+.D1 configure ports eth1 med location address country US street Qo Commercial Road Qc city Qo Roseville Qc
+.Ed
+
+.Cd configure
+.Op ports Ar ethX Op ,...
+.Cd med location elin
+.Ar number
+.Bd -ragged -offset XXXXXX
+Advertise the availability of an ELIN number. This is used for setting
+up emergency call. If the provided number is too small, it will be
+padded with 0. Here is an example of use:
+.D1 configure ports eth2 med location elin 911
+.Ed
+
+.Cd configure
+.Op ports Ar ethX Op ,...
+.Cd med policy
+.Cd application Ar application
+.Op Cd unknown
+.Op Cd tagged
+.Op Cd vlan Ar vlan
+.Op Cd priority Ar priority
+.Op Cd dscp Ar dscp
+.Bd -ragged -offset XXXXXX
+Advertise a specific network policy for the given ports (or for all
+ports if no port was provided). Only the application type is
+mandatory.
+.Ar application
+should be one of the following values:
+.Bl -bullet -compact -offset XXXXXXXX
+.It
+voice
+.It
+voice-signaling
+.It
+guest-voice
+.It
+guest-voice-signaling
+.It
+softphone-voice
+.It
+video-conferencing
+.It
+streaming-video
+.It
+video-signaling
+.El
+.Pp
+The
+.Cd unknown
+flag tells that the network policy for the specified application type
+is required by the device but is currently unknown. This is used by
+Endpoint Devices, not by Network Connectivity Devices. If not
+specified, the network policy for the given application type is
+defined.
+.Pp
+When a VLAN is specified with
+.Ar vlan
+tells which 802.1q VLAN ID has to be advertised for the network
+policy. A valid value is between 1 and 4094.
+.Cd tagged
+tells the VLAN should be tagged for the specified application type.
+.Pp
+.Ar priority
+allows one to specify IEEE 802.1d / IEEE 802.1p Layer 2 Priority, also
+known as Class of Service (CoS), to be used for the specified
+application type. This field is usually ignored if no VLAN is
+specified. The names match 802.1D-2004 standard (table G-2). Some more
+recent standards may use different labels. Only the numeric values
+should be relied upon. The accepted labels are:
+.Bl -tag -width "X." -compact -offset XXXX
+.It Sy 1
+background
+.It Sy 0
+best-effort
+.It Sy 2
+excellent-effort
+.It Sy 3
+critical-applications
+.It Sy 4
+video
+.It Sy 5
+voice
+.It Sy 6
+internetwork-control
+.It Sy 7
+network-control
+.El
+.Pp
+.Ar dscp
+represents the DSCP value to be advertised for the given network
+policy. DiffServ/Differentiated Services Code Point (DSCP) value as
+defined in IETF RFC 2474 for the specified application type. Value: 0
+(default per RFC 2475) through 63. Note: The class selector DSCP
+values are backwards compatible for devices that only support the old
+IP precedence Type of Service (ToS) format. (See the RFCs for what
+these values mean)
+.Pp
+A valid use of this command is:
+.D1 configure med policy application voice vlan 500 priority voice dscp 46
+.Ed
+
+.Cd configure
+.Cd inventory hardware-revision Ar value
+.Bd -ragged -offset XXXXXX
+Override hardware-revision with the provided value. By default, the
+hardware-revision is fetched from /sys/class/dmi
+.Ed
+
+.Cd unconfigure
+.Cd inventory hardware-revision
+.Bd -ragged -offset XXXXXX
+Do not override hardware-revision and restore the use of the /sys/class/dmi value.
+.Ed
+
+.Cd configure
+.Cd inventory software-revision Ar value
+.Bd -ragged -offset XXXXXX
+Override software-revision with the provided value. By default, the
+software-revision is fetched from uname
+.Ed
+
+.Cd unconfigure
+.Cd inventory software-revision
+.Bd -ragged -offset XXXXXX
+Do not override software-revision and restore the use of the uname value.
+.Ed
+
+.Cd configure
+.Cd inventory firmware-revision Ar value
+.Bd -ragged -offset XXXXXX
+Override firmware-revision with the provided value. By default, the
+firmware-revision is fetched from /sys/class/dmi
+.Ed
+
+.Cd unconfigure
+.Cd inventory firmware-revision
+.Bd -ragged -offset XXXXXX
+Do not override firmware-revision and restore the use of the /sys/class/dmi value.
+.Ed
+
+.Cd configure
+.Cd inventory serial-number Ar value
+.Bd -ragged -offset XXXXXX
+Override serial-number with the provided value. By default, the
+serial-number is fetched from /sys/class/dmi
+.Ed
+
+.Cd unconfigure
+.Cd inventory serial-number
+.Bd -ragged -offset XXXXXX
+Do not override serial-number and restore the use of the /sys/class/dmi value.
+.Ed
+
+.Cd configure
+.Cd inventory manufacturer Ar value
+.Bd -ragged -offset XXXXXX
+Override manufacturer with the provided value. By default, the
+manufacturer is fetched from /sys/class/dmi
+.Ed
+
+.Cd unconfigure
+.Cd inventory manufacturer
+.Bd -ragged -offset XXXXXX
+Do not override manufacturer and restore the use of the /sys/class/dmi value.
+.Ed
+
+.Cd configure
+.Cd inventory model Ar value
+.Bd -ragged -offset XXXXXX
+Override model with the provided value. By default, the
+model is fetched from /sys/class/dmi
+.Ed
+
+.Cd unconfigure
+.Cd inventory model
+.Bd -ragged -offset XXXXXX
+Do not override model and restore the use of the /sys/class/dmi value.
+.Ed
+
+.Cd configure
+.Cd inventory asset Ar value
+.Bd -ragged -offset XXXXXX
+Override asset with the provided value. By default, the
+asset is fetched from /sys/class/dmi
+.Ed
+
+.Cd unconfigure
+.Cd inventory asset
+.Bd -ragged -offset XXXXXX
+Do not override asset and restore the use of the /sys/class/dmi value.
+.Ed
+
+.Cd configure
+.Op ports Ar ethX Op ,...
+.Cd med power pse | pd
+.Cd source Ar source
+.Cd priority Ar priority
+.Cd value Ar value
+.Bd -ragged -offset XXXXXX
+Advertise the LLDP-MED POE-MDI TLV for the given ports or for all
+interfaces if no port is provided. One can act as a PD (power
+consumer) or a PSE (power provider). No check is done on the validity
+of the parameters while LLDP-MED requires some restrictions:
+.Bl -bullet
+.It
+PD shall never request more power than physical 802.3af class.
+.It
+PD shall never draw more than the maximum power advertised by PSE.
+.It
+PSE shall not reduce power allocated to PD when this power is in use.
+.It
+PSE may request reduced power using conservation mode
+.It
+Being PSE or PD is a global parameter, not a per-port parameter.
+.Nm
+does not enforce this: a port can be set as PD or PSE. LLDP-MED also
+requires for a PSE to only have one power source (primary or
+backup). Again,
+.Nm
+does not enforce this. Each port can have its own power source. The
+same applies for PD and power priority. LLDP-MED MIB does not allow
+this kind of representation.
+.El
+.Pp
+Valid types are:
+.Bl -tag -width "XXX." -compact -offset XX
+.It Sy pse
+Power Sourcing Entity (power provider)
+.It Sy pd
+Power Device (power consumer)
+.El
+.Pp
+Valid sources are:
+.Bl -tag -width "XXXXXXX" -compact -offset XX
+.It Sy unknown
+Unknown
+.It Sy primary
+For PSE, the power source is the primary power source.
+.It Sy backup
+For PSE, the power source is the backup power source or a power
+conservation mode is asked (the PSE may be running on UPS for
+example).
+.It Sy pse
+For PD, the power source is the PSE.
+.It Sy local
+For PD, the power source is a local source.
+.It Sy both
+For PD, the power source is both the PSE and a local source.
+.El
+.Pp
+Valid priorities are:
+.Bl -tag -width "XXXXXXXXX" -compact -offset XX
+.It Sy unknown
+Unknown priority
+.It Sy critical
+Critical
+.It Sy high
+High
+.It Sy low
+Low
+.El
+.Pp
+.Ar value
+should be the total power in milliwatts required by the PD device or
+available by the PSE device.
+.Pp
+Here is an example of use:
+.D1 configure med power pd source pse priority high value 5000
+.Ed
+
+.Cd configure
+.Op ports Ar ethX Op ,...
+.Cd dot3 power pse | pd
+.Op Cd supported
+.Op Cd enabled
+.Op Cd paircontrol
+.Cd powerpairs Ar powerpairs
+.Op Cd class Ar class
+.Op Cd type Ar type Cd source Ar source Cd priority Ar priority Cd requested Ar requested Cd allocated Ar allocated
+.Bd -ragged -offset XXXXXX
+Advertise Dot3 POE-MDI TLV for the given port or for all ports if none
+was provided. One can act as a PD (power consumer) or a PSE (power
+provider). This configuration is distinct of the configuration of the
+transmission of the LLDP-MED POE-MDI TLV but the user should ensure
+the coherency of those two configurations if they are used together.
+.Pp
+.Ar supported
+means that MDI power is supported on the given port while
+.Ar enabled
+means that MDI power is enabled.
+.Ar paircontrol
+is used to indicate if pair selection can be controlled. Valid values
+for
+.Ar powerpairs
+are:
+.Bl -tag -width "XXXXXX" -compact -offset XX
+.It Sy signal
+The signal pairs only are in use.
+.It Sy spare
+The spare pairs only are in use.
+.El
+.Pp
+When specified,
+.Ar class
+is a number between 0 and 4.
+.Pp
+The remaining parameters are in conformance with 802.3at and are optional.
+.Ar type
+should be either 1 or 2, indicating which if the device conforms to
+802.3at type 1 or 802.3at type 2. Values of
+.Ar source
+and
+.Ar priority
+are the same as for LLDP-MED POE-MDI TLV.
+.Ar requested
+and
+.Ar allocated
+are expressed in milliwats.
+.Pp
+Here are two valid uses of this command:
+.D1 configure ports eth3 dot3 power pse supported enabled paircontrol powerpairs spare class class-3
+.D1 configure dot3 power pd supported enabled powerpairs spare class class-3 type 1 source pse priority low requested 10000 allocated 15000
+.Ed
+
+.Cd pause
+.Bd -ragged -offset XXXXXX
+Pause
+.Nm lldpd
+operations.
+.Nm lldpd
+will not send any more frames or receive ones. This can be undone with
+.Cd resume
+command. This only works interactively as lldpd asks lldpcli to
+unpause after reading the configuration file.
+.Ed
+
+.Cd resume
+.Bd -ragged -offset XXXXXX
+Resume
+.Nm lldpd
+operations.
+.Nm lldpd
+will start to send and receive frames. This command is issued
+internally after processing configuration but can be used at any time
+if a manual
+.Cd pause
+command is issued.
+.Ed
+
+.Ed
+.Sh FILES
+.Bl -tag -width "@LLDPD_CTL_SOCKET@XX" -compact
+.It @LLDPD_CTL_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/src/client/lldpcli.c b/src/client/lldpcli.c
new file mode 100644
index 0000000..8999ea7
--- /dev/null
+++ b/src/client/lldpcli.c
@@ -0,0 +1,611 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/*
+ * Copyright (c) 2008 Vincent Bernat <bernat@luffy.cx>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <time.h>
+#include <errno.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <arpa/inet.h>
+#include <libgen.h>
+#include <dirent.h>
+#include <signal.h>
+#include <sys/queue.h>
+
+#include "client.h"
+
+#ifdef HAVE___PROGNAME
+extern const char *__progname;
+#else
+# define __progname "lldpcli"
+#endif
+
+/* Global for completion */
+static struct cmd_node *root = NULL;
+const char *ctlname = NULL;
+
+static int
+is_lldpctl(const char *name)
+{
+ static int last_result = -1;
+ if (last_result == -1 && name) {
+ char *basec = strdup(name);
+ if (!basec) return 0;
+ char *bname = basename(basec);
+ last_result = (!strcmp(bname, "lldpctl"));
+ free(basec);
+ }
+ return (last_result == -1) ? 0 : last_result;
+}
+
+static void
+usage()
+{
+ fprintf(stderr, "Usage: %s [OPTIONS ...] [COMMAND ...]\n", __progname);
+ fprintf(stderr, "Version: %s\n", PACKAGE_STRING);
+
+ fprintf(stderr, "\n");
+
+ fprintf(stderr, "-d Enable more debugging information.\n");
+ fprintf(stderr,
+ "-u socket Specify the Unix-domain socket used for communication with lldpd(8).\n");
+ fprintf(stderr,
+ "-f format Choose output format (plain, keyvalue, json, json0"
+#if defined USE_XML
+ ", xml"
+#endif
+ ").\n");
+ if (!is_lldpctl(NULL))
+ fprintf(stderr, "-c conf Read the provided configuration file.\n");
+
+ fprintf(stderr, "\n");
+
+ fprintf(stderr, "See manual page lldpcli(8) for more information\n");
+ exit(1);
+}
+
+static int
+is_privileged()
+{
+ /* Check we can access the control socket with read/write
+ * privileges. The `access()` function uses the real UID and real GID,
+ * therefore we don't have to mangle with our identity. */
+ return (ctlname && access(ctlname, R_OK | W_OK) == 0);
+}
+
+static const char *
+prompt()
+{
+#define CESC "\033"
+ int privileged = is_privileged();
+ if (isatty(STDIN_FILENO)) {
+ if (privileged) return "[lldpcli] # ";
+ return "[lldpcli] $ ";
+ }
+ return "";
+}
+
+static int must_exit = 0;
+/**
+ * Exit the interpreter.
+ */
+static int
+cmd_exit(struct lldpctl_conn_t *conn, struct writer *w, struct cmd_env *env,
+ const void *arg)
+{
+ log_info("lldpctl", "quit lldpcli");
+ must_exit = 1;
+ return 1;
+}
+
+/**
+ * Send an "update" request.
+ */
+static int
+cmd_update(struct lldpctl_conn_t *conn, struct writer *w, struct cmd_env *env,
+ const void *arg)
+{
+ log_info("lldpctl", "ask for global update");
+
+ lldpctl_atom_t *config = lldpctl_get_configuration(conn);
+ if (config == NULL) {
+ log_warnx("lldpctl", "unable to get configuration from lldpd. %s",
+ lldpctl_last_strerror(conn));
+ return 0;
+ }
+ if (lldpctl_atom_set_int(config, lldpctl_k_config_tx_interval, -1) == NULL) {
+ log_warnx("lldpctl",
+ "unable to ask lldpd for immediate retransmission. %s",
+ lldpctl_last_strerror(conn));
+ lldpctl_atom_dec_ref(config);
+ return 0;
+ }
+ log_info("lldpctl", "immediate retransmission requested successfully");
+ lldpctl_atom_dec_ref(config);
+ return 1;
+}
+
+/**
+ * Pause or resume execution of lldpd.
+ *
+ * @param conn The connection to lldpd.
+ * @param pause 1 if we want to pause lldpd, 0 otherwise
+ * @return 1 on success, 0 on error
+ */
+static int
+cmd_pause_resume(lldpctl_conn_t *conn, int pause)
+{
+ lldpctl_atom_t *config = lldpctl_get_configuration(conn);
+ if (config == NULL) {
+ log_warnx("lldpctl", "unable to get configuration from lldpd. %s",
+ lldpctl_last_strerror(conn));
+ return 0;
+ }
+ if (lldpctl_atom_get_int(config, lldpctl_k_config_paused) == pause) {
+ log_debug("lldpctl", "lldpd is already %s",
+ pause ? "paused" : "resumed");
+ lldpctl_atom_dec_ref(config);
+ return 1;
+ }
+ if (lldpctl_atom_set_int(config, lldpctl_k_config_paused, pause) == NULL) {
+ log_warnx("lldpctl", "unable to ask lldpd to %s operations. %s",
+ pause ? "pause" : "resume", lldpctl_last_strerror(conn));
+ lldpctl_atom_dec_ref(config);
+ return 0;
+ }
+ log_info("lldpctl", "lldpd should %s operations", pause ? "pause" : "resume");
+ lldpctl_atom_dec_ref(config);
+ return 1;
+}
+static int
+cmd_pause(struct lldpctl_conn_t *conn, struct writer *w, struct cmd_env *env,
+ const void *arg)
+{
+ (void)w;
+ (void)env;
+ return cmd_pause_resume(conn, 1);
+}
+static int
+cmd_resume(struct lldpctl_conn_t *conn, struct writer *w, struct cmd_env *env,
+ const void *arg)
+{
+ (void)w;
+ (void)env;
+ return cmd_pause_resume(conn, 0);
+}
+
+#ifdef HAVE_LIBREADLINE
+static int
+_cmd_complete(int all)
+{
+ char **argv = NULL;
+ int argc = 0;
+ int rc = 1;
+ size_t len = strlen(rl_line_buffer);
+ char *line = malloc(len + 2);
+ if (!line) return -1;
+ strlcpy(line, rl_line_buffer, len + 2);
+ line[rl_point] = 2; /* empty character, will force a word */
+ line[rl_point + 1] = 0;
+
+ if (tokenize_line(line, &argc, &argv) != 0) goto end;
+
+ char *compl =
+ commands_complete(root, argc, (const char **)argv, all, is_privileged());
+ if (compl &&strlen(argv[argc - 1]) < strlen(compl )) {
+ if (rl_insert_text(compl +strlen(argv[argc - 1])) < 0) {
+ free(compl );
+ goto end;
+ }
+ free(compl );
+ rc = 0;
+ goto end;
+ }
+ /* No completion or several completion available. */
+ free(compl );
+ fprintf(stderr, "\n");
+ rl_forced_update_display();
+ rc = 0;
+end:
+ free(line);
+ tokenize_free(argc, argv);
+ return rc;
+}
+
+static int
+cmd_complete(int count, int ch)
+{
+ return _cmd_complete(0);
+}
+
+static int
+cmd_help(int count, int ch)
+{
+ return _cmd_complete(1);
+}
+#else
+static char *
+readline(const char *p)
+{
+ static char line[2048];
+ fprintf(stderr, "%s", p);
+ fflush(stderr);
+ if (fgets(line, sizeof(line) - 2, stdin) == NULL) return NULL;
+ return strdup(line);
+}
+#endif
+
+/**
+ * Execute a tokenized command and display its output.
+ *
+ * @param conn The connection to lldpd.
+ * @param fmt Output format.
+ * @param argc Number of arguments.
+ * @param argv Array of arguments.
+ * @return 0 if an error occurred, 1 otherwise
+ */
+static int
+cmd_exec(lldpctl_conn_t *conn, const char *fmt, int argc, const char **argv)
+{
+ /* Init output formatter */
+ struct writer *w;
+
+ if (strcmp(fmt, "plain") == 0)
+ w = txt_init(stdout);
+ else if (strcmp(fmt, "keyvalue") == 0)
+ w = kv_init(stdout);
+ else if (strcmp(fmt, "json") == 0)
+ w = json_init(stdout, 1);
+ else if (strcmp(fmt, "json0") == 0)
+ w = json_init(stdout, 0);
+#ifdef USE_XML
+ else if (strcmp(fmt, "xml") == 0)
+ w = xml_init(stdout);
+#endif
+ else {
+ log_warnx("lldpctl", "unknown output format \"%s\"", fmt);
+ w = txt_init(stdout);
+ }
+
+ /* Execute command */
+ int rc = commands_execute(conn, w, root, argc, argv, is_privileged());
+ if (rc != 0) {
+ log_info("lldpctl", "an error occurred while executing last command");
+ w->finish(w);
+ return 0;
+ }
+ w->finish(w);
+ return 1;
+}
+
+/**
+ * Execute a command line and display its output.
+ *
+ * @param conn The connection to lldpd.
+ * @param fmt Output format.
+ * @param line Line to execute.
+ * @return -1 if an error occurred, 0 if nothing was executed. 1 otherwise.
+ */
+static int
+parse_and_exec(lldpctl_conn_t *conn, const char *fmt, const char *line)
+{
+ int cargc = 0;
+ char **cargv = NULL;
+ int n;
+ log_debug("lldpctl", "tokenize command line");
+ n = tokenize_line(line, &cargc, &cargv);
+ switch (n) {
+ case -1:
+ log_warnx("lldpctl", "internal error while tokenizing");
+ return -1;
+ case 1:
+ log_warnx("lldpctl", "unmatched quotes");
+ return -1;
+ }
+ if (cargc != 0) n = cmd_exec(conn, fmt, cargc, (const char **)cargv);
+ tokenize_free(cargc, cargv);
+ return (cargc == 0) ? 0 : (n == 0) ? -1 : 1;
+}
+
+static struct cmd_node *
+register_commands()
+{
+ root = commands_root();
+ register_commands_show(root);
+ register_commands_watch(root);
+ commands_new(commands_privileged(commands_new(root, "update",
+ "Update information and send LLDPU on all ports", NULL, NULL,
+ NULL)),
+ NEWLINE, "Update information and send LLDPU on all ports", NULL, cmd_update,
+ NULL);
+ register_commands_configure(root);
+ commands_hidden(commands_new(root, "complete",
+ "Get possible completions from a given command", NULL,
+ cmd_store_env_and_pop, "complete"));
+ commands_new(root, "help", "Get help on a possible command", NULL,
+ cmd_store_env_and_pop, "help");
+ commands_new(commands_new(root, "pause", "Pause lldpd operations", NULL, NULL,
+ NULL),
+ NEWLINE, "Pause lldpd operations", NULL, cmd_pause, NULL);
+ commands_new(commands_new(root, "resume", "Resume lldpd operations", NULL, NULL,
+ NULL),
+ NEWLINE, "Resume lldpd operations", NULL, cmd_resume, NULL);
+ commands_new(commands_new(root, "exit", "Exit interpreter", NULL, NULL, NULL),
+ NEWLINE, "Exit interpreter", NULL, cmd_exit, NULL);
+ return root;
+}
+
+struct input {
+ TAILQ_ENTRY(input) next;
+ char *name;
+};
+TAILQ_HEAD(inputs, input);
+static int
+filter(const struct dirent *dir)
+{
+ if (strlen(dir->d_name) < 5) return 0;
+ if (strcmp(dir->d_name + strlen(dir->d_name) - 5, ".conf")) return 0;
+ return 1;
+}
+
+/**
+ * Append a new input file/directory to the list of inputs.
+ *
+ * @param arg Directory or file name to add.
+ * @param inputs List of inputs
+ * @param acceptdir 1 if we accept a directory, 0 otherwise
+ */
+static void
+input_append(const char *arg, struct inputs *inputs, int acceptdir, int warn)
+{
+ struct stat statbuf;
+ if (stat(arg, &statbuf) == -1) {
+ if (warn) {
+ log_warn("lldpctl",
+ "cannot find configuration file/directory %s", arg);
+ } else {
+ log_debug("lldpctl",
+ "cannot find configuration file/directory %s", arg);
+ }
+ return;
+ }
+
+ if (!S_ISDIR(statbuf.st_mode)) {
+ struct input *input = malloc(sizeof(struct input));
+ if (!input) {
+ log_warn("lldpctl", "not enough memory to process %s", arg);
+ return;
+ }
+ log_debug("lldpctl", "input: %s", arg);
+ input->name = strdup(arg);
+ TAILQ_INSERT_TAIL(inputs, input, next);
+ return;
+ }
+ if (!acceptdir) {
+ log_debug("lldpctl", "skip directory %s", arg);
+ return;
+ }
+
+ struct dirent **namelist = NULL;
+ int n = scandir(arg, &namelist, filter, alphasort);
+ if (n < 0) {
+ log_warnx("lldpctl", "unable to read directory %s", arg);
+ return;
+ }
+ for (int i = 0; i < n; i++) {
+ char *fullname;
+ if (asprintf(&fullname, "%s/%s", arg, namelist[i]->d_name) != -1) {
+ input_append(fullname, inputs, 0, 1);
+ free(fullname);
+ }
+ free(namelist[i]);
+ }
+ free(namelist);
+}
+
+int
+main(int argc, char *argv[])
+{
+ int ch, debug = 0, use_syslog = 0, rc = EXIT_FAILURE;
+ const char *fmt = "plain";
+ lldpctl_conn_t *conn = NULL;
+ const char *options = is_lldpctl(argv[0]) ? "hdvf:u:" : "hdsvf:c:C:u:";
+ lldpctl_atom_t *configuration;
+
+ int gotinputs = 0, version = 0;
+ struct inputs inputs;
+ TAILQ_INIT(&inputs);
+
+ ctlname = lldpctl_get_default_transport();
+
+ signal(SIGHUP, SIG_IGN);
+
+ /* Get and parse command line options */
+ optind = 1;
+ while ((ch = getopt(argc, argv, options)) != -1) {
+ switch (ch) {
+ case 'd':
+ if (use_syslog)
+ use_syslog = 0;
+ else
+ debug++;
+ break;
+ case 's':
+ if (debug == 0)
+ use_syslog = 1;
+ else
+ debug--;
+ break;
+ case 'h':
+ usage();
+ break;
+ case 'u':
+ ctlname = optarg;
+ break;
+ case 'v':
+ version++;
+ break;
+ case 'f':
+ fmt = optarg;
+ break;
+ case 'C':
+ case 'c':
+ if (!gotinputs) {
+ log_init(use_syslog, debug, __progname);
+ lldpctl_log_level(debug + 1);
+ gotinputs = 1;
+ }
+ input_append(optarg, &inputs, 1, ch == 'c');
+ break;
+ default:
+ usage();
+ }
+ }
+
+ if (version) {
+ version_display(stdout, "lldpcli", version > 1);
+ exit(0);
+ }
+
+ if (!gotinputs) {
+ log_init(use_syslog, debug, __progname);
+ lldpctl_log_level(debug + 1);
+ }
+
+ /* Disable SIGPIPE */
+ signal(SIGPIPE, SIG_IGN);
+
+ /* Register commands */
+ root = register_commands();
+
+ /* Make a connection */
+ log_debug("lldpctl", "connect to lldpd");
+ conn = lldpctl_new_name(ctlname, NULL, NULL, NULL);
+ if (conn == NULL) goto end;
+
+ /* Check we have a working connection */
+ if ((configuration = lldpctl_get_configuration(conn)) == NULL) {
+ /* ctl.c already outputs an error */
+ goto end;
+ }
+ lldpctl_atom_dec_ref(configuration);
+
+ /* Process file inputs */
+ while (gotinputs && !TAILQ_EMPTY(&inputs)) {
+ /* coverity[use_after_free]
+ TAILQ_REMOVE does the right thing */
+ struct input *first = TAILQ_FIRST(&inputs);
+ log_debug("lldpctl", "process: %s", first->name);
+ FILE *file = fopen(first->name, "r");
+ if (file) {
+ size_t n;
+ ssize_t len;
+ char *line;
+ while (line = NULL, len = 0,
+ (len = getline(&line, &n, file)) > 0) {
+ if (line[len - 1] == '\n') {
+ line[len - 1] = '\0';
+ parse_and_exec(conn, fmt, line);
+ }
+ free(line);
+ }
+ free(line);
+ fclose(file);
+ } else {
+ log_warn("lldpctl", "unable to open %s", first->name);
+ }
+ TAILQ_REMOVE(&inputs, first, next);
+ free(first->name);
+ free(first);
+ }
+
+ /* Process additional arguments. First if we are lldpctl (interfaces) */
+ if (is_lldpctl(NULL)) {
+ char *line = NULL;
+ for (int i = optind; i < argc; i++) {
+ char *prev = line;
+ if (asprintf(&line, "%s%s%s", prev ? prev : "show neigh ports ",
+ argv[i], (i == argc - 1) ? " details" : ",") == -1) {
+ log_warnx("lldpctl",
+ "not enough memory to build list of interfaces");
+ free(prev);
+ goto end;
+ }
+ free(prev);
+ }
+ if (line == NULL && (line = strdup("show neigh details")) == NULL) {
+ log_warnx("lldpctl", "not enough memory to build command line");
+ goto end;
+ }
+ log_debug("lldpctl", "execute %s", line);
+ if (parse_and_exec(conn, fmt, line) != -1) rc = EXIT_SUCCESS;
+ free(line);
+ goto end;
+ }
+
+ /* Then, if we are regular lldpcli (command line) */
+ if (optind < argc) {
+ const char **cargv;
+ int cargc;
+ cargv = &((const char **)argv)[optind];
+ cargc = argc - optind;
+ if (cmd_exec(conn, fmt, cargc, cargv) == 1) rc = EXIT_SUCCESS;
+ goto end;
+ }
+
+ if (gotinputs) {
+ rc = EXIT_SUCCESS;
+ goto end;
+ }
+
+ /* Interactive session */
+#ifdef HAVE_LIBREADLINE
+ rl_bind_key('?', cmd_help);
+ rl_bind_key('\t', cmd_complete);
+#endif
+ char *line = NULL;
+ do {
+ if ((line = readline(prompt()))) {
+ int n = parse_and_exec(conn, fmt, line);
+ if (n != 0) {
+#ifdef HAVE_READLINE_HISTORY
+ add_history(line);
+#endif
+ }
+ free(line);
+ }
+ } while (!must_exit && line != NULL);
+ rc = EXIT_SUCCESS;
+
+end:
+ while (!TAILQ_EMPTY(&inputs)) {
+ /* coverity[use_after_free]
+ TAILQ_REMOVE does the right thing */
+ struct input *first = TAILQ_FIRST(&inputs);
+ TAILQ_REMOVE(&inputs, first, next);
+ free(first->name);
+ free(first);
+ }
+ if (conn) lldpctl_release(conn);
+ if (root) commands_free(root);
+ return rc;
+}
diff --git a/src/client/lldpctl.8 b/src/client/lldpctl.8
new file mode 100644
index 0000000..68a27a1
--- /dev/null
+++ b/src/client/lldpctl.8
@@ -0,0 +1 @@
+.so man8/lldpcli.8
diff --git a/src/client/misc.c b/src/client/misc.c
new file mode 100644
index 0000000..827bd90
--- /dev/null
+++ b/src/client/misc.c
@@ -0,0 +1,73 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/*
+ * Copyright (c) 2012 Vincent Bernat <bernat@luffy.cx>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "client.h"
+#include <string.h>
+#include <ctype.h>
+
+/**
+ * Check if an element is present in a comma-separated list.
+ *
+ * @param list Comma-separated list of elements.
+ * @param element Element we want to check for.
+ * @return 0 if the element was not found, 1 otherwise.
+ */
+int
+contains(const char *list, const char *element)
+{
+ int len;
+ if (element == NULL || list == NULL) return 0;
+ while (list) {
+ len = strlen(element);
+ if (!strncmp(list, element, len) &&
+ (list[len] == '\0' || list[len] == ','))
+ return 1;
+ list = strchr(list, ',');
+ if (list) list++;
+ }
+ return 0;
+}
+
+/**
+ * Transform a string to a tag. This puts the string into lower space and
+ * replace spaces with '-'. The result is statically allocated.
+ *
+ * @param value String to transform to a tag.
+ * @return The tagged value or the string "none" if @c value is @c NULL
+ */
+const char *
+totag(const char *value)
+{
+ int i;
+ static char *result = NULL;
+ free(result);
+ result = NULL;
+ if (!value) return "none";
+ result = calloc(1, strlen(value) + 1);
+ if (!result) return "none";
+ for (i = 0; i < strlen(value); i++) {
+ switch (value[i]) {
+ case ' ':
+ result[i] = '-';
+ break;
+ default:
+ result[i] = tolower((int)value[i]);
+ break;
+ }
+ }
+ return result;
+}
diff --git a/src/client/show.c b/src/client/show.c
new file mode 100644
index 0000000..f13ae57
--- /dev/null
+++ b/src/client/show.c
@@ -0,0 +1,364 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/*
+ * Copyright (c) 2012 Vincent Bernat <bernat@luffy.cx>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "client.h"
+#include <string.h>
+#include <limits.h>
+
+/**
+ * Show neighbors.
+ *
+ * The environment will contain the following keys:
+ * - C{ports} list of ports we want to restrict showing.
+ * - C{hidden} if we should show hidden ports.
+ * - C{summary} if we want to show only a summary
+ * - C{detailed} for a detailed overview
+ */
+static int
+cmd_show_neighbors(struct lldpctl_conn_t *conn, struct writer *w, struct cmd_env *env,
+ const void *arg)
+{
+ log_debug("lldpctl", "show neighbors data (%s) %s hidden neighbors",
+ cmdenv_get(env, "summary") ? "summary" :
+ cmdenv_get(env, "detailed") ? "detailed" :
+ "normal",
+ cmdenv_get(env, "hidden") ? "with" : "without");
+ if (cmdenv_get(env, "ports"))
+ log_debug("lldpctl", "restrict to the following ports: %s",
+ cmdenv_get(env, "ports"));
+
+ display_interfaces(conn, w, env, !!cmdenv_get(env, "hidden"),
+ cmdenv_get(env, "summary") ? DISPLAY_BRIEF :
+ cmdenv_get(env, "detailed") ? DISPLAY_DETAILS :
+ DISPLAY_NORMAL);
+
+ return 1;
+}
+
+/**
+ * Show interfaces.
+ *
+ * The environment will contain the following keys:
+ * - C{ports} list of ports we want to restrict showing.
+ * - C{hidden} if we should show hidden ports.
+ * - C{summary} if we want to show only a summary
+ * - C{detailed} for a detailed overview
+ */
+static int
+cmd_show_interfaces(struct lldpctl_conn_t *conn, struct writer *w, struct cmd_env *env,
+ const void *arg)
+{
+ log_debug("lldpctl", "show interfaces data (%s) %s hidden interfaces",
+ cmdenv_get(env, "summary") ? "summary" :
+ cmdenv_get(env, "detailed") ? "detailed" :
+ "normal",
+ cmdenv_get(env, "hidden") ? "with" : "without");
+ if (cmdenv_get(env, "ports"))
+ log_debug("lldpctl", "restrict to the following ports: %s",
+ cmdenv_get(env, "ports"));
+
+ display_local_interfaces(conn, w, env, !!cmdenv_get(env, "hidden"),
+ cmdenv_get(env, "summary") ? DISPLAY_BRIEF :
+ cmdenv_get(env, "detailed") ? DISPLAY_DETAILS :
+ DISPLAY_NORMAL);
+
+ return 1;
+}
+
+/**
+ * Show chassis.
+ *
+ * The environment will contain the following keys:
+ * - C{summary} if we want to show only a summary
+ * - C{detailed} for a detailed overview
+ */
+static int
+cmd_show_chassis(struct lldpctl_conn_t *conn, struct writer *w, struct cmd_env *env,
+ const void *arg)
+{
+ log_debug("lldpctl", "show chassis data (%s)",
+ cmdenv_get(env, "summary") ? "summary" :
+ cmdenv_get(env, "detailed") ? "detailed" :
+ "normal");
+
+ display_local_chassis(conn, w, env,
+ cmdenv_get(env, "summary") ? DISPLAY_BRIEF :
+ cmdenv_get(env, "detailed") ? DISPLAY_DETAILS :
+ DISPLAY_NORMAL);
+
+ return 1;
+}
+
+/**
+ * Show stats.
+ *
+ * The environment will contain the following keys:
+ * - C{ports} list of ports we want to restrict showing.
+ * - C{summary} summary of stats
+ */
+static int
+cmd_show_interface_stats(struct lldpctl_conn_t *conn, struct writer *w,
+ struct cmd_env *env, const void *arg)
+{
+ log_debug("lldpctl", "show stats data");
+ if (cmdenv_get(env, "ports"))
+ log_debug("lldpctl", "restrict to the following ports: %s",
+ cmdenv_get(env, "ports"));
+ if (cmdenv_get(env, "summary"))
+ log_debug("lldpctl", "show summary of stats across ports");
+
+ display_interfaces_stats(conn, w, env);
+
+ return 1;
+}
+
+static int
+cmd_check_no_detailed_nor_summary(struct cmd_env *env, const void *arg)
+{
+ if (cmdenv_get(env, "detailed")) return 0;
+ if (cmdenv_get(env, "summary")) return 0;
+ return 1;
+}
+
+/**
+ * Show running configuration.
+ */
+static int
+cmd_show_configuration(struct lldpctl_conn_t *conn, struct writer *w,
+ struct cmd_env *env, const void *arg)
+{
+ log_debug("lldpctl", "show running configuration");
+ display_configuration(conn, w);
+ return 1;
+}
+
+struct watcharg {
+ struct cmd_env *env;
+ struct writer *w;
+ size_t nb;
+};
+
+/**
+ * Callback for the next function to display a new neighbor.
+ */
+static void
+watchcb(lldpctl_change_t type, lldpctl_atom_t *interface, lldpctl_atom_t *neighbor,
+ void *data)
+{
+ struct watcharg *wa = data;
+ struct cmd_env *env = wa->env;
+ struct writer *w = wa->w;
+ const char *interfaces = cmdenv_get(env, "ports");
+ const char *proto_str;
+ int protocol = LLDPD_MODE_MAX;
+
+ if (interfaces &&
+ !contains(interfaces,
+ lldpctl_atom_get_str(interface, lldpctl_k_interface_name)))
+ return;
+
+ /* user might have specified protocol to filter display results */
+ proto_str = cmdenv_get(env, "protocol");
+
+ if (proto_str) {
+ log_debug("display", "filter protocol: %s ", proto_str);
+
+ protocol = 0; /* unsupported */
+ for (lldpctl_map_t *protocol_map =
+ lldpctl_key_get_map(lldpctl_k_port_protocol);
+ protocol_map->string; protocol_map++) {
+ if (!strcasecmp(proto_str, protocol_map->string)) {
+ protocol = protocol_map->value;
+ break;
+ }
+ }
+ }
+
+ switch (type) {
+ case lldpctl_c_deleted:
+ tag_start(w, "lldp-deleted", "LLDP neighbor deleted");
+ break;
+ case lldpctl_c_updated:
+ tag_start(w, "lldp-updated", "LLDP neighbor updated");
+ break;
+ case lldpctl_c_added:
+ tag_start(w, "lldp-added", "LLDP neighbor added");
+ break;
+ default:
+ return;
+ }
+ display_interface(NULL, w, 1, interface, neighbor,
+ cmdenv_get(env, "summary") ? DISPLAY_BRIEF :
+ cmdenv_get(env, "detailed") ? DISPLAY_DETAILS :
+ DISPLAY_NORMAL,
+ protocol);
+ tag_end(w);
+ wa->nb++;
+}
+
+/**
+ * Watch for neighbor changes.
+ */
+static int
+cmd_watch_neighbors(struct lldpctl_conn_t *conn, struct writer *w, struct cmd_env *env,
+ const void *arg)
+{
+ struct watcharg wa = { .env = env, .w = w, .nb = 0 };
+ const char *limit_str = cmdenv_get(env, "limit");
+ size_t limit = 0;
+
+ if (limit_str) {
+ const char *errstr;
+ limit = strtonum(limit_str, 1, LLONG_MAX, &errstr);
+ if (errstr != NULL) {
+ log_warnx("lldpctl", "specified limit (%s) is %s and ignored",
+ limit_str, errstr);
+ }
+ }
+
+ log_debug("lldpctl", "watch for neighbor changes");
+ if (lldpctl_watch_callback2(conn, watchcb, &wa) < 0) {
+ log_warnx("lldpctl", "unable to watch for neighbors. %s",
+ lldpctl_last_strerror(conn));
+ return 0;
+ }
+ while (1) {
+ if (lldpctl_watch(conn) < 0) {
+ log_warnx("lldpctl", "unable to watch for neighbors. %s",
+ lldpctl_last_strerror(conn));
+ return 0;
+ }
+ if (limit > 0 && wa.nb >= limit) return 1;
+ }
+ return 0;
+}
+
+/**
+ * Register common subcommands for `watch` and `show neighbors` and `show chassis'
+ */
+static void
+register_common_commands(struct cmd_node *root, int neighbor)
+{
+ /* With more details */
+ commands_new(root, "details", "With more details",
+ cmd_check_no_detailed_nor_summary, cmd_store_env_and_pop, "detailed");
+
+ /* With less details */
+ commands_new(root, "summary", "With less details",
+ cmd_check_no_detailed_nor_summary, cmd_store_env_and_pop, "summary");
+
+ if (!neighbor) return;
+
+ /* With hidden neighbors */
+ commands_new(root, "hidden", "Include hidden neighbors", cmd_check_no_env,
+ cmd_store_env_and_pop, "hidden");
+
+ /* Some specific port */
+ cmd_restrict_ports(root);
+
+ /* Specific protocol */
+ cmd_restrict_protocol(root);
+}
+
+/**
+ * Register sub command summary
+ */
+static void
+register_summary_command(struct cmd_node *root)
+{
+ commands_new(root, "summary", "With less details",
+ cmd_check_no_detailed_nor_summary, cmd_store_env_and_pop, "summary");
+}
+
+/**
+ * Register subcommands to `show`
+ *
+ * @param root Root node
+ */
+void
+register_commands_show(struct cmd_node *root)
+{
+ struct cmd_node *show = commands_new(root, "show",
+ "Show running system information", NULL, NULL, NULL);
+ struct cmd_node *neighbors =
+ commands_new(show, "neighbors", "Show neighbors data", NULL, NULL, NULL);
+
+ struct cmd_node *interfaces =
+ commands_new(show, "interfaces", "Show interfaces data", NULL, NULL, NULL);
+
+ struct cmd_node *chassis =
+ commands_new(show, "chassis", "Show local chassis data", NULL, NULL, NULL);
+
+ struct cmd_node *stats =
+ commands_new(show, "statistics", "Show statistics", NULL, NULL, NULL);
+
+ /* Neighbors data */
+ commands_new(neighbors, NEWLINE, "Show neighbors data", NULL,
+ cmd_show_neighbors, NULL);
+
+ register_common_commands(neighbors, 1);
+
+ /* Interfaces data */
+ commands_new(interfaces, NEWLINE, "Show interfaces data", NULL,
+ cmd_show_interfaces, NULL);
+
+ cmd_restrict_ports(interfaces);
+ register_common_commands(interfaces, 0);
+
+ /* Chassis data */
+ commands_new(chassis, NEWLINE, "Show local chassis data", NULL,
+ cmd_show_chassis, NULL);
+
+ register_common_commands(chassis, 0);
+
+ /* Stats data */
+ commands_new(stats, NEWLINE, "Show stats data", NULL, cmd_show_interface_stats,
+ NULL);
+
+ cmd_restrict_ports(stats);
+ register_summary_command(stats);
+
+ /* Register "show configuration" and "show running-configuration" */
+ commands_new(commands_new(show, "configuration", "Show running configuration",
+ NULL, NULL, NULL),
+ NEWLINE, "Show running configuration", NULL, cmd_show_configuration, NULL);
+ commands_new(commands_new(show, "running-configuration",
+ "Show running configuration", NULL, NULL, NULL),
+ NEWLINE, "Show running configuration", NULL, cmd_show_configuration, NULL);
+}
+
+/**
+ * Register subcommands to `watch`
+ *
+ * @param root Root node
+ */
+void
+register_commands_watch(struct cmd_node *root)
+{
+ struct cmd_node *watch =
+ commands_new(root, "watch", "Monitor neighbor changes", NULL, NULL, NULL);
+
+ commands_new(watch, NEWLINE, "Monitor neighbors change", NULL,
+ cmd_watch_neighbors, NULL);
+
+ commands_new(commands_new(watch, "limit", "Don't show more than X events",
+ cmd_check_no_env, NULL, "limit"),
+ NULL, "Stop after getting X events", NULL, cmd_store_env_value_and_pop2,
+ "limit");
+
+ register_common_commands(watch, 1);
+}
diff --git a/src/client/text_writer.c b/src/client/text_writer.c
new file mode 100644
index 0000000..95990e7
--- /dev/null
+++ b/src/client/text_writer.c
@@ -0,0 +1,183 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/*
+ * Copyright (c) 2010 Andreas Hofmeister <andi@collax.com>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+
+#include "writer.h"
+#include "../log.h"
+
+static char sep[] =
+ "-------------------------------------------------------------------------------";
+
+struct txt_writer_private {
+ FILE *fh;
+ int level;
+ int attrs;
+ int lastnl;
+};
+
+static void
+txt_fprintf(struct txt_writer_private *p, const char *fmt, ...)
+{
+ va_list ap;
+
+ // Don't add a newline if we already had one.
+ while (p->lastnl && fmt[0] == '\n') {
+ fmt += 1;
+ }
+ if (fmt[0] == '\0') return;
+
+ va_start(ap, fmt);
+ vfprintf(p->fh, fmt, ap);
+ va_end(ap);
+
+ p->lastnl = (strlen(fmt) > 0 && fmt[strlen(fmt) - 1] == '\n');
+}
+
+static void
+txt_start(struct writer *w, const char *tag, const char *descr)
+{
+ struct txt_writer_private *p = w->priv;
+ int i = 0;
+ char buf[128];
+
+ if (p->level == 0) {
+ txt_fprintf(p, "%s\n", sep);
+ } else if (!p->lastnl) {
+ txt_fprintf(p, "\n");
+ }
+
+ for (i = 1; i < p->level; i++) {
+ txt_fprintf(p, " ");
+ }
+
+ snprintf(buf, sizeof(buf), "%s:", descr);
+ txt_fprintf(p, "%-13s", buf);
+
+ if (p->level == 0) txt_fprintf(p, "\n%s", sep);
+
+ p->level++;
+ p->attrs = 0;
+}
+
+static void
+txt_attr(struct writer *w, const char *tag, const char *descr, const char *value)
+{
+ struct txt_writer_private *p = w->priv;
+
+ if (descr == NULL || strlen(descr) == 0) {
+ txt_fprintf(p, "%s%s", (p->attrs > 0 ? ", " : " "),
+ value ? value : "(none)");
+ } else {
+ txt_fprintf(p, "%s%s: %s", (p->attrs > 0 ? ", " : " "), descr,
+ value ? value : "(none)");
+ }
+
+ p->attrs++;
+}
+
+static void
+txt_data(struct writer *w, const char *data)
+{
+ struct txt_writer_private *p = w->priv;
+ char *nl, *begin;
+ char *v = begin = data ? strdup(data) : NULL;
+
+ if (v == NULL) {
+ txt_fprintf(p, " %s", data ? data : "(none)");
+ return;
+ }
+
+ txt_fprintf(p, " ");
+ while ((nl = strchr(v, '\n')) != NULL) {
+ *nl = '\0';
+ txt_fprintf(p, "%s\n", v);
+ v = nl + 1;
+
+ /* Indent */
+ int i;
+ for (i = 1; i < p->level - 1; i++) {
+ txt_fprintf(p, " ");
+ }
+ txt_fprintf(p, "%-14s", " ");
+ }
+ txt_fprintf(p, "%s", v);
+ free(begin);
+}
+
+static void
+txt_end(struct writer *w)
+{
+ struct txt_writer_private *p = w->priv;
+ p->level--;
+
+ if (p->level == 1) {
+ txt_fprintf(p, "\n%s", sep);
+ fflush(p->fh);
+ }
+}
+
+static void
+txt_finish(struct writer *w)
+{
+ struct txt_writer_private *p = w->priv;
+
+ txt_fprintf(p, "\n");
+
+ free(w->priv);
+ w->priv = NULL;
+
+ free(w);
+}
+
+struct writer *
+txt_init(FILE *fh)
+{
+
+ struct writer *result;
+ struct txt_writer_private *priv;
+
+ priv = malloc(sizeof(*priv));
+ if (!priv) {
+ fatalx("lldpctl", "out of memory");
+ return NULL;
+ }
+
+ priv->fh = fh;
+ priv->level = 0;
+ priv->attrs = 0;
+ priv->lastnl = 1;
+
+ result = malloc(sizeof(struct writer));
+ if (!result) {
+ fatalx("lldpctl", "out of memory");
+ free(priv);
+ return NULL;
+ }
+
+ result->priv = priv;
+ result->start = txt_start;
+ result->attr = txt_attr;
+ result->data = txt_data;
+ result->end = txt_end;
+ result->finish = txt_finish;
+
+ return result;
+}
diff --git a/src/client/tokenizer.c b/src/client/tokenizer.c
new file mode 100644
index 0000000..c4cdda6
--- /dev/null
+++ b/src/client/tokenizer.c
@@ -0,0 +1,116 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/*
+ * Copyright (c) 2013 Vincent Bernat <bernat@luffy.cx>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "client.h"
+
+#include <string.h>
+/**
+ * Tokenize the given line. We support quoted strings and escaped characters
+ * with backslash.
+ *
+ * @param line Line to tokenize.
+ * @param argv Will get an array of arguments tokenized.
+ * @param argc Will get the number of tokenized arguments.
+ * @return 0 on success, -1 on internal error, 1 on unmatched quotes
+ */
+int
+tokenize_line(const char *line, int *argc, char ***argv)
+{
+ int iargc = 0;
+ char **iargv = NULL;
+ const char ifs[] = " \n\t";
+ const char quotes[] = "'\"";
+ const char escapes[] = "\\";
+ char empty = 2; /* Empty character, will be removed from output
+ * but will mark a word. */
+
+ /* Escape handle. Also escape quoted characters. */
+ int escaped = 0;
+ int ipos = 0;
+ char quote = 0;
+ char input[2 * strlen(line) + 3]; /* 3 = 2 for '\n ' and 1 for \0 */
+ memset(input, 0, 2 * strlen(line) + 3);
+ for (int pos = 0; line[pos]; pos++) {
+ if (line[pos] == '#' && !escaped && !quote) break;
+ if (!escaped && strchr(escapes, line[pos]))
+ escaped = 1;
+ else if (!escaped && strchr(quotes, line[pos]) && !quote) {
+ input[ipos++] = empty;
+ input[ipos++] = '!';
+ quote = line[pos];
+ } else if (!escaped && quote == line[pos])
+ quote = 0;
+ else {
+ input[ipos++] = line[pos];
+ input[ipos++] = (escaped || quote) ? '!' : ' ';
+ escaped = 0;
+ }
+ }
+ if (escaped || quote) return 1;
+ /* Trick to not have to handle \0 in a special way */
+ input[ipos++] = ifs[0];
+ input[ipos++] = ' ';
+
+ /* Tokenize, we don't have to handle quotes anymore */
+ int wbegin = -1; /* Offset of the beginning of the current word */
+
+#define CURRENT (input[2 * pos])
+#define ESCAPED (input[2 * pos + 1] != ' ')
+ for (int pos = 0; CURRENT; pos++) {
+ if (wbegin == -1) {
+ if (!ESCAPED && strchr(ifs, CURRENT))
+ /* IFS while not in a word, continue. */
+ continue;
+ /* Start a word. */
+ wbegin = pos;
+ continue;
+ }
+ if (ESCAPED || !strchr(ifs, CURRENT)) /* Regular character in a word. */
+ continue;
+
+ /* End of word. */
+ char *word = calloc(1, pos - wbegin + 1);
+ if (!word) goto error;
+ int i, j;
+ for (i = wbegin, j = 0; i != pos; i++)
+ if (input[2 * i] != empty) word[j++] = input[2 * i];
+ char **nargv = realloc(iargv, sizeof(char *) * (iargc + 1));
+ if (!nargv) {
+ free(word);
+ goto error;
+ }
+ nargv[iargc++] = word;
+ iargv = nargv;
+ wbegin = -1;
+ }
+
+ *argc = iargc;
+ *argv = iargv;
+ return 0;
+
+error:
+ tokenize_free(iargc, iargv);
+ return -1;
+}
+
+void
+tokenize_free(int argc, char **argv)
+{
+ while (argc)
+ free(argv[--argc]);
+ free(argv);
+}
diff --git a/src/client/utf8.c b/src/client/utf8.c
new file mode 100644
index 0000000..4dbf178
--- /dev/null
+++ b/src/client/utf8.c
@@ -0,0 +1,90 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/*
+ Copyright (c) 2011 Joseph A. Adams (joeyadams3.14159@gmail.com)
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.
+*/
+
+#include <stddef.h>
+#include "writer.h"
+
+/*
+ * Validate a single UTF-8 character starting at @s.
+ * The string must be null-terminated.
+ *
+ * If it's valid, return its length (1 thru 4).
+ * If it's invalid or clipped, return 0.
+ *
+ * This function implements the syntax given in RFC3629, which is
+ * the same as that given in The Unicode Standard, Version 6.0.
+ *
+ * It has the following properties:
+ *
+ * * All codepoints U+0000..U+10FFFF may be encoded,
+ * except for U+D800..U+DFFF, which are reserved
+ * for UTF-16 surrogate pair encoding.
+ * * UTF-8 byte sequences longer than 4 bytes are not permitted,
+ * as they exceed the range of Unicode.
+ * * The sixty-six Unicode "non-characters" are permitted
+ * (namely, U+FDD0..U+FDEF, U+xxFFFE, and U+xxFFFF).
+ */
+size_t
+utf8_validate_cz(const char *s)
+{
+ unsigned char c = *s++;
+
+ if (c <= 0x7F) { /* 00..7F */
+ return 1;
+ } else if (c <= 0xC1) { /* 80..C1 */
+ /* Disallow overlong 2-byte sequence. */
+ return 0;
+ } else if (c <= 0xDF) { /* C2..DF */
+ /* Make sure subsequent byte is in the range 0x80..0xBF. */
+ if (((unsigned char)*s++ & 0xC0) != 0x80) return 0;
+
+ return 2;
+ } else if (c <= 0xEF) { /* E0..EF */
+ /* Disallow overlong 3-byte sequence. */
+ if (c == 0xE0 && (unsigned char)*s < 0xA0) return 0;
+
+ /* Disallow U+D800..U+DFFF. */
+ if (c == 0xED && (unsigned char)*s > 0x9F) return 0;
+
+ /* Make sure subsequent bytes are in the range 0x80..0xBF. */
+ if (((unsigned char)*s++ & 0xC0) != 0x80) return 0;
+ if (((unsigned char)*s++ & 0xC0) != 0x80) return 0;
+
+ return 3;
+ } else if (c <= 0xF4) { /* F0..F4 */
+ /* Disallow overlong 4-byte sequence. */
+ if (c == 0xF0 && (unsigned char)*s < 0x90) return 0;
+
+ /* Disallow codepoints beyond U+10FFFF. */
+ if (c == 0xF4 && (unsigned char)*s > 0x8F) return 0;
+
+ /* Make sure subsequent bytes are in the range 0x80..0xBF. */
+ if (((unsigned char)*s++ & 0xC0) != 0x80) return 0;
+ if (((unsigned char)*s++ & 0xC0) != 0x80) return 0;
+ if (((unsigned char)*s++ & 0xC0) != 0x80) return 0;
+
+ return 4;
+ } else { /* F5..FF */
+ return 0;
+ }
+}
diff --git a/src/client/writer.h b/src/client/writer.h
new file mode 100644
index 0000000..c66f699
--- /dev/null
+++ b/src/client/writer.h
@@ -0,0 +1,57 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/*
+ * Copyright (c) 2010 Andreas Hofmeister <andi@collax.com>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef _WRITER_H
+#define _WRITER_H
+
+#include <stdio.h>
+#include <config.h>
+
+struct writer {
+ void *priv;
+ void (*start)(struct writer *, const char *tag, const char *descr);
+ void (*attr)(struct writer *, const char *tag, const char *descr,
+ const char *value);
+ void (*data)(struct writer *, const char *data);
+ void (*end)(struct writer *);
+ void (*finish)(struct writer *);
+};
+
+#define tag_start(w, ...) w->start(w, ##__VA_ARGS__)
+#define tag_attr(w, ...) w->attr(w, ##__VA_ARGS__)
+#define tag_data(w, ...) w->data(w, ##__VA_ARGS__)
+#define tag_end(w, ...) w->end(w, ##__VA_ARGS__)
+#define tag_datatag(w, t, d, v) \
+ do { \
+ if ((v) == NULL) break; \
+ w->start(w, t, d); \
+ w->data(w, v); \
+ w->end(w); \
+ } while (0);
+
+extern struct writer *txt_init(FILE *);
+extern struct writer *kv_init(FILE *);
+extern struct writer *json_init(FILE *, int);
+
+#ifdef USE_XML
+extern struct writer *xml_init(FILE *);
+#endif
+
+/* utf8.c */
+size_t utf8_validate_cz(const char *s);
+
+#endif /* _WRITER_H */
diff --git a/src/client/xml_writer.c b/src/client/xml_writer.c
new file mode 100644
index 0000000..0c5efb5
--- /dev/null
+++ b/src/client/xml_writer.c
@@ -0,0 +1,158 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/*
+ * Copyright (c) 2010 Andreas Hofmeister <andi@collax.com>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#if defined(__clang__)
+# pragma clang diagnostic push
+# pragma clang diagnostic ignored "-Wdocumentation"
+#endif
+#include <libxml/encoding.h>
+#include <libxml/xmlwriter.h>
+#if defined(__clang__)
+# pragma clang diagnostic pop
+#endif
+
+#include "writer.h"
+#include "../log.h"
+
+struct xml_writer_private {
+ FILE *fh;
+ ssize_t depth;
+ xmlTextWriterPtr xw;
+ xmlDocPtr doc;
+};
+
+static void
+xml_new_writer(struct xml_writer_private *priv)
+{
+ priv->xw = xmlNewTextWriterDoc(&(priv->doc), 0);
+ if (!priv->xw) fatalx("lldpctl", "cannot create xml writer");
+
+ xmlTextWriterSetIndent(priv->xw, 4);
+
+ if (xmlTextWriterStartDocument(priv->xw, NULL, "UTF-8", NULL) < 0)
+ fatalx("lldpctl", "cannot start xml document");
+}
+
+static void
+xml_start(struct writer *w, const char *tag, const char *descr)
+{
+ struct xml_writer_private *p = w->priv;
+
+ if (p->depth == 0) xml_new_writer(p);
+
+ if (xmlTextWriterStartElement(p->xw, BAD_CAST tag) < 0)
+ log_warnx("lldpctl", "cannot start '%s' element", tag);
+
+ if (descr && (strlen(descr) > 0)) {
+ if (xmlTextWriterWriteFormatAttribute(p->xw, BAD_CAST "label", "%s",
+ descr) < 0)
+ log_warnx("lldpctl",
+ "cannot add attribute 'label' to element %s", tag);
+ }
+
+ p->depth++;
+}
+
+static void
+xml_attr(struct writer *w, const char *tag, const char *descr, const char *value)
+{
+ struct xml_writer_private *p = w->priv;
+
+ if (xmlTextWriterWriteFormatAttribute(p->xw, BAD_CAST tag, "%s",
+ value ? value : "") < 0)
+ log_warnx("lldpctl", "cannot add attribute %s with value %s", tag,
+ value ? value : "(none)");
+}
+
+static void
+xml_data(struct writer *w, const char *data)
+{
+ struct xml_writer_private *p = w->priv;
+ if (xmlTextWriterWriteString(p->xw, BAD_CAST(data ? data : "")) < 0)
+ log_warnx("lldpctl", "cannot add '%s' as data to element",
+ data ? data : "(none)");
+}
+
+static void
+xml_end(struct writer *w)
+{
+ struct xml_writer_private *p = w->priv;
+
+ if (xmlTextWriterEndElement(p->xw) < 0)
+ log_warnx("lldpctl", "cannot end element");
+
+ if (--p->depth == 0) {
+ int failed = 0;
+
+ if (xmlTextWriterEndDocument(p->xw) < 0) {
+ log_warnx("lldpctl", "cannot finish document");
+ failed = 1;
+ }
+
+ xmlFreeTextWriter(p->xw);
+ if (!failed) {
+ xmlDocDump(p->fh, p->doc);
+ fflush(p->fh);
+ }
+ xmlFreeDoc(p->doc);
+ }
+}
+
+static void
+xml_finish(struct writer *w)
+{
+ struct xml_writer_private *p = w->priv;
+ if (p->depth != 0) {
+ log_warnx("lldpctl", "unbalanced tags");
+ /* memory leak... */
+ }
+
+ free(p);
+ free(w);
+}
+
+struct writer *
+xml_init(FILE *fh)
+{
+
+ struct writer *result;
+ struct xml_writer_private *priv;
+
+ priv = malloc(sizeof(*priv));
+ if (!priv) {
+ fatalx("lldpctl", "out of memory");
+ return NULL;
+ }
+ priv->fh = fh;
+ priv->depth = 0;
+
+ result = malloc(sizeof(struct writer));
+ if (!result) fatalx("lldpctl", "out of memory");
+
+ result->priv = priv;
+ result->start = xml_start;
+ result->attr = xml_attr;
+ result->data = xml_data;
+ result->end = xml_end;
+ result->finish = xml_finish;
+
+ return result;
+}
diff --git a/src/compat/Makefile.am b/src/compat/Makefile.am
new file mode 100644
index 0000000..3717edd
--- /dev/null
+++ b/src/compat/Makefile.am
@@ -0,0 +1,8 @@
+AM_CFLAGS = $(LLDP_CFLAGS)
+AM_CPPFLAGS = $(LLDP_CPPFLAGS)
+AM_LDFLAGS = $(LLDP_LDFLAGS)
+
+noinst_LTLIBRARIES = libcompat.la
+
+libcompat_la_SOURCES = compat.h empty.c
+libcompat_la_LIBADD = @LTLIBOBJS@
diff --git a/src/compat/Makefile.in b/src/compat/Makefile.in
new file mode 100644
index 0000000..55187e0
--- /dev/null
+++ b/src/compat/Makefile.in
@@ -0,0 +1,725 @@
+# Makefile.in generated by automake 1.16.5 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2021 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/compat
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/alignof.m4 \
+ $(top_srcdir)/m4/args.m4 \
+ $(top_srcdir)/m4/ax_build_date_epoch.m4 \
+ $(top_srcdir)/m4/ax_cflags_gcc_option.m4 \
+ $(top_srcdir)/m4/ax_ld_check_flag.m4 \
+ $(top_srcdir)/m4/ax_lib_readline.m4 \
+ $(top_srcdir)/m4/ax_prog_doxygen.m4 \
+ $(top_srcdir)/m4/config_subdirs.m4 \
+ $(top_srcdir)/m4/ld-version-script.m4 \
+ $(top_srcdir)/m4/libcap.m4 $(top_srcdir)/m4/libevent.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/os.m4 \
+ $(top_srcdir)/m4/progname.m4 $(top_srcdir)/m4/seccomp.m4 \
+ $(top_srcdir)/m4/snmp.m4 $(top_srcdir)/m4/stdint.m4 \
+ $(top_srcdir)/m4/systemtap.m4 $(top_srcdir)/m4/xml2.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+libcompat_la_DEPENDENCIES = @LTLIBOBJS@
+am_libcompat_la_OBJECTS = empty.lo
+libcompat_la_OBJECTS = $(am_libcompat_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = $(DEPDIR)/asprintf.Plo $(DEPDIR)/daemon.Plo \
+ $(DEPDIR)/getline.Plo $(DEPDIR)/malloc.Plo \
+ $(DEPDIR)/realloc.Plo $(DEPDIR)/setproctitle.Plo \
+ $(DEPDIR)/strlcpy.Plo $(DEPDIR)/strndup.Plo \
+ $(DEPDIR)/strnlen.Plo $(DEPDIR)/strtonum.Plo \
+ $(DEPDIR)/vsyslog.Plo ./$(DEPDIR)/empty.Plo
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libcompat_la_SOURCES)
+DIST_SOURCES = $(libcompat_la_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp \
+ asprintf.c daemon.c getline.c malloc.c realloc.c \
+ setproctitle.c strlcpy.c strndup.c strnlen.c strtonum.c \
+ vsyslog.c
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+APPARMORDIR = @APPARMORDIR@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CONFIGURE_ARGS = @CONFIGURE_ARGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CSCOPE = @CSCOPE@
+CTAGS = @CTAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DLLTOOL = @DLLTOOL@
+DOXYGEN_PAPER_SIZE = @DOXYGEN_PAPER_SIZE@
+DSYMUTIL = @DSYMUTIL@
+DTRACE = @DTRACE@
+DUMPBIN = @DUMPBIN@
+DX_CONFIG = @DX_CONFIG@
+DX_DOCDIR = @DX_DOCDIR@
+DX_DOT = @DX_DOT@
+DX_DOXYGEN = @DX_DOXYGEN@
+DX_DVIPS = @DX_DVIPS@
+DX_EGREP = @DX_EGREP@
+DX_ENV = @DX_ENV@
+DX_FLAG_chi = @DX_FLAG_chi@
+DX_FLAG_chm = @DX_FLAG_chm@
+DX_FLAG_doc = @DX_FLAG_doc@
+DX_FLAG_dot = @DX_FLAG_dot@
+DX_FLAG_html = @DX_FLAG_html@
+DX_FLAG_man = @DX_FLAG_man@
+DX_FLAG_pdf = @DX_FLAG_pdf@
+DX_FLAG_ps = @DX_FLAG_ps@
+DX_FLAG_rtf = @DX_FLAG_rtf@
+DX_FLAG_xml = @DX_FLAG_xml@
+DX_HHC = @DX_HHC@
+DX_LATEX = @DX_LATEX@
+DX_MAKEINDEX = @DX_MAKEINDEX@
+DX_PDFLATEX = @DX_PDFLATEX@
+DX_PERL = @DX_PERL@
+DX_PROJECT = @DX_PROJECT@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+ETAGS = @ETAGS@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+FILECMD = @FILECMD@
+FUZZ_DECODE_ENGINE = @FUZZ_DECODE_ENGINE@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LAUNCHDDAEMONSDIR = @LAUNCHDDAEMONSDIR@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LLDPD_CTL_SOCKET = @LLDPD_CTL_SOCKET@
+LLDPD_PID_FILE = @LLDPD_PID_FILE@
+LLDP_BIN_LDFLAGS = @LLDP_BIN_LDFLAGS@
+LLDP_CFLAGS = @LLDP_CFLAGS@
+LLDP_CPPFLAGS = @LLDP_CPPFLAGS@
+LLDP_LDFLAGS = @LLDP_LDFLAGS@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+NETLINK_MAX_RECEIVE_BUFSIZE = @NETLINK_MAX_RECEIVE_BUFSIZE@
+NETLINK_RECEIVE_BUFSIZE = @NETLINK_RECEIVE_BUFSIZE@
+NETLINK_SEND_BUFSIZE = @NETLINK_SEND_BUFSIZE@
+NETSNMP_CFLAGS = @NETSNMP_CFLAGS@
+NETSNMP_CONFIG = @NETSNMP_CONFIG@
+NETSNMP_LIBS = @NETSNMP_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+PRIVSEP_CHROOT = @PRIVSEP_CHROOT@
+PRIVSEP_GROUP = @PRIVSEP_GROUP@
+PRIVSEP_USER = @PRIVSEP_USER@
+RANLIB = @RANLIB@
+READLINE_LIBS = @READLINE_LIBS@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+SYSTEMDSYSTEMUNITDIR = @SYSTEMDSYSTEMUNITDIR@
+SYSUSERSDIR = @SYSUSERSDIR@
+VERSION = @VERSION@
+XML2_CONFIG = @XML2_CONFIG@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+apparmordir = @apparmordir@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+check_CFLAGS = @check_CFLAGS@
+check_LIBS = @check_LIBS@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+launchddaemonsdir = @launchddaemonsdir@
+libbsd_CFLAGS = @libbsd_CFLAGS@
+libbsd_LIBS = @libbsd_LIBS@
+libcap_CFLAGS = @libcap_CFLAGS@
+libcap_LIBS = @libcap_LIBS@
+libdir = @libdir@
+libevent_CFLAGS = @libevent_CFLAGS@
+libevent_LDFLAGS = @libevent_LDFLAGS@
+libevent_LIBS = @libevent_LIBS@
+libexecdir = @libexecdir@
+libseccomp_CFLAGS = @libseccomp_CFLAGS@
+libseccomp_LIBS = @libseccomp_LIBS@
+libxml2_CFLAGS = @libxml2_CFLAGS@
+libxml2_LIBS = @libxml2_LIBS@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+pkgconfigdir = @pkgconfigdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+systemdsystemunitdir = @systemdsystemunitdir@
+sysusersdir = @sysusersdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+AM_CFLAGS = $(LLDP_CFLAGS)
+AM_CPPFLAGS = $(LLDP_CPPFLAGS)
+AM_LDFLAGS = $(LLDP_LDFLAGS)
+noinst_LTLIBRARIES = libcompat.la
+libcompat_la_SOURCES = compat.h empty.c
+libcompat_la_LIBADD = @LTLIBOBJS@
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/compat/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/compat/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstLTLIBRARIES:
+ -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES)
+ @list='$(noinst_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+libcompat.la: $(libcompat_la_OBJECTS) $(libcompat_la_DEPENDENCIES) $(EXTRA_libcompat_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(libcompat_la_OBJECTS) $(libcompat_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@$(DEPDIR)/asprintf.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@$(DEPDIR)/daemon.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@$(DEPDIR)/getline.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@$(DEPDIR)/malloc.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@$(DEPDIR)/realloc.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@$(DEPDIR)/setproctitle.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@$(DEPDIR)/strlcpy.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@$(DEPDIR)/strndup.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@$(DEPDIR)/strnlen.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@$(DEPDIR)/strtonum.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@$(DEPDIR)/vsyslog.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/empty.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\
+@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.obj$$||'`;\
+@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ `$(CYGPATH_W) '$<'` &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.lo$$||'`;\
+@am__fastdepCC_TRUE@ $(LTCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LTLIBRARIES)
+installdirs:
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f $(DEPDIR)/asprintf.Plo
+ -rm -f $(DEPDIR)/daemon.Plo
+ -rm -f $(DEPDIR)/getline.Plo
+ -rm -f $(DEPDIR)/malloc.Plo
+ -rm -f $(DEPDIR)/realloc.Plo
+ -rm -f $(DEPDIR)/setproctitle.Plo
+ -rm -f $(DEPDIR)/strlcpy.Plo
+ -rm -f $(DEPDIR)/strndup.Plo
+ -rm -f $(DEPDIR)/strnlen.Plo
+ -rm -f $(DEPDIR)/strtonum.Plo
+ -rm -f $(DEPDIR)/vsyslog.Plo
+ -rm -f ./$(DEPDIR)/empty.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f $(DEPDIR)/asprintf.Plo
+ -rm -f $(DEPDIR)/daemon.Plo
+ -rm -f $(DEPDIR)/getline.Plo
+ -rm -f $(DEPDIR)/malloc.Plo
+ -rm -f $(DEPDIR)/realloc.Plo
+ -rm -f $(DEPDIR)/setproctitle.Plo
+ -rm -f $(DEPDIR)/strlcpy.Plo
+ -rm -f $(DEPDIR)/strndup.Plo
+ -rm -f $(DEPDIR)/strnlen.Plo
+ -rm -f $(DEPDIR)/strtonum.Plo
+ -rm -f $(DEPDIR)/vsyslog.Plo
+ -rm -f ./$(DEPDIR)/empty.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man install-pdf \
+ install-pdf-am install-ps install-ps-am install-strip \
+ installcheck installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/compat/asprintf.c b/src/compat/asprintf.c
new file mode 100644
index 0000000..58c5dae
--- /dev/null
+++ b/src/compat/asprintf.c
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2004 Darren Tucker.
+ *
+ * Based originally on asprintf.c from OpenBSD:
+ * Copyright (c) 1997 Todd C. Miller <Todd.Miller@courtesan.com>
+ *
+ * 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 <errno.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <limits.h>
+#include "compat.h"
+
+#define INIT_SZ 128
+
+int
+vasprintf(char **str, const char *fmt, va_list ap)
+{
+ int ret = -1;
+ va_list ap2;
+ char *string, *newstr;
+ size_t len;
+
+ va_copy(ap2, ap);
+ if ((string = malloc(INIT_SZ)) == NULL) goto fail;
+
+ ret = vsnprintf(string, INIT_SZ, fmt, ap2);
+ if (ret >= 0 && ret < INIT_SZ) { /* succeeded with initial alloc */
+ *str = string;
+ } else if (ret == INT_MAX || ret < 0) { /* Bad length */
+ free(string);
+ goto fail;
+ } else { /* bigger than initial, realloc allowing for nul */
+ len = (size_t)ret + 1;
+ if ((newstr = realloc(string, len)) == NULL) {
+ free(string);
+ goto fail;
+ } else {
+ va_end(ap2);
+ va_copy(ap2, ap);
+ ret = vsnprintf(newstr, len, fmt, ap2);
+ if (ret >= 0 && (size_t)ret < len) {
+ *str = newstr;
+ } else { /* failed with realloc'ed string, give up */
+ free(newstr);
+ goto fail;
+ }
+ }
+ }
+ va_end(ap2);
+ return (ret);
+
+fail:
+ *str = NULL;
+ errno = ENOMEM;
+ va_end(ap2);
+ return (-1);
+}
+
+int
+asprintf(char **str, const char *fmt, ...)
+{
+ va_list ap;
+ int ret;
+
+ *str = NULL;
+ va_start(ap, fmt);
+ ret = vasprintf(str, fmt, ap);
+ va_end(ap);
+
+ return ret;
+}
diff --git a/src/compat/compat.h b/src/compat/compat.h
new file mode 100644
index 0000000..91ff4ad
--- /dev/null
+++ b/src/compat/compat.h
@@ -0,0 +1,93 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/*
+ * 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
+ */
+
+#ifndef _COMPAT_H
+#define _COMPAT_H
+
+#if HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stddef.h>
+#include <stdarg.h>
+#include <string.h>
+#include <unistd.h>
+
+#undef getopt
+
+#if !HAVE_ASPRINTF
+int vasprintf(char **, const char *, va_list) __attribute__((format(printf, 2, 0)));
+int asprintf(char **, const char *, ...) __attribute__((format(printf, 2, 3)));
+#endif
+
+#if !HAVE_VSYSLOG
+void vsyslog(int, const char *, va_list) __attribute__((format(printf, 2, 0)));
+#endif
+
+#if !HAVE_DAEMON
+int daemon(int, int);
+#endif
+
+#if !HAVE_STRLCPY
+size_t strlcpy(char *, const char *, size_t);
+#endif
+
+#if !HAVE_STRNLEN
+size_t strnlen(const char *, size_t);
+#endif
+
+#if !HAVE_STRNDUP
+char *strndup(const char *, size_t);
+#endif
+
+#if !HAVE_STRTONUM
+long long strtonum(const char *, long long, long long, const char **);
+#endif
+
+#if !HAVE_GETLINE
+ssize_t getline(char **, size_t *, FILE *);
+#endif
+
+#if !HAVE_SETPROCTITLE
+void setproctitle(const char *fmt, ...) __attribute__((format(printf, 1, 2)));
+#endif
+
+#if !HAVE_MALLOC
+void *malloc(size_t size);
+#endif
+
+#if !HAVE_REALLOC
+void *realloc(void *ptr, size_t size);
+#endif
+
+#endif
diff --git a/src/compat/daemon.c b/src/compat/daemon.c
new file mode 100644
index 0000000..5b7f980
--- /dev/null
+++ b/src/compat/daemon.c
@@ -0,0 +1,66 @@
+/* $OpenBSD: daemon.c,v 1.6 2005/08/08 08:05:33 espie Exp $ */
+/*-
+ * Copyright (c) 1990, 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.
+ */
+
+/* OPENBSD ORIGINAL: lib/libc/gen/daemon.c */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include "compat.h"
+
+int
+daemon(int nochdir, int noclose)
+{
+ int fd;
+
+ switch (fork()) {
+ case -1:
+ return (-1);
+ case 0:
+ break;
+ default:
+ _exit(0);
+ }
+
+ if (setsid() == -1) return (-1);
+
+ if (!nochdir) (void)chdir("/");
+
+ /* coverity[resource_leak]
+ fd may be leaked if < 2, it's expected */
+ if (!noclose && (fd = open("/dev/null", O_RDWR, 0)) != -1) {
+ (void)dup2(fd, STDIN_FILENO);
+ (void)dup2(fd, STDOUT_FILENO);
+ (void)dup2(fd, STDERR_FILENO);
+ if (fd > 2) (void)close(fd);
+ }
+ return (0);
+}
diff --git a/src/compat/empty.c b/src/compat/empty.c
new file mode 100644
index 0000000..85d5837
--- /dev/null
+++ b/src/compat/empty.c
@@ -0,0 +1,21 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/*
+ * Copyright (c) 2013 Vincent Bernat <bernat@luffy.cx>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/* Some versions of ar don't like to build a library from
+ * nothing. This happens on Mac OS X where we don't need any
+ * compatibility layer. So, we put a tiny variable. Just here. */
+int __lldpd_int_zero = 0;
diff --git a/src/compat/getline.c b/src/compat/getline.c
new file mode 100644
index 0000000..a3e6047
--- /dev/null
+++ b/src/compat/getline.c
@@ -0,0 +1,106 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/*
+ * Copyright (c) 2009 The NetBSD Foundation, Inc.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Roy Marples.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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.
+ */
+
+/* Several modifications to make the code more portable (and less robust and far less
+ * efficient) */
+
+#include <stdio.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "compat.h"
+
+#define MINBUF 128
+
+static ssize_t
+___getdelim(char **buf, size_t *buflen, int sep, FILE *fp)
+{
+ int p;
+ size_t len = 0, newlen;
+ char *newb;
+
+ if (buf == NULL || buflen == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ /* If buf is NULL, we have to assume a size of zero */
+ if (*buf == NULL) *buflen = 0;
+
+ do {
+ p = fgetc(fp);
+ if (ferror(fp)) return -1;
+ if (p == EOF) break;
+
+ /* Ensure we can handle it */
+ if (len > SSIZE_MAX) {
+ errno = EOVERFLOW;
+ return -1;
+ }
+ newlen = len + 2; /* reserve space for NUL terminator */
+ if (newlen > *buflen) {
+ if (newlen < MINBUF) newlen = MINBUF;
+#define powerof2(x) ((((x)-1) & (x)) == 0)
+ if (!powerof2(newlen)) {
+ /* Grow the buffer to the next power of 2 */
+ newlen--;
+ newlen |= newlen >> 1;
+ newlen |= newlen >> 2;
+ newlen |= newlen >> 4;
+ newlen |= newlen >> 8;
+ newlen |= newlen >> 16;
+#if SIZE_MAX > 0xffffffffU
+ newlen |= newlen >> 32;
+#endif
+ newlen++;
+ }
+
+ newb = realloc(*buf, newlen);
+ if (newb == NULL) return -1;
+ *buf = newb;
+ *buflen = newlen;
+ }
+
+ (*buf)[len++] = p;
+ } while (p != sep);
+
+ /* POSIX demands we return -1 on EOF. */
+ if (len == 0) return -1;
+
+ if (*buf != NULL) (*buf)[len] = '\0';
+ return (ssize_t)len;
+}
+
+ssize_t
+getline(char **buf, size_t *buflen, FILE *fp)
+{
+ return ___getdelim(buf, buflen, '\n', fp);
+}
diff --git a/src/compat/malloc.c b/src/compat/malloc.c
new file mode 100644
index 0000000..0c8bb2c
--- /dev/null
+++ b/src/compat/malloc.c
@@ -0,0 +1,16 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/* malloc replacement that can allocate 0 byte */
+
+#undef malloc
+#include <stdlib.h>
+#include <sys/types.h>
+#include "compat.h"
+
+/* Allocate an N-byte block of memory from the heap.
+ If N is zero, allocate a 1-byte block. */
+void *
+rpl_malloc(size_t n)
+{
+ if (n == 0) n = 1;
+ return malloc(n);
+}
diff --git a/src/compat/realloc.c b/src/compat/realloc.c
new file mode 100644
index 0000000..4d74064
--- /dev/null
+++ b/src/compat/realloc.c
@@ -0,0 +1,17 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/* realloc replacement that can reallocate 0 byte or NULL pointers*/
+
+#undef realloc
+#include <stdlib.h>
+#include <sys/types.h>
+#include "compat.h"
+
+/* Reallocate an N-byte block of memory from the heap.
+ If N is zero, allocate a 1-byte block. */
+void *
+rpl_realloc(void *ptr, size_t n)
+{
+ if (!ptr) return malloc(n);
+ if (n == 0) n = 1;
+ return realloc(ptr, n);
+}
diff --git a/src/compat/setproctitle.c b/src/compat/setproctitle.c
new file mode 100644
index 0000000..c5e40ec
--- /dev/null
+++ b/src/compat/setproctitle.c
@@ -0,0 +1,9 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+
+#include "compat.h"
+
+void
+setproctitle(const char *fmt, ...)
+{
+ /* Do nothing. */
+}
diff --git a/src/compat/strlcpy.c b/src/compat/strlcpy.c
new file mode 100644
index 0000000..dda982f
--- /dev/null
+++ b/src/compat/strlcpy.c
@@ -0,0 +1,51 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/* $OpenBSD: strlcpy.c,v 1.11 2006/05/05 15:27:38 millert Exp $ */
+
+/*
+ * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <string.h>
+#include "compat.h"
+
+/*
+ * 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 */
+}
diff --git a/src/compat/strndup.c b/src/compat/strndup.c
new file mode 100644
index 0000000..1db7069
--- /dev/null
+++ b/src/compat/strndup.c
@@ -0,0 +1,24 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+
+#include <stdlib.h>
+#include <string.h>
+#include "compat.h"
+
+/*
+ * Similar to `strdup()` but copies at most n bytes.
+ */
+char *
+strndup(const char *string, size_t maxlen)
+{
+ char *result;
+ /* We may use `strnlen()` but it may be unavailable. */
+ const char *end = memchr(string, '\0', maxlen);
+ size_t len = end ? (size_t)(end - string) : maxlen;
+
+ result = malloc(len + 1);
+ if (!result) return 0;
+
+ memcpy(result, string, len);
+ result[len] = '\0';
+ return result;
+}
diff --git a/src/compat/strnlen.c b/src/compat/strnlen.c
new file mode 100644
index 0000000..07db4a4
--- /dev/null
+++ b/src/compat/strnlen.c
@@ -0,0 +1,15 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+
+#include <string.h>
+#include "compat.h"
+
+/*
+ * Determine the length of a fixed-size string. This is really a
+ * wrapper around `memchr()`.
+ */
+size_t
+strnlen(const char *string, size_t maxlen)
+{
+ const char *end = memchr(string, '\0', maxlen);
+ return end ? (size_t)(end - string) : maxlen;
+}
diff --git a/src/compat/strtonum.c b/src/compat/strtonum.c
new file mode 100644
index 0000000..aed36b9
--- /dev/null
+++ b/src/compat/strtonum.c
@@ -0,0 +1,65 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+
+/* $OpenBSD: strtonum.c,v 1.8 2015/09/13 08:31:48 guenther Exp $ */
+
+/*
+ * Copyright (c) 2004 Ted Unangst and Todd Miller
+ * All rights reserved.
+ *
+ * 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 <errno.h>
+#include <limits.h>
+#include <stdlib.h>
+#include "compat.h"
+
+#define INVALID 1
+#define TOOSMALL 2
+#define TOOLARGE 3
+
+long long
+strtonum(const char *numstr, long long minval, long long maxval, const char **errstrp)
+{
+ long long ll = 0;
+ int error = 0;
+ char *ep;
+ struct errval {
+ const char *errstr;
+ int err;
+ } ev[4] = {
+ { NULL, 0 },
+ { "invalid", EINVAL },
+ { "too small", ERANGE },
+ { "too large", ERANGE },
+ };
+
+ ev[0].err = errno;
+ errno = 0;
+ if (minval > maxval) {
+ error = INVALID;
+ } else {
+ ll = strtoll(numstr, &ep, 10);
+ if (numstr == ep || *ep != '\0')
+ error = INVALID;
+ else if ((ll == LLONG_MIN && errno == ERANGE) || ll < minval)
+ error = TOOSMALL;
+ else if ((ll == LLONG_MAX && errno == ERANGE) || ll > maxval)
+ error = TOOLARGE;
+ }
+ if (errstrp != NULL) *errstrp = ev[error].errstr;
+ errno = ev[error].err;
+ if (error) ll = 0;
+
+ return (ll);
+}
diff --git a/src/compat/vsyslog.c b/src/compat/vsyslog.c
new file mode 100644
index 0000000..a3209cb
--- /dev/null
+++ b/src/compat/vsyslog.c
@@ -0,0 +1,17 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+
+#include <stdlib.h>
+#include <syslog.h>
+#include "compat.h"
+
+/* vsyslog() doesn't exist on HP-UX */
+void
+vsyslog(int facility, const char *format, va_list ap)
+{
+ char *msg = NULL;
+ if (vasprintf(&msg, format, ap) == -1) {
+ return;
+ }
+ syslog(facility, "%s", msg);
+ free(msg);
+}
diff --git a/src/ctl.c b/src/ctl.c
new file mode 100644
index 0000000..f9473a2
--- /dev/null
+++ b/src/ctl.c
@@ -0,0 +1,261 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/*
+ * Copyright (c) 2008 Vincent Bernat <bernat@luffy.cx>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <string.h>
+
+#include "ctl.h"
+#include "marshal.h"
+#include "log.h"
+#include "compat/compat.h"
+
+/**
+ * Create a new listening Unix socket for control protocol.
+ *
+ * @param name The name of the Unix socket.
+ * @return The socket when successful, -1 otherwise.
+ */
+int
+ctl_create(const char *name)
+{
+ int s;
+ struct sockaddr_un su;
+ int rc;
+
+ log_debug("control", "create control socket %s", name);
+
+ if ((s = socket(PF_UNIX, SOCK_STREAM, 0)) == -1) return -1;
+ if (fcntl(s, F_SETFD, FD_CLOEXEC) == -1) {
+ close(s);
+ return -1;
+ }
+ su.sun_family = AF_UNIX;
+ strlcpy(su.sun_path, name, sizeof(su.sun_path));
+ if (bind(s, (struct sockaddr *)&su, sizeof(struct sockaddr_un)) == -1) {
+ rc = errno;
+ close(s);
+ errno = rc;
+ return -1;
+ }
+
+ log_debug("control", "listen to control socket %s", name);
+ if (listen(s, 5) == -1) {
+ rc = errno;
+ close(s);
+ errno = rc;
+ log_debug("control", "cannot listen to control socket %s", name);
+ return -1;
+ }
+ return s;
+}
+
+/**
+ * Connect to the control Unix socket.
+ *
+ * @param name The name of the Unix socket.
+ * @return The socket when successful, -1 otherwise.
+ */
+int
+ctl_connect(const char *name)
+{
+ int s;
+ struct sockaddr_un su;
+ int rc;
+
+ log_debug("control", "connect to control socket %s", name);
+
+ if ((s = socket(PF_UNIX, SOCK_STREAM, 0)) == -1) return -1;
+ su.sun_family = AF_UNIX;
+ strlcpy(su.sun_path, name, sizeof(su.sun_path));
+ if (connect(s, (struct sockaddr *)&su, sizeof(struct sockaddr_un)) == -1) {
+ rc = errno;
+ log_warn("control", "unable to connect to socket %s", name);
+ close(s);
+ errno = rc;
+ return -1;
+ }
+ return s;
+}
+
+/**
+ * Remove the control Unix socket.
+ *
+ * @param name The name of the Unix socket.
+ */
+void
+ctl_cleanup(const char *name)
+{
+ log_debug("control", "cleanup control socket");
+ if (unlink(name) == -1) log_warn("control", "unable to unlink %s", name);
+}
+
+/**
+ * Serialize and "send" a structure through the control protocol.
+ *
+ * This function does not really send the message but outputs it to a buffer.
+ *
+ * @param output_buffer A pointer to a buffer to which the message will be
+ * appended. Can be @c NULL. In this case, the buffer will
+ * be allocated.
+ * @param[in,out] output_len The length of the provided buffer. Will be updated
+ * with the new length
+ * @param type The type of message we want to send.
+ * @param t The structure to be serialized and sent.
+ * @param mi The appropriate marshal structure for serialization.
+ * @return -1 in case of failure, 0 in case of success.
+ *
+ * Make sure this function logic matches the server-side one: @c levent_ctl_recv().
+ */
+int
+ctl_msg_send_unserialized(uint8_t **output_buffer, size_t *output_len,
+ enum hmsg_type type, void *t, struct marshal_info *mi)
+{
+ ssize_t len = 0, newlen;
+ void *buffer = NULL;
+
+ log_debug("control", "send a message through control socket");
+ if (t) {
+ len = marshal_serialize_(mi, t, &buffer, 0, NULL, 0);
+ if (len <= 0) {
+ log_warnx("control", "unable to serialize data");
+ return -1;
+ }
+ }
+
+ newlen = len + sizeof(struct hmsg_header);
+
+ if (*output_buffer == NULL) {
+ *output_len = 0;
+ if ((*output_buffer = malloc(newlen)) == NULL) {
+ log_warn("control", "no memory available");
+ free(buffer);
+ return -1;
+ }
+ } else {
+ void *new = realloc(*output_buffer, *output_len + newlen);
+ if (new == NULL) {
+ log_warn("control", "no memory available");
+ free(buffer);
+ return -1;
+ }
+ *output_buffer = new;
+ }
+
+ struct hmsg_header hdr;
+ memset(&hdr, 0, sizeof(struct hmsg_header));
+ hdr.type = type;
+ hdr.len = len;
+ memcpy(*output_buffer + *output_len, &hdr, sizeof(struct hmsg_header));
+ if (t)
+ memcpy(*output_buffer + *output_len + sizeof(struct hmsg_header),
+ buffer, len);
+ *output_len += newlen;
+ free(buffer);
+ return 0;
+}
+
+/**
+ * "Receive" and unserialize a structure through the control protocol.
+ *
+ * Like @c ctl_msg_send_unserialized(), this function uses buffer to receive the
+ * incoming message.
+ *
+ * @param[in,out] input_buffer The buffer with the incoming message. Will be
+ * updated once the message has been unserialized to
+ * point to the remaining of the message or will be
+ * freed if all the buffer has been consumed. Can be
+ * @c NULL.
+ * @param[in,out] input_len The length of the provided buffer. Will be updated
+ * to the length of remaining data once the message
+ * has been unserialized.
+ * @param expected_type The expected message type.
+ * @param[out] t Will contain a pointer to the unserialized structure.
+ * Can be @c NULL if we don't want to store the
+ * answer.
+ * @param mi The appropriate marshal structure for unserialization.
+ *
+ * @return -1 in case of error, 0 in case of success and the number of bytes we
+ * request to complete unserialization.
+ *
+ * When requesting a notification, the input buffer is left untouched if we
+ * don't get one and we fail silently.
+ */
+size_t
+ctl_msg_recv_unserialized(uint8_t **input_buffer, size_t *input_len,
+ enum hmsg_type expected_type, void **t, struct marshal_info *mi)
+{
+ struct hmsg_header hdr;
+ int rc = -1;
+
+ if (*input_buffer == NULL || *input_len < sizeof(struct hmsg_header)) {
+ /* Not enough data. */
+ return sizeof(struct hmsg_header) - *input_len;
+ }
+
+ log_debug("control", "receive a message through control socket");
+ memcpy(&hdr, *input_buffer, sizeof(struct hmsg_header));
+ if (hdr.len > HMSG_MAX_SIZE) {
+ log_warnx("control", "message received is too large");
+ /* We discard the whole buffer */
+ free(*input_buffer);
+ *input_buffer = NULL;
+ *input_len = 0;
+ return -1;
+ }
+ if (*input_len < sizeof(struct hmsg_header) + hdr.len) {
+ /* Not enough data. */
+ return sizeof(struct hmsg_header) + hdr.len - *input_len;
+ }
+ if (hdr.type != expected_type) {
+ if (expected_type == NOTIFICATION) return -1;
+ log_warnx("control",
+ "incorrect received message type (expected: %d, received: %d)",
+ expected_type, hdr.type);
+ goto end;
+ }
+
+ if (t && !hdr.len) {
+ log_warnx("control", "no payload available in answer");
+ goto end;
+ }
+ if (t) {
+ /* We have data to unserialize. */
+ if (marshal_unserialize_(mi, *input_buffer + sizeof(struct hmsg_header),
+ hdr.len, t, NULL, 0, 0) <= 0) {
+ log_warnx("control", "unable to deserialize received data");
+ goto end;
+ }
+ }
+
+ rc = 0;
+end:
+ /* Discard input buffer */
+ *input_len -= sizeof(struct hmsg_header) + hdr.len;
+ if (*input_len == 0) {
+ free(*input_buffer);
+ *input_buffer = NULL;
+ } else
+ memmove(*input_buffer,
+ *input_buffer + sizeof(struct hmsg_header) + hdr.len, *input_len);
+ return rc;
+}
diff --git a/src/ctl.h b/src/ctl.h
new file mode 100644
index 0000000..7e5b076
--- /dev/null
+++ b/src/ctl.h
@@ -0,0 +1,64 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/*
+ * Copyright (c) 2012 Vincent Bernat <bernat@luffy.cx>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef _CTL_H
+#define _CTL_H
+
+#if HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdint.h>
+#include "marshal.h"
+
+enum hmsg_type {
+ NONE,
+ GET_CONFIG, /* Get global configuration */
+ SET_CONFIG, /* Change global configuration */
+ GET_INTERFACES, /* Get list of interfaces */
+ SET_CHASSIS, /* Set local chassis */
+ GET_CHASSIS, /* Get local chassis */
+ GET_INTERFACE, /* Get all information related to an interface */
+ GET_DEFAULT_PORT, /* Get all information related to default port */
+ SET_PORT, /* Set port-related information (location, power, policy) */
+ SUBSCRIBE, /* Subscribe to neighbor changes */
+ NOTIFICATION, /* Notification message (sent by lldpd!) */
+};
+
+/** Header for the control protocol.
+ *
+ * The protocol is pretty simple. We send a single message containing the
+ * provided message type with the message length, followed by the message
+ * content.
+ */
+struct hmsg_header {
+ enum hmsg_type type;
+ size_t len;
+};
+#define HMSG_MAX_SIZE (1 << 19)
+
+/* ctl.c */
+int ctl_create(const char *);
+int ctl_connect(const char *);
+void ctl_cleanup(const char *);
+
+int ctl_msg_send_unserialized(uint8_t **, size_t *, enum hmsg_type, void *,
+ struct marshal_info *);
+size_t ctl_msg_recv_unserialized(uint8_t **, size_t *, enum hmsg_type, void **,
+ struct marshal_info *);
+
+#endif
diff --git a/src/daemon/Makefile.am b/src/daemon/Makefile.am
new file mode 100644
index 0000000..42240a4
--- /dev/null
+++ b/src/daemon/Makefile.am
@@ -0,0 +1,180 @@
+AM_CFLAGS = -I $(top_srcdir)/include $(LLDP_CFLAGS)
+AM_CPPFLAGS = $(LLDP_CPPFLAGS)
+AM_LDFLAGS = $(LLDP_LDFLAGS)
+BUILT_SOURCES =
+CLEANFILES =
+
+sbin_PROGRAMS = lldpd
+man_MANS = lldpd.8
+
+noinst_LTLIBRARIES = liblldpd.la
+
+## Convenience library for lldpd and tests
+nodist_liblldpd_la_SOURCES =
+liblldpd_la_SOURCES = \
+ frame.h frame.c \
+ lldp-tlv.h \
+ client.c \
+ priv.c \
+ privsep.c privsep_io.c privsep_fd.c \
+ interfaces.c \
+ event.c lldpd.c \
+ pattern.c \
+ bitmap.c \
+ probes.d trace.h \
+ protocols/lldp.c \
+ protocols/cdp.c \
+ protocols/cdp.h \
+ protocols/sonmp.c \
+ protocols/sonmp.h \
+ protocols/edp.c \
+ protocols/edp.h
+liblldpd_la_CFLAGS = $(AM_CFLAGS) @libevent_CFLAGS@ @libcap_CFLAGS@
+liblldpd_la_CPPFLAGS = $(AM_CPPFLAGS) -DSYSCONFDIR='"$(sysconfdir)"' -DLLDPCLI_PATH='"$(sbindir)/lldpcli"'
+liblldpd_la_LIBADD = \
+ $(top_builddir)/src/libcommon-daemon-client.la \
+ $(top_builddir)/src/libcommon-daemon-lib.la @libevent_LIBS@ @libcap_LIBS@
+
+## lldpd
+lldpd_SOURCES = main.c
+lldpd_LDFLAGS = $(AM_LDFLAGS) $(LLDP_BIN_LDFLAGS)
+lldpd_LDADD = liblldpd.la @libevent_LDFLAGS@
+
+if HOST_OS_LINUX
+liblldpd_la_SOURCES += \
+ forward-linux.c \
+ interfaces-linux.c \
+ netlink.c \
+ dmi-linux.c \
+ priv-linux.c
+endif
+if HOST_OS_DRAGONFLY
+liblldpd_la_SOURCES += \
+ forward-bsd.c \
+ interfaces-bpf.c \
+ interfaces-bsd.c \
+ dmi-dummy.c \
+ priv-bsd.c
+endif
+if HOST_OS_FREEBSD
+liblldpd_la_SOURCES += \
+ forward-bsd.c \
+ interfaces-bpf.c \
+ interfaces-bsd.c \
+ dmi-freebsd.c \
+ priv-bsd.c
+endif
+if HOST_OS_OPENBSD
+liblldpd_la_SOURCES += \
+ interfaces-bpf.c \
+ forward-bsd.c \
+ interfaces-bsd.c \
+ dmi-openbsd.c \
+ priv-bsd.c
+endif
+if HOST_OS_NETBSD
+liblldpd_la_SOURCES += \
+ forward-bsd.c \
+ interfaces-bpf.c \
+ interfaces-bsd.c \
+ dmi-dummy.c \
+ priv-bsd.c
+endif
+if HOST_OS_OSX
+liblldpd_la_SOURCES += \
+ forward-bsd.c \
+ interfaces-bpf.c \
+ interfaces-bsd.c \
+ dmi-osx.c \
+ priv-bsd.c
+liblldpd_la_LDFLAGS = $(AM_LDFLAGS)
+liblldpd_la_LDFLAGS += -framework Foundation
+liblldpd_la_LDFLAGS += -framework CoreFoundation -framework IOKit
+liblldpd_la_LDFLAGS += -framework IOKit
+endif
+if HOST_OS_SOLARIS
+liblldpd_la_SOURCES += \
+ forward-solaris.c \
+ interfaces-bpf.c \
+ interfaces-solaris.c \
+ dmi-dummy.c \
+ priv-bsd.c
+endif
+
+# seccomp support
+if USE_SECCOMP
+BUILT_SOURCES += syscall-names.h
+CLEANFILES += syscall-names.h syscall-names.h.tmp
+syscall-names.h:
+ $(AM_V_GEN)
+ $(AM_V_at)echo "#include <sys/syscall.h>" | $(CPP) -dM - > $@.tmp ;\
+ echo "static const char *syscall_names[] = {" > $@ ;\
+ grep '^#define __NR_' $@.tmp | \
+ LC_ALL=C sed -r -n -e 's/^\#define[ \t]+__NR_([a-z0-9_]+)[ \t]+([0-9]+)(.*)/ [\2] = "\1",/p' >> $@ ;\
+ echo "};" >> $@ ;\
+ rm $@.tmp
+nodist_liblldpd_la_SOURCES += syscall-names.h
+liblldpd_la_SOURCES += priv-seccomp.c
+liblldpd_la_CFLAGS += @libseccomp_CFLAGS@
+liblldpd_la_LIBADD += @libseccomp_LIBS@
+endif
+
+# Add SNMP support if needed
+if USE_SNMP
+noinst_LTLIBRARIES += liblldpd-snmp.la
+liblldpd_snmp_la_SOURCES = agent.c agent_priv.c agent.h
+liblldpd_snmp_la_CFLAGS = $(liblldpd_la_CFLAGS) @NETSNMP_CFLAGS@
+liblldpd_snmp_la_CPPFLAGS = $(liblldpd_la_CPPFLAGS)
+liblldpd_la_LIBADD += liblldpd-snmp.la
+lldpd_LDADD += @NETSNMP_LIBS@
+endif
+
+## Systemtap/DTrace
+EXTRA_DIST = dtrace2systemtap.awk
+if ENABLE_SYSTEMTAP
+BUILT_SOURCES += probes.h
+CLEANFILES += probes.h lldpd.stp
+probes.h: probes.d
+ $(AM_V_GEN)
+ $(AM_V_at)$(DTRACE) -C -h -s $< -o $@
+probes.o: probes.d
+ $(AM_V_GEN)
+ $(AM_V_at)$(DTRACE) -C -G -s $< -o $@
+lldpd_LDADD += probes.o
+
+lldpd.stp: probes.d $(srcdir)/dtrace2systemtap.awk $(top_builddir)/config.status
+ $(AM_V_GEN)$(AWK) -f $(srcdir)/dtrace2systemtap.awk -v sbindir=$(sbindir) $< > $@ || ( rm -f $@ ; exit 1 )
+tapsetdir = $(datadir)/systemtap/tapset
+tapset_DATA = lldpd.stp
+endif
+
+## libevent
+if LIBEVENT_EMBEDDED
+event.c: $(top_builddir)/libevent/libevent.la
+$(top_builddir)/libevent/libevent.la: $(top_srcdir)/libevent/*.c $(top_srcdir)/libevent/*.h
+ (cd $(top_builddir)/libevent && $(MAKE))
+endif
+
+## systemd service file
+if HAVE_SYSTEMDSYSTEMUNITDIR
+systemdsystemunit_DATA = lldpd.service
+endif
+
+if HAVE_SYSUSERSDIR
+sysusers_DATA = lldpd.sysusers.conf
+endif
+
+if HOST_OS_LINUX
+if HAVE_APPARMORDIR
+apparmor_DATA = usr.sbin.lldpd
+endif
+endif
+
+TEMPLATES = lldpd.8 lldpd.service lldpd.sysusers.conf usr.sbin.lldpd
+EXTRA_DIST += lldpd.8.in lldpd.service.in lldpd.sysusers.conf.in usr.sbin.lldpd.in
+CLEANFILES += $(TEMPLATES)
+lldpd.8: lldpd.8.in
+lldpd.service: lldpd.service.in
+lldpd.sysusers.conf: lldpd.sysusers.conf.in
+usr.sbin.lldpd: usr.sbin.lldpd.in
+include $(top_srcdir)/edit.am
diff --git a/src/daemon/Makefile.in b/src/daemon/Makefile.in
new file mode 100644
index 0000000..397289e
--- /dev/null
+++ b/src/daemon/Makefile.in
@@ -0,0 +1,1545 @@
+# Makefile.in generated by automake 1.16.5 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2021 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+sbin_PROGRAMS = lldpd$(EXEEXT)
+@HOST_OS_LINUX_TRUE@am__append_1 = \
+@HOST_OS_LINUX_TRUE@ forward-linux.c \
+@HOST_OS_LINUX_TRUE@ interfaces-linux.c \
+@HOST_OS_LINUX_TRUE@ netlink.c \
+@HOST_OS_LINUX_TRUE@ dmi-linux.c \
+@HOST_OS_LINUX_TRUE@ priv-linux.c
+
+@HOST_OS_DRAGONFLY_TRUE@am__append_2 = \
+@HOST_OS_DRAGONFLY_TRUE@ forward-bsd.c \
+@HOST_OS_DRAGONFLY_TRUE@ interfaces-bpf.c \
+@HOST_OS_DRAGONFLY_TRUE@ interfaces-bsd.c \
+@HOST_OS_DRAGONFLY_TRUE@ dmi-dummy.c \
+@HOST_OS_DRAGONFLY_TRUE@ priv-bsd.c
+
+@HOST_OS_FREEBSD_TRUE@am__append_3 = \
+@HOST_OS_FREEBSD_TRUE@ forward-bsd.c \
+@HOST_OS_FREEBSD_TRUE@ interfaces-bpf.c \
+@HOST_OS_FREEBSD_TRUE@ interfaces-bsd.c \
+@HOST_OS_FREEBSD_TRUE@ dmi-freebsd.c \
+@HOST_OS_FREEBSD_TRUE@ priv-bsd.c
+
+@HOST_OS_OPENBSD_TRUE@am__append_4 = \
+@HOST_OS_OPENBSD_TRUE@ interfaces-bpf.c \
+@HOST_OS_OPENBSD_TRUE@ forward-bsd.c \
+@HOST_OS_OPENBSD_TRUE@ interfaces-bsd.c \
+@HOST_OS_OPENBSD_TRUE@ dmi-openbsd.c \
+@HOST_OS_OPENBSD_TRUE@ priv-bsd.c
+
+@HOST_OS_NETBSD_TRUE@am__append_5 = \
+@HOST_OS_NETBSD_TRUE@ forward-bsd.c \
+@HOST_OS_NETBSD_TRUE@ interfaces-bpf.c \
+@HOST_OS_NETBSD_TRUE@ interfaces-bsd.c \
+@HOST_OS_NETBSD_TRUE@ dmi-dummy.c \
+@HOST_OS_NETBSD_TRUE@ priv-bsd.c
+
+@HOST_OS_OSX_TRUE@am__append_6 = \
+@HOST_OS_OSX_TRUE@ forward-bsd.c \
+@HOST_OS_OSX_TRUE@ interfaces-bpf.c \
+@HOST_OS_OSX_TRUE@ interfaces-bsd.c \
+@HOST_OS_OSX_TRUE@ dmi-osx.c \
+@HOST_OS_OSX_TRUE@ priv-bsd.c
+
+@HOST_OS_SOLARIS_TRUE@am__append_7 = \
+@HOST_OS_SOLARIS_TRUE@ forward-solaris.c \
+@HOST_OS_SOLARIS_TRUE@ interfaces-bpf.c \
+@HOST_OS_SOLARIS_TRUE@ interfaces-solaris.c \
+@HOST_OS_SOLARIS_TRUE@ dmi-dummy.c \
+@HOST_OS_SOLARIS_TRUE@ priv-bsd.c
+
+
+# seccomp support
+@USE_SECCOMP_TRUE@am__append_8 = syscall-names.h
+@USE_SECCOMP_TRUE@am__append_9 = syscall-names.h syscall-names.h.tmp
+@USE_SECCOMP_TRUE@am__append_10 = syscall-names.h
+@USE_SECCOMP_TRUE@am__append_11 = priv-seccomp.c
+@USE_SECCOMP_TRUE@am__append_12 = @libseccomp_CFLAGS@
+@USE_SECCOMP_TRUE@am__append_13 = @libseccomp_LIBS@
+
+# Add SNMP support if needed
+@USE_SNMP_TRUE@am__append_14 = liblldpd-snmp.la
+@USE_SNMP_TRUE@am__append_15 = liblldpd-snmp.la
+@USE_SNMP_TRUE@am__append_16 = @NETSNMP_LIBS@
+@ENABLE_SYSTEMTAP_TRUE@am__append_17 = probes.h
+@ENABLE_SYSTEMTAP_TRUE@am__append_18 = probes.h lldpd.stp
+@ENABLE_SYSTEMTAP_TRUE@am__append_19 = probes.o
+subdir = src/daemon
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/alignof.m4 \
+ $(top_srcdir)/m4/args.m4 \
+ $(top_srcdir)/m4/ax_build_date_epoch.m4 \
+ $(top_srcdir)/m4/ax_cflags_gcc_option.m4 \
+ $(top_srcdir)/m4/ax_ld_check_flag.m4 \
+ $(top_srcdir)/m4/ax_lib_readline.m4 \
+ $(top_srcdir)/m4/ax_prog_doxygen.m4 \
+ $(top_srcdir)/m4/config_subdirs.m4 \
+ $(top_srcdir)/m4/ld-version-script.m4 \
+ $(top_srcdir)/m4/libcap.m4 $(top_srcdir)/m4/libevent.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/os.m4 \
+ $(top_srcdir)/m4/progname.m4 $(top_srcdir)/m4/seccomp.m4 \
+ $(top_srcdir)/m4/snmp.m4 $(top_srcdir)/m4/stdint.m4 \
+ $(top_srcdir)/m4/systemtap.m4 $(top_srcdir)/m4/xml2.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+am__installdirs = "$(DESTDIR)$(sbindir)" "$(DESTDIR)$(man8dir)" \
+ "$(DESTDIR)$(apparmordir)" "$(DESTDIR)$(systemdsystemunitdir)" \
+ "$(DESTDIR)$(sysusersdir)" "$(DESTDIR)$(tapsetdir)"
+PROGRAMS = $(sbin_PROGRAMS)
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+liblldpd_snmp_la_LIBADD =
+am__liblldpd_snmp_la_SOURCES_DIST = agent.c agent_priv.c agent.h
+@USE_SNMP_TRUE@am_liblldpd_snmp_la_OBJECTS = \
+@USE_SNMP_TRUE@ liblldpd_snmp_la-agent.lo \
+@USE_SNMP_TRUE@ liblldpd_snmp_la-agent_priv.lo
+liblldpd_snmp_la_OBJECTS = $(am_liblldpd_snmp_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+liblldpd_snmp_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \
+ $(liblldpd_snmp_la_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) \
+ -o $@
+@USE_SNMP_TRUE@am_liblldpd_snmp_la_rpath =
+am__DEPENDENCIES_1 =
+liblldpd_la_DEPENDENCIES = \
+ $(top_builddir)/src/libcommon-daemon-client.la \
+ $(top_builddir)/src/libcommon-daemon-lib.la \
+ $(am__DEPENDENCIES_1) $(am__append_15)
+am__liblldpd_la_SOURCES_DIST = frame.h frame.c lldp-tlv.h client.c \
+ priv.c privsep.c privsep_io.c privsep_fd.c interfaces.c \
+ event.c lldpd.c pattern.c bitmap.c probes.d trace.h \
+ protocols/lldp.c protocols/cdp.c protocols/cdp.h \
+ protocols/sonmp.c protocols/sonmp.h protocols/edp.c \
+ protocols/edp.h forward-linux.c interfaces-linux.c netlink.c \
+ dmi-linux.c priv-linux.c forward-bsd.c interfaces-bpf.c \
+ interfaces-bsd.c dmi-dummy.c priv-bsd.c dmi-freebsd.c \
+ dmi-openbsd.c dmi-osx.c forward-solaris.c interfaces-solaris.c \
+ priv-seccomp.c
+am__dirstamp = $(am__leading_dot)dirstamp
+@HOST_OS_LINUX_TRUE@am__objects_1 = liblldpd_la-forward-linux.lo \
+@HOST_OS_LINUX_TRUE@ liblldpd_la-interfaces-linux.lo \
+@HOST_OS_LINUX_TRUE@ liblldpd_la-netlink.lo \
+@HOST_OS_LINUX_TRUE@ liblldpd_la-dmi-linux.lo \
+@HOST_OS_LINUX_TRUE@ liblldpd_la-priv-linux.lo
+@HOST_OS_DRAGONFLY_TRUE@am__objects_2 = liblldpd_la-forward-bsd.lo \
+@HOST_OS_DRAGONFLY_TRUE@ liblldpd_la-interfaces-bpf.lo \
+@HOST_OS_DRAGONFLY_TRUE@ liblldpd_la-interfaces-bsd.lo \
+@HOST_OS_DRAGONFLY_TRUE@ liblldpd_la-dmi-dummy.lo \
+@HOST_OS_DRAGONFLY_TRUE@ liblldpd_la-priv-bsd.lo
+@HOST_OS_FREEBSD_TRUE@am__objects_3 = liblldpd_la-forward-bsd.lo \
+@HOST_OS_FREEBSD_TRUE@ liblldpd_la-interfaces-bpf.lo \
+@HOST_OS_FREEBSD_TRUE@ liblldpd_la-interfaces-bsd.lo \
+@HOST_OS_FREEBSD_TRUE@ liblldpd_la-dmi-freebsd.lo \
+@HOST_OS_FREEBSD_TRUE@ liblldpd_la-priv-bsd.lo
+@HOST_OS_OPENBSD_TRUE@am__objects_4 = liblldpd_la-interfaces-bpf.lo \
+@HOST_OS_OPENBSD_TRUE@ liblldpd_la-forward-bsd.lo \
+@HOST_OS_OPENBSD_TRUE@ liblldpd_la-interfaces-bsd.lo \
+@HOST_OS_OPENBSD_TRUE@ liblldpd_la-dmi-openbsd.lo \
+@HOST_OS_OPENBSD_TRUE@ liblldpd_la-priv-bsd.lo
+@HOST_OS_NETBSD_TRUE@am__objects_5 = liblldpd_la-forward-bsd.lo \
+@HOST_OS_NETBSD_TRUE@ liblldpd_la-interfaces-bpf.lo \
+@HOST_OS_NETBSD_TRUE@ liblldpd_la-interfaces-bsd.lo \
+@HOST_OS_NETBSD_TRUE@ liblldpd_la-dmi-dummy.lo \
+@HOST_OS_NETBSD_TRUE@ liblldpd_la-priv-bsd.lo
+@HOST_OS_OSX_TRUE@am__objects_6 = liblldpd_la-forward-bsd.lo \
+@HOST_OS_OSX_TRUE@ liblldpd_la-interfaces-bpf.lo \
+@HOST_OS_OSX_TRUE@ liblldpd_la-interfaces-bsd.lo \
+@HOST_OS_OSX_TRUE@ liblldpd_la-dmi-osx.lo \
+@HOST_OS_OSX_TRUE@ liblldpd_la-priv-bsd.lo
+@HOST_OS_SOLARIS_TRUE@am__objects_7 = liblldpd_la-forward-solaris.lo \
+@HOST_OS_SOLARIS_TRUE@ liblldpd_la-interfaces-bpf.lo \
+@HOST_OS_SOLARIS_TRUE@ liblldpd_la-interfaces-solaris.lo \
+@HOST_OS_SOLARIS_TRUE@ liblldpd_la-dmi-dummy.lo \
+@HOST_OS_SOLARIS_TRUE@ liblldpd_la-priv-bsd.lo
+@USE_SECCOMP_TRUE@am__objects_8 = liblldpd_la-priv-seccomp.lo
+am_liblldpd_la_OBJECTS = liblldpd_la-frame.lo liblldpd_la-client.lo \
+ liblldpd_la-priv.lo liblldpd_la-privsep.lo \
+ liblldpd_la-privsep_io.lo liblldpd_la-privsep_fd.lo \
+ liblldpd_la-interfaces.lo liblldpd_la-event.lo \
+ liblldpd_la-lldpd.lo liblldpd_la-pattern.lo \
+ liblldpd_la-bitmap.lo protocols/liblldpd_la-lldp.lo \
+ protocols/liblldpd_la-cdp.lo protocols/liblldpd_la-sonmp.lo \
+ protocols/liblldpd_la-edp.lo $(am__objects_1) $(am__objects_2) \
+ $(am__objects_3) $(am__objects_4) $(am__objects_5) \
+ $(am__objects_6) $(am__objects_7) $(am__objects_8)
+am__objects_9 =
+nodist_liblldpd_la_OBJECTS = $(am__objects_9)
+liblldpd_la_OBJECTS = $(am_liblldpd_la_OBJECTS) \
+ $(nodist_liblldpd_la_OBJECTS)
+liblldpd_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(liblldpd_la_CFLAGS) \
+ $(CFLAGS) $(liblldpd_la_LDFLAGS) $(LDFLAGS) -o $@
+am_lldpd_OBJECTS = main.$(OBJEXT)
+lldpd_OBJECTS = $(am_lldpd_OBJECTS)
+lldpd_DEPENDENCIES = liblldpd.la $(am__DEPENDENCIES_1) \
+ $(am__append_19)
+lldpd_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(lldpd_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/liblldpd_la-bitmap.Plo \
+ ./$(DEPDIR)/liblldpd_la-client.Plo \
+ ./$(DEPDIR)/liblldpd_la-dmi-dummy.Plo \
+ ./$(DEPDIR)/liblldpd_la-dmi-freebsd.Plo \
+ ./$(DEPDIR)/liblldpd_la-dmi-linux.Plo \
+ ./$(DEPDIR)/liblldpd_la-dmi-openbsd.Plo \
+ ./$(DEPDIR)/liblldpd_la-dmi-osx.Plo \
+ ./$(DEPDIR)/liblldpd_la-event.Plo \
+ ./$(DEPDIR)/liblldpd_la-forward-bsd.Plo \
+ ./$(DEPDIR)/liblldpd_la-forward-linux.Plo \
+ ./$(DEPDIR)/liblldpd_la-forward-solaris.Plo \
+ ./$(DEPDIR)/liblldpd_la-frame.Plo \
+ ./$(DEPDIR)/liblldpd_la-interfaces-bpf.Plo \
+ ./$(DEPDIR)/liblldpd_la-interfaces-bsd.Plo \
+ ./$(DEPDIR)/liblldpd_la-interfaces-linux.Plo \
+ ./$(DEPDIR)/liblldpd_la-interfaces-solaris.Plo \
+ ./$(DEPDIR)/liblldpd_la-interfaces.Plo \
+ ./$(DEPDIR)/liblldpd_la-lldpd.Plo \
+ ./$(DEPDIR)/liblldpd_la-netlink.Plo \
+ ./$(DEPDIR)/liblldpd_la-pattern.Plo \
+ ./$(DEPDIR)/liblldpd_la-priv-bsd.Plo \
+ ./$(DEPDIR)/liblldpd_la-priv-linux.Plo \
+ ./$(DEPDIR)/liblldpd_la-priv-seccomp.Plo \
+ ./$(DEPDIR)/liblldpd_la-priv.Plo \
+ ./$(DEPDIR)/liblldpd_la-privsep.Plo \
+ ./$(DEPDIR)/liblldpd_la-privsep_fd.Plo \
+ ./$(DEPDIR)/liblldpd_la-privsep_io.Plo \
+ ./$(DEPDIR)/liblldpd_snmp_la-agent.Plo \
+ ./$(DEPDIR)/liblldpd_snmp_la-agent_priv.Plo \
+ ./$(DEPDIR)/main.Po protocols/$(DEPDIR)/liblldpd_la-cdp.Plo \
+ protocols/$(DEPDIR)/liblldpd_la-edp.Plo \
+ protocols/$(DEPDIR)/liblldpd_la-lldp.Plo \
+ protocols/$(DEPDIR)/liblldpd_la-sonmp.Plo
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(liblldpd_snmp_la_SOURCES) $(liblldpd_la_SOURCES) \
+ $(nodist_liblldpd_la_SOURCES) $(lldpd_SOURCES)
+DIST_SOURCES = $(am__liblldpd_snmp_la_SOURCES_DIST) \
+ $(am__liblldpd_la_SOURCES_DIST) $(lldpd_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+man8dir = $(mandir)/man8
+NROFF = nroff
+MANS = $(man_MANS)
+DATA = $(apparmor_DATA) $(systemdsystemunit_DATA) $(sysusers_DATA) \
+ $(tapset_DATA)
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp \
+ $(top_srcdir)/edit.am
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+APPARMORDIR = @APPARMORDIR@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CONFIGURE_ARGS = @CONFIGURE_ARGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CSCOPE = @CSCOPE@
+CTAGS = @CTAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DLLTOOL = @DLLTOOL@
+DOXYGEN_PAPER_SIZE = @DOXYGEN_PAPER_SIZE@
+DSYMUTIL = @DSYMUTIL@
+DTRACE = @DTRACE@
+DUMPBIN = @DUMPBIN@
+DX_CONFIG = @DX_CONFIG@
+DX_DOCDIR = @DX_DOCDIR@
+DX_DOT = @DX_DOT@
+DX_DOXYGEN = @DX_DOXYGEN@
+DX_DVIPS = @DX_DVIPS@
+DX_EGREP = @DX_EGREP@
+DX_ENV = @DX_ENV@
+DX_FLAG_chi = @DX_FLAG_chi@
+DX_FLAG_chm = @DX_FLAG_chm@
+DX_FLAG_doc = @DX_FLAG_doc@
+DX_FLAG_dot = @DX_FLAG_dot@
+DX_FLAG_html = @DX_FLAG_html@
+DX_FLAG_man = @DX_FLAG_man@
+DX_FLAG_pdf = @DX_FLAG_pdf@
+DX_FLAG_ps = @DX_FLAG_ps@
+DX_FLAG_rtf = @DX_FLAG_rtf@
+DX_FLAG_xml = @DX_FLAG_xml@
+DX_HHC = @DX_HHC@
+DX_LATEX = @DX_LATEX@
+DX_MAKEINDEX = @DX_MAKEINDEX@
+DX_PDFLATEX = @DX_PDFLATEX@
+DX_PERL = @DX_PERL@
+DX_PROJECT = @DX_PROJECT@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+ETAGS = @ETAGS@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+FILECMD = @FILECMD@
+FUZZ_DECODE_ENGINE = @FUZZ_DECODE_ENGINE@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LAUNCHDDAEMONSDIR = @LAUNCHDDAEMONSDIR@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LLDPD_CTL_SOCKET = @LLDPD_CTL_SOCKET@
+LLDPD_PID_FILE = @LLDPD_PID_FILE@
+LLDP_BIN_LDFLAGS = @LLDP_BIN_LDFLAGS@
+LLDP_CFLAGS = @LLDP_CFLAGS@
+LLDP_CPPFLAGS = @LLDP_CPPFLAGS@
+LLDP_LDFLAGS = @LLDP_LDFLAGS@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+NETLINK_MAX_RECEIVE_BUFSIZE = @NETLINK_MAX_RECEIVE_BUFSIZE@
+NETLINK_RECEIVE_BUFSIZE = @NETLINK_RECEIVE_BUFSIZE@
+NETLINK_SEND_BUFSIZE = @NETLINK_SEND_BUFSIZE@
+NETSNMP_CFLAGS = @NETSNMP_CFLAGS@
+NETSNMP_CONFIG = @NETSNMP_CONFIG@
+NETSNMP_LIBS = @NETSNMP_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+PRIVSEP_CHROOT = @PRIVSEP_CHROOT@
+PRIVSEP_GROUP = @PRIVSEP_GROUP@
+PRIVSEP_USER = @PRIVSEP_USER@
+RANLIB = @RANLIB@
+READLINE_LIBS = @READLINE_LIBS@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+SYSTEMDSYSTEMUNITDIR = @SYSTEMDSYSTEMUNITDIR@
+SYSUSERSDIR = @SYSUSERSDIR@
+VERSION = @VERSION@
+XML2_CONFIG = @XML2_CONFIG@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+apparmordir = @apparmordir@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+check_CFLAGS = @check_CFLAGS@
+check_LIBS = @check_LIBS@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+launchddaemonsdir = @launchddaemonsdir@
+libbsd_CFLAGS = @libbsd_CFLAGS@
+libbsd_LIBS = @libbsd_LIBS@
+libcap_CFLAGS = @libcap_CFLAGS@
+libcap_LIBS = @libcap_LIBS@
+libdir = @libdir@
+libevent_CFLAGS = @libevent_CFLAGS@
+libevent_LDFLAGS = @libevent_LDFLAGS@
+libevent_LIBS = @libevent_LIBS@
+libexecdir = @libexecdir@
+libseccomp_CFLAGS = @libseccomp_CFLAGS@
+libseccomp_LIBS = @libseccomp_LIBS@
+libxml2_CFLAGS = @libxml2_CFLAGS@
+libxml2_LIBS = @libxml2_LIBS@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+pkgconfigdir = @pkgconfigdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+systemdsystemunitdir = @systemdsystemunitdir@
+sysusersdir = @sysusersdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+AM_CFLAGS = -I $(top_srcdir)/include $(LLDP_CFLAGS)
+AM_CPPFLAGS = $(LLDP_CPPFLAGS)
+AM_LDFLAGS = $(LLDP_LDFLAGS)
+BUILT_SOURCES = $(am__append_8) $(am__append_17)
+CLEANFILES = $(am__append_9) $(am__append_18) $(TEMPLATES)
+man_MANS = lldpd.8
+noinst_LTLIBRARIES = liblldpd.la $(am__append_14)
+nodist_liblldpd_la_SOURCES = $(am__append_10)
+liblldpd_la_SOURCES = frame.h frame.c lldp-tlv.h client.c priv.c \
+ privsep.c privsep_io.c privsep_fd.c interfaces.c event.c \
+ lldpd.c pattern.c bitmap.c probes.d trace.h protocols/lldp.c \
+ protocols/cdp.c protocols/cdp.h protocols/sonmp.c \
+ protocols/sonmp.h protocols/edp.c protocols/edp.h \
+ $(am__append_1) $(am__append_2) $(am__append_3) \
+ $(am__append_4) $(am__append_5) $(am__append_6) \
+ $(am__append_7) $(am__append_11)
+liblldpd_la_CFLAGS = $(AM_CFLAGS) @libevent_CFLAGS@ @libcap_CFLAGS@ \
+ $(am__append_12)
+liblldpd_la_CPPFLAGS = $(AM_CPPFLAGS) -DSYSCONFDIR='"$(sysconfdir)"' -DLLDPCLI_PATH='"$(sbindir)/lldpcli"'
+liblldpd_la_LIBADD = $(top_builddir)/src/libcommon-daemon-client.la \
+ $(top_builddir)/src/libcommon-daemon-lib.la @libevent_LIBS@ \
+ @libcap_LIBS@ $(am__append_13) $(am__append_15)
+lldpd_SOURCES = main.c
+lldpd_LDFLAGS = $(AM_LDFLAGS) $(LLDP_BIN_LDFLAGS)
+lldpd_LDADD = liblldpd.la @libevent_LDFLAGS@ $(am__append_16) \
+ $(am__append_19)
+@HOST_OS_OSX_TRUE@liblldpd_la_LDFLAGS = $(AM_LDFLAGS) -framework \
+@HOST_OS_OSX_TRUE@ Foundation -framework CoreFoundation \
+@HOST_OS_OSX_TRUE@ -framework IOKit -framework IOKit
+@USE_SNMP_TRUE@liblldpd_snmp_la_SOURCES = agent.c agent_priv.c agent.h
+@USE_SNMP_TRUE@liblldpd_snmp_la_CFLAGS = $(liblldpd_la_CFLAGS) @NETSNMP_CFLAGS@
+@USE_SNMP_TRUE@liblldpd_snmp_la_CPPFLAGS = $(liblldpd_la_CPPFLAGS)
+EXTRA_DIST = dtrace2systemtap.awk lldpd.8.in lldpd.service.in \
+ lldpd.sysusers.conf.in usr.sbin.lldpd.in
+@ENABLE_SYSTEMTAP_TRUE@tapsetdir = $(datadir)/systemtap/tapset
+@ENABLE_SYSTEMTAP_TRUE@tapset_DATA = lldpd.stp
+@HAVE_SYSTEMDSYSTEMUNITDIR_TRUE@systemdsystemunit_DATA = lldpd.service
+@HAVE_SYSUSERSDIR_TRUE@sysusers_DATA = lldpd.sysusers.conf
+@HAVE_APPARMORDIR_TRUE@@HOST_OS_LINUX_TRUE@apparmor_DATA = usr.sbin.lldpd
+TEMPLATES = lldpd.8 lldpd.service lldpd.sysusers.conf usr.sbin.lldpd
+edit = $(SED) \
+ -e 's|@bindir[@]|$(bindir)|g' \
+ -e 's|@sbindir[@]|$(sbindir)|g' \
+ -e 's|@sysconfdir[@]|$(sysconfdir)|g' \
+ -e 's|@pkgdatadir[@]|$(pkgdatadir)|g' \
+ -e 's|@libdir[@]|$(libdir)|g' \
+ -e 's|@srcdir[@]|$(srcdir)|g' \
+ -e 's|@top_builddir[@]|$(top_builddir)|g' \
+ -e 's|@includedir[@]|$(includedir)|g' \
+ -e 's|@exec_prefix[@]|$(exec_prefix)|g' \
+ -e 's|@prefix[@]|$(prefix)|g' \
+ -e 's|@VERSION[@]|$(VERSION)|g' \
+ -e 's|@PACKAGE[@]|$(PACKAGE)|g' \
+ -e 's|@PACKAGE_NAME[@]|$(PACKAGE_NAME)|g' \
+ -e 's|@PACKAGE_URL[@]|$(PACKAGE_URL)|g' \
+ -e 's|@PRIVSEP_USER[@]|$(PRIVSEP_USER)|g' \
+ -e 's|@PRIVSEP_GROUP[@]|$(PRIVSEP_GROUP)|g' \
+ -e 's|@PRIVSEP_CHROOT[@]|$(PRIVSEP_CHROOT)|g' \
+ -e 's|@LLDPD_PID_FILE[@]|$(LLDPD_PID_FILE)|g' \
+ -e 's|@LLDPD_CTL_SOCKET[@]|$(LLDPD_CTL_SOCKET)|g' \
+ -e 's|@PRIVSEP_CHROOT[@]|$(PRIVSEP_CHROOT)|g'
+
+all: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(top_srcdir)/edit.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/daemon/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/daemon/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+$(top_srcdir)/edit.am $(am__empty):
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+install-sbinPROGRAMS: $(sbin_PROGRAMS)
+ @$(NORMAL_INSTALL)
+ @list='$(sbin_PROGRAMS)'; test -n "$(sbindir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(sbindir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(sbindir)" || exit 1; \
+ fi; \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed 's/$(EXEEXT)$$//' | \
+ while read p p1; do if test -f $$p \
+ || test -f $$p1 \
+ ; then echo "$$p"; echo "$$p"; else :; fi; \
+ done | \
+ sed -e 'p;s,.*/,,;n;h' \
+ -e 's|.*|.|' \
+ -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \
+ sed 'N;N;N;s,\n, ,g' | \
+ $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \
+ { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \
+ if ($$2 == $$4) files[d] = files[d] " " $$1; \
+ else { print "f", $$3 "/" $$4, $$1; } } \
+ END { for (d in files) print "f", d, files[d] }' | \
+ while read type dir files; do \
+ if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \
+ test -z "$$files" || { \
+ echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(sbindir)$$dir'"; \
+ $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(sbindir)$$dir" || exit $$?; \
+ } \
+ ; done
+
+uninstall-sbinPROGRAMS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(sbin_PROGRAMS)'; test -n "$(sbindir)" || list=; \
+ files=`for p in $$list; do echo "$$p"; done | \
+ sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \
+ -e 's/$$/$(EXEEXT)/' \
+ `; \
+ test -n "$$list" || exit 0; \
+ echo " ( cd '$(DESTDIR)$(sbindir)' && rm -f" $$files ")"; \
+ cd "$(DESTDIR)$(sbindir)" && rm -f $$files
+
+clean-sbinPROGRAMS:
+ @list='$(sbin_PROGRAMS)'; test -n "$$list" || exit 0; \
+ echo " rm -f" $$list; \
+ rm -f $$list || exit $$?; \
+ test -n "$(EXEEXT)" || exit 0; \
+ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \
+ echo " rm -f" $$list; \
+ rm -f $$list
+
+clean-noinstLTLIBRARIES:
+ -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES)
+ @list='$(noinst_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+liblldpd-snmp.la: $(liblldpd_snmp_la_OBJECTS) $(liblldpd_snmp_la_DEPENDENCIES) $(EXTRA_liblldpd_snmp_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(liblldpd_snmp_la_LINK) $(am_liblldpd_snmp_la_rpath) $(liblldpd_snmp_la_OBJECTS) $(liblldpd_snmp_la_LIBADD) $(LIBS)
+protocols/$(am__dirstamp):
+ @$(MKDIR_P) protocols
+ @: > protocols/$(am__dirstamp)
+protocols/$(DEPDIR)/$(am__dirstamp):
+ @$(MKDIR_P) protocols/$(DEPDIR)
+ @: > protocols/$(DEPDIR)/$(am__dirstamp)
+protocols/liblldpd_la-lldp.lo: protocols/$(am__dirstamp) \
+ protocols/$(DEPDIR)/$(am__dirstamp)
+protocols/liblldpd_la-cdp.lo: protocols/$(am__dirstamp) \
+ protocols/$(DEPDIR)/$(am__dirstamp)
+protocols/liblldpd_la-sonmp.lo: protocols/$(am__dirstamp) \
+ protocols/$(DEPDIR)/$(am__dirstamp)
+protocols/liblldpd_la-edp.lo: protocols/$(am__dirstamp) \
+ protocols/$(DEPDIR)/$(am__dirstamp)
+
+liblldpd.la: $(liblldpd_la_OBJECTS) $(liblldpd_la_DEPENDENCIES) $(EXTRA_liblldpd_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(liblldpd_la_LINK) $(liblldpd_la_OBJECTS) $(liblldpd_la_LIBADD) $(LIBS)
+
+lldpd$(EXEEXT): $(lldpd_OBJECTS) $(lldpd_DEPENDENCIES) $(EXTRA_lldpd_DEPENDENCIES)
+ @rm -f lldpd$(EXEEXT)
+ $(AM_V_CCLD)$(lldpd_LINK) $(lldpd_OBJECTS) $(lldpd_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+ -rm -f protocols/*.$(OBJEXT)
+ -rm -f protocols/*.lo
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblldpd_la-bitmap.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblldpd_la-client.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblldpd_la-dmi-dummy.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblldpd_la-dmi-freebsd.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblldpd_la-dmi-linux.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblldpd_la-dmi-openbsd.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblldpd_la-dmi-osx.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblldpd_la-event.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblldpd_la-forward-bsd.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblldpd_la-forward-linux.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblldpd_la-forward-solaris.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblldpd_la-frame.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblldpd_la-interfaces-bpf.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblldpd_la-interfaces-bsd.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblldpd_la-interfaces-linux.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblldpd_la-interfaces-solaris.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblldpd_la-interfaces.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblldpd_la-lldpd.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblldpd_la-netlink.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblldpd_la-pattern.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblldpd_la-priv-bsd.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblldpd_la-priv-linux.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblldpd_la-priv-seccomp.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblldpd_la-priv.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblldpd_la-privsep.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblldpd_la-privsep_fd.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblldpd_la-privsep_io.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblldpd_snmp_la-agent.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblldpd_snmp_la-agent_priv.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/main.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@protocols/$(DEPDIR)/liblldpd_la-cdp.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@protocols/$(DEPDIR)/liblldpd_la-edp.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@protocols/$(DEPDIR)/liblldpd_la-lldp.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@protocols/$(DEPDIR)/liblldpd_la-sonmp.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\
+@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.obj$$||'`;\
+@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ `$(CYGPATH_W) '$<'` &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.lo$$||'`;\
+@am__fastdepCC_TRUE@ $(LTCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+liblldpd_snmp_la-agent.lo: agent.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(liblldpd_snmp_la_CPPFLAGS) $(CPPFLAGS) $(liblldpd_snmp_la_CFLAGS) $(CFLAGS) -MT liblldpd_snmp_la-agent.lo -MD -MP -MF $(DEPDIR)/liblldpd_snmp_la-agent.Tpo -c -o liblldpd_snmp_la-agent.lo `test -f 'agent.c' || echo '$(srcdir)/'`agent.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/liblldpd_snmp_la-agent.Tpo $(DEPDIR)/liblldpd_snmp_la-agent.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='agent.c' object='liblldpd_snmp_la-agent.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(liblldpd_snmp_la_CPPFLAGS) $(CPPFLAGS) $(liblldpd_snmp_la_CFLAGS) $(CFLAGS) -c -o liblldpd_snmp_la-agent.lo `test -f 'agent.c' || echo '$(srcdir)/'`agent.c
+
+liblldpd_snmp_la-agent_priv.lo: agent_priv.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(liblldpd_snmp_la_CPPFLAGS) $(CPPFLAGS) $(liblldpd_snmp_la_CFLAGS) $(CFLAGS) -MT liblldpd_snmp_la-agent_priv.lo -MD -MP -MF $(DEPDIR)/liblldpd_snmp_la-agent_priv.Tpo -c -o liblldpd_snmp_la-agent_priv.lo `test -f 'agent_priv.c' || echo '$(srcdir)/'`agent_priv.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/liblldpd_snmp_la-agent_priv.Tpo $(DEPDIR)/liblldpd_snmp_la-agent_priv.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='agent_priv.c' object='liblldpd_snmp_la-agent_priv.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(liblldpd_snmp_la_CPPFLAGS) $(CPPFLAGS) $(liblldpd_snmp_la_CFLAGS) $(CFLAGS) -c -o liblldpd_snmp_la-agent_priv.lo `test -f 'agent_priv.c' || echo '$(srcdir)/'`agent_priv.c
+
+liblldpd_la-frame.lo: frame.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(liblldpd_la_CPPFLAGS) $(CPPFLAGS) $(liblldpd_la_CFLAGS) $(CFLAGS) -MT liblldpd_la-frame.lo -MD -MP -MF $(DEPDIR)/liblldpd_la-frame.Tpo -c -o liblldpd_la-frame.lo `test -f 'frame.c' || echo '$(srcdir)/'`frame.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/liblldpd_la-frame.Tpo $(DEPDIR)/liblldpd_la-frame.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='frame.c' object='liblldpd_la-frame.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(liblldpd_la_CPPFLAGS) $(CPPFLAGS) $(liblldpd_la_CFLAGS) $(CFLAGS) -c -o liblldpd_la-frame.lo `test -f 'frame.c' || echo '$(srcdir)/'`frame.c
+
+liblldpd_la-client.lo: client.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(liblldpd_la_CPPFLAGS) $(CPPFLAGS) $(liblldpd_la_CFLAGS) $(CFLAGS) -MT liblldpd_la-client.lo -MD -MP -MF $(DEPDIR)/liblldpd_la-client.Tpo -c -o liblldpd_la-client.lo `test -f 'client.c' || echo '$(srcdir)/'`client.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/liblldpd_la-client.Tpo $(DEPDIR)/liblldpd_la-client.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='client.c' object='liblldpd_la-client.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(liblldpd_la_CPPFLAGS) $(CPPFLAGS) $(liblldpd_la_CFLAGS) $(CFLAGS) -c -o liblldpd_la-client.lo `test -f 'client.c' || echo '$(srcdir)/'`client.c
+
+liblldpd_la-priv.lo: priv.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(liblldpd_la_CPPFLAGS) $(CPPFLAGS) $(liblldpd_la_CFLAGS) $(CFLAGS) -MT liblldpd_la-priv.lo -MD -MP -MF $(DEPDIR)/liblldpd_la-priv.Tpo -c -o liblldpd_la-priv.lo `test -f 'priv.c' || echo '$(srcdir)/'`priv.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/liblldpd_la-priv.Tpo $(DEPDIR)/liblldpd_la-priv.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='priv.c' object='liblldpd_la-priv.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(liblldpd_la_CPPFLAGS) $(CPPFLAGS) $(liblldpd_la_CFLAGS) $(CFLAGS) -c -o liblldpd_la-priv.lo `test -f 'priv.c' || echo '$(srcdir)/'`priv.c
+
+liblldpd_la-privsep.lo: privsep.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(liblldpd_la_CPPFLAGS) $(CPPFLAGS) $(liblldpd_la_CFLAGS) $(CFLAGS) -MT liblldpd_la-privsep.lo -MD -MP -MF $(DEPDIR)/liblldpd_la-privsep.Tpo -c -o liblldpd_la-privsep.lo `test -f 'privsep.c' || echo '$(srcdir)/'`privsep.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/liblldpd_la-privsep.Tpo $(DEPDIR)/liblldpd_la-privsep.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='privsep.c' object='liblldpd_la-privsep.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(liblldpd_la_CPPFLAGS) $(CPPFLAGS) $(liblldpd_la_CFLAGS) $(CFLAGS) -c -o liblldpd_la-privsep.lo `test -f 'privsep.c' || echo '$(srcdir)/'`privsep.c
+
+liblldpd_la-privsep_io.lo: privsep_io.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(liblldpd_la_CPPFLAGS) $(CPPFLAGS) $(liblldpd_la_CFLAGS) $(CFLAGS) -MT liblldpd_la-privsep_io.lo -MD -MP -MF $(DEPDIR)/liblldpd_la-privsep_io.Tpo -c -o liblldpd_la-privsep_io.lo `test -f 'privsep_io.c' || echo '$(srcdir)/'`privsep_io.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/liblldpd_la-privsep_io.Tpo $(DEPDIR)/liblldpd_la-privsep_io.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='privsep_io.c' object='liblldpd_la-privsep_io.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(liblldpd_la_CPPFLAGS) $(CPPFLAGS) $(liblldpd_la_CFLAGS) $(CFLAGS) -c -o liblldpd_la-privsep_io.lo `test -f 'privsep_io.c' || echo '$(srcdir)/'`privsep_io.c
+
+liblldpd_la-privsep_fd.lo: privsep_fd.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(liblldpd_la_CPPFLAGS) $(CPPFLAGS) $(liblldpd_la_CFLAGS) $(CFLAGS) -MT liblldpd_la-privsep_fd.lo -MD -MP -MF $(DEPDIR)/liblldpd_la-privsep_fd.Tpo -c -o liblldpd_la-privsep_fd.lo `test -f 'privsep_fd.c' || echo '$(srcdir)/'`privsep_fd.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/liblldpd_la-privsep_fd.Tpo $(DEPDIR)/liblldpd_la-privsep_fd.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='privsep_fd.c' object='liblldpd_la-privsep_fd.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(liblldpd_la_CPPFLAGS) $(CPPFLAGS) $(liblldpd_la_CFLAGS) $(CFLAGS) -c -o liblldpd_la-privsep_fd.lo `test -f 'privsep_fd.c' || echo '$(srcdir)/'`privsep_fd.c
+
+liblldpd_la-interfaces.lo: interfaces.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(liblldpd_la_CPPFLAGS) $(CPPFLAGS) $(liblldpd_la_CFLAGS) $(CFLAGS) -MT liblldpd_la-interfaces.lo -MD -MP -MF $(DEPDIR)/liblldpd_la-interfaces.Tpo -c -o liblldpd_la-interfaces.lo `test -f 'interfaces.c' || echo '$(srcdir)/'`interfaces.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/liblldpd_la-interfaces.Tpo $(DEPDIR)/liblldpd_la-interfaces.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='interfaces.c' object='liblldpd_la-interfaces.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(liblldpd_la_CPPFLAGS) $(CPPFLAGS) $(liblldpd_la_CFLAGS) $(CFLAGS) -c -o liblldpd_la-interfaces.lo `test -f 'interfaces.c' || echo '$(srcdir)/'`interfaces.c
+
+liblldpd_la-event.lo: event.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(liblldpd_la_CPPFLAGS) $(CPPFLAGS) $(liblldpd_la_CFLAGS) $(CFLAGS) -MT liblldpd_la-event.lo -MD -MP -MF $(DEPDIR)/liblldpd_la-event.Tpo -c -o liblldpd_la-event.lo `test -f 'event.c' || echo '$(srcdir)/'`event.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/liblldpd_la-event.Tpo $(DEPDIR)/liblldpd_la-event.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='event.c' object='liblldpd_la-event.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(liblldpd_la_CPPFLAGS) $(CPPFLAGS) $(liblldpd_la_CFLAGS) $(CFLAGS) -c -o liblldpd_la-event.lo `test -f 'event.c' || echo '$(srcdir)/'`event.c
+
+liblldpd_la-lldpd.lo: lldpd.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(liblldpd_la_CPPFLAGS) $(CPPFLAGS) $(liblldpd_la_CFLAGS) $(CFLAGS) -MT liblldpd_la-lldpd.lo -MD -MP -MF $(DEPDIR)/liblldpd_la-lldpd.Tpo -c -o liblldpd_la-lldpd.lo `test -f 'lldpd.c' || echo '$(srcdir)/'`lldpd.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/liblldpd_la-lldpd.Tpo $(DEPDIR)/liblldpd_la-lldpd.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='lldpd.c' object='liblldpd_la-lldpd.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(liblldpd_la_CPPFLAGS) $(CPPFLAGS) $(liblldpd_la_CFLAGS) $(CFLAGS) -c -o liblldpd_la-lldpd.lo `test -f 'lldpd.c' || echo '$(srcdir)/'`lldpd.c
+
+liblldpd_la-pattern.lo: pattern.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(liblldpd_la_CPPFLAGS) $(CPPFLAGS) $(liblldpd_la_CFLAGS) $(CFLAGS) -MT liblldpd_la-pattern.lo -MD -MP -MF $(DEPDIR)/liblldpd_la-pattern.Tpo -c -o liblldpd_la-pattern.lo `test -f 'pattern.c' || echo '$(srcdir)/'`pattern.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/liblldpd_la-pattern.Tpo $(DEPDIR)/liblldpd_la-pattern.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='pattern.c' object='liblldpd_la-pattern.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(liblldpd_la_CPPFLAGS) $(CPPFLAGS) $(liblldpd_la_CFLAGS) $(CFLAGS) -c -o liblldpd_la-pattern.lo `test -f 'pattern.c' || echo '$(srcdir)/'`pattern.c
+
+liblldpd_la-bitmap.lo: bitmap.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(liblldpd_la_CPPFLAGS) $(CPPFLAGS) $(liblldpd_la_CFLAGS) $(CFLAGS) -MT liblldpd_la-bitmap.lo -MD -MP -MF $(DEPDIR)/liblldpd_la-bitmap.Tpo -c -o liblldpd_la-bitmap.lo `test -f 'bitmap.c' || echo '$(srcdir)/'`bitmap.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/liblldpd_la-bitmap.Tpo $(DEPDIR)/liblldpd_la-bitmap.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='bitmap.c' object='liblldpd_la-bitmap.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(liblldpd_la_CPPFLAGS) $(CPPFLAGS) $(liblldpd_la_CFLAGS) $(CFLAGS) -c -o liblldpd_la-bitmap.lo `test -f 'bitmap.c' || echo '$(srcdir)/'`bitmap.c
+
+protocols/liblldpd_la-lldp.lo: protocols/lldp.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(liblldpd_la_CPPFLAGS) $(CPPFLAGS) $(liblldpd_la_CFLAGS) $(CFLAGS) -MT protocols/liblldpd_la-lldp.lo -MD -MP -MF protocols/$(DEPDIR)/liblldpd_la-lldp.Tpo -c -o protocols/liblldpd_la-lldp.lo `test -f 'protocols/lldp.c' || echo '$(srcdir)/'`protocols/lldp.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) protocols/$(DEPDIR)/liblldpd_la-lldp.Tpo protocols/$(DEPDIR)/liblldpd_la-lldp.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='protocols/lldp.c' object='protocols/liblldpd_la-lldp.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(liblldpd_la_CPPFLAGS) $(CPPFLAGS) $(liblldpd_la_CFLAGS) $(CFLAGS) -c -o protocols/liblldpd_la-lldp.lo `test -f 'protocols/lldp.c' || echo '$(srcdir)/'`protocols/lldp.c
+
+protocols/liblldpd_la-cdp.lo: protocols/cdp.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(liblldpd_la_CPPFLAGS) $(CPPFLAGS) $(liblldpd_la_CFLAGS) $(CFLAGS) -MT protocols/liblldpd_la-cdp.lo -MD -MP -MF protocols/$(DEPDIR)/liblldpd_la-cdp.Tpo -c -o protocols/liblldpd_la-cdp.lo `test -f 'protocols/cdp.c' || echo '$(srcdir)/'`protocols/cdp.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) protocols/$(DEPDIR)/liblldpd_la-cdp.Tpo protocols/$(DEPDIR)/liblldpd_la-cdp.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='protocols/cdp.c' object='protocols/liblldpd_la-cdp.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(liblldpd_la_CPPFLAGS) $(CPPFLAGS) $(liblldpd_la_CFLAGS) $(CFLAGS) -c -o protocols/liblldpd_la-cdp.lo `test -f 'protocols/cdp.c' || echo '$(srcdir)/'`protocols/cdp.c
+
+protocols/liblldpd_la-sonmp.lo: protocols/sonmp.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(liblldpd_la_CPPFLAGS) $(CPPFLAGS) $(liblldpd_la_CFLAGS) $(CFLAGS) -MT protocols/liblldpd_la-sonmp.lo -MD -MP -MF protocols/$(DEPDIR)/liblldpd_la-sonmp.Tpo -c -o protocols/liblldpd_la-sonmp.lo `test -f 'protocols/sonmp.c' || echo '$(srcdir)/'`protocols/sonmp.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) protocols/$(DEPDIR)/liblldpd_la-sonmp.Tpo protocols/$(DEPDIR)/liblldpd_la-sonmp.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='protocols/sonmp.c' object='protocols/liblldpd_la-sonmp.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(liblldpd_la_CPPFLAGS) $(CPPFLAGS) $(liblldpd_la_CFLAGS) $(CFLAGS) -c -o protocols/liblldpd_la-sonmp.lo `test -f 'protocols/sonmp.c' || echo '$(srcdir)/'`protocols/sonmp.c
+
+protocols/liblldpd_la-edp.lo: protocols/edp.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(liblldpd_la_CPPFLAGS) $(CPPFLAGS) $(liblldpd_la_CFLAGS) $(CFLAGS) -MT protocols/liblldpd_la-edp.lo -MD -MP -MF protocols/$(DEPDIR)/liblldpd_la-edp.Tpo -c -o protocols/liblldpd_la-edp.lo `test -f 'protocols/edp.c' || echo '$(srcdir)/'`protocols/edp.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) protocols/$(DEPDIR)/liblldpd_la-edp.Tpo protocols/$(DEPDIR)/liblldpd_la-edp.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='protocols/edp.c' object='protocols/liblldpd_la-edp.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(liblldpd_la_CPPFLAGS) $(CPPFLAGS) $(liblldpd_la_CFLAGS) $(CFLAGS) -c -o protocols/liblldpd_la-edp.lo `test -f 'protocols/edp.c' || echo '$(srcdir)/'`protocols/edp.c
+
+liblldpd_la-forward-linux.lo: forward-linux.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(liblldpd_la_CPPFLAGS) $(CPPFLAGS) $(liblldpd_la_CFLAGS) $(CFLAGS) -MT liblldpd_la-forward-linux.lo -MD -MP -MF $(DEPDIR)/liblldpd_la-forward-linux.Tpo -c -o liblldpd_la-forward-linux.lo `test -f 'forward-linux.c' || echo '$(srcdir)/'`forward-linux.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/liblldpd_la-forward-linux.Tpo $(DEPDIR)/liblldpd_la-forward-linux.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='forward-linux.c' object='liblldpd_la-forward-linux.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(liblldpd_la_CPPFLAGS) $(CPPFLAGS) $(liblldpd_la_CFLAGS) $(CFLAGS) -c -o liblldpd_la-forward-linux.lo `test -f 'forward-linux.c' || echo '$(srcdir)/'`forward-linux.c
+
+liblldpd_la-interfaces-linux.lo: interfaces-linux.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(liblldpd_la_CPPFLAGS) $(CPPFLAGS) $(liblldpd_la_CFLAGS) $(CFLAGS) -MT liblldpd_la-interfaces-linux.lo -MD -MP -MF $(DEPDIR)/liblldpd_la-interfaces-linux.Tpo -c -o liblldpd_la-interfaces-linux.lo `test -f 'interfaces-linux.c' || echo '$(srcdir)/'`interfaces-linux.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/liblldpd_la-interfaces-linux.Tpo $(DEPDIR)/liblldpd_la-interfaces-linux.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='interfaces-linux.c' object='liblldpd_la-interfaces-linux.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(liblldpd_la_CPPFLAGS) $(CPPFLAGS) $(liblldpd_la_CFLAGS) $(CFLAGS) -c -o liblldpd_la-interfaces-linux.lo `test -f 'interfaces-linux.c' || echo '$(srcdir)/'`interfaces-linux.c
+
+liblldpd_la-netlink.lo: netlink.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(liblldpd_la_CPPFLAGS) $(CPPFLAGS) $(liblldpd_la_CFLAGS) $(CFLAGS) -MT liblldpd_la-netlink.lo -MD -MP -MF $(DEPDIR)/liblldpd_la-netlink.Tpo -c -o liblldpd_la-netlink.lo `test -f 'netlink.c' || echo '$(srcdir)/'`netlink.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/liblldpd_la-netlink.Tpo $(DEPDIR)/liblldpd_la-netlink.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='netlink.c' object='liblldpd_la-netlink.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(liblldpd_la_CPPFLAGS) $(CPPFLAGS) $(liblldpd_la_CFLAGS) $(CFLAGS) -c -o liblldpd_la-netlink.lo `test -f 'netlink.c' || echo '$(srcdir)/'`netlink.c
+
+liblldpd_la-dmi-linux.lo: dmi-linux.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(liblldpd_la_CPPFLAGS) $(CPPFLAGS) $(liblldpd_la_CFLAGS) $(CFLAGS) -MT liblldpd_la-dmi-linux.lo -MD -MP -MF $(DEPDIR)/liblldpd_la-dmi-linux.Tpo -c -o liblldpd_la-dmi-linux.lo `test -f 'dmi-linux.c' || echo '$(srcdir)/'`dmi-linux.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/liblldpd_la-dmi-linux.Tpo $(DEPDIR)/liblldpd_la-dmi-linux.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='dmi-linux.c' object='liblldpd_la-dmi-linux.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(liblldpd_la_CPPFLAGS) $(CPPFLAGS) $(liblldpd_la_CFLAGS) $(CFLAGS) -c -o liblldpd_la-dmi-linux.lo `test -f 'dmi-linux.c' || echo '$(srcdir)/'`dmi-linux.c
+
+liblldpd_la-priv-linux.lo: priv-linux.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(liblldpd_la_CPPFLAGS) $(CPPFLAGS) $(liblldpd_la_CFLAGS) $(CFLAGS) -MT liblldpd_la-priv-linux.lo -MD -MP -MF $(DEPDIR)/liblldpd_la-priv-linux.Tpo -c -o liblldpd_la-priv-linux.lo `test -f 'priv-linux.c' || echo '$(srcdir)/'`priv-linux.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/liblldpd_la-priv-linux.Tpo $(DEPDIR)/liblldpd_la-priv-linux.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='priv-linux.c' object='liblldpd_la-priv-linux.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(liblldpd_la_CPPFLAGS) $(CPPFLAGS) $(liblldpd_la_CFLAGS) $(CFLAGS) -c -o liblldpd_la-priv-linux.lo `test -f 'priv-linux.c' || echo '$(srcdir)/'`priv-linux.c
+
+liblldpd_la-forward-bsd.lo: forward-bsd.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(liblldpd_la_CPPFLAGS) $(CPPFLAGS) $(liblldpd_la_CFLAGS) $(CFLAGS) -MT liblldpd_la-forward-bsd.lo -MD -MP -MF $(DEPDIR)/liblldpd_la-forward-bsd.Tpo -c -o liblldpd_la-forward-bsd.lo `test -f 'forward-bsd.c' || echo '$(srcdir)/'`forward-bsd.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/liblldpd_la-forward-bsd.Tpo $(DEPDIR)/liblldpd_la-forward-bsd.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='forward-bsd.c' object='liblldpd_la-forward-bsd.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(liblldpd_la_CPPFLAGS) $(CPPFLAGS) $(liblldpd_la_CFLAGS) $(CFLAGS) -c -o liblldpd_la-forward-bsd.lo `test -f 'forward-bsd.c' || echo '$(srcdir)/'`forward-bsd.c
+
+liblldpd_la-interfaces-bpf.lo: interfaces-bpf.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(liblldpd_la_CPPFLAGS) $(CPPFLAGS) $(liblldpd_la_CFLAGS) $(CFLAGS) -MT liblldpd_la-interfaces-bpf.lo -MD -MP -MF $(DEPDIR)/liblldpd_la-interfaces-bpf.Tpo -c -o liblldpd_la-interfaces-bpf.lo `test -f 'interfaces-bpf.c' || echo '$(srcdir)/'`interfaces-bpf.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/liblldpd_la-interfaces-bpf.Tpo $(DEPDIR)/liblldpd_la-interfaces-bpf.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='interfaces-bpf.c' object='liblldpd_la-interfaces-bpf.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(liblldpd_la_CPPFLAGS) $(CPPFLAGS) $(liblldpd_la_CFLAGS) $(CFLAGS) -c -o liblldpd_la-interfaces-bpf.lo `test -f 'interfaces-bpf.c' || echo '$(srcdir)/'`interfaces-bpf.c
+
+liblldpd_la-interfaces-bsd.lo: interfaces-bsd.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(liblldpd_la_CPPFLAGS) $(CPPFLAGS) $(liblldpd_la_CFLAGS) $(CFLAGS) -MT liblldpd_la-interfaces-bsd.lo -MD -MP -MF $(DEPDIR)/liblldpd_la-interfaces-bsd.Tpo -c -o liblldpd_la-interfaces-bsd.lo `test -f 'interfaces-bsd.c' || echo '$(srcdir)/'`interfaces-bsd.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/liblldpd_la-interfaces-bsd.Tpo $(DEPDIR)/liblldpd_la-interfaces-bsd.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='interfaces-bsd.c' object='liblldpd_la-interfaces-bsd.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(liblldpd_la_CPPFLAGS) $(CPPFLAGS) $(liblldpd_la_CFLAGS) $(CFLAGS) -c -o liblldpd_la-interfaces-bsd.lo `test -f 'interfaces-bsd.c' || echo '$(srcdir)/'`interfaces-bsd.c
+
+liblldpd_la-dmi-dummy.lo: dmi-dummy.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(liblldpd_la_CPPFLAGS) $(CPPFLAGS) $(liblldpd_la_CFLAGS) $(CFLAGS) -MT liblldpd_la-dmi-dummy.lo -MD -MP -MF $(DEPDIR)/liblldpd_la-dmi-dummy.Tpo -c -o liblldpd_la-dmi-dummy.lo `test -f 'dmi-dummy.c' || echo '$(srcdir)/'`dmi-dummy.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/liblldpd_la-dmi-dummy.Tpo $(DEPDIR)/liblldpd_la-dmi-dummy.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='dmi-dummy.c' object='liblldpd_la-dmi-dummy.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(liblldpd_la_CPPFLAGS) $(CPPFLAGS) $(liblldpd_la_CFLAGS) $(CFLAGS) -c -o liblldpd_la-dmi-dummy.lo `test -f 'dmi-dummy.c' || echo '$(srcdir)/'`dmi-dummy.c
+
+liblldpd_la-priv-bsd.lo: priv-bsd.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(liblldpd_la_CPPFLAGS) $(CPPFLAGS) $(liblldpd_la_CFLAGS) $(CFLAGS) -MT liblldpd_la-priv-bsd.lo -MD -MP -MF $(DEPDIR)/liblldpd_la-priv-bsd.Tpo -c -o liblldpd_la-priv-bsd.lo `test -f 'priv-bsd.c' || echo '$(srcdir)/'`priv-bsd.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/liblldpd_la-priv-bsd.Tpo $(DEPDIR)/liblldpd_la-priv-bsd.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='priv-bsd.c' object='liblldpd_la-priv-bsd.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(liblldpd_la_CPPFLAGS) $(CPPFLAGS) $(liblldpd_la_CFLAGS) $(CFLAGS) -c -o liblldpd_la-priv-bsd.lo `test -f 'priv-bsd.c' || echo '$(srcdir)/'`priv-bsd.c
+
+liblldpd_la-dmi-freebsd.lo: dmi-freebsd.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(liblldpd_la_CPPFLAGS) $(CPPFLAGS) $(liblldpd_la_CFLAGS) $(CFLAGS) -MT liblldpd_la-dmi-freebsd.lo -MD -MP -MF $(DEPDIR)/liblldpd_la-dmi-freebsd.Tpo -c -o liblldpd_la-dmi-freebsd.lo `test -f 'dmi-freebsd.c' || echo '$(srcdir)/'`dmi-freebsd.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/liblldpd_la-dmi-freebsd.Tpo $(DEPDIR)/liblldpd_la-dmi-freebsd.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='dmi-freebsd.c' object='liblldpd_la-dmi-freebsd.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(liblldpd_la_CPPFLAGS) $(CPPFLAGS) $(liblldpd_la_CFLAGS) $(CFLAGS) -c -o liblldpd_la-dmi-freebsd.lo `test -f 'dmi-freebsd.c' || echo '$(srcdir)/'`dmi-freebsd.c
+
+liblldpd_la-dmi-openbsd.lo: dmi-openbsd.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(liblldpd_la_CPPFLAGS) $(CPPFLAGS) $(liblldpd_la_CFLAGS) $(CFLAGS) -MT liblldpd_la-dmi-openbsd.lo -MD -MP -MF $(DEPDIR)/liblldpd_la-dmi-openbsd.Tpo -c -o liblldpd_la-dmi-openbsd.lo `test -f 'dmi-openbsd.c' || echo '$(srcdir)/'`dmi-openbsd.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/liblldpd_la-dmi-openbsd.Tpo $(DEPDIR)/liblldpd_la-dmi-openbsd.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='dmi-openbsd.c' object='liblldpd_la-dmi-openbsd.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(liblldpd_la_CPPFLAGS) $(CPPFLAGS) $(liblldpd_la_CFLAGS) $(CFLAGS) -c -o liblldpd_la-dmi-openbsd.lo `test -f 'dmi-openbsd.c' || echo '$(srcdir)/'`dmi-openbsd.c
+
+liblldpd_la-dmi-osx.lo: dmi-osx.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(liblldpd_la_CPPFLAGS) $(CPPFLAGS) $(liblldpd_la_CFLAGS) $(CFLAGS) -MT liblldpd_la-dmi-osx.lo -MD -MP -MF $(DEPDIR)/liblldpd_la-dmi-osx.Tpo -c -o liblldpd_la-dmi-osx.lo `test -f 'dmi-osx.c' || echo '$(srcdir)/'`dmi-osx.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/liblldpd_la-dmi-osx.Tpo $(DEPDIR)/liblldpd_la-dmi-osx.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='dmi-osx.c' object='liblldpd_la-dmi-osx.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(liblldpd_la_CPPFLAGS) $(CPPFLAGS) $(liblldpd_la_CFLAGS) $(CFLAGS) -c -o liblldpd_la-dmi-osx.lo `test -f 'dmi-osx.c' || echo '$(srcdir)/'`dmi-osx.c
+
+liblldpd_la-forward-solaris.lo: forward-solaris.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(liblldpd_la_CPPFLAGS) $(CPPFLAGS) $(liblldpd_la_CFLAGS) $(CFLAGS) -MT liblldpd_la-forward-solaris.lo -MD -MP -MF $(DEPDIR)/liblldpd_la-forward-solaris.Tpo -c -o liblldpd_la-forward-solaris.lo `test -f 'forward-solaris.c' || echo '$(srcdir)/'`forward-solaris.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/liblldpd_la-forward-solaris.Tpo $(DEPDIR)/liblldpd_la-forward-solaris.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='forward-solaris.c' object='liblldpd_la-forward-solaris.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(liblldpd_la_CPPFLAGS) $(CPPFLAGS) $(liblldpd_la_CFLAGS) $(CFLAGS) -c -o liblldpd_la-forward-solaris.lo `test -f 'forward-solaris.c' || echo '$(srcdir)/'`forward-solaris.c
+
+liblldpd_la-interfaces-solaris.lo: interfaces-solaris.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(liblldpd_la_CPPFLAGS) $(CPPFLAGS) $(liblldpd_la_CFLAGS) $(CFLAGS) -MT liblldpd_la-interfaces-solaris.lo -MD -MP -MF $(DEPDIR)/liblldpd_la-interfaces-solaris.Tpo -c -o liblldpd_la-interfaces-solaris.lo `test -f 'interfaces-solaris.c' || echo '$(srcdir)/'`interfaces-solaris.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/liblldpd_la-interfaces-solaris.Tpo $(DEPDIR)/liblldpd_la-interfaces-solaris.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='interfaces-solaris.c' object='liblldpd_la-interfaces-solaris.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(liblldpd_la_CPPFLAGS) $(CPPFLAGS) $(liblldpd_la_CFLAGS) $(CFLAGS) -c -o liblldpd_la-interfaces-solaris.lo `test -f 'interfaces-solaris.c' || echo '$(srcdir)/'`interfaces-solaris.c
+
+liblldpd_la-priv-seccomp.lo: priv-seccomp.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(liblldpd_la_CPPFLAGS) $(CPPFLAGS) $(liblldpd_la_CFLAGS) $(CFLAGS) -MT liblldpd_la-priv-seccomp.lo -MD -MP -MF $(DEPDIR)/liblldpd_la-priv-seccomp.Tpo -c -o liblldpd_la-priv-seccomp.lo `test -f 'priv-seccomp.c' || echo '$(srcdir)/'`priv-seccomp.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/liblldpd_la-priv-seccomp.Tpo $(DEPDIR)/liblldpd_la-priv-seccomp.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='priv-seccomp.c' object='liblldpd_la-priv-seccomp.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(liblldpd_la_CPPFLAGS) $(CPPFLAGS) $(liblldpd_la_CFLAGS) $(CFLAGS) -c -o liblldpd_la-priv-seccomp.lo `test -f 'priv-seccomp.c' || echo '$(srcdir)/'`priv-seccomp.c
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+ -rm -rf protocols/.libs protocols/_libs
+install-man8: $(man_MANS)
+ @$(NORMAL_INSTALL)
+ @list1=''; \
+ list2='$(man_MANS)'; \
+ test -n "$(man8dir)" \
+ && test -n "`echo $$list1$$list2`" \
+ || exit 0; \
+ echo " $(MKDIR_P) '$(DESTDIR)$(man8dir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(man8dir)" || exit 1; \
+ { for i in $$list1; do echo "$$i"; done; \
+ if test -n "$$list2"; then \
+ for i in $$list2; do echo "$$i"; done \
+ | sed -n '/\.8[a-z]*$$/p'; \
+ fi; \
+ } | while read p; do \
+ if test -f $$p; then d=; else d="$(srcdir)/"; fi; \
+ echo "$$d$$p"; echo "$$p"; \
+ done | \
+ sed -e 'n;s,.*/,,;p;h;s,.*\.,,;s,^[^8][0-9a-z]*$$,8,;x' \
+ -e 's,\.[0-9a-z]*$$,,;$(transform);G;s,\n,.,' | \
+ sed 'N;N;s,\n, ,g' | { \
+ list=; while read file base inst; do \
+ if test "$$base" = "$$inst"; then list="$$list $$file"; else \
+ echo " $(INSTALL_DATA) '$$file' '$(DESTDIR)$(man8dir)/$$inst'"; \
+ $(INSTALL_DATA) "$$file" "$(DESTDIR)$(man8dir)/$$inst" || exit $$?; \
+ fi; \
+ done; \
+ for i in $$list; do echo "$$i"; done | $(am__base_list) | \
+ while read files; do \
+ test -z "$$files" || { \
+ echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(man8dir)'"; \
+ $(INSTALL_DATA) $$files "$(DESTDIR)$(man8dir)" || exit $$?; }; \
+ done; }
+
+uninstall-man8:
+ @$(NORMAL_UNINSTALL)
+ @list=''; test -n "$(man8dir)" || exit 0; \
+ files=`{ for i in $$list; do echo "$$i"; done; \
+ l2='$(man_MANS)'; for i in $$l2; do echo "$$i"; done | \
+ sed -n '/\.8[a-z]*$$/p'; \
+ } | sed -e 's,.*/,,;h;s,.*\.,,;s,^[^8][0-9a-z]*$$,8,;x' \
+ -e 's,\.[0-9a-z]*$$,,;$(transform);G;s,\n,.,'`; \
+ dir='$(DESTDIR)$(man8dir)'; $(am__uninstall_files_from_dir)
+install-apparmorDATA: $(apparmor_DATA)
+ @$(NORMAL_INSTALL)
+ @list='$(apparmor_DATA)'; test -n "$(apparmordir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(apparmordir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(apparmordir)" || exit 1; \
+ fi; \
+ for p in $$list; do \
+ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+ echo "$$d$$p"; \
+ done | $(am__base_list) | \
+ while read files; do \
+ echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(apparmordir)'"; \
+ $(INSTALL_DATA) $$files "$(DESTDIR)$(apparmordir)" || exit $$?; \
+ done
+
+uninstall-apparmorDATA:
+ @$(NORMAL_UNINSTALL)
+ @list='$(apparmor_DATA)'; test -n "$(apparmordir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(apparmordir)'; $(am__uninstall_files_from_dir)
+install-systemdsystemunitDATA: $(systemdsystemunit_DATA)
+ @$(NORMAL_INSTALL)
+ @list='$(systemdsystemunit_DATA)'; test -n "$(systemdsystemunitdir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(systemdsystemunitdir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(systemdsystemunitdir)" || exit 1; \
+ fi; \
+ for p in $$list; do \
+ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+ echo "$$d$$p"; \
+ done | $(am__base_list) | \
+ while read files; do \
+ echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(systemdsystemunitdir)'"; \
+ $(INSTALL_DATA) $$files "$(DESTDIR)$(systemdsystemunitdir)" || exit $$?; \
+ done
+
+uninstall-systemdsystemunitDATA:
+ @$(NORMAL_UNINSTALL)
+ @list='$(systemdsystemunit_DATA)'; test -n "$(systemdsystemunitdir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(systemdsystemunitdir)'; $(am__uninstall_files_from_dir)
+install-sysusersDATA: $(sysusers_DATA)
+ @$(NORMAL_INSTALL)
+ @list='$(sysusers_DATA)'; test -n "$(sysusersdir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(sysusersdir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(sysusersdir)" || exit 1; \
+ fi; \
+ for p in $$list; do \
+ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+ echo "$$d$$p"; \
+ done | $(am__base_list) | \
+ while read files; do \
+ echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(sysusersdir)'"; \
+ $(INSTALL_DATA) $$files "$(DESTDIR)$(sysusersdir)" || exit $$?; \
+ done
+
+uninstall-sysusersDATA:
+ @$(NORMAL_UNINSTALL)
+ @list='$(sysusers_DATA)'; test -n "$(sysusersdir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(sysusersdir)'; $(am__uninstall_files_from_dir)
+install-tapsetDATA: $(tapset_DATA)
+ @$(NORMAL_INSTALL)
+ @list='$(tapset_DATA)'; test -n "$(tapsetdir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(tapsetdir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(tapsetdir)" || exit 1; \
+ fi; \
+ for p in $$list; do \
+ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+ echo "$$d$$p"; \
+ done | $(am__base_list) | \
+ while read files; do \
+ echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(tapsetdir)'"; \
+ $(INSTALL_DATA) $$files "$(DESTDIR)$(tapsetdir)" || exit $$?; \
+ done
+
+uninstall-tapsetDATA:
+ @$(NORMAL_UNINSTALL)
+ @list='$(tapset_DATA)'; test -n "$(tapsetdir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(tapsetdir)'; $(am__uninstall_files_from_dir)
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) check-am
+all-am: Makefile $(PROGRAMS) $(LTLIBRARIES) $(MANS) $(DATA)
+installdirs:
+ for dir in "$(DESTDIR)$(sbindir)" "$(DESTDIR)$(man8dir)" "$(DESTDIR)$(apparmordir)" "$(DESTDIR)$(systemdsystemunitdir)" "$(DESTDIR)$(sysusersdir)" "$(DESTDIR)$(tapsetdir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) install-am
+install-exec: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+ -test -z "$(CLEANFILES)" || rm -f $(CLEANFILES)
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+ -rm -f protocols/$(DEPDIR)/$(am__dirstamp)
+ -rm -f protocols/$(am__dirstamp)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+ -test -z "$(BUILT_SOURCES)" || rm -f $(BUILT_SOURCES)
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ clean-sbinPROGRAMS mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/liblldpd_la-bitmap.Plo
+ -rm -f ./$(DEPDIR)/liblldpd_la-client.Plo
+ -rm -f ./$(DEPDIR)/liblldpd_la-dmi-dummy.Plo
+ -rm -f ./$(DEPDIR)/liblldpd_la-dmi-freebsd.Plo
+ -rm -f ./$(DEPDIR)/liblldpd_la-dmi-linux.Plo
+ -rm -f ./$(DEPDIR)/liblldpd_la-dmi-openbsd.Plo
+ -rm -f ./$(DEPDIR)/liblldpd_la-dmi-osx.Plo
+ -rm -f ./$(DEPDIR)/liblldpd_la-event.Plo
+ -rm -f ./$(DEPDIR)/liblldpd_la-forward-bsd.Plo
+ -rm -f ./$(DEPDIR)/liblldpd_la-forward-linux.Plo
+ -rm -f ./$(DEPDIR)/liblldpd_la-forward-solaris.Plo
+ -rm -f ./$(DEPDIR)/liblldpd_la-frame.Plo
+ -rm -f ./$(DEPDIR)/liblldpd_la-interfaces-bpf.Plo
+ -rm -f ./$(DEPDIR)/liblldpd_la-interfaces-bsd.Plo
+ -rm -f ./$(DEPDIR)/liblldpd_la-interfaces-linux.Plo
+ -rm -f ./$(DEPDIR)/liblldpd_la-interfaces-solaris.Plo
+ -rm -f ./$(DEPDIR)/liblldpd_la-interfaces.Plo
+ -rm -f ./$(DEPDIR)/liblldpd_la-lldpd.Plo
+ -rm -f ./$(DEPDIR)/liblldpd_la-netlink.Plo
+ -rm -f ./$(DEPDIR)/liblldpd_la-pattern.Plo
+ -rm -f ./$(DEPDIR)/liblldpd_la-priv-bsd.Plo
+ -rm -f ./$(DEPDIR)/liblldpd_la-priv-linux.Plo
+ -rm -f ./$(DEPDIR)/liblldpd_la-priv-seccomp.Plo
+ -rm -f ./$(DEPDIR)/liblldpd_la-priv.Plo
+ -rm -f ./$(DEPDIR)/liblldpd_la-privsep.Plo
+ -rm -f ./$(DEPDIR)/liblldpd_la-privsep_fd.Plo
+ -rm -f ./$(DEPDIR)/liblldpd_la-privsep_io.Plo
+ -rm -f ./$(DEPDIR)/liblldpd_snmp_la-agent.Plo
+ -rm -f ./$(DEPDIR)/liblldpd_snmp_la-agent_priv.Plo
+ -rm -f ./$(DEPDIR)/main.Po
+ -rm -f protocols/$(DEPDIR)/liblldpd_la-cdp.Plo
+ -rm -f protocols/$(DEPDIR)/liblldpd_la-edp.Plo
+ -rm -f protocols/$(DEPDIR)/liblldpd_la-lldp.Plo
+ -rm -f protocols/$(DEPDIR)/liblldpd_la-sonmp.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am: install-apparmorDATA install-man \
+ install-systemdsystemunitDATA install-sysusersDATA \
+ install-tapsetDATA
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am: install-sbinPROGRAMS
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man: install-man8
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/liblldpd_la-bitmap.Plo
+ -rm -f ./$(DEPDIR)/liblldpd_la-client.Plo
+ -rm -f ./$(DEPDIR)/liblldpd_la-dmi-dummy.Plo
+ -rm -f ./$(DEPDIR)/liblldpd_la-dmi-freebsd.Plo
+ -rm -f ./$(DEPDIR)/liblldpd_la-dmi-linux.Plo
+ -rm -f ./$(DEPDIR)/liblldpd_la-dmi-openbsd.Plo
+ -rm -f ./$(DEPDIR)/liblldpd_la-dmi-osx.Plo
+ -rm -f ./$(DEPDIR)/liblldpd_la-event.Plo
+ -rm -f ./$(DEPDIR)/liblldpd_la-forward-bsd.Plo
+ -rm -f ./$(DEPDIR)/liblldpd_la-forward-linux.Plo
+ -rm -f ./$(DEPDIR)/liblldpd_la-forward-solaris.Plo
+ -rm -f ./$(DEPDIR)/liblldpd_la-frame.Plo
+ -rm -f ./$(DEPDIR)/liblldpd_la-interfaces-bpf.Plo
+ -rm -f ./$(DEPDIR)/liblldpd_la-interfaces-bsd.Plo
+ -rm -f ./$(DEPDIR)/liblldpd_la-interfaces-linux.Plo
+ -rm -f ./$(DEPDIR)/liblldpd_la-interfaces-solaris.Plo
+ -rm -f ./$(DEPDIR)/liblldpd_la-interfaces.Plo
+ -rm -f ./$(DEPDIR)/liblldpd_la-lldpd.Plo
+ -rm -f ./$(DEPDIR)/liblldpd_la-netlink.Plo
+ -rm -f ./$(DEPDIR)/liblldpd_la-pattern.Plo
+ -rm -f ./$(DEPDIR)/liblldpd_la-priv-bsd.Plo
+ -rm -f ./$(DEPDIR)/liblldpd_la-priv-linux.Plo
+ -rm -f ./$(DEPDIR)/liblldpd_la-priv-seccomp.Plo
+ -rm -f ./$(DEPDIR)/liblldpd_la-priv.Plo
+ -rm -f ./$(DEPDIR)/liblldpd_la-privsep.Plo
+ -rm -f ./$(DEPDIR)/liblldpd_la-privsep_fd.Plo
+ -rm -f ./$(DEPDIR)/liblldpd_la-privsep_io.Plo
+ -rm -f ./$(DEPDIR)/liblldpd_snmp_la-agent.Plo
+ -rm -f ./$(DEPDIR)/liblldpd_snmp_la-agent_priv.Plo
+ -rm -f ./$(DEPDIR)/main.Po
+ -rm -f protocols/$(DEPDIR)/liblldpd_la-cdp.Plo
+ -rm -f protocols/$(DEPDIR)/liblldpd_la-edp.Plo
+ -rm -f protocols/$(DEPDIR)/liblldpd_la-lldp.Plo
+ -rm -f protocols/$(DEPDIR)/liblldpd_la-sonmp.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-apparmorDATA uninstall-man \
+ uninstall-sbinPROGRAMS uninstall-systemdsystemunitDATA \
+ uninstall-sysusersDATA uninstall-tapsetDATA
+
+uninstall-man: uninstall-man8
+
+.MAKE: all check install install-am install-exec install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ clean-sbinPROGRAMS cscopelist-am ctags ctags-am distclean \
+ distclean-compile distclean-generic distclean-libtool \
+ distclean-tags distdir dvi dvi-am html html-am info info-am \
+ install install-am install-apparmorDATA install-data \
+ install-data-am install-dvi install-dvi-am install-exec \
+ install-exec-am install-html install-html-am install-info \
+ install-info-am install-man install-man8 install-pdf \
+ install-pdf-am install-ps install-ps-am install-sbinPROGRAMS \
+ install-strip install-systemdsystemunitDATA \
+ install-sysusersDATA install-tapsetDATA installcheck \
+ installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am uninstall-apparmorDATA \
+ uninstall-man uninstall-man8 uninstall-sbinPROGRAMS \
+ uninstall-systemdsystemunitDATA uninstall-sysusersDATA \
+ uninstall-tapsetDATA
+
+.PRECIOUS: Makefile
+
+@USE_SECCOMP_TRUE@syscall-names.h:
+@USE_SECCOMP_TRUE@ $(AM_V_GEN)
+@USE_SECCOMP_TRUE@ $(AM_V_at)echo "#include <sys/syscall.h>" | $(CPP) -dM - > $@.tmp ;\
+@USE_SECCOMP_TRUE@ echo "static const char *syscall_names[] = {" > $@ ;\
+@USE_SECCOMP_TRUE@ grep '^#define __NR_' $@.tmp | \
+@USE_SECCOMP_TRUE@ LC_ALL=C sed -r -n -e 's/^\#define[ \t]+__NR_([a-z0-9_]+)[ \t]+([0-9]+)(.*)/ [\2] = "\1",/p' >> $@ ;\
+@USE_SECCOMP_TRUE@ echo "};" >> $@ ;\
+@USE_SECCOMP_TRUE@ rm $@.tmp
+@ENABLE_SYSTEMTAP_TRUE@probes.h: probes.d
+@ENABLE_SYSTEMTAP_TRUE@ $(AM_V_GEN)
+@ENABLE_SYSTEMTAP_TRUE@ $(AM_V_at)$(DTRACE) -C -h -s $< -o $@
+@ENABLE_SYSTEMTAP_TRUE@probes.o: probes.d
+@ENABLE_SYSTEMTAP_TRUE@ $(AM_V_GEN)
+@ENABLE_SYSTEMTAP_TRUE@ $(AM_V_at)$(DTRACE) -C -G -s $< -o $@
+
+@ENABLE_SYSTEMTAP_TRUE@lldpd.stp: probes.d $(srcdir)/dtrace2systemtap.awk $(top_builddir)/config.status
+@ENABLE_SYSTEMTAP_TRUE@ $(AM_V_GEN)$(AWK) -f $(srcdir)/dtrace2systemtap.awk -v sbindir=$(sbindir) $< > $@ || ( rm -f $@ ; exit 1 )
+
+@LIBEVENT_EMBEDDED_TRUE@event.c: $(top_builddir)/libevent/libevent.la
+@LIBEVENT_EMBEDDED_TRUE@$(top_builddir)/libevent/libevent.la: $(top_srcdir)/libevent/*.c $(top_srcdir)/libevent/*.h
+@LIBEVENT_EMBEDDED_TRUE@ (cd $(top_builddir)/libevent && $(MAKE))
+lldpd.8: lldpd.8.in
+lldpd.service: lldpd.service.in
+lldpd.sysusers.conf: lldpd.sysusers.conf.in
+usr.sbin.lldpd: usr.sbin.lldpd.in
+
+$(TEMPLATES): Makefile
+ $(AM_V_GEN)$(MKDIR_P) $(@D) && $(edit) $(srcdir)/$@.in > $@.tmp && mv $@.tmp $@
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/daemon/agent.c b/src/daemon/agent.c
new file mode 100644
index 0000000..cd631a8
--- /dev/null
+++ b/src/daemon/agent.c
@@ -0,0 +1,1939 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/*
+ * Copyright (c) 2008 Vincent Bernat <bernat@luffy.cx>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "lldpd.h"
+
+#include <assert.h>
+
+#include "agent.h"
+
+#if HAVE_NET_SNMP_AGENT_UTIL_FUNCS_H
+# include <net-snmp/agent/util_funcs.h>
+#else
+/* The above header may be buggy. We just need this function. */
+int header_generic(struct variable *, oid *, size_t *, int, size_t *, WriteMethod **);
+#endif
+
+/* For net-snmp */
+extern int register_sysORTable(oid *, size_t, const char *);
+extern int unregister_sysORTable(oid *, size_t);
+
+/* Global variable because no way to pass it as argument. Should not be used
+ * elsewhere. */
+#define scfg agent_scfg
+struct lldpd *agent_scfg;
+
+static 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;
+};
+
+extern struct timeval starttime;
+static long int
+lastchange(struct lldpd_port *port)
+{
+ if (port->p_lastchange > starttime.tv_sec)
+ return (port->p_lastchange - starttime.tv_sec) * 100;
+ return 0;
+}
+
+/* -------------
+ Helper functions to build header_*indexed_table() functions.
+ Those functions keep an internal state. They are not reentrant!
+*/
+struct header_index {
+ struct variable *vp;
+ oid *name; /* Requested/returned OID */
+ size_t *length; /* Length of above OID */
+ int exact;
+ oid best[MAX_OID_LEN]; /* Best OID */
+ size_t best_len; /* Best OID length */
+ void *entity; /* Best entity */
+};
+static struct header_index header_idx;
+
+static int
+header_index_init(struct variable *vp, oid *name, size_t *length, int exact,
+ size_t *var_len, WriteMethod **write_method)
+{
+ /* If the requested OID name is less than OID prefix we
+ handle, adjust it to our prefix. */
+ if ((snmp_oid_compare(name, *length, vp->name, vp->namelen)) < 0) {
+ memcpy(name, vp->name, sizeof(oid) * vp->namelen);
+ *length = vp->namelen;
+ }
+ /* Now, we can only handle OID matching our prefix. Those two
+ tests are not really necessary since NetSNMP won't give us
+ OID "above" our prefix. But this makes unit tests
+ easier. */
+ if (*length < vp->namelen) return 0;
+ if (memcmp(name, vp->name, vp->namelen * sizeof(oid))) return 0;
+
+ if (write_method != NULL) *write_method = 0;
+ *var_len = sizeof(long);
+
+ /* Initialize our header index structure */
+ header_idx.vp = vp;
+ header_idx.name = name;
+ header_idx.length = length;
+ header_idx.exact = exact;
+ header_idx.best_len = 0;
+ header_idx.entity = NULL;
+ return 1;
+}
+
+static int
+header_index_add(oid *index, size_t len, void *entity)
+{
+ int result;
+ oid *target;
+ size_t target_len;
+
+ target = header_idx.name + header_idx.vp->namelen;
+ target_len = *header_idx.length - header_idx.vp->namelen;
+ if ((result = snmp_oid_compare(index, len, target, target_len)) < 0)
+ return 0; /* Too small. */
+ if (result == 0) return header_idx.exact;
+ if (header_idx.best_len == 0 ||
+ (snmp_oid_compare(index, len, header_idx.best, header_idx.best_len) < 0)) {
+ memcpy(header_idx.best, index, sizeof(oid) * len);
+ header_idx.best_len = len;
+ header_idx.entity = entity;
+ }
+ return 0; /* No best match yet. */
+}
+
+static void *
+header_index_best()
+{
+ if (header_idx.entity == NULL) return NULL;
+ if (header_idx.exact) return NULL;
+ memcpy(header_idx.name + header_idx.vp->namelen, header_idx.best,
+ sizeof(oid) * header_idx.best_len);
+ *header_idx.length = header_idx.vp->namelen + header_idx.best_len;
+ return header_idx.entity;
+}
+/* ----------------------------- */
+
+static 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;
+
+ if (!header_index_init(vp, name, length, exact, var_len, write_method))
+ return NULL;
+ TAILQ_FOREACH (hardware, &scfg->g_hardware, h_entries) {
+ oid index[1] = { hardware->h_ifindex };
+ if (header_index_add(index, 1, hardware)) return hardware;
+ }
+ return header_index_best();
+}
+
+#ifdef ENABLE_LLDPMED
+static struct lldpd_med_policy *
+header_pmedindexed_policy_table(struct variable *vp, oid *name, size_t *length,
+ int exact, size_t *var_len, WriteMethod **write_method)
+{
+ struct lldpd_hardware *hardware;
+ int i;
+ oid index[2];
+
+ if (!header_index_init(vp, name, length, exact, var_len, write_method))
+ return NULL;
+ TAILQ_FOREACH (hardware, &scfg->g_hardware, h_entries) {
+ for (i = 0; i < LLDP_MED_APPTYPE_LAST; i++) {
+ if (hardware->h_lport.p_med_policy[i].type != i + 1) continue;
+ index[0] = hardware->h_ifindex;
+ index[1] = i + 1;
+ if (header_index_add(index, 2,
+ &hardware->h_lport.p_med_policy[i]))
+ return &hardware->h_lport.p_med_policy[i];
+ }
+ }
+ return header_index_best();
+}
+
+static struct lldpd_med_loc *
+header_pmedindexed_location_table(struct variable *vp, oid *name, size_t *length,
+ int exact, size_t *var_len, WriteMethod **write_method)
+{
+ struct lldpd_hardware *hardware;
+ int i;
+ oid index[2];
+
+ if (!header_index_init(vp, name, length, exact, var_len, write_method))
+ return NULL;
+ TAILQ_FOREACH (hardware, &scfg->g_hardware, h_entries) {
+ for (i = 0; i < LLDP_MED_LOCFORMAT_LAST; i++) {
+ if (hardware->h_lport.p_med_location[i].format != i + 1)
+ continue;
+ index[0] = hardware->h_ifindex;
+ index[1] = i + 2;
+ if (header_index_add(index, 2,
+ &hardware->h_lport.p_med_location[i]))
+ return &hardware->h_lport.p_med_location[i];
+ }
+ }
+ return header_index_best();
+}
+#endif
+
+static struct lldpd_port *
+header_tprindexed_table(struct variable *vp, oid *name, size_t *length, int exact,
+ size_t *var_len, WriteMethod **write_method, int withmed)
+{
+ struct lldpd_hardware *hardware;
+ struct lldpd_port *port;
+ oid index[3];
+
+ if (!header_index_init(vp, name, length, exact, var_len, write_method))
+ return NULL;
+ TAILQ_FOREACH (hardware, &scfg->g_hardware, h_entries) {
+ TAILQ_FOREACH (port, &hardware->h_rports, p_entries) {
+ if (SMART_HIDDEN(port)) continue;
+#ifdef ENABLE_LLDPMED
+ if (withmed && !port->p_chassis->c_med_cap_available) continue;
+#endif
+ index[0] = lastchange(port);
+ index[1] = hardware->h_ifindex;
+ index[2] = port->p_chassis->c_index;
+ if (header_index_add(index, 3, port)) return port;
+ }
+ }
+ return header_index_best();
+}
+
+static struct lldpd_mgmt *
+header_ipindexed_table(struct variable *vp, oid *name, size_t *length, int exact,
+ size_t *var_len, WriteMethod **write_method)
+{
+ struct lldpd_chassis *chassis = LOCAL_CHASSIS(scfg);
+ struct lldpd_mgmt *mgmt;
+ oid index[2 + 16];
+
+ if (!header_index_init(vp, name, length, exact, var_len, write_method))
+ return NULL;
+ TAILQ_FOREACH (mgmt, &chassis->c_mgmt, m_entries) {
+ int i;
+ switch (mgmt->m_family) {
+ case LLDPD_AF_IPV4:
+ index[0] = 1;
+ break;
+ case LLDPD_AF_IPV6:
+ index[0] = 2;
+ break;
+ default:
+ assert(0);
+ }
+ index[1] = mgmt->m_addrsize;
+ if (index[1] > sizeof(index) - 2) continue; /* Odd... */
+ for (i = 0; i < index[1]; i++)
+ index[i + 2] = mgmt->m_addr.octets[i];
+ if (header_index_add(index, 2 + index[1], mgmt)) return mgmt;
+ }
+
+ return header_index_best();
+}
+
+static struct lldpd_mgmt *
+header_tpripindexed_table(struct variable *vp, oid *name, size_t *length, int exact,
+ size_t *var_len, WriteMethod **write_method)
+{
+ struct lldpd_hardware *hardware;
+ struct lldpd_port *port;
+ struct lldpd_mgmt *mgmt;
+ oid index[5 + 16];
+
+ if (!header_index_init(vp, name, length, exact, var_len, write_method))
+ return NULL;
+ TAILQ_FOREACH (hardware, &scfg->g_hardware, h_entries) {
+ TAILQ_FOREACH (port, &hardware->h_rports, p_entries) {
+ if (SMART_HIDDEN(port)) continue;
+ TAILQ_FOREACH (mgmt, &port->p_chassis->c_mgmt, m_entries) {
+ int i;
+ index[0] = lastchange(port);
+ index[1] = hardware->h_ifindex;
+ index[2] = port->p_chassis->c_index;
+ switch (mgmt->m_family) {
+ case LLDPD_AF_IPV4:
+ index[3] = 1;
+ break;
+ case LLDPD_AF_IPV6:
+ index[3] = 2;
+ break;
+ default:
+ assert(0);
+ }
+ index[4] = mgmt->m_addrsize;
+ if (index[4] > sizeof(index) - 5) continue; /* Odd... */
+ for (i = 0; i < index[4]; i++)
+ index[i + 5] = mgmt->m_addr.octets[i];
+ if (header_index_add(index, 5 + index[4], mgmt))
+ return mgmt;
+ }
+ }
+ }
+ return header_index_best();
+}
+
+#ifdef ENABLE_CUSTOM
+static struct lldpd_custom *
+header_tprcustomindexed_table(struct variable *vp, oid *name, size_t *length, int exact,
+ size_t *var_len, WriteMethod **write_method)
+{
+ struct lldpd_hardware *hardware;
+ struct lldpd_port *port;
+ struct lldpd_custom *custom;
+ oid index[8];
+ oid idx;
+
+ if (!header_index_init(vp, name, length, exact, var_len, write_method))
+ return NULL;
+ TAILQ_FOREACH (hardware, &scfg->g_hardware, h_entries) {
+ TAILQ_FOREACH (port, &hardware->h_rports, p_entries) {
+ if (SMART_HIDDEN(port)) continue;
+ idx = 1;
+ TAILQ_FOREACH (custom, &port->p_custom_list, next) {
+ index[0] = lastchange(port);
+ index[1] = hardware->h_ifindex;
+ index[2] = port->p_chassis->c_index;
+ index[3] = custom->oui[0];
+ index[4] = custom->oui[1];
+ index[5] = custom->oui[2];
+ index[6] = custom->subtype;
+ index[7] = idx++;
+ if (header_index_add(index, 8, custom)) return custom;
+ }
+ }
+ }
+ return header_index_best();
+}
+#endif
+
+#ifdef ENABLE_LLDPMED
+# define TPR_VARIANT_MED_POLICY 2
+# define TPR_VARIANT_MED_LOCATION 3
+static void *
+header_tprmedindexed_table(struct variable *vp, oid *name, size_t *length, int exact,
+ size_t *var_len, WriteMethod **write_method, int variant)
+{
+ struct lldpd_hardware *hardware;
+ struct lldpd_port *port;
+ int j;
+ oid index[4];
+
+ if (!header_index_init(vp, name, length, exact, var_len, write_method))
+ return NULL;
+ TAILQ_FOREACH (hardware, &scfg->g_hardware, h_entries) {
+ TAILQ_FOREACH (port, &hardware->h_rports, p_entries) {
+ if (SMART_HIDDEN(port)) continue;
+ if (!port->p_chassis->c_med_cap_available) continue;
+ switch (variant) {
+ case TPR_VARIANT_MED_POLICY:
+ for (j = 0; j < LLDP_MED_APPTYPE_LAST; j++) {
+ if (port->p_med_policy[j].type != j + 1)
+ continue;
+ index[0] = lastchange(port);
+ index[1] = hardware->h_ifindex;
+ index[2] = port->p_chassis->c_index;
+ index[3] = j + 1;
+ if (header_index_add(index, 4,
+ &port->p_med_policy[j]))
+ return &port->p_med_policy[j];
+ }
+ break;
+ case TPR_VARIANT_MED_LOCATION:
+ for (j = 0; j < LLDP_MED_LOCFORMAT_LAST; j++) {
+ if (port->p_med_location[j].format != j + 1)
+ continue;
+ index[0] = lastchange(port);
+ index[1] = hardware->h_ifindex;
+ index[2] = port->p_chassis->c_index;
+ index[3] = j + 2;
+ if (header_index_add(index, 4,
+ &port->p_med_location[j]))
+ return &port->p_med_location[j];
+ }
+ break;
+ }
+ }
+ }
+ return header_index_best();
+}
+#endif
+
+#ifdef ENABLE_DOT1
+static 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;
+
+ if (!header_index_init(vp, name, length, exact, var_len, write_method))
+ return NULL;
+ TAILQ_FOREACH (hardware, &scfg->g_hardware, h_entries) {
+ TAILQ_FOREACH (vlan, &hardware->h_lport.p_vlans, v_entries) {
+ oid index[2] = { hardware->h_ifindex, vlan->v_vid };
+ if (header_index_add(index, 2, vlan)) return vlan;
+ }
+ }
+ return header_index_best();
+}
+
+static 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_port *port;
+ struct lldpd_vlan *vlan;
+
+ if (!header_index_init(vp, name, length, exact, var_len, write_method))
+ return NULL;
+ TAILQ_FOREACH (hardware, &scfg->g_hardware, h_entries) {
+ TAILQ_FOREACH (port, &hardware->h_rports, p_entries) {
+ if (SMART_HIDDEN(port)) continue;
+ TAILQ_FOREACH (vlan, &port->p_vlans, v_entries) {
+ oid index[4] = { lastchange(port), hardware->h_ifindex,
+ port->p_chassis->c_index, vlan->v_vid };
+ if (header_index_add(index, 4, vlan)) return vlan;
+ }
+ }
+ }
+ return header_index_best();
+}
+
+static struct lldpd_ppvid *
+header_pppvidindexed_table(struct variable *vp, oid *name, size_t *length, int exact,
+ size_t *var_len, WriteMethod **write_method)
+{
+ struct lldpd_hardware *hardware;
+ struct lldpd_ppvid *ppvid;
+
+ if (!header_index_init(vp, name, length, exact, var_len, write_method))
+ return NULL;
+ TAILQ_FOREACH (hardware, &scfg->g_hardware, h_entries) {
+ TAILQ_FOREACH (ppvid, &hardware->h_lport.p_ppvids, p_entries) {
+ oid index[2] = { hardware->h_ifindex, ppvid->p_ppvid };
+ if (header_index_add(index, 2, ppvid)) return ppvid;
+ }
+ }
+ return header_index_best();
+}
+
+static struct lldpd_ppvid *
+header_tprppvidindexed_table(struct variable *vp, oid *name, size_t *length, int exact,
+ size_t *var_len, WriteMethod **write_method)
+{
+ struct lldpd_hardware *hardware;
+ struct lldpd_port *port;
+ struct lldpd_ppvid *ppvid;
+
+ if (!header_index_init(vp, name, length, exact, var_len, write_method))
+ return NULL;
+ TAILQ_FOREACH (hardware, &scfg->g_hardware, h_entries) {
+ TAILQ_FOREACH (port, &hardware->h_rports, p_entries) {
+ if (SMART_HIDDEN(port)) continue;
+ TAILQ_FOREACH (ppvid, &port->p_ppvids, p_entries) {
+ oid index[4] = { lastchange(port), hardware->h_ifindex,
+ port->p_chassis->c_index, ppvid->p_ppvid };
+ if (header_index_add(index, 4, ppvid)) return ppvid;
+ }
+ }
+ }
+ return header_index_best();
+}
+
+static struct lldpd_pi *
+header_ppiindexed_table(struct variable *vp, oid *name, size_t *length, int exact,
+ size_t *var_len, WriteMethod **write_method)
+{
+ struct lldpd_hardware *hardware;
+ struct lldpd_pi *pi;
+
+ if (!header_index_init(vp, name, length, exact, var_len, write_method))
+ return NULL;
+ TAILQ_FOREACH (hardware, &scfg->g_hardware, h_entries) {
+ TAILQ_FOREACH (pi, &hardware->h_lport.p_pids, p_entries) {
+ oid index[2] = { hardware->h_ifindex,
+ frame_checksum((const u_char *)pi->p_pi, pi->p_pi_len,
+ 0) };
+ if (header_index_add(index, 2, pi)) return pi;
+ }
+ }
+ return header_index_best();
+}
+
+static struct lldpd_pi *
+header_tprpiindexed_table(struct variable *vp, oid *name, size_t *length, int exact,
+ size_t *var_len, WriteMethod **write_method)
+{
+ struct lldpd_hardware *hardware;
+ struct lldpd_port *port;
+ struct lldpd_pi *pi;
+
+ if (!header_index_init(vp, name, length, exact, var_len, write_method))
+ return NULL;
+ TAILQ_FOREACH (hardware, &scfg->g_hardware, h_entries) {
+ TAILQ_FOREACH (port, &hardware->h_rports, p_entries) {
+ if (SMART_HIDDEN(port)) continue;
+ TAILQ_FOREACH (pi, &port->p_pids, p_entries) {
+ oid index[4] = { lastchange(port), hardware->h_ifindex,
+ port->p_chassis->c_index,
+ frame_checksum((const u_char *)pi->p_pi,
+ pi->p_pi_len, 0) };
+ if (header_index_add(index, 4, pi)) return pi;
+ }
+ }
+ }
+ return header_index_best();
+}
+#endif
+
+/* 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
+/* Chassis */
+#define LLDP_SNMP_CIDSUBTYPE 1
+#define LLDP_SNMP_CID 2
+#define LLDP_SNMP_SYSNAME 3
+#define LLDP_SNMP_SYSDESCR 4
+#define LLDP_SNMP_SYSCAP_SUP 5
+#define LLDP_SNMP_SYSCAP_ENA 6
+/* Stats */
+#define LLDP_SNMP_STATS_TX 2
+#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
+/* Ports */
+#define LLDP_SNMP_PIDSUBTYPE 2
+#define LLDP_SNMP_PID 3
+#define LLDP_SNMP_PORTDESC 4
+#define LLDP_SNMP_DOT3_AUTONEG_SUPPORT 5
+#define LLDP_SNMP_DOT3_AUTONEG_ENABLED 6
+#define LLDP_SNMP_DOT3_AUTONEG_ADVERTISED 7
+#define LLDP_SNMP_DOT3_AUTONEG_MAU 8
+#define LLDP_SNMP_DOT3_AGG_STATUS 9
+#define LLDP_SNMP_DOT3_AGG_ID 10
+#define LLDP_SNMP_DOT3_MFS 11
+#define LLDP_SNMP_DOT3_POWER_DEVICETYPE 12
+#define LLDP_SNMP_DOT3_POWER_SUPPORT 13
+#define LLDP_SNMP_DOT3_POWER_ENABLED 14
+#define LLDP_SNMP_DOT3_POWER_PAIRCONTROL 15
+#define LLDP_SNMP_DOT3_POWER_PAIRS 16
+#define LLDP_SNMP_DOT3_POWER_CLASS 17
+#define LLDP_SNMP_DOT3_POWER_TYPE 18
+#define LLDP_SNMP_DOT3_POWER_SOURCE 19
+#define LLDP_SNMP_DOT3_POWER_PRIORITY 20
+#define LLDP_SNMP_DOT3_POWER_REQUESTED 21
+#define LLDP_SNMP_DOT3_POWER_ALLOCATED 22
+#define LLDP_SNMP_DOT1_PVID 23
+/* Vlans */
+#define LLDP_SNMP_DOT1_VLANNAME 1
+/* Protocol VLAN IDs */
+#define LLDP_SNMP_DOT1_PPVLAN_SUPPORTED 2
+#define LLDP_SNMP_DOT1_PPVLAN_ENABLED 3
+/* Protocol Identity */
+#define LLDP_SNMP_DOT1_PI 1
+/* Management address */
+#define LLDP_SNMP_ADDR_LEN 1
+#define LLDP_SNMP_ADDR_IFSUBTYPE 2
+#define LLDP_SNMP_ADDR_IFID 3
+#define LLDP_SNMP_ADDR_OID 4
+/* Custom TLVs */
+#define LLDP_SNMP_ORG_DEF_INFO 1
+/* LLDP-MED */
+#define LLDP_SNMP_MED_CAP_AVAILABLE 1
+#define LLDP_SNMP_MED_CAP_ENABLED 2
+#define LLDP_SNMP_MED_CLASS 3
+#define LLDP_SNMP_MED_HW 4
+#define LLDP_SNMP_MED_FW 5
+#define LLDP_SNMP_MED_SW 6
+#define LLDP_SNMP_MED_SN 7
+#define LLDP_SNMP_MED_MANUF 8
+#define LLDP_SNMP_MED_MODEL 9
+#define LLDP_SNMP_MED_ASSET 10
+#define LLDP_SNMP_MED_POLICY_VID 11
+#define LLDP_SNMP_MED_POLICY_PRIO 12
+#define LLDP_SNMP_MED_POLICY_DSCP 13
+#define LLDP_SNMP_MED_POLICY_UNKNOWN 14
+#define LLDP_SNMP_MED_POLICY_TAGGED 15
+#define LLDP_SNMP_MED_LOCATION 16
+#define LLDP_SNMP_MED_POE_DEVICETYPE 17
+#define LLDP_SNMP_MED_POE_PSE_POWERVAL 19
+#define LLDP_SNMP_MED_POE_PSE_POWERSOURCE 20
+#define LLDP_SNMP_MED_POE_PSE_POWERPRIORITY 21
+#define LLDP_SNMP_MED_POE_PD_POWERVAL 22
+#define LLDP_SNMP_MED_POE_PD_POWERSOURCE 23
+#define LLDP_SNMP_MED_POE_PD_POWERPRIORITY 24
+
+/* The following macro should be used anytime where the selected OID
+ is finally not returned (for example, when the associated data is
+ not available). In this case, we retry the function with the next
+ OID. */
+#define TRYNEXT(X) \
+ do { \
+ if (!exact && (name[*length - 1] < MAX_SUBID)) \
+ return X(vp, name, length, exact, var_len, write_method); \
+ return NULL; \
+ } while (0)
+
+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;
+ struct lldpd_port *port;
+
+ if (header_generic(vp, name, length, exact, var_len, write_method)) return NULL;
+
+ switch (vp->magic) {
+ case LLDP_SNMP_TXINTERVAL:
+ long_ret = (scfg->g_config.c_tx_interval + 999) / 1000;
+ return (u_char *)&long_ret;
+ case LLDP_SNMP_TXMULTIPLIER:
+ long_ret = scfg->g_config.c_tx_hold;
+ 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) {
+ /* Check if the last removal of a remote port on this local port
+ * was the last change. */
+ if (hardware->h_lport.p_lastremove > long_ret)
+ long_ret = hardware->h_lport.p_lastremove;
+ /* Check if any change on the existing remote ports was the last
+ * change. */
+ TAILQ_FOREACH (port, &hardware->h_rports, p_entries) {
+ if (SMART_HIDDEN(port)) continue;
+ if (port->p_lastchange > long_ret)
+ long_ret = port->p_lastchange;
+ }
+ }
+ 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_insert_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_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_delete_cnt;
+ return (u_char *)&long_ret;
+ case LLDP_SNMP_STATS_DROPS:
+ long_ret = 0;
+ TAILQ_FOREACH (hardware, &scfg->g_hardware, h_entries)
+ long_ret += hardware->h_drop_cnt;
+ return (u_char *)&long_ret;
+ default:
+ break;
+ }
+ return NULL;
+}
+
+#ifdef ENABLE_LLDPMED
+static u_char *
+agent_v_med_power(struct variable *vp, size_t *var_len, struct lldpd_med_power *power)
+{
+ static unsigned long long_ret;
+
+ switch (vp->magic) {
+ case LLDP_SNMP_MED_POE_DEVICETYPE:
+ switch (power->devicetype) {
+ case LLDP_MED_POW_TYPE_PSE:
+ long_ret = 2;
+ break;
+ case LLDP_MED_POW_TYPE_PD:
+ long_ret = 3;
+ break;
+ case 0:
+ long_ret = 4;
+ break;
+ default:
+ long_ret = 1;
+ }
+ return (u_char *)&long_ret;
+ case LLDP_SNMP_MED_POE_PSE_POWERVAL:
+ case LLDP_SNMP_MED_POE_PD_POWERVAL:
+ if (((vp->magic == LLDP_SNMP_MED_POE_PSE_POWERVAL) &&
+ (power->devicetype == LLDP_MED_POW_TYPE_PSE)) ||
+ ((vp->magic == LLDP_SNMP_MED_POE_PD_POWERVAL) &&
+ (power->devicetype == LLDP_MED_POW_TYPE_PD))) {
+ long_ret = power->val;
+ return (u_char *)&long_ret;
+ }
+ break;
+ case LLDP_SNMP_MED_POE_PSE_POWERSOURCE:
+ if (power->devicetype == LLDP_MED_POW_TYPE_PSE) {
+ switch (power->source) {
+ case LLDP_MED_POW_SOURCE_PRIMARY:
+ long_ret = 2;
+ break;
+ case LLDP_MED_POW_SOURCE_BACKUP:
+ long_ret = 3;
+ break;
+ default:
+ long_ret = 1;
+ }
+ return (u_char *)&long_ret;
+ }
+ break;
+ case LLDP_SNMP_MED_POE_PD_POWERSOURCE:
+ if (power->devicetype == LLDP_MED_POW_TYPE_PD) {
+ switch (power->source) {
+ case LLDP_MED_POW_SOURCE_PSE:
+ long_ret = 2;
+ break;
+ case LLDP_MED_POW_SOURCE_LOCAL:
+ long_ret = 3;
+ break;
+ case LLDP_MED_POW_SOURCE_BOTH:
+ long_ret = 4;
+ break;
+ default:
+ long_ret = 1;
+ }
+ return (u_char *)&long_ret;
+ }
+ break;
+ case LLDP_SNMP_MED_POE_PSE_POWERPRIORITY:
+ case LLDP_SNMP_MED_POE_PD_POWERPRIORITY:
+ if (((vp->magic == LLDP_SNMP_MED_POE_PSE_POWERPRIORITY) &&
+ (power->devicetype == LLDP_MED_POW_TYPE_PSE)) ||
+ ((vp->magic == LLDP_SNMP_MED_POE_PD_POWERPRIORITY) &&
+ (power->devicetype == LLDP_MED_POW_TYPE_PD))) {
+ switch (power->priority) {
+ case LLDP_MED_POW_PRIO_CRITICAL:
+ long_ret = 2;
+ break;
+ case LLDP_MED_POW_PRIO_HIGH:
+ long_ret = 3;
+ break;
+ case LLDP_MED_POW_PRIO_LOW:
+ long_ret = 4;
+ break;
+ default:
+ long_ret = 1;
+ }
+ return (u_char *)&long_ret;
+ }
+ break;
+ }
+
+ return NULL;
+}
+static u_char *
+agent_h_local_med_power(struct variable *vp, oid *name, size_t *length, int exact,
+ size_t *var_len, WriteMethod **write_method)
+{
+ struct lldpd_med_power *power = NULL;
+ struct lldpd_hardware *hardware;
+ int pse = 0;
+
+ if (!LOCAL_CHASSIS(scfg)->c_med_cap_available) return NULL;
+ if (header_generic(vp, name, length, exact, var_len, write_method)) return NULL;
+
+ /* LLDP-MED requires only one device type for all
+ ports. Moreover, a PSE can only have one power source. At
+ least, all PD values are global and not per-port. We try to
+ do our best. For device type, we decide on the number of
+ PD/PSE ports. */
+ TAILQ_FOREACH (hardware, &scfg->g_hardware, h_entries) {
+ if (hardware->h_lport.p_med_power.devicetype == LLDP_MED_POW_TYPE_PSE) {
+ pse++;
+ if (pse == 1) /* Take this port as a reference */
+ power = &hardware->h_lport.p_med_power;
+ } else if (hardware->h_lport.p_med_power.devicetype ==
+ LLDP_MED_POW_TYPE_PD) {
+ pse--;
+ if (pse == -1) /* Take this one instead */
+ power = &hardware->h_lport.p_med_power;
+ }
+ }
+ if (power) {
+ u_char *a;
+ if ((a = agent_v_med_power(vp, var_len, power)) != NULL) return a;
+ }
+ TRYNEXT(agent_h_local_med_power);
+}
+static u_char *
+agent_h_remote_med_power(struct variable *vp, oid *name, size_t *length, int exact,
+ size_t *var_len, WriteMethod **write_method)
+{
+ struct lldpd_port *port;
+ u_char *a;
+
+ if ((port = header_tprindexed_table(vp, name, length, exact, var_len,
+ write_method, 1)) == NULL)
+ return NULL;
+
+ if ((a = agent_v_med_power(vp, var_len, &port->p_med_power)) != NULL) return a;
+ TRYNEXT(agent_h_remote_med_power);
+}
+
+static u_char *
+agent_v_med(struct variable *vp, size_t *var_len, struct lldpd_chassis *chassis,
+ struct lldpd_port *port)
+{
+ static unsigned long long_ret;
+ static uint8_t bit;
+
+ switch (vp->magic) {
+ case LLDP_SNMP_MED_CLASS:
+ long_ret = chassis->c_med_type;
+ return (u_char *)&long_ret;
+ case LLDP_SNMP_MED_CAP_AVAILABLE:
+ *var_len = 1;
+ bit = swap_bits(chassis->c_med_cap_available);
+ return (u_char *)&bit;
+ case LLDP_SNMP_MED_CAP_ENABLED:
+ if (!port) break;
+ *var_len = 1;
+ bit = swap_bits(port->p_med_cap_enabled);
+ return (u_char *)&bit;
+
+# define LLDP_H_MED(magic, variable) \
+ case magic: \
+ if (chassis->variable) { \
+ *var_len = strlen(chassis->variable); \
+ return (u_char *)chassis->variable; \
+ } \
+ break
+
+ LLDP_H_MED(LLDP_SNMP_MED_HW, c_med_hw);
+ LLDP_H_MED(LLDP_SNMP_MED_SW, c_med_sw);
+ LLDP_H_MED(LLDP_SNMP_MED_FW, c_med_fw);
+ LLDP_H_MED(LLDP_SNMP_MED_SN, c_med_sn);
+ LLDP_H_MED(LLDP_SNMP_MED_MANUF, c_med_manuf);
+ LLDP_H_MED(LLDP_SNMP_MED_MODEL, c_med_model);
+ LLDP_H_MED(LLDP_SNMP_MED_ASSET, c_med_asset);
+ }
+ return NULL;
+}
+static u_char *
+agent_h_local_med(struct variable *vp, oid *name, size_t *length, int exact,
+ size_t *var_len, WriteMethod **write_method)
+{
+ u_char *a;
+
+ if (!LOCAL_CHASSIS(scfg)->c_med_cap_available) return NULL;
+ if (header_generic(vp, name, length, exact, var_len, write_method)) return NULL;
+
+ if ((a = agent_v_med(vp, var_len, LOCAL_CHASSIS(scfg), NULL)) != NULL) return a;
+ TRYNEXT(agent_h_local_med);
+}
+
+static u_char *
+agent_h_remote_med(struct variable *vp, oid *name, size_t *length, int exact,
+ size_t *var_len, WriteMethod **write_method)
+{
+ struct lldpd_port *port;
+ u_char *a;
+
+ if ((port = header_tprindexed_table(vp, name, length, exact, var_len,
+ write_method, 1)) == NULL)
+ return NULL;
+
+ if ((a = agent_v_med(vp, var_len, port->p_chassis, port)) != NULL) return a;
+ TRYNEXT(agent_h_remote_med);
+}
+
+static u_char *
+agent_v_med_policy(struct variable *vp, size_t *var_len,
+ struct lldpd_med_policy *policy)
+{
+ static unsigned long long_ret;
+
+ switch (vp->magic) {
+ case LLDP_SNMP_MED_POLICY_VID:
+ long_ret = policy->vid;
+ return (u_char *)&long_ret;
+ case LLDP_SNMP_MED_POLICY_PRIO:
+ long_ret = policy->priority;
+ return (u_char *)&long_ret;
+ case LLDP_SNMP_MED_POLICY_DSCP:
+ long_ret = policy->dscp;
+ return (u_char *)&long_ret;
+ case LLDP_SNMP_MED_POLICY_UNKNOWN:
+ long_ret = policy->unknown ? 1 : 2;
+ return (u_char *)&long_ret;
+ case LLDP_SNMP_MED_POLICY_TAGGED:
+ long_ret = policy->tagged ? 1 : 2;
+ return (u_char *)&long_ret;
+ default:
+ return NULL;
+ }
+}
+static u_char *
+agent_h_remote_med_policy(struct variable *vp, oid *name, size_t *length, int exact,
+ size_t *var_len, WriteMethod **write_method)
+{
+ struct lldpd_med_policy *policy;
+
+ if ((policy = (struct lldpd_med_policy *)header_tprmedindexed_table(vp, name,
+ length, exact, var_len, write_method, TPR_VARIANT_MED_POLICY)) == NULL)
+ return NULL;
+
+ return agent_v_med_policy(vp, var_len, policy);
+}
+static u_char *
+agent_h_local_med_policy(struct variable *vp, oid *name, size_t *length, int exact,
+ size_t *var_len, WriteMethod **write_method)
+{
+ struct lldpd_med_policy *policy;
+
+ if ((policy = (struct lldpd_med_policy *)header_pmedindexed_policy_table(vp,
+ name, length, exact, var_len, write_method)) == NULL)
+ return NULL;
+
+ return agent_v_med_policy(vp, var_len, policy);
+}
+
+static u_char *
+agent_v_med_location(struct variable *vp, size_t *var_len,
+ struct lldpd_med_loc *location)
+{
+ switch (vp->magic) {
+ case LLDP_SNMP_MED_LOCATION:
+ *var_len = location->data_len;
+ return (u_char *)location->data;
+ default:
+ return NULL;
+ }
+}
+static u_char *
+agent_h_remote_med_location(struct variable *vp, oid *name, size_t *length, int exact,
+ size_t *var_len, WriteMethod **write_method)
+{
+ struct lldpd_med_loc *location;
+
+ if ((location = (struct lldpd_med_loc *)header_tprmedindexed_table(vp, name,
+ length, exact, var_len, write_method, TPR_VARIANT_MED_LOCATION)) ==
+ NULL)
+ return NULL;
+
+ return agent_v_med_location(vp, var_len, location);
+}
+static u_char *
+agent_h_local_med_location(struct variable *vp, oid *name, size_t *length, int exact,
+ size_t *var_len, WriteMethod **write_method)
+{
+ struct lldpd_med_loc *location;
+
+ if ((location = (struct lldpd_med_loc *)header_pmedindexed_location_table(vp,
+ name, length, exact, var_len, write_method)) == NULL)
+ return NULL;
+
+ return agent_v_med_location(vp, var_len, location);
+}
+#endif
+
+static u_char *
+agent_v_chassis(struct variable *vp, size_t *var_len, struct lldpd_chassis *chassis)
+{
+ static uint8_t bit;
+ static unsigned long long_ret;
+
+ switch (vp->magic) {
+ case LLDP_SNMP_CIDSUBTYPE:
+ long_ret = chassis->c_id_subtype;
+ return (u_char *)&long_ret;
+ case LLDP_SNMP_CID:
+ *var_len = chassis->c_id_len;
+ return (u_char *)chassis->c_id;
+ case LLDP_SNMP_SYSNAME:
+ if (!chassis->c_name || *chassis->c_name == '\0') break;
+ *var_len = strlen(chassis->c_name);
+ return (u_char *)chassis->c_name;
+ case LLDP_SNMP_SYSDESCR:
+ if (!chassis->c_descr || *chassis->c_descr == '\0') break;
+ *var_len = strlen(chassis->c_descr);
+ return (u_char *)chassis->c_descr;
+ case LLDP_SNMP_SYSCAP_SUP:
+ *var_len = 1;
+ bit = swap_bits(chassis->c_cap_available);
+ return (u_char *)&bit;
+ case LLDP_SNMP_SYSCAP_ENA:
+ *var_len = 1;
+ bit = swap_bits(chassis->c_cap_enabled);
+ return (u_char *)&bit;
+ 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)
+{
+ u_char *a;
+
+ if (header_generic(vp, name, length, exact, var_len, write_method)) return NULL;
+
+ if ((a = agent_v_chassis(vp, var_len, LOCAL_CHASSIS(scfg))) != NULL) return a;
+ TRYNEXT(agent_h_local_chassis);
+}
+static u_char *
+agent_h_remote_chassis(struct variable *vp, oid *name, size_t *length, int exact,
+ size_t *var_len, WriteMethod **write_method)
+{
+ struct lldpd_port *port;
+ u_char *a;
+
+ if ((port = header_tprindexed_table(vp, name, length, exact, var_len,
+ write_method, 0)) == NULL)
+ return NULL;
+
+ if ((a = agent_v_chassis(vp, var_len, port->p_chassis)) != NULL) return a;
+ TRYNEXT(agent_h_remote_chassis);
+}
+
+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:
+ /* We discard only frame with errors. Therefore, the two values
+ * are equal */
+ long_ret = hardware->h_rx_discarded_cnt;
+ return (u_char *)&long_ret;
+ case LLDP_SNMP_STATS_RX_TLVDISCARDED:
+ case LLDP_SNMP_STATS_RX_TLVUNRECOGNIZED:
+ /* We discard only unrecognized TLV. Malformed TLV
+ implies dropping the whole frame */
+ long_ret = hardware->h_rx_unrecognized_cnt;
+ return (u_char *)&long_ret;
+ case LLDP_SNMP_STATS_RX_AGEOUTS:
+ long_ret = hardware->h_ageout_cnt;
+ return (u_char *)&long_ret;
+ default:
+ return NULL;
+ }
+}
+
+#ifdef ENABLE_DOT1
+static u_char *
+agent_v_vlan(struct variable *vp, size_t *var_len, struct lldpd_vlan *vlan)
+{
+ switch (vp->magic) {
+ case LLDP_SNMP_DOT1_VLANNAME:
+ *var_len = strlen(vlan->v_name);
+ return (u_char *)vlan->v_name;
+ default:
+ 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;
+
+ return agent_v_vlan(vp, var_len, vlan);
+}
+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;
+
+ return agent_v_vlan(vp, var_len, vlan);
+}
+
+static u_char *
+agent_v_ppvid(struct variable *vp, size_t *var_len, struct lldpd_ppvid *ppvid)
+{
+ static unsigned long long_ret;
+
+ switch (vp->magic) {
+ case LLDP_SNMP_DOT1_PPVLAN_SUPPORTED:
+ long_ret = (ppvid->p_cap_status & LLDP_PPVID_CAP_SUPPORTED) ? 1 : 2;
+ return (u_char *)&long_ret;
+ case LLDP_SNMP_DOT1_PPVLAN_ENABLED:
+ long_ret = (ppvid->p_cap_status & LLDP_PPVID_CAP_ENABLED) ? 1 : 2;
+ return (u_char *)&long_ret;
+ default:
+ return NULL;
+ }
+}
+static u_char *
+agent_h_local_ppvid(struct variable *vp, oid *name, size_t *length, int exact,
+ size_t *var_len, WriteMethod **write_method)
+{
+ struct lldpd_ppvid *ppvid;
+
+ if ((ppvid = header_pppvidindexed_table(vp, name, length, exact, var_len,
+ write_method)) == NULL)
+ return NULL;
+
+ return agent_v_ppvid(vp, var_len, ppvid);
+}
+
+static u_char *
+agent_h_remote_ppvid(struct variable *vp, oid *name, size_t *length, int exact,
+ size_t *var_len, WriteMethod **write_method)
+{
+ struct lldpd_ppvid *ppvid;
+
+ if ((ppvid = header_tprppvidindexed_table(vp, name, length, exact, var_len,
+ write_method)) == NULL)
+ return NULL;
+
+ return agent_v_ppvid(vp, var_len, ppvid);
+}
+
+static u_char *
+agent_v_pi(struct variable *vp, size_t *var_len, struct lldpd_pi *pi)
+{
+ switch (vp->magic) {
+ case LLDP_SNMP_DOT1_PI:
+ *var_len = pi->p_pi_len;
+ return (u_char *)pi->p_pi;
+ default:
+ return NULL;
+ }
+}
+static u_char *
+agent_h_local_pi(struct variable *vp, oid *name, size_t *length, int exact,
+ size_t *var_len, WriteMethod **write_method)
+{
+ struct lldpd_pi *pi;
+
+ if ((pi = header_ppiindexed_table(vp, name, length, exact, var_len,
+ write_method)) == NULL)
+ return NULL;
+
+ return agent_v_pi(vp, var_len, pi);
+}
+static u_char *
+agent_h_remote_pi(struct variable *vp, oid *name, size_t *length, int exact,
+ size_t *var_len, WriteMethod **write_method)
+{
+ struct lldpd_pi *pi;
+
+ if ((pi = header_tprpiindexed_table(vp, name, length, exact, var_len,
+ write_method)) == NULL)
+ return NULL;
+
+ return agent_v_pi(vp, var_len, pi);
+}
+#endif
+
+static u_char *
+agent_v_port(struct variable *vp, size_t *var_len, struct lldpd_port *port)
+{
+#ifdef ENABLE_DOT3
+ static uint16_t short_ret;
+ static uint8_t bit;
+#endif
+ static unsigned long long_ret;
+
+ switch (vp->magic) {
+ case LLDP_SNMP_PIDSUBTYPE:
+ long_ret = port->p_id_subtype;
+ return (u_char *)&long_ret;
+ case LLDP_SNMP_PID:
+ *var_len = port->p_id_len;
+ return (u_char *)port->p_id;
+ case LLDP_SNMP_PORTDESC:
+ if (!port->p_descr || *port->p_descr == '\0') break;
+ *var_len = strlen(port->p_descr);
+ return (u_char *)port->p_descr;
+#ifdef ENABLE_DOT3
+ case LLDP_SNMP_DOT3_AUTONEG_SUPPORT:
+ long_ret = 2 - port->p_macphy.autoneg_support;
+ return (u_char *)&long_ret;
+ case LLDP_SNMP_DOT3_AUTONEG_ENABLED:
+ long_ret = 2 - port->p_macphy.autoneg_enabled;
+ return (u_char *)&long_ret;
+ case LLDP_SNMP_DOT3_AUTONEG_ADVERTISED:
+ *var_len = 2;
+ short_ret = htons(port->p_macphy.autoneg_advertised);
+ return (u_char *)&short_ret;
+ case LLDP_SNMP_DOT3_AUTONEG_MAU:
+ long_ret = port->p_macphy.mau_type;
+ return (u_char *)&long_ret;
+ case LLDP_SNMP_DOT3_AGG_STATUS:
+ bit = swap_bits((port->p_aggregid > 0) ? 3 : 0);
+ *var_len = 1;
+ return (u_char *)&bit;
+ case LLDP_SNMP_DOT3_AGG_ID:
+ long_ret = port->p_aggregid;
+ return (u_char *)&long_ret;
+ case LLDP_SNMP_DOT3_MFS:
+ if (port->p_mfs) {
+ long_ret = port->p_mfs;
+ return (u_char *)&long_ret;
+ }
+ break;
+ case LLDP_SNMP_DOT3_POWER_DEVICETYPE:
+ if (port->p_power.devicetype) {
+ long_ret =
+ (port->p_power.devicetype == LLDP_DOT3_POWER_PSE) ? 1 : 2;
+ return (u_char *)&long_ret;
+ }
+ break;
+ case LLDP_SNMP_DOT3_POWER_SUPPORT:
+ if (port->p_power.devicetype) {
+ long_ret = (port->p_power.supported) ? 1 : 2;
+ return (u_char *)&long_ret;
+ }
+ break;
+ case LLDP_SNMP_DOT3_POWER_ENABLED:
+ if (port->p_power.devicetype) {
+ long_ret = (port->p_power.enabled) ? 1 : 2;
+ return (u_char *)&long_ret;
+ }
+ break;
+ case LLDP_SNMP_DOT3_POWER_PAIRCONTROL:
+ if (port->p_power.devicetype) {
+ long_ret = (port->p_power.paircontrol) ? 1 : 2;
+ return (u_char *)&long_ret;
+ }
+ break;
+ case LLDP_SNMP_DOT3_POWER_PAIRS:
+ if (port->p_power.devicetype) {
+ long_ret = port->p_power.pairs;
+ return (u_char *)&long_ret;
+ }
+ break;
+ case LLDP_SNMP_DOT3_POWER_CLASS:
+ if (port->p_power.devicetype && port->p_power.class) {
+ long_ret = port->p_power.class;
+ return (u_char *)&long_ret;
+ }
+ break;
+ case LLDP_SNMP_DOT3_POWER_TYPE:
+ if (port->p_power.devicetype &&
+ port->p_power.powertype != LLDP_DOT3_POWER_8023AT_OFF) {
+ *var_len = 1;
+ bit = (((port->p_power.powertype ==
+ LLDP_DOT3_POWER_8023AT_TYPE1) ?
+ 0 :
+ 1)
+ << 7) |
+ (((port->p_power.devicetype == LLDP_DOT3_POWER_PSE) ? 0 : 1)
+ << 6);
+ return (u_char *)&bit;
+ }
+ break;
+ case LLDP_SNMP_DOT3_POWER_SOURCE:
+ if (port->p_power.devicetype &&
+ port->p_power.powertype != LLDP_DOT3_POWER_8023AT_OFF) {
+ *var_len = 1;
+ bit = swap_bits(port->p_power.source % (1 << 2));
+ return (u_char *)&bit;
+ }
+ break;
+ case LLDP_SNMP_DOT3_POWER_PRIORITY:
+ if (port->p_power.devicetype &&
+ port->p_power.powertype != LLDP_DOT3_POWER_8023AT_OFF) {
+ /* See 30.12.2.1.16. This seems defined in reverse order... */
+ long_ret = 4 - port->p_power.priority;
+ return (u_char *)&long_ret;
+ }
+ break;
+ case LLDP_SNMP_DOT3_POWER_REQUESTED:
+ if (port->p_power.devicetype &&
+ port->p_power.powertype != LLDP_DOT3_POWER_8023AT_OFF) {
+ long_ret = port->p_power.requested;
+ return (u_char *)&long_ret;
+ }
+ break;
+ case LLDP_SNMP_DOT3_POWER_ALLOCATED:
+ if (port->p_power.devicetype &&
+ port->p_power.powertype != LLDP_DOT3_POWER_8023AT_OFF) {
+ long_ret = port->p_power.allocated;
+ return (u_char *)&long_ret;
+ }
+ break;
+#endif
+#ifdef ENABLE_DOT1
+ case LLDP_SNMP_DOT1_PVID:
+ long_ret = port->p_pvid;
+ return (u_char *)&long_ret;
+#endif
+ 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_port *port;
+ u_char *a;
+
+ if ((port = header_tprindexed_table(vp, name, length, exact, var_len,
+ write_method, 0)) == NULL)
+ return NULL;
+
+ if ((a = agent_v_port(vp, var_len, port)) != NULL) return a;
+ TRYNEXT(agent_h_remote_port);
+}
+static u_char *
+agent_h_local_port(struct variable *vp, oid *name, size_t *length, int exact,
+ size_t *var_len, WriteMethod **write_method)
+{
+ struct lldpd_hardware *hardware;
+ u_char *a;
+
+ if ((hardware = header_portindexed_table(vp, name, length, exact, var_len,
+ write_method)) == NULL)
+ return NULL;
+
+ if ((a = agent_v_port(vp, var_len, &hardware->h_lport)) != NULL) return a;
+ TRYNEXT(agent_h_local_port);
+}
+
+static u_char *
+agent_v_management(struct variable *vp, size_t *var_len, struct lldpd_mgmt *mgmt)
+{
+ static unsigned long int long_ret;
+ static oid zeroDotZero[2] = { 0, 0 };
+
+ switch (vp->magic) {
+ case LLDP_SNMP_ADDR_LEN:
+ long_ret = mgmt->m_addrsize + 1;
+ return (u_char *)&long_ret;
+ case LLDP_SNMP_ADDR_IFSUBTYPE:
+ if (mgmt->m_iface != 0)
+ long_ret = LLDP_MGMT_IFACE_IFINDEX;
+ else
+ long_ret = 1;
+ return (u_char *)&long_ret;
+ case LLDP_SNMP_ADDR_IFID:
+ long_ret = mgmt->m_iface;
+ return (u_char *)&long_ret;
+ case LLDP_SNMP_ADDR_OID:
+ *var_len = sizeof(zeroDotZero);
+ return (u_char *)zeroDotZero;
+ default:
+ 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)
+{
+
+ struct lldpd_mgmt *mgmt;
+
+ if ((mgmt = header_ipindexed_table(vp, name, length, exact, var_len,
+ write_method)) == NULL)
+ return NULL;
+
+ return agent_v_management(vp, var_len, mgmt);
+}
+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_mgmt *mgmt;
+
+ if ((mgmt = header_tpripindexed_table(vp, name, length, exact, var_len,
+ write_method)) == NULL)
+ return NULL;
+
+ return agent_v_management(vp, var_len, mgmt);
+}
+
+#ifdef ENABLE_CUSTOM
+static u_char *
+agent_v_custom(struct variable *vp, size_t *var_len, struct lldpd_custom *custom)
+{
+ switch (vp->magic) {
+ case LLDP_SNMP_ORG_DEF_INFO:
+ *var_len = custom->oui_info_len;
+ return (u_char *)custom->oui_info;
+ default:
+ return NULL;
+ }
+}
+static u_char *
+agent_h_remote_custom(struct variable *vp, oid *name, size_t *length, int exact,
+ size_t *var_len, WriteMethod **write_method)
+{
+ struct lldpd_custom *custom;
+
+ if ((custom = header_tprcustomindexed_table(vp, name, length, exact, var_len,
+ write_method)) == NULL)
+ return NULL;
+
+ return agent_v_custom(vp, var_len, custom);
+}
+#endif
+
+/*
+ Here is how it works: a agent_h_*() function will handle incoming
+ requests. It will use an appropriate header_*indexed_table()
+ function to grab the appropriate structure that was queried (a port,
+ a chassis, ...). It will then delegate to a agent_v_*() function the
+ responsability to extract the appropriate answer.
+
+ agent_h_*() functions and header_*indexed_table() are not shared
+ between remote and not remote version while agent_v_*() functions
+ are the same for both version.
+*/
+
+/* For testing purposes, keep this structure ordered by increasing OID! */
+struct variable8 agent_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 } },
+ /* 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 chassis */
+ { LLDP_SNMP_CIDSUBTYPE, ASN_INTEGER, RONLY, agent_h_local_chassis, 3,
+ { 1, 3, 1 } },
+ { LLDP_SNMP_CID, ASN_OCTET_STR, RONLY, agent_h_local_chassis, 3, { 1, 3, 2 } },
+ { LLDP_SNMP_SYSNAME, ASN_OCTET_STR, RONLY, agent_h_local_chassis, 3,
+ { 1, 3, 3 } },
+ { LLDP_SNMP_SYSDESCR, ASN_OCTET_STR, RONLY, agent_h_local_chassis, 3,
+ { 1, 3, 4 } },
+ { LLDP_SNMP_SYSCAP_SUP, ASN_OCTET_STR, RONLY, agent_h_local_chassis, 3,
+ { 1, 3, 5 } },
+ { LLDP_SNMP_SYSCAP_ENA, ASN_OCTET_STR, RONLY, agent_h_local_chassis, 3,
+ { 1, 3, 6 } },
+ /* Local ports */
+ { LLDP_SNMP_PIDSUBTYPE, ASN_INTEGER, RONLY, agent_h_local_port, 5,
+ { 1, 3, 7, 1, 2 } },
+ { LLDP_SNMP_PID, ASN_OCTET_STR, RONLY, agent_h_local_port, 5,
+ { 1, 3, 7, 1, 3 } },
+ { LLDP_SNMP_PORTDESC, ASN_OCTET_STR, RONLY, agent_h_local_port, 5,
+ { 1, 3, 7, 1, 4 } },
+ /* Local management address */
+ { LLDP_SNMP_ADDR_LEN, ASN_INTEGER, RONLY, agent_h_local_management, 5,
+ { 1, 3, 8, 1, 3 } },
+ { LLDP_SNMP_ADDR_IFSUBTYPE, ASN_INTEGER, RONLY, agent_h_local_management, 5,
+ { 1, 3, 8, 1, 4 } },
+ { LLDP_SNMP_ADDR_IFID, ASN_INTEGER, RONLY, agent_h_local_management, 5,
+ { 1, 3, 8, 1, 5 } },
+ { LLDP_SNMP_ADDR_OID, ASN_OBJECT_ID, RONLY, agent_h_local_management, 5,
+ { 1, 3, 8, 1, 6 } },
+ /* Remote ports */
+ { LLDP_SNMP_CIDSUBTYPE, ASN_INTEGER, RONLY, agent_h_remote_chassis, 5,
+ { 1, 4, 1, 1, 4 } },
+ { LLDP_SNMP_CID, ASN_OCTET_STR, RONLY, agent_h_remote_chassis, 5,
+ { 1, 4, 1, 1, 5 } },
+ { LLDP_SNMP_PIDSUBTYPE, ASN_INTEGER, RONLY, agent_h_remote_port, 5,
+ { 1, 4, 1, 1, 6 } },
+ { LLDP_SNMP_PID, ASN_OCTET_STR, RONLY, agent_h_remote_port, 5,
+ { 1, 4, 1, 1, 7 } },
+ { LLDP_SNMP_PORTDESC, ASN_OCTET_STR, RONLY, agent_h_remote_port, 5,
+ { 1, 4, 1, 1, 8 } },
+ { LLDP_SNMP_SYSNAME, ASN_OCTET_STR, RONLY, agent_h_remote_chassis, 5,
+ { 1, 4, 1, 1, 9 } },
+ { LLDP_SNMP_SYSDESCR, ASN_OCTET_STR, RONLY, agent_h_remote_chassis, 5,
+ { 1, 4, 1, 1, 10 } },
+ { LLDP_SNMP_SYSCAP_SUP, ASN_OCTET_STR, RONLY, agent_h_remote_chassis, 5,
+ { 1, 4, 1, 1, 11 } },
+ { LLDP_SNMP_SYSCAP_ENA, ASN_OCTET_STR, RONLY, agent_h_remote_chassis, 5,
+ { 1, 4, 1, 1, 12 } },
+ /* Remote management address */
+ { LLDP_SNMP_ADDR_IFSUBTYPE, ASN_INTEGER, RONLY, agent_h_remote_management, 5,
+ { 1, 4, 2, 1, 3 } },
+ { LLDP_SNMP_ADDR_IFID, ASN_INTEGER, RONLY, agent_h_remote_management, 5,
+ { 1, 4, 2, 1, 4 } },
+ { LLDP_SNMP_ADDR_OID, ASN_OBJECT_ID, RONLY, agent_h_remote_management, 5,
+ { 1, 4, 2, 1, 5 } },
+#ifdef ENABLE_CUSTOM
+ /* Custom TLVs */
+ { LLDP_SNMP_ORG_DEF_INFO, ASN_OCTET_STR, RONLY, agent_h_remote_custom, 5,
+ { 1, 4, 4, 1, 4 } },
+#endif
+#ifdef ENABLE_DOT3
+ /* Dot3, local ports */
+ { LLDP_SNMP_DOT3_AUTONEG_SUPPORT, ASN_INTEGER, RONLY, agent_h_local_port, 8,
+ { 1, 5, 4623, 1, 2, 1, 1, 1 } },
+ { LLDP_SNMP_DOT3_AUTONEG_ENABLED, ASN_INTEGER, RONLY, agent_h_local_port, 8,
+ { 1, 5, 4623, 1, 2, 1, 1, 2 } },
+ { LLDP_SNMP_DOT3_AUTONEG_ADVERTISED, ASN_OCTET_STR, RONLY, agent_h_local_port,
+ 8, { 1, 5, 4623, 1, 2, 1, 1, 3 } },
+ { LLDP_SNMP_DOT3_AUTONEG_MAU, ASN_INTEGER, RONLY, agent_h_local_port, 8,
+ { 1, 5, 4623, 1, 2, 1, 1, 4 } },
+ { LLDP_SNMP_DOT3_POWER_DEVICETYPE, ASN_INTEGER, RONLY, agent_h_local_port, 8,
+ { 1, 5, 4623, 1, 2, 2, 1, 1 } },
+ { LLDP_SNMP_DOT3_POWER_SUPPORT, ASN_INTEGER, RONLY, agent_h_local_port, 8,
+ { 1, 5, 4623, 1, 2, 2, 1, 2 } },
+ { LLDP_SNMP_DOT3_POWER_ENABLED, ASN_INTEGER, RONLY, agent_h_local_port, 8,
+ { 1, 5, 4623, 1, 2, 2, 1, 3 } },
+ { LLDP_SNMP_DOT3_POWER_PAIRCONTROL, ASN_INTEGER, RONLY, agent_h_local_port, 8,
+ { 1, 5, 4623, 1, 2, 2, 1, 4 } },
+ { LLDP_SNMP_DOT3_POWER_PAIRS, ASN_INTEGER, RONLY, agent_h_local_port, 8,
+ { 1, 5, 4623, 1, 2, 2, 1, 5 } },
+ { LLDP_SNMP_DOT3_POWER_CLASS, ASN_INTEGER, RONLY, agent_h_local_port, 8,
+ { 1, 5, 4623, 1, 2, 2, 1, 6 } },
+ { LLDP_SNMP_DOT3_POWER_TYPE, ASN_OCTET_STR, RONLY, agent_h_local_port, 8,
+ { 1, 5, 4623, 1, 2, 2, 1, 7 } },
+ { LLDP_SNMP_DOT3_POWER_SOURCE, ASN_OCTET_STR, RONLY, agent_h_local_port, 8,
+ { 1, 5, 4623, 1, 2, 2, 1, 8 } },
+ { LLDP_SNMP_DOT3_POWER_PRIORITY, ASN_INTEGER, RONLY, agent_h_local_port, 8,
+ { 1, 5, 4623, 1, 2, 2, 1, 9 } },
+ { LLDP_SNMP_DOT3_POWER_REQUESTED, ASN_INTEGER, RONLY, agent_h_local_port, 8,
+ { 1, 5, 4623, 1, 2, 2, 1, 10 } },
+ { LLDP_SNMP_DOT3_POWER_ALLOCATED, ASN_INTEGER, RONLY, agent_h_local_port, 8,
+ { 1, 5, 4623, 1, 2, 2, 1, 11 } },
+ { LLDP_SNMP_DOT3_AGG_STATUS, ASN_OCTET_STR, RONLY, agent_h_local_port, 8,
+ { 1, 5, 4623, 1, 2, 3, 1, 1 } },
+ { LLDP_SNMP_DOT3_AGG_ID, ASN_INTEGER, RONLY, agent_h_local_port, 8,
+ { 1, 5, 4623, 1, 2, 3, 1, 2 } },
+ { LLDP_SNMP_DOT3_MFS, ASN_INTEGER, RONLY, agent_h_local_port, 8,
+ { 1, 5, 4623, 1, 2, 4, 1, 1 } },
+#endif
+/* Dot3, remote ports */
+#ifdef ENABLE_DOT3
+ { LLDP_SNMP_DOT3_AUTONEG_SUPPORT, ASN_INTEGER, RONLY, agent_h_remote_port, 8,
+ { 1, 5, 4623, 1, 3, 1, 1, 1 } },
+ { LLDP_SNMP_DOT3_AUTONEG_ENABLED, ASN_INTEGER, RONLY, agent_h_remote_port, 8,
+ { 1, 5, 4623, 1, 3, 1, 1, 2 } },
+ { LLDP_SNMP_DOT3_AUTONEG_ADVERTISED, ASN_OCTET_STR, RONLY, agent_h_remote_port,
+ 8, { 1, 5, 4623, 1, 3, 1, 1, 3 } },
+ { LLDP_SNMP_DOT3_AUTONEG_MAU, ASN_INTEGER, RONLY, agent_h_remote_port, 8,
+ { 1, 5, 4623, 1, 3, 1, 1, 4 } },
+ { LLDP_SNMP_DOT3_POWER_DEVICETYPE, ASN_INTEGER, RONLY, agent_h_remote_port, 8,
+ { 1, 5, 4623, 1, 3, 2, 1, 1 } },
+ { LLDP_SNMP_DOT3_POWER_SUPPORT, ASN_INTEGER, RONLY, agent_h_remote_port, 8,
+ { 1, 5, 4623, 1, 3, 2, 1, 2 } },
+ { LLDP_SNMP_DOT3_POWER_ENABLED, ASN_INTEGER, RONLY, agent_h_remote_port, 8,
+ { 1, 5, 4623, 1, 3, 2, 1, 3 } },
+ { LLDP_SNMP_DOT3_POWER_PAIRCONTROL, ASN_INTEGER, RONLY, agent_h_remote_port, 8,
+ { 1, 5, 4623, 1, 3, 2, 1, 4 } },
+ { LLDP_SNMP_DOT3_POWER_PAIRS, ASN_INTEGER, RONLY, agent_h_remote_port, 8,
+ { 1, 5, 4623, 1, 3, 2, 1, 5 } },
+ { LLDP_SNMP_DOT3_POWER_CLASS, ASN_INTEGER, RONLY, agent_h_remote_port, 8,
+ { 1, 5, 4623, 1, 3, 2, 1, 6 } },
+ { LLDP_SNMP_DOT3_POWER_TYPE, ASN_OCTET_STR, RONLY, agent_h_remote_port, 8,
+ { 1, 5, 4623, 1, 3, 2, 1, 7 } },
+ { LLDP_SNMP_DOT3_POWER_SOURCE, ASN_OCTET_STR, RONLY, agent_h_remote_port, 8,
+ { 1, 5, 4623, 1, 3, 2, 1, 8 } },
+ { LLDP_SNMP_DOT3_POWER_PRIORITY, ASN_INTEGER, RONLY, agent_h_remote_port, 8,
+ { 1, 5, 4623, 1, 3, 2, 1, 9 } },
+ { LLDP_SNMP_DOT3_POWER_REQUESTED, ASN_INTEGER, RONLY, agent_h_remote_port, 8,
+ { 1, 5, 4623, 1, 3, 2, 1, 10 } },
+ { LLDP_SNMP_DOT3_POWER_ALLOCATED, ASN_INTEGER, RONLY, agent_h_remote_port, 8,
+ { 1, 5, 4623, 1, 3, 2, 1, 11 } },
+ { LLDP_SNMP_DOT3_AGG_STATUS, ASN_OCTET_STR, RONLY, agent_h_remote_port, 8,
+ { 1, 5, 4623, 1, 3, 3, 1, 1 } },
+ { LLDP_SNMP_DOT3_AGG_ID, ASN_INTEGER, RONLY, agent_h_remote_port, 8,
+ { 1, 5, 4623, 1, 3, 3, 1, 2 } },
+ { LLDP_SNMP_DOT3_MFS, ASN_INTEGER, RONLY, agent_h_remote_port, 8,
+ { 1, 5, 4623, 1, 3, 4, 1, 1 } },
+#endif
+#ifdef ENABLE_LLDPMED
+ /* LLDP-MED local */
+ { LLDP_SNMP_MED_CLASS, ASN_INTEGER, RONLY, agent_h_local_med, 6,
+ { 1, 5, 4795, 1, 1, 1 } },
+ { LLDP_SNMP_MED_POLICY_VID, ASN_INTEGER, RONLY, agent_h_local_med_policy, 8,
+ { 1, 5, 4795, 1, 2, 1, 1, 2 } },
+ { LLDP_SNMP_MED_POLICY_PRIO, ASN_INTEGER, RONLY, agent_h_local_med_policy, 8,
+ { 1, 5, 4795, 1, 2, 1, 1, 3 } },
+ { LLDP_SNMP_MED_POLICY_DSCP, ASN_INTEGER, RONLY, agent_h_local_med_policy, 8,
+ { 1, 5, 4795, 1, 2, 1, 1, 4 } },
+ { LLDP_SNMP_MED_POLICY_UNKNOWN, ASN_INTEGER, RONLY, agent_h_local_med_policy, 8,
+ { 1, 5, 4795, 1, 2, 1, 1, 5 } },
+ { LLDP_SNMP_MED_POLICY_TAGGED, ASN_INTEGER, RONLY, agent_h_local_med_policy, 8,
+ { 1, 5, 4795, 1, 2, 1, 1, 6 } },
+ { LLDP_SNMP_MED_HW, ASN_OCTET_STR, RONLY, agent_h_local_med, 6,
+ { 1, 5, 4795, 1, 2, 2 } },
+ { LLDP_SNMP_MED_FW, ASN_OCTET_STR, RONLY, agent_h_local_med, 6,
+ { 1, 5, 4795, 1, 2, 3 } },
+ { LLDP_SNMP_MED_SW, ASN_OCTET_STR, RONLY, agent_h_local_med, 6,
+ { 1, 5, 4795, 1, 2, 4 } },
+ { LLDP_SNMP_MED_SN, ASN_OCTET_STR, RONLY, agent_h_local_med, 6,
+ { 1, 5, 4795, 1, 2, 5 } },
+ { LLDP_SNMP_MED_MANUF, ASN_OCTET_STR, RONLY, agent_h_local_med, 6,
+ { 1, 5, 4795, 1, 2, 6 } },
+ { LLDP_SNMP_MED_MODEL, ASN_OCTET_STR, RONLY, agent_h_local_med, 6,
+ { 1, 5, 4795, 1, 2, 7 } },
+ { LLDP_SNMP_MED_ASSET, ASN_OCTET_STR, RONLY, agent_h_local_med, 6,
+ { 1, 5, 4795, 1, 2, 8 } },
+ { LLDP_SNMP_MED_LOCATION, ASN_OCTET_STR, RONLY, agent_h_local_med_location, 8,
+ { 1, 5, 4795, 1, 2, 9, 1, 2 } },
+ { LLDP_SNMP_MED_POE_DEVICETYPE, ASN_INTEGER, RONLY, agent_h_local_med_power, 6,
+ { 1, 5, 4795, 1, 2, 10 } },
+ { LLDP_SNMP_MED_POE_PSE_POWERVAL, ASN_GAUGE, RONLY, agent_h_local_med_power, 8,
+ { 1, 5, 4795, 1, 2, 11, 1, 1 } },
+ { LLDP_SNMP_MED_POE_PSE_POWERPRIORITY, ASN_INTEGER, RONLY,
+ agent_h_local_med_power, 8, { 1, 5, 4795, 1, 2, 11, 1, 2 } },
+ { LLDP_SNMP_MED_POE_PSE_POWERSOURCE, ASN_INTEGER, RONLY,
+ agent_h_local_med_power, 6, { 1, 5, 4795, 1, 2, 12 } },
+ { LLDP_SNMP_MED_POE_PD_POWERVAL, ASN_GAUGE, RONLY, agent_h_local_med_power, 6,
+ { 1, 5, 4795, 1, 2, 13 } },
+ { LLDP_SNMP_MED_POE_PD_POWERSOURCE, ASN_INTEGER, RONLY, agent_h_local_med_power,
+ 6, { 1, 5, 4795, 1, 2, 14 } },
+ { LLDP_SNMP_MED_POE_PD_POWERPRIORITY, ASN_INTEGER, RONLY,
+ agent_h_local_med_power, 6, { 1, 5, 4795, 1, 2, 15 } },
+ /* LLDP-MED remote */
+ { LLDP_SNMP_MED_CAP_AVAILABLE, ASN_OCTET_STR, RONLY, agent_h_remote_med, 8,
+ { 1, 5, 4795, 1, 3, 1, 1, 1 } },
+ { LLDP_SNMP_MED_CAP_ENABLED, ASN_OCTET_STR, RONLY, agent_h_remote_med, 8,
+ { 1, 5, 4795, 1, 3, 1, 1, 2 } },
+ { LLDP_SNMP_MED_CLASS, ASN_INTEGER, RONLY, agent_h_remote_med, 8,
+ { 1, 5, 4795, 1, 3, 1, 1, 3 } },
+ { LLDP_SNMP_MED_POLICY_VID, ASN_INTEGER, RONLY, agent_h_remote_med_policy, 8,
+ { 1, 5, 4795, 1, 3, 2, 1, 2 } },
+ { LLDP_SNMP_MED_POLICY_PRIO, ASN_INTEGER, RONLY, agent_h_remote_med_policy, 8,
+ { 1, 5, 4795, 1, 3, 2, 1, 3 } },
+ { LLDP_SNMP_MED_POLICY_DSCP, ASN_INTEGER, RONLY, agent_h_remote_med_policy, 8,
+ { 1, 5, 4795, 1, 3, 2, 1, 4 } },
+ { LLDP_SNMP_MED_POLICY_UNKNOWN, ASN_INTEGER, RONLY, agent_h_remote_med_policy,
+ 8, { 1, 5, 4795, 1, 3, 2, 1, 5 } },
+ { LLDP_SNMP_MED_POLICY_TAGGED, ASN_INTEGER, RONLY, agent_h_remote_med_policy, 8,
+ { 1, 5, 4795, 1, 3, 2, 1, 6 } },
+ { LLDP_SNMP_MED_HW, ASN_OCTET_STR, RONLY, agent_h_remote_med, 8,
+ { 1, 5, 4795, 1, 3, 3, 1, 1 } },
+ { LLDP_SNMP_MED_FW, ASN_OCTET_STR, RONLY, agent_h_remote_med, 8,
+ { 1, 5, 4795, 1, 3, 3, 1, 2 } },
+ { LLDP_SNMP_MED_SW, ASN_OCTET_STR, RONLY, agent_h_remote_med, 8,
+ { 1, 5, 4795, 1, 3, 3, 1, 3 } },
+ { LLDP_SNMP_MED_SN, ASN_OCTET_STR, RONLY, agent_h_remote_med, 8,
+ { 1, 5, 4795, 1, 3, 3, 1, 4 } },
+ { LLDP_SNMP_MED_MANUF, ASN_OCTET_STR, RONLY, agent_h_remote_med, 8,
+ { 1, 5, 4795, 1, 3, 3, 1, 5 } },
+ { LLDP_SNMP_MED_MODEL, ASN_OCTET_STR, RONLY, agent_h_remote_med, 8,
+ { 1, 5, 4795, 1, 3, 3, 1, 6 } },
+ { LLDP_SNMP_MED_ASSET, ASN_OCTET_STR, RONLY, agent_h_remote_med, 8,
+ { 1, 5, 4795, 1, 3, 3, 1, 7 } },
+ { LLDP_SNMP_MED_LOCATION, ASN_OCTET_STR, RONLY, agent_h_remote_med_location, 8,
+ { 1, 5, 4795, 1, 3, 4, 1, 2 } },
+ { LLDP_SNMP_MED_POE_DEVICETYPE, ASN_INTEGER, RONLY, agent_h_remote_med_power, 8,
+ { 1, 5, 4795, 1, 3, 5, 1, 1 } },
+ { LLDP_SNMP_MED_POE_PSE_POWERVAL, ASN_GAUGE, RONLY, agent_h_remote_med_power, 8,
+ { 1, 5, 4795, 1, 3, 6, 1, 1 } },
+ { LLDP_SNMP_MED_POE_PSE_POWERSOURCE, ASN_INTEGER, RONLY,
+ agent_h_remote_med_power, 8, { 1, 5, 4795, 1, 3, 6, 1, 2 } },
+ { LLDP_SNMP_MED_POE_PSE_POWERPRIORITY, ASN_INTEGER, RONLY,
+ agent_h_remote_med_power, 8, { 1, 5, 4795, 1, 3, 6, 1, 3 } },
+ { LLDP_SNMP_MED_POE_PD_POWERVAL, ASN_GAUGE, RONLY, agent_h_remote_med_power, 8,
+ { 1, 5, 4795, 1, 3, 7, 1, 1 } },
+ { LLDP_SNMP_MED_POE_PD_POWERSOURCE, ASN_INTEGER, RONLY,
+ agent_h_remote_med_power, 8, { 1, 5, 4795, 1, 3, 7, 1, 2 } },
+ { LLDP_SNMP_MED_POE_PD_POWERPRIORITY, ASN_INTEGER, RONLY,
+ agent_h_remote_med_power, 8, { 1, 5, 4795, 1, 3, 7, 1, 3 } },
+#endif
+/* Dot1, local and remote ports */
+#ifdef ENABLE_DOT1
+ { LLDP_SNMP_DOT1_PVID, ASN_INTEGER, RONLY, agent_h_local_port, 8,
+ { 1, 5, 32962, 1, 2, 1, 1, 1 } },
+ { LLDP_SNMP_DOT1_PPVLAN_SUPPORTED, ASN_INTEGER, RONLY, agent_h_local_ppvid, 8,
+ { 1, 5, 32962, 1, 2, 2, 1, 2 } },
+ { LLDP_SNMP_DOT1_PPVLAN_ENABLED, ASN_INTEGER, RONLY, agent_h_local_ppvid, 8,
+ { 1, 5, 32962, 1, 2, 2, 1, 3 } },
+ { LLDP_SNMP_DOT1_VLANNAME, ASN_OCTET_STR, RONLY, agent_h_local_vlan, 8,
+ { 1, 5, 32962, 1, 2, 3, 1, 2 } },
+ { LLDP_SNMP_DOT1_PI, ASN_OCTET_STR, RONLY, agent_h_local_pi, 8,
+ { 1, 5, 32962, 1, 2, 4, 1, 2 } },
+#endif
+#ifdef ENABLE_DOT1
+ { LLDP_SNMP_DOT1_PVID, ASN_INTEGER, RONLY, agent_h_remote_port, 8,
+ { 1, 5, 32962, 1, 3, 1, 1, 1 } },
+ { LLDP_SNMP_DOT1_PPVLAN_SUPPORTED, ASN_INTEGER, RONLY, agent_h_remote_ppvid, 8,
+ { 1, 5, 32962, 1, 3, 2, 1, 2 } },
+ { LLDP_SNMP_DOT1_PPVLAN_ENABLED, ASN_INTEGER, RONLY, agent_h_remote_ppvid, 8,
+ { 1, 5, 32962, 1, 3, 2, 1, 3 } },
+ /* Remote vlans */
+ { LLDP_SNMP_DOT1_VLANNAME, ASN_OCTET_STR, RONLY, agent_h_remote_vlan, 8,
+ { 1, 5, 32962, 1, 3, 3, 1, 2 } },
+ /* Protocol identity */
+ { LLDP_SNMP_DOT1_PI, ASN_OCTET_STR, RONLY, agent_h_remote_pi, 8,
+ { 1, 5, 32962, 1, 3, 4, 1, 2 } },
+#endif
+};
+size_t
+agent_lldp_vars_size(void)
+{
+ return sizeof(agent_lldp_vars) / sizeof(struct variable8);
+}
+
+/**
+ * Send a notification about a change in one remote neighbor.
+ *
+ * @param hardware Interface on which the change has happened.
+ * @param type Type of change (add, delete, update)
+ * @param rport Changed remote port
+ */
+void
+agent_notify(struct lldpd_hardware *hardware, int type, struct lldpd_port *rport)
+{
+ struct lldpd_hardware *h;
+
+ /* OID of the notification */
+ oid notification_oid[] = { LLDP_OID, 0, 0, 1 };
+ size_t notification_oid_len = OID_LENGTH(notification_oid);
+ /* OID for snmpTrapOID.0 */
+ oid objid_snmptrap[] = { SNMPTRAP_OID };
+ size_t objid_snmptrap_len = OID_LENGTH(objid_snmptrap);
+
+ /* Other OID */
+ oid inserts_oid[] = { LLDP_OID, 1, 2, 2 };
+ size_t inserts_oid_len = OID_LENGTH(inserts_oid);
+ unsigned long inserts = 0;
+
+ oid deletes_oid[] = { LLDP_OID, 1, 2, 3 };
+ size_t deletes_oid_len = OID_LENGTH(deletes_oid);
+ unsigned long deletes = 0;
+
+ oid drops_oid[] = { LLDP_OID, 1, 2, 4 };
+ size_t drops_oid_len = OID_LENGTH(drops_oid);
+ unsigned long drops = 0;
+
+ oid ageouts_oid[] = { LLDP_OID, 1, 2, 5 };
+ size_t ageouts_oid_len = OID_LENGTH(ageouts_oid);
+ unsigned long ageouts = 0;
+
+ /* We also add some extra. Easy ones. */
+ oid locport_oid[] = { LLDP_OID, 1, 3, 7, 1, 4, hardware->h_ifindex };
+ size_t locport_oid_len = OID_LENGTH(locport_oid);
+ oid sysname_oid[] = { LLDP_OID, 1, 4, 1, 1, 9, lastchange(rport),
+ hardware->h_ifindex, rport->p_chassis->c_index };
+ size_t sysname_oid_len = OID_LENGTH(sysname_oid);
+ oid portdescr_oid[] = { LLDP_OID, 1, 4, 1, 1, 8, lastchange(rport),
+ hardware->h_ifindex, rport->p_chassis->c_index };
+ size_t portdescr_oid_len = OID_LENGTH(portdescr_oid);
+
+ netsnmp_variable_list *notification_vars = NULL;
+
+ if (!hardware->h_cfg->g_snmp) return;
+
+ switch (type) {
+ case NEIGHBOR_CHANGE_DELETED:
+ log_debug("snmp", "send notification for neighbor deleted on %s",
+ hardware->h_ifname);
+ break;
+ case NEIGHBOR_CHANGE_UPDATED:
+ log_debug("snmp", "send notification for neighbor updated on %s",
+ hardware->h_ifname);
+ break;
+ case NEIGHBOR_CHANGE_ADDED:
+ log_debug("snmp", "send notification for neighbor added on %s",
+ hardware->h_ifname);
+ break;
+ }
+
+ TAILQ_FOREACH (h, &hardware->h_cfg->g_hardware, h_entries) {
+ inserts += h->h_insert_cnt;
+ deletes += h->h_delete_cnt;
+ ageouts += h->h_ageout_cnt;
+ drops += h->h_drop_cnt;
+ }
+
+ /* snmpTrapOID */
+ snmp_varlist_add_variable(&notification_vars, objid_snmptrap,
+ objid_snmptrap_len, ASN_OBJECT_ID, (u_char *)notification_oid,
+ notification_oid_len * sizeof(oid));
+
+ snmp_varlist_add_variable(&notification_vars, inserts_oid, inserts_oid_len,
+ ASN_GAUGE, (u_char *)&inserts, sizeof(inserts));
+ snmp_varlist_add_variable(&notification_vars, deletes_oid, deletes_oid_len,
+ ASN_GAUGE, (u_char *)&deletes, sizeof(inserts));
+ snmp_varlist_add_variable(&notification_vars, drops_oid, drops_oid_len,
+ ASN_GAUGE, (u_char *)&drops, sizeof(drops));
+ snmp_varlist_add_variable(&notification_vars, ageouts_oid, ageouts_oid_len,
+ ASN_GAUGE, (u_char *)&ageouts, sizeof(ageouts));
+
+ if (type != NEIGHBOR_CHANGE_DELETED) {
+ snmp_varlist_add_variable(&notification_vars, locport_oid,
+ locport_oid_len, ASN_OCTET_STR, (u_char *)hardware->h_ifname,
+ strnlen(hardware->h_ifname, IFNAMSIZ));
+ if (rport->p_chassis->c_name && *rport->p_chassis->c_name != '\0') {
+ snmp_varlist_add_variable(&notification_vars, sysname_oid,
+ sysname_oid_len, ASN_OCTET_STR,
+ (u_char *)rport->p_chassis->c_name,
+ strlen(rport->p_chassis->c_name));
+ }
+ if (rport->p_descr) {
+ snmp_varlist_add_variable(&notification_vars, portdescr_oid,
+ portdescr_oid_len, ASN_OCTET_STR, (u_char *)rport->p_descr,
+ strlen(rport->p_descr));
+ }
+ }
+
+ log_debug("snmp", "sending SNMP trap (%ld, %ld, %ld)", inserts, deletes,
+ ageouts);
+ send_v2trap(notification_vars);
+ snmp_free_varbind(notification_vars);
+}
+
+/* Logging NetSNMP messages */
+static int
+agent_log_callback(int major, int minor, void *serverarg, void *clientarg)
+{
+ struct snmp_log_message *slm = (struct snmp_log_message *)serverarg;
+ char *msg = strdup(slm->msg);
+ (void)major;
+ (void)minor;
+ (void)clientarg;
+
+ if (msg && msg[strlen(msg) - 1] == '\n') msg[strlen(msg) - 1] = '\0';
+ switch (slm->priority) {
+ case LOG_EMERG:
+ log_warnx("libsnmp", "%s", msg ? msg : slm->msg);
+ break;
+ case LOG_ALERT:
+ log_warnx("libsnmp", "%s", msg ? msg : slm->msg);
+ break;
+ case LOG_CRIT:
+ log_warnx("libsnmp", "%s", msg ? msg : slm->msg);
+ break;
+ case LOG_ERR:
+ log_warnx("libsnmp", "%s", msg ? msg : slm->msg);
+ break;
+ case LOG_WARNING:
+ log_warnx("libsnmp", "%s", msg ? msg : slm->msg);
+ break;
+ case LOG_NOTICE:
+ log_info("libsnmp", "%s", msg ? msg : slm->msg);
+ break;
+ case LOG_INFO:
+ log_info("libsnmp", "%s", msg ? msg : slm->msg);
+ break;
+ case LOG_DEBUG:
+ log_debug("libsnmp", "%s", msg ? msg : slm->msg);
+ break;
+ }
+ free(msg);
+ return SNMP_ERR_NOERROR;
+}
+
+void
+agent_init(struct lldpd *cfg, const char *agentx)
+{
+ int rc;
+
+ log_info("snmp", "enable SNMP subagent");
+ netsnmp_enable_subagent();
+
+ log_debug("snmp", "enable logging");
+ snmp_disable_log();
+ snmp_enable_calllog();
+ snmp_register_callback(SNMP_CALLBACK_LIBRARY, SNMP_CALLBACK_LOGGING,
+ agent_log_callback, NULL);
+
+ scfg = cfg;
+
+ /* We are chrooted, we don't want to handle persistent states */
+ netsnmp_ds_set_boolean(NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_DONT_PERSIST_STATE,
+ TRUE);
+ /* Do not load any MIB */
+ setenv("MIBS", "", 1);
+ setenv("MIBDIRS", "/dev/null", 1);
+
+#ifdef ENABLE_PRIVSEP
+ /* We provide our UNIX domain transport */
+ log_debug("snmp", "register UNIX domain transport");
+ agent_priv_register_domain();
+#endif
+
+ if (agentx)
+ netsnmp_ds_set_string(NETSNMP_DS_APPLICATION_ID,
+ NETSNMP_DS_AGENT_X_SOCKET, agentx);
+ init_agent("lldpAgent");
+ REGISTER_MIB("lldp", agent_lldp_vars, variable8, lldp_oid);
+ init_snmp("lldpAgent");
+
+ log_debug("snmp", "register to sysORTable");
+ if ((rc = register_sysORTable(lldp_oid, OID_LENGTH(lldp_oid),
+ "lldpMIB implementation by lldpd")) != 0)
+ log_warnx("snmp", "unable to register to sysORTable (%d)", rc);
+}
+
+void
+agent_shutdown()
+{
+ log_debug("snmp", "agent shutdown");
+ unregister_sysORTable(lldp_oid, OID_LENGTH(lldp_oid));
+ snmp_shutdown("lldpAgent");
+}
diff --git a/src/daemon/agent.h b/src/daemon/agent.h
new file mode 100644
index 0000000..b498aeb
--- /dev/null
+++ b/src/daemon/agent.h
@@ -0,0 +1,35 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/*
+ * Copyright (c) 2008 Vincent Bernat <bernat@luffy.cx>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef _AGENT_H
+#define _AGENT_H
+
+#include <net-snmp/net-snmp-config.h>
+#include <net-snmp/net-snmp-includes.h>
+#include <net-snmp/agent/net-snmp-agent-includes.h>
+#include <net-snmp/agent/snmp_vars.h>
+
+#ifndef RONLY
+# define RONLY NETSNMP_OLDAPI_RONLY
+#endif
+
+#define LLDP_OID 1, 0, 8802, 1, 1, 2
+#define SNMPTRAP_OID 1, 3, 6, 1, 6, 3, 1, 1, 4, 1, 0
+static oid lldp_oid[] = { LLDP_OID };
+size_t agent_lldp_vars_size(void);
+
+#endif
diff --git a/src/daemon/agent_priv.c b/src/daemon/agent_priv.c
new file mode 100644
index 0000000..b5cf72a
--- /dev/null
+++ b/src/daemon/agent_priv.c
@@ -0,0 +1,243 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/*
+ * Copyright (c) 2008 Vincent Bernat <bernat@luffy.cx>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/* Some of the code here (agent_priv_unix_*) has been adapted from code from
+ * Net-SNMP project (snmplib/snmpUnixDomain.c). Net-SNMP project is licensed
+ * using BSD and BSD-like licenses. I don't know the exact license of the file
+ * snmplib/snmpUnixDomain.c. */
+
+#include "lldpd.h"
+
+#include <unistd.h>
+#include <errno.h>
+#include <poll.h>
+
+#ifdef ENABLE_PRIVSEP
+# include <net-snmp/net-snmp-config.h>
+# include <net-snmp/net-snmp-includes.h>
+# include <net-snmp/agent/net-snmp-agent-includes.h>
+# include <net-snmp/agent/snmp_vars.h>
+# include <net-snmp/library/snmpUnixDomain.h>
+
+# ifdef ASN_PRIV_STOP
+/* NetSNMP 5.8+ */
+# define F_SEND_SIGNATURE \
+ netsnmp_transport *t, const void *buf, int size, void **opaque, int *olength
+# define F_FMTADDR_SIGNATURE netsnmp_transport *t, const void *data, int len
+# define F_FROM_OSTRING_SIGNATURE const void *o, size_t o_len, int local
+# else
+# define F_SEND_SIGNATURE \
+ netsnmp_transport *t, void *buf, int size, void **opaque, int *olength
+# define F_FMTADDR_SIGNATURE netsnmp_transport *t, void *data, int len
+# define F_FROM_OSTRING_SIGNATURE const u_char *o, size_t o_len, int local
+# endif
+
+static oid netsnmp_unix[] = { TRANSPORT_DOMAIN_LOCAL };
+static netsnmp_tdomain unixDomain;
+
+static char *
+agent_priv_unix_fmtaddr(F_FMTADDR_SIGNATURE)
+{
+ /* We don't bother to implement the full function */
+ return strdup("Local Unix socket with privilege separation: unknown");
+}
+
+static int
+agent_priv_unix_recv(netsnmp_transport *t, void *buf, int size, void **opaque,
+ int *olength)
+{
+ int rc = -1;
+ socklen_t tolen = sizeof(struct sockaddr_un);
+ struct sockaddr *to = NULL;
+
+ if (t == NULL || t->sock < 0) goto recv_error;
+ to = (struct sockaddr *)calloc(1, sizeof(struct sockaddr_un));
+ if (to == NULL) goto recv_error;
+ if (getsockname(t->sock, to, &tolen) != 0) goto recv_error;
+ while (rc < 0) {
+ rc = recv(t->sock, buf, size, 0);
+ /* TODO: handle the (unlikely) case where we get EAGAIN or EWOULDBLOCK
+ */
+ if (rc < 0 && errno != EINTR) {
+ log_warn("snmp", "unable to receive from fd %d", t->sock);
+ goto recv_error;
+ }
+ }
+ *opaque = (void *)to;
+ *olength = sizeof(struct sockaddr_un);
+ return rc;
+
+recv_error:
+ free(to);
+ *opaque = NULL;
+ *olength = 0;
+ return -1;
+}
+
+# define AGENT_WRITE_TIMEOUT 2000
+static int
+agent_priv_unix_send(F_SEND_SIGNATURE)
+{
+ int rc = -1;
+
+ if (t != NULL && t->sock >= 0) {
+ struct pollfd sagentx = { .fd = t->sock,
+ .events = POLLOUT | POLLERR | POLLHUP };
+ while (rc < 0) {
+ rc = poll(&sagentx, 1, AGENT_WRITE_TIMEOUT);
+ if (rc == 0) {
+ log_warnx("snmp",
+ "timeout while communicating with the master agent");
+ rc = -1;
+ break;
+ }
+ if (rc > 0) {
+ /* We can either write or have an error somewhere */
+ rc = send(t->sock, buf, size, 0);
+ if (rc < 0) {
+ if (errno == EAGAIN || errno == EWOULDBLOCK ||
+ errno == EINTR)
+ /* Let's retry */
+ continue;
+ log_warn("snmp",
+ "error while sending to master agent");
+ break;
+ }
+ } else {
+ if (errno != EINTR) {
+ log_warn("snmp",
+ "error while attempting to send to master agent");
+ break;
+ }
+ continue;
+ }
+ }
+ }
+ return rc;
+}
+
+static int
+agent_priv_unix_close(netsnmp_transport *t)
+{
+ int rc = 0;
+
+ if (t->sock >= 0) {
+ rc = close(t->sock);
+ t->sock = -1;
+ return rc;
+ }
+ return -1;
+}
+
+static int
+agent_priv_unix_accept(netsnmp_transport *t)
+{
+ log_warnx("snmp", "should not have been called");
+ return -1;
+}
+
+static netsnmp_transport *
+agent_priv_unix_transport(const char *string, int len, int local)
+{
+ struct sockaddr_un addr = { .sun_family = AF_UNIX };
+ netsnmp_transport *t = NULL;
+
+ if (local) {
+ log_warnx("snmp", "should not have been called for local transport");
+ return NULL;
+ }
+ if (!string) return NULL;
+ if (len >= sizeof(addr.sun_path) ||
+ strlcpy(addr.sun_path, string, sizeof(addr.sun_path)) >=
+ sizeof(addr.sun_path)) {
+ log_warnx("snmp", "path too long for Unix domain transport");
+ return NULL;
+ }
+
+ if ((t = (netsnmp_transport *)calloc(1, sizeof(netsnmp_transport))) == NULL)
+ return NULL;
+
+ t->domain = netsnmp_unix;
+ t->domain_length = sizeof(netsnmp_unix) / sizeof(netsnmp_unix[0]);
+
+ if ((t->sock = priv_snmp_socket(&addr)) < 0) {
+ netsnmp_transport_free(t);
+ return NULL;
+ }
+
+ t->flags = NETSNMP_TRANSPORT_FLAG_STREAM;
+
+ if ((t->remote = (u_char *)calloc(1, strlen(addr.sun_path) + 1)) == NULL) {
+ agent_priv_unix_close(t);
+ netsnmp_transport_free(t);
+ return NULL;
+ }
+ memcpy(t->remote, addr.sun_path, strlen(addr.sun_path));
+ t->remote_length = strlen(addr.sun_path);
+
+ t->msgMaxSize = 0x7fffffff;
+ t->f_recv = agent_priv_unix_recv;
+ t->f_send = agent_priv_unix_send;
+ t->f_close = agent_priv_unix_close;
+ t->f_accept = agent_priv_unix_accept;
+ t->f_fmtaddr = agent_priv_unix_fmtaddr;
+
+ return t;
+}
+
+# if HAVE_NETSNMP_TDOMAIN_F_CREATE_FROM_TSTRING_NEW
+static netsnmp_transport *
+agent_priv_unix_create_tstring_new(const char *string, int local,
+ const char *default_target)
+{
+ if ((!string || *string == '\0') && default_target && *default_target != '\0') {
+ string = default_target;
+ }
+ if (!string) return NULL;
+ return agent_priv_unix_transport(string, strlen(string), local);
+}
+# else
+static netsnmp_transport *
+agent_priv_unix_create_tstring(const char *string, int local)
+{
+ if (!string) return NULL;
+ return agent_priv_unix_transport(string, strlen(string), local);
+}
+# endif
+
+static netsnmp_transport *
+agent_priv_unix_create_ostring(F_FROM_OSTRING_SIGNATURE)
+{
+ return agent_priv_unix_transport((char *)o, o_len, local);
+}
+
+void
+agent_priv_register_domain()
+{
+ unixDomain.name = netsnmp_unix;
+ unixDomain.name_length = sizeof(netsnmp_unix) / sizeof(oid);
+ unixDomain.prefix = (const char **)calloc(2, sizeof(char *));
+ unixDomain.prefix[0] = "unix";
+# if HAVE_NETSNMP_TDOMAIN_F_CREATE_FROM_TSTRING_NEW
+ unixDomain.f_create_from_tstring_new = agent_priv_unix_create_tstring_new;
+# else
+ unixDomain.f_create_from_tstring = agent_priv_unix_create_tstring;
+# endif
+ unixDomain.f_create_from_ostring = agent_priv_unix_create_ostring;
+ netsnmp_tdomain_register(&unixDomain);
+}
+#endif
diff --git a/src/daemon/bitmap.c b/src/daemon/bitmap.c
new file mode 100644
index 0000000..c7a6330
--- /dev/null
+++ b/src/daemon/bitmap.c
@@ -0,0 +1,63 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/*
+ * Copyright (c) 2020 Vincent Bernat <bernat@luffy.cx>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/* Helpers around bitmaps */
+
+#include "lldpd.h"
+
+/*
+ * Set vlan id in the bitmap
+ */
+void
+bitmap_set(uint32_t *bmap, uint16_t vlan_id)
+{
+ if (vlan_id < MAX_VLAN) bmap[vlan_id / 32] |= (((uint32_t)1) << (vlan_id % 32));
+}
+
+/*
+ * Checks if the bitmap is empty
+ */
+int
+bitmap_isempty(uint32_t *bmap)
+{
+ int i;
+
+ for (i = 0; i < VLAN_BITMAP_LEN; i++) {
+ if (bmap[i] != 0) return 0;
+ }
+
+ return 1;
+}
+
+/*
+ * Calculate the number of bits set in the bitmap to get total
+ * number of VLANs
+ */
+unsigned int
+bitmap_numbits(uint32_t *bmap)
+{
+ unsigned int num = 0;
+
+ for (int i = 0; (i < VLAN_BITMAP_LEN); i++) {
+ uint32_t v = bmap[i];
+ v = v - ((v >> 1) & 0x55555555);
+ v = (v & 0x33333333) + ((v >> 2) & 0x33333333);
+ num += (((v + (v >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24;
+ }
+
+ return num;
+}
diff --git a/src/daemon/client.c b/src/daemon/client.c
new file mode 100644
index 0000000..d9d907f
--- /dev/null
+++ b/src/daemon/client.c
@@ -0,0 +1,700 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/*
+ * Copyright (c) 2008 Vincent Bernat <bernat@luffy.cx>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "lldpd.h"
+#include "trace.h"
+
+#include <sys/utsname.h>
+
+static ssize_t
+client_handle_none(struct lldpd *cfg, enum hmsg_type *type, void *input, int input_len,
+ void **output, int *subscribed)
+{
+ log_info("rpc", "received noop request from client");
+ *type = NONE;
+ return 0;
+}
+
+/* Return the global configuration */
+static ssize_t
+client_handle_get_configuration(struct lldpd *cfg, enum hmsg_type *type, void *input,
+ int input_len, void **output, int *subscribed)
+{
+ ssize_t output_len;
+ log_debug("rpc", "client requested configuration");
+ output_len = lldpd_config_serialize(&cfg->g_config, output);
+ if (output_len <= 0) {
+ output_len = 0;
+ *type = NONE;
+ }
+ return output_len;
+}
+
+static char *
+xstrdup(const char *str)
+{
+ if (!str) return NULL;
+ return strdup(str);
+}
+
+/* Change the global configuration */
+static ssize_t
+client_handle_set_configuration(struct lldpd *cfg, enum hmsg_type *type, void *input,
+ int input_len, void **output, int *subscribed)
+{
+ struct lldpd_config *config;
+
+ log_debug("rpc", "client request a change in configuration");
+ /* Get the proposed configuration. */
+ if (lldpd_config_unserialize(input, input_len, &config) <= 0) {
+ *type = NONE;
+ return 0;
+ }
+
+#define CHANGED(w) (config->w != cfg->g_config.w)
+#define CHANGED_STR(w) \
+ (!(config->w == cfg->g_config.w || \
+ (config->w && cfg->g_config.w && !strcmp(config->w, cfg->g_config.w))))
+
+ /* What needs to be done? Transmit delay? */
+ if (CHANGED(c_tx_interval) && config->c_tx_interval != 0) {
+ if (config->c_tx_interval < 0) {
+ log_debug("rpc", "client asked for immediate retransmission");
+ } else {
+ log_debug("rpc", "client change transmit interval to %d ms",
+ config->c_tx_interval);
+ cfg->g_config.c_tx_interval = config->c_tx_interval;
+ cfg->g_config.c_ttl =
+ cfg->g_config.c_tx_interval * cfg->g_config.c_tx_hold;
+ cfg->g_config.c_ttl = (cfg->g_config.c_ttl + 999) / 1000;
+ }
+ levent_send_now(cfg);
+ }
+ if (CHANGED(c_tx_hold) && config->c_tx_hold > 0) {
+ log_debug("rpc", "client change transmit hold to %d",
+ config->c_tx_hold);
+ cfg->g_config.c_tx_hold = config->c_tx_hold;
+ cfg->g_config.c_ttl =
+ cfg->g_config.c_tx_interval * cfg->g_config.c_tx_hold;
+ cfg->g_config.c_ttl = (cfg->g_config.c_ttl + 999) / 1000;
+ }
+ if (CHANGED(c_max_neighbors) && config->c_max_neighbors > 0) {
+ log_debug("rpc", "client change maximum neighbors to %d",
+ config->c_max_neighbors);
+ cfg->g_config.c_max_neighbors = config->c_max_neighbors;
+ }
+ if (CHANGED(c_lldp_portid_type) &&
+ config->c_lldp_portid_type > LLDP_PORTID_SUBTYPE_UNKNOWN &&
+ config->c_lldp_portid_type <= LLDP_PORTID_SUBTYPE_MAX) {
+ log_debug("rpc", "change lldp portid tlv subtype to %d",
+ config->c_lldp_portid_type);
+ cfg->g_config.c_lldp_portid_type = config->c_lldp_portid_type;
+ levent_update_now(cfg);
+ }
+ if (CHANGED(c_lldp_agent_type) &&
+ config->c_lldp_agent_type > LLDP_AGENT_TYPE_UNKNOWN &&
+ config->c_lldp_agent_type <= LLDP_AGENT_TYPE_MAX) {
+ log_debug("rpc", "change lldp agent type to %d",
+ config->c_lldp_agent_type);
+ cfg->g_config.c_lldp_agent_type = config->c_lldp_agent_type;
+ levent_update_now(cfg);
+ }
+ /* Pause/resume */
+ if (CHANGED(c_paused)) {
+ log_debug("rpc", "client asked to %s lldpd",
+ config->c_paused ? "pause" : "resume");
+ cfg->g_config.c_paused = config->c_paused;
+ levent_send_now(cfg);
+ }
+
+#ifdef ENABLE_LLDPMED
+ if (CHANGED(c_enable_fast_start)) {
+ cfg->g_config.c_enable_fast_start = config->c_enable_fast_start;
+ log_debug("rpc", "client asked to %s fast start",
+ cfg->g_config.c_enable_fast_start ? "enable" : "disable");
+ }
+ if (CHANGED(c_tx_fast_interval) && config->c_tx_fast_interval > 0) {
+ log_debug("rpc", "change fast interval to %d",
+ config->c_tx_fast_interval);
+ cfg->g_config.c_tx_fast_interval = config->c_tx_fast_interval;
+ }
+#endif
+ if (CHANGED_STR(c_iface_pattern)) {
+ log_debug("rpc", "change interface pattern to %s",
+ config->c_iface_pattern ? config->c_iface_pattern : "(NULL)");
+ free(cfg->g_config.c_iface_pattern);
+ cfg->g_config.c_iface_pattern = xstrdup(config->c_iface_pattern);
+ levent_update_now(cfg);
+ }
+ if (CHANGED_STR(c_perm_ifaces)) {
+ log_debug("rpc", "change permanent interface pattern to %s",
+ config->c_perm_ifaces ? config->c_perm_ifaces : "(NULL)");
+ free(cfg->g_config.c_perm_ifaces);
+ cfg->g_config.c_perm_ifaces = xstrdup(config->c_perm_ifaces);
+ levent_update_now(cfg);
+ }
+ if (CHANGED_STR(c_mgmt_pattern)) {
+ log_debug("rpc", "change management pattern to %s",
+ config->c_mgmt_pattern ? config->c_mgmt_pattern : "(NULL)");
+ free(cfg->g_config.c_mgmt_pattern);
+ cfg->g_config.c_mgmt_pattern = xstrdup(config->c_mgmt_pattern);
+ levent_update_now(cfg);
+ }
+ if (CHANGED_STR(c_cid_string)) {
+ log_debug("rpc", "change chassis ID string to %s",
+ config->c_cid_string ? config->c_cid_string : "(NULL)");
+ free(cfg->g_config.c_cid_string);
+ cfg->g_config.c_cid_string = xstrdup(config->c_cid_string);
+ free(LOCAL_CHASSIS(cfg)->c_id);
+ LOCAL_CHASSIS(cfg)->c_id = NULL;
+ lldpd_update_localchassis(cfg);
+ levent_update_now(cfg);
+ }
+ if (CHANGED_STR(c_description)) {
+ log_debug("rpc", "change chassis description to %s",
+ config->c_description ? config->c_description : "(NULL)");
+ free(cfg->g_config.c_description);
+ cfg->g_config.c_description = xstrdup(config->c_description);
+ lldpd_update_localchassis(cfg);
+ levent_update_now(cfg);
+ }
+ if (CHANGED_STR(c_platform)) {
+ log_debug("rpc", "change platform description to %s",
+ config->c_platform ? config->c_platform : "(NULL)");
+ free(cfg->g_config.c_platform);
+ cfg->g_config.c_platform = xstrdup(config->c_platform);
+ lldpd_update_localchassis(cfg);
+ levent_update_now(cfg);
+ }
+ if (CHANGED_STR(c_hostname)) {
+ log_debug("rpc", "change system name to %s",
+ config->c_hostname ? config->c_hostname : "(NULL)");
+ free(cfg->g_config.c_hostname);
+ cfg->g_config.c_hostname = xstrdup(config->c_hostname);
+ lldpd_update_localchassis(cfg);
+ levent_update_now(cfg);
+ }
+ if (CHANGED(c_set_ifdescr)) {
+ log_debug("rpc",
+ "%s setting of interface description based on discovered neighbors",
+ config->c_set_ifdescr ? "enable" : "disable");
+ cfg->g_config.c_set_ifdescr = config->c_set_ifdescr;
+ levent_update_now(cfg);
+ }
+ if (CHANGED(c_promisc)) {
+ log_debug("rpc", "%s promiscuous mode on managed interfaces",
+ config->c_promisc ? "enable" : "disable");
+ cfg->g_config.c_promisc = config->c_promisc;
+ levent_update_now(cfg);
+ }
+ if (CHANGED(c_cap_advertise)) {
+ log_debug("rpc", "%s chassis capabilities advertisement",
+ config->c_cap_advertise ? "enable" : "disable");
+ cfg->g_config.c_cap_advertise = config->c_cap_advertise;
+ levent_update_now(cfg);
+ }
+ if (CHANGED(c_cap_override)) {
+ log_debug("rpc", "%s chassis capabilities override",
+ config->c_cap_override ? "enable" : "disable");
+ cfg->g_config.c_cap_override = config->c_cap_override;
+ levent_update_now(cfg);
+ }
+ if (CHANGED(c_mgmt_advertise)) {
+ log_debug("rpc", "%s management addresses advertisement",
+ config->c_mgmt_advertise ? "enable" : "disable");
+ cfg->g_config.c_mgmt_advertise = config->c_mgmt_advertise;
+ levent_update_now(cfg);
+ }
+ if (CHANGED(c_bond_slave_src_mac_type)) {
+ if (config->c_bond_slave_src_mac_type >
+ LLDP_BOND_SLAVE_SRC_MAC_TYPE_UNKNOWN &&
+ config->c_bond_slave_src_mac_type <=
+ LLDP_BOND_SLAVE_SRC_MAC_TYPE_MAX) {
+ log_debug("rpc", "change bond src mac type to %d",
+ config->c_bond_slave_src_mac_type);
+ cfg->g_config.c_bond_slave_src_mac_type =
+ config->c_bond_slave_src_mac_type;
+ } else {
+ log_info("rpc", "Invalid bond slave src mac type: %d\n",
+ config->c_bond_slave_src_mac_type);
+ }
+ }
+
+ lldpd_config_cleanup(config);
+ free(config);
+
+ return 0;
+}
+
+/* Return the list of interfaces.
+ Input: nothing.
+ Output: list of interface names (lldpd_interface_list)
+*/
+static ssize_t
+client_handle_get_interfaces(struct lldpd *cfg, enum hmsg_type *type, void *input,
+ int input_len, void **output, int *subscribed)
+{
+ struct lldpd_interface *iff, *iff_next;
+ struct lldpd_hardware *hardware;
+ ssize_t output_len;
+
+ /* Build the list of interfaces */
+ struct lldpd_interface_list ifs;
+
+ log_debug("rpc", "client request the list of interfaces");
+ TAILQ_INIT(&ifs);
+ TAILQ_FOREACH (hardware, &cfg->g_hardware, h_entries) {
+ if ((iff = (struct lldpd_interface *)malloc(
+ sizeof(struct lldpd_interface))) == NULL)
+ fatal("rpc", NULL);
+ iff->name = hardware->h_ifname;
+ TAILQ_INSERT_TAIL(&ifs, iff, next);
+ }
+
+ output_len = lldpd_interface_list_serialize(&ifs, output);
+ if (output_len <= 0) {
+ output_len = 0;
+ *type = NONE;
+ }
+
+ /* 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);
+ }
+
+ return output_len;
+}
+
+/**
+ * Set local chassis info
+ * Input: chassis object
+ * Output: updated chassis object
+ */
+static ssize_t
+client_handle_set_local_chassis(struct lldpd *cfg, enum hmsg_type *type, void *input,
+ int input_len, void **output, int *subscribed)
+{
+ struct lldpd_chassis *chassis = NULL;
+ struct lldpd_chassis *local_chassis = NULL;
+#ifdef ENABLE_LLDPMED
+ struct utsname un;
+#endif
+
+ log_debug("rpc", "client request a change in chassis configuration");
+ if (lldpd_chassis_unserialize(input, input_len, &chassis) <= 0) {
+ *type = NONE;
+ return 0;
+ }
+
+ local_chassis = LOCAL_CHASSIS(cfg);
+
+#ifdef ENABLE_LLDPMED
+ free(local_chassis->c_med_hw);
+ local_chassis->c_med_hw =
+ (!chassis->c_med_hw) ? dmi_hw() : strdup(chassis->c_med_hw);
+
+ // Follows lldpd.c - only set sw if advertising is enabled
+ if (cfg->g_config.c_advertise_version) {
+ free(local_chassis->c_med_sw);
+
+ if (!chassis->c_med_sw) {
+ if (uname(&un) < 0) {
+ log_warn("rpc",
+ "Could not get default uname. Will continue anyway.");
+ local_chassis->c_med_sw = NULL;
+ } else {
+ local_chassis->c_med_sw = strdup(un.release);
+ }
+ } else {
+ local_chassis->c_med_sw = strdup(chassis->c_med_sw);
+ }
+ }
+
+ free(local_chassis->c_med_fw);
+ local_chassis->c_med_fw =
+ (!chassis->c_med_fw) ? dmi_fw() : strdup(chassis->c_med_fw);
+
+ free(local_chassis->c_med_sn);
+ local_chassis->c_med_sn =
+ (!chassis->c_med_sn) ? dmi_sn() : strdup(chassis->c_med_sn);
+
+ free(local_chassis->c_med_manuf);
+ local_chassis->c_med_manuf =
+ (!chassis->c_med_manuf) ? dmi_manuf() : strdup(chassis->c_med_manuf);
+
+ free(local_chassis->c_med_model);
+ local_chassis->c_med_model =
+ (!chassis->c_med_model) ? dmi_model() : strdup(chassis->c_med_model);
+
+ free(local_chassis->c_med_asset);
+ local_chassis->c_med_asset =
+ (!chassis->c_med_asset) ? dmi_asset() : strdup(chassis->c_med_asset);
+#endif
+
+ if (chassis->c_cap_enabled != local_chassis->c_cap_enabled) {
+ local_chassis->c_cap_enabled = chassis->c_cap_enabled;
+ log_debug("rpc", "change capabilities enabled to: %d",
+ local_chassis->c_cap_enabled);
+ }
+
+#ifdef ENABLE_LLDPMED
+ log_debug("rpc", "change hardware-revision to: %s", local_chassis->c_med_hw);
+ log_debug("rpc", "change software-revision to: %s", local_chassis->c_med_sw);
+ log_debug("rpc", "change firmware-revision to: %s", local_chassis->c_med_fw);
+ log_debug("rpc", "change serial-number to: %s", local_chassis->c_med_sn);
+ log_debug("rpc", "change manufacturer to: %s", local_chassis->c_med_manuf);
+ log_debug("rpc", "change model to: %s", local_chassis->c_med_model);
+ log_debug("rpc", "change asset to: %s", local_chassis->c_med_asset);
+#endif
+
+ lldpd_chassis_cleanup(chassis, 1);
+
+ ssize_t output_len = lldpd_chassis_serialize(local_chassis, output);
+ if (output_len <= 0) {
+ *type = NONE;
+ return 0;
+ }
+
+ return output_len;
+}
+
+/* Return the local chassis.
+ Input: nothing.
+ Output: local chassis (lldpd_chassis)
+*/
+static ssize_t
+client_handle_get_local_chassis(struct lldpd *cfg, enum hmsg_type *type, void *input,
+ int input_len, void **output, int *subscribed)
+{
+ struct lldpd_chassis *chassis = LOCAL_CHASSIS(cfg);
+ ssize_t output_len;
+
+ log_debug("rpc", "client request the local chassis");
+ output_len = lldpd_chassis_serialize(chassis, output);
+ if (output_len <= 0) {
+ output_len = 0;
+ *type = NONE;
+ }
+
+ return output_len;
+}
+
+/* Return all available information related to an interface
+ Input: name of the interface (serialized)
+ Output: Information about the interface (lldpd_hardware)
+*/
+static ssize_t
+client_handle_get_interface(struct lldpd *cfg, enum hmsg_type *type, void *input,
+ int input_len, void **output, int *subscribed)
+{
+ char *name;
+ struct lldpd_hardware *hardware;
+ void *p;
+
+ /* Get name of the interface */
+ if (marshal_unserialize(string, input, input_len, &p) <= 0) {
+ *type = NONE;
+ return 0;
+ }
+ name = p;
+
+ /* Search appropriate hardware */
+ log_debug("rpc", "client request interface %s", name);
+ TAILQ_FOREACH (hardware, &cfg->g_hardware, h_entries)
+ if (!strcmp(hardware->h_ifname, name)) {
+ ssize_t output_len = lldpd_hardware_serialize(hardware, output);
+ free(name);
+ if (output_len <= 0) {
+ *type = NONE;
+ return 0;
+ }
+ return output_len;
+ }
+
+ log_warnx("rpc", "no interface %s found", name);
+ free(name);
+ *type = NONE;
+ return 0;
+}
+
+/* Return all available information related to an interface
+ Input: name of the interface (serialized)
+ Output: Information about the interface (lldpd_hardware)
+*/
+static ssize_t
+client_handle_get_default_port(struct lldpd *cfg, enum hmsg_type *type, void *input,
+ int input_len, void **output, int *subscribed)
+{
+ log_debug("rpc", "client request the default local port");
+ ssize_t output_len = lldpd_port_serialize(cfg->g_default_local_port, output);
+ if (output_len <= 0) {
+ *type = NONE;
+ return 0;
+ }
+ return output_len;
+}
+
+static int
+_client_handle_set_port(struct lldpd *cfg, struct lldpd_port *port,
+ struct lldpd_port_set *set)
+{
+#ifdef ENABLE_LLDPMED
+ struct lldpd_med_loc *loc = NULL;
+#endif
+ if (set->local_id) {
+ log_debug("rpc", "requested change to Port ID");
+ free(port->p_id);
+ port->p_id = strdup(set->local_id);
+ port->p_id_len = strlen(set->local_id);
+ port->p_id_subtype = LLDP_PORTID_SUBTYPE_LOCAL;
+ port->p_descr_force = 0;
+ }
+ if (set->local_descr) {
+ log_debug("rpc", "requested change to Port Description");
+ free(port->p_descr);
+ port->p_descr = strdup(set->local_descr);
+ port->p_descr_force = 1;
+ }
+ switch (set->rxtx) {
+ case LLDPD_RXTX_TXONLY:
+ log_debug("rpc", "requested TX only mode");
+ port->p_disable_rx = 1;
+ port->p_disable_tx = 0;
+ break;
+ case LLDPD_RXTX_RXONLY:
+ log_debug("rpc", "requested RX only mode");
+ port->p_disable_rx = 0;
+ port->p_disable_tx = 1;
+ break;
+ case LLDPD_RXTX_BOTH:
+ log_debug("rpc", "requested RX/TX mode");
+ port->p_disable_rx = port->p_disable_tx = 0;
+ break;
+ case LLDPD_RXTX_DISABLED:
+ log_debug("rpc", "requested disabled mode");
+ port->p_disable_rx = port->p_disable_tx = 1;
+ break;
+ }
+ if (set->vlan_tx_enabled > -1) {
+ port->p_vlan_tx_enabled = set->vlan_tx_enabled;
+ port->p_vlan_tx_tag = set->vlan_tx_tag;
+ }
+#ifdef ENABLE_LLDPMED
+ if (set->med_policy && set->med_policy->type > 0) {
+ log_debug("rpc", "requested change to MED policy");
+ if (set->med_policy->type > LLDP_MED_APPTYPE_LAST) {
+ log_warnx("rpc", "invalid policy provided: %d",
+ set->med_policy->type);
+ return -1;
+ }
+ memcpy(&port->p_med_policy[set->med_policy->type - 1], set->med_policy,
+ sizeof(struct lldpd_med_policy));
+ port->p_med_cap_enabled |= LLDP_MED_CAP_POLICY;
+ }
+ if (set->med_location && set->med_location->format > 0) {
+ char *newdata = NULL;
+ log_debug("rpc", "requested change to MED location");
+ if (set->med_location->format > LLDP_MED_LOCFORMAT_LAST) {
+ log_warnx("rpc", "invalid location format provided: %d",
+ set->med_location->format);
+ return -1;
+ }
+ loc = &port->p_med_location[set->med_location->format - 1];
+ free(loc->data);
+ memcpy(loc, set->med_location, sizeof(struct lldpd_med_loc));
+ if (!loc->data || !(newdata = malloc(loc->data_len))) loc->data_len = 0;
+ if (newdata) memcpy(newdata, loc->data, loc->data_len);
+ loc->data = newdata;
+ port->p_med_cap_enabled |= LLDP_MED_CAP_LOCATION;
+ }
+ if (set->med_power) {
+ log_debug("rpc", "requested change to MED power");
+ memcpy(&port->p_med_power, set->med_power,
+ sizeof(struct lldpd_med_power));
+ switch (set->med_power->devicetype) {
+ case LLDP_MED_POW_TYPE_PD:
+ port->p_med_cap_enabled |= LLDP_MED_CAP_MDI_PD;
+ port->p_med_cap_enabled &= ~LLDP_MED_CAP_MDI_PSE;
+ break;
+ case LLDP_MED_POW_TYPE_PSE:
+ port->p_med_cap_enabled |= LLDP_MED_CAP_MDI_PSE;
+ port->p_med_cap_enabled &= ~LLDP_MED_CAP_MDI_PD;
+ break;
+ }
+ }
+#endif
+#ifdef ENABLE_DOT3
+ if (set->dot3_power) {
+ log_debug("rpc", "requested change to Dot3 power");
+ memcpy(&port->p_power, set->dot3_power,
+ sizeof(struct lldpd_dot3_power));
+ }
+#endif
+#ifdef ENABLE_CUSTOM
+ if (set->custom_list_clear) {
+ log_debug("rpc", "requested custom TLVs clear");
+ lldpd_custom_list_cleanup(port);
+ } else {
+ if (set->custom) {
+ log_info("rpc",
+ "custom TLV op %s oui %02x:%02x:%02x subtype %x",
+ (set->custom_tlv_op == CUSTOM_TLV_REMOVE) ? "remove" :
+ (set->custom_tlv_op == CUSTOM_TLV_ADD) ? "add" :
+ "replace",
+ set->custom->oui[0], set->custom->oui[1],
+ set->custom->oui[2], set->custom->subtype);
+ switch (set->custom_tlv_op) {
+ case CUSTOM_TLV_REMOVE:
+ lldpd_custom_tlv_cleanup(port, set->custom);
+ break;
+ case CUSTOM_TLV_ADD:
+ lldpd_custom_tlv_add(port, set->custom);
+ break;
+ case CUSTOM_TLV_REPLACE:
+ default:
+ lldpd_custom_tlv_cleanup(port, set->custom);
+ lldpd_custom_tlv_add(port, set->custom);
+ break;
+ }
+ }
+ }
+#endif
+ return 0;
+}
+
+/* Set some port related settings (policy, location, power)
+ Input: name of the interface, policy/location/power setting to be modified
+ Output: nothing
+*/
+static ssize_t
+client_handle_set_port(struct lldpd *cfg, enum hmsg_type *type, void *input,
+ int input_len, void **output, int *subscribed)
+{
+ int ret = 0;
+ struct lldpd_port_set *set = NULL;
+ struct lldpd_hardware *hardware = NULL;
+
+ if (lldpd_port_set_unserialize(input, input_len, &set) <= 0) {
+ *type = NONE;
+ return 0;
+ }
+ if (!set->ifname) {
+ log_warnx("rpc", "no interface provided");
+ goto set_port_finished;
+ }
+
+ /* Search the appropriate hardware */
+ if (strlen(set->ifname) == 0) {
+ log_debug("rpc", "client request change to default port");
+ if (_client_handle_set_port(cfg, cfg->g_default_local_port, set) == -1)
+ goto set_port_finished;
+ ret = 1;
+ } else {
+ log_debug("rpc", "client request change to port %s", set->ifname);
+ TAILQ_FOREACH (hardware, &cfg->g_hardware, h_entries) {
+ if (!strcmp(hardware->h_ifname, set->ifname)) {
+ struct lldpd_port *port = &hardware->h_lport;
+ if (_client_handle_set_port(cfg, port, set) == -1)
+ goto set_port_finished;
+ ret = 1;
+ break;
+ }
+ }
+ }
+
+ if (ret == 0)
+ log_warn("rpc", "no interface %s found", set->ifname);
+ else
+ levent_update_now(cfg);
+
+set_port_finished:
+ if (!ret) *type = NONE;
+ free(set->ifname);
+ free(set->local_id);
+ free(set->local_descr);
+#ifdef ENABLE_LLDPMED
+ free(set->med_policy);
+ if (set->med_location) free(set->med_location->data);
+ free(set->med_location);
+ free(set->med_power);
+#endif
+#ifdef ENABLE_DOT3
+ free(set->dot3_power);
+#endif
+#ifdef ENABLE_CUSTOM
+ if (set->custom) {
+ free(set->custom->oui_info);
+ free(set->custom);
+ }
+#endif
+ free(set);
+ return 0;
+}
+
+/* Register subscribtion to neighbor changes */
+static ssize_t
+client_handle_subscribe(struct lldpd *cfg, enum hmsg_type *type, void *input,
+ int input_len, void **output, int *subscribed)
+{
+ log_debug("rpc", "client subscribe to changes");
+ *subscribed = 1;
+ return 0;
+}
+
+struct client_handle {
+ enum hmsg_type type;
+ const char *name;
+ ssize_t (
+ *handle)(struct lldpd *, enum hmsg_type *, void *, int, void **, int *);
+};
+
+static struct client_handle client_handles[] = { { NONE, "None", client_handle_none },
+ { GET_CONFIG, "Get configuration", client_handle_get_configuration },
+ { SET_CONFIG, "Set configuration", client_handle_set_configuration },
+ { GET_INTERFACES, "Get interfaces", client_handle_get_interfaces },
+ { GET_INTERFACE, "Get interface", client_handle_get_interface },
+ { GET_DEFAULT_PORT, "Get default port", client_handle_get_default_port },
+ { SET_CHASSIS, "Set local chassis", client_handle_set_local_chassis },
+ { GET_CHASSIS, "Get local chassis", client_handle_get_local_chassis },
+ { SET_PORT, "Set port", client_handle_set_port },
+ { SUBSCRIBE, "Subscribe", client_handle_subscribe }, { 0, NULL } };
+
+int
+client_handle_client(struct lldpd *cfg, ssize_t (*send)(void *, int, void *, size_t),
+ void *out, enum hmsg_type type, void *buffer, size_t n, int *subscribed)
+{
+ struct client_handle *ch;
+ void *answer;
+ ssize_t len, sent;
+
+ log_debug("rpc", "handle client request");
+ for (ch = client_handles; ch->handle != NULL; ch++) {
+ if (ch->type == type) {
+ TRACE(LLDPD_CLIENT_REQUEST(ch->name));
+ answer = NULL;
+ len = ch->handle(cfg, &type, buffer, n, &answer, subscribed);
+ sent = send(out, type, answer, len);
+ free(answer);
+ return sent;
+ }
+ }
+
+ log_warnx("rpc", "unknown message request (%d) received", type);
+ return -1;
+}
diff --git a/src/daemon/dmi-dummy.c b/src/daemon/dmi-dummy.c
new file mode 100644
index 0000000..2954d50
--- /dev/null
+++ b/src/daemon/dmi-dummy.c
@@ -0,0 +1,57 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/*
+ * Copyright (c) 2012 Vincent Bernat <bernat@luffy.cx>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "lldpd.h"
+
+#ifdef ENABLE_LLDPMED
+
+char *
+dmi_hw()
+{
+ return NULL;
+}
+
+char *
+dmi_fw()
+{
+ return NULL;
+}
+
+char *
+dmi_sn()
+{
+ return NULL;
+}
+
+char *
+dmi_manuf()
+{
+ return NULL;
+}
+
+char *
+dmi_model()
+{
+ return NULL;
+}
+
+char *
+dmi_asset()
+{
+ return NULL;
+}
+#endif
diff --git a/src/daemon/dmi-freebsd.c b/src/daemon/dmi-freebsd.c
new file mode 100644
index 0000000..f20b71b
--- /dev/null
+++ b/src/daemon/dmi-freebsd.c
@@ -0,0 +1,82 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/*
+ * Copyright (c) 2012 Vincent Bernat <bernat@luffy.cx>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "lldpd.h"
+#include <unistd.h>
+#include <kenv.h>
+
+#ifdef ENABLE_LLDPMED
+/* Fill in inventory stuff:
+ - hardware version: smbios.system.version
+ - firmware version: smbios.bios.version
+ - software version: `uname -r`
+ - serial number: smbios.system.serial
+ - manufacturer: smbios.system.maker
+ - model: smbios.system.product
+ - asset: smbios.chassis.tag
+*/
+
+static char *
+dmi_get(char *file)
+{
+ char buffer[100] = {};
+
+ log_debug("localchassis", "DMI request for %s", file);
+ if (kenv(KENV_GET, file, buffer, sizeof(buffer) - 1) == -1) {
+ log_debug("localchassis", "cannot get %s", file);
+ return NULL;
+ }
+ if (strlen(buffer)) return strdup(buffer);
+ return NULL;
+}
+
+char *
+dmi_hw()
+{
+ return dmi_get("smbios.system.version");
+}
+
+char *
+dmi_fw()
+{
+ return dmi_get("smbios.bios.version");
+}
+
+char *
+dmi_sn()
+{
+ return dmi_get("smbios.system.serial");
+}
+
+char *
+dmi_manuf()
+{
+ return dmi_get("smbios.system.maker");
+}
+
+char *
+dmi_model()
+{
+ return dmi_get("smibios.system.product");
+}
+
+char *
+dmi_asset()
+{
+ return dmi_get("smibios.chassis.tag");
+}
+#endif
diff --git a/src/daemon/dmi-linux.c b/src/daemon/dmi-linux.c
new file mode 100644
index 0000000..47a6d1b
--- /dev/null
+++ b/src/daemon/dmi-linux.c
@@ -0,0 +1,90 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/*
+ * Copyright (c) 2009 Vincent Bernat <bernat@luffy.cx>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "lldpd.h"
+#include <unistd.h>
+
+#ifdef ENABLE_LLDPMED
+/* Fill in inventory stuff:
+ - hardware version: /sys/class/dmi/id/product_version
+ - firmware version: /sys/class/dmi/id/bios_version
+ - software version: `uname -r`
+ - serial number: /sys/class/dmi/id/product_serial
+ - manufacturer: /sys/class/dmi/id/sys_vendor
+ - model: /sys/class/dmi/id/product_name
+ - asset: /sys/class/dmi/id/chassis_asset_tag
+*/
+
+static char *
+dmi_get(const char *file)
+{
+ int dmi, s;
+ char buffer[100] = {};
+
+ log_debug("localchassis", "DMI request for file %s", file);
+ if ((dmi = priv_open(file)) < 0) {
+ log_debug("localchassis", "cannot get DMI file %s", file);
+ return NULL;
+ }
+ if ((s = read(dmi, buffer, sizeof(buffer))) == -1) {
+ log_debug("localchassis", "cannot read DMI file %s", file);
+ close(dmi);
+ return NULL;
+ }
+ close(dmi);
+ buffer[sizeof(buffer) - 1] = '\0';
+ if ((s > 0) && (buffer[s - 1] == '\n')) buffer[s - 1] = '\0';
+ if (strlen(buffer)) return strdup(buffer);
+ return NULL;
+}
+
+char *
+dmi_hw()
+{
+ return dmi_get(SYSFS_CLASS_DMI "product_version");
+}
+
+char *
+dmi_fw()
+{
+ return dmi_get(SYSFS_CLASS_DMI "bios_version");
+}
+
+char *
+dmi_sn()
+{
+ return dmi_get(SYSFS_CLASS_DMI "product_serial");
+}
+
+char *
+dmi_manuf()
+{
+ return dmi_get(SYSFS_CLASS_DMI "sys_vendor");
+}
+
+char *
+dmi_model()
+{
+ return dmi_get(SYSFS_CLASS_DMI "product_name");
+}
+
+char *
+dmi_asset()
+{
+ return dmi_get(SYSFS_CLASS_DMI "chassis_asset_tag");
+}
+#endif
diff --git a/src/daemon/dmi-openbsd.c b/src/daemon/dmi-openbsd.c
new file mode 100644
index 0000000..a226829
--- /dev/null
+++ b/src/daemon/dmi-openbsd.c
@@ -0,0 +1,73 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/*
+ * Copyright (c) 2012 Vincent Bernat <bernat@luffy.cx>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "lldpd.h"
+#include <sys/param.h>
+#include <sys/sysctl.h>
+
+#ifdef ENABLE_LLDPMED
+
+static char *
+dmi_get(int what, const char *descr)
+{
+ char result[100] = {};
+ size_t len = sizeof(result) - 1;
+ int mib[2] = { CTL_HW, what };
+ if (sysctl(mib, 2, result, &len, NULL, 0) == -1) {
+ log_debug("localchassis", "cannot get %s", descr);
+ return NULL;
+ }
+ log_debug("localchassis", "got `%s` for %s", result, descr);
+ return strdup(result);
+}
+
+char *
+dmi_hw()
+{
+ return dmi_get(HW_VERSION, "hardware revision");
+}
+
+char *
+dmi_fw()
+{
+ return NULL;
+}
+
+char *
+dmi_sn()
+{
+ return dmi_get(HW_SERIALNO, "serial number");
+}
+
+char *
+dmi_manuf()
+{
+ return dmi_get(HW_VENDOR, "hardware vendor");
+}
+
+char *
+dmi_model()
+{
+ return dmi_get(HW_PRODUCT, "hardware product");
+}
+
+char *
+dmi_asset()
+{
+ return dmi_get(HW_UUID, "hardware UUID");
+}
+#endif
diff --git a/src/daemon/dmi-osx.c b/src/daemon/dmi-osx.c
new file mode 100644
index 0000000..cde8676
--- /dev/null
+++ b/src/daemon/dmi-osx.c
@@ -0,0 +1,109 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/*
+ * Copyright (c) 2012 Vincent Bernat <bernat@luffy.cx>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "lldpd.h"
+
+#include <CoreFoundation/CoreFoundation.h>
+#include <IOKit/IOKitLib.h>
+
+#ifdef ENABLE_LLDPMED
+static char *
+dmi_get(const char *classname, CFStringRef property)
+{
+ char *result = NULL;
+ CFMutableDictionaryRef matching = NULL;
+ CFTypeRef cfres = NULL;
+ io_service_t service = 0;
+ matching = IOServiceMatching(classname);
+ if (!matching) {
+ log_debug("localchassis", "cannot get %s class from registry",
+ classname);
+ goto end;
+ }
+ service = IOServiceGetMatchingService(kIOMasterPortDefault, matching);
+ if (!service) {
+ log_warnx("localchassis", "cannot get matching %s class from registry",
+ classname);
+ goto end;
+ }
+ cfres = IORegistryEntryCreateCFProperty(service, property, kCFAllocatorDefault,
+ kNilOptions);
+ if (!cfres) {
+ log_debug("localchassis",
+ "cannot find property %s in class %s in registry",
+ CFStringGetCStringPtr(property, kCFStringEncodingMacRoman),
+ classname);
+ goto end;
+ }
+
+ if (CFGetTypeID(cfres) == CFStringGetTypeID())
+ result = strdup(CFStringGetCStringPtr((CFStringRef)cfres,
+ kCFStringEncodingMacRoman));
+ else if (CFGetTypeID(cfres) == CFDataGetTypeID()) {
+ /* OK, we know this is a string. */
+ result = calloc(1, CFDataGetLength((CFDataRef)cfres) + 1);
+ if (!result) goto end;
+ memcpy(result, CFDataGetBytePtr((CFDataRef)cfres),
+ CFDataGetLength((CFDataRef)cfres));
+ } else
+ log_debug("localchassis", "unknown type for property %s in class %s",
+ CFStringGetCStringPtr(property, kCFStringEncodingMacRoman),
+ classname);
+
+end:
+ if (cfres) CFRelease(cfres);
+ if (service) IOObjectRelease(service);
+ return result;
+}
+
+char *
+dmi_hw()
+{
+ return dmi_get("IOPlatformExpertDevice", CFSTR("version"));
+}
+
+char *
+dmi_fw()
+{
+ /* Dunno where it is. Maybe in SMC? */
+ return NULL;
+}
+
+char *
+dmi_sn()
+{
+ return dmi_get("IOPlatformExpertDevice", CFSTR("IOPlatformSerialNumber"));
+}
+
+char *
+dmi_manuf()
+{
+ return dmi_get("IOPlatformExpertDevice", CFSTR("manufacturer"));
+}
+
+char *
+dmi_model()
+{
+ return dmi_get("IOPlatformExpertDevice", CFSTR("model"));
+}
+
+char *
+dmi_asset()
+{
+ return dmi_get("IOPlatformExpertDevice", CFSTR("board-id"));
+}
+#endif
diff --git a/src/daemon/dtrace2systemtap.awk b/src/daemon/dtrace2systemtap.awk
new file mode 100644
index 0000000..3761008
--- /dev/null
+++ b/src/daemon/dtrace2systemtap.awk
@@ -0,0 +1,25 @@
+#!/usr/bin/awk -f
+
+# Convert a simple dtrace probe files into a tapset. Heavily inspired
+# by dtrace2systemtap.pl from libvirt
+
+($1 == "provider") {
+ provider = $2
+}
+
+($1 == "probe") {
+ name = substr($2, 0, index($2, "(") - 1)
+ split(substr($0, index($0, "(") + 1, index($0, ")") - index($0, "(") - 1),
+ args, /, /)
+ printf "probe %s.%s = process(\"%s/%s\").provider(\"%s\").mark(\"%s\") {\n", provider, name, sbindir, provider, provider, name
+ for (arg in args) {
+ match(args[arg], /^(.+[^a-z_])([a-z_]+)$/, aarg)
+ type = aarg[1]
+ argname = aarg[2]
+ if (type == "char *")
+ printf " %s = user_string($arg%d);\n", argname, arg
+ else
+ printf " %s = $arg%d;\n", argname, arg
+ }
+ printf "}\n\n"
+}
diff --git a/src/daemon/event.c b/src/daemon/event.c
new file mode 100644
index 0000000..971500f
--- /dev/null
+++ b/src/daemon/event.c
@@ -0,0 +1,911 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/*
+ * Copyright (c) 2012 Vincent Bernat <bernat@luffy.cx>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "lldpd.h"
+#include "trace.h"
+
+#include <unistd.h>
+#include <signal.h>
+#include <errno.h>
+#include <time.h>
+#include <fcntl.h>
+#if defined(__clang__)
+# pragma clang diagnostic push
+# pragma clang diagnostic ignored "-Wdocumentation"
+#endif
+#include <event2/event.h>
+#include <event2/bufferevent.h>
+#include <event2/buffer.h>
+#if defined(__clang__)
+# pragma clang diagnostic pop
+#endif
+
+#define EVENT_BUFFER 1024
+
+static void
+levent_log_cb(int severity, const char *msg)
+{
+ switch (severity) {
+ case _EVENT_LOG_DEBUG:
+ log_debug("libevent", "%s", msg);
+ break;
+ case _EVENT_LOG_MSG:
+ log_info("libevent", "%s", msg);
+ break;
+ case _EVENT_LOG_WARN:
+ log_warnx("libevent", "%s", msg);
+ break;
+ case _EVENT_LOG_ERR:
+ log_warnx("libevent", "%s", msg);
+ break;
+ }
+}
+
+struct lldpd_events {
+ TAILQ_ENTRY(lldpd_events) next;
+ struct event *ev;
+};
+TAILQ_HEAD(ev_l, lldpd_events);
+
+#define levent_snmp_fds(cfg) ((struct ev_l *)(cfg)->g_snmp_fds)
+#define levent_hardware_fds(hardware) ((struct ev_l *)(hardware)->h_recv)
+
+#ifdef USE_SNMP
+# include <net-snmp/net-snmp-config.h>
+# include <net-snmp/net-snmp-includes.h>
+# include <net-snmp/agent/net-snmp-agent-includes.h>
+# include <net-snmp/agent/snmp_vars.h>
+
+/* Compatibility with older versions of NetSNMP */
+# ifndef HAVE_SNMP_SELECT_INFO2
+# define netsnmp_large_fd_set fd_set
+# define snmp_read2 snmp_read
+# define snmp_select_info2 snmp_select_info
+# define netsnmp_large_fd_set_init(...)
+# define netsnmp_large_fd_set_cleanup(...)
+# define NETSNMP_LARGE_FD_SET FD_SET
+# define NETSNMP_LARGE_FD_CLR FD_CLR
+# define NETSNMP_LARGE_FD_ZERO FD_ZERO
+# define NETSNMP_LARGE_FD_ISSET FD_ISSET
+# else
+# include <net-snmp/library/large_fd_set.h>
+# endif
+
+static void levent_snmp_update(struct lldpd *);
+
+/*
+ * Callback function when we have something to read from SNMP.
+ *
+ * This function is called because we have a read event on one SNMP
+ * file descriptor. When need to call snmp_read() on it.
+ */
+static void
+levent_snmp_read(evutil_socket_t fd, short what, void *arg)
+{
+ struct lldpd *cfg = arg;
+ netsnmp_large_fd_set fdset;
+ (void)what;
+ netsnmp_large_fd_set_init(&fdset, FD_SETSIZE);
+ NETSNMP_LARGE_FD_ZERO(&fdset);
+ NETSNMP_LARGE_FD_SET(fd, &fdset);
+ snmp_read2(&fdset);
+ levent_snmp_update(cfg);
+}
+
+/*
+ * Callback function for a SNMP timeout.
+ *
+ * A SNMP timeout has occurred. Call `snmp_timeout()` to handle it.
+ */
+static void
+levent_snmp_timeout(evutil_socket_t fd, short what, void *arg)
+{
+ struct lldpd *cfg = arg;
+ (void)what;
+ (void)fd;
+ snmp_timeout();
+ run_alarms();
+ levent_snmp_update(cfg);
+}
+
+/*
+ * Watch a new SNMP FD.
+ *
+ * @param base The libevent base we are working on.
+ * @param fd The file descriptor we want to watch.
+ *
+ * The file descriptor is appended to the list of file descriptors we
+ * want to watch.
+ */
+static void
+levent_snmp_add_fd(struct lldpd *cfg, int fd)
+{
+ struct event_base *base = cfg->g_base;
+ struct lldpd_events *snmpfd = calloc(1, sizeof(struct lldpd_events));
+ if (!snmpfd) {
+ log_warn("event", "unable to allocate memory for new SNMP event");
+ return;
+ }
+ levent_make_socket_nonblocking(fd);
+ if ((snmpfd->ev = event_new(base, fd, EV_READ | EV_PERSIST, levent_snmp_read,
+ cfg)) == NULL) {
+ log_warnx("event", "unable to allocate a new SNMP event for FD %d", fd);
+ free(snmpfd);
+ return;
+ }
+ if (event_add(snmpfd->ev, NULL) == -1) {
+ log_warnx("event", "unable to schedule new SNMP event for FD %d", fd);
+ event_free(snmpfd->ev);
+ free(snmpfd);
+ return;
+ }
+ TAILQ_INSERT_TAIL(levent_snmp_fds(cfg), snmpfd, next);
+}
+
+/*
+ * Update SNMP event loop.
+ *
+ * New events are added and some other are removed. This function
+ * should be called every time a SNMP event happens: either when
+ * handling a SNMP packet, a SNMP timeout or when sending a SNMP
+ * packet. This function will keep libevent in sync with NetSNMP.
+ *
+ * @param base The libevent base we are working on.
+ */
+static void
+levent_snmp_update(struct lldpd *cfg)
+{
+ int maxfd = 0;
+ int block = 1;
+ struct timeval timeout;
+ static int howmany = 0;
+ int added = 0, removed = 0, current = 0;
+ struct lldpd_events *snmpfd, *snmpfd_next;
+
+ /* snmp_select_info() can be tricky to understand. We set `block` to
+ 1 to means that we don't request a timeout. snmp_select_info()
+ will reset `block` to 0 if it wants us to setup a timeout. In
+ this timeout, `snmp_timeout()` should be invoked.
+
+ Each FD in `fdset` will need to be watched for reading. If one of
+ them become active, `snmp_read()` should be called on it.
+ */
+
+ netsnmp_large_fd_set fdset;
+ netsnmp_large_fd_set_init(&fdset, FD_SETSIZE);
+ NETSNMP_LARGE_FD_ZERO(&fdset);
+ snmp_select_info2(&maxfd, &fdset, &timeout, &block);
+
+ /* We need to untrack any event whose FD is not in `fdset`
+ anymore */
+ for (snmpfd = TAILQ_FIRST(levent_snmp_fds(cfg)); snmpfd; snmpfd = snmpfd_next) {
+ snmpfd_next = TAILQ_NEXT(snmpfd, next);
+ if (event_get_fd(snmpfd->ev) >= maxfd ||
+ (!NETSNMP_LARGE_FD_ISSET(event_get_fd(snmpfd->ev), &fdset))) {
+ event_free(snmpfd->ev);
+ TAILQ_REMOVE(levent_snmp_fds(cfg), snmpfd, next);
+ free(snmpfd);
+ removed++;
+ } else {
+ NETSNMP_LARGE_FD_CLR(event_get_fd(snmpfd->ev), &fdset);
+ current++;
+ }
+ }
+
+ /* Invariant: FD in `fdset` are not in list of FD */
+ for (int fd = 0; fd < maxfd; fd++) {
+ if (NETSNMP_LARGE_FD_ISSET(fd, &fdset)) {
+ levent_snmp_add_fd(cfg, fd);
+ added++;
+ }
+ }
+ current += added;
+ if (howmany != current) {
+ log_debug("event",
+ "added %d events, removed %d events, total of %d events", added,
+ removed, current);
+ howmany = current;
+ }
+
+ /* If needed, handle timeout */
+ if (evtimer_add(cfg->g_snmp_timeout, block ? NULL : &timeout) == -1)
+ log_warnx("event", "unable to schedule timeout function for SNMP");
+
+ netsnmp_large_fd_set_cleanup(&fdset);
+}
+#endif /* USE_SNMP */
+
+struct lldpd_one_client {
+ TAILQ_ENTRY(lldpd_one_client) next;
+ struct lldpd *cfg;
+ struct bufferevent *bev;
+ int subscribed; /* Is this client subscribed to changes? */
+};
+TAILQ_HEAD(, lldpd_one_client) lldpd_clients;
+
+static void
+levent_ctl_free_client(struct lldpd_one_client *client)
+{
+ if (client && client->bev) bufferevent_free(client->bev);
+ if (client) {
+ TAILQ_REMOVE(&lldpd_clients, client, next);
+ free(client);
+ }
+}
+
+static void
+levent_ctl_close_clients()
+{
+ struct lldpd_one_client *client, *client_next;
+ for (client = TAILQ_FIRST(&lldpd_clients); client; client = client_next) {
+ client_next = TAILQ_NEXT(client, next);
+ levent_ctl_free_client(client);
+ }
+}
+
+static ssize_t
+levent_ctl_send(struct lldpd_one_client *client, int type, void *data, size_t len)
+{
+ struct bufferevent *bev = client->bev;
+ struct hmsg_header hdr = { .len = len, .type = type };
+ bufferevent_disable(bev, EV_WRITE);
+ if (bufferevent_write(bev, &hdr, sizeof(struct hmsg_header)) == -1 ||
+ (len > 0 && bufferevent_write(bev, data, len) == -1)) {
+ log_warnx("event", "unable to create answer to client");
+ levent_ctl_free_client(client);
+ return -1;
+ }
+ bufferevent_enable(bev, EV_WRITE);
+ return len;
+}
+
+void
+levent_ctl_notify(char *ifname, int state, struct lldpd_port *neighbor)
+{
+ struct lldpd_one_client *client, *client_next;
+ struct lldpd_neighbor_change neigh = { .ifname = ifname,
+ .state = state,
+ .neighbor = neighbor };
+ void *output = NULL;
+ ssize_t output_len = 0;
+
+ /* Don't use TAILQ_FOREACH, the client may be deleted in case of errors. */
+ log_debug("control", "notify clients of neighbor changes");
+ for (client = TAILQ_FIRST(&lldpd_clients); client; client = client_next) {
+ client_next = TAILQ_NEXT(client, next);
+ if (!client->subscribed) continue;
+
+ if (output == NULL) {
+ /* Ugly hack: we don't want to transmit a list of
+ * ports. We patch the port to avoid this. */
+ TAILQ_ENTRY(lldpd_port) backup_p_entries;
+ memcpy(&backup_p_entries, &neighbor->p_entries,
+ sizeof(backup_p_entries));
+ memset(&neighbor->p_entries, 0, sizeof(backup_p_entries));
+ output_len = lldpd_neighbor_change_serialize(&neigh, &output);
+ memcpy(&neighbor->p_entries, &backup_p_entries,
+ sizeof(backup_p_entries));
+
+ if (output_len <= 0) {
+ log_warnx("event",
+ "unable to serialize changed neighbor");
+ return;
+ }
+ }
+
+ levent_ctl_send(client, NOTIFICATION, output, output_len);
+ }
+
+ free(output);
+}
+
+static ssize_t
+levent_ctl_send_cb(void *out, int type, void *data, size_t len)
+{
+ struct lldpd_one_client *client = out;
+ return levent_ctl_send(client, type, data, len);
+}
+
+static void
+levent_ctl_recv(struct bufferevent *bev, void *ptr)
+{
+ struct lldpd_one_client *client = ptr;
+ struct evbuffer *buffer = bufferevent_get_input(bev);
+ size_t buffer_len = evbuffer_get_length(buffer);
+ struct hmsg_header hdr;
+ void *data = NULL;
+
+ log_debug("control", "receive data on Unix socket");
+ if (buffer_len < sizeof(struct hmsg_header)) return; /* Not enough data yet */
+ if (evbuffer_copyout(buffer, &hdr, sizeof(struct hmsg_header)) !=
+ sizeof(struct hmsg_header)) {
+ log_warnx("event", "not able to read header");
+ return;
+ }
+ if (hdr.len > HMSG_MAX_SIZE) {
+ log_warnx("event", "message received is too large");
+ goto recv_error;
+ }
+
+ if (buffer_len < hdr.len + sizeof(struct hmsg_header))
+ return; /* Not enough data yet */
+ if (hdr.len > 0 && (data = malloc(hdr.len)) == NULL) {
+ log_warnx("event", "not enough memory");
+ goto recv_error;
+ }
+ evbuffer_drain(buffer, sizeof(struct hmsg_header));
+ if (hdr.len > 0) evbuffer_remove(buffer, data, hdr.len);
+
+ /* Currently, we should not receive notification acknowledgment. But if
+ * we receive one, we can discard it. */
+ if (hdr.len == 0 && hdr.type == NOTIFICATION) return;
+ if (client_handle_client(client->cfg, levent_ctl_send_cb, client, hdr.type,
+ data, hdr.len, &client->subscribed) == -1)
+ goto recv_error;
+ free(data);
+ return;
+
+recv_error:
+ free(data);
+ levent_ctl_free_client(client);
+}
+
+static void
+levent_ctl_event(struct bufferevent *bev, short events, void *ptr)
+{
+ struct lldpd_one_client *client = ptr;
+ if (events & BEV_EVENT_ERROR) {
+ log_warnx("event", "an error occurred with client: %s",
+ evutil_socket_error_to_string(EVUTIL_SOCKET_ERROR()));
+ levent_ctl_free_client(client);
+ } else if (events & BEV_EVENT_EOF) {
+ log_debug("event", "client has been disconnected");
+ levent_ctl_free_client(client);
+ }
+}
+
+static void
+levent_ctl_accept(evutil_socket_t fd, short what, void *arg)
+{
+ struct lldpd *cfg = arg;
+ struct lldpd_one_client *client = NULL;
+ int s;
+ (void)what;
+
+ log_debug("control", "accept a new connection");
+ if ((s = accept(fd, NULL, NULL)) == -1) {
+ log_warn("event", "unable to accept connection from socket");
+ return;
+ }
+ client = calloc(1, sizeof(struct lldpd_one_client));
+ if (!client) {
+ log_warnx("event", "unable to allocate memory for new client");
+ close(s);
+ goto accept_failed;
+ }
+ client->cfg = cfg;
+ levent_make_socket_nonblocking(s);
+ TAILQ_INSERT_TAIL(&lldpd_clients, client, next);
+ if ((client->bev = bufferevent_socket_new(cfg->g_base, s,
+ BEV_OPT_CLOSE_ON_FREE)) == NULL) {
+ log_warnx("event",
+ "unable to allocate a new buffer event for new client");
+ close(s);
+ goto accept_failed;
+ }
+ bufferevent_setcb(client->bev, levent_ctl_recv, NULL, levent_ctl_event, client);
+ bufferevent_enable(client->bev, EV_READ | EV_WRITE);
+ log_debug("event", "new client accepted");
+ /* coverity[leaked_handle]
+ s has been saved by bufferevent_socket_new */
+ return;
+accept_failed:
+ levent_ctl_free_client(client);
+}
+
+static void
+levent_priv(evutil_socket_t fd, short what, void *arg)
+{
+ struct event_base *base = arg;
+ ssize_t n;
+ int err;
+ char one;
+ (void)what;
+ /* Check if we have some data available. We need to pass the socket in
+ * non-blocking mode to be able to run the check without disruption. */
+ levent_make_socket_nonblocking(fd);
+ n = read(fd, &one, 1);
+ err = errno;
+ levent_make_socket_blocking(fd);
+
+ switch (n) {
+ case -1:
+ if (err == EAGAIN || err == EWOULDBLOCK) /* No data, all good */
+ return;
+ log_warnx("event", "unable to poll monitor process, exit");
+ break;
+ case 0:
+ log_warnx("event", "monitor process has terminated, exit");
+ break;
+ default:
+ /* This is a bit unsafe as we are now out-of-sync with the
+ * monitor. It would be safer to request 0 byte, but some OS
+ * (illumos) seem to take the shortcut that by asking 0 byte,
+ * we can just return 0 byte. */
+ log_warnx("event",
+ "received unexpected data from monitor process, exit");
+ break;
+ }
+ event_base_loopbreak(base);
+}
+
+static void
+levent_dump(evutil_socket_t fd, short what, void *arg)
+{
+ struct event_base *base = arg;
+ (void)fd;
+ (void)what;
+ log_debug("event", "dumping all events");
+ event_base_dump_events(base, stderr);
+}
+static void
+levent_stop(evutil_socket_t fd, short what, void *arg)
+{
+ struct event_base *base = arg;
+ (void)fd;
+ (void)what;
+ event_base_loopbreak(base);
+}
+
+static void
+levent_update_and_send(evutil_socket_t fd, short what, void *arg)
+{
+ struct lldpd *cfg = arg;
+ struct timeval tv;
+ long interval_ms = cfg->g_config.c_tx_interval;
+
+ (void)fd;
+ (void)what;
+ lldpd_loop(cfg);
+ if (cfg->g_iface_event != NULL) interval_ms *= 20;
+ if (interval_ms < 30000) interval_ms = 30000;
+ tv.tv_sec = interval_ms / 1000;
+ tv.tv_usec = (interval_ms % 1000) * 1000;
+ event_add(cfg->g_main_loop, &tv);
+}
+
+void
+levent_update_now(struct lldpd *cfg)
+{
+ if (cfg->g_main_loop) event_active(cfg->g_main_loop, EV_TIMEOUT, 1);
+}
+
+void
+levent_send_now(struct lldpd *cfg)
+{
+ struct lldpd_hardware *hardware;
+ TAILQ_FOREACH (hardware, &cfg->g_hardware, h_entries) {
+ if (hardware->h_timer)
+ event_active(hardware->h_timer, EV_TIMEOUT, 1);
+ else
+ log_warnx("event", "BUG: no timer present for interface %s",
+ hardware->h_ifname);
+ }
+}
+
+static void
+levent_init(struct lldpd *cfg)
+{
+ /* Setup libevent */
+ log_debug("event", "initialize libevent");
+ event_set_log_callback(levent_log_cb);
+ if (!(cfg->g_base = event_base_new()))
+ fatalx("event", "unable to create a new libevent base");
+ log_info("event", "libevent %s initialized with %s method", event_get_version(),
+ event_base_get_method(cfg->g_base));
+
+ /* Setup SNMP */
+#ifdef USE_SNMP
+ if (cfg->g_snmp) {
+ agent_init(cfg, cfg->g_snmp_agentx);
+ cfg->g_snmp_timeout =
+ evtimer_new(cfg->g_base, levent_snmp_timeout, cfg);
+ if (!cfg->g_snmp_timeout)
+ fatalx("event", "unable to setup timeout function for SNMP");
+ if ((cfg->g_snmp_fds = malloc(sizeof(struct ev_l))) == NULL)
+ fatalx("event", "unable to allocate memory for SNMP events");
+ TAILQ_INIT(levent_snmp_fds(cfg));
+ }
+#endif
+
+ /* Setup loop that will run every X seconds. */
+ log_debug("event", "register loop timer");
+ if (!(cfg->g_main_loop =
+ event_new(cfg->g_base, -1, 0, levent_update_and_send, cfg)))
+ fatalx("event", "unable to setup main timer");
+ event_active(cfg->g_main_loop, EV_TIMEOUT, 1);
+
+ /* Setup unix socket */
+ struct event *ctl_event;
+ log_debug("event", "register Unix socket");
+ TAILQ_INIT(&lldpd_clients);
+ levent_make_socket_nonblocking(cfg->g_ctl);
+ if ((ctl_event = event_new(cfg->g_base, cfg->g_ctl, EV_READ | EV_PERSIST,
+ levent_ctl_accept, cfg)) == NULL)
+ fatalx("event", "unable to setup control socket event");
+ event_add(ctl_event, NULL);
+
+ /* Somehow monitor the monitor process */
+ struct event *monitor_event;
+ log_debug("event", "monitor the monitor process");
+ if ((monitor_event = event_new(cfg->g_base, priv_fd(PRIV_UNPRIVILEGED),
+ EV_READ | EV_PERSIST, levent_priv, cfg->g_base)) == NULL)
+ fatalx("event", "unable to monitor monitor process");
+ event_add(monitor_event, NULL);
+
+ /* Signals */
+ log_debug("event", "register signals");
+ evsignal_add(evsignal_new(cfg->g_base, SIGUSR1, levent_dump, cfg->g_base),
+ NULL);
+ evsignal_add(evsignal_new(cfg->g_base, SIGINT, levent_stop, cfg->g_base), NULL);
+ evsignal_add(evsignal_new(cfg->g_base, SIGTERM, levent_stop, cfg->g_base),
+ NULL);
+}
+
+/* Initialize libevent and start the event loop */
+void
+levent_loop(struct lldpd *cfg)
+{
+ levent_init(cfg);
+ lldpd_loop(cfg);
+#ifdef USE_SNMP
+ if (cfg->g_snmp) levent_snmp_update(cfg);
+#endif
+
+ /* libevent loop */
+ do {
+ TRACE(LLDPD_EVENT_LOOP());
+ if (event_base_got_break(cfg->g_base) ||
+ event_base_got_exit(cfg->g_base))
+ break;
+ } while (event_base_loop(cfg->g_base, EVLOOP_ONCE) == 0);
+
+ if (cfg->g_iface_timer_event != NULL) event_free(cfg->g_iface_timer_event);
+
+#ifdef USE_SNMP
+ if (cfg->g_snmp) agent_shutdown();
+#endif /* USE_SNMP */
+
+ levent_ctl_close_clients();
+}
+
+/* Release libevent resources */
+void
+levent_shutdown(struct lldpd *cfg)
+{
+ if (cfg->g_iface_event) event_free(cfg->g_iface_event);
+ if (cfg->g_cleanup_timer) event_free(cfg->g_cleanup_timer);
+ event_base_free(cfg->g_base);
+}
+
+static void
+levent_hardware_recv(evutil_socket_t fd, short what, void *arg)
+{
+ struct lldpd_hardware *hardware = arg;
+ struct lldpd *cfg = hardware->h_cfg;
+ (void)what;
+ log_debug("event", "received something for %s", hardware->h_ifname);
+ lldpd_recv(cfg, hardware, fd);
+ levent_schedule_cleanup(cfg);
+}
+
+void
+levent_hardware_init(struct lldpd_hardware *hardware)
+{
+ log_debug("event", "initialize events for %s", hardware->h_ifname);
+ if ((hardware->h_recv = malloc(sizeof(struct ev_l))) == NULL) {
+ log_warnx("event", "unable to allocate memory for %s",
+ hardware->h_ifname);
+ return;
+ }
+ TAILQ_INIT(levent_hardware_fds(hardware));
+}
+
+void
+levent_hardware_add_fd(struct lldpd_hardware *hardware, int fd)
+{
+ struct lldpd_events *hfd = NULL;
+ if (!hardware->h_recv) return;
+
+ hfd = calloc(1, sizeof(struct lldpd_events));
+ if (!hfd) {
+ log_warnx("event", "unable to allocate new event for %s",
+ hardware->h_ifname);
+ return;
+ }
+ levent_make_socket_nonblocking(fd);
+ if ((hfd->ev = event_new(hardware->h_cfg->g_base, fd, EV_READ | EV_PERSIST,
+ levent_hardware_recv, hardware)) == NULL) {
+ log_warnx("event", "unable to allocate a new event for %s",
+ hardware->h_ifname);
+ free(hfd);
+ return;
+ }
+ if (event_add(hfd->ev, NULL) == -1) {
+ log_warnx("event", "unable to schedule new event for %s",
+ hardware->h_ifname);
+ event_free(hfd->ev);
+ free(hfd);
+ return;
+ }
+ TAILQ_INSERT_TAIL(levent_hardware_fds(hardware), hfd, next);
+}
+
+void
+levent_hardware_release(struct lldpd_hardware *hardware)
+{
+ struct lldpd_events *ev, *ev_next;
+ if (hardware->h_timer) {
+ event_free(hardware->h_timer);
+ hardware->h_timer = NULL;
+ }
+ if (!hardware->h_recv) return;
+
+ log_debug("event", "release events for %s", hardware->h_ifname);
+ for (ev = TAILQ_FIRST(levent_hardware_fds(hardware)); ev; ev = ev_next) {
+ ev_next = TAILQ_NEXT(ev, next);
+ /* We may close several time the same FD. This is harmless. */
+ close(event_get_fd(ev->ev));
+ event_free(ev->ev);
+ TAILQ_REMOVE(levent_hardware_fds(hardware), ev, next);
+ free(ev);
+ }
+ free(levent_hardware_fds(hardware));
+}
+
+static void
+levent_iface_trigger(evutil_socket_t fd, short what, void *arg)
+{
+ struct lldpd *cfg = arg;
+ log_debug("event", "triggering update of all interfaces");
+ lldpd_update_localports(cfg);
+}
+
+static void
+levent_iface_recv(evutil_socket_t fd, short what, void *arg)
+{
+ struct lldpd *cfg = arg;
+ char buffer[EVENT_BUFFER];
+ int n;
+
+ if (cfg->g_iface_cb == NULL) {
+ /* Discard the message */
+ while (1) {
+ n = read(fd, buffer, sizeof(buffer));
+ if (n == -1 && (errno == EWOULDBLOCK || errno == EAGAIN)) break;
+ if (n == -1) {
+ log_warn("event",
+ "unable to receive interface change notification message");
+ return;
+ }
+ if (n == 0) {
+ log_warnx("event",
+ "end of file reached while getting interface change notification message");
+ return;
+ }
+ }
+ } else {
+ cfg->g_iface_cb(cfg);
+ }
+
+ /* Schedule local port update. We don't run it right away because we may
+ * receive a batch of events like this. */
+ struct timeval one_sec = { 1, 0 };
+ TRACE(LLDPD_INTERFACES_NOTIFICATION());
+ log_debug("event",
+ "received notification change, schedule an update of all interfaces in one second");
+ if (cfg->g_iface_timer_event == NULL) {
+ if ((cfg->g_iface_timer_event = evtimer_new(cfg->g_base,
+ levent_iface_trigger, cfg)) == NULL) {
+ log_warnx("event",
+ "unable to create a new event to trigger interface update");
+ return;
+ }
+ }
+ if (evtimer_add(cfg->g_iface_timer_event, &one_sec) == -1) {
+ log_warnx("event", "unable to schedule interface updates");
+ return;
+ }
+}
+
+int
+levent_iface_subscribe(struct lldpd *cfg, int socket)
+{
+ log_debug("event", "subscribe to interface changes from socket %d", socket);
+ levent_make_socket_nonblocking(socket);
+ cfg->g_iface_event = event_new(cfg->g_base, socket, EV_READ | EV_PERSIST,
+ levent_iface_recv, cfg);
+ if (cfg->g_iface_event == NULL) {
+ log_warnx("event",
+ "unable to allocate a new event for interface changes");
+ return -1;
+ }
+ if (event_add(cfg->g_iface_event, NULL) == -1) {
+ log_warnx("event", "unable to schedule new interface changes event");
+ event_free(cfg->g_iface_event);
+ cfg->g_iface_event = NULL;
+ return -1;
+ }
+ return 0;
+}
+
+static void
+levent_trigger_cleanup(evutil_socket_t fd, short what, void *arg)
+{
+ struct lldpd *cfg = arg;
+ lldpd_cleanup(cfg);
+}
+
+void
+levent_schedule_cleanup(struct lldpd *cfg)
+{
+ log_debug("event", "schedule next cleanup");
+ if (cfg->g_cleanup_timer != NULL) {
+ event_free(cfg->g_cleanup_timer);
+ }
+ cfg->g_cleanup_timer = evtimer_new(cfg->g_base, levent_trigger_cleanup, cfg);
+ if (cfg->g_cleanup_timer == NULL) {
+ log_warnx("event", "unable to allocate a new event for cleanup tasks");
+ return;
+ }
+
+ /* Compute the next TTL event */
+ struct timeval tv = { cfg->g_config.c_ttl, 0 };
+ time_t now = time(NULL);
+ time_t next;
+ struct lldpd_hardware *hardware;
+ struct lldpd_port *port;
+ TAILQ_FOREACH (hardware, &cfg->g_hardware, h_entries) {
+ TAILQ_FOREACH (port, &hardware->h_rports, p_entries) {
+ if (now >= port->p_lastupdate + port->p_ttl) {
+ tv.tv_sec = 0;
+ log_debug("event",
+ "immediate cleanup on port %s (%lld, %d, %lld)",
+ hardware->h_ifname, (long long)now, port->p_ttl,
+ (long long)port->p_lastupdate);
+ break;
+ }
+ next = port->p_ttl - (now - port->p_lastupdate);
+ if (next < tv.tv_sec) tv.tv_sec = next;
+ }
+ }
+
+ log_debug("event", "next cleanup in %ld seconds", (long)tv.tv_sec);
+ if (event_add(cfg->g_cleanup_timer, &tv) == -1) {
+ log_warnx("event", "unable to schedule cleanup task");
+ event_free(cfg->g_cleanup_timer);
+ cfg->g_cleanup_timer = NULL;
+ return;
+ }
+}
+
+static void
+levent_send_pdu(evutil_socket_t fd, short what, void *arg)
+{
+ struct lldpd_hardware *hardware = arg;
+ int tx_interval = hardware->h_cfg->g_config.c_tx_interval;
+
+ log_debug("event", "trigger sending PDU for port %s", hardware->h_ifname);
+ lldpd_send(hardware);
+
+#ifdef ENABLE_LLDPMED
+ if (hardware->h_tx_fast > 0) hardware->h_tx_fast--;
+
+ if (hardware->h_tx_fast > 0)
+ tx_interval = hardware->h_cfg->g_config.c_tx_fast_interval * 1000;
+#endif
+
+ struct timeval tv;
+ tv.tv_sec = tx_interval / 1000;
+ tv.tv_usec = (tx_interval % 1000) * 1000;
+ if (event_add(hardware->h_timer, &tv) == -1) {
+ log_warnx("event", "unable to re-register timer event for port %s",
+ hardware->h_ifname);
+ event_free(hardware->h_timer);
+ hardware->h_timer = NULL;
+ return;
+ }
+}
+
+void
+levent_schedule_pdu(struct lldpd_hardware *hardware)
+{
+ log_debug("event", "schedule sending PDU on %s", hardware->h_ifname);
+ if (hardware->h_timer == NULL) {
+ hardware->h_timer =
+ evtimer_new(hardware->h_cfg->g_base, levent_send_pdu, hardware);
+ if (hardware->h_timer == NULL) {
+ log_warnx("event", "unable to schedule PDU sending for port %s",
+ hardware->h_ifname);
+ return;
+ }
+ }
+
+ struct timeval tv = { 0, 0 };
+ if (event_add(hardware->h_timer, &tv) == -1) {
+ log_warnx("event", "unable to register timer event for port %s",
+ hardware->h_ifname);
+ event_free(hardware->h_timer);
+ hardware->h_timer = NULL;
+ return;
+ }
+}
+
+int
+levent_make_socket_nonblocking(int fd)
+{
+ int flags;
+ if ((flags = fcntl(fd, F_GETFL, NULL)) < 0) {
+ log_warn("event", "fcntl(%d, F_GETFL)", fd);
+ return -1;
+ }
+ if (flags & O_NONBLOCK) return 0;
+ if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) {
+ log_warn("event", "fcntl(%d, F_SETFL)", fd);
+ return -1;
+ }
+ return 0;
+}
+
+int
+levent_make_socket_blocking(int fd)
+{
+ int flags;
+ if ((flags = fcntl(fd, F_GETFL, NULL)) < 0) {
+ log_warn("event", "fcntl(%d, F_GETFL)", fd);
+ return -1;
+ }
+ if (!(flags & O_NONBLOCK)) return 0;
+ if (fcntl(fd, F_SETFL, flags & ~O_NONBLOCK) == -1) {
+ log_warn("event", "fcntl(%d, F_SETFL)", fd);
+ return -1;
+ }
+ return 0;
+}
+
+#ifdef HOST_OS_LINUX
+/* Receive and log error from a socket when there is suspicion of an error. */
+void
+levent_recv_error(int fd, const char *source)
+{
+ do {
+ ssize_t n;
+ char buf[1024] = {};
+ struct msghdr msg = { .msg_control = buf,
+ .msg_controllen = sizeof(buf) };
+ if ((n = recvmsg(fd, &msg, MSG_ERRQUEUE | MSG_DONTWAIT)) <= 0) {
+ return;
+ }
+ struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
+ if (cmsg == NULL)
+ log_warnx("event", "received unknown error on %s", source);
+ else
+ log_warnx("event", "received error (level=%d/type=%d) on %s",
+ cmsg->cmsg_level, cmsg->cmsg_type, source);
+ } while (1);
+}
+#endif
diff --git a/src/daemon/forward-bsd.c b/src/daemon/forward-bsd.c
new file mode 100644
index 0000000..1f930de
--- /dev/null
+++ b/src/daemon/forward-bsd.c
@@ -0,0 +1,31 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/*
+ * Copyright (c) 2013 Vincent Bernat <bernat@luffy.cx>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "lldpd.h"
+
+#include <sys/param.h>
+#include <sys/sysctl.h>
+
+int
+interfaces_routing_enabled(struct lldpd *cfg)
+{
+ (void)cfg;
+ int n, mib[4] = { CTL_NET, PF_INET, IPPROTO_IP, IPCTL_FORWARDING };
+ size_t len = sizeof(int);
+ if (sysctl(mib, 4, &n, &len, NULL, 0) != -1) return (n == 1);
+ return -1;
+}
diff --git a/src/daemon/forward-linux.c b/src/daemon/forward-linux.c
new file mode 100644
index 0000000..6308445
--- /dev/null
+++ b/src/daemon/forward-linux.c
@@ -0,0 +1,59 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/*
+ * Copyright (c) 2013 Vincent Bernat <bernat@luffy.cx>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "lldpd.h"
+
+#include <unistd.h>
+
+static int
+ip_forwarding_enabled(int af)
+{
+ int fd, rc = -1;
+ const char *fname;
+ char status;
+
+ if (af == LLDPD_AF_IPV4)
+ fname = PROCFS_SYS_NET "ipv4/ip_forward";
+ else if (af == LLDPD_AF_IPV6)
+ fname = PROCFS_SYS_NET "ipv6/conf/all/forwarding";
+ else
+ return -1;
+
+ if ((fd = priv_open(fname)) < 0) return -1;
+
+ if (read(fd, &status, 1) == 1) rc = (status == '1');
+
+ close(fd);
+ return rc;
+}
+
+int
+interfaces_routing_enabled(struct lldpd *cfg)
+{
+ (void)cfg;
+ int rc;
+
+ rc = ip_forwarding_enabled(LLDPD_AF_IPV4);
+ /*
+ * Report being a router if IPv4 forwarding is enabled.
+ * In case of error also stop the execution right away.
+ * If IPv4 forwarding is disabled we'll check the IPv6 status.
+ */
+ if (rc != 0) return rc;
+
+ return ip_forwarding_enabled(LLDPD_AF_IPV6);
+}
diff --git a/src/daemon/forward-solaris.c b/src/daemon/forward-solaris.c
new file mode 100644
index 0000000..b44a110
--- /dev/null
+++ b/src/daemon/forward-solaris.c
@@ -0,0 +1,27 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/*
+ * Copyright (c) 2013 Vincent Bernat <bernat@luffy.cx>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "lldpd.h"
+
+int
+interfaces_routing_enabled(struct lldpd *cfg)
+{
+ /* Dunno how to get this for Solaris. See the commit introducing Solaris
+ support (maybe c3e340b6be8add4eb3a41882847a96e66793e82c) for a
+ solution which does not work in a chroot. */
+ return 0;
+}
diff --git a/src/daemon/frame.c b/src/daemon/frame.c
new file mode 100644
index 0000000..adc4629
--- /dev/null
+++ b/src/daemon/frame.c
@@ -0,0 +1,66 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/*
+ * Copyright (c) 2009 Vincent Bernat <bernat@luffy.cx>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "lldpd.h"
+
+/**
+ * Compute the checksum as 16-bit word.
+ */
+u_int16_t
+frame_checksum(const u_char *cp, int len, int cisco)
+{
+ unsigned int sum = 0, v = 0;
+ int oddbyte = 0;
+
+ 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. From function
+ * dissect_cdp() in wireshark. 2014/6/14,zhengy@yealink.com:
+ *
+ * CDP doesn't adhere to RFC 1071 section 2. (B). It incorrectly assumes
+ * checksums are calculated on a big endian platform, therefore i.s.o.
+ * padding odd sized data with a zero byte _at the end_ it sets the last
+ * big endian _word_ to contain the last network _octet_. This byteswap
+ * has to be done on the last octet of network data before feeding it to
+ * the Internet checksum routine.
+ * CDP checksumming code has a bug in the addition of this last _word_
+ * as a signed number into the long word intermediate checksum. When
+ * reducing this long to word size checksum an off-by-one error can be
+ * made. This off-by-one error is compensated for in the last _word_ of
+ * the network data.
+ */
+ if (oddbyte) {
+ if (cisco) {
+ if (v & 0x80) {
+ sum += 0xff << 8;
+ sum += v - 1;
+ } else {
+ sum += v;
+ }
+ } else {
+ sum += v << 8;
+ }
+ }
+
+ sum = (sum >> 16) + (sum & 0xffff);
+ sum += sum >> 16;
+ return (0xffff & ~sum);
+}
diff --git a/src/daemon/frame.h b/src/daemon/frame.h
new file mode 100644
index 0000000..714de09
--- /dev/null
+++ b/src/daemon/frame.h
@@ -0,0 +1,102 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/*
+ * Copyright (c) 2009 Vincent Bernat <bernat@luffy.cx>
+ * Copyright (c) 2014 Michael Chapman
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef _FRAME_H
+#define _FRAME_H
+
+static union {
+ uint8_t f_uint8;
+ uint16_t f_uint16;
+ uint32_t f_uint32;
+} types;
+
+/* This set of macro are used to build packets. The current position in buffer
+ * is `pos'. The length of the remaining space in buffer is `length'. `type'
+ * should be a member of `types'.
+ *
+ * This was stolen from ladvd which was adapted from Net::CDP. The original
+ * author of those macros, Michael Chapman, has relicensed those macros under
+ * the ISC license. */
+
+#define POKE(value, type, func) \
+ ((length >= sizeof(type)) && \
+ (type = func(value), memcpy(pos, &type, sizeof(type)), length -= sizeof(type), \
+ pos += sizeof(type), 1))
+#define POKE_UINT8(value) POKE(value, types.f_uint8, )
+#define POKE_UINT16(value) POKE(value, types.f_uint16, htons)
+#define POKE_UINT32(value) POKE(value, types.f_uint32, htonl)
+#define POKE_BYTES(value, bytes) \
+ ((length >= (bytes)) && \
+ (memcpy(pos, value, bytes), length -= (bytes), pos += (bytes), 1))
+#define POKE_SAVE(where) (where = pos, 1)
+#define POKE_RESTORE(where) \
+ do { \
+ if ((where) > pos) \
+ length -= ((where)-pos); \
+ else \
+ length += (pos - (where)); \
+ pos = (where); \
+ } while (0)
+
+/* This set of macro are used to parse packets. The same variable as for POKE_*
+ * are used. There is no check on boundaries. */
+
+#define PEEK(type, func) \
+ (memcpy(&type, pos, sizeof(type)), length -= sizeof(type), pos += sizeof(type), \
+ func(type))
+#define PEEK_UINT8 PEEK(types.f_uint8, )
+#define PEEK_UINT16 PEEK(types.f_uint16, ntohs)
+#define PEEK_UINT32 PEEK(types.f_uint32, ntohl)
+#define PEEK_BYTES(value, bytes) \
+ do { \
+ memcpy(value, pos, bytes); \
+ length -= (bytes); \
+ pos += (bytes); \
+ } while (0)
+#define PEEK_DISCARD(bytes) \
+ do { \
+ length -= (bytes); \
+ pos += (bytes); \
+ } while (0)
+#define PEEK_DISCARD_UINT8 PEEK_DISCARD(1)
+#define PEEK_DISCARD_UINT16 PEEK_DISCARD(2)
+#define PEEK_DISCARD_UINT32 PEEK_DISCARD(4)
+#define PEEK_CMP(value, bytes) \
+ (length -= (bytes), pos += (bytes), memcmp(pos - bytes, value, bytes))
+#define PEEK_SAVE POKE_SAVE
+#define PEEK_RESTORE POKE_RESTORE
+
+/* LLDP specific. We need a `tlv' pointer. */
+#define POKE_START_LLDP_TLV(type) (tlv = pos, POKE_UINT16(type << 9))
+#define POKE_END_LLDP_TLV \
+ (memcpy(&types.f_uint16, tlv, sizeof(uint16_t)), \
+ types.f_uint16 |= htons((pos - (tlv + 2)) & 0x01ff), \
+ memcpy(tlv, &types.f_uint16, sizeof(uint16_t)), 1)
+
+/* Same for CDP */
+#define POKE_START_CDP_TLV(type) ((void)POKE_UINT16(type), tlv = pos, POKE_UINT16(0))
+#define POKE_END_CDP_TLV \
+ (types.f_uint16 = htons(pos - tlv + 2), \
+ memcpy(tlv, &types.f_uint16, sizeof(uint16_t)), 1)
+
+/* Same for EDP */
+#define POKE_START_EDP_TLV(type) \
+ ((void)POKE_UINT8(EDP_TLV_MARKER), (void)POKE_UINT8(type), tlv = pos, POKE_UINT16(0))
+#define POKE_END_EDP_TLV POKE_END_CDP_TLV
+
+#endif /* _FRAME_H */
diff --git a/src/daemon/interfaces-bpf.c b/src/daemon/interfaces-bpf.c
new file mode 100644
index 0000000..2091eaf
--- /dev/null
+++ b/src/daemon/interfaces-bpf.c
@@ -0,0 +1,117 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/*
+ * Copyright (c) 2013 Vincent Bernat <bernat@luffy.cx>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "lldpd.h"
+#include <unistd.h>
+#include <errno.h>
+#include <net/bpf.h>
+
+struct bpf_buffer {
+ size_t len; /* Total length of the buffer */
+ struct bpf_hdr data[0];
+};
+
+int
+ifbpf_phys_init(struct lldpd *cfg, struct lldpd_hardware *hardware)
+{
+ struct bpf_buffer *buffer = NULL;
+ int fd = -1;
+
+ log_debug("interfaces", "initialize ethernet device %s", hardware->h_ifname);
+ if ((fd = priv_iface_init(hardware->h_ifindex, hardware->h_ifname)) == -1)
+ return -1;
+
+ /* Allocate receive buffer */
+ hardware->h_data = buffer = malloc(ETHER_MAX_LEN +
+ BPF_WORDALIGN(sizeof(struct bpf_hdr)) + sizeof(struct bpf_buffer));
+ if (buffer == NULL) {
+ log_warn("interfaces", "unable to allocate buffer space for BPF on %s",
+ hardware->h_ifname);
+ goto end;
+ }
+ buffer->len = ETHER_MAX_LEN + BPF_WORDALIGN(sizeof(struct bpf_hdr));
+
+ /* Setup multicast */
+ interfaces_setup_multicast(cfg, hardware->h_ifname, 0);
+
+ hardware->h_sendfd = fd; /* Send */
+
+ levent_hardware_add_fd(hardware, fd); /* Receive */
+ log_debug("interfaces", "interface %s initialized (fd=%d)", hardware->h_ifname,
+ fd);
+ return 0;
+
+end:
+ if (fd >= 0) close(fd);
+ free(buffer);
+ hardware->h_data = NULL;
+ return -1;
+}
+
+/* Ethernet send/receive through BPF */
+static int
+ifbpf_eth_send(struct lldpd *cfg, struct lldpd_hardware *hardware, char *buffer,
+ size_t size)
+{
+ log_debug("interfaces", "send PDU to ethernet device %s (fd=%d)",
+ hardware->h_ifname, hardware->h_sendfd);
+ return write(hardware->h_sendfd, buffer, size);
+}
+
+static int
+ifbpf_eth_recv(struct lldpd *cfg, struct lldpd_hardware *hardware, int fd, char *buffer,
+ size_t size)
+{
+ struct bpf_buffer *bpfbuf = hardware->h_data;
+ struct bpf_hdr *bh;
+ log_debug("interfaces", "receive PDU from ethernet device %s",
+ hardware->h_ifname);
+
+ /* We assume we have only receive one packet (unbuffered mode). Dunno if
+ * this is correct. */
+ if (read(fd, bpfbuf->data, bpfbuf->len) == -1) {
+ if (errno == ENETDOWN) {
+ log_debug("interfaces",
+ "error while receiving frame on %s (network down)",
+ hardware->h_ifname);
+ } else {
+ log_warn("interfaces", "error while receiving frame on %s",
+ hardware->h_ifname);
+ hardware->h_rx_discarded_cnt++;
+ }
+ return -1;
+ }
+ bh = (struct bpf_hdr *)bpfbuf->data;
+ if (bh->bh_caplen < size) size = bh->bh_caplen;
+ memcpy(buffer, (char *)bpfbuf->data + bh->bh_hdrlen, size);
+
+ return size;
+}
+
+static int
+ifbpf_eth_close(struct lldpd *cfg, struct lldpd_hardware *hardware)
+{
+ log_debug("interfaces", "close ethernet device %s", hardware->h_ifname);
+ interfaces_setup_multicast(cfg, hardware->h_ifname, 1);
+ return 0;
+}
+
+struct lldpd_ops bpf_ops = {
+ .send = ifbpf_eth_send,
+ .recv = ifbpf_eth_recv,
+ .cleanup = ifbpf_eth_close,
+};
diff --git a/src/daemon/interfaces-bsd.c b/src/daemon/interfaces-bsd.c
new file mode 100644
index 0000000..a8248fe
--- /dev/null
+++ b/src/daemon/interfaces-bsd.c
@@ -0,0 +1,641 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/*
+ * Copyright (c) 2012 Vincent Bernat <bernat@luffy.cx>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "lldpd.h"
+
+#include <unistd.h>
+#include <ifaddrs.h>
+#include <errno.h>
+#include <ctype.h>
+#include <sys/param.h>
+#include <sys/sysctl.h>
+#include <sys/ioctl.h>
+#include <net/bpf.h>
+#include <net/if_types.h>
+#include <net/if_media.h>
+#include <net/if_dl.h>
+#if defined HOST_OS_FREEBSD
+# include <net/if_vlan_var.h>
+# include <net/if_bridgevar.h>
+# include <net/if_lagg.h>
+#elif defined HOST_OS_DRAGONFLY
+# include <net/vlan/if_vlan_var.h>
+# include <net/bridge/if_bridgevar.h>
+#elif defined HOST_OS_OPENBSD
+# include <net/if_vlan_var.h>
+# include <net/if_bridge.h>
+# include <net/if_trunk.h>
+#elif defined HOST_OS_NETBSD
+# include <net/if_vlanvar.h>
+# include <net/if_bridgevar.h>
+# include <net/agr/if_agrioctl.h>
+#elif defined HOST_OS_OSX
+# include <osx/if_vlan_var.h>
+# include <osx/if_bridgevar.h>
+# include <osx/if_bond_var.h>
+#endif
+
+#ifndef IFDESCRSIZE
+# define IFDESCRSIZE 64
+#endif
+
+static int
+ifbsd_check_wireless(struct lldpd *cfg, struct ifaddrs *ifaddr,
+ struct interfaces_device *iface)
+{
+ struct ifmediareq ifmr = {};
+ strlcpy(ifmr.ifm_name, iface->name, sizeof(ifmr.ifm_name));
+ if (ioctl(cfg->g_sock, SIOCGIFMEDIA, (caddr_t)&ifmr) < 0 ||
+ IFM_TYPE(ifmr.ifm_current) != IFM_IEEE80211)
+ return 0; /* Not wireless either */
+ iface->type |= IFACE_WIRELESS_T | IFACE_PHYSICAL_T;
+ return 0;
+}
+
+static void
+ifbsd_check_bridge(struct lldpd *cfg, struct interfaces_device_list *interfaces,
+ struct interfaces_device *master)
+{
+ static size_t ifbic_len = 64;
+ struct ifbreq *req = NULL;
+ struct ifbifconf bifc = {};
+
+retry_alloc:
+ if ((req = realloc(req, ifbic_len)) == NULL) {
+ log_warn("interfaces", "unable to allocate memory to query bridge %s",
+ master->name);
+ free(bifc.ifbic_req);
+ return;
+ }
+ bifc.ifbic_len = ifbic_len;
+ bifc.ifbic_req = req;
+
+#if defined HOST_OS_FREEBSD || defined HOST_OS_NETBSD || defined HOST_OS_OSX || \
+ defined HOST_OS_DRAGONFLY
+ struct ifdrv ifd = { .ifd_cmd = BRDGGIFS,
+ .ifd_len = sizeof(bifc),
+ .ifd_data = &bifc };
+
+ strlcpy(ifd.ifd_name, master->name, sizeof(ifd.ifd_name));
+ if (ioctl(cfg->g_sock, SIOCGDRVSPEC, (caddr_t)&ifd) < 0) {
+ log_debug("interfaces", "%s is not a bridge", master->name);
+ return;
+ }
+#elif defined HOST_OS_OPENBSD
+ strlcpy(bifc.ifbic_name, master->name, sizeof(bifc.ifbic_name));
+ if (ioctl(cfg->g_sock, SIOCBRDGIFS, (caddr_t)&bifc) < 0) {
+ log_debug("interfaces", "%s is not a bridge", master->name);
+ return;
+ }
+#else
+# error Unsupported OS
+#endif
+ if (bifc.ifbic_len >= ifbic_len) {
+ ifbic_len = bifc.ifbic_len + 1;
+ goto retry_alloc;
+ }
+ for (int i = 0; i < bifc.ifbic_len / sizeof(*req); i++) {
+ struct interfaces_device *slave =
+ interfaces_nametointerface(interfaces, req[i].ifbr_ifsname);
+ if (slave == NULL) {
+ log_warnx("interfaces",
+ "%s should be bridged to %s but we don't know %s",
+ req[i].ifbr_ifsname, master->name, req[i].ifbr_ifsname);
+ continue;
+ }
+ log_debug("interfaces", "%s is bridged to %s", slave->name,
+ master->name);
+ slave->upper = master;
+ }
+ master->type |= IFACE_BRIDGE_T;
+}
+
+static void
+ifbsd_check_bond(struct lldpd *cfg, struct interfaces_device_list *interfaces,
+ struct interfaces_device *master)
+{
+#if defined HOST_OS_OPENBSD
+/* OpenBSD is the same as FreeBSD, just lagg->trunk */
+# define lagg_reqport trunk_reqport
+# define lagg_reqall trunk_reqall
+# define SIOCGLAGG SIOCGTRUNK
+# define LAGG_MAX_PORTS TRUNK_MAX_PORTS
+#endif
+#if defined HOST_OS_OPENBSD || defined HOST_OS_FREEBSD
+ struct lagg_reqport rpbuf[LAGG_MAX_PORTS];
+ struct lagg_reqall ra = { .ra_size = sizeof(rpbuf), .ra_port = rpbuf };
+ strlcpy(ra.ra_ifname, master->name, IFNAMSIZ);
+ if (ioctl(cfg->g_sock, SIOCGLAGG, (caddr_t)&ra) < 0) {
+ log_debug("interfaces", "%s is not a bond", master->name);
+ return;
+ }
+
+ for (int i = 0; i < ra.ra_ports; i++) {
+ struct interfaces_device *slave;
+ slave = interfaces_nametointerface(interfaces, rpbuf[i].rp_portname);
+ if (slave == NULL) {
+ log_warnx("interfaces",
+ "%s should be enslaved to %s but we don't know %s",
+ rpbuf[i].rp_portname, master->name, rpbuf[i].rp_portname);
+ continue;
+ }
+ log_debug("interfaces", "%s is enslaved to bond %s", slave->name,
+ master->name);
+ slave->upper = master;
+ }
+ master->type |= IFACE_BOND_T;
+#elif defined HOST_OS_NETBSD
+ /* No max, we consider a maximum of 24 ports */
+ char buf[sizeof(struct agrportinfo) * 24] = {};
+ size_t buflen = sizeof(buf);
+ struct agrreq ar = { .ar_version = AGRREQ_VERSION,
+ .ar_cmd = AGRCMD_PORTLIST,
+ .ar_buf = buf,
+ .ar_buflen = buflen };
+ struct ifreq ifr = { .ifr_data = &ar };
+ struct agrportlist *apl = (void *)buf;
+ struct agrportinfo *api = (void *)(apl + 1);
+ strlcpy(ifr.ifr_name, master->name, sizeof(ifr.ifr_name));
+ if (ioctl(cfg->g_sock, SIOCGETAGR, &ifr) == -1) {
+ if (errno == E2BIG) {
+ log_warnx("interfaces",
+ "%s is a too big aggregate. Please, report the problem",
+ master->name);
+ } else {
+ log_debug("interfaces", "%s is not an aggregate", master->name);
+ }
+ return;
+ }
+ for (int i = 0; i < apl->apl_nports; i++, api++) {
+ struct interfaces_device *slave;
+ slave = interfaces_nametointerface(interfaces, api->api_ifname);
+ if (slave == NULL) {
+ log_warnx("interfaces",
+ "%s should be enslaved to %s but we don't know %s",
+ api->api_ifname, master->name, api->api_ifname);
+ continue;
+ }
+ log_debug("interfaces", "%s is enslaved to bond %s", slave->name,
+ master->name);
+ slave->upper = master;
+ }
+ master->type |= IFACE_BOND_T;
+#elif defined HOST_OS_OSX
+ struct if_bond_req ibr = { .ibr_op = IF_BOND_OP_GET_STATUS,
+ .ibr_ibru = {
+ .ibru_status = { .ibsr_version = IF_BOND_STATUS_REQ_VERSION } } };
+ struct ifreq ifr = { .ifr_data = (caddr_t)&ibr };
+ strlcpy(ifr.ifr_name, master->name, sizeof(ifr.ifr_name));
+ if (ioctl(cfg->g_sock, SIOCGIFBOND, (caddr_t)&ifr) < 0) {
+ log_debug("interfaces", "%s is not an aggregate", master->name);
+ return;
+ }
+ master->type |= IFACE_BOND_T;
+ if (ibr.ibr_ibru.ibru_status.ibsr_total == 0) {
+ log_debug("interfaces", "no members for bond %s", master->name);
+ return;
+ }
+
+ struct if_bond_status_req *ibsr_p = &ibr.ibr_ibru.ibru_status;
+ ibsr_p->ibsr_buffer =
+ malloc(sizeof(struct if_bond_status) * ibsr_p->ibsr_total);
+ if (ibsr_p->ibsr_buffer == NULL) {
+ log_warnx("interfaces", "not enough memory to check bond members");
+ return;
+ }
+ ibsr_p->ibsr_count = ibsr_p->ibsr_total;
+ if (ioctl(cfg->g_sock, SIOCGIFBOND, (caddr_t)&ifr) < 0) {
+ log_warn("interfaces", "unable to get members for bond %s",
+ master->name);
+ goto end;
+ }
+
+ struct if_bond_status *ibs_p = (struct if_bond_status *)ibsr_p->ibsr_buffer;
+ for (int i = 0; i < ibsr_p->ibsr_total; i++, ibs_p++) {
+ struct interfaces_device *slave;
+ slave = interfaces_nametointerface(interfaces, ibs_p->ibs_if_name);
+ if (slave == NULL) {
+ log_warnx("interfaces",
+ "%s should be enslaved to %s but we don't know %s",
+ ibs_p->ibs_if_name, master->name, ibs_p->ibs_if_name);
+ continue;
+ }
+ log_debug("interfaces", "%s is enslaved to bond %s", slave->name,
+ master->name);
+ slave->upper = master;
+ }
+end:
+ free(ibsr_p->ibsr_buffer);
+#elif defined HOST_OS_DRAGONFLY
+ log_debug("interfaces", "DragonFly BSD does not support link aggregation");
+#else
+# error Unsupported OS
+#endif
+}
+
+static void
+ifbsd_check_vlan(struct lldpd *cfg, struct interfaces_device_list *interfaces,
+ struct interfaces_device *vlan)
+{
+ struct interfaces_device *lower;
+ struct vlanreq vreq = {};
+ struct ifreq ifr = { .ifr_data = (caddr_t)&vreq };
+ strlcpy(ifr.ifr_name, vlan->name, sizeof(ifr.ifr_name));
+ if (ioctl(cfg->g_sock, SIOCGETVLAN, (caddr_t)&ifr) < 0) {
+ log_debug("interfaces", "%s is not a VLAN", vlan->name);
+ return;
+ }
+ if (strlen(vreq.vlr_parent) == 0) {
+ log_debug("interfaces", "%s is a VLAN but has no lower interface",
+ vlan->name);
+ vlan->lower = NULL;
+ vlan->type |= IFACE_VLAN_T;
+ return;
+ }
+ lower = interfaces_nametointerface(interfaces, vreq.vlr_parent);
+ if (lower == NULL) {
+ log_warnx("interfaces",
+ "%s should be a VLAN of %s but %s does not exist", vlan->name,
+ vreq.vlr_parent, vreq.vlr_parent);
+ return;
+ }
+ log_debug("interfaces", "%s is VLAN %d of %s", vlan->name, vreq.vlr_tag,
+ lower->name);
+ vlan->lower = lower;
+ bitmap_set(vlan->vlan_bmap, vreq.vlr_tag);
+ vlan->type |= IFACE_VLAN_T;
+}
+
+static void
+ifbsd_check_physical(struct lldpd *cfg, struct interfaces_device_list *interfaces,
+ struct interfaces_device *iface)
+{
+ if (iface->type &
+ (IFACE_VLAN_T | IFACE_BOND_T | IFACE_BRIDGE_T | IFACE_PHYSICAL_T))
+ return;
+
+ if (!(iface->flags & (IFF_MULTICAST | IFF_BROADCAST))) {
+ log_debug("interfaces",
+ "skip %s: not able to do multicast nor broadcast", iface->name);
+ return;
+ }
+ log_debug("interfaces", "%s is a physical interface", iface->name);
+ iface->type |= IFACE_PHYSICAL_T;
+}
+
+/* Remove any dangerous interface. Currently, only p2p0 is removed as it
+ * triggers some AirDrop functionality when we send something on it.
+ * See: https://github.com/lldpd/lldpd/issues/61
+ */
+static void
+ifbsd_denylist(struct lldpd *cfg, struct interfaces_device_list *interfaces)
+{
+#ifdef HOST_OS_OSX
+ struct interfaces_device *iface = NULL;
+ TAILQ_FOREACH (iface, interfaces, next) {
+ int i;
+ if (strncmp(iface->name, "p2p", 3)) continue;
+ if (strlen(iface->name) < 4) continue;
+ for (i = 3;
+ iface->name[i] != '\0' && isdigit((unsigned char)(iface->name[i]));
+ i++)
+ ;
+ if (iface->name[i] == '\0') {
+ log_debug("interfaces", "skip %s: AirDrop interface",
+ iface->name);
+ iface->ignore = 1;
+ }
+ }
+#endif
+}
+
+static struct interfaces_device *
+ifbsd_extract_device(struct lldpd *cfg, struct ifaddrs *ifaddr)
+{
+ struct interfaces_device *iface = NULL;
+ struct sockaddr_dl *saddrdl =
+ ALIGNED_CAST(struct sockaddr_dl *, ifaddr->ifa_addr);
+ if ((saddrdl->sdl_type != IFT_BRIDGE) && (saddrdl->sdl_type != IFT_L2VLAN) &&
+ (saddrdl->sdl_type != IFT_ETHER)) {
+ log_debug("interfaces", "skip %s: not an ethernet device (%d)",
+ ifaddr->ifa_name, saddrdl->sdl_type);
+ return NULL;
+ }
+ if ((iface = calloc(1, sizeof(struct interfaces_device))) == NULL) {
+ log_warn("interfaces", "unable to allocate memory for %s",
+ ifaddr->ifa_name);
+ return NULL;
+ }
+
+ iface->index = saddrdl->sdl_index;
+ iface->name = strdup(ifaddr->ifa_name);
+ iface->flags = ifaddr->ifa_flags;
+
+ /* MAC address */
+ iface->address = malloc(ETHER_ADDR_LEN);
+ if (iface->address) memcpy(iface->address, LLADDR(saddrdl), ETHER_ADDR_LEN);
+
+ /* Grab description */
+#ifdef SIOCGIFDESCR
+# if defined HOST_OS_FREEBSD || defined HOST_OS_OPENBSD
+ iface->alias = malloc(IFDESCRSIZE);
+ if (iface->alias) {
+# if defined HOST_OS_FREEBSD
+ struct ifreq ifr = { .ifr_buffer = { .buffer = iface->alias,
+ .length = IFDESCRSIZE } };
+# else
+ struct ifreq ifr = { .ifr_data = (caddr_t)iface->alias };
+# endif
+ strlcpy(ifr.ifr_name, ifaddr->ifa_name, sizeof(ifr.ifr_name));
+ if (ioctl(cfg->g_sock, SIOCGIFDESCR, (caddr_t)&ifr) < 0) {
+ free(iface->alias);
+ iface->alias = NULL;
+ }
+ }
+# endif
+#endif /* SIOCGIFDESCR */
+
+ if (ifbsd_check_wireless(cfg, ifaddr, iface) == -1) {
+ interfaces_free_device(iface);
+ return NULL;
+ }
+
+ return iface;
+}
+
+static void
+ifbsd_extract(struct lldpd *cfg, struct interfaces_device_list *interfaces,
+ struct interfaces_address_list *addresses, struct ifaddrs *ifaddr)
+{
+ struct interfaces_address *address = NULL;
+ struct interfaces_device *device = NULL;
+ if (!ifaddr->ifa_name) return;
+ if (!ifaddr->ifa_addr) return;
+ switch (ifaddr->ifa_addr->sa_family) {
+ case AF_LINK:
+ log_debug("interfaces", "grabbing information on interface %s",
+ ifaddr->ifa_name);
+ device = ifbsd_extract_device(cfg, ifaddr);
+ if (device) {
+#if defined HOST_OS_OPENBSD
+ /* On OpenBSD, the interface can have IFF_RUNNING but be down.
+ */
+ struct if_data *ifdata;
+ ifdata = ifaddr->ifa_data;
+ if (!LINK_STATE_IS_UP(ifdata->ifi_link_state))
+ device->flags &= ~IFF_RUNNING;
+#endif
+ TAILQ_INSERT_TAIL(interfaces, device, next);
+ }
+ break;
+ case AF_INET:
+ case AF_INET6:
+ log_debug("interfaces", "got an IP address on %s", ifaddr->ifa_name);
+ address = malloc(sizeof(struct interfaces_address));
+ if (address == NULL) {
+ log_warn("interfaces",
+ "not enough memory for a new IP address on %s",
+ ifaddr->ifa_name);
+ return;
+ }
+ address->flags = ifaddr->ifa_flags;
+ address->index = if_nametoindex(ifaddr->ifa_name);
+ memcpy(&address->address, ifaddr->ifa_addr,
+ (ifaddr->ifa_addr->sa_family == AF_INET) ?
+ sizeof(struct sockaddr_in) :
+ sizeof(struct sockaddr_in6));
+ TAILQ_INSERT_TAIL(addresses, address, next);
+ break;
+ default:
+ log_debug("interfaces", "unhandled family %d for interface %s",
+ ifaddr->ifa_addr->sa_family, ifaddr->ifa_name);
+ }
+}
+
+static void
+ifbsd_macphy(struct lldpd *cfg, struct lldpd_hardware *hardware)
+{
+#ifdef ENABLE_DOT3
+ struct ifmediareq ifmr = {};
+# ifdef HAVE_TYPEOF
+ typeof(ifmr.ifm_ulist[0]) media_list[32] = {};
+# else
+ int media_list[32] = {};
+# endif
+ ifmr.ifm_ulist = media_list;
+ ifmr.ifm_count = 32;
+ struct lldpd_port *port = &hardware->h_lport;
+ unsigned int duplex;
+ unsigned int media;
+ int advertised_ifmedia_to_rfc3636[][3] = {
+ { IFM_10_T, LLDP_DOT3_LINK_AUTONEG_10BASE_T,
+ LLDP_DOT3_LINK_AUTONEG_10BASET_FD },
+ { IFM_10_STP, LLDP_DOT3_LINK_AUTONEG_10BASE_T,
+ LLDP_DOT3_LINK_AUTONEG_10BASET_FD },
+ { IFM_100_TX, LLDP_DOT3_LINK_AUTONEG_100BASE_TX,
+ LLDP_DOT3_LINK_AUTONEG_100BASE_TXFD },
+ { IFM_100_T4, LLDP_DOT3_LINK_AUTONEG_100BASE_T4,
+ LLDP_DOT3_LINK_AUTONEG_100BASE_T4 },
+ { IFM_100_T2, LLDP_DOT3_LINK_AUTONEG_100BASE_T2,
+ LLDP_DOT3_LINK_AUTONEG_100BASE_T2FD },
+ { IFM_1000_SX, LLDP_DOT3_LINK_AUTONEG_1000BASE_X,
+ LLDP_DOT3_LINK_AUTONEG_1000BASE_XFD },
+ { IFM_1000_LX, LLDP_DOT3_LINK_AUTONEG_1000BASE_X,
+ LLDP_DOT3_LINK_AUTONEG_1000BASE_XFD },
+ { IFM_1000_CX, LLDP_DOT3_LINK_AUTONEG_1000BASE_X,
+ LLDP_DOT3_LINK_AUTONEG_1000BASE_XFD },
+ { IFM_1000_T, LLDP_DOT3_LINK_AUTONEG_1000BASE_T,
+ LLDP_DOT3_LINK_AUTONEG_1000BASE_TFD },
+ { 0, 0, 0 }
+ };
+ int current_ifmedia_to_rfc3636[][3] = { { IFM_10_T, LLDP_DOT3_MAU_10BASETHD,
+ LLDP_DOT3_MAU_10BASETFD },
+ { IFM_10_STP, LLDP_DOT3_MAU_10BASETHD, LLDP_DOT3_MAU_10BASETFD },
+ { IFM_10_2, LLDP_DOT3_MAU_10BASE2, LLDP_DOT3_MAU_10BASE2 },
+ { IFM_10_5, LLDP_DOT3_MAU_10BASE5, LLDP_DOT3_MAU_10BASE5 },
+ { IFM_100_TX, LLDP_DOT3_MAU_100BASETXHD, LLDP_DOT3_MAU_100BASETXFD },
+ { IFM_100_FX, LLDP_DOT3_MAU_100BASEFXHD, LLDP_DOT3_MAU_100BASEFXFD },
+ { IFM_100_T2, LLDP_DOT3_MAU_100BASET2HD, LLDP_DOT3_MAU_100BASET2FD },
+ { IFM_1000_SX, LLDP_DOT3_MAU_1000BASESXHD, LLDP_DOT3_MAU_1000BASESXFD },
+ { IFM_10_FL, LLDP_DOT3_MAU_10BASEFLHD, LLDP_DOT3_MAU_10BASEFLFD },
+ { IFM_1000_LX, LLDP_DOT3_MAU_1000BASELXHD, LLDP_DOT3_MAU_1000BASELXFD },
+ { IFM_1000_CX, LLDP_DOT3_MAU_1000BASECXHD, LLDP_DOT3_MAU_1000BASECXFD },
+ { IFM_1000_T, LLDP_DOT3_MAU_1000BASETHD, LLDP_DOT3_MAU_1000BASETFD },
+ { IFM_10G_LR, LLDP_DOT3_MAU_10GIGBASELR, LLDP_DOT3_MAU_10GIGBASELR },
+ { IFM_10G_SR, LLDP_DOT3_MAU_10GIGBASESR, LLDP_DOT3_MAU_10GIGBASESR },
+ { IFM_10G_CX4, LLDP_DOT3_MAU_10GIGBASELX4, LLDP_DOT3_MAU_10GIGBASELX4 },
+# ifdef IFM_10G_T
+ { IFM_10G_T, LLDP_DOT3_MAU_10GIGBASECX4, LLDP_DOT3_MAU_10GIGBASECX4 },
+# endif
+# ifdef IFM_10G_TWINAX
+ { IFM_10G_TWINAX, LLDP_DOT3_MAU_10GIGBASECX4,
+ LLDP_DOT3_MAU_10GIGBASECX4 },
+# endif
+# ifdef IFM_10G_TWINAX_LONG
+ { IFM_10G_TWINAX_LONG, LLDP_DOT3_MAU_10GIGBASECX4,
+ LLDP_DOT3_MAU_10GIGBASECX4 },
+# endif
+# ifdef IFM_10G_LRM
+ { IFM_10G_LRM, LLDP_DOT3_MAU_10GIGBASELR, LLDP_DOT3_MAU_10GIGBASELR },
+# endif
+# ifdef IFM_10G_SFP_CU
+ { IFM_10G_SFP_CU, LLDP_DOT3_MAU_10GIGBASECX4,
+ LLDP_DOT3_MAU_10GIGBASECX4 },
+# endif
+ { 0, 0, 0 } };
+
+ log_debug("interfaces", "get MAC/phy for %s", hardware->h_ifname);
+ strlcpy(ifmr.ifm_name, hardware->h_ifname, sizeof(ifmr.ifm_name));
+ if (ioctl(cfg->g_sock, SIOCGIFMEDIA, (caddr_t)&ifmr) < 0) {
+ log_debug("interfaces", "unable to get media information from %s",
+ hardware->h_ifname);
+ return;
+ }
+ if (IFM_TYPE(ifmr.ifm_current) != IFM_ETHER) {
+ log_warnx("interfaces",
+ "cannot get media information from %s: not an ethernet device",
+ hardware->h_ifname);
+ return;
+ }
+ if ((ifmr.ifm_status & IFM_ACTIVE) == 0) {
+ log_debug("interfaces", "interface %s is now down, skip",
+ hardware->h_ifname);
+ return;
+ }
+ if (ifmr.ifm_count == 0) {
+ log_warnx("interfaces", "no media information available on %s",
+ hardware->h_ifname);
+ return;
+ }
+ port->p_macphy.autoneg_support = port->p_macphy.autoneg_enabled = 0;
+ for (int m = 0; m < ifmr.ifm_count; m++) {
+ media = IFM_SUBTYPE(ifmr.ifm_ulist[m]);
+ duplex = !!(IFM_OPTIONS(ifmr.ifm_ulist[m]) & IFM_FDX);
+ if (media == IFM_AUTO) {
+ port->p_macphy.autoneg_support = 1;
+ port->p_macphy.autoneg_enabled =
+ (IFM_SUBTYPE(ifmr.ifm_current) == IFM_AUTO);
+ continue;
+ }
+
+ int found = 0;
+ for (int j = 0; advertised_ifmedia_to_rfc3636[j][0]; j++) {
+ if (advertised_ifmedia_to_rfc3636[j][0] == media) {
+ port->p_macphy.autoneg_advertised |=
+ advertised_ifmedia_to_rfc3636[j][1 + duplex];
+ found = 1;
+ break;
+ }
+ }
+ if (!found)
+ port->p_macphy.autoneg_advertised |=
+ LLDP_DOT3_LINK_AUTONEG_OTHER;
+ }
+
+ port->p_macphy.mau_type = 0;
+ media = IFM_SUBTYPE(ifmr.ifm_active);
+ duplex = !!(IFM_OPTIONS(ifmr.ifm_active) & IFM_FDX);
+ for (int j = 0; current_ifmedia_to_rfc3636[j][0]; j++) {
+ if (current_ifmedia_to_rfc3636[j][0] == media) {
+ port->p_macphy.mau_type =
+ current_ifmedia_to_rfc3636[j][1 + duplex];
+ break;
+ }
+ }
+#endif
+}
+
+extern struct lldpd_ops bpf_ops;
+void
+interfaces_update(struct lldpd *cfg)
+{
+ struct lldpd_hardware *hardware;
+ struct interfaces_device *iface;
+ struct interfaces_device_list *interfaces;
+ struct interfaces_address_list *addresses;
+ struct ifaddrs *ifaddrs = NULL, *ifaddr;
+
+ interfaces = malloc(sizeof(struct interfaces_device_list));
+ addresses = malloc(sizeof(struct interfaces_address_list));
+ if (interfaces == NULL || addresses == NULL) {
+ log_warnx("interfaces", "unable to allocate memory");
+ goto end;
+ }
+ TAILQ_INIT(interfaces);
+ TAILQ_INIT(addresses);
+ if (getifaddrs(&ifaddrs) < 0) {
+ log_warnx("interfaces", "unable to get list of interfaces");
+ goto end;
+ }
+
+ for (ifaddr = ifaddrs; ifaddr != NULL; ifaddr = ifaddr->ifa_next) {
+ ifbsd_extract(cfg, interfaces, addresses, ifaddr);
+ }
+ /* Link interfaces together if needed */
+ TAILQ_FOREACH (iface, interfaces, next) {
+ ifbsd_check_bridge(cfg, interfaces, iface);
+ ifbsd_check_bond(cfg, interfaces, iface);
+ ifbsd_check_vlan(cfg, interfaces, iface);
+ ifbsd_check_physical(cfg, interfaces, iface);
+ }
+
+ ifbsd_denylist(cfg, interfaces);
+ interfaces_helper_allowlist(cfg, interfaces);
+ interfaces_helper_physical(cfg, interfaces, &bpf_ops, ifbpf_phys_init);
+#ifdef ENABLE_DOT1
+ interfaces_helper_vlan(cfg, interfaces);
+#endif
+ interfaces_helper_mgmt(cfg, addresses, interfaces);
+ interfaces_helper_chassis(cfg, interfaces);
+
+ /* Mac/PHY */
+ TAILQ_FOREACH (hardware, &cfg->g_hardware, h_entries) {
+ if (!hardware->h_flags) continue;
+ ifbsd_macphy(cfg, hardware);
+ interfaces_helper_promisc(cfg, hardware);
+ }
+
+ if (cfg->g_iface_event == NULL) {
+ int s;
+ log_debug("interfaces", "subscribe to route socket notifications");
+ if ((s = socket(PF_ROUTE, SOCK_RAW, 0)) < 0) {
+ log_warn("interfaces", "unable to open route socket");
+ goto end;
+ }
+
+#ifdef ROUTE_MSGFILTER
+ unsigned int rtfilter;
+ rtfilter = ROUTE_FILTER(RTM_IFINFO);
+ if (setsockopt(s, PF_ROUTE, ROUTE_MSGFILTER, &rtfilter,
+ sizeof(rtfilter)) == -1)
+ log_warn("interfaces",
+ "unable to set filter for interface updates");
+#endif
+
+ if (levent_iface_subscribe(cfg, s) == -1) close(s);
+ }
+
+end:
+ interfaces_free_devices(interfaces);
+ interfaces_free_addresses(addresses);
+ if (ifaddrs) freeifaddrs(ifaddrs);
+}
+
+void
+interfaces_cleanup(struct lldpd *cfg)
+{
+}
diff --git a/src/daemon/interfaces-linux.c b/src/daemon/interfaces-linux.c
new file mode 100644
index 0000000..e764943
--- /dev/null
+++ b/src/daemon/interfaces-linux.c
@@ -0,0 +1,1046 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/*
+ * Copyright (c) 2008 Vincent Bernat <bernat@luffy.cx>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "lldpd.h"
+
+#include <stdio.h>
+#include <unistd.h>
+#include <inttypes.h>
+#include <errno.h>
+#include <sys/ioctl.h>
+#if defined(__clang__)
+# pragma clang diagnostic push
+# pragma clang diagnostic ignored "-Wdocumentation"
+#endif
+#include <netinet/in.h>
+#include <linux/if_vlan.h>
+#include <linux/if_bonding.h>
+#include <linux/if_bridge.h>
+#include <linux/wireless.h>
+#include <linux/sockios.h>
+#include <linux/if_packet.h>
+#include <linux/ethtool.h>
+#if defined(__clang__)
+# pragma clang diagnostic pop
+#endif
+
+#define SYSFS_PATH_MAX 256
+#define MAX_PORTS 1024
+#define MAX_BRIDGES 1024
+
+static int
+iflinux_eth_init(struct lldpd *cfg, struct lldpd_hardware *hardware)
+{
+ int fd;
+
+ log_debug("interfaces", "initialize ethernet device %s", hardware->h_ifname);
+ if ((fd = priv_iface_init(hardware->h_ifindex, hardware->h_ifname)) == -1)
+ return -1;
+ hardware->h_sendfd = fd; /* Send */
+
+ interfaces_setup_multicast(cfg, hardware->h_ifname, 0);
+
+ levent_hardware_add_fd(hardware, fd); /* Receive */
+ log_debug("interfaces", "interface %s initialized (fd=%d)", hardware->h_ifname,
+ fd);
+ return 0;
+}
+
+/* Generic ethernet send/receive */
+static int
+iflinux_eth_send(struct lldpd *cfg, struct lldpd_hardware *hardware, char *buffer,
+ size_t size)
+{
+ log_debug("interfaces", "send PDU to ethernet device %s (fd=%d)",
+ hardware->h_ifname, hardware->h_sendfd);
+ return write(hardware->h_sendfd, buffer, size);
+}
+
+static int
+iflinux_generic_recv(struct lldpd_hardware *hardware, int fd, char *buffer, size_t size,
+ struct sockaddr_ll *from)
+{
+ int n, retry = 0;
+ socklen_t fromlen;
+
+retry:
+ fromlen = sizeof(*from);
+ memset(from, 0, fromlen);
+ if ((n = recvfrom(fd, buffer, size, 0, (struct sockaddr *)from, &fromlen)) ==
+ -1) {
+ if (errno == EAGAIN && retry == 0) {
+ /* There may be an error queued in the socket. Clear it and
+ * retry. */
+ levent_recv_error(fd, hardware->h_ifname);
+ retry++;
+ goto retry;
+ }
+ if (errno == ENETDOWN) {
+ log_debug("interfaces",
+ "error while receiving frame on %s (network down)",
+ hardware->h_ifname);
+ } else {
+ log_warn("interfaces",
+ "error while receiving frame on %s (retry: %d)",
+ hardware->h_ifname, retry);
+ hardware->h_rx_discarded_cnt++;
+ }
+ return -1;
+ }
+ if (from->sll_pkttype == PACKET_OUTGOING) return -1;
+ return n;
+}
+
+static int
+iflinux_eth_recv(struct lldpd *cfg, struct lldpd_hardware *hardware, int fd,
+ char *buffer, size_t size)
+{
+ int n;
+ struct sockaddr_ll from;
+
+ log_debug("interfaces", "receive PDU from ethernet device %s",
+ hardware->h_ifname);
+ if ((n = iflinux_generic_recv(hardware, fd, buffer, size, &from)) == -1)
+ return -1;
+ return n;
+}
+
+static int
+iflinux_eth_close(struct lldpd *cfg, struct lldpd_hardware *hardware)
+{
+ log_debug("interfaces", "close ethernet device %s", hardware->h_ifname);
+ interfaces_setup_multicast(cfg, hardware->h_ifname, 1);
+ return 0;
+}
+
+static struct lldpd_ops eth_ops = {
+ .send = iflinux_eth_send,
+ .recv = iflinux_eth_recv,
+ .cleanup = iflinux_eth_close,
+};
+
+static int
+iflinux_is_bridge(struct lldpd *cfg, struct interfaces_device_list *interfaces,
+ struct interfaces_device *iface)
+{
+#ifdef ENABLE_OLDIES
+ struct interfaces_device *port;
+ char path[SYSFS_PATH_MAX];
+ int f;
+
+ if ((snprintf(path, SYSFS_PATH_MAX, SYSFS_CLASS_NET "%s/" SYSFS_BRIDGE_FDB,
+ iface->name)) >= SYSFS_PATH_MAX)
+ log_warnx("interfaces", "path truncated");
+ if ((f = priv_open(path)) < 0) return 0;
+ close(f);
+
+ /* Also grab all ports */
+ TAILQ_FOREACH (port, interfaces, next) {
+ if (port->upper) continue;
+ if (snprintf(path, SYSFS_PATH_MAX,
+ SYSFS_CLASS_NET "%s/" SYSFS_BRIDGE_PORT_SUBDIR "/%s/port_no",
+ iface->name, port->name) >= SYSFS_PATH_MAX)
+ log_warnx("interfaces", "path truncated");
+ if ((f = priv_open(path)) < 0) continue;
+ log_debug("interfaces", "port %s is bridged to %s", port->name,
+ iface->name);
+ port->upper = iface;
+ close(f);
+ }
+
+ return 1;
+#else
+ return 0;
+#endif
+}
+
+static int
+iflinux_is_vlan(struct lldpd *cfg, struct interfaces_device_list *interfaces,
+ struct interfaces_device *iface)
+{
+#ifdef ENABLE_OLDIES
+ struct vlan_ioctl_args ifv = {};
+ ifv.cmd = GET_VLAN_REALDEV_NAME_CMD;
+ strlcpy(ifv.device1, iface->name, sizeof(ifv.device1));
+ if (ioctl(cfg->g_sock, SIOCGIFVLAN, &ifv) >= 0) {
+ /* This is a VLAN, get the lower interface and the VID */
+ struct interfaces_device *lower =
+ interfaces_nametointerface(interfaces, ifv.u.device2);
+ if (!lower) {
+ log_debug("interfaces",
+ "unable to find lower interface for VLAN %s", iface->name);
+ return 0;
+ }
+
+ memset(&ifv, 0, sizeof(ifv));
+ ifv.cmd = GET_VLAN_VID_CMD;
+ strlcpy(ifv.device1, iface->name, sizeof(ifv.device1));
+ if (ioctl(cfg->g_sock, SIOCGIFVLAN, &ifv) < 0) {
+ log_debug("interfaces", "unable to find VID for VLAN %s",
+ iface->name);
+ return 0;
+ }
+
+ iface->lower = lower;
+ bitmap_set(iface->vlan_bmap, ifv.u.VID);
+ return 1;
+ }
+#endif
+ return 0;
+}
+
+static int
+iflinux_is_bond(struct lldpd *cfg, struct interfaces_device_list *interfaces,
+ struct interfaces_device *master)
+{
+#ifdef ENABLE_OLDIES
+ /* Shortcut if we detect the new team driver. Upper and lower links
+ * should already be set with netlink in this case. */
+ if (master->driver && !strcmp(master->driver, "team")) {
+ return 1;
+ }
+
+ struct ifreq ifr = {};
+ struct ifbond ifb = {};
+ strlcpy(ifr.ifr_name, master->name, sizeof(ifr.ifr_name));
+ ifr.ifr_data = (char *)&ifb;
+ if (ioctl(cfg->g_sock, SIOCBONDINFOQUERY, &ifr) >= 0) {
+ while (ifb.num_slaves--) {
+ struct ifslave ifs;
+ memset(&ifr, 0, sizeof(ifr));
+ memset(&ifs, 0, sizeof(ifs));
+ strlcpy(ifr.ifr_name, master->name, sizeof(ifr.ifr_name));
+ ifr.ifr_data = (char *)&ifs;
+ ifs.slave_id = ifb.num_slaves;
+ if (ioctl(cfg->g_sock, SIOCBONDSLAVEINFOQUERY, &ifr) >= 0) {
+ struct interfaces_device *slave =
+ interfaces_nametointerface(interfaces,
+ ifs.slave_name);
+ if (slave == NULL) continue;
+ if (slave->upper) continue;
+ log_debug("interfaces",
+ "interface %s is enslaved to %s", slave->name,
+ master->name);
+ slave->upper = master;
+ }
+ }
+ return 1;
+ }
+#endif
+ return 0;
+}
+
+/**
+ * Get permanent MAC from ethtool.
+ *
+ * Return 0 on success, -1 on error.
+ */
+static int
+iflinux_get_permanent_mac_ethtool(struct lldpd *cfg,
+ struct interfaces_device_list *interfaces, struct interfaces_device *iface)
+{
+ int ret = -1;
+ struct ifreq ifr = {};
+ struct ethtool_perm_addr *epaddr =
+ calloc(sizeof(struct ethtool_perm_addr) + ETHER_ADDR_LEN, 1);
+ if (epaddr == NULL) goto end;
+
+ strlcpy(ifr.ifr_name, iface->name, sizeof(ifr.ifr_name));
+ epaddr->cmd = ETHTOOL_GPERMADDR;
+ epaddr->size = ETHER_ADDR_LEN;
+ ifr.ifr_data = (caddr_t)epaddr;
+ if (ioctl(cfg->g_sock, SIOCETHTOOL, &ifr) == -1) {
+ static int once = 0;
+ if (errno == EPERM && !once) {
+ log_warnx("interfaces",
+ "no permission to get permanent MAC address for %s (requires 2.6.19+)",
+ iface->name);
+ once = 1;
+ goto end;
+ }
+ if (errno != EPERM)
+ log_warn("interfaces",
+ "cannot get permanent MAC address for %s", iface->name);
+ goto end;
+ }
+ if (epaddr->data[0] != 0 || epaddr->data[1] != 0 || epaddr->data[2] != 0 ||
+ epaddr->data[3] != 0 || epaddr->data[4] != 0 || epaddr->data[5] != 0) {
+ memcpy(iface->address, epaddr->data, ETHER_ADDR_LEN);
+ ret = 0;
+ goto end;
+ }
+ log_debug("interfaces", "cannot get permanent MAC for %s (all 0)", iface->name);
+end:
+ free(epaddr);
+ return ret;
+}
+
+/**
+ * Get permanent MAC address for a bond device.
+ */
+static void
+iflinux_get_permanent_mac_bond(struct lldpd *cfg,
+ struct interfaces_device_list *interfaces, struct interfaces_device *iface)
+{
+ struct interfaces_device *master = iface->upper;
+ int f, state = 0;
+ FILE *netbond;
+ const char *slaveif = "Slave Interface: ";
+ const char *hwaddr = "Permanent HW addr: ";
+ u_int8_t mac[ETHER_ADDR_LEN];
+ char path[SYSFS_PATH_MAX];
+ char line[100];
+
+ /* We have a bond, we need to query it to get real MAC addresses */
+ if (snprintf(path, SYSFS_PATH_MAX, "/proc/net/bonding/%s", master->name) >=
+ SYSFS_PATH_MAX) {
+ log_warnx("interfaces", "path truncated");
+ return;
+ }
+ if ((f = priv_open(path)) < 0) {
+ if (snprintf(path, SYSFS_PATH_MAX, "/proc/self/net/bonding/%s",
+ master->name) >= SYSFS_PATH_MAX) {
+ log_warnx("interfaces", "path truncated");
+ return;
+ }
+ f = priv_open(path);
+ }
+ if (f < 0) {
+ log_warnx("interfaces", "unable to get permanent MAC address for %s",
+ iface->name);
+ return;
+ }
+ if ((netbond = fdopen(f, "r")) == NULL) {
+ log_warn("interfaces", "unable to read stream from %s", path);
+ close(f);
+ return;
+ }
+ /* State 0:
+ We parse the file to search "Slave Interface: ". If found, go to
+ state 1.
+ State 1:
+ We parse the file to search "Permanent HW addr: ". If found, we get
+ the mac.
+ */
+ while (fgets(line, sizeof(line), netbond)) {
+ switch (state) {
+ case 0:
+ if (strncmp(line, slaveif, strlen(slaveif)) == 0) {
+ if (line[strlen(line) - 1] == '\n')
+ line[strlen(line) - 1] = '\0';
+ if (strcmp(iface->name, line + strlen(slaveif)) == 0)
+ state++;
+ }
+ break;
+ case 1:
+ if (strncmp(line, hwaddr, strlen(hwaddr)) == 0) {
+ if (line[strlen(line) - 1] == '\n')
+ line[strlen(line) - 1] = '\0';
+ if (sscanf(line + strlen(hwaddr),
+ "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx",
+ &mac[0], &mac[1], &mac[2], &mac[3], &mac[4],
+ &mac[5]) != ETHER_ADDR_LEN) {
+ log_warn("interfaces", "unable to parse %s",
+ line + strlen(hwaddr));
+ fclose(netbond);
+ return;
+ }
+ memcpy(iface->address, mac, ETHER_ADDR_LEN);
+ fclose(netbond);
+ return;
+ }
+ break;
+ }
+ }
+ log_warnx("interfaces", "unable to find real MAC address for enslaved %s",
+ iface->name);
+ fclose(netbond);
+}
+
+/**
+ * Get permanent MAC.
+ */
+static void
+iflinux_get_permanent_mac(struct lldpd *cfg, struct interfaces_device_list *interfaces,
+ struct interfaces_device *iface)
+{
+ struct interfaces_device *master = iface->upper;
+
+ if (master == NULL || master->type != IFACE_BOND_T) return;
+ if (iflinux_get_permanent_mac_ethtool(cfg, interfaces, iface) == -1 &&
+ (master->driver == NULL || !strcmp(master->driver, "bonding")))
+ /* Fallback to old method for a bond */
+ iflinux_get_permanent_mac_bond(cfg, interfaces, iface);
+}
+
+#ifdef ENABLE_DOT3
+# define ETHTOOL_LINK_MODE_MASK_MAX_KERNEL_NU32 (SCHAR_MAX)
+# define ETHTOOL_DECLARE_LINK_MODE_MASK(name) \
+ uint32_t name[ETHTOOL_LINK_MODE_MASK_MAX_KERNEL_NU32]
+
+struct ethtool_link_usettings {
+ struct ethtool_link_settings base;
+ struct {
+ ETHTOOL_DECLARE_LINK_MODE_MASK(supported);
+ ETHTOOL_DECLARE_LINK_MODE_MASK(advertising);
+ ETHTOOL_DECLARE_LINK_MODE_MASK(lp_advertising);
+ } link_modes;
+};
+
+static int
+iflinux_ethtool_link_mode_test_bit(unsigned int nr, const uint32_t *mask)
+{
+ if (nr >= 32 * ETHTOOL_LINK_MODE_MASK_MAX_KERNEL_NU32) return 0;
+ return !!(mask[nr / 32] & (1 << (nr % 32)));
+}
+static void
+iflinux_ethtool_link_mode_unset_bit(unsigned int nr, uint32_t *mask)
+{
+ if (nr >= 32 * ETHTOOL_LINK_MODE_MASK_MAX_KERNEL_NU32) return;
+ mask[nr / 32] &= ~(1 << (nr % 32));
+}
+static int
+iflinux_ethtool_link_mode_is_empty(const uint32_t *mask)
+{
+ for (unsigned int i = 0; i < ETHTOOL_LINK_MODE_MASK_MAX_KERNEL_NU32; ++i) {
+ if (mask[i] != 0) return 0;
+ }
+
+ return 1;
+}
+
+static int
+iflinux_ethtool_glink(struct lldpd *cfg, const char *ifname,
+ struct ethtool_link_usettings *uset)
+{
+ int rc;
+
+ /* Try with ETHTOOL_GLINKSETTINGS first */
+ struct {
+ struct ethtool_link_settings req;
+ uint32_t link_mode_data[3 * ETHTOOL_LINK_MODE_MASK_MAX_KERNEL_NU32];
+ } ecmd;
+ static int8_t nwords = 0;
+ struct ifreq ifr = {};
+ strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
+
+ if (nwords == 0) {
+ /* Do a handshake first. We assume that this is device-independant. */
+ memset(&ecmd, 0, sizeof(ecmd));
+ ecmd.req.cmd = ETHTOOL_GLINKSETTINGS;
+ ifr.ifr_data = (caddr_t)&ecmd;
+ rc = ioctl(cfg->g_sock, SIOCETHTOOL, &ifr);
+ if (rc == 0) {
+ nwords = -ecmd.req.link_mode_masks_nwords;
+ log_debug("interfaces", "glinksettings nwords is %" PRId8,
+ nwords);
+ } else {
+ static int once = 0;
+ if (errno == EPERM && !once) {
+ log_warnx("interfaces",
+ "cannot get ethtool link information "
+ "with GLINKSETTINGS (requires 4.9+). "
+ "25G+ speeds may be missing in MAC/PHY TLVs");
+ once = 1;
+ }
+ nwords = -1;
+ }
+ }
+ if (nwords > 0) {
+ memset(&ecmd, 0, sizeof(ecmd));
+ ecmd.req.cmd = ETHTOOL_GLINKSETTINGS;
+ ecmd.req.link_mode_masks_nwords = nwords;
+ ifr.ifr_data = (caddr_t)&ecmd;
+ rc = ioctl(cfg->g_sock, SIOCETHTOOL, &ifr);
+ if (rc == 0) {
+ log_debug("interfaces",
+ "got ethtool results for %s with GLINKSETTINGS", ifname);
+ memcpy(&uset->base, &ecmd.req, sizeof(uset->base));
+ unsigned int u32_offs = 0;
+ memcpy(uset->link_modes.supported,
+ &ecmd.link_mode_data[u32_offs],
+ 4 * ecmd.req.link_mode_masks_nwords);
+ u32_offs += ecmd.req.link_mode_masks_nwords;
+ memcpy(uset->link_modes.advertising,
+ &ecmd.link_mode_data[u32_offs],
+ 4 * ecmd.req.link_mode_masks_nwords);
+ u32_offs += ecmd.req.link_mode_masks_nwords;
+ memcpy(uset->link_modes.lp_advertising,
+ &ecmd.link_mode_data[u32_offs],
+ 4 * ecmd.req.link_mode_masks_nwords);
+ goto end;
+ }
+ }
+
+ /* Try with ETHTOOL_GSET */
+ struct ethtool_cmd ethc;
+ memset(&ethc, 0, sizeof(ethc));
+ ethc.cmd = ETHTOOL_GSET;
+ ifr.ifr_data = (caddr_t)&ethc;
+ rc = ioctl(cfg->g_sock, SIOCETHTOOL, &ifr);
+ if (rc == 0) {
+ /* Do a partial copy (only what we need) */
+ log_debug("interfaces", "got ethtool results for %s with GSET", ifname);
+ memset(uset, 0, sizeof(*uset));
+ uset->base.cmd = ETHTOOL_GSET;
+ uset->base.link_mode_masks_nwords = 1;
+ uset->link_modes.supported[0] = ethc.supported;
+ uset->link_modes.advertising[0] = ethc.advertising;
+ uset->link_modes.lp_advertising[0] = ethc.lp_advertising;
+ uset->base.speed = (ethc.speed_hi << 16) | ethc.speed;
+ uset->base.duplex = ethc.duplex;
+ uset->base.port = ethc.port;
+ uset->base.autoneg = ethc.autoneg;
+ } else {
+ static int once = 0;
+ if (errno == EPERM && !once) {
+ log_warnx("interfaces",
+ "cannot get ethtool link information "
+ "with GSET (requires 2.6.19+). "
+ "MAC/PHY TLV will be unavailable");
+ once = 1;
+ }
+ }
+end:
+ return rc;
+}
+
+/* Fill up MAC/PHY for a given hardware port */
+static void
+iflinux_macphy(struct lldpd *cfg, struct lldpd_hardware *hardware)
+{
+ struct ethtool_link_usettings uset = {};
+ struct lldpd_port *port = &hardware->h_lport;
+ int j;
+ int advertised_ethtool_to_rfc3636[][2] = {
+ { ETHTOOL_LINK_MODE_10baseT_Half_BIT, LLDP_DOT3_LINK_AUTONEG_10BASE_T },
+ { ETHTOOL_LINK_MODE_10baseT_Full_BIT,
+ LLDP_DOT3_LINK_AUTONEG_10BASET_FD },
+ { ETHTOOL_LINK_MODE_100baseT_Half_BIT,
+ LLDP_DOT3_LINK_AUTONEG_100BASE_TX },
+ { ETHTOOL_LINK_MODE_100baseT_Full_BIT,
+ LLDP_DOT3_LINK_AUTONEG_100BASE_TXFD },
+ { ETHTOOL_LINK_MODE_1000baseT_Half_BIT,
+ LLDP_DOT3_LINK_AUTONEG_1000BASE_T },
+ { ETHTOOL_LINK_MODE_1000baseT_Full_BIT,
+ LLDP_DOT3_LINK_AUTONEG_1000BASE_TFD },
+ { ETHTOOL_LINK_MODE_1000baseKX_Full_BIT,
+ LLDP_DOT3_LINK_AUTONEG_1000BASE_XFD },
+ { ETHTOOL_LINK_MODE_Pause_BIT, LLDP_DOT3_LINK_AUTONEG_FDX_PAUSE },
+ { ETHTOOL_LINK_MODE_Asym_Pause_BIT, LLDP_DOT3_LINK_AUTONEG_FDX_APAUSE },
+ { -1, 0 }
+ };
+
+ log_debug("interfaces", "ask ethtool for the appropriate MAC/PHY for %s",
+ hardware->h_ifname);
+ if (iflinux_ethtool_glink(cfg, hardware->h_ifname, &uset) == 0) {
+ port->p_macphy.autoneg_support =
+ iflinux_ethtool_link_mode_test_bit(ETHTOOL_LINK_MODE_Autoneg_BIT,
+ uset.link_modes.supported);
+ port->p_macphy.autoneg_enabled =
+ (uset.base.autoneg == AUTONEG_DISABLE) ? 0 : 1;
+ for (j = 0; advertised_ethtool_to_rfc3636[j][0] >= 0; j++) {
+ if (iflinux_ethtool_link_mode_test_bit(
+ advertised_ethtool_to_rfc3636[j][0],
+ uset.link_modes.advertising)) {
+ port->p_macphy.autoneg_advertised |=
+ advertised_ethtool_to_rfc3636[j][1];
+ iflinux_ethtool_link_mode_unset_bit(
+ advertised_ethtool_to_rfc3636[j][0],
+ uset.link_modes.advertising);
+ }
+ }
+ iflinux_ethtool_link_mode_unset_bit(ETHTOOL_LINK_MODE_Autoneg_BIT,
+ uset.link_modes.advertising);
+ iflinux_ethtool_link_mode_unset_bit(ETHTOOL_LINK_MODE_TP_BIT,
+ uset.link_modes.advertising);
+ iflinux_ethtool_link_mode_unset_bit(ETHTOOL_LINK_MODE_AUI_BIT,
+ uset.link_modes.advertising);
+ iflinux_ethtool_link_mode_unset_bit(ETHTOOL_LINK_MODE_MII_BIT,
+ uset.link_modes.advertising);
+ iflinux_ethtool_link_mode_unset_bit(ETHTOOL_LINK_MODE_FIBRE_BIT,
+ uset.link_modes.advertising);
+ iflinux_ethtool_link_mode_unset_bit(ETHTOOL_LINK_MODE_BNC_BIT,
+ uset.link_modes.advertising);
+ iflinux_ethtool_link_mode_unset_bit(ETHTOOL_LINK_MODE_Pause_BIT,
+ uset.link_modes.advertising);
+ iflinux_ethtool_link_mode_unset_bit(ETHTOOL_LINK_MODE_Asym_Pause_BIT,
+ uset.link_modes.advertising);
+ iflinux_ethtool_link_mode_unset_bit(ETHTOOL_LINK_MODE_Backplane_BIT,
+ uset.link_modes.advertising);
+ if (!iflinux_ethtool_link_mode_is_empty(uset.link_modes.advertising)) {
+ port->p_macphy.autoneg_advertised |=
+ LLDP_DOT3_LINK_AUTONEG_OTHER;
+ }
+ switch (uset.base.speed) {
+ case SPEED_10:
+ port->p_macphy.mau_type = (uset.base.duplex == DUPLEX_FULL) ?
+ LLDP_DOT3_MAU_10BASETFD :
+ LLDP_DOT3_MAU_10BASETHD;
+ if (uset.base.port == PORT_BNC)
+ port->p_macphy.mau_type = LLDP_DOT3_MAU_10BASE2;
+ if (uset.base.port == PORT_FIBRE)
+ port->p_macphy.mau_type =
+ (uset.base.duplex == DUPLEX_FULL) ?
+ LLDP_DOT3_MAU_10BASEFLFD :
+ LLDP_DOT3_MAU_10BASEFLHD;
+ break;
+ case SPEED_100:
+ port->p_macphy.mau_type = (uset.base.duplex == DUPLEX_FULL) ?
+ LLDP_DOT3_MAU_100BASETXFD :
+ LLDP_DOT3_MAU_100BASETXHD;
+ if (uset.base.port == PORT_BNC)
+ port->p_macphy.mau_type =
+ (uset.base.duplex == DUPLEX_FULL) ?
+ LLDP_DOT3_MAU_100BASET2FD :
+ LLDP_DOT3_MAU_100BASET2HD;
+ if (uset.base.port == PORT_FIBRE)
+ port->p_macphy.mau_type =
+ (uset.base.duplex == DUPLEX_FULL) ?
+ LLDP_DOT3_MAU_100BASEFXFD :
+ LLDP_DOT3_MAU_100BASEFXHD;
+ break;
+ case SPEED_1000:
+ port->p_macphy.mau_type = (uset.base.duplex == DUPLEX_FULL) ?
+ LLDP_DOT3_MAU_1000BASETFD :
+ LLDP_DOT3_MAU_1000BASETHD;
+ if (uset.base.port == PORT_FIBRE)
+ port->p_macphy.mau_type =
+ (uset.base.duplex == DUPLEX_FULL) ?
+ LLDP_DOT3_MAU_1000BASEXFD :
+ LLDP_DOT3_MAU_1000BASEXHD;
+ break;
+ case SPEED_2500:
+ port->p_macphy.mau_type = LLDP_DOT3_MAU_2P5GIGT;
+ break;
+ case SPEED_5000:
+ port->p_macphy.mau_type = LLDP_DOT3_MAU_5GIGT;
+ break;
+ case SPEED_10000:
+ // Distinguish between RJ45 BaseT, DAC BaseCX4, or Fibre BaseLR
+ if (uset.base.port == PORT_TP) {
+ port->p_macphy.mau_type = LLDP_DOT3_MAU_10GBASET;
+ } else if (uset.base.port == PORT_FIBRE) {
+ port->p_macphy.mau_type = LLDP_DOT3_MAU_10GIGBASELR;
+ } else if (uset.base.port == PORT_DA) {
+ port->p_macphy.mau_type = LLDP_DOT3_MAU_10GIGBASECX4;
+ }
+ break;
+ case SPEED_25000:
+ // Distinguish between RJ45 BaseT, DAC BaseCR, or Fibre BaseLR
+ if (uset.base.port == PORT_TP) {
+ port->p_macphy.mau_type = LLDP_DOT3_MAU_25GBASET;
+ } else if (uset.base.port == PORT_FIBRE) {
+ port->p_macphy.mau_type = LLDP_DOT3_MAU_25GBASELR;
+ } else if (uset.base.port == PORT_DA) {
+ port->p_macphy.mau_type = LLDP_DOT3_MAU_25GBASECR;
+ }
+ break;
+ case SPEED_40000:
+ // Same kind of approximation.
+ port->p_macphy.mau_type = (uset.base.port == PORT_FIBRE) ?
+ LLDP_DOT3_MAU_40GBASELR4 :
+ LLDP_DOT3_MAU_40GBASECR4;
+ break;
+ case SPEED_50000:
+ // Same kind of approximation.
+ port->p_macphy.mau_type = (uset.base.port == PORT_FIBRE) ?
+ LLDP_DOT3_MAU_50GBASELR :
+ LLDP_DOT3_MAU_50GBASECR;
+ break;
+ case SPEED_100000:
+ // Ditto
+ port->p_macphy.mau_type = (uset.base.port == PORT_FIBRE) ?
+ LLDP_DOT3_MAU_100GBASELR4 :
+ LLDP_DOT3_MAU_100GBASECR4;
+ break;
+ }
+ if (uset.base.port == PORT_AUI)
+ port->p_macphy.mau_type = LLDP_DOT3_MAU_AUI;
+ }
+}
+#else /* ENABLE_DOT3 */
+static void
+iflinux_macphy(struct lldpd *cfg, struct lldpd_hardware *hardware)
+{
+}
+#endif /* ENABLE_DOT3 */
+
+#ifdef ENABLE_OLDIES
+struct bond_master {
+ char name[IFNAMSIZ];
+ int index;
+};
+
+static int
+iface_bond_init(struct lldpd *cfg, struct lldpd_hardware *hardware)
+{
+ struct bond_master *master = hardware->h_data;
+ int fd;
+ int un = 1;
+
+ if (!master) return -1;
+
+ log_debug("interfaces", "initialize enslaved device %s", hardware->h_ifname);
+
+ /* First, we get a socket to the raw physical interface */
+ if ((fd = priv_iface_init(hardware->h_ifindex, hardware->h_ifname)) == -1)
+ return -1;
+ hardware->h_sendfd = fd;
+ interfaces_setup_multicast(cfg, hardware->h_ifname, 0);
+
+ /* Then, we open a raw interface for the master */
+ log_debug("interfaces", "enslaved device %s has master %s(%d)",
+ hardware->h_ifname, master->name, master->index);
+ if ((fd = priv_iface_init(master->index, master->name)) == -1) {
+ close(hardware->h_sendfd);
+ return -1;
+ }
+ /* With bonding and older kernels (< 2.6.27) we need to listen
+ * to bond device. We use setsockopt() PACKET_ORIGDEV to get
+ * physical device instead of bond device (works with >=
+ * 2.6.24). */
+ if (setsockopt(fd, SOL_PACKET, PACKET_ORIGDEV, &un, sizeof(un)) == -1) {
+ log_info("interfaces",
+ "unable to setsockopt for master bonding device of %s. "
+ "You will get inaccurate results",
+ hardware->h_ifname);
+ }
+ interfaces_setup_multicast(cfg, master->name, 0);
+
+ levent_hardware_add_fd(hardware, hardware->h_sendfd);
+ levent_hardware_add_fd(hardware, fd);
+ log_debug("interfaces", "interface %s initialized (fd=%d,master=%s[%d])",
+ hardware->h_ifname, hardware->h_sendfd, master->name, fd);
+ return 0;
+}
+
+static int
+iface_bond_recv(struct lldpd *cfg, struct lldpd_hardware *hardware, int fd,
+ char *buffer, size_t size)
+{
+ int n;
+ struct sockaddr_ll from;
+ struct bond_master *master = hardware->h_data;
+
+ log_debug("interfaces", "receive PDU from enslaved device %s",
+ hardware->h_ifname);
+ if ((n = iflinux_generic_recv(hardware, fd, buffer, size, &from)) == -1)
+ return -1;
+ if (fd == hardware->h_sendfd) /* We received this on the physical interface. */
+ return n;
+ /* We received this on the bonding interface. Is it really for us? */
+ if (from.sll_ifindex == hardware->h_ifindex) /* This is for us */
+ return n;
+ if (from.sll_ifindex == master->index)
+ /* We don't know from which physical interface it comes (kernel
+ * < 2.6.24). In doubt, this is for us. */
+ return n;
+ return -1; /* Not for us */
+}
+
+static int
+iface_bond_close(struct lldpd *cfg, struct lldpd_hardware *hardware)
+{
+ struct bond_master *master = hardware->h_data;
+ log_debug("interfaces", "closing enslaved device %s", hardware->h_ifname);
+ interfaces_setup_multicast(cfg, hardware->h_ifname, 1);
+ interfaces_setup_multicast(cfg, master->name, 1);
+ free(hardware->h_data);
+ hardware->h_data = NULL;
+ return 0;
+}
+
+struct lldpd_ops bond_ops = {
+ .send = iflinux_eth_send,
+ .recv = iface_bond_recv,
+ .cleanup = iface_bond_close,
+};
+
+static void
+iflinux_handle_bond(struct lldpd *cfg, struct interfaces_device_list *interfaces)
+{
+ struct interfaces_device *iface;
+ struct interfaces_device *master;
+ struct lldpd_hardware *hardware;
+ struct bond_master *bmaster;
+ int created;
+ TAILQ_FOREACH (iface, interfaces, next) {
+ if (!(iface->type & IFACE_PHYSICAL_T)) continue;
+ if (iface->ignore) continue;
+ if (!iface->upper || !(iface->upper->type & IFACE_BOND_T)) continue;
+
+ master = iface->upper;
+ log_debug("interfaces",
+ "%s is an acceptable enslaved device (master=%s)", iface->name,
+ master->name);
+ created = 0;
+ if ((hardware = lldpd_get_hardware(cfg, iface->name, iface->index)) ==
+ NULL) {
+ if ((hardware = lldpd_alloc_hardware(cfg, iface->name,
+ iface->index)) == NULL) {
+ log_warnx("interfaces",
+ "Unable to allocate space for %s", iface->name);
+ continue;
+ }
+ created = 1;
+ }
+ if (hardware->h_flags) continue;
+ if (hardware->h_ops != &bond_ops || hardware->h_ifindex_changed) {
+ if (!created) {
+ log_debug("interfaces",
+ "bond %s is converted from another type of interface",
+ hardware->h_ifname);
+ if (hardware->h_ops && hardware->h_ops->cleanup)
+ hardware->h_ops->cleanup(cfg, hardware);
+ levent_hardware_release(hardware);
+ levent_hardware_init(hardware);
+ }
+ bmaster = hardware->h_data =
+ calloc(1, sizeof(struct bond_master));
+ if (!bmaster) {
+ log_warn("interfaces", "not enough memory");
+ lldpd_hardware_cleanup(cfg, hardware);
+ continue;
+ }
+ } else
+ bmaster = hardware->h_data;
+ bmaster->index = master->index;
+ strlcpy(bmaster->name, master->name, IFNAMSIZ);
+ if (hardware->h_ops != &bond_ops || hardware->h_ifindex_changed) {
+ if (iface_bond_init(cfg, hardware) != 0) {
+ log_warn("interfaces", "unable to initialize %s",
+ hardware->h_ifname);
+ lldpd_hardware_cleanup(cfg, hardware);
+ continue;
+ }
+ hardware->h_ops = &bond_ops;
+ hardware->h_mangle = 1;
+ }
+ if (created)
+ interfaces_helper_add_hardware(cfg, hardware);
+ else
+ lldpd_port_cleanup(&hardware->h_lport, 0);
+
+ hardware->h_flags = iface->flags;
+ iface->ignore = 1;
+
+ /* Get local address */
+ memcpy(&hardware->h_lladdr, iface->address, ETHER_ADDR_LEN);
+
+ /* Fill information about port */
+ interfaces_helper_port_name_desc(cfg, hardware, iface);
+
+ /* Fill additional info */
+# ifdef ENABLE_DOT3
+ hardware->h_lport.p_aggregid = master->index;
+# endif
+ hardware->h_mtu = iface->mtu ? iface->mtu : 1500;
+ }
+}
+#endif
+
+/* Query each interface to get the appropriate driver */
+static void
+iflinux_add_driver(struct lldpd *cfg, struct interfaces_device_list *interfaces)
+{
+ struct interfaces_device *iface;
+ TAILQ_FOREACH (iface, interfaces, next) {
+ struct ethtool_drvinfo ethc = { .cmd = ETHTOOL_GDRVINFO };
+ struct ifreq ifr = { .ifr_data = (caddr_t)&ethc };
+ if (iface->driver) continue;
+
+ strlcpy(ifr.ifr_name, iface->name, IFNAMSIZ);
+ if (ioctl(cfg->g_sock, SIOCETHTOOL, &ifr) == 0) {
+ iface->driver = strdup(ethc.driver);
+ log_debug("interfaces", "driver for %s is `%s`", iface->name,
+ iface->driver);
+ }
+ }
+}
+
+/* Query each interface to see if it is a wireless one */
+static void
+iflinux_add_wireless(struct lldpd *cfg, struct interfaces_device_list *interfaces)
+{
+ struct interfaces_device *iface;
+ TAILQ_FOREACH (iface, interfaces, next) {
+ struct iwreq iwr = {};
+ strlcpy(iwr.ifr_name, iface->name, IFNAMSIZ);
+ if (ioctl(cfg->g_sock, SIOCGIWNAME, &iwr) >= 0) {
+ log_debug("interfaces", "%s is wireless", iface->name);
+ iface->type |= IFACE_WIRELESS_T | IFACE_PHYSICAL_T;
+ }
+ }
+}
+
+/* Query each interface to see if it is a bridge */
+static void
+iflinux_add_bridge(struct lldpd *cfg, struct interfaces_device_list *interfaces)
+{
+ struct interfaces_device *iface;
+
+ TAILQ_FOREACH (iface, interfaces, next) {
+ if (iface->type &
+ (IFACE_PHYSICAL_T | IFACE_VLAN_T | IFACE_BOND_T | IFACE_BRIDGE_T))
+ continue;
+ if (iflinux_is_bridge(cfg, interfaces, iface)) {
+ log_debug("interfaces", "interface %s is a bridge",
+ iface->name);
+ iface->type |= IFACE_BRIDGE_T;
+ }
+ }
+}
+
+/* Query each interface to see if it is a bond */
+static void
+iflinux_add_bond(struct lldpd *cfg, struct interfaces_device_list *interfaces)
+{
+ struct interfaces_device *iface;
+
+ TAILQ_FOREACH (iface, interfaces, next) {
+ if (iface->type &
+ (IFACE_PHYSICAL_T | IFACE_VLAN_T | IFACE_BOND_T | IFACE_BRIDGE_T))
+ continue;
+ if (iflinux_is_bond(cfg, interfaces, iface)) {
+ log_debug("interfaces", "interface %s is a bond", iface->name);
+ iface->type |= IFACE_BOND_T;
+ }
+ }
+}
+
+/* Query each interface to see if it is a vlan */
+static void
+iflinux_add_vlan(struct lldpd *cfg, struct interfaces_device_list *interfaces)
+{
+ struct interfaces_device *iface;
+
+ TAILQ_FOREACH (iface, interfaces, next) {
+ if (iface->type &
+ (IFACE_PHYSICAL_T | IFACE_VLAN_T | IFACE_BOND_T | IFACE_BRIDGE_T))
+ continue;
+ if (iflinux_is_vlan(cfg, interfaces, iface)) {
+ log_debug("interfaces", "interface %s is a VLAN", iface->name);
+ iface->type |= IFACE_VLAN_T;
+ }
+ }
+}
+
+static void
+iflinux_add_physical(struct lldpd *cfg, struct interfaces_device_list *interfaces)
+{
+ struct interfaces_device *iface;
+ /* Deny some drivers */
+ const char *const *rif;
+ const char *const denied_drivers[] = { "cdc_mbim", "vxlan", NULL };
+
+ TAILQ_FOREACH (iface, interfaces, next) {
+ if (iface->type & (IFACE_VLAN_T | IFACE_BOND_T | IFACE_BRIDGE_T))
+ continue;
+
+ iface->type &= ~IFACE_PHYSICAL_T;
+
+ /* We request that the interface is able to do either multicast
+ * or broadcast to be able to send discovery frames. */
+ if (!(iface->flags & (IFF_MULTICAST | IFF_BROADCAST))) {
+ log_debug("interfaces",
+ "skip %s: not able to do multicast nor broadcast",
+ iface->name);
+ continue;
+ }
+
+ /* Check if the driver is not denied */
+ if (iface->driver) {
+ int skip = 0;
+ for (rif = denied_drivers; *rif; rif++) {
+ if (strcmp(iface->driver, *rif) == 0) {
+ log_debug("interfaces",
+ "skip %s: denied driver", iface->name);
+ skip = 1;
+ break;
+ }
+ }
+ if (skip) continue;
+ }
+
+ /* If the interface is linked to another one, skip it too. */
+ if (iface->lower &&
+ (!iface->driver ||
+ (strcmp(iface->driver, "veth") &&
+ strcmp(iface->driver, "dsa")))) {
+ log_debug("interfaces",
+ "skip %s: there is a lower interface (%s)", iface->name,
+ iface->lower->name);
+ continue;
+ }
+
+ /* Get the real MAC address (for example, if the interface is enslaved)
+ */
+ iflinux_get_permanent_mac(cfg, interfaces, iface);
+
+ log_debug("interfaces", "%s is a physical interface", iface->name);
+ iface->type |= IFACE_PHYSICAL_T;
+ }
+}
+
+void
+interfaces_update(struct lldpd *cfg)
+{
+ struct lldpd_hardware *hardware;
+ struct interfaces_device_list *interfaces;
+ struct interfaces_address_list *addresses;
+ interfaces = netlink_get_interfaces(cfg);
+ addresses = netlink_get_addresses(cfg);
+ if (interfaces == NULL || addresses == NULL) {
+ log_warnx("interfaces", "cannot update the list of local interfaces");
+ return;
+ }
+
+ /* Add missing bits to list of interfaces */
+ iflinux_add_driver(cfg, interfaces);
+ if (LOCAL_CHASSIS(cfg)->c_cap_available & LLDP_CAP_WLAN)
+ iflinux_add_wireless(cfg, interfaces);
+ if (LOCAL_CHASSIS(cfg)->c_cap_available & LLDP_CAP_BRIDGE)
+ iflinux_add_bridge(cfg, interfaces);
+ iflinux_add_bond(cfg, interfaces);
+ iflinux_add_vlan(cfg, interfaces);
+ iflinux_add_physical(cfg, interfaces);
+
+ interfaces_helper_allowlist(cfg, interfaces);
+#ifdef ENABLE_OLDIES
+ iflinux_handle_bond(cfg, interfaces);
+#endif
+ interfaces_helper_physical(cfg, interfaces, &eth_ops, iflinux_eth_init);
+#ifdef ENABLE_DOT1
+ interfaces_helper_vlan(cfg, interfaces);
+#endif
+ interfaces_helper_mgmt(cfg, addresses, interfaces);
+ interfaces_helper_chassis(cfg, interfaces);
+
+ /* Mac/PHY */
+ TAILQ_FOREACH (hardware, &cfg->g_hardware, h_entries) {
+ if (!hardware->h_flags) continue;
+ iflinux_macphy(cfg, hardware);
+ interfaces_helper_promisc(cfg, hardware);
+ }
+}
+
+void
+interfaces_cleanup(struct lldpd *cfg)
+{
+ netlink_cleanup(cfg);
+}
diff --git a/src/daemon/interfaces-solaris.c b/src/daemon/interfaces-solaris.c
new file mode 100644
index 0000000..5e3ae3c
--- /dev/null
+++ b/src/daemon/interfaces-solaris.c
@@ -0,0 +1,174 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/*
+ * Copyright (c) 2013 Vincent Bernat <bernat@luffy.cx>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "lldpd.h"
+
+#include <unistd.h>
+#include <sys/sockio.h>
+#include <net/if_types.h>
+
+/* Solaris comes with libdladm which seems to be handy to get all the necessary
+ * information. Unfortunately, this library needs a special device file and a
+ * Unix socket to a daemon. This is a bit difficult to use it in a
+ * privilege-separated daemon. Therefore, we keep using ioctl(). This should
+ * also improve compatibility with older versions of Solaris.
+ */
+
+static void
+ifsolaris_extract(struct lldpd *cfg, struct interfaces_device_list *interfaces,
+ struct interfaces_address_list *addresses, struct lifreq *lifr)
+{
+ int flags = 0;
+ int index = 0;
+ struct interfaces_address *address = NULL;
+ struct interfaces_device *device = NULL;
+
+ sa_family_t lifr_af = lifr->lifr_addr.ss_family;
+ struct lifreq lifrl = { .lifr_name = {} };
+ strlcpy(lifrl.lifr_name, lifr->lifr_name, sizeof(lifrl.lifr_name));
+
+ /* Flags */
+ if (ioctl(cfg->g_sock, SIOCGLIFFLAGS, (caddr_t)&lifrl) < 0) {
+ log_warn("interfaces", "unable to get flags for %s", lifrl.lifr_name);
+ return;
+ }
+ flags = lifrl.lifr_flags;
+
+ /* Index */
+ if (ioctl(cfg->g_sock, SIOCGLIFINDEX, (caddr_t)&lifrl) < 0) {
+ log_warn("interfaces", "unable to get index for %s", lifrl.lifr_name);
+ return;
+ }
+ index = lifrl.lifr_index;
+
+ /* Record the address */
+ if ((address = malloc(sizeof(struct interfaces_address))) == NULL) {
+ log_warn("interfaces", "not enough memory for a new IP address on %s",
+ lifrl.lifr_name);
+ return;
+ }
+ address->flags = flags;
+ address->index = index;
+ memcpy(&address->address, &lifr->lifr_addr,
+ (lifr_af == AF_INET) ? sizeof(struct sockaddr_in) :
+ sizeof(struct sockaddr_in6));
+ TAILQ_INSERT_TAIL(addresses, address, next);
+
+ /* Hardware address */
+ if (ioctl(cfg->g_sock, SIOCGLIFHWADDR, (caddr_t)&lifrl) < 0) {
+ log_debug("interfaces", "unable to get hardware address for %s",
+ lifrl.lifr_name);
+ return;
+ }
+ struct sockaddr_dl *saddrdl = (struct sockaddr_dl *)&lifrl.lifr_addr;
+ if (saddrdl->sdl_type != 4) {
+ log_debug("interfaces", "skip %s: not an ethernet device (%d)",
+ lifrl.lifr_name, saddrdl->sdl_type);
+ return;
+ }
+
+ /* Handle the interface */
+ if ((device = calloc(1, sizeof(struct interfaces_device))) == NULL) {
+ log_warn("interfaces", "unable to allocate memory for %s",
+ lifrl.lifr_name);
+ return;
+ }
+
+ device->name = strdup(lifrl.lifr_name);
+ device->flags = flags;
+ device->index = index;
+ device->type = IFACE_PHYSICAL_T;
+ device->address = malloc(ETHER_ADDR_LEN);
+ if (device->address) memcpy(device->address, LLADDR(saddrdl), ETHER_ADDR_LEN);
+
+ /* MTU */
+ if (ioctl(cfg->g_sock, SIOCGLIFMTU, (caddr_t)&lifrl) < 0) {
+ log_debug("interfaces", "unable to get MTU for %s", lifrl.lifr_name);
+ } else
+ device->mtu = lifrl.lifr_mtu;
+
+ TAILQ_INSERT_TAIL(interfaces, device, next);
+}
+
+extern struct lldpd_ops bpf_ops;
+void
+interfaces_update(struct lldpd *cfg)
+{
+ struct lldpd_hardware *hardware;
+ caddr_t buffer = NULL;
+ struct interfaces_device_list *interfaces;
+ struct interfaces_address_list *addresses;
+ interfaces = malloc(sizeof(struct interfaces_device_list));
+ addresses = malloc(sizeof(struct interfaces_address_list));
+ if (interfaces == NULL || addresses == NULL) {
+ log_warnx("interfaces", "unable to allocate memory");
+ goto end;
+ }
+ TAILQ_INIT(interfaces);
+ TAILQ_INIT(addresses);
+
+ struct lifnum lifn = { .lifn_family = AF_UNSPEC, .lifn_flags = LIFC_ENABLED };
+ if (ioctl(cfg->g_sock, SIOCGLIFNUM, &lifn) < 0) {
+ log_warn("interfaces", "unable to get the number of interfaces");
+ goto end;
+ }
+
+ size_t bufsize = lifn.lifn_count * sizeof(struct lifreq);
+ if ((buffer = malloc(bufsize)) == NULL) {
+ log_warn("interfaces", "unable to allocate buffer to get interfaces");
+ goto end;
+ }
+
+ struct lifconf lifc = { .lifc_family = AF_UNSPEC,
+ .lifc_flags = LIFC_ENABLED,
+ .lifc_len = bufsize,
+ .lifc_buf = buffer };
+ if (ioctl(cfg->g_sock, SIOCGLIFCONF, (char *)&lifc) < 0) {
+ log_warn("interfaces", "unable to get the network interfaces");
+ goto end;
+ }
+
+ int num = lifc.lifc_len / sizeof(struct lifreq);
+ if (num > lifn.lifn_count) num = lifn.lifn_count;
+ log_debug("interfaces", "got %d interfaces", num);
+
+ struct lifreq *lifrp = (struct lifreq *)buffer;
+ for (int n = 0; n < num; n++, lifrp++)
+ ifsolaris_extract(cfg, interfaces, addresses, lifrp);
+
+ interfaces_helper_allowlist(cfg, interfaces);
+ interfaces_helper_physical(cfg, interfaces, &bpf_ops, ifbpf_phys_init);
+ interfaces_helper_mgmt(cfg, addresses, interfaces);
+ interfaces_helper_chassis(cfg, interfaces);
+
+ /* Mac/PHY */
+ TAILQ_FOREACH (hardware, &cfg->g_hardware, h_entries) {
+ if (!hardware->h_flags) continue;
+ /* TODO: mac/phy for Solaris */
+ interfaces_helper_promisc(cfg, hardware);
+ }
+
+end:
+ free(buffer);
+ interfaces_free_devices(interfaces);
+ interfaces_free_addresses(addresses);
+}
+
+void
+interfaces_cleanup(struct lldpd *cfg)
+{
+}
diff --git a/src/daemon/interfaces.c b/src/daemon/interfaces.c
new file mode 100644
index 0000000..c1179d9
--- /dev/null
+++ b/src/daemon/interfaces.c
@@ -0,0 +1,764 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/*
+ * Copyright (c) 2012 Vincent Bernat <bernat@luffy.cx>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "lldpd.h"
+#include "trace.h"
+
+#include <stddef.h>
+#include <unistd.h>
+#include <errno.h>
+#include <assert.h>
+#include <arpa/inet.h>
+
+static int
+lldpd_af(int af)
+{
+ switch (af) {
+ case LLDPD_AF_IPV4:
+ return AF_INET;
+ case LLDPD_AF_IPV6:
+ return AF_INET6;
+ case LLDPD_AF_LAST:
+ return AF_MAX;
+ default:
+ return AF_UNSPEC;
+ }
+}
+
+/* Generic ethernet interface initialization */
+/**
+ * Enable multicast on the given interface.
+ */
+void
+interfaces_setup_multicast(struct lldpd *cfg, const char *name, int remove)
+{
+ int rc;
+ size_t i, j;
+ const u_int8_t *mac;
+ const u_int8_t zero[ETHER_ADDR_LEN] = {};
+
+ for (i = 0; cfg->g_protocols[i].mode != 0; i++) {
+ if (!cfg->g_protocols[i].enabled) continue;
+ for (j = 0; j < sizeof(cfg->g_protocols[0].mac) /
+ sizeof(cfg->g_protocols[0].mac[0]);
+ j++) {
+ mac = cfg->g_protocols[i].mac[j];
+ if (memcmp(mac, zero, ETHER_ADDR_LEN) == 0) break;
+ if ((rc = priv_iface_multicast(name, mac, !remove)) != 0) {
+ errno = rc;
+ if (errno != ENOENT)
+ log_debug("interfaces",
+ "unable to %s %s address to multicast filter for %s (%s)",
+ (remove) ? "delete" : "add",
+ cfg->g_protocols[i].name, name,
+ strerror(rc));
+ }
+ }
+ }
+}
+
+/**
+ * Free an interface.
+ *
+ * @param iff interface to be freed
+ */
+void
+interfaces_free_device(struct interfaces_device *iff)
+{
+ if (!iff) return;
+ free(iff->name);
+ free(iff->alias);
+ free(iff->address);
+ free(iff->driver);
+ free(iff);
+}
+
+/**
+ * Free a list of interfaces.
+ *
+ * @param ifs list of interfaces to be freed
+ */
+void
+interfaces_free_devices(struct interfaces_device_list *ifs)
+{
+ struct interfaces_device *iff, *iff_next;
+ if (!ifs) return;
+ for (iff = TAILQ_FIRST(ifs); iff != NULL; iff = iff_next) {
+ iff_next = TAILQ_NEXT(iff, next);
+ interfaces_free_device(iff);
+ }
+ free(ifs);
+}
+
+/**
+ * Free one address
+ *
+ * @param ifaddr Address to be freed
+ */
+void
+interfaces_free_address(struct interfaces_address *ifaddr)
+{
+ free(ifaddr);
+}
+
+/**
+ * Free a list of addresses.
+ *
+ * @param ifaddrs list of addresses
+ */
+void
+interfaces_free_addresses(struct interfaces_address_list *ifaddrs)
+{
+ struct interfaces_address *ifa, *ifa_next;
+ if (!ifaddrs) return;
+ for (ifa = TAILQ_FIRST(ifaddrs); ifa != NULL; ifa = ifa_next) {
+ ifa_next = TAILQ_NEXT(ifa, next);
+ interfaces_free_address(ifa);
+ }
+ free(ifaddrs);
+}
+
+/**
+ * Find the appropriate interface from the name.
+ *
+ * @param interfaces List of available interfaces
+ * @param device Name of the device we search for
+ * @return The interface or NULL if not found
+ */
+struct interfaces_device *
+interfaces_nametointerface(struct interfaces_device_list *interfaces,
+ const char *device)
+{
+ struct interfaces_device *iface;
+ TAILQ_FOREACH (iface, interfaces, next) {
+ if (!strncmp(iface->name, device, IFNAMSIZ)) return iface;
+ }
+ log_debug("interfaces", "cannot get interface for index %s", device);
+ return NULL;
+}
+
+/**
+ * Find the appropriate interface from the index.
+ *
+ * @param interfaces List of available interfaces
+ * @param index Index of the device we search for
+ * @return The interface or NULL if not found
+ */
+struct interfaces_device *
+interfaces_indextointerface(struct interfaces_device_list *interfaces, int index)
+{
+ struct interfaces_device *iface;
+ TAILQ_FOREACH (iface, interfaces, next) {
+ if (iface->index == index) return iface;
+ }
+ log_debug("interfaces", "cannot get interface for index %d", index);
+ return NULL;
+}
+
+void
+interfaces_helper_allowlist(struct lldpd *cfg,
+ struct interfaces_device_list *interfaces)
+{
+ struct interfaces_device *iface;
+
+ if (!cfg->g_config.c_iface_pattern) return;
+
+ TAILQ_FOREACH (iface, interfaces, next) {
+ int m = pattern_match(iface->name, cfg->g_config.c_iface_pattern, 0);
+ switch (m) {
+ case PATTERN_MATCH_DENIED:
+ log_debug("interfaces", "deny %s", iface->name);
+ iface->ignore = 1;
+ continue;
+ case PATTERN_MATCH_ALLOWED_EXACT:
+ log_debug("interfaces",
+ "allow %s (consider it as a physical interface)",
+ iface->name);
+ iface->type |= IFACE_PHYSICAL_T;
+ continue;
+ }
+ }
+}
+
+#ifdef ENABLE_DOT1
+static void
+iface_append_vlan(struct lldpd *cfg, struct interfaces_device *vlan,
+ struct interfaces_device *lower)
+{
+ struct lldpd_hardware *hardware =
+ lldpd_get_hardware(cfg, lower->name, lower->index);
+ struct lldpd_port *port;
+ struct lldpd_vlan *v;
+ char *name = NULL;
+ uint16_t vlan_id;
+
+ if (hardware == NULL) {
+ log_debug("interfaces", "cannot find real interface %s for VLAN %s",
+ lower->name, vlan->name);
+ return;
+ }
+ port = &hardware->h_lport;
+
+ for (int i = 0; (i < VLAN_BITMAP_LEN); i++) {
+ if (vlan->vlan_bmap[i] == 0) continue;
+ for (unsigned bit = 0; bit < 32; bit++) {
+ uint32_t mask = 1L << bit;
+ if (!(vlan->vlan_bmap[i] & mask)) continue;
+ vlan_id = (i * 32) + bit;
+ if (asprintf(&name, "vlan%d", vlan_id) == -1) return;
+
+ /* Check if the VLAN is already here. */
+ TAILQ_FOREACH (v, &port->p_vlans, v_entries)
+ if (strncmp(name, v->v_name, IFNAMSIZ) == 0) {
+ free(name);
+ return;
+ }
+
+ if ((v = (struct lldpd_vlan *)calloc(1,
+ sizeof(struct lldpd_vlan))) == NULL) {
+ free(name);
+ return;
+ }
+ v->v_name = name;
+ v->v_vid = vlan_id;
+ if (vlan->pvid) port->p_pvid = vlan->pvid;
+ log_debug("interfaces", "append VLAN %s for %s", v->v_name,
+ hardware->h_ifname);
+ TAILQ_INSERT_TAIL(&port->p_vlans, v, v_entries);
+ }
+ }
+}
+
+/**
+ * Append VLAN to the lowest possible interface.
+ *
+ * @param vlan The VLAN interface (used to get VLAN ID).
+ * @param upper The upper interface we are currently examining.
+ * @param depth Depth of the stack (avoid infinite recursion)
+ *
+ * Initially, upper == vlan. This function will be called recursively.
+ */
+static void
+iface_append_vlan_to_lower(struct lldpd *cfg, struct interfaces_device_list *interfaces,
+ struct interfaces_device *vlan, struct interfaces_device *upper, int depth)
+{
+ if (depth > 5) {
+ log_warnx("interfaces",
+ "BUG: maximum depth reached when applying VLAN %s (loop?)",
+ vlan->name);
+ return;
+ }
+ depth++;
+ struct interfaces_device *lower;
+ log_debug("interfaces",
+ "looking to apply VLAN %s to physical interface behind %s", vlan->name,
+ upper->name);
+
+ /* Some bridges managed VLAN internally, skip them. */
+ if (upper->type & IFACE_BRIDGE_VLAN_T) {
+ log_debug("interfaces",
+ "VLAN %s ignored for VLAN-aware bridge interface %s", vlan->name,
+ upper->name);
+ return;
+ }
+
+ /* Easy: check if we have a lower interface. */
+ if (upper->lower) {
+ log_debug("interfaces", "VLAN %s on lower interface %s", vlan->name,
+ upper->name);
+ iface_append_vlan_to_lower(cfg, interfaces, vlan, upper->lower, depth);
+ return;
+ }
+
+ /* Other easy case, we have a physical interface. */
+ if (upper->type & IFACE_PHYSICAL_T) {
+ log_debug("interfaces", "VLAN %s on physical interface %s", vlan->name,
+ upper->name);
+ iface_append_vlan(cfg, vlan, upper);
+ return;
+ }
+
+ /* We can now search for interfaces that have our interface as an upper
+ * interface. */
+ TAILQ_FOREACH (lower, interfaces, next) {
+ if (lower->upper != upper) continue;
+ log_debug("interfaces", "VLAN %s on lower interface %s", vlan->name,
+ upper->name);
+ iface_append_vlan_to_lower(cfg, interfaces, vlan, lower, depth);
+ }
+}
+
+void
+interfaces_helper_vlan(struct lldpd *cfg, struct interfaces_device_list *interfaces)
+{
+ struct interfaces_device *iface;
+
+ TAILQ_FOREACH (iface, interfaces, next) {
+ if (!(iface->type & IFACE_VLAN_T) && bitmap_isempty(iface->vlan_bmap))
+ continue;
+
+ /* We need to find the physical interfaces of this
+ vlan, through bonds and bridges. */
+ log_debug("interfaces",
+ "search physical interface for VLAN interface %s", iface->name);
+ iface_append_vlan_to_lower(cfg, interfaces, iface, iface, 0);
+ }
+}
+#endif
+
+/* Fill out chassis ID if not already done. Only physical interfaces are
+ * considered. */
+void
+interfaces_helper_chassis(struct lldpd *cfg, struct interfaces_device_list *interfaces)
+{
+ struct interfaces_device *iface;
+ struct lldpd_hardware *hardware;
+ char *name = NULL;
+ static u_int8_t zero_mac[] = { 0, 0, 0, 0, 0, 0 };
+
+ if (!cfg->g_config.c_cap_override) {
+ LOCAL_CHASSIS(cfg)->c_cap_enabled &=
+ ~(LLDP_CAP_BRIDGE | LLDP_CAP_WLAN | LLDP_CAP_STATION);
+ TAILQ_FOREACH (iface, interfaces, next) {
+ if (iface->type & IFACE_BRIDGE_T)
+ LOCAL_CHASSIS(cfg)->c_cap_enabled |= LLDP_CAP_BRIDGE;
+ if (iface->type & IFACE_WIRELESS_T)
+ LOCAL_CHASSIS(cfg)->c_cap_enabled |= LLDP_CAP_WLAN;
+ }
+ if ((LOCAL_CHASSIS(cfg)->c_cap_available & LLDP_CAP_STATION) &&
+ (LOCAL_CHASSIS(cfg)->c_cap_enabled == 0))
+ LOCAL_CHASSIS(cfg)->c_cap_enabled = LLDP_CAP_STATION;
+ }
+
+ /* Do not modify the chassis if it's already set to a MAC address or if
+ * it's set to a local address equal to the user-provided
+ * configuration. */
+ if ((LOCAL_CHASSIS(cfg)->c_id != NULL &&
+ LOCAL_CHASSIS(cfg)->c_id_subtype == LLDP_CHASSISID_SUBTYPE_LLADDR) ||
+ cfg->g_config.c_cid_string != NULL)
+ return; /* We already have one */
+
+ TAILQ_FOREACH (iface, interfaces, next) {
+ if (!(iface->type & IFACE_PHYSICAL_T)) continue;
+ if (cfg->g_config.c_cid_pattern &&
+ !pattern_match(iface->name, cfg->g_config.c_cid_pattern, 0))
+ continue;
+
+ if ((hardware = lldpd_get_hardware(cfg, iface->name, iface->index)) ==
+ NULL)
+ /* That's odd. Let's skip. */
+ continue;
+ if (memcmp(hardware->h_lladdr, zero_mac, ETHER_ADDR_LEN) == 0)
+ /* All-zero MAC address */
+ continue;
+
+ name = malloc(ETHER_ADDR_LEN);
+ if (!name) {
+ log_warn("interfaces", "not enough memory for chassis ID");
+ return;
+ }
+ free(LOCAL_CHASSIS(cfg)->c_id);
+ memcpy(name, hardware->h_lladdr, ETHER_ADDR_LEN);
+ LOCAL_CHASSIS(cfg)->c_id = name;
+ LOCAL_CHASSIS(cfg)->c_id_len = ETHER_ADDR_LEN;
+ LOCAL_CHASSIS(cfg)->c_id_subtype = LLDP_CHASSISID_SUBTYPE_LLADDR;
+ return;
+ }
+}
+
+#undef IN_IS_ADDR_LOOPBACK
+#define IN_IS_ADDR_LOOPBACK(a) ((a)->s_addr == htonl(INADDR_LOOPBACK))
+#undef IN_IS_ADDR_ANY
+#define IN_IS_ADDR_ANY(a) ((a)->s_addr == htonl(INADDR_ANY))
+#undef IN_IS_ADDR_LINKLOCAL
+#define IN_IS_ADDR_LINKLOCAL(a) (((a)->s_addr & htonl(0xffff0000)) == htonl(0xa9fe0000))
+#undef IN_IS_ADDR_GLOBAL
+#define IN_IS_ADDR_GLOBAL(a) \
+ (!IN_IS_ADDR_LOOPBACK(a) && !IN_IS_ADDR_ANY(a) && !IN_IS_ADDR_LINKLOCAL(a))
+#undef IN6_IS_ADDR_GLOBAL
+#define IN6_IS_ADDR_GLOBAL(a) (!IN6_IS_ADDR_LOOPBACK(a) && !IN6_IS_ADDR_LINKLOCAL(a))
+
+/* Add management addresses for the given family. We only take one of each
+ address family, unless a pattern is provided and is not all negative. For
+ example !*:*,!10.* will only deny addresses. We will pick the first IPv4
+ address not matching 10.*.
+*/
+static int
+interfaces_helper_mgmt_for_af(struct lldpd *cfg, int af,
+ struct interfaces_address_list *addrs, struct interfaces_device_list *interfaces,
+ int global, int allnegative)
+{
+ struct interfaces_address *addr;
+ struct interfaces_device *device;
+ struct lldpd_mgmt *mgmt;
+ char addrstrbuf[INET6_ADDRSTRLEN];
+ int found = 0;
+ union lldpd_address in_addr;
+ size_t in_addr_size;
+
+ TAILQ_FOREACH (addr, addrs, next) {
+ if (addr->address.ss_family != lldpd_af(af)) continue;
+
+ switch (af) {
+ case LLDPD_AF_IPV4:
+ in_addr_size = sizeof(struct in_addr);
+ memcpy(&in_addr,
+ &((struct sockaddr_in *)&addr->address)->sin_addr,
+ in_addr_size);
+ if (global) {
+ if (!IN_IS_ADDR_GLOBAL(&in_addr.inet)) continue;
+ } else {
+ if (!IN_IS_ADDR_LINKLOCAL(&in_addr.inet)) continue;
+ }
+ break;
+ case LLDPD_AF_IPV6:
+ in_addr_size = sizeof(struct in6_addr);
+ memcpy(&in_addr,
+ &((struct sockaddr_in6 *)&addr->address)->sin6_addr,
+ in_addr_size);
+ if (global) {
+ if (!IN6_IS_ADDR_GLOBAL(&in_addr.inet6)) continue;
+ } else {
+ if (!IN6_IS_ADDR_LINKLOCAL(&in_addr.inet6)) continue;
+ }
+ break;
+ default:
+ assert(0);
+ continue;
+ }
+ if (inet_ntop(lldpd_af(af), &in_addr, addrstrbuf, sizeof(addrstrbuf)) ==
+ NULL) {
+ log_warn("interfaces",
+ "unable to convert IP address to a string");
+ continue;
+ }
+ if (cfg->g_config.c_mgmt_pattern == NULL ||
+ /* Match on IP address */
+ pattern_match(addrstrbuf, cfg->g_config.c_mgmt_pattern,
+ allnegative) ||
+ /* Match on interface name */
+ ((device = interfaces_indextointerface(interfaces, addr->index)) &&
+ pattern_match(device->name, cfg->g_config.c_mgmt_pattern,
+ allnegative))) {
+ mgmt =
+ lldpd_alloc_mgmt(af, &in_addr, in_addr_size, addr->index);
+ if (mgmt == NULL) {
+ assert(errno == ENOMEM); /* anything else is a bug */
+ log_warn("interfaces", "out of memory error");
+ return found;
+ }
+ log_debug("interfaces", "add management address %s",
+ addrstrbuf);
+ TAILQ_INSERT_TAIL(&LOCAL_CHASSIS(cfg)->c_mgmt, mgmt, m_entries);
+ found = 1;
+
+ /* Don't take additional address if the pattern is all negative.
+ */
+ if (allnegative) break;
+ }
+ }
+ return found;
+}
+
+/* Find a management address in all available interfaces, even those that were
+ already handled. This is a special interface handler because it does not
+ really handle interface related information (management address is attached
+ to the local chassis). */
+void
+interfaces_helper_mgmt(struct lldpd *cfg, struct interfaces_address_list *addrs,
+ struct interfaces_device_list *interfaces)
+{
+ int allnegative = 0;
+ int af;
+ const char *pattern = cfg->g_config.c_mgmt_pattern;
+
+ lldpd_chassis_mgmt_cleanup(LOCAL_CHASSIS(cfg));
+ if (!cfg->g_config.c_mgmt_advertise) return;
+
+ /* Is the pattern provided an actual IP address? */
+ if (pattern && strpbrk(pattern, "!,*?") == NULL) {
+ unsigned char addr[sizeof(struct in6_addr)];
+ size_t addr_size;
+ struct lldpd_mgmt *mgmt;
+ struct interfaces_address *ifaddr;
+
+ for (af = LLDPD_AF_UNSPEC + 1; af != LLDPD_AF_LAST; af++) {
+ switch (af) {
+ case LLDPD_AF_IPV4:
+ addr_size = sizeof(struct in_addr);
+ break;
+ case LLDPD_AF_IPV6:
+ addr_size = sizeof(struct in6_addr);
+ break;
+ default:
+ assert(0);
+ }
+ if (inet_pton(lldpd_af(af), pattern, addr) == 1) break;
+ }
+ if (af != LLDPD_AF_LAST) {
+ /* Try to get the index if possible. */
+ TAILQ_FOREACH (ifaddr, addrs, next) {
+ if (ifaddr->address.ss_family != lldpd_af(af)) continue;
+ if (LLDPD_AF_IPV4 == af) {
+ struct sockaddr_in *sa_sin;
+ sa_sin = (struct sockaddr_in *)&ifaddr->address;
+ if (0 ==
+ memcmp(addr, &(sa_sin->sin_addr),
+ addr_size))
+ break;
+ } else if (LLDPD_AF_IPV6 == af) {
+ if (0 ==
+ memcmp(addr,
+ &((struct sockaddr_in6 *)&ifaddr
+ ->address)
+ ->sin6_addr,
+ addr_size))
+ break;
+ }
+ }
+
+ mgmt = lldpd_alloc_mgmt(af, addr, addr_size,
+ ifaddr ? ifaddr->index : 0);
+ if (mgmt == NULL) {
+ log_warn("interfaces", "out of memory error");
+ return;
+ }
+ log_debug("interfaces", "add exact management address %s",
+ pattern);
+ TAILQ_INSERT_TAIL(&LOCAL_CHASSIS(cfg)->c_mgmt, mgmt, m_entries);
+ return;
+ }
+ /* else: could be an interface name */
+ }
+
+ /* Is the pattern provided all negative? */
+ if (pattern == NULL)
+ allnegative = 1;
+ else if (pattern[0] == '!') {
+ /* If each comma is followed by '!', its an all
+ negative pattern */
+ const char *sep = pattern;
+ while ((sep = strchr(sep, ',')) && (*(++sep) == '!'))
+ ;
+ if (sep == NULL) allnegative = 1;
+ }
+
+ /* Find management addresses */
+ for (af = LLDPD_AF_UNSPEC + 1; af != LLDPD_AF_LAST; af++) {
+ (void)(interfaces_helper_mgmt_for_af(cfg, af, addrs, interfaces, 1,
+ allnegative) ||
+ interfaces_helper_mgmt_for_af(cfg, af, addrs, interfaces, 0,
+ allnegative));
+ }
+}
+
+/* Fill up port name and description */
+void
+interfaces_helper_port_name_desc(struct lldpd *cfg, struct lldpd_hardware *hardware,
+ struct interfaces_device *iface)
+{
+ struct lldpd_port *port = &hardware->h_lport;
+
+ /* We need to set the portid to what the client configured.
+ This can be done from the CLI.
+ */
+ int has_alias = (iface->alias != NULL && strlen(iface->alias) != 0 &&
+ strncmp("lldpd: ", iface->alias, 7));
+ int portid_type = cfg->g_config.c_lldp_portid_type;
+ if (portid_type == LLDP_PORTID_SUBTYPE_IFNAME ||
+ (portid_type == LLDP_PORTID_SUBTYPE_UNKNOWN && has_alias) ||
+ (port->p_id_subtype == LLDP_PORTID_SUBTYPE_LOCAL && has_alias)) {
+ if (port->p_id_subtype != LLDP_PORTID_SUBTYPE_LOCAL) {
+ log_debug("interfaces", "use ifname for %s",
+ hardware->h_ifname);
+ port->p_id_subtype = LLDP_PORTID_SUBTYPE_IFNAME;
+ port->p_id_len = strlen(hardware->h_ifname);
+ free(port->p_id);
+ if ((port->p_id = calloc(1, port->p_id_len)) == NULL)
+ fatal("interfaces", NULL);
+ memcpy(port->p_id, hardware->h_ifname, port->p_id_len);
+ }
+
+ if (port->p_descr_force == 0) {
+ /* use the actual alias in the port description */
+ log_debug("interfaces", "using alias in description for %s",
+ hardware->h_ifname);
+ free(port->p_descr);
+ if (has_alias) {
+ port->p_descr = strdup(iface->alias);
+ } else {
+ /* We don't have anything else to put here and for CDP
+ * with need something non-NULL */
+ port->p_descr = strdup(hardware->h_ifname);
+ }
+ }
+ } else {
+ if (port->p_id_subtype != LLDP_PORTID_SUBTYPE_LOCAL) {
+ log_debug("interfaces", "use MAC address for %s",
+ hardware->h_ifname);
+ port->p_id_subtype = LLDP_PORTID_SUBTYPE_LLADDR;
+ free(port->p_id);
+ if ((port->p_id = calloc(1, ETHER_ADDR_LEN)) == NULL)
+ fatal("interfaces", NULL);
+ memcpy(port->p_id, hardware->h_lladdr, ETHER_ADDR_LEN);
+ port->p_id_len = ETHER_ADDR_LEN;
+ }
+
+ if (port->p_descr_force == 0) {
+ /* use the ifname in the port description until alias is set */
+ log_debug("interfaces", "using ifname in description for %s",
+ hardware->h_ifname);
+ free(port->p_descr);
+ port->p_descr = strdup(hardware->h_ifname);
+ }
+ }
+}
+
+void
+interfaces_helper_add_hardware(struct lldpd *cfg, struct lldpd_hardware *hardware)
+{
+ TRACE(LLDPD_INTERFACES_NEW(hardware->h_ifname));
+ TAILQ_INSERT_TAIL(&cfg->g_hardware, hardware, h_entries);
+}
+
+void
+interfaces_helper_physical(struct lldpd *cfg, struct interfaces_device_list *interfaces,
+ struct lldpd_ops *ops, int (*init)(struct lldpd *, struct lldpd_hardware *))
+{
+ struct interfaces_device *iface;
+ struct lldpd_hardware *hardware;
+ int created;
+
+ TAILQ_FOREACH (iface, interfaces, next) {
+ if (!(iface->type & IFACE_PHYSICAL_T)) continue;
+ if (iface->ignore) continue;
+
+ log_debug("interfaces", "%s is an acceptable ethernet device",
+ iface->name);
+ created = 0;
+ if ((hardware = lldpd_get_hardware(cfg, iface->name, iface->index)) ==
+ NULL) {
+ if ((hardware = lldpd_alloc_hardware(cfg, iface->name,
+ iface->index)) == NULL) {
+ log_warnx("interfaces",
+ "Unable to allocate space for %s", iface->name);
+ continue;
+ }
+ created = 1;
+ }
+ if (hardware->h_flags) continue;
+ if (hardware->h_ops != ops || hardware->h_ifindex_changed) {
+ if (!created) {
+ log_debug("interfaces",
+ "interface %s is converted from another type of interface",
+ hardware->h_ifname);
+ if (hardware->h_ops && hardware->h_ops->cleanup) {
+ hardware->h_ops->cleanup(cfg, hardware);
+ levent_hardware_release(hardware);
+ levent_hardware_init(hardware);
+ }
+ }
+ if (init(cfg, hardware) != 0) {
+ log_warnx("interfaces", "unable to initialize %s",
+ hardware->h_ifname);
+ lldpd_hardware_cleanup(cfg, hardware);
+ continue;
+ }
+ hardware->h_ops = ops;
+ hardware->h_mangle =
+ (iface->upper && iface->upper->type & IFACE_BOND_T);
+ }
+ if (created)
+ interfaces_helper_add_hardware(cfg, hardware);
+ else
+ lldpd_port_cleanup(&hardware->h_lport, 0);
+
+ hardware->h_flags = iface->flags; /* Should be non-zero */
+ iface->ignore = 1; /* Future handlers
+ don't have to
+ care about this
+ interface. */
+
+ /* Get local address */
+ memcpy(&hardware->h_lladdr, iface->address, ETHER_ADDR_LEN);
+
+ /* Fill information about port */
+ interfaces_helper_port_name_desc(cfg, hardware, iface);
+
+ /* Fill additional info */
+ hardware->h_mtu = iface->mtu ? iface->mtu : 1500;
+
+#ifdef ENABLE_DOT3
+ if (iface->upper && iface->upper->type & IFACE_BOND_T)
+ hardware->h_lport.p_aggregid = iface->upper->index;
+ else
+ hardware->h_lport.p_aggregid = 0;
+#endif
+ }
+}
+
+void
+interfaces_helper_promisc(struct lldpd *cfg, struct lldpd_hardware *hardware)
+{
+ if (!cfg->g_config.c_promisc) return;
+ if (priv_iface_promisc(hardware->h_ifname) != 0) {
+ log_warnx("interfaces", "unable to enable promiscuous mode for %s",
+ hardware->h_ifname);
+ }
+}
+
+/**
+ * Send the packet using the hardware function. Optionnaly mangle the MAC address.
+ *
+ * With bonds, we have duplicate MAC address on different physical
+ * interfaces. We need to alter the source MAC address when we send on an
+ * inactive slave. The `h_mangle` flah is used to know if we need to do
+ * something like that.
+ */
+int
+interfaces_send_helper(struct lldpd *cfg, struct lldpd_hardware *hardware, char *buffer,
+ size_t size)
+{
+ if (size < 2 * ETHER_ADDR_LEN) {
+ log_warnx("interfaces", "packet to send on %s is too small!",
+ hardware->h_ifname);
+ return 0;
+ }
+ if (hardware->h_mangle) {
+#define MAC_UL_ADMINISTERED_BIT_MASK 0x02
+ char *src_mac = buffer + ETHER_ADDR_LEN;
+ char arbitrary[] = { 0x00, 0x60, 0x08, 0x69, 0x97, 0xef };
+
+ switch (cfg->g_config.c_bond_slave_src_mac_type) {
+ case LLDP_BOND_SLAVE_SRC_MAC_TYPE_LOCALLY_ADMINISTERED:
+ if (!(*src_mac & MAC_UL_ADMINISTERED_BIT_MASK)) {
+ *src_mac |= MAC_UL_ADMINISTERED_BIT_MASK;
+ break;
+ }
+ /* Fallback to fixed value */
+ memcpy(src_mac, arbitrary, ETHER_ADDR_LEN);
+ break;
+ case LLDP_BOND_SLAVE_SRC_MAC_TYPE_FIXED:
+ memcpy(src_mac, arbitrary, ETHER_ADDR_LEN);
+ break;
+ case LLDP_BOND_SLAVE_SRC_MAC_TYPE_ZERO:
+ memset(src_mac, 0, ETHER_ADDR_LEN);
+ break;
+ }
+ }
+ return hardware->h_ops->send(cfg, hardware, buffer, size);
+}
diff --git a/src/daemon/lldp-tlv.h b/src/daemon/lldp-tlv.h
new file mode 100644
index 0000000..a9bf7cf
--- /dev/null
+++ b/src/daemon/lldp-tlv.h
@@ -0,0 +1,83 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/*
+ * Copyright (c) 2012 Vincent Bernat <bernat@luffy.cx>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef _LLDP_TLV_H
+#define _LLDP_TLV_H
+
+#define LLDP_ADDR_NEAREST_BRIDGE \
+ { \
+ 0x01, 0x80, 0xc2, 0x00, 0x00, 0x0e \
+ }
+#define LLDP_ADDR_NEAREST_NONTPMR_BRIDGE \
+ { \
+ 0x01, 0x80, 0xc2, 0x00, 0x00, 0x03 \
+ }
+#define LLDP_ADDR_NEAREST_CUSTOMER_BRIDGE \
+ { \
+ 0x01, 0x80, 0xc2, 0x00, 0x00, 0x00 \
+ }
+
+#define LLDP_TLV_END 0
+#define LLDP_TLV_CHASSIS_ID 1
+#define LLDP_TLV_PORT_ID 2
+#define LLDP_TLV_TTL 3
+#define LLDP_TLV_PORT_DESCR 4
+#define LLDP_TLV_SYSTEM_NAME 5
+#define LLDP_TLV_SYSTEM_DESCR 6
+#define LLDP_TLV_SYSTEM_CAP 7
+#define LLDP_TLV_MGMT_ADDR 8
+
+#define LLDP_TLV_ORG_DOT1 \
+ { \
+ 0x00, 0x80, 0xc2 \
+ }
+#define LLDP_TLV_ORG_DOT3 \
+ { \
+ 0x00, 0x12, 0x0f \
+ }
+#define LLDP_TLV_ORG_MED \
+ { \
+ 0x00, 0x12, 0xbb \
+ }
+#define LLDP_TLV_ORG_DCBX \
+ { \
+ 0x00, 0x1b, 0x21 \
+ }
+
+#define LLDP_TLV_DOT1_PVID 1
+#define LLDP_TLV_DOT1_PPVID 2
+#define LLDP_TLV_DOT1_VLANNAME 3
+#define LLDP_TLV_DOT1_PI 4
+
+#define LLDP_TLV_DOT3_MAC 1
+#define LLDP_TLV_DOT3_POWER 2
+#define LLDP_TLV_DOT3_LA 3
+#define LLDP_TLV_DOT3_MFS 4
+
+#define LLDP_TLV_MED_CAP 1
+#define LLDP_TLV_MED_POLICY 2
+#define LLDP_TLV_MED_LOCATION 3
+#define LLDP_TLV_MED_MDI 4
+#define LLDP_TLV_MED_IV_HW 5
+#define LLDP_TLV_MED_IV_FW 6
+#define LLDP_TLV_MED_IV_SW 7
+#define LLDP_TLV_MED_IV_SN 8
+#define LLDP_TLV_MED_IV_MANUF 9
+#define LLDP_TLV_MED_IV_MODEL 10
+#define LLDP_TLV_MED_IV_ASSET 11
+
+#endif
diff --git a/src/daemon/lldpd.8.in b/src/daemon/lldpd.8.in
new file mode 100644
index 0000000..32d57a6
--- /dev/null
+++ b/src/daemon/lldpd.8.in
@@ -0,0 +1,424 @@
+.\" Copyright (c) 2006 Pierre-Yves Ritschard <pyr@openbsd.org>
+.\" Copyright (c) 2008 Vincent Bernat <bernat@luffy.cx>
+.\"
+.\" Permission to use, copy, modify, and/or distribute this software for any
+.\" purpose with or without fee is hereby granted, provided that the above
+.\" copyright notice and this permission notice appear in all copies.
+.\"
+.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+.\"
+.Dd $Mdocdate: August 21 2008 $
+.Dt LLDPD 8
+.Os
+.Sh NAME
+.Nm lldpd
+.Nd LLDP daemon
+.Sh SYNOPSIS
+.Nm
+.Op Fl dxcseiklrv
+.Op Fl D Ar debug
+.Op Fl p Ar pidfile
+.Op Fl S Ar description
+.Op Fl P Ar platform
+.Op Fl X Ar socket
+.Op Fl m Ar management
+.Op Fl u Ar file
+.Op Fl I Ar interfaces
+.Op Fl C Ar interfaces
+.Op Fl M Ar class
+.Op Fl H Ar hide
+.Op Fl L Ar lldpcli
+.Op Fl O Ar configfile
+.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. When specified one more time,
+.Nm
+will not log to syslog but only to stderr. Then, this option can be
+specified many times to increase verbosity. When specified four times,
+debug logs will be enabled. They can be filtered with
+.Fl D
+flag.
+.It Fl D Ar debug
+This option allows the user to filter out debugging information by
+specifying allowed tokens. This option can be repeated several times
+to allow several tokens. This option must be combined with the
+.Fl d
+flag to have some effect. Only debugging logs can be filtered. Here is
+a list of allowed tokens with their description:
+.Bl -tag -width "XXXXXXXXXX" -offset "XXXX" -compact
+.It Sy main
+Main daemon.
+.It Sy interfaces
+Discovery of local interfaces.
+.It Sy lldp
+LLDP PDU encoding/decoding.
+.It Sy edp
+EDP PDU encoding/decoding.
+.It Sy cdp
+CDP/FDP PDU encoding/decoding.
+.It Sy sonmp
+SONMP PDU encoding/decoding.
+.It Sy event
+Events management.
+.It Sy libevent
+Events management but for logs generated by libevent.
+.It Sy privsep
+Privilege separation.
+.It Sy localchassis
+Retrieval of information related to the local chassis.
+.It Sy rpc
+Client communication.
+.It Sy control
+Management of the Unix control socket.
+.It Sy snmp
+SNMP subagent.
+.It Sy libsnmp
+SNMP subagent but for logs generated by NetSNMP.
+.It Sy decode
+Generic PDU decoding.
+.It Sy marshal
+Low-level serialization mechanisms.
+.It Sy alloc
+Low-level allocation mechanisms.
+.It Sy send
+Sending PDU to some interface.
+.It Sy receive
+Receiving PDU from some interface.
+.It Sy loop
+Main loop.
+.It Sy smartfilter
+Smart filtering of different protocols on the same port.
+.It Sy netlink
+Netlink subsystem.
+.El
+.It Fl p Ar pidfile
+Use the provided PID file to record
+.Nm
+PID instead of @LLDPD_PID_FILE@.
+.It Fl k
+Disable advertising of kernel release, version and machine. Kernel name
+(ie: Linux) will still be shared, and Inventory software version will be set
+to 'Unknown'.
+.It Fl S Ar description
+Override system description with the provided description. The default
+description is the kernel name, the node name, the kernel version, the
+build date and the architecture (except if you use the
+.Fl k
+flag described above).
+.It Fl P Ar platform
+Override the CDP platform name with the provided value. The default
+description is the kernel name (Linux).
+.It Fl x
+Enable SNMP subagent.
+With this option,
+.Nm
+will enable an SNMP subagent using AgentX protocol. This allows you to
+get information about local system and remote systems through SNMP.
+.It Fl X Ar socket
+Enable SNMP subagent using the specified socket.
+.Nm
+will enable an SNMP subagent using AgentX protocol for the given
+socket. This option implies the previous one. The default socket is
+usually
+.Em /var/agentx/master .
+You can specify a socket like
+.Em tcp:127.0.0.1:705
+for example. Since the process that will open this socket is enclosed
+in a chroot, you need to specify an IP address (not a hostname) when
+using a TCP or UDP socket.
+.It Fl c
+Enable the support of CDP protocol to deal with Cisco routers that do
+not speak LLDP. If repeated, CDPv1 packets will be sent even when
+there is no CDP peer detected. If repeated once again, CDPv2 packets
+will be sent even when there is no CDP peer detected. If repeated once
+again (i.e.
+.Fl cccc ) ,
+CDPv1 will be disabled and CDPv2 will be enabled. If repeated once
+again (i.e.
+.Fl ccccc ) ,
+CDPv1 will be disabled and CDPv2 will be forced.
+.It Fl f
+Enable the support of FDP protocol to deal with Foundry routers that do
+not speak LLDP. If repeated, FDP packets will be sent even when there
+is no FDP peer detected.
+.It Fl s
+Enable the support of SONMP protocol to deal with Nortel routers and
+switches that do not speak LLDP. If repeated, SONMP packets will be
+sent even when there is no SONMP peer detected.
+.It Fl e
+Enable the support of EDP protocol to deal with Extreme routers and
+switches that do not speak LLDP. If repeated, EDP packets will be sent
+even when there is no EDP peer detected.
+.It Fl l
+Force to send LLDP packets even when there is no LLDP peer detected
+but there is a peer speaking another protocol detected. By default,
+LLDP packets are sent when there is a peer speaking LLDP detected or
+when there is no peer at all. If repeated, LLDP is disabled.
+.It Fl r
+Receive-only mode. With this switch,
+.Nm
+will not send any frame. It will only listen to neighbors.
+.It Fl m Ar management
+Specify the management addresses of this system. As for interfaces
+(described below), this option can use wildcards and inversions.
+Without this option, the first IPv4 and the first IPv6 are used. If an
+exact IP address is provided, it is used as a management address
+without any check. If only negative patterns are provided, only one
+IPv4 and one IPv6 addresses are chosen. Otherwise, many of them can be
+selected. If you want to remove IPv6 addresses, you can use
+.Em !*:* .
+If an interface name is matched, the first IPv4 address and the first
+IPv6 address associated to this interface will be chosen.
+.It Fl u Ar file
+Specify the Unix-domain socket used for communication with
+.Xr lldpctl 8 .
+.It Fl I Ar interfaces
+Specify which interface to listen and send LLDPDU to. Without this
+option,
+.Nm
+will use all available physical interfaces. This option can use
+wildcards. Several interfaces can be specified separated by commas.
+It is also possible to remove an interface by prefixing it with an
+exclamation mark. It is possible to allow an interface by
+prefixing it with two exclamation marks. An allowed interface beats
+a forbidden interface which beats a simple matched interface. For
+example, with
+.Em eth*,!eth1,!eth2
+.Nm
+will only use interfaces starting by
+.Em eth
+with the exception of
+.Em eth1
+and
+.Em eth2 .
+While with
+.Em *,!eth*,!!eth1
+.Nm
+will use all interfaces, except interfaces starting by
+.Em eth
+with the exception of
+.Em eth1 .
+When an exact match is found, it will circumvent some tests. For example, if
+.Em eth0.12
+is specified, it will be accepted even if this is a VLAN interface.
+.It Fl C Ar interfaces
+Specify which interfaces to use for computing chassis ID. Without this
+option, all interfaces are considered.
+.Nm
+will take the first MAC address from all the considered interfaces
+to compute the chassis ID. The logic of this option is the same as for
+.Fl I
+flag: you can exclude interfaces with an exclamation mark and use
+globbing to specify several interfaces. If all interfaces are
+removed (with
+.Em !* ) ,
+the system name is used as a chassis ID instead.
+.It Fl M Ar class
+Enable emission of LLDP-MED frame. Depending on the selected class,
+the standard defines which set of TLV should be transmitted. See
+section 10.2.1. Some devices may be strict about this aspect. The
+class should be one of the following value:
+.Bl -tag -width "0:XX" -compact
+.It Sy 1
+Generic Endpoint (Class I)
+.It Sy 2
+Media Endpoint (Class II). In this case, the standard requires to
+define at least one network policy through
+.Nm lldpcli .
+.It Sy 3
+Communication Device Endpoints (Class III). In this case, the standard
+requires to define at least one network policy through
+.Nm lldpcli .
+.It Sy 4
+Network Connectivity Device
+.El
+.It Fl i
+Disable LLDP-MED inventory TLV transmission.
+.Nm
+will still receive (and publish using SNMP if enabled) those LLDP-MED
+TLV but will not send them. Use this option if you don't want to
+transmit sensible information like serial numbers.
+.It Fl H Ar hide
+Filter neighbors. See section
+.Sx FILTERING NEIGHBORS
+for details.
+.It Fl L Ar lldpcli
+Provide an alternative path to
+.Nm lldpcli
+for configuration. If empty, does not use
+.Nm lldpcli
+for configuration.
+.It Fl O Ar configfile
+Override default configuration locations processed by
+.Nm lldpcli
+at start. If a directory is provided, each file contained in it will be read if ending by
+.Sy .conf.
+Order is alphabetical.
+.It Fl v
+Show
+.Nm
+version. When repeated, show more build information.
+.El
+.Sh FILTERING NEIGHBORS
+In a heterogeneous network, you may see several different hosts on the
+same port, even if there is only one physically plugged to this
+port. For example, if you have a Nortel switch running LLDP which is
+plugged to a Cisco switch running CDP and your host is plugged to the
+Cisco switch, you will see the Nortel switch as well because LLDP
+frames are forwarded by the Cisco switch. This may not be what you
+want. The
+.Fl H Ar hide
+parameter will allow you to tell
+.Nm
+to discard some frames that it receives and to avoid to send some
+other frames.
+.Pp
+Incoming filtering and outgoing filtering are
+unrelated. Incoming filtering will hide some remote ports to get you a
+chance to know exactly what equipment is on the other side of the
+network cable. Outgoing filtering will avoid to use some protocols to
+avoid flooding your network with a protocol that is not handled by the
+nearest equipment. Keep in mind that even without filtering,
+.Nm
+will speak protocols for which at least one frame has been received
+and LLDP otherwise (there are other options to change this behaviour,
+for example
+.Fl cc , ss , ee , ll
+and
+.Fl ff
+).
+.Pp
+When enabling incoming filtering,
+.Nm
+will try to select one protocol and filter out neighbors using other
+protocols. To select this protocol, the rule is to take the less used
+protocol. If on one port, you get 12 CDP neighbors and 1 LLDP
+neighbor, this mean that the remote switch speaks LLDP and does not
+filter CDP. Therefore, we select LLDP. When enabling outgoing
+filtering,
+.Nm
+will also try to select one protocol and only speaks this
+protocol. The filtering is done per port. Each port may select a
+different protocol.
+.Pp
+There are two additional criteria when enabling filtering: allowing
+one or several protocols to be selected (in case of a tie) and
+allowing one or several neighbors to be selected. Even when allowing
+several protocols, the rule of selecting the protocols with the less
+neighbors still apply. If
+.Nm
+selects LLDP and CDP, this means they have the same number of
+neighbors. The selection of the neighbor is random. Incoming filtering
+will select a set of neighbors to be displayed while outgoing
+filtering will use the selected set of neighbors to decide which
+protocols to use: if a selected neighbor speaks LLDP and another one
+CDP,
+.Nm
+will speak both CDP and LLDP on this port.
+.Pp
+There are some corner cases. A typical example is a switch speaking
+two protocols (CDP and LLDP for example). You want to get the
+information from the best protocol but you want to speak both
+protocols because some tools use the CDP table and some other the LLDP
+table.
+.Pp
+The table below summarize all accepted values for the
+.Fl H Ar hide
+parameter. The default value is
+.Em 15
+which corresponds to the corner case described above. The
+.Em filter
+column means that filtering is enabled. The
+.Em 1proto
+column tells that only one protocol will be kept. The
+.Em 1neigh
+column tells that only one neighbor will be kept.
+.Pp
+.Bl -column -compact -offset indent "HXXX" "filterX" "1protoX" "1neighX" "filterX" "1protoX" "1neighX"
+.It Ta Ta incoming Ta Ta outgoing Ta
+.It Ta Em filter Ta Em 1proto Ta Em 1neigh Ta Em filter Ta Em 1proto Ta Em 1neigh
+.It Em 0 Ta Ta Ta Ta Ta Ta
+.It Em 1 Ta x Ta x Ta Ta x Ta x Ta
+.It Em 2 Ta x Ta x Ta Ta Ta Ta
+.It Em 3 Ta Ta Ta Ta x Ta x Ta
+.It Em 4 Ta x Ta Ta Ta x Ta Ta
+.It Em 5 Ta x Ta Ta Ta Ta Ta
+.It Em 6 Ta Ta Ta Ta x Ta Ta
+.It Em 7 Ta x Ta x Ta x Ta x Ta x Ta
+.It Em 8 Ta x Ta x Ta x Ta Ta Ta
+.It Em 9 Ta x Ta Ta x Ta x Ta x Ta
+.It Em 10 Ta Ta Ta Ta x Ta Ta x
+.It Em 11 Ta x Ta Ta x Ta Ta Ta
+.It Em 12 Ta x Ta Ta x Ta x Ta Ta x
+.It Em 13 Ta x Ta Ta x Ta x Ta Ta
+.It Em 14 Ta x Ta x Ta Ta x Ta Ta x
+.It Em 15 Ta x Ta x Ta Ta x Ta Ta
+.It Em 16 Ta x Ta x Ta x Ta x Ta Ta x
+.It Em 17 Ta x Ta x Ta x Ta x Ta Ta
+.It Em 18 Ta x Ta Ta Ta x Ta Ta x
+.It Em 19 Ta x Ta Ta Ta x Ta x Ta
+.El
+.Sh FILES
+.Bl -tag -width "@LLDPD_CTL_SOCKET@XX" -compact
+.It @LLDPD_CTL_SOCKET@
+Unix-domain socket used for communication with
+.Xr lldpctl 8 .
+.It @sysconfdir@/lldpd.conf
+Configuration file for
+.Nm .
+Commands in this files are executed by
+.Xr lldpcli 8
+at start.
+.It @sysconfdir@/lldpd.d
+Directory containing configuration files whose commands are executed
+by
+.Xr lldpcli 8
+at start.
+.El
+.Sh SEE ALSO
+.Xr lldpctl 8 ,
+.Xr lldpcli 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/src/daemon/lldpd.c b/src/daemon/lldpd.c
new file mode 100644
index 0000000..4859fb8
--- /dev/null
+++ b/src/daemon/lldpd.c
@@ -0,0 +1,2020 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/*
+ * Copyright (c) 2008 Vincent Bernat <bernat@luffy.cx>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "lldpd.h"
+#include "trace.h"
+
+#include <stdio.h>
+#include <unistd.h>
+#include <errno.h>
+#include <limits.h>
+#include <signal.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <time.h>
+#include <libgen.h>
+#include <assert.h>
+#include <sys/utsname.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/socket.h>
+#include <sys/select.h>
+#include <sys/time.h>
+#include <sys/ioctl.h>
+#include <arpa/inet.h>
+#include <netinet/if_ether.h>
+#include <pwd.h>
+#include <grp.h>
+
+#if HAVE_VFORK_H
+# include <vfork.h>
+#endif
+#if HAVE_WORKING_FORK
+# define vfork fork
+#endif
+
+static void usage(void);
+
+static struct protocol protos[] = {
+ { LLDPD_MODE_LLDP, 1, "LLDP", 'l', lldp_send, lldp_decode, NULL,
+ { LLDP_ADDR_NEAREST_BRIDGE, LLDP_ADDR_NEAREST_NONTPMR_BRIDGE,
+ LLDP_ADDR_NEAREST_CUSTOMER_BRIDGE } },
+#ifdef ENABLE_CDP
+ { LLDPD_MODE_CDPV1, 0, "CDPv1", 'c', cdpv1_send, cdp_decode, cdpv1_guess,
+ { CDP_MULTICAST_ADDR } },
+ { LLDPD_MODE_CDPV2, 0, "CDPv2", 'c', cdpv2_send, cdp_decode, cdpv2_guess,
+ { CDP_MULTICAST_ADDR } },
+#endif
+#ifdef ENABLE_SONMP
+ { LLDPD_MODE_SONMP, 0, "SONMP", 's', sonmp_send, sonmp_decode, NULL,
+ { SONMP_MULTICAST_ADDR } },
+#endif
+#ifdef ENABLE_EDP
+ { LLDPD_MODE_EDP, 0, "EDP", 'e', edp_send, edp_decode, NULL,
+ { EDP_MULTICAST_ADDR } },
+#endif
+#ifdef ENABLE_FDP
+ { LLDPD_MODE_FDP, 0, "FDP", 'f', fdp_send, cdp_decode, NULL,
+ { FDP_MULTICAST_ADDR } },
+#endif
+ { 0, 0, "any", ' ', NULL, NULL, NULL, { { 0, 0, 0, 0, 0, 0 } } }
+};
+
+static char **saved_argv;
+#ifdef HAVE___PROGNAME
+extern const char *__progname;
+#else
+# define __progname "lldpd"
+#endif
+
+static void
+usage(void)
+{
+ fprintf(stderr, "Usage: %s [OPTIONS ...]\n", __progname);
+ fprintf(stderr, "Version: %s\n", PACKAGE_STRING);
+
+ fprintf(stderr, "\n");
+
+ fprintf(stderr, "-d Do not daemonize.\n");
+ fprintf(stderr, "-r Receive-only mode\n");
+ fprintf(stderr, "-i Disable LLDP-MED inventory TLV transmission.\n");
+ fprintf(stderr,
+ "-k Disable advertising of kernel release, version, machine.\n");
+ fprintf(stderr, "-S descr Override the default system description.\n");
+ fprintf(stderr, "-P name Override the default hardware platform.\n");
+ fprintf(stderr,
+ "-m IP Specify the IP management addresses of this system.\n");
+ fprintf(stderr,
+ "-u file Specify the Unix-domain socket used for communication with lldpctl(8).\n");
+ fprintf(stderr,
+ "-H mode Specify the behaviour when detecting multiple neighbors.\n");
+ fprintf(stderr, "-I iface Limit interfaces to use.\n");
+ fprintf(stderr, "-C iface Limit interfaces to use for computing chassis ID.\n");
+ fprintf(stderr, "-L path Override path for lldpcli command.\n");
+ fprintf(stderr,
+ "-O file Override default configuration locations processed by lldpcli(8) at start.\n");
+#ifdef ENABLE_LLDPMED
+ fprintf(stderr,
+ "-M class Enable emission of LLDP-MED frame. 'class' should be one of:\n");
+ fprintf(stderr, " 1 Generic Endpoint (Class I)\n");
+ fprintf(stderr, " 2 Media Endpoint (Class II)\n");
+ fprintf(stderr, " 3 Communication Device Endpoints (Class III)\n");
+ fprintf(stderr, " 4 Network Connectivity Device\n");
+#endif
+#ifdef USE_SNMP
+ fprintf(stderr, "-x Enable SNMP subagent.\n");
+ fprintf(stderr, "-X sock Specify the SNMP subagent socket.\n");
+#endif
+ fprintf(stderr, "\n");
+
+#if defined ENABLE_CDP || defined ENABLE_EDP || defined ENABLE_FDP || \
+ defined ENABLE_SONMP
+ fprintf(stderr, "Additional protocol support.\n");
+# ifdef ENABLE_CDP
+ fprintf(stderr, "-c Enable the support of CDP protocol. (Cisco)\n");
+# endif
+# ifdef ENABLE_EDP
+ fprintf(stderr, "-e Enable the support of EDP protocol. (Extreme)\n");
+# endif
+# ifdef ENABLE_FDP
+ fprintf(stderr, "-f Enable the support of FDP protocol. (Foundry)\n");
+# endif
+# ifdef ENABLE_SONMP
+ fprintf(stderr, "-s Enable the support of SONMP protocol. (Nortel)\n");
+# endif
+
+ fprintf(stderr, "\n");
+#endif
+
+ fprintf(stderr, "See manual page lldpd(8) for more information\n");
+ exit(1);
+}
+
+struct lldpd_hardware *
+lldpd_get_hardware(struct lldpd *cfg, char *name, int index)
+{
+ struct lldpd_hardware *hardware;
+ TAILQ_FOREACH (hardware, &cfg->g_hardware, h_entries) {
+ if (strcmp(hardware->h_ifname, name) == 0) {
+ if (hardware->h_flags == 0) {
+ if (hardware->h_ifindex != 0 &&
+ hardware->h_ifindex != index) {
+ log_debug("interfaces",
+ "%s changed index: from %d to %d",
+ hardware->h_ifname, hardware->h_ifindex,
+ index);
+ hardware->h_ifindex_changed = 1;
+ }
+ hardware->h_ifindex = index;
+ break;
+ }
+ if (hardware->h_ifindex == index) break;
+ }
+ }
+ return hardware;
+}
+
+/**
+ * Allocate the default local port. This port will be cloned each time we need a
+ * new local port.
+ */
+static void
+lldpd_alloc_default_local_port(struct lldpd *cfg)
+{
+ struct lldpd_port *port;
+
+ if ((port = (struct lldpd_port *)calloc(1, sizeof(struct lldpd_port))) == NULL)
+ fatal("main", NULL);
+
+#ifdef ENABLE_DOT1
+ TAILQ_INIT(&port->p_vlans);
+ TAILQ_INIT(&port->p_ppvids);
+ TAILQ_INIT(&port->p_pids);
+#endif
+#ifdef ENABLE_CUSTOM
+ TAILQ_INIT(&port->p_custom_list);
+#endif
+ cfg->g_default_local_port = port;
+}
+
+/**
+ * Clone a given port. The destination needs to be already allocated.
+ */
+static int
+lldpd_clone_port(struct lldpd_port *destination, struct lldpd_port *source)
+{
+
+ u_int8_t *output = NULL;
+ ssize_t output_len;
+ struct lldpd_port *cloned = NULL;
+ output_len = lldpd_port_serialize(source, (void **)&output);
+ if (output_len == -1 ||
+ lldpd_port_unserialize(output, output_len, &cloned) <= 0) {
+ log_warnx("alloc", "unable to clone default port");
+ free(output);
+ return -1;
+ }
+ memcpy(destination, cloned, sizeof(struct lldpd_port));
+ free(cloned);
+ free(output);
+#ifdef ENABLE_DOT1
+ marshal_repair_tailq(lldpd_vlan, &destination->p_vlans, v_entries);
+ marshal_repair_tailq(lldpd_ppvid, &destination->p_ppvids, p_entries);
+ marshal_repair_tailq(lldpd_pi, &destination->p_pids, p_entries);
+#endif
+#ifdef ENABLE_CUSTOM
+ marshal_repair_tailq(lldpd_custom, &destination->p_custom_list, next);
+#endif
+ return 0;
+}
+
+struct lldpd_hardware *
+lldpd_alloc_hardware(struct lldpd *cfg, char *name, int index)
+{
+ struct lldpd_hardware *hardware;
+
+ log_debug("alloc", "allocate a new local port (%s)", name);
+
+ if ((hardware = (struct lldpd_hardware *)calloc(1,
+ sizeof(struct lldpd_hardware))) == NULL)
+ return NULL;
+
+ /* Clone default local port */
+ if (lldpd_clone_port(&hardware->h_lport, cfg->g_default_local_port) == -1) {
+ log_warnx("alloc", "unable to clone default port");
+ free(hardware);
+ return NULL;
+ }
+
+ hardware->h_cfg = cfg;
+ strlcpy(hardware->h_ifname, name, sizeof(hardware->h_ifname));
+ hardware->h_ifindex = index;
+ hardware->h_lport.p_chassis = LOCAL_CHASSIS(cfg);
+ hardware->h_lport.p_chassis->c_refcount++;
+ TAILQ_INIT(&hardware->h_rports);
+
+#ifdef ENABLE_LLDPMED
+ if (LOCAL_CHASSIS(cfg)->c_med_cap_available) {
+ hardware->h_lport.p_med_cap_enabled = LLDP_MED_CAP_CAP;
+ if (!cfg->g_config.c_noinventory)
+ hardware->h_lport.p_med_cap_enabled |= LLDP_MED_CAP_IV;
+ }
+#endif
+
+ levent_hardware_init(hardware);
+ return hardware;
+}
+
+struct lldpd_mgmt *
+lldpd_alloc_mgmt(int family, void *addrptr, size_t addrsize, u_int32_t iface)
+{
+ struct lldpd_mgmt *mgmt;
+
+ log_debug("alloc", "allocate a new management address (family: %d)", family);
+
+ if (family <= LLDPD_AF_UNSPEC || family >= LLDPD_AF_LAST) {
+ errno = EAFNOSUPPORT;
+ return NULL;
+ }
+ if (addrsize > LLDPD_MGMT_MAXADDRSIZE) {
+ errno = EOVERFLOW;
+ return NULL;
+ }
+ mgmt = calloc(1, sizeof(struct lldpd_mgmt));
+ if (mgmt == NULL) {
+ errno = ENOMEM;
+ return NULL;
+ }
+ mgmt->m_family = family;
+ memcpy(&mgmt->m_addr, addrptr, addrsize);
+ mgmt->m_addrsize = addrsize;
+ mgmt->m_iface = iface;
+ return mgmt;
+}
+
+void
+lldpd_hardware_cleanup(struct lldpd *cfg, struct lldpd_hardware *hardware)
+{
+ log_debug("alloc", "cleanup hardware port %s", hardware->h_ifname);
+
+ free(hardware->h_lport_previous);
+ free(hardware->h_lchassis_previous_id);
+ free(hardware->h_lport_previous_id);
+ free(hardware->h_ifdescr_previous);
+ lldpd_port_cleanup(&hardware->h_lport, 1);
+ if (hardware->h_ops && hardware->h_ops->cleanup)
+ hardware->h_ops->cleanup(cfg, hardware);
+ levent_hardware_release(hardware);
+ free(hardware);
+}
+
+static void
+lldpd_ifdescr_neighbors(struct lldpd *cfg)
+{
+ if (!cfg->g_config.c_set_ifdescr) return;
+ struct lldpd_hardware *hardware;
+ TAILQ_FOREACH (hardware, &cfg->g_hardware, h_entries) {
+ struct lldpd_port *port;
+ char *description;
+ const char *neighbor = NULL;
+ unsigned neighbors = 0;
+ TAILQ_FOREACH (port, &hardware->h_rports, p_entries) {
+ if (SMART_HIDDEN(port)) continue;
+ neighbors++;
+ neighbor = port->p_chassis->c_name;
+ }
+ if (neighbors == 0)
+ description = strdup("");
+ else if (neighbors == 1 && neighbor && *neighbor != '\0') {
+ if (asprintf(&description, "%s", neighbor) == -1) {
+ continue;
+ }
+ } else {
+ if (asprintf(&description, "%d neighbor%s", neighbors,
+ (neighbors > 1) ? "s" : "") == -1) {
+ continue;
+ }
+ }
+ if (hardware->h_ifdescr_previous == NULL ||
+ strcmp(hardware->h_ifdescr_previous, description)) {
+ priv_iface_description(hardware->h_ifname, description);
+ free(hardware->h_ifdescr_previous);
+ hardware->h_ifdescr_previous = description;
+ } else
+ free(description);
+ }
+}
+
+static void
+lldpd_count_neighbors(struct lldpd *cfg)
+{
+#if HAVE_SETPROCTITLE
+ struct lldpd_chassis *chassis;
+ const char *neighbor;
+ unsigned neighbors = 0;
+ TAILQ_FOREACH (chassis, &cfg->g_chassis, c_entries) {
+ neighbors++;
+ neighbor = chassis->c_name;
+ }
+ neighbors--;
+ if (neighbors == 0)
+ setproctitle("no neighbor.");
+ else if (neighbors == 1 && neighbor && *neighbor != '\0')
+ setproctitle("connected to %s.", neighbor);
+ else
+ setproctitle("%d neighbor%s.", neighbors, (neighbors > 1) ? "s" : "");
+#endif
+ lldpd_ifdescr_neighbors(cfg);
+}
+
+static void
+notify_clients_deletion(struct lldpd_hardware *hardware, struct lldpd_port *rport)
+{
+ TRACE(LLDPD_NEIGHBOR_DELETE(hardware->h_ifname, rport->p_chassis->c_name,
+ rport->p_descr));
+ levent_ctl_notify(hardware->h_ifname, NEIGHBOR_CHANGE_DELETED, rport);
+#ifdef USE_SNMP
+ agent_notify(hardware, NEIGHBOR_CHANGE_DELETED, rport);
+#endif
+}
+
+static void
+lldpd_reset_timer(struct lldpd *cfg)
+{
+ /* Reset timer for ports that have been changed. */
+ struct lldpd_hardware *hardware;
+ TAILQ_FOREACH (hardware, &cfg->g_hardware, h_entries) {
+ /* We keep a flat copy of the local port to see if there is any
+ * change. To do this, we zero out fields that are not
+ * significant, marshal the port, then restore. */
+ struct lldpd_port *port = &hardware->h_lport;
+ /* Take the current flags into account to detect a change. */
+ port->_p_hardware_flags = hardware->h_flags;
+ u_int8_t *output = NULL;
+ ssize_t output_len;
+ char save[LLDPD_PORT_START_MARKER];
+ memcpy(save, port, sizeof(save));
+ /* coverity[sizeof_mismatch]
+ We intentionally partially memset port */
+ memset(port, 0, sizeof(save));
+ output_len = lldpd_port_serialize(port, (void **)&output);
+ memcpy(port, save, sizeof(save));
+ if (output_len == -1) {
+ log_warnx("localchassis",
+ "unable to serialize local port %s to check for differences",
+ hardware->h_ifname);
+ continue;
+ }
+
+ /* Compare with the previous value */
+ if (!hardware->h_ifindex_changed && hardware->h_lport_previous &&
+ output_len == hardware->h_lport_previous_len &&
+ !memcmp(output, hardware->h_lport_previous, output_len)) {
+ log_debug("localchassis", "no change detected for port %s",
+ hardware->h_ifname);
+ } else {
+ log_debug("localchassis",
+ "change detected for port %s, resetting its timer",
+ hardware->h_ifname);
+ hardware->h_ifindex_changed = 0;
+ levent_schedule_pdu(hardware);
+ }
+
+ /* Update the value */
+ free(hardware->h_lport_previous);
+ hardware->h_lport_previous = output;
+ hardware->h_lport_previous_len = output_len;
+ }
+}
+
+static void
+lldpd_all_chassis_cleanup(struct lldpd *cfg)
+{
+ struct lldpd_chassis *chassis, *chassis_next;
+ log_debug("localchassis", "cleanup all chassis");
+
+ for (chassis = TAILQ_FIRST(&cfg->g_chassis); chassis; chassis = chassis_next) {
+ chassis_next = TAILQ_NEXT(chassis, c_entries);
+ if (chassis->c_refcount == 0) {
+ TAILQ_REMOVE(&cfg->g_chassis, chassis, c_entries);
+ lldpd_chassis_cleanup(chassis, 1);
+ }
+ }
+}
+
+void
+lldpd_cleanup(struct lldpd *cfg)
+{
+ struct lldpd_hardware *hardware, *hardware_next;
+
+ log_debug("localchassis", "cleanup all ports");
+
+ for (hardware = TAILQ_FIRST(&cfg->g_hardware); hardware != NULL;
+ hardware = hardware_next) {
+ hardware_next = TAILQ_NEXT(hardware, h_entries);
+ if (!hardware->h_flags) {
+ int m = cfg->g_config.c_perm_ifaces ?
+ pattern_match(hardware->h_ifname,
+ cfg->g_config.c_perm_ifaces, 0) :
+ 0;
+ switch (m) {
+ case PATTERN_MATCH_DENIED:
+ log_debug("localchassis",
+ "delete non-permanent interface %s",
+ hardware->h_ifname);
+ TRACE(LLDPD_INTERFACES_DELETE(hardware->h_ifname));
+ TAILQ_REMOVE(&cfg->g_hardware, hardware, h_entries);
+ lldpd_remote_cleanup(hardware, notify_clients_deletion,
+ 1);
+ lldpd_hardware_cleanup(cfg, hardware);
+ break;
+ case PATTERN_MATCH_ALLOWED:
+ case PATTERN_MATCH_ALLOWED_EXACT:
+ log_debug("localchassis", "do not delete %s, permanent",
+ hardware->h_ifname);
+ lldpd_remote_cleanup(hardware, notify_clients_deletion,
+ 1);
+ break;
+ }
+ } else {
+ lldpd_remote_cleanup(hardware, notify_clients_deletion,
+ !(hardware->h_flags & IFF_RUNNING));
+ }
+ }
+
+ levent_schedule_cleanup(cfg);
+ lldpd_all_chassis_cleanup(cfg);
+ lldpd_count_neighbors(cfg);
+}
+
+/* Update chassis `ochassis' with values from `chassis'. The later one is not
+ expected to be part of a list! It will also be wiped from memory. */
+static void
+lldpd_move_chassis(struct lldpd_chassis *ochassis, struct lldpd_chassis *chassis)
+{
+ struct lldpd_mgmt *mgmt, *mgmt_next;
+
+ /* We want to keep refcount, index and list stuff from the current
+ * chassis */
+ TAILQ_ENTRY(lldpd_chassis) entries;
+ int refcount = ochassis->c_refcount;
+ int index = ochassis->c_index;
+ memcpy(&entries, &ochassis->c_entries, sizeof(entries));
+ lldpd_chassis_cleanup(ochassis, 0);
+
+ /* Make the copy. */
+ /* WARNING: this is a kludgy hack, we need in-place copy and cannot use
+ * marshaling. */
+ memcpy(ochassis, chassis, sizeof(struct lldpd_chassis));
+ TAILQ_INIT(&ochassis->c_mgmt);
+
+ /* Copy of management addresses */
+ for (mgmt = TAILQ_FIRST(&chassis->c_mgmt); mgmt != NULL; mgmt = mgmt_next) {
+ mgmt_next = TAILQ_NEXT(mgmt, m_entries);
+ TAILQ_REMOVE(&chassis->c_mgmt, mgmt, m_entries);
+ TAILQ_INSERT_TAIL(&ochassis->c_mgmt, mgmt, m_entries);
+ }
+
+ /* Restore saved values */
+ ochassis->c_refcount = refcount;
+ ochassis->c_index = index;
+ memcpy(&ochassis->c_entries, &entries, sizeof(entries));
+
+ /* Get rid of the new chassis */
+ free(chassis);
+}
+
+static int
+lldpd_guess_type(struct lldpd *cfg, char *frame, int s)
+{
+ size_t i, j;
+ if (s < ETHER_ADDR_LEN) return -1;
+ for (i = 0; cfg->g_protocols[i].mode != 0; i++) {
+ if (!cfg->g_protocols[i].enabled) continue;
+ if (cfg->g_protocols[i].guess == NULL) {
+ for (j = 0; j < sizeof(cfg->g_protocols[0].mac) /
+ sizeof(cfg->g_protocols[0].mac[0]);
+ j++) {
+ if (memcmp(frame, cfg->g_protocols[i].mac[j],
+ ETHER_ADDR_LEN) == 0) {
+ log_debug("decode",
+ "guessed protocol is %s (from MAC address)",
+ cfg->g_protocols[i].name);
+ return cfg->g_protocols[i].mode;
+ }
+ }
+ } else {
+ if (cfg->g_protocols[i].guess(frame, s)) {
+ log_debug("decode",
+ "guessed protocol is %s (from detector function)",
+ cfg->g_protocols[i].name);
+ return cfg->g_protocols[i].mode;
+ }
+ }
+ }
+ return -1;
+}
+
+static void
+lldpd_decode(struct lldpd *cfg, char *frame, int s, struct lldpd_hardware *hardware)
+{
+ int i;
+ struct lldpd_chassis *chassis, *ochassis = NULL;
+ struct lldpd_port *port, *oport = NULL, *aport;
+ int guess = LLDPD_MODE_LLDP;
+
+ log_debug("decode", "decode a received frame on %s", hardware->h_ifname);
+
+ if (s < sizeof(struct ether_header) + 4) {
+ /* Too short, just discard it */
+ hardware->h_rx_discarded_cnt++;
+ return;
+ }
+
+ /* Decapsulate VLAN frames */
+ struct ether_header eheader;
+ memcpy(&eheader, frame, sizeof(struct ether_header));
+ if (eheader.ether_type == htons(ETHERTYPE_VLAN)) {
+ /* VLAN decapsulation means to shift 4 bytes left the frame from
+ * offset 2*ETHER_ADDR_LEN */
+ memmove(frame + 2 * ETHER_ADDR_LEN, frame + 2 * ETHER_ADDR_LEN + 4,
+ s - 2 * ETHER_ADDR_LEN);
+ s -= 4;
+ }
+
+ TAILQ_FOREACH (oport, &hardware->h_rports, p_entries) {
+ if ((oport->p_lastframe != NULL) && (oport->p_lastframe->size == s) &&
+ (memcmp(oport->p_lastframe->frame, frame, s) == 0)) {
+ /* Already received the same frame */
+ log_debug("decode", "duplicate frame, no need to decode");
+ oport->p_lastupdate = time(NULL);
+ return;
+ }
+ }
+
+ guess = lldpd_guess_type(cfg, frame, s);
+ for (i = 0; cfg->g_protocols[i].mode != 0; i++) {
+ if (!cfg->g_protocols[i].enabled) continue;
+ if (cfg->g_protocols[i].mode == guess) {
+ log_debug("decode", "using decode function for %s protocol",
+ cfg->g_protocols[i].name);
+ if (cfg->g_protocols[i].decode(cfg, frame, s, hardware,
+ &chassis, &port) == -1) {
+ log_debug("decode",
+ "function for %s protocol did not decode this frame",
+ cfg->g_protocols[i].name);
+ hardware->h_rx_discarded_cnt++;
+ return;
+ }
+ chassis->c_protocol = port->p_protocol =
+ cfg->g_protocols[i].mode;
+ break;
+ }
+ }
+ if (cfg->g_protocols[i].mode == 0) {
+ log_debug("decode", "unable to guess frame type on %s",
+ hardware->h_ifname);
+ return;
+ }
+ TRACE(LLDPD_FRAME_DECODED(hardware->h_ifname, cfg->g_protocols[i].name,
+ chassis->c_name, port->p_descr));
+
+ /* Do we already have the same MSAP somewhere? */
+ int count = 0;
+ log_debug("decode", "search for the same MSAP");
+ TAILQ_FOREACH (oport, &hardware->h_rports, p_entries) {
+ if (port->p_protocol == oport->p_protocol) {
+ count++;
+ if ((port->p_id_subtype == oport->p_id_subtype) &&
+ (port->p_id_len == oport->p_id_len) &&
+ (memcmp(port->p_id, oport->p_id, port->p_id_len) == 0) &&
+ (chassis->c_id_subtype == oport->p_chassis->c_id_subtype) &&
+ (chassis->c_id_len == oport->p_chassis->c_id_len) &&
+ (memcmp(chassis->c_id, oport->p_chassis->c_id,
+ chassis->c_id_len) == 0)) {
+ ochassis = oport->p_chassis;
+ log_debug("decode", "MSAP is already known");
+ break;
+ }
+ }
+ }
+ /* Do we have room for a new MSAP? */
+ if (!oport && cfg->g_config.c_max_neighbors) {
+ if (count == (cfg->g_config.c_max_neighbors - 1)) {
+ log_debug("decode",
+ "max neighbors %d reached for port %s, "
+ "dropping any new ones silently",
+ cfg->g_config.c_max_neighbors, hardware->h_ifname);
+ } else if (count > cfg->g_config.c_max_neighbors - 1) {
+ log_debug("decode",
+ "too many neighbors for port %s, drop this new one",
+ hardware->h_ifname);
+ lldpd_port_cleanup(port, 1);
+ lldpd_chassis_cleanup(chassis, 1);
+ free(port);
+ return;
+ }
+ }
+ /* No, but do we already know the system? */
+ if (!oport) {
+ log_debug("decode", "MSAP is unknown, search for the chassis");
+ TAILQ_FOREACH (ochassis, &cfg->g_chassis, c_entries) {
+ if ((chassis->c_protocol == ochassis->c_protocol) &&
+ (chassis->c_id_subtype == ochassis->c_id_subtype) &&
+ (chassis->c_id_len == ochassis->c_id_len) &&
+ (memcmp(chassis->c_id, ochassis->c_id, chassis->c_id_len) ==
+ 0))
+ break;
+ }
+ }
+
+ if (oport) {
+ /* The port is known, remove it before adding it back */
+ TAILQ_REMOVE(&hardware->h_rports, oport, p_entries);
+ lldpd_port_cleanup(oport, 1);
+ free(oport);
+ }
+ if (ochassis) {
+ if (port->p_ttl == 0) {
+ /* Shutdown LLDPDU is special. We do not want to replace
+ * the chassis. Free the new chassis (which is mostly empty) */
+ log_debug("decode", "received a shutdown LLDPDU");
+ lldpd_chassis_cleanup(chassis, 1);
+ } else {
+ lldpd_move_chassis(ochassis, chassis);
+ }
+ chassis = ochassis;
+ } else {
+ /* Chassis not known, add it */
+ log_debug("decode", "unknown chassis, add it to the list");
+ chassis->c_index = ++cfg->g_lastrid;
+ chassis->c_refcount = 0;
+ TAILQ_INSERT_TAIL(&cfg->g_chassis, chassis, c_entries);
+ i = 0;
+ TAILQ_FOREACH (ochassis, &cfg->g_chassis, c_entries)
+ i++;
+ log_debug("decode", "%d different systems are known", i);
+ }
+ /* Add port */
+ port->p_lastchange = port->p_lastupdate = time(NULL);
+ if ((port->p_lastframe = (struct lldpd_frame *)malloc(
+ s + sizeof(struct lldpd_frame))) != NULL) {
+ port->p_lastframe->size = s;
+ memcpy(port->p_lastframe->frame, frame, s);
+ }
+ TAILQ_INSERT_TAIL(&hardware->h_rports, port, p_entries);
+ port->p_chassis = chassis;
+ port->p_chassis->c_refcount++;
+ /* Several cases are possible :
+ 1. chassis is new, its refcount was 0. It is now attached
+ to this port, its refcount is 1.
+ 2. chassis already exists and was attached to another
+ port, we increase its refcount accordingly.
+ 3. chassis already exists and was attached to the same
+ port, its refcount was decreased with
+ lldpd_port_cleanup() and is now increased again.
+
+ In all cases, if the port already existed, it has been
+ freed with lldpd_port_cleanup() and therefore, the refcount
+ of the chassis that was attached to it is decreased.
+ */
+ i = 0;
+ /* coverity[use_after_free]
+ TAILQ_REMOVE does the right thing */
+ TAILQ_FOREACH (aport, &hardware->h_rports, p_entries)
+ i++;
+ log_debug("decode", "%d neighbors for %s", i, hardware->h_ifname);
+
+ if (!oport) hardware->h_insert_cnt++;
+
+ /* Notify */
+ log_debug("decode", "send notifications for changes on %s", hardware->h_ifname);
+ if (oport) {
+ TRACE(LLDPD_NEIGHBOR_UPDATE(hardware->h_ifname, chassis->c_name,
+ port->p_descr, i));
+ levent_ctl_notify(hardware->h_ifname, NEIGHBOR_CHANGE_UPDATED, port);
+#ifdef USE_SNMP
+ agent_notify(hardware, NEIGHBOR_CHANGE_UPDATED, port);
+#endif
+ } else {
+ TRACE(LLDPD_NEIGHBOR_NEW(hardware->h_ifname, chassis->c_name,
+ port->p_descr, i));
+ levent_ctl_notify(hardware->h_ifname, NEIGHBOR_CHANGE_ADDED, port);
+#ifdef USE_SNMP
+ agent_notify(hardware, NEIGHBOR_CHANGE_ADDED, port);
+#endif
+ }
+
+#ifdef ENABLE_LLDPMED
+ if (!oport && port->p_chassis->c_med_type) {
+ /* New neighbor, fast start */
+ if (hardware->h_cfg->g_config.c_enable_fast_start &&
+ !hardware->h_tx_fast) {
+ log_debug("decode",
+ "%s: entering fast start due to "
+ "new neighbor",
+ hardware->h_ifname);
+ hardware->h_tx_fast = hardware->h_cfg->g_config.c_tx_fast_init;
+ }
+
+ levent_schedule_pdu(hardware);
+ }
+#endif
+
+ return;
+}
+
+/* Get the output of lsb_release -s -d. This is a slow function. It should be
+ called once. It return NULL if any problem happens. Otherwise, this is a
+ statically allocated buffer. The result includes the trailing \n */
+static char *
+lldpd_get_lsb_release()
+{
+ static char release[1024];
+ char cmd[][12] = { "lsb_release", "-s", "-d" };
+ char *const command[] = { cmd[0], cmd[1], cmd[2], NULL };
+ int pid, status, devnull, count;
+ int pipefd[2];
+
+ log_debug("localchassis", "grab LSB release");
+
+ if (pipe(pipefd)) {
+ log_warn("localchassis", "unable to get a pair of pipes");
+ return NULL;
+ }
+
+ pid = vfork();
+ switch (pid) {
+ case -1:
+ log_warn("localchassis", "unable to fork");
+ return NULL;
+ case 0:
+ /* Child, exec lsb_release */
+ close(pipefd[0]);
+ if ((devnull = open("/dev/null", O_RDWR, 0)) != -1) {
+ dup2(devnull, STDIN_FILENO);
+ dup2(devnull, STDERR_FILENO);
+ dup2(pipefd[1], STDOUT_FILENO);
+ if (devnull > 2) close(devnull);
+ if (pipefd[1] > 2) close(pipefd[1]);
+ execvp("lsb_release", command);
+ }
+ _exit(127);
+ break;
+ default:
+ /* Father, read the output from the children */
+ close(pipefd[1]);
+ count = 0;
+ do {
+ status =
+ read(pipefd[0], release + count, sizeof(release) - count);
+ if ((status == -1) && (errno == EINTR)) continue;
+ if (status > 0) count += status;
+ } while (count < sizeof(release) && (status > 0));
+ if (status < 0) {
+ log_info("localchassis", "unable to read from lsb_release");
+ close(pipefd[0]);
+ waitpid(pid, &status, 0);
+ return NULL;
+ }
+ close(pipefd[0]);
+ if (count >= sizeof(release)) {
+ log_info("localchassis", "output of lsb_release is too large");
+ waitpid(pid, &status, 0);
+ return NULL;
+ }
+ status = -1;
+ if (waitpid(pid, &status, 0) != pid) return NULL;
+ if (!WIFEXITED(status) || (WEXITSTATUS(status) != 0)) {
+ log_info("localchassis",
+ "lsb_release information not available");
+ return NULL;
+ }
+ if (!count) {
+ log_info("localchassis",
+ "lsb_release returned an empty string");
+ return NULL;
+ }
+ release[count] = '\0';
+ return release;
+ }
+ /* Should not be here */
+ return NULL;
+}
+
+/* Same like lldpd_get_lsb_release but reads /etc/os-release for PRETTY_NAME=. */
+static char *
+lldpd_get_os_release()
+{
+ static char release[1024];
+ char line[1024];
+ char *key, *val;
+ char *ptr1 = release;
+
+ log_debug("localchassis", "grab OS release");
+ FILE *fp = fopen("/etc/os-release", "r");
+ if (!fp) {
+ log_debug("localchassis", "could not open /etc/os-release");
+ fp = fopen("/usr/lib/os-release", "r");
+ }
+ if (!fp) {
+ log_info("localchassis",
+ "could not open either /etc/os-release or /usr/lib/os-release");
+ return NULL;
+ }
+
+ while ((fgets(line, sizeof(line), fp) != NULL)) {
+ key = strtok(line, "=");
+ val = strtok(NULL, "=");
+
+ if (strncmp(key, "PRETTY_NAME", sizeof(line)) == 0) {
+ strlcpy(release, val, sizeof(line));
+ break;
+ }
+ }
+ fclose(fp);
+
+ /* Remove trailing newline and all " in the string. */
+ ptr1 = release + strlen(release) - 1;
+ while (ptr1 != release && ((*ptr1 == '"') || (*ptr1 == '\n'))) {
+ *ptr1 = '\0';
+ ptr1--;
+ }
+ if (release[0] == '"') return release + 1;
+ return release;
+}
+
+static void
+lldpd_hide_ports(struct lldpd *cfg, struct lldpd_hardware *hardware, int mask)
+{
+ struct lldpd_port *port;
+ int protocols[LLDPD_MODE_MAX + 1];
+ char buffer[256];
+ int i, j, k, found;
+ unsigned int min;
+
+ log_debug("smartfilter", "apply smart filter for port %s", hardware->h_ifname);
+
+ /* Compute the number of occurrences of each protocol */
+ for (i = 0; i <= LLDPD_MODE_MAX; i++)
+ protocols[i] = 0;
+ TAILQ_FOREACH (port, &hardware->h_rports, p_entries)
+ protocols[port->p_protocol]++;
+
+ /* Turn the protocols[] array into an array of
+ enabled/disabled protocols. 1 means enabled, 0
+ means disabled. */
+ min = (unsigned int)-1;
+ for (i = 0; i <= LLDPD_MODE_MAX; i++)
+ if (protocols[i] && (protocols[i] < min)) min = protocols[i];
+ found = 0;
+ for (i = 0; i <= LLDPD_MODE_MAX; i++)
+ if ((protocols[i] == min) && !found) {
+ /* If we need a tie breaker, we take
+ the first protocol only */
+ if (cfg->g_config.c_smart & mask &
+ (SMART_OUTGOING_ONE_PROTO | SMART_INCOMING_ONE_PROTO))
+ found = 1;
+ protocols[i] = 1;
+ } else
+ protocols[i] = 0;
+
+ /* We set the p_hidden flag to 1 if the protocol is disabled */
+ TAILQ_FOREACH (port, &hardware->h_rports, p_entries) {
+ if (mask == SMART_OUTGOING)
+ port->p_hidden_out = protocols[port->p_protocol] ? 0 : 1;
+ else
+ port->p_hidden_in = protocols[port->p_protocol] ? 0 : 1;
+ }
+
+ /* If we want only one neighbor, we take the first one */
+ if (cfg->g_config.c_smart & mask &
+ (SMART_OUTGOING_ONE_NEIGH | SMART_INCOMING_ONE_NEIGH)) {
+ found = 0;
+ TAILQ_FOREACH (port, &hardware->h_rports, p_entries) {
+ if (mask == SMART_OUTGOING) {
+ if (found) port->p_hidden_out = 1;
+ if (!port->p_hidden_out) found = 1;
+ }
+ if (mask == SMART_INCOMING) {
+ if (found) port->p_hidden_in = 1;
+ if (!port->p_hidden_in) found = 1;
+ }
+ }
+ }
+
+ /* Print a debug message summarizing the operation */
+ for (i = 0; i <= LLDPD_MODE_MAX; i++)
+ protocols[i] = 0;
+ k = j = 0;
+ TAILQ_FOREACH (port, &hardware->h_rports, p_entries) {
+ if (!(((mask == SMART_OUTGOING) && port->p_hidden_out) ||
+ ((mask == SMART_INCOMING) && port->p_hidden_in))) {
+ k++;
+ protocols[port->p_protocol] = 1;
+ }
+ j++;
+ }
+ buffer[0] = '\0';
+ for (i = 0; cfg->g_protocols[i].mode != 0; i++) {
+ if (cfg->g_protocols[i].enabled &&
+ protocols[cfg->g_protocols[i].mode]) {
+ if (strlen(buffer) + strlen(cfg->g_protocols[i].name) + 3 >
+ sizeof(buffer)) {
+ /* Unlikely, our buffer is too small */
+ memcpy(buffer + sizeof(buffer) - 4, "...", 4);
+ break;
+ }
+ if (buffer[0])
+ strncat(buffer, ", ",
+ sizeof(buffer) - strlen(buffer) - 1);
+ strncat(buffer, cfg->g_protocols[i].name,
+ sizeof(buffer) - strlen(buffer) - 1);
+ }
+ }
+ log_debug("smartfilter", "%s: %s: %d visible neighbors (out of %d)",
+ hardware->h_ifname, (mask == SMART_OUTGOING) ? "out filter" : "in filter",
+ k, j);
+ log_debug("smartfilter", "%s: protocols: %s", hardware->h_ifname,
+ buffer[0] ? buffer : "(none)");
+}
+
+/* Hide unwanted ports depending on smart mode set by the user */
+static void
+lldpd_hide_all(struct lldpd *cfg)
+{
+ struct lldpd_hardware *hardware;
+
+ if (!cfg->g_config.c_smart) return;
+ log_debug("smartfilter", "apply smart filter results on all ports");
+ TAILQ_FOREACH (hardware, &cfg->g_hardware, h_entries) {
+ if (cfg->g_config.c_smart & SMART_INCOMING_FILTER)
+ lldpd_hide_ports(cfg, hardware, SMART_INCOMING);
+ if (cfg->g_config.c_smart & SMART_OUTGOING_FILTER)
+ lldpd_hide_ports(cfg, hardware, SMART_OUTGOING);
+ }
+}
+
+/* If PD device and PSE allocated power, echo back this change. If we have
+ * several LLDP neighbors, we use the latest updated. */
+static void
+lldpd_dot3_power_pd_pse(struct lldpd_hardware *hardware)
+{
+#ifdef ENABLE_DOT3
+ struct lldpd_port *port, *selected_port = NULL;
+ /* Are we a PD device? */
+ if (hardware->h_lport.p_power.devicetype != LLDP_DOT3_POWER_PD) return;
+ TAILQ_FOREACH (port, &hardware->h_rports, p_entries) {
+ if (port->p_hidden_in) continue;
+
+ if (port->p_protocol != LLDPD_MODE_LLDP &&
+ port->p_protocol != LLDPD_MODE_CDPV2)
+ continue;
+
+ if (port->p_power.devicetype != LLDP_DOT3_POWER_PSE) continue;
+ if (!selected_port || port->p_lastupdate > selected_port->p_lastupdate)
+ selected_port = port;
+ }
+ if (selected_port &&
+ selected_port->p_power.allocated != hardware->h_lport.p_power.allocated) {
+ log_info("receive",
+ "for %s, PSE told us allocated is now %d instead of %d",
+ hardware->h_ifname, selected_port->p_power.allocated,
+ hardware->h_lport.p_power.allocated);
+ hardware->h_lport.p_power.allocated = selected_port->p_power.allocated;
+ levent_schedule_pdu(hardware);
+ }
+
+# ifdef ENABLE_CDP
+ if (selected_port &&
+ selected_port->p_cdp_power.management_id !=
+ hardware->h_lport.p_cdp_power.management_id) {
+ hardware->h_lport.p_cdp_power.management_id =
+ selected_port->p_cdp_power.management_id;
+ }
+# endif
+
+#endif
+}
+
+void
+lldpd_recv(struct lldpd *cfg, struct lldpd_hardware *hardware, int fd)
+{
+ char *buffer = NULL;
+ int n;
+ log_debug("receive", "receive a frame on %s", hardware->h_ifname);
+ if ((buffer = (char *)malloc(hardware->h_mtu)) == NULL) {
+ log_warn("receive", "failed to alloc reception buffer");
+ return;
+ }
+ if ((n = hardware->h_ops->recv(cfg, hardware, fd, buffer, hardware->h_mtu)) ==
+ -1) {
+ log_debug("receive", "discard frame received on %s",
+ hardware->h_ifname);
+ free(buffer);
+ return;
+ }
+ if (hardware->h_lport.p_disable_rx) {
+ log_debug("receive", "RX disabled, ignore the frame on %s",
+ hardware->h_ifname);
+ free(buffer);
+ return;
+ }
+ if (cfg->g_config.c_paused) {
+ log_debug("receive", "paused, ignore the frame on %s",
+ hardware->h_ifname);
+ free(buffer);
+ return;
+ }
+ hardware->h_rx_cnt++;
+ log_debug("receive", "decode received frame on %s", hardware->h_ifname);
+ TRACE(LLDPD_FRAME_RECEIVED(hardware->h_ifname, buffer, (size_t)n));
+ lldpd_decode(cfg, buffer, n, hardware);
+ lldpd_hide_all(cfg); /* Immediatly hide */
+ lldpd_dot3_power_pd_pse(hardware);
+ lldpd_count_neighbors(cfg);
+ free(buffer);
+}
+
+static void
+lldpd_send_shutdown(struct lldpd_hardware *hardware)
+{
+ struct lldpd *cfg = hardware->h_cfg;
+ if (cfg->g_config.c_receiveonly || cfg->g_config.c_paused) return;
+ if (hardware->h_lport.p_disable_tx) return;
+ if ((hardware->h_flags & IFF_RUNNING) == 0) return;
+
+ /* It's safe to call `lldp_send_shutdown()` because shutdown LLDPU will
+ * only be emitted if LLDP was sent on that port. */
+ if (lldp_send_shutdown(hardware->h_cfg, hardware) != 0)
+ log_warnx("send", "unable to send shutdown LLDPDU on %s",
+ hardware->h_ifname);
+}
+
+void
+lldpd_send(struct lldpd_hardware *hardware)
+{
+ struct lldpd *cfg = hardware->h_cfg;
+ struct lldpd_port *port;
+ int i, sent;
+
+ if (cfg->g_config.c_receiveonly || cfg->g_config.c_paused) return;
+ if (hardware->h_lport.p_disable_tx) return;
+ if ((hardware->h_flags & IFF_RUNNING) == 0) return;
+
+ log_debug("send", "send PDU on %s", hardware->h_ifname);
+ sent = 0;
+ for (i = 0; cfg->g_protocols[i].mode != 0; i++) {
+ if (!cfg->g_protocols[i].enabled) continue;
+ /* We send only if we have at least one remote system
+ * speaking this protocol or if the protocol is forced */
+ if (cfg->g_protocols[i].enabled > 1) {
+ cfg->g_protocols[i].send(cfg, hardware);
+ sent++;
+ continue;
+ }
+ TAILQ_FOREACH (port, &hardware->h_rports, p_entries) {
+ /* If this remote port is disabled, we don't
+ * consider it */
+ if (port->p_hidden_out) continue;
+ if (port->p_protocol == cfg->g_protocols[i].mode) {
+ TRACE(LLDPD_FRAME_SEND(hardware->h_ifname,
+ cfg->g_protocols[i].name));
+ log_debug("send", "send PDU on %s with protocol %s",
+ hardware->h_ifname, cfg->g_protocols[i].name);
+ cfg->g_protocols[i].send(cfg, hardware);
+ hardware->h_lport.p_protocol = cfg->g_protocols[i].mode;
+ sent++;
+ break;
+ }
+ }
+ }
+
+ if (!sent) {
+ /* Nothing was sent for this port, let's speak the first
+ * available protocol. */
+ for (i = 0; cfg->g_protocols[i].mode != 0; i++) {
+ if (!cfg->g_protocols[i].enabled) continue;
+ TRACE(LLDPD_FRAME_SEND(hardware->h_ifname,
+ cfg->g_protocols[i].name));
+ log_debug("send", "fallback to protocol %s for %s",
+ cfg->g_protocols[i].name, hardware->h_ifname);
+ cfg->g_protocols[i].send(cfg, hardware);
+ break;
+ }
+ if (cfg->g_protocols[i].mode == 0)
+ log_warnx("send", "no protocol enabled, dunno what to send");
+ }
+}
+
+#ifdef ENABLE_LLDPMED
+static void
+lldpd_med(struct lldpd *cfg, struct utsname *un)
+{
+ static short int once = 0;
+ if (!once && cfg) {
+ LOCAL_CHASSIS(cfg)->c_med_hw = dmi_hw();
+ LOCAL_CHASSIS(cfg)->c_med_fw = dmi_fw();
+ LOCAL_CHASSIS(cfg)->c_med_sn = dmi_sn();
+ LOCAL_CHASSIS(cfg)->c_med_manuf = dmi_manuf();
+ LOCAL_CHASSIS(cfg)->c_med_model = dmi_model();
+ LOCAL_CHASSIS(cfg)->c_med_asset = dmi_asset();
+ if (un) {
+ if (LOCAL_CHASSIS(cfg)->c_med_sw)
+ free(LOCAL_CHASSIS(cfg)->c_med_sw);
+
+ if (cfg->g_config.c_advertise_version)
+ LOCAL_CHASSIS(cfg)->c_med_sw = strdup(un->release);
+ else
+ LOCAL_CHASSIS(cfg)->c_med_sw = strdup("Unknown");
+ }
+ once = 1;
+ }
+}
+#endif
+
+static int
+lldpd_routing_enabled(struct lldpd *cfg)
+{
+ int routing;
+
+ if ((LOCAL_CHASSIS(cfg)->c_cap_available & LLDP_CAP_ROUTER) == 0) return 0;
+
+ if ((routing = interfaces_routing_enabled(cfg)) == -1) {
+ log_debug("localchassis", "unable to check if routing is enabled");
+ return 0;
+ }
+ return routing;
+}
+
+void
+lldpd_update_localchassis(struct lldpd *cfg)
+{
+ struct utsname un;
+ char *hp;
+
+ log_debug("localchassis", "update information for local chassis");
+ assert(LOCAL_CHASSIS(cfg) != NULL);
+
+ /* Set system name and description */
+ if (uname(&un) < 0) fatal("localchassis", "failed to get system information");
+ if (cfg->g_config.c_hostname) {
+ log_debug("localchassis", "use overridden system name `%s`",
+ cfg->g_config.c_hostname);
+ hp = cfg->g_config.c_hostname;
+ } else {
+ if ((hp = priv_gethostname()) == NULL)
+ fatal("localchassis", "failed to get system name");
+ }
+ free(LOCAL_CHASSIS(cfg)->c_name);
+ free(LOCAL_CHASSIS(cfg)->c_descr);
+ if ((LOCAL_CHASSIS(cfg)->c_name = strdup(hp)) == NULL)
+ fatal("localchassis", NULL);
+ if (cfg->g_config.c_description) {
+ log_debug("localchassis", "use overridden description `%s`",
+ cfg->g_config.c_description);
+ if (asprintf(&LOCAL_CHASSIS(cfg)->c_descr, "%s",
+ cfg->g_config.c_description) == -1)
+ fatal("localchassis", "failed to set full system description");
+ } else {
+ if (cfg->g_config.c_advertise_version) {
+ log_debug("localchassis", "advertise system version");
+ if (asprintf(&LOCAL_CHASSIS(cfg)->c_descr, "%s %s %s %s %s",
+ cfg->g_lsb_release ? cfg->g_lsb_release : "",
+ un.sysname, un.release, un.version, un.machine) == -1)
+ fatal("localchassis",
+ "failed to set full system description");
+ } else {
+ log_debug("localchassis", "do not advertise system version");
+ if (asprintf(&LOCAL_CHASSIS(cfg)->c_descr, "%s",
+ cfg->g_lsb_release ? cfg->g_lsb_release : un.sysname) ==
+ -1)
+ fatal("localchassis",
+ "failed to set minimal system description");
+ }
+ }
+ if (cfg->g_config.c_platform == NULL)
+ cfg->g_config.c_platform = strdup(un.sysname);
+
+ if (!cfg->g_config.c_cap_override) {
+ /* Check routing */
+ if (lldpd_routing_enabled(cfg)) {
+ log_debug("localchassis",
+ "routing is enabled, enable router capability");
+ LOCAL_CHASSIS(cfg)->c_cap_enabled |= LLDP_CAP_ROUTER;
+ } else
+ LOCAL_CHASSIS(cfg)->c_cap_enabled &= ~LLDP_CAP_ROUTER;
+
+#ifdef ENABLE_LLDPMED
+ if (LOCAL_CHASSIS(cfg)->c_cap_available & LLDP_CAP_TELEPHONE)
+ LOCAL_CHASSIS(cfg)->c_cap_enabled |= LLDP_CAP_TELEPHONE;
+ lldpd_med(cfg, &un);
+#endif
+ if ((LOCAL_CHASSIS(cfg)->c_cap_available & LLDP_CAP_STATION) &&
+ (LOCAL_CHASSIS(cfg)->c_cap_enabled == 0))
+ LOCAL_CHASSIS(cfg)->c_cap_enabled = LLDP_CAP_STATION;
+ else if (LOCAL_CHASSIS(cfg)->c_cap_enabled != LLDP_CAP_STATION)
+ LOCAL_CHASSIS(cfg)->c_cap_enabled &= ~LLDP_CAP_STATION;
+ }
+
+ /* Set chassis ID if needed. This is only done if chassis ID
+ has not been set previously (with the MAC address of an
+ interface for example)
+ */
+ if (cfg->g_config.c_cid_string != NULL) {
+ log_debug("localchassis", "use specified chassis ID string");
+ free(LOCAL_CHASSIS(cfg)->c_id);
+ if (!(LOCAL_CHASSIS(cfg)->c_id = strdup(cfg->g_config.c_cid_string)))
+ fatal("localchassis", NULL);
+ LOCAL_CHASSIS(cfg)->c_id_len = strlen(cfg->g_config.c_cid_string);
+ LOCAL_CHASSIS(cfg)->c_id_subtype = LLDP_CHASSISID_SUBTYPE_LOCAL;
+ }
+ if (LOCAL_CHASSIS(cfg)->c_id == NULL) {
+ log_debug("localchassis",
+ "no chassis ID is currently set, use chassis name");
+ if (!(LOCAL_CHASSIS(cfg)->c_id = strdup(LOCAL_CHASSIS(cfg)->c_name)))
+ fatal("localchassis", NULL);
+ LOCAL_CHASSIS(cfg)->c_id_len = strlen(LOCAL_CHASSIS(cfg)->c_name);
+ LOCAL_CHASSIS(cfg)->c_id_subtype = LLDP_CHASSISID_SUBTYPE_LOCAL;
+ }
+}
+
+void
+lldpd_update_localports(struct lldpd *cfg)
+{
+ struct lldpd_hardware *hardware;
+
+ log_debug("localchassis", "update information for local ports");
+
+ /* h_flags is set to 0 for each port. If the port is updated, h_flags
+ * will be set to a non-zero value. This will allow us to clean up any
+ * non up-to-date port */
+ TAILQ_FOREACH (hardware, &cfg->g_hardware, h_entries)
+ hardware->h_flags = 0;
+
+ TRACE(LLDPD_INTERFACES_UPDATE());
+ interfaces_update(cfg);
+ lldpd_cleanup(cfg);
+ lldpd_reset_timer(cfg);
+}
+
+void
+lldpd_loop(struct lldpd *cfg)
+{
+ /* Main loop.
+ 1. Update local ports information
+ 2. Update local chassis information
+ */
+ log_debug("loop", "start new loop");
+ if (!cfg->g_config.c_cap_override) LOCAL_CHASSIS(cfg)->c_cap_enabled = 0;
+ /* Information for local ports is triggered even when it is possible to
+ * update them on some other event because we want to refresh them if we
+ * missed something. */
+ log_debug("loop", "update information for local ports");
+ lldpd_update_localports(cfg);
+ log_debug("loop", "update information for local chassis");
+ lldpd_update_localchassis(cfg);
+ lldpd_count_neighbors(cfg);
+}
+
+static void
+lldpd_exit(struct lldpd *cfg)
+{
+ char *lockname = NULL;
+ struct lldpd_hardware *hardware, *hardware_next;
+ log_debug("main", "exit lldpd");
+
+ TAILQ_FOREACH (hardware, &cfg->g_hardware, h_entries)
+ lldpd_send_shutdown(hardware);
+
+ if (asprintf(&lockname, "%s.lock", cfg->g_ctlname) != -1) {
+ priv_ctl_cleanup(lockname);
+ free(lockname);
+ }
+ close(cfg->g_ctl);
+ priv_ctl_cleanup(cfg->g_ctlname);
+ log_debug("main", "cleanup hardware information");
+ for (hardware = TAILQ_FIRST(&cfg->g_hardware); hardware != NULL;
+ hardware = hardware_next) {
+ hardware_next = TAILQ_NEXT(hardware, h_entries);
+ log_debug("main", "cleanup interface %s", hardware->h_ifname);
+ lldpd_remote_cleanup(hardware, NULL, 1);
+ lldpd_hardware_cleanup(cfg, hardware);
+ }
+ interfaces_cleanup(cfg);
+ lldpd_port_cleanup(cfg->g_default_local_port, 1);
+ lldpd_all_chassis_cleanup(cfg);
+ free(cfg->g_default_local_port);
+ free(cfg->g_config.c_platform);
+ levent_shutdown(cfg);
+}
+
+/**
+ * Run lldpcli to configure lldpd.
+ *
+ * @return PID of running lldpcli or -1 if error.
+ */
+static pid_t
+lldpd_configure(int use_syslog, int debug, const char *path, const char *ctlname,
+ const char *config_path)
+{
+ pid_t lldpcli = vfork();
+ int devnull;
+
+ char sdebug[debug + 4];
+ if (use_syslog)
+ strlcpy(sdebug, "-s", 3);
+ else {
+ /* debug = 0 -> -sd */
+ /* debug = 1 -> -sdd */
+ /* debug = 2 -> -sddd */
+ memset(sdebug, 'd', sizeof(sdebug));
+ sdebug[debug + 3] = '\0';
+ sdebug[0] = '-';
+ sdebug[1] = 's';
+ }
+ log_debug("main", "invoke %s %s", path, sdebug);
+
+ switch (lldpcli) {
+ case -1:
+ log_warn("main", "unable to fork");
+ return -1;
+ case 0:
+ /* Child, exec lldpcli */
+ if ((devnull = open("/dev/null", O_RDWR, 0)) != -1) {
+ dup2(devnull, STDIN_FILENO);
+ dup2(devnull, STDOUT_FILENO);
+ if (devnull > 2) close(devnull);
+
+ if (config_path) {
+ execl(path, "lldpcli", sdebug, "-u", ctlname, "-C",
+ config_path, "resume", (char *)NULL);
+ } else {
+ execl(path, "lldpcli", sdebug, "-u", ctlname, "-C",
+ SYSCONFDIR "/lldpd.conf", "-C",
+ SYSCONFDIR "/lldpd.d", "resume", (char *)NULL);
+ }
+
+ log_warn("main", "unable to execute %s", path);
+ log_warnx("main",
+ "configuration is incomplete, lldpd needs to be unpaused");
+ }
+ _exit(127);
+ break;
+ default:
+ /* Father, don't do anything stupid */
+ return lldpcli;
+ }
+ /* Should not be here */
+ return -1;
+}
+
+struct intint {
+ int a;
+ int b;
+};
+static const struct intint filters[] = { { 0, 0 },
+ { 1,
+ SMART_INCOMING_FILTER | SMART_INCOMING_ONE_PROTO | SMART_OUTGOING_FILTER |
+ SMART_OUTGOING_ONE_PROTO },
+ { 2, SMART_INCOMING_FILTER | SMART_INCOMING_ONE_PROTO },
+ { 3, SMART_OUTGOING_FILTER | SMART_OUTGOING_ONE_PROTO },
+ { 4, SMART_INCOMING_FILTER | SMART_OUTGOING_FILTER },
+ { 5, SMART_INCOMING_FILTER }, { 6, SMART_OUTGOING_FILTER },
+ { 7,
+ SMART_INCOMING_FILTER | SMART_INCOMING_ONE_PROTO |
+ SMART_INCOMING_ONE_NEIGH | SMART_OUTGOING_FILTER |
+ SMART_OUTGOING_ONE_PROTO },
+ { 8,
+ SMART_INCOMING_FILTER | SMART_INCOMING_ONE_PROTO |
+ SMART_INCOMING_ONE_NEIGH },
+ { 9,
+ SMART_INCOMING_FILTER | SMART_INCOMING_ONE_NEIGH | SMART_OUTGOING_FILTER |
+ SMART_OUTGOING_ONE_PROTO },
+ { 10, SMART_OUTGOING_FILTER | SMART_OUTGOING_ONE_NEIGH },
+ { 11, SMART_INCOMING_FILTER | SMART_INCOMING_ONE_NEIGH },
+ { 12,
+ SMART_INCOMING_FILTER | SMART_INCOMING_ONE_NEIGH | SMART_OUTGOING_FILTER |
+ SMART_OUTGOING_ONE_NEIGH },
+ { 13,
+ SMART_INCOMING_FILTER | SMART_INCOMING_ONE_NEIGH | SMART_OUTGOING_FILTER },
+ { 14,
+ SMART_INCOMING_FILTER | SMART_INCOMING_ONE_PROTO | SMART_OUTGOING_FILTER |
+ SMART_OUTGOING_ONE_NEIGH },
+ { 15,
+ SMART_INCOMING_FILTER | SMART_INCOMING_ONE_PROTO | SMART_OUTGOING_FILTER },
+ { 16,
+ SMART_INCOMING_FILTER | SMART_INCOMING_ONE_PROTO |
+ SMART_INCOMING_ONE_NEIGH | SMART_OUTGOING_FILTER |
+ SMART_OUTGOING_ONE_NEIGH },
+ { 17,
+ SMART_INCOMING_FILTER | SMART_INCOMING_ONE_PROTO |
+ SMART_INCOMING_ONE_NEIGH | SMART_OUTGOING_FILTER },
+ { 18,
+ SMART_INCOMING_FILTER | SMART_OUTGOING_FILTER | SMART_OUTGOING_ONE_NEIGH },
+ { 19,
+ SMART_INCOMING_FILTER | SMART_OUTGOING_FILTER | SMART_OUTGOING_ONE_PROTO },
+ { -1, 0 } };
+
+#ifndef HOST_OS_OSX
+/**
+ * Tell if we have been started by systemd.
+ */
+static int
+lldpd_started_by_systemd()
+{
+# ifdef HOST_OS_LINUX
+ int fd = -1;
+ const char *notifysocket = getenv("NOTIFY_SOCKET");
+ if (!notifysocket || !strchr("@/", notifysocket[0]) || strlen(notifysocket) < 2)
+ return 0;
+
+ log_debug("main", "running with systemd, don't fork but signal ready");
+ if ((fd = socket(AF_UNIX, SOCK_DGRAM, 0)) < 0) {
+ log_warn("main", "unable to open systemd notification socket %s",
+ notifysocket);
+ return 0;
+ }
+
+ struct sockaddr_un su = { .sun_family = AF_UNIX };
+ strlcpy(su.sun_path, notifysocket, sizeof(su.sun_path));
+ if (notifysocket[0] == '@') su.sun_path[0] = 0;
+
+ char ready[] = "READY=1";
+ struct iovec iov = { .iov_base = ready, .iov_len = sizeof ready - 1 };
+ struct msghdr hdr = { .msg_name = &su,
+ .msg_namelen =
+ offsetof(struct sockaddr_un, sun_path) + strlen(notifysocket),
+ .msg_iov = &iov,
+ .msg_iovlen = 1 };
+ unsetenv("NOTIFY_SOCKET");
+ if (sendmsg(fd, &hdr, MSG_NOSIGNAL) < 0) {
+ log_warn("main", "unable to send notification to systemd");
+ close(fd);
+ return 0;
+ }
+ close(fd);
+ return 1;
+# else
+ return 0;
+# endif
+}
+#endif
+
+#ifdef HOST_OS_LINUX
+static void
+version_convert(const char *sversion, unsigned iversion[], size_t n)
+{
+ const char *p = sversion;
+ char *end;
+ for (size_t i = 0; i < n; i++) {
+ iversion[i] = strtol(p, &end, 10);
+ if (*end != '.') break;
+ p = end + 1;
+ }
+}
+
+static void
+version_check(void)
+{
+ struct utsname uts;
+ if (uname(&uts) == -1) return;
+ unsigned version_min[3] = {};
+ unsigned version_cur[3] = {};
+ version_convert(uts.release, version_cur, 3);
+ version_convert(MIN_LINUX_KERNEL_VERSION, version_min, 3);
+ if (version_min[0] > version_cur[0] ||
+ (version_min[0] == version_cur[0] && version_min[1] > version_cur[1]) ||
+ (version_min[0] == version_cur[0] && version_min[1] == version_cur[1] &&
+ version_min[2] > version_cur[2])) {
+ log_warnx("lldpd", "minimal kernel version required is %s, got %s",
+ MIN_LINUX_KERNEL_VERSION, uts.release);
+ log_warnx("lldpd",
+ "lldpd may be unable to detect bonds and bridges correctly");
+# ifndef ENABLE_OLDIES
+ log_warnx("lldpd", "consider recompiling with --enable-oldies option");
+# endif
+ }
+}
+#else
+static void
+version_check(void)
+{
+}
+#endif
+
+int
+lldpd_main(int argc, char *argv[], char *envp[])
+{
+ struct lldpd *cfg;
+ struct lldpd_chassis *lchassis;
+ int ch, debug = 0, use_syslog = 1, daemonize = 1;
+ const char *errstr;
+#ifdef USE_SNMP
+ int snmp = 0;
+ const char *agentx = NULL; /* AgentX socket */
+#endif
+ const char *ctlname = NULL;
+ char *mgmtp = NULL;
+ char *cidp = NULL;
+ char *interfaces = NULL;
+ /* We do not want more options here. Please add them in lldpcli instead
+ * unless there is a very good reason. Most command-line options will
+ * get deprecated at some point. */
+ char *popt,
+ opts[] = "H:vhkrdD:p:xX:m:u:4:6:I:C:p:M:P:S:iL:O:@ ";
+ int i, found, advertise_version = 1;
+#ifdef ENABLE_LLDPMED
+ int lldpmed = 0, noinventory = 0;
+ int enable_fast_start = 1;
+#endif
+ char *descr_override = NULL;
+ char *platform_override = NULL;
+ char *lsb_release = NULL;
+ const char *lldpcli = LLDPCLI_PATH;
+ const char *pidfile = LLDPD_PID_FILE;
+ int smart = 15;
+ int receiveonly = 0, version = 0;
+ int ctl;
+ const char *config_file = NULL;
+
+#ifdef ENABLE_PRIVSEP
+ /* Non privileged user */
+ struct passwd *user;
+ struct group *group;
+ uid_t uid;
+ gid_t gid;
+#endif
+
+ saved_argv = argv;
+
+#if HAVE_SETPROCTITLE_INIT
+ setproctitle_init(argc, argv, envp);
+#endif
+
+ /*
+ * Get and parse command line options
+ */
+ if ((popt = strchr(opts, '@')) != NULL) {
+ for (i = 0; protos[i].mode != 0 && *popt != '\0'; i++)
+ *(popt++) = protos[i].arg;
+ *popt = '\0';
+ }
+ while ((ch = getopt(argc, argv, opts)) != -1) {
+ switch (ch) {
+ case 'h':
+ usage();
+ break;
+ case 'v':
+ version++;
+ break;
+ case 'd':
+ if (daemonize)
+ daemonize = 0;
+ else if (use_syslog)
+ use_syslog = 0;
+ else
+ debug++;
+ break;
+ case 'D':
+ log_accept(optarg);
+ break;
+ case 'p':
+ pidfile = optarg;
+ break;
+ case 'r':
+ receiveonly = 1;
+ break;
+ case 'm':
+ if (mgmtp) {
+ fprintf(stderr, "-m can only be used once\n");
+ usage();
+ }
+ mgmtp = strdup(optarg);
+ break;
+ case 'u':
+ if (ctlname) {
+ fprintf(stderr, "-u can only be used once\n");
+ usage();
+ }
+ ctlname = optarg;
+ break;
+ case 'I':
+ if (interfaces) {
+ fprintf(stderr, "-I can only be used once\n");
+ usage();
+ }
+ interfaces = strdup(optarg);
+ break;
+ case 'C':
+ if (cidp) {
+ fprintf(stderr, "-C can only be used once\n");
+ usage();
+ }
+ cidp = strdup(optarg);
+ break;
+ case 'L':
+ if (strlen(optarg))
+ lldpcli = optarg;
+ else
+ lldpcli = NULL;
+ break;
+ case 'k':
+ advertise_version = 0;
+ break;
+#ifdef ENABLE_LLDPMED
+ case 'M':
+ lldpmed = strtonum(optarg, 1, 4, &errstr);
+ if (errstr) {
+ fprintf(stderr,
+ "-M requires an argument between 1 and 4\n");
+ usage();
+ }
+ break;
+ case 'i':
+ noinventory = 1;
+ break;
+#else
+ case 'M':
+ case 'i':
+ fprintf(stderr, "LLDP-MED support is not built-in\n");
+ usage();
+ break;
+#endif
+#ifdef USE_SNMP
+ case 'x':
+ snmp = 1;
+ break;
+ case 'X':
+ if (agentx) {
+ fprintf(stderr, "-X can only be used once\n");
+ usage();
+ }
+ snmp = 1;
+ agentx = optarg;
+ break;
+#else
+ case 'x':
+ case 'X':
+ fprintf(stderr, "SNMP support is not built-in\n");
+ usage();
+#endif
+ break;
+ case 'S':
+ if (descr_override) {
+ fprintf(stderr, "-S can only be used once\n");
+ usage();
+ }
+ descr_override = strdup(optarg);
+ break;
+ case 'P':
+ if (platform_override) {
+ fprintf(stderr, "-P can only be used once\n");
+ usage();
+ }
+ platform_override = strdup(optarg);
+ break;
+ case 'H':
+ smart = strtonum(optarg, 0,
+ sizeof(filters) / sizeof(filters[0]), &errstr);
+ if (errstr) {
+ fprintf(stderr,
+ "-H requires an int between 0 and %zu\n",
+ sizeof(filters) / sizeof(filters[0]));
+ usage();
+ }
+ break;
+ case 'O':
+ if (config_file) {
+ fprintf(stderr, "-O can only be used once\n");
+ usage();
+ }
+ config_file = optarg;
+ break;
+ default:
+ found = 0;
+ for (i = 0; protos[i].mode != 0; i++) {
+ if (ch == protos[i].arg) {
+ found = 1;
+ protos[i].enabled++;
+ }
+ }
+ if (!found) usage();
+ }
+ }
+
+ if (version) {
+ version_display(stdout, "lldpd", version > 1);
+ exit(0);
+ }
+
+ if (ctlname == NULL) ctlname = LLDPD_CTL_SOCKET;
+
+ /* Set correct smart mode */
+ for (i = 0; (filters[i].a != -1) && (filters[i].a != smart); i++)
+ ;
+ if (filters[i].a == -1) {
+ fprintf(stderr, "Incorrect mode for -H\n");
+ usage();
+ }
+ smart = filters[i].b;
+
+ log_init(use_syslog, debug, __progname);
+ tzset(); /* Get timezone info before chroot */
+ if (use_syslog && daemonize) {
+ /* So, we use syslog and we daemonize (or we are started by
+ * systemd). No need to continue writing to stdout. */
+ int fd;
+ /* coverity[resource_leak]
+ fd may be leaked if < 2, it's expected */
+ if ((fd = open("/dev/null", O_RDWR, 0)) != -1) {
+ dup2(fd, STDIN_FILENO);
+ dup2(fd, STDOUT_FILENO);
+ dup2(fd, STDERR_FILENO);
+ if (fd > 2) close(fd);
+ }
+ }
+ log_debug("main", "lldpd " PACKAGE_VERSION " starting...");
+ version_check();
+#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
+ fatalx("main", "fuzzing enabled, unsafe for production");
+#endif
+
+ /* Grab uid and gid to use for priv sep */
+#ifdef ENABLE_PRIVSEP
+ if ((user = getpwnam(PRIVSEP_USER)) == NULL)
+ fatalx("main",
+ "no " PRIVSEP_USER
+ " user for privilege separation, please create it");
+ uid = user->pw_uid;
+ if ((group = getgrnam(PRIVSEP_GROUP)) == NULL)
+ fatalx("main",
+ "no " PRIVSEP_GROUP
+ " group for privilege separation, please create it");
+ gid = group->gr_gid;
+#endif
+
+ /* Create and setup socket */
+ int retry = 1;
+ log_debug("main", "creating control socket");
+ while ((ctl = ctl_create(ctlname)) == -1) {
+ if (retry-- && errno == EADDRINUSE) {
+ /* Check if a daemon is really listening */
+ int tfd;
+ log_info("main",
+ "unable to create control socket because it already exists");
+ log_info("main", "check if another instance is running");
+ if ((tfd = ctl_connect(ctlname)) != -1) {
+ /* Another instance is running */
+ close(tfd);
+ log_warnx("main",
+ "another instance is running, please stop it");
+ fatalx("main", "giving up");
+ } else if (errno == ECONNREFUSED) {
+ /* Nobody is listening */
+ log_info("main",
+ "old control socket is present, clean it");
+ ctl_cleanup(ctlname);
+ continue;
+ }
+ log_warn("main",
+ "cannot determine if another daemon is already running");
+ fatalx("main", "giving up");
+ }
+ log_warn("main", "unable to create control socket at %s", ctlname);
+ fatalx("main", "giving up");
+ }
+#ifdef ENABLE_PRIVSEP
+ if (chown(ctlname, uid, gid) == -1)
+ log_warn("main", "unable to chown control socket");
+ if (chmod(ctlname, S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IWGRP | S_IXGRP) ==
+ -1)
+ log_warn("main", "unable to chmod control socket");
+#endif
+
+ /* Create associated advisory lock file */
+ char *lockname = NULL;
+ int fd;
+ if (asprintf(&lockname, "%s.lock", ctlname) == -1)
+ fatal("main", "cannot build lock name");
+ if ((fd = open(lockname, O_CREAT | O_RDWR, 0000)) == -1)
+ fatal("main", "cannot create lock file for control socket");
+ close(fd);
+#ifdef ENABLE_PRIVSEP
+ if (chown(lockname, uid, gid) == -1)
+ log_warn("main", "unable to chown control socket lock");
+ if (chmod(lockname,
+ S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IWGRP | S_IXGRP) == -1)
+ log_warn("main", "unable to chmod control socket lock");
+#endif
+ free(lockname);
+
+ /* Disable SIGPIPE */
+ signal(SIGPIPE, SIG_IGN);
+
+ /* Disable SIGHUP, until handlers are installed */
+ signal(SIGHUP, SIG_IGN);
+
+ /* Daemonization, unless started by systemd or launchd or debug */
+#ifndef HOST_OS_OSX
+ if (!lldpd_started_by_systemd() && daemonize) {
+ int pid;
+ char *spid;
+ log_debug("main", "going into background");
+ if (daemon(0, 1) != 0) fatal("main", "failed to detach daemon");
+ if ((pid = open(pidfile, O_TRUNC | O_CREAT | O_WRONLY, 0666)) == -1)
+ fatal("main",
+ "unable to open pid file " LLDPD_PID_FILE
+ " (or the specified one)");
+ if (asprintf(&spid, "%d\n", getpid()) == -1)
+ fatal("main",
+ "unable to create pid file " LLDPD_PID_FILE
+ " (or the specified one)");
+ if (write(pid, spid, strlen(spid)) == -1)
+ fatal("main",
+ "unable to write pid file " LLDPD_PID_FILE
+ " (or the specified one)");
+ free(spid);
+ close(pid);
+ }
+#endif
+
+ /* Configuration with lldpcli */
+ if (lldpcli) {
+ if (!config_file) {
+ log_debug("main",
+ "invoking lldpcli for default configuration locations");
+ } else {
+ log_debug("main",
+ "invoking lldpcli for user supplied configuration location");
+ }
+ if (lldpd_configure(use_syslog, debug, lldpcli, ctlname, config_file) ==
+ -1)
+ fatal("main", "unable to spawn lldpcli");
+ }
+
+ /* Try to read system information from /etc/os-release if possible.
+ Fall back to lsb_release for compatibility. */
+ log_debug("main", "get OS/LSB release information");
+ lsb_release = lldpd_get_os_release();
+ if (!lsb_release) {
+ lsb_release = lldpd_get_lsb_release();
+ }
+
+ log_debug("main", "initialize privilege separation");
+#ifdef ENABLE_PRIVSEP
+ priv_init(PRIVSEP_CHROOT, ctl, uid, gid);
+#else
+ priv_init();
+#endif
+
+ /* Initialization of global configuration */
+ if ((cfg = (struct lldpd *)calloc(1, sizeof(struct lldpd))) == NULL)
+ fatal("main", NULL);
+
+ lldpd_alloc_default_local_port(cfg);
+ cfg->g_ctlname = ctlname;
+ cfg->g_ctl = ctl;
+ cfg->g_config.c_mgmt_pattern = mgmtp;
+ cfg->g_config.c_cid_pattern = cidp;
+ cfg->g_config.c_iface_pattern = interfaces;
+ cfg->g_config.c_smart = smart;
+ if (lldpcli) cfg->g_config.c_paused = 1;
+ cfg->g_config.c_receiveonly = receiveonly;
+ cfg->g_config.c_tx_interval = LLDPD_TX_INTERVAL * 1000;
+ cfg->g_config.c_tx_hold = LLDPD_TX_HOLD;
+ cfg->g_config.c_ttl = cfg->g_config.c_tx_interval * cfg->g_config.c_tx_hold;
+ cfg->g_config.c_ttl = (cfg->g_config.c_ttl + 999) / 1000;
+ cfg->g_config.c_max_neighbors = LLDPD_MAX_NEIGHBORS;
+#ifdef ENABLE_LLDPMED
+ cfg->g_config.c_enable_fast_start = enable_fast_start;
+ cfg->g_config.c_tx_fast_init = LLDPD_FAST_INIT;
+ cfg->g_config.c_tx_fast_interval = LLDPD_FAST_TX_INTERVAL;
+#endif
+#ifdef USE_SNMP
+ cfg->g_snmp = snmp;
+ cfg->g_snmp_agentx = agentx;
+#endif /* USE_SNMP */
+ cfg->g_config.c_bond_slave_src_mac_type =
+ LLDP_BOND_SLAVE_SRC_MAC_TYPE_LOCALLY_ADMINISTERED;
+
+ /* Get ioctl socket */
+ log_debug("main", "get an ioctl socket");
+ if ((cfg->g_sock = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
+ fatal("main", "failed to get ioctl socket");
+
+ /* Description */
+ if (!(cfg->g_config.c_advertise_version = advertise_version) && lsb_release &&
+ lsb_release[strlen(lsb_release) - 1] == '\n')
+ lsb_release[strlen(lsb_release) - 1] = '\0';
+ cfg->g_lsb_release = lsb_release;
+ if (descr_override) cfg->g_config.c_description = descr_override;
+
+ if (platform_override) cfg->g_config.c_platform = platform_override;
+
+ /* Set system capabilities */
+ log_debug("main", "set system capabilities");
+ if ((lchassis = (struct lldpd_chassis *)calloc(1,
+ sizeof(struct lldpd_chassis))) == NULL)
+ fatal("localchassis", NULL);
+ cfg->g_config.c_cap_advertise = 1;
+ cfg->g_config.c_cap_override = 0;
+ lchassis->c_cap_available =
+ LLDP_CAP_BRIDGE | LLDP_CAP_WLAN | LLDP_CAP_ROUTER | LLDP_CAP_STATION;
+ cfg->g_config.c_mgmt_advertise = 1;
+ TAILQ_INIT(&lchassis->c_mgmt);
+#ifdef ENABLE_LLDPMED
+ if (lldpmed > 0) {
+ if (lldpmed == LLDP_MED_CLASS_III)
+ lchassis->c_cap_available |= LLDP_CAP_TELEPHONE;
+ lchassis->c_med_type = lldpmed;
+ lchassis->c_med_cap_available = LLDP_MED_CAP_CAP | LLDP_MED_CAP_IV |
+ LLDP_MED_CAP_LOCATION | LLDP_MED_CAP_POLICY | LLDP_MED_CAP_MDI_PSE |
+ LLDP_MED_CAP_MDI_PD;
+ cfg->g_config.c_noinventory = noinventory;
+ } else
+ cfg->g_config.c_noinventory = 1;
+#endif
+
+ log_debug("main", "initialize protocols");
+ cfg->g_protocols = protos;
+ for (i = 0; protos[i].mode != 0; i++) {
+
+ /* With -ll, disable LLDP */
+ if (protos[i].mode == LLDPD_MODE_LLDP) protos[i].enabled %= 3;
+ /* With -ccc force CDPV2, enable CDPV1 */
+ if (protos[i].mode == LLDPD_MODE_CDPV1 && protos[i].enabled == 3) {
+ protos[i].enabled = 1;
+ }
+ /* With -cc force CDPV1, enable CDPV2 */
+ if (protos[i].mode == LLDPD_MODE_CDPV2 && protos[i].enabled == 2) {
+ protos[i].enabled = 1;
+ }
+
+ /* With -cccc disable CDPV1, enable CDPV2 */
+ if (protos[i].mode == LLDPD_MODE_CDPV1 && protos[i].enabled >= 4) {
+ protos[i].enabled = 0;
+ }
+
+ /* With -cccc disable CDPV1, enable CDPV2; -ccccc will force CDPv2 */
+ if (protos[i].mode == LLDPD_MODE_CDPV2 && protos[i].enabled == 4) {
+ protos[i].enabled = 1;
+ }
+
+ if (protos[i].enabled > 1)
+ log_info("main", "protocol %s enabled and forced",
+ protos[i].name);
+ else if (protos[i].enabled)
+ log_info("main", "protocol %s enabled", protos[i].name);
+ else
+ log_info("main", "protocol %s disabled", protos[i].name);
+ }
+
+ TAILQ_INIT(&cfg->g_hardware);
+ TAILQ_INIT(&cfg->g_chassis);
+ TAILQ_INSERT_TAIL(&cfg->g_chassis, lchassis, c_entries);
+ lchassis->c_refcount++; /* We should always keep a reference to local chassis */
+
+ /* Main loop */
+ log_debug("main", "start main loop");
+ levent_loop(cfg);
+ lchassis->c_refcount--;
+ lldpd_exit(cfg);
+ free(cfg);
+
+ return (0);
+}
diff --git a/src/daemon/lldpd.h b/src/daemon/lldpd.h
new file mode 100644
index 0000000..2fc381b
--- /dev/null
+++ b/src/daemon/lldpd.h
@@ -0,0 +1,429 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/*
+ * Copyright (c) 2008 Vincent Bernat <bernat@luffy.cx>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef _LLDPD_H
+#define _LLDPD_H
+
+#if HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#ifdef HAVE_VALGRIND_VALGRIND_H
+# include <valgrind/valgrind.h>
+#else
+# define RUNNING_ON_VALGRIND 0
+#endif
+
+#include <stdlib.h>
+#include <stddef.h>
+#include <string.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <net/if_arp.h>
+#include <netinet/if_ether.h>
+#include <sys/un.h>
+
+#include "lldp-tlv.h"
+#if defined ENABLE_CDP || defined ENABLE_FDP
+# include "protocols/cdp.h"
+#endif
+#ifdef ENABLE_SONMP
+# include "protocols/sonmp.h"
+#endif
+#ifdef ENABLE_EDP
+# include "protocols/edp.h"
+#endif
+
+#include "../compat/compat.h"
+#include "../marshal.h"
+#include "../log.h"
+#include "../ctl.h"
+#include "../lldpd-structs.h"
+
+/* We don't want to import event2/event.h. We only need those as
+ opaque structs. */
+struct event;
+struct event_base;
+
+#define PROCFS_SYS_NET "/proc/sys/net/"
+#define SYSFS_CLASS_NET "/sys/class/net/"
+#define SYSFS_CLASS_DMI "/sys/class/dmi/id/"
+#define LLDPD_TX_INTERVAL 30
+#define LLDPD_TX_HOLD 4
+#define LLDPD_TTL LLDPD_TX_INTERVAL *LLDPD_TX_HOLD
+#define LLDPD_TX_MSGDELAY 1
+#define LLDPD_MAX_NEIGHBORS 32
+#define LLDPD_FAST_TX_INTERVAL 1
+#define LLDPD_FAST_INIT 4
+
+#define USING_AGENTX_SUBAGENT_MODULE 1
+
+#define PROTO_SEND_SIG struct lldpd *, 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
+
+#define ALIGNED_CAST(TYPE, ATTR) ((TYPE)(void *)(ATTR))
+
+struct protocol {
+ int mode; /* > 0 mode identifier (unique per protocol) */
+ int enabled; /* Is this protocol enabled? */
+ const 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[3][ETHER_ADDR_LEN]; /* Destination MAC addresses used by this
+ protocol */
+};
+
+#define SMART_HIDDEN(port) (port->p_hidden_in)
+
+struct lldpd;
+
+/* lldpd.c */
+struct lldpd_hardware *lldpd_get_hardware(struct lldpd *, char *, int);
+struct lldpd_hardware *lldpd_alloc_hardware(struct lldpd *, char *, int);
+void lldpd_hardware_cleanup(struct lldpd *, struct lldpd_hardware *);
+struct lldpd_mgmt *lldpd_alloc_mgmt(int family, void *addr, size_t addrsize,
+ u_int32_t iface);
+void lldpd_recv(struct lldpd *, struct lldpd_hardware *, int);
+void lldpd_send(struct lldpd_hardware *);
+void lldpd_loop(struct lldpd *);
+int lldpd_main(int, char **, char **);
+void lldpd_update_localports(struct lldpd *);
+void lldpd_update_localchassis(struct lldpd *);
+void lldpd_cleanup(struct lldpd *);
+
+/* frame.c */
+u_int16_t frame_checksum(const u_int8_t *, int, int);
+
+/* event.c */
+void levent_loop(struct lldpd *);
+void levent_shutdown(struct lldpd *);
+void levent_hardware_init(struct lldpd_hardware *);
+void levent_hardware_add_fd(struct lldpd_hardware *, int);
+void levent_hardware_release(struct lldpd_hardware *);
+void levent_ctl_notify(char *, int, struct lldpd_port *);
+void levent_send_now(struct lldpd *);
+void levent_update_now(struct lldpd *);
+int levent_iface_subscribe(struct lldpd *, int);
+void levent_schedule_pdu(struct lldpd_hardware *);
+void levent_schedule_cleanup(struct lldpd *);
+int levent_make_socket_nonblocking(int);
+int levent_make_socket_blocking(int);
+#ifdef HOST_OS_LINUX
+void levent_recv_error(int, const char *);
+#endif
+
+/* lldp.c */
+int lldp_send_shutdown(PROTO_SEND_SIG);
+int lldp_send(PROTO_SEND_SIG);
+int lldp_decode(PROTO_DECODE_SIG);
+
+/* cdp.c */
+#ifdef ENABLE_CDP
+int cdpv1_send(PROTO_SEND_SIG);
+int cdpv2_send(PROTO_SEND_SIG);
+int cdpv1_guess(PROTO_GUESS_SIG);
+int cdpv2_guess(PROTO_GUESS_SIG);
+#endif
+#if defined ENABLE_CDP || defined ENABLE_FDP
+int cdp_decode(PROTO_DECODE_SIG);
+#endif
+#ifdef ENABLE_FDP
+int fdp_send(PROTO_SEND_SIG);
+#endif
+
+#ifdef ENABLE_SONMP
+/* sonmp.c */
+int sonmp_send(PROTO_SEND_SIG);
+int sonmp_decode(PROTO_DECODE_SIG);
+#endif
+
+#ifdef ENABLE_EDP
+/* edp.c */
+int edp_send(PROTO_SEND_SIG);
+int edp_decode(PROTO_DECODE_SIG);
+#endif
+
+/* dmi.c */
+#ifdef ENABLE_LLDPMED
+char *dmi_hw(void);
+char *dmi_fw(void);
+char *dmi_sn(void);
+char *dmi_manuf(void);
+char *dmi_model(void);
+char *dmi_asset(void);
+#endif
+
+#ifdef USE_SNMP
+/* agent.c */
+void agent_shutdown(void);
+void agent_init(struct lldpd *, const char *);
+void agent_notify(struct lldpd_hardware *, int, struct lldpd_port *);
+#endif
+
+#ifdef ENABLE_PRIVSEP
+/* agent_priv.c */
+void agent_priv_register_domain(void);
+#endif
+
+/* client.c */
+int client_handle_client(struct lldpd *cfg,
+ ssize_t (*send)(void *, int, void *, size_t), void *, enum hmsg_type type,
+ void *buffer, size_t n, int *);
+
+/* priv.c */
+#ifdef ENABLE_PRIVSEP
+void priv_init(const char *, int, uid_t, gid_t);
+#else
+void priv_init(void);
+#endif
+void priv_wait(void);
+void priv_ctl_cleanup(const char *ctlname);
+char *priv_gethostname(void);
+#ifdef HOST_OS_LINUX
+int priv_open(const char *);
+void asroot_open(void);
+#endif
+int priv_iface_init(int, char *);
+int asroot_iface_init_os(int, char *, int *);
+int priv_iface_multicast(const char *, const u_int8_t *, int);
+int priv_iface_description(const char *, const char *);
+int asroot_iface_description_os(const char *, const char *);
+int priv_iface_promisc(const char *);
+int asroot_iface_promisc_os(const char *);
+int priv_snmp_socket(struct sockaddr_un *);
+
+enum priv_cmd {
+ PRIV_PING,
+ PRIV_DELETE_CTL_SOCKET,
+ PRIV_GET_HOSTNAME,
+ PRIV_OPEN,
+ PRIV_IFACE_INIT,
+ PRIV_IFACE_MULTICAST,
+ PRIV_IFACE_DESCRIPTION,
+ PRIV_IFACE_PROMISC,
+ PRIV_SNMP_SOCKET,
+};
+
+/* priv-seccomp.c */
+#if defined USE_SECCOMP && defined ENABLE_PRIVSEP
+int priv_seccomp_init(int, int);
+#endif
+
+/* privsep_io.c */
+enum priv_context { PRIV_PRIVILEGED, PRIV_UNPRIVILEGED };
+int may_read(enum priv_context, void *, size_t);
+void must_read(enum priv_context, void *, size_t);
+void must_write(enum priv_context, const void *, size_t);
+void priv_privileged_fd(int);
+void priv_unprivileged_fd(int);
+int priv_fd(enum priv_context);
+int receive_fd(enum priv_context);
+void send_fd(enum priv_context, int);
+
+/* interfaces-*.c */
+
+/* BPF filter to get revelant information from interfaces */
+/* LLDP: "ether proto 0x88cc and ether dst 01:80:c2:00:00:0e" */
+/* FDP: "ether dst 01:e0:52:cc:cc:cc" */
+/* CDP: "ether dst 01:00:0c:cc:cc:cc" */
+/* SONMP: "ether dst 01:00:81:00:01:00" */
+/* EDP: "ether dst 00:e0:2b:00:00:00" */
+/* For optimization purpose, we first check if the first bit of the
+ first byte is 1. if not, this can only be an EDP packet:
+
+ tcpdump -dd "(ether[0] & 1 = 1 and
+ ((ether proto 0x88cc and (ether dst 01:80:c2:00:00:0e or
+ ether dst 01:80:c2:00:00:03 or
+ ether dst 01:80:c2:00:00:00)) or
+ (ether dst 01:e0:52:cc:cc:cc) or
+ (ether dst 01:00:0c:cc:cc:cc) or
+ (ether dst 01:00:81:00:01:00))) or
+ (ether dst 00:e0:2b:00:00:00)"
+*/
+
+#ifndef ETH_P_LLDP
+# define ETH_P_LLDP 0x88cc
+#endif
+#define LLDPD_FILTER_F \
+ { 0x30, 0, 0, 0x00000000 }, { 0x54, 0, 0, 0x00000001 }, { 0x15, 0, 16, 0x00000001 }, \
+ { 0x28, 0, 0, 0x0000000c }, { 0x15, 0, 6, ETH_P_LLDP }, \
+ { 0x20, 0, 0, 0x00000002 }, { 0x15, 2, 0, 0xc200000e }, \
+ { 0x15, 1, 0, 0xc2000003 }, { 0x15, 0, 2, 0xc2000000 }, \
+ { 0x28, 0, 0, 0x00000000 }, { 0x15, 12, 13, 0x00000180 }, \
+ { 0x20, 0, 0, 0x00000002 }, { 0x15, 0, 2, 0x52cccccc }, \
+ { 0x28, 0, 0, 0x00000000 }, { 0x15, 8, 9, 0x000001e0 }, \
+ { 0x15, 1, 0, 0x0ccccccc }, { 0x15, 0, 2, 0x81000100 }, \
+ { 0x28, 0, 0, 0x00000000 }, { 0x15, 4, 5, 0x00000100 }, \
+ { 0x20, 0, 0, 0x00000002 }, { 0x15, 0, 3, 0x2b000000 }, \
+ { 0x28, 0, 0, 0x00000000 }, { 0x15, 0, 1, 0x000000e0 }, \
+ { 0x6, 0, 0, 0x00040000 }, \
+ { \
+ 0x6, 0, 0, 0x00000000 \
+ }
+
+/* This function is responsible to refresh information about interfaces. It is
+ * OS specific but should be present for each OS. It can use the functions in
+ * `interfaces.c` as helper by providing a list of OS-independent interface
+ * devices. */
+void interfaces_update(struct lldpd *);
+
+/* interfaces.c */
+/* An interface cannot be both physical and (bridge or bond or vlan) */
+#define IFACE_PHYSICAL_T (1 << 0) /* Physical interface */
+#define IFACE_BRIDGE_T (1 << 1) /* Bridge interface */
+#define IFACE_BOND_T (1 << 2) /* Bond interface */
+#define IFACE_VLAN_T (1 << 3) /* VLAN interface */
+#define IFACE_WIRELESS_T (1 << 4) /* Wireless interface */
+#define IFACE_BRIDGE_VLAN_T (1 << 5) /* Bridge-aware VLAN interface */
+
+#define MAX_VLAN 4096
+#define VLAN_BITMAP_LEN (MAX_VLAN / 32)
+struct interfaces_device {
+ TAILQ_ENTRY(interfaces_device) next;
+ int ignore; /* Ignore this interface */
+ int index; /* Index */
+ char *name; /* Name */
+ char *alias; /* Alias */
+ char *address; /* MAC address */
+ char *driver; /* Driver */
+ int flags; /* Flags (IFF_*) */
+ int mtu; /* MTU */
+ int type; /* Type (see IFACE_*_T) */
+ uint32_t vlan_bmap[VLAN_BITMAP_LEN]; /* If a VLAN, what are the VLAN ID? */
+ int pvid; /* If a VLAN, what is the default VLAN? */
+ struct interfaces_device *lower; /* Lower interface (for a VLAN for example) */
+ struct interfaces_device *upper; /* Upper interface (for a bridge or a bond) */
+
+ /* The following are OS specific. Should be static (no free function) */
+#ifdef HOST_OS_LINUX
+ int lower_idx; /* Index to lower interface */
+ int upper_idx; /* Index to upper interface */
+#endif
+};
+struct interfaces_address {
+ TAILQ_ENTRY(interfaces_address) next;
+ int index; /* Index */
+ int flags; /* Flags */
+ struct sockaddr_storage address; /* Address */
+
+ /* The following are OS specific. */
+ /* Nothing yet. */
+};
+TAILQ_HEAD(interfaces_device_list, interfaces_device);
+TAILQ_HEAD(interfaces_address_list, interfaces_address);
+void interfaces_free_device(struct interfaces_device *);
+void interfaces_free_address(struct interfaces_address *);
+void interfaces_free_devices(struct interfaces_device_list *);
+void interfaces_free_addresses(struct interfaces_address_list *);
+struct interfaces_device *interfaces_indextointerface(struct interfaces_device_list *,
+ int);
+struct interfaces_device *interfaces_nametointerface(struct interfaces_device_list *,
+ const char *);
+
+void interfaces_helper_promisc(struct lldpd *, struct lldpd_hardware *);
+void interfaces_helper_allowlist(struct lldpd *, struct interfaces_device_list *);
+void interfaces_helper_chassis(struct lldpd *, struct interfaces_device_list *);
+void interfaces_helper_add_hardware(struct lldpd *, struct lldpd_hardware *);
+void interfaces_helper_physical(struct lldpd *, struct interfaces_device_list *,
+ struct lldpd_ops *, int (*init)(struct lldpd *, struct lldpd_hardware *));
+void interfaces_helper_port_name_desc(struct lldpd *, struct lldpd_hardware *,
+ struct interfaces_device *);
+void interfaces_helper_mgmt(struct lldpd *, struct interfaces_address_list *,
+ struct interfaces_device_list *);
+#ifdef ENABLE_DOT1
+void interfaces_helper_vlan(struct lldpd *, struct interfaces_device_list *);
+#endif
+int interfaces_send_helper(struct lldpd *, struct lldpd_hardware *, char *, size_t);
+
+void interfaces_setup_multicast(struct lldpd *, const char *, int);
+int interfaces_routing_enabled(struct lldpd *);
+void interfaces_cleanup(struct lldpd *);
+
+#ifdef HOST_OS_LINUX
+/* netlink.c */
+struct interfaces_device_list *netlink_get_interfaces(struct lldpd *);
+struct interfaces_address_list *netlink_get_addresses(struct lldpd *);
+void netlink_cleanup(struct lldpd *);
+struct lldpd_netlink;
+#endif
+
+#ifndef HOST_OS_LINUX
+/* interfaces-bpf.c */
+int ifbpf_phys_init(struct lldpd *, struct lldpd_hardware *);
+#endif
+
+/* pattern.c */
+enum pattern_match_result {
+ PATTERN_MATCH_DENIED,
+ PATTERN_MATCH_ALLOWED,
+ PATTERN_MATCH_ALLOWED_EXACT
+};
+enum pattern_match_result pattern_match(char *, char *, int);
+
+/* bitmap.c */
+void bitmap_set(uint32_t *bmap, uint16_t vlan_id);
+int bitmap_isempty(uint32_t *bmap);
+unsigned int bitmap_numbits(uint32_t *bmap);
+
+struct lldpd {
+ int g_sock;
+ struct event_base *g_base;
+#ifdef USE_SNMP
+#endif
+
+ struct lldpd_config g_config;
+
+ struct protocol *g_protocols;
+ int g_lastrid;
+ struct event *g_main_loop;
+ struct event *g_cleanup_timer;
+#ifdef USE_SNMP
+ int g_snmp;
+ struct event *g_snmp_timeout;
+ void *g_snmp_fds;
+ const char *g_snmp_agentx;
+#endif /* USE_SNMP */
+
+ /* Unix socket handling */
+ const char *g_ctlname;
+ int g_ctl;
+ struct event *g_iface_event; /* Triggered when there is an interface change */
+ struct event
+ *g_iface_timer_event; /* Triggered one second after last interface change */
+ void (*g_iface_cb)(
+ struct lldpd *); /* Called when there is an interface change */
+
+ char *g_lsb_release;
+
+#ifdef HOST_OS_LINUX
+ struct lldpd_netlink *g_netlink;
+#endif
+
+ struct lldpd_port *g_default_local_port;
+#define LOCAL_CHASSIS(cfg) ((struct lldpd_chassis *)(TAILQ_FIRST(&cfg->g_chassis)))
+ TAILQ_HEAD(, lldpd_chassis) g_chassis;
+ TAILQ_HEAD(, lldpd_hardware) g_hardware;
+};
+
+#endif /* _LLDPD_H */
diff --git a/src/daemon/lldpd.service.in b/src/daemon/lldpd.service.in
new file mode 100644
index 0000000..80288a5
--- /dev/null
+++ b/src/daemon/lldpd.service.in
@@ -0,0 +1,22 @@
+[Unit]
+Description=LLDP daemon
+Documentation=man:lldpd(8)
+After=network.target
+RequiresMountsFor=@PRIVSEP_CHROOT@
+
+[Service]
+Type=notify
+NotifyAccess=main
+EnvironmentFile=-/etc/default/lldpd
+EnvironmentFile=-/etc/sysconfig/lldpd
+ExecStart=@sbindir@/lldpd $DAEMON_ARGS $LLDPD_OPTIONS
+Restart=on-failure
+PrivateTmp=yes
+ProtectHome=yes
+ProtectKernelTunables=no
+ProtectControlGroups=yes
+ProtectKernelModules=yes
+#ProtectSystem=full
+
+[Install]
+WantedBy=multi-user.target
diff --git a/src/daemon/lldpd.sysusers.conf.in b/src/daemon/lldpd.sysusers.conf.in
new file mode 100644
index 0000000..7cbf50a
--- /dev/null
+++ b/src/daemon/lldpd.sysusers.conf.in
@@ -0,0 +1,6 @@
+# System user and group for lldpd
+# @PRIVSEP_USER@:@PRIVSEP_GROUP@
+
+# Type Name ID GECOS Home
+u @PRIVSEP_USER@ - "lldpd user" @PRIVSEP_CHROOT@
+m @PRIVSEP_USER@ @PRIVSEP_GROUP@
diff --git a/src/daemon/main.c b/src/daemon/main.c
new file mode 100644
index 0000000..c2d9297
--- /dev/null
+++ b/src/daemon/main.c
@@ -0,0 +1,17 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+#include "lldpd.h"
+
+/**
+ * @mainpage
+ *
+ * lldpd is an implementation of 802.1AB (aka LLDP). It provides an interface
+ * for third party clients to interact with it: querying neighbors, setting some
+ * TLV. This interface is included into a library whose API can be found in @ref
+ * liblldpctl
+ */
+
+int
+main(int argc, char **argv, char **envp)
+{
+ return lldpd_main(argc, argv, envp);
+}
diff --git a/src/daemon/netlink.c b/src/daemon/netlink.c
new file mode 100644
index 0000000..32fee76
--- /dev/null
+++ b/src/daemon/netlink.c
@@ -0,0 +1,993 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/*
+ * Copyright (c) 2012 Vincent Bernat <bernat@luffy.cx>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/* Grabbing interfaces information with netlink only. */
+
+#include "lldpd.h"
+
+#include <errno.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include <net/if_arp.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#include <linux/if_bridge.h>
+
+#define NETLINK_BUFFER 4096
+
+struct netlink_req {
+ struct nlmsghdr hdr;
+ struct ifinfomsg ifm;
+ /* attribute has to be NLMSG aligned */
+ struct rtattr ext_req __attribute__((aligned(NLMSG_ALIGNTO)));
+ __u32 ext_filter_mask;
+};
+
+struct lldpd_netlink {
+ int nl_socket_queries;
+ int nl_socket_changes;
+ int nl_socket_recv_size;
+ /* Cache */
+ struct interfaces_device_list *devices;
+ struct interfaces_address_list *addresses;
+};
+
+/**
+ * Set netlink socket buffer size.
+ *
+ * This returns the effective size on success. If the provided value is 0, this
+ * returns the current size instead. It returns -1 on system errors and -2 if
+ * the size was not changed appropriately (when reaching the max).
+ */
+static int
+netlink_socket_set_buffer_size(int s, int optname, const char *optname_str, int bufsize)
+{
+ socklen_t size = sizeof(int);
+ int got = 0;
+
+ if (bufsize > 0 &&
+ setsockopt(s, SOL_SOCKET, optname, &bufsize, sizeof(bufsize)) < 0) {
+ log_warn("netlink", "unable to set %s to '%d'", optname_str, bufsize);
+ return -1;
+ }
+
+ /* Now read them back from kernel.
+ * SO_SNDBUF & SO_RCVBUF are cap-ed at sysctl `net.core.rmem_max` &
+ * `net.core.wmem_max`. This it the easiest [probably sanest too]
+ * to validate that our socket buffers were set properly.
+ */
+ if (getsockopt(s, SOL_SOCKET, optname, &got, &size) < 0) {
+ log_warn("netlink", "unable to get %s", optname_str);
+ return -1;
+ }
+ if (bufsize > 0 && got < bufsize) {
+ log_warnx("netlink",
+ "tried to set %s to '%d' "
+ "but got '%d'",
+ optname_str, bufsize, got);
+ return -2;
+ }
+
+ return got;
+}
+
+/**
+ * Connect to netlink.
+ *
+ * Open a Netlink socket and connect to it.
+ *
+ * @param groups Which groups we want to subscribe to
+ * @return 0 on success, -1 otherwise
+ */
+static int
+netlink_connect(struct lldpd *cfg, unsigned groups)
+{
+ int s1 = -1, s2 = -1;
+ struct sockaddr_nl local = { .nl_family = AF_NETLINK,
+ .nl_pid = 0,
+ .nl_groups = groups };
+
+ /* Open Netlink socket for subscriptions */
+ log_debug("netlink", "opening netlink sockets");
+ s1 = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
+ if (s1 == -1) {
+ log_warn("netlink", "unable to open netlink socket for changes");
+ goto error;
+ }
+ if (NETLINK_SEND_BUFSIZE &&
+ netlink_socket_set_buffer_size(s1, SO_SNDBUF, "SO_SNDBUF",
+ NETLINK_SEND_BUFSIZE) == -1) {
+ log_warn("netlink", "unable to set send buffer size");
+ goto error;
+ }
+
+ int rc = netlink_socket_set_buffer_size(s1, SO_RCVBUF, "SO_RCVBUF",
+ NETLINK_RECEIVE_BUFSIZE);
+ switch (rc) {
+ case -1:
+ log_warn("netlink", "unable to set receiver buffer size");
+ goto error;
+ case -2:
+ /* Cannot set size */
+ cfg->g_netlink->nl_socket_recv_size = 0;
+ break;
+ default:
+ cfg->g_netlink->nl_socket_recv_size = rc;
+ break;
+ }
+ if (groups &&
+ bind(s1, (struct sockaddr *)&local, sizeof(struct sockaddr_nl)) < 0) {
+ log_warn("netlink", "unable to bind netlink socket");
+ goto error;
+ }
+
+ /* Opening Netlink socket to for queries */
+ s2 = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
+ if (s2 == -1) {
+ log_warn("netlink", "unable to open netlink socket for queries");
+ goto error;
+ }
+ cfg->g_netlink->nl_socket_changes = s1;
+ cfg->g_netlink->nl_socket_queries = s2;
+ return 0;
+error:
+ if (s1 != -1) close(s1);
+ if (s2 != -1) close(s2);
+ return -1;
+}
+
+/**
+ * Send a netlink message.
+ *
+ * The type of the message can be chosen as well the route family. The
+ * mesage will always be NLM_F_REQUEST | NLM_F_DUMP.
+ *
+ * @param s the netlink socket
+ * @param type the request type (eg RTM_GETLINK)
+ * @param family the rt family (eg AF_PACKET)
+ * @return 0 on success, -1 otherwise
+ */
+static int
+netlink_send(int s, int type, int family, int seq)
+{
+ struct netlink_req req = { .hdr = { .nlmsg_len =
+ NLMSG_LENGTH(sizeof(struct ifinfomsg)),
+ .nlmsg_type = type,
+ .nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP,
+ .nlmsg_seq = seq,
+ .nlmsg_pid = getpid() },
+ .ifm = { .ifi_family = family } };
+ struct iovec iov = { .iov_base = &req, .iov_len = req.hdr.nlmsg_len };
+ struct sockaddr_nl peer = { .nl_family = AF_NETLINK };
+ struct msghdr rtnl_msg = { .msg_iov = &iov,
+ .msg_iovlen = 1,
+ .msg_name = &peer,
+ .msg_namelen = sizeof(struct sockaddr_nl) };
+
+ if (family == AF_BRIDGE) {
+ unsigned int len = RTA_LENGTH(sizeof(__u32));
+ /* request bridge vlan attributes */
+ req.ext_req.rta_type = IFLA_EXT_MASK;
+ req.ext_req.rta_len = len;
+ req.ext_filter_mask = RTEXT_FILTER_BRVLAN;
+ req.hdr.nlmsg_len = NLMSG_ALIGN(req.hdr.nlmsg_len) + RTA_ALIGN(len);
+ iov.iov_len = req.hdr.nlmsg_len;
+ }
+
+ /* Send netlink message. This is synchronous but we are guaranteed
+ * to not block. */
+ log_debug("netlink", "sending netlink message");
+ if (sendmsg(s, (struct msghdr *)&rtnl_msg, 0) == -1) {
+ log_warn("netlink", "unable to send netlink message");
+ return -1;
+ }
+
+ return 0;
+}
+
+static void
+netlink_parse_rtattr(struct rtattr *tb[], int max, struct rtattr *rta, int len)
+{
+ while (RTA_OK(rta, len)) {
+ if ((rta->rta_type <= max) && (!tb[rta->rta_type]))
+ tb[rta->rta_type] = rta;
+ rta = RTA_NEXT(rta, len);
+ }
+}
+
+/**
+ * Parse a `linkinfo` attributes.
+ *
+ * @param iff where to put the result
+ * @param rta linkinfo attribute
+ * @param len length of attributes
+ */
+static void
+netlink_parse_linkinfo(struct interfaces_device *iff, struct rtattr *rta, int len)
+{
+ struct rtattr *link_info_attrs[IFLA_INFO_MAX + 1] = {};
+ char *kind = NULL;
+ uint16_t vlan_id;
+
+ netlink_parse_rtattr(link_info_attrs, IFLA_INFO_MAX, rta, len);
+
+ if (link_info_attrs[IFLA_INFO_KIND]) {
+ kind = strdup(RTA_DATA(link_info_attrs[IFLA_INFO_KIND]));
+ if (kind) {
+ if (!strcmp(kind, "vlan")) {
+ log_debug("netlink", "interface %s is a VLAN",
+ iff->name);
+ iff->type |= IFACE_VLAN_T;
+ } else if (!strcmp(kind, "bridge")) {
+ log_debug("netlink", "interface %s is a bridge",
+ iff->name);
+ iff->type |= IFACE_BRIDGE_T;
+ } else if (!strcmp(kind, "bond")) {
+ log_debug("netlink", "interface %s is a bond",
+ iff->name);
+ iff->type |= IFACE_BOND_T;
+ } else if (!strcmp(kind, "team")) {
+ log_debug("netlink", "interface %s is a team",
+ iff->name);
+ iff->type |= IFACE_BOND_T;
+ }
+ }
+ }
+
+ if (kind && !strcmp(kind, "vlan") && link_info_attrs[IFLA_INFO_DATA]) {
+ struct rtattr *vlan_link_info_data_attrs[IFLA_VLAN_MAX + 1] = {};
+ netlink_parse_rtattr(vlan_link_info_data_attrs, IFLA_VLAN_MAX,
+ RTA_DATA(link_info_attrs[IFLA_INFO_DATA]),
+ RTA_PAYLOAD(link_info_attrs[IFLA_INFO_DATA]));
+
+ if (vlan_link_info_data_attrs[IFLA_VLAN_ID]) {
+ vlan_id = *(uint16_t *)RTA_DATA(
+ vlan_link_info_data_attrs[IFLA_VLAN_ID]);
+ bitmap_set(iff->vlan_bmap, vlan_id);
+ log_debug("netlink", "VLAN ID for interface %s is %d",
+ iff->name, vlan_id);
+ }
+ }
+
+ if (kind && !strcmp(kind, "bridge") && link_info_attrs[IFLA_INFO_DATA]) {
+ struct rtattr *bridge_link_info_data_attrs[IFLA_BR_MAX + 1] = {};
+ netlink_parse_rtattr(bridge_link_info_data_attrs, IFLA_BR_MAX,
+ RTA_DATA(link_info_attrs[IFLA_INFO_DATA]),
+ RTA_PAYLOAD(link_info_attrs[IFLA_INFO_DATA]));
+
+ if (bridge_link_info_data_attrs[IFLA_BR_VLAN_FILTERING] &&
+ *(uint8_t *)RTA_DATA(
+ bridge_link_info_data_attrs[IFLA_BR_VLAN_FILTERING]) > 0) {
+ iff->type |= IFACE_BRIDGE_VLAN_T;
+ }
+ }
+
+ free(kind);
+}
+
+/**
+ * Parse a `afspec` attributes.
+ *
+ * @param iff where to put the result
+ * @param rta afspec attribute
+ * @param len length of attributes
+ */
+static void
+netlink_parse_afspec(struct interfaces_device *iff, struct rtattr *rta, int len)
+{
+ while (RTA_OK(rta, len)) {
+ struct bridge_vlan_info *vinfo;
+ switch (rta->rta_type) {
+ case IFLA_BRIDGE_VLAN_INFO:
+ vinfo = RTA_DATA(rta);
+ log_debug("netlink", "found VLAN %d on interface %s",
+ vinfo->vid, iff->name ? iff->name : "(unknown)");
+
+ bitmap_set(iff->vlan_bmap, vinfo->vid);
+ if (vinfo->flags &
+ (BRIDGE_VLAN_INFO_PVID | BRIDGE_VLAN_INFO_UNTAGGED))
+ iff->pvid = vinfo->vid;
+ break;
+ default:
+ log_debug("netlink",
+ "unknown afspec attribute type %d for iface %s",
+ rta->rta_type, iff->name ? iff->name : "(unknown)");
+ break;
+ }
+ rta = RTA_NEXT(rta, len);
+ }
+ /* All enbridged interfaces will have VLAN 1 by default, ignore it */
+ if (iff->vlan_bmap[0] == 2 && (bitmap_numbits(iff->vlan_bmap) == 1) &&
+ iff->pvid == 1) {
+ log_debug("netlink",
+ "found only default VLAN 1 on interface %s, removing",
+ iff->name ? iff->name : "(unknown)");
+ iff->vlan_bmap[0] = iff->pvid = 0;
+ }
+}
+
+/**
+ * Parse a `link` netlink message.
+ *
+ * @param msg message to be parsed
+ * @param iff where to put the result
+ * return 0 if the interface is worth it, -1 otherwise
+ */
+static int
+netlink_parse_link(struct nlmsghdr *msg, struct interfaces_device *iff)
+{
+ struct ifinfomsg *ifi;
+ struct rtattr *attribute;
+ int len;
+ ifi = NLMSG_DATA(msg);
+ len = msg->nlmsg_len - NLMSG_LENGTH(sizeof(struct ifinfomsg));
+
+ if (ifi->ifi_type != ARPHRD_ETHER) {
+ log_debug("netlink", "skip non Ethernet interface at index %d",
+ ifi->ifi_index);
+ return -1;
+ }
+
+ iff->index = ifi->ifi_index;
+ iff->flags = ifi->ifi_flags;
+ iff->lower_idx = -1;
+ iff->upper_idx = -1;
+
+ for (attribute = IFLA_RTA(ifi); RTA_OK(attribute, len);
+ attribute = RTA_NEXT(attribute, len)) {
+ switch (attribute->rta_type) {
+ case IFLA_IFNAME:
+ /* Interface name */
+ iff->name = strdup(RTA_DATA(attribute));
+ break;
+ case IFLA_IFALIAS:
+ /* Interface alias */
+ iff->alias = strdup(RTA_DATA(attribute));
+ break;
+ case IFLA_ADDRESS:
+ /* Interface MAC address */
+ iff->address = malloc(RTA_PAYLOAD(attribute));
+ if (iff->address)
+ memcpy(iff->address, RTA_DATA(attribute),
+ RTA_PAYLOAD(attribute));
+ break;
+ case IFLA_LINK:
+ /* Index of "lower" interface */
+ if (iff->lower_idx == -1) {
+ iff->lower_idx = *(int *)RTA_DATA(attribute);
+ log_debug("netlink", "attribute IFLA_LINK for %s: %d",
+ iff->name ? iff->name : "(unknown)",
+ iff->lower_idx);
+ } else {
+ log_debug("netlink",
+ "attribute IFLA_LINK for %s: %d (ignored)",
+ iff->name ? iff->name : "(unknown)",
+ iff->lower_idx);
+ }
+ break;
+ case IFLA_LINK_NETNSID:
+ /* Is the lower interface into another namesapce? */
+ iff->lower_idx = -2;
+ log_debug("netlink",
+ "attribute IFLA_LINK_NETNSID received for %s",
+ iff->name ? iff->name : "(unknown)");
+ break;
+ case IFLA_MASTER:
+ /* Index of master interface */
+ iff->upper_idx = *(int *)RTA_DATA(attribute);
+ break;
+ case IFLA_MTU:
+ /* Maximum Transmission Unit */
+ iff->mtu = *(int *)RTA_DATA(attribute);
+ break;
+ case IFLA_LINKINFO:
+ netlink_parse_linkinfo(iff, RTA_DATA(attribute),
+ RTA_PAYLOAD(attribute));
+ break;
+ case IFLA_AF_SPEC:
+ if (ifi->ifi_family != AF_BRIDGE) break;
+ netlink_parse_afspec(iff, RTA_DATA(attribute),
+ RTA_PAYLOAD(attribute));
+ break;
+ default:
+ log_debug("netlink",
+ "unhandled link attribute type %d for iface %s",
+ attribute->rta_type, iff->name ? iff->name : "(unknown)");
+ break;
+ }
+ }
+ if (!iff->name || !iff->address) {
+ log_debug("netlink",
+ "interface %d does not have a name or an address, skip",
+ iff->index);
+ return -1;
+ }
+ if (iff->upper_idx == -1) {
+ /* No upper interface, we cannot be enslaved. We need to clear
+ * the flag because the appropriate information may come later
+ * and we don't want to miss it. */
+ iff->flags &= ~IFF_SLAVE;
+ }
+ if (iff->lower_idx == -2) iff->lower_idx = -1;
+
+ if (ifi->ifi_family == AF_BRIDGE && msg->nlmsg_type == RTM_DELLINK &&
+ iff->upper_idx != -1) {
+ log_debug("netlink", "removal of %s from bridge %d", iff->name,
+ iff->upper_idx);
+ msg->nlmsg_type = RTM_NEWLINK;
+ iff->upper_idx = -1;
+ }
+
+ log_debug("netlink", "parsed link %d (%s, flags: %d)", iff->index, iff->name,
+ iff->flags);
+ return 0;
+}
+
+/**
+ * Parse a `address` netlink message.
+ *
+ * @param msg message to be parsed
+ * @param ifa where to put the result
+ * return 0 if the address is worth it, -1 otherwise
+ */
+static int
+netlink_parse_address(struct nlmsghdr *msg, struct interfaces_address *ifa)
+{
+ struct ifaddrmsg *ifi;
+ struct rtattr *attribute;
+ int len;
+ ifi = NLMSG_DATA(msg);
+ len = msg->nlmsg_len - NLMSG_LENGTH(sizeof(struct ifaddrmsg));
+
+ ifa->index = ifi->ifa_index;
+ ifa->flags = ifi->ifa_flags;
+ switch (ifi->ifa_family) {
+ case AF_INET:
+ case AF_INET6:
+ break;
+ default:
+ log_debug("netlink", "got a non IP address on if %d (family: %d)",
+ ifa->index, ifi->ifa_family);
+ return -1;
+ }
+
+ for (attribute = IFA_RTA(ifi); RTA_OK(attribute, len);
+ attribute = RTA_NEXT(attribute, len)) {
+ switch (attribute->rta_type) {
+ case IFA_ADDRESS:
+ /* Address */
+ if (ifi->ifa_family == AF_INET) {
+ struct sockaddr_in ip;
+ memset(&ip, 0, sizeof(struct sockaddr_in));
+ ip.sin_family = AF_INET;
+ memcpy(&ip.sin_addr, RTA_DATA(attribute),
+ sizeof(struct in_addr));
+ memcpy(&ifa->address, &ip, sizeof(struct sockaddr_in));
+ } else {
+ struct sockaddr_in6 ip6;
+ memset(&ip6, 0, sizeof(struct sockaddr_in6));
+ ip6.sin6_family = AF_INET6;
+ memcpy(&ip6.sin6_addr, RTA_DATA(attribute),
+ sizeof(struct in6_addr));
+ memcpy(&ifa->address, &ip6,
+ sizeof(struct sockaddr_in6));
+ }
+ break;
+ default:
+ log_debug("netlink",
+ "unhandled address attribute type %d for iface %d",
+ attribute->rta_type, ifa->index);
+ break;
+ }
+ }
+ if (ifa->address.ss_family == AF_UNSPEC) {
+ log_debug("netlink", "no IP for interface %d", ifa->index);
+ return -1;
+ }
+ return 0;
+}
+
+/**
+ * Merge an old interface with a new one.
+ *
+ * Some properties may be absent in the new interface that should be copied over
+ * from the old one.
+ */
+static void
+netlink_merge(struct interfaces_device *old, struct interfaces_device *new)
+{
+ if (new->alias == NULL) {
+ new->alias = old->alias;
+ old->alias = NULL;
+ }
+ if (new->address == NULL) {
+ new->address = old->address;
+ old->address = NULL;
+ }
+ if (new->mtu == 0) new->mtu = old->mtu;
+ if (new->type == 0) new->type = old->type;
+
+ if (bitmap_isempty(new->vlan_bmap) && new->type == IFACE_VLAN_T)
+ memcpy((void *)new->vlan_bmap, (void *)old->vlan_bmap,
+ sizeof(uint32_t) * VLAN_BITMAP_LEN);
+
+ /* It's not possible for lower link to change */
+ new->lower_idx = old->lower_idx;
+}
+
+/**
+ * Receive netlink answer from the kernel.
+ *
+ * @param ifs list to store interface list or NULL if we don't
+ * @param ifas list to store address list or NULL if we don't
+ * @return 0 on success, -1 on error
+ */
+static int
+netlink_recv(struct lldpd *cfg, int s, struct interfaces_device_list *ifs,
+ struct interfaces_address_list *ifas)
+{
+ int end = 0, ret = 0, flags, retry = 0;
+ struct iovec iov;
+ int link_update = 0;
+
+ struct interfaces_device *ifdold;
+ struct interfaces_device *ifdnew;
+ struct interfaces_address *ifaold;
+ struct interfaces_address *ifanew;
+ char addr[INET6_ADDRSTRLEN + 1];
+
+ iov.iov_len = NETLINK_BUFFER;
+ iov.iov_base = malloc(iov.iov_len);
+ if (!iov.iov_base) {
+ log_warn("netlink", "not enough memory");
+ return -1;
+ }
+
+ while (!end) {
+ ssize_t len;
+ struct nlmsghdr *msg;
+ struct sockaddr_nl peer = { .nl_family = AF_NETLINK };
+ struct msghdr rtnl_reply = { .msg_iov = &iov,
+ .msg_iovlen = 1,
+ .msg_name = &peer,
+ .msg_namelen = sizeof(struct sockaddr_nl) };
+ flags = MSG_PEEK | MSG_TRUNC;
+ retry:
+ len = recvmsg(s, &rtnl_reply, flags);
+ if (len == -1) {
+ if (errno == EAGAIN || errno == EWOULDBLOCK) {
+ if (retry++ == 0) {
+ levent_recv_error(s, "netlink socket");
+ goto retry;
+ }
+ log_warnx("netlink",
+ "should have received something, but didn't");
+ ret = 0;
+ goto out;
+ }
+ int rsize = cfg->g_netlink->nl_socket_recv_size;
+ if (errno == ENOBUFS && rsize > 0 &&
+ rsize < NETLINK_MAX_RECEIVE_BUFSIZE &&
+ s == cfg->g_netlink->nl_socket_changes) {
+ /* Try to increase buffer size, only for the
+ * socket used to receive changes */
+ rsize *= 2;
+ if (rsize > NETLINK_MAX_RECEIVE_BUFSIZE) {
+ rsize = NETLINK_MAX_RECEIVE_BUFSIZE;
+ }
+ int rc = netlink_socket_set_buffer_size(s, SO_RCVBUF,
+ "SO_RCVBUF", rsize);
+ if (rc < 0)
+ cfg->g_netlink->nl_socket_recv_size = 0;
+ else
+ cfg->g_netlink->nl_socket_recv_size = rsize;
+ if (rc > 0 || rc == -2) {
+ log_info("netlink",
+ "netlink receive buffer too small, retry with larger one (%d)",
+ rsize);
+ flags = 0;
+ goto retry;
+ }
+ }
+ log_warn("netlink", "unable to receive netlink answer");
+ ret = -1;
+ goto out;
+ }
+ if (!len) {
+ ret = 0;
+ goto out;
+ }
+
+ if (iov.iov_len < len || (rtnl_reply.msg_flags & MSG_TRUNC)) {
+ void *tmp;
+
+ /* Provided buffer is not large enough, enlarge it
+ * to size of len (which should be total length of the message)
+ * and try again. */
+ iov.iov_len = len;
+ tmp = realloc(iov.iov_base, iov.iov_len);
+ if (!tmp) {
+ log_warn("netlink", "not enough memory");
+ ret = -1;
+ goto out;
+ }
+ log_debug("netlink", "enlarge message size to %zu bytes", len);
+ iov.iov_base = tmp;
+ flags = 0;
+ goto retry;
+ }
+
+ if (flags != 0) {
+ /* Buffer is big enough, do the actual reading */
+ flags = 0;
+ goto retry;
+ }
+
+ for (msg = (struct nlmsghdr *)(void *)(iov.iov_base);
+ NLMSG_OK(msg, len); msg = NLMSG_NEXT(msg, len)) {
+ if (!(msg->nlmsg_flags & NLM_F_MULTI)) end = 1;
+ switch (msg->nlmsg_type) {
+ case NLMSG_DONE:
+ log_debug("netlink", "received done message");
+ end = 1;
+ break;
+ case RTM_NEWLINK:
+ case RTM_DELLINK:
+ if (!ifs) break;
+ log_debug("netlink", "received link information");
+ ifdnew = calloc(1, sizeof(struct interfaces_device));
+ if (ifdnew == NULL) {
+ log_warn("netlink",
+ "not enough memory for another interface, give up what we have");
+ goto end;
+ }
+ if (netlink_parse_link(msg, ifdnew) == 0) {
+ /* We need to find if we already have this
+ * interface */
+ TAILQ_FOREACH (ifdold, ifs, next) {
+ if (ifdold->index == ifdnew->index)
+ break;
+ }
+
+ if (msg->nlmsg_type == RTM_NEWLINK) {
+ if (ifdold == NULL) {
+ log_debug("netlink",
+ "interface %s is new",
+ ifdnew->name);
+ TAILQ_INSERT_TAIL(ifs, ifdnew,
+ next);
+ } else {
+ log_debug("netlink",
+ "interface %s/%s is updated",
+ ifdold->name, ifdnew->name);
+ netlink_merge(ifdold, ifdnew);
+ TAILQ_INSERT_AFTER(ifs, ifdold,
+ ifdnew, next);
+ TAILQ_REMOVE(ifs, ifdold, next);
+ interfaces_free_device(ifdold);
+ }
+ } else {
+ if (ifdold == NULL) {
+ log_warnx("netlink",
+ "removal request for %s, but no knowledge of it",
+ ifdnew->name);
+ } else {
+ log_debug("netlink",
+ "interface %s is to be removed",
+ ifdold->name);
+ TAILQ_REMOVE(ifs, ifdold, next);
+ interfaces_free_device(ifdold);
+ }
+ interfaces_free_device(ifdnew);
+ }
+ link_update = 1;
+ } else {
+ interfaces_free_device(ifdnew);
+ }
+ break;
+ case RTM_NEWADDR:
+ case RTM_DELADDR:
+ if (!ifas) break;
+ log_debug("netlink", "received address information");
+ ifanew = calloc(1, sizeof(struct interfaces_address));
+ if (ifanew == NULL) {
+ log_warn("netlink",
+ "not enough memory for another address, give what we have");
+ goto end;
+ }
+ if (netlink_parse_address(msg, ifanew) == 0) {
+ if (ifanew->address.ss_family == AF_INET6 &&
+ ifanew->flags & IFA_F_TEMPORARY) {
+ interfaces_free_address(ifanew);
+ break;
+ }
+ TAILQ_FOREACH (ifaold, ifas, next) {
+ if ((ifaold->index == ifanew->index) &&
+ !memcmp(&ifaold->address,
+ &ifanew->address,
+ sizeof(ifaold->address)))
+ break;
+ }
+ if (getnameinfo(
+ (struct sockaddr *)&ifanew->address,
+ sizeof(ifanew->address), addr,
+ sizeof(addr), NULL, 0,
+ NI_NUMERICHOST) != 0) {
+ strlcpy(addr, "(unknown)",
+ sizeof(addr));
+ }
+
+ if (msg->nlmsg_type == RTM_NEWADDR) {
+ if (ifaold == NULL) {
+ log_debug("netlink",
+ "new address %s%%%d", addr,
+ ifanew->index);
+ TAILQ_INSERT_TAIL(ifas, ifanew,
+ next);
+ } else {
+ log_debug("netlink",
+ "updated address %s%%%d",
+ addr, ifaold->index);
+ TAILQ_INSERT_AFTER(ifas, ifaold,
+ ifanew, next);
+ TAILQ_REMOVE(ifas, ifaold,
+ next);
+ interfaces_free_address(ifaold);
+ }
+ } else {
+ if (ifaold == NULL) {
+ log_info("netlink",
+ "removal request for address of %s%%%d, but no knowledge of it",
+ addr, ifanew->index);
+ } else {
+ log_debug("netlink",
+ "address %s%%%d is to be removed",
+ addr, ifaold->index);
+ TAILQ_REMOVE(ifas, ifaold,
+ next);
+ interfaces_free_address(ifaold);
+ }
+ interfaces_free_address(ifanew);
+ }
+ } else {
+ interfaces_free_address(ifanew);
+ }
+ break;
+ default:
+ log_debug("netlink",
+ "received unhandled message type %d (len: %d)",
+ msg->nlmsg_type, msg->nlmsg_len);
+ }
+ }
+ }
+end:
+ if (link_update) {
+ /* Fill out lower/upper */
+ struct interfaces_device *iface1, *iface2;
+ TAILQ_FOREACH (iface1, ifs, next) {
+ if (iface1->upper_idx != -1 &&
+ iface1->upper_idx != iface1->index) {
+ TAILQ_FOREACH (iface2, ifs, next) {
+ if (iface1->upper_idx == iface2->index) {
+ log_debug("netlink",
+ "upper interface for %s is %s",
+ iface1->name, iface2->name);
+ iface1->upper = iface2;
+ break;
+ }
+ }
+ if (iface2 == NULL) iface1->upper = NULL;
+ } else {
+ iface1->upper = NULL;
+ }
+ if (iface1->lower_idx != -1 &&
+ iface1->lower_idx != iface1->index) {
+ TAILQ_FOREACH (iface2, ifs, next) {
+ if (iface1->lower_idx == iface2->index) {
+ /* Workaround a bug introduced
+ * in Linux 4.1: a pair of veth
+ * will be lower interface of
+ * each other. Do not modify
+ * index as if one of them is
+ * updated, we will loose the
+ * information about the
+ * loop. */
+ if (iface2->lower_idx ==
+ iface1->index) {
+ iface1->lower = NULL;
+ log_debug("netlink",
+ "link loop detected between %s(%d) and %s(%d)",
+ iface1->name, iface1->index,
+ iface2->name,
+ iface2->index);
+ } else {
+ log_debug("netlink",
+ "lower interface for %s is %s",
+ iface1->name, iface2->name);
+ iface1->lower = iface2;
+ }
+ break;
+ }
+ }
+ } else {
+ iface1->lower = NULL;
+ }
+ }
+ }
+
+out:
+ free(iov.iov_base);
+ return ret;
+}
+
+static int
+netlink_group_mask(int group)
+{
+ return group ? (1 << (group - 1)) : 0;
+}
+
+/**
+ * Subscribe to link changes.
+ *
+ * @return 0 on success, -1 otherwise
+ */
+static int
+netlink_subscribe_changes(struct lldpd *cfg)
+{
+ unsigned int groups;
+
+ log_debug("netlink", "listening on interface changes");
+
+ groups = netlink_group_mask(RTNLGRP_LINK) |
+ netlink_group_mask(RTNLGRP_IPV4_IFADDR) |
+ netlink_group_mask(RTNLGRP_IPV6_IFADDR);
+
+ return netlink_connect(cfg, groups);
+}
+
+/**
+ * Receive changes from netlink */
+static void
+netlink_change_cb(struct lldpd *cfg)
+{
+ if (cfg->g_netlink == NULL) return;
+ netlink_recv(cfg, cfg->g_netlink->nl_socket_changes, cfg->g_netlink->devices,
+ cfg->g_netlink->addresses);
+}
+
+/**
+ * Initialize netlink subsystem.
+ *
+ * This can be called several times but will have effect only the first time.
+ *
+ * @return 0 on success, -1 otherwise
+ */
+static int
+netlink_initialize(struct lldpd *cfg)
+{
+#ifdef ENABLE_DOT1
+ struct interfaces_device *iff;
+#endif
+
+ if (cfg->g_netlink) return 0;
+
+ log_debug("netlink", "initialize netlink subsystem");
+ if ((cfg->g_netlink = calloc(sizeof(struct lldpd_netlink), 1)) == NULL) {
+ log_warn("netlink", "unable to allocate memory for netlink subsystem");
+ goto end;
+ }
+
+ /* Connect to netlink (by requesting to get notified on updates) and
+ * request updated information right now */
+ if (netlink_subscribe_changes(cfg) == -1) goto end;
+
+ struct interfaces_address_list *ifaddrs = cfg->g_netlink->addresses =
+ malloc(sizeof(struct interfaces_address_list));
+ if (ifaddrs == NULL) {
+ log_warn("netlink", "not enough memory for address list");
+ goto end;
+ }
+ TAILQ_INIT(ifaddrs);
+
+ struct interfaces_device_list *ifs = cfg->g_netlink->devices =
+ malloc(sizeof(struct interfaces_device_list));
+ if (ifs == NULL) {
+ log_warn("netlink", "not enough memory for interface list");
+ goto end;
+ }
+ TAILQ_INIT(ifs);
+
+ if (netlink_send(cfg->g_netlink->nl_socket_queries, RTM_GETADDR, AF_UNSPEC,
+ 1) == -1)
+ goto end;
+ netlink_recv(cfg, cfg->g_netlink->nl_socket_queries, NULL, ifaddrs);
+ if (netlink_send(cfg->g_netlink->nl_socket_queries, RTM_GETLINK, AF_PACKET,
+ 2) == -1)
+ goto end;
+ netlink_recv(cfg, cfg->g_netlink->nl_socket_queries, ifs, NULL);
+#ifdef ENABLE_DOT1
+ /* If we have a bridge, search for VLAN-aware bridges */
+ TAILQ_FOREACH (iff, ifs, next) {
+ if (iff->type & IFACE_BRIDGE_T) {
+ log_debug("netlink",
+ "interface %s is a bridge, check for VLANs", iff->name);
+ if (netlink_send(cfg->g_netlink->nl_socket_queries, RTM_GETLINK,
+ AF_BRIDGE, 3) == -1)
+ goto end;
+ netlink_recv(cfg, cfg->g_netlink->nl_socket_queries, ifs, NULL);
+ break;
+ }
+ }
+#endif
+
+ /* Listen to any future change */
+ cfg->g_iface_cb = netlink_change_cb;
+ if (levent_iface_subscribe(cfg, cfg->g_netlink->nl_socket_changes) == -1) {
+ goto end;
+ }
+
+ return 0;
+end:
+ netlink_cleanup(cfg);
+ return -1;
+}
+
+/**
+ * Cleanup netlink subsystem.
+ */
+void
+netlink_cleanup(struct lldpd *cfg)
+{
+ if (cfg->g_netlink == NULL) return;
+ if (cfg->g_netlink->nl_socket_changes != -1)
+ close(cfg->g_netlink->nl_socket_changes);
+ if (cfg->g_netlink->nl_socket_queries != -1)
+ close(cfg->g_netlink->nl_socket_queries);
+ interfaces_free_devices(cfg->g_netlink->devices);
+ interfaces_free_addresses(cfg->g_netlink->addresses);
+
+ free(cfg->g_netlink);
+ cfg->g_netlink = NULL;
+}
+
+/**
+ * Receive the list of interfaces.
+ *
+ * @return a list of interfaces.
+ */
+struct interfaces_device_list *
+netlink_get_interfaces(struct lldpd *cfg)
+{
+ if (netlink_initialize(cfg) == -1) return NULL;
+ struct interfaces_device *ifd;
+ TAILQ_FOREACH (ifd, cfg->g_netlink->devices, next) {
+ ifd->ignore = 0;
+ }
+ return cfg->g_netlink->devices;
+}
+
+/**
+ * Receive the list of addresses.
+ *
+ * @return a list of addresses.
+ */
+struct interfaces_address_list *
+netlink_get_addresses(struct lldpd *cfg)
+{
+ if (netlink_initialize(cfg) == -1) return NULL;
+ return cfg->g_netlink->addresses;
+}
diff --git a/src/daemon/pattern.c b/src/daemon/pattern.c
new file mode 100644
index 0000000..0f9885d
--- /dev/null
+++ b/src/daemon/pattern.c
@@ -0,0 +1,80 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/*
+ * Copyright (c) 2008 Vincent Bernat <bernat@luffy.cx>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "lldpd.h"
+
+#include <string.h>
+#include <fnmatch.h>
+
+/**
+ * Match a list of patterns.
+ *
+ * @param string String to match against the list of patterns
+ * @param patterns List of comma separated patterns. A pattern may
+ * begin by `!` to negate it. In this case, it is
+ * denied. A pattern may begin with `!!`. In this
+ * case, it is allowed back. Each pattern will then be
+ * matched against `fnmatch()` function.
+ * @param found Value to return if the pattern isn't found. Should be either
+ * PATTERN_MATCH_DENIED or PATTERN_MACTH_DENIED.
+ *
+ * If a pattern is found matching and denied at the same time, it
+ * will be denied. If it is both allowed and denied, it
+ * will be allowed.
+ *
+ * @return PATTERN_MATCH_DENIED if the string matches a denied pattern which is not
+ * allowed or if the pattern wasn't found and `found` was set to
+ * PATTERN_MATCH_DENIED. Otherwise, return PATTERN_MATCH_ALLOWED unless the
+ * interface match is exact, in this case return PATTERN_MATCH_ALLOWED_EXACT.
+ */
+enum pattern_match_result
+pattern_match(char *string, char *patterns, int found)
+{
+ char *pattern;
+ int denied = 0;
+ found = found ? PATTERN_MATCH_ALLOWED : PATTERN_MATCH_DENIED;
+
+ if ((patterns = strdup(patterns)) == NULL) {
+ log_warnx("interfaces", "unable to allocate memory");
+ return PATTERN_MATCH_DENIED;
+ }
+
+ for (pattern = strtok(patterns, ","); pattern != NULL;
+ pattern = strtok(NULL, ",")) {
+ if ((pattern[0] == '!') && (pattern[1] == '!') &&
+ (fnmatch(pattern + 2, string, 0) == 0)) {
+ /* Allowed. No need to search further. */
+ found = (strcmp(pattern + 2, string)) ?
+ PATTERN_MATCH_ALLOWED :
+ PATTERN_MATCH_ALLOWED_EXACT;
+ break;
+ }
+ if ((pattern[0] == '!') && (fnmatch(pattern + 1, string, 0) == 0)) {
+ denied = 1;
+ found = PATTERN_MATCH_DENIED;
+ } else if (!denied && fnmatch(pattern, string, 0) == 0) {
+ if (!strcmp(pattern, string)) {
+ found = PATTERN_MATCH_ALLOWED_EXACT;
+ } else if (found < 2) {
+ found = PATTERN_MATCH_ALLOWED;
+ }
+ }
+ }
+
+ free(patterns);
+ return found;
+}
diff --git a/src/daemon/priv-bsd.c b/src/daemon/priv-bsd.c
new file mode 100644
index 0000000..61f332a
--- /dev/null
+++ b/src/daemon/priv-bsd.c
@@ -0,0 +1,202 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/*
+ * Copyright (c) 2008 Vincent Bernat <bernat@luffy.cx>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "lldpd.h"
+
+#include <unistd.h>
+#include <net/bpf.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <string.h>
+
+int
+asroot_iface_init_os(int ifindex, char *name, int *fd)
+{
+ int enable, required, rc;
+ struct bpf_insn filter[] = { LLDPD_FILTER_F };
+ struct ifreq ifr = { .ifr_name = {} };
+ struct bpf_program fprog = { .bf_insns = filter,
+ .bf_len = sizeof(filter) / sizeof(struct bpf_insn) };
+
+#ifndef HOST_OS_SOLARIS
+ int n = 0;
+ char dev[20];
+ do {
+ snprintf(dev, sizeof(dev), "/dev/bpf%d", n++);
+ *fd = open(dev, O_RDWR);
+ } while (*fd < 0 && errno == EBUSY);
+#else
+ *fd = open("/dev/bpf", O_RDWR);
+#endif
+ if (*fd < 0) {
+ rc = errno;
+ log_warn("privsep", "unable to find a free BPF");
+ return rc;
+ }
+
+ /* Set buffer size */
+ required = ETHER_MAX_LEN + BPF_WORDALIGN(sizeof(struct bpf_hdr));
+ if (ioctl(*fd, BIOCSBLEN, (caddr_t)&required) < 0) {
+ rc = errno;
+ log_warn("privsep", "unable to set receive buffer size for BPF on %s",
+ name);
+ return rc;
+ }
+
+ /* Bind the interface to BPF device */
+ strlcpy(ifr.ifr_name, name, IFNAMSIZ);
+ if (ioctl(*fd, BIOCSETIF, (caddr_t)&ifr) < 0) {
+ rc = errno;
+ log_warn("privsep", "failed to bind interface %s to BPF", name);
+ return rc;
+ }
+
+ /* Disable buffering */
+ enable = 1;
+ if (ioctl(*fd, BIOCIMMEDIATE, (caddr_t)&enable) < 0) {
+ rc = errno;
+ log_warn("privsep", "unable to disable buffering for %s", name);
+ return rc;
+ }
+
+ /* Let us write the MAC address (raw packet mode) */
+ enable = 1;
+ if (ioctl(*fd, BIOCSHDRCMPLT, (caddr_t)&enable) < 0) {
+ rc = errno;
+ log_warn("privsep", "unable to set the `header complete` flag for %s",
+ name);
+ return rc;
+ }
+
+ /* Don't see sent packets */
+#ifdef HOST_OS_OPENBSD
+ enable = BPF_DIRECTION_OUT;
+ if (ioctl(*fd, BIOCSDIRFILT, (caddr_t)&enable) < 0)
+#else
+ enable = 0;
+ if (ioctl(*fd, BIOCSSEESENT, (caddr_t)&enable) < 0)
+#endif
+ {
+ rc = errno;
+ log_warn("privsep",
+ "unable to set packet direction for BPF filter on %s", name);
+ return rc;
+ }
+
+ /* Install read filter */
+ if (ioctl(*fd, BIOCSETF, (caddr_t)&fprog) < 0) {
+ rc = errno;
+ log_warn("privsep", "unable to setup BPF filter for %s", name);
+ return rc;
+ }
+#ifdef BIOCSETWF
+ /* Install write filter (optional) */
+ if (ioctl(*fd, BIOCSETWF, (caddr_t)&fprog) < 0) {
+ rc = errno;
+ log_info("privsep", "unable to setup write BPF filter for %s", name);
+ return rc;
+ }
+#endif
+
+#ifdef BIOCLOCK
+ /* Lock interface, but first make it non blocking since we cannot do
+ * this later */
+ levent_make_socket_nonblocking(*fd);
+ if (ioctl(*fd, BIOCLOCK, (caddr_t)&enable) < 0) {
+ rc = errno;
+ log_info("privsep", "unable to lock BPF interface %s", name);
+ return rc;
+ }
+#endif
+ return 0;
+}
+
+int
+asroot_iface_description_os(const char *name, const char *description)
+{
+#ifdef IFDESCRSIZE
+# if defined HOST_OS_FREEBSD || defined HOST_OS_OPENBSD
+ char descr[IFDESCRSIZE];
+ int rc, sock = -1;
+# if defined HOST_OS_FREEBSD
+ struct ifreq ifr = { .ifr_buffer = { .buffer = descr, .length = IFDESCRSIZE } };
+# else
+ struct ifreq ifr = { .ifr_data = (caddr_t)descr };
+# endif
+ strlcpy(ifr.ifr_name, name, sizeof(ifr.ifr_name));
+ if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) == 1) {
+ rc = errno;
+ log_warnx("privsep", "unable to open inet socket");
+ return rc;
+ }
+ if (strlen(description) == 0) {
+ /* No neighbor, try to append "was" to the current description */
+ if (ioctl(sock, SIOCGIFDESCR, (caddr_t)&ifr) < 0) {
+ rc = errno;
+ log_warnx("privsep", "unable to get description of %s", name);
+ close(sock);
+ return rc;
+ }
+ if (strncmp(descr, "lldpd: ", 7) == 0) {
+ if (strncmp(descr + 7, "was ", 4) == 0) {
+ /* Already has an old neighbor */
+ close(sock);
+ return 0;
+ } else {
+ /* Append was */
+ memmove(descr + 11, descr + 7, sizeof(descr) - 11);
+ memcpy(descr, "lldpd: was ", 11);
+ }
+ } else {
+ /* No description, no neighbor */
+ strlcpy(descr, "lldpd: no neighbor", sizeof(descr));
+ }
+ } else
+ snprintf(descr, sizeof(descr), "lldpd: connected to %s", description);
+# if defined HOST_OS_FREEBSD
+ ift.ifr_buffer.length = strlen(descr);
+# endif
+ if (ioctl(sock, SIOCSIFDESCR, (caddr_t)&ifr) < 0) {
+ rc = errno;
+ log_warnx("privsep", "unable to set description of %s", name);
+ close(sock);
+ return rc;
+ }
+ close(sock);
+ return 0;
+# endif
+#endif /* IFDESCRSIZE */
+ static int once = 0;
+ if (!once) {
+ log_warnx("privsep", "cannot set interface description for this OS");
+ once = 1;
+ }
+ return 0;
+}
+
+int
+asroot_iface_promisc_os(const char *name)
+{
+ /* The promiscuous mode can be set when setting BPF
+ (BIOCPROMISC). Unfortunately, the interface is locked down and we
+ cannot change that without reopening a new socket. Let's do nothing
+ for now. */
+ return 0;
+}
diff --git a/src/daemon/priv-linux.c b/src/daemon/priv-linux.c
new file mode 100644
index 0000000..9a6f90a
--- /dev/null
+++ b/src/daemon/priv-linux.c
@@ -0,0 +1,337 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/*
+ * Copyright (c) 2008 Vincent Bernat <bernat@luffy.cx>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "lldpd.h"
+
+#include <unistd.h>
+#include <inttypes.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <limits.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <regex.h>
+#include <sys/ioctl.h>
+#include <netpacket/packet.h> /* For sockaddr_ll */
+#if defined(__clang__)
+# pragma clang diagnostic push
+# pragma clang diagnostic ignored "-Wdocumentation"
+#endif
+#include <linux/filter.h> /* For BPF filtering */
+#include <linux/sockios.h>
+#include <linux/if_ether.h>
+#include <linux/ethtool.h>
+#if defined(__clang__)
+# pragma clang diagnostic pop
+#endif
+
+/* Defined in linux/pkt_sched.h */
+#define TC_PRIO_CONTROL 7
+/* Defined in sysfs/libsysfs.h */
+#define SYSFS_PATH_MAX 256
+
+/* Proxy for open */
+int
+priv_open(const char *file)
+{
+ int len, rc;
+ enum priv_cmd cmd = PRIV_OPEN;
+ must_write(PRIV_UNPRIVILEGED, &cmd, sizeof(enum priv_cmd));
+ len = strlen(file);
+ must_write(PRIV_UNPRIVILEGED, &len, sizeof(int));
+ must_write(PRIV_UNPRIVILEGED, file, len);
+ priv_wait();
+ must_read(PRIV_UNPRIVILEGED, &rc, sizeof(int));
+ if (rc == -1) return rc;
+ return receive_fd(PRIV_UNPRIVILEGED);
+}
+
+void
+asroot_open()
+{
+ const char *authorized[] = { PROCFS_SYS_NET "ipv4/ip_forward",
+ PROCFS_SYS_NET "ipv6/conf/all/forwarding",
+ "/proc/net/bonding/[^.][^/]*", "/proc/self/net/bonding/[^.][^/]*",
+#ifdef ENABLE_OLDIES
+ SYSFS_CLASS_NET "[^.][^/]*/brforward",
+ SYSFS_CLASS_NET "[^.][^/]*/brport",
+ SYSFS_CLASS_NET "[^.][^/]*/brif/[^.][^/]*/port_no",
+#endif
+ SYSFS_CLASS_DMI "product_version", SYSFS_CLASS_DMI "product_serial",
+ SYSFS_CLASS_DMI "product_name", SYSFS_CLASS_DMI "bios_version",
+ SYSFS_CLASS_DMI "sys_vendor", SYSFS_CLASS_DMI "chassis_asset_tag",
+ NULL };
+ const char **f;
+ char *file;
+ int fd, len, rc;
+ regex_t preg;
+
+ must_read(PRIV_PRIVILEGED, &len, sizeof(len));
+ if (len < 0 || len > PATH_MAX) fatalx("privsep", "too large value requested");
+ if ((file = (char *)malloc(len + 1)) == NULL) fatal("privsep", NULL);
+ must_read(PRIV_PRIVILEGED, file, len);
+ file[len] = '\0';
+
+ for (f = authorized; *f != NULL; f++) {
+ if (regcomp(&preg, *f, REG_NOSUB) != 0) /* Should not happen */
+ fatal("privsep", "unable to compile a regex");
+ if (regexec(&preg, file, 0, NULL, 0) == 0) {
+ regfree(&preg);
+ break;
+ }
+ regfree(&preg);
+ }
+ if (*f == NULL) {
+ log_warnx("privsep", "not authorized to open %s", file);
+ rc = -1;
+ must_write(PRIV_PRIVILEGED, &rc, sizeof(int));
+ free(file);
+ return;
+ }
+ if ((fd = open(file, O_RDONLY)) == -1) {
+ rc = -1;
+ must_write(PRIV_PRIVILEGED, &rc, sizeof(int));
+ free(file);
+ return;
+ }
+ free(file);
+ must_write(PRIV_PRIVILEGED, &fd, sizeof(int));
+ send_fd(PRIV_PRIVILEGED, fd);
+ close(fd);
+}
+
+/* Quirks needed by some additional interfaces. Currently, this is limited to
+ * disabling LLDP firmware for i40e. */
+static void
+asroot_iface_init_quirks(int ifindex, char *name)
+{
+ int s = -1;
+ int fd = -1;
+
+ /* Check driver. */
+ struct ethtool_drvinfo ethc = { .cmd = ETHTOOL_GDRVINFO };
+ struct ifreq ifr = { .ifr_data = (caddr_t)&ethc };
+ if ((s = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
+ log_warn("privsep", "unable to open a socket");
+ goto end;
+ }
+ strlcpy(ifr.ifr_name, name, IFNAMSIZ);
+ if (ioctl(s, SIOCETHTOOL, &ifr) != 0 ||
+ strncmp("i40e", ethc.driver, sizeof(ethc.driver))) {
+ /* Not i40e */
+ goto end;
+ }
+ log_info("interfaces",
+ "i40e driver detected for %s, disabling LLDP in firmware", name);
+
+ /* We assume debugfs is mounted. Otherwise, we would need to check if it
+ * is mounted, then unshare a new mount namespace, mount it, issues the
+ * command, leave the namespace. Let's see if there is such a need. */
+
+ /* Alternative is to use ethtool (ethtool --set-priv-flags ens5f0
+ * disable-fw-lldp on). However, this requires a recent firmware (from
+ * i40e_ethtool.c):
+ *
+ * If the driver detected FW LLDP was disabled on init, this flag could
+ * be set, however we do not support _changing_ the flag:
+ * - on XL710 if NPAR is enabled or FW API version < 1.7
+ * - on X722 with FW API version < 1.6
+ */
+
+ char command[] = "lldp stop";
+ char sysfs_path[SYSFS_PATH_MAX + 1];
+ if (snprintf(sysfs_path, SYSFS_PATH_MAX, "/sys/kernel/debug/i40e/%.*s/command",
+ (int)sizeof(ethc.bus_info), ethc.bus_info) >= SYSFS_PATH_MAX) {
+ log_warnx("interfaces", "path truncated");
+ goto end;
+ }
+ if ((fd = open(sysfs_path, O_WRONLY)) == -1) {
+ if (errno == ENOENT) {
+ log_info("interfaces",
+ "%s does not exist, "
+ "cannot disable LLDP in firmware for %s",
+ sysfs_path, name);
+ goto end;
+ }
+ log_warn("interfaces",
+ "cannot open %s to disable LLDP in firmware for %s", sysfs_path,
+ name);
+ goto end;
+ }
+ if (write(fd, command, sizeof(command) - 1) == -1) {
+ log_warn("interfaces", "cannot disable LLDP in firmware for %s", name);
+ goto end;
+ }
+end:
+ if (s != -1) close(s);
+ if (fd != -1) close(fd);
+}
+
+int
+asroot_iface_init_os(int ifindex, char *name, int *fd)
+{
+ int rc;
+ /* Open listening socket to receive/send frames */
+ if ((*fd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL))) < 0) {
+ rc = errno;
+ return rc;
+ }
+
+ struct sockaddr_ll sa = { .sll_family = AF_PACKET, .sll_ifindex = ifindex };
+ if (bind(*fd, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
+ rc = errno;
+ log_warn("privsep", "unable to bind to raw socket for interface %s",
+ name);
+ return rc;
+ }
+
+ /* Set filter */
+ log_debug("privsep", "set BPF filter for %s", name);
+ static struct sock_filter lldpd_filter_f[] = { LLDPD_FILTER_F };
+ struct sock_fprog prog = { .filter = lldpd_filter_f,
+ .len = sizeof(lldpd_filter_f) / sizeof(struct sock_filter) };
+ if (setsockopt(*fd, SOL_SOCKET, SO_ATTACH_FILTER, &prog, sizeof(prog)) < 0) {
+ rc = errno;
+ log_warn("privsep", "unable to change filter for %s", name);
+ return rc;
+ }
+
+ /* Set priority to TC_PRIO_CONTROL for ice Intel cards. See #444. */
+ int prio = TC_PRIO_CONTROL;
+ if (setsockopt(*fd, SOL_SOCKET, SO_PRIORITY, &prio, sizeof(prio)) < 0) {
+ rc = errno;
+ log_warn("privsep",
+ "unable to set priority \"control\" to socket for interface %s",
+ name);
+ return rc;
+ }
+
+#ifdef SO_LOCK_FILTER
+ int lock = 1;
+ if (setsockopt(*fd, SOL_SOCKET, SO_LOCK_FILTER, &lock, sizeof(lock)) < 0) {
+ if (errno != ENOPROTOOPT) {
+ rc = errno;
+ log_warn("privsep", "unable to lock filter for %s", name);
+ return rc;
+ }
+ }
+#endif
+#ifdef PACKET_IGNORE_OUTGOING
+ int ignore = 1;
+ if (setsockopt(*fd, SOL_PACKET, PACKET_IGNORE_OUTGOING, &ignore,
+ sizeof(ignore)) < 0) {
+ if (errno != ENOPROTOOPT) {
+ rc = errno;
+ log_warn("privsep",
+ "unable to set packet direction for BPF filter on %s",
+ name);
+ return rc;
+ }
+ }
+#endif
+
+ asroot_iface_init_quirks(ifindex, name);
+ return 0;
+}
+
+int
+asroot_iface_description_os(const char *name, const char *description)
+{
+ /* We could use netlink but this is a lot to do in a privileged
+ * process. Just write to /sys/class/net/XXXX/ifalias. */
+ char *file;
+ char descr[IFALIASZ];
+ FILE *fp;
+ int rc;
+ if (name[0] == '\0' || name[0] == '.') {
+ log_warnx("privsep", "odd interface name %s", name);
+ return -1;
+ }
+ if (asprintf(&file, SYSFS_CLASS_NET "%s/ifalias", name) == -1) {
+ log_warn("privsep",
+ "unable to allocate memory for setting description");
+ return -1;
+ }
+ if ((fp = fopen(file, "r+")) == NULL) {
+ rc = errno;
+ log_debug("privsep", "cannot open interface description for %s: %s",
+ name, strerror(errno));
+ free(file);
+ return rc;
+ }
+ free(file);
+ if (strlen(description) == 0 && fgets(descr, sizeof(descr), fp) != NULL) {
+ if (strncmp(descr, "lldpd: ", 7) == 0) {
+ if (strncmp(descr + 7, "was ", 4) == 0) {
+ /* Already has an old neighbor */
+ fclose(fp);
+ return 0;
+ } else {
+ /* Append was */
+ memmove(descr + 11, descr + 7, sizeof(descr) - 11);
+ memcpy(descr, "lldpd: was ", 11);
+ }
+ } else {
+ /* No description, no neighbor */
+ strlcpy(descr, "lldpd: no neighbor", sizeof(descr));
+ }
+ } else
+ snprintf(descr, sizeof(descr), "lldpd: connected to %s", description);
+ if (fputs(descr, fp) == EOF) {
+ log_debug("privsep", "cannot set interface description for %s", name);
+ fclose(fp);
+ return -1;
+ }
+ fclose(fp);
+ return 0;
+}
+
+int
+asroot_iface_promisc_os(const char *name)
+{
+ int s, rc;
+ if ((s = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL))) < 0) {
+ rc = errno;
+ log_warn("privsep", "unable to open raw socket");
+ return rc;
+ }
+
+ struct ifreq ifr = {};
+ strlcpy(ifr.ifr_name, name, sizeof(ifr.ifr_name));
+
+ if (ioctl(s, SIOCGIFFLAGS, &ifr) == -1) {
+ rc = errno;
+ log_warn("privsep", "unable to get interface flags for %s", name);
+ close(s);
+ return rc;
+ }
+
+ if (ifr.ifr_flags & IFF_PROMISC) {
+ close(s);
+ return 0;
+ }
+ ifr.ifr_flags |= IFF_PROMISC;
+ if (ioctl(s, SIOCSIFFLAGS, &ifr) == -1) {
+ rc = errno;
+ log_warn("privsep", "unable to set promisc mode for %s", name);
+ close(s);
+ return rc;
+ }
+ log_info("privsep", "promiscuous mode enabled for %s", name);
+ close(s);
+ return 0;
+}
diff --git a/src/daemon/priv-seccomp.c b/src/daemon/priv-seccomp.c
new file mode 100644
index 0000000..8322cb2
--- /dev/null
+++ b/src/daemon/priv-seccomp.c
@@ -0,0 +1,210 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/*
+ * Copyright (c) 2008 Vincent Bernat <bernat@luffy.cx>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "lldpd.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <signal.h>
+
+#include "syscall-names.h"
+#include <seccomp.h>
+
+#ifndef SYS_SECCOMP
+# define SYS_SECCOMP 1
+#endif
+
+#if defined(__i386__)
+# define REG_SYSCALL REG_EAX
+# define ARCH_NR AUDIT_ARCH_I386
+#elif defined(__x86_64__)
+# define REG_SYSCALL REG_RAX
+# define ARCH_NR AUDIT_ARCH_X86_64
+#else
+# error "Platform does not support seccomp filter yet"
+# define REG_SYSCALL 0
+# define ARCH_NR 0
+#endif
+
+/* If there is no privilege separation, seccomp is currently useless */
+#ifdef ENABLE_PRIVSEP
+static int monitored = -1;
+static int trapped = 0;
+/**
+ * SIGSYS signal handler
+ * @param nr the signal number
+ * @param info siginfo_t pointer
+ * @param void_context handler context
+ *
+ * Simple signal handler for SIGSYS displaying the error, killing the child and
+ * exiting.
+ *
+ */
+static void
+priv_seccomp_trap_handler(int signal, siginfo_t *info, void *vctx)
+{
+ ucontext_t *ctx = (ucontext_t *)(vctx);
+ unsigned int syscall;
+
+ if (trapped) _exit(161); /* Avoid loops */
+
+ /* Get details */
+ if (info->si_code != SYS_SECCOMP) return;
+ if (!ctx) _exit(161);
+ syscall = ctx->uc_mcontext.gregs[REG_SYSCALL];
+ trapped = 1;
+
+ /* Log them. Technically, `log_warnx()` is not signal safe, but we are
+ * unlikely to reenter here. */
+ log_warnx("seccomp", "invalid syscall attempted: %s(%d)",
+ (syscall < sizeof(syscall_names)) ? syscall_names[syscall] : "unknown",
+ syscall);
+
+ /* Kill children and exit */
+ kill(monitored, SIGTERM);
+ fatalx("seccomp", "invalid syscall not allowed: stop here");
+ _exit(161);
+}
+
+/**
+ * Install a TRAP action signal handler
+ *
+ * This function installs the TRAP action signal handler and is based on
+ * examples from Will Drewry and Kees Cook. Returns zero on success, negative
+ * values on failure.
+ *
+ */
+static int
+priv_seccomp_trap_install()
+{
+ struct sigaction signal_handler = {};
+ sigset_t signal_mask;
+
+ sigemptyset(&signal_mask);
+ sigaddset(&signal_mask, SIGSYS);
+
+ signal_handler.sa_sigaction = &priv_seccomp_trap_handler;
+ signal_handler.sa_flags = SA_SIGINFO;
+ if (sigaction(SIGSYS, &signal_handler, NULL) < 0) return -errno;
+ if (sigprocmask(SIG_UNBLOCK, &signal_mask, NULL)) return -errno;
+
+ return 0;
+}
+
+/**
+ * Initialize seccomp.
+ *
+ * @param remote file descriptor to talk with the unprivileged process
+ * @param monitored monitored child
+ * @return negative on failures or 0 if everything was setup
+ */
+int
+priv_seccomp_init(int remote, int child)
+{
+ int rc = -1;
+ scmp_filter_ctx ctx = NULL;
+
+ log_debug("seccomp", "initialize libseccomp filter");
+ monitored = child;
+ if (priv_seccomp_trap_install() < 0) {
+ log_warn("seccomp", "unable to install SIGSYS handler");
+ goto failure_scmp;
+ }
+
+ if ((ctx = seccomp_init(SCMP_ACT_TRAP)) == NULL) {
+ log_warnx("seccomp", "unable to initialize libseccomp subsystem");
+ goto failure_scmp;
+ }
+
+ if ((rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(read), 1,
+ SCMP_CMP(0, SCMP_CMP_EQ, remote))) < 0 ||
+ (rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 1,
+ SCMP_CMP(0, SCMP_CMP_EQ, remote))) < 0) {
+ errno = -rc;
+ log_warn("seccomp", "unable to allow read/write on remote socket");
+ goto failure_scmp;
+ }
+
+ /* We are far more generic from here. */
+ if ((rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(read), 0)) < 0 ||
+ (rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 0)) <
+ 0 || /* write needed for */
+ (rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(open), 0)) < 0 ||
+ (rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(openat), 0)) < 0 ||
+ (rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(lseek), 0)) < 0 ||
+ (rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(fcntl), 0)) < 0 ||
+ (rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(kill), 0)) < 0 ||
+ (rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(socket), 0)) < 0 ||
+ (rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(bind), 0)) < 0 ||
+ (rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(setsockopt), 0)) < 0 ||
+ (rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(getsockname), 0)) <
+ 0 ||
+ (rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(uname), 0)) < 0 ||
+ (rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(unlink), 0)) < 0 ||
+ (rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(ioctl), 0)) < 0 ||
+ (rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(sendmsg), 0)) < 0 ||
+ (rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(sendmmsg), 0)) < 0 ||
+ (rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(wait4), 0)) < 0 ||
+ (rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(stat), 0)) < 0 ||
+ (rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(brk), 0)) <
+ 0 || /* brk needed for newer libc */
+ (rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(getpid), 0)) < 0 ||
+ (rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(rt_sigreturn), 0)) <
+ 0 ||
+ (rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(close), 0)) < 0 ||
+ (rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(sendto), 0)) < 0 ||
+ (rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(poll), 0)) < 0 ||
+ (rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(recvmsg), 0)) < 0 ||
+ (rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(recvfrom), 0)) < 0 ||
+ (rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(readv), 0)) < 0 ||
+ (rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(mprotect), 0)) < 0 ||
+ (rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(sendmmsg), 0)) < 0 ||
+ (rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(clock_gettime), 0)) <
+ 0 ||
+ (rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(gettimeofday), 0)) <
+ 0 ||
+ (rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(newfstatat), 0)) < 0 ||
+ (rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(pread64), 0)) < 0 ||
+ (rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(access), 0)) < 0 ||
+ (rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(rt_sigprocmask), 0)) <
+ 0 ||
+ (rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(getsockopt), 0)) < 0 ||
+ (rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(ppoll), 0)) < 0 ||
+ /* The following are for resolving addresses */
+ (rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(mmap), 0)) < 0 ||
+ (rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(munmap), 0)) < 0 ||
+ (rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(fstat), 0)) < 0 ||
+ (rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(connect), 0)) < 0 ||
+ (rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(futex), 0)) < 0 ||
+
+ (rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit_group), 0)) < 0) {
+ errno = -rc;
+ log_warn("seccomp", "unable to build seccomp rules");
+ goto failure_scmp;
+ }
+
+ if ((rc = seccomp_load(ctx)) < 0) {
+ errno = -rc;
+ log_warn("seccomp", "unable to load libseccomp filter");
+ goto failure_scmp;
+ }
+
+failure_scmp:
+ seccomp_release(ctx);
+ return rc;
+}
+#endif
diff --git a/src/daemon/priv.c b/src/daemon/priv.c
new file mode 100644
index 0000000..d642328
--- /dev/null
+++ b/src/daemon/priv.c
@@ -0,0 +1,761 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/*
+ * Copyright (c) 2008 Vincent Bernat <bernat@luffy.cx>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/* This file contains code for privilege separation. When an error arises in
+ * monitor (which is running as root), it just stops instead of trying to
+ * recover. This module also contains proxies to privileged operations. In this
+ * case, error can be non fatal. */
+
+#include "lldpd.h"
+#include "trace.h"
+
+#include <stdio.h>
+#include <unistd.h>
+#include <signal.h>
+#include <errno.h>
+#include <limits.h>
+#include <sys/wait.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <fcntl.h>
+#include <grp.h>
+#include <sys/utsname.h>
+#include <sys/ioctl.h>
+#include <netinet/if_ether.h>
+
+#ifdef HAVE_LINUX_CAPABILITIES
+# include <sys/capability.h>
+# include <sys/prctl.h>
+#endif
+
+#if defined HOST_OS_FREEBSD || defined HOST_OS_OSX || defined HOST_OS_DRAGONFLY
+# include <net/if_dl.h>
+#endif
+#if defined HOST_OS_SOLARIS
+# include <sys/sockio.h>
+#endif
+
+/* Use resolv.h */
+#ifdef HAVE_SYS_TYPES_H
+# include <sys/types.h>
+#endif
+#ifdef HAVE_NETINET_IN_H
+# include <netinet/in.h>
+#endif
+#ifdef HAVE_ARPA_NAMESER_H
+# include <arpa/nameser.h> /* DNS HEADER struct */
+#endif
+#ifdef HAVE_NETDB_H
+# include <netdb.h>
+#endif
+#ifdef HAVE_RESOLV_H
+# include <resolv.h>
+#endif
+
+/* Bionic has res_init() but it's not in any header */
+#if defined HAVE_RES_INIT && defined __BIONIC__
+int res_init(void);
+#endif
+
+#ifdef ENABLE_PRIVSEP
+static int monitored = -1; /* Child */
+#endif
+
+/* Proxies */
+static void
+priv_ping()
+{
+ int rc;
+ enum priv_cmd cmd = PRIV_PING;
+ must_write(PRIV_UNPRIVILEGED, &cmd, sizeof(enum priv_cmd));
+ priv_wait();
+ must_read(PRIV_UNPRIVILEGED, &rc, sizeof(int));
+ log_debug("privsep", "monitor ready");
+}
+
+/* Proxy for ctl_cleanup */
+void
+priv_ctl_cleanup(const char *ctlname)
+{
+ int rc, len = strlen(ctlname);
+ enum priv_cmd cmd = PRIV_DELETE_CTL_SOCKET;
+ must_write(PRIV_UNPRIVILEGED, &cmd, sizeof(enum priv_cmd));
+ must_write(PRIV_UNPRIVILEGED, &len, sizeof(int));
+ must_write(PRIV_UNPRIVILEGED, ctlname, len);
+ priv_wait();
+ must_read(PRIV_UNPRIVILEGED, &rc, sizeof(int));
+}
+
+/* Proxy for gethostname */
+char *
+priv_gethostname()
+{
+ static char *buf = NULL;
+ int len;
+ enum priv_cmd cmd = PRIV_GET_HOSTNAME;
+ must_write(PRIV_UNPRIVILEGED, &cmd, sizeof(enum priv_cmd));
+ priv_wait();
+ must_read(PRIV_UNPRIVILEGED, &len, sizeof(int));
+ if (len < 0 || len > 255) fatalx("privsep", "too large value requested");
+ if ((buf = (char *)realloc(buf, len + 1)) == NULL) fatal("privsep", NULL);
+ must_read(PRIV_UNPRIVILEGED, buf, len);
+ buf[len] = '\0';
+ return buf;
+}
+
+int
+priv_iface_init(int index, char *iface)
+{
+ int rc;
+ char dev[IFNAMSIZ] = {};
+ enum priv_cmd cmd = PRIV_IFACE_INIT;
+ must_write(PRIV_UNPRIVILEGED, &cmd, sizeof(enum priv_cmd));
+ must_write(PRIV_UNPRIVILEGED, &index, sizeof(int));
+ strlcpy(dev, iface, IFNAMSIZ);
+ must_write(PRIV_UNPRIVILEGED, dev, IFNAMSIZ);
+ priv_wait();
+ must_read(PRIV_UNPRIVILEGED, &rc, sizeof(int));
+ if (rc != 0) return -1;
+ return receive_fd(PRIV_UNPRIVILEGED);
+}
+
+int
+priv_iface_multicast(const char *name, const u_int8_t *mac, int add)
+{
+ int rc;
+ enum priv_cmd cmd = PRIV_IFACE_MULTICAST;
+ must_write(PRIV_UNPRIVILEGED, &cmd, sizeof(enum priv_cmd));
+ must_write(PRIV_UNPRIVILEGED, name, IFNAMSIZ);
+ must_write(PRIV_UNPRIVILEGED, mac, ETHER_ADDR_LEN);
+ must_write(PRIV_UNPRIVILEGED, &add, sizeof(int));
+ priv_wait();
+ must_read(PRIV_UNPRIVILEGED, &rc, sizeof(int));
+ return rc;
+}
+
+int
+priv_iface_description(const char *name, const char *description)
+{
+ int rc, len = strlen(description);
+ enum priv_cmd cmd = PRIV_IFACE_DESCRIPTION;
+ must_write(PRIV_UNPRIVILEGED, &cmd, sizeof(enum priv_cmd));
+ must_write(PRIV_UNPRIVILEGED, name, IFNAMSIZ);
+ must_write(PRIV_UNPRIVILEGED, &len, sizeof(int));
+ must_write(PRIV_UNPRIVILEGED, description, len);
+ priv_wait();
+ must_read(PRIV_UNPRIVILEGED, &rc, sizeof(int));
+ return rc;
+}
+
+/* Proxy to set interface in promiscuous mode */
+int
+priv_iface_promisc(const char *ifname)
+{
+ int rc;
+ enum priv_cmd cmd = PRIV_IFACE_PROMISC;
+ must_write(PRIV_UNPRIVILEGED, &cmd, sizeof(enum priv_cmd));
+ must_write(PRIV_UNPRIVILEGED, ifname, IFNAMSIZ);
+ priv_wait();
+ must_read(PRIV_UNPRIVILEGED, &rc, sizeof(int));
+ return rc;
+}
+
+int
+priv_snmp_socket(struct sockaddr_un *addr)
+{
+ int rc;
+ enum priv_cmd cmd = PRIV_SNMP_SOCKET;
+ must_write(PRIV_UNPRIVILEGED, &cmd, sizeof(enum priv_cmd));
+ must_write(PRIV_UNPRIVILEGED, addr, sizeof(struct sockaddr_un));
+ priv_wait();
+ must_read(PRIV_UNPRIVILEGED, &rc, sizeof(int));
+ if (rc < 0) return rc;
+ return receive_fd(PRIV_UNPRIVILEGED);
+}
+
+static void
+asroot_ping()
+{
+ int rc = 1;
+ must_write(PRIV_PRIVILEGED, &rc, sizeof(int));
+}
+
+static void
+asroot_ctl_cleanup()
+{
+ int len;
+ char *ctlname;
+ int rc = 0;
+
+ must_read(PRIV_PRIVILEGED, &len, sizeof(int));
+ if (len < 0 || len > PATH_MAX) fatalx("privsep", "too large value requested");
+ if ((ctlname = (char *)malloc(len + 1)) == NULL) fatal("privsep", NULL);
+
+ must_read(PRIV_PRIVILEGED, ctlname, len);
+ ctlname[len] = 0;
+
+ ctl_cleanup(ctlname);
+ free(ctlname);
+
+ /* Ack */
+ must_write(PRIV_PRIVILEGED, &rc, sizeof(int));
+}
+
+static void
+asroot_gethostname()
+{
+ struct utsname un;
+ struct addrinfo hints = { .ai_flags = AI_CANONNAME };
+ struct addrinfo *res;
+ int len;
+ if (uname(&un) < 0) fatal("privsep", "failed to get system information");
+ if (getaddrinfo(un.nodename, NULL, &hints, &res) != 0) {
+ log_info("privsep", "unable to get system name");
+#ifdef HAVE_RES_INIT
+ res_init();
+#endif
+ len = strlen(un.nodename);
+ must_write(PRIV_PRIVILEGED, &len, sizeof(int));
+ must_write(PRIV_PRIVILEGED, un.nodename, len);
+ } else {
+ len = strlen(res->ai_canonname);
+ must_write(PRIV_PRIVILEGED, &len, sizeof(int));
+ must_write(PRIV_PRIVILEGED, res->ai_canonname, len);
+ freeaddrinfo(res);
+ }
+}
+
+static void
+asroot_iface_init()
+{
+ int rc = -1, fd = -1;
+ int ifindex;
+ char name[IFNAMSIZ];
+ must_read(PRIV_PRIVILEGED, &ifindex, sizeof(ifindex));
+ must_read(PRIV_PRIVILEGED, &name, sizeof(name));
+ name[sizeof(name) - 1] = '\0';
+
+ TRACE(LLDPD_PRIV_INTERFACE_INIT(name));
+ rc = asroot_iface_init_os(ifindex, name, &fd);
+ must_write(PRIV_PRIVILEGED, &rc, sizeof(rc));
+ if (rc == 0 && fd >= 0) send_fd(PRIV_PRIVILEGED, fd);
+ if (fd >= 0) close(fd);
+}
+
+static void
+asroot_iface_multicast()
+{
+ int sock = -1, add, rc = 0;
+ struct ifreq ifr = { .ifr_name = {} };
+ must_read(PRIV_PRIVILEGED, ifr.ifr_name, IFNAMSIZ);
+#if defined HOST_OS_LINUX
+ must_read(PRIV_PRIVILEGED, ifr.ifr_hwaddr.sa_data, ETHER_ADDR_LEN);
+#elif defined HOST_OS_FREEBSD || defined HOST_OS_OSX || defined HOST_OS_DRAGONFLY
+ /* Black magic from mtest.c */
+ struct sockaddr_dl *dlp = ALIGNED_CAST(struct sockaddr_dl *, &ifr.ifr_addr);
+ dlp->sdl_len = sizeof(struct sockaddr_dl);
+ dlp->sdl_family = AF_LINK;
+ dlp->sdl_index = 0;
+ dlp->sdl_nlen = 0;
+ dlp->sdl_alen = ETHER_ADDR_LEN;
+ dlp->sdl_slen = 0;
+ must_read(PRIV_PRIVILEGED, LLADDR(dlp), ETHER_ADDR_LEN);
+#elif defined HOST_OS_OPENBSD || defined HOST_OS_NETBSD || defined HOST_OS_SOLARIS
+ struct sockaddr *sap = (struct sockaddr *)&ifr.ifr_addr;
+# if !defined HOST_OS_SOLARIS
+ sap->sa_len = sizeof(struct sockaddr);
+# endif
+ sap->sa_family = AF_UNSPEC;
+ must_read(PRIV_PRIVILEGED, sap->sa_data, ETHER_ADDR_LEN);
+#else
+# error Unsupported OS
+#endif
+
+ must_read(PRIV_PRIVILEGED, &add, sizeof(int));
+ if (((sock = socket(AF_INET, SOCK_DGRAM, 0)) == -1) ||
+ ((ioctl(sock, (add) ? SIOCADDMULTI : SIOCDELMULTI, &ifr) < 0) &&
+ (errno != EADDRINUSE)))
+ rc = errno;
+
+ if (sock != -1) close(sock);
+ must_write(PRIV_PRIVILEGED, &rc, sizeof(rc));
+}
+
+static void
+asroot_iface_description()
+{
+ char name[IFNAMSIZ];
+ char *description;
+ int len, rc;
+ must_read(PRIV_PRIVILEGED, &name, sizeof(name));
+ name[sizeof(name) - 1] = '\0';
+ must_read(PRIV_PRIVILEGED, &len, sizeof(int));
+ if (len < 0 || len > PATH_MAX) fatalx("privsep", "too large value requested");
+ if ((description = (char *)malloc(len + 1)) == NULL) fatal("privsep", NULL);
+
+ must_read(PRIV_PRIVILEGED, description, len);
+ description[len] = 0;
+ TRACE(LLDPD_PRIV_INTERFACE_DESCRIPTION(name, description));
+ rc = asroot_iface_description_os(name, description);
+ must_write(PRIV_PRIVILEGED, &rc, sizeof(rc));
+ free(description);
+}
+
+static void
+asroot_iface_promisc()
+{
+ char name[IFNAMSIZ];
+ int rc;
+ must_read(PRIV_PRIVILEGED, &name, sizeof(name));
+ name[sizeof(name) - 1] = '\0';
+ rc = asroot_iface_promisc_os(name);
+ must_write(PRIV_PRIVILEGED, &rc, sizeof(rc));
+}
+
+static void
+asroot_snmp_socket()
+{
+ int sock, rc;
+ static struct sockaddr_un *addr = NULL;
+ struct sockaddr_un bogus;
+
+ if (!addr) {
+ addr = (struct sockaddr_un *)malloc(sizeof(struct sockaddr_un));
+ if (!addr) fatal("privsep", NULL);
+ must_read(PRIV_PRIVILEGED, addr, sizeof(struct sockaddr_un));
+ } else
+ /* We have already been asked to connect to a socket. We will
+ * connect to the same socket. */
+ must_read(PRIV_PRIVILEGED, &bogus, sizeof(struct sockaddr_un));
+ if (addr->sun_family != AF_UNIX)
+ fatal("privsep", "someone is trying to trick me");
+ addr->sun_path[sizeof(addr->sun_path) - 1] = '\0';
+
+ if ((sock = socket(PF_UNIX, SOCK_STREAM, 0)) < 0) {
+ log_warn("privsep", "cannot open socket");
+ must_write(PRIV_PRIVILEGED, &sock, sizeof(int));
+ return;
+ }
+ if ((rc = connect(sock, (struct sockaddr *)addr, sizeof(struct sockaddr_un))) !=
+ 0) {
+ log_info("privsep", "cannot connect to %s: %s", addr->sun_path,
+ strerror(errno));
+ close(sock);
+ rc = -1;
+ must_write(PRIV_PRIVILEGED, &rc, sizeof(int));
+ return;
+ }
+
+ int flags;
+ if ((flags = fcntl(sock, F_GETFL, NULL)) < 0 ||
+ fcntl(sock, F_SETFL, flags | O_NONBLOCK) < 0) {
+ log_warn("privsep", "cannot set sock %s to non-block : %s",
+ addr->sun_path, strerror(errno));
+
+ close(sock);
+ rc = -1;
+ must_write(PRIV_PRIVILEGED, &rc, sizeof(int));
+ return;
+ }
+
+ must_write(PRIV_PRIVILEGED, &rc, sizeof(int));
+ send_fd(PRIV_PRIVILEGED, sock);
+ close(sock);
+}
+
+struct dispatch_actions {
+ enum priv_cmd msg;
+ void (*function)(void);
+};
+
+static struct dispatch_actions actions[] = { { PRIV_PING, asroot_ping },
+ { PRIV_DELETE_CTL_SOCKET, asroot_ctl_cleanup },
+ { PRIV_GET_HOSTNAME, asroot_gethostname },
+#ifdef HOST_OS_LINUX
+ { PRIV_OPEN, asroot_open },
+#endif
+ { PRIV_IFACE_INIT, asroot_iface_init },
+ { PRIV_IFACE_MULTICAST, asroot_iface_multicast },
+ { PRIV_IFACE_DESCRIPTION, asroot_iface_description },
+ { PRIV_IFACE_PROMISC, asroot_iface_promisc },
+ { PRIV_SNMP_SOCKET, asroot_snmp_socket }, { -1, NULL } };
+
+/* Main loop, run as root */
+static void
+priv_loop(int privileged, int once)
+{
+ enum priv_cmd cmd;
+ struct dispatch_actions *a;
+
+#ifdef ENABLE_PRIVSEP
+ setproctitle("monitor.");
+# ifdef USE_SECCOMP
+ if (priv_seccomp_init(privileged, monitored) != 0)
+ fatal("privsep", "cannot continue without seccomp setup");
+# endif
+#endif
+ while (!may_read(PRIV_PRIVILEGED, &cmd, sizeof(enum priv_cmd))) {
+ log_debug("privsep", "received command %d", cmd);
+ for (a = actions; a->function != NULL; a++) {
+ if (cmd == a->msg) {
+ a->function();
+ break;
+ }
+ }
+ if (a->function == NULL) fatalx("privsep", "bogus message received");
+ if (once) break;
+ }
+}
+
+/* This function is a NOOP when privilege separation is enabled. In
+ * the other case, it should be called when we wait an action from the
+ * privileged side. */
+void
+priv_wait()
+{
+#ifndef ENABLE_PRIVSEP
+ /* We have no remote process on the other side. Let's emulate it. */
+ priv_loop(0, 1);
+#endif
+}
+
+#ifdef ENABLE_PRIVSEP
+static void
+priv_exit_rc_status(int rc, int status)
+{
+ switch (rc) {
+ case 0:
+ /* kill child */
+ kill(monitored, SIGTERM);
+ /* we will receive a sigchld in the future */
+ return;
+ case -1:
+ /* child doesn't exist anymore, we consider this is an error to
+ * be here */
+ _exit(1);
+ break;
+ default:
+ /* Monitored child has terminated */
+ /* Mimic the exit state of the child */
+ if (WIFEXITED(status)) {
+ /* Normal exit */
+ _exit(WEXITSTATUS(status));
+ }
+ if (WIFSIGNALED(status)) {
+ /* Terminated with signal */
+ signal(WTERMSIG(status), SIG_DFL);
+ raise(WTERMSIG(status));
+ _exit(1); /* We consider that not being killed is an error. */
+ }
+ /* Other cases, consider this as an error. */
+ _exit(1);
+ break;
+ }
+}
+
+static void
+priv_exit()
+{
+ int status;
+ int rc;
+ rc = waitpid(monitored, &status, WNOHANG);
+ priv_exit_rc_status(rc, status);
+}
+
+/* If priv parent gets a TERM or HUP, pass it through to child instead */
+static void
+sig_pass_to_chld(int sig)
+{
+ int oerrno = errno;
+ if (monitored != -1) kill(monitored, sig);
+ errno = oerrno;
+}
+
+/* If priv parent gets a SIGCHLD, it will exit if this is the monitored
+ * process. Other processes (including lldpcli)) are just reaped without
+ * consequences. */
+static void
+sig_chld(int sig)
+{
+ int status;
+ int rc = waitpid(monitored, &status, WNOHANG);
+ if (rc == 0) {
+ while ((rc = waitpid(-1, &status, WNOHANG)) > 0) {
+ if (rc == monitored) priv_exit_rc_status(rc, status);
+ }
+ return;
+ }
+ priv_exit_rc_status(rc, status);
+}
+
+/* Create a subdirectory and check if it's here. */
+static int
+_mkdir(const char *pathname, mode_t mode)
+{
+ int save_errno;
+ if (mkdir(pathname, mode) == 0 || errno == EEXIST) {
+ errno = 0;
+ return 0;
+ }
+
+ /* We can get EROFS on some platforms. Let's check if the directory exists. */
+ save_errno = errno;
+ if (chdir(pathname) == -1) {
+ errno = save_errno;
+ return -1;
+ }
+
+ /* We should restore current directory, but in the context we are
+ * running, we do not care. */
+ return 0;
+}
+
+/* Create a directory recursively. */
+static int
+mkdir_p(const char *pathname, mode_t mode)
+{
+ char path[PATH_MAX + 1];
+ char *current;
+
+ if (strlcpy(path, pathname, sizeof(path)) >= sizeof(path)) {
+ errno = ENAMETOOLONG;
+ return -1;
+ }
+
+ /* Use strtok which will provides non-empty tokens only. */
+ for (current = path + 1; *current; current++) {
+ if (*current != '/') continue;
+ *current = '\0';
+ if (_mkdir(path, mode) != 0) return -1;
+ *current = '/';
+ }
+ if (_mkdir(path, mode) != 0) return -1;
+
+ return 0;
+}
+
+/* Initialization */
+# define LOCALTIME "/etc/localtime"
+static void
+priv_setup_chroot(const char *chrootdir)
+{
+ /* Create chroot if it does not exist */
+ if (mkdir_p(chrootdir, 0755) == -1) {
+ fatal("privsep", "unable to create chroot directory");
+ }
+
+ /* Check if /etc/localtime exists in chroot or outside chroot */
+ char path[1024];
+ int source = -1, destination = -1;
+ if (snprintf(path, sizeof(path), "%s" LOCALTIME, chrootdir) >= sizeof(path))
+ return;
+ if ((source = open(LOCALTIME, O_RDONLY)) == -1) {
+ if (errno == ENOENT) return;
+ log_warn("privsep", "cannot read " LOCALTIME);
+ return;
+ }
+
+ /* Prepare copy of /etc/localtime */
+ path[strlen(chrootdir) + 4] = '\0';
+ if (mkdir(path, 0755) == -1) {
+ if (errno != EEXIST) {
+ log_warn("privsep", "unable to create %s directory", path);
+ close(source);
+ return;
+ }
+ }
+ path[strlen(chrootdir) + 4] = '/';
+
+ /* Do copy */
+ char buffer[1024];
+ ssize_t n;
+ mode_t old = umask(S_IWGRP | S_IWOTH);
+ if ((destination = open(path, O_WRONLY | O_CREAT | O_TRUNC | O_EXCL, 0666)) ==
+ -1) {
+ if (errno != EEXIST) log_warn("privsep", "cannot create %s", path);
+ close(source);
+ umask(old);
+ return;
+ }
+ umask(old);
+ while ((n = read(source, buffer, sizeof(buffer))) > 0) {
+ ssize_t nw, left = n;
+ char *p = buffer;
+ while (left > 0) {
+ if ((nw = write(destination, p, left)) == -1) {
+ if (errno == EINTR) continue;
+ log_warn("privsep", "cannot write to %s", path);
+ close(source);
+ close(destination);
+ unlink(path);
+ return;
+ }
+ left -= nw;
+ p += nw;
+ }
+ }
+ if (n == -1) {
+ log_warn("privsep", "cannot read " LOCALTIME);
+ unlink(path);
+ } else {
+ log_info("privsep", LOCALTIME " copied to chroot");
+ }
+ close(source);
+ close(destination);
+}
+#else /* !ENABLE_PRIVSEP */
+
+/* Reap any children. It should only be lldpcli since there is not monitored
+ * process. */
+static void
+sig_chld(int sig)
+{
+ int status = 0;
+ while (waitpid(-1, &status, WNOHANG) > 0)
+ ;
+}
+
+#endif
+
+#ifdef ENABLE_PRIVSEP
+static void
+priv_drop(uid_t uid, gid_t gid)
+{
+ gid_t gidset[1];
+ gidset[0] = gid;
+ log_debug("privsep", "dropping privileges");
+# ifdef HAVE_SETRESGID
+ if (setresgid(gid, gid, gid) == -1) fatal("privsep", "setresgid() failed");
+# else
+ if (setregid(gid, gid) == -1) fatal("privsep", "setregid() failed");
+# endif
+ if (setgroups(1, gidset) == -1) fatal("privsep", "setgroups() failed");
+# ifdef HAVE_SETRESUID
+ if (setresuid(uid, uid, uid) == -1) fatal("privsep", "setresuid() failed");
+# else
+ if (setreuid(uid, uid) == -1) fatal("privsep", "setreuid() failed");
+# endif
+}
+
+static void
+priv_caps(uid_t uid, gid_t gid)
+{
+# ifdef HAVE_LINUX_CAPABILITIES
+ cap_t caps;
+ const char *caps_strings[2] = {
+ "cap_dac_override,cap_net_raw,cap_net_admin,cap_setuid,cap_setgid=pe",
+ "cap_dac_override,cap_net_raw,cap_net_admin=pe"
+ };
+ log_debug("privsep",
+ "getting CAP_NET_RAW/ADMIN and CAP_DAC_OVERRIDE privilege");
+ if (!(caps = cap_from_text(caps_strings[0])))
+ fatal("privsep", "unable to convert caps");
+ if (cap_set_proc(caps) == -1) {
+ log_warn("privsep",
+ "unable to drop privileges, monitor running as root");
+ cap_free(caps);
+ return;
+ }
+ cap_free(caps);
+
+ if (prctl(PR_SET_KEEPCAPS, 1L, 0L, 0L, 0L) == -1)
+ fatal("privsep", "cannot keep capabilities");
+ priv_drop(uid, gid);
+
+ log_debug("privsep", "dropping extra capabilities");
+ if (!(caps = cap_from_text(caps_strings[1])))
+ fatal("privsep", "unable to convert caps");
+ if (cap_set_proc(caps) == -1)
+ fatal("privsep", "unable to drop extra privileges");
+ cap_free(caps);
+# else
+ log_info("privsep", "no libcap support, running monitor as root");
+# endif
+}
+#endif
+
+void
+#ifdef ENABLE_PRIVSEP
+priv_init(const char *chrootdir, int ctl, uid_t uid, gid_t gid)
+#else
+priv_init(void)
+#endif
+{
+
+ int pair[2];
+
+ /* Create socket pair */
+ if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, pair) < 0) {
+ fatal("privsep",
+ "unable to create socket pair for privilege separation");
+ }
+
+ priv_unprivileged_fd(pair[0]);
+ priv_privileged_fd(pair[1]);
+
+#ifdef ENABLE_PRIVSEP
+ /* Spawn off monitor */
+ if ((monitored = fork()) < 0) fatal("privsep", "unable to fork monitor");
+ switch (monitored) {
+ case 0:
+ /* We are in the children, drop privileges */
+ if (RUNNING_ON_VALGRIND)
+ log_warnx("privsep", "running on valgrind, keep privileges");
+ else {
+ priv_setup_chroot(chrootdir);
+ if (chroot(chrootdir) == -1)
+ fatal("privsep", "unable to chroot");
+ if (chdir("/") != 0) fatal("privsep", "unable to chdir");
+ priv_drop(uid, gid);
+ }
+ close(pair[1]);
+ priv_ping();
+ break;
+ default:
+ /* We are in the monitor */
+ if (ctl != -1) close(ctl);
+ close(pair[0]);
+ if (atexit(priv_exit) != 0)
+ fatal("privsep", "unable to set exit function");
+
+ priv_caps(uid, gid);
+
+ /* Install signal handlers */
+ const struct sigaction pass_to_child = { .sa_handler = sig_pass_to_chld,
+ .sa_flags = SA_RESTART };
+ sigaction(SIGALRM, &pass_to_child, NULL);
+ sigaction(SIGTERM, &pass_to_child, NULL);
+ sigaction(SIGHUP, &pass_to_child, NULL);
+ sigaction(SIGINT, &pass_to_child, NULL);
+ sigaction(SIGQUIT, &pass_to_child, NULL);
+ const struct sigaction child = { .sa_handler = sig_chld,
+ .sa_flags = SA_RESTART };
+ sigaction(SIGCHLD, &child, NULL);
+ sig_chld(0); /* Reap already dead children */
+ priv_loop(pair[1], 0);
+ exit(0);
+ }
+#else
+ const struct sigaction child = { .sa_handler = sig_chld,
+ .sa_flags = SA_RESTART };
+ sigaction(SIGCHLD, &child, NULL);
+ sig_chld(0); /* Reap already dead children */
+ log_warnx("priv", "no privilege separation available");
+ priv_ping();
+#endif
+}
diff --git a/src/daemon/privsep.c b/src/daemon/privsep.c
new file mode 100644
index 0000000..04c49a9
--- /dev/null
+++ b/src/daemon/privsep.c
@@ -0,0 +1,26 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+
+#include "lldpd.h"
+
+static int privileged, unprivileged;
+void
+priv_privileged_fd(int fd)
+{
+ privileged = fd;
+}
+void
+priv_unprivileged_fd(int fd)
+{
+ unprivileged = fd;
+}
+int
+priv_fd(enum priv_context ctx)
+{
+ switch (ctx) {
+ case PRIV_PRIVILEGED:
+ return privileged;
+ case PRIV_UNPRIVILEGED:
+ return unprivileged;
+ }
+ return -1; /* Not possible */
+}
diff --git a/src/daemon/privsep_fd.c b/src/daemon/privsep_fd.c
new file mode 100644
index 0000000..fa98663
--- /dev/null
+++ b/src/daemon/privsep_fd.c
@@ -0,0 +1,129 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+
+#include "lldpd.h"
+
+#include <sys/param.h>
+#include <sys/uio.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+/*
+ * Copyright 2001 Niels Provos <provos@citi.umich.edu>
+ * All rights reserved.
+ *
+ * Copyright (c) 2002 Matthieu Herrb
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * - 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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
+ * COPYRIGHT HOLDERS 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.
+ */
+
+void
+send_fd(enum priv_context ctx, int fd)
+{
+ struct msghdr msg;
+ union {
+ struct cmsghdr hdr;
+ char buf[CMSG_SPACE(sizeof(int))];
+ } cmsgbuf;
+ struct cmsghdr *cmsg;
+ struct iovec vec;
+ int result = 0;
+ ssize_t n;
+
+ memset(&msg, 0, sizeof(msg));
+ memset(&cmsgbuf.buf, 0, sizeof(cmsgbuf.buf));
+
+ if (fd >= 0) {
+ msg.msg_control = (caddr_t)&cmsgbuf.buf;
+ msg.msg_controllen = sizeof(cmsgbuf.buf);
+ cmsg = CMSG_FIRSTHDR(&msg);
+ cmsg->cmsg_len = CMSG_LEN(sizeof(int));
+ cmsg->cmsg_level = SOL_SOCKET;
+ cmsg->cmsg_type = SCM_RIGHTS;
+ memcpy(CMSG_DATA(cmsg), &fd, sizeof(int));
+ } else {
+ result = errno;
+ }
+
+ vec.iov_base = &result;
+ vec.iov_len = sizeof(int);
+ msg.msg_iov = &vec;
+ msg.msg_iovlen = 1;
+
+ if ((n = sendmsg(priv_fd(ctx), &msg, 0)) == -1)
+ log_warn("privsep", "sendmsg(%d)", priv_fd(ctx));
+ if (n != sizeof(int))
+ log_warnx("privsep", "sendmsg: expected sent 1 got %ld", (long)n);
+}
+
+int
+receive_fd(enum priv_context ctx)
+{
+ struct msghdr msg;
+ union {
+ struct cmsghdr hdr;
+ char buf[CMSG_SPACE(sizeof(int))];
+ } cmsgbuf;
+ struct cmsghdr *cmsg;
+ struct iovec vec;
+ ssize_t n;
+ int result;
+ int fd;
+
+ memset(&msg, 0, sizeof(msg));
+ vec.iov_base = &result;
+ vec.iov_len = sizeof(int);
+ msg.msg_iov = &vec;
+ msg.msg_iovlen = 1;
+ msg.msg_control = &cmsgbuf.buf;
+ msg.msg_controllen = sizeof(cmsgbuf.buf);
+
+ if ((n = recvmsg(priv_fd(ctx), &msg, 0)) == -1) log_warn("privsep", "recvmsg");
+ if (n != sizeof(int))
+ log_warnx("privsep", "recvmsg: expected received 1 got %ld", (long)n);
+ if (result == 0) {
+ cmsg = CMSG_FIRSTHDR(&msg);
+ if (cmsg == NULL) {
+ log_warnx("privsep", "no message header");
+ return -1;
+ }
+ if (cmsg->cmsg_type != SCM_RIGHTS)
+ log_warnx("privsep", "expected type %d got %d", SCM_RIGHTS,
+ cmsg->cmsg_type);
+ memcpy(&fd, CMSG_DATA(cmsg), sizeof(int));
+ return fd;
+ } else {
+ errno = result;
+ return -1;
+ }
+}
diff --git a/src/daemon/privsep_io.c b/src/daemon/privsep_io.c
new file mode 100644
index 0000000..9d0923a
--- /dev/null
+++ b/src/daemon/privsep_io.c
@@ -0,0 +1,100 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+
+#include "lldpd.h"
+
+#include <sys/param.h>
+#include <sys/uio.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+/* Stolen from sbin/pflogd/privsep.c from OpenBSD */
+/*
+ * Copyright (c) 2003 Can Erkin Acar
+ * Copyright (c) 2003 Anil Madhavapeddy <anil@recoil.org>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/* Read all data or return 1 for error. */
+int
+may_read(enum priv_context ctx, void *buf, size_t n)
+{
+ char *s = buf;
+ ssize_t res, pos = 0;
+
+ while (n > pos) {
+ res = read(priv_fd(ctx), s + pos, n - pos);
+ switch (res) {
+ case -1:
+ if (errno == EINTR || errno == EAGAIN) continue;
+ return 1;
+ case 0:
+ return 1;
+ default:
+ pos += res;
+ }
+ }
+ return (0);
+}
+
+/* Read data with the assertion that it all must come through, or
+ * else abort the process. Based on atomicio() from openssh. */
+void
+must_read(enum priv_context ctx, void *buf, size_t n)
+{
+ char *s = buf;
+ ssize_t res, pos = 0;
+
+ while (n > pos) {
+ res = read(priv_fd(ctx), s + pos, n - pos);
+ switch (res) {
+ case -1:
+ if (errno == EINTR || errno == EAGAIN) continue;
+ _exit(0);
+ case 0:
+ _exit(0);
+ default:
+ pos += res;
+ }
+ }
+}
+
+/* Write data with the assertion that it all has to be written, or
+ * else abort the process. Based on atomicio() from openssh. */
+void
+must_write(enum priv_context ctx, const void *buf, size_t n)
+{
+ const char *s = buf;
+ ssize_t res, pos = 0;
+
+ while (n > pos) {
+ res = write(priv_fd(ctx), s + pos, n - pos);
+ switch (res) {
+ case -1:
+ if (errno == EINTR || errno == EAGAIN) continue;
+ _exit(0);
+ case 0:
+ _exit(0);
+ default:
+ pos += res;
+ }
+ }
+}
diff --git a/src/daemon/probes.d b/src/daemon/probes.d
new file mode 100644
index 0000000..a4d7785
--- /dev/null
+++ b/src/daemon/probes.d
@@ -0,0 +1,115 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/*
+ * Copyright (c) 2013 Vincent Bernat <bernat@luffy.cx>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+provider lldpd {
+
+ /**
+ * Fired when a frame is received, before it is decoded.
+ * @param ifname the name of the interface
+ * @param frame the received frame
+ * @param len the len of the received frame
+ */
+ probe frame_received(char *ifname, void *frame, size_t len);
+
+ /**
+ * Fired when a frame is decoded.
+ * @param ifname the name of the interface
+ * @param protocol the name of the protocol
+ * @param chassis_name the name of chassis (may be NULL)
+ * @param port_descr the description of the port (may be NULL)
+ */
+ probe frame_decoded(char *ifname, char *protocol, char *chassis_name, char *port_descr);
+
+ /**
+ * Fired when a frame is sent.
+ * @param ifname the name of the interface
+ * @param protocol the name of the protocol
+ */
+ probe frame_send(char *ifname, char *protocol);
+
+ /**
+ * Fired when a neighbor is added.
+ * @param ifname the name of the interface where the neighbor appeared
+ * @param chassis_name the name of chassis (may be NULL)
+ * @param port_descr the description of the port (may be NULL)
+ * @param count the total number of neighbors known
+ */
+ probe neighbor_new(char *ifname, char *chassis_name, char *port_descr, int count);
+
+ /**
+ * Fired when a neighbor is updated.
+ * @param ifname the name of the interface where the neighbor updated
+ * @param chassis_name the name of chassis (may be NULL)
+ * @param port_descr the description of the port (may be NULL)
+ * @param count the total number of neighbors known
+ */
+ probe neighbor_update(char *ifname, char *chassis_name, char *port_descr, int count);
+
+ /**
+ * Fired when a neighbor is deleted.
+ * @param ifname the name of the interface where the neighbor deleted
+ * @param chassis_name the name of chassis (may be NULL)
+ * @param port_descr the description of the port (may be NULL)
+ * @param count the total number of neighbors known
+ */
+ probe neighbor_delete(char *ifname, char *chassis_name, char *port_descr);
+
+ /**
+ * Fired before handling a client request.
+ * @param name the name of the request
+ */
+ probe client_request(char *name);
+
+ /**
+ * Fired for each iteration of the event loop.
+ */
+ probe event_loop();
+
+ /**
+ * Fired when initializing a new interface in privileged mode.
+ * @param name the name of the interface
+ */
+ probe priv_interface_init(char *name);
+
+ /**
+ * Fired when setting description of an interface.
+ * @param name the name of the interface
+ * @param desc the description of the interface
+ */
+ probe priv_interface_description(char *name, char *description);
+
+ /**
+ * Fired when doing an interface updates.
+ */
+ probe interfaces_update();
+
+ /**
+ * Fired when receiving an interface update notification.
+ */
+ probe interfaces_notification();
+
+ /**
+ * Fired when an interface is removed.
+ * @param name the name of the interface
+ */
+ probe interfaces_delete(char *name);
+
+ /**
+ * Fired when an interface is added.
+ * @param name the name of the interface
+ */
+ probe interfaces_new(char *name);
+};
diff --git a/src/daemon/protocols/cdp.c b/src/daemon/protocols/cdp.c
new file mode 100644
index 0000000..620455b
--- /dev/null
+++ b/src/daemon/protocols/cdp.c
@@ -0,0 +1,711 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/*
+ * Copyright (c) 2008 Vincent Bernat <bernat@luffy.cx>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/* We also supports FDP which is very similar to CDPv1 */
+#include "../lldpd.h"
+#include "../frame.h"
+
+/*
+ * CDP Requests Power at the switch output and therefore has to take into
+ * account the loss in the PoE cable. This is done by the switch automatically
+ * if lldp is used as the protocol.
+ */
+#define CDP_CLASS_3_MAX_PSE_POE 154 /* 15.4W Max PoE at PSE class 3 */
+#define CDP_SWTICH_DEFAULT_POE_PD 130 /* 13.W default PoE at PD */
+#define CDP_SWTICH_DEFAULT_POE_PSE 154 /* 15.4W default PoE at PSE */
+#define CDP_SWITCH_POE_CLASS_4_OFFSET 45 /* 4.5W max loss from cable */
+#define CDP_SWITCH_POE_CLASS_3_OFFSET 24 /* 2.4W max loss from cable */
+
+#if defined ENABLE_CDP || defined ENABLE_FDP
+
+# include <stdio.h>
+# include <unistd.h>
+# include <errno.h>
+# include <arpa/inet.h>
+
+static int
+cdp_send(struct lldpd *global, struct lldpd_hardware *hardware, int version)
+{
+ const char *platform = "Unknown";
+ struct lldpd_chassis *chassis;
+ struct lldpd_mgmt *mgmt;
+ struct lldpd_port *port;
+ u_int8_t mcastaddr[] = CDP_MULTICAST_ADDR;
+ u_int8_t llcorg[] = LLC_ORG_CISCO;
+# ifdef ENABLE_FDP
+ const char *capstr;
+# endif
+ u_int16_t checksum;
+ int length, i;
+ u_int32_t cap;
+ u_int8_t *packet;
+ u_int8_t *pos, *pos_len_eh, *pos_llc, *pos_cdp, *pos_checksum, *tlv, *end;
+
+ log_debug("cdp", "send CDP frame on %s", hardware->h_ifname);
+
+ port = &(hardware->h_lport);
+ chassis = port->p_chassis;
+
+# ifdef ENABLE_FDP
+ if (version == 0) {
+ /* With FDP, change multicast address and LLC PID */
+ const u_int8_t fdpmcastaddr[] = FDP_MULTICAST_ADDR;
+ const u_int8_t fdpllcorg[] = LLC_ORG_FOUNDRY;
+ memcpy(mcastaddr, fdpmcastaddr, sizeof(mcastaddr));
+ memcpy(llcorg, fdpllcorg, sizeof(llcorg));
+ }
+# endif
+
+ length = hardware->h_mtu;
+ if ((packet = (u_int8_t *)calloc(1, length)) == NULL) return ENOMEM;
+ pos = packet;
+
+ /* Ethernet header */
+ if (!(POKE_BYTES(mcastaddr, sizeof(mcastaddr)) &&
+ POKE_BYTES(&hardware->h_lladdr, ETHER_ADDR_LEN) &&
+ POKE_SAVE(pos_len_eh) && /* We compute the len later */
+ POKE_UINT16(0)))
+ goto toobig;
+
+ /* LLC */
+ if (!(POKE_SAVE(pos_llc) && POKE_UINT8(0xaa) && /* SSAP */
+ POKE_UINT8(0xaa) && /* DSAP */
+ POKE_UINT8(0x03) && /* Control field */
+ POKE_BYTES(llcorg, sizeof(llcorg)) && POKE_UINT16(LLC_PID_CDP)))
+ goto toobig;
+
+ /* CDP header */
+ if (!(POKE_SAVE(pos_cdp) && POKE_UINT8((version == 0) ? 1 : version) &&
+ POKE_UINT8(global ? global->g_config.c_ttl : 180) &&
+ POKE_SAVE(pos_checksum) && /* Save checksum position */
+ POKE_UINT16(0)))
+ goto toobig;
+
+ /* Chassis ID */
+ const char *chassis_name = chassis->c_name ? chassis->c_name : "";
+ if (!(POKE_START_CDP_TLV(CDP_TLV_CHASSIS) &&
+ POKE_BYTES(chassis_name, strlen(chassis_name)) && POKE_END_CDP_TLV))
+ goto toobig;
+
+ /* Adresses */
+ /* See:
+ * http://www.cisco.com/univercd/cc/td/doc/product/lan/trsrb/frames.htm#xtocid12
+ *
+ * It seems that Cisco implies that CDP supports IPv6 using
+ * 802.2 address format with 0xAAAA03 0x000000 0x0800, but
+ * 0x0800 is the Ethernet protocol type for IPv4. Therefore,
+ * we support only IPv4. */
+ i = 0;
+ TAILQ_FOREACH (mgmt, &chassis->c_mgmt, m_entries)
+ if (mgmt->m_family == LLDPD_AF_IPV4) i++;
+ if (i > 0) {
+ if (!(POKE_START_CDP_TLV(CDP_TLV_ADDRESSES) && POKE_UINT32(i)))
+ goto toobig;
+ TAILQ_FOREACH (mgmt, &chassis->c_mgmt, m_entries) {
+ switch (mgmt->m_family) {
+ case LLDPD_AF_IPV4:
+ if (!(POKE_UINT8(1) && /* Type: NLPID */
+ POKE_UINT8(1) && /* Length: 1 */
+ POKE_UINT8(CDP_ADDRESS_PROTO_IP) && /* IP */
+ POKE_UINT16(sizeof(
+ struct in_addr)) && /* Address length */
+ POKE_BYTES(&mgmt->m_addr,
+ sizeof(struct in_addr))))
+ goto toobig;
+ break;
+ }
+ }
+ if (!(POKE_END_CDP_TLV)) goto toobig;
+ }
+
+ /* Port ID */
+ const char *port_descr =
+ hardware->h_lport.p_descr ? hardware->h_lport.p_descr : "";
+ if (!(POKE_START_CDP_TLV(CDP_TLV_PORT) &&
+ POKE_BYTES(port_descr, strlen(port_descr)) && POKE_END_CDP_TLV))
+ goto toobig;
+
+ /* Capabilities */
+ if (version != 0) {
+ cap = 0;
+ if (chassis->c_cap_enabled & LLDP_CAP_ROUTER) cap |= CDP_CAP_ROUTER;
+ if (chassis->c_cap_enabled & LLDP_CAP_BRIDGE) cap |= CDP_CAP_SWITCH;
+ cap |= CDP_CAP_HOST;
+ if (!(POKE_START_CDP_TLV(CDP_TLV_CAPABILITIES) && POKE_UINT32(cap) &&
+ POKE_END_CDP_TLV))
+ goto toobig;
+# ifdef ENABLE_FDP
+ } else {
+ /* With FDP, it seems that a string is used in place of an int */
+ if (chassis->c_cap_enabled & LLDP_CAP_ROUTER)
+ capstr = "Router";
+ else if (chassis->c_cap_enabled & LLDP_CAP_BRIDGE)
+ capstr = "Switch";
+ else if (chassis->c_cap_enabled & LLDP_CAP_REPEATER)
+ capstr = "Bridge";
+ else
+ capstr = "Host";
+ if (!(POKE_START_CDP_TLV(CDP_TLV_CAPABILITIES) &&
+ POKE_BYTES(capstr, strlen(capstr)) && POKE_END_CDP_TLV))
+ goto toobig;
+# endif
+ }
+
+ /* Native VLAN */
+# ifdef ENABLE_DOT1
+ if (version >= 2 && hardware->h_lport.p_pvid != 0) {
+ if (!(POKE_START_CDP_TLV(CDP_TLV_NATIVEVLAN) &&
+ POKE_UINT16(hardware->h_lport.p_pvid) && POKE_END_CDP_TLV))
+ goto toobig;
+ }
+# endif
+
+ /* Software version */
+ const char *chassis_descr = chassis->c_descr ? chassis->c_descr : "";
+ if (!(POKE_START_CDP_TLV(CDP_TLV_SOFTWARE) &&
+ POKE_BYTES(chassis_descr, strlen(chassis_descr)) && POKE_END_CDP_TLV))
+ goto toobig;
+
+ /* Platform */
+ if (global && global->g_config.c_platform)
+ platform = global->g_config.c_platform;
+
+ if (!(POKE_START_CDP_TLV(CDP_TLV_PLATFORM) &&
+ POKE_BYTES(platform, strlen(platform)) && POKE_END_CDP_TLV))
+ goto toobig;
+
+# ifdef ENABLE_DOT3
+ if ((version >= 2) && (port->p_power.powertype != LLDP_DOT3_POWER_8023AT_OFF) &&
+ (port->p_power.devicetype == LLDP_DOT3_POWER_PD) &&
+ (port->p_power.requested > 0) && (port->p_power.requested <= 655)) {
+ u_int16_t requested;
+ u_int16_t consumption;
+
+ if (port->p_power.requested != port->p_power.allocated) {
+ port->p_cdp_power.request_id++;
+ log_debug("cdp", "requested: %d, allocated:%d",
+ port->p_power.requested, port->p_power.allocated);
+ }
+ consumption = port->p_power.allocated ? port->p_power.allocated :
+ CDP_SWTICH_DEFAULT_POE_PD;
+ if (consumption > 130) {
+ consumption += CDP_SWITCH_POE_CLASS_4_OFFSET;
+ } else {
+ consumption += CDP_SWITCH_POE_CLASS_3_OFFSET;
+ }
+ if (port->p_power.requested > 130) { /* Class 4 */
+ requested =
+ port->p_power.requested + CDP_SWITCH_POE_CLASS_4_OFFSET;
+ } else { /* Class 3 */
+ requested =
+ port->p_power.requested + CDP_SWITCH_POE_CLASS_3_OFFSET;
+ }
+ if (!(POKE_START_CDP_TLV(CDP_TLV_POWER_CONSUMPTION) &&
+ POKE_UINT16(consumption * 100) && POKE_END_CDP_TLV))
+ goto toobig;
+ /* Avoid request id 0 from overflow */
+ if (!port->p_cdp_power.request_id) {
+ port->p_cdp_power.request_id = 1;
+ }
+ if (!port->p_cdp_power.management_id) {
+ port->p_cdp_power.management_id = 1;
+ }
+ if (!(POKE_START_CDP_TLV(CDP_TLV_POWER_REQUESTED) &&
+ POKE_UINT16(port->p_cdp_power.request_id) &&
+ POKE_UINT16(port->p_cdp_power.management_id) &&
+ POKE_UINT32(requested * 100) && POKE_END_CDP_TLV))
+ goto toobig;
+ }
+# elif defined ENABLE_LLDPMED
+ /* Power use */
+ if ((version >= 2) && port->p_med_cap_enabled &&
+ (port->p_med_power.source != LLDP_MED_POW_SOURCE_LOCAL) &&
+ (port->p_med_power.val > 0) && (port->p_med_power.val <= 655)) {
+ if (!(POKE_START_CDP_TLV(CDP_TLV_POWER_CONSUMPTION) &&
+ POKE_UINT16(port->p_med_power.val * 100) && POKE_END_CDP_TLV))
+ goto toobig;
+ }
+# endif
+
+ (void)POKE_SAVE(end);
+
+ /* Compute len and checksum */
+ POKE_RESTORE(pos_len_eh);
+ if (!(POKE_UINT16(end - pos_llc))) goto toobig;
+ checksum = frame_checksum(pos_cdp, end - pos_cdp, (version != 0) ? 1 : 0);
+ POKE_RESTORE(pos_checksum);
+ if (!(POKE_UINT16(checksum))) goto toobig;
+
+ if (interfaces_send_helper(global, hardware, (char *)packet, end - packet) ==
+ -1) {
+ log_warn("cdp", "unable to send packet on real device for %s",
+ hardware->h_ifname);
+ free(packet);
+ return ENETDOWN;
+ }
+
+ hardware->h_tx_cnt++;
+
+ free(packet);
+ return 0;
+toobig:
+ free(packet);
+ return -1;
+}
+
+# define CHECK_TLV_SIZE(x, name) \
+ do { \
+ if (tlv_len < (x)) { \
+ log_warnx("cdp", name " CDP/FDP TLV too short received on %s", \
+ hardware->h_ifname); \
+ goto malformed; \
+ } \
+ } while (0)
+/* cdp_decode also decodes FDP */
+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 lldpd_mgmt *mgmt;
+ struct in_addr addr;
+# if 0
+ u_int16_t cksum;
+# endif
+ u_int8_t *software = NULL, *platform = NULL;
+ int software_len = 0, platform_len = 0, proto, version, nb, caps;
+ const unsigned char cdpaddr[] = CDP_MULTICAST_ADDR;
+# ifdef ENABLE_FDP
+ const unsigned char fdpaddr[] = CDP_MULTICAST_ADDR;
+ int fdp = 0;
+# endif
+ u_int8_t *pos, *tlv, *pos_address, *pos_next_address;
+ int length, len_eth, tlv_type, tlv_len, addresses_len, address_len;
+# ifdef ENABLE_DOT1
+ struct lldpd_vlan *vlan;
+# endif
+
+ log_debug("cdp", "decode CDP frame received on %s", hardware->h_ifname);
+
+ if ((chassis = calloc(1, sizeof(struct lldpd_chassis))) == NULL) {
+ log_warn("cdp", "failed to allocate remote chassis");
+ return -1;
+ }
+ TAILQ_INIT(&chassis->c_mgmt);
+ if ((port = calloc(1, sizeof(struct lldpd_port))) == NULL) {
+ log_warn("cdp", "failed to allocate remote port");
+ free(chassis);
+ return -1;
+ }
+# ifdef ENABLE_DOT1
+ TAILQ_INIT(&port->p_vlans);
+# endif
+
+ length = s;
+ pos = (u_int8_t *)frame;
+
+ if (length < 2 * ETHER_ADDR_LEN + sizeof(u_int16_t) /* Ethernet */ +
+ 8 /* LLC */ + 4 /* CDP header */) {
+ log_warn("cdp", "too short CDP/FDP frame received on %s",
+ hardware->h_ifname);
+ goto malformed;
+ }
+
+ if (PEEK_CMP(cdpaddr, sizeof(cdpaddr)) != 0) {
+# ifdef ENABLE_FDP
+ PEEK_RESTORE((u_int8_t *)frame);
+ if (PEEK_CMP(fdpaddr, sizeof(fdpaddr)) != 0)
+ fdp = 1;
+ else {
+# endif
+ log_info("cdp",
+ "frame not targeted at CDP/FDP multicast address received on %s",
+ hardware->h_ifname);
+ goto malformed;
+# ifdef ENABLE_FDP
+ }
+# endif
+ }
+ PEEK_DISCARD(ETHER_ADDR_LEN); /* Don't care of source address */
+ len_eth = PEEK_UINT16;
+ if (len_eth > length) {
+ log_warnx("cdp", "incorrect 802.3 frame size reported on %s",
+ hardware->h_ifname);
+ goto malformed;
+ }
+
+ /* This is the correct length of the CDP + LLC packets */
+ length = len_eth;
+
+ PEEK_DISCARD(6); /* Skip beginning of LLC */
+ proto = PEEK_UINT16;
+ if (proto != LLC_PID_CDP) {
+ if ((proto != LLC_PID_DRIP) && (proto != LLC_PID_PAGP) &&
+ (proto != LLC_PID_PVSTP) && (proto != LLC_PID_UDLD) &&
+ (proto != LLC_PID_VTP) && (proto != LLC_PID_DTP) &&
+ (proto != LLC_PID_STP))
+ log_debug("cdp", "incorrect LLC protocol ID received on %s",
+ hardware->h_ifname);
+ goto malformed;
+ }
+
+# if 0
+ /* Check checksum */
+ cksum = frame_checksum(pos, len_eth - 8,
+# ifdef ENABLE_FDP
+ !fdp /* fdp = 0 -> cisco checksum */
+# else
+ 1 /* cisco checksum */
+# endif
+ );
+ if (cksum != 0) {
+ log_info("cdp", "incorrect CDP/FDP checksum for frame received on %s (%d)",
+ hardware->h_ifname, cksum);
+ goto malformed;
+ }
+# endif
+
+ /* Check version */
+ version = PEEK_UINT8;
+ if ((version != 1) && (version != 2)) {
+ log_warnx("cdp",
+ "incorrect CDP/FDP version (%d) for frame received on %s", version,
+ hardware->h_ifname);
+ goto malformed;
+ }
+ port->p_ttl = PEEK_UINT8; /* TTL */
+ PEEK_DISCARD_UINT16; /* Checksum, already checked */
+
+ while (length) {
+ if (length < 4) {
+ log_warnx("cdp",
+ "CDP/FDP TLV header is too large for "
+ "frame received on %s",
+ hardware->h_ifname);
+ goto malformed;
+ }
+ tlv_type = PEEK_UINT16;
+ tlv_len = PEEK_UINT16 - 4;
+
+ (void)PEEK_SAVE(tlv);
+ if ((tlv_len < 0) || (length < tlv_len)) {
+ log_warnx("cdp",
+ "incorrect size in CDP/FDP TLV header for frame "
+ "received on %s",
+ hardware->h_ifname);
+ goto malformed;
+ }
+ switch (tlv_type) {
+ case CDP_TLV_CHASSIS:
+ free(chassis->c_name);
+ if ((chassis->c_name = (char *)calloc(1, tlv_len + 1)) ==
+ NULL) {
+ log_warn("cdp",
+ "unable to allocate memory for chassis name");
+ goto malformed;
+ }
+ PEEK_BYTES(chassis->c_name, tlv_len);
+ chassis->c_id_subtype = LLDP_CHASSISID_SUBTYPE_LOCAL;
+ free(chassis->c_id);
+ if ((chassis->c_id = (char *)malloc(tlv_len)) == NULL) {
+ log_warn("cdp",
+ "unable to allocate memory for chassis ID");
+ goto malformed;
+ }
+ memcpy(chassis->c_id, chassis->c_name, tlv_len);
+ chassis->c_id_len = tlv_len;
+ break;
+ case CDP_TLV_ADDRESSES:
+ CHECK_TLV_SIZE(4, "Address");
+ addresses_len = tlv_len - 4;
+ for (nb = PEEK_UINT32; nb > 0; nb--) {
+ (void)PEEK_SAVE(pos_address);
+ /* We first try to get the real length of the packet */
+ if (addresses_len < 2) {
+ log_warn("cdp",
+ "too short address subframe "
+ "received on %s",
+ hardware->h_ifname);
+ goto malformed;
+ }
+ PEEK_DISCARD_UINT8;
+ addresses_len--;
+ address_len = PEEK_UINT8;
+ addresses_len--;
+ if (addresses_len < address_len + 2) {
+ log_warn("cdp",
+ "too short address subframe "
+ "received on %s",
+ hardware->h_ifname);
+ goto malformed;
+ }
+ PEEK_DISCARD(address_len);
+ addresses_len -= address_len;
+ address_len = PEEK_UINT16;
+ addresses_len -= 2;
+ if (addresses_len < address_len) {
+ log_warn("cdp",
+ "too short address subframe "
+ "received on %s",
+ hardware->h_ifname);
+ goto malformed;
+ }
+ PEEK_DISCARD(address_len);
+ addresses_len -= address_len;
+ (void)PEEK_SAVE(pos_next_address);
+ /* Next, we go back and try to extract
+ IPv4 address */
+ PEEK_RESTORE(pos_address);
+ if ((PEEK_UINT8 == 1) && (PEEK_UINT8 == 1) &&
+ (PEEK_UINT8 == CDP_ADDRESS_PROTO_IP) &&
+ (PEEK_UINT16 == sizeof(struct in_addr))) {
+ PEEK_BYTES(&addr, sizeof(struct in_addr));
+ mgmt = lldpd_alloc_mgmt(LLDPD_AF_IPV4, &addr,
+ sizeof(struct in_addr), 0);
+ if (mgmt == NULL) {
+ if (errno == ENOMEM)
+ log_warn("cdp",
+ "unable to allocate memory for management address");
+ else
+ log_warn("cdp",
+ "too large management address received on %s",
+ hardware->h_ifname);
+ goto malformed;
+ }
+ TAILQ_INSERT_TAIL(&chassis->c_mgmt, mgmt,
+ m_entries);
+ }
+ /* Go to the end of the address */
+ PEEK_RESTORE(pos_next_address);
+ }
+ break;
+ case CDP_TLV_PORT:
+ if (tlv_len == 0) {
+ log_warn("cdp", "too short port description received");
+ goto malformed;
+ }
+ free(port->p_descr);
+ if ((port->p_descr = (char *)calloc(1, tlv_len + 1)) == NULL) {
+ log_warn("cdp",
+ "unable to allocate memory for port description");
+ goto malformed;
+ }
+ PEEK_BYTES(port->p_descr, tlv_len);
+ port->p_id_subtype = LLDP_PORTID_SUBTYPE_IFNAME;
+ free(port->p_id);
+ if ((port->p_id = (char *)calloc(1, tlv_len)) == NULL) {
+ log_warn("cdp",
+ "unable to allocate memory for port ID");
+ goto malformed;
+ }
+ memcpy(port->p_id, port->p_descr, tlv_len);
+ port->p_id_len = tlv_len;
+ break;
+ case CDP_TLV_CAPABILITIES:
+# ifdef ENABLE_FDP
+ if (fdp) {
+ /* Capabilities are string with FDP */
+ if (!strncmp("Router", (char *)pos, tlv_len))
+ chassis->c_cap_enabled = LLDP_CAP_ROUTER;
+ else if (!strncmp("Switch", (char *)pos, tlv_len))
+ chassis->c_cap_enabled = LLDP_CAP_BRIDGE;
+ else if (!strncmp("Bridge", (char *)pos, tlv_len))
+ chassis->c_cap_enabled = LLDP_CAP_REPEATER;
+ else
+ chassis->c_cap_enabled = LLDP_CAP_STATION;
+ chassis->c_cap_available = chassis->c_cap_enabled;
+ break;
+ }
+# endif
+ CHECK_TLV_SIZE(4, "Capabilities");
+ caps = PEEK_UINT32;
+ if (caps & CDP_CAP_ROUTER)
+ chassis->c_cap_enabled |= LLDP_CAP_ROUTER;
+ if (caps & 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;
+ break;
+ case CDP_TLV_SOFTWARE:
+ software_len = tlv_len;
+ (void)PEEK_SAVE(software);
+ break;
+ case CDP_TLV_PLATFORM:
+ platform_len = tlv_len;
+ (void)PEEK_SAVE(platform);
+ break;
+# ifdef ENABLE_DOT1
+ case CDP_TLV_NATIVEVLAN:
+ CHECK_TLV_SIZE(2, "Native VLAN");
+ if ((vlan = (struct lldpd_vlan *)calloc(1,
+ sizeof(struct lldpd_vlan))) == NULL) {
+ log_warn("cdp",
+ "unable to alloc vlan "
+ "structure for "
+ "tlv received on %s",
+ hardware->h_ifname);
+ goto malformed;
+ }
+ vlan->v_vid = port->p_pvid = PEEK_UINT16;
+ if (asprintf(&vlan->v_name, "VLAN #%d", vlan->v_vid) == -1) {
+ log_warn("cdp",
+ "unable to alloc VLAN name for "
+ "TLV received on %s",
+ hardware->h_ifname);
+ free(vlan);
+ goto malformed;
+ }
+ TAILQ_INSERT_TAIL(&port->p_vlans, vlan, v_entries);
+ break;
+# endif
+# ifdef ENABLE_DOT3
+ case CDP_TLV_POWER_AVAILABLE:
+ CHECK_TLV_SIZE(12, "Power Available");
+ /* check if it is a respone to a request id */
+ if (PEEK_UINT16 > 0) {
+ port->p_cdp_power.management_id = PEEK_UINT16;
+ port->p_power.allocated = PEEK_UINT32;
+ port->p_power.allocated /= 100;
+ port->p_power.supported = 1;
+ port->p_power.enabled = 1;
+ port->p_power.devicetype = LLDP_DOT3_POWER_PSE;
+ port->p_power.powertype = LLDP_DOT3_POWER_8023AT_TYPE2;
+ log_debug("cdp", "Allocated power %d00",
+ port->p_power.allocated);
+ if (port->p_power.allocated > CDP_CLASS_3_MAX_PSE_POE) {
+ port->p_power.allocated -=
+ CDP_SWITCH_POE_CLASS_4_OFFSET;
+ } else if (port->p_power.allocated >
+ CDP_SWITCH_POE_CLASS_3_OFFSET) {
+ port->p_power.allocated -=
+ CDP_SWITCH_POE_CLASS_3_OFFSET;
+ } else {
+ port->p_power.allocated = 0;
+ }
+ port->p_power.requested =
+ hardware->h_lport.p_power.requested;
+ }
+ break;
+# endif
+ default:
+ log_debug("cdp", "unknown CDP/FDP TLV type (%d) received on %s",
+ ntohs(tlv_type), hardware->h_ifname);
+ hardware->h_rx_unrecognized_cnt++;
+ }
+ PEEK_DISCARD(tlv + tlv_len - pos);
+ }
+ if (!software && platform) {
+ if ((chassis->c_descr = (char *)calloc(1, platform_len + 1)) == NULL) {
+ log_warn("cdp",
+ "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) {
+ log_warn("cdp",
+ "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) {
+ log_warn("cdp",
+ "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) || (port->p_ttl == 0) ||
+ (chassis->c_cap_enabled == 0)) {
+ log_warnx("cdp",
+ "some mandatory CDP/FDP tlv are missing for frame received on %s",
+ hardware->h_ifname);
+ goto malformed;
+ }
+ *newchassis = chassis;
+ *newport = port;
+ return 1;
+
+malformed:
+ lldpd_chassis_cleanup(chassis, 1);
+ lldpd_port_cleanup(port, 1);
+ free(port);
+ return -1;
+}
+
+# ifdef ENABLE_CDP
+int
+cdpv1_send(struct lldpd *global, struct lldpd_hardware *hardware)
+{
+ return cdp_send(global, hardware, 1);
+}
+
+int
+cdpv2_send(struct lldpd *global, struct lldpd_hardware *hardware)
+{
+ return cdp_send(global, hardware, 2);
+}
+# endif
+
+# ifdef ENABLE_FDP
+int
+fdp_send(struct lldpd *global, struct lldpd_hardware *hardware)
+{
+ return cdp_send(global, hardware, 0);
+}
+# endif
+
+# ifdef ENABLE_CDP
+static int
+cdp_guess(char *pos, int length, int version)
+{
+ const u_int8_t mcastaddr[] = CDP_MULTICAST_ADDR;
+ if (length < 2 * ETHER_ADDR_LEN + sizeof(u_int16_t) /* Ethernet */ +
+ 8 /* LLC */ + 4 /* CDP header */)
+ return 0;
+ if (PEEK_CMP(mcastaddr, ETHER_ADDR_LEN) != 0) return 0;
+ PEEK_DISCARD(ETHER_ADDR_LEN);
+ PEEK_DISCARD_UINT16; /* Ethernet */
+ PEEK_DISCARD(8); /* LLC */
+ return (PEEK_UINT8 == 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);
+}
+# endif
+
+#endif /* defined (ENABLE_CDP) || defined (ENABLE_FDP) */
diff --git a/src/daemon/protocols/cdp.h b/src/daemon/protocols/cdp.h
new file mode 100644
index 0000000..f83ffef
--- /dev/null
+++ b/src/daemon/protocols/cdp.h
@@ -0,0 +1,70 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/*
+ * Copyright (c) 2008 Vincent Bernat <bernat@luffy.cx>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef _CDP_H
+#define _CDP_H
+
+#define CDP_MULTICAST_ADDR \
+ { \
+ 0x01, 0x00, 0x0c, 0xcc, 0xcc, 0xcc \
+ }
+#define FDP_MULTICAST_ADDR \
+ { \
+ 0x01, 0xe0, 0x52, 0xcc, 0xcc, 0xcc, \
+ }
+#define LLC_ORG_CISCO \
+ { \
+ 0x00, 0x00, 0x0c \
+ }
+#define LLC_ORG_FOUNDRY \
+ { \
+ 0x00, 0xe0, 0x52 \
+ }
+#define LLC_PID_CDP 0x2000
+/* Other protocols */
+#define LLC_PID_DRIP 0x102
+#define LLC_PID_PAGP 0x104
+#define LLC_PID_PVSTP 0x10b
+#define LLC_PID_UDLD 0x111
+#define LLC_PID_VTP 0x2003
+#define LLC_PID_DTP 0x2004
+#define LLC_PID_STP 0x200a
+
+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,
+ CDP_TLV_NATIVEVLAN = 10,
+ CDP_TLV_POWER_CONSUMPTION = 0x10,
+ CDP_TLV_POWER_REQUESTED = 0x19,
+ CDP_TLV_POWER_AVAILABLE = 0x1A
+};
+
+#define CDP_ADDRESS_PROTO_IP 0xcc
+
+#define CDP_CAP_ROUTER 0x01
+#define CDP_CAP_TRANSPARENT_BRIDGE 0x02
+#define CDP_CAP_SOURCE_BRIDGE 0x04
+#define CDP_CAP_SWITCH 0x08
+#define CDP_CAP_HOST 0x10
+#define CDP_CAP_IGMP 0x20
+#define CDP_CAP_REPEATER 0x40
+
+#endif /* _CDP_H */
diff --git a/src/daemon/protocols/edp.c b/src/daemon/protocols/edp.c
new file mode 100644
index 0000000..b55130b
--- /dev/null
+++ b/src/daemon/protocols/edp.c
@@ -0,0 +1,514 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/*
+ * Copyright (c) 2008 Vincent Bernat <bernat@luffy.cx>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "../lldpd.h"
+#include "../frame.h"
+
+#ifdef ENABLE_EDP
+
+# include <stdio.h>
+# include <unistd.h>
+# include <errno.h>
+# include <arpa/inet.h>
+# include <fnmatch.h>
+
+static int seq = 0;
+
+int
+edp_send(struct lldpd *global, struct lldpd_hardware *hardware)
+{
+ const u_int8_t mcastaddr[] = EDP_MULTICAST_ADDR;
+ const u_int8_t llcorg[] = LLC_ORG_EXTREME;
+ struct lldpd_chassis *chassis;
+ int length, i, v;
+ u_int8_t *packet, *pos, *pos_llc, *pos_len_eh, *pos_len_edp, *pos_edp, *tlv,
+ *end;
+ u_int16_t checksum;
+# ifdef ENABLE_DOT1
+ struct lldpd_vlan *vlan;
+ unsigned int state = 0;
+# endif
+ u_int8_t edp_fakeversion[] = { 7, 6, 4, 99 };
+ /* 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. */
+ const char *deviceslot[] = { "eth", "veth", "XXX", "XXX", "XXX", "XXX", "XXX",
+ "XXX", "", NULL };
+
+ log_debug("edp", "send EDP frame on port %s", hardware->h_ifname);
+
+ chassis = hardware->h_lport.p_chassis;
+# ifdef ENABLE_DOT1
+ while (state != 2) {
+# endif
+ length = hardware->h_mtu;
+ if ((packet = (u_int8_t *)calloc(1, length)) == NULL) return ENOMEM;
+ pos = packet;
+ v = 0;
+
+ /* Ethernet header */
+ if (!(POKE_BYTES(mcastaddr, sizeof(mcastaddr)) &&
+ POKE_BYTES(&hardware->h_lladdr, ETHER_ADDR_LEN) &&
+ POKE_SAVE(pos_len_eh) && /* We compute the len later */
+ POKE_UINT16(0)))
+ goto toobig;
+
+ /* LLC */
+ if (!(POKE_SAVE(pos_llc) && /* We need to save our
+ current position to
+ compute ethernet len */
+ /* SSAP and DSAP */
+ POKE_UINT8(0xaa) && POKE_UINT8(0xaa) &&
+ /* Control field */
+ POKE_UINT8(0x03) &&
+ /* ORG */
+ POKE_BYTES(llcorg, sizeof(llcorg)) && POKE_UINT16(LLC_PID_EDP)))
+ goto toobig;
+
+ /* EDP header */
+ if ((chassis->c_id_len != ETHER_ADDR_LEN) ||
+ (chassis->c_id_subtype != LLDP_CHASSISID_SUBTYPE_LLADDR)) {
+ log_warnx("edp",
+ "local chassis does not use MAC address as chassis ID!?");
+ free(packet);
+ return EINVAL;
+ }
+ if (!(POKE_SAVE(pos_edp) && /* Save the start of EDP frame */
+ POKE_UINT8(1) && POKE_UINT8(0) &&
+ POKE_SAVE(pos_len_edp) && /* We compute the len
+ and the checksum
+ later */
+ POKE_UINT32(0) && /* Len + Checksum */
+ POKE_UINT16(seq) && POKE_UINT16(0) &&
+ POKE_BYTES(chassis->c_id, ETHER_ADDR_LEN)))
+ goto toobig;
+ seq++;
+
+# ifdef ENABLE_DOT1
+ switch (state) {
+ case 0:
+# endif
+ /* Display TLV */
+ if (!(POKE_START_EDP_TLV(EDP_TLV_DISPLAY) &&
+ POKE_BYTES(chassis->c_name, strlen(chassis->c_name)) &&
+ POKE_UINT8(0) && /* Add a NULL character
+ for better
+ compatibility */
+ POKE_END_EDP_TLV))
+ goto toobig;
+
+ /* Info TLV */
+ if (!(POKE_START_EDP_TLV(EDP_TLV_INFO))) goto toobig;
+ /* We try to emulate the slot thing */
+ for (i = 0; deviceslot[i] != NULL; i++) {
+ if (strncmp(hardware->h_ifname, deviceslot[i],
+ strlen(deviceslot[i])) == 0) {
+ if (!(POKE_UINT16(i) &&
+ POKE_UINT16(atoi(hardware->h_ifname +
+ strlen(deviceslot[i])))))
+ goto toobig;
+ break;
+ }
+ }
+ /* If we don't find a "slot", we say that the
+ interface is in slot 8 */
+ if (deviceslot[i] == NULL) {
+ if (!(POKE_UINT16(8) &&
+ POKE_UINT16(hardware->h_ifindex)))
+ goto toobig;
+ }
+ if (!(POKE_UINT16(0) && /* vchassis */
+ POKE_UINT32(0) && POKE_UINT16(0) && /* Reserved */
+ /* Version */
+ POKE_BYTES(edp_fakeversion, sizeof(edp_fakeversion)) &&
+ /* Connections, we say that we won't
+ have more interfaces than this
+ mask. */
+ POKE_UINT32(0xffffffff) && POKE_UINT32(0) &&
+ POKE_UINT32(0) && POKE_UINT32(0) && POKE_END_EDP_TLV))
+ goto toobig;
+
+# ifdef ENABLE_DOT1
+ break;
+ case 1:
+ TAILQ_FOREACH (vlan, &hardware->h_lport.p_vlans, v_entries) {
+ v++;
+ if (!(POKE_START_EDP_TLV(EDP_TLV_VLAN) &&
+ POKE_UINT8(0) && /* Flags: no IP address */
+ POKE_UINT8(0) && /* Reserved */
+ POKE_UINT16(vlan->v_vid) &&
+ POKE_UINT32(0) && /* Reserved */
+ POKE_UINT32(0) && /* IP address */
+ /* VLAN name */
+ POKE_BYTES(vlan->v_name,
+ strlen(vlan->v_name)) &&
+ POKE_UINT8(0) && POKE_END_EDP_TLV))
+ goto toobig;
+ }
+ break;
+ }
+
+ if ((state == 1) && (v == 0)) {
+ /* No VLAN, no need to send another TLV */
+ free(packet);
+ break;
+ }
+# endif
+
+ /* Null TLV */
+ if (!(POKE_START_EDP_TLV(EDP_TLV_NULL) && POKE_END_EDP_TLV &&
+ POKE_SAVE(end)))
+ goto toobig;
+
+ /* Compute len and checksum */
+ i = end - pos_llc; /* Ethernet length */
+ v = end - pos_edp; /* EDP length */
+ POKE_RESTORE(pos_len_eh);
+ if (!(POKE_UINT16(i))) goto toobig;
+ POKE_RESTORE(pos_len_edp);
+ if (!(POKE_UINT16(v))) goto toobig;
+ checksum = frame_checksum(pos_edp, v, 0);
+ if (!(POKE_UINT16(checksum))) goto toobig;
+
+ if (interfaces_send_helper(global, hardware, (char *)packet,
+ end - packet) == -1) {
+ log_warn("edp", "unable to send packet on real device for %s",
+ hardware->h_ifname);
+ free(packet);
+ return ENETDOWN;
+ }
+ free(packet);
+
+# ifdef ENABLE_DOT1
+ state++;
+ }
+# endif
+
+ hardware->h_tx_cnt++;
+ return 0;
+toobig:
+ free(packet);
+ return E2BIG;
+}
+
+# define CHECK_TLV_SIZE(x, name) \
+ do { \
+ if (tlv_len < (x)) { \
+ log_warnx("edp", name " EDP TLV too short received on %s", \
+ hardware->h_ifname); \
+ goto malformed; \
+ } \
+ } while (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;
+# ifdef ENABLE_DOT1
+ struct lldpd_mgmt *mgmt, *mgmt_next, *m;
+ struct lldpd_vlan *lvlan = NULL, *lvlan_next;
+# endif
+ const unsigned char edpaddr[] = EDP_MULTICAST_ADDR;
+ int length, gotend = 0, gotvlans = 0, edp_len, tlv_len, tlv_type;
+ int edp_port, edp_slot;
+ u_int8_t *pos, *pos_edp, *tlv;
+ u_int8_t version[4];
+# ifdef ENABLE_DOT1
+ struct in_addr address;
+ struct lldpd_port *oport;
+# endif
+
+ log_debug("edp", "decode EDP frame on port %s", hardware->h_ifname);
+
+ if ((chassis = calloc(1, sizeof(struct lldpd_chassis))) == NULL) {
+ log_warn("edp", "failed to allocate remote chassis");
+ return -1;
+ }
+ TAILQ_INIT(&chassis->c_mgmt);
+ if ((port = calloc(1, sizeof(struct lldpd_port))) == NULL) {
+ log_warn("edp", "failed to allocate remote port");
+ free(chassis);
+ return -1;
+ }
+# ifdef ENABLE_DOT1
+ TAILQ_INIT(&port->p_vlans);
+# endif
+
+ length = s;
+ pos = (u_int8_t *)frame;
+
+ if (length < 2 * ETHER_ADDR_LEN + sizeof(u_int16_t) + 8 /* LLC */ + 10 +
+ ETHER_ADDR_LEN /* EDP header */) {
+ log_warnx("edp", "too short EDP frame received on %s",
+ hardware->h_ifname);
+ goto malformed;
+ }
+
+ if (PEEK_CMP(edpaddr, sizeof(edpaddr)) != 0) {
+ log_info("edp",
+ "frame not targeted at EDP multicast address received on %s",
+ hardware->h_ifname);
+ goto malformed;
+ }
+ PEEK_DISCARD(ETHER_ADDR_LEN);
+ PEEK_DISCARD_UINT16;
+ PEEK_DISCARD(6); /* LLC: DSAP + SSAP + control + org */
+ if (PEEK_UINT16 != LLC_PID_EDP) {
+ log_debug("edp", "incorrect LLC protocol ID received on %s",
+ hardware->h_ifname);
+ goto malformed;
+ }
+
+ (void)PEEK_SAVE(pos_edp); /* Save the start of EDP packet */
+ if (PEEK_UINT8 != 1) {
+ log_warnx("edp", "incorrect EDP version for frame received on %s",
+ hardware->h_ifname);
+ goto malformed;
+ }
+ PEEK_DISCARD_UINT8; /* Reserved */
+ edp_len = PEEK_UINT16;
+ PEEK_DISCARD_UINT16; /* Checksum */
+ PEEK_DISCARD_UINT16; /* Sequence */
+ if (PEEK_UINT16 != 0) { /* ID Type = 0 = MAC */
+ log_warnx("edp", "incorrect device id type for frame received on %s",
+ hardware->h_ifname);
+ goto malformed;
+ }
+ if (edp_len > length + 10) {
+ log_warnx("edp", "incorrect size for EDP frame received on %s",
+ hardware->h_ifname);
+ goto malformed;
+ }
+ port->p_ttl = cfg ? cfg->g_config.c_tx_interval * cfg->g_config.c_tx_hold : 0;
+ port->p_ttl = (port->p_ttl + 999) / 1000;
+ chassis->c_id_subtype = LLDP_CHASSISID_SUBTYPE_LLADDR;
+ chassis->c_id_len = ETHER_ADDR_LEN;
+ if ((chassis->c_id = (char *)malloc(ETHER_ADDR_LEN)) == NULL) {
+ log_warn("edp", "unable to allocate memory for chassis ID");
+ goto malformed;
+ }
+ PEEK_BYTES(chassis->c_id, ETHER_ADDR_LEN);
+
+# ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
+ /* Let's check checksum */
+ if (frame_checksum(pos_edp, edp_len, 0) != 0) {
+ log_warnx("edp", "incorrect EDP checksum for frame received on %s",
+ hardware->h_ifname);
+ goto malformed;
+ }
+# endif
+
+ while (length && !gotend) {
+ if (length < 4) {
+ log_warnx("edp",
+ "EDP TLV header is too large for "
+ "frame received on %s",
+ hardware->h_ifname);
+ goto malformed;
+ }
+ if (PEEK_UINT8 != EDP_TLV_MARKER) {
+ log_warnx("edp",
+ "incorrect marker starting EDP TLV header for frame "
+ "received on %s",
+ hardware->h_ifname);
+ goto malformed;
+ }
+ tlv_type = PEEK_UINT8;
+ tlv_len = PEEK_UINT16 - 4;
+ (void)PEEK_SAVE(tlv);
+ if ((tlv_len < 0) || (tlv_len > length)) {
+ log_debug("edp",
+ "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;
+ }
+ switch (tlv_type) {
+ case EDP_TLV_INFO:
+ CHECK_TLV_SIZE(32, "Info");
+ port->p_id_subtype = LLDP_PORTID_SUBTYPE_IFNAME;
+ edp_slot = PEEK_UINT16;
+ edp_port = PEEK_UINT16;
+ free(port->p_id);
+ port->p_id_len =
+ asprintf(&port->p_id, "%d/%d", edp_slot + 1, edp_port + 1);
+ if (port->p_id_len == -1) {
+ log_warn("edp",
+ "unable to allocate memory for "
+ "port ID");
+ goto malformed;
+ }
+ free(port->p_descr);
+ if (asprintf(&port->p_descr, "Slot %d / Port %d", edp_slot + 1,
+ edp_port + 1) == -1) {
+ log_warn("edp",
+ "unable to allocate memory for "
+ "port description");
+ goto malformed;
+ }
+ PEEK_DISCARD_UINT16; /* vchassis */
+ PEEK_DISCARD(6); /* Reserved */
+ PEEK_BYTES(version, 4);
+ free(chassis->c_descr);
+ if (asprintf(&chassis->c_descr,
+ "EDP enabled device, version %d.%d.%d.%d", version[0],
+ version[1], version[2], version[3]) == -1) {
+ log_warn("edp",
+ "unable to allocate memory for "
+ "chassis description");
+ goto malformed;
+ }
+ break;
+ case EDP_TLV_DISPLAY:
+ free(chassis->c_name);
+ if ((chassis->c_name = (char *)calloc(1, tlv_len + 1)) ==
+ NULL) {
+ log_warn("edp",
+ "unable to allocate memory for chassis "
+ "name");
+ goto malformed;
+ }
+ /* TLV display contains a lot of garbage */
+ PEEK_BYTES(chassis->c_name, tlv_len);
+ break;
+ case EDP_TLV_NULL:
+ if (tlv_len != 0) {
+ log_warnx("edp",
+ "null tlv with incorrect size in frame "
+ "received on %s",
+ hardware->h_ifname);
+ goto malformed;
+ }
+ if (length)
+ log_debug("edp", "extra data after edp frame on %s",
+ hardware->h_ifname);
+ gotend = 1;
+ break;
+ case EDP_TLV_VLAN:
+# ifdef ENABLE_DOT1
+ CHECK_TLV_SIZE(12, "VLAN");
+ if ((lvlan = (struct lldpd_vlan *)calloc(1,
+ sizeof(struct lldpd_vlan))) == NULL) {
+ log_warn("edp", "unable to allocate vlan");
+ goto malformed;
+ }
+ PEEK_DISCARD_UINT16; /* Flags + reserved */
+ lvlan->v_vid = PEEK_UINT16; /* VID */
+ PEEK_DISCARD(4); /* Reserved */
+ PEEK_BYTES(&address, sizeof(address));
+
+ if (address.s_addr != INADDR_ANY) {
+ mgmt = lldpd_alloc_mgmt(LLDPD_AF_IPV4, &address,
+ sizeof(struct in_addr), 0);
+ if (mgmt == NULL) {
+ log_warn("edp", "Out of memory");
+ goto malformed;
+ }
+ TAILQ_INSERT_TAIL(&chassis->c_mgmt, mgmt, m_entries);
+ }
+
+ if ((lvlan->v_name = (char *)calloc(1, tlv_len + 1 - 12)) ==
+ NULL) {
+ log_warn("edp", "unable to allocate vlan name");
+ goto malformed;
+ }
+ PEEK_BYTES(lvlan->v_name, tlv_len - 12);
+
+ TAILQ_INSERT_TAIL(&port->p_vlans, lvlan, v_entries);
+ lvlan = NULL;
+# endif
+ gotvlans = 1;
+ break;
+ default:
+ log_debug("edp", "unknown EDP TLV type (%d) received on %s",
+ tlv_type, hardware->h_ifname);
+ hardware->h_rx_unrecognized_cnt++;
+ }
+ PEEK_DISCARD(tlv + tlv_len - pos);
+ }
+ if ((chassis->c_id == NULL) || (port->p_id == NULL) ||
+ (chassis->c_name == NULL) || (chassis->c_descr == NULL) ||
+ (port->p_descr == NULL) || (gotend == 0)) {
+# ifdef ENABLE_DOT1
+ if (gotvlans && gotend) {
+ /* VLAN can be sent in a separate frames. We need to add
+ * those vlans to an existing port */
+ TAILQ_FOREACH (oport, &hardware->h_rports, p_entries) {
+ if (!((oport->p_protocol == LLDPD_MODE_EDP) &&
+ (oport->p_chassis->c_id_subtype ==
+ chassis->c_id_subtype) &&
+ (oport->p_chassis->c_id_len ==
+ chassis->c_id_len) &&
+ (memcmp(oport->p_chassis->c_id, chassis->c_id,
+ chassis->c_id_len) == 0)))
+ continue;
+ /* We attach the VLANs to the found port */
+ lldpd_vlan_cleanup(oport);
+ 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(&oport->p_vlans, lvlan,
+ v_entries);
+ }
+ /* And the IP addresses */
+ for (mgmt = TAILQ_FIRST(&chassis->c_mgmt); mgmt != NULL;
+ mgmt = mgmt_next) {
+ mgmt_next = TAILQ_NEXT(mgmt, m_entries);
+ TAILQ_REMOVE(&chassis->c_mgmt, mgmt, m_entries);
+ /* Don't add an address that already exists! */
+ TAILQ_FOREACH (m, &chassis->c_mgmt, m_entries)
+ if (m->m_family == mgmt->m_family &&
+ !memcmp(&m->m_addr, &mgmt->m_addr,
+ sizeof(m->m_addr)))
+ break;
+ if (m == NULL)
+ TAILQ_INSERT_TAIL(
+ &oport->p_chassis->c_mgmt, mgmt,
+ m_entries);
+ }
+ }
+ /* We discard the remaining frame */
+ goto malformed;
+ }
+# else
+ if (gotvlans) goto malformed;
+# endif
+ log_warnx("edp",
+ "some mandatory tlv are missing for frame received on %s",
+ hardware->h_ifname);
+ goto malformed;
+ }
+ *newchassis = chassis;
+ *newport = port;
+ return 1;
+
+malformed:
+# ifdef ENABLE_DOT1
+ free(lvlan);
+# endif
+ lldpd_chassis_cleanup(chassis, 1);
+ lldpd_port_cleanup(port, 1);
+ free(port);
+ return -1;
+}
+
+#endif /* ENABLE_EDP */
diff --git a/src/daemon/protocols/edp.h b/src/daemon/protocols/edp.h
new file mode 100644
index 0000000..f4ee183
--- /dev/null
+++ b/src/daemon/protocols/edp.h
@@ -0,0 +1,41 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/*
+ * Copyright (c) 2008 Vincent Bernat <bernat@luffy.cx>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#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
+
+enum {
+ EDP_TLV_NULL = 0,
+ EDP_TLV_DISPLAY = 1,
+ EDP_TLV_INFO = 2,
+ EDP_TLV_VLAN = 5,
+ EDP_TLV_ESRP = 8,
+};
+
+#endif /* _EDP_H */
diff --git a/src/daemon/protocols/lldp.c b/src/daemon/protocols/lldp.c
new file mode 100644
index 0000000..6e73237
--- /dev/null
+++ b/src/daemon/protocols/lldp.c
@@ -0,0 +1,1315 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/*
+ * Copyright (c) 2008 Vincent Bernat <bernat@luffy.cx>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "../lldpd.h"
+#include "../frame.h"
+
+#include <unistd.h>
+#include <errno.h>
+#include <time.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+
+static int
+lldpd_af_to_lldp_proto(int af)
+{
+ switch (af) {
+ case LLDPD_AF_IPV4:
+ return LLDP_MGMT_ADDR_IP4;
+ case LLDPD_AF_IPV6:
+ return LLDP_MGMT_ADDR_IP6;
+ default:
+ return LLDP_MGMT_ADDR_NONE;
+ }
+}
+
+static int
+lldpd_af_from_lldp_proto(int proto)
+{
+ switch (proto) {
+ case LLDP_MGMT_ADDR_IP4:
+ return LLDPD_AF_IPV4;
+ case LLDP_MGMT_ADDR_IP6:
+ return LLDPD_AF_IPV6;
+ default:
+ return LLDPD_AF_UNSPEC;
+ }
+}
+
+static int
+_lldp_send(struct lldpd *global, struct lldpd_hardware *hardware, u_int8_t c_id_subtype,
+ char *c_id, int c_id_len, u_int8_t p_id_subtype, char *p_id, int p_id_len,
+ int shutdown, int without_vlans)
+{
+ struct lldpd_port *port;
+ struct lldpd_chassis *chassis;
+ struct lldpd_frame *frame;
+ int length;
+ u_int8_t *packet, *pos, *tlv;
+ struct lldpd_mgmt *mgmt;
+ int proto;
+ int vlans = 0;
+
+ u_int8_t mcastaddr_regular[] = LLDP_ADDR_NEAREST_BRIDGE;
+ u_int8_t mcastaddr_nontpmr[] = LLDP_ADDR_NEAREST_NONTPMR_BRIDGE;
+ u_int8_t mcastaddr_customer[] = LLDP_ADDR_NEAREST_CUSTOMER_BRIDGE;
+ u_int8_t *mcastaddr;
+#ifdef ENABLE_DOT1
+ const u_int8_t dot1[] = LLDP_TLV_ORG_DOT1;
+ struct lldpd_vlan *vlan;
+ struct lldpd_ppvid *ppvid;
+ struct lldpd_pi *pi;
+#endif
+#ifdef ENABLE_DOT3
+ const u_int8_t dot3[] = LLDP_TLV_ORG_DOT3;
+#endif
+#ifdef ENABLE_LLDPMED
+ int i;
+ const u_int8_t med[] = LLDP_TLV_ORG_MED;
+#endif
+#ifdef ENABLE_CUSTOM
+ struct lldpd_custom *custom;
+#endif
+ port = &hardware->h_lport;
+ chassis = port->p_chassis;
+ length = hardware->h_mtu;
+ if ((packet = (u_int8_t *)calloc(1, length)) == NULL) return ENOMEM;
+ pos = packet;
+
+ /* Ethernet header */
+ switch (global->g_config.c_lldp_agent_type) {
+ case LLDP_AGENT_TYPE_NEAREST_NONTPMR_BRIDGE:
+ mcastaddr = mcastaddr_nontpmr;
+ break;
+ case LLDP_AGENT_TYPE_NEAREST_CUSTOMER_BRIDGE:
+ mcastaddr = mcastaddr_customer;
+ break;
+ case LLDP_AGENT_TYPE_NEAREST_BRIDGE:
+ default:
+ mcastaddr = mcastaddr_regular;
+ break;
+ }
+ if (!(
+ /* LLDP multicast address */
+ POKE_BYTES(mcastaddr, ETHER_ADDR_LEN) &&
+ /* Source MAC address */
+ POKE_BYTES(&hardware->h_lladdr, ETHER_ADDR_LEN)))
+ goto toobig;
+
+ /* Insert VLAN tag if needed */
+ if (port->p_vlan_tx_enabled) {
+ if (!(
+ /* VLAN ethertype */
+ POKE_UINT16(ETHERTYPE_VLAN) &&
+ /* VLAN Tag Control Information (TCI) */
+ /* Priority(3bits) | DEI(1bit) | VID(12bit) */
+ POKE_UINT16(port->p_vlan_tx_tag)))
+ goto toobig;
+ }
+
+ if (!(
+ /* LLDP frame */
+ POKE_UINT16(ETH_P_LLDP)))
+ goto toobig;
+
+ /* Chassis ID */
+ if (!(POKE_START_LLDP_TLV(LLDP_TLV_CHASSIS_ID) && POKE_UINT8(c_id_subtype) &&
+ POKE_BYTES(c_id, c_id_len) && POKE_END_LLDP_TLV))
+ goto toobig;
+
+ /* Port ID */
+ if (!(POKE_START_LLDP_TLV(LLDP_TLV_PORT_ID) && POKE_UINT8(p_id_subtype) &&
+ POKE_BYTES(p_id, p_id_len) && POKE_END_LLDP_TLV))
+ goto toobig;
+
+ /* Time to live */
+ if (!(POKE_START_LLDP_TLV(LLDP_TLV_TTL) &&
+ POKE_UINT16(shutdown ? 0 : (global ? global->g_config.c_ttl : 180)) &&
+ POKE_END_LLDP_TLV))
+ goto toobig;
+
+ if (shutdown) goto end;
+
+ /* System name */
+ if (chassis->c_name && *chassis->c_name != '\0') {
+ if (!(POKE_START_LLDP_TLV(LLDP_TLV_SYSTEM_NAME) &&
+ POKE_BYTES(chassis->c_name, strlen(chassis->c_name)) &&
+ POKE_END_LLDP_TLV))
+ goto toobig;
+ }
+
+ /* System description (skip it if empty) */
+ if (chassis->c_descr && *chassis->c_descr != '\0') {
+ if (!(POKE_START_LLDP_TLV(LLDP_TLV_SYSTEM_DESCR) &&
+ POKE_BYTES(chassis->c_descr, strlen(chassis->c_descr)) &&
+ POKE_END_LLDP_TLV))
+ goto toobig;
+ }
+
+ /* System capabilities */
+ if (global->g_config.c_cap_advertise && chassis->c_cap_available) {
+ if (!(POKE_START_LLDP_TLV(LLDP_TLV_SYSTEM_CAP) &&
+ POKE_UINT16(chassis->c_cap_available) &&
+ POKE_UINT16(chassis->c_cap_enabled) && POKE_END_LLDP_TLV))
+ goto toobig;
+ }
+
+ /* Management addresses */
+ TAILQ_FOREACH (mgmt, &chassis->c_mgmt, m_entries) {
+ proto = lldpd_af_to_lldp_proto(mgmt->m_family);
+ if (proto == LLDP_MGMT_ADDR_NONE) continue;
+ if (!(POKE_START_LLDP_TLV(LLDP_TLV_MGMT_ADDR) &&
+ /* Size of the address, including its type */
+ POKE_UINT8(mgmt->m_addrsize + 1) && POKE_UINT8(proto) &&
+ POKE_BYTES(&mgmt->m_addr, mgmt->m_addrsize)))
+ goto toobig;
+
+ /* Interface port type, OID */
+ if (mgmt->m_iface == 0) {
+ if (!(
+ /* We don't know the management interface */
+ POKE_UINT8(LLDP_MGMT_IFACE_UNKNOWN) && POKE_UINT32(0)))
+ goto toobig;
+ } else {
+ if (!(
+ /* We have the index of the management interface */
+ POKE_UINT8(LLDP_MGMT_IFACE_IFINDEX) &&
+ POKE_UINT32(mgmt->m_iface)))
+ goto toobig;
+ }
+ if (!(
+ /* We don't provide an OID for management */
+ POKE_UINT8(0) && POKE_END_LLDP_TLV))
+ goto toobig;
+ }
+
+ /* Port description */
+ if (port->p_descr && *port->p_descr != '\0') {
+ if (!(POKE_START_LLDP_TLV(LLDP_TLV_PORT_DESCR) &&
+ POKE_BYTES(port->p_descr, strlen(port->p_descr)) &&
+ POKE_END_LLDP_TLV))
+ goto toobig;
+ }
+
+#ifdef ENABLE_DOT1
+ /* Port VLAN ID */
+ if (port->p_pvid != 0) {
+ if (!(POKE_START_LLDP_TLV(LLDP_TLV_ORG) &&
+ POKE_BYTES(dot1, sizeof(dot1)) &&
+ POKE_UINT8(LLDP_TLV_DOT1_PVID) && POKE_UINT16(port->p_pvid) &&
+ POKE_END_LLDP_TLV)) {
+ goto toobig;
+ }
+ }
+ /* Port and Protocol VLAN IDs */
+ TAILQ_FOREACH (ppvid, &port->p_ppvids, p_entries) {
+ if (!(POKE_START_LLDP_TLV(LLDP_TLV_ORG) &&
+ POKE_BYTES(dot1, sizeof(dot1)) &&
+ POKE_UINT8(LLDP_TLV_DOT1_PPVID) &&
+ POKE_UINT8(ppvid->p_cap_status) &&
+ POKE_UINT16(ppvid->p_ppvid) && POKE_END_LLDP_TLV)) {
+ goto toobig;
+ }
+ }
+ /* VLANs */
+ if (!without_vlans) {
+ TAILQ_FOREACH (vlan, &port->p_vlans, v_entries) {
+ vlans++;
+ if (!(POKE_START_LLDP_TLV(LLDP_TLV_ORG) &&
+ POKE_BYTES(dot1, sizeof(dot1)) &&
+ POKE_UINT8(LLDP_TLV_DOT1_VLANNAME) &&
+ POKE_UINT16(vlan->v_vid) &&
+ POKE_UINT8(strlen(vlan->v_name)) &&
+ POKE_BYTES(vlan->v_name, strlen(vlan->v_name)) &&
+ POKE_END_LLDP_TLV))
+ goto toobig;
+ }
+ }
+ /* Protocol Identities */
+ TAILQ_FOREACH (pi, &port->p_pids, p_entries) {
+ if (!(POKE_START_LLDP_TLV(LLDP_TLV_ORG) &&
+ POKE_BYTES(dot1, sizeof(dot1)) &&
+ POKE_UINT8(LLDP_TLV_DOT1_PI) && POKE_UINT8(pi->p_pi_len) &&
+ POKE_BYTES(pi->p_pi, pi->p_pi_len) && POKE_END_LLDP_TLV))
+ goto toobig;
+ }
+#endif
+
+#ifdef ENABLE_DOT3
+ /* Aggregation status */
+ if (!(POKE_START_LLDP_TLV(LLDP_TLV_ORG) && POKE_BYTES(dot3, sizeof(dot3)) &&
+ POKE_UINT8(LLDP_TLV_DOT3_LA) &&
+ /* Bit 0 = capability ; Bit 1 = status */
+ POKE_UINT8((port->p_aggregid) ? 3 : 1) &&
+ POKE_UINT32(port->p_aggregid) && POKE_END_LLDP_TLV))
+ goto toobig;
+
+ /* MAC/PHY */
+ if (!(POKE_START_LLDP_TLV(LLDP_TLV_ORG) && POKE_BYTES(dot3, sizeof(dot3)) &&
+ POKE_UINT8(LLDP_TLV_DOT3_MAC) &&
+ POKE_UINT8(port->p_macphy.autoneg_support |
+ (port->p_macphy.autoneg_enabled << 1)) &&
+ POKE_UINT16(port->p_macphy.autoneg_advertised) &&
+ POKE_UINT16(port->p_macphy.mau_type) && POKE_END_LLDP_TLV))
+ goto toobig;
+
+ /* MFS */
+ if (port->p_mfs) {
+ if (!(POKE_START_LLDP_TLV(LLDP_TLV_ORG) &&
+ POKE_BYTES(dot3, sizeof(dot3)) &&
+ POKE_UINT8(LLDP_TLV_DOT3_MFS) && POKE_UINT16(port->p_mfs) &&
+ POKE_END_LLDP_TLV))
+ goto toobig;
+ }
+ /* Power */
+ if (port->p_power.devicetype) {
+ if (!((POKE_START_LLDP_TLV(LLDP_TLV_ORG)) &&
+ POKE_BYTES(dot3, sizeof(dot3)) &&
+ POKE_UINT8(LLDP_TLV_DOT3_POWER) &&
+ POKE_UINT8(((((2 - port->p_power.devicetype) % (1 << 1)) << 0) |
+ ((port->p_power.supported % (1 << 1)) << 1) |
+ ((port->p_power.enabled % (1 << 1)) << 2) |
+ ((port->p_power.paircontrol % (1 << 1)) << 3))) &&
+ POKE_UINT8(port->p_power.pairs) &&
+ POKE_UINT8(port->p_power.class)))
+ goto toobig;
+ /* 802.3at */
+ if (port->p_power.powertype != LLDP_DOT3_POWER_8023AT_OFF) {
+ if (!(POKE_UINT8(((((port->p_power.powertype ==
+ LLDP_DOT3_POWER_8023AT_TYPE1) ?
+ 1 :
+ 0)
+ << 7) |
+ (((port->p_power.devicetype == LLDP_DOT3_POWER_PSE) ?
+ 0 :
+ 1)
+ << 6) |
+ ((port->p_power.source % (1 << 2)) << 4) |
+ ((port->p_power.priority % (1 << 2)) << 0))) &&
+ POKE_UINT16(port->p_power.requested) &&
+ POKE_UINT16(port->p_power.allocated)))
+ goto toobig;
+ }
+ if (!(POKE_END_LLDP_TLV)) goto toobig;
+ }
+#endif
+
+#ifdef ENABLE_LLDPMED
+ if (port->p_med_cap_enabled) {
+ /* LLDP-MED cap */
+ if (port->p_med_cap_enabled & LLDP_MED_CAP_CAP) {
+ if (!(POKE_START_LLDP_TLV(LLDP_TLV_ORG) &&
+ POKE_BYTES(med, sizeof(med)) &&
+ POKE_UINT8(LLDP_TLV_MED_CAP) &&
+ POKE_UINT16(chassis->c_med_cap_available) &&
+ POKE_UINT8(chassis->c_med_type) && POKE_END_LLDP_TLV))
+ goto toobig;
+ }
+
+ /* LLDP-MED inventory */
+# define LLDP_INVENTORY(value, subtype) \
+ if (value) { \
+ if (!(POKE_START_LLDP_TLV(LLDP_TLV_ORG) && POKE_BYTES(med, sizeof(med)) && \
+ POKE_UINT8(subtype) && \
+ POKE_BYTES(value, (strlen(value) > 32) ? 32 : strlen(value)) && \
+ POKE_END_LLDP_TLV)) \
+ goto toobig; \
+ }
+
+ if (port->p_med_cap_enabled & LLDP_MED_CAP_IV) {
+ LLDP_INVENTORY(chassis->c_med_hw, LLDP_TLV_MED_IV_HW);
+ LLDP_INVENTORY(chassis->c_med_fw, LLDP_TLV_MED_IV_FW);
+ LLDP_INVENTORY(chassis->c_med_sw, LLDP_TLV_MED_IV_SW);
+ LLDP_INVENTORY(chassis->c_med_sn, LLDP_TLV_MED_IV_SN);
+ LLDP_INVENTORY(chassis->c_med_manuf, LLDP_TLV_MED_IV_MANUF);
+ LLDP_INVENTORY(chassis->c_med_model, LLDP_TLV_MED_IV_MODEL);
+ LLDP_INVENTORY(chassis->c_med_asset, LLDP_TLV_MED_IV_ASSET);
+ }
+
+ /* LLDP-MED location */
+ for (i = 0; i < LLDP_MED_LOCFORMAT_LAST; i++) {
+ if (port->p_med_location[i].format == i + 1) {
+ if (!(POKE_START_LLDP_TLV(LLDP_TLV_ORG) &&
+ POKE_BYTES(med, sizeof(med)) &&
+ POKE_UINT8(LLDP_TLV_MED_LOCATION) &&
+ POKE_UINT8(port->p_med_location[i].format) &&
+ POKE_BYTES(port->p_med_location[i].data,
+ port->p_med_location[i].data_len) &&
+ POKE_END_LLDP_TLV))
+ goto toobig;
+ }
+ }
+
+ /* LLDP-MED network policy */
+ for (i = 0; i < LLDP_MED_APPTYPE_LAST; i++) {
+ if (port->p_med_policy[i].type == i + 1) {
+ if (!(POKE_START_LLDP_TLV(LLDP_TLV_ORG) &&
+ POKE_BYTES(med, sizeof(med)) &&
+ POKE_UINT8(LLDP_TLV_MED_POLICY) &&
+ POKE_UINT32((
+ ((port->p_med_policy[i].type % (1 << 8))
+ << 24) |
+ ((port->p_med_policy[i].unknown % (1 << 1))
+ << 23) |
+ ((port->p_med_policy[i].tagged % (1 << 1))
+ << 22) |
+ /*((0 %(1<<
+ 1))<<21) |*/
+ ((port->p_med_policy[i].vid % (1 << 12))
+ << 9) |
+ ((port->p_med_policy[i].priority % (1 << 3))
+ << 6) |
+ ((port->p_med_policy[i].dscp % (1 << 6))
+ << 0))) &&
+ POKE_END_LLDP_TLV))
+ goto toobig;
+ }
+ }
+
+ /* LLDP-MED POE-MDI */
+ if ((port->p_med_power.devicetype == LLDP_MED_POW_TYPE_PSE) ||
+ (port->p_med_power.devicetype == LLDP_MED_POW_TYPE_PD)) {
+ int devicetype = 0, source = 0;
+ if (!(POKE_START_LLDP_TLV(LLDP_TLV_ORG) &&
+ POKE_BYTES(med, sizeof(med)) &&
+ POKE_UINT8(LLDP_TLV_MED_MDI)))
+ goto toobig;
+ switch (port->p_med_power.devicetype) {
+ case LLDP_MED_POW_TYPE_PSE:
+ devicetype = 0;
+ switch (port->p_med_power.source) {
+ case LLDP_MED_POW_SOURCE_PRIMARY:
+ source = 1;
+ break;
+ case LLDP_MED_POW_SOURCE_BACKUP:
+ source = 2;
+ break;
+ case LLDP_MED_POW_SOURCE_RESERVED:
+ source = 3;
+ break;
+ default:
+ source = 0;
+ break;
+ }
+ break;
+ case LLDP_MED_POW_TYPE_PD:
+ devicetype = 1;
+ switch (port->p_med_power.source) {
+ case LLDP_MED_POW_SOURCE_PSE:
+ source = 1;
+ break;
+ case LLDP_MED_POW_SOURCE_LOCAL:
+ source = 2;
+ break;
+ case LLDP_MED_POW_SOURCE_BOTH:
+ source = 3;
+ break;
+ default:
+ source = 0;
+ break;
+ }
+ break;
+ }
+ if (!(POKE_UINT8((((devicetype % (1 << 2)) << 6) |
+ ((source % (1 << 2)) << 4) |
+ ((port->p_med_power.priority % (1 << 4)) << 0))) &&
+ POKE_UINT16(port->p_med_power.val) &&
+ POKE_END_LLDP_TLV))
+ goto toobig;
+ }
+ }
+#endif
+
+#ifdef ENABLE_CUSTOM
+ TAILQ_FOREACH (custom, &port->p_custom_list, next) {
+ if (!(POKE_START_LLDP_TLV(LLDP_TLV_ORG) &&
+ POKE_BYTES(custom->oui, sizeof(custom->oui)) &&
+ POKE_UINT8(custom->subtype) &&
+ POKE_BYTES(custom->oui_info, custom->oui_info_len) &&
+ POKE_END_LLDP_TLV))
+ goto toobig;
+ }
+#endif
+
+end:
+ /* END */
+ if (!(POKE_START_LLDP_TLV(LLDP_TLV_END) && POKE_END_LLDP_TLV)) goto toobig;
+
+ if (interfaces_send_helper(global, hardware, (char *)packet, pos - packet) ==
+ -1) {
+ log_warn("lldp", "unable to send packet on real device for %s",
+ hardware->h_ifname);
+ free(packet);
+ return ENETDOWN;
+ }
+
+ hardware->h_tx_cnt++;
+
+ /* We assume that LLDP frame is the reference */
+ if (!shutdown &&
+ (frame = (struct lldpd_frame *)malloc(sizeof(int) + pos - packet)) !=
+ NULL) {
+ frame->size = pos - packet;
+ memcpy(&frame->frame, packet, frame->size);
+ if ((hardware->h_lport.p_lastframe == NULL) ||
+ (hardware->h_lport.p_lastframe->size != frame->size) ||
+ (memcmp(hardware->h_lport.p_lastframe->frame, frame->frame,
+ frame->size) != 0)) {
+ free(hardware->h_lport.p_lastframe);
+ hardware->h_lport.p_lastframe = frame;
+ hardware->h_lport.p_lastchange = time(NULL);
+ } else
+ free(frame);
+ }
+
+ free(packet);
+ return 0;
+
+toobig:
+ free(packet);
+ if (vlans > 0 && !without_vlans) {
+ /* Retry without VLANs */
+ return _lldp_send(global, hardware, c_id_subtype, c_id, c_id_len,
+ p_id_subtype, p_id, p_id_len, shutdown, 1);
+ }
+ log_info("lldp", "Cannot send LLDP packet for %s, too big message",
+ hardware->h_ifname);
+ return E2BIG;
+}
+
+/* Send a shutdown LLDPDU. */
+int
+lldp_send_shutdown(struct lldpd *global, struct lldpd_hardware *hardware)
+{
+ if (hardware->h_lchassis_previous_id == NULL ||
+ hardware->h_lport_previous_id == NULL)
+ return 0;
+ return _lldp_send(global, hardware, hardware->h_lchassis_previous_id_subtype,
+ hardware->h_lchassis_previous_id, hardware->h_lchassis_previous_id_len,
+ hardware->h_lport_previous_id_subtype, hardware->h_lport_previous_id,
+ hardware->h_lport_previous_id_len, 1, 0);
+}
+
+int
+lldp_send(struct lldpd *global, struct lldpd_hardware *hardware)
+{
+ struct lldpd_port *port = &hardware->h_lport;
+ struct lldpd_chassis *chassis = port->p_chassis;
+ int ret;
+
+ /* Check if we have a change. */
+ if (hardware->h_lchassis_previous_id != NULL &&
+ hardware->h_lport_previous_id != NULL &&
+ (hardware->h_lchassis_previous_id_subtype != chassis->c_id_subtype ||
+ hardware->h_lchassis_previous_id_len != chassis->c_id_len ||
+ hardware->h_lport_previous_id_subtype != port->p_id_subtype ||
+ hardware->h_lport_previous_id_len != port->p_id_len ||
+ memcmp(hardware->h_lchassis_previous_id, chassis->c_id,
+ chassis->c_id_len) ||
+ memcmp(hardware->h_lport_previous_id, port->p_id, port->p_id_len))) {
+ log_info("lldp",
+ "MSAP has changed for port %s, sending a shutdown LLDPDU",
+ hardware->h_ifname);
+ if ((ret = lldp_send_shutdown(global, hardware)) != 0) return ret;
+ }
+
+ log_debug("lldp", "send LLDP PDU to %s", hardware->h_ifname);
+
+ if ((ret = _lldp_send(global, hardware, chassis->c_id_subtype, chassis->c_id,
+ chassis->c_id_len, port->p_id_subtype, port->p_id, port->p_id_len, 0,
+ 0)) != 0)
+ return ret;
+
+ /* Record current chassis and port ID */
+ free(hardware->h_lchassis_previous_id);
+ hardware->h_lchassis_previous_id_subtype = chassis->c_id_subtype;
+ hardware->h_lchassis_previous_id_len = chassis->c_id_len;
+ if ((hardware->h_lchassis_previous_id = malloc(chassis->c_id_len)) != NULL)
+ memcpy(hardware->h_lchassis_previous_id, chassis->c_id,
+ chassis->c_id_len);
+ free(hardware->h_lport_previous_id);
+ hardware->h_lport_previous_id_subtype = port->p_id_subtype;
+ hardware->h_lport_previous_id_len = port->p_id_len;
+ if ((hardware->h_lport_previous_id = malloc(port->p_id_len)) != NULL)
+ memcpy(hardware->h_lport_previous_id, port->p_id, port->p_id_len);
+
+ return 0;
+}
+
+#define CHECK_TLV_SIZE(x, name) \
+ do { \
+ if (tlv_size < (x)) { \
+ log_warnx("lldp", name " TLV too short received on %s", hardware->h_ifname); \
+ goto malformed; \
+ } \
+ } while (0)
+#define CHECK_TLV_MAX_SIZE(x, name) \
+ do { \
+ if (tlv_size > (x)) { \
+ log_warnx("lldp", name " TLV too large received on %s", hardware->h_ifname); \
+ goto malformed; \
+ } \
+ } while (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;
+ char lldpaddr[ETHER_ADDR_LEN];
+ const char dot1[] = LLDP_TLV_ORG_DOT1;
+ const char dot3[] = LLDP_TLV_ORG_DOT3;
+ const char med[] = LLDP_TLV_ORG_MED;
+ const char dcbx[] = LLDP_TLV_ORG_DCBX;
+ unsigned char orgid[3];
+ int length, gotend = 0, ttl_received = 0;
+ int tlv_size, tlv_type, tlv_subtype, tlv_count = 0;
+ u_int8_t *pos, *tlv;
+ char *b;
+#ifdef ENABLE_DOT1
+ struct lldpd_vlan *vlan = NULL;
+ int vlan_len;
+ struct lldpd_ppvid *ppvid;
+ struct lldpd_pi *pi = NULL;
+#endif
+ struct lldpd_mgmt *mgmt;
+ int af;
+ u_int8_t addr_str_length, addr_str_buffer[32] = { 0 };
+ u_int8_t addr_family, addr_length, *addr_ptr, iface_subtype;
+ u_int32_t iface_number, iface;
+ int unrecognized;
+#ifdef ENABLE_CUSTOM
+ struct lldpd_custom *custom = NULL;
+#endif
+
+ log_debug("lldp", "receive LLDP PDU on %s", hardware->h_ifname);
+
+ if ((chassis = calloc(1, sizeof(struct lldpd_chassis))) == NULL) {
+ log_warn("lldp", "failed to allocate remote chassis");
+ return -1;
+ }
+ TAILQ_INIT(&chassis->c_mgmt);
+ if ((port = calloc(1, sizeof(struct lldpd_port))) == NULL) {
+ log_warn("lldp", "failed to allocate remote port");
+ free(chassis);
+ return -1;
+ }
+#ifdef ENABLE_DOT1
+ TAILQ_INIT(&port->p_vlans);
+ TAILQ_INIT(&port->p_ppvids);
+ TAILQ_INIT(&port->p_pids);
+#endif
+#ifdef ENABLE_CUSTOM
+ TAILQ_INIT(&port->p_custom_list);
+#endif
+
+ length = s;
+ pos = (u_int8_t *)frame;
+
+ if (length < 2 * ETHER_ADDR_LEN + sizeof(u_int16_t)) {
+ log_warnx("lldp", "too short frame received on %s", hardware->h_ifname);
+ goto malformed;
+ }
+ PEEK_BYTES(lldpaddr, ETHER_ADDR_LEN);
+ if (memcmp(lldpaddr, (const char[])LLDP_ADDR_NEAREST_BRIDGE, ETHER_ADDR_LEN) &&
+ memcmp(lldpaddr, (const char[])LLDP_ADDR_NEAREST_NONTPMR_BRIDGE,
+ ETHER_ADDR_LEN) &&
+ memcmp(lldpaddr, (const char[])LLDP_ADDR_NEAREST_CUSTOMER_BRIDGE,
+ ETHER_ADDR_LEN)) {
+ log_info("lldp",
+ "frame not targeted at LLDP multicast address received on %s",
+ hardware->h_ifname);
+ goto malformed;
+ }
+ PEEK_DISCARD(ETHER_ADDR_LEN); /* Skip source address */
+ if (PEEK_UINT16 != ETH_P_LLDP) {
+ log_info("lldp", "non LLDP frame received on %s", hardware->h_ifname);
+ goto malformed;
+ }
+
+ while (length && (!gotend)) {
+ if (length < 2) {
+ log_warnx("lldp", "tlv header too short received on %s",
+ hardware->h_ifname);
+ goto malformed;
+ }
+ tlv_size = PEEK_UINT16;
+ tlv_type = tlv_size >> 9;
+ tlv_size = tlv_size & 0x1ff;
+ (void)PEEK_SAVE(tlv);
+ if (length < tlv_size) {
+ log_warnx("lldp", "frame too short for tlv received on %s",
+ hardware->h_ifname);
+ goto malformed;
+ }
+ /* Check order for mandatory TLVs */
+ tlv_count++;
+ switch (tlv_type) {
+ case LLDP_TLV_CHASSIS_ID:
+ if (tlv_count != 1) {
+ log_warnx("lldp",
+ "Chassis ID TLV should be first on %s, but it is on position %d",
+ hardware->h_ifname, tlv_count);
+ goto malformed;
+ }
+ break;
+ case LLDP_TLV_PORT_ID:
+ if (tlv_count != 2) {
+ log_warnx("lldp",
+ "Port ID TLV should be second on %s, but it is on position %d",
+ hardware->h_ifname, tlv_count);
+ goto malformed;
+ }
+ break;
+ case LLDP_TLV_TTL:
+ if (tlv_count != 3) {
+ log_warnx("lldp",
+ "TTL TLV should be third on %s, but it is on position %d",
+ hardware->h_ifname, tlv_count);
+ goto malformed;
+ }
+ break;
+ }
+
+ switch (tlv_type) {
+ case LLDP_TLV_END:
+ if (tlv_size != 0) {
+ log_warnx("lldp",
+ "lldp end received with size not null on %s",
+ hardware->h_ifname);
+ goto malformed;
+ }
+ if (length)
+ log_debug("lldp", "extra data after lldp end on %s",
+ hardware->h_ifname);
+ gotend = 1;
+ break;
+ case LLDP_TLV_CHASSIS_ID:
+ case LLDP_TLV_PORT_ID:
+ CHECK_TLV_SIZE(2, "Port/Chassis Id");
+ CHECK_TLV_MAX_SIZE(256, "Port/Chassis Id");
+ tlv_subtype = PEEK_UINT8;
+ if ((tlv_subtype == 0) || (tlv_subtype > 7)) {
+ log_warnx("lldp",
+ "unknown subtype for tlv id received on %s",
+ hardware->h_ifname);
+ goto malformed;
+ }
+ if ((b = (char *)calloc(1, tlv_size - 1)) == NULL) {
+ log_warn("lldp",
+ "unable to allocate memory for id tlv "
+ "received on %s",
+ hardware->h_ifname);
+ goto malformed;
+ }
+ PEEK_BYTES(b, tlv_size - 1);
+ if (tlv_type == LLDP_TLV_PORT_ID) {
+ if (port->p_id != NULL) {
+ log_warnx("lldp",
+ "Port ID TLV received twice on %s",
+ hardware->h_ifname);
+ free(b);
+ goto malformed;
+ }
+ port->p_id_subtype = tlv_subtype;
+ port->p_id = b;
+ port->p_id_len = tlv_size - 1;
+ } else {
+ if (chassis->c_id != NULL) {
+ log_warnx("lldp",
+ "Chassis ID TLV received twice on %s",
+ hardware->h_ifname);
+ free(b);
+ goto malformed;
+ }
+ chassis->c_id_subtype = tlv_subtype;
+ chassis->c_id = b;
+ chassis->c_id_len = tlv_size - 1;
+ }
+ break;
+ case LLDP_TLV_TTL:
+ if (ttl_received) {
+ log_warnx("lldp", "TTL TLV received twice on %s",
+ hardware->h_ifname);
+ goto malformed;
+ }
+ CHECK_TLV_SIZE(2, "TTL");
+ port->p_ttl = PEEK_UINT16;
+ ttl_received = 1;
+ break;
+ case LLDP_TLV_PORT_DESCR:
+ case LLDP_TLV_SYSTEM_NAME:
+ case LLDP_TLV_SYSTEM_DESCR:
+ if (tlv_size < 1) {
+ log_debug("lldp", "empty tlv received on %s",
+ hardware->h_ifname);
+ break;
+ }
+ if ((b = (char *)calloc(1, tlv_size + 1)) == NULL) {
+ log_warn("lldp",
+ "unable to allocate memory for string tlv "
+ "received on %s",
+ hardware->h_ifname);
+ goto malformed;
+ }
+ PEEK_BYTES(b, tlv_size);
+ switch (tlv_type) {
+ case LLDP_TLV_PORT_DESCR:
+ free(port->p_descr);
+ port->p_descr = b;
+ break;
+ case LLDP_TLV_SYSTEM_NAME:
+ free(chassis->c_name);
+ chassis->c_name = b;
+ break;
+ case LLDP_TLV_SYSTEM_DESCR:
+ free(chassis->c_descr);
+ chassis->c_descr = b;
+ break;
+ default:
+ /* unreachable */
+ free(b);
+ break;
+ }
+ break;
+ case LLDP_TLV_SYSTEM_CAP:
+ CHECK_TLV_SIZE(4, "System capabilities");
+ chassis->c_cap_available = PEEK_UINT16;
+ chassis->c_cap_enabled = PEEK_UINT16;
+ break;
+ case LLDP_TLV_MGMT_ADDR:
+ CHECK_TLV_SIZE(1, "Management address");
+ addr_str_length = PEEK_UINT8;
+ if (addr_str_length > sizeof(addr_str_buffer)) {
+ log_warnx("lldp", "too large management address on %s",
+ hardware->h_ifname);
+ goto malformed;
+ }
+ CHECK_TLV_SIZE(1 + addr_str_length, "Management address");
+ PEEK_BYTES(addr_str_buffer, addr_str_length);
+ addr_length = addr_str_length - 1;
+ addr_family = addr_str_buffer[0];
+ addr_ptr = &addr_str_buffer[1];
+ CHECK_TLV_SIZE(1 + addr_str_length + 5, "Management address");
+ iface_subtype = PEEK_UINT8;
+ iface_number = PEEK_UINT32;
+
+ af = lldpd_af_from_lldp_proto(addr_family);
+ if (af == LLDPD_AF_UNSPEC) break;
+ if (iface_subtype == LLDP_MGMT_IFACE_IFINDEX)
+ iface = iface_number;
+ else
+ iface = 0;
+ mgmt = lldpd_alloc_mgmt(af, addr_ptr, addr_length, iface);
+ if (mgmt == NULL) {
+ if (errno == ENOMEM)
+ log_warn("lldp",
+ "unable to allocate memory "
+ "for management address");
+ else
+ log_warn("lldp",
+ "too large management address "
+ "received on %s",
+ hardware->h_ifname);
+ goto malformed;
+ }
+ TAILQ_INSERT_TAIL(&chassis->c_mgmt, mgmt, m_entries);
+ break;
+ case LLDP_TLV_ORG:
+ CHECK_TLV_SIZE(1 + (int)sizeof(orgid), "Organisational");
+ PEEK_BYTES(orgid, sizeof(orgid));
+ unrecognized = 0;
+ tlv_subtype = PEEK_UINT8;
+ if (memcmp(dot1, orgid, sizeof(orgid)) == 0) {
+#ifndef ENABLE_DOT1
+ unrecognized = 1;
+#else
+ /* Dot1 */
+ switch (tlv_subtype) {
+ case LLDP_TLV_DOT1_VLANNAME:
+ CHECK_TLV_SIZE(7, "VLAN");
+ if ((vlan = (struct lldpd_vlan *)calloc(1,
+ sizeof(struct lldpd_vlan))) == NULL) {
+ log_warn("lldp",
+ "unable to alloc vlan "
+ "structure for "
+ "tlv received on %s",
+ hardware->h_ifname);
+ goto malformed;
+ }
+ vlan->v_vid = PEEK_UINT16;
+ vlan_len = PEEK_UINT8;
+ CHECK_TLV_SIZE(7 + vlan_len, "VLAN");
+ if ((vlan->v_name = (char *)calloc(1,
+ vlan_len + 1)) == NULL) {
+ log_warn("lldp",
+ "unable to alloc vlan name for "
+ "tlv received on %s",
+ hardware->h_ifname);
+ goto malformed;
+ }
+ PEEK_BYTES(vlan->v_name, vlan_len);
+ TAILQ_INSERT_TAIL(&port->p_vlans, vlan,
+ v_entries);
+ vlan = NULL;
+ break;
+ case LLDP_TLV_DOT1_PVID:
+ CHECK_TLV_SIZE(6, "PVID");
+ port->p_pvid = PEEK_UINT16;
+ break;
+ case LLDP_TLV_DOT1_PPVID:
+ CHECK_TLV_SIZE(7, "PPVID");
+ /* validation needed */
+ /* PPVID has to be unique if more than
+ one PPVID TLVs are received -
+ discard if duplicate */
+ /* if support bit is not set and
+ enabled bit is set - PPVID TLV is
+ considered error and discarded */
+ /* if PPVID > 4096 - bad and discard */
+ if ((ppvid = (struct lldpd_ppvid *)calloc(1,
+ sizeof(struct lldpd_ppvid))) == NULL) {
+ log_warn("lldp",
+ "unable to alloc ppvid "
+ "structure for "
+ "tlv received on %s",
+ hardware->h_ifname);
+ goto malformed;
+ }
+ ppvid->p_cap_status = PEEK_UINT8;
+ ppvid->p_ppvid = PEEK_UINT16;
+ TAILQ_INSERT_TAIL(&port->p_ppvids, ppvid,
+ p_entries);
+ break;
+ case LLDP_TLV_DOT1_PI:
+ /* validation needed */
+ /* PI has to be unique if more than
+ one PI TLVs are received - discard
+ if duplicate ?? */
+ CHECK_TLV_SIZE(5, "PI");
+ if ((pi = (struct lldpd_pi *)calloc(1,
+ sizeof(struct lldpd_pi))) == NULL) {
+ log_warn("lldp",
+ "unable to alloc PI "
+ "structure for "
+ "tlv received on %s",
+ hardware->h_ifname);
+ goto malformed;
+ }
+ pi->p_pi_len = PEEK_UINT8;
+ CHECK_TLV_SIZE(5 + pi->p_pi_len, "PI");
+ if ((pi->p_pi = (char *)calloc(1,
+ pi->p_pi_len)) == NULL) {
+ log_warn("lldp",
+ "unable to alloc pid name for "
+ "tlv received on %s",
+ hardware->h_ifname);
+ goto malformed;
+ }
+ PEEK_BYTES(pi->p_pi, pi->p_pi_len);
+ TAILQ_INSERT_TAIL(&port->p_pids, pi, p_entries);
+ pi = NULL;
+ break;
+ default:
+ /* Unknown Dot1 TLV, ignore it */
+ unrecognized = 1;
+ }
+#endif
+ } else if (memcmp(dot3, orgid, sizeof(orgid)) == 0) {
+#ifndef ENABLE_DOT3
+ unrecognized = 1;
+#else
+ /* Dot3 */
+ switch (tlv_subtype) {
+ case LLDP_TLV_DOT3_MAC:
+ CHECK_TLV_SIZE(9, "MAC/PHY");
+ port->p_macphy.autoneg_support = PEEK_UINT8;
+ port->p_macphy.autoneg_enabled =
+ (port->p_macphy.autoneg_support & 0x2) >> 1;
+ port->p_macphy.autoneg_support =
+ port->p_macphy.autoneg_support & 0x1;
+ port->p_macphy.autoneg_advertised = PEEK_UINT16;
+ port->p_macphy.mau_type = PEEK_UINT16;
+ break;
+ case LLDP_TLV_DOT3_LA:
+ CHECK_TLV_SIZE(9, "Link aggregation");
+ PEEK_DISCARD_UINT8;
+ port->p_aggregid = PEEK_UINT32;
+ break;
+ case LLDP_TLV_DOT3_MFS:
+ CHECK_TLV_SIZE(6, "MFS");
+ port->p_mfs = PEEK_UINT16;
+ break;
+ case LLDP_TLV_DOT3_POWER:
+ CHECK_TLV_SIZE(7, "Power");
+ port->p_power.devicetype = PEEK_UINT8;
+ port->p_power.supported =
+ (port->p_power.devicetype & 0x2) >> 1;
+ port->p_power.enabled =
+ (port->p_power.devicetype & 0x4) >> 2;
+ port->p_power.paircontrol =
+ (port->p_power.devicetype & 0x8) >> 3;
+ port->p_power.devicetype =
+ (port->p_power.devicetype & 0x1) ?
+ LLDP_DOT3_POWER_PSE :
+ LLDP_DOT3_POWER_PD;
+ port->p_power.pairs = PEEK_UINT8;
+ port->p_power.class = PEEK_UINT8;
+ /* 802.3at? */
+ if (tlv_size >= 12) {
+ port->p_power.powertype = PEEK_UINT8;
+ port->p_power.source =
+ (port->p_power.powertype &
+ (1 << 5 | 1 << 4)) >>
+ 4;
+ port->p_power.priority =
+ (port->p_power.powertype &
+ (1 << 1 | 1 << 0));
+ port->p_power.powertype =
+ (port->p_power.powertype &
+ (1 << 7)) ?
+ LLDP_DOT3_POWER_8023AT_TYPE1 :
+ LLDP_DOT3_POWER_8023AT_TYPE2;
+ port->p_power.requested = PEEK_UINT16;
+ port->p_power.allocated = PEEK_UINT16;
+ } else
+ port->p_power.powertype =
+ LLDP_DOT3_POWER_8023AT_OFF;
+ /* 802.3bt? */
+ if (tlv_size >= 29) {
+ port->p_power.requested_a = PEEK_UINT16;
+ port->p_power.requested_b = PEEK_UINT16;
+ port->p_power.allocated_a = PEEK_UINT16;
+ port->p_power.allocated_b = PEEK_UINT16;
+ port->p_power.pse_status = PEEK_UINT16;
+ port->p_power.pd_status =
+ (port->p_power.pse_status &
+ (1 << 13 | 1 << 12)) >>
+ 12;
+ port->p_power.pse_pairs_ext =
+ (port->p_power.pse_status &
+ (1 << 11 | 1 << 10)) >>
+ 10;
+ port->p_power.class_a =
+ (port->p_power.pse_status &
+ (1 << 9 | 1 << 8 | 1 << 7)) >>
+ 7;
+ port->p_power.class_b =
+ (port->p_power.pse_status &
+ (1 << 6 | 1 << 5 | 1 << 4)) >>
+ 4;
+ port->p_power.class_ext =
+ (port->p_power.pse_status & 0xf);
+ port->p_power.pse_status =
+ (port->p_power.pse_status &
+ (1 << 15 | 1 << 14)) >>
+ 14;
+ port->p_power.type_ext = PEEK_UINT8;
+ port->p_power.pd_load =
+ (port->p_power.type_ext & 0x1);
+ port->p_power.type_ext =
+ ((port->p_power.type_ext &
+ (1 << 3 | 1 << 2 | 1 << 1)) +
+ 1);
+ port->p_power.pse_max = PEEK_UINT16;
+ } else {
+ port->p_power.type_ext =
+ LLDP_DOT3_POWER_8023BT_OFF;
+ }
+ break;
+ default:
+ /* Unknown Dot3 TLV, ignore it */
+ unrecognized = 1;
+ }
+#endif
+ } else if (memcmp(med, orgid, sizeof(orgid)) == 0) {
+ /* LLDP-MED */
+#ifndef ENABLE_LLDPMED
+ unrecognized = 1;
+#else
+ u_int32_t policy;
+ unsigned loctype;
+ unsigned power;
+
+ switch (tlv_subtype) {
+ case LLDP_TLV_MED_CAP:
+ CHECK_TLV_SIZE(7, "LLDP-MED capabilities");
+ chassis->c_med_cap_available = PEEK_UINT16;
+ chassis->c_med_type = PEEK_UINT8;
+ port->p_med_cap_enabled |= LLDP_MED_CAP_CAP;
+ break;
+ case LLDP_TLV_MED_POLICY:
+ CHECK_TLV_SIZE(8, "LLDP-MED policy");
+ policy = PEEK_UINT32;
+ if (((policy >> 24) < 1) ||
+ ((policy >> 24) > LLDP_MED_APPTYPE_LAST)) {
+ log_info("lldp",
+ "unknown policy field %d "
+ "received on %s",
+ policy, hardware->h_ifname);
+ break;
+ }
+ port->p_med_policy[(policy >> 24) - 1].type =
+ (policy >> 24);
+ port->p_med_policy[(policy >> 24) - 1].unknown =
+ ((policy & 0x800000) != 0);
+ port->p_med_policy[(policy >> 24) - 1].tagged =
+ ((policy & 0x400000) != 0);
+ port->p_med_policy[(policy >> 24) - 1].vid =
+ (policy & 0x001FFE00) >> 9;
+ port->p_med_policy[(policy >> 24) - 1]
+ .priority = (policy & 0x1C0) >> 6;
+ port->p_med_policy[(policy >> 24) - 1].dscp =
+ policy & 0x3F;
+ port->p_med_cap_enabled |= LLDP_MED_CAP_POLICY;
+ break;
+ case LLDP_TLV_MED_LOCATION:
+ CHECK_TLV_SIZE(5, "LLDP-MED Location");
+ loctype = PEEK_UINT8;
+ if ((loctype < 1) ||
+ (loctype > LLDP_MED_LOCFORMAT_LAST)) {
+ log_info("lldp",
+ "unknown location type "
+ "received on %s",
+ hardware->h_ifname);
+ break;
+ }
+ free(port->p_med_location[loctype - 1].data);
+ if ((port->p_med_location[loctype - 1].data =
+ (char *)malloc(tlv_size - 5)) ==
+ NULL) {
+ log_warn("lldp",
+ "unable to allocate memory "
+ "for LLDP-MED location for "
+ "frame received on %s",
+ hardware->h_ifname);
+ goto malformed;
+ }
+ PEEK_BYTES(
+ port->p_med_location[loctype - 1].data,
+ tlv_size - 5);
+ port->p_med_location[loctype - 1].data_len =
+ tlv_size - 5;
+ port->p_med_location[loctype - 1].format =
+ loctype;
+ port->p_med_cap_enabled |=
+ LLDP_MED_CAP_LOCATION;
+ break;
+ case LLDP_TLV_MED_MDI:
+ CHECK_TLV_SIZE(7, "LLDP-MED PoE-MDI");
+ power = PEEK_UINT8;
+ switch (power & 0xC0) {
+ case 0x0:
+ port->p_med_power.devicetype =
+ LLDP_MED_POW_TYPE_PSE;
+ port->p_med_cap_enabled |=
+ LLDP_MED_CAP_MDI_PSE;
+ switch (power & 0x30) {
+ case 0x0:
+ port->p_med_power.source =
+ LLDP_MED_POW_SOURCE_UNKNOWN;
+ break;
+ case 0x10:
+ port->p_med_power.source =
+ LLDP_MED_POW_SOURCE_PRIMARY;
+ break;
+ case 0x20:
+ port->p_med_power.source =
+ LLDP_MED_POW_SOURCE_BACKUP;
+ break;
+ default:
+ port->p_med_power.source =
+ LLDP_MED_POW_SOURCE_RESERVED;
+ }
+ break;
+ case 0x40:
+ port->p_med_power.devicetype =
+ LLDP_MED_POW_TYPE_PD;
+ port->p_med_cap_enabled |=
+ LLDP_MED_CAP_MDI_PD;
+ switch (power & 0x30) {
+ case 0x0:
+ port->p_med_power.source =
+ LLDP_MED_POW_SOURCE_UNKNOWN;
+ break;
+ case 0x10:
+ port->p_med_power.source =
+ LLDP_MED_POW_SOURCE_PSE;
+ break;
+ case 0x20:
+ port->p_med_power.source =
+ LLDP_MED_POW_SOURCE_LOCAL;
+ break;
+ default:
+ port->p_med_power.source =
+ LLDP_MED_POW_SOURCE_BOTH;
+ }
+ break;
+ default:
+ port->p_med_power.devicetype =
+ LLDP_MED_POW_TYPE_RESERVED;
+ }
+ if ((power & 0x0F) > LLDP_MED_POW_PRIO_LOW)
+ port->p_med_power.priority =
+ LLDP_MED_POW_PRIO_UNKNOWN;
+ else
+ port->p_med_power.priority =
+ power & 0x0F;
+ port->p_med_power.val = PEEK_UINT16;
+ break;
+ case LLDP_TLV_MED_IV_HW:
+ case LLDP_TLV_MED_IV_SW:
+ case LLDP_TLV_MED_IV_FW:
+ case LLDP_TLV_MED_IV_SN:
+ case LLDP_TLV_MED_IV_MANUF:
+ case LLDP_TLV_MED_IV_MODEL:
+ case LLDP_TLV_MED_IV_ASSET:
+ if (tlv_size <= 4)
+ b = NULL;
+ else {
+ if ((b = (char *)malloc(
+ tlv_size - 3)) == NULL) {
+ log_warn("lldp",
+ "unable to allocate "
+ "memory for LLDP-MED "
+ "inventory for frame "
+ "received on %s",
+ hardware->h_ifname);
+ goto malformed;
+ }
+ PEEK_BYTES(b, tlv_size - 4);
+ b[tlv_size - 4] = '\0';
+ }
+ switch (tlv_subtype) {
+ case LLDP_TLV_MED_IV_HW:
+ free(chassis->c_med_hw);
+ chassis->c_med_hw = b;
+ break;
+ case LLDP_TLV_MED_IV_FW:
+ free(chassis->c_med_fw);
+ chassis->c_med_fw = b;
+ break;
+ case LLDP_TLV_MED_IV_SW:
+ free(chassis->c_med_sw);
+ chassis->c_med_sw = b;
+ break;
+ case LLDP_TLV_MED_IV_SN:
+ free(chassis->c_med_sn);
+ chassis->c_med_sn = b;
+ break;
+ case LLDP_TLV_MED_IV_MANUF:
+ free(chassis->c_med_manuf);
+ chassis->c_med_manuf = b;
+ break;
+ case LLDP_TLV_MED_IV_MODEL:
+ free(chassis->c_med_model);
+ chassis->c_med_model = b;
+ break;
+ case LLDP_TLV_MED_IV_ASSET:
+ free(chassis->c_med_asset);
+ chassis->c_med_asset = b;
+ break;
+ default:
+ /* unreachable */
+ free(b);
+ break;
+ }
+ port->p_med_cap_enabled |= LLDP_MED_CAP_IV;
+ break;
+ default:
+ /* Unknown LLDP MED, ignore it */
+ hardware->h_rx_unrecognized_cnt++;
+ }
+#endif /* ENABLE_LLDPMED */
+ } else if (memcmp(dcbx, orgid, sizeof(orgid)) == 0) {
+ log_debug("lldp",
+ "unsupported DCBX tlv received on %s - ignore",
+ hardware->h_ifname);
+ unrecognized = 1;
+ } else {
+ log_debug("lldp",
+ "unknown org tlv [%02x:%02x:%02x] received on %s",
+ orgid[0], orgid[1], orgid[2], hardware->h_ifname);
+ unrecognized = 1;
+ }
+ if (unrecognized) {
+ hardware->h_rx_unrecognized_cnt++;
+#ifdef ENABLE_CUSTOM
+ custom = (struct lldpd_custom *)calloc(1,
+ sizeof(struct lldpd_custom));
+ if (!custom) {
+ log_warn("lldp",
+ "unable to allocate memory for custom TLV");
+ goto malformed;
+ }
+ custom->oui_info_len = tlv_size > 4 ? tlv_size - 4 : 0;
+ memcpy(custom->oui, orgid, sizeof(custom->oui));
+ custom->subtype = tlv_subtype;
+ if (custom->oui_info_len > 0) {
+ custom->oui_info = malloc(custom->oui_info_len);
+ if (!custom->oui_info) {
+ log_warn("lldp",
+ "unable to allocate memory for custom TLV data");
+ goto malformed;
+ }
+ PEEK_BYTES(custom->oui_info,
+ custom->oui_info_len);
+ }
+ TAILQ_INSERT_TAIL(&port->p_custom_list, custom, next);
+ custom = NULL;
+#endif
+ }
+ break;
+ default:
+ log_warnx("lldp", "unknown tlv (%d) received on %s", tlv_type,
+ hardware->h_ifname);
+ hardware->h_rx_unrecognized_cnt++;
+ break;
+ }
+ if (pos > tlv + tlv_size) {
+ log_warnx("lldp", "BUG: already past TLV!");
+ goto malformed;
+ }
+ PEEK_DISCARD(tlv + tlv_size - pos);
+ }
+
+ /* Some random check */
+ if ((chassis->c_id == NULL) || (port->p_id == NULL) || (!ttl_received) ||
+ (gotend == 0)) {
+ log_warnx("lldp",
+ "some mandatory tlv are missing for frame received on %s",
+ hardware->h_ifname);
+ goto malformed;
+ }
+ *newchassis = chassis;
+ *newport = port;
+ return 1;
+malformed:
+#ifdef ENABLE_CUSTOM
+ free(custom);
+#endif
+#ifdef ENABLE_DOT1
+ free(vlan);
+ free(pi);
+#endif
+ lldpd_chassis_cleanup(chassis, 1);
+ lldpd_port_cleanup(port, 1);
+ free(port);
+ return -1;
+}
diff --git a/src/daemon/protocols/sonmp.c b/src/daemon/protocols/sonmp.c
new file mode 100644
index 0000000..34ebcd7
--- /dev/null
+++ b/src/daemon/protocols/sonmp.c
@@ -0,0 +1,410 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/*
+ * Copyright (c) 2008 Vincent Bernat <bernat@luffy.cx>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "../lldpd.h"
+#include "../frame.h"
+
+#ifdef ENABLE_SONMP
+
+# include <stdio.h>
+# include <unistd.h>
+# include <errno.h>
+# include <arpa/inet.h>
+
+static 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_hardware *hardware)
+{
+ const u_int8_t mcastaddr[] = SONMP_MULTICAST_ADDR;
+ const u_int8_t llcorg[] = LLC_ORG_NORTEL;
+ struct lldpd_chassis *chassis;
+ struct lldpd_mgmt *mgmt;
+ u_int8_t *packet, *pos, *pos_pid, *end;
+ int length;
+ struct in_addr address;
+
+ log_debug("sonmp", "send SONMP PDU to %s", hardware->h_ifname);
+
+ chassis = hardware->h_lport.p_chassis;
+ length = hardware->h_mtu;
+ if ((packet = (u_int8_t *)calloc(1, length)) == NULL) return ENOMEM;
+ pos = packet;
+
+ /* Ethernet header */
+ if (!(
+ /* SONMP multicast address as target */
+ POKE_BYTES(mcastaddr, sizeof(mcastaddr)) &&
+ /* Source MAC addresss */
+ POKE_BYTES(&hardware->h_lladdr, ETHER_ADDR_LEN) &&
+ /* SONMP frame is of fixed size */
+ POKE_UINT16(SONMP_SIZE)))
+ goto toobig;
+
+ /* LLC header */
+ if (!(
+ /* DSAP and SSAP */
+ POKE_UINT8(0xaa) && POKE_UINT8(0xaa) &&
+ /* Control field */
+ POKE_UINT8(0x03) &&
+ /* ORG */
+ POKE_BYTES(llcorg, sizeof(llcorg)) &&
+ POKE_SAVE(pos_pid) && /* We will modify PID later to
+ create a new frame */
+ POKE_UINT16(LLC_PID_SONMP_HELLO)))
+ goto toobig;
+
+ address.s_addr = htonl(INADDR_ANY);
+ TAILQ_FOREACH (mgmt, &chassis->c_mgmt, m_entries) {
+ if (mgmt->m_family == LLDPD_AF_IPV4) {
+ address.s_addr = mgmt->m_addr.inet.s_addr;
+ }
+ break;
+ }
+
+ /* SONMP */
+ if (!(
+ /* Our IP address */
+ POKE_BYTES(&address, sizeof(struct in_addr)) &&
+ /* Segment on three bytes, we don't have slots, so we
+ skip the first two bytes */
+ POKE_UINT16(0) && POKE_UINT8(hardware->h_ifindex) &&
+ POKE_UINT8(1) && /* Chassis: Other */
+ POKE_UINT8(12) && /* Back: Ethernet, Fast Ethernet and Gigabit */
+ POKE_UINT8(SONMP_TOPOLOGY_NEW) && /* Should work. We have no state */
+ POKE_UINT8(1) && /* Links: Dunno what it is */
+ POKE_SAVE(end)))
+ goto toobig;
+
+ if (interfaces_send_helper(global, hardware, (char *)packet, end - packet) ==
+ -1) {
+ log_warn("sonmp", "unable to send packet on real device for %s",
+ hardware->h_ifname);
+ free(packet);
+ return ENETDOWN;
+ }
+
+ POKE_RESTORE(pos_pid); /* Modify LLC PID */
+ (void)POKE_UINT16(LLC_PID_SONMP_FLATNET);
+ POKE_RESTORE(packet); /* Go to the beginning */
+ PEEK_DISCARD(ETHER_ADDR_LEN - 1); /* Modify the last byte of the MAC address */
+ (void)POKE_UINT8(1);
+
+ if (interfaces_send_helper(global, hardware, (char *)packet, end - packet) ==
+ -1) {
+ log_warn("sonmp",
+ "unable to send second SONMP packet on real device for %s",
+ hardware->h_ifname);
+ free(packet);
+ return ENETDOWN;
+ }
+
+ free(packet);
+ hardware->h_tx_cnt++;
+ return 0;
+toobig:
+ free(packet);
+ return -1;
+}
+
+int
+sonmp_decode(struct lldpd *cfg, char *frame, int s, struct lldpd_hardware *hardware,
+ struct lldpd_chassis **newchassis, struct lldpd_port **newport)
+{
+ const u_int8_t mcastaddr[] = SONMP_MULTICAST_ADDR;
+ struct lldpd_chassis *chassis;
+ struct lldpd_port *port;
+ struct lldpd_mgmt *mgmt;
+ int length, i;
+ u_int8_t *pos;
+ u_int8_t seg[3], rchassis;
+ struct in_addr address;
+
+ log_debug("sonmp", "decode SONMP PDU from %s", hardware->h_ifname);
+
+ if ((chassis = calloc(1, sizeof(struct lldpd_chassis))) == NULL) {
+ log_warn("sonmp", "failed to allocate remote chassis");
+ return -1;
+ }
+ TAILQ_INIT(&chassis->c_mgmt);
+ if ((port = calloc(1, sizeof(struct lldpd_port))) == NULL) {
+ log_warn("sonmp", "failed to allocate remote port");
+ free(chassis);
+ return -1;
+ }
+# ifdef ENABLE_DOT1
+ TAILQ_INIT(&port->p_vlans);
+# endif
+
+ length = s;
+ pos = (u_int8_t *)frame;
+ if (length < SONMP_SIZE + 2 * ETHER_ADDR_LEN + sizeof(u_int16_t)) {
+ log_warnx("sonmp", "too short SONMP frame received on %s",
+ hardware->h_ifname);
+ goto malformed;
+ }
+ if (PEEK_CMP(mcastaddr, sizeof(mcastaddr)) != 0)
+ /* There is two multicast address. We just handle only one of
+ * them. */
+ goto malformed;
+ /* We skip to LLC PID */
+ PEEK_DISCARD(ETHER_ADDR_LEN);
+ PEEK_DISCARD_UINT16;
+ PEEK_DISCARD(6);
+ if (PEEK_UINT16 != LLC_PID_SONMP_HELLO) {
+ log_debug("sonmp", "incorrect LLC protocol ID received for SONMP 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) {
+ log_warn("sonmp", "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;
+ PEEK_BYTES(&address, sizeof(struct in_addr));
+ memcpy(chassis->c_id + 1, &address, sizeof(struct in_addr));
+ if (asprintf(&chassis->c_name, "%s", inet_ntoa(address)) == -1) {
+ log_warnx("sonmp", "unable to write chassis name for %s",
+ hardware->h_ifname);
+ goto malformed;
+ }
+ PEEK_BYTES(seg, sizeof(seg));
+ rchassis = PEEK_UINT8;
+ for (i = 0; sonmp_chassis_types[i].type != 0; i++) {
+ if (sonmp_chassis_types[i].type == rchassis) break;
+ }
+ if (asprintf(&chassis->c_descr, "%s", sonmp_chassis_types[i].description) ==
+ -1) {
+ log_warnx("sonmp", "unable to write chassis description for %s",
+ hardware->h_ifname);
+ goto malformed;
+ }
+ mgmt = lldpd_alloc_mgmt(LLDPD_AF_IPV4, &address, sizeof(struct in_addr), 0);
+ if (mgmt == NULL) {
+ if (errno == ENOMEM)
+ log_warn("sonmp",
+ "unable to allocate memory for management address");
+ else
+ log_warn("sonmp", "too large management address received on %s",
+ hardware->h_ifname);
+ goto malformed;
+ }
+ TAILQ_INSERT_TAIL(&chassis->c_mgmt, mgmt, m_entries);
+ port->p_ttl =
+ cfg ? (cfg->g_config.c_tx_interval * cfg->g_config.c_tx_hold) : LLDPD_TTL;
+ port->p_ttl = (port->p_ttl + 999) / 1000;
+
+ port->p_id_subtype = LLDP_PORTID_SUBTYPE_LOCAL;
+
+ port->p_id_len =
+ asprintf(&port->p_id, "%02x-%02x-%02x", seg[0], seg[1], seg[2]);
+ if (port->p_id_len == -1) {
+ log_warn("sonmp", "unable to allocate memory for port id on %s",
+ hardware->h_ifname);
+ goto malformed;
+ }
+
+ /* Port description depend on the number of segments */
+ if ((seg[0] == 0) && (seg[1] == 0)) {
+ if (asprintf(&port->p_descr, "port %d", seg[2]) == -1) {
+ log_warnx("sonmp", "unable to write port description for %s",
+ hardware->h_ifname);
+ goto malformed;
+ }
+ } else if (seg[0] == 0) {
+ if (asprintf(&port->p_descr, "port %d/%d", seg[1], seg[2]) == -1) {
+ log_warnx("sonmp", "unable to write port description for %s",
+ hardware->h_ifname);
+ goto malformed;
+ }
+ } else {
+ if (asprintf(&port->p_descr, "port %x:%x:%x", seg[0], seg[1], seg[2]) ==
+ -1) {
+ log_warnx("sonmp", "unable to write port description for %s",
+ hardware->h_ifname);
+ goto malformed;
+ }
+ }
+ *newchassis = chassis;
+ *newport = port;
+ return 1;
+
+malformed:
+ lldpd_chassis_cleanup(chassis, 1);
+ lldpd_port_cleanup(port, 1);
+ free(port);
+ return -1;
+}
+
+#endif /* ENABLE_SONMP */
diff --git a/src/daemon/protocols/sonmp.h b/src/daemon/protocols/sonmp.h
new file mode 100644
index 0000000..513c4bb
--- /dev/null
+++ b/src/daemon/protocols/sonmp.h
@@ -0,0 +1,42 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/*
+ * Copyright (c) 2008 Vincent Bernat <bernat@luffy.cx>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#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
+#define SONMP_SIZE 19
+
+struct sonmp_chassis {
+ int type;
+ const char *description;
+};
+
+#define SONMP_TOPOLOGY_CHANGED 1
+#define SONMP_TOPOLOGY_UNCHANGED 2
+#define SONMP_TOPOLOGY_NEW 3
+
+#endif /* _SONMP_H */
diff --git a/src/daemon/trace.h b/src/daemon/trace.h
new file mode 100644
index 0000000..2e7c482
--- /dev/null
+++ b/src/daemon/trace.h
@@ -0,0 +1,8 @@
+#ifdef ENABLE_DTRACE
+# include "probes.h"
+# define TRACE(probe) probe
+# define TRACE_ENABLED(probe) probe##_ENABLED()
+#else
+# define TRACE(probe)
+# define TRACE_ENABLED(probe) (0)
+#endif
diff --git a/src/daemon/usr.sbin.lldpd.in b/src/daemon/usr.sbin.lldpd.in
new file mode 100644
index 0000000..d459cd4
--- /dev/null
+++ b/src/daemon/usr.sbin.lldpd.in
@@ -0,0 +1,65 @@
+#include <tunables/global>
+
+@sbindir@/lldpd {
+ #include <abstractions/base>
+ #include <abstractions/nameservice>
+
+ capability chown,
+ capability dac_override,
+ capability fowner,
+ capability fsetid,
+ capability kill,
+ capability net_admin,
+ capability net_raw,
+ capability setgid,
+ capability setuid,
+ capability sys_chroot,
+ capability sys_module,
+
+ # Need to receive/send raw packets
+ network packet raw,
+
+ @sbindir@/lldpd mr,
+ /run/systemd/notify w,
+
+ # Ability to run lldpcli for self-configuration
+ @sbindir@/lldpcli rix,
+ @sysconfdir@/lldpd.d/ r,
+ @sysconfdir@/lldpd.d/* r,
+ @sysconfdir@/lldpd.conf r,
+
+ # PID file and socket
+ @LLDPD_PID_FILE@ rw,
+ @LLDPD_CTL_SOCKET@ rw,
+
+ # Chroot setup
+ @PRIVSEP_CHROOT@ w,
+ @PRIVSEP_CHROOT@/etc/ rw,
+ @PRIVSEP_CHROOT@/etc/localtime rw,
+
+ # Gather system description
+ /etc/os-release r,
+ /usr/lib/os-release r,
+ /usr/bin/lsb_release Cxr -> lsb_release,
+ profile lsb_release {
+ #include <abstractions/base>
+ #include <abstractions/python>
+ /usr/bin/lsb_release r,
+ /bin/dash ixr,
+ /usr/bin/dpkg-query ixr,
+ /usr/include/python2.[4567]/pyconfig.h r,
+ /etc/lsb-release r,
+ /etc/debian_version r,
+ /var/lib/dpkg/** r,
+ /usr/local/lib/python3.[0-5]/dist-packages/ r,
+ /usr/bin/ r,
+ /usr/bin/python3.[0-5] r,
+ }
+
+ # Gather network information
+ @{PROC}/sys/net/ipv4/ip_forward r,
+ @{PROC}/net/bonding/* r,
+ @{PROC}/self/net/bonding/* r,
+ /sys/devices/virtual/dmi/** r,
+ /sys/devices/pci**/net/*/ifalias r,
+}
diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am
new file mode 100644
index 0000000..c1a6d81
--- /dev/null
+++ b/src/lib/Makefile.am
@@ -0,0 +1,75 @@
+AM_CFLAGS = -I $(top_srcdir)/include $(LLDP_CFLAGS)
+AM_CPPFLAGS = $(LLDP_CPPFLAGS)
+AM_LDFLAGS = $(LLDP_LDFLAGS)
+
+lib_LTLIBRARIES = liblldpctl.la
+include_HEADERS = lldpctl.h
+
+noinst_LTLIBRARIES = libfixedpoint.la
+libfixedpoint_la_SOURCES = fixedpoint.h fixedpoint.c
+
+ATOM_FILES = \
+ atoms/config.c atoms/dot1.c atoms/dot3.c \
+ atoms/interface.c atoms/med.c atoms/mgmt.c atoms/port.c \
+ atoms/custom.c atoms/chassis.c
+liblldpctl_la_SOURCES = \
+ lldpctl.h atom.h helpers.h \
+ errors.c connection.c atom.c helpers.c \
+ $(ATOM_FILES)
+nodist_liblldpctl_la_SOURCES = atom-glue.c
+liblldpctl_la_LIBADD = $(top_builddir)/src/libcommon-daemon-lib.la libfixedpoint.la
+
+atom-glue.c: $(ATOM_FILES) Makefile
+ $(AM_V_GEN)(for f in $(ATOM_FILES:%=$(srcdir)/%); do \
+ $(CPP) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) $$f; done | \
+ $(SED) -n 's+^void init_atom_builder_\([^(]*\)(void).*, \([0-9]*\)).*+\2 \1+p' | \
+ sort | \
+ $(AWK) '{ atoms[$$2] = 1 } \
+ END { for (atom in atoms) { print "void init_atom_builder_"atom"(void);" } \
+ print "void init_atom_builder(void);"; \
+ print "void init_atom_builder(void) {"; \
+ print " static int init = 0; if (init) return; init++;"; \
+ for (atom in atoms) { print " init_atom_builder_"atom"();" } \
+ print "}"; }' && \
+ for f in $(ATOM_FILES:%=$(srcdir)/%); do \
+ $(CPP) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) $$f; done | \
+ $(SED) -n 's+^void init_atom_map_\([^(]*\)(void).*, \([0-9]*\)).*+\2 \1+p' | \
+ sort -n | \
+ $(AWK) '{ atoms[$$2] = 1 } \
+ END { for (atom in atoms) { print "void init_atom_map_"atom"(void);" } \
+ print "void init_atom_map(void);"; \
+ print "void init_atom_map(void) {"; \
+ print " static int init = 0; if (init) return; init++;"; \
+ for (atom in atoms) { print " init_atom_map_"atom"();" } \
+ print "}"; }' ) \
+ > $@.tmp
+ $(AM_V_at)$(GREP) -q init_atom_builder_ $@.tmp
+ $(AM_V_at)$(GREP) -q init_atom_map_ $@.tmp
+ $(AM_V_at)mv $@.tmp $@
+CLEANFILES = atom-glue.c
+
+# -version-info format is `current`:`revision`:`age`. For more details, see:
+# https://www.sourceware.org/autobook/autobook/autobook_61.html#Library-Versioning
+#
+# -version-number could be computed from -version-info, mostly major
+# is `current` - `age`, minor is `age` and revision is `revision' and
+# major.minor should be used when updating lldpctl.map.
+liblldpctl_la_LDFLAGS = $(AM_LDFLAGS) -version-info 13:1:9
+liblldpctl_la_DEPENDENCIES = libfixedpoint.la
+
+if HAVE_LD_VERSION_SCRIPT
+liblldpctl_la_DEPENDENCIES += lldpctl.map
+liblldpctl_la_LDFLAGS += -Wl,--version-script=$(srcdir)/lldpctl.map
+else
+liblldpctl_la_LDFLAGS += -export-symbols-regex '^lldpctl_'
+endif
+
+pkgconfig_DATA = lldpctl.pc
+
+TEMPLATES = lldpctl.pc
+EXTRA_DIST = lldpctl.pc.in lldpctl.map
+CLEANFILES += $(TEMPLATES)
+lldpctl.pc: lldpctl.pc.in
+include $(top_srcdir)/edit.am
diff --git a/src/lib/Makefile.in b/src/lib/Makefile.in
new file mode 100644
index 0000000..9306493
--- /dev/null
+++ b/src/lib/Makefile.in
@@ -0,0 +1,978 @@
+# Makefile.in generated by automake 1.16.5 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2021 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+@HAVE_LD_VERSION_SCRIPT_TRUE@am__append_1 = lldpctl.map
+@HAVE_LD_VERSION_SCRIPT_TRUE@am__append_2 = -Wl,--version-script=$(srcdir)/lldpctl.map
+@HAVE_LD_VERSION_SCRIPT_FALSE@am__append_3 = -export-symbols-regex '^lldpctl_'
+subdir = src/lib
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/alignof.m4 \
+ $(top_srcdir)/m4/args.m4 \
+ $(top_srcdir)/m4/ax_build_date_epoch.m4 \
+ $(top_srcdir)/m4/ax_cflags_gcc_option.m4 \
+ $(top_srcdir)/m4/ax_ld_check_flag.m4 \
+ $(top_srcdir)/m4/ax_lib_readline.m4 \
+ $(top_srcdir)/m4/ax_prog_doxygen.m4 \
+ $(top_srcdir)/m4/config_subdirs.m4 \
+ $(top_srcdir)/m4/ld-version-script.m4 \
+ $(top_srcdir)/m4/libcap.m4 $(top_srcdir)/m4/libevent.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/os.m4 \
+ $(top_srcdir)/m4/progname.m4 $(top_srcdir)/m4/seccomp.m4 \
+ $(top_srcdir)/m4/snmp.m4 $(top_srcdir)/m4/stdint.m4 \
+ $(top_srcdir)/m4/systemtap.m4 $(top_srcdir)/m4/xml2.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(include_HEADERS) \
+ $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+am__installdirs = "$(DESTDIR)$(libdir)" "$(DESTDIR)$(pkgconfigdir)" \
+ "$(DESTDIR)$(includedir)"
+LTLIBRARIES = $(lib_LTLIBRARIES) $(noinst_LTLIBRARIES)
+libfixedpoint_la_LIBADD =
+am_libfixedpoint_la_OBJECTS = fixedpoint.lo
+libfixedpoint_la_OBJECTS = $(am_libfixedpoint_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+am__dirstamp = $(am__leading_dot)dirstamp
+am__objects_1 = atoms/config.lo atoms/dot1.lo atoms/dot3.lo \
+ atoms/interface.lo atoms/med.lo atoms/mgmt.lo atoms/port.lo \
+ atoms/custom.lo atoms/chassis.lo
+am_liblldpctl_la_OBJECTS = errors.lo connection.lo atom.lo helpers.lo \
+ $(am__objects_1)
+nodist_liblldpctl_la_OBJECTS = atom-glue.lo
+liblldpctl_la_OBJECTS = $(am_liblldpctl_la_OBJECTS) \
+ $(nodist_liblldpctl_la_OBJECTS)
+liblldpctl_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(liblldpctl_la_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/atom-glue.Plo ./$(DEPDIR)/atom.Plo \
+ ./$(DEPDIR)/connection.Plo ./$(DEPDIR)/errors.Plo \
+ ./$(DEPDIR)/fixedpoint.Plo ./$(DEPDIR)/helpers.Plo \
+ atoms/$(DEPDIR)/chassis.Plo atoms/$(DEPDIR)/config.Plo \
+ atoms/$(DEPDIR)/custom.Plo atoms/$(DEPDIR)/dot1.Plo \
+ atoms/$(DEPDIR)/dot3.Plo atoms/$(DEPDIR)/interface.Plo \
+ atoms/$(DEPDIR)/med.Plo atoms/$(DEPDIR)/mgmt.Plo \
+ atoms/$(DEPDIR)/port.Plo
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libfixedpoint_la_SOURCES) $(liblldpctl_la_SOURCES) \
+ $(nodist_liblldpctl_la_SOURCES)
+DIST_SOURCES = $(libfixedpoint_la_SOURCES) $(liblldpctl_la_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+DATA = $(pkgconfig_DATA)
+HEADERS = $(include_HEADERS)
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp \
+ $(top_srcdir)/edit.am
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+APPARMORDIR = @APPARMORDIR@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CONFIGURE_ARGS = @CONFIGURE_ARGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CSCOPE = @CSCOPE@
+CTAGS = @CTAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DLLTOOL = @DLLTOOL@
+DOXYGEN_PAPER_SIZE = @DOXYGEN_PAPER_SIZE@
+DSYMUTIL = @DSYMUTIL@
+DTRACE = @DTRACE@
+DUMPBIN = @DUMPBIN@
+DX_CONFIG = @DX_CONFIG@
+DX_DOCDIR = @DX_DOCDIR@
+DX_DOT = @DX_DOT@
+DX_DOXYGEN = @DX_DOXYGEN@
+DX_DVIPS = @DX_DVIPS@
+DX_EGREP = @DX_EGREP@
+DX_ENV = @DX_ENV@
+DX_FLAG_chi = @DX_FLAG_chi@
+DX_FLAG_chm = @DX_FLAG_chm@
+DX_FLAG_doc = @DX_FLAG_doc@
+DX_FLAG_dot = @DX_FLAG_dot@
+DX_FLAG_html = @DX_FLAG_html@
+DX_FLAG_man = @DX_FLAG_man@
+DX_FLAG_pdf = @DX_FLAG_pdf@
+DX_FLAG_ps = @DX_FLAG_ps@
+DX_FLAG_rtf = @DX_FLAG_rtf@
+DX_FLAG_xml = @DX_FLAG_xml@
+DX_HHC = @DX_HHC@
+DX_LATEX = @DX_LATEX@
+DX_MAKEINDEX = @DX_MAKEINDEX@
+DX_PDFLATEX = @DX_PDFLATEX@
+DX_PERL = @DX_PERL@
+DX_PROJECT = @DX_PROJECT@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+ETAGS = @ETAGS@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+FILECMD = @FILECMD@
+FUZZ_DECODE_ENGINE = @FUZZ_DECODE_ENGINE@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LAUNCHDDAEMONSDIR = @LAUNCHDDAEMONSDIR@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LLDPD_CTL_SOCKET = @LLDPD_CTL_SOCKET@
+LLDPD_PID_FILE = @LLDPD_PID_FILE@
+LLDP_BIN_LDFLAGS = @LLDP_BIN_LDFLAGS@
+LLDP_CFLAGS = @LLDP_CFLAGS@
+LLDP_CPPFLAGS = @LLDP_CPPFLAGS@
+LLDP_LDFLAGS = @LLDP_LDFLAGS@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+NETLINK_MAX_RECEIVE_BUFSIZE = @NETLINK_MAX_RECEIVE_BUFSIZE@
+NETLINK_RECEIVE_BUFSIZE = @NETLINK_RECEIVE_BUFSIZE@
+NETLINK_SEND_BUFSIZE = @NETLINK_SEND_BUFSIZE@
+NETSNMP_CFLAGS = @NETSNMP_CFLAGS@
+NETSNMP_CONFIG = @NETSNMP_CONFIG@
+NETSNMP_LIBS = @NETSNMP_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+PRIVSEP_CHROOT = @PRIVSEP_CHROOT@
+PRIVSEP_GROUP = @PRIVSEP_GROUP@
+PRIVSEP_USER = @PRIVSEP_USER@
+RANLIB = @RANLIB@
+READLINE_LIBS = @READLINE_LIBS@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+SYSTEMDSYSTEMUNITDIR = @SYSTEMDSYSTEMUNITDIR@
+SYSUSERSDIR = @SYSUSERSDIR@
+VERSION = @VERSION@
+XML2_CONFIG = @XML2_CONFIG@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+apparmordir = @apparmordir@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+check_CFLAGS = @check_CFLAGS@
+check_LIBS = @check_LIBS@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+launchddaemonsdir = @launchddaemonsdir@
+libbsd_CFLAGS = @libbsd_CFLAGS@
+libbsd_LIBS = @libbsd_LIBS@
+libcap_CFLAGS = @libcap_CFLAGS@
+libcap_LIBS = @libcap_LIBS@
+libdir = @libdir@
+libevent_CFLAGS = @libevent_CFLAGS@
+libevent_LDFLAGS = @libevent_LDFLAGS@
+libevent_LIBS = @libevent_LIBS@
+libexecdir = @libexecdir@
+libseccomp_CFLAGS = @libseccomp_CFLAGS@
+libseccomp_LIBS = @libseccomp_LIBS@
+libxml2_CFLAGS = @libxml2_CFLAGS@
+libxml2_LIBS = @libxml2_LIBS@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+pkgconfigdir = @pkgconfigdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+systemdsystemunitdir = @systemdsystemunitdir@
+sysusersdir = @sysusersdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+AM_CFLAGS = -I $(top_srcdir)/include $(LLDP_CFLAGS)
+AM_CPPFLAGS = $(LLDP_CPPFLAGS)
+AM_LDFLAGS = $(LLDP_LDFLAGS)
+lib_LTLIBRARIES = liblldpctl.la
+include_HEADERS = lldpctl.h
+noinst_LTLIBRARIES = libfixedpoint.la
+libfixedpoint_la_SOURCES = fixedpoint.h fixedpoint.c
+ATOM_FILES = \
+ atoms/config.c atoms/dot1.c atoms/dot3.c \
+ atoms/interface.c atoms/med.c atoms/mgmt.c atoms/port.c \
+ atoms/custom.c atoms/chassis.c
+
+liblldpctl_la_SOURCES = \
+ lldpctl.h atom.h helpers.h \
+ errors.c connection.c atom.c helpers.c \
+ $(ATOM_FILES)
+
+nodist_liblldpctl_la_SOURCES = atom-glue.c
+liblldpctl_la_LIBADD = $(top_builddir)/src/libcommon-daemon-lib.la libfixedpoint.la
+CLEANFILES = atom-glue.c $(TEMPLATES)
+
+# -version-info format is `current`:`revision`:`age`. For more details, see:
+# https://www.sourceware.org/autobook/autobook/autobook_61.html#Library-Versioning
+#
+# -version-number could be computed from -version-info, mostly major
+# is `current` - `age`, minor is `age` and revision is `revision' and
+# major.minor should be used when updating lldpctl.map.
+liblldpctl_la_LDFLAGS = $(AM_LDFLAGS) -version-info 13:1:9 \
+ $(am__append_2) $(am__append_3)
+liblldpctl_la_DEPENDENCIES = libfixedpoint.la $(am__append_1)
+pkgconfig_DATA = lldpctl.pc
+TEMPLATES = lldpctl.pc
+EXTRA_DIST = lldpctl.pc.in lldpctl.map
+edit = $(SED) \
+ -e 's|@bindir[@]|$(bindir)|g' \
+ -e 's|@sbindir[@]|$(sbindir)|g' \
+ -e 's|@sysconfdir[@]|$(sysconfdir)|g' \
+ -e 's|@pkgdatadir[@]|$(pkgdatadir)|g' \
+ -e 's|@libdir[@]|$(libdir)|g' \
+ -e 's|@srcdir[@]|$(srcdir)|g' \
+ -e 's|@top_builddir[@]|$(top_builddir)|g' \
+ -e 's|@includedir[@]|$(includedir)|g' \
+ -e 's|@exec_prefix[@]|$(exec_prefix)|g' \
+ -e 's|@prefix[@]|$(prefix)|g' \
+ -e 's|@VERSION[@]|$(VERSION)|g' \
+ -e 's|@PACKAGE[@]|$(PACKAGE)|g' \
+ -e 's|@PACKAGE_NAME[@]|$(PACKAGE_NAME)|g' \
+ -e 's|@PACKAGE_URL[@]|$(PACKAGE_URL)|g' \
+ -e 's|@PRIVSEP_USER[@]|$(PRIVSEP_USER)|g' \
+ -e 's|@PRIVSEP_GROUP[@]|$(PRIVSEP_GROUP)|g' \
+ -e 's|@PRIVSEP_CHROOT[@]|$(PRIVSEP_CHROOT)|g' \
+ -e 's|@LLDPD_PID_FILE[@]|$(LLDPD_PID_FILE)|g' \
+ -e 's|@LLDPD_CTL_SOCKET[@]|$(LLDPD_CTL_SOCKET)|g' \
+ -e 's|@PRIVSEP_CHROOT[@]|$(PRIVSEP_CHROOT)|g'
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(top_srcdir)/edit.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/lib/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+$(top_srcdir)/edit.am $(am__empty):
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+install-libLTLIBRARIES: $(lib_LTLIBRARIES)
+ @$(NORMAL_INSTALL)
+ @list='$(lib_LTLIBRARIES)'; test -n "$(libdir)" || list=; \
+ list2=; for p in $$list; do \
+ if test -f $$p; then \
+ list2="$$list2 $$p"; \
+ else :; fi; \
+ done; \
+ test -z "$$list2" || { \
+ echo " $(MKDIR_P) '$(DESTDIR)$(libdir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(libdir)" || exit 1; \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(libdir)'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(libdir)"; \
+ }
+
+uninstall-libLTLIBRARIES:
+ @$(NORMAL_UNINSTALL)
+ @list='$(lib_LTLIBRARIES)'; test -n "$(libdir)" || list=; \
+ for p in $$list; do \
+ $(am__strip_dir) \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(libdir)/$$f'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(libdir)/$$f"; \
+ done
+
+clean-libLTLIBRARIES:
+ -test -z "$(lib_LTLIBRARIES)" || rm -f $(lib_LTLIBRARIES)
+ @list='$(lib_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+clean-noinstLTLIBRARIES:
+ -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES)
+ @list='$(noinst_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+libfixedpoint.la: $(libfixedpoint_la_OBJECTS) $(libfixedpoint_la_DEPENDENCIES) $(EXTRA_libfixedpoint_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(libfixedpoint_la_OBJECTS) $(libfixedpoint_la_LIBADD) $(LIBS)
+atoms/$(am__dirstamp):
+ @$(MKDIR_P) atoms
+ @: > atoms/$(am__dirstamp)
+atoms/$(DEPDIR)/$(am__dirstamp):
+ @$(MKDIR_P) atoms/$(DEPDIR)
+ @: > atoms/$(DEPDIR)/$(am__dirstamp)
+atoms/config.lo: atoms/$(am__dirstamp) atoms/$(DEPDIR)/$(am__dirstamp)
+atoms/dot1.lo: atoms/$(am__dirstamp) atoms/$(DEPDIR)/$(am__dirstamp)
+atoms/dot3.lo: atoms/$(am__dirstamp) atoms/$(DEPDIR)/$(am__dirstamp)
+atoms/interface.lo: atoms/$(am__dirstamp) \
+ atoms/$(DEPDIR)/$(am__dirstamp)
+atoms/med.lo: atoms/$(am__dirstamp) atoms/$(DEPDIR)/$(am__dirstamp)
+atoms/mgmt.lo: atoms/$(am__dirstamp) atoms/$(DEPDIR)/$(am__dirstamp)
+atoms/port.lo: atoms/$(am__dirstamp) atoms/$(DEPDIR)/$(am__dirstamp)
+atoms/custom.lo: atoms/$(am__dirstamp) atoms/$(DEPDIR)/$(am__dirstamp)
+atoms/chassis.lo: atoms/$(am__dirstamp) \
+ atoms/$(DEPDIR)/$(am__dirstamp)
+
+liblldpctl.la: $(liblldpctl_la_OBJECTS) $(liblldpctl_la_DEPENDENCIES) $(EXTRA_liblldpctl_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(liblldpctl_la_LINK) -rpath $(libdir) $(liblldpctl_la_OBJECTS) $(liblldpctl_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+ -rm -f atoms/*.$(OBJEXT)
+ -rm -f atoms/*.lo
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/atom-glue.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/atom.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/connection.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/errors.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fixedpoint.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/helpers.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@atoms/$(DEPDIR)/chassis.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@atoms/$(DEPDIR)/config.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@atoms/$(DEPDIR)/custom.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@atoms/$(DEPDIR)/dot1.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@atoms/$(DEPDIR)/dot3.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@atoms/$(DEPDIR)/interface.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@atoms/$(DEPDIR)/med.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@atoms/$(DEPDIR)/mgmt.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@atoms/$(DEPDIR)/port.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\
+@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.obj$$||'`;\
+@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ `$(CYGPATH_W) '$<'` &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.lo$$||'`;\
+@am__fastdepCC_TRUE@ $(LTCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+ -rm -rf atoms/.libs atoms/_libs
+install-pkgconfigDATA: $(pkgconfig_DATA)
+ @$(NORMAL_INSTALL)
+ @list='$(pkgconfig_DATA)'; test -n "$(pkgconfigdir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(pkgconfigdir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(pkgconfigdir)" || exit 1; \
+ fi; \
+ for p in $$list; do \
+ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+ echo "$$d$$p"; \
+ done | $(am__base_list) | \
+ while read files; do \
+ echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(pkgconfigdir)'"; \
+ $(INSTALL_DATA) $$files "$(DESTDIR)$(pkgconfigdir)" || exit $$?; \
+ done
+
+uninstall-pkgconfigDATA:
+ @$(NORMAL_UNINSTALL)
+ @list='$(pkgconfig_DATA)'; test -n "$(pkgconfigdir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(pkgconfigdir)'; $(am__uninstall_files_from_dir)
+install-includeHEADERS: $(include_HEADERS)
+ @$(NORMAL_INSTALL)
+ @list='$(include_HEADERS)'; test -n "$(includedir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(includedir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(includedir)" || exit 1; \
+ fi; \
+ for p in $$list; do \
+ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+ echo "$$d$$p"; \
+ done | $(am__base_list) | \
+ while read files; do \
+ echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(includedir)'"; \
+ $(INSTALL_HEADER) $$files "$(DESTDIR)$(includedir)" || exit $$?; \
+ done
+
+uninstall-includeHEADERS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(include_HEADERS)'; test -n "$(includedir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(includedir)'; $(am__uninstall_files_from_dir)
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LTLIBRARIES) $(DATA) $(HEADERS)
+installdirs:
+ for dir in "$(DESTDIR)$(libdir)" "$(DESTDIR)$(pkgconfigdir)" "$(DESTDIR)$(includedir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+ -test -z "$(CLEANFILES)" || rm -f $(CLEANFILES)
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+ -rm -f atoms/$(DEPDIR)/$(am__dirstamp)
+ -rm -f atoms/$(am__dirstamp)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libLTLIBRARIES clean-libtool \
+ clean-noinstLTLIBRARIES mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/atom-glue.Plo
+ -rm -f ./$(DEPDIR)/atom.Plo
+ -rm -f ./$(DEPDIR)/connection.Plo
+ -rm -f ./$(DEPDIR)/errors.Plo
+ -rm -f ./$(DEPDIR)/fixedpoint.Plo
+ -rm -f ./$(DEPDIR)/helpers.Plo
+ -rm -f atoms/$(DEPDIR)/chassis.Plo
+ -rm -f atoms/$(DEPDIR)/config.Plo
+ -rm -f atoms/$(DEPDIR)/custom.Plo
+ -rm -f atoms/$(DEPDIR)/dot1.Plo
+ -rm -f atoms/$(DEPDIR)/dot3.Plo
+ -rm -f atoms/$(DEPDIR)/interface.Plo
+ -rm -f atoms/$(DEPDIR)/med.Plo
+ -rm -f atoms/$(DEPDIR)/mgmt.Plo
+ -rm -f atoms/$(DEPDIR)/port.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am: install-includeHEADERS install-pkgconfigDATA
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am: install-libLTLIBRARIES
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/atom-glue.Plo
+ -rm -f ./$(DEPDIR)/atom.Plo
+ -rm -f ./$(DEPDIR)/connection.Plo
+ -rm -f ./$(DEPDIR)/errors.Plo
+ -rm -f ./$(DEPDIR)/fixedpoint.Plo
+ -rm -f ./$(DEPDIR)/helpers.Plo
+ -rm -f atoms/$(DEPDIR)/chassis.Plo
+ -rm -f atoms/$(DEPDIR)/config.Plo
+ -rm -f atoms/$(DEPDIR)/custom.Plo
+ -rm -f atoms/$(DEPDIR)/dot1.Plo
+ -rm -f atoms/$(DEPDIR)/dot3.Plo
+ -rm -f atoms/$(DEPDIR)/interface.Plo
+ -rm -f atoms/$(DEPDIR)/med.Plo
+ -rm -f atoms/$(DEPDIR)/mgmt.Plo
+ -rm -f atoms/$(DEPDIR)/port.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-includeHEADERS uninstall-libLTLIBRARIES \
+ uninstall-pkgconfigDATA
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libLTLIBRARIES clean-libtool \
+ clean-noinstLTLIBRARIES cscopelist-am ctags ctags-am distclean \
+ distclean-compile distclean-generic distclean-libtool \
+ distclean-tags distdir dvi dvi-am html html-am info info-am \
+ install install-am install-data install-data-am install-dvi \
+ install-dvi-am install-exec install-exec-am install-html \
+ install-html-am install-includeHEADERS install-info \
+ install-info-am install-libLTLIBRARIES install-man install-pdf \
+ install-pdf-am install-pkgconfigDATA install-ps install-ps-am \
+ install-strip installcheck installcheck-am installdirs \
+ maintainer-clean maintainer-clean-generic mostlyclean \
+ mostlyclean-compile mostlyclean-generic mostlyclean-libtool \
+ pdf pdf-am ps ps-am tags tags-am uninstall uninstall-am \
+ uninstall-includeHEADERS uninstall-libLTLIBRARIES \
+ uninstall-pkgconfigDATA
+
+.PRECIOUS: Makefile
+
+
+atom-glue.c: $(ATOM_FILES) Makefile
+ $(AM_V_GEN)(for f in $(ATOM_FILES:%=$(srcdir)/%); do \
+ $(CPP) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) $$f; done | \
+ $(SED) -n 's+^void init_atom_builder_\([^(]*\)(void).*, \([0-9]*\)).*+\2 \1+p' | \
+ sort | \
+ $(AWK) '{ atoms[$$2] = 1 } \
+ END { for (atom in atoms) { print "void init_atom_builder_"atom"(void);" } \
+ print "void init_atom_builder(void);"; \
+ print "void init_atom_builder(void) {"; \
+ print " static int init = 0; if (init) return; init++;"; \
+ for (atom in atoms) { print " init_atom_builder_"atom"();" } \
+ print "}"; }' && \
+ for f in $(ATOM_FILES:%=$(srcdir)/%); do \
+ $(CPP) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) $$f; done | \
+ $(SED) -n 's+^void init_atom_map_\([^(]*\)(void).*, \([0-9]*\)).*+\2 \1+p' | \
+ sort -n | \
+ $(AWK) '{ atoms[$$2] = 1 } \
+ END { for (atom in atoms) { print "void init_atom_map_"atom"(void);" } \
+ print "void init_atom_map(void);"; \
+ print "void init_atom_map(void) {"; \
+ print " static int init = 0; if (init) return; init++;"; \
+ for (atom in atoms) { print " init_atom_map_"atom"();" } \
+ print "}"; }' ) \
+ > $@.tmp
+ $(AM_V_at)$(GREP) -q init_atom_builder_ $@.tmp
+ $(AM_V_at)$(GREP) -q init_atom_map_ $@.tmp
+ $(AM_V_at)mv $@.tmp $@
+lldpctl.pc: lldpctl.pc.in
+
+$(TEMPLATES): Makefile
+ $(AM_V_GEN)$(MKDIR_P) $(@D) && $(edit) $(srcdir)/$@.in > $@.tmp && mv $@.tmp $@
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/lib/atom.c b/src/lib/atom.c
new file mode 100644
index 0000000..04011d4
--- /dev/null
+++ b/src/lib/atom.c
@@ -0,0 +1,655 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/*
+ * Copyright (c) 2012 Vincent Bernat <bernat@luffy.cx>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <limits.h>
+#include "lldpctl.h"
+#include "atom.h"
+#include "../log.h"
+#include "../marshal.h"
+#include "../ctl.h"
+
+lldpctl_conn_t *
+lldpctl_atom_get_connection(lldpctl_atom_t *atom)
+{
+ if (atom) return atom->conn;
+ return NULL;
+}
+
+void
+lldpctl_atom_inc_ref(lldpctl_atom_t *atom)
+{
+ if (atom) atom->count++;
+}
+
+void
+lldpctl_atom_dec_ref(lldpctl_atom_t *atom)
+{
+ struct atom_buffer *buffer, *buffer_next;
+ if (atom && (--atom->count == 0)) {
+ if (atom->free) atom->free(atom);
+
+ /* Remove special allocated buffers */
+ for (buffer = TAILQ_FIRST(&atom->buffers); buffer;
+ buffer = buffer_next) {
+ buffer_next = TAILQ_NEXT(buffer, next);
+ free(buffer);
+ }
+
+ free(atom);
+ }
+}
+
+lldpctl_atom_t *
+lldpctl_atom_get(lldpctl_atom_t *atom, lldpctl_key_t key)
+{
+ if (atom == NULL) return NULL;
+ RESET_ERROR(atom->conn);
+
+ if (atom->get == NULL) {
+ SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
+ return NULL;
+ }
+ return atom->get(atom, key);
+}
+
+lldpctl_atom_t *
+lldpctl_atom_set(lldpctl_atom_t *atom, lldpctl_key_t key, lldpctl_atom_t *value)
+{
+ if (atom == NULL) return NULL;
+ RESET_ERROR(atom->conn);
+
+ if (atom->set == NULL) {
+ SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
+ return NULL;
+ }
+ return atom->set(atom, key, value);
+}
+
+const char *
+lldpctl_atom_get_str(lldpctl_atom_t *atom, lldpctl_key_t key)
+{
+ char *strresult = NULL;
+ const uint8_t *bufresult = NULL;
+ long int intresult = -1;
+ int n1;
+ size_t n2;
+
+ if (atom == NULL) return NULL;
+ RESET_ERROR(atom->conn);
+
+ if (atom->get_str != NULL) {
+ strresult = (char *)atom->get_str(atom, key);
+ if (strresult) return strresult;
+ if (lldpctl_last_error(atom->conn) != LLDPCTL_ERR_NOT_EXIST)
+ return NULL;
+ }
+
+ RESET_ERROR(atom->conn);
+ if (atom->get_int != NULL) {
+ intresult = atom->get_int(atom, key);
+ if (lldpctl_last_error(atom->conn) != LLDPCTL_ERR_NOT_EXIST) {
+ strresult = _lldpctl_alloc_in_atom(atom, 21);
+ if (!strresult) return NULL;
+ n1 = snprintf(strresult, 21, "%ld", intresult);
+ if (n1 > -1 && n1 < 21) return strresult;
+ SET_ERROR(atom->conn,
+ LLDPCTL_ERR_NOMEM); /* Not really true... */
+ return NULL;
+ }
+ }
+
+ RESET_ERROR(atom->conn);
+ if (atom->get_buffer != NULL) {
+ bufresult = atom->get_buffer(atom, key, &n2);
+ if (bufresult)
+ return _lldpctl_dump_in_atom(atom, bufresult, n2, ' ', 0);
+ if (lldpctl_last_error(atom->conn) != LLDPCTL_ERR_NOT_EXIST)
+ return NULL;
+ }
+
+ SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
+ return NULL;
+}
+
+lldpctl_atom_t *
+lldpctl_atom_set_str(lldpctl_atom_t *atom, lldpctl_key_t key, const char *value)
+{
+ lldpctl_atom_t *result = NULL;
+ const char *errstr;
+ long long converted = 0;
+ int isint = 0;
+ int bad = 0;
+
+ if (atom == NULL) return NULL;
+ RESET_ERROR(atom->conn);
+
+ if (atom->set_str != NULL) {
+ result = atom->set_str(atom, key, value);
+ if (result) return result;
+ if (lldpctl_last_error(atom->conn) != LLDPCTL_ERR_NOT_EXIST &&
+ lldpctl_last_error(atom->conn) != LLDPCTL_ERR_BAD_VALUE)
+ return NULL;
+ bad = bad || (lldpctl_last_error(atom->conn) == LLDPCTL_ERR_BAD_VALUE);
+ }
+
+ if (value) {
+ converted = strtonum(value, LLONG_MIN, LLONG_MAX, &errstr);
+ isint = (errstr == NULL);
+ }
+
+ RESET_ERROR(atom->conn);
+ if (atom->set_int != NULL && isint) {
+ result = atom->set_int(atom, key, converted);
+ if (result) return result;
+ if (lldpctl_last_error(atom->conn) != LLDPCTL_ERR_NOT_EXIST &&
+ lldpctl_last_error(atom->conn) != LLDPCTL_ERR_BAD_VALUE)
+ return NULL;
+ bad = bad || (lldpctl_last_error(atom->conn) == LLDPCTL_ERR_BAD_VALUE);
+ }
+
+ RESET_ERROR(atom->conn);
+ if (atom->set_buffer != NULL) {
+ result = atom->set_buffer(atom, key, (u_int8_t *)value,
+ value ? strlen(value) : 0);
+ if (result) return result;
+ if (lldpctl_last_error(atom->conn) != LLDPCTL_ERR_NOT_EXIST &&
+ lldpctl_last_error(atom->conn) != LLDPCTL_ERR_BAD_VALUE)
+ return NULL;
+ bad = bad || (lldpctl_last_error(atom->conn) == LLDPCTL_ERR_BAD_VALUE);
+ }
+
+ SET_ERROR(atom->conn, bad ? LLDPCTL_ERR_BAD_VALUE : LLDPCTL_ERR_NOT_EXIST);
+ return NULL;
+}
+
+const u_int8_t *
+lldpctl_atom_get_buffer(lldpctl_atom_t *atom, lldpctl_key_t key, size_t *length)
+{
+ if (atom == NULL) return NULL;
+ RESET_ERROR(atom->conn);
+
+ if (atom->get_buffer == NULL) {
+ SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
+ return NULL;
+ }
+ return atom->get_buffer(atom, key, length);
+}
+
+lldpctl_atom_t *
+lldpctl_atom_set_buffer(lldpctl_atom_t *atom, lldpctl_key_t key, const u_int8_t *value,
+ size_t length)
+{
+ if (atom == NULL) return NULL;
+ RESET_ERROR(atom->conn);
+
+ if (atom->set_buffer == NULL) {
+ SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
+ return NULL;
+ }
+ return atom->set_buffer(atom, key, value, length);
+}
+
+long int
+lldpctl_atom_get_int(lldpctl_atom_t *atom, lldpctl_key_t key)
+{
+ if (atom == NULL) return LLDPCTL_ERR_NOT_EXIST;
+ RESET_ERROR(atom->conn);
+
+ if (atom->get_int == NULL) return SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
+ return atom->get_int(atom, key);
+}
+
+lldpctl_atom_t *
+lldpctl_atom_set_int(lldpctl_atom_t *atom, lldpctl_key_t key, long int value)
+{
+ if (atom == NULL) return NULL;
+ RESET_ERROR(atom->conn);
+
+ if (atom->set_int == NULL) {
+ SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
+ return NULL;
+ }
+ return atom->set_int(atom, key, value);
+}
+
+lldpctl_atom_iter_t *
+lldpctl_atom_iter(lldpctl_atom_t *atom)
+{
+ if (atom == NULL) return NULL;
+ RESET_ERROR(atom->conn);
+
+ if (!atom->iter) {
+ SET_ERROR(atom->conn, LLDPCTL_ERR_CANNOT_ITERATE);
+ return NULL;
+ }
+ return atom->iter(atom);
+}
+
+lldpctl_atom_iter_t *
+lldpctl_atom_iter_next(lldpctl_atom_t *atom, lldpctl_atom_iter_t *iter)
+{
+ if (atom == NULL) return NULL;
+ RESET_ERROR(atom->conn);
+
+ if (!atom->next) {
+ SET_ERROR(atom->conn, LLDPCTL_ERR_CANNOT_ITERATE);
+ return NULL;
+ }
+ return atom->next(atom, iter);
+}
+
+lldpctl_atom_t *
+lldpctl_atom_iter_value(lldpctl_atom_t *atom, lldpctl_atom_iter_t *iter)
+{
+ if (atom == NULL) return NULL;
+ RESET_ERROR(atom->conn);
+
+ if (!atom->value) {
+ SET_ERROR(atom->conn, LLDPCTL_ERR_CANNOT_ITERATE);
+ return NULL;
+ }
+ return atom->value(atom, iter);
+}
+
+lldpctl_atom_t *
+lldpctl_atom_create(lldpctl_atom_t *atom)
+{
+ if (atom == NULL) return NULL;
+ RESET_ERROR(atom->conn);
+
+ if (!atom->create) {
+ SET_ERROR(atom->conn, LLDPCTL_ERR_CANNOT_CREATE);
+ return NULL;
+ }
+ return atom->create(atom);
+}
+
+/**
+ * Get somethin with IO.
+ *
+ * @param conn The connection to lldpd.
+ * @param state_send State to be when "sending"
+ * @param state_recv State to be when "receiving"
+ * @param state_data Ancillary data for state handling
+ * @param type Type of message to send (and receive)
+ * @param to_send Data to send.
+ * @param mi_send Marshalling info for data to send.
+ * @param to_recv Data to receive.
+ * @param mi_recv Marshalling info for data to recive.
+ * @return 0 in case of success, a negative integer in case of failure.
+ *
+ * The current state must match one of @c CONN_STATE_IDLE, @c state_send or @c
+ * state_recv and in the two later cases, the provided @c state_data must match.
+ */
+int
+_lldpctl_do_something(lldpctl_conn_t *conn, int state_send, int state_recv,
+ const char *state_data, enum hmsg_type type, void *to_send,
+ struct marshal_info *mi_send, void **to_recv, struct marshal_info *mi_recv)
+{
+ ssize_t rc;
+
+ if (conn->state == CONN_STATE_WATCHING)
+ /* The connection cannot be used anymore. */
+ return SET_ERROR(conn, LLDPCTL_ERR_INVALID_STATE);
+
+ if (conn->state == CONN_STATE_IDLE) {
+ /* We need to build the message to send, then send
+ * it. */
+ if (ctl_msg_send_unserialized(&conn->output_buffer,
+ &conn->output_buffer_len, type, to_send, mi_send) != 0)
+ return SET_ERROR(conn, LLDPCTL_ERR_SERIALIZATION);
+ conn->state = state_send;
+ if (state_data)
+ strlcpy(conn->state_data, state_data, sizeof(conn->state_data));
+ else
+ conn->state_data[0] = 0;
+ }
+ if (conn->state == state_send &&
+ (state_data == NULL ||
+ !strncmp(conn->state_data, state_data, sizeof(conn->state_data) - 1))) {
+ /* We need to send the currently built message */
+ rc = lldpctl_send(conn);
+ if (rc < 0) return SET_ERROR(conn, rc);
+ conn->state = state_recv;
+ }
+ if (conn->state == state_recv &&
+ (state_data == NULL ||
+ !strncmp(conn->state_data, state_data, sizeof(conn->state_data) - 1))) {
+ /* We need to receive the answer */
+ while ((rc = ctl_msg_recv_unserialized(&conn->input_buffer,
+ &conn->input_buffer_len, type, to_recv, mi_recv)) > 0) {
+ /* We need more bytes */
+ rc = _lldpctl_needs(conn, rc);
+ if (rc < 0) return SET_ERROR(conn, rc);
+ }
+ if (rc < 0) return SET_ERROR(conn, LLDPCTL_ERR_SERIALIZATION);
+ /* rc == 0 */
+ conn->state = CONN_STATE_IDLE;
+ conn->state_data[0] = 0;
+ return 0;
+ } else
+ return SET_ERROR(conn, LLDPCTL_ERR_INVALID_STATE);
+}
+
+int
+lldpctl_watch_callback(lldpctl_conn_t *conn, lldpctl_change_callback cb, void *data)
+{
+ int rc;
+
+ RESET_ERROR(conn);
+
+ rc = _lldpctl_do_something(conn, CONN_STATE_SET_WATCH_SEND,
+ CONN_STATE_SET_WATCH_RECV, NULL, SUBSCRIBE, NULL, NULL, NULL, NULL);
+ if (rc == 0) {
+ conn->watch_cb = cb;
+ conn->watch_data = data;
+ conn->state = CONN_STATE_WATCHING;
+ }
+ return rc;
+}
+
+int
+lldpctl_watch_callback2(lldpctl_conn_t *conn, lldpctl_change_callback2 cb, void *data)
+{
+ int rc;
+
+ RESET_ERROR(conn);
+
+ rc = _lldpctl_do_something(conn, CONN_STATE_SET_WATCH_SEND,
+ CONN_STATE_SET_WATCH_RECV, NULL, SUBSCRIBE, NULL, NULL, NULL, NULL);
+ if (rc == 0) {
+ conn->watch_cb2 = cb;
+ conn->watch_data = data;
+ conn->state = CONN_STATE_WATCHING;
+ }
+ return rc;
+}
+
+int
+lldpctl_watch(lldpctl_conn_t *conn)
+{
+ int rc = 0;
+
+ RESET_ERROR(conn);
+
+ if (conn->state != CONN_STATE_WATCHING)
+ return SET_ERROR(conn, LLDPCTL_ERR_INVALID_STATE);
+
+ conn->watch_triggered = 0;
+ while (!conn->watch_triggered) {
+ rc = _lldpctl_needs(conn, 1);
+ if (rc < 0) return SET_ERROR(conn, rc);
+ }
+
+ RESET_ERROR(conn);
+ return 0;
+}
+
+lldpctl_atom_t *
+lldpctl_get_configuration(lldpctl_conn_t *conn)
+{
+ int rc;
+ struct lldpd_config *config;
+ void *p;
+
+ RESET_ERROR(conn);
+
+ rc = _lldpctl_do_something(conn, CONN_STATE_GET_CONFIG_SEND,
+ CONN_STATE_GET_CONFIG_RECV, NULL, GET_CONFIG, NULL, NULL, &p,
+ &MARSHAL_INFO(lldpd_config));
+ if (rc == 0) {
+ config = p;
+ return _lldpctl_new_atom(conn, atom_config, config);
+ }
+ return NULL;
+}
+
+lldpctl_atom_t *
+lldpctl_get_interfaces(lldpctl_conn_t *conn)
+{
+ struct lldpd_interface_list *ifs;
+ void *p;
+ int rc;
+
+ RESET_ERROR(conn);
+
+ rc = _lldpctl_do_something(conn, CONN_STATE_GET_INTERFACES_SEND,
+ CONN_STATE_GET_INTERFACES_RECV, NULL, GET_INTERFACES, NULL, NULL, &p,
+ &MARSHAL_INFO(lldpd_interface_list));
+ if (rc == 0) {
+ ifs = p;
+ return _lldpctl_new_atom(conn, atom_interfaces_list, ifs);
+ }
+ return NULL;
+}
+
+lldpctl_atom_t *
+lldpctl_get_local_chassis(lldpctl_conn_t *conn)
+{
+ struct lldpd_chassis *chassis;
+ void *p;
+ int rc;
+
+ RESET_ERROR(conn);
+
+ rc = _lldpctl_do_something(conn, CONN_STATE_GET_CHASSIS_SEND,
+ CONN_STATE_GET_CHASSIS_RECV, NULL, GET_CHASSIS, NULL, NULL, &p,
+ &MARSHAL_INFO(lldpd_chassis));
+ if (rc == 0) {
+ chassis = p;
+ return _lldpctl_new_atom(conn, atom_chassis, chassis, NULL, 0);
+ }
+ return NULL;
+}
+
+lldpctl_atom_t *
+lldpctl_get_port(lldpctl_atom_t *atom)
+{
+ int rc;
+ lldpctl_conn_t *conn = atom->conn;
+ struct lldpd_hardware *hardware;
+ void *p;
+ struct _lldpctl_atom_interface_t *iface =
+ (struct _lldpctl_atom_interface_t *)atom;
+
+ RESET_ERROR(conn);
+
+ if (atom->type != atom_interface) {
+ SET_ERROR(conn, LLDPCTL_ERR_INCORRECT_ATOM_TYPE);
+ return NULL;
+ }
+ rc = _lldpctl_do_something(conn, CONN_STATE_GET_PORT_SEND,
+ CONN_STATE_GET_PORT_RECV, iface->name, GET_INTERFACE, (void *)iface->name,
+ &MARSHAL_INFO(string), &p, &MARSHAL_INFO(lldpd_hardware));
+ if (rc == 0) {
+ hardware = p;
+ return _lldpctl_new_atom(conn, atom_port, 1, hardware,
+ &hardware->h_lport, NULL);
+ }
+ return NULL;
+}
+
+lldpctl_atom_t *
+lldpctl_get_default_port(lldpctl_conn_t *conn)
+{
+ struct lldpd_port *port;
+ void *p;
+ int rc;
+
+ RESET_ERROR(conn);
+
+ rc = _lldpctl_do_something(conn, CONN_STATE_GET_DEFAULT_PORT_SEND,
+ CONN_STATE_GET_DEFAULT_PORT_RECV, "", GET_DEFAULT_PORT, NULL, NULL, &p,
+ &MARSHAL_INFO(lldpd_port));
+ if (rc == 0) {
+ port = p;
+ return _lldpctl_new_atom(conn, atom_port, 1, NULL, port, NULL);
+ }
+ return NULL;
+}
+
+static lldpctl_map_t empty_map[] = { { 0, NULL } };
+
+static struct atom_map atom_map_list = { .next = NULL };
+
+lldpctl_map_t *
+lldpctl_key_get_map(lldpctl_key_t key)
+{
+ init_atom_map();
+ struct atom_map *map;
+ for (map = atom_map_list.next; map; map = map->next) {
+ if (map->key == key) return map->map;
+ }
+ return empty_map;
+}
+
+void
+atom_map_register(struct atom_map *map, int prio)
+{
+ (void)prio;
+ struct atom_map *iter = &atom_map_list;
+
+ while (iter->next)
+ iter = iter->next;
+
+ iter->next = map;
+}
+
+static struct atom_builder atom_builder_list = { .nextb = NULL };
+
+void
+atom_builder_register(struct atom_builder *builder, int prio)
+{
+ (void)prio;
+ struct atom_builder *iter = &atom_builder_list;
+
+ while (iter->nextb)
+ iter = iter->nextb;
+
+ iter->nextb = builder;
+}
+
+lldpctl_atom_t *
+_lldpctl_new_atom(lldpctl_conn_t *conn, atom_t type, ...)
+{
+ init_atom_builder();
+ struct atom_builder *builder;
+ struct lldpctl_atom_t *atom;
+ va_list(ap);
+ for (builder = atom_builder_list.nextb; builder; builder = builder->nextb) {
+ if (builder->type != type) continue;
+ atom = calloc(1, builder->size);
+ if (atom == NULL) {
+ SET_ERROR(conn, LLDPCTL_ERR_NOMEM);
+ return NULL;
+ }
+ atom->count = 1;
+ atom->type = type;
+ atom->conn = conn;
+ TAILQ_INIT(&atom->buffers);
+ atom->free = builder->free;
+
+ atom->iter = builder->iter;
+ atom->next = builder->next;
+ atom->value = builder->value;
+
+ atom->get = builder->get;
+ atom->get_str = builder->get_str;
+ atom->get_buffer = builder->get_buffer;
+ atom->get_int = builder->get_int;
+
+ atom->set = builder->set;
+ atom->set_str = builder->set_str;
+ atom->set_buffer = builder->set_buffer;
+ atom->set_int = builder->set_int;
+ atom->create = builder->create;
+
+ va_start(ap, type);
+ if (builder->init && builder->init(atom, ap) == 0) {
+ free(atom);
+ va_end(ap);
+ /* Error to be set in init() */
+ return NULL;
+ }
+ va_end(ap);
+ return atom;
+ }
+ log_warnx("rpc", "unknown atom type: %d", type);
+ SET_ERROR(conn, LLDPCTL_ERR_FATAL);
+ return NULL;
+}
+
+/**
+ * Allocate a buffer inside an atom.
+ *
+ * It will be freed automatically when the atom is released. This buffer cannot
+ * be reallocated and should not be freed!
+ *
+ * @param atom Atom which will be used as a container.
+ * @param size Size of the allocated area.
+ * @return Pointer to the buffer or @c NULL if allocation fails.
+ */
+void *
+_lldpctl_alloc_in_atom(lldpctl_atom_t *atom, size_t size)
+{
+ struct atom_buffer *buffer;
+
+ if ((buffer = calloc(1, size + sizeof(struct atom_buffer))) == NULL) {
+ SET_ERROR(atom->conn, LLDPCTL_ERR_NOMEM);
+ return NULL;
+ }
+ TAILQ_INSERT_TAIL(&atom->buffers, buffer, next);
+ return &buffer->data[0];
+}
+
+/**
+ * Allocate a buffer inside an atom and dump another buffer in it.
+ *
+ * The dump is done in hexadecimal with the provided separator.
+ *
+ * @param atom Atom which will be used as a container.
+ * @param input Buffer we want to dump.
+ * @param size Size of the buffer
+ * @param sep Separator to use.
+ * @param max Maximum number of bytes to dump. Can be 0 if no maximum.
+ * @return A string representing the dump of the buffer or @c NULL if error.
+ */
+const char *
+_lldpctl_dump_in_atom(lldpctl_atom_t *atom, const uint8_t *input, size_t size, char sep,
+ size_t max)
+{
+ static const char truncation[] = "[...]";
+ size_t i, len;
+ char *buffer = NULL;
+
+ if (max > 0 && size > max)
+ len = max * 3 + sizeof(truncation) + 1;
+ else
+ len = size * 3 + 1;
+
+ if ((buffer = _lldpctl_alloc_in_atom(atom, len)) == NULL) return NULL;
+
+ for (i = 0; (i < size) && (max == 0 || i < max); i++)
+ snprintf(buffer + i * 3, 4, "%02x%c", *(u_int8_t *)(input + i), sep);
+ if (max > 0 && size > max)
+ snprintf(buffer + i * 3, sizeof(truncation) + 1, "%s", truncation);
+ else
+ *(buffer + i * 3 - 1) = 0;
+ return buffer;
+}
diff --git a/src/lib/atom.h b/src/lib/atom.h
new file mode 100644
index 0000000..0dfc85b
--- /dev/null
+++ b/src/lib/atom.h
@@ -0,0 +1,344 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/*
+ * Copyright (c) 2012 Vincent Bernat <bernat@luffy.cx>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/queue.h>
+#include "../lldpd-structs.h"
+#include "../compat/compat.h"
+#include "../marshal.h"
+#include "../ctl.h"
+
+/* connection.c */
+struct lldpctl_conn_t {
+ /* the Unix-domain socket to connect to lldpd */
+ char *ctlname;
+
+ /* Callback handling */
+ lldpctl_recv_callback recv; /* Receive callback */
+ lldpctl_send_callback send; /* Send callback */
+ void *user_data; /* Callback user data */
+
+ /* IO state handling. */
+ uint8_t *input_buffer; /* Current input/output buffer */
+ uint8_t *output_buffer; /* Current input/output buffer */
+ size_t input_buffer_len;
+ size_t output_buffer_len;
+
+#define CONN_STATE_IDLE 0
+#define CONN_STATE_GET_INTERFACES_SEND 1
+#define CONN_STATE_GET_INTERFACES_RECV 2
+#define CONN_STATE_GET_PORT_SEND 3
+#define CONN_STATE_GET_PORT_RECV 4
+#define CONN_STATE_SET_PORT_SEND 5
+#define CONN_STATE_SET_PORT_RECV 6
+#define CONN_STATE_SET_WATCH_SEND 7
+#define CONN_STATE_SET_WATCH_RECV 8
+#define CONN_STATE_GET_CONFIG_SEND 9
+#define CONN_STATE_GET_CONFIG_RECV 10
+#define CONN_STATE_SET_CONFIG_SEND 11
+#define CONN_STATE_SET_CONFIG_RECV 12
+#define CONN_STATE_GET_CHASSIS_SEND 13
+#define CONN_STATE_GET_CHASSIS_RECV 14
+#define CONN_STATE_GET_DEFAULT_PORT_SEND 15
+#define CONN_STATE_GET_DEFAULT_PORT_RECV 16
+#define CONN_STATE_WATCHING 17
+#define CONN_STATE_SET_CHASSIS_SEND 18
+#define CONN_STATE_SET_CHASSIS_RECV 19
+
+ int state; /* Current state */
+ /* Data attached to the state. It is used to check that we are using the
+ * same data as a previous call until the state machine goes to
+ * CONN_STATE_IDLE. */
+ char state_data[IFNAMSIZ + 64];
+ /* Error handling */
+ lldpctl_error_t error; /* Last error */
+
+ /* Handling notifications */
+ lldpctl_change_callback watch_cb;
+ lldpctl_change_callback2 watch_cb2;
+ void *watch_data;
+ int watch_triggered;
+};
+
+/* User data for synchronous callbacks. */
+struct lldpctl_conn_sync_t {
+ int fd; /* File descriptor to the socket. */
+};
+
+ssize_t _lldpctl_needs(lldpctl_conn_t *lldpctl, size_t length);
+int _lldpctl_do_something(lldpctl_conn_t *conn, int state_send, int state_recv,
+ const char *state_data, enum hmsg_type type, void *to_send,
+ struct marshal_info *mi_send, void **to_recv, struct marshal_info *mi_recv);
+
+/* errors.c */
+#define SET_ERROR(conn, x) ((conn)->error = x)
+#define RESET_ERROR(conn) SET_ERROR((conn), LLDPCTL_NO_ERROR)
+
+/* atom.c and atom-private.c */
+typedef enum {
+ atom_config,
+ atom_interfaces_list,
+ atom_interface,
+ atom_ports_list,
+ atom_port,
+ atom_mgmts_list,
+ atom_mgmt,
+#ifdef ENABLE_DOT3
+ atom_dot3_power,
+#endif
+#ifdef ENABLE_DOT1
+ atom_vlans_list,
+ atom_vlan,
+ atom_ppvids_list,
+ atom_ppvid,
+ atom_pis_list,
+ atom_pi,
+#endif
+#ifdef ENABLE_LLDPMED
+ atom_med_policies_list,
+ atom_med_policy,
+ atom_med_locations_list,
+ atom_med_location,
+ atom_med_caelements_list,
+ atom_med_caelement,
+ atom_med_power,
+#endif
+#ifdef ENABLE_CUSTOM
+ atom_custom_list,
+ atom_custom,
+#endif
+ atom_chassis,
+} atom_t;
+
+void *_lldpctl_alloc_in_atom(lldpctl_atom_t *, size_t);
+const char *_lldpctl_dump_in_atom(lldpctl_atom_t *, const uint8_t *, size_t, char,
+ size_t);
+
+struct atom_buffer {
+ TAILQ_ENTRY(atom_buffer) next;
+ u_int8_t data[0];
+};
+
+struct lldpctl_atom_t {
+ int count;
+ atom_t type;
+ lldpctl_conn_t *conn;
+ TAILQ_HEAD(, atom_buffer) buffers; /* List of buffers */
+
+ /* Destructor */
+ void (*free)(lldpctl_atom_t *);
+
+ /* Iterators */
+ lldpctl_atom_iter_t *(*iter)(lldpctl_atom_t *);
+ lldpctl_atom_iter_t *(*next)(lldpctl_atom_t *, lldpctl_atom_iter_t *);
+ lldpctl_atom_t *(*value)(lldpctl_atom_t *, lldpctl_atom_iter_t *);
+
+ /* Getters */
+ lldpctl_atom_t *(*get)(lldpctl_atom_t *, lldpctl_key_t);
+ const char *(*get_str)(lldpctl_atom_t *, lldpctl_key_t);
+ const u_int8_t *(*get_buffer)(lldpctl_atom_t *, lldpctl_key_t, size_t *);
+ long int (*get_int)(lldpctl_atom_t *, lldpctl_key_t);
+
+ /* Setters */
+ lldpctl_atom_t *(*set)(lldpctl_atom_t *, lldpctl_key_t, lldpctl_atom_t *);
+ lldpctl_atom_t *(*set_str)(lldpctl_atom_t *, lldpctl_key_t, const char *);
+ lldpctl_atom_t *(
+ *set_buffer)(lldpctl_atom_t *, lldpctl_key_t, const u_int8_t *, size_t);
+ lldpctl_atom_t *(*set_int)(lldpctl_atom_t *, lldpctl_key_t, long int);
+ lldpctl_atom_t *(*create)(lldpctl_atom_t *);
+};
+
+struct _lldpctl_atom_config_t {
+ lldpctl_atom_t base;
+ struct lldpd_config *config;
+};
+
+struct _lldpctl_atom_interfaces_list_t {
+ lldpctl_atom_t base;
+ struct lldpd_interface_list *ifs;
+};
+
+struct _lldpctl_atom_interface_t {
+ lldpctl_atom_t base;
+ char *name;
+};
+
+struct _lldpctl_atom_chassis_t {
+ lldpctl_atom_t base;
+ struct lldpd_chassis *chassis;
+ struct _lldpctl_atom_port_t
+ *parent; /* Optional: parent of this atom (owning our reference) */
+ int embedded; /* This atom is "embedded" (not refcounted) */
+};
+
+struct _lldpctl_atom_port_t {
+ lldpctl_atom_t base;
+ int local; /* Local or remote port? */
+ struct lldpd_hardware *hardware; /* Local port only (but optional) */
+ struct lldpd_port *port; /* Local and remote */
+ struct _lldpctl_atom_port_t *parent; /* Local port if we are a remote port */
+ lldpctl_atom_t *chassis; /* Internal atom for chassis */
+};
+
+/* Can represent any simple list holding just a reference to a port. */
+struct _lldpctl_atom_any_list_t {
+ lldpctl_atom_t base;
+ struct _lldpctl_atom_port_t *parent;
+};
+
+struct _lldpctl_atom_mgmts_list_t {
+ lldpctl_atom_t base;
+ lldpctl_atom_t *parent;
+ struct lldpd_chassis *chassis; /* Chassis containing the list of IP addresses */
+};
+
+struct _lldpctl_atom_mgmt_t {
+ lldpctl_atom_t base;
+ lldpctl_atom_t *parent;
+ struct lldpd_mgmt *mgmt;
+};
+
+#ifdef ENABLE_DOT3
+struct _lldpctl_atom_dot3_power_t {
+ lldpctl_atom_t base;
+ struct _lldpctl_atom_port_t *parent;
+};
+#endif
+
+#ifdef ENABLE_DOT1
+struct _lldpctl_atom_vlan_t {
+ lldpctl_atom_t base;
+ struct _lldpctl_atom_port_t *parent;
+ struct lldpd_vlan *vlan;
+};
+
+struct _lldpctl_atom_ppvid_t {
+ lldpctl_atom_t base;
+ struct _lldpctl_atom_port_t *parent;
+ struct lldpd_ppvid *ppvid;
+};
+
+struct _lldpctl_atom_pi_t {
+ lldpctl_atom_t base;
+ struct _lldpctl_atom_port_t *parent;
+ struct lldpd_pi *pi;
+};
+#endif
+
+#ifdef ENABLE_LLDPMED
+struct _lldpctl_atom_med_policy_t {
+ lldpctl_atom_t base;
+ struct _lldpctl_atom_port_t *parent;
+ struct lldpd_med_policy *policy;
+};
+
+struct _lldpctl_atom_med_location_t {
+ lldpctl_atom_t base;
+ struct _lldpctl_atom_port_t *parent;
+ struct lldpd_med_loc *location;
+};
+
+/* This list should have the same structure than _llpdctl_atom_any_list_t */
+struct _lldpctl_atom_med_caelements_list_t {
+ lldpctl_atom_t base;
+ struct _lldpctl_atom_med_location_t *parent;
+};
+
+struct _lldpctl_atom_med_caelement_t {
+ lldpctl_atom_t base;
+ struct _lldpctl_atom_med_location_t *parent;
+ int type;
+ uint8_t *value;
+ size_t len;
+};
+
+struct _lldpctl_atom_med_power_t {
+ lldpctl_atom_t base;
+ struct _lldpctl_atom_port_t *parent;
+};
+#endif
+
+#ifdef ENABLE_CUSTOM
+struct _lldpctl_atom_custom_list_t {
+ lldpctl_atom_t base;
+ struct _lldpctl_atom_port_t *parent;
+ struct lldpd_custom_list *list;
+};
+
+struct _lldpctl_atom_custom_t {
+ lldpctl_atom_t base;
+ struct _lldpctl_atom_port_t *parent;
+ int op;
+ struct lldpd_custom *tlv;
+};
+#endif
+
+struct lldpctl_atom_t *_lldpctl_new_atom(lldpctl_conn_t *conn, atom_t type, ...);
+
+struct atom_map {
+ int key;
+ struct atom_map *next;
+ lldpctl_map_t map[];
+};
+
+void atom_map_register(struct atom_map *map, int);
+void init_atom_map(void);
+
+#define ATOM_MAP_REGISTER(NAME, PRIO) \
+ void init_atom_map_##NAME(void); \
+ void init_atom_map_##NAME(void) \
+ { \
+ atom_map_register(&NAME, PRIO); \
+ }
+
+struct atom_builder {
+ atom_t type; /* Atom type */
+ size_t size; /* Size of structure to allocate */
+ int (*init)(lldpctl_atom_t *, va_list); /* Optional additional init steps */
+ void (*free)(lldpctl_atom_t *); /* Optional deallocation steps */
+
+ lldpctl_atom_iter_t *(*iter)(
+ lldpctl_atom_t *); /* Optional, return an iterator for this object */
+ lldpctl_atom_iter_t *(*next)(lldpctl_atom_t *,
+ lldpctl_atom_iter_t
+ *); /* Return the next object for the provided iterator */
+ lldpctl_atom_t *(*value)(lldpctl_atom_t *,
+ lldpctl_atom_iter_t
+ *); /* Return the current object for the provided iterator */
+
+ lldpctl_atom_t *(*get)(lldpctl_atom_t *, lldpctl_key_t);
+ const char *(*get_str)(lldpctl_atom_t *, lldpctl_key_t);
+ const u_int8_t *(*get_buffer)(lldpctl_atom_t *, lldpctl_key_t, size_t *);
+ long int (*get_int)(lldpctl_atom_t *, lldpctl_key_t);
+
+ lldpctl_atom_t *(*set)(lldpctl_atom_t *, lldpctl_key_t, lldpctl_atom_t *);
+ lldpctl_atom_t *(*set_str)(lldpctl_atom_t *, lldpctl_key_t, const char *);
+ lldpctl_atom_t *(
+ *set_buffer)(lldpctl_atom_t *, lldpctl_key_t, const u_int8_t *, size_t);
+ lldpctl_atom_t *(*set_int)(lldpctl_atom_t *, lldpctl_key_t, long int);
+ lldpctl_atom_t *(*create)(lldpctl_atom_t *);
+ struct atom_builder *nextb;
+};
+
+void atom_builder_register(struct atom_builder *builder, int);
+void init_atom_builder(void);
+
+#define ATOM_BUILDER_REGISTER(NAME, PRIO) \
+ void init_atom_builder_##NAME(void); \
+ void init_atom_builder_##NAME(void) \
+ { \
+ atom_builder_register(&NAME, PRIO); \
+ }
diff --git a/src/lib/atoms/chassis.c b/src/lib/atoms/chassis.c
new file mode 100644
index 0000000..4d195f7
--- /dev/null
+++ b/src/lib/atoms/chassis.c
@@ -0,0 +1,326 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/*
+ * Copyright (c) 2015 Vincent Bernat <vincent@bernat.im>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+#include <arpa/inet.h>
+
+#include "../lldpctl.h"
+#include "../../log.h"
+#include "../atom.h"
+#include "../helpers.h"
+
+static lldpctl_map_t chassis_id_subtype_map[] = {
+ { LLDP_CHASSISID_SUBTYPE_IFNAME, "ifname" },
+ { LLDP_CHASSISID_SUBTYPE_IFALIAS, "ifalias" },
+ { LLDP_CHASSISID_SUBTYPE_LOCAL, "local" },
+ { LLDP_CHASSISID_SUBTYPE_LLADDR, "mac" },
+ { LLDP_CHASSISID_SUBTYPE_ADDR, "ip" },
+ { LLDP_CHASSISID_SUBTYPE_PORT, "unhandled" },
+ { LLDP_CHASSISID_SUBTYPE_CHASSIS, "unhandled" },
+ { 0, NULL },
+};
+
+#ifdef ENABLE_LLDPMED
+
+static lldpctl_map_t chassis_med_type_map[] = {
+ { LLDP_MED_CLASS_I, "Generic Endpoint (Class I)" },
+ { LLDP_MED_CLASS_II, "Media Endpoint (Class II)" },
+ { LLDP_MED_CLASS_III, "Communication Device Endpoint (Class III)" },
+ { LLDP_MED_NETWORK_DEVICE, "Network Connectivity Device" },
+ { 0, NULL },
+};
+
+#endif
+
+static int
+_lldpctl_atom_new_chassis(lldpctl_atom_t *atom, va_list ap)
+{
+ struct _lldpctl_atom_chassis_t *p = (struct _lldpctl_atom_chassis_t *)atom;
+ p->chassis = va_arg(ap, struct lldpd_chassis *);
+ p->parent = va_arg(ap, struct _lldpctl_atom_port_t *);
+ p->embedded = va_arg(ap, int);
+ if (p->parent && !p->embedded)
+ lldpctl_atom_inc_ref((lldpctl_atom_t *)p->parent);
+ return 1;
+}
+
+static void
+_lldpctl_atom_free_chassis(lldpctl_atom_t *atom)
+{
+ struct _lldpctl_atom_chassis_t *p = (struct _lldpctl_atom_chassis_t *)atom;
+ /* When we have a parent, the chassis structure is in fact part of the
+ * parent, just decrement the reference count of the parent. Otherwise,
+ * we need to free the whole chassis. When embedded, we don't alter the
+ * reference count of the parent. Therefore, it's important to also not
+ * increase the reference count of this atom. See
+ * `_lldpctl_atom_get_atom_chassis' for how to handle that. */
+ if (p->parent) {
+ if (!p->embedded) lldpctl_atom_dec_ref((lldpctl_atom_t *)p->parent);
+ } else
+ lldpd_chassis_cleanup(p->chassis, 1);
+}
+
+static lldpctl_atom_t *
+_lldpctl_atom_get_atom_chassis(lldpctl_atom_t *atom, lldpctl_key_t key)
+{
+ struct _lldpctl_atom_chassis_t *p = (struct _lldpctl_atom_chassis_t *)atom;
+ struct lldpd_chassis *chassis = p->chassis;
+
+ switch (key) {
+ case lldpctl_k_chassis_mgmt:
+ return _lldpctl_new_atom(atom->conn, atom_mgmts_list,
+ (p->parent && p->embedded) ? (lldpctl_atom_t *)p->parent :
+ (lldpctl_atom_t *)p,
+ chassis);
+ default:
+ SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
+ return NULL;
+ }
+}
+
+#ifdef ENABLE_LLDPMED
+static lldpctl_atom_t *
+_lldpctl_atom_set_str_chassis(lldpctl_atom_t *atom, lldpctl_key_t key,
+ const char *value)
+{
+ struct _lldpctl_atom_chassis_t *p = (struct _lldpctl_atom_chassis_t *)atom;
+ struct lldpd_chassis *chassis = p->chassis;
+
+ char *canary = NULL;
+
+ int rc;
+
+ switch (key) {
+ case lldpctl_k_chassis_med_inventory_hw:
+ free(chassis->c_med_hw);
+ chassis->c_med_hw = xstrdup(value);
+ break;
+ case lldpctl_k_chassis_med_inventory_sw:
+ free(chassis->c_med_sw);
+ chassis->c_med_sw = xstrdup(value);
+ break;
+ case lldpctl_k_chassis_med_inventory_fw:
+ free(chassis->c_med_fw);
+ chassis->c_med_fw = xstrdup(value);
+ break;
+ case lldpctl_k_chassis_med_inventory_sn:
+ free(chassis->c_med_sn);
+ chassis->c_med_sn = xstrdup(value);
+ break;
+ case lldpctl_k_chassis_med_inventory_manuf:
+ free(chassis->c_med_manuf);
+ chassis->c_med_manuf = xstrdup(value);
+ break;
+ case lldpctl_k_chassis_med_inventory_model:
+ free(chassis->c_med_model);
+ chassis->c_med_model = xstrdup(value);
+ break;
+ case lldpctl_k_chassis_med_inventory_asset:
+ free(chassis->c_med_asset);
+ chassis->c_med_asset = xstrdup(value);
+ break;
+ default:
+ SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
+ return NULL;
+ }
+
+ if (asprintf(&canary, "%d%s", key, value ? value : "(NULL)") == -1) {
+ SET_ERROR(atom->conn, LLDPCTL_ERR_NOMEM);
+ return NULL;
+ }
+
+ rc = _lldpctl_do_something(atom->conn, CONN_STATE_SET_CHASSIS_SEND,
+ CONN_STATE_SET_CHASSIS_RECV, canary, SET_CHASSIS, chassis,
+ &MARSHAL_INFO(lldpd_chassis), NULL, NULL);
+
+ free(canary);
+ if (rc == 0) return atom;
+ return NULL;
+}
+#endif /* ENABLE_LLDPMED */
+
+static const char *
+_lldpctl_atom_get_str_chassis(lldpctl_atom_t *atom, lldpctl_key_t key)
+{
+ struct _lldpctl_atom_chassis_t *p = (struct _lldpctl_atom_chassis_t *)atom;
+ struct lldpd_chassis *chassis = p->chassis;
+ char *ipaddress = NULL;
+ size_t len;
+
+ /* Local and remote port */
+ switch (key) {
+
+ case lldpctl_k_chassis_id_subtype:
+ return map_lookup(chassis_id_subtype_map, chassis->c_id_subtype);
+ case lldpctl_k_chassis_id:
+ switch (chassis->c_id_subtype) {
+ case LLDP_CHASSISID_SUBTYPE_IFNAME:
+ case LLDP_CHASSISID_SUBTYPE_IFALIAS:
+ case LLDP_CHASSISID_SUBTYPE_LOCAL:
+ return chassis->c_id;
+ case LLDP_CHASSISID_SUBTYPE_LLADDR:
+ return _lldpctl_dump_in_atom(atom, (uint8_t *)chassis->c_id,
+ chassis->c_id_len, ':', 0);
+ case LLDP_CHASSISID_SUBTYPE_ADDR:
+ switch (chassis->c_id[0]) {
+ case LLDP_MGMT_ADDR_IP4:
+ len = INET_ADDRSTRLEN + 1;
+ break;
+ case LLDP_MGMT_ADDR_IP6:
+ len = INET6_ADDRSTRLEN + 1;
+ break;
+ default:
+ len = 0;
+ }
+ if (len > 0) {
+ ipaddress = _lldpctl_alloc_in_atom(atom, len);
+ if (!ipaddress) return NULL;
+ if (inet_ntop((chassis->c_id[0] == LLDP_MGMT_ADDR_IP4) ?
+ AF_INET :
+ AF_INET6,
+ &chassis->c_id[1], ipaddress, len) == NULL)
+ break;
+ return ipaddress;
+ }
+ break;
+ }
+ SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
+ return NULL;
+ case lldpctl_k_chassis_name:
+ return chassis->c_name;
+ case lldpctl_k_chassis_descr:
+ return chassis->c_descr;
+
+#ifdef ENABLE_LLDPMED
+ case lldpctl_k_chassis_med_type:
+ return map_lookup(chassis_med_type_map, chassis->c_med_type);
+ case lldpctl_k_chassis_med_inventory_hw:
+ return chassis->c_med_hw;
+ case lldpctl_k_chassis_med_inventory_sw:
+ return chassis->c_med_sw;
+ case lldpctl_k_chassis_med_inventory_fw:
+ return chassis->c_med_fw;
+ case lldpctl_k_chassis_med_inventory_sn:
+ return chassis->c_med_sn;
+ case lldpctl_k_chassis_med_inventory_manuf:
+ return chassis->c_med_manuf;
+ case lldpctl_k_chassis_med_inventory_model:
+ return chassis->c_med_model;
+ case lldpctl_k_chassis_med_inventory_asset:
+ return chassis->c_med_asset;
+#endif
+
+ default:
+ SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
+ return NULL;
+ }
+}
+
+static lldpctl_atom_t *
+_lldpctl_atom_set_int_chassis(lldpctl_atom_t *atom, lldpctl_key_t key, long int value)
+{
+ int rc;
+ char *canary = NULL;
+ struct _lldpctl_atom_chassis_t *c = (struct _lldpctl_atom_chassis_t *)atom;
+ struct lldpd_chassis chassis;
+ memcpy(&chassis, c->chassis, sizeof(struct lldpd_chassis));
+
+ switch (key) {
+ case lldpctl_k_chassis_cap_enabled:
+ chassis.c_cap_enabled = c->chassis->c_cap_enabled =
+ chassis.c_cap_available & value;
+ break;
+ default:
+ SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
+ return NULL;
+ }
+
+ if (asprintf(&canary, "%d%ld", key, value) == -1) {
+ SET_ERROR(atom->conn, LLDPCTL_ERR_NOMEM);
+ return NULL;
+ }
+
+ rc = _lldpctl_do_something(atom->conn, CONN_STATE_SET_CHASSIS_SEND,
+ CONN_STATE_SET_CHASSIS_RECV, canary, SET_CHASSIS, &chassis,
+ &MARSHAL_INFO(lldpd_chassis), NULL, NULL);
+
+ free(canary);
+ if (rc == 0) return atom;
+ return NULL;
+}
+
+static long int
+_lldpctl_atom_get_int_chassis(lldpctl_atom_t *atom, lldpctl_key_t key)
+{
+ struct _lldpctl_atom_chassis_t *p = (struct _lldpctl_atom_chassis_t *)atom;
+ struct lldpd_chassis *chassis = p->chassis;
+
+ /* Local and remote port */
+ switch (key) {
+ case lldpctl_k_chassis_index:
+ return chassis->c_index;
+ case lldpctl_k_chassis_id_subtype:
+ return chassis->c_id_subtype;
+ case lldpctl_k_chassis_cap_available:
+ return chassis->c_cap_available;
+ case lldpctl_k_chassis_cap_enabled:
+ return chassis->c_cap_enabled;
+#ifdef ENABLE_LLDPMED
+ case lldpctl_k_chassis_med_type:
+ return chassis->c_med_type;
+ case lldpctl_k_chassis_med_cap:
+ return chassis->c_med_cap_available;
+#endif
+ default:
+ return SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
+ }
+}
+
+static const uint8_t *
+_lldpctl_atom_get_buf_chassis(lldpctl_atom_t *atom, lldpctl_key_t key, size_t *n)
+{
+ struct _lldpctl_atom_chassis_t *p = (struct _lldpctl_atom_chassis_t *)atom;
+ struct lldpd_chassis *chassis = p->chassis;
+
+ switch (key) {
+ case lldpctl_k_chassis_id:
+ *n = chassis->c_id_len;
+ return (uint8_t *)chassis->c_id;
+ default:
+ SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
+ return NULL;
+ }
+}
+
+static struct atom_builder chassis = {
+ atom_chassis,
+ sizeof(struct _lldpctl_atom_chassis_t),
+ .init = _lldpctl_atom_new_chassis,
+ .free = _lldpctl_atom_free_chassis,
+ .get = _lldpctl_atom_get_atom_chassis,
+ .get_str = _lldpctl_atom_get_str_chassis,
+ .get_int = _lldpctl_atom_get_int_chassis,
+ .set_int = _lldpctl_atom_set_int_chassis,
+ .get_buffer = _lldpctl_atom_get_buf_chassis,
+#ifdef ENABLE_LLDPMED
+ .set_str = _lldpctl_atom_set_str_chassis,
+#endif
+};
+
+ATOM_BUILDER_REGISTER(chassis, 3);
diff --git a/src/lib/atoms/config.c b/src/lib/atoms/config.c
new file mode 100644
index 0000000..8a4af2e
--- /dev/null
+++ b/src/lib/atoms/config.c
@@ -0,0 +1,338 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/*
+ * Copyright (c) 2015 Vincent Bernat <vincent@bernat.im>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+#include <arpa/inet.h>
+
+#include "../lldpctl.h"
+#include "../../log.h"
+#include "../atom.h"
+#include "../helpers.h"
+
+static struct atom_map bond_slave_src_mac_map = {
+ .key = lldpctl_k_config_bond_slave_src_mac_type,
+ .map = {
+ { LLDP_BOND_SLAVE_SRC_MAC_TYPE_REAL, "real"},
+ { LLDP_BOND_SLAVE_SRC_MAC_TYPE_ZERO, "zero"},
+ { LLDP_BOND_SLAVE_SRC_MAC_TYPE_FIXED, "fixed"},
+ { LLDP_BOND_SLAVE_SRC_MAC_TYPE_LOCALLY_ADMINISTERED, "local" },
+ { LLDP_BOND_SLAVE_SRC_MAC_TYPE_UNKNOWN, NULL},
+ },
+};
+
+static struct atom_map lldp_portid_map = {
+ .key = lldpctl_k_config_lldp_portid_type,
+ .map = {
+ { LLDP_PORTID_SUBTYPE_IFNAME, "ifname"},
+ { LLDP_PORTID_SUBTYPE_LLADDR, "macaddress"},
+ { LLDP_PORTID_SUBTYPE_LOCAL, "local"},
+ { LLDP_PORTID_SUBTYPE_UNKNOWN, NULL},
+ },
+};
+
+static struct atom_map lldp_agent_map = {
+ .key = lldpctl_k_config_lldp_agent_type,
+ .map = {
+ { LLDP_AGENT_TYPE_NEAREST_BRIDGE, "nearest bridge"},
+ { LLDP_AGENT_TYPE_NEAREST_NONTPMR_BRIDGE, "nearest non-TPMR bridge"},
+ { LLDP_AGENT_TYPE_NEAREST_CUSTOMER_BRIDGE, "nearest customer bridge"},
+ { LLDP_AGENT_TYPE_UNKNOWN, NULL},
+ },
+};
+
+ATOM_MAP_REGISTER(bond_slave_src_mac_map, 1);
+ATOM_MAP_REGISTER(lldp_portid_map, 2);
+ATOM_MAP_REGISTER(lldp_agent_map, 3);
+
+static int
+_lldpctl_atom_new_config(lldpctl_atom_t *atom, va_list ap)
+{
+ struct _lldpctl_atom_config_t *c = (struct _lldpctl_atom_config_t *)atom;
+ c->config = va_arg(ap, struct lldpd_config *);
+ return 1;
+}
+
+static void
+_lldpctl_atom_free_config(lldpctl_atom_t *atom)
+{
+ struct _lldpctl_atom_config_t *c = (struct _lldpctl_atom_config_t *)atom;
+ lldpd_config_cleanup(c->config);
+ free(c->config);
+}
+
+static const char *
+_lldpctl_atom_get_str_config(lldpctl_atom_t *atom, lldpctl_key_t key)
+{
+ char *res = NULL;
+ struct _lldpctl_atom_config_t *c = (struct _lldpctl_atom_config_t *)atom;
+ switch (key) {
+ case lldpctl_k_config_mgmt_pattern:
+ res = c->config->c_mgmt_pattern;
+ break;
+ case lldpctl_k_config_iface_pattern:
+ res = c->config->c_iface_pattern;
+ break;
+ case lldpctl_k_config_perm_iface_pattern:
+ res = c->config->c_perm_ifaces;
+ break;
+ case lldpctl_k_config_cid_pattern:
+ res = c->config->c_cid_pattern;
+ break;
+ case lldpctl_k_config_cid_string:
+ res = c->config->c_cid_string;
+ break;
+ case lldpctl_k_config_description:
+ res = c->config->c_description;
+ break;
+ case lldpctl_k_config_platform:
+ res = c->config->c_platform;
+ break;
+ case lldpctl_k_config_hostname:
+ res = c->config->c_hostname;
+ break;
+ case lldpctl_k_config_bond_slave_src_mac_type:
+ return map_lookup(bond_slave_src_mac_map.map,
+ c->config->c_bond_slave_src_mac_type);
+ case lldpctl_k_config_lldp_portid_type:
+ return map_lookup(lldp_portid_map.map, c->config->c_lldp_portid_type);
+ case lldpctl_k_config_lldp_agent_type:
+ return map_lookup(lldp_agent_map.map, c->config->c_lldp_agent_type);
+ default:
+ SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
+ return NULL;
+ }
+ return res ? res : "";
+}
+
+static struct _lldpctl_atom_config_t *
+__lldpctl_atom_set_str_config(struct _lldpctl_atom_config_t *c, char **local,
+ char **global, const char *value)
+{
+ if (value) {
+ char *aval = NULL;
+ size_t len = strlen(value) + 1;
+ aval = _lldpctl_alloc_in_atom((lldpctl_atom_t *)c, len);
+ if (!aval) return NULL;
+ memcpy(aval, value, len);
+ *local = aval;
+ free(*global);
+ *global = strdup(aval);
+ } else {
+ free(*global);
+ *local = *global = NULL;
+ }
+ return c;
+}
+
+static lldpctl_atom_t *
+_lldpctl_atom_set_str_config(lldpctl_atom_t *atom, lldpctl_key_t key, const char *value)
+{
+ struct _lldpctl_atom_config_t *c = (struct _lldpctl_atom_config_t *)atom;
+ struct lldpd_config config;
+ memcpy(&config, c->config, sizeof(struct lldpd_config));
+ char *canary = NULL;
+ int rc;
+
+ switch (key) {
+ case lldpctl_k_config_perm_iface_pattern:
+ if (!__lldpctl_atom_set_str_config(c, &config.c_perm_ifaces,
+ &c->config->c_perm_ifaces, value))
+ return NULL;
+ break;
+ case lldpctl_k_config_iface_pattern:
+ if (!__lldpctl_atom_set_str_config(c, &config.c_iface_pattern,
+ &c->config->c_iface_pattern, value))
+ return NULL;
+ break;
+ case lldpctl_k_config_mgmt_pattern:
+ if (!__lldpctl_atom_set_str_config(c, &config.c_mgmt_pattern,
+ &c->config->c_mgmt_pattern, value))
+ return NULL;
+ break;
+ case lldpctl_k_config_cid_string:
+ if (!__lldpctl_atom_set_str_config(c, &config.c_cid_string,
+ &c->config->c_cid_string, value))
+ return NULL;
+ break;
+ case lldpctl_k_config_description:
+ if (!__lldpctl_atom_set_str_config(c, &config.c_description,
+ &c->config->c_description, value))
+ return NULL;
+ break;
+ case lldpctl_k_config_platform:
+ if (!__lldpctl_atom_set_str_config(c, &config.c_platform,
+ &c->config->c_platform, value))
+ return NULL;
+ break;
+ case lldpctl_k_config_hostname:
+ if (!__lldpctl_atom_set_str_config(c, &config.c_hostname,
+ &c->config->c_hostname, value))
+ return NULL;
+ break;
+ default:
+ SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
+ return NULL;
+ }
+
+ if (asprintf(&canary, "%d%s", key, value ? value : "(NULL)") == -1) {
+ SET_ERROR(atom->conn, LLDPCTL_ERR_NOMEM);
+ return NULL;
+ }
+ rc = _lldpctl_do_something(atom->conn, CONN_STATE_SET_CONFIG_SEND,
+ CONN_STATE_SET_CONFIG_RECV, canary, SET_CONFIG, &config,
+ &MARSHAL_INFO(lldpd_config), NULL, NULL);
+ free(canary);
+ if (rc == 0) return atom;
+
+#undef SET_STR
+
+ return NULL;
+}
+
+static long int
+_lldpctl_atom_get_int_config(lldpctl_atom_t *atom, lldpctl_key_t key)
+{
+ struct _lldpctl_atom_config_t *c = (struct _lldpctl_atom_config_t *)atom;
+ switch (key) {
+ case lldpctl_k_config_paused:
+ return c->config->c_paused;
+ case lldpctl_k_config_tx_interval:
+ return (c->config->c_tx_interval + 999) / 1000; /* s units */
+ case lldpctl_k_config_tx_interval_ms:
+ return c->config->c_tx_interval; /* ms units */
+ case lldpctl_k_config_receiveonly:
+ return c->config->c_receiveonly;
+ case lldpctl_k_config_advertise_version:
+ return c->config->c_advertise_version;
+ case lldpctl_k_config_ifdescr_update:
+ return c->config->c_set_ifdescr;
+ case lldpctl_k_config_iface_promisc:
+ return c->config->c_promisc;
+ case lldpctl_k_config_chassis_cap_advertise:
+ return c->config->c_cap_advertise;
+ case lldpctl_k_config_chassis_cap_override:
+ return c->config->c_cap_override;
+ case lldpctl_k_config_chassis_mgmt_advertise:
+ return c->config->c_mgmt_advertise;
+#ifdef ENABLE_LLDPMED
+ case lldpctl_k_config_lldpmed_noinventory:
+ return c->config->c_noinventory;
+ case lldpctl_k_config_fast_start_enabled:
+ return c->config->c_enable_fast_start;
+ case lldpctl_k_config_fast_start_interval:
+ return c->config->c_tx_fast_interval;
+#endif
+ case lldpctl_k_config_tx_hold:
+ return c->config->c_tx_hold;
+ case lldpctl_k_config_max_neighbors:
+ return c->config->c_max_neighbors;
+ default:
+ return SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
+ }
+}
+
+static lldpctl_atom_t *
+_lldpctl_atom_set_int_config(lldpctl_atom_t *atom, lldpctl_key_t key, long int value)
+{
+ int rc;
+ char *canary = NULL;
+ struct _lldpctl_atom_config_t *c = (struct _lldpctl_atom_config_t *)atom;
+ struct lldpd_config config;
+ memcpy(&config, c->config, sizeof(struct lldpd_config));
+
+ switch (key) {
+ case lldpctl_k_config_paused:
+ config.c_paused = c->config->c_paused = value;
+ break;
+ case lldpctl_k_config_tx_interval:
+ config.c_tx_interval = value * 1000;
+ if (value > 0) c->config->c_tx_interval = value * 1000;
+ break;
+ case lldpctl_k_config_tx_interval_ms:
+ config.c_tx_interval = value;
+ if (value > 0) c->config->c_tx_interval = value;
+ break;
+ case lldpctl_k_config_ifdescr_update:
+ config.c_set_ifdescr = c->config->c_set_ifdescr = value;
+ break;
+ case lldpctl_k_config_iface_promisc:
+ config.c_promisc = c->config->c_promisc = value;
+ break;
+ case lldpctl_k_config_chassis_cap_advertise:
+ config.c_cap_advertise = c->config->c_cap_advertise = value;
+ break;
+ case lldpctl_k_config_chassis_cap_override:
+ config.c_cap_override = c->config->c_cap_override = value;
+ break;
+ case lldpctl_k_config_chassis_mgmt_advertise:
+ config.c_mgmt_advertise = c->config->c_mgmt_advertise = value;
+ break;
+#ifdef ENABLE_LLDPMED
+ case lldpctl_k_config_fast_start_enabled:
+ config.c_enable_fast_start = c->config->c_enable_fast_start = value;
+ break;
+ case lldpctl_k_config_fast_start_interval:
+ config.c_tx_fast_interval = c->config->c_tx_fast_interval = value;
+ break;
+#endif
+ case lldpctl_k_config_tx_hold:
+ config.c_tx_hold = value;
+ if (value > 0) c->config->c_tx_hold = value;
+ break;
+ case lldpctl_k_config_max_neighbors:
+ config.c_max_neighbors = value;
+ if (value > 0) c->config->c_max_neighbors = value;
+ break;
+ case lldpctl_k_config_bond_slave_src_mac_type:
+ config.c_bond_slave_src_mac_type = value;
+ c->config->c_bond_slave_src_mac_type = value;
+ break;
+ case lldpctl_k_config_lldp_portid_type:
+ config.c_lldp_portid_type = value;
+ c->config->c_lldp_portid_type = value;
+ break;
+ case lldpctl_k_config_lldp_agent_type:
+ config.c_lldp_agent_type = value;
+ c->config->c_lldp_agent_type = value;
+ break;
+ default:
+ SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
+ return NULL;
+ }
+
+ if (asprintf(&canary, "%d%ld", key, value) == -1) {
+ SET_ERROR(atom->conn, LLDPCTL_ERR_NOMEM);
+ return NULL;
+ }
+ rc = _lldpctl_do_something(atom->conn, CONN_STATE_SET_CONFIG_SEND,
+ CONN_STATE_SET_CONFIG_RECV, canary, SET_CONFIG, &config,
+ &MARSHAL_INFO(lldpd_config), NULL, NULL);
+ free(canary);
+ if (rc == 0) return atom;
+ return NULL;
+}
+
+static struct atom_builder config = { atom_config,
+ sizeof(struct _lldpctl_atom_config_t), .init = _lldpctl_atom_new_config,
+ .free = _lldpctl_atom_free_config, .get_str = _lldpctl_atom_get_str_config,
+ .set_str = _lldpctl_atom_set_str_config,
+ .get_int = _lldpctl_atom_get_int_config,
+ .set_int = _lldpctl_atom_set_int_config };
+
+ATOM_BUILDER_REGISTER(config, 1);
diff --git a/src/lib/atoms/custom.c b/src/lib/atoms/custom.c
new file mode 100644
index 0000000..ffa5b03
--- /dev/null
+++ b/src/lib/atoms/custom.c
@@ -0,0 +1,226 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/*
+ * Copyright (c) 2015 Vincent Bernat <vincent@bernat.im>
+ * Copyright (c) 2015 Alexandru Ardelean <ardeleanalex@gmail.com>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+#include <arpa/inet.h>
+
+#include "../lldpctl.h"
+#include "../../log.h"
+#include "../atom.h"
+#include "../helpers.h"
+
+#ifdef ENABLE_CUSTOM
+
+# define min(x, y) ((x > y) ? y : x)
+
+static lldpctl_atom_iter_t *
+_lldpctl_atom_iter_custom_list(lldpctl_atom_t *atom)
+{
+ struct _lldpctl_atom_custom_list_t *custom =
+ (struct _lldpctl_atom_custom_list_t *)atom;
+ return (lldpctl_atom_iter_t *)TAILQ_FIRST(&custom->parent->port->p_custom_list);
+}
+
+static lldpctl_atom_iter_t *
+_lldpctl_atom_next_custom_list(lldpctl_atom_t *atom, lldpctl_atom_iter_t *iter)
+{
+ return (lldpctl_atom_iter_t *)TAILQ_NEXT((struct lldpd_custom *)iter, next);
+}
+
+static lldpctl_atom_t *
+_lldpctl_atom_value_custom_list(lldpctl_atom_t *atom, lldpctl_atom_iter_t *iter)
+{
+ struct _lldpctl_atom_custom_list_t *custom =
+ (struct _lldpctl_atom_custom_list_t *)atom;
+ struct lldpd_custom *tlv = (struct lldpd_custom *)iter;
+ return _lldpctl_new_atom(atom->conn, atom_custom, custom->parent, tlv);
+}
+
+static lldpctl_atom_t *
+_lldpctl_atom_create_custom_list(lldpctl_atom_t *atom)
+{
+ struct _lldpctl_atom_custom_list_t *custom =
+ (struct _lldpctl_atom_custom_list_t *)atom;
+ struct lldpd_custom *tlv;
+
+ tlv = _lldpctl_alloc_in_atom(atom, sizeof(struct lldpd_custom));
+ if (!tlv) return NULL;
+ return _lldpctl_new_atom(atom->conn, atom_custom, custom->parent, tlv);
+}
+
+static int
+_lldpctl_atom_new_custom(lldpctl_atom_t *atom, va_list ap)
+{
+ struct _lldpctl_atom_custom_t *custom = (struct _lldpctl_atom_custom_t *)atom;
+
+ custom->parent = va_arg(ap, struct _lldpctl_atom_port_t *);
+ custom->tlv = va_arg(ap, struct lldpd_custom *);
+ lldpctl_atom_inc_ref((lldpctl_atom_t *)custom->parent);
+ return 1;
+}
+
+static void
+_lldpctl_atom_free_custom(lldpctl_atom_t *atom)
+{
+ struct _lldpctl_atom_custom_t *custom = (struct _lldpctl_atom_custom_t *)atom;
+ lldpctl_atom_dec_ref((lldpctl_atom_t *)custom->parent);
+}
+
+static long int
+_lldpctl_atom_get_int_custom(lldpctl_atom_t *atom, lldpctl_key_t key)
+{
+ struct _lldpctl_atom_custom_t *custom = (struct _lldpctl_atom_custom_t *)atom;
+
+ switch (key) {
+ case lldpctl_k_custom_tlv_oui_subtype:
+ return custom->tlv->subtype;
+ default:
+ return SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
+ }
+}
+
+static lldpctl_atom_t *
+_lldpctl_atom_set_str_custom(lldpctl_atom_t *atom, lldpctl_key_t key, const char *value)
+{
+ struct _lldpctl_atom_custom_t *custom = (struct _lldpctl_atom_custom_t *)atom;
+
+ if (!value || !strlen(value)) return NULL;
+
+ /* Only local port can be modified */
+ if (!custom->parent->local) {
+ SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
+ return NULL;
+ }
+
+ switch (key) {
+ case lldpctl_k_custom_tlv_op:
+ if (!strcmp(value, "replace"))
+ custom->op = CUSTOM_TLV_REPLACE;
+ else if (!strcmp(value, "remove"))
+ custom->op = CUSTOM_TLV_REMOVE;
+ else
+ custom->op = CUSTOM_TLV_ADD;
+ return atom;
+ default:
+ SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
+ return NULL;
+ }
+
+ SET_ERROR(atom->conn, LLDPCTL_ERR_BAD_VALUE);
+ return NULL;
+}
+
+static lldpctl_atom_t *
+_lldpctl_atom_set_int_custom(lldpctl_atom_t *atom, lldpctl_key_t key, long int value)
+{
+ struct _lldpctl_atom_custom_t *custom = (struct _lldpctl_atom_custom_t *)atom;
+
+ /* Only local port can be modified */
+ if (!custom->parent->local) {
+ SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
+ return NULL;
+ }
+
+ switch (key) {
+ case lldpctl_k_custom_tlv_oui_subtype:
+ if (value < 0 || value > 255) goto bad;
+ custom->tlv->subtype = value;
+ return atom;
+ default:
+ SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
+ return NULL;
+ }
+bad:
+ SET_ERROR(atom->conn, LLDPCTL_ERR_BAD_VALUE);
+ return NULL;
+}
+
+static const uint8_t *
+_lldpctl_atom_get_buffer_custom(lldpctl_atom_t *atom, lldpctl_key_t key, size_t *n)
+{
+ struct _lldpctl_atom_custom_t *custom = (struct _lldpctl_atom_custom_t *)atom;
+
+ switch (key) {
+ case lldpctl_k_custom_tlv_oui:
+ *n = sizeof(custom->tlv->oui);
+ return (const uint8_t *)&custom->tlv->oui;
+ case lldpctl_k_custom_tlv_oui_info_string:
+ *n = custom->tlv->oui_info_len;
+ return (const uint8_t *)custom->tlv->oui_info;
+ default:
+ SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
+ return NULL;
+ }
+}
+
+static lldpctl_atom_t *
+_lldpctl_atom_set_buffer_custom(lldpctl_atom_t *atom, lldpctl_key_t key,
+ const u_int8_t *buf, size_t n)
+{
+ struct _lldpctl_atom_custom_t *custom = (struct _lldpctl_atom_custom_t *)atom;
+
+ /* Only local port can be modified */
+ if (!custom->parent->local) {
+ SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
+ return NULL;
+ }
+
+ switch (key) {
+ case lldpctl_k_custom_tlv_oui:
+ memcpy(&custom->tlv->oui, buf, min(n, sizeof(custom->tlv->oui)));
+ return atom;
+ case lldpctl_k_custom_tlv_oui_info_string:
+ if (n == 0 || n > LLDP_TLV_ORG_OUI_INFO_MAXLEN) {
+ SET_ERROR(atom->conn, LLDPCTL_ERR_BAD_VALUE);
+ return NULL;
+ }
+ custom->tlv->oui_info_len = n;
+ if (!(custom->tlv->oui_info = _lldpctl_alloc_in_atom(atom, n))) {
+ custom->tlv->oui_info_len = 0;
+ SET_ERROR(atom->conn, LLDPCTL_ERR_NOMEM);
+ return NULL;
+ }
+ memcpy(custom->tlv->oui_info, buf, n);
+ return atom;
+ default:
+ SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
+ return NULL;
+ }
+}
+
+static struct atom_builder custom_list = { atom_custom_list,
+ sizeof(struct _lldpctl_atom_any_list_t), .init = _lldpctl_atom_new_any_list,
+ .free = _lldpctl_atom_free_any_list, .iter = _lldpctl_atom_iter_custom_list,
+ .next = _lldpctl_atom_next_custom_list,
+ .value = _lldpctl_atom_value_custom_list,
+ .create = _lldpctl_atom_create_custom_list };
+
+static struct atom_builder custom = { atom_custom,
+ sizeof(struct _lldpctl_atom_custom_t), .init = _lldpctl_atom_new_custom,
+ .free = _lldpctl_atom_free_custom, .get_int = _lldpctl_atom_get_int_custom,
+ .set_int = _lldpctl_atom_set_int_custom,
+ .set_str = _lldpctl_atom_set_str_custom,
+ .get_buffer = _lldpctl_atom_get_buffer_custom,
+ .set_buffer = _lldpctl_atom_set_buffer_custom };
+
+ATOM_BUILDER_REGISTER(custom_list, 22);
+ATOM_BUILDER_REGISTER(custom, 23);
+
+#endif /* ENABLE_CUSTOM */
diff --git a/src/lib/atoms/dot1.c b/src/lib/atoms/dot1.c
new file mode 100644
index 0000000..c2feda0
--- /dev/null
+++ b/src/lib/atoms/dot1.c
@@ -0,0 +1,250 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/*
+ * Copyright (c) 2015 Vincent Bernat <vincent@bernat.im>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+#include <arpa/inet.h>
+
+#include "../lldpctl.h"
+#include "../../log.h"
+#include "../atom.h"
+#include "../helpers.h"
+
+#ifdef ENABLE_DOT1
+
+static lldpctl_atom_iter_t *
+_lldpctl_atom_iter_vlans_list(lldpctl_atom_t *atom)
+{
+ struct _lldpctl_atom_any_list_t *vlist =
+ (struct _lldpctl_atom_any_list_t *)atom;
+ return (lldpctl_atom_iter_t *)TAILQ_FIRST(&vlist->parent->port->p_vlans);
+}
+
+static lldpctl_atom_iter_t *
+_lldpctl_atom_next_vlans_list(lldpctl_atom_t *atom, lldpctl_atom_iter_t *iter)
+{
+ struct lldpd_vlan *vlan = (struct lldpd_vlan *)iter;
+ return (lldpctl_atom_iter_t *)TAILQ_NEXT(vlan, v_entries);
+}
+
+static lldpctl_atom_t *
+_lldpctl_atom_value_vlans_list(lldpctl_atom_t *atom, lldpctl_atom_iter_t *iter)
+{
+ struct _lldpctl_atom_any_list_t *vlist =
+ (struct _lldpctl_atom_any_list_t *)atom;
+ struct lldpd_vlan *vlan = (struct lldpd_vlan *)iter;
+ return _lldpctl_new_atom(atom->conn, atom_vlan, vlist->parent, vlan);
+}
+
+static int
+_lldpctl_atom_new_vlan(lldpctl_atom_t *atom, va_list ap)
+{
+ struct _lldpctl_atom_vlan_t *vlan = (struct _lldpctl_atom_vlan_t *)atom;
+ vlan->parent = va_arg(ap, struct _lldpctl_atom_port_t *);
+ vlan->vlan = va_arg(ap, struct lldpd_vlan *);
+ lldpctl_atom_inc_ref((lldpctl_atom_t *)vlan->parent);
+ return 1;
+}
+
+static void
+_lldpctl_atom_free_vlan(lldpctl_atom_t *atom)
+{
+ struct _lldpctl_atom_vlan_t *vlan = (struct _lldpctl_atom_vlan_t *)atom;
+ lldpctl_atom_dec_ref((lldpctl_atom_t *)vlan->parent);
+}
+
+static const char *
+_lldpctl_atom_get_str_vlan(lldpctl_atom_t *atom, lldpctl_key_t key)
+{
+ struct _lldpctl_atom_vlan_t *m = (struct _lldpctl_atom_vlan_t *)atom;
+
+ /* Local and remote port */
+ switch (key) {
+ case lldpctl_k_vlan_name:
+ return m->vlan->v_name;
+ default:
+ SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
+ return NULL;
+ }
+}
+
+static long int
+_lldpctl_atom_get_int_vlan(lldpctl_atom_t *atom, lldpctl_key_t key)
+{
+ struct _lldpctl_atom_vlan_t *m = (struct _lldpctl_atom_vlan_t *)atom;
+
+ /* Local and remote port */
+ switch (key) {
+ case lldpctl_k_vlan_id:
+ return m->vlan->v_vid;
+ default:
+ return SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
+ }
+}
+
+static lldpctl_atom_iter_t *
+_lldpctl_atom_iter_ppvids_list(lldpctl_atom_t *atom)
+{
+ struct _lldpctl_atom_any_list_t *vlist =
+ (struct _lldpctl_atom_any_list_t *)atom;
+ return (lldpctl_atom_iter_t *)TAILQ_FIRST(&vlist->parent->port->p_ppvids);
+}
+
+static lldpctl_atom_iter_t *
+_lldpctl_atom_next_ppvids_list(lldpctl_atom_t *atom, lldpctl_atom_iter_t *iter)
+{
+ struct lldpd_ppvid *ppvid = (struct lldpd_ppvid *)iter;
+ return (lldpctl_atom_iter_t *)TAILQ_NEXT(ppvid, p_entries);
+}
+
+static lldpctl_atom_t *
+_lldpctl_atom_value_ppvids_list(lldpctl_atom_t *atom, lldpctl_atom_iter_t *iter)
+{
+ struct _lldpctl_atom_any_list_t *vlist =
+ (struct _lldpctl_atom_any_list_t *)atom;
+ struct lldpd_ppvid *ppvid = (struct lldpd_ppvid *)iter;
+ return _lldpctl_new_atom(atom->conn, atom_ppvid, vlist->parent, ppvid);
+}
+
+static int
+_lldpctl_atom_new_ppvid(lldpctl_atom_t *atom, va_list ap)
+{
+ struct _lldpctl_atom_ppvid_t *ppvid = (struct _lldpctl_atom_ppvid_t *)atom;
+ ppvid->parent = va_arg(ap, struct _lldpctl_atom_port_t *);
+ ppvid->ppvid = va_arg(ap, struct lldpd_ppvid *);
+ lldpctl_atom_inc_ref((lldpctl_atom_t *)ppvid->parent);
+ return 1;
+}
+
+static void
+_lldpctl_atom_free_ppvid(lldpctl_atom_t *atom)
+{
+ struct _lldpctl_atom_ppvid_t *ppvid = (struct _lldpctl_atom_ppvid_t *)atom;
+ lldpctl_atom_dec_ref((lldpctl_atom_t *)ppvid->parent);
+}
+
+static long int
+_lldpctl_atom_get_int_ppvid(lldpctl_atom_t *atom, lldpctl_key_t key)
+{
+ struct _lldpctl_atom_ppvid_t *m = (struct _lldpctl_atom_ppvid_t *)atom;
+
+ /* Local and remote port */
+ switch (key) {
+ case lldpctl_k_ppvid_id:
+ return m->ppvid->p_ppvid;
+ case lldpctl_k_ppvid_status:
+ return m->ppvid->p_cap_status;
+ default:
+ return SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
+ }
+}
+
+static lldpctl_atom_iter_t *
+_lldpctl_atom_iter_pis_list(lldpctl_atom_t *atom)
+{
+ struct _lldpctl_atom_any_list_t *vlist =
+ (struct _lldpctl_atom_any_list_t *)atom;
+ return (lldpctl_atom_iter_t *)TAILQ_FIRST(&vlist->parent->port->p_pids);
+}
+
+static lldpctl_atom_iter_t *
+_lldpctl_atom_next_pis_list(lldpctl_atom_t *atom, lldpctl_atom_iter_t *iter)
+{
+ struct lldpd_pi *pi = (struct lldpd_pi *)iter;
+ return (lldpctl_atom_iter_t *)TAILQ_NEXT(pi, p_entries);
+}
+
+static lldpctl_atom_t *
+_lldpctl_atom_value_pis_list(lldpctl_atom_t *atom, lldpctl_atom_iter_t *iter)
+{
+ struct _lldpctl_atom_any_list_t *vlist =
+ (struct _lldpctl_atom_any_list_t *)atom;
+ struct lldpd_pi *pi = (struct lldpd_pi *)iter;
+ return _lldpctl_new_atom(atom->conn, atom_pi, vlist->parent, pi);
+}
+
+static int
+_lldpctl_atom_new_pi(lldpctl_atom_t *atom, va_list ap)
+{
+ struct _lldpctl_atom_pi_t *pi = (struct _lldpctl_atom_pi_t *)atom;
+ pi->parent = va_arg(ap, struct _lldpctl_atom_port_t *);
+ pi->pi = va_arg(ap, struct lldpd_pi *);
+ lldpctl_atom_inc_ref((lldpctl_atom_t *)pi->parent);
+ return 1;
+}
+
+static void
+_lldpctl_atom_free_pi(lldpctl_atom_t *atom)
+{
+ struct _lldpctl_atom_pi_t *pi = (struct _lldpctl_atom_pi_t *)atom;
+ lldpctl_atom_dec_ref((lldpctl_atom_t *)pi->parent);
+}
+
+static const uint8_t *
+_lldpctl_atom_get_buf_pi(lldpctl_atom_t *atom, lldpctl_key_t key, size_t *n)
+{
+ struct _lldpctl_atom_pi_t *m = (struct _lldpctl_atom_pi_t *)atom;
+
+ /* Local and remote port */
+ switch (key) {
+ case lldpctl_k_pi_id:
+ *n = m->pi->p_pi_len;
+ return (const uint8_t *)m->pi->p_pi;
+ default:
+ SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
+ return NULL;
+ }
+}
+
+static struct atom_builder vlans_list = { atom_vlans_list,
+ sizeof(struct _lldpctl_atom_any_list_t), .init = _lldpctl_atom_new_any_list,
+ .free = _lldpctl_atom_free_any_list, .iter = _lldpctl_atom_iter_vlans_list,
+ .next = _lldpctl_atom_next_vlans_list,
+ .value = _lldpctl_atom_value_vlans_list };
+
+static struct atom_builder vlan = { atom_vlan, sizeof(struct _lldpctl_atom_vlan_t),
+ .init = _lldpctl_atom_new_vlan, .free = _lldpctl_atom_free_vlan,
+ .get_str = _lldpctl_atom_get_str_vlan, .get_int = _lldpctl_atom_get_int_vlan };
+
+static struct atom_builder ppvids_list = { atom_ppvids_list,
+ sizeof(struct _lldpctl_atom_any_list_t), .init = _lldpctl_atom_new_any_list,
+ .free = _lldpctl_atom_free_any_list, .iter = _lldpctl_atom_iter_ppvids_list,
+ .next = _lldpctl_atom_next_ppvids_list,
+ .value = _lldpctl_atom_value_ppvids_list };
+
+static struct atom_builder ppvid = { atom_ppvid, sizeof(struct _lldpctl_atom_ppvid_t),
+ .init = _lldpctl_atom_new_ppvid, .free = _lldpctl_atom_free_ppvid,
+ .get_int = _lldpctl_atom_get_int_ppvid };
+
+static struct atom_builder pis_list = { atom_pis_list,
+ sizeof(struct _lldpctl_atom_any_list_t), .init = _lldpctl_atom_new_any_list,
+ .free = _lldpctl_atom_free_any_list, .iter = _lldpctl_atom_iter_pis_list,
+ .next = _lldpctl_atom_next_pis_list, .value = _lldpctl_atom_value_pis_list };
+
+static struct atom_builder pi = { atom_pi, sizeof(struct _lldpctl_atom_pi_t),
+ .init = _lldpctl_atom_new_pi, .free = _lldpctl_atom_free_pi,
+ .get_buffer = _lldpctl_atom_get_buf_pi };
+
+ATOM_BUILDER_REGISTER(vlans_list, 9);
+ATOM_BUILDER_REGISTER(vlan, 10);
+ATOM_BUILDER_REGISTER(ppvids_list, 11);
+ATOM_BUILDER_REGISTER(ppvid, 12);
+ATOM_BUILDER_REGISTER(pis_list, 13);
+ATOM_BUILDER_REGISTER(pi, 14);
+
+#endif
diff --git a/src/lib/atoms/dot3.c b/src/lib/atoms/dot3.c
new file mode 100644
index 0000000..f3f490a
--- /dev/null
+++ b/src/lib/atoms/dot3.c
@@ -0,0 +1,474 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/*
+ * Copyright (c) 2015 Vincent Bernat <vincent@bernat.im>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+#include <arpa/inet.h>
+
+#include "../lldpctl.h"
+#include "../../log.h"
+#include "../atom.h"
+#include "../helpers.h"
+
+#ifdef ENABLE_DOT3
+
+static lldpctl_map_t port_dot3_power_devicetype_map[] = {
+ { LLDP_DOT3_POWER_PSE, "PSE" }, { LLDP_DOT3_POWER_PD, "PD" }, { 0, NULL }
+};
+
+static lldpctl_map_t port_dot3_power_pse_source_map[] = { { LLDP_DOT3_POWER_SOURCE_BOTH,
+ "PSE + Local" },
+ { LLDP_DOT3_POWER_SOURCE_PSE, "PSE" }, { 0, NULL } };
+
+static lldpctl_map_t port_dot3_power_pd_source_map[] = {
+ { LLDP_DOT3_POWER_SOURCE_BACKUP, "Backup source" },
+ { LLDP_DOT3_POWER_SOURCE_PRIMARY, "Primary power source" }, { 0, NULL }
+};
+
+static struct atom_map port_dot3_power_pairs_map = {
+ .key = lldpctl_k_dot3_power_pairs,
+ .map = { { LLDP_DOT3_POWERPAIRS_SIGNAL, "signal" },
+ { LLDP_DOT3_POWERPAIRS_SPARE, "spare" }, { 0, NULL } },
+};
+
+static struct atom_map port_dot3_power_class_map = {
+ .key = lldpctl_k_dot3_power_class,
+ .map = { { 1, "class 0" }, { 2, "class 1" }, { 3, "class 2" }, { 4, "class 3" },
+ { 5, "class 4" }, { 0, NULL } },
+};
+
+static struct atom_map port_dot3_power_priority_map = {
+ .key = lldpctl_k_dot3_power_priority,
+ .map = {
+ { 0, "unknown" },
+ { LLDP_MED_POW_PRIO_CRITICAL, "critical" },
+ { LLDP_MED_POW_PRIO_HIGH, "high" },
+ { LLDP_MED_POW_PRIO_LOW, "low" },
+ { 0, NULL },
+ },
+};
+
+static struct atom_map port_dot3_power_pd_4pid_map = {
+ .key = lldpctl_k_dot3_power_pd_4pid,
+ .map = {
+ { 0, "PD does not support powering both modes" },
+ { 1, "PD supports powering both modes" },
+ { 0, NULL},
+ },
+};
+
+static struct atom_map port_dot3_power_pse_status_map = {
+ .key = lldpctl_k_dot3_power_pse_status,
+ .map = {
+ { 0, "unknown" },
+ { 1, "2-pair powering" },
+ { 2, "4-pair powering dual-signature PD" },
+ { 3, "4-pair powering single-signature PD" },
+ { 0, NULL },
+ },
+};
+
+static struct atom_map port_dot3_power_pd_status_map = {
+ .key = lldpctl_k_dot3_power_pd_status,
+ .map = {
+ { 0, "unknown" },
+ { 1, "2-pair powered PD" },
+ { 2, "4-pair powered dual-signature PD" },
+ { 3, "4-pair powered single-signature PD" },
+ { 0, NULL },
+ },
+};
+
+static struct atom_map port_dot3_power_pse_pairs_ext_map = {
+ .key = lldpctl_k_dot3_power_pse_pairs_ext,
+ .map = {
+ { 0, "unknown" },
+ { 1, "alternative A" },
+ { 2, "alternative B" },
+ { 3, "both alternatives" },
+ { 0, NULL },
+ },
+};
+
+static struct atom_map port_dot3_power_class_a_map = {
+ .key = lldpctl_k_dot3_power_class_a,
+ .map = {
+ { 0, "unknown" },
+ { 1, "class 1" },
+ { 2, "class 2" },
+ { 3, "class 3" },
+ { 4, "class 4" },
+ { 5, "class 5" },
+ { 6, "unknown" },
+ { 7, "single-signature PD or 2-pair only PSE" },
+ { 0, NULL },
+ },
+};
+
+static struct atom_map port_dot3_power_class_b_map = {
+ .key = lldpctl_k_dot3_power_class_b,
+ .map = {
+ { 0, "unknown" },
+ { 1, "class 1" },
+ { 2, "class 2" },
+ { 3, "class 3" },
+ { 4, "class 4" },
+ { 5, "class 5" },
+ { 6, "unknown" },
+ { 7, "single-signature PD or 2-pair only PSE" },
+ { 0, NULL },
+ },
+};
+
+static struct atom_map port_dot3_power_class_ext_map = {
+ .key = lldpctl_k_dot3_power_class_ext,
+ .map = {
+ { 0, "unknown" },
+ { 1, "class 1" },
+ { 2, "class 2" },
+ { 3, "class 3" },
+ { 4, "class 4" },
+ { 5, "class 5" },
+ { 6, "class 6" },
+ { 7, "class 7" },
+ { 8, "class 8" },
+ { 9, "unknown" },
+ { 10, "unknown" },
+ { 11, "unknown" },
+ { 12, "unknown" },
+ { 13, "unknown" },
+ { 14, "unknown" },
+ { 15, "dual-signature PD" },
+ { 0, NULL },
+ },
+};
+
+static struct atom_map port_dot3_power_type_ext_map = {
+ .key = lldpctl_k_dot3_power_type_ext,
+ .map = {
+ { LLDP_DOT3_POWER_8023BT_OFF, "802.3bt off" },
+ { 1, "type 3 PSE" },
+ { 2, "type 4 PSE" },
+ { 3, "type 3 single-signature PD" },
+ { 4, "type 3 dual-signature PD" },
+ { 5, "type 4 single-signature PD" },
+ { 6, "type 4 dual-signature PD" },
+ { 7, "unknown" },
+ { 8, "unknown" },
+ { 0, NULL },
+ },
+};
+
+static struct atom_map port_dot3_power_pd_load_map = {
+ .key = lldpctl_k_dot3_power_pd_load,
+ .map = {
+ { 0, "PD is single- or dual-signature and power is not "
+ "electrically isolated" },
+ { 1, "PD is dual-signature and power is electrically "
+ "isolated" },
+ { 0, NULL },
+ },
+};
+
+ATOM_MAP_REGISTER(port_dot3_power_pairs_map, 4);
+ATOM_MAP_REGISTER(port_dot3_power_class_map, 5);
+ATOM_MAP_REGISTER(port_dot3_power_priority_map, 6);
+
+static int
+_lldpctl_atom_new_dot3_power(lldpctl_atom_t *atom, va_list ap)
+{
+ struct _lldpctl_atom_dot3_power_t *dpow =
+ (struct _lldpctl_atom_dot3_power_t *)atom;
+ dpow->parent = va_arg(ap, struct _lldpctl_atom_port_t *);
+ lldpctl_atom_inc_ref((lldpctl_atom_t *)dpow->parent);
+ return 1;
+}
+
+static void
+_lldpctl_atom_free_dot3_power(lldpctl_atom_t *atom)
+{
+ struct _lldpctl_atom_dot3_power_t *dpow =
+ (struct _lldpctl_atom_dot3_power_t *)atom;
+ lldpctl_atom_dec_ref((lldpctl_atom_t *)dpow->parent);
+}
+
+static const char *
+_lldpctl_atom_get_str_dot3_power(lldpctl_atom_t *atom, lldpctl_key_t key)
+{
+ struct _lldpctl_atom_dot3_power_t *dpow =
+ (struct _lldpctl_atom_dot3_power_t *)atom;
+ struct lldpd_port *port = dpow->parent->port;
+
+ /* Local and remote port */
+ switch (key) {
+ case lldpctl_k_dot3_power_devicetype:
+ return map_lookup(port_dot3_power_devicetype_map,
+ port->p_power.devicetype);
+ case lldpctl_k_dot3_power_pairs:
+ return map_lookup(port_dot3_power_pairs_map.map, port->p_power.pairs);
+ case lldpctl_k_dot3_power_class:
+ return map_lookup(port_dot3_power_class_map.map, port->p_power.class);
+ case lldpctl_k_dot3_power_source:
+ return map_lookup((port->p_power.devicetype == LLDP_DOT3_POWER_PSE) ?
+ port_dot3_power_pse_source_map :
+ port_dot3_power_pd_source_map,
+ port->p_power.source);
+ case lldpctl_k_dot3_power_priority:
+ return map_lookup(port_dot3_power_priority_map.map,
+ port->p_power.priority);
+ case lldpctl_k_dot3_power_pd_4pid:
+ return map_lookup(port_dot3_power_pd_4pid_map.map,
+ port->p_power.pd_4pid);
+ case lldpctl_k_dot3_power_pse_status:
+ return map_lookup(port_dot3_power_pse_status_map.map,
+ port->p_power.pse_status);
+ case lldpctl_k_dot3_power_pd_status:
+ return map_lookup(port_dot3_power_pd_status_map.map,
+ port->p_power.pd_status);
+ case lldpctl_k_dot3_power_pse_pairs_ext:
+ return map_lookup(port_dot3_power_pse_pairs_ext_map.map,
+ port->p_power.pse_pairs_ext);
+ case lldpctl_k_dot3_power_class_a:
+ return map_lookup(port_dot3_power_class_a_map.map,
+ port->p_power.class_a);
+ case lldpctl_k_dot3_power_class_b:
+ return map_lookup(port_dot3_power_class_b_map.map,
+ port->p_power.class_b);
+ case lldpctl_k_dot3_power_class_ext:
+ return map_lookup(port_dot3_power_class_ext_map.map,
+ port->p_power.class_ext);
+ case lldpctl_k_dot3_power_type_ext:
+ return map_lookup(port_dot3_power_type_ext_map.map,
+ port->p_power.type_ext);
+ case lldpctl_k_dot3_power_pd_load:
+ return map_lookup(port_dot3_power_pd_load_map.map,
+ port->p_power.pd_load);
+ default:
+ SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
+ return NULL;
+ }
+}
+
+static long int
+_lldpctl_atom_get_int_dot3_power(lldpctl_atom_t *atom, lldpctl_key_t key)
+{
+ struct _lldpctl_atom_dot3_power_t *dpow =
+ (struct _lldpctl_atom_dot3_power_t *)atom;
+ struct lldpd_port *port = dpow->parent->port;
+
+ /* Local and remote port */
+ switch (key) {
+ case lldpctl_k_dot3_power_devicetype:
+ return port->p_power.devicetype;
+ case lldpctl_k_dot3_power_supported:
+ return port->p_power.supported;
+ case lldpctl_k_dot3_power_enabled:
+ return port->p_power.enabled;
+ case lldpctl_k_dot3_power_paircontrol:
+ return port->p_power.paircontrol;
+ case lldpctl_k_dot3_power_pairs:
+ return port->p_power.pairs;
+ case lldpctl_k_dot3_power_class:
+ return port->p_power.class;
+ case lldpctl_k_dot3_power_type:
+ return port->p_power.powertype;
+ case lldpctl_k_dot3_power_source:
+ return port->p_power.source;
+ case lldpctl_k_dot3_power_priority:
+ return port->p_power.priority;
+ case lldpctl_k_dot3_power_requested:
+ return port->p_power.requested * 100;
+ case lldpctl_k_dot3_power_allocated:
+ return port->p_power.allocated * 100;
+ /* 802.3bt additions */
+ case lldpctl_k_dot3_power_pd_4pid:
+ return port->p_power.pd_4pid;
+ case lldpctl_k_dot3_power_requested_a:
+ return port->p_power.requested_a * 100;
+ case lldpctl_k_dot3_power_requested_b:
+ return port->p_power.requested_b * 100;
+ case lldpctl_k_dot3_power_allocated_a:
+ return port->p_power.allocated_a * 100;
+ case lldpctl_k_dot3_power_allocated_b:
+ return port->p_power.allocated_b * 100;
+ case lldpctl_k_dot3_power_pse_status:
+ return port->p_power.pse_status;
+ case lldpctl_k_dot3_power_pd_status:
+ return port->p_power.pd_status;
+ case lldpctl_k_dot3_power_pse_pairs_ext:
+ return port->p_power.pse_pairs_ext;
+ case lldpctl_k_dot3_power_class_a:
+ return port->p_power.class_a;
+ case lldpctl_k_dot3_power_class_b:
+ return port->p_power.class_b;
+ case lldpctl_k_dot3_power_class_ext:
+ return port->p_power.class_ext;
+ case lldpctl_k_dot3_power_type_ext:
+ return port->p_power.type_ext;
+ case lldpctl_k_dot3_power_pd_load:
+ return port->p_power.pd_load;
+ case lldpctl_k_dot3_power_pse_max:
+ return port->p_power.pse_max * 100;
+ default:
+ return SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
+ }
+}
+
+static lldpctl_atom_t *
+_lldpctl_atom_set_int_dot3_power(lldpctl_atom_t *atom, lldpctl_key_t key,
+ long int value)
+{
+ struct _lldpctl_atom_dot3_power_t *dpow =
+ (struct _lldpctl_atom_dot3_power_t *)atom;
+ struct lldpd_port *port = dpow->parent->port;
+
+ /* Only local port can be modified */
+ if (!dpow->parent->local) {
+ SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
+ return NULL;
+ }
+
+ switch (key) {
+ case lldpctl_k_dot3_power_devicetype:
+ switch (value) {
+ case 0: /* Disabling */
+ case LLDP_DOT3_POWER_PSE:
+ case LLDP_DOT3_POWER_PD:
+ port->p_power.devicetype = value;
+ return atom;
+ default:
+ goto bad;
+ }
+ case lldpctl_k_dot3_power_supported:
+ switch (value) {
+ case 0:
+ case 1:
+ port->p_power.supported = value;
+ return atom;
+ default:
+ goto bad;
+ }
+ case lldpctl_k_dot3_power_enabled:
+ switch (value) {
+ case 0:
+ case 1:
+ port->p_power.enabled = value;
+ return atom;
+ default:
+ goto bad;
+ }
+ case lldpctl_k_dot3_power_paircontrol:
+ switch (value) {
+ case 0:
+ case 1:
+ port->p_power.paircontrol = value;
+ return atom;
+ default:
+ goto bad;
+ }
+ case lldpctl_k_dot3_power_pairs:
+ switch (value) {
+ case 1:
+ case 2:
+ port->p_power.pairs = value;
+ return atom;
+ default:
+ goto bad;
+ }
+ case lldpctl_k_dot3_power_class:
+ if (value < 0 || value > 5) goto bad;
+ port->p_power.class = value;
+ return atom;
+ case lldpctl_k_dot3_power_type:
+ switch (value) {
+ case LLDP_DOT3_POWER_8023AT_TYPE1:
+ case LLDP_DOT3_POWER_8023AT_TYPE2:
+ case LLDP_DOT3_POWER_8023AT_OFF:
+ port->p_power.powertype = value;
+ return atom;
+ default:
+ goto bad;
+ }
+ case lldpctl_k_dot3_power_source:
+ if (value < 0 || value > 3) goto bad;
+ port->p_power.source = value;
+ return atom;
+ case lldpctl_k_dot3_power_priority:
+ switch (value) {
+ case LLDP_DOT3_POWER_PRIO_UNKNOWN:
+ case LLDP_DOT3_POWER_PRIO_CRITICAL:
+ case LLDP_DOT3_POWER_PRIO_HIGH:
+ case LLDP_DOT3_POWER_PRIO_LOW:
+ port->p_power.priority = value;
+ return atom;
+ default:
+ goto bad;
+ }
+ case lldpctl_k_dot3_power_allocated:
+ if (value < 0) goto bad;
+ port->p_power.allocated = value / 100;
+ return atom;
+ case lldpctl_k_dot3_power_requested:
+ if (value < 0) goto bad;
+ port->p_power.requested = value / 100;
+ return atom;
+ default:
+ SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
+ return NULL;
+ }
+
+ return atom;
+bad:
+ SET_ERROR(atom->conn, LLDPCTL_ERR_BAD_VALUE);
+ return NULL;
+}
+
+static lldpctl_atom_t *
+_lldpctl_atom_set_str_dot3_power(lldpctl_atom_t *atom, lldpctl_key_t key,
+ const char *value)
+{
+ switch (key) {
+ case lldpctl_k_dot3_power_devicetype:
+ return _lldpctl_atom_set_int_dot3_power(atom, key,
+ map_reverse_lookup(port_dot3_power_devicetype_map, value));
+ case lldpctl_k_dot3_power_pairs:
+ return _lldpctl_atom_set_int_dot3_power(atom, key,
+ map_reverse_lookup(port_dot3_power_pairs_map.map, value));
+ case lldpctl_k_dot3_power_class:
+ return _lldpctl_atom_set_int_dot3_power(atom, key,
+ map_reverse_lookup(port_dot3_power_class_map.map, value));
+ case lldpctl_k_dot3_power_priority:
+ return _lldpctl_atom_set_int_dot3_power(atom, key,
+ map_reverse_lookup(port_dot3_power_priority_map.map, value));
+ default:
+ SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
+ return NULL;
+ }
+}
+
+static struct atom_builder dot3_power = { atom_dot3_power,
+ sizeof(struct _lldpctl_atom_dot3_power_t), .init = _lldpctl_atom_new_dot3_power,
+ .free = _lldpctl_atom_free_dot3_power,
+ .get_int = _lldpctl_atom_get_int_dot3_power,
+ .set_int = _lldpctl_atom_set_int_dot3_power,
+ .get_str = _lldpctl_atom_get_str_dot3_power,
+ .set_str = _lldpctl_atom_set_str_dot3_power };
+
+ATOM_BUILDER_REGISTER(dot3_power, 8);
+
+#endif
diff --git a/src/lib/atoms/interface.c b/src/lib/atoms/interface.c
new file mode 100644
index 0000000..95cae3a
--- /dev/null
+++ b/src/lib/atoms/interface.c
@@ -0,0 +1,118 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/*
+ * Copyright (c) 2015 Vincent Bernat <vincent@bernat.im>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+#include <arpa/inet.h>
+
+#include "../lldpctl.h"
+#include "../../log.h"
+#include "../atom.h"
+#include "../helpers.h"
+
+static int
+_lldpctl_atom_new_interfaces_list(lldpctl_atom_t *atom, va_list ap)
+{
+ struct _lldpctl_atom_interfaces_list_t *iflist =
+ (struct _lldpctl_atom_interfaces_list_t *)atom;
+ iflist->ifs = va_arg(ap, struct lldpd_interface_list *);
+ return 1;
+}
+
+static void
+_lldpctl_atom_free_interfaces_list(lldpctl_atom_t *atom)
+{
+ struct _lldpctl_atom_interfaces_list_t *iflist =
+ (struct _lldpctl_atom_interfaces_list_t *)atom;
+ struct lldpd_interface *iface, *iface_next;
+ for (iface = TAILQ_FIRST(iflist->ifs); iface != NULL; iface = iface_next) {
+ /* Don't TAILQ_REMOVE, this is not a real list! */
+ iface_next = TAILQ_NEXT(iface, next);
+ free(iface->name);
+ free(iface);
+ }
+ free(iflist->ifs);
+}
+
+static lldpctl_atom_iter_t *
+_lldpctl_atom_iter_interfaces_list(lldpctl_atom_t *atom)
+{
+ struct _lldpctl_atom_interfaces_list_t *iflist =
+ (struct _lldpctl_atom_interfaces_list_t *)atom;
+ return (lldpctl_atom_iter_t *)TAILQ_FIRST(iflist->ifs);
+}
+
+static lldpctl_atom_iter_t *
+_lldpctl_atom_next_interfaces_list(lldpctl_atom_t *atom, lldpctl_atom_iter_t *iter)
+{
+ return (lldpctl_atom_iter_t *)TAILQ_NEXT((struct lldpd_interface *)iter, next);
+}
+
+static lldpctl_atom_t *
+_lldpctl_atom_value_interfaces_list(lldpctl_atom_t *atom, lldpctl_atom_iter_t *iter)
+{
+ struct lldpd_interface *iface = (struct lldpd_interface *)iter;
+ return _lldpctl_new_atom(atom->conn, atom_interface, iface->name);
+}
+
+static int
+_lldpctl_atom_new_interface(lldpctl_atom_t *atom, va_list ap)
+{
+ struct _lldpctl_atom_interface_t *port =
+ (struct _lldpctl_atom_interface_t *)atom;
+ port->name = strdup(va_arg(ap, char *));
+ return (port->name != NULL);
+}
+
+static void
+_lldpctl_atom_free_interface(lldpctl_atom_t *atom)
+{
+ struct _lldpctl_atom_interface_t *port =
+ (struct _lldpctl_atom_interface_t *)atom;
+ free(port->name);
+}
+
+static const char *
+_lldpctl_atom_get_str_interface(lldpctl_atom_t *atom, lldpctl_key_t key)
+{
+ struct _lldpctl_atom_interface_t *port =
+ (struct _lldpctl_atom_interface_t *)atom;
+ switch (key) {
+ case lldpctl_k_interface_name:
+ return port->name;
+ default:
+ SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
+ return NULL;
+ }
+}
+
+static struct atom_builder interfaces_list = { atom_interfaces_list,
+ sizeof(struct _lldpctl_atom_interfaces_list_t),
+ .init = _lldpctl_atom_new_interfaces_list,
+ .free = _lldpctl_atom_free_interfaces_list,
+ .iter = _lldpctl_atom_iter_interfaces_list,
+ .next = _lldpctl_atom_next_interfaces_list,
+ .value = _lldpctl_atom_value_interfaces_list };
+
+static struct atom_builder interface = { atom_interface,
+ sizeof(struct _lldpctl_atom_interface_t), .init = _lldpctl_atom_new_interface,
+ .free = _lldpctl_atom_free_interface,
+ .get_str = _lldpctl_atom_get_str_interface };
+
+ATOM_BUILDER_REGISTER(interfaces_list, 2);
+ATOM_BUILDER_REGISTER(interface, 3);
diff --git a/src/lib/atoms/med.c b/src/lib/atoms/med.c
new file mode 100644
index 0000000..c1e3234
--- /dev/null
+++ b/src/lib/atoms/med.c
@@ -0,0 +1,1123 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/*
+ * Copyright (c) 2015 Vincent Bernat <vincent@bernat.im>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+#include <arpa/inet.h>
+
+#include "../lldpctl.h"
+#include "../../log.h"
+#include "../atom.h"
+#include "../helpers.h"
+#include "../fixedpoint.h"
+
+#ifdef ENABLE_LLDPMED
+
+static lldpctl_map_t port_med_location_map[] = {
+ { LLDP_MED_LOCFORMAT_COORD, "Coordinates" },
+ { LLDP_MED_LOCFORMAT_CIVIC, "Civic address" },
+ { LLDP_MED_LOCFORMAT_ELIN, "ELIN" },
+ { 0, NULL },
+};
+
+static lldpctl_map_t port_med_pow_devicetype_map[] = {
+ { LLDP_MED_POW_TYPE_PSE, "PSE" },
+ { LLDP_MED_POW_TYPE_PD, "PD" },
+ { 0, NULL },
+};
+
+static lldpctl_map_t port_med_pow_source_map[] = {
+ { LLDP_MED_POW_SOURCE_PRIMARY, "Primary Power Source" },
+ { LLDP_MED_POW_SOURCE_BACKUP, "Backup Power Source / Power Conservation Mode" },
+ { LLDP_MED_POW_SOURCE_PSE, "PSE" },
+ { LLDP_MED_POW_SOURCE_LOCAL, "Local" },
+ { LLDP_MED_POW_SOURCE_BOTH, "PSE + Local" },
+ { 0, NULL },
+};
+
+static lldpctl_map_t port_med_pow_source_map2[] = {
+ { 0, "unknown" },
+ { LLDP_MED_POW_SOURCE_PRIMARY, "primary" },
+ { LLDP_MED_POW_SOURCE_BACKUP, "backup" },
+ { LLDP_MED_POW_SOURCE_PSE, "pse" },
+ { LLDP_MED_POW_SOURCE_LOCAL, "local" },
+ { LLDP_MED_POW_SOURCE_BOTH, "both" },
+ { 0, NULL },
+};
+
+static struct atom_map port_med_geoid_map = {
+ .key = lldpctl_k_med_location_geoid,
+ .map = {
+ { LLDP_MED_LOCATION_GEOID_WGS84, "WGS84" },
+ { LLDP_MED_LOCATION_GEOID_NAD83, "NAD83" },
+ { LLDP_MED_LOCATION_GEOID_NAD83_MLLW, "NAD83/MLLW" },
+ { 0, NULL },
+ },
+};
+
+static struct atom_map civic_address_type_map = {
+ .key = lldpctl_k_med_civicaddress_type,
+ .map = {
+ { 0, "Language" },
+ { 1, "Country subdivision" },
+ { 2, "County" },
+ { 3, "City" },
+ { 4, "City division" },
+ { 5, "Block" },
+ { 6, "Street" },
+ { 16, "Direction" },
+ { 17, "Trailing street suffix" },
+ { 18, "Street suffix" },
+ { 19, "Number" },
+ { 20, "Number suffix" },
+ { 21, "Landmark" },
+ { 22, "Additional" },
+ { 23, "Name" },
+ { 24, "ZIP" },
+ { 25, "Building" },
+ { 26, "Unit" },
+ { 27, "Floor" },
+ { 28, "Room" },
+ { 29, "Place type" },
+ { 128, "Script" },
+ { 0, NULL },
+ },
+};
+
+static struct atom_map port_med_policy_map = { .key = lldpctl_k_med_policy_type,
+ .map = {
+ { LLDP_MED_APPTYPE_VOICE, "Voice" },
+ { LLDP_MED_APPTYPE_VOICESIGNAL, "Voice Signaling" },
+ { LLDP_MED_APPTYPE_GUESTVOICE, "Guest Voice" },
+ { LLDP_MED_APPTYPE_GUESTVOICESIGNAL, "Guest Voice Signaling" },
+ { LLDP_MED_APPTYPE_SOFTPHONEVOICE, "Softphone Voice" },
+ { LLDP_MED_APPTYPE_VIDEOCONFERENCE, "Video Conferencing" },
+ { LLDP_MED_APPTYPE_VIDEOSTREAM, "Streaming Video" },
+ { LLDP_MED_APPTYPE_VIDEOSIGNAL, "Video Signaling" },
+ { 0, NULL },
+ } };
+
+static struct atom_map port_med_policy_prio_map = {
+ .key = lldpctl_k_med_policy_priority,
+ .map = {
+ { 1, "Background" },
+ { 0, "Best effort" },
+ { 2, "Excellent effort" },
+ { 3, "Critical applications" },
+ { 4, "Video" },
+ { 5, "Voice" },
+ { 6, "Internetwork control" },
+ { 7, "Network control" },
+ { 0, NULL },
+ },
+};
+
+static struct atom_map port_med_pow_priority_map = {
+ .key = lldpctl_k_med_power_priority,
+ .map = {
+ { 0, "unknown" },
+ { LLDP_MED_POW_PRIO_CRITICAL, "critical" },
+ { LLDP_MED_POW_PRIO_HIGH, "high" },
+ { LLDP_MED_POW_PRIO_LOW, "low" },
+ { 0, NULL },
+ },
+};
+
+ATOM_MAP_REGISTER(port_med_geoid_map, 7);
+ATOM_MAP_REGISTER(civic_address_type_map, 8);
+ATOM_MAP_REGISTER(port_med_policy_map, 9);
+ATOM_MAP_REGISTER(port_med_policy_prio_map, 10);
+ATOM_MAP_REGISTER(port_med_pow_priority_map, 11);
+
+static lldpctl_atom_iter_t *
+_lldpctl_atom_iter_med_policies_list(lldpctl_atom_t *atom)
+{
+ int i;
+ struct _lldpctl_atom_any_list_t *vlist =
+ (struct _lldpctl_atom_any_list_t *)atom;
+ for (i = 0; i < LLDP_MED_APPTYPE_LAST; i++)
+ vlist->parent->port->p_med_policy[i].index = i;
+ return (lldpctl_atom_iter_t *)&vlist->parent->port->p_med_policy[0];
+}
+
+static lldpctl_atom_iter_t *
+_lldpctl_atom_next_med_policies_list(lldpctl_atom_t *atom, lldpctl_atom_iter_t *iter)
+{
+ struct lldpd_med_policy *policy = (struct lldpd_med_policy *)iter;
+ if (policy->index == LLDP_MED_APPTYPE_LAST - 1) return NULL;
+ return (lldpctl_atom_iter_t *)(++policy);
+}
+
+static lldpctl_atom_t *
+_lldpctl_atom_value_med_policies_list(lldpctl_atom_t *atom, lldpctl_atom_iter_t *iter)
+{
+ struct _lldpctl_atom_any_list_t *vlist =
+ (struct _lldpctl_atom_any_list_t *)atom;
+ struct lldpd_med_policy *policy = (struct lldpd_med_policy *)iter;
+ return _lldpctl_new_atom(atom->conn, atom_med_policy, vlist->parent, policy);
+}
+
+static int
+_lldpctl_atom_new_med_policy(lldpctl_atom_t *atom, va_list ap)
+{
+ struct _lldpctl_atom_med_policy_t *policy =
+ (struct _lldpctl_atom_med_policy_t *)atom;
+ policy->parent = va_arg(ap, struct _lldpctl_atom_port_t *);
+ policy->policy = va_arg(ap, struct lldpd_med_policy *);
+ lldpctl_atom_inc_ref((lldpctl_atom_t *)policy->parent);
+ return 1;
+}
+
+static void
+_lldpctl_atom_free_med_policy(lldpctl_atom_t *atom)
+{
+ struct _lldpctl_atom_med_policy_t *policy =
+ (struct _lldpctl_atom_med_policy_t *)atom;
+ lldpctl_atom_dec_ref((lldpctl_atom_t *)policy->parent);
+}
+
+static long int
+_lldpctl_atom_get_int_med_policy(lldpctl_atom_t *atom, lldpctl_key_t key)
+{
+ struct _lldpctl_atom_med_policy_t *m =
+ (struct _lldpctl_atom_med_policy_t *)atom;
+
+ /* Local and remote port */
+ switch (key) {
+ case lldpctl_k_med_policy_type:
+ return m->policy->type;
+ case lldpctl_k_med_policy_unknown:
+ return m->policy->unknown;
+ case lldpctl_k_med_policy_tagged:
+ return m->policy->tagged;
+ case lldpctl_k_med_policy_vid:
+ return m->policy->vid;
+ case lldpctl_k_med_policy_dscp:
+ return m->policy->dscp;
+ case lldpctl_k_med_policy_priority:
+ return m->policy->priority;
+ default:
+ return SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
+ }
+}
+
+static lldpctl_atom_t *
+_lldpctl_atom_set_int_med_policy(lldpctl_atom_t *atom, lldpctl_key_t key,
+ long int value)
+{
+ struct _lldpctl_atom_med_policy_t *m =
+ (struct _lldpctl_atom_med_policy_t *)atom;
+
+ /* Only local port can be modified */
+ if (!m->parent->local) {
+ SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
+ return NULL;
+ }
+
+ switch (key) {
+ case lldpctl_k_med_policy_type:
+ /* We let set any policy type, including one whose are not
+ * compatible with the index. If a policy type is set, the index
+ * will be ignored. If a policy type is 0, the index will be
+ * used to know which policy to "erase". */
+ if (value < 0 || value > LLDP_MED_APPTYPE_LAST) goto bad;
+ m->policy->type = value;
+ return atom;
+ case lldpctl_k_med_policy_unknown:
+ if (value != 0 && value != 1) goto bad;
+ m->policy->unknown = value;
+ return atom;
+ case lldpctl_k_med_policy_tagged:
+ if (value != 0 && value != 1) goto bad;
+ m->policy->tagged = value;
+ return atom;
+ case lldpctl_k_med_policy_vid:
+ if (value < 0 || value > 4094) goto bad;
+ m->policy->vid = value;
+ return atom;
+ case lldpctl_k_med_policy_dscp:
+ if (value < 0 || value > 63) goto bad;
+ m->policy->dscp = value;
+ return atom;
+ case lldpctl_k_med_policy_priority:
+ if (value < 0 || value > 7) goto bad;
+ m->policy->priority = value;
+ return atom;
+ default:
+ SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
+ return NULL;
+ }
+
+ return atom;
+bad:
+ SET_ERROR(atom->conn, LLDPCTL_ERR_BAD_VALUE);
+ return NULL;
+}
+
+static const char *
+_lldpctl_atom_get_str_med_policy(lldpctl_atom_t *atom, lldpctl_key_t key)
+{
+ struct _lldpctl_atom_med_policy_t *m =
+ (struct _lldpctl_atom_med_policy_t *)atom;
+
+ /* Local and remote port */
+ switch (key) {
+ case lldpctl_k_med_policy_type:
+ return map_lookup(port_med_policy_map.map, m->policy->type);
+ case lldpctl_k_med_policy_priority:
+ return map_lookup(port_med_policy_prio_map.map, m->policy->priority);
+ default:
+ SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
+ return NULL;
+ }
+}
+
+static lldpctl_atom_t *
+_lldpctl_atom_set_str_med_policy(lldpctl_atom_t *atom, lldpctl_key_t key,
+ const char *value)
+{
+ /* Local and remote port */
+ switch (key) {
+ case lldpctl_k_med_policy_type:
+ return _lldpctl_atom_set_int_med_policy(atom, key,
+ map_reverse_lookup(port_med_policy_map.map, value));
+ case lldpctl_k_med_policy_priority:
+ return _lldpctl_atom_set_int_med_policy(atom, key,
+ map_reverse_lookup(port_med_policy_prio_map.map, value));
+ default:
+ SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
+ return NULL;
+ }
+}
+
+static lldpctl_atom_iter_t *
+_lldpctl_atom_iter_med_locations_list(lldpctl_atom_t *atom)
+{
+ int i;
+ struct _lldpctl_atom_any_list_t *vlist =
+ (struct _lldpctl_atom_any_list_t *)atom;
+ for (i = 0; i < LLDP_MED_LOCFORMAT_LAST; i++)
+ vlist->parent->port->p_med_location[i].index = i;
+ return (lldpctl_atom_iter_t *)&vlist->parent->port->p_med_location[0];
+}
+
+static lldpctl_atom_iter_t *
+_lldpctl_atom_next_med_locations_list(lldpctl_atom_t *atom, lldpctl_atom_iter_t *iter)
+{
+ struct lldpd_med_loc *location = (struct lldpd_med_loc *)iter;
+ if (location->index == LLDP_MED_LOCFORMAT_LAST - 1) return NULL;
+ return (lldpctl_atom_iter_t *)(++location);
+}
+
+static lldpctl_atom_t *
+_lldpctl_atom_value_med_locations_list(lldpctl_atom_t *atom, lldpctl_atom_iter_t *iter)
+{
+ struct _lldpctl_atom_any_list_t *vlist =
+ (struct _lldpctl_atom_any_list_t *)atom;
+ struct lldpd_med_loc *location = (struct lldpd_med_loc *)iter;
+ return _lldpctl_new_atom(atom->conn, atom_med_location, vlist->parent,
+ location);
+}
+
+static int
+_lldpctl_atom_new_med_location(lldpctl_atom_t *atom, va_list ap)
+{
+ struct _lldpctl_atom_med_location_t *location =
+ (struct _lldpctl_atom_med_location_t *)atom;
+ location->parent = va_arg(ap, struct _lldpctl_atom_port_t *);
+ location->location = va_arg(ap, struct lldpd_med_loc *);
+ lldpctl_atom_inc_ref((lldpctl_atom_t *)location->parent);
+ return 1;
+}
+
+static void
+_lldpctl_atom_free_med_location(lldpctl_atom_t *atom)
+{
+ struct _lldpctl_atom_med_location_t *location =
+ (struct _lldpctl_atom_med_location_t *)atom;
+ lldpctl_atom_dec_ref((lldpctl_atom_t *)location->parent);
+}
+
+static long int
+_lldpctl_atom_get_int_med_location(lldpctl_atom_t *atom, lldpctl_key_t key)
+{
+ struct _lldpctl_atom_med_location_t *m =
+ (struct _lldpctl_atom_med_location_t *)atom;
+
+ /* Local and remote port */
+ switch (key) {
+ case lldpctl_k_med_location_format:
+ switch (m->location->format) {
+ case LLDP_MED_LOCFORMAT_COORD:
+ if (m->location->data_len != 16) break;
+ return LLDP_MED_LOCFORMAT_COORD;
+ case LLDP_MED_LOCFORMAT_CIVIC:
+ if ((m->location->data_len < 3) ||
+ (m->location->data_len - 1 < m->location->data[0]))
+ break;
+ return LLDP_MED_LOCFORMAT_CIVIC;
+ case LLDP_MED_LOCFORMAT_ELIN:
+ return LLDP_MED_LOCFORMAT_ELIN;
+ default:
+ return 0;
+ }
+ return SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
+ case lldpctl_k_med_location_geoid:
+ if (m->location->format != LLDP_MED_LOCFORMAT_COORD)
+ return SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
+ return m->location->data[15];
+ case lldpctl_k_med_location_altitude_unit:
+ if (m->location->format != LLDP_MED_LOCFORMAT_COORD)
+ return SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
+ return (m->location->data[10] & 0xf0) >> 4;
+ default:
+ return SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
+ }
+}
+
+static lldpctl_atom_t *
+_lldpctl_atom_set_int_med_location(lldpctl_atom_t *atom, lldpctl_key_t key,
+ long int value)
+{
+ struct _lldpctl_atom_med_location_t *mloc =
+ (struct _lldpctl_atom_med_location_t *)atom;
+
+ /* Only local port can be modified */
+ if (!mloc->parent->local) {
+ SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
+ return NULL;
+ }
+
+ switch (key) {
+ case lldpctl_k_med_location_format:
+ switch (value) {
+ case 0: /* Disabling */
+ case LLDP_MED_LOCFORMAT_COORD:
+ mloc->location->format = value;
+ free(mloc->location->data);
+ mloc->location->data = calloc(1, 16);
+ if (mloc->location->data == NULL) {
+ mloc->location->data_len = 0;
+ SET_ERROR(atom->conn, LLDPCTL_ERR_NOMEM);
+ return NULL;
+ }
+ mloc->location->data_len = 16;
+ return atom;
+ case LLDP_MED_LOCFORMAT_CIVIC:
+ mloc->location->format = value;
+ free(mloc->location->data);
+ mloc->location->data = calloc(1, 4);
+ if (mloc->location->data == NULL) {
+ mloc->location->data_len = 0;
+ SET_ERROR(atom->conn, LLDPCTL_ERR_NOMEM);
+ return NULL;
+ }
+ mloc->location->data_len = 4;
+ mloc->location->data[0] = 3;
+ mloc->location->data[1] = 2; /* Client */
+ mloc->location->data[2] = 'U';
+ mloc->location->data[3] = 'S';
+ return atom;
+ case LLDP_MED_LOCFORMAT_ELIN:
+ mloc->location->format = value;
+ free(mloc->location->data);
+ mloc->location->data = NULL;
+ mloc->location->data_len = 0;
+ return atom;
+ default:
+ goto bad;
+ }
+ case lldpctl_k_med_location_geoid:
+ if (mloc->location->format != LLDP_MED_LOCFORMAT_COORD) goto bad;
+ if (mloc->location->data == NULL || mloc->location->data_len != 16)
+ goto bad;
+ switch (value) {
+ case 0:
+ case LLDP_MED_LOCATION_GEOID_WGS84:
+ case LLDP_MED_LOCATION_GEOID_NAD83:
+ case LLDP_MED_LOCATION_GEOID_NAD83_MLLW:
+ mloc->location->data[15] = value;
+ return atom;
+ default:
+ goto bad;
+ }
+ case lldpctl_k_med_location_altitude_unit:
+ if (mloc->location->format != LLDP_MED_LOCFORMAT_COORD) goto bad;
+ if (mloc->location->data == NULL || mloc->location->data_len != 16)
+ goto bad;
+ switch (value) {
+ case 0:
+ case LLDP_MED_LOCATION_ALTITUDE_UNIT_METER:
+ case LLDP_MED_LOCATION_ALTITUDE_UNIT_FLOOR:
+ mloc->location->data[10] &= 0x0f;
+ mloc->location->data[10] |= value << 4;
+ return atom;
+ default:
+ goto bad;
+ }
+ default:
+ SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
+ return NULL;
+ }
+
+ return atom;
+bad:
+ SET_ERROR(atom->conn, LLDPCTL_ERR_BAD_VALUE);
+ return NULL;
+}
+
+static const char *
+read_fixed_precision(lldpctl_atom_t *atom, char *buffer, unsigned shift,
+ unsigned intbits, unsigned fltbits, const char *suffix)
+{
+ struct fp_number fp =
+ fp_buftofp((unsigned char *)buffer, intbits, fltbits, shift);
+ char *result = fp_fptostr(fp, suffix);
+ if (result == NULL) {
+ SET_ERROR(atom->conn, LLDPCTL_ERR_NOMEM);
+ return NULL;
+ }
+
+ size_t len = strlen(result) + 1;
+ char *stored = _lldpctl_alloc_in_atom(atom, len);
+ if (stored == NULL) {
+ free(result);
+ return NULL;
+ }
+ strlcpy(stored, result, len);
+ free(result);
+ return stored;
+}
+
+static const char *
+_lldpctl_atom_get_str_med_location(lldpctl_atom_t *atom, lldpctl_key_t key)
+{
+ struct _lldpctl_atom_med_location_t *m =
+ (struct _lldpctl_atom_med_location_t *)atom;
+ char *value;
+
+ /* Local and remote port */
+ switch (key) {
+ case lldpctl_k_med_location_format:
+ return map_lookup(port_med_location_map, m->location->format);
+ case lldpctl_k_med_location_geoid:
+ if (m->location->format != LLDP_MED_LOCFORMAT_COORD) break;
+ return map_lookup(port_med_geoid_map.map, m->location->data[15]);
+ case lldpctl_k_med_location_latitude:
+ if (m->location->format != LLDP_MED_LOCFORMAT_COORD) break;
+ return read_fixed_precision(atom, m->location->data, 0, 9, 25, "NS");
+ case lldpctl_k_med_location_longitude:
+ if (m->location->format != LLDP_MED_LOCFORMAT_COORD) break;
+ return read_fixed_precision(atom, m->location->data, 40, 9, 25, "EW");
+ case lldpctl_k_med_location_altitude:
+ if (m->location->format != LLDP_MED_LOCFORMAT_COORD) break;
+ return read_fixed_precision(atom, m->location->data, 84, 22, 8, NULL);
+ case lldpctl_k_med_location_altitude_unit:
+ if (m->location->format != LLDP_MED_LOCFORMAT_COORD) break;
+ switch (m->location->data[10] & 0xf0) {
+ case (LLDP_MED_LOCATION_ALTITUDE_UNIT_METER << 4):
+ return "m";
+ case (LLDP_MED_LOCATION_ALTITUDE_UNIT_FLOOR << 4):
+ return "floor";
+ }
+ SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
+ return NULL;
+ case lldpctl_k_med_location_country:
+ if (m->location->format != LLDP_MED_LOCFORMAT_CIVIC) break;
+ if (m->location->data_len < 4) return NULL;
+ value = _lldpctl_alloc_in_atom(atom, 3);
+ if (!value) return NULL;
+ memcpy(value, m->location->data + 2, 2);
+ return value;
+ case lldpctl_k_med_location_elin:
+ if (m->location->format != LLDP_MED_LOCFORMAT_ELIN) break;
+ value = _lldpctl_alloc_in_atom(atom, m->location->data_len + 1);
+ if (!value) return NULL;
+ memcpy(value, m->location->data, m->location->data_len);
+ return value;
+ default:
+ SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
+ return NULL;
+ }
+ SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
+ return NULL;
+}
+
+static lldpctl_atom_t *
+_lldpctl_atom_set_str_med_location(lldpctl_atom_t *atom, lldpctl_key_t key,
+ const char *value)
+{
+ struct _lldpctl_atom_med_location_t *mloc =
+ (struct _lldpctl_atom_med_location_t *)atom;
+ struct fp_number fp;
+ char *end = NULL;
+
+ /* Only local port can be modified */
+ if (!mloc->parent->local) {
+ SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
+ return NULL;
+ }
+
+ switch (key) {
+ case lldpctl_k_med_location_latitude:
+ if (mloc->location->format != LLDP_MED_LOCFORMAT_COORD) goto bad;
+ if (mloc->location->data == NULL || mloc->location->data_len != 16)
+ goto bad;
+ if (value) fp = fp_strtofp(value, &end, 9, 25);
+ if (!end) goto bad;
+ if (end && *end != '\0') {
+ if (*(end + 1) != '\0') goto bad;
+ if (*end == 'S')
+ fp = fp_negate(fp);
+ else if (*end != 'N')
+ goto bad;
+ }
+ fp_fptobuf(fp, (unsigned char *)mloc->location->data, 0);
+ return atom;
+ case lldpctl_k_med_location_longitude:
+ if (mloc->location->format != LLDP_MED_LOCFORMAT_COORD) goto bad;
+ if (mloc->location->data == NULL || mloc->location->data_len != 16)
+ goto bad;
+ if (value) fp = fp_strtofp(value, &end, 9, 25);
+ if (!end) goto bad;
+ if (end && *end != '\0') {
+ if (*(end + 1) != '\0') goto bad;
+ if (*end == 'W')
+ fp = fp_negate(fp);
+ else if (*end != 'E')
+ goto bad;
+ }
+ fp_fptobuf(fp, (unsigned char *)mloc->location->data, 40);
+ return atom;
+ case lldpctl_k_med_location_altitude:
+ if (mloc->location->format != LLDP_MED_LOCFORMAT_COORD) goto bad;
+ if (mloc->location->data == NULL || mloc->location->data_len != 16)
+ goto bad;
+ if (value) fp = fp_strtofp(value, &end, 22, 8);
+ if (!end || *end != '\0') goto bad;
+ fp_fptobuf(fp, (unsigned char *)mloc->location->data, 84);
+ return atom;
+ case lldpctl_k_med_location_altitude_unit:
+ if (!value) goto bad;
+ if (mloc->location->format != LLDP_MED_LOCFORMAT_COORD) goto bad;
+ if (mloc->location->data == NULL || mloc->location->data_len != 16)
+ goto bad;
+ if (!strcmp(value, "m"))
+ return _lldpctl_atom_set_int_med_location(atom, key,
+ LLDP_MED_LOCATION_ALTITUDE_UNIT_METER);
+ if (!strcmp(value, "f") || (!strcmp(value, "floor")))
+ return _lldpctl_atom_set_int_med_location(atom, key,
+ LLDP_MED_LOCATION_ALTITUDE_UNIT_FLOOR);
+ goto bad;
+ break;
+ case lldpctl_k_med_location_geoid:
+ return _lldpctl_atom_set_int_med_location(atom, key,
+ map_reverse_lookup(port_med_geoid_map.map, value));
+ case lldpctl_k_med_location_country:
+ if (mloc->location->format != LLDP_MED_LOCFORMAT_CIVIC) goto bad;
+ if (mloc->location->data == NULL || mloc->location->data_len < 3)
+ goto bad;
+ if (!value || strlen(value) != 2) goto bad;
+ memcpy(mloc->location->data + 2, value, 2);
+ return atom;
+ case lldpctl_k_med_location_elin:
+ if (!value) goto bad;
+ if (mloc->location->format != LLDP_MED_LOCFORMAT_ELIN) goto bad;
+ free(mloc->location->data);
+ mloc->location->data = calloc(1, strlen(value));
+ if (mloc->location->data == NULL) {
+ mloc->location->data_len = 0;
+ SET_ERROR(atom->conn, LLDPCTL_ERR_NOMEM);
+ return NULL;
+ }
+ mloc->location->data_len = strlen(value);
+ memcpy(mloc->location->data, value, mloc->location->data_len);
+ return atom;
+ default:
+ SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
+ return NULL;
+ }
+
+ return atom;
+bad:
+ SET_ERROR(atom->conn, LLDPCTL_ERR_BAD_VALUE);
+ return NULL;
+}
+
+static lldpctl_atom_t *
+_lldpctl_atom_get_atom_med_location(lldpctl_atom_t *atom, lldpctl_key_t key)
+{
+ struct _lldpctl_atom_med_location_t *m =
+ (struct _lldpctl_atom_med_location_t *)atom;
+
+ /* Local and remote port */
+ switch (key) {
+ case lldpctl_k_med_location_ca_elements:
+ if (m->location->format != LLDP_MED_LOCFORMAT_CIVIC) {
+ SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
+ return NULL;
+ }
+ return _lldpctl_new_atom(atom->conn, atom_med_caelements_list, m);
+ default:
+ SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
+ return NULL;
+ }
+}
+
+static lldpctl_atom_t *
+_lldpctl_atom_set_atom_med_location(lldpctl_atom_t *atom, lldpctl_key_t key,
+ lldpctl_atom_t *value)
+{
+ struct _lldpctl_atom_med_location_t *m =
+ (struct _lldpctl_atom_med_location_t *)atom;
+ struct _lldpctl_atom_med_caelement_t *el;
+ uint8_t *new;
+
+ /* Only local port can be modified */
+ if (!m->parent->local) {
+ SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
+ return NULL;
+ }
+
+ switch (key) {
+ case lldpctl_k_med_location_ca_elements:
+ if (value->type != atom_med_caelement) {
+ SET_ERROR(atom->conn, LLDPCTL_ERR_INCORRECT_ATOM_TYPE);
+ return NULL;
+ }
+ if (m->location->format != LLDP_MED_LOCFORMAT_CIVIC) goto bad;
+ if (m->location->data == NULL || m->location->data_len < 3) goto bad;
+
+ /* We append this element. */
+ el = (struct _lldpctl_atom_med_caelement_t *)value;
+ new = malloc(m->location->data_len + 2 + el->len);
+ if (new == NULL) {
+ SET_ERROR(atom->conn, LLDPCTL_ERR_NOMEM);
+ return NULL;
+ }
+ memcpy(new, m->location->data, m->location->data_len);
+ new[m->location->data_len] = el->type;
+ new[m->location->data_len + 1] = el->len;
+ memcpy(new + m->location->data_len + 2, el->value, el->len);
+ new[0] += 2 + el->len;
+ free(m->location->data);
+ m->location->data = (char *)new;
+ m->location->data_len += 2 + el->len;
+ return atom;
+ default:
+ SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
+ return NULL;
+ }
+bad:
+ SET_ERROR(atom->conn, LLDPCTL_ERR_BAD_VALUE);
+ return NULL;
+}
+
+struct ca_iter {
+ uint8_t *data;
+ size_t data_len;
+};
+
+static lldpctl_atom_iter_t *
+_lldpctl_atom_iter_med_caelements_list(lldpctl_atom_t *atom)
+{
+ struct _lldpctl_atom_med_caelements_list_t *plist =
+ (struct _lldpctl_atom_med_caelements_list_t *)atom;
+ struct ca_iter *iter;
+ if (plist->parent->location->data_len < 4 ||
+ *(uint8_t *)plist->parent->location->data < 3 ||
+ !(iter = _lldpctl_alloc_in_atom(atom, sizeof(struct ca_iter))))
+ return NULL;
+ iter->data = (uint8_t *)plist->parent->location->data + 4;
+ iter->data_len = *(uint8_t *)plist->parent->location->data - 3;
+ return (lldpctl_atom_iter_t *)iter;
+}
+
+static lldpctl_atom_iter_t *
+_lldpctl_atom_next_med_caelements_list(lldpctl_atom_t *atom, lldpctl_atom_iter_t *iter)
+{
+ struct ca_iter *cai = (struct ca_iter *)iter;
+ int len;
+ if (cai->data_len < 2) return NULL;
+ len = *((uint8_t *)cai->data + 1);
+ if (cai->data_len < 2 + len) return NULL;
+ cai->data += 2 + len;
+ cai->data_len -= 2 + len;
+ return (lldpctl_atom_iter_t *)cai;
+}
+
+static lldpctl_atom_t *
+_lldpctl_atom_value_med_caelements_list(lldpctl_atom_t *atom, lldpctl_atom_iter_t *iter)
+{
+ struct _lldpctl_atom_med_caelements_list_t *plist =
+ (struct _lldpctl_atom_med_caelements_list_t *)atom;
+ struct ca_iter *cai = (struct ca_iter *)iter;
+ size_t len;
+ if (cai->data_len < 2) return NULL;
+ len = *((uint8_t *)cai->data + 1);
+ if (cai->data_len < 2 + len) return NULL;
+ return _lldpctl_new_atom(atom->conn, atom_med_caelement, plist->parent,
+ (int)*cai->data, cai->data + 2, len);
+}
+
+static lldpctl_atom_t *
+_lldpctl_atom_create_med_caelements_list(lldpctl_atom_t *atom)
+{
+ struct _lldpctl_atom_med_caelements_list_t *plist =
+ (struct _lldpctl_atom_med_caelements_list_t *)atom;
+ return _lldpctl_new_atom(atom->conn, atom_med_caelement, plist->parent, -1,
+ NULL, 0);
+}
+
+static int
+_lldpctl_atom_new_med_caelement(lldpctl_atom_t *atom, va_list ap)
+{
+ struct _lldpctl_atom_med_caelement_t *el =
+ (struct _lldpctl_atom_med_caelement_t *)atom;
+ el->parent = va_arg(ap, struct _lldpctl_atom_med_location_t *);
+ el->type = va_arg(ap, int);
+ el->value = va_arg(ap, uint8_t *);
+ el->len = va_arg(ap, size_t);
+ lldpctl_atom_inc_ref((lldpctl_atom_t *)el->parent);
+ return 1;
+}
+
+static void
+_lldpctl_atom_free_med_caelement(lldpctl_atom_t *atom)
+{
+ struct _lldpctl_atom_med_caelement_t *el =
+ (struct _lldpctl_atom_med_caelement_t *)atom;
+ lldpctl_atom_dec_ref((lldpctl_atom_t *)el->parent);
+}
+
+static long int
+_lldpctl_atom_get_int_med_caelement(lldpctl_atom_t *atom, lldpctl_key_t key)
+{
+ struct _lldpctl_atom_med_caelement_t *m =
+ (struct _lldpctl_atom_med_caelement_t *)atom;
+
+ switch (key) {
+ case lldpctl_k_med_civicaddress_type:
+ return m->type;
+ default:
+ return SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
+ }
+}
+
+static lldpctl_atom_t *
+_lldpctl_atom_set_int_med_caelement(lldpctl_atom_t *atom, lldpctl_key_t key,
+ long int value)
+{
+ struct _lldpctl_atom_med_caelement_t *el =
+ (struct _lldpctl_atom_med_caelement_t *)atom;
+
+ /* Only local port can be modified */
+ if (!el->parent->parent->local) {
+ SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
+ return NULL;
+ }
+
+ switch (key) {
+ case lldpctl_k_med_civicaddress_type:
+ if (value < 0 || value > 128) goto bad;
+ el->type = value;
+ return atom;
+ default:
+ SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
+ return NULL;
+ }
+
+ return atom;
+bad:
+ SET_ERROR(atom->conn, LLDPCTL_ERR_BAD_VALUE);
+ return NULL;
+}
+
+static const char *
+_lldpctl_atom_get_str_med_caelement(lldpctl_atom_t *atom, lldpctl_key_t key)
+{
+ char *value = NULL;
+ struct _lldpctl_atom_med_caelement_t *m =
+ (struct _lldpctl_atom_med_caelement_t *)atom;
+
+ /* Local and remote port */
+ switch (key) {
+ case lldpctl_k_med_civicaddress_type:
+ return map_lookup(civic_address_type_map.map, m->type);
+ case lldpctl_k_med_civicaddress_value:
+ value = _lldpctl_alloc_in_atom(atom, m->len + 1);
+ if (!value) return NULL;
+ memcpy(value, m->value, m->len);
+ return value;
+ default:
+ SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
+ return NULL;
+ }
+}
+
+static lldpctl_atom_t *
+_lldpctl_atom_set_str_med_caelement(lldpctl_atom_t *atom, lldpctl_key_t key,
+ const char *value)
+{
+ struct _lldpctl_atom_med_caelement_t *el =
+ (struct _lldpctl_atom_med_caelement_t *)atom;
+ size_t len;
+
+ /* Only local port can be modified */
+ if (!el->parent->parent->local) {
+ SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
+ return NULL;
+ }
+
+ switch (key) {
+ case lldpctl_k_med_civicaddress_value:
+ if (!value) goto bad;
+ len = strlen(value) + 1;
+ if (len > 251) goto bad;
+ el->value = _lldpctl_alloc_in_atom(atom, len);
+ if (el->value == NULL) return NULL;
+ strlcpy((char *)el->value, value, len);
+ el->len = strlen(value);
+ return atom;
+ case lldpctl_k_med_civicaddress_type:
+ return _lldpctl_atom_set_int_med_caelement(atom, key,
+ map_reverse_lookup(civic_address_type_map.map, value));
+ default:
+ SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
+ return NULL;
+ }
+
+ return atom;
+bad:
+ SET_ERROR(atom->conn, LLDPCTL_ERR_BAD_VALUE);
+ return NULL;
+}
+
+static int
+_lldpctl_atom_new_med_power(lldpctl_atom_t *atom, va_list ap)
+{
+ struct _lldpctl_atom_med_power_t *mpow =
+ (struct _lldpctl_atom_med_power_t *)atom;
+ mpow->parent = va_arg(ap, struct _lldpctl_atom_port_t *);
+ lldpctl_atom_inc_ref((lldpctl_atom_t *)mpow->parent);
+ return 1;
+}
+
+static void
+_lldpctl_atom_free_med_power(lldpctl_atom_t *atom)
+{
+ struct _lldpctl_atom_med_power_t *mpow =
+ (struct _lldpctl_atom_med_power_t *)atom;
+ lldpctl_atom_dec_ref((lldpctl_atom_t *)mpow->parent);
+}
+
+static const char *
+_lldpctl_atom_get_str_med_power(lldpctl_atom_t *atom, lldpctl_key_t key)
+{
+ struct _lldpctl_atom_med_power_t *mpow =
+ (struct _lldpctl_atom_med_power_t *)atom;
+ struct lldpd_port *port = mpow->parent->port;
+
+ /* Local and remote port */
+ switch (key) {
+ case lldpctl_k_med_power_type:
+ return map_lookup(port_med_pow_devicetype_map,
+ port->p_med_power.devicetype);
+ case lldpctl_k_med_power_source:
+ return map_lookup(port_med_pow_source_map, port->p_med_power.source);
+ case lldpctl_k_med_power_priority:
+ return map_lookup(port_med_pow_priority_map.map,
+ port->p_med_power.priority);
+ default:
+ SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
+ return NULL;
+ }
+}
+
+static long int
+_lldpctl_atom_get_int_med_power(lldpctl_atom_t *atom, lldpctl_key_t key)
+{
+ struct _lldpctl_atom_med_power_t *dpow =
+ (struct _lldpctl_atom_med_power_t *)atom;
+ struct lldpd_port *port = dpow->parent->port;
+
+ /* Local and remote port */
+ switch (key) {
+ case lldpctl_k_med_power_type:
+ return port->p_med_power.devicetype;
+ case lldpctl_k_med_power_source:
+ return port->p_med_power.source;
+ case lldpctl_k_med_power_priority:
+ return port->p_med_power.priority;
+ case lldpctl_k_med_power_val:
+ return port->p_med_power.val * 100;
+ default:
+ return SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
+ }
+}
+
+static lldpctl_atom_t *
+_lldpctl_atom_set_int_med_power(lldpctl_atom_t *atom, lldpctl_key_t key, long int value)
+{
+ struct _lldpctl_atom_med_power_t *dpow =
+ (struct _lldpctl_atom_med_power_t *)atom;
+ struct lldpd_port *port = dpow->parent->port;
+
+ /* Only local port can be modified */
+ if (!dpow->parent->local) {
+ SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
+ return NULL;
+ }
+
+ switch (key) {
+ case lldpctl_k_med_power_type:
+ switch (value) {
+ case 0:
+ case LLDP_MED_POW_TYPE_PSE:
+ case LLDP_MED_POW_TYPE_PD:
+ port->p_med_power.devicetype = value;
+ return atom;
+ default:
+ goto bad;
+ }
+ case lldpctl_k_med_power_source:
+ switch (value) {
+ case LLDP_MED_POW_SOURCE_PRIMARY:
+ case LLDP_MED_POW_SOURCE_BACKUP:
+ if (port->p_med_power.devicetype != LLDP_MED_POW_TYPE_PSE)
+ goto bad;
+ port->p_med_power.source = value;
+ return atom;
+ case LLDP_MED_POW_SOURCE_PSE:
+ case LLDP_MED_POW_SOURCE_LOCAL:
+ case LLDP_MED_POW_SOURCE_BOTH:
+ if (port->p_med_power.devicetype != LLDP_MED_POW_TYPE_PD)
+ goto bad;
+ port->p_med_power.source = value;
+ return atom;
+ case LLDP_MED_POW_SOURCE_UNKNOWN:
+ port->p_med_power.source = value;
+ return atom;
+ default:
+ goto bad;
+ }
+ case lldpctl_k_med_power_priority:
+ if (value < 0 || value > 3) goto bad;
+ port->p_med_power.priority = value;
+ return atom;
+ case lldpctl_k_med_power_val:
+ if (value < 0) goto bad;
+ port->p_med_power.val = value / 100;
+ return atom;
+ default:
+ SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
+ return NULL;
+ }
+
+ return atom;
+bad:
+ SET_ERROR(atom->conn, LLDPCTL_ERR_BAD_VALUE);
+ return NULL;
+}
+
+static lldpctl_atom_t *
+_lldpctl_atom_set_str_med_power(lldpctl_atom_t *atom, lldpctl_key_t key,
+ const char *value)
+{
+ switch (key) {
+ case lldpctl_k_med_power_type:
+ return _lldpctl_atom_set_int_med_power(atom, key,
+ map_reverse_lookup(port_med_pow_devicetype_map, value));
+ case lldpctl_k_med_power_source:
+ return _lldpctl_atom_set_int_med_power(atom, key,
+ map_reverse_lookup(port_med_pow_source_map2, value));
+ case lldpctl_k_med_power_priority:
+ return _lldpctl_atom_set_int_med_power(atom, key,
+ map_reverse_lookup(port_med_pow_priority_map.map, value));
+ default:
+ SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
+ return NULL;
+ }
+}
+
+static struct atom_builder med_policies_list = { atom_med_policies_list,
+ sizeof(struct _lldpctl_atom_any_list_t), .init = _lldpctl_atom_new_any_list,
+ .free = _lldpctl_atom_free_any_list,
+ .iter = _lldpctl_atom_iter_med_policies_list,
+ .next = _lldpctl_atom_next_med_policies_list,
+ .value = _lldpctl_atom_value_med_policies_list };
+
+static struct atom_builder med_policy = { atom_med_policy,
+ sizeof(struct _lldpctl_atom_med_policy_t), .init = _lldpctl_atom_new_med_policy,
+ .free = _lldpctl_atom_free_med_policy,
+ .get_int = _lldpctl_atom_get_int_med_policy,
+ .set_int = _lldpctl_atom_set_int_med_policy,
+ .get_str = _lldpctl_atom_get_str_med_policy,
+ .set_str = _lldpctl_atom_set_str_med_policy };
+
+static struct atom_builder med_locations_list = { atom_med_locations_list,
+ sizeof(struct _lldpctl_atom_any_list_t), .init = _lldpctl_atom_new_any_list,
+ .free = _lldpctl_atom_free_any_list,
+ .iter = _lldpctl_atom_iter_med_locations_list,
+ .next = _lldpctl_atom_next_med_locations_list,
+ .value = _lldpctl_atom_value_med_locations_list };
+
+static struct atom_builder med_location = { atom_med_location,
+ sizeof(struct _lldpctl_atom_med_location_t),
+ .init = _lldpctl_atom_new_med_location, .free = _lldpctl_atom_free_med_location,
+ .get = _lldpctl_atom_get_atom_med_location,
+ .set = _lldpctl_atom_set_atom_med_location,
+ .get_int = _lldpctl_atom_get_int_med_location,
+ .set_int = _lldpctl_atom_set_int_med_location,
+ .get_str = _lldpctl_atom_get_str_med_location,
+ .set_str = _lldpctl_atom_set_str_med_location };
+
+static struct atom_builder med_caelements_list = { atom_med_caelements_list,
+ sizeof(struct _lldpctl_atom_med_caelements_list_t),
+ .init = _lldpctl_atom_new_any_list, .free = _lldpctl_atom_free_any_list,
+ .iter = _lldpctl_atom_iter_med_caelements_list,
+ .next = _lldpctl_atom_next_med_caelements_list,
+ .value = _lldpctl_atom_value_med_caelements_list,
+ .create = _lldpctl_atom_create_med_caelements_list };
+
+static struct atom_builder med_caelement = { atom_med_caelement,
+ sizeof(struct _lldpctl_atom_med_caelement_t),
+ .init = _lldpctl_atom_new_med_caelement,
+ .free = _lldpctl_atom_free_med_caelement,
+ .get_int = _lldpctl_atom_get_int_med_caelement,
+ .set_int = _lldpctl_atom_set_int_med_caelement,
+ .get_str = _lldpctl_atom_get_str_med_caelement,
+ .set_str = _lldpctl_atom_set_str_med_caelement };
+
+static struct atom_builder med_power = { atom_med_power,
+ sizeof(struct _lldpctl_atom_med_power_t), .init = _lldpctl_atom_new_med_power,
+ .free = _lldpctl_atom_free_med_power,
+ .get_int = _lldpctl_atom_get_int_med_power,
+ .set_int = _lldpctl_atom_set_int_med_power,
+ .get_str = _lldpctl_atom_get_str_med_power,
+ .set_str = _lldpctl_atom_set_str_med_power };
+
+ATOM_BUILDER_REGISTER(med_policies_list, 15);
+ATOM_BUILDER_REGISTER(med_policy, 16);
+ATOM_BUILDER_REGISTER(med_locations_list, 17);
+ATOM_BUILDER_REGISTER(med_location, 18);
+ATOM_BUILDER_REGISTER(med_caelements_list, 19);
+ATOM_BUILDER_REGISTER(med_caelement, 20);
+ATOM_BUILDER_REGISTER(med_power, 21);
+
+#endif
diff --git a/src/lib/atoms/mgmt.c b/src/lib/atoms/mgmt.c
new file mode 100644
index 0000000..095de76
--- /dev/null
+++ b/src/lib/atoms/mgmt.c
@@ -0,0 +1,149 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/*
+ * Copyright (c) 2015 Vincent Bernat <vincent@bernat.im>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+#include <arpa/inet.h>
+
+#include "../lldpctl.h"
+#include "../../log.h"
+#include "../atom.h"
+#include "../helpers.h"
+
+static int
+_lldpctl_atom_new_mgmts_list(lldpctl_atom_t *atom, va_list ap)
+{
+ struct _lldpctl_atom_mgmts_list_t *plist =
+ (struct _lldpctl_atom_mgmts_list_t *)atom;
+ plist->parent = va_arg(ap, lldpctl_atom_t *);
+ plist->chassis = va_arg(ap, struct lldpd_chassis *);
+ lldpctl_atom_inc_ref(plist->parent);
+ return 1;
+}
+
+static void
+_lldpctl_atom_free_mgmts_list(lldpctl_atom_t *atom)
+{
+ struct _lldpctl_atom_mgmts_list_t *plist =
+ (struct _lldpctl_atom_mgmts_list_t *)atom;
+ lldpctl_atom_dec_ref(plist->parent);
+}
+
+static lldpctl_atom_iter_t *
+_lldpctl_atom_iter_mgmts_list(lldpctl_atom_t *atom)
+{
+ struct _lldpctl_atom_mgmts_list_t *plist =
+ (struct _lldpctl_atom_mgmts_list_t *)atom;
+ return (lldpctl_atom_iter_t *)TAILQ_FIRST(&plist->chassis->c_mgmt);
+}
+
+static lldpctl_atom_iter_t *
+_lldpctl_atom_next_mgmts_list(lldpctl_atom_t *atom, lldpctl_atom_iter_t *iter)
+{
+ struct lldpd_mgmt *mgmt = (struct lldpd_mgmt *)iter;
+ return (lldpctl_atom_iter_t *)TAILQ_NEXT(mgmt, m_entries);
+}
+
+static lldpctl_atom_t *
+_lldpctl_atom_value_mgmts_list(lldpctl_atom_t *atom, lldpctl_atom_iter_t *iter)
+{
+ struct _lldpctl_atom_mgmts_list_t *plist =
+ (struct _lldpctl_atom_mgmts_list_t *)atom;
+ struct lldpd_mgmt *mgmt = (struct lldpd_mgmt *)iter;
+ return _lldpctl_new_atom(atom->conn, atom_mgmt, plist->parent, mgmt);
+}
+
+static int
+_lldpctl_atom_new_mgmt(lldpctl_atom_t *atom, va_list ap)
+{
+ struct _lldpctl_atom_mgmt_t *mgmt = (struct _lldpctl_atom_mgmt_t *)atom;
+ mgmt->parent = va_arg(ap, lldpctl_atom_t *);
+ mgmt->mgmt = va_arg(ap, struct lldpd_mgmt *);
+ lldpctl_atom_inc_ref(mgmt->parent);
+ return 1;
+}
+
+static void
+_lldpctl_atom_free_mgmt(lldpctl_atom_t *atom)
+{
+ struct _lldpctl_atom_mgmt_t *mgmt = (struct _lldpctl_atom_mgmt_t *)atom;
+ lldpctl_atom_dec_ref(mgmt->parent);
+}
+
+static long int
+_lldpctl_atom_get_int_mgmt(lldpctl_atom_t *atom, lldpctl_key_t key)
+{
+ struct _lldpctl_atom_mgmt_t *m = (struct _lldpctl_atom_mgmt_t *)atom;
+
+ /* Local and remote port */
+ switch (key) {
+ case lldpctl_k_mgmt_iface_index:
+ return m->mgmt->m_iface;
+ default:
+ return SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
+ }
+}
+
+static const char *
+_lldpctl_atom_get_str_mgmt(lldpctl_atom_t *atom, lldpctl_key_t key)
+{
+ char *ipaddress = NULL;
+ size_t len;
+ int af;
+ struct _lldpctl_atom_mgmt_t *m = (struct _lldpctl_atom_mgmt_t *)atom;
+
+ /* Local and remote port */
+ switch (key) {
+ case lldpctl_k_mgmt_ip:
+ switch (m->mgmt->m_family) {
+ case LLDPD_AF_IPV4:
+ len = INET_ADDRSTRLEN + 1;
+ af = AF_INET;
+ break;
+ case LLDPD_AF_IPV6:
+ len = INET6_ADDRSTRLEN + 1;
+ af = AF_INET6;
+ break;
+ default:
+ len = 0;
+ }
+ if (len == 0) break;
+ ipaddress = _lldpctl_alloc_in_atom(atom, len);
+ if (!ipaddress) return NULL;
+ if (inet_ntop(af, &m->mgmt->m_addr, ipaddress, len) == NULL) break;
+ return ipaddress;
+ default:
+ SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
+ return NULL;
+ }
+ SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
+ return NULL;
+}
+
+static struct atom_builder mgmts_list = { atom_mgmts_list,
+ sizeof(struct _lldpctl_atom_mgmts_list_t), .init = _lldpctl_atom_new_mgmts_list,
+ .free = _lldpctl_atom_free_mgmts_list, .iter = _lldpctl_atom_iter_mgmts_list,
+ .next = _lldpctl_atom_next_mgmts_list,
+ .value = _lldpctl_atom_value_mgmts_list };
+
+static struct atom_builder mgmt = { atom_mgmt, sizeof(struct _lldpctl_atom_mgmt_t),
+ .init = _lldpctl_atom_new_mgmt, .free = _lldpctl_atom_free_mgmt,
+ .get_int = _lldpctl_atom_get_int_mgmt, .get_str = _lldpctl_atom_get_str_mgmt };
+
+ATOM_BUILDER_REGISTER(mgmts_list, 6);
+ATOM_BUILDER_REGISTER(mgmt, 7);
diff --git a/src/lib/atoms/port.c b/src/lib/atoms/port.c
new file mode 100644
index 0000000..f99e18b
--- /dev/null
+++ b/src/lib/atoms/port.c
@@ -0,0 +1,862 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/*
+ * Copyright (c) 2015 Vincent Bernat <vincent@bernat.im>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+#include <arpa/inet.h>
+
+#include "../lldpctl.h"
+#include "../../log.h"
+#include "../atom.h"
+#include "../helpers.h"
+
+static struct atom_map lldpd_protocol_map = { .key = lldpctl_k_port_protocol,
+ .map = {
+ { LLDPD_MODE_LLDP, "LLDP" },
+ { LLDPD_MODE_CDPV1, "CDPv1" },
+ { LLDPD_MODE_CDPV2, "CDPv2" },
+ { LLDPD_MODE_EDP, "EDP" },
+ { LLDPD_MODE_FDP, "FDP" },
+ { LLDPD_MODE_SONMP, "SONMP" },
+ { 0, NULL },
+ } };
+
+ATOM_MAP_REGISTER(lldpd_protocol_map, 3);
+
+static lldpctl_map_t port_id_subtype_map[] = {
+ { LLDP_PORTID_SUBTYPE_IFNAME, "ifname" },
+ { LLDP_PORTID_SUBTYPE_IFALIAS, "ifalias" },
+ { LLDP_PORTID_SUBTYPE_LOCAL, "local" },
+ { LLDP_PORTID_SUBTYPE_LLADDR, "mac" },
+ { LLDP_PORTID_SUBTYPE_ADDR, "ip" },
+ { LLDP_PORTID_SUBTYPE_PORT, "unhandled" },
+ { LLDP_PORTID_SUBTYPE_AGENTCID, "unhandled" },
+ { 0, NULL },
+};
+
+static struct atom_map port_status_map = { .key = lldpctl_k_port_status,
+ .map = {
+ { LLDPD_RXTX_TXONLY, "TX only" },
+ { LLDPD_RXTX_RXONLY, "RX only" },
+ { LLDPD_RXTX_DISABLED, "disabled" },
+ { LLDPD_RXTX_BOTH, "RX and TX" },
+ { 0, NULL },
+ } };
+
+ATOM_MAP_REGISTER(port_status_map, 3);
+
+#ifdef ENABLE_DOT3
+static lldpctl_map_t operational_mau_type_values[] = {
+ { LLDP_DOT3_MAU_AUI, "AUI - No internal MAU, view from AUI" },
+ { LLDP_DOT3_MAU_10BASE5, "10Base5 - Thick coax MAU" },
+ { LLDP_DOT3_MAU_FOIRL, "Foirl - FOIRL MAU" },
+ { LLDP_DOT3_MAU_10BASE2, "10Base2 - Thin coax MAU" },
+ { LLDP_DOT3_MAU_10BASET, "10BaseT - UTP MAU" },
+ { LLDP_DOT3_MAU_10BASEFP, "10BaseFP - Passive fiber MAU" },
+ { LLDP_DOT3_MAU_10BASEFB, "10BaseFB - Sync fiber MAU" },
+ { LLDP_DOT3_MAU_10BASEFL, "10BaseFL - Async fiber MAU" },
+ { LLDP_DOT3_MAU_10BROAD36, "10Broad36 - Broadband DTE MAU" },
+ { LLDP_DOT3_MAU_10BASETHD, "10BaseTHD - UTP MAU, half duplex mode" },
+ { LLDP_DOT3_MAU_10BASETFD, "10BaseTFD - UTP MAU, full duplex mode" },
+ { LLDP_DOT3_MAU_10BASEFLHD, "10BaseFLHD - Async fiber MAU, half duplex mode" },
+ { LLDP_DOT3_MAU_10BASEFLFD, "10BaseFLDF - Async fiber MAU, full duplex mode" },
+ { LLDP_DOT3_MAU_100BASET4, "100BaseT4 - 4 pair category 3 UTP" },
+ { LLDP_DOT3_MAU_100BASETXHD,
+ "100BaseTXHD - 2 pair category 5 UTP, half duplex mode" },
+ { LLDP_DOT3_MAU_100BASETXFD,
+ "100BaseTXFD - 2 pair category 5 UTP, full duplex mode" },
+ { LLDP_DOT3_MAU_100BASEFXHD,
+ "100BaseFXHD - X fiber over PMT, half duplex mode" },
+ { LLDP_DOT3_MAU_100BASEFXFD,
+ "100BaseFXFD - X fiber over PMT, full duplex mode" },
+ { LLDP_DOT3_MAU_100BASET2HD,
+ "100BaseT2HD - 2 pair category 3 UTP, half duplex mode" },
+ { LLDP_DOT3_MAU_100BASET2FD,
+ "100BaseT2FD - 2 pair category 3 UTP, full duplex mode" },
+ { LLDP_DOT3_MAU_1000BASEXHD,
+ "1000BaseXHD - PCS/PMA, unknown PMD, half duplex mode" },
+ { LLDP_DOT3_MAU_1000BASEXFD,
+ "1000BaseXFD - PCS/PMA, unknown PMD, full duplex mode" },
+ { LLDP_DOT3_MAU_1000BASELXHD,
+ "1000BaseLXHD - Fiber over long-wavelength laser, half duplex mode" },
+ { LLDP_DOT3_MAU_1000BASELXFD,
+ "1000BaseLXFD - Fiber over long-wavelength laser, full duplex mode" },
+ { LLDP_DOT3_MAU_1000BASESXHD,
+ "1000BaseSXHD - Fiber over short-wavelength laser, half duplex mode" },
+ { LLDP_DOT3_MAU_1000BASESXFD,
+ "1000BaseSXFD - Fiber over short-wavelength laser, full duplex mode" },
+ { LLDP_DOT3_MAU_1000BASECXHD,
+ "1000BaseCXHD - Copper over 150-Ohm balanced cable, half duplex mode" },
+ { LLDP_DOT3_MAU_1000BASECXFD,
+ "1000BaseCXFD - Copper over 150-Ohm balanced cable, full duplex mode" },
+ { LLDP_DOT3_MAU_1000BASETHD,
+ "1000BaseTHD - Four-pair Category 5 UTP, half duplex mode" },
+ { LLDP_DOT3_MAU_1000BASETFD,
+ "1000BaseTFD - Four-pair Category 5 UTP, full duplex mode" },
+ { LLDP_DOT3_MAU_10GIGBASEX, "10GigBaseX - X PCS/PMA, unknown PMD." },
+ { LLDP_DOT3_MAU_10GIGBASELX4, "10GigBaseLX4 - X fiber over WWDM optics" },
+ { LLDP_DOT3_MAU_10GIGBASER, "10GigBaseR - R PCS/PMA, unknown PMD." },
+ { LLDP_DOT3_MAU_10GIGBASEER, "10GigBaseER - R fiber over 1550 nm optics" },
+ { LLDP_DOT3_MAU_10GIGBASELR, "10GigBaseLR - R fiber over 1310 nm optics" },
+ { LLDP_DOT3_MAU_10GIGBASESR, "10GigBaseSR - R fiber over 850 nm optics" },
+ { LLDP_DOT3_MAU_10GIGBASEW, "10GigBaseW - W PCS/PMA, unknown PMD." },
+ { LLDP_DOT3_MAU_10GIGBASEEW, "10GigBaseEW - W fiber over 1550 nm optics" },
+ { LLDP_DOT3_MAU_10GIGBASELW, "10GigBaseLW - W fiber over 1310 nm optics" },
+ { LLDP_DOT3_MAU_10GIGBASESW, "10GigBaseSW - W fiber over 850 nm optics" },
+ { LLDP_DOT3_MAU_10GIGBASECX4,
+ "10GigBaseCX4 - X copper over 8 pair 100-Ohm balanced cable" },
+ { LLDP_DOT3_MAU_2BASETL,
+ "2BaseTL - Voice grade UTP copper, up to 2700m, optional PAF" },
+ { LLDP_DOT3_MAU_10PASSTS,
+ "10PassTS - Voice grade UTP copper, up to 750m, optional PAF" },
+ { LLDP_DOT3_MAU_100BASEBX10D,
+ "100BaseBX10D - One single-mode fiber OLT, long wavelength, 10km" },
+ { LLDP_DOT3_MAU_100BASEBX10U,
+ "100BaseBX10U - One single-mode fiber ONU, long wavelength, 10km" },
+ { LLDP_DOT3_MAU_100BASELX10,
+ "100BaseLX10 - Two single-mode fibers, long wavelength, 10km" },
+ { LLDP_DOT3_MAU_1000BASEBX10D,
+ "1000BaseBX10D - One single-mode fiber OLT, long wavelength, 10km" },
+ { LLDP_DOT3_MAU_1000BASEBX10U,
+ "1000BaseBX10U - One single-mode fiber ONU, long wavelength, 10km" },
+ { LLDP_DOT3_MAU_1000BASELX10,
+ "1000BaseLX10 - Two sigle-mode fiber, long wavelength, 10km" },
+ { LLDP_DOT3_MAU_1000BASEPX10D,
+ "1000BasePX10D - One single-mode fiber EPON OLT, 10km" },
+ { LLDP_DOT3_MAU_1000BASEPX10U,
+ "1000BasePX10U - One single-mode fiber EPON ONU, 10km" },
+ { LLDP_DOT3_MAU_1000BASEPX20D,
+ "1000BasePX20D - One single-mode fiber EPON OLT, 20km" },
+ { LLDP_DOT3_MAU_1000BASEPX20U,
+ "1000BasePX20U - One single-mode fiber EPON ONU, 20km" },
+ { LLDP_DOT3_MAU_10GBASET,
+ "10GbaseT - Four-pair Category 6A or better, full duplex mode only" },
+ { LLDP_DOT3_MAU_10GBASELRM,
+ "10GbaseLRM - R multimode fiber over 1310 nm optics" },
+ { LLDP_DOT3_MAU_1000BASEKX, "1000baseKX - X backplane, full duplex mode only" },
+ { LLDP_DOT3_MAU_10GBASEKX4,
+ "10GbaseKX4 - 4 lane X backplane, full duplex mode only" },
+ { LLDP_DOT3_MAU_10GBASEKR, "10GbaseKR - R backplane, full duplex mode only" },
+ { LLDP_DOT3_MAU_10G1GBASEPRXD1,
+ "10G1GbasePRXD1 - One single-mode fiber asymmetric-rate EPON OLT, low power budget (PRX10)" },
+ { LLDP_DOT3_MAU_10G1GBASEPRXD2,
+ "10G1GbasePRXD2 - One single-mode fiber asymmetric-rate EPON OLT, medium power budget (PRX20)" },
+ { LLDP_DOT3_MAU_10G1GBASEPRXD3,
+ "10G1GbasePRXD3 - One single-mode fiber asymmetric-rate EPON OLT, high power budget (PRX30)" },
+ { LLDP_DOT3_MAU_10G1GBASEPRXU1,
+ "10G1GbasePRXU1 - One single-mode fiber asymmetric-rate EPON ONU, low power budget (PRX10)" },
+ { LLDP_DOT3_MAU_10G1GBASEPRXU2,
+ "10G1GbasePRXU2 - One single-mode fiber asymmetric-rate EPON ONU, medium power budget (PRX20)" },
+ { LLDP_DOT3_MAU_10G1GBASEPRXU3,
+ "10G1GbasePRXU3 - One single-mode fiber asymmetric-rate EPON ONU, high power budget (PRX30)" },
+ { LLDP_DOT3_MAU_10GBASEPRD1,
+ "10GbasePRD1 - One single-mode fiber symmetric-rate EPON OLT, low power budget (PR10)" },
+ { LLDP_DOT3_MAU_10GBASEPRD2,
+ "10GbasePRD2 - One single-mode fiber symmetric-rate EPON OLT, medium power budget (PR20)" },
+ { LLDP_DOT3_MAU_10GBASEPRD3,
+ "10GbasePRD3 - One single-mode fiber symmetric-rate EPON OLT, high power budget (PR30)" },
+ { LLDP_DOT3_MAU_10GBASEPRU1,
+ "10GbasePRU1 - One single-mode fiber symmetric-rate EPON ONU, low and medium power budget" },
+ { LLDP_DOT3_MAU_10GBASEPRU3,
+ "10GbasePRU3 - One single-mode fiber symmetric-rate EPON ONU, high power budget (PR30)" },
+ { LLDP_DOT3_MAU_40GBASEKR4,
+ "40GbaseKR4 - 40GBASE-R PCS/PMA over an electrical backplane" },
+ { LLDP_DOT3_MAU_40GBASECR4,
+ "40GbaseCR4 - 40GBASE-R PCS/PMA over 4 lane shielded copper balanced cable" },
+ { LLDP_DOT3_MAU_40GBASESR4,
+ "40GbaseSR4 - 40GBASE-R PCS/PMA over 4 lane multimode fiber" },
+ { LLDP_DOT3_MAU_40GBASEFR,
+ "40GbaseFR - 40GBASE-R PCS/PMA over single mode fiber" },
+ { LLDP_DOT3_MAU_40GBASELR4,
+ "40GbaseLR4 - 40GBASE-R PCS/PMA over 4 WDM lane single mode fiber" },
+ { LLDP_DOT3_MAU_100GBASECR10,
+ "100GbaseCR10 - 100GBASE-R PCS/PMA over 10 lane shielded copper balanced cable" },
+ { LLDP_DOT3_MAU_100GBASESR10,
+ "100GbaseSR10 - 100GBASE-R PCS/PMA over 10 lane multimode fiber" },
+ { LLDP_DOT3_MAU_100GBASELR4,
+ "100GbaseLR4 - 100GBASE-R PCS/PMA over 4 WDM lane single mode fiber, long reach" },
+ { LLDP_DOT3_MAU_100GBASEER4,
+ "100GbaseER4 - 100GBASE-R PCS/PMA over 4 WDM lane single mode fiber PMD, extended reach" },
+ { LLDP_DOT3_MAU_1000BASET1,
+ "1000baseT1 - 1000BASE-T1 single balanced twisted-pair copper cabling PHY" },
+ { LLDP_DOT3_MAU_1000BASEPX30D,
+ "1000basePX30D - One single-mode fiber EPON OLT, 20km, 1:32 split ratio" },
+ { LLDP_DOT3_MAU_1000BASEPX30U,
+ "1000basePX30U - One single-mode fiber EPON ONU, 20km, 1:32 split ratio" },
+ { LLDP_DOT3_MAU_1000BASEPX40D,
+ "1000basePX40D - One single-mode fiber EPON OLT, 20km, 1:64 split ratio" },
+ { LLDP_DOT3_MAU_1000BASEPX40U,
+ "1000basePX40U - One single-mode fiber EPON ONU, 20km, 1:64 split ratio" },
+ { LLDP_DOT3_MAU_10G1GBASEPRXD4,
+ "10G1GbasePRXD4 - One single-mode fiber asymmetric-rate EPON OLT, supporting extended power budget (PRX40)" },
+ { LLDP_DOT3_MAU_10G1GBASEPRXU4,
+ "10G1GbasePRXU4 - One single-mode fiber asymmetric-rate EPON ONU, supporting extended power budget (PRX40)" },
+ { LLDP_DOT3_MAU_10GBASEPRD4,
+ "10GbasePRD4 - One single-mode fiber symmetric-rate EPON OLT, supporting extended power budget (PR40)" },
+ { LLDP_DOT3_MAU_10GBASEPRU4,
+ "10GbasePRU4 - One single-mode fiber symmetric-rate EPON ONU, supporting extended power budget (PR40)" },
+ { LLDP_DOT3_MAU_25GBASECR,
+ "25GbaseCR - 25GBASE-R PCS/PMA over shielded balanced copper cable" },
+ { LLDP_DOT3_MAU_25GBASECRS,
+ "25GbaseCRS - 25GBASE-R PCS/PMA over shielded balanced copper cable without RS-FEC" },
+ { LLDP_DOT3_MAU_25GBASEKR,
+ "25GbaseKR - 25GBASE-R PCS/PMA over an electrical backplane" },
+ { LLDP_DOT3_MAU_25GBASEKRS,
+ "25GbaseKRS - 25GBASE-R PCS/PMA over an electrical backplane without RS-FEC" },
+ { LLDP_DOT3_MAU_25GBASER, "25GbaseR - 25GBASE-R PCS/PMA over undefined PMD" },
+ { LLDP_DOT3_MAU_25GBASESR,
+ "25GbaseSR - 25GBASE-R PCS/PMA over multimode fiber" },
+ { LLDP_DOT3_MAU_25GBASET,
+ "25GbaseT - Four-pair twisted-pair balanced copper cabling" },
+ { LLDP_DOT3_MAU_40GBASEER4,
+ "40GbaseER4 - 40GBASE-R PCS/PMA over 4 WDM lane single mode fiber" },
+ { LLDP_DOT3_MAU_40GBASER,
+ "40GbaseR - 40GBASE-R PCS as over undefined PMA/PMD" },
+ { LLDP_DOT3_MAU_40GBASET,
+ "40GbaseT - Four-pair twisted-pair balanced copper cabling" },
+ { LLDP_DOT3_MAU_100GBASECR4,
+ "100GbaseCR4 - 100GBASE-R PCS/PMA over 4 lane shielded copper balanced cable" },
+ { LLDP_DOT3_MAU_100GBASEKR4,
+ "100GbaseKR4 - 100GBASE-R PCS/PMA over an electrical backplane" },
+ { LLDP_DOT3_MAU_100GBASEKP4,
+ "100GbaseKP4 - 100GBASE-P PCS/PMA over an electrical backplane PMD" },
+ { LLDP_DOT3_MAU_100GBASER,
+ "100GbaseR - 100GBASE-R Multi-lane PCS over undefined PMA/PMD" },
+ { LLDP_DOT3_MAU_100GBASESR4,
+ "100GbaseSR4 - 100GBASE-R PCS/PMA over 4 lane multimode fiber" },
+ { LLDP_DOT3_MAU_2P5GIGT,
+ "2p5GigT - 2.5GBASE-T Four-pair twisted-pair balanced copper cabling PHY" },
+ { LLDP_DOT3_MAU_5GIGT,
+ "5GigT - 5GBASE-T Four-pair twisted-pair balanced copper cabling PHY" },
+ { LLDP_DOT3_MAU_100BASET1,
+ "100baseT1 - 100BASE-T1 Single balanced twisted-pair copper cabling PHY" },
+ { LLDP_DOT3_MAU_1000BASERHA,
+ "1000baseRHA - 1000BASE-RHA Plastic optical fiber PHY" },
+ { LLDP_DOT3_MAU_1000BASERHB,
+ "1000baseRHB - 1000BASE-RHB Plastic optical fiber PHY" },
+ { LLDP_DOT3_MAU_1000BASERHC,
+ "1000baseRHC - 1000BASE-RHC Plastic optical fiber PHY" },
+ { LLDP_DOT3_MAU_2P5GBASEKX,
+ "2p5GbaseKX - 2.5GBASE-X PMD over an electrical backplane" },
+ { LLDP_DOT3_MAU_2P5GBASEX,
+ "2p5GbaseX - 2.5GBASE-X PCS/PMA over undefined PMD" },
+ { LLDP_DOT3_MAU_5GBASEKR,
+ "5GbaseKR - 5GBASE-KR PMD over an electrical backplane" },
+ { LLDP_DOT3_MAU_5GBASER, "5GbaseR - 5GBASE-R PCS/PMA over undefined PMD" },
+ { LLDP_DOT3_MAU_10GPASSXR,
+ "10GpassXR - Coax cable distribution network PHY continuous downstream/burst mode upstream PHY" },
+ { LLDP_DOT3_MAU_25GBASELR,
+ "25GbaseLR - 25GBASE-R PCS/PMA over single-mode fiber PMD, with long reach" },
+ { LLDP_DOT3_MAU_25GBASEER,
+ "25GbaseER - 25GBASE-R PCS/PMA over single-mode fiber PMD, with extended reach" },
+ { LLDP_DOT3_MAU_50GBASER,
+ "50GbaseR - 50GBASE-R Multi-lane PCS over undefined PMA/PMD" },
+ { LLDP_DOT3_MAU_50GBASECR,
+ "50GbaseCR - 50GBASE-R PCS/PMA over shielded copper balanced cable PMD" },
+ { LLDP_DOT3_MAU_50GBASEKR,
+ "50GbaseKR - 50GBASE-R PCS/PMA over an electrical backplane PMD" },
+ { LLDP_DOT3_MAU_50GBASESR,
+ "50GbaseSR - 50GBASE-R PCS/PMA over multimode fiber PMD" },
+ { LLDP_DOT3_MAU_50GBASEFR,
+ "50GbaseFR - 50GBASE-R PCS/PMA over single mode fiber PMD with reach up to at least 2 km" },
+ { LLDP_DOT3_MAU_50GBASELR,
+ "50GbaseLR - 50GBASE-R PCS/PMA over single mode fiber PMD with reach up to at least 10 km" },
+ { LLDP_DOT3_MAU_50GBASEER,
+ "50GbaseER - 50GBASE-R PCS/PMA over single-mode fiber PMD with reach up to at least 40 km" },
+ { LLDP_DOT3_MAU_100GBASECR2,
+ "100GbaseCR2 - 100GBASE-R PCS/PMA over 2 lane shielded copper balanced cable PMD" },
+ { LLDP_DOT3_MAU_100GBASEKR2,
+ "100GbaseKR2 - 100GBASE-R PCS/PMA over an electrical backplane PMD" },
+ { LLDP_DOT3_MAU_100GBASESR2,
+ "100GbaseSR2 - 100GBASE-R PCS/PMA over 2 lane multimode fiber PMD" },
+ { LLDP_DOT3_MAU_100GBASEDR,
+ "100GbaseDR - 100GBASE-R PCS/PMA over single mode fiber PMD" },
+ { LLDP_DOT3_MAU_200GBASER,
+ "200GbaseR - 200GBASE-R Multi-lane PCS over undefined PMA/PMD" },
+ { LLDP_DOT3_MAU_200GBASEDR4,
+ "200GbaseDR4 - 200GBASE-R PCS/PMA over 4-lane single-mode fiber PMD" },
+ { LLDP_DOT3_MAU_200GBASEFR4,
+ "200GbaseFR4 - 200GBASE-R PCS/PMA over 4 WDM lane single-mode fiber PMD with reach up to at least 2 km" },
+ { LLDP_DOT3_MAU_200GBASELR4,
+ "200GbaseLR4 - 200GBASE-R PCS/PMA over 4 WDM lane single-mode fiber PMD with reach up to at least 10 km" },
+ { LLDP_DOT3_MAU_200GBASECR4,
+ "200GbaseCR4 - 200GBASE-R PCS/PMA over 4 lane shielded copper balanced cable PMD" },
+ { LLDP_DOT3_MAU_200GBASEKR4,
+ "200GbaseKR4 - 200GBASE-R PCS/PMA over an electrical backplane PMD" },
+ { LLDP_DOT3_MAU_200GBASESR4,
+ "200GbaseSR4 - 200GBASE-R PCS/PMA over 4 lane multimode fiber PMD" },
+ { LLDP_DOT3_MAU_200GBASEER4,
+ "200GbaseER4 - 200GBASE-R PCS/PMA over 4 WDM lane single-mode fiber PMD with reach up to at least 40 km" },
+ { LLDP_DOT3_MAU_400GBASER,
+ "400GbaseR - 400GBASE-R Multi-lane PCS over undefined PMA/PMD" },
+ { LLDP_DOT3_MAU_400GBASESR16,
+ "400GbaseSR16 - 400GBASE-R PCS/PMA over 16-lane multimode fiber PMD" },
+ { LLDP_DOT3_MAU_400GBASEDR4,
+ "400GbaseDR4 - 400GBASE-R PCS/PMA over 4-lane single-mode fiber PMD" },
+ { LLDP_DOT3_MAU_400GBASEFR8,
+ "400GbaseFR8 - 400GBASE-R PCS/PMA over 8 WDM lane single-mode fiber PMD with reach up to at least 2 km" },
+ { LLDP_DOT3_MAU_400GBASELR8,
+ "400GbaseLR8 - 400GBASE-R PCS/PMA over 8 WDM lane single-mode fiber PMD with reach up to at least 10 km" },
+ { LLDP_DOT3_MAU_400GBASEER8,
+ "400GbaseER8 - 400GBASE-R PCS/PMA over 8 WDM lane single-mode fiber PMD with reach up to at least 40 km" },
+ { LLDP_DOT3_MAU_10BASET1L, "10baseT1L - 10BASE-T1L Single balanced pair PHY" },
+ { LLDP_DOT3_MAU_10BASET1SHD,
+ "10baseT1SHD - 10BASE-T1S Single balanced pair PHY, half duplex mode" },
+ { LLDP_DOT3_MAU_10BASET1SMD,
+ "10baseT1SMD - 10BASE-T1S Single balanced pair PHY, multidrop mode" },
+ { LLDP_DOT3_MAU_10BASET1SFD,
+ "10baseT1SFD - 10BASE-T1S Single balanced pair PHY, full duplex mode" },
+ { 0, NULL }
+};
+#endif
+
+static lldpctl_atom_iter_t *
+_lldpctl_atom_iter_ports_list(lldpctl_atom_t *atom)
+{
+ struct _lldpctl_atom_any_list_t *plist =
+ (struct _lldpctl_atom_any_list_t *)atom;
+ return (lldpctl_atom_iter_t *)TAILQ_FIRST(&plist->parent->hardware->h_rports);
+}
+
+static lldpctl_atom_iter_t *
+_lldpctl_atom_next_ports_list(lldpctl_atom_t *atom, lldpctl_atom_iter_t *iter)
+{
+ struct lldpd_port *port = (struct lldpd_port *)iter;
+ return (lldpctl_atom_iter_t *)TAILQ_NEXT(port, p_entries);
+}
+
+static lldpctl_atom_t *
+_lldpctl_atom_value_ports_list(lldpctl_atom_t *atom, lldpctl_atom_iter_t *iter)
+{
+ struct lldpd_port *port = (struct lldpd_port *)iter;
+ return _lldpctl_new_atom(atom->conn, atom_port, 0, NULL, port,
+ ((struct _lldpctl_atom_any_list_t *)atom)->parent);
+}
+
+static int
+_lldpctl_atom_new_port(lldpctl_atom_t *atom, va_list ap)
+{
+ struct _lldpctl_atom_port_t *port = (struct _lldpctl_atom_port_t *)atom;
+ port->local = va_arg(ap, int);
+ port->hardware = va_arg(ap, struct lldpd_hardware *);
+ port->port = va_arg(ap, struct lldpd_port *);
+ port->parent = va_arg(ap, struct _lldpctl_atom_port_t *);
+ if (port->parent) lldpctl_atom_inc_ref((lldpctl_atom_t *)port->parent);
+
+ if (port->port) {
+ /* Internal atom. We are the parent, but our reference count is
+ * not incremented. */
+ port->chassis = _lldpctl_new_atom(atom->conn, atom_chassis,
+ port->port->p_chassis, port, 1);
+ }
+ return 1;
+}
+
+TAILQ_HEAD(chassis_list, lldpd_chassis);
+
+static void
+add_chassis(struct chassis_list *chassis_list, struct lldpd_chassis *chassis)
+{
+ struct lldpd_chassis *one_chassis;
+ TAILQ_FOREACH (one_chassis, chassis_list, c_entries) {
+ if (one_chassis == chassis) return;
+ }
+ TAILQ_INSERT_TAIL(chassis_list, chassis, c_entries);
+}
+
+static void
+_lldpctl_atom_free_port(lldpctl_atom_t *atom)
+{
+ struct _lldpctl_atom_port_t *port = (struct _lldpctl_atom_port_t *)atom;
+ struct lldpd_hardware *hardware = port->hardware;
+ struct lldpd_chassis *one_chassis, *one_chassis_next;
+ struct lldpd_port *one_port;
+
+ /* Free internal chassis atom. Should be freed immediately since we
+ * should have the only reference. */
+ lldpctl_atom_dec_ref((lldpctl_atom_t *)port->chassis);
+
+ /* We need to free the whole struct lldpd_hardware: local port, local
+ * chassis and remote ports... The same chassis may be present several
+ * times. We build a list of chassis (we don't use reference count). */
+ struct chassis_list chassis_list;
+ TAILQ_INIT(&chassis_list);
+
+ if (port->parent)
+ lldpctl_atom_dec_ref((lldpctl_atom_t *)port->parent);
+ else if (!hardware && port->port) {
+ /* No parent, no hardware, we assume a single neighbor: one
+ * port, one chassis. */
+ if (port->port->p_chassis) {
+ lldpd_chassis_cleanup(port->port->p_chassis, 1);
+ port->port->p_chassis = NULL;
+ }
+ lldpd_port_cleanup(port->port, 1);
+ free(port->port);
+ }
+ if (!hardware) return;
+
+ add_chassis(&chassis_list, port->port->p_chassis);
+ TAILQ_FOREACH (one_port, &hardware->h_rports, p_entries)
+ add_chassis(&chassis_list, one_port->p_chassis);
+
+ /* Free hardware port */
+ lldpd_remote_cleanup(hardware, NULL, 1);
+ lldpd_port_cleanup(port->port, 1);
+ free(port->hardware);
+
+ /* Free list of chassis */
+ for (one_chassis = TAILQ_FIRST(&chassis_list); one_chassis != NULL;
+ one_chassis = one_chassis_next) {
+ one_chassis_next = TAILQ_NEXT(one_chassis, c_entries);
+ lldpd_chassis_cleanup(one_chassis, 1);
+ }
+}
+
+static lldpctl_atom_t *
+_lldpctl_atom_get_atom_port(lldpctl_atom_t *atom, lldpctl_key_t key)
+{
+ struct _lldpctl_atom_port_t *p = (struct _lldpctl_atom_port_t *)atom;
+ struct lldpd_port *port = p->port;
+ struct lldpd_hardware *hardware = p->hardware;
+
+ /* Local port only */
+ if (hardware != NULL) {
+ switch (key) {
+ case lldpctl_k_port_neighbors:
+ return _lldpctl_new_atom(atom->conn, atom_ports_list, p);
+ default:
+ break;
+ }
+ }
+
+ /* Local and remote port */
+ switch (key) {
+ case lldpctl_k_port_chassis:
+ if (port->p_chassis) {
+ return _lldpctl_new_atom(atom->conn, atom_chassis,
+ port->p_chassis, p, 0);
+ }
+ SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
+ return NULL;
+#ifdef ENABLE_DOT3
+ case lldpctl_k_port_dot3_power:
+ return _lldpctl_new_atom(atom->conn, atom_dot3_power, p);
+#endif
+#ifdef ENABLE_DOT1
+ case lldpctl_k_port_vlans:
+ return _lldpctl_new_atom(atom->conn, atom_vlans_list, p);
+ case lldpctl_k_port_ppvids:
+ return _lldpctl_new_atom(atom->conn, atom_ppvids_list, p);
+ case lldpctl_k_port_pis:
+ return _lldpctl_new_atom(atom->conn, atom_pis_list, p);
+#endif
+#ifdef ENABLE_LLDPMED
+ case lldpctl_k_port_med_policies:
+ return _lldpctl_new_atom(atom->conn, atom_med_policies_list, p);
+ case lldpctl_k_port_med_locations:
+ return _lldpctl_new_atom(atom->conn, atom_med_locations_list, p);
+ case lldpctl_k_port_med_power:
+ return _lldpctl_new_atom(atom->conn, atom_med_power, p);
+#endif
+#ifdef ENABLE_CUSTOM
+ case lldpctl_k_custom_tlvs:
+ return _lldpctl_new_atom(atom->conn, atom_custom_list, p);
+#endif
+ default:
+ /* Compatibility: query the associated chassis too */
+ if (port->p_chassis) return lldpctl_atom_get(p->chassis, key);
+ SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
+ return NULL;
+ }
+}
+
+static lldpctl_atom_t *
+_lldpctl_atom_set_atom_port(lldpctl_atom_t *atom, lldpctl_key_t key,
+ lldpctl_atom_t *value)
+{
+ struct _lldpctl_atom_port_t *p = (struct _lldpctl_atom_port_t *)atom;
+ struct lldpd_hardware *hardware = p->hardware;
+ struct lldpd_port_set set = {};
+ int rc;
+ char *canary = NULL;
+
+#ifdef ENABLE_DOT3
+ struct _lldpctl_atom_dot3_power_t *dpow;
+#endif
+#ifdef ENABLE_LLDPMED
+ struct _lldpctl_atom_med_power_t *mpow;
+ struct _lldpctl_atom_med_policy_t *mpol;
+ struct _lldpctl_atom_med_location_t *mloc;
+#endif
+#ifdef ENABLE_CUSTOM
+ struct _lldpctl_atom_custom_t *custom;
+#endif
+
+ /* Local and default port only */
+ if (!p->local) {
+ SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
+ return NULL;
+ }
+
+ set.vlan_tx_enabled = -1;
+
+ switch (key) {
+ case lldpctl_k_port_id:
+ set.local_id = p->port->p_id;
+ break;
+ case lldpctl_k_port_descr:
+ set.local_descr = p->port->p_descr;
+ break;
+ case lldpctl_k_port_status:
+ set.rxtx = LLDPD_RXTX_FROM_PORT(p->port);
+ break;
+ case lldpctl_k_port_vlan_tx:
+ set.vlan_tx_tag = p->port->p_vlan_tx_tag;
+ set.vlan_tx_enabled = p->port->p_vlan_tx_enabled;
+ break;
+#ifdef ENABLE_DOT3
+ case lldpctl_k_port_dot3_power:
+ if (value->type != atom_dot3_power) {
+ SET_ERROR(atom->conn, LLDPCTL_ERR_INCORRECT_ATOM_TYPE);
+ return NULL;
+ }
+
+ dpow = (struct _lldpctl_atom_dot3_power_t *)value;
+ set.dot3_power = &dpow->parent->port->p_power;
+ break;
+#endif
+#ifdef ENABLE_LLDPMED
+ case lldpctl_k_port_med_power:
+ if (value->type != atom_med_power) {
+ SET_ERROR(atom->conn, LLDPCTL_ERR_INCORRECT_ATOM_TYPE);
+ return NULL;
+ }
+
+ mpow = (struct _lldpctl_atom_med_power_t *)value;
+ set.med_power = &mpow->parent->port->p_med_power;
+ break;
+ case lldpctl_k_port_med_policies:
+ if (value->type != atom_med_policy) {
+ SET_ERROR(atom->conn, LLDPCTL_ERR_INCORRECT_ATOM_TYPE);
+ return NULL;
+ }
+ mpol = (struct _lldpctl_atom_med_policy_t *)value;
+ set.med_policy = mpol->policy;
+ break;
+ case lldpctl_k_port_med_locations:
+ if (value->type != atom_med_location) {
+ SET_ERROR(atom->conn, LLDPCTL_ERR_INCORRECT_ATOM_TYPE);
+ return NULL;
+ }
+ mloc = (struct _lldpctl_atom_med_location_t *)value;
+ set.med_location = mloc->location;
+ break;
+#endif
+#ifdef ENABLE_CUSTOM
+ case lldpctl_k_custom_tlvs_clear:
+ set.custom_list_clear = 1;
+ break;
+ case lldpctl_k_custom_tlv:
+ if (value->type != atom_custom) {
+ SET_ERROR(atom->conn, LLDPCTL_ERR_INCORRECT_ATOM_TYPE);
+ return NULL;
+ }
+ custom = (struct _lldpctl_atom_custom_t *)value;
+ set.custom = custom->tlv;
+ set.custom_tlv_op = custom->op;
+ break;
+#endif
+ default:
+ SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
+ return NULL;
+ }
+
+ char empty_str[] = "";
+ set.ifname = hardware ? hardware->h_ifname : empty_str;
+
+ if (asprintf(&canary, "%d%p%s", key, value, set.ifname) == -1) {
+ SET_ERROR(atom->conn, LLDPCTL_ERR_NOMEM);
+ return NULL;
+ }
+ rc = _lldpctl_do_something(atom->conn, CONN_STATE_SET_PORT_SEND,
+ CONN_STATE_SET_PORT_RECV, canary, SET_PORT, &set,
+ &MARSHAL_INFO(lldpd_port_set), NULL, NULL);
+ free(canary);
+ if (rc == 0) return atom;
+ return NULL;
+}
+
+static const char *
+_lldpctl_atom_get_str_port(lldpctl_atom_t *atom, lldpctl_key_t key)
+{
+ struct _lldpctl_atom_port_t *p = (struct _lldpctl_atom_port_t *)atom;
+ struct lldpd_port *port = p->port;
+ struct lldpd_hardware *hardware = p->hardware;
+ char *ipaddress = NULL;
+ size_t len;
+
+ /* Local port only */
+ switch (key) {
+ case lldpctl_k_port_name:
+ if (hardware != NULL) return hardware->h_ifname;
+ break;
+ case lldpctl_k_port_status:
+ if (p->local)
+ return map_lookup(port_status_map.map,
+ LLDPD_RXTX_FROM_PORT(port));
+ break;
+ default:
+ break;
+ }
+
+ if (!port) return NULL;
+
+ /* Local and remote port */
+ switch (key) {
+ case lldpctl_k_port_protocol:
+ return map_lookup(lldpd_protocol_map.map, port->p_protocol);
+ case lldpctl_k_port_id_subtype:
+ return map_lookup(port_id_subtype_map, port->p_id_subtype);
+ case lldpctl_k_port_id:
+ switch (port->p_id_subtype) {
+ case LLDP_PORTID_SUBTYPE_IFNAME:
+ case LLDP_PORTID_SUBTYPE_IFALIAS:
+ case LLDP_PORTID_SUBTYPE_LOCAL:
+ return port->p_id;
+ case LLDP_PORTID_SUBTYPE_LLADDR:
+ return _lldpctl_dump_in_atom(atom, (uint8_t *)port->p_id,
+ port->p_id_len, ':', 0);
+ case LLDP_PORTID_SUBTYPE_ADDR:
+ switch (port->p_id[0]) {
+ case LLDP_MGMT_ADDR_IP4:
+ len = INET_ADDRSTRLEN + 1;
+ break;
+ case LLDP_MGMT_ADDR_IP6:
+ len = INET6_ADDRSTRLEN + 1;
+ break;
+ default:
+ len = 0;
+ }
+ if (len > 0) {
+ ipaddress = _lldpctl_alloc_in_atom(atom, len);
+ if (!ipaddress) return NULL;
+ if (inet_ntop((port->p_id[0] == LLDP_MGMT_ADDR_IP4) ?
+ AF_INET :
+ AF_INET6,
+ &port->p_id[1], ipaddress, len) == NULL)
+ break;
+ return ipaddress;
+ }
+ break;
+ }
+ SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
+ return NULL;
+ case lldpctl_k_port_descr:
+ return port->p_descr;
+
+#ifdef ENABLE_DOT3
+ case lldpctl_k_port_dot3_mautype:
+ return map_lookup(operational_mau_type_values, port->p_macphy.mau_type);
+#endif
+
+ default:
+ /* Compatibility: query the associated chassis too */
+ return lldpctl_atom_get_str(p->chassis, key);
+ }
+}
+
+static lldpctl_atom_t *
+_lldpctl_atom_set_int_port(lldpctl_atom_t *atom, lldpctl_key_t key, long int value)
+{
+ struct _lldpctl_atom_port_t *p = (struct _lldpctl_atom_port_t *)atom;
+ struct lldpd_port *port = p->port;
+
+ if (p->local) {
+ switch (key) {
+ case lldpctl_k_port_status:
+ port->p_disable_rx = !LLDPD_RXTX_RXENABLED(value);
+ port->p_disable_tx = !LLDPD_RXTX_TXENABLED(value);
+ break;
+ case lldpctl_k_port_vlan_tx:
+ if (value > -1) {
+ port->p_vlan_tx_tag = value;
+ port->p_vlan_tx_enabled = 1;
+ } else
+ port->p_vlan_tx_enabled = 0;
+ break;
+ default:
+ SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
+ return NULL;
+ }
+ } else {
+ SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
+ return NULL;
+ }
+
+ return _lldpctl_atom_set_atom_port(atom, key, NULL);
+}
+
+static lldpctl_atom_t *
+_lldpctl_atom_set_str_port(lldpctl_atom_t *atom, lldpctl_key_t key, const char *value)
+{
+ struct _lldpctl_atom_port_t *p = (struct _lldpctl_atom_port_t *)atom;
+ struct lldpd_port *port = p->port;
+
+ if (!value || !strlen(value)) return NULL;
+
+ if (p->local) {
+ switch (key) {
+ case lldpctl_k_port_status:
+ return _lldpctl_atom_set_int_port(atom, key,
+ map_reverse_lookup(port_status_map.map, value));
+ default:
+ break;
+ }
+ }
+
+ switch (key) {
+ case lldpctl_k_port_id:
+ free(port->p_id);
+ port->p_id = strdup(value);
+ port->p_id_len = strlen(value);
+ break;
+ case lldpctl_k_port_descr:
+ free(port->p_descr);
+ port->p_descr = strdup(value);
+ break;
+ default:
+ SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
+ return NULL;
+ }
+
+ return _lldpctl_atom_set_atom_port(atom, key, NULL);
+}
+
+static long int
+_lldpctl_atom_get_int_port(lldpctl_atom_t *atom, lldpctl_key_t key)
+{
+ struct _lldpctl_atom_port_t *p = (struct _lldpctl_atom_port_t *)atom;
+ struct lldpd_port *port = p->port;
+ struct lldpd_hardware *hardware = p->hardware;
+
+ /* Local port only */
+ if (hardware != NULL) {
+ switch (key) {
+ case lldpctl_k_port_index:
+ return hardware->h_ifindex;
+ case lldpctl_k_tx_cnt:
+ return hardware->h_tx_cnt;
+ case lldpctl_k_rx_cnt:
+ return hardware->h_rx_cnt;
+ case lldpctl_k_rx_discarded_cnt:
+ return hardware->h_rx_discarded_cnt;
+ case lldpctl_k_rx_unrecognized_cnt:
+ return hardware->h_rx_unrecognized_cnt;
+ case lldpctl_k_ageout_cnt:
+ return hardware->h_ageout_cnt;
+ case lldpctl_k_insert_cnt:
+ return hardware->h_insert_cnt;
+ case lldpctl_k_delete_cnt:
+ return hardware->h_delete_cnt;
+ default:
+ break;
+ }
+ }
+ if (p->local) {
+ switch (key) {
+ case lldpctl_k_port_status:
+ return LLDPD_RXTX_FROM_PORT(port);
+ default:
+ break;
+ }
+ }
+ if (!port) return SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
+
+ /* Local and remote port */
+ switch (key) {
+ case lldpctl_k_port_protocol:
+ return port->p_protocol;
+ case lldpctl_k_port_age:
+ return port->p_lastchange;
+ case lldpctl_k_port_ttl:
+ return port->p_ttl;
+ case lldpctl_k_port_id_subtype:
+ return port->p_id_subtype;
+ case lldpctl_k_port_hidden:
+ return port->p_hidden_in;
+ case lldpctl_k_port_vlan_tx:
+ return port->p_vlan_tx_enabled ? port->p_vlan_tx_tag : -1;
+#ifdef ENABLE_DOT3
+ case lldpctl_k_port_dot3_mfs:
+ if (port->p_mfs > 0) return port->p_mfs;
+ break;
+ case lldpctl_k_port_dot3_aggregid:
+ if (port->p_aggregid > 0) return port->p_aggregid;
+ break;
+ case lldpctl_k_port_dot3_autoneg_support:
+ return port->p_macphy.autoneg_support;
+ case lldpctl_k_port_dot3_autoneg_enabled:
+ return port->p_macphy.autoneg_enabled;
+ case lldpctl_k_port_dot3_autoneg_advertised:
+ return port->p_macphy.autoneg_advertised;
+ case lldpctl_k_port_dot3_mautype:
+ return port->p_macphy.mau_type;
+#endif
+#ifdef ENABLE_DOT1
+ case lldpctl_k_port_vlan_pvid:
+ return port->p_pvid;
+#endif
+ default:
+ /* Compatibility: query the associated chassis too */
+ return lldpctl_atom_get_int(p->chassis, key);
+ }
+ return SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
+}
+
+static const uint8_t *
+_lldpctl_atom_get_buf_port(lldpctl_atom_t *atom, lldpctl_key_t key, size_t *n)
+{
+ struct _lldpctl_atom_port_t *p = (struct _lldpctl_atom_port_t *)atom;
+ struct lldpd_port *port = p->port;
+
+ switch (key) {
+ case lldpctl_k_port_id:
+ *n = port->p_id_len;
+ return (uint8_t *)port->p_id;
+ default:
+ /* Compatibility: query the associated chassis too */
+ return lldpctl_atom_get_buffer(p->chassis, key, n);
+ }
+}
+
+static struct atom_builder ports_list = { atom_ports_list,
+ sizeof(struct _lldpctl_atom_any_list_t), .init = _lldpctl_atom_new_any_list,
+ .free = _lldpctl_atom_free_any_list, .iter = _lldpctl_atom_iter_ports_list,
+ .next = _lldpctl_atom_next_ports_list,
+ .value = _lldpctl_atom_value_ports_list };
+
+static struct atom_builder port = { atom_port, sizeof(struct _lldpctl_atom_port_t),
+ .init = _lldpctl_atom_new_port, .free = _lldpctl_atom_free_port,
+ .get = _lldpctl_atom_get_atom_port, .set = _lldpctl_atom_set_atom_port,
+ .get_str = _lldpctl_atom_get_str_port, .set_str = _lldpctl_atom_set_str_port,
+ .get_int = _lldpctl_atom_get_int_port, .set_int = _lldpctl_atom_set_int_port,
+ .get_buffer = _lldpctl_atom_get_buf_port };
+
+ATOM_BUILDER_REGISTER(ports_list, 4);
+ATOM_BUILDER_REGISTER(port, 5);
diff --git a/src/lib/connection.c b/src/lib/connection.c
new file mode 100644
index 0000000..43f46cb
--- /dev/null
+++ b/src/lib/connection.c
@@ -0,0 +1,306 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/*
+ * Copyright (c) 2012 Vincent Bernat <bernat@luffy.cx>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include "lldpctl.h"
+#include "atom.h"
+#include "../compat/compat.h"
+#include "../ctl.h"
+#include "../log.h"
+
+const char *
+lldpctl_get_default_transport(void)
+{
+ return LLDPD_CTL_SOCKET;
+}
+
+/* Connect to the remote end */
+static int
+sync_connect(lldpctl_conn_t *lldpctl)
+{
+ return ctl_connect(lldpctl->ctlname);
+}
+
+/* Synchronously send data to remote end. */
+static ssize_t
+sync_send(lldpctl_conn_t *lldpctl, const uint8_t *data, size_t length, void *user_data)
+{
+ struct lldpctl_conn_sync_t *conn = user_data;
+ ssize_t nb;
+
+ if (conn->fd == -1 && ((conn->fd = sync_connect(lldpctl)) == -1)) {
+ return LLDPCTL_ERR_CANNOT_CONNECT;
+ }
+
+ while ((nb = write(conn->fd, data, length)) == -1) {
+ if (errno == EAGAIN || errno == EINTR) continue;
+ return LLDPCTL_ERR_CALLBACK_FAILURE;
+ }
+ return nb;
+}
+
+/* Statically receive data from remote end. */
+static ssize_t
+sync_recv(lldpctl_conn_t *lldpctl, const uint8_t *data, size_t length, void *user_data)
+{
+ struct lldpctl_conn_sync_t *conn = user_data;
+ ssize_t nb;
+ size_t remain, offset = 0;
+
+ if (conn->fd == -1 && ((conn->fd = sync_connect(lldpctl)) == -1)) {
+ lldpctl->error = LLDPCTL_ERR_CANNOT_CONNECT;
+ return LLDPCTL_ERR_CANNOT_CONNECT;
+ }
+
+ remain = length;
+ do {
+ if ((nb = read(conn->fd, (unsigned char *)data + offset, remain)) ==
+ -1) {
+ if (errno == EAGAIN || errno == EINTR) continue;
+ return LLDPCTL_ERR_CALLBACK_FAILURE;
+ }
+ remain -= nb;
+ offset += nb;
+ } while (remain > 0 && nb != 0);
+ return offset;
+}
+
+lldpctl_conn_t *
+lldpctl_new(lldpctl_send_callback send, lldpctl_recv_callback recv, void *user_data)
+{
+ return lldpctl_new_name(lldpctl_get_default_transport(), send, recv, user_data);
+}
+
+lldpctl_conn_t *
+lldpctl_new_name(const char *ctlname, lldpctl_send_callback send,
+ lldpctl_recv_callback recv, void *user_data)
+{
+ lldpctl_conn_t *conn = NULL;
+ struct lldpctl_conn_sync_t *data = NULL;
+
+ /* Both callbacks are mandatory or should be NULL. */
+ if (send && !recv) return NULL;
+ if (recv && !send) return NULL;
+
+ if ((conn = calloc(1, sizeof(lldpctl_conn_t))) == NULL) return NULL;
+
+ conn->ctlname = strdup(ctlname);
+ if (conn->ctlname == NULL) {
+ free(conn);
+ return NULL;
+ }
+ if (!send && !recv) {
+ if ((data = malloc(sizeof(struct lldpctl_conn_sync_t))) == NULL) {
+ free(conn->ctlname);
+ free(conn);
+ return NULL;
+ }
+ data->fd = -1;
+ conn->send = sync_send;
+ conn->recv = sync_recv;
+ conn->user_data = data;
+ } else {
+ conn->send = send;
+ conn->recv = recv;
+ conn->user_data = user_data;
+ }
+
+ return conn;
+}
+
+int
+lldpctl_release(lldpctl_conn_t *conn)
+{
+ if (conn == NULL) return 0;
+ free(conn->ctlname);
+ if (conn->send == sync_send) {
+ struct lldpctl_conn_sync_t *data = conn->user_data;
+ if (data->fd != -1) close(data->fd);
+ free(conn->user_data);
+ }
+ free(conn->input_buffer);
+ free(conn->output_buffer);
+ free(conn);
+ return 0;
+}
+
+/**
+ * Request some bytes if they are not already here.
+ *
+ * @param conn The connection to lldpd.
+ * @param length The number of requested bytes.
+ * @return A negative integer if we can't have the bytes or the number of bytes we got.
+ */
+ssize_t
+_lldpctl_needs(lldpctl_conn_t *conn, size_t length)
+{
+ uint8_t *buffer;
+ ssize_t rc;
+
+ if ((buffer = calloc(1, length)) == NULL)
+ return SET_ERROR(conn, LLDPCTL_ERR_NOMEM);
+ rc = conn->recv(conn, buffer, length, conn->user_data);
+ if (rc < 0) {
+ free(buffer);
+ return SET_ERROR(conn, rc);
+ }
+ if (rc == 0) {
+ free(buffer);
+ return SET_ERROR(conn, LLDPCTL_ERR_EOF);
+ }
+ rc = lldpctl_recv(conn, buffer, rc);
+ free(buffer);
+ if (rc < 0) return SET_ERROR(conn, rc);
+ RESET_ERROR(conn);
+ return rc;
+}
+
+static int
+check_for_notification(lldpctl_conn_t *conn)
+{
+ struct lldpd_neighbor_change *change;
+ void *p;
+ int rc;
+ lldpctl_change_t type;
+ lldpctl_atom_t *interface = NULL, *neighbor = NULL;
+ rc = ctl_msg_recv_unserialized(&conn->input_buffer, &conn->input_buffer_len,
+ NOTIFICATION, &p, &MARSHAL_INFO(lldpd_neighbor_change));
+ if (rc != 0) return rc;
+ change = p;
+
+ /* We have a notification, call the callback */
+ if (conn->watch_cb || conn->watch_cb2) {
+ switch (change->state) {
+ case NEIGHBOR_CHANGE_DELETED:
+ type = lldpctl_c_deleted;
+ break;
+ case NEIGHBOR_CHANGE_ADDED:
+ type = lldpctl_c_added;
+ break;
+ case NEIGHBOR_CHANGE_UPDATED:
+ type = lldpctl_c_updated;
+ break;
+ default:
+ log_warnx("control", "unknown notification type (%d)",
+ change->state);
+ goto end;
+ }
+ interface = _lldpctl_new_atom(conn, atom_interface, change->ifname);
+ if (interface == NULL) goto end;
+ neighbor =
+ _lldpctl_new_atom(conn, atom_port, 0, NULL, change->neighbor, NULL);
+ if (neighbor == NULL) goto end;
+ if (conn->watch_cb)
+ conn->watch_cb(conn, type, interface, neighbor,
+ conn->watch_data);
+ else
+ conn->watch_cb2(type, interface, neighbor, conn->watch_data);
+ conn->watch_triggered = 1;
+ goto end;
+ }
+
+end:
+ if (interface) lldpctl_atom_dec_ref(interface);
+ if (neighbor)
+ lldpctl_atom_dec_ref(neighbor);
+ else {
+ lldpd_chassis_cleanup(change->neighbor->p_chassis, 1);
+ lldpd_port_cleanup(change->neighbor, 1);
+ free(change->neighbor);
+ }
+ free(change->ifname);
+ free(change);
+
+ /* Indicate if more data remains in the buffer for processing */
+ return (rc);
+}
+
+ssize_t
+lldpctl_recv(lldpctl_conn_t *conn, const uint8_t *data, size_t length)
+{
+
+ RESET_ERROR(conn);
+
+ if (length == 0) return 0;
+
+ /* Received data should be appended to the input buffer. */
+ if (conn->input_buffer == NULL) {
+ conn->input_buffer_len = 0;
+ if ((conn->input_buffer = malloc(length)) == NULL)
+ return SET_ERROR(conn, LLDPCTL_ERR_NOMEM);
+ } else {
+ uint8_t *new =
+ realloc(conn->input_buffer, conn->input_buffer_len + length);
+ if (new == NULL) return SET_ERROR(conn, LLDPCTL_ERR_NOMEM);
+ conn->input_buffer = new;
+ }
+ memcpy(conn->input_buffer + conn->input_buffer_len, data, length);
+ conn->input_buffer_len += length;
+
+ /* Read all notifications */
+ while (!check_for_notification(conn))
+ ;
+
+ RESET_ERROR(conn);
+
+ return conn->input_buffer_len;
+}
+
+int
+lldpctl_process_conn_buffer(lldpctl_conn_t *conn)
+{
+ int rc;
+
+ rc = check_for_notification(conn);
+
+ RESET_ERROR(conn);
+
+ return rc;
+}
+
+ssize_t
+lldpctl_send(lldpctl_conn_t *conn)
+{
+ /* Send waiting data. */
+ ssize_t rc;
+
+ RESET_ERROR(conn);
+
+ if (!conn->output_buffer) return 0;
+ rc = conn->send(conn, conn->output_buffer, conn->output_buffer_len,
+ conn->user_data);
+ if (rc < 0) return SET_ERROR(conn, rc);
+
+ /* "Shrink" the output buffer. */
+ if (rc == conn->output_buffer_len) {
+ free(conn->output_buffer);
+ conn->output_buffer = NULL;
+ conn->output_buffer_len = 0;
+ RESET_ERROR(conn);
+ return rc;
+ }
+ conn->output_buffer_len -= rc;
+ memmove(conn->output_buffer, conn->output_buffer + rc, conn->output_buffer_len);
+ /* We don't shrink the buffer. It will be either freed or shrinked later */
+ RESET_ERROR(conn);
+ return rc;
+}
diff --git a/src/lib/errors.c b/src/lib/errors.c
new file mode 100644
index 0000000..899ca6f
--- /dev/null
+++ b/src/lib/errors.c
@@ -0,0 +1,75 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/*
+ * Copyright (c) 2012 Vincent Bernat <bernat@luffy.cx>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "lldpctl.h"
+#include "atom.h"
+#include "../log.h"
+
+const char *
+lldpctl_strerror(lldpctl_error_t error)
+{
+ /* No default case to let the compiler warns us if we miss an error code. */
+ switch (error) {
+ case LLDPCTL_NO_ERROR:
+ return "No error";
+ case LLDPCTL_ERR_WOULDBLOCK:
+ return "Requested operation would block";
+ case LLDPCTL_ERR_EOF:
+ return "End of file reached";
+ case LLDPCTL_ERR_NOT_EXIST:
+ return "The requested information does not exist";
+ case LLDPCTL_ERR_CANNOT_CONNECT:
+ return "Unable to connect to lldpd daemon";
+ case LLDPCTL_ERR_INCORRECT_ATOM_TYPE:
+ return "Provided atom is of incorrect type";
+ case LLDPCTL_ERR_SERIALIZATION:
+ return "Error while serializing or unserializing data";
+ case LLDPCTL_ERR_INVALID_STATE:
+ return "Other input/output operation already in progress";
+ case LLDPCTL_ERR_CANNOT_ITERATE:
+ return "Cannot iterate on this atom";
+ case LLDPCTL_ERR_CANNOT_CREATE:
+ return "Cannot create a new element for this atom";
+ case LLDPCTL_ERR_BAD_VALUE:
+ return "Provided value is invalid";
+ case LLDPCTL_ERR_FATAL:
+ return "Unexpected fatal error";
+ case LLDPCTL_ERR_NOMEM:
+ return "Not enough memory available";
+ case LLDPCTL_ERR_CALLBACK_FAILURE:
+ return "A failure occurred during callback processing";
+ }
+ return "Unknown error code";
+}
+
+lldpctl_error_t
+lldpctl_last_error(lldpctl_conn_t *lldpctl)
+{
+ return lldpctl->error;
+}
+
+void
+lldpctl_log_callback(void (*cb)(int severity, const char *msg))
+{
+ log_register(cb);
+}
+
+void
+lldpctl_log_level(int level)
+{
+ if (level >= 1) log_level(level - 1);
+}
diff --git a/src/lib/fixedpoint.c b/src/lib/fixedpoint.c
new file mode 100644
index 0000000..4df0b50
--- /dev/null
+++ b/src/lib/fixedpoint.c
@@ -0,0 +1,255 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/*
+ * Copyright (c) 2013 Vincent Bernat <bernat@luffy.cx>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <arpa/inet.h>
+#include "fixedpoint.h"
+
+/* This is not a general purpose fixed point library. First, there is no
+ * arithmetic. Second, some functions assume that the total precision does not
+ * exceed 64 bits.
+ */
+
+#ifdef ENABLE_LLDPMED
+
+# ifndef ntohll
+# define ntohll(x) \
+ (((u_int64_t)(ntohl((int)(((x) << 32) >> 32))) << 32) | \
+ (unsigned int)ntohl(((int)((x) >> 32))))
+# endif
+
+/**
+ * Convert a string to fixed point number.
+ *
+ * @param repr String to convert.
+ * @param end If not NULL, will contain a pointer to the character after the
+ * last character used in the conversion.
+ * @param intbits Number of bits to represent the integer part.
+ * @param fltbits Number of bits to represent the float part.
+ * @return A fixed point number.
+ *
+ * If there is an overflow, there will be a truncation. Moreover, the fraction
+ * part will be rounded to the nearest possible power of two representation. The
+ * point will depend on the number of decimal provided with the fraction
+ * part.
+ */
+struct fp_number
+fp_strtofp(const char *repr, char **end, unsigned intbits, unsigned fltbits)
+{
+ char *endptr = NULL, *e2;
+ struct fp_number result = { .integer = { 0, intbits },
+ .fraction = { 0, fltbits, 0 } };
+ result.integer.value = strtoll(repr, &endptr, 10);
+ if (result.integer.value >= (1LL << (intbits - 1)))
+ result.integer.value = (1LL << (intbits - 1)) - 1;
+ else if (result.integer.value < ~(1LL << (intbits - 1)) + 1)
+ result.integer.value = ~(1LL << (intbits - 1)) + 1;
+ if (*endptr == '.') {
+ long long precision = 1;
+ e2 = endptr + 1;
+ result.fraction.value = strtoll(e2, &endptr, 10);
+ /* Convert to a representation in power of two. Get the
+ * precision from the number of digits provided. This is NOT the
+ * value of the higher bits in the binary representation: we
+ * consider that if the user inputs, 0.9375, it means to
+ * represent anything between 0 and 0.9999 with the same
+ * precision. Therefore, we don't have only 4 bits of precision
+ * but 14. */
+ while (e2++ != endptr)
+ precision *= 10;
+ result.fraction.value <<= fltbits;
+ result.fraction.value /= precision;
+ result.fraction.precision = (precision == 1) ?
+ 1 :
+ (sizeof(precision) * 8 - __builtin_clzll(precision - 1));
+ if (result.fraction.precision > fltbits)
+ result.fraction.precision = fltbits;
+ }
+ if (end) *end = endptr;
+ return result;
+}
+
+/**
+ * Get a string representation of a fixed point number.
+ *
+ * @param fp Fixed point number.
+ * @param suffix If not NULL, use the first character when positive and the
+ * second one when negative instead of prefixing by `-`.
+ * @return the string representation
+ *
+ * Since we convert from binary to decimal, we are as precise as the binary
+ * representation.
+ */
+char *
+fp_fptostr(struct fp_number fp, const char *suffix)
+{
+ char *result = NULL;
+ char *frac = NULL;
+ int negative = (fp.integer.value < 0);
+ if (fp.fraction.value == 0)
+ frac = strdup("");
+ else {
+ long long decimal = fp.fraction.value;
+ long long precision = 1;
+ int len = 0;
+ while ((1LL << fp.fraction.precision) > precision) {
+ precision *= 10;
+ len += 1;
+ }
+ /* We did round-up, when converting from decimal. We round-down
+ * to have some coherency. */
+ precision /= 10;
+ len -= 1;
+ if (precision == 0) precision = 1;
+ decimal *= precision;
+ decimal >>= fp.fraction.bits;
+ if (asprintf(&frac, ".%0*llu", len, decimal) == -1) return NULL;
+ }
+ if (asprintf(&result, "%s%llu%s%c", (suffix == NULL && negative) ? "-" : "",
+ (negative) ? (-fp.integer.value) : fp.integer.value, frac,
+ (suffix && !negative) ? suffix[0] :
+ (suffix && negative) ? suffix[1] :
+ ' ') == -1) {
+ free(frac);
+ return NULL;
+ }
+ free(frac);
+ if (!suffix) result[strlen(result) - 1] = '\0';
+ return result;
+}
+
+/**
+ * Turn a fixed point number into its representation in a buffer.
+ *
+ * @param fp Fixed point number.
+ * @param buf Output buffer.
+ * @param shift Number of bits to skip at the beginning of the buffer.
+ *
+ * The representation of a fixed point number is the precision (always 6 bits
+ * because we assume that int part + frac part does not exceed 64 bits), the
+ * integer part and the fractional part.
+ */
+void
+fp_fptobuf(struct fp_number fp, unsigned char *buf, unsigned shift)
+{
+ unsigned long long value = (fp.integer.value >= 0) ?
+ ((fp.integer.value << fp.fraction.bits) + fp.fraction.value) :
+ (~(((unsigned long long)(-fp.integer.value) << fp.fraction.bits) +
+ fp.fraction.value) +
+ 1);
+ unsigned long long ints[] = { fp.integer.bits + fp.fraction.precision, value };
+ unsigned int bits[] = { 6, fp.integer.bits + fp.fraction.bits };
+
+ unsigned i, obit, o;
+ for (i = 0, obit = 8 - (shift % 8), o = shift / 8; i < 2;) {
+ if (obit > bits[i]) {
+ /* We need to clear bits that will be overwritten but do not
+ * touch other bits */
+ if (bits[i] != 0) {
+ buf[o] = buf[o] &
+ (~((1 << obit) - 1) |
+ ((1 << (obit - bits[i])) - 1));
+ buf[o] = buf[o] |
+ ((ints[i] & ((1 << bits[i]) - 1))
+ << (obit - bits[i]));
+ obit -= bits[i];
+ }
+ i++;
+ } else {
+ /* As in the other branch... */
+ buf[o] = buf[o] & (~((1 << obit) - 1));
+ buf[o] = buf[o] |
+ ((ints[i] >> (bits[i] - obit)) & ((1 << obit) - 1));
+ bits[i] -= obit;
+ obit = 8;
+ o++;
+ }
+ }
+}
+
+/**
+ * Parse a fixed point number from a buffer.
+ *
+ * @param buf Input buffer
+ * @param intbits Number of bits used for integer part.
+ * @param fltbits Number of bits used for fractional part.
+ * @param shift Number of bits to skip at the beginning of the buffer.
+ *
+ * @return the parsed fixed point number.
+ *
+ * The representation is the same as for @c fp_fptobuf().
+ */
+struct fp_number
+fp_buftofp(const unsigned char *buf, unsigned intbits, unsigned fltbits, unsigned shift)
+{
+ unsigned long long value = 0, precision = 0;
+ unsigned long long *ints[] = { &precision, &value };
+ unsigned int bits[] = { 6, intbits + fltbits };
+
+ unsigned o, ibit, i;
+ for (o = 0, ibit = 8 - (shift % 8), i = shift / 8; o < 2;) {
+ if (ibit > bits[o]) {
+ if (bits[o] > 0) {
+ *ints[o] = *ints[o] |
+ ((buf[i] >> (ibit - bits[o])) &
+ ((1ULL << bits[o]) - 1));
+ ibit -= bits[o];
+ }
+ o++;
+ } else {
+ *ints[o] = *ints[o] |
+ ((buf[i] & ((1ULL << ibit) - 1)) << (bits[o] - ibit));
+ bits[o] -= ibit;
+ ibit = 8;
+ i++;
+ }
+ }
+
+ /* Don't handle too low precision */
+ if (precision > intbits)
+ precision -= intbits;
+ else
+ precision = intbits;
+
+ int negative = !!(value & (1ULL << (intbits + fltbits - 1)));
+ if (negative) value = (~value + 1) & ((1ULL << (intbits + fltbits - 1)) - 1);
+ struct fp_number result = { .integer = { value >> fltbits, intbits },
+ .fraction = { value & ((1ULL << fltbits) - 1), fltbits, precision } };
+ if (negative) result.integer.value = -result.integer.value;
+
+ return result;
+}
+
+/**
+ * Negate a fixed point number.
+ */
+struct fp_number
+fp_negate(struct fp_number fp)
+{
+ unsigned intbits = fp.integer.bits;
+ struct fp_number result = fp;
+ result.integer.value = -result.integer.value;
+ if (result.integer.value >= (1LL << (intbits - 1)))
+ result.integer.value = (1LL << (intbits - 1)) - 1;
+ else if (result.integer.value < ~(1LL << (intbits - 1)) + 1)
+ result.integer.value = ~(1LL << (intbits - 1)) + 1;
+ return result;
+}
+
+#endif
diff --git a/src/lib/fixedpoint.h b/src/lib/fixedpoint.h
new file mode 100644
index 0000000..8b4216c
--- /dev/null
+++ b/src/lib/fixedpoint.h
@@ -0,0 +1,42 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/*
+ * Copyright (c) 2012 Vincent Bernat <bernat@luffy.cx>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#if HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#if !defined FIXEDPOINT_H && defined ENABLE_LLDPMED
+# define FIXEDPOINT_H
+
+struct fp_number {
+ struct {
+ long long value;
+ unsigned bits;
+ } integer;
+ struct {
+ long long value;
+ unsigned bits;
+ unsigned precision;
+ } fraction;
+};
+struct fp_number fp_strtofp(const char *, char **, unsigned, unsigned);
+struct fp_number fp_buftofp(const unsigned char *, unsigned, unsigned, unsigned);
+struct fp_number fp_negate(struct fp_number);
+char *fp_fptostr(struct fp_number, const char *);
+void fp_fptobuf(struct fp_number, unsigned char *, unsigned);
+
+#endif
diff --git a/src/lib/helpers.c b/src/lib/helpers.c
new file mode 100644
index 0000000..973f8f4
--- /dev/null
+++ b/src/lib/helpers.c
@@ -0,0 +1,78 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/*
+ * Copyright (c) 2015 Vincent Bernat <vincent@bernat.im>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+#include <arpa/inet.h>
+
+#include "lldpctl.h"
+#include "../log.h"
+#include "atom.h"
+#include "helpers.h"
+
+const char *
+map_lookup(lldpctl_map_t *list, int n)
+{
+
+ unsigned int i;
+
+ for (i = 0; list[i].string != NULL; i++) {
+ if (list[i].value == n) {
+ return list[i].string;
+ }
+ }
+
+ return "unknown";
+}
+
+int
+map_reverse_lookup(lldpctl_map_t *list, const char *string)
+{
+ if (!string) return -1;
+
+ for (unsigned int i = 0; list[i].string != NULL; i++) {
+ if (!strcasecmp(list[i].string, string)) return list[i].value;
+ }
+
+ return -1;
+}
+
+int
+_lldpctl_atom_new_any_list(lldpctl_atom_t *atom, va_list ap)
+{
+ struct _lldpctl_atom_any_list_t *plist =
+ (struct _lldpctl_atom_any_list_t *)atom;
+ plist->parent = va_arg(ap, struct _lldpctl_atom_port_t *);
+ lldpctl_atom_inc_ref((lldpctl_atom_t *)plist->parent);
+ return 1;
+}
+
+void
+_lldpctl_atom_free_any_list(lldpctl_atom_t *atom)
+{
+ struct _lldpctl_atom_any_list_t *plist =
+ (struct _lldpctl_atom_any_list_t *)atom;
+ lldpctl_atom_dec_ref((lldpctl_atom_t *)plist->parent);
+}
+
+char *
+xstrdup(const char *str)
+{
+ if (!str) return NULL;
+ return strdup(str);
+}
diff --git a/src/lib/helpers.h b/src/lib/helpers.h
new file mode 100644
index 0000000..6f7ae0b
--- /dev/null
+++ b/src/lib/helpers.h
@@ -0,0 +1,24 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/*
+ * Copyright (c) 2015 Vincent Bernat <vincent@bernat.im>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+const char *map_lookup(lldpctl_map_t *list, int n);
+int map_reverse_lookup(lldpctl_map_t *list, const char *string);
+
+int _lldpctl_atom_new_any_list(lldpctl_atom_t *atom, va_list ap);
+void _lldpctl_atom_free_any_list(lldpctl_atom_t *atom);
+
+char *xstrdup(const char *);
diff --git a/src/lib/lldpctl.h b/src/lib/lldpctl.h
new file mode 100644
index 0000000..5d2668f
--- /dev/null
+++ b/src/lib/lldpctl.h
@@ -0,0 +1,1186 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/*
+ * Copyright (c) 2012 Vincent Bernat <bernat@luffy.cx>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef LLDPCTL_H
+#define LLDPCTL_H
+
+/**
+ * @defgroup liblldpctl liblldpctl: library to interface with lldpd
+ *
+ * `liblldpctl` allows any program to convenienty query and modify the behaviour
+ * of a running lldpd daemon.
+ *
+ * To use this library, use `pkg-config` to get the appropriate options:
+ * * `pkg-config --libs lldpctl` for `LIBS` or `LDFLAGS`
+ * * `pkg-config --cflags lldpctl` for `CFLAGS`
+ *
+ * @warning This library is tightly coupled with lldpd. The library to use
+ * should be the one shipped with lldpd. Clients of the library are then tied
+ * by the classic API/ABI rules and may be compiled separatly.
+ *
+ * There are two important structures in this library: @c lldpctl_conn_t which
+ * represents a connection and @c lldpctl_atom_t which represents a piece of
+ * information. Those types are opaque. No direct access to them should be done.
+ *
+ * The library is expected to be reentrant and therefore thread-safe. It is
+ * however not expected that a connection to be used in several thread
+ * simultaneously. This also applies to the different pieces of information
+ * gathered through this connection. Several connection to lldpd can be used
+ * simultaneously.
+ *
+ * The first step is to establish a connection. See @ref lldpctl_connection for
+ * more information about this. The next step is to query the lldpd daemon. See
+ * @ref lldpctl_atoms on how to do this.
+ *
+ * `liblldpctl` tries to handle errors in a coherent way. Any function returning
+ * a pointer will return @c NULL on error and the last error can be retrieved
+ * through @ref lldpctl_last_error() function. Most functions returning integers
+ * will return a negative integer representing the error if something goes
+ * wrong. The use of @ref lldpctl_last_error() allows one to check if this is a
+ * real error if there is a doubt. See @ref lldpctl_errors_logs for more about
+ * this.
+ *
+ * @{
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <sys/types.h>
+
+/**
+ * @defgroup lldpctl_connection Managing connection to lldpd
+ *
+ * Connection with lldpd.
+ *
+ * This library does not handle IO. They are delegated to a set of functions to
+ * allow a user to specify exactly how IO should be done. A user is expected to
+ * provide two functions: the first one is called when the library requests
+ * incoming data, the other one when it requests outgoing data. Moreover, the
+ * user is also expected to call the appropriate functions when data comes back
+ * (@ref lldpctl_recv()) or needs to be sent (@ref lldpctl_send()).
+ *
+ * Because the most common case is synchronous IO, `liblldpctl` will use classic
+ * synchronous IO with the Unix socket if no IO functions are provided by the
+ * user. For all other cases, the user must provide the appropriate functions.
+ *
+ * A connection should be allocated by using @ref lldpctl_new(). It needs to be
+ * released with @ref lldpctl_release().
+ *
+ * @{
+ */
+
+/**
+ * Get default transport name.
+ *
+ * Currently, this is the default location of the Unix socket.
+ */
+const char *lldpctl_get_default_transport(void);
+
+/**
+ * Structure referencing a connection with lldpd.
+ *
+ * This structure should be handled as opaque. It can be allocated
+ * with @c lldpctl_new() and the associated resources will be freed
+ * with @c lldpctl_release().
+ */
+typedef struct lldpctl_conn_t lldpctl_conn_t;
+
+/**
+ * Callback function invoked to send data to lldpd.
+ *
+ * @param conn Handle to the connection to lldpd.
+ * @param data Bytes to be sent.
+ * @param length Length of provided data.
+ * @param user_data Provided user data.
+ * @return The number of bytes really sent or either @c LLDPCTL_ERR_WOULDBLOCK
+ * if no bytes can be sent without blocking or @c
+ * LLDPCTL_ERR_CALLBACK_FAILURE for other errors.
+ */
+typedef ssize_t (*lldpctl_send_callback)(lldpctl_conn_t *conn, const uint8_t *data,
+ size_t length, void *user_data);
+
+/**
+ * Callback function invoked to receive data from lldpd.
+ *
+ * @param conn Handle to the connection to lldpd.
+ * @param data Buffer for receiving data
+ * @param length Maximum bytes we can receive
+ * @param user_data Provided user data.
+ * @return The number of bytes really received or either @c
+ * LLDPCTL_ERR_WOULDBLOCK if no bytes can be received without blocking,
+ * @c LLDPCTL_ERR_CALLBACK_FAILURE for other errors or @c
+ * LLDPCTL_ERR_EOF if end of file was reached.
+ */
+typedef ssize_t (*lldpctl_recv_callback)(lldpctl_conn_t *conn, const uint8_t *data,
+ size_t length, void *user_data);
+
+/**
+ * Function invoked when additional data is available from lldpd.
+ *
+ * This function should be invoked in case of asynchronous IO when new data is
+ * available from lldpd (expected or unexpected).
+ *
+ * @param conn Handle to the connection to lldpd.
+ * @param data Data received from lldpd.
+ * @param length Length of data received.
+ * @return The number of bytes available or a negative integer if an error has
+ * occurred. 0 is not an error. It usually means that a notification has
+ * been processed.
+ */
+ssize_t lldpctl_recv(lldpctl_conn_t *conn, const uint8_t *data, size_t length);
+
+/**
+ * Function invoked when there is an opportunity to send data to lldpd.
+ *
+ * This function should be invoked in case of asynchronous IO when new data can
+ * be written to lldpd.
+ *
+ * @param conn Handle to the connection to lldpd.
+ * @return The number of bytes processed or a negative integer if an error has
+ * occurred.
+ */
+ssize_t lldpctl_send(lldpctl_conn_t *conn);
+
+/**
+ * Function invoked to see if there's more data to be processed in the buffer.
+ *
+ * This function should be invoked to check for notifications in the data that
+ * has already been read. Its used typically for asynchronous connections.
+ *
+ * @param conn Handle to the connection to lldpd.
+ * @return 0 to indicate maybe more data is available for processing
+ * !0 to indicate no data or insufficient data for processing
+ */
+int lldpctl_process_conn_buffer(lldpctl_conn_t *conn);
+
+/**
+ * Allocate a new handler for connecting to lldpd.
+ *
+ * @param send Callback to be used when sending new data is requested.
+ * @param recv Callback to be used when receiving new data is requested.
+ * @param user_data Data to pass to callbacks.
+ * @return An handler to be used to connect to lldpd or @c NULL in
+ * case of error. In the later case, the error is probable an
+ * out of memory condition.
+ *
+ * The allocated handler can be released with @c lldpctl_release(). If the
+ * provided parameters are both @c NULL, default synchronous callbacks will be
+ * used.
+ */
+lldpctl_conn_t *lldpctl_new(lldpctl_send_callback send, lldpctl_recv_callback recv,
+ void *user_data);
+
+/**
+ * Allocate a new handler for connecting to lldpd.
+ *
+ * @param ctlname the Unix-domain socket to connect to lldpd.
+ * @param send Callback to be used when sending new data is requested.
+ * @param recv Callback to be used when receiving new data is requested.
+ * @param user_data Data to pass to callbacks.
+ * @return An handler to be used to connect to lldpd or @c NULL in
+ * case of error. In the later case, the error is probable an
+ * out of memory condition.
+ *
+ * The allocated handler can be released with @c lldpctl_release(). If the
+ * provided parameters are both @c NULL, default synchronous callbacks will be
+ * used.
+ */
+lldpctl_conn_t *lldpctl_new_name(const char *ctlname, lldpctl_send_callback send,
+ lldpctl_recv_callback recv, void *user_data);
+
+/**
+ * Release resources associated with a connection to lldpd.
+ *
+ * @param conn Previously allocated handler to a connection to lldpd.
+ * @return 0 on success or a negative integer
+ *
+ * @see lldpctl_new()
+ */
+int lldpctl_release(lldpctl_conn_t *conn);
+/**@}*/
+
+/**
+ * @defgroup lldpctl_errors_logs Errors and logs handling
+ *
+ * Error codes and logs handling.
+ *
+ * When a function returns a pointer, it may return @c NULL to indicate an error
+ * condition. In this case, it is possible to use @ref lldpctl_last_error() to
+ * get the related error code which is one of the values in @ref lldpctl_error_t
+ * enumeration. For display purpose @ref lldpctl_strerror() may be used to
+ * translate this error code.
+ *
+ * When a function returns an integer, it may return a negative value. It
+ * usually means this is an error but some functions may return a legitimate
+ * negative value (for example @ref lldpctl_atom_get_int()). When there is a
+ * doubt, @ref lldpctl_last_error() should be checked.
+ *
+ * An error is attached to a connection. If there is no connection, no error
+ * handling is available. Most functions use a connection or an atom as first
+ * argument and therefore are attached to a connection. To get the connection
+ * related to an atom, use @ref lldpctl_atom_get_connection().
+ *
+ * Also have a look at @ref lldpctl_log_callback() function if you want a custom
+ * log handling.
+ *
+ * @{
+ */
+
+/**
+ * Setup log handlers.
+ *
+ * By default, liblldpctl will log to stderr. The following function will
+ * register another callback for this purpose. Messages logged through this
+ * callback may be cryptic. They are targeted for the developer. Message for end
+ * users should rely on return codes.
+ */
+void lldpctl_log_callback(void (*cb)(int severity, const char *msg));
+
+/**
+ * Setup log level.
+ *
+ * By default, liblldpctl will only log warnings. The following function allows
+ * to increase verbosity. This function has no effect if callbacks are
+ * registered with the previous function.
+ *
+ * @param level Level of verbosity (1 = warnings, 2 = info, 3 = debug).
+ */
+void lldpctl_log_level(int level);
+
+/**
+ * Possible error codes for functions that return negative integers on
+ * this purpose or for @c lldpctl_last_error().
+ */
+typedef enum {
+ /**
+ * No error has happened (yet).
+ */
+ LLDPCTL_NO_ERROR = 0,
+ /**
+ * A IO related operation would block if performed.
+ */
+ LLDPCTL_ERR_WOULDBLOCK = -501,
+ /**
+ * A IO related operation has reached a end of file condition.
+ */
+ LLDPCTL_ERR_EOF = -502,
+ /**
+ * The requested information does not exist. For example, when
+ * requesting an inexistant information from an atom.
+ */
+ LLDPCTL_ERR_NOT_EXIST = -503,
+ /**
+ * Cannot connect to the lldpd daemon. This error only happens with
+ * default synchronous handlers.
+ */
+ LLDPCTL_ERR_CANNOT_CONNECT = -504,
+ /**
+ * Atom is of incorrect type for the requested operation.
+ */
+ LLDPCTL_ERR_INCORRECT_ATOM_TYPE = -505,
+ /**
+ * An error occurred during serialization of message.
+ */
+ LLDPCTL_ERR_SERIALIZATION = -506,
+ /**
+ * The requested operation cannot be performed because we have another
+ * operation already running.
+ */
+ LLDPCTL_ERR_INVALID_STATE = -507,
+ /**
+ * The provided atom cannot be iterated.
+ */
+ LLDPCTL_ERR_CANNOT_ITERATE = -508,
+ /**
+ * The provided value is invalid.
+ */
+ LLDPCTL_ERR_BAD_VALUE = -509,
+ /**
+ * No new element can be created for this element.
+ */
+ LLDPCTL_ERR_CANNOT_CREATE = -510,
+ /**
+ * The library is under unexpected conditions and cannot process
+ * any further data reliably.
+ */
+ LLDPCTL_ERR_FATAL = -900,
+ /**
+ * Out of memory condition. Things may get havoc here but we
+ * should be able to recover.
+ */
+ LLDPCTL_ERR_NOMEM = -901,
+ /**
+ * An error occurred in a user provided callback.
+ */
+ LLDPCTL_ERR_CALLBACK_FAILURE = -902
+} lldpctl_error_t;
+
+/**
+ * Describe a provided error code.
+ *
+ * @param error Error code to be described.
+ * @return Statically allocated string describing the error.
+ */
+const char *lldpctl_strerror(lldpctl_error_t error);
+
+/**
+ * Get the last error associated to a connection to lldpd.
+ *
+ * @param conn Previously allocated handler to a connection to lldpd.
+ * @return 0 if no error is currently registered. A negative integer
+ * otherwise.
+ *
+ * For functions returning int, this function will return the same
+ * error number. For functions returning something else, you can use
+ * this function to get the appropriate error number.
+ */
+lldpctl_error_t lldpctl_last_error(lldpctl_conn_t *conn);
+
+/**
+ * Describe the last error associate to a connection.
+ *
+ * @param conn Previously allocated handler to a connection to lldpd.
+ * @return Statically allocated string describing the error
+ */
+#define lldpctl_last_strerror(conn) lldpctl_strerror(lldpctl_last_error(conn))
+/**@}*/
+
+/**
+ * @defgroup lldpctl_atoms Extracting information: atoms
+ *
+ * Information retrieved from lldpd is represented as an atom.
+ *
+ * This is an opaque structure that can be passed along some functions to
+ * transmit chassis, ports, VLAN and other information related to LLDP. Most
+ * information are extracted using @c lldpctl_atom_get(), @c
+ * lldpctl_atom_get_str(), @c lldpctl_atom_get_buffer() or @c
+ * lldpctl_atom_get_int(), unless some IO with lldpd is needed to retrieve the
+ * requested information. In this case, there exists an appropriate function to
+ * convert the "deferred" atom into a normal one (like @c lldpctl_get_port()).
+ *
+ * For some information, setters are also available: @c lldpctl_atom_set(), @c
+ * lldpctl_atom_set_str(), @c lldpctl_atom_set_buffer() or @c
+ * lldpctl_atom_set_int(). Unlike getters, some of those may require IO to
+ * achieve their goal.
+ *
+ * An atom is reference counted. The semantics are quite similar to Python and
+ * you must be careful of the ownership of a reference. It is possible to own a
+ * reference by calling @c lldpctl_atom_inc_ref(). Once the atom is not needed
+ * any more, you can abandon ownership with @c lldpctl_atom_dec_ref(). Unless
+ * documented otherwise, a function returning an atom will return a new
+ * reference (the ownership is assigned to the caller, no need to call @c
+ * lldpctl_atom_inc_ref()). Unless documented otherwise, when providing an atom
+ * to a function, the atom is usually borrowed (no change in reference
+ * counting). Currently, no function will steal ownership.
+ *
+ * It is quite important to use the reference counting functions
+ * correctly. Segfaults or memory leaks may occur otherwise. Once the reference
+ * count reaches 0, the atom is immediately freed. Reusing it will likely lead
+ * to memory corruption.
+ *
+ * @{
+ */
+
+/**
+ * Structure representing an element (chassis, port, VLAN, ...)
+ *
+ * @see lldpctl_atom_inc_ref(), lldpctl_atom_dec_ref().
+ */
+typedef struct lldpctl_atom_t lldpctl_atom_t;
+
+/**
+ * Structure representing a map from an integer to a character string.
+ *
+ * @see lldpctl_key_get_map().
+ */
+typedef const struct {
+ int value;
+ const char *string;
+} lldpctl_map_t;
+
+/**
+ * Return the reference to connection with lldpd.
+ *
+ * @param atom The atom we want reference from.
+ * @return The reference to the connection to lldpd.
+ *
+ * Each atom contains an internal reference to the corresponding connection to
+ * lldpd. Use this function to get it.
+ */
+lldpctl_conn_t *lldpctl_atom_get_connection(lldpctl_atom_t *atom);
+
+/**
+ * Increment reference count for an atom.
+ *
+ * @param atom Atom we which to increase reference count.
+ */
+void lldpctl_atom_inc_ref(lldpctl_atom_t *atom);
+
+/**
+ * Decrement reference count for an atom.
+ *
+ * @param atom Atom we want to decrease reference count. Can be @c NULL. In this
+ * case, nothing happens.
+ *
+ * When the reference count becomes 0, the atom is freed.
+ */
+void lldpctl_atom_dec_ref(lldpctl_atom_t *atom);
+
+/**
+ * Possible events for a change (notification).
+ *
+ * @see lldpctl_watch_callback2
+ */
+typedef enum {
+ lldpctl_c_deleted, /**< The neighbor has been deleted */
+ lldpctl_c_updated, /**< The neighbor has been updated */
+ lldpctl_c_added, /**< This is a new neighbor */
+} lldpctl_change_t;
+
+/**
+ * Callback function invoked when a change is detected.
+ *
+ * @param conn Connection with lldpd. Should not be used.
+ * @param type Type of change detected.
+ * @param interface Physical interface on which the change has happened.
+ * @param neighbor Changed neighbor.
+ * @param data Data provided when registering the callback.
+ *
+ * The provided interface and neighbor atoms are stolen by the callback: their
+ * reference count are decremented when the callback ends. If you want to keep a
+ * reference to it, be sure to increment the reference count in the callback.
+ *
+ * @warning The provided connection should not be used at all. Do not use @c
+ * lldpctl_atom_set_*() functions on @c interface or @c neighbor either. If you
+ * do, you will get a @c LLDPCTL_ERR_INVALID_STATE error.
+ *
+ * @see lldpctl_watch_callback
+ */
+typedef void (*lldpctl_change_callback)(lldpctl_conn_t *conn, lldpctl_change_t type,
+ lldpctl_atom_t *interface, lldpctl_atom_t *neighbor, void *data);
+
+/**
+ * Callback function invoked when a change is detected.
+ *
+ * @param type Type of change detected.
+ * @param interface Physical interface on which the change has happened.
+ * @param neighbor Changed neighbor.
+ * @param data Data provided when registering the callback.
+ *
+ * The provided interface and neighbor atoms are stolen by the callback: their
+ * reference count are decremented when the callback ends. If you want to keep a
+ * reference to it, be sure to increment the reference count in the callback.
+ *
+ * @see lldpctl_watch_callback2
+ */
+typedef void (*lldpctl_change_callback2)(lldpctl_change_t type,
+ lldpctl_atom_t *interface, lldpctl_atom_t *neighbor, void *data);
+
+/**
+ * Register a callback to be called on changes.
+ *
+ * @param conn Connection with lldpd.
+ * @param cb Replace the current callback with the provided one.
+ * @param data Data that will be passed to the callback.
+ * @return 0 in case of success or -1 in case of errors.
+ *
+ * This function will register the necessity to push neighbor changes to lldpd
+ * and therefore will issue IO operations. The error code could then be @c
+ * LLDPCTL_ERR_WOULDBLOCK.
+ *
+ * @warning Once a callback is registered, the connection shouldn't be used for
+ * anything else than receiving notifications. If you do, you will get a @c
+ * LLDPCTL_ERR_INVALID_STATE error.
+ *
+ * @deprecated This function is deprecated and lldpctl_watch_callback2 should be
+ * used instead.
+ */
+int lldpctl_watch_callback(lldpctl_conn_t *conn, lldpctl_change_callback cb, void *data)
+ __attribute__((deprecated));
+
+/**
+ * Register a callback to be called on changes.
+ *
+ * @param conn Connection with lldpd.
+ * @param cb Replace the current callback with the provided one.
+ * @param data Data that will be passed to the callback.
+ * @return 0 in case of success or -1 in case of errors.
+ *
+ * This function will register the necessity to push neighbor changes to lldpd
+ * and therefore will issue IO operations. The error code could then be @c
+ * LLDPCTL_ERR_WOULDBLOCK.
+ *
+ * @warning Once a callback is registered, the connection shouldn't be used for
+ * anything else than receiving notifications. If you do, you will get a @c
+ * LLDPCTL_ERR_INVALID_STATE error.
+ */
+int lldpctl_watch_callback2(lldpctl_conn_t *conn, lldpctl_change_callback2 cb,
+ void *data);
+
+/**
+ * Wait for the next change.
+ *
+ * @param conn Connection with lldpd.
+ * @return 0 on success or a negative integer in case of error.
+ *
+ * This function will return once a change has been detected. It is only useful
+ * as a main loop when using the builtin blocking IO mechanism.
+ */
+int lldpctl_watch(lldpctl_conn_t *conn);
+
+/**
+ * @defgroup liblldpctl_atom_get_special Retrieving atoms from lldpd
+ *
+ * Special access functions.
+ *
+ * Most information can be retrieved through @ref lldpctl_atom_get(), @ref
+ * lldpctl_atom_get_int(), @ref lldpctl_atom_get_str() or @ref
+ * lldpctl_atom_get_buffer() but some information can only be retrieved through
+ * special functions because IO operation is needed (and also, for some of them,
+ * because we don't have an atom yet).
+ *
+ * @{
+ */
+
+/**
+ * Retrieve global configuration of lldpd daemon.
+ *
+ * @param conn Connection with lldpd.
+ * @return The global configuration or @c NULL if an error happened.
+ *
+ * This function will make IO with the daemon to get the
+ * configuration. Depending on the IO model, information may not be available
+ * right now and the function should be called again later. If @c NULL is
+ * returned, check the last error. If it is @c LLDPCTL_ERR_WOULDBLOCK, try again
+ * later.
+ */
+lldpctl_atom_t *lldpctl_get_configuration(lldpctl_conn_t *conn);
+
+/**
+ * Retrieve the list of available interfaces.
+ *
+ * @param conn Previously allocated handler to a connection to lldpd.
+ * @return The list of available ports or @c NULL if an error happened.
+ *
+ * This function will make IO with the daemon to get the list of
+ * ports. Depending on the IO model, information may not be available right now
+ * and the function should be called again later. If @c NULL is returned, check
+ * what the last error is. If it is @c LLDPCTL_ERR_WOULDBLOCK, try again later
+ * (when more data is available).
+ *
+ * The list of available ports can be iterated with @ref lldpctl_atom_foreach().
+ */
+lldpctl_atom_t *lldpctl_get_interfaces(lldpctl_conn_t *conn);
+
+/**
+ * Retrieve the information related to the local chassis.
+ *
+ * @param conn Previously allocated handler to a connection to lldpd.
+ * @return Atom related to the local chassis which may be used in subsequent functions.
+ *
+ * This function may have to do IO to get the information related to the local
+ * chassis. Depending on the IO mode, information may not be available right now
+ * and the function should be called again later. If @c NULL is returned, check
+ * what the last error is. If it is @c LLDPCTL_ERR_WOULDBLOCK, try again later
+ * (when more data is available).
+ */
+lldpctl_atom_t *lldpctl_get_local_chassis(lldpctl_conn_t *conn);
+
+/**
+ * Retrieve the information related to a given interface.
+ *
+ * @param port The port we want to retrieve information from. This port is an
+ * atom retrieved from an interation on @c lldpctl_get_interfaces().
+ * @return Atom related to this port which may be used in subsequent functions.
+ *
+ * This function may have to do IO to get the information related to the given
+ * port. Depending on the IO mode, information may not be available right now
+ * and the function should be called again later. If @c NULL is returned, check
+ * what the last error is. If it is @c LLDPCTL_ERR_WOULDBLOCK, try again later
+ * (when more data is available).
+ */
+lldpctl_atom_t *lldpctl_get_port(lldpctl_atom_t *port);
+
+/**
+ * Retrieve the default port information.
+ *
+ * This port contains default settings whenever a new port needs to be created.
+ *
+ * @param conn Previously allocated handler to a connection to lldpd.
+ * @return Atom of the default port which may be used in subsequent functions.
+ *
+ * This function may have to do IO to get the information related to the given
+ * port. Depending on the IO mode, information may not be available right now
+ * and the function should be called again later. If @c NULL is returned, check
+ * what the last error is. If it is @c LLDPCTL_ERR_WOULDBLOCK, try again later
+ * (when more data is available).
+ */
+lldpctl_atom_t *lldpctl_get_default_port(lldpctl_conn_t *conn);
+
+/**@}*/
+
+/**
+ * Piece of information that can be retrieved from/written to an atom.
+ *
+ * Each piece of information can potentially be retrieved as an atom (A), a
+ * string (S), a buffer (B) or an integer (I). Additionaly, when an information
+ * can be retrieved as an atom, it is usually iterable (L). When an atom can be
+ * retrieved as a string and as an additional type, the string is expected to be
+ * formatted. For example, the MAC address of a local port can be retrieved as a
+ * buffer and a string. As a string, you'll get something like
+ * "00:11:22:33:44:55". Also, all values that can be get as an integer or a
+ * buffer can be get as a string too. There is no special formatting in this
+ * case. "(BS)" means that the string get a special appropriate format.
+ *
+ * The name of a key is an indication on the type of atom that information can
+ * be extracted from. For example, @c lldpctl_k_med_policy_type can be extracted
+ * from an atom you got by iterating on @c lldpctl_k_port_med_policies. On the
+ * other hand, @c lldpctl_k_port_descr and @c lldpctl_k_chassis can be retrieved
+ * from an atom retrieved either by iterating @c lldpctl_k_port_neighbors or
+ * with @c lldpctl_get_port().
+ *
+ * Some values may be written. They are marked with (W). Such a change may or
+ * may not be transmitted immediatly. If they are not transmitted immediatly,
+ * this means that the resulting atom should be written to another atom. For
+ * example, when writting @c lldpctl_k_med_policy_tagged, you need to write the
+ * resulting atom to @c lldpctl_k_port_med_policies. If the change is
+ * transmitted immediatly, you need to check the error status of the connection
+ * to know if it has been transmitted correctly. Notably, if you get @c
+ * LLDPCTL_ERR_WOULDBLOCK, you need to try again later. Usually, changes are
+ * transmitted immediatly. The exception are changes that need to be grouped to
+ * be consistent, like a LLDP MED location. When a change is transmitted
+ * immediatly, it is marked with (O). @c lldpctl_atom_set_str() may accept a @c
+ * NULL value. This case is marked with (N) and usually reset the item to the
+ * default value or no value.
+ *
+ * Some values may also be created. They are flagged with (C). This only applies
+ * to elements that can be iterated (L) and written (W). The element created
+ * still needs to be appended to the list by being written to it. The creation
+ * is done with @c lldpctl_atom_create().
+ *
+ * An atom marked with (S) can be retrieved as a string only. It cannot be
+ * written. An atom marked with (IS) can be retrieved as an integer and features
+ * an appropriate representation as a string (usually, the name of a constant)
+ * which is more meaningful than just the integer. An atom marked as (I) can be
+ * retrieved as an integer and as a string. In the later case, this is just a
+ * string representation of the integer. An atom marked with (AL) can be
+ * retrieved as an atom only and can be iterated over. This is usually a list of
+ * things. An atom marked (I,W) can be read as an integer or a string and can be
+ * written as an integer. The change would not be commited until the atom is
+ * written to the nearest atom supporting (A,WO) operation (eventually with an
+ * indirection, i.e first write to a (A,W), then to a (A,WO)).
+ */
+typedef enum {
+ lldpctl_k_config_tx_interval, /**< `(I,WO)` Transmit interval. When set to -1,
+ it is meant to transmit now. */
+ lldpctl_k_config_receiveonly, /**< `(I)` Receive only mode */
+ lldpctl_k_config_mgmt_pattern, /**< `(S,WON)` Pattern to choose the management
+ address */
+ lldpctl_k_config_iface_pattern, /**< `(S,WON)` Pattern of enabled interfaces */
+ lldpctl_k_config_cid_pattern, /**< `(S)` Interface pattern to choose the chassis
+ ID */
+ lldpctl_k_config_description, /**< `(S,WON)` Chassis description overridden */
+ lldpctl_k_config_platform, /**< `(S,WON)` Platform description overridden (CDP)
+ */
+ lldpctl_k_config_hostname, /**< `(S,WON)` System name overridden */
+ lldpctl_k_config_advertise_version, /**< `(I)` Advertise version */
+ lldpctl_k_config_lldpmed_noinventory, /**< `(I)` Disable LLDP-MED inventory */
+ lldpctl_k_config_paused, /**< `(I,WO)` lldpd is paused */
+ lldpctl_k_config_fast_start_enabled, /**< `(I,WO)` Is fast start enabled */
+ lldpctl_k_config_fast_start_interval, /**< `(I,WO)` Start fast transmit interval
+ */
+ lldpctl_k_config_ifdescr_update, /**< `(I,WO)` Enable or disable setting
+ interface description */
+ lldpctl_k_config_iface_promisc, /**< `(I,WO)` Enable or disable promiscuous mode
+ on interfaces */
+ lldpctl_k_config_chassis_cap_advertise, /**< `(I,WO)` Enable or disable chassis
+ capabilities advertisement */
+ lldpctl_k_config_chassis_mgmt_advertise, /**< `(I,WO)` Enable or disable
+ management addresses advertisement
+ */
+ lldpctl_k_config_cid_string, /**< `(S,WON)` User defined string for the chassis
+ ID */
+ lldpctl_k_config_perm_iface_pattern, /**< `(S,WON)` Pattern of permanent
+ interfaces */
+ lldpctl_k_config_tx_interval_ms, /**< `(I,WO)` Transmit interval in
+ milliseconds. Set to -1 to transmit now. */
+ lldpctl_k_config_chassis_cap_override, /**< `(I,WO)` Override chassis
+ capabilities */
+
+ lldpctl_k_interface_name = 1000, /**< `(S)` The interface name. */
+
+ lldpctl_k_port_name =
+ 1100, /**< `(S)` The port name. Only works for a local port. */
+ lldpctl_k_port_index, /**< `(I)` The port index. Only works for a local port. */
+ /**
+ * `(AL)` The list of known neighbors for this port.
+ *
+ * A neighbor is in fact a remote port.
+ */
+ lldpctl_k_port_neighbors = 1200,
+ lldpctl_k_port_protocol, /**< `(IS)` The protocol that was used to retrieve this
+ information. */
+ lldpctl_k_port_age, /**< `(I)` Age of information, seconds from epoch. */
+ lldpctl_k_port_id_subtype, /**< `(IS)` The subtype ID of this port. */
+ lldpctl_k_port_id, /**< `(BS,WO)` The ID of this port. */
+ lldpctl_k_port_descr, /**< `(S,WO)` The description of this port. */
+ lldpctl_k_port_hidden, /**< `(I)` Is this port hidden (or should it be
+ displayed?)? */
+ lldpctl_k_port_status, /**< `(IS,WO)` Operational status of this (local) port */
+ lldpctl_k_port_chassis, /**< `(A)` Chassis associated to the port */
+ lldpctl_k_port_ttl, /**< `(I)` TTL for port, 0 if info is attached to chassis */
+ lldpctl_k_port_vlan_tx, /**< `(I,W)` VLAN tag for TX on port, -1 VLAN disabled
+ */
+
+ lldpctl_k_port_dot3_mfs = 1300, /**< `(I)` MFS */
+ lldpctl_k_port_dot3_aggregid, /**< `(I)` Port aggregation ID */
+ lldpctl_k_port_dot3_autoneg_support, /**< `(I)` Autonegotiation support. */
+ lldpctl_k_port_dot3_autoneg_enabled, /**< `(I)` Autonegotiation enabled. */
+ lldpctl_k_port_dot3_autoneg_advertised, /**< `(I)` Advertised protocols. See
+ `LLDP_DOT3_LINK_AUTONEG_*` */
+ lldpctl_k_port_dot3_mautype, /**< `(IS)` Current MAU type. See `LLDP_DOT3_MAU_*`
+ */
+
+ lldpctl_k_port_dot3_power = 1400, /**< `(A,WO)` Dot3 power related stuff. */
+ lldpctl_k_dot3_power_devicetype, /**< `(IS,W)` Device type. See
+ `LLDP_DOT3_POWER_PSE/PD` */
+ lldpctl_k_dot3_power_supported, /**< `(I,W)` Is MDI power supported. */
+ lldpctl_k_dot3_power_enabled, /**< `(I,W)` Is MDI power enabled. */
+ lldpctl_k_dot3_power_paircontrol, /**< `(I,W)` Pair-control enabled? */
+ lldpctl_k_dot3_power_pairs, /**< `(IS,W)` See `LLDP_DOT3_POWERPAIRS_*` */
+ lldpctl_k_dot3_power_class, /**< `(IS,W)` Power class. */
+ lldpctl_k_dot3_power_type, /**< `(I,W)` 802.3AT power type */
+ lldpctl_k_dot3_power_source, /**< `(IS,W)` 802.3AT power source */
+ lldpctl_k_dot3_power_priority, /**< `(IS,W)` 802.3AT power priority */
+ lldpctl_k_dot3_power_allocated, /**< `(I,W)` 802.3AT power allocated */
+ lldpctl_k_dot3_power_requested, /**< `(I,W)` 802.3AT power requested */
+
+ /* 802.3bt additions */
+ lldpctl_k_dot3_power_pd_4pid, /**< `(IS)` 802.3BT both modes supported? */
+ lldpctl_k_dot3_power_requested_a, /**< `(I)` 802.3BT power value requested for
+ A */
+ lldpctl_k_dot3_power_requested_b, /**< `(I)` 802.3BT power value requested for
+ B */
+ lldpctl_k_dot3_power_allocated_a, /**< `(I)` 802.3BT power value allocated for
+ A */
+ lldpctl_k_dot3_power_allocated_b, /**< `(I)` 802.3BT power value allocated for
+ B */
+ lldpctl_k_dot3_power_pse_status, /**< `(IS)` 802.3BT PSE powering status */
+ lldpctl_k_dot3_power_pd_status, /**< `(IS)` 802.3BT PD powering status */
+ lldpctl_k_dot3_power_pse_pairs_ext, /**< `(IS)` 802.3BT PSE power pairs */
+ lldpctl_k_dot3_power_class_a, /**< `(IS)` 802.3BT power class for A */
+ lldpctl_k_dot3_power_class_b, /**< `(IS)` 802.3BT power class for B */
+ lldpctl_k_dot3_power_class_ext, /**< `(IS)` 802.3BT power class */
+ lldpctl_k_dot3_power_type_ext, /**< `(IS)` 802.3BT power type */
+ lldpctl_k_dot3_power_pd_load, /**< `(IS)` 802.3BT dualsig isolated? */
+ lldpctl_k_dot3_power_pse_max, /**< `(I)` 802.3BT maximum available power */
+
+ lldpctl_k_port_vlan_pvid = 1500, /**< `(I)` Primary VLAN ID */
+ lldpctl_k_port_vlans, /**< `(AL)` List of VLAN */
+ lldpctl_k_vlan_id, /**< `(I)` VLAN ID */
+ lldpctl_k_vlan_name, /**< `(S)` VLAN name */
+
+ lldpctl_k_port_ppvids = 1600, /**< `(AL)` List of PPVIDs */
+ lldpctl_k_ppvid_status, /**< `(I)` Status of PPVID (see `LLDP_PPVID_CAP_*`) */
+ lldpctl_k_ppvid_id, /**< `(I)` ID of PPVID */
+
+ lldpctl_k_port_pis = 1700, /**< `(AL)` List of PIDs */
+ lldpctl_k_pi_id, /**< `(B)` PID value */
+
+ lldpctl_k_chassis_index = 1800, /**< `(I)` The chassis index. */
+ lldpctl_k_chassis_id_subtype, /**< `(IS)` The subtype ID of this chassis. */
+ lldpctl_k_chassis_id, /**< `(BS)` The ID of this chassis. */
+ lldpctl_k_chassis_name, /**< `(S)` The name of this chassis. */
+ lldpctl_k_chassis_descr, /**< `(S)` The description of this chassis. */
+ lldpctl_k_chassis_cap_available, /**< `(I)` Available capabilities (see
+ `LLDP_CAP_*`) */
+ lldpctl_k_chassis_cap_enabled, /**< `(I)` Enabled capabilities (see
+ `LLDP_CAP_*`) */
+ lldpctl_k_chassis_mgmt, /**< `(AL)` List of management addresses */
+ lldpctl_k_chassis_ttl, /**< Deprecated */
+
+ lldpctl_k_chassis_med_type =
+ 1900, /**< `(IS)` Chassis MED type. See `LLDP_MED_CLASS_*` */
+ lldpctl_k_chassis_med_cap, /**< `(I)` Available MED capabilities. See
+ `LLDP_MED_CAP_*` */
+ lldpctl_k_chassis_med_inventory_hw, /**< `(S,W)` LLDP MED inventory "Hardware
+ Revision" */
+ lldpctl_k_chassis_med_inventory_sw, /**< `(S,W)` LLDP MED inventory "Software
+ Revision" */
+ lldpctl_k_chassis_med_inventory_fw, /**< `(S,W)` LLDP MED inventory "Firmware
+ Revision" */
+ lldpctl_k_chassis_med_inventory_sn, /**< `(S,W)` LLDP MED inventory "Serial
+ Number" */
+ lldpctl_k_chassis_med_inventory_manuf, /**< `(S,W)` LLDP MED inventory
+ "Manufacturer" */
+ lldpctl_k_chassis_med_inventory_model, /**< `(S,W)` LLDP MED inventory "Model"
+ */
+ lldpctl_k_chassis_med_inventory_asset, /**< `(S,W)` LLDP MED inventory "Asset
+ ID" */
+
+ lldpctl_k_port_med_policies =
+ 2000, /**< `(AL,WO)` MED policies attached to a port. */
+ lldpctl_k_med_policy_type, /**< `(IS,W)` MED policy app type. See
+ `LLDP_MED_APPTYPE_*`. 0 if a policy is not
+ defined. */
+ lldpctl_k_med_policy_unknown, /**< `(I,W)` Is MED policy defined? */
+ lldpctl_k_med_policy_tagged, /**< `(I,W)` MED policy tagging */
+ lldpctl_k_med_policy_vid, /**< `(I,W)` MED policy VID */
+ lldpctl_k_med_policy_priority, /**< `(I,W)` MED policy priority */
+ lldpctl_k_med_policy_dscp, /**< `(I,W)` MED policy DSCP */
+
+ lldpctl_k_port_med_locations =
+ 2100, /**< `(AL,WO)` MED locations attached to a port. */
+ lldpctl_k_med_location_format, /**< `(IS,W)` MED location format. See
+ * `LLDP_MED_LOCFORMAT_*`. 0 if this
+ * location is not defined. When written,
+ * the following fields will be zeroed
+ * out. */
+ lldpctl_k_med_location_geoid, /**< `(IS,W)` MED geoid. See
+ `LLDP_MED_LOCATION_GEOID_*`. Only if format is
+ COORD. */
+ lldpctl_k_med_location_latitude, /**< `(S,W)` MED latitude. Only if format is
+ COORD. */
+ lldpctl_k_med_location_longitude, /**< `(S,W)` MED longitude. Only if format is
+ COORD. */
+ lldpctl_k_med_location_altitude, /**< `(S,W)` MED altitude. Only if format is
+ COORD. */
+ lldpctl_k_med_location_altitude_unit, /**< `(S,W)` MED altitude unit. See
+ * `LLDP_MED_LOCATION_ALTITUDE_UNIT_*`.
+ * Only if format is COORD. */
+
+ lldpctl_k_med_location_country =
+ 2200, /**< `(S,W)` MED country. Only if format is CIVIC. */
+ lldpctl_k_med_location_elin, /**< `(S,W)` MED ELIN. Only if format is ELIN. */
+
+ lldpctl_k_med_location_ca_elements =
+ 2300, /**< `(AL,WC)` MED civic address elements. Only if format is CIVIC */
+ lldpctl_k_med_civicaddress_type, /**< `(IS,W)` MED civic address type. */
+ lldpctl_k_med_civicaddress_value, /**< `(S,W)` MED civic address value. */
+
+ lldpctl_k_port_med_power = 2400, /**< `(A,WO)` LLDP-MED power related stuff. */
+ lldpctl_k_med_power_type, /**< `(IS,W)` LLDP MED power device type. See
+ `LLDP_MED_POW_TYPE_*` */
+ lldpctl_k_med_power_source, /**< `(IS,W)` LLDP MED power source. See
+ `LLDP_MED_POW_SOURCE_*` */
+ lldpctl_k_med_power_priority, /**< `(IS,W)` LLDP MED power priority. See
+ `LLDP_MED_POW_PRIO_*` */
+ lldpctl_k_med_power_val, /**< `(I,W)` LLDP MED power value */
+
+ lldpctl_k_mgmt_ip = 3000, /**< `(S)` IP address */
+ lldpctl_k_mgmt_iface_index = 30001, /**< `(I)` Interface index */
+
+ lldpctl_k_tx_cnt = 4000, /**< `(I)` tx cnt. Only works for a local port. */
+ lldpctl_k_rx_cnt, /**< `(I)` rx cnt. Only works for a local port. */
+ lldpctl_k_rx_discarded_cnt, /**< `(I)` discarded cnt. Only works for a local
+ port. */
+ lldpctl_k_rx_unrecognized_cnt, /**< `(I)` unrecognized cnt. Only works for a
+ local port. */
+ lldpctl_k_ageout_cnt, /**< `(I)` ageout cnt. Only works for a local port. */
+ lldpctl_k_insert_cnt, /**< `(I)` insert cnt. Only works for a local port. */
+ lldpctl_k_delete_cnt, /**< `(I)` delete cnt. Only works for a local port. */
+ lldpctl_k_config_tx_hold, /**< `(I,WO)` Transmit hold interval. */
+ lldpctl_k_config_bond_slave_src_mac_type, /**< `(I,WO)` bond slave src mac type.
+ */
+ lldpctl_k_config_lldp_portid_type, /**< `(I,WO)` LLDP PortID TLV Subtype */
+ lldpctl_k_config_lldp_agent_type, /**< `(I,WO)` LLDP agent type */
+ lldpctl_k_config_max_neighbors, /**< `(I,WO)`Maximum number of neighbors per
+ port. */
+
+ lldpctl_k_custom_tlvs = 5000, /**< `(AL)` custom TLVs */
+ lldpctl_k_custom_tlvs_clear, /**< `(WO)` clear list of custom TLVs */
+ lldpctl_k_custom_tlv, /**< `(AL,WO)` custom TLV **/
+ lldpctl_k_custom_tlv_oui, /**< `(B,W)` custom TLV Organizationally Unique
+ Identifier. Default is 0 (3 bytes) */
+ lldpctl_k_custom_tlv_oui_subtype, /**< `(I,W)` custom TLV subtype. Default is 0
+ (1 byte) */
+ lldpctl_k_custom_tlv_oui_info_string, /**< `(BS,W)` custom TLV Organizationally
+ Unique Identifier Information String
+ (up to 507 bytes) */
+ lldpctl_k_custom_tlv_op, /**< `(S,W)` custom TLV operation */
+
+} lldpctl_key_t;
+
+/**
+ * Get a map related to a key.
+ *
+ * Many keys expect to be written with a discrete number of values. Take for
+ * example @c lldpctl_k_med_civicaddress_type, it can take any integer between 1
+ * and 128. However, each integer can be named. It can be useful for an
+ * application to get a translation between the integer that can be provided and
+ * a more human-readable name. This function allows to retrieve the
+ * corresponding map.
+ *
+ * @param key The piece of information we want a map from.
+ * @return The map or @c NULL if no map is available.
+ *
+ * The returned map has its last element set to 0. It is also expected that the
+ * string value can be used with a set operation. It will be translated to the
+ * integer value.
+ */
+lldpctl_map_t *lldpctl_key_get_map(lldpctl_key_t key);
+
+/**
+ * Retrieve a bit of information as an atom.
+ *
+ * @param atom The atom we want to query.
+ * @param key The information we want from the atom.
+ * @return The atom representing the requested information or @c NULL if the
+ * information is not available.
+ *
+ * Not every value of @c info will be available as an atom. See the
+ * documentation of @c lldpctl_key_t for values accepting to be extracted as an
+ * atom. Usually, this is only iterable values or values representing a complex
+ * object.
+ *
+ * The provided atom is not a _borrowed_ reference. You need to decrement the
+ * reference count when you don't need it anymore.
+ *
+ * As a convenience, this function will return @c NULL if the first parameter is
+ * @c NULL and no error will be raised.
+ */
+lldpctl_atom_t *lldpctl_atom_get(lldpctl_atom_t *atom, lldpctl_key_t key);
+
+/**
+ * Set a bit of information with an atom.
+ *
+ * @param atom The atom we want to write to.
+ * @param key The key information we want to write.
+ * @param value The value of the information we want to write.
+ * @return The updated atom with the appropriate information.
+ *
+ * This function will return @c NULL in case of error. If the last error is @c
+ * LLDPCTL_ERR_WOULDBLOCK, the write should be retried later with the exact same
+ * parameters. LLDPCTL_ERR_BAD_VALUE is raised when the provided atom is not
+ * correct.
+ */
+lldpctl_atom_t *lldpctl_atom_set(lldpctl_atom_t *atom, lldpctl_key_t key,
+ lldpctl_atom_t *value);
+
+/**
+ * Retrieve a bit of information as a null-terminated string.
+ *
+ * @param atom The atom we want to query.
+ * @param key The information we want from the atom.
+ * @return The requested string or @c NULL if the information is not available.
+ *
+ * Not every value of @c info will be available as a string. See the
+ * documentation of @c lldpctl_key_t for values accepting to be extracted as a
+ * string. Usually, only piece of information stored as string are available in
+ * this form but sometimes, you can get a nice formatted string instead of an
+ * integer with this function.
+ *
+ * As a convenience, this function will return @c NULL if the first parameter is
+ * @c NULL and no error will be raised.
+ *
+ * The provided string may live inside the atom providing it. If you need it
+ * longer, duplicate it.
+ */
+const char *lldpctl_atom_get_str(lldpctl_atom_t *atom, lldpctl_key_t key);
+
+/**
+ * Set a bit of information using a null-terminated string.
+ *
+ * @param atom The atom we want to write to.
+ * @param key The key information we want to write.
+ * @param value The value of the information we want to write.
+ * @return The updated atom with the appropriate information.
+ *
+ * This function will return @c NULL in case of error. If the last error is @c
+ * LLDPCTL_ERR_WOULDBLOCK, the write should be retried later with the exact same
+ * parameters. LLDPCTL_ERR_BAD_VALUE is raised when the provided atom is not
+ * correct.
+ */
+lldpctl_atom_t *lldpctl_atom_set_str(lldpctl_atom_t *atom, lldpctl_key_t key,
+ const char *value);
+
+/**
+ * Retrieve a bit of information as a buffer.
+ *
+ * @param atom The atom we want to query.
+ * @param key The information we want from the atom.
+ * @param[out] length The size of the returned buffer.
+ * @return The requested buffer or @c NULL if the information is not available.
+ *
+ * Not every value of @c info will be available as a buffer. See the
+ * documentation of @c lldpctl_key_t for values accepting to be extracted as a
+ * string. Usually, only piece of information stored as buffer are available in
+ * this form.
+ *
+ * As a convenience, this function will return @c NULL if the first parameter is
+ * @c NULL and no error will be raised. If this function returns @c NULL, the
+ * third parameter is set to 0.
+ *
+ * The provided buffer may live inside the atom providing it. If you need it
+ * longer, duplicate it.
+ */
+const uint8_t *lldpctl_atom_get_buffer(lldpctl_atom_t *atom, lldpctl_key_t key,
+ size_t *length);
+
+/**
+ * Set a bit of information using a buffer
+ *
+ * @param atom The atom we want to write to.
+ * @param key The key information we want to write.
+ * @param value The value of the information we want to write.
+ * @param length The length of the provided buffer.
+ * @return The updated atom with the appropriate information.
+ *
+ * This function will return @c NULL in case of error. If the last error is @c
+ * LLDPCTL_ERR_WOULDBLOCK, the write should be retried later with the exact same
+ * parameters. LLDPCTL_ERR_BAD_VALUE is raised when the provided atom is not
+ * correct.
+ */
+lldpctl_atom_t *lldpctl_atom_set_buffer(lldpctl_atom_t *atom, lldpctl_key_t key,
+ const uint8_t *value, size_t length);
+
+/**
+ * Retrieve a bit of information as an integer.
+ *
+ * @param atom The atom we want to query.
+ * @param key The information we want from the atom.
+ * @return The requested integer or -1 if the information is not available
+ *
+ * Not every value of @c info will be available as an integer. See the
+ * documentation of @c lldpctl_key_t for values accepting to be extracted as a
+ * string. Usually, only piece of information stored as an integer are available
+ * in this form.
+ *
+ * Only @c lldpctl_last_error() can tell if the returned value is an error or
+ * not. However, most values extracted from lldpd cannot be negative.
+ */
+long int lldpctl_atom_get_int(lldpctl_atom_t *atom, lldpctl_key_t key);
+
+/**
+ * Set a bit of information using an integer
+ *
+ * @param atom The atom we want to write to.
+ * @param key The key information we want to write.
+ * @param value The value of the information we want to write.
+ * @return The updated atom with the appropriate information.
+ *
+ * This function will return @c NULL in case of error. If the last error is @c
+ * LLDPCTL_ERR_WOULDBLOCK, the write should be retried later with the exact same
+ * parameters. LLDPCTL_ERR_BAD_VALUE is raised when the provided atom is not
+ * correct.
+ */
+lldpctl_atom_t *lldpctl_atom_set_int(lldpctl_atom_t *atom, lldpctl_key_t key,
+ long int value);
+
+/**
+ * @defgroup liblldpctl_atom_iter Iterating over atoms
+ *
+ * Iterate over atoms (lists).
+ *
+ * @{
+ */
+/**
+ * Iterator over an iterable atom (a list of ports, a list of VLAN, ...). When
+ * an atom is a list, it can be iterated over to extract the appropriate values.
+ *
+ * @see lldpctl_atom_iter(), lldpctl_atom_iter_next(), lldpctl_atom_iter_value()
+ */
+typedef struct lldpctl_atom_iter_t lldpctl_atom_iter_t;
+
+/**
+ * Return an iterator over a given atom.
+ *
+ * If an atom is iterable (if it is a list, like a list of ports, a list of
+ * VLAN, a list of neighbors), it is possible to iterate over it. First use this
+ * function to get an iterator then use @c lldpctl_atom_iter_next() to get the
+ * next item and @c lldpctl_atom_iter_value() to the actuel item.
+ *
+ * @param atom The atom we want to create an iterator from.
+ * @return The iterator or @c NULL if an error happened or if the atom is empty
+ * (check with @c lldpctl_last_error()).
+ *
+ * As a convenience, if the provided atom is @c NULL, this function will return
+ * @c NULL and no error will be raised.
+ */
+lldpctl_atom_iter_t *lldpctl_atom_iter(lldpctl_atom_t *atom);
+
+/**
+ * Return the next element of an iterator.
+ *
+ * @param atom The atom we are currently iterating.
+ * @param iter The iterator we want the next element from.
+ * @return An iterator starting on the next element or @c NULL if we have no
+ * more elements
+ *
+ * @see lldpctl_atom_iter(), lldpctl_atom_iter_value().
+ *
+ * As a convenience, if the provided atom is @c NULL, this function will return
+ * @c NULL and no error will be raised.
+ */
+lldpctl_atom_iter_t *lldpctl_atom_iter_next(lldpctl_atom_t *atom,
+ lldpctl_atom_iter_t *iter);
+
+/**
+ * Return the value of an iterator.
+ *
+ * @param atom The atom we are currently iterating.
+ * @param iter The iterator we want the next element from.
+ * @return The atom currently associated with the iterator.
+ *
+ * @see lldpctl_atom_iter(), lldpctl_atom_iter_next().
+ */
+lldpctl_atom_t *lldpctl_atom_iter_value(lldpctl_atom_t *atom,
+ lldpctl_atom_iter_t *iter);
+
+/**
+ * Convenience macro to iter over every value of an iterable object.
+ *
+ * @param atom The atom you want to iterate on.
+ * @param value Atom name that will be used to contain each value.
+ *
+ * This macro behaves as a for loop. Moreover, at the end of each iteration, the
+ * reference count of the provided value is decremented. If you need to use it
+ * outside of the loop, you need to increment it.
+ */
+#define lldpctl_atom_foreach(atom, value) \
+ for (lldpctl_atom_iter_t *iter##_LINE_ = lldpctl_atom_iter(atom); \
+ iter##_LINE_ && (value = lldpctl_atom_iter_value(atom, iter##_LINE_)); \
+ iter##_LINE_ = lldpctl_atom_iter_next(atom, iter##_LINE_), \
+ lldpctl_atom_dec_ref(value))
+
+/**
+ * Create a new value for an iterable element.
+ *
+ * The value is meant to be appended using @c lldpctl_atom_set(). Currently,
+ * there is no way to delete an element from a list. It is also not advisable to
+ * use getters on a newly created object until it is fully initialized. If its
+ * internal representation is using a buffer, it may not be initialized until
+ * the first set.
+ *
+ * @param atom The atom we want to create a new element for.
+ * @return The new element.
+ */
+lldpctl_atom_t *lldpctl_atom_create(lldpctl_atom_t *atom);
+/**@}*/
+/**@}*/
+
+#ifdef __cplusplus
+}
+#endif
+
+/**@}*/
+
+#endif
diff --git a/src/lib/lldpctl.map b/src/lib/lldpctl.map
new file mode 100644
index 0000000..c602b64
--- /dev/null
+++ b/src/lib/lldpctl.map
@@ -0,0 +1,53 @@
+LIBLLDPCTL_4.9 {
+ global:
+ lldpctl_watch_callback2;
+};
+
+LIBLLDPCTL_4.8 {
+ global:
+ lldpctl_get_default_port;
+};
+
+LIBLLDPCTL_4.7 {
+ global:
+ lldpctl_get_local_chassis;
+};
+
+LIBLLDPCTL_4.6 {
+ global:
+ lldpctl_atom_create;
+ lldpctl_atom_dec_ref;
+ lldpctl_atom_get;
+ lldpctl_atom_get_buffer;
+ lldpctl_atom_get_connection;
+ lldpctl_atom_get_int;
+ lldpctl_atom_get_str;
+ lldpctl_atom_inc_ref;
+ lldpctl_atom_iter;
+ lldpctl_atom_iter_next;
+ lldpctl_atom_iter_value;
+ lldpctl_atom_set;
+ lldpctl_atom_set_buffer;
+ lldpctl_atom_set_int;
+ lldpctl_atom_set_str;
+ lldpctl_get_configuration;
+ lldpctl_get_default_transport;
+ lldpctl_get_interfaces;
+ lldpctl_get_port;
+ lldpctl_key_get_map;
+ lldpctl_last_error;
+ lldpctl_log_callback;
+ lldpctl_log_level;
+ lldpctl_new;
+ lldpctl_new_name;
+ lldpctl_process_conn_buffer;
+ lldpctl_recv;
+ lldpctl_release;
+ lldpctl_send;
+ lldpctl_strerror;
+ lldpctl_watch;
+ lldpctl_watch_callback;
+
+ local:
+ *;
+};
diff --git a/src/lib/lldpctl.pc.in b/src/lib/lldpctl.pc.in
new file mode 100644
index 0000000..65ce1ef
--- /dev/null
+++ b/src/lib/lldpctl.pc.in
@@ -0,0 +1,6 @@
+Name: lldpctl
+Description: Library to interface with lldpd, a 802.1AB daemon
+Version: @VERSION@
+URL: @PACKAGE_URL@
+Libs: -L@libdir@ -llldpctl
+Cflags: -I@includedir@
diff --git a/src/lldp-const.h b/src/lldp-const.h
new file mode 100644
index 0000000..454424d
--- /dev/null
+++ b/src/lldp-const.h
@@ -0,0 +1,352 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/*
+ * Copyright (c) 2008 Vincent Bernat <bernat@luffy.cx>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef _LLDP_H
+#define _LLDP_H
+
+/* Definitions prefixed by `LLDP_` are constants from LLDP
+ * specifications. Definitions prefixed by `LLDPD_` are custom
+ * constants that are useful in the context of lldpd and its clients.
+ */
+
+#define LLDP_TLV_ORG 127
+#define LLDP_TLV_ORG_OUI_LEN 3
+#define LLDP_TLV_ORG_OUI_INFO_MAXLEN 507
+
+/* Chassis ID subtype */
+#define LLDP_CHASSISID_SUBTYPE_CHASSIS 1
+#define LLDP_CHASSISID_SUBTYPE_IFALIAS 2
+#define LLDP_CHASSISID_SUBTYPE_PORT 3
+#define LLDP_CHASSISID_SUBTYPE_LLADDR 4
+#define LLDP_CHASSISID_SUBTYPE_ADDR 5
+#define LLDP_CHASSISID_SUBTYPE_IFNAME 6
+#define LLDP_CHASSISID_SUBTYPE_LOCAL 7
+
+/* Port ID subtype */
+#define LLDP_PORTID_SUBTYPE_UNKNOWN 0
+#define LLDP_PORTID_SUBTYPE_IFALIAS 1
+#define LLDP_PORTID_SUBTYPE_PORT 2
+#define LLDP_PORTID_SUBTYPE_LLADDR 3
+#define LLDP_PORTID_SUBTYPE_ADDR 4
+#define LLDP_PORTID_SUBTYPE_IFNAME 5
+#define LLDP_PORTID_SUBTYPE_AGENTCID 6
+#define LLDP_PORTID_SUBTYPE_LOCAL 7
+#define LLDP_PORTID_SUBTYPE_MAX LLDP_PORTID_SUBTYPE_LOCAL
+
+/* Operational MAU Type field. See:
+ * https://www.iana.org/assignments/ianamau-mib/ianamau-mib */
+#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_10BASEFLFD 13
+#define LLDP_DOT3_MAU_100BASET4 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_100BASET2FD 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
+#define LLDP_DOT3_MAU_10GIGBASECX4 41
+#define LLDP_DOT3_MAU_2BASETL 42
+#define LLDP_DOT3_MAU_10PASSTS 43
+#define LLDP_DOT3_MAU_100BASEBX10D 44
+#define LLDP_DOT3_MAU_100BASEBX10U 45
+#define LLDP_DOT3_MAU_100BASELX10 46
+#define LLDP_DOT3_MAU_1000BASEBX10D 47
+#define LLDP_DOT3_MAU_1000BASEBX10U 48
+#define LLDP_DOT3_MAU_1000BASELX10 49
+#define LLDP_DOT3_MAU_1000BASEPX10D 50
+#define LLDP_DOT3_MAU_1000BASEPX10U 51
+#define LLDP_DOT3_MAU_1000BASEPX20D 52
+#define LLDP_DOT3_MAU_1000BASEPX20U 53
+#define LLDP_DOT3_MAU_10GBASET 54
+#define LLDP_DOT3_MAU_10GBASELRM 55
+#define LLDP_DOT3_MAU_1000BASEKX 56
+#define LLDP_DOT3_MAU_10GBASEKX4 57
+#define LLDP_DOT3_MAU_10GBASEKR 58
+#define LLDP_DOT3_MAU_10G1GBASEPRXD1 59
+#define LLDP_DOT3_MAU_10G1GBASEPRXD2 60
+#define LLDP_DOT3_MAU_10G1GBASEPRXD3 61
+#define LLDP_DOT3_MAU_10G1GBASEPRXU1 62
+#define LLDP_DOT3_MAU_10G1GBASEPRXU2 63
+#define LLDP_DOT3_MAU_10G1GBASEPRXU3 64
+#define LLDP_DOT3_MAU_10GBASEPRD1 65
+#define LLDP_DOT3_MAU_10GBASEPRD2 66
+#define LLDP_DOT3_MAU_10GBASEPRD3 67
+#define LLDP_DOT3_MAU_10GBASEPRU1 68
+#define LLDP_DOT3_MAU_10GBASEPRU3 69
+#define LLDP_DOT3_MAU_40GBASEKR4 70
+#define LLDP_DOT3_MAU_40GBASECR4 71
+#define LLDP_DOT3_MAU_40GBASESR4 72
+#define LLDP_DOT3_MAU_40GBASEFR 73
+#define LLDP_DOT3_MAU_40GBASELR4 74
+#define LLDP_DOT3_MAU_100GBASECR10 75
+#define LLDP_DOT3_MAU_100GBASESR10 76
+#define LLDP_DOT3_MAU_100GBASELR4 77
+#define LLDP_DOT3_MAU_100GBASEER4 78
+#define LLDP_DOT3_MAU_1000BASET1 79
+#define LLDP_DOT3_MAU_1000BASEPX30D 80
+#define LLDP_DOT3_MAU_1000BASEPX30U 81
+#define LLDP_DOT3_MAU_1000BASEPX40D 82
+#define LLDP_DOT3_MAU_1000BASEPX40U 83
+#define LLDP_DOT3_MAU_10G1GBASEPRXD4 84
+#define LLDP_DOT3_MAU_10G1GBASEPRXU4 85
+#define LLDP_DOT3_MAU_10GBASEPRD4 86
+#define LLDP_DOT3_MAU_10GBASEPRU4 87
+#define LLDP_DOT3_MAU_25GBASECR 88
+#define LLDP_DOT3_MAU_25GBASECRS 89
+#define LLDP_DOT3_MAU_25GBASEKR 90
+#define LLDP_DOT3_MAU_25GBASEKRS 91
+#define LLDP_DOT3_MAU_25GBASER 92
+#define LLDP_DOT3_MAU_25GBASESR 93
+#define LLDP_DOT3_MAU_25GBASET 94
+#define LLDP_DOT3_MAU_40GBASEER4 95
+#define LLDP_DOT3_MAU_40GBASER 96
+#define LLDP_DOT3_MAU_40GBASET 97
+#define LLDP_DOT3_MAU_100GBASECR4 98
+#define LLDP_DOT3_MAU_100GBASEKR4 99
+#define LLDP_DOT3_MAU_100GBASEKP4 100
+#define LLDP_DOT3_MAU_100GBASER 101
+#define LLDP_DOT3_MAU_100GBASESR4 102
+#define LLDP_DOT3_MAU_2P5GIGT 103
+#define LLDP_DOT3_MAU_5GIGT 104
+#define LLDP_DOT3_MAU_100BASET1 105
+#define LLDP_DOT3_MAU_1000BASERHA 106
+#define LLDP_DOT3_MAU_1000BASERHB 107
+#define LLDP_DOT3_MAU_1000BASERHC 108
+#define LLDP_DOT3_MAU_2P5GBASEKX 109
+#define LLDP_DOT3_MAU_2P5GBASEX 110
+#define LLDP_DOT3_MAU_5GBASEKR 111
+#define LLDP_DOT3_MAU_5GBASER 112
+#define LLDP_DOT3_MAU_10GPASSXR 113
+#define LLDP_DOT3_MAU_25GBASELR 114
+#define LLDP_DOT3_MAU_25GBASEER 115
+#define LLDP_DOT3_MAU_50GBASER 116
+#define LLDP_DOT3_MAU_50GBASECR 117
+#define LLDP_DOT3_MAU_50GBASEKR 118
+#define LLDP_DOT3_MAU_50GBASESR 119
+#define LLDP_DOT3_MAU_50GBASEFR 120
+#define LLDP_DOT3_MAU_50GBASELR 121
+#define LLDP_DOT3_MAU_50GBASEER 122
+#define LLDP_DOT3_MAU_100GBASECR2 123
+#define LLDP_DOT3_MAU_100GBASEKR2 124
+#define LLDP_DOT3_MAU_100GBASESR2 125
+#define LLDP_DOT3_MAU_100GBASEDR 126
+#define LLDP_DOT3_MAU_200GBASER 127
+#define LLDP_DOT3_MAU_200GBASEDR4 128
+#define LLDP_DOT3_MAU_200GBASEFR4 129
+#define LLDP_DOT3_MAU_200GBASELR4 130
+#define LLDP_DOT3_MAU_200GBASECR4 131
+#define LLDP_DOT3_MAU_200GBASEKR4 132
+#define LLDP_DOT3_MAU_200GBASESR4 133
+#define LLDP_DOT3_MAU_200GBASEER4 134
+#define LLDP_DOT3_MAU_400GBASER 135
+#define LLDP_DOT3_MAU_400GBASESR16 136
+#define LLDP_DOT3_MAU_400GBASEDR4 137
+#define LLDP_DOT3_MAU_400GBASEFR8 138
+#define LLDP_DOT3_MAU_400GBASELR8 139
+#define LLDP_DOT3_MAU_400GBASEER8 140
+#define LLDP_DOT3_MAU_10BASET1L 141
+#define LLDP_DOT3_MAU_10BASET1SHD 142
+#define LLDP_DOT3_MAU_10BASET1SMD 143
+#define LLDP_DOT3_MAU_10BASET1SFD 144
+
+/* Dot3 Power Devicetype */
+#define LLDP_DOT3_POWER_PSE 1
+#define LLDP_DOT3_POWER_PD 2
+
+/* Dot3 Power Pairs (RFC 3621) */
+#define LLDP_DOT3_POWERPAIRS_SIGNAL 1
+#define LLDP_DOT3_POWERPAIRS_SPARE 2
+
+/* Dot3 Power type (for 802.3at) */
+#define LLDP_DOT3_POWER_8023AT_OFF 0
+#define LLDP_DOT3_POWER_8023AT_TYPE1 1
+#define LLDP_DOT3_POWER_8023AT_TYPE2 2
+
+/* 802.3bt additions */
+#define LLDP_DOT3_POWER_8023BT_OFF 0
+#define LLDP_DOT3_POWER_8023BT_TYPE3 1
+#define LLDP_DOT3_POWER_8023BT_TYPE4 2
+
+/* Dot3 power source */
+#define LLDP_DOT3_POWER_SOURCE_UNKNOWN 0
+#define LLDP_DOT3_POWER_SOURCE_PRIMARY 1
+#define LLDP_DOT3_POWER_SOURCE_PSE 1
+#define LLDP_DOT3_POWER_SOURCE_BACKUP 2
+#define LLDP_DOT3_POWER_SOURCE_LOCAL 2
+#define LLDP_DOT3_POWER_SOURCE_BOTH 3
+
+/* Dot3 power priority */
+#define LLDP_DOT3_POWER_PRIO_UNKNOWN 0
+#define LLDP_DOT3_POWER_PRIO_CRITICAL 1
+#define LLDP_DOT3_POWER_PRIO_HIGH 2
+#define LLDP_DOT3_POWER_PRIO_LOW 3
+
+/* PMD Auto-Negotiation Advertised Capability field, from RFC 3636 (see
+ * IANAifMauAutoNegCapBits). Unfortunately, we are limited to two bytes, so
+ * higher speed capabilities will map to "other". */
+#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
+
+/* Capabilities */
+#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
+
+#define LLDP_PPVID_CAP_SUPPORTED (1 << 1)
+#define LLDP_PPVID_CAP_ENABLED (1 << 2)
+
+/* see http://www.iana.org/assignments/address-family-numbers */
+#define LLDP_MGMT_ADDR_NONE 0
+#define LLDP_MGMT_ADDR_IP4 1
+#define LLDP_MGMT_ADDR_IP6 2
+
+#define LLDP_MGMT_IFACE_UNKNOWN 1
+#define LLDP_MGMT_IFACE_IFINDEX 2
+#define LLDP_MGMT_IFACE_SYSPORT 3
+
+#define LLDP_MED_CLASS_I 1
+#define LLDP_MED_CLASS_II 2
+#define LLDP_MED_CLASS_III 3
+#define LLDP_MED_NETWORK_DEVICE 4
+
+/* LLDP MED application ttpes */
+#define LLDP_MED_APPTYPE_UNDEFINED 0
+#define LLDP_MED_APPTYPE_VOICE 1
+#define LLDP_MED_APPTYPE_VOICESIGNAL 2
+#define LLDP_MED_APPTYPE_GUESTVOICE 3
+#define LLDP_MED_APPTYPE_GUESTVOICESIGNAL 4
+#define LLDP_MED_APPTYPE_SOFTPHONEVOICE 5
+#define LLDP_MED_APPTYPE_VIDEOCONFERENCE 6
+#define LLDP_MED_APPTYPE_VIDEOSTREAM 7
+#define LLDP_MED_APPTYPE_VIDEOSIGNAL 8
+#define LLDP_MED_APPTYPE_LAST LLDP_MED_APPTYPE_VIDEOSIGNAL
+
+/* LLDP MED location formats */
+#define LLDP_MED_LOCFORMAT_COORD 1
+#define LLDP_MED_LOCFORMAT_CIVIC 2
+#define LLDP_MED_LOCFORMAT_ELIN 3
+#define LLDP_MED_LOCFORMAT_LAST LLDP_MED_LOCFORMAT_ELIN
+
+#define LLDP_MED_LOCATION_GEOID_WGS84 1
+#define LLDP_MED_LOCATION_GEOID_NAD83 2
+#define LLDP_MED_LOCATION_GEOID_NAD83_MLLW 3
+
+#define LLDP_MED_LOCATION_ALTITUDE_UNIT_METER 1
+#define LLDP_MED_LOCATION_ALTITUDE_UNIT_FLOOR 2
+
+/* LLDP MED power related constants */
+#define LLDP_MED_POW_TYPE_PSE 1
+#define LLDP_MED_POW_TYPE_PD 2
+#define LLDP_MED_POW_TYPE_RESERVED 3
+
+#define LLDP_MED_POW_SOURCE_UNKNOWN 1
+#define LLDP_MED_POW_SOURCE_PRIMARY 2
+#define LLDP_MED_POW_SOURCE_BACKUP 3
+#define LLDP_MED_POW_SOURCE_RESERVED 4
+#define LLDP_MED_POW_SOURCE_PSE 5
+#define LLDP_MED_POW_SOURCE_LOCAL 6
+#define LLDP_MED_POW_SOURCE_BOTH 7
+
+#define LLDP_MED_POW_PRIO_UNKNOWN 0
+#define LLDP_MED_POW_PRIO_CRITICAL 1
+#define LLDP_MED_POW_PRIO_HIGH 2
+#define LLDP_MED_POW_PRIO_LOW 3
+
+/* LLDP MED capabilities */
+#define LLDP_MED_CAP_CAP 0x01
+#define LLDP_MED_CAP_POLICY 0x02
+#define LLDP_MED_CAP_LOCATION 0x04
+#define LLDP_MED_CAP_MDI_PSE 0x08
+#define LLDP_MED_CAP_MDI_PD 0x10
+#define LLDP_MED_CAP_IV 0x20
+
+/* Protocol constants for multi-protocol lldpd */
+#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
+#define LLDPD_MODE_FDP 6
+#define LLDPD_MODE_MAX LLDPD_MODE_FDP
+
+/* Bond slave src mac type constants */
+#define LLDP_BOND_SLAVE_SRC_MAC_TYPE_UNKNOWN 0
+#define LLDP_BOND_SLAVE_SRC_MAC_TYPE_REAL 1
+#define LLDP_BOND_SLAVE_SRC_MAC_TYPE_ZERO 2
+#define LLDP_BOND_SLAVE_SRC_MAC_TYPE_FIXED 3
+#define LLDP_BOND_SLAVE_SRC_MAC_TYPE_LOCALLY_ADMINISTERED 4
+#define LLDP_BOND_SLAVE_SRC_MAC_TYPE_MAX \
+ LLDP_BOND_SLAVE_SRC_MAC_TYPE_LOCALLY_ADMINISTERED
+
+/* Agent types */
+#define LLDP_AGENT_TYPE_UNKNOWN 0
+#define LLDP_AGENT_TYPE_NEAREST_BRIDGE 1
+#define LLDP_AGENT_TYPE_NEAREST_NONTPMR_BRIDGE 2
+#define LLDP_AGENT_TYPE_NEAREST_CUSTOMER_BRIDGE 3
+#define LLDP_AGENT_TYPE_MAX LLDP_AGENT_TYPE_NEAREST_CUSTOMER_BRIDGE
+
+#endif /* _LLDP_H */
diff --git a/src/lldpd-structs.c b/src/lldpd-structs.c
new file mode 100644
index 0000000..6b42713
--- /dev/null
+++ b/src/lldpd-structs.c
@@ -0,0 +1,232 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/*
+ * Copyright (c) 2008 Vincent Bernat <bernat@luffy.cx>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <time.h>
+#include "lldpd-structs.h"
+#include "log.h"
+
+void
+lldpd_chassis_mgmt_cleanup(struct lldpd_chassis *chassis)
+{
+ struct lldpd_mgmt *mgmt, *mgmt_next;
+
+ log_debug("alloc", "cleanup management addresses for chassis %s",
+ chassis->c_name ? chassis->c_name : "(unknown)");
+
+ for (mgmt = TAILQ_FIRST(&chassis->c_mgmt); mgmt != NULL; mgmt = mgmt_next) {
+ mgmt_next = TAILQ_NEXT(mgmt, m_entries);
+ free(mgmt);
+ }
+ TAILQ_INIT(&chassis->c_mgmt);
+}
+
+void
+lldpd_chassis_cleanup(struct lldpd_chassis *chassis, int all)
+{
+ lldpd_chassis_mgmt_cleanup(chassis);
+ log_debug("alloc", "cleanup chassis %s",
+ chassis->c_name ? chassis->c_name : "(unknown)");
+#ifdef ENABLE_LLDPMED
+ free(chassis->c_med_hw);
+ free(chassis->c_med_sw);
+ free(chassis->c_med_fw);
+ free(chassis->c_med_sn);
+ free(chassis->c_med_manuf);
+ free(chassis->c_med_model);
+ free(chassis->c_med_asset);
+#endif
+ free(chassis->c_id);
+ free(chassis->c_name);
+ free(chassis->c_descr);
+ if (all) free(chassis);
+}
+
+#ifdef ENABLE_DOT1
+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);
+ free(vlan);
+ }
+ TAILQ_INIT(&port->p_vlans);
+ port->p_pvid = 0;
+}
+
+void
+lldpd_ppvid_cleanup(struct lldpd_port *port)
+{
+ struct lldpd_ppvid *ppvid, *ppvid_next;
+ for (ppvid = TAILQ_FIRST(&port->p_ppvids); ppvid != NULL; ppvid = ppvid_next) {
+ ppvid_next = TAILQ_NEXT(ppvid, p_entries);
+ free(ppvid);
+ }
+ TAILQ_INIT(&port->p_ppvids);
+}
+
+void
+lldpd_pi_cleanup(struct lldpd_port *port)
+{
+ struct lldpd_pi *pi, *pi_next;
+ for (pi = TAILQ_FIRST(&port->p_pids); pi != NULL; pi = pi_next) {
+ free(pi->p_pi);
+ pi_next = TAILQ_NEXT(pi, p_entries);
+ free(pi);
+ }
+ TAILQ_INIT(&port->p_pids);
+}
+#endif
+
+#ifdef ENABLE_CUSTOM
+void
+lldpd_custom_tlv_add(struct lldpd_port *port, struct lldpd_custom *curr)
+{
+ struct lldpd_custom *custom;
+
+ if ((custom = malloc(sizeof(struct lldpd_custom)))) {
+ memcpy(custom, curr, sizeof(struct lldpd_custom));
+ if ((custom->oui_info = malloc(custom->oui_info_len))) {
+ memcpy(custom->oui_info, curr->oui_info, custom->oui_info_len);
+ TAILQ_INSERT_TAIL(&port->p_custom_list, custom, next);
+ } else {
+ free(custom);
+ log_warn("rpc",
+ "could not allocate memory for custom TLV info");
+ }
+ }
+}
+
+void
+lldpd_custom_tlv_cleanup(struct lldpd_port *port, struct lldpd_custom *curr)
+{
+ struct lldpd_custom *custom, *custom_next;
+ for (custom = TAILQ_FIRST(&port->p_custom_list); custom != NULL;
+ custom = custom_next) {
+ custom_next = TAILQ_NEXT(custom, next);
+ if (!memcmp(curr->oui, custom->oui, sizeof(curr->oui)) &&
+ curr->subtype == custom->subtype) {
+ TAILQ_REMOVE(&port->p_custom_list, custom, next);
+ free(custom->oui_info);
+ free(custom);
+ }
+ }
+}
+
+void
+lldpd_custom_list_cleanup(struct lldpd_port *port)
+{
+ struct lldpd_custom *custom, *custom_next;
+ for (custom = TAILQ_FIRST(&port->p_custom_list); custom != NULL;
+ custom = custom_next) {
+ custom_next = TAILQ_NEXT(custom, next);
+ free(custom->oui_info);
+ free(custom);
+ }
+ TAILQ_INIT(&port->p_custom_list);
+}
+#endif
+
+/* Cleanup a remote port. The before last argument, `expire` is a function that
+ * should be called when a remote port is removed. If the last argument is 1,
+ * all remote ports are removed.
+ */
+void
+lldpd_remote_cleanup(struct lldpd_hardware *hardware,
+ void (*expire)(struct lldpd_hardware *, struct lldpd_port *), int all)
+{
+ struct lldpd_port *port, *port_next;
+ int del;
+ time_t now = time(NULL);
+
+ log_debug("alloc", "cleanup remote port on %s", hardware->h_ifname);
+ for (port = TAILQ_FIRST(&hardware->h_rports); port != NULL; port = port_next) {
+ port_next = TAILQ_NEXT(port, p_entries);
+ del = all;
+ if (!all && expire && (now >= port->p_lastupdate + port->p_ttl)) {
+ if (port->p_ttl > 0) hardware->h_ageout_cnt++;
+ del = 1;
+ }
+ if (del) {
+ if (expire) expire(hardware, port);
+ /* This TAILQ_REMOVE is dangerous. It should not be
+ * called while in liblldpctl because we don't have a
+ * real list. It is only needed to be called when we
+ * don't delete the entire list. */
+ if (!all) TAILQ_REMOVE(&hardware->h_rports, port, p_entries);
+
+ hardware->h_delete_cnt++;
+ /* Register last removal to be able to report
+ * lldpStatsRemTablesLastChangeTime */
+ hardware->h_lport.p_lastremove = time(NULL);
+ lldpd_port_cleanup(port, 1);
+ free(port);
+ }
+ }
+ if (all) TAILQ_INIT(&hardware->h_rports);
+}
+
+/* If `all' is true, clear all information, including information that
+ are not refreshed periodically. Port should be freed manually. */
+void
+lldpd_port_cleanup(struct lldpd_port *port, int all)
+{
+#ifdef ENABLE_LLDPMED
+ int i;
+ if (all)
+ for (i = 0; i < LLDP_MED_LOCFORMAT_LAST; i++)
+ free(port->p_med_location[i].data);
+#endif
+#ifdef ENABLE_DOT1
+ lldpd_vlan_cleanup(port);
+ lldpd_ppvid_cleanup(port);
+ lldpd_pi_cleanup(port);
+#endif
+ /* will set these to NULL so we don't free wrong memory */
+
+ if (all) {
+ free(port->p_id);
+ port->p_id = NULL;
+ free(port->p_descr);
+ port->p_descr = NULL;
+ free(port->p_lastframe);
+ if (port->p_chassis) { /* chassis may not have been attributed, yet */
+ port->p_chassis->c_refcount--;
+ port->p_chassis = NULL;
+ }
+#ifdef ENABLE_CUSTOM
+ lldpd_custom_list_cleanup(port);
+#endif
+ }
+}
+
+void
+lldpd_config_cleanup(struct lldpd_config *config)
+{
+ log_debug("alloc", "general configuration cleanup");
+ free(config->c_mgmt_pattern);
+ free(config->c_cid_pattern);
+ free(config->c_cid_string);
+ free(config->c_iface_pattern);
+ free(config->c_perm_ifaces);
+ free(config->c_hostname);
+ free(config->c_platform);
+ free(config->c_description);
+}
diff --git a/src/lldpd-structs.h b/src/lldpd-structs.h
new file mode 100644
index 0000000..8d19282
--- /dev/null
+++ b/src/lldpd-structs.h
@@ -0,0 +1,568 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/*
+ * Copyright (c) 2008 Vincent Bernat <bernat@luffy.cx>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef _LLDPD_STRUCTS_H
+#define _LLDPD_STRUCTS_H
+
+#if HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <sys/types.h>
+#include <sys/socket.h>
+
+/* This is not very convenient, but we need net/if.h for IFNAMSIZ and others but
+ * we may also need linux/if.h in some modules. And they conflict each others.
+ */
+#ifdef HOST_OS_LINUX
+# include <linux/if.h>
+#else
+# include <net/if.h>
+#endif
+
+#include <netinet/in.h>
+#include <netinet/if_ether.h>
+#include <sys/queue.h>
+
+#include "compat/compat.h"
+#include "marshal.h"
+#include "lldp-const.h"
+
+#ifdef ENABLE_DOT1
+struct lldpd_ppvid {
+ TAILQ_ENTRY(lldpd_ppvid) p_entries;
+ u_int8_t p_cap_status;
+ u_int16_t p_ppvid;
+};
+MARSHAL_BEGIN(lldpd_ppvid)
+MARSHAL_TQE(lldpd_ppvid, p_entries)
+MARSHAL_END(lldpd_ppvid);
+
+struct lldpd_vlan {
+ TAILQ_ENTRY(lldpd_vlan) v_entries;
+ char *v_name;
+ u_int16_t v_vid;
+};
+MARSHAL_BEGIN(lldpd_vlan)
+MARSHAL_TQE(lldpd_vlan, v_entries)
+MARSHAL_STR(lldpd_vlan, v_name)
+MARSHAL_END(lldpd_vlan);
+
+struct lldpd_pi {
+ TAILQ_ENTRY(lldpd_pi) p_entries;
+ char *p_pi;
+ int p_pi_len;
+};
+MARSHAL_BEGIN(lldpd_pi)
+MARSHAL_TQE(lldpd_pi, p_entries)
+MARSHAL_FSTR(lldpd_pi, p_pi, p_pi_len)
+MARSHAL_END(lldpd_pi);
+#endif
+
+#ifdef ENABLE_LLDPMED
+struct lldpd_med_policy {
+ u_int8_t index; /* Not used. */
+ u_int8_t type;
+ u_int8_t unknown;
+ u_int8_t tagged;
+ u_int16_t vid;
+ u_int8_t priority;
+ u_int8_t dscp;
+};
+MARSHAL(lldpd_med_policy);
+
+struct lldpd_med_loc {
+ u_int8_t index; /* Not used. */
+ u_int8_t format;
+ char *data;
+ int data_len;
+};
+MARSHAL_BEGIN(lldpd_med_loc)
+MARSHAL_FSTR(lldpd_med_loc, data, data_len)
+MARSHAL_END(lldpd_med_loc);
+
+struct lldpd_med_power {
+ u_int8_t devicetype; /* PD or PSE */
+ u_int8_t source;
+ u_int8_t priority;
+ u_int16_t val;
+};
+MARSHAL(lldpd_med_power);
+#endif
+
+#ifdef ENABLE_DOT3
+struct lldpd_dot3_macphy {
+ u_int8_t autoneg_support;
+ u_int8_t autoneg_enabled;
+ u_int16_t autoneg_advertised;
+ u_int16_t mau_type;
+};
+
+struct lldpd_dot3_power {
+ u_int8_t devicetype;
+ u_int8_t supported;
+ u_int8_t enabled;
+ u_int8_t paircontrol;
+ u_int8_t pairs;
+ u_int8_t class;
+ u_int8_t powertype; /* If set to LLDP_DOT3_POWER_8023AT_OFF,
+ following fields have no meaning */
+ u_int8_t source;
+ u_int8_t priority;
+ u_int16_t requested;
+ u_int16_t allocated;
+
+ /* For 802.3BT */
+ u_int8_t pd_4pid;
+ u_int16_t requested_a;
+ u_int16_t requested_b;
+ u_int16_t allocated_a;
+ u_int16_t allocated_b;
+ u_int16_t pse_status;
+ u_int8_t pd_status;
+ u_int8_t pse_pairs_ext;
+ u_int8_t class_a;
+ u_int8_t class_b;
+ u_int8_t class_ext;
+ u_int8_t type_ext;
+ u_int8_t pd_load;
+ u_int16_t pse_max;
+};
+MARSHAL(lldpd_dot3_power);
+#endif
+
+#if defined ENABLE_CDP || defined ENABLE_FDP
+struct cdpv2_power {
+ u_int16_t request_id;
+ u_int16_t management_id;
+};
+#endif
+
+enum { LLDPD_AF_UNSPEC = 0, LLDPD_AF_IPV4, LLDPD_AF_IPV6, LLDPD_AF_LAST };
+
+#define LLDPD_MGMT_MAXADDRSIZE 16 /* sizeof(struct in6_addr) */
+union lldpd_address {
+ struct in_addr inet;
+ struct in6_addr inet6;
+ u_int8_t octets[LLDPD_MGMT_MAXADDRSIZE]; /* network byte order! */
+};
+struct lldpd_mgmt {
+ TAILQ_ENTRY(lldpd_mgmt) m_entries;
+ int m_family;
+ union lldpd_address m_addr;
+ size_t m_addrsize;
+ u_int32_t m_iface;
+};
+MARSHAL_BEGIN(lldpd_mgmt)
+MARSHAL_TQE(lldpd_mgmt, m_entries)
+MARSHAL_END(lldpd_mgmt);
+
+struct lldpd_chassis {
+ TAILQ_ENTRY(lldpd_chassis) c_entries;
+ u_int16_t c_refcount; /* Reference count by ports */
+ u_int16_t c_index; /* Monotonic index */
+ u_int8_t c_protocol; /* Protocol used to get this 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;
+
+ TAILQ_HEAD(, lldpd_mgmt) c_mgmt;
+
+#ifdef ENABLE_LLDPMED
+ u_int16_t c_med_cap_available;
+ u_int8_t c_med_type;
+ char *c_med_hw;
+ char *c_med_fw;
+ char *c_med_sw;
+ char *c_med_sn;
+ char *c_med_manuf;
+ char *c_med_model;
+ char *c_med_asset;
+#endif
+};
+/* WARNING: any change to this structure should also be reflected into
+ `lldpd_copy_chassis()` which is not using marshaling. */
+MARSHAL_BEGIN(lldpd_chassis)
+MARSHAL_IGNORE(lldpd_chassis, c_entries.tqe_next)
+MARSHAL_IGNORE(lldpd_chassis, c_entries.tqe_prev)
+MARSHAL_FSTR(lldpd_chassis, c_id, c_id_len)
+MARSHAL_STR(lldpd_chassis, c_name)
+MARSHAL_STR(lldpd_chassis, c_descr)
+MARSHAL_SUBTQ(lldpd_chassis, lldpd_mgmt, c_mgmt)
+#ifdef ENABLE_LLDPMED
+MARSHAL_STR(lldpd_chassis, c_med_hw)
+MARSHAL_STR(lldpd_chassis, c_med_fw)
+MARSHAL_STR(lldpd_chassis, c_med_sw)
+MARSHAL_STR(lldpd_chassis, c_med_sn)
+MARSHAL_STR(lldpd_chassis, c_med_manuf)
+MARSHAL_STR(lldpd_chassis, c_med_model)
+MARSHAL_STR(lldpd_chassis, c_med_asset)
+#endif
+MARSHAL_END(lldpd_chassis);
+
+#ifdef ENABLE_CUSTOM
+
+# define CUSTOM_TLV_ADD 1
+# define CUSTOM_TLV_REPLACE 2
+# define CUSTOM_TLV_REMOVE 3
+
+/* Custom TLV struct as defined on page 35 of IEEE 802.1AB-2005 */
+struct lldpd_custom {
+ TAILQ_ENTRY(lldpd_custom) next; /* Pointer to next custom TLV */
+
+ /* Organizationally Unique Identifier */
+ u_int8_t oui[LLDP_TLV_ORG_OUI_LEN];
+ /* Organizationally Defined Subtype */
+ u_int8_t subtype;
+ /* Organizationally Defined Information String */
+ u_int8_t *oui_info;
+ /* Organizationally Defined Information String length */
+ int oui_info_len;
+};
+MARSHAL_BEGIN(lldpd_custom)
+MARSHAL_TQE(lldpd_custom, next)
+MARSHAL_FSTR(lldpd_custom, oui_info, oui_info_len)
+MARSHAL_END(lldpd_custom);
+#endif
+
+struct lldpd_port {
+ TAILQ_ENTRY(lldpd_port) p_entries;
+ struct lldpd_chassis *p_chassis; /* Attached chassis */
+ time_t p_lastchange; /* Time of last change of values */
+ time_t p_lastupdate; /* Time of last update received */
+ time_t
+ p_lastremove; /* Time of last removal of a remote port. Used for local ports
+ * only Used for deciding lldpStatsRemTablesLastChangeTime */
+ struct lldpd_frame *p_lastframe; /* Frame received during last update */
+ u_int8_t p_protocol; /* Protocol used to get this port */
+ u_int8_t p_hidden_in : 1; /* Considered as hidden for reception */
+ u_int8_t p_hidden_out : 1; /* Considered as hidden for emission */
+ u_int8_t p_disable_rx : 1; /* Should RX be disabled for this port? */
+ u_int8_t p_disable_tx : 1; /* Should TX be disabled for this port? */
+ /* Important: all fields that should be ignored to check if a port has
+ * been changed should be before this mark. */
+#define LLDPD_PORT_START_MARKER (offsetof(struct lldpd_port, _p_hardware_flags))
+ int _p_hardware_flags; /* This is a copy of hardware flags. Do not use it! */
+ u_int8_t p_id_subtype;
+ char *p_id;
+ int p_id_len;
+ char *p_descr;
+ int p_descr_force; /* Description has been forced by user */
+ u_int16_t p_mfs;
+ u_int16_t p_ttl; /* TTL for remote port */
+ int p_vlan_tx_tag;
+ int p_vlan_tx_enabled;
+
+#ifdef ENABLE_DOT3
+ /* Dot3 stuff */
+ u_int32_t p_aggregid;
+ struct lldpd_dot3_macphy p_macphy;
+ struct lldpd_dot3_power p_power;
+#endif
+
+#ifdef ENABLE_LLDPMED
+ u_int16_t p_med_cap_enabled;
+ struct lldpd_med_policy p_med_policy[LLDP_MED_APPTYPE_LAST];
+ struct lldpd_med_loc p_med_location[LLDP_MED_LOCFORMAT_LAST];
+ struct lldpd_med_power p_med_power;
+#endif
+
+#if defined ENABLE_CDP || defined ENABLE_FDP
+ struct cdpv2_power p_cdp_power;
+#endif
+
+#ifdef ENABLE_DOT1
+ u_int16_t p_pvid;
+ TAILQ_HEAD(, lldpd_vlan) p_vlans;
+ TAILQ_HEAD(, lldpd_ppvid) p_ppvids;
+ TAILQ_HEAD(, lldpd_pi) p_pids;
+#endif
+#ifdef ENABLE_CUSTOM
+ TAILQ_HEAD(, lldpd_custom) p_custom_list;
+#endif
+};
+MARSHAL_BEGIN(lldpd_port)
+MARSHAL_TQE(lldpd_port, p_entries)
+MARSHAL_POINTER(lldpd_port, lldpd_chassis, p_chassis)
+MARSHAL_IGNORE(lldpd_port, p_lastframe)
+MARSHAL_FSTR(lldpd_port, p_id, p_id_len)
+MARSHAL_STR(lldpd_port, p_descr)
+#ifdef ENABLE_LLDPMED
+MARSHAL_SUBSTRUCT(lldpd_port, lldpd_med_loc, p_med_location[0])
+MARSHAL_SUBSTRUCT(lldpd_port, lldpd_med_loc, p_med_location[1])
+MARSHAL_SUBSTRUCT(lldpd_port, lldpd_med_loc, p_med_location[2])
+#endif
+#ifdef ENABLE_DOT1
+MARSHAL_SUBTQ(lldpd_port, lldpd_vlan, p_vlans)
+MARSHAL_SUBTQ(lldpd_port, lldpd_ppvid, p_ppvids)
+MARSHAL_SUBTQ(lldpd_port, lldpd_pi, p_pids)
+#endif
+#ifdef ENABLE_CUSTOM
+MARSHAL_SUBTQ(lldpd_port, lldpd_custom, p_custom_list)
+#endif
+MARSHAL_END(lldpd_port);
+
+/* Used to modify some port related settings */
+#define LLDPD_RXTX_UNCHANGED 0
+#define LLDPD_RXTX_TXONLY 1
+#define LLDPD_RXTX_RXONLY 2
+#define LLDPD_RXTX_DISABLED 3
+#define LLDPD_RXTX_BOTH 4
+#define LLDPD_RXTX_FROM_PORT(p) \
+ (((p)->p_disable_rx && (p)->p_disable_tx) ? LLDPD_RXTX_DISABLED : \
+ ((p)->p_disable_rx && !(p)->p_disable_tx) ? LLDPD_RXTX_TXONLY : \
+ (!(p)->p_disable_rx && (p)->p_disable_tx) ? LLDPD_RXTX_RXONLY : \
+ LLDPD_RXTX_BOTH)
+#define LLDPD_RXTX_RXENABLED(v) ((v) == LLDPD_RXTX_RXONLY || (v) == LLDPD_RXTX_BOTH)
+#define LLDPD_RXTX_TXENABLED(v) ((v) == LLDPD_RXTX_TXONLY || (v) == LLDPD_RXTX_BOTH)
+struct lldpd_port_set {
+ char *ifname;
+ char *local_id;
+ char *local_descr;
+ int rxtx;
+ int vlan_tx_tag;
+ int vlan_tx_enabled;
+#ifdef ENABLE_LLDPMED
+ struct lldpd_med_policy *med_policy;
+ struct lldpd_med_loc *med_location;
+ struct lldpd_med_power *med_power;
+#endif
+#ifdef ENABLE_DOT3
+ struct lldpd_dot3_power *dot3_power;
+#endif
+#ifdef ENABLE_CUSTOM
+ struct lldpd_custom *custom;
+ int custom_list_clear;
+ int custom_tlv_op;
+#endif
+};
+MARSHAL_BEGIN(lldpd_port_set)
+MARSHAL_STR(lldpd_port_set, ifname)
+MARSHAL_STR(lldpd_port_set, local_id)
+MARSHAL_STR(lldpd_port_set, local_descr)
+#ifdef ENABLE_LLDPMED
+MARSHAL_POINTER(lldpd_port_set, lldpd_med_policy, med_policy)
+MARSHAL_POINTER(lldpd_port_set, lldpd_med_loc, med_location)
+MARSHAL_POINTER(lldpd_port_set, lldpd_med_power, med_power)
+#endif
+#ifdef ENABLE_DOT3
+MARSHAL_POINTER(lldpd_port_set, lldpd_dot3_power, dot3_power)
+#endif
+#ifdef ENABLE_CUSTOM
+MARSHAL_POINTER(lldpd_port_set, lldpd_custom, custom)
+#endif
+MARSHAL_END(lldpd_port_set);
+
+/* Smart mode / Hide mode */
+#define SMART_INCOMING_FILTER (1 << 0) /* Incoming filtering enabled */
+#define SMART_INCOMING_ONE_PROTO (1 << 1) /* On reception, keep only one proto */
+#define SMART_INCOMING_ONE_NEIGH (1 << 2) /* On reception, keep only one neighbor */
+#define SMART_OUTGOING_FILTER (1 << 3) /* Outgoing filtering enabled */
+#define SMART_OUTGOING_ONE_PROTO (1 << 4) /* On emission, keep only one proto */
+#define SMART_OUTGOING_ONE_NEIGH (1 << 5) /* On emission, only one neighbor */
+#define SMART_INCOMING \
+ (SMART_INCOMING_FILTER | SMART_INCOMING_ONE_PROTO | SMART_INCOMING_ONE_NEIGH)
+#define SMART_OUTGOING \
+ (SMART_OUTGOING_FILTER | SMART_OUTGOING_ONE_PROTO | SMART_OUTGOING_ONE_NEIGH)
+
+struct lldpd_config {
+ int c_paused; /* lldpd is paused */
+ int c_tx_interval; /* Transmit interval (in ms) */
+ int c_ttl; /* TTL */
+ int c_smart; /* Bitmask for smart configuration (see SMART_*) */
+ int c_receiveonly; /* Receive only mode */
+ int c_max_neighbors; /* Maximum number of neighbors (per protocol) */
+
+ char *c_mgmt_pattern; /* Pattern to match a management address */
+ char *c_cid_pattern; /* Pattern to match interfaces to use for chassis ID */
+ char *c_cid_string; /* User defined string for chassis ID */
+ char *c_iface_pattern; /* Pattern to match interfaces to use */
+ char *c_perm_ifaces; /* Pattern to match interfaces to keep */
+
+ char *c_platform; /* Override platform description (for CDP) */
+ char *c_description; /* Override chassis description */
+ char *c_hostname; /* Override system name */
+ int c_advertise_version; /* Should the precise version be advertised? */
+ int c_set_ifdescr; /* Set interface description */
+ int c_promisc; /* Interfaces should be in promiscuous mode */
+ int c_cap_advertise; /* Chassis capabilities advertisement */
+ int c_cap_override; /* Override chassis capabilities enabled */
+ int c_mgmt_advertise; /* Management addresses advertisement */
+
+#ifdef ENABLE_LLDPMED
+ int c_noinventory; /* Don't send inventory with LLDP-MED */
+ int c_enable_fast_start; /* enable fast start */
+ int c_tx_fast_init; /* Num of lldpd lldppdu's for fast start */
+ int c_tx_fast_interval; /* Time intr between sends during fast start */
+#endif
+ int c_tx_hold; /* Transmit hold */
+ int c_bond_slave_src_mac_type; /* Src mac type in lldp frames over bond
+ slaves */
+ int c_lldp_portid_type; /* The PortID type */
+ int c_lldp_agent_type; /* The agent type */
+};
+MARSHAL_BEGIN(lldpd_config)
+MARSHAL_STR(lldpd_config, c_mgmt_pattern)
+MARSHAL_STR(lldpd_config, c_cid_pattern)
+MARSHAL_STR(lldpd_config, c_cid_string)
+MARSHAL_STR(lldpd_config, c_iface_pattern)
+MARSHAL_STR(lldpd_config, c_perm_ifaces)
+MARSHAL_STR(lldpd_config, c_hostname)
+MARSHAL_STR(lldpd_config, c_platform)
+MARSHAL_STR(lldpd_config, c_description)
+MARSHAL_END(lldpd_config);
+
+struct lldpd_frame {
+ int size;
+ unsigned char frame[1];
+};
+
+struct lldpd_hardware;
+struct lldpd;
+struct lldpd_ops {
+ int (*send)(struct lldpd *, struct lldpd_hardware *, char *,
+ size_t); /* Function to send a frame */
+ int (*recv)(struct lldpd *, struct lldpd_hardware *, int, char *,
+ size_t); /* Function to receive a frame */
+ int (*cleanup)(struct lldpd *, struct lldpd_hardware *); /* Cleanup function. */
+};
+
+/* An interface is uniquely identified by h_ifindex, h_ifname and h_ops. This
+ * means if an interface becomes enslaved, it will be considered as a new
+ * interface. The same applies for renaming and we include the index in case of
+ * renaming to an existing interface. */
+struct lldpd_hardware {
+ TAILQ_ENTRY(lldpd_hardware) h_entries;
+
+ struct lldpd *h_cfg; /* Pointer to main configuration */
+ void *h_recv; /* FD for reception */
+ int h_sendfd; /* FD for sending, only used by h_ops */
+ int h_mangle; /* 1 if we have to mangle the MAC address */
+ struct lldpd_ops *h_ops; /* Hardware-dependent functions */
+ void *h_data; /* Hardware-dependent data */
+ void *h_timer; /* Timer for this port */
+
+ int h_mtu;
+ int h_flags; /* Packets will be sent only
+ if IFF_RUNNING. Will be
+ removed if this is left
+ to 0. */
+ int h_ifindex; /* Interface index, used by SNMP */
+ int h_ifindex_changed; /* Interface index has changed */
+ char h_ifname[IFNAMSIZ]; /* Should be unique */
+ 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_unrecognized_cnt;
+ u_int64_t h_ageout_cnt;
+ u_int64_t h_insert_cnt;
+ u_int64_t h_delete_cnt;
+ u_int64_t h_drop_cnt;
+
+ /* Previous values of different stuff. */
+ /* Backup of the previous local port. Used to check if there was a
+ * change to send an immediate update. All those are not marshalled to
+ * the client. */
+ void *h_lport_previous;
+ ssize_t h_lport_previous_len;
+ /* Backup of the previous chassis ID. Used to check if there was a
+ * change and send an LLDP shutdown. */
+ u_int8_t h_lchassis_previous_id_subtype;
+ char *h_lchassis_previous_id;
+ int h_lchassis_previous_id_len;
+ /* Backup of the previous port ID. Used to check if there was a change
+ * and send an LLDP shutdown. */
+ u_int8_t h_lport_previous_id_subtype;
+ char *h_lport_previous_id;
+ int h_lport_previous_id_len;
+ char *h_ifdescr_previous;
+
+ struct lldpd_port h_lport; /* Port attached to this hardware port */
+ TAILQ_HEAD(, lldpd_port) h_rports; /* Remote ports */
+
+#ifdef ENABLE_LLDPMED
+ int h_tx_fast; /* current tx fast start count */
+#endif
+};
+MARSHAL_BEGIN(lldpd_hardware)
+MARSHAL_IGNORE(lldpd_hardware, h_entries.tqe_next)
+MARSHAL_IGNORE(lldpd_hardware, h_entries.tqe_prev)
+MARSHAL_IGNORE(lldpd_hardware, h_ops)
+MARSHAL_IGNORE(lldpd_hardware, h_data)
+MARSHAL_IGNORE(lldpd_hardware, h_cfg)
+MARSHAL_IGNORE(lldpd_hardware, h_lport_previous)
+MARSHAL_IGNORE(lldpd_hardware, h_lport_previous_len)
+MARSHAL_IGNORE(lldpd_hardware, h_lchassis_previous_id_subtype)
+MARSHAL_IGNORE(lldpd_hardware, h_lchassis_previous_id)
+MARSHAL_IGNORE(lldpd_hardware, h_lchassis_previous_id_len)
+MARSHAL_IGNORE(lldpd_hardware, h_lport_previous_id_subtype)
+MARSHAL_IGNORE(lldpd_hardware, h_lport_previous_id)
+MARSHAL_IGNORE(lldpd_hardware, h_lport_previous_id_len)
+MARSHAL_SUBSTRUCT(lldpd_hardware, lldpd_port, h_lport)
+MARSHAL_SUBTQ(lldpd_hardware, lldpd_port, h_rports)
+MARSHAL_END(lldpd_hardware);
+
+struct lldpd_interface {
+ TAILQ_ENTRY(lldpd_interface) next;
+ char *name;
+};
+MARSHAL_BEGIN(lldpd_interface)
+MARSHAL_TQE(lldpd_interface, next)
+MARSHAL_STR(lldpd_interface, name)
+MARSHAL_END(lldpd_interface);
+TAILQ_HEAD(lldpd_interface_list, lldpd_interface);
+MARSHAL_TQ(lldpd_interface_list, lldpd_interface);
+
+struct lldpd_neighbor_change {
+ char *ifname;
+#define NEIGHBOR_CHANGE_DELETED -1
+#define NEIGHBOR_CHANGE_ADDED 1
+#define NEIGHBOR_CHANGE_UPDATED 0
+ int state;
+ struct lldpd_port *neighbor;
+};
+MARSHAL_BEGIN(lldpd_neighbor_change)
+MARSHAL_STR(lldpd_neighbor_change, ifname)
+MARSHAL_POINTER(lldpd_neighbor_change, lldpd_port, neighbor)
+MARSHAL_END(lldpd_neighbor_change);
+
+/* Cleanup functions */
+void lldpd_chassis_mgmt_cleanup(struct lldpd_chassis *);
+void lldpd_chassis_cleanup(struct lldpd_chassis *, int);
+void lldpd_remote_cleanup(struct lldpd_hardware *,
+ void (*expire)(struct lldpd_hardware *, struct lldpd_port *), int);
+void lldpd_port_cleanup(struct lldpd_port *, int);
+void lldpd_config_cleanup(struct lldpd_config *);
+#ifdef ENABLE_DOT1
+void lldpd_ppvid_cleanup(struct lldpd_port *);
+void lldpd_vlan_cleanup(struct lldpd_port *);
+void lldpd_pi_cleanup(struct lldpd_port *);
+#endif
+#ifdef ENABLE_CUSTOM
+void lldpd_custom_tlv_cleanup(struct lldpd_port *, struct lldpd_custom *);
+void lldpd_custom_tlv_add(struct lldpd_port *, struct lldpd_custom *);
+void lldpd_custom_list_cleanup(struct lldpd_port *);
+#endif
+
+#endif
diff --git a/src/log.c b/src/log.c
new file mode 100644
index 0000000..ddfc14a
--- /dev/null
+++ b/src/log.c
@@ -0,0 +1,279 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/* $OpenBSD: log.c,v 1.11 2007/12/07 17:17:00 reyk Exp $ */
+
+/*
+ * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF 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 <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <syslog.h>
+#include <sys/types.h>
+#include <string.h>
+#include <errno.h>
+#include <time.h>
+#include "log.h"
+
+/* By default, logging is done on stderr. */
+static int use_syslog = 0;
+/* Default debug level */
+static int debug = 0;
+
+/* Logging can be modified by providing an appropriate log handler. */
+static void (*logh)(int severity, const char *msg) = NULL;
+
+static void vlog(int, const char *, const char *, va_list);
+static void logit(int, const char *, const char *, ...);
+
+#define MAX_DBG_TOKENS 40
+static const char *tokens[MAX_DBG_TOKENS + 1] = { NULL };
+
+void
+log_init(int n_syslog, int n_debug, const char *progname)
+{
+ use_syslog = n_syslog;
+ debug = n_debug;
+
+ if (use_syslog) openlog(progname, LOG_PID | LOG_NDELAY, LOG_DAEMON);
+
+ tzset();
+}
+
+void
+log_level(int n_debug)
+{
+ if (n_debug >= 0) debug = n_debug;
+}
+
+void
+log_register(void (*cb)(int, const char *))
+{
+ logh = cb;
+}
+
+void
+log_accept(const char *token)
+{
+ int i;
+ for (i = 0; i < MAX_DBG_TOKENS; i++) {
+ if (tokens[i] == NULL) {
+ tokens[i + 1] = NULL;
+ tokens[i] = token;
+ return;
+ }
+ }
+}
+
+static void
+logit(int pri, const char *token, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vlog(pri, token, fmt, ap);
+ va_end(ap);
+}
+
+static char *
+date()
+{
+ /* Return the current date as incomplete ISO 8601 (2012-12-12T16:13:30) */
+ static char date[] = "2012-12-12T16:13:30";
+ time_t t = time(NULL);
+ struct tm *tmp = localtime(&t);
+ strftime(date, sizeof(date), "%Y-%m-%dT%H:%M:%S", tmp);
+ return date;
+}
+
+static const char *
+translate(int fd, int priority)
+{
+ /* Translate a syslog priority to a string. With colors if the output is a
+ * terminal. */
+ int tty = isatty(fd);
+ switch (tty) {
+ case 1:
+ switch (priority) {
+ case LOG_EMERG:
+ return "\033[1;37;41m[EMRG";
+ case LOG_ALERT:
+ return "\033[1;37;41m[ALRT";
+ case LOG_CRIT:
+ return "\033[1;37;41m[CRIT";
+ case LOG_ERR:
+ return "\033[1;31m[ ERR";
+ case LOG_WARNING:
+ return "\033[1;33m[WARN";
+ case LOG_NOTICE:
+ return "\033[1;34m[NOTI";
+ case LOG_INFO:
+ return "\033[1;34m[INFO";
+ case LOG_DEBUG:
+ return "\033[36m[ DBG";
+ }
+ break;
+ default:
+ switch (priority) {
+ case LOG_EMERG:
+ return "[EMRG";
+ case LOG_ALERT:
+ return "[ALRT";
+ case LOG_CRIT:
+ return "[CRIT";
+ case LOG_ERR:
+ return "[ ERR";
+ case LOG_WARNING:
+ return "[WARN";
+ case LOG_NOTICE:
+ return "[NOTI";
+ case LOG_INFO:
+ return "[INFO";
+ case LOG_DEBUG:
+ return "[ DBG";
+ }
+ }
+ return "[UNKN]";
+}
+
+static void
+vlog(int pri, const char *token, const char *fmt, va_list ap)
+{
+ if (logh) {
+ char *result = NULL;
+ if (vasprintf(&result, fmt, ap) != -1) {
+ logh(pri, result);
+ free(result);
+ return;
+ }
+ /* Otherwise, abort. We don't know if "ap" is still OK. We could
+ * have made a copy, but this is too much overhead for a
+ * situation that shouldn't happen. */
+ return;
+ }
+
+ /* Log to syslog if requested */
+ if (use_syslog) {
+ va_list ap2;
+ va_copy(ap2, ap);
+ vsyslog(pri, fmt, ap2);
+ va_end(ap2);
+ }
+
+ /* Log to standard error in all cases */
+ char *nfmt;
+ /* best effort in out of mem situations */
+ if (asprintf(&nfmt, "%s %s%s%s]%s %s\n", date(), translate(STDERR_FILENO, pri),
+ token ? "/" : "", token ? token : "",
+ isatty(STDERR_FILENO) ? "\033[0m" : "", fmt) == -1) {
+ vfprintf(stderr, fmt, ap);
+ fprintf(stderr, "\n");
+ } else {
+ vfprintf(stderr, nfmt, ap);
+ free(nfmt);
+ }
+ fflush(stderr);
+}
+
+void
+log_warn(const char *token, const char *emsg, ...)
+{
+ char *nfmt = NULL;
+ va_list ap;
+
+ /* best effort to even work in out of memory situations */
+ if (emsg == NULL)
+ logit(LOG_WARNING, "%s", strerror(errno));
+ else {
+ va_start(ap, emsg);
+
+ if (asprintf(&nfmt, "%s: %s", emsg, strerror(errno)) == -1) {
+ /* we tried it... */
+ vlog(LOG_WARNING, token, emsg, ap);
+ logit(LOG_WARNING, "%s", strerror(errno));
+ } else {
+ vlog(LOG_WARNING, token, nfmt, ap);
+ free(nfmt);
+ }
+ va_end(ap);
+ }
+}
+
+void
+log_warnx(const char *token, const char *emsg, ...)
+{
+ va_list ap;
+
+ va_start(ap, emsg);
+ vlog(LOG_WARNING, token, emsg, ap);
+ va_end(ap);
+}
+
+void
+log_info(const char *token, const char *emsg, ...)
+{
+ va_list ap;
+
+ if (use_syslog || debug > 0 || logh) {
+ va_start(ap, emsg);
+ vlog(LOG_INFO, token, emsg, ap);
+ va_end(ap);
+ }
+}
+
+static int
+log_debug_accept_token(const char *token)
+{
+ int i;
+ if (tokens[0] == NULL) return 1;
+ for (i = 0; (i < MAX_DBG_TOKENS) && (tokens[i] != NULL); i++) {
+ if (!strcmp(tokens[i], token)) return 1;
+ }
+ return 0;
+}
+
+void
+log_debug(const char *token, const char *emsg, ...)
+{
+ va_list ap;
+
+ if ((debug > 1 && log_debug_accept_token(token)) || logh) {
+ va_start(ap, emsg);
+ vlog(LOG_DEBUG, token, emsg, ap);
+ va_end(ap);
+ }
+}
+
+void
+fatal(const char *token, const char *emsg)
+{
+ if (emsg == NULL)
+ logit(LOG_CRIT, token ? token : "fatal", "%s", strerror(errno));
+ else if (errno)
+ logit(LOG_CRIT, token ? token : "fatal", "%s: %s", emsg,
+ strerror(errno));
+ else
+ logit(LOG_CRIT, token ? token : "fatal", "%s", emsg);
+
+ exit(1);
+}
+
+void
+fatalx(const char *token, const char *emsg)
+{
+ errno = 0;
+ fatal(token, emsg);
+}
diff --git a/src/log.h b/src/log.h
new file mode 100644
index 0000000..a85ccbf
--- /dev/null
+++ b/src/log.h
@@ -0,0 +1,39 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/*
+ * Copyright (c) 2012 Vincent Bernat <bernat@luffy.cx>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef _LOG_H
+#define _LOG_H
+
+#include <stdio.h>
+
+/* log.c */
+void log_init(int, int, const char *);
+void log_warn(const char *, const char *, ...) __attribute__((format(printf, 2, 3)));
+void log_warnx(const char *, const char *, ...) __attribute__((format(printf, 2, 3)));
+void log_info(const char *, const char *, ...) __attribute__((format(printf, 2, 3)));
+void log_debug(const char *, const char *, ...) __attribute__((format(printf, 2, 3)));
+void fatal(const char *, const char *) __attribute__((__noreturn__));
+void fatalx(const char *, const char *) __attribute__((__noreturn__));
+
+void log_register(void (*cb)(int, const char *));
+void log_accept(const char *);
+void log_level(int);
+
+/* version.c */
+void version_display(FILE *, const char *, int);
+
+#endif
diff --git a/src/marshal.c b/src/marshal.c
new file mode 100644
index 0000000..c6a25f7
--- /dev/null
+++ b/src/marshal.c
@@ -0,0 +1,380 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/*
+ * Copyright (c) 2012 Vincent Bernat <bernat@luffy.cx>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#define MARSHAL_EXPORT
+#include "marshal.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <string.h>
+
+#include "compat/compat.h"
+#include "log.h"
+
+#include "lldpd-structs.h"
+
+/* Stolen from CCAN */
+#if HAVE_ALIGNOF
+# define ALIGNOF(t) (__alignof__(t))
+#else
+# define ALIGNOF(t) \
+ ((sizeof(t) > 1) ? \
+ ((char *)(&((struct { \
+ char c; \
+ t _h; \
+ } *)0) \
+ ->_h) - \
+ (char *)0) : \
+ 1)
+#endif
+
+/* A serialized object */
+struct marshal_serialized {
+ void *orig; /* Original reference. Also enforce alignment. */
+ size_t size;
+ unsigned char object[0];
+};
+
+struct marshal_info marshal_info_string = {
+ .name = "null string",
+ .size = 0,
+ .pointers = { MARSHAL_SUBINFO_NULL },
+};
+struct marshal_info marshal_info_fstring = {
+ .name = "fixed string",
+ .size = 0,
+ .pointers = { MARSHAL_SUBINFO_NULL },
+};
+struct marshal_info marshal_info_ignore = {
+ .name = "ignored",
+ .size = 0,
+ .pointers = { MARSHAL_SUBINFO_NULL },
+};
+
+/* List of already seen pointers */
+struct ref {
+ TAILQ_ENTRY(ref) next;
+ void *pointer;
+ uintptr_t dummy; /* To renumerate pointers */
+};
+TAILQ_HEAD(ref_l, ref);
+
+/* Serialize the given object. */
+ssize_t
+marshal_serialize_(struct marshal_info *mi, void *unserialized, void **input, int skip,
+ void *_refs, int osize)
+{
+ struct ref_l *refs = _refs;
+ struct ref *cref;
+ int size;
+ size_t len;
+ struct marshal_subinfo *current;
+ struct marshal_serialized *new = NULL, *serialized = NULL;
+ uintptr_t dummy = 1;
+
+ log_debug("marshal", "start serialization of %s", mi->name);
+
+ /* Check if we have already serialized this one. */
+ if (!refs) {
+ refs = calloc(1, sizeof(struct ref_l));
+ if (!refs) {
+ log_warnx("marshal",
+ "unable to allocate memory for list of references");
+ return -1;
+ }
+ TAILQ_INIT(refs);
+ }
+ TAILQ_FOREACH (cref, refs, next) {
+ if (unserialized == cref->pointer) return 0;
+ /* dummy should be higher than any existing dummy */
+ if (cref->dummy >= dummy) dummy = cref->dummy + 1;
+ }
+
+ /* Handle special cases. */
+ size = mi->size;
+ if (!strcmp(mi->name, "null string")) /* We know we can't be called with NULL */
+ size = strlen((char *)unserialized) + 1;
+ else if (!strcmp(mi->name, "fixed string"))
+ size = osize;
+
+ /* Allocate serialized structure */
+ len = sizeof(struct marshal_serialized) + (skip ? 0 : size);
+ serialized = calloc(1, len);
+ if (!serialized) {
+ log_warnx("marshal",
+ "unable to allocate memory to serialize structure %s", mi->name);
+ len = -1;
+ goto marshal_error;
+ }
+ /* We don't use the original pointer but a dummy one. */
+ serialized->orig = (unsigned char *)dummy;
+
+ /* Append the new reference */
+ if (!(cref = calloc(1, sizeof(struct ref)))) {
+ log_warnx("marshal",
+ "unable to allocate memory for list of references");
+ free(serialized);
+ len = -1;
+ goto marshal_error;
+ }
+ cref->pointer = unserialized;
+ cref->dummy = dummy;
+ TAILQ_INSERT_TAIL(refs, cref, next);
+
+ /* First, serialize the main structure */
+ if (!skip) memcpy(serialized->object, unserialized, size);
+
+ /* Then, serialize inner structures */
+ for (current = mi->pointers; current->mi; current++) {
+ size_t sublen;
+ size_t padlen;
+ void *source;
+ void *target = NULL;
+ if (current->kind == ignore) continue;
+ if (current->kind == pointer) {
+ memcpy(&source, (unsigned char *)unserialized + current->offset,
+ sizeof(void *));
+ if (source == NULL) continue;
+ } else
+ source =
+ (void *)((unsigned char *)unserialized + current->offset);
+ if (current->offset2)
+ memcpy(&osize, (unsigned char *)unserialized + current->offset2,
+ sizeof(int));
+ target = NULL;
+ sublen = marshal_serialize_(current->mi, source, &target,
+ current->kind == substruct, refs, osize);
+ if (sublen == -1) {
+ log_warnx("marshal",
+ "unable to serialize substructure %s for %s",
+ current->mi->name, mi->name);
+ free(serialized);
+ return -1;
+ }
+ /* We want to put the renumerated pointer instead of the real one. */
+ if (current->kind == pointer && !skip) {
+ TAILQ_FOREACH (cref, refs, next) {
+ if (source == cref->pointer) {
+ void *fakepointer =
+ (unsigned char *)cref->dummy;
+ memcpy((unsigned char *)serialized->object +
+ current->offset,
+ &fakepointer, sizeof(void *));
+ break;
+ }
+ }
+ }
+ if (sublen == 0) continue; /* This was already serialized */
+ /* Append the result, force alignment to be able to unserialize it */
+ padlen = ALIGNOF(struct marshal_serialized);
+ padlen = (padlen - (len % padlen)) % padlen;
+ new = realloc(serialized, len + padlen + sublen);
+ if (!new) {
+ log_warnx("marshal",
+ "unable to allocate more memory to serialize structure %s",
+ mi->name);
+ free(serialized);
+ free(target);
+ len = -1;
+ goto marshal_error;
+ }
+ memset((unsigned char *)new + len, 0, padlen);
+ memcpy((unsigned char *)new + len + padlen, target, sublen);
+ free(target);
+ len += sublen + padlen;
+ serialized = (struct marshal_serialized *)new;
+ }
+
+ serialized->size = len;
+ *input = serialized;
+marshal_error:
+ if (refs && !_refs) {
+ struct ref *cref, *cref_next;
+ for (cref = TAILQ_FIRST(refs); cref != NULL; cref = cref_next) {
+ cref_next = TAILQ_NEXT(cref, next);
+ TAILQ_REMOVE(refs, cref, next);
+ free(cref);
+ }
+ free(refs);
+ }
+ return len;
+}
+
+/* This structure is used to track memory allocation when serializing */
+struct gc {
+ TAILQ_ENTRY(gc) next;
+ void *pointer;
+ void *orig; /* Original reference (not valid anymore !) */
+};
+TAILQ_HEAD(gc_l, gc);
+
+static void *
+marshal_alloc(struct gc_l *pointers, size_t len, void *orig)
+{
+ struct gc *gpointer = NULL;
+
+ void *result = calloc(1, len);
+ if (!result) return NULL;
+ if ((gpointer = (struct gc *)calloc(1, sizeof(struct gc))) == NULL) {
+ free(result);
+ return NULL;
+ }
+ gpointer->pointer = result;
+ gpointer->orig = orig;
+ TAILQ_INSERT_TAIL(pointers, gpointer, next);
+ return result;
+}
+static void
+marshal_free(struct gc_l *pointers, int gconly)
+{
+ 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 (!gconly) free(pointer->pointer);
+ free(pointer);
+ }
+}
+
+/* Unserialize the given object. */
+size_t
+marshal_unserialize_(struct marshal_info *mi, void *buffer, size_t len, void **output,
+ void *_pointers, int skip, int osize)
+{
+ int total_len = sizeof(struct marshal_serialized) + (skip ? 0 : mi->size);
+ struct marshal_serialized *serialized = buffer;
+ struct gc_l *pointers = _pointers;
+ int size, already, extra = 0;
+ void *new;
+ struct marshal_subinfo *current;
+ struct gc *apointer;
+
+ log_debug("marshal", "start unserialization of %s", mi->name);
+
+ if (len < sizeof(struct marshal_serialized) || len < total_len) {
+ log_warnx("marshal",
+ "data to deserialize is too small (%zu) for structure %s", len,
+ mi->name);
+ return 0;
+ }
+
+ /* Initialize garbage collection */
+ if (!pointers) {
+ pointers = calloc(1, sizeof(struct gc_l));
+ if (!pointers) {
+ log_warnx("marshal",
+ "unable to allocate memory for garbage collection");
+ return 0;
+ }
+ TAILQ_INIT(pointers);
+ }
+
+ /* Special cases */
+ size = mi->size;
+ if (!strcmp(mi->name, "null string") || !strcmp(mi->name, "fixed string")) {
+ switch (mi->name[0]) {
+ case 'n':
+ size = strnlen((char *)serialized->object,
+ len - sizeof(struct marshal_serialized)) +
+ 1;
+ break;
+ case 'f':
+ size = osize;
+ extra = 1;
+ break; /* The extra byte is to ensure that
+ the string is null terminated. */
+ }
+ if (size > len - sizeof(struct marshal_serialized)) {
+ log_warnx("marshal",
+ "data to deserialize contains a string too long");
+ total_len = 0;
+ goto unmarshal_error;
+ }
+ total_len += size;
+ }
+
+ /* First, the main structure */
+ if (!skip) {
+ if ((*output = marshal_alloc(pointers, size + extra,
+ serialized->orig)) == NULL) {
+ log_warnx("marshal",
+ "unable to allocate memory to unserialize structure %s",
+ mi->name);
+ total_len = 0;
+ goto unmarshal_error;
+ }
+ memcpy(*output, serialized->object, size);
+ }
+
+ /* Then, each substructure */
+ for (current = mi->pointers; current->mi; current++) {
+ size_t sublen;
+ size_t padlen;
+ new = (unsigned char *)*output + current->offset;
+ if (current->kind == ignore) {
+ memset((unsigned char *)*output + current->offset, 0,
+ sizeof(void *));
+ continue;
+ }
+ if (current->kind == pointer) {
+ if (*(void **)new == NULL) continue;
+
+ /* Did we already see this reference? */
+ already = 0;
+ TAILQ_FOREACH (apointer, pointers, next)
+ if (apointer->orig == *(void **)new) {
+ memcpy((unsigned char *)*output +
+ current->offset,
+ &apointer->pointer, sizeof(void *));
+ already = 1;
+ break;
+ }
+ if (already) continue;
+ }
+ /* Deserialize */
+ if (current->offset2)
+ memcpy(&osize, (unsigned char *)*output + current->offset2,
+ sizeof(int));
+ padlen = ALIGNOF(struct marshal_serialized);
+ padlen = (padlen - (total_len % padlen)) % padlen;
+ if (len < total_len + padlen ||
+ ((sublen = marshal_unserialize_(current->mi,
+ (unsigned char *)buffer + total_len + padlen,
+ len - total_len - padlen, &new, pointers,
+ current->kind == substruct, osize)) == 0)) {
+ log_warnx("marshal",
+ "unable to serialize substructure %s for %s",
+ current->mi->name, mi->name);
+ total_len = 0;
+ goto unmarshal_error;
+ }
+ /* Link the result */
+ if (current->kind == pointer)
+ memcpy((unsigned char *)*output + current->offset, &new,
+ sizeof(void *));
+ total_len += sublen + padlen;
+ }
+
+unmarshal_error:
+ if (pointers && !_pointers) {
+ marshal_free(pointers, (total_len > 0));
+ free(pointers);
+ }
+ return total_len;
+}
diff --git a/src/marshal.h b/src/marshal.h
new file mode 100644
index 0000000..e28a011
--- /dev/null
+++ b/src/marshal.h
@@ -0,0 +1,158 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/*
+ * Copyright (c) 2012 Vincent Bernat <bernat@luffy.cx>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef _MARSHAL_H
+#define _MARSHAL_H
+
+#include <stddef.h>
+#include <stdint.h>
+#include <sys/types.h>
+
+struct marshal_info;
+enum marshal_subinfo_kind {
+ pointer,
+ substruct,
+ ignore,
+};
+#define MARSHAL_INFO_POINTER 1
+#define MARSHAL_INFO_SUB 2
+struct marshal_subinfo {
+ size_t offset; /* Offset compared to parent structure */
+ size_t offset2; /* Ancillary offset (for related data) */
+ enum marshal_subinfo_kind kind; /* Kind of substructure */
+ struct marshal_info *mi;
+};
+#define MARSHAL_SUBINFO_NULL \
+ { \
+ .offset = 0, .offset2 = 0, .kind = ignore, .mi = NULL \
+ }
+struct marshal_info {
+ const char *name; /* Name of structure */
+ size_t size; /* Size of the structure */
+#if defined __GNUC__ && __GNUC__ < 3
+ /* With gcc 2.96, flexible arrays are not supported, even with
+ * -std=gnu99. And with gcc 3.x, zero-sized arrays cannot be statically
+ * initialized (with more than one element). */
+ struct marshal_subinfo pointers[0]; /* Pointer to other structures */
+#else
+ struct marshal_subinfo pointers[]; /* Pointer to other structures */
+#endif
+};
+/* Special case for strings */
+extern struct marshal_info marshal_info_string;
+extern struct marshal_info marshal_info_fstring;
+extern struct marshal_info marshal_info_ignore;
+
+/* Declare a new marshal_info struct named after the type we want to
+ marshal. The marshalled type has to be a structure. */
+#define MARSHAL_INFO(type) marshal_info_##type
+#ifdef MARSHAL_EXPORT
+# define MARSHAL_HELPER_FUNCTIONS(type, ttype) \
+ ssize_t type##_serialize(ttype *source, void *buffer); \
+ ssize_t type##_serialize(ttype *source, void *buffer) \
+ { \
+ return marshal_serialize(type, source, buffer); \
+ } \
+ size_t type##_unserialize(void *buffer, size_t len, ttype **destination); \
+ size_t type##_unserialize(void *buffer, size_t len, ttype **destination) \
+ { \
+ void *p; \
+ size_t rc; \
+ rc = marshal_unserialize(type, buffer, len, &p); \
+ if (rc <= 0) return rc; \
+ *destination = p; \
+ return rc; \
+ }
+# define MARSHAL_BEGIN(type) \
+ struct marshal_info MARSHAL_INFO( \
+ type) = { .name = #type, .size = sizeof(struct type), .pointers = {
+# define MARSHAL_ADD(_kind, type, subtype, member) \
+ { .offset = offsetof(struct type, member), \
+ .offset2 = 0, \
+ .kind = _kind, \
+ .mi = &MARSHAL_INFO(subtype) },
+# define MARSHAL_FSTR(type, member, len) \
+ { .offset = offsetof(struct type, member), \
+ .offset2 = offsetof(struct type, len), \
+ .kind = pointer, \
+ .mi = &marshal_info_fstring },
+# define MARSHAL_END(type) \
+ MARSHAL_SUBINFO_NULL \
+ } \
+ } \
+ ; \
+ MARSHAL_HELPER_FUNCTIONS(type, struct type)
+#else
+# define MARSHAL_HELPER_FUNCTIONS(type, ttype) \
+ ssize_t type##_serialize(ttype *, void *); \
+ size_t type##_unserialize(void *, size_t, ttype **);
+# define MARSHAL_BEGIN(type) extern struct marshal_info MARSHAL_INFO(type);
+# define MARSHAL_ADD(...)
+# define MARSHAL_FSTR(...)
+# define MARSHAL_END(type) MARSHAL_HELPER_FUNCTIONS(type, struct type)
+#endif
+/* Shortcuts */
+#define MARSHAL_POINTER(...) MARSHAL_ADD(pointer, ##__VA_ARGS__)
+#define MARSHAL_SUBSTRUCT(...) MARSHAL_ADD(substruct, ##__VA_ARGS__)
+#define MARSHAL_STR(type, member) MARSHAL_ADD(pointer, type, string, member)
+#define MARSHAL_IGNORE(type, member) MARSHAL_ADD(ignore, type, ignore, member)
+#define MARSHAL_TQE(type, field) \
+ MARSHAL_POINTER(type, type, field.tqe_next) \
+ MARSHAL_IGNORE(type, field.tqe_prev)
+/* Support for TAILQ list is partial. Access to last and previous
+ elements is not available. Some operations are therefore not
+ possible. However, TAILQ_FOREACH is still
+ available. */
+#define MARSHAL_TQH(type, subtype) \
+ MARSHAL_POINTER(type, subtype, tqh_first) \
+ MARSHAL_IGNORE(type, tqh_last)
+#define MARSHAL_SUBTQ(type, subtype, field) \
+ MARSHAL_POINTER(type, subtype, field.tqh_first) \
+ MARSHAL_IGNORE(type, field.tqh_last)
+#define MARSHAL(type) \
+ MARSHAL_BEGIN(type) \
+ MARSHAL_END(type)
+#define MARSHAL_TQ(type, subtype) \
+ MARSHAL_BEGIN(type) \
+ MARSHAL_TQH(type, subtype) \
+ MARSHAL_END(type)
+
+/* Serialization */
+ssize_t marshal_serialize_(struct marshal_info *, void *, void **, int, void *, int)
+ __attribute__((nonnull(1, 2, 3)));
+#define marshal_serialize(type, o, output) \
+ marshal_serialize_(&MARSHAL_INFO(type), o, output, 0, NULL, 0)
+
+/* Unserialization */
+size_t marshal_unserialize_(struct marshal_info *, void *, size_t, void **, void *, int,
+ int) __attribute__((nonnull(1, 2, 4)));
+#define marshal_unserialize(type, o, l, input) \
+ marshal_unserialize_(&MARSHAL_INFO(type), o, l, input, NULL, 0, 0)
+
+#define marshal_repair_tailq(type, head, field) \
+ do { \
+ struct type *__item, *__item_next; \
+ (head)->tqh_last = &(head)->tqh_first; \
+ for (__item = TAILQ_FIRST(head); __item != NULL; __item = __item_next) { \
+ __item_next = TAILQ_NEXT(__item, field); \
+ __item->field.tqe_prev = (head)->tqh_last; \
+ *(head)->tqh_last = __item; \
+ (head)->tqh_last = &__item->field.tqe_next; \
+ } \
+ } while (0)
+
+#endif
diff --git a/src/version.c b/src/version.c
new file mode 100644
index 0000000..c921564
--- /dev/null
+++ b/src/version.c
@@ -0,0 +1,138 @@
+/* -*- mode: c; c-file-style: "openbsd" -*- */
+/*
+ * Copyright (c) 2016 Vincent Bernat <bernat@luffy.cx>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#if HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include "compat/compat.h"
+#include "log.h"
+
+static void
+version_display_array(FILE *destination, const char *prefix, const char *const *items)
+{
+ fprintf(destination, "%s", prefix);
+ size_t count = 0;
+ for (const char *const *p = items; *p; p++, count++)
+ fprintf(destination, "%s%s", count ? ", " : "", *p);
+ if (count == 0)
+ fprintf(destination, "(none)\n");
+ else
+ fprintf(destination, "\n");
+}
+
+void
+version_display(FILE *destination, const char *progname, int verbose)
+{
+ if (!verbose) {
+ fprintf(destination, "%s\n", PACKAGE_VERSION);
+ return;
+ }
+
+ const char *const lldp_features[] = {
+#ifdef ENABLE_LLDPMED
+ "LLDP-MED",
+#endif
+#ifdef ENABLE_DOT1
+ "Dot1",
+#endif
+#ifdef ENABLE_DOT3
+ "Dot3",
+#endif
+#ifdef ENABLE_CUSTOM
+ "Custom TLV",
+#endif
+ NULL
+ };
+ const char *const protocols[] = {
+#ifdef ENABLE_CDP
+ "CDP",
+#endif
+#ifdef ENABLE_FDP
+ "FDP",
+#endif
+#ifdef ENABLE_EDP
+ "EDP",
+#endif
+#ifdef ENABLE_SONMP
+ "SONMP",
+#endif
+ NULL
+ };
+ const char *const output_formats[] = { "TEXT", "KV", "JSON",
+#ifdef USE_XML
+ "XML",
+#endif
+ NULL };
+
+ fprintf(destination, "%s %s\n", progname, PACKAGE_VERSION);
+ fprintf(destination, " Built on " BUILD_DATE "\n");
+ fprintf(destination, "\n");
+
+ /* Features */
+ if (!strcmp(progname, "lldpd")) {
+ version_display_array(destination,
+ "Additional LLDP features: ", lldp_features);
+ version_display_array(destination,
+ "Additional protocols: ", protocols);
+ fprintf(destination,
+ "SNMP support: "
+#ifdef USE_SNMP
+ "yes\n"
+#else
+ "no\n"
+#endif
+ );
+#ifdef HOST_OS_LINUX
+ fprintf(destination,
+ "Old kernel support: "
+# ifdef ENABLE_OLDIES
+ "yes"
+# else
+ "no"
+# endif
+ " (Linux " MIN_LINUX_KERNEL_VERSION "+)\n");
+#endif
+#ifdef ENABLE_PRIVSEP
+ fprintf(destination,
+ "Privilege separation: "
+ "enabled\n");
+ fprintf(destination, "Privilege separation user: " PRIVSEP_USER "\n");
+ fprintf(destination,
+ "Privilege separation group: " PRIVSEP_GROUP "\n");
+ fprintf(destination,
+ "Privilege separation chroot: " PRIVSEP_CHROOT "\n");
+#else
+ fprintf(destination,
+ "Privilege separation: "
+ "disabled\n");
+#endif
+ fprintf(destination, "Configuration directory: " SYSCONFDIR "\n");
+ }
+
+ if (!strcmp(progname, "lldpcli")) {
+ version_display_array(destination,
+ "Additional output formats: ", output_formats);
+ }
+
+ fprintf(destination, "\n");
+
+ /* Build */
+ fprintf(destination, "C compiler command: %s\n", LLDP_CC);
+ fprintf(destination, "Linker command: %s\n", LLDP_LD);
+}