xref: /freebsd/usr.bin/genl/genl.c (revision 88cd1e17a7d8ba66eb5fb04441dd9264d48708b1)
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
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 		printf("\t  - ID: %#02x, Capabilities: %#02x (",
218 		    ops->ops[i]->id,
219 		    ops->ops[i]->flags);
220 		for (size_t j = 0; j < nitems(op_caps); j++)
221 			if ((ops->ops[i]->flags & op_caps[j].flag) == op_caps[j].flag)
222 				printf("%s; ", op_caps[j].str);
223 		printf("\b\b)\n");
224 	}
225 }
226 
227 static void
228 dump_mcast_groups(struct genl_mcast_groups *mcast_groups)
229 {
230 	if (mcast_groups->num_groups == 0)
231 		return;
232 	printf("\tmulticast groups: \n");
233 	for (uint32_t i = 0; i < mcast_groups->num_groups; i++)
234 		printf("\t  - ID: %#02x, Name: %s\n",
235 		    mcast_groups->groups[i]->id,
236 		    mcast_groups->groups[i]->name);
237 }
238 
239 static void
240 usage(void)
241 {
242 	fprintf(stderr, "Usage: %s\n", getprogname());
243 	for (size_t i = 0; i < nitems(cmds); i++)
244 		fprintf(stderr, "       %s %s\n", getprogname(), cmds[i].usage);
245 }
246 
247 static void
248 dump_family(struct genl_family *family)
249 {
250 	printf("Name: %s\n\tID: %#02hx, Version: %#02x, "
251 	    "header size: %d, max attributes: %d\n",
252 	    family->name, family->id, family->version,
253 	    family->hdrsize, family->max_attr);
254 	dump_operations(&family->ops);
255 	dump_mcast_groups(&family->mcast_groups);
256 }
257 
258 static void
259 parser_nlctrl_notify(struct snl_state *ss, struct nlmsghdr *hdr)
260 {
261 	struct genl_family family = {};
262 
263 	if (snl_parse_nlmsg(ss, hdr, &genl_family_parser,
264 				&family))
265 		dump_family(&family);
266 }
267 
268 static void
269 parser_nlsysevent(struct snl_state *ss, struct nlmsghdr *hdr)
270 {
271 	struct nlevent ne = {};
272 	if (snl_parse_nlmsg(ss, hdr, &nlevent_get_parser, &ne)) {
273 		printf("system=%s subsystem=%s type=%s", ne.name, ne.subsystem, ne.type);
274 		if (ne.data) {
275 			printf(" %s", ne.data);
276 			if (ne.data[strlen(ne.data) -1] != '\n')
277 				printf("\n");
278 		}
279 	}
280 }
281 
282 static void
283 parser_fallback(struct snl_state *ss __unused, struct nlmsghdr *hdr)
284 {
285 	printf("Unknown message: type 0x%x, length %u\n",
286 	    hdr->nlmsg_type, hdr->nlmsg_len);
287 }
288 
289 /* Populated by monitor_mcast() and may be used by protocol parser callbacks. */
290 static struct genl_family attrs;
291 
292 const char *
293 group_name(uint32_t id)
294 {
295 	for (u_int i = 0; i < attrs.mcast_groups.num_groups; i++)
296 		if (attrs.mcast_groups.groups[i]->id == id)
297 			return (attrs.mcast_groups.groups[i]->name);
298 	return ("???");
299 }
300 
301 static int
302 monitor_mcast(int argc, char **argv)
303 {
304 	struct snl_state ss;
305 	struct snl_writer nw;
306 	struct nlmsghdr *hdr;
307 	struct pollfd pfd;
308 	bool found = false;
309 	bool all = false;
310 	monitor_parser_t *parser;
311 
312 	if (argc < 1 || argc > 2) {
313 		usage();
314 		return (EXIT_FAILURE);
315 	}
316 
317 	if (!snl_init(&ss, NETLINK_GENERIC))
318 		err(EXIT_FAILURE, "snl_init()");
319 	snl_init_writer(&ss, &nw);
320 	snl_create_genl_msg_request(&nw, GENL_ID_CTRL, CTRL_CMD_GETFAMILY);
321 	snl_add_msg_attr_string(&nw, CTRL_ATTR_FAMILY_NAME, argv[0]);
322 	if ((hdr = snl_finalize_msg(&nw)) == NULL)
323 		err(EXIT_FAILURE, "snl_finalize_msg");
324 	if (!snl_send_message(&ss, hdr))
325 		err(EXIT_FAILURE, "snl_send_message");
326 	hdr = snl_read_reply(&ss, hdr->nlmsg_seq);
327 	if (hdr == NULL)
328 		err(EXIT_FAILURE, "snl_read_reply");
329 	if (hdr->nlmsg_type == NLMSG_ERROR)
330 		err(EXIT_FAILURE, "netlink(4) returned error");
331 	memset(&attrs, 0, sizeof(attrs));
332 	if (!snl_parse_nlmsg(&ss, hdr, &genl_family_parser, &attrs))
333 		err(EXIT_FAILURE, "snl_parse_nlmsg CTRL_CMD_GETFAMILY");
334 
335 	if (argc == 1)
336 		all = true;
337 	for (u_int i = 0; i < attrs.mcast_groups.num_groups; i++) {
338 		if (all ||
339 		    strcmp(attrs.mcast_groups.groups[i]->name, argv[1]) == 0) {
340 			found = true;
341 			if (setsockopt(ss.fd, SOL_NETLINK,
342 			    NETLINK_ADD_MEMBERSHIP,
343 			    &attrs.mcast_groups.groups[i]->id,
344 			    sizeof(attrs.mcast_groups.groups[i]->id))
345 			    == -1)
346 				err(EXIT_FAILURE, "Cannot subscribe to command "
347 				    "notify");
348 			if (!all)
349 				break;
350 		}
351 	}
352 	if (!found)
353 		errx(EXIT_FAILURE, "No such multicast group '%s'"
354 		    " in family '%s'", argv[1], argv[0]);
355 	parser = parser_fallback;
356 	for (size_t i= 0; i < nitems(mcast_parsers); i++) {
357 		if (strcmp(mcast_parsers[i].family, argv[0]) == 0) {
358 			parser = mcast_parsers[i].parser;
359 			break;
360 		}
361 	}
362 	memset(&pfd, 0, sizeof(pfd));
363 	pfd.fd = ss.fd;
364 	pfd.events = POLLIN | POLLERR;
365 	while (true) {
366 		pfd.revents = 0;
367 		if (poll(&pfd, 1, -1) == -1) {
368 			if (errno == EINTR)
369 				continue;
370 			err(EXIT_FAILURE, "poll()");
371 		}
372 		hdr = snl_read_message(&ss);
373 		if (hdr != NULL && hdr->nlmsg_type != NLMSG_ERROR)
374 			parser(&ss, hdr);
375 
376 	}
377 
378 	return (EXIT_SUCCESS);
379 }
380 
381 int
382 list_families(int argc, char **argv __unused)
383 {
384 	struct snl_state ss;
385 	struct snl_writer nw;
386 	struct nlmsghdr *hdr;
387 	struct snl_errmsg_data e = {};
388 	uint32_t seq_id;
389 
390 	if (argc != 0) {
391 		usage();
392 		return (EXIT_FAILURE);
393 	}
394 	if (!snl_init(&ss, NETLINK_GENERIC))
395 		err(EXIT_FAILURE, "snl_init()");
396 
397 	snl_init_writer(&ss, &nw);
398 	hdr = snl_create_genl_msg_request(&nw, GENL_ID_CTRL,
399 	    CTRL_CMD_GETFAMILY);
400 	if ((hdr = snl_finalize_msg(&nw)) == NULL)
401 		err(EXIT_FAILURE, "snl_finalize_msg");
402 	seq_id = hdr->nlmsg_seq;
403 	if (!snl_send_message(&ss, hdr))
404 		err(EXIT_FAILURE, "snl_send_message");
405 
406 	while ((hdr = snl_read_reply_multi(&ss, seq_id, &e)) != NULL) {
407 		if (e.error != 0) {
408 			err(EXIT_FAILURE, "Error reading generic netlink");
409 		}
410 		struct genl_family family = {};
411 		if (snl_parse_nlmsg(&ss, hdr, &genl_family_parser, &family))
412 			dump_family(&family);
413 	}
414 
415 	return (EXIT_SUCCESS);
416 }
417 
418 int
419 main(int argc, char **argv)
420 {
421 	if (modfind("netlink") == -1)
422 		err(EXIT_FAILURE, "require netlink module to be loaded");
423 
424 	if (argc == 1)
425 		return (list_families(0, NULL));
426 
427 	for (size_t i = 0; i < nitems(cmds); i++) {
428 		if (strcmp(argv[1], cmds[i].name) == 0)
429 			return (cmds[i].cmd(argc - 2, argv + 2));
430 	}
431 	usage();
432 
433 	return (EXIT_FAILURE);
434 }
435