[RFC PATCH ethtool v2 04/23] netlink: add support for string sets

From: Michal Kubecek
Date: Mon Jul 30 2018 - 08:58:48 EST


Add infrastructure for querying kernel string sets (analog to ioct commands
ETHTOOL_GSSET_INFO and ETHTOOL_GSTRINGS).

Let notification monitor request string sets on start so that it can
resolve bit indices to names when processing bitsets in compact format.

Signed-off-by: Michal Kubecek <mkubecek@xxxxxxx>
---
Makefile.am | 1 +
netlink/strset.c | 265 +++++++++++++++++++++++++++++++++++++++++++++++
netlink/strset.h | 16 +++
3 files changed, 282 insertions(+)
create mode 100644 netlink/strset.c
create mode 100644 netlink/strset.h

diff --git a/Makefile.am b/Makefile.am
index bdb4f4df2b0f..6a4d7083919e 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -22,6 +22,7 @@ endif
if ETHTOOL_ENABLE_NETLINK
ethtool_SOURCES += \
netlink/netlink.c netlink/netlink.h netlink/extapi.h \
+ netlink/strset.c netlink/strset.h \
uapi/linux/ethtool_netlink.h \
uapi/linux/netlink.h uapi/linux/genetlink.h
ethtool_CFLAGS += @MNL_CFLAGS@
diff --git a/netlink/strset.c b/netlink/strset.c
new file mode 100644
index 000000000000..9ea456748531
--- /dev/null
+++ b/netlink/strset.c
@@ -0,0 +1,265 @@
+#include <errno.h>
+#include <string.h>
+
+#include "../internal.h"
+#include "netlink.h"
+
+struct stringset {
+ const char **strings;
+ void *raw_data;
+ unsigned int count;
+};
+
+struct perdev_strings {
+ char devname[IFNAMSIZ];
+ struct stringset strings[ETH_SS_MAX + 1];
+ struct perdev_strings *next;
+};
+
+/* universal string sets */
+static struct stringset global_strings[ETH_SS_MAX + 1];
+/* linked list of string sets related to network devices */
+static struct perdev_strings *device_strings;
+
+static void drop_stringset(struct stringset *set)
+{
+ free(set->strings);
+ free(set->raw_data);
+ set->count = 0;
+}
+
+static int import_stringset(struct stringset *dest, const struct nlattr *nest)
+{
+ const struct nlattr *tb_stringset[ETHA_STRINGSET_MAX + 1] = {};
+ DECLARE_ATTR_TB_INFO(tb_stringset);
+ const struct nlattr *string;
+ unsigned int size;
+ unsigned int count;
+ unsigned int idx;
+ int ret;
+
+ ret = mnl_attr_parse_nested(nest, attr_cb, &tb_stringset_info);
+ if (ret < 0)
+ return ret;
+ if (!tb_stringset[ETHA_STRINGSET_ID] ||
+ !tb_stringset[ETHA_STRINGSET_COUNT] ||
+ !tb_stringset[ETHA_STRINGSET_STRINGS])
+ return -EFAULT;
+ idx = mnl_attr_get_u32(tb_stringset[ETHA_STRINGSET_ID]);
+ if (idx > ETH_SS_MAX)
+ return 0;
+ count = mnl_attr_get_u32(tb_stringset[ETHA_STRINGSET_COUNT]);
+ if (count == 0)
+ return 0;
+
+ size = mnl_attr_get_len(tb_stringset[ETHA_STRINGSET_STRINGS]);
+ ret = -ENOMEM;
+ dest[idx].raw_data = malloc(size);
+ if (!dest[idx].raw_data)
+ goto err;
+ memcpy(dest[idx].raw_data, tb_stringset[ETHA_STRINGSET_STRINGS], size);
+ dest[idx].strings = malloc(count * sizeof(dest[idx].strings[0]));
+ if (!dest[idx].strings)
+ goto err;
+ dest[idx].count = count;
+
+ nest = dest[idx].raw_data;
+ mnl_attr_for_each_nested(string, nest) {
+ const struct nlattr *tb[ETHA_STRING_MAX + 1] = {};
+ DECLARE_ATTR_TB_INFO(tb);
+ unsigned int i;
+
+ if (mnl_attr_get_type(string) != ETHA_STRINGS_STRING)
+ continue;
+ ret = mnl_attr_parse_nested(string, attr_cb, &tb_info);
+ if (ret < 0)
+ goto err;
+ ret = -EFAULT;
+ if (!tb[ETHA_STRING_INDEX] || !tb[ETHA_STRING_VALUE])
+ goto err;
+
+ i = mnl_attr_get_u32(tb[ETHA_STRING_INDEX]);
+ if (i >= count)
+ goto err;
+ dest[idx].strings[i] =
+ mnl_attr_get_payload(tb[ETHA_STRING_VALUE]);
+ }
+
+ return 0;
+err:
+ drop_stringset(&dest[idx]);
+ return ret;
+}
+
+static const char *stringset_names[] = {
+ [ETH_SS_TEST] = "test",
+ [ETH_SS_STATS] = "stats",
+ [ETH_SS_PRIV_FLAGS] = "priv-flags",
+ [ETH_SS_NTUPLE_FILTERS] = "ntuple-filters",
+ [ETH_SS_FEATURES] = "features",
+ [ETH_SS_RSS_HASH_FUNCS] = "rss-hash-funcs",
+ [ETH_SS_TUNABLES] = "tunables",
+ [ETH_SS_PHY_STATS] = "phy-stats",
+ [ETH_SS_PHY_TUNABLES] = "phy-tunables",
+ [ETH_SS_LINK_MODES] = "link-modes",
+};
+
+void debug_stringsets(const struct stringset *sets)
+{
+ unsigned int i;
+
+ for (i = 0; i <= ETH_SS_MAX; i++) {
+ if (sets[i].count > 0) {
+ printf(" set %s, count %u\n", stringset_names[i],
+ sets[i].count);
+ }
+ }
+}
+
+void debug_strings()
+{
+ struct perdev_strings *pd;
+
+ fputs("global strings:\n", stdout);
+ debug_stringsets(global_strings);
+
+ for (pd = device_strings; pd; pd = pd->next) {
+ printf("strings for %s:\n", pd->devname);
+ debug_stringsets(pd->strings);
+ }
+}
+
+static int strset_reply_cb(const struct nlmsghdr *nlhdr, void *data)
+{
+ const struct nlattr *tb[ETHA_STRSET_MAX + 1] = {};
+ DECLARE_ATTR_TB_INFO(tb);
+ struct nl_context *nlctx = data;
+ struct stringset *dest;
+ struct nlattr *attr;
+ unsigned int i;
+ int ret;
+
+ ret = mnl_attr_parse(nlhdr, GENL_HDRLEN, attr_cb, &tb_info);
+ if (ret < 0)
+ return ret;
+ nlctx->devname = get_dev_name(tb[ETHA_STRSET_DEV]);
+ if (!dev_ok(nlctx))
+ return MNL_CB_OK;
+
+ if (nlctx->devname) {
+ struct perdev_strings *perdev = device_strings;
+
+ while (perdev && strcmp(perdev->devname, nlctx->devname))
+ perdev = perdev->next;
+ if (perdev) {
+ for (i = 0; i <= ETH_SS_MAX; i++)
+ drop_stringset(&perdev->strings[i]);
+ } else {
+ perdev = calloc(sizeof(*perdev), 1);
+ if (!perdev)
+ return -ENOMEM;
+ strncpy(perdev->devname, nlctx->devname, IFNAMSIZ);
+ perdev->devname[IFNAMSIZ - 1] = '\0';
+ perdev->next = device_strings;
+ device_strings = perdev;
+ }
+ dest = perdev->strings;
+ } else {
+ for (i = 0; i <= ETH_SS_MAX; i++)
+ drop_stringset(&global_strings[i]);
+ dest = global_strings;
+ }
+
+ mnl_attr_for_each(attr, nlhdr, GENL_HDRLEN) {
+ if (mnl_attr_get_type(attr) == ETHA_STRSET_STRINGSET)
+ import_stringset(dest, attr);
+ }
+
+ return MNL_CB_OK;
+}
+
+/* interface */
+
+const struct stringset *global_stringset(unsigned int type)
+{
+ if (type > ETH_SS_MAX)
+ return NULL;
+ return &global_strings[type];
+}
+
+const struct stringset *perdev_stringset(const char *dev, unsigned int type)
+{
+ const struct perdev_strings *p;
+
+ if (type > ETH_SS_MAX)
+ return NULL;
+ for (p = device_strings; p; p = p->next)
+ if (!strcmp(p->devname, dev))
+ return &p->strings[type];
+
+ return NULL;
+}
+
+unsigned int get_count(const struct stringset *set)
+{
+ return set->count;
+}
+
+const char *get_string(const struct stringset *set, unsigned int idx)
+{
+ if (idx > set->count)
+ return NULL;
+ return set->strings[idx];
+}
+
+int load_global_strings(struct nl_context *nlctx)
+{
+ int ret;
+
+ ret = msg_init(nlctx, ETHNL_CMD_GET_STRSET, NLM_F_REQUEST | NLM_F_ACK);
+ if (ret < 0)
+ return ret;
+ ret = ethnl_send_get_request(nlctx, strset_reply_cb);
+ return ret;
+}
+
+int load_perdev_strings(struct nl_context *nlctx, const char *dev)
+{
+ int ret;
+
+ ret = msg_init(nlctx, ETHNL_CMD_GET_STRSET,
+ NLM_F_REQUEST | NLM_F_ACK | (dev ? 0 : NLM_F_DUMP));
+ if (ret < 0)
+ return ret;
+ if (dev) {
+ if (ethnla_put_dev(nlctx, ETHA_STRSET_DEV, dev))
+ return -EMSGSIZE;
+ }
+ ret = ethnl_send_get_request(nlctx, strset_reply_cb);
+ return ret;
+}
+
+void free_perdev_strings(const char *devname)
+{
+ struct perdev_strings **p = &device_strings;
+ unsigned int i;
+
+ p = &device_strings;
+ while (*p) {
+ struct perdev_strings *perdev = *p;
+
+ if (devname && strcmp(perdev->devname, devname)) {
+ p = &((*p)->next);
+ continue;
+ }
+ *p = perdev->next;
+ for (i = 0; i <= ETH_SS_MAX; i++)
+ drop_stringset(&perdev->strings[i]);
+ free(perdev);
+ }
+
+ if (!devname) {
+ for (i = 0; i <= ETH_SS_MAX; i++)
+ drop_stringset(&global_strings[i]);
+ }
+}
diff --git a/netlink/strset.h b/netlink/strset.h
new file mode 100644
index 000000000000..81ddf33a5d15
--- /dev/null
+++ b/netlink/strset.h
@@ -0,0 +1,16 @@
+#ifndef ETHTOOL_NETLINK_STRSET_H__
+#define ETHTOOL_NETLINK_STRSET_H__
+
+struct stringset;
+
+const struct stringset *global_stringset(unsigned int type);
+const struct stringset *perdev_stringset(const char *dev, unsigned int type);
+
+unsigned int get_count(const struct stringset *set);
+const char *get_string(const struct stringset *set, unsigned int idx);
+
+int load_global_strings(struct nl_context *nlctx);
+int load_perdev_strings(struct nl_context *nlctx, const char *dev);
+void free_perdev_strings(const char *devname);
+
+#endif /* ETHTOOL_NETLINK_STRSET_H__ */
--
2.18.0