diff --git a/.dist-version b/.dist-version new file mode 100644 index 0000000000000000000000000000000000000000..afaf360d37fb71bcfa8cc082882f910ac2628bda --- /dev/null +++ b/.dist-version @@ -0,0 +1 @@ +1.0.0 \ No newline at end of file diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000000000000000000000000000000000000..5694e6b9fe1947583d6db507cb9ac757f800f13c --- /dev/null +++ b/.dockerignore @@ -0,0 +1,2 @@ +tests/integration/__pycache__ +Dockerfile diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..d90e1b846b42083f91a996c72bf82e8ea9704ca1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,43 @@ +*~ +*.o +*.lo +*.a +*.la +*.swp +/build*/ +/result + +# autotools stuff +/m4/lt*.m4 +/m4/libtool.m4 +/aclocal.m4 +/ltmain.sh +/install-sh +/config.guess +/config.h.in +/config.sub +/autom4te.cache/ +/compile +/configure +/depcomp +/missing +/test-driver + +# automake +/Makefile.in +/src/Makefile.in +/src/client/Makefile.in +/src/lib/Makefile.in +/src/daemon/Makefile.in +/src/compat/Makefile.in +/src/Makefile.in +/tests/Makefile.in +/osx/Makefile.in +/ar-lib + +# cscope +/cscope.* +/GPATH +/GRTAGS +/GSYMS +/GTAGS diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000000000000000000000000000000000000..2119f124a0af19567ae2a20af4c4772bc7422bea --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "libevent"] + path = libevent + url = https://github.com/libevent/libevent.git diff --git a/CONTRIBUTE.md b/CONTRIBUTE.md new file mode 100644 index 0000000000000000000000000000000000000000..3c2f5011c002853e41406f74e4a1f0152fdb2684 --- /dev/null +++ b/CONTRIBUTE.md @@ -0,0 +1,66 @@ +Coding standards +---------------- + +Contributed code should roughly follow [OpenBSD style][1]. For Emacs, +I am using the following snippet to get the appropriate indentation: + + (c-add-style + "openbsd" + '("bsd" + (c-basic-offset . 8) + (c-tab-width . 8) + (fill-column . 80) + (indent-tabs-mode . t) + (c-offsets-alist . ((defun-block-intro . +) + (statement-block-intro . +) + (statement-case-intro . +) + (statement-cont . *) + (substatement-open . *) + (substatement . +) + (arglist-cont-nonempty . *) + (inclass . +) + (inextern-lang . 0) + (knr-argdecl-intro . +))))) + +Important stuff is to use tabulations. Each tabulation has a width of +8 characters. This limits excessive nesting. Try to respect the 80 +columns limit if possible. + +Opening braces are on the same line, except for functions where they +are on their own lines. Closing braces are always on their own +lives. Return type for functions are on their own lines too: + + int + main(int argc, char *argv[]) + { + /* [...] */ + } + +[1]: http://www.openbsd.org/cgi-bin/man.cgi?query=style&sektion=9 + +Submitting patches +------------------ + +Patches against git tip are preferred. Please, clone the repository: + + git clone https://github.com/lldpd/lldpd.git + +Do any modification you need. Commit the modifications with a +meaningful message: + + 1. Use a descriptive first-line. + + 2. Prepend the first line with the name of the subsystem modified + (`lldpd`, `lib`, `client`) or something a bit more precise. + + 3. Don't be afraid to put a lot of details in the commit message. + +Use `git format-patch` to get patches to submit: + + git format-patch origin/master + +Feel free to use `git send-email` which is like `git format-patch` but +will propose to directly send patches by email. You can also open a +[pull request][2] on Github. + +[2]: https://help.github.com/articles/using-pull-requests diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..8088a726a8696895d5894dc7064754237e08d1eb --- /dev/null +++ b/Dockerfile @@ -0,0 +1,32 @@ +FROM alpine:latest AS build +RUN apk add autoconf automake libtool \ + libevent-dev libxml2-dev jansson-dev \ + readline-dev libcap-dev alpine-sdk +WORKDIR /build +COPY . . +RUN ./autogen.sh +RUN ./configure \ + --prefix=/usr \ + --sysconfdir=/etc \ + --enable-pie \ + --enable-hardening \ + --without-embedded-libevent \ + --without-snmp \ + --with-xml \ + --with-privsep-user=_lldpd \ + --with-privsep-group=_lldpd \ + --with-privsep-chroot=/run/lldpd \ + --with-lldpd-ctl-socket=/run/lldpd.socket \ + --with-lldpd-pid-file=/run/lldpd.pid +RUN make +RUN make install DESTDIR=/lldpd + +FROM alpine:latest +RUN apk add libevent libxml2 jansson readline libcap \ + && addgroup -S _lldpd \ + && adduser -S -G _lldpd -D -H -g "lldpd user" _lldpd +COPY --from=build /lldpd / +VOLUME /etc/lldpd.d +ENTRYPOINT ["lldpd", "-d"] +CMD [] + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..b5c9a8390d0eba8baf9f6cef3396695a92789a68 --- /dev/null +++ b/LICENSE @@ -0,0 +1,19 @@ +The license below applies to most, but not all content in this project. +Files with different licensing and authorship terms are marked as such. +That information must be considered when ensuring licensing compliance. + +ISC License + +Copyright (c) 2008-2017, Vincent Bernat + +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. diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000000000000000000000000000000000000..990e631d3e40e3f384630507eef474d1dfff9220 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,44 @@ +include doxygen.am + +ACLOCAL_AMFLAGS = -I m4 + +SUBDIRS = src/compat src src/daemon src/lib src/client tests +EXTRA_DIST = $(DX_CONFIG) include get-version autogen.sh +DIST_SUBDIRS = $(SUBDIRS) libevent +DISTCLEANFILES = ChangeLog + +dist_doc_DATA = README.md NEWS CONTRIBUTE.md LICENSE +doc_DATA = ChangeLog + +__force-changelog-generation: +ChangeLog: __force-changelog-generation + $(AM_V_GEN)if test -e $(top_srcdir)/.git; then \ + prev=$$(git describe --tags --always --match '[0-9]*' 2> /dev/null) ; \ + for tag in $$(git tag | $(EGREP) '^[0-9]+(\.[0-9]+){1,}$$' | sort -t. -k 1,1nr -k 2,2nr -k 3,3nr); do \ + if [ x"$$prev" = x ]; then prev=$$tag ; fi ; \ + if [ x"$$prev" = x"$$tag" ]; then continue; fi ; \ + echo "$$prev [$$(git log --no-merges $$prev -1 --pretty=format:'%ai')]:" ; \ + echo "" ; \ + git log --pretty=' - [%h] %s (%an)' $$tag..$$prev ; \ + echo "" ; \ + prev=$$tag ; \ + done > $@ ; \ + else \ + touch $@ ; \ + fi + +dist-hook: + echo $(VERSION) > $(distdir)/.dist-version + +MOSTLYCLEANFILES = $(DX_CLEANFILES) + +# systemd and launchd files are not installed in the prefix, don't +# request them for distcheck +DISTCHECK_CONFIGURE_FLAGS = $(CONFIGURE_ARGS) \ + --with-sysusersdir=no \ + --with-systemdsystemunitdir=no \ + --with-launchddaemonsdir=no \ + --with-apparmordir=no \ + --sysconfdir='$$(prefix)/etc' + + diff --git a/NEWS b/NEWS new file mode 100644 index 0000000000000000000000000000000000000000..dbb1b804b91b57932e02743f09d363723a035c39 --- /dev/null +++ b/NEWS @@ -0,0 +1,619 @@ +lldpd (1.0.13) + * Fix: + + Add support for 2.5G, 5G, 25G and 50G based Ethernet (#475) + + Fix link-down detection on OpenBSD (#476) + + Fix LLDP packets encapsuled in VLAN 0 in some conditions + + Fix heap overflow when reading SONMP. CVE-2021-43612. + Thanks to Jeremy Galindo for discovering this one. + +lldpd (1.0.12) + * Fix: + + Use a dedicated file lock to prevent concurrent changes from lldpcli. + + Require/display powerpairs for Dot3 power even when device type is PD. + * Changes: + + Provide a Docker image and make it available on GitHub. + +lldpd (1.0.11) + * Changes: + + Disable LLDP in firmware for Intel X7xx cards. + * Fix: + + Ensure Intel E8xx cards can transmit LLDP packets. + +lldpd (1.0.10) + * Fix: + + Fix chroot directory creation. + +lldpd (1.0.9) + * Fix: + + Do not use interface alias if we set it ourselves. + + More memory leak fixes on duplicate TLVs in LLDP, CDP and EDP + (related to CVE-2020-27827). + + On OSX, handle empty groups correctly when looking for a free UID/GID. + * Changes: + + Display port status with "show interfaces". + + Do not display "age" and "via" when using "show interfaces". + +lldpd (1.0.8) + * Fix: + + Out-of-bound read access when parsing LLDP-MED civic address in + liblldpctl for malformed fields. + + Fix memory leak when receiving LLDPU with duplicate fields. + CVE-2020-27827. + * Changes: + + Enable "router" capability bit when IPv6 routing is enabled. + +lldpd (1.0.7) + * Fix: + + Do not listen only to LLDP packets on Linux. When an interface + is enslaved to an Open vSwitch, incoming packets are missed. + +lldpd (1.0.6) + * Fix: + + Do not loose chassis local information when interface status changes. + + Fix SNMP walk on lldpRemTable when missing remote system + name or description. + + Remove length limitation on system description and platform. + * Changes: + + Deprecate use of lldpctl_watch_callback(). Use + lldpctl_watch_callback2() instead. + + Upgrade embedded libevent to 2.1.11-stable + + Add support of sending LLDP frames on a configured VLAN + +lldpd (1.0.5) + * Changes: + + Interface names are also matched for management addresses. + + On Linux, only register protocol handler for LLDP when only LLDP + is enabled. + + Stricter on LLDP incoming frames validation. + + Add support for VLAN-aware bridges for Linux (no range support). + + Add support for 802.3BT (no SNMP support). + + Add support for millisecond-grained tx-interval (Jean-Pierre Tosoni). + + Use generic names for VLAN names, instead of interface names (eg + vlan100 instead of eth1.100). + * Fix: + + Don't clear chassis TLV on shutdown LLDPDU. + + Don't require/display powerpairs for Dot3 power when device type + is PD. + +lldpd (1.0.4) + * Changes: + + Add "configure system max-neighbors XX" command to modify maximum + of neighbors accepted per port. + + Implement lldpRemOrgDefInfoTable table for custom TLVs. + * Fix: + + Better compliance for statsTLVsUnrecognizedTotal and + statsAgeoutsTotal counters. + + On Linux, handle rare blocking case in Netlink code. + +lldpd (1.0.3) + * Fix: + + Fix creation of chroot directory. + +lldpd (1.0.2) + * Changes: + + On Linux, the monitor process will now drop its privileges + instead of running as root. It will keep CAP_NET_RAW, + CAP_NET_ADMIN and CAP_DAC_OVERRIDE capabilities. + + Support for CDP PD PoE (with negotiation). Thanks to Gustav + Wiklander. + + Move support for bonded devices on Linux < 2.6.27 into the + `--enable-oldies` option. This avoids duplicate packets + starting from Linux 4.19. + +lldpd (1.0.1) + * Fix: + + Use "mkdir -p" instead of "mkdir" in systemd unit. + +lldpd (1.0.0) + * Changes: + + Chassis ID can be set to an arbitrary value with "configure system + chassisid". + + Port description can be overriden directly with "configure lldp + portdescription". + + Command "configure system interface permanent" enables one to + specify a pattern for interfaces to be kept in memory even when + they are removed from the system. + * Fix: + + Ensure chassis-related changes are propagated immediately. + + Ensure management address change is correctly detected. + +lldpd (0.9.9) + * Changes: + + lldpcli can now display local interfaces with LLDP data sent on + each of them ("show interfaces"). + + As Dot3 PD device, echo back allocated value from PSE device. + * Fix: + + Don't remove interfaces when they are released from a bridge. + + Don't use "expect stop" with Upstart. It's buggy. + +lldpd (0.9.8) + * Changes: + + "Station" capability is only set if no other bit is set. + + Use ethtool to get permanent address for bonds and teams. This + might provide different results than the previous method. Some + devices may still use the previous method. + + Don't run ethtool as root. Kernels older than 2.6.19 won't get + link information anymore. + + Add "configure system hostname ." option to not use a FQDN + for system name. + + Add "-f json0" to provide a more regular/machine-parsable output + to JSON output, even when not compiled with --enable-json0. + * Fixes: + + Handle team interfaces like a bond. Real MAC address cannot be + retrieved yet. + +lldpd (0.9.7) + * Changes: + + Attach remote TTL to port instead of chassis. + + JSON support is now built-in and unconditionally enabled. Use + --enable-json0 to keep the pre-0.9.2 json-c format. + + When logging to syslog and daemonizing, don't log to stderr. + + vxlan interfaces are now ignored as they are multi-point interfaces. + + Maximum number of neighbors for an interface is increased from 4 to 32. + +lldpd (0.9.6) + * Changes: + + Add a compile-time option to restore pre-0.9.2 JSON format (when + using json-c). Use `--enable-json0` to enable this option. + + Support for newer ethtool interface on Linux + (ETHTOOL_GLINKSETTINGS) and additional speed settings. + + Current MAU type is displayed even when autoneg is off. + + Increase netlink receive buffer by default. Can be changed at + compile-time through ./configure. + * Fixes: + + Correctly parse LLDP-MED civic address when the length of the + TLV exceeds the length of the address. + + Fix 100% CPU on some rare error condition. + + Fix lost timer when an interface is enslaved on Linux. + +lldpd (0.9.5) + * Changes: + + More Ethernet media supported. However, RFC4836 is quite + out-of-date with respected to 10G+ speeds, bringing some + inaccuracies. + + Directly get media information for an interface without using + the privileged process. + + LLDP-MED capability TLV is not sent when LLDP-MED is not enabled, + even if other LLDP-MED TLV are present. + * Fixes: + + Compilation fix with older versions of GCC. + + Don't use ethtool at all to get real MAC address for enslaved + devices (always use /proc). + +lldpd (0.9.4) + * Changes: + + Make lldpd accepts a `-p` option to specify the PID file. + + Ability to change multicast MAC address to two additional values + to reach customer bridges. + + lldpcli will now display chassis TTL when detailed view is enabled. + * Fixes: + + Fix setting of local value for port ID. + + Fix compilation with BSD make. + + Ensure lldpcli returns an error code on invalid commands. + +lldpd (0.9.3) + * Changes: + + Do not rely on support of constructors for liblldpctl. + + Always log to stderr (even in addition to syslog). + + `lldpcli watch` accepts a limit on the number of received events. + * Fixes: + + `lldpcli -f {xml,json} watch` should work now. + + Consider `veth` interfaces as physical interfaces. + +lldpd (0.9.2) + * Changes: + + Ability to add/remove/replace custom TLV from lldpcli. + + LLDP-MED capabilities are displayed differently in lldpcli. + + Limit the maximum depth (5) when trying to apply a VLAN. + + Change JSON output format when using json-c to match Jansson + output. + + Integration tests for the major parts of lldpd, including use of + address and leak sanitizer. + * Fixes: + + LLDP-MED POE TLV are now displayed in lldpcli. + + Ignore lower link when it is in another namespace. + + Fix various problems with interfaces being enslaved. + + Fix a memory leak when modifying port-related settings. + +lldpd (0.9.1) + * Changes: + + Rework packaging for OS X to make it work with El Capitan. To + simplify a bit, it is not possible anymore to build fat + binaries. Latest version of OS X supporting 32bit was 10.6. + * Fixes: + + By default, when using port alias as description, use port name + as port ID. + + Miscellaneous fixes with netlink cache. + + Ensure large netlink messages can be received. + +lldpd (0.9.0) + * Changes: + + Don't rely on libnl3 for netlink. Reuse the previous code and + implement a lighweight cache. + +lldpd (0.8.0, never released) + * Changes: + + PIE is now disabled by default. It's too difficult to reliably + detect if it works. Use --enable-pie to enable it. + + Retrieve the permanent MAC address of an interface through + ethtool for Linux if /proc/net/bonding is not available. + + Running lldpd with "-d" will keep the process in foreground but + logs will still go to syslog. To log to the console, add at + least one "-d". + + Fix minimal kernel version to 2.6.39. Add a runtime warning when + this is not the case. + + Remove old bridge code (the one using ioctl). + + Don't discard down interfaces. Notably, this enables us to keep + their specific configuration if any. + + For Linux, switch to libnl3. Be aware of the licensing issues in + case of static linking. + + Introduce the notion of default local port. New interfaces will + use it as a base. This allows setting various MED stuff. + + Provide an apparmor profile (untested). + * Fixes: + + Fix a buffer overflow when receiving a too large management + address TLV. Unless hardening has been disabled, this overflow + cannot be used for arbitrary code execution. + + Update LLDP-MED policy L2 priority values to match + 802.1Q-2005. This may be a breaking change. + +lldpd (0.7.17) + * Fixes: + + Fix the way libevent configure is called. + + Fix an infinite loop when using veth on Linux 4.1+ kernels. + + Make CDP advertise the appropriate kernel name as platform, + not just "Linux". + +lldpd (0.7.16) + * Changes: + + For Linux, 2.6.32 is now the minimal required kernel. When using + an older kernel, use `--enable-oldies`. + + For Linux, use netlink to retrieve information about bridges, + VLAN and bonds. The code was contributed by Cumulus Networks. + + Use symbol versioning for liblldpctl.so. + + Ability to get local chassis information with "show + chassis". + + The library also has the same ability with the + `lldpctl_get_local_chassis()` function. It is also possible to + get a chassis atom from a port with `lldpctl_k_port_chassis` + key. This is now the preferred way to retrieve chassis related + information. + * Fixes: + + Fix build on OS X. + + Accept "language" when configuring MED location as a civic address. + +lldpd (0.7.15) + * Changes: + + Optional features can be configured with "auto" to autodetect if + they are usable. This is the default value for JSON and XML support. + + Ability to send and decode custom/unknown TLV. Thanks to Alexandru + Ardelean. + + Modify checksum function. While this should be strictly + equivalent, if you notice CDP packets not accepted anymore, this + change is the first culprit. + +lldpd (0.7.14) + * Changes: + + Shutdown LLPDU are sent on MSAP change and when lldpd exits. + + When an exact IP is provided as a management pattern, use it + unconditionally. + + Ability to set port ID and description to an arbitrary value, + thanks to Alexandru Ardelean. + * Fixes: + + Incorrect boundary check when decoding management address and + protocol identity may lead to lldpd crash when processing + malformed LLDPDU. + + Many edge cases where lldpd was leaving hanging processes after + crashing. + +lldpd (0.7.13) + * Fixes: + + Unbreak customization of Unix socket path from command line. + +lldpd (0.7.12) + * Changes: + + Interface pattern, management pattern, system description, + system platform and system hostname can be unconfigured to their + default values. + * Fixes: + + Don't complain when parsing a commented line. + + Correctly persist configuration changes for "system interface + promiscuous", "system interface description" "med fast-start + enable", "pause" and "resume". + + Fix listening on bond devices for old kernels (< 2.6.27). + +lldpd (0.7.11) + * Changes: + + Ship bash and zsh completion. + + Abort when some command-line options are repeated. + * Fixes: + + Handle correctly read failures in liblldpctl. + +lldpd (0.7.10) + * Changes: + + Ability to set promiscuous mode to work around bugs of some + switches encapsulating LLDP frames inside 802.1Q frames. + + JSON support for lldpcli can use json-c instead of jansson, + thanks to Michel Stam. + * Fixes: + + Fix checksum computation for Cisco CDP. + + Fix ability to disable LLDP. + + Fix seccomp sandbox, thanks to Patrick McLean. + +lldpd (0.7.9) + * Changes: + + Default location for chroot, socket and PID are now configurable + in `./configure`. The default location is based on the value of + `runstatedir` which in turn may be based on the value of + `localstatedir` which defaults to `/usr/local/var`. Therefore, + to get the previous locations, lldpd should be configured with + `./configure --localstatedir=/var`. + + Add support for shutdown LLDPU. + + Ability to configure IP management pattern from lldpcli. + + Ability to choose what port ID should be (MAC or interface name). + * Fixes: + + Fix `configure system bond-slave-src-mac-type local`. Also use + it as default. + +lldpd (0.7.8) + * Changes: + + Android support + + Add the possibility to disable privilege separation (lower + memory consumption, lower security, don't do it). + + Interfaces can now be whitelisted. For example, *,!eth*,!!eth1 + is a valid pattern for all interfaces except eth ones, except + eth1. Moreover, on exact match, an matching interface + circumvents most sanity checks (like VLAN handling). + + Ability to override the hostname. + * Fixes: + + Don't hard-code default values for system name, system + description and port description. When the field is not present, + just don't display it. + + Fix lldpcli behaviour when suid. + + On OSX, don't use p2p0 interfaces: it would break WLAN. + + Fix SNMP support on RHEL. + +lldpd (0.7.7) + * Changes: + + Use a locally administered MAC address or an arbitrary one + instead of null MAC address for bond devices on Linux. This is + configurable through `lldpcli`. + + Add support for "team" driver (alternative to bond devices). + + Preliminary support for DTrace/systemtap. + + Preliminary support for seccomp (for monitor process). + + Setup chroot inside lldpd instead of relying on init script. + * Fixes: + + Various bugs related to fixed point number handling (for + coordinates in LLDP-MED) + + Fix a regression in how MAC address of an enslaved device is + retrieved. + +lldpd (0.7.6) + * Changes: + + Provide a way to build packages for OSX. + + Add an option to update interface description with neighbor name. + * Fixes: + + Compilation fix for OSX 10.6. + +lldpd (0.7.5) + * Fixes: + + Segfault while tokenizing in lldpcli. + +lldpd (0.7.4) + * Fixes: + + Segfault in lldpcli. + + Memory leak in liblldpctl when using a custom log handler. + + Fix some unaligned memory accesses. + + Fix frame reception on OpenBSD. + * Changes: + + Allow to configure hold value from lldpcli (and hence the TTL). + + Allow to configure pattern for valid interfaces from lldpcli. + + Allow to override system description from lldpcli. + + Display the neighbor connected as the process title (or the + number of connected neighbors). + +lldpd (0.7.3) + * Changes: + + DragonFly BSD support. + + Solaris support (incomplete). + + LLDP-MED fast start support (thanks to Roopa Prabhu). + + Provide global statistics through "show statistics summary" + command (thanks to Roopa Prabhu). + * Fixes: + + Fix IPv4/IPv6 address discovery in Linux. + +lldpd (0.7.2) + * Changes: + + lldpd can be configured through /etc/lldpd.conf and + /etc/lldpd.d. All commands accepted by lldpcli are accepted. + + Lock BPF interfaces before handing them to chrooted process on + BSD. + + Limit the number of neighbors for each port to 4 (per protocol). + + Force CDPv2 protocol with argument `-ccc`. + + Provide port statistics through "show statistics" command + (thanks to Roopa Prabhu). + * Fixes: + + Driver whitelisting is done before checking if an interface has + a lower interface in Linux. + + Expire remote ports and chassis in a timely manner. + +lldpd (0.7.1) + * Changes: + + Mac OS X support, sponsored by Xcloud, Mac cloud server hosting + provider. http://xcloud.me/ + + Upstart and systemd support. + + Remove Unix socket when there is no process listening. + +lldpd (0.7.0) + * Changes: + + FreeBSD support. + + OpenBSD support. + + NetBSD support. + + Detect interface changes. + + CLI for lldpctl: lldpcli. + + Allow to disable LLDP protocol (with `-ll`). In this case, the + first enabled protocol will be used when no neighbor is detected. + + Allow to filter debug logs using tokens. Add more debug logs. + + lldpctl can now output JSON. + + Use netlink to gather interface information on Linux. + + Don't use ioctl for bridges anymore on Linux. The configure + option `--enable-oldies` allow to reenable their uses for + systems not supporting sysfs. + +lldpd (0.6.1) + * Changes: + + Provide liblldpctl.so, a library to interface with lldpd. The + documentation is provided through Doxygen. See src/lib/lldpctl.h + which contains all the exported functions. + + Make lldpctl uses liblldpctl.so. + + Add a "watch" option to lldpctl to monitor neighbor changes. + + Add the possibility to display the current configuration of + lldpd with lldpctl. Also add the possibility to reset the + current transmit delay. + +lldpd (0.6) + * Changes: + + Allow lldpctl to display hidden ports. + + Add a switch to specify interfaces to use to get chassis ID. + + Support for multiple management addresses and IPv6 management + addresses. Contributed by João Valverde. + + Switch to libevent. See README.md for details. + + Partial rewrite of the SNMP part. Less code. + + Unit tests for SNMP. + + Major rewrite of the protocol between lldpd and lldpctl. Less + code. + * Fixes: + + Several small SNMP fixes (discovered by unit tests). + +lldpd (0.5.7) + * Fixes: + + Configure issue with NetSNMP and some linkers + + Fix infinite loop for the receive part: on certain conditions, + lldpd will stop sending packets and stop updating local data. + +lldpd (0.5.6) + * Changes: + + Send and receive native VLAN TLV with CDP + + Add a whitelist for some drivers (currently: dsa and veth) + * Fixes: + + Compilation issues with NetSNMP 5.7 (and with earlier versions too) + + Small optimization of BPF filter + +lldpd (0.5.5) + * Changes: + + Support for PPVID and PI Dot1 TLV, thanks to Shuah Khan. + + Extend whitelist with possibility to blacklist. + * Fixes: + + Key/value output was incorrect when a dot was present in + interface names. This is fixed but it is preferable to use XML + output since the parsing is more difficult in this case. + + Only grab DMI information once. Only uses DMI for x86 platform. + + Padding issues with socket protocol. This introduces a change in + the socket protocol! + + Fix a segfault when neither /etc/os-release nor lsb_release + are available. + +lldpd (0.5.4) + * Changes: + + Get OS information from /etc/os-release if available. Patch from + Michael Tremer. + + Add a flag to specify which interfaces lldpd should listen to. + +lldpd (0.5.3) + * Changes: + + Handle Dot3 POE-MDI TLV (802.3af and 802.3at). + + Allow to set Dot3 POE-MDI from lldpctl. + * Fixes: + + Allow root to change configuration of lldpd when lldpctl has suid set. + +lldpd (0.5.2) + * Changes: + + More flexible smart mode and new default. Manual page has been updated. + + Add a "receive-only" mode with "-r" switch. + +lldpd (0.5.1) + * Changes: + + Allow to force a protocol even when no peer for this protocol is + detected. + + Add a smart mode that allows to discard bogus port information, + for example CDP packets that are flooded through a switch that + does not support CDP. + + Allow to set LLDP-MED network policy from lldpctl, thanks to a patch from + Philipp Kempgen. + + Allow to set LLDP-MED POE-MDI from lldpctl. + + Add a summary of available options in "lldpd -h" and "lldpctl -h", + thanks to a patch from Jorge Boncompte. + + Add a new output (keyvalue) for lldpctl. + + Listen on VLAN using an appropriate BPF filter, VLAN + decapsulation. Older "listen on vlan" feature is discarded. See + README for more information on the new feature. + + Use output of lsb_release if available for system description. + * Fixes: + + Ignore interface with no queue. It should filter out interfaces + like "vnet0" that would fail if we try to send something on them. + + Don't check CDP checksums (not really a fix but it appears that + Cisco checksum have some difficult corner cases). + +lldpd (0.5.0) + * Changes: + + lldpd can now handle several systems on the same port. This + modification also allows to speak to a switch using CDP and LLDP + for example. + + The way that lldpd gathers information for each port has been + abstracted. This should allow to support more systems (BSD for + examples) or switch cores in the future. Sending/receive support + is also abstracted. + + Add "-k" switch to avoid to emit too much information on running + kernel. + + Support of ifAlias with kernel >= 2.6.28 + + Lot of portability stuff. lldpd can now be compiled on RHEL + 2.1. Still Linux-only though. + + Add an option to specify AgentX socket (-X). + + Add some unit tests + + lldpctl has been reworked; it is now able to output data in XML + format for easier parsing. Patches were provided by Andreas + Hofmeister. + * Fixes: + + Fix EDP VLAN handling + + Silent warnings about bridge stuff. + + Copy /etc/localtime into chroot before starting lldpd daemon to + ensure correct timestamps for logs. + +lldpd (0.4.1) + * Fix EDP handling when there is no VLAN + * Fix CDP version to not always be 1 + * Misc fix: + + incorrect number of arguments for a LLOG_INFO call + + fix SNMP last change in case this change occurs before start time + +lldpd (0.4) + * Rewrite of packet builder and parser to be able to cope with + architecture that cannot do unaligned read. For decoder, we don't + cast structures any more since they can be unaligned. For encoder, + we use memcpy through the use of macro that build packets step by + step. + +lldpd (0.3.2) + * Fix LLDP-MED support + +lldpd (0.3.1) + * Misc fixes, including memory leaks + +lldpd (0.3) + * Initial support of LLDP-MED + * Fix for bridge detection (don't send bridge ioctl on random interfaces) + * For bonded devices, get the real hardware address. For inactive + slaves, transmit using a random MAC address. + +lldpd (0.2.1) + * Fix a syntax error in manual page + * Fix open() calls + +lldpd (0.2) + * Add privilege separation + * Add FDP support + * Support CDP encapsulated into native VLAN + * Various fixes + +lldpd (0.1) + * Initial release diff --git a/README.en.md b/README.en.md deleted file mode 100644 index 8f59c11509dff48537aefdf86c97fe919b78c91e..0000000000000000000000000000000000000000 --- a/README.en.md +++ /dev/null @@ -1,36 +0,0 @@ -# ub-lldpd - -#### Description -ub-lldpd is an ISC-licensed implementation of Linux LLDP for ub device. - -#### Software Architecture -Software architecture description - -#### Installation - -1. xxxx -2. xxxx -3. xxxx - -#### Instructions - -1. xxxx -2. xxxx -3. xxxx - -#### Contribution - -1. Fork the repository -2. Create Feat_xxx branch -3. Commit your code -4. Create Pull Request - - -#### Gitee Feature - -1. You can use Readme\_XXX.md to support different languages, such as Readme\_en.md, Readme\_zh.md -2. Gitee blog [blog.gitee.com](https://blog.gitee.com) -3. Explore open source project [https://gitee.com/explore](https://gitee.com/explore) -4. The most valuable open source project [GVP](https://gitee.com/gvp) -5. The manual of Gitee [https://gitee.com/help](https://gitee.com/help) -6. The most popular members [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/) diff --git a/README.md b/README.md index d1739bdd2e4a575e2f9d87bfe2460a550375422a..6f350b16234d7f77c519de73406786b985b2528a 100644 --- a/README.md +++ b/README.md @@ -1,37 +1,426 @@ -# ub-lldpd +# lldpd: implementation of IEEE 802.1ab (LLDP) -#### 介绍 -ub-lldpd is an ISC-licensed implementation of Linux LLDP for ub device. +![Build Status](https://github.com/lldpd/lldpd/workflows/CI/badge.svg) -#### 软件架构 -软件架构说明 + https://lldpd.github.io/ +## Features -#### 安装教程 +LLDP (Link Layer Discovery Protocol) is an industry standard protocol +designed to supplant proprietary Link-Layer protocols such as +Extreme's EDP (Extreme Discovery Protocol) and CDP (Cisco Discovery +Protocol). The goal of LLDP is to provide an inter-vendor compatible +mechanism to deliver Link-Layer notifications to adjacent network +devices. -1. xxxx -2. xxxx -3. xxxx +lldpd implements both reception and sending. It also implements an +SNMP subagent for net-snmp to get local and remote LLDP +information. The LLDP-MIB is partially implemented but the most useful +tables are here. lldpd also partially implements LLDP-MED. -#### 使用说明 +lldpd supports bridge, vlan and bonding. -1. xxxx -2. xxxx -3. xxxx +The following OS are supported: -#### 参与贡献 + * FreeBSD + * GNU/Linux + * macOS + * NetBSD + * OpenBSD + * Solaris -1. Fork 本仓库 -2. 新建 Feat_xxx 分支 -3. 提交代码 -4. 新建 Pull Request +Windows is not supported but you can use +[WinLLDPService](https://github.com/raspi/WinLLDPService/) as a +transmit-only agent. +## Installation -#### 特技 +For general instructions [prefer the +website](https://lldpd.github.io/installation.html), +including building from released tarballs. -1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md -2. Gitee 官方博客 [blog.gitee.com](https://blog.gitee.com) -3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解 Gitee 上的优秀开源项目 -4. [GVP](https://gitee.com/gvp) 全称是 Gitee 最有价值开源项目,是综合评定出的优秀开源项目 -5. Gitee 官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help) -6. Gitee 封面人物是一档用来展示 Gitee 会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/) +To compile lldpd from Git, use the following commands: + + ./autogen.sh + ./configure + make + sudo make install + +lldpd uses privilege separation to increase its security. Two +processes, one running as root and doing minimal stuff and the other +running as an unprivileged user into a chroot doing most of the stuff, +are cooperating. You need to create a user called `_lldpd` in a group +`_lldpd` (this can be change with `./configure`). You also need to +create an empty directory `/usr/local/var/run/lldpd` (it needs to be +owned by root, not `_lldpd`!). If you get fuzzy timestamps from +syslog, copy `/etc/locatime` into the chroot. + +`lldpcli` lets one query information collected through the command +line. If you don't want to run it as root, just install it setuid or +setgid `_lldpd`. + +## Installation (Docker) + +You can use Docker to run `lldpd`: + + docker run --rm --net=host --uts=host \ + -v /etc/os-release:/etc/os-release \ + --cap-add=NET_RAW --cap-add=NET_ADMIN \ + --name lldpd \ + ghcr.io/lldpd/lldpd:latest + +In place of `latest` which provides you with the latest stable +version, you may use `1`, `1.0`, `1.0.12` to match specific versions, +or `master` to get the development version. + +To execute `lldpcli`, use: + + docker exec lldpd lldpcli show neighbors + +Or to get the command-line: + + docker exec -it lldpd lldpcli + +## Installation (macOS) + +The same procedure as above applies for macOS. However, there are +simpler alternatives: + + 1. Use [Homebrew](https://brew.sh): + + brew install lldpd + # Or, for the latest version: + brew install https://raw.github.com/lldpd/lldpd/master/osx/lldpd.rb + + 2. Build an macOS installer package which should work on the same + version of macOS: + + mkdir build && cd build + ../configure --prefix=/usr/local --localstatedir=/var --sysconfdir=/private/etc --with-embedded-libevent \ + --without-snmp + make -C osx pkg + + If you want to compile for an older version of macOS, you need + to find the right SDK and issues commands like those: + + SDK=/Developer/SDKs/MacOSX10.6.sdk + mkdir build && cd build + ../configure --prefix=/usr/local --localstatedir=/var --sysconfdir=/private/etc --with-embedded-libevent \ + --without-snmp \ + CFLAGS="-mmacosx-version-min=10.6 -isysroot $SDK" \ + LDFLAGS="-mmacosx-version-min=10.6 -isysroot $SDK" + make -C osx pkg + + With recent SDK, you don't need to specify an alternate SDK. They + are organized in a way that should enable compatibility with older + versions of OSX: + + mkdir build && cd build + ../configure --prefix=/usr/local --localstatedir=/var --sysconfdir=/private/etc --with-embedded-libevent \ + --without-snmp \ + CFLAGS="-mmacosx-version-min=10.9" \ + LDFLAGS="-mmacosx-version-min=10.9" + make -C osx pkg + + You can check with `otool -l` that you got what you expected in + term of supported versions. + +If you don't follow the above procedures, you will have to create the +user/group `_lldpd`. Have a look at how this is done in +`osx/scripts/postinstall`. + +## Installation (Android) + +1. Don't clone the repo or download the master branch from GitHub. Instead, download the official release from the website [https://lldpd.github.io/](https://lldpd.github.io/installation.html#install-from-source). Unpack into a working directory. + +2. Download the [Android NDK](https://developer.android.com/ndk/downloads#stable-downloads) (version 22 or later). Unpack into a working directory next to the `lldpd` directory. + +3. Install `automake`, `libtool`, and `pkg-config`. (`sudo apt-get install automake libtool pkg-config`) + +4. In the root of the `lldpd` directory, make a `compile.sh` file containing this script: +```sh +export TOOLCHAIN=$PWD/android-ndk/toolchains/llvm/prebuilt/linux-x86_64 +export TARGET=armv7a-linux-androideabi +export API=30 +# DO NOT TOUCH BELOW +export AR=$TOOLCHAIN/bin/llvm-ar +export CC=$TOOLCHAIN/bin/$TARGET$API-clang +export CXX=$TOOLCHAIN/bin/$TARGET$API-clang++ +export LD=$TOOLCHAIN/bin/ld +export RANLIB=$TOOLCHAIN/bin/llvm-ranlib +export STRIP=$TOOLCHAIN/bin/llvm-strip +export AS=$CC +./autogen.sh +mkdir -p build && cd build +../configure \ + --host=$TARGET \ + --with-sysroot=$TOOLCHAIN/sysroot \ + --prefix=/system \ + --sbindir=/system/bin \ + --runstatedir=/data/data/lldpd \ + --with-privsep-user=root \ + --with-privsep-group=root \ + PKG_CONFIG=/bin/false +make +make install DESTDIR=$PWD/install +``` + +5. In the **Android NDK** directory, locate the `toolchains/llvm/prebuilt/linux-x86_64` directory and change the `TOOLCHAIN` variable of the above script to match the path where the `linux-x86_64` directory resides. + +```sh +export TOOLCHAIN=$PWD/android-ndk-r22b-linux-x86_64/android-ndk-r22b/toolchains/llvm/prebuilt/linux-x86_64 +``` + +6. Determine the CPU architecture target (`adb shell getprop ro.product.cpu.abi`). Change the `TARGET` variable in the above script to match the target architecture. The target name will not exactly match the output of the `adb` command as there will be a trailing suffix to the target name, so look in the `linux-x86_64/bin` directory for the `clang` file that starts with the CPU architecture target. Don't include the API version in the target name. +```sh +$ adb shell getprop ro.product.cpu.abi +armeabi-v7a +``` +```sh +linux-x86_64/bin$ ls *-clang +aarch64-linux-android21-clang armv7a-linux-androideabi23-clang i686-linux-android26-clang +aarch64-linux-android22-clang armv7a-linux-androideabi24-clang i686-linux-android27-clang +aarch64-linux-android23-clang armv7a-linux-androideabi26-clang i686-linux-android28-clang +aarch64-linux-android24-clang armv7a-linux-androideabi27-clang i686-linux-android29-clang +aarch64-linux-android26-clang armv7a-linux-androideabi28-clang i686-linux-android30-clang +aarch64-linux-android27-clang armv7a-linux-androideabi29-clang x86_64-linux-android21-clang +aarch64-linux-android28-clang armv7a-linux-androideabi30-clang x86_64-linux-android22-clang +aarch64-linux-android29-clang i686-linux-android16-clang x86_64-linux-android23-clang +aarch64-linux-android30-clang i686-linux-android17-clang x86_64-linux-android24-clang +armv7a-linux-androideabi16-clang i686-linux-android18-clang x86_64-linux-android26-clang +armv7a-linux-androideabi17-clang i686-linux-android19-clang x86_64-linux-android27-clang +armv7a-linux-androideabi18-clang i686-linux-android21-clang x86_64-linux-android28-clang +armv7a-linux-androideabi19-clang i686-linux-android22-clang x86_64-linux-android29-clang +armv7a-linux-androideabi21-clang i686-linux-android23-clang x86_64-linux-android30-clang +armv7a-linux-androideabi22-clang i686-linux-android24-clang +``` +```sh +export TARGET=armv7a-linux-androideabi +``` + +7. Set the `API` variable in the script above to your target API version. Check in the same `linux-x86_64/bin` to ensure the API you are targeting has a supported `clang` file for that CPU architecture and version. As of this writing, there is support for API `21-30` included for all architectures and some CPU architectures supported back to version `16`. +```sh +export API=30 +``` + +8. Run the compile script (`./compile.sh`). + +9. Copy the `./bin/*` and `./lib/*.so` files from `lldpd/build/install/system` to the target system (`./bin/*` to `/system/bin`, `./lib/*.so` to `/system/lib64`): +```sh +# Push files to target +cd build/install/system +adb shell mkdir -p /sdcard/Download/lldpd/bin +adb push bin/lldpcli /sdcard/Download/lldpd/bin/lldpcli +adb push bin/lldpd /sdcard/Download/lldpd/bin/lldpd +adb shell mkdir -p /sdcard/Download/lldpd/lib64 +adb push lib/liblldpctl.so /sdcard/Download/lldpd/lib64/liblldpctl.so + +# Enter target shell and move files +adb shell +# Run as root for all commands +$ su +# Make /system writeable +$ mount -o rw,remount /system +$ mv /sdcard/Download/lldpd/bin/lldpcli /system/bin/lldpcli +$ chmod 755 /system/bin/lldpcli +$ chown root:shell /system/bin/lldpcli +$ mv /sdcard/Download/lldpd/bin/lldpd /system/bin/lldpd +$ chmod 755 /system/bin/lldpd +$ chown root:shell /system/bin/lldpd +# $ touch /system/bin/lldpctl +# $ chmod 755 /system/bin/lldpctl +# $ chown root:shell /system/bin/lldpctl +$ mv /sdcard/Download/lldpd/lib64/liblldpctl.so /system/lib64/liblldpctl.so +$ chmod 644 /system/lib64/liblldpctl.so +$ chown root:root /system/lib64/liblldpctl.so +# Make /system readonly again +$ mount -o ro,remount /system +# Might not be necessary on some systems +$ mkdir /data/data/lldpd +$ chmod 700 /data/data/lldpd +$ chown shell:shell /data/data/lldpd +# Clean up +$ rm -rf /sdcard/Download/lldpd +``` + +## Usage + +lldpd also implements CDP (Cisco Discovery Protocol), FDP (Foundry +Discovery Protocol), SONMP (Nortel Discovery Protocol) and EDP +(Extreme Discovery Protocol). However, recent versions of IOS should +support LLDP and most Extreme stuff support LLDP. When a EDP, CDP or +SONMP frame is received on a given interface, lldpd starts sending +EDP, CDP, FDP or SONMP frame on this interface. Informations collected +through EDP/CDP/FDP/SONMP are integrated with other informations and +can be queried with `lldpcli` or through SNMP. + +More information: + * http://en.wikipedia.org/wiki/Link_Layer_Discovery_Protocol + * http://standards.ieee.org/getieee802/download/802.1AB-2005.pdf + * https://gitlab.com/wireshark/wireshark/-/wikis/LinkLayerDiscoveryProtocol + +## Compatibility with older kernels + +If you have a kernel older than Linux 2.6.39, you need to compile +lldpd with `--enable-oldies` to enable some compatibility functions: +otherwise, lldpd will only rely on Netlink to receive bridge, bond and +VLAN information. + +For bonding, you need 2.6.24 (in previous version, PACKET_ORIGDEV +affected only non multicast packets). See: + + * http://git.kernel.org/?p=linux/kernel/git/torvalds/linux-2.6.git;a=commitdiff;h=80feaacb8a6400a9540a961b6743c69a5896b937 + * http://git.kernel.org/?p=linux/kernel/git/torvalds/linux-2.6.git;a=commitdiff;h=8032b46489e50ef8f3992159abd0349b5b8e476c + +Otherwise, a packet received on a bond will be affected to all +interfaces of the bond. In this case, lldpd will affect a received +randomly to one of the interface (so a neighbor may be affected to the +wrong interface). + +On 2.6.27, we are able to receive packets on real interface for enslaved +devices. This allows one to get neighbor information on active/backup +bonds. Without the 2.6.27, lldpd won't receive any information on +inactive slaves. Here are the patchs (thanks to Joe Eykholt): + + * http://git.kernel.org/?p=linux/kernel/git/torvalds/linux-2.6.git;a=commit;h=0d7a3681232f545c6a59f77e60f7667673ef0e93 + * http://git.kernel.org/?p=linux/kernel/git/torvalds/linux-2.6.git;a=commit;h=cc9bd5cebc0825e0fabc0186ab85806a0891104f + * http://git.kernel.org/?p=linux/kernel/git/torvalds/linux-2.6.git;a=commit;h=f982307f22db96201e41540295f24e8dcc10c78f + +On FreeBSD, only a recent 9 kernel (9.1 or more recent) will allow to +send LLDP frames on enslaved devices. See this bug report for more +information: + + * http://www.freebsd.org/cgi/query-pr.cgi?pr=138620 + +Some devices (notably Cisco IOS) send frames tagged with the native +VLAN while they should send them untagged. If your network card does +not support accelerated VLAN, you will receive those frames as long as +the corresponding interface exists (see below). However, if your +network card handles VLAN encapsulation/decapsulation (check with +`ethtool -k`), you need a recent kernel to be able to receive those +frames without listening on all available VLAN. Starting from Linux +2.6.27, lldpd is able to capture VLAN frames when VLAN acceleration is +supported by the network card. Here is the patch: + + * http://git.kernel.org/?p=linux/kernel/git/torvalds/linux-2.6.git;a=commit;h=bc1d0411b804ad190cdadabac48a10067f17b9e6 + +On some other versions, frames are sent on VLAN 1. If this is not the +native VLAN and if your network card support accelerated VLAN, you +need to subscribe to this VLAN as well. The Linux kernel does not +provide any interface for this. The easiest way is to create the VLAN +for each port: + + ip link add link eth0 name eth0.1 type vlan id 1 + ip link set up dev eth0.1 + +You can check both cases using tcpdump: + + tcpdump -epni eth0 ether host 01:80:c2:00:00:0e + tcpdump -eni eth0 ether host 01:80:c2:00:00:0e + +If the first command does not display received LLDP packets but the +second one does, LLDP packets are likely encapsulated into a VLAN: + + 10:54:06.431154 f0:29:29:1d:7c:01 > 01:80:c2:00:00:0e, ethertype 802.1Q (0x8100), length 363: vlan 1, p 7, ethertype LLDP, LLDP, name SW-APP-D07.VTY, length 345 + +In this case, just create VLAN 1 will fix the situation. There are +other solutions: + + 1. Disable VLAN acceleration on the receive side (`ethtool -K eth0 + rxvlan off`) but this may or may not work. Check if there are + similar properties that could apply with `ethtool -k eth0`. + 2. Put the interface in promiscuous mode with `ip link set + promisc on dev eth0`. + +The last solution can be done directly by `lldpd` (on Linux only) by +using the option `configure system interface promiscuous`. + +On modern networks, the performance impact should be nonexistent. + +## Development + +During development, you may want to execute lldpd at its current +location instead of doing `make install`. The correct way to do this is +to issue the following command: + + sudo libtool execute src/daemon/lldpd -L $PWD/src/client/lldpcli -d + +You can append any further arguments. If lldpd is unable to find +`lldpcli` it will start in an unconfigured mode and won't send or +accept LLDP frames. + +You can use [afl](http://lcamtuf.coredump.cx/afl/) to test some +aspects of lldpd. To test frame decoding, you can do something like +that: + + export AFL_USE_ASAN=1 # only on 32bit arch + ./configure CC=afl-gcc + make clean check + cd tests + mkdir inputs + mv *.pcap inputs + afl-fuzz -i inputs -o outputs ./decode @@ + +There is a general test suite with `make check`. It's also possible to +run integration tests. They need [pytest](http://pytest.org/latest/) +and rely on Linux containers to be executed. + +To enable code coverage, use: + + ../configure --prefix=/usr --sysconfdir=/etc --localstatedir=/var \ + --enable-sanitizers --enable-gcov --with-snmp \ + CFLAGS="-O0 -g" + make + make check + # maybe, run integration tests + lcov --base-directory $PWD/src/lib \ + --directory src --capture --output-file gcov.info + genhtml gcov.info --output-directory coverage + +## Embedding + +To embed lldpd into an existing system, there are two point of entries: + + 1. If your system does not use standard Linux interface, you can + support additional interfaces by implementing the appropriate + `struct lldpd_ops`. You can look at + `src/daemon/interfaces-linux.c` for examples. Also, have a look at + `interfaces_update()` which is responsible for discovering and + registering interfaces. + + 2. `lldpcli` provides a convenient way to query `lldpd`. It also + comes with various outputs, including XML which allows one to + parse its output for integration and automation purpose. Another + way is to use SNMP support. A third way is to write your own + controller using `liblldpctl.so`. Its API is described in + `src/lib/lldpctl.h`. The custom binary protocol between + `liblldpctl.so` and `lldpd` is not stable. Therefore, the library + should always be shipped with `lldpd`. On the other hand, programs + using `liblldpctl.so` can rely on the classic ABI rules. + +## Troubleshooting + +You can use `tcpdump` to look after the packets received and send by +`lldpd`. To look after LLDPU, use: + + tcpdump -s0 -vv -pni eth0 ether dst 01:80:c2:00:00:0e + +## License + +lldpd is distributed under the ISC license: + + > 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. + +Also, `lldpcli` will be linked to GNU Readline (which is GPL licensed) +if available. To avoid this, use `--without-readline` as a configure +option. diff --git a/autogen.sh b/autogen.sh new file mode 100755 index 0000000000000000000000000000000000000000..f58af72f6b5ca67b37fc5e1bdcae362208fc99bd --- /dev/null +++ b/autogen.sh @@ -0,0 +1,88 @@ +#!/bin/sh + +set -e + +[ ! -e .gitmodules ] || [ ! -e .git ] || { + echo "autogen.sh: updating git submodules" + git submodule init + git submodule update +} + +case "$(uname)" in + Darwin) + LIBTOOLIZE=${LIBTOOLIZE:-glibtoolize} + ;; + *) + LIBTOOLIZE=${LIBTOOLIZE:-libtoolize} + ;; +esac +AUTORECONF=${AUTORECONF:-autoreconf} +ACLOCAL=${ACLOCAL:-aclocal} +AUTOCONF=${AUTOCONF:-autoconf} +AUTOHEADER=${AUTOHEADER:-autoheader} +AUTOMAKE=${AUTOMAKE:-automake} + +# Check we have all tools installed +check_command() { + command -v "${1}" > /dev/null 2>&1 || { + >&2 echo "autogen.sh: could not find \`$1'. \`$1' is required to run autogen.sh." + exit 1 + } +} +check_command "$LIBTOOLIZE" +check_command "$AUTORECONF" +check_command "$ACLOCAL" +check_command "$AUTOCONF" +check_command "$AUTOHEADER" +check_command "$AUTOMAKE" + +# Absence of pkg-config or misconfiguration can make some odd error +# messages, we check if it is installed correctly. See: +# https://blogs.oracle.com/mandy/entry/autoconf_weirdness +# +# We cannot just check for pkg-config command, we need to check for +# PKG_* macros. The pkg-config command can be defined in ./configure, +# we cannot tell anything when not present. +check_pkg_config() { + grep -q '^AC_DEFUN.*PKG_CHECK_MODULES' aclocal.m4 || { + cat <&2 +autogen.sh: could not find PKG_CHECK_MODULES macro. + + Either pkg-config is not installed on your system or + \`pkg.m4' is missing or not found by aclocal. + + If \`pkg.m4' is installed at an unusual location, re-run + \`autogen.sh' by setting \`ACLOCAL_FLAGS': + + ACLOCAL_FLAGS="-I /share/aclocal" ./autogen.sh + +EOF + exit 1 + } +} + + +echo "autogen.sh: start libtoolize to get ltmain.sh" +${LIBTOOLIZE} --copy --force +echo "autogen.sh: reconfigure with autoreconf" +${AUTORECONF} -vif -I m4 || { + echo "autogen.sh: autoreconf has failed ($?), let's do it manually" + for dir in $PWD *; do + [ -d "$dir" ] || continue + [ -f "$dir"/configure.ac ] || [ -f "$dir"/configure.in ] || continue + echo "autogen.sh: configure `basename $dir`" + (cd "$dir" && ${ACLOCAL} -I m4 ${ACLOCAL_FLAGS}) + if [ x"$dir" = x"$PWD" ]; then + (cd "$dir" && check_pkg_config) + fi + (cd "$dir" && ${LIBTOOLIZE} --automake --copy --force) + (cd "$dir" && ${ACLOCAL} -I m4 ${ACLOCAL_FLAGS}) + (cd "$dir" && ${AUTOCONF} --force) + (cd "$dir" && ${AUTOHEADER}) + (cd "$dir" && ${AUTOMAKE} --add-missing --copy --force-missing) + done +} + +echo "autogen.sh: for the next step, run ./configure" + +exit 0 diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000000000000000000000000000000000000..021412a83de4659db8dcb831c94ee619c88f4f46 --- /dev/null +++ b/configure.ac @@ -0,0 +1,432 @@ +# -*- Autoconf -*- +# Process this file with autoconf to produce a configure script. + +####################### +### Base configuration + +# Configure autoconf +AC_PREREQ([2.69]) + +AC_INIT([ub-lldpd], + [m4_esyscmd_s([./get-version])], + [https://github.com/lldpd/lldpd/issues], + [ub-lldpd], + [https://lldpd.github.io/]) + +AC_CONFIG_SRCDIR([src/log.c]) +AC_CONFIG_HEADERS([config.h]) +AC_CONFIG_FILES([Makefile + src/Makefile + src/compat/Makefile + src/daemon/Makefile + src/lib/Makefile + src/client/Makefile + tests/Makefile]) +AC_CONFIG_MACRO_DIR([m4]) +AC_SUBST([CONFIGURE_ARGS], [$ac_configure_args]) + +# Configure automake +AM_INIT_AUTOMAKE([foreign subdir-objects -Wall -Werror tar-ustar]) +AM_MAINTAINER_MODE +m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES(yes)]) + +# Automake 1.12... +m4_pattern_allow([AM_PROG_AR]) +AM_PROG_AR + +# Configure libtool +LT_INIT +gl_LD_VERSION_SCRIPT + +####################### +### Checks + +# Checks for programs. +AC_PROG_CC +AC_PROG_CC_C99 +if test x"$ac_cv_prog_cc_c99" = x"no"; then + AC_MSG_FAILURE([*** C99 support is mandatory]) +fi +AM_PROG_CC_C_O +LT_INIT +AC_PROG_LN_S +AC_PROG_EGREP +AC_PROG_AWK + +# Check for pkg-config +m4_ifndef([PKG_CHECK_MODULES], [ + AC_MSG_ERROR([PKG_CHECK_MODULES not found. Please install pkg-config and re-run autogen.sh])]) + +# Doxygen +DX_HTML_FEATURE(ON) +DX_DOT_FEATURE(OFF) +DX_CHM_FEATURE(OFF) +DX_CHI_FEATURE(OFF) +DX_MAN_FEATURE(OFF) +DX_RTF_FEATURE(OFF) +DX_XML_FEATURE(OFF) +DX_PDF_FEATURE(ON) +DX_PS_FEATURE(OFF) +DX_INIT_DOXYGEN([ub-lldpd], [doxygen.cfg], [doxygen]) + +# Check some compiler flags +AX_CFLAGS_GCC_OPTION([-Wunknown-warning-option], [LLDP_CFLAGS]) +AX_CFLAGS_GCC_OPTION([-fdiagnostics-show-option], [LLDP_CFLAGS]) +AX_CFLAGS_GCC_OPTION([-fdiagnostics-color=auto], [LLDP_CFLAGS]) +AX_CFLAGS_GCC_OPTION([-pipe], [LLDP_CFLAGS]) +AX_CFLAGS_GCC_OPTION([-Wall], [LLDP_CFLAGS]) +AX_CFLAGS_GCC_OPTION([-W], [LLDP_CFLAGS]) +AX_CFLAGS_GCC_OPTION([-Wextra], [LLDP_CFLAGS]) +AX_CFLAGS_GCC_OPTION([-Wformat], [LLDP_CFLAGS]) +AX_CFLAGS_GCC_OPTION([-Wformat-security], [LLDP_CFLAGS]) +AX_CFLAGS_GCC_OPTION([-Wimplicit-fallthrough], [LLDP_CFLAGS]) +AX_CFLAGS_GCC_OPTION([-Wfatal-errors], [LLDP_CFLAGS]) +AX_CFLAGS_GCC_OPTION([-Wheader-guard], [LLDP_CFLAGS]) +AX_CFLAGS_GCC_OPTION([-Wdocumentation], [LLDP_CFLAGS]) +AX_CFLAGS_GCC_OPTION([-Winline], [LLDP_CFLAGS]) +AX_CFLAGS_GCC_OPTION([-Wpointer-arith], [LLDP_CFLAGS]) +AX_CFLAGS_GCC_OPTION([-fno-omit-frame-pointer], [LLDP_CFLAGS]) +AX_CFLAGS_GCC_OPTION([-Wno-cast-align], [LLDP_CFLAGS]) dnl clang is bad at this +AX_CFLAGS_GCC_OPTION([-Wno-unused-parameter], [LLDP_CFLAGS]) +AX_CFLAGS_GCC_OPTION([-Wno-missing-field-initializers], [LLDP_CFLAGS]) +AX_CFLAGS_GCC_OPTION([-Wno-sign-compare], [LLDP_CFLAGS]) dnl Should be fixed later +AX_LDFLAGS_OPTION([-Wl,-z,relro], [LLDP_LDFLAGS]) +AX_LDFLAGS_OPTION([-Wl,-z,now], [LLDP_LDFLAGS]) + +AC_C_TYPEOF + +# Hardening +AC_ARG_ENABLE([hardening], + [AS_HELP_STRING([--enable-hardening], + [Enable compiler and linker options to frustrate memory corruption exploits @<:@default=yes@:>@])], + [hardening="$enableval"], + [hardening="yes"]) +AC_ARG_ENABLE([pie], + [AS_HELP_STRING([--enable-pie], + [Enable PIE (position independant executable) @<:@default=no@:>@])], + [pie="$enableval"], + [pie="no"]) + +if test x"$hardening" != x"no"; then + AX_CFLAGS_GCC_OPTION([-fstack-protector], [LLDP_CFLAGS]) + AX_CFLAGS_GCC_OPTION([-fstack-protector-all], [LLDP_CFLAGS]) + AX_CFLAGS_GCC_OPTION([-fstack-protector-strong], [LLDP_CFLAGS]) + AX_CFLAGS_GCC_OPTION([-fstack-protector-strong], [LLDP_CFLAGS]) + AX_CFLAGS_GCC_OPTION([-fstack-clash-protection], [LLDP_CFLAGS]) + AX_CFLAGS_GCC_OPTION([-D_FORTIFY_SOURCE=2], [LLDP_CPPFLAGS]) +fi +if test x"$pie" = x"yes"; then + AX_CFLAGS_GCC_OPTION([-fPIE], [LLDP_CFLAGS]) + AX_LDFLAGS_OPTION([-fPIE -pie], [LLDP_BIN_LDFLAGS], + [AX_LDFLAGS_OPTION([-fPIE -Wl,-pie], [LLDP_BIN_LDFLAGS])]) +fi + +# Sanitizers +AC_ARG_ENABLE([sanitizers], + AS_HELP_STRING([--enable-sanitizers], + [Enable code instrumentation with selected sanitizers @<:@default=no@:>@]), + [ +case "$enableval" in + no) sanitizers= ;; + yes) sanitizers="-fsanitize=address,undefined" ;; + *) sanitizers="-fsanitize=$enableval" ;; +esac +if test x"$sanitizers" != x; then + LLDP_CFLAGS="$LLDP_CFLAGS $sanitizers" + LLDP_LDFLAGS="$LLDP_LDFLAGS $sanitizers" + AC_DEFINE([HAVE_ADDRESS_SANITIZER], 1, [Define if have both address and leak sanitizer]) +elif test x"$hardening" != x"no"; then + AX_LDFLAGS_OPTION([-fsanitize=safe-stack], [LLDP_BIN_LDFLAGS]) + if test x"$ax_cv_ld_check_flag__fsanitize_safe_stack" != x"no"; then + AX_CFLAGS_GCC_OPTION([-fsanitize=safe-stack], [LLDP_CFLAGS]) + fi +fi + ]) + +# Code coverage +AC_ARG_ENABLE([gcov], + AS_HELP_STRING([--enable-gcov], + [Enable coverage instrumentation @<:@default=no@:>@]), + [gcov="$enableval"], + [gcov="no"]) +if test x"$gcov" != x"no"; then + LLDP_CFLAGS="$LLDP_CFLAGS --coverage" + LLDP_LDFLAGS="$LLDP_LDFLAGS --coverage" +fi + +# OS +lldp_CHECK_OS +lldp_CFLAGS_OS + +AC_CACHE_SAVE + +# Checks for header files. +AC_HEADER_RESOLV +AC_CHECK_HEADERS([valgrind/valgrind.h]) +lldp_CHECK_STDINT + +AC_CACHE_SAVE + +# Checks for typedefs, structures, and compiler characteristics. +lldp_CHECK___PROGNAME +lldp_CHECK_ALIGNOF + +# Checks for library functions. +AC_CONFIG_LIBOBJ_DIR([src/compat]) +AC_FUNC_MALLOC +AC_FUNC_REALLOC +AC_FUNC_FORK + +# Some functions can be in libbsd +AC_ARG_WITH([libbsd], + AS_HELP_STRING( + [--with-libbsd], + [Use libbsd @<:@default=auto@:>@]), + [], + [with_libbsd=auto]) +if test x"$with_libbsd" != x"no"; then + PKG_CHECK_MODULES([libbsd], [libbsd-overlay], [ + _save_CFLAGS="$CFLAGS" + _save_LIBS="$LIBS" + CFLAGS="$CFLAGS $libbsd_CFLAGS" + LIBS="$LIBS $libbsd_LIBS" + AC_MSG_CHECKING([if libbsd can be linked correctly]) + AC_LINK_IFELSE([AC_LANG_PROGRAM([[ + @%:@include + @%:@include + ]], [[]])],[ + AC_MSG_RESULT(yes) + LLDP_CFLAGS="$LLDP_CFLAGS $libbsd_CFLAGS" + LLDP_LDFLAGS="$LLDP_LDFLAGS $libbsd_LIBS" + with_libbsd=yes + ],[ + AC_MSG_RESULT(no) + CFLAGS="$_save_CFLAGS" + LIBS="$_save_LIBS" + if test x"$with_libbsd" = x"yes"; then + AC_MSG_FAILURE([*** no libbsd support found]) + fi + with_libbsd=no + ]) + ], [ + if test x"$with_libbsd" = x"yes"; then + AC_MSG_FAILURE([*** no libbsd support found]) + fi + with_libbsd=no + ]) +fi + +# setproctitle may have an _init function +AC_REPLACE_FUNCS([setproctitle]) +AC_CHECK_FUNCS([setproctitle_init]) +# Other functions +AC_REPLACE_FUNCS([strlcpy + strnlen + strndup + strtonum + getline + asprintf + vsyslog + daemon]) +# Optional functions +AC_CHECK_FUNCS([setresuid setresgid]) + +# Check for res_init. On OSX, res_init is a symbol in libsystem_info +# and a macro in resolv.h. We need to ensure we test with resolv.h. +m4_pushdef([AC_LANG_CALL(C)], [ + AC_LANG_PROGRAM([$1 +@%:@include ], [return $2 ();])]) +AC_SEARCH_LIBS([res_init], resolv bind, + AC_DEFINE([HAVE_RES_INIT], 1, + [Define to indicate that res_init() exists])) +m4_popdef([AC_LANG_CALL(C)]) + +AC_CACHE_SAVE + +## Unit tests wich check +PKG_CHECK_MODULES([check], [check >= 0.9.4], [have_check=yes], [have_check=no]) + +# Third-party libraries +lldp_CHECK_LIBEVENT +lldp_CHECK_LIBCAP + +# Compatibility with pkg.m4 < 0.27 +m4_ifdef([PKG_INSTALLDIR], [PKG_INSTALLDIR], + [AC_ARG_WITH([pkgconfigdir], + [AS_HELP_STRING([--with-pkgconfigdir], + [install directory for *.pc pkg-config file])], + [],[with_pkgconfigdir='$(libdir)/pkgconfig']) + AC_SUBST([pkgconfigdir], [${with_pkgconfigdir}])]) + +####################### +### Options + +# Readline +AC_ARG_WITH([readline], + AS_HELP_STRING( + [--with-readline], + [Enable the use of readline-like library @<:@default=auto@:>@]), + [], + [with_readline=auto]) +if test x"$with_readline" != x"no"; then + AX_LIB_READLINE_LLDPD + if test x"$with_readline" != x"check" -a x"$with_readline" != x"auto"; then + if test x"$ax_cv_lib_readline" = x"no"; then + AC_MSG_FAILURE([*** no readline support found]) + fi + fi +else + ax_cv_lib_readline="no" +fi + +# XML +AC_ARG_WITH([xml], + AS_HELP_STRING( + [--with-xml], + [Enable XML output via libxml2 @<:@default=auto@:>@]), + [], + [with_xml=auto]) +lldp_CHECK_XML2 + +# JSON (built-in) +lldp_ARG_ENABLE([json0], [use of pre-0.9.2 JSON/json-c format], [no]) + +# Seccomp +AC_ARG_WITH([seccomp], + AS_HELP_STRING( + [--with-seccomp], + [Enable seccomp support (with libseccomp, experimental) @<:@default=no@:>@]), + [], + [with_seccomp=no]) +lldp_CHECK_SECCOMP + +# OS X launchd support +lldp_ARG_WITH([launchddaemonsdir], [Directory for launchd configuration file (OSX)], + [/Library/LaunchDaemons]) +AC_SUBST([launchddaemonsdir], [$with_launchddaemonsdir]) +AM_CONDITIONAL(HAVE_LAUNCHDDAEMONSDIR, + [test -n "$with_launchddaemonsdir" -a "x$with_launchddaemonsdir" != xno ]) + +# Systemd +lldp_ARG_WITH([systemdsystemunitdir], [Directory for systemd service files], + [$($PKG_CONFIG --variable=systemdsystemunitdir systemd 2> /dev/null)]) +AC_SUBST([systemdsystemunitdir], [$with_systemdsystemunitdir]) +AM_CONDITIONAL(HAVE_SYSTEMDSYSTEMUNITDIR, + [test -n "$with_systemdsystemunitdir" -a "x$with_systemdsystemunitdir" != xno ]) + +# sysusers +lldp_ARG_WITH([sysusersdir], [Directory for sysusers files], + [$($PKG_CONFIG --variable=sysusersdir systemd 2> /dev/null)]) +AC_SUBST([sysusersdir], [$with_sysusersdir]) +AM_CONDITIONAL(HAVE_SYSUSERSDIR, + [test -n "$with_sysusersdir" -a "x$with_sysusersdir" != xno ]) + +# AppArmor +lldp_ARG_WITH([apparmordir], [Directory for AppArmor profiles (Linux)], + [no]) +AC_SUBST([apparmordir], [$with_apparmordir]) +AM_CONDITIONAL(HAVE_APPARMORDIR, + [test -n "$with_apparmordir" -a "x$with_apparmordir" != xno ]) + +# Systemtap/DTrace +lldp_SYSTEMTAP + +# Privsep settings +lldp_ARG_ENABLE([privsep], [Privilege separation], [yes]) +lldp_ARG_WITH([privsep-user], [Which user to use for privilege separation], [_ub_lldpd]) +lldp_ARG_WITH([privsep-group], [Which group to use for privilege separation], [_ub_lldpd]) + +# Directories +dnl On autoconf 2.69 and before, runstatedir is not configurable, let be able to use it anyway +if test "x$runstatedir" = x; then + AC_SUBST([runstatedir], ['${localstatedir}/run']) +fi +lldp_ARG_WITH([privsep-chroot], [Which directory to use to chroot ub-lldpd], [${runstatedir}/ub-lldpd]) +lldp_ARG_WITH([lldpd-ctl-socket], [Path to socket for communication with ub-lldpd], [${runstatedir}/ub-lldpd.socket]) +lldp_ARG_WITH([lldpd-pid-file], [Path to ub-lldpd PID file], [${runstatedir}/ub-lldpd.pid]) + +# Netlink +lldp_ARG_WITH_UNQUOTED([netlink-max-receive-bufsize], [Netlink maximum receive buffer size], [1024*1024]) +lldp_ARG_WITH_UNQUOTED([netlink-receive-bufsize], [Netlink initial receive buffer size], [0]) +lldp_ARG_WITH_UNQUOTED([netlink-send-bufsize], [Netlink send buffer size], [0]) + +# Oldies +MIN_LINUX_KERNEL_VERSION=2.6.39 +lldp_ARG_ENABLE([oldies], [compatibility with Linux kernel older than 2.6.39], [no]) +if test x"$os" = x"Linux"; then + if test x"$enable_oldies" = x"no"; then + AC_DEFINE_UNQUOTED(MIN_LINUX_KERNEL_VERSION, "[$MIN_LINUX_KERNEL_VERSION]", [Minimal Linux kernel version required]) + else + AC_DEFINE(MIN_LINUX_KERNEL_VERSION, "2.6.11", [Minimal kernel version required]) + fi +fi + +AX_BUILD_DATE_EPOCH(BUILD_DATE, "%FT%TZ", [BUILD_DATE="(unknown)"]) +AC_DEFINE_UNQUOTED(BUILD_DATE, "[$BUILD_DATE]", [Build date and time]) + +dnl per reproducible-builds.org check SOURCE_DATE_EPOCH +dnl +if test -z "${SOURCE_DATE_EPOCH+set}" ; then +AC_DEFINE_UNQUOTED(LLDP_CC, "[$CC $LLDP_CFLAGS $LLDP_CPPFLAGS $CFLAGS $CPPFLAGS]", [C compiler command]) +AC_DEFINE_UNQUOTED(LLDP_LD, "[$LD $LLDP_LDFLAGS $LLDP_BIN_LDFLAGS $LDFLAGS $LIBS]", [Linker compiler command]) +else +AC_DEFINE_UNQUOTED(LLDP_CC, "[C compiler command is not available for reproducible builds]", [C compiler command]) +AC_DEFINE_UNQUOTED(LLDP_LD, "[Linker compiler command is not available for reproducible builds]", [Linker compiler command]) +fi + +####################### +# Output results +AC_SUBST([LLDP_CFLAGS]) +AC_SUBST([LLDP_CPPFLAGS]) +AC_SUBST([LLDP_LDFLAGS]) +AC_SUBST([LLDP_BIN_LDFLAGS]) +AM_CONDITIONAL([HAVE_CHECK], [test x"$have_check" = x"yes"]) +AM_CONDITIONAL([USE_XML], [test x"$with_xml" = x"yes"]) +AM_CONDITIONAL([USE_SECCOMP], [test x"$with_seccomp" = x"yes"]) +dnl If old default of AR_FLAGS is otherwise being used (because of older automake), +dnl replace it with one without 'u' +if test "x$AR_FLAGS" = "xcru" ; then + AR_FLAGS="cr" +fi +AC_OUTPUT + +if test x"$LIBEVENT_EMBEDDED" = x; then + libevent=system +else + libevent=embedded +fi + +cat < {} +}: + +pkgs.stdenv.mkDerivation rec { + name = "lldpd"; + src = pkgs.nix-gitignore.gitignoreSource [] ./.; + configureFlags = [ + "--localstatedir=/var" + "--enable-pie" + "--with-snmp" + "--with-systemdsystemunitdir=\${out}/lib/systemd/system" + ]; + + nativeBuildInputs = [ pkgs.pkgconfig pkgs.autoreconfHook ]; + buildInputs = [ pkgs.libevent pkgs.readline pkgs.net-snmp pkgs.openssl ]; + outputs = [ "out" "dev" "man" "doc" ]; +} diff --git a/doxygen.am b/doxygen.am new file mode 100644 index 0000000000000000000000000000000000000000..050ff6b0c0d9371c6c3adae70c78f964fe851ffe --- /dev/null +++ b/doxygen.am @@ -0,0 +1,186 @@ +# Copyright (C) 2004 Oren Ben-Kiki +# This file is distributed under the same terms as the Automake macro files. + +# Generate automatic documentation using Doxygen. Goals and variables values +# are controlled by the various DX_COND_??? conditionals set by autoconf. +# +# The provided goals are: +# doxygen-doc: Generate all doxygen documentation. +# doxygen-run: Run doxygen, which will generate some of the documentation +# (HTML, CHM, CHI, MAN, RTF, XML) but will not do the post +# processing required for the rest of it (PS, PDF, and some MAN). +# doxygen-man: Rename some doxygen generated man pages. +# doxygen-ps: Generate doxygen PostScript documentation. +# doxygen-pdf: Generate doxygen PDF documentation. +# +# Note that by default these are not integrated into the automake goals. If +# doxygen is used to generate man pages, you can achieve this integration by +# setting man3_MANS to the list of man pages generated and then adding the +# dependency: +# +# $(man3_MANS): doxygen-doc +# +# This will cause make to run doxygen and generate all the documentation. +# +# The following variable is intended for use in Makefile.am: +# +# DX_CLEANFILES = everything to clean. +# +# This is usually added to MOSTLYCLEANFILES. + +## --------------------------------- ## +## Format-independent Doxygen rules. ## +## --------------------------------- ## + +if DX_COND_doc + +## ------------------------------- ## +## Rules specific for HTML output. ## +## ------------------------------- ## + +if DX_COND_html + +DX_CLEAN_HTML = @DX_DOCDIR@/html + +endif DX_COND_html + +## ------------------------------ ## +## Rules specific for CHM output. ## +## ------------------------------ ## + +if DX_COND_chm + +DX_CLEAN_CHM = @DX_DOCDIR@/chm + +if DX_COND_chi + +DX_CLEAN_CHI = @DX_DOCDIR@/@PACKAGE@.chi + +endif DX_COND_chi + +endif DX_COND_chm + +## ------------------------------ ## +## Rules specific for MAN output. ## +## ------------------------------ ## + +if DX_COND_man + +DX_CLEAN_MAN = @DX_DOCDIR@/man + +endif DX_COND_man + +## ------------------------------ ## +## Rules specific for RTF output. ## +## ------------------------------ ## + +if DX_COND_rtf + +DX_CLEAN_RTF = @DX_DOCDIR@/rtf + +endif DX_COND_rtf + +## ------------------------------ ## +## Rules specific for XML output. ## +## ------------------------------ ## + +if DX_COND_xml + +DX_CLEAN_XML = @DX_DOCDIR@/xml + +endif DX_COND_xml + +## ----------------------------- ## +## Rules specific for PS output. ## +## ----------------------------- ## + +if DX_COND_ps + +DX_CLEAN_PS = @DX_DOCDIR@/@PACKAGE@.ps + +DX_PS_GOAL = doxygen-ps + +doxygen-ps: @DX_DOCDIR@/@PACKAGE@.ps + +@DX_DOCDIR@/@PACKAGE@.ps: @DX_DOCDIR@/@PACKAGE@.tag + cd @DX_DOCDIR@/latex; \ + rm -f *.aux *.toc *.idx *.ind *.ilg *.log *.out; \ + $(DX_LATEX) refman.tex; \ + $(MAKEINDEX_PATH) refman.idx; \ + $(DX_LATEX) refman.tex; \ + countdown=5; \ + while $(DX_EGREP) 'Rerun (LaTeX|to get cross-references right)' \ + refman.log > /dev/null 2>&1 \ + && test $$countdown -gt 0; do \ + $(DX_LATEX) refman.tex; \ + countdown=`expr $$countdown - 1`; \ + done; \ + $(DX_DVIPS) -o ../@PACKAGE@.ps refman.dvi + +endif DX_COND_ps + +## ------------------------------ ## +## Rules specific for PDF output. ## +## ------------------------------ ## + +if DX_COND_pdf + +DX_CLEAN_PDF = @DX_DOCDIR@/@PACKAGE@.pdf + +DX_PDF_GOAL = doxygen-pdf + +doxygen-pdf: @DX_DOCDIR@/@PACKAGE@.pdf + +@DX_DOCDIR@/@PACKAGE@.pdf: @DX_DOCDIR@/@PACKAGE@.tag + cd @DX_DOCDIR@/latex; \ + rm -f *.aux *.toc *.idx *.ind *.ilg *.log *.out; \ + $(DX_PDFLATEX) refman.tex; \ + $(DX_MAKEINDEX) refman.idx; \ + $(DX_PDFLATEX) refman.tex; \ + countdown=5; \ + while $(DX_EGREP) 'Rerun (LaTeX|to get cross-references right)' \ + refman.log > /dev/null 2>&1 \ + && test $$countdown -gt 0; do \ + $(DX_PDFLATEX) refman.tex; \ + countdown=`expr $$countdown - 1`; \ + done; \ + mv refman.pdf ../@PACKAGE@.pdf + +endif DX_COND_pdf + +## ------------------------------------------------- ## +## Rules specific for LaTeX (shared for PS and PDF). ## +## ------------------------------------------------- ## + +if DX_COND_latex + +DX_CLEAN_LATEX = @DX_DOCDIR@/latex + +endif DX_COND_latex + +.PHONY: doxygen-run doxygen-doc $(DX_PS_GOAL) $(DX_PDF_GOAL) @DX_DOCDIR@/@PACKAGE@.tag + +.INTERMEDIATE: doxygen-run $(DX_PS_GOAL) $(DX_PDF_GOAL) + +doxygen-run: @DX_DOCDIR@/@PACKAGE@.tag + +doxygen-doc: doxygen-run $(DX_PS_GOAL) $(DX_PDF_GOAL) + +@DX_DOCDIR@/@PACKAGE@.tag: $(DX_CONFIG) + rm -rf @DX_DOCDIR@ + $(DX_ENV) $(DX_DOXYGEN) $(srcdir)/$(DX_CONFIG) + +DX_CLEANFILES = \ + @DX_DOCDIR@/@PACKAGE@.tag \ + -r \ + $(DX_CLEAN_HTML) \ + $(DX_CLEAN_CHM) \ + $(DX_CLEAN_CHI) \ + $(DX_CLEAN_MAN) \ + $(DX_CLEAN_RTF) \ + $(DX_CLEAN_XML) \ + $(DX_CLEAN_PS) \ + $(DX_CLEAN_PDF) \ + $(DX_CLEAN_LATEX) + +endif DX_COND_doc diff --git a/doxygen.cfg b/doxygen.cfg new file mode 100644 index 0000000000000000000000000000000000000000..1a070c91187460149013ab55e7d19f456d31a479 --- /dev/null +++ b/doxygen.cfg @@ -0,0 +1,305 @@ +# Doxyfile 1.7.6.1 + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +DOXYFILE_ENCODING = UTF-8 +PROJECT_NAME = $(PROJECT)-$(VERSION) +PROJECT_NUMBER = +PROJECT_BRIEF = +PROJECT_LOGO = +OUTPUT_DIRECTORY = $(DOCDIR) +CREATE_SUBDIRS = NO +OUTPUT_LANGUAGE = English +BRIEF_MEMBER_DESC = YES +REPEAT_BRIEF = YES +ABBREVIATE_BRIEF = +ALWAYS_DETAILED_SEC = NO +INLINE_INHERITED_MEMB = NO +FULL_PATH_NAMES = YES +STRIP_FROM_PATH = $(SRCDIR) +STRIP_FROM_INC_PATH = $(SRCDIR) +SHORT_NAMES = NO +JAVADOC_AUTOBRIEF = YES +QT_AUTOBRIEF = NO +MULTILINE_CPP_IS_BRIEF = NO +INHERIT_DOCS = YES +SEPARATE_MEMBER_PAGES = NO +TAB_SIZE = 8 +ALIASES = +TCL_SUBST = +OPTIMIZE_OUTPUT_FOR_C = YES +OPTIMIZE_OUTPUT_JAVA = NO +OPTIMIZE_FOR_FORTRAN = NO +OPTIMIZE_OUTPUT_VHDL = NO +EXTENSION_MAPPING = +BUILTIN_STL_SUPPORT = NO +CPP_CLI_SUPPORT = NO +SIP_SUPPORT = NO +IDL_PROPERTY_SUPPORT = YES +DISTRIBUTE_GROUP_DOC = NO +SUBGROUPING = YES +INLINE_GROUPED_CLASSES = NO +INLINE_SIMPLE_STRUCTS = NO +TYPEDEF_HIDES_STRUCT = NO +SYMBOL_CACHE_SIZE = 0 +LOOKUP_CACHE_SIZE = 0 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- +EXTRACT_ALL = YES +EXTRACT_PRIVATE = NO +EXTRACT_STATIC = NO +EXTRACT_LOCAL_CLASSES = NO +EXTRACT_LOCAL_METHODS = NO +EXTRACT_ANON_NSPACES = NO +HIDE_UNDOC_MEMBERS = NO +HIDE_UNDOC_CLASSES = NO +HIDE_FRIEND_COMPOUNDS = NO +HIDE_IN_BODY_DOCS = NO +INTERNAL_DOCS = NO +CASE_SENSE_NAMES = YES +HIDE_SCOPE_NAMES = NO +SHOW_INCLUDE_FILES = YES +FORCE_LOCAL_INCLUDES = NO +INLINE_INFO = YES +SORT_MEMBER_DOCS = YES +SORT_BRIEF_DOCS = NO +SORT_MEMBERS_CTORS_1ST = NO +SORT_GROUP_NAMES = NO +SORT_BY_SCOPE_NAME = NO +STRICT_PROTO_MATCHING = NO +GENERATE_TODOLIST = YES +GENERATE_TESTLIST = YES +GENERATE_BUGLIST = YES +GENERATE_DEPRECATEDLIST= YES +ENABLED_SECTIONS = +MAX_INITIALIZER_LINES = 30 +SHOW_USED_FILES = YES +SHOW_FILES = YES +SHOW_NAMESPACES = YES +FILE_VERSION_FILTER = +LAYOUT_FILE = +CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- +QUIET = YES +WARNINGS = YES +WARN_IF_UNDOCUMENTED = YES +WARN_IF_DOC_ERROR = YES +WARN_NO_PARAMDOC = YES +WARN_FORMAT = "$file:$line: $text" +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- +INPUT = $(SRCDIR) +INPUT_ENCODING = UTF-8 +FILE_PATTERNS = *.c *.h +RECURSIVE = YES +EXCLUDE = config.h test-glue.h +EXCLUDE_SYMLINKS = NO +EXCLUDE_PATTERNS = */tests/* */libevent/* */build/* */include/linux/* *.git *.svn +EXCLUDE_SYMBOLS = +EXAMPLE_PATH = +EXAMPLE_PATTERNS = +EXAMPLE_RECURSIVE = NO +IMAGE_PATH = +INPUT_FILTER = +FILTER_PATTERNS = +FILTER_SOURCE_FILES = NO +FILTER_SOURCE_PATTERNS = + +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- +SOURCE_BROWSER = NO +INLINE_SOURCES = NO +STRIP_CODE_COMMENTS = YES +REFERENCED_BY_RELATION = NO +REFERENCES_RELATION = NO +REFERENCES_LINK_SOURCE = YES +USE_HTAGS = NO +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- +ALPHABETICAL_INDEX = YES +COLS_IN_ALPHA_INDEX = 5 +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- +GENERATE_HTML = $(GENERATE_HTML) +HTML_OUTPUT = html +HTML_FILE_EXTENSION = .html +HTML_HEADER = +HTML_FOOTER = +HTML_STYLESHEET = +HTML_EXTRA_FILES = +HTML_COLORSTYLE_HUE = 220 +HTML_COLORSTYLE_SAT = 100 +HTML_COLORSTYLE_GAMMA = 80 +HTML_TIMESTAMP = YES +HTML_DYNAMIC_SECTIONS = NO +GENERATE_DOCSET = NO +DOCSET_FEEDNAME = "Doxygen generated docs" +DOCSET_BUNDLE_ID = org.doxygen.Project +DOCSET_PUBLISHER_ID = org.doxygen.Publisher +DOCSET_PUBLISHER_NAME = Publisher +GENERATE_HTMLHELP = $(GENERATE_CHM) +CHM_FILE = ../$(PROJECT).chm +HHC_LOCATION = $(HHC_PATH) +GENERATE_CHI = $(GENERATE_CHI) +CHM_INDEX_ENCODING = +BINARY_TOC = NO +TOC_EXPAND = NO +GENERATE_QHP = NO +QCH_FILE = +QHP_NAMESPACE = org.doxygen.Project +QHP_VIRTUAL_FOLDER = doc +QHP_CUST_FILTER_NAME = +QHP_CUST_FILTER_ATTRS = +QHP_SECT_FILTER_ATTRS = +QHG_LOCATION = +GENERATE_ECLIPSEHELP = NO +ECLIPSE_DOC_ID = org.doxygen.Project +DISABLE_INDEX = NO +GENERATE_TREEVIEW = NO +ENUM_VALUES_PER_LINE = 4 +TREEVIEW_WIDTH = 250 +EXT_LINKS_IN_WINDOW = NO +FORMULA_FONTSIZE = 10 +FORMULA_TRANSPARENT = YES +USE_MATHJAX = NO +MATHJAX_RELPATH = http://www.mathjax.org/mathjax +MATHJAX_EXTENSIONS = +SEARCHENGINE = YES +SERVER_BASED_SEARCH = NO + +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- +GENERATE_LATEX = $(GENERATE_LATEX) +LATEX_OUTPUT = latex +LATEX_CMD_NAME = latex +MAKEINDEX_CMD_NAME = makeindex +COMPACT_LATEX = NO +PAPER_TYPE = $(PAPER_SIZE) +EXTRA_PACKAGES = +LATEX_HEADER = +LATEX_FOOTER = +PDF_HYPERLINKS = YES +USE_PDFLATEX = YES +LATEX_BATCHMODE = YES +LATEX_HIDE_INDICES = NO +LATEX_SOURCE_CODE = NO +LATEX_BIB_STYLE = plain + +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- +GENERATE_RTF = $(GENERATE_RTF) +RTF_OUTPUT = rtf +COMPACT_RTF = NO +RTF_HYPERLINKS = YES +RTF_STYLESHEET_FILE = +RTF_EXTENSIONS_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- +GENERATE_MAN = $(GENERATE_MAN) +MAN_OUTPUT = man +MAN_EXTENSION = .1 +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- +GENERATE_XML = $(GENERATE_XML) +XML_OUTPUT = xml +XML_SCHEMA = +XML_DTD = +XML_PROGRAMLISTING = YES + +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- +GENERATE_AUTOGEN_DEF = NO + +#--------------------------------------------------------------------------- +# configuration options related to the Perl module output +#--------------------------------------------------------------------------- +GENERATE_PERLMOD = NO +PERLMOD_LATEX = NO +PERLMOD_PRETTY = YES +PERLMOD_MAKEVAR_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- +ENABLE_PREPROCESSING = YES +MACRO_EXPANSION = YES +EXPAND_ONLY_PREDEF = YES +SEARCH_INCLUDES = YES +INCLUDE_PATH = +INCLUDE_FILE_PATTERNS = +PREDEFINED = "TAILQ_ENTRY(t)=struct t" \ + "TAILQ_HEAD(t,v)=struct v" \ + "SIMPLEQ_ENTRY(t)=struct t" \ + "SIMPLEQ_HEAD(t,v)=struct v" \ + "__attribute__(x)=" \ + DOCSTATIC +EXPAND_AS_DEFINED = +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration::additions related to external references +#--------------------------------------------------------------------------- +TAGFILES = +GENERATE_TAGFILE = $(DOCDIR)/$(PROJECT).tag +ALLEXTERNALS = NO +EXTERNAL_GROUPS = YES +PERL_PATH = $(PERL_PATH) + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- +CLASS_DIAGRAMS = YES +MSCGEN_PATH = +HIDE_UNDOC_RELATIONS = YES +HAVE_DOT = $(HAVE_DOT) +DOT_NUM_THREADS = 0 +DOT_FONTNAME = Helvetica +DOT_FONTSIZE = 10 +DOT_FONTPATH = +CLASS_GRAPH = YES +COLLABORATION_GRAPH = YES +GROUP_GRAPHS = YES +UML_LOOK = NO +TEMPLATE_RELATIONS = NO +INCLUDE_GRAPH = YES +INCLUDED_BY_GRAPH = YES +CALL_GRAPH = NO +CALLER_GRAPH = NO +GRAPHICAL_HIERARCHY = YES +DIRECTORY_GRAPH = YES +DOT_IMAGE_FORMAT = svg +INTERACTIVE_SVG = YES +DOT_PATH = $(DOT_PATH) +DOTFILE_DIRS = +MSCFILE_DIRS = +DOT_GRAPH_MAX_NODES = 50 +MAX_DOT_GRAPH_DEPTH = 0 +DOT_TRANSPARENT = NO +DOT_MULTI_TARGETS = YES +GENERATE_LEGEND = YES +DOT_CLEANUP = YES diff --git a/edit.am b/edit.am new file mode 100644 index 0000000000000000000000000000000000000000..e531590b9cc659c7aca0d99a476783886b8fbfb6 --- /dev/null +++ b/edit.am @@ -0,0 +1,24 @@ +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' + +$(TEMPLATES): Makefile + $(AM_V_GEN)$(MKDIR_P) $(@D) && $(edit) $(srcdir)/$@.in > $@.tmp && mv $@.tmp $@ diff --git a/get-version b/get-version new file mode 100755 index 0000000000000000000000000000000000000000..e926d88ba3aaab5c68fca5a5a3d0cf86105d86b7 --- /dev/null +++ b/get-version @@ -0,0 +1,47 @@ +#!/bin/sh +# +# get-version +# +# Copyright © 2009 Guillem Jover +# +# 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. The name of the author may not be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED ``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. + +if [ -f .dist-version ]; then + # Get the version from the file distributed in the tarball. + version=$(cat .dist-version) +elif [ -e .git ]; then + # Ger the version from the git repository. + version=$(git describe --tags --always --match [0-9]* 2> /dev/null) + + # Check if we are on a dirty checkout. + git update-index --refresh -q >/dev/null + dirty=$(git diff-index --name-only --ignore-submodules=untracked HEAD 2>/dev/null) + if [ -n "$dirty" ]; then + version="$version-dirty" + fi +else + version=$(date +%F) +fi + +# Use printf to avoid the trailing new line that m4_esyscmd would not handle. +printf "$version" diff --git a/include/linux/bpf_common.h b/include/linux/bpf_common.h new file mode 100644 index 0000000000000000000000000000000000000000..f0fe1394971d19422a3fe39bec4451d2a436b27e --- /dev/null +++ b/include/linux/bpf_common.h @@ -0,0 +1,57 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +#ifndef __LINUX_BPF_COMMON_H__ +#define __LINUX_BPF_COMMON_H__ + +/* Instruction classes */ +#define BPF_CLASS(code) ((code) & 0x07) +#define BPF_LD 0x00 +#define BPF_LDX 0x01 +#define BPF_ST 0x02 +#define BPF_STX 0x03 +#define BPF_ALU 0x04 +#define BPF_JMP 0x05 +#define BPF_RET 0x06 +#define BPF_MISC 0x07 + +/* ld/ldx fields */ +#define BPF_SIZE(code) ((code) & 0x18) +#define BPF_W 0x00 /* 32-bit */ +#define BPF_H 0x08 /* 16-bit */ +#define BPF_B 0x10 /* 8-bit */ +/* eBPF BPF_DW 0x18 64-bit */ +#define BPF_MODE(code) ((code) & 0xe0) +#define BPF_IMM 0x00 +#define BPF_ABS 0x20 +#define BPF_IND 0x40 +#define BPF_MEM 0x60 +#define BPF_LEN 0x80 +#define BPF_MSH 0xa0 + +/* alu/jmp fields */ +#define BPF_OP(code) ((code) & 0xf0) +#define BPF_ADD 0x00 +#define BPF_SUB 0x10 +#define BPF_MUL 0x20 +#define BPF_DIV 0x30 +#define BPF_OR 0x40 +#define BPF_AND 0x50 +#define BPF_LSH 0x60 +#define BPF_RSH 0x70 +#define BPF_NEG 0x80 +#define BPF_MOD 0x90 +#define BPF_XOR 0xa0 + +#define BPF_JA 0x00 +#define BPF_JEQ 0x10 +#define BPF_JGT 0x20 +#define BPF_JGE 0x30 +#define BPF_JSET 0x40 +#define BPF_SRC(code) ((code) & 0x08) +#define BPF_K 0x00 +#define BPF_X 0x08 + +#ifndef BPF_MAXINSNS +#define BPF_MAXINSNS 4096 +#endif + +#endif /* __LINUX_BPF_COMMON_H__ */ diff --git a/include/linux/ethtool.h b/include/linux/ethtool.h new file mode 100644 index 0000000000000000000000000000000000000000..6bfbb85f94022eba08af5cfcc5b4e2491285464c --- /dev/null +++ b/include/linux/ethtool.h @@ -0,0 +1,1846 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +/* + * ethtool.h: Defines for Linux ethtool. + * + * Copyright (C) 1998 David S. Miller (davem@redhat.com) + * Copyright 2001 Jeff Garzik + * Portions Copyright 2001 Sun Microsystems (thockin@sun.com) + * Portions Copyright 2002 Intel (eli.kupermann@intel.com, + * christopher.leech@intel.com, + * scott.feldman@intel.com) + * Portions Copyright (C) Sun Microsystems 2008 + */ + +#ifndef _LINUX_ETHTOOL_H +#define _LINUX_ETHTOOL_H + +#include +#include +#include + +#include /* for INT_MAX */ + +/* All structures exposed to userland should be defined such that they + * have the same layout for 32-bit and 64-bit userland. + */ + +/** + * struct ethtool_cmd - DEPRECATED, link control and status + * This structure is DEPRECATED, please use struct ethtool_link_settings. + * @cmd: Command number = %ETHTOOL_GSET or %ETHTOOL_SSET + * @supported: Bitmask of %SUPPORTED_* flags for the link modes, + * physical connectors and other link features for which the + * interface supports autonegotiation or auto-detection. + * Read-only. + * @advertising: Bitmask of %ADVERTISED_* flags for the link modes, + * physical connectors and other link features that are + * advertised through autonegotiation or enabled for + * auto-detection. + * @speed: Low bits of the speed, 1Mb units, 0 to INT_MAX or SPEED_UNKNOWN + * @duplex: Duplex mode; one of %DUPLEX_* + * @port: Physical connector type; one of %PORT_* + * @phy_address: MDIO address of PHY (transceiver); 0 or 255 if not + * applicable. For clause 45 PHYs this is the PRTAD. + * @transceiver: Historically used to distinguish different possible + * PHY types, but not in a consistent way. Deprecated. + * @autoneg: Enable/disable autonegotiation and auto-detection; + * either %AUTONEG_DISABLE or %AUTONEG_ENABLE + * @mdio_support: Bitmask of %ETH_MDIO_SUPPORTS_* flags for the MDIO + * protocols supported by the interface; 0 if unknown. + * Read-only. + * @maxtxpkt: Historically used to report TX IRQ coalescing; now + * obsoleted by &struct ethtool_coalesce. Read-only; deprecated. + * @maxrxpkt: Historically used to report RX IRQ coalescing; now + * obsoleted by &struct ethtool_coalesce. Read-only; deprecated. + * @speed_hi: High bits of the speed, 1Mb units, 0 to INT_MAX or SPEED_UNKNOWN + * @eth_tp_mdix: Ethernet twisted-pair MDI(-X) status; one of + * %ETH_TP_MDI_*. If the status is unknown or not applicable, the + * value will be %ETH_TP_MDI_INVALID. Read-only. + * @eth_tp_mdix_ctrl: Ethernet twisted pair MDI(-X) control; one of + * %ETH_TP_MDI_*. If MDI(-X) control is not implemented, reads + * yield %ETH_TP_MDI_INVALID and writes may be ignored or rejected. + * When written successfully, the link should be renegotiated if + * necessary. + * @lp_advertising: Bitmask of %ADVERTISED_* flags for the link modes + * and other link features that the link partner advertised + * through autonegotiation; 0 if unknown or not applicable. + * Read-only. + * + * The link speed in Mbps is split between @speed and @speed_hi. Use + * the ethtool_cmd_speed() and ethtool_cmd_speed_set() functions to + * access it. + * + * If autonegotiation is disabled, the speed and @duplex represent the + * fixed link mode and are writable if the driver supports multiple + * link modes. If it is enabled then they are read-only; if the link + * is up they represent the negotiated link mode; if the link is down, + * the speed is 0, %SPEED_UNKNOWN or the highest enabled speed and + * @duplex is %DUPLEX_UNKNOWN or the best enabled duplex mode. + * + * Some hardware interfaces may have multiple PHYs and/or physical + * connectors fitted or do not allow the driver to detect which are + * fitted. For these interfaces @port and/or @phy_address may be + * writable, possibly dependent on @autoneg being %AUTONEG_DISABLE. + * Otherwise, attempts to write different values may be ignored or + * rejected. + * + * Users should assume that all fields not marked read-only are + * writable and subject to validation by the driver. They should use + * %ETHTOOL_GSET to get the current values before making specific + * changes and then applying them with %ETHTOOL_SSET. + * + * Drivers that implement set_settings() should validate all fields + * other than @cmd that are not described as read-only or deprecated, + * and must ignore all fields described as read-only. + * + * Deprecated fields should be ignored by both users and drivers. + */ +struct ethtool_cmd { + __u32 cmd; + __u32 supported; + __u32 advertising; + __u16 speed; + __u8 duplex; + __u8 port; + __u8 phy_address; + __u8 transceiver; + __u8 autoneg; + __u8 mdio_support; + __u32 maxtxpkt; + __u32 maxrxpkt; + __u16 speed_hi; + __u8 eth_tp_mdix; + __u8 eth_tp_mdix_ctrl; + __u32 lp_advertising; + __u32 reserved[2]; +}; + +static __inline__ void ethtool_cmd_speed_set(struct ethtool_cmd *ep, + __u32 speed) +{ + ep->speed = (__u16)(speed & 0xFFFF); + ep->speed_hi = (__u16)(speed >> 16); +} + +static __inline__ __u32 ethtool_cmd_speed(const struct ethtool_cmd *ep) +{ + return (ep->speed_hi << 16) | ep->speed; +} + +/* Device supports clause 22 register access to PHY or peripherals + * using the interface defined in . This should not be + * set if there are known to be no such peripherals present or if + * the driver only emulates clause 22 registers for compatibility. + */ +#define ETH_MDIO_SUPPORTS_C22 1 + +/* Device supports clause 45 register access to PHY or peripherals + * using the interface defined in and . + * This should not be set if there are known to be no such peripherals + * present. + */ +#define ETH_MDIO_SUPPORTS_C45 2 + +#define ETHTOOL_FWVERS_LEN 32 +#define ETHTOOL_BUSINFO_LEN 32 +#define ETHTOOL_EROMVERS_LEN 32 + +/** + * struct ethtool_drvinfo - general driver and device information + * @cmd: Command number = %ETHTOOL_GDRVINFO + * @driver: Driver short name. This should normally match the name + * in its bus driver structure (e.g. pci_driver::name). Must + * not be an empty string. + * @version: Driver version string; may be an empty string + * @fw_version: Firmware version string; may be an empty string + * @erom_version: Expansion ROM version string; may be an empty string + * @bus_info: Device bus address. This should match the dev_name() + * string for the underlying bus device, if there is one. May be + * an empty string. + * @n_priv_flags: Number of flags valid for %ETHTOOL_GPFLAGS and + * %ETHTOOL_SPFLAGS commands; also the number of strings in the + * %ETH_SS_PRIV_FLAGS set + * @n_stats: Number of u64 statistics returned by the %ETHTOOL_GSTATS + * command; also the number of strings in the %ETH_SS_STATS set + * @testinfo_len: Number of results returned by the %ETHTOOL_TEST + * command; also the number of strings in the %ETH_SS_TEST set + * @eedump_len: Size of EEPROM accessible through the %ETHTOOL_GEEPROM + * and %ETHTOOL_SEEPROM commands, in bytes + * @regdump_len: Size of register dump returned by the %ETHTOOL_GREGS + * command, in bytes + * + * Users can use the %ETHTOOL_GSSET_INFO command to get the number of + * strings in any string set (from Linux 2.6.34). + * + * Drivers should set at most @driver, @version, @fw_version and + * @bus_info in their get_drvinfo() implementation. The ethtool + * core fills in the other fields using other driver operations. + */ +struct ethtool_drvinfo { + __u32 cmd; + char driver[32]; + char version[32]; + char fw_version[ETHTOOL_FWVERS_LEN]; + char bus_info[ETHTOOL_BUSINFO_LEN]; + char erom_version[ETHTOOL_EROMVERS_LEN]; + char reserved2[12]; + __u32 n_priv_flags; + __u32 n_stats; + __u32 testinfo_len; + __u32 eedump_len; + __u32 regdump_len; +}; + +#define SOPASS_MAX 6 + +/** + * struct ethtool_wolinfo - Wake-On-Lan configuration + * @cmd: Command number = %ETHTOOL_GWOL or %ETHTOOL_SWOL + * @supported: Bitmask of %WAKE_* flags for supported Wake-On-Lan modes. + * Read-only. + * @wolopts: Bitmask of %WAKE_* flags for enabled Wake-On-Lan modes. + * @sopass: SecureOn(tm) password; meaningful only if %WAKE_MAGICSECURE + * is set in @wolopts. + */ +struct ethtool_wolinfo { + __u32 cmd; + __u32 supported; + __u32 wolopts; + __u8 sopass[SOPASS_MAX]; +}; + +/* for passing single values */ +struct ethtool_value { + __u32 cmd; + __u32 data; +}; + +#define PFC_STORM_PREVENTION_AUTO 0xffff +#define PFC_STORM_PREVENTION_DISABLE 0 + +enum tunable_id { + ETHTOOL_ID_UNSPEC, + ETHTOOL_RX_COPYBREAK, + ETHTOOL_TX_COPYBREAK, + ETHTOOL_PFC_PREVENTION_TOUT, /* timeout in msecs */ + /* + * Add your fresh new tunable attribute above and remember to update + * tunable_strings[] in net/core/ethtool.c + */ + __ETHTOOL_TUNABLE_COUNT, +}; + +enum tunable_type_id { + ETHTOOL_TUNABLE_UNSPEC, + ETHTOOL_TUNABLE_U8, + ETHTOOL_TUNABLE_U16, + ETHTOOL_TUNABLE_U32, + ETHTOOL_TUNABLE_U64, + ETHTOOL_TUNABLE_STRING, + ETHTOOL_TUNABLE_S8, + ETHTOOL_TUNABLE_S16, + ETHTOOL_TUNABLE_S32, + ETHTOOL_TUNABLE_S64, +}; + +struct ethtool_tunable { + __u32 cmd; + __u32 id; + __u32 type_id; + __u32 len; + void *data[0]; +}; + +#define DOWNSHIFT_DEV_DEFAULT_COUNT 0xff +#define DOWNSHIFT_DEV_DISABLE 0 + +enum phy_tunable_id { + ETHTOOL_PHY_ID_UNSPEC, + ETHTOOL_PHY_DOWNSHIFT, + /* + * Add your fresh new phy tunable attribute above and remember to update + * phy_tunable_strings[] in net/core/ethtool.c + */ + __ETHTOOL_PHY_TUNABLE_COUNT, +}; + +/** + * struct ethtool_regs - hardware register dump + * @cmd: Command number = %ETHTOOL_GREGS + * @version: Dump format version. This is driver-specific and may + * distinguish different chips/revisions. Drivers must use new + * version numbers whenever the dump format changes in an + * incompatible way. + * @len: On entry, the real length of @data. On return, the number of + * bytes used. + * @data: Buffer for the register dump + * + * Users should use %ETHTOOL_GDRVINFO to find the maximum length of + * a register dump for the interface. They must allocate the buffer + * immediately following this structure. + */ +struct ethtool_regs { + __u32 cmd; + __u32 version; + __u32 len; + __u8 data[0]; +}; + +/** + * struct ethtool_eeprom - EEPROM dump + * @cmd: Command number = %ETHTOOL_GEEPROM, %ETHTOOL_GMODULEEEPROM or + * %ETHTOOL_SEEPROM + * @magic: A 'magic cookie' value to guard against accidental changes. + * The value passed in to %ETHTOOL_SEEPROM must match the value + * returned by %ETHTOOL_GEEPROM for the same device. This is + * unused when @cmd is %ETHTOOL_GMODULEEEPROM. + * @offset: Offset within the EEPROM to begin reading/writing, in bytes + * @len: On entry, number of bytes to read/write. On successful + * return, number of bytes actually read/written. In case of + * error, this may indicate at what point the error occurred. + * @data: Buffer to read/write from + * + * Users may use %ETHTOOL_GDRVINFO or %ETHTOOL_GMODULEINFO to find + * the length of an on-board or module EEPROM, respectively. They + * must allocate the buffer immediately following this structure. + */ +struct ethtool_eeprom { + __u32 cmd; + __u32 magic; + __u32 offset; + __u32 len; + __u8 data[0]; +}; + +/** + * struct ethtool_eee - Energy Efficient Ethernet information + * @cmd: ETHTOOL_{G,S}EEE + * @supported: Mask of %SUPPORTED_* flags for the speed/duplex combinations + * for which there is EEE support. + * @advertised: Mask of %ADVERTISED_* flags for the speed/duplex combinations + * advertised as eee capable. + * @lp_advertised: Mask of %ADVERTISED_* flags for the speed/duplex + * combinations advertised by the link partner as eee capable. + * @eee_active: Result of the eee auto negotiation. + * @eee_enabled: EEE configured mode (enabled/disabled). + * @tx_lpi_enabled: Whether the interface should assert its tx lpi, given + * that eee was negotiated. + * @tx_lpi_timer: Time in microseconds the interface delays prior to asserting + * its tx lpi (after reaching 'idle' state). Effective only when eee + * was negotiated and tx_lpi_enabled was set. + */ +struct ethtool_eee { + __u32 cmd; + __u32 supported; + __u32 advertised; + __u32 lp_advertised; + __u32 eee_active; + __u32 eee_enabled; + __u32 tx_lpi_enabled; + __u32 tx_lpi_timer; + __u32 reserved[2]; +}; + +/** + * struct ethtool_modinfo - plugin module eeprom information + * @cmd: %ETHTOOL_GMODULEINFO + * @type: Standard the module information conforms to %ETH_MODULE_SFF_xxxx + * @eeprom_len: Length of the eeprom + * + * This structure is used to return the information to + * properly size memory for a subsequent call to %ETHTOOL_GMODULEEEPROM. + * The type code indicates the eeprom data format + */ +struct ethtool_modinfo { + __u32 cmd; + __u32 type; + __u32 eeprom_len; + __u32 reserved[8]; +}; + +/** + * struct ethtool_coalesce - coalescing parameters for IRQs and stats updates + * @cmd: ETHTOOL_{G,S}COALESCE + * @rx_coalesce_usecs: How many usecs to delay an RX interrupt after + * a packet arrives. + * @rx_max_coalesced_frames: Maximum number of packets to receive + * before an RX interrupt. + * @rx_coalesce_usecs_irq: Same as @rx_coalesce_usecs, except that + * this value applies while an IRQ is being serviced by the host. + * @rx_max_coalesced_frames_irq: Same as @rx_max_coalesced_frames, + * except that this value applies while an IRQ is being serviced + * by the host. + * @tx_coalesce_usecs: How many usecs to delay a TX interrupt after + * a packet is sent. + * @tx_max_coalesced_frames: Maximum number of packets to be sent + * before a TX interrupt. + * @tx_coalesce_usecs_irq: Same as @tx_coalesce_usecs, except that + * this value applies while an IRQ is being serviced by the host. + * @tx_max_coalesced_frames_irq: Same as @tx_max_coalesced_frames, + * except that this value applies while an IRQ is being serviced + * by the host. + * @stats_block_coalesce_usecs: How many usecs to delay in-memory + * statistics block updates. Some drivers do not have an + * in-memory statistic block, and in such cases this value is + * ignored. This value must not be zero. + * @use_adaptive_rx_coalesce: Enable adaptive RX coalescing. + * @use_adaptive_tx_coalesce: Enable adaptive TX coalescing. + * @pkt_rate_low: Threshold for low packet rate (packets per second). + * @rx_coalesce_usecs_low: How many usecs to delay an RX interrupt after + * a packet arrives, when the packet rate is below @pkt_rate_low. + * @rx_max_coalesced_frames_low: Maximum number of packets to be received + * before an RX interrupt, when the packet rate is below @pkt_rate_low. + * @tx_coalesce_usecs_low: How many usecs to delay a TX interrupt after + * a packet is sent, when the packet rate is below @pkt_rate_low. + * @tx_max_coalesced_frames_low: Maximum nuumber of packets to be sent before + * a TX interrupt, when the packet rate is below @pkt_rate_low. + * @pkt_rate_high: Threshold for high packet rate (packets per second). + * @rx_coalesce_usecs_high: How many usecs to delay an RX interrupt after + * a packet arrives, when the packet rate is above @pkt_rate_high. + * @rx_max_coalesced_frames_high: Maximum number of packets to be received + * before an RX interrupt, when the packet rate is above @pkt_rate_high. + * @tx_coalesce_usecs_high: How many usecs to delay a TX interrupt after + * a packet is sent, when the packet rate is above @pkt_rate_high. + * @tx_max_coalesced_frames_high: Maximum number of packets to be sent before + * a TX interrupt, when the packet rate is above @pkt_rate_high. + * @rate_sample_interval: How often to do adaptive coalescing packet rate + * sampling, measured in seconds. Must not be zero. + * + * Each pair of (usecs, max_frames) fields specifies that interrupts + * should be coalesced until + * (usecs > 0 && time_since_first_completion >= usecs) || + * (max_frames > 0 && completed_frames >= max_frames) + * + * It is illegal to set both usecs and max_frames to zero as this + * would cause interrupts to never be generated. To disable + * coalescing, set usecs = 0 and max_frames = 1. + * + * Some implementations ignore the value of max_frames and use the + * condition time_since_first_completion >= usecs + * + * This is deprecated. Drivers for hardware that does not support + * counting completions should validate that max_frames == !rx_usecs. + * + * Adaptive RX/TX coalescing is an algorithm implemented by some + * drivers to improve latency under low packet rates and improve + * throughput under high packet rates. Some drivers only implement + * one of RX or TX adaptive coalescing. Anything not implemented by + * the driver causes these values to be silently ignored. + * + * When the packet rate is below @pkt_rate_high but above + * @pkt_rate_low (both measured in packets per second) the + * normal {rx,tx}_* coalescing parameters are used. + */ +struct ethtool_coalesce { + __u32 cmd; + __u32 rx_coalesce_usecs; + __u32 rx_max_coalesced_frames; + __u32 rx_coalesce_usecs_irq; + __u32 rx_max_coalesced_frames_irq; + __u32 tx_coalesce_usecs; + __u32 tx_max_coalesced_frames; + __u32 tx_coalesce_usecs_irq; + __u32 tx_max_coalesced_frames_irq; + __u32 stats_block_coalesce_usecs; + __u32 use_adaptive_rx_coalesce; + __u32 use_adaptive_tx_coalesce; + __u32 pkt_rate_low; + __u32 rx_coalesce_usecs_low; + __u32 rx_max_coalesced_frames_low; + __u32 tx_coalesce_usecs_low; + __u32 tx_max_coalesced_frames_low; + __u32 pkt_rate_high; + __u32 rx_coalesce_usecs_high; + __u32 rx_max_coalesced_frames_high; + __u32 tx_coalesce_usecs_high; + __u32 tx_max_coalesced_frames_high; + __u32 rate_sample_interval; +}; + +/** + * struct ethtool_ringparam - RX/TX ring parameters + * @cmd: Command number = %ETHTOOL_GRINGPARAM or %ETHTOOL_SRINGPARAM + * @rx_max_pending: Maximum supported number of pending entries per + * RX ring. Read-only. + * @rx_mini_max_pending: Maximum supported number of pending entries + * per RX mini ring. Read-only. + * @rx_jumbo_max_pending: Maximum supported number of pending entries + * per RX jumbo ring. Read-only. + * @tx_max_pending: Maximum supported number of pending entries per + * TX ring. Read-only. + * @rx_pending: Current maximum number of pending entries per RX ring + * @rx_mini_pending: Current maximum number of pending entries per RX + * mini ring + * @rx_jumbo_pending: Current maximum number of pending entries per RX + * jumbo ring + * @tx_pending: Current maximum supported number of pending entries + * per TX ring + * + * If the interface does not have separate RX mini and/or jumbo rings, + * @rx_mini_max_pending and/or @rx_jumbo_max_pending will be 0. + * + * There may also be driver-dependent minimum values for the number + * of entries per ring. + */ +struct ethtool_ringparam { + __u32 cmd; + __u32 rx_max_pending; + __u32 rx_mini_max_pending; + __u32 rx_jumbo_max_pending; + __u32 tx_max_pending; + __u32 rx_pending; + __u32 rx_mini_pending; + __u32 rx_jumbo_pending; + __u32 tx_pending; +}; + +/** + * struct ethtool_channels - configuring number of network channel + * @cmd: ETHTOOL_{G,S}CHANNELS + * @max_rx: Read only. Maximum number of receive channel the driver support. + * @max_tx: Read only. Maximum number of transmit channel the driver support. + * @max_other: Read only. Maximum number of other channel the driver support. + * @max_combined: Read only. Maximum number of combined channel the driver + * support. Set of queues RX, TX or other. + * @rx_count: Valid values are in the range 1 to the max_rx. + * @tx_count: Valid values are in the range 1 to the max_tx. + * @other_count: Valid values are in the range 1 to the max_other. + * @combined_count: Valid values are in the range 1 to the max_combined. + * + * This can be used to configure RX, TX and other channels. + */ + +struct ethtool_channels { + __u32 cmd; + __u32 max_rx; + __u32 max_tx; + __u32 max_other; + __u32 max_combined; + __u32 rx_count; + __u32 tx_count; + __u32 other_count; + __u32 combined_count; +}; + +/** + * struct ethtool_pauseparam - Ethernet pause (flow control) parameters + * @cmd: Command number = %ETHTOOL_GPAUSEPARAM or %ETHTOOL_SPAUSEPARAM + * @autoneg: Flag to enable autonegotiation of pause frame use + * @rx_pause: Flag to enable reception of pause frames + * @tx_pause: Flag to enable transmission of pause frames + * + * Drivers should reject a non-zero setting of @autoneg when + * autoneogotiation is disabled (or not supported) for the link. + * + * If the link is autonegotiated, drivers should use + * mii_advertise_flowctrl() or similar code to set the advertised + * pause frame capabilities based on the @rx_pause and @tx_pause flags, + * even if @autoneg is zero. They should also allow the advertised + * pause frame capabilities to be controlled directly through the + * advertising field of &struct ethtool_cmd. + * + * If @autoneg is non-zero, the MAC is configured to send and/or + * receive pause frames according to the result of autonegotiation. + * Otherwise, it is configured directly based on the @rx_pause and + * @tx_pause flags. + */ +struct ethtool_pauseparam { + __u32 cmd; + __u32 autoneg; + __u32 rx_pause; + __u32 tx_pause; +}; + +#define ETH_GSTRING_LEN 32 + +/** + * enum ethtool_stringset - string set ID + * @ETH_SS_TEST: Self-test result names, for use with %ETHTOOL_TEST + * @ETH_SS_STATS: Statistic names, for use with %ETHTOOL_GSTATS + * @ETH_SS_PRIV_FLAGS: Driver private flag names, for use with + * %ETHTOOL_GPFLAGS and %ETHTOOL_SPFLAGS + * @ETH_SS_NTUPLE_FILTERS: Previously used with %ETHTOOL_GRXNTUPLE; + * now deprecated + * @ETH_SS_FEATURES: Device feature names + * @ETH_SS_RSS_HASH_FUNCS: RSS hush function names + * @ETH_SS_PHY_STATS: Statistic names, for use with %ETHTOOL_GPHYSTATS + * @ETH_SS_PHY_TUNABLES: PHY tunable names + */ +enum ethtool_stringset { + ETH_SS_TEST = 0, + ETH_SS_STATS, + ETH_SS_PRIV_FLAGS, + ETH_SS_NTUPLE_FILTERS, + ETH_SS_FEATURES, + ETH_SS_RSS_HASH_FUNCS, + ETH_SS_TUNABLES, + ETH_SS_PHY_STATS, + ETH_SS_PHY_TUNABLES, +}; + +/** + * struct ethtool_gstrings - string set for data tagging + * @cmd: Command number = %ETHTOOL_GSTRINGS + * @string_set: String set ID; one of &enum ethtool_stringset + * @len: On return, the number of strings in the string set + * @data: Buffer for strings. Each string is null-padded to a size of + * %ETH_GSTRING_LEN. + * + * Users must use %ETHTOOL_GSSET_INFO to find the number of strings in + * the string set. They must allocate a buffer of the appropriate + * size immediately following this structure. + */ +struct ethtool_gstrings { + __u32 cmd; + __u32 string_set; + __u32 len; + __u8 data[0]; +}; + +/** + * struct ethtool_sset_info - string set information + * @cmd: Command number = %ETHTOOL_GSSET_INFO + * @sset_mask: On entry, a bitmask of string sets to query, with bits + * numbered according to &enum ethtool_stringset. On return, a + * bitmask of those string sets queried that are supported. + * @data: Buffer for string set sizes. On return, this contains the + * size of each string set that was queried and supported, in + * order of ID. + * + * Example: The user passes in @sset_mask = 0x7 (sets 0, 1, 2) and on + * return @sset_mask == 0x6 (sets 1, 2). Then @data[0] contains the + * size of set 1 and @data[1] contains the size of set 2. + * + * Users must allocate a buffer of the appropriate size (4 * number of + * sets queried) immediately following this structure. + */ +struct ethtool_sset_info { + __u32 cmd; + __u32 reserved; + __u64 sset_mask; + __u32 data[0]; +}; + +/** + * enum ethtool_test_flags - flags definition of ethtool_test + * @ETH_TEST_FL_OFFLINE: if set perform online and offline tests, otherwise + * only online tests. + * @ETH_TEST_FL_FAILED: Driver set this flag if test fails. + * @ETH_TEST_FL_EXTERNAL_LB: Application request to perform external loopback + * test. + * @ETH_TEST_FL_EXTERNAL_LB_DONE: Driver performed the external loopback test + */ + +enum ethtool_test_flags { + ETH_TEST_FL_OFFLINE = (1 << 0), + ETH_TEST_FL_FAILED = (1 << 1), + ETH_TEST_FL_EXTERNAL_LB = (1 << 2), + ETH_TEST_FL_EXTERNAL_LB_DONE = (1 << 3), +}; + +/** + * struct ethtool_test - device self-test invocation + * @cmd: Command number = %ETHTOOL_TEST + * @flags: A bitmask of flags from &enum ethtool_test_flags. Some + * flags may be set by the user on entry; others may be set by + * the driver on return. + * @len: On return, the number of test results + * @data: Array of test results + * + * Users must use %ETHTOOL_GSSET_INFO or %ETHTOOL_GDRVINFO to find the + * number of test results that will be returned. They must allocate a + * buffer of the appropriate size (8 * number of results) immediately + * following this structure. + */ +struct ethtool_test { + __u32 cmd; + __u32 flags; + __u32 reserved; + __u32 len; + __u64 data[0]; +}; + +/** + * struct ethtool_stats - device-specific statistics + * @cmd: Command number = %ETHTOOL_GSTATS + * @n_stats: On return, the number of statistics + * @data: Array of statistics + * + * Users must use %ETHTOOL_GSSET_INFO or %ETHTOOL_GDRVINFO to find the + * number of statistics that will be returned. They must allocate a + * buffer of the appropriate size (8 * number of statistics) + * immediately following this structure. + */ +struct ethtool_stats { + __u32 cmd; + __u32 n_stats; + __u64 data[0]; +}; + +/** + * struct ethtool_perm_addr - permanent hardware address + * @cmd: Command number = %ETHTOOL_GPERMADDR + * @size: On entry, the size of the buffer. On return, the size of the + * address. The command fails if the buffer is too small. + * @data: Buffer for the address + * + * Users must allocate the buffer immediately following this structure. + * A buffer size of %MAX_ADDR_LEN should be sufficient for any address + * type. + */ +struct ethtool_perm_addr { + __u32 cmd; + __u32 size; + __u8 data[0]; +}; + +/* boolean flags controlling per-interface behavior characteristics. + * When reading, the flag indicates whether or not a certain behavior + * is enabled/present. When writing, the flag indicates whether + * or not the driver should turn on (set) or off (clear) a behavior. + * + * Some behaviors may read-only (unconditionally absent or present). + * If such is the case, return EINVAL in the set-flags operation if the + * flag differs from the read-only value. + */ +enum ethtool_flags { + ETH_FLAG_TXVLAN = (1 << 7), /* TX VLAN offload enabled */ + ETH_FLAG_RXVLAN = (1 << 8), /* RX VLAN offload enabled */ + ETH_FLAG_LRO = (1 << 15), /* LRO is enabled */ + ETH_FLAG_NTUPLE = (1 << 27), /* N-tuple filters enabled */ + ETH_FLAG_RXHASH = (1 << 28), +}; + +/* The following structures are for supporting RX network flow + * classification and RX n-tuple configuration. Note, all multibyte + * fields, e.g., ip4src, ip4dst, psrc, pdst, spi, etc. are expected to + * be in network byte order. + */ + +/** + * struct ethtool_tcpip4_spec - flow specification for TCP/IPv4 etc. + * @ip4src: Source host + * @ip4dst: Destination host + * @psrc: Source port + * @pdst: Destination port + * @tos: Type-of-service + * + * This can be used to specify a TCP/IPv4, UDP/IPv4 or SCTP/IPv4 flow. + */ +struct ethtool_tcpip4_spec { + __be32 ip4src; + __be32 ip4dst; + __be16 psrc; + __be16 pdst; + __u8 tos; +}; + +/** + * struct ethtool_ah_espip4_spec - flow specification for IPsec/IPv4 + * @ip4src: Source host + * @ip4dst: Destination host + * @spi: Security parameters index + * @tos: Type-of-service + * + * This can be used to specify an IPsec transport or tunnel over IPv4. + */ +struct ethtool_ah_espip4_spec { + __be32 ip4src; + __be32 ip4dst; + __be32 spi; + __u8 tos; +}; + +#define ETH_RX_NFC_IP4 1 + +/** + * struct ethtool_usrip4_spec - general flow specification for IPv4 + * @ip4src: Source host + * @ip4dst: Destination host + * @l4_4_bytes: First 4 bytes of transport (layer 4) header + * @tos: Type-of-service + * @ip_ver: Value must be %ETH_RX_NFC_IP4; mask must be 0 + * @proto: Transport protocol number; mask must be 0 + */ +struct ethtool_usrip4_spec { + __be32 ip4src; + __be32 ip4dst; + __be32 l4_4_bytes; + __u8 tos; + __u8 ip_ver; + __u8 proto; +}; + +/** + * struct ethtool_tcpip6_spec - flow specification for TCP/IPv6 etc. + * @ip6src: Source host + * @ip6dst: Destination host + * @psrc: Source port + * @pdst: Destination port + * @tclass: Traffic Class + * + * This can be used to specify a TCP/IPv6, UDP/IPv6 or SCTP/IPv6 flow. + */ +struct ethtool_tcpip6_spec { + __be32 ip6src[4]; + __be32 ip6dst[4]; + __be16 psrc; + __be16 pdst; + __u8 tclass; +}; + +/** + * struct ethtool_ah_espip6_spec - flow specification for IPsec/IPv6 + * @ip6src: Source host + * @ip6dst: Destination host + * @spi: Security parameters index + * @tclass: Traffic Class + * + * This can be used to specify an IPsec transport or tunnel over IPv6. + */ +struct ethtool_ah_espip6_spec { + __be32 ip6src[4]; + __be32 ip6dst[4]; + __be32 spi; + __u8 tclass; +}; + +/** + * struct ethtool_usrip6_spec - general flow specification for IPv6 + * @ip6src: Source host + * @ip6dst: Destination host + * @l4_4_bytes: First 4 bytes of transport (layer 4) header + * @tclass: Traffic Class + * @l4_proto: Transport protocol number (nexthdr after any Extension Headers) + */ +struct ethtool_usrip6_spec { + __be32 ip6src[4]; + __be32 ip6dst[4]; + __be32 l4_4_bytes; + __u8 tclass; + __u8 l4_proto; +}; + +union ethtool_flow_union { + struct ethtool_tcpip4_spec tcp_ip4_spec; + struct ethtool_tcpip4_spec udp_ip4_spec; + struct ethtool_tcpip4_spec sctp_ip4_spec; + struct ethtool_ah_espip4_spec ah_ip4_spec; + struct ethtool_ah_espip4_spec esp_ip4_spec; + struct ethtool_usrip4_spec usr_ip4_spec; + struct ethtool_tcpip6_spec tcp_ip6_spec; + struct ethtool_tcpip6_spec udp_ip6_spec; + struct ethtool_tcpip6_spec sctp_ip6_spec; + struct ethtool_ah_espip6_spec ah_ip6_spec; + struct ethtool_ah_espip6_spec esp_ip6_spec; + struct ethtool_usrip6_spec usr_ip6_spec; + struct ethhdr ether_spec; + __u8 hdata[52]; +}; + +/** + * struct ethtool_flow_ext - additional RX flow fields + * @h_dest: destination MAC address + * @vlan_etype: VLAN EtherType + * @vlan_tci: VLAN tag control information + * @data: user defined data + * + * Note, @vlan_etype, @vlan_tci, and @data are only valid if %FLOW_EXT + * is set in &struct ethtool_rx_flow_spec @flow_type. + * @h_dest is valid if %FLOW_MAC_EXT is set. + */ +struct ethtool_flow_ext { + __u8 padding[2]; + unsigned char h_dest[ETH_ALEN]; + __be16 vlan_etype; + __be16 vlan_tci; + __be32 data[2]; +}; + +/** + * struct ethtool_rx_flow_spec - classification rule for RX flows + * @flow_type: Type of match to perform, e.g. %TCP_V4_FLOW + * @h_u: Flow fields to match (dependent on @flow_type) + * @h_ext: Additional fields to match + * @m_u: Masks for flow field bits to be matched + * @m_ext: Masks for additional field bits to be matched + * Note, all additional fields must be ignored unless @flow_type + * includes the %FLOW_EXT or %FLOW_MAC_EXT flag + * (see &struct ethtool_flow_ext description). + * @ring_cookie: RX ring/queue index to deliver to, or %RX_CLS_FLOW_DISC + * if packets should be discarded, or %RX_CLS_FLOW_WAKE if the + * packets should be used for Wake-on-LAN with %WAKE_FILTER + * @location: Location of rule in the table. Locations must be + * numbered such that a flow matching multiple rules will be + * classified according to the first (lowest numbered) rule. + */ +struct ethtool_rx_flow_spec { + __u32 flow_type; + union ethtool_flow_union h_u; + struct ethtool_flow_ext h_ext; + union ethtool_flow_union m_u; + struct ethtool_flow_ext m_ext; + __u64 ring_cookie; + __u32 location; +}; + +/* How rings are layed out when accessing virtual functions or + * offloaded queues is device specific. To allow users to do flow + * steering and specify these queues the ring cookie is partitioned + * into a 32bit queue index with an 8 bit virtual function id. + * This also leaves the 3bytes for further specifiers. It is possible + * future devices may support more than 256 virtual functions if + * devices start supporting PCIe w/ARI. However at the moment I + * do not know of any devices that support this so I do not reserve + * space for this at this time. If a future patch consumes the next + * byte it should be aware of this possiblity. + */ +#define ETHTOOL_RX_FLOW_SPEC_RING 0x00000000FFFFFFFFLL +#define ETHTOOL_RX_FLOW_SPEC_RING_VF 0x000000FF00000000LL +#define ETHTOOL_RX_FLOW_SPEC_RING_VF_OFF 32 +static __inline__ __u64 ethtool_get_flow_spec_ring(__u64 ring_cookie) +{ + return ETHTOOL_RX_FLOW_SPEC_RING & ring_cookie; +} + +static __inline__ __u64 ethtool_get_flow_spec_ring_vf(__u64 ring_cookie) +{ + return (ETHTOOL_RX_FLOW_SPEC_RING_VF & ring_cookie) >> + ETHTOOL_RX_FLOW_SPEC_RING_VF_OFF; +} + +/** + * struct ethtool_rxnfc - command to get or set RX flow classification rules + * @cmd: Specific command number - %ETHTOOL_GRXFH, %ETHTOOL_SRXFH, + * %ETHTOOL_GRXRINGS, %ETHTOOL_GRXCLSRLCNT, %ETHTOOL_GRXCLSRULE, + * %ETHTOOL_GRXCLSRLALL, %ETHTOOL_SRXCLSRLDEL or %ETHTOOL_SRXCLSRLINS + * @flow_type: Type of flow to be affected, e.g. %TCP_V4_FLOW + * @data: Command-dependent value + * @fs: Flow classification rule + * @rss_context: RSS context to be affected + * @rule_cnt: Number of rules to be affected + * @rule_locs: Array of used rule locations + * + * For %ETHTOOL_GRXFH and %ETHTOOL_SRXFH, @data is a bitmask indicating + * the fields included in the flow hash, e.g. %RXH_IP_SRC. The following + * structure fields must not be used, except that if @flow_type includes + * the %FLOW_RSS flag, then @rss_context determines which RSS context to + * act on. + * + * For %ETHTOOL_GRXRINGS, @data is set to the number of RX rings/queues + * on return. + * + * For %ETHTOOL_GRXCLSRLCNT, @rule_cnt is set to the number of defined + * rules on return. If @data is non-zero on return then it is the + * size of the rule table, plus the flag %RX_CLS_LOC_SPECIAL if the + * driver supports any special location values. If that flag is not + * set in @data then special location values should not be used. + * + * For %ETHTOOL_GRXCLSRULE, @fs.@location specifies the location of an + * existing rule on entry and @fs contains the rule on return; if + * @fs.@flow_type includes the %FLOW_RSS flag, then @rss_context is + * filled with the RSS context ID associated with the rule. + * + * For %ETHTOOL_GRXCLSRLALL, @rule_cnt specifies the array size of the + * user buffer for @rule_locs on entry. On return, @data is the size + * of the rule table, @rule_cnt is the number of defined rules, and + * @rule_locs contains the locations of the defined rules. Drivers + * must use the second parameter to get_rxnfc() instead of @rule_locs. + * + * For %ETHTOOL_SRXCLSRLINS, @fs specifies the rule to add or update. + * @fs.@location either specifies the location to use or is a special + * location value with %RX_CLS_LOC_SPECIAL flag set. On return, + * @fs.@location is the actual rule location. If @fs.@flow_type + * includes the %FLOW_RSS flag, @rss_context is the RSS context ID to + * use for flow spreading traffic which matches this rule. The value + * from the rxfh indirection table will be added to @fs.@ring_cookie + * to choose which ring to deliver to. + * + * For %ETHTOOL_SRXCLSRLDEL, @fs.@location specifies the location of an + * existing rule on entry. + * + * A driver supporting the special location values for + * %ETHTOOL_SRXCLSRLINS may add the rule at any suitable unused + * location, and may remove a rule at a later location (lower + * priority) that matches exactly the same set of flows. The special + * values are %RX_CLS_LOC_ANY, selecting any location; + * %RX_CLS_LOC_FIRST, selecting the first suitable location (maximum + * priority); and %RX_CLS_LOC_LAST, selecting the last suitable + * location (minimum priority). Additional special values may be + * defined in future and drivers must return -%EINVAL for any + * unrecognised value. + */ +struct ethtool_rxnfc { + __u32 cmd; + __u32 flow_type; + __u64 data; + struct ethtool_rx_flow_spec fs; + union { + __u32 rule_cnt; + __u32 rss_context; + }; + __u32 rule_locs[0]; +}; + + +/** + * struct ethtool_rxfh_indir - command to get or set RX flow hash indirection + * @cmd: Specific command number - %ETHTOOL_GRXFHINDIR or %ETHTOOL_SRXFHINDIR + * @size: On entry, the array size of the user buffer, which may be zero. + * On return from %ETHTOOL_GRXFHINDIR, the array size of the hardware + * indirection table. + * @ring_index: RX ring/queue index for each hash value + * + * For %ETHTOOL_GRXFHINDIR, a @size of zero means that only the size + * should be returned. For %ETHTOOL_SRXFHINDIR, a @size of zero means + * the table should be reset to default values. This last feature + * is not supported by the original implementations. + */ +struct ethtool_rxfh_indir { + __u32 cmd; + __u32 size; + __u32 ring_index[0]; +}; + +/** + * struct ethtool_rxfh - command to get/set RX flow hash indir or/and hash key. + * @cmd: Specific command number - %ETHTOOL_GRSSH or %ETHTOOL_SRSSH + * @rss_context: RSS context identifier. Context 0 is the default for normal + * traffic; other contexts can be referenced as the destination for RX flow + * classification rules. %ETH_RXFH_CONTEXT_ALLOC is used with command + * %ETHTOOL_SRSSH to allocate a new RSS context; on return this field will + * contain the ID of the newly allocated context. + * @indir_size: On entry, the array size of the user buffer for the + * indirection table, which may be zero, or (for %ETHTOOL_SRSSH), + * %ETH_RXFH_INDIR_NO_CHANGE. On return from %ETHTOOL_GRSSH, + * the array size of the hardware indirection table. + * @key_size: On entry, the array size of the user buffer for the hash key, + * which may be zero. On return from %ETHTOOL_GRSSH, the size of the + * hardware hash key. + * @hfunc: Defines the current RSS hash function used by HW (or to be set to). + * Valid values are one of the %ETH_RSS_HASH_*. + * @rsvd: Reserved for future extensions. + * @rss_config: RX ring/queue index for each hash value i.e., indirection table + * of @indir_size __u32 elements, followed by hash key of @key_size + * bytes. + * + * For %ETHTOOL_GRSSH, a @indir_size and key_size of zero means that only the + * size should be returned. For %ETHTOOL_SRSSH, an @indir_size of + * %ETH_RXFH_INDIR_NO_CHANGE means that indir table setting is not requested + * and a @indir_size of zero means the indir table should be reset to default + * values (if @rss_context == 0) or that the RSS context should be deleted. + * An hfunc of zero means that hash function setting is not requested. + */ +struct ethtool_rxfh { + __u32 cmd; + __u32 rss_context; + __u32 indir_size; + __u32 key_size; + __u8 hfunc; + __u8 rsvd8[3]; + __u32 rsvd32; + __u32 rss_config[0]; +}; +#define ETH_RXFH_CONTEXT_ALLOC 0xffffffff +#define ETH_RXFH_INDIR_NO_CHANGE 0xffffffff + +/** + * struct ethtool_rx_ntuple_flow_spec - specification for RX flow filter + * @flow_type: Type of match to perform, e.g. %TCP_V4_FLOW + * @h_u: Flow field values to match (dependent on @flow_type) + * @m_u: Masks for flow field value bits to be ignored + * @vlan_tag: VLAN tag to match + * @vlan_tag_mask: Mask for VLAN tag bits to be ignored + * @data: Driver-dependent data to match + * @data_mask: Mask for driver-dependent data bits to be ignored + * @action: RX ring/queue index to deliver to (non-negative) or other action + * (negative, e.g. %ETHTOOL_RXNTUPLE_ACTION_DROP) + * + * For flow types %TCP_V4_FLOW, %UDP_V4_FLOW and %SCTP_V4_FLOW, where + * a field value and mask are both zero this is treated as if all mask + * bits are set i.e. the field is ignored. + */ +struct ethtool_rx_ntuple_flow_spec { + __u32 flow_type; + union { + struct ethtool_tcpip4_spec tcp_ip4_spec; + struct ethtool_tcpip4_spec udp_ip4_spec; + struct ethtool_tcpip4_spec sctp_ip4_spec; + struct ethtool_ah_espip4_spec ah_ip4_spec; + struct ethtool_ah_espip4_spec esp_ip4_spec; + struct ethtool_usrip4_spec usr_ip4_spec; + struct ethhdr ether_spec; + __u8 hdata[72]; + } h_u, m_u; + + __u16 vlan_tag; + __u16 vlan_tag_mask; + __u64 data; + __u64 data_mask; + + __s32 action; +#define ETHTOOL_RXNTUPLE_ACTION_DROP (-1) /* drop packet */ +#define ETHTOOL_RXNTUPLE_ACTION_CLEAR (-2) /* clear filter */ +}; + +/** + * struct ethtool_rx_ntuple - command to set or clear RX flow filter + * @cmd: Command number - %ETHTOOL_SRXNTUPLE + * @fs: Flow filter specification + */ +struct ethtool_rx_ntuple { + __u32 cmd; + struct ethtool_rx_ntuple_flow_spec fs; +}; + +#define ETHTOOL_FLASH_MAX_FILENAME 128 +enum ethtool_flash_op_type { + ETHTOOL_FLASH_ALL_REGIONS = 0, +}; + +/* for passing firmware flashing related parameters */ +struct ethtool_flash { + __u32 cmd; + __u32 region; + char data[ETHTOOL_FLASH_MAX_FILENAME]; +}; + +/** + * struct ethtool_dump - used for retrieving, setting device dump + * @cmd: Command number - %ETHTOOL_GET_DUMP_FLAG, %ETHTOOL_GET_DUMP_DATA, or + * %ETHTOOL_SET_DUMP + * @version: FW version of the dump, filled in by driver + * @flag: driver dependent flag for dump setting, filled in by driver during + * get and filled in by ethtool for set operation. + * flag must be initialized by macro ETH_FW_DUMP_DISABLE value when + * firmware dump is disabled. + * @len: length of dump data, used as the length of the user buffer on entry to + * %ETHTOOL_GET_DUMP_DATA and this is returned as dump length by driver + * for %ETHTOOL_GET_DUMP_FLAG command + * @data: data collected for get dump data operation + */ +struct ethtool_dump { + __u32 cmd; + __u32 version; + __u32 flag; + __u32 len; + __u8 data[0]; +}; + +#define ETH_FW_DUMP_DISABLE 0 + +/* for returning and changing feature sets */ + +/** + * struct ethtool_get_features_block - block with state of 32 features + * @available: mask of changeable features + * @requested: mask of features requested to be enabled if possible + * @active: mask of currently enabled features + * @never_changed: mask of features not changeable for any device + */ +struct ethtool_get_features_block { + __u32 available; + __u32 requested; + __u32 active; + __u32 never_changed; +}; + +/** + * struct ethtool_gfeatures - command to get state of device's features + * @cmd: command number = %ETHTOOL_GFEATURES + * @size: On entry, the number of elements in the features[] array; + * on return, the number of elements in features[] needed to hold + * all features + * @features: state of features + */ +struct ethtool_gfeatures { + __u32 cmd; + __u32 size; + struct ethtool_get_features_block features[0]; +}; + +/** + * struct ethtool_set_features_block - block with request for 32 features + * @valid: mask of features to be changed + * @requested: values of features to be changed + */ +struct ethtool_set_features_block { + __u32 valid; + __u32 requested; +}; + +/** + * struct ethtool_sfeatures - command to request change in device's features + * @cmd: command number = %ETHTOOL_SFEATURES + * @size: array size of the features[] array + * @features: feature change masks + */ +struct ethtool_sfeatures { + __u32 cmd; + __u32 size; + struct ethtool_set_features_block features[0]; +}; + +/** + * struct ethtool_ts_info - holds a device's timestamping and PHC association + * @cmd: command number = %ETHTOOL_GET_TS_INFO + * @so_timestamping: bit mask of the sum of the supported SO_TIMESTAMPING flags + * @phc_index: device index of the associated PHC, or -1 if there is none + * @tx_types: bit mask of the supported hwtstamp_tx_types enumeration values + * @rx_filters: bit mask of the supported hwtstamp_rx_filters enumeration values + * + * The bits in the 'tx_types' and 'rx_filters' fields correspond to + * the 'hwtstamp_tx_types' and 'hwtstamp_rx_filters' enumeration values, + * respectively. For example, if the device supports HWTSTAMP_TX_ON, + * then (1 << HWTSTAMP_TX_ON) in 'tx_types' will be set. + * + * Drivers should only report the filters they actually support without + * upscaling in the SIOCSHWTSTAMP ioctl. If the SIOCSHWSTAMP request for + * HWTSTAMP_FILTER_V1_SYNC is supported by HWTSTAMP_FILTER_V1_EVENT, then the + * driver should only report HWTSTAMP_FILTER_V1_EVENT in this op. + */ +struct ethtool_ts_info { + __u32 cmd; + __u32 so_timestamping; + __s32 phc_index; + __u32 tx_types; + __u32 tx_reserved[3]; + __u32 rx_filters; + __u32 rx_reserved[3]; +}; + +/* + * %ETHTOOL_SFEATURES changes features present in features[].valid to the + * values of corresponding bits in features[].requested. Bits in .requested + * not set in .valid or not changeable are ignored. + * + * Returns %EINVAL when .valid contains undefined or never-changeable bits + * or size is not equal to required number of features words (32-bit blocks). + * Returns >= 0 if request was completed; bits set in the value mean: + * %ETHTOOL_F_UNSUPPORTED - there were bits set in .valid that are not + * changeable (not present in %ETHTOOL_GFEATURES' features[].available) + * those bits were ignored. + * %ETHTOOL_F_WISH - some or all changes requested were recorded but the + * resulting state of bits masked by .valid is not equal to .requested. + * Probably there are other device-specific constraints on some features + * in the set. When %ETHTOOL_F_UNSUPPORTED is set, .valid is considered + * here as though ignored bits were cleared. + * %ETHTOOL_F_COMPAT - some or all changes requested were made by calling + * compatibility functions. Requested offload state cannot be properly + * managed by kernel. + * + * Meaning of bits in the masks are obtained by %ETHTOOL_GSSET_INFO (number of + * bits in the arrays - always multiple of 32) and %ETHTOOL_GSTRINGS commands + * for ETH_SS_FEATURES string set. First entry in the table corresponds to least + * significant bit in features[0] fields. Empty strings mark undefined features. + */ +enum ethtool_sfeatures_retval_bits { + ETHTOOL_F_UNSUPPORTED__BIT, + ETHTOOL_F_WISH__BIT, + ETHTOOL_F_COMPAT__BIT, +}; + +#define ETHTOOL_F_UNSUPPORTED (1 << ETHTOOL_F_UNSUPPORTED__BIT) +#define ETHTOOL_F_WISH (1 << ETHTOOL_F_WISH__BIT) +#define ETHTOOL_F_COMPAT (1 << ETHTOOL_F_COMPAT__BIT) + +#define MAX_NUM_QUEUE 4096 + +/** + * struct ethtool_per_queue_op - apply sub command to the queues in mask. + * @cmd: ETHTOOL_PERQUEUE + * @sub_command: the sub command which apply to each queues + * @queue_mask: Bitmap of the queues which sub command apply to + * @data: A complete command structure following for each of the queues addressed + */ +struct ethtool_per_queue_op { + __u32 cmd; + __u32 sub_command; + __u32 queue_mask[__KERNEL_DIV_ROUND_UP(MAX_NUM_QUEUE, 32)]; + char data[]; +}; + +/** + * struct ethtool_fecparam - Ethernet forward error correction(fec) parameters + * @cmd: Command number = %ETHTOOL_GFECPARAM or %ETHTOOL_SFECPARAM + * @active_fec: FEC mode which is active on porte + * @fec: Bitmask of supported/configured FEC modes + * @rsvd: Reserved for future extensions. i.e FEC bypass feature. + * + * Drivers should reject a non-zero setting of @autoneg when + * autoneogotiation is disabled (or not supported) for the link. + * + */ +struct ethtool_fecparam { + __u32 cmd; + /* bitmask of FEC modes */ + __u32 active_fec; + __u32 fec; + __u32 reserved; +}; + +/** + * enum ethtool_fec_config_bits - flags definition of ethtool_fec_configuration + * @ETHTOOL_FEC_NONE: FEC mode configuration is not supported + * @ETHTOOL_FEC_AUTO: Default/Best FEC mode provided by driver + * @ETHTOOL_FEC_OFF: No FEC Mode + * @ETHTOOL_FEC_RS: Reed-Solomon Forward Error Detection mode + * @ETHTOOL_FEC_BASER: Base-R/Reed-Solomon Forward Error Detection mode + */ +enum ethtool_fec_config_bits { + ETHTOOL_FEC_NONE_BIT, + ETHTOOL_FEC_AUTO_BIT, + ETHTOOL_FEC_OFF_BIT, + ETHTOOL_FEC_RS_BIT, + ETHTOOL_FEC_BASER_BIT, +}; + +#define ETHTOOL_FEC_NONE (1 << ETHTOOL_FEC_NONE_BIT) +#define ETHTOOL_FEC_AUTO (1 << ETHTOOL_FEC_AUTO_BIT) +#define ETHTOOL_FEC_OFF (1 << ETHTOOL_FEC_OFF_BIT) +#define ETHTOOL_FEC_RS (1 << ETHTOOL_FEC_RS_BIT) +#define ETHTOOL_FEC_BASER (1 << ETHTOOL_FEC_BASER_BIT) + +/* CMDs currently supported */ +#define ETHTOOL_GSET 0x00000001 /* DEPRECATED, Get settings. + * Please use ETHTOOL_GLINKSETTINGS + */ +#define ETHTOOL_SSET 0x00000002 /* DEPRECATED, Set settings. + * Please use ETHTOOL_SLINKSETTINGS + */ +#define ETHTOOL_GDRVINFO 0x00000003 /* Get driver info. */ +#define ETHTOOL_GREGS 0x00000004 /* Get NIC registers. */ +#define ETHTOOL_GWOL 0x00000005 /* Get wake-on-lan options. */ +#define ETHTOOL_SWOL 0x00000006 /* Set wake-on-lan options. */ +#define ETHTOOL_GMSGLVL 0x00000007 /* Get driver message level */ +#define ETHTOOL_SMSGLVL 0x00000008 /* Set driver msg level. */ +#define ETHTOOL_NWAY_RST 0x00000009 /* Restart autonegotiation. */ +/* Get link status for host, i.e. whether the interface *and* the + * physical port (if there is one) are up (ethtool_value). */ +#define ETHTOOL_GLINK 0x0000000a +#define ETHTOOL_GEEPROM 0x0000000b /* Get EEPROM data */ +#define ETHTOOL_SEEPROM 0x0000000c /* Set EEPROM data. */ +#define ETHTOOL_GCOALESCE 0x0000000e /* Get coalesce config */ +#define ETHTOOL_SCOALESCE 0x0000000f /* Set coalesce config. */ +#define ETHTOOL_GRINGPARAM 0x00000010 /* Get ring parameters */ +#define ETHTOOL_SRINGPARAM 0x00000011 /* Set ring parameters. */ +#define ETHTOOL_GPAUSEPARAM 0x00000012 /* Get pause parameters */ +#define ETHTOOL_SPAUSEPARAM 0x00000013 /* Set pause parameters. */ +#define ETHTOOL_GRXCSUM 0x00000014 /* Get RX hw csum enable (ethtool_value) */ +#define ETHTOOL_SRXCSUM 0x00000015 /* Set RX hw csum enable (ethtool_value) */ +#define ETHTOOL_GTXCSUM 0x00000016 /* Get TX hw csum enable (ethtool_value) */ +#define ETHTOOL_STXCSUM 0x00000017 /* Set TX hw csum enable (ethtool_value) */ +#define ETHTOOL_GSG 0x00000018 /* Get scatter-gather enable + * (ethtool_value) */ +#define ETHTOOL_SSG 0x00000019 /* Set scatter-gather enable + * (ethtool_value). */ +#define ETHTOOL_TEST 0x0000001a /* execute NIC self-test. */ +#define ETHTOOL_GSTRINGS 0x0000001b /* get specified string set */ +#define ETHTOOL_PHYS_ID 0x0000001c /* identify the NIC */ +#define ETHTOOL_GSTATS 0x0000001d /* get NIC-specific statistics */ +#define ETHTOOL_GTSO 0x0000001e /* Get TSO enable (ethtool_value) */ +#define ETHTOOL_STSO 0x0000001f /* Set TSO enable (ethtool_value) */ +#define ETHTOOL_GPERMADDR 0x00000020 /* Get permanent hardware address */ +#define ETHTOOL_GUFO 0x00000021 /* Get UFO enable (ethtool_value) */ +#define ETHTOOL_SUFO 0x00000022 /* Set UFO enable (ethtool_value) */ +#define ETHTOOL_GGSO 0x00000023 /* Get GSO enable (ethtool_value) */ +#define ETHTOOL_SGSO 0x00000024 /* Set GSO enable (ethtool_value) */ +#define ETHTOOL_GFLAGS 0x00000025 /* Get flags bitmap(ethtool_value) */ +#define ETHTOOL_SFLAGS 0x00000026 /* Set flags bitmap(ethtool_value) */ +#define ETHTOOL_GPFLAGS 0x00000027 /* Get driver-private flags bitmap */ +#define ETHTOOL_SPFLAGS 0x00000028 /* Set driver-private flags bitmap */ + +#define ETHTOOL_GRXFH 0x00000029 /* Get RX flow hash configuration */ +#define ETHTOOL_SRXFH 0x0000002a /* Set RX flow hash configuration */ +#define ETHTOOL_GGRO 0x0000002b /* Get GRO enable (ethtool_value) */ +#define ETHTOOL_SGRO 0x0000002c /* Set GRO enable (ethtool_value) */ +#define ETHTOOL_GRXRINGS 0x0000002d /* Get RX rings available for LB */ +#define ETHTOOL_GRXCLSRLCNT 0x0000002e /* Get RX class rule count */ +#define ETHTOOL_GRXCLSRULE 0x0000002f /* Get RX classification rule */ +#define ETHTOOL_GRXCLSRLALL 0x00000030 /* Get all RX classification rule */ +#define ETHTOOL_SRXCLSRLDEL 0x00000031 /* Delete RX classification rule */ +#define ETHTOOL_SRXCLSRLINS 0x00000032 /* Insert RX classification rule */ +#define ETHTOOL_FLASHDEV 0x00000033 /* Flash firmware to device */ +#define ETHTOOL_RESET 0x00000034 /* Reset hardware */ +#define ETHTOOL_SRXNTUPLE 0x00000035 /* Add an n-tuple filter to device */ +#define ETHTOOL_GRXNTUPLE 0x00000036 /* deprecated */ +#define ETHTOOL_GSSET_INFO 0x00000037 /* Get string set info */ +#define ETHTOOL_GRXFHINDIR 0x00000038 /* Get RX flow hash indir'n table */ +#define ETHTOOL_SRXFHINDIR 0x00000039 /* Set RX flow hash indir'n table */ + +#define ETHTOOL_GFEATURES 0x0000003a /* Get device offload settings */ +#define ETHTOOL_SFEATURES 0x0000003b /* Change device offload settings */ +#define ETHTOOL_GCHANNELS 0x0000003c /* Get no of channels */ +#define ETHTOOL_SCHANNELS 0x0000003d /* Set no of channels */ +#define ETHTOOL_SET_DUMP 0x0000003e /* Set dump settings */ +#define ETHTOOL_GET_DUMP_FLAG 0x0000003f /* Get dump settings */ +#define ETHTOOL_GET_DUMP_DATA 0x00000040 /* Get dump data */ +#define ETHTOOL_GET_TS_INFO 0x00000041 /* Get time stamping and PHC info */ +#define ETHTOOL_GMODULEINFO 0x00000042 /* Get plug-in module information */ +#define ETHTOOL_GMODULEEEPROM 0x00000043 /* Get plug-in module eeprom */ +#define ETHTOOL_GEEE 0x00000044 /* Get EEE settings */ +#define ETHTOOL_SEEE 0x00000045 /* Set EEE settings */ + +#define ETHTOOL_GRSSH 0x00000046 /* Get RX flow hash configuration */ +#define ETHTOOL_SRSSH 0x00000047 /* Set RX flow hash configuration */ +#define ETHTOOL_GTUNABLE 0x00000048 /* Get tunable configuration */ +#define ETHTOOL_STUNABLE 0x00000049 /* Set tunable configuration */ +#define ETHTOOL_GPHYSTATS 0x0000004a /* get PHY-specific statistics */ + +#define ETHTOOL_PERQUEUE 0x0000004b /* Set per queue options */ + +#define ETHTOOL_GLINKSETTINGS 0x0000004c /* Get ethtool_link_settings */ +#define ETHTOOL_SLINKSETTINGS 0x0000004d /* Set ethtool_link_settings */ +#define ETHTOOL_PHY_GTUNABLE 0x0000004e /* Get PHY tunable configuration */ +#define ETHTOOL_PHY_STUNABLE 0x0000004f /* Set PHY tunable configuration */ +#define ETHTOOL_GFECPARAM 0x00000050 /* Get FEC settings */ +#define ETHTOOL_SFECPARAM 0x00000051 /* Set FEC settings */ + +/* compatibility with older code */ +#define SPARC_ETH_GSET ETHTOOL_GSET +#define SPARC_ETH_SSET ETHTOOL_SSET + +/* Link mode bit indices */ +enum ethtool_link_mode_bit_indices { + ETHTOOL_LINK_MODE_10baseT_Half_BIT = 0, + ETHTOOL_LINK_MODE_10baseT_Full_BIT = 1, + ETHTOOL_LINK_MODE_100baseT_Half_BIT = 2, + ETHTOOL_LINK_MODE_100baseT_Full_BIT = 3, + ETHTOOL_LINK_MODE_1000baseT_Half_BIT = 4, + ETHTOOL_LINK_MODE_1000baseT_Full_BIT = 5, + ETHTOOL_LINK_MODE_Autoneg_BIT = 6, + ETHTOOL_LINK_MODE_TP_BIT = 7, + ETHTOOL_LINK_MODE_AUI_BIT = 8, + ETHTOOL_LINK_MODE_MII_BIT = 9, + ETHTOOL_LINK_MODE_FIBRE_BIT = 10, + ETHTOOL_LINK_MODE_BNC_BIT = 11, + ETHTOOL_LINK_MODE_10000baseT_Full_BIT = 12, + ETHTOOL_LINK_MODE_Pause_BIT = 13, + ETHTOOL_LINK_MODE_Asym_Pause_BIT = 14, + ETHTOOL_LINK_MODE_2500baseX_Full_BIT = 15, + ETHTOOL_LINK_MODE_Backplane_BIT = 16, + ETHTOOL_LINK_MODE_1000baseKX_Full_BIT = 17, + ETHTOOL_LINK_MODE_10000baseKX4_Full_BIT = 18, + ETHTOOL_LINK_MODE_10000baseKR_Full_BIT = 19, + ETHTOOL_LINK_MODE_10000baseR_FEC_BIT = 20, + ETHTOOL_LINK_MODE_20000baseMLD2_Full_BIT = 21, + ETHTOOL_LINK_MODE_20000baseKR2_Full_BIT = 22, + ETHTOOL_LINK_MODE_40000baseKR4_Full_BIT = 23, + ETHTOOL_LINK_MODE_40000baseCR4_Full_BIT = 24, + ETHTOOL_LINK_MODE_40000baseSR4_Full_BIT = 25, + ETHTOOL_LINK_MODE_40000baseLR4_Full_BIT = 26, + ETHTOOL_LINK_MODE_56000baseKR4_Full_BIT = 27, + ETHTOOL_LINK_MODE_56000baseCR4_Full_BIT = 28, + ETHTOOL_LINK_MODE_56000baseSR4_Full_BIT = 29, + ETHTOOL_LINK_MODE_56000baseLR4_Full_BIT = 30, + ETHTOOL_LINK_MODE_25000baseCR_Full_BIT = 31, + ETHTOOL_LINK_MODE_25000baseKR_Full_BIT = 32, + ETHTOOL_LINK_MODE_25000baseSR_Full_BIT = 33, + ETHTOOL_LINK_MODE_50000baseCR2_Full_BIT = 34, + ETHTOOL_LINK_MODE_50000baseKR2_Full_BIT = 35, + ETHTOOL_LINK_MODE_100000baseKR4_Full_BIT = 36, + ETHTOOL_LINK_MODE_100000baseSR4_Full_BIT = 37, + ETHTOOL_LINK_MODE_100000baseCR4_Full_BIT = 38, + ETHTOOL_LINK_MODE_100000baseLR4_ER4_Full_BIT = 39, + ETHTOOL_LINK_MODE_50000baseSR2_Full_BIT = 40, + ETHTOOL_LINK_MODE_1000baseX_Full_BIT = 41, + ETHTOOL_LINK_MODE_10000baseCR_Full_BIT = 42, + ETHTOOL_LINK_MODE_10000baseSR_Full_BIT = 43, + ETHTOOL_LINK_MODE_10000baseLR_Full_BIT = 44, + ETHTOOL_LINK_MODE_10000baseLRM_Full_BIT = 45, + ETHTOOL_LINK_MODE_10000baseER_Full_BIT = 46, + ETHTOOL_LINK_MODE_2500baseT_Full_BIT = 47, + ETHTOOL_LINK_MODE_5000baseT_Full_BIT = 48, + + ETHTOOL_LINK_MODE_FEC_NONE_BIT = 49, + ETHTOOL_LINK_MODE_FEC_RS_BIT = 50, + ETHTOOL_LINK_MODE_FEC_BASER_BIT = 51, + + /* Last allowed bit for __ETHTOOL_LINK_MODE_LEGACY_MASK is bit + * 31. Please do NOT define any SUPPORTED_* or ADVERTISED_* + * macro for bits > 31. The only way to use indices > 31 is to + * use the new ETHTOOL_GLINKSETTINGS/ETHTOOL_SLINKSETTINGS API. + */ + + __ETHTOOL_LINK_MODE_LAST + = ETHTOOL_LINK_MODE_FEC_BASER_BIT, +}; + +#define __ETHTOOL_LINK_MODE_LEGACY_MASK(base_name) \ + (1UL << (ETHTOOL_LINK_MODE_ ## base_name ## _BIT)) + +/* DEPRECATED macros. Please migrate to + * ETHTOOL_GLINKSETTINGS/ETHTOOL_SLINKSETTINGS API. Please do NOT + * define any new SUPPORTED_* macro for bits > 31. + */ +#define SUPPORTED_10baseT_Half __ETHTOOL_LINK_MODE_LEGACY_MASK(10baseT_Half) +#define SUPPORTED_10baseT_Full __ETHTOOL_LINK_MODE_LEGACY_MASK(10baseT_Full) +#define SUPPORTED_100baseT_Half __ETHTOOL_LINK_MODE_LEGACY_MASK(100baseT_Half) +#define SUPPORTED_100baseT_Full __ETHTOOL_LINK_MODE_LEGACY_MASK(100baseT_Full) +#define SUPPORTED_1000baseT_Half __ETHTOOL_LINK_MODE_LEGACY_MASK(1000baseT_Half) +#define SUPPORTED_1000baseT_Full __ETHTOOL_LINK_MODE_LEGACY_MASK(1000baseT_Full) +#define SUPPORTED_Autoneg __ETHTOOL_LINK_MODE_LEGACY_MASK(Autoneg) +#define SUPPORTED_TP __ETHTOOL_LINK_MODE_LEGACY_MASK(TP) +#define SUPPORTED_AUI __ETHTOOL_LINK_MODE_LEGACY_MASK(AUI) +#define SUPPORTED_MII __ETHTOOL_LINK_MODE_LEGACY_MASK(MII) +#define SUPPORTED_FIBRE __ETHTOOL_LINK_MODE_LEGACY_MASK(FIBRE) +#define SUPPORTED_BNC __ETHTOOL_LINK_MODE_LEGACY_MASK(BNC) +#define SUPPORTED_10000baseT_Full __ETHTOOL_LINK_MODE_LEGACY_MASK(10000baseT_Full) +#define SUPPORTED_Pause __ETHTOOL_LINK_MODE_LEGACY_MASK(Pause) +#define SUPPORTED_Asym_Pause __ETHTOOL_LINK_MODE_LEGACY_MASK(Asym_Pause) +#define SUPPORTED_2500baseX_Full __ETHTOOL_LINK_MODE_LEGACY_MASK(2500baseX_Full) +#define SUPPORTED_Backplane __ETHTOOL_LINK_MODE_LEGACY_MASK(Backplane) +#define SUPPORTED_1000baseKX_Full __ETHTOOL_LINK_MODE_LEGACY_MASK(1000baseKX_Full) +#define SUPPORTED_10000baseKX4_Full __ETHTOOL_LINK_MODE_LEGACY_MASK(10000baseKX4_Full) +#define SUPPORTED_10000baseKR_Full __ETHTOOL_LINK_MODE_LEGACY_MASK(10000baseKR_Full) +#define SUPPORTED_10000baseR_FEC __ETHTOOL_LINK_MODE_LEGACY_MASK(10000baseR_FEC) +#define SUPPORTED_20000baseMLD2_Full __ETHTOOL_LINK_MODE_LEGACY_MASK(20000baseMLD2_Full) +#define SUPPORTED_20000baseKR2_Full __ETHTOOL_LINK_MODE_LEGACY_MASK(20000baseKR2_Full) +#define SUPPORTED_40000baseKR4_Full __ETHTOOL_LINK_MODE_LEGACY_MASK(40000baseKR4_Full) +#define SUPPORTED_40000baseCR4_Full __ETHTOOL_LINK_MODE_LEGACY_MASK(40000baseCR4_Full) +#define SUPPORTED_40000baseSR4_Full __ETHTOOL_LINK_MODE_LEGACY_MASK(40000baseSR4_Full) +#define SUPPORTED_40000baseLR4_Full __ETHTOOL_LINK_MODE_LEGACY_MASK(40000baseLR4_Full) +#define SUPPORTED_56000baseKR4_Full __ETHTOOL_LINK_MODE_LEGACY_MASK(56000baseKR4_Full) +#define SUPPORTED_56000baseCR4_Full __ETHTOOL_LINK_MODE_LEGACY_MASK(56000baseCR4_Full) +#define SUPPORTED_56000baseSR4_Full __ETHTOOL_LINK_MODE_LEGACY_MASK(56000baseSR4_Full) +#define SUPPORTED_56000baseLR4_Full __ETHTOOL_LINK_MODE_LEGACY_MASK(56000baseLR4_Full) +/* Please do not define any new SUPPORTED_* macro for bits > 31, see + * notice above. + */ + +/* + * DEPRECATED macros. Please migrate to + * ETHTOOL_GLINKSETTINGS/ETHTOOL_SLINKSETTINGS API. Please do NOT + * define any new ADERTISE_* macro for bits > 31. + */ +#define ADVERTISED_10baseT_Half __ETHTOOL_LINK_MODE_LEGACY_MASK(10baseT_Half) +#define ADVERTISED_10baseT_Full __ETHTOOL_LINK_MODE_LEGACY_MASK(10baseT_Full) +#define ADVERTISED_100baseT_Half __ETHTOOL_LINK_MODE_LEGACY_MASK(100baseT_Half) +#define ADVERTISED_100baseT_Full __ETHTOOL_LINK_MODE_LEGACY_MASK(100baseT_Full) +#define ADVERTISED_1000baseT_Half __ETHTOOL_LINK_MODE_LEGACY_MASK(1000baseT_Half) +#define ADVERTISED_1000baseT_Full __ETHTOOL_LINK_MODE_LEGACY_MASK(1000baseT_Full) +#define ADVERTISED_Autoneg __ETHTOOL_LINK_MODE_LEGACY_MASK(Autoneg) +#define ADVERTISED_TP __ETHTOOL_LINK_MODE_LEGACY_MASK(TP) +#define ADVERTISED_AUI __ETHTOOL_LINK_MODE_LEGACY_MASK(AUI) +#define ADVERTISED_MII __ETHTOOL_LINK_MODE_LEGACY_MASK(MII) +#define ADVERTISED_FIBRE __ETHTOOL_LINK_MODE_LEGACY_MASK(FIBRE) +#define ADVERTISED_BNC __ETHTOOL_LINK_MODE_LEGACY_MASK(BNC) +#define ADVERTISED_10000baseT_Full __ETHTOOL_LINK_MODE_LEGACY_MASK(10000baseT_Full) +#define ADVERTISED_Pause __ETHTOOL_LINK_MODE_LEGACY_MASK(Pause) +#define ADVERTISED_Asym_Pause __ETHTOOL_LINK_MODE_LEGACY_MASK(Asym_Pause) +#define ADVERTISED_2500baseX_Full __ETHTOOL_LINK_MODE_LEGACY_MASK(2500baseX_Full) +#define ADVERTISED_Backplane __ETHTOOL_LINK_MODE_LEGACY_MASK(Backplane) +#define ADVERTISED_1000baseKX_Full __ETHTOOL_LINK_MODE_LEGACY_MASK(1000baseKX_Full) +#define ADVERTISED_10000baseKX4_Full __ETHTOOL_LINK_MODE_LEGACY_MASK(10000baseKX4_Full) +#define ADVERTISED_10000baseKR_Full __ETHTOOL_LINK_MODE_LEGACY_MASK(10000baseKR_Full) +#define ADVERTISED_10000baseR_FEC __ETHTOOL_LINK_MODE_LEGACY_MASK(10000baseR_FEC) +#define ADVERTISED_20000baseMLD2_Full __ETHTOOL_LINK_MODE_LEGACY_MASK(20000baseMLD2_Full) +#define ADVERTISED_20000baseKR2_Full __ETHTOOL_LINK_MODE_LEGACY_MASK(20000baseKR2_Full) +#define ADVERTISED_40000baseKR4_Full __ETHTOOL_LINK_MODE_LEGACY_MASK(40000baseKR4_Full) +#define ADVERTISED_40000baseCR4_Full __ETHTOOL_LINK_MODE_LEGACY_MASK(40000baseCR4_Full) +#define ADVERTISED_40000baseSR4_Full __ETHTOOL_LINK_MODE_LEGACY_MASK(40000baseSR4_Full) +#define ADVERTISED_40000baseLR4_Full __ETHTOOL_LINK_MODE_LEGACY_MASK(40000baseLR4_Full) +#define ADVERTISED_56000baseKR4_Full __ETHTOOL_LINK_MODE_LEGACY_MASK(56000baseKR4_Full) +#define ADVERTISED_56000baseCR4_Full __ETHTOOL_LINK_MODE_LEGACY_MASK(56000baseCR4_Full) +#define ADVERTISED_56000baseSR4_Full __ETHTOOL_LINK_MODE_LEGACY_MASK(56000baseSR4_Full) +#define ADVERTISED_56000baseLR4_Full __ETHTOOL_LINK_MODE_LEGACY_MASK(56000baseLR4_Full) +/* Please do not define any new ADVERTISED_* macro for bits > 31, see + * notice above. + */ + +/* The following are all involved in forcing a particular link + * mode for the device for setting things. When getting the + * devices settings, these indicate the current mode and whether + * it was forced up into this mode or autonegotiated. + */ + +/* The forced speed, in units of 1Mb. All values 0 to INT_MAX are legal. + * Update drivers/net/phy/phy.c:phy_speed_to_str() and + * drivers/net/bonding/bond_3ad.c:__get_link_speed() when adding new values. + */ +#define SPEED_10 10 +#define SPEED_100 100 +#define SPEED_1000 1000 +#define SPEED_2500 2500 +#define SPEED_5000 5000 +#define SPEED_10000 10000 +#define SPEED_14000 14000 +#define SPEED_20000 20000 +#define SPEED_25000 25000 +#define SPEED_40000 40000 +#define SPEED_50000 50000 +#define SPEED_56000 56000 +#define SPEED_100000 100000 + +#define SPEED_UNKNOWN -1 + +static __inline__ int ethtool_validate_speed(__u32 speed) +{ + return speed <= INT_MAX || speed == SPEED_UNKNOWN; +} + +/* Duplex, half or full. */ +#define DUPLEX_HALF 0x00 +#define DUPLEX_FULL 0x01 +#define DUPLEX_UNKNOWN 0xff + +static __inline__ int ethtool_validate_duplex(__u8 duplex) +{ + switch (duplex) { + case DUPLEX_HALF: + case DUPLEX_FULL: + case DUPLEX_UNKNOWN: + return 1; + } + + return 0; +} + +/* Which connector port. */ +#define PORT_TP 0x00 +#define PORT_AUI 0x01 +#define PORT_MII 0x02 +#define PORT_FIBRE 0x03 +#define PORT_BNC 0x04 +#define PORT_DA 0x05 +#define PORT_NONE 0xef +#define PORT_OTHER 0xff + +/* Which transceiver to use. */ +#define XCVR_INTERNAL 0x00 /* PHY and MAC are in the same package */ +#define XCVR_EXTERNAL 0x01 /* PHY and MAC are in different packages */ +#define XCVR_DUMMY1 0x02 +#define XCVR_DUMMY2 0x03 +#define XCVR_DUMMY3 0x04 + +/* Enable or disable autonegotiation. */ +#define AUTONEG_DISABLE 0x00 +#define AUTONEG_ENABLE 0x01 + +/* MDI or MDI-X status/control - if MDI/MDI_X/AUTO is set then + * the driver is required to renegotiate link + */ +#define ETH_TP_MDI_INVALID 0x00 /* status: unknown; control: unsupported */ +#define ETH_TP_MDI 0x01 /* status: MDI; control: force MDI */ +#define ETH_TP_MDI_X 0x02 /* status: MDI-X; control: force MDI-X */ +#define ETH_TP_MDI_AUTO 0x03 /* control: auto-select */ + +/* Wake-On-Lan options. */ +#define WAKE_PHY (1 << 0) +#define WAKE_UCAST (1 << 1) +#define WAKE_MCAST (1 << 2) +#define WAKE_BCAST (1 << 3) +#define WAKE_ARP (1 << 4) +#define WAKE_MAGIC (1 << 5) +#define WAKE_MAGICSECURE (1 << 6) /* only meaningful if WAKE_MAGIC */ +#define WAKE_FILTER (1 << 7) + +/* L2-L4 network traffic flow types */ +#define TCP_V4_FLOW 0x01 /* hash or spec (tcp_ip4_spec) */ +#define UDP_V4_FLOW 0x02 /* hash or spec (udp_ip4_spec) */ +#define SCTP_V4_FLOW 0x03 /* hash or spec (sctp_ip4_spec) */ +#define AH_ESP_V4_FLOW 0x04 /* hash only */ +#define TCP_V6_FLOW 0x05 /* hash or spec (tcp_ip6_spec; nfc only) */ +#define UDP_V6_FLOW 0x06 /* hash or spec (udp_ip6_spec; nfc only) */ +#define SCTP_V6_FLOW 0x07 /* hash or spec (sctp_ip6_spec; nfc only) */ +#define AH_ESP_V6_FLOW 0x08 /* hash only */ +#define AH_V4_FLOW 0x09 /* hash or spec (ah_ip4_spec) */ +#define ESP_V4_FLOW 0x0a /* hash or spec (esp_ip4_spec) */ +#define AH_V6_FLOW 0x0b /* hash or spec (ah_ip6_spec; nfc only) */ +#define ESP_V6_FLOW 0x0c /* hash or spec (esp_ip6_spec; nfc only) */ +#define IPV4_USER_FLOW 0x0d /* spec only (usr_ip4_spec) */ +#define IP_USER_FLOW IPV4_USER_FLOW +#define IPV6_USER_FLOW 0x0e /* spec only (usr_ip6_spec; nfc only) */ +#define IPV4_FLOW 0x10 /* hash only */ +#define IPV6_FLOW 0x11 /* hash only */ +#define ETHER_FLOW 0x12 /* spec only (ether_spec) */ +/* Flag to enable additional fields in struct ethtool_rx_flow_spec */ +#define FLOW_EXT 0x80000000 +#define FLOW_MAC_EXT 0x40000000 +/* Flag to enable RSS spreading of traffic matching rule (nfc only) */ +#define FLOW_RSS 0x20000000 + +/* L3-L4 network traffic flow hash options */ +#define RXH_L2DA (1 << 1) +#define RXH_VLAN (1 << 2) +#define RXH_L3_PROTO (1 << 3) +#define RXH_IP_SRC (1 << 4) +#define RXH_IP_DST (1 << 5) +#define RXH_L4_B_0_1 (1 << 6) /* src port in case of TCP/UDP/SCTP */ +#define RXH_L4_B_2_3 (1 << 7) /* dst port in case of TCP/UDP/SCTP */ +#define RXH_DISCARD (1 << 31) + +#define RX_CLS_FLOW_DISC 0xffffffffffffffffULL +#define RX_CLS_FLOW_WAKE 0xfffffffffffffffeULL + +/* Special RX classification rule insert location values */ +#define RX_CLS_LOC_SPECIAL 0x80000000 /* flag */ +#define RX_CLS_LOC_ANY 0xffffffff +#define RX_CLS_LOC_FIRST 0xfffffffe +#define RX_CLS_LOC_LAST 0xfffffffd + +/* EEPROM Standards for plug in modules */ +#define ETH_MODULE_SFF_8079 0x1 +#define ETH_MODULE_SFF_8079_LEN 256 +#define ETH_MODULE_SFF_8472 0x2 +#define ETH_MODULE_SFF_8472_LEN 512 +#define ETH_MODULE_SFF_8636 0x3 +#define ETH_MODULE_SFF_8636_LEN 256 +#define ETH_MODULE_SFF_8436 0x4 +#define ETH_MODULE_SFF_8436_LEN 256 + +/* Reset flags */ +/* The reset() operation must clear the flags for the components which + * were actually reset. On successful return, the flags indicate the + * components which were not reset, either because they do not exist + * in the hardware or because they cannot be reset independently. The + * driver must never reset any components that were not requested. + */ +enum ethtool_reset_flags { + /* These flags represent components dedicated to the interface + * the command is addressed to. Shift any flag left by + * ETH_RESET_SHARED_SHIFT to reset a shared component of the + * same type. + */ + ETH_RESET_MGMT = 1 << 0, /* Management processor */ + ETH_RESET_IRQ = 1 << 1, /* Interrupt requester */ + ETH_RESET_DMA = 1 << 2, /* DMA engine */ + ETH_RESET_FILTER = 1 << 3, /* Filtering/flow direction */ + ETH_RESET_OFFLOAD = 1 << 4, /* Protocol offload */ + ETH_RESET_MAC = 1 << 5, /* Media access controller */ + ETH_RESET_PHY = 1 << 6, /* Transceiver/PHY */ + ETH_RESET_RAM = 1 << 7, /* RAM shared between + * multiple components */ + ETH_RESET_AP = 1 << 8, /* Application processor */ + + ETH_RESET_DEDICATED = 0x0000ffff, /* All components dedicated to + * this interface */ + ETH_RESET_ALL = 0xffffffff, /* All components used by this + * interface, even if shared */ +}; +#define ETH_RESET_SHARED_SHIFT 16 + + +/** + * struct ethtool_link_settings - link control and status + * + * IMPORTANT, Backward compatibility notice: When implementing new + * user-space tools, please first try %ETHTOOL_GLINKSETTINGS, and + * if it succeeds use %ETHTOOL_SLINKSETTINGS to change link + * settings; do not use %ETHTOOL_SSET if %ETHTOOL_GLINKSETTINGS + * succeeded: stick to %ETHTOOL_GLINKSETTINGS/%SLINKSETTINGS in + * that case. Conversely, if %ETHTOOL_GLINKSETTINGS fails, use + * %ETHTOOL_GSET to query and %ETHTOOL_SSET to change link + * settings; do not use %ETHTOOL_SLINKSETTINGS if + * %ETHTOOL_GLINKSETTINGS failed: stick to + * %ETHTOOL_GSET/%ETHTOOL_SSET in that case. + * + * @cmd: Command number = %ETHTOOL_GLINKSETTINGS or %ETHTOOL_SLINKSETTINGS + * @speed: Link speed (Mbps) + * @duplex: Duplex mode; one of %DUPLEX_* + * @port: Physical connector type; one of %PORT_* + * @phy_address: MDIO address of PHY (transceiver); 0 or 255 if not + * applicable. For clause 45 PHYs this is the PRTAD. + * @autoneg: Enable/disable autonegotiation and auto-detection; + * either %AUTONEG_DISABLE or %AUTONEG_ENABLE + * @mdio_support: Bitmask of %ETH_MDIO_SUPPORTS_* flags for the MDIO + * protocols supported by the interface; 0 if unknown. + * Read-only. + * @eth_tp_mdix: Ethernet twisted-pair MDI(-X) status; one of + * %ETH_TP_MDI_*. If the status is unknown or not applicable, the + * value will be %ETH_TP_MDI_INVALID. Read-only. + * @eth_tp_mdix_ctrl: Ethernet twisted pair MDI(-X) control; one of + * %ETH_TP_MDI_*. If MDI(-X) control is not implemented, reads + * yield %ETH_TP_MDI_INVALID and writes may be ignored or rejected. + * When written successfully, the link should be renegotiated if + * necessary. + * @link_mode_masks_nwords: Number of 32-bit words for each of the + * supported, advertising, lp_advertising link mode bitmaps. For + * %ETHTOOL_GLINKSETTINGS: on entry, number of words passed by user + * (>= 0); on return, if handshake in progress, negative if + * request size unsupported by kernel: absolute value indicates + * kernel expected size and all the other fields but cmd + * are 0; otherwise (handshake completed), strictly positive + * to indicate size used by kernel and cmd field stays + * %ETHTOOL_GLINKSETTINGS, all other fields populated by driver. For + * %ETHTOOL_SLINKSETTINGS: must be valid on entry, ie. a positive + * value returned previously by %ETHTOOL_GLINKSETTINGS, otherwise + * refused. For drivers: ignore this field (use kernel's + * __ETHTOOL_LINK_MODE_MASK_NBITS instead), any change to it will + * be overwritten by kernel. + * @supported: Bitmap with each bit meaning given by + * %ethtool_link_mode_bit_indices for the link modes, physical + * connectors and other link features for which the interface + * supports autonegotiation or auto-detection. Read-only. + * @advertising: Bitmap with each bit meaning given by + * %ethtool_link_mode_bit_indices for the link modes, physical + * connectors and other link features that are advertised through + * autonegotiation or enabled for auto-detection. + * @lp_advertising: Bitmap with each bit meaning given by + * %ethtool_link_mode_bit_indices for the link modes, and other + * link features that the link partner advertised through + * autonegotiation; 0 if unknown or not applicable. Read-only. + * @transceiver: Used to distinguish different possible PHY types, + * reported consistently by PHYLIB. Read-only. + * + * If autonegotiation is disabled, the speed and @duplex represent the + * fixed link mode and are writable if the driver supports multiple + * link modes. If it is enabled then they are read-only; if the link + * is up they represent the negotiated link mode; if the link is down, + * the speed is 0, %SPEED_UNKNOWN or the highest enabled speed and + * @duplex is %DUPLEX_UNKNOWN or the best enabled duplex mode. + * + * Some hardware interfaces may have multiple PHYs and/or physical + * connectors fitted or do not allow the driver to detect which are + * fitted. For these interfaces @port and/or @phy_address may be + * writable, possibly dependent on @autoneg being %AUTONEG_DISABLE. + * Otherwise, attempts to write different values may be ignored or + * rejected. + * + * Deprecated %ethtool_cmd fields transceiver, maxtxpkt and maxrxpkt + * are not available in %ethtool_link_settings. Until all drivers are + * converted to ignore them or to the new %ethtool_link_settings API, + * for both queries and changes, users should always try + * %ETHTOOL_GLINKSETTINGS first, and if it fails with -ENOTSUPP stick + * only to %ETHTOOL_GSET and %ETHTOOL_SSET consistently. If it + * succeeds, then users should stick to %ETHTOOL_GLINKSETTINGS and + * %ETHTOOL_SLINKSETTINGS (which would support drivers implementing + * either %ethtool_cmd or %ethtool_link_settings). + * + * Users should assume that all fields not marked read-only are + * writable and subject to validation by the driver. They should use + * %ETHTOOL_GLINKSETTINGS to get the current values before making specific + * changes and then applying them with %ETHTOOL_SLINKSETTINGS. + * + * Drivers that implement %get_link_ksettings and/or + * %set_link_ksettings should ignore the @cmd + * and @link_mode_masks_nwords fields (any change to them overwritten + * by kernel), and rely only on kernel's internal + * %__ETHTOOL_LINK_MODE_MASK_NBITS and + * %ethtool_link_mode_mask_t. Drivers that implement + * %set_link_ksettings() should validate all fields other than @cmd + * and @link_mode_masks_nwords that are not described as read-only or + * deprecated, and must ignore all fields described as read-only. + */ +struct ethtool_link_settings { + __u32 cmd; + __u32 speed; + __u8 duplex; + __u8 port; + __u8 phy_address; + __u8 autoneg; + __u8 mdio_support; + __u8 eth_tp_mdix; + __u8 eth_tp_mdix_ctrl; + __s8 link_mode_masks_nwords; + __u8 transceiver; + __u8 reserved1[3]; + __u32 reserved[7]; + __u32 link_mode_masks[0]; + /* layout of link_mode_masks fields: + * __u32 map_supported[link_mode_masks_nwords]; + * __u32 map_advertising[link_mode_masks_nwords]; + * __u32 map_lp_advertising[link_mode_masks_nwords]; + */ +}; +#endif /* _LINUX_ETHTOOL_H */ diff --git a/include/linux/filter.h b/include/linux/filter.h new file mode 100644 index 0000000000000000000000000000000000000000..f5d00f485a2ebc3f70158870f225117e1fbfc613 --- /dev/null +++ b/include/linux/filter.h @@ -0,0 +1,87 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +/* + * Linux Socket Filter Data Structures + */ + +#ifndef __LINUX_FILTER_H__ +#define __LINUX_FILTER_H__ + + +#include +#include + +/* + * Current version of the filter code architecture. + */ +#define BPF_MAJOR_VERSION 1 +#define BPF_MINOR_VERSION 1 + +/* + * Try and keep these values and structures similar to BSD, especially + * the BPF code definitions which need to match so you can share filters + */ + +struct sock_filter { /* Filter block */ + __u16 code; /* Actual filter code */ + __u8 jt; /* Jump true */ + __u8 jf; /* Jump false */ + __u32 k; /* Generic multiuse field */ +}; + +struct sock_fprog { /* Required for SO_ATTACH_FILTER. */ + unsigned short len; /* Number of filter blocks */ + struct sock_filter *filter; +}; + +/* ret - BPF_K and BPF_X also apply */ +#define BPF_RVAL(code) ((code) & 0x18) +#define BPF_A 0x10 + +/* misc */ +#define BPF_MISCOP(code) ((code) & 0xf8) +#define BPF_TAX 0x00 +#define BPF_TXA 0x80 + +/* + * Macros for filter block array initializers. + */ +#ifndef BPF_STMT +#define BPF_STMT(code, k) { (unsigned short)(code), 0, 0, k } +#endif +#ifndef BPF_JUMP +#define BPF_JUMP(code, k, jt, jf) { (unsigned short)(code), jt, jf, k } +#endif + +/* + * Number of scratch memory words for: BPF_ST and BPF_STX + */ +#define BPF_MEMWORDS 16 + +/* RATIONALE. Negative offsets are invalid in BPF. + We use them to reference ancillary data. + Unlike introduction new instructions, it does not break + existing compilers/optimizers. + */ +#define SKF_AD_OFF (-0x1000) +#define SKF_AD_PROTOCOL 0 +#define SKF_AD_PKTTYPE 4 +#define SKF_AD_IFINDEX 8 +#define SKF_AD_NLATTR 12 +#define SKF_AD_NLATTR_NEST 16 +#define SKF_AD_MARK 20 +#define SKF_AD_QUEUE 24 +#define SKF_AD_HATYPE 28 +#define SKF_AD_RXHASH 32 +#define SKF_AD_CPU 36 +#define SKF_AD_ALU_XOR_X 40 +#define SKF_AD_PAY_OFFSET 52 +#define SKF_AD_RANDOM 56 +#define SKF_AD_MAX 64 + +#define SKF_NET_OFF (-0x100000) +#define SKF_LL_OFF (-0x200000) + +#define BPF_NET_OFF SKF_NET_OFF +#define BPF_LL_OFF SKF_LL_OFF + +#endif /* __LINUX_FILTER_H__ */ diff --git a/include/linux/hdlc/ioctl.h b/include/linux/hdlc/ioctl.h new file mode 100644 index 0000000000000000000000000000000000000000..0fe4238e824624d22b847cca023bec3fbb709324 --- /dev/null +++ b/include/linux/hdlc/ioctl.h @@ -0,0 +1,85 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +#ifndef __HDLC_IOCTL_H__ +#define __HDLC_IOCTL_H__ + + +#define GENERIC_HDLC_VERSION 4 /* For synchronization with sethdlc utility */ + +#define CLOCK_DEFAULT 0 /* Default setting */ +#define CLOCK_EXT 1 /* External TX and RX clock - DTE */ +#define CLOCK_INT 2 /* Internal TX and RX clock - DCE */ +#define CLOCK_TXINT 3 /* Internal TX and external RX clock */ +#define CLOCK_TXFROMRX 4 /* TX clock derived from external RX clock */ + + +#define ENCODING_DEFAULT 0 /* Default setting */ +#define ENCODING_NRZ 1 +#define ENCODING_NRZI 2 +#define ENCODING_FM_MARK 3 +#define ENCODING_FM_SPACE 4 +#define ENCODING_MANCHESTER 5 + + +#define PARITY_DEFAULT 0 /* Default setting */ +#define PARITY_NONE 1 /* No parity */ +#define PARITY_CRC16_PR0 2 /* CRC16, initial value 0x0000 */ +#define PARITY_CRC16_PR1 3 /* CRC16, initial value 0xFFFF */ +#define PARITY_CRC16_PR0_CCITT 4 /* CRC16, initial 0x0000, ITU-T version */ +#define PARITY_CRC16_PR1_CCITT 5 /* CRC16, initial 0xFFFF, ITU-T version */ +#define PARITY_CRC32_PR0_CCITT 6 /* CRC32, initial value 0x00000000 */ +#define PARITY_CRC32_PR1_CCITT 7 /* CRC32, initial value 0xFFFFFFFF */ + +#define LMI_DEFAULT 0 /* Default setting */ +#define LMI_NONE 1 /* No LMI, all PVCs are static */ +#define LMI_ANSI 2 /* ANSI Annex D */ +#define LMI_CCITT 3 /* ITU-T Annex A */ +#define LMI_CISCO 4 /* The "original" LMI, aka Gang of Four */ + +#ifndef __ASSEMBLY__ + +typedef struct { + unsigned int clock_rate; /* bits per second */ + unsigned int clock_type; /* internal, external, TX-internal etc. */ + unsigned short loopback; +} sync_serial_settings; /* V.35, V.24, X.21 */ + +typedef struct { + unsigned int clock_rate; /* bits per second */ + unsigned int clock_type; /* internal, external, TX-internal etc. */ + unsigned short loopback; + unsigned int slot_map; +} te1_settings; /* T1, E1 */ + +typedef struct { + unsigned short encoding; + unsigned short parity; +} raw_hdlc_proto; + +typedef struct { + unsigned int t391; + unsigned int t392; + unsigned int n391; + unsigned int n392; + unsigned int n393; + unsigned short lmi; + unsigned short dce; /* 1 for DCE (network side) operation */ +} fr_proto; + +typedef struct { + unsigned int dlci; +} fr_proto_pvc; /* for creating/deleting FR PVCs */ + +typedef struct { + unsigned int dlci; + char master[IFNAMSIZ]; /* Name of master FRAD device */ +}fr_proto_pvc_info; /* for returning PVC information only */ + +typedef struct { + unsigned int interval; + unsigned int timeout; +} cisco_proto; + +/* PPP doesn't need any info now - supply length = 0 to ioctl */ + +#endif /* __ASSEMBLY__ */ +#endif /* __HDLC_IOCTL_H__ */ diff --git a/include/linux/if.h b/include/linux/if.h new file mode 100644 index 0000000000000000000000000000000000000000..495cdd23244283716d6892214c361ba3d3501f20 --- /dev/null +++ b/include/linux/if.h @@ -0,0 +1,293 @@ +/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */ +/* + * INET An implementation of the TCP/IP protocol suite for the LINUX + * operating system. INET is implemented using the BSD Socket + * interface as the means of communication with the user level. + * + * Global definitions for the INET interface module. + * + * Version: @(#)if.h 1.0.2 04/18/93 + * + * Authors: Original taken from Berkeley UNIX 4.3, (c) UCB 1982-1988 + * Ross Biro + * Fred N. van Kempen, + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ +#ifndef _LINUX_IF_H +#define _LINUX_IF_H + +#include /* for compatibility with glibc */ +#include /* for "__kernel_caddr_t" et al */ +#include /* for "struct sockaddr" et al */ + /* for "__user" et al */ + +#include /* for struct sockaddr. */ + +#if __UAPI_DEF_IF_IFNAMSIZ +#define IFNAMSIZ 16 +#endif /* __UAPI_DEF_IF_IFNAMSIZ */ +#define IFALIASZ 256 +#include + +/* For glibc compatibility. An empty enum does not compile. */ +#if __UAPI_DEF_IF_NET_DEVICE_FLAGS_LOWER_UP_DORMANT_ECHO != 0 || \ + __UAPI_DEF_IF_NET_DEVICE_FLAGS != 0 +/** + * enum net_device_flags - &struct net_device flags + * + * These are the &struct net_device flags, they can be set by drivers, the + * kernel and some can be triggered by userspace. Userspace can query and + * set these flags using userspace utilities but there is also a sysfs + * entry available for all dev flags which can be queried and set. These flags + * are shared for all types of net_devices. The sysfs entries are available + * via /sys/class/net//flags. Flags which can be toggled through sysfs + * are annotated below, note that only a few flags can be toggled and some + * other flags are always preserved from the original net_device flags + * even if you try to set them via sysfs. Flags which are always preserved + * are kept under the flag grouping @IFF_VOLATILE. Flags which are __volatile__ + * are annotated below as such. + * + * You should have a pretty good reason to be extending these flags. + * + * @IFF_UP: interface is up. Can be toggled through sysfs. + * @IFF_BROADCAST: broadcast address valid. Volatile. + * @IFF_DEBUG: turn on debugging. Can be toggled through sysfs. + * @IFF_LOOPBACK: is a loopback net. Volatile. + * @IFF_POINTOPOINT: interface is has p-p link. Volatile. + * @IFF_NOTRAILERS: avoid use of trailers. Can be toggled through sysfs. + * Volatile. + * @IFF_RUNNING: interface RFC2863 OPER_UP. Volatile. + * @IFF_NOARP: no ARP protocol. Can be toggled through sysfs. Volatile. + * @IFF_PROMISC: receive all packets. Can be toggled through sysfs. + * @IFF_ALLMULTI: receive all multicast packets. Can be toggled through + * sysfs. + * @IFF_MASTER: master of a load balancer. Volatile. + * @IFF_SLAVE: slave of a load balancer. Volatile. + * @IFF_MULTICAST: Supports multicast. Can be toggled through sysfs. + * @IFF_PORTSEL: can set media type. Can be toggled through sysfs. + * @IFF_AUTOMEDIA: auto media select active. Can be toggled through sysfs. + * @IFF_DYNAMIC: dialup device with changing addresses. Can be toggled + * through sysfs. + * @IFF_LOWER_UP: driver signals L1 up. Volatile. + * @IFF_DORMANT: driver signals dormant. Volatile. + * @IFF_ECHO: echo sent packets. Volatile. + */ +enum net_device_flags { +/* for compatibility with glibc net/if.h */ +#if __UAPI_DEF_IF_NET_DEVICE_FLAGS + IFF_UP = 1<<0, /* sysfs */ + IFF_BROADCAST = 1<<1, /* __volatile__ */ + IFF_DEBUG = 1<<2, /* sysfs */ + IFF_LOOPBACK = 1<<3, /* __volatile__ */ + IFF_POINTOPOINT = 1<<4, /* __volatile__ */ + IFF_NOTRAILERS = 1<<5, /* sysfs */ + IFF_RUNNING = 1<<6, /* __volatile__ */ + IFF_NOARP = 1<<7, /* sysfs */ + IFF_PROMISC = 1<<8, /* sysfs */ + IFF_ALLMULTI = 1<<9, /* sysfs */ + IFF_MASTER = 1<<10, /* __volatile__ */ + IFF_SLAVE = 1<<11, /* __volatile__ */ + IFF_MULTICAST = 1<<12, /* sysfs */ + IFF_PORTSEL = 1<<13, /* sysfs */ + IFF_AUTOMEDIA = 1<<14, /* sysfs */ + IFF_DYNAMIC = 1<<15, /* sysfs */ +#endif /* __UAPI_DEF_IF_NET_DEVICE_FLAGS */ +#if __UAPI_DEF_IF_NET_DEVICE_FLAGS_LOWER_UP_DORMANT_ECHO + IFF_LOWER_UP = 1<<16, /* __volatile__ */ + IFF_DORMANT = 1<<17, /* __volatile__ */ + IFF_ECHO = 1<<18, /* __volatile__ */ +#endif /* __UAPI_DEF_IF_NET_DEVICE_FLAGS_LOWER_UP_DORMANT_ECHO */ +}; +#endif /* __UAPI_DEF_IF_NET_DEVICE_FLAGS_LOWER_UP_DORMANT_ECHO != 0 || __UAPI_DEF_IF_NET_DEVICE_FLAGS != 0 */ + +/* for compatibility with glibc net/if.h */ +#if __UAPI_DEF_IF_NET_DEVICE_FLAGS +#define IFF_UP IFF_UP +#define IFF_BROADCAST IFF_BROADCAST +#define IFF_DEBUG IFF_DEBUG +#define IFF_LOOPBACK IFF_LOOPBACK +#define IFF_POINTOPOINT IFF_POINTOPOINT +#define IFF_NOTRAILERS IFF_NOTRAILERS +#define IFF_RUNNING IFF_RUNNING +#define IFF_NOARP IFF_NOARP +#define IFF_PROMISC IFF_PROMISC +#define IFF_ALLMULTI IFF_ALLMULTI +#define IFF_MASTER IFF_MASTER +#define IFF_SLAVE IFF_SLAVE +#define IFF_MULTICAST IFF_MULTICAST +#define IFF_PORTSEL IFF_PORTSEL +#define IFF_AUTOMEDIA IFF_AUTOMEDIA +#define IFF_DYNAMIC IFF_DYNAMIC +#endif /* __UAPI_DEF_IF_NET_DEVICE_FLAGS */ + +#if __UAPI_DEF_IF_NET_DEVICE_FLAGS_LOWER_UP_DORMANT_ECHO +#define IFF_LOWER_UP IFF_LOWER_UP +#define IFF_DORMANT IFF_DORMANT +#define IFF_ECHO IFF_ECHO +#endif /* __UAPI_DEF_IF_NET_DEVICE_FLAGS_LOWER_UP_DORMANT_ECHO */ + +#define IFF_VOLATILE (IFF_LOOPBACK|IFF_POINTOPOINT|IFF_BROADCAST|IFF_ECHO|\ + IFF_MASTER|IFF_SLAVE|IFF_RUNNING|IFF_LOWER_UP|IFF_DORMANT) + +#define IF_GET_IFACE 0x0001 /* for querying only */ +#define IF_GET_PROTO 0x0002 + +/* For definitions see hdlc.h */ +#define IF_IFACE_V35 0x1000 /* V.35 serial interface */ +#define IF_IFACE_V24 0x1001 /* V.24 serial interface */ +#define IF_IFACE_X21 0x1002 /* X.21 serial interface */ +#define IF_IFACE_T1 0x1003 /* T1 telco serial interface */ +#define IF_IFACE_E1 0x1004 /* E1 telco serial interface */ +#define IF_IFACE_SYNC_SERIAL 0x1005 /* can't be set by software */ +#define IF_IFACE_X21D 0x1006 /* X.21 Dual Clocking (FarSite) */ + +/* For definitions see hdlc.h */ +#define IF_PROTO_HDLC 0x2000 /* raw HDLC protocol */ +#define IF_PROTO_PPP 0x2001 /* PPP protocol */ +#define IF_PROTO_CISCO 0x2002 /* Cisco HDLC protocol */ +#define IF_PROTO_FR 0x2003 /* Frame Relay protocol */ +#define IF_PROTO_FR_ADD_PVC 0x2004 /* Create FR PVC */ +#define IF_PROTO_FR_DEL_PVC 0x2005 /* Delete FR PVC */ +#define IF_PROTO_X25 0x2006 /* X.25 */ +#define IF_PROTO_HDLC_ETH 0x2007 /* raw HDLC, Ethernet emulation */ +#define IF_PROTO_FR_ADD_ETH_PVC 0x2008 /* Create FR Ethernet-bridged PVC */ +#define IF_PROTO_FR_DEL_ETH_PVC 0x2009 /* Delete FR Ethernet-bridged PVC */ +#define IF_PROTO_FR_PVC 0x200A /* for reading PVC status */ +#define IF_PROTO_FR_ETH_PVC 0x200B +#define IF_PROTO_RAW 0x200C /* RAW Socket */ + +/* RFC 2863 operational status */ +enum { + IF_OPER_UNKNOWN, + IF_OPER_NOTPRESENT, + IF_OPER_DOWN, + IF_OPER_LOWERLAYERDOWN, + IF_OPER_TESTING, + IF_OPER_DORMANT, + IF_OPER_UP, +}; + +/* link modes */ +enum { + IF_LINK_MODE_DEFAULT, + IF_LINK_MODE_DORMANT, /* limit upward transition to dormant */ +}; + +/* + * Device mapping structure. I'd just gone off and designed a + * beautiful scheme using only loadable modules with arguments + * for driver options and along come the PCMCIA people 8) + * + * Ah well. The get() side of this is good for WDSETUP, and it'll + * be handy for debugging things. The set side is fine for now and + * being very small might be worth keeping for clean configuration. + */ + +/* for compatibility with glibc net/if.h */ +#if __UAPI_DEF_IF_IFMAP +struct ifmap { + unsigned long mem_start; + unsigned long mem_end; + unsigned short base_addr; + unsigned char irq; + unsigned char dma; + unsigned char port; + /* 3 bytes spare */ +}; +#endif /* __UAPI_DEF_IF_IFMAP */ + +struct if_settings { + unsigned int type; /* Type of physical device or protocol */ + unsigned int size; /* Size of the data allocated by the caller */ + union { + /* {atm/eth/dsl}_settings anyone ? */ + raw_hdlc_proto *raw_hdlc; + cisco_proto *cisco; + fr_proto *fr; + fr_proto_pvc *fr_pvc; + fr_proto_pvc_info *fr_pvc_info; + + /* interface settings */ + sync_serial_settings *sync; + te1_settings *te1; + } ifs_ifsu; +}; + +/* + * Interface request structure used for socket + * ioctl's. All interface ioctl's must have parameter + * definitions which begin with ifr_name. The + * remainder may be interface specific. + */ + +/* for compatibility with glibc net/if.h */ +#if __UAPI_DEF_IF_IFREQ +struct ifreq { +#define IFHWADDRLEN 6 + union + { + char ifrn_name[IFNAMSIZ]; /* if name, e.g. "en0" */ + } ifr_ifrn; + + union { + struct sockaddr ifru_addr; + struct sockaddr ifru_dstaddr; + struct sockaddr ifru_broadaddr; + struct sockaddr ifru_netmask; + struct sockaddr ifru_hwaddr; + short ifru_flags; + int ifru_ivalue; + int ifru_mtu; + struct ifmap ifru_map; + char ifru_slave[IFNAMSIZ]; /* Just fits the size */ + char ifru_newname[IFNAMSIZ]; + void * ifru_data; + struct if_settings ifru_settings; + } ifr_ifru; +}; +#endif /* __UAPI_DEF_IF_IFREQ */ + +#define ifr_name ifr_ifrn.ifrn_name /* interface name */ +#define ifr_hwaddr ifr_ifru.ifru_hwaddr /* MAC address */ +#define ifr_addr ifr_ifru.ifru_addr /* address */ +#define ifr_dstaddr ifr_ifru.ifru_dstaddr /* other end of p-p lnk */ +#define ifr_broadaddr ifr_ifru.ifru_broadaddr /* broadcast address */ +#define ifr_netmask ifr_ifru.ifru_netmask /* interface net mask */ +#define ifr_flags ifr_ifru.ifru_flags /* flags */ +#define ifr_metric ifr_ifru.ifru_ivalue /* metric */ +#define ifr_mtu ifr_ifru.ifru_mtu /* mtu */ +#define ifr_map ifr_ifru.ifru_map /* device map */ +#define ifr_slave ifr_ifru.ifru_slave /* slave device */ +#define ifr_data ifr_ifru.ifru_data /* for use by interface */ +#define ifr_ifindex ifr_ifru.ifru_ivalue /* interface index */ +#define ifr_bandwidth ifr_ifru.ifru_ivalue /* link bandwidth */ +#define ifr_qlen ifr_ifru.ifru_ivalue /* Queue length */ +#define ifr_newname ifr_ifru.ifru_newname /* New name */ +#define ifr_settings ifr_ifru.ifru_settings /* Device/proto settings*/ + +/* + * Structure used in SIOCGIFCONF request. + * Used to retrieve interface configuration + * for machine (useful for programs which + * must know all networks accessible). + */ + +/* for compatibility with glibc net/if.h */ +#if __UAPI_DEF_IF_IFCONF +struct ifconf { + int ifc_len; /* size of buffer */ + union { + char *ifcu_buf; + struct ifreq *ifcu_req; + } ifc_ifcu; +}; +#endif /* __UAPI_DEF_IF_IFCONF */ + +#define ifc_buf ifc_ifcu.ifcu_buf /* buffer address */ +#define ifc_req ifc_ifcu.ifcu_req /* array of structures */ + +#endif /* _LINUX_IF_H */ diff --git a/include/linux/if_addr.h b/include/linux/if_addr.h new file mode 100644 index 0000000000000000000000000000000000000000..a924606f36e56f76043d782041dbf217fb73cbc5 --- /dev/null +++ b/include/linux/if_addr.h @@ -0,0 +1,69 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +#ifndef __LINUX_IF_ADDR_H +#define __LINUX_IF_ADDR_H + +#include +#include + +struct ifaddrmsg { + __u8 ifa_family; + __u8 ifa_prefixlen; /* The prefix length */ + __u8 ifa_flags; /* Flags */ + __u8 ifa_scope; /* Address scope */ + __u32 ifa_index; /* Link index */ +}; + +/* + * Important comment: + * IFA_ADDRESS is prefix address, rather than local interface address. + * It makes no difference for normally configured broadcast interfaces, + * but for point-to-point IFA_ADDRESS is DESTINATION address, + * local address is supplied in IFA_LOCAL attribute. + * + * IFA_FLAGS is a u32 attribute that extends the u8 field ifa_flags. + * If present, the value from struct ifaddrmsg will be ignored. + */ +enum { + IFA_UNSPEC, + IFA_ADDRESS, + IFA_LOCAL, + IFA_LABEL, + IFA_BROADCAST, + IFA_ANYCAST, + IFA_CACHEINFO, + IFA_MULTICAST, + IFA_FLAGS, + IFA_RT_PRIORITY, /* u32, priority/metric for prefix route */ + __IFA_MAX, +}; + +#define IFA_MAX (__IFA_MAX - 1) + +/* ifa_flags */ +#define IFA_F_SECONDARY 0x01 +#define IFA_F_TEMPORARY IFA_F_SECONDARY + +#define IFA_F_NODAD 0x02 +#define IFA_F_OPTIMISTIC 0x04 +#define IFA_F_DADFAILED 0x08 +#define IFA_F_HOMEADDRESS 0x10 +#define IFA_F_DEPRECATED 0x20 +#define IFA_F_TENTATIVE 0x40 +#define IFA_F_PERMANENT 0x80 +#define IFA_F_MANAGETEMPADDR 0x100 +#define IFA_F_NOPREFIXROUTE 0x200 +#define IFA_F_MCAUTOJOIN 0x400 +#define IFA_F_STABLE_PRIVACY 0x800 + +struct ifa_cacheinfo { + __u32 ifa_prefered; + __u32 ifa_valid; + __u32 cstamp; /* created timestamp, hundredths of seconds */ + __u32 tstamp; /* updated timestamp, hundredths of seconds */ +}; + +/* backwards compatibility for userspace */ +#define IFA_RTA(r) ((struct rtattr*)(((char*)(r)) + NLMSG_ALIGN(sizeof(struct ifaddrmsg)))) +#define IFA_PAYLOAD(n) NLMSG_PAYLOAD(n,sizeof(struct ifaddrmsg)) + +#endif diff --git a/include/linux/if_bonding.h b/include/linux/if_bonding.h new file mode 100644 index 0000000000000000000000000000000000000000..61a1bf6e865e8b41aa1f5bd8ce188f513f58e143 --- /dev/null +++ b/include/linux/if_bonding.h @@ -0,0 +1,131 @@ +/* SPDX-License-Identifier: GPL-1.0+ WITH Linux-syscall-note */ +/* + * Bond several ethernet interfaces into a Cisco, running 'Etherchannel'. + * + * + * Portions are (c) Copyright 1995 Simon "Guru Aleph-Null" Janes + * NCM: Network and Communications Management, Inc. + * + * BUT, I'm the one who modified it for ethernet, so: + * (c) Copyright 1999, Thomas Davis, tadavis@lbl.gov + * + * This software may be used and distributed according to the terms + * of the GNU Public License, incorporated herein by reference. + * + * 2003/03/18 - Amir Noam + * - Added support for getting slave's speed and duplex via ethtool. + * Needed for 802.3ad and other future modes. + * + * 2003/03/18 - Tsippy Mendelson and + * Shmulik Hen + * - Enable support of modes that need to use the unique mac address of + * each slave. + * + * 2003/03/18 - Tsippy Mendelson and + * Amir Noam + * - Moved driver's private data types to bonding.h + * + * 2003/03/18 - Amir Noam , + * Tsippy Mendelson and + * Shmulik Hen + * - Added support for IEEE 802.3ad Dynamic link aggregation mode. + * + * 2003/05/01 - Amir Noam + * - Added ABI version control to restore compatibility between + * new/old ifenslave and new/old bonding. + * + * 2003/12/01 - Shmulik Hen + * - Code cleanup and style changes + * + * 2005/05/05 - Jason Gabler + * - added definitions for various XOR hashing policies + */ + +#ifndef _LINUX_IF_BONDING_H +#define _LINUX_IF_BONDING_H + +#include +#include +#include + +/* userland - kernel ABI version (2003/05/08) */ +#define BOND_ABI_VERSION 2 + +/* + * We can remove these ioctl definitions in 2.5. People should use the + * SIOC*** versions of them instead + */ +#define BOND_ENSLAVE_OLD (SIOCDEVPRIVATE) +#define BOND_RELEASE_OLD (SIOCDEVPRIVATE + 1) +#define BOND_SETHWADDR_OLD (SIOCDEVPRIVATE + 2) +#define BOND_SLAVE_INFO_QUERY_OLD (SIOCDEVPRIVATE + 11) +#define BOND_INFO_QUERY_OLD (SIOCDEVPRIVATE + 12) +#define BOND_CHANGE_ACTIVE_OLD (SIOCDEVPRIVATE + 13) + +#define BOND_CHECK_MII_STATUS (SIOCGMIIPHY) + +#define BOND_MODE_ROUNDROBIN 0 +#define BOND_MODE_ACTIVEBACKUP 1 +#define BOND_MODE_XOR 2 +#define BOND_MODE_BROADCAST 3 +#define BOND_MODE_8023AD 4 +#define BOND_MODE_TLB 5 +#define BOND_MODE_ALB 6 /* TLB + RLB (receive load balancing) */ + +/* each slave's link has 4 states */ +#define BOND_LINK_UP 0 /* link is up and running */ +#define BOND_LINK_FAIL 1 /* link has just gone down */ +#define BOND_LINK_DOWN 2 /* link has been down for too long time */ +#define BOND_LINK_BACK 3 /* link is going back */ + +/* each slave has several states */ +#define BOND_STATE_ACTIVE 0 /* link is active */ +#define BOND_STATE_BACKUP 1 /* link is backup */ + +#define BOND_DEFAULT_MAX_BONDS 1 /* Default maximum number of devices to support */ + +#define BOND_DEFAULT_TX_QUEUES 16 /* Default number of tx queues per device */ + +#define BOND_DEFAULT_RESEND_IGMP 1 /* Default number of IGMP membership reports */ + +/* hashing types */ +#define BOND_XMIT_POLICY_LAYER2 0 /* layer 2 (MAC only), default */ +#define BOND_XMIT_POLICY_LAYER34 1 /* layer 3+4 (IP ^ (TCP || UDP)) */ +#define BOND_XMIT_POLICY_LAYER23 2 /* layer 2+3 (IP ^ MAC) */ +#define BOND_XMIT_POLICY_ENCAP23 3 /* encapsulated layer 2+3 */ +#define BOND_XMIT_POLICY_ENCAP34 4 /* encapsulated layer 3+4 */ + +typedef struct ifbond { + __s32 bond_mode; + __s32 num_slaves; + __s32 miimon; +} ifbond; + +typedef struct ifslave { + __s32 slave_id; /* Used as an IN param to the BOND_SLAVE_INFO_QUERY ioctl */ + char slave_name[IFNAMSIZ]; + __s8 link; + __s8 state; + __u32 link_failure_count; +} ifslave; + +struct ad_info { + __u16 aggregator_id; + __u16 ports; + __u16 actor_key; + __u16 partner_key; + __u8 partner_system[ETH_ALEN]; +}; + +#endif /* _LINUX_IF_BONDING_H */ + +/* + * Local variables: + * version-control: t + * kept-new-versions: 5 + * c-indent-level: 8 + * c-basic-offset: 8 + * tab-width: 8 + * End: + */ + diff --git a/include/linux/if_bridge.h b/include/linux/if_bridge.h new file mode 100644 index 0000000000000000000000000000000000000000..4c67082f940f9af7c068fb05583765f13d4a82b9 --- /dev/null +++ b/include/linux/if_bridge.h @@ -0,0 +1,277 @@ +/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */ +/* + * Linux ethernet bridge + * + * Authors: + * Lennert Buytenhek + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#ifndef _LINUX_IF_BRIDGE_H +#define _LINUX_IF_BRIDGE_H + +#include +#include + +#define SYSFS_BRIDGE_ATTR "bridge" +#define SYSFS_BRIDGE_FDB "brforward" +#define SYSFS_BRIDGE_PORT_SUBDIR "brif" +#define SYSFS_BRIDGE_PORT_ATTR "brport" +#define SYSFS_BRIDGE_PORT_LINK "bridge" + +#define BRCTL_VERSION 1 + +#define BRCTL_GET_VERSION 0 +#define BRCTL_GET_BRIDGES 1 +#define BRCTL_ADD_BRIDGE 2 +#define BRCTL_DEL_BRIDGE 3 +#define BRCTL_ADD_IF 4 +#define BRCTL_DEL_IF 5 +#define BRCTL_GET_BRIDGE_INFO 6 +#define BRCTL_GET_PORT_LIST 7 +#define BRCTL_SET_BRIDGE_FORWARD_DELAY 8 +#define BRCTL_SET_BRIDGE_HELLO_TIME 9 +#define BRCTL_SET_BRIDGE_MAX_AGE 10 +#define BRCTL_SET_AGEING_TIME 11 +#define BRCTL_SET_GC_INTERVAL 12 +#define BRCTL_GET_PORT_INFO 13 +#define BRCTL_SET_BRIDGE_STP_STATE 14 +#define BRCTL_SET_BRIDGE_PRIORITY 15 +#define BRCTL_SET_PORT_PRIORITY 16 +#define BRCTL_SET_PATH_COST 17 +#define BRCTL_GET_FDB_ENTRIES 18 + +#define BR_STATE_DISABLED 0 +#define BR_STATE_LISTENING 1 +#define BR_STATE_LEARNING 2 +#define BR_STATE_FORWARDING 3 +#define BR_STATE_BLOCKING 4 + +struct __bridge_info { + __u64 designated_root; + __u64 bridge_id; + __u32 root_path_cost; + __u32 max_age; + __u32 hello_time; + __u32 forward_delay; + __u32 bridge_max_age; + __u32 bridge_hello_time; + __u32 bridge_forward_delay; + __u8 topology_change; + __u8 topology_change_detected; + __u8 root_port; + __u8 stp_enabled; + __u32 ageing_time; + __u32 gc_interval; + __u32 hello_timer_value; + __u32 tcn_timer_value; + __u32 topology_change_timer_value; + __u32 gc_timer_value; +}; + +struct __port_info { + __u64 designated_root; + __u64 designated_bridge; + __u16 port_id; + __u16 designated_port; + __u32 path_cost; + __u32 designated_cost; + __u8 state; + __u8 top_change_ack; + __u8 config_pending; + __u8 unused0; + __u32 message_age_timer_value; + __u32 forward_delay_timer_value; + __u32 hold_timer_value; +}; + +struct __fdb_entry { + __u8 mac_addr[ETH_ALEN]; + __u8 port_no; + __u8 is_local; + __u32 ageing_timer_value; + __u8 port_hi; + __u8 pad0; + __u16 unused; +}; + +/* Bridge Flags */ +#define BRIDGE_FLAGS_MASTER 1 /* Bridge command to/from master */ +#define BRIDGE_FLAGS_SELF 2 /* Bridge command to/from lowerdev */ + +#define BRIDGE_MODE_VEB 0 /* Default loopback mode */ +#define BRIDGE_MODE_VEPA 1 /* 802.1Qbg defined VEPA mode */ +#define BRIDGE_MODE_UNDEF 0xFFFF /* mode undefined */ + +/* Bridge management nested attributes + * [IFLA_AF_SPEC] = { + * [IFLA_BRIDGE_FLAGS] + * [IFLA_BRIDGE_MODE] + * [IFLA_BRIDGE_VLAN_INFO] + * } + */ +enum { + IFLA_BRIDGE_FLAGS, + IFLA_BRIDGE_MODE, + IFLA_BRIDGE_VLAN_INFO, + IFLA_BRIDGE_VLAN_TUNNEL_INFO, + __IFLA_BRIDGE_MAX, +}; +#define IFLA_BRIDGE_MAX (__IFLA_BRIDGE_MAX - 1) + +#define BRIDGE_VLAN_INFO_MASTER (1<<0) /* Operate on Bridge device as well */ +#define BRIDGE_VLAN_INFO_PVID (1<<1) /* VLAN is PVID, ingress untagged */ +#define BRIDGE_VLAN_INFO_UNTAGGED (1<<2) /* VLAN egresses untagged */ +#define BRIDGE_VLAN_INFO_RANGE_BEGIN (1<<3) /* VLAN is start of vlan range */ +#define BRIDGE_VLAN_INFO_RANGE_END (1<<4) /* VLAN is end of vlan range */ +#define BRIDGE_VLAN_INFO_BRENTRY (1<<5) /* Global bridge VLAN entry */ + +struct bridge_vlan_info { + __u16 flags; + __u16 vid; +}; + +enum { + IFLA_BRIDGE_VLAN_TUNNEL_UNSPEC, + IFLA_BRIDGE_VLAN_TUNNEL_ID, + IFLA_BRIDGE_VLAN_TUNNEL_VID, + IFLA_BRIDGE_VLAN_TUNNEL_FLAGS, + __IFLA_BRIDGE_VLAN_TUNNEL_MAX, +}; + +#define IFLA_BRIDGE_VLAN_TUNNEL_MAX (__IFLA_BRIDGE_VLAN_TUNNEL_MAX - 1) + +struct bridge_vlan_xstats { + __u64 rx_bytes; + __u64 rx_packets; + __u64 tx_bytes; + __u64 tx_packets; + __u16 vid; + __u16 flags; + __u32 pad2; +}; + +/* Bridge multicast database attributes + * [MDBA_MDB] = { + * [MDBA_MDB_ENTRY] = { + * [MDBA_MDB_ENTRY_INFO] { + * struct br_mdb_entry + * [MDBA_MDB_EATTR attributes] + * } + * } + * } + * [MDBA_ROUTER] = { + * [MDBA_ROUTER_PORT] = { + * u32 ifindex + * [MDBA_ROUTER_PATTR attributes] + * } + * } + */ +enum { + MDBA_UNSPEC, + MDBA_MDB, + MDBA_ROUTER, + __MDBA_MAX, +}; +#define MDBA_MAX (__MDBA_MAX - 1) + +enum { + MDBA_MDB_UNSPEC, + MDBA_MDB_ENTRY, + __MDBA_MDB_MAX, +}; +#define MDBA_MDB_MAX (__MDBA_MDB_MAX - 1) + +enum { + MDBA_MDB_ENTRY_UNSPEC, + MDBA_MDB_ENTRY_INFO, + __MDBA_MDB_ENTRY_MAX, +}; +#define MDBA_MDB_ENTRY_MAX (__MDBA_MDB_ENTRY_MAX - 1) + +/* per mdb entry additional attributes */ +enum { + MDBA_MDB_EATTR_UNSPEC, + MDBA_MDB_EATTR_TIMER, + __MDBA_MDB_EATTR_MAX +}; +#define MDBA_MDB_EATTR_MAX (__MDBA_MDB_EATTR_MAX - 1) + +/* multicast router types */ +enum { + MDB_RTR_TYPE_DISABLED, + MDB_RTR_TYPE_TEMP_QUERY, + MDB_RTR_TYPE_PERM, + MDB_RTR_TYPE_TEMP +}; + +enum { + MDBA_ROUTER_UNSPEC, + MDBA_ROUTER_PORT, + __MDBA_ROUTER_MAX, +}; +#define MDBA_ROUTER_MAX (__MDBA_ROUTER_MAX - 1) + +/* router port attributes */ +enum { + MDBA_ROUTER_PATTR_UNSPEC, + MDBA_ROUTER_PATTR_TIMER, + MDBA_ROUTER_PATTR_TYPE, + __MDBA_ROUTER_PATTR_MAX +}; +#define MDBA_ROUTER_PATTR_MAX (__MDBA_ROUTER_PATTR_MAX - 1) + +struct br_port_msg { + __u8 family; + __u32 ifindex; +}; + +enum { + MDBA_SET_ENTRY_UNSPEC, + MDBA_SET_ENTRY, + __MDBA_SET_ENTRY_MAX, +}; +#define MDBA_SET_ENTRY_MAX (__MDBA_SET_ENTRY_MAX - 1) + +/* Embedded inside LINK_XSTATS_TYPE_BRIDGE */ +enum { + BRIDGE_XSTATS_UNSPEC, + BRIDGE_XSTATS_VLAN, + BRIDGE_XSTATS_MCAST, + BRIDGE_XSTATS_PAD, + __BRIDGE_XSTATS_MAX +}; +#define BRIDGE_XSTATS_MAX (__BRIDGE_XSTATS_MAX - 1) + +enum { + BR_MCAST_DIR_RX, + BR_MCAST_DIR_TX, + BR_MCAST_DIR_SIZE +}; + +/* IGMP/MLD statistics */ +struct br_mcast_stats { + __u64 igmp_v1queries[BR_MCAST_DIR_SIZE]; + __u64 igmp_v2queries[BR_MCAST_DIR_SIZE]; + __u64 igmp_v3queries[BR_MCAST_DIR_SIZE]; + __u64 igmp_leaves[BR_MCAST_DIR_SIZE]; + __u64 igmp_v1reports[BR_MCAST_DIR_SIZE]; + __u64 igmp_v2reports[BR_MCAST_DIR_SIZE]; + __u64 igmp_v3reports[BR_MCAST_DIR_SIZE]; + __u64 igmp_parse_errors; + + __u64 mld_v1queries[BR_MCAST_DIR_SIZE]; + __u64 mld_v2queries[BR_MCAST_DIR_SIZE]; + __u64 mld_leaves[BR_MCAST_DIR_SIZE]; + __u64 mld_v1reports[BR_MCAST_DIR_SIZE]; + __u64 mld_v2reports[BR_MCAST_DIR_SIZE]; + __u64 mld_parse_errors; + + __u64 mcast_bytes[BR_MCAST_DIR_SIZE]; + __u64 mcast_packets[BR_MCAST_DIR_SIZE]; +}; +#endif /* _LINUX_IF_BRIDGE_H */ diff --git a/include/linux/if_ether.h b/include/linux/if_ether.h new file mode 100644 index 0000000000000000000000000000000000000000..8c36f63e6a38f9117504098a3676d3b1653af962 --- /dev/null +++ b/include/linux/if_ether.h @@ -0,0 +1,169 @@ +/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */ +/* + * INET An implementation of the TCP/IP protocol suite for the LINUX + * operating system. INET is implemented using the BSD Socket + * interface as the means of communication with the user level. + * + * Global definitions for the Ethernet IEEE 802.3 interface. + * + * Version: @(#)if_ether.h 1.0.1a 02/08/94 + * + * Author: Fred N. van Kempen, + * Donald Becker, + * Alan Cox, + * Steve Whitehouse, + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#ifndef _LINUX_IF_ETHER_H +#define _LINUX_IF_ETHER_H + +#include + +/* + * IEEE 802.3 Ethernet magic constants. The frame sizes omit the preamble + * and FCS/CRC (frame check sequence). + */ + +#define ETH_ALEN 6 /* Octets in one ethernet addr */ +#define ETH_TLEN 2 /* Octets in ethernet type field */ +#define ETH_HLEN 14 /* Total octets in header. */ +#define ETH_ZLEN 60 /* Min. octets in frame sans FCS */ +#define ETH_DATA_LEN 1500 /* Max. octets in payload */ +#define ETH_FRAME_LEN 1514 /* Max. octets in frame sans FCS */ +#define ETH_FCS_LEN 4 /* Octets in the FCS */ + +#define ETH_MIN_MTU 68 /* Min IPv4 MTU per RFC791 */ +#define ETH_MAX_MTU 0xFFFFU /* 65535, same as IP_MAX_MTU */ + +/* + * These are the defined Ethernet Protocol ID's. + */ + +#define ETH_P_LOOP 0x0060 /* Ethernet Loopback packet */ +#define ETH_P_PUP 0x0200 /* Xerox PUP packet */ +#define ETH_P_PUPAT 0x0201 /* Xerox PUP Addr Trans packet */ +#define ETH_P_TSN 0x22F0 /* TSN (IEEE 1722) packet */ +#define ETH_P_ERSPAN2 0x22EB /* ERSPAN version 2 (type III) */ +#define ETH_P_IP 0x0800 /* Internet Protocol packet */ +#define ETH_P_X25 0x0805 /* CCITT X.25 */ +#define ETH_P_ARP 0x0806 /* Address Resolution packet */ +#define ETH_P_BPQ 0x08FF /* G8BPQ AX.25 Ethernet Packet [ NOT AN OFFICIALLY REGISTERED ID ] */ +#define ETH_P_IEEEPUP 0x0a00 /* Xerox IEEE802.3 PUP packet */ +#define ETH_P_IEEEPUPAT 0x0a01 /* Xerox IEEE802.3 PUP Addr Trans packet */ +#define ETH_P_BATMAN 0x4305 /* B.A.T.M.A.N.-Advanced packet [ NOT AN OFFICIALLY REGISTERED ID ] */ +#define ETH_P_DEC 0x6000 /* DEC Assigned proto */ +#define ETH_P_DNA_DL 0x6001 /* DEC DNA Dump/Load */ +#define ETH_P_DNA_RC 0x6002 /* DEC DNA Remote Console */ +#define ETH_P_DNA_RT 0x6003 /* DEC DNA Routing */ +#define ETH_P_LAT 0x6004 /* DEC LAT */ +#define ETH_P_DIAG 0x6005 /* DEC Diagnostics */ +#define ETH_P_CUST 0x6006 /* DEC Customer use */ +#define ETH_P_SCA 0x6007 /* DEC Systems Comms Arch */ +#define ETH_P_TEB 0x6558 /* Trans Ether Bridging */ +#define ETH_P_RARP 0x8035 /* Reverse Addr Res packet */ +#define ETH_P_ATALK 0x809B /* Appletalk DDP */ +#define ETH_P_AARP 0x80F3 /* Appletalk AARP */ +#define ETH_P_8021Q 0x8100 /* 802.1Q VLAN Extended Header */ +#define ETH_P_ERSPAN 0x88BE /* ERSPAN type II */ +#define ETH_P_IPX 0x8137 /* IPX over DIX */ +#define ETH_P_IPV6 0x86DD /* IPv6 over bluebook */ +#define ETH_P_PAUSE 0x8808 /* IEEE Pause frames. See 802.3 31B */ +#define ETH_P_SLOW 0x8809 /* Slow Protocol. See 802.3ad 43B */ +#define ETH_P_WCCP 0x883E /* Web-cache coordination protocol + * defined in draft-wilson-wrec-wccp-v2-00.txt */ +#define ETH_P_MPLS_UC 0x8847 /* MPLS Unicast traffic */ +#define ETH_P_MPLS_MC 0x8848 /* MPLS Multicast traffic */ +#define ETH_P_ATMMPOA 0x884c /* MultiProtocol Over ATM */ +#define ETH_P_PPP_DISC 0x8863 /* PPPoE discovery messages */ +#define ETH_P_PPP_SES 0x8864 /* PPPoE session messages */ +#define ETH_P_LINK_CTL 0x886c /* HPNA, wlan link local tunnel */ +#define ETH_P_ATMFATE 0x8884 /* Frame-based ATM Transport + * over Ethernet + */ +#define ETH_P_PAE 0x888E /* Port Access Entity (IEEE 802.1X) */ +#define ETH_P_AOE 0x88A2 /* ATA over Ethernet */ +#define ETH_P_8021AD 0x88A8 /* 802.1ad Service VLAN */ +#define ETH_P_802_EX1 0x88B5 /* 802.1 Local Experimental 1. */ +#define ETH_P_PREAUTH 0x88C7 /* 802.11 Preauthentication */ +#define ETH_P_TIPC 0x88CA /* TIPC */ +#define ETH_P_MACSEC 0x88E5 /* 802.1ae MACsec */ +#define ETH_P_8021AH 0x88E7 /* 802.1ah Backbone Service Tag */ +#define ETH_P_MVRP 0x88F5 /* 802.1Q MVRP */ +#define ETH_P_1588 0x88F7 /* IEEE 1588 Timesync */ +#define ETH_P_NCSI 0x88F8 /* NCSI protocol */ +#define ETH_P_PRP 0x88FB /* IEC 62439-3 PRP/HSRv0 */ +#define ETH_P_FCOE 0x8906 /* Fibre Channel over Ethernet */ +#define ETH_P_IBOE 0x8915 /* Infiniband over Ethernet */ +#define ETH_P_TDLS 0x890D /* TDLS */ +#define ETH_P_FIP 0x8914 /* FCoE Initialization Protocol */ +#define ETH_P_80221 0x8917 /* IEEE 802.21 Media Independent Handover Protocol */ +#define ETH_P_HSR 0x892F /* IEC 62439-3 HSRv1 */ +#define ETH_P_NSH 0x894F /* Network Service Header */ +#define ETH_P_LOOPBACK 0x9000 /* Ethernet loopback packet, per IEEE 802.3 */ +#define ETH_P_QINQ1 0x9100 /* deprecated QinQ VLAN [ NOT AN OFFICIALLY REGISTERED ID ] */ +#define ETH_P_QINQ2 0x9200 /* deprecated QinQ VLAN [ NOT AN OFFICIALLY REGISTERED ID ] */ +#define ETH_P_QINQ3 0x9300 /* deprecated QinQ VLAN [ NOT AN OFFICIALLY REGISTERED ID ] */ +#define ETH_P_EDSA 0xDADA /* Ethertype DSA [ NOT AN OFFICIALLY REGISTERED ID ] */ +#define ETH_P_IFE 0xED3E /* ForCES inter-FE LFB type */ +#define ETH_P_AF_IUCV 0xFBFB /* IBM af_iucv [ NOT AN OFFICIALLY REGISTERED ID ] */ + +#define ETH_P_802_3_MIN 0x0600 /* If the value in the ethernet type is less than this value + * then the frame is Ethernet II. Else it is 802.3 */ + +/* + * Non DIX types. Won't clash for 1500 types. + */ + +#define ETH_P_802_3 0x0001 /* Dummy type for 802.3 frames */ +#define ETH_P_AX25 0x0002 /* Dummy protocol id for AX.25 */ +#define ETH_P_ALL 0x0003 /* Every packet (be careful!!!) */ +#define ETH_P_802_2 0x0004 /* 802.2 frames */ +#define ETH_P_SNAP 0x0005 /* Internal only */ +#define ETH_P_DDCMP 0x0006 /* DEC DDCMP: Internal only */ +#define ETH_P_WAN_PPP 0x0007 /* Dummy type for WAN PPP frames*/ +#define ETH_P_PPP_MP 0x0008 /* Dummy type for PPP MP frames */ +#define ETH_P_LOCALTALK 0x0009 /* Localtalk pseudo type */ +#define ETH_P_CAN 0x000C /* CAN: Controller Area Network */ +#define ETH_P_CANFD 0x000D /* CANFD: CAN flexible data rate*/ +#define ETH_P_PPPTALK 0x0010 /* Dummy type for Atalk over PPP*/ +#define ETH_P_TR_802_2 0x0011 /* 802.2 frames */ +#define ETH_P_MOBITEX 0x0015 /* Mobitex (kaz@cafe.net) */ +#define ETH_P_CONTROL 0x0016 /* Card specific control frames */ +#define ETH_P_IRDA 0x0017 /* Linux-IrDA */ +#define ETH_P_ECONET 0x0018 /* Acorn Econet */ +#define ETH_P_HDLC 0x0019 /* HDLC frames */ +#define ETH_P_ARCNET 0x001A /* 1A for ArcNet :-) */ +#define ETH_P_DSA 0x001B /* Distributed Switch Arch. */ +#define ETH_P_TRAILER 0x001C /* Trailer switch tagging */ +#define ETH_P_PHONET 0x00F5 /* Nokia Phonet frames */ +#define ETH_P_IEEE802154 0x00F6 /* IEEE802.15.4 frame */ +#define ETH_P_CAIF 0x00F7 /* ST-Ericsson CAIF protocol */ +#define ETH_P_XDSA 0x00F8 /* Multiplexed DSA protocol */ +#define ETH_P_MAP 0x00F9 /* Qualcomm multiplexing and + * aggregation protocol + */ + +/* + * This is an Ethernet frame header. + */ + +/* allow libcs like musl to deactivate this, glibc does not implement this. */ +#ifndef __UAPI_DEF_ETHHDR +#define __UAPI_DEF_ETHHDR 1 +#endif + +#if __UAPI_DEF_ETHHDR +struct ethhdr { + unsigned char h_dest[ETH_ALEN]; /* destination eth addr */ + unsigned char h_source[ETH_ALEN]; /* source ether addr */ + __be16 h_proto; /* packet type ID field */ +} __attribute__((packed)); +#endif + + +#endif /* _LINUX_IF_ETHER_H */ diff --git a/include/linux/if_link.h b/include/linux/if_link.h new file mode 100644 index 0000000000000000000000000000000000000000..a1e75597b8b2e69b9e7f637343ba04d2e03244cf --- /dev/null +++ b/include/linux/if_link.h @@ -0,0 +1,251 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +#ifndef _LINUX_IF_LINK_H +#define _LINUX_IF_LINK_H + +#include +#include + +/* This struct should be in sync with struct rtnl_link_stats64 */ +struct rtnl_link_stats { + __u32 rx_packets; /* total packets received */ + __u32 tx_packets; /* total packets transmitted */ + __u32 rx_bytes; /* total bytes received */ + __u32 tx_bytes; /* total bytes transmitted */ + __u32 rx_errors; /* bad packets received */ + __u32 tx_errors; /* packet transmit problems */ + __u32 rx_dropped; /* no space in linux buffers */ + __u32 tx_dropped; /* no space available in linux */ + __u32 multicast; /* multicast packets received */ + __u32 collisions; + + /* detailed rx_errors: */ + __u32 rx_length_errors; + __u32 rx_over_errors; /* receiver ring buff overflow */ + __u32 rx_crc_errors; /* recved pkt with crc error */ + __u32 rx_frame_errors; /* recv'd frame alignment error */ + __u32 rx_fifo_errors; /* recv'r fifo overrun */ + __u32 rx_missed_errors; /* receiver missed packet */ + + /* detailed tx_errors */ + __u32 tx_aborted_errors; + __u32 tx_carrier_errors; + __u32 tx_fifo_errors; + __u32 tx_heartbeat_errors; + __u32 tx_window_errors; + + /* for cslip etc */ + __u32 rx_compressed; + __u32 tx_compressed; + + __u32 rx_nohandler; /* dropped, no handler found */ +}; + +/* The main device statistics structure */ +struct rtnl_link_stats64 { + __u64 rx_packets; /* total packets received */ + __u64 tx_packets; /* total packets transmitted */ + __u64 rx_bytes; /* total bytes received */ + __u64 tx_bytes; /* total bytes transmitted */ + __u64 rx_errors; /* bad packets received */ + __u64 tx_errors; /* packet transmit problems */ + __u64 rx_dropped; /* no space in linux buffers */ + __u64 tx_dropped; /* no space available in linux */ + __u64 multicast; /* multicast packets received */ + __u64 collisions; + + /* detailed rx_errors: */ + __u64 rx_length_errors; + __u64 rx_over_errors; /* receiver ring buff overflow */ + __u64 rx_crc_errors; /* recved pkt with crc error */ + __u64 rx_frame_errors; /* recv'd frame alignment error */ + __u64 rx_fifo_errors; /* recv'r fifo overrun */ + __u64 rx_missed_errors; /* receiver missed packet */ + + /* detailed tx_errors */ + __u64 tx_aborted_errors; + __u64 tx_carrier_errors; + __u64 tx_fifo_errors; + __u64 tx_heartbeat_errors; + __u64 tx_window_errors; + + /* for cslip etc */ + __u64 rx_compressed; + __u64 tx_compressed; + + __u64 rx_nohandler; /* dropped, no handler found */ +}; + +/* The struct should be in sync with struct ifmap */ +struct rtnl_link_ifmap { + __u64 mem_start; + __u64 mem_end; + __u64 base_addr; + __u16 irq; + __u8 dma; + __u8 port; +}; + +/* + * IFLA_AF_SPEC + * Contains nested attributes for address family specific attributes. + * Each address family may create a attribute with the address family + * number as type and create its own attribute structure in it. + * + * Example: + * [IFLA_AF_SPEC] = { + * [AF_INET] = { + * [IFLA_INET_CONF] = ..., + * }, + * [AF_INET6] = { + * [IFLA_INET6_FLAGS] = ..., + * [IFLA_INET6_CONF] = ..., + * } + * } + */ + +enum { + IFLA_UNSPEC, + IFLA_ADDRESS, + IFLA_BROADCAST, + IFLA_IFNAME, + IFLA_MTU, + IFLA_LINK, + IFLA_QDISC, + IFLA_STATS, + IFLA_COST, +#define IFLA_COST IFLA_COST + IFLA_PRIORITY, +#define IFLA_PRIORITY IFLA_PRIORITY + IFLA_MASTER, +#define IFLA_MASTER IFLA_MASTER + IFLA_WIRELESS, /* Wireless Extension event - see wireless.h */ +#define IFLA_WIRELESS IFLA_WIRELESS + IFLA_PROTINFO, /* Protocol specific information for a link */ +#define IFLA_PROTINFO IFLA_PROTINFO + IFLA_TXQLEN, +#define IFLA_TXQLEN IFLA_TXQLEN + IFLA_MAP, +#define IFLA_MAP IFLA_MAP + IFLA_WEIGHT, +#define IFLA_WEIGHT IFLA_WEIGHT + IFLA_OPERSTATE, + IFLA_LINKMODE, + IFLA_LINKINFO, +#define IFLA_LINKINFO IFLA_LINKINFO + IFLA_NET_NS_PID, + IFLA_IFALIAS, + IFLA_NUM_VF, /* Number of VFs if device is SR-IOV PF */ + IFLA_VFINFO_LIST, + IFLA_STATS64, + IFLA_VF_PORTS, + IFLA_PORT_SELF, + IFLA_AF_SPEC, + IFLA_GROUP, /* Group the device belongs to */ + IFLA_NET_NS_FD, + IFLA_EXT_MASK, /* Extended info mask, VFs, etc */ + IFLA_PROMISCUITY, /* Promiscuity count: > 0 means acts PROMISC */ +#define IFLA_PROMISCUITY IFLA_PROMISCUITY + IFLA_NUM_TX_QUEUES, + IFLA_NUM_RX_QUEUES, + IFLA_CARRIER, + IFLA_PHYS_PORT_ID, + IFLA_CARRIER_CHANGES, + IFLA_PHYS_SWITCH_ID, + IFLA_LINK_NETNSID, + IFLA_PHYS_PORT_NAME, + IFLA_PROTO_DOWN, + IFLA_GSO_MAX_SEGS, + IFLA_GSO_MAX_SIZE, + IFLA_PAD, + IFLA_XDP, + IFLA_EVENT, + IFLA_NEW_NETNSID, + IFLA_IF_NETNSID, + IFLA_CARRIER_UP_COUNT, + IFLA_CARRIER_DOWN_COUNT, + IFLA_NEW_IFINDEX, + IFLA_MIN_MTU, + IFLA_MAX_MTU, + __IFLA_MAX +}; + + +#define IFLA_MAX (__IFLA_MAX - 1) + +/* backwards compatibility for userspace */ +#define IFLA_RTA(r) ((struct rtattr*)(((char*)(r)) + NLMSG_ALIGN(sizeof(struct ifinfomsg)))) +#define IFLA_PAYLOAD(n) NLMSG_PAYLOAD(n,sizeof(struct ifinfomsg)) + +enum { + IFLA_INET_UNSPEC, + IFLA_INET_CONF, + __IFLA_INET_MAX, +}; + +#define IFLA_INET_MAX (__IFLA_INET_MAX - 1) + +/* ifi_flags. + + IFF_* flags. + + The only change is: + IFF_LOOPBACK, IFF_BROADCAST and IFF_POINTOPOINT are + more not changeable by user. They describe link media + characteristics and set by device driver. + + Comments: + - Combination IFF_BROADCAST|IFF_POINTOPOINT is invalid + - If neither of these three flags are set; + the interface is NBMA. + + - IFF_MULTICAST does not mean anything special: + multicasts can be used on all not-NBMA links. + IFF_MULTICAST means that this media uses special encapsulation + for multicast frames. Apparently, all IFF_POINTOPOINT and + IFF_BROADCAST devices are able to use multicasts too. + */ + +/* IFLA_LINK. + For usual devices it is equal ifi_index. + If it is a "virtual interface" (f.e. tunnel), ifi_link + can point to real physical interface (f.e. for bandwidth calculations), + or maybe 0, what means, that real media is unknown (usual + for IPIP tunnels, when route to endpoint is allowed to change) + */ + +/* Subtype attributes for IFLA_PROTINFO */ +enum { + IFLA_INET6_UNSPEC, + IFLA_INET6_FLAGS, /* link flags */ + IFLA_INET6_CONF, /* sysctl parameters */ + IFLA_INET6_STATS, /* statistics */ + IFLA_INET6_MCAST, /* MC things. What of them? */ + IFLA_INET6_CACHEINFO, /* time values and max reasm size */ + IFLA_INET6_ICMP6STATS, /* statistics (icmpv6) */ + IFLA_INET6_TOKEN, /* device token */ + IFLA_INET6_ADDR_GEN_MODE, /* implicit address generator mode */ + __IFLA_INET6_MAX +}; + +#define IFLA_INET6_MAX (__IFLA_INET6_MAX - 1) + +enum in6_addr_gen_mode { + IN6_ADDR_GEN_MODE_EUI64, + IN6_ADDR_GEN_MODE_NONE, + IN6_ADDR_GEN_MODE_STABLE_PRIVACY, + IN6_ADDR_GEN_MODE_RANDOM, +}; + +enum { + IFLA_INFO_UNSPEC, + IFLA_INFO_KIND, + IFLA_INFO_DATA, + IFLA_INFO_XSTATS, + IFLA_INFO_SLAVE_KIND, + IFLA_INFO_SLAVE_DATA, + __IFLA_INFO_MAX, +}; + +#define IFLA_INFO_MAX (__IFLA_INFO_MAX - 1) + +#endif /* _LINUX_IF_LINK_H */ diff --git a/include/linux/if_packet.h b/include/linux/if_packet.h new file mode 100644 index 0000000000000000000000000000000000000000..67b61d91d89bf4fc8a54e01ffa3ca253a7fc45c9 --- /dev/null +++ b/include/linux/if_packet.h @@ -0,0 +1,303 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +#ifndef __LINUX_IF_PACKET_H +#define __LINUX_IF_PACKET_H + +#include + +struct sockaddr_pkt { + unsigned short spkt_family; + unsigned char spkt_device[14]; + __be16 spkt_protocol; +}; + +struct sockaddr_ll { + unsigned short sll_family; + __be16 sll_protocol; + int sll_ifindex; + unsigned short sll_hatype; + unsigned char sll_pkttype; + unsigned char sll_halen; + unsigned char sll_addr[8]; +}; + +/* Packet types */ + +#define PACKET_HOST 0 /* To us */ +#define PACKET_BROADCAST 1 /* To all */ +#define PACKET_MULTICAST 2 /* To group */ +#define PACKET_OTHERHOST 3 /* To someone else */ +#define PACKET_OUTGOING 4 /* Outgoing of any type */ +#define PACKET_LOOPBACK 5 /* MC/BRD frame looped back */ +#define PACKET_USER 6 /* To user space */ +#define PACKET_KERNEL 7 /* To kernel space */ +/* Unused, PACKET_FASTROUTE and PACKET_LOOPBACK are invisible to user space */ +#define PACKET_FASTROUTE 6 /* Fastrouted frame */ + +/* Packet socket options */ + +#define PACKET_ADD_MEMBERSHIP 1 +#define PACKET_DROP_MEMBERSHIP 2 +#define PACKET_RECV_OUTPUT 3 +/* Value 4 is still used by obsolete turbo-packet. */ +#define PACKET_RX_RING 5 +#define PACKET_STATISTICS 6 +#define PACKET_COPY_THRESH 7 +#define PACKET_AUXDATA 8 +#define PACKET_ORIGDEV 9 +#define PACKET_VERSION 10 +#define PACKET_HDRLEN 11 +#define PACKET_RESERVE 12 +#define PACKET_TX_RING 13 +#define PACKET_LOSS 14 +#define PACKET_VNET_HDR 15 +#define PACKET_TX_TIMESTAMP 16 +#define PACKET_TIMESTAMP 17 +#define PACKET_FANOUT 18 +#define PACKET_TX_HAS_OFF 19 +#define PACKET_QDISC_BYPASS 20 +#define PACKET_ROLLOVER_STATS 21 +#define PACKET_FANOUT_DATA 22 + +#define PACKET_FANOUT_HASH 0 +#define PACKET_FANOUT_LB 1 +#define PACKET_FANOUT_CPU 2 +#define PACKET_FANOUT_ROLLOVER 3 +#define PACKET_FANOUT_RND 4 +#define PACKET_FANOUT_QM 5 +#define PACKET_FANOUT_CBPF 6 +#define PACKET_FANOUT_EBPF 7 +#define PACKET_FANOUT_FLAG_ROLLOVER 0x1000 +#define PACKET_FANOUT_FLAG_UNIQUEID 0x2000 +#define PACKET_FANOUT_FLAG_DEFRAG 0x8000 + +struct tpacket_stats { + unsigned int tp_packets; + unsigned int tp_drops; +}; + +struct tpacket_stats_v3 { + unsigned int tp_packets; + unsigned int tp_drops; + unsigned int tp_freeze_q_cnt; +}; + +struct tpacket_rollover_stats { + __aligned_u64 tp_all; + __aligned_u64 tp_huge; + __aligned_u64 tp_failed; +}; + +union tpacket_stats_u { + struct tpacket_stats stats1; + struct tpacket_stats_v3 stats3; +}; + +struct tpacket_auxdata { + __u32 tp_status; + __u32 tp_len; + __u32 tp_snaplen; + __u16 tp_mac; + __u16 tp_net; + __u16 tp_vlan_tci; + __u16 tp_vlan_tpid; +}; + +/* Rx ring - header status */ +#define TP_STATUS_KERNEL 0 +#define TP_STATUS_USER (1 << 0) +#define TP_STATUS_COPY (1 << 1) +#define TP_STATUS_LOSING (1 << 2) +#define TP_STATUS_CSUMNOTREADY (1 << 3) +#define TP_STATUS_VLAN_VALID (1 << 4) /* auxdata has valid tp_vlan_tci */ +#define TP_STATUS_BLK_TMO (1 << 5) +#define TP_STATUS_VLAN_TPID_VALID (1 << 6) /* auxdata has valid tp_vlan_tpid */ +#define TP_STATUS_CSUM_VALID (1 << 7) + +/* Tx ring - header status */ +#define TP_STATUS_AVAILABLE 0 +#define TP_STATUS_SEND_REQUEST (1 << 0) +#define TP_STATUS_SENDING (1 << 1) +#define TP_STATUS_WRONG_FORMAT (1 << 2) + +/* Rx and Tx ring - header status */ +#define TP_STATUS_TS_SOFTWARE (1 << 29) +#define TP_STATUS_TS_SYS_HARDWARE (1 << 30) /* deprecated, never set */ +#define TP_STATUS_TS_RAW_HARDWARE (1 << 31) + +/* Rx ring - feature request bits */ +#define TP_FT_REQ_FILL_RXHASH 0x1 + +struct tpacket_hdr { + unsigned long tp_status; + unsigned int tp_len; + unsigned int tp_snaplen; + unsigned short tp_mac; + unsigned short tp_net; + unsigned int tp_sec; + unsigned int tp_usec; +}; + +#define TPACKET_ALIGNMENT 16 +#define TPACKET_ALIGN(x) (((x)+TPACKET_ALIGNMENT-1)&~(TPACKET_ALIGNMENT-1)) +#define TPACKET_HDRLEN (TPACKET_ALIGN(sizeof(struct tpacket_hdr)) + sizeof(struct sockaddr_ll)) + +struct tpacket2_hdr { + __u32 tp_status; + __u32 tp_len; + __u32 tp_snaplen; + __u16 tp_mac; + __u16 tp_net; + __u32 tp_sec; + __u32 tp_nsec; + __u16 tp_vlan_tci; + __u16 tp_vlan_tpid; + __u8 tp_padding[4]; +}; + +struct tpacket_hdr_variant1 { + __u32 tp_rxhash; + __u32 tp_vlan_tci; + __u16 tp_vlan_tpid; + __u16 tp_padding; +}; + +struct tpacket3_hdr { + __u32 tp_next_offset; + __u32 tp_sec; + __u32 tp_nsec; + __u32 tp_snaplen; + __u32 tp_len; + __u32 tp_status; + __u16 tp_mac; + __u16 tp_net; + /* pkt_hdr variants */ + union { + struct tpacket_hdr_variant1 hv1; + }; + __u8 tp_padding[8]; +}; + +struct tpacket_bd_ts { + unsigned int ts_sec; + union { + unsigned int ts_usec; + unsigned int ts_nsec; + }; +}; + +struct tpacket_hdr_v1 { + __u32 block_status; + __u32 num_pkts; + __u32 offset_to_first_pkt; + + /* Number of valid bytes (including padding) + * blk_len <= tp_block_size + */ + __u32 blk_len; + + /* + * Quite a few uses of sequence number: + * 1. Make sure cache flush etc worked. + * Well, one can argue - why not use the increasing ts below? + * But look at 2. below first. + * 2. When you pass around blocks to other user space decoders, + * you can see which blk[s] is[are] outstanding etc. + * 3. Validate kernel code. + */ + __aligned_u64 seq_num; + + /* + * ts_last_pkt: + * + * Case 1. Block has 'N'(N >=1) packets and TMO'd(timed out) + * ts_last_pkt == 'time-stamp of last packet' and NOT the + * time when the timer fired and the block was closed. + * By providing the ts of the last packet we can absolutely + * guarantee that time-stamp wise, the first packet in the + * next block will never precede the last packet of the + * previous block. + * Case 2. Block has zero packets and TMO'd + * ts_last_pkt = time when the timer fired and the block + * was closed. + * Case 3. Block has 'N' packets and NO TMO. + * ts_last_pkt = time-stamp of the last pkt in the block. + * + * ts_first_pkt: + * Is always the time-stamp when the block was opened. + * Case a) ZERO packets + * No packets to deal with but atleast you know the + * time-interval of this block. + * Case b) Non-zero packets + * Use the ts of the first packet in the block. + * + */ + struct tpacket_bd_ts ts_first_pkt, ts_last_pkt; +}; + +union tpacket_bd_header_u { + struct tpacket_hdr_v1 bh1; +}; + +struct tpacket_block_desc { + __u32 version; + __u32 offset_to_priv; + union tpacket_bd_header_u hdr; +}; + +#define TPACKET2_HDRLEN (TPACKET_ALIGN(sizeof(struct tpacket2_hdr)) + sizeof(struct sockaddr_ll)) +#define TPACKET3_HDRLEN (TPACKET_ALIGN(sizeof(struct tpacket3_hdr)) + sizeof(struct sockaddr_ll)) + +enum tpacket_versions { + TPACKET_V1, + TPACKET_V2, + TPACKET_V3 +}; + +/* + Frame structure: + + - Start. Frame must be aligned to TPACKET_ALIGNMENT=16 + - struct tpacket_hdr + - pad to TPACKET_ALIGNMENT=16 + - struct sockaddr_ll + - Gap, chosen so that packet data (Start+tp_net) alignes to TPACKET_ALIGNMENT=16 + - Start+tp_mac: [ Optional MAC header ] + - Start+tp_net: Packet data, aligned to TPACKET_ALIGNMENT=16. + - Pad to align to TPACKET_ALIGNMENT=16 + */ + +struct tpacket_req { + unsigned int tp_block_size; /* Minimal size of contiguous block */ + unsigned int tp_block_nr; /* Number of blocks */ + unsigned int tp_frame_size; /* Size of frame */ + unsigned int tp_frame_nr; /* Total number of frames */ +}; + +struct tpacket_req3 { + unsigned int tp_block_size; /* Minimal size of contiguous block */ + unsigned int tp_block_nr; /* Number of blocks */ + unsigned int tp_frame_size; /* Size of frame */ + unsigned int tp_frame_nr; /* Total number of frames */ + unsigned int tp_retire_blk_tov; /* timeout in msecs */ + unsigned int tp_sizeof_priv; /* offset to private data area */ + unsigned int tp_feature_req_word; +}; + +union tpacket_req_u { + struct tpacket_req req; + struct tpacket_req3 req3; +}; + +struct packet_mreq { + int mr_ifindex; + unsigned short mr_type; + unsigned short mr_alen; + unsigned char mr_address[8]; +}; + +#define PACKET_MR_MULTICAST 0 +#define PACKET_MR_PROMISC 1 +#define PACKET_MR_ALLMULTI 2 +#define PACKET_MR_UNICAST 3 + +#endif diff --git a/include/linux/if_vlan.h b/include/linux/if_vlan.h new file mode 100644 index 0000000000000000000000000000000000000000..18a15dad5547b6c33a95d9c7197cf674512197ca --- /dev/null +++ b/include/linux/if_vlan.h @@ -0,0 +1,65 @@ +/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */ +/* + * VLAN An implementation of 802.1Q VLAN tagging. + * + * Authors: Ben Greear + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + */ + +#ifndef _LINUX_IF_VLAN_H_ +#define _LINUX_IF_VLAN_H_ + + +/* VLAN IOCTLs are found in sockios.h */ + +/* Passed in vlan_ioctl_args structure to determine behaviour. */ +enum vlan_ioctl_cmds { + ADD_VLAN_CMD, + DEL_VLAN_CMD, + SET_VLAN_INGRESS_PRIORITY_CMD, + SET_VLAN_EGRESS_PRIORITY_CMD, + GET_VLAN_INGRESS_PRIORITY_CMD, + GET_VLAN_EGRESS_PRIORITY_CMD, + SET_VLAN_NAME_TYPE_CMD, + SET_VLAN_FLAG_CMD, + GET_VLAN_REALDEV_NAME_CMD, /* If this works, you know it's a VLAN device, btw */ + GET_VLAN_VID_CMD /* Get the VID of this VLAN (specified by name) */ +}; + +enum vlan_flags { + VLAN_FLAG_REORDER_HDR = 0x1, + VLAN_FLAG_GVRP = 0x2, + VLAN_FLAG_LOOSE_BINDING = 0x4, + VLAN_FLAG_MVRP = 0x8, +}; + +enum vlan_name_types { + VLAN_NAME_TYPE_PLUS_VID, /* Name will look like: vlan0005 */ + VLAN_NAME_TYPE_RAW_PLUS_VID, /* name will look like: eth1.0005 */ + VLAN_NAME_TYPE_PLUS_VID_NO_PAD, /* Name will look like: vlan5 */ + VLAN_NAME_TYPE_RAW_PLUS_VID_NO_PAD, /* Name will look like: eth0.5 */ + VLAN_NAME_TYPE_HIGHEST +}; + +struct vlan_ioctl_args { + int cmd; /* Should be one of the vlan_ioctl_cmds enum above. */ + char device1[24]; + + union { + char device2[24]; + int VID; + unsigned int skb_priority; + unsigned int name_type; + unsigned int bind_type; + unsigned int flag; /* Matches vlan_dev_priv flags */ + } u; + + short vlan_qos; +}; + +#endif /* _LINUX_IF_VLAN_H_ */ diff --git a/include/linux/kernel.h b/include/linux/kernel.h new file mode 100644 index 0000000000000000000000000000000000000000..048e6b1a515bfb4803d183c1e526d37fc705ecc5 --- /dev/null +++ b/include/linux/kernel.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +#ifndef _LINUX_KERNEL_H +#define _LINUX_KERNEL_H + +#include + +/* + * 'kernel.h' contains some often-used function prototypes etc + */ +#define __ALIGN_KERNEL(x, a) __ALIGN_KERNEL_MASK(x, (typeof(x))(a) - 1) +#define __ALIGN_KERNEL_MASK(x, mask) (((x) + (mask)) & ~(mask)) + +#define __KERNEL_DIV_ROUND_UP(n, d) (((n) + (d) - 1) / (d)) + +#endif /* _LINUX_KERNEL_H */ diff --git a/include/linux/libc-compat.h b/include/linux/libc-compat.h new file mode 100644 index 0000000000000000000000000000000000000000..a1599911e7a942322aed41c6aa730caaa66075e9 --- /dev/null +++ b/include/linux/libc-compat.h @@ -0,0 +1,267 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +/* + * Compatibility interface for userspace libc header coordination: + * + * Define compatibility macros that are used to control the inclusion or + * exclusion of UAPI structures and definitions in coordination with another + * userspace C library. + * + * This header is intended to solve the problem of UAPI definitions that + * conflict with userspace definitions. If a UAPI header has such conflicting + * definitions then the solution is as follows: + * + * * Synchronize the UAPI header and the libc headers so either one can be + * used and such that the ABI is preserved. If this is not possible then + * no simple compatibility interface exists (you need to write translating + * wrappers and rename things) and you can't use this interface. + * + * Then follow this process: + * + * (a) Include libc-compat.h in the UAPI header. + * e.g. #include + * This include must be as early as possible. + * + * (b) In libc-compat.h add enough code to detect that the comflicting + * userspace libc header has been included first. + * + * (c) If the userspace libc header has been included first define a set of + * guard macros of the form __UAPI_DEF_FOO and set their values to 1, else + * set their values to 0. + * + * (d) Back in the UAPI header with the conflicting definitions, guard the + * definitions with: + * #if __UAPI_DEF_FOO + * ... + * #endif + * + * This fixes the situation where the linux headers are included *after* the + * libc headers. To fix the problem with the inclusion in the other order the + * userspace libc headers must be fixed like this: + * + * * For all definitions that conflict with kernel definitions wrap those + * defines in the following: + * #if !__UAPI_DEF_FOO + * ... + * #endif + * + * This prevents the redefinition of a construct already defined by the kernel. + */ +#ifndef _LIBC_COMPAT_H +#define _LIBC_COMPAT_H + +/* We have included glibc headers... */ +#if defined(__GLIBC__) + +/* Coordinate with glibc net/if.h header. */ +#if defined(_NET_IF_H) && defined(__USE_MISC) + +/* GLIBC headers included first so don't define anything + * that would already be defined. */ + +#define __UAPI_DEF_IF_IFCONF 0 +#define __UAPI_DEF_IF_IFMAP 0 +#define __UAPI_DEF_IF_IFNAMSIZ 0 +#define __UAPI_DEF_IF_IFREQ 0 +/* Everything up to IFF_DYNAMIC, matches net/if.h until glibc 2.23 */ +#define __UAPI_DEF_IF_NET_DEVICE_FLAGS 0 +/* For the future if glibc adds IFF_LOWER_UP, IFF_DORMANT and IFF_ECHO */ +#ifndef __UAPI_DEF_IF_NET_DEVICE_FLAGS_LOWER_UP_DORMANT_ECHO +#define __UAPI_DEF_IF_NET_DEVICE_FLAGS_LOWER_UP_DORMANT_ECHO 1 +#endif /* __UAPI_DEF_IF_NET_DEVICE_FLAGS_LOWER_UP_DORMANT_ECHO */ + +#else /* _NET_IF_H */ + +/* Linux headers included first, and we must define everything + * we need. The expectation is that glibc will check the + * __UAPI_DEF_* defines and adjust appropriately. */ + +#define __UAPI_DEF_IF_IFCONF 1 +#define __UAPI_DEF_IF_IFMAP 1 +#define __UAPI_DEF_IF_IFNAMSIZ 1 +#define __UAPI_DEF_IF_IFREQ 1 +/* Everything up to IFF_DYNAMIC, matches net/if.h until glibc 2.23 */ +#define __UAPI_DEF_IF_NET_DEVICE_FLAGS 1 +/* For the future if glibc adds IFF_LOWER_UP, IFF_DORMANT and IFF_ECHO */ +#define __UAPI_DEF_IF_NET_DEVICE_FLAGS_LOWER_UP_DORMANT_ECHO 1 + +#endif /* _NET_IF_H */ + +/* Coordinate with glibc netinet/in.h header. */ +#if defined(_NETINET_IN_H) + +/* GLIBC headers included first so don't define anything + * that would already be defined. */ +#define __UAPI_DEF_IN_ADDR 0 +#define __UAPI_DEF_IN_IPPROTO 0 +#define __UAPI_DEF_IN_PKTINFO 0 +#define __UAPI_DEF_IP_MREQ 0 +#define __UAPI_DEF_SOCKADDR_IN 0 +#define __UAPI_DEF_IN_CLASS 0 + +#define __UAPI_DEF_IN6_ADDR 0 +/* The exception is the in6_addr macros which must be defined + * if the glibc code didn't define them. This guard matches + * the guard in glibc/inet/netinet/in.h which defines the + * additional in6_addr macros e.g. s6_addr16, and s6_addr32. */ +#if defined(__USE_MISC) || defined (__USE_GNU) +#define __UAPI_DEF_IN6_ADDR_ALT 0 +#else +#define __UAPI_DEF_IN6_ADDR_ALT 1 +#endif +#define __UAPI_DEF_SOCKADDR_IN6 0 +#define __UAPI_DEF_IPV6_MREQ 0 +#define __UAPI_DEF_IPPROTO_V6 0 +#define __UAPI_DEF_IPV6_OPTIONS 0 +#define __UAPI_DEF_IN6_PKTINFO 0 +#define __UAPI_DEF_IP6_MTUINFO 0 + +#else + +/* Linux headers included first, and we must define everything + * we need. The expectation is that glibc will check the + * __UAPI_DEF_* defines and adjust appropriately. */ +#define __UAPI_DEF_IN_ADDR 1 +#define __UAPI_DEF_IN_IPPROTO 1 +#define __UAPI_DEF_IN_PKTINFO 1 +#define __UAPI_DEF_IP_MREQ 1 +#define __UAPI_DEF_SOCKADDR_IN 1 +#define __UAPI_DEF_IN_CLASS 1 + +#define __UAPI_DEF_IN6_ADDR 1 +/* We unconditionally define the in6_addr macros and glibc must + * coordinate. */ +#define __UAPI_DEF_IN6_ADDR_ALT 1 +#define __UAPI_DEF_SOCKADDR_IN6 1 +#define __UAPI_DEF_IPV6_MREQ 1 +#define __UAPI_DEF_IPPROTO_V6 1 +#define __UAPI_DEF_IPV6_OPTIONS 1 +#define __UAPI_DEF_IN6_PKTINFO 1 +#define __UAPI_DEF_IP6_MTUINFO 1 + +#endif /* _NETINET_IN_H */ + +/* Coordinate with glibc netipx/ipx.h header. */ +#if defined(__NETIPX_IPX_H) + +#define __UAPI_DEF_SOCKADDR_IPX 0 +#define __UAPI_DEF_IPX_ROUTE_DEFINITION 0 +#define __UAPI_DEF_IPX_INTERFACE_DEFINITION 0 +#define __UAPI_DEF_IPX_CONFIG_DATA 0 +#define __UAPI_DEF_IPX_ROUTE_DEF 0 + +#else /* defined(__NETIPX_IPX_H) */ + +#define __UAPI_DEF_SOCKADDR_IPX 1 +#define __UAPI_DEF_IPX_ROUTE_DEFINITION 1 +#define __UAPI_DEF_IPX_INTERFACE_DEFINITION 1 +#define __UAPI_DEF_IPX_CONFIG_DATA 1 +#define __UAPI_DEF_IPX_ROUTE_DEF 1 + +#endif /* defined(__NETIPX_IPX_H) */ + +/* Definitions for xattr.h */ +#if defined(_SYS_XATTR_H) +#define __UAPI_DEF_XATTR 0 +#else +#define __UAPI_DEF_XATTR 1 +#endif + +/* If we did not see any headers from any supported C libraries, + * or we are being included in the kernel, then define everything + * that we need. Check for previous __UAPI_* definitions to give + * unsupported C libraries a way to opt out of any kernel definition. */ +#else /* !defined(__GLIBC__) */ + +/* Definitions for if.h */ +#ifndef __UAPI_DEF_IF_IFCONF +#define __UAPI_DEF_IF_IFCONF 1 +#endif +#ifndef __UAPI_DEF_IF_IFMAP +#define __UAPI_DEF_IF_IFMAP 1 +#endif +#ifndef __UAPI_DEF_IF_IFNAMSIZ +#define __UAPI_DEF_IF_IFNAMSIZ 1 +#endif +#ifndef __UAPI_DEF_IF_IFREQ +#define __UAPI_DEF_IF_IFREQ 1 +#endif +/* Everything up to IFF_DYNAMIC, matches net/if.h until glibc 2.23 */ +#ifndef __UAPI_DEF_IF_NET_DEVICE_FLAGS +#define __UAPI_DEF_IF_NET_DEVICE_FLAGS 1 +#endif +/* For the future if glibc adds IFF_LOWER_UP, IFF_DORMANT and IFF_ECHO */ +#ifndef __UAPI_DEF_IF_NET_DEVICE_FLAGS_LOWER_UP_DORMANT_ECHO +#define __UAPI_DEF_IF_NET_DEVICE_FLAGS_LOWER_UP_DORMANT_ECHO 1 +#endif + +/* Definitions for in.h */ +#ifndef __UAPI_DEF_IN_ADDR +#define __UAPI_DEF_IN_ADDR 1 +#endif +#ifndef __UAPI_DEF_IN_IPPROTO +#define __UAPI_DEF_IN_IPPROTO 1 +#endif +#ifndef __UAPI_DEF_IN_PKTINFO +#define __UAPI_DEF_IN_PKTINFO 1 +#endif +#ifndef __UAPI_DEF_IP_MREQ +#define __UAPI_DEF_IP_MREQ 1 +#endif +#ifndef __UAPI_DEF_SOCKADDR_IN +#define __UAPI_DEF_SOCKADDR_IN 1 +#endif +#ifndef __UAPI_DEF_IN_CLASS +#define __UAPI_DEF_IN_CLASS 1 +#endif + +/* Definitions for in6.h */ +#ifndef __UAPI_DEF_IN6_ADDR +#define __UAPI_DEF_IN6_ADDR 1 +#endif +#ifndef __UAPI_DEF_IN6_ADDR_ALT +#define __UAPI_DEF_IN6_ADDR_ALT 1 +#endif +#ifndef __UAPI_DEF_SOCKADDR_IN6 +#define __UAPI_DEF_SOCKADDR_IN6 1 +#endif +#ifndef __UAPI_DEF_IPV6_MREQ +#define __UAPI_DEF_IPV6_MREQ 1 +#endif +#ifndef __UAPI_DEF_IPPROTO_V6 +#define __UAPI_DEF_IPPROTO_V6 1 +#endif +#ifndef __UAPI_DEF_IPV6_OPTIONS +#define __UAPI_DEF_IPV6_OPTIONS 1 +#endif +#ifndef __UAPI_DEF_IN6_PKTINFO +#define __UAPI_DEF_IN6_PKTINFO 1 +#endif +#ifndef __UAPI_DEF_IP6_MTUINFO +#define __UAPI_DEF_IP6_MTUINFO 1 +#endif + +/* Definitions for ipx.h */ +#ifndef __UAPI_DEF_SOCKADDR_IPX +#define __UAPI_DEF_SOCKADDR_IPX 1 +#endif +#ifndef __UAPI_DEF_IPX_ROUTE_DEFINITION +#define __UAPI_DEF_IPX_ROUTE_DEFINITION 1 +#endif +#ifndef __UAPI_DEF_IPX_INTERFACE_DEFINITION +#define __UAPI_DEF_IPX_INTERFACE_DEFINITION 1 +#endif +#ifndef __UAPI_DEF_IPX_CONFIG_DATA +#define __UAPI_DEF_IPX_CONFIG_DATA 1 +#endif +#ifndef __UAPI_DEF_IPX_ROUTE_DEF +#define __UAPI_DEF_IPX_ROUTE_DEF 1 +#endif + +/* Definitions for xattr.h */ +#ifndef __UAPI_DEF_XATTR +#define __UAPI_DEF_XATTR 1 +#endif + +#endif /* __GLIBC__ */ + +#endif /* _LIBC_COMPAT_H */ diff --git a/include/linux/neighbour.h b/include/linux/neighbour.h new file mode 100644 index 0000000000000000000000000000000000000000..904db614847667eb4f75584113738d232e069dd6 --- /dev/null +++ b/include/linux/neighbour.h @@ -0,0 +1,172 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +#ifndef __LINUX_NEIGHBOUR_H +#define __LINUX_NEIGHBOUR_H + +#include +#include + +struct ndmsg { + __u8 ndm_family; + __u8 ndm_pad1; + __u16 ndm_pad2; + __s32 ndm_ifindex; + __u16 ndm_state; + __u8 ndm_flags; + __u8 ndm_type; +}; + +enum { + NDA_UNSPEC, + NDA_DST, + NDA_LLADDR, + NDA_CACHEINFO, + NDA_PROBES, + NDA_VLAN, + NDA_PORT, + NDA_VNI, + NDA_IFINDEX, + NDA_MASTER, + NDA_LINK_NETNSID, + NDA_SRC_VNI, + __NDA_MAX +}; + +#define NDA_MAX (__NDA_MAX - 1) + +/* + * Neighbor Cache Entry Flags + */ + +#define NTF_USE 0x01 +#define NTF_SELF 0x02 +#define NTF_MASTER 0x04 +#define NTF_PROXY 0x08 /* == ATF_PUBL */ +#define NTF_EXT_LEARNED 0x10 +#define NTF_OFFLOADED 0x20 +#define NTF_ROUTER 0x80 + +/* + * Neighbor Cache Entry States. + */ + +#define NUD_INCOMPLETE 0x01 +#define NUD_REACHABLE 0x02 +#define NUD_STALE 0x04 +#define NUD_DELAY 0x08 +#define NUD_PROBE 0x10 +#define NUD_FAILED 0x20 + +/* Dummy states */ +#define NUD_NOARP 0x40 +#define NUD_PERMANENT 0x80 +#define NUD_NONE 0x00 + +/* NUD_NOARP & NUD_PERMANENT are pseudostates, they never change + and make no address resolution or NUD. + NUD_PERMANENT also cannot be deleted by garbage collectors. + */ + +struct nda_cacheinfo { + __u32 ndm_confirmed; + __u32 ndm_used; + __u32 ndm_updated; + __u32 ndm_refcnt; +}; + +/***************************************************************** + * Neighbour tables specific messages. + * + * To retrieve the neighbour tables send RTM_GETNEIGHTBL with the + * NLM_F_DUMP flag set. Every neighbour table configuration is + * spread over multiple messages to avoid running into message + * size limits on systems with many interfaces. The first message + * in the sequence transports all not device specific data such as + * statistics, configuration, and the default parameter set. + * This message is followed by 0..n messages carrying device + * specific parameter sets. + * Although the ordering should be sufficient, NDTA_NAME can be + * used to identify sequences. The initial message can be identified + * by checking for NDTA_CONFIG. The device specific messages do + * not contain this TLV but have NDTPA_IFINDEX set to the + * corresponding interface index. + * + * To change neighbour table attributes, send RTM_SETNEIGHTBL + * with NDTA_NAME set. Changeable attribute include NDTA_THRESH[1-3], + * NDTA_GC_INTERVAL, and all TLVs in NDTA_PARMS unless marked + * otherwise. Device specific parameter sets can be changed by + * setting NDTPA_IFINDEX to the interface index of the corresponding + * device. + ****/ + +struct ndt_stats { + __u64 ndts_allocs; + __u64 ndts_destroys; + __u64 ndts_hash_grows; + __u64 ndts_res_failed; + __u64 ndts_lookups; + __u64 ndts_hits; + __u64 ndts_rcv_probes_mcast; + __u64 ndts_rcv_probes_ucast; + __u64 ndts_periodic_gc_runs; + __u64 ndts_forced_gc_runs; + __u64 ndts_table_fulls; +}; + +enum { + NDTPA_UNSPEC, + NDTPA_IFINDEX, /* u32, unchangeable */ + NDTPA_REFCNT, /* u32, read-only */ + NDTPA_REACHABLE_TIME, /* u64, read-only, msecs */ + NDTPA_BASE_REACHABLE_TIME, /* u64, msecs */ + NDTPA_RETRANS_TIME, /* u64, msecs */ + NDTPA_GC_STALETIME, /* u64, msecs */ + NDTPA_DELAY_PROBE_TIME, /* u64, msecs */ + NDTPA_QUEUE_LEN, /* u32 */ + NDTPA_APP_PROBES, /* u32 */ + NDTPA_UCAST_PROBES, /* u32 */ + NDTPA_MCAST_PROBES, /* u32 */ + NDTPA_ANYCAST_DELAY, /* u64, msecs */ + NDTPA_PROXY_DELAY, /* u64, msecs */ + NDTPA_PROXY_QLEN, /* u32 */ + NDTPA_LOCKTIME, /* u64, msecs */ + NDTPA_QUEUE_LENBYTES, /* u32 */ + NDTPA_MCAST_REPROBES, /* u32 */ + NDTPA_PAD, + __NDTPA_MAX +}; +#define NDTPA_MAX (__NDTPA_MAX - 1) + +struct ndtmsg { + __u8 ndtm_family; + __u8 ndtm_pad1; + __u16 ndtm_pad2; +}; + +struct ndt_config { + __u16 ndtc_key_len; + __u16 ndtc_entry_size; + __u32 ndtc_entries; + __u32 ndtc_last_flush; /* delta to now in msecs */ + __u32 ndtc_last_rand; /* delta to now in msecs */ + __u32 ndtc_hash_rnd; + __u32 ndtc_hash_mask; + __u32 ndtc_hash_chain_gc; + __u32 ndtc_proxy_qlen; +}; + +enum { + NDTA_UNSPEC, + NDTA_NAME, /* char *, unchangeable */ + NDTA_THRESH1, /* u32 */ + NDTA_THRESH2, /* u32 */ + NDTA_THRESH3, /* u32 */ + NDTA_CONFIG, /* struct ndt_config, read-only */ + NDTA_PARMS, /* nested TLV NDTPA_* */ + NDTA_STATS, /* struct ndt_stats, read-only */ + NDTA_GC_INTERVAL, /* u64, msecs */ + NDTA_PAD, + __NDTA_MAX +}; +#define NDTA_MAX (__NDTA_MAX - 1) + +#endif diff --git a/include/linux/netlink.h b/include/linux/netlink.h new file mode 100644 index 0000000000000000000000000000000000000000..8209dbe7ed9611313968a800236bb5434c1c4a30 --- /dev/null +++ b/include/linux/netlink.h @@ -0,0 +1,247 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +#ifndef __LINUX_NETLINK_H +#define __LINUX_NETLINK_H + +#include +#include /* for __kernel_sa_family_t */ +#include + +#define NETLINK_ROUTE 0 /* Routing/device hook */ +#define NETLINK_UNUSED 1 /* Unused number */ +#define NETLINK_USERSOCK 2 /* Reserved for user mode socket protocols */ +#define NETLINK_FIREWALL 3 /* Unused number, formerly ip_queue */ +#define NETLINK_SOCK_DIAG 4 /* socket monitoring */ +#define NETLINK_NFLOG 5 /* netfilter/iptables ULOG */ +#define NETLINK_XFRM 6 /* ipsec */ +#define NETLINK_SELINUX 7 /* SELinux event notifications */ +#define NETLINK_ISCSI 8 /* Open-iSCSI */ +#define NETLINK_AUDIT 9 /* auditing */ +#define NETLINK_FIB_LOOKUP 10 +#define NETLINK_CONNECTOR 11 +#define NETLINK_NETFILTER 12 /* netfilter subsystem */ +#define NETLINK_IP6_FW 13 +#define NETLINK_DNRTMSG 14 /* DECnet routing messages */ +#define NETLINK_KOBJECT_UEVENT 15 /* Kernel messages to userspace */ +#define NETLINK_GENERIC 16 +/* leave room for NETLINK_DM (DM Events) */ +#define NETLINK_SCSITRANSPORT 18 /* SCSI Transports */ +#define NETLINK_ECRYPTFS 19 +#define NETLINK_RDMA 20 +#define NETLINK_CRYPTO 21 /* Crypto layer */ +#define NETLINK_SMC 22 /* SMC monitoring */ + +#define NETLINK_INET_DIAG NETLINK_SOCK_DIAG + +#define MAX_LINKS 32 + +struct sockaddr_nl { + unsigned short nl_family; /* AF_NETLINK */ + unsigned short nl_pad; /* zero */ + __u32 nl_pid; /* port ID */ + __u32 nl_groups; /* multicast groups mask */ +}; + +struct nlmsghdr { + __u32 nlmsg_len; /* Length of message including header */ + __u16 nlmsg_type; /* Message content */ + __u16 nlmsg_flags; /* Additional flags */ + __u32 nlmsg_seq; /* Sequence number */ + __u32 nlmsg_pid; /* Sending process port ID */ +}; + +/* Flags values */ + +#define NLM_F_REQUEST 0x01 /* It is request message. */ +#define NLM_F_MULTI 0x02 /* Multipart message, terminated by NLMSG_DONE */ +#define NLM_F_ACK 0x04 /* Reply with ack, with zero or error code */ +#define NLM_F_ECHO 0x08 /* Echo this request */ +#define NLM_F_DUMP_INTR 0x10 /* Dump was inconsistent due to sequence change */ +#define NLM_F_DUMP_FILTERED 0x20 /* Dump was filtered as requested */ + +/* Modifiers to GET request */ +#define NLM_F_ROOT 0x100 /* specify tree root */ +#define NLM_F_MATCH 0x200 /* return all matching */ +#define NLM_F_ATOMIC 0x400 /* atomic GET */ +#define NLM_F_DUMP (NLM_F_ROOT|NLM_F_MATCH) + +/* Modifiers to NEW request */ +#define NLM_F_REPLACE 0x100 /* Override existing */ +#define NLM_F_EXCL 0x200 /* Do not touch, if it exists */ +#define NLM_F_CREATE 0x400 /* Create, if it does not exist */ +#define NLM_F_APPEND 0x800 /* Add to end of list */ + +/* Modifiers to DELETE request */ +#define NLM_F_NONREC 0x100 /* Do not delete recursively */ + +/* Flags for ACK message */ +#define NLM_F_CAPPED 0x100 /* request was capped */ +#define NLM_F_ACK_TLVS 0x200 /* extended ACK TVLs were included */ + +/* + 4.4BSD ADD NLM_F_CREATE|NLM_F_EXCL + 4.4BSD CHANGE NLM_F_REPLACE + + True CHANGE NLM_F_CREATE|NLM_F_REPLACE + Append NLM_F_CREATE + Check NLM_F_EXCL + */ + +#define NLMSG_ALIGNTO 4U +#define NLMSG_ALIGN(len) ( ((len)+NLMSG_ALIGNTO-1) & ~(NLMSG_ALIGNTO-1) ) +#define NLMSG_HDRLEN ((int) NLMSG_ALIGN(sizeof(struct nlmsghdr))) +#define NLMSG_LENGTH(len) ((len) + NLMSG_HDRLEN) +#define NLMSG_SPACE(len) NLMSG_ALIGN(NLMSG_LENGTH(len)) +#define NLMSG_DATA(nlh) ((void*)(((char*)nlh) + NLMSG_LENGTH(0))) +#define NLMSG_NEXT(nlh,len) ((len) -= NLMSG_ALIGN((nlh)->nlmsg_len), \ + (struct nlmsghdr*)(((char*)(nlh)) + NLMSG_ALIGN((nlh)->nlmsg_len))) +#define NLMSG_OK(nlh,len) ((len) >= (int)sizeof(struct nlmsghdr) && \ + (nlh)->nlmsg_len >= sizeof(struct nlmsghdr) && \ + (nlh)->nlmsg_len <= (len)) +#define NLMSG_PAYLOAD(nlh,len) ((nlh)->nlmsg_len - NLMSG_SPACE((len))) + +#define NLMSG_NOOP 0x1 /* Nothing. */ +#define NLMSG_ERROR 0x2 /* Error */ +#define NLMSG_DONE 0x3 /* End of a dump */ +#define NLMSG_OVERRUN 0x4 /* Data lost */ + +#define NLMSG_MIN_TYPE 0x10 /* < 0x10: reserved control messages */ + +struct nlmsgerr { + int error; + struct nlmsghdr msg; + /* + * followed by the message contents unless NETLINK_CAP_ACK was set + * or the ACK indicates success (error == 0) + * message length is aligned with NLMSG_ALIGN() + */ + /* + * followed by TLVs defined in enum nlmsgerr_attrs + * if NETLINK_EXT_ACK was set + */ +}; + +/** + * enum nlmsgerr_attrs - nlmsgerr attributes + * @NLMSGERR_ATTR_UNUSED: unused + * @NLMSGERR_ATTR_MSG: error message string (string) + * @NLMSGERR_ATTR_OFFS: offset of the invalid attribute in the original + * message, counting from the beginning of the header (u32) + * @NLMSGERR_ATTR_COOKIE: arbitrary subsystem specific cookie to + * be used - in the success case - to identify a created + * object or operation or similar (binary) + * @__NLMSGERR_ATTR_MAX: number of attributes + * @NLMSGERR_ATTR_MAX: highest attribute number + */ +enum nlmsgerr_attrs { + NLMSGERR_ATTR_UNUSED, + NLMSGERR_ATTR_MSG, + NLMSGERR_ATTR_OFFS, + NLMSGERR_ATTR_COOKIE, + + __NLMSGERR_ATTR_MAX, + NLMSGERR_ATTR_MAX = __NLMSGERR_ATTR_MAX - 1 +}; + +#define NETLINK_ADD_MEMBERSHIP 1 +#define NETLINK_DROP_MEMBERSHIP 2 +#define NETLINK_PKTINFO 3 +#define NETLINK_BROADCAST_ERROR 4 +#define NETLINK_NO_ENOBUFS 5 +#define NETLINK_RX_RING 6 +#define NETLINK_TX_RING 7 +#define NETLINK_LISTEN_ALL_NSID 8 +#define NETLINK_LIST_MEMBERSHIPS 9 +#define NETLINK_CAP_ACK 10 +#define NETLINK_EXT_ACK 11 + +struct nl_pktinfo { + __u32 group; +}; + +struct nl_mmap_req { + unsigned int nm_block_size; + unsigned int nm_block_nr; + unsigned int nm_frame_size; + unsigned int nm_frame_nr; +}; + +struct nl_mmap_hdr { + unsigned int nm_status; + unsigned int nm_len; + __u32 nm_group; + /* credentials */ + __u32 nm_pid; + __u32 nm_uid; + __u32 nm_gid; +}; + +enum nl_mmap_status { + NL_MMAP_STATUS_UNUSED, + NL_MMAP_STATUS_RESERVED, + NL_MMAP_STATUS_VALID, + NL_MMAP_STATUS_COPY, + NL_MMAP_STATUS_SKIP, +}; + +#define NL_MMAP_MSG_ALIGNMENT NLMSG_ALIGNTO +#define NL_MMAP_MSG_ALIGN(sz) __ALIGN_KERNEL(sz, NL_MMAP_MSG_ALIGNMENT) +#define NL_MMAP_HDRLEN NL_MMAP_MSG_ALIGN(sizeof(struct nl_mmap_hdr)) + +#define NET_MAJOR 36 /* Major 36 is reserved for networking */ + +enum { + NETLINK_UNCONNECTED = 0, + NETLINK_CONNECTED, +}; + +/* + * <------- NLA_HDRLEN ------> <-- NLA_ALIGN(payload)--> + * +---------------------+- - -+- - - - - - - - - -+- - -+ + * | Header | Pad | Payload | Pad | + * | (struct nlattr) | ing | | ing | + * +---------------------+- - -+- - - - - - - - - -+- - -+ + * <-------------- nlattr->nla_len --------------> + */ + +struct nlattr { + __u16 nla_len; + __u16 nla_type; +}; + +/* + * nla_type (16 bits) + * +---+---+-------------------------------+ + * | N | O | Attribute Type | + * +---+---+-------------------------------+ + * N := Carries nested attributes + * O := Payload stored in network byte order + * + * Note: The N and O flag are mutually exclusive. + */ +#define NLA_F_NESTED (1 << 15) +#define NLA_F_NET_BYTEORDER (1 << 14) +#define NLA_TYPE_MASK ~(NLA_F_NESTED | NLA_F_NET_BYTEORDER) + +#define NLA_ALIGNTO 4 +#define NLA_ALIGN(len) (((len) + NLA_ALIGNTO - 1) & ~(NLA_ALIGNTO - 1)) +#define NLA_HDRLEN ((int) NLA_ALIGN(sizeof(struct nlattr))) + +/* Generic 32 bitflags attribute content sent to the kernel. + * + * The value is a bitmap that defines the values being set + * The selector is a bitmask that defines which value is legit + * + * Examples: + * value = 0x0, and selector = 0x1 + * implies we are selecting bit 1 and we want to set its value to 0. + * + * value = 0x2, and selector = 0x2 + * implies we are selecting bit 2 and we want to set its value to 1. + * + */ +struct nla_bitfield32 { + __u32 value; + __u32 selector; +}; + +#endif /* __LINUX_NETLINK_H */ diff --git a/include/linux/rtnetlink.h b/include/linux/rtnetlink.h new file mode 100644 index 0000000000000000000000000000000000000000..643e475408e4f9338392d8cf0f2913c73df2b0e0 --- /dev/null +++ b/include/linux/rtnetlink.h @@ -0,0 +1,743 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +#ifndef __LINUX_RTNETLINK_H +#define __LINUX_RTNETLINK_H + +#include +#include +#include +#include +#include + +/* rtnetlink families. Values up to 127 are reserved for real address + * families, values above 128 may be used arbitrarily. + */ +#define RTNL_FAMILY_IPMR 128 +#define RTNL_FAMILY_IP6MR 129 +#define RTNL_FAMILY_MAX 129 + +/**** + * Routing/neighbour discovery messages. + ****/ + +/* Types of messages */ + +enum { + RTM_BASE = 16, +#define RTM_BASE RTM_BASE + + RTM_NEWLINK = 16, +#define RTM_NEWLINK RTM_NEWLINK + RTM_DELLINK, +#define RTM_DELLINK RTM_DELLINK + RTM_GETLINK, +#define RTM_GETLINK RTM_GETLINK + RTM_SETLINK, +#define RTM_SETLINK RTM_SETLINK + + RTM_NEWADDR = 20, +#define RTM_NEWADDR RTM_NEWADDR + RTM_DELADDR, +#define RTM_DELADDR RTM_DELADDR + RTM_GETADDR, +#define RTM_GETADDR RTM_GETADDR + + RTM_NEWROUTE = 24, +#define RTM_NEWROUTE RTM_NEWROUTE + RTM_DELROUTE, +#define RTM_DELROUTE RTM_DELROUTE + RTM_GETROUTE, +#define RTM_GETROUTE RTM_GETROUTE + + RTM_NEWNEIGH = 28, +#define RTM_NEWNEIGH RTM_NEWNEIGH + RTM_DELNEIGH, +#define RTM_DELNEIGH RTM_DELNEIGH + RTM_GETNEIGH, +#define RTM_GETNEIGH RTM_GETNEIGH + + RTM_NEWRULE = 32, +#define RTM_NEWRULE RTM_NEWRULE + RTM_DELRULE, +#define RTM_DELRULE RTM_DELRULE + RTM_GETRULE, +#define RTM_GETRULE RTM_GETRULE + + RTM_NEWQDISC = 36, +#define RTM_NEWQDISC RTM_NEWQDISC + RTM_DELQDISC, +#define RTM_DELQDISC RTM_DELQDISC + RTM_GETQDISC, +#define RTM_GETQDISC RTM_GETQDISC + + RTM_NEWTCLASS = 40, +#define RTM_NEWTCLASS RTM_NEWTCLASS + RTM_DELTCLASS, +#define RTM_DELTCLASS RTM_DELTCLASS + RTM_GETTCLASS, +#define RTM_GETTCLASS RTM_GETTCLASS + + RTM_NEWTFILTER = 44, +#define RTM_NEWTFILTER RTM_NEWTFILTER + RTM_DELTFILTER, +#define RTM_DELTFILTER RTM_DELTFILTER + RTM_GETTFILTER, +#define RTM_GETTFILTER RTM_GETTFILTER + + RTM_NEWACTION = 48, +#define RTM_NEWACTION RTM_NEWACTION + RTM_DELACTION, +#define RTM_DELACTION RTM_DELACTION + RTM_GETACTION, +#define RTM_GETACTION RTM_GETACTION + + RTM_NEWPREFIX = 52, +#define RTM_NEWPREFIX RTM_NEWPREFIX + + RTM_GETMULTICAST = 58, +#define RTM_GETMULTICAST RTM_GETMULTICAST + + RTM_GETANYCAST = 62, +#define RTM_GETANYCAST RTM_GETANYCAST + + RTM_NEWNEIGHTBL = 64, +#define RTM_NEWNEIGHTBL RTM_NEWNEIGHTBL + RTM_GETNEIGHTBL = 66, +#define RTM_GETNEIGHTBL RTM_GETNEIGHTBL + RTM_SETNEIGHTBL, +#define RTM_SETNEIGHTBL RTM_SETNEIGHTBL + + RTM_NEWNDUSEROPT = 68, +#define RTM_NEWNDUSEROPT RTM_NEWNDUSEROPT + + RTM_NEWADDRLABEL = 72, +#define RTM_NEWADDRLABEL RTM_NEWADDRLABEL + RTM_DELADDRLABEL, +#define RTM_DELADDRLABEL RTM_DELADDRLABEL + RTM_GETADDRLABEL, +#define RTM_GETADDRLABEL RTM_GETADDRLABEL + + RTM_GETDCB = 78, +#define RTM_GETDCB RTM_GETDCB + RTM_SETDCB, +#define RTM_SETDCB RTM_SETDCB + + RTM_NEWNETCONF = 80, +#define RTM_NEWNETCONF RTM_NEWNETCONF + RTM_DELNETCONF, +#define RTM_DELNETCONF RTM_DELNETCONF + RTM_GETNETCONF = 82, +#define RTM_GETNETCONF RTM_GETNETCONF + + RTM_NEWMDB = 84, +#define RTM_NEWMDB RTM_NEWMDB + RTM_DELMDB = 85, +#define RTM_DELMDB RTM_DELMDB + RTM_GETMDB = 86, +#define RTM_GETMDB RTM_GETMDB + + RTM_NEWNSID = 88, +#define RTM_NEWNSID RTM_NEWNSID + RTM_DELNSID = 89, +#define RTM_DELNSID RTM_DELNSID + RTM_GETNSID = 90, +#define RTM_GETNSID RTM_GETNSID + + RTM_NEWSTATS = 92, +#define RTM_NEWSTATS RTM_NEWSTATS + RTM_GETSTATS = 94, +#define RTM_GETSTATS RTM_GETSTATS + + RTM_NEWCACHEREPORT = 96, +#define RTM_NEWCACHEREPORT RTM_NEWCACHEREPORT + + RTM_NEWCHAIN = 100, +#define RTM_NEWCHAIN RTM_NEWCHAIN + RTM_DELCHAIN, +#define RTM_DELCHAIN RTM_DELCHAIN + RTM_GETCHAIN, +#define RTM_GETCHAIN RTM_GETCHAIN + + __RTM_MAX, +#define RTM_MAX (((__RTM_MAX + 3) & ~3) - 1) +}; + +#define RTM_NR_MSGTYPES (RTM_MAX + 1 - RTM_BASE) +#define RTM_NR_FAMILIES (RTM_NR_MSGTYPES >> 2) +#define RTM_FAM(cmd) (((cmd) - RTM_BASE) >> 2) + +/* + Generic structure for encapsulation of optional route information. + It is reminiscent of sockaddr, but with sa_family replaced + with attribute type. + */ + +struct rtattr { + unsigned short rta_len; + unsigned short rta_type; +}; + +/* Macros to handle rtattributes */ + +#define RTA_ALIGNTO 4U +#define RTA_ALIGN(len) ( ((len)+RTA_ALIGNTO-1) & ~(RTA_ALIGNTO-1) ) +#define RTA_OK(rta,len) ((len) >= (int)sizeof(struct rtattr) && \ + (rta)->rta_len >= sizeof(struct rtattr) && \ + (rta)->rta_len <= (len)) +#define RTA_NEXT(rta,attrlen) ((attrlen) -= RTA_ALIGN((rta)->rta_len), \ + (struct rtattr*)(((char*)(rta)) + RTA_ALIGN((rta)->rta_len))) +#define RTA_LENGTH(len) (RTA_ALIGN(sizeof(struct rtattr)) + (len)) +#define RTA_SPACE(len) RTA_ALIGN(RTA_LENGTH(len)) +#define RTA_DATA(rta) ((void*)(((char*)(rta)) + RTA_LENGTH(0))) +#define RTA_PAYLOAD(rta) ((int)((rta)->rta_len) - RTA_LENGTH(0)) + + + + +/****************************************************************************** + * Definitions used in routing table administration. + ****/ + +struct rtmsg { + unsigned char rtm_family; + unsigned char rtm_dst_len; + unsigned char rtm_src_len; + unsigned char rtm_tos; + + unsigned char rtm_table; /* Routing table id */ + unsigned char rtm_protocol; /* Routing protocol; see below */ + unsigned char rtm_scope; /* See below */ + unsigned char rtm_type; /* See below */ + + unsigned rtm_flags; +}; + +/* rtm_type */ + +enum { + RTN_UNSPEC, + RTN_UNICAST, /* Gateway or direct route */ + RTN_LOCAL, /* Accept locally */ + RTN_BROADCAST, /* Accept locally as broadcast, + send as broadcast */ + RTN_ANYCAST, /* Accept locally as broadcast, + but send as unicast */ + RTN_MULTICAST, /* Multicast route */ + RTN_BLACKHOLE, /* Drop */ + RTN_UNREACHABLE, /* Destination is unreachable */ + RTN_PROHIBIT, /* Administratively prohibited */ + RTN_THROW, /* Not in this table */ + RTN_NAT, /* Translate this address */ + RTN_XRESOLVE, /* Use external resolver */ + __RTN_MAX +}; + +#define RTN_MAX (__RTN_MAX - 1) + + +/* rtm_protocol */ + +#define RTPROT_UNSPEC 0 +#define RTPROT_REDIRECT 1 /* Route installed by ICMP redirects; + not used by current IPv4 */ +#define RTPROT_KERNEL 2 /* Route installed by kernel */ +#define RTPROT_BOOT 3 /* Route installed during boot */ +#define RTPROT_STATIC 4 /* Route installed by administrator */ + +/* Values of protocol >= RTPROT_STATIC are not interpreted by kernel; + they are just passed from user and back as is. + It will be used by hypothetical multiple routing daemons. + Note that protocol values should be standardized in order to + avoid conflicts. + */ + +#define RTPROT_GATED 8 /* Apparently, GateD */ +#define RTPROT_RA 9 /* RDISC/ND router advertisements */ +#define RTPROT_MRT 10 /* Merit MRT */ +#define RTPROT_ZEBRA 11 /* Zebra */ +#define RTPROT_BIRD 12 /* BIRD */ +#define RTPROT_DNROUTED 13 /* DECnet routing daemon */ +#define RTPROT_XORP 14 /* XORP */ +#define RTPROT_NTK 15 /* Netsukuku */ +#define RTPROT_DHCP 16 /* DHCP client */ +#define RTPROT_MROUTED 17 /* Multicast daemon */ +#define RTPROT_BABEL 42 /* Babel daemon */ +#define RTPROT_BGP 186 /* BGP Routes */ +#define RTPROT_ISIS 187 /* ISIS Routes */ +#define RTPROT_OSPF 188 /* OSPF Routes */ +#define RTPROT_RIP 189 /* RIP Routes */ +#define RTPROT_EIGRP 192 /* EIGRP Routes */ + +/* rtm_scope + + Really it is not scope, but sort of distance to the destination. + NOWHERE are reserved for not existing destinations, HOST is our + local addresses, LINK are destinations, located on directly attached + link and UNIVERSE is everywhere in the Universe. + + Intermediate values are also possible f.e. interior routes + could be assigned a value between UNIVERSE and LINK. +*/ + +enum rt_scope_t { + RT_SCOPE_UNIVERSE=0, +/* User defined values */ + RT_SCOPE_SITE=200, + RT_SCOPE_LINK=253, + RT_SCOPE_HOST=254, + RT_SCOPE_NOWHERE=255 +}; + +/* rtm_flags */ + +#define RTM_F_NOTIFY 0x100 /* Notify user of route change */ +#define RTM_F_CLONED 0x200 /* This route is cloned */ +#define RTM_F_EQUALIZE 0x400 /* Multipath equalizer: NI */ +#define RTM_F_PREFIX 0x800 /* Prefix addresses */ +#define RTM_F_LOOKUP_TABLE 0x1000 /* set rtm_table to FIB lookup result */ +#define RTM_F_FIB_MATCH 0x2000 /* return full fib lookup match */ + +/* Reserved table identifiers */ + +enum rt_class_t { + RT_TABLE_UNSPEC=0, +/* User defined values */ + RT_TABLE_COMPAT=252, + RT_TABLE_DEFAULT=253, + RT_TABLE_MAIN=254, + RT_TABLE_LOCAL=255, + RT_TABLE_MAX=0xFFFFFFFF +}; + + +/* Routing message attributes */ + +enum rtattr_type_t { + RTA_UNSPEC, + RTA_DST, + RTA_SRC, + RTA_IIF, + RTA_OIF, + RTA_GATEWAY, + RTA_PRIORITY, + RTA_PREFSRC, + RTA_METRICS, + RTA_MULTIPATH, + RTA_PROTOINFO, /* no longer used */ + RTA_FLOW, + RTA_CACHEINFO, + RTA_SESSION, /* no longer used */ + RTA_MP_ALGO, /* no longer used */ + RTA_TABLE, + RTA_MARK, + RTA_MFC_STATS, + RTA_VIA, + RTA_NEWDST, + RTA_PREF, + RTA_ENCAP_TYPE, + RTA_ENCAP, + RTA_EXPIRES, + RTA_PAD, + RTA_UID, + RTA_TTL_PROPAGATE, + RTA_IP_PROTO, + RTA_SPORT, + RTA_DPORT, + __RTA_MAX +}; + +#define RTA_MAX (__RTA_MAX - 1) + +#define RTM_RTA(r) ((struct rtattr*)(((char*)(r)) + NLMSG_ALIGN(sizeof(struct rtmsg)))) +#define RTM_PAYLOAD(n) NLMSG_PAYLOAD(n,sizeof(struct rtmsg)) + +/* RTM_MULTIPATH --- array of struct rtnexthop. + * + * "struct rtnexthop" describes all necessary nexthop information, + * i.e. parameters of path to a destination via this nexthop. + * + * At the moment it is impossible to set different prefsrc, mtu, window + * and rtt for different paths from multipath. + */ + +struct rtnexthop { + unsigned short rtnh_len; + unsigned char rtnh_flags; + unsigned char rtnh_hops; + int rtnh_ifindex; +}; + +/* rtnh_flags */ + +#define RTNH_F_DEAD 1 /* Nexthop is dead (used by multipath) */ +#define RTNH_F_PERVASIVE 2 /* Do recursive gateway lookup */ +#define RTNH_F_ONLINK 4 /* Gateway is forced on link */ +#define RTNH_F_OFFLOAD 8 /* offloaded route */ +#define RTNH_F_LINKDOWN 16 /* carrier-down on nexthop */ +#define RTNH_F_UNRESOLVED 32 /* The entry is unresolved (ipmr) */ + +#define RTNH_COMPARE_MASK (RTNH_F_DEAD | RTNH_F_LINKDOWN | RTNH_F_OFFLOAD) + +/* Macros to handle hexthops */ + +#define RTNH_ALIGNTO 4 +#define RTNH_ALIGN(len) ( ((len)+RTNH_ALIGNTO-1) & ~(RTNH_ALIGNTO-1) ) +#define RTNH_OK(rtnh,len) ((rtnh)->rtnh_len >= sizeof(struct rtnexthop) && \ + ((int)(rtnh)->rtnh_len) <= (len)) +#define RTNH_NEXT(rtnh) ((struct rtnexthop*)(((char*)(rtnh)) + RTNH_ALIGN((rtnh)->rtnh_len))) +#define RTNH_LENGTH(len) (RTNH_ALIGN(sizeof(struct rtnexthop)) + (len)) +#define RTNH_SPACE(len) RTNH_ALIGN(RTNH_LENGTH(len)) +#define RTNH_DATA(rtnh) ((struct rtattr*)(((char*)(rtnh)) + RTNH_LENGTH(0))) + +/* RTM_CACHEINFO */ + +struct rta_cacheinfo { + __u32 rta_clntref; + __u32 rta_lastuse; + __s32 rta_expires; + __u32 rta_error; + __u32 rta_used; + +#define RTNETLINK_HAVE_PEERINFO 1 + __u32 rta_id; + __u32 rta_ts; + __u32 rta_tsage; +}; + +/* RTM_METRICS --- array of struct rtattr with types of RTAX_* */ + +enum { + RTAX_UNSPEC, +#define RTAX_UNSPEC RTAX_UNSPEC + RTAX_LOCK, +#define RTAX_LOCK RTAX_LOCK + RTAX_MTU, +#define RTAX_MTU RTAX_MTU + RTAX_WINDOW, +#define RTAX_WINDOW RTAX_WINDOW + RTAX_RTT, +#define RTAX_RTT RTAX_RTT + RTAX_RTTVAR, +#define RTAX_RTTVAR RTAX_RTTVAR + RTAX_SSTHRESH, +#define RTAX_SSTHRESH RTAX_SSTHRESH + RTAX_CWND, +#define RTAX_CWND RTAX_CWND + RTAX_ADVMSS, +#define RTAX_ADVMSS RTAX_ADVMSS + RTAX_REORDERING, +#define RTAX_REORDERING RTAX_REORDERING + RTAX_HOPLIMIT, +#define RTAX_HOPLIMIT RTAX_HOPLIMIT + RTAX_INITCWND, +#define RTAX_INITCWND RTAX_INITCWND + RTAX_FEATURES, +#define RTAX_FEATURES RTAX_FEATURES + RTAX_RTO_MIN, +#define RTAX_RTO_MIN RTAX_RTO_MIN + RTAX_INITRWND, +#define RTAX_INITRWND RTAX_INITRWND + RTAX_QUICKACK, +#define RTAX_QUICKACK RTAX_QUICKACK + RTAX_CC_ALGO, +#define RTAX_CC_ALGO RTAX_CC_ALGO + RTAX_FASTOPEN_NO_COOKIE, +#define RTAX_FASTOPEN_NO_COOKIE RTAX_FASTOPEN_NO_COOKIE + __RTAX_MAX +}; + +#define RTAX_MAX (__RTAX_MAX - 1) + +#define RTAX_FEATURE_ECN (1 << 0) +#define RTAX_FEATURE_SACK (1 << 1) +#define RTAX_FEATURE_TIMESTAMP (1 << 2) +#define RTAX_FEATURE_ALLFRAG (1 << 3) + +#define RTAX_FEATURE_MASK (RTAX_FEATURE_ECN | RTAX_FEATURE_SACK | \ + RTAX_FEATURE_TIMESTAMP | RTAX_FEATURE_ALLFRAG) + +struct rta_session { + __u8 proto; + __u8 pad1; + __u16 pad2; + + union { + struct { + __u16 sport; + __u16 dport; + } ports; + + struct { + __u8 type; + __u8 code; + __u16 ident; + } icmpt; + + __u32 spi; + } u; +}; + +struct rta_mfc_stats { + __u64 mfcs_packets; + __u64 mfcs_bytes; + __u64 mfcs_wrong_if; +}; + +/**** + * General form of address family dependent message. + ****/ + +struct rtgenmsg { + unsigned char rtgen_family; +}; + +/***************************************************************** + * Link layer specific messages. + ****/ + +/* struct ifinfomsg + * passes link level specific information, not dependent + * on network protocol. + */ + +struct ifinfomsg { + unsigned char ifi_family; + unsigned char __ifi_pad; + unsigned short ifi_type; /* ARPHRD_* */ + int ifi_index; /* Link index */ + unsigned ifi_flags; /* IFF_* flags */ + unsigned ifi_change; /* IFF_* change mask */ +}; + +/******************************************************************** + * prefix information + ****/ + +struct prefixmsg { + unsigned char prefix_family; + unsigned char prefix_pad1; + unsigned short prefix_pad2; + int prefix_ifindex; + unsigned char prefix_type; + unsigned char prefix_len; + unsigned char prefix_flags; + unsigned char prefix_pad3; +}; + +enum +{ + PREFIX_UNSPEC, + PREFIX_ADDRESS, + PREFIX_CACHEINFO, + __PREFIX_MAX +}; + +#define PREFIX_MAX (__PREFIX_MAX - 1) + +struct prefix_cacheinfo { + __u32 preferred_time; + __u32 valid_time; +}; + + +/***************************************************************** + * Traffic control messages. + ****/ + +struct tcmsg { + unsigned char tcm_family; + unsigned char tcm__pad1; + unsigned short tcm__pad2; + int tcm_ifindex; + __u32 tcm_handle; + __u32 tcm_parent; +/* tcm_block_index is used instead of tcm_parent + * in case tcm_ifindex == TCM_IFINDEX_MAGIC_BLOCK + */ +#define tcm_block_index tcm_parent + __u32 tcm_info; +}; + +/* For manipulation of filters in shared block, tcm_ifindex is set to + * TCM_IFINDEX_MAGIC_BLOCK, and tcm_parent is aliased to tcm_block_index + * which is the block index. + */ +#define TCM_IFINDEX_MAGIC_BLOCK (0xFFFFFFFFU) + +enum { + TCA_UNSPEC, + TCA_KIND, + TCA_OPTIONS, + TCA_STATS, + TCA_XSTATS, + TCA_RATE, + TCA_FCNT, + TCA_STATS2, + TCA_STAB, + TCA_PAD, + TCA_DUMP_INVISIBLE, + TCA_CHAIN, + TCA_HW_OFFLOAD, + TCA_INGRESS_BLOCK, + TCA_EGRESS_BLOCK, + __TCA_MAX +}; + +#define TCA_MAX (__TCA_MAX - 1) + +#define TCA_RTA(r) ((struct rtattr*)(((char*)(r)) + NLMSG_ALIGN(sizeof(struct tcmsg)))) +#define TCA_PAYLOAD(n) NLMSG_PAYLOAD(n,sizeof(struct tcmsg)) + +/******************************************************************** + * Neighbor Discovery userland options + ****/ + +struct nduseroptmsg { + unsigned char nduseropt_family; + unsigned char nduseropt_pad1; + unsigned short nduseropt_opts_len; /* Total length of options */ + int nduseropt_ifindex; + __u8 nduseropt_icmp_type; + __u8 nduseropt_icmp_code; + unsigned short nduseropt_pad2; + unsigned int nduseropt_pad3; + /* Followed by one or more ND options */ +}; + +enum { + NDUSEROPT_UNSPEC, + NDUSEROPT_SRCADDR, + __NDUSEROPT_MAX +}; + +#define NDUSEROPT_MAX (__NDUSEROPT_MAX - 1) + +/* RTnetlink multicast groups - backwards compatibility for userspace */ +#define RTMGRP_LINK 1 +#define RTMGRP_NOTIFY 2 +#define RTMGRP_NEIGH 4 +#define RTMGRP_TC 8 + +#define RTMGRP_IPV4_IFADDR 0x10 +#define RTMGRP_IPV4_MROUTE 0x20 +#define RTMGRP_IPV4_ROUTE 0x40 +#define RTMGRP_IPV4_RULE 0x80 + +#define RTMGRP_IPV6_IFADDR 0x100 +#define RTMGRP_IPV6_MROUTE 0x200 +#define RTMGRP_IPV6_ROUTE 0x400 +#define RTMGRP_IPV6_IFINFO 0x800 + +#define RTMGRP_DECnet_IFADDR 0x1000 +#define RTMGRP_DECnet_ROUTE 0x4000 + +#define RTMGRP_IPV6_PREFIX 0x20000 + +/* RTnetlink multicast groups */ +enum rtnetlink_groups { + RTNLGRP_NONE, +#define RTNLGRP_NONE RTNLGRP_NONE + RTNLGRP_LINK, +#define RTNLGRP_LINK RTNLGRP_LINK + RTNLGRP_NOTIFY, +#define RTNLGRP_NOTIFY RTNLGRP_NOTIFY + RTNLGRP_NEIGH, +#define RTNLGRP_NEIGH RTNLGRP_NEIGH + RTNLGRP_TC, +#define RTNLGRP_TC RTNLGRP_TC + RTNLGRP_IPV4_IFADDR, +#define RTNLGRP_IPV4_IFADDR RTNLGRP_IPV4_IFADDR + RTNLGRP_IPV4_MROUTE, +#define RTNLGRP_IPV4_MROUTE RTNLGRP_IPV4_MROUTE + RTNLGRP_IPV4_ROUTE, +#define RTNLGRP_IPV4_ROUTE RTNLGRP_IPV4_ROUTE + RTNLGRP_IPV4_RULE, +#define RTNLGRP_IPV4_RULE RTNLGRP_IPV4_RULE + RTNLGRP_IPV6_IFADDR, +#define RTNLGRP_IPV6_IFADDR RTNLGRP_IPV6_IFADDR + RTNLGRP_IPV6_MROUTE, +#define RTNLGRP_IPV6_MROUTE RTNLGRP_IPV6_MROUTE + RTNLGRP_IPV6_ROUTE, +#define RTNLGRP_IPV6_ROUTE RTNLGRP_IPV6_ROUTE + RTNLGRP_IPV6_IFINFO, +#define RTNLGRP_IPV6_IFINFO RTNLGRP_IPV6_IFINFO + RTNLGRP_DECnet_IFADDR, +#define RTNLGRP_DECnet_IFADDR RTNLGRP_DECnet_IFADDR + RTNLGRP_NOP2, + RTNLGRP_DECnet_ROUTE, +#define RTNLGRP_DECnet_ROUTE RTNLGRP_DECnet_ROUTE + RTNLGRP_DECnet_RULE, +#define RTNLGRP_DECnet_RULE RTNLGRP_DECnet_RULE + RTNLGRP_NOP4, + RTNLGRP_IPV6_PREFIX, +#define RTNLGRP_IPV6_PREFIX RTNLGRP_IPV6_PREFIX + RTNLGRP_IPV6_RULE, +#define RTNLGRP_IPV6_RULE RTNLGRP_IPV6_RULE + RTNLGRP_ND_USEROPT, +#define RTNLGRP_ND_USEROPT RTNLGRP_ND_USEROPT + RTNLGRP_PHONET_IFADDR, +#define RTNLGRP_PHONET_IFADDR RTNLGRP_PHONET_IFADDR + RTNLGRP_PHONET_ROUTE, +#define RTNLGRP_PHONET_ROUTE RTNLGRP_PHONET_ROUTE + RTNLGRP_DCB, +#define RTNLGRP_DCB RTNLGRP_DCB + RTNLGRP_IPV4_NETCONF, +#define RTNLGRP_IPV4_NETCONF RTNLGRP_IPV4_NETCONF + RTNLGRP_IPV6_NETCONF, +#define RTNLGRP_IPV6_NETCONF RTNLGRP_IPV6_NETCONF + RTNLGRP_MDB, +#define RTNLGRP_MDB RTNLGRP_MDB + RTNLGRP_MPLS_ROUTE, +#define RTNLGRP_MPLS_ROUTE RTNLGRP_MPLS_ROUTE + RTNLGRP_NSID, +#define RTNLGRP_NSID RTNLGRP_NSID + RTNLGRP_MPLS_NETCONF, +#define RTNLGRP_MPLS_NETCONF RTNLGRP_MPLS_NETCONF + RTNLGRP_IPV4_MROUTE_R, +#define RTNLGRP_IPV4_MROUTE_R RTNLGRP_IPV4_MROUTE_R + RTNLGRP_IPV6_MROUTE_R, +#define RTNLGRP_IPV6_MROUTE_R RTNLGRP_IPV6_MROUTE_R + __RTNLGRP_MAX +}; +#define RTNLGRP_MAX (__RTNLGRP_MAX - 1) + +/* TC action piece */ +struct tcamsg { + unsigned char tca_family; + unsigned char tca__pad1; + unsigned short tca__pad2; +}; + +enum { + TCA_ROOT_UNSPEC, + TCA_ROOT_TAB, +#define TCA_ACT_TAB TCA_ROOT_TAB +#define TCAA_MAX TCA_ROOT_TAB + TCA_ROOT_FLAGS, + TCA_ROOT_COUNT, + TCA_ROOT_TIME_DELTA, /* in msecs */ + __TCA_ROOT_MAX, +#define TCA_ROOT_MAX (__TCA_ROOT_MAX - 1) +}; + +#define TA_RTA(r) ((struct rtattr*)(((char*)(r)) + NLMSG_ALIGN(sizeof(struct tcamsg)))) +#define TA_PAYLOAD(n) NLMSG_PAYLOAD(n,sizeof(struct tcamsg)) +/* tcamsg flags stored in attribute TCA_ROOT_FLAGS + * + * TCA_FLAG_LARGE_DUMP_ON user->kernel to request for larger than TCA_ACT_MAX_PRIO + * actions in a dump. All dump responses will contain the number of actions + * being dumped stored in for user app's consumption in TCA_ROOT_COUNT + * + */ +#define TCA_FLAG_LARGE_DUMP_ON (1 << 0) + +/* New extended info filters for IFLA_EXT_MASK */ +#define RTEXT_FILTER_VF (1 << 0) +#define RTEXT_FILTER_BRVLAN (1 << 1) +#define RTEXT_FILTER_BRVLAN_COMPRESSED (1 << 2) +#define RTEXT_FILTER_SKIP_STATS (1 << 3) + +/* End of information exported to user level */ + + + +#endif /* __LINUX_RTNETLINK_H */ diff --git a/include/linux/sockios.h b/include/linux/sockios.h new file mode 100644 index 0000000000000000000000000000000000000000..d393e9ed396426125152a0583a6837274e111e0f --- /dev/null +++ b/include/linux/sockios.h @@ -0,0 +1,153 @@ +/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */ +/* + * INET An implementation of the TCP/IP protocol suite for the LINUX + * operating system. INET is implemented using the BSD Socket + * interface as the means of communication with the user level. + * + * Definitions of the socket-level I/O control calls. + * + * Version: @(#)sockios.h 1.0.2 03/09/93 + * + * Authors: Ross Biro + * Fred N. van Kempen, + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ +#ifndef _LINUX_SOCKIOS_H +#define _LINUX_SOCKIOS_H + +#include + +/* Linux-specific socket ioctls */ +#define SIOCINQ FIONREAD +#define SIOCOUTQ TIOCOUTQ /* output queue size (not sent + not acked) */ + +#define SOCK_IOC_TYPE 0x89 + +/* Routing table calls. */ +#define SIOCADDRT 0x890B /* add routing table entry */ +#define SIOCDELRT 0x890C /* delete routing table entry */ +#define SIOCRTMSG 0x890D /* unused */ + +/* Socket configuration controls. */ +#define SIOCGIFNAME 0x8910 /* get iface name */ +#define SIOCSIFLINK 0x8911 /* set iface channel */ +#define SIOCGIFCONF 0x8912 /* get iface list */ +#define SIOCGIFFLAGS 0x8913 /* get flags */ +#define SIOCSIFFLAGS 0x8914 /* set flags */ +#define SIOCGIFADDR 0x8915 /* get PA address */ +#define SIOCSIFADDR 0x8916 /* set PA address */ +#define SIOCGIFDSTADDR 0x8917 /* get remote PA address */ +#define SIOCSIFDSTADDR 0x8918 /* set remote PA address */ +#define SIOCGIFBRDADDR 0x8919 /* get broadcast PA address */ +#define SIOCSIFBRDADDR 0x891a /* set broadcast PA address */ +#define SIOCGIFNETMASK 0x891b /* get network PA mask */ +#define SIOCSIFNETMASK 0x891c /* set network PA mask */ +#define SIOCGIFMETRIC 0x891d /* get metric */ +#define SIOCSIFMETRIC 0x891e /* set metric */ +#define SIOCGIFMEM 0x891f /* get memory address (BSD) */ +#define SIOCSIFMEM 0x8920 /* set memory address (BSD) */ +#define SIOCGIFMTU 0x8921 /* get MTU size */ +#define SIOCSIFMTU 0x8922 /* set MTU size */ +#define SIOCSIFNAME 0x8923 /* set interface name */ +#define SIOCSIFHWADDR 0x8924 /* set hardware address */ +#define SIOCGIFENCAP 0x8925 /* get/set encapsulations */ +#define SIOCSIFENCAP 0x8926 +#define SIOCGIFHWADDR 0x8927 /* Get hardware address */ +#define SIOCGIFSLAVE 0x8929 /* Driver slaving support */ +#define SIOCSIFSLAVE 0x8930 +#define SIOCADDMULTI 0x8931 /* Multicast address lists */ +#define SIOCDELMULTI 0x8932 +#define SIOCGIFINDEX 0x8933 /* name -> if_index mapping */ +#define SIOGIFINDEX SIOCGIFINDEX /* misprint compatibility :-) */ +#define SIOCSIFPFLAGS 0x8934 /* set/get extended flags set */ +#define SIOCGIFPFLAGS 0x8935 +#define SIOCDIFADDR 0x8936 /* delete PA address */ +#define SIOCSIFHWBROADCAST 0x8937 /* set hardware broadcast addr */ +#define SIOCGIFCOUNT 0x8938 /* get number of devices */ + +#define SIOCGIFBR 0x8940 /* Bridging support */ +#define SIOCSIFBR 0x8941 /* Set bridging options */ + +#define SIOCGIFTXQLEN 0x8942 /* Get the tx queue length */ +#define SIOCSIFTXQLEN 0x8943 /* Set the tx queue length */ + +/* SIOCGIFDIVERT was: 0x8944 Frame diversion support */ +/* SIOCSIFDIVERT was: 0x8945 Set frame diversion options */ + +#define SIOCETHTOOL 0x8946 /* Ethtool interface */ + +#define SIOCGMIIPHY 0x8947 /* Get address of MII PHY in use. */ +#define SIOCGMIIREG 0x8948 /* Read MII PHY register. */ +#define SIOCSMIIREG 0x8949 /* Write MII PHY register. */ + +#define SIOCWANDEV 0x894A /* get/set netdev parameters */ + +#define SIOCOUTQNSD 0x894B /* output queue size (not sent only) */ +#define SIOCGSKNS 0x894C /* get socket network namespace */ + +/* ARP cache control calls. */ + /* 0x8950 - 0x8952 * obsolete calls, don't re-use */ +#define SIOCDARP 0x8953 /* delete ARP table entry */ +#define SIOCGARP 0x8954 /* get ARP table entry */ +#define SIOCSARP 0x8955 /* set ARP table entry */ + +/* RARP cache control calls. */ +#define SIOCDRARP 0x8960 /* delete RARP table entry */ +#define SIOCGRARP 0x8961 /* get RARP table entry */ +#define SIOCSRARP 0x8962 /* set RARP table entry */ + +/* Driver configuration calls */ + +#define SIOCGIFMAP 0x8970 /* Get device parameters */ +#define SIOCSIFMAP 0x8971 /* Set device parameters */ + +/* DLCI configuration calls */ + +#define SIOCADDDLCI 0x8980 /* Create new DLCI device */ +#define SIOCDELDLCI 0x8981 /* Delete DLCI device */ + +#define SIOCGIFVLAN 0x8982 /* 802.1Q VLAN support */ +#define SIOCSIFVLAN 0x8983 /* Set 802.1Q VLAN options */ + +/* bonding calls */ + +#define SIOCBONDENSLAVE 0x8990 /* enslave a device to the bond */ +#define SIOCBONDRELEASE 0x8991 /* release a slave from the bond*/ +#define SIOCBONDSETHWADDR 0x8992 /* set the hw addr of the bond */ +#define SIOCBONDSLAVEINFOQUERY 0x8993 /* rtn info about slave state */ +#define SIOCBONDINFOQUERY 0x8994 /* rtn info about bond state */ +#define SIOCBONDCHANGEACTIVE 0x8995 /* update to a new active slave */ + +/* bridge calls */ +#define SIOCBRADDBR 0x89a0 /* create new bridge device */ +#define SIOCBRDELBR 0x89a1 /* remove bridge device */ +#define SIOCBRADDIF 0x89a2 /* add interface to bridge */ +#define SIOCBRDELIF 0x89a3 /* remove interface from bridge */ + +/* hardware time stamping: parameters in linux/net_tstamp.h */ +#define SIOCSHWTSTAMP 0x89b0 /* set and get config */ +#define SIOCGHWTSTAMP 0x89b1 /* get config */ + +/* Device private ioctl calls */ + +/* + * These 16 ioctls are available to devices via the do_ioctl() device + * vector. Each device should include this file and redefine these names + * as their own. Because these are device dependent it is a good idea + * _NOT_ to issue them to random objects and hope. + * + * THESE IOCTLS ARE _DEPRECATED_ AND WILL DISAPPEAR IN 2.5.X -DaveM + */ + +#define SIOCDEVPRIVATE 0x89F0 /* to 89FF */ + +/* + * These 16 ioctl calls are protocol private + */ + +#define SIOCPROTOPRIVATE 0x89E0 /* to 89EF */ +#endif /* _LINUX_SOCKIOS_H */ diff --git a/include/linux/types.h b/include/linux/types.h new file mode 100644 index 0000000000000000000000000000000000000000..999cb0fa88ebdf0a3c9503b43eb2605cd9c487aa --- /dev/null +++ b/include/linux/types.h @@ -0,0 +1,50 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +#ifndef _LINUX_TYPES_H +#define _LINUX_TYPES_H + +#include + +#ifndef __ASSEMBLY__ + +#include + + +/* + * Below are truly Linux-specific types that should never collide with + * any application/library that wants linux/types.h. + */ + +#ifdef __CHECKER__ +#define __bitwise__ __attribute__((bitwise)) +#else +#define __bitwise__ +#endif +#define __bitwise __bitwise__ + +typedef __u16 __bitwise __le16; +typedef __u16 __bitwise __be16; +typedef __u32 __bitwise __le32; +typedef __u32 __bitwise __be32; +typedef __u64 __bitwise __le64; +typedef __u64 __bitwise __be64; + +typedef __u16 __bitwise __sum16; +typedef __u32 __bitwise __wsum; + +/* + * aligned_u64 should be used in defining kernel<->userspace ABIs to avoid + * common 32/64-bit compat problems. + * 64-bit values align to 4-byte boundaries on x86_32 (and possibly other + * architectures) and to 8-byte boundaries on 64-bit architectures. The new + * aligned_64 type enforces 8-byte alignment so that structs containing + * aligned_64 values have the same alignment on 32-bit and 64-bit architectures. + * No conversions are necessary between 32-bit user-space and a 64-bit kernel. + */ +#define __aligned_u64 __u64 __attribute__((aligned(8))) +#define __aligned_be64 __be64 __attribute__((aligned(8))) +#define __aligned_le64 __le64 __attribute__((aligned(8))) + +typedef unsigned __bitwise __poll_t; + +#endif /* __ASSEMBLY__ */ +#endif /* _LINUX_TYPES_H */ diff --git a/include/linux/wireless.h b/include/linux/wireless.h new file mode 100644 index 0000000000000000000000000000000000000000..5de7306c92c7872abaa91fed155735c3fecb1150 --- /dev/null +++ b/include/linux/wireless.h @@ -0,0 +1,1110 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +/* + * This file define a set of standard wireless extensions + * + * Version : 22 16.3.07 + * + * Authors : Jean Tourrilhes - HPL - + * Copyright (c) 1997-2007 Jean Tourrilhes, All Rights Reserved. + */ + +#ifndef _LINUX_WIRELESS_H +#define _LINUX_WIRELESS_H + +/************************** DOCUMENTATION **************************/ +/* + * Initial APIs (1996 -> onward) : + * ----------------------------- + * Basically, the wireless extensions are for now a set of standard ioctl + * call + /proc/net/wireless + * + * The entry /proc/net/wireless give statistics and information on the + * driver. + * This is better than having each driver having its entry because + * its centralised and we may remove the driver module safely. + * + * Ioctl are used to configure the driver and issue commands. This is + * better than command line options of insmod because we may want to + * change dynamically (while the driver is running) some parameters. + * + * The ioctl mechanimsm are copied from standard devices ioctl. + * We have the list of command plus a structure descibing the + * data exchanged... + * Note that to add these ioctl, I was obliged to modify : + * # net/core/dev.c (two place + add include) + * # net/ipv4/af_inet.c (one place + add include) + * + * /proc/net/wireless is a copy of /proc/net/dev. + * We have a structure for data passed from the driver to /proc/net/wireless + * Too add this, I've modified : + * # net/core/dev.c (two other places) + * # include/linux/netdevice.h (one place) + * # include/linux/proc_fs.h (one place) + * + * New driver API (2002 -> onward) : + * ------------------------------- + * This file is only concerned with the user space API and common definitions. + * The new driver API is defined and documented in : + * # include/net/iw_handler.h + * + * Note as well that /proc/net/wireless implementation has now moved in : + * # net/core/wireless.c + * + * Wireless Events (2002 -> onward) : + * -------------------------------- + * Events are defined at the end of this file, and implemented in : + * # net/core/wireless.c + * + * Other comments : + * -------------- + * Do not add here things that are redundant with other mechanisms + * (drivers init, ifconfig, /proc/net/dev, ...) and with are not + * wireless specific. + * + * These wireless extensions are not magic : each driver has to provide + * support for them... + * + * IMPORTANT NOTE : As everything in the kernel, this is very much a + * work in progress. Contact me if you have ideas of improvements... + */ + +/***************************** INCLUDES *****************************/ + +#include /* for __u* and __s* typedefs */ +#include /* for "struct sockaddr" et al */ +#include /* for IFNAMSIZ and co... */ + +/***************************** VERSION *****************************/ +/* + * This constant is used to know the availability of the wireless + * extensions and to know which version of wireless extensions it is + * (there is some stuff that will be added in the future...) + * I just plan to increment with each new version. + */ +#define WIRELESS_EXT 22 + +/* + * Changes : + * + * V2 to V3 + * -------- + * Alan Cox start some incompatibles changes. I've integrated a bit more. + * - Encryption renamed to Encode to avoid US regulation problems + * - Frequency changed from float to struct to avoid problems on old 386 + * + * V3 to V4 + * -------- + * - Add sensitivity + * + * V4 to V5 + * -------- + * - Missing encoding definitions in range + * - Access points stuff + * + * V5 to V6 + * -------- + * - 802.11 support (ESSID ioctls) + * + * V6 to V7 + * -------- + * - define IW_ESSID_MAX_SIZE and IW_MAX_AP + * + * V7 to V8 + * -------- + * - Changed my e-mail address + * - More 802.11 support (nickname, rate, rts, frag) + * - List index in frequencies + * + * V8 to V9 + * -------- + * - Support for 'mode of operation' (ad-hoc, managed...) + * - Support for unicast and multicast power saving + * - Change encoding to support larger tokens (>64 bits) + * - Updated iw_params (disable, flags) and use it for NWID + * - Extracted iw_point from iwreq for clarity + * + * V9 to V10 + * --------- + * - Add PM capability to range structure + * - Add PM modifier : MAX/MIN/RELATIVE + * - Add encoding option : IW_ENCODE_NOKEY + * - Add TxPower ioctls (work like TxRate) + * + * V10 to V11 + * ---------- + * - Add WE version in range (help backward/forward compatibility) + * - Add retry ioctls (work like PM) + * + * V11 to V12 + * ---------- + * - Add SIOCSIWSTATS to get /proc/net/wireless programatically + * - Add DEV PRIVATE IOCTL to avoid collisions in SIOCDEVPRIVATE space + * - Add new statistics (frag, retry, beacon) + * - Add average quality (for user space calibration) + * + * V12 to V13 + * ---------- + * - Document creation of new driver API. + * - Extract union iwreq_data from struct iwreq (for new driver API). + * - Rename SIOCSIWNAME as SIOCSIWCOMMIT + * + * V13 to V14 + * ---------- + * - Wireless Events support : define struct iw_event + * - Define additional specific event numbers + * - Add "addr" and "param" fields in union iwreq_data + * - AP scanning stuff (SIOCSIWSCAN and friends) + * + * V14 to V15 + * ---------- + * - Add IW_PRIV_TYPE_ADDR for struct sockaddr private arg + * - Make struct iw_freq signed (both m & e), add explicit padding + * - Add IWEVCUSTOM for driver specific event/scanning token + * - Add IW_MAX_GET_SPY for driver returning a lot of addresses + * - Add IW_TXPOW_RANGE for range of Tx Powers + * - Add IWEVREGISTERED & IWEVEXPIRED events for Access Points + * - Add IW_MODE_MONITOR for passive monitor + * + * V15 to V16 + * ---------- + * - Increase the number of bitrates in iw_range to 32 (for 802.11g) + * - Increase the number of frequencies in iw_range to 32 (for 802.11b+a) + * - Reshuffle struct iw_range for increases, add filler + * - Increase IW_MAX_AP to 64 for driver returning a lot of addresses + * - Remove IW_MAX_GET_SPY because conflict with enhanced spy support + * - Add SIOCSIWTHRSPY/SIOCGIWTHRSPY and "struct iw_thrspy" + * - Add IW_ENCODE_TEMP and iw_range->encoding_login_index + * + * V16 to V17 + * ---------- + * - Add flags to frequency -> auto/fixed + * - Document (struct iw_quality *)->updated, add new flags (INVALID) + * - Wireless Event capability in struct iw_range + * - Add support for relative TxPower (yick !) + * + * V17 to V18 (From Jouni Malinen ) + * ---------- + * - Add support for WPA/WPA2 + * - Add extended encoding configuration (SIOCSIWENCODEEXT and + * SIOCGIWENCODEEXT) + * - Add SIOCSIWGENIE/SIOCGIWGENIE + * - Add SIOCSIWMLME + * - Add SIOCSIWPMKSA + * - Add struct iw_range bit field for supported encoding capabilities + * - Add optional scan request parameters for SIOCSIWSCAN + * - Add SIOCSIWAUTH/SIOCGIWAUTH for setting authentication and WPA + * related parameters (extensible up to 4096 parameter values) + * - Add wireless events: IWEVGENIE, IWEVMICHAELMICFAILURE, + * IWEVASSOCREQIE, IWEVASSOCRESPIE, IWEVPMKIDCAND + * + * V18 to V19 + * ---------- + * - Remove (struct iw_point *)->pointer from events and streams + * - Remove header includes to help user space + * - Increase IW_ENCODING_TOKEN_MAX from 32 to 64 + * - Add IW_QUAL_ALL_UPDATED and IW_QUAL_ALL_INVALID macros + * - Add explicit flag to tell stats are in dBm : IW_QUAL_DBM + * - Add IW_IOCTL_IDX() and IW_EVENT_IDX() macros + * + * V19 to V20 + * ---------- + * - RtNetlink requests support (SET/GET) + * + * V20 to V21 + * ---------- + * - Remove (struct net_device *)->get_wireless_stats() + * - Change length in ESSID and NICK to strlen() instead of strlen()+1 + * - Add IW_RETRY_SHORT/IW_RETRY_LONG retry modifiers + * - Power/Retry relative values no longer * 100000 + * - Add explicit flag to tell stats are in 802.11k RCPI : IW_QUAL_RCPI + * + * V21 to V22 + * ---------- + * - Prevent leaking of kernel space in stream on 64 bits. + */ + +/**************************** CONSTANTS ****************************/ + +/* -------------------------- IOCTL LIST -------------------------- */ + +/* Wireless Identification */ +#define SIOCSIWCOMMIT 0x8B00 /* Commit pending changes to driver */ +#define SIOCGIWNAME 0x8B01 /* get name == wireless protocol */ +/* SIOCGIWNAME is used to verify the presence of Wireless Extensions. + * Common values : "IEEE 802.11-DS", "IEEE 802.11-FH", "IEEE 802.11b"... + * Don't put the name of your driver there, it's useless. */ + +/* Basic operations */ +#define SIOCSIWNWID 0x8B02 /* set network id (pre-802.11) */ +#define SIOCGIWNWID 0x8B03 /* get network id (the cell) */ +#define SIOCSIWFREQ 0x8B04 /* set channel/frequency (Hz) */ +#define SIOCGIWFREQ 0x8B05 /* get channel/frequency (Hz) */ +#define SIOCSIWMODE 0x8B06 /* set operation mode */ +#define SIOCGIWMODE 0x8B07 /* get operation mode */ +#define SIOCSIWSENS 0x8B08 /* set sensitivity (dBm) */ +#define SIOCGIWSENS 0x8B09 /* get sensitivity (dBm) */ + +/* Informative stuff */ +#define SIOCSIWRANGE 0x8B0A /* Unused */ +#define SIOCGIWRANGE 0x8B0B /* Get range of parameters */ +#define SIOCSIWPRIV 0x8B0C /* Unused */ +#define SIOCGIWPRIV 0x8B0D /* get private ioctl interface info */ +#define SIOCSIWSTATS 0x8B0E /* Unused */ +#define SIOCGIWSTATS 0x8B0F /* Get /proc/net/wireless stats */ +/* SIOCGIWSTATS is strictly used between user space and the kernel, and + * is never passed to the driver (i.e. the driver will never see it). */ + +/* Spy support (statistics per MAC address - used for Mobile IP support) */ +#define SIOCSIWSPY 0x8B10 /* set spy addresses */ +#define SIOCGIWSPY 0x8B11 /* get spy info (quality of link) */ +#define SIOCSIWTHRSPY 0x8B12 /* set spy threshold (spy event) */ +#define SIOCGIWTHRSPY 0x8B13 /* get spy threshold */ + +/* Access Point manipulation */ +#define SIOCSIWAP 0x8B14 /* set access point MAC addresses */ +#define SIOCGIWAP 0x8B15 /* get access point MAC addresses */ +#define SIOCGIWAPLIST 0x8B17 /* Deprecated in favor of scanning */ +#define SIOCSIWSCAN 0x8B18 /* trigger scanning (list cells) */ +#define SIOCGIWSCAN 0x8B19 /* get scanning results */ + +/* 802.11 specific support */ +#define SIOCSIWESSID 0x8B1A /* set ESSID (network name) */ +#define SIOCGIWESSID 0x8B1B /* get ESSID */ +#define SIOCSIWNICKN 0x8B1C /* set node name/nickname */ +#define SIOCGIWNICKN 0x8B1D /* get node name/nickname */ +/* As the ESSID and NICKN are strings up to 32 bytes long, it doesn't fit + * within the 'iwreq' structure, so we need to use the 'data' member to + * point to a string in user space, like it is done for RANGE... */ + +/* Other parameters useful in 802.11 and some other devices */ +#define SIOCSIWRATE 0x8B20 /* set default bit rate (bps) */ +#define SIOCGIWRATE 0x8B21 /* get default bit rate (bps) */ +#define SIOCSIWRTS 0x8B22 /* set RTS/CTS threshold (bytes) */ +#define SIOCGIWRTS 0x8B23 /* get RTS/CTS threshold (bytes) */ +#define SIOCSIWFRAG 0x8B24 /* set fragmentation thr (bytes) */ +#define SIOCGIWFRAG 0x8B25 /* get fragmentation thr (bytes) */ +#define SIOCSIWTXPOW 0x8B26 /* set transmit power (dBm) */ +#define SIOCGIWTXPOW 0x8B27 /* get transmit power (dBm) */ +#define SIOCSIWRETRY 0x8B28 /* set retry limits and lifetime */ +#define SIOCGIWRETRY 0x8B29 /* get retry limits and lifetime */ + +/* Encoding stuff (scrambling, hardware security, WEP...) */ +#define SIOCSIWENCODE 0x8B2A /* set encoding token & mode */ +#define SIOCGIWENCODE 0x8B2B /* get encoding token & mode */ +/* Power saving stuff (power management, unicast and multicast) */ +#define SIOCSIWPOWER 0x8B2C /* set Power Management settings */ +#define SIOCGIWPOWER 0x8B2D /* get Power Management settings */ + +/* WPA : Generic IEEE 802.11 informatiom element (e.g., for WPA/RSN/WMM). + * This ioctl uses struct iw_point and data buffer that includes IE id and len + * fields. More than one IE may be included in the request. Setting the generic + * IE to empty buffer (len=0) removes the generic IE from the driver. Drivers + * are allowed to generate their own WPA/RSN IEs, but in these cases, drivers + * are required to report the used IE as a wireless event, e.g., when + * associating with an AP. */ +#define SIOCSIWGENIE 0x8B30 /* set generic IE */ +#define SIOCGIWGENIE 0x8B31 /* get generic IE */ + +/* WPA : IEEE 802.11 MLME requests */ +#define SIOCSIWMLME 0x8B16 /* request MLME operation; uses + * struct iw_mlme */ +/* WPA : Authentication mode parameters */ +#define SIOCSIWAUTH 0x8B32 /* set authentication mode params */ +#define SIOCGIWAUTH 0x8B33 /* get authentication mode params */ + +/* WPA : Extended version of encoding configuration */ +#define SIOCSIWENCODEEXT 0x8B34 /* set encoding token & mode */ +#define SIOCGIWENCODEEXT 0x8B35 /* get encoding token & mode */ + +/* WPA2 : PMKSA cache management */ +#define SIOCSIWPMKSA 0x8B36 /* PMKSA cache operation */ + +/* -------------------- DEV PRIVATE IOCTL LIST -------------------- */ + +/* These 32 ioctl are wireless device private, for 16 commands. + * Each driver is free to use them for whatever purpose it chooses, + * however the driver *must* export the description of those ioctls + * with SIOCGIWPRIV and *must* use arguments as defined below. + * If you don't follow those rules, DaveM is going to hate you (reason : + * it make mixed 32/64bit operation impossible). + */ +#define SIOCIWFIRSTPRIV 0x8BE0 +#define SIOCIWLASTPRIV 0x8BFF +/* Previously, we were using SIOCDEVPRIVATE, but we now have our + * separate range because of collisions with other tools such as + * 'mii-tool'. + * We now have 32 commands, so a bit more space ;-). + * Also, all 'even' commands are only usable by root and don't return the + * content of ifr/iwr to user (but you are not obliged to use the set/get + * convention, just use every other two command). More details in iwpriv.c. + * And I repeat : you are not forced to use them with iwpriv, but you + * must be compliant with it. + */ + +/* ------------------------- IOCTL STUFF ------------------------- */ + +/* The first and the last (range) */ +#define SIOCIWFIRST 0x8B00 +#define SIOCIWLAST SIOCIWLASTPRIV /* 0x8BFF */ +#define IW_IOCTL_IDX(cmd) ((cmd) - SIOCIWFIRST) +#define IW_HANDLER(id, func) \ + [IW_IOCTL_IDX(id)] = func + +/* Odd : get (world access), even : set (root access) */ +#define IW_IS_SET(cmd) (!((cmd) & 0x1)) +#define IW_IS_GET(cmd) ((cmd) & 0x1) + +/* ----------------------- WIRELESS EVENTS ----------------------- */ +/* Those are *NOT* ioctls, do not issue request on them !!! */ +/* Most events use the same identifier as ioctl requests */ + +#define IWEVTXDROP 0x8C00 /* Packet dropped to excessive retry */ +#define IWEVQUAL 0x8C01 /* Quality part of statistics (scan) */ +#define IWEVCUSTOM 0x8C02 /* Driver specific ascii string */ +#define IWEVREGISTERED 0x8C03 /* Discovered a new node (AP mode) */ +#define IWEVEXPIRED 0x8C04 /* Expired a node (AP mode) */ +#define IWEVGENIE 0x8C05 /* Generic IE (WPA, RSN, WMM, ..) + * (scan results); This includes id and + * length fields. One IWEVGENIE may + * contain more than one IE. Scan + * results may contain one or more + * IWEVGENIE events. */ +#define IWEVMICHAELMICFAILURE 0x8C06 /* Michael MIC failure + * (struct iw_michaelmicfailure) + */ +#define IWEVASSOCREQIE 0x8C07 /* IEs used in (Re)Association Request. + * The data includes id and length + * fields and may contain more than one + * IE. This event is required in + * Managed mode if the driver + * generates its own WPA/RSN IE. This + * should be sent just before + * IWEVREGISTERED event for the + * association. */ +#define IWEVASSOCRESPIE 0x8C08 /* IEs used in (Re)Association + * Response. The data includes id and + * length fields and may contain more + * than one IE. This may be sent + * between IWEVASSOCREQIE and + * IWEVREGISTERED events for the + * association. */ +#define IWEVPMKIDCAND 0x8C09 /* PMKID candidate for RSN + * pre-authentication + * (struct iw_pmkid_cand) */ + +#define IWEVFIRST 0x8C00 +#define IW_EVENT_IDX(cmd) ((cmd) - IWEVFIRST) + +/* ------------------------- PRIVATE INFO ------------------------- */ +/* + * The following is used with SIOCGIWPRIV. It allow a driver to define + * the interface (name, type of data) for its private ioctl. + * Privates ioctl are SIOCIWFIRSTPRIV -> SIOCIWLASTPRIV + */ + +#define IW_PRIV_TYPE_MASK 0x7000 /* Type of arguments */ +#define IW_PRIV_TYPE_NONE 0x0000 +#define IW_PRIV_TYPE_BYTE 0x1000 /* Char as number */ +#define IW_PRIV_TYPE_CHAR 0x2000 /* Char as character */ +#define IW_PRIV_TYPE_INT 0x4000 /* 32 bits int */ +#define IW_PRIV_TYPE_FLOAT 0x5000 /* struct iw_freq */ +#define IW_PRIV_TYPE_ADDR 0x6000 /* struct sockaddr */ + +#define IW_PRIV_SIZE_FIXED 0x0800 /* Variable or fixed number of args */ + +#define IW_PRIV_SIZE_MASK 0x07FF /* Max number of those args */ + +/* + * Note : if the number of args is fixed and the size < 16 octets, + * instead of passing a pointer we will put args in the iwreq struct... + */ + +/* ----------------------- OTHER CONSTANTS ----------------------- */ + +/* Maximum frequencies in the range struct */ +#define IW_MAX_FREQUENCIES 32 +/* Note : if you have something like 80 frequencies, + * don't increase this constant and don't fill the frequency list. + * The user will be able to set by channel anyway... */ + +/* Maximum bit rates in the range struct */ +#define IW_MAX_BITRATES 32 + +/* Maximum tx powers in the range struct */ +#define IW_MAX_TXPOWER 8 +/* Note : if you more than 8 TXPowers, just set the max and min or + * a few of them in the struct iw_range. */ + +/* Maximum of address that you may set with SPY */ +#define IW_MAX_SPY 8 + +/* Maximum of address that you may get in the + list of access points in range */ +#define IW_MAX_AP 64 + +/* Maximum size of the ESSID and NICKN strings */ +#define IW_ESSID_MAX_SIZE 32 + +/* Modes of operation */ +#define IW_MODE_AUTO 0 /* Let the driver decides */ +#define IW_MODE_ADHOC 1 /* Single cell network */ +#define IW_MODE_INFRA 2 /* Multi cell network, roaming, ... */ +#define IW_MODE_MASTER 3 /* Synchronisation master or Access Point */ +#define IW_MODE_REPEAT 4 /* Wireless Repeater (forwarder) */ +#define IW_MODE_SECOND 5 /* Secondary master/repeater (backup) */ +#define IW_MODE_MONITOR 6 /* Passive monitor (listen only) */ +#define IW_MODE_MESH 7 /* Mesh (IEEE 802.11s) network */ + +/* Statistics flags (bitmask in updated) */ +#define IW_QUAL_QUAL_UPDATED 0x01 /* Value was updated since last read */ +#define IW_QUAL_LEVEL_UPDATED 0x02 +#define IW_QUAL_NOISE_UPDATED 0x04 +#define IW_QUAL_ALL_UPDATED 0x07 +#define IW_QUAL_DBM 0x08 /* Level + Noise are dBm */ +#define IW_QUAL_QUAL_INVALID 0x10 /* Driver doesn't provide value */ +#define IW_QUAL_LEVEL_INVALID 0x20 +#define IW_QUAL_NOISE_INVALID 0x40 +#define IW_QUAL_RCPI 0x80 /* Level + Noise are 802.11k RCPI */ +#define IW_QUAL_ALL_INVALID 0x70 + +/* Frequency flags */ +#define IW_FREQ_AUTO 0x00 /* Let the driver decides */ +#define IW_FREQ_FIXED 0x01 /* Force a specific value */ + +/* Maximum number of size of encoding token available + * they are listed in the range structure */ +#define IW_MAX_ENCODING_SIZES 8 + +/* Maximum size of the encoding token in bytes */ +#define IW_ENCODING_TOKEN_MAX 64 /* 512 bits (for now) */ + +/* Flags for encoding (along with the token) */ +#define IW_ENCODE_INDEX 0x00FF /* Token index (if needed) */ +#define IW_ENCODE_FLAGS 0xFF00 /* Flags defined below */ +#define IW_ENCODE_MODE 0xF000 /* Modes defined below */ +#define IW_ENCODE_DISABLED 0x8000 /* Encoding disabled */ +#define IW_ENCODE_ENABLED 0x0000 /* Encoding enabled */ +#define IW_ENCODE_RESTRICTED 0x4000 /* Refuse non-encoded packets */ +#define IW_ENCODE_OPEN 0x2000 /* Accept non-encoded packets */ +#define IW_ENCODE_NOKEY 0x0800 /* Key is write only, so not present */ +#define IW_ENCODE_TEMP 0x0400 /* Temporary key */ + +/* Power management flags available (along with the value, if any) */ +#define IW_POWER_ON 0x0000 /* No details... */ +#define IW_POWER_TYPE 0xF000 /* Type of parameter */ +#define IW_POWER_PERIOD 0x1000 /* Value is a period/duration of */ +#define IW_POWER_TIMEOUT 0x2000 /* Value is a timeout (to go asleep) */ +#define IW_POWER_MODE 0x0F00 /* Power Management mode */ +#define IW_POWER_UNICAST_R 0x0100 /* Receive only unicast messages */ +#define IW_POWER_MULTICAST_R 0x0200 /* Receive only multicast messages */ +#define IW_POWER_ALL_R 0x0300 /* Receive all messages though PM */ +#define IW_POWER_FORCE_S 0x0400 /* Force PM procedure for sending unicast */ +#define IW_POWER_REPEATER 0x0800 /* Repeat broadcast messages in PM period */ +#define IW_POWER_MODIFIER 0x000F /* Modify a parameter */ +#define IW_POWER_MIN 0x0001 /* Value is a minimum */ +#define IW_POWER_MAX 0x0002 /* Value is a maximum */ +#define IW_POWER_RELATIVE 0x0004 /* Value is not in seconds/ms/us */ + +/* Transmit Power flags available */ +#define IW_TXPOW_TYPE 0x00FF /* Type of value */ +#define IW_TXPOW_DBM 0x0000 /* Value is in dBm */ +#define IW_TXPOW_MWATT 0x0001 /* Value is in mW */ +#define IW_TXPOW_RELATIVE 0x0002 /* Value is in arbitrary units */ +#define IW_TXPOW_RANGE 0x1000 /* Range of value between min/max */ + +/* Retry limits and lifetime flags available */ +#define IW_RETRY_ON 0x0000 /* No details... */ +#define IW_RETRY_TYPE 0xF000 /* Type of parameter */ +#define IW_RETRY_LIMIT 0x1000 /* Maximum number of retries*/ +#define IW_RETRY_LIFETIME 0x2000 /* Maximum duration of retries in us */ +#define IW_RETRY_MODIFIER 0x00FF /* Modify a parameter */ +#define IW_RETRY_MIN 0x0001 /* Value is a minimum */ +#define IW_RETRY_MAX 0x0002 /* Value is a maximum */ +#define IW_RETRY_RELATIVE 0x0004 /* Value is not in seconds/ms/us */ +#define IW_RETRY_SHORT 0x0010 /* Value is for short packets */ +#define IW_RETRY_LONG 0x0020 /* Value is for long packets */ + +/* Scanning request flags */ +#define IW_SCAN_DEFAULT 0x0000 /* Default scan of the driver */ +#define IW_SCAN_ALL_ESSID 0x0001 /* Scan all ESSIDs */ +#define IW_SCAN_THIS_ESSID 0x0002 /* Scan only this ESSID */ +#define IW_SCAN_ALL_FREQ 0x0004 /* Scan all Frequencies */ +#define IW_SCAN_THIS_FREQ 0x0008 /* Scan only this Frequency */ +#define IW_SCAN_ALL_MODE 0x0010 /* Scan all Modes */ +#define IW_SCAN_THIS_MODE 0x0020 /* Scan only this Mode */ +#define IW_SCAN_ALL_RATE 0x0040 /* Scan all Bit-Rates */ +#define IW_SCAN_THIS_RATE 0x0080 /* Scan only this Bit-Rate */ +/* struct iw_scan_req scan_type */ +#define IW_SCAN_TYPE_ACTIVE 0 +#define IW_SCAN_TYPE_PASSIVE 1 +/* Maximum size of returned data */ +#define IW_SCAN_MAX_DATA 4096 /* In bytes */ + +/* Scan capability flags - in (struct iw_range *)->scan_capa */ +#define IW_SCAN_CAPA_NONE 0x00 +#define IW_SCAN_CAPA_ESSID 0x01 +#define IW_SCAN_CAPA_BSSID 0x02 +#define IW_SCAN_CAPA_CHANNEL 0x04 +#define IW_SCAN_CAPA_MODE 0x08 +#define IW_SCAN_CAPA_RATE 0x10 +#define IW_SCAN_CAPA_TYPE 0x20 +#define IW_SCAN_CAPA_TIME 0x40 + +/* Max number of char in custom event - use multiple of them if needed */ +#define IW_CUSTOM_MAX 256 /* In bytes */ + +/* Generic information element */ +#define IW_GENERIC_IE_MAX 1024 + +/* MLME requests (SIOCSIWMLME / struct iw_mlme) */ +#define IW_MLME_DEAUTH 0 +#define IW_MLME_DISASSOC 1 +#define IW_MLME_AUTH 2 +#define IW_MLME_ASSOC 3 + +/* SIOCSIWAUTH/SIOCGIWAUTH struct iw_param flags */ +#define IW_AUTH_INDEX 0x0FFF +#define IW_AUTH_FLAGS 0xF000 +/* SIOCSIWAUTH/SIOCGIWAUTH parameters (0 .. 4095) + * (IW_AUTH_INDEX mask in struct iw_param flags; this is the index of the + * parameter that is being set/get to; value will be read/written to + * struct iw_param value field) */ +#define IW_AUTH_WPA_VERSION 0 +#define IW_AUTH_CIPHER_PAIRWISE 1 +#define IW_AUTH_CIPHER_GROUP 2 +#define IW_AUTH_KEY_MGMT 3 +#define IW_AUTH_TKIP_COUNTERMEASURES 4 +#define IW_AUTH_DROP_UNENCRYPTED 5 +#define IW_AUTH_80211_AUTH_ALG 6 +#define IW_AUTH_WPA_ENABLED 7 +#define IW_AUTH_RX_UNENCRYPTED_EAPOL 8 +#define IW_AUTH_ROAMING_CONTROL 9 +#define IW_AUTH_PRIVACY_INVOKED 10 +#define IW_AUTH_CIPHER_GROUP_MGMT 11 +#define IW_AUTH_MFP 12 + +/* IW_AUTH_WPA_VERSION values (bit field) */ +#define IW_AUTH_WPA_VERSION_DISABLED 0x00000001 +#define IW_AUTH_WPA_VERSION_WPA 0x00000002 +#define IW_AUTH_WPA_VERSION_WPA2 0x00000004 + +/* IW_AUTH_PAIRWISE_CIPHER, IW_AUTH_GROUP_CIPHER, and IW_AUTH_CIPHER_GROUP_MGMT + * values (bit field) */ +#define IW_AUTH_CIPHER_NONE 0x00000001 +#define IW_AUTH_CIPHER_WEP40 0x00000002 +#define IW_AUTH_CIPHER_TKIP 0x00000004 +#define IW_AUTH_CIPHER_CCMP 0x00000008 +#define IW_AUTH_CIPHER_WEP104 0x00000010 +#define IW_AUTH_CIPHER_AES_CMAC 0x00000020 + +/* IW_AUTH_KEY_MGMT values (bit field) */ +#define IW_AUTH_KEY_MGMT_802_1X 1 +#define IW_AUTH_KEY_MGMT_PSK 2 + +/* IW_AUTH_80211_AUTH_ALG values (bit field) */ +#define IW_AUTH_ALG_OPEN_SYSTEM 0x00000001 +#define IW_AUTH_ALG_SHARED_KEY 0x00000002 +#define IW_AUTH_ALG_LEAP 0x00000004 + +/* IW_AUTH_ROAMING_CONTROL values */ +#define IW_AUTH_ROAMING_ENABLE 0 /* driver/firmware based roaming */ +#define IW_AUTH_ROAMING_DISABLE 1 /* user space program used for roaming + * control */ + +/* IW_AUTH_MFP (management frame protection) values */ +#define IW_AUTH_MFP_DISABLED 0 /* MFP disabled */ +#define IW_AUTH_MFP_OPTIONAL 1 /* MFP optional */ +#define IW_AUTH_MFP_REQUIRED 2 /* MFP required */ + +/* SIOCSIWENCODEEXT definitions */ +#define IW_ENCODE_SEQ_MAX_SIZE 8 +/* struct iw_encode_ext ->alg */ +#define IW_ENCODE_ALG_NONE 0 +#define IW_ENCODE_ALG_WEP 1 +#define IW_ENCODE_ALG_TKIP 2 +#define IW_ENCODE_ALG_CCMP 3 +#define IW_ENCODE_ALG_PMK 4 +#define IW_ENCODE_ALG_AES_CMAC 5 +/* struct iw_encode_ext ->ext_flags */ +#define IW_ENCODE_EXT_TX_SEQ_VALID 0x00000001 +#define IW_ENCODE_EXT_RX_SEQ_VALID 0x00000002 +#define IW_ENCODE_EXT_GROUP_KEY 0x00000004 +#define IW_ENCODE_EXT_SET_TX_KEY 0x00000008 + +/* IWEVMICHAELMICFAILURE : struct iw_michaelmicfailure ->flags */ +#define IW_MICFAILURE_KEY_ID 0x00000003 /* Key ID 0..3 */ +#define IW_MICFAILURE_GROUP 0x00000004 +#define IW_MICFAILURE_PAIRWISE 0x00000008 +#define IW_MICFAILURE_STAKEY 0x00000010 +#define IW_MICFAILURE_COUNT 0x00000060 /* 1 or 2 (0 = count not supported) + */ + +/* Bit field values for enc_capa in struct iw_range */ +#define IW_ENC_CAPA_WPA 0x00000001 +#define IW_ENC_CAPA_WPA2 0x00000002 +#define IW_ENC_CAPA_CIPHER_TKIP 0x00000004 +#define IW_ENC_CAPA_CIPHER_CCMP 0x00000008 +#define IW_ENC_CAPA_4WAY_HANDSHAKE 0x00000010 + +/* Event capability macros - in (struct iw_range *)->event_capa + * Because we have more than 32 possible events, we use an array of + * 32 bit bitmasks. Note : 32 bits = 0x20 = 2^5. */ +#define IW_EVENT_CAPA_BASE(cmd) ((cmd >= SIOCIWFIRSTPRIV) ? \ + (cmd - SIOCIWFIRSTPRIV + 0x60) : \ + (cmd - SIOCIWFIRST)) +#define IW_EVENT_CAPA_INDEX(cmd) (IW_EVENT_CAPA_BASE(cmd) >> 5) +#define IW_EVENT_CAPA_MASK(cmd) (1 << (IW_EVENT_CAPA_BASE(cmd) & 0x1F)) +/* Event capability constants - event autogenerated by the kernel + * This list is valid for most 802.11 devices, customise as needed... */ +#define IW_EVENT_CAPA_K_0 (IW_EVENT_CAPA_MASK(0x8B04) | \ + IW_EVENT_CAPA_MASK(0x8B06) | \ + IW_EVENT_CAPA_MASK(0x8B1A)) +#define IW_EVENT_CAPA_K_1 (IW_EVENT_CAPA_MASK(0x8B2A)) +/* "Easy" macro to set events in iw_range (less efficient) */ +#define IW_EVENT_CAPA_SET(event_capa, cmd) (event_capa[IW_EVENT_CAPA_INDEX(cmd)] |= IW_EVENT_CAPA_MASK(cmd)) +#define IW_EVENT_CAPA_SET_KERNEL(event_capa) {event_capa[0] |= IW_EVENT_CAPA_K_0; event_capa[1] |= IW_EVENT_CAPA_K_1; } + + +/****************************** TYPES ******************************/ + +/* --------------------------- SUBTYPES --------------------------- */ +/* + * Generic format for most parameters that fit in an int + */ +struct iw_param { + __s32 value; /* The value of the parameter itself */ + __u8 fixed; /* Hardware should not use auto select */ + __u8 disabled; /* Disable the feature */ + __u16 flags; /* Various specifc flags (if any) */ +}; + +/* + * For all data larger than 16 octets, we need to use a + * pointer to memory allocated in user space. + */ +struct iw_point { + void *pointer; /* Pointer to the data (in user space) */ + __u16 length; /* number of fields or size in bytes */ + __u16 flags; /* Optional params */ +}; + + +/* + * A frequency + * For numbers lower than 10^9, we encode the number in 'm' and + * set 'e' to 0 + * For number greater than 10^9, we divide it by the lowest power + * of 10 to get 'm' lower than 10^9, with 'm'= f / (10^'e')... + * The power of 10 is in 'e', the result of the division is in 'm'. + */ +struct iw_freq { + __s32 m; /* Mantissa */ + __s16 e; /* Exponent */ + __u8 i; /* List index (when in range struct) */ + __u8 flags; /* Flags (fixed/auto) */ +}; + +/* + * Quality of the link + */ +struct iw_quality { + __u8 qual; /* link quality (%retries, SNR, + %missed beacons or better...) */ + __u8 level; /* signal level (dBm) */ + __u8 noise; /* noise level (dBm) */ + __u8 updated; /* Flags to know if updated */ +}; + +/* + * Packet discarded in the wireless adapter due to + * "wireless" specific problems... + * Note : the list of counter and statistics in net_device_stats + * is already pretty exhaustive, and you should use that first. + * This is only additional stats... + */ +struct iw_discarded { + __u32 nwid; /* Rx : Wrong nwid/essid */ + __u32 code; /* Rx : Unable to code/decode (WEP) */ + __u32 fragment; /* Rx : Can't perform MAC reassembly */ + __u32 retries; /* Tx : Max MAC retries num reached */ + __u32 misc; /* Others cases */ +}; + +/* + * Packet/Time period missed in the wireless adapter due to + * "wireless" specific problems... + */ +struct iw_missed { + __u32 beacon; /* Missed beacons/superframe */ +}; + +/* + * Quality range (for spy threshold) + */ +struct iw_thrspy { + struct sockaddr addr; /* Source address (hw/mac) */ + struct iw_quality qual; /* Quality of the link */ + struct iw_quality low; /* Low threshold */ + struct iw_quality high; /* High threshold */ +}; + +/* + * Optional data for scan request + * + * Note: these optional parameters are controlling parameters for the + * scanning behavior, these do not apply to getting scan results + * (SIOCGIWSCAN). Drivers are expected to keep a local BSS table and + * provide a merged results with all BSSes even if the previous scan + * request limited scanning to a subset, e.g., by specifying an SSID. + * Especially, scan results are required to include an entry for the + * current BSS if the driver is in Managed mode and associated with an AP. + */ +struct iw_scan_req { + __u8 scan_type; /* IW_SCAN_TYPE_{ACTIVE,PASSIVE} */ + __u8 essid_len; + __u8 num_channels; /* num entries in channel_list; + * 0 = scan all allowed channels */ + __u8 flags; /* reserved as padding; use zero, this may + * be used in the future for adding flags + * to request different scan behavior */ + struct sockaddr bssid; /* ff:ff:ff:ff:ff:ff for broadcast BSSID or + * individual address of a specific BSS */ + + /* + * Use this ESSID if IW_SCAN_THIS_ESSID flag is used instead of using + * the current ESSID. This allows scan requests for specific ESSID + * without having to change the current ESSID and potentially breaking + * the current association. + */ + __u8 essid[IW_ESSID_MAX_SIZE]; + + /* + * Optional parameters for changing the default scanning behavior. + * These are based on the MLME-SCAN.request from IEEE Std 802.11. + * TU is 1.024 ms. If these are set to 0, driver is expected to use + * reasonable default values. min_channel_time defines the time that + * will be used to wait for the first reply on each channel. If no + * replies are received, next channel will be scanned after this. If + * replies are received, total time waited on the channel is defined by + * max_channel_time. + */ + __u32 min_channel_time; /* in TU */ + __u32 max_channel_time; /* in TU */ + + struct iw_freq channel_list[IW_MAX_FREQUENCIES]; +}; + +/* ------------------------- WPA SUPPORT ------------------------- */ + +/* + * Extended data structure for get/set encoding (this is used with + * SIOCSIWENCODEEXT/SIOCGIWENCODEEXT. struct iw_point and IW_ENCODE_* + * flags are used in the same way as with SIOCSIWENCODE/SIOCGIWENCODE and + * only the data contents changes (key data -> this structure, including + * key data). + * + * If the new key is the first group key, it will be set as the default + * TX key. Otherwise, default TX key index is only changed if + * IW_ENCODE_EXT_SET_TX_KEY flag is set. + * + * Key will be changed with SIOCSIWENCODEEXT in all cases except for + * special "change TX key index" operation which is indicated by setting + * key_len = 0 and ext_flags |= IW_ENCODE_EXT_SET_TX_KEY. + * + * tx_seq/rx_seq are only used when respective + * IW_ENCODE_EXT_{TX,RX}_SEQ_VALID flag is set in ext_flags. Normal + * TKIP/CCMP operation is to set RX seq with SIOCSIWENCODEEXT and start + * TX seq from zero whenever key is changed. SIOCGIWENCODEEXT is normally + * used only by an Authenticator (AP or an IBSS station) to get the + * current TX sequence number. Using TX_SEQ_VALID for SIOCSIWENCODEEXT and + * RX_SEQ_VALID for SIOCGIWENCODEEXT are optional, but can be useful for + * debugging/testing. + */ +struct iw_encode_ext { + __u32 ext_flags; /* IW_ENCODE_EXT_* */ + __u8 tx_seq[IW_ENCODE_SEQ_MAX_SIZE]; /* LSB first */ + __u8 rx_seq[IW_ENCODE_SEQ_MAX_SIZE]; /* LSB first */ + struct sockaddr addr; /* ff:ff:ff:ff:ff:ff for broadcast/multicast + * (group) keys or unicast address for + * individual keys */ + __u16 alg; /* IW_ENCODE_ALG_* */ + __u16 key_len; + __u8 key[0]; +}; + +/* SIOCSIWMLME data */ +struct iw_mlme { + __u16 cmd; /* IW_MLME_* */ + __u16 reason_code; + struct sockaddr addr; +}; + +/* SIOCSIWPMKSA data */ +#define IW_PMKSA_ADD 1 +#define IW_PMKSA_REMOVE 2 +#define IW_PMKSA_FLUSH 3 + +#define IW_PMKID_LEN 16 + +struct iw_pmksa { + __u32 cmd; /* IW_PMKSA_* */ + struct sockaddr bssid; + __u8 pmkid[IW_PMKID_LEN]; +}; + +/* IWEVMICHAELMICFAILURE data */ +struct iw_michaelmicfailure { + __u32 flags; + struct sockaddr src_addr; + __u8 tsc[IW_ENCODE_SEQ_MAX_SIZE]; /* LSB first */ +}; + +/* IWEVPMKIDCAND data */ +#define IW_PMKID_CAND_PREAUTH 0x00000001 /* RNS pre-authentication enabled */ +struct iw_pmkid_cand { + __u32 flags; /* IW_PMKID_CAND_* */ + __u32 index; /* the smaller the index, the higher the + * priority */ + struct sockaddr bssid; +}; + +/* ------------------------ WIRELESS STATS ------------------------ */ +/* + * Wireless statistics (used for /proc/net/wireless) + */ +struct iw_statistics { + __u16 status; /* Status + * - device dependent for now */ + + struct iw_quality qual; /* Quality of the link + * (instant/mean/max) */ + struct iw_discarded discard; /* Packet discarded counts */ + struct iw_missed miss; /* Packet missed counts */ +}; + +/* ------------------------ IOCTL REQUEST ------------------------ */ +/* + * This structure defines the payload of an ioctl, and is used + * below. + * + * Note that this structure should fit on the memory footprint + * of iwreq (which is the same as ifreq), which mean a max size of + * 16 octets = 128 bits. Warning, pointers might be 64 bits wide... + * You should check this when increasing the structures defined + * above in this file... + */ +union iwreq_data { + /* Config - generic */ + char name[IFNAMSIZ]; + /* Name : used to verify the presence of wireless extensions. + * Name of the protocol/provider... */ + + struct iw_point essid; /* Extended network name */ + struct iw_param nwid; /* network id (or domain - the cell) */ + struct iw_freq freq; /* frequency or channel : + * 0-1000 = channel + * > 1000 = frequency in Hz */ + + struct iw_param sens; /* signal level threshold */ + struct iw_param bitrate; /* default bit rate */ + struct iw_param txpower; /* default transmit power */ + struct iw_param rts; /* RTS threshold threshold */ + struct iw_param frag; /* Fragmentation threshold */ + __u32 mode; /* Operation mode */ + struct iw_param retry; /* Retry limits & lifetime */ + + struct iw_point encoding; /* Encoding stuff : tokens */ + struct iw_param power; /* PM duration/timeout */ + struct iw_quality qual; /* Quality part of statistics */ + + struct sockaddr ap_addr; /* Access point address */ + struct sockaddr addr; /* Destination address (hw/mac) */ + + struct iw_param param; /* Other small parameters */ + struct iw_point data; /* Other large parameters */ +}; + +/* + * The structure to exchange data for ioctl. + * This structure is the same as 'struct ifreq', but (re)defined for + * convenience... + * Do I need to remind you about structure size (32 octets) ? + */ +struct iwreq { + union + { + char ifrn_name[IFNAMSIZ]; /* if name, e.g. "eth0" */ + } ifr_ifrn; + + /* Data part (defined just above) */ + union iwreq_data u; +}; + +/* -------------------------- IOCTL DATA -------------------------- */ +/* + * For those ioctl which want to exchange mode data that what could + * fit in the above structure... + */ + +/* + * Range of parameters + */ + +struct iw_range { + /* Informative stuff (to choose between different interface) */ + __u32 throughput; /* To give an idea... */ + /* In theory this value should be the maximum benchmarked + * TCP/IP throughput, because with most of these devices the + * bit rate is meaningless (overhead an co) to estimate how + * fast the connection will go and pick the fastest one. + * I suggest people to play with Netperf or any benchmark... + */ + + /* NWID (or domain id) */ + __u32 min_nwid; /* Minimal NWID we are able to set */ + __u32 max_nwid; /* Maximal NWID we are able to set */ + + /* Old Frequency (backward compat - moved lower ) */ + __u16 old_num_channels; + __u8 old_num_frequency; + + /* Scan capabilities */ + __u8 scan_capa; /* IW_SCAN_CAPA_* bit field */ + + /* Wireless event capability bitmasks */ + __u32 event_capa[6]; + + /* signal level threshold range */ + __s32 sensitivity; + + /* Quality of link & SNR stuff */ + /* Quality range (link, level, noise) + * If the quality is absolute, it will be in the range [0 ; max_qual], + * if the quality is dBm, it will be in the range [max_qual ; 0]. + * Don't forget that we use 8 bit arithmetics... */ + struct iw_quality max_qual; /* Quality of the link */ + /* This should contain the average/typical values of the quality + * indicator. This should be the threshold between a "good" and + * a "bad" link (example : monitor going from green to orange). + * Currently, user space apps like quality monitors don't have any + * way to calibrate the measurement. With this, they can split + * the range between 0 and max_qual in different quality level + * (using a geometric subdivision centered on the average). + * I expect that people doing the user space apps will feedback + * us on which value we need to put in each driver... */ + struct iw_quality avg_qual; /* Quality of the link */ + + /* Rates */ + __u8 num_bitrates; /* Number of entries in the list */ + __s32 bitrate[IW_MAX_BITRATES]; /* list, in bps */ + + /* RTS threshold */ + __s32 min_rts; /* Minimal RTS threshold */ + __s32 max_rts; /* Maximal RTS threshold */ + + /* Frag threshold */ + __s32 min_frag; /* Minimal frag threshold */ + __s32 max_frag; /* Maximal frag threshold */ + + /* Power Management duration & timeout */ + __s32 min_pmp; /* Minimal PM period */ + __s32 max_pmp; /* Maximal PM period */ + __s32 min_pmt; /* Minimal PM timeout */ + __s32 max_pmt; /* Maximal PM timeout */ + __u16 pmp_flags; /* How to decode max/min PM period */ + __u16 pmt_flags; /* How to decode max/min PM timeout */ + __u16 pm_capa; /* What PM options are supported */ + + /* Encoder stuff */ + __u16 encoding_size[IW_MAX_ENCODING_SIZES]; /* Different token sizes */ + __u8 num_encoding_sizes; /* Number of entry in the list */ + __u8 max_encoding_tokens; /* Max number of tokens */ + /* For drivers that need a "login/passwd" form */ + __u8 encoding_login_index; /* token index for login token */ + + /* Transmit power */ + __u16 txpower_capa; /* What options are supported */ + __u8 num_txpower; /* Number of entries in the list */ + __s32 txpower[IW_MAX_TXPOWER]; /* list, in bps */ + + /* Wireless Extension version info */ + __u8 we_version_compiled; /* Must be WIRELESS_EXT */ + __u8 we_version_source; /* Last update of source */ + + /* Retry limits and lifetime */ + __u16 retry_capa; /* What retry options are supported */ + __u16 retry_flags; /* How to decode max/min retry limit */ + __u16 r_time_flags; /* How to decode max/min retry life */ + __s32 min_retry; /* Minimal number of retries */ + __s32 max_retry; /* Maximal number of retries */ + __s32 min_r_time; /* Minimal retry lifetime */ + __s32 max_r_time; /* Maximal retry lifetime */ + + /* Frequency */ + __u16 num_channels; /* Number of channels [0; num - 1] */ + __u8 num_frequency; /* Number of entry in the list */ + struct iw_freq freq[IW_MAX_FREQUENCIES]; /* list */ + /* Note : this frequency list doesn't need to fit channel numbers, + * because each entry contain its channel index */ + + __u32 enc_capa; /* IW_ENC_CAPA_* bit field */ +}; + +/* + * Private ioctl interface information + */ + +struct iw_priv_args { + __u32 cmd; /* Number of the ioctl to issue */ + __u16 set_args; /* Type and number of args */ + __u16 get_args; /* Type and number of args */ + char name[IFNAMSIZ]; /* Name of the extension */ +}; + +/* ----------------------- WIRELESS EVENTS ----------------------- */ +/* + * Wireless events are carried through the rtnetlink socket to user + * space. They are encapsulated in the IFLA_WIRELESS field of + * a RTM_NEWLINK message. + */ + +/* + * A Wireless Event. Contains basically the same data as the ioctl... + */ +struct iw_event { + __u16 len; /* Real length of this stuff */ + __u16 cmd; /* Wireless IOCTL */ + union iwreq_data u; /* IOCTL fixed payload */ +}; + +/* Size of the Event prefix (including padding and alignement junk) */ +#define IW_EV_LCP_LEN (sizeof(struct iw_event) - sizeof(union iwreq_data)) +/* Size of the various events */ +#define IW_EV_CHAR_LEN (IW_EV_LCP_LEN + IFNAMSIZ) +#define IW_EV_UINT_LEN (IW_EV_LCP_LEN + sizeof(__u32)) +#define IW_EV_FREQ_LEN (IW_EV_LCP_LEN + sizeof(struct iw_freq)) +#define IW_EV_PARAM_LEN (IW_EV_LCP_LEN + sizeof(struct iw_param)) +#define IW_EV_ADDR_LEN (IW_EV_LCP_LEN + sizeof(struct sockaddr)) +#define IW_EV_QUAL_LEN (IW_EV_LCP_LEN + sizeof(struct iw_quality)) + +/* iw_point events are special. First, the payload (extra data) come at + * the end of the event, so they are bigger than IW_EV_POINT_LEN. Second, + * we omit the pointer, so start at an offset. */ +#define IW_EV_POINT_OFF (((char *) &(((struct iw_point *) NULL)->length)) - \ + (char *) NULL) +#define IW_EV_POINT_LEN (IW_EV_LCP_LEN + sizeof(struct iw_point) - \ + IW_EV_POINT_OFF) + + +/* Size of the Event prefix when packed in stream */ +#define IW_EV_LCP_PK_LEN (4) +/* Size of the various events when packed in stream */ +#define IW_EV_CHAR_PK_LEN (IW_EV_LCP_PK_LEN + IFNAMSIZ) +#define IW_EV_UINT_PK_LEN (IW_EV_LCP_PK_LEN + sizeof(__u32)) +#define IW_EV_FREQ_PK_LEN (IW_EV_LCP_PK_LEN + sizeof(struct iw_freq)) +#define IW_EV_PARAM_PK_LEN (IW_EV_LCP_PK_LEN + sizeof(struct iw_param)) +#define IW_EV_ADDR_PK_LEN (IW_EV_LCP_PK_LEN + sizeof(struct sockaddr)) +#define IW_EV_QUAL_PK_LEN (IW_EV_LCP_PK_LEN + sizeof(struct iw_quality)) +#define IW_EV_POINT_PK_LEN (IW_EV_LCP_PK_LEN + 4) + +#endif /* _LINUX_WIRELESS_H */ diff --git a/include/netinet/if_ether.h b/include/netinet/if_ether.h new file mode 100644 index 0000000000000000000000000000000000000000..3e3068a7c304e1a620eef1e635ba05bfde98504c --- /dev/null +++ b/include/netinet/if_ether.h @@ -0,0 +1,89 @@ +/* $OpenBSD: if_ether.h,v 1.47 2010/02/08 13:32:50 claudio Exp $ */ +/* $NetBSD: if_ether.h,v 1.22 1996/05/11 13:00:00 mycroft Exp $ */ + +/* + * Copyright (c) 1982, 1986, 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. + * + * @(#)if_ether.h 8.1 (Berkeley) 6/10/93 + */ + +#ifndef _NETINET_IF_ETHER_H_ +#define _NETINET_IF_ETHER_H_ + +/* + * Some basic Ethernet constants. + */ +#define ETHER_ADDR_LEN 6 /* Ethernet address length */ +#define ETHER_TYPE_LEN 2 /* Ethernet type field length */ +#define ETHER_CRC_LEN 4 /* Ethernet CRC length */ +#define ETHER_HDR_LEN ((ETHER_ADDR_LEN * 2) + ETHER_TYPE_LEN) +#define ETHER_MIN_LEN 64 /* Minimum frame length, CRC included */ +#define ETHER_MAX_LEN 1518 /* Maximum frame length, CRC included */ +#define ETHER_MAX_DIX_LEN 1536 /* Maximum DIX frame length */ + +/* + * Some Ethernet extensions. + */ +#define ETHER_VLAN_ENCAP_LEN 4 /* len of 802.1Q VLAN encapsulation */ + +/* + * Mbuf adjust factor to force 32-bit alignment of IP header. + * Drivers should do m_adj(m, ETHER_ALIGN) when setting up a + * receive so the upper layers get the IP header properly aligned + * past the 14-byte Ethernet header. + */ +#define ETHER_ALIGN 2 /* driver adjust for IP hdr alignment */ + +/* + * Ethernet address - 6 octets + */ +struct ether_addr { + u_int8_t ether_addr_octet[ETHER_ADDR_LEN]; +}; + +/* + * The length of the combined header. + */ +struct ether_header { + u_int8_t ether_dhost[ETHER_ADDR_LEN]; + u_int8_t ether_shost[ETHER_ADDR_LEN]; + u_int16_t ether_type; +}; + +#define ETHERTYPE_LLDP 0x88CC /* Link Layer Discovery Protocol */ + +#define ETHERMTU (ETHER_MAX_LEN - ETHER_HDR_LEN - ETHER_CRC_LEN) +#define ETHERMIN (ETHER_MIN_LEN - ETHER_HDR_LEN - ETHER_CRC_LEN) + +/* + * Ethernet CRC32 polynomials (big- and little-endian verions). + */ +#define ETHER_CRC_POLY_LE 0xedb88320 +#define ETHER_CRC_POLY_BE 0x04c11db6 + +#endif /* _NETINET_IF_ETHER_H_ */ diff --git a/include/osx/README.md b/include/osx/README.md new file mode 100644 index 0000000000000000000000000000000000000000..316fbe5e157d36841b658706ef2569b4c1b8542e --- /dev/null +++ b/include/osx/README.md @@ -0,0 +1,6 @@ +Those include headers have been stolen from `xnu-2050.18.24.tar.gz` +and slightly modified. They should be shipped with the OS or the +development tools but they are not. They are from Mac OS X 10.8.2 but +should work with previous versions just fine. + + http://www.opensource.apple.com/source/xnu/xnu-2050.18.24/ diff --git a/include/osx/if_bond_var.h b/include/osx/if_bond_var.h new file mode 100644 index 0000000000000000000000000000000000000000..73f457ec1593b51553e6067554de5db278e26117 --- /dev/null +++ b/include/osx/if_bond_var.h @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2004 Apple Computer, Inc. All rights reserved. + * + * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. The rights granted to you under the License + * may not be used to create, or enable the creation or redistribution of, + * unlawful or unlicensed copies of an Apple operating system, or to + * circumvent, violate, or enable the circumvention or violation of, any + * terms of an Apple operating system software license agreement. + * + * Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_OSREFERENCE_LICENSE_HEADER_END@ + */ + +#ifndef _NET_IF_BOND_VAR_H_ +#define _NET_IF_BOND_VAR_H_ + +#include + +#include + +#pragma pack(4) + +#define IF_BOND_OP_ADD_INTERFACE 1 +#define IF_BOND_OP_REMOVE_INTERFACE 2 +#define IF_BOND_OP_GET_STATUS 3 +#define IF_BOND_OP_SET_VERBOSE 4 +#define IF_BOND_OP_SET_MODE 5 + +#define IF_BOND_MODE_LACP 0 +#define IF_BOND_MODE_STATIC 1 + +struct if_bond_partner_state { + lacp_system ibps_system; + lacp_system_priority ibps_system_priority; + lacp_key ibps_key; + lacp_port ibps_port; + lacp_port_priority ibps_port_priority; + lacp_actor_partner_state ibps_state; + u_char ibps_reserved1; +}; + +#define IF_BOND_STATUS_SELECTED_STATE_UNSELECTED 0 +#define IF_BOND_STATUS_SELECTED_STATE_SELECTED 1 +#define IF_BOND_STATUS_SELECTED_STATE_STANDBY 2 + +struct if_bond_status { + char ibs_if_name[IFNAMSIZ]; /* interface name */ + lacp_port_priority ibs_port_priority; + lacp_actor_partner_state ibs_state; + u_char ibs_selected_state; + struct if_bond_partner_state ibs_partner_state; + u_int32_t ibs_reserved[8]; +}; + +#define IF_BOND_STATUS_REQ_VERSION 1 + +struct if_bond_status_req { + int ibsr_version; /* version */ + int ibsr_total; /* returned number of struct if_bond_status's */ + int ibsr_count; /* number that will fit in ibsr_buffer */ + union { /* buffer to hold if_bond_status's */ + void * ibsru_buffer; + u_int64_t ibsru_buffer64; + } ibsr_ibsru; + lacp_key ibsr_key; /* returned */ + u_int8_t ibsr_mode; /* returned (IF_BOND_MODE_{LACP, STATIC}) */ + u_int8_t ibsr_reserved0; /* for future use */ + u_int32_t ibsr_reserved[3];/* for future use */ +}; +#define ibsr_buffer ibsr_ibsru.ibsru_buffer + +struct if_bond_req { + u_int32_t ibr_op; /* operation */ + union { + char ibru_if_name[IFNAMSIZ]; /* interface name */ + struct if_bond_status_req ibru_status; /* status information */ + int ibru_int_val; + } ibr_ibru; +}; + +#pragma pack() + +#endif /* _NET_IF_BOND_VAR_H_ */ diff --git a/include/osx/if_bridgevar.h b/include/osx/if_bridgevar.h new file mode 100644 index 0000000000000000000000000000000000000000..cc7381f82bac9a710a30968e8a2ea33ef3236c72 --- /dev/null +++ b/include/osx/if_bridgevar.h @@ -0,0 +1,497 @@ +/* $NetBSD: if_bridgevar.h,v 1.4 2003/07/08 07:13:50 itojun Exp $ */ +/* + * Copyright (c) 2004-2010 Apple Inc. All rights reserved. + * + * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. The rights granted to you under the License + * may not be used to create, or enable the creation or redistribution of, + * unlawful or unlicensed copies of an Apple operating system, or to + * circumvent, violate, or enable the circumvention or violation of, any + * terms of an Apple operating system software license agreement. + * + * Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_OSREFERENCE_LICENSE_HEADER_END@ + */ + +/* + * Copyright 2001 Wasabi Systems, Inc. + * All rights reserved. + * + * Written by Jason R. Thorpe for Wasabi Systems, Inc. + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed for the NetBSD Project by + * Wasabi Systems, Inc. + * 4. The name of Wasabi Systems, Inc. may not be used to endorse + * or promote products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY WASABI SYSTEMS, INC. ``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 WASABI SYSTEMS, INC + * 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. + */ + +/* + * Copyright (c) 1999, 2000 Jason L. Wright (jason@thought.net) + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Jason L. Wright + * 4. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * 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. + * + * OpenBSD: if_bridge.h,v 1.14 2001/03/22 03:48:29 jason Exp + * + * $FreeBSD$ + */ + +/* + * Data structure and control definitions for bridge interfaces. + */ + +#ifndef _NET_IF_BRIDGEVAR_H_ +#define _NET_IF_BRIDGEVAR_H_ + +#include + +#include + +/* + * Commands used in the SIOCSDRVSPEC ioctl. Note the lookup of the + * bridge interface itself is keyed off the ifdrv structure. + */ +#define BRDGADD 0 /* add bridge member (ifbreq) */ +#define BRDGDEL 1 /* delete bridge member (ifbreq) */ +#define BRDGGIFFLGS 2 /* get member if flags (ifbreq) */ +#define BRDGSIFFLGS 3 /* set member if flags (ifbreq) */ +#define BRDGSCACHE 4 /* set cache size (ifbrparam) */ +#define BRDGGCACHE 5 /* get cache size (ifbrparam) */ +#define BRDGGIFS 6 /* get member list (ifbifconf) */ +#define BRDGRTS 7 /* get address list (ifbaconf) */ +#define BRDGSADDR 8 /* set static address (ifbareq) */ +#define BRDGSTO 9 /* set cache timeout (ifbrparam) */ +#define BRDGGTO 10 /* get cache timeout (ifbrparam) */ +#define BRDGDADDR 11 /* delete address (ifbareq) */ +#define BRDGFLUSH 12 /* flush address cache (ifbreq) */ + +#define BRDGGPRI 13 /* get priority (ifbrparam) */ +#define BRDGSPRI 14 /* set priority (ifbrparam) */ +#define BRDGGHT 15 /* get hello time (ifbrparam) */ +#define BRDGSHT 16 /* set hello time (ifbrparam) */ +#define BRDGGFD 17 /* get forward delay (ifbrparam) */ +#define BRDGSFD 18 /* set forward delay (ifbrparam) */ +#define BRDGGMA 19 /* get max age (ifbrparam) */ +#define BRDGSMA 20 /* set max age (ifbrparam) */ +#define BRDGSIFPRIO 21 /* set if priority (ifbreq) */ +#define BRDGSIFCOST 22 /* set if path cost (ifbreq) */ +#define BRDGGFILT 23 /* get filter flags (ifbrparam) */ +#define BRDGSFILT 24 /* set filter flags (ifbrparam) */ +#define BRDGPURGE 25 /* purge address cache for a particular interface (ifbreq) */ +#define BRDGADDS 26 /* add bridge span member (ifbreq) */ +#define BRDGDELS 27 /* delete bridge span member (ifbreq) */ +#define BRDGPARAM 28 /* get bridge STP params (ifbropreq) */ +#define BRDGGRTE 29 /* get cache drops (ifbrparam) */ +#define BRDGGIFSSTP 30 /* get member STP params list (ifbpstpconf) */ +#define BRDGSPROTO 31 /* set protocol (ifbrparam) */ +#define BRDGSTXHC 32 /* set tx hold count (ifbrparam) */ +#define BRDGSIFAMAX 33 /* set max interface addrs (ifbreq) */ + +/* + * Generic bridge control request. + */ +#pragma pack(4) + +struct ifbreq { + char ifbr_ifsname[IFNAMSIZ]; /* member if name */ + uint32_t ifbr_ifsflags; /* member if flags */ + uint32_t ifbr_stpflags; /* member if STP flags */ + uint32_t ifbr_path_cost; /* member if STP cost */ + uint8_t ifbr_portno; /* member if port number */ + uint8_t ifbr_priority; /* member if STP priority */ + uint8_t ifbr_proto; /* member if STP protocol */ + uint8_t ifbr_role; /* member if STP role */ + uint8_t ifbr_state; /* member if STP state */ + uint32_t ifbr_addrcnt; /* member if addr number */ + uint32_t ifbr_addrmax; /* member if addr max */ + uint32_t ifbr_addrexceeded; /* member if addr violations */ + uint8_t pad[32]; +}; + +#pragma pack() + +/* BRDGGIFFLAGS, BRDGSIFFLAGS */ +#define IFBIF_LEARNING 0x0001 /* if can learn */ +#define IFBIF_DISCOVER 0x0002 /* if sends packets w/ unknown dest. */ +#define IFBIF_STP 0x0004 /* if participates in spanning tree */ +#define IFBIF_SPAN 0x0008 /* if is a span port */ +#define IFBIF_STICKY 0x0010 /* if learned addresses stick */ +#define IFBIF_BSTP_EDGE 0x0020 /* member stp edge port */ +#define IFBIF_BSTP_AUTOEDGE 0x0040 /* member stp autoedge enabled */ +#define IFBIF_BSTP_PTP 0x0080 /* member stp point to point */ +#define IFBIF_BSTP_AUTOPTP 0x0100 /* member stp autoptp enabled */ +#define IFBIF_BSTP_ADMEDGE 0x0200 /* member stp admin edge enabled */ +#define IFBIF_BSTP_ADMCOST 0x0400 /* member stp admin path cost */ +#define IFBIF_PRIVATE 0x0800 /* if is a private segment */ + +#define IFBIFBITS "\020\001LEARNING\002DISCOVER\003STP\004SPAN" \ + "\005STICKY\014PRIVATE\006EDGE\007AUTOEDGE\010PTP" \ + "\011AUTOPTP" +#define IFBIFMASK ~(IFBIF_BSTP_EDGE|IFBIF_BSTP_AUTOEDGE|IFBIF_BSTP_PTP| \ + IFBIF_BSTP_AUTOPTP|IFBIF_BSTP_ADMEDGE| \ + IFBIF_BSTP_ADMCOST) /* not saved */ + +/* BRDGFLUSH */ +#define IFBF_FLUSHDYN 0x00 /* flush learned addresses only */ +#define IFBF_FLUSHALL 0x01 /* flush all addresses */ + +/* BRDGSFILT */ +#define IFBF_FILT_USEIPF 0x00000001 /* run pfil hooks on the bridge +interface */ +#define IFBF_FILT_MEMBER 0x00000002 /* run pfil hooks on the member +interfaces */ +#define IFBF_FILT_ONLYIP 0x00000004 /* only pass IP[46] packets when +pfil is enabled */ +#define IFBF_FILT_MASK 0x00000007 /* mask of valid values */ + + +/* APPLE MODIFICATION : Default is to pass non-IP packets. */ +#define IFBF_FILT_DEFAULT ( IFBF_FILT_USEIPF | IFBF_FILT_MEMBER ) +#if 0 +#define IFBF_FILT_DEFAULT (IFBF_FILT_USEIPF | \ +IFBF_FILT_MEMBER | \ +IFBF_FILT_ONLYIP) +#endif + +/* + * Interface list structure. + */ + +#pragma pack(4) + +#ifndef XNU_KERNEL_PRIVATE + +struct ifbifconf { + uint32_t ifbic_len; /* buffer size */ + union { + caddr_t ifbicu_buf; + struct ifbreq *ifbicu_req; +#define ifbic_buf ifbic_ifbicu.ifbicu_buf +#define ifbic_req ifbic_ifbicu.ifbicu_req + } ifbic_ifbicu; +}; + +#else /* XNU_KERNEL_PRIVATE */ + +struct ifbifconf32 { + uint32_t ifbic_len; /* buffer size */ + union { + user32_addr_t ifbicu_buf; + user32_addr_t ifbicu_req; +#define ifbic_buf ifbic_ifbicu.ifbicu_buf +#define ifbic_req ifbic_ifbicu.ifbicu_req + } ifbic_ifbicu; +}; + +struct ifbifconf64 { + uint32_t ifbic_len; /* buffer size */ + union { + user64_addr_t ifbicu_buf; + user64_addr_t ifbicu_req; + } ifbic_ifbicu; +}; +#endif /* XNU_KERNEL_PRIVATE */ + +#pragma pack() + +/* + * Bridge address request. + */ + +#pragma pack(4) + +#ifndef XNU_KERNEL_PRIVATE + +struct ifbareq { + char ifba_ifsname[IFNAMSIZ]; /* member if name */ + unsigned long ifba_expire; /* address expire time */ + uint8_t ifba_flags; /* address flags */ + uint8_t ifba_dst[ETHER_ADDR_LEN];/* destination address */ + uint16_t ifba_vlan; /* vlan id */ +}; + +#else /* XNU_KERNEL_PRIVATE */ + +struct ifbareq32 { + char ifba_ifsname[IFNAMSIZ]; /* member if name */ + uint32_t ifba_expire; /* address expire time */ + uint8_t ifba_flags; /* address flags */ + uint8_t ifba_dst[ETHER_ADDR_LEN];/* destination address */ + uint16_t ifba_vlan; /* vlan id */ +}; + +struct ifbareq64 { + char ifba_ifsname[IFNAMSIZ]; /* member if name */ + uint64_t ifba_expire; /* address expire time */ + uint8_t ifba_flags; /* address flags */ + uint8_t ifba_dst[ETHER_ADDR_LEN];/* destination address */ + uint16_t ifba_vlan; /* vlan id */ +}; +#endif /* XNU_KERNEL_PRIVATE */ + +#pragma pack() + +#define IFBAF_TYPEMASK 0x03 /* address type mask */ +#define IFBAF_DYNAMIC 0x00 /* dynamically learned address */ +#define IFBAF_STATIC 0x01 /* static address */ +#define IFBAF_STICKY 0x02 /* sticky address */ + +#define IFBAFBITS "\020\1STATIC\2STICKY" + +/* + * Address list structure. + */ + +#pragma pack(4) + +#ifndef XNU_KERNEL_PRIVATE + +struct ifbaconf { + uint32_t ifbac_len; /* buffer size */ + union { + caddr_t ifbacu_buf; + struct ifbareq *ifbacu_req; +#define ifbac_buf ifbac_ifbacu.ifbacu_buf +#define ifbac_req ifbac_ifbacu.ifbacu_req + } ifbac_ifbacu; +}; + +#else /* XNU_KERNEL_PRIVATE */ + +struct ifbaconf32 { + uint32_t ifbac_len; /* buffer size */ + union { + user32_addr_t ifbacu_buf; + user32_addr_t ifbacu_req; +#define ifbac_buf ifbac_ifbacu.ifbacu_buf +#define ifbac_req ifbac_ifbacu.ifbacu_req + } ifbac_ifbacu; +}; + +struct ifbaconf64 { + uint32_t ifbac_len; /* buffer size */ + union { + user64_addr_t ifbacu_buf; + user64_addr_t ifbacu_req; + } ifbac_ifbacu; +}; +#endif /* XNU_KERNEL_PRIVATE */ + +#pragma pack() + +/* + * Bridge parameter structure. + */ + +#pragma pack(4) + +struct ifbrparam { + union { + uint32_t ifbrpu_int32; + uint16_t ifbrpu_int16; + uint8_t ifbrpu_int8; + } ifbrp_ifbrpu; +}; + +#pragma pack() + +#define ifbrp_csize ifbrp_ifbrpu.ifbrpu_int32 /* cache size */ +#define ifbrp_ctime ifbrp_ifbrpu.ifbrpu_int32 /* cache time (sec) */ +#define ifbrp_prio ifbrp_ifbrpu.ifbrpu_int16 /* bridge priority */ +#define ifbrp_proto ifbrp_ifbrpu.ifbrpu_int8 /* bridge protocol */ +#define ifbrp_txhc ifbrp_ifbrpu.ifbrpu_int8 /* bpdu tx holdcount */ +#define ifbrp_hellotime ifbrp_ifbrpu.ifbrpu_int8 /* hello time (sec) */ +#define ifbrp_fwddelay ifbrp_ifbrpu.ifbrpu_int8 /* fwd time (sec) */ +#define ifbrp_maxage ifbrp_ifbrpu.ifbrpu_int8 /* max age (sec) */ +#define ifbrp_cexceeded ifbrp_ifbrpu.ifbrpu_int32 /* # of cache dropped + * adresses */ +#define ifbrp_filter ifbrp_ifbrpu.ifbrpu_int32 /* filtering flags */ + +/* + * Bridge current operational parameters structure. + */ + +#pragma pack(4) + +#ifndef XNU_KERNEL_PRIVATE + +struct ifbropreq { + uint8_t ifbop_holdcount; + uint8_t ifbop_maxage; + uint8_t ifbop_hellotime; + uint8_t ifbop_fwddelay; + uint8_t ifbop_protocol; + uint16_t ifbop_priority; + uint16_t ifbop_root_port; + uint32_t ifbop_root_path_cost; + uint64_t ifbop_bridgeid; + uint64_t ifbop_designated_root; + uint64_t ifbop_designated_bridge; + struct timeval ifbop_last_tc_time; +}; + +#else /* XNU_KERNEL_PRIVATE */ + +struct ifbropreq32 { + uint8_t ifbop_holdcount; + uint8_t ifbop_maxage; + uint8_t ifbop_hellotime; + uint8_t ifbop_fwddelay; + uint8_t ifbop_protocol; + uint16_t ifbop_priority; + uint16_t ifbop_root_port; + uint32_t ifbop_root_path_cost; + uint64_t ifbop_bridgeid; + uint64_t ifbop_designated_root; + uint64_t ifbop_designated_bridge; + struct timeval ifbop_last_tc_time; +}; + +struct ifbropreq64 { + uint8_t ifbop_holdcount; + uint8_t ifbop_maxage; + uint8_t ifbop_hellotime; + uint8_t ifbop_fwddelay; + uint8_t ifbop_protocol; + uint16_t ifbop_priority; + uint16_t ifbop_root_port; + uint32_t ifbop_root_path_cost; + uint64_t ifbop_bridgeid; + uint64_t ifbop_designated_root; + uint64_t ifbop_designated_bridge; + struct timeval ifbop_last_tc_time; +}; + +#endif + +#pragma pack() + +/* + * Bridge member operational STP params structure. + */ + +#pragma pack(4) + +struct ifbpstpreq { + uint8_t ifbp_portno; /* bp STP port number */ + uint32_t ifbp_fwd_trans; /* bp STP fwd transitions */ + uint32_t ifbp_design_cost; /* bp STP designated cost */ + uint32_t ifbp_design_port; /* bp STP designated port */ + uint64_t ifbp_design_bridge; /* bp STP designated bridge */ + uint64_t ifbp_design_root; /* bp STP designated root */ +}; + +#pragma pack() + +/* + * Bridge STP ports list structure. + */ + +#pragma pack(4) + +#ifndef XNU_KERNEL_PRIVATE + +struct ifbpstpconf { + uint32_t ifbpstp_len; /* buffer size */ + union { + caddr_t ifbpstpu_buf; + struct ifbpstpreq *ifbpstpu_req; + } ifbpstp_ifbpstpu; +#define ifbpstp_buf ifbpstp_ifbpstpu.ifbpstpu_buf +#define ifbpstp_req ifbpstp_ifbpstpu.ifbpstpu_req +}; + +#else /* XNU_KERNEL_PRIVATE */ + +struct ifbpstpconf32 { + uint32_t ifbpstp_len; /* buffer size */ + union { + user32_addr_t ifbpstpu_buf; + user32_addr_t ifbpstpu_req; +#define ifbpstp_buf ifbpstp_ifbpstpu.ifbpstpu_buf +#define ifbpstp_req ifbpstp_ifbpstpu.ifbpstpu_req + } ifbpstp_ifbpstpu; +}; + +struct ifbpstpconf64 { + uint32_t ifbpstp_len; /* buffer size */ + union { + user64_addr_t ifbpstpu_buf; + user64_addr_t ifbpstpu_req; + } ifbpstp_ifbpstpu; +}; + +#endif /* XNU_KERNEL_PRIVATE */ + +#pragma pack() + + +#ifdef XNU_KERNEL_PRIVATE + +extern u_int8_t bstp_etheraddr[ETHER_ADDR_LEN]; + +int bridgeattach(int); + +#endif /* XNU_KERNEL_PRIVATE */ +#endif /* !_NET_IF_BRIDGEVAR_H_ */ diff --git a/include/osx/if_vlan_var.h b/include/osx/if_vlan_var.h new file mode 100644 index 0000000000000000000000000000000000000000..069a81d92a1064730458ad338c08e4bf57f71ce5 --- /dev/null +++ b/include/osx/if_vlan_var.h @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2003 Apple Computer, Inc. All rights reserved. + * + * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. The rights granted to you under the License + * may not be used to create, or enable the creation or redistribution of, + * unlawful or unlicensed copies of an Apple operating system, or to + * circumvent, violate, or enable the circumvention or violation of, any + * terms of an Apple operating system software license agreement. + * + * Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_OSREFERENCE_LICENSE_HEADER_END@ + */ +/* + * Copyright 1998 Massachusetts Institute of Technology + * + * Permission to use, copy, modify, and distribute this software and + * its documentation for any purpose and without fee is hereby + * granted, provided that both the above copyright notice and this + * permission notice appear in all copies, that both the above + * copyright notice and this permission notice appear in all + * supporting documentation, and that the name of M.I.T. not be used + * in advertising or publicity pertaining to distribution of the + * software without specific, written prior permission. M.I.T. makes + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied + * warranty. + * + * THIS SOFTWARE IS PROVIDED BY M.I.T. ``AS IS''. M.I.T. DISCLAIMS + * ALL EXPRESS OR IMPLIED WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT + * SHALL M.I.T. 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. + * + * $FreeBSD: src/sys/net/if_vlan_var.h,v 1.16 2003/07/08 21:54:20 wpaul Exp $ + */ + +#ifndef _NET_IF_VLAN_VAR_H_ +#define _NET_IF_VLAN_VAR_H_ 1 + +#define ETHER_VLAN_ENCAP_LEN 4 /* len of 802.1Q VLAN encapsulation */ +struct ether_vlan_header { + u_char evl_dhost[ETHER_ADDR_LEN]; + u_char evl_shost[ETHER_ADDR_LEN]; + u_int16_t evl_encap_proto; + u_int16_t evl_tag; + u_int16_t evl_proto; +}; + +#define EVL_VLID_MASK 0x0FFF +#define EVL_VLANOFTAG(tag) ((tag) & EVL_VLID_MASK) +#define EVL_PRIOFTAG(tag) (((tag) >> 13) & 7) + +#if 0 +/* sysctl(3) tags, for compatibility purposes */ +#define VLANCTL_PROTO 1 +#define VLANCTL_MAX 2 +#endif + +/* + * Configuration structure for SIOCSETVLAN and SIOCGETVLAN ioctls. + */ +struct vlanreq { + char vlr_parent[IFNAMSIZ]; + u_short vlr_tag; +}; + +#ifdef KERNEL_PRIVATE +int vlan_family_init(void) __attribute__((section("__TEXT, initcode"))); +#endif /* KERNEL_PRIVATE */ +#endif /* _NET_IF_VLAN_VAR_H_ */ diff --git a/include/osx/lacp.h b/include/osx/lacp.h new file mode 100644 index 0000000000000000000000000000000000000000..04c81c167feac888cfcd15544e438c65ed32eebb --- /dev/null +++ b/include/osx/lacp.h @@ -0,0 +1,472 @@ +/* + * Copyright (c) 2004 Apple Computer, Inc. All rights reserved. + * + * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. The rights granted to you under the License + * may not be used to create, or enable the creation or redistribution of, + * unlawful or unlicensed copies of an Apple operating system, or to + * circumvent, violate, or enable the circumvention or violation of, any + * terms of an Apple operating system software license agreement. + * + * Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_OSREFERENCE_LICENSE_HEADER_END@ + */ + +/* + * lacp.h + * - definitions for the Link Aggregation Control Protocol (LACP) and + * the Link Aggregation Marker Protocol + */ + +/* + * Modification History + * + * May 14, 2004 Dieter Siegmund (dieter@apple.com) + * - created + */ + +#ifndef _NET_LACP_H_ +#define _NET_LACP_H_ + +#include +#include + +/** + ** Link Aggregation Control Protocol (LACP) definitions + **/ +#define LACPDU_VERSION_1 1 + +#define LACPDU_TLV_TYPE_TERMINATOR 0x00 +#define LACPDU_TLV_TYPE_ACTOR 0x01 +#define LACPDU_TLV_TYPE_PARTNER 0x02 +#define LACPDU_TLV_TYPE_COLLECTOR 0x03 + +#define LACPDU_ACTOR_TLV_LENGTH 20 +#define LACPDU_PARTNER_TLV_LENGTH 20 +#define LACPDU_COLLECTOR_TLV_LENGTH 16 + +typedef u_char lacp_actor_partner_state; +typedef u_int16_t lacp_key; +typedef u_int16_t lacp_system_priority, lacp_port_priority, lacp_port; +typedef u_int16_t lacp_collector_max_delay; +typedef struct { + u_char system_id[6]; +} lacp_system, *lacp_system_ref; + +/* + * LACP Actor/Partner TLV + */ +typedef struct lacp_actor_partner_tlv_s { + u_char lap_tlv_type; /* 0x01 or 0x02 */ + u_char lap_length; /* 20 */ + u_char lap_system_priority[2]; + u_char lap_system[6]; + u_char lap_key[2]; + u_char lap_port_priority[2]; + u_char lap_port[2]; + u_char lap_state; + u_char lap_reserved[3]; +} lacp_actor_partner_tlv, *lacp_actor_partner_tlv_ref; + +/* + * LACP Collector TLV + */ +typedef struct lacp_collector_tlv_s { + u_char lac_tlv_type; /* 0x03 */ + u_char lac_length; /* 16 */ + u_char lac_max_delay[2]; + u_char lac_reserved[12]; +} lacp_collector_tlv, *lacp_collector_tlv_ref; + + +/* + * LACP Actor/Partner State bits + */ +#define LACP_ACTOR_PARTNER_STATE_LACP_ACTIVITY 0x01 +#define LACP_ACTOR_PARTNER_STATE_LACP_TIMEOUT 0x02 +#define LACP_ACTOR_PARTNER_STATE_AGGREGATION 0x04 +#define LACP_ACTOR_PARTNER_STATE_SYNCHRONIZATION 0x08 +#define LACP_ACTOR_PARTNER_STATE_COLLECTING 0x10 +#define LACP_ACTOR_PARTNER_STATE_DISTRIBUTING 0x20 +#define LACP_ACTOR_PARTNER_STATE_DEFAULTED 0x40 +#define LACP_ACTOR_PARTNER_STATE_EXPIRED 0x80 + +static __inline__ lacp_actor_partner_state +lacp_actor_partner_state_set_active_lacp(lacp_actor_partner_state state) +{ + return (state | LACP_ACTOR_PARTNER_STATE_LACP_ACTIVITY); +} + +static __inline__ lacp_actor_partner_state +lacp_actor_partner_state_set_passive_lacp(lacp_actor_partner_state state) +{ + return (state &= ~LACP_ACTOR_PARTNER_STATE_LACP_ACTIVITY); +} + +static __inline__ int +lacp_actor_partner_state_active_lacp(lacp_actor_partner_state state) +{ + return ((state & LACP_ACTOR_PARTNER_STATE_LACP_ACTIVITY) != 0); +} + +static __inline__ lacp_actor_partner_state +lacp_actor_partner_state_set_short_timeout(lacp_actor_partner_state state) +{ + return (state | LACP_ACTOR_PARTNER_STATE_LACP_TIMEOUT); +} + +static __inline__ lacp_actor_partner_state +lacp_actor_partner_state_set_long_timeout(lacp_actor_partner_state state) +{ + return (state &= ~LACP_ACTOR_PARTNER_STATE_LACP_TIMEOUT); +} + +static __inline__ int +lacp_actor_partner_state_short_timeout(lacp_actor_partner_state state) +{ + return ((state & LACP_ACTOR_PARTNER_STATE_LACP_TIMEOUT) != 0); +} + +static __inline__ lacp_actor_partner_state +lacp_actor_partner_state_set_aggregatable(lacp_actor_partner_state state) +{ + return (state | LACP_ACTOR_PARTNER_STATE_AGGREGATION); +} + +static __inline__ lacp_actor_partner_state +lacp_actor_partner_state_set_individual(lacp_actor_partner_state state) +{ + return (state &= ~LACP_ACTOR_PARTNER_STATE_AGGREGATION); +} + +static __inline__ lacp_actor_partner_state +lacp_actor_partner_state_aggregatable(lacp_actor_partner_state state) +{ + return ((state & LACP_ACTOR_PARTNER_STATE_AGGREGATION) != 0); +} + +static __inline__ lacp_actor_partner_state +lacp_actor_partner_state_set_in_sync(lacp_actor_partner_state state) +{ + return (state | LACP_ACTOR_PARTNER_STATE_SYNCHRONIZATION); +} + +static __inline__ lacp_actor_partner_state +lacp_actor_partner_state_set_out_of_sync(lacp_actor_partner_state state) +{ + return (state &= ~LACP_ACTOR_PARTNER_STATE_SYNCHRONIZATION); +} + +static __inline__ int +lacp_actor_partner_state_in_sync(lacp_actor_partner_state state) +{ + return ((state & LACP_ACTOR_PARTNER_STATE_SYNCHRONIZATION) != 0); +} + +static __inline__ lacp_actor_partner_state +lacp_actor_partner_state_set_collecting(lacp_actor_partner_state state) +{ + return (state | LACP_ACTOR_PARTNER_STATE_COLLECTING); +} + +static __inline__ lacp_actor_partner_state +lacp_actor_partner_state_set_not_collecting(lacp_actor_partner_state state) +{ + return (state &= ~LACP_ACTOR_PARTNER_STATE_COLLECTING); +} + +static __inline__ lacp_actor_partner_state +lacp_actor_partner_state_collecting(lacp_actor_partner_state state) +{ + return ((state & LACP_ACTOR_PARTNER_STATE_COLLECTING) != 0); +} + +static __inline__ lacp_actor_partner_state +lacp_actor_partner_state_set_distributing(lacp_actor_partner_state state) +{ + return (state | LACP_ACTOR_PARTNER_STATE_DISTRIBUTING); +} + +static __inline__ lacp_actor_partner_state +lacp_actor_partner_state_set_not_distributing(lacp_actor_partner_state state) +{ + return (state &= ~LACP_ACTOR_PARTNER_STATE_DISTRIBUTING); +} + +static __inline__ lacp_actor_partner_state +lacp_actor_partner_state_distributing(lacp_actor_partner_state state) +{ + return ((state & LACP_ACTOR_PARTNER_STATE_DISTRIBUTING) != 0); +} + +static __inline__ lacp_actor_partner_state +lacp_actor_partner_state_set_defaulted(lacp_actor_partner_state state) +{ + return (state | LACP_ACTOR_PARTNER_STATE_DEFAULTED); +} + +static __inline__ lacp_actor_partner_state +lacp_actor_partner_state_set_not_defaulted(lacp_actor_partner_state state) +{ + return (state &= ~LACP_ACTOR_PARTNER_STATE_DEFAULTED); +} + +static __inline__ lacp_actor_partner_state +lacp_actor_partner_state_defaulted(lacp_actor_partner_state state) +{ + return ((state & LACP_ACTOR_PARTNER_STATE_DEFAULTED) != 0); +} + +static __inline__ lacp_actor_partner_state +lacp_actor_partner_state_set_expired(lacp_actor_partner_state state) +{ + return (state | LACP_ACTOR_PARTNER_STATE_EXPIRED); +} + +static __inline__ lacp_actor_partner_state +lacp_actor_partner_state_set_not_expired(lacp_actor_partner_state state) +{ + return (state &= ~LACP_ACTOR_PARTNER_STATE_EXPIRED); +} + +static __inline__ lacp_actor_partner_state +lacp_actor_partner_state_expired(lacp_actor_partner_state state) +{ + return ((state & LACP_ACTOR_PARTNER_STATE_EXPIRED) != 0); +} + +/* + * Function: lacp_uint16_set + * Purpose: + * Set a field in a structure that's at least 16 bits to the given + * value, putting it into network byte order + */ +static __inline__ void +lacp_uint16_set(uint8_t * field, uint16_t value) +{ + uint16_t tmp_value = htons(value); + memcpy((void *)field, (void *)&tmp_value, sizeof(uint16_t)); + return; +} + +/* + * Function: lacp_uint16_get + * Purpose: + * Get a field in a structure that's at least 16 bits, converting + * to host byte order. + */ +static __inline__ uint16_t +lacp_uint16_get(const uint8_t * field) +{ + uint16_t tmp_field; + memcpy((void *)&tmp_field, (void *)field, sizeof(uint16_t)); + return (ntohs(tmp_field)); +} + +/* + * Function: lacp_uint32_set + * Purpose: + * Set a field in a structure that's at least 32 bits to the given + * value, putting it into network byte order + */ +static __inline__ void +lacp_uint32_set(uint8_t * field, uint32_t value) +{ + uint32_t tmp_value = htonl(value); + memcpy((void *)field, (void *)&tmp_value, sizeof(uint32_t)); + return; +} + +/* + * Function: lacp_uint32_get + * Purpose: + * Get a field in a structure that's at least 32 bits, converting + * to host byte order. + */ +static __inline__ uint32_t +lacp_uint32_get(const uint8_t * field) +{ + uint32_t tmp_field; + memcpy((void *)&tmp_field, (void *)field, sizeof(uint32_t)); + return (ntohl(tmp_field)); +} + +/* + * LACP Actor/Partner TLV access functions + */ +static __inline__ void +lacp_actor_partner_tlv_set_system_priority(lacp_actor_partner_tlv_ref tlv, + lacp_system_priority system_priority) +{ + lacp_uint16_set(tlv->lap_system_priority, system_priority); + return; +} + +static __inline__ lacp_system_priority +lacp_actor_partner_tlv_get_system_priority(const lacp_actor_partner_tlv_ref tlv) +{ + return (lacp_system_priority)lacp_uint16_get(tlv->lap_system_priority); +} + +static __inline__ void +lacp_actor_partner_tlv_set_key(lacp_actor_partner_tlv_ref tlv, lacp_key key) +{ + lacp_uint16_set(tlv->lap_key, key); + return; +} + +static __inline__ lacp_key +lacp_actor_partner_tlv_get_key(const lacp_actor_partner_tlv_ref tlv) +{ + return (lacp_key)lacp_uint16_get(tlv->lap_key); +} + +static __inline__ void +lacp_actor_partner_tlv_set_port_priority(lacp_actor_partner_tlv_ref tlv, + lacp_port_priority port_priority) +{ + lacp_uint16_set(tlv->lap_port_priority, port_priority); + return; +} + +static __inline__ lacp_port_priority +lacp_actor_partner_tlv_get_port_priority(const lacp_actor_partner_tlv_ref tlv) +{ + return (lacp_port_priority)lacp_uint16_get(tlv->lap_port_priority); +} + +static __inline__ void +lacp_actor_partner_tlv_set_port(lacp_actor_partner_tlv_ref tlv, lacp_port port) +{ + lacp_uint16_set(tlv->lap_port, port); + return; +} + +static __inline__ lacp_port +lacp_actor_partner_tlv_get_port(const lacp_actor_partner_tlv_ref tlv) +{ + return (lacp_port)lacp_uint16_get(tlv->lap_port); +} + +/* + * LACP Collector TLV access functions + */ +static __inline__ void +lacp_collector_tlv_set_max_delay(lacp_collector_tlv_ref tlv, + lacp_collector_max_delay delay) +{ + lacp_uint16_set(tlv->lac_max_delay, delay); + return; +} + +static __inline__ lacp_collector_max_delay +lacp_collector_tlv_get_max_delay(const lacp_collector_tlv_ref tlv) +{ + return (lacp_collector_max_delay)lacp_uint16_get(tlv->lac_max_delay); +} + +typedef struct lacpdu_s { + u_char la_subtype; + u_char la_version; + u_char la_actor_tlv[LACPDU_ACTOR_TLV_LENGTH]; + u_char la_partner_tlv[LACPDU_PARTNER_TLV_LENGTH]; + u_char la_collector_tlv[LACPDU_COLLECTOR_TLV_LENGTH]; + u_char la_terminator_type; + u_char la_terminator_length; + u_char la_reserved[50]; +} lacpdu, *lacpdu_ref; + +/* timer values in seconds */ +#define LACP_FAST_PERIODIC_TIME 1 +#define LACP_SLOW_PERIODIC_TIME 30 +#define LACP_SHORT_TIMEOUT_TIME 3 +#define LACP_LONG_TIMEOUT_TIME 90 +#define LACP_CHURN_DETECTION_TIME 60 +#define LACP_AGGREGATE_WAIT_TIME 2 + +/* packet rate per second */ +#define LACP_PACKET_RATE 3 + +/** + ** Link Aggregation Marker Protocol definitions + **/ +#define LA_MARKER_PDU_VERSION_1 1 +#define LA_MARKER_TLV_TYPE_TERMINATOR 0x00 +#define LA_MARKER_TLV_TYPE_MARKER 0x01 +#define LA_MARKER_TLV_TYPE_MARKER_RESPONSE 0x02 + +#define LA_MARKER_TLV_LENGTH 16 +#define LA_MARKER_RESPONSE_TLV_LENGTH 16 + +typedef u_int32_t la_marker_transaction_id; + +typedef struct la_marker_pdu_s { + u_char lm_subtype; /* 0x02 */ + u_char lm_version; /* 0x01 */ + u_char lm_marker_tlv_type; /* 0x01 or 0x02 */ + u_char lm_marker_tlv_length; /* 16 */ + u_char lm_requestor_port[2]; + u_char lm_requestor_system[6]; + u_char lm_requestor_transaction_id[4]; + u_char lm_pad[2]; + u_char lm_terminator_type; /* 0x00 */ + u_char lm_terminator_length; /* 0 */ + u_char lm_reserved[90]; +} la_marker_pdu, *la_marker_pdu_ref, + la_marker_response_pdu, * la_marker_response_pdu_ref; + +static __inline__ void +la_marker_pdu_set_requestor_port(la_marker_pdu_ref lmpdu, lacp_port port) +{ + lacp_uint16_set(lmpdu->lm_requestor_port, port); + return; +} + +static __inline__ lacp_port +la_marker_pdu_get_requestor_port(la_marker_pdu_ref lmpdu) +{ + return (lacp_port)lacp_uint16_get(lmpdu->lm_requestor_port); +} + +static __inline__ void +la_marker_pdu_set_requestor_transaction_id(la_marker_pdu_ref lmpdu, + la_marker_transaction_id xid) +{ + lacp_uint32_set(lmpdu->lm_requestor_transaction_id, xid); + return; +} + +static __inline__ la_marker_transaction_id +la_marker_pdu_get_requestor_transaction_id(la_marker_pdu_ref lmpdu) +{ + return (la_marker_transaction_id)lacp_uint32_get(lmpdu->lm_requestor_transaction_id); +} + +static __inline__ void +la_marker_pdu_set_requestor_system(la_marker_pdu_ref lmpdu, lacp_system sys) +{ + *((lacp_system_ref)lmpdu->lm_requestor_system) = sys; + return; +} + +static __inline__ lacp_system +la_marker_pdu_get_requestor_system(la_marker_pdu_ref lmpdu) +{ + return (*(lacp_system_ref)(lmpdu->lm_requestor_system)); +} + +#endif /* _NET_LACP_H_ */ diff --git a/include/sys/queue.h b/include/sys/queue.h new file mode 100644 index 0000000000000000000000000000000000000000..daf4553d33e9a21d4fa8d730e00a1d34b48ee66d --- /dev/null +++ b/include/sys/queue.h @@ -0,0 +1,574 @@ +/* + * 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 _SYS_QUEUE_H_ +#define _SYS_QUEUE_H_ + +/* + * This file defines five types of data structures: singly-linked lists, + * lists, simple queues, tail queues, and circular queues. + * + * A singly-linked list is headed by a single forward pointer. The + * elements are singly linked for minimum space and pointer manipulation + * overhead at the expense of O(n) removal for arbitrary elements. New + * elements can be added to the list after an existing element or at the + * head of the list. Elements being removed from the head of the list + * should use the explicit macro for this purpose for optimum + * efficiency. A singly-linked list may only be traversed in the forward + * direction. Singly-linked lists are ideal for applications with large + * datasets and few or no removals or for implementing a LIFO queue. + * + * A list is headed by a single forward pointer (or an array of forward + * pointers for a hash table header). The elements are doubly linked + * so that an arbitrary element can be removed without a need to + * traverse the list. New elements can be added to the list before + * or after an existing element or at the head of the list. A list + * may only be traversed in the forward direction. + * + * A simple queue is headed by a pair of pointers, one the head of the + * list and the other to the tail of the list. The elements are singly + * linked to save space, so elements can only be removed from the + * head of the list. New elements can be added to the list after + * an existing element, at the head of the list, or at the end of the + * list. A simple queue may only be traversed in the forward direction. + * + * A tail queue is headed by a pair of pointers, one to the head of the + * list and the other to the tail of the list. The elements are doubly + * linked so that an arbitrary element can be removed without a need to + * traverse the list. New elements can be added to the list before or + * after an existing element, at the head of the list, or at the end of + * the list. A tail queue may be traversed in either direction. + * + * A circle queue is headed by a pair of pointers, one to the head of the + * list and the other to the tail of the list. The elements are doubly + * linked so that an arbitrary element can be removed without a need to + * traverse the list. New elements can be added to the list before or after + * an existing element, at the head of the list, or at the end of the list. + * A circle queue may be traversed in either direction, but has a more + * complex end of list detection. + * + * For details on the use of these macros, see the queue(3) manual page. + */ + +/* + * List definitions. + */ +#define LIST_HEAD(name, type) \ +struct name { \ + struct type *lh_first; /* first element */ \ +} + +#define LIST_HEAD_INITIALIZER(head) \ + { NULL } + +#define LIST_ENTRY(type) \ +struct { \ + struct type *le_next; /* next element */ \ + struct type **le_prev; /* address of previous next element */ \ +} + +/* + * List functions. + */ +#define LIST_INIT(head) do { \ + (head)->lh_first = NULL; \ +} while (/*CONSTCOND*/0) + +#define LIST_INSERT_AFTER(listelm, elm, field) do { \ + if (((elm)->field.le_next = (listelm)->field.le_next) != NULL) \ + (listelm)->field.le_next->field.le_prev = \ + &(elm)->field.le_next; \ + (listelm)->field.le_next = (elm); \ + (elm)->field.le_prev = &(listelm)->field.le_next; \ +} while (/*CONSTCOND*/0) + +#define LIST_INSERT_BEFORE(listelm, elm, field) do { \ + (elm)->field.le_prev = (listelm)->field.le_prev; \ + (elm)->field.le_next = (listelm); \ + *(listelm)->field.le_prev = (elm); \ + (listelm)->field.le_prev = &(elm)->field.le_next; \ +} while (/*CONSTCOND*/0) + +#define LIST_INSERT_HEAD(head, elm, field) do { \ + if (((elm)->field.le_next = (head)->lh_first) != NULL) \ + (head)->lh_first->field.le_prev = &(elm)->field.le_next;\ + (head)->lh_first = (elm); \ + (elm)->field.le_prev = &(head)->lh_first; \ +} while (/*CONSTCOND*/0) + +#define LIST_REMOVE(elm, field) do { \ + if ((elm)->field.le_next != NULL) \ + (elm)->field.le_next->field.le_prev = \ + (elm)->field.le_prev; \ + *(elm)->field.le_prev = (elm)->field.le_next; \ +} while (/*CONSTCOND*/0) + +#define LIST_FOREACH(var, head, field) \ + for ((var) = ((head)->lh_first); \ + (var); \ + (var) = ((var)->field.le_next)) + +/* + * List access methods. + */ +#define LIST_EMPTY(head) ((head)->lh_first == NULL) +#define LIST_FIRST(head) ((head)->lh_first) +#define LIST_NEXT(elm, field) ((elm)->field.le_next) + + +/* + * Singly-linked List definitions. + */ +#define SLIST_HEAD(name, type) \ +struct name { \ + struct type *slh_first; /* first element */ \ +} + +#define SLIST_HEAD_INITIALIZER(head) \ + { NULL } + +#define SLIST_ENTRY(type) \ +struct { \ + struct type *sle_next; /* next element */ \ +} + +/* + * Singly-linked List functions. + */ +#define SLIST_INIT(head) do { \ + (head)->slh_first = NULL; \ +} while (/*CONSTCOND*/0) + +#define SLIST_INSERT_AFTER(slistelm, elm, field) do { \ + (elm)->field.sle_next = (slistelm)->field.sle_next; \ + (slistelm)->field.sle_next = (elm); \ +} while (/*CONSTCOND*/0) + +#define SLIST_INSERT_HEAD(head, elm, field) do { \ + (elm)->field.sle_next = (head)->slh_first; \ + (head)->slh_first = (elm); \ +} while (/*CONSTCOND*/0) + +#define SLIST_REMOVE_HEAD(head, field) do { \ + (head)->slh_first = (head)->slh_first->field.sle_next; \ +} while (/*CONSTCOND*/0) + +#define SLIST_REMOVE(head, elm, type, field) do { \ + if ((head)->slh_first == (elm)) { \ + SLIST_REMOVE_HEAD((head), field); \ + } \ + else { \ + struct type *curelm = (head)->slh_first; \ + while(curelm->field.sle_next != (elm)) \ + curelm = curelm->field.sle_next; \ + curelm->field.sle_next = \ + curelm->field.sle_next->field.sle_next; \ + } \ +} while (/*CONSTCOND*/0) + +#define SLIST_FOREACH(var, head, field) \ + for((var) = (head)->slh_first; (var); (var) = (var)->field.sle_next) + +/* + * Singly-linked List access methods. + */ +#define SLIST_EMPTY(head) ((head)->slh_first == NULL) +#define SLIST_FIRST(head) ((head)->slh_first) +#define SLIST_NEXT(elm, field) ((elm)->field.sle_next) + + +/* + * Singly-linked Tail queue declarations. + */ +#define STAILQ_HEAD(name, type) \ +struct name { \ + struct type *stqh_first; /* first element */ \ + struct type **stqh_last; /* addr of last next element */ \ +} + +#define STAILQ_HEAD_INITIALIZER(head) \ + { NULL, &(head).stqh_first } + +#define STAILQ_ENTRY(type) \ +struct { \ + struct type *stqe_next; /* next element */ \ +} + +/* + * Singly-linked Tail queue functions. + */ +#define STAILQ_INIT(head) do { \ + (head)->stqh_first = NULL; \ + (head)->stqh_last = &(head)->stqh_first; \ +} while (/*CONSTCOND*/0) + +#define STAILQ_INSERT_HEAD(head, elm, field) do { \ + if (((elm)->field.stqe_next = (head)->stqh_first) == NULL) \ + (head)->stqh_last = &(elm)->field.stqe_next; \ + (head)->stqh_first = (elm); \ +} while (/*CONSTCOND*/0) + +#define STAILQ_INSERT_TAIL(head, elm, field) do { \ + (elm)->field.stqe_next = NULL; \ + *(head)->stqh_last = (elm); \ + (head)->stqh_last = &(elm)->field.stqe_next; \ +} while (/*CONSTCOND*/0) + +#define STAILQ_INSERT_AFTER(head, listelm, elm, field) do { \ + if (((elm)->field.stqe_next = (listelm)->field.stqe_next) == NULL)\ + (head)->stqh_last = &(elm)->field.stqe_next; \ + (listelm)->field.stqe_next = (elm); \ +} while (/*CONSTCOND*/0) + +#define STAILQ_REMOVE_HEAD(head, field) do { \ + if (((head)->stqh_first = (head)->stqh_first->field.stqe_next) == NULL) \ + (head)->stqh_last = &(head)->stqh_first; \ +} while (/*CONSTCOND*/0) + +#define STAILQ_REMOVE(head, elm, type, field) do { \ + if ((head)->stqh_first == (elm)) { \ + STAILQ_REMOVE_HEAD((head), field); \ + } else { \ + struct type *curelm = (head)->stqh_first; \ + while (curelm->field.stqe_next != (elm)) \ + curelm = curelm->field.stqe_next; \ + if ((curelm->field.stqe_next = \ + curelm->field.stqe_next->field.stqe_next) == NULL) \ + (head)->stqh_last = &(curelm)->field.stqe_next; \ + } \ +} while (/*CONSTCOND*/0) + +#define STAILQ_FOREACH(var, head, field) \ + for ((var) = ((head)->stqh_first); \ + (var); \ + (var) = ((var)->field.stqe_next)) + +#define STAILQ_CONCAT(head1, head2) do { \ + if (!STAILQ_EMPTY((head2))) { \ + *(head1)->stqh_last = (head2)->stqh_first; \ + (head1)->stqh_last = (head2)->stqh_last; \ + STAILQ_INIT((head2)); \ + } \ +} while (/*CONSTCOND*/0) + +/* + * Singly-linked Tail queue access methods. + */ +#define STAILQ_EMPTY(head) ((head)->stqh_first == NULL) +#define STAILQ_FIRST(head) ((head)->stqh_first) +#define STAILQ_NEXT(elm, field) ((elm)->field.stqe_next) + + +/* + * Simple queue definitions. + */ +#define SIMPLEQ_HEAD(name, type) \ +struct name { \ + struct type *sqh_first; /* first element */ \ + struct type **sqh_last; /* addr of last next element */ \ +} + +#define SIMPLEQ_HEAD_INITIALIZER(head) \ + { NULL, &(head).sqh_first } + +#define SIMPLEQ_ENTRY(type) \ +struct { \ + struct type *sqe_next; /* next element */ \ +} + +/* + * Simple queue functions. + */ +#define SIMPLEQ_INIT(head) do { \ + (head)->sqh_first = NULL; \ + (head)->sqh_last = &(head)->sqh_first; \ +} while (/*CONSTCOND*/0) + +#define SIMPLEQ_INSERT_HEAD(head, elm, field) do { \ + if (((elm)->field.sqe_next = (head)->sqh_first) == NULL) \ + (head)->sqh_last = &(elm)->field.sqe_next; \ + (head)->sqh_first = (elm); \ +} while (/*CONSTCOND*/0) + +#define SIMPLEQ_INSERT_TAIL(head, elm, field) do { \ + (elm)->field.sqe_next = NULL; \ + *(head)->sqh_last = (elm); \ + (head)->sqh_last = &(elm)->field.sqe_next; \ +} while (/*CONSTCOND*/0) + +#define SIMPLEQ_INSERT_AFTER(head, listelm, elm, field) do { \ + if (((elm)->field.sqe_next = (listelm)->field.sqe_next) == NULL)\ + (head)->sqh_last = &(elm)->field.sqe_next; \ + (listelm)->field.sqe_next = (elm); \ +} while (/*CONSTCOND*/0) + +#define SIMPLEQ_REMOVE_HEAD(head, field) do { \ + if (((head)->sqh_first = (head)->sqh_first->field.sqe_next) == NULL) \ + (head)->sqh_last = &(head)->sqh_first; \ +} while (/*CONSTCOND*/0) + +#define SIMPLEQ_REMOVE(head, elm, type, field) do { \ + if ((head)->sqh_first == (elm)) { \ + SIMPLEQ_REMOVE_HEAD((head), field); \ + } else { \ + struct type *curelm = (head)->sqh_first; \ + while (curelm->field.sqe_next != (elm)) \ + curelm = curelm->field.sqe_next; \ + if ((curelm->field.sqe_next = \ + curelm->field.sqe_next->field.sqe_next) == NULL) \ + (head)->sqh_last = &(curelm)->field.sqe_next; \ + } \ +} while (/*CONSTCOND*/0) + +#define SIMPLEQ_FOREACH(var, head, field) \ + for ((var) = ((head)->sqh_first); \ + (var); \ + (var) = ((var)->field.sqe_next)) + +/* + * Simple queue access methods. + */ +#define SIMPLEQ_EMPTY(head) ((head)->sqh_first == NULL) +#define SIMPLEQ_FIRST(head) ((head)->sqh_first) +#define SIMPLEQ_NEXT(elm, field) ((elm)->field.sqe_next) + + +/* + * Tail queue definitions. + */ +#define _TAILQ_HEAD(name, type, qual) \ +struct name { \ + qual type *tqh_first; /* first element */ \ + qual type *qual *tqh_last; /* addr of last next element */ \ +} +#define TAILQ_HEAD(name, type) _TAILQ_HEAD(name, struct type,) + +#define TAILQ_HEAD_INITIALIZER(head) \ + { NULL, &(head).tqh_first } + +#define _TAILQ_ENTRY(type, qual) \ +struct { \ + qual type *tqe_next; /* next element */ \ + qual type *qual *tqe_prev; /* address of previous next element */\ +} +#define TAILQ_ENTRY(type) _TAILQ_ENTRY(struct type,) + +/* + * Tail queue functions. + */ +#define TAILQ_INIT(head) do { \ + (head)->tqh_first = NULL; \ + (head)->tqh_last = &(head)->tqh_first; \ +} while (/*CONSTCOND*/0) + +#define TAILQ_INSERT_HEAD(head, elm, field) do { \ + if (((elm)->field.tqe_next = (head)->tqh_first) != NULL) \ + (head)->tqh_first->field.tqe_prev = \ + &(elm)->field.tqe_next; \ + else \ + (head)->tqh_last = &(elm)->field.tqe_next; \ + (head)->tqh_first = (elm); \ + (elm)->field.tqe_prev = &(head)->tqh_first; \ +} while (/*CONSTCOND*/0) + +#define TAILQ_INSERT_TAIL(head, elm, field) do { \ + (elm)->field.tqe_next = NULL; \ + (elm)->field.tqe_prev = (head)->tqh_last; \ + *(head)->tqh_last = (elm); \ + (head)->tqh_last = &(elm)->field.tqe_next; \ +} while (/*CONSTCOND*/0) + +#define TAILQ_INSERT_AFTER(head, listelm, elm, field) do { \ + if (((elm)->field.tqe_next = (listelm)->field.tqe_next) != NULL)\ + (elm)->field.tqe_next->field.tqe_prev = \ + &(elm)->field.tqe_next; \ + else \ + (head)->tqh_last = &(elm)->field.tqe_next; \ + (listelm)->field.tqe_next = (elm); \ + (elm)->field.tqe_prev = &(listelm)->field.tqe_next; \ +} while (/*CONSTCOND*/0) + +#define TAILQ_INSERT_BEFORE(listelm, elm, field) do { \ + (elm)->field.tqe_prev = (listelm)->field.tqe_prev; \ + (elm)->field.tqe_next = (listelm); \ + *(listelm)->field.tqe_prev = (elm); \ + (listelm)->field.tqe_prev = &(elm)->field.tqe_next; \ +} while (/*CONSTCOND*/0) + +#define TAILQ_REMOVE(head, elm, field) do { \ + if (((elm)->field.tqe_next) != NULL) \ + (elm)->field.tqe_next->field.tqe_prev = \ + (elm)->field.tqe_prev; \ + else \ + (head)->tqh_last = (elm)->field.tqe_prev; \ + *(elm)->field.tqe_prev = (elm)->field.tqe_next; \ +} while (/*CONSTCOND*/0) + +#define TAILQ_FOREACH(var, head, field) \ + for ((var) = ((head)->tqh_first); \ + (var); \ + (var) = ((var)->field.tqe_next)) + +#define TAILQ_FOREACH_REVERSE(var, head, headname, field) \ + for ((var) = (*(((struct headname *)((head)->tqh_last))->tqh_last)); \ + (var); \ + (var) = (*(((struct headname *)((var)->field.tqe_prev))->tqh_last))) + +#define TAILQ_CONCAT(head1, head2, field) do { \ + if (!TAILQ_EMPTY(head2)) { \ + *(head1)->tqh_last = (head2)->tqh_first; \ + (head2)->tqh_first->field.tqe_prev = (head1)->tqh_last; \ + (head1)->tqh_last = (head2)->tqh_last; \ + TAILQ_INIT((head2)); \ + } \ +} while (/*CONSTCOND*/0) + +/* + * Tail queue access methods. + */ +#define TAILQ_EMPTY(head) ((head)->tqh_first == NULL) +#define TAILQ_FIRST(head) ((head)->tqh_first) +#define TAILQ_NEXT(elm, field) ((elm)->field.tqe_next) + +#define TAILQ_LAST(head, headname) \ + (*(((struct headname *)((head)->tqh_last))->tqh_last)) +#define TAILQ_PREV(elm, headname, field) \ + (*(((struct headname *)((elm)->field.tqe_prev))->tqh_last)) + + +/* + * Circular queue definitions. + */ +#define CIRCLEQ_HEAD(name, type) \ +struct name { \ + struct type *cqh_first; /* first element */ \ + struct type *cqh_last; /* last element */ \ +} + +#define CIRCLEQ_HEAD_INITIALIZER(head) \ + { (void *)&head, (void *)&head } + +#define CIRCLEQ_ENTRY(type) \ +struct { \ + struct type *cqe_next; /* next element */ \ + struct type *cqe_prev; /* previous element */ \ +} + +/* + * Circular queue functions. + */ +#define CIRCLEQ_INIT(head) do { \ + (head)->cqh_first = (void *)(head); \ + (head)->cqh_last = (void *)(head); \ +} while (/*CONSTCOND*/0) + +#define CIRCLEQ_INSERT_AFTER(head, listelm, elm, field) do { \ + (elm)->field.cqe_next = (listelm)->field.cqe_next; \ + (elm)->field.cqe_prev = (listelm); \ + if ((listelm)->field.cqe_next == (void *)(head)) \ + (head)->cqh_last = (elm); \ + else \ + (listelm)->field.cqe_next->field.cqe_prev = (elm); \ + (listelm)->field.cqe_next = (elm); \ +} while (/*CONSTCOND*/0) + +#define CIRCLEQ_INSERT_BEFORE(head, listelm, elm, field) do { \ + (elm)->field.cqe_next = (listelm); \ + (elm)->field.cqe_prev = (listelm)->field.cqe_prev; \ + if ((listelm)->field.cqe_prev == (void *)(head)) \ + (head)->cqh_first = (elm); \ + else \ + (listelm)->field.cqe_prev->field.cqe_next = (elm); \ + (listelm)->field.cqe_prev = (elm); \ +} while (/*CONSTCOND*/0) + +#define CIRCLEQ_INSERT_HEAD(head, elm, field) do { \ + (elm)->field.cqe_next = (head)->cqh_first; \ + (elm)->field.cqe_prev = (void *)(head); \ + if ((head)->cqh_last == (void *)(head)) \ + (head)->cqh_last = (elm); \ + else \ + (head)->cqh_first->field.cqe_prev = (elm); \ + (head)->cqh_first = (elm); \ +} while (/*CONSTCOND*/0) + +#define CIRCLEQ_INSERT_TAIL(head, elm, field) do { \ + (elm)->field.cqe_next = (void *)(head); \ + (elm)->field.cqe_prev = (head)->cqh_last; \ + if ((head)->cqh_first == (void *)(head)) \ + (head)->cqh_first = (elm); \ + else \ + (head)->cqh_last->field.cqe_next = (elm); \ + (head)->cqh_last = (elm); \ +} while (/*CONSTCOND*/0) + +#define CIRCLEQ_REMOVE(head, elm, field) do { \ + if ((elm)->field.cqe_next == (void *)(head)) \ + (head)->cqh_last = (elm)->field.cqe_prev; \ + else \ + (elm)->field.cqe_next->field.cqe_prev = \ + (elm)->field.cqe_prev; \ + if ((elm)->field.cqe_prev == (void *)(head)) \ + (head)->cqh_first = (elm)->field.cqe_next; \ + else \ + (elm)->field.cqe_prev->field.cqe_next = \ + (elm)->field.cqe_next; \ +} while (/*CONSTCOND*/0) + +#define CIRCLEQ_FOREACH(var, head, field) \ + for ((var) = ((head)->cqh_first); \ + (var) != (const void *)(head); \ + (var) = ((var)->field.cqe_next)) + +#define CIRCLEQ_FOREACH_REVERSE(var, head, field) \ + for ((var) = ((head)->cqh_last); \ + (var) != (const void *)(head); \ + (var) = ((var)->field.cqe_prev)) + +/* + * Circular queue access methods. + */ +#define CIRCLEQ_EMPTY(head) ((head)->cqh_first == (void *)(head)) +#define CIRCLEQ_FIRST(head) ((head)->cqh_first) +#define CIRCLEQ_LAST(head) ((head)->cqh_last) +#define CIRCLEQ_NEXT(elm, field) ((elm)->field.cqe_next) +#define CIRCLEQ_PREV(elm, field) ((elm)->field.cqe_prev) + +#define CIRCLEQ_LOOP_NEXT(head, elm, field) \ + (((elm)->field.cqe_next == (void *)(head)) \ + ? ((head)->cqh_first) \ + : (elm->field.cqe_next)) +#define CIRCLEQ_LOOP_PREV(head, elm, field) \ + (((elm)->field.cqe_prev == (void *)(head)) \ + ? ((head)->cqh_last) \ + : (elm->field.cqe_prev)) + +#endif /* sys/queue.h */ diff --git a/libevent/.gitkeep b/libevent/.gitkeep new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/m4/alignof.m4 b/m4/alignof.m4 new file mode 100644 index 0000000000000000000000000000000000000000..a90475ddcde7991afd13a2fbf3e1256711496ed8 --- /dev/null +++ b/m4/alignof.m4 @@ -0,0 +1,13 @@ +# +# lldp_CHECK_ALIGNOF +# +AC_DEFUN([lldp_CHECK_ALIGNOF],[ + AC_CACHE_CHECK([whether compiler understands __alignof__], lldp_cv_check_alignof, [ + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[]], [[ return __alignof__(long); ]])], + [ lldp_cv_check_alignof="yes" ], + [ lldp_cv_check_alignof="no" ]) + ]) + if test x"$lldp_cv_check_alignof" = x"yes"; then + AC_DEFINE([HAVE_ALIGNOF], [1], [Define if __alignof__ operator is available]) + fi +]) diff --git a/m4/args.m4 b/m4/args.m4 new file mode 100644 index 0000000000000000000000000000000000000000..4f29724c0a2a9ffb87c5704a3bbe35f9586b67a0 --- /dev/null +++ b/m4/args.m4 @@ -0,0 +1,81 @@ +# +# lldp_ARG_WITH +# + +dnl lldp_AC_EXPAND(var) + +AC_DEFUN([lldp_AC_EXPAND], [ + dnl first expand prefix and exec_prefix if necessary + prefix_save=$prefix + exec_prefix_save=$exec_prefix + + dnl if no prefix given, then use /usr/local, the default prefix + if test "x$prefix" = "xNONE"; then + prefix="$ac_default_prefix" + fi + dnl if no exec_prefix given, then use prefix + if test "x$exec_prefix" = "xNONE"; then + exec_prefix=$prefix + fi + + full_var="$1" + dnl loop until it doesn't change anymore + while true; do + dnl echo DEBUG: full_var: $full_var + new_full_var="`eval echo $full_var`" + if test "x$new_full_var" = "x$full_var"; then break; fi + full_var=$new_full_var + done + + dnl clean up + full_var=$new_full_var + eval $2="$full_var" + + dnl restore prefix and exec_prefix + prefix=$prefix_save + exec_prefix=$exec_prefix_save +]) + +dnl lldp_ARG_WITH_UNQUOTED(name, help1, default) + +AC_DEFUN([lldp_ARG_WITH_UNQUOTED],[ + AC_ARG_WITH([$1], + AS_HELP_STRING([--with-$1], + [$2 @<:@default=$3@:>@]),[ + AC_DEFINE_UNQUOTED(AS_TR_CPP([$1]), [$withval], [$2]) + AC_SUBST(AS_TR_CPP([$1]), [$withval])],[ + AC_DEFINE_UNQUOTED(AS_TR_CPP([$1]), [$3], [$2]) + AC_SUBST(AS_TR_CPP([$1]), [$3]) + eval with_[]m4_translit([$1], [-+.], [___])=$3 +])]) + +dnl lldp_ARG_WITH(name, help1, default) + +AC_DEFUN([lldp_ARG_WITH],[ + AC_ARG_WITH([$1], + AS_HELP_STRING([--with-$1], + [$2 @<:@default=$3@:>@]),[ + lldp_AC_EXPAND("$withval", expanded) + AC_DEFINE_UNQUOTED(AS_TR_CPP([$1]), ["$expanded"], [$2]) + AC_SUBST(AS_TR_CPP([$1]), [$expanded])],[ + lldp_AC_EXPAND("$3", expanded) + AC_DEFINE_UNQUOTED(AS_TR_CPP([$1]), ["$expanded"], [$2]) + AC_SUBST(AS_TR_CPP([$1]), [$expanded]) + eval with_[]m4_translit([$1], [-+.], [___])="$expanded" +])]) + +dnl lldp_ARG_ENABLE(name, help1, default) + +AC_DEFUN([lldp_ARG_ENABLE],[ + AC_ARG_ENABLE([$1], + AS_HELP_STRING([--enable-$1], + [Enable $2 @<:@default=$3@:>@]), + [enable_$1=$enableval], [enable_$1=$3]) + AC_MSG_CHECKING(whether to enable $2) + if test x"$enable_$1" = x"yes"; then + AC_MSG_RESULT(yes) + AC_DEFINE([ENABLE_]AS_TR_CPP([$1]),, [$2]) + else + AC_MSG_RESULT(no) + fi +]) diff --git a/m4/ax_build_date_epoch.m4 b/m4/ax_build_date_epoch.m4 new file mode 100644 index 0000000000000000000000000000000000000000..2e81b72cd81823b5778829bd043bebfd86e24866 --- /dev/null +++ b/m4/ax_build_date_epoch.m4 @@ -0,0 +1,70 @@ +# =========================================================================== +# http://www.gnu.org/software/autoconf-archive/ax_build_date_epoch.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_BUILD_DATE_EPOCH(VARIABLE[, FORMAT[, ACTION-IF-FAIL]]) +# +# DESCRIPTION +# +# Sets VARIABLE to a string representing the current time. It is +# formatted according to FORMAT if specified, otherwise it is formatted as +# the number of seconds (excluding leap seconds) since the UNIX epoch (01 +# Jan 1970 00:00:00 UTC). +# +# If the SOURCE_DATE_EPOCH environment variable is set, it uses the value +# of that variable instead of the current time. See +# https://reproducible-builds.org/specs/source-date-epoch). If +# SOURCE_DATE_EPOCH is set but cannot be properly interpreted as a UNIX +# timestamp, then execute ACTION-IF-FAIL if specified, otherwise error. +# +# VARIABLE is AC_SUBST-ed. +# +# LICENSE +# +# Copyright (c) 2016 Eric Bavier +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation, either version 3 of the License, or (at your +# option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +# Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program. If not, see . +# +# As a special exception, the respective Autoconf Macro's copyright owner +# gives unlimited permission to copy, distribute and modify the configure +# scripts that are the output of Autoconf when processing the Macro. You +# need not follow the terms of the GNU General Public License when using +# or distributing such scripts, even though portions of the text of the +# Macro appear in them. The GNU General Public License (GPL) does govern +# all other use of the material that constitutes the Autoconf Macro. +# +# This special exception to the GPL applies to versions of the Autoconf +# Macro released by the Autoconf Archive. When you make and distribute a +# modified version of the Autoconf Macro, you may extend this special +# exception to the GPL to apply to your modified version as well. + +#serial 1 + +AC_DEFUN([AX_BUILD_DATE_EPOCH], +[dnl +AC_MSG_CHECKING([for build time]) +ax_date_fmt="m4_default($2,%s)" +AS_IF([test x"$SOURCE_DATE_EPOCH" = x], + [$1=`date -u "+$ax_date_fmt"`], + [ax_build_date=`date -u -d "@$SOURCE_DATE_EPOCH" "+$ax_date_fmt" 2>/dev/null \ + || date -u -r "$SOURCE_DATE_EPOCH" "+$ax_date_fmt" 2>/dev/null` + AS_IF([test x"$ax_build_date" = x], + [m4_ifval([$3], + [$3], + [AC_MSG_ERROR([malformed SOURCE_DATE_EPOCH])])], + [$1=$ax_build_date])]) +AC_MSG_RESULT([$$1]) +])dnl AX_BUILD_DATE_EPOCH diff --git a/m4/ax_cflags_gcc_option.m4 b/m4/ax_cflags_gcc_option.m4 new file mode 100644 index 0000000000000000000000000000000000000000..ae0d6da7757648ae4b52e44a1776e309d7c17aef --- /dev/null +++ b/m4/ax_cflags_gcc_option.m4 @@ -0,0 +1,111 @@ +# =========================================================================== +# http://autoconf-archive.cryp.to/ax_cflags_gcc_option.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_CFLAGS_GCC_OPTION (optionflag [,[shellvar][,[A][,[NA]]]) +# +# DESCRIPTION +# +# AX_CFLAGS_GCC_OPTION(-fvomit-frame) would show a message as like +# "checking CFLAGS for gcc -fvomit-frame ... yes" and adds the optionflag +# to CFLAGS if it is understood. You can override the shellvar-default of +# CFLAGS of course. The order of arguments stems from the explicit macros +# like AX_CFLAGS_WARN_ALL. +# +# The cousin AX_CXXFLAGS_GCC_OPTION would check for an option to add to +# CXXFLAGS - and it uses the autoconf setup for C++ instead of C (since it +# is possible to use different compilers for C and C++). +# +# The macro is a lot simpler than any special AX_CFLAGS_* macro (or +# ac_cxx_rtti.m4 macro) but allows to check for arbitrary options. +# However, if you use this macro in a few places, it would be great if you +# would make up a new function-macro and submit it to the ac-archive. +# +# - $1 option-to-check-for : required ("-option" as non-value) +# - $2 shell-variable-to-add-to : CFLAGS (or CXXFLAGS in the other case) +# - $3 action-if-found : add value to shellvariable +# - $4 action-if-not-found : nothing +# +# There are other variants that emerged from the original macro variant +# which did just test an option to be possibly added. However, some +# compilers accept an option silently, or possibly for just another option +# that was not intended. Therefore, we have to do a generic test for a +# compiler family. For gcc we check "-pedantic" being accepted which is +# also understood by compilers who just want to be compatible with gcc +# even when not being made from gcc sources. +# +# This version has been modified by Vincent Bernat +# to ensure that the shell variable the option is added to is +# prepended to the test variable (like a regular CFLAGS). +# +# See also: AX_CFLAGS_SUN_OPTION, AX_CFLAGS_HPUX_OPTION, +# AX_CFLAGS_AIX_OPTION, and AX_CFLAGS_IRIX_OPTION. +# +# LICENSE +# +# Copyright (c) 2008 Guido U. Draheim +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; either version 2 of the License, or (at your +# option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +# Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program. If not, see . +# +# As a special exception, the respective Autoconf Macro's copyright owner +# gives unlimited permission to copy, distribute and modify the configure +# scripts that are the output of Autoconf when processing the Macro. You +# need not follow the terms of the GNU General Public License when using +# or distributing such scripts, even though portions of the text of the +# Macro appear in them. The GNU General Public License (GPL) does govern +# all other use of the material that constitutes the Autoconf Macro. +# +# This special exception to the GPL applies to versions of the Autoconf +# Macro released by the Autoconf Archive. When you make and distribute a +# modified version of the Autoconf Macro, you may extend this special +# exception to the GPL to apply to your modified version as well. + +dnl ------------------------------------------------------------------------- + +AC_DEFUN([AX_CFLAGS_GCC_OPTION], [dnl +AS_VAR_PUSHDEF([FLAGS],[CFLAGS])dnl +AS_VAR_PUSHDEF([VAR],[ac_cv_cflags_gcc_option_$1])dnl +AC_CACHE_CHECK([FLAGS for gcc m4_ifval($1,$1,-option)], +VAR,[AS_VAR_SET([VAR],["no, unknown"]) + AC_LANG_SAVE + AC_LANG([C]) + ac_save_[]FLAGS="$[]FLAGS" +for ac_arg dnl +in "-pedantic -Werror % m4_ifval($1,$1,-option)" dnl GCC + "-pedantic % m4_ifval($1,$1,-option) %% no, obsolete" dnl new GCC + # +do FLAGS="$ac_save_[]FLAGS $[]m4_ifval($2,$2,) "`echo $ac_arg | sed -e 's,%%.*,,' -e 's,%,,'` + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[]], [[return 0;]])], + [AS_VAR_SET([VAR],[`echo $ac_arg | sed -e 's,.*% *,,'`]) ; break], + []) +done + FLAGS="$ac_save_[]FLAGS" + AC_LANG_RESTORE +]) +AS_VAR_COPY([ac_res], [VAR]) +case ".${ac_res}" in + .ok|.ok,*) m4_ifvaln($3,$3) ;; + .|.no|.no,*) m4_ifvaln($4,$4) ;; + *) m4_ifvaln($3,$3,[ + if echo " $[]m4_ifval($2,$2,FLAGS) " | grep " ${ac_res} " 2>&1 >/dev/null + then AC_RUN_LOG([: m4_ifval($2,$2,FLAGS) does contain ${ac_res}]) + else AC_RUN_LOG([: m4_ifval($2,$2,FLAGS)="$m4_ifval($2,$2,FLAGS) ${ac_res}"]) + m4_ifval($2,$2,FLAGS)="$m4_ifval($2,$2,FLAGS) ${ac_res}" + fi ]) ;; +esac +AS_VAR_POPDEF([VAR])dnl +AS_VAR_POPDEF([FLAGS])dnl +]) diff --git a/m4/ax_gcc_func_attribute.m4 b/m4/ax_gcc_func_attribute.m4 new file mode 100644 index 0000000000000000000000000000000000000000..c788ca9bd435fbfe4e1642281aabd7f9492786ff --- /dev/null +++ b/m4/ax_gcc_func_attribute.m4 @@ -0,0 +1,223 @@ +# =========================================================================== +# http://www.gnu.org/software/autoconf-archive/ax_gcc_func_attribute.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_GCC_FUNC_ATTRIBUTE(ATTRIBUTE) +# +# DESCRIPTION +# +# This macro checks if the compiler supports one of GCC's function +# attributes; many other compilers also provide function attributes with +# the same syntax. Compiler warnings are used to detect supported +# attributes as unsupported ones are ignored by default so quieting +# warnings when using this macro will yield false positives. +# +# The ATTRIBUTE parameter holds the name of the attribute to be checked. +# +# If ATTRIBUTE is supported define HAVE_FUNC_ATTRIBUTE_. +# +# The macro caches its result in the ax_cv_have_func_attribute_ +# variable. +# +# The macro currently supports the following function attributes: +# +# alias +# aligned +# alloc_size +# always_inline +# artificial +# cold +# const +# constructor +# constructor_priority for constructor attribute with priority +# deprecated +# destructor +# dllexport +# dllimport +# error +# externally_visible +# flatten +# format +# format_arg +# gnu_inline +# hot +# ifunc +# leaf +# malloc +# noclone +# noinline +# nonnull +# noreturn +# nothrow +# optimize +# pure +# unused +# used +# visibility +# warning +# warn_unused_result +# weak +# weakref +# +# Unsuppored function attributes will be tested with a prototype returning +# an int and not accepting any arguments and the result of the check might +# be wrong or meaningless so use with care. +# +# LICENSE +# +# Copyright (c) 2013 Gabriele Svelto +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. This file is offered as-is, without any +# warranty. + +#serial 3 + +AC_DEFUN([AX_GCC_FUNC_ATTRIBUTE], [ + AS_VAR_PUSHDEF([ac_var], [ax_cv_have_func_attribute_$1]) + + AC_CACHE_CHECK([for __attribute__(($1))], [ac_var], [ + AC_LINK_IFELSE([AC_LANG_PROGRAM([ + m4_case([$1], + [alias], [ + int foo( void ) { return 0; } + int bar( void ) __attribute__(($1("foo"))); + ], + [aligned], [ + int foo( void ) __attribute__(($1(32))); + ], + [alloc_size], [ + void *foo(int a) __attribute__(($1(1))); + ], + [always_inline], [ + inline __attribute__(($1)) int foo( void ) { return 0; } + ], + [artificial], [ + inline __attribute__(($1)) int foo( void ) { return 0; } + ], + [cold], [ + int foo( void ) __attribute__(($1)); + ], + [const], [ + int foo( void ) __attribute__(($1)); + ], + [constructor_priority], [ + int foo( void ) __attribute__((__constructor__(65535/2))); + ], + [constructor], [ + int foo( void ) __attribute__(($1)); + ], + [deprecated], [ + int foo( void ) __attribute__(($1(""))); + ], + [destructor], [ + int foo( void ) __attribute__(($1)); + ], + [dllexport], [ + __attribute__(($1)) int foo( void ) { return 0; } + ], + [dllimport], [ + int foo( void ) __attribute__(($1)); + ], + [error], [ + int foo( void ) __attribute__(($1(""))); + ], + [externally_visible], [ + int foo( void ) __attribute__(($1)); + ], + [flatten], [ + int foo( void ) __attribute__(($1)); + ], + [format], [ + int foo(const char *p, ...) __attribute__(($1(printf, 1, 2))); + ], + [format_arg], [ + char *foo(const char *p) __attribute__(($1(1))); + ], + [gnu_inline], [ + inline __attribute__(($1)) int foo( void ) { return 0; } + ], + [hot], [ + int foo( void ) __attribute__(($1)); + ], + [ifunc], [ + int my_foo( void ) { return 0; } + static int (*resolve_foo(void))(void) { return my_foo; } + int foo( void ) __attribute__(($1("resolve_foo"))); + ], + [leaf], [ + __attribute__(($1)) int foo( void ) { return 0; } + ], + [malloc], [ + void *foo( void ) __attribute__(($1)); + ], + [noclone], [ + int foo( void ) __attribute__(($1)); + ], + [noinline], [ + __attribute__(($1)) int foo( void ) { return 0; } + ], + [nonnull], [ + int foo(char *p) __attribute__(($1(1))); + ], + [noreturn], [ + void foo( void ) __attribute__(($1)); + ], + [nothrow], [ + int foo( void ) __attribute__(($1)); + ], + [optimize], [ + __attribute__(($1(3))) int foo( void ) { return 0; } + ], + [pure], [ + int foo( void ) __attribute__(($1)); + ], + [unused], [ + int foo( void ) __attribute__(($1)); + ], + [used], [ + int foo( void ) __attribute__(($1)); + ], + [visibility], [ + int foo_def( void ) __attribute__(($1("default"))); + int foo_hid( void ) __attribute__(($1("hidden"))); + int foo_int( void ) __attribute__(($1("internal"))); + int foo_pro( void ) __attribute__(($1("protected"))); + ], + [warning], [ + int foo( void ) __attribute__(($1(""))); + ], + [warn_unused_result], [ + int foo( void ) __attribute__(($1)); + ], + [weak], [ + int foo( void ) __attribute__(($1)); + ], + [weakref], [ + static int foo( void ) { return 0; } + static int bar( void ) __attribute__(($1("foo"))); + ], + [ + m4_warn([syntax], [Unsupported attribute $1, the test may fail]) + int foo( void ) __attribute__(($1)); + ] + )], []) + ], + dnl GCC doesn't exit with an error if an unknown attribute is + dnl provided but only outputs a warning, so accept the attribute + dnl only if no warning were issued. + [AS_IF([test -s conftest.err], + [AS_VAR_SET([ac_var], [no])], + [AS_VAR_SET([ac_var], [yes])])], + [AS_VAR_SET([ac_var], [no])]) + ]) + + AS_IF([test yes = AS_VAR_GET([ac_var])], + [AC_DEFINE_UNQUOTED(AS_TR_CPP(HAVE_FUNC_ATTRIBUTE_$1), 1, + [Define to 1 if the system has the `$1' function attribute])], []) + + AS_VAR_POPDEF([ac_var]) +]) diff --git a/m4/ax_ld_check_flag.m4 b/m4/ax_ld_check_flag.m4 new file mode 100644 index 0000000000000000000000000000000000000000..898168a9d6a94050ab1036db0cfa2c5a9610d62a --- /dev/null +++ b/m4/ax_ld_check_flag.m4 @@ -0,0 +1,85 @@ +# =========================================================================== +# http://www.gnu.org/software/autoconf-archive/ax_ld_check_flag.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_LDFLAGS_OPTION(FLAG-TO-CHECK,[VAR],[NOTFOUND]) +# +# DESCRIPTION +# +# This macro tests if the C compiler supports the flag FLAG-TO-CHECK. If +# successfull add it to VAR. +# +# This code is inspired from KDE_CHECK_COMPILER_FLAG macro. Thanks to +# Bogdan Drozdowski for testing and bug fixes. +# +# This version has been (heavily) modified by Vincent Bernat +# to match AX_CFLAGS_GCC_OPTION. +# +# LICENSE +# +# Copyright (c) 2008 Francesco Salvestrini +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; either version 2 of the License, or (at your +# option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +# Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program. If not, see . +# +# As a special exception, the respective Autoconf Macro's copyright owner +# gives unlimited permission to copy, distribute and modify the configure +# scripts that are the output of Autoconf when processing the Macro. You +# need not follow the terms of the GNU General Public License when using +# or distributing such scripts, even though portions of the text of the +# Macro appear in them. The GNU General Public License (GPL) does govern +# all other use of the material that constitutes the Autoconf Macro. +# +# This special exception to the GPL applies to versions of the Autoconf +# Macro released by the Autoconf Archive. When you make and distribute a +# modified version of the Autoconf Macro, you may extend this special +# exception to the GPL to apply to your modified version as well. + +#serial 6 + +AC_DEFUN([AX_LDFLAGS_OPTION],[ + AC_PREREQ([2.69]) + AC_REQUIRE([AC_PROG_SED]) + + flag=`echo "$1" | $SED 'y% .=/+-(){}<>:*,%_______________%'` + + AC_CACHE_CHECK([whether the linker accepts the $1 flag], + [ax_cv_ld_check_flag_$flag],[ + + AC_LANG_SAVE + AC_LANG([C]) + + save_LDFLAGS="$LDFLAGS" + LDFLAGS="-Werror $LDFLAGS $[]m4_ifval($2,$2,) $1" + AC_LINK_IFELSE([ + AC_LANG_PROGRAM([],[]) + ],[ + eval "ax_cv_ld_check_flag_$flag=yes" + ],[ + eval "ax_cv_ld_check_flag_$flag=no" + ]) + + LDFLAGS="$save_LDFLAGS" + + AC_LANG_RESTORE + + ]) + + AS_IF([eval "test \"`echo '$ax_cv_ld_check_flag_'$flag`\" = yes"],[ + m4_ifval($2,$2,LDFLAGS)="$[]m4_ifval($2,$2,LDFLAGS) $1" + ],[ + :; $3 + ]) +]) diff --git a/m4/ax_lib_readline.m4 b/m4/ax_lib_readline.m4 new file mode 100644 index 0000000000000000000000000000000000000000..352c55af0045d348ddb4a387a34073e57f5cd0cd --- /dev/null +++ b/m4/ax_lib_readline.m4 @@ -0,0 +1,124 @@ +# =========================================================================== +# http://www.gnu.org/software/autoconf-archive/ax_lib_readline.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_LIB_READLINE_LLDPD +# +# DESCRIPTION +# +# Searches for a readline compatible library. If found, defines +# `HAVE_LIBREADLINE'. If the found library has the `add_history' function, +# sets also `HAVE_READLINE_HISTORY'. Also checks for the locations of the +# necessary include files and sets `HAVE_READLINE_H' or +# `HAVE_READLINE_READLINE_H' and `HAVE_READLINE_HISTORY_H' or +# 'HAVE_HISTORY_H' if the corresponding include files exists. +# +# The libraries that may be readline compatible are `libedit', +# `libeditline' and `libreadline'. Sometimes we need to link a termcap +# library for readline to work, this macro tests these cases too by trying +# to link with `libtermcap', `libcurses' or `libncurses' before giving up. +# +# Here is an example of how to use the information provided by this macro +# to perform the necessary includes or declarations in a C file: +# +# #ifdef HAVE_LIBREADLINE +# # if defined(HAVE_READLINE_READLINE_H) +# # include +# # elif defined(HAVE_READLINE_H) +# # include +# # else /* !defined(HAVE_READLINE_H) */ +# extern char *readline (); +# 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 /* !defined(HAVE_READLINE_H) */ +# char *cmdline = NULL; +# #else /* !defined(HAVE_READLINE_READLINE_H) */ +# /* no readline */ +# #endif /* HAVE_LIBREADLINE */ +# +# #ifdef HAVE_READLINE_HISTORY +# # if defined(HAVE_READLINE_HISTORY_H) +# # include +# # elif defined(HAVE_HISTORY_H) +# # include +# # else /* !defined(HAVE_HISTORY_H) */ +# extern void add_history (); +# extern int write_history (); +# extern int read_history (); +# # endif /* defined(HAVE_READLINE_HISTORY_H) */ +# /* no history */ +# #endif /* HAVE_READLINE_HISTORY */ +# +# LICENSE +# +# Copyright (c) 2008 Ville Laurikari +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. This file is offered as-is, without any +# warranty. + +# Modified version. Original version is available here: +# http://www.gnu.org/software/autoconf-archive/ax_lib_readline.html + +#serial 6 + +AU_ALIAS([VL_LIB_READLINE], [AX_LIB_READLINE_LLDPD]) +AC_DEFUN([AX_LIB_READLINE_LLDPD], [ + AC_CACHE_CHECK([for a readline compatible library], + ax_cv_lib_readline, [ + _save_LIBS="$LIBS" + for readline_lib in readline edit editline; do + for termcap_lib in "" termcap curses ncurses; do + if test -z "$termcap_lib"; then + TRY_LIB="-l$readline_lib" + else + TRY_LIB="-l$readline_lib -l$termcap_lib" + fi + LIBS="$ORIG_LIBS $TRY_LIB" + for readline_func in readline rl_insert_text rl_forced_update_display; do + AC_TRY_LINK_FUNC($readline_func, ax_cv_lib_readline="$TRY_LIB", ax_cv_lib_readline="") + if test -z "$ax_cv_lib_readline"; then + break + fi + done + if test -n "$ax_cv_lib_readline"; then + break + fi + done + if test -n "$ax_cv_lib_readline"; then + break + fi + done + if test -z "$ax_cv_lib_readline"; then + ax_cv_lib_readline="no" + fi + LIBS="$_save_LIBS" + ]) + + if test "$ax_cv_lib_readline" != "no"; then + READLINE_LIBS="$ax_cv_lib_readline" + AC_SUBST(READLINE_LIBS) + + _save_LIBS="$LIBS" + LIBS="$LIBS $READLINE_LIBS" + AC_DEFINE(HAVE_LIBREADLINE, 1, + [Define if you have a readline compatible library]) + AC_CHECK_HEADERS(readline.h readline/readline.h) + AC_CACHE_CHECK([whether readline supports history], + ax_cv_lib_readline_history, [ + ax_cv_lib_readline_history="no" + AC_TRY_LINK_FUNC(add_history, ax_cv_lib_readline_history="yes") + ]) + if test "$ax_cv_lib_readline_history" = "yes"; then + AC_DEFINE(HAVE_READLINE_HISTORY, 1, + [Define if your readline library has \`add_history']) + AC_CHECK_HEADERS(history.h readline/history.h) + fi + + LIBS="$_save_LIBS" + fi +])dnl diff --git a/m4/ax_prog_doxygen.m4 b/m4/ax_prog_doxygen.m4 new file mode 100644 index 0000000000000000000000000000000000000000..44b22b00abc655f0355163b03caef5eb3dd67866 --- /dev/null +++ b/m4/ax_prog_doxygen.m4 @@ -0,0 +1,532 @@ +# =========================================================================== +# http://www.gnu.org/software/autoconf-archive/ax_prog_doxygen.html +# =========================================================================== +# +# SYNOPSIS +# +# DX_INIT_DOXYGEN(PROJECT-NAME, DOXYFILE-PATH, [OUTPUT-DIR]) +# DX_DOXYGEN_FEATURE(ON|OFF) +# DX_DOT_FEATURE(ON|OFF) +# DX_HTML_FEATURE(ON|OFF) +# DX_CHM_FEATURE(ON|OFF) +# DX_CHI_FEATURE(ON|OFF) +# DX_MAN_FEATURE(ON|OFF) +# DX_RTF_FEATURE(ON|OFF) +# DX_XML_FEATURE(ON|OFF) +# DX_PDF_FEATURE(ON|OFF) +# DX_PS_FEATURE(ON|OFF) +# +# DESCRIPTION +# +# The DX_*_FEATURE macros control the default setting for the given +# Doxygen feature. Supported features are 'DOXYGEN' itself, 'DOT' for +# generating graphics, 'HTML' for plain HTML, 'CHM' for compressed HTML +# help (for MS users), 'CHI' for generating a seperate .chi file by the +# .chm file, and 'MAN', 'RTF', 'XML', 'PDF' and 'PS' for the appropriate +# output formats. The environment variable DOXYGEN_PAPER_SIZE may be +# specified to override the default 'a4wide' paper size. +# +# By default, HTML, PDF and PS documentation is generated as this seems to +# be the most popular and portable combination. MAN pages created by +# Doxygen are usually problematic, though by picking an appropriate subset +# and doing some massaging they might be better than nothing. CHM and RTF +# are specific for MS (note that you can't generate both HTML and CHM at +# the same time). The XML is rather useless unless you apply specialized +# post-processing to it. +# +# The macros mainly control the default state of the feature. The use can +# override the default by specifying --enable or --disable. The macros +# ensure that contradictory flags are not given (e.g., +# --enable-doxygen-html and --enable-doxygen-chm, +# --enable-doxygen-anything with --disable-doxygen, etc.) Finally, each +# feature will be automatically disabled (with a warning) if the required +# programs are missing. +# +# Once all the feature defaults have been specified, call DX_INIT_DOXYGEN +# with the following parameters: a one-word name for the project for use +# as a filename base etc., an optional configuration file name (the +# default is 'Doxyfile', the same as Doxygen's default), and an optional +# output directory name (the default is 'doxygen-doc'). +# +# Automake Support +# +# The following is a template aminclude.am file for use with Automake. +# Make targets and variables values are controlled by the various +# DX_COND_* conditionals set by autoconf. +# +# The provided targets are: +# +# doxygen-doc: Generate all doxygen documentation. +# +# doxygen-run: Run doxygen, which will generate some of the +# documentation (HTML, CHM, CHI, MAN, RTF, XML) +# but will not do the post processing required +# for the rest of it (PS, PDF, and some MAN). +# +# doxygen-man: Rename some doxygen generated man pages. +# +# doxygen-ps: Generate doxygen PostScript documentation. +# +# doxygen-pdf: Generate doxygen PDF documentation. +# +# Note that by default these are not integrated into the automake targets. +# If doxygen is used to generate man pages, you can achieve this +# integration by setting man3_MANS to the list of man pages generated and +# then adding the dependency: +# +# $(man3_MANS): doxygen-doc +# +# This will cause make to run doxygen and generate all the documentation. +# +# The following variable is intended for use in Makefile.am: +# +# DX_CLEANFILES = everything to clean. +# +# Then add this variable to MOSTLYCLEANFILES. +# +# ----- begin aminclude.am ------------------------------------- +# +# ## --------------------------------- ## +# ## Format-independent Doxygen rules. ## +# ## --------------------------------- ## +# +# if DX_COND_doc +# +# ## ------------------------------- ## +# ## Rules specific for HTML output. ## +# ## ------------------------------- ## +# +# if DX_COND_html +# +# DX_CLEAN_HTML = @DX_DOCDIR@/html +# +# endif DX_COND_html +# +# ## ------------------------------ ## +# ## Rules specific for CHM output. ## +# ## ------------------------------ ## +# +# if DX_COND_chm +# +# DX_CLEAN_CHM = @DX_DOCDIR@/chm +# +# if DX_COND_chi +# +# DX_CLEAN_CHI = @DX_DOCDIR@/@PACKAGE@.chi +# +# endif DX_COND_chi +# +# endif DX_COND_chm +# +# ## ------------------------------ ## +# ## Rules specific for MAN output. ## +# ## ------------------------------ ## +# +# if DX_COND_man +# +# DX_CLEAN_MAN = @DX_DOCDIR@/man +# +# endif DX_COND_man +# +# ## ------------------------------ ## +# ## Rules specific for RTF output. ## +# ## ------------------------------ ## +# +# if DX_COND_rtf +# +# DX_CLEAN_RTF = @DX_DOCDIR@/rtf +# +# endif DX_COND_rtf +# +# ## ------------------------------ ## +# ## Rules specific for XML output. ## +# ## ------------------------------ ## +# +# if DX_COND_xml +# +# DX_CLEAN_XML = @DX_DOCDIR@/xml +# +# endif DX_COND_xml +# +# ## ----------------------------- ## +# ## Rules specific for PS output. ## +# ## ----------------------------- ## +# +# if DX_COND_ps +# +# DX_CLEAN_PS = @DX_DOCDIR@/@PACKAGE@.ps +# +# DX_PS_GOAL = doxygen-ps +# +# doxygen-ps: @DX_DOCDIR@/@PACKAGE@.ps +# +# @DX_DOCDIR@/@PACKAGE@.ps: @DX_DOCDIR@/@PACKAGE@.tag +# cd @DX_DOCDIR@/latex; \ +# rm -f *.aux *.toc *.idx *.ind *.ilg *.log *.out; \ +# $(DX_LATEX) refman.tex; \ +# $(MAKEINDEX_PATH) refman.idx; \ +# $(DX_LATEX) refman.tex; \ +# countdown=5; \ +# while $(DX_EGREP) 'Rerun (LaTeX|to get cross-references right)' \ +# refman.log > /dev/null 2>&1 \ +# && test $$countdown -gt 0; do \ +# $(DX_LATEX) refman.tex; \ +# countdown=`expr $$countdown - 1`; \ +# done; \ +# $(DX_DVIPS) -o ../@PACKAGE@.ps refman.dvi +# +# endif DX_COND_ps +# +# ## ------------------------------ ## +# ## Rules specific for PDF output. ## +# ## ------------------------------ ## +# +# if DX_COND_pdf +# +# DX_CLEAN_PDF = @DX_DOCDIR@/@PACKAGE@.pdf +# +# DX_PDF_GOAL = doxygen-pdf +# +# doxygen-pdf: @DX_DOCDIR@/@PACKAGE@.pdf +# +# @DX_DOCDIR@/@PACKAGE@.pdf: @DX_DOCDIR@/@PACKAGE@.tag +# cd @DX_DOCDIR@/latex; \ +# rm -f *.aux *.toc *.idx *.ind *.ilg *.log *.out; \ +# $(DX_PDFLATEX) refman.tex; \ +# $(DX_MAKEINDEX) refman.idx; \ +# $(DX_PDFLATEX) refman.tex; \ +# countdown=5; \ +# while $(DX_EGREP) 'Rerun (LaTeX|to get cross-references right)' \ +# refman.log > /dev/null 2>&1 \ +# && test $$countdown -gt 0; do \ +# $(DX_PDFLATEX) refman.tex; \ +# countdown=`expr $$countdown - 1`; \ +# done; \ +# mv refman.pdf ../@PACKAGE@.pdf +# +# endif DX_COND_pdf +# +# ## ------------------------------------------------- ## +# ## Rules specific for LaTeX (shared for PS and PDF). ## +# ## ------------------------------------------------- ## +# +# if DX_COND_latex +# +# DX_CLEAN_LATEX = @DX_DOCDIR@/latex +# +# endif DX_COND_latex +# +# .PHONY: doxygen-run doxygen-doc $(DX_PS_GOAL) $(DX_PDF_GOAL) +# +# .INTERMEDIATE: doxygen-run $(DX_PS_GOAL) $(DX_PDF_GOAL) +# +# doxygen-run: @DX_DOCDIR@/@PACKAGE@.tag +# +# doxygen-doc: doxygen-run $(DX_PS_GOAL) $(DX_PDF_GOAL) +# +# @DX_DOCDIR@/@PACKAGE@.tag: $(DX_CONFIG) $(pkginclude_HEADERS) +# rm -rf @DX_DOCDIR@ +# $(DX_ENV) $(DX_DOXYGEN) $(srcdir)/$(DX_CONFIG) +# +# DX_CLEANFILES = \ +# @DX_DOCDIR@/@PACKAGE@.tag \ +# -r \ +# $(DX_CLEAN_HTML) \ +# $(DX_CLEAN_CHM) \ +# $(DX_CLEAN_CHI) \ +# $(DX_CLEAN_MAN) \ +# $(DX_CLEAN_RTF) \ +# $(DX_CLEAN_XML) \ +# $(DX_CLEAN_PS) \ +# $(DX_CLEAN_PDF) \ +# $(DX_CLEAN_LATEX) +# +# endif DX_COND_doc +# +# ----- end aminclude.am --------------------------------------- +# +# LICENSE +# +# Copyright (c) 2009 Oren Ben-Kiki +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. This file is offered as-is, without any +# warranty. + +#serial 12 + +## ----------## +## Defaults. ## +## ----------## + +DX_ENV="" +AC_DEFUN([DX_FEATURE_doc], ON) +AC_DEFUN([DX_FEATURE_dot], OFF) +AC_DEFUN([DX_FEATURE_man], OFF) +AC_DEFUN([DX_FEATURE_html], ON) +AC_DEFUN([DX_FEATURE_chm], OFF) +AC_DEFUN([DX_FEATURE_chi], OFF) +AC_DEFUN([DX_FEATURE_rtf], OFF) +AC_DEFUN([DX_FEATURE_xml], OFF) +AC_DEFUN([DX_FEATURE_pdf], ON) +AC_DEFUN([DX_FEATURE_ps], ON) + +## --------------- ## +## Private macros. ## +## --------------- ## + +# DX_ENV_APPEND(VARIABLE, VALUE) +# ------------------------------ +# Append VARIABLE="VALUE" to DX_ENV for invoking doxygen. +AC_DEFUN([DX_ENV_APPEND], [AC_SUBST([DX_ENV], ["$DX_ENV $1='$2'"])]) + +# DX_DIRNAME_EXPR +# --------------- +# Expand into a shell expression prints the directory part of a path. +AC_DEFUN([DX_DIRNAME_EXPR], + [[expr ".$1" : '\(\.\)[^/]*$' \| "x$1" : 'x\(.*\)/[^/]*$']]) + +# DX_IF_FEATURE(FEATURE, IF-ON, IF-OFF) +# ------------------------------------- +# Expands according to the M4 (static) status of the feature. +AC_DEFUN([DX_IF_FEATURE], [ifelse(DX_FEATURE_$1, ON, [$2], [$3])]) + +# DX_REQUIRE_PROG(VARIABLE, PROGRAM) +# ---------------------------------- +# Require the specified program to be found for the DX_CURRENT_FEATURE to work. +AC_DEFUN([DX_REQUIRE_PROG], [ +AC_PATH_TOOL([$1], [$2]) +if test "$DX_FLAG_[]DX_CURRENT_FEATURE$$1" = 1; then + AC_MSG_WARN([$2 not found - will not DX_CURRENT_DESCRIPTION]) + AC_SUBST(DX_FLAG_[]DX_CURRENT_FEATURE, 0) +fi +]) + +# DX_TEST_FEATURE(FEATURE) +# ------------------------ +# Expand to a shell expression testing whether the feature is active. +AC_DEFUN([DX_TEST_FEATURE], [test "$DX_FLAG_$1" = 1]) + +# DX_CHECK_DEPEND(REQUIRED_FEATURE, REQUIRED_STATE) +# ------------------------------------------------- +# Verify that a required features has the right state before trying to turn on +# the DX_CURRENT_FEATURE. +AC_DEFUN([DX_CHECK_DEPEND], [ +test "$DX_FLAG_$1" = "$2" \ +|| AC_MSG_ERROR([doxygen-DX_CURRENT_FEATURE ifelse([$2], 1, + requires, contradicts) doxygen-DX_CURRENT_FEATURE]) +]) + +# DX_CLEAR_DEPEND(FEATURE, REQUIRED_FEATURE, REQUIRED_STATE) +# ---------------------------------------------------------- +# Turn off the DX_CURRENT_FEATURE if the required feature is off. +AC_DEFUN([DX_CLEAR_DEPEND], [ +test "$DX_FLAG_$1" = "$2" || AC_SUBST(DX_FLAG_[]DX_CURRENT_FEATURE, 0) +]) + +# DX_FEATURE_ARG(FEATURE, DESCRIPTION, +# CHECK_DEPEND, CLEAR_DEPEND, +# REQUIRE, DO-IF-ON, DO-IF-OFF) +# -------------------------------------------- +# Parse the command-line option controlling a feature. CHECK_DEPEND is called +# if the user explicitly turns the feature on (and invokes DX_CHECK_DEPEND), +# otherwise CLEAR_DEPEND is called to turn off the default state if a required +# feature is disabled (using DX_CLEAR_DEPEND). REQUIRE performs additional +# requirement tests (DX_REQUIRE_PROG). Finally, an automake flag is set and +# DO-IF-ON or DO-IF-OFF are called according to the final state of the feature. +AC_DEFUN([DX_ARG_ABLE], [ + AC_DEFUN([DX_CURRENT_FEATURE], [$1]) + AC_DEFUN([DX_CURRENT_DESCRIPTION], [$2]) + AC_ARG_ENABLE(doxygen-$1, + [AS_HELP_STRING(DX_IF_FEATURE([$1], [--disable-doxygen-$1], + [--enable-doxygen-$1]), + DX_IF_FEATURE([$1], [don't $2], [$2]))], + [ +case "$enableval" in +#( +y|Y|yes|Yes|YES) + AC_SUBST([DX_FLAG_$1], 1) + $3 +;; #( +n|N|no|No|NO) + AC_SUBST([DX_FLAG_$1], 0) +;; #( +*) + AC_MSG_ERROR([invalid value '$enableval' given to doxygen-$1]) +;; +esac +], [ +AC_SUBST([DX_FLAG_$1], [DX_IF_FEATURE([$1], 1, 0)]) +$4 +]) +if DX_TEST_FEATURE([$1]); then + $5 + : +fi +AM_CONDITIONAL(DX_COND_$1, DX_TEST_FEATURE([$1])) +if DX_TEST_FEATURE([$1]); then + $6 + : +else + $7 + : +fi +]) + +## -------------- ## +## Public macros. ## +## -------------- ## + +# DX_XXX_FEATURE(DEFAULT_STATE) +# ----------------------------- +AC_DEFUN([DX_DOXYGEN_FEATURE], [AC_DEFUN([DX_FEATURE_doc], [$1])]) +AC_DEFUN([DX_DOT_FEATURE], [AC_DEFUN([DX_FEATURE_dot], [$1])]) +AC_DEFUN([DX_MAN_FEATURE], [AC_DEFUN([DX_FEATURE_man], [$1])]) +AC_DEFUN([DX_HTML_FEATURE], [AC_DEFUN([DX_FEATURE_html], [$1])]) +AC_DEFUN([DX_CHM_FEATURE], [AC_DEFUN([DX_FEATURE_chm], [$1])]) +AC_DEFUN([DX_CHI_FEATURE], [AC_DEFUN([DX_FEATURE_chi], [$1])]) +AC_DEFUN([DX_RTF_FEATURE], [AC_DEFUN([DX_FEATURE_rtf], [$1])]) +AC_DEFUN([DX_XML_FEATURE], [AC_DEFUN([DX_FEATURE_xml], [$1])]) +AC_DEFUN([DX_XML_FEATURE], [AC_DEFUN([DX_FEATURE_xml], [$1])]) +AC_DEFUN([DX_PDF_FEATURE], [AC_DEFUN([DX_FEATURE_pdf], [$1])]) +AC_DEFUN([DX_PS_FEATURE], [AC_DEFUN([DX_FEATURE_ps], [$1])]) + +# DX_INIT_DOXYGEN(PROJECT, [CONFIG-FILE], [OUTPUT-DOC-DIR]) +# --------------------------------------------------------- +# PROJECT also serves as the base name for the documentation files. +# The default CONFIG-FILE is "Doxyfile" and OUTPUT-DOC-DIR is "doxygen-doc". +AC_DEFUN([DX_INIT_DOXYGEN], [ + +# Files: +AC_SUBST([DX_PROJECT], [$1]) +AC_SUBST([DX_CONFIG], [ifelse([$2], [], Doxyfile, [$2])]) +AC_SUBST([DX_DOCDIR], [ifelse([$3], [], doxygen-doc, [$3])]) + +# Environment variables used inside doxygen.cfg: +DX_ENV_APPEND(SRCDIR, $srcdir) +DX_ENV_APPEND(PROJECT, $DX_PROJECT) +DX_ENV_APPEND(DOCDIR, $DX_DOCDIR) +DX_ENV_APPEND(VERSION, $PACKAGE_VERSION) + +# Doxygen itself: +DX_ARG_ABLE(doc, [generate any doxygen documentation], + [], + [], + [DX_REQUIRE_PROG([DX_DOXYGEN], doxygen) + DX_REQUIRE_PROG([DX_PERL], perl)], + [DX_ENV_APPEND(PERL_PATH, $DX_PERL)]) + +# Dot for graphics: +DX_ARG_ABLE(dot, [generate graphics for doxygen documentation], + [DX_CHECK_DEPEND(doc, 1)], + [DX_CLEAR_DEPEND(doc, 1)], + [DX_REQUIRE_PROG([DX_DOT], dot)], + [DX_ENV_APPEND(HAVE_DOT, YES) + DX_ENV_APPEND(DOT_PATH, [`DX_DIRNAME_EXPR($DX_DOT)`])], + [DX_ENV_APPEND(HAVE_DOT, NO)]) + +# Man pages generation: +DX_ARG_ABLE(man, [generate doxygen manual pages], + [DX_CHECK_DEPEND(doc, 1)], + [DX_CLEAR_DEPEND(doc, 1)], + [], + [DX_ENV_APPEND(GENERATE_MAN, YES)], + [DX_ENV_APPEND(GENERATE_MAN, NO)]) + +# RTF file generation: +DX_ARG_ABLE(rtf, [generate doxygen RTF documentation], + [DX_CHECK_DEPEND(doc, 1)], + [DX_CLEAR_DEPEND(doc, 1)], + [], + [DX_ENV_APPEND(GENERATE_RTF, YES)], + [DX_ENV_APPEND(GENERATE_RTF, NO)]) + +# XML file generation: +DX_ARG_ABLE(xml, [generate doxygen XML documentation], + [DX_CHECK_DEPEND(doc, 1)], + [DX_CLEAR_DEPEND(doc, 1)], + [], + [DX_ENV_APPEND(GENERATE_XML, YES)], + [DX_ENV_APPEND(GENERATE_XML, NO)]) + +# (Compressed) HTML help generation: +DX_ARG_ABLE(chm, [generate doxygen compressed HTML help documentation], + [DX_CHECK_DEPEND(doc, 1)], + [DX_CLEAR_DEPEND(doc, 1)], + [DX_REQUIRE_PROG([DX_HHC], hhc)], + [DX_ENV_APPEND(HHC_PATH, $DX_HHC) + DX_ENV_APPEND(GENERATE_HTML, YES) + DX_ENV_APPEND(GENERATE_HTMLHELP, YES)], + [DX_ENV_APPEND(GENERATE_HTMLHELP, NO)]) + +# Seperate CHI file generation. +DX_ARG_ABLE(chi, [generate doxygen seperate compressed HTML help index file], + [DX_CHECK_DEPEND(chm, 1)], + [DX_CLEAR_DEPEND(chm, 1)], + [], + [DX_ENV_APPEND(GENERATE_CHI, YES)], + [DX_ENV_APPEND(GENERATE_CHI, NO)]) + +# Plain HTML pages generation: +DX_ARG_ABLE(html, [generate doxygen plain HTML documentation], + [DX_CHECK_DEPEND(doc, 1) DX_CHECK_DEPEND(chm, 0)], + [DX_CLEAR_DEPEND(doc, 1) DX_CLEAR_DEPEND(chm, 0)], + [], + [DX_ENV_APPEND(GENERATE_HTML, YES)], + [DX_TEST_FEATURE(chm) || DX_ENV_APPEND(GENERATE_HTML, NO)]) + +# PostScript file generation: +DX_ARG_ABLE(ps, [generate doxygen PostScript documentation], + [DX_CHECK_DEPEND(doc, 1)], + [DX_CLEAR_DEPEND(doc, 1)], + [DX_REQUIRE_PROG([DX_LATEX], latex) + DX_REQUIRE_PROG([DX_MAKEINDEX], makeindex) + DX_REQUIRE_PROG([DX_DVIPS], dvips) + DX_REQUIRE_PROG([DX_EGREP], egrep)]) + +# PDF file generation: +DX_ARG_ABLE(pdf, [generate doxygen PDF documentation], + [DX_CHECK_DEPEND(doc, 1)], + [DX_CLEAR_DEPEND(doc, 1)], + [DX_REQUIRE_PROG([DX_PDFLATEX], pdflatex) + DX_REQUIRE_PROG([DX_MAKEINDEX], makeindex) + DX_REQUIRE_PROG([DX_EGREP], egrep)]) + +# LaTeX generation for PS and/or PDF: +AM_CONDITIONAL(DX_COND_latex, DX_TEST_FEATURE(ps) || DX_TEST_FEATURE(pdf)) +if DX_TEST_FEATURE(ps) || DX_TEST_FEATURE(pdf); then + DX_ENV_APPEND(GENERATE_LATEX, YES) +else + DX_ENV_APPEND(GENERATE_LATEX, NO) +fi + +# Paper size for PS and/or PDF: +AC_ARG_VAR(DOXYGEN_PAPER_SIZE, + [a4wide (default), a4, letter, legal or executive]) +case "$DOXYGEN_PAPER_SIZE" in +#( +"") + AC_SUBST(DOXYGEN_PAPER_SIZE, "") +;; #( +a4wide|a4|letter|legal|executive) + DX_ENV_APPEND(PAPER_SIZE, $DOXYGEN_PAPER_SIZE) +;; #( +*) + AC_MSG_ERROR([unknown DOXYGEN_PAPER_SIZE='$DOXYGEN_PAPER_SIZE']) +;; +esac + +#For debugging: +#echo DX_FLAG_doc=$DX_FLAG_doc +#echo DX_FLAG_dot=$DX_FLAG_dot +#echo DX_FLAG_man=$DX_FLAG_man +#echo DX_FLAG_html=$DX_FLAG_html +#echo DX_FLAG_chm=$DX_FLAG_chm +#echo DX_FLAG_chi=$DX_FLAG_chi +#echo DX_FLAG_rtf=$DX_FLAG_rtf +#echo DX_FLAG_xml=$DX_FLAG_xml +#echo DX_FLAG_pdf=$DX_FLAG_pdf +#echo DX_FLAG_ps=$DX_FLAG_ps +#echo DX_ENV=$DX_ENV +]) diff --git a/m4/config_subdirs.m4 b/m4/config_subdirs.m4 new file mode 100644 index 0000000000000000000000000000000000000000..83d9594623446dec8d963c389172ab12f8ca48c7 --- /dev/null +++ b/m4/config_subdirs.m4 @@ -0,0 +1,104 @@ +# +# lldp_CONFIG_SUBDIRS +# +# This is almost like AC_CONFIG_SUBDIRS but it will take additional +# arguments for ./configure. Also, ./configure is not delayed. Be sure +# to call that late enough. + +AC_DEFUN([lldp_CONFIG_SUBDIRS], [ + AC_CONFIG_SUBDIRS([$1]) + ac_dir="m4_normalize([$1])" + if test -f "$srcdir/$ac_dir/configure"; then + ac_sub_configure_args= + ac_prev= + eval "set x $ac_configure_args" + shift + for ac_arg + do + if test -n "$ac_prev"; then + ac_prev= + continue + fi + case $ac_arg in + -cache-file | --cache-file | --cache-fil | --cache-fi \ + | --cache-f | --cache- | --cache | --cach | --cac | --ca | --c) + ac_prev=cache_file ;; + -cache-file=* | --cache-file=* | --cache-fil=* | --cache-fi=* \ + | --cache-f=* | --cache-=* | --cache=* | --cach=* | --cac=* | --ca=* \ + | --c=*) + ;; + --config-cache | -C) + ;; + -srcdir | --srcdir | --srcdi | --srcd | --src | --sr) + ac_prev=srcdir ;; + -srcdir=* | --srcdir=* | --srcdi=* | --srcd=* | --src=* | --sr=*) + ;; + -prefix | --prefix | --prefi | --pref | --pre | --pr | --p) + ac_prev=prefix ;; + -prefix=* | --prefix=* | --prefi=* | --pref=* | --pre=* | --pr=* | --p=*) + ;; + --disable-option-checking) + ;; + *) + case $ac_arg in + *\'*) ac_arg=`AS_ECHO(["$ac_arg"]) | sed "s/'/'\\\\\\\\''/g"` ;; + esac + AS_VAR_APPEND([ac_sub_configure_args], [" '$ac_arg'"]) ;; + esac + done + + # Always prepend --prefix to ensure using the same prefix + # in subdir configurations. + ac_arg="--prefix=$prefix" + case $ac_arg in + *\'*) ac_arg=`AS_ECHO(["$ac_arg"]) | sed "s/'/'\\\\\\\\''/g"` ;; + esac + ac_sub_configure_args="'$ac_arg' $ac_sub_configure_args" + + # Always prepend --disable-option-checking to silence warnings, since + # different subdirs can have different --enable and --with options. + ac_sub_configure_args="--disable-option-checking $ac_sub_configure_args" + + # Silent rules + case $enable_silent_rules in + no) ac_sub_configure_args="$ac_sub_configure_args --disable-silent-rules" ;; + *) ac_sub_configure_args="$ac_sub_configure_args --enable-silent-rules" ;; + esac + + # Add additional options + ac_sub_configure_args="$ac_sub_configure_args $2" + + ac_popdir=`pwd` + + ac_msg="=== configuring in $ac_dir (`pwd`/$ac_dir)" + _AS_ECHO_LOG([$ac_msg]) + _AS_ECHO([$ac_msg]) + AS_MKDIR_P(["$ac_dir"]) + _AC_SRCDIRS(["$ac_dir"]) + + cd "$ac_dir" + + ac_sub_configure=$ac_srcdir/configure + + # Make the cache file name correct relative to the subdirectory. + case $cache_file in + [[\\/]]* | ?:[[\\/]]* ) ac_sub_cache_file=$cache_file ;; + *) # Relative name. + ac_sub_cache_file=$ac_top_build_prefix$cache_file ;; + esac + + AC_MSG_NOTICE([running $SHELL $ac_sub_configure $ac_sub_configure_args --cache-file=$ac_sub_cache_file --srcdir=$ac_srcdir]) + # The eval makes quoting arguments work. + eval "\$SHELL \"\$ac_sub_configure\" $ac_sub_configure_args \ + --cache-file=\"\$ac_sub_cache_file\" --srcdir=\"\$ac_srcdir\"" || + AC_MSG_ERROR([$ac_sub_configure failed for $ac_dir]) + + ac_msg="=== end of configure in $ac_dir (`pwd`/$ac_dir)" + _AS_ECHO_LOG([$ac_msg]) + _AS_ECHO([$ac_msg]) + cd "$ac_popdir" + fi +]) + +# Dummy AC_CONFIG_SUBDIRS for autoreconf tracing +AC_DEFUN([AC_CONFIG_SUBDIRS], []) diff --git a/m4/ld-version-script.m4 b/m4/ld-version-script.m4 new file mode 100644 index 0000000000000000000000000000000000000000..f8b4a5c51fec4382121a01854214320f50242cd6 --- /dev/null +++ b/m4/ld-version-script.m4 @@ -0,0 +1,53 @@ +# ld-version-script.m4 serial 3 +dnl Copyright (C) 2008-2014 Free Software Foundation, Inc. +dnl This file is free software; the Free Software Foundation +dnl gives unlimited permission to copy and/or distribute it, +dnl with or without modifications, as long as this notice is preserved. + +dnl From Simon Josefsson + +# FIXME: The test below returns a false positive for mingw +# cross-compiles, 'local:' statements does not reduce number of +# exported symbols in a DLL. Use --disable-ld-version-script to work +# around the problem. + +# gl_LD_VERSION_SCRIPT +# -------------------- +# Check if LD supports linker scripts, and define automake conditional +# HAVE_LD_VERSION_SCRIPT if so. +AC_DEFUN([gl_LD_VERSION_SCRIPT], +[ + AC_ARG_ENABLE([ld-version-script], + AS_HELP_STRING([--enable-ld-version-script], + [enable linker version script (default is enabled when possible)]), + [have_ld_version_script=$enableval], []) + if test -z "$have_ld_version_script"; then + AC_MSG_CHECKING([if LD -Wl,--version-script works]) + save_LDFLAGS="$LDFLAGS" + LDFLAGS="$LDFLAGS -Wl,--version-script=conftest.map" + cat > conftest.map < conftest.map <= 2], [ + AC_DEFINE([HAVE_LINUX_CAPABILITIES], 1, [Define to indicate support of linux capabilities]) + ], [ + libcap_LIBS=-lcap + libcap_CFLAGS= + _save_libs="$LIBS" + LIBS="$LIBS ${libcap_LIBS}" + AC_MSG_CHECKING([libcap (without pkg-config)]) + AC_TRY_LINK_FUNC([cap_set_proc], [ + AC_DEFINE([HAVE_LINUX_CAPABILITIES], 1, [Define to indicate support of linux capabilities]) + AC_MSG_RESULT(yes) + ], [ + libcap_LIBS= + AC_MSG_RESULT(no) + ]) + LIBS="$_save_libs" + ]) + AC_SUBST([libcap_LIBS]) + AC_SUBST([libcap_CFLAGS]) +]) diff --git a/m4/libevent.m4 b/m4/libevent.m4 new file mode 100644 index 0000000000000000000000000000000000000000..62e49801b69c2430e3a4e9a0ad93005ecf32c38b --- /dev/null +++ b/m4/libevent.m4 @@ -0,0 +1,71 @@ +# +# lldp_CHECK_LIBEVENT +# + +AC_DEFUN([lldp_CHECK_LIBEVENT], [ + # Do we require embedded libevent? + AC_ARG_WITH([embedded-libevent], + AS_HELP_STRING( + [--with-embedded-libevent], + [Use embedded libevent @<:@default=auto@:>@] + ), [], [with_embedded_libevent=auto]) + if test x"$with_embedded_libevent" = x"yes"; then + LIBEVENT_EMBEDDED=1 + else + # If not forced, check first with pkg-config + PKG_CHECK_MODULES([libevent], [libevent >= 2.0.5], [ + # Check if we have a working libevent + AC_MSG_CHECKING([if system libevent works as expected]) + _save_CFLAGS="$CFLAGS" + _save_LIBS="$LIBS" + CFLAGS="$CFLAGS $libevent_CFLAGS" + LIBS="$LIBS $libevent_LIBS" + AC_LINK_IFELSE([AC_LANG_PROGRAM([[ +@%:@include +@%:@include +@%:@include ]], [[ struct event_base *base = event_base_new(); event_new(base, -1, 0, NULL, NULL); ]])],[ + AC_MSG_RESULT([yes]) + ],[ + if test x"$with_embedded_libevent" = x"auto"; then + AC_MSG_RESULT([no, using shipped libevent]) + LIBEVENT_EMBEDDED=1 + else + AC_MSG_ERROR([*** unusable system libevent]) + fi + ]) + CFLAGS="$_save_CFLAGS" + LIBS="$_save_LIBS" + ], [ + # No appropriate version, let's use the shipped copy if possible + if test x"$with_embedded_libevent" = x"auto"; then + AC_MSG_NOTICE([using shipped libevent]) + LIBEVENT_EMBEDDED=1 + else + AC_MSG_ERROR([*** libevent not found]) + fi + ]) + fi + + if test x"$LIBEVENT_EMBEDDED" != x; then + unset libevent_LIBS + libevent_CFLAGS="-I\$(top_srcdir)/libevent/include -I\$(top_builddir)/libevent/include" + libevent_LDFLAGS="\$(top_builddir)/libevent/libevent.la" + fi + + # Call ./configure in libevent. Need it for make dist... + libevent_configure_args="$libevent_configure_args --disable-libevent-regress" + libevent_configure_args="$libevent_configure_args --disable-thread-support" + libevent_configure_args="$libevent_configure_args --disable-openssl" + libevent_configure_args="$libevent_configure_args --disable-malloc-replacement" + libevent_configure_args="$libevent_configure_args --disable-debug-mode" + libevent_configure_args="$libevent_configure_args --enable-function-sections" + libevent_configure_args="$libevent_configure_args --disable-shared" + libevent_configure_args="$libevent_configure_args --with-pic" + libevent_configure_args="$libevent_configure_args --enable-static" + lldp_CONFIG_SUBDIRS([libevent], [$libevent_configure_args]) + + AM_CONDITIONAL([LIBEVENT_EMBEDDED], [test x"$LIBEVENT_EMBEDDED" != x]) + AC_SUBST([libevent_LIBS]) + AC_SUBST([libevent_CFLAGS]) + AC_SUBST([libevent_LDFLAGS]) +]) diff --git a/m4/os.m4 b/m4/os.m4 new file mode 100644 index 0000000000000000000000000000000000000000..3762c69fa007abfb809c0b5f840f3c423d9b6118 --- /dev/null +++ b/m4/os.m4 @@ -0,0 +1,44 @@ +# +# lldp_CHECK_OS +# +# List of supported OS. +# +AC_DEFUN([lldp_DEFINE_OS], [dnl + case $host_os in + $1) + os="$2" + AC_DEFINE_UNQUOTED(HOST_OS_$3, 1, [Host operating system is $2]) + ;; + esac + AM_CONDITIONAL(HOST_OS_$3, test x"$os" = x"$2")dnl +]) + +AC_DEFUN([lldp_CHECK_OS], [ + AC_CANONICAL_HOST + AC_MSG_CHECKING([if host OS is supported]) + + lldp_DEFINE_OS(linux*, Linux, LINUX) + + if test x"$os" = x; then + AC_MSG_RESULT(no) + AC_MSG_ERROR([*** unsupported OS $host_os]) + fi + AC_MSG_RESULT([yes ($os)]) +]) + +# Enable some additional CFLAGS depending on the OS +AC_DEFUN([lldp_CFLAGS_OS], [ + # Most of what we want can be enabled nowadays with _GNU_SOURCE + AX_CFLAGS_GCC_OPTION([-D_GNU_SOURCE], [LLDP_CPPFLAGS]) dnl GNU systems (asprintf, ...) + + case $host_os in + solaris*) + AX_CFLAGS_GCC_OPTION([-D__EXTENSIONS__], [LLDP_CPPFLAGS]) dnl (CMSG_*) + AX_CFLAGS_GCC_OPTION([-D_XPG4_2], [LLDP_CPPFLAGS]) dnl (CMSG_*) + ;; + hpux*) + AX_CFLAGS_GCC_OPTION([-D_XOPEN_SOURCE=500], [LLDP_CPPFLAGS]) dnl HP-UX + AX_CFLAGS_GCC_OPTION([-D_XOPEN_SOURCE_EXTENDED], [LLDP_CPPFLAGS]) dnl HP-UX + ;; + esac +]) diff --git a/m4/progname.m4 b/m4/progname.m4 new file mode 100644 index 0000000000000000000000000000000000000000..031aba65cbf0cc7dc86ba6b598e63d61d78bb688 --- /dev/null +++ b/m4/progname.m4 @@ -0,0 +1,15 @@ +# +# lldp_CHECK___PROGNAME +# +AC_DEFUN([lldp_CHECK___PROGNAME],[ + AC_CACHE_CHECK([whether libc defines __progname], lldp_cv_check___progname, [ + AC_LINK_IFELSE([AC_LANG_PROGRAM( + [[]], + [[ extern char *__progname; printf("%s", __progname); ]])], + [ lldp_cv_check___progname="yes" ], + [ lldp_cv_check___progname="no" ]) + ]) + if test x"$lldp_cv_check___progname" = x"yes"; then + AC_DEFINE([HAVE___PROGNAME], [1], [Define if libc defines __progname]) + fi +]) diff --git a/m4/seccomp.m4 b/m4/seccomp.m4 new file mode 100644 index 0000000000000000000000000000000000000000..e9134d52b4456f64742d2f4476147d2e3ce7fde4 --- /dev/null +++ b/m4/seccomp.m4 @@ -0,0 +1,19 @@ +# +# lldp_CHECK_SECCOMP +# + +AC_DEFUN([lldp_CHECK_SECCOMP], [ + if test x"$with_seccomp" != x"no"; then + PKG_CHECK_MODULES([libseccomp], [libseccomp >= 1], [ + AC_SUBST([libseccomp_LIBS]) + AC_SUBST([libseccomp_CFLAGS]) + AC_DEFINE_UNQUOTED([USE_SECCOMP], 1, [Define to indicate to enable seccomp support]) + with_seccomp=yes + ], [ + if test x"$with_seccomp" = x"yes"; then + AC_MSG_FAILURE([*** no seccomp support found]) + fi + with_seccomp=no + ]) + fi +]) diff --git a/m4/snmp.m4 b/m4/snmp.m4 new file mode 100644 index 0000000000000000000000000000000000000000..8e4b496fc25e40c7652b4ba40f7928eb84896afa --- /dev/null +++ b/m4/snmp.m4 @@ -0,0 +1,82 @@ +# +# lldp_CHECK_SNMP +# +AC_DEFUN([lldp_CHECK_SNMP], [ + if test x"$with_snmp" != x"no"; then + AC_PATH_TOOL([NETSNMP_CONFIG], [net-snmp-config], [no]) + if test x"$NETSNMP_CONFIG" = x"no"; then + dnl No luck + if test x"$with_snmp" = x"yes"; then + AC_MSG_FAILURE([*** no NetSNMP support found]) + fi + with_snmp=no + else + dnl Check it is working as expected + NETSNMP_LIBS=`${NETSNMP_CONFIG} --agent-libs` + NETSNMP_CFLAGS="`${NETSNMP_CONFIG} --base-cflags` -DNETSNMP_NO_INLINE" + + _save_flags="$CFLAGS" + _save_libs="$LIBS" + CFLAGS="$CFLAGS ${NETSNMP_CFLAGS}" + LIBS="$LIBS ${NETSNMP_LIBS}" + AC_MSG_CHECKING([whether C compiler supports flag "${NETSNMP_CFLAGS} ${NETSNMP_LIBS}" from Net-SNMP]) + AC_LINK_IFELSE([AC_LANG_PROGRAM([ +int main(void); +], +[ +{ + return 0; +} +])],[ + AC_MSG_RESULT(yes) + dnl Is Net-SNMP usable? + AC_CHECK_LIB([netsnmp], [snmp_register_callback], [ + dnl Do we have subagent support? + AC_CHECK_FUNCS([netsnmp_enable_subagent], [ + dnl Yes, we need to check a few more things + AC_SUBST([NETSNMP_LIBS]) + AC_SUBST([NETSNMP_CFLAGS]) + AC_DEFINE_UNQUOTED([USE_SNMP], 1, [Define to indicate to enable SNMP support]) + with_snmp=yes + + dnl Should we use f_create_from_tstring_new or f_create_from_tstring? + AC_CHECK_MEMBERS([netsnmp_tdomain.f_create_from_tstring_new],,,[ +@%:@include +@%:@include +@%:@include + ]) + dnl Do we have a usable header? + AC_CHECK_HEADERS([net-snmp/agent/util_funcs.h],,,[ +@%:@include +@%:@include +@%:@include +@%:@include +@%:@include + ]) + dnl Can we use snmp_select_info2? + AC_CHECK_FUNCS([snmp_select_info2]) + ],[ + if test x"$with_snmp" = x"yes"; then + AC_MSG_ERROR([*** no subagent support in net-snmp]) + fi + with_snmp=no + ]) + ],[ + if test x"$with_snmp" = x"yes"; then + AC_MSG_ERROR([*** unable to use net-snmp]) + fi + with_snmp=no + ]) + ],[ + AC_MSG_RESULT(no) + if test x"$with_snmp" = x"yes"; then + AC_MSG_ERROR([*** incorrect CFLAGS from net-snmp-config]) + fi + with_snmp=no + ]) + + CFLAGS="$_save_flags" + LIBS="$_save_libs" + fi + fi +]) diff --git a/m4/stdint.m4 b/m4/stdint.m4 new file mode 100644 index 0000000000000000000000000000000000000000..96558be0347a3d441c917c00a4cd96140f04bc0d --- /dev/null +++ b/m4/stdint.m4 @@ -0,0 +1,13 @@ +# +# lldp_CHECK_STDINT +# +AC_DEFUN([lldp_CHECK_STDINT], [ + AC_CHECK_TYPES([u_int32_t, uint32_t]) + if test "_$ac_cv_type_uint32_t" = _yes; then + if test "_$ac_cv_type_u_int32_t" = _no; then + AC_DEFINE(u_int8_t, uint8_t, [Compatibility with Linux u_int8_t]) + AC_DEFINE(u_int16_t, uint16_t, [Compatibility with Linux u_int16_t]) + AC_DEFINE(u_int32_t, uint32_t, [Compatibility with Linux u_int32_t]) + AC_DEFINE(u_int64_t, uint64_t, [Compatibility with Linux u_int64_t]) + fi + fi]) diff --git a/m4/systemtap.m4 b/m4/systemtap.m4 new file mode 100644 index 0000000000000000000000000000000000000000..0564e3da8784e3aeb78d855b3946e30081e32752 --- /dev/null +++ b/m4/systemtap.m4 @@ -0,0 +1,17 @@ +# +# lldp_SYSTEMTAP +# +# Check for DTrace/Systemtap support + +AC_DEFUN([lldp_SYSTEMTAP], [ + # Enable systemtap support + lldp_ARG_ENABLE([dtrace], [systemtap/DTrace trace support], [no]) + AM_CONDITIONAL([ENABLE_SYSTEMTAP], [test x"$enable_dtrace" = x"yes"]) + if test x"$enable_dtrace" = x"yes"; then + AC_CHECK_PROGS(DTRACE, dtrace) + if test -z "$DTRACE"; then + AC_MSG_ERROR([*** dtrace command not found]) + fi + AC_CHECK_HEADER([sys/sdt.h],,[AC_MSG_ERROR([*** no sys/sdt.h header found])]) + fi +]) diff --git a/m4/xml2.m4 b/m4/xml2.m4 new file mode 100644 index 0000000000000000000000000000000000000000..763755e149c7ce4f0d7e6bc8e410e25007be7f51 --- /dev/null +++ b/m4/xml2.m4 @@ -0,0 +1,56 @@ +# +# lldp_CHECK_XML2 +# + + +AC_DEFUN([lldp_CHECK_XML2], [ + if test x"$with_xml" != x"no"; then + PKG_CHECK_MODULES([libxml2], [libxml-2.0], [ + dnl Found through pkg-config + AC_DEFINE_UNQUOTED([USE_XML], 1, [Define to indicate to enable XML support]) + with_xml=yes + ],[ + dnl Fallback to xml2-config + AC_PATH_TOOL([XML2_CONFIG], [xml2-config], [no]) + if test x"$XML2_CONFIG" = x"no"; then + dnl No luck + if test x"$with_xml" = x"yes"; then + AC_MSG_FAILURE([*** no libxml2 support found]) + fi + with_xml=no + else + dnl Check that it's working as expected + libxml2_LIBS=`${XML2_CONFIG} --libs` + libxml2_CFLAGS=`${XML2_CONFIG} --cflags` + + _save_flags="$CFLAGS" + _save_libs="$LIBS" + CFLAGS="$CFLAGS ${libxml2_CFLAGS}" + LIBS="$LIBS ${libxml2_LIBS}" + AC_MSG_CHECKING([whether libxml-2 work as expected]) + AC_LINK_IFELSE([AC_LANG_PROGRAM([ +@%:@include +@%:@include +],[ + xmlDocPtr doc; + xmlTextWriterPtr xw = xmlNewTextWriterDoc(&doc, 0); + return (xw != NULL); +])],[ + AC_MSG_RESULT(yes) + AC_SUBST([libxml2_LIBS]) + AC_SUBST([libxml2_CFLAGS]) + AC_DEFINE_UNQUOTED([USE_XML], 1, [Define to indicate to enable XML support]) + with_xml=yes + ],[ + AC_MSG_RESULT(no) + if test x"$with_xml" = x"yes"; then + AC_MSG_FAILURE([*** libxml2 not working as expected]) + fi + with_xml=no + ]) + CFLAGS="$_save_flags" + LIBS="$_save_libs" + fi + ]) + fi +]) diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000000000000000000000000000000000000..19644f3e345dfa578fcb98e59462632d1b55f451 --- /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/client/Makefile.am b/src/client/Makefile.am new file mode 100644 index 0000000000000000000000000000000000000000..c2b0bd842fe51eaf608f0ff8645ce8e66d39cfd1 --- /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 = ub-lldpcli +man_MANS = ub-lldpcli.8 +dist_man_MANS = ub-lldpctl.8 + +install-exec-local: ub-lldpcli + cd $(DESTDIR)$(sbindir) && rm -f ub-lldpctl + cd $(DESTDIR)$(sbindir) && $(LN_S) ub-lldpcli ub-lldpctl +uninstall-local: + cd $(DESTDIR)$(sbindir) && rm -f ub-lldpctl + +ub_lldpcli_SOURCES = client.h lldpcli.c display.c \ + conf.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 +ub_lldpcli_LDADD = \ + $(top_builddir)/src/libcommon-daemon-client.la \ + $(top_builddir)/src/lib/libublldpctl.la \ + @READLINE_LIBS@ +ub_lldpcli_CFLAGS = $(AM_CFLAGS) +ub_lldpcli_LDFLAGS = $(AM_LDFLAGS) $(LLDP_BIN_LDFLAGS) + +if USE_XML +ub_lldpcli_SOURCES += xml_writer.c +ub_lldpcli_CFLAGS += @libxml2_CFLAGS@ +ub_lldpcli_LDADD += @libxml2_LIBS@ +endif + +# Completions +bashcompletiondir = $(datadir)/bash-completion/completions +dist_bashcompletion_DATA = completion/ub-lldpcli +zshcompletiondir = $(datadir)/zsh/site-functions +dist_zshcompletion_DATA = completion/_ub_lldpcli + +# Default configuration +lldpdconfdir = $(sysconfdir)/ub-lldpd.d +dist_lldpdconf_DATA = README.conf + +TEMPLATES = ub-lldpcli.8 +EXTRA_DIST = ub-lldpcli.8.in +CLEANFILES = $(TEMPLATES) +ub-lldpcli.8: ub-lldpcli.8.in +include $(top_srcdir)/edit.am diff --git a/src/client/README.conf b/src/client/README.conf new file mode 100644 index 0000000000000000000000000000000000000000..0165ae28297bda0106c00e3d4d9bd6f15ab015b2 --- /dev/null +++ b/src/client/README.conf @@ -0,0 +1,8 @@ +# You can put ub-lldpd configuration snippets into this directory. +# Upon start, ub-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 ub-lldpcli(8) for more details. diff --git a/src/client/client.h b/src/client/client.h new file mode 100644 index 0000000000000000000000000000000000000000..4750ca0999db5b4c3c308cfd8aef23ca9f4cf726 --- /dev/null +++ b/src/client/client.h @@ -0,0 +1,148 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2012 Vincent Bernat + * + * 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 +#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 +# 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 +# elif defined(HAVE_READLINE_H) +# include +# 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 +# elif defined(HAVE_HISTORY_H) +# include +# else +extern void add_history (); +# endif +#endif +#undef NEWLINE + +extern const char *ctlname; + +/* commands.c */ +#define NEWLINE "" +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*, void *), + int(*execute)(struct lldpctl_conn_t*, struct writer*, + struct cmd_env*, void *), + 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 *, void *); +int cmd_check_env(struct cmd_env *, void *); +int cmd_store_env(struct lldpctl_conn_t *, struct writer *, + struct cmd_env *, void *); +int cmd_store_env_and_pop(struct lldpctl_conn_t *, struct writer *, + struct cmd_env *, void *); +int cmd_store_env_value(struct lldpctl_conn_t *, struct writer *, + struct cmd_env *, void *); +int cmd_store_env_value_and_pop2(struct lldpctl_conn_t *, struct writer *, + struct cmd_env *, void *); +int cmd_store_something_env_value(const char *, struct cmd_env *, + 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 *); + +/* misc.c */ +int contains(const char *, const char *); +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 *); + +/* 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 0000000000000000000000000000000000000000..acd46c0db944f7b0750759a836c8aa2757fcf7ef --- /dev/null +++ b/src/client/commands.c @@ -0,0 +1,821 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2012 Vincent Bernat + * + * 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 +#include +#include +#include +#include +#include +#include +#include + +/** + * 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*, 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*, void *); + 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("ub-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*, void *), + int(*execute)(struct lldpctl_conn_t*, struct writer*, + struct cmd_env*, void *), + void *arg) +{ + struct cmd_node *new = calloc(1, sizeof(struct cmd_node)); + if (new == NULL) + fatalx("ub-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("ub-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("ub-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("ub-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 ub-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("ub-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("ub-lldpctl", "process argument %02d: `%s`", + env.argp, token); + TAILQ_FOREACH(candidate, ¤t->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("ub-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, ¤t->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("ub-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("ub-lldpctl", + "not enough memory to build lock filename"); + rc = -1; + goto end; + } + log_debug("ub-lldpctl", "open %s for locking", lockname); + if ((lockfd = open(lockname, O_RDWR)) == -1) { + log_warn("ub-lldpctl", "cannot open lock %s", lockname); + rc = -1; + goto end; + } + } + if (lockf(lockfd, F_LOCK, 0) == -1) { + log_warn("ub-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("ub-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("ub-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(¤t->subentries)) { + TAILQ_FOREACH(candidate, ¤t->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, 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, 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, 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, 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 + */ +#define CMDENV_POP_COUNT 2 + +int +cmd_store_env_value_and_pop2(struct lldpctl_conn_t *conn, struct writer *w, + struct cmd_env *env, void *key) +{ + return (cmdenv_put(env, key, cmdenv_arg(env)) != -1 && + cmdenv_pop(env, CMDENV_POP_COUNT) != -1); +} + +int +cmd_store_env_value(struct lldpctl_conn_t *conn, struct writer *w, + struct cmd_env *env, void *key) +{ + return (cmdenv_put(env, key, cmdenv_arg(env)) != -1); +} +int +cmd_store_something_env_value(const char *what, + struct cmd_env *env, 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("ub-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"); +} diff --git a/src/client/completion/_ub_lldpcli b/src/client/completion/_ub_lldpcli new file mode 100644 index 0000000000000000000000000000000000000000..3f58d16e21d9afc67f0c827d6a6478aed8d95283 --- /dev/null +++ b/src/client/completion/_ub_lldpcli @@ -0,0 +1,48 @@ +#compdef ub-lldpcli +# +# zsh completion for ub-lldpcli +# +# Copyright (c) 2014 Vincent Bernat +# +# 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. + + +__ub_lldpcli_command () { + local -a completions + completions=(${(f)"$(_call_program commands ub-lldpcli complete ${words[1,$(($CURRENT-1))]})"}) + completions=(${completions:s/ /:/}) + _describe -t ub-lldpcli-command "ub-lldpcli completion" completions "$@" +} + +_ub_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 ub-lldpd]:UNIX socket:_files' \ + '-f[output format]:format:(plain xml json json0 keyvalue)' \ + '*-c[read a configuration file]:configuration file:_files' \ + '(-)*::ub-lldpcli command:__ub_lldpcli_command' +} + + +_ub_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/ub-lldpcli b/src/client/completion/ub-lldpcli new file mode 100644 index 0000000000000000000000000000000000000000..2a5fc065e0e020a5687a11632c93b64378be0d56 --- /dev/null +++ b/src/client/completion/ub-lldpcli @@ -0,0 +1,26 @@ +_ub_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 _ub_lldpcli ub-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 0000000000000000000000000000000000000000..a71aa7aa596b7ae49bfe663753a6ffcb0e3078a6 --- /dev/null +++ b/src/client/conf-dot3.c @@ -0,0 +1,39 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2013 Vincent Bernat + * + * 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 +#include + +#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-lldp.c b/src/client/conf-lldp.c new file mode 100644 index 0000000000000000000000000000000000000000..bd3976afd2adc1faa2b3a5c11a76ab11327f0d80 --- /dev/null +++ b/src/client/conf-lldp.c @@ -0,0 +1,424 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2013 Vincent Bernat + * + * 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 +#include +#include + +#include "client.h" +#include "../log.h" + +static int +cmd_txdelay(struct lldpctl_conn_t *conn, struct writer *w, + struct cmd_env *env, void *arg) +{ + const char *interval; + char interval_ms[8]; /* less than 2.5 hours */ + lldpctl_key_t key; + int arglen; + + log_debug("ub-lldpctl", "set transmit delay"); + + lldpctl_atom_t *config = lldpctl_get_configuration(conn); + if (config == NULL) { + log_warnx("ub-lldpctl", "unable to get configuration from ub-lldpd. %s", + lldpctl_last_strerror(conn)); + return 0; + } + interval = cmdenv_get(env, "tx-interval"); + key = lldpctl_k_config_tx_interval; + /* interval is either for seconds or 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("ub-lldpctl", "unable to set transmit delay. %s", + lldpctl_last_strerror(conn)); + lldpctl_atom_dec_ref(config); + return 0; + } + log_info("ub-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, void *arg) +{ + log_debug("ub-lldpctl", "set transmit hold"); + + lldpctl_atom_t *config = lldpctl_get_configuration(conn); + if (config == NULL) { + log_warnx("ub-lldpctl", "unable to get configuration from ub-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("ub-lldpctl", "unable to set transmit hold. %s", + lldpctl_last_strerror(conn)); + lldpctl_atom_dec_ref(config); + return 0; + } + log_info("ub-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, void *arg) +{ + lldpctl_atom_t *port; + const char *name; + const char *status = cmdenv_get(env, "status"); + + log_debug("ub-lldpctl", "lldp administrative port status set to '%s'", status); + + if (!status || !strlen(status)) { + log_warnx("ub-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("ub-lldpctl", "unable to set LLDP status for %s." + " %s", name, lldpctl_last_strerror(conn)); + } + } + + return 1; +} + +static int +cmd_portid_type_local(struct lldpctl_conn_t *conn, struct writer *w, + struct cmd_env *env, 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("ub-lldpctl", "lldp PortID TLV Subtype Local port-id '%s' port-descr '%s'", id, descr); + + if (!id || !strlen(id)) { + log_warnx("ub-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("ub-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("ub-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, void *arg) +{ + lldpctl_atom_t *port; + const char *name; + const char *descr = cmdenv_get(env, "port-descr"); + + log_debug("ub-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("ub-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, void *arg) +{ + char *value_str; + int value = -1; + + log_debug("ub-lldpctl", "lldp PortID TLV Subtype"); + + lldpctl_atom_t *config = lldpctl_get_configuration(conn); + if (config == NULL) { + log_warnx("ub-lldpctl", + "unable to get configuration from ub-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("ub-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("ub-lldpctl", "unable to set LLDP PortID type." + " %s", lldpctl_last_strerror(conn)); + lldpctl_atom_dec_ref(config); + return 0; + } + + log_info("ub-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, void *arg) +{ + log_debug("ub-lldpctl", "lldp capabilities-advertisements %s", arg?"enable":"disable"); + + lldpctl_atom_t *config = lldpctl_get_configuration(conn); + if (config == NULL) { + log_warnx("ub-lldpctl", "unable to get configuration from ub-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("ub-lldpctl", "unable to %s chassis capabilities advertisement: %s", + arg?"enable":"disable", + lldpctl_last_strerror(conn)); + lldpctl_atom_dec_ref(config); + return 0; + } + log_info("ub-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, void *arg) +{ + log_debug("ub-lldpctl", "lldp management-addresses-advertisements %s", arg?"enable":"disable"); + + lldpctl_atom_t *config = lldpctl_get_configuration(conn); + if (config == NULL) { + log_warnx("ub-lldpctl", "unable to get configuration from ub-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("ub-lldpctl", "unable to %s management addresses advertisement: %s", + arg?"enable":"disable", + lldpctl_last_strerror(conn)); + lldpctl_atom_dec_ref(config); + return 0; + } + log_info("ub-lldpctl", "management addresses advertisement %s", + arg?"enabled":"disabled"); + lldpctl_atom_dec_ref(config); + return 1; +} + +static int +cmd_store_status_env_value(struct lldpctl_conn_t *conn, struct writer *w, + struct cmd_env *env, 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 in seconds or 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); + } + + /* 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); +} diff --git a/src/client/conf-med.c b/src/client/conf-med.c new file mode 100644 index 0000000000000000000000000000000000000000..180272a88ae0da212f2073800cc32f9f63c56835 --- /dev/null +++ b/src/client/conf-med.c @@ -0,0 +1,570 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2012 Vincent Bernat + * + * 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 +#include + +#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, 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, 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, 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, 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, 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, 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, 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, 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; + } + + 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 0000000000000000000000000000000000000000..ef6f9ccd1c55687e63a0f030c8b06370bef54804 --- /dev/null +++ b/src/client/conf-power.c @@ -0,0 +1,433 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2013 Vincent Bernat + * + * 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 +#include + +#include "client.h" +#include "../log.h" + +static int +cmd_medpower(struct lldpctl_conn_t *conn, struct writer *w, + struct cmd_env *env, 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, 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, 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, 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, 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, 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, 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, void *arg) +{ + return cmd_check_type(env, "pse"); +} +static int +cmd_check_pd(struct cmd_env *env, 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, 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 0000000000000000000000000000000000000000..2f306fe57d3245440589adb0ea568439e3a98e34 --- /dev/null +++ b/src/client/conf-system.c @@ -0,0 +1,465 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2013 Vincent Bernat + * + * 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 +#include +#include + +#include "client.h" +#include "../log.h" + +static int +cmd_iface_pattern(struct lldpctl_conn_t *conn, struct writer *w, + struct cmd_env *env, void *arg) +{ + log_debug("ub-lldpctl", "set iface pattern"); + + lldpctl_atom_t *config = lldpctl_get_configuration(conn); + if (config == NULL) { + log_warnx("ub-lldpctl", "unable to get configuration from ub-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("ub-lldpctl", "unable to set iface-pattern. %s", + lldpctl_last_strerror(conn)); + lldpctl_atom_dec_ref(config); + return 0; + } + log_info("ub-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, void *arg) +{ + log_debug("ub-lldpctl", "set permanent iface pattern"); + + lldpctl_atom_t *config = lldpctl_get_configuration(conn); + if (config == NULL) { + log_warnx("ub-lldpctl", "unable to get configuration from ub-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("ub-lldpctl", "unable to set permanent iface pattern. %s", + lldpctl_last_strerror(conn)); + lldpctl_atom_dec_ref(config); + return 0; + } + log_info("ub-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, void *arg) +{ + lldpctl_atom_t *config = lldpctl_get_configuration(conn); + if (config == NULL) { + log_warnx("ub-lldpctl", "unable to get configuration from ub-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("ub-lldpctl", "unable to %s promiscuous mode: %s", + arg?"enable":"disable", + lldpctl_last_strerror(conn)); + lldpctl_atom_dec_ref(config); + return 0; + } + log_info("ub-lldpctl", "interface promiscuous mode %s", + arg?"enabled":"disabled"); + lldpctl_atom_dec_ref(config); + return 1; +} + +static int +cmd_system_description(struct lldpctl_conn_t *conn, struct writer *w, + struct cmd_env *env, void *arg) +{ + 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("ub-lldpctl", "set %s description", what); + lldpctl_atom_t *config = lldpctl_get_configuration(conn); + if (config == NULL) { + log_warnx("ub-lldpctl", "unable to get configuration from ub-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("ub-lldpctl", "unable to set description. %s", + lldpctl_last_strerror(conn)); + lldpctl_atom_dec_ref(config); + return 0; + } + log_info("ub-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, void *arg) +{ + const char *value; + value = cmdenv_get(env, "description"); + log_debug("ub-lldpctl", "set chassis ID"); + lldpctl_atom_t *config = lldpctl_get_configuration(conn); + if (config == NULL) { + log_warnx("ub-lldpctl", "unable to get configuration from ub-lldpd. %s", + lldpctl_last_strerror(conn)); + return 0; + } + if (lldpctl_atom_set_str(config, + lldpctl_k_config_cid_string, + value) == NULL) { + log_warnx("ub-lldpctl", "unable to set chassis ID. %s", + lldpctl_last_strerror(conn)); + lldpctl_atom_dec_ref(config); + return 0; + } + log_info("ub-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, void *arg) +{ + log_debug("ub-lldpctl", "set management pattern"); + + lldpctl_atom_t *config = lldpctl_get_configuration(conn); + if (config == NULL) { + log_warnx("ub-lldpctl", "unable to get configuration from ub-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("ub-lldpctl", "unable to set management pattern. %s", + lldpctl_last_strerror(conn)); + lldpctl_atom_dec_ref(config); + return 0; + } + log_info("ub-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, void *arg) +{ + struct utsname un; + log_debug("ub-lldpctl", "set system name"); + + lldpctl_atom_t *config = lldpctl_get_configuration(conn); + if (config == NULL) { + log_warnx("ub-lldpctl", "unable to get configuration from ub-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("ub-lldpctl", "cannot get node name"); + lldpctl_atom_dec_ref(config); + return 0; + } + value = un.nodename; + } + if (lldpctl_atom_set_str(config, + lldpctl_k_config_hostname, value) == NULL) { + log_warnx("ub-lldpctl", "unable to set system name. %s", + lldpctl_last_strerror(conn)); + lldpctl_atom_dec_ref(config); + return 0; + } + log_info("ub-lldpctl", "system name set to new value %s", + value?value:"(none)"); + lldpctl_atom_dec_ref(config); + return 1; +} + +static int +cmd_update_descriptions(struct lldpctl_conn_t *conn, struct writer *w, + struct cmd_env *env, void *arg) +{ + lldpctl_atom_t *config = lldpctl_get_configuration(conn); + if (config == NULL) { + log_warnx("ub-lldpctl", "unable to get configuration from ub-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("ub-lldpctl", "unable to %s interface description update: %s", + arg?"enable":"disable", + lldpctl_last_strerror(conn)); + lldpctl_atom_dec_ref(config); + return 0; + } + log_info("ub-lldpctl", "interface description update %s", + arg?"enabled":"disabled"); + lldpctl_atom_dec_ref(config); + return 1; +} + +static int +cmd_maxneighs(struct lldpctl_conn_t *conn, struct writer *w, + struct cmd_env *env, void *arg) +{ + log_debug("ub-lldpctl", "set maximum neighbors"); + + lldpctl_atom_t *config = lldpctl_get_configuration(conn); + if (config == NULL) { + log_warnx("ub-lldpctl", "unable to get configuration from ub-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("ub-lldpctl", "unable to set maximum of neighbors. %s", + lldpctl_last_strerror(conn)); + lldpctl_atom_dec_ref(config); + return 0; + } + log_info("ub-lldpctl", "maximum neighbors set to new value %s", cmdenv_get(env, "max-neighbors")); + lldpctl_atom_dec_ref(config); + return 1; +} + +/** + * 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); +} + diff --git a/src/client/conf.c b/src/client/conf.c new file mode 100644 index 0000000000000000000000000000000000000000..128eea8a70a7cd52403ec45285d607545b52875b --- /dev/null +++ b/src/client/conf.c @@ -0,0 +1,47 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2013 Vincent Bernat + * + * 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 +#include + +#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); +} diff --git a/src/client/display.c b/src/client/display.c new file mode 100644 index 0000000000000000000000000000000000000000..3283f758e3f4d2858475da7862f1a00b8c7120db --- /dev/null +++ b/src/client/display.c @@ -0,0 +1,527 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2008 Vincent Bernat + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../log.h" +#include "client.h" + +static void +display_cap(struct writer * w, lldpctl_atom_t *chassis, u_int8_t bit, 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_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_port(struct writer *w, lldpctl_atom_t *port, int details) +{ + 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 (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)); + + 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("ub-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("ub-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_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); + 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_ppvids(w, port); + display_pids(w, port); + } + + lldpctl_atom_dec_ref(chassis); + + tag_end(w); +} + +/** + * Display information about interfaces. + * + * @param conn Connection to ub-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 ub-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); + } + +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 ub-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("ub-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, "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, "lldp-portid-type", + "Port ID TLV subtype for LLDP frames", + lldpctl_atom_get_str(configuration, + lldpctl_k_config_lldp_portid_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 0000000000000000000000000000000000000000..2809935bfc42787168f9a01077b65f6cd50d7252 --- /dev/null +++ b/src/client/json_writer.c @@ -0,0 +1,374 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2017 Vincent Bernat + * + * 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 +#endif + +#include +#include +#include +#include + +#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(¤t->children); + el != NULL; + el = el_next) { + el_next = TAILQ_NEXT(el, next); + json_element_free(el); + TAILQ_REMOVE(¤t->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, ¤t->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("ub-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("ub-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 0000000000000000000000000000000000000000..68a50adf8fa140cf658806ffce52636886827c82 --- /dev/null +++ b/src/client/kv_writer.c @@ -0,0 +1,136 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2010 Andreas Hofmeister + * 2010 Vincent Bernat + * + * 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 +#include +#include + +#include "writer.h" +#include "../log.h" + +#define SEP '.' + +struct kv_writer_private { + FILE * fh; + char * prefix; +}; + +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; +} + +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); +} + +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'; +} + +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); + } +} + +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.c b/src/client/lldpcli.c new file mode 100644 index 0000000000000000000000000000000000000000..b59f26fdc6c9470292835e4819d07f51d3c33955 --- /dev/null +++ b/src/client/lldpcli.c @@ -0,0 +1,607 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2008 Vincent Bernat + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "client.h" + +#ifdef HAVE___PROGNAME +extern const char *__progname; +#else +# define __progname "ub-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, "ub-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 ub-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 ub-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 char* +prompt() +{ +#define CESC "\033" + int privileged = is_privileged(); + if (isatty(STDIN_FILENO)) { + if (privileged) + return "[ub-lldpcli] # "; + return "[ub-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, void *arg) +{ + log_info("ub-lldpctl", "quit ub-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, void *arg) +{ + log_info("ub-lldpctl", "ask for global update"); + + lldpctl_atom_t *config = lldpctl_get_configuration(conn); + if (config == NULL) { + log_warnx("ub-lldpctl", "unable to get configuration from ub-lldpd. %s", + lldpctl_last_strerror(conn)); + return 0; + } + if (lldpctl_atom_set_int(config, + lldpctl_k_config_tx_interval, -1) == NULL) { + log_warnx("ub-lldpctl", "unable to ask ub-lldpd for immediate retransmission. %s", + lldpctl_last_strerror(conn)); + lldpctl_atom_dec_ref(config); + return 0; + } + log_info("ub-lldpctl", "immediate retransmission requested successfully"); + lldpctl_atom_dec_ref(config); + return 1; +} + +/** + * Pause or resume execution of ub-lldpd. + * + * @param conn The connection to ub-lldpd. + * @param pause 1 if we want to pause ub-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("ub-lldpctl", "unable to get configuration from ub-lldpd. %s", + lldpctl_last_strerror(conn)); + return 0; + } + if (lldpctl_atom_get_int(config, lldpctl_k_config_paused) == pause) { + log_debug("ub-lldpctl", "ub-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("ub-lldpctl", "unable to ask ub-lldpd to %s operations. %s", + pause?"pause":"resume", + lldpctl_last_strerror(conn)); + lldpctl_atom_dec_ref(config); + return 0; + } + log_info("ub-lldpctl", "ub-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, 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, 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 ub-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("ub-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("ub-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("ub-lldpctl", "tokenize command line"); + n = tokenize_line(line, &cargc, &cargv); + switch (n) { + case -1: + log_warnx("ub-lldpctl", "internal error while tokenizing"); + return -1; + case 1: + log_warnx("ub-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_privileged(commands_new( + 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 ub-lldpd operations", NULL, NULL, NULL), + NEWLINE, "Pause ub-lldpd operations", NULL, cmd_pause, NULL); + commands_new( + commands_new(root, "resume", "Resume ub-lldpd operations", NULL, NULL, NULL), + NEWLINE, "Resume ub-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("ub-lldpctl", "cannot find configuration file/directory %s", + arg); + } else { + log_debug("ub-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("ub-lldpctl", "not enough memory to process %s", + arg); + return; + } + log_debug("ub-lldpctl", "input: %s", arg); + input->name = strdup(arg); + TAILQ_INSERT_TAIL(inputs, input, next); + return; + } + if (!acceptdir) { + log_debug("ub-lldpctl", "skip directory %s", + arg); + return; + } + + struct dirent **namelist = NULL; + int n = scandir(arg, &namelist, filter, alphasort); + if (n < 0) { + log_warnx("ub-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:"; + + 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, "ub-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("ub-lldpctl", "connect to ub-lldpd"); + conn = lldpctl_new_name(ctlname, NULL, NULL, NULL); + if (conn == NULL) goto end; + + /* 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("ub-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("ub-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("ub-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("ub-lldpctl", "not enough memory to build command line"); + goto end; + } + log_debug("ub-lldpctl", "execute %s", line); + if (parse_and_exec(conn, fmt, line) != -1) + rc = EXIT_SUCCESS; + free(line); + goto end; + } + + /* Then, if we are regular ub-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/misc.c b/src/client/misc.c new file mode 100644 index 0000000000000000000000000000000000000000..c6ce9caa8c828311fd3c41201cc9dd8301e2b104 --- /dev/null +++ b/src/client/misc.c @@ -0,0 +1,68 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2012 Vincent Bernat + * + * 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 +#include + +/** + * 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 + */ +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 0000000000000000000000000000000000000000..85f22b42d57fa568ed83d77e64a8d40da478e16b --- /dev/null +++ b/src/client/show.c @@ -0,0 +1,414 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2012 Vincent Bernat + * + * 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 +#include + +/** + * 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, void *arg) +{ + log_debug("ub-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("ub-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, void *arg) +{ + log_debug("ub-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("ub-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, void *arg) +{ + log_debug("ub-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, void *arg) +{ + log_debug("ub-lldpctl", "show stats data"); + if (cmdenv_get(env, "ports")) + log_debug("ub-lldpctl", "restrict to the following ports: %s", + cmdenv_get(env, "ports")); + if (cmdenv_get(env, "summary")) + log_debug("ub-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, 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, void *arg) +{ + log_debug("ub-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, 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("ub-lldpctl", "specified limit (%s) is %s and ignored", + limit_str, errstr); + } + } + + log_debug("ub-lldpctl", "watch for neighbor changes"); + if (lldpctl_watch_callback2(conn, watchcb, &wa) < 0) { + log_warnx("ub-lldpctl", "unable to watch for neighbors. %s", + lldpctl_last_strerror(conn)); + return 0; + } + while (1) { + if (lldpctl_watch(conn) < 0) { + log_warnx("ub-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' + */ +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); +} + +/** + * Register sub command summary + */ +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 0000000000000000000000000000000000000000..1808bd0740d8c64830daad2ec4b27903b0ae08fb --- /dev/null +++ b/src/client/text_writer.c @@ -0,0 +1,154 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2010 Andreas Hofmeister + * + * 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 +#include +#include + +#include "writer.h" +#include "../log.h" + +static char sep[] = "-------------------------------------------------------------------------------"; + +struct txt_writer_private { + FILE * fh; + int level; + int attrs; +}; + +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) { + fprintf(p->fh, "%s\n", sep); + } else { + fprintf(p->fh, "\n"); + } + + for (i = 1; i < p->level; i++) { + fprintf(p->fh, " "); + } + + snprintf(buf, sizeof(buf), "%s:", descr); + fprintf(p->fh, "%-13s", buf); + + if (p->level == 0) + fprintf(p->fh, "\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) { + fprintf(p->fh, "%s%s", (p->attrs > 0 ? ", " : " "), value?value:"(none)"); + } else { + fprintf(p->fh, "%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) { + fprintf(p->fh, " %s", data?data:"(none)"); + return; + } + + fprintf(p->fh, " "); + while ((nl = strchr(v, '\n')) != NULL) { + *nl = '\0'; + fprintf(p->fh, "%s\n", v); + v = nl + 1; + + /* Indent */ + int i; + for (i = 1; i < p->level - 1; i++) { + fprintf(p->fh, " "); + } + fprintf(p->fh, "%-14s", " "); + } + fprintf(p->fh, "%s", v); + free(begin); +} + +static void +txt_end(struct writer *w) { + struct txt_writer_private *p = w->priv; + p->level--; + + if (p->level == 1) { + fprintf(p->fh, "\n%s", sep); + fflush(p->fh); + } +} + +static void +txt_finish(struct writer *w) { + struct txt_writer_private *p = w->priv; + + fprintf(p->fh, "\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("ub-lldpctl", "out of memory"); + return NULL; + } + + priv->fh = fh; + priv->level = 0; + priv->attrs = 0; + + result = malloc(sizeof(struct writer)); + if (!result) { + fatalx("ub-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 0000000000000000000000000000000000000000..234091f77b4a74c995e037e8f2eb5d63272c71e1 --- /dev/null +++ b/src/client/tokenizer.c @@ -0,0 +1,119 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2013 Vincent Bernat + * + * 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 +/** + * 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; + char *ifs = " \n\t"; + char *quotes = "'\""; + 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/ub-lldpcli.8.in b/src/client/ub-lldpcli.8.in new file mode 100644 index 0000000000000000000000000000000000000000..24e443434878e6c13cb747e4b8e44156282869ed --- /dev/null +++ b/src/client/ub-lldpcli.8.in @@ -0,0 +1,1011 @@ +.\" Copyright (c) 2006 Pierre-Yves Ritschard +.\" Copyright (c) 2008 Vincent Bernat +.\" +.\" 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 UB-LLDPCLI 8 +.Os +.Sh NAME +.Nm ub-lldpcli , +.Nm ub-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 ub-lldpctl +.Op Fl dv +.Op Fl u Ar socket +.Op Fl f Ar format +.Op Ar interfaces ... +.Sh DESCRIPTION +The +.Nm +program controls +.Xr ub-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 ub-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 ub-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 ub-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 ub-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 ub-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 ub-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 interface pattern Ar pattern +.Bd -ragged -offset XXXXXX +Specify which interface to listen and send LLDPDU to. Without this +option, +.Nm ub-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 ub-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 ub-lldpd . +By default, +.Nm ub-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 ub-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 ub-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 ub-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 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 ub-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 ub-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 ub-lldpd +accepts LLDP frames on all VLANs. +.Ed + +.Cd configure +.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 +.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 ub-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 +.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 ub-lldpd +operations. +.Nm ub-lldpd +will not send any more frames or receive ones. This can be undone with +.Cd resume +command. +.Ed + +.Cd resume +.Bd -ragged -offset XXXXXX +Resume +.Nm ub-lldpd +operations. +.Nm ub-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 ub-lldpd 8 . +.El +.Sh SEE ALSO +.Xr ub-lldpd 8 +.Sh AUTHORS +.An -nosplit +The +.Nm +program was written by +.An Vincent Bernat Aq bernat@luffy.cx . diff --git a/src/client/ub-lldpcli.supp b/src/client/ub-lldpcli.supp new file mode 100644 index 0000000000000000000000000000000000000000..572897e434452eacb9fd84e7b477b365f1fe9122 --- /dev/null +++ b/src/client/ub-lldpcli.supp @@ -0,0 +1,53 @@ +# We have those one-time leaks that we don't bother to correct. Those leaks +# are due to the conversion of the some map strings to tags. This +# happens only when registering new commands, so once. +{ + one-time-memory-leak-with-med-civicaddress-map-conversion + Memcheck:Leak + ... + fun:strdup + fun:register_commands_medloc_addr + ... +} +{ + one-time-memory-leak-with-med-policy-map-conversion + Memcheck:Leak + ... + fun:strdup + fun:register_commands_medpol + ... +} +{ + one-time-memory-leak-with-power-map-conversion + Memcheck:Leak + ... + fun:strdup + fun:register_commands_pow_priority + ... +} +{ + one-time-memory-leak-with-dot3-power-map-conversion + Memcheck:Leak + ... + fun:strdup + fun:register_commands_dot3pow + ... +} +{ + one-time-memory-leak-with-lldp-status-map-conversion + Memcheck:Leak + ... + fun:strdup + fun:register_commands_configure_lldp + ... +} + +# libreadline has no function to free memory +{ + readline-internal-memory + Memcheck:Leak + fun:malloc + ... + fun:readline + fun:main +} diff --git a/src/client/ub-lldpctl.8 b/src/client/ub-lldpctl.8 new file mode 100644 index 0000000000000000000000000000000000000000..972ef36ba5bad10cce52b116eae6dec30b85dbe0 --- /dev/null +++ b/src/client/ub-lldpctl.8 @@ -0,0 +1 @@ +.so man8/ub-lldpcli.8 diff --git a/src/client/utf8.c b/src/client/utf8.c new file mode 100644 index 0000000000000000000000000000000000000000..2107639313648a3234dce5fbfda70e79855197a8 --- /dev/null +++ b/src/client/utf8.c @@ -0,0 +1,99 @@ +/* -*- 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 + +/* + * 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 0000000000000000000000000000000000000000..e15b30e3c1cd761b9fd855a5275365481f50b50e --- /dev/null +++ b/src/client/writer.h @@ -0,0 +1,49 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2010 Andreas Hofmeister + * + * 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 + +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 0000000000000000000000000000000000000000..8684b173db9cd0c1e0bf4d4a10166ba489c183a9 --- /dev/null +++ b/src/client/xml_writer.c @@ -0,0 +1,143 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2010 Andreas Hofmeister + * + * 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 +#include +#include + +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdocumentation" +#endif +#include +#include +#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; +}; + +void xml_new_writer(struct xml_writer_private *priv) +{ + priv->xw = xmlNewTextWriterDoc(&(priv->doc), 0); + if (!priv->xw) + fatalx("ub-lldpctl", "cannot create xml writer"); + + xmlTextWriterSetIndent(priv->xw, 4); + + if (xmlTextWriterStartDocument(priv->xw, NULL, "UTF-8", NULL) < 0 ) + fatalx("ub-lldpctl", "cannot start xml document"); +} + +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("ub-lldpctl", "cannot start '%s' element", tag); + + if (descr && (strlen(descr) > 0)) { + if (xmlTextWriterWriteFormatAttribute(p->xw, BAD_CAST "label", "%s", descr) < 0) + log_warnx("ub-lldpctl", "cannot add attribute 'label' to element %s", tag); + } + + p->depth++; +} + +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("ub-lldpctl", "cannot add attribute %s with value %s", tag, value?value:"(none)"); +} + +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("ub-lldpctl", "cannot add '%s' as data to element", data?data:"(none)"); +} + +void xml_end(struct writer *w) { + struct xml_writer_private *p = w->priv; + + if (xmlTextWriterEndElement(p->xw) < 0 ) + log_warnx("ub-lldpctl", "cannot end element"); + + if (--p->depth == 0) { + int failed = 0; + + if (xmlTextWriterEndDocument(p->xw) < 0 ) { + log_warnx("ub-lldpctl", "cannot finish document"); + failed = 1; + } + + xmlFreeTextWriter(p->xw); + if (!failed) { + xmlDocDump(p->fh, p->doc); + fflush(p->fh); + } + xmlFreeDoc(p->doc); + } +} + +void xml_finish(struct writer *w) { + struct xml_writer_private *p = w->priv; + if (p->depth != 0) { + log_warnx("ub-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("ub-lldpctl", "out of memory"); + return NULL; + } + priv->fh = fh; + priv->depth = 0; + + result = malloc(sizeof(struct writer)); + if (!result) + fatalx("ub-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 0000000000000000000000000000000000000000..3717edddacb3c2bc3a6ebd81546bcb305e4af980 --- /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/asprintf.c b/src/compat/asprintf.c new file mode 100644 index 0000000000000000000000000000000000000000..1bc5d7ffc3df4e74d8017978f6e0162a45c1ae9b --- /dev/null +++ b/src/compat/asprintf.c @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2004 Darren Tucker. + * + * Based originally on asprintf.c from OpenBSD: + * Copyright (c) 1997 Todd C. Miller + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include + +#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 0000000000000000000000000000000000000000..3994265fa04a2f0f0d091fc9655ae0629a076c30 --- /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 +#endif + +#include +#include +#include +#include +#include + +#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 0000000000000000000000000000000000000000..cf7f81d53f9aae4207f7424e1f5e9c3712b4b9ce --- /dev/null +++ b/src/compat/daemon.c @@ -0,0 +1,68 @@ +/* $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 +#include +#include +#include + +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 0000000000000000000000000000000000000000..85d5837670302d44596f9f03e02e791990796598 --- /dev/null +++ b/src/compat/empty.c @@ -0,0 +1,21 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2013 Vincent Bernat + * + * 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 0000000000000000000000000000000000000000..2e11f23b7c631cdd16a01d4468b6c6cef4a9d643 --- /dev/null +++ b/src/compat/getline.c @@ -0,0 +1,112 @@ +/* -*- 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 +#include +#include +#include +#include +#include +#include + +#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 0000000000000000000000000000000000000000..5b97294de40945dcb4b7269369ce86a54021ee3d --- /dev/null +++ b/src/compat/malloc.c @@ -0,0 +1,15 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* malloc replacement that can allocate 0 byte */ + +#undef malloc +#include +#include + +/* 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 0000000000000000000000000000000000000000..c6aa351ca9df8dfe496fa5127acef0fdff58e8ca --- /dev/null +++ b/src/compat/realloc.c @@ -0,0 +1,16 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* realloc replacement that can reallocate 0 byte or NULL pointers*/ + +#undef realloc +#include +#include + +/* 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 0000000000000000000000000000000000000000..cc8e6384c00bdc96660f05bb2c6494b1a1ca095d --- /dev/null +++ b/src/compat/setproctitle.c @@ -0,0 +1,7 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ + +void +setproctitle(const char *fmt, ...) +{ + /* Do nothing. */ +} diff --git a/src/compat/strlcpy.c b/src/compat/strlcpy.c new file mode 100644 index 0000000000000000000000000000000000000000..f5f135afd35ab23b802dc83f4a10fe0e5d5eb5bb --- /dev/null +++ b/src/compat/strlcpy.c @@ -0,0 +1,52 @@ +/* -*- 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 + * + * 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 +#include + +/* + * Copy src to string dst of size siz. At most siz-1 characters + * will be copied. Always NUL terminates (unless siz == 0). + * Returns strlen(src); if retval >= siz, truncation occurred. + */ +size_t +strlcpy(char *dst, const char *src, size_t siz) +{ + char *d = dst; + const char *s = src; + size_t n = siz; + + /* Copy as many bytes as will fit */ + if (n != 0) { + while (--n != 0) { + if ((*d++ = *s++) == '\0') + break; + } + } + + /* Not enough room in dst, add NUL and traverse rest of src */ + if (n == 0) { + if (siz != 0) + *d = '\0'; /* NUL-terminate dst */ + while (*s++) + ; + } + + return(s - src - 1); /* count does not include NUL */ +} diff --git a/src/compat/strndup.c b/src/compat/strndup.c new file mode 100644 index 0000000000000000000000000000000000000000..9b57d4b7935e77f4996cd6d3045ecf4cb3593256 --- /dev/null +++ b/src/compat/strndup.c @@ -0,0 +1,23 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ + +#include +#include + +/* + * 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 0000000000000000000000000000000000000000..8bc3525c2f0ecb18e53d08f17e5ba5b40cf002e5 --- /dev/null +++ b/src/compat/strnlen.c @@ -0,0 +1,14 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ + +#include + +/* + * 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 0000000000000000000000000000000000000000..547f39af404de694885736db2accbc0629de1027 --- /dev/null +++ b/src/compat/strtonum.c @@ -0,0 +1,67 @@ +/* -*- 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 +#include +#include + +#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 0000000000000000000000000000000000000000..be59210a1860aa9b0f9bcdfcc4b18dab49a201a9 --- /dev/null +++ b/src/compat/vsyslog.c @@ -0,0 +1,16 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ + +#include +#include +#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 0000000000000000000000000000000000000000..a347908eaf5e429a1e5fcd71a8ecc5842ef06543 --- /dev/null +++ b/src/ctl.c @@ -0,0 +1,261 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2008 Vincent Bernat + * + * 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 +#include +#include +#include +#include +#include +#include +#include + +#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 0000000000000000000000000000000000000000..a42c173b13dae9bf75c2172e26e0ac6341aa7f91 --- /dev/null +++ b/src/ctl.h @@ -0,0 +1,65 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2012 Vincent Bernat + * + * 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 +#endif + +#include +#include "marshal.h" + +enum hmsg_type { + NONE, + GET_CONFIG, /* Get global configuration */ + SET_CONFIG, /* Change global configuration */ + GET_INTERFACES, /* Get list of interfaces */ + 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 ub-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 0000000000000000000000000000000000000000..0459f935b5ffa9599021b14669691c28910bd922 --- /dev/null +++ b/src/daemon/Makefile.am @@ -0,0 +1,106 @@ +AM_CFLAGS = -I $(top_srcdir)/include $(LLDP_CFLAGS) +AM_CPPFLAGS = $(LLDP_CPPFLAGS) +AM_LDFLAGS = $(LLDP_LDFLAGS) +BUILT_SOURCES = +CLEANFILES = + +sbin_PROGRAMS = ub-lldpd +man_MANS = ub-lldpd.8 + +noinst_LTLIBRARIES = libublldpd.la + +## Convenience library for ub-lldpd and tests +nodist_libublldpd_la_SOURCES = +libublldpd_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 \ + probes.d trace.h \ + protocols/lldp.c +libublldpd_la_CFLAGS = $(AM_CFLAGS) @libevent_CFLAGS@ @libcap_CFLAGS@ +libublldpd_la_CPPFLAGS = $(AM_CPPFLAGS) -DSYSCONFDIR='"$(sysconfdir)"' -DLLDPCLI_PATH='"$(sbindir)/ub-lldpcli"' +libublldpd_la_LIBADD = \ + $(top_builddir)/src/libcommon-daemon-client.la \ + $(top_builddir)/src/libcommon-daemon-lib.la @libevent_LIBS@ @libcap_LIBS@ + +## ub-lldpd +ub_lldpd_SOURCES = main.c +ub_lldpd_LDFLAGS = $(AM_LDFLAGS) $(LLDP_BIN_LDFLAGS) +ub_lldpd_LDADD = libublldpd.la @libevent_LDFLAGS@ + +libublldpd_la_SOURCES += \ + forward-linux.c \ + interfaces-linux.c \ + netlink.c \ + priv-linux.c + +# 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 " | $(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_libublldpd_la_SOURCES += syscall-names.h +libublldpd_la_SOURCES += priv-seccomp.c +libublldpd_la_CFLAGS += @libseccomp_CFLAGS@ +libublldpd_la_LIBADD += @libseccomp_LIBS@ +endif + +## Systemtap/DTrace +EXTRA_DIST = dtrace2systemtap.awk +if ENABLE_SYSTEMTAP +BUILT_SOURCES += probes.h +CLEANFILES += probes.h ub-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 $@ +ub_lldpd_LDADD += probes.o + +ub-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 = ub-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 = ub-lldpd.service +endif + +if HAVE_SYSUSERSDIR +sysusers_DATA = ub-lldpd.sysusers.conf +endif + +if HAVE_APPARMORDIR +apparmor_DATA = usr.sbin.ub-lldpd +endif + +TEMPLATES = ub-lldpd.8 ub-lldpd.service ub-lldpd.sysusers.conf usr.sbin.ub-lldpd +EXTRA_DIST += ub-lldpd.8.in ub-lldpd.service.in ub-lldpd.sysusers.conf.in usr.sbin.ub-lldpd.in +CLEANFILES += $(TEMPLATES) +ub-lldpd.8: ub-lldpd.8.in +ub-lldpd.service: ub-lldpd.service.in +ub-lldpd.sysusers.conf: ub-lldpd.sysusers.conf.in +usr.sbin.ub-lldpd: usr.sbin.ub-lldpd.in +include $(top_srcdir)/edit.am diff --git a/src/daemon/agent.c b/src/daemon/agent.c new file mode 100644 index 0000000000000000000000000000000000000000..2fd1a4a9a1e51ccc7fd64f0a136c7230faac1c8c --- /dev/null +++ b/src/daemon/agent.c @@ -0,0 +1,1957 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2008 Vincent Bernat + * + * 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 + +#include "agent.h" + +#if HAVE_NET_SNMP_AGENT_UTIL_FUNCS_H +#include +#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. */ +} + +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(¬ification_vars, + objid_snmptrap, objid_snmptrap_len, + ASN_OBJECT_ID, + (u_char *) notification_oid, + notification_oid_len * sizeof(oid)); + + snmp_varlist_add_variable(¬ification_vars, + inserts_oid, inserts_oid_len, + ASN_GAUGE, + (u_char *)&inserts, + sizeof(inserts)); + snmp_varlist_add_variable(¬ification_vars, + deletes_oid, deletes_oid_len, + ASN_GAUGE, + (u_char *)&deletes, + sizeof(inserts)); + snmp_varlist_add_variable(¬ification_vars, + drops_oid, drops_oid_len, + ASN_GAUGE, + (u_char *)&drops, + sizeof(drops)); + snmp_varlist_add_variable(¬ification_vars, + ageouts_oid, ageouts_oid_len, + ASN_GAUGE, + (u_char *)&ageouts, + sizeof(ageouts)); + + if (type != NEIGHBOR_CHANGE_DELETED) { + snmp_varlist_add_variable(¬ification_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(¬ification_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(¬ification_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 0000000000000000000000000000000000000000..8f101bd02f4c05665f259e9d84de2f299d91c365 --- /dev/null +++ b/src/daemon/agent.h @@ -0,0 +1,35 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* +* Copyright (c) 2008 Vincent Bernat +* +* 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 +#include +#include +#include + +#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 0000000000000000000000000000000000000000..2f9d8da2c3870d529bb8f4f79f6ab967b47b07a7 --- /dev/null +++ b/src/daemon/agent_priv.c @@ -0,0 +1,252 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2008 Vincent Bernat + * + * 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 +#include +#include + +#ifdef ENABLE_PRIVSEP +#include +#include +#include +#include +#include + +#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 +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 +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 0000000000000000000000000000000000000000..d6e4caa354ec8a7a4eb79a5d96d56fb591779434 --- /dev/null +++ b/src/daemon/bitmap.c @@ -0,0 +1,65 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2020 Vincent Bernat + * + * 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 0000000000000000000000000000000000000000..4efa162299c94d57d8f518eb7310b35830a42d2f --- /dev/null +++ b/src/daemon/client.c @@ -0,0 +1,468 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2008 Vincent Bernat + * + * 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" + +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); + } + /* Pause/resume */ + if (CHANGED(c_paused)) { + log_debug("rpc", "client asked to %s ub-lldpd", + config->c_paused?"pause":"resume"); + cfg->g_config.c_paused = config->c_paused; + levent_send_now(cfg); + } + + 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_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); + } + + 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; +} + +/* 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) +{ + 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; + } + 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); + 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 }, + { 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 0000000000000000000000000000000000000000..65a68b891772cde10efa64bb77f9d03febcf6a3a --- /dev/null +++ b/src/daemon/dmi-dummy.c @@ -0,0 +1,57 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2012 Vincent Bernat + * + * 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 0000000000000000000000000000000000000000..7c4a0afb1fd4ab5b14ca832753ac9a4a69fbaf9b --- /dev/null +++ b/src/daemon/dmi-freebsd.c @@ -0,0 +1,83 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2012 Vincent Bernat + * + * 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 +#include + +#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 0000000000000000000000000000000000000000..6e1857ef68851bf48fc6f76a2a294d33ea50d1b9 --- /dev/null +++ b/src/daemon/dmi-linux.c @@ -0,0 +1,92 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2009 Vincent Bernat + * + * 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 + +#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(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 0000000000000000000000000000000000000000..a2b9556b06bdce5cc3b607b452b8c0517eca7ff0 --- /dev/null +++ b/src/daemon/dmi-openbsd.c @@ -0,0 +1,78 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2012 Vincent Bernat + * + * 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 +#include + +#ifdef ENABLE_LLDPMED + +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 0000000000000000000000000000000000000000..65d476d07d94e712b5612b09836e2cbc06e174c6 --- /dev/null +++ b/src/daemon/dmi-osx.c @@ -0,0 +1,106 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2012 Vincent Bernat + * + * 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 +#include + +#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 0000000000000000000000000000000000000000..37610085a29b40cec62b29dc0b98d6850cfea0f6 --- /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 0000000000000000000000000000000000000000..a3685696499364b38ff9a83cf55fc4354c377cb1 --- /dev/null +++ b/src/daemon/event.c @@ -0,0 +1,749 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2012 Vincent Bernat + * + * 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 +#include +#include +#include +#include +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdocumentation" +#endif +#include +#include +#include +#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) + +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 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); + + /* 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); + + 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); + + 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 0000000000000000000000000000000000000000..e22a45d5799ac372347e8bb7536b621eca63f8a8 --- /dev/null +++ b/src/daemon/forward-bsd.c @@ -0,0 +1,36 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2013 Vincent Bernat + * + * 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 +#include + +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 0000000000000000000000000000000000000000..db563bdc8435ec942018918712793581238319e7 --- /dev/null +++ b/src/daemon/forward-linux.c @@ -0,0 +1,61 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2013 Vincent Bernat + * + * 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 + +int +ip_forwarding_enabled(int af) +{ + int fd, rc = -1; + 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 0000000000000000000000000000000000000000..22e323fa186a0c1e1dc1de8f3249d4ff693aad54 --- /dev/null +++ b/src/daemon/forward-solaris.c @@ -0,0 +1,26 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2013 Vincent Bernat + * + * 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 0000000000000000000000000000000000000000..a440764e5655a605004f4765d48eb8a7790999e8 --- /dev/null +++ b/src/daemon/frame.c @@ -0,0 +1,67 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2009 Vincent Bernat + * + * 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 0000000000000000000000000000000000000000..eea77aecad3a2874de401fddf7b7ed760ec9cb64 --- /dev/null +++ b/src/daemon/frame.h @@ -0,0 +1,139 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2009 Vincent Bernat + * 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 0000000000000000000000000000000000000000..5237ee75370e3493b887ef0030d0a607992c2a75 --- /dev/null +++ b/src/daemon/interfaces-bpf.c @@ -0,0 +1,123 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2013 Vincent Bernat + * + * 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 +#include +#include + +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 0000000000000000000000000000000000000000..c8e466b0bf9b6b3468f68a32109ef683dc9fa730 --- /dev/null +++ b/src/daemon/interfaces-bsd.c @@ -0,0 +1,722 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2012 Vincent Bernat + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if defined HOST_OS_FREEBSD +# include +# include +# include +#elif defined HOST_OS_DRAGONFLY +# include +# include +#elif defined HOST_OS_OPENBSD +# include +# include +# include +#elif defined HOST_OS_NETBSD +# include +# include +# include +#elif defined HOST_OS_OSX +# include +# include +# include +#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) +{ + struct ifbreq req[64]; + struct ifbifconf bifc = { + .ifbic_len = sizeof(req), + .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 >= sizeof(req)) { + log_warnx("interfaces", + "%s is a bridge too big. Please, report the problem", + master->name); + return; + } + 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(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 0000000000000000000000000000000000000000..15cd562c38d570ed28035923b7eabb4373ba3d8e --- /dev/null +++ b/src/daemon/interfaces-linux.c @@ -0,0 +1,209 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2023-2023 Hisilicon Limited. + * Copyright (c) 2008 Vincent Bernat + * + * 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 +#include +#include +#include +#include +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdocumentation" +#endif +#include +#include +#include +#include +#if defined(__clang__) +#pragma clang diagnostic pop +#endif + +#define SYSFS_PATH_MAX 256 +#define MAX_PORTS 1024 +#define MAX_BRIDGES 1024 + +static int +iflinux_init(struct lldpd *cfg, struct lldpd_hardware *hardware) +{ + int fd; + + log_debug("interfaces", "initialize ub device %s", + hardware->h_ifname); + if ((fd = priv_iface_init(hardware->h_ifindex, hardware->h_ifname)) == -1) + return -1; + 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; +} + +/* Generic ub send/receive */ +static int +iflinux_send(struct lldpd *cfg, struct lldpd_hardware *hardware, + char *buffer, size_t size) +{ + log_debug("interfaces", "send PDU to ub device %s (fd=%d)", + hardware->h_ifname, hardware->h_sendfd); + lldpd_dump_packet("send", buffer, size, hardware); + 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_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 ub device %s", + hardware->h_ifname); + if ((n = iflinux_generic_recv(hardware, fd, buffer, size, &from)) == -1) + return -1; + return n; +} + +static int +iflinux_close(struct lldpd *cfg, struct lldpd_hardware *hardware) +{ + log_debug("interfaces", "close ub device %s", + hardware->h_ifname); + return 0; +} + +static struct lldpd_ops ops = { + .send = iflinux_send, + .recv = iflinux_recv, + .cleanup = iflinux_close, +}; + +/* 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)ðc + }; + 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); + } + } +} + +static void iflinux_add_physical(struct lldpd *cfg, struct interfaces_device_list *interfaces) +{ + struct interfaces_device *iface; + + TAILQ_FOREACH(iface, interfaces, next) + { + iface->type &= ~IFACE_PHYSICAL_T; + + if (iface->dev_type == ARPHRD_UB) { + iface->type |= IFACE_PHYSICAL_T; + log_debug("interfaces", "Device list add UB physical device for %s", iface->name); + } + continue; + } +} + +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); + iflinux_add_physical(cfg, interfaces); + + interfaces_helper_allowlist(cfg, interfaces); + interfaces_helper_physical(cfg, interfaces, + &ops, + iflinux_init); + interfaces_helper_mgmt(cfg, addresses, interfaces); + interfaces_helper_chassis(cfg, interfaces); + + /* GUID/PHY */ + TAILQ_FOREACH(hardware, &cfg->g_hardware, h_entries) { + if (!hardware->h_flags) continue; + 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 0000000000000000000000000000000000000000..fd61d38809a5ea1632ffc0d3e52f80ab8093e74f --- /dev/null +++ b/src/daemon/interfaces-solaris.c @@ -0,0 +1,186 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2013 Vincent Bernat + * + * 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 +#include +#include + +/* 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 0000000000000000000000000000000000000000..d13cb982cb3242149dc59f659ceb51086bc6e672 --- /dev/null +++ b/src/daemon/interfaces.c @@ -0,0 +1,585 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2023-2023 Hisilicon Limited. + * Copyright (c) 2012 Vincent Bernat + * + * 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 +#include +#include +#include +#include + +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; + } +} + +/** + * 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 UB interface for index %d", + index); + return NULL; +} + +void +interfaces_helper_allowlist(struct lldpd *cfg, + struct interfaces_device_list *interfaces) +{ + struct interfaces_device *iface; + int found = 0; + + 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 0: + log_debug("interfaces", "deny %s", iface->name); + iface->ignore = 1; + continue; + case 2: + log_debug("interfaces", "allow %s (consider it as a physical interface)", + iface->name); + found = 1; + iface->type |= IFACE_PHYSICAL_T; + continue; + } + } + + if (!found) { + fatalx("interfaces", "The device is not in the UB device linked list."); + } +} + +/* 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; + + 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 GUID 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; + + 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("ub-lldpd: ", iface->alias, strlen("ub-lldpd: "))); + 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 GUID 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 UB 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) { + 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; + } + 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 + (GUID_LEN - ETHER_ADDR_LEN), + 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; + } +} + +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 GUID. + */ +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; + } + 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 0000000000000000000000000000000000000000..c1e016132fe99e39c216c3dd0a3f8d4ef9721752 --- /dev/null +++ b/src/daemon/lldp-tlv.h @@ -0,0 +1,34 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2012 Vincent Bernat + * + * 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 +#endif diff --git a/src/daemon/lldpd.c b/src/daemon/lldpd.c new file mode 100644 index 0000000000000000000000000000000000000000..6a47c8a9d05fa8c4e5ee6d44d47fb0d707176f25 --- /dev/null +++ b/src/daemon/lldpd.c @@ -0,0 +1,1938 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2023-2023 Hisilicon Limited. + * Copyright (c) 2008 Vincent Bernat + * + * 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 "frame.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if HAVE_VFORK_H +# include +#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 } }, + { 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 "ub-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, "-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 ub-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 ub-lldpcli command.\n"); + fprintf(stderr, "-O file Override default configuration locations processed by ub-lldpcli(8) at start.\n"); + fprintf(stderr, "-F Enable dfx output.\n"); + fprintf(stderr, "\n"); + fprintf(stderr, "see manual page ub-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) { + hardware->h_ifindex = index; + break; + } + if (hardware->h_ifindex == index) + break; + } + } + return hardware; +} + +struct interfaces_device * +lldpd_get_device(struct lldpd *cfg, const char *name) +{ + struct interfaces_device *iff; + + TAILQ_FOREACH (iff, cfg->g_netlink->devices, next) { + if (strcmp(iff->name, name) == 0) break; + } + if (iff == NULL) log_warn("interfaces", "Not found interfaces device for name: %s", name); + return iff; +} + +/** + * 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); + + 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); + 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); + + 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); + 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_display_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) + priv_iface_description(hardware->h_ifname, + ""); + else if (neighbors == 1 && neighbor && *neighbor != '\0') { + if (asprintf(&description, "%s", + neighbor) != -1) { + priv_iface_description(hardware->h_ifname, description); + free(description); + } + } else { + if (asprintf(&description, "%d neighbor%s", + neighbors, (neighbors > 1)?"s":"") != -1) { + priv_iface_description(hardware->h_ifname, + description); + 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_display_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); +} + +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_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); + 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 0: + 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 1: + case 2: + log_debug("localchassis", "do not delete %s, permanent", + hardware->h_ifname); + 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 GUID)", + 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 *buff, int s, + struct lldpd_hardware *hardware) +{ + int i; + struct lldpd_chassis *chassis, *ochassis = NULL; + struct lldpd_port *port, *oport = NULL, *aport; + struct interfaces_device *dev; + int guess = LLDPD_MODE_LLDP; + char *frame = buff; + 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; + } + + dev = lldpd_get_device(cfg, hardware->h_ifname); + if (dev == NULL) + return; + + frame += sizeof(struct ub_link_header); + + 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); + } 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); + } + + 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 *const command[] = { "lsb_release", "-s", "-d", 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, ", ", 2); + strncat(buffer, cfg->g_protocols[i].name, strlen(cfg->g_protocols[i].name)); + } + } + 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); + } +} + +static char * +lldpd_get_tlv_name(int tlv_type) +{ + /* UB support TLV type 0-8, parse them only */ + switch (tlv_type) { + case LLDP_TLV_END: + return "End of LLDPDU"; + case LLDP_TLV_CHASSIS_ID: + return "Chassis ID"; + case LLDP_TLV_PORT_ID: + return "Port ID"; + case LLDP_TLV_TTL: + return "Time To Live"; + case LLDP_TLV_PORT_DESCR: + return "Port Description"; + case LLDP_TLV_SYSTEM_NAME: + return "System Name"; + case LLDP_TLV_SYSTEM_DESCR: + return "System Description"; + case LLDP_TLV_SYSTEM_CAP: + return "System Capabilities"; + case LLDP_TLV_MGMT_ADDR: + return "Management Address"; + default: + return NULL; + } +} + +static int +lldpd_dump_tlv_value_per_line(const char *token, const u_int8_t *pos, int tlv_size) +{ + char *buffer, *buffer_pos; + int ret, i; + + /* A maximum of 16 bytes should be output per line. */ + if (tlv_size >= GUID_LEN) { + log_dfx(token, "%.2x%.2x %.2x%.2x %.2x%.2x %.2x%.2x " + "%.2x%.2x %.2x%.2x %.2x%.2x %.2x%.2x", + pos[HW_ADDR0], pos[HW_ADDR1], pos[HW_ADDR2], pos[HW_ADDR3], pos[HW_ADDR4], + pos[HW_ADDR5], pos[HW_ADDR6], pos[HW_ADDR7], pos[HW_ADDR8], pos[HW_ADDR9], + pos[HW_ADDR10], pos[HW_ADDR11], pos[HW_ADDR12], + pos[HW_ADDR13], pos[HW_ADDR14], pos[HW_ADDR15]); + return GUID_LEN; + } + + /* 1 byte takes 2 places in %.2x, an extra place for blank */ + buffer = (char *)calloc(tlv_size * DFX_PLACE_NUM, sizeof(char)); + if (!buffer) { + log_dfx(token, "unable to allocate memory for tlv value"); + return -1; + } + + buffer_pos = buffer; + for (i = 0; i < tlv_size; i++) { + ret = sprintf(buffer_pos, "%.2x", pos[i]); + if (ret < 0) { + log_dfx(token, "unable to compose tlv value"); + free(buffer); + return -1; + } + + buffer_pos += ret; + if (i % DFX_BYTE_NUM == 1) + strcat(buffer_pos++, " "); + } + log_dfx(token, "%s", buffer); + free(buffer); + return tlv_size; +} + +static void +lldpd_dump_tlvs(const char *token, const u_int8_t *position, int len) +{ + int tlv_size, tlv_type, ret; + char *tlv_name; + int length = len; + const u_int8_t *pos = position; + + while (length >= sizeof(u_int16_t)) { + tlv_size = PEEK_UINT16; + tlv_type = tlv_size >> TLV_INFO_BITS; + tlv_size = tlv_size & 0x1ff; + tlv_name = lldpd_get_tlv_name(tlv_type); + if (tlv_name) + log_dfx(token, "TLV type: %s", tlv_name); + else + log_dfx(token, "TLV type: %d", tlv_type); + log_dfx(token, "TLV length: %d", tlv_size); + if (tlv_type == LLDP_TLV_END) + return; + log_dfx(token, "TLV value:"); + + if (length < tlv_size) + tlv_size = length; + length -= tlv_size; + + while (tlv_size > 0) { + ret = lldpd_dump_tlv_value_per_line(token, pos, tlv_size); + if (ret < 0) + return; + tlv_size -= ret; + pos += ret; + } + } +} + +static void +lldpd_dump_ub_packet(const char *token, const char *packet, int len) +{ + struct ub_link_header ub_header; + u_int8_t *pos; + int length = len; + + if (length < sizeof(struct ub_link_header)) + return; + + pos = (u_int8_t *)packet; + PEEK_BYTES(&ub_header, sizeof(struct ub_link_header)); + ub_header.ub_protocol = htons(ub_header.ub_protocol); + if (ub_header.ub_protocol != LLDP_PROTO || ub_header.ub_cfg != UB_CFG_TYPE) + return; + + log_dfx(token, "Parse Packet:"); + log_dfx(token, "Type: UB"); + log_dfx(token, "CFG: %u", ub_header.ub_cfg); + /* UB GUID has length 16, show every byte inside */ + log_dfx(token, "dGUID: %.2x:%.2x:%.2x:%.2x:%.2x:%.2x:%.2x:%.2x:" + "%.2x:%.2x:%.2x:%.2x:%.2x:%.2x:%.2x:%.2x", + ub_header.ub_dguid[HW_ADDR0], ub_header.ub_dguid[HW_ADDR1], + ub_header.ub_dguid[HW_ADDR2], ub_header.ub_dguid[HW_ADDR3], + ub_header.ub_dguid[HW_ADDR4], ub_header.ub_dguid[HW_ADDR5], + ub_header.ub_dguid[HW_ADDR6], ub_header.ub_dguid[HW_ADDR7], + ub_header.ub_dguid[HW_ADDR8], ub_header.ub_dguid[HW_ADDR9], + ub_header.ub_dguid[HW_ADDR10], ub_header.ub_dguid[HW_ADDR11], + ub_header.ub_dguid[HW_ADDR12], ub_header.ub_dguid[HW_ADDR13], + ub_header.ub_dguid[HW_ADDR14], ub_header.ub_dguid[HW_ADDR15]); + log_dfx(token, "sGUID: %.2x:%.2x:%.2x:%.2x:%.2x:%.2x:%.2x:%.2x:" + "%.2x:%.2x:%.2x:%.2x:%.2x:%.2x:%.2x:%.2x", + ub_header.ub_sguid[HW_ADDR0], ub_header.ub_sguid[HW_ADDR1], + ub_header.ub_sguid[HW_ADDR2], ub_header.ub_sguid[HW_ADDR3], + ub_header.ub_sguid[HW_ADDR4], ub_header.ub_sguid[HW_ADDR5], + ub_header.ub_sguid[HW_ADDR6], ub_header.ub_sguid[HW_ADDR7], + ub_header.ub_sguid[HW_ADDR8], ub_header.ub_sguid[HW_ADDR9], + ub_header.ub_sguid[HW_ADDR10], ub_header.ub_sguid[HW_ADDR11], + ub_header.ub_sguid[HW_ADDR12], ub_header.ub_sguid[HW_ADDR13], + ub_header.ub_sguid[HW_ADDR14], ub_header.ub_sguid[HW_ADDR15]); + log_dfx(token, "Protocol: 0x%.3x", ub_header.ub_protocol); + + PEEK_DISCARD(sizeof(struct ether_header)); + lldpd_dump_tlvs(token, pos, length); + log_dfx(token, "End Parse Packet"); +} + +void +lldpd_dump_packet(const char *token, const char *packet, int length, struct lldpd_hardware *hardware) +{ + struct interfaces_device *iface; + + if (!token || !packet || !hardware) + return; + + iface = lldpd_get_device(hardware->h_cfg, hardware->h_ifname); + if (!iface) + return; + + if (iface->dev_type == ARPHRD_UB) { + lldpd_dump_ub_packet(token, packet, length); + } else { + log_dfx(token, "skip dump packet for unknown dev_type %d", iface->dev_type); + } +} + +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++; + lldpd_dump_packet("receive", buffer, n, hardware); + 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_count_neighbors(cfg); + free(buffer); +} + +static int lldpd_send_status_check(struct lldpd_hardware *hardware) +{ + if (hardware->h_cfg->g_config.c_receiveonly) { + log_dfx("send", "ub-lldpd is rx-only"); + return 1; + } + if (hardware->h_cfg->g_config.c_paused) { + log_dfx("send", "ub-lldpd is paused."); + return 1; + } + if (hardware->h_lport.p_disable_tx) { + log_dfx("send", "ub-lldpd tx is disable"); + return 1; + } + if ((hardware->h_flags & IFF_RUNNING) == 0) { + log_dfx("send", "hardware %s is not IFF_RUNNING", hardware->h_ifname); + return 1; + } + return 0; +} + + +static void +lldpd_send_shutdown(struct lldpd_hardware *hardware) +{ + if (lldpd_send_status_check(hardware)) + 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 (lldpd_send_status_check(hardware)) + 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"); + } +} + +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); + + /* 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; + + 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"); + 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 ub-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, "ub-lldpcli", sdebug, + "-u", ctlname, + "-C", config_path, + "resume", + (char *)NULL); + } else { + execl(path, "ub-lldpcli", sdebug, + "-u", ctlname, + "-C", SYSCONFDIR "/ub-lldpd.conf", + "-C", SYSCONFDIR "/ub-lldpd.d", + "resume", + (char *)NULL); + } + + log_warn("main", "unable to execute %s", path); + log_warnx("main", "configuration is incomplete, ub-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 } +}; + +/** + * Tell if we have been started by systemd. + */ +static int +lldpd_started_by_systemd() +{ + 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; + + struct iovec iov = { + .iov_base = "READY=1", + .iov_len = strlen("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; +} + +#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("ub-lldpd", "minimal kernel version required is %s, got %s", + MIN_LINUX_KERNEL_VERSION, uts.release); + log_warnx("ub-lldpd", "ub-lldpd may be unable to detect bonds and bridges correctly"); + } +} +#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; + const char *ctlname = NULL; + char *mgmtp = NULL; + char *cidp = NULL; + char *interfaces = NULL; + /* We do not want more options here. Please add them in ub-lldpcli instead + * unless there is a very good reason. Most command-line options will + * get deprecated at some point. */ + char *popt, + opts[] = "H:vhkrdD:Fp:m:u:4:6:I:C:p:P:S:L:O:@ "; + int i, found, advertise_version = 1; + 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; + 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; + case 'F': + log_dfx_enable(); + 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, "ub-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", "ub-lldpd " PACKAGE_VERSION " starting..."); + log_dfx("main", "ub-lldpd dfx output enabled"); + version_check(); + + /* 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 */ + 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); + } + + /* Configuration with ub-lldpcli */ + if (lldpcli) { + if (!config_file) { + log_debug("main", "invoking ub-lldpcli for default configuration locations"); + } else { + log_debug("main", "invoking ub-lldpcli for user supplied configuration location"); + } + if (lldpd_configure(use_syslog, debug, lldpcli, ctlname, config_file) == -1) + fatal("main", "unable to spawn ub-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; + + /* 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; + 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); + + 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; + + 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 0000000000000000000000000000000000000000..98602d8605be35cea44496a4e21c13a261a74d34 --- /dev/null +++ b/src/daemon/lldpd.h @@ -0,0 +1,360 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2023-2023 Hisilicon Limited. + * Copyright (c) 2008 Vincent Bernat + * + * 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 +#endif + +#ifdef HAVE_VALGRIND_VALGRIND_H +# include +#else +# define RUNNING_ON_VALGRIND 0 +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lldp-tlv.h" + +#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)) + +#define LLDP_PROTO 0x109 +#define GUID_LEN 16 +#define UB_CFG_TYPE 5 +#define ARPHRD_UB 38 +#define DFX_PLACE_NUM 3 +#define DFX_BYTE_NUM 2 +#define TLV_INFO_BITS 9 +#define IEEE802_1Q_TAG_LEN 4 + +enum link_header_addr { + HW_ADDR0 = 0, + HW_ADDR1, + HW_ADDR2, + HW_ADDR3, + HW_ADDR4, + HW_ADDR5, + HW_ADDR6, + HW_ADDR7, + HW_ADDR8, + HW_ADDR9, + HW_ADDR10, + HW_ADDR11, + HW_ADDR12, + HW_ADDR13, + HW_ADDR14, + HW_ADDR15 +}; + +struct ub_link_header { + u_int8_t ub_cfg; + u_int16_t ub_protocol; + u_int8_t ub_dguid[GUID_LEN]; + u_int8_t ub_sguid[GUID_LEN]; +} __attribute__((packed)); + +struct protocol { + int mode; /* > 0 mode identifier (unique per protocol) */ + int enabled; /* Is this protocol enabled? */ + char *name; /* Name of protocol */ + char arg; /* Argument to enable this protocol */ + int(*send)(PROTO_SEND_SIG); /* How to send a frame */ + int(*decode)(PROTO_DECODE_SIG); /* How to decode a frame */ + int(*guess)(PROTO_GUESS_SIG); /* Can be NULL, use MAC address in this case */ + u_int8_t mac[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 interfaces_device *lldpd_get_device(struct lldpd *, const char *); +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 *); +void lldpd_dump_packet(const char *token, const char *packet, int length, struct lldpd_hardware *hardware); + +/* 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); + +/* 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(char*); +void asroot_open(void); +#endif +int priv_iface_init(int, char *); +int asroot_iface_init_os(int, char *, 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 *); + +enum priv_cmd { + PRIV_PING, + PRIV_DELETE_CTL_SOCKET, + PRIV_GET_HOSTNAME, + PRIV_OPEN, + PRIV_IFACE_INIT, + PRIV_IFACE_DESCRIPTION, + PRIV_IFACE_PROMISC, +}; + +/* 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); + +/*BPF filter that carries the UBL2 packet header*/ +/*Bit[0:7] of the packet is CFG, and the value is 0x5.*/ +/*Bits [8:23] in the packet are protocol, and the value is 0x109.*/ + +#define UB_LLDPD_FILTER_F \ + { 0x30, 0, 0, 0x00000000 }, { 0x15, 0, 3, 0x00000005 }, \ + { 0x28, 0, 0, 0x00000001 }, { 0x15, 0, 1, 0x00000109 }, \ + { 0x6, 0, 0, 0xffffffff }, { 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 */ + +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) */ + int dev_type; /* Device type from driver */ + 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 *); +int interfaces_send_helper(struct lldpd *, + struct lldpd_hardware *, char *, size_t); +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 { + int nl_socket; + int nl_socket_recv_size; + /* Cache */ + struct interfaces_device_list *devices; + struct interfaces_address_list *addresses; +}; +#endif + +/* pattern.c */ +int pattern_match(char *, char *, int); + +struct lldpd { + int g_sock; + struct event_base *g_base; + + struct lldpd_config g_config; + + struct protocol *g_protocols; + int g_lastrid; + struct event *g_main_loop; + struct event *g_cleanup_timer; + + /* 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/main.c b/src/daemon/main.c new file mode 100644 index 0000000000000000000000000000000000000000..c2d9297966f79a61612adb8099391c3a417aa198 --- /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 0000000000000000000000000000000000000000..31fc05608615a2346b8a4fd212fd45dfc6152381 --- /dev/null +++ b/src/daemon/netlink.c @@ -0,0 +1,828 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2023-2023 Hisilicon Limited. + * Copyright (c) 2012 Vincent Bernat + * + * 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 +#include +#include +#include +#include +#include +#include + +#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; +}; + +/** + * 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 protocol Which protocol to use (eg NETLINK_ROUTE). + * @param groups Which groups we want to subscribe to + * @return 0 on success, -1 otherwise + */ +static int +netlink_connect(struct lldpd *cfg, int protocol, unsigned groups) +{ + int s; + struct sockaddr_nl local = { + .nl_family = AF_NETLINK, + .nl_pid = 0, + .nl_groups = groups + }; + + /* Open Netlink socket */ + log_debug("netlink", "opening netlink socket"); + s = socket(AF_NETLINK, SOCK_RAW, protocol); + if (s == -1) { + log_warn("netlink", "unable to open netlink socket"); + return -1; + } + if (NETLINK_SEND_BUFSIZE && + netlink_socket_set_buffer_size(s, + SO_SNDBUF, "SO_SNDBUF", NETLINK_SEND_BUFSIZE) == -1) { + close(s); + return -1; + } + + int rc = netlink_socket_set_buffer_size(s, + SO_RCVBUF, "SO_RCVBUF", NETLINK_RECEIVE_BUFSIZE); + switch (rc) { + case -1: + close(s); + return -1; + case -2: cfg->g_netlink->nl_socket_recv_size = 0; break; + default: cfg->g_netlink->nl_socket_recv_size = rc; break; + } + if (groups && bind(s, (struct sockaddr *)&local, sizeof(struct sockaddr_nl)) < 0) { + log_warn("netlink", "unable to bind netlink socket"); + close(s); + return -1; + } + cfg->g_netlink->nl_socket = s; + return 0; +} + +/** + * 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) + }; + + /* 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 `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) +{ + return; +} + +/** + * 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_UB) { + log_debug("netlink", "skip non UB interface at index %d", + ifi->ifi_index); + return -1; + } + + iff->index = ifi->ifi_index; + iff->flags = ifi->ifi_flags; + iff->dev_type = ifi->ifi_type; + 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)); + log_dfx("netlink", "The alias value of the %s is %s", + iff->name, iff->alias); + break; + case IFLA_ADDRESS: + /* Interface GUID */ + 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); + log_dfx("netlink", "The MTU value of the %s is %d", + iff->name, iff->mtu); + break; + case IFLA_LINKINFO: + 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; +} + +static void netlink_copy_address(struct nlmsghdr *msg, struct ifaddrmsg *ifi, struct interfaces_address *ifa) +{ + struct rtattr *attribute; + + int len; + len = msg->nlmsg_len - NLMSG_LENGTH(sizeof(struct ifaddrmsg)); + + 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; + } + } +} + +/** + * 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_device_list *interfaces, + struct interfaces_address *ifa) +{ + struct interfaces_device *device; + struct ifaddrmsg *ifi; + ifi = NLMSG_DATA(msg); + + ifa->index = ifi->ifa_index; + device = interfaces_indextointerface(interfaces, ifa->index); + if (device == NULL || device->dev_type != ARPHRD_UB) + return -1; + + 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; + } + + netlink_copy_address(msg, ifi, ifa); + 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. + */ +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; + + /* 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, + 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; + int s = cfg->g_netlink->nl_socket; + + 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) { + /* Try to increase buffer size */ + 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, cfg->g_netlink->devices, ifanew) == 0) { + 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, NETLINK_ROUTE, 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->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) +{ + 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, RTM_GETLINK, AF_PACKET, 2) == -1) + goto end; + netlink_recv(cfg, ifs, NULL); + if (netlink_send(cfg->g_netlink->nl_socket, RTM_GETADDR, AF_UNSPEC, 1) == -1) + goto end; + netlink_recv(cfg, NULL, ifaddrs); + /* Listen to any future change */ + cfg->g_iface_cb = netlink_change_cb; + if (levent_iface_subscribe(cfg, cfg->g_netlink->nl_socket) == -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 != -1) + close(cfg->g_netlink->nl_socket); + 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 0000000000000000000000000000000000000000..a13817b8cdf51ab805a323ac1346f90206053c27 --- /dev/null +++ b/src/daemon/pattern.c @@ -0,0 +1,80 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2008 Vincent Bernat + * + * 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 +#include + +/** + * 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 0 + * or 1. + * + * 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 0 if the string matches a denied pattern which is not + * allowed or if the pattern wasn't found and `found` was set to + * 0. Otherwise, return 1 unless the interface match is exact, in this + * case return 2. + */ +int +pattern_match(char *string, char *patterns, int found) +{ + char *pattern; + int denied = 0; + found = !!found; + + if ((patterns = strdup(patterns)) == NULL) { + log_warnx("interfaces", "unable to allocate memory"); + return 0; + } + + 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))?1:2; + break; + } + if ((pattern[0] == '!') && + (fnmatch(pattern + 1, string, 0) == 0)) { + denied = 1; + found = 0; + } else if (!denied && fnmatch(pattern, string, 0) == 0) { + if (!strcmp(pattern, string)) { + found = 2; + } else if (found < 2) { + found = 1; + } + } + } + + free(patterns); + return found; +} diff --git a/src/daemon/priv-bsd.c b/src/daemon/priv-bsd.c new file mode 100644 index 0000000000000000000000000000000000000000..f32f990cb15269d1da5b776a86971512691725d3 --- /dev/null +++ b/src/daemon/priv-bsd.c @@ -0,0 +1,220 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2008 Vincent Bernat + * + * 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 +#include +#include +#include +#include +#include +#include +#include + +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 0000000000000000000000000000000000000000..97790640d695bb4e9c2ac38cca10a80ebb28e7ce --- /dev/null +++ b/src/daemon/priv-linux.c @@ -0,0 +1,408 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2023-2023 Hisilicon Limited. + * Copyright (c) 2008 Vincent Bernat + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include /* For sockaddr_ll */ +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdocumentation" +#endif +#include /* For BPF filtering */ +#include +#include +#include +#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(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/[^.][^/]*", + 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)ðc + }; + 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); +} + +static int +asroot_iface_get_hw_type(const char *name, int *hwtype) +{ + struct ifreq tmp; + int sock; + + if (name == NULL) { + log_warn("interfaces", "Device name is null"); + return -1; + } + + if (strlen(name) >= sizeof(tmp.ifr_name)) { + log_warn("interfaces", "Device name too long: %s", name); + return -1; + } + + sock = socket(AF_INET, SOCK_DGRAM, 0); + if (sock < 0) { + log_warn("interfaces", "Can't create socket for %s", name); + return -1; + } + + memset(&tmp, 0, sizeof(tmp)); + strcpy(tmp.ifr_name, name); + if (ioctl(sock, SIOCGIFHWADDR, &tmp) < 0) { + log_warn("interfaces", "Error getting hardware address for %s", name); + close(sock); + return -1; + } + *hwtype = tmp.ifr_hwaddr.sa_family; + close(sock); + return 0; +} + +int +asroot_iface_init_os(int ifindex, char *name, int *fd) +{ + struct sock_fprog prog; + int hwtype; + 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); + if (asroot_iface_get_hw_type(name, &hwtype) != 0) + return -1; + + static struct sock_filter ub_lldpd_filter_f[] = { UB_LLDPD_FILTER_F }; + prog.filter = ub_lldpd_filter_f; + prog.len = sizeof(ub_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; +#ifndef IFALIASZ +# define IFALIASZ 256 +#endif + 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, "ub-lldpd: ", strlen("ub-lldpd: ")) == 0) { + if (strncmp(descr + strlen("ub-lldpd: "), "was ", strlen("was ")) == 0) { + /* Already has an old neighbor */ + fclose(fp); + return 0; + } else { + /* Append was */ + memmove(descr + strlen("ub-lldpd: was "), descr + strlen("ub-lldpd: "), + sizeof(descr) - strlen("ub-lldpd: was ")); + memcpy(descr, "ub-lldpd: was ", strlen("ub-lldpd: was ")); + } + } else { + /* No description, no neighbor */ + strlcpy(descr, "ub-lldpd: no neighbor", sizeof(descr)); + } + } else + snprintf(descr, sizeof(descr), "ub-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) { + log_dfx("privsep", "promiscuous mode is enabled for %s, no need to set", + name); + 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 0000000000000000000000000000000000000000..6d2736afd515f8d591d06507d55c2d3d64d70e78 --- /dev/null +++ b/src/daemon/priv-seccomp.c @@ -0,0 +1,202 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2008 Vincent Bernat + * + * 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 +#include +#include +#include + +#include "syscall-names.h" +#include + +#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 || + /* 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 0000000000000000000000000000000000000000..3d0a98d68755fa7569f1ac43b9156312ea300b50 --- /dev/null +++ b/src/daemon/priv.c @@ -0,0 +1,668 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2008 Vincent Bernat + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_LINUX_CAPABILITIES +#include +#include +#endif + +#if defined HOST_OS_FREEBSD || defined HOST_OS_OSX || defined HOST_OS_DRAGONFLY +# include +#endif +#if defined HOST_OS_SOLARIS +# include +#endif + +/* Use resolv.h */ +#ifdef HAVE_SYS_TYPES_H +# include +#endif +#ifdef HAVE_NETINET_IN_H +# include +#endif +#ifdef HAVE_ARPA_NAMESER_H +# include /* DNS HEADER struct */ +#endif +#ifdef HAVE_NETDB_H +# include +#endif +#ifdef HAVE_RESOLV_H +# include +#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_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; +} + +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_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)); +} + +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_DESCRIPTION, asroot_iface_description}, + {PRIV_IFACE_PROMISC, asroot_iface_promisc}, + {-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 + +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 +} + +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 +} + +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 0000000000000000000000000000000000000000..068cb5c339db4d4fb0809024d22c16fd1f37435a --- /dev/null +++ b/src/daemon/privsep.c @@ -0,0 +1,24 @@ +/* -*- 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 0000000000000000000000000000000000000000..ab2d9a6c5a824d2f490f4f8749feaf570cf42838 --- /dev/null +++ b/src/daemon/privsep_fd.c @@ -0,0 +1,132 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ + +#include "lldpd.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * Copyright 2001 Niels Provos + * 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 0000000000000000000000000000000000000000..5b46531c6ccc39526ddc414a7951bf49e3d5abd6 --- /dev/null +++ b/src/daemon/privsep_io.c @@ -0,0 +1,101 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ + +#include "lldpd.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Stolen from sbin/pflogd/privsep.c from OpenBSD */ +/* + * Copyright (c) 2003 Can Erkin Acar + * Copyright (c) 2003 Anil Madhavapeddy + * + * 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 0000000000000000000000000000000000000000..a4d778549c2c55c15a8f24376ec1670403562ae7 --- /dev/null +++ b/src/daemon/probes.d @@ -0,0 +1,115 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2013 Vincent Bernat + * + * 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 0000000000000000000000000000000000000000..06da59ecf41751d782132189c91355af2220b683 --- /dev/null +++ b/src/daemon/protocols/cdp.c @@ -0,0 +1,725 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2008 Vincent Bernat + * + * 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 +#include +#include +#include + +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 + 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); + (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 0000000000000000000000000000000000000000..73f127cd7d72544b3bab8902f5c5d4e71391be3d --- /dev/null +++ b/src/daemon/protocols/cdp.h @@ -0,0 +1,63 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2008 Vincent Bernat + * + * 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 0000000000000000000000000000000000000000..0c9b05c8b58ab1c1aa2094e32e42b5a2e52c5b80 --- /dev/null +++ b/src/daemon/protocols/edp.c @@ -0,0 +1,515 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2008 Vincent Bernat + * + * 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 +#include +#include +#include +#include + +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. */ + 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); + + /* 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; + } + + 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; + if (asprintf(&port->p_id, "%d/%d", + edp_slot + 1, edp_port + 1) == -1) { + log_warn("edp", "unable to allocate memory for " + "port ID"); + goto malformed; + } + port->p_id_len = strlen(port->p_id); + if (asprintf(&port->p_descr, "Slot %d / Port %d", + 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); + 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 0000000000000000000000000000000000000000..e3cb7382b7caee4c2d09490f9fb5daa778d19ed7 --- /dev/null +++ b/src/daemon/protocols/edp.h @@ -0,0 +1,37 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2008 Vincent Bernat + * + * 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 0000000000000000000000000000000000000000..3fc0d8b1b48f57893db4d330186372a25b6c66d3 --- /dev/null +++ b/src/daemon/protocols/lldp.c @@ -0,0 +1,597 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2023-2023 Hisilicon Limited. + * Copyright (c) 2008 Vincent Bernat + * + * 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 +#include +#include +#include +#include +#include + +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) +{ + struct lldpd_port *port; + struct lldpd_chassis *chassis; + struct interfaces_device *iff; + struct lldpd_frame *frame; + int length; + u_int8_t *packet, *pos, *tlv; + struct lldpd_mgmt *mgmt; + int proto; + + u_int8_t mcastaddr_regular[] = LLDP_ADDR_NEAREST_BRIDGE; + u_int8_t *mcastaddr; + 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; + + iff = lldpd_get_device(global, hardware->h_ifname); + if (iff == NULL) return ENODEV; + + struct ub_link_header ub_header; + + memset(&ub_header, 0x0, sizeof(struct ub_link_header)); + memset(ub_header.ub_dguid, 0xff, GUID_LEN); + + ub_header.ub_cfg = UB_CFG_TYPE; + ub_header.ub_protocol = htons(LLDP_PROTO); + memcpy(ub_header.ub_sguid, iff->address, GUID_LEN); + if (!(POKE_BYTES(&ub_header, sizeof(struct ub_link_header)))) + goto toobig; + + /* Ethernet header */ + mcastaddr = mcastaddr_regular; + + if (!( + /* LLDP multicast address */ + POKE_BYTES(mcastaddr, ETHER_ADDR_LEN) && + /* Source GUID */ + POKE_BYTES(&hardware->h_lladdr, ETHER_ADDR_LEN))) + goto toobig; + + if (!( + /* LLDP frame */ + POKE_UINT16(ETHERTYPE_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; + } + +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: + log_info("lldp", "Cannot send LLDP packet for %s, Too big message", p_id); + free(packet); + 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); +} + +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) + 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]; + 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; + struct lldpd_mgmt *mgmt; + int af; + u_int8_t addr_str_length, addr_str_buffer[32]; + u_int8_t addr_family, addr_length, *addr_ptr, iface_subtype; + u_int32_t iface_number, iface; + + 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; + } + + 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 != ETHERTYPE_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", "first TLV should be a chassis ID on %s, not %d", + hardware->h_ifname, tlv_type); + goto malformed; + } + break; + case LLDP_TLV_PORT_ID: + if (tlv_count != 2) { + log_warnx("lldp", "second TLV should be a port ID on %s, not %d", + hardware->h_ifname, tlv_type); + goto malformed; + } + break; + case LLDP_TLV_TTL: + if (tlv_count != 3) { + log_warnx("lldp", "third TLV should be a TTL on %s, not %d", + hardware->h_ifname, tlv_type); + 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; + 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: + 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 0000000000000000000000000000000000000000..f8f12469e28ad996a550e645fda3dfff665f538f --- /dev/null +++ b/src/daemon/protocols/sonmp.c @@ -0,0 +1,415 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2008 Vincent Bernat + * + * 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 +#include +#include +#include + +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; + if (asprintf(&port->p_id, "%02x-%02x-%02x", + seg[0], seg[1], seg[2]) == -1) { + log_warn("sonmp", "unable to allocate memory for port id on %s", + hardware->h_ifname); + goto malformed; + } + port->p_id_len = strlen(port->p_id); + + /* 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 0000000000000000000000000000000000000000..ff7a720f0b5d6cc9dc04b301f0c40361b96da8a5 --- /dev/null +++ b/src/daemon/protocols/sonmp.h @@ -0,0 +1,38 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2008 Vincent Bernat + * + * 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; + 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 0000000000000000000000000000000000000000..d4b48464b2f350ab13274627d4795fb5d62957ab --- /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/ub-lldpd.8.in b/src/daemon/ub-lldpd.8.in new file mode 100644 index 0000000000000000000000000000000000000000..3e121e2cc07d2cf30160fe294aff0061fb49a061 --- /dev/null +++ b/src/daemon/ub-lldpd.8.in @@ -0,0 +1,424 @@ +.\" Copyright (c) 2006 Pierre-Yves Ritschard +.\" Copyright (c) 2008 Vincent Bernat +.\" +.\" 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 UB_LLDPD 8 +.Os +.Sh NAME +.Nm ub-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 ub-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 ub-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 ub-lldpcli . +.It Sy 3 +Communication Device Endpoints (Class III). In this case, the standard +requires to define at least one network policy through +.Nm ub-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 ub-lldpcli +Provide an alternative path to +.Nm ub-lldpcli +for configuration. If empty, does not use +.Nm ub-lldpcli +for configuration. +.It Fl O Ar configfile +Override default configuration locations processed by +.Nm ub-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 ub-lldpctl 8 . +.It /etc/ub-lldpd.conf +Configuration file for +.Nm . +Commands in this files are executed by +.Xr ub-lldpcli 8 +at start. +.It /etc/ub-lldpd.d +Directory containing configuration files whose commands are executed +by +.Xr ub-lldpcli 8 +at start. +.El +.Sh SEE ALSO +.Xr ub-lldpctl 8 , +.Xr ub-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/ub-lldpd.service.in b/src/daemon/ub-lldpd.service.in new file mode 100644 index 0000000000000000000000000000000000000000..c8ac4d8bf0a0c47f5e8310923e62b9dbd2fdf5fb --- /dev/null +++ b/src/daemon/ub-lldpd.service.in @@ -0,0 +1,22 @@ +[Unit] +Description=LLDP daemon +Documentation=man:ub-lldpd(8) +After=network.target +RequiresMountsFor=@PRIVSEP_CHROOT@ + +[Service] +Type=notify +NotifyAccess=main +EnvironmentFile=-/etc/default/ub-lldpd +EnvironmentFile=-/etc/sysconfig/ub-lldpd +ExecStart=@sbindir@/ub-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/ub-lldpd.supp b/src/daemon/ub-lldpd.supp new file mode 100644 index 0000000000000000000000000000000000000000..7e60fc21759c8dd10469ed456348062085f170e7 --- /dev/null +++ b/src/daemon/ub-lldpd.supp @@ -0,0 +1,43 @@ +# Memory leak when initializing the event loop. Should only happens once. +{ + one-time-malloc-in-levent-init + Memcheck:Leak + fun:malloc + ... + fun:levent_init + ... +} +{ + one-time-realloc-in-levent-init + Memcheck:Leak + fun:realloc + ... + fun:levent_init + ... +} +{ + one-time-calloc-in-levent-init + Memcheck:Leak + fun:calloc + ... + fun:levent_init + ... +} + +# setproctitle_init happens ony one time +{ + one-time-setproctitle-init + Memcheck:Leak + ... + fun:setproctitle_init + ... +} + +# Static variable in priv.c +{ + static-storage-gethostname + Memcheck:Leak + fun:realloc + fun:priv_gethostname + ... +} diff --git a/src/daemon/ub-lldpd.sysusers.conf.in b/src/daemon/ub-lldpd.sysusers.conf.in new file mode 100644 index 0000000000000000000000000000000000000000..2c4de62d765fc32316e80f1c10992f8667af7fd1 --- /dev/null +++ b/src/daemon/ub-lldpd.sysusers.conf.in @@ -0,0 +1,6 @@ +# System user and group for ub-lldpd +# @PRIVSEP_USER@:@PRIVSEP_GROUP@ + +# Type Name ID GECOS Home +u @PRIVSEP_USER@ - "ub-lldpd user" @PRIVSEP_CHROOT@ +m @PRIVSEP_USER@ @PRIVSEP_GROUP@ diff --git a/src/daemon/usr.sbin.ub-lldpd.in b/src/daemon/usr.sbin.ub-lldpd.in new file mode 100644 index 0000000000000000000000000000000000000000..8217b45681ec7d63241ba0cdb3318fa21f336763 --- /dev/null +++ b/src/daemon/usr.sbin.ub-lldpd.in @@ -0,0 +1,65 @@ +#include + +@sbindir@/ub-lldpd { + #include + #include + + 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@/ub-lldpd mr, + /run/systemd/notify w, + + # Ability to run ub-lldpcli for self-configuration + @sbindir@/ub-lldpcli rix, + @sysconfdir@/ub-lldpd.d/ r, + @sysconfdir@/ub-lldpd.d/* r, + @sysconfdir@/ub-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 + #include + /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 0000000000000000000000000000000000000000..cc1c024dc79c98c5386c53a3a3e3c047405b029d --- /dev/null +++ b/src/lib/Makefile.am @@ -0,0 +1,69 @@ +AM_CFLAGS = -I $(top_srcdir)/include $(LLDP_CFLAGS) +AM_CPPFLAGS = $(LLDP_CPPFLAGS) +AM_LDFLAGS = $(LLDP_LDFLAGS) + +lib_LTLIBRARIES = libublldpctl.la +include_HEADERS = lldpctl.h + +ATOM_FILES = \ + atoms/config.c \ + atoms/interface.c atoms/mgmt.c atoms/port.c \ + atoms/chassis.c +libublldpctl_la_SOURCES = \ + lldpctl.h atom.h helpers.h \ + errors.c connection.c atom.c helpers.c \ + $(ATOM_FILES) +nodist_libublldpctl_la_SOURCES = atom-glue.c +libublldpctl_la_LIBADD = $(top_builddir)/src/libcommon-daemon-lib.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_\([^(]*\)().*, \([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() {"; \ + 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_\([^(]*\)().*, \([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() {"; \ + 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 updaing ub-lldpctl.map. +libublldpctl_la_LDFLAGS = $(AM_LDFLAGS) -version-info 13:0:9 + +if HAVE_LD_VERSION_SCRIPT +libublldpctl_la_DEPENDENCIES = ub-lldpctl.map +libublldpctl_la_LDFLAGS += -Wl,--version-script=$(srcdir)/ub-lldpctl.map +else +libublldpctl_la_LDFLAGS += -export-symbols-regex '^ub-lldpctl_' +endif + +pkgconfig_DATA = ub-lldpctl.pc + +TEMPLATES = ub-lldpctl.pc +EXTRA_DIST = ub-lldpctl.pc.in ub-lldpctl.map +CLEANFILES += $(TEMPLATES) +ub-lldpctl.pc: ub-lldpctl.pc.in +include $(top_srcdir)/edit.am diff --git a/src/lib/atom.c b/src/lib/atom.c new file mode 100644 index 0000000000000000000000000000000000000000..6cfbcadd3923342bc7843be82be810ec1129e196 --- /dev/null +++ b/src/lib/atom.c @@ -0,0 +1,688 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2012 Vincent Bernat + * + * 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 +#include +#include +#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 ub-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 0000000000000000000000000000000000000000..8c26a512fc007abf65053fc97fc9347b4b58df4b --- /dev/null +++ b/src/lib/atom.h @@ -0,0 +1,226 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2012 Vincent Bernat + * + * 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 +#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 ub-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 + 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, + 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; +}; + +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() { 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() { atom_builder_register(& NAME, PRIO); } diff --git a/src/lib/atoms/chassis.c b/src/lib/atoms/chassis.c new file mode 100644 index 0000000000000000000000000000000000000000..96c79eeadd858a528fe0a7024831d12fe76e2da7 --- /dev/null +++ b/src/lib/atoms/chassis.c @@ -0,0 +1,190 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2015 Vincent Bernat + * + * 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 +#include +#include +#include + +#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}, +}; + +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; + } +} + +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; + + default: + SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST); + 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; + default: + return SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST); + } + 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, + .get_buffer = _lldpctl_atom_get_buf_chassis }; + +ATOM_BUILDER_REGISTER(chassis, 3); diff --git a/src/lib/atoms/config.c b/src/lib/atoms/config.c new file mode 100644 index 0000000000000000000000000000000000000000..215f5ec830b7b3a6afcdcdbef197e6d25634c326 --- /dev/null +++ b/src/lib/atoms/config.c @@ -0,0 +1,293 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2015 Vincent Bernat + * + * 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 +#include +#include +#include + +#include "../lldpctl.h" +#include "../../log.h" +#include "../atom.h" +#include "../helpers.h" + +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}, + }, +}; +ATOM_MAP_REGISTER(lldp_portid_map, 2); + +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_lldp_portid_type: + return map_lookup(lldp_portid_map.map, + c->config->c_lldp_portid_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_mgmt_advertise: + return c->config->c_mgmt_advertise; + 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_mgmt_advertise: + config.c_mgmt_advertise = c->config->c_mgmt_advertise = value; + break; + 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_lldp_portid_type: + config.c_lldp_portid_type = value; + c->config->c_lldp_portid_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 0000000000000000000000000000000000000000..457a047d738c727f7702f65250f7b242ea30efa0 --- /dev/null +++ b/src/lib/atoms/custom.c @@ -0,0 +1,242 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2015 Vincent Bernat + * Copyright (c) 2015 Alexandru Ardelean + * + * 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 +#include +#include +#include + +#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 0000000000000000000000000000000000000000..66f76f5f5bef45b44b0c53a4164f7a3be9da16df --- /dev/null +++ b/src/lib/atoms/dot1.c @@ -0,0 +1,275 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2015 Vincent Bernat + * + * 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 +#include +#include +#include + +#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 0000000000000000000000000000000000000000..b6b084601750564d19a3a928f6bd993272c298e0 --- /dev/null +++ b/src/lib/atoms/dot3.c @@ -0,0 +1,522 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2015 Vincent Bernat + * + * 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 +#include +#include +#include + +#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" }, + }, +}; + +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" }, + }, +}; + +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" }, + }, +}; + +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" }, + }, +}; + +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" }, + }, +}; + +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" }, + }, +}; + +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" }, + }, +}; + +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" }, + }, +}; + +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" }, + }, +}; + + +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; + /* 802.3bt additions */ + case lldpctl_k_dot3_power_pd_4pid: + port->p_power.pd_4pid = value; + return atom; + case lldpctl_k_dot3_power_requested_a: + port->p_power.requested_a = value / 100; + return atom; + case lldpctl_k_dot3_power_requested_b: + port->p_power.requested_b = value / 100; + return atom; + case lldpctl_k_dot3_power_allocated_a: + port->p_power.allocated_a = value / 100; + return atom; + case lldpctl_k_dot3_power_allocated_b: + port->p_power.allocated_b = value / 100; + return atom; + case lldpctl_k_dot3_power_pse_status: + port->p_power.pse_status = value; + return atom; + case lldpctl_k_dot3_power_pd_status: + port->p_power.pd_status = value; + return atom; + case lldpctl_k_dot3_power_pse_pairs_ext: + port->p_power.pse_pairs_ext = value; + return atom; + case lldpctl_k_dot3_power_class_a: + port->p_power.class_a = value; + return atom; + case lldpctl_k_dot3_power_class_b: + port->p_power.class_b = value; + return atom; + case lldpctl_k_dot3_power_class_ext: + port->p_power.class_ext = value; + return atom; + case lldpctl_k_dot3_power_type_ext: + port->p_power.type_ext = value; + return atom; + case lldpctl_k_dot3_power_pd_load: + port->p_power.pd_load = value; + return atom; + case lldpctl_k_dot3_power_pse_max: + port->p_power.pse_max = 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 0000000000000000000000000000000000000000..54a040d89e877e568a334a87ab45aa527a34bd32 --- /dev/null +++ b/src/lib/atoms/interface.c @@ -0,0 +1,122 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2015 Vincent Bernat + * + * 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 +#include +#include +#include + +#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 0000000000000000000000000000000000000000..1ebafc5bc009784ff8911091529f102abcef3bad --- /dev/null +++ b/src/lib/atoms/med.c @@ -0,0 +1,1125 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2015 Vincent Bernat + * + * 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 +#include +#include +#include + +#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 0000000000000000000000000000000000000000..3d3ec9b8c7afbe0b478bc98bdd65a234873d8c82 --- /dev/null +++ b/src/lib/atoms/mgmt.c @@ -0,0 +1,159 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2015 Vincent Bernat + * + * 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 +#include +#include +#include + +#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 0000000000000000000000000000000000000000..ef7e11a4ba2d37a54ed7cc31ef3e600a33ecc3cd --- /dev/null +++ b/src/lib/atoms/port.c @@ -0,0 +1,482 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2015 Vincent Bernat + * + * 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 +#include +#include +#include + +#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" }, + { 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); + +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; + 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; + + /* Local and default port only */ + if (!p->local) { + SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST); + return NULL; + } + + 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; + default: + SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST); + return NULL; + } + + set.ifname = hardware ? hardware->h_ifname : ""; + + 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; + + 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; + 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; + 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 0000000000000000000000000000000000000000..8ab63d48c5fd8468cfd74a26607f657ab3d04135 --- /dev/null +++ b/src/lib/connection.c @@ -0,0 +1,307 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2012 Vincent Bernat + * + * 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 +#include +#include +#include +#include + +#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 ub-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 = NULL; + ssize_t rc; + + if ((buffer = malloc(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 0000000000000000000000000000000000000000..525e0af3e6aa93c702cca1309fe59c02dccc338c --- /dev/null +++ b/src/lib/errors.c @@ -0,0 +1,61 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2012 Vincent Bernat + * + * 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 ub-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 0000000000000000000000000000000000000000..e5184e7b2074043ec06463031679f36aa357ffe7 --- /dev/null +++ b/src/lib/fixedpoint.c @@ -0,0 +1,258 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2013 Vincent Bernat + * + * 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 +#include +#include +#include +#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 0000000000000000000000000000000000000000..cc07643978df2fcdb64fea98b29577c10c3a0ad9 --- /dev/null +++ b/src/lib/fixedpoint.h @@ -0,0 +1,42 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2012 Vincent Bernat + * + * 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 +#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 0000000000000000000000000000000000000000..f7ea51d0b479a9c047f9235da834a902e2e81c64 --- /dev/null +++ b/src/lib/helpers.c @@ -0,0 +1,73 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2015 Vincent Bernat + * + * 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 +#include +#include +#include + +#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); +} + diff --git a/src/lib/helpers.h b/src/lib/helpers.h new file mode 100644 index 0000000000000000000000000000000000000000..d1b9415a74d6c0e7924bc4d56cd454f5c9713a4c --- /dev/null +++ b/src/lib/helpers.h @@ -0,0 +1,23 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2015 Vincent Bernat + * + * 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); + diff --git a/src/lib/lldpctl.h b/src/lib/lldpctl.h new file mode 100644 index 0000000000000000000000000000000000000000..27588f924d3c48509b4a258b57aceb60a728b172 --- /dev/null +++ b/src/lib/lldpctl.h @@ -0,0 +1,1033 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2012 Vincent Bernat + * + * 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 ub-lldpd + * + * `liblldpctl` allows any program to convenienty query and modify the behaviour + * of a running ub-lldpd daemon. + * + * To use this library, use `pkg-config` to get the appropriate options: + * * `pkg-config --libs ub-lldpctl` for `LIBS` or `LDFLAGS` + * * `pkg-config --cflags ub-lldpctl` for `CFLAGS` + * + * @warning This library is tightly coupled with ub-lldpd. The library to use + * should be the one shipped with ub-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 ub-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 ub-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 +#include +#include + +/** + * @defgroup lldpctl_connection Managing connection to ub-lldpd + * + * Connection with ub-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 ub-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 ub-lldpd. + * + * @param conn Handle to the connection to ub-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 ub-lldpd. + * + * @param conn Handle to the connection to ub-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 ub-lldpd. + * + * This function should be invoked in case of asynchronous IO when new data is + * available from ub-lldpd (expected or unexpected). + * + * @param conn Handle to the connection to ub-lldpd. + * @param data Data received from ub-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 ub-lldpd. + * + * This function should be invoked in case of asynchronous IO when new data can + * be written to ub-lldpd. + * + * @param conn Handle to the connection to ub-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 ub-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 ub-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 ub-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 ub-lldpd. + * + * @param ctlname the Unix-domain socket to connect to ub-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 ub-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 ub-lldpd. + * + * @param conn Previously allocated handler to a connection to ub-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 ub-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 ub-lldpd. + * + * @param conn Previously allocated handler to a connection to ub-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 ub-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 ub-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 ub-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; + char *string; +} lldpctl_map_t; + +/** + * Return the reference to connection with ub-lldpd. + * + * @param atom The atom we want reference from. + * @return The reference to the connection to ub-lldpd. + * + * Each atom contains an internal reference to the corresponding connection to + * ub-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 ub-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 ub-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 ub-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 ub-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 ub-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 ub-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 ub-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 ub-lldpd daemon. + * + * @param conn Connection with ub-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 ub-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 ub-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 ub-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_paused, /**< `(I,WO)` lldpd is paused */ + 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_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_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 capabalities (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_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_lldp_portid_type, /**< `(I,WO)` LLDP PortID TLV Subtype */ + lldpctl_k_config_max_neighbors, /**< `(I,WO)`Maximum number of neighbors per port. */ +} 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 ub-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/ub-lldpctl.map b/src/lib/ub-lldpctl.map new file mode 100644 index 0000000000000000000000000000000000000000..c602b641aff1ee22f7a4c1b21d093c1b20ce4a75 --- /dev/null +++ b/src/lib/ub-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/ub-lldpctl.pc.in b/src/lib/ub-lldpctl.pc.in new file mode 100644 index 0000000000000000000000000000000000000000..7e51f2282f531bd2369f63a8f1e115394ee0f0ec --- /dev/null +++ b/src/lib/ub-lldpctl.pc.in @@ -0,0 +1,6 @@ +Name: ub-lldpctl +Description: Library to interface with ub-lldpd +Version: @VERSION@ +URL: @PACKAGE_URL@ +Libs: -L@libdir@ -lub-lldpctl +Cflags: -I@includedir@ diff --git a/src/lldp-const.h b/src/lldp-const.h new file mode 100644 index 0000000000000000000000000000000000000000..af33c82a874b050273a5ad72aba9c3e3d225e092 --- /dev/null +++ b/src/lldp-const.h @@ -0,0 +1,66 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2008 Vincent Bernat + * + * 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 + +/* 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 + +/* 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 LLDPD_MODE_LLDP 1 +#define LLDPD_MODE_MAX LLDPD_MODE_LLDP + +#endif /* _LLDP_H */ diff --git a/src/lldpd-structs.c b/src/lldpd-structs.c new file mode 100644 index 0000000000000000000000000000000000000000..24fe588807835903864799af6c49f31980358cd7 --- /dev/null +++ b/src/lldpd-structs.c @@ -0,0 +1,128 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2008 Vincent Bernat + * + * 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 +#include +#include +#include "log.h" +#include "lldpd-structs.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)"); + free(chassis->c_id); + free(chassis->c_name); + free(chassis->c_descr); + if (all) + free(chassis); +} + +/* 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) +{ + /* 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; + } + } +} + +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 0000000000000000000000000000000000000000..8de83c803f1b4cd20781b2bbea663a1a80e559a2 --- /dev/null +++ b/src/lldpd-structs.h @@ -0,0 +1,325 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2008 Vincent Bernat + * + * 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 +#endif + +#include +#include + +/* 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 +#else +# include +#endif + +#include +#include +#include + +#include "compat/compat.h" +#include "marshal.h" +#include "lldp-const.h" + +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; +}; +/* 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) +MARSHAL_END(lldpd_chassis); + +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 */ + +}; +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) +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; +}; +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) +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, consider 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_mgmt_advertise; /* Management addresses advertisement */ + + int c_tx_hold; /* Transmit hold */ + int c_lldp_portid_type; /* The PortID 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 */ + 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 */ + 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; + + struct lldpd_port h_lport; /* Port attached to this hardware port */ + TAILQ_HEAD(, lldpd_port) h_rports; /* Remote ports */ +}; +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 *); + +#endif diff --git a/src/log.c b/src/log.c new file mode 100644 index 0000000000000000000000000000000000000000..92787145f941310402201d0164bf4102ad0041cb --- /dev/null +++ b/src/log.c @@ -0,0 +1,297 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* $OpenBSD: log.c,v 1.11 2007/12/07 17:17:00 reyk Exp $ */ + +/* + * Copyright (c) 2023-2023 Hisilicon Limited. + * Copyright (c) 2003, 2004 Henning Brauer + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include + +/* By default, logging is done on stderr. */ +static int use_syslog = 0; +/* Default debug level */ +static int debug = 0; +/* Default dfx level */ +static int dfx = 0; +#define LOG_DFX 8 + +/* 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"; + case LOG_DFX: + return "\033[32m[ DFX"; + } + 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"; + case LOG_DFX: + return "[ DFX"; + } + } + return "[UNKN]"; +} + +static void +vlog(int pri, const char *token, const char *fmt, va_list ap) +{ + if (logh) { + char *result; + 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; + 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 +log_dfx_enable(void) +{ + dfx++; +} + +void +log_dfx(const char *token, const char *emsg, ...) +{ + va_list ap; + + if ((dfx > 0 && log_debug_accept_token(token)) || logh) { + va_start(ap, emsg); + vlog(LOG_DFX, 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 0000000000000000000000000000000000000000..298da73a8b39096cd6ff905a3714e0f0d2574c8d --- /dev/null +++ b/src/log.h @@ -0,0 +1,42 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2023-2023 Hisilicon Limited. + * Copyright (c) 2012 Vincent Bernat + * + * 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 + +/* 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 log_dfx(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); +void log_dfx_enable(void); + +/* 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 0000000000000000000000000000000000000000..0cde547c401266289fdee6f0a56bf645fe6ca8c8 --- /dev/null +++ b/src/marshal.c @@ -0,0 +1,361 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2012 Vincent Bernat + * + * 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 +#include +#include +#include +#include + +#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 0000000000000000000000000000000000000000..0a782d53e26bea7cf2971e5bce19c644340a4112 --- /dev/null +++ b/src/marshal.h @@ -0,0 +1,155 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2012 Vincent Bernat + * + * 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 +#include +#include + +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 { + 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) { \ + return marshal_serialize(type, \ + source, buffer); \ + } \ + 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 0000000000000000000000000000000000000000..92996d428b571596065d1a043e03a081ce711426 --- /dev/null +++ b/src/version.c @@ -0,0 +1,96 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2016 Vincent Bernat + * + * 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 +#endif + +#include +#include "compat/compat.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 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, "ub-lldpd")) { +#ifdef HOST_OS_LINUX + fprintf(destination, + " (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, "ub-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); + +} diff --git a/tests/Makefile.am b/tests/Makefile.am new file mode 100644 index 0000000000000000000000000000000000000000..bc990b78c0183982921cf54c517692ba3b76faf3 --- /dev/null +++ b/tests/Makefile.am @@ -0,0 +1,44 @@ +AM_CFLAGS = -I $(top_srcdir)/include $(LLDP_CFLAGS) +AM_CPPFLAGS = $(LLDP_CPPFLAGS) +AM_LDFLAGS = $(LLDP_LDFLAGS) $(LLDP_BIN_LDFLAGS) + +check_PROGRAMS = decode + +decode_SOURCES = decode.c \ + $(top_srcdir)/src/daemon/ub-lldpd.h \ + pcap-hdr.h + +LDADD = $(top_builddir)/src/daemon/libublldpd.la @check_LIBS@ @libevent_LDFLAGS@ +if ENABLE_SYSTEMTAP +LDADD += $(top_builddir)/src/daemon/probes.o +endif + +if HAVE_CHECK + +TESTS = check_marshal check_pattern check_bitmap check_fixedpoint \ + check_lldp +AM_CFLAGS += @check_CFLAGS@ -Wno-format-extra-args +LDADD += @check_LIBS@ + +check_marshal_SOURCES = check_marshal.c \ + $(top_srcdir)/src/marshal.h \ + check-compat.h + +check_pattern_SOURCES = check_pattern.c \ + $(top_srcdir)/src/daemon/ub-lldpd.h + +check_bitmap_SOURCES = check_bitmap.c \ + $(top_srcdir)/src/daemon/ub-lldpd.h + +check_lldp_SOURCES = check_lldp.c \ + $(top_srcdir)/src/daemon/ub-lldpd.h \ + common.h common.c check-compat.h pcap-hdr.h + +check_fixedpoint_SOURCES = check_fixedpoint.c +check_fixedpoint_LDADD = $(top_builddir)/src/lib/libfixedpoint.la $(LDADD) + +check_PROGRAMS += $(TESTS) + +endif + +MOSTLYCLEANFILES = *.pcap diff --git a/tests/check-compat.h b/tests/check-compat.h new file mode 100644 index 0000000000000000000000000000000000000000..47abe6fdb1707eeb047580a880aec4122ef41b5c --- /dev/null +++ b/tests/check-compat.h @@ -0,0 +1,14 @@ +#ifndef _CHECK_COMPAT_H +#define _CHECK_COMPAT_H + +#if (CHECK_MAJOR_VERSION == 0 && (CHECK_MINOR_VERSION < 9 || (CHECK_MINOR_VERSION == 9 && CHECK_MICRO_VERSION < 10))) +# define ck_assert_ptr_eq(X,Y) do { \ + void* _ck_x = (X); \ + void* _ck_y = (Y); \ + ck_assert_msg(_ck_x == _ck_y, \ + "Assertion '"#X"=="#Y"' failed: "#X"==%p, "#Y"==%p", \ + _ck_x, _ck_y); \ + } while (0) +#endif + +#endif diff --git a/tests/check_bitmap.c b/tests/check_bitmap.c new file mode 100644 index 0000000000000000000000000000000000000000..7123a15645e9425c951e860d2f1f591ff0b89a4c --- /dev/null +++ b/tests/check_bitmap.c @@ -0,0 +1,74 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2020 Vincent Bernat + * + * 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 + +#include "../src/daemon/ub-lldpd.h" + +START_TEST(test_empty) { + uint32_t vlan_bmap[VLAN_BITMAP_LEN] = {}; + ck_assert(bitmap_isempty(vlan_bmap)); + ck_assert_int_eq(bitmap_numbits(vlan_bmap), 0); +} +END_TEST + +START_TEST(test_first_bit) { + uint32_t vlan_bmap[VLAN_BITMAP_LEN] = {}; + bitmap_set(vlan_bmap, 1); + ck_assert_int_eq(vlan_bmap[0], 2); + ck_assert_int_eq(bitmap_numbits(vlan_bmap), 1); +} +END_TEST + +START_TEST(test_some_bits) { + uint32_t vlan_bmap[VLAN_BITMAP_LEN] = {}; + bitmap_set(vlan_bmap, 1); + bitmap_set(vlan_bmap, 6); + bitmap_set(vlan_bmap, 31); + bitmap_set(vlan_bmap, 50); + ck_assert_int_eq(vlan_bmap[0], (1UL << 1) | (1UL << 6) | (1UL << 31)); + ck_assert_int_eq(vlan_bmap[1], (1UL << (50-32))); + ck_assert_int_eq(vlan_bmap[2], 0); + ck_assert_int_eq(bitmap_numbits(vlan_bmap), 4); +} +END_TEST + +Suite * +bitmap_suite(void) +{ + Suite *s = suite_create("Bitmap handling"); + + TCase *tc_bitmap = tcase_create("Bitmap handling"); + tcase_add_test(tc_bitmap, test_empty); + tcase_add_test(tc_bitmap, test_first_bit); + tcase_add_test(tc_bitmap, test_some_bits); + suite_add_tcase(s, tc_bitmap); + + return s; +} + +int +main() +{ + int number_failed; + Suite *s = bitmap_suite(); + SRunner *sr = srunner_create(s); + srunner_run_all(sr, CK_ENV); + number_failed = srunner_ntests_failed(sr); + srunner_free(sr); + return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/tests/check_cdp.c b/tests/check_cdp.c new file mode 100644 index 0000000000000000000000000000000000000000..dddd063cbae9fa531c766ca8373e357a280cd7b0 --- /dev/null +++ b/tests/check_cdp.c @@ -0,0 +1,611 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2015 Vincent Bernat + * + * 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 +#include +#include +#include +#include +#include "common.h" + +char filenameprefix[] = "cdp_send"; + +#ifdef ENABLE_CDP + +START_TEST (test_send_cdpv1) +{ + int n; + /* Packet we should build: +IEEE 802.3 Ethernet + Destination: CDP/VTP/DTP/PAgP/UDLD (01:00:0c:cc:cc:cc) + Source: 5e:10:8e:e7:84:ad (5e:10:8e:e7:84:ad) + Length: 106 +Logical-Link Control + DSAP: SNAP (0xaa) + IG Bit: Individual + SSAP: SNAP (0xaa) + CR Bit: Command + Control field: U, func=UI (0x03) + 000. 00.. = Command: Unnumbered Information (0x00) + .... ..11 = Frame type: Unnumbered frame (0x03) + Organization Code: Cisco (0x00000c) + PID: CDP (0x2000) +Cisco Discovery Protocol + Version: 1 + TTL: 180 seconds + Checksum: 0x3af7 [correct] + [Good: True] + [Bad : False] + Device ID: First chassis + Type: Device ID (0x0001) + Length: 17 + Device ID: First chassis + Addresses + Type: Addresses (0x0002) + Length: 17 + Number of addresses: 1 + IP address: 172.17.142.37 + Protocol type: NLPID + Protocol length: 1 + Protocol: IP + Address length: 4 + IP address: 172.17.142.37 + Port ID: FastEthernet 1/5 + Type: Port ID (0x0003) + Length: 20 + Sent through Interface: FastEthernet 1/5 + Capabilities + Type: Capabilities (0x0004) + Length: 8 + Capabilities: 0x00000011 + .... .... .... .... .... .... .... ...1 = Is a Router + .... .... .... .... .... .... .... ..0. = Not a Transparent Bridge + .... .... .... .... .... .... .... .0.. = Not a Source Route Bridge + .... .... .... .... .... .... .... 0... = Not a Switch + .... .... .... .... .... .... ...1 .... = Is a Host + .... .... .... .... .... .... ..0. .... = Not IGMP capable + .... .... .... .... .... .... .0.. .... = Not a Repeater + Software Version + Type: Software version (0x0005) + Length: 23 + Software Version: Chassis description + Platform: Linux + Type: Platform (0x0006) + Length: 9 + Platform: Linux + */ + char pkt1[] = { + 0x01, 0x00, 0x0c, 0xcc, 0xcc, 0xcc, 0x5e, 0x10, + 0x8e, 0xe7, 0x84, 0xad, 0x00, 0x6a, 0xaa, 0xaa, + 0x03, 0x00, 0x00, 0x0c, 0x20, 0x00, 0x01, 0xb4, + 0x3a, 0xf7, 0x00, 0x01, 0x00, 0x11, 0x46, 0x69, + 0x72, 0x73, 0x74, 0x20, 0x63, 0x68, 0x61, 0x73, + 0x73, 0x69, 0x73, 0x00, 0x02, 0x00, 0x11, 0x00, + 0x00, 0x00, 0x01, 0x01, 0x01, 0xcc, 0x00, 0x04, + 0xac, 0x11, 0x8e, 0x25, 0x00, 0x03, 0x00, 0x14, + 0x46, 0x61, 0x73, 0x74, 0x45, 0x74, 0x68, 0x65, + 0x72, 0x6e, 0x65, 0x74, 0x20, 0x31, 0x2f, 0x35, + 0x00, 0x04, 0x00, 0x08, 0x00, 0x00, 0x00, 0x11, + 0x00, 0x05, 0x00, 0x17, 0x43, 0x68, 0x61, 0x73, + 0x73, 0x69, 0x73, 0x20, 0x64, 0x65, 0x73, 0x63, + 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x00, + 0x06, 0x00, 0x09, 0x4c, 0x69, 0x6e, 0x75, 0x78 }; + struct packet *pkt; + in_addr_t addr; + struct lldpd_mgmt *mgmt; + struct lldpd cfg = { + .g_config = { + .c_ttl = 180, + .c_platform = "Linux" + } + }; + + /* Populate port and chassis */ + hardware.h_lport.p_id_subtype = LLDP_PORTID_SUBTYPE_IFNAME; + hardware.h_lport.p_id = "Not used"; + hardware.h_lport.p_id_len = strlen(hardware.h_lport.p_id); + hardware.h_lport.p_descr = "FastEthernet 1/5"; + chassis.c_id_subtype = LLDP_CHASSISID_SUBTYPE_LLADDR; + chassis.c_id = macaddress; + chassis.c_id_len = ETHER_ADDR_LEN; + chassis.c_name = "First chassis"; + chassis.c_descr = "Chassis description"; + chassis.c_cap_available = chassis.c_cap_enabled = LLDP_CAP_ROUTER; + TAILQ_INIT(&chassis.c_mgmt); + addr = inet_addr("172.17.142.37"); + mgmt = lldpd_alloc_mgmt(LLDPD_AF_IPV4, + &addr, sizeof(in_addr_t), 0); + if (mgmt == NULL) + ck_abort(); + TAILQ_INSERT_TAIL(&chassis.c_mgmt, mgmt, m_entries); + + /* Build packet */ + n = cdpv1_send(&cfg, &hardware); + if (n != 0) { + fail("unable to build packet"); + return; + } + if (TAILQ_EMPTY(&pkts)) { + fail("no packets sent"); + return; + } + pkt = TAILQ_FIRST(&pkts); + ck_assert_int_eq(pkt->size, sizeof(pkt1)); + fail_unless(memcmp(pkt->data, pkt1, sizeof(pkt1)) == 0); + fail_unless(TAILQ_NEXT(pkt, next) == NULL, "more than one packet sent"); +} +END_TEST + +START_TEST (test_send_cdpv2) +{ + int n; + /* Packet we should build: +IEEE 802.3 Ethernet + Destination: CDP/VTP/DTP/PAgP/UDLD (01:00:0c:cc:cc:cc) + Source: 5e:10:8e:e7:84:ad (5e:10:8e:e7:84:ad) + the factory default) + Length: 111 +Logical-Link Control + DSAP: SNAP (0xaa) + IG Bit: Individual + SSAP: SNAP (0xaa) + CR Bit: Command + Control field: U, func=UI (0x03) + 000. 00.. = Command: Unnumbered Information (0x00) + .... ..11 = Frame type: Unnumbered frame (0x03) + Organization Code: Cisco (0x00000c) + PID: CDP (0x2000) +Cisco Discovery Protocol + Version: 2 + TTL: 180 seconds + Checksum: 0x5926 [correct] + [Good: True] + [Bad : False] + Device ID: Second chassis + Type: Device ID (0x0001) + Length: 18 + Device ID: Second chassis + Addresses + Type: Addresses (0x0002) + Length: 26 + Number of addresses: 2 + IP address: 172.17.142.36 + Protocol type: NLPID + Protocol length: 1 + Protocol: IP + Address length: 4 + IP address: 172.17.142.36 + IP address: 172.17.142.38 + Protocol type: NLPID + Protocol length: 1 + Protocol: IP + Address length: 4 + IP address: 172.17.142.38 + Port ID: Gigabit Ethernet 5/8 + Type: Port ID (0x0003) + Length: 24 + Sent through Interface: Gigabit Ethernet 5/8 + Capabilities + Type: Capabilities (0x0004) + Length: 8 + Capabilities: 0x00000019 + .... .... .... .... .... .... .... ...1 = Is a Router + .... .... .... .... .... .... .... ..0. = Not a Transparent Bridge + .... .... .... .... .... .... .... .0.. = Not a Source Route Bridge + .... .... .... .... .... .... .... 1... = Is a Switch + .... .... .... .... .... .... ...1 .... = Is a Host + .... .... .... .... .... .... ..0. .... = Not IGMP capable + .... .... .... .... .... .... .0.. .... = Not a Repeater + Software Version + Type: Software version (0x0005) + Length: 23 + Software Version: Chassis description + Platform: Linux + Type: Platform (0x0006) + Length: 9 + Platform: Linux + */ + char pkt1[] = { + 0x01, 0x00, 0x0c, 0xcc, 0xcc, 0xcc, 0x5e, 0x10, + 0x8e, 0xe7, 0x84, 0xad, 0x00, 0x78, 0xaa, 0xaa, + 0x03, 0x00, 0x00, 0x0c, 0x20, 0x00, 0x02, 0xb4, + 0xc8, 0x67, 0x00, 0x01, 0x00, 0x12, 0x53, 0x65, + 0x63, 0x6f, 0x6e, 0x64, 0x20, 0x63, 0x68, 0x61, + 0x73, 0x73, 0x69, 0x73, 0x00, 0x02, 0x00, 0x1a, + 0x00, 0x00, 0x00, 0x02, 0x01, 0x01, 0xcc, 0x00, + 0x04, 0xac, 0x11, 0x8e, 0x24, 0x01, 0x01, 0xcc, + 0x00, 0x04, 0xac, 0x11, 0x8e, 0x26, 0x00, 0x03, + 0x00, 0x18, 0x47, 0x69, 0x67, 0x61, 0x62, 0x69, + 0x74, 0x20, 0x45, 0x74, 0x68, 0x65, 0x72, 0x6e, + 0x65, 0x74, 0x20, 0x35, 0x2f, 0x38, 0x00, 0x04, + 0x00, 0x08, 0x00, 0x00, 0x00, 0x19, 0x00, 0x05, + 0x00, 0x17, 0x43, 0x68, 0x61, 0x73, 0x73, 0x69, + 0x73, 0x20, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, + 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x06, 0x00, + 0x09, 0x4c, 0x69, 0x6e, 0x75, 0x78 }; + struct packet *pkt; + in_addr_t addr1; + in_addr_t addr2; + struct lldpd_mgmt *mgmt1; + struct lldpd_mgmt *mgmt2; + struct lldpd cfg = { + .g_config = { + .c_ttl = 180, + .c_platform = "Linux" + } + }; + + /* Populate port and chassis */ + hardware.h_lport.p_id_subtype = LLDP_PORTID_SUBTYPE_LLADDR; + hardware.h_lport.p_id = macaddress; + hardware.h_lport.p_id_len = ETHER_ADDR_LEN; + hardware.h_lport.p_descr = "Gigabit Ethernet 5/8"; + chassis.c_id_subtype = LLDP_CHASSISID_SUBTYPE_LLADDR; + chassis.c_id = macaddress; + chassis.c_id_len = ETHER_ADDR_LEN; + chassis.c_name = "Second chassis"; + chassis.c_descr = "Chassis description"; + chassis.c_cap_available = chassis.c_cap_enabled = + LLDP_CAP_ROUTER | LLDP_CAP_BRIDGE; + TAILQ_INIT(&chassis.c_mgmt); + addr1 = inet_addr("172.17.142.36"); + addr2 = inet_addr("172.17.142.38"); + mgmt1 = lldpd_alloc_mgmt(LLDPD_AF_IPV4, + &addr1, sizeof(in_addr_t), 0); + mgmt2 = lldpd_alloc_mgmt(LLDPD_AF_IPV4, + &addr2, sizeof(in_addr_t), 0); + if (mgmt1 == NULL || mgmt2 == NULL) + ck_abort(); + TAILQ_INSERT_TAIL(&chassis.c_mgmt, mgmt1, m_entries); + TAILQ_INSERT_TAIL(&chassis.c_mgmt, mgmt2, m_entries); + + /* Build packet */ + n = cdpv2_send(&cfg, &hardware); + if (n != 0) { + fail("unable to build packet"); + return; + } + if (TAILQ_EMPTY(&pkts)) { + fail("no packets sent"); + return; + } + pkt = TAILQ_FIRST(&pkts); + ck_assert_int_eq(pkt->size, sizeof(pkt1)); + fail_unless(memcmp(pkt->data, pkt1, sizeof(pkt1)) == 0); + fail_unless(TAILQ_NEXT(pkt, next) == NULL, "more than one packet sent"); +} +END_TEST + +START_TEST (test_recv_cdpv1) +{ + char pkt1[] = { + 0x01, 0x00, 0x0c, 0xcc, 0xcc, 0xcc, 0x00, 0xe0, + 0x1e, 0xd5, 0xd5, 0x15, 0x01, 0x1e, 0xaa, 0xaa, + 0x03, 0x00, 0x00, 0x0c, 0x20, 0x00, 0x01, 0xb4, + 0xdf, 0xf0, 0x00, 0x01, 0x00, 0x06, 0x52, 0x31, + 0x00, 0x02, 0x00, 0x11, 0x00, 0x00, 0x00, 0x01, + 0x01, 0x01, 0xcc, 0x00, 0x04, 0xc0, 0xa8, 0x0a, + 0x01, 0x00, 0x03, 0x00, 0x0d, 0x45, 0x74, 0x68, + 0x65, 0x72, 0x6e, 0x65, 0x74, 0x30, 0x00, 0x04, + 0x00, 0x08, 0x00, 0x00, 0x00, 0x11, 0x00, 0x05, + 0x00, 0xd8, 0x43, 0x69, 0x73, 0x63, 0x6f, 0x20, + 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, + 0x77, 0x6f, 0x72, 0x6b, 0x20, 0x4f, 0x70, 0x65, + 0x72, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x20, 0x53, + 0x79, 0x73, 0x74, 0x65, 0x6d, 0x20, 0x53, 0x6f, + 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x20, 0x0a, + 0x49, 0x4f, 0x53, 0x20, 0x28, 0x74, 0x6d, 0x29, + 0x20, 0x31, 0x36, 0x30, 0x30, 0x20, 0x53, 0x6f, + 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x20, 0x28, + 0x43, 0x31, 0x36, 0x30, 0x30, 0x2d, 0x4e, 0x59, + 0x2d, 0x4c, 0x29, 0x2c, 0x20, 0x56, 0x65, 0x72, + 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x31, 0x31, 0x2e, + 0x32, 0x28, 0x31, 0x32, 0x29, 0x50, 0x2c, 0x20, + 0x52, 0x45, 0x4c, 0x45, 0x41, 0x53, 0x45, 0x20, + 0x53, 0x4f, 0x46, 0x54, 0x57, 0x41, 0x52, 0x45, + 0x20, 0x28, 0x66, 0x63, 0x31, 0x29, 0x0a, 0x43, + 0x6f, 0x70, 0x79, 0x72, 0x69, 0x67, 0x68, 0x74, + 0x20, 0x28, 0x63, 0x29, 0x20, 0x31, 0x39, 0x38, + 0x36, 0x2d, 0x31, 0x39, 0x39, 0x38, 0x20, 0x62, + 0x79, 0x20, 0x63, 0x69, 0x73, 0x63, 0x6f, 0x20, + 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x73, 0x2c, + 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x0a, 0x43, 0x6f, + 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x64, 0x20, 0x54, + 0x75, 0x65, 0x20, 0x30, 0x33, 0x2d, 0x4d, 0x61, + 0x72, 0x2d, 0x39, 0x38, 0x20, 0x30, 0x36, 0x3a, + 0x33, 0x33, 0x20, 0x62, 0x79, 0x20, 0x64, 0x73, + 0x63, 0x68, 0x77, 0x61, 0x72, 0x74, 0x00, 0x06, + 0x00, 0x0e, 0x63, 0x69, 0x73, 0x63, 0x6f, 0x20, + 0x31, 0x36, 0x30, 0x31 }; + /* This is: +IEEE 802.3 Ethernet + Destination: CDP/VTP/DTP/PAgP/UDLD (01:00:0c:cc:cc:cc) + Source: Cisco_d5:d5:15 (00:e0:1e:d5:d5:15) + Length: 286 +Logical-Link Control + DSAP: SNAP (0xaa) + IG Bit: Individual + SSAP: SNAP (0xaa) + CR Bit: Command + Control field: U, func=UI (0x03) + 000. 00.. = Command: Unnumbered Information (0x00) + .... ..11 = Frame type: Unnumbered frame (0x03) + Organization Code: Cisco (0x00000c) + PID: CDP (0x2000) +Cisco Discovery Protocol + Version: 1 + TTL: 180 seconds + Checksum: 0xdff0 [correct] + [Good: True] + [Bad : False] + Device ID: R1 + Type: Device ID (0x0001) + Length: 6 + Device ID: R1 + Addresses + Type: Addresses (0x0002) + Length: 17 + Number of addresses: 1 + IP address: 192.168.10.1 + Protocol type: NLPID + Protocol length: 1 + Protocol: IP + Address length: 4 + IP address: 192.168.10.1 + Port ID: Ethernet0 + Type: Port ID (0x0003) + Length: 13 + Sent through Interface: Ethernet0 + Capabilities + Type: Capabilities (0x0004) + Length: 8 + Capabilities: 0x00000011 + .... .... .... .... .... .... .... ...1 = Is a Router + .... .... .... .... .... .... .... ..0. = Not a Transparent Bridge + .... .... .... .... .... .... .... .0.. = Not a Source Route Bridge + .... .... .... .... .... .... .... 0... = Not a Switch + .... .... .... .... .... .... ...1 .... = Is a Host + .... .... .... .... .... .... ..0. .... = Not IGMP capable + .... .... .... .... .... .... .0.. .... = Not a Repeater + Software Version + Type: Software version (0x0005) + Length: 216 + Software Version: Cisco Internetwork Operating System Software + IOS (tm) 1600 Software (C1600-NY-L), Version 11.2(12)P, RELEASE SOFTWARE (fc1) + Copyright (c) 1986-1998 by cisco Systems, Inc. + Compiled Tue 03-Mar-98 06:33 by dschwart + Platform: cisco 1601 + Type: Platform (0x0006) + Length: 14 + Platform: cisco 1601 + */ + struct lldpd_chassis *nchassis = NULL; + struct lldpd_port *nport = NULL; + + fail_unless(cdpv1_guess(pkt1, sizeof(pkt1))); + fail_unless(cdp_decode(NULL, pkt1, sizeof(pkt1), &hardware, + &nchassis, &nport) != -1); + if (!nchassis || !nport) { + fail("unable to decode packet"); + return; + } + ck_assert_int_eq(nchassis->c_id_subtype, + LLDP_CHASSISID_SUBTYPE_LOCAL); + ck_assert_int_eq(nchassis->c_id_len, 2); + fail_unless(memcmp(nchassis->c_id, "R1", 2) == 0); + ck_assert_str_eq(nchassis->c_name, "R1"); + ck_assert_int_eq(TAILQ_FIRST(&nchassis->c_mgmt)->m_addr.inet.s_addr, + (u_int32_t)inet_addr("192.168.10.1")); + ck_assert_int_eq(TAILQ_FIRST(&nchassis->c_mgmt)->m_iface, 0); + ck_assert_int_eq(nport->p_id_subtype, + LLDP_PORTID_SUBTYPE_IFNAME); + ck_assert_int_eq(nport->p_id_len, strlen("Ethernet0")); + fail_unless(memcmp(nport->p_id, + "Ethernet0", strlen("Ethernet0")) == 0); + ck_assert_str_eq(nport->p_descr, "Ethernet0"); + ck_assert_int_eq(nchassis->c_cap_enabled, LLDP_CAP_ROUTER); + ck_assert_str_eq(nchassis->c_descr, + "cisco 1601 running on\n" + "Cisco Internetwork Operating System Software \n" + "IOS (tm) 1600 Software (C1600-NY-L), Version 11.2(12)P, RELEASE SOFTWARE (fc1)\n" + "Copyright (c) 1986-1998 by cisco Systems, Inc.\n" + "Compiled Tue 03-Mar-98 06:33 by dschwart"); +} +END_TEST + +START_TEST (test_recv_cdpv2) +{ + char pkt1[] = { + 0x01, 0x00, 0x0c, 0xcc, 0xcc, 0xcc, 0xca, 0x00, + 0x68, 0x46, 0x00, 0x00, 0x01, 0x30, 0xaa, 0xaa, + 0x03, 0x00, 0x00, 0x0c, 0x20, 0x00, 0x02, 0xb4, + 0x54, 0x27, 0x00, 0x01, 0x00, 0x0f, 0x72, 0x74, + 0x62, 0x67, 0x36, 0x74, 0x65, 0x73, 0x74, 0x30, + 0x31, 0x00, 0x05, 0x00, 0xd3, 0x43, 0x69, 0x73, + 0x63, 0x6f, 0x20, 0x49, 0x6e, 0x74, 0x65, 0x72, + 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x20, + 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6e, + 0x67, 0x20, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, + 0x20, 0x53, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, + 0x65, 0x20, 0x0a, 0x49, 0x4f, 0x53, 0x20, 0x28, + 0x74, 0x6d, 0x29, 0x20, 0x37, 0x32, 0x30, 0x30, + 0x20, 0x53, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, + 0x65, 0x20, 0x28, 0x43, 0x37, 0x32, 0x30, 0x30, + 0x2d, 0x50, 0x2d, 0x4d, 0x29, 0x2c, 0x20, 0x56, + 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x31, + 0x32, 0x2e, 0x32, 0x28, 0x34, 0x36, 0x29, 0x2c, + 0x20, 0x52, 0x45, 0x4c, 0x45, 0x41, 0x53, 0x45, + 0x20, 0x53, 0x4f, 0x46, 0x54, 0x57, 0x41, 0x52, + 0x45, 0x20, 0x28, 0x66, 0x63, 0x31, 0x29, 0x0a, + 0x43, 0x6f, 0x70, 0x79, 0x72, 0x69, 0x67, 0x68, + 0x74, 0x20, 0x28, 0x63, 0x29, 0x20, 0x31, 0x39, + 0x38, 0x36, 0x2d, 0x32, 0x30, 0x30, 0x37, 0x20, + 0x62, 0x79, 0x20, 0x63, 0x69, 0x73, 0x63, 0x6f, + 0x20, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x73, + 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x0a, 0x43, + 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x64, 0x20, + 0x54, 0x68, 0x75, 0x20, 0x32, 0x36, 0x2d, 0x41, + 0x70, 0x72, 0x2d, 0x30, 0x37, 0x20, 0x32, 0x31, + 0x3a, 0x35, 0x36, 0x20, 0x62, 0x79, 0x20, 0x70, + 0x77, 0x61, 0x64, 0x65, 0x00, 0x06, 0x00, 0x11, + 0x63, 0x69, 0x73, 0x63, 0x6f, 0x20, 0x37, 0x32, + 0x30, 0x36, 0x56, 0x58, 0x52, 0x00, 0x02, 0x00, + 0x11, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0xcc, + 0x00, 0x04, 0xac, 0x42, 0x37, 0x03, 0x00, 0x03, + 0x00, 0x13, 0x46, 0x61, 0x73, 0x74, 0x45, 0x74, + 0x68, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x30, 0x2f, + 0x30, 0x00, 0x04, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x0b, 0x00, 0x05, 0x00 }; + /* This is: +IEEE 802.3 Ethernet + Destination: CDP/VTP/DTP/PAgP/UDLD (01:00:0c:cc:cc:cc) + Source: ca:00:68:46:00:00 (ca:00:68:46:00:00) + Length: 304 +Logical-Link Control + DSAP: SNAP (0xaa) + IG Bit: Individual + SSAP: SNAP (0xaa) + CR Bit: Command + Control field: U, func=UI (0x03) + 000. 00.. = Command: Unnumbered Information (0x00) + .... ..11 = Frame type: Unnumbered frame (0x03) + Organization Code: Cisco (0x00000c) + PID: CDP (0x2000) +Cisco Discovery Protocol + Version: 2 + TTL: 180 seconds + Checksum: 0x5427 [correct] + [Good: True] + [Bad : False] + Device ID: rtbg6test01 + Type: Device ID (0x0001) + Length: 15 + Device ID: rtbg6test01 + Software Version + Type: Software version (0x0005) + Length: 211 + Software Version: Cisco Internetwork Operating System Software + IOS (tm) 7200 Software (C7200-P-M), Version 12.2(46), RELEASE SOFTWARE (fc1) + Copyright (c) 1986-2007 by cisco Systems, Inc. + Compiled Thu 26-Apr-07 21:56 by pwade + Platform: cisco 7206VXR + Type: Platform (0x0006) + Length: 17 + Platform: cisco 7206VXR + Addresses + Type: Addresses (0x0002) + Length: 17 + Number of addresses: 1 + IP address: 172.66.55.3 + Protocol type: NLPID + Protocol length: 1 + Protocol: IP + Address length: 4 + IP address: 172.66.55.3 + Port ID: FastEthernet0/0 + Type: Port ID (0x0003) + Length: 19 + Sent through Interface: FastEthernet0/0 + Capabilities + Type: Capabilities (0x0004) + Length: 8 + Capabilities: 0x00000000 + .... .... .... .... .... .... .... ...0 = Not a Router + .... .... .... .... .... .... .... ..0. = Not a Transparent Bridge + .... .... .... .... .... .... .... .0.. = Not a Source Route Bridge + .... .... .... .... .... .... .... 0... = Not a Switch + .... .... .... .... .... .... ...0 .... = Not a Host + .... .... .... .... .... .... ..0. .... = Not IGMP capable + .... .... .... .... .... .... .0.. .... = Not a Repeater + Duplex: Half + Type: Duplex (0x000b) + Length: 5 + Duplex: Half + */ + struct lldpd_chassis *nchassis = NULL; + struct lldpd_port *nport = NULL; + + fail_unless(cdpv2_guess(pkt1, sizeof(pkt1))); + fail_unless(cdp_decode(NULL, pkt1, sizeof(pkt1), &hardware, + &nchassis, &nport) != -1); + if (!nchassis || !nport) { + fail("unable to decode packet"); + return; + } + ck_assert_int_eq(nchassis->c_id_subtype, + LLDP_CHASSISID_SUBTYPE_LOCAL); + ck_assert_int_eq(nchassis->c_id_len, strlen("rtbg6test01")); + fail_unless(memcmp(nchassis->c_id, + "rtbg6test01", strlen("rtbg6test01")) == 0); + ck_assert_str_eq(nchassis->c_name, "rtbg6test01"); + ck_assert_int_eq(TAILQ_FIRST(&nchassis->c_mgmt)->m_addr.inet.s_addr, + (u_int32_t)inet_addr("172.66.55.3")); + ck_assert_int_eq(TAILQ_FIRST(&nchassis->c_mgmt)->m_iface, 0); + ck_assert_int_eq(nport->p_id_subtype, + LLDP_PORTID_SUBTYPE_IFNAME); + ck_assert_int_eq(nport->p_id_len, strlen("FastEthernet0/0")); + fail_unless(memcmp(nport->p_id, + "FastEthernet0/0", strlen("FastEthernet0/0")) == 0); + ck_assert_str_eq(nport->p_descr, "FastEthernet0/0"); + ck_assert_int_eq(nchassis->c_cap_enabled, LLDP_CAP_STATION); + ck_assert_str_eq(nchassis->c_descr, + "cisco 7206VXR running on\n" + "Cisco Internetwork Operating System Software \n" + "IOS (tm) 7200 Software (C7200-P-M), Version 12.2(46), RELEASE SOFTWARE (fc1)\n" + "Copyright (c) 1986-2007 by cisco Systems, Inc.\n" + "Compiled Thu 26-Apr-07 21:56 by pwade"); +} +END_TEST + +#endif + +Suite * +cdp_suite(void) +{ + Suite *s = suite_create("CDP"); + +#ifdef ENABLE_CDP + TCase *tc_send = tcase_create("Send CDP packets"); + TCase *tc_receive = tcase_create("Receive CDP packets"); + + tcase_add_checked_fixture(tc_send, pcap_setup, pcap_teardown); + tcase_add_test(tc_send, test_send_cdpv1); + tcase_add_test(tc_send, test_send_cdpv2); + suite_add_tcase(s, tc_send); + + tcase_add_test(tc_receive, test_recv_cdpv1); + tcase_add_test(tc_receive, test_recv_cdpv2); + suite_add_tcase(s, tc_receive); +#endif + + return s; +} + +int +main() +{ + int number_failed; + Suite *s = cdp_suite (); + SRunner *sr = srunner_create (s); + srunner_set_fork_status (sr, CK_NOFORK); /* Can't fork because + we need to write + files */ + srunner_run_all (sr, CK_ENV); + number_failed = srunner_ntests_failed (sr); + srunner_free (sr); + return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/tests/check_edp.c b/tests/check_edp.c new file mode 100644 index 0000000000000000000000000000000000000000..126c567846f50c4d3848cf9753c0a8899bf7d4c4 --- /dev/null +++ b/tests/check_edp.c @@ -0,0 +1,621 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2015 Vincent Bernat + * + * 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 +#include +#include +#include +#include +#include "common.h" + +char filenameprefix[] = "edp_send"; + +#ifdef ENABLE_EDP + +START_TEST (test_send_basic) +{ + int n; + /* Packet we should build: +Extreme Discovery Protocol + Version: 1 + Reserved: 0 + Data length: 74 + Checksum: 0xde22 [correct] + [Good: True] + [Bad : False] + Sequence number: 0 + Machine ID type: MAC (0) + Machine MAC: 5e:10:8e:e7:84:ad (5e:10:8e:e7:84:ad) + Display: "First chassis" + Marker 0x99, length 18, type 1 = Display + TLV Marker: 0x99 + TLV type: Display (1) + TLV length: 18 + Name: First chassis + Info: Slot/Port: 1/4, Version: 7.6.4.99 + Marker 0x99, length 36, type 2 = Info + TLV Marker: 0x99 + TLV type: Info (2) + TLV length: 36 + Slot: 1 + Port: 4 + Virt chassis: 0 + Reserved: 000000000000 + Version: 7.6.4 Internal: 99 + Version: 0x07060463 + Version (major1): 7 + Version (major2): 6 + Version (sustaining): 4 + Version (internal): 99 + Connections: FFFFFFFF000000000000000000000000 + Null + Marker 0x99, length 4, type 0 = Null + TLV Marker: 0x99 + TLV type: Null (0) + TLV length: 4 + */ + char pkt1[] = { + 0x00, 0xe0, 0x2b, 0x00, 0x00, 0x00, 0x5e, 0x10, + 0x8e, 0xe7, 0x84, 0xad, 0x00, 0x52, 0xaa, 0xaa, + 0x03, 0x00, 0xe0, 0x2b, 0x00, 0xbb, 0x01, 0x00, + 0x00, 0x4a, 0xde, 0x22, 0x00, 0x00, 0x00, 0x00, + 0x5e, 0x10, 0x8e, 0xe7, 0x84, 0xad, 0x99, 0x01, + 0x00, 0x12, 0x46, 0x69, 0x72, 0x73, 0x74, 0x20, + 0x63, 0x68, 0x61, 0x73, 0x73, 0x69, 0x73, 0x00, + 0x99, 0x02, 0x00, 0x24, 0x00, 0x00, 0x00, 0x03, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x07, 0x06, 0x04, 0x63, 0xff, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x99, 0x00, 0x00, 0x04 }; + struct packet *pkt; + + /* Populate port and chassis */ + hardware.h_lport.p_id_subtype = LLDP_PORTID_SUBTYPE_IFNAME; + hardware.h_lport.p_id = "Not used"; + hardware.h_lport.p_id_len = strlen(hardware.h_lport.p_id); + hardware.h_lport.p_descr = "Not used"; + strlcpy(hardware.h_ifname, "eth3", sizeof(hardware.h_ifname)); + hardware.h_ifindex = 4; + chassis.c_id_subtype = LLDP_CHASSISID_SUBTYPE_LLADDR; + chassis.c_id = macaddress; + chassis.c_id_len = ETHER_ADDR_LEN; + chassis.c_name = "First chassis"; + /* Build packet */ + n = edp_send(NULL, &hardware); + if (n != 0) { + fail("unable to build packet"); + return; + } + if (TAILQ_EMPTY(&pkts)) { + fail("no packets sent"); + return; + } + pkt = TAILQ_FIRST(&pkts); + ck_assert_int_eq(pkt->size, sizeof(pkt1)); + fail_unless(memcmp(pkt->data, pkt1, sizeof(pkt1)) == 0); + fail_unless(TAILQ_NEXT(pkt, next) == NULL, "more than one packet sent"); +} +END_TEST + +#ifdef ENABLE_DOT1 +START_TEST (test_send_vlans) +{ + int n; + /* Packets we should build: +Extreme Discovery Protocol + Version: 1 + Reserved: 0 + Data length: 74 + Checksum: 0xde20 [correct] + [Good: True] + [Bad : False] + Sequence number: 2 + Machine ID type: MAC (0) + Machine MAC: 5e:10:8e:e7:84:ad (5e:10:8e:e7:84:ad) + Display: "First chassis" + Marker 0x99, length 18, type 1 = Display + TLV Marker: 0x99 + TLV type: Display (1) + TLV length: 18 + Name: First chassis + Info: Slot/Port: 1/4, Version: 7.6.4.99 + Marker 0x99, length 36, type 2 = Info + TLV Marker: 0x99 + TLV type: Info (2) + TLV length: 36 + Slot: 1 + Port: 4 + Virt chassis: 0 + Reserved: 000000000000 + Version: 7.6.4 Internal: 99 + Version: 0x07060463 + Version (major1): 7 + Version (major2): 6 + Version (sustaining): 4 + Version (internal): 99 + Connections: FFFFFFFF000000000000000000000000 + Null + Marker 0x99, length 4, type 0 = Null + TLV Marker: 0x99 + TLV type: Null (0) + TLV length: 4 + +Extreme Discovery Protocol + Version: 1 + Reserved: 0 + Data length: 102 + Checksum: 0x28c4 [correct] + [Good: True] + [Bad : False] + Sequence number: 3 + Machine ID type: MAC (0) + Machine MAC: 5e:10:8e:e7:84:ad (5e:10:8e:e7:84:ad) + Vlan: ID 157, Name "First VLAN" + Marker 0x99, length 27, type 5 = VL + TLV Marker: 0x99 + TLV type: VL (5) + TLV length: 27 + Flags: 0x00 + 0... .... = Flags-IP: Not set + .000 000. = Flags-reserved: 0x00 + .... ...0 = Flags-Unknown: Not set + Reserved1: 00 + Vlan ID: 157 + Reserved2: 00000000 + IP addr: 0.0.0.0 (0.0.0.0) + Name: First VLAN + Vlan: ID 1247, Name "Second VLAN" + Marker 0x99, length 28, type 5 = VL + TLV Marker: 0x99 + TLV type: VL (5) + TLV length: 28 + Flags: 0x00 + 0... .... = Flags-IP: Not set + .000 000. = Flags-reserved: 0x00 + .... ...0 = Flags-Unknown: Not set + Reserved1: 00 + Vlan ID: 1247 + Reserved2: 00000000 + IP addr: 0.0.0.0 (0.0.0.0) + Name: Second VLAN + Vlan: ID 741, Name "Third VLAN" + Marker 0x99, length 27, type 5 = VL + TLV Marker: 0x99 + TLV type: VL (5) + TLV length: 27 + Flags: 0x00 + 0... .... = Flags-IP: Not set + .000 000. = Flags-reserved: 0x00 + .... ...0 = Flags-Unknown: Not set + Reserved1: 00 + Vlan ID: 741 + Reserved2: 00000000 + IP addr: 0.0.0.0 (0.0.0.0) + Name: Third VLAN + Null + Marker 0x99, length 4, type 0 = Null + TLV Marker: 0x99 + TLV type: Null (0) + TLV length: 4 + */ + char pkt1[] = { + 0x00, 0xe0, 0x2b, 0x00, 0x00, 0x00, 0x5e, 0x10, + 0x8e, 0xe7, 0x84, 0xad, 0x00, 0x52, 0xaa, 0xaa, + 0x03, 0x00, 0xe0, 0x2b, 0x00, 0xbb, 0x01, 0x00, + 0x00, 0x4a, 0xde, 0x20, 0x00, 0x02, 0x00, 0x00, + 0x5e, 0x10, 0x8e, 0xe7, 0x84, 0xad, 0x99, 0x01, + 0x00, 0x12, 0x46, 0x69, 0x72, 0x73, 0x74, 0x20, + 0x63, 0x68, 0x61, 0x73, 0x73, 0x69, 0x73, 0x00, + 0x99, 0x02, 0x00, 0x24, 0x00, 0x00, 0x00, 0x03, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x07, 0x06, 0x04, 0x63, 0xff, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x99, 0x00, 0x00, 0x04 }; + + char pkt2[] = { + 0x00, 0xe0, 0x2b, 0x00, 0x00, 0x00, 0x5e, 0x10, + 0x8e, 0xe7, 0x84, 0xad, 0x00, 0x6e, 0xaa, 0xaa, + 0x03, 0x00, 0xe0, 0x2b, 0x00, 0xbb, 0x01, 0x00, + 0x00, 0x66, 0x28, 0xc4, 0x00, 0x03, 0x00, 0x00, + 0x5e, 0x10, 0x8e, 0xe7, 0x84, 0xad, 0x99, 0x05, + 0x00, 0x1b, 0x00, 0x00, 0x00, 0x9d, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x69, + 0x72, 0x73, 0x74, 0x20, 0x56, 0x4c, 0x41, 0x4e, + 0x00, 0x99, 0x05, 0x00, 0x1c, 0x00, 0x00, 0x04, + 0xdf, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x20, + 0x56, 0x4c, 0x41, 0x4e, 0x00, 0x99, 0x05, 0x00, + 0x1b, 0x00, 0x00, 0x02, 0xe5, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x54, 0x68, 0x69, + 0x72, 0x64, 0x20, 0x56, 0x4c, 0x41, 0x4e, 0x00, + 0x99, 0x00, 0x00, 0x04 }; + + struct packet *pkt; + struct lldpd_vlan vlan1, vlan2, vlan3; + + /* Populate port and chassis */ + hardware.h_lport.p_id_subtype = LLDP_PORTID_SUBTYPE_IFNAME; + hardware.h_lport.p_id = "Not used"; + hardware.h_lport.p_id_len = strlen(hardware.h_lport.p_id); + hardware.h_lport.p_descr = "Not used"; + strlcpy(hardware.h_ifname, "eth3", sizeof(hardware.h_ifname)); + hardware.h_ifindex = 4; + chassis.c_id_subtype = LLDP_CHASSISID_SUBTYPE_LLADDR; + chassis.c_id = macaddress; + chassis.c_id_len = ETHER_ADDR_LEN; + chassis.c_name = "First chassis"; + vlan1.v_name = "First VLAN"; vlan1.v_vid = 157; + vlan2.v_name = "Second VLAN"; vlan2.v_vid = 1247; + vlan3.v_name = "Third VLAN"; vlan3.v_vid = 741; + TAILQ_INSERT_TAIL(&hardware.h_lport.p_vlans, &vlan1, v_entries); + TAILQ_INSERT_TAIL(&hardware.h_lport.p_vlans, &vlan2, v_entries); + TAILQ_INSERT_TAIL(&hardware.h_lport.p_vlans, &vlan3, v_entries); + + /* Build packet */ + n = edp_send(NULL, &hardware); + if (n != 0) { + fail("unable to build packet"); + return; + } + if (TAILQ_EMPTY(&pkts)) { + fail("no packets sent"); + return; + } + pkt = TAILQ_FIRST(&pkts); + ck_assert_int_eq(pkt->size, sizeof(pkt1)); + fail_unless(memcmp(pkt->data, pkt1, sizeof(pkt1)) == 0); + pkt = TAILQ_NEXT(pkt, next); + if (!pkt) { + fail("need one more packet"); + return; + } + ck_assert_int_eq(pkt->size, sizeof(pkt2)); + fail_unless(memcmp(pkt->data, pkt2, sizeof(pkt2)) == 0); + fail_unless(TAILQ_NEXT(pkt, next) == NULL, "more than two packets sent"); +} +END_TEST +#endif + +START_TEST (test_recv_edp) +{ + char pkt1[] = { + 0x00, 0xe0, 0x2b, 0x00, 0x00, 0x00, 0x00, 0x04, + 0x96, 0x05, 0x44, 0x6f, 0x01, 0x44, 0xaa, 0xaa, + 0x03, 0x00, 0xe0, 0x2b, 0x00, 0xbb, 0x01, 0x00, + 0x01, 0x3c, 0x05, 0xdf, 0x03, 0x0f, 0x00, 0x00, + 0x00, 0x04, 0x96, 0x05, 0x44, 0x6f, 0x99, 0x02, + 0x00, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x06, + 0x04, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x99, 0x01, 0x01, 0x04, 0x6e, 0x65, + 0x30, 0x35, 0x30, 0x31, 0x73, 0x77, 0x2e, 0x58, + 0x58, 0x58, 0x58, 0x58, 0x58, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x03, 0x7b, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x80, 0x79, 0x0d, 0xec, 0xff, 0xff, + 0xff, 0xff, 0x80, 0xa7, 0x8b, 0x24, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x17, 0x08, 0x7e, 0xe5, 0xe2, + 0x00, 0x00, 0xee, 0xee, 0xee, 0xee, 0x00, 0x00, + 0x00, 0x02, 0x81, 0xb2, 0x19, 0xf0, 0x00, 0x00, + 0x00, 0x02, 0x80, 0xa5, 0x67, 0x20, 0xee, 0xee, + 0xee, 0xee, 0x80, 0xea, 0x8c, 0xac, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xe0, 0x2b, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xe0, 0x2b, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x80, 0xa4, 0x86, 0x2c, 0xee, 0xee, + 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, + 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0x00, 0x00, + 0x00, 0x00, 0xee, 0xee, 0xee, 0xee, 0x00, 0xe0, + 0x2b, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, + 0x00, 0x00, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, + 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0x99, 0x00, + 0x00, 0x04 }; + /* This is: +IEEE 802.3 Ethernet + Destination: Extreme-EDP (00:e0:2b:00:00:00) + Source: ExtremeN_05:44:6f (00:04:96:05:44:6f) + Length: 324 +Logical-Link Control + DSAP: SNAP (0xaa) + IG Bit: Individual + SSAP: SNAP (0xaa) + CR Bit: Command + Control field: U, func=UI (0x03) + 000. 00.. = Command: Unnumbered Information (0x00) + .... ..11 = Frame type: Unnumbered frame (0x03) + Organization Code: Extreme Networks (0x00e02b) + PID: EDP (0x00bb) +Extreme Discovery Protocol + Version: 1 + Reserved: 0 + Data length: 316 + Checksum: 0xdf05 [correct] + [Good: True] + [Bad : False] + Sequence number: 783 + Machine ID type: MAC (0) + Machine MAC: ExtremeN_05:44:6f (00:04:96:05:44:6f) + Info: Slot/Port: 1/1, Version: 7.6.4.0 + Marker 0x99, length 36, type 2 = Info + TLV Marker: 0x99 + TLV type: Info (2) + TLV length: 36 + Slot: 1 + Port: 1 + Virt chassis: 0 + Reserved: 000000000000 + Version: 7.6.4 Internal: 0 + Version: 0x07060400 + Version (major1): 7 + Version (major2): 6 + Version (sustaining): 4 + Version (internal): 0 + Connections: FFFF0000000000000000000000000000 + Display: "ne0501sw.XXXXXX" + Marker 0x99, length 260, type 1 = Display + TLV Marker: 0x99 + TLV type: Display (1) + TLV length: 260 + Name: ne0501sw.XXXXXX + Null + Marker 0x99, length 4, type 0 = Null + TLV Marker: 0x99 + TLV type: Null (0) + TLV length: 4 + */ + +#ifdef ENABLE_DOT1 + char pkt2[] = { + 0x00, 0xe0, 0x2b, 0x00, 0x00, 0x00, 0x00, 0x04, + 0x96, 0x05, 0x44, 0x6f, 0x01, 0x48, 0xaa, 0xaa, + 0x03, 0x00, 0xe0, 0x2b, 0x00, 0xbb, 0x01, 0x00, + 0x01, 0x40, 0x73, 0x04, 0x03, 0x10, 0x00, 0x00, + 0x00, 0x04, 0x96, 0x05, 0x44, 0x6f, 0x99, 0x05, + 0x00, 0x64, 0x80, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0x65, + 0x66, 0x61, 0x75, 0x6c, 0x74, 0x00, 0x43, 0x61, + 0x6e, 0x6e, 0x6f, 0x74, 0x20, 0x73, 0x61, 0x76, + 0x65, 0x20, 0x62, 0x72, 0x69, 0x64, 0x67, 0x65, + 0x20, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, + 0x20, 0x74, 0x6f, 0x20, 0x6e, 0x76, 0x20, 0x28, + 0x25, 0x64, 0x29, 0x0a, 0x00, 0x00, 0x4e, 0x6f, + 0x20, 0x62, 0x72, 0x69, 0x64, 0x67, 0x65, 0x20, + 0x66, 0x6f, 0x72, 0x20, 0x73, 0x75, 0x70, 0x65, + 0x72, 0x42, 0x72, 0x69, 0x64, 0x67, 0x65, 0x49, + 0x6e, 0x73, 0x74, 0x20, 0x25, 0x64, 0x00, 0x00, + 0x00, 0x00, 0x99, 0x05, 0x00, 0x64, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x4d, 0x61, 0x63, 0x56, 0x6c, 0x61, + 0x6e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, + 0x72, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x99, 0x05, + 0x00, 0x64, 0x80, 0x00, 0x00, 0x32, 0x00, 0x00, + 0x00, 0x00, 0x0a, 0x32, 0x00, 0x3f, 0x41, 0x64, + 0x6d, 0x69, 0x6e, 0x42, 0x32, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x99, 0x00, 0x00, 0x04 }; + /* This is: +IEEE 802.3 Ethernet + Destination: Extreme-EDP (00:e0:2b:00:00:00) + Source: ExtremeN_05:44:6f (00:04:96:05:44:6f) + Length: 328 +Logical-Link Control + DSAP: SNAP (0xaa) + IG Bit: Individual + SSAP: SNAP (0xaa) + CR Bit: Command + Control field: U, func=UI (0x03) + 000. 00.. = Command: Unnumbered Information (0x00) + .... ..11 = Frame type: Unnumbered frame (0x03) + Organization Code: Extreme Networks (0x00e02b) + PID: EDP (0x00bb) +Extreme Discovery Protocol + Version: 1 + Reserved: 0 + Data length: 320 + Checksum: 0x7304 [correct] + [Good: True] + [Bad : False] + Sequence number: 784 + Machine ID type: MAC (0) + Machine MAC: ExtremeN_05:44:6f (00:04:96:05:44:6f) + Vlan: ID 1, Name "Default" + Marker 0x99, length 100, type 5 = VL + TLV Marker: 0x99 + TLV type: VL (5) + TLV length: 100 + Flags: 0x80 + 1... .... = Flags-IP: Set + .000 000. = Flags-reserved: 0x00 + .... ...0 = Flags-Unknown: Not set + Reserved1: 00 + Vlan ID: 1 + Reserved2: 00000000 + IP addr: 0.0.0.0 (0.0.0.0) + Name: Default + Vlan: ID 0, Name "MacVlanDiscover" + Marker 0x99, length 100, type 5 = VL + TLV Marker: 0x99 + TLV type: VL (5) + TLV length: 100 + Flags: 0x00 + 0... .... = Flags-IP: Not set + .000 000. = Flags-reserved: 0x00 + .... ...0 = Flags-Unknown: Not set + Reserved1: 00 + Vlan ID: 0 + Reserved2: 00000000 + IP addr: 0.0.0.0 (0.0.0.0) + Name: MacVlanDiscover + Vlan: ID 50, Name "AdminB2" + Marker 0x99, length 100, type 5 = VL + TLV Marker: 0x99 + TLV type: VL (5) + TLV length: 100 + Flags: 0x80 + 1... .... = Flags-IP: Set + .000 000. = Flags-reserved: 0x00 + .... ...0 = Flags-Unknown: Not set + Reserved1: 00 + Vlan ID: 50 + Reserved2: 00000000 + IP addr: 10.50.0.63 (10.50.0.63) + Name: AdminB2 + Null + Marker 0x99, length 4, type 0 = Null + TLV Marker: 0x99 + TLV type: Null (0) + TLV length: 4 + */ + struct lldpd_vlan *vlan; +#endif + struct lldpd_chassis *nchassis = NULL; + struct lldpd_port *nport = NULL; + struct lldpd cfg; + char mac1[] = { 0x00, 0x04, 0x96, 0x05, 0x44, 0x6f }; + + cfg.g_config.c_mgmt_pattern = NULL; + fail_unless(edp_decode(&cfg, pkt1, sizeof(pkt1), &hardware, + &nchassis, &nport) != -1); + if (!nchassis || !nport) { + fail("unable to decode packet"); + return; + } + ck_assert_int_eq(nchassis->c_id_subtype, + LLDP_CHASSISID_SUBTYPE_LLADDR); + ck_assert_int_eq(nchassis->c_id_len, ETHER_ADDR_LEN); + fail_unless(memcmp(nchassis->c_id, mac1, ETHER_ADDR_LEN) == 0); + ck_assert_int_eq(nport->p_id_subtype, + LLDP_PORTID_SUBTYPE_IFNAME); + ck_assert_int_eq(nport->p_id_len, strlen("1/1")); + fail_unless(memcmp(nport->p_id, + "1/1", strlen("1/1")) == 0); + ck_assert_str_eq(nport->p_descr, "Slot 1 / Port 1"); + ck_assert_str_eq(nchassis->c_name, "ne0501sw.XXXXXX"); + ck_assert_str_eq(nchassis->c_descr, "EDP enabled device, version 7.6.4.0"); + ck_assert_int_eq(nchassis->c_cap_enabled, 0); + +#ifdef ENABLE_DOT1 + /* Add this port to list of remote port for hardware port */ + TAILQ_INSERT_TAIL(&hardware.h_rports, nport, p_entries); + nport->p_chassis = nchassis; + nport->p_protocol = LLDPD_MODE_EDP; + + /* Recept second packet */ + nchassis = NULL; nport = NULL; + fail_unless(edp_decode(&cfg, pkt2, sizeof(pkt2), &hardware, + &nchassis, &nport) == -1); + nport = TAILQ_FIRST(&hardware.h_rports); + if (!nport) { + fail("unable to find our previous port?"); + return; + } + ck_assert_int_eq(TAILQ_FIRST(&nport->p_chassis->c_mgmt)->m_addr.inet.s_addr, + (u_int32_t)inet_addr("10.50.0.63")); + if (TAILQ_EMPTY(&nport->p_vlans)) { + fail("no VLAN"); + return; + } + vlan = TAILQ_FIRST(&nport->p_vlans); + ck_assert_int_eq(vlan->v_vid, 1); + ck_assert_str_eq(vlan->v_name, "Default"); + vlan = TAILQ_NEXT(vlan, v_entries); + if (!vlan) { + fail("no more VLAN"); + return; + } + ck_assert_int_eq(vlan->v_vid, 0); + ck_assert_str_eq(vlan->v_name, "MacVlanDiscover"); + vlan = TAILQ_NEXT(vlan, v_entries); + if (!vlan) { + fail("no more VLAN"); + return; + } + ck_assert_int_eq(vlan->v_vid, 50); + ck_assert_str_eq(vlan->v_name, "AdminB2"); + vlan = TAILQ_NEXT(vlan, v_entries); + fail_unless(vlan == NULL); +#endif +} +END_TEST + +#endif + +Suite * +edp_suite(void) +{ + Suite *s = suite_create("EDP"); + +#ifdef ENABLE_EDP + TCase *tc_send = tcase_create("Send EDP packets"); + TCase *tc_receive = tcase_create("Receive EDP packets"); + + tcase_add_checked_fixture(tc_send, pcap_setup, pcap_teardown); + tcase_add_test(tc_send, test_send_basic); +#ifdef ENABLE_DOT1 + tcase_add_test(tc_send, test_send_vlans); +#endif + suite_add_tcase(s, tc_send); + + tcase_add_test(tc_receive, test_recv_edp); + suite_add_tcase(s, tc_receive); +#endif + + return s; +} + +int +main() +{ + int number_failed; + Suite *s = edp_suite (); + SRunner *sr = srunner_create (s); + srunner_set_fork_status (sr, CK_NOFORK); /* Can't fork because + we need to write + files */ + srunner_run_all (sr, CK_ENV); + number_failed = srunner_ntests_failed (sr); + srunner_free (sr); + return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/tests/check_fixedpoint.c b/tests/check_fixedpoint.c new file mode 100644 index 0000000000000000000000000000000000000000..db889bba4baea2e9ba60b7b065512d45d2869afe --- /dev/null +++ b/tests/check_fixedpoint.c @@ -0,0 +1,324 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2012 Vincent Bernat + * + * 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 +#include + +#include "../src/lib/fixedpoint.h" + +#ifdef ENABLE_LLDPMED + +START_TEST(test_string_parsing_suffix) { + char *end; + fp_strtofp("4541T", &end, 14, 8); + ck_assert_int_eq(*end, 'T'); + fp_strtofp("4541.U", &end, 14, 8); + ck_assert_int_eq(*end, 'U'); + fp_strtofp("4541.676V", &end, 14, 8); + ck_assert_int_eq(*end, 'V'); +} +END_TEST + +START_TEST(test_string_parsing_positive_int) { + struct fp_number fp = fp_strtofp("4541T", NULL, 14, 8); + ck_assert_int_eq(fp.integer.bits, 14); + ck_assert_int_eq(fp.integer.value, 4541); + ck_assert_int_eq(fp.fraction.bits, 8); + ck_assert_int_eq(fp.fraction.value, 0); + ck_assert_int_eq(fp.fraction.precision, 0); +} +END_TEST + +START_TEST(test_string_parsing_negative_int) { + struct fp_number fp = fp_strtofp("-4214N", NULL, 14, 8); + ck_assert_int_eq(fp.integer.bits, 14); + ck_assert_int_eq(fp.integer.value, -4214); + ck_assert_int_eq(fp.fraction.bits, 8); + ck_assert_int_eq(fp.fraction.value, 0); + ck_assert_int_eq(fp.fraction.precision, 0); +} +END_TEST + +START_TEST(test_string_parsing_positive_int_overflow) { + struct fp_number fp1 = fp_strtofp("4098", NULL, 13, 8); + struct fp_number fp2 = fp_strtofp("4096", NULL, 13, 8); + struct fp_number fp3 = fp_strtofp("4095", NULL, 13, 8); + struct fp_number fp4 = fp_strtofp("4094", NULL, 13, 8); + ck_assert_int_eq(fp1.integer.value, 4095); + ck_assert_int_eq(fp2.integer.value, 4095); + ck_assert_int_eq(fp3.integer.value, 4095); + ck_assert_int_eq(fp4.integer.value, 4094); +} +END_TEST + +START_TEST(test_string_parsing_negative_int_overflow) { + struct fp_number fp1 = fp_strtofp("-4097", NULL, 13, 8); + struct fp_number fp2 = fp_strtofp("-4096", NULL, 13, 8); + struct fp_number fp3 = fp_strtofp("-4095", NULL, 13, 8); + struct fp_number fp4 = fp_strtofp("-4094", NULL, 13, 8); + ck_assert_int_eq(fp1.integer.value, -4096); + ck_assert_int_eq(fp2.integer.value, -4096); + ck_assert_int_eq(fp3.integer.value, -4095); + ck_assert_int_eq(fp4.integer.value, -4094); +} +END_TEST + +START_TEST(test_string_parsing_positive_float) { + struct fp_number fp1 = fp_strtofp("1542.6250E", NULL, 13, 20); + ck_assert_int_eq(fp1.integer.value, 1542); + ck_assert_int_eq(fp1.fraction.precision, 14); + ck_assert_int_eq((fp1.fraction.value * 10000) >> fp1.fraction.bits, 6250); + + struct fp_number fp2 = fp_strtofp("1542.06250E", NULL, 13, 4); + ck_assert_int_eq(fp2.integer.value, 1542); + ck_assert_int_eq(fp2.fraction.precision, 4); + ck_assert_int_eq((fp2.fraction.value * 10000) >> fp2.fraction.bits, 625); +} +END_TEST + +START_TEST(test_string_parsing_negative_float) { + struct fp_number fp = fp_strtofp("-11542.6250N", NULL, 15, 4); + ck_assert_int_eq(fp.integer.value, -11542); + ck_assert_int_eq(fp.fraction.precision, 4); + ck_assert_int_eq((fp.fraction.value * 10000) >> fp.fraction.bits, 6250); +} +END_TEST + +START_TEST(test_string_parsing_no_fract_part) { + struct fp_number fp = fp_strtofp("11542.", NULL, 15, 4); + ck_assert_int_eq(fp.integer.value, 11542); + ck_assert_int_eq(fp.fraction.value, 0); + ck_assert_int_eq(fp.fraction.precision, 1); +} +END_TEST + +START_TEST(test_string_parsing_no_int_part) { + struct fp_number fp = fp_strtofp(".6250E", NULL, 13, 4); + ck_assert_int_eq(fp.integer.value, 0); + ck_assert_int_eq(fp.fraction.precision, 4); + ck_assert_int_eq((fp.fraction.value * 10000) >> fp.fraction.bits, 6250); +} +END_TEST + + +START_TEST(test_string_representation_positive_int) { + struct fp_number fp1 = fp_strtofp("214", NULL, 9, 9); + struct fp_number fp2 = fp_strtofp("11178.0000", NULL, 15, 9); + ck_assert_str_eq(fp_fptostr(fp1, NULL), "214"); + ck_assert_str_eq(fp_fptostr(fp2, NULL), "11178"); + ck_assert_str_eq(fp_fptostr(fp2, "ES"), "11178E"); +} +END_TEST + +START_TEST(test_string_representation_negative_int) { + struct fp_number fp1 = fp_strtofp("-214", NULL, 9, 9); + struct fp_number fp2 = fp_strtofp("-11178.0000", NULL, 15, 9); + ck_assert_str_eq(fp_fptostr(fp1, NULL), "-214"); + ck_assert_str_eq(fp_fptostr(fp2, NULL), "-11178"); + ck_assert_str_eq(fp_fptostr(fp2, "ES"), "11178S"); +} +END_TEST + +START_TEST(test_string_representation_positive_float) { + struct fp_number fp = fp_strtofp("214.6250", NULL, 9, 20); + ck_assert_str_eq(fp_fptostr(fp, NULL), "214.6250"); + ck_assert_str_eq(fp_fptostr(fp, "ES"), "214.6250E"); +} +END_TEST + +START_TEST(test_string_representation_positive_float_with_leading_zero) { + struct fp_number fp = fp_strtofp("214.06250", NULL, 9, 24); + ck_assert_str_eq(fp_fptostr(fp, NULL), "214.06250"); + ck_assert_str_eq(fp_fptostr(fp, "ES"), "214.06250E"); +} +END_TEST + +START_TEST(test_string_representation_negative_float) { + struct fp_number fp1 = fp_strtofp("-214.625", NULL, 22, 10); + struct fp_number fp2 = fp_strtofp("-415.5", NULL, 22, 4); + ck_assert_str_eq(fp_fptostr(fp1, NULL), "-214.625"); + ck_assert_str_eq(fp_fptostr(fp2, NULL), "-415.5"); + ck_assert_str_eq(fp_fptostr(fp2, "ES"), "415.5S"); +} +END_TEST + +START_TEST(test_buffer_representation_positive_float) { + unsigned char buffer[5] = {}; + unsigned char expected[] = { 0x21 << 2, 47 << 1, 0x68, 0x00, 0x00 }; + /* 47.2031250 = 47 + 2**-3 + 2**-4 + 2**-6, precision = 9+24 */ + struct fp_number fp = fp_strtofp("47.2031250", NULL, 9, 25); + fp_fptobuf(fp, buffer, 0); + fail_unless(memcmp(buffer, expected, sizeof(expected)) == 0); +} +END_TEST + +START_TEST(test_buffer_representation_negative_float) { + unsigned char buffer[5] = {}; + unsigned char expected[] = { (0x21 << 2) | 3, 0xa1, 0x98, 0x00, 0x00 }; + /* 47.2031250 = 000101111.0011010000000000000000000 */ + /* -47.2031250 = 111010000.1100101111111111111111111 + 1 */ + /* -47.2031250 = 111010000.1100110000000000000000000 */ + struct fp_number fp = fp_strtofp("-47.2031250", NULL, 9, 25); + fp_fptobuf(fp, buffer, 0); + fail_unless(memcmp(buffer, expected, sizeof(expected)) == 0); +} +END_TEST + +START_TEST(test_buffer_representation_with_shift) { + unsigned char buffer[] = { 0x77, 0xc6, 0x0, 0x0, 0x0, 0x0, 0xc7 }; + unsigned char expected[] = { 0x77, 0xc8, 0x45, 0xe6, 0x80, 0x00, 0x07 }; + struct fp_number fp = fp_strtofp("47.2031250", NULL, 9, 25); + fp_fptobuf(fp, buffer, 12); + fail_unless(memcmp(buffer, expected, sizeof(buffer)) == 0); +} +END_TEST + +START_TEST(test_buffer_representation_altitude) { + unsigned char buffer[5] = {}; + unsigned char expected[] = { (22 + 4) << 2, 0, 0, 14 << 4 | 1 << 3, 0 }; + struct fp_number fp = fp_strtofp("14.5", NULL, 22, 8); + fp_fptobuf(fp, buffer, 0); + fail_unless(memcmp(buffer, expected, sizeof(buffer)) == 0); +} +END_TEST + +START_TEST(test_buffer_parsing_positive_float) { + unsigned char buffer[] = { 0x21 << 2, 47 << 1, 0x68, 0x00, 0x00 }; + struct fp_number fp = fp_buftofp(buffer, 9, 25, 0); + ck_assert_int_eq(fp.integer.value, 47); + ck_assert_int_eq(fp.integer.bits, 9); + ck_assert_int_eq((fp.fraction.value * 10000000) >> fp.fraction.bits, 2031250); + ck_assert_int_eq(fp.fraction.bits, 25); + ck_assert_int_eq(fp.fraction.precision, 24); +} +END_TEST + +START_TEST(test_buffer_parsing_negative_float) { + unsigned char buffer[] = { (0x21 << 2) | 3, 0xa1, 0x98, 0x00, 0x00 }; + struct fp_number fp = fp_buftofp(buffer, 9, 25, 0); + ck_assert_int_eq(fp.integer.value, -47); + ck_assert_int_eq(fp.integer.bits, 9); + ck_assert_int_eq((fp.fraction.value * 10000000) >> fp.fraction.bits, 2031250); + ck_assert_int_eq(fp.fraction.bits, 25); + ck_assert_int_eq(fp.fraction.precision, 24); +} +END_TEST + +/* This is some corner case */ +START_TEST(test_buffer_parsing_positive_float_2) { + unsigned char buffer[] = { 0x40, 0x9c, 0x80, 0x00, 0x00 }; + struct fp_number fp = fp_buftofp(buffer, 9, 25, 0); + ck_assert_int_eq(fp.integer.value, 78); +} +END_TEST + +START_TEST(test_buffer_parsing_positive_float_with_shift) { + unsigned char buffer[] = { 0x77, 0xc8, 0x45, 0xe6, 0x80, 0x00, 0x07 }; + struct fp_number fp = fp_buftofp(buffer, 9, 25, 12); + ck_assert_int_eq(fp.integer.value, 47); + ck_assert_int_eq(fp.integer.bits, 9); + ck_assert_int_eq((fp.fraction.value * 10000000) >> fp.fraction.bits, 2031250); + ck_assert_int_eq(fp.fraction.bits, 25); + ck_assert_int_eq(fp.fraction.precision, 24); +} +END_TEST + +START_TEST(test_buffer_parsing_negative_float_with_shift) { + unsigned char buffer[] = { 0x00, 0xff, (0x21 << 2) | 3, 0xa1, 0x98, 0x00, 0x00 }; + struct fp_number fp = fp_buftofp(buffer, 9, 25, 16); + ck_assert_int_eq(fp.integer.value, -47); + ck_assert_int_eq(fp.integer.bits, 9); + ck_assert_int_eq((fp.fraction.value * 10000000) >> fp.fraction.bits, 2031250); + ck_assert_int_eq(fp.fraction.bits, 25); + ck_assert_int_eq(fp.fraction.precision, 24); +} +END_TEST + +START_TEST(test_negate_positive) { + struct fp_number fp = fp_strtofp("14.5", NULL, 9, 25); + struct fp_number nfp = fp_negate(fp); + ck_assert_int_eq(nfp.integer.value, -14); + ck_assert_int_eq(fp.fraction.value, nfp.fraction.value); + ck_assert_str_eq(fp_fptostr(nfp, NULL), "-14.5"); +} +END_TEST + +START_TEST(test_negate_negative) { + struct fp_number fp = fp_strtofp("-14.5", NULL, 9, 25); + struct fp_number nfp = fp_negate(fp); + ck_assert_int_eq(nfp.integer.value, 14); + ck_assert_int_eq(fp.fraction.value, nfp.fraction.value); + ck_assert_str_eq(fp_fptostr(nfp, NULL), "14.5"); +} +END_TEST + +#endif + +Suite * +fixedpoint_suite(void) +{ + Suite *s = suite_create("Fixed point representation"); + +#ifdef ENABLE_LLDPMED + TCase *tc_fp = tcase_create("Fixed point representation"); + tcase_add_test(tc_fp, test_string_parsing_suffix); + tcase_add_test(tc_fp, test_string_parsing_positive_int); + tcase_add_test(tc_fp, test_string_parsing_negative_int); + tcase_add_test(tc_fp, test_string_parsing_no_fract_part); + tcase_add_test(tc_fp, test_string_parsing_no_int_part); + tcase_add_test(tc_fp, test_string_parsing_positive_int_overflow); + tcase_add_test(tc_fp, test_string_parsing_negative_int_overflow); + tcase_add_test(tc_fp, test_string_parsing_positive_float); + tcase_add_test(tc_fp, test_string_parsing_negative_float); + tcase_add_test(tc_fp, test_string_representation_positive_int); + tcase_add_test(tc_fp, test_string_representation_negative_int); + tcase_add_test(tc_fp, test_string_representation_positive_float); + tcase_add_test(tc_fp, test_string_representation_positive_float_with_leading_zero); + tcase_add_test(tc_fp, test_string_representation_negative_float); + tcase_add_test(tc_fp, test_buffer_representation_positive_float); + tcase_add_test(tc_fp, test_buffer_representation_negative_float); + tcase_add_test(tc_fp, test_buffer_representation_with_shift); + tcase_add_test(tc_fp, test_buffer_representation_altitude); + tcase_add_test(tc_fp, test_buffer_parsing_positive_float); + tcase_add_test(tc_fp, test_buffer_parsing_positive_float_2); + tcase_add_test(tc_fp, test_buffer_parsing_negative_float); + tcase_add_test(tc_fp, test_buffer_parsing_positive_float_with_shift); + tcase_add_test(tc_fp, test_buffer_parsing_negative_float_with_shift); + tcase_add_test(tc_fp, test_negate_positive); + tcase_add_test(tc_fp, test_negate_negative); + suite_add_tcase(s, tc_fp); +#endif + + return s; +} + +/* Disable leak detection sanitizer */ +int __lsan_is_turned_off() { + return 1; +} + +int +main() +{ + int number_failed; + Suite *s = fixedpoint_suite(); + SRunner *sr = srunner_create(s); + srunner_run_all(sr, CK_ENV); + number_failed = srunner_ntests_failed(sr); + srunner_free(sr); + return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/tests/check_lldp.c b/tests/check_lldp.c new file mode 100644 index 0000000000000000000000000000000000000000..03251956339ebf9c1fd601b4079f0d0f9055f5e1 --- /dev/null +++ b/tests/check_lldp.c @@ -0,0 +1,501 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2015 Vincent Bernat + * + * 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 +#include +#include +#include +#include +#include "common.h" + +char filenameprefix[] = "lldp_send"; + +static struct lldpd test_lldpd = { + .g_config = { + .c_cap_advertise = 1, /* Chassis capabilities advertisement */ + .c_mgmt_advertise = 1, /* Management addresses advertisement */ + } +}; + +#define ck_assert_str_eq_n(X, Y, N) \ + ck_assert_msg(!strncmp(X, Y, N), "Assertion '"#X"=="#Y"' failed: "#X"==\"%s\", "#Y"==\"%s\"", X, Y) + +static void +check_received_port( + struct lldpd_port *sport, + struct lldpd_port *rport) +{ + ck_assert_int_eq(rport->p_id_subtype, sport->p_id_subtype); + ck_assert_int_eq(rport->p_id_len, sport->p_id_len); + ck_assert_str_eq_n(rport->p_id, sport->p_id, sport->p_id_len); + ck_assert_str_eq(rport->p_descr, sport->p_descr); +} + +static void +check_received_chassis( + struct lldpd_chassis *schassis, + struct lldpd_chassis *rchassis) +{ + ck_assert_int_eq(rchassis->c_id_subtype, schassis->c_id_subtype); + ck_assert_int_eq(rchassis->c_id_len, schassis->c_id_len); + ck_assert_str_eq_n(rchassis->c_id, schassis->c_id, schassis->c_id_len); + ck_assert_str_eq(rchassis->c_name, schassis->c_name); + ck_assert_str_eq(rchassis->c_descr, schassis->c_descr); + ck_assert_int_eq(rchassis->c_cap_available, schassis->c_cap_available); + ck_assert_int_eq(rchassis->c_cap_enabled, schassis->c_cap_enabled); +} + +START_TEST (test_send_rcv_basic) +{ + int n; + struct packet *pkt; + struct lldpd_chassis *nchassis = NULL; + struct lldpd_port *nport = NULL; + + /* Populate port and chassis */ + hardware.h_lport.p_id_subtype = LLDP_PORTID_SUBTYPE_IFNAME; + hardware.h_lport.p_id = "FastEthernet 1/5"; + hardware.h_lport.p_id_len = strlen(hardware.h_lport.p_id); + hardware.h_lport.p_descr = "Fake port description"; + hardware.h_lport.p_mfs = 1516; + chassis.c_id_subtype = LLDP_CHASSISID_SUBTYPE_LLADDR; + chassis.c_id = macaddress; + chassis.c_id_len = ETHER_ADDR_LEN; + chassis.c_name = "First chassis"; + chassis.c_descr = "Chassis description"; + chassis.c_cap_available = chassis.c_cap_enabled = LLDP_CAP_ROUTER; + + /* Build packet */ + n = lldp_send(&test_lldpd, &hardware); + if (n != 0) { + fail("unable to build packet"); + return; + } + if (TAILQ_EMPTY(&pkts)) { + fail("no packets sent"); + return; + } + pkt = TAILQ_FIRST(&pkts); + fail_unless(TAILQ_NEXT(pkt, next) == NULL, "more than one packet sent"); + + /* decode the retrieved packet calling lldp_decode() */ + fail_unless(lldp_decode(NULL, pkt->data, pkt->size, &hardware, + &nchassis, &nport) != -1); + if (!nchassis || !nport) { + fail("unable to decode packet"); + return; + } + /* verify port values */ + check_received_port(&hardware.h_lport, nport); + /* verify chassis values */ + check_received_chassis(&chassis, nchassis); +} +END_TEST + +#define ETHERTYPE_OFFSET 2 * ETHER_ADDR_LEN +#define VLAN_TAG_SIZE 2 +START_TEST (test_send_rcv_vlan_tx) +{ + int n; + struct packet *pkt; + struct lldpd_chassis *nchassis = NULL; + struct lldpd_port *nport = NULL; + int vlan_id = 100; + int vlan_prio = 5; + int vlan_dei = 1; + unsigned int vlan_tag = 0; + unsigned int tmp; + + /* Populate port and chassis */ + hardware.h_lport.p_id_subtype = LLDP_PORTID_SUBTYPE_IFNAME; + hardware.h_lport.p_id = "FastEthernet 1/5"; + hardware.h_lport.p_id_len = strlen(hardware.h_lport.p_id); + hardware.h_lport.p_descr = "Fake port description"; + hardware.h_lport.p_mfs = 1516; + + /* Assembly VLAN tag: Priority(3bits) | DEI(1bit) | VID(12bits) */ + vlan_tag = ((vlan_prio & 0x7) << 13) | + ((vlan_dei & 0x1) << 12) | + (vlan_id & 0xfff); + hardware.h_lport.p_vlan_tx_tag = vlan_tag; + hardware.h_lport.p_vlan_tx_enabled = 1; + chassis.c_id_subtype = LLDP_CHASSISID_SUBTYPE_LLADDR; + chassis.c_id = macaddress; + chassis.c_id_len = ETHER_ADDR_LEN; + chassis.c_name = "First chassis"; + chassis.c_descr = "Chassis description"; + chassis.c_cap_available = chassis.c_cap_enabled = LLDP_CAP_ROUTER; + + /* Build packet */ + n = lldp_send(&test_lldpd, &hardware); + if (n != 0) { + fail("unable to build packet"); + return; + } + if (TAILQ_EMPTY(&pkts)) { + fail("no packets sent"); + return; + } + pkt = TAILQ_FIRST(&pkts); + fail_unless(TAILQ_NEXT(pkt, next) == NULL, "more than one packet sent"); + + /* Check ETHER_TYPE, should be VLAN */ + memcpy(&tmp, (unsigned char*) pkt->data + ETHERTYPE_OFFSET, ETHER_TYPE_LEN); + ck_assert_uint_eq(ntohl(tmp)>>16, ETHERTYPE_VLAN); + + /* Check VLAN tag */ + memcpy(&tmp, (unsigned char*) pkt->data + ETHERTYPE_OFFSET + ETHER_TYPE_LEN, VLAN_TAG_SIZE); + ck_assert_uint_eq(ntohl(tmp)>>16, vlan_tag); + + /* Remove VLAN ethertype and VLAN tag */ + memmove((unsigned char*) pkt->data + ETHERTYPE_OFFSET, + /* move all after VLAN tag */ + (unsigned char*) pkt->data + ETHERTYPE_OFFSET + ETHER_TYPE_LEN + VLAN_TAG_SIZE, + /* size without src and dst MAC, VLAN tag */ + pkt->size - (ETHERTYPE_OFFSET + ETHER_TYPE_LEN + VLAN_TAG_SIZE)); + + /* Decode the packet without VLAN tag, calling lldp_decode() */ + fail_unless(lldp_decode(NULL, pkt->data, pkt->size, &hardware, + &nchassis, &nport) != -1); + if (!nchassis || !nport) { + fail("unable to decode packet"); + return; + } + + /* Verify port values (VLAN information is not checked here) */ + check_received_port(&hardware.h_lport, nport); + /* Verify chassis values */ + check_received_chassis(&chassis, nchassis); +} +END_TEST + +START_TEST (test_recv_min) +{ + char pkt1[] = { + 0x01, 0x80, 0xc2, 0x00, 0x00, 0x0e, 0x00, 0x17, + 0xd1, 0xa8, 0x35, 0xbe, 0x88, 0xcc, 0x02, 0x07, + 0x04, 0x00, 0x17, 0xd1, 0xa8, 0x35, 0xbf, 0x04, + 0x07, 0x03, 0x00, 0x17, 0xd1, 0xa8, 0x36, 0x02, + 0x06, 0x02, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00 }; + /* This is: +Ethernet II, Src: Nortel_a8:35:be (00:17:d1:a8:35:be), Dst: LLDP_Multicast (01:80:c2:00:00:0e) + Destination: LLDP_Multicast (01:80:c2:00:00:0e) + Source: Nortel_a8:35:be (00:17:d1:a8:35:be) + Type: 802.1 Link Layer Discovery Protocol (LLDP) (0x88cc) +Link Layer Discovery Protocol + Chassis Subtype = MAC address + 0000 001. .... .... = TLV Type: Chassis Id (1) + .... ...0 0000 0111 = TLV Length: 7 + Chassis Id Subtype: MAC address (4) + Chassis Id: Nortel_a8:35:bf (00:17:d1:a8:35:bf) + Port Subtype = MAC address + 0000 010. .... .... = TLV Type: Port Id (2) + .... ...0 0000 0111 = TLV Length: 7 + Port Id Subtype: MAC address (3) + Port Id: Nortel_a8:36:02 (00:17:d1:a8:36:02) + Time To Live = 120 sec + 0000 011. .... .... = TLV Type: Time to Live (3) + .... ...0 0000 0010 = TLV Length: 2 + Seconds: 120 + End of LLDPDU + 0000 000. .... .... = TLV Type: End of LLDPDU (0) + .... ...0 0000 0000 = TLV Length: 0 + */ + struct lldpd_chassis *nchassis = NULL; + struct lldpd_port *nport = NULL; + char mac1[] = { 0x0, 0x17, 0xd1, 0xa8, 0x35, 0xbf }; + char mac2[] = { 0x0, 0x17, 0xd1, 0xa8, 0x36, 0x02 }; + + fail_unless(lldp_decode(NULL, pkt1, sizeof(pkt1), &hardware, + &nchassis, &nport) != -1); + if (!nchassis || !nport) { + fail("unable to decode packet"); + return; + } + ck_assert_int_eq(nchassis->c_id_subtype, + LLDP_CHASSISID_SUBTYPE_LLADDR); + ck_assert_int_eq(nchassis->c_id_len, ETHER_ADDR_LEN); + fail_unless(memcmp(mac1, nchassis->c_id, ETHER_ADDR_LEN) == 0); + ck_assert_int_eq(nport->p_id_subtype, + LLDP_PORTID_SUBTYPE_LLADDR); + ck_assert_int_eq(nport->p_id_len, ETHER_ADDR_LEN); + fail_unless(memcmp(mac2, nport->p_id, ETHER_ADDR_LEN) == 0); + ck_assert_ptr_eq(nchassis->c_name, NULL); + ck_assert_ptr_eq(nchassis->c_descr, NULL); + ck_assert_ptr_eq(nport->p_descr, NULL); + ck_assert_int_eq(nport->p_ttl, 120); +} +END_TEST + +START_TEST (test_recv_lldpd) +{ + /* This is a frame generated by ub-lldpd */ + char pkt1[] = { + 0x01, 0x80, 0xc2, 0x00, 0x00, 0x0e, 0x00, 0x16, + 0x17, 0x2f, 0xa1, 0xb6, 0x88, 0xcc, 0x02, 0x07, + 0x04, 0x00, 0x16, 0x17, 0x2f, 0xa1, 0xb6, 0x04, + 0x07, 0x03, 0x00, 0x16, 0x17, 0x2f, 0xa1, 0xb6, + 0x06, 0x02, 0x00, 0x78, 0x0a, 0x1a, 0x6e, 0x61, + 0x72, 0x75, 0x74, 0x6f, 0x2e, 0x58, 0x58, 0x58, + 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, + 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, + 0x0c, 0x3f, 0x4c, 0x69, 0x6e, 0x75, 0x78, 0x20, + 0x32, 0x2e, 0x36, 0x2e, 0x32, 0x39, 0x2d, 0x32, + 0x2d, 0x61, 0x6d, 0x64, 0x36, 0x34, 0x20, 0x23, + 0x31, 0x20, 0x53, 0x4d, 0x50, 0x20, 0x53, 0x75, + 0x6e, 0x20, 0x4d, 0x61, 0x79, 0x20, 0x31, 0x37, + 0x20, 0x31, 0x37, 0x3a, 0x31, 0x35, 0x3a, 0x34, + 0x37, 0x20, 0x55, 0x54, 0x43, 0x20, 0x32, 0x30, + 0x30, 0x39, 0x20, 0x78, 0x38, 0x36, 0x5f, 0x36, + 0x34, 0x0e, 0x04, 0x00, 0x1c, 0x00, 0x14, 0x10, + 0x0c, 0x05, 0x01, 0x0a, 0xee, 0x50, 0x4b, 0x02, + 0x00, 0x00, 0x00, 0x03, 0x00, 0x08, 0x04, 0x65, + 0x74, 0x68, 0x30, 0xfe, 0x09, 0x00, 0x12, 0x0f, + 0x03, 0x01, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x09, + 0x00, 0x12, 0x0f, 0x01, 0x03, 0x6c, 0x03, 0x00, + 0x10, 0xfe, 0x06, 0x00, 0x12, 0x0f, 0x04, 0x05, + 0xdc, 0xfe, 0x07, 0x00, 0x12, 0xbb, 0x01, 0x00, + 0x00, 0x00, 0xfe, 0x0f, 0x00, 0x12, 0xbb, 0x05, + 0x4e, 0x44, 0x39, 0x39, 0x31, 0x37, 0x38, 0x39, + 0x37, 0x30, 0x32, 0xfe, 0x0b, 0x00, 0x12, 0xbb, + 0x06, 0x30, 0x38, 0x30, 0x30, 0x31, 0x32, 0x20, + 0xfe, 0x12, 0x00, 0x12, 0xbb, 0x07, 0x32, 0x2e, + 0x36, 0x2e, 0x32, 0x39, 0x2d, 0x32, 0x2d, 0x61, + 0x6d, 0x64, 0x36, 0x34, 0xfe, 0x10, 0x00, 0x12, + 0xbb, 0x08, 0x31, 0x30, 0x35, 0x38, 0x32, 0x30, + 0x38, 0x35, 0x30, 0x30, 0x30, 0x39, 0xfe, 0x15, + 0x00, 0x12, 0xbb, 0x09, 0x4e, 0x45, 0x43, 0x20, + 0x43, 0x6f, 0x6d, 0x70, 0x75, 0x74, 0x65, 0x72, + 0x73, 0x20, 0x53, 0x41, 0x53, 0xfe, 0x13, 0x00, + 0x12, 0xbb, 0x0a, 0x50, 0x4f, 0x57, 0x45, 0x52, + 0x4d, 0x41, 0x54, 0x45, 0x20, 0x56, 0x4c, 0x33, + 0x35, 0x30, 0xfe, 0x0d, 0x00, 0x12, 0xbb, 0x0b, + 0x31, 0x30, 0x30, 0x32, 0x30, 0x37, 0x31, 0x32, + 0x30, 0x00, 0x00 }; + /* This is: +Ethernet II, Src: Msi_2f:a1:b6 (00:16:17:2f:a1:b6), Dst: LLDP_Multicast (01:80:c2:00:00:0e) + Destination: LLDP_Multicast (01:80:c2:00:00:0e) + Source: Msi_2f:a1:b6 (00:16:17:2f:a1:b6) + Type: 802.1 Link Layer Discovery Protocol (LLDP) (0x88cc) +Link Layer Discovery Protocol + Chassis Subtype = MAC address + 0000 001. .... .... = TLV Type: Chassis Id (1) + .... ...0 0000 0111 = TLV Length: 7 + Chassis Id Subtype: MAC address (4) + Chassis Id: Msi_2f:a1:b6 (00:16:17:2f:a1:b6) + Port Subtype = MAC address + 0000 010. .... .... = TLV Type: Port Id (2) + .... ...0 0000 0111 = TLV Length: 7 + Port Id Subtype: MAC address (3) + Port Id: Msi_2f:a1:b6 (00:16:17:2f:a1:b6) + Time To Live = 120 sec + 0000 011. .... .... = TLV Type: Time to Live (3) + .... ...0 0000 0010 = TLV Length: 2 + Seconds: 120 + System Name = naruto.XXXXXXXXXXXXXXXXXXX + 0000 101. .... .... = TLV Type: System Name (5) + .... ...0 0001 1010 = TLV Length: 26 + System Name = naruto.bureau.b1.p.fti.net + System Description = Linux 2.6.29-2-amd64 #1 SMP Sun May 17 17:15:47 UTC 2009 x86_64 + 0000 110. .... .... = TLV Type: System Description (6) + .... ...0 0011 1111 = TLV Length: 63 + System Description = Linux 2.6.29-2-amd64 #1 SMP Sun May 17 17:15:47 UTC 2009 x86_64 + Capabilities + 0000 111. .... .... = TLV Type: System Capabilities (7) + .... ...0 0000 0100 = TLV Length: 4 + Capabilities: 0x001c + .... .... .... .1.. = Bridge + .... .... .... 1... = WLAN access point + .... .... ...1 .... = Router + Enabled Capabilities: 0x0014 + .... .... .... .1.. = Bridge + .... .... ...1 .... = Router + Management Address + 0001 000. .... .... = TLV Type: Management Address (8) + .... ...0 0000 1100 = TLV Length: 12 + Address String Length: 5 + Address Subtype: IPv4 (1) + Management Address: 10.238.80.75 + Interface Subtype: ifIndex (2) + Interface Number: 3 + OID String Length: 0 + Port Description = eth0 + 0000 100. .... .... = TLV Type: Port Description (4) + .... ...0 0000 0100 = TLV Length: 4 + Port Description: eth0 + IEEE 802.3 - Link Aggregation + 1111 111. .... .... = TLV Type: Organization Specific (127) + .... ...0 0000 1001 = TLV Length: 9 + Organization Unique Code: IEEE 802.3 (0x00120f) + IEEE 802.3 Subtype: Link Aggregation (0x03) + Aggregation Status: 0x01 + .... ...1 = Aggregation Capability: Yes + .... ..0. = Aggregation Status: Not Enabled + Aggregated Port Id: 0 + IEEE 802.3 - MAC/PHY Configuration/Status + 1111 111. .... .... = TLV Type: Organization Specific (127) + .... ...0 0000 1001 = TLV Length: 9 + Organization Unique Code: IEEE 802.3 (0x00120f) + IEEE 802.3 Subtype: MAC/PHY Configuration/Status (0x01) + Auto-Negotiation Support/Status: 0x03 + .... ...1 = Auto-Negotiation: Supported + .... ..1. = Auto-Negotiation: Enabled + PMD Auto-Negotiation Advertised Capability: 0x6C03 + .... .... .... ...1 = 1000BASE-T (full duplex mode) + .... .... .... ..1. = 1000BASE-T (half duplex mode) + .... .1.. .... .... = 100BASE-TX (full duplex mode) + .... 1... .... .... = 100BASE-TX (half duplex mode) + ..1. .... .... .... = 10BASE-T (full duplex mode) + .1.. .... .... .... = 10BASE-T (half duplex mode) + Operational MAU Type: 100BaseTXFD - 2 pair category 5 UTP, full duplex mode (0x0010) + IEEE 802.3 - Maximum Frame Size + 1111 111. .... .... = TLV Type: Organization Specific (127) + .... ...0 0000 0110 = TLV Length: 6 + Organization Unique Code: IEEE 802.3 (0x00120f) + IEEE 802.3 Subtype: Maximum Frame Size (0x04) + Maximum Frame Size: 1500 + TIA - Media Capabilities + 1111 111. .... .... = TLV Type: Organization Specific (127) + .... ...0 0000 0111 = TLV Length: 7 + Organization Unique Code: TIA (0x0012bb) + Media Subtype: Media Capabilities (0x01) + Capabilities: 0x0000 + Class Type: Type Not Defined + TIA - Inventory - Hardware Revision + 1111 111. .... .... = TLV Type: Organization Specific (127) + .... ...0 0000 1111 = TLV Length: 15 + Organization Unique Code: TIA (0x0012bb) + Media Subtype: Inventory - Hardware Revision (0x05) + Hardware Revision: ND991789702 + TIA - Inventory - Firmware Revision + 1111 111. .... .... = TLV Type: Organization Specific (127) + .... ...0 0000 1011 = TLV Length: 10 + Organization Unique Code: TIA (0x0012bb) + Media Subtype: Inventory - Firmware Revision (0x06) + Firmware Revision: 080012 + TIA - Inventory - Software Revision + 1111 111. .... .... = TLV Type: Organization Specific (127) + .... ...0 0001 0010 = TLV Length: 18 + Organization Unique Code: TIA (0x0012bb) + Media Subtype: Inventory - Software Revision (0x07) + Software Revision: 2.6.29-2-amd64 + TIA - Inventory - Serial Number + 1111 111. .... .... = TLV Type: Organization Specific (127) + .... ...0 0001 0000 = TLV Length: 16 + Organization Unique Code: TIA (0x0012bb) + Media Subtype: Inventory - Serial Number (0x08) + Serial Number: 105820850009 + TIA - Inventory - Manufacturer Name + 1111 111. .... .... = TLV Type: Organization Specific (127) + .... ...0 0001 0101 = TLV Length: 21 + Organization Unique Code: TIA (0x0012bb) + Media Subtype: Inventory - Manufacturer Name (0x09) + Manufacturer Name: NEC Computers SAS + TIA - Inventory - Model Name + 1111 111. .... .... = TLV Type: Organization Specific (127) + .... ...0 0001 0011 = TLV Length: 19 + Organization Unique Code: TIA (0x0012bb) + Media Subtype: Inventory - Model Name (0x0a) + Model Name: POWERMATE VL350 + TIA - Inventory - Asset ID + 1111 111. .... .... = TLV Type: Organization Specific (127) + .... ...0 0000 1101 = TLV Length: 13 + Organization Unique Code: TIA (0x0012bb) + Media Subtype: Inventory - Asset ID (0x0b) + Asset ID: 100207120 + End of LLDPDU + 0000 000. .... .... = TLV Type: End of LLDPDU (0) + .... ...0 0000 0000 = TLV Length: 0 + */ + struct lldpd_chassis *nchassis = NULL; + struct lldpd_port *nport = NULL; + char mac1[] = { 0x00, 0x16, 0x17, 0x2f, 0xa1, 0xb6 }; + + fail_unless(lldp_decode(NULL, pkt1, sizeof(pkt1), &hardware, + &nchassis, &nport) != -1); + if (!nchassis || !nport) { + fail("unable to decode packet"); + return; + } + ck_assert_int_eq(nchassis->c_id_subtype, + LLDP_CHASSISID_SUBTYPE_LLADDR); + ck_assert_int_eq(nchassis->c_id_len, ETHER_ADDR_LEN); + fail_unless(memcmp(mac1, nchassis->c_id, ETHER_ADDR_LEN) == 0); + ck_assert_int_eq(nport->p_id_subtype, + LLDP_PORTID_SUBTYPE_LLADDR); + ck_assert_int_eq(nport->p_id_len, ETHER_ADDR_LEN); + fail_unless(memcmp(mac1, nport->p_id, ETHER_ADDR_LEN) == 0); + ck_assert_int_eq(nport->p_ttl, 120); + ck_assert_str_eq(nchassis->c_name, "naruto.XXXXXXXXXXXXXXXXXXX"); + ck_assert_str_eq(nchassis->c_descr, + "Linux 2.6.29-2-amd64 #1 SMP Sun May 17 17:15:47 UTC 2009 x86_64"); + ck_assert_str_eq(nport->p_descr, "eth0"); + ck_assert_int_eq(nchassis->c_cap_available, + LLDP_CAP_WLAN | LLDP_CAP_ROUTER | LLDP_CAP_BRIDGE); + ck_assert_int_eq(nchassis->c_cap_enabled, + LLDP_CAP_ROUTER | LLDP_CAP_BRIDGE); + ck_assert_int_eq(nchassis->c_mgmt.tqh_first->m_addr.inet.s_addr, + (u_int32_t)inet_addr("10.238.80.75")); + ck_assert_int_eq(nchassis->c_mgmt.tqh_first->m_iface, 3); +} +END_TEST + +Suite * +lldp_suite(void) +{ + Suite *s = suite_create("LLDP"); + TCase *tc_send = tcase_create("Send LLDP packets"); + TCase *tc_receive = tcase_create("Receive LLDP packets"); + + /* Send tests are first run without knowing the result. The + result is then checked with: + tshark -V -T text -r tests/lldp_send_0000.pcap + + If the result is correct, then, we get the packet as C + bytes using wireshark export to C arrays (tshark seems not + be able to do this). + */ + + tcase_add_checked_fixture(tc_send, pcap_setup, pcap_teardown); + tcase_add_test(tc_send, test_send_rcv_basic); + tcase_add_test(tc_send, test_send_rcv_vlan_tx); + suite_add_tcase(s, tc_send); + + tcase_add_test(tc_receive, test_recv_min); + tcase_add_test(tc_receive, test_recv_lldpd); + suite_add_tcase(s, tc_receive); + + return s; +} + +int +main() +{ + int number_failed; + Suite *s = lldp_suite (); + SRunner *sr = srunner_create (s); + srunner_set_fork_status (sr, CK_NOFORK); /* Can't fork because + we need to write + files */ + srunner_run_all (sr, CK_ENV); + number_failed = srunner_ntests_failed (sr); + srunner_free (sr); + return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/tests/check_marshal.c b/tests/check_marshal.c new file mode 100644 index 0000000000000000000000000000000000000000..4f9344625d89d10eb768c4c217c03fbe459254ba --- /dev/null +++ b/tests/check_marshal.c @@ -0,0 +1,895 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2015 Vincent Bernat + * + * 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 +#include +#include +#include + +#define MARSHAL_EXPORT +#include "check-compat.h" +#include "../src/marshal.h" +#include "../src/log.h" + +/* This suite can be run in valgrind for memory leaks: + CK_FORK=no valgrind -v --leak-check=yes ./tests/check_marshal +*/ + +/* Use this callback to avoid some logs */ +void donothing(int pri, const char *msg) {}; + +struct struct_simple { + int a1; + long a2; + char a3; + time_t a4; + char a5[7]; +}; +MARSHAL(struct_simple); + +START_TEST(test_simple_structure) { + struct struct_simple source = { + .a1 = 78452, + .a2 = 48751424, + .a3 = 'h', + .a4 = 784254, + .a5 = { 'A', 'B', 'C', 'D', 'E', 'F', 'G'}, + }; + struct struct_simple *destination; + void *buffer; + size_t len, len2; + + len = struct_simple_serialize(&source, &buffer); + fail_unless(len > 0, "Unable to serialize"); + memset(&source, 0, sizeof(struct struct_simple)); + len2 = struct_simple_unserialize(buffer, len, &destination); + fail_unless(len2 > 0, "Unable to deserialize"); + free(buffer); + ck_assert_int_eq(len, len2); + ck_assert_int_eq(destination->a1, 78452); + ck_assert_int_eq(destination->a2, 48751424); + ck_assert_int_eq(destination->a3, 'h'); + ck_assert_int_eq(destination->a4, 784254); + ck_assert_int_eq(destination->a5[0], 'A'); + ck_assert_int_eq(destination->a5[1], 'B'); + ck_assert_int_eq(destination->a5[2], 'C'); + ck_assert_int_eq(destination->a5[3], 'D'); + ck_assert_int_eq(destination->a5[4], 'E'); + ck_assert_int_eq(destination->a5[5], 'F'); + ck_assert_int_eq(destination->a5[6], 'G'); + free(destination); +} +END_TEST + +struct struct_sub { + int e1; + struct struct_simple e2; + char e3; +}; +MARSHAL_BEGIN(struct_sub) +MARSHAL_SUBSTRUCT(struct_sub, struct_simple, e2) +MARSHAL_END(struct_sub); + +START_TEST(test_substruct_structure) { + struct struct_sub source = { + .e1 = -5122, + .e2 = { + .a1 = 78452, + .a2 = 48751424, + .a3 = 'h', + .a4 = 784254, + .a5 = { 'A', 'B', 'C', 'D', 'E', 'F', 'G'}, + }, + .e3 = 'a', + }; + + struct struct_sub *destination; + void *buffer; + size_t len, len2; + + len = struct_sub_serialize(&source, &buffer); + fail_unless(len > 0, "Unable to serialize"); + memset(&source, 0, sizeof(struct struct_sub)); + len2 = struct_sub_unserialize(buffer, len, &destination); + fail_unless(len2 > 0, "Unable to deserialize"); + free(buffer); + ck_assert_int_eq(len, len2); + ck_assert_int_eq(destination->e1, -5122); + ck_assert_int_eq(destination->e2.a1, 78452); + ck_assert_int_eq(destination->e2.a2, 48751424); + ck_assert_int_eq(destination->e2.a3, 'h'); + ck_assert_int_eq(destination->e2.a4, 784254); + ck_assert_int_eq(destination->e2.a5[0], 'A'); + ck_assert_int_eq(destination->e2.a5[1], 'B'); + ck_assert_int_eq(destination->e2.a5[2], 'C'); + ck_assert_int_eq(destination->e2.a5[3], 'D'); + ck_assert_int_eq(destination->e2.a5[4], 'E'); + ck_assert_int_eq(destination->e2.a5[5], 'F'); + ck_assert_int_eq(destination->e2.a5[6], 'G'); + ck_assert_int_eq(destination->e3, 'a'); + free(destination); +} +END_TEST + +struct struct_onepointer { + int b1; + long b2; + char b3; + struct struct_simple *b4; + int b5; +}; +MARSHAL_BEGIN(struct_onepointer) +MARSHAL_POINTER(struct_onepointer, struct_simple, b4) +MARSHAL_END(struct_onepointer); + +START_TEST(test_pointer_structure) { + struct struct_simple source_simple = { + .a1 = 78452, + .a2 = 48751424, + .a3 = 'h', + .a4 = 784254, + .a5 = { 'A', 'B', 'C', 'D', 'E', 'F', 'G'}, + }; + struct struct_onepointer source = { + .b1 = 18, + .b2 = 15454, + .b3 = 'o', + .b4 = &source_simple, + .b5 = 333333, + }; + + struct struct_onepointer *destination; + void *buffer; + size_t len, len2; + + len = struct_onepointer_serialize(&source, &buffer); + fail_unless(len > 0, "Unable to serialize"); + memset(&source_simple, 0, sizeof(struct struct_simple)); + memset(&source, 0, sizeof(struct struct_onepointer)); + len2 = struct_onepointer_unserialize(buffer, len, &destination); + fail_unless(len2 > 0, "Unable to deserialize"); + free(buffer); + ck_assert_int_eq(len, len2); + ck_assert_int_eq(destination->b1, 18); + ck_assert_int_eq(destination->b2, 15454); + ck_assert_int_eq(destination->b3, 'o'); + ck_assert_int_eq(destination->b4->a1, 78452); + ck_assert_int_eq(destination->b4->a2, 48751424); + ck_assert_int_eq(destination->b4->a3, 'h'); + ck_assert_int_eq(destination->b4->a4, 784254); + ck_assert_int_eq(destination->b4->a5[0], 'A'); + ck_assert_int_eq(destination->b4->a5[1], 'B'); + ck_assert_int_eq(destination->b4->a5[2], 'C'); + ck_assert_int_eq(destination->b4->a5[3], 'D'); + ck_assert_int_eq(destination->b4->a5[4], 'E'); + ck_assert_int_eq(destination->b4->a5[5], 'F'); + ck_assert_int_eq(destination->b4->a5[6], 'G'); + ck_assert_int_eq(destination->b5, 333333); + free(destination->b4); free(destination); +} +END_TEST + +struct struct_nestedpointers { + int c1; + long c2; + struct struct_simple *c3; + struct struct_onepointer *c4; + int c5; +}; +MARSHAL_BEGIN(struct_nestedpointers) +MARSHAL_POINTER(struct_nestedpointers, struct_simple, c3) +MARSHAL_POINTER(struct_nestedpointers, struct_onepointer, c4) +MARSHAL_END(struct_nestedpointers); + +START_TEST(test_several_pointers_structure) { + struct struct_simple source_simple1 = { + .a1 = 78452, + .a2 = 48751424, + .a3 = 'h', + .a4 = 784254, + .a5 = { 'A', 'B', 'C', 'D', 'E', 'F', 'G'}, + }; + struct struct_simple source_simple2 = { + .a1 = 451, + .a2 = 451424, + .a3 = 'o', + .a4 = 74, + .a5 = { 'a', 'b', 'c', 'd', 'e', 'f', 'g'}, + }; + struct struct_onepointer source_onepointer = { + .b1 = 18, + .b2 = 15454, + .b3 = 'o', + .b4 = &source_simple1, + .b5 = 333333, + }; + struct struct_nestedpointers source = { + .c1 = 4542, + .c2 = 5665454, + .c3 = &source_simple2, + .c4 = &source_onepointer, + .c5 = -545424, + }; + + struct struct_nestedpointers *destination; + void *buffer; + size_t len, len2; + + len = struct_nestedpointers_serialize(&source, &buffer); + fail_unless(len > 0, "Unable to serialize"); + memset(&source_simple1, 0, sizeof(struct struct_simple)); + memset(&source_simple2, 0, sizeof(struct struct_simple)); + memset(&source_onepointer, 0, sizeof(struct struct_onepointer)); + memset(&source, 0, sizeof(struct struct_nestedpointers)); + len2 = struct_nestedpointers_unserialize(buffer, len, &destination); + fail_unless(len2 > 0, "Unable to deserialize"); + free(buffer); + ck_assert_int_eq(len, len2); + ck_assert_int_eq(destination->c1, 4542); + ck_assert_int_eq(destination->c2, 5665454); + ck_assert_int_eq(destination->c3->a1, 451); + ck_assert_int_eq(destination->c3->a2, 451424); + ck_assert_int_eq(destination->c3->a3, 'o'); + ck_assert_int_eq(destination->c3->a4, 74); + ck_assert_int_eq(destination->c3->a5[3], 'd'); + ck_assert_int_eq(destination->c3->a5[4], 'e'); + ck_assert_int_eq(destination->c3->a5[6], 'g'); + ck_assert_int_eq(destination->c4->b1, 18); + ck_assert_int_eq(destination->c4->b2, 15454); + ck_assert_int_eq(destination->c4->b3, 'o'); + ck_assert_int_eq(destination->c4->b4->a1, 78452); + ck_assert_int_eq(destination->c4->b4->a2, 48751424); + ck_assert_int_eq(destination->c4->b4->a3, 'h'); + ck_assert_int_eq(destination->c4->b4->a4, 784254); + ck_assert_int_eq(destination->c4->b4->a5[0], 'A'); + ck_assert_int_eq(destination->c4->b4->a5[1], 'B'); + ck_assert_int_eq(destination->c4->b4->a5[2], 'C'); + ck_assert_int_eq(destination->c4->b4->a5[3], 'D'); + ck_assert_int_eq(destination->c4->b4->a5[4], 'E'); + ck_assert_int_eq(destination->c4->b4->a5[5], 'F'); + ck_assert_int_eq(destination->c4->b4->a5[6], 'G'); + ck_assert_int_eq(destination->c4->b5, 333333); + free(destination->c3); free(destination->c4->b4); + free(destination->c4); free(destination); +} +END_TEST + +START_TEST(test_null_pointers) { + struct struct_simple source_simple2 = { + .a1 = 451, + .a2 = 451424, + .a3 = 'o', + .a4 = 74, + .a5 = { 'a', 'b', 'c', 'd', 'e', 'f', 'g'}, + }; + struct struct_nestedpointers source = { + .c1 = 4542, + .c2 = 5665454, + .c3 = &source_simple2, + .c4 = NULL, + .c5 = -545424, + }; + + struct struct_nestedpointers *destination; + void *buffer; + size_t len, len2; + + len = struct_nestedpointers_serialize(&source, &buffer); + fail_unless(len > 0, "Unable to serialize"); + memset(&source_simple2, 0, sizeof(struct struct_simple)); + memset(&source, 0, sizeof(struct struct_nestedpointers)); + len2 = struct_nestedpointers_unserialize(buffer, len, &destination); + fail_unless(len2 > 0, "Unable to deserialize"); + free(buffer); + ck_assert_int_eq(len, len2); + ck_assert_int_eq(destination->c1, 4542); + ck_assert_int_eq(destination->c2, 5665454); + ck_assert_int_eq(destination->c3->a1, 451); + ck_assert_int_eq(destination->c3->a2, 451424); + ck_assert_int_eq(destination->c3->a3, 'o'); + ck_assert_int_eq(destination->c3->a4, 74); + ck_assert_int_eq(destination->c3->a5[3], 'd'); + ck_assert_int_eq(destination->c3->a5[4], 'e'); + ck_assert_int_eq(destination->c3->a5[6], 'g'); + ck_assert_ptr_eq(destination->c4, NULL); + free(destination->c3); free(destination); +} +END_TEST + +struct struct_multipleref { + int f1; + struct struct_simple* f2; + struct struct_simple* f3; + struct struct_nestedpointers* f4; +}; +MARSHAL_BEGIN(struct_multipleref) +MARSHAL_POINTER(struct_multipleref, struct_simple, f2) +MARSHAL_POINTER(struct_multipleref, struct_simple, f3) +MARSHAL_POINTER(struct_multipleref, struct_nestedpointers, f4) +MARSHAL_END(struct_multipleref); + +START_TEST(test_multiple_references) { + struct struct_simple source_simple = { + .a1 = 451, + .a2 = 451424, + .a3 = 'o', + .a4 = 74, + .a5 = { 'a', 'b', 'c', 'd', 'e', 'f', 'g'}, + }; + struct struct_nestedpointers source_nested = { + .c3 = &source_simple, + .c4 = NULL, + }; + struct struct_multipleref source = { + .f1 = 15, + .f2 = &source_simple, + .f3 = &source_simple, + .f4 = &source_nested, + }; + + struct struct_multipleref *destination; + void *buffer = NULL; + size_t len, len2; + + len = struct_multipleref_serialize(&source, &buffer); + fail_unless(buffer != NULL, "Buffer is empty"); + fail_unless(len > 0, "Unable to serialize"); + memset(&source_simple, 0, sizeof(struct struct_simple)); + memset(&source_nested, 0, sizeof(struct struct_nestedpointers)); + memset(&source, 0, sizeof(struct struct_multipleref)); + len2 = struct_multipleref_unserialize(buffer, len, &destination); + fail_unless(len2 > 0, "Unable to deserialize"); + free(buffer); + ck_assert_int_eq(len, len2); + ck_assert_int_eq(destination->f1, 15); + ck_assert_ptr_eq(destination->f2, destination->f3); + ck_assert_ptr_eq(destination->f2, destination->f4->c3); + ck_assert_int_eq(destination->f2->a1, 451); + ck_assert_int_eq(destination->f2->a2, 451424); + ck_assert_int_eq(destination->f2->a3, 'o'); + ck_assert_int_eq(destination->f2->a4, 74); + ck_assert_ptr_eq(destination->f4->c4, NULL); + free(destination->f2); free(destination->f4); free(destination); +} +END_TEST + +struct struct_circularref { + int g1; + struct struct_circularref* g2; +}; +MARSHAL_BEGIN(struct_circularref) +MARSHAL_POINTER(struct_circularref, struct_circularref, g2) +MARSHAL_END(struct_circularref); + +START_TEST(test_circular_references) { + struct struct_circularref source = { + .g1 = 42, + .g2 = &source, + }; + + struct struct_circularref *destination; + void *buffer = NULL; + size_t len, len2; + + len = struct_circularref_serialize(&source, &buffer); + fail_unless(len > 0, "Unable to serialize"); + memset(&source, 0, sizeof(struct struct_circularref)); + len2 = struct_circularref_unserialize(buffer, len, &destination); + fail_unless(len2 > 0, "Unable to deserialize"); + free(buffer); + ck_assert_int_eq(len, len2); + ck_assert_int_eq(destination->g1, 42); + ck_assert_int_eq(destination->g2->g1, 42); + ck_assert_ptr_eq(destination->g2, destination->g2->g2); + free(destination); +} +END_TEST + +START_TEST(test_too_small_unmarshal) { + struct struct_simple source_simple1; + struct struct_onepointer source_onepointer = { + .b4 = &source_simple1, + }; + struct struct_nestedpointers source = { + .c3 = &source_simple1, + .c4 = &source_onepointer, + }; + + struct struct_nestedpointers *destination; + void *buffer; + size_t len, len2; + int i, j; + + log_register(donothing); + + len = struct_nestedpointers_serialize(&source, &buffer); + fail_unless(len > 0, "Unable to serialize"); + memset(&source_simple1, 0, sizeof(struct struct_simple)); + memset(&source_onepointer, 0, sizeof(struct struct_onepointer)); + memset(&source, 0, sizeof(struct struct_nestedpointers)); + /* Loop 30 times to ease debugging leaks with valgrind */ + for (j = 0; j < 30; j++) { + for (i = 0; i < len; i++) { + len2 = struct_nestedpointers_unserialize(buffer, 1, &destination); + fail_unless(len2 == 0, + "Should not be able to deserialize, too small (%d<%d)", + i, len); + } + } + len2 = struct_nestedpointers_unserialize(buffer, len + 5, &destination); + fail_unless(len2 == len, "Deserialized too much"); + free(destination->c3); + free(destination->c4); free(destination); free(buffer); + + log_register(NULL); +} +END_TEST + +struct struct_simpleentry { + TAILQ_ENTRY(struct_simpleentry) s_entries; + int g1; + struct struct_simple *g2; +}; +MARSHAL_BEGIN(struct_simpleentry) +MARSHAL_TQE(struct_simpleentry, s_entries) +MARSHAL_POINTER(struct_simpleentry, struct_simple, g2) +MARSHAL_END(struct_simpleentry); + +TAILQ_HEAD(list_simple, struct_simpleentry); +MARSHAL_TQ(list_simple, struct_simpleentry); + +START_TEST(test_simple_list) { + struct struct_simple source_simple = { + .a1 = 451, + .a2 = 451424, + .a3 = 'o', + .a4 = 74, + .a5 = { 'a', 'b', 'c', 'd', 'e', 'f', 'g'}, + }; + struct list_simple source; + struct struct_simpleentry entry1 = { + .g1 = 47, + .g2 = &source_simple, + }; + struct struct_simpleentry entry2 = { + .g1 = 49, + .g2 = &source_simple, + }; + struct struct_simpleentry entry3 = { + .g1 = 4700, + .g2 = NULL, + }; + struct struct_simpleentry entry4 = { + .g1 = -47, + .g2 = &source_simple, + }; + struct list_simple *destination; + void *buffer; + size_t len, len2; + struct struct_simpleentry *e1, *e2; + struct struct_simple *s; + + TAILQ_INIT(&source); + TAILQ_INSERT_TAIL(&source, &entry1, s_entries); + TAILQ_INSERT_TAIL(&source, &entry2, s_entries); + TAILQ_INSERT_TAIL(&source, &entry3, s_entries); + TAILQ_INSERT_TAIL(&source, &entry4, s_entries); + + len = list_simple_serialize(&source, &buffer); + fail_unless(len > 0, "Unable to serialize"); + memset(&source, 0, sizeof(struct list_simple)); + memset(&entry1, 0, sizeof(struct struct_simpleentry)); + memset(&entry2, 0, sizeof(struct struct_simpleentry)); + memset(&entry3, 0, sizeof(struct struct_simpleentry)); + memset(&entry4, 0, sizeof(struct struct_simpleentry)); + len2 = list_simple_unserialize(buffer, len, &destination); + fail_unless(len2 > 0, "Unable to deserialize"); + free(buffer); + + e1 = TAILQ_FIRST(destination); + ck_assert_int_eq(e1->g1, 47); + s = e1->g2; + e2 = TAILQ_NEXT(e1, s_entries); + free(e1); + ck_assert_int_eq(e2->g1, 49); + ck_assert_ptr_eq(e2->g2, s); + e1 = TAILQ_NEXT(e2, s_entries); + free(e2); + ck_assert_int_eq(e1->g1, 4700); + ck_assert_ptr_eq(e1->g2, NULL); + e2 = TAILQ_NEXT(e1, s_entries); + free(e1); + ck_assert_int_eq(e2->g1, -47); + ck_assert_ptr_eq(e2->g2, s); + e1 = TAILQ_NEXT(e2, s_entries); + free(e2); + ck_assert_ptr_eq(e1, NULL); + free(s); + free(destination); +} +END_TEST + +START_TEST(test_simple_repaired_list) { + struct struct_simple source_simple = { + .a1 = 451, + .a2 = 451424, + .a3 = 'o', + .a4 = 74, + .a5 = { 'a', 'b', 'c', 'd', 'e', 'f', 'g'}, + }; + struct list_simple source; + struct struct_simpleentry entry1 = { + .g1 = 47, + .g2 = &source_simple, + }; + struct struct_simpleentry entry2 = { + .g1 = 49, + .g2 = &source_simple, + }; + struct struct_simpleentry entry3 = { + .g1 = 4700, + .g2 = NULL, + }; + struct struct_simpleentry entry4 = { + .g1 = -47, + .g2 = &source_simple, + }; + struct struct_simpleentry entry5 = { + .g1 = -1000, + .g2 = NULL, + }; + struct list_simple *destination; + void *buffer; + size_t len, len2; + struct struct_simpleentry *e1, *e2, *e3, *e4; + + TAILQ_INIT(&source); + TAILQ_INSERT_TAIL(&source, &entry1, s_entries); + TAILQ_INSERT_TAIL(&source, &entry2, s_entries); + TAILQ_INSERT_TAIL(&source, &entry3, s_entries); + TAILQ_INSERT_TAIL(&source, &entry4, s_entries); + + len = list_simple_serialize(&source, &buffer); + fail_unless(len > 0, "Unable to serialize"); + memset(&source, 0, sizeof(struct list_simple)); + memset(&entry1, 0, sizeof(struct struct_simpleentry)); + memset(&entry2, 0, sizeof(struct struct_simpleentry)); + memset(&entry3, 0, sizeof(struct struct_simpleentry)); + memset(&entry4, 0, sizeof(struct struct_simpleentry)); + len2 = list_simple_unserialize(buffer, len, &destination); + fail_unless(len2 > 0, "Unable to deserialize"); + free(buffer); + + marshal_repair_tailq(struct_simpleentry, destination, s_entries); + + e1 = TAILQ_FIRST(destination); + ck_assert_int_eq(e1->g1, 47); + e4 = TAILQ_LAST(destination, list_simple); + ck_assert_int_eq(e4->g1, -47); + e3 = TAILQ_PREV(e4, list_simple, s_entries); + ck_assert_int_eq(e3->g1, 4700); + e2 = TAILQ_PREV(e3, list_simple, s_entries); + ck_assert_int_eq(e2->g1, 49); + + TAILQ_INSERT_TAIL(destination, &entry5, s_entries); + free(e1->g2); + free(e1); + free(e2); + free(e3); + free(e4); + free(destination); +} +END_TEST + +START_TEST(test_empty_repaired_list) { + struct list_simple source; + size_t len, len2; + struct list_simple *destination; + void *buffer; + struct struct_simpleentry *e1; + struct struct_simpleentry entry5 = { + .g1 = -1000, + .g2 = NULL, + }; + TAILQ_INIT(&source); + + len = list_simple_serialize(&source, &buffer); + fail_unless(len > 0, "Unable to serialize"); + memset(&source, 0, sizeof(struct list_simple)); + len2 = list_simple_unserialize(buffer, len, &destination); + fail_unless(len2 > 0, "Unable to deserialize"); + free(buffer); + + marshal_repair_tailq(struct_simpleentry, destination, s_entries); + + e1 = TAILQ_FIRST(destination); + ck_assert_ptr_eq(e1, NULL); + e1 = TAILQ_LAST(destination, list_simple); + ck_assert_ptr_eq(e1, NULL); + + TAILQ_INSERT_TAIL(destination, &entry5, s_entries); + + free(destination); +} +END_TEST + +struct struct_withlist { + int i1; + TAILQ_HEAD(, struct_simpleentry) i2; + int i3; +}; +MARSHAL_BEGIN(struct_withlist) +MARSHAL_SUBTQ(struct_withlist, struct_simpleentry, i2) +MARSHAL_END(struct_withlist); + +START_TEST(test_embedded_list) { + struct struct_withlist source = { + .i1 = 45424, + .i3 = 4542, + }; + struct struct_simple source_simple = { + .a1 = 451, + .a2 = 451424, + .a3 = 'o', + .a4 = 74, + .a5 = { 'a', 'b', 'c', 'd', 'e', 'f', 'g'}, + }; + struct struct_simpleentry entry1 = { + .g1 = 47, + .g2 = &source_simple, + }; + struct struct_simpleentry entry2 = { + .g1 = 49, + .g2 = &source_simple, + }; + struct struct_simpleentry entry3 = { + .g1 = 4700, + .g2 = NULL, + }; + struct struct_simpleentry entry4 = { + .g1 = -47, + .g2 = &source_simple, + }; + struct struct_withlist *destination; + void *buffer; + size_t len, len2; + struct struct_simpleentry *e1, *e2; + struct struct_simple *s; + + TAILQ_INIT(&source.i2); + TAILQ_INSERT_TAIL(&source.i2, &entry1, s_entries); + TAILQ_INSERT_TAIL(&source.i2, &entry2, s_entries); + TAILQ_INSERT_TAIL(&source.i2, &entry3, s_entries); + TAILQ_INSERT_TAIL(&source.i2, &entry4, s_entries); + + len = struct_withlist_serialize(&source, &buffer); + fail_unless(len > 0, "Unable to serialize"); + memset(&source, 0, sizeof(struct list_simple)); + memset(&entry1, 0, sizeof(struct struct_simpleentry)); + memset(&entry2, 0, sizeof(struct struct_simpleentry)); + memset(&entry3, 0, sizeof(struct struct_simpleentry)); + memset(&entry4, 0, sizeof(struct struct_simpleentry)); + len2 = struct_withlist_unserialize(buffer, len, &destination); + fail_unless(len2 > 0, "Unable to deserialize"); + free(buffer); + + ck_assert_int_eq(destination->i1, 45424); + ck_assert_int_eq(destination->i3, 4542); + e1 = TAILQ_FIRST(&destination->i2); + ck_assert_int_eq(e1->g1, 47); + ck_assert_int_eq(e1->g2->a4, 74); + s = e1->g2; + e2 = TAILQ_NEXT(e1, s_entries); + free(e1); + ck_assert_int_eq(e2->g1, 49); + ck_assert_ptr_eq(e2->g2, s); + e1 = TAILQ_NEXT(e2, s_entries); + free(e2); + ck_assert_int_eq(e1->g1, 4700); + ck_assert_ptr_eq(e1->g2, NULL); + e2 = TAILQ_NEXT(e1, s_entries); + free(e1); + ck_assert_int_eq(e2->g1, -47); + ck_assert_ptr_eq(e2->g2, s); + e1 = TAILQ_NEXT(e2, s_entries); + free(e2); + ck_assert_ptr_eq(e1, NULL); + free(s); + free(destination); +} +END_TEST + +struct struct_string { + int s1; + char *s2; + char *s3; +}; +MARSHAL_BEGIN(struct_string) +MARSHAL_STR(struct_string, s2) +MARSHAL_STR(struct_string, s3) +MARSHAL_END(struct_string); + +START_TEST(test_string) { + struct struct_string source = { + .s1 = 44444, + .s2 = "String 2", + .s3 = "String 3", + }; + struct struct_string *destination; + void *buffer; + size_t len, len2; + + len = struct_string_serialize(&source, &buffer); + fail_unless(len > 0, "Unable to serialize"); + memset(&source, 0, sizeof(struct struct_string)); + len2 = struct_string_unserialize(buffer, len, &destination); + fail_unless(len2 > 0, "Unable to deserialize"); + free(buffer); + ck_assert_int_eq(len, len2); + ck_assert_int_eq(destination->s1, 44444); + ck_assert_str_eq(destination->s2, "String 2"); + ck_assert_str_eq(destination->s3, "String 3"); + free(destination->s2); free(destination->s3); + free(destination); +} +END_TEST + +struct struct_fixedstring { + int s1; + char *s2; + int s2_len; + char *s3; +}; +MARSHAL_BEGIN(struct_fixedstring) +MARSHAL_FSTR(struct_fixedstring, s2, s2_len) +MARSHAL_STR(struct_fixedstring, s3) +MARSHAL_END(struct_fixedstring); + +START_TEST(test_fixed_string) { + struct struct_fixedstring source = { + .s1 = 44444, + .s2 = "String 2 Bla", + .s2_len = 8, /* Not 12! */ + .s3 = "String 3", + }; + struct struct_fixedstring *destination; + void *buffer; + size_t len, len2; + + len = struct_fixedstring_serialize(&source, &buffer); + fail_unless(len > 0, "Unable to serialize"); + memset(&source, 0, sizeof(struct struct_fixedstring)); + len2 = struct_fixedstring_unserialize(buffer, len, &destination); + fail_unless(len2 > 0, "Unable to deserialize"); + free(buffer); + ck_assert_int_eq(len, len2); + ck_assert_int_eq(destination->s1, 44444); + ck_assert_int_eq(destination->s2_len, 8); + ck_assert_int_eq(destination->s2[0], 'S'); + ck_assert_int_eq(destination->s2[2], 'r'); + ck_assert_int_eq(destination->s2[4], 'n'); + ck_assert_int_eq(destination->s2[5], 'g'); + ck_assert_int_eq(destination->s2[6], ' '); + ck_assert_int_eq(destination->s2[7], '2'); + ck_assert_int_eq(destination->s2[8], '\0'); /* fixed string are null-terminated too */ + ck_assert_str_eq(destination->s3, "String 3"); + free(destination->s2); free(destination->s3); + free(destination); +} +END_TEST + +struct struct_ignore { + int t1; + void *t2; + int t3; +}; +MARSHAL_BEGIN(struct_ignore) +MARSHAL_IGNORE(struct_ignore, t2) +MARSHAL_END(struct_ignore); + +START_TEST(test_ignore) { + struct struct_ignore source = { + .t1 = 4544, + .t2 = (void *)"String 2 Bla", + .t3 = 11111, + }; + struct struct_ignore *destination; + void *buffer; + size_t len, len2; + + len = struct_ignore_serialize(&source, &buffer); + fail_unless(len > 0, "Unable to serialize"); + memset(&source, 0, sizeof(struct struct_ignore)); + len2 = struct_ignore_unserialize(buffer, len, &destination); + fail_unless(len2 > 0, "Unable to deserialize"); + free(buffer); + ck_assert_int_eq(len, len2); + ck_assert_int_eq(destination->t1, 4544); + ck_assert_ptr_eq(destination->t2, NULL); + ck_assert_int_eq(destination->t3, 11111); + free(destination); +} +END_TEST + +START_TEST(test_equality) { + struct struct_simple source_simple1 = { + .a1 = 451, + .a2 = 451424, + .a3 = 'o', + .a4 = 74, + .a5 = { 'a', 'b', 'c', 'd', 'e', 'f', 'g'}, + }; + struct struct_simpleentry entry1 = { + .g1 = 47, + .g2 = &source_simple1, + }; + + struct struct_simple source_simple2; + struct struct_simpleentry entry2; + + void *buffer1, *buffer2; + memcpy(&source_simple2, &source_simple1, sizeof(source_simple1)); + memcpy(&entry2, &entry1, sizeof(entry1)); + entry2.g2 = &source_simple2; + ssize_t len1 = struct_simpleentry_serialize(&entry1, &buffer1); + ssize_t len2 = struct_simpleentry_serialize(&entry2, &buffer2); + fail_unless(len1 > 0, "Unable to serialize"); + fail_unless(len2 > 0, "Unable to serialize"); + ck_assert_int_eq(len1, len2); + fail_unless(!memcmp(buffer1, buffer2, len1), "Same content should give the same serialization"); + free(buffer1); free(buffer2); +} +END_TEST + +Suite * +marshal_suite(void) +{ + Suite *s = suite_create("Marshalling"); + + TCase *tc_marshal = tcase_create("Marshalling"); + tcase_add_test(tc_marshal, test_simple_structure); + tcase_add_test(tc_marshal, test_substruct_structure); + tcase_add_test(tc_marshal, test_pointer_structure); + tcase_add_test(tc_marshal, test_several_pointers_structure); + tcase_add_test(tc_marshal, test_null_pointers); + tcase_add_test(tc_marshal, test_multiple_references); + tcase_add_test(tc_marshal, test_circular_references); + tcase_add_test(tc_marshal, test_too_small_unmarshal); + tcase_add_test(tc_marshal, test_simple_list); + tcase_add_test(tc_marshal, test_simple_repaired_list); + tcase_add_test(tc_marshal, test_empty_repaired_list); + tcase_add_test(tc_marshal, test_embedded_list); + tcase_add_test(tc_marshal, test_string); + tcase_add_test(tc_marshal, test_fixed_string); + tcase_add_test(tc_marshal, test_ignore); + tcase_add_test(tc_marshal, test_equality); + suite_add_tcase(s, tc_marshal); + + return s; +} + +int +main() +{ + int number_failed; + Suite *s = marshal_suite(); + SRunner *sr = srunner_create(s); + srunner_run_all(sr, CK_ENV); + number_failed = srunner_ntests_failed(sr); + srunner_free(sr); + return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/tests/check_pattern.c b/tests/check_pattern.c new file mode 100644 index 0000000000000000000000000000000000000000..5721c07969ceaa7f8f76e0a0f17ef5d7461d52d7 --- /dev/null +++ b/tests/check_pattern.c @@ -0,0 +1,139 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2014 Vincent Bernat + * + * 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 + +#include "../src/daemon/ub-lldpd.h" + +START_TEST(test_empty) { + ck_assert_int_eq(pattern_match("eth0", "", 0), 0); + ck_assert_int_eq(pattern_match("eth0", "", 1), 1); +} +END_TEST + +START_TEST(test_simple_match) { + ck_assert_int_eq(pattern_match("eth0", "eth0", 0), 2); + ck_assert_int_eq(pattern_match("eth0", "eth0", 1), 2); + ck_assert_int_eq(pattern_match("eth0", "eth1", 0), 0); + ck_assert_int_eq(pattern_match("eth0", "eth1", 1), 1); +} +END_TEST + +START_TEST(test_wildcard) { + ck_assert_int_eq(pattern_match("eth0", "eth*", 0), 1); + ck_assert_int_eq(pattern_match("eth0", "eth*", 1), 1); + ck_assert_int_eq(pattern_match("vlan0", "eth*", 0), 0); + ck_assert_int_eq(pattern_match("vlan0", "eth*", 1), 1); +} +END_TEST + +START_TEST(test_match_list) { + ck_assert_int_eq(pattern_match("eth0", "eth0,eth1,eth2", 0), 2); + ck_assert_int_eq(pattern_match("eth1", "eth0,eth1,eth2", 0), 2); + ck_assert_int_eq(pattern_match("eth3", "eth0,eth1,eth2", 0), 0); + ck_assert_int_eq(pattern_match("eth3", "eth0,eth1,eth2", 1), 1); +} +END_TEST + +START_TEST(test_match_list_with_wildcards) { + ck_assert_int_eq(pattern_match("eth0", "eth0,eth*,eth2", 0), 2); + ck_assert_int_eq(pattern_match("eth1", "eth0,eth*,eth2", 0), 1); + ck_assert_int_eq(pattern_match("eth2", "eth0,eth*,eth2", 0), 2); + ck_assert_int_eq(pattern_match("eth3", "eth0,eth*,eth2", 0), 1); + ck_assert_int_eq(pattern_match("vlan3", "eth0,eth*,eth2", 0), 0); + ck_assert_int_eq(pattern_match("vlan3", "eth0,eth*,eth2", 1), 1); +} +END_TEST + +START_TEST(test_simple_denylist) { + ck_assert_int_eq(pattern_match("eth0", "!eth0", 0), 0); + ck_assert_int_eq(pattern_match("eth0", "!eth0", 1), 0); + ck_assert_int_eq(pattern_match("eth1", "!eth0", 0), 0); + ck_assert_int_eq(pattern_match("eth1", "!eth0", 1), 1); +} +END_TEST + +START_TEST(test_match_and_denylist) { + ck_assert_int_eq(pattern_match("eth0", "eth0,!eth0", 0), 0); + ck_assert_int_eq(pattern_match("eth0", "eth0,!eth0", 1), 0); + ck_assert_int_eq(pattern_match("eth1", "eth0,!eth0", 0), 0); + ck_assert_int_eq(pattern_match("eth1", "eth0,!eth0", 1), 1); +} +END_TEST + +START_TEST(test_denylist_wildcard) { + ck_assert_int_eq(pattern_match("eth0", "!eth*", 0), 0); + ck_assert_int_eq(pattern_match("eth0", "!eth*", 1), 0); + ck_assert_int_eq(pattern_match("eth1", "!eth*", 0), 0); + ck_assert_int_eq(pattern_match("eth1", "!eth*", 1), 0); + ck_assert_int_eq(pattern_match("eth1", "eth*,!eth1", 0), 0); + ck_assert_int_eq(pattern_match("eth1", "eth*,!eth1", 1), 0); + ck_assert_int_eq(pattern_match("eth0", "eth*,!eth1", 0), 1); + ck_assert_int_eq(pattern_match("eth0", "eth*,!eth1", 1), 1); +} +END_TEST + +START_TEST(test_allowlist) { + ck_assert_int_eq(pattern_match("eth0", "!!eth0", 0), 2); + ck_assert_int_eq(pattern_match("eth0", "!!eth0", 1), 2); + ck_assert_int_eq(pattern_match("eth1", "!!eth0", 1), 1); + ck_assert_int_eq(pattern_match("eth0", "!eth*,!!eth0", 0), 2); + ck_assert_int_eq(pattern_match("eth0", "!eth*,!!eth0", 1), 2); + ck_assert_int_eq(pattern_match("eth1", "!eth*,!!eth0", 0), 0); + ck_assert_int_eq(pattern_match("eth1", "!eth*,!!eth0", 1), 0); + ck_assert_int_eq(pattern_match("vlan0", "*,!eth*,!!eth0", 0), 1); + ck_assert_int_eq(pattern_match("vlan0", "*,!eth*,!!eth0", 1), 1); + ck_assert_int_eq(pattern_match("eth0", "*,!eth*,!!eth0", 0), 2); + ck_assert_int_eq(pattern_match("eth0", "*,!eth*,!!eth0", 1), 2); + ck_assert_int_eq(pattern_match("eth1", "*,!eth*,!!eth0", 0), 0); + ck_assert_int_eq(pattern_match("eth1", "*,!!eth0,!eth*", 1), 0); + ck_assert_int_eq(pattern_match("eth0", "*,!!eth0,!eth*", 0), 2); + ck_assert_int_eq(pattern_match("eth0", "*,!!eth0,!eth*", 1), 2); +} +END_TEST + +Suite * +pattern_suite(void) +{ + Suite *s = suite_create("Pattern matching"); + + TCase *tc_pattern = tcase_create("Pattern matching"); + tcase_add_test(tc_pattern, test_empty); + tcase_add_test(tc_pattern, test_simple_match); + tcase_add_test(tc_pattern, test_wildcard); + tcase_add_test(tc_pattern, test_match_list); + tcase_add_test(tc_pattern, test_match_list_with_wildcards); + tcase_add_test(tc_pattern, test_simple_denylist); + tcase_add_test(tc_pattern, test_match_and_denylist); + tcase_add_test(tc_pattern, test_denylist_wildcard); + tcase_add_test(tc_pattern, test_allowlist); + suite_add_tcase(s, tc_pattern); + + return s; +} + +int +main() +{ + int number_failed; + Suite *s = pattern_suite(); + SRunner *sr = srunner_create(s); + srunner_run_all(sr, CK_ENV); + number_failed = srunner_ntests_failed(sr); + srunner_free(sr); + return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/tests/check_snmp.c b/tests/check_snmp.c new file mode 100644 index 0000000000000000000000000000000000000000..3836ae5dc39324d9a27d3edcf0ad21d623a7e7fa --- /dev/null +++ b/tests/check_snmp.c @@ -0,0 +1,1114 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2015 Vincent Bernat + * + * 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 + +#include "../src/daemon/lldpd.h" +#include "../src/daemon/agent.h" + +#include +#include +#include +#include + +extern struct lldpd *agent_scfg; +extern struct timeval starttime; +extern struct variable8 agent_lldp_vars[]; + +/* Our test config */ +struct lldpd test_cfg = { + .g_config = { + .c_tx_interval = 30000, + .c_tx_hold = 2, + .c_ttl = 60, + .c_smart = 0 + } +}; +struct timeval test_starttime = { .tv_sec = 100, .tv_usec = 0 }; + +/* First chassis */ +struct lldpd_mgmt mgmt1 = { + .m_family = LLDPD_AF_IPV4, + .m_addr = { .octets = { 0xc0, 0, 0x2, 0xf } }, /* 192.0.2.15 */ + .m_addrsize = sizeof(struct in_addr), + .m_iface = 3 +}; +struct lldpd_chassis chassis1 = { + .c_index = 1, + .c_protocol = LLDPD_MODE_LLDP, + .c_id_subtype = LLDP_CHASSISID_SUBTYPE_LLADDR, + .c_id = "AAA012", + .c_id_len = 6, + .c_name = "chassis1.example.com", + .c_descr = "First chassis", + .c_cap_available = LLDP_CAP_BRIDGE | LLDP_CAP_WLAN | LLDP_CAP_ROUTER, + .c_cap_enabled = LLDP_CAP_ROUTER, +#ifdef ENABLE_LLDPMED + .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, + .c_med_type = LLDP_MED_CLASS_II, + .c_med_hw = "Hardware 1", + /* We skip c_med_fw */ + .c_med_sw = "Software 1", + .c_med_sn = "00-00-0000-AAAA", + .c_med_manuf = "Manufacturer 1", + .c_med_model = "Model 1", + .c_med_asset = "Asset 1", +#endif +}; +/* Second chassis */ +struct lldpd_mgmt mgmt2 = { + .m_family = LLDPD_AF_IPV4, + .m_addr = { .octets = { 0xc0, 0, 0x2, 0x11 } }, /* 192.0.2.17 */ + .m_addrsize = sizeof(struct in_addr), + .m_iface = 5 +}; +struct lldpd_mgmt mgmt3 = { + .m_family = LLDPD_AF_IPV6, + .m_addr = { .octets = { 0x20, 0x01, 0x0d, 0xb8, + 0xca, 0xfe, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x17 } }, /* 2001:db8:cafe::17 */ + .m_addrsize = sizeof(struct in6_addr), + .m_iface = 5 +}; +struct lldpd_chassis chassis2 = { + .c_index = 4, + .c_protocol = LLDPD_MODE_LLDP, + .c_id_subtype = LLDP_CHASSISID_SUBTYPE_LOCAL, + .c_id = "chassis2", + .c_id_len = 6, + .c_name = "chassis2.example.com", + .c_descr = "Second chassis", + .c_cap_available = LLDP_CAP_ROUTER, + .c_cap_enabled = LLDP_CAP_ROUTER, +#ifdef ENABLE_LLDPMED + .c_med_hw = "Hardware 2", + /* We skip c_med_fw */ + .c_med_sw = "Software 2", + .c_med_sn = "00-00-0000-AAAC", + .c_med_manuf = "Manufacturer 2", + .c_med_model = "Model 2", + .c_med_asset = "Asset 2", +#endif +}; + +/* First port of first chassis */ +struct lldpd_hardware hardware1 = { + .h_ifindex = 3, + .h_tx_cnt = 1352, + .h_rx_cnt = 1458, + .h_rx_discarded_cnt = 5, + .h_rx_unrecognized_cnt = 4, + .h_insert_cnt = 100, + .h_delete_cnt = 5, + .h_ageout_cnt = 20, + .h_drop_cnt = 1, + .h_lport = { + .p_chassis = &chassis1, + .p_lastchange = 200, + .p_protocol = LLDPD_MODE_LLDP, + .p_id_subtype = LLDP_PORTID_SUBTYPE_LLADDR, + .p_id = "AAA012", + .p_id_len = 6, + .p_descr = "eth2", + .p_mfs = 1600, +#ifdef ENABLE_DOT3 + .p_aggregid = 0, + .p_macphy = { + .autoneg_support = 1, + .autoneg_enabled = 1, + .autoneg_advertised = LLDP_DOT3_LINK_AUTONEG_100BASE_TX | LLDP_DOT3_LINK_AUTONEG_100BASE_TXFD, + .mau_type = LLDP_DOT3_MAU_100BASETXFD, + }, + .p_power = { + .devicetype = LLDP_DOT3_POWER_PD, + .supported = 1, + .enabled = 1, + .paircontrol = 1, + .pairs = 2, + .class = 3, + .powertype = LLDP_DOT3_POWER_8023AT_TYPE2, + .source = LLDP_DOT3_POWER_SOURCE_BOTH, + .priority = LLDP_DOT3_POWER_PRIO_LOW, + .requested = 2000, + .allocated = 2500, + }, +#endif +#ifdef ENABLE_LLDPMED + .p_med_cap_enabled = LLDP_MED_CAP_CAP | LLDP_MED_CAP_IV | LLDP_MED_CAP_MDI_PD | + LLDP_MED_CAP_POLICY | LLDP_MED_CAP_LOCATION, + .p_med_policy = { + { .type = 0 }, { .type = 0 }, { + .type = LLDP_MED_APPTYPE_GUESTVOICE, + .unknown = 1, + .tagged = 1, + .vid = 475, + .priority = 3, + .dscp = 62 + }, { .type = 0 }, { .type = 0 }, { .type = 0 }, { + .type = LLDP_MED_APPTYPE_VIDEOSTREAM, + .unknown = 0, + .tagged = 1, + .vid = 472, + .priority = 1, + .dscp = 60 + }, { .type = 0 } + }, + .p_med_location = { + { .format = 0 }, { + .format = LLDP_MED_LOCFORMAT_CIVIC, + /* 2:FR:6:Commercial Rd:19:4 */ + .data = "\x15" "\x02" "FR" "\x06" "\x0d" "Commercial Rd" "\x13" "\x01" "4", + .data_len = 22, + }, { .format = 0 } + }, + .p_med_power = { + .devicetype = LLDP_MED_POW_TYPE_PD, + .source = LLDP_MED_POW_SOURCE_LOCAL, + .priority = LLDP_MED_POW_PRIO_HIGH, + .val = 100 + }, +#endif +#ifdef ENABLE_DOT1 + .p_pvid = 47, + /* Remaining is done is snmp_config */ +#endif + } +}; +/* Second port of first chassis */ +struct lldpd_hardware hardware2 = { + .h_ifindex = 4, + .h_tx_cnt = 11352, + .h_rx_cnt = 11458, + .h_rx_discarded_cnt = 55, + .h_rx_unrecognized_cnt = 14, + .h_insert_cnt = 1000, + .h_delete_cnt = 51, + .h_ageout_cnt = 210, + .h_drop_cnt = 1, + .h_lport = { + .p_chassis = &chassis1, + .p_lastchange = 50, + .p_protocol = LLDPD_MODE_LLDP, + .p_id_subtype = LLDP_PORTID_SUBTYPE_IFNAME, + .p_id = "eth4", + .p_id_len = 4, + .p_descr = "Intel 1000 GE", + .p_mfs = 9000, +#ifdef ENABLE_DOT3 + .p_aggregid = 3, + .p_macphy = { + .autoneg_support = 1, + .autoneg_enabled = 1, + .autoneg_advertised = LLDP_DOT3_LINK_AUTONEG_100BASE_TXFD | LLDP_DOT3_LINK_AUTONEG_1000BASE_TFD, + .mau_type = LLDP_DOT3_MAU_1000BASETFD, + }, +#endif +#ifdef ENABLE_LLDPMED + .p_med_cap_enabled = LLDP_MED_CAP_CAP | LLDP_MED_CAP_IV | LLDP_MED_CAP_MDI_PD | + LLDP_MED_CAP_MDI_PSE | LLDP_MED_CAP_POLICY | LLDP_MED_CAP_LOCATION, + .p_med_policy = { + { .type = 0 }, { .type = 0 }, { + .type = LLDP_MED_APPTYPE_GUESTVOICE, + .unknown = 1, + .tagged = 1, + .vid = 475, + .priority = 3, + .dscp = 62 + }, { .type = 0 }, { .type = 0 }, { + .type = LLDP_MED_APPTYPE_VIDEOCONFERENCE, + .unknown = 0, + .tagged = 0, + .vid = 1007, + .priority = 1, + .dscp = 49 + }, { .type = 0 }, { .type = 0 } + }, + .p_med_location = { + { + .format = LLDP_MED_LOCFORMAT_COORD, + .data = "Not interpreted", + .data_len = 15, + }, { .format = 0 }, { .format = 0 }, + }, +#endif + } +}; + +#ifdef ENABLE_CUSTOM +struct lldpd_custom custom1 = { + .oui = { 33, 44, 55 }, + .subtype = 44, + .oui_info = (u_int8_t*)"OUI content", +}; +struct lldpd_custom custom2 = { + .oui = { 33, 44, 55 }, + .subtype = 44, + .oui_info = (u_int8_t*)"More content", +}; +struct lldpd_custom custom3 = { + .oui = { 33, 44, 55 }, + .subtype = 45, + .oui_info = (u_int8_t*)"More more content", +}; +struct lldpd_custom custom4 = { + .oui = { 33, 44, 56 }, + .subtype = 44, + .oui_info = (u_int8_t*)"Even more content", +}; +struct lldpd_custom custom5 = { + .oui = { 33, 44, 55 }, + .subtype = 44, + .oui_info = (u_int8_t*)"Still more content", +}; +#endif + +#ifdef ENABLE_DOT1 +struct lldpd_vlan vlan47 = { + .v_name = "VLAN #47", + .v_vid = 47, +}; +struct lldpd_vlan vlan49 = { + .v_name = "VLAN #49", + .v_vid = 49, +}; +struct lldpd_vlan vlan1449 = { + .v_name = "VLAN #1449", + .v_vid = 1449, +}; +struct lldpd_ppvid ppvid47 = { + .p_cap_status = LLDP_PPVID_CAP_SUPPORTED | LLDP_PPVID_CAP_ENABLED, + .p_ppvid = 47, +}; +struct lldpd_ppvid ppvid118 = { + .p_cap_status = LLDP_PPVID_CAP_SUPPORTED | LLDP_PPVID_CAP_ENABLED, + .p_ppvid = 118, +}; +struct lldpd_pi pi88cc = { + .p_pi = "\x88\xcc", + .p_pi_len = 2, +}; +struct lldpd_pi pi888e01 = { + .p_pi = "\x88\x8e\x01", + .p_pi_len = 3, +}; +#endif + +/* First port of second chassis */ +struct lldpd_port port2 = { + .p_chassis = &chassis2, + .p_lastchange = 180, + .p_protocol = LLDPD_MODE_LLDP, + .p_id_subtype = LLDP_PORTID_SUBTYPE_IFALIAS, + .p_id = "Giga1/7", + .p_id_len = 7, + .p_descr = "Gigabit Ethernet 1/7", +}; + +void +snmp_config() +{ + starttime = test_starttime; + agent_scfg = &test_cfg; + TAILQ_INIT(&test_cfg.g_chassis); + TAILQ_INIT(&chassis1.c_mgmt); + TAILQ_INSERT_TAIL(&chassis1.c_mgmt, &mgmt1, m_entries); + TAILQ_INSERT_TAIL(&test_cfg.g_chassis, &chassis1, c_entries); + TAILQ_INIT(&chassis2.c_mgmt); + TAILQ_INSERT_TAIL(&chassis2.c_mgmt, &mgmt2, m_entries); + TAILQ_INSERT_TAIL(&chassis2.c_mgmt, &mgmt3, m_entries); + TAILQ_INSERT_TAIL(&test_cfg.g_chassis, &chassis2, c_entries); + TAILQ_INIT(&test_cfg.g_hardware); + TAILQ_INSERT_TAIL(&test_cfg.g_hardware, &hardware1, h_entries); + TAILQ_INSERT_TAIL(&test_cfg.g_hardware, &hardware2, h_entries); +#ifdef ENABLE_CUSTOM + custom1.oui_info_len = strlen((char*)custom1.oui_info); + custom2.oui_info_len = strlen((char*)custom2.oui_info); + custom3.oui_info_len = strlen((char*)custom3.oui_info); + custom4.oui_info_len = strlen((char*)custom4.oui_info); + custom5.oui_info_len = strlen((char*)custom5.oui_info); + TAILQ_INIT(&hardware1.h_lport.p_custom_list); + TAILQ_INIT(&hardware2.h_lport.p_custom_list); + TAILQ_INIT(&port2.p_custom_list); + TAILQ_INSERT_TAIL(&hardware2.h_lport.p_custom_list, &custom1, next); + TAILQ_INSERT_TAIL(&hardware2.h_lport.p_custom_list, &custom2, next); + TAILQ_INSERT_TAIL(&hardware2.h_lport.p_custom_list, &custom3, next); + TAILQ_INSERT_TAIL(&hardware2.h_lport.p_custom_list, &custom4, next); + TAILQ_INSERT_TAIL(&hardware1.h_lport.p_custom_list, &custom5, next); +#endif +#ifdef ENABLE_DOT1 + TAILQ_INIT(&hardware1.h_lport.p_vlans); + TAILQ_INSERT_TAIL(&hardware1.h_lport.p_vlans, &vlan47, v_entries); + TAILQ_INSERT_TAIL(&hardware1.h_lport.p_vlans, &vlan49, v_entries); + TAILQ_INSERT_TAIL(&hardware1.h_lport.p_vlans, &vlan1449, v_entries); + TAILQ_INIT(&hardware1.h_lport.p_ppvids); + TAILQ_INSERT_TAIL(&hardware1.h_lport.p_ppvids, &ppvid47, p_entries); + TAILQ_INSERT_TAIL(&hardware1.h_lport.p_ppvids, &ppvid118, p_entries); + TAILQ_INIT(&hardware1.h_lport.p_pids); + TAILQ_INSERT_TAIL(&hardware1.h_lport.p_pids, &pi88cc, p_entries); + TAILQ_INSERT_TAIL(&hardware1.h_lport.p_pids, &pi888e01, p_entries); + TAILQ_INIT(&hardware2.h_lport.p_vlans); + TAILQ_INIT(&hardware2.h_lport.p_ppvids); + TAILQ_INIT(&hardware2.h_lport.p_pids); + TAILQ_INIT(&port2.p_vlans); + TAILQ_INIT(&port2.p_ppvids); + TAILQ_INIT(&port2.p_pids); +#endif + TAILQ_INIT(&hardware1.h_rports); + TAILQ_INSERT_TAIL(&hardware1.h_rports, &port2, p_entries); + TAILQ_INSERT_TAIL(&hardware1.h_rports, &hardware2.h_lport, p_entries); + TAILQ_INIT(&hardware2.h_rports); + TAILQ_INSERT_TAIL(&hardware2.h_rports, &hardware1.h_lport, p_entries); +} + +/* Convert OID to a string. Static buffer. */ +char* +snmp_oidrepr(oid *name, size_t namelen) +{ + static char *buffer[4] = {NULL, NULL, NULL, NULL}; + static int current = 0; + size_t i; + + current = (current + 1)%4; + free(buffer[current]); buffer[current] = NULL; + + for (i = 0; i < namelen; i++) { + /* Not very efficient... */ + char *newbuffer = NULL; + if (asprintf(&newbuffer, "%s.%lu", buffer[current]?buffer[current]:"", + (unsigned long)name[i]) == -1) { + free(buffer[current]); + buffer[current] = NULL; + return NULL; + } + free(buffer[current]); + buffer[current] = newbuffer; + } + return buffer[current++]; +} + +struct tree_node { + oid name[MAX_OID_LEN]; + size_t namelen; + int type; /* ASN_* */ + union { + unsigned long int integer; + struct { + char *octet; + size_t len; + } string; + } value; +}; + +static oid zeroDotZero[2] = {0, 0}; +struct tree_node snmp_tree[] = { + { {1, 1, 1, 0}, 4, ASN_INTEGER, { .integer = 30 } }, /* lldpMessageTxInterval */ + { {1, 1, 2, 0}, 4, ASN_INTEGER, { .integer = 2 } }, /* lldpMessageTxHoldMultiplier */ + { {1, 1, 3, 0}, 4, ASN_INTEGER, { .integer = 1 } }, /* lldpReinitDelay */ + { {1, 1, 4, 0}, 4, ASN_INTEGER, { .integer = 1 } }, /* lldpTxDelay */ + { {1, 1, 5, 0}, 4, ASN_INTEGER, { .integer = 5 } }, /* lldpNotificationInterval */ + { {1, 2, 1, 0}, 4, ASN_TIMETICKS, { .integer = 10000 } },/* lldpStatsRemTablesLastChangeTime */ + { {1, 2, 2, 0}, 4, ASN_GAUGE, { .integer = 1100 } }, /* lldpStatsRemTablesInserts */ + { {1, 2, 3, 0}, 4, ASN_GAUGE, { .integer = 56 } }, /* lldpStatsRemTablesDeletes */ + { {1, 2, 4, 0}, 4, ASN_GAUGE, { .integer = 2 } }, /* lldpStatsRemTablesDrops */ + { {1, 2, 5, 0}, 4, ASN_GAUGE, { .integer = 230 } }, /* lldpStatsRemTablesAgeouts */ + + { {1, 2, 6, 1, 2, 3}, 6, ASN_COUNTER, { .integer = 1352 } }, /* lldpStatsTxPortFramesTotal.3 */ + { {1, 2, 6, 1, 2, 4}, 6, ASN_COUNTER, { .integer = 11352 } }, /* lldpStatsTxPortFramesTotal.4 */ + { {1, 2, 7, 1, 2, 3}, 6, ASN_COUNTER, { .integer = 5 } }, /* lldpStatsRxPortFramesDiscardedTotal.3 */ + { {1, 2, 7, 1, 2, 4}, 6, ASN_COUNTER, { .integer = 55 } }, /* lldpStatsRxPortFramesDiscardedTotal.4 */ + { {1, 2, 7, 1, 3, 3}, 6, ASN_COUNTER, { .integer = 5 } }, /* lldpStatsRxPortFramesError.3 */ + { {1, 2, 7, 1, 3, 4}, 6, ASN_COUNTER, { .integer = 55 } }, /* lldpStatsRxPortFramesError.4 */ + { {1, 2, 7, 1, 4, 3}, 6, ASN_COUNTER, { .integer = 1458 } }, /* lldpStatsRxPortFramesTotal.3 */ + { {1, 2, 7, 1, 4, 4}, 6, ASN_COUNTER, { .integer = 11458 } }, /* lldpStatsRxPortFramesTotal.4 */ + { {1, 2, 7, 1, 5, 3}, 6, ASN_COUNTER, { .integer = 4 } }, /* lldpStatsRxPortTLVsDiscardedTotal.3 */ + { {1, 2, 7, 1, 5, 4}, 6, ASN_COUNTER, { .integer = 14 } }, /* lldpStatsRxPortTLVsDiscardedTotal.4 */ + { {1, 2, 7, 1, 6, 3}, 6, ASN_COUNTER, { .integer = 4 } }, /* lldpStatsRxPortTLVsUnrecognizedTotal.3 */ + { {1, 2, 7, 1, 6, 4}, 6, ASN_COUNTER, { .integer = 14 } }, /* lldpStatsRxPortTLVsUnrecognizedTotal.4 */ + { {1, 2, 7, 1, 7, 3}, 6, ASN_GAUGE, { .integer = 20 } }, /* lldpStatsRxPortAgeoutsTotal.3 */ + { {1, 2, 7, 1, 7, 4}, 6, ASN_GAUGE, { .integer = 210 } }, /* lldpStatsRxPortAgeoutsTotal.4 */ + + { {1, 3, 1, 0}, 4, ASN_INTEGER, { .integer = 4 } }, /* lldpLocChassisIdSubtype */ + /* lldpLocChassisId */ + { {1, 3, 2, 0}, 4, ASN_OCTET_STR, { .string = { .octet = "AAA012", + .len = 6 } }}, + /* lldpLocSysName */ + { {1, 3, 3, 0}, 4, ASN_OCTET_STR, { .string = { .octet = "chassis1.example.com", + .len = 20 } }}, + /* lldpLocSysDesc */ + { {1, 3, 4, 0}, 4, ASN_OCTET_STR, { .string = { .octet = "First chassis", + .len = 13 } }}, + /* lldpLocSysCapSupported */ + { {1, 3, 5, 0}, 4, ASN_OCTET_STR, { .string = { .octet = "\x38", + .len = 1 } }}, + /* lldpLocSysCapEnabled */ + { {1, 3, 6, 0}, 4, ASN_OCTET_STR, { .string = { .octet = "\x8", + .len = 1 } }}, + + { {1, 3, 7, 1, 2, 3}, 6, ASN_INTEGER, { .integer = 3 } }, /* lldpLocPortIdSubtype.3 */ + { {1, 3, 7, 1, 2, 4}, 6, ASN_INTEGER, { .integer = 5 } }, /* lldpLocPortIdSubtype.5 */ + /* lldpLocPortId.3 */ + { {1, 3, 7, 1, 3, 3}, 6, ASN_OCTET_STR, { .string = { .octet = "AAA012", + .len = 6 } }}, + /* lldpLocPortId.4 */ + { {1, 3, 7, 1, 3, 4}, 6, ASN_OCTET_STR, { .string = { .octet = "eth4", + .len = 4 } }}, + /* lldpLocPortDesc.3 */ + { {1, 3, 7, 1, 4, 3}, 6, ASN_OCTET_STR, { .string = { .octet = "eth2", + .len = 4 } }}, + /* lldpLocPortDesc.4 */ + { {1, 3, 7, 1, 4, 4}, 6, ASN_OCTET_STR, { .string = { .octet = "Intel 1000 GE", + .len = 13 } }}, + + { {1, 3, 8, 1, 3, 1, 4, 192, 0, 2, 15}, 11, ASN_INTEGER, { .integer = 5 } }, /* lldpLocManAddrLen */ + { {1, 3, 8, 1, 4, 1, 4, 192, 0, 2, 15}, 11, ASN_INTEGER, { .integer = 2 } }, /* lldpLocManAddrIfSubtype */ + { {1, 3, 8, 1, 5, 1, 4, 192, 0, 2, 15}, 11, ASN_INTEGER, { .integer = 3 } }, /* lldpLocManAddrIfId */ + /* lldpLocManAddrOID */ + { {1, 3, 8, 1, 6, 1, 4, 192, 0, 2, 15}, 11, ASN_OBJECT_ID, + { .string = { .octet = (char *)zeroDotZero, + .len = sizeof(zeroDotZero) }} }, + + /* lldpRemChassisIdSubtype */ + { {1, 4, 1, 1, 4, 0, 3, 1 }, 8, ASN_INTEGER, { .integer = 4 } }, + { {1, 4, 1, 1, 4, 8000, 3, 4}, 8, ASN_INTEGER, { .integer = 7 } }, + { {1, 4, 1, 1, 4, 10000, 4, 1}, 8, ASN_INTEGER, { .integer = 4 } }, + /* lldpRemChassisId */ + { {1, 4, 1, 1, 5, 0, 3, 1 }, 8, ASN_OCTET_STR, { .string = { .octet = "AAA012", .len = 6 }} }, + { {1, 4, 1, 1, 5, 8000, 3, 4}, 8, ASN_OCTET_STR, { .string = + { .octet = "chassis2", + .len = 6 }} }, + { {1, 4, 1, 1, 5, 10000, 4, 1}, 8, ASN_OCTET_STR, { .string = { .octet = "AAA012", .len = 6 }} }, + /* lldpRemPortIdSubtype */ + { {1, 4, 1, 1, 6, 0, 3, 1 }, 8, ASN_INTEGER, { .integer = 5 } }, + { {1, 4, 1, 1, 6, 8000, 3, 4}, 8, ASN_INTEGER, { .integer = 1 } }, + { {1, 4, 1, 1, 6, 10000, 4, 1}, 8, ASN_INTEGER, { .integer = 3 } }, + /* lldpRemPortId */ + { {1, 4, 1, 1, 7, 0, 3, 1 }, 8, ASN_OCTET_STR, { .string = { .octet = "eth4", .len = 4 }} }, + { {1, 4, 1, 1, 7, 8000, 3, 4}, 8, ASN_OCTET_STR, { .string = + { .octet = "Giga1/7", .len = 7 }} }, + { {1, 4, 1, 1, 7, 10000, 4, 1}, 8, ASN_OCTET_STR, { .string = { .octet = "AAA012", .len = 6 }} }, + /* lldpRemPortDesc */ + { {1, 4, 1, 1, 8, 0, 3, 1 }, 8, ASN_OCTET_STR, + { .string = { .octet = "Intel 1000 GE", .len = 13 }} }, + { {1, 4, 1, 1, 8, 8000, 3, 4}, 8, ASN_OCTET_STR, + { .string = { .octet = "Gigabit Ethernet 1/7", .len = 20 }} }, + { {1, 4, 1, 1, 8, 10000, 4, 1}, 8, ASN_OCTET_STR, + { .string = { .octet = "eth2", .len = 4 }} }, + /* lldpRemSysName */ + { {1, 4, 1, 1, 9, 0, 3, 1 }, 8, ASN_OCTET_STR, + { .string = { .octet = "chassis1.example.com", .len = 20 }} }, + { {1, 4, 1, 1, 9, 8000, 3, 4}, 8, ASN_OCTET_STR, + { .string = { .octet = "chassis2.example.com", .len = 20 }} }, + { {1, 4, 1, 1, 9, 10000, 4, 1}, 8, ASN_OCTET_STR, + { .string = { .octet = "chassis1.example.com", .len = 20 }} }, + /* lldpRemSysDesc */ + { {1, 4, 1, 1, 10, 0, 3, 1 }, 8, ASN_OCTET_STR, + { .string = { .octet = "First chassis", .len = 13 }} }, + { {1, 4, 1, 1, 10, 8000, 3, 4}, 8, ASN_OCTET_STR, + { .string = { .octet = "Second chassis", .len = 14 }} }, + { {1, 4, 1, 1, 10, 10000, 4, 1}, 8, ASN_OCTET_STR, + { .string = { .octet = "First chassis", .len = 13 }} }, + /* lldpRemSysCapSupported */ + { {1, 4, 1, 1, 11, 0, 3, 1 }, 8, ASN_OCTET_STR, + { .string = { .octet = "\x38", .len = 1 }} }, + { {1, 4, 1, 1, 11, 8000, 3, 4}, 8, ASN_OCTET_STR, + { .string = { .octet = "\x8", .len = 1 }} }, + { {1, 4, 1, 1, 11, 10000, 4, 1}, 8, ASN_OCTET_STR, + { .string = { .octet = "\x38", .len = 1 }} }, + /* lldpRemSysCapEnabled */ + { {1, 4, 1, 1, 12, 0, 3, 1 }, 8, ASN_OCTET_STR, + { .string = { .octet = "\x8", .len = 1 }} }, + { {1, 4, 1, 1, 12, 8000, 3, 4}, 8, ASN_OCTET_STR, + { .string = { .octet = "\x8", .len = 1 }} }, + { {1, 4, 1, 1, 12, 10000, 4, 1}, 8, ASN_OCTET_STR, + { .string = { .octet = "\x8", .len = 1 }} }, + + /* lldpRemManAddrIfSubtype */ + { {1, 4, 2, 1, 3, 0, 3, 1, 1, 4, 192, 0, 2, 15 }, 14, ASN_INTEGER, { .integer = 2 } }, + { {1, 4, 2, 1, 3, 8000, 3, 4, 1, 4, 192, 0, 2, 17 }, 14, ASN_INTEGER, { .integer = 2 } }, + { {1, 4, 2, 1, 3, 8000, 3, 4, 2, 16, + 0x20, 0x01, 0x0d, 0xb8, 0xca, 0xfe, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x17 }, 26, ASN_INTEGER, { .integer = 2 } }, + { {1, 4, 2, 1, 3, 10000, 4, 1, 1, 4, 192, 0, 2, 15 }, 14, ASN_INTEGER, { .integer = 2 } }, + /* lldpRemManAddrIfId */ + { {1, 4, 2, 1, 4, 0, 3, 1, 1, 4, 192, 0, 2, 15 }, 14, ASN_INTEGER, { .integer = 3 } }, + { {1, 4, 2, 1, 4, 8000, 3, 4, 1, 4, 192, 0, 2, 17 }, 14, ASN_INTEGER, { .integer = 5 } }, + { {1, 4, 2, 1, 4, 8000, 3, 4, 2, 16, + 0x20, 0x01, 0x0d, 0xb8, 0xca, 0xfe, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x17 }, 26, ASN_INTEGER, { .integer = 5 } }, + { {1, 4, 2, 1, 4, 10000, 4, 1, 1, 4, 192, 0, 2, 15 }, 14, ASN_INTEGER, { .integer = 3 } }, + /* lldpRemManAddrOID */ + { {1, 4, 2, 1, 5, 0, 3, 1, 1, 4, 192, 0, 2, 15 }, 14, ASN_OBJECT_ID, + { .string = { .octet = (char *)zeroDotZero, + .len = sizeof(zeroDotZero) }} }, + { {1, 4, 2, 1, 5, 8000, 3, 4, 1, 4, 192, 0, 2, 17 }, 14, ASN_OBJECT_ID, + { .string = { .octet = (char *)zeroDotZero, + .len = sizeof(zeroDotZero) }} }, + { {1, 4, 2, 1, 5, 8000, 3, 4, 2, 16, + 0x20, 0x01, 0x0d, 0xb8, 0xca, 0xfe, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x17 }, 26, ASN_OBJECT_ID, + { .string = { .octet = (char *)zeroDotZero, + .len = sizeof(zeroDotZero) }} }, + { {1, 4, 2, 1, 5, 10000, 4, 1, 1, 4, 192, 0, 2, 15 }, 14, ASN_OBJECT_ID, + { .string = { .octet = (char *)zeroDotZero, + .len = sizeof(zeroDotZero) }} }, + +#ifdef ENABLE_CUSTOM + /* lldpRemOrgDefInfo */ + { {1, 4, 4, 1, 4, 0, 3, 1, 33, 44, 55, 44, 1 }, 13, ASN_OCTET_STR, + { .string = { .octet = "OUI content", .len = 11 }} }, + { {1, 4, 4, 1, 4, 0, 3, 1, 33, 44, 55, 44, 2 }, 13, ASN_OCTET_STR, + { .string = { .octet = "More content", .len = 12 }} }, + { {1, 4, 4, 1, 4, 0, 3, 1, 33, 44, 55, 45, 3 }, 13, ASN_OCTET_STR, + { .string = { .octet = "More more content", .len = 17 }} }, + { {1, 4, 4, 1, 4, 0, 3, 1, 33, 44, 56, 44, 4 }, 13, ASN_OCTET_STR, + { .string = { .octet = "Even more content", .len = 17 }} }, + { {1, 4, 4, 1, 4, 10000, 4, 1, 33, 44, 55, 44, 1 }, 13, ASN_OCTET_STR, + { .string = { .octet = "Still more content", .len = 18 }} }, +#endif + +#ifdef ENABLE_DOT3 + /* lldpXdot3LocPortAutoNegSupported */ + { {1, 5, 4623, 1, 2, 1, 1, 1, 3 }, 9, ASN_INTEGER, { .integer = 1 }}, + { {1, 5, 4623, 1, 2, 1, 1, 1, 4 }, 9, ASN_INTEGER, { .integer = 1 }}, + /* lldpXdot3LocPortAutoNegEnabled */ + { {1, 5, 4623, 1, 2, 1, 1, 2, 3 }, 9, ASN_INTEGER, { .integer = 1 }}, + { {1, 5, 4623, 1, 2, 1, 1, 2, 4 }, 9, ASN_INTEGER, { .integer = 1 }}, + /* lldpXdot3LocPortAutoNegAdvertisedCap */ + { {1, 5, 4623, 1, 2, 1, 1, 3, 3 }, 9, ASN_OCTET_STR, + { .string = { .octet = "\x0c\x00", .len = 2 }} }, + { {1, 5, 4623, 1, 2, 1, 1, 3, 4 }, 9, ASN_OCTET_STR, + { .string = { .octet = "\x04\x01", .len = 2 }} }, + /* lldpXdot3LocPortOperMauType */ + { {1, 5, 4623, 1, 2, 1, 1, 4, 3 }, 9, ASN_INTEGER, { .integer = 16 }}, + { {1, 5, 4623, 1, 2, 1, 1, 4, 4 }, 9, ASN_INTEGER, { .integer = 30 }}, + + /* lldpXdot3LocPowerPortClass */ + { {1, 5, 4623, 1, 2, 2, 1, 1, 3 }, 9, ASN_INTEGER, { .integer = 2 }}, + /* lldpXdot3LocPowerMDISupported */ + { {1, 5, 4623, 1, 2, 2, 1, 2, 3 }, 9, ASN_INTEGER, { .integer = 1 }}, + /* lldpXdot3LocPowerMDIEnabled */ + { {1, 5, 4623, 1, 2, 2, 1, 3, 3 }, 9, ASN_INTEGER, { .integer = 1 }}, + /* lldpXdot3LocPowerPairControlable */ + { {1, 5, 4623, 1, 2, 2, 1, 4, 3 }, 9, ASN_INTEGER, { .integer = 1 }}, + /* lldpXdot3LocPowerPairs */ + { {1, 5, 4623, 1, 2, 2, 1, 5, 3 }, 9, ASN_INTEGER, { .integer = 2 }}, + /* lldpXdot3LocPowerClass */ + { {1, 5, 4623, 1, 2, 2, 1, 6, 3 }, 9, ASN_INTEGER, { .integer = 3 }}, + /* As per 802.3at-2009, not sure of the OID... */ + /* lldpXdot3LocPowerType */ + { {1, 5, 4623, 1, 2, 2, 1, 7, 3 }, 9, ASN_OCTET_STR, + { .string = { .octet = "\xC0", .len = 1 } }}, + /* lldpXdot3LocPowerSource */ + { {1, 5, 4623, 1, 2, 2, 1, 8, 3 }, 9, ASN_OCTET_STR, + { .string = { .octet = "\xC0", .len = 1 } }}, + /* lldpXdot3LocPowerPriority */ + { {1, 5, 4623, 1, 2, 2, 1, 9, 3 }, 9, ASN_INTEGER, { .integer = 1 }}, + /* lldpXdot3LocPDRequestedPowerValue */ + { {1, 5, 4623, 1, 2, 2, 1, 10, 3 }, 9, ASN_INTEGER, { .integer = 2000 }}, + /* lldpXdot3LocPSEAllocatedPowerValue */ + { {1, 5, 4623, 1, 2, 2, 1, 11, 3 }, 9, ASN_INTEGER, { .integer = 2500 }}, + + /* lldpXdot3LocLinkAggStatus */ + { {1, 5, 4623, 1, 2, 3, 1, 1, 3 }, 9, ASN_OCTET_STR, + { .string = { .octet = "\x00", .len = 1 }} }, + { {1, 5, 4623, 1, 2, 3, 1, 1, 4 }, 9, ASN_OCTET_STR, + { .string = { .octet = "\xC0", .len = 1 }} }, + /* lldpXdot3LocLinkAggPortId */ + { {1, 5, 4623, 1, 2, 3, 1, 2, 3 }, 9, ASN_INTEGER, { .integer = 0 }}, + { {1, 5, 4623, 1, 2, 3, 1, 2, 4 }, 9, ASN_INTEGER, { .integer = 3 }}, + + /* lldpXdot3LocMaxFrameSize */ + { {1, 5, 4623, 1, 2, 4, 1, 1, 3 }, 9, ASN_INTEGER, { .integer = 1600 }}, + { {1, 5, 4623, 1, 2, 4, 1, 1, 4 }, 9, ASN_INTEGER, { .integer = 9000 }}, + + /* lldpXdot3RemPortAutoNegSupported */ + { {1, 5, 4623, 1, 3, 1, 1, 1, 0, 3, 1 }, 11, ASN_INTEGER, { .integer = 1 }}, + { {1, 5, 4623, 1, 3, 1, 1, 1, 8000, 3, 4 }, 11, ASN_INTEGER, { .integer = 2 }}, + { {1, 5, 4623, 1, 3, 1, 1, 1, 10000, 4, 1 }, 11, ASN_INTEGER, { .integer = 1 }}, + /* lldpXdot3RemPortAutoNegEnabled */ + { {1, 5, 4623, 1, 3, 1, 1, 2, 0, 3, 1 }, 11, ASN_INTEGER, { .integer = 1 }}, + { {1, 5, 4623, 1, 3, 1, 1, 2, 8000, 3, 4 }, 11, ASN_INTEGER, { .integer = 2 }}, + { {1, 5, 4623, 1, 3, 1, 1, 2, 10000, 4, 1 }, 11, ASN_INTEGER, { .integer = 1 }}, + /* lldpXdot3RemPortAutoNegAdvertisedCap */ + { {1, 5, 4623, 1, 3, 1, 1, 3, 0, 3, 1 }, 11, ASN_OCTET_STR, + { .string = { .octet = "\x04\x01", .len = 2 }} }, + { {1, 5, 4623, 1, 3, 1, 1, 3, 8000, 3, 4 }, 11, ASN_OCTET_STR, + { .string = { .octet = "\x00\x00", .len = 2 }} }, + { {1, 5, 4623, 1, 3, 1, 1, 3, 10000, 4, 1 }, 11, ASN_OCTET_STR, + { .string = { .octet = "\x0c\x00", .len = 2 }} }, + /* lldpXdot3RemPortOperMauType */ + { {1, 5, 4623, 1, 3, 1, 1, 4, 0, 3, 1 }, 11, ASN_INTEGER, { .integer = 30 }}, + { {1, 5, 4623, 1, 3, 1, 1, 4, 8000, 3, 4 }, 11, ASN_INTEGER, { .integer = 0 }}, + { {1, 5, 4623, 1, 3, 1, 1, 4, 10000, 4, 1 }, 11, ASN_INTEGER, { .integer = 16 }}, + + /* lldpXdot3RemPowerPortClass */ + { {1, 5, 4623, 1, 3, 2, 1, 1, 10000, 4, 1 }, 11, ASN_INTEGER, { .integer = 2 }}, + /* lldpXdot3RemPowerMDISupported */ + { {1, 5, 4623, 1, 3, 2, 1, 2, 10000, 4, 1 }, 11, ASN_INTEGER, { .integer = 1 }}, + /* lldpXdot3RemPowerMDIEnabled */ + { {1, 5, 4623, 1, 3, 2, 1, 3, 10000, 4, 1 }, 11, ASN_INTEGER, { .integer = 1 }}, + /* lldpXdot3RemPowerPairControlable */ + { {1, 5, 4623, 1, 3, 2, 1, 4, 10000, 4, 1 }, 11, ASN_INTEGER, { .integer = 1 }}, + /* lldpXdot3RemPowerPairs */ + { {1, 5, 4623, 1, 3, 2, 1, 5, 10000, 4, 1 }, 11, ASN_INTEGER, { .integer = 2 }}, + /* lldpXdot3RemPowerClass */ + { {1, 5, 4623, 1, 3, 2, 1, 6, 10000, 4, 1 }, 11, ASN_INTEGER, { .integer = 3 }}, + /* As per 802.3at-2009, not sure of the OID... */ + /* lldpXdot3RemPowerType */ + { {1, 5, 4623, 1, 3, 2, 1, 7, 10000, 4, 1 }, 11, ASN_OCTET_STR, + { .string = { .octet = "\xC0", .len = 1 } }}, + /* lldpXdot3RemPowerSource */ + { {1, 5, 4623, 1, 3, 2, 1, 8, 10000, 4, 1 }, 11, ASN_OCTET_STR, + { .string = { .octet = "\xC0", .len = 1 } }}, + /* lldpXdot3RemPowerPriority */ + { {1, 5, 4623, 1, 3, 2, 1, 9, 10000, 4, 1 }, 11, ASN_INTEGER, { .integer = 1 }}, + /* lldpXdot3RemPDRequestedPowerValue */ + { {1, 5, 4623, 1, 3, 2, 1, 10, 10000, 4, 1 }, 11, ASN_INTEGER, { .integer = 2000 }}, + /* lldpXdot3RemPSEAllocatedPowerValue */ + { {1, 5, 4623, 1, 3, 2, 1, 11, 10000, 4, 1 }, 11, ASN_INTEGER, { .integer = 2500 }}, + + /* lldpXdot3RemLinkAggStatus */ + { {1, 5, 4623, 1, 3, 3, 1, 1, 0, 3, 1 }, 11, ASN_OCTET_STR, + { .string = { .octet = "\xC0", .len = 1 }} }, + { {1, 5, 4623, 1, 3, 3, 1, 1, 8000, 3, 4 }, 11, ASN_OCTET_STR, + { .string = { .octet = "\x00", .len = 1 }} }, + { {1, 5, 4623, 1, 3, 3, 1, 1, 10000, 4, 1 }, 11, ASN_OCTET_STR, + { .string = { .octet = "\x00", .len = 1 }} }, + /* lldpXdot3RemLinkAggPortId */ + { {1, 5, 4623, 1, 3, 3, 1, 2, 0, 3, 1 }, 11, ASN_INTEGER, { .integer = 3 }}, + { {1, 5, 4623, 1, 3, 3, 1, 2, 8000, 3, 4 }, 11, ASN_INTEGER, { .integer = 0 }}, + { {1, 5, 4623, 1, 3, 3, 1, 2, 10000, 4, 1 }, 11, ASN_INTEGER, { .integer = 0 }}, + + /* lldpXdot3RemMaxFrameSize */ + { {1, 5, 4623, 1, 3, 4, 1, 1, 0, 3, 1 }, 11, ASN_INTEGER, { .integer = 9000 }}, + { {1, 5, 4623, 1, 3, 4, 1, 1, 10000, 4, 1 }, 11, ASN_INTEGER, { .integer = 1600 }}, +#endif +#ifdef ENABLE_LLDPMED + /* lldpXMedLocDeviceClass */ + { {1, 5, 4795, 1, 1, 1, 0 }, 7, ASN_INTEGER, { .integer = 2 }}, + + /* lldpXMedLocMediaPolicyVlanID */ + { {1, 5, 4795, 1, 2, 1, 1, 2, 3, 3 }, 10, ASN_INTEGER, { .integer = 475 }}, + { {1, 5, 4795, 1, 2, 1, 1, 2, 3, 7 }, 10, ASN_INTEGER, { .integer = 472 }}, + { {1, 5, 4795, 1, 2, 1, 1, 2, 4, 3 }, 10, ASN_INTEGER, { .integer = 475 }}, + { {1, 5, 4795, 1, 2, 1, 1, 2, 4, 6 }, 10, ASN_INTEGER, { .integer = 1007 }}, + /* lldpXMedLocMediaPolicyPriority */ + { {1, 5, 4795, 1, 2, 1, 1, 3, 3, 3 }, 10, ASN_INTEGER, { .integer = 3 }}, + { {1, 5, 4795, 1, 2, 1, 1, 3, 3, 7 }, 10, ASN_INTEGER, { .integer = 1 }}, + { {1, 5, 4795, 1, 2, 1, 1, 3, 4, 3 }, 10, ASN_INTEGER, { .integer = 3 }}, + { {1, 5, 4795, 1, 2, 1, 1, 3, 4, 6 }, 10, ASN_INTEGER, { .integer = 1 }}, + /* lldpXMedLocMediaPolicyDscp */ + { {1, 5, 4795, 1, 2, 1, 1, 4, 3, 3 }, 10, ASN_INTEGER, { .integer = 62 }}, + { {1, 5, 4795, 1, 2, 1, 1, 4, 3, 7 }, 10, ASN_INTEGER, { .integer = 60 }}, + { {1, 5, 4795, 1, 2, 1, 1, 4, 4, 3 }, 10, ASN_INTEGER, { .integer = 62 }}, + { {1, 5, 4795, 1, 2, 1, 1, 4, 4, 6 }, 10, ASN_INTEGER, { .integer = 49 }}, + /* lldpXMedLocMediaPolicyUnknown */ + { {1, 5, 4795, 1, 2, 1, 1, 5, 3, 3 }, 10, ASN_INTEGER, { .integer = 1 }}, + { {1, 5, 4795, 1, 2, 1, 1, 5, 3, 7 }, 10, ASN_INTEGER, { .integer = 2 }}, + { {1, 5, 4795, 1, 2, 1, 1, 5, 4, 3 }, 10, ASN_INTEGER, { .integer = 1 }}, + { {1, 5, 4795, 1, 2, 1, 1, 5, 4, 6 }, 10, ASN_INTEGER, { .integer = 2 }}, + /* lldpXMedLocMediaPolicyTagged */ + { {1, 5, 4795, 1, 2, 1, 1, 6, 3, 3 }, 10, ASN_INTEGER, { .integer = 1 }}, + { {1, 5, 4795, 1, 2, 1, 1, 6, 3, 7 }, 10, ASN_INTEGER, { .integer = 1 }}, + { {1, 5, 4795, 1, 2, 1, 1, 6, 4, 3 }, 10, ASN_INTEGER, { .integer = 1 }}, + { {1, 5, 4795, 1, 2, 1, 1, 6, 4, 6 }, 10, ASN_INTEGER, { .integer = 2 }}, + + /* lldpXMedLocHardwareRev */ + { {1, 5, 4795, 1, 2, 2, 0 }, 7, ASN_OCTET_STR, + { .string = { .octet = "Hardware 1", .len = 10 }} }, + /* lldpXMedLocSoftwareRev */ + { {1, 5, 4795, 1, 2, 4, 0 }, 7, ASN_OCTET_STR, + { .string = { .octet = "Software 1", .len = 10 }} }, + /* lldpXMedLocSerialNum */ + { {1, 5, 4795, 1, 2, 5, 0 }, 7, ASN_OCTET_STR, + { .string = { .octet = "00-00-0000-AAAA", .len = 15 }} }, + /* lldpXMedLocMfgName */ + { {1, 5, 4795, 1, 2, 6, 0 }, 7, ASN_OCTET_STR, + { .string = { .octet = "Manufacturer 1", .len = 14 }} }, + /* lldpXMedLocModelName */ + { {1, 5, 4795, 1, 2, 7, 0 }, 7, ASN_OCTET_STR, + { .string = { .octet = "Model 1", .len = 7 }} }, + /* lldpXMedLocAssetID */ + { {1, 5, 4795, 1, 2, 8, 0 }, 7, ASN_OCTET_STR, + { .string = { .octet = "Asset 1", .len = 7 }} }, + + /* lldpXMedLocLocationInfo */ + { {1, 5, 4795, 1, 2, 9, 1, 2, 3, 3}, 10, ASN_OCTET_STR, + { .string = { .octet = "\x15" "\x02" "FR" "\x06" "\x0d" "Commercial Rd" "\x13" "\x01" "4", .len = 22 }} }, + { {1, 5, 4795, 1, 2, 9, 1, 2, 4, 2}, 10, ASN_OCTET_STR, + { .string = { .octet = "Not interpreted", .len = 15 }} }, + + /* lldpXMedLocXPoEDeviceType */ + { {1, 5, 4795, 1, 2, 10, 0 }, 7, ASN_INTEGER, { .integer = 3 }}, + /* lldpXMedLocXPoEPDPowerReq */ + { {1, 5, 4795, 1, 2, 13, 0 }, 7, ASN_GAUGE, { .integer = 100 }}, + /* lldpXMedLocXPoEPDPowerSource */ + { {1, 5, 4795, 1, 2, 14, 0 }, 7, ASN_INTEGER, { .integer = 3 }}, + /* lldpXMedLocXPoEPDPowerPriority */ + { {1, 5, 4795, 1, 2, 15, 0 }, 7, ASN_INTEGER, { .integer = 3 }}, + + /* lldpXMedRemCapSupported */ + { {1, 5, 4795, 1, 3, 1, 1, 1, 0, 3, 1 }, 11, ASN_OCTET_STR, + { .string = { .octet = "\xFC", .len = 1 }} }, + { {1, 5, 4795, 1, 3, 1, 1, 1, 10000, 4, 1 }, 11, ASN_OCTET_STR, + { .string = { .octet = "\xFC", .len = 1 }}}, + /* lldpXMedRemCapCurrent */ + { {1, 5, 4795, 1, 3, 1, 1, 2, 0, 3, 1 }, 11, ASN_OCTET_STR, + { .string = { .octet = "\xFC", .len = 1 }} }, + { {1, 5, 4795, 1, 3, 1, 1, 2, 10000, 4, 1 }, 11, ASN_OCTET_STR, + { .string = { .octet = "\xEC", .len = 1 }}}, + /* lldpXMedRemDeviceClass */ + { {1, 5, 4795, 1, 3, 1, 1, 3, 0, 3, 1 }, 11, ASN_INTEGER, { .integer = 2 }}, + { {1, 5, 4795, 1, 3, 1, 1, 3, 10000, 4, 1 }, 11, ASN_INTEGER, { .integer = 2 }}, + + /* lldpXMedRemMediaPolicyVlanID */ + { {1, 5, 4795, 1, 3, 2, 1, 2, 0, 3, 1, 3 }, 12, ASN_INTEGER, { .integer = 475 }}, + { {1, 5, 4795, 1, 3, 2, 1, 2, 0, 3, 1, 6 }, 12, ASN_INTEGER, { .integer = 1007 }}, + { {1, 5, 4795, 1, 3, 2, 1, 2, 10000, 4, 1, 3 }, 12, ASN_INTEGER, { .integer = 475 }}, + { {1, 5, 4795, 1, 3, 2, 1, 2, 10000, 4, 1, 7 }, 12, ASN_INTEGER, { .integer = 472 }}, + /* lldpXMedRemMediaPolicyPriority */ + { {1, 5, 4795, 1, 3, 2, 1, 3, 0, 3, 1, 3 }, 12, ASN_INTEGER, { .integer = 3 }}, + { {1, 5, 4795, 1, 3, 2, 1, 3, 0, 3, 1, 6 }, 12, ASN_INTEGER, { .integer = 1 }}, + { {1, 5, 4795, 1, 3, 2, 1, 3, 10000, 4, 1, 3 }, 12, ASN_INTEGER, { .integer = 3 }}, + { {1, 5, 4795, 1, 3, 2, 1, 3, 10000, 4, 1, 7 }, 12, ASN_INTEGER, { .integer = 1 }}, + /* lldpXMedLocMediaPolicyDscp */ + { {1, 5, 4795, 1, 3, 2, 1, 4, 0, 3, 1, 3 }, 12, ASN_INTEGER, { .integer = 62 }}, + { {1, 5, 4795, 1, 3, 2, 1, 4, 0, 3, 1, 6 }, 12, ASN_INTEGER, { .integer = 49 }}, + { {1, 5, 4795, 1, 3, 2, 1, 4, 10000, 4, 1, 3 }, 12, ASN_INTEGER, { .integer = 62 }}, + { {1, 5, 4795, 1, 3, 2, 1, 4, 10000, 4, 1, 7 }, 12, ASN_INTEGER, { .integer = 60 }}, + /* lldpXMedLocMediaPolicyUnknown */ + { {1, 5, 4795, 1, 3, 2, 1, 5, 0, 3, 1, 3 }, 12, ASN_INTEGER, { .integer = 1 }}, + { {1, 5, 4795, 1, 3, 2, 1, 5, 0, 3, 1, 6 }, 12, ASN_INTEGER, { .integer = 2 }}, + { {1, 5, 4795, 1, 3, 2, 1, 5, 10000, 4, 1, 3 }, 12, ASN_INTEGER, { .integer = 1 }}, + { {1, 5, 4795, 1, 3, 2, 1, 5, 10000, 4, 1, 7 }, 12, ASN_INTEGER, { .integer = 2 }}, + /* lldpXMedLocMediaPolicyTagged */ + { {1, 5, 4795, 1, 3, 2, 1, 6, 0, 3, 1, 3 }, 12, ASN_INTEGER, { .integer = 1 }}, + { {1, 5, 4795, 1, 3, 2, 1, 6, 0, 3, 1, 6 }, 12, ASN_INTEGER, { .integer = 2 }}, + { {1, 5, 4795, 1, 3, 2, 1, 6, 10000, 4, 1, 3 }, 12, ASN_INTEGER, { .integer = 1 }}, + { {1, 5, 4795, 1, 3, 2, 1, 6, 10000, 4, 1, 7 }, 12, ASN_INTEGER, { .integer = 1 }}, + + /* lldpXMedRemHardwareRev */ + { {1, 5, 4795, 1, 3, 3, 1, 1, 0, 3, 1 }, 11, ASN_OCTET_STR, + { .string = { .octet = "Hardware 1", .len = 10 }} }, + { {1, 5, 4795, 1, 3, 3, 1, 1, 10000, 4, 1 }, 11, ASN_OCTET_STR, + { .string = { .octet = "Hardware 1", .len = 10 }} }, + /* lldpXMedRemSoftwareRev */ + { {1, 5, 4795, 1, 3, 3, 1, 3, 0, 3, 1 }, 11, ASN_OCTET_STR, + { .string = { .octet = "Software 1", .len = 10 }} }, + { {1, 5, 4795, 1, 3, 3, 1, 3, 10000, 4, 1 }, 11, ASN_OCTET_STR, + { .string = { .octet = "Software 1", .len = 10 }} }, + /* lldpXMedRemSerialNum */ + { {1, 5, 4795, 1, 3, 3, 1, 4, 0, 3, 1 }, 11, ASN_OCTET_STR, + { .string = { .octet = "00-00-0000-AAAA", .len = 15 }} }, + { {1, 5, 4795, 1, 3, 3, 1, 4, 10000, 4, 1 }, 11, ASN_OCTET_STR, + { .string = { .octet = "00-00-0000-AAAA", .len = 15 }} }, + /* lldpXMedRemMfgName */ + { {1, 5, 4795, 1, 3, 3, 1, 5, 0, 3, 1 }, 11, ASN_OCTET_STR, + { .string = { .octet = "Manufacturer 1", .len = 14 }} }, + { {1, 5, 4795, 1, 3, 3, 1, 5, 10000, 4, 1 }, 11, ASN_OCTET_STR, + { .string = { .octet = "Manufacturer 1", .len = 14 }} }, + /* lldpXMedRemModelName */ + { {1, 5, 4795, 1, 3, 3, 1, 6, 0, 3, 1 }, 11, ASN_OCTET_STR, + { .string = { .octet = "Model 1", .len = 7 }} }, + { {1, 5, 4795, 1, 3, 3, 1, 6, 10000, 4, 1 }, 11, ASN_OCTET_STR, + { .string = { .octet = "Model 1", .len = 7 }} }, + /* lldpXMedRemAssetID */ + { {1, 5, 4795, 1, 3, 3, 1, 7, 0, 3, 1 }, 11, ASN_OCTET_STR, + { .string = { .octet = "Asset 1", .len = 7 }} }, + { {1, 5, 4795, 1, 3, 3, 1, 7, 10000, 4, 1 }, 11, ASN_OCTET_STR, + { .string = { .octet = "Asset 1", .len = 7 }} }, + + /* lldpXMedLocLocationInfo */ + { {1, 5, 4795, 1, 3, 4, 1, 2, 0, 3, 1, 2}, 12, ASN_OCTET_STR, + { .string = { .octet = "Not interpreted", .len = 15 }} }, + { {1, 5, 4795, 1, 3, 4, 1, 2, 10000, 4, 1, 3}, 12, ASN_OCTET_STR, + { .string = { .octet = "\x15" "\x02" "FR" "\x06" "\x0d" "Commercial Rd" "\x13" "\x01" "4", .len = 22 }} }, + + /* lldpXMedRemXPoEDeviceType */ + { {1, 5, 4795, 1, 3, 5, 1, 1, 0, 3, 1}, 11, ASN_INTEGER, { .integer = 4 }}, + { {1, 5, 4795, 1, 3, 5, 1, 1, 10000, 4, 1}, 11, ASN_INTEGER, { .integer = 3 }}, + + /* lldpXMedRemXPoEPDPowerReq */ + { {1, 5, 4795, 1, 3, 7, 1, 1, 10000, 4, 1}, 11, ASN_GAUGE, { .integer = 100 }}, + /* lldpXMedRemXPoEPDPowerSource */ + { {1, 5, 4795, 1, 3, 7, 1, 2, 10000, 4, 1}, 11, ASN_INTEGER, { .integer = 3 }}, + /* lldpXMedRemXPoEPDPowerPriority */ + { {1, 5, 4795, 1, 3, 7, 1, 3, 10000, 4, 1}, 11, ASN_INTEGER, { .integer = 3 }}, + +#endif +#ifdef ENABLE_DOT1 + /* lldpXdot1LocPortVlanId */ + { { 1, 5, 32962, 1, 2, 1, 1, 1, 3}, 9, ASN_INTEGER, { .integer = 47 }}, + { { 1, 5, 32962, 1, 2, 1, 1, 1, 4}, 9, ASN_INTEGER, { .integer = 0 }}, + /* lldpXdot1LocProtoVlanSupported */ + { { 1, 5, 32962, 1, 2, 2, 1, 2, 3, 47}, 10, ASN_INTEGER, { .integer = 1 }}, + { { 1, 5, 32962, 1, 2, 2, 1, 2, 3, 118}, 10, ASN_INTEGER, { .integer = 1 }}, + /* lldpXdot1LocProtoVlanEnabled */ + { { 1, 5, 32962, 1, 2, 2, 1, 3, 3, 47}, 10, ASN_INTEGER, { .integer = 1 }}, + { { 1, 5, 32962, 1, 2, 2, 1, 3, 3, 118}, 10, ASN_INTEGER, { .integer = 1 }}, + /* lldpXdot1LocVlanName */ + { { 1, 5, 32962, 1, 2, 3, 1, 2, 3, 47}, 10, ASN_OCTET_STR, + { .string = { .octet = "VLAN #47", .len = 8 }} }, + { { 1, 5, 32962, 1, 2, 3, 1, 2, 3, 49}, 10, ASN_OCTET_STR, + { .string = { .octet = "VLAN #49", .len = 8 }} }, + { { 1, 5, 32962, 1, 2, 3, 1, 2, 3, 1449}, 10, ASN_OCTET_STR, + { .string = { .octet = "VLAN #1449", .len = 10 }} }, + /* lldpXdot1LocProtocolId */ + { { 1, 5, 32962, 1, 2, 4, 1, 2, 3, 30321}, 10, ASN_OCTET_STR, + { .string = { .octet = "\x88\x8e\x01", .len = 3 } }}, + { { 1, 5, 32962, 1, 2, 4, 1, 2, 3, 30515}, 10, ASN_OCTET_STR, + { .string = { .octet = "\x88\xcc", .len = 2 } }}, + + /* lldpXdot1RemPortVlanId */ + { { 1, 5, 32962, 1, 3, 1, 1, 1, 0, 3, 1}, 11, ASN_INTEGER, { .integer = 0 }}, + { { 1, 5, 32962, 1, 3, 1, 1, 1, 8000, 3, 4}, 11, ASN_INTEGER, { .integer = 0 }}, + { { 1, 5, 32962, 1, 3, 1, 1, 1, 10000, 4, 1}, 11, ASN_INTEGER, { .integer = 47 }}, + /* lldpXdot1RemProtoVlanSupported */ + { { 1, 5, 32962, 1, 3, 2, 1, 2, 10000, 4, 1, 47}, 12, ASN_INTEGER, { .integer = 1 }}, + { { 1, 5, 32962, 1, 3, 2, 1, 2, 10000, 4, 1, 118}, 12, ASN_INTEGER, { .integer = 1 }}, + /* lldpXdot1RemProtoVlanEnabled */ + { { 1, 5, 32962, 1, 3, 2, 1, 3, 10000, 4, 1, 47}, 12, ASN_INTEGER, { .integer = 1 }}, + { { 1, 5, 32962, 1, 3, 2, 1, 3, 10000, 4, 1, 118}, 12, ASN_INTEGER, { .integer = 1 }}, + /* lldpXdot1RemVlanName */ + { { 1, 5, 32962, 1, 3, 3, 1, 2, 10000, 4, 1, 47}, 12, ASN_OCTET_STR, + { .string = { .octet = "VLAN #47", .len = 8 }} }, + { { 1, 5, 32962, 1, 3, 3, 1, 2, 10000, 4, 1, 49}, 12, ASN_OCTET_STR, + { .string = { .octet = "VLAN #49", .len = 8 }} }, + { { 1, 5, 32962, 1, 3, 3, 1, 2, 10000, 4, 1, 1449}, 12, ASN_OCTET_STR, + { .string = { .octet = "VLAN #1449", .len = 10 }} }, + /* lldpXdot1RemProtocolId */ + { { 1, 5, 32962, 1, 3, 4, 1, 2, 10000, 4, 1, 30321}, 12, ASN_OCTET_STR, + { .string = { .octet = "\x88\x8e\x01", .len = 3 } }}, + { { 1, 5, 32962, 1, 3, 4, 1, 2, 10000, 4, 1, 30515}, 12, ASN_OCTET_STR, + { .string = { .octet = "\x88\xcc", .len = 2 } }} +#endif +}; + +char* +tohex(char *str, size_t len) +{ + static char *hex[] = { NULL, NULL }; + static int which = 0; + free(hex[which]); hex[which] = NULL; + hex[which] = malloc(len * 3 + 1); + fail_unless(hex[which] != NULL, "Not enough memory?"); + for (size_t i = 0; i < len; i++) + snprintf(hex[which] + 3*i, 4, "%02X ", (unsigned char)str[i]); + which = 1 - which; + return hex[1 - which]; +} + +int +snmp_is_prefix_of(struct variable8 *vp, struct tree_node *n, char *repr) +{ + if (n->namelen < vp->namelen) return 0; + if (memcmp(n->name, + vp->name, + vp->namelen * sizeof(oid))) + return 0; + fail_unless(n->type == vp->type, "Inappropriate type for OID %s", repr); + return 1; +} + +void +snmp_merge(struct variable8 *v1, struct tree_node *n, struct variable *vp, + oid *target, size_t *targetlen) +{ + vp->magic = v1->magic; + vp->type = v1->type; + vp->acl = v1->acl; + vp->findVar = v1->findVar; + vp->namelen = v1->namelen + + sizeof(lldp_oid)/sizeof(oid); + memcpy(vp->name, lldp_oid, sizeof(lldp_oid)); + memcpy(vp->name + sizeof(lldp_oid)/sizeof(oid), + v1->name, + v1->namelen*sizeof(oid)); + *targetlen = n->namelen + + sizeof(lldp_oid)/sizeof(oid); + memcpy(target, lldp_oid, sizeof(lldp_oid)); + memcpy(target + sizeof(lldp_oid)/sizeof(oid), + n->name, + n->namelen * sizeof(oid)); +} + +void +snmp_compare(struct tree_node *n, + u_char *result, size_t varlen, + oid *target, size_t targetlen, char *repr) +{ + unsigned long int value; + fail_unless((targetlen == sizeof(lldp_oid)/sizeof(oid) + n->namelen) && + !memcmp(target, lldp_oid, sizeof(lldp_oid)) && + !memcmp(target + sizeof(lldp_oid)/sizeof(oid), + n->name, + n->namelen * sizeof(oid)), + "Bad OID returned when querying %s: got %s", repr, + snmp_oidrepr(target, targetlen)); + switch (n->type) { + case ASN_INTEGER: + case ASN_TIMETICKS: + case ASN_GAUGE: + case ASN_COUNTER: + fail_unless(varlen == sizeof(unsigned long int), + "Inappropriate length for integer type for OID %s", + repr); + memcpy(&value, result, sizeof(value)); + fail_unless(n->value.integer == value, + "For OID %s, expected value %u but got %u instead", + repr, + n->value.integer, + value); + break; + default: + fail_unless(((n->value.string.len == varlen) && + !memcmp(n->value.string.octet, + result, varlen)), + "OID %s: wanted %s, got %s", + repr, + tohex(n->value.string.octet, n->value.string.len), + tohex((char *)result, varlen)); + } +} + +START_TEST (test_variable_order) +{ + size_t i; + for (i = 0; i < agent_lldp_vars_size() - 1; i++) { + fail_unless(snmp_oid_compare(agent_lldp_vars[i].name, + agent_lldp_vars[i].namelen, + agent_lldp_vars[i+1].name, + agent_lldp_vars[i+1].namelen) < 0, + "Registered OID are out of orders (see %s and next one)", + snmp_oidrepr(agent_lldp_vars[i].name, + agent_lldp_vars[i].namelen)); + } +} +END_TEST + +START_TEST (test_get) +{ + size_t j; + for (j = 0; + j < sizeof(snmp_tree)/sizeof(struct tree_node); + j++) { + size_t i; + int found = 0; + struct variable vp; + oid target[MAX_OID_LEN]; + size_t targetlen; + size_t varlen; + u_char *result; + WriteMethod *wmethod; + char *repr = snmp_oidrepr(snmp_tree[j].name, + snmp_tree[j].namelen); + + for (i = 0; i < agent_lldp_vars_size(); i++) { + /* Search for the appropriate prefix. */ + if (!snmp_is_prefix_of(&agent_lldp_vars[i], &snmp_tree[j], + repr)) continue; + + /* We have our prefix. Fill out the vp struct + correctly. We need to complete OID with + LLDP prefix. */ + snmp_merge(&agent_lldp_vars[i], &snmp_tree[j], &vp, target, &targetlen); + + /* Invoke the function */ + result = vp.findVar(&vp, target, &targetlen, 1, &varlen, &wmethod); + + /* Check the result */ + fail_unless(result != NULL, + "No result when querying %s", repr); + snmp_compare(&snmp_tree[j], result, varlen, target, targetlen, repr); + + found = 1; + break; + } + if (!found) + fail("OID %s not found", repr); + } +} +END_TEST + +START_TEST (test_getnext) +{ + size_t j; + size_t end = sizeof(snmp_tree)/sizeof(struct tree_node); + for (j = 0; + j < end; + j++) { + size_t i; + struct variable vp; + oid target[MAX_OID_LEN]; + size_t targetlen; + size_t varlen; + u_char *result = NULL; + WriteMethod *wmethod; + char *repr = snmp_oidrepr(snmp_tree[j].name, + snmp_tree[j].namelen); + for (i = 0; i < agent_lldp_vars_size(); i++) { + snmp_merge(&agent_lldp_vars[i], &snmp_tree[j], &vp, target, &targetlen); + result = vp.findVar(&vp, target, &targetlen, 0, &varlen, &wmethod); + if (result) /* Check next! */ + break; + } + if (!result) { + fail_unless(j == end - 1, + "No next result found for %s", repr); + return; + } + fail_unless(j < end - 1, + "More results after %s", repr); + + /* For unknown reasons, snmp_compare can be executed + even when the above test fails... */ + if (j < end - 1) + snmp_compare(&snmp_tree[j+1], result, varlen, target, targetlen, repr); + + } +} +END_TEST + +Suite * +snmp_suite(void) +{ + Suite *s = suite_create("SNMP"); + + TCase *tc_snmp = tcase_create("SNMP"); + tcase_add_checked_fixture(tc_snmp, snmp_config, NULL); + tcase_add_test(tc_snmp, test_variable_order); + tcase_add_test(tc_snmp, test_get); + tcase_add_test(tc_snmp, test_getnext); + suite_add_tcase(s, tc_snmp); + + return s; +} + +int +main() +{ + int number_failed; + Suite *s = snmp_suite(); + SRunner *sr = srunner_create(s); + srunner_run_all(sr, CK_ENV); + number_failed = srunner_ntests_failed(sr); + srunner_free(sr); + return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/tests/check_sonmp.c b/tests/check_sonmp.c new file mode 100644 index 0000000000000000000000000000000000000000..b25f0e2fbb88eba662079da1d035eebb6c48529b --- /dev/null +++ b/tests/check_sonmp.c @@ -0,0 +1,235 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2015 Vincent Bernat + * + * 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 +#include +#include +#include +#include +#include "common.h" + +char filenameprefix[] = "sonmp_send"; + +#ifdef ENABLE_SONMP + +START_TEST (test_send_sonmp) +{ + int n; + /* Packet we should build: +IEEE 802.3 Ethernet + Destination: Bay-Networks-(Synoptics)-autodiscovery (01:00:81:00:01:00) + Source: 5e:10:8e:e7:84:ad (5e:10:8e:e7:84:ad) + Length: 19 +Logical-Link Control + DSAP: SNAP (0xaa) + IG Bit: Individual + SSAP: SNAP (0xaa) + CR Bit: Command + Control field: U, func=UI (0x03) + 000. 00.. = Command: Unnumbered Information (0x00) + .... ..11 = Frame type: Unnumbered frame (0x03) + Organization Code: Nortel Networks SONMP (0x000081) + PID: SONMP segment hello (0x01a2) +Nortel Networks / SynOptics Network Management Protocol + NMM IP address: 172.17.142.37 (172.17.142.37) + Segment Identifier: 0x000004 + Chassis type: Unknown (1) + Backplane type: ethernet, fast ethernet and gigabit ethernet (12) + NMM state: New (3) + Number of links: 1 + +IEEE 802.3 Ethernet + Destination: Bay-Networks-(Synoptics)-autodiscovery (01:00:81:00:01:01) + Source: 5e:10:8e:e7:84:ad (5e:10:8e:e7:84:ad) + Length: 19 +Logical-Link Control + DSAP: SNAP (0xaa) + IG Bit: Individual + SSAP: SNAP (0xaa) + CR Bit: Command + Control field: U, func=UI (0x03) + 000. 00.. = Command: Unnumbered Information (0x00) + .... ..11 = Frame type: Unnumbered frame (0x03) + Organization Code: Nortel Networks SONMP (0x000081) + PID: SONMP flatnet hello (0x01a1) +Nortel Networks / SynOptics Network Management Protocol + NMM IP address: 172.17.142.37 (172.17.142.37) + Segment Identifier: 0x000004 + Chassis type: Unknown (1) + Backplane type: ethernet, fast ethernet and gigabit ethernet (12) + NMM state: New (3) + Number of links: 1 + */ + char pkt1[] = { + 0x01, 0x00, 0x81, 0x00, 0x01, 0x00, 0x5e, 0x10, + 0x8e, 0xe7, 0x84, 0xad, 0x00, 0x13, 0xaa, 0xaa, + 0x03, 0x00, 0x00, 0x81, 0x01, 0xa2, 0xac, 0x11, + 0x8e, 0x25, 0x00, 0x00, 0x04, 0x01, 0x0c, 0x03, + 0x01 }; + char pkt2[] = { + 0x01, 0x00, 0x81, 0x00, 0x01, 0x01, 0x5e, 0x10, + 0x8e, 0xe7, 0x84, 0xad, 0x00, 0x13, 0xaa, 0xaa, + 0x03, 0x00, 0x00, 0x81, 0x01, 0xa1, 0xac, 0x11, + 0x8e, 0x25, 0x00, 0x00, 0x04, 0x01, 0x0c, 0x03, + 0x01 }; + struct packet *pkt; + in_addr_t addr; + struct lldpd_mgmt *mgmt; + + /* Populate port and chassis */ + hardware.h_lport.p_id_subtype = LLDP_PORTID_SUBTYPE_IFNAME; + hardware.h_lport.p_id = "Not used"; + hardware.h_lport.p_id_len = strlen(hardware.h_lport.p_id); + chassis.c_id_subtype = LLDP_CHASSISID_SUBTYPE_LLADDR; + chassis.c_id = macaddress; + chassis.c_id_len = ETHER_ADDR_LEN; + TAILQ_INIT(&chassis.c_mgmt); + addr = inet_addr("172.17.142.37"); + mgmt = lldpd_alloc_mgmt(LLDPD_AF_IPV4, + &addr, sizeof(in_addr_t), 0); + if (mgmt == NULL) + ck_abort(); + TAILQ_INSERT_TAIL(&chassis.c_mgmt, mgmt, m_entries); + + /* Build packet */ + n = sonmp_send(NULL, &hardware); + if (n != 0) { + fail("unable to build packet"); + return; + } + if (TAILQ_EMPTY(&pkts)) { + fail("no packets sent"); + return; + } + pkt = TAILQ_FIRST(&pkts); + ck_assert_int_eq(pkt->size, sizeof(pkt1)); + fail_unless(memcmp(pkt->data, pkt1, sizeof(pkt1)) == 0); + pkt = TAILQ_NEXT(pkt, next); + if (!pkt) { + fail("need one more packet"); + return; + } + ck_assert_int_eq(pkt->size, sizeof(pkt2)); + fail_unless(memcmp(pkt->data, pkt2, sizeof(pkt2)) == 0); + fail_unless(TAILQ_NEXT(pkt, next) == NULL, "more than two packets sent"); +} +END_TEST + +START_TEST (test_recv_sonmp) +{ + char pkt1[] = { + 0x01, 0x00, 0x81, 0x00, 0x01, 0x00, 0x00, 0x1b, + 0x25, 0x08, 0x50, 0x47, 0x00, 0x13, 0xaa, 0xaa, + 0x03, 0x00, 0x00, 0x81, 0x01, 0xa2, 0xac, 0x10, + 0x65, 0xa8, 0x00, 0x02, 0x08, 0x38, 0x0c, 0x02, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00 }; + /* This is: +IEEE 802.3 Ethernet + Destination: Bay-Networks-(Synoptics)-autodiscovery (01:00:81:00:01:00) + Source: Nortel_08:50:47 (00:1b:25:08:50:47) + Length: 19 + Trailer: 000000000000000000000000000000000000000000000000... +Logical-Link Control + DSAP: SNAP (0xaa) + IG Bit: Individual + SSAP: SNAP (0xaa) + CR Bit: Command + Control field: U, func=UI (0x03) + 000. 00.. = Command: Unnumbered Information (0x00) + .... ..11 = Frame type: Unnumbered frame (0x03) + Organization Code: Nortel Networks SONMP (0x000081) + PID: SONMP segment hello (0x01a2) +Nortel Networks / SynOptics Network Management Protocol + NMM IP address: 172.16.101.168 (172.16.101.168) + Segment Identifier: 0x000208 + Chassis type: Accelar 8610 L3 switch (56) + Backplane type: ethernet, fast ethernet and gigabit ethernet (12) + NMM state: Heartbeat (2) + Number of links: 1 + */ + struct lldpd_chassis *nchassis = NULL; + struct lldpd_port *nport = NULL; + char cid[5]; + in_addr_t ip; + + fail_unless(sonmp_decode(NULL, pkt1, sizeof(pkt1), &hardware, + &nchassis, &nport) != -1); + if (!nchassis || !nport) { + fail("unable to decode packet"); + return; + } + ck_assert_int_eq(nchassis->c_id_subtype, + LLDP_CHASSISID_SUBTYPE_ADDR); + ck_assert_int_eq(nchassis->c_id_len, 5); + cid[0] = 1; + ip = inet_addr("172.16.101.168"); + memcpy(cid + 1, &ip, sizeof(in_addr_t)); + fail_unless(memcmp(nchassis->c_id, cid, 5) == 0); + ck_assert_str_eq(nchassis->c_name, "172.16.101.168"); + ck_assert_str_eq(nchassis->c_descr, "Nortel Ethernet Routing 8610 L3 Switch"); + ck_assert_int_eq(TAILQ_FIRST(&nchassis->c_mgmt)->m_addr.inet.s_addr, + (u_int32_t)inet_addr("172.16.101.168")); + ck_assert_int_eq(TAILQ_FIRST(&nchassis->c_mgmt)->m_iface, 0); + ck_assert_str_eq(nport->p_descr, "port 2/8"); + ck_assert_int_eq(nport->p_id_subtype, + LLDP_PORTID_SUBTYPE_LOCAL); + ck_assert_int_eq(nport->p_id_len, strlen("00-02-08")); + fail_unless(memcmp(nport->p_id, + "00-02-08", strlen("00-02-08")) == 0); + ck_assert_int_eq(nchassis->c_cap_enabled, 0); +} +END_TEST + +#endif + +Suite * +sonmp_suite(void) +{ + Suite *s = suite_create("SONMP"); + +#ifdef ENABLE_SONMP + TCase *tc_send = tcase_create("Send SONMP packets"); + TCase *tc_receive = tcase_create("Receive SONMP packets"); + + tcase_add_checked_fixture(tc_send, pcap_setup, pcap_teardown); + tcase_add_test(tc_send, test_send_sonmp); + suite_add_tcase(s, tc_send); + + tcase_add_test(tc_receive, test_recv_sonmp); + suite_add_tcase(s, tc_receive); +#endif + + return s; +} + +int +main() +{ + int number_failed; + Suite *s = sonmp_suite (); + SRunner *sr = srunner_create (s); + srunner_set_fork_status (sr, CK_NOFORK); /* Can't fork because + we need to write + files */ + srunner_run_all (sr, CK_ENV); + number_failed = srunner_ntests_failed (sr); + srunner_free (sr); + return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/tests/ci/install.sh b/tests/ci/install.sh new file mode 100755 index 0000000000000000000000000000000000000000..7139bdeeaff90ca4b7c063cbff2629480ef0490e --- /dev/null +++ b/tests/ci/install.sh @@ -0,0 +1,28 @@ +#!/bin/sh + +set -e + +case "$(uname -s)" in + Darwin) + brew update > /dev/null + brew bundle --file=- <<-EOS +brew "automake" +brew "autoconf" +brew "libtool" +brew "check" +EOS + ;; + Linux) + sudo apt-get -qqy update + sudo apt-get -qqy install \ + automake autoconf libtool pkg-config \ + libsnmp-dev libxml2-dev \ + libevent-dev libreadline-dev libbsd-dev \ + check libc6-dbg libseccomp-dev \ + libpcap-dev libcap-dev systemtap-sdt-dev \ + snmpd snmp \ + python3-pip python3-setuptools python3-wheel + # For integration tests + sudo -H $(which python3) -m pip install -r tests/integration/requirements.txt + ;; +esac diff --git a/tests/ci/release.sh b/tests/ci/release.sh new file mode 100755 index 0000000000000000000000000000000000000000..e53b3936ad4f4563d31b6b52fb0f6e6ab2e1bd0b --- /dev/null +++ b/tests/ci/release.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +echo "## Summary" +sed -n '2,/^$/p' NEWS + +echo "## Changelog" +sed -n '3,/^$/p' ChangeLog diff --git a/tests/ci/run.sh b/tests/ci/run.sh new file mode 100755 index 0000000000000000000000000000000000000000..e1d36f3ecb95bca6ba4fd35a5d45d41aa668c9d7 --- /dev/null +++ b/tests/ci/run.sh @@ -0,0 +1,50 @@ +#!/bin/sh + +set -e + +LLDPD_CONFIG_ARGS="$LLDPD_CONFIG_ARGS --enable-pie" +case "$(uname -s)" in + Linux) + LLDPD_CONFIG_ARGS="$LLDPD_CONFIG_ARGS --localstatedir=/var --sysconfdir=/etc --prefix=/usr" + [ $(uname -m) != x86_64 ] || \ + LLDPD_CONFIG_ARGS="$LLDPD_CONFIG_ARGS --enable-sanitizers" + LLDPD_CONFIG_ARGS="$LLDPD_CONFIG_ARGS LDFLAGS=-fuse-ld=gold" + MAKE_ARGS="-Werror" + ;; + Darwin) + LLDPD_CONFIG_ARGS="$LLDPD_CONFIG_ARGS CFLAGS=-mmacosx-version-min=10.9" + LLDPD_CONFIG_ARGS="$LLDPD_CONFIG_ARGS LDFLAGS=-mmacosx-version-min=10.9" + MAKE_ARGS="" + ;; +esac + +./autogen.sh +./configure $LLDPD_CONFIG_ARGS || { + cat config.log + exit 1 +} +make all ${MAKE_ARGS-CFLAGS=-Werror} || make all ${MAKE_ARGS-CFLAGS=-Werror} V=1 + +# Temporarily don't run checks with clang, due to libcheck incompatibility: +# See: +if [ "$CC" != clang ]; then + make check ${MAKE_ARGS-CFLAGS=-Werror} || { + [ ! -f tests/test-suite.log ] || cat tests/test-suite.log + exit 1 + } + make distcheck +fi + +case "$(uname -s)" in + Darwin) + # Create a package + make -C osx pkg + otool -l osx/lldpd*/usr/local/sbin/lldpd + ;; + Linux) + # Integration tests + cd tests/integration + sudo $(which python3) -m pytest -n 5 -vv --boxed || \ + sudo $(which python3) -m pytest -vvv --last-failed --maxfail=5 + ;; +esac diff --git a/tests/common.c b/tests/common.c new file mode 100644 index 0000000000000000000000000000000000000000..f338f60c2cdc743e0aceca9e90a2151f1ce434cb --- /dev/null +++ b/tests/common.c @@ -0,0 +1,148 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2015 Vincent Bernat + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include "common.h" + +int dump = -1; +char *filename = NULL; +struct pkts_t pkts; +char macaddress[ETHER_ADDR_LEN] = { 0x5e, 0x10, 0x8e, 0xe7, 0x84, 0xad }; +struct lldpd_hardware hardware; +struct lldpd_chassis chassis; + +int +pcap_send(struct lldpd *cfg, struct lldpd_hardware *hardware, + char *buffer, size_t size) +{ + struct pcaprec_hdr hdr; + struct packet *pkt; + int n; + + /* Write pcap record header */ + hdr.ts_sec = time(NULL); + hdr.ts_usec = 0; + hdr.incl_len = hdr.orig_len = size; + n = write(dump, &hdr, sizeof(hdr)); + if (n == 1) { + fail("unable to write pcap record header to %s", filename); + return -1; + } + + /* Write data */ + n = write(dump, buffer, size); + if (n == -1) { + fail("unable to write pcap data to %s", filename); + return -1; + } + + /* Append to list of packets */ + pkt = (struct packet *)malloc(size + sizeof(TAILQ_HEAD(,packet)) + sizeof(int)); + if (!pkt) { + fail("unable to allocate packet"); + return -1; + } + memcpy(pkt->data, buffer, size); + pkt->size = size; + TAILQ_INSERT_TAIL(&pkts, pkt, next); + return 0; +} + +struct lldpd_ops pcap_ops = { + .send = pcap_send, + .recv = NULL, /* Won't be used */ + .cleanup = NULL, /* Won't be used */ +}; + + +void +pcap_setup() +{ + static int serial = 0; + struct pcap_hdr hdr; + int n; + /* Prepare packet buffer */ + TAILQ_INIT(&pkts); + /* Open a new dump file */ + n = asprintf(&filename, "%s_%04d.pcap", filenameprefix, serial++); + if (n == -1) { + fail("unable to compute filename"); + return; + } + dump = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0644); + if (dump == -1) { + fail("unable to open %s", filename); + return; + } + /* Write a PCAP header */ + hdr.magic_number = 0xa1b2c3d4; + hdr.version_major = 2; + hdr.version_minor = 4; + hdr.thiszone = 0; + hdr.sigfigs = 0; + hdr.snaplen = 65535; + hdr.network = 1; + n = write(dump, &hdr, sizeof(hdr)); + if (n == -1) { + fail("unable to write pcap header to %s", filename); + return; + } + /* Prepare hardware */ + memset(&hardware, 0, sizeof(struct lldpd_hardware)); + TAILQ_INIT(&hardware.h_rports); + hardware.h_mtu = 1500; + hardware.h_ifindex = 4; + strlcpy(hardware.h_ifname, "test", sizeof(hardware.h_ifname)); + memcpy(hardware.h_lladdr, macaddress, ETHER_ADDR_LEN); + hardware.h_ops = &pcap_ops; + /* Prepare chassis */ + memset(&chassis, 0, sizeof(struct lldpd_chassis)); + hardware.h_lport.p_chassis = &chassis; +} + +void +pcap_teardown() +{ + struct packet *npkt, *pkt; + for (pkt = TAILQ_FIRST(&pkts); + pkt != NULL; + pkt = npkt) { + npkt = TAILQ_NEXT(pkt, next); + TAILQ_REMOVE(&pkts, pkt, next); + free(pkt); + } + if (dump != -1) { + close(dump); + dump = -1; + } + if (filename) { + free(filename); + filename = NULL; + } +} + +/* Disable leak detection sanitizer */ +int __lsan_is_turned_off() { + return 1; +} diff --git a/tests/common.h b/tests/common.h new file mode 100644 index 0000000000000000000000000000000000000000..4dd5d8013894e960c9d3359b8e1c76a74810a4eb --- /dev/null +++ b/tests/common.h @@ -0,0 +1,45 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2015 Vincent Bernat + * + * 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 _COMMON_H +#define _COMMON_H + +#include "check-compat.h" +#include "pcap-hdr.h" +#include "../src/daemon/ub-lldpd.h" + +struct packet { + TAILQ_ENTRY(packet) next; + int size; + char data[]; +}; +TAILQ_HEAD(pkts_t, packet); + +extern int dump; /* Dump file descriptor in pcap format */ +extern char filenameprefix[]; /* Prefix for filename dumping */ +extern char *filename; /* Filename we are dumping to */ +extern char macaddress[]; /* MAC address we use to send */ + +extern struct pkts_t pkts; /* List of sent packets */ +extern struct lldpd_hardware hardware; +extern struct lldpd_chassis chassis; + +int pcap_send(struct lldpd *, struct lldpd_hardware *, char *, size_t); +void pcap_setup(); +void pcap_teardown(); + +#endif diff --git a/tests/decode.c b/tests/decode.c new file mode 100644 index 0000000000000000000000000000000000000000..f0ffc2caa8430e23b0974ecef23d448ab6956b03 --- /dev/null +++ b/tests/decode.c @@ -0,0 +1,147 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2015 Vincent Bernat + * + * 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 +#include +#include +#include +#include +#include +#include +#include "pcap-hdr.h" +#include "../src/daemon/ub-lldpd.h" + +#define BUFSIZE 2000 + +static void +usage(void) +{ + fprintf(stderr, "Usage: %s PCAP\n", "decode"); + fprintf(stderr, "Version: %s\n", PACKAGE_STRING); + + fprintf(stderr, "\n"); + + fprintf(stderr, "Decode content of PCAP files and display a summary\n"); + fprintf(stderr, "on standard output. Only the first packet is decoded.\n"); + exit(1); +} + +char* +tohex(char *str, size_t len) +{ + static char *hex = NULL; + free(hex); hex = NULL; + if ((hex = malloc(len * 3 + 1)) == NULL) return NULL; + for (size_t i = 0; i < len; i++) + snprintf(hex + 3*i, 4, "%02X ", (unsigned char)str[i]); + return hex; +} + +/* We need an assert macro which doesn't abort */ +#define assert(x) while (!(x)) { \ + fprintf(stderr, "%s:%d: %s: Assertion `%s' failed.\n", \ + __FILE__, __LINE__, __func__, #x); \ + exit(5); \ + } + +int +main(int argc, char **argv) +{ + if (argc != 2 || + !strcmp(argv[1], "-h") || + !strcmp(argv[1], "--help")) + usage(); + + int fd = open(argv[1], O_RDONLY); + assert(fd != -1); + + char buf[BUFSIZE]; + ssize_t len = read(fd, buf, BUFSIZE); + assert(len != -1); + + struct pcap_hdr hdr; + assert(len >= sizeof(hdr)); + memcpy(&hdr, buf, sizeof(hdr)); + assert(hdr.magic_number == 0xa1b2c3d4); /* Assume the same byte order as us */ + assert(hdr.version_major == 2); + assert(hdr.version_minor == 4); + assert(hdr.thiszone == 0); + /* Don't care about other flags */ + + struct pcaprec_hdr rechdr; + assert(len >= sizeof(hdr) + sizeof(rechdr)); + memcpy(&rechdr, buf + sizeof(hdr), sizeof(rechdr)); + assert(len >= sizeof(hdr) + sizeof(rechdr) + rechdr.incl_len); + + /* For decoding, we only need a very basic hardware */ + struct lldpd_hardware hardware; + memset(&hardware, 0, sizeof(struct lldpd_hardware)); + hardware.h_mtu = 1500; + strlcpy(hardware.h_ifname, "test", sizeof(hardware.h_ifname)); + + char *frame = buf + sizeof(hdr) + sizeof(rechdr); + struct lldpd_chassis *nchassis = NULL; + struct lldpd_port *nport = NULL; + int decoded = 0; + if (lldp_decode(NULL, frame, rechdr.incl_len, &hardware, &nchassis, &nport) == -1) { + fprintf(stderr, "Not decoded as a LLDP frame\n"); + } else { + fprintf(stderr, "Decoded as a LLDP frame\n"); + decoded = 1; + } + + if (!decoded) exit(1); + + printf("Chassis:\n"); + printf(" Index: %" PRIu16 "\n", nchassis->c_index); + printf(" Protocol: %" PRIu8 "\n", nchassis->c_protocol); + printf(" ID subtype: %" PRIu8 "\n", nchassis->c_id_subtype); + printf(" ID: %s\n", tohex(nchassis->c_id, nchassis->c_id_len)); + printf(" Name: %s\n", nchassis->c_name?nchassis->c_name:"(null)"); + printf(" Description: %s\n", nchassis->c_descr?nchassis->c_descr:"(null)"); + printf(" Cap available: %" PRIu16 "\n", nchassis->c_cap_available); + printf(" Cap enabled: %" PRIu16 "\n", nchassis->c_cap_enabled); + struct lldpd_mgmt *mgmt; + TAILQ_FOREACH(mgmt, &nchassis->c_mgmt, m_entries) { + char ipaddress[INET6_ADDRSTRLEN + 1]; + int af; size_t alen; + switch (mgmt->m_family) { + case LLDPD_AF_IPV4: + alen = INET_ADDRSTRLEN + 1; + af = AF_INET; + break; + case LLDPD_AF_IPV6: + alen = INET6_ADDRSTRLEN + 1; + af = AF_INET6; + break; + default: + len = 0; + } + if (len == 0) continue; + if (inet_ntop(af, &mgmt->m_addr, ipaddress, alen) == NULL) + break; + printf(" mgmt: %s\n", ipaddress); + } + + printf("Port:\n"); + printf(" ID subtype: %" PRIu8 "\n", nport->p_id_subtype); + printf(" ID: %s\n", tohex(nport->p_id, nport->p_id_len)); + printf(" Description: %s\n", nport->p_descr?nport->p_descr:"(null)"); + printf(" MFS: %" PRIu16 "\n", nport->p_mfs); + printf(" TTL: %" PRIu16 "\n", nport->p_ttl); + exit(0); +} diff --git a/tests/integration/.gitignore b/tests/integration/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..eb727832696cfa372cf0b58d5bba0cfc1992c741 --- /dev/null +++ b/tests/integration/.gitignore @@ -0,0 +1,6 @@ +# Python +__pycache__ +*.pyc + +# pytest +/.pytest_cache diff --git a/tests/integration/README.md b/tests/integration/README.md new file mode 100644 index 0000000000000000000000000000000000000000..3128ca7f5e4ab3f5374ed410d0fe664b912c9c53 --- /dev/null +++ b/tests/integration/README.md @@ -0,0 +1,21 @@ +lldpd integration tests +======================= + +To run those tests, you need Python 3. + + $ virtualenv -p /usr/bin/python3 venv + $ . venv/bin/activate + $ pip install -r requirements.txt + +The tests rely on namespace support. Therefore, they only work on +Linux. At least a 3.11 kernel is needed. While it would have been +convenient to rely on a user namespace to avoid to run tests as root, +there are restrictions that makes that difficult, notably we can only +map one user to root and we have to map the current user and the +_lldpd user. + +Then, tests can be run with: + + $ sudo $(which pytest) -vv -n 10 --boxed + +Add an additional `-v` to get even more traces. diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py new file mode 100644 index 0000000000000000000000000000000000000000..e0146356595c06238e85b6c0b95babeca48240a6 --- /dev/null +++ b/tests/integration/conftest.py @@ -0,0 +1,26 @@ +pytest_plugins = ['helpers_namespace'] + +import pytest +import scapy.all + +from fixtures.programs import * +from fixtures.namespaces import * +from fixtures.network import * + + +@pytest.yield_fixture(autouse=True, scope='session') +def root(): + """Ensure we are somewhat root.""" + # We could do a user namespace but there are too many + # restrictions: we cannot do arbitrary user mapping and therefore, + # this doesn't play well with privilege separation and the use of + # _lldpd. Just do a plain namespace. + with Namespace('pid', 'net', 'mnt', 'ipc', 'uts'): + yield + + +@pytest.helpers.register +def send_pcap(location, interface): + packets = scapy.all.rdpcap(location) + print(packets) + scapy.all.sendp(packets, iface=interface) diff --git a/tests/integration/data/8023bt.pcap b/tests/integration/data/8023bt.pcap new file mode 100644 index 0000000000000000000000000000000000000000..932b593f0266a78881beffc5e633d072a990847f Binary files /dev/null and b/tests/integration/data/8023bt.pcap differ diff --git a/tests/integration/data/connectx.pcap b/tests/integration/data/connectx.pcap new file mode 100644 index 0000000000000000000000000000000000000000..a20aa12c1440ef1e966d1ea5f5d25a397be36e23 Binary files /dev/null and b/tests/integration/data/connectx.pcap differ diff --git a/tests/integration/data/med-loc-malformed.pcap b/tests/integration/data/med-loc-malformed.pcap new file mode 100644 index 0000000000000000000000000000000000000000..7e809516e657945b5cccc269351c81eac1daf250 Binary files /dev/null and b/tests/integration/data/med-loc-malformed.pcap differ diff --git a/tests/integration/data/sg200.pcap b/tests/integration/data/sg200.pcap new file mode 100644 index 0000000000000000000000000000000000000000..619150dda011dab54b0b37660f12698cde480e57 Binary files /dev/null and b/tests/integration/data/sg200.pcap differ diff --git a/tests/integration/fixtures/__init__.py b/tests/integration/fixtures/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/tests/integration/fixtures/namespaces.py b/tests/integration/fixtures/namespaces.py new file mode 100644 index 0000000000000000000000000000000000000000..ad33fd7f157a7cf464b76d0ec01c0ece0746f0be --- /dev/null +++ b/tests/integration/fixtures/namespaces.py @@ -0,0 +1,246 @@ +import contextlib +import ctypes +import errno +import os +import pyroute2 +import pytest +import signal +import multiprocessing + +# All allowed namespace types +NAMESPACE_FLAGS = dict(mnt=0x00020000, + uts=0x04000000, + ipc=0x08000000, + user=0x10000000, + pid=0x20000000, + net=0x40000000) +STACKSIZE = 1024*1024 + +libc = ctypes.CDLL('libc.so.6', use_errno=True) + + +@contextlib.contextmanager +def keep_directory(): + """Restore the current directory on exit.""" + pwd = os.getcwd() + try: + yield + finally: + os.chdir(pwd) + + +def mount_sys(target="/sys"): + flags = [2 | 4 | 8] # MS_NOSUID | MS_NODEV | MS_NOEXEC + flags.append(1 << 18) # MS_PRIVATE + flags.append(1 << 19) # MS_SLAVE + for fl in flags: + ret = libc.mount(b"none", + target.encode('ascii'), + b"sysfs", + fl, + None) + if ret == -1: + e = ctypes.get_errno() + raise OSError(e, os.strerror(e)) + + +def mount_tmpfs(target, private=False): + flags = [0] + if private: + flags.append(1 << 18) # MS_PRIVATE + flags.append(1 << 19) # MS_SLAVE + for fl in flags: + ret = libc.mount(b"none", + target.encode('ascii'), + b"tmpfs", + fl, + None) + if ret == -1: + e = ctypes.get_errno() + raise OSError(e, os.strerror(e)) + + +def _mount_proc(target): + flags = [2 | 4 | 8] # MS_NOSUID | MS_NODEV | MS_NOEXEC + flags.append(1 << 18) # MS_PRIVATE + flags.append(1 << 19) # MS_SLAVE + for fl in flags: + ret = libc.mount(b"proc", + target.encode('ascii'), + b"proc", + fl, + None) + if ret == -1: + e = ctypes.get_errno() + raise OSError(e, os.strerror(e)) + + +def mount_proc(target="/proc"): + # We need to be sure /proc is correct. We do that in another + # process as this doesn't play well with setns(). + if not os.path.isdir(target): + os.mkdir(target) + p = multiprocessing.Process(target=_mount_proc, args=(target,)) + p.start() + p.join() + + +class Namespace(object): + """Combine several namespaces into one. + + This gets a list of namespace types to create and combine into one. The + combined namespace can be used as a context manager to enter all the + created namespaces and exit them at the end. + """ + + def __init__(self, *namespaces): + self.next = [] + self.namespaces = namespaces + for ns in namespaces: + assert ns in NAMESPACE_FLAGS + + # Get a pipe to signal the future child to exit + self.pipe = os.pipe() + + # First, create a child in the given namespaces + child = ctypes.CFUNCTYPE(ctypes.c_int)(self.child) + child_stack = ctypes.create_string_buffer(STACKSIZE) + child_stack_pointer = ctypes.c_void_p( + ctypes.cast(child_stack, + ctypes.c_void_p).value + STACKSIZE) + flags = signal.SIGCHLD + for ns in namespaces: + flags |= NAMESPACE_FLAGS[ns] + pid = libc.clone(child, child_stack_pointer, flags) + if pid == -1: + e = ctypes.get_errno() + raise OSError(e, os.strerror(e)) + + # If a user namespace, map UID 0 to the current one + if 'user' in namespaces: + uid_map = '0 {} 1'.format(os.getuid()) + gid_map = '0 {} 1'.format(os.getgid()) + with open('/proc/{}/uid_map'.format(pid), 'w') as f: + f.write(uid_map) + with open('/proc/{}/setgroups'.format(pid), 'w') as f: + f.write('deny') + with open('/proc/{}/gid_map'.format(pid), 'w') as f: + f.write(gid_map) + + # Retrieve a file descriptor to this new namespace + self.next = [os.open('/proc/{}/ns/{}'.format(pid, x), + os.O_RDONLY) for x in namespaces] + + # Keep a file descriptor to our old namespaces + self.previous = [os.open('/proc/self/ns/{}'.format(x), + os.O_RDONLY) for x in namespaces] + + # Tell the child all is done and let it die + os.close(self.pipe[0]) + if 'pid' not in namespaces: + os.close(self.pipe[1]) + self.pipe = None + os.waitpid(pid, 0) + + def __del__(self): + for fd in self.next: + os.close(fd) + for fd in self.previous: + os.close(fd) + if self.pipe is not None: + os.close(self.pipe[1]) + + def child(self): + """Cloned child. + + Just be here until our parent extract the file descriptor from + us. + + """ + os.close(self.pipe[1]) + + # For a network namespace, enable lo + if 'net' in self.namespaces: + ipr = pyroute2.IPRoute() + lo = ipr.link_lookup(ifname='lo')[0] + ipr.link('set', index=lo, state='up') + # For a mount namespace, make it private + if 'mnt' in self.namespaces: + libc.mount(b"none", b"/", None, + # MS_REC | MS_PRIVATE + 16384 | (1 << 18), + None) + + while True: + try: + os.read(self.pipe[0], 1) + except OSError as e: + if e.errno in [errno.EAGAIN, errno.EINTR]: + continue + break + + os._exit(0) + + def fd(self, namespace): + """Return the file descriptor associated to a namespace""" + assert namespace in self.namespaces + return self.next[self.namespaces.index(namespace)] + + def __enter__(self): + with keep_directory(): + for n in self.next: + if libc.setns(n, 0) == -1: + ns = self.namespaces[self.next.index(n)] # NOQA + e = ctypes.get_errno() + raise OSError(e, os.strerror(e)) + + def __exit__(self, *exc): + with keep_directory(): + err = None + for p in reversed(self.previous): + if libc.setns(p, 0) == -1 and err is None: + ns = self.namespaces[self.previous.index(p)] # NOQA + e = ctypes.get_errno() + err = OSError(e, os.strerror(e)) + if err: + raise err + + def __repr__(self): + return 'Namespace({})'.format(", ".join(self.namespaces)) + + +class NamespaceFactory(object): + """Dynamically create namespaces as they are created. + + Those namespaces are namespaces for IPC, net, mount and UTS. PID + is a bit special as we have to keep a process for that. We don't + do that to ensure that everything is cleaned + automatically. Therefore, the child process is killed as soon as + we got a file descriptor to the namespace. We don't use a user + namespace either because we are unlikely to be able to exit it. + + """ + + def __init__(self, tmpdir): + self.namespaces = {} + self.tmpdir = tmpdir + + def __call__(self, ns): + """Return a namespace. Create it if it doesn't exist.""" + if ns in self.namespaces: + return self.namespaces[ns] + + self.namespaces[ns] = Namespace('ipc', 'net', 'mnt', 'uts') + with self.namespaces[ns]: + mount_proc() + mount_sys() + # Also setup the "namespace-dependant" directory + self.tmpdir.join("ns").ensure(dir=True) + mount_tmpfs(str(self.tmpdir.join("ns")), private=True) + + return self.namespaces[ns] + + +@pytest.fixture +def namespaces(tmpdir): + return NamespaceFactory(tmpdir) diff --git a/tests/integration/fixtures/network.py b/tests/integration/fixtures/network.py new file mode 100644 index 0000000000000000000000000000000000000000..8796e36044580ea86c6c0bd4f78c0057f86954ae --- /dev/null +++ b/tests/integration/fixtures/network.py @@ -0,0 +1,179 @@ +import pytest +import pyroute2 +import struct +import socket +import fcntl +import time +from .namespaces import Namespace + + +def int_to_mac(c): + """Turn an int into a MAC address.""" + return ":".join(('{:02x}',)*6).format( + *struct.unpack('BBBBBB', c.to_bytes(6, byteorder='big'))) + + +class LinksFactory(object): + """A factory for veth pair of interfaces and other L2 stuff. + + Each veth interfaces will get named ethX with X strictly + increasing at each call. + + """ + + def __init__(self): + # We create all those links in a dedicated namespace to avoid + # conflict with other namespaces. + self.ns = Namespace('net') + self.count = 0 + + def __call__(self, *args, **kwargs): + return self.veth(*args, **kwargs) + + def veth(self, ns1, ns2, sleep=0, mtu=1500): + """Create a veth pair between two namespaces.""" + with self.ns: + # First, create a link + first = 'eth{}'.format(self.count) + second = 'eth{}'.format(self.count + 1) + ipr = pyroute2.IPRoute() + ipr.link('add', + ifname=first, + peer=second, + kind='veth') + idx = [ipr.link_lookup(ifname=x)[0] + for x in (first, second)] + + # Set an easy to remember MAC address + ipr.link('set', index=idx[0], + address=int_to_mac(self.count + 1)) + ipr.link('set', index=idx[1], + address=int_to_mac(self.count + 2)) + + # Set MTU + ipr.link('set', index=idx[0], mtu=mtu) + ipr.link('set', index=idx[1], mtu=mtu) + + # Then, move each to the target namespace + ipr.link('set', index=idx[0], net_ns_fd=ns1.fd('net')) + ipr.link('set', index=idx[1], net_ns_fd=ns2.fd('net')) + + # And put them up + with ns1: + ipr = pyroute2.IPRoute() + ipr.link('set', index=idx[0], state='up') + time.sleep(sleep) + with ns2: + ipr = pyroute2.IPRoute() + ipr.link('set', index=idx[1], state='up') + + self.count += 2 + + def bridge(self, name, *ifaces, filtering=False): + """Create a bridge.""" + ipr = pyroute2.IPRoute() + # Create the bridge + ipr.link('add', + ifname=name, + kind='bridge', + br_vlan_filtering=filtering) + idx = ipr.link_lookup(ifname=name)[0] + # Attach interfaces + for iface in ifaces: + port = ipr.link_lookup(ifname=iface)[0] + ipr.link('set', index=port, master=idx) + # Put the bridge up + ipr.link('set', index=idx, state='up') + return idx + + def _bond_or_team(self, kind, name, *ifaces): + """Create a bond or a team.""" + ipr = pyroute2.RawIPRoute() + # Create the bond + ipr.link('add', + ifname=name, + kind=kind) + idx = ipr.link_lookup(ifname=name)[0] + # Attach interfaces + for iface in ifaces: + slave = ipr.link_lookup(ifname=iface)[0] + ipr.link('set', index=slave, state='down') + ipr.link('set', index=slave, master=idx) + # Put the bond up + ipr.link('set', index=idx, state='up') + return idx + + def team(self, name, *ifaces): + """Create a team.""" + # Unfortunately, pyroute2 will try to run teamd too. This + # doesn't work. + return self._bond_or_team("team", name, *ifaces) + + def bond(self, name, *ifaces): + """Create a bond.""" + return self._bond_or_team("bond", name, *ifaces) + + def dummy(self, name): + """Create a dummy interface.""" + ipr = pyroute2.IPRoute() + ipr.link('add', ifname=name, kind='dummy') + idx = ipr.link_lookup(ifname=name)[0] + ipr.link('set', index=idx, state='up') + return idx + + def vlan(self, name, id, iface): + """Create a VLAN.""" + ipr = pyroute2.IPRoute() + idx = ipr.link_lookup(ifname=iface)[0] + ipr.link('add', + ifname=name, + kind='vlan', + vlan_id=id, + link=idx) + idx = ipr.link_lookup(ifname=name)[0] + ipr.link('set', index=idx, state='up') + return idx + + def bridge_vlan(self, iface, vid, tagged=True, pvid=False, remove=False): + ipr = pyroute2.IPRoute() + idx = ipr.link_lookup(ifname=iface)[0] + flags = [] + if not tagged: + flags.append('untagged') + if pvid: + flags.append('pvid') + if not remove: + ipr.vlan_filter('del', index=idx, vlan_info={"vid": 1}) + ipr.vlan_filter('add' if not remove else 'del', index=idx, + vlan_info={'vid': vid, + 'flags': flags}) + + def up(self, name): + ipr = pyroute2.IPRoute() + idx = ipr.link_lookup(ifname=name)[0] + ipr.link('set', index=idx, state='up') + + def down(self, name): + ipr = pyroute2.IPRoute() + idx = ipr.link_lookup(ifname=name)[0] + ipr.link('set', index=idx, state='down') + + def remove(self, name): + ipr = pyroute2.IPRoute() + idx = ipr.link_lookup(ifname=name)[0] + ipr.link('del', index=idx) + + def unbridge(self, bridgename, name): + ipr = pyroute2.IPRoute() + idx = ipr.link_lookup(ifname=name)[0] + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 0) + ifr = struct.pack("16si", b"br42", idx) + fcntl.ioctl(s, + 0x89a3, # SIOCBRDELIF + ifr) + s.close() + + +@pytest.fixture +def links(): + return LinksFactory() diff --git a/tests/integration/fixtures/programs.py b/tests/integration/fixtures/programs.py new file mode 100644 index 0000000000000000000000000000000000000000..e389c011e249064a4125152e5a01b4957d087a9c --- /dev/null +++ b/tests/integration/fixtures/programs.py @@ -0,0 +1,472 @@ +import pytest +import glob +import os +import pwd +import grp +import re +import signal +import subprocess +import multiprocessing +import uuid +import time +import platform +import ctypes +from collections import namedtuple + +from .namespaces import mount_proc, mount_tmpfs + +libc = ctypes.CDLL('libc.so.6', use_errno=True) + + +def mount_bind(source, target): + ret = libc.mount(source.encode('ascii'), + target.encode('ascii'), + None, + 4096, # MS_BIND + None) + if ret == -1: + e = ctypes.get_errno() + raise OSError(e, os.strerror(e)) + + +def most_recent(*args): + """Return the most recent files matching one of the provided glob + expression.""" + candidates = [l + for location in args + for l in glob.glob(location)] + candidates.sort(key=lambda x: os.stat(x).st_mtime) + assert len(candidates) > 0 + return candidates[0] + + +libtool_location = most_recent('../../libtool', + '../../*/libtool') +lldpcli_location = most_recent('../../src/client/lldpcli', + '../../*/src/client/lldpcli') +lldpd_location = most_recent('../../src/daemon/lldpd', + '../../*/src/daemon/lldpd') + + +def _replace_file(tmpdir, target, content): + tmpname = str(uuid.uuid1()) + with tmpdir.join(tmpname).open("w") as tmp: + tmp.write(content) + mount_bind(str(tmpdir.join(tmpname)), + target) + + +@pytest.fixture +def replace_file(tmpdir): + """Replace a file by another content by bind-mounting on it.""" + return lambda target, content: _replace_file(tmpdir, target, content) + + +def format_process_output(program, args, result): + """Return a string representing the result of a process.""" + return "\n".join([ + 'P: {} {}'.format(program, " ".join(args)), + 'C: {}'.format(os.getcwd()), + '\n'.join(['O: {}'.format(l) + for l in result.stdout.decode( + 'ascii', 'ignore').strip().split('\n')]), + '\n'.join(['E: {}'.format(l) + for l in result.stderr.decode( + 'ascii', 'ignore').strip().split('\n')]), + 'S: {}'.format(result.returncode), + '']) + + +class LldpdFactory(object): + """Factory for lldpd. When invoked, lldpd will configure the current + namespace to be in a reproducible environment and spawn itself in + the background. On termination, output will be logged to temporary + file. + """ + def __init__(self, tmpdir, config): + """Create a new wrapped program.""" + tmpdir.join('lldpd-outputs').ensure(dir=True) + self.tmpdir = tmpdir + self.config = config + self.pids = [] + self.threads = [] + self.counter = 0 + + def __call__(self, *args, sleep=3, silent=False): + self.counter += 1 + self.setup_namespace("ns-{}".format(self.counter)) + args = (self.config.option.verbose > 2 and "-dddd" or "-dd", + "-L", + lldpcli_location, + "-u", + str(self.tmpdir.join("ns", "lldpd.socket"))) + args + p = subprocess.Popen((libtool_location, 'execute', + lldpd_location) + args, + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + self.pids.append(p.pid) + t = multiprocessing.Process(target=self.run, args=(p, args, silent)) + self.threads.append(t) + t.start() + time.sleep(sleep) + return t + + def run(self, p, args, silent): + stdout, stderr = p.communicate() + self.pids.remove(p.pid) + if not silent: + o = format_process_output("lldpd", + args, + namedtuple('ProcessResult', + ['returncode', + 'stdout', + 'stderr'])( + p.returncode, + stdout, + stderr)) + self.tmpdir.join('lldpd-outputs', '{}-{}'.format( + os.getpid(), + p.pid)).write(o) + + def killall(self): + for p in self.pids[:]: + try: + os.kill(p, signal.SIGTERM) + except ProcessLookupError: + continue + for t in self.threads: + if t.is_alive(): + t.join(1) + for p in self.pids[:]: + try: + os.kill(p, signal.SIGKILL) + except ProcessLookupError: + continue + for t in self.threads: + if t.is_alive(): + t.join(1) + + def setup_namespace(self, name): + # Setup privsep. While not enforced, we assume we are running in a + # throwaway mount namespace. + tmpdir = self.tmpdir + if self.config.lldpd.privsep.enabled: + # Chroot + chroot = self.config.lldpd.privsep.chroot + parent = os.path.abspath(os.path.join(chroot, os.pardir)) + assert os.path.isdir(parent) + mount_tmpfs(parent) + # User/group + user = self.config.lldpd.privsep.user + group = self.config.lldpd.privsep.group + try: + pwd.getpwnam(user) + grp.getgrnam(group) + except KeyError: + passwd = "" + for l in open("/etc/passwd", "r").readlines(): + if not l.startswith("{}:".format(user)): + passwd += l + passwd += "{}:x:39861:39861::{}:/bin/false\n".format( + user, chroot) + fgroup = "" + for l in open("/etc/group", "r").readlines(): + if not l.startswith("{}:".format(group)): + fgroup += l + fgroup += "{}:x:39861:\n".format(group) + _replace_file(tmpdir, "/etc/passwd", passwd) + _replace_file(tmpdir, "/etc/group", fgroup) + + # We also need a proper /etc/os-release + _replace_file(tmpdir, "/etc/os-release", + """PRETTY_NAME="Spectacular GNU/Linux 2016" +NAME="Spectacular GNU/Linux" +ID=spectacular +HOME_URL="https://www.example.com/spectacular" +SUPPORT_URL="https://www.example.com/spectacular/support" +BUG_REPORT_URL="https://www.example.com/spectacular/bugs" +""") + + # We also need a proper name + subprocess.check_call(["hostname", name]) + + # And we need to ensure name resolution is sane + _replace_file(tmpdir, "/etc/hosts", + """ +127.0.0.1 localhost.localdomain localhost +127.0.1.1 {name}.example.com {name} +::1 ip6-localhost ip6-loopback +""".format(name=name)) + _replace_file(tmpdir, "/etc/nsswitch.conf", + """ +passwd: files +group: files +shadow: files +hosts: files +networks: files +protocols: files +services: files +""") + + # Remove any config + path = os.path.join(self.config.lldpd.confdir, "lldpd.conf") + if os.path.isfile(path): + _replace_file(tmpdir, path, "") + path = os.path.join(self.config.lldpd.confdir, "lldpd.d") + if os.path.isdir(path): + mount_tmpfs(path) + + +@pytest.fixture() +def lldpd(request, tmpdir): + """Execute ``lldpd``.""" + p = LldpdFactory(tmpdir, request.config) + request.addfinalizer(p.killall) + return p + + +@pytest.fixture() +def lldpd1(lldpd, links, namespaces): + """Shortcut for a first receive-only lldpd daemon.""" + links(namespaces(1), namespaces(2)) + with namespaces(1): + lldpd("-r") + + +@pytest.fixture() +def lldpcli(request, tmpdir): + """Execute ``lldpcli``.""" + socketdir = tmpdir.join("ns", "lldpd.socket") + count = [0] + + def run(*args): + cargs = ("-u", str(socketdir)) + args + p = subprocess.Popen((libtool_location, 'execute', + lldpcli_location) + cargs, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + stdout, stderr = p.communicate(timeout=30) + result = namedtuple('ProcessResult', + ['returncode', 'stdout', 'stderr'])( + p.returncode, stdout, stderr) + request.node.add_report_section( + 'run', 'lldpcli output {}'.format(count[0]), + format_process_output("lldpcli", cargs, result)) + count[0] += 1 + # When keyvalue is requested, return a formatted result + if args[:2] == ("-f", "keyvalue"): + assert result.returncode == 0 + out = {} + for k, v in [l.split('=', 2) + for l in result.stdout.decode('ascii').split("\n") + if '=' in l]: + if k in out: + out[k] += [v] + else: + out[k] = [v] + for k in out: + if len(out[k]) == 1: + out[k] = out[k][0] + return out + # Otherwise, return the named tuple + return result + return run + + +@pytest.fixture() +def snmpd(request, tmpdir): + """Execute ``snmpd``.""" + count = [0] + + def run(*args): + conffile = tmpdir.join("ns", "snmpd.conf") + pidfile = tmpdir.join("ns", "snmpd.pid") + with conffile.open("w") as f: + f.write(""" +rocommunity public +rwcommunity private +master agentx +trap2sink 127.0.0.1 +""") + sargs = ("-I", + "snmp_mib,sysORTable" + ",usmConf,usmStats,usmUser" + ",vacm_conf,vacm_context,vacm_vars", + "-Ln", + "-p", str(pidfile), + "-C", "-c", str(conffile)) + try: + p = subprocess.Popen(("snmpd",) + sargs + args, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + except OSError as e: + if e.errno == os.errno.ENOENT: + pytest.skip("snmpd not present") + return + raise e + stdout, stderr = p.communicate(timeout=5) + result = namedtuple('ProcessResult', + ['returncode', 'stdout', 'stderr'])( + p.returncode, stdout, stderr) + request.node.add_report_section( + 'run', 'snmpd output {}'.format(count[0]), + format_process_output("snmpd", sargs, result)) + count[0] += 1 + time.sleep(1) + + def kill(): + try: + with pidfile.open("r") as p: + os.kill(int(p.read())) + except: + pass + request.addfinalizer(kill) + + return run + + +@pytest.fixture() +def snmpwalk(): + def run(*args): + try: + p = subprocess.Popen(("env", "MIBDIRS=", + "snmpwalk", + "-v2c", "-c", "private", + "-Ob", "-Oe", "-On", + "localhost") + args, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + except OSError as e: + if e.errno == os.errno.ENOENT: + pytest.skip("snmpwalk not present") + return + raise e + stdout, stderr = p.communicate(timeout=30) + result = namedtuple('ProcessResult', + ['returncode', 'stdout', 'stderr'])( + p.returncode, stdout, stderr) + # When keyvalue is requested, return a formatted result + assert result.returncode == 0 + out = {} + for k, v in [l.split(' = ', 2) + for l in result.stdout.decode('ascii').split("\n") + if ' = ' in l]: + out[k] = v + return out + return run + + +def pytest_runtest_makereport(item, call): + """Collect outputs written to tmpdir and put them in report.""" + # Only do that after tests are run, but not on teardown (too late) + if call.when != 'call': + return + # We can't wait for teardown, kill any running lldpd daemon right + # now. Otherwise, we won't get any output. + if "lldpd" in item.fixturenames and "lldpd" in item.funcargs: + lldpd = item.funcargs["lldpd"] + lldpd.killall() + if "tmpdir" in item.fixturenames and "tmpdir" in item.funcargs: + tmpdir = item.funcargs["tmpdir"] + if tmpdir.join('lldpd-outputs').check(dir=1): + for path in tmpdir.join('lldpd-outputs').visit(): + item.add_report_section( + call.when, + 'lldpd {}'.format(path.basename), + path.read()) + + +def pytest_configure(config): + """Put lldpd/lldpcli configuration into the config object.""" + output = subprocess.check_output([lldpcli_location, "-vv"]) + output = output.decode('ascii') + config.lldpcli = namedtuple( + 'lldpcli', + ['version', + 'outputs'])( + re.search( + r"^lldpcli (.*)$", output, + re.MULTILINE).group(1), + re.search( + r"^Additional output formats:\s+(.*)$", + output, + re.MULTILINE).group(1).split(", ")) + output = subprocess.check_output([lldpd_location, "-vv"]) + output = output.decode('ascii') + if {"enabled": True, + "disabled": False}[re.search(r"^Privilege separation:\s+(.*)$", + output, re.MULTILINE).group(1)]: + privsep = namedtuple('privsep', + ['user', + 'group', + 'chroot', + 'enabled'])( + re.search( + r"^Privilege separation user:\s+(.*)$", + output, + re.MULTILINE).group(1), + re.search( + r"^Privilege separation group:\s+(.*)$", + output, + re.MULTILINE).group(1), + re.search( + r"^Privilege separation chroot:\s(.*)$", + output, + re.MULTILINE).group(1), + True) + else: + privsep = namedtuple('privsep', + ['enabled'])(False) + config.lldpd = namedtuple('lldpd', + ['features', + 'protocols', + 'confdir', + 'snmp', + 'privsep', + 'version'])( + re.search( + r"^Additional LLDP features:\s+(.*)$", + output, + re.MULTILINE).group(1).split(", "), + re.search( + r"^Additional protocols:\s+(.*)$", + output, + re.MULTILINE).group(1).split(", "), + re.search( + r"^Configuration directory:\s+(.*)$", + output, re.MULTILINE).group(1), + {"yes": True, + "no": False}[re.search( + r"^SNMP support:\s+(.*)$", + output, + re.MULTILINE).group(1)], + privsep, + re.search(r"^lldpd (.*)$", + output, re.MULTILINE).group(1)) + + # Also retrieve some kernel capabilities + features = [] + for feature in ["rtnl-link-team"]: + ret = subprocess.call(["/sbin/modprobe", "--quiet", "--dry-run", + feature]) + if ret == 0: + features.append(feature) + config.kernel = namedtuple('kernel', + ['features', + 'version'])( + features, + os.uname().release) + + +def pytest_report_header(config): + """Report lldpd/lldpcli version and configuration.""" + print('lldpd: {} {}'.format(config.lldpd.version, + ", ".join(config.lldpd.protocols + + config.lldpd.features))) + print('lldpcli: {} {}'.format(config.lldpcli.version, + ", ".join(config.lldpcli.outputs))) + print('kernel: {} {}'.format(config.kernel.version, + ", ".join(config.kernel.features))) + print('{}: {} {} {}'.format(platform.system().lower(), + platform.release(), + platform.version(), + platform.machine())) diff --git a/tests/integration/requirements.txt b/tests/integration/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..6724e8a83ade66fe7588bb85c6e24faaffdd76f2 --- /dev/null +++ b/tests/integration/requirements.txt @@ -0,0 +1,5 @@ +pyroute2==0.5.2 +pytest==3.10.1 +pytest-helpers-namespace==2019.1.8 +pytest-xdist==1.26.1 +scapy==2.4.3 diff --git a/tests/integration/test_basic.py b/tests/integration/test_basic.py new file mode 100644 index 0000000000000000000000000000000000000000..b8cd49b1faa489aa419fb20ce5a89aa61ae55bc7 --- /dev/null +++ b/tests/integration/test_basic.py @@ -0,0 +1,509 @@ +import time +import pytest +import pyroute2 +import scapy.all +import scapy.contrib.lldp + +def test_one_neighbor(lldpd1, lldpd, lldpcli, namespaces): + with namespaces(2): + lldpd() + with namespaces(1): + out = lldpcli("-f", "keyvalue", "show", "neighbors") + assert out['lldp.eth0.age'].startswith('0 day, 00:00:') + assert out['lldp.eth0.chassis.descr'].startswith( + "Spectacular GNU/Linux 2016 Linux") + assert 'lldp.eth0.chassis.Router.enabled' in out + assert 'lldp.eth0.chassis.Station.enabled' in out + del out['lldp.eth0.age'] + del out['lldp.eth0.chassis.descr'] + del out['lldp.eth0.chassis.Router.enabled'] + del out['lldp.eth0.chassis.Station.enabled'] + assert out == {"lldp.eth0.via": "LLDP", + "lldp.eth0.rid": "1", + "lldp.eth0.chassis.mac": "00:00:00:00:00:02", + "lldp.eth0.chassis.name": "ns-2.example.com", + "lldp.eth0.chassis.mgmt-ip": "fe80::200:ff:fe00:2", + "lldp.eth0.chassis.mgmt-iface": "2", + "lldp.eth0.chassis.Bridge.enabled": "off", + "lldp.eth0.chassis.Wlan.enabled": "off", + "lldp.eth0.port.mac": "00:00:00:00:00:02", + "lldp.eth0.port.descr": "eth1", + "lldp.eth0.port.ttl": "120"} + + +@pytest.mark.parametrize("neighbors", (5, 10, 20)) +def test_several_neighbors(lldpd, lldpcli, links, namespaces, neighbors): + for i in range(2, neighbors + 1): + links(namespaces(1), namespaces(i)) + for i in range(1, neighbors + 1): + with namespaces(i): + lldpd(sleep=(i == 1 and 2 or 0), + silent=True) + time.sleep(10) + with namespaces(1): + out = lldpcli("-f", "keyvalue", "show", "neighbors") + for i in range(2, neighbors + 1): + assert out['lldp.eth{}.chassis.name'.format((i - 2)*2)] == \ + 'ns-{}.example.com'.format(i) + + +def test_one_interface(lldpd1, lldpd, lldpcli, namespaces): + with namespaces(2): + lldpd() + with namespaces(1): + out = lldpcli("-f", "keyvalue", "show", "interfaces") + assert out['lldp.eth0.chassis.descr'].startswith( + "Spectacular GNU/Linux 2016 Linux") + assert 'lldp.eth0.chassis.Router.enabled' in out + assert 'lldp.eth0.chassis.Station.enabled' in out + del out['lldp.eth0.chassis.descr'] + del out['lldp.eth0.chassis.Router.enabled'] + del out['lldp.eth0.chassis.Station.enabled'] + assert out == {"lldp.eth0.status": "RX and TX", + "lldp.eth0.chassis.mac": "00:00:00:00:00:01", + "lldp.eth0.chassis.name": "ns-1.example.com", + "lldp.eth0.chassis.mgmt-ip": "fe80::200:ff:fe00:1", + "lldp.eth0.chassis.mgmt-iface": "3", + "lldp.eth0.chassis.Bridge.enabled": "off", + "lldp.eth0.chassis.Wlan.enabled": "off", + "lldp.eth0.port.mac": "00:00:00:00:00:01", + "lldp.eth0.port.descr": "eth0", + "lldp.eth0.ttl.ttl": "120"} + + +@pytest.mark.parametrize("interfaces", (5, 10, 20)) +def test_several_interfaces(lldpd, lldpcli, links, namespaces, interfaces): + for i in range(2, interfaces + 1): + links(namespaces(1), namespaces(i)) + for i in range(1, interfaces + 1): + with namespaces(i): + lldpd() + with namespaces(1): + out = lldpcli("-f", "keyvalue", "show", "interfaces") + for i in range(2, interfaces + 1): + assert out['lldp.eth{}.chassis.mac'.format((i - 2)*2)] == \ + '00:00:00:00:00:01' + assert out['lldp.eth{}.port.mac'.format((i - 2)*2)] == \ + '00:00:00:00:00:{num:02x}'.format(num=(i - 2)*2 + 1) + + +def test_different_mtu(lldpd, lldpcli, links, namespaces): + links(namespaces(1), namespaces(2), mtu=1500) + links(namespaces(1), namespaces(2), mtu=9000) + with namespaces(1): + lldpd() + with namespaces(2): + lldpd() + with namespaces(1): + out = lldpcli("-f", "keyvalue", "show", "interfaces") + assert out['lldp.eth0.chassis.mac'] == '00:00:00:00:00:01' + assert out['lldp.eth2.chassis.mac'] == '00:00:00:00:00:01' + + +def test_overrided_description(lldpd1, lldpd, lldpcli, namespaces): + with namespaces(2): + lldpd("-S", "Modified description") + with namespaces(1): + out = lldpcli("-f", "keyvalue", "show", "neighbors") + assert out['lldp.eth0.chassis.descr'] == "Modified description" + + +def test_overrided_description2(lldpd1, lldpd, lldpcli, namespaces): + with namespaces(2): + lldpd() + lldpcli("configure", "system", "description", "Modified description") + lldpcli("update") + time.sleep(1) + with namespaces(1): + out = lldpcli("-f", "keyvalue", "show", "neighbors") + assert out['lldp.eth0.chassis.descr'] == "Modified description" + + +def test_overrided_chassisid(lldpd1, lldpd, lldpcli, namespaces): + with namespaces(2): + lldpd() + lldpcli("configure", "system", "chassisid", "Modified chassis ID") + lldpcli("update") + time.sleep(1) + with namespaces(1): + out = lldpcli("-f", "keyvalue", "show", "neighbors") + assert out['lldp.eth0.chassis.local'] == "Modified chassis ID" + + +def test_overrided_chassisid_kept(lldpd1, lldpd, lldpcli, namespaces, links): + with namespaces(2): + lldpd() + lldpcli("configure", "system", "chassisid", "Modified chassis ID") + links.down("eth1") + time.sleep(1) + links.up("eth1") + time.sleep(1) + lldpcli("update") + time.sleep(1) + with namespaces(1): + out = lldpcli("-f", "keyvalue", "show", "neighbors") + assert out['lldp.eth0.chassis.local'] == "Modified chassis ID" + + +def test_overrided_chassisid_reverse(lldpd1, lldpd, lldpcli, namespaces): + with namespaces(2): + lldpd() + lldpcli("configure", "system", "chassisid", "Modified chassis ID") + lldpcli("unconfigure", "system", "chassisid") + lldpcli("update") + time.sleep(1) + with namespaces(1): + out = lldpcli("-f", "keyvalue", "show", "neighbors") + assert out['lldp.eth0.chassis.mac'] == "00:00:00:00:00:02" + + +def test_hide_kernel(lldpd1, lldpd, lldpcli, namespaces): + with namespaces(2): + lldpd("-k") + with namespaces(1): + out = lldpcli("-f", "keyvalue", "show", "neighbors") + assert out["lldp.eth0.chassis.descr"] == \ + "Spectacular GNU/Linux 2016" + + +def test_listen_only(lldpd1, lldpd, lldpcli, namespaces): + with namespaces(2): + lldpd("-r") + with namespaces(1): + out = lldpcli("-f", "keyvalue", "show", "neighbors") + assert out == {} + + +def test_forced_unknown_management_address(lldpd1, lldpd, lldpcli, namespaces): + with namespaces(2): + lldpd("-m", "2001:db8::47") + with namespaces(1): + out = lldpcli("-f", "keyvalue", "show", "neighbors") + assert out["lldp.eth0.chassis.mgmt-ip"] == "2001:db8::47" + assert "lldp.eth0.chassis.mgmt-iface" not in out + + +def test_forced_known_management_address(lldpd1, lldpd, lldpcli, namespaces): + with namespaces(2): + ipr = pyroute2.IPRoute() + idx = ipr.link_lookup(ifname="eth1")[0] + ipr.addr('add', index=idx, address="192.168.14.2", mask=24) + lldpd("-m", "192.168.14.2") + with namespaces(1): + out = lldpcli("-f", "keyvalue", "show", "neighbors") + assert out["lldp.eth0.chassis.mgmt-ip"] == "192.168.14.2" + assert out["lldp.eth0.chassis.mgmt-iface"] == "2" + + +def test_management_address(lldpd1, lldpd, lldpcli, links, namespaces): + with namespaces(2): + ipr = pyroute2.IPRoute() + idx = ipr.link_lookup(ifname="eth1")[0] + ipr.addr('add', index=idx, address="192.168.14.2", mask=24) + ipr.addr('add', index=idx, address="172.25.21.47", mask=24) + lldpd("-m", "172.25.*") + with namespaces(1): + out = lldpcli("-f", "keyvalue", "show", "neighbors") + assert out["lldp.eth0.chassis.mgmt-ip"] == "172.25.21.47" + assert out["lldp.eth0.chassis.mgmt-iface"] == "2" + + +def test_management_interface(lldpd1, lldpd, lldpcli, links, namespaces): + links(namespaces(1), namespaces(2), 4) + with namespaces(2): + ipr = pyroute2.IPRoute() + idx = ipr.link_lookup(ifname="eth1")[0] + ipr.addr('add', index=idx, address="192.168.14.2", mask=24) + idx = ipr.link_lookup(ifname="eth3")[0] + ipr.addr('add', index=idx, address="172.25.21.47", mask=24) + lldpd("-m", "eth3") + with namespaces(1): + out = lldpcli("-f", "keyvalue", "show", "neighbors") + assert out["lldp.eth0.chassis.mgmt-ip"] == ["172.25.21.47", + "fe80::200:ff:fe00:4"] + assert out["lldp.eth0.chassis.mgmt-iface"] == ["4", "4"] + + +def test_change_management_address(lldpd1, lldpd, lldpcli, links, namespaces): + with namespaces(2): + ipr = pyroute2.IPRoute() + idx = ipr.link_lookup(ifname="eth1")[0] + ipr.addr('add', index=idx, address="192.168.14.2", mask=24) + lldpd("-m", "192.168.*") + # We need a short TX interval as updating the IP address + # doesn't trigger a resend. + lldpcli("configure", "lldp", "tx-interval", "2") + with namespaces(1): + out = lldpcli("-f", "keyvalue", "show", "neighbors") + assert out["lldp.eth0.chassis.mgmt-ip"] == "192.168.14.2" + assert out["lldp.eth0.chassis.mgmt-iface"] == "2" + with namespaces(2): + ipr.addr('del', index=idx, address="192.168.14.2", mask=24) + ipr.addr('add', index=idx, address="192.168.14.5", mask=24) + time.sleep(5) + with namespaces(1): + out = lldpcli("-f", "keyvalue", "show", "neighbors") + assert out["lldp.eth0.chassis.mgmt-ip"] == "192.168.14.5" + assert out["lldp.eth0.chassis.mgmt-iface"] == "2" + + +def test_portid_subtype_ifname(lldpd1, lldpd, lldpcli, namespaces): + with namespaces(2): + lldpd() + lldpcli("configure", "lldp", "portidsubtype", "ifname") + time.sleep(3) + with namespaces(1): + out = lldpcli("-f", "keyvalue", "show", "neighbors") + assert out["lldp.eth0.port.ifname"] == "eth1" + assert out["lldp.eth0.port.descr"] == "eth1" + + +def test_portid_subtype_with_alias(lldpd1, lldpd, lldpcli, links, namespaces): + with namespaces(2): + ipr = pyroute2.IPRoute() + idx = ipr.link_lookup(ifname="eth1")[0] + ipr.link('set', index=idx, ifalias="alias of eth1") + lldpd() + with namespaces(1): + out = lldpcli("-f", "keyvalue", "show", "neighbors") + assert out["lldp.eth0.port.ifname"] == "eth1" + assert out["lldp.eth0.port.descr"] == "alias of eth1" + + +def test_portid_subtype_macaddress(lldpd1, lldpd, lldpcli, links, namespaces): + with namespaces(2): + ipr = pyroute2.IPRoute() + idx = ipr.link_lookup(ifname="eth1")[0] + ipr.link('set', index=idx, ifalias="alias of eth1") + lldpd() + lldpcli("configure", "lldp", "portidsubtype", "macaddress") + time.sleep(3) + with namespaces(1): + out = lldpcli("-f", "keyvalue", "show", "neighbors") + assert out["lldp.eth0.port.mac"] == "00:00:00:00:00:02" + assert out["lldp.eth0.port.descr"] == "eth1" + + +def test_portid_subtype_local(lldpd1, lldpd, lldpcli, namespaces): + with namespaces(2): + lldpd() + lldpcli("configure", "lldp", "portidsubtype", "local", "localname") + time.sleep(3) + with namespaces(1): + out = lldpcli("-f", "keyvalue", "show", "neighbors") + assert out["lldp.eth0.port.local"] == "localname" + assert out["lldp.eth0.port.descr"] == "eth1" + + +def test_portid_subtype_local_with_description(lldpd1, lldpd, lldpcli, namespaces): + with namespaces(2): + lldpd() + lldpcli("configure", "lldp", "portidsubtype", "local", "localname", "description", "localdescription") + time.sleep(3) + with namespaces(1): + out = lldpcli("-f", "keyvalue", "show", "neighbors") + assert out["lldp.eth0.port.local"] == "localname" + assert out["lldp.eth0.port.descr"] == "localdescription" + + +def test_portdescription(lldpd1, lldpd, lldpcli, namespaces): + with namespaces(2): + lldpd() + lldpcli("configure", "lldp", "portdescription", "localdescription") + time.sleep(3) + with namespaces(1): + out = lldpcli("-f", "keyvalue", "show", "neighbors") + assert out["lldp.eth0.port.descr"] == "localdescription" + + +def test_portid_subtype_local_with_alias(lldpd1, lldpd, lldpcli, namespaces): + with namespaces(2): + ipr = pyroute2.IPRoute() + idx = ipr.link_lookup(ifname="eth1")[0] + ipr.link('set', index=idx, ifalias="alias of eth1") + lldpd() + lldpcli("configure", "lldp", "portidsubtype", "local", "localname") + time.sleep(3) + with namespaces(1): + out = lldpcli("-f", "keyvalue", "show", "neighbors") + assert out["lldp.eth0.port.local"] == "localname" + assert out["lldp.eth0.port.descr"] == "alias of eth1" + + +def test_port_status_txonly(lldpd, lldpcli, namespaces, links): + links(namespaces(1), namespaces(2)) + with namespaces(1): + lldpd() + lldpcli("configure", "lldp", "status", "tx-only") + with namespaces(2): + lldpd() + with namespaces(1): + out = lldpcli("-f", "keyvalue", "show", "neighbors") + assert out == {} + lldpcli("update") + with namespaces(2): + out = lldpcli("-f", "keyvalue", "show", "neighbors") + assert out["lldp.eth1.chassis.mac"] == "00:00:00:00:00:01" + + +def test_port_status_rxonly(lldpd, lldpcli, namespaces, links): + links(namespaces(1), namespaces(2)) + with namespaces(1): + lldpd() + lldpcli("configure", "lldp", "status", "rx-only") + with namespaces(2): + lldpd() + with namespaces(1): + out = lldpcli("-f", "keyvalue", "show", "neighbors") + assert out["lldp.eth0.chassis.mac"] == "00:00:00:00:00:02" + lldpcli("update") + with namespaces(2): + out = lldpcli("-f", "keyvalue", "show", "neighbors") + assert out == {} + + +def test_port_status_rxandtx(lldpd, lldpcli, namespaces, links): + links(namespaces(1), namespaces(2)) + with namespaces(1): + lldpd() + lldpcli("configure", "lldp", "status", "rx-and-tx") # noop + with namespaces(2): + lldpd() + with namespaces(1): + out = lldpcli("-f", "keyvalue", "show", "neighbors") + assert out["lldp.eth0.chassis.mac"] == "00:00:00:00:00:02" + lldpcli("update") + with namespaces(2): + out = lldpcli("-f", "keyvalue", "show", "neighbors") + assert out["lldp.eth1.chassis.mac"] == "00:00:00:00:00:01" + + +def test_port_status_disabled(lldpd, lldpcli, namespaces, links): + links(namespaces(1), namespaces(2)) + with namespaces(1): + lldpd() + lldpcli("configure", "lldp", "status", "disabled") + with namespaces(2): + lldpd() + with namespaces(1): + out = lldpcli("-f", "keyvalue", "show", "neighbors") + assert out == {} + lldpcli("update") + with namespaces(2): + out = lldpcli("-f", "keyvalue", "show", "neighbors") + assert out == {} + + +def test_port_vlan_tx(lldpd1, lldpd, lldpcli, namespaces): + with namespaces(1): + lldpd() + lldpcli("configure", "ports", "eth0", "lldp", "vlan-tx", "100", "priority", "5", "dei", "1") + out = lldpcli("-f", "keyvalue", "show", "interfaces", "ports", "eth0") + assert out["lldp.eth0.port.vlanTX.id"] == '100' + assert out["lldp.eth0.port.vlanTX.prio"] == '5' + assert out["lldp.eth0.port.vlanTX.dei"] == '1' + # unconfigure VLAN TX + lldpcli("unconfigure", "ports", "eth0", "lldp", "vlan-tx") + out = lldpcli("-f", "keyvalue", "show", "interfaces", "ports", "eth0") + assert not "lldp.eth0.port.vlanTX.id" in out + assert not "lldp.eth0.port.vlanTX.prio" in out + assert not "lldp.eth0.port.vlanTX.dei" in out + + +def test_set_interface_alias(lldpd1, lldpd, lldpcli, namespaces): + with namespaces(1): + lldpcli("configure", "system", "interface", "description") + with namespaces(2): + lldpd() + with namespaces(1): + ipr = pyroute2.IPRoute() + link = ipr.link('get', ifname='eth0')[0] + assert link.get_attr('IFLA_IFALIAS') == 'lldpd: connected to ns-2.example.com' + + +def test_lldpdu_shutdown(lldpd, lldpcli, namespaces, links): + links(namespaces(1), namespaces(2)) + links(namespaces(1), namespaces(2)) + with namespaces(1): + lldpd() + # From https://github.com/lldpd/lldpd/issues/348 + frm_fa01 = scapy.all.Ether( + src='04:fe:7f:00:00:01', + dst=scapy.contrib.lldp.LLDP_NEAREST_BRIDGE_MAC) / \ + scapy.contrib.lldp.LLDPDUChassisID( + subtype=scapy.contrib.lldp.LLDPDUChassisID.SUBTYPE_MAC_ADDRESS, + id=b'\x04\xfe\x7f\x00\x00\x00') / \ + scapy.contrib.lldp.LLDPDUPortID( + subtype=scapy.contrib.lldp.LLDPDUPortID.SUBTYPE_INTERFACE_NAME, + id='Fa0/1') / \ + scapy.contrib.lldp.LLDPDUTimeToLive(ttl=65535) / \ + scapy.contrib.lldp.LLDPDUSystemName( + system_name='this info should not disappear') / \ + scapy.contrib.lldp.LLDPDUEndOfLLDPDU() + frm_fa01 = frm_fa01.build() + frm_fa01 = scapy.all.Ether(frm_fa01) + + frm_fa02 = scapy.all.Ether( + src='04:fe:7f:00:00:02', + dst=scapy.contrib.lldp.LLDP_NEAREST_BRIDGE_MAC) / \ + scapy.contrib.lldp.LLDPDUChassisID( + subtype=scapy.contrib.lldp.LLDPDUChassisID.SUBTYPE_MAC_ADDRESS, + id=b'\x04\xfe\x7f\x00\x00\x00') / \ + scapy.contrib.lldp.LLDPDUPortID( + subtype=scapy.contrib.lldp.LLDPDUPortID.SUBTYPE_INTERFACE_NAME, + id='Fa0/2') / \ + scapy.contrib.lldp.LLDPDUTimeToLive(ttl=65535) / \ + scapy.contrib.lldp.LLDPDUSystemName( + system_name='this info should not disappear') / \ + scapy.contrib.lldp.LLDPDUEndOfLLDPDU() + frm_fa02 = frm_fa02.build() + frm_fa02 = scapy.all.Ether(frm_fa02) + + frm_shut_fa01 = scapy.all.Ether( + src='04:fe:7f:00:00:01', + dst=scapy.contrib.lldp.LLDP_NEAREST_BRIDGE_MAC) / \ + scapy.contrib.lldp.LLDPDUChassisID( + subtype=scapy.contrib.lldp.LLDPDUChassisID.SUBTYPE_MAC_ADDRESS, + id=b'\x04\xfe\x7f\x00\x00\x00') / \ + scapy.contrib.lldp.LLDPDUPortID( + subtype=scapy.contrib.lldp.LLDPDUPortID.SUBTYPE_INTERFACE_NAME, + id='Fa0/1') / \ + scapy.contrib.lldp.LLDPDUTimeToLive(ttl=0) / \ + scapy.contrib.lldp.LLDPDUEndOfLLDPDU() + frm_shut_fa01 = frm_shut_fa01.build() + frm_shut_fa01 = scapy.all.Ether(frm_shut_fa01) + + with namespaces(2): + scapy.all.sendp(frm_fa01, iface='eth1') + scapy.all.sendp(frm_fa02, iface='eth3') + time.sleep(2) + with namespaces(1): + out = lldpcli("-f", "keyvalue", "show", "neighbors") + del out['lldp.eth0.age'] + del out['lldp.eth2.age'] + assert out == { + "lldp.eth0.via": "LLDP", + "lldp.eth0.rid": "1", + "lldp.eth0.chassis.mac": "04:fe:7f:00:00:00", + "lldp.eth0.chassis.name": "this info should not disappear", + "lldp.eth0.port.ifname": "Fa0/1", + "lldp.eth0.port.ttl": "65535", + "lldp.eth2.via": "LLDP", + "lldp.eth2.rid": "1", + "lldp.eth2.chassis.mac": "04:fe:7f:00:00:00", + "lldp.eth2.chassis.name": "this info should not disappear", + "lldp.eth2.port.ifname": "Fa0/2", + "lldp.eth2.port.ttl": "65535"} + with namespaces(2): + scapy.all.sendp(frm_shut_fa01, iface='eth1') + time.sleep(2) + with namespaces(1): + out = lldpcli("-f", "keyvalue", "show", "neighbors") + del out['lldp.eth2.age'] + assert out == { + "lldp.eth2.via": "LLDP", + "lldp.eth2.rid": "1", + "lldp.eth2.chassis.mac": "04:fe:7f:00:00:00", + "lldp.eth2.chassis.name": "this info should not disappear", + "lldp.eth2.port.ifname": "Fa0/2", + "lldp.eth2.port.ttl": "65535"} diff --git a/tests/integration/test_custom.py b/tests/integration/test_custom.py new file mode 100644 index 0000000000000000000000000000000000000000..a2d6dc79b7e61abbe180c3cd355520aa236cee0c --- /dev/null +++ b/tests/integration/test_custom.py @@ -0,0 +1,70 @@ +import pytest +import shlex +import time + + +@pytest.mark.skipif("'Custom TLV' not in config.lldpd.features", + reason="Custom TLV not supported") +@pytest.mark.parametrize("commands, expected", [ + (["oui 33,44,55 subtype 44"], + {'unknown-tlv.oui': '33,44,55', + 'unknown-tlv.subtype': '44', + 'unknown-tlv.len': '0'}), + (["oui 33,44,55 subtype 44 oui-info 45,45,45,45,45"], + {'unknown-tlv.oui': '33,44,55', + 'unknown-tlv.subtype': '44', + 'unknown-tlv.len': '5', + 'unknown-tlv': '45,45,45,45,45'}), + (["oui 33,44,55 subtype 44 oui-info 45,45,45,45,45", + "add oui 33,44,55 subtype 44 oui-info 55,55,55,55,55", + "add oui 33,44,55 subtype 55 oui-info 65,65,65,65,65"], + {'unknown-tlv.oui': ['33,44,55', '33,44,55', '33,44,55'], + 'unknown-tlv.subtype': ['44', '44', '55'], + 'unknown-tlv.len': ['5', '5', '5'], + 'unknown-tlv': ['45,45,45,45,45', + '55,55,55,55,55', + '65,65,65,65,65']}), + (["oui 33,44,55 subtype 44 oui-info 45,45,45,45,45", + "add oui 33,44,55 subtype 55 oui-info 65,65,65,65,65", + "replace oui 33,44,55 subtype 44 oui-info 66,66,66,66,66"], + {'unknown-tlv.oui': ['33,44,55', '33,44,55'], + 'unknown-tlv.subtype': ['55', '44'], + 'unknown-tlv.len': ['5', '5'], + 'unknown-tlv': ['65,65,65,65,65', + '66,66,66,66,66']}), + (["add oui 33,44,55 subtype 55 oui-info 65,65,65,65,65", + "replace oui 33,44,55 subtype 44 oui-info 66,66,66,66,66"], + {'unknown-tlv.oui': ['33,44,55', '33,44,55'], + 'unknown-tlv.subtype': ['55', '44'], + 'unknown-tlv.len': ['5', '5'], + 'unknown-tlv': ['65,65,65,65,65', + '66,66,66,66,66']}), + (["oui 33,44,55 subtype 44 oui-info 45,45,45,45,45", + "add oui 33,44,55 subtype 55 oui-info 55,55,55,55,55", + "-oui 33,44,55 subtype 55"], + {'unknown-tlv.oui': '33,44,55', + 'unknown-tlv.subtype': '44', + 'unknown-tlv.len': '5', + 'unknown-tlv': '45,45,45,45,45'}), + (["oui 33,44,55 subtype 44 oui-info 45,45,45,45,45", + "add oui 33,44,55 subtype 55 oui-info 65,65,65,65,65", + "-"], + {})]) +def test_custom_tlv(lldpd1, lldpd, lldpcli, namespaces, + commands, expected): + with namespaces(2): + lldpd() + for command in commands: + result = lldpcli( + *shlex.split("{}configure lldp custom-tlv {}".format( + command.startswith("-") and "un" or "", + command.lstrip("-")))) + assert result.returncode == 0 + time.sleep(3) + with namespaces(1): + pfx = "lldp.eth0.unknown-tlvs." + out = lldpcli("-f", "keyvalue", "show", "neighbors", "details") + out = {k[len(pfx):]: v + for k, v in out.items() + if k.startswith(pfx)} + assert out == expected diff --git a/tests/integration/test_dot1.py b/tests/integration/test_dot1.py new file mode 100644 index 0000000000000000000000000000000000000000..ba16baa4c0967409149a4b3e5344cd25d2f06f35 --- /dev/null +++ b/tests/integration/test_dot1.py @@ -0,0 +1,30 @@ +import pytest + + +@pytest.mark.skipif("'Dot1' not in config.lldpd.features", + reason="Dot1 not supported") +class TestLldpDot1(object): + + def test_one_vlan(self, lldpd1, lldpd, lldpcli, namespaces, links): + with namespaces(2): + links.vlan('vlan100', 100, 'eth1') + lldpd() + with namespaces(1): + out = lldpcli("-f", "keyvalue", "show", "neighbors", "details") + assert out['lldp.eth0.vlan'] == 'vlan100' + assert out['lldp.eth0.vlan.vlan-id'] == '100' + + def test_several_vlans(self, lldpd1, lldpd, lldpcli, namespaces, links): + with namespaces(2): + for v in [100, 200, 300, 4000]: + links.vlan('vlan{}'.format(v), v, 'eth1') + lldpd() + with namespaces(1): + out = lldpcli("-f", "keyvalue", "show", "neighbors", "details") + # We know that lldpd is walking interfaces in index order + assert out['lldp.eth0.vlan'] == \ + ['vlan100', 'vlan200', 'vlan300', 'vlan4000'] + assert out['lldp.eth0.vlan.vlan-id'] == \ + ['100', '200', '300', '4000'] + + # TODO: PI and PPVID (but lldpd doesn't know how to generate them) diff --git a/tests/integration/test_dot3.py b/tests/integration/test_dot3.py new file mode 100644 index 0000000000000000000000000000000000000000..1eb5ee46db34a66e4cf9412bee020988870cc728 --- /dev/null +++ b/tests/integration/test_dot3.py @@ -0,0 +1,101 @@ +import pytest +import shlex +import time + + +@pytest.mark.skipif("'Dot3' not in config.lldpd.features", + reason="Dot3 not supported") +class TestLldpDot3(object): + + def test_aggregate(self, lldpd1, lldpd, lldpcli, namespaces, links): + links(namespaces(3), namespaces(2)) # Another link to setup a bond + with namespaces(2): + idx = links.bond('bond42', 'eth1', 'eth3') + lldpd() + with namespaces(1): + out = lldpcli("-f", "keyvalue", "show", "neighbors", "details") + assert out['lldp.eth0.port.descr'] == 'eth1' + assert out['lldp.eth0.port.aggregation'] == str(idx) + + # TODO: unfortunately, with veth, it's not possible to get an + # interface with autoneg. + + @pytest.mark.parametrize("command, expected", [ + ("pse supported enabled paircontrol powerpairs spare class class-3", + {'supported': 'yes', + 'enabled': 'yes', + 'paircontrol': 'yes', + 'device-type': 'PSE', + 'pairs': 'spare', + 'class': 'class 3'}), + ("pd supported enabled powerpairs spare class class-3 type 1 source " + "pse priority low requested 10000 allocated 15000", + {'supported': 'yes', + 'enabled': 'yes', + 'paircontrol': 'no', + 'device-type': 'PD', + 'pairs': 'spare', + 'class': 'class 3', + 'power-type': '1', + 'source': 'Primary power source', + 'priority': 'low', + 'requested': '10000', + 'allocated': '15000'})]) + def test_power(self, lldpd1, lldpd, lldpcli, namespaces, + command, expected): + with namespaces(2): + lldpd() + result = lldpcli( + *shlex.split("configure dot3 power {}".format(command))) + assert result.returncode == 0 + time.sleep(3) + with namespaces(1): + pfx = "lldp.eth0.port.power." + out = lldpcli("-f", "keyvalue", "show", "neighbors", "details") + out = {k[len(pfx):]: v + for k, v in out.items() + if k.startswith(pfx)} + assert out == expected + + def test_autoneg_power(self, links, lldpd, lldpcli, namespaces): + links(namespaces(1), namespaces(2)) + with namespaces(1): + lldpd() + with namespaces(2): + lldpd() + result = lldpcli( + *shlex.split("configure dot3 power pd " + "supported enabled paircontrol " + "powerpairs spare " + "class class-3 " + "type 1 source both priority low " + "requested 20000 allocated 5000")) + assert result.returncode == 0 + time.sleep(2) + with namespaces(1): + # Did we receive the request? + out = lldpcli("-f", "keyvalue", "show", "neighbors", "details") + assert out['lldp.eth0.port.power.requested'] == '20000' + assert out['lldp.eth0.port.power.allocated'] == '5000' + # Send an answer we agree to give almost that (this part + # cannot be automated, lldpd cannot take this decision). + result = lldpcli( + *shlex.split("configure dot3 power pse " + "supported enabled paircontrol powerpairs " + "spare class class-3 " + "type 1 source primary priority high " + "requested 20000 allocated 19000")) + assert result.returncode == 0 + time.sleep(2) + with namespaces(2): + # Did we receive that? + out = lldpcli("-f", "keyvalue", "show", "neighbors", "details") + assert out['lldp.eth1.port.power.requested'] == '20000' + assert out['lldp.eth1.port.power.allocated'] == '19000' + with namespaces(1): + # Did we get an echo back? This part is handled + # automatically by lldpd: we confirm we received the + # answer "immediately". + out = lldpcli("-f", "keyvalue", "show", "neighbors", "details") + assert out['lldp.eth0.port.power.requested'] == '20000' + assert out['lldp.eth0.port.power.allocated'] == '19000' diff --git a/tests/integration/test_interfaces.py b/tests/integration/test_interfaces.py new file mode 100644 index 0000000000000000000000000000000000000000..04233cf301fa320c5650e2f81a3ce837a1f03dd9 --- /dev/null +++ b/tests/integration/test_interfaces.py @@ -0,0 +1,373 @@ +import pytest +import pyroute2 +import time + + +def test_simple_bridge(lldpd1, lldpd, lldpcli, namespaces, links): + links(namespaces(3), namespaces(2)) # Another link to setup a bridge + with namespaces(2): + links.bridge('br42', 'eth1', 'eth3') + lldpd() + with namespaces(1): + out = lldpcli("-f", "keyvalue", "show", "neighbors", "details") + assert out['lldp.eth0.port.descr'] == 'eth1' + assert out['lldp.eth0.chassis.Bridge.enabled'] == 'on' + + +def test_remove_bridge(lldpd, lldpcli, namespaces, links): + links(namespaces(1), namespaces(2)) + links(namespaces(3), namespaces(1)) # Another link to setup a bridge + with namespaces(1): + links.bridge('br42', 'eth0', 'eth3') + lldpd("-r") + with namespaces(2): + lldpd() + time.sleep(2) + lldpcli("pause") # Prevent any updates + with namespaces(1): + out = lldpcli("-f", "keyvalue", "show", "neighbors", "details") + assert out['lldp.eth0.port.descr'] == 'eth1' + # Remove from bridge. We don't use netlink because we wouldn't + # get the wanted effect: we also get a RTM_NEWLINK by doing + # that. Only the bridge ioctl() would prevent that. + links.unbridge('br42', 'eth0') + time.sleep(1) + # Check if we still have eth0 + out = lldpcli("-f", "keyvalue", "show", "neighbors", "details") + assert out['lldp.eth0.port.descr'] == 'eth1' + + +@pytest.mark.skipif("'Dot1' not in config.lldpd.features", + reason="Dot1 not supported") +@pytest.mark.parametrize('when', ['before', 'after']) +def test_bridge_with_vlan(lldpd1, lldpd, lldpcli, namespaces, links, when): + links(namespaces(3), namespaces(2)) # Another link to setup a bridge + with namespaces(2): + if when == 'after': + lldpd() + links.bridge('br42', 'eth1', 'eth3') + links.vlan('vlan100', 100, 'br42') + links.vlan('vlan200', 200, 'br42') + links.vlan('vlan300', 300, 'br42') + if when == 'before': + lldpd() + else: + time.sleep(6) + with namespaces(1): + out = lldpcli("-f", "keyvalue", "show", "neighbors", "details") + assert out['lldp.eth0.port.descr'] == 'eth1' + assert out['lldp.eth0.vlan'] == \ + ['vlan100', 'vlan200', 'vlan300'] + assert out['lldp.eth0.vlan.vlan-id'] == \ + ['100', '200', '300'] + + +@pytest.mark.skipif("'Dot1' not in config.lldpd.features", + reason="Dot1 not supported") +@pytest.mark.parametrize('when', ['before', 'after']) +def test_vlan_aware_bridge_with_vlan(lldpd1, lldpd, lldpcli, namespaces, links, + when): + links(namespaces(3), namespaces(2)) # Another link to setup a bridge + with namespaces(3): + lldpd() + with namespaces(2): + if when == 'after': + lldpd() + links.bridge('br42', 'eth1', 'eth3', filtering=True) + links.bridge_vlan('eth1', 100, pvid=True) + links.bridge_vlan('eth1', 200) + links.bridge_vlan('eth1', 300) + links.bridge_vlan('eth3', 400) + if when == 'before': + lldpd() + else: + time.sleep(6) + with namespaces(1): + out = lldpcli("-f", "keyvalue", "show", "neighbors", "details") + assert out['lldp.eth0.port.descr'] == 'eth1' + assert out['lldp.eth0.vlan'] == \ + ['vlan100', 'vlan200', 'vlan300'] + assert out['lldp.eth0.vlan.vlan-id'] == \ + ['100', '200', '300'] + assert out['lldp.eth0.vlan.pvid'] == \ + ['yes', 'no', 'no'] + with namespaces(3): + out = lldpcli("-f", "keyvalue", "show", "neighbors", "details") + assert out['lldp.eth2.port.descr'] == 'eth3' + assert out['lldp.eth2.vlan'] == \ + 'vlan400' + assert out['lldp.eth2.vlan.vlan-id'] == \ + '400' + assert out['lldp.eth2.vlan.pvid'] == \ + 'no' + + +@pytest.mark.skipif("'Dot1' not in config.lldpd.features", + reason="Dot1 not supported") +@pytest.mark.parametrize('filtering', [False, True]) +def test_vlan_aware_bridge_filtering(lldpd1, lldpd, lldpcli, + namespaces, links, filtering): + links(namespaces(3), namespaces(2)) # Another link to setup a bridge + with namespaces(2): + links.bridge('br42', 'eth1', 'eth3', filtering=filtering) + links.bridge_vlan('eth1', 100, pvid=True) + links.bridge_vlan('eth1', 200) + links.bridge_vlan('eth1', 300) + links.bridge_vlan('eth3', 400) + links.vlan('vlan400', 400, 'br42') + lldpd() + with namespaces(1): + out = lldpcli("-f", "keyvalue", "show", "neighbors", "details") + assert out['lldp.eth0.port.descr'] == 'eth1' + if filtering: + assert out['lldp.eth0.vlan'] == \ + ['vlan100', 'vlan200', 'vlan300'] + assert out['lldp.eth0.vlan.vlan-id'] == \ + ['100', '200', '300'] + assert out['lldp.eth0.vlan.pvid'] == \ + ['yes', 'no', 'no'] + else: + assert out['lldp.eth0.vlan'] == \ + ['vlan100', 'vlan200', 'vlan300', 'vlan400'] + assert out['lldp.eth0.vlan.vlan-id'] == \ + ['100', '200', '300', '400'] + assert out['lldp.eth0.vlan.pvid'] == \ + ['yes', 'no', 'no', 'no'] + + +@pytest.mark.skipif("'Dot3' not in config.lldpd.features", + reason="Dot3 not supported") +@pytest.mark.parametrize('when', ['before', 'after']) +def test_bond(lldpd1, lldpd, lldpcli, namespaces, links, when): + links(namespaces(3), namespaces(2)) # Another link to setup a bond + with namespaces(2): + if when == 'after': + lldpd() + idx = links.bond('bond42', 'eth3', 'eth1') + ipr = pyroute2.IPRoute() + # The bond has the MAC of eth3 + assert ipr.get_links(idx)[0].get_attr('IFLA_ADDRESS') == \ + "00:00:00:00:00:04" + if when == 'before': + lldpd() + else: + time.sleep(6) + with namespaces(1): + out = lldpcli("-f", "keyvalue", "show", "neighbors", "details") + assert out['lldp.eth0.port.descr'] == 'eth1' + assert out['lldp.eth0.port.aggregation'] == str(idx) + # lldpd should be able to retrieve the right MAC + assert out['lldp.eth0.port.mac'] == '00:00:00:00:00:02' + + +@pytest.mark.skipif("'Dot3' not in config.lldpd.features", + reason="Dot3 not supported") +@pytest.mark.skipif("'rtnl-link-team' not in config.kernel.features", + reason="No team support in kernel") +@pytest.mark.parametrize('when', ['before', 'after']) +def test_team(lldpd1, lldpd, lldpcli, namespaces, links, when): + links(namespaces(3), namespaces(2)) # Another link to setup a bond + with namespaces(2): + if when == 'after': + lldpd() + idx = links.team('team42', 'eth3', 'eth1') + if when == 'before': + lldpd() + else: + time.sleep(6) + with namespaces(1): + out = lldpcli("-f", "keyvalue", "show", "neighbors", "details") + assert out['lldp.eth0.port.descr'] == 'eth1' + assert out['lldp.eth0.port.aggregation'] == str(idx) + # Unfortunately, we cannot get the right MAC currently... So, + # this bit will succeed by chance. + assert out['lldp.eth0.port.mac'] == '00:00:00:00:00:02' + + +@pytest.mark.skipif("'Dot3' not in config.lldpd.features", + reason="Dot3 not supported") +@pytest.mark.skipif("'Dot1' not in config.lldpd.features", + reason="Dot1 not supported") +@pytest.mark.parametrize('when', ['before', 'after']) +def test_bond_with_vlan(lldpd1, lldpd, lldpcli, namespaces, links, when): + links(namespaces(3), namespaces(2)) # Another link to setup a bond + with namespaces(2): + if when == 'after': + lldpd() + links.bond('bond42', 'eth3', 'eth1') + links.vlan('vlan300', 300, 'bond42') + links.vlan('vlan301', 301, 'bond42') + links.vlan('vlan302', 302, 'bond42') + links.vlan('vlan303', 303, 'bond42') + if when == 'before': + lldpd() + else: + time.sleep(6) + with namespaces(1): + out = lldpcli("-f", "keyvalue", "show", "neighbors", "details") + assert out['lldp.eth0.port.descr'] == 'eth1' + assert out['lldp.eth0.vlan'] == \ + ['vlan300', 'vlan301', 'vlan302', 'vlan303'] + assert out['lldp.eth0.vlan.vlan-id'] == \ + ['300', '301', '302', '303'] + + +@pytest.mark.skipif("'Dot1' not in config.lldpd.features", + reason="Dot1 not supported") +@pytest.mark.parametrize('when', ['before', 'after']) +def test_just_vlan(lldpd1, lldpd, lldpcli, namespaces, links, when): + with namespaces(2): + if when == 'after': + lldpd() + links.vlan('vlan300', 300, 'eth1') + links.vlan('vlan400', 400, 'eth1') + if when == 'before': + lldpd() + else: + time.sleep(6) + with namespaces(1): + out = lldpcli("-f", "keyvalue", "show", "neighbors", "details") + assert out['lldp.eth0.port.descr'] == 'eth1' + assert out['lldp.eth0.vlan'] == ['vlan300', 'vlan400'] + assert out['lldp.eth0.vlan.vlan-id'] == ['300', '400'] + + +@pytest.mark.skipif("'Dot1' not in config.lldpd.features", + reason="Dot1 not supported") +@pytest.mark.parametrize('kind', ['plain', 'bridge', 'vlan-aware-bridge', 'bond']) +def test_remove_vlan(lldpd1, lldpd, lldpcli, namespaces, links, kind): + with namespaces(2): + if kind == 'bond': + iface = 'bond42' + links.bond(iface, 'eth1') + elif kind in ('bridge', 'vlan-aware-bridge'): + iface = 'bridge42' + links.bridge(iface, 'eth1') + else: + assert kind == 'plain' + iface = 'eth1' + if kind != 'vlan-aware-bridge': + links.vlan('vlan300', 300, iface) + links.vlan('vlan400', 400, iface) + links.vlan('vlan500', 500, iface) + lldpd() + links.remove('vlan300') + else: + links.bridge_vlan('eth1', 300, pvid=True) + links.bridge_vlan('eth1', 400) + links.bridge_vlan('eth1', 500) + lldpd() + links.bridge_vlan('eth1', 300, remove=True) + time.sleep(6) + with namespaces(1): + out = lldpcli("-f", "keyvalue", "show", "neighbors", "details") + assert out['lldp.eth0.port.descr'] == 'eth1' + assert out['lldp.eth0.vlan'] == ['vlan400', 'vlan500'] + assert out['lldp.eth0.vlan.vlan-id'] == ['400', '500'] + assert out['lldp.eth0.vlan.pvid'] == ['no', 'no'] + + +@pytest.mark.skipif("'Dot3' not in config.lldpd.features", + reason="Dot3 not supported") +def test_unenslave_bond(lldpd1, lldpd, lldpcli, namespaces, links): + with namespaces(2): + links.bond('bond42', 'eth1') + lldpd() + links.remove('bond42') + links.up('eth1') + time.sleep(6) + with namespaces(1): + out = lldpcli("-f", "keyvalue", "show", "neighbors", "details") + assert out['lldp.eth0.port.descr'] == 'eth1' + assert 'lldp.eth0.port.aggregation' not in out + + +@pytest.mark.skipif("'Dot1' not in config.lldpd.features", + reason="Dot1 not supported") +def test_unenslave_bond_with_vlan(lldpd1, lldpd, lldpcli, namespaces, links): + with namespaces(2): + links.bond('bond42', 'eth1') + links.vlan('vlan300', 300, 'bond42') + links.vlan('vlan400', 400, 'eth1') + lldpd() + links.remove('bond42') + links.up('eth1') + time.sleep(6) + with namespaces(1): + out = lldpcli("-f", "keyvalue", "show", "neighbors", "details") + assert out['lldp.eth0.port.descr'] == 'eth1' + assert out['lldp.eth0.vlan'] == 'vlan400' + assert out['lldp.eth0.vlan.vlan-id'] == '400' + + +def test_down_then_up(lldpd1, lldpd, lldpcli, namespaces, links): + with namespaces(2): + links.down('eth1') + lldpd() + with namespaces(1): + out = lldpcli("-f", "keyvalue", "show", "neighbors", "details") + assert out == {} + with namespaces(2): + links.up('eth1') + time.sleep(6) + with namespaces(1): + out = lldpcli("-f", "keyvalue", "show", "neighbors", "details") + assert out['lldp.eth0.port.descr'] == 'eth1' + + +@pytest.mark.skipif("'Dot1' not in config.lldpd.features", + reason="Dot1 not supported") +def test_down_then_up_with_vlan(lldpd1, lldpd, lldpcli, namespaces, links): + with namespaces(2): + links.vlan('vlan300', 300, 'eth1') + links.vlan('vlan400', 400, 'eth1') + links.down('eth1') + lldpd() + links.up('eth1') + time.sleep(6) + with namespaces(1): + out = lldpcli("-f", "keyvalue", "show", "neighbors", "details") + assert out['lldp.eth0.port.descr'] == 'eth1' + assert out['lldp.eth0.vlan'] == ['vlan300', 'vlan400'] + assert out['lldp.eth0.vlan.vlan-id'] == ['300', '400'] + + +def test_new_interface(lldpd1, lldpd, lldpcli, namespaces, links): + with namespaces(2): + lldpd() + links(namespaces(1), namespaces(2), 4) + time.sleep(6) + with namespaces(1): + out = lldpcli("-f", "keyvalue", "show", "neighbors", "details") + assert out['lldp.eth0.port.descr'] == 'eth1' + assert out['lldp.eth2.port.descr'] == 'eth3' + assert out['lldp.eth0.rid'] == out['lldp.eth2.rid'] # Same chassis + + +def test_set_interface_description(lldpd, lldpcli, namespaces, links): + links(namespaces(1), namespaces(2)) + with namespaces(1): + # On namespace 1, put neighbor description in interface description + lldpd() + result = lldpcli("configure", "system", "interface", "description") + assert result.returncode == 0 + with namespaces(2): + # On namespace 2, set an interface description + open("/sys/class/net/eth1/ifalias", "w").write("blip blop") + lldpd() + time.sleep(1) + with namespaces(1): + # Alias should be set + alias = open("/sys/class/net/eth0/ifalias").read().strip() + assert alias == "lldpd: connected to ns-2.example.com" + # We should see neighbor interface description + out = lldpcli("-f", "keyvalue", "show", "neighbors", "details") + assert out['lldp.eth0.port.ifname'] == 'eth1' + assert out['lldp.eth0.port.descr'] == 'blip blop' + # Our new alias should not be sent to neighbor + lldpcli("update") + time.sleep(1) + with namespaces(2): + out = lldpcli("-f", "keyvalue", "show", "neighbors", "details") + assert out['lldp.eth1.port.descr'] == 'eth0' + diff --git a/tests/integration/test_lldpcli.py b/tests/integration/test_lldpcli.py new file mode 100644 index 0000000000000000000000000000000000000000..6cef96a1a6ca6e5469c807cbcf51182c73ff365e --- /dev/null +++ b/tests/integration/test_lldpcli.py @@ -0,0 +1,627 @@ +import pytest +import shlex +import time +import re +import platform +import json +import xml.etree.ElementTree as ET + +if hasattr(ET, "canonicalize"): + canonicalize = ET.canonicalize +else: + def canonicalize(x): + x + + +@pytest.fixture(scope='session') +def uname(): + return "{} {} {} {}".format( + platform.system(), + platform.release(), + platform.version(), + platform.machine()) + +@pytest.mark.parametrize("command, expected", [ + ("neighbors", + """------------------------------------------------------------------------------- +LLDP neighbors: +------------------------------------------------------------------------------- +Interface: eth0, via: LLDP, RID: 1, Time: 0 day, 00:00:{seconds} + Chassis: + ChassisID: mac 00:00:00:00:00:02 + SysName: ns-2.example.com + SysDescr: Spectacular GNU/Linux 2016 {uname} + MgmtIP: fe80::200:ff:fe00:2 + MgmtIface: 2 + Capability: Bridge, off + Capability: Router, {router} + Capability: Wlan, off + Capability: Station, {station} + Port: + PortID: mac 00:00:00:00:00:02 + PortDescr: eth1 + TTL: 120{dot3} +------------------------------------------------------------------------------- +"""), + ("interfaces", + """------------------------------------------------------------------------------- +LLDP interfaces: +------------------------------------------------------------------------------- +Interface: eth0 + Administrative status: RX and TX + Chassis: + ChassisID: mac 00:00:00:00:00:01 + SysName: ns-1.example.com + SysDescr: Spectacular GNU/Linux 2016 {uname} + MgmtIP: fe80::200:ff:fe00:1 + MgmtIface: 3 + Capability: Bridge, off + Capability: Router, {router} + Capability: Wlan, off + Capability: Station, {station} + Port: + PortID: mac 00:00:00:00:00:01 + PortDescr: eth0{dot3} + TTL: 120 +------------------------------------------------------------------------------- +""")], ids=["neighbors", "interfaces"]) +def test_text_output(request, lldpd1, lldpd, lldpcli, namespaces, uname, + command, expected): + with namespaces(2): + lldpd() + with namespaces(1): + result = lldpcli( + *shlex.split("show {} details".format(command))) + assert result.returncode == 0 + out = result.stdout.decode('ascii') + + if 'Dot3' in request.config.lldpd.features: + dot3 = """ + PMD autoneg: supported: no, enabled: no + MAU oper type: 10GbaseT - Four-pair Category 6A or better, full duplex mode only""" + else: + dot3 = "" + + out = result.stdout.decode('ascii') + if command == "neighbors": + time = re.search(r'^Interface: .*Time: (.*)$', + out, + re.MULTILINE).group(1) + seconds = re.search(r'^Interface: .*(\d\d)$', + out, + re.MULTILINE).group(1) + else: + time = None + seconds = None + router = re.search(r'^ Capability: Router, (.*)$', + out, + re.MULTILINE).group(1) + station = re.search(r'^ Capability: Station, (.*)$', + out, + re.MULTILINE).group(1) + out = re.sub(r' *$', '', out, flags=re.MULTILINE) + assert out == expected.format(seconds=seconds, + time=time, + router=router, + station=station, + uname=uname, + dot3=dot3) + +@pytest.mark.skipif("'JSON' not in config.lldpcli.outputs", + reason="JSON not supported") +@pytest.mark.parametrize("command, expected", [ + ("neighbors", + {"lldp": { + "interface": { + "eth0": { + "via": "LLDP", + "rid": "1", + "chassis": { + "ns-2.example.com": { + "id": { + "type": "mac", + "value": "00:00:00:00:00:02"}, + "descr": "Spectacular GNU/Linux 2016 {}".format(uname), + "mgmt-ip": "fe80::200:ff:fe00:2", + "mgmt-iface": "2", + "capability": [ + {"type": "Bridge", "enabled": False}, + {"type": "Wlan", "enabled": False},]}}, + "port": { + "id": { + "type": "mac", + "value": "00:00:00:00:00:02"}, + "descr": "eth1", + "ttl": "120"}}}}}), + ("interfaces", + {"lldp": { + "interface": { + "eth0": { + "status": "RX and TX", + "chassis": { + "ns-1.example.com": { + "id": { + "type": "mac", + "value": "00:00:00:00:00:01"}, + "descr": "Spectacular GNU/Linux 2016 {}".format(uname), + "mgmt-ip": "fe80::200:ff:fe00:1", + "mgmt-iface": "3", + "capability": [ + {"type": "Bridge", "enabled": False}, + {"type": "Wlan", "enabled": False},]}}, + "port": { + "id": { + "type": "mac", + "value": "00:00:00:00:00:01"}, + "descr": "eth0"}, + "ttl": { + "ttl": "120"}}}}})], ids=["neighbors", "interfaces"]) +def test_json_output(request, lldpd1, lldpd, lldpcli, namespaces, uname, + command, expected): + with namespaces(2): + lldpd() + with namespaces(1): + result = lldpcli( + *shlex.split("-f json show {} details".format(command))) + assert result.returncode == 0 + out = result.stdout.decode('ascii') + j = json.loads(out) + + eth0 = j['lldp']['interface']['eth0'] + name = next(k for k,v in eth0['chassis'].items() if k.startswith('ns')) + if command == "neighbors": + del eth0['age'] + del eth0['chassis'][name]['capability'][3] + del eth0['chassis'][name]['capability'][1] + + descr = "Spectacular GNU/Linux 2016 {}".format(uname) + expected['lldp']['interface']['eth0']['chassis'][name]["descr"] = descr + + if 'Dot3' in request.config.lldpd.features: + expected['lldp']['interface']['eth0']['port']['auto-negotiation'] = { + "enabled": False, + "supported": False, + "current": "10GbaseT - Four-pair Category 6A or better, full duplex mode only" + } + + assert j == expected + +@pytest.mark.skipif("'JSON' not in config.lldpcli.outputs", + reason="JSON not supported") +@pytest.mark.parametrize("command, expected", [ + ("neighbors", + {"lldp": [{ + "interface": [{ + "name": "eth0", + "via": "LLDP", + "rid": "1", + "chassis": [{ + "id": [{ + "type": "mac", + "value": "00:00:00:00:00:02" + }], + "name": [{"value": "ns-2.example.com"}], + "descr": [{"value": "Spectacular GNU/Linux 2016 {}".format(uname)}], + "mgmt-ip": [{"value": "fe80::200:ff:fe00:2"}], + "mgmt-iface": [{"value": "2"}], + "capability": [ + {"type": "Bridge", "enabled": False}, + {"type": "Wlan", "enabled": False}, + ]} + ], + "port": [{ + "id": [{ + "type": "mac", + "value": "00:00:00:00:00:02" + }], + "descr": [{"value": "eth1"}], + "ttl": [{"value": "120"}] + }] + }]} + ]}), + ("interfaces", + {"lldp": [{ + "interface": [{ + "name": "eth0", + "status": [{ + "value": "RX and TX", + }], + "chassis": [{ + "id": [{ + "type": "mac", + "value": "00:00:00:00:00:01" + }], + "name": [{"value": "ns-1.example.com"}], + "descr": [{"value": "Spectacular GNU/Linux 2016 {}".format(uname)}], + "mgmt-ip": [{"value": "fe80::200:ff:fe00:1"}], + "mgmt-iface": [{"value": "3"}], + "capability": [ + {"type": "Bridge", "enabled": False}, + {"type": "Wlan", "enabled": False}, + ]} + ], + "port": [{ + "id": [{ + "type": "mac", + "value": "00:00:00:00:00:01" + }], + "descr": [{"value": "eth0"}] + }], + "ttl": [{"ttl": "120"}] + }]} + ]})], ids=["neighbors", "interfaces"]) +def test_json0_output(request, lldpd1, lldpd, lldpcli, namespaces, uname, + command, expected): + with namespaces(2): + lldpd() + with namespaces(1): + result = lldpcli( + *shlex.split("-f json0 show {} details".format(command))) + assert result.returncode == 0 + out = result.stdout.decode('ascii') + j = json.loads(out) + + eth0 = j['lldp'][0]['interface'][0] + if command == "neighbors": + del eth0['age'] + del eth0['chassis'][0]['capability'][3] + del eth0['chassis'][0]['capability'][1] + + descr = "Spectacular GNU/Linux 2016 {}".format(uname) + expected['lldp'][0]['interface'][0]['chassis'][0]["descr"][0]['value'] = descr + + if 'Dot3' in request.config.lldpd.features: + expected['lldp'][0]['interface'][0]['port'][0]['auto-negotiation'] = [{ + "enabled": False, + "supported": False, + "current": [{"value": + "10GbaseT - Four-pair Category 6A or better, full duplex mode only"}] + }] + assert j == expected + + +@pytest.mark.skipif("'XML' not in config.lldpcli.outputs", + reason="XML not supported") +@pytest.mark.parametrize("command, expected", [ + ("neighbors", +""" + + + + 00:00:00:00:00:02 + ns-2.example.com + Spectacular GNU/Linux 2016 {uname} + fe80::200:ff:fe00:2 + 2 + + + + + + + 00:00:00:00:00:02 + eth1 + 120{dot3} + + + +"""), +("interfaces", +""" + + + RX and TX + + 00:00:00:00:00:01 + ns-1.example.com + Spectacular GNU/Linux 2016 {uname} + fe80::200:ff:fe00:1 + 3 + + + + + + + 00:00:00:00:00:01 + eth0{dot3} + + + + +""")], ids=["neighbors", "interfaces"]) +def test_xml_output(request, lldpd1, lldpd, lldpcli, namespaces, uname, + command, expected): + with namespaces(2): + lldpd() + with namespaces(1): + result = lldpcli( + *shlex.split("-f xml show {} details".format(command))) + assert result.returncode == 0 + out = result.stdout.decode('ascii') + xml = ET.fromstring(out) + + if command == "neighbors": + age = xml.findall('./interface[1]')[0].attrib['age'] + else: + age = None + router = xml.findall("./interface[1]/chassis/" + "capability[@type='Router']")[0].attrib['enabled'] + station = xml.findall("./interface[1]/chassis/" + "capability[@type='Station']")[0].attrib['enabled'] + if 'Dot3' in request.config.lldpd.features: + dot3 = """ + + 10GbaseT - Four-pair Category 6A or better, full duplex mode only + """ + else: + dot3 = "" + expected = ET.fromstring(expected.format(age=age, + router=router, + station=station, + uname=uname, + dot3=dot3)) + assert canonicalize(ET.tostring(xml)) == canonicalize(ET.tostring(expected)) + + +@pytest.mark.skipif("'Dot3' not in config.lldpd.features", + reason="Dot3 not supported") +def test_configure_one_port(lldpd1, lldpd, lldpcli, namespaces, links): + links(namespaces(1), namespaces(2)) + with namespaces(2): + lldpd() + result = lldpcli(*("configure ports eth3 dot3 power " + "pse supported enabled paircontrol powerpairs " + "spare class class-3").split()) + assert result.returncode == 0 + time.sleep(3) + out = lldpcli("-f", "keyvalue", "show", "interfaces", "details") + assert 'lldp.eth1.port.power.device-type' not in out + assert out['lldp.eth3.port.power.device-type'] == 'PSE' + with namespaces(1): + out = lldpcli("-f", "keyvalue", "show", "neighbors", "details") + assert out['lldp.eth0.port.descr'] == 'eth1' + assert 'lldp.eth0.port.power.device-type' not in out + assert out['lldp.eth2.port.descr'] == 'eth3' + assert out['lldp.eth2.port.power.device-type'] == 'PSE' + + +@pytest.mark.skipif("'Dot3' not in config.lldpd.features", + reason="Dot3 not supported") +def test_new_port_take_default(lldpd1, lldpd, lldpcli, namespaces, links): + with namespaces(2): + lldpd() + result = lldpcli(*("configure dot3 power " + "pse supported enabled paircontrol powerpairs " + "spare class class-3").split()) + assert result.returncode == 0 + time.sleep(3) + out = lldpcli("-f", "keyvalue", "show", "interfaces", "details") + assert out['lldp.eth1.port.power.device-type'] == 'PSE' + with namespaces(1): + # Check this worked + out = lldpcli("-f", "keyvalue", "show", "neighbors", "details") + assert out['lldp.eth0.port.descr'] == 'eth1' + assert out['lldp.eth0.port.power.device-type'] == 'PSE' + links(namespaces(1), namespaces(2), 4) + time.sleep(6) + with namespaces(1): + out = lldpcli("-f", "keyvalue", "show", "neighbors", "details") + assert out['lldp.eth2.port.descr'] == 'eth3' + assert out['lldp.eth2.port.power.device-type'] == 'PSE' + with namespaces(2): + out = lldpcli("-f", "keyvalue", "show", "interfaces", "details") + assert out['lldp.eth3.port.power.device-type'] == 'PSE' + + +@pytest.mark.skipif("'Dot3' not in config.lldpd.features", + reason="Dot3 not supported") +def test_port_keep_configuration_when_down(lldpd, lldpcli, namespaces, links): + with namespaces(1): + links.dummy('eth3') + lldpd() + result = lldpcli(*("configure ports eth3 dot3 power " + "pse supported enabled paircontrol powerpairs " + "spare class class-3").split()) + assert result.returncode == 0 + time.sleep(3) + links.down('eth3') + time.sleep(4) + # eth3 configuration is kept because the port still exists. + out = lldpcli("-f", "keyvalue", "show", "interfaces", "details") + assert out['lldp.eth3.port.power.device-type'] == 'PSE' + + links.up('eth3') + time.sleep(4) + # eth3 configuration is unchanged + out = lldpcli("-f", "keyvalue", "show", "interfaces", "details") + assert out['lldp.eth3.port.power.device-type'] == 'PSE' + + +@pytest.mark.skipif("'Dot3' not in config.lldpd.features", + reason="Dot3 not supported") +def test_port_forget_configuration(lldpd, lldpcli, + namespaces, links): + with namespaces(1): + links.dummy('eth3') + lldpd() + result = lldpcli(*("configure dot3 power " + "pse supported enabled paircontrol powerpairs " + "spare class class-3").split()) + assert result.returncode == 0 + time.sleep(3) + links.remove('eth3') + time.sleep(4) + # eth3 configuration was forgotten because it disappeared. + out = lldpcli("-f", "keyvalue", "show", "interfaces", "details") + assert 'lldp.eth3.port.power.device-type' not in out + + +@pytest.mark.skipif("'Dot3' not in config.lldpd.features", + reason="Dot3 not supported") +def test_port_keep_configuration_of_permanent_ports(lldpd, lldpcli, + namespaces, links): + with namespaces(1): + links.dummy('eth3') + links.dummy('noteth3') + lldpd() + result = lldpcli(*("configure system interface permanent e*").split()) + assert result.returncode == 0 + result = lldpcli(*("configure dot3 power " + "pse supported enabled paircontrol powerpairs " + "spare class class-3").split()) + assert result.returncode == 0 + time.sleep(3) + links.remove('eth3') + links.remove('noteth3') + time.sleep(4) + # eth3 configuration is kept because it matches the permanent + # port pattern. + out = lldpcli("-f", "keyvalue", "show", "interfaces", "details") + assert out['lldp.eth3.port.power.device-type'] == 'PSE' + assert 'lldp.noteth3.port.power.device-type' not in out + + links.dummy('eth3') + links.dummy('noteth3') + time.sleep(4) + # eth3 configuration is unchanged + out = lldpcli("-f", "keyvalue", "show", "interfaces", "details") + assert out['lldp.eth3.port.power.device-type'] == 'PSE' + # noteth3 inherited from default + assert out['lldp.noteth3.port.power.device-type'] == 'PSE' + + +def test_watch(lldpd1, lldpd, lldpcli, namespaces, links): + with namespaces(2): + lldpd() + with namespaces(1): + result = lldpcli("show", "neighbors") + assert result.returncode == 0 + out = result.stdout.decode('ascii') + assert "ns-2.example.com" in out + + # Put a link down and immediately watch for a change + links.down('eth0') + result = lldpcli("watch", "limit", "1") + assert result.returncode == 0 + expected = out.replace('LLDP neighbors:', 'LLDP neighbor deleted:') + expected = re.sub(r', Time: 0 day, 00:.*$', '', expected, + flags=re.MULTILINE) + got = result.stdout.decode('ascii') + got = re.sub(r', Time: 0 day, 00:.*$', '', got, + flags=re.MULTILINE) + assert got == expected + + +@pytest.mark.skipif("'XML' not in config.lldpcli.outputs", + reason="XML not supported") +def test_watch_xml(lldpd1, lldpd, lldpcli, namespaces, links): + with namespaces(2): + lldpd() + with namespaces(1): + result = lldpcli("-f", "xml", "show", "neighbors") + assert result.returncode == 0 + expected = result.stdout.decode('ascii') + expected = ET.fromstring(expected) + assert [x.text + for x in expected.findall("./interface/chassis/name")] == \ + ["ns-2.example.com"] + + # Put a link down and immediately watch for a change + links.down('eth0') + result = lldpcli("-f", "xml", "watch", "limit", "1") + assert result.returncode == 0 + expected.tag = 'lldp-deleted' + expected.set('label', 'LLDP neighbor deleted') + expected.find('./interface').set('age', '') + got = result.stdout.decode('ascii') + got = ET.fromstring(got) + got.find('./interface').set('age', '') + assert canonicalize(ET.tostring(got)) == canonicalize(ET.tostring(expected)) + + +@pytest.mark.skipif("'JSON' not in config.lldpcli.outputs", + reason="JSON not supported") +def test_watch_json(lldpd1, lldpd, lldpcli, namespaces, links): + with namespaces(2): + lldpd() + with namespaces(1): + result = lldpcli("-f", "json", "show", "neighbors") + assert result.returncode == 0 + expected = result.stdout.decode('ascii') + expected = json.loads(expected) + assert 'ns-2.example.com' in \ + expected['lldp']['interface']['eth0']['chassis'] + + # Put a link down and immediately watch for a change + links.down('eth0') + result = lldpcli("-f", "json", "watch", "limit", "1") + assert result.returncode == 0 + got = result.stdout.decode('ascii') + got = json.loads(got) + expected['lldp-deleted'] = expected['lldp'] + del expected['lldp'] + del expected['lldp-deleted']['interface']['eth0']['age'] + del got['lldp-deleted']['interface']['eth0']['age'] + assert got == expected + + +def test_return_code(lldpd1, lldpcli, namespaces): + with namespaces(1): + result = lldpcli("show", "neighbors") + assert result.returncode == 0 + result = lldpcli("show", "interfaces") + assert result.returncode == 0 + result = lldpcli("unknown", "command") + assert result.returncode == 1 + + +@pytest.mark.parametrize("command, name, expected", [ + ("configure system max-neighbors 10", "max-neighbors", 10), + # get integral tx-delay from non-integral value (rounded up value) + ("configure lldp tx-interval 1500ms", "tx-delay", 2), + # get non-integral tx-delay-ms from non-integral value (exact value) + ("configure lldp tx-interval 2500ms", "tx-delay-ms", 2500), + ("configure lldp tx-interval 20", "tx-delay", 20), + ("configure lldp tx-hold 5", "tx-hold", 5), + ("configure lldp portidsubtype ifname", "lldp-portid-type", "ifname"), + pytest.param("unconfigure med fast-start", + "lldpmed-faststart", "no", + marks=pytest.mark.skipif( + "'LLDP-MED' not in config.lldpd.features", + reason="LLDP-MED not supported")), + pytest.param("configure med fast-start tx-interval 2", + "lldpmed-faststart-interval", 2, + marks=pytest.mark.skipif( + "'LLDP-MED' not in config.lldpd.features", + reason="LLDP-MED not supported")), + ("configure system interface pattern eth*", "iface-pattern", "eth*"), + ("configure system interface permanent eth*", + "perm-iface-pattern", "eth*"), + ("configure system ip management pattern 10.*", "mgmt-pattern", "10.*"), + ("configure system chassisid squid", "cid-string", "squid"), + ("configure system platform squid", "platform", "squid"), + ("configure system description squid", "description", "squid"), + ("configure system hostname squid", "hostname", "squid"), + ("configure system interface description", "ifdescr-update", "yes"), + ("configure system interface promiscuous", "iface-promisc", "yes"), + ("configure system bond-slave-src-mac-type fixed", + "bond-slave-src-mac-type", "fixed"), + ("configure system description " + "1234567890123456789012345678901234567890" + "1234567890123456789012345678901234567890", + "description", + "1234567890123456789012345678901234567890" + "1234567890123456789012345678901234567890"), + ("configure lldp agent-type nearest-customer-bridge", + "lldp-agent-type", "nearest customer bridge")]) +def test_config_change(lldpd1, lldpcli, namespaces, command, name, expected): + with namespaces(1): + # Check initial value first + out = lldpcli("-f", "keyvalue", "show", "configuration") + assert out['configuration.config.{}'.format(name)] != str(expected) + # Issue change and check new value + result = lldpcli(*shlex.split(command)) + assert result.returncode == 0 + out = lldpcli("-f", "keyvalue", "show", "configuration") + assert out['configuration.config.{}'.format(name)] == str(expected) diff --git a/tests/integration/test_med.py b/tests/integration/test_med.py new file mode 100644 index 0000000000000000000000000000000000000000..3f20e40ed86680cf09835565695fa4f1cd75b13d --- /dev/null +++ b/tests/integration/test_med.py @@ -0,0 +1,131 @@ +import os +import pytest +import platform +import time +import shlex + + +@pytest.mark.skipif("'LLDP-MED' not in config.lldpd.features", + reason="LLDP-MED not supported") +class TestLldpMed(object): + + @pytest.mark.parametrize("classe, expected", [ + (1, "Generic Endpoint (Class I)"), + (2, "Media Endpoint (Class II)"), + (3, "Communication Device Endpoint (Class III)"), + (4, "Network Connectivity Device")]) + def test_med_devicetype(self, lldpd1, lldpd, lldpcli, namespaces, + classe, expected): + with namespaces(2): + lldpd("-M", str(classe)) + with namespaces(1): + out = lldpcli("-f", "keyvalue", "show", "neighbors", "details") + assert out['lldp.eth0.lldp-med.device-type'] == expected + + def test_med_capabilities(self, lldpd1, lldpd, lldpcli, namespaces): + with namespaces(2): + lldpd("-M", "2") + with namespaces(1): + out = lldpcli("-f", "keyvalue", "show", "neighbors", "details") + out = {k.split(".")[3]: v + for k, v in out.items() + if k.endswith('.available')} + assert out == {'Capabilities': 'yes', + 'Policy': 'yes', + 'Location': 'yes', + 'MDI/PSE': 'yes', + 'MDI/PD': 'yes', + 'Inventory': 'yes'} + + @pytest.mark.skipif(not os.path.isdir("/sys/class/dmi/id"), + reason="/sys/class/dmi not available") + def test_med_inventory(self, lldpd1, lldpd, lldpcli, namespaces, + replace_file): + with namespaces(2): + # /sys/class/dmi/id/* + for what, value in dict(product_version="1.14", + bios_version="1.10", + product_serial="45872512", + sys_vendor="Spectacular", + product_name="Workstation", + chassis_asset_tag="487122").items(): + replace_file("/sys/class/dmi/id/{}".format(what), + value) + lldpd("-M", "1") + with namespaces(1): + out = lldpcli("-f", "keyvalue", "show", "neighbors", "details") + assert out['lldp.eth0.chassis.name'] == 'ns-2.example.com' + assert out['lldp.eth0.lldp-med.inventory.hardware'] == '1.14' + assert out['lldp.eth0.lldp-med.inventory.firmware'] == '1.10' + assert out['lldp.eth0.lldp-med.inventory.serial'] == '45872512' + assert out['lldp.eth0.lldp-med.inventory.manufacturer'] == \ + 'Spectacular' + assert out['lldp.eth0.lldp-med.inventory.model'] == 'Workstation' + assert out['lldp.eth0.lldp-med.inventory.asset'] == '487122' + assert out['lldp.eth0.lldp-med.inventory.software'] == \ + platform.release() + + @pytest.mark.parametrize("command, pfx, expected", [ + # Policies + ("policy application voice tagged vlan 500 priority voice dscp 46", + "policy", + {'apptype': 'Voice', + 'defined': 'yes', + 'priority': 'Voice', + 'pcp': '5', + 'dscp': '46', + 'vlan.vid': '500'}), + ("policy application video-conferencing unknown dscp 3 priority video", + "policy", + {'apptype': 'Video Conferencing', + 'defined': 'no', + 'priority': 'Video', + 'pcp': '4', + 'dscp': '3'}), + # Locations + ("location coordinate latitude 48.58667N longitude 2.2014E " + "altitude 117.47 m datum WGS84", + "Coordinates", + {'geoid': 'WGS84', + 'lat': '48.58666N', + 'lon': '2.2013E', + 'altitude.unit': 'm', + 'altitude': '117.46'}), + ('location address country US language en_US street ' + '"Commercial Road" city "Roseville"', + "Civic address", + {'country': 'US', + 'language': 'en_US', + 'city': 'Roseville', + 'street': 'Commercial Road'}), + ('location elin 911', + "ELIN", + {'ecs': '911'}), + # Power + ("power pd source pse priority high value 5000", + "poe", + {'device-type': 'PD', + 'source': 'PSE', + 'priority': 'high', + 'power': '5000'}), + ("power pse source backup priority critical value 300", + "poe", + {'device-type': 'PSE', + 'source': 'Backup Power Source / Power Conservation Mode', + 'priority': 'critical', + 'power': '300'})]) + def test_med_configuration(self, lldpd1, lldpd, lldpcli, namespaces, + command, pfx, expected): + with namespaces(2): + lldpd("-M", "1") + result = lldpcli( + *shlex.split("configure med {}".format(command))) + assert result.returncode == 0 + time.sleep(3) + with namespaces(1): + pfx = "lldp.eth0.lldp-med.{}.".format(pfx) + out = lldpcli("-f", "keyvalue", "show", "neighbors", "details") + out = {k[len(pfx):]: v + for k, v in out.items() + if k.startswith(pfx)} + assert out == expected diff --git a/tests/integration/test_pcap.py b/tests/integration/test_pcap.py new file mode 100644 index 0000000000000000000000000000000000000000..5f3c0b1a4352026dc074a1fbe12531707714ac53 --- /dev/null +++ b/tests/integration/test_pcap.py @@ -0,0 +1,114 @@ +import pytest + + +def test_cisco_sg200(request, lldpd1, lldpcli, namespaces): + with namespaces(2): + pytest.helpers.send_pcap('data/sg200.pcap', 'eth1') + with namespaces(1): + out = lldpcli("-f", "keyvalue", "show", "neighbors", "details") + assert out['lldp.eth0.age'].startswith('0 day, 00:00:') + del out['lldp.eth0.age'] + expected = { + "lldp.eth0.via": "LLDP", + "lldp.eth0.rid": "1", + "lldp.eth0.chassis.mac": "00:35:35:35:35:35", + "lldp.eth0.port.ttl": "120", + "lldp.eth0.port.ifname": "g1", + } + if 'Dot3' in request.config.lldpd.features: + expected.update({ + "lldp.eth0.port.auto-negotiation.supported": "yes", + "lldp.eth0.port.auto-negotiation.enabled": "yes", + "lldp.eth0.port.auto-negotiation.1000Base-T.hd": "no", + "lldp.eth0.port.auto-negotiation.1000Base-T.fd": "yes", + "lldp.eth0.port.auto-negotiation.current": "unknown", + }) + if 'LLDP-MED' in request.config.lldpd.features: + expected.update({ + "lldp.eth0.lldp-med.device-type": + "Network Connectivity Device", + "lldp.eth0.lldp-med.Capabilities.available": "yes", + "lldp.eth0.lldp-med.Policy.available": "yes", + "lldp.eth0.lldp-med.Location.available": "yes", + "lldp.eth0.lldp-med.MDI/PSE.available": "yes", + "lldp.eth0.lldp-med.Inventory.available": "yes", + "lldp.eth0.lldp-med.Civic address.country": "DE", + "lldp.eth0.lldp-med.Civic address.city": "Berlin", + "lldp.eth0.lldp-med.Civic address.street": + "Karl-Liebknecht-Strase", + "lldp.eth0.lldp-med.Civic address.building": "42", + "lldp.eth0.lldp-med.inventory.hardware": "V02", + "lldp.eth0.lldp-med.inventory.software": "1.0.8.3", + "lldp.eth0.lldp-med.inventory.firmware": "1.0.8.3", + "lldp.eth0.lldp-med.inventory.serial": "XXX11111ZZZ", + "lldp.eth0.lldp-med.inventory.manufacturer": "0xbc00", + "lldp.eth0.lldp-med.inventory.model": "SG 200-08P", + "lldp.eth0.lldp-med.inventory.asset": "1" + }) + assert out == expected + + +@pytest.mark.skipif("'Dot3' not in config.lldpd.features", + readon="Dot3 not supported") +def test_8023bt(lldpd1, lldpcli, namespaces): + with namespaces(2): + pytest.helpers.send_pcap('data/8023bt.pcap', 'eth1') + with namespaces(1): + out = lldpcli("-f", "keyvalue", "show", "neighbors", "details") + for k in list(out.keys()): + if not k.startswith("lldp.eth0.port.power."): + del out[k] + assert out == { + 'lldp.eth0.port.power.supported': 'yes', + 'lldp.eth0.port.power.enabled': 'yes', + 'lldp.eth0.port.power.paircontrol': 'yes', + 'lldp.eth0.port.power.device-type': 'PSE', + 'lldp.eth0.port.power.pairs': 'signal', + 'lldp.eth0.port.power.class': 'class 4', + 'lldp.eth0.port.power.power-type': '2', + 'lldp.eth0.port.power.source': 'PSE', + 'lldp.eth0.port.power.priority': 'low', + 'lldp.eth0.port.power.requested': '71000', + 'lldp.eth0.port.power.allocated': '51000', + 'lldp.eth0.port.power.requested-a': '35500', + 'lldp.eth0.port.power.requested-b': '35500', + 'lldp.eth0.port.power.allocated-a': '25500', + 'lldp.eth0.port.power.allocated-b': '25500', + 'lldp.eth0.port.power.pse-powering-status': + '4-pair powering single-signature PD', + 'lldp.eth0.port.power.pd-powering-status': 'Unknown', + 'lldp.eth0.port.power.power-pairs-ext': 'Both alternatives', + 'lldp.eth0.port.power.power-class-ext-a': 'Class 4', + 'lldp.eth0.port.power.power-class-ext-b': 'Class 4', + 'lldp.eth0.port.power.power-class-ext': 'Dual-signature PD', + 'lldp.eth0.port.power.power-type-ext': 'Type 3 PSE', + 'lldp.eth0.port.power.pd-load': + ('PD is single- or dual-signature and power ' + 'is not electrically isolated'), + 'lldp.eth0.port.power.max-power': '51000' + } + +@pytest.mark.skipif("'LLDP-MED' not in config.lldpd.features", + readon="LLDP-MED not supported") +def test_med_loc_malformed(lldpd1, lldpcli, namespaces): + with namespaces(2): + pytest.helpers.send_pcap('data/med-loc-malformed.pcap', 'eth1') + with namespaces(1): + out = lldpcli("-f", "keyvalue", "show", "neighbors", "details") + for k in list(out.keys()): + if not k.startswith("lldp.eth0.lldp-med."): + del out[k] + assert out == { + 'lldp.eth0.lldp-med.device-type': 'Communication Device Endpoint (Class III)', + 'lldp.eth0.lldp-med.Capabilities.available': 'yes', + 'lldp.eth0.lldp-med.Policy.available': 'yes', + 'lldp.eth0.lldp-med.Location.available': 'yes', + 'lldp.eth0.lldp-med.Inventory.available': 'yes', + 'lldp.eth0.lldp-med.policy.apptype': 'Voice', + 'lldp.eth0.lldp-med.policy.defined': 'yes', + 'lldp.eth0.lldp-med.policy.priority': 'Best effort', + 'lldp.eth0.lldp-med.policy.pcp': '0', + 'lldp.eth0.lldp-med.policy.dscp': '0', + 'lldp.eth0.lldp-med.Civic address.country': 'F5' + # Truncated + } diff --git a/tests/integration/test_protocols.py b/tests/integration/test_protocols.py new file mode 100644 index 0000000000000000000000000000000000000000..1dc3ed0d4c3f7b3297180a9f60a0bb41d50038ab --- /dev/null +++ b/tests/integration/test_protocols.py @@ -0,0 +1,84 @@ +import pytest +import pyroute2 + + +@pytest.mark.skipif("'CDP' not in config.lldpd.protocols", + reason="CDP not supported") +@pytest.mark.parametrize("argument, expected", [ + ("-cc", "CDPv1"), + ("-ccc", "CDPv2")]) +def test_cdp(lldpd, lldpcli, links, namespaces, + argument, expected): + links(namespaces(1), namespaces(2)) + with namespaces(1): + lldpd("-c", "-ll", "-r") + with namespaces(2): + lldpd(argument, "-ll") + with namespaces(1): + out = lldpcli("-f", "keyvalue", "show", "neighbors", "details") + assert out["lldp.eth0.via"] == expected + assert out["lldp.eth0.chassis.local"] == "ns-2.example.com" + assert out["lldp.eth0.chassis.name"] == "ns-2.example.com" + assert out["lldp.eth0.chassis.descr"].startswith( + "Linux running on Spectacular GNU/Linux 2016") + assert out["lldp.eth0.port.ifname"] == "eth1" + assert out["lldp.eth0.port.descr"] == "eth1" + + +@pytest.mark.skipif("'FDP' not in config.lldpd.protocols", + reason="FDP not supported") +def test_fdp(lldpd, lldpcli, links, namespaces): + links(namespaces(1), namespaces(2)) + with namespaces(1): + lldpd("-f", "-ll", "-r") + with namespaces(2): + lldpd("-ff", "-ll") + with namespaces(1): + out = lldpcli("-f", "keyvalue", "show", "neighbors", "details") + assert out["lldp.eth0.via"] == "FDP" + assert out["lldp.eth0.chassis.local"] == "ns-2.example.com" + assert out["lldp.eth0.chassis.name"] == "ns-2.example.com" + assert out["lldp.eth0.chassis.descr"].startswith( + "Linux running on Spectacular GNU/Linux 2016") + assert out["lldp.eth0.port.ifname"] == "eth1" + assert out["lldp.eth0.port.descr"] == "eth1" + + +@pytest.mark.skipif("'EDP' not in config.lldpd.protocols", + reason="EDP not supported") +def test_edp(lldpd, lldpcli, links, namespaces): + links(namespaces(1), namespaces(2)) + with namespaces(1): + lldpd("-e", "-ll", "-r") + with namespaces(2): + lldpd("-ee", "-ll") + with namespaces(1): + out = lldpcli("-f", "keyvalue", "show", "neighbors", "details") + assert out["lldp.eth0.via"] == "EDP" + assert out["lldp.eth0.chassis.mac"] == "00:00:00:00:00:02" + assert out["lldp.eth0.chassis.name"] == "ns-2.example.com" + assert out["lldp.eth0.chassis.descr"] == \ + "EDP enabled device, version 7.6.4.99" + assert out["lldp.eth0.port.ifname"] == "1/2" + assert out["lldp.eth0.port.descr"] == "Slot 1 / Port 2" + + +@pytest.mark.skipif("'SONMP' not in config.lldpd.protocols", + reason="SONMP not supported") +def test_sonmp(lldpd, lldpcli, links, namespaces): + links(namespaces(1), namespaces(2)) + with namespaces(1): + lldpd("-s", "-ll", "-r") + with namespaces(2): + ipr = pyroute2.IPRoute() + idx = ipr.link_lookup(ifname="eth1")[0] + ipr.addr('add', index=idx, address="192.168.14.2", mask=24) + lldpd("-ss", "-ll") + with namespaces(1): + out = lldpcli("-f", "keyvalue", "show", "neighbors", "details") + assert out["lldp.eth0.via"] == "SONMP" + assert out["lldp.eth0.chassis.name"] == "192.168.14.2" + assert out["lldp.eth0.chassis.descr"] == "unknown (via SONMP)" + port = out["lldp.eth0.port.local"][-1] + assert out["lldp.eth0.port.local"] == "00-00-0{}".format(port) + assert out["lldp.eth0.port.descr"] == "port {}".format(port) diff --git a/tests/integration/test_snmp.py b/tests/integration/test_snmp.py new file mode 100644 index 0000000000000000000000000000000000000000..6ce3e48c0966d7fe1f4c659b790878cb6af2afb2 --- /dev/null +++ b/tests/integration/test_snmp.py @@ -0,0 +1,58 @@ +import pytest + +pytestmark = pytest.mark.skipif("not config.lldpd.snmp", + reason="no SNMP support") + + +def test_snmp_register(snmpd, snmpwalk, lldpd, namespaces): + with namespaces(1): + snmpd() + lldpd("-x") + out = snmpwalk(".1.3.6.1.2.1.1.9.1.3") + assert 'STRING: "lldpMIB implementation by lldpd"' in out.values() + + +def test_snmp_one_neighbor(snmpd, snmpwalk, lldpd, namespaces): + with namespaces(1): + snmpd() + lldpd("-x") + with namespaces(2): + lldpd() + with namespaces(1): + out = snmpwalk(".1.0.8802.1.1.2.1") + assert out['.1.0.8802.1.1.2.1.2.1.0'].startswith( + "Timeticks: ") + assert out['.1.0.8802.1.1.2.1.3.2.0'] == 'STRING: "ns-1.example.com"' + assert out['.1.0.8802.1.1.2.1.3.3.0'] == 'STRING: "ns-1.example.com"' + assert out['.1.0.8802.1.1.2.1.3.4.0'].startswith( + 'STRING: "Spectacular GNU/Linux 2016 Linux') + + +def test_snmp_empty_sysname(snmpd, snmpwalk, lldpd, links, namespaces): + # See https://github.com/lldpd/lldpd/issues/392 + links(namespaces(1), namespaces(2)) + links(namespaces(1), namespaces(3)) + links(namespaces(1), namespaces(4)) + with namespaces(1): + snmpd() + lldpd("-x") + with namespaces(2): + lldpd() + with namespaces(3): + # Packet without sysName + lldpd("-r") + pytest.helpers.send_pcap('data/connectx.pcap', 'eth3') + with namespaces(4): + lldpd() + with namespaces(1): + out = snmpwalk(".1.0.8802.1.1.2.1.4.1.1.9") # lldpRemSysName + # We should get something like: + # .1.0.8802.1.1.2.1.4.1.1.9.400.3.1 STRING: "ns-2.example.com" + # .1.0.8802.1.1.2.1.4.1.1.9.700.5.2 (not present) + # .1.0.8802.1.1.2.1.4.1.1.9.1000.7.3 STRING: "ns-4.example.com" + print(out) + assert list(out.values()) == ['STRING: "ns-2.example.com"', + 'STRING: "ns-4.example.com"'] + oids = list(out.keys()) + assert oids[0].endswith(".3.1") + assert oids[1].endswith(".7.3") diff --git a/tests/lldpcli.conf b/tests/lldpcli.conf new file mode 100644 index 0000000000000000000000000000000000000000..600235ebed214b62c380f61fb9c7b8bb1962b44f --- /dev/null +++ b/tests/lldpcli.conf @@ -0,0 +1,60 @@ +# Try all commands. +# Keep those commands in the same order as the one in the manual page. + +show neighbors details hidden +show neighbors summary +show neighbors ports eth0 details +show configuration +show chassis details +show statistics +show statistics summary +show statistics ports eth0 +configure system hostname Batman +unconfigure system hostname +configure system description Batman +unconfigure system description +configure system platform Batman +unconfigure system platform +configure system interface pattern * +unconfigure system interface pattern +configure system interface description +unconfigure system interface description +configure system interface promiscuous +unconfigure system interface promiscuous +configure system ip management pattern * +unconfigure system ip management pattern +configure system max-neighbors 16 +configure lldp portidsubtype ifname +configure lldp portidsubtype macaddress +configure lldp portidsubtype local Batman +configure lldp portidsubtype local Batman description Batman +configure lldp tx-interval 30 +configure lldp tx-hold 4 +configure ports eth0 lldp status tx-only +configure lldp status rx-and-tx +configure lldp custom-tlv oui 33,44,55 subtype 44 +configure lldp custom-tlv oui 33,44,55 subtype 44 oui-info 45,45,45,45,45 +configure lldp custom-tlv add oui 33,44,55 subtype 44 oui-info 55,55,55,55,55 +configure lldp custom-tlv add oui 33,44,55 subtype 55 oui-info 65,65,65,65,65 +configure lldp custom-tlv replace oui 33,44,55 subtype 44 oui-info 66,66,66,66,66 +unconfigure lldp custom-tlv oui 33,44,55 subtype 55 +unconfigure lldp custom-tlv +configure system bond-slave-src-mac-type fixed +configure system bond-slave-src-mac-type local +configure med fast-start enable +configure med fast-start tx-interval 3 +unconfigure med fast-start +configure med location coordinate latitude 48.58667N longitude 2.2014E altitude 117.47 m datum WGS84 +configure med location address country US language en_US street "Commercial Road" city "Roseville" +configure med location elin 911 +configure ports eth0 med location elin 911 +configure med policy application voice tagged vlan 500 priority voice dscp 46 +configure ports eth0 med policy application voice vlan 500 priority voice dscp 46 +configure med power pd source pse priority high value 5000 +configure ports eth0 med power pd source pse priority high value 5000 +configure dot3 power pse supported enabled paircontrol powerpairs spare class class-3 +configure ports eth0 dot3 power pse supported enabled paircontrol powerpairs spare class class-3 +configure dot3 power pd supported enabled powerpairs spare class class-3 type 1 source pse priority low requested 10000 allocated 15000 + +# A convenient way to "test" lldpcli and liblldpctl is to load those commands in lldpcli with valgrind: +# libtool execute valgrind --suppressions=../src/client/lldpcli.supp --leak-check=full src/client/lldpcli -c ../tests/lldpcli.conf diff --git a/tests/many-neighbors.py b/tests/many-neighbors.py new file mode 100644 index 0000000000000000000000000000000000000000..c016e347ea3217ea2f9dfa6b52d5bbe7b73271cd --- /dev/null +++ b/tests/many-neighbors.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python + +# Simple script to generate a lot of neighbors. This uses scapy. It +# needs a version of Scapy that contains an LLDP dissector. There is +# one in the scapy community repository: +# https://hg.secdev.org/scapy-com/file/dc0876d1c302/scapy/layers/lldp.py + +from scapy.all import * +from optparse import OptionParser + +parser = OptionParser() +parser.add_option("-o", "--output", dest="output", + help="write PCAP file to FILE", metavar="FILE", + default="out.pcap") +parser.add_option("-n", "--neighbors", dest="neighbors", + help="generate N neighbors", metavar="N", + default=60, type="int") + +(options, args) = parser.parse_args() + +wrpcap(options.output, + [Ether(dst="01:80:c2:00:00:0e", src="00:17:d1:a8:35:be")/ + LLDP(tlvlist=[LLDPChassisId(subtype="Locally assigned", value="titi-%03d" % i), + LLDPPortId(subtype="Interface name", value="eth0"), + LLDPTTL(seconds=120), + LLDPDUEnd()]) + for i in range(options.neighbors)]) + +# The generated pcap can be replayed with tcpreplay: +# tcpreplay -i veth0 -t out.pcap diff --git a/tests/pcap-hdr.h b/tests/pcap-hdr.h new file mode 100644 index 0000000000000000000000000000000000000000..92de0f0f3bf21bfccd8dddb6bc7ba8113ea780b4 --- /dev/null +++ b/tests/pcap-hdr.h @@ -0,0 +1,25 @@ +#ifndef _PCAP_HDR_H +#define _PCAP_HDR_H + +#include + +/* See: + * http://wiki.wireshark.org/Development/LibpcapFileFormat + */ +struct pcap_hdr { + uint32_t magic_number; /* magic number */ + uint16_t version_major; /* major version number */ + uint16_t version_minor; /* minor version number */ + uint32_t thiszone; /* GMT to local correction */ + uint32_t sigfigs; /* accuracy of timestamps */ + uint32_t snaplen; /* max length of captured packets, in octets */ + uint32_t network; /* data link type */ +}; +struct pcaprec_hdr { + uint32_t ts_sec; /* timestamp seconds */ + uint32_t ts_usec; /* timestamp microseconds */ + uint32_t incl_len; /* number of octets of packet saved in file */ + uint32_t orig_len; /* actual length of packet */ +}; + +#endif