Download raw body.
LLDP daemon and display tool
this adds a small daemon and command line tool for receiving and
displaying LLDP messages from neighbors connected to Ethernet
interfaces.
the daemon is called olldpd(8) to avoid colliding with the existing
lldpd from ports. the command line tool is lldp(8).
it uses the AF_FRAME sockets that were recently added rather than BPF.
this means it retains fewer privileges while it's running because it
doesn't have to open and configure BPF devices when new interfaces
appear in the system. avoiding BPF means it has basically 0 impact on
the kernel packet path because AF_FRAME is handled as a last resort for
packets rather than up front for every packet on an interface.
it's good enough now that i can leave the daemon running, and it handles
interfaces coming and going, and lldp neighbours coming and going. the
command line utility defaults to a brief output, but can produce verbose
output that handles most of the basic set of lldp information from the
specification.
now that i've done the easy bits (AF_FRAME and the packet reception)
i think it's time to get it into the tree so people can help with
the hard stuff (pretty printing strings in c).
things that i'd like to get working in the future are:
- parse organisationally specific TLVs from IEEE specs
- allow configuration to disable lldp reception on specified interfaces
- query the daemon for stats/counters
things that i do not want to get working in the future:
- support for any other protocols like cdp/edp/etc
long term it should be reasonable to implement tx so openbsd can send
lldp packets to neighbor devices.
this is what the command line tool looks like:
dlg@r5s ~$ lldp
IFACE SYSTEM PORTID CHASSISID
rge0 rb450gx4 ether3 dc:2c:6e:87:a0:7c
dlg@r5s ~$ lldp -v
Local interface: rge0, Source address: dc:2c:6e:87:a0:7e
Chassis ID: dc:2c:6e:87:a0:7c
Port ID: ether3
Time-To-Live: 2m1s
System Name: rb450gx4
System Description: MikroTik RouterOS 7.17 (stable) 2025-01-16 08:19:28 RB450Gx4
Management Address: 192.0.2.8, port 3
Management Address: fe80::de2c:6eff:fe87:a07c, port 3
System Capabilities: Bridge: enabled, Router: enabled
Port Description: bridge/ether3
---
dlg@r6415 ~$ lldp
IFACE SYSTEM PORTID CHASSISID
bnxt0 eait-42-dc3-a2-2 ethernet1/1/9:1 8c:04:ba:c9:4a:c0
mcx0 eait-42-dc3-a2-2 ethernet1/1/8:1 8c:04:ba:c9:4a:c0
bnxt1 eait-42-dc3-a2-1 ethernet1/1/9:1 8c:04:ba:cf:6b:c0
mcx1 eait-42-dc3-a2-1 ethernet1/1/8:1 8c:04:ba:cf:6b:c0
dlg@r6415 ~$ lldp -i mcx0
IFACE SYSTEM PORTID CHASSISID
mcx0 eait-42-dc3-a2-2 ethernet1/1/8:1 8c:04:ba:c9:4a:c0
dlg@r6415 ~$ lldp -i mcx0 -v
Local interface: mcx0, Source address: 8c:04:ba:c9:4a:c8
Chassis ID: 8c:04:ba:c9:4a:c0
Port ID: ethernet1/1/8:1
Time-To-Live: 2m
Port Description: Po38:r6415
System Name: eait-42-dc3-a2-2
System Description: Dell SmartFabric OS10 Enterprise.
Copyright (c) 1999-2023 by Dell Inc. All Rights Reserved.
System Description: OS10 Enterprise.
OS Version: 10.5.6.0.
System Type: S5248F-ON
System Capabilities: Repeater: enabled, Bridge: enabled, Router: enabled
Management Address: 8c:04:ba:c9:4a:c0, ifIndex 0
00-80-C2 subtype 1: 0000
00-80-C2 subtype 7: 0300000026
00-12-0F subtype 1: 0100020047
00-12-0F subtype 4: 2400
F8-B1-56 subtype 21: 20
F8-B1-56 subtype 22: 20
F8-B1-56 subtype 23: 20
F8-B1-56 subtype 24: 20
---
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 24 Apr 2025 03:07:00 -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/olldpd/Makefile
===================================================================
RCS file: usr.sbin/olldpd/Makefile
diff -N usr.sbin/olldpd/Makefile
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ usr.sbin/olldpd/Makefile 24 Apr 2025 03:07:00 -0000
@@ -0,0 +1,12 @@
+PROG= olldpd
+SRCS= olldpd.c pdu.c
+SRCS+= log.c
+MAN= olldpd.8
+
+CFLAGS+= -Wall -Werror
+DEBUG= -g
+
+LDADD+= -levent
+DPADD+= ${LIBEVENT}
+
+.include <bsd.prog.mk>
Index: usr.sbin/olldpd/lldpctl.h
===================================================================
RCS file: usr.sbin/olldpd/lldpctl.h
diff -N usr.sbin/olldpd/lldpctl.h
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ usr.sbin/olldpd/lldpctl.h 24 Apr 2025 03:07:00 -0000
@@ -0,0 +1,76 @@
+/* $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.
+ */
+
+enum olldp_ctl_msg {
+ OLLDPD_CTL_MSG_PING,
+ OLLDPD_CTL_MSG_PONG,
+
+ OLLDPD_CTL_MSG_MSAP_REQ, /* ctl -> daemon */
+ OLLDPD_CTL_MSG_MSAP, /* daemon -> ctl */
+ OLLDPD_CTL_MSG_MSAP_END, /* daemon -> ctl */
+
+ OLLDPD_CTL_MSG_ACTR_REQ, /* ctl -> daemon */
+ OLLDPD_CTL_MSG_ACTR, /* daemon -> ctl */
+ OLLDPD_CTL_MSG_ACTR_END, /* daemon -> ctl */
+};
+
+enum agent_counter {
+ statsAgeoutsTotal,
+ statsFramesDiscardedTotal,
+ statsFramesInErrorsTotal,
+ statsFramesInTotal,
+ statsFramesOutTotal,
+ statsTLVsDiscardedTotal,
+ statsTLVsUnrecognisedTotal,
+ lldpduLengthErrors,
+
+ AGENT_COUNTER_NCOUNTERS
+};
+
+struct olldpd_ctl_msg_msap_req {
+ char ifname[IFNAMSIZ];
+ unsigned int gen;
+};
+
+struct olldpd_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 olldpd_ctl_msg_actrs_req {
+ unsigned int which;
+#define OLLDPD_ACTRS_ALL 0
+#define OLLDPD_ACTRS_DAEMON 1
+#define OLLDPD_ACTRS_IFACE 2
+ char ifname[IFNAMSIZ];
+};
+
+struct olldpd_ctl_msg_actrs {
+ char ifname[IFNAMSIZ];
+ uint64_t ctrs[AGENT_COUNTER_NCOUNTERS];
+};
Index: usr.sbin/olldpd/log.c
===================================================================
RCS file: usr.sbin/olldpd/log.c
diff -N usr.sbin/olldpd/log.c
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ usr.sbin/olldpd/log.c 24 Apr 2025 03:07:00 -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/olldpd/log.h
===================================================================
RCS file: usr.sbin/olldpd/log.h
diff -N usr.sbin/olldpd/log.h
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ usr.sbin/olldpd/log.h 24 Apr 2025 03:07:00 -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/olldpd/olldpd.8
===================================================================
RCS file: usr.sbin/olldpd/olldpd.8
diff -N usr.sbin/olldpd/olldpd.8
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ usr.sbin/olldpd/olldpd.8 24 Apr 2025 03:07:00 -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 olldpd
+.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/olldpd.sock" -compact
+.It Pa /var/run/olldpd.sock
+Default
+.Nm
+control socket.
+.El
+.Sh SEE ALSO
+.\" Xr frame 4 ,
+.Xr lldpd 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/olldpd/olldpd.c
===================================================================
RCS file: usr.sbin/olldpd/olldpd.c
diff -N usr.sbin/olldpd/olldpd.c
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ usr.sbin/olldpd/olldpd.c 24 Apr 2025 03:07:00 -0000
@@ -0,0 +1,1327 @@
+/* $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 OLLDPD_USER "_olldpd"
+#define OLLDPD_CTL_PATH "/var/run/olldpd.sock"
+
+#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 olldpd *if_olldpd;
+ 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 olldpd_ctl {
+ struct olldpd *ctl_olldpd;
+
+ struct event ctl_rd_ev;
+ struct event ctl_wr_ev;
+
+ uid_t ctl_peer_uid;
+ gid_t ctl_peer_gid;
+
+ void (*ctl_handler)(struct olldpd *, struct olldpd_ctl *, int fd);
+ void *ctl_ctx;
+};
+
+struct olldpd {
+ 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 olldpd *);
+static void rtsock_recv(int, short, void *);
+static void ensock_open(struct olldpd *);
+static void ensock_recv(int, short, void *);
+static void ctlsock_open(struct olldpd *);
+static void ctlsock_accept(int, short, void *);
+
+static int getall(struct olldpd *);
+
+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 olldpd _olldpd = {
+ .ctl_path = OLLDPD_CTL_PATH,
+ .ifaces = RBT_INITIALIZER(_olldpd.ifaces),
+ .msaps = TAILQ_HEAD_INITIALIZER(_olldpd.msaps),
+ };
+ struct olldpd *olldpd = &_olldpd; /* 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':
+ olldpd->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(OLLDPD_USER);
+ if (pw == NULL)
+ errx(1, "no %s user", OLLDPD_USER);
+
+ if (!debug) {
+ logger_syslog(__progname, LOG_DAEMON);
+ devnull = open(_PATH_DEVNULL, O_RDWR);
+ if (devnull == -1)
+ err(1, "%s", _PATH_DEVNULL);
+ }
+
+ rtsock_open(olldpd);
+ ensock_open(olldpd);
+ ctlsock_open(olldpd);
+
+ 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();
+
+ olldpd->s = socket(AF_INET, SOCK_DGRAM, 0);
+ if (olldpd->s == -1)
+ err(1, "inet sock");
+
+ if (getall(olldpd) == -1)
+ warn("getall");
+
+ if (!debug && rdaemon(devnull) == -1)
+ err(1, "unable to daemonize");
+
+ event_init();
+
+ event_set(&olldpd->rt_ev, EVENT_FD(&olldpd->rt_ev),
+ EV_READ|EV_PERSIST, rtsock_recv, olldpd);
+ event_set(&olldpd->en_ev, EVENT_FD(&olldpd->en_ev),
+ EV_READ|EV_PERSIST, ensock_recv, olldpd);
+ event_set(&olldpd->ctl_ev, EVENT_FD(&olldpd->ctl_ev),
+ EV_READ|EV_PERSIST, ctlsock_accept, olldpd);
+
+ event_add(&olldpd->rt_ev, NULL);
+ event_add(&olldpd->en_ev, NULL);
+ event_add(&olldpd->ctl_ev, NULL);
+
+ event_dispatch();
+
+ return (0);
+}
+
+static void
+agent_counter_inc(struct olldpd *olldpd, struct iface *ifp,
+ enum agent_counter c)
+{
+ olldpd->agent_counters[c]++;
+ ifp->if_agent_counters[c]++;
+}
+
+static struct lldp_msap *
+lldp_msap_take(struct olldpd *olldpd, struct lldp_msap *msap)
+{
+ msap->msap_refs++;
+ return (msap);
+}
+
+static void
+lldp_msap_rele(struct olldpd *olldpd, struct lldp_msap *msap)
+{
+ if (--msap->msap_refs == 0) {
+ TAILQ_REMOVE(&olldpd->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_olldpd, msap);
+}
+
+static void
+lldp_msap_expire(int nil, short events, void *arg)
+{
+ struct lldp_msap *msap = arg;
+ struct iface *ifp = msap->msap_iface;
+ struct olldpd *olldpd = ifp->if_olldpd;
+
+ agent_counter_inc(olldpd, 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 olldpd *olldpd)
+{
+ 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(&olldpd->rt_ev, s, 0, NULL, NULL);
+}
+
+static inline struct iface *
+iface_insert(struct olldpd *olldpd, struct iface *ifp)
+{
+ return (RBT_INSERT(ifaces, &olldpd->ifaces, ifp));
+}
+
+static struct iface *
+iface_find(struct olldpd *olldpd, const char *ifname, int ifindex)
+{
+ struct iface_key key = { .if_index = ifindex };
+
+ return (RBT_FIND(ifaces, &olldpd->ifaces, (struct iface *)&key));
+}
+
+static inline void
+iface_remove(struct olldpd *olldpd, struct iface *ifp)
+{
+ RBT_REMOVE(ifaces, &olldpd->ifaces, ifp);
+}
+
+static void
+rtsock_if_attach(struct olldpd *olldpd, 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(olldpd->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_olldpd = olldpd;
+ TAILQ_INIT(&ifp->if_msaps);
+
+ if (iface_insert(olldpd, 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(&olldpd->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 olldpd *olldpd, const struct if_announcemsghdr *ifan)
+{
+ struct iface *ifp;
+ struct lldp_msap *msap, *nmsap;
+ struct frame_mreq fmr;
+
+ ifp = iface_find(olldpd, 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(&olldpd->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(olldpd, ifp);
+
+ TAILQ_FOREACH_SAFE(msap, &ifp->if_msaps, msap_entry, nmsap)
+ lldp_msap_remove(ifp, msap);
+
+ free(ifp);
+}
+
+static void
+rtsock_ifannounce(struct olldpd *olldpd,
+ 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(olldpd, ifan);
+ break;
+ case IFAN_DEPARTURE:
+ rtsock_if_detach(olldpd, 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 olldpd *olldpd = 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(olldpd, rtm, rv);
+ break;
+ default:
+ return;
+ }
+}
+
+static void
+ensock_open(struct olldpd *olldpd)
+{
+ 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(&olldpd->en_ev, s, 0, NULL, NULL);
+}
+
+static void
+ensock_recv(int s, short events, void *arg)
+{
+ struct olldpd *olldpd = 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, &olldpd->ifaces, (struct iface *)&key);
+ if (ifp == NULL) {
+ /* count */
+ return;
+ }
+
+ /* XXX check if RX is enabled */
+ agent_counter_inc(olldpd, 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((struct ether_addr *)sfrm.sfrm_addr));
+ 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((struct ether_addr *)sfrm.sfrm_addr));
+ 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((struct ether_addr *)sfrm.sfrm_addr),
+ 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((struct ether_addr *)sfrm.sfrm_addr));
+ 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((struct ether_addr *)sfrm.sfrm_addr));
+ 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((struct ether_addr *)sfrm.sfrm_addr),
+ 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((struct ether_addr *)sfrm.sfrm_addr));
+ 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((struct ether_addr *)sfrm.sfrm_addr));
+ goto discard;
+ }
+ if (tlv.tlv_len < 2) {
+ ldebug("%s: pdu from %s: "
+ "TTL TLV length %u is too short",
+ sfrm.sfrm_ifname,
+ ether_ntoa((struct ether_addr *)sfrm.sfrm_addr),
+ 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(olldpd, 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(&olldpd->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(olldpd, msap);
+
+ ldebug("%s: entry from %s deleted", ifp->if_key.if_name,
+ ether_ntoa((struct ether_addr *)sfrm.sfrm_addr));
+ 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(&olldpd->msaps, msap,
+ msap_aentry);
+ TAILQ_REMOVE(&ifp->if_msaps, msap,
+ msap_entry);
+ free(msap);
+ }
+ agent_counter_inc(olldpd, 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(olldpd, ifp, statsFramesDiscardedTotal);
+ agent_counter_inc(olldpd, ifp, statsFramesInErrorsTotal);
+}
+
+static void
+ctlsock_open(struct olldpd *olldpd)
+{
+ struct sockaddr_un sun = {
+ .sun_family = AF_UNIX,
+ };
+ const char *path = olldpd->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(&olldpd->ctl_ev, s, 0, NULL, NULL);
+}
+
+static void
+ctl_close(struct olldpd *olldpd, struct olldpd_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 olldpd *, struct olldpd_ctl *,
+ const void *, size_t);
+static ssize_t ctl_msap_req(struct olldpd *, struct olldpd_ctl *,
+ const void *, size_t);
+
+static void
+ctl_recv(int fd, short events, void *arg)
+{
+ struct olldpd_ctl *ctl = arg;
+ struct olldpd *olldpd = ctl->ctl_olldpd;
+ 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(olldpd, ctl);
+ return;
+ }
+
+ if (ctl->ctl_handler != NULL) {
+ ldebug("ctl recv while not idle");
+ ctl_close(olldpd, ctl);
+ return;
+ }
+
+ len = rv;
+ ldebug("%s: msgtype %u, %zu bytes", __func__, msgtype, len);
+ if (len < sizeof(msgtype)) {
+ /* short message */
+ ctl_close(olldpd, ctl);
+ return;
+ }
+ len -= sizeof(msgtype);
+
+ switch (msgtype) {
+ case OLLDPD_CTL_MSG_PING:
+ rv = ctl_ping(olldpd, ctl, buf, len);
+ break;
+ case OLLDPD_CTL_MSG_MSAP_REQ:
+ rv = ctl_msap_req(olldpd, ctl, buf, len);
+ break;
+ default:
+ lwarnx("%s: unhandled message %u", __func__, msgtype);
+ rv = -1;
+ return;
+ }
+
+ if (rv == -1) {
+ ctl_close(olldpd, ctl);
+ return;
+ }
+
+ (*ctl->ctl_handler)(olldpd, ctl, fd);
+}
+
+static void
+ctl_done(struct olldpd *olldpd, struct olldpd_ctl *ctl)
+{
+ ctl->ctl_handler = NULL;
+ ctl->ctl_ctx = NULL;
+}
+
+static void
+ctl_pong(struct olldpd *olldpd, struct olldpd_ctl *ctl, int fd)
+{
+ unsigned int msgtype = OLLDPD_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(olldpd, ctl);
+ return;
+ }
+
+ free(piov->iov_base);
+ free(piov);
+
+ ctl_done(olldpd, ctl);
+}
+
+static ssize_t
+ctl_ping(struct olldpd *olldpd, struct olldpd_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 olldpd_ctl *ctl = arg;
+ struct olldpd *olldpd = ctl->ctl_olldpd;
+
+ ctl->ctl_handler(olldpd, ctl, fd);
+}
+
+struct ctl_msap_ctx {
+ char ifname[IFNAMSIZ];
+ struct lldp_msap *msap;
+};
+
+static void ctl_msap(struct olldpd *, struct olldpd_ctl *, int);
+static ssize_t ctl_msap_req_next(struct olldpd *, struct olldpd_ctl *,
+ struct lldp_msap *);
+static void ctl_msap_req_end(struct olldpd *, struct olldpd_ctl *,
+ int);
+
+static ssize_t
+ctl_msap_req(struct olldpd *olldpd, struct olldpd_ctl *ctl,
+ const void *buf, size_t len)
+{
+ const struct olldpd_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(olldpd, ctl, TAILQ_FIRST(&olldpd->msaps)));
+}
+
+static void
+ctl_msap(struct olldpd *olldpd, struct olldpd_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 = OLLDPD_CTL_MSG_MSAP;
+ struct olldpd_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(olldpd, msap);
+ free(ctx);
+ ctl_close(olldpd, ctl);
+ return;
+ }
+
+ rv = ctl_msap_req_next(olldpd, ctl, TAILQ_NEXT(msap, msap_aentry));
+ lldp_msap_rele(olldpd, msap);
+
+ if (rv == -1) {
+ free(ctx);
+ ctl_close(olldpd, ctl);
+ return;
+ }
+
+ event_add(&ctl->ctl_wr_ev, NULL);
+}
+
+static ssize_t
+ctl_msap_req_next(struct olldpd *olldpd, struct olldpd_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(olldpd, msap);
+ return (0);
+}
+
+static void
+ctl_msap_req_end(struct olldpd *olldpd, struct olldpd_ctl *ctl, int fd)
+{
+ struct ctl_msap_ctx *ctx = ctl->ctl_ctx;
+
+ unsigned int msgtype = OLLDPD_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(olldpd, ctl);
+ return;
+ }
+
+ free(ctx);
+ ctl_done(olldpd, ctl);
+}
+
+static void
+ctlsock_accept(int s, short events, void *arg)
+{
+ struct olldpd *olldpd = arg;
+ struct olldpd_ctl *ctl;
+ int fd;
+
+ fd = accept4(s, NULL, NULL, SOCK_NONBLOCK);
+ if (fd == -1) {
+ lwarn("control socket %s accept", olldpd->ctl_path);
+ return;
+ }
+
+ ctl = malloc(sizeof(*ctl));
+ if (ctl == NULL) {
+ lwarn("ctl alloc");
+ close(fd);
+ return;
+ }
+ ctl->ctl_olldpd = olldpd;
+ 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 olldpd *olldpd)
+{
+ 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_olldpd = olldpd;
+ TAILQ_INIT(&ifp->if_msaps);
+
+ if (RBT_INSERT(ifaces, &olldpd->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(&olldpd->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/olldpd/pdu.c
===================================================================
RCS file: usr.sbin/olldpd/pdu.c
diff -N usr.sbin/olldpd/pdu.c
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ usr.sbin/olldpd/pdu.c 24 Apr 2025 03:07:00 -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/olldpd/pdu.h
===================================================================
RCS file: usr.sbin/olldpd/pdu.h
diff -N usr.sbin/olldpd/pdu.h
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ usr.sbin/olldpd/pdu.h 24 Apr 2025 03:07:00 -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 24 Apr 2025 03:07:00 -0000
@@ -0,0 +1,14 @@
+
+OLLDPD_DIR= ${.CURDIR}/../olldpd
+
+.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 24 Apr 2025 03:07:00 -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 olldpd agent
+.Sh SYNOPSIS
+.Nm lldp
+.Op Fl v
+.Op Fl i Ar interface
+.Sh DESCRIPTION
+The
+.Nm
+program communicates with the
+.Xr olldpd 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/olldpd.sock" -compact
+.It Pa /var/run/olldpd.sock
+default
+.Xr olldpd 8
+control socket
+.El
+.Sh SEE ALSO
+.Xr olldpd 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 24 Apr 2025 03:07:00 -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 olldpd_ctl_msg_msap *,
+ const void *, size_t);
+static void lldp_dump_verbose(const struct olldpd_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 = "/var/run/olldpd.sock",
+ };
+ 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 olldpd_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 == OLLDPD_CTL_MSG_MSAP_END)
+ break;
+ if (msgtype != OLLDPD_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 = OLLDPD_CTL_MSG_MSAP_REQ;
+ struct olldpd_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 olldpd_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 olldpd_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 24 Apr 2025 03:19:49 -0000
@@ -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