xref: /aosp_15_r20/external/ethtool/netlink/features.c (revision 1b481fc3bb1b45d4cf28d1ec12969dc1055f555d)
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