xref: /aosp_15_r20/external/ethtool/netlink/netlink.c (revision 1b481fc3bb1b45d4cf28d1ec12969dc1055f555d)
1 /*
2  * netlink.c - basic infrastructure for netlink code
3  *
4  * Heart of the netlink interface implementation.
5  */
6 
7 #include <errno.h>
8 
9 #include "../internal.h"
10 #include "netlink.h"
11 #include "extapi.h"
12 #include "msgbuff.h"
13 #include "nlsock.h"
14 #include "strset.h"
15 
16 /* Used as reply callback for requests where no reply is expected (e.g. most
17  * "set" type commands)
18  */
nomsg_reply_cb(const struct nlmsghdr * nlhdr,void * data __maybe_unused)19 int nomsg_reply_cb(const struct nlmsghdr *nlhdr, void *data __maybe_unused)
20 {
21 	const struct genlmsghdr *ghdr = (const struct genlmsghdr *)(nlhdr + 1);
22 
23 	fprintf(stderr, "received unexpected message: len=%u type=%u cmd=%u\n",
24 		nlhdr->nlmsg_len, nlhdr->nlmsg_type, ghdr->cmd);
25 	return MNL_CB_OK;
26 }
27 
28 /* standard attribute parser callback; it fills provided array with pointers
29  * to attributes like kernel nla_parse(). We must expect to run on top of
30  * a newer kernel which may send attributes that we do not know (yet). Rather
31  * than treating them as an error, just ignore them.
32  */
attr_cb(const struct nlattr * attr,void * data)33 int attr_cb(const struct nlattr *attr, void *data)
34 {
35 	const struct attr_tb_info *tb_info = data;
36 	uint16_t type = mnl_attr_get_type(attr);
37 
38 	if (type <= tb_info->max_type)
39 		tb_info->tb[type] = attr;
40 
41 	return MNL_CB_OK;
42 }
43 
44 /* misc helpers */
45 
get_dev_name(const struct nlattr * nest)46 const char *get_dev_name(const struct nlattr *nest)
47 {
48 	const struct nlattr *tb[ETHTOOL_A_HEADER_MAX + 1] = {};
49 	DECLARE_ATTR_TB_INFO(tb);
50 	int ret;
51 
52 	if (!nest)
53 		return NULL;
54 	ret = mnl_attr_parse_nested(nest, attr_cb, &tb_info);
55 	if (ret < 0 || !tb[ETHTOOL_A_HEADER_DEV_NAME])
56 		return "(none)";
57 	return mnl_attr_get_str(tb[ETHTOOL_A_HEADER_DEV_NAME]);
58 }
59 
get_dev_info(const struct nlattr * nest,int * ifindex,char * ifname)60 int get_dev_info(const struct nlattr *nest, int *ifindex, char *ifname)
61 {
62 	const struct nlattr *tb[ETHTOOL_A_HEADER_MAX + 1] = {};
63 	const struct nlattr *index_attr;
64 	const struct nlattr *name_attr;
65 	DECLARE_ATTR_TB_INFO(tb);
66 	int ret;
67 
68 	if (ifindex)
69 		*ifindex = 0;
70 	if (ifname)
71 		memset(ifname, '\0', ALTIFNAMSIZ);
72 
73 	if (!nest)
74 		return -EFAULT;
75 	ret = mnl_attr_parse_nested(nest, attr_cb, &tb_info);
76 	index_attr = tb[ETHTOOL_A_HEADER_DEV_INDEX];
77 	name_attr = tb[ETHTOOL_A_HEADER_DEV_NAME];
78 	if (ret < 0 || (ifindex && !index_attr) || (ifname && !name_attr))
79 		return -EFAULT;
80 
81 	if (ifindex)
82 		*ifindex = mnl_attr_get_u32(index_attr);
83 	if (ifname) {
84 		strncpy(ifname, mnl_attr_get_str(name_attr), ALTIFNAMSIZ);
85 		if (ifname[ALTIFNAMSIZ - 1]) {
86 			ifname[ALTIFNAMSIZ - 1] = '\0';
87 			fprintf(stderr, "kernel device name too long: '%s'\n",
88 				mnl_attr_get_str(name_attr));
89 			return -EFAULT;
90 		}
91 	}
92 	return 0;
93 }
94 
95 /**
96  * netlink_cmd_check() - check support for netlink command
97  * @ctx:            ethtool command context
98  * @cmd:            netlink command id
99  * @devname:        device name from user
100  * @allow_wildcard: wildcard dumps supported
101  *
102  * Check if command @cmd is known to be unsupported based on ops information
103  * from genetlink family id request. Set nlctx->ioctl_fallback if ethtool
104  * should fall back to ioctl, i.e. when we do not know in advance that
105  * netlink request is supported. Set nlctx->wildcard_unsupported if "*" was
106  * used as device name but the request does not support wildcards (on either
107  * side).
108  *
109  * Return: true if we know the netlink request is not supported and should
110  * fail (and possibly fall back) without actually sending it to kernel.
111  */
netlink_cmd_check(struct cmd_context * ctx,unsigned int cmd,bool allow_wildcard)112 bool netlink_cmd_check(struct cmd_context *ctx, unsigned int cmd,
113 		       bool allow_wildcard)
114 {
115 	bool is_dump = !strcmp(ctx->devname, WILDCARD_DEVNAME);
116 	uint32_t cap = is_dump ? GENL_CMD_CAP_DUMP : GENL_CMD_CAP_DO;
117 	struct nl_context *nlctx = ctx->nlctx;
118 
119 	if (is_dump && !allow_wildcard) {
120 		nlctx->wildcard_unsupported = true;
121 		return true;
122 	}
123 	if (!nlctx->ops_info) {
124 		nlctx->ioctl_fallback = true;
125 		return false;
126 	}
127 	if (cmd > ETHTOOL_MSG_USER_MAX || !nlctx->ops_info[cmd].op_flags) {
128 		nlctx->ioctl_fallback = true;
129 		return true;
130 	}
131 
132 	if (is_dump && !(nlctx->ops_info[cmd].op_flags & GENL_CMD_CAP_DUMP))
133 		nlctx->wildcard_unsupported = true;
134 
135 	return !(nlctx->ops_info[cmd].op_flags & cap);
136 }
137 
138 struct ethtool_op_policy_query_ctx {
139 	struct nl_context *nlctx;
140 	unsigned int op;
141 	unsigned int op_hdr_attr;
142 
143 	bool op_policy_found;
144 	bool hdr_policy_found;
145 	unsigned int op_policy_idx;
146 	unsigned int hdr_policy_idx;
147 	uint64_t flag_mask;
148 };
149 
family_policy_find_op(struct ethtool_op_policy_query_ctx * policy_ctx,const struct nlattr * op_policy)150 static int family_policy_find_op(struct ethtool_op_policy_query_ctx *policy_ctx,
151 				 const struct nlattr *op_policy)
152 {
153 	const struct nlattr *attr;
154 	unsigned int type;
155 	int ret;
156 
157 	type = policy_ctx->nlctx->is_dump ?
158 		CTRL_ATTR_POLICY_DUMP : CTRL_ATTR_POLICY_DO;
159 
160 	mnl_attr_for_each_nested(attr, op_policy) {
161 		const struct nlattr *tb[CTRL_ATTR_POLICY_DUMP_MAX + 1] = {};
162 		DECLARE_ATTR_TB_INFO(tb);
163 
164 		if (mnl_attr_get_type(attr) != policy_ctx->op)
165 			continue;
166 
167 		ret = mnl_attr_parse_nested(attr, attr_cb, &tb_info);
168 		if (ret < 0)
169 			return ret;
170 
171 		if (!tb[type])
172 			continue;
173 
174 		policy_ctx->op_policy_found = true;
175 		policy_ctx->op_policy_idx = mnl_attr_get_u32(tb[type]);
176 		break;
177 	}
178 
179 	return 0;
180 }
181 
family_policy_cb(const struct nlmsghdr * nlhdr,void * data)182 static int family_policy_cb(const struct nlmsghdr *nlhdr, void *data)
183 {
184 	const struct nlattr *tba[NL_POLICY_TYPE_ATTR_MAX + 1] = {};
185 	DECLARE_ATTR_TB_INFO(tba);
186 	const struct nlattr *tb[CTRL_ATTR_MAX + 1] = {};
187 	DECLARE_ATTR_TB_INFO(tb);
188 	struct ethtool_op_policy_query_ctx *policy_ctx = data;
189 	const struct nlattr *policy_attr, *attr_attr, *attr;
190 	unsigned int attr_idx, policy_idx;
191 	int ret;
192 
193 	ret = mnl_attr_parse(nlhdr, GENL_HDRLEN, attr_cb, &tb_info);
194 	if (ret < 0)
195 		return MNL_CB_ERROR;
196 
197 	if (!policy_ctx->op_policy_found) {
198 		if (!tb[CTRL_ATTR_OP_POLICY]) {
199 			fprintf(stderr, "Error: op policy map not present\n");
200 			return MNL_CB_ERROR;
201 		}
202 		ret = family_policy_find_op(policy_ctx, tb[CTRL_ATTR_OP_POLICY]);
203 		return ret < 0 ? MNL_CB_ERROR : MNL_CB_OK;
204 	}
205 
206 	if (!tb[CTRL_ATTR_POLICY])
207 		return MNL_CB_OK;
208 
209 	policy_attr = mnl_attr_get_payload(tb[CTRL_ATTR_POLICY]);
210 	policy_idx = mnl_attr_get_type(policy_attr);
211 	attr_attr = mnl_attr_get_payload(policy_attr);
212 	attr_idx = mnl_attr_get_type(attr_attr);
213 
214 	ret = mnl_attr_parse_nested(attr_attr, attr_cb, &tba_info);
215 	if (ret < 0)
216 		return MNL_CB_ERROR;
217 
218 	if (policy_idx == policy_ctx->op_policy_idx &&
219 	    attr_idx == policy_ctx->op_hdr_attr) {
220 		attr = tba[NL_POLICY_TYPE_ATTR_POLICY_IDX];
221 		if (!attr) {
222 			fprintf(stderr,	"Error: no policy index in what was expected to be ethtool header attribute\n");
223 			return MNL_CB_ERROR;
224 		}
225 		policy_ctx->hdr_policy_found = true;
226 		policy_ctx->hdr_policy_idx = mnl_attr_get_u32(attr);
227 	}
228 
229 	if (policy_ctx->hdr_policy_found &&
230 	    policy_ctx->hdr_policy_idx == policy_idx &&
231 	    attr_idx == ETHTOOL_A_HEADER_FLAGS) {
232 		attr = tba[NL_POLICY_TYPE_ATTR_MASK];
233 		if (!attr) {
234 			fprintf(stderr,	"Error: validation mask not reported for ethtool header flags\n");
235 			return MNL_CB_ERROR;
236 		}
237 
238 		policy_ctx->flag_mask = mnl_attr_get_u64(attr);
239 	}
240 
241 	return MNL_CB_OK;
242 }
243 
read_flags_policy(struct nl_context * nlctx,struct nl_socket * nlsk,unsigned int nlcmd,unsigned int hdrattr)244 static int read_flags_policy(struct nl_context *nlctx, struct nl_socket *nlsk,
245 			     unsigned int nlcmd, unsigned int hdrattr)
246 {
247 	struct ethtool_op_policy_query_ctx policy_ctx;
248 	struct nl_msg_buff *msgbuff = &nlsk->msgbuff;
249 	int ret;
250 
251 	if (nlctx->ops_info[nlcmd].hdr_policy_loaded)
252 		return 0;
253 
254 	memset(&policy_ctx, 0, sizeof(policy_ctx));
255 	policy_ctx.nlctx = nlctx;
256 	policy_ctx.op = nlcmd;
257 	policy_ctx.op_hdr_attr = hdrattr;
258 
259 	ret = __msg_init(msgbuff, GENL_ID_CTRL, CTRL_CMD_GETPOLICY,
260 			 NLM_F_REQUEST | NLM_F_ACK | NLM_F_DUMP, 1);
261 	if (ret < 0)
262 		return ret;
263 	ret = -EMSGSIZE;
264 	if (ethnla_put_u16(msgbuff, CTRL_ATTR_FAMILY_ID, nlctx->ethnl_fam))
265 		return ret;
266 	if (ethnla_put_u32(msgbuff, CTRL_ATTR_OP, nlcmd))
267 		return ret;
268 
269 	nlsock_sendmsg(nlsk, NULL);
270 	nlsock_process_reply(nlsk, family_policy_cb, &policy_ctx);
271 
272 	nlctx->ops_info[nlcmd].hdr_policy_loaded = 1;
273 	nlctx->ops_info[nlcmd].hdr_flags = policy_ctx.flag_mask;
274 	return 0;
275 }
276 
get_stats_flag(struct nl_context * nlctx,unsigned int nlcmd,unsigned int hdrattr)277 u32 get_stats_flag(struct nl_context *nlctx, unsigned int nlcmd,
278 		   unsigned int hdrattr)
279 {
280 	if (!nlctx->ctx->show_stats)
281 		return 0;
282 	if (nlcmd > ETHTOOL_MSG_USER_MAX ||
283 	    !(nlctx->ops_info[nlcmd].op_flags & GENL_CMD_CAP_HASPOL))
284 		return 0;
285 
286 	if (read_flags_policy(nlctx, nlctx->ethnl_socket, nlcmd, hdrattr) < 0)
287 		return 0;
288 
289 	return nlctx->ops_info[nlcmd].hdr_flags & ETHTOOL_FLAG_STATS;
290 }
291 
292 /* initialization */
293 
genl_read_ops(struct nl_context * nlctx,const struct nlattr * ops_attr)294 static int genl_read_ops(struct nl_context *nlctx,
295 			 const struct nlattr *ops_attr)
296 {
297 	struct nl_op_info *ops_info;
298 	struct nlattr *op_attr;
299 	int ret;
300 
301 	ops_info = calloc(__ETHTOOL_MSG_USER_CNT, sizeof(ops_info[0]));
302 	if (!ops_info)
303 		return -ENOMEM;
304 
305 	mnl_attr_for_each_nested(op_attr, ops_attr) {
306 		const struct nlattr *tb[CTRL_ATTR_OP_MAX + 1] = {};
307 		DECLARE_ATTR_TB_INFO(tb);
308 		uint32_t op_id;
309 
310 		ret = mnl_attr_parse_nested(op_attr, attr_cb, &tb_info);
311 		if (ret < 0)
312 			goto err;
313 
314 		if (!tb[CTRL_ATTR_OP_ID] || !tb[CTRL_ATTR_OP_FLAGS])
315 			continue;
316 		op_id = mnl_attr_get_u32(tb[CTRL_ATTR_OP_ID]);
317 		if (op_id >= __ETHTOOL_MSG_USER_CNT)
318 			continue;
319 
320 		ops_info[op_id].op_flags =
321 			mnl_attr_get_u32(tb[CTRL_ATTR_OP_FLAGS]);
322 	}
323 
324 	nlctx->ops_info = ops_info;
325 	return 0;
326 err:
327 	free(ops_info);
328 	return ret;
329 }
330 
find_mc_group(struct nl_context * nlctx,struct nlattr * nest)331 static void find_mc_group(struct nl_context *nlctx, struct nlattr *nest)
332 {
333 	const struct nlattr *grp_tb[CTRL_ATTR_MCAST_GRP_MAX + 1] = {};
334 	DECLARE_ATTR_TB_INFO(grp_tb);
335 	struct nlattr *grp_attr;
336 	int ret;
337 
338 	mnl_attr_for_each_nested(grp_attr, nest) {
339 		ret = mnl_attr_parse_nested(grp_attr, attr_cb, &grp_tb_info);
340 		if (ret < 0)
341 			return;
342 		if (!grp_tb[CTRL_ATTR_MCAST_GRP_NAME] ||
343 		    !grp_tb[CTRL_ATTR_MCAST_GRP_ID])
344 			continue;
345 		if (strcmp(mnl_attr_get_str(grp_tb[CTRL_ATTR_MCAST_GRP_NAME]),
346 			   ETHTOOL_MCGRP_MONITOR_NAME))
347 			continue;
348 		nlctx->ethnl_mongrp =
349 			mnl_attr_get_u32(grp_tb[CTRL_ATTR_MCAST_GRP_ID]);
350 		return;
351 	}
352 }
353 
family_info_cb(const struct nlmsghdr * nlhdr,void * data)354 static int __maybe_unused family_info_cb(const struct nlmsghdr *nlhdr,
355 					 void *data)
356 {
357 	struct nl_context *nlctx = data;
358 	struct nlattr *attr;
359 	int ret;
360 
361 	mnl_attr_for_each(attr, nlhdr, GENL_HDRLEN) {
362 		switch (mnl_attr_get_type(attr)) {
363 		case CTRL_ATTR_FAMILY_ID:
364 			nlctx->ethnl_fam = mnl_attr_get_u16(attr);
365 			break;
366 		case CTRL_ATTR_OPS:
367 			ret = genl_read_ops(nlctx, attr);
368 			if (ret < 0)
369 				return MNL_CB_ERROR;
370 			break;
371 		case CTRL_ATTR_MCAST_GROUPS:
372 			find_mc_group(nlctx, attr);
373 			break;
374 		}
375 	}
376 
377 	return MNL_CB_OK;
378 }
379 
380 #ifdef TEST_ETHTOOL
get_genl_family(struct nl_context * nlctx __maybe_unused,struct nl_socket * nlsk __maybe_unused)381 static int get_genl_family(struct nl_context *nlctx __maybe_unused,
382 			   struct nl_socket *nlsk __maybe_unused)
383 {
384 	return 0;
385 }
386 #else
get_genl_family(struct nl_context * nlctx,struct nl_socket * nlsk)387 static int get_genl_family(struct nl_context *nlctx, struct nl_socket *nlsk)
388 {
389 	struct nl_msg_buff *msgbuff = &nlsk->msgbuff;
390 	int ret;
391 
392 	nlctx->suppress_nlerr = 2;
393 	ret = __msg_init(msgbuff, GENL_ID_CTRL, CTRL_CMD_GETFAMILY,
394 			 NLM_F_REQUEST | NLM_F_ACK, 1);
395 	if (ret < 0)
396 		goto out;
397 	ret = -EMSGSIZE;
398 	if (ethnla_put_strz(msgbuff, CTRL_ATTR_FAMILY_NAME, ETHTOOL_GENL_NAME))
399 		goto out;
400 
401 	nlsock_sendmsg(nlsk, NULL);
402 	nlsock_process_reply(nlsk, family_info_cb, nlctx);
403 	ret = nlctx->ethnl_fam ? 0 : -EADDRNOTAVAIL;
404 
405 out:
406 	nlctx->suppress_nlerr = 0;
407 	return ret;
408 }
409 #endif
410 
netlink_init(struct cmd_context * ctx)411 int netlink_init(struct cmd_context *ctx)
412 {
413 	struct nl_context *nlctx;
414 	int ret;
415 
416 	nlctx = calloc(1, sizeof(*nlctx));
417 	if (!nlctx)
418 		return -ENOMEM;
419 	nlctx->ctx = ctx;
420 	ret = nlsock_init(nlctx, &nlctx->ethnl_socket, NETLINK_GENERIC);
421 	if (ret < 0)
422 		goto out_free;
423 	ret = get_genl_family(nlctx, nlctx->ethnl_socket);
424 	if (ret < 0)
425 		goto out_nlsk;
426 
427 	ctx->nlctx = nlctx;
428 	return 0;
429 
430 out_nlsk:
431 	nlsock_done(nlctx->ethnl_socket);
432 out_free:
433 	free(nlctx->ops_info);
434 	free(nlctx);
435 	return ret;
436 }
437 
netlink_done(struct cmd_context * ctx)438 static void netlink_done(struct cmd_context *ctx)
439 {
440 	struct nl_context *nlctx = ctx->nlctx;
441 
442 	if (!nlctx)
443 		return;
444 
445 	nlsock_done(nlctx->ethnl_socket);
446 	nlsock_done(nlctx->ethnl2_socket);
447 	nlsock_done(nlctx->rtnl_socket);
448 	free(nlctx->ops_info);
449 	free(nlctx);
450 	ctx->nlctx = NULL;
451 	cleanup_all_strings();
452 }
453 
454 /**
455  * netlink_run_handler() - run netlink handler for subcommand
456  * @ctx:         command context
457  * @nlchk:       netlink capability check
458  * @nlfunc:      subcommand netlink handler to call
459  * @no_fallback: there is no ioctl fallback handler
460  *
461  * This function returns only if ioctl() handler should be run as fallback.
462  * Otherwise it exits with appropriate return code.
463  */
netlink_run_handler(struct cmd_context * ctx,nl_chk_t nlchk,nl_func_t nlfunc,bool no_fallback)464 void netlink_run_handler(struct cmd_context *ctx, nl_chk_t nlchk,
465 			 nl_func_t nlfunc, bool no_fallback)
466 {
467 	bool wildcard = ctx->devname && !strcmp(ctx->devname, WILDCARD_DEVNAME);
468 	bool wildcard_unsupported, ioctl_fallback;
469 	struct nl_context *nlctx;
470 	const char *reason;
471 	int ret;
472 
473 	if (nlchk && !nlchk(ctx)) {
474 		reason = "ioctl-only request";
475 		goto no_support;
476 	}
477 	if (ctx->devname && strlen(ctx->devname) >= ALTIFNAMSIZ) {
478 		fprintf(stderr, "device name '%s' longer than %u characters\n",
479 			ctx->devname, ALTIFNAMSIZ - 1);
480 		exit(1);
481 	}
482 
483 	if (!nlfunc) {
484 		reason = "ethtool netlink support for subcommand missing";
485 		goto no_support;
486 	}
487 	if (netlink_init(ctx)) {
488 		reason = "netlink interface initialization failed";
489 		goto no_support;
490 	}
491 	nlctx = ctx->nlctx;
492 
493 	ret = nlfunc(ctx);
494 	wildcard_unsupported = nlctx->wildcard_unsupported;
495 	ioctl_fallback = nlctx->ioctl_fallback;
496 	netlink_done(ctx);
497 
498 	if (no_fallback || ret != -EOPNOTSUPP || !ioctl_fallback) {
499 		if (wildcard_unsupported)
500 			fprintf(stderr, "%s\n",
501 				"subcommand does not support wildcard dump");
502 		exit(ret >= 0 ? ret : 1);
503 	}
504 	if (wildcard_unsupported)
505 		reason = "subcommand does not support wildcard dump";
506 	else
507 		reason = "kernel netlink support for subcommand missing";
508 
509 no_support:
510 	if (no_fallback) {
511 		fprintf(stderr, "%s, subcommand not supported by ioctl\n",
512 			reason);
513 		exit(1);
514 	}
515 	if (wildcard) {
516 		fprintf(stderr, "%s, wildcard dump not supported\n", reason);
517 		exit(1);
518 	}
519 	if (ctx->devname && strlen(ctx->devname) >= IFNAMSIZ) {
520 		fprintf(stderr,
521 			"%s, device name longer than %u not supported\n",
522 			reason, IFNAMSIZ - 1);
523 		exit(1);
524 	}
525 
526 	/* fallback to ioctl() */
527 }
528