Download raw body.
LLDP daemon and display tool
On Mon, Apr 28, 2025 at 10:19:47AM +1000, David Gwynne wrote:
> On Fri, Apr 25, 2025 at 10:10:32PM -0600, Theo de Raadt wrote:
> > Sebastian Benoit <benno@openbsd.org> wrote:
> >
> > > Claudio Jeker(cjeker@diehard.n-r-g.com) on 2025.04.25 13:31:32 +0200:
> > > > On Fri, Apr 25, 2025 at 11:36:50AM +0100, Stuart Henderson wrote:
> > > > > On 2025/04/25 12:04, Henning Brauer wrote:
> > > > > > * Stuart Henderson <stu@spacehopper.org> [2025-04-25 03:25]:
> > > > > > > On 2025/04/24 10:38, Lyndon Nerenberg (VE7TFX/VE6BBM) wrote:
> > > > > > > > This is great! Some quick testing shows it correctly sees
> > > > > > > > all the Fortinet and Juniper hardware on my networks.
> > > > > > > >
> > > > > > > > But I would suggest just calling it lldp[d] right from the
> > > > > > > > start. I don't see a conflict as it's makes no sense to
> > > > > > > > run both this and ports at the same time. And if they
> > > > > > > > are both installed, the ports cli names don't collisde
> > > > > > > > with this one's.
> > > > > > >
> > > > > > > The rc.d scripts conflict.
> > > > > >
> > > > > > then the ports one needs to be adjusted.
> > > > > >
> > > > > > our ntpd is ntpd, not ontpd.
> > > > >
> > > > > yes and we had a problem with that around 5.0-5.1
> > > > >
> > > > > > our ldapd is ldapd, not oldapd.
> > > > >
> > > > > no conflict
> > > > >
> > > > > > our smtpd is smtpd, not osmtpd.
> > > > >
> > > > > no conflict
> > > > >
> > > > > > our bgpd is bgpd, not obgpd.
> > > > >
> > > > > the possibly-conflicting rc script was named quagga_bgpd from the start
> > > > >
> > > > > > and so on and so on.
> > > > >
> > > > > the rc-script could be renamed, but:
> > > > >
> > > > > 1. what to?
> > > > >
> > > > > 2. unless it's renamed in the release _before_ this is added, upgrades
> > > > > will be broken. user updates base from a version with ports lldpd
> > > > > installed to a version with lldpd from base, so overwriting rc.d/lldpd.
> > > > > updating packages at that point will _remove_ the rc.d/lldpd script.
> > > > >
> > > > > if we want to reuse existing names of things from ports in base we
> > > > > could really do with a separate namespace for ports and base rc.d scripts.
> > > >
> > > > Would it be acceptable to use lldpd as program name but use rc.d/lldp as
> > > > startup script?
> > > >
> > > > The only other problem point would be the file in /var/run but I think
> > > > that implies that someone wants to run both tools at the same time which
> > > > IMO makes little sense and people can then use an overwrite to toggle the
> > > > control socket path.
> > > >
> > > > I would really like to see dlg's tool in our tree and it seems this is the
> > > > main pain point.
> > >
> > > Does the daemon need to have a "d" at the end?
> > >
> > > If not, how about
> > >
> > > lldp - the daemon binary
> > > lldpctl - the tool
> > > lldp - rc-script
> > > _lldp - username
> >
> > I think we've been here before. When we write our own components, eventually
> > people stop using the ports components because ours are good enough.
> >
> > Sometimes, it was an external component, which we modified iteratively until
> > we had to throw it away, and our own system matured once we dogfooded it.
> >
> > This lldp is not as feature complete, but it's privsep design is stronger,
> > perhaps even strong enough that it can be run by default. It probably doesn't
> > need to be feature complete. lldp protocol is supposed to, over time, replace
> > all the other legacy things like cdp. Noone wants a daemon on OpenBSD that
> > does all the features. Noone wants a speaker on a vendor device that speaks
> > their custom protocol, since so many have had scary bugs, and they don't
> > interoperate. So can we say, bye by legacy, at the same time? Us making such
> > a decision also partly feeds into the internet culture and allows others to
> > consider making the same decision (lldp, only).
> >
> > With the ports lldp code, running it by default is not going to happen.
> >
> > So I'm confused about why we are bending over backwards for a ports daemon.
> >
> > Let's just take over the naming and keep it simple.
>
> so the plan is i rename olldpd to lldpd?
>
> lldp(8) stays as lldp(8)? do i use _lldp for the user the daemon runs
> as or take over _lldpd too? what's the rc.d script called?
>
> users will just have to deal with the conflict if they have the port
> already installed?
this is lldpd(8) running as _lldp, which lldp(8) can talk to via
/var/run/lldp.sock.
Index: sys/net/lldp.h
===================================================================
RCS file: sys/net/lldp.h
diff -N sys/net/lldp.h
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ sys/net/lldp.h 29 Apr 2025 01:47:08 -0000
@@ -0,0 +1,69 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2025 David Gwynne <dlg@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef _NET_LLDP_H_
+#define _NET_LLDP_H_
+
+#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_MANAGEMENT_ADDR 8
+#define LLDP_TLV_ORG 127
+
+/*
+ * Chassis ID subtype enumeration
+ */
+#define LLDP_CHASSIS_ID_CHASSIS 1 /* Chassis component */
+#define LLDP_CHASSIS_ID_IFALIAS 2 /* Interface alias */
+#define LLDP_CHASSIS_ID_PORT 3 /* Port component */
+#define LLDP_CHASSIS_ID_MACADDR 4 /* MAC address */
+#define LLDP_CHASSIS_ID_ADDR 5 /* Network address */
+#define LLDP_CHASSIS_ID_IFNAME 6 /* Interface name */
+#define LLDP_CHASSIS_ID_LOCAL 7 /* Locally assigned */
+
+/*
+ * Port ID subtype enumeration
+ */
+
+#define LLDP_PORT_ID_IFALIAS 1 /* Interface alias */
+#define LLDP_PORT_ID_PORT 2 /* Port component */
+#define LLDP_PORT_ID_MACADDR 3 /* MAC address */
+#define LLDP_PORT_ID_ADDR 4 /* Network address */
+#define LLDP_PORT_ID_IFNAME 5 /* Interface name */
+#define LLDP_PORT_ID_AGENTCID 6 /* Agent circuit ID */
+#define LLDP_PORT_ID_LOCAL 7 /* Locally assigned */
+
+/*
+ * System Capabilities bits
+ */
+
+#define LLDP_SYSTEM_CAP_OTHER (1 << 0)
+#define LLDP_SYSTEM_CAP_REPEATER (1 << 1)
+#define LLDP_SYSTEM_CAP_BRIDGE (1 << 2)
+#define LLDP_SYSTEM_CAP_WLAN (1 << 3)
+#define LLDP_SYSTEM_CAP_ROUTER (1 << 4)
+#define LLDP_SYSTEM_CAP_TELEPHONE (1 << 5)
+#define LLDP_SYSTEM_CAP_DOCSIS (1 << 6)
+#define LLDP_SYSTEM_CAP_STATION (1 << 7)
+
+#endif /* _NET_LLDP_H_ */
Index: usr.sbin/lldpd/Makefile
===================================================================
RCS file: usr.sbin/lldpd/Makefile
diff -N usr.sbin/lldpd/Makefile
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ usr.sbin/lldpd/Makefile 29 Apr 2025 01:47:08 -0000
@@ -0,0 +1,12 @@
+PROG= lldpd
+SRCS= lldpd.c pdu.c
+SRCS+= log.c
+MAN= lldpd.8
+
+CFLAGS+= -Wall -Werror
+DEBUG= -g
+
+LDADD+= -levent
+DPADD+= ${LIBEVENT}
+
+.include <bsd.prog.mk>
Index: usr.sbin/lldpd/lldpd.8
===================================================================
RCS file: usr.sbin/lldpd/lldpd.8
diff -N usr.sbin/lldpd/lldpd.8
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ usr.sbin/lldpd/lldpd.8 29 Apr 2025 01:47:08 -0000
@@ -0,0 +1,76 @@
+.\" $OpenBSD$
+.\"
+.\" Copyright (c) 2025 David Gwynne <dlg@openbsd.org>
+.\"
+.\" Permission to use, copy, modify, and distribute this software for any
+.\" purpose with or without fee is hereby granted, provided that the above
+.\" copyright notice and this permission notice appear in all copies.
+.\"
+.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+.\"
+.Dd $Mdocdate$
+.Dt OLLDPD 8
+.Os
+.Sh NAME
+.Nm lldpd
+.Nd Link Layer Discovery Protocol (LLDP) daemon
+.Sh SYNOPSIS
+.Nm
+.Op Fl d
+.Op Fl s Ar /path/to/ctl.sock
+.Sh DESCRIPTION
+The
+.Nm
+daemon receives
+Link Layer Discovery Protocol
+.Pq LLDP
+messages on Ethernet interfaces in the system and stores them for
+display by
+.Xr lldp 8 .
+.Pp
+LLDP is a link layer protocol that allows network devices to advertise
+and discover neighbour identity and capabability with neighbour devices.
+.Pp
+The options are as follows:
+.Bl -tag -width "-f fileXXX"
+.It Fl d
+Do not daemonize.
+If this option is specified,
+.Nm
+will run in the foreground and log to stderr.
+.It Fl s Ar path
+Specify the filesystem
+.Ar path
+used for the control socket.
+The control socket is used by
+.Xr lldp 8
+to communicate with the
+.Nm
+daemon.
+.El
+.Sh FILES
+.Bl -tag -width "/var/run/lldp.sock" -compact
+.It Pa /var/run/lldp.sock
+Default
+.Nm
+control socket path.
+.El
+.Sh SEE ALSO
+.\" Xr frame 4 ,
+.Xr lldp 8
+.Sh STANDARDS
+.Rs
+.%R IEEE 802.11AB
+.%T Station and Media Access Control Connectivity Discovery
+.Re
+.Sh HISTORY
+The
+.Nm
+program first appeared in
+.Ox 7.8 .
Index: usr.sbin/lldpd/lldpd.c
===================================================================
RCS file: usr.sbin/lldpd/lldpd.c
diff -N usr.sbin/lldpd/lldpd.c
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ usr.sbin/lldpd/lldpd.c 29 Apr 2025 01:47:08 -0000
@@ -0,0 +1,1316 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2024 David Gwynne <dlg@openbsd.org>
+ *
+ * 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 <sys/socket.h>
+#include <sys/ioctl.h>
+#include <sys/un.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <sys/queue.h>
+#include <sys/tree.h>
+
+#include <net/if.h>
+#include <net/if_dl.h>
+#include <net/route.h>
+#include <net/frame.h>
+#include <net/if_types.h>
+
+#include <netinet/in.h>
+#include <netinet/if_ether.h>
+#include <net/ethertypes.h>
+#include <net/lldp.h>
+
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <err.h>
+#include <errno.h>
+#include <ifaddrs.h>
+#include <pwd.h>
+#include <paths.h>
+
+#include <event.h>
+
+#include "pdu.h"
+#include "lldpctl.h"
+
+#include <syslog.h>
+#include "log.h"
+
+#ifndef nitems
+#define nitems(_a) ((sizeof((_a)) / sizeof((_a)[0])))
+#endif
+
+int rdaemon(int);
+
+#define LLDPD_USER "_lldp"
+
+#define CMSG_FOREACH(_cmsg, _msgp) \
+ for ((_cmsg) = CMSG_FIRSTHDR((_msgp)); \
+ (_cmsg) != NULL; \
+ (_cmsg) = CMSG_NXTHDR((_msgp), (_cmsg)))
+
+static const uint8_t maddr[ETHER_ADDR_LEN] =
+ { 0x01, 0x80, 0xc2, 0x00, 0x00, 0x0e };
+
+static inline int
+cmsg_match(const struct cmsghdr *cmsg, size_t len, int level, int type)
+{
+ return (cmsg->cmsg_len == CMSG_LEN(len) &&
+ cmsg->cmsg_level == level && cmsg->cmsg_type == type);
+}
+
+#define CMSG_MATCH(_cmsg, _len, _level, _type) \
+ cmsg_match((_cmsg), (_len), (_level), (_type))
+
+struct iface;
+
+struct lldp_msap {
+ struct iface *msap_iface;
+ struct ether_addr msap_saddr;
+ struct ether_addr msap_daddr;
+ struct timespec msap_created;
+ struct timespec msap_updated;
+ uint64_t msap_packets;
+ uint64_t msap_updates;
+
+ struct event msap_expiry;
+
+ TAILQ_ENTRY(lldp_msap) msap_entry; /* iface list */
+ TAILQ_ENTRY(lldp_msap) msap_aentry; /* daemon list */
+ unsigned int msap_refs;
+
+ void * *msap_pdu;
+ size_t msap_pdu_len;
+
+ /*
+ * LLDP PDUs are required to start with chassis id, port
+ * id, and ttl as the first three TLVs. The MSAP id is made
+ * up of the chassis and port id, so rathert than extract and
+ * compose an id out of these TLVs, we can use the start of
+ * the PDU directly as the identifier.
+ */
+ unsigned int msap_id_len;
+};
+
+TAILQ_HEAD(lldp_msaps, lldp_msap);
+
+struct iface_key {
+ unsigned int if_index;
+ char if_name[IFNAMSIZ];
+};
+
+struct iface {
+ struct iface_key if_key; /* must be first */
+ RBT_ENTRY(iface) if_entry;
+
+ struct lldpd *if_lldpd;
+ struct lldp_msaps if_msaps;
+
+ uint64_t if_agent_counters[AGENT_COUNTER_NCOUNTERS];
+};
+
+RBT_HEAD(ifaces, iface);
+
+static inline int
+
+iface_cmp(const struct iface *a, const struct iface *b)
+{
+ const struct iface_key *ka = &a->if_key;
+ const struct iface_key *kb = &b->if_key;
+
+ if (ka->if_index > kb->if_index)
+ return (1);
+ if (ka->if_index < kb->if_index)
+ return (-1);
+ return (0);
+}
+
+RBT_PROTOTYPE(ifaces, iface, if_entry, iface_cmp);
+
+struct lldpd_ctl {
+ struct lldpd *ctl_lldpd;
+
+ struct event ctl_rd_ev;
+ struct event ctl_wr_ev;
+
+ uid_t ctl_peer_uid;
+ gid_t ctl_peer_gid;
+
+ void (*ctl_handler)(struct lldpd *, struct lldpd_ctl *, int fd);
+ void *ctl_ctx;
+};
+
+struct lldpd {
+ const char *ctl_path;
+
+ struct event rt_ev;
+ struct event en_ev;
+ struct event ctl_ev;
+ int s;
+
+ struct ifaces ifaces;
+
+ struct lldp_msaps msaps;
+
+ uint64_t agent_counters[AGENT_COUNTER_NCOUNTERS];
+};
+
+static void rtsock_open(struct lldpd *);
+static void rtsock_recv(int, short, void *);
+static void ensock_open(struct lldpd *);
+static void ensock_recv(int, short, void *);
+static void ctlsock_open(struct lldpd *);
+static void ctlsock_accept(int, short, void *);
+
+static int getall(struct lldpd *);
+
+extern char *__progname;
+
+__dead static void
+usage(void)
+{
+ fprintf(stderr, "usage: %s [-d] [-s /path/to/ctl.sock]\n", __progname);
+ exit(1);
+}
+
+int
+main(int argc, char *argv[])
+{
+ struct lldpd _lldpd = {
+ .ctl_path = LLDP_CTL_PATH,
+ .ifaces = RBT_INITIALIZER(_lldpd.ifaces),
+ .msaps = TAILQ_HEAD_INITIALIZER(_lldpd.msaps),
+ };
+ struct lldpd *lldpd = &_lldpd; /* let me use -> consistently */
+ struct passwd *pw;
+ int debug = 0;
+ int devnull = -1;
+
+ int ch;
+
+ while ((ch = getopt(argc, argv, "ds:")) != -1) {
+ switch (ch) {
+ case 'd':
+ debug = 1;
+ break;
+ case 's':
+ lldpd->ctl_path = optarg;
+ break;
+ default:
+ usage();
+ /* NOTREACHED */
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (argc != 0)
+ usage();
+
+ if (geteuid() != 0)
+ errx(1, "need root privileges");
+
+ pw = getpwnam(LLDPD_USER);
+ if (pw == NULL)
+ errx(1, "no %s user", LLDPD_USER);
+
+ if (!debug) {
+ logger_syslog(__progname, LOG_DAEMON);
+ devnull = open(_PATH_DEVNULL, O_RDWR);
+ if (devnull == -1)
+ err(1, "%s", _PATH_DEVNULL);
+ }
+
+ rtsock_open(lldpd);
+ ensock_open(lldpd);
+ ctlsock_open(lldpd);
+
+ printf("debug = %d\n", debug);
+
+ if (chroot(pw->pw_dir) == -1)
+ err(1, "chroot %s", pw->pw_dir);
+ if (chdir("/") == -1)
+ err(1, "chdir %s", pw->pw_dir);
+
+ /* drop privs */
+ if (setgroups(1, &pw->pw_gid) ||
+ setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) ||
+ setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid))
+ errx(1, "can't drop privileges");
+
+ pw = NULL;
+ endpwent();
+
+ lldpd->s = socket(AF_INET, SOCK_DGRAM, 0);
+ if (lldpd->s == -1)
+ err(1, "inet sock");
+
+ if (getall(lldpd) == -1)
+ warn("getall");
+
+ if (!debug && rdaemon(devnull) == -1)
+ err(1, "unable to daemonize");
+
+ event_init();
+
+ event_set(&lldpd->rt_ev, EVENT_FD(&lldpd->rt_ev),
+ EV_READ|EV_PERSIST, rtsock_recv, lldpd);
+ event_set(&lldpd->en_ev, EVENT_FD(&lldpd->en_ev),
+ EV_READ|EV_PERSIST, ensock_recv, lldpd);
+ event_set(&lldpd->ctl_ev, EVENT_FD(&lldpd->ctl_ev),
+ EV_READ|EV_PERSIST, ctlsock_accept, lldpd);
+
+ event_add(&lldpd->rt_ev, NULL);
+ event_add(&lldpd->en_ev, NULL);
+ event_add(&lldpd->ctl_ev, NULL);
+
+ event_dispatch();
+
+ return (0);
+}
+
+static void
+agent_counter_inc(struct lldpd *lldpd, struct iface *ifp,
+ enum agent_counter c)
+{
+ lldpd->agent_counters[c]++;
+ ifp->if_agent_counters[c]++;
+}
+
+static struct lldp_msap *
+lldp_msap_take(struct lldpd *lldpd, struct lldp_msap *msap)
+{
+ msap->msap_refs++;
+ return (msap);
+}
+
+static void
+lldp_msap_rele(struct lldpd *lldpd, struct lldp_msap *msap)
+{
+ if (--msap->msap_refs == 0) {
+ TAILQ_REMOVE(&lldpd->msaps, msap, msap_aentry);
+ free(msap->msap_pdu);
+ free(msap);
+ }
+}
+
+static void
+lldp_msap_remove(struct iface *ifp, struct lldp_msap *msap)
+{
+ evtimer_del(&msap->msap_expiry);
+ TAILQ_REMOVE(&ifp->if_msaps, msap, msap_entry);
+
+ lldp_msap_rele(ifp->if_lldpd, msap);
+}
+
+static void
+lldp_msap_expire(int nil, short events, void *arg)
+{
+ struct lldp_msap *msap = arg;
+ struct iface *ifp = msap->msap_iface;
+ struct lldpd *lldpd = ifp->if_lldpd;
+
+ agent_counter_inc(lldpd, ifp, statsAgeoutsTotal);
+
+ ldebug("%s: entry from %s has expired", ifp->if_key.if_name,
+ ether_ntoa(&msap->msap_saddr));
+
+ lldp_msap_remove(ifp, msap);
+}
+
+static void
+rtsock_open(struct lldpd *lldpd)
+{
+ unsigned int rtfilter;
+ int s;
+
+ s = socket(AF_ROUTE, SOCK_RAW | SOCK_NONBLOCK, AF_UNSPEC);
+ if (s == -1)
+ err(1, "route socket");
+
+ rtfilter = ROUTE_FILTER(RTM_IFINFO) | ROUTE_FILTER(RTM_IFANNOUNCE);
+ if (setsockopt(s, AF_ROUTE, ROUTE_MSGFILTER,
+ &rtfilter, sizeof(rtfilter)) == -1)
+ err(1, "route socket setsockopt msgfilter");
+
+ event_set(&lldpd->rt_ev, s, 0, NULL, NULL);
+}
+
+static inline struct iface *
+iface_insert(struct lldpd *lldpd, struct iface *ifp)
+{
+ return (RBT_INSERT(ifaces, &lldpd->ifaces, ifp));
+}
+
+static struct iface *
+iface_find(struct lldpd *lldpd, const char *ifname, int ifindex)
+{
+ struct iface_key key = { .if_index = ifindex };
+
+ return (RBT_FIND(ifaces, &lldpd->ifaces, (struct iface *)&key));
+}
+
+static inline void
+iface_remove(struct lldpd *lldpd, struct iface *ifp)
+{
+ RBT_REMOVE(ifaces, &lldpd->ifaces, ifp);
+}
+
+static void
+rtsock_if_attach(struct lldpd *lldpd, const struct if_announcemsghdr *ifan)
+{
+ struct ifreq ifr;
+ struct if_data ifi;
+ struct iface *ifp;
+ struct frame_mreq fmr;
+
+ memset(&ifr, 0, sizeof(ifr));
+ memcpy(ifr.ifr_name, ifan->ifan_name, sizeof(ifr.ifr_name));
+ ifr.ifr_data = (caddr_t)&ifi;
+
+ if (ioctl(lldpd->s, SIOCGIFDATA, &ifr) == -1) {
+ lwarn("%s index %u: attach get data",
+ ifan->ifan_name, ifan->ifan_index);
+ return;
+ }
+
+ if (ifi.ifi_type != IFT_ETHER)
+ return;
+
+ ifp = malloc(sizeof(*ifp));
+ if (ifp == NULL) {
+ lwarn("%s index %u: allocation",
+ ifan->ifan_name, ifan->ifan_index);
+ return;
+ }
+
+ ifp->if_key.if_index = ifan->ifan_index;
+ strlcpy(ifp->if_key.if_name, ifan->ifan_name,
+ sizeof(ifp->if_key.if_name));
+
+ ifp->if_lldpd = lldpd;
+ TAILQ_INIT(&ifp->if_msaps);
+
+ if (iface_insert(lldpd, ifp) != NULL) {
+ lwarnx("%s index %u: already exists",
+ ifan->ifan_name, ifan->ifan_index);
+ free(ifp);
+ return;
+ }
+
+ linfo("%s index %u: attached", ifan->ifan_name, ifan->ifan_index);
+
+ memset(&fmr, 0, sizeof(fmr));
+ fmr.fmr_ifindex = ifp->if_key.if_index;
+ memcpy(fmr.fmr_addr, maddr, ETHER_ADDR_LEN);
+
+ if (setsockopt(EVENT_FD(&lldpd->en_ev),
+ IFT_ETHER, FRAME_ADD_MEMBERSHIP,
+ &fmr, sizeof(fmr)) == -1) {
+ lwarn("%s index %u: add membership",
+ ifan->ifan_name, ifan->ifan_index);
+ }
+}
+
+static void
+rtsock_if_detach(struct lldpd *lldpd, const struct if_announcemsghdr *ifan)
+{
+ struct iface *ifp;
+ struct lldp_msap *msap, *nmsap;
+ struct frame_mreq fmr;
+
+ ifp = iface_find(lldpd, ifan->ifan_name, ifan->ifan_index);
+ if (ifp == NULL)
+ return;
+
+ memset(&fmr, 0, sizeof(fmr));
+ fmr.fmr_ifindex = ifp->if_key.if_index;
+ memcpy(fmr.fmr_addr, maddr, ETHER_ADDR_LEN);
+
+ if (setsockopt(EVENT_FD(&lldpd->en_ev),
+ IFT_ETHER, FRAME_DEL_MEMBERSHIP,
+ &fmr, sizeof(fmr)) == -1) {
+ lwarn("%s index %u: del membership",
+ ifp->if_key.if_name, ifp->if_key.if_index);
+ }
+
+ /* don't have to leave mcast group */
+ linfo("%s index %u: detached", ifan->ifan_name, ifan->ifan_index);
+
+ iface_remove(lldpd, ifp);
+
+ TAILQ_FOREACH_SAFE(msap, &ifp->if_msaps, msap_entry, nmsap)
+ lldp_msap_remove(ifp, msap);
+
+ free(ifp);
+}
+
+static void
+rtsock_ifannounce(struct lldpd *lldpd, const struct rt_msghdr *rtm, size_t len)
+{
+ const struct if_announcemsghdr *ifan;
+
+ if (len < sizeof(*ifan)) {
+ lwarnx("short ifannounce message: %zu < %zu", len,
+ sizeof(*ifan));
+ return;
+ }
+
+ ifan = (const struct if_announcemsghdr *)rtm;
+ if (ifan->ifan_index == 0) {
+ lwarnx("%s index %u: %s() invalid index, ignoring",
+ ifan->ifan_name, ifan->ifan_index, __func__);
+ return;
+ }
+
+ switch (ifan->ifan_what) {
+ case IFAN_ARRIVAL:
+ rtsock_if_attach(lldpd, ifan);
+ break;
+ case IFAN_DEPARTURE:
+ rtsock_if_detach(lldpd, ifan);
+ break;
+ default:
+ lwarnx("%s: %s index %u: unexpected ifannounce ifan %u",
+ __func__, ifan->ifan_name, ifan->ifan_index,
+ ifan->ifan_what);
+ return;
+ }
+}
+
+static void
+rtsock_recv(int s, short events, void *arg)
+{
+ struct lldpd *lldpd = arg;
+ char buf[1024];
+ const struct rt_msghdr *rtm = (struct rt_msghdr *)buf;
+ ssize_t rv;
+
+ rv = recv(s, buf, sizeof(buf), 0);
+ if (rv == -1) {
+ lwarn("route message");
+ return;
+ }
+
+ linfo("route message: %zd bytes", rv);
+ if (rtm->rtm_version != RTM_VERSION) {
+ lwarnx("routing message version %u not understood",
+ rtm->rtm_version);
+ return;
+ }
+
+ switch (rtm->rtm_type) {
+ case RTM_IFINFO:
+ linfo("ifinfo");
+ break;
+ case RTM_IFANNOUNCE:
+ rtsock_ifannounce(lldpd, rtm, rv);
+ break;
+ default:
+ return;
+ }
+}
+
+static void
+ensock_open(struct lldpd *lldpd)
+{
+ struct sockaddr_frame sfrm = {
+ .sfrm_family = AF_FRAME,
+ .sfrm_proto = htons(ETHERTYPE_LLDP),
+ };
+ int opt;
+ int s;
+
+ s = socket(AF_FRAME, SOCK_DGRAM | SOCK_NONBLOCK, IFT_ETHER);
+ if (s == -1)
+ err(1, "Ethernet socket");
+
+ opt = 1;
+ if (setsockopt(s, IFT_ETHER, FRAME_RECVDSTADDR,
+ &opt, sizeof(opt)) == -1)
+ err(1, "Ethernet setsockopt recv dstaddr");
+
+ if (bind(s, (struct sockaddr *)&sfrm, sizeof(sfrm)) == -1)
+ err(1, "Ethernet bind lldp");
+
+ event_set(&lldpd->en_ev, s, 0, NULL, NULL);
+}
+
+static void
+ensock_recv(int s, short events, void *arg)
+{
+ struct lldpd *lldpd = arg;
+ struct sockaddr_frame sfrm;
+ uint8_t buf[1500];
+
+ static const struct ether_addr naddr;
+ struct ether_addr *saddr = NULL;
+ struct ether_addr *daddr = (struct ether_addr *)&naddr;
+ struct cmsghdr *cmsg;
+ union {
+ struct cmsghdr hdr;
+ uint8_t buf[CMSG_SPACE(sizeof(*daddr))];
+ } cmsgbuf;
+ struct iovec iov[1] = {
+ { .iov_base = buf, .iov_len = sizeof(buf) },
+ };
+ struct msghdr msg = {
+ .msg_name = &sfrm,
+ .msg_namelen = sizeof(sfrm),
+ .msg_control = &cmsgbuf.buf,
+ .msg_controllen = sizeof(cmsgbuf.buf),
+ .msg_iov = iov,
+ .msg_iovlen = 1,
+ };
+ ssize_t rv;
+ size_t len;
+
+ struct iface *ifp;
+ struct iface_key key;
+ struct lldp_msap *msap;
+
+ struct tlv tlv;
+ unsigned int tlvs;
+ int ok;
+ unsigned int idlen;
+ unsigned int ttl;
+ struct timeval age;
+ int update = 0;
+
+ rv = recvmsg(s, &msg, 0);
+ if (rv == -1) {
+ lwarn("Ethernet recv");
+ return;
+ }
+
+ CMSG_FOREACH(cmsg, &msg) {
+ if (CMSG_MATCH(cmsg,
+ sizeof(*daddr), IFT_ETHER, FRAME_RECVDSTADDR)) {
+ daddr = (struct ether_addr *)CMSG_DATA(cmsg);
+ }
+ }
+ saddr = (struct ether_addr *)sfrm.sfrm_addr;
+
+ ldebug("%s: pdu from %s: %zd bytes", sfrm.sfrm_ifname,
+ ether_ntoa(saddr), rv);
+
+ key.if_index = sfrm.sfrm_ifindex;
+ ifp = RBT_FIND(ifaces, &lldpd->ifaces, (struct iface *)&key);
+ if (ifp == NULL) {
+ /* count */
+ return;
+ }
+
+ /* XXX check if RX is enabled */
+ agent_counter_inc(lldpd, ifp, statsFramesInTotal);
+
+ len = rv;
+
+ ok = tlv_first(&tlv, buf, len);
+ if (ok != 1) {
+ ldebug("%s: pdu from %s: first TLV extraction failed",
+ sfrm.sfrm_ifname, ether_ntoa(saddr));
+ goto discard;
+ }
+ if (tlv.tlv_type != LLDP_TLV_CHASSIS_ID) {
+ ldebug("%s: pdu from %s: first TLV type is not Chassis ID",
+ sfrm.sfrm_ifname, ether_ntoa(saddr));
+ goto discard;
+ }
+ if (tlv.tlv_len < 2 || tlv.tlv_len > 256) {
+ ldebug("%s: pdu from %s: "
+ "Chassis ID TLV length %u is out of range",
+ sfrm.sfrm_ifname, ether_ntoa(saddr),
+ tlv.tlv_len);
+ goto discard;
+ }
+
+ ok = tlv_next(&tlv, buf, len);
+ if (ok != 1) {
+ ldebug("%s: pdu from %s: second TLV extraction failed",
+ sfrm.sfrm_ifname, ether_ntoa(saddr));
+ goto discard;
+ }
+ if (tlv.tlv_type != LLDP_TLV_PORT_ID) {
+ ldebug("%s: pdu from %s: first TLV type is not Port ID",
+ sfrm.sfrm_ifname, ether_ntoa(saddr));
+ goto discard;
+ }
+ if (tlv.tlv_len < 2 || tlv.tlv_len > 256) {
+ ldebug("%s: pdu from %s: "
+ "Port ID TLV length %u is out of range",
+ sfrm.sfrm_ifname, ether_ntoa(saddr),
+ tlv.tlv_len);
+ goto discard;
+ }
+
+ ok = tlv_next(&tlv, buf, rv);
+ if (ok != 1) {
+ ldebug("%s: pdu from %s: third TLV extraction failed",
+ sfrm.sfrm_ifname, ether_ntoa(saddr));
+ goto discard;
+ }
+ if (tlv.tlv_type != LLDP_TLV_TTL) {
+ ldebug("%s: pdu from %s: third TLV type is not TTL",
+ sfrm.sfrm_ifname, ether_ntoa(saddr));
+ goto discard;
+ }
+ if (tlv.tlv_len < 2) {
+ ldebug("%s: pdu from %s: "
+ "TTL TLV length %u is too short",
+ sfrm.sfrm_ifname, ether_ntoa(saddr),
+ tlv.tlv_len);
+ goto discard;
+ }
+
+ ttl = pdu_u16(tlv.tlv_payload);
+
+ /*
+ * the tlv_offset points to the start of the current tlv,
+ * which is also the end of the previous tlv. the msap id is
+ * a concat of the first two tlvs, which is where the offset
+ * is now.
+ */
+ idlen = tlv.tlv_offset;
+
+ tlvs = (1 << LLDP_TLV_CHASSIS_ID) | (1 << LLDP_TLV_PORT_ID) |
+ (1 << LLDP_TLV_TTL);
+
+ for (;;) {
+ ok = tlv_next(&tlv, buf, rv);
+ if (ok == -1) {
+ lwarnx("TLV extraction failed");
+ goto discard;
+ }
+ if (ok == 0)
+ break;
+
+ switch (tlv.tlv_type) {
+ case LLDP_TLV_END:
+ lwarnx("end of pdu with non-zero length");
+ goto discard;
+
+ case LLDP_TLV_CHASSIS_ID:
+ case LLDP_TLV_PORT_ID:
+ case LLDP_TLV_TTL:
+ case LLDP_TLV_PORT_DESCR:
+ case LLDP_TLV_SYSTEM_NAME:
+ case LLDP_TLV_SYSTEM_DESCR:
+ case LLDP_TLV_SYSTEM_CAP:
+ if ((1 << tlv.tlv_type) & tlvs) {
+ lwarnx("TLV type %u repeated", tlv.tlv_type);
+ goto discard;
+ }
+ tlvs |= (1 << tlv.tlv_type);
+ break;
+ }
+ }
+
+ TAILQ_FOREACH(msap, &ifp->if_msaps, msap_entry) {
+ if (msap->msap_id_len == idlen &&
+ memcmp(msap->msap_pdu, buf, idlen) == 0)
+ break;
+ }
+
+ if (msap == NULL) {
+ if (ttl == 0) {
+ /* optimise DELETE_INFO below */
+ return;
+ }
+
+ msap = malloc(sizeof(*msap));
+ if (msap == NULL) {
+ lwarn("%s: msap alloc", ifp->if_key.if_name);
+ agent_counter_inc(lldpd, ifp,
+ statsFramesDiscardedTotal);
+ return;
+ }
+ msap->msap_iface = ifp;
+ msap->msap_pdu = NULL;
+ msap->msap_pdu_len = 0;
+ msap->msap_id_len = idlen;
+
+ if (clock_gettime(CLOCK_BOOTTIME, &msap->msap_created) == -1)
+ lerr(1, "CLOCK_BOOTTIME");
+
+ msap->msap_updated.tv_sec = 0;
+ msap->msap_updated.tv_nsec = 0;
+
+ msap->msap_packets = 1;
+ msap->msap_updates = 0;
+
+ msap->msap_refs = 1;
+ evtimer_set(&msap->msap_expiry, lldp_msap_expire, msap);
+ TAILQ_INSERT_TAIL(&ifp->if_msaps, msap, msap_entry);
+ TAILQ_INSERT_TAIL(&lldpd->msaps, msap, msap_aentry);
+ }
+
+ if (ttl == 0) {
+ /* DELETE_INFO */
+ if (msap->msap_pdu == NULL) {
+ lwarnx("new msap DELETE_INFO");
+ abort();
+ }
+
+ evtimer_del(&msap->msap_expiry);
+ TAILQ_REMOVE(&ifp->if_msaps, msap, msap_entry);
+ lldp_msap_rele(lldpd, msap);
+
+ ldebug("%s: entry from %s deleted",
+ sfrm.sfrm_ifname, ether_ntoa(saddr));
+ return;
+ }
+
+ if (len != msap->msap_pdu_len) {
+ void *pdu = realloc(msap->msap_pdu, len);
+ if (pdu == NULL) {
+ lwarn("%s: pdu alloc", ifp->if_key.if_name);
+ if (msap->msap_pdu == NULL) {
+ TAILQ_REMOVE(&lldpd->msaps, msap,
+ msap_aentry);
+ TAILQ_REMOVE(&ifp->if_msaps, msap,
+ msap_entry);
+ free(msap);
+ }
+ agent_counter_inc(lldpd, ifp,
+ statsFramesDiscardedTotal);
+ return;
+ }
+ msap->msap_pdu = pdu;
+ msap->msap_pdu_len = len;
+ update = 1;
+ } else if (memcmp(msap->msap_pdu, buf, len) != 0)
+ update = 1;
+
+ if (update) {
+ msap->msap_updates++;
+ memcpy(msap->msap_pdu, buf, len);
+ if (clock_gettime(CLOCK_BOOTTIME, &msap->msap_updated) == -1)
+ lerr(1, "CLOCK_BOOTTIME");
+ }
+ msap->msap_saddr = *saddr;
+ msap->msap_daddr = *daddr;
+ msap->msap_packets++;
+
+ age.tv_sec = ttl;
+ age.tv_usec = 0;
+ evtimer_add(&msap->msap_expiry, &age);
+
+ return;
+
+discard:
+ agent_counter_inc(lldpd, ifp, statsFramesDiscardedTotal);
+ agent_counter_inc(lldpd, ifp, statsFramesInErrorsTotal);
+}
+
+static void
+ctlsock_open(struct lldpd *lldpd)
+{
+ struct sockaddr_un sun = {
+ .sun_family = AF_UNIX,
+ };
+ const char *path = lldpd->ctl_path;
+ mode_t oumask;
+ int s;
+
+ if (strlcpy(sun.sun_path, path, sizeof(sun.sun_path)) >=
+ sizeof(sun.sun_path))
+ errc(ENAMETOOLONG, 1, "control socket %s", path);
+
+ s = socket(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK, 0);
+ if (s == -1)
+ err(1, "control socket");
+
+ /* try connect first? */
+
+ if (unlink(path) == -1) {
+ if (errno != ENOENT)
+ err(1, "control socket %s unlink", path);
+ }
+
+ oumask = umask(S_IXUSR|S_IXGRP|S_IWOTH|S_IROTH|S_IXOTH);
+ if (oumask == -1)
+ err(1, "umask");
+ if (bind(s, (struct sockaddr *)&sun, sizeof(sun)) == -1)
+ err(1, "control socket %s bind", path);
+ if (umask(oumask) == -1)
+ err(1, "umask restore");
+
+ if (chmod(path, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP) == -1)
+ err(1, "control socket %s chmod", path);
+
+ if (listen(s, 5) == -1)
+ err(1, "control socket %s listen", path);
+
+ event_set(&lldpd->ctl_ev, s, 0, NULL, NULL);
+}
+
+static void
+ctl_close(struct lldpd *lldpd, struct lldpd_ctl *ctl)
+{
+ int fd = EVENT_FD(&ctl->ctl_rd_ev);
+
+ event_del(&ctl->ctl_rd_ev);
+ event_del(&ctl->ctl_wr_ev);
+ free(ctl);
+ close(fd);
+}
+
+static ssize_t ctl_ping(struct lldpd *, struct lldpd_ctl *,
+ const void *, size_t);
+static ssize_t ctl_msap_req(struct lldpd *, struct lldpd_ctl *,
+ const void *, size_t);
+
+static void
+ctl_recv(int fd, short events, void *arg)
+{
+ struct lldpd_ctl *ctl = arg;
+ struct lldpd *lldpd = ctl->ctl_lldpd;
+ unsigned int msgtype;
+ uint8_t buf[4096];
+ struct iovec iov[2] = {
+ { &msgtype, sizeof(msgtype) },
+ { buf, sizeof(buf) },
+ };
+ struct msghdr msg = {
+ .msg_iov = iov,
+ .msg_iovlen = nitems(iov),
+ };
+ ssize_t rv;
+ size_t len;
+
+ rv = recvmsg(fd, &msg, 0);
+ if (rv == -1) {
+ lwarn("ctl recv");
+ return;
+ }
+ if (rv == 0) {
+ ctl_close(lldpd, ctl);
+ return;
+ }
+
+ if (ctl->ctl_handler != NULL) {
+ ldebug("ctl recv while not idle");
+ ctl_close(lldpd, ctl);
+ return;
+ }
+
+ len = rv;
+ ldebug("%s: msgtype %u, %zu bytes", __func__, msgtype, len);
+ if (len < sizeof(msgtype)) {
+ /* short message */
+ ctl_close(lldpd, ctl);
+ return;
+ }
+ len -= sizeof(msgtype);
+
+ switch (msgtype) {
+ case LLDP_CTL_MSG_PING:
+ rv = ctl_ping(lldpd, ctl, buf, len);
+ break;
+ case LLDP_CTL_MSG_MSAP_REQ:
+ rv = ctl_msap_req(lldpd, ctl, buf, len);
+ break;
+ default:
+ lwarnx("%s: unhandled message %u", __func__, msgtype);
+ rv = -1;
+ return;
+ }
+
+ if (rv == -1) {
+ ctl_close(lldpd, ctl);
+ return;
+ }
+
+ (*ctl->ctl_handler)(lldpd, ctl, fd);
+}
+
+static void
+ctl_done(struct lldpd *lldpd, struct lldpd_ctl *ctl)
+{
+ ctl->ctl_handler = NULL;
+ ctl->ctl_ctx = NULL;
+}
+
+static void
+ctl_pong(struct lldpd *lldpd, struct lldpd_ctl *ctl, int fd)
+{
+ unsigned int msgtype = LLDP_CTL_MSG_PONG;
+ struct iovec *piov = ctl->ctl_ctx;
+ struct iovec iov[2] = {
+ { &msgtype, sizeof(msgtype) },
+ *piov,
+ };
+ struct msghdr msg = {
+ .msg_iov = iov,
+ .msg_iovlen = nitems(iov),
+ };
+ ssize_t rv;
+
+ rv = sendmsg(fd, &msg, 0);
+ if (rv == -1) {
+ switch (errno) {
+ case EINTR:
+ case EAGAIN:
+ event_add(&ctl->ctl_wr_ev, NULL);
+ return;
+ default:
+ lwarn("%s", __func__);
+ break;
+ }
+
+ ctl_close(lldpd, ctl);
+ return;
+ }
+
+ free(piov->iov_base);
+ free(piov);
+
+ ctl_done(lldpd, ctl);
+}
+
+static ssize_t
+ctl_ping(struct lldpd *lldpd, struct lldpd_ctl *ctl,
+ const void *buf, size_t len)
+{
+ struct iovec *iov;
+
+ iov = malloc(sizeof(*iov));
+ if (iov == NULL) {
+ lwarn("%s iovec", __func__);
+ return (-1);
+ }
+
+ iov->iov_base = malloc(len);
+ if (iov->iov_base == NULL) {
+ lwarn("%s", __func__);
+ free(iov);
+ return (-1);
+ }
+ memcpy(iov->iov_base, buf, len);
+ iov->iov_len = len;
+
+ ctl->ctl_handler = ctl_pong;
+ ctl->ctl_ctx = iov;
+
+ return (0);
+}
+
+static void
+ctl_send(int fd, short events, void *arg)
+{
+ struct lldpd_ctl *ctl = arg;
+ struct lldpd *lldpd = ctl->ctl_lldpd;
+
+ ctl->ctl_handler(lldpd, ctl, fd);
+}
+
+struct ctl_msap_ctx {
+ char ifname[IFNAMSIZ];
+ struct lldp_msap *msap;
+};
+
+static void ctl_msap(struct lldpd *, struct lldpd_ctl *, int);
+static ssize_t ctl_msap_req_next(struct lldpd *, struct lldpd_ctl *,
+ struct lldp_msap *);
+static void ctl_msap_req_end(struct lldpd *, struct lldpd_ctl *,
+ int);
+
+static ssize_t
+ctl_msap_req(struct lldpd *lldpd, struct lldpd_ctl *ctl,
+ const void *buf, size_t len)
+{
+ const struct lldp_ctl_msg_msap_req *req;
+ struct ctl_msap_ctx *ctx;
+
+ if (len != sizeof(*req)) {
+ lwarnx("%s req len", __func__);
+ return (-1);
+ }
+ req = buf;
+
+ ctx = malloc(sizeof(*ctx));
+ if (ctx == NULL) {
+ lwarnx("%s ctx", __func__);
+ return (-1);
+ }
+ memcpy(ctx->ifname, req->ifname, sizeof(ctx->ifname));
+
+ ctl->ctl_handler = ctl_msap;
+ ctl->ctl_ctx = ctx;
+
+ return (ctl_msap_req_next(lldpd, ctl, TAILQ_FIRST(&lldpd->msaps)));
+}
+
+static void
+ctl_msap(struct lldpd *lldpd, struct lldpd_ctl *ctl, int fd)
+{
+ struct ctl_msap_ctx *ctx = ctl->ctl_ctx;
+ struct lldp_msap *msap = ctx->msap;
+ struct iface *ifp;
+ ssize_t rv;
+
+ unsigned int msgtype = LLDP_CTL_MSG_MSAP;
+ struct lldp_ctl_msg_msap msg_msap;
+ struct iovec iov[3] = {
+ { &msgtype, sizeof(msgtype) },
+ { &msg_msap, sizeof(msg_msap) }
+ };
+ struct msghdr msg = {
+ .msg_iov = iov,
+ .msg_iovlen = nitems(iov),
+ };
+
+ memset(&msg_msap, 0, sizeof(msg_msap));
+
+ ifp = msap->msap_iface;
+ if (ifp != NULL) {
+ strlcpy(msg_msap.ifname, ifp->if_key.if_name,
+ sizeof(msg_msap.ifname));
+ }
+ msg_msap.saddr = msap->msap_saddr;
+ msg_msap.daddr = msap->msap_daddr;
+ msg_msap.created = msap->msap_created;
+ msg_msap.updated = msap->msap_updated;
+ msg_msap.packets = msap->msap_packets;
+ msg_msap.updates = msap->msap_updates;
+
+ iov[2].iov_base = msap->msap_pdu;
+ iov[2].iov_len = msap->msap_pdu_len;
+
+ rv = sendmsg(fd, &msg, 0);
+ if (rv == -1) {
+ switch (errno) {
+ case EINTR:
+ case EAGAIN:
+ event_add(&ctl->ctl_wr_ev, NULL);
+ return;
+ default:
+ lwarn("ctl send");
+ break;
+ }
+
+ lldp_msap_rele(lldpd, msap);
+ free(ctx);
+ ctl_close(lldpd, ctl);
+ return;
+ }
+
+ rv = ctl_msap_req_next(lldpd, ctl, TAILQ_NEXT(msap, msap_aentry));
+ lldp_msap_rele(lldpd, msap);
+
+ if (rv == -1) {
+ free(ctx);
+ ctl_close(lldpd, ctl);
+ return;
+ }
+
+ event_add(&ctl->ctl_wr_ev, NULL);
+}
+
+static ssize_t
+ctl_msap_req_next(struct lldpd *lldpd, struct lldpd_ctl *ctl,
+ struct lldp_msap *msap)
+{
+ struct ctl_msap_ctx *ctx = ctl->ctl_ctx;
+ struct iface *ifp;
+
+ for (;;) {
+ if (msap == NULL) {
+ ctl->ctl_handler = ctl_msap_req_end;
+ return (0);
+ }
+
+ if (ctx->ifname[0] == '\0')
+ break;
+ ifp = msap->msap_iface;
+ if (ifp != NULL && strncmp(ifp->if_key.if_name, ctx->ifname,
+ sizeof(ifp->if_key.if_name)) == 0)
+ break;
+
+ msap = TAILQ_NEXT(msap, msap_aentry);
+ }
+
+ ctx->msap = lldp_msap_take(lldpd, msap);
+ return (0);
+}
+
+static void
+ctl_msap_req_end(struct lldpd *lldpd, struct lldpd_ctl *ctl, int fd)
+{
+ struct ctl_msap_ctx *ctx = ctl->ctl_ctx;
+
+ unsigned int msgtype = LLDP_CTL_MSG_MSAP_END;
+ ssize_t rv;
+
+ rv = send(fd, &msgtype, sizeof(msgtype), 0);
+ if (rv == -1) {
+ switch (errno) {
+ case EINTR:
+ case EAGAIN:
+ event_add(&ctl->ctl_wr_ev, NULL);
+ return;
+ default:
+ lwarn("%s", __func__);
+ break;
+ }
+
+ free(ctx);
+ ctl_close(lldpd, ctl);
+ return;
+ }
+
+ free(ctx);
+ ctl_done(lldpd, ctl);
+}
+
+static void
+ctlsock_accept(int s, short events, void *arg)
+{
+ struct lldpd *lldpd = arg;
+ struct lldpd_ctl *ctl;
+ int fd;
+
+ fd = accept4(s, NULL, NULL, SOCK_NONBLOCK);
+ if (fd == -1) {
+ lwarn("control socket %s accept", lldpd->ctl_path);
+ return;
+ }
+
+ ctl = malloc(sizeof(*ctl));
+ if (ctl == NULL) {
+ lwarn("ctl alloc");
+ close(fd);
+ return;
+ }
+ ctl->ctl_lldpd = lldpd;
+ ctl->ctl_handler = NULL;
+ ctl->ctl_ctx = NULL;
+
+ if (getpeereid(fd, &ctl->ctl_peer_uid, &ctl->ctl_peer_gid) == -1)
+ err(1, "ctl getpeereid");
+
+ event_set(&ctl->ctl_rd_ev, fd, EV_READ|EV_PERSIST,
+ ctl_recv, ctl);
+ event_set(&ctl->ctl_wr_ev, fd, EV_WRITE,
+ ctl_send, ctl);
+
+ event_add(&ctl->ctl_rd_ev, NULL);
+}
+
+static int
+getall(struct lldpd *lldpd)
+{
+ struct ifaddrs *ifa0, *ifa;
+ struct sockaddr_dl *sdl;
+ struct if_data *ifi;
+ struct iface *ifp;
+ struct frame_mreq fmr;
+
+ if (getifaddrs(&ifa0) == -1)
+ return (-1);
+
+ for (ifa = ifa0; ifa != NULL; ifa = ifa->ifa_next) {
+ if (ifa->ifa_addr->sa_family != AF_LINK)
+ continue;
+ ifi = ifa->ifa_data;
+ if (ifi->ifi_type != IFT_ETHER)
+ continue;
+
+ sdl = (struct sockaddr_dl *)ifa->ifa_addr;
+ if (sdl->sdl_index == 0) {
+ warnx("interface %s has index 0, skipping",
+ ifa->ifa_name);
+ continue;
+ }
+
+ printf("%s index %u\n", ifa->ifa_name, sdl->sdl_index);
+
+ ifp = malloc(sizeof(*ifp));
+ if (ifp == NULL) {
+ warn("interface %s allocation", ifa->ifa_name);
+ continue;
+ }
+
+ ifp->if_key.if_index = sdl->sdl_index;
+ strlcpy(ifp->if_key.if_name, ifa->ifa_name,
+ sizeof(ifp->if_key.if_name));
+
+ ifp->if_lldpd = lldpd;
+ TAILQ_INIT(&ifp->if_msaps);
+
+ if (RBT_INSERT(ifaces, &lldpd->ifaces, ifp) != NULL) {
+ warnx("interface %s: index %u already exists",
+ ifa->ifa_name, ifp->if_key.if_index);
+ free(ifp);
+ }
+
+ memset(&fmr, 0, sizeof(fmr));
+ fmr.fmr_ifindex = ifp->if_key.if_index;
+ memcpy(fmr.fmr_addr, maddr, ETHER_ADDR_LEN);
+
+ if (setsockopt(EVENT_FD(&lldpd->en_ev),
+ IFT_ETHER, FRAME_ADD_MEMBERSHIP,
+ &fmr, sizeof(fmr)) == -1)
+ warn("interface %s: add membership", ifa->ifa_name);
+ }
+
+ freeifaddrs(ifa0);
+
+ return (0);
+}
+
+RBT_GENERATE(ifaces, iface, if_entry, iface_cmp);
+
+/* daemon(3) clone, intended to be used in a "r"estricted environment */
+int
+rdaemon(int devnull)
+{
+ if (devnull == -1) {
+ errno = EBADF;
+ return (-1);
+ }
+ if (fcntl(devnull, F_GETFL) == -1)
+ return (-1);
+
+ switch (fork()) {
+ case -1:
+ return (-1);
+ case 0:
+ break;
+ default:
+ _exit(0);
+ }
+
+ if (setsid() == -1)
+ return (-1);
+
+ (void)dup2(devnull, STDIN_FILENO);
+ (void)dup2(devnull, STDOUT_FILENO);
+ (void)dup2(devnull, STDERR_FILENO);
+ if (devnull > 2)
+ (void)close(devnull);
+
+ return (0);
+}
Index: usr.sbin/lldpd/lldpctl.h
===================================================================
RCS file: usr.sbin/lldpd/lldpctl.h
diff -N usr.sbin/lldpd/lldpctl.h
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ usr.sbin/lldpd/lldpctl.h 29 Apr 2025 01:47:08 -0000
@@ -0,0 +1,78 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2024 David Gwynne <dlg@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#define LLDP_CTL_PATH "/var/run/lldp.sock"
+
+enum lldp_ctl_msg {
+ LLDP_CTL_MSG_PING,
+ LLDP_CTL_MSG_PONG,
+
+ LLDP_CTL_MSG_MSAP_REQ, /* ctl -> daemon */
+ LLDP_CTL_MSG_MSAP, /* daemon -> ctl */
+ LLDP_CTL_MSG_MSAP_END, /* daemon -> ctl */
+
+ LLDP_CTL_MSG_ACTR_REQ, /* ctl -> daemon */
+ LLDP_CTL_MSG_ACTR, /* daemon -> ctl */
+ LLDP_CTL_MSG_ACTR_END, /* daemon -> ctl */
+};
+
+enum agent_counter {
+ statsAgeoutsTotal,
+ statsFramesDiscardedTotal,
+ statsFramesInErrorsTotal,
+ statsFramesInTotal,
+ statsFramesOutTotal,
+ statsTLVsDiscardedTotal,
+ statsTLVsUnrecognisedTotal,
+ lldpduLengthErrors,
+
+ AGENT_COUNTER_NCOUNTERS
+};
+
+struct lldp_ctl_msg_msap_req {
+ char ifname[IFNAMSIZ];
+ unsigned int gen;
+};
+
+struct lldp_ctl_msg_msap {
+ char ifname[IFNAMSIZ];
+ unsigned int gen;
+ struct ether_addr saddr;
+ struct ether_addr daddr;
+
+ struct timespec created;
+ struct timespec updated;
+
+ uint64_t packets;
+ uint64_t updates;
+
+ /* followed by the pdu */
+};
+
+struct lldp_ctl_msg_actrs_req {
+ unsigned int which;
+#define LLDP_ACTRS_ALL 0
+#define LLDP_ACTRS_DAEMON 1
+#define LLDP_ACTRS_IFACE 2
+ char ifname[IFNAMSIZ];
+};
+
+struct lldp_ctl_msg_actrs {
+ char ifname[IFNAMSIZ];
+ uint64_t ctrs[AGENT_COUNTER_NCOUNTERS];
+};
Index: usr.sbin/lldpd/log.c
===================================================================
RCS file: usr.sbin/lldpd/log.c
diff -N usr.sbin/lldpd/log.c
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ usr.sbin/lldpd/log.c 29 Apr 2025 01:47:08 -0000
@@ -0,0 +1,150 @@
+
+/*
+ * Copyright (c) 2008 David Gwynne <loki@animata.net>
+ *
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <err.h>
+#include <time.h>
+#include <syslog.h>
+#include <stdarg.h>
+
+#include "log.h"
+
+static const struct __logger conslogger = {
+ err,
+ errx,
+ warn,
+ warnx,
+ warnx, /* info */
+ warnx /* debug */
+};
+
+__dead static void syslog_err(int, const char *, ...)
+ __attribute__((__format__ (printf, 2, 3)));
+__dead static void syslog_errx(int, const char *, ...)
+ __attribute__((__format__ (printf, 2, 3)));
+static void syslog_warn(const char *, ...)
+ __attribute__((__format__ (printf, 1, 2)));
+static void syslog_warnx(const char *, ...)
+ __attribute__((__format__ (printf, 1, 2)));
+static void syslog_info(const char *, ...)
+ __attribute__((__format__ (printf, 1, 2)));
+static void syslog_debug(const char *, ...)
+ __attribute__((__format__ (printf, 1, 2)));
+static void syslog_vstrerror(int, int, const char *, va_list)
+ __attribute__((__format__ (printf, 3, 0)));
+
+static const struct __logger syslogger = {
+ syslog_err,
+ syslog_errx,
+ syslog_warn,
+ syslog_warnx,
+ syslog_info,
+ syslog_debug
+};
+
+const struct __logger *__logger = &conslogger;
+
+void
+logger_syslog(const char *progname, int facility)
+{
+ openlog(progname, LOG_PID | LOG_NDELAY, facility);
+ tzset();
+
+ __logger = &syslogger;
+}
+
+static void
+syslog_vstrerror(int e, int priority, const char *fmt, va_list ap)
+{
+ char *s;
+
+ if (vasprintf(&s, fmt, ap) == -1) {
+ syslog(LOG_EMERG, "unable to alloc in syslog_vstrerror");
+ exit(1);
+ }
+
+ syslog(priority, "%s: %s", s, strerror(e));
+
+ free(s);
+}
+
+static void
+syslog_err(int ecode, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ syslog_vstrerror(errno, LOG_CRIT, fmt, ap);
+ va_end(ap);
+
+ exit(ecode);
+}
+
+static void
+syslog_errx(int ecode, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vsyslog(LOG_CRIT, fmt, ap);
+ va_end(ap);
+
+ exit(ecode);
+}
+
+static void
+syslog_warn(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ syslog_vstrerror(errno, LOG_ERR, fmt, ap);
+ va_end(ap);
+}
+
+static void
+syslog_warnx(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vsyslog(LOG_ERR, fmt, ap);
+ va_end(ap);
+}
+
+static void
+syslog_info(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vsyslog(LOG_INFO, fmt, ap);
+ va_end(ap);
+}
+
+static void
+syslog_debug(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vsyslog(LOG_DEBUG, fmt, ap);
+ va_end(ap);
+}
Index: usr.sbin/lldpd/log.h
===================================================================
RCS file: usr.sbin/lldpd/log.h
diff -N usr.sbin/lldpd/log.h
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ usr.sbin/lldpd/log.h 29 Apr 2025 01:47:08 -0000
@@ -0,0 +1,47 @@
+
+/*
+ * Copyright (c) 2008 David Gwynne <loki@animata.net>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef _LOG_H_
+#define _LOG_H_
+
+struct __logger {
+ __dead void (*err)(int, const char *, ...)
+ __attribute__((__format__ (printf, 2, 3)));
+ __dead void (*errx)(int, const char *, ...)
+ __attribute__((__format__ (printf, 2, 3)));
+ void (*warn)(const char *, ...)
+ __attribute__((__format__ (printf, 1, 2)));
+ void (*warnx)(const char *, ...)
+ __attribute__((__format__ (printf, 1, 2)));
+ void (*info)(const char *, ...)
+ __attribute__((__format__ (printf, 1, 2)));
+ void (*debug)(const char *, ...)
+ __attribute__((__format__ (printf, 1, 2)));
+};
+
+extern const struct __logger *__logger;
+
+#define lerr(_e, _f...) __logger->err((_e), _f)
+#define lerrx(_e, _f...) __logger->errx((_e), _f)
+#define lwarn(_f...) __logger->warn(_f)
+#define lwarnx(_f...) __logger->warnx(_f)
+#define linfo(_f...) __logger->info(_f)
+#define ldebug(_f...) __logger->debug(_f)
+
+void logger_syslog(const char *, int);
+
+#endif /* _LOG_H_ */
Index: usr.sbin/lldpd/pdu.c
===================================================================
RCS file: usr.sbin/lldpd/pdu.c
diff -N usr.sbin/lldpd/pdu.c
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ usr.sbin/lldpd/pdu.c 29 Apr 2025 01:47:08 -0000
@@ -0,0 +1,93 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2024 David Gwynne <dlg@openbsd.org>
+ *
+ * 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 <stdlib.h>
+#include <stdint.h>
+
+#include "pdu.h"
+
+uint16_t
+pdu_u16(const uint8_t *buf)
+{
+ uint16_t u16;
+
+ u16 = (uint16_t)buf[0] << 8;
+ u16 |= (uint16_t)buf[1];
+
+ return (u16);
+}
+
+uint32_t
+pdu_u32(const uint8_t *buf)
+{
+ uint32_t u32;
+
+ u32 = (uint32_t)buf[0] << 24;
+ u32 |= (uint32_t)buf[1] << 16;
+ u32 |= (uint32_t)buf[2] << 8;
+ u32 |= (uint32_t)buf[3];
+
+ return (u32);
+}
+
+static int
+tlv_read(struct tlv *tlv, const uint8_t *buf, size_t len, unsigned int offset)
+{
+ unsigned int hdr;
+ unsigned int plen;
+
+ len -= offset;
+ if (len == 0)
+ return (0);
+ if (len < 2)
+ return (-1);
+
+ buf += offset;
+ hdr = pdu_u16(buf);
+
+ /* End of LLDPDU TLV */
+ if (hdr == 0)
+ return (0);
+
+ plen = hdr & 0x1ff;
+
+ len -= 2;
+ if (len < plen)
+ return (-1);
+
+ tlv->tlv_payload = buf + 2;
+ tlv->tlv_type = hdr >> 9;
+ tlv->tlv_len = plen;
+ tlv->tlv_offset = offset;
+
+ return (1);
+}
+
+int
+tlv_first(struct tlv *tlv, const void *pdu, size_t len)
+{
+ return (tlv_read(tlv, pdu, len, 0));
+}
+
+int
+tlv_next(struct tlv *tlv, const void *pdu, size_t len)
+{
+ unsigned int offset = tlv->tlv_offset + 2 + tlv->tlv_len;
+
+ return (tlv_read(tlv, pdu, len, offset));
+}
Index: usr.sbin/lldpd/pdu.h
===================================================================
RCS file: usr.sbin/lldpd/pdu.h
diff -N usr.sbin/lldpd/pdu.h
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ usr.sbin/lldpd/pdu.h 29 Apr 2025 01:47:08 -0000
@@ -0,0 +1,32 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2024 David Gwynne <dlg@openbsd.org>
+ *
+ * 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.
+ */
+
+struct tlv {
+ const void *tlv_payload;
+
+ unsigned int tlv_type;
+ unsigned int tlv_len;
+
+ unsigned int tlv_offset;
+};
+
+uint16_t pdu_u16(const uint8_t *);
+uint32_t pdu_u32(const uint8_t *);
+
+int tlv_first(struct tlv *, const void *, size_t);
+int tlv_next(struct tlv *, const void *, size_t);
Index: usr.sbin/lldp/Makefile
===================================================================
RCS file: usr.sbin/lldp/Makefile
diff -N usr.sbin/lldp/Makefile
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ usr.sbin/lldp/Makefile 29 Apr 2025 01:47:08 -0000
@@ -0,0 +1,14 @@
+
+OLLDPD_DIR= ${.CURDIR}/../lldpd
+
+.PATH: ${OLLDPD_DIR}
+
+PROG= lldp
+SRCS= lldp.c pdu.c
+MAN= lldp.8
+
+CFLAGS+= -I${.CURDIR} -I${OLLDPD_DIR}
+CFLAGS+= -Wall -Werror
+DEBUG= -g
+
+.include <bsd.prog.mk>
Index: usr.sbin/lldp/lldp.8
===================================================================
RCS file: usr.sbin/lldp/lldp.8
diff -N usr.sbin/lldp/lldp.8
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ usr.sbin/lldp/lldp.8 29 Apr 2025 01:47:08 -0000
@@ -0,0 +1,56 @@
+.\" $OpenBSD$
+.\"
+.\" Copyright (c) 2025 David Gwynne<dlg@openbsd.org>
+.\"
+.\" Permission to use, copy, modify, and distribute this software for any
+.\" purpose with or without fee is hereby granted, provided that the above
+.\" copyright notice and this permission notice appear in all copies.
+.\"
+.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+.\"
+.Dd $Mdocdate$
+.Dt LLDP 8
+.Os
+.Sh NAME
+.Nm lldp
+.Nd control the lldpd agent
+.Sh SYNOPSIS
+.Nm lldp
+.Op Fl v
+.Op Fl i Ar interface
+.Sh DESCRIPTION
+The
+.Nm
+program communicates with the
+.Xr lldpd 8
+Link Layer Discovery Protocol (LLDP)
+agent to fetch and display LLDP entries received by the agent.
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl i Ar interface
+Limit the LLDP entries to those received on the specified
+.Ar interface .
+.It Fl v
+Increase the verbosity of output.
+.El
+.Sh FILES
+.Bl -tag -width "/var/run/lldp.sock" -compact
+.It Pa /var/run/lldp.sock
+default
+.Xr lldpd 8
+control socket
+.El
+.Sh SEE ALSO
+.Xr lldpd 8
+.Sh HISTORY
+The
+.Nm
+program first appeared in
+.Ox 7.8 .
Index: usr.sbin/lldp/lldp.c
===================================================================
RCS file: usr.sbin/lldp/lldp.c
diff -N usr.sbin/lldp/lldp.c
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ usr.sbin/lldp/lldp.c 29 Apr 2025 01:47:08 -0000
@@ -0,0 +1,869 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2024 David Gwynne <dlg@openbsd.org>
+ *
+ * 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 <sys/socket.h>
+#include <sys/types.h>
+#include <sys/un.h>
+
+#include <net/if.h> /* IFNAMSIZ */
+#include <net/frame.h>
+#include <net/lldp.h>
+
+#include <netinet/in.h>
+#include <netinet/if_ether.h> /* ether_ntoa */
+
+#include <arpa/inet.h> /* inet_ntop */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <unistd.h>
+#include <vis.h>
+#include <assert.h>
+#include <ctype.h>
+#include <err.h>
+
+#include "pdu.h"
+#include "lldpctl.h"
+
+#ifndef nitems
+#define nitems(_a) (sizeof((_a)) / sizeof((_a)[0]))
+#endif
+
+#ifndef ISSET
+#define ISSET(_a, _b) ((_a) & (_b))
+#endif
+
+struct slice {
+ const void *sl_base;
+ size_t sl_len;
+};
+
+struct string {
+ char *str_base;
+ size_t str_len;
+};
+
+void hexdump(const void *, size_t);
+
+enum lldp_tlv_idx {
+ tlv_chassis_id,
+ tlv_port_id,
+ tlv_ttl,
+ tlv_port_descr,
+ tlv_system_name,
+ tlv_system_descr,
+ tlv_system_cap,
+ tlv_management_addr,
+
+ tlv_org,
+
+ tlv_count
+};
+
+struct lldp_tlv {
+ uint8_t type;
+ const char *name;
+
+ void (*toscratch)(const void *, size_t, int);
+};
+
+static void lldp_bytes_to_scratch(const void *, size_t, int);
+static void lldp_string_to_scratch(const void *, size_t, int);
+
+static void lldp_chassis_id_to_scratch(const void *, size_t, int);
+static void lldp_port_id_to_scratch(const void *, size_t, int);
+static void lldp_ttl_to_scratch(const void *, size_t, int);
+static void lldp_system_cap_to_scratch(const void *, size_t, int);
+static void lldp_system_descr_to_scratch(const void *, size_t, int);
+static void lldp_mgmt_addr_to_scratch(const void *, size_t, int);
+static void lldp_org_to_scratch(const void *, size_t, int);
+
+static const struct lldp_tlv lldp_tlv_map[] = {
+ [tlv_chassis_id] = {
+ .type = LLDP_TLV_CHASSIS_ID,
+ .name = "Chassis ID",
+ .toscratch = lldp_chassis_id_to_scratch,
+ },
+ [tlv_port_id] = {
+ .type = LLDP_TLV_PORT_ID,
+ .name = "Port ID",
+ .toscratch = lldp_port_id_to_scratch,
+ },
+ [tlv_ttl] = {
+ .type = LLDP_TLV_TTL,
+ .name = "Time-To-Live",
+ .toscratch = lldp_ttl_to_scratch,
+ },
+ [tlv_port_descr] = {
+ .type = LLDP_TLV_PORT_DESCR,
+ .name = "Port Description",
+ .toscratch = lldp_string_to_scratch,
+ },
+ [tlv_system_name] = {
+ .type = LLDP_TLV_SYSTEM_NAME,
+ .name = "System Name",
+ .toscratch = lldp_string_to_scratch,
+ },
+ [tlv_system_descr] = {
+ .type = LLDP_TLV_SYSTEM_DESCR,
+ .name = "System Description",
+ .toscratch = lldp_system_descr_to_scratch,
+ },
+ [tlv_system_cap] = {
+ .type = LLDP_TLV_SYSTEM_CAP,
+ .name = "System Capabilities",
+ .toscratch = lldp_system_cap_to_scratch,
+ },
+ [tlv_management_addr] = {
+ .type = LLDP_TLV_MANAGEMENT_ADDR,
+ .name = "Management Address",
+ .toscratch = lldp_mgmt_addr_to_scratch,
+ },
+
+ [tlv_org] = {
+ .type = LLDP_TLV_ORG,
+ .name = NULL,
+ .toscratch = lldp_org_to_scratch,
+ },
+};
+
+struct lldp_pdu {
+ struct slice slices[tlv_count];
+ struct string strings[tlv_count];
+};
+
+static void lldpctl_req_msaps(int, const char *);
+static void lldp_dump(const struct lldp_ctl_msg_msap *,
+ const void *, size_t);
+static void lldp_dump_verbose(const struct lldp_ctl_msg_msap *,
+ const void *, size_t, int);
+
+static char scratch_mem[8192];
+static FILE *scratch;
+
+static void scratch_reset(void);
+static size_t scratch_len(void);
+static size_t scratch_end(void);
+
+static void reltime_to_scratch(time_t);
+
+__dead static void
+usage(void)
+{
+ extern char *__progname;
+
+ fprintf(stderr, "usage: %s [-v] [-i ifname]\n", __progname);
+
+ exit(1);
+}
+
+int
+main(int argc, char *argv[])
+{
+ const char *ifname = NULL;
+ int verbose = 0;
+ struct sockaddr_un sun = {
+ .sun_family = AF_UNIX,
+ .sun_path = LLDP_CTL_PATH,
+ };
+ int ch;
+ int s;
+
+ scratch = fmemopen(scratch_mem, sizeof(scratch_mem), "w");
+ if (scratch == NULL)
+ err(1, "fmemopen scratch");
+
+ while ((ch = getopt(argc, argv, "i:v")) != -1) {
+ switch (ch) {
+ case 'i':
+ ifname = optarg;
+ break;
+ case 'v':
+ verbose++;
+ break;
+ default:
+ usage();
+ /* NOTREACHED */
+ }
+ }
+
+ s = socket(AF_UNIX, SOCK_SEQPACKET, 0);
+ if (s == -1)
+ err(1, "socket");
+
+ if (connect(s, (struct sockaddr *)&sun, sizeof(sun)) == -1)
+ err(1, "%s connect", sun.sun_path);
+
+ lldpctl_req_msaps(s, ifname);
+
+ if (!verbose) {
+ printf("%-8s %-24s %-24s %s\n", "IFACE",
+ "SYSTEM", "PORTID", "CHASSISID");
+ }
+
+ for (;;) {
+ unsigned int msgtype;
+ struct lldp_ctl_msg_msap msg_msap;
+ char buf[2048];
+ struct iovec iov[3] = {
+ { &msgtype, sizeof(msgtype) },
+ { &msg_msap, sizeof(msg_msap) },
+ { buf, sizeof(buf) },
+ };
+ struct msghdr msg = {
+ .msg_iov = iov,
+ .msg_iovlen = nitems(iov),
+ };
+ ssize_t rv;
+ size_t len;
+
+ rv = recvmsg(s, &msg, 0);
+ if (rv == -1)
+ err(1, "recv");
+ if (rv == 0)
+ break;
+ len = rv;
+
+ if (len < sizeof(msgtype)) {
+ warnx("too short for msgtype\n");
+ continue;
+ }
+ if (msgtype == LLDP_CTL_MSG_MSAP_END)
+ break;
+ if (msgtype != LLDP_CTL_MSG_MSAP) {
+ warnx("unexpected msgtype %u", msgtype);
+ continue;
+ }
+ len -= sizeof(msgtype);
+
+ if (len < sizeof(msg_msap)) {
+ warnx("too short for msg_msap\n");
+ continue;
+ }
+ len -= sizeof(msg_msap);
+#if 0
+ printf("%s %s\n", msg_msap.ifname,
+ ether_ntoa(&msg_msap.saddr));
+ hexdump(buf, len);
+#endif
+
+ if (!verbose)
+ lldp_dump(&msg_msap, buf, len);
+ else
+ lldp_dump_verbose(&msg_msap, buf, len, verbose);
+ }
+
+ return (0);
+}
+
+static void
+lldpctl_req_msaps(int fd, const char *ifname)
+{
+ unsigned int msgtype = LLDP_CTL_MSG_MSAP_REQ;
+ struct lldp_ctl_msg_msap_req msg_msap_req;
+ struct iovec iov[2] = {
+ { &msgtype, sizeof(msgtype) },
+ { &msg_msap_req, sizeof(msg_msap_req) },
+ };
+ struct msghdr msg = {
+ .msg_iov = iov,
+ .msg_iovlen = nitems(iov),
+ };
+ ssize_t rv;
+
+ memset(&msg_msap_req, 0, sizeof(msg_msap_req));
+
+ if (ifname != NULL) {
+ if (strlcpy(msg_msap_req.ifname, ifname,
+ sizeof(msg_msap_req.ifname)) >=
+ sizeof(msg_msap_req.ifname))
+ errx(1, "interface name too long");
+ }
+
+ rv = sendmsg(fd, &msg, 0);
+ if (rv == -1)
+ err(1, "send msap msg req");
+}
+
+static const struct lldp_tlv *
+lldp_tlv_lookup(unsigned int type)
+{
+ const struct lldp_tlv *ltlv;
+ size_t i;
+
+ for (i = 0; i < nitems(lldp_tlv_map); i++) {
+ ltlv = &lldp_tlv_map[i];
+ if (ltlv->type == type)
+ return (ltlv);
+ }
+
+ return (NULL);
+}
+
+static size_t
+lldp_tlv_to_idx(const struct lldp_tlv *ltlv)
+{
+ return (ltlv - lldp_tlv_map);
+}
+
+static void
+lldp_dump(const struct lldp_ctl_msg_msap *msg_msap,
+ const void *pdu, size_t len)
+{
+ struct lldp_pdu lpdu;
+ struct tlv tlv;
+ const struct lldp_tlv *ltlv;
+ struct slice *sl;
+ size_t i;
+
+ for (i = 0; i < tlv_count; i++) {
+ sl = &lpdu.slices[i];
+ sl->sl_base = NULL;
+ sl->sl_len = 0;
+ }
+
+ /* olldpd only gives us well formed PDUs */
+ tlv_first(&tlv, pdu, len);
+ do {
+ struct slice *sl;
+
+ ltlv = lldp_tlv_lookup(tlv.tlv_type);
+ if (ltlv == NULL)
+ continue;
+ i = lldp_tlv_to_idx(ltlv);
+
+ sl = &lpdu.slices[i];
+ sl->sl_base = tlv.tlv_payload;
+ sl->sl_len = tlv.tlv_len;
+ } while (tlv_next(&tlv, pdu, len));
+
+ printf("%-8s", msg_msap->ifname);
+
+ scratch_reset();
+ sl = &lpdu.slices[tlv_system_name];
+ if (sl->sl_base == NULL) {
+ fprintf(scratch, "-");
+ } else {
+ ltlv = &lldp_tlv_map[tlv_system_name];
+ ltlv->toscratch(sl->sl_base, sl->sl_len, 0);
+ }
+ scratch_end();
+ printf(" %-24s", scratch_mem);
+
+ scratch_reset();
+ sl = &lpdu.slices[tlv_port_id];
+ if (sl->sl_base == NULL) {
+ fprintf(scratch, "-");
+ } else {
+ ltlv = &lldp_tlv_map[tlv_port_id];
+ ltlv->toscratch(sl->sl_base, sl->sl_len, 0);
+ }
+ scratch_end();
+ printf(" %-24s", scratch_mem);
+
+ scratch_reset();
+ sl = &lpdu.slices[tlv_chassis_id];
+ if (sl->sl_base == NULL) {
+ fprintf(scratch, "-");
+ } else {
+ ltlv = &lldp_tlv_map[tlv_chassis_id];
+ ltlv->toscratch(sl->sl_base, sl->sl_len, 0);
+ }
+ scratch_end();
+ printf(" %s", scratch_mem);
+
+ printf("\n");
+}
+
+static void
+lldp_dump_verbose(const struct lldp_ctl_msg_msap *msg_msap,
+ const void *pdu, size_t len, int verbose)
+{
+ struct tlv tlv;
+ const struct lldp_tlv *ltlv;
+
+ printf("Local interface: %s, Source address: %s\n", msg_msap->ifname,
+ ether_ntoa((struct ether_addr *)&msg_msap->saddr));
+
+ if (verbose > 1) {
+ struct timespec now, diff;
+ if (clock_gettime(CLOCK_BOOTTIME, &now) == -1)
+ err(1, "clock_gettime(CLOCK_BOOTTIME)");
+
+ scratch_reset();
+ timespecsub(&now, &msg_msap->created, &diff);
+ reltime_to_scratch(diff.tv_sec);
+ scratch_end();
+ printf("LLDP entry created: %s\n", scratch_mem);
+
+ scratch_reset();
+ timespecsub(&now, &msg_msap->updated, &diff);
+ reltime_to_scratch(diff.tv_sec);
+ scratch_end();
+ printf("LLDP entry updated: %s\n", scratch_mem);
+
+ printf("LLDP packets: %llu\n", msg_msap->packets);
+ printf("LLDP updates: %llu\n", msg_msap->updates);
+ }
+
+ /* olldpd only gives us well formed PDUs */
+ tlv_first(&tlv, pdu, len);
+ do {
+ void (*toscratch)(const void *, size_t, int) =
+ lldp_bytes_to_scratch;
+
+ ltlv = lldp_tlv_lookup(tlv.tlv_type);
+ if (ltlv == NULL) {
+ printf("tlv type %u: ", tlv.tlv_type);
+ } else {
+ if (ltlv->name)
+ printf("%s: ", ltlv->name);
+ toscratch = ltlv->toscratch;
+ }
+
+ scratch_reset();
+ toscratch(tlv.tlv_payload, tlv.tlv_len, 1);
+ scratch_end();
+ printf("%s\n", scratch_mem);
+
+ } while (tlv_next(&tlv, pdu, len));
+
+ printf("---\n");
+}
+
+static void
+scratch_reset(void)
+{
+ if (fseeko(scratch, 0, SEEK_SET) == -1)
+ err(1, "scratch reset");
+}
+
+static size_t
+scratch_len(void)
+{
+ off_t len = ftello(scratch);
+ assert(len < sizeof(scratch_mem));
+ return (len);
+}
+
+static size_t
+scratch_end(void)
+{
+ off_t len = scratch_len();
+ scratch_mem[len] = '\0';
+ return (len);
+}
+
+struct interval {
+ const char p;
+ time_t s;
+};
+
+static const struct interval intervals[] = {
+ { 'w', 60 * 60 * 24 * 7 },
+ { 'd', 60 * 60 * 24 },
+ { 'h', 60 * 60 },
+ { 'm', 60 },
+ { 's', 1 },
+};
+
+static void
+reltime_to_scratch(time_t sec)
+{
+ size_t i;
+
+ for (i = 0; i < nitems(intervals); i++) {
+ const struct interval *ival = &intervals[i];
+
+ if (sec >= ival->s) {
+ time_t d = sec / ival->s;
+ fprintf(scratch, "%lld%c", d, ival->p);
+ sec -= d * ival->s;
+ }
+ }
+}
+
+static void
+lldp_bytes_to_scratch(const void *base, size_t len, int flags)
+{
+ const uint8_t *buf = base;
+ size_t i;
+
+ for (i = 0; i < len; i++)
+ fprintf(scratch, "%02x", buf[i]);
+}
+
+static void
+lldp_string_to_scratch(const void *base, size_t len, int flags)
+{
+ const uint8_t *buf = base;
+ size_t i;
+ char dst[8];
+
+ for (i = 0; i < len; i++) {
+ vis(dst, buf[i], VIS_NL, 0);
+ fprintf(scratch, "%s", dst);
+ }
+}
+
+static void
+lldp_system_descr_to_scratch(const void *base, size_t len, int flags)
+{
+ const uint8_t *buf = base;
+ size_t i;
+ char dst[8];
+
+ for (i = 0; i < len; i++) {
+ int ch = buf[i];
+ switch (ch) {
+ case '\r':
+ break;
+ case '\n':
+ fprintf(scratch, "\n\t");
+ break;
+ default:
+ vis(dst, ch, 0, 0);
+ fprintf(scratch, "%s", dst);
+ break;
+ }
+ }
+}
+
+static void
+lldp_macaddr_to_scratch(const void *base, size_t len, int flags)
+{
+ struct ether_addr *ea;
+
+ if (len < sizeof(*ea)) {
+ lldp_bytes_to_scratch(base, len, flags);
+ return;
+ }
+
+ ea = (struct ether_addr *)base;
+ fprintf(scratch, "%s", ether_ntoa(ea));
+}
+
+static void
+lldp_chassis_id_to_scratch(const void *base, size_t len, int flags)
+{
+ const uint8_t *buf = base;
+ uint8_t subtype;
+
+ assert(len >= 2);
+
+ subtype = buf[0];
+
+ buf++;
+ len--;
+
+ switch (subtype) {
+ case LLDP_CHASSIS_ID_MACADDR:
+ lldp_macaddr_to_scratch(buf, len, flags);
+ break;
+ case LLDP_CHASSIS_ID_CHASSIS:
+ case LLDP_CHASSIS_ID_IFALIAS:
+ case LLDP_CHASSIS_ID_PORT:
+ case LLDP_CHASSIS_ID_ADDR:
+ case LLDP_CHASSIS_ID_IFNAME:
+ case LLDP_CHASSIS_ID_LOCAL:
+ lldp_string_to_scratch(buf, len, flags);
+ break;
+ default:
+ fprintf(scratch, "reserved (subtype %u) ", subtype);
+ lldp_bytes_to_scratch(buf, len, flags);
+ break;
+ }
+}
+
+static void
+lldp_port_id_to_scratch(const void *base, size_t len, int flags)
+{
+ const uint8_t *buf = base;
+ uint8_t subtype;
+
+ assert(len >= 2);
+
+ subtype = buf[0];
+
+ buf++;
+ len--;
+
+ switch (subtype) {
+ case LLDP_PORT_ID_MACADDR:
+ lldp_macaddr_to_scratch(base, len, flags);
+ break;
+ case LLDP_PORT_ID_IFALIAS:
+ case LLDP_PORT_ID_PORT:
+ case LLDP_PORT_ID_ADDR:
+ case LLDP_PORT_ID_IFNAME:
+ case LLDP_PORT_ID_AGENTCID:
+ case LLDP_PORT_ID_LOCAL:
+ lldp_string_to_scratch(buf, len, flags);
+ break;
+ default:
+ fprintf(scratch, "reserved (subtype %u) ", subtype);
+ lldp_bytes_to_scratch(buf, len, flags);
+ break;
+ }
+}
+
+static void
+lldp_ttl_to_scratch(const void *base, size_t len, int flags)
+{
+ uint16_t ttl;
+
+ assert(len >= sizeof(ttl));
+ ttl = pdu_u16(base);
+
+ reltime_to_scratch(ttl);
+}
+
+struct lldp_system_cap {
+ uint16_t bit;
+ const char *name;
+};
+
+static const struct lldp_system_cap lldp_system_caps[] = {
+ { LLDP_SYSTEM_CAP_OTHER, "Other" },
+ { LLDP_SYSTEM_CAP_REPEATER, "Repeater" },
+ { LLDP_SYSTEM_CAP_BRIDGE, "Bridge" },
+ { LLDP_SYSTEM_CAP_WLAN, "WLAN" },
+ { LLDP_SYSTEM_CAP_ROUTER, "Router" },
+ { LLDP_SYSTEM_CAP_TELEPHONE, "Telephone" },
+ { LLDP_SYSTEM_CAP_DOCSIS, "DOCSIS" },
+ { LLDP_SYSTEM_CAP_STATION, "Station" },
+};
+
+static const char *
+lldp_system_cap_name(uint16_t bit)
+{
+ size_t i;
+
+ for (i = 0; i < nitems(lldp_system_caps); i++) {
+ const struct lldp_system_cap *e = &lldp_system_caps[i];
+ if (e->bit == bit)
+ return (e->name);
+ }
+
+ return (NULL);
+}
+
+static void
+lldp_system_cap_to_scratch(const void *base, size_t len, int flags)
+{
+ const uint8_t *buf;
+ struct lldp_system_cap {
+ uint16_t available;
+ uint16_t enabled;
+ } cap;
+ const char *sep = "";
+ unsigned int i;
+
+ if (len < sizeof(cap) ){
+ fprintf(scratch, "[|system cap]");
+ return;
+ }
+
+ buf = base;
+
+ cap.available = pdu_u16(buf +
+ offsetof(struct lldp_system_cap, available));
+ cap.enabled = pdu_u16(buf +
+ offsetof(struct lldp_system_cap, enabled));
+
+ for (i = 0; i < NBBY * sizeof(cap.available); i++) {
+ const char *name;
+ uint16_t bit = (1 << i);
+
+ if (!ISSET(cap.available, bit))
+ continue;
+
+ fprintf(scratch, "%s", sep);
+ name = lldp_system_cap_name(bit);
+ if (name == NULL)
+ fprintf(scratch, "Bit%u", i + 1);
+ else
+ fprintf(scratch, "%s", name);
+
+ fprintf(scratch, ": %s",
+ ISSET(cap.enabled, bit) ? "enabled" : "disabled");
+
+ sep = ", ";
+ }
+}
+
+static void
+lldp_mgmt_addr_to_scratch(const void *base, size_t len, int flags)
+{
+ const uint8_t *buf = base;
+ uint8_t aftype;
+ size_t alen;
+ const uint8_t *abuf;
+ char ipbuf[64];
+ uint32_t ifnum;
+
+ if (len < 1) {
+ fprintf(scratch, "too short (%zu bytes)", len);
+ return;
+ }
+ alen = buf[0];
+ len--;
+ buf++;
+
+ if (len < alen) {
+ fprintf(scratch, "address len %zu is longer than tlv", alen);
+ return;
+ }
+ abuf = buf;
+ len -= alen;
+ buf += alen;
+
+ if (alen < 1) {
+ fprintf(scratch, "address len %zu is too short", alen);
+ return;
+ }
+ aftype = abuf[0];
+ alen--;
+ abuf++;
+
+ switch (aftype) {
+ case 1:
+ if (alen != 4) {
+ fprintf(scratch, "IPv4? ");
+ goto abytes;
+ }
+ inet_ntop(AF_INET, abuf, ipbuf, sizeof(ipbuf));
+ fprintf(scratch, "%s", ipbuf);
+ break;
+ case 2:
+ if (alen != 16) {
+ fprintf(scratch, "IPv7? ");
+ goto abytes;
+ }
+ inet_ntop(AF_INET6, abuf, ipbuf, sizeof(ipbuf));
+ fprintf(scratch, "%s", ipbuf);
+ break;
+
+ case 6:
+ if (alen != 6) {
+ fprintf(scratch, "802? ");
+ goto abytes;
+ }
+ fprintf(scratch, "%s", ether_ntoa((struct ether_addr *)abuf));
+ break;
+
+ default:
+ fprintf(scratch, "af %u ", aftype);
+abytes:
+ lldp_bytes_to_scratch(abuf, alen, 0);
+ break;
+ }
+
+ if (len < 5) {
+ fprintf(scratch, ", [|interface number]");
+ return;
+ }
+ ifnum = pdu_u32(buf + 1);
+
+ switch (buf[0]) {
+ case 0:
+ break;
+ case 1:
+ fprintf(scratch, ", ifIndex %u", ifnum);
+ break;
+ case 2:
+ fprintf(scratch, ", port %u", ifnum);
+ break;
+ default:
+ fprintf(scratch, ", if type %u num %u", buf[0], ifnum);
+ break;
+ }
+
+ len -= 5;
+ buf += 5;
+
+ if (len < 1) {
+ fprintf(scratch, ", [|object identifier]");
+ return;
+ }
+ alen = buf[0];
+ len--;
+ buf++;
+
+ if (alen == 0)
+ return;
+ if (len < alen) {
+ fprintf(scratch, ", oid %zu is longer than tlv", alen);
+ return;
+ }
+ fprintf(scratch, ", oid ");
+ lldp_bytes_to_scratch(buf, len, 0);
+}
+
+static void
+lldp_org_to_scratch(const void *base, size_t len, int flags)
+{
+ const uint8_t *buf;
+
+ if (len < 4) {
+ fprintf(scratch, "[|org]");
+ return;
+ }
+
+ buf = base;
+ fprintf(scratch, "%02X-%02X-%02X subtype %u: ",
+ buf[0], buf[1], buf[2], buf[3]);
+
+ buf += 4;
+ len -= 4;
+
+ lldp_bytes_to_scratch(buf, len, flags);
+}
+
+static int
+printable(int ch)
+{
+ if (ch == '\0')
+ return ('_');
+ if (!isprint(ch))
+ return ('~');
+
+ return (ch);
+}
+
+void
+hexdump(const void *d, size_t datalen)
+{
+ const uint8_t *data = d;
+ size_t i, j = 0;
+
+ for (i = 0; i < datalen; i += j) {
+ printf("%4zu: ", i);
+ for (j = 0; j < 16 && i+j < datalen; j++)
+ printf("%02x ", data[i + j]);
+ while (j++ < 16)
+ printf(" ");
+ printf("|");
+ for (j = 0; j < 16 && i+j < datalen; j++)
+ putchar(printable(data[i + j]));
+ printf("|\n");
+ }
+}
Index: sys/conf/GENERIC
===================================================================
RCS file: /cvs/src/sys/conf/GENERIC,v
diff -u -p -r1.299 GENERIC
--- sys/conf/GENERIC 3 Oct 2024 04:39:09 -0000 1.299
+++ sys/conf/GENERIC 29 Apr 2025 01:47:08 -0000
@@ -81,6 +81,7 @@ pseudo-device endrun 1 # EndRun line dis
pseudo-device vnd 4 # vnode disk devices
pseudo-device ksyms 1 # kernel symbols device
pseudo-device kstat # kernel statistics device
+pseudo-device llt # low-level tracing device
# clonable devices
pseudo-device bpfilter # packet filter
@@ -114,5 +115,7 @@ pseudo-device wg # WireGuard
pseudo-device bio 1 # ioctl multiplexing device
pseudo-device fuse # fuse device
+
+pseudo-device af_frame # (Ethernet) frame sockets
option BOOT_CONFIG # add support for boot -c
LLDP daemon and display tool