File netlink-add-netlink-handler-for-gfeatures-k.patch of Package ethtool

From: Michal Kubecek <mkubecek@suse.cz>
Date: Sun, 29 Jul 2018 16:20:40 +0200
Subject: netlink: add netlink handler for gfeatures (-k)
Patch-mainline: Not yet, work in progress
References: none

Implement "ethtool -k <dev>" subcommand using netlink interface command
ETHNL_CMD_GET_SETTINGS with ETH_SETTINGS_IM_FEATURES mask.

The implementation tries to emulate legacy flags but the result is not
exactly equal to the ioctl code. In particular, we map netdev features to
legacy flags based on name patterns in off_flag_def[] but this mapping is
slightly different from mapping used by kernel ioctl code.

Signed-off-by: Michal Kubecek <mkubecek@suse.cz>
---
 common.c           |  30 ++++++
 common.h           |  19 ++++
 ethtool.c          |  68 +++-----------
 netlink/extapi.h   |   1 +
 netlink/monitor.c  |   6 ++
 netlink/settings.c | 226 +++++++++++++++++++++++++++++++++++++++++++++
 6 files changed, 294 insertions(+), 56 deletions(-)

diff --git a/common.c b/common.c
index 962f3f9c318c..f52a1ba5f04c 100644
--- a/common.c
+++ b/common.c
@@ -320,3 +320,33 @@ void dump_mdix(u8 mdix, u8 mdix_ctrl)
 		fprintf(stdout, "\n");
 	}
 }
+
+const struct off_flag_def off_flag_def[OFF_FLAG_DEF_SIZE] = {
+	{ "rx",     "rx-checksumming",		    "rx-checksum",
+	  ETHTOOL_GRXCSUM, ETHTOOL_SRXCSUM, ETH_FLAG_RXCSUM,	0 },
+	{ "tx",     "tx-checksumming",		    "tx-checksum-*",
+	  ETHTOOL_GTXCSUM, ETHTOOL_STXCSUM, ETH_FLAG_TXCSUM,	0 },
+	{ "sg",     "scatter-gather",		    "tx-scatter-gather*",
+	  ETHTOOL_GSG,	   ETHTOOL_SSG,     ETH_FLAG_SG,	0 },
+	{ "tso",    "tcp-segmentation-offload",	    "tx-tcp*-segmentation",
+	  ETHTOOL_GTSO,	   ETHTOOL_STSO,    ETH_FLAG_TSO,	0 },
+	{ "ufo",    "udp-fragmentation-offload",    "tx-udp-fragmentation",
+	  ETHTOOL_GUFO,	   ETHTOOL_SUFO,    ETH_FLAG_UFO,	0 },
+	{ "gso",    "generic-segmentation-offload", "tx-generic-segmentation",
+	  ETHTOOL_GGSO,	   ETHTOOL_SGSO,    ETH_FLAG_GSO,	0 },
+	{ "gro",    "generic-receive-offload",	    "rx-gro",
+	  ETHTOOL_GGRO,	   ETHTOOL_SGRO,    ETH_FLAG_GRO,	0 },
+	{ "lro",    "large-receive-offload",	    "rx-lro",
+	  0,		   0,		    ETH_FLAG_LRO,
+	  KERNEL_VERSION(2,6,24) },
+	{ "rxvlan", "rx-vlan-offload",		    "rx-vlan-hw-parse",
+	  0,		   0,		    ETH_FLAG_RXVLAN,
+	  KERNEL_VERSION(2,6,37) },
+	{ "txvlan", "tx-vlan-offload",		    "tx-vlan-hw-insert",
+	  0,		   0,		    ETH_FLAG_TXVLAN,
+	  KERNEL_VERSION(2,6,37) },
+	{ "ntuple", "ntuple-filters",		    "rx-ntuple-filter",
+	  0,		   0,		    ETH_FLAG_NTUPLE,	0 },
+	{ "rxhash", "receive-hashing",		    "rx-hashing",
+	  0,		   0,		    ETH_FLAG_RXHASH,	0 },
+};
diff --git a/common.h b/common.h
index 37edac765e65..749bf389e9e1 100644
--- a/common.h
+++ b/common.h
@@ -34,6 +34,25 @@ struct link_mode_info {
 	u8			duplex;
 };
 
+struct off_flag_def {
+	const char *short_name;
+	const char *long_name;
+	const char *kernel_name;
+	u32 get_cmd, set_cmd;
+	u32 value;
+	/* For features exposed through ETHTOOL_GFLAGS, the oldest
+	 * kernel version for which we can trust the result.  Where
+	 * the flag was added at the same time the kernel started
+	 * supporting the feature, this is 0 (to allow for backports).
+	 * Where the feature was supported before the flag was added,
+	 * it is the version that introduced the flag.
+	 */
+	u32 min_kernel_ver;
+};
+
+#define OFF_FLAG_DEF_SIZE 12
+extern const struct off_flag_def off_flag_def[OFF_FLAG_DEF_SIZE];
+
 extern const struct link_mode_info link_modes[];
 extern const unsigned int link_modes_count;
 
diff --git a/ethtool.c b/ethtool.c
index 1a50958ea0d6..ea7afb12f405 100644
--- a/ethtool.c
+++ b/ethtool.c
@@ -102,51 +102,6 @@ struct cmdline_info {
 	void *seen_val;
 };
 
-struct off_flag_def {
-	const char *short_name;
-	const char *long_name;
-	const char *kernel_name;
-	u32 get_cmd, set_cmd;
-	u32 value;
-	/* For features exposed through ETHTOOL_GFLAGS, the oldest
-	 * kernel version for which we can trust the result.  Where
-	 * the flag was added at the same time the kernel started
-	 * supporting the feature, this is 0 (to allow for backports).
-	 * Where the feature was supported before the flag was added,
-	 * it is the version that introduced the flag.
-	 */
-	u32 min_kernel_ver;
-};
-static const struct off_flag_def off_flag_def[] = {
-	{ "rx",     "rx-checksumming",		    "rx-checksum",
-	  ETHTOOL_GRXCSUM, ETHTOOL_SRXCSUM, ETH_FLAG_RXCSUM,	0 },
-	{ "tx",     "tx-checksumming",		    "tx-checksum-*",
-	  ETHTOOL_GTXCSUM, ETHTOOL_STXCSUM, ETH_FLAG_TXCSUM,	0 },
-	{ "sg",     "scatter-gather",		    "tx-scatter-gather*",
-	  ETHTOOL_GSG,	   ETHTOOL_SSG,     ETH_FLAG_SG,	0 },
-	{ "tso",    "tcp-segmentation-offload",	    "tx-tcp*-segmentation",
-	  ETHTOOL_GTSO,	   ETHTOOL_STSO,    ETH_FLAG_TSO,	0 },
-	{ "ufo",    "udp-fragmentation-offload",    "tx-udp-fragmentation",
-	  ETHTOOL_GUFO,	   ETHTOOL_SUFO,    ETH_FLAG_UFO,	0 },
-	{ "gso",    "generic-segmentation-offload", "tx-generic-segmentation",
-	  ETHTOOL_GGSO,	   ETHTOOL_SGSO,    ETH_FLAG_GSO,	0 },
-	{ "gro",    "generic-receive-offload",	    "rx-gro",
-	  ETHTOOL_GGRO,	   ETHTOOL_SGRO,    ETH_FLAG_GRO,	0 },
-	{ "lro",    "large-receive-offload",	    "rx-lro",
-	  0,		   0,		    ETH_FLAG_LRO,
-	  KERNEL_VERSION(2,6,24) },
-	{ "rxvlan", "rx-vlan-offload",		    "rx-vlan-hw-parse",
-	  0,		   0,		    ETH_FLAG_RXVLAN,
-	  KERNEL_VERSION(2,6,37) },
-	{ "txvlan", "tx-vlan-offload",		    "tx-vlan-hw-insert",
-	  0,		   0,		    ETH_FLAG_TXVLAN,
-	  KERNEL_VERSION(2,6,37) },
-	{ "ntuple", "ntuple-filters",		    "rx-ntuple-filter",
-	  0,		   0,		    ETH_FLAG_NTUPLE,	0 },
-	{ "rxhash", "receive-hashing",		    "rx-hashing",
-	  0,		   0,		    ETH_FLAG_RXHASH,	0 },
-};
-
 struct feature_def {
 	char name[ETH_GSTRING_LEN];
 	int off_flag_index; /* index in off_flag_def; negative if none match */
@@ -155,7 +110,7 @@ struct feature_def {
 struct feature_defs {
 	size_t n_features;
 	/* Number of features each offload flag is associated with */
-	unsigned int off_flag_matched[ARRAY_SIZE(off_flag_def)];
+	unsigned int off_flag_matched[OFF_FLAG_DEF_SIZE];
 	/* Name and offload flag index for each feature */
 	struct feature_def def[0];
 };
@@ -1326,7 +1281,7 @@ static void dump_features(const struct feature_defs *defs,
 	int indent;
 	int i, j;
 
-	for (i = 0; i < ARRAY_SIZE(off_flag_def); i++) {
+	for (i = 0; i < OFF_FLAG_DEF_SIZE; i++) {
 		/* Don't show features whose state is unknown on this
 		 * kernel version
 		 */
@@ -1600,7 +1555,7 @@ static struct feature_defs *get_feature_defs(struct cmd_context *ctx)
 		defs->def[i].off_flag_index = -1;
 
 		for (j = 0;
-		     j < ARRAY_SIZE(off_flag_def) &&
+		     j < OFF_FLAG_DEF_SIZE &&
 			     defs->def[i].off_flag_index < 0;
 		     j++) {
 			const char *pattern =
@@ -2042,7 +1997,7 @@ get_features(struct cmd_context *ctx, const struct feature_defs *defs)
 
 	state->off_flags = 0;
 
-	for (i = 0; i < ARRAY_SIZE(off_flag_def); i++) {
+	for (i = 0; i < OFF_FLAG_DEF_SIZE; i++) {
 		value = off_flag_def[i].value;
 		if (!off_flag_def[i].get_cmd)
 			continue;
@@ -2158,14 +2113,14 @@ static int do_sfeatures(struct cmd_context *ctx)
 	/* Generate cmdline_info for legacy flags and kernel-named
 	 * features, and parse our arguments.
 	 */
-	cmdline_features = calloc(ARRAY_SIZE(off_flag_def) + defs->n_features,
+	cmdline_features = calloc(OFF_FLAG_DEF_SIZE + defs->n_features,
 				  sizeof(cmdline_features[0]));
 	if (!cmdline_features) {
 		perror("Cannot parse arguments");
 		rc = 1;
 		goto err;
 	}
-	for (i = 0; i < ARRAY_SIZE(off_flag_def); i++)
+	for (i = 0; i < OFF_FLAG_DEF_SIZE; i++)
 		flag_to_cmdline_info(off_flag_def[i].short_name,
 				     off_flag_def[i].value,
 				     &off_flags_wanted, &off_flags_mask,
@@ -2175,9 +2130,9 @@ static int do_sfeatures(struct cmd_context *ctx)
 			defs->def[i].name, FEATURE_FIELD_FLAG(i),
 			&FEATURE_WORD(efeatures->features, i, requested),
 			&FEATURE_WORD(efeatures->features, i, valid),
-			&cmdline_features[ARRAY_SIZE(off_flag_def) + i]);
+			&cmdline_features[OFF_FLAG_DEF_SIZE + i]);
 	parse_generic_cmdline(ctx, &any_changed, cmdline_features,
-			      ARRAY_SIZE(off_flag_def) + defs->n_features);
+			      OFF_FLAG_DEF_SIZE + defs->n_features);
 	free(cmdline_features);
 
 	if (!any_changed) {
@@ -2197,7 +2152,7 @@ static int do_sfeatures(struct cmd_context *ctx)
 		 * related features that the user did not specify and that
 		 * are not fixed.  Warn if all related features are fixed.
 		 */
-		for (i = 0; i < ARRAY_SIZE(off_flag_def); i++) {
+		for (i = 0; i < OFF_FLAG_DEF_SIZE; i++) {
 			int fixed = 1;
 
 			if (!(off_flags_mask & off_flag_def[i].value))
@@ -2238,7 +2193,7 @@ static int do_sfeatures(struct cmd_context *ctx)
 			goto err;
 		}
 	} else {
-		for (i = 0; i < ARRAY_SIZE(off_flag_def); i++) {
+		for (i = 0; i < OFF_FLAG_DEF_SIZE; i++) {
 			if (!off_flag_def[i].set_cmd)
 				continue;
 			if (off_flags_mask & off_flag_def[i].value) {
@@ -4886,6 +4841,7 @@ static int show_usage(struct cmd_context *ctx);
 #define nl_tsinfo	NULL
 #define nl_gset		NULL
 #define nl_sset		NULL
+#define nl_gfeatures	NULL
 #endif
 
 static const struct option {
@@ -4950,7 +4906,7 @@ static const struct option {
 	  "		[ rx-mini N ]\n"
 	  "		[ rx-jumbo N ]\n"
 	  "		[ tx N ]\n" },
-	{ "-k|--show-features|--show-offload", 1, do_gfeatures, NULL,
+	{ "-k|--show-features|--show-offload", 1, do_gfeatures, nl_gfeatures,
 	  "Get state of protocol offload and other features" },
 	{ "-K|--features|--offload", 1, do_sfeatures, NULL,
 	  "Set protocol offload and other features",
diff --git a/netlink/extapi.h b/netlink/extapi.h
index e3b8c6949f36..d955966da72d 100644
--- a/netlink/extapi.h
+++ b/netlink/extapi.h
@@ -18,6 +18,7 @@ int nl_permaddr(struct cmd_context *ctx);
 int nl_tsinfo(struct cmd_context *ctx);
 int nl_gset(struct cmd_context *ctx);
 int nl_sset(struct cmd_context *ctx);
+int nl_gfeatures(struct cmd_context *ctx);
 int nl_monitor(struct cmd_context *ctx);
 
 void monitor_usage();
diff --git a/netlink/monitor.c b/netlink/monitor.c
index 5d4f984be7c3..38d669ef58a6 100644
--- a/netlink/monitor.c
+++ b/netlink/monitor.c
@@ -127,6 +127,12 @@ static struct monitor_option monitor_opts[] = {
 				  ETH_SETTINGS_IM_WOLINFO |
 				  ETH_SETTINGS_IM_LINK,
 	},
+	{
+		.pattern	= "-k|--show-features|--show-offload|"
+				  "-K|--features|--offload",
+		.cmd		= ETHNL_CMD_SET_SETTINGS,
+		.info_mask	= ETH_SETTINGS_IM_FEATURES,
+	},
 };
 
 static bool pattern_match(const char *s, const char *pattern)
diff --git a/netlink/settings.c b/netlink/settings.c
index 04a49ff92a81..dcda1d63c1a7 100644
--- a/netlink/settings.c
+++ b/netlink/settings.c
@@ -5,6 +5,7 @@
 #include "../internal.h"
 #include "../common.h"
 #include "netlink.h"
+#include "strset.h"
 #include "parser.h"
 
 /* GET_SETTINGS */
@@ -34,6 +35,219 @@ err:
 	return ret;
 }
 
+uint32_t *get_compact_bitset_value(const struct nlattr *bitset)
+{
+        const struct nlattr *tb[ETHA_BITSET_MAX + 1] = {};
+        DECLARE_ATTR_TB_INFO(tb);
+	unsigned int count;
+        int ret;
+
+        ret = mnl_attr_parse_nested(bitset, attr_cb, &tb_info);
+        if (ret < 0 || !tb[ETHA_BITSET_SIZE] || !tb[ETHA_BITSET_VALUE])
+		return NULL;
+	count = mnl_attr_get_u32(tb[ETHA_BITSET_SIZE]);
+	if (32 * mnl_attr_get_payload_len(tb[ETHA_BITSET_VALUE]) < count)
+		return NULL;
+
+	return mnl_attr_get_payload(tb[ETHA_BITSET_VALUE]);
+}
+
+uint32_t *get_compact_bitset_mask(const struct nlattr *bitset)
+{
+        const struct nlattr *tb[ETHA_BITSET_MAX + 1] = {};
+        DECLARE_ATTR_TB_INFO(tb);
+	unsigned int count;
+        int ret;
+
+        ret = mnl_attr_parse_nested(bitset, attr_cb, &tb_info);
+        if (ret < 0 || !tb[ETHA_BITSET_SIZE] || !tb[ETHA_BITSET_MASK])
+		return NULL;
+	count = mnl_attr_get_u32(tb[ETHA_BITSET_SIZE]);
+	if (32 * mnl_attr_get_payload_len(tb[ETHA_BITSET_MASK]) < count)
+		return NULL;
+
+	return mnl_attr_get_payload(tb[ETHA_BITSET_MASK]);
+}
+
+struct feature_results {
+	uint32_t	*hw;
+	uint32_t	*wanted;
+	uint32_t	*active;
+	uint32_t	*nochange;
+	unsigned int	count;
+	unsigned int	words;
+};
+
+static int prepare_feature_results(const struct nlattr *bitset,
+				   struct feature_results *dest)
+{
+	const struct nlattr *tb[ETHA_FEATURES_MAX + 1] = {};
+	DECLARE_ATTR_TB_INFO(tb);
+	unsigned int count;
+	int ret;
+
+	memset(dest, '\0', sizeof(*dest));
+	ret = mnl_attr_parse_nested(bitset, attr_cb, &tb_info);
+	if (ret < 0)
+		return ret;
+	if (!tb[ETHA_FEATURES_HW] || !tb[ETHA_FEATURES_WANTED] ||
+	    !tb[ETHA_FEATURES_ACTIVE] || !tb[ETHA_FEATURES_NOCHANGE])
+		return -EFAULT;
+	count = bitset_get_count(tb[ETHA_FEATURES_HW], &ret);
+	if (ret < 0)
+		return -EFAULT;
+	if ((bitset_get_count(tb[ETHA_FEATURES_WANTED], &ret) != count) ||
+	    (bitset_get_count(tb[ETHA_FEATURES_ACTIVE], &ret) != count) ||
+	    (bitset_get_count(tb[ETHA_FEATURES_NOCHANGE], &ret) != count))
+		return -EFAULT;
+	dest->hw = get_compact_bitset_value(tb[ETHA_FEATURES_HW]);
+	dest->wanted = get_compact_bitset_value(tb[ETHA_FEATURES_WANTED]);
+	dest->active = get_compact_bitset_value(tb[ETHA_FEATURES_ACTIVE]);
+	dest->nochange = get_compact_bitset_value(tb[ETHA_FEATURES_NOCHANGE]);
+	if (!dest->hw || !dest->wanted || !dest->active || !dest->nochange)
+		return -EFAULT;
+	dest->count = count;
+	dest->words = (count + 31) / 32;
+
+	return 0;
+}
+
+static bool feature_on(const uint32_t *bitmap, unsigned int idx)
+{
+	return bitmap[idx / 32] & (1 << (idx % 32));
+}
+
+static void dump_feature(const struct feature_results *results,
+			 const uint32_t *ref, const uint32_t *ref_mask,
+			 unsigned int idx, const char *name, const char *prefix)
+{
+	const char *suffix = "";
+
+	if (!name || !*name)
+		return;
+	if (ref) {
+		if (ref_mask && !feature_on(ref_mask, idx))
+			return;
+		if ((!ref_mask || feature_on(ref_mask, idx)) &&
+		    (feature_on(results->active, idx) == feature_on(ref, idx)))
+			return;
+	}
+
+	if (!feature_on(results->hw, idx) || feature_on(results->nochange, idx))
+		suffix = " [fixed]";
+	else if (feature_on(results->active, idx) !=
+		 feature_on(results->wanted, idx))
+		suffix = feature_on(results->wanted, idx) ?
+			 " [requested on]" : " [requested off]";
+	printf("%s%s: %s%s\n", prefix, name,
+	       feature_on(results->active, idx) ? "on" : "off", suffix);
+}
+
+/* this assumes pattern contains no more than one asterisk */
+static bool _flag_pattern_match(const char *name, const char *pattern)
+{
+	const char *p_ast = strchr(pattern, '*');
+
+	if (p_ast) {
+		size_t name_len = strlen(name);
+		size_t pattern_len = strlen(pattern);
+
+		if (name_len + 1 < pattern_len)
+			return false;
+		if (strncmp(name, pattern, p_ast - pattern))
+			return false;
+		pattern_len -= (p_ast - pattern) + 1;
+		name += name_len  - pattern_len;
+		pattern = p_ast + 1;
+	}
+	return !strcmp(name, pattern);
+}
+
+static bool flag_pattern_match(const char *name, const char *pattern)
+{
+	bool ret = _flag_pattern_match(name, pattern);
+
+	return ret;
+}
+
+int dump_features(const struct nlattr *bitset)
+{
+	const struct stringset *feature_names;
+	struct feature_results results;
+	unsigned int i, j;
+	int *feature_flags = NULL;
+	int ret;
+
+	ret = prepare_feature_results(bitset, &results);
+	if (ret < 0)
+		return -EFAULT;
+
+	ret = -ENOMEM;
+	feature_flags = calloc(results.count, sizeof(feature_flags[0]));
+	if (!feature_flags)
+	       goto out_free;
+	feature_names = global_stringset(ETH_SS_FEATURES);
+
+	/* map netdev features to legacy flags */
+	for (i = 0; i < results.count; i++) {
+		const char *name = get_string(feature_names, i);
+		feature_flags[i] = -1;
+
+		if (!name || !*name)
+			continue;
+		for (j = 0; j < OFF_FLAG_DEF_SIZE; j++) {
+			const char *flag_name = off_flag_def[j].kernel_name;
+
+			if (flag_pattern_match(name, flag_name)) {
+				feature_flags[i] = j;
+				break;
+			}
+		}
+	}
+	/* show legacy flags and their matching features first */
+	for (i = 0; i < OFF_FLAG_DEF_SIZE; i++) {
+		unsigned int n_match = 0;
+		bool flag_value = false;
+
+		for (j = 0; j < results.count; j++) {
+			if (feature_flags[j] == i) {
+				n_match++;
+				flag_value = flag_value ||
+					     feature_on(results.active, j);
+			}
+		}
+		if (n_match != 1)
+			printf("%s: %s\n", off_flag_def[i].long_name,
+			       flag_value ? "on" : "off");
+		if (n_match == 0)
+			continue;
+		for (j = 0; j < results.count; j++) {
+			const char *name = get_string(feature_names, j);
+
+			if (feature_flags[j] != i)
+				continue;
+			if (n_match == 1)
+				dump_feature(&results, NULL, NULL, j,
+					     off_flag_def[i].long_name, "");
+			else
+				dump_feature(&results, NULL, NULL, j, name,
+					     "\t");
+		}
+	}
+	/* and, finally, remaining netdev_features not matching legacy flags */
+	for (i = 0; i < results.count; i++) {
+		const char *name = get_string(feature_names, i);
+
+		if (!name || !*name || feature_flags[i] >= 0)
+			continue;
+		dump_feature(&results, NULL, NULL, i, name, "");
+	}
+
+out_free:
+	free(feature_flags);
+	return 0;
+}
+
 int settings_reply_cb(const struct nlmsghdr *nlhdr, void *data)
 {
 	const struct nlattr *tb[ETHA_SETTINGS_MAX + 1] = {};
@@ -263,6 +477,15 @@ int settings_reply_cb(const struct nlmsghdr *nlhdr, void *data)
 		printf("\tLink detected: %s\n", link ? "yes" : "no");
 		allfail = false;
 	};
+	if (tb[ETHA_SETTINGS_FEATURES] &&
+	    mask_ok(nlctx, ETH_SETTINGS_IM_FEATURES)) {
+		int ret;
+
+		printf("Features for %s:\n", nlctx->devname);
+		ret = dump_features(tb[ETHA_SETTINGS_FEATURES]);
+		if (ret == 0)
+			allfail = false;
+	}
 
 	if (allfail && !nlctx->is_monitor && !nlctx->is_dump) {
 		fputs("No data available\n", stdout);
@@ -300,6 +523,9 @@ int nl_gset(struct cmd_context *ctx)
 				     ETH_SETTINGS_IM_LINK);
 }
 
+int nl_gfeatures(struct cmd_context *ctx)
+{
+	return settings_request(ctx, ETH_SETTINGS_IM_FEATURES);
 }
 
 /* SET_SETTINGS */
-- 
2.19.0