1 /*
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright 2023 Baptiste Daroussin <bapt@FreeBSD.org>
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted providing that the following conditions~
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
16 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
19 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
23 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
24 * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
25 * POSSIBILITY OF SUCH DAMAGE.
26 */
27
28 #include <sys/param.h>
29 #include <sys/module.h>
30 #include <sys/socket.h>
31
32 #include <stdint.h>
33 #include <stdlib.h>
34 #include <unistd.h>
35 #include <err.h>
36 #include <stdio.h>
37 #include <poll.h>
38
39 #include <netlink/netlink.h>
40 #include <netlink/netlink_generic.h>
41 #include <netlink/netlink_snl.h>
42 #include <netlink/netlink_snl_generic.h>
43 #include <netlink/netlink_sysevent.h>
44
45 #include "genl.h"
46
47 static int monitor_mcast(int argc, char **argv);
48 static int list_families(int argc, char **argv);
49
50 static struct commands {
51 const char *name;
52 const char *usage;
53 int (*cmd)(int argc, char **argv);
54 } cmds[] = {
55 { "monitor", "monitor <family> [multicast group]", monitor_mcast },
56 { "list", "list", list_families },
57 };
58
59 static monitor_parser_t parser_nlctrl_notify;
60 static monitor_parser_t parser_nlsysevent;
61
62 static struct mcast_parsers {
63 const char *family;
64 monitor_parser_t *parser;
65 } mcast_parsers [] = {
66 { "nlctrl", parser_nlctrl_notify },
67 { "nlsysevent", parser_nlsysevent },
68 { "rpc", parser_rpc },
69 };
70
71 struct nlevent {
72 const char *name;
73 const char *subsystem;
74 const char *type;
75 const char *data;
76 };
77 #define _OUT(_field) offsetof(struct nlevent, _field)
78 static struct snl_attr_parser ap_nlevent_get[] = {
79 { .type = NLSE_ATTR_SYSTEM, .off = _OUT(name), .cb = snl_attr_get_string },
80 { .type = NLSE_ATTR_SUBSYSTEM, .off = _OUT(subsystem), .cb = snl_attr_get_string },
81 { .type = NLSE_ATTR_TYPE, .off = _OUT(type), .cb = snl_attr_get_string },
82 { .type = NLSE_ATTR_DATA, .off = _OUT(data), .cb = snl_attr_get_string },
83 };
84 #undef _OUT
85 SNL_DECLARE_GENL_PARSER(nlevent_get_parser, ap_nlevent_get);
86
87 /*
88 * We run our own parser(s) for CTRL_CMD_GETFAMILY instead of interfaces
89 * provided by <netlink_snl_generic.h>. One reason is that we want the parsed
90 * string attributes to point into long living memory, instead of inside of
91 * just received message, hence we use snl_attr_get_stringn() instead of the
92 * snl_attr_get_string(). The shared library uses the latter to avoid creating
93 * ambiguous memory leaks. Second reason is that genl(1) usage of the data is
94 * more extended than typical application that cares only of its own family and
95 * usually one group. We are going to cycle around all available.
96 */
97 struct genl_ctrl_op {
98 uint32_t id;
99 uint32_t flags;
100 };
101 struct genl_ctrl_ops {
102 uint32_t num_ops;
103 struct genl_ctrl_op **ops;
104 };
105 static struct snl_attr_parser nla_p_getops[] = {
106 #define _OUT(_field) offsetof(struct genl_ctrl_op, _field)
107 {
108 .type = CTRL_ATTR_OP_ID,
109 .off = _OUT(id),
110 .cb = snl_attr_get_uint32
111 },
112 {
113 .type = CTRL_ATTR_OP_FLAGS,
114 .off = _OUT(flags),
115 .cb = snl_attr_get_uint32
116 },
117 #undef _OUT
118 };
119 SNL_DECLARE_ATTR_PARSER_EXT(genl_ctrl_op_parser, sizeof(struct genl_ctrl_op),
120 nla_p_getops, NULL);
121
122 struct genl_mcast_group {
123 uint32_t id;
124 const char *name;
125 };
126 struct genl_mcast_groups {
127 uint32_t num_groups;
128 struct genl_mcast_group **groups;
129 };
130 static struct snl_attr_parser nla_p_getmc[] = {
131 #define _OUT(_field) offsetof(struct genl_mcast_group, _field)
132 {
133 .type = CTRL_ATTR_MCAST_GRP_NAME,
134 .off = _OUT(name),
135 .cb = snl_attr_get_stringn,
136 },
137 {
138 .type = CTRL_ATTR_MCAST_GRP_ID,
139 .off = _OUT(id),
140 .cb = snl_attr_get_uint32,
141 },
142 #undef _OUT
143 };
144 SNL_DECLARE_ATTR_PARSER_EXT(genl_mc_parser, sizeof(struct genl_mcast_group),
145 nla_p_getmc, NULL);
146
147 struct genl_family {
148 uint16_t id;
149 const char *name;
150 uint32_t version;
151 uint32_t hdrsize;
152 uint32_t max_attr;
153 struct genl_mcast_groups mcast_groups;
154 struct genl_ctrl_ops ops;
155 };
156
157 static struct snl_attr_parser nla_p_getfamily[] = {
158 #define _OUT(_field) offsetof(struct genl_family, _field)
159 {
160 .type = CTRL_ATTR_FAMILY_ID,
161 .off = _OUT(id),
162 .cb = snl_attr_get_uint16,
163 },
164 {
165 .type = CTRL_ATTR_FAMILY_NAME,
166 .off = _OUT(name),
167 .cb = snl_attr_get_stringn,
168 },
169 {
170 .type = CTRL_ATTR_VERSION,
171 .off = _OUT(version),
172 .cb = snl_attr_get_uint32,
173 },
174 {
175 .type = CTRL_ATTR_VERSION,
176 .off = _OUT(hdrsize),
177 .cb = snl_attr_get_uint32,
178 },
179 {
180 .type = CTRL_ATTR_MAXATTR,
181 .off = _OUT(max_attr),
182 .cb = snl_attr_get_uint32,
183 },
184 {
185 .type = CTRL_ATTR_OPS,
186 .off = _OUT(ops),
187 .cb = snl_attr_get_parray,
188 .arg = &genl_ctrl_op_parser,
189 },
190 {
191 .type = CTRL_ATTR_MCAST_GROUPS,
192 .off = _OUT(mcast_groups),
193 .cb = snl_attr_get_parray,
194 .arg = &genl_mc_parser,
195 },
196 #undef _OUT
197 };
198 SNL_DECLARE_GENL_PARSER(genl_family_parser, nla_p_getfamily);
199
200 static struct op_capability {
201 uint32_t flag;
202 const char *str;
203 } op_caps[] = {
204 { GENL_ADMIN_PERM, "requires admin permission" },
205 { GENL_CMD_CAP_DO, "can modify" },
206 { GENL_CMD_CAP_DUMP, "can get/dump" },
207 { GENL_CMD_CAP_HASPOL, "has policy" },
208 };
209
210 static void
dump_operations(struct genl_ctrl_ops * ops)211 dump_operations(struct genl_ctrl_ops *ops)
212 {
213 if (ops->num_ops == 0)
214 return;
215 printf("\tsupported operations: \n");
216 for (uint32_t i = 0; i < ops->num_ops; i++) {
217 bool p = true;
218
219 printf("\t - ID: %#02x, Capabilities: %#02x",
220 ops->ops[i]->id,
221 ops->ops[i]->flags);
222 for (size_t j = 0; j < nitems(op_caps); j++)
223 if ((ops->ops[i]->flags & op_caps[j].flag) ==
224 op_caps[j].flag) {
225 printf("%s%s", p ? " (" : "; ",
226 op_caps[j].str);
227 p = false;
228 }
229 printf("%s\n", p ? "" : ")");
230 }
231 }
232
233 static void
dump_mcast_groups(struct genl_mcast_groups * mcast_groups)234 dump_mcast_groups(struct genl_mcast_groups *mcast_groups)
235 {
236 if (mcast_groups->num_groups == 0)
237 return;
238 printf("\tmulticast groups: \n");
239 for (uint32_t i = 0; i < mcast_groups->num_groups; i++)
240 printf("\t - ID: %#02x, Name: %s\n",
241 mcast_groups->groups[i]->id,
242 mcast_groups->groups[i]->name);
243 }
244
245 static void
usage(void)246 usage(void)
247 {
248 fprintf(stderr, "Usage: %s\n", getprogname());
249 for (size_t i = 0; i < nitems(cmds); i++)
250 fprintf(stderr, " %s %s\n", getprogname(), cmds[i].usage);
251 }
252
253 static void
dump_family(struct genl_family * family)254 dump_family(struct genl_family *family)
255 {
256 printf("Name: %s\n\tID: %#02hx, Version: %#02x, "
257 "header size: %d, max attributes: %d\n",
258 family->name, family->id, family->version,
259 family->hdrsize, family->max_attr);
260 dump_operations(&family->ops);
261 dump_mcast_groups(&family->mcast_groups);
262 }
263
264 static void
parser_nlctrl_notify(struct snl_state * ss,struct nlmsghdr * hdr)265 parser_nlctrl_notify(struct snl_state *ss, struct nlmsghdr *hdr)
266 {
267 struct genl_family family = {};
268
269 if (snl_parse_nlmsg(ss, hdr, &genl_family_parser,
270 &family))
271 dump_family(&family);
272 }
273
274 static void
parser_nlsysevent(struct snl_state * ss,struct nlmsghdr * hdr)275 parser_nlsysevent(struct snl_state *ss, struct nlmsghdr *hdr)
276 {
277 struct nlevent ne = {};
278 if (snl_parse_nlmsg(ss, hdr, &nlevent_get_parser, &ne)) {
279 printf("system=%s subsystem=%s type=%s", ne.name, ne.subsystem, ne.type);
280 if (ne.data) {
281 printf(" %s", ne.data);
282 if (ne.data[strlen(ne.data) -1] != '\n')
283 printf("\n");
284 }
285 }
286 }
287
288 static void
parser_fallback(struct snl_state * ss __unused,struct nlmsghdr * hdr)289 parser_fallback(struct snl_state *ss __unused, struct nlmsghdr *hdr)
290 {
291 printf("Unknown message: type 0x%x, length %u\n",
292 hdr->nlmsg_type, hdr->nlmsg_len);
293 }
294
295 /* Populated by monitor_mcast() and may be used by protocol parser callbacks. */
296 static struct genl_family attrs;
297
298 const char *
group_name(uint32_t id)299 group_name(uint32_t id)
300 {
301 for (u_int i = 0; i < attrs.mcast_groups.num_groups; i++)
302 if (attrs.mcast_groups.groups[i]->id == id)
303 return (attrs.mcast_groups.groups[i]->name);
304 return ("???");
305 }
306
307 static int
monitor_mcast(int argc,char ** argv)308 monitor_mcast(int argc, char **argv)
309 {
310 struct snl_state ss;
311 struct snl_writer nw;
312 struct nlmsghdr *hdr;
313 struct pollfd pfd;
314 bool found = false;
315 bool all = false;
316 monitor_parser_t *parser;
317
318 if (argc < 1 || argc > 2) {
319 usage();
320 return (EXIT_FAILURE);
321 }
322
323 if (!snl_init(&ss, NETLINK_GENERIC))
324 err(EXIT_FAILURE, "snl_init()");
325 snl_init_writer(&ss, &nw);
326 snl_create_genl_msg_request(&nw, GENL_ID_CTRL, CTRL_CMD_GETFAMILY);
327 snl_add_msg_attr_string(&nw, CTRL_ATTR_FAMILY_NAME, argv[0]);
328 if ((hdr = snl_finalize_msg(&nw)) == NULL)
329 err(EXIT_FAILURE, "snl_finalize_msg");
330 if (!snl_send_message(&ss, hdr))
331 err(EXIT_FAILURE, "snl_send_message");
332 hdr = snl_read_reply(&ss, hdr->nlmsg_seq);
333 if (hdr == NULL)
334 err(EXIT_FAILURE, "snl_read_reply");
335 if (hdr->nlmsg_type == NLMSG_ERROR)
336 err(EXIT_FAILURE, "netlink(4) returned error");
337 memset(&attrs, 0, sizeof(attrs));
338 if (!snl_parse_nlmsg(&ss, hdr, &genl_family_parser, &attrs))
339 err(EXIT_FAILURE, "snl_parse_nlmsg CTRL_CMD_GETFAMILY");
340
341 if (argc == 1)
342 all = true;
343 for (u_int i = 0; i < attrs.mcast_groups.num_groups; i++) {
344 if (all ||
345 strcmp(attrs.mcast_groups.groups[i]->name, argv[1]) == 0) {
346 found = true;
347 if (setsockopt(ss.fd, SOL_NETLINK,
348 NETLINK_ADD_MEMBERSHIP,
349 &attrs.mcast_groups.groups[i]->id,
350 sizeof(attrs.mcast_groups.groups[i]->id))
351 == -1)
352 err(EXIT_FAILURE, "Cannot subscribe to command "
353 "notify");
354 if (!all)
355 break;
356 }
357 }
358 if (!found)
359 errx(EXIT_FAILURE, "No such multicast group '%s'"
360 " in family '%s'", argv[1], argv[0]);
361 parser = parser_fallback;
362 for (size_t i= 0; i < nitems(mcast_parsers); i++) {
363 if (strcmp(mcast_parsers[i].family, argv[0]) == 0) {
364 parser = mcast_parsers[i].parser;
365 break;
366 }
367 }
368 memset(&pfd, 0, sizeof(pfd));
369 pfd.fd = ss.fd;
370 pfd.events = POLLIN | POLLERR;
371 while (true) {
372 pfd.revents = 0;
373 if (poll(&pfd, 1, -1) == -1) {
374 if (errno == EINTR)
375 continue;
376 err(EXIT_FAILURE, "poll()");
377 }
378 hdr = snl_read_message(&ss);
379 if (hdr != NULL && hdr->nlmsg_type != NLMSG_ERROR)
380 parser(&ss, hdr);
381
382 }
383
384 return (EXIT_SUCCESS);
385 }
386
387 int
list_families(int argc,char ** argv __unused)388 list_families(int argc, char **argv __unused)
389 {
390 struct snl_state ss;
391 struct snl_writer nw;
392 struct nlmsghdr *hdr;
393 struct snl_errmsg_data e = {};
394 uint32_t seq_id;
395
396 if (argc != 0) {
397 usage();
398 return (EXIT_FAILURE);
399 }
400 if (!snl_init(&ss, NETLINK_GENERIC))
401 err(EXIT_FAILURE, "snl_init()");
402
403 snl_init_writer(&ss, &nw);
404 hdr = snl_create_genl_msg_request(&nw, GENL_ID_CTRL,
405 CTRL_CMD_GETFAMILY);
406 if ((hdr = snl_finalize_msg(&nw)) == NULL)
407 err(EXIT_FAILURE, "snl_finalize_msg");
408 seq_id = hdr->nlmsg_seq;
409 if (!snl_send_message(&ss, hdr))
410 err(EXIT_FAILURE, "snl_send_message");
411
412 while ((hdr = snl_read_reply_multi(&ss, seq_id, &e)) != NULL) {
413 if (e.error != 0) {
414 err(EXIT_FAILURE, "Error reading generic netlink");
415 }
416 struct genl_family family = {};
417 if (snl_parse_nlmsg(&ss, hdr, &genl_family_parser, &family))
418 dump_family(&family);
419 }
420
421 return (EXIT_SUCCESS);
422 }
423
424 int
main(int argc,char ** argv)425 main(int argc, char **argv)
426 {
427 if (modfind("netlink") == -1)
428 err(EXIT_FAILURE, "require netlink module to be loaded");
429
430 if (argc == 1)
431 return (list_families(0, NULL));
432
433 for (size_t i = 0; i < nitems(cmds); i++) {
434 if (strcmp(argv[1], cmds[i].name) == 0)
435 return (cmds[i].cmd(argc - 2, argv + 2));
436 }
437 usage();
438
439 return (EXIT_FAILURE);
440 }
441