@@ -48,4 +48,48 @@ struct ethtool_channels {
__u32 combined_count;
};
+#define ETH_GSTRING_LEN 32
+
+/**
+ * enum ethtool_stringset - string set ID
+ * @ETH_SS_TEST: Self-test result names, for use with %ETHTOOL_TEST
+ * @ETH_SS_STATS: Statistic names, for use with %ETHTOOL_GSTATS
+ * @ETH_SS_PRIV_FLAGS: Driver private flag names, for use with
+ * %ETHTOOL_GPFLAGS and %ETHTOOL_SPFLAGS
+ * @ETH_SS_NTUPLE_FILTERS: Previously used with %ETHTOOL_GRXNTUPLE;
+ * now deprecated
+ * @ETH_SS_FEATURES: Device feature names
+ * @ETH_SS_RSS_HASH_FUNCS: RSS hush function names
+ * @ETH_SS_PHY_STATS: Statistic names, for use with %ETHTOOL_GPHYSTATS
+ * @ETH_SS_PHY_TUNABLES: PHY tunable names
+ * @ETH_SS_LINK_MODES: link mode names
+ * @ETH_SS_MSG_CLASSES: debug message class names
+ * @ETH_SS_WOL_MODES: wake-on-lan modes
+ * @ETH_SS_SOF_TIMESTAMPING: SOF_TIMESTAMPING_* flags
+ * @ETH_SS_TS_TX_TYPES: timestamping Tx types
+ * @ETH_SS_TS_RX_FILTERS: timestamping Rx filters
+ * @ETH_SS_UDP_TUNNEL_TYPES: UDP tunnel types
+ */
+enum ethtool_stringset {
+ ETH_SS_TEST = 0,
+ ETH_SS_STATS,
+ ETH_SS_PRIV_FLAGS,
+ ETH_SS_NTUPLE_FILTERS,
+ ETH_SS_FEATURES,
+ ETH_SS_RSS_HASH_FUNCS,
+ ETH_SS_TUNABLES,
+ ETH_SS_PHY_STATS,
+ ETH_SS_PHY_TUNABLES,
+ ETH_SS_LINK_MODES,
+ ETH_SS_MSG_CLASSES,
+ ETH_SS_WOL_MODES,
+ ETH_SS_SOF_TIMESTAMPING,
+ ETH_SS_TS_TX_TYPES,
+ ETH_SS_TS_RX_FILTERS,
+ ETH_SS_UDP_TUNNEL_TYPES,
+
+ /* add new constants above here */
+ ETH_SS_COUNT
+};
+
#endif /* _UAPI_LINUX_ETHTOOL_H */
new file mode 100644
@@ -0,0 +1,49 @@
+/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */
+
+/*
+ * Generic netlink ethtool family required defines
+ *
+ * Copyright (c) 2020 Intel
+ */
+
+#ifndef __LIBBPF_ETHTOOL_H_
+#define __LIBBPF_ETHTOOL_H_
+
+#include <linux/ethtool_netlink.h>
+
+#define DIV_ROUND_UP(n, d) (((n) + (d) - 1) / (d))
+#define FEATURE_BITS_TO_BLOCKS(n_bits) DIV_ROUND_UP(n_bits, 32U)
+
+#define FEATURE_WORD(blocks, index) ((blocks)[(index) / 32U])
+#define FEATURE_FIELD_FLAG(index) (1U << (index) % 32U)
+#define FEATURE_BIT_IS_SET(blocks, index) \
+ (FEATURE_WORD(blocks, index) & FEATURE_FIELD_FLAG(index))
+
+#define NETDEV_XDP_STR "xdp"
+#define NETDEV_XDP_LEN 4
+
+#define NETDEV_AF_XDP_ZC_STR "af-xdp-zc"
+#define NETDEV_AF_XDP_ZC_LEN 10
+
+#define BUF_SIZE_4096 4096
+#define BUF_SIZE_8192 8192
+
+#define MAX_FEATURES 500
+
+struct ethnl_params {
+ const char *ifname;
+ const char *nl_family;
+ int features;
+ int xdp_idx;
+ int xdp_zc_idx;
+ int xdp_flags;
+ int xdp_zc_flags;
+ __u16 fam_id;
+};
+
+int libbpf_ethnl_get_ethtool_family_id(struct ethnl_params *param);
+int libbpf_ethnl_get_netdev_features(struct ethnl_params *param);
+int libbpf_ethnl_get_active_bits(struct ethnl_params *param);
+
+#endif /* __LIBBPF_ETHTOOL_H_ */
+
@@ -41,6 +41,7 @@ enum libbpf_errno {
LIBBPF_ERRNO__WRNGPID, /* Wrong pid in netlink message */
LIBBPF_ERRNO__INVSEQ, /* Invalid netlink sequence */
LIBBPF_ERRNO__NLPARSE, /* netlink parsing error */
+ LIBBPF_ERRNO__INVXDP, /* Invalid XDP data */
__LIBBPF_ERRNO__END,
};
@@ -6,6 +6,8 @@
#include <unistd.h>
#include <linux/bpf.h>
#include <linux/rtnetlink.h>
+#include <linux/genetlink.h>
+#include <linux/if.h>
#include <sys/socket.h>
#include <errno.h>
#include <time.h>
@@ -14,6 +16,7 @@
#include "libbpf.h"
#include "libbpf_internal.h"
#include "nlattr.h"
+#include "ethtool.h"
#ifndef SOL_NETLINK
#define SOL_NETLINK 270
@@ -23,6 +26,11 @@ typedef int (*libbpf_dump_nlmsg_t)(void *cookie, void *msg, struct nlattr **tb);
typedef int (*__dump_nlmsg_t)(struct nlmsghdr *nlmsg, libbpf_dump_nlmsg_t,
void *cookie);
+struct ethnl_msg {
+ struct nlmsghdr nlh;
+ struct genlmsghdr genlhdr;
+ char msg[BUF_SIZE_4096];
+};
struct xdp_id_md {
int ifindex;
@@ -30,7 +38,7 @@ struct xdp_id_md {
struct xdp_link_info info;
};
-static int libbpf_netlink_open(__u32 *nl_pid)
+static int libbpf_netlink_open(__u32 *nl_pid, int protocol)
{
struct sockaddr_nl sa;
socklen_t addrlen;
@@ -40,7 +48,7 @@ static int libbpf_netlink_open(__u32 *nl_pid)
memset(&sa, 0, sizeof(sa));
sa.nl_family = AF_NETLINK;
- sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
+ sock = socket(AF_NETLINK, SOCK_RAW, protocol);
if (sock < 0)
return -errno;
@@ -143,7 +151,7 @@ static int __bpf_set_link_xdp_fd_replace(int ifindex, int fd, int old_fd,
} req;
__u32 nl_pid = 0;
- sock = libbpf_netlink_open(&nl_pid);
+ sock = libbpf_netlink_open(&nl_pid, NETLINK_ROUTE);
if (sock < 0)
return sock;
@@ -302,7 +310,7 @@ int bpf_get_link_xdp_info(int ifindex, struct xdp_link_info *info,
if (flags && flags & mask)
return -EINVAL;
- sock = libbpf_netlink_open(&nl_pid);
+ sock = libbpf_netlink_open(&nl_pid, NETLINK_ROUTE);
if (sock < 0)
return sock;
@@ -370,3 +378,366 @@ int libbpf_nl_get_link(int sock, unsigned int nl_pid,
return bpf_netlink_recv(sock, nl_pid, seq, __dump_link_nlmsg,
dump_link_nlmsg, cookie);
}
+
+static int libbpf_ethtool_parse_feature_strings(struct nlattr *start, int elem,
+ int *xdp, int *xdp_zc)
+{
+ struct nlattr *tbs[__ETHTOOL_A_STRING_CNT + 1];
+ struct nlattr *tab[elem > 0 ? elem : 0];
+ struct libbpf_nla_policy policy[] = {
+ [ETHTOOL_A_STRING_UNSPEC] = {
+ .type = LIBBPF_NLA_UNSPEC,
+ .minlen = 0,
+ .maxlen = 0,
+ },
+ [ETHTOOL_A_STRING_INDEX] = {
+ .type = LIBBPF_NLA_U32,
+ .minlen = sizeof(uint32_t),
+ .maxlen = sizeof(uint32_t),
+ },
+ [ETHTOOL_A_STRING_VALUE] = {
+ .type = LIBBPF_NLA_STRING,
+ .minlen = 1,
+ .maxlen = ETH_GSTRING_LEN,
+ }
+ };
+ const char *f;
+ int n = 0;
+ __u32 v;
+ int ret;
+ int i;
+
+ if (!xdp || !xdp_zc || !start || elem <= 0)
+ return -EINVAL;
+
+ *xdp = -1;
+ *xdp_zc = -1;
+
+ ret = libbpf_nla_parse_table(tab, elem, start, 0, NULL);
+ if (ret)
+ goto cleanup;
+
+ for (i = 0; tab[i] && i < elem; ++i) {
+ ret = libbpf_nla_parse_nested(tbs, __ETHTOOL_A_STRING_CNT, tab[i], policy);
+ if (ret)
+ break;
+
+ if (tbs[ETHTOOL_A_STRING_INDEX] && tbs[ETHTOOL_A_STRING_VALUE]) {
+ f = libbpf_nla_getattr_str(tbs[ETHTOOL_A_STRING_VALUE]);
+ v = libbpf_nla_getattr_u32(tbs[ETHTOOL_A_STRING_INDEX]);
+
+ if (!strncmp(NETDEV_XDP_STR, f, NETDEV_XDP_LEN)) {
+ *xdp = v;
+ n++;
+ }
+
+ if (!strncmp(NETDEV_AF_XDP_ZC_STR, f, NETDEV_AF_XDP_ZC_LEN)) {
+ *xdp_zc = v;
+ n++;
+ }
+ } else {
+ ret = -LIBBPF_ERRNO__NLPARSE;
+ break;
+ }
+ }
+
+cleanup:
+ /* If error occurred return it. */
+ if (ret)
+ return ret;
+
+ /*
+ * If zero or two xdp flags found that is okay.
+ * Zero means older kernel without any xdp flags added.
+ * Two means newer kernel with xdp flags added.
+ * Both flags were added in single commit, so that
+ * n == 1 is a faulty value.
+ */
+ if (n == 2 || n == 0)
+ return 0;
+
+ /* If no error and one or more than 2 xdp flags found return error */
+ return -LIBBPF_ERRNO__INVXDP;
+}
+
+static int libbpf_ethnl_send(int sock, __u32 seq, __u32 nl_pid, struct ethnl_msg *req)
+{
+ ssize_t written;
+
+ req->nlh.nlmsg_pid = nl_pid;
+ req->nlh.nlmsg_seq = seq;
+
+ written = send(sock, req, req->nlh.nlmsg_len, 0);
+ if (written < 0)
+ return -errno;
+
+ if (written == req->nlh.nlmsg_len)
+ return 0;
+ else
+ return -errno;
+}
+
+static int libbpf_ethnl_validate(int len, __u16 fam_id, __u32 nl_pid, __u32 seq,
+ struct ethnl_msg *req)
+{
+ if (!NLMSG_OK(&req->nlh, (unsigned int)len))
+ return -ENOMSG;
+
+ if (req->nlh.nlmsg_pid != nl_pid)
+ return -LIBBPF_ERRNO__WRNGPID;
+
+ if (req->nlh.nlmsg_seq != seq)
+ return -LIBBPF_ERRNO__INVSEQ;
+
+ if (req->nlh.nlmsg_type != fam_id) {
+ int ret = -ENOMSG;
+
+ if (req->nlh.nlmsg_type == NLMSG_ERROR) {
+ struct nlmsgerr *err = (struct nlmsgerr *)&req->genlhdr;
+
+ if (err->error)
+ ret = err->error;
+ libbpf_nla_dump_errormsg(&req->nlh);
+ }
+ return ret;
+ }
+
+ return 0;
+}
+
+static int libbpf_ethnl_send_recv(struct ethnl_msg *req, struct ethnl_params *param)
+{
+ __u32 nl_pid;
+ __u32 seq;
+ int sock;
+ int ret;
+ int len;
+
+ sock = libbpf_netlink_open(&nl_pid, NETLINK_GENERIC);
+ if (sock < 0) {
+ ret = sock;
+ goto cleanup;
+ }
+
+ seq = time(NULL);
+ ret = libbpf_ethnl_send(sock, seq, nl_pid, req);
+ if (ret)
+ goto cleanup;
+
+ len = recv(sock, req, sizeof(struct ethnl_msg), 0);
+ if (len < 0) {
+ ret = -errno;
+ goto cleanup;
+ }
+
+ ret = libbpf_ethnl_validate(len, param->fam_id, nl_pid, seq, req);
+ if (ret < 0)
+ goto cleanup;
+
+ ret = len;
+
+cleanup:
+ if (sock >= 0)
+ close(sock);
+
+ return ret;
+}
+
+int libbpf_ethnl_get_netdev_features(struct ethnl_params *param)
+{
+ struct ethnl_msg req = {
+ .nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct genlmsghdr)),
+ .nlh.nlmsg_flags = NLM_F_REQUEST,
+ .nlh.nlmsg_type = param->fam_id,
+ .nlh.nlmsg_pid = 0,
+ .genlhdr.version = ETHTOOL_GENL_VERSION,
+ .genlhdr.cmd = ETHTOOL_MSG_STRSET_GET,
+ .genlhdr.reserved = 0,
+ };
+ struct nlattr *tbn[__ETHTOOL_A_STRINGSETS_CNT + 1];
+ struct nlattr *tbnn[__ETHTOOL_A_STRINGSET_CNT + 1];
+ struct nlattr *tb[__ETHTOOL_A_STRSET_CNT + 1];
+ struct nlattr *nla, *nla_next, *nla_set;
+ int string_set = ETH_SS_FEATURES;
+ int ret;
+ int len;
+
+ memset(&req.msg, 0, BUF_SIZE_4096);
+
+ nla = (struct nlattr *)req.msg;
+ nla_next = libbpf_nla_nest_start(nla, ETHTOOL_A_STRSET_HEADER);
+ nla_next = libbpf_nla_put_str(nla_next, ETHTOOL_A_HEADER_DEV_NAME,
+ param->ifname, IFNAMSIZ);
+ libbpf_nla_nest_end(nla, nla_next);
+
+ nla = nla_next;
+ nla_set = libbpf_nla_nest_start(nla, ETHTOOL_A_STRSET_STRINGSETS);
+ nla_next = libbpf_nla_nest_start(nla_set, ETHTOOL_A_STRINGSETS_STRINGSET);
+ nla_next = libbpf_nla_put_u32(nla_next, ETHTOOL_A_STRINGSET_ID, string_set);
+ libbpf_nla_nest_end(nla_set, nla_next);
+ libbpf_nla_nest_end(nla, nla_next);
+ if (!param->features)
+ nla_next = libbpf_nla_put_flag(nla_next, ETHTOOL_A_STRSET_COUNTS_ONLY);
+
+ req.nlh.nlmsg_len += libbpf_nla_attrs_length((struct nlattr *)req.msg, nla_next);
+
+ len = libbpf_ethnl_send_recv(&req, param);
+ if (len < 0)
+ return len;
+
+ /* set parsing error, and change if succeeded */
+ ret = -LIBBPF_ERRNO__NLPARSE;
+ nla = (struct nlattr *)req.msg;
+ len = len - NLMSG_HDRLEN - GENL_HDRLEN;
+
+ if (libbpf_nla_parse(tb, __ETHTOOL_A_STRSET_CNT, nla, len, NULL))
+ return ret;
+
+ if (!tb[ETHTOOL_A_STRSET_STRINGSETS])
+ return ret;
+
+ if (libbpf_nla_parse_nested(tbn, __ETHTOOL_A_STRINGSETS_CNT,
+ tb[ETHTOOL_A_STRSET_STRINGSETS], NULL))
+ return ret;
+
+ if (!tbn[ETHTOOL_A_STRINGSETS_STRINGSET])
+ return ret;
+
+ if (libbpf_nla_parse_nested(tbnn, __ETHTOOL_A_STRINGSET_CNT,
+ tbn[ETHTOOL_A_STRINGSETS_STRINGSET], NULL))
+ return ret;
+
+ if (param->features == 0) {
+ if (!tbnn[ETHTOOL_A_STRINGSET_COUNT])
+ return ret;
+
+ param->features = libbpf_nla_getattr_u32(tbnn[ETHTOOL_A_STRINGSET_COUNT]);
+
+ /* success */
+ ret = 0;
+ } else if (param->features > 0) {
+ if (!tbnn[ETHTOOL_A_STRINGSET_STRINGS])
+ return ret;
+
+ /*
+ * Upper boundary is known, but it is input from socket stream.
+ * Let's perform upper limit check anyway, and limit it up to
+ * MAX_FEATURES (which is still far more than is actually needed).
+ */
+ if (param->features > MAX_FEATURES)
+ param->features = MAX_FEATURES;
+
+ /* success if returns 0 */
+ ret = libbpf_ethtool_parse_feature_strings(tbnn[ETHTOOL_A_STRINGSET_STRINGS],
+ param->features, ¶m->xdp_idx,
+ ¶m->xdp_zc_idx);
+ }
+
+ return ret;
+}
+
+int libbpf_ethnl_get_ethtool_family_id(struct ethnl_params *param)
+{
+ struct ethnl_msg req = {
+ .nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct genlmsghdr)),
+ .nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK,
+ .nlh.nlmsg_type = GENL_ID_CTRL,
+ .nlh.nlmsg_pid = 0,
+ .genlhdr.version = ETHTOOL_GENL_VERSION,
+ .genlhdr.cmd = CTRL_CMD_GETFAMILY,
+ .genlhdr.reserved = 0,
+ };
+ struct nlattr *tb[__CTRL_ATTR_MAX + 1] = {0};
+ struct nlattr *nla, *nla_next;
+ int ret = -1;
+ int len;
+
+ memset(&req.msg, 0, BUF_SIZE_4096);
+ param->fam_id = GENL_ID_CTRL;
+
+ nla = (struct nlattr *)req.msg;
+ nla_next = libbpf_nla_put_str(nla, CTRL_ATTR_FAMILY_NAME, param->nl_family, GENL_NAMSIZ);
+ req.nlh.nlmsg_len += libbpf_nla_attrs_length(nla, nla_next);
+
+ len = libbpf_ethnl_send_recv(&req, param);
+ if (len < 0)
+ return len;
+
+ /* set parsing error, and change if succeeded */
+ ret = -LIBBPF_ERRNO__NLPARSE;
+ len = len - NLMSG_HDRLEN - GENL_HDRLEN;
+ if (!libbpf_nla_parse(tb, __CTRL_ATTR_MAX, nla, len, NULL)) {
+ if (tb[CTRL_ATTR_FAMILY_ID]) {
+ param->fam_id = libbpf_nla_getattr_u16(tb[CTRL_ATTR_FAMILY_ID]);
+ ret = 0;
+ }
+ }
+
+ return ret;
+}
+
+int libbpf_ethnl_get_active_bits(struct ethnl_params *param)
+{
+ struct ethnl_msg req = {
+ .nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct genlmsghdr)),
+ .nlh.nlmsg_flags = NLM_F_REQUEST,
+ .nlh.nlmsg_type = param->fam_id,
+ .nlh.nlmsg_pid = 0,
+ .genlhdr.cmd = ETHTOOL_MSG_FEATURES_GET,
+ .genlhdr.version = ETHTOOL_GENL_VERSION,
+ .genlhdr.reserved = 0,
+ };
+ __u32 active[FEATURE_BITS_TO_BLOCKS(param->features)];
+ struct nlattr *tb[__ETHTOOL_A_FEATURES_CNT + 1];
+ struct nlattr *tbn[__ETHTOOL_A_BITSET_CNT + 1];
+ int flags = ETHTOOL_FLAG_COMPACT_BITSETS;
+ struct nlattr *nla, *nla_next;
+ int ret = -1;
+ int len;
+
+ memset(&req.msg, 0, BUF_SIZE_4096);
+
+ nla = (struct nlattr *)req.msg;
+ nla_next = libbpf_nla_nest_start(nla, ETHTOOL_A_FEATURES_HEADER);
+ nla_next = libbpf_nla_put_str(nla_next, ETHTOOL_A_HEADER_DEV_NAME, param->ifname, IFNAMSIZ);
+ nla_next = libbpf_nla_put_u32(nla_next, ETHTOOL_A_HEADER_FLAGS, flags);
+ libbpf_nla_nest_end(nla, nla_next);
+ req.nlh.nlmsg_len += libbpf_nla_attrs_length(nla, nla_next);
+
+ len = libbpf_ethnl_send_recv(&req, param);
+ if (len < 0)
+ return len;
+
+ ret = -LIBBPF_ERRNO__NLPARSE;
+ nla = (struct nlattr *)req.msg;
+ len = len - NLMSG_HDRLEN - GENL_HDRLEN;
+ if (libbpf_nla_parse(tb, __ETHTOOL_A_FEATURES_CNT, nla, len, NULL))
+ return ret;
+
+ if (!tb[ETHTOOL_A_FEATURES_ACTIVE])
+ return ret;
+
+ if (libbpf_nla_parse_nested(tbn, __ETHTOOL_A_BITSET_CNT,
+ tb[ETHTOOL_A_FEATURES_ACTIVE], NULL))
+ return ret;
+
+ if (!tbn[ETHTOOL_A_BITSET_VALUE])
+ return ret;
+
+ for (unsigned int i = 0; i < FEATURE_BITS_TO_BLOCKS(param->features); ++i)
+ active[i] = libbpf_nla_getattr_u32(tbn[ETHTOOL_A_BITSET_VALUE] + i);
+
+ /* mark successful parsing */
+ ret = 0;
+ if (FEATURE_BIT_IS_SET(active, param->xdp_idx)) {
+ param->xdp_flags = 1;
+ if (FEATURE_BIT_IS_SET(active, param->xdp_zc_idx))
+ param->xdp_zc_flags = 1;
+ } else {
+ /* zero copy without driver mode makes no sense */
+ if (FEATURE_BIT_IS_SET(active, param->xdp_zc_idx))
+ ret = -LIBBPF_ERRNO__INVXDP;
+ }
+
+ return ret;
+}