diff options
Diffstat (limited to '')
-rw-r--r-- | src/libknot/xdp/eth.c | 312 |
1 files changed, 312 insertions, 0 deletions
diff --git a/src/libknot/xdp/eth.c b/src/libknot/xdp/eth.c new file mode 100644 index 0000000..608eb20 --- /dev/null +++ b/src/libknot/xdp/eth.c @@ -0,0 +1,312 @@ +/* Copyright (C) 2023 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include <assert.h> +#include <bpf/libbpf.h> +#include <errno.h> +#include <ifaddrs.h> +#include <linux/ethtool.h> +#include <linux/if_link.h> +#include <linux/if_vlan.h> +#include <linux/sockios.h> +#include <net/if.h> +#include <stdlib.h> +#include <string.h> +#include <sys/ioctl.h> +#include <unistd.h> + +#include "contrib/openbsd/strlcpy.h" +#include "contrib/sockaddr.h" +#include "libknot/attribute.h" +#include "libknot/endian.h" +#include "libknot/errcode.h" +#include "libknot/xdp/eth.h" + +_public_ +int knot_eth_queues(const char *devname) +{ + if (devname == NULL) { + return KNOT_EINVAL; + } + + int fd = socket(AF_INET, SOCK_DGRAM, 0); + if (fd < 0) { + return knot_map_errno(); + } + + struct ethtool_channels ch = { + .cmd = ETHTOOL_GCHANNELS + }; + struct ifreq ifr = { + .ifr_data = (char *)&ch + }; + strlcpy(ifr.ifr_name, devname, IFNAMSIZ); + + int ret = ioctl(fd, SIOCETHTOOL, &ifr); + if (ret != 0) { + if (errno == EOPNOTSUPP) { + ret = 1; + } else { + ret = knot_map_errno(); + } + } else { + if (ch.combined_count == 0) { + ret = 1; + } else { + ret = ch.combined_count; + } + } + + close(fd); + return ret; +} + +_public_ +int knot_eth_rss(const char *devname, knot_eth_rss_conf_t **rss_conf) +{ + if (devname == NULL || rss_conf == NULL) { + return KNOT_EINVAL; + } + + struct ethtool_rxfh *ctx = NULL; + knot_eth_rss_conf_t *out = NULL; + int ret = KNOT_ERROR; + + int fd = socket(AF_INET, SOCK_DGRAM, 0); + if (fd < 0) { + return knot_map_errno(); + } + + struct ethtool_rxfh sizes = { + .cmd = ETHTOOL_GRSSH + }; + struct ifreq ifr = { + .ifr_data = (char *)&sizes + }; + strlcpy(ifr.ifr_name, devname, IFNAMSIZ); + + ret = ioctl(fd, SIOCETHTOOL, &ifr); + if (ret != 0) { + ret = knot_map_errno(); + goto finish; + } + + const unsigned data_size = sizes.indir_size * sizeof(sizes.rss_config[0]) + + sizes.key_size; + + ctx = calloc(1, sizeof(*ctx) + data_size); + if (ctx == NULL) { + ret = KNOT_ENOMEM; + goto finish; + } + ctx->cmd = ETHTOOL_GRSSH; + ctx->indir_size = sizes.indir_size; + ctx->key_size = sizes.key_size; + ifr.ifr_data = (char *)ctx; + + ret = ioctl(fd, SIOCETHTOOL, &ifr); + if (ret != 0) { + ret = knot_map_errno(); + goto finish; + } + + out = calloc(1, sizeof(*out) + data_size); + if (out == NULL) { + ret = KNOT_ENOMEM; + goto finish; + } + + out->table_size = sizes.indir_size; + out->key_size = sizes.key_size; + memcpy(out->data, ctx->rss_config, data_size); + out->mask = out->table_size - 1; +finish: + *rss_conf = out; + + free(ctx); + close(fd); + return ret; +} + +_public_ +int knot_eth_mtu(const char *devname) +{ + if (devname == NULL) { + return KNOT_EINVAL; + } + + int fd = socket(AF_INET, SOCK_DGRAM, 0); + if (fd < 0) { + return knot_map_errno(); + } + + struct ifreq ifr = { 0 }; + strlcpy(ifr.ifr_name, devname, IFNAMSIZ); + + int ret = ioctl(fd, SIOCGIFMTU, &ifr); + if (ret != 0) { + if (errno == EOPNOTSUPP) { + ret = KNOT_ENOTSUP; + } else { + ret = knot_map_errno(); + } + } else { + ret = ifr.ifr_mtu; + } + + close(fd); + return ret; +} + +_public_ +int knot_eth_name_from_addr(const struct sockaddr_storage *addr, char *out, + size_t out_len) +{ + if (addr == NULL || out == NULL) { + return KNOT_EINVAL; + } + + struct ifaddrs *ifaces = NULL; + if (getifaddrs(&ifaces) != 0) { + return -errno; + } + + size_t matches = 0; + char *match_name = NULL; + + for (struct ifaddrs *ifa = ifaces; ifa != NULL; ifa = ifa->ifa_next) { + const struct sockaddr_storage *ifss = (struct sockaddr_storage *)ifa->ifa_addr; + if (ifss == NULL) { // Observed on interfaces without any address. + continue; + } + + if ((ifss->ss_family == addr->ss_family && sockaddr_is_any(addr)) || + sockaddr_cmp(ifss, addr, true) == 0) { + matches++; + match_name = ifa->ifa_name; + } + } + + if (matches == 1) { + size_t len = strlcpy(out, match_name, out_len); + freeifaddrs(ifaces); + return (len >= out_len) ? KNOT_ESPACE : KNOT_EOK; + } + + freeifaddrs(ifaces); + return matches == 0 ? KNOT_EADDRNOTAVAIL : KNOT_ELIMIT; +} + +_public_ +int knot_eth_vlans(uint16_t *vlan_map[], uint16_t *vlan_map_max) +{ + if (vlan_map == NULL || vlan_map_max == NULL) { + return KNOT_EINVAL; + } + + struct ifaddrs *ifaces = NULL; + if (getifaddrs(&ifaces) != 0) { + return knot_map_errno(); + } + + unsigned map_size = 0; + for (struct ifaddrs *ifa = ifaces; ifa != NULL; ifa = ifa->ifa_next) { + if (ifa->ifa_addr->sa_family != AF_PACKET) { + continue; + } + map_size++; + } + + uint16_t *map = calloc(sizeof(uint16_t), 1 + map_size); // Indexed from 1. + if (map == NULL) { + freeifaddrs(ifaces); + return KNOT_ENOMEM; + } + + int fd = socket(AF_INET, SOCK_DGRAM, 0); + if (fd < 0) { + free(map); + freeifaddrs(ifaces); + return knot_map_errno(); + } + + for (struct ifaddrs *ifa = ifaces; ifa != NULL; ifa = ifa->ifa_next) { + if (ifa->ifa_addr == NULL || ifa->ifa_addr->sa_family != AF_PACKET) { + continue; + } + + unsigned if_index = if_nametoindex(ifa->ifa_name); + if (if_index == 0) { + close(fd); + free(map); + freeifaddrs(ifaces); + return knot_map_errno(); + } + + struct vlan_ioctl_args ifv = { + .cmd = GET_VLAN_REALDEV_NAME_CMD + }; + strlcpy(ifv.device1, ifa->ifa_name, sizeof(ifv.device1)); + + if (ioctl(fd, SIOCGIFVLAN, &ifv) >= 0) { + memset(&ifv, 0, sizeof(ifv)); + ifv.cmd = GET_VLAN_VID_CMD; + strlcpy(ifv.device1, ifa->ifa_name, sizeof(ifv.device1)); + + if (ioctl(fd, SIOCGIFVLAN, &ifv) < 0) { + close(fd); + free(map); + freeifaddrs(ifaces); + return knot_map_errno(); + } + + map[if_index] = htobe16(ifv.u.VID); + } + } + + close(fd); + freeifaddrs(ifaces); + + *vlan_map = map; + *vlan_map_max = map_size; + + return KNOT_EOK; +} + +_public_ +knot_xdp_mode_t knot_eth_xdp_mode(int if_index) +{ +#if USE_LIBXDP + struct bpf_xdp_query_opts info = { .sz = sizeof(info) }; + int ret = bpf_xdp_query(if_index, 0, &info); +#else + struct xdp_link_info info; + int ret = bpf_get_link_xdp_info(if_index, &info, sizeof(info), 0); +#endif + if (ret != 0) { + return KNOT_XDP_MODE_NONE; + } + + switch (info.attach_mode) { + case XDP_ATTACHED_DRV: + case XDP_ATTACHED_HW: + return KNOT_XDP_MODE_FULL; + case XDP_ATTACHED_SKB: + return KNOT_XDP_MODE_EMUL; + default: + return KNOT_XDP_MODE_NONE; + } +} |