[RFC PATCH ethtool v2 10/23] netlink: add netlink handler for gfeatures (-k)

From: Michal Kubecek
Date: Mon Jul 30 2018 - 08:56:42 EST


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@xxxxxxx>
---
common.c | 30 ++++++
common.h | 21 +++++
ethtool.c | 70 +++-----------
netlink/extapi.h | 1 +
netlink/monitor.c | 6 ++
netlink/settings.c | 226 +++++++++++++++++++++++++++++++++++++++++++++
6 files changed, 296 insertions(+), 58 deletions(-)

diff --git a/common.c b/common.c
index 59897acd2512..db9a360ffae6 100644
--- a/common.c
+++ b/common.c
@@ -283,3 +283,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 a980b844a0ed..36f96ddaa3bd 100644
--- a/common.h
+++ b/common.h
@@ -1,6 +1,8 @@
#ifndef ETHTOOL_COMMON_H__
#define ETHTOOL_COMMON_H__

+#define KERNEL_VERSION(a,b,c) (((a) << 16) + ((b) << 8) + (c))
+
struct flag_info {
const char *name;
u32 value;
@@ -25,6 +27,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 0b40b96cf9bf..b29086bbabde 100644
--- a/ethtool.c
+++ b/ethtool.c
@@ -61,8 +61,6 @@
#define NETLINK_GENERIC 16
#endif

-#define KERNEL_VERSION(a,b,c) (((a) << 16) + ((b) << 8) + (c))
-
static void exit_bad_args(void) __attribute__((noreturn));

static void exit_bad_args(void)
@@ -104,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 */
@@ -157,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];
};
@@ -1325,7 +1278,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
*/
@@ -1640,7 +1593,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 =
@@ -2082,7 +2035,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;
@@ -2198,14 +2151,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,
@@ -2215,9 +2168,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) {
@@ -2237,7 +2190,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))
@@ -2278,7 +2231,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) {
@@ -4924,6 +4877,7 @@ static int show_usage(struct cmd_context *ctx);
#define nl_gdrv NULL
#define nl_gset NULL
#define nl_sset NULL
+#define nl_gfeatures NULL
#endif

static const struct option {
@@ -4988,7 +4942,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 66573f0b4304..c656ff862687 100644
--- a/netlink/extapi.h
+++ b/netlink/extapi.h
@@ -16,6 +16,7 @@ int netlink_done(struct cmd_context *ctx);
int nl_gdrv(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 32d842611011..e3b8bb46f561 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 b077a96325e5..51a01b400224 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] = {};
@@ -262,6 +476,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) {
fputs("No data available\n", stdout);
@@ -299,6 +522,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.18.0