xref: /freebsd/usr.bin/genl/genl.c (revision 40462a376a324c19845a5f696c565069142ef326)
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 static int monitor_mcast(int argc, char **argv);
46 static int list_families(int argc, char **argv);
47 static void parser_nlctrl_notify(struct snl_state *ss, struct nlmsghdr *hdr);
48 static void parser_nlsysevent(struct snl_state *ss, struct nlmsghdr *hdr);
49 static void parser_fallback(struct snl_state *ss, struct nlmsghdr *hdr);
50 
51 static struct commands {
52 	const char *name;
53 	const char *usage;
54 	int (*cmd)(int argc, char **argv);
55 } cmds[] = {
56 	{ "monitor", "monitor <family> [multicast group]", monitor_mcast },
57 	{ "list", "list", list_families },
58 };
59 
60 static struct mcast_parsers {
61 	const char *family;
62 	void (*parser)(struct snl_state *ss, struct nlmsghdr *hdr);
63 } mcast_parsers [] = {
64 	{ "nlctrl", parser_nlctrl_notify },
65 	{ "nlsysevent", parser_nlsysevent },
66 };
67 
68 struct nlevent {
69 	const char *name;
70 	const char *subsystem;
71 	const char *type;
72 	const char *data;
73 };
74 #define _OUT(_field) offsetof(struct nlevent, _field)
75 static struct snl_attr_parser ap_nlevent_get[] = {
76 	{ .type = NLSE_ATTR_SYSTEM, .off = _OUT(name), .cb = snl_attr_get_string },
77 	{ .type = NLSE_ATTR_SUBSYSTEM, .off = _OUT(subsystem), .cb = snl_attr_get_string },
78 	{ .type = NLSE_ATTR_TYPE, .off = _OUT(type), .cb = snl_attr_get_string },
79 	{ .type = NLSE_ATTR_DATA, .off = _OUT(data), .cb = snl_attr_get_string },
80 };
81 #undef _OUT
82 SNL_DECLARE_GENL_PARSER(nlevent_get_parser, ap_nlevent_get);
83 
84 /*
85  * We run our own parser(s) for CTRL_CMD_GETFAMILY instead of interfaces
86  * provided by <netlink_snl_generic.h>.  One reason is that we want the parsed
87  * string attributes to point into long living memory, instead of inside of
88  * just received message, hence we use snl_attr_get_stringn() instead of the
89  * snl_attr_get_string().  The shared library uses the latter to avoid creating
90  * ambiguous memory leaks.  Second reason is that genl(1) usage of the data is
91  * more extended than typical application that cares only of its own family and
92  * usually one group.  We are going to cycle around all available.
93  */
94 struct genl_ctrl_op {
95 	uint32_t id;
96 	uint32_t flags;
97 };
98 struct genl_ctrl_ops {
99 	uint32_t num_ops;
100 	struct genl_ctrl_op **ops;
101 };
102 static struct snl_attr_parser nla_p_getops[] = {
103 #define _OUT(_field)	offsetof(struct genl_ctrl_op, _field)
104 	{
105 		.type = CTRL_ATTR_OP_ID,
106 		.off = _OUT(id),
107 		.cb = snl_attr_get_uint32
108 	},
109 	{
110 		.type = CTRL_ATTR_OP_FLAGS,
111 		.off = _OUT(flags),
112 		.cb = snl_attr_get_uint32
113 	},
114 #undef _OUT
115 };
116 SNL_DECLARE_ATTR_PARSER_EXT(genl_ctrl_op_parser, sizeof(struct genl_ctrl_op),
117     nla_p_getops, NULL);
118 
119 struct genl_mcast_group {
120 	uint32_t id;
121 	const char *name;
122 };
123 struct genl_mcast_groups {
124 	uint32_t num_groups;
125 	struct genl_mcast_group **groups;
126 };
127 static struct snl_attr_parser nla_p_getmc[] = {
128 #define	_OUT(_field)	offsetof(struct genl_mcast_group, _field)
129 	{
130 		.type = CTRL_ATTR_MCAST_GRP_NAME,
131 		.off = _OUT(name),
132 		.cb = snl_attr_get_stringn,
133 	},
134 	{
135 		.type = CTRL_ATTR_MCAST_GRP_ID,
136 		.off = _OUT(id),
137 		.cb = snl_attr_get_uint32,
138 	},
139 #undef _OUT
140 };
141 SNL_DECLARE_ATTR_PARSER_EXT(genl_mc_parser, sizeof(struct genl_mcast_group),
142     nla_p_getmc, NULL);
143 
144 struct genl_family {
145 	uint16_t id;
146 	const char *name;
147 	uint32_t version;
148 	uint32_t hdrsize;
149 	uint32_t max_attr;
150 	struct genl_mcast_groups mcast_groups;
151 	struct genl_ctrl_ops ops;
152 };
153 
154 static struct snl_attr_parser nla_p_getfamily[] = {
155 #define	_OUT(_field)	offsetof(struct genl_family, _field)
156 	{
157 		.type = CTRL_ATTR_FAMILY_ID,
158 		.off = _OUT(id),
159 		.cb = snl_attr_get_uint16,
160 	},
161 	{
162 		.type = CTRL_ATTR_FAMILY_NAME,
163 		.off = _OUT(name),
164 		.cb = snl_attr_get_stringn,
165 	},
166 	{
167 		.type = CTRL_ATTR_VERSION,
168 		.off = _OUT(version),
169 		.cb = snl_attr_get_uint32,
170 	},
171 	{
172 		.type = CTRL_ATTR_VERSION,
173 		.off = _OUT(hdrsize),
174 		.cb = snl_attr_get_uint32,
175 	},
176 	{
177 		.type = CTRL_ATTR_MAXATTR,
178 		.off = _OUT(max_attr),
179 		.cb = snl_attr_get_uint32,
180 	},
181 	{
182 		.type = CTRL_ATTR_OPS,
183 		.off = _OUT(ops),
184 		.cb = snl_attr_get_parray,
185 		.arg = &genl_ctrl_op_parser,
186 	},
187 	{
188 		.type = CTRL_ATTR_MCAST_GROUPS,
189 		.off = _OUT(mcast_groups),
190 		.cb = snl_attr_get_parray,
191 		.arg = &genl_mc_parser,
192 	},
193 #undef _OUT
194 };
195 SNL_DECLARE_GENL_PARSER(genl_family_parser, nla_p_getfamily);
196 
197 static struct op_capability {
198 	uint32_t flag;
199 	const char *str;
200 } op_caps[] = {
201 	{ GENL_ADMIN_PERM, "requires admin permission" },
202 	{ GENL_CMD_CAP_DO, "can modify" },
203 	{ GENL_CMD_CAP_DUMP, "can get/dump" },
204 	{ GENL_CMD_CAP_HASPOL, "has policy" },
205 };
206 
207 static void
208 dump_operations(struct genl_ctrl_ops *ops)
209 {
210 	if (ops->num_ops == 0)
211 		return;
212 	printf("\tsupported operations: \n");
213 	for (uint32_t i = 0; i < ops->num_ops; i++) {
214 		printf("\t  - ID: %#02x, Capabilities: %#02x (",
215 		    ops->ops[i]->id,
216 		    ops->ops[i]->flags);
217 		for (size_t j = 0; j < nitems(op_caps); j++)
218 			if ((ops->ops[i]->flags & op_caps[j].flag) == op_caps[j].flag)
219 				printf("%s; ", op_caps[j].str);
220 		printf("\b\b)\n");
221 	}
222 }
223 
224 static void
225 dump_mcast_groups(struct genl_mcast_groups *mcast_groups)
226 {
227 	if (mcast_groups->num_groups == 0)
228 		return;
229 	printf("\tmulticast groups: \n");
230 	for (uint32_t i = 0; i < mcast_groups->num_groups; i++)
231 		printf("\t  - ID: %#02x, Name: %s\n",
232 		    mcast_groups->groups[i]->id,
233 		    mcast_groups->groups[i]->name);
234 }
235 
236 static void
237 usage(void)
238 {
239 	fprintf(stderr, "Usage: %s\n", getprogname());
240 	for (size_t i = 0; i < nitems(cmds); i++)
241 		fprintf(stderr, "       %s %s\n", getprogname(), cmds[i].usage);
242 }
243 
244 static void
245 dump_family(struct genl_family *family)
246 {
247 	printf("Name: %s\n\tID: %#02hx, Version: %#02x, "
248 	    "header size: %d, max attributes: %d\n",
249 	    family->name, family->id, family->version,
250 	    family->hdrsize, family->max_attr);
251 	dump_operations(&family->ops);
252 	dump_mcast_groups(&family->mcast_groups);
253 }
254 
255 void
256 parser_nlctrl_notify(struct snl_state *ss, struct nlmsghdr *hdr)
257 {
258 	struct genl_family family = {};
259 
260 	if (snl_parse_nlmsg(ss, hdr, &genl_family_parser,
261 				&family))
262 		dump_family(&family);
263 }
264 
265 void
266 parser_nlsysevent(struct snl_state *ss, struct nlmsghdr *hdr)
267 {
268 	struct nlevent ne = {};
269 	if (snl_parse_nlmsg(ss, hdr, &nlevent_get_parser, &ne)) {
270 		printf("system=%s subsystem=%s type=%s", ne.name, ne.subsystem, ne.type);
271 		if (ne.data) {
272 			printf(" %s", ne.data);
273 			if (ne.data[strlen(ne.data) -1] != '\n')
274 				printf("\n");
275 		}
276 	}
277 }
278 
279 void
280 parser_fallback(struct snl_state *ss __unused, struct nlmsghdr *hdr __unused)
281 {
282 	printf("New unknown message\n");
283 }
284 
285 /* Populated by monitor_mcast() and may be used by protocol parser callbacks. */
286 static struct genl_family attrs;
287 
288 static int
289 monitor_mcast(int argc, char **argv)
290 {
291 	struct snl_state ss;
292 	struct snl_writer nw;
293 	struct nlmsghdr *hdr;
294 	struct pollfd pfd;
295 	bool found = false;
296 	bool all = false;
297 	void (*parser)(struct snl_state *ss, struct nlmsghdr *hdr);
298 
299 	if (argc < 1 || argc > 2) {
300 		usage();
301 		return (EXIT_FAILURE);
302 	}
303 
304 	if (!snl_init(&ss, NETLINK_GENERIC))
305 		err(EXIT_FAILURE, "snl_init()");
306 	snl_init_writer(&ss, &nw);
307 	snl_create_genl_msg_request(&nw, GENL_ID_CTRL, CTRL_CMD_GETFAMILY);
308 	snl_add_msg_attr_string(&nw, CTRL_ATTR_FAMILY_NAME, argv[0]);
309 	if ((hdr = snl_finalize_msg(&nw)) == NULL)
310 		err(EXIT_FAILURE, "snl_finalize_msg");
311 	if (!snl_send_message(&ss, hdr))
312 		err(EXIT_FAILURE, "snl_send_message");
313 	hdr = snl_read_reply(&ss, hdr->nlmsg_seq);
314 	if (hdr == NULL)
315 		err(EXIT_FAILURE, "snl_read_reply");
316 	if (hdr->nlmsg_type == NLMSG_ERROR)
317 		err(EXIT_FAILURE, "netlink(4) returned error");
318 	memset(&attrs, 0, sizeof(attrs));
319 	if (!snl_parse_nlmsg(&ss, hdr, &genl_family_parser, &attrs))
320 		err(EXIT_FAILURE, "snl_parse_nlmsg CTRL_CMD_GETFAMILY");
321 
322 	if (argc == 1)
323 		all = true;
324 	for (u_int i = 0; i < attrs.mcast_groups.num_groups; i++) {
325 		if (all ||
326 		    strcmp(attrs.mcast_groups.groups[i]->name, argv[1]) == 0) {
327 			found = true;
328 			if (setsockopt(ss.fd, SOL_NETLINK,
329 			    NETLINK_ADD_MEMBERSHIP,
330 			    &attrs.mcast_groups.groups[i]->id,
331 			    sizeof(attrs.mcast_groups.groups[i]->id))
332 			    == -1)
333 				err(EXIT_FAILURE, "Cannot subscribe to command "
334 				    "notify");
335 			if (!all)
336 				break;
337 		}
338 	}
339 	if (!found)
340 		errx(EXIT_FAILURE, "No such multicast group '%s'"
341 		    " in family '%s'", argv[1], argv[0]);
342 	parser = parser_fallback;
343 	for (size_t i= 0; i < nitems(mcast_parsers); i++) {
344 		if (strcmp(mcast_parsers[i].family, argv[0]) == 0) {
345 			parser = mcast_parsers[i].parser;
346 			break;
347 		}
348 	}
349 	memset(&pfd, 0, sizeof(pfd));
350 	pfd.fd = ss.fd;
351 	pfd.events = POLLIN | POLLERR;
352 	while (true) {
353 		pfd.revents = 0;
354 		if (poll(&pfd, 1, -1) == -1) {
355 			if (errno == EINTR)
356 				continue;
357 			err(EXIT_FAILURE, "poll()");
358 		}
359 		hdr = snl_read_message(&ss);
360 		if (hdr != NULL && hdr->nlmsg_type != NLMSG_ERROR)
361 			parser(&ss, hdr);
362 
363 	}
364 
365 	return (EXIT_SUCCESS);
366 }
367 
368 int
369 list_families(int argc, char **argv __unused)
370 {
371 	struct snl_state ss;
372 	struct snl_writer nw;
373 	struct nlmsghdr *hdr;
374 	struct snl_errmsg_data e = {};
375 	uint32_t seq_id;
376 
377 	if (argc != 0) {
378 		usage();
379 		return (EXIT_FAILURE);
380 	}
381 	if (!snl_init(&ss, NETLINK_GENERIC))
382 		err(EXIT_FAILURE, "snl_init()");
383 
384 	snl_init_writer(&ss, &nw);
385 	hdr = snl_create_genl_msg_request(&nw, GENL_ID_CTRL,
386 	    CTRL_CMD_GETFAMILY);
387 	if ((hdr = snl_finalize_msg(&nw)) == NULL)
388 		err(EXIT_FAILURE, "snl_finalize_msg");
389 	seq_id = hdr->nlmsg_seq;
390 	if (!snl_send_message(&ss, hdr))
391 		err(EXIT_FAILURE, "snl_send_message");
392 
393 	while ((hdr = snl_read_reply_multi(&ss, seq_id, &e)) != NULL) {
394 		if (e.error != 0) {
395 			err(EXIT_FAILURE, "Error reading generic netlink");
396 		}
397 		struct genl_family family = {};
398 		if (snl_parse_nlmsg(&ss, hdr, &genl_family_parser, &family))
399 			dump_family(&family);
400 	}
401 
402 	return (EXIT_SUCCESS);
403 }
404 
405 int
406 main(int argc, char **argv)
407 {
408 	if (modfind("netlink") == -1)
409 		err(EXIT_FAILURE, "require netlink module to be loaded");
410 
411 	if (argc == 1)
412 		return (list_families(0, NULL));
413 
414 	for (size_t i = 0; i < nitems(cmds); i++) {
415 		if (strcmp(argv[1], cmds[i].name) == 0)
416 			return (cmds[i].cmd(argc - 2, argv + 2));
417 	}
418 	usage();
419 
420 	return (EXIT_FAILURE);
421 }
422