1 /*
2 * features.c - netlink implementation of netdev features commands
3 *
4 * Implementation of "ethtool -k <dev>".
5 */
6
7 #include <errno.h>
8 #include <string.h>
9 #include <stdio.h>
10
11 #include "../internal.h"
12 #include "../common.h"
13 #include "netlink.h"
14 #include "strset.h"
15 #include "bitset.h"
16
17 /* FEATURES_GET */
18
19 struct feature_results {
20 uint32_t *hw;
21 uint32_t *wanted;
22 uint32_t *active;
23 uint32_t *nochange;
24 unsigned int count;
25 unsigned int words;
26 };
27
prepare_feature_results(const struct nlattr * const * tb,struct feature_results * dest)28 static int prepare_feature_results(const struct nlattr *const *tb,
29 struct feature_results *dest)
30 {
31 unsigned int count;
32 int ret;
33
34 memset(dest, '\0', sizeof(*dest));
35 if (!tb[ETHTOOL_A_FEATURES_HW] || !tb[ETHTOOL_A_FEATURES_WANTED] ||
36 !tb[ETHTOOL_A_FEATURES_ACTIVE] || !tb[ETHTOOL_A_FEATURES_NOCHANGE])
37 return -EFAULT;
38 count = bitset_get_count(tb[ETHTOOL_A_FEATURES_HW], &ret);
39 if (ret < 0)
40 return -EFAULT;
41 if ((bitset_get_count(tb[ETHTOOL_A_FEATURES_WANTED], &ret) != count) ||
42 (bitset_get_count(tb[ETHTOOL_A_FEATURES_ACTIVE], &ret) != count) ||
43 (bitset_get_count(tb[ETHTOOL_A_FEATURES_NOCHANGE], &ret) != count))
44 return -EFAULT;
45 dest->hw = get_compact_bitset_value(tb[ETHTOOL_A_FEATURES_HW]);
46 dest->wanted = get_compact_bitset_value(tb[ETHTOOL_A_FEATURES_WANTED]);
47 dest->active = get_compact_bitset_value(tb[ETHTOOL_A_FEATURES_ACTIVE]);
48 dest->nochange =
49 get_compact_bitset_value(tb[ETHTOOL_A_FEATURES_NOCHANGE]);
50 if (!dest->hw || !dest->wanted || !dest->active || !dest->nochange)
51 return -EFAULT;
52 dest->count = count;
53 dest->words = (count + 31) / 32;
54
55 return 0;
56 }
57
feature_on(const uint32_t * bitmap,unsigned int idx)58 static bool feature_on(const uint32_t *bitmap, unsigned int idx)
59 {
60 return bitmap[idx / 32] & (1 << (idx % 32));
61 }
62
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)63 static void dump_feature(const struct feature_results *results,
64 const uint32_t *ref, const uint32_t *ref_mask,
65 unsigned int idx, const char *name, const char *prefix)
66 {
67 const char *suffix = "";
68
69 if (!name || !*name)
70 return;
71 if (ref) {
72 if (ref_mask && !feature_on(ref_mask, idx))
73 return;
74 if ((!ref_mask || feature_on(ref_mask, idx)) &&
75 (feature_on(results->active, idx) == feature_on(ref, idx)))
76 return;
77 }
78
79 if (!feature_on(results->hw, idx) || feature_on(results->nochange, idx))
80 suffix = " [fixed]";
81 else if (feature_on(results->active, idx) !=
82 feature_on(results->wanted, idx))
83 suffix = feature_on(results->wanted, idx) ?
84 " [requested on]" : " [requested off]";
85 if (is_json_context()) {
86 open_json_object(name);
87 print_bool(PRINT_JSON, "active", NULL, feature_on(results->active, idx));
88 print_bool(PRINT_JSON, "fixed", NULL,
89 (!feature_on(results->hw, idx) || feature_on(results->nochange, idx)));
90 print_bool(PRINT_JSON, "requested", NULL, feature_on(results->wanted, idx));
91 close_json_object();
92 } else {
93 printf("%s%s: %s%s\n", prefix, name,
94 feature_on(results->active, idx) ? "on" : "off", suffix);
95 }
96 }
97
98 /* this assumes pattern contains no more than one asterisk */
flag_pattern_match(const char * name,const char * pattern)99 static bool flag_pattern_match(const char *name, const char *pattern)
100 {
101 const char *p_ast = strchr(pattern, '*');
102
103 if (p_ast) {
104 size_t name_len = strlen(name);
105 size_t pattern_len = strlen(pattern);
106
107 if (name_len + 1 < pattern_len)
108 return false;
109 if (strncmp(name, pattern, p_ast - pattern))
110 return false;
111 pattern_len -= (p_ast - pattern) + 1;
112 name += name_len - pattern_len;
113 pattern = p_ast + 1;
114 }
115 return !strcmp(name, pattern);
116 }
117
dump_features(const struct nlattr * const * tb,const struct stringset * feature_names)118 int dump_features(const struct nlattr *const *tb,
119 const struct stringset *feature_names)
120 {
121 unsigned int *feature_flags = NULL;
122 struct feature_results results;
123 unsigned int i, j;
124 int ret;
125
126 ret = prepare_feature_results(tb, &results);
127 if (ret < 0)
128 return -EFAULT;
129 feature_flags = calloc(results.count, sizeof(feature_flags[0]));
130 if (!feature_flags)
131 return -ENOMEM;
132
133 /* map netdev features to legacy flags */
134 for (i = 0; i < results.count; i++) {
135 const char *name = get_string(feature_names, i);
136 feature_flags[i] = UINT_MAX;
137
138 if (!name || !*name)
139 continue;
140 for (j = 0; j < OFF_FLAG_DEF_SIZE; j++) {
141 const char *flag_name = off_flag_def[j].kernel_name;
142
143 if (flag_pattern_match(name, flag_name)) {
144 feature_flags[i] = j;
145 break;
146 }
147 }
148 }
149 /* show legacy flags and their matching features first */
150 for (i = 0; i < OFF_FLAG_DEF_SIZE; i++) {
151 unsigned int n_match = 0;
152 bool flag_value = false;
153
154 /* no kernel with netlink interface supports UFO */
155 if (off_flag_def[i].value == ETH_FLAG_UFO)
156 continue;
157
158 for (j = 0; j < results.count; j++) {
159 if (feature_flags[j] == i) {
160 n_match++;
161 flag_value = flag_value ||
162 feature_on(results.active, j);
163 }
164 }
165 if (n_match != 1) {
166 if (is_json_context()) {
167 open_json_object(off_flag_def[i].long_name);
168 print_bool(PRINT_JSON, "active", NULL, flag_value);
169 print_null(PRINT_JSON, "fixed", NULL, NULL);
170 print_null(PRINT_JSON, "requested", NULL, NULL);
171 close_json_object();
172 } else {
173 printf("%s: %s\n", off_flag_def[i].long_name,
174 flag_value ? "on" : "off");
175 }
176 }
177 if (n_match == 0)
178 continue;
179 for (j = 0; j < results.count; j++) {
180 const char *name = get_string(feature_names, j);
181
182 if (feature_flags[j] != i)
183 continue;
184 if (n_match == 1)
185 dump_feature(&results, NULL, NULL, j,
186 off_flag_def[i].long_name, "");
187 else
188 dump_feature(&results, NULL, NULL, j, name,
189 "\t");
190 }
191 }
192 /* and, finally, remaining netdev_features not matching legacy flags */
193 for (i = 0; i < results.count; i++) {
194 const char *name = get_string(feature_names, i);
195
196 if (!name || !*name || feature_flags[i] != UINT_MAX)
197 continue;
198 dump_feature(&results, NULL, NULL, i, name, "");
199 }
200
201 free(feature_flags);
202 return 0;
203 }
204
features_reply_cb(const struct nlmsghdr * nlhdr,void * data)205 int features_reply_cb(const struct nlmsghdr *nlhdr, void *data)
206 {
207 const struct nlattr *tb[ETHTOOL_A_FEATURES_MAX + 1] = {};
208 DECLARE_ATTR_TB_INFO(tb);
209 const struct stringset *feature_names;
210 struct nl_context *nlctx = data;
211 bool silent;
212 int ret;
213
214 silent = nlctx->is_dump || nlctx->is_monitor;
215 if (!nlctx->is_monitor) {
216 ret = netlink_init_ethnl2_socket(nlctx);
217 if (ret < 0)
218 return MNL_CB_ERROR;
219 }
220 feature_names = global_stringset(ETH_SS_FEATURES, nlctx->ethnl2_socket);
221
222 ret = mnl_attr_parse(nlhdr, GENL_HDRLEN, attr_cb, &tb_info);
223 if (ret < 0)
224 return silent ? MNL_CB_OK : MNL_CB_ERROR;
225 nlctx->devname = get_dev_name(tb[ETHTOOL_A_FEATURES_HEADER]);
226 if (!dev_ok(nlctx))
227 return MNL_CB_OK;
228
229 if (silent)
230 putchar('\n');
231 open_json_object(NULL);
232 print_string(PRINT_ANY, "ifname", "Features for %s:\n", nlctx->devname);
233 ret = dump_features(tb, feature_names);
234 close_json_object();
235 return (silent || !ret) ? MNL_CB_OK : MNL_CB_ERROR;
236 }
237
nl_gfeatures(struct cmd_context * ctx)238 int nl_gfeatures(struct cmd_context *ctx)
239 {
240 struct nl_context *nlctx = ctx->nlctx;
241 struct nl_socket *nlsk = nlctx->ethnl_socket;
242 int ret;
243
244 if (netlink_cmd_check(ctx, ETHTOOL_MSG_FEATURES_GET, true))
245 return -EOPNOTSUPP;
246 if (ctx->argc > 0) {
247 fprintf(stderr, "ethtool: unexpected parameter '%s'\n",
248 *ctx->argp);
249 return 1;
250 }
251
252 ret = nlsock_prep_get_request(nlsk, ETHTOOL_MSG_FEATURES_GET,
253 ETHTOOL_A_FEATURES_HEADER,
254 ETHTOOL_FLAG_COMPACT_BITSETS);
255 if (ret < 0)
256 return ret;
257
258 new_json_obj(ctx->json);
259 ret = nlsock_send_get_request(nlsk, features_reply_cb);
260 delete_json_obj();
261
262 return ret;
263 }
264
265 /* FEATURES_SET */
266
267 struct sfeatures_context {
268 bool nothing_changed;
269 uint32_t req_mask[];
270 };
271
find_feature(const char * name,const struct stringset * feature_names)272 static int find_feature(const char *name,
273 const struct stringset *feature_names)
274 {
275 const unsigned int count = get_count(feature_names);
276 unsigned int i;
277
278 for (i = 0; i < count; i++)
279 if (!strcmp(name, get_string(feature_names, i)))
280 return i;
281
282 return -1;
283 }
284
fill_feature(struct nl_msg_buff * msgbuff,const char * name,bool val)285 static int fill_feature(struct nl_msg_buff *msgbuff, const char *name, bool val)
286 {
287 struct nlattr *bit_attr;
288
289 bit_attr = ethnla_nest_start(msgbuff, ETHTOOL_A_BITSET_BITS_BIT);
290 if (!bit_attr)
291 return -EMSGSIZE;
292 if (ethnla_put_strz(msgbuff, ETHTOOL_A_BITSET_BIT_NAME, name))
293 return -EMSGSIZE;
294 if (ethnla_put_flag(msgbuff, ETHTOOL_A_BITSET_BIT_VALUE, val))
295 return -EMSGSIZE;
296 mnl_attr_nest_end(msgbuff->nlhdr, bit_attr);
297
298 return 0;
299 }
300
set_sf_req_mask(struct nl_context * nlctx,unsigned int idx)301 static void set_sf_req_mask(struct nl_context *nlctx, unsigned int idx)
302 {
303 struct sfeatures_context *sfctx = nlctx->cmd_private;
304
305 sfctx->req_mask[idx / 32] |= (1 << (idx % 32));
306 }
307
fill_legacy_flag(struct nl_context * nlctx,const char * flag_name,const struct stringset * feature_names,bool val)308 static int fill_legacy_flag(struct nl_context *nlctx, const char *flag_name,
309 const struct stringset *feature_names, bool val)
310 {
311 struct nl_msg_buff *msgbuff = &nlctx->ethnl_socket->msgbuff;
312 const unsigned int count = get_count(feature_names);
313 unsigned int i, j;
314 int ret;
315
316 for (i = 0; i < OFF_FLAG_DEF_SIZE; i++) {
317 const char *pattern;
318
319 if (strcmp(flag_name, off_flag_def[i].short_name) &&
320 strcmp(flag_name, off_flag_def[i].long_name))
321 continue;
322 pattern = off_flag_def[i].kernel_name;
323
324 for (j = 0; j < count; j++) {
325 const char *name = get_string(feature_names, j);
326
327 if (flag_pattern_match(name, pattern)) {
328 ret = fill_feature(msgbuff, name, val);
329 if (ret < 0)
330 return ret;
331 set_sf_req_mask(nlctx, j);
332 }
333 }
334
335 return 0;
336 }
337
338 return 1;
339 }
340
fill_sfeatures_bitmap(struct nl_context * nlctx,const struct stringset * feature_names)341 int fill_sfeatures_bitmap(struct nl_context *nlctx,
342 const struct stringset *feature_names)
343 {
344 struct nl_msg_buff *msgbuff = &nlctx->ethnl_socket->msgbuff;
345 struct nlattr *bitset_attr;
346 struct nlattr *bits_attr;
347 int ret;
348
349 ret = -EMSGSIZE;
350 bitset_attr = ethnla_nest_start(msgbuff, ETHTOOL_A_FEATURES_WANTED);
351 if (!bitset_attr)
352 return ret;
353 bits_attr = ethnla_nest_start(msgbuff, ETHTOOL_A_BITSET_BITS);
354 if (!bits_attr)
355 goto err;
356
357 while (nlctx->argc > 0) {
358 bool val;
359
360 if (!strcmp(*nlctx->argp, "--")) {
361 nlctx->argp++;
362 nlctx->argc--;
363 break;
364 }
365 ret = -EINVAL;
366 if (nlctx->argc < 2 ||
367 (strcmp(nlctx->argp[1], "on") &&
368 strcmp(nlctx->argp[1], "off"))) {
369 fprintf(stderr,
370 "ethtool (%s): flag '%s' for parameter '%s' is"
371 " not followed by 'on' or 'off'\n",
372 nlctx->cmd, nlctx->argp[1], nlctx->param);
373 goto err;
374 }
375
376 val = !strcmp(nlctx->argp[1], "on");
377 ret = fill_legacy_flag(nlctx, nlctx->argp[0], feature_names,
378 val);
379 if (ret > 0) {
380 ret = fill_feature(msgbuff, nlctx->argp[0], val);
381 if (ret == 0) {
382 int idx = find_feature(nlctx->argp[0],
383 feature_names);
384
385 if (idx >= 0)
386 set_sf_req_mask(nlctx, idx);
387 }
388 }
389 if (ret < 0)
390 goto err;
391
392 nlctx->argp += 2;
393 nlctx->argc -= 2;
394 }
395
396 ethnla_nest_end(msgbuff, bits_attr);
397 ethnla_nest_end(msgbuff, bitset_attr);
398 return 0;
399 err:
400 ethnla_nest_cancel(msgbuff, bitset_attr);
401 return ret;
402 }
403
show_feature_changes(struct nl_context * nlctx,const struct nlattr * const * tb)404 static void show_feature_changes(struct nl_context *nlctx,
405 const struct nlattr *const *tb)
406 {
407 struct sfeatures_context *sfctx = nlctx->cmd_private;
408 const struct stringset *feature_names;
409 const uint32_t *wanted_mask;
410 const uint32_t *active_mask;
411 const uint32_t *wanted_val;
412 const uint32_t *active_val;
413 unsigned int count, words;
414 unsigned int i;
415 bool diff;
416 int ret;
417
418 feature_names = global_stringset(ETH_SS_FEATURES, nlctx->ethnl_socket);
419 count = get_count(feature_names);
420 words = DIV_ROUND_UP(count, 32);
421
422 if (!tb[ETHTOOL_A_FEATURES_WANTED] || !tb[ETHTOOL_A_FEATURES_ACTIVE])
423 goto err;
424 if (bitset_get_count(tb[ETHTOOL_A_FEATURES_WANTED], &ret) != count ||
425 ret < 0)
426 goto err;
427 if (bitset_get_count(tb[ETHTOOL_A_FEATURES_ACTIVE], &ret) != count ||
428 ret < 0)
429 goto err;
430 wanted_val = get_compact_bitset_value(tb[ETHTOOL_A_FEATURES_WANTED]);
431 wanted_mask = get_compact_bitset_mask(tb[ETHTOOL_A_FEATURES_WANTED]);
432 active_val = get_compact_bitset_value(tb[ETHTOOL_A_FEATURES_ACTIVE]);
433 active_mask = get_compact_bitset_mask(tb[ETHTOOL_A_FEATURES_ACTIVE]);
434 if (!wanted_val || !wanted_mask || !active_val || !active_mask)
435 goto err;
436
437 sfctx->nothing_changed = true;
438 diff = false;
439 for (i = 0; i < words; i++) {
440 if (wanted_mask[i] != sfctx->req_mask[i])
441 sfctx->nothing_changed = false;
442 if (wanted_mask[i] || (active_mask[i] & ~sfctx->req_mask[i]))
443 diff = true;
444 }
445 if (!diff)
446 return;
447
448 /* result is not exactly as requested, show differences */
449 printf("Actual changes:\n");
450 for (i = 0; i < count; i++) {
451 const char *name = get_string(feature_names, i);
452
453 if (!name)
454 continue;
455 if (!feature_on(wanted_mask, i) && !feature_on(active_mask, i))
456 continue;
457 printf("%s: ", name);
458 if (feature_on(wanted_mask, i))
459 /* we requested a value but result is different */
460 printf("%s [requested %s]",
461 feature_on(wanted_val, i) ? "off" : "on",
462 feature_on(wanted_val, i) ? "on" : "off");
463 else if (!feature_on(sfctx->req_mask, i))
464 /* not requested but changed anyway */
465 printf("%s [not requested]",
466 feature_on(active_val, i) ? "on" : "off");
467 else
468 printf("%s", feature_on(active_val, i) ? "on" : "off");
469 fputc('\n', stdout);
470 }
471
472 return;
473 err:
474 fprintf(stderr, "malformed diff info from kernel\n");
475 }
476
sfeatures_reply_cb(const struct nlmsghdr * nlhdr,void * data)477 int sfeatures_reply_cb(const struct nlmsghdr *nlhdr, void *data)
478 {
479 const struct genlmsghdr *ghdr = (const struct genlmsghdr *)(nlhdr + 1);
480 const struct nlattr *tb[ETHTOOL_A_FEATURES_MAX + 1] = {};
481 DECLARE_ATTR_TB_INFO(tb);
482 struct nl_context *nlctx = data;
483 const char *devname;
484 int ret;
485
486 if (ghdr->cmd != ETHTOOL_MSG_FEATURES_SET_REPLY) {
487 fprintf(stderr, "warning: unexpected reply message type %u\n",
488 ghdr->cmd);
489 return MNL_CB_OK;
490 }
491 ret = mnl_attr_parse(nlhdr, GENL_HDRLEN, attr_cb, &tb_info);
492 if (ret < 0)
493 return ret;
494 devname = get_dev_name(tb[ETHTOOL_A_FEATURES_HEADER]);
495 if (strcmp(devname, nlctx->devname)) {
496 fprintf(stderr, "warning: unexpected message for device %s\n",
497 devname);
498 return MNL_CB_OK;
499 }
500
501 show_feature_changes(nlctx, tb);
502 return MNL_CB_OK;
503 }
504
nl_sfeatures(struct cmd_context * ctx)505 int nl_sfeatures(struct cmd_context *ctx)
506 {
507 const struct stringset *feature_names;
508 struct nl_context *nlctx = ctx->nlctx;
509 struct sfeatures_context *sfctx;
510 struct nl_msg_buff *msgbuff;
511 struct nl_socket *nlsk;
512 unsigned int words;
513 int ret;
514
515 if (netlink_cmd_check(ctx, ETHTOOL_MSG_FEATURES_SET, false))
516 return -EOPNOTSUPP;
517
518 nlctx->cmd = "-K";
519 nlctx->argp = ctx->argp;
520 nlctx->argc = ctx->argc;
521 nlctx->cmd_private = &sfctx;
522 nlsk = nlctx->ethnl_socket;
523 msgbuff = &nlsk->msgbuff;
524
525 feature_names = global_stringset(ETH_SS_FEATURES, nlctx->ethnl_socket);
526 words = (get_count(feature_names) + 31) / 32;
527 sfctx = malloc(sizeof(*sfctx) + words * sizeof(sfctx->req_mask[0]));
528 if (!sfctx)
529 return -ENOMEM;
530 memset(sfctx, '\0',
531 sizeof(*sfctx) + words * sizeof(sfctx->req_mask[0]));
532 nlctx->cmd_private = sfctx;
533
534 nlctx->devname = ctx->devname;
535 ret = msg_init(nlctx, msgbuff, ETHTOOL_MSG_FEATURES_SET,
536 NLM_F_REQUEST | NLM_F_ACK);
537 if (ret < 0) {
538 free(sfctx);
539 return 2;
540 }
541 if (ethnla_fill_header(msgbuff, ETHTOOL_A_FEATURES_HEADER, ctx->devname,
542 ETHTOOL_FLAG_COMPACT_BITSETS)) {
543 free(sfctx);
544 return -EMSGSIZE;
545 }
546 ret = fill_sfeatures_bitmap(nlctx, feature_names);
547 if (ret < 0) {
548 free(sfctx);
549 return ret;
550 }
551
552 ret = nlsock_sendmsg(nlsk, NULL);
553 if (ret < 0) {
554 free(sfctx);
555 return 92;
556 }
557 ret = nlsock_process_reply(nlsk, sfeatures_reply_cb, nlctx);
558 if (sfctx->nothing_changed) {
559 fprintf(stderr, "Could not change any device features\n");
560 free(sfctx);
561 return nlctx->exit_code ?: 1;
562 }
563 if (ret == 0) {
564 free(sfctx);
565 return 0;
566 }
567 free(sfctx);
568 return nlctx->exit_code ?: 92;
569 }
570