xref: /freebsd/usr.bin/genl/genl.c (revision 5036d9652a5701d00e9e40ea942c278e9f77d33d)
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 struct genl_ctrl_op {
85 	uint32_t id;
86 	uint32_t flags;
87 };
88 
89 struct genl_ctrl_ops {
90 	uint32_t num_ops;
91 	struct genl_ctrl_op **ops;
92 };
93 
94 #define _OUT(_field)	offsetof(struct genl_ctrl_op, _field)
95 static struct snl_attr_parser _nla_p_getops[] = {
96 	{ .type = CTRL_ATTR_OP_ID, .off = _OUT(id), .cb = snl_attr_get_uint32},
97 	{ .type = CTRL_ATTR_OP_FLAGS, .off = _OUT(flags), .cb = snl_attr_get_uint32 },
98 };
99 #undef _OUT
100 SNL_DECLARE_ATTR_PARSER_EXT(genl_ctrl_op_parser,
101 		sizeof(struct genl_ctrl_op),
102 		_nla_p_getops, NULL);
103 
104 struct genl_family {
105 	uint16_t id;
106 	char *name;
107 	uint32_t version;
108 	uint32_t hdrsize;
109 	uint32_t max_attr;
110 	struct snl_genl_ctrl_mcast_groups mcast_groups;
111 	struct genl_ctrl_ops ops;
112 };
113 
114 #define	_OUT(_field)	offsetof(struct genl_family, _field)
115 static struct snl_attr_parser _nla_p_getfamily[] = {
116 	{ .type = CTRL_ATTR_FAMILY_ID , .off = _OUT(id), .cb = snl_attr_get_uint16 },
117 	{ .type = CTRL_ATTR_FAMILY_NAME, .off = _OUT(name), .cb = snl_attr_get_string },
118 	{ .type = CTRL_ATTR_VERSION, .off = _OUT(version), .cb = snl_attr_get_uint32 },
119 	{ .type = CTRL_ATTR_VERSION, .off = _OUT(hdrsize), .cb = snl_attr_get_uint32 },
120 	{ .type = CTRL_ATTR_MAXATTR, .off = _OUT(max_attr), .cb = snl_attr_get_uint32 },
121 	{
122 		.type = CTRL_ATTR_OPS,
123 		.off = _OUT(ops),
124 		.cb = snl_attr_get_parray,
125 		.arg = &genl_ctrl_op_parser,
126 	},
127 	{
128 		.type = CTRL_ATTR_MCAST_GROUPS,
129 		.off = _OUT(mcast_groups),
130 		.cb = snl_attr_get_parray,
131 		.arg = &_genl_ctrl_mc_parser,
132 	},
133 };
134 #undef _OUT
135 SNL_DECLARE_GENL_PARSER(genl_family_parser, _nla_p_getfamily);
136 
137 static struct op_capability {
138 	uint32_t flag;
139 	const char *str;
140 } op_caps[] = {
141 	{ GENL_ADMIN_PERM, "requires admin permission" },
142 	{ GENL_CMD_CAP_DO, "can modify" },
143 	{ GENL_CMD_CAP_DUMP, "can get/dump" },
144 	{ GENL_CMD_CAP_HASPOL, "has policy" },
145 };
146 
147 static void
148 dump_operations(struct genl_ctrl_ops *ops)
149 {
150 	if (ops->num_ops == 0)
151 		return;
152 	printf("\tsupported operations: \n");
153 	for (uint32_t i = 0; i < ops->num_ops; i++) {
154 		printf("\t  - ID: %#02x, Capabilities: %#02x (",
155 		    ops->ops[i]->id,
156 		    ops->ops[i]->flags);
157 		for (size_t j = 0; j < nitems(op_caps); j++)
158 			if ((ops->ops[i]->flags & op_caps[j].flag) == op_caps[j].flag)
159 				printf("%s; ", op_caps[j].str);
160 		printf("\b\b)\n");
161 	}
162 }
163 
164 static void
165 dump_mcast_groups( struct snl_genl_ctrl_mcast_groups *mcast_groups)
166 {
167 	if (mcast_groups->num_groups == 0)
168 		return;
169 	printf("\tmulticast groups: \n");
170 	for (uint32_t i = 0; i < mcast_groups->num_groups; i++)
171 		printf("\t  - ID: %#02x, Name: %s\n",
172 		    mcast_groups->groups[i]->mcast_grp_id,
173 		    mcast_groups->groups[i]->mcast_grp_name);
174 }
175 
176 static void
177 usage(void)
178 {
179 	fprintf(stderr, "Usage: %s\n", getprogname());
180 	for (size_t i = 0; i < nitems(cmds); i++)
181 		fprintf(stderr, "       %s %s\n", getprogname(), cmds[i].usage);
182 }
183 
184 static void
185 dump_family(struct genl_family *family)
186 {
187 	printf("Name: %s\n\tID: %#02hx, Version: %#02x, "
188 	    "header size: %d, max attributes: %d\n",
189 	    family->name, family->id, family->version,
190 	    family->hdrsize, family->max_attr);
191 	dump_operations(&family->ops);
192 	dump_mcast_groups(&family->mcast_groups);
193 }
194 
195 void
196 parser_nlctrl_notify(struct snl_state *ss, struct nlmsghdr *hdr)
197 {
198 	struct genl_family family = {};
199 
200 	if (snl_parse_nlmsg(ss, hdr, &genl_family_parser,
201 				&family))
202 		dump_family(&family);
203 }
204 
205 void
206 parser_nlsysevent(struct snl_state *ss, struct nlmsghdr *hdr)
207 {
208 	struct nlevent ne = {};
209 	if (snl_parse_nlmsg(ss, hdr, &nlevent_get_parser, &ne)) {
210 		printf("system=%s subsystem=%s type=%s", ne.name, ne.subsystem, ne.type);
211 		if (ne.data) {
212 			printf(" %s", ne.data);
213 			if (ne.data[strlen(ne.data) -1] != '\n')
214 				printf("\n");
215 		}
216 	}
217 }
218 
219 void
220 parser_fallback(struct snl_state *ss __unused, struct nlmsghdr *hdr __unused)
221 {
222 	printf("New unknown message\n");
223 }
224 
225 int
226 monitor_mcast(int argc __unused, char **argv)
227 {
228 	struct snl_state ss;
229 	struct nlmsghdr *hdr;
230 	struct _getfamily_attrs attrs;
231 	struct pollfd pfd;
232 	bool found = false;
233 	bool all = false;
234 	void (*parser)(struct snl_state *ss, struct nlmsghdr *hdr);
235 
236 	parser = parser_fallback;
237 
238 	if (!snl_init(&ss, NETLINK_GENERIC))
239 		err(EXIT_FAILURE, "snl_init()");
240 
241 	if (argc < 1 || argc > 2) {
242 		usage();
243 		return (EXIT_FAILURE);
244 	}
245 
246 	if (!snl_get_genl_family_info(&ss, argv[0], &attrs))
247 		errx(EXIT_FAILURE, "Unknown family '%s'", argv[0]);
248 	if (argc == 1)
249 		all = true;
250 	for (unsigned int i = 0; i < attrs.mcast_groups.num_groups; i++) {
251 		if (all || strcmp(attrs.mcast_groups.groups[i]->mcast_grp_name,
252 		    argv[1]) == 0) {
253 			found = true;
254 			if (setsockopt(ss.fd, SOL_NETLINK,
255 			    NETLINK_ADD_MEMBERSHIP,
256 			    &attrs.mcast_groups.groups[i]->mcast_grp_id,
257 			    sizeof(attrs.mcast_groups.groups[i]->mcast_grp_id))
258 			    == -1)
259 				err(EXIT_FAILURE, "Cannot subscribe to command "
260 				    "notify");
261 			if (!all)
262 				break;
263 		}
264 	}
265 	if (!found)
266 		errx(EXIT_FAILURE, "No such multicast group '%s'"
267 		    " in family '%s'", argv[1], argv[0]);
268 	for (size_t i= 0; i < nitems(mcast_parsers); i++) {
269 		if (strcmp(mcast_parsers[i].family, argv[0]) == 0) {
270 			parser = mcast_parsers[i].parser;
271 			break;
272 		}
273 	}
274 	memset(&pfd, 0, sizeof(pfd));
275 	pfd.fd = ss.fd;
276 	pfd.events = POLLIN | POLLERR;
277 	while (true) {
278 		pfd.revents = 0;
279 		if (poll(&pfd, 1, -1) == -1) {
280 			if (errno == EINTR)
281 				continue;
282 			err(EXIT_FAILURE, "poll()");
283 		}
284 		hdr = snl_read_message(&ss);
285 		if (hdr != NULL && hdr->nlmsg_type != NLMSG_ERROR)
286 			parser(&ss, hdr);
287 
288 	}
289 
290 	return (EXIT_SUCCESS);
291 }
292 
293 int
294 list_families(int argc, char **argv __unused)
295 {
296 	struct snl_state ss;
297 	struct snl_writer nw;
298 	struct nlmsghdr *hdr;
299 	struct snl_errmsg_data e = {};
300 	uint32_t seq_id;
301 
302 	if (argc != 0) {
303 		usage();
304 		return (EXIT_FAILURE);
305 	}
306 	if (!snl_init(&ss, NETLINK_GENERIC))
307 		err(EXIT_FAILURE, "snl_init()");
308 
309 	snl_init_writer(&ss, &nw);
310 	hdr = snl_create_genl_msg_request(&nw, GENL_ID_CTRL,
311 	    CTRL_CMD_GETFAMILY);
312 	if ((hdr = snl_finalize_msg(&nw)) == NULL)
313 		err(EXIT_FAILURE, "snl_finalize_msg");
314 	seq_id = hdr->nlmsg_seq;
315 	if (!snl_send_message(&ss, hdr))
316 		err(EXIT_FAILURE, "snl_send_message");
317 
318 	while ((hdr = snl_read_reply_multi(&ss, seq_id, &e)) != NULL) {
319 		if (e.error != 0) {
320 			err(EXIT_FAILURE, "Error reading generic netlink");
321 		}
322 		struct genl_family family = {};
323 		if (snl_parse_nlmsg(&ss, hdr, &genl_family_parser, &family))
324 			dump_family(&family);
325 	}
326 
327 	return (EXIT_SUCCESS);
328 }
329 
330 int
331 main(int argc, char **argv)
332 {
333 	if (modfind("netlink") == -1)
334 		err(EXIT_FAILURE, "require netlink module to be loaded");
335 
336 	if (argc == 1)
337 		return (list_families(0, NULL));
338 
339 	for (size_t i = 0; i < nitems(cmds); i++) {
340 		if (strcmp(argv[1], cmds[i].name) == 0)
341 			return (cmds[i].cmd(argc - 2, argv + 2));
342 	}
343 	usage();
344 
345 	return (EXIT_FAILURE);
346 }
347