/* * features.c - netlink implementation of netdev features commands * * Implementation of "ethtool -k ". */ #include #include #include #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]"; 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) 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'); printf("Features for %s:\n", nlctx->devname); ret = dump_features(tb, feature_names); 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; return nlsock_send_get_request(nlsk, features_reply_cb); } /* FEATURES_SET */ struct sfeatures_context { bool nothing_changed; uint32_t req_mask[0]; }; 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) return 2; if (ethnla_fill_header(msgbuff, ETHTOOL_A_FEATURES_HEADER, ctx->devname, ETHTOOL_FLAG_COMPACT_BITSETS)) return -EMSGSIZE; ret = fill_sfeatures_bitmap(nlctx, feature_names); if (ret < 0) return ret; ret = nlsock_sendmsg(nlsk, NULL); if (ret < 0) return 92; ret = nlsock_process_reply(nlsk, sfeatures_reply_cb, nlctx); if (sfctx->nothing_changed) { fprintf(stderr, "Could not change any device features\n"); return nlctx->exit_code ?: 1; } if (ret == 0) return 0; return nlctx->exit_code ?: 92; }