? usr.sbin/lldp/ktrace.out 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 17 Apr 2025 06:46:51 -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. + */ + +#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 */ + +#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 17 Apr 2025 06:46:51 -0000 @@ -0,0 +1,12 @@ +PROG= olldpd +SRCS= olldpd.c pdu.c +SRCS+= log.c +MAN= + +CFLAGS+= -Wall -Werror +DEBUG= -g + +LDADD+= -levent +DPADD+= ${LIBEVENT} + +.include 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 17 Apr 2025 06:46:51 -0000 @@ -0,0 +1,30 @@ +/* $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. + */ + +enum agent_counter { + statsAgeoutsTotal, + statsFramesDiscardedTotal, + statsFramesInErrorsTotal, + statsFramesInTotal, + statsFramesOutTotal, + statsTLVsDiscardedTotal, + statsTLVsUnrecognisedTotal, + lldpduLengthErrors, + + 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 17 Apr 2025 06:46:51 -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/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 17 Apr 2025 06:46:51 -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/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 17 Apr 2025 06:46:51 -0000 @@ -0,0 +1,1052 @@ +/* $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" + +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_faddr; + struct ether_addr msap_laddr; + struct timespec msap_creation; + + 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; + + struct lldp_msap *ctl_msap; /* cursor */ +}; + +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_faddr)); + + 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]; + + struct ether_addr *ea = NULL; + struct cmsghdr *cmsg; + union { + struct cmsghdr hdr; + uint8_t buf[CMSG_SPACE(sizeof(*ea))]; + } 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; + int ok; + unsigned int idlen; + unsigned int ttl; + struct timeval age; + + rv = recvmsg(s, &msg, 0); + if (rv == -1) { + lwarn("Ethernet recv"); + return; + } + + CMSG_FOREACH(cmsg, &msg) { + if (CMSG_MATCH(cmsg, + sizeof(*ea), IFT_ETHER, FRAME_RECVDSTADDR)) { + ea = (struct ether_addr *)CMSG_DATA(cmsg); + } + } + + ldebug("%s: pdu from %s: %zd bytes", sfrm.sfrm_ifname, + ether_ntoa((struct ether_addr *)sfrm.sfrm_addr), 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; + + 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: + lwarnx("TLV type %u repeated", tlv.tlv_type); + return; + } + } + + 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_creation) == -1) + lerr(1, "CLOCK_BOOTTIME"); + + 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; + memcpy(msap->msap_pdu, buf, len); + msap->msap_faddr = *(struct ether_addr *)sfrm.sfrm_addr; + + 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); + struct lldp_msap *msap = ctl->ctl_msap; + + if (msap != NULL) + lldp_msap_rele(olldpd, msap); + event_del(&ctl->ctl_rd_ev); + event_del(&ctl->ctl_wr_ev); + free(ctl); + close(fd); +} + +static void +ctl_recv(int fd, short events, void *arg) +{ + struct olldpd_ctl *ctl = arg; + struct olldpd *olldpd = ctl->ctl_olldpd; + uint8_t buf[1024]; + ssize_t rv; + + rv = recv(fd, buf, sizeof(buf), 0); + if (rv == -1) { + lwarn("ctl recv"); + return; + } + if (rv == 0) { + ctl_close(olldpd, ctl); + return; + } + + linfo("%s: %zd bytes", __func__, rv); +} + +static void +ctl_send(int fd, short events, void *arg) +{ + struct olldpd_ctl *ctl = arg; + struct olldpd *olldpd = ctl->ctl_olldpd; + struct lldp_msap *msap = ctl->ctl_msap; + struct iface *ifp; + struct lldp_msap *nmsap; + ssize_t rv; + + struct sockaddr_frame sfrm = { + .sfrm_family = AF_FRAME, + .sfrm_proto = htons(ETHERTYPE_LLDP), + }; + struct iovec iov[2] = { { &sfrm, sizeof(sfrm) } }; + struct msghdr msg = { + .msg_iov = iov, + .msg_iovlen = 2, + }; + + ifp = msap->msap_iface; + if (ifp != NULL) { + sfrm.sfrm_ifindex = ifp->if_key.if_index; + strlcpy(sfrm.sfrm_ifname, ifp->if_key.if_name, + sizeof(sfrm.sfrm_ifname)); + } + *(struct ether_addr *)sfrm.sfrm_addr = msap->msap_faddr; + + iov[1].iov_base = msap->msap_pdu; + iov[1].iov_len = msap->msap_pdu_len; + + rv = sendmsg(fd, &msg, 0); + if (rv == -1) { + lwarn("ctl send"); + return; + } + + nmsap = TAILQ_NEXT(msap, msap_aentry); + if (nmsap == NULL) { + ctl_close(olldpd, ctl); + return; + } + ctl->ctl_msap = lldp_msap_take(olldpd, nmsap); + lldp_msap_rele(olldpd, msap); + + event_add(&ctl->ctl_wr_ev, NULL); +} + +static void +ctlsock_accept(int s, short events, void *arg) +{ + struct olldpd *olldpd = arg; + struct olldpd_ctl *ctl; + struct lldp_msap *msap; + int fd; + + fd = accept4(s, NULL, NULL, SOCK_NONBLOCK); + if (fd == -1) { + lwarn("control socket %s accept", olldpd->ctl_path); + return; + } + + msap = TAILQ_FIRST(&olldpd->msaps); + if (msap == NULL) { + close(fd); + return; + } + + ctl = malloc(sizeof(*ctl)); + if (ctl == NULL) { + lwarn("ctl alloc"); + close(fd); + return; + } + ctl->ctl_olldpd = olldpd; + + 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); + + ctl->ctl_msap = lldp_msap_take(olldpd, msap); + ctl_send(fd, EV_WRITE, ctl); +} + +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 17 Apr 2025 06:46:51 -0000 @@ -0,0 +1,80 @@ +/* $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); +} + +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 17 Apr 2025 06:46:51 -0000 @@ -0,0 +1,31 @@ +/* $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 *); + +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 17 Apr 2025 06:46:51 -0000 @@ -0,0 +1,14 @@ + +OLLDPD_DIR= ${.CURDIR}/../olldpd + +.PATH: ${OLLDPD_DIR} + +PROG= lldp +SRCS= lldp.c pdu.c +MAN= + +CFLAGS+= -I${.CURDIR} -I${OLLDPD_DIR} +CFLAGS+= -Wall -Werror +DEBUG= -g + +.include 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 17 Apr 2025 06:46:51 -0000 @@ -0,0 +1,467 @@ +/* $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 +#include +#include +#include +#include +#include +#include + +#include "pdu.h" + +#ifndef nitems +#define nitems(_a) (sizeof((_a)) / sizeof((_a)[0])) +#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_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 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_string_to_scratch, + }, + [tlv_management_addr] = { + .type = LLDP_TLV_MANAGEMENT_ADDR, + .name = "Management Address", + .toscratch = lldp_bytes_to_scratch, + }, +}; + +struct lldp_pdu { + struct slice slices[tlv_count]; + struct string strings[tlv_count]; +}; + +static void lldp_dump(const struct sockaddr_frame *, + const void *, size_t); + +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); + +int +main(int argc, char *argv[]) +{ + struct sockaddr_un sun = { + .sun_family = AF_UNIX, + .sun_path = "/var/run/olldpd.sock", + }; + int s; + + scratch = fmemopen(scratch_mem, sizeof(scratch_mem), "w"); + if (scratch == NULL) + err(1, "fmemopen scratch"); + + 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); + + printf("%-8s %-24s %-24s %s\n", "IFACE", + "SYSTEM", "PORTID", "CHASSISID"); + + for (;;) { + struct sockaddr_frame sfrm; + char buf[2048]; + struct iovec iov[2] = { + { &sfrm, sizeof(sfrm) }, + { buf, sizeof(buf) }, + }; + struct msghdr msg = { + .msg_iov = iov, + .msg_iovlen = 2, + }; + ssize_t rv; + + rv = recvmsg(s, &msg, 0); + if (rv == -1) + err(1, "recv"); + if (rv == 0) + break; + + if (rv < sizeof(sfrm)) { + warnx("too short for sockaddr_frame\n"); + continue; + } + if (sfrm.sfrm_family != AF_FRAME) { + warnx("unexpected address family"); + continue; + } + + rv -= sizeof(sfrm); +#if 0 + printf("%s nei %s hi\n", sfrm.sfrm_ifname, + ether_ntoa((struct ether_addr *)sfrm.sfrm_addr)); + hexdump(buf, rv); +#endif + + lldp_dump(&sfrm, buf, rv); + } + + return (0); +} + +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 sockaddr_frame *sfrm, 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", sfrm->sfrm_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"); + +#if 0 + printf("%s\n", sfrm->sfrm_ifname); + for (i = 0; i < nitems(lldp_tlv_map); i++) { + ltlv = &lldp_tlv_map[i]; + + iov = &lpdu.tlvs[ltlv->idx]; + if (iov->iov_base == NULL) + continue; + + printf("%s:", ltlv->name); + ltlv->print(iov, -1, 0); + printf("\n"); + } +#endif +} + +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); +} + +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_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(base, 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); + + fprintf(scratch, "%us", ttl); +} + +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"); + } +}