summaryrefslogtreecommitdiffstats
path: root/netlink/features.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--netlink/features.c569
1 files changed, 569 insertions, 0 deletions
diff --git a/netlink/features.c b/netlink/features.c
new file mode 100644
index 0000000..5711ff4
--- /dev/null
+++ b/netlink/features.c
@@ -0,0 +1,569 @@
+/*
+ * features.c - netlink implementation of netdev features commands
+ *
+ * Implementation of "ethtool -k <dev>".
+ */
+
+#include <errno.h>
+#include <string.h>
+#include <stdio.h>
+
+#include "../internal.h"
+#include "../common.h"
+#include "netlink.h"
+#include "strset.h"
+#include "bitset.h"
+
+/* FEATURES_GET */
+
+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 *const *tb,
+ struct feature_results *dest)
+{
+ unsigned int count;
+ int ret;
+
+ memset(dest, '\0', sizeof(*dest));
+ if (!tb[ETHTOOL_A_FEATURES_HW] || !tb[ETHTOOL_A_FEATURES_WANTED] ||
+ !tb[ETHTOOL_A_FEATURES_ACTIVE] || !tb[ETHTOOL_A_FEATURES_NOCHANGE])
+ return -EFAULT;
+ count = bitset_get_count(tb[ETHTOOL_A_FEATURES_HW], &ret);
+ if (ret < 0)
+ return -EFAULT;
+ if ((bitset_get_count(tb[ETHTOOL_A_FEATURES_WANTED], &ret) != count) ||
+ (bitset_get_count(tb[ETHTOOL_A_FEATURES_ACTIVE], &ret) != count) ||
+ (bitset_get_count(tb[ETHTOOL_A_FEATURES_NOCHANGE], &ret) != count))
+ return -EFAULT;
+ dest->hw = get_compact_bitset_value(tb[ETHTOOL_A_FEATURES_HW]);
+ dest->wanted = get_compact_bitset_value(tb[ETHTOOL_A_FEATURES_WANTED]);
+ dest->active = get_compact_bitset_value(tb[ETHTOOL_A_FEATURES_ACTIVE]);
+ dest->nochange =
+ get_compact_bitset_value(tb[ETHTOOL_A_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]";
+ if (is_json_context()) {
+ open_json_object(name);
+ print_bool(PRINT_JSON, "active", NULL, feature_on(results->active, idx));
+ print_bool(PRINT_JSON, "fixed", NULL,
+ (!feature_on(results->hw, idx) || feature_on(results->nochange, idx)));
+ print_bool(PRINT_JSON, "requested", NULL, feature_on(results->wanted, idx));
+ close_json_object();
+ } else {
+ 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);
+}
+
+int dump_features(const struct nlattr *const *tb,
+ const struct stringset *feature_names)
+{
+ unsigned int *feature_flags = NULL;
+ struct feature_results results;
+ unsigned int i, j;
+ int ret;
+
+ ret = prepare_feature_results(tb, &results);
+ if (ret < 0)
+ return -EFAULT;
+ feature_flags = calloc(results.count, sizeof(feature_flags[0]));
+ if (!feature_flags)
+ return -ENOMEM;
+
+ /* map netdev features to legacy flags */
+ for (i = 0; i < results.count; i++) {
+ const char *name = get_string(feature_names, i);
+ feature_flags[i] = UINT_MAX;
+
+ 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;
+
+ /* no kernel with netlink interface supports UFO */
+ if (off_flag_def[i].value == ETH_FLAG_UFO)
+ continue;
+
+ 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) {
+ if (is_json_context()) {
+ open_json_object(off_flag_def[i].long_name);
+ print_bool(PRINT_JSON, "active", NULL, flag_value);
+ print_null(PRINT_JSON, "fixed", NULL, NULL);
+ print_null(PRINT_JSON, "requested", NULL, NULL);
+ close_json_object();
+ } else {
+ 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] != UINT_MAX)
+ continue;
+ dump_feature(&results, NULL, NULL, i, name, "");
+ }
+
+ free(feature_flags);
+ return 0;
+}
+
+int features_reply_cb(const struct nlmsghdr *nlhdr, void *data)
+{
+ const struct nlattr *tb[ETHTOOL_A_FEATURES_MAX + 1] = {};
+ DECLARE_ATTR_TB_INFO(tb);
+ const struct stringset *feature_names;
+ struct nl_context *nlctx = data;
+ bool silent;
+ int ret;
+
+ silent = nlctx->is_dump || nlctx->is_monitor;
+ if (!nlctx->is_monitor) {
+ ret = netlink_init_ethnl2_socket(nlctx);
+ if (ret < 0)
+ return MNL_CB_ERROR;
+ }
+ feature_names = global_stringset(ETH_SS_FEATURES, nlctx->ethnl2_socket);
+
+ ret = mnl_attr_parse(nlhdr, GENL_HDRLEN, attr_cb, &tb_info);
+ if (ret < 0)
+ return silent ? MNL_CB_OK : MNL_CB_ERROR;
+ nlctx->devname = get_dev_name(tb[ETHTOOL_A_FEATURES_HEADER]);
+ if (!dev_ok(nlctx))
+ return MNL_CB_OK;
+
+ if (silent)
+ putchar('\n');
+ open_json_object(NULL);
+ print_string(PRINT_ANY, "ifname", "Features for %s:\n", nlctx->devname);
+ ret = dump_features(tb, feature_names);
+ close_json_object();
+ return (silent || !ret) ? MNL_CB_OK : MNL_CB_ERROR;
+}
+
+int nl_gfeatures(struct cmd_context *ctx)
+{
+ struct nl_context *nlctx = ctx->nlctx;
+ struct nl_socket *nlsk = nlctx->ethnl_socket;
+ int ret;
+
+ if (netlink_cmd_check(ctx, ETHTOOL_MSG_FEATURES_GET, true))
+ return -EOPNOTSUPP;
+ if (ctx->argc > 0) {
+ fprintf(stderr, "ethtool: unexpected parameter '%s'\n",
+ *ctx->argp);
+ return 1;
+ }
+
+ ret = nlsock_prep_get_request(nlsk, ETHTOOL_MSG_FEATURES_GET,
+ ETHTOOL_A_FEATURES_HEADER,
+ ETHTOOL_FLAG_COMPACT_BITSETS);
+ if (ret < 0)
+ return ret;
+
+ new_json_obj(ctx->json);
+ ret = nlsock_send_get_request(nlsk, features_reply_cb);
+ delete_json_obj();
+
+ return ret;
+}
+
+/* FEATURES_SET */
+
+struct sfeatures_context {
+ bool nothing_changed;
+ uint32_t req_mask[];
+};
+
+static int find_feature(const char *name,
+ const struct stringset *feature_names)
+{
+ const unsigned int count = get_count(feature_names);
+ unsigned int i;
+
+ for (i = 0; i < count; i++)
+ if (!strcmp(name, get_string(feature_names, i)))
+ return i;
+
+ return -1;
+}
+
+static int fill_feature(struct nl_msg_buff *msgbuff, const char *name, bool val)
+{
+ struct nlattr *bit_attr;
+
+ bit_attr = ethnla_nest_start(msgbuff, ETHTOOL_A_BITSET_BITS_BIT);
+ if (!bit_attr)
+ return -EMSGSIZE;
+ if (ethnla_put_strz(msgbuff, ETHTOOL_A_BITSET_BIT_NAME, name))
+ return -EMSGSIZE;
+ if (ethnla_put_flag(msgbuff, ETHTOOL_A_BITSET_BIT_VALUE, val))
+ return -EMSGSIZE;
+ mnl_attr_nest_end(msgbuff->nlhdr, bit_attr);
+
+ return 0;
+}
+
+static void set_sf_req_mask(struct nl_context *nlctx, unsigned int idx)
+{
+ struct sfeatures_context *sfctx = nlctx->cmd_private;
+
+ sfctx->req_mask[idx / 32] |= (1 << (idx % 32));
+}
+
+static int fill_legacy_flag(struct nl_context *nlctx, const char *flag_name,
+ const struct stringset *feature_names, bool val)
+{
+ struct nl_msg_buff *msgbuff = &nlctx->ethnl_socket->msgbuff;
+ const unsigned int count = get_count(feature_names);
+ unsigned int i, j;
+ int ret;
+
+ for (i = 0; i < OFF_FLAG_DEF_SIZE; i++) {
+ const char *pattern;
+
+ if (strcmp(flag_name, off_flag_def[i].short_name) &&
+ strcmp(flag_name, off_flag_def[i].long_name))
+ continue;
+ pattern = off_flag_def[i].kernel_name;
+
+ for (j = 0; j < count; j++) {
+ const char *name = get_string(feature_names, j);
+
+ if (flag_pattern_match(name, pattern)) {
+ ret = fill_feature(msgbuff, name, val);
+ if (ret < 0)
+ return ret;
+ set_sf_req_mask(nlctx, j);
+ }
+ }
+
+ return 0;
+ }
+
+ return 1;
+}
+
+int fill_sfeatures_bitmap(struct nl_context *nlctx,
+ const struct stringset *feature_names)
+{
+ struct nl_msg_buff *msgbuff = &nlctx->ethnl_socket->msgbuff;
+ struct nlattr *bitset_attr;
+ struct nlattr *bits_attr;
+ int ret;
+
+ ret = -EMSGSIZE;
+ bitset_attr = ethnla_nest_start(msgbuff, ETHTOOL_A_FEATURES_WANTED);
+ if (!bitset_attr)
+ return ret;
+ bits_attr = ethnla_nest_start(msgbuff, ETHTOOL_A_BITSET_BITS);
+ if (!bits_attr)
+ goto err;
+
+ while (nlctx->argc > 0) {
+ bool val;
+
+ if (!strcmp(*nlctx->argp, "--")) {
+ nlctx->argp++;
+ nlctx->argc--;
+ break;
+ }
+ ret = -EINVAL;
+ if (nlctx->argc < 2 ||
+ (strcmp(nlctx->argp[1], "on") &&
+ strcmp(nlctx->argp[1], "off"))) {
+ fprintf(stderr,
+ "ethtool (%s): flag '%s' for parameter '%s' is"
+ " not followed by 'on' or 'off'\n",
+ nlctx->cmd, nlctx->argp[1], nlctx->param);
+ goto err;
+ }
+
+ val = !strcmp(nlctx->argp[1], "on");
+ ret = fill_legacy_flag(nlctx, nlctx->argp[0], feature_names,
+ val);
+ if (ret > 0) {
+ ret = fill_feature(msgbuff, nlctx->argp[0], val);
+ if (ret == 0) {
+ int idx = find_feature(nlctx->argp[0],
+ feature_names);
+
+ if (idx >= 0)
+ set_sf_req_mask(nlctx, idx);
+ }
+ }
+ if (ret < 0)
+ goto err;
+
+ nlctx->argp += 2;
+ nlctx->argc -= 2;
+ }
+
+ ethnla_nest_end(msgbuff, bits_attr);
+ ethnla_nest_end(msgbuff, bitset_attr);
+ return 0;
+err:
+ ethnla_nest_cancel(msgbuff, bitset_attr);
+ return ret;
+}
+
+static void show_feature_changes(struct nl_context *nlctx,
+ const struct nlattr *const *tb)
+{
+ struct sfeatures_context *sfctx = nlctx->cmd_private;
+ const struct stringset *feature_names;
+ const uint32_t *wanted_mask;
+ const uint32_t *active_mask;
+ const uint32_t *wanted_val;
+ const uint32_t *active_val;
+ unsigned int count, words;
+ unsigned int i;
+ bool diff;
+ int ret;
+
+ feature_names = global_stringset(ETH_SS_FEATURES, nlctx->ethnl_socket);
+ count = get_count(feature_names);
+ words = DIV_ROUND_UP(count, 32);
+
+ if (!tb[ETHTOOL_A_FEATURES_WANTED] || !tb[ETHTOOL_A_FEATURES_ACTIVE])
+ goto err;
+ if (bitset_get_count(tb[ETHTOOL_A_FEATURES_WANTED], &ret) != count ||
+ ret < 0)
+ goto err;
+ if (bitset_get_count(tb[ETHTOOL_A_FEATURES_ACTIVE], &ret) != count ||
+ ret < 0)
+ goto err;
+ wanted_val = get_compact_bitset_value(tb[ETHTOOL_A_FEATURES_WANTED]);
+ wanted_mask = get_compact_bitset_mask(tb[ETHTOOL_A_FEATURES_WANTED]);
+ active_val = get_compact_bitset_value(tb[ETHTOOL_A_FEATURES_ACTIVE]);
+ active_mask = get_compact_bitset_mask(tb[ETHTOOL_A_FEATURES_ACTIVE]);
+ if (!wanted_val || !wanted_mask || !active_val || !active_mask)
+ goto err;
+
+ sfctx->nothing_changed = true;
+ diff = false;
+ for (i = 0; i < words; i++) {
+ if (wanted_mask[i] != sfctx->req_mask[i])
+ sfctx->nothing_changed = false;
+ if (wanted_mask[i] || (active_mask[i] & ~sfctx->req_mask[i]))
+ diff = true;
+ }
+ if (!diff)
+ return;
+
+ /* result is not exactly as requested, show differences */
+ printf("Actual changes:\n");
+ for (i = 0; i < count; i++) {
+ const char *name = get_string(feature_names, i);
+
+ if (!name)
+ continue;
+ if (!feature_on(wanted_mask, i) && !feature_on(active_mask, i))
+ continue;
+ printf("%s: ", name);
+ if (feature_on(wanted_mask, i))
+ /* we requested a value but result is different */
+ printf("%s [requested %s]",
+ feature_on(wanted_val, i) ? "off" : "on",
+ feature_on(wanted_val, i) ? "on" : "off");
+ else if (!feature_on(sfctx->req_mask, i))
+ /* not requested but changed anyway */
+ printf("%s [not requested]",
+ feature_on(active_val, i) ? "on" : "off");
+ else
+ printf("%s", feature_on(active_val, i) ? "on" : "off");
+ fputc('\n', stdout);
+ }
+
+ return;
+err:
+ fprintf(stderr, "malformed diff info from kernel\n");
+}
+
+int sfeatures_reply_cb(const struct nlmsghdr *nlhdr, void *data)
+{
+ const struct genlmsghdr *ghdr = (const struct genlmsghdr *)(nlhdr + 1);
+ const struct nlattr *tb[ETHTOOL_A_FEATURES_MAX + 1] = {};
+ DECLARE_ATTR_TB_INFO(tb);
+ struct nl_context *nlctx = data;
+ const char *devname;
+ int ret;
+
+ if (ghdr->cmd != ETHTOOL_MSG_FEATURES_SET_REPLY) {
+ fprintf(stderr, "warning: unexpected reply message type %u\n",
+ ghdr->cmd);
+ return MNL_CB_OK;
+ }
+ ret = mnl_attr_parse(nlhdr, GENL_HDRLEN, attr_cb, &tb_info);
+ if (ret < 0)
+ return ret;
+ devname = get_dev_name(tb[ETHTOOL_A_FEATURES_HEADER]);
+ if (strcmp(devname, nlctx->devname)) {
+ fprintf(stderr, "warning: unexpected message for device %s\n",
+ devname);
+ return MNL_CB_OK;
+ }
+
+ show_feature_changes(nlctx, tb);
+ return MNL_CB_OK;
+}
+
+int nl_sfeatures(struct cmd_context *ctx)
+{
+ const struct stringset *feature_names;
+ struct nl_context *nlctx = ctx->nlctx;
+ struct sfeatures_context *sfctx;
+ struct nl_msg_buff *msgbuff;
+ struct nl_socket *nlsk;
+ unsigned int words;
+ int ret;
+
+ if (netlink_cmd_check(ctx, ETHTOOL_MSG_FEATURES_SET, false))
+ return -EOPNOTSUPP;
+
+ nlctx->cmd = "-K";
+ nlctx->argp = ctx->argp;
+ nlctx->argc = ctx->argc;
+ nlctx->cmd_private = &sfctx;
+ nlsk = nlctx->ethnl_socket;
+ msgbuff = &nlsk->msgbuff;
+
+ feature_names = global_stringset(ETH_SS_FEATURES, nlctx->ethnl_socket);
+ words = (get_count(feature_names) + 31) / 32;
+ sfctx = malloc(sizeof(*sfctx) + words * sizeof(sfctx->req_mask[0]));
+ if (!sfctx)
+ return -ENOMEM;
+ memset(sfctx, '\0',
+ sizeof(*sfctx) + words * sizeof(sfctx->req_mask[0]));
+ nlctx->cmd_private = sfctx;
+
+ nlctx->devname = ctx->devname;
+ ret = msg_init(nlctx, msgbuff, ETHTOOL_MSG_FEATURES_SET,
+ NLM_F_REQUEST | NLM_F_ACK);
+ if (ret < 0) {
+ free(sfctx);
+ return 2;
+ }
+ if (ethnla_fill_header(msgbuff, ETHTOOL_A_FEATURES_HEADER, ctx->devname,
+ ETHTOOL_FLAG_COMPACT_BITSETS)) {
+ free(sfctx);
+ return -EMSGSIZE;
+ }
+ ret = fill_sfeatures_bitmap(nlctx, feature_names);
+ if (ret < 0) {
+ free(sfctx);
+ return ret;
+ }
+
+ ret = nlsock_sendmsg(nlsk, NULL);
+ if (ret < 0) {
+ free(sfctx);
+ return 92;
+ }
+ ret = nlsock_process_reply(nlsk, sfeatures_reply_cb, nlctx);
+ if (sfctx->nothing_changed) {
+ fprintf(stderr, "Could not change any device features\n");
+ free(sfctx);
+ return nlctx->exit_code ?: 1;
+ }
+ if (ret == 0) {
+ free(sfctx);
+ return 0;
+ }
+ free(sfctx);
+ return nlctx->exit_code ?: 92;
+}