diff options
Diffstat (limited to 'src/lib/poa/eth.c')
| -rw-r--r-- | src/lib/poa/eth.c | 1987 |
1 files changed, 1987 insertions, 0 deletions
diff --git a/src/lib/poa/eth.c b/src/lib/poa/eth.c new file mode 100644 index 00000000..5e959ef6 --- /dev/null +++ b/src/lib/poa/eth.c @@ -0,0 +1,1987 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2026 + * + * Points of attachment (PoA) - Ethernet transport + * + * Dimitri Staessens <dimitri@ouroboros.rocks> + * Sander Vrijders <sander@ouroboros.rocks> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * version 2.1 as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., http://www.fsf.org/about/contact/. + */ + +#if defined(__APPLE__) +#define _BSD_SOURCE +#define _DARWIN_C_SOURCE +#elif defined(__FreeBSD__) +#define __BSD_VISIBLE 1 +#elif defined(__linux__) || defined(__CYGWIN__) +#ifndef _DEFAULT_SOURCE +#define _DEFAULT_SOURCE +#endif +#else +#ifndef _POSIX_C_SOURCE +#define _POSIX_C_SOURCE 200809L +#endif +#endif + +#include "config.h" + +#define OUROBOROS_PREFIX "poa-eth" + +#include <ouroboros/endian.h> +#include <ouroboros/errno.h> +#include <ouroboros/hash.h> +#include <ouroboros/logs.h> +#include <ouroboros/pthread.h> +#include <ouroboros/sockets.h> +#include <ouroboros/time.h> + +#include "poa.h" + +#ifdef HAVE_RAW_SOCKETS +#include <net/if.h> +#include <netinet/in.h> +#include <linux/if_ether.h> +#include <linux/if_packet.h> +#include <linux/netlink.h> +#include <linux/gen_stats.h> +#include <linux/pkt_sched.h> +#include <linux/rtnetlink.h> +#include <linux/sockios.h> +#include <sys/ioctl.h> +#include <sys/socket.h> +#include <sys/uio.h> + +#include <ifaddrs.h> +#elif defined(HAVE_BPF) +#include <net/bpf.h> +#include <net/if.h> +#include <net/if_dl.h> +#include <sys/ioctl.h> +#include <sys/socket.h> +#include <sys/uio.h> + +#include <fcntl.h> +#include <ifaddrs.h> +#elif defined(HAVE_NETMAP) +#define NETMAP_WITH_LIBS +#include <net/netmap_user.h> +#include <net/if.h> +#include <sys/ioctl.h> +#include <sys/socket.h> + +#include <poll.h> +#ifndef __linux__ +#include <net/if_dl.h> +#include <ifaddrs.h> +#endif +#endif + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#define ETH_TYPE_LEN_SIZE sizeof(uint16_t) +#define ETH_HDR_SIZE (2 * POA_MAC_SIZE + ETH_TYPE_LEN_SIZE) +#define POA_HDR_SIZE (sizeof(struct poa_hdr)) +#define ETH_HDR_TOT_SIZE (ETH_HDR_SIZE + POA_HDR_SIZE) + +#define BPF_DEV_MAX 256 /* /dev/bpfN units to try */ +/* Wait for the link to come back before reading it again. */ +#define ETH_DOWN_TIMEO 100 /* ms */ +/* Budget for a qdisc dump; the send path must not wait on netlink. */ +#define POA_QDISC_TIMEO 5 /* ms */ + +#ifndef ETH_MAX_MTU /* In if_ether.h as of Linux 4.10. */ +#define ETH_MAX_MTU 0xFFFFU +#endif + +struct poa_hdr { + uint16_t eid; + uint16_t len; + uint8_t hcs; +} __attribute__((packed)); + +struct eth_hdr { + uint8_t dst[POA_MAC_SIZE]; + uint8_t src[POA_MAC_SIZE]; + uint16_t ethertype; + struct poa_hdr poa; +} __attribute__((packed)); + + +struct eth_priv { + struct poa * poa; + + int s_fd; /* raw socket or bpf device */ + uint16_t ethertype; /* network order */ + uint8_t hw_addr[POA_MAC_SIZE]; + uint32_t mtu; /* device payload capacity */ + int ifindex; /* link id; 0 where unknown */ + + pthread_t reader; + bool running; +#ifdef HAVE_RAW_SOCKETS + struct sockaddr_ll device; +#elif defined(HAVE_BPF) + size_t blen; /* bpf device buffer size */ +#elif defined(HAVE_NETMAP) + struct nm_desc * nmd; + struct pollfd poll_in; + struct pollfd poll_out; +#endif + /* The kernel zeroes its counters on read, so accumulate. */ + size_t kern_rcv; + size_t kern_drp; +#ifdef HAVE_RAW_SOCKETS + /* Qdisc depth: netlink descriptor, its try-lock and cache. */ + int nl_fd; + uint8_t nl_busy; + size_t nl_pkt; + size_t nl_byt; + uint64_t nl_time; +#endif +}; + +struct eth_query { + struct list_head next; + uint8_t hash[POA_QUERY_HLEN]; + /* The resolve's constraints; replies must satisfy them. */ + uint16_t ethertype; + char c_dev[DEV_NAME_SIZE + 1]; + uint8_t c_mac[POA_MAC_SIZE]; + bool replied; + char dev[DEV_NAME_SIZE + 1]; + uint8_t src_mac[POA_MAC_SIZE]; + uint8_t mac[POA_MAC_SIZE]; + uint16_t r_ethertype; +}; + +static struct { + pthread_once_t once; + + struct llist pending; + pthread_mutex_t mtx; + pthread_cond_t cond; +} queries = { .once = PTHREAD_ONCE_INIT }; + +static void eth_hdr_ser(const struct eth_priv * priv, + struct eth_hdr * hdr, + const uint8_t * dst, + uint32_t eid, + size_t len) +{ + memcpy(hdr->dst, dst, POA_MAC_SIZE); + memcpy(hdr->src, priv->hw_addr, POA_MAC_SIZE); + + hdr->ethertype = priv->ethertype; + hdr->poa.eid = htons((uint16_t) eid); + hdr->poa.len = htons((uint16_t) len); + mem_hash(HASH_CRC8, &hdr->poa.hcs, + (uint8_t *) &hdr->poa.eid, 2 * sizeof(uint16_t)); +} + +/* Oriented from us to the sender: our PoA, then their MAC. */ +static void frame_to_addr(const struct eth_priv * priv, + const struct eth_hdr * hdr, + struct poa_addr * addr) +{ + memset(addr, 0, sizeof(*addr)); + + addr->type = priv->poa->type; + + addr->eth.src = priv->poa->local.eth.src; + + memcpy(addr->eth.dst.mac, hdr->src, POA_MAC_SIZE); + + addr->eth.dst.ethertype = ntohs(priv->ethertype); +} + +static bool frame_is_for_us(const struct eth_priv * priv, + const uint8_t * dst) +{ + static const uint8_t bc[POA_MAC_SIZE] = + { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; + + if (memcmp(dst, priv->hw_addr, POA_MAC_SIZE) == 0) + return true; + + return memcmp(dst, bc, POA_MAC_SIZE) == 0; +} + +/* + * Parse and validate a frame; on success *eid and *plen locate the + * payload. Errors are silent: anyone can spray an interface. + */ +static int frame_parse(const struct eth_priv * priv, + const uint8_t * buf, + size_t len, + uint32_t * eid, + size_t * plen) +{ + const struct eth_hdr * hdr = (const struct eth_hdr *) buf; + uint8_t hcs; + + if (len < ETH_HDR_TOT_SIZE) + return -1; + + if (!frame_is_for_us(priv, hdr->dst)) + return -1; + + if (hdr->ethertype != priv->ethertype) + return -1; + + mem_hash(HASH_CRC8, &hcs, + (const uint8_t *) &hdr->poa.eid, 2 * sizeof(uint16_t)); + + if (hcs != hdr->poa.hcs) + return -1; + + *eid = ntohs(hdr->poa.eid); + + *plen = ntohs(hdr->poa.len); + if (*plen > len - ETH_HDR_TOT_SIZE) + return -1; + + return 0; +} + +#define ETH_QUERY_TIMEO 1900 /* ms total budget */ +#define ETH_QUERY_RETRIES 3 /* retransmits, 4 attempts total */ + +/* A resolve in progress; a reply fills every matching entry. */ +static void queries_init(void) +{ + pthread_condattr_t cattr; + + llist_init(&queries.pending); + + pthread_mutex_init(&queries.mtx, NULL); + + pthread_condattr_init(&cattr); +#ifndef __APPLE__ + pthread_condattr_setclock(&cattr, PTHREAD_COND_CLOCK); +#endif + pthread_cond_init(&queries.cond, &cattr); + pthread_condattr_destroy(&cattr); +} + +static void eth_query_cleanup(void * o) +{ + struct eth_query * q = (struct eth_query *) o; + + pthread_mutex_lock(&queries.mtx); + llist_del(&q->next, &queries.pending); + pthread_mutex_unlock(&queries.mtx); +} + +static void eth_query_arr(struct poa * poa, + const struct poa_addr * src, + const uint8_t * hash) +{ + uint8_t buf[sizeof(struct poa_mgmt_msg) + + POA_QUERY_HLEN]; + struct poa_mgmt_msg * msg = (struct poa_mgmt_msg *) buf; + + if (!poa_has_name(hash)) + return; + + poa_mgmt_msg_ser(msg, POA_NAME_REPLY, 0, 0, qos_raw, 0, POA_QUERY_HLEN); + + memcpy(buf + sizeof(*msg), hash, POA_QUERY_HLEN); + + if (poa->ops->poa_send_mgmt(poa, src, buf, sizeof(buf)) < 0) + return; /* the requester retransmits */ + + POA_STAT_BUMP(poa, rep_tx); +} + +static bool mac_is_zero(const uint8_t * mac) +{ + static const uint8_t zero[POA_MAC_SIZE] = { 0 }; + + return memcmp(mac, zero, POA_MAC_SIZE) == 0; +} + +static bool eth_query_is_match(const struct eth_query * q, + const struct poa * poa) +{ + if (q->ethertype != 0 && + q->ethertype != poa->local.eth.src.ethertype) + return false; + + if (q->c_dev[0] != '\0' && + strcmp(q->c_dev, poa->local.eth.src.dev) != 0) + return false; + + if (mac_is_zero(q->c_mac)) + return true; + + return memcmp(q->c_mac, poa->local.eth.src.mac, POA_MAC_SIZE) == 0; +} + +static void eth_reply_arr(struct poa * poa, + const struct poa_addr * src, + const uint8_t * hash) +{ + struct list_head * p; + + pthread_once(&queries.once, queries_init); + + pthread_mutex_lock(&queries.mtx); + + list_for_each(p, &queries.pending.list) { + struct eth_query * q; + + q = list_entry(p, struct eth_query, next); + if (q->replied || memcmp(q->hash, hash, POA_QUERY_HLEN) != 0) + continue; + + if (!eth_query_is_match(q, poa)) + continue; + + memcpy(q->mac, src->eth.dst.mac, POA_MAC_SIZE); + memcpy(q->src_mac, src->eth.src.mac, POA_MAC_SIZE); + strcpy(q->dev, src->eth.src.dev); + + q->r_ethertype = poa->local.eth.src.ethertype; + + q->replied = true; + } + + pthread_cond_broadcast(&queries.cond); + + pthread_mutex_unlock(&queries.mtx); +} + +/* Name query frames are handled in the transport. */ +static void eth_rx_mgmt(struct poa * poa, + const struct poa_addr * src, + const uint8_t * buf, + size_t len) +{ + const struct poa_mgmt_msg * msg; + const uint8_t * hash; + + msg = (const struct poa_mgmt_msg *) buf; + if (len < sizeof(*msg)) { + poa_rx_mgmt(poa, src, buf, len); + return; + } + + if (msg->code != POA_NAME_QUERY && msg->code != POA_NAME_REPLY) { + poa_rx_mgmt(poa, src, buf, len); + return; + } + + if (ntoh16(msg->data_len) != POA_QUERY_HLEN) + return; /* malformed */ + + if (len < sizeof(*msg) + POA_QUERY_HLEN) + return; /* malformed */ + + hash = buf + sizeof(*msg); + + if (msg->code == POA_NAME_QUERY) { + POA_STAT_BUMP(poa, qry_rx); + eth_query_arr(poa, src, hash); + } else { + POA_STAT_BUMP(poa, rep_rx); + eth_reply_arr(poa, src, hash); + } +} + +static int eth_query_send(const uint8_t * hash, + const struct poa_addr * addr) +{ + uint8_t buf[sizeof(struct poa_mgmt_msg) + + POA_QUERY_HLEN]; + struct poa_mgmt_msg * msg = (struct poa_mgmt_msg *) buf; + struct poa_addr bcast; + + bcast = *addr; + + memset(bcast.eth.dst.mac, 0xff, POA_MAC_SIZE); + + poa_mgmt_msg_ser(msg, POA_NAME_QUERY, 0, 0, qos_raw, 0, POA_QUERY_HLEN); + + memcpy(buf + sizeof(*msg), hash, POA_QUERY_HLEN); + + return poa_bcast_mgmt(&bcast, buf, sizeof(buf)); +} + +/* Complete addr for dst by broadcast query; the poa_query op. */ +static int eth_query(const char * dst, + const struct timespec * timeo, + struct poa_addr * addr) +{ + struct timespec dflt = TIMESPEC_INIT_MS(ETH_QUERY_TIMEO); + struct timespec rintv = TIMESPEC_INIT_MS(ETH_QUERY_TIMEO + / (ETH_QUERY_RETRIES + 1)); + struct eth_query q; + struct timespec abstime; + struct timespec now; + struct timespec dl; + time_t rintv_ns = (time_t) TS_TO_UINT64(rintv); + time_t diff; + uint8_t hash[POA_QUERY_HLEN]; + int n; + int err = -ETIMEDOUT; + + if (strlen(addr->eth.src.dev) > DEV_NAME_SIZE) + return -EINVAL; + + addr->eth.src.ethertype = addr->eth.dst.ethertype; + + /* The destination is set; a zero ethertype cannot be sent. */ + if (!mac_is_zero(addr->eth.dst.mac)) + return addr->eth.dst.ethertype < 0x0600 ? -EINVAL : 0; + + str_hash(HASH_SHA3_256, hash, dst); + + pthread_once(&queries.once, queries_init); + + memset(&q, 0, sizeof(q)); + memcpy(q.hash, hash, POA_QUERY_HLEN); + + q.ethertype = addr->eth.dst.ethertype; + + memcpy(q.c_mac, addr->eth.src.mac, POA_MAC_SIZE); + strcpy(q.c_dev, addr->eth.src.dev); + + pthread_mutex_lock(&queries.mtx); + llist_add(&q.next, &queries.pending); + pthread_mutex_unlock(&queries.mtx); + + pthread_cleanup_push(eth_query_cleanup, &q); + + clock_gettime(PTHREAD_COND_CLOCK, &now); + ts_add(&now, timeo != NULL ? timeo : &dflt, &abstime); + + while (ts_diff_ns(&now, &abstime) < 0) { + n = eth_query_send(hash, addr); + if (n < 0) { + err = n; + break; + } + + if (n == 0) { + err = -EPERM; + break; + } + + ts_add(&now, &rintv, &dl); + + if (ts_diff_ns(&dl, &abstime) > 0) + dl = abstime; + + pthread_mutex_lock(&queries.mtx); + + pthread_cleanup_push(__cleanup_mutex_unlock, &queries.mtx); + + while (!q.replied) { + if (pthread_cond_timedwait(&queries.cond, &queries.mtx, + &dl) == ETIMEDOUT) + break; + } + + if (q.replied) { + memcpy(addr->eth.dst.mac, q.mac, POA_MAC_SIZE); + memcpy(addr->eth.src.mac, q.src_mac, POA_MAC_SIZE); + strcpy(addr->eth.src.dev, q.dev); + + addr->eth.dst.ethertype = q.r_ethertype; + addr->eth.src.ethertype = q.r_ethertype; + + err = 0; + } + + pthread_cleanup_pop(true); + + if (err == 0) + break; + + clock_gettime(PTHREAD_COND_CLOCK, &now); + + diff = ts_diff_ns(&now, &abstime); + if (diff > -rintv_ns) /* skip the runt attempt */ + break; + } + + pthread_cleanup_pop(true); + + return err; +} + +static void eth_spec(const struct poa * poa, + struct poa_spec * spec) +{ + spec->type = poa->type; + spec->eth = poa->local.eth.src; +} + +static bool eth_has_id(const struct poa * poa, + const struct poa_spec * spec) +{ + if (strnlen(spec->eth.dev, sizeof(spec->eth.dev)) > DEV_NAME_SIZE) + return false; + + if (strcmp(poa->local.eth.src.dev, spec->eth.dev) != 0) + return false; + + return poa->local.eth.src.ethertype == spec->eth.ethertype; +} + +/* Our end of the link; an unnamed one matches any. */ +static bool eth_is_src(const struct poa * poa, + const struct eth_poa * src) +{ + if (src->dev[0] != '\0' && + strcmp(poa->local.eth.src.dev, src->dev) != 0) + return false; + + if (mac_is_zero(src->mac)) + return true; + + return memcmp(poa->local.eth.src.mac, src->mac, + POA_MAC_SIZE) == 0; +} + +/* An ethertype of 0 in dst matches any, for broadcast queries. */ +static bool eth_match(const struct poa * poa, + const struct poa_addr * dst) +{ + uint16_t et = dst->eth.dst.ethertype; + + if (et != 0 && et != poa->local.eth.src.ethertype) + return false; + + return eth_is_src(poa, &dst->eth.src); +} + +static bool eth_link_match(const struct poa * poa, + int id) +{ + struct eth_priv * priv = (struct eth_priv *) poa->priv; + + return priv->ifindex == id; +} + +#ifdef HAVE_RAW_SOCKETS + +/* MSG_DONTWAIT: the reader blocks on this socket. */ +static int eth_sendv(struct eth_priv * priv, + const uint8_t * dst, + uint32_t eid, + const uint8_t * body, + size_t len, + bool block, + const struct timespec * abstime) +{ + struct eth_hdr hdr; + struct msghdr msg; + struct iovec iov[2]; + int ret; + + if (len > priv->mtu - POA_HDR_SIZE) + return -EMSGSIZE; + + eth_hdr_ser(priv, &hdr, dst, eid, len); + + iov[0].iov_base = &hdr; + iov[0].iov_len = ETH_HDR_TOT_SIZE; + iov[1].iov_base = (void *) body; + iov[1].iov_len = len; + + memset(&msg, 0, sizeof(msg)); + + msg.msg_name = &priv->device; + msg.msg_namelen = sizeof(priv->device); + msg.msg_iov = iov; + msg.msg_iovlen = len > 0 ? 2 : 1; + while (sendmsg(priv->s_fd, &msg, MSG_DONTWAIT) < 0) { + if (errno != EAGAIN && errno != EWOULDBLOCK) + return -EIO; + + if (!block) + return -EAGAIN; + + ret = poa_wait_out(priv->s_fd, abstime); + if (ret < 0) + return ret; + } + + return 0; +} + +static void * eth_reader(void * o) +{ + struct poa * poa = (struct poa *) o; + struct eth_priv * priv = (struct eth_priv *) poa->priv; + struct timespec down = TIMESPEC_INIT_MS(ETH_DOWN_TIMEO); + uint8_t * buf; + size_t bufsz; + + bufsz = ETH_HDR_SIZE + priv->mtu; + + buf = malloc(bufsz); + if (buf == NULL) + return (void *) -1; + + pthread_cleanup_push(free, buf); + + while (true) { + struct ssm_pk_buff * spb; + struct sockaddr_ll from; + socklen_t flen; + struct poa_addr src; + ssize_t n; + uint32_t eid; + size_t plen; + const uint8_t * body; + + flen = sizeof(from); + + n = recvfrom(priv->s_fd, buf, bufsz, 0, + (struct sockaddr *) &from, &flen); + + if (n < 0) { + if (errno == EINTR) + continue; + + POA_STAT_BUMP(poa, rcv_fail); + + if (errno == ENETDOWN) { + nanosleep(&down, NULL); + continue; + } + + log_err("Reader on %s stopped: %s.", + poa->local.eth.src.dev, + strerror(errno)); + break; + } + + if (from.sll_pkttype == PACKET_OUTGOING) + continue; + + if (frame_parse(priv, buf, (size_t) n, &eid, &plen) < 0) + continue; + + body = buf + ETH_HDR_TOT_SIZE; + + if (eid == POA_MGMT_EID) { + frame_to_addr(priv, (struct eth_hdr *) buf, &src); + eth_rx_mgmt(poa, &src, body, plen); + continue; + } + + if (poa_spb_reserve(&spb, plen) < 0) { + POA_STAT_BUMP(poa, buf_fail); + continue; + } + + memcpy(ssm_pk_buff_head(spb), body, plen); + + poa_rx_pkt(poa, eid, spb); + } + + pthread_cleanup_pop(true); + + return (void *) 0; +} + +/* + * One netlink socket for the whole subsystem: RTMGRP_LINK delivers the + * events of every interface anyway, so a socket per PoA only added + * discards. + */ +int poa_monitor_open(void) +{ + struct sockaddr_nl sa; + int fd; + + memset(&sa, 0, sizeof(sa)); + + sa.nl_family = AF_NETLINK; + sa.nl_groups = RTMGRP_LINK; + + fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); + if (fd < 0) + return -1; + + if (bind(fd, (struct sockaddr *) &sa, sizeof(sa)) < 0) { + close(fd); + return -1; + } + + return fd; +} + +void poa_monitor_read(int fd) +{ + uint8_t buf[4096]; + struct nlmsghdr * h; + ssize_t n; + + n = recv(fd, buf, sizeof(buf), 0); + if (n < 0) + return; + + for (h = (struct nlmsghdr *) buf; + NLMSG_OK(h, (unsigned int) n); + h = NLMSG_NEXT(h, n)) { + struct ifinfomsg * ifi; + unsigned int usable; + bool up; + size_t cnt; + + if (h->nlmsg_type == NLMSG_DONE) + break; + + if (h->nlmsg_type != RTM_NEWLINK) + continue; + + ifi = NLMSG_DATA(h); + + usable = ifi->ifi_flags & (IFF_UP | IFF_RUNNING); + up = usable == (IFF_UP | IFF_RUNNING); + + cnt = poa_link_updown(ifi->ifi_index, up); + if (cnt > 0) + log_info("Link %d %s, %zu flows.", ifi->ifi_index, + up ? "up" : "down", cnt); + } +} + +static int eth_dev_info(const char * dev, + int * idx, + uint8_t * mac, + uint32_t * mtu) +{ + struct ifreq ifr; + int fd; + + if (strlen(dev) >= IFNAMSIZ) + return -EINVAL; + + *idx = if_nametoindex(dev); + if (*idx == 0) { + log_err("Failed to find device %s.", dev); + return -ENODEV; + } + + fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd < 0) + return -EIO; + + memset(&ifr, 0, sizeof(ifr)); + strcpy(ifr.ifr_name, dev); + + if (ioctl(fd, SIOCGIFHWADDR, &ifr) < 0) { + log_err("Failed to get hardware address of %s.", dev); + goto fail_ioctl; + } + + memcpy(mac, ifr.ifr_hwaddr.sa_data, POA_MAC_SIZE); + + if (ioctl(fd, SIOCGIFMTU, &ifr) < 0) { + log_err("Failed to get MTU of %s.", dev); + goto fail_ioctl; + } + + close(fd); + + *mtu = MIN(MIN(ETH_MAX_MTU, POA_ETH_RD_BUF), (uint32_t) ifr.ifr_mtu); + if (memcmp(dev, "lo", 2) == 0 && *mtu > POA_ETH_LO_MTU) + *mtu = POA_ETH_LO_MTU; + + return 0; + + fail_ioctl: + close(fd); + return -EIO; +} + +/* SO_RCVBUFFORCE bypasses rmem_max; SO_RCVBUF is the fallback. */ +static void eth_set_rcvbuf(int fd, + int rcvbuf) +{ + if (setsockopt(fd, SOL_SOCKET, SO_RCVBUFFORCE, + &rcvbuf, sizeof(rcvbuf)) == 0) + return; + + if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(rcvbuf)) < 0) + log_info("Failed to set SO_RCVBUF to %d.", rcvbuf); +} + +/* The send buffer holds at least one frame, or sendmsg cannot fit a PDU. */ +static int eth_socket(struct eth_priv * priv, + int idx, + const uint8_t * mac) +{ + int sndbuf; + int rcvbuf; +#ifdef POA_ETH_QDISC_BYPASS + int bypass = 1; +#endif + + memset(&priv->device, 0, sizeof(priv->device)); + + priv->device.sll_ifindex = idx; + priv->device.sll_family = AF_PACKET; + priv->device.sll_halen = POA_MAC_SIZE; + priv->device.sll_protocol = htons(ETH_P_ALL); + + memcpy(priv->device.sll_addr, mac, POA_MAC_SIZE); + memcpy(priv->hw_addr, mac, POA_MAC_SIZE); + + priv->s_fd = socket(AF_PACKET, SOCK_RAW, priv->ethertype); + if (priv->s_fd < 0) { + log_err("Failed to create socket: %s.", strerror(errno)); + return -1; + } + +#ifdef POA_ETH_QDISC_BYPASS + if (setsockopt(priv->s_fd, SOL_PACKET, PACKET_QDISC_BYPASS, + &bypass, sizeof(bypass)) < 0) + log_info("Qdisc bypass not supported."); +#endif + + sndbuf = POA_ETH_SNDBUF; + if (sndbuf > 0) { + sndbuf = MAX(sndbuf, (int) (ETH_HDR_SIZE + priv->mtu)); + + if (setsockopt(priv->s_fd, SOL_SOCKET, SO_SNDBUF, + &sndbuf, sizeof(sndbuf)) < 0) + log_info("Failed to set SO_SNDBUF to %d.", sndbuf); + } + + rcvbuf = POA_ETH_RCVBUF; + if (rcvbuf > 0) + eth_set_rcvbuf(priv->s_fd, rcvbuf); + + if (bind(priv->s_fd, (struct sockaddr *) &priv->device, + sizeof(priv->device)) < 0) { + log_err("Failed to bind socket to %d.", idx); + close(priv->s_fd); + return -1; + } + + return 0; +} + +/* + * Pull qlen and backlog from the nested TCA_STATS2. The top-level + * TCA_STATS shares TCA_STATS_QUEUE's id but carries a wholly + * different struct, so descend first; never match on id alone. + */ +static void eth_qdisc_parse(struct rtattr * rta, + size_t rlen, + size_t * byt, + size_t * pkt) +{ + struct gnet_stats_queue q; + struct rtattr * in; + size_t ilen; + + for (; RTA_OK(rta, rlen); rta = RTA_NEXT(rta, rlen)) { + if (rta->rta_type != TCA_STATS2) + continue; + + in = (struct rtattr *) RTA_DATA(rta); + ilen = RTA_PAYLOAD(rta); + for (; RTA_OK(in, ilen); in = RTA_NEXT(in, ilen)) { + if (in->rta_type != TCA_STATS_QUEUE) + continue; + + if (RTA_PAYLOAD(in) < sizeof(q)) + continue; + + memcpy(&q, RTA_DATA(in), sizeof(q)); + + *byt = q.backlog; + *pkt = q.qlen; + } + } +} + +/* + * Egress backlog of the device's root qdisc, in bytes and packets, + * as the kernel queues them. The caller owns the netlink descriptor + * and serialises the query. An early end of dump reports failure. + */ +static int eth_qdisc_backlog(int fd, + int ifindex, + size_t * byt, + size_t * pkt) +{ + struct { + struct nlmsghdr nh; + struct tcmsg tc; + } req; + struct nlmsghdr * nh; + struct rtattr * rta; + struct tcmsg * tc; + char buf[16384]; + ssize_t len; + int ret = -1; + + if (fd < 0) + goto fail; + + memset(&req, 0, sizeof(req)); + + req.nh.nlmsg_len = NLMSG_LENGTH(sizeof(req.tc)); + req.nh.nlmsg_type = RTM_GETQDISC; + req.nh.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; + req.nh.nlmsg_seq = 1; + req.tc.tcm_family = AF_UNSPEC; + req.tc.tcm_ifindex = ifindex; + if (send(fd, &req, req.nh.nlmsg_len, 0) < 0) + goto fail; + + *byt = 0; + *pkt = 0; + + while ((len = recv(fd, buf, sizeof(buf), 0)) > 0) { + nh = (struct nlmsghdr *) buf; + for (; NLMSG_OK(nh, len); nh = NLMSG_NEXT(nh, len)) { + if (nh->nlmsg_type == NLMSG_DONE) + goto done; + + if (nh->nlmsg_type == NLMSG_ERROR) + goto fail; + + if (nh->nlmsg_type != RTM_NEWQDISC) + continue; + + tc = (struct tcmsg *) NLMSG_DATA(nh); + if (tc->tcm_ifindex != ifindex) + continue; + + if (tc->tcm_parent != TC_H_ROOT) + continue; + + rta = (struct rtattr *) + ((char *) tc + NLMSG_ALIGN(sizeof(*tc))); + + eth_qdisc_parse(rta, nh->nlmsg_len + - NLMSG_LENGTH(sizeof(*tc)), + byt, pkt); + } + } + + goto fail; /* early end: a zero would read as empty */ + done: + ret = 0; + fail: + return ret; +} + +/* + * A netlink descriptor for the qdisc query. SO_RCVTIMEO bounds the + * dump: this is read from the send path, and a reply that never + * arrives must not park a sender thread. + */ +static int eth_qdisc_open(void) +{ + struct sockaddr_nl sa; + struct timeval tv = TIMEVAL_INIT_MS(POA_QDISC_TIMEO); + int fd; + + fd = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE); + if (fd < 0) + return -1; + + memset(&sa, 0, sizeof(sa)); + + sa.nl_family = AF_NETLINK; + if (bind(fd, (struct sockaddr *) &sa, sizeof(sa)) < 0) + goto fail; + + if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) < 0) + goto fail; + + return fd; + fail: + close(fd); + + return -1; +} + +/* + * Queue depth in packets, straight from the qdisc. Gated like the + * transport's own depth and skipped when another sender is already + * asking, so the send path never waits on netlink. + */ +static int eth_qpkts(struct poa * poa, + size_t * pkts, + size_t * byts) +{ + struct eth_priv * priv = (struct eth_priv *) poa->priv; + struct timespec now; + uint64_t ns; + size_t byt; + size_t pkt; + + if (priv->nl_fd < 0) + return -1; + + clock_gettime(PTHREAD_COND_CLOCK, &now); + + ns = TS_TO_UINT64(now); + if (ns - LOAD_RELAXED(&priv->nl_time) < POA_QLEN_GATE) + goto cached; + + if (__atomic_exchange_n(&priv->nl_busy, 1, __ATOMIC_ACQUIRE) != 0) + goto cached; + + if (eth_qdisc_backlog(priv->nl_fd, priv->ifindex, &byt, &pkt) == 0) { + STORE_RELAXED(&priv->nl_pkt, pkt); + STORE_RELAXED(&priv->nl_byt, byt); + STORE_RELAXED(&priv->nl_time, ns); + } + + __atomic_store_n(&priv->nl_busy, 0, __ATOMIC_RELEASE); + cached: + if (LOAD_RELAXED(&priv->nl_time) == 0) /* nothing measured yet */ + return -1; + + *pkts = LOAD_RELAXED(&priv->nl_pkt); + *byts = LOAD_RELAXED(&priv->nl_byt); + + return 0; +} + +static int eth_attach(struct poa * poa, + const struct poa_spec * spec) +{ + const struct eth_poa * c = &spec->eth; + struct eth_priv * priv; + uint8_t mac[POA_MAC_SIZE]; + uint32_t mtu; + int idx; + int err; + + if (strnlen(c->dev, sizeof(c->dev)) > DEV_NAME_SIZE) + return -EINVAL; + + if (c->ethertype < 0x0600 || c->ethertype == 0xFFFF) { + log_err("Invalid Ethertype 0x%x.", c->ethertype); + return -EINVAL; + } + + priv = malloc(sizeof(*priv)); + if (priv == NULL) + return -ENOMEM; + + memset(priv, 0, sizeof(*priv)); + + priv->poa = poa; + priv->nl_fd = eth_qdisc_open(); /* optional; socket fallback */ + + priv->ethertype = htons(c->ethertype); + + err = eth_dev_info(c->dev, &idx, mac, &mtu); + if (err < 0) + goto fail_conf; + + priv->mtu = mtu; + if (eth_socket(priv, idx, mac) < 0) + goto fail_conf; + + poa->priv = priv; + poa->local.type = poa->type; + poa->local.eth.src.ethertype = c->ethertype; + priv->ifindex = idx; + + memcpy(poa->local.eth.src.mac, mac, POA_MAC_SIZE); + strcpy(poa->local.eth.src.dev, c->dev); + + return 0; + + fail_conf: + if (priv->nl_fd >= 0) + close(priv->nl_fd); + + free(priv); + + return -EIO; +} + +static void eth_detach(struct poa * poa) +{ + struct eth_priv * priv = (struct eth_priv *) poa->priv; + + if (priv == NULL) + return; + + close(priv->s_fd); + + if (priv->nl_fd >= 0) + close(priv->nl_fd); + + free(priv); + + poa->priv = NULL; +} + +static uint32_t eth_mtu(struct poa * poa, + const struct poa_addr * dst) +{ + struct eth_priv * priv = (struct eth_priv *) poa->priv; + + (void) dst; + + return priv->mtu - POA_HDR_SIZE; +} + +/* All flows on the PoA share the socket, so this is aggregate. */ +static size_t eth_qlen(struct poa * poa) +{ +#ifdef SIOCOUTQ + struct eth_priv * priv = (struct eth_priv *) poa->priv; + int qlen; + + qlen = 0; + if (ioctl(priv->s_fd, SIOCOUTQ, &qlen) < 0) + return 0; + + return (size_t) qlen; +#else + (void) poa; + + return 0; +#endif +} + +/* + * PACKET_STATISTICS zeroes the kernel counters on read, so totals + * accumulate here; relaxed atomics allow concurrent RIB reads. + * The qdisc depth rides the sender's guarded path: one dump at a time. + */ +static int eth_rib(struct poa * poa, + char * buf, + size_t len) +{ + struct eth_priv * priv = (struct eth_priv *) poa->priv; + struct tpacket_stats ts; + socklen_t optlen; + size_t sndbuf = 0; + size_t rcvbuf = 0; + size_t qd_byt; + size_t qd_pkt; + int val; + int size; + + optlen = sizeof(val); + if (getsockopt(priv->s_fd, SOL_SOCKET, SO_SNDBUF, &val, &optlen) == 0) + sndbuf = (size_t) val; + + optlen = sizeof(val); + if (getsockopt(priv->s_fd, SOL_SOCKET, SO_RCVBUF, &val, &optlen) == 0) + rcvbuf = (size_t) val; + + optlen = sizeof(ts); + + if (getsockopt(priv->s_fd, SOL_PACKET, PACKET_STATISTICS, + &ts, &optlen) == 0) { + FETCH_ADD_RELAXED(&priv->kern_rcv, ts.tp_packets); + FETCH_ADD_RELAXED(&priv->kern_drp, ts.tp_drops); + } + + if (eth_qpkts(poa, &qd_pkt, &qd_byt) < 0) { + qd_byt = 0; + qd_pkt = 0; + } + + size = snprintf(buf, len, + "Socket sndbuf (bytes): %zu\n" + "Socket rcvbuf (bytes): %zu\n" + "Kernel packets received: %zu\n" + "Kernel packets dropped: %zu\n" + "Qdisc backlog (bytes): %zu\n" + "Qdisc backlog (packets): %zu\n", + sndbuf, rcvbuf, + LOAD_RELAXED(&priv->kern_rcv), + LOAD_RELAXED(&priv->kern_drp), + qd_byt, qd_pkt); + if (size < 0 || (size_t) size >= len) + return -1; + + return size; +} + +#elif defined(HAVE_BPF) + +/* + * BSD and macOS reach the link layer through a cloned /dev/bpf + * device, bound to an interface with BIOCSETIF. One device per PoA. + */ + +static int eth_sendv(struct eth_priv * priv, + const uint8_t * dst, + uint32_t eid, + const uint8_t * body, + size_t len, + bool block, + const struct timespec * abstime) +{ + struct eth_hdr hdr; + struct iovec iov[2]; + int n; + int ret; + + if (len > priv->mtu - POA_HDR_SIZE) + return -EMSGSIZE; + + eth_hdr_ser(priv, &hdr, dst, eid, len); + + iov[0].iov_base = &hdr; + iov[0].iov_len = ETH_HDR_TOT_SIZE; + iov[1].iov_base = (void *) body; + iov[1].iov_len = len; + n = len > 0 ? 2 : 1; + while (writev(priv->s_fd, iov, n) < 0) { + if (errno != EAGAIN && errno != EWOULDBLOCK) + return -EIO; + + if (!block) + return -EAGAIN; + + ret = poa_wait_out(priv->s_fd, abstime); + if (ret < 0) + return ret; + } + + return 0; +} + +/* One read yields a batch of BPF_WORDALIGN'ed frames; walk all of them. */ +static void * eth_reader(void * o) +{ + struct poa * poa = (struct poa *) o; + struct eth_priv * priv = (struct eth_priv *) poa->priv; + uint8_t * buf; + + buf = malloc(priv->blen); + if (buf == NULL) + return (void *) -1; + + pthread_cleanup_push(free, buf); + + while (true) { + uint8_t * p; + uint8_t * end; + ssize_t n; + + n = read(priv->s_fd, buf, priv->blen); + if (n < 0) { + if (errno == EINTR) + continue; + + POA_STAT_BUMP(poa, rcv_fail); + break; + } + + p = buf; + end = buf + n; + while (p + sizeof(struct bpf_hdr) <= end) { + struct bpf_hdr * bh = (struct bpf_hdr *) p; + struct ssm_pk_buff * spb; + struct poa_addr src; + const uint8_t * frame; + const uint8_t * body; + uint32_t eid; + size_t plen; + + frame = p + bh->bh_hdrlen; + if (frame + bh->bh_caplen > end) + break; + + if (frame_parse(priv, frame, bh->bh_caplen, + &eid, &plen) < 0) + goto next; + + body = frame + ETH_HDR_TOT_SIZE; + + if (eid == POA_MGMT_EID) { + frame_to_addr(priv, + (const struct eth_hdr *) frame, + &src); + eth_rx_mgmt(poa, &src, body, plen); + goto next; + } + + if (poa_spb_reserve(&spb, plen) < 0) { + POA_STAT_BUMP(poa, buf_fail); + goto next; + } + + memcpy(ssm_pk_buff_head(spb), body, plen); + + poa_rx_pkt(poa, eid, spb); + next: + p += BPF_WORDALIGN(bh->bh_hdrlen + bh->bh_caplen); + } + } + + pthread_cleanup_pop(true); + + return (void *) 0; +} + +static int eth_dev_info(const char * dev, + uint8_t * mac, + uint32_t * mtu) +{ + struct ifaddrs * ifas; + struct ifaddrs * ifa; + struct ifreq ifr; + int fd; + int found = 0; + + if (getifaddrs(&ifas) < 0) { + log_err("Failed to list interfaces."); + return -1; + } + + for (ifa = ifas; ifa != NULL; ifa = ifa->ifa_next) { + struct sockaddr_dl * dl; + + if (ifa->ifa_addr == NULL) + continue; + + if (ifa->ifa_addr->sa_family != AF_LINK) + continue; + + if (strcmp(ifa->ifa_name, dev) != 0) + continue; + + dl = (struct sockaddr_dl *) ifa->ifa_addr; + if (dl->sdl_alen != POA_MAC_SIZE) + continue; + + memcpy(mac, LLADDR(dl), POA_MAC_SIZE); + + found = 1; + break; + } + + freeifaddrs(ifas); + + if (!found) { + log_err("No such device: %s.", dev); + return -1; + } + + fd = socket(AF_INET, SOCK_DGRAM, 0); + if (fd < 0) + return -1; + + memset(&ifr, 0, sizeof(ifr)); + + strcpy(ifr.ifr_name, dev); + + if (ioctl(fd, SIOCGIFMTU, &ifr) < 0) { + log_err("Failed to get MTU of %s.", dev); + close(fd); + return -1; + } + + close(fd); + + *mtu = MIN(MIN(ETH_MAX_MTU, POA_ETH_RD_BUF), (uint32_t) ifr.ifr_mtu); + if (memcmp(dev, "lo", 2) == 0 && *mtu > POA_ETH_LO_MTU) + *mtu = POA_ETH_LO_MTU; + + return 0; +} + +static int eth_bpf_open(void) +{ + char dev[32]; + size_t i; + + for (i = 0; i < BPF_DEV_MAX; ++i) { + int fd; + + sprintf(dev, "/dev/bpf%zu", i); + + fd = open(dev, O_RDWR); + if (fd >= 0) + return fd; + } + + return -1; +} + +/* + * BIOCSHDRCMPLT: we fill in the source address ourselves. + * BIOCSSEESENT: our own egress must not come back at us. + * BIOCIMMEDIATE: deliver on arrival, do not wait for a full buffer. + */ +static int eth_bpf_setup(struct eth_priv * priv, + const char * dev) +{ + struct ifreq ifr; + int enable = 1; + int disable = 0; + int blen = 0; + + memset(&ifr, 0, sizeof(ifr)); + + strcpy(ifr.ifr_name, dev); + + if (ioctl(priv->s_fd, BIOCSETIF, &ifr) < 0) { + log_err("Failed to bind bpf device to %s.", dev); + return -1; + } + + if (ioctl(priv->s_fd, BIOCGBLEN, &blen) < 0 || blen <= 0) { + log_err("Failed to get the bpf buffer length."); + return -1; + } + + priv->blen = (size_t) blen; + if (ioctl(priv->s_fd, BIOCSHDRCMPLT, &enable) < 0) { + log_err("Failed to set BIOCSHDRCMPLT."); + return -1; + } + + if (ioctl(priv->s_fd, BIOCSSEESENT, &disable) < 0) { + log_err("Failed to set BIOCSSEESENT."); + return -1; + } + + if (ioctl(priv->s_fd, BIOCIMMEDIATE, &enable) < 0) { + log_err("Failed to set BIOCIMMEDIATE."); + return -1; + } + + return 0; +} + +static int eth_attach(struct poa * poa, + const struct poa_spec * spec) +{ + const struct eth_poa * c = &spec->eth; + struct eth_priv * priv; + uint8_t mac[POA_MAC_SIZE]; + uint32_t mtu; + + if (strnlen(c->dev, sizeof(c->dev)) > DEV_NAME_SIZE) + return -EINVAL; + + if (c->ethertype < 0x0600 || c->ethertype == 0xFFFF) { + log_err("Invalid Ethertype 0x%x.", c->ethertype); + return -EINVAL; + } + + priv = malloc(sizeof(*priv)); + if (priv == NULL) + return -ENOMEM; + + memset(priv, 0, sizeof(*priv)); + + priv->poa = poa; + priv->s_fd = -1; + + priv->ethertype = htons(c->ethertype); + + if (eth_dev_info(c->dev, mac, &mtu) < 0) + goto fail_conf; + + priv->mtu = mtu; + + memcpy(priv->hw_addr, mac, POA_MAC_SIZE); + + priv->s_fd = eth_bpf_open(); + if (priv->s_fd < 0) { + log_err("Failed to open a bpf device."); + goto fail_conf; + } + + if (eth_bpf_setup(priv, c->dev) < 0) + goto fail_device; + + poa->priv = priv; + poa->local.type = poa->type; + poa->local.eth.src.ethertype = c->ethertype; + + memcpy(poa->local.eth.src.mac, mac, POA_MAC_SIZE); + strcpy(poa->local.eth.src.dev, c->dev); + + log_info("Using Berkeley Packet Filter on %s.", c->dev); + + return 0; + + fail_device: + close(priv->s_fd); + fail_conf: + free(priv); + + return -EIO; +} + +static void eth_detach(struct poa * poa) +{ + struct eth_priv * priv = (struct eth_priv *) poa->priv; + + if (priv == NULL) + return; + + close(priv->s_fd); + + free(priv); + + poa->priv = NULL; +} + +static uint32_t eth_mtu(struct poa * poa, + const struct poa_addr * dst) +{ + struct eth_priv * priv = (struct eth_priv *) poa->priv; + + (void) dst; + + return priv->mtu - POA_HDR_SIZE; +} + +/* A bpf device has no send queue to report; mb-ECN cannot mark here. */ +static size_t eth_qlen(struct poa * poa) +{ + (void) poa; + + return 0; +} + +/* The bpf device buffer is all the receive queue there is. */ +static int eth_rib(struct poa * poa, + char * buf, + size_t len) +{ + struct eth_priv * priv = (struct eth_priv *) poa->priv; + struct bpf_stat bs; + int size; + + if (ioctl(priv->s_fd, BIOCGSTATS, &bs) == 0) { + FETCH_ADD_RELAXED(&priv->kern_rcv, bs.bs_recv); + FETCH_ADD_RELAXED(&priv->kern_drp, bs.bs_drop); + } + + size = snprintf(buf, len, + "Socket rcvbuf (bytes): %zu\n" + "Kernel packets received: %zu\n" + "Kernel packets dropped: %zu\n", + priv->blen, + LOAD_RELAXED(&priv->kern_rcv), + LOAD_RELAXED(&priv->kern_drp)); + if (size < 0 || (size_t) size >= len) + return -1; + + return size; +} + +#elif defined(HAVE_NETMAP) + +/* + * netmap gives one PoA the whole port: reads copy out of the NIC + * ring, writes inject into it. + */ + +/* nm_inject takes one contiguous frame, so the header is copied in. */ +static int eth_sendv(struct eth_priv * priv, + const uint8_t * dst, + uint32_t eid, + const uint8_t * body, + size_t len, + bool block, + const struct timespec * abstime) +{ + uint8_t * frame; + size_t flen; + int ret; + + if (len > priv->mtu - POA_HDR_SIZE) + return -EMSGSIZE; + + flen = ETH_HDR_TOT_SIZE + len; + + frame = malloc(flen); + if (frame == NULL) + return -ENOMEM; + + eth_hdr_ser(priv, (struct eth_hdr *) frame, dst, eid, len); + + if (len > 0) + memcpy(frame + ETH_HDR_TOT_SIZE, body, len); + + if (block) + ret = poa_wait_out(priv->poll_out.fd, abstime); + else + ret = poll(&priv->poll_out, 1, 0) > 0 ? 0 : -EAGAIN; + + if (ret < 0) + goto fail; + + ret = nm_inject(priv->nmd, frame, flen) == (int) flen ? 0 : -EIO; + fail: + free(frame); + + return ret; +} + +/* A slot stays owned by the ring, so each frame is copied out. */ +static void * eth_reader(void * o) +{ + struct poa * poa = (struct poa *) o; + struct eth_priv * priv = (struct eth_priv *) poa->priv; + + while (true) { + struct ssm_pk_buff * spb; + struct poa_addr src; + struct nm_pkthdr hdr; + const uint8_t * frame; + const uint8_t * body; + uint32_t eid; + size_t plen; + + if (poll(&priv->poll_in, 1, -1) < 0) { + if (errno == EINTR) + continue; + + POA_STAT_BUMP(poa, rcv_fail); + break; + } + + if (priv->poll_in.revents == 0) + continue; + + frame = nm_nextpkt(priv->nmd, &hdr); + if (frame == NULL) + continue; + + if (frame_parse(priv, frame, hdr.len, &eid, &plen) < 0) + continue; + + body = frame + ETH_HDR_TOT_SIZE; + + if (eid == POA_MGMT_EID) { + frame_to_addr(priv, (const struct eth_hdr *) frame, + &src); + eth_rx_mgmt(poa, &src, body, plen); + continue; + } + + if (poa_spb_reserve(&spb, plen) < 0) { + POA_STAT_BUMP(poa, buf_fail); + continue; + } + + memcpy(ssm_pk_buff_head(spb), body, plen); + + poa_rx_pkt(poa, eid, spb); + } + + return (void *) 0; +} + +static int eth_dev_info(const char * dev, + uint8_t * mac, + uint32_t * mtu) +{ + struct ifreq ifr; +#ifndef __linux__ + struct ifaddrs * ifas; + struct ifaddrs * ifa; + int found = 0; +#endif + int fd; + + if (strlen(dev) >= IFNAMSIZ) + return -EINVAL; + + fd = socket(AF_INET, SOCK_DGRAM, 0); + if (fd < 0) + return -EIO; + + memset(&ifr, 0, sizeof(ifr)); + + strcpy(ifr.ifr_name, dev); + +#ifdef __linux__ + if (ioctl(fd, SIOCGIFHWADDR, &ifr) < 0) { + log_err("Failed to get hardware address of %s.", dev); + goto fail_ioctl; + } + + memcpy(mac, ifr.ifr_hwaddr.sa_data, POA_MAC_SIZE); +#else + if (getifaddrs(&ifas) < 0) + goto fail_ioctl; + + for (ifa = ifas; ifa != NULL; ifa = ifa->ifa_next) { + struct sockaddr_dl * dl; + + if (ifa->ifa_addr == NULL) + continue; + + if (ifa->ifa_addr->sa_family != AF_LINK) + continue; + + if (strcmp(ifa->ifa_name, dev) != 0) + continue; + + dl = (struct sockaddr_dl *) ifa->ifa_addr; + if (dl->sdl_alen != POA_MAC_SIZE) + continue; + + memcpy(mac, LLADDR(dl), POA_MAC_SIZE); + + found = 1; + break; + } + + freeifaddrs(ifas); + + if (!found) { + log_err("No hardware address for %s.", dev); + goto fail_ioctl; + } +#endif + if (ioctl(fd, SIOCGIFMTU, &ifr) < 0) { + log_err("Failed to get MTU of %s.", dev); + goto fail_ioctl; + } + + close(fd); + + *mtu = MIN(MIN(ETH_MAX_MTU, POA_ETH_RD_BUF), (uint32_t) ifr.ifr_mtu); + if (memcmp(dev, "lo", 2) == 0 && *mtu > POA_ETH_LO_MTU) + *mtu = POA_ETH_LO_MTU; + + return 0; + + fail_ioctl: + close(fd); + + return -EIO; +} + +static int eth_nm_open(struct eth_priv * priv, + const char * dev) +{ + char ifn[IFNAMSIZ + sizeof("netmap:")]; + + strcpy(ifn, "netmap:"); + strcat(ifn, dev); + + priv->nmd = nm_open(ifn, NULL, 0, NULL); + if (priv->nmd == NULL) { + log_err("Failed to open netmap device for %s.", dev); + return -1; + } + + memset(&priv->poll_in, 0, sizeof(priv->poll_in)); + memset(&priv->poll_out, 0, sizeof(priv->poll_out)); + + priv->poll_in.fd = NETMAP_FD(priv->nmd); + priv->poll_in.events = POLLIN; + priv->poll_out.fd = NETMAP_FD(priv->nmd); + priv->poll_out.events = POLLOUT; + + return 0; +} + +static int eth_attach(struct poa * poa, + const struct poa_spec * spec) +{ + const struct eth_poa * c = &spec->eth; + struct eth_priv * priv; + uint8_t mac[POA_MAC_SIZE]; + uint32_t mtu; + + if (strnlen(c->dev, sizeof(c->dev)) > DEV_NAME_SIZE) + return -EINVAL; + + if (c->ethertype < 0x0600 || c->ethertype == 0xFFFF) { + log_err("Invalid Ethertype 0x%x.", c->ethertype); + return -EINVAL; + } + + priv = malloc(sizeof(*priv)); + if (priv == NULL) + return -ENOMEM; + + memset(priv, 0, sizeof(*priv)); + + priv->poa = poa; + + priv->ethertype = htons(c->ethertype); + + if (eth_dev_info(c->dev, mac, &mtu) < 0) + goto fail_conf; + + priv->mtu = mtu; + + memcpy(priv->hw_addr, mac, POA_MAC_SIZE); + + if (eth_nm_open(priv, c->dev) < 0) + goto fail_conf; + + poa->priv = priv; + poa->local.type = poa->type; + poa->local.eth.src.ethertype = c->ethertype; + + memcpy(poa->local.eth.src.mac, mac, POA_MAC_SIZE); + strcpy(poa->local.eth.src.dev, c->dev); + + log_info("Using netmap on %s.", c->dev); + + return 0; + + fail_conf: + free(priv); + + return -EIO; +} + +static void eth_detach(struct poa * poa) +{ + struct eth_priv * priv = (struct eth_priv *) poa->priv; + + if (priv == NULL) + return; + + nm_close(priv->nmd); + + free(priv); + + poa->priv = NULL; +} + +static uint32_t eth_mtu(struct poa * poa, + const struct poa_addr * dst) +{ + struct eth_priv * priv = (struct eth_priv *) poa->priv; + + (void) dst; + + return priv->mtu - POA_HDR_SIZE; +} + +/* The ring is drained by the NIC; there is no queue to report. */ +static size_t eth_qlen(struct poa * poa) +{ + (void) poa; + + return 0; +} + +#endif /* HAVE_RAW_SOCKETS */ + +#ifndef HAVE_RAW_SOCKETS + +/* Only netlink reports link events; no other backend has a monitor. */ +int poa_monitor_open(void) +{ + return -1; +} + +void poa_monitor_read(int fd) +{ + (void) fd; +} + +#endif + +/* One reader per socket, so a flow cannot be reordered on receive. */ +static int eth_start(struct poa * poa) +{ + struct eth_priv * priv = (struct eth_priv *) poa->priv; + + if (pthread_create(&priv->reader, NULL, eth_reader, poa) != 0) + return -1; + + priv->running = true; + + return 0; +} + +static void eth_stop(struct poa * poa) +{ + struct eth_priv * priv = (struct eth_priv *) poa->priv; + + if (!priv->running) + return; + + pthread_cancel(priv->reader); + pthread_join(priv->reader, NULL); + + priv->running = false; +} + +static int eth_send(struct poa * poa, + const struct poa_addr * dst, + uint32_t eid, + struct ssm_pk_buff * spb, + bool block, + const struct timespec * abstime) +{ + return eth_sendv((struct eth_priv *) poa->priv, dst->eth.dst.mac, eid, + ssm_pk_buff_head(spb), ssm_pk_buff_len(spb), + block, abstime); +} + +static int eth_send_mgmt(struct poa * poa, + const struct poa_addr * dst, + const uint8_t * buf, + size_t len) +{ + struct timespec timeo = TIMESPEC_INIT_MS(POA_MGMT_SND_TIMEO); + struct timespec abstime; + + clock_gettime(PTHREAD_COND_CLOCK, &abstime); + ts_add(&abstime, &timeo, &abstime); + + return eth_sendv((struct eth_priv *) poa->priv, dst->eth.dst.mac, + POA_MGMT_EID, buf, len, true, &abstime); +} + +const struct poa_ops eth_poa_ops = { + .poa_attach = eth_attach, + .poa_detach = eth_detach, + .poa_start = eth_start, + .poa_stop = eth_stop, + .poa_send = eth_send, + .poa_send_mgmt = eth_send_mgmt, + .poa_query = eth_query, + .poa_mtu = eth_mtu, + .poa_qlen = eth_qlen, +#ifdef HAVE_RAW_SOCKETS + .poa_qpkts = eth_qpkts, +#endif +#ifndef HAVE_NETMAP + .poa_rib = eth_rib, +#endif + .poa_spec = eth_spec, + .poa_has_id = eth_has_id, + .poa_match = eth_match, + .poa_link_match = eth_link_match, + .mpl = POA_ETH_MPL +}; |
