#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/un.h>
#include <sys/stat.h>
#include <sys/queue.h>
#include <sys/tree.h>
#include <net/if.h>
#include <net/if_dl.h>
#include <net/route.h>
#include <net/frame.h>
#include <net/if_types.h>
#include <netinet/in.h>
#include <netinet/if_ether.h>
#include <net/ethertypes.h>
#include <net/lldp.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <err.h>
#include <errno.h>
#include <ifaddrs.h>
#include <pwd.h>
#include <paths.h>
#include <event.h>
#include "pdu.h"
#include "lldpctl.h"
#include <syslog.h>
#include "log.h"
#ifndef nitems
#define nitems(_a) ((sizeof((_a)) / sizeof((_a)[0])))
#endif
int rdaemon(int);
#define LLDPD_USER "_lldpd"
#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;
TAILQ_ENTRY(lldp_msap) msap_aentry;
unsigned int msap_refs;
void * *msap_pdu;
size_t msap_pdu_len;
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;
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 socket]\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;
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();
}
}
argc -= optind;
argv += optind;
if (argc != 0)
usage();
if (geteuid() != 0)
errx(1, "need root privileges");
closefrom(STDERR_FILENO + 1);
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);
if (chroot(pw->pw_dir) == -1)
err(1, "chroot %s", pw->pw_dir);
if (chdir("/") == -1)
err(1, "chdir %s", pw->pw_dir);
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");
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");
if (pledge("stdio unix", NULL) == -1)
err(1, "pledge");
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);
}
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, "AF_FRAME socket");
opt = 1;
if (setsockopt(s, IFT_ETHER, FRAME_RECVDSTADDR,
&opt, sizeof(opt)) == -1)
err(1, "AF_FRAME setsockopt enable recv dstaddr");
if (bind(s, (struct sockaddr *)&sfrm, sizeof(sfrm)) == -1)
err(1, "AF_FRAME 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) {
return;
}
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);
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) {
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) {
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");
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)) {
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;
}
ldebug("%s index %u", 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);
continue;
}
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);
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);
}