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 + * + * 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 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 +.\" +.\" 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 + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "pdu.h" +#include "lldpctl.h" + +#include +#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 + * + * 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 + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#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 + * + * 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 + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include + +#include "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 + * + * 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 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 +.\" +.\" 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 + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include + +#include /* IFNAMSIZ */ +#include +#include + +#include +#include /* ether_ntoa */ + +#include /* inet_ntop */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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