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