xref: /freebsd/sys/netlink/netlink_generic.c (revision 7e5bf68495cc0a8c9793a338a8a02009a7f6dbb6)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3  *
4  * Copyright (c) 2022 Alexander V. Chernikov <melifaro@FreeBSD.org>
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided 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 AND CONTRIBUTORS ``AS IS'' AND
16  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18  * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19  * FOR ANY 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, STRICT
23  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25  * SUCH DAMAGE.
26  */
27 
28 #include <sys/cdefs.h>
29 __FBSDID("$FreeBSD$");
30 #include <sys/types.h>
31 #include <sys/malloc.h>
32 #include <sys/priv.h>
33 #include <sys/socket.h>
34 #include <sys/ck.h>
35 
36 #include <netlink/netlink.h>
37 #include <netlink/netlink_ctl.h>
38 #include <netlink/netlink_var.h>
39 #include <netlink/netlink_generic.h>
40 
41 #define	DEBUG_MOD_NAME	nl_generic
42 #define	DEBUG_MAX_LEVEL	LOG_DEBUG3
43 #include <netlink/netlink_debug.h>
44 _DECLARE_DEBUG(LOG_DEBUG3);
45 
46 #define	MAX_FAMILIES	20
47 #define	MAX_GROUPS	20
48 
49 #define	MIN_GROUP_NUM	48
50 
51 static struct sx sx_lock;
52 
53 #define	GENL_LOCK_INIT()	sx_init(&sx_lock, "genetlink lock")
54 #define	GENL_LOCK_DESTROY()	sx_destroy(&sx_lock)
55 #define	GENL_LOCK()		sx_xlock(&sx_lock)
56 #define	GENL_UNLOCK()		sx_xunlock(&sx_lock)
57 
58 struct genl_family {
59 	const char	*family_name;
60 	uint16_t	family_hdrsize;
61 	uint16_t	family_id;
62 	uint16_t	family_version;
63 	uint16_t	family_attr_max;
64 	uint16_t	family_cmd_size;
65 	uint16_t	family_num_groups;
66 	struct genl_cmd	*family_cmds;
67 };
68 
69 static struct genl_family	families[MAX_FAMILIES];
70 
71 
72 struct genl_group {
73 	struct genl_family	*group_family;
74 	const char		*group_name;
75 };
76 static struct genl_group	groups[MAX_GROUPS];
77 
78 
79 static int dump_family(struct nlmsghdr *hdr, struct genlmsghdr *ghdr,
80     const struct genl_family *gf, struct nl_writer *nw);
81 static void nlctrl_notify(const struct genl_family *gf, int action);
82 
83 static struct genl_family *
84 find_family(const char *family_name)
85 {
86 	for (int i = 0; i < MAX_FAMILIES; i++) {
87 		struct genl_family *gf = &families[i];
88 		if (gf->family_name != NULL && !strcmp(gf->family_name, family_name))
89 			return (gf);
90 	}
91 
92 	return (NULL);
93 }
94 
95 uint32_t
96 genl_register_family(const char *family_name, size_t hdrsize, int family_version,
97     int max_attr_idx)
98 {
99 	uint32_t family_id = 0;
100 
101 	MPASS(family_name != NULL);
102 	if (find_family(family_name) != NULL)
103 		return (0);
104 
105 	GENL_LOCK();
106 	for (int i = 0; i < MAX_FAMILIES; i++) {
107 		struct genl_family *gf = &families[i];
108 		if (gf->family_name == NULL) {
109 			gf->family_name = family_name;
110 			gf->family_version = family_version;
111 			gf->family_hdrsize = hdrsize;
112 			gf->family_attr_max = max_attr_idx;
113 			gf->family_id = i + GENL_MIN_ID;
114 			NL_LOG(LOG_DEBUG2, "Registered family %s id %d",
115 			    gf->family_name, gf->family_id);
116 			family_id = gf->family_id;
117 			nlctrl_notify(gf, CTRL_CMD_NEWFAMILY);
118 			break;
119 		}
120 	}
121 	GENL_UNLOCK();
122 
123 	return (family_id);
124 }
125 
126 static void
127 free_family(struct genl_family *gf)
128 {
129 	if (gf->family_cmds != NULL)
130 		free(gf->family_cmds, M_NETLINK);
131 }
132 
133 /*
134  * Can sleep, I guess
135  */
136 bool
137 genl_unregister_family(const char *family_name)
138 {
139 	bool found = false;
140 
141 	GENL_LOCK();
142 	struct genl_family *gf = find_family(family_name);
143 
144 	nlctrl_notify(gf, CTRL_CMD_DELFAMILY);
145 
146 	if (gf != NULL) {
147 		found = true;
148 		/* TODO: zero pointer first */
149 		free_family(gf);
150 		bzero(gf, sizeof(*gf));
151 	}
152 	GENL_UNLOCK();
153 
154 	return (found);
155 }
156 
157 bool
158 genl_register_cmds(const char *family_name, const struct genl_cmd *cmds, int count)
159 {
160 	GENL_LOCK();
161 	struct genl_family *gf = find_family(family_name);
162 	if (gf == NULL) {
163 		GENL_UNLOCK();
164 		return (false);
165 	}
166 
167 	int cmd_size = gf->family_cmd_size;
168 
169 	for (int i = 0; i < count; i++) {
170 		MPASS(cmds[i].cmd_cb != NULL);
171 		if (cmds[i].cmd_num >= cmd_size)
172 			cmd_size = cmds[i].cmd_num + 1;
173 	}
174 
175 	if (cmd_size > gf->family_cmd_size) {
176 		/* need to realloc */
177 		size_t sz = cmd_size * sizeof(struct genl_cmd);
178 		void *data = malloc(sz, M_NETLINK, M_WAITOK | M_ZERO);
179 
180 		memcpy(data, gf->family_cmds, gf->family_cmd_size * sizeof(struct genl_cmd));
181 		void *old_data = gf->family_cmds;
182 		gf->family_cmds = data;
183 		gf->family_cmd_size = cmd_size;
184 		free(old_data, M_NETLINK);
185 	}
186 
187 	for (int i = 0; i < count; i++) {
188 		const struct genl_cmd *cmd = &cmds[i];
189 		MPASS(gf->family_cmds[cmd->cmd_num].cmd_cb == NULL);
190 		gf->family_cmds[cmd->cmd_num] = cmds[i];
191 		NL_LOG(LOG_DEBUG2, "Adding cmd %s(%d) to family %s",
192 		    cmd->cmd_name, cmd->cmd_num, gf->family_name);
193 	}
194 	GENL_UNLOCK();
195 	return (true);
196 }
197 
198 static struct genl_group *
199 find_group(const struct genl_family *gf, const char *group_name)
200 {
201 	for (int i = 0; i < MAX_GROUPS; i++) {
202 		struct genl_group *gg = &groups[i];
203 		if (gg->group_family == gf && !strcmp(gg->group_name, group_name))
204 			return (gg);
205 	}
206 	return (NULL);
207 }
208 
209 uint32_t
210 genl_register_group(const char *family_name, const char *group_name)
211 {
212 	uint32_t group_id = 0;
213 
214 	MPASS(family_name != NULL);
215 	MPASS(group_name != NULL);
216 
217 	GENL_LOCK();
218 	struct genl_family *gf = find_family(family_name);
219 
220 	if (gf == NULL || find_group(gf, group_name) != NULL) {
221 		GENL_UNLOCK();
222 		return (0);
223 	}
224 
225 	for (int i = 0; i < MAX_GROUPS; i++) {
226 		struct genl_group *gg = &groups[i];
227 		if (gg->group_family == NULL) {
228 			gf->family_num_groups++;
229 			gg->group_family = gf;
230 			gg->group_name = group_name;
231 			group_id = i + MIN_GROUP_NUM;
232 			break;
233 		}
234 	}
235 	GENL_UNLOCK();
236 
237 	return (group_id);
238 }
239 
240 /*
241  * Handler called by netlink subsystem when matching netlink message is received
242  */
243 static int
244 genl_handle_message(struct nlmsghdr *hdr, struct nl_pstate *npt)
245 {
246 	struct nlpcb *nlp = npt->nlp;
247 	int error = 0;
248 
249 	int family_id = (int)hdr->nlmsg_type - GENL_MIN_ID;
250 
251 	if (__predict_false(family_id < 0 || family_id > MAX_FAMILIES)) {
252 		NLP_LOG(LOG_DEBUG, nlp, "invalid message type: %d", hdr->nlmsg_type);
253 		return (ENOTSUP);
254 	}
255 
256 	if (__predict_false(hdr->nlmsg_len < sizeof(hdr) + GENL_HDRLEN)) {
257 		NLP_LOG(LOG_DEBUG, nlp, "invalid message size: %d", hdr->nlmsg_len);
258 		return (EINVAL);
259 	}
260 
261 	struct genl_family *gf = &families[family_id];
262 
263 	struct genlmsghdr *ghdr = (struct genlmsghdr *)(hdr + 1);
264 
265 	if (ghdr->cmd >= gf->family_cmd_size || gf->family_cmds[ghdr->cmd].cmd_cb == NULL) {
266 		NLP_LOG(LOG_DEBUG, nlp, "family %s: invalid cmd %d",
267 		    gf->family_name, ghdr->cmd);
268 		return (ENOTSUP);
269 	}
270 
271 	struct genl_cmd *cmd = &gf->family_cmds[ghdr->cmd];
272 
273 	if (cmd->cmd_priv != 0 && !nlp_has_priv(nlp, cmd->cmd_priv)) {
274 		NLP_LOG(LOG_DEBUG, nlp, "family %s: cmd %d priv_check() failed",
275 		    gf->family_name, ghdr->cmd);
276 		return (EPERM);
277 	}
278 
279 	NLP_LOG(LOG_DEBUG2, nlp, "received family %s cmd %s(%d) len %d",
280 	    gf->family_name, cmd->cmd_name, ghdr->cmd, hdr->nlmsg_len);
281 
282 	error = cmd->cmd_cb(hdr, npt);
283 
284 	return (error);
285 }
286 
287 static uint32_t
288 get_cmd_flags(const struct genl_cmd *cmd)
289 {
290 	uint32_t flags = cmd->cmd_flags;
291 	if (cmd->cmd_priv != 0)
292 		flags |= GENL_ADMIN_PERM;
293 	return (flags);
294 }
295 
296 static int
297 dump_family(struct nlmsghdr *hdr, struct genlmsghdr *ghdr,
298     const struct genl_family *gf, struct nl_writer *nw)
299 {
300 	if (!nlmsg_reply(nw, hdr, sizeof(struct genlmsghdr)))
301 		goto enomem;
302 
303 	struct genlmsghdr *ghdr_new = nlmsg_reserve_object(nw, struct genlmsghdr);
304 	ghdr_new->cmd = ghdr->cmd;
305 	ghdr_new->version = gf->family_version;
306 	ghdr_new->reserved = 0;
307 
308         nlattr_add_string(nw, CTRL_ATTR_FAMILY_NAME, gf->family_name);
309         nlattr_add_u16(nw, CTRL_ATTR_FAMILY_ID, gf->family_id);
310         nlattr_add_u32(nw, CTRL_ATTR_VERSION, gf->family_version);
311         nlattr_add_u32(nw, CTRL_ATTR_HDRSIZE, gf->family_hdrsize);
312         nlattr_add_u32(nw, CTRL_ATTR_MAXATTR, gf->family_attr_max);
313 
314 	if (gf->family_cmd_size > 0) {
315 		int off = nlattr_add_nested(nw, CTRL_ATTR_OPS);
316 		if (off == 0)
317 			goto enomem;
318 		for (int i = 0, cnt=0; i < gf->family_cmd_size; i++) {
319 			struct genl_cmd *cmd = &gf->family_cmds[i];
320 			if (cmd->cmd_cb == NULL)
321 				continue;
322 			int cmd_off = nlattr_add_nested(nw, ++cnt);
323 			if (cmd_off == 0)
324 				goto enomem;
325 
326 			nlattr_add_u32(nw, CTRL_ATTR_OP_ID, cmd->cmd_num);
327 			nlattr_add_u32(nw, CTRL_ATTR_OP_FLAGS, get_cmd_flags(cmd));
328 			nlattr_set_len(nw, cmd_off);
329 		}
330 		nlattr_set_len(nw, off);
331 	}
332 	if (gf->family_num_groups > 0) {
333 		int off = nlattr_add_nested(nw, CTRL_ATTR_MCAST_GROUPS);
334 		if (off == 0)
335 			goto enomem;
336 		for (int i = 0, cnt = 0; i < MAX_GROUPS; i++) {
337 			struct genl_group *gg = &groups[i];
338 			if (gg->group_family != gf)
339 				continue;
340 
341 			int cmd_off = nlattr_add_nested(nw, ++cnt);
342 			if (cmd_off == 0)
343 				goto enomem;
344 			nlattr_add_u32(nw, CTRL_ATTR_MCAST_GRP_ID, i + MIN_GROUP_NUM);
345 			nlattr_add_string(nw, CTRL_ATTR_MCAST_GRP_NAME, gg->group_name);
346 			nlattr_set_len(nw, cmd_off);
347 		}
348 		nlattr_set_len(nw, off);
349 	}
350 	if (nlmsg_end(nw))
351 		return (0);
352 enomem:
353         NL_LOG(LOG_DEBUG, "unable to dump family %s state (ENOMEM)", gf->family_name);
354         nlmsg_abort(nw);
355 	return (ENOMEM);
356 }
357 
358 
359 /* Declare ourself as a user */
360 #define	CTRL_FAMILY_NAME	"nlctrl"
361 
362 static uint32_t ctrl_family_id;
363 static uint32_t ctrl_group_id;
364 
365 struct nl_parsed_family {
366 	uint32_t	family_id;
367 	char		*family_name;
368 	uint8_t		version;
369 };
370 
371 #define	_IN(_field)	offsetof(struct genlmsghdr, _field)
372 #define	_OUT(_field)	offsetof(struct nl_parsed_family, _field)
373 static const struct nlfield_parser nlf_p_generic[] = {
374 	{ .off_in = _IN(version), .off_out = _OUT(version), .cb = nlf_get_u8 },
375 };
376 
377 static struct nlattr_parser nla_p_generic[] = {
378 	{ .type = CTRL_ATTR_FAMILY_ID , .off = _OUT(family_id), .cb = nlattr_get_uint32 },
379 	{ .type = CTRL_ATTR_FAMILY_NAME , .off = _OUT(family_id), .cb = nlattr_get_string },
380 };
381 #undef _IN
382 #undef _OUT
383 NL_DECLARE_PARSER(genl_parser, struct genlmsghdr, nlf_p_generic, nla_p_generic);
384 
385 static int
386 nlctrl_handle_getfamily(struct nlmsghdr *hdr, struct nl_pstate *npt)
387 {
388 	int error = 0;
389 
390 	struct nl_parsed_family attrs = {};
391 	error = nl_parse_nlmsg(hdr, &genl_parser, npt, &attrs);
392 	if (error != 0)
393 		return (error);
394 
395 	struct genlmsghdr ghdr = {
396 		.cmd = CTRL_CMD_NEWFAMILY,
397 	};
398 
399 	for (int i = 0; i < MAX_FAMILIES; i++) {
400 		struct genl_family *gf = &families[i];
401 		if (gf->family_name == NULL)
402 			continue;
403 		if (attrs.family_id != 0 && attrs.family_id != gf->family_id)
404 			continue;
405 		if (attrs.family_name != NULL && strcmp(attrs.family_name, gf->family_name))
406 			continue;
407 		error = dump_family(hdr, &ghdr, &families[i], npt->nw);
408 		if (error != 0)
409 			break;
410 	}
411 
412 	return (error);
413 }
414 
415 static void
416 nlctrl_notify(const struct genl_family *gf, int cmd)
417 {
418 	struct nlmsghdr hdr = {.nlmsg_type = NETLINK_GENERIC };
419 	struct genlmsghdr ghdr = { .cmd = cmd };
420 	struct nl_writer nw = {};
421 
422 	if (nlmsg_get_group_writer(&nw, NLMSG_SMALL, NETLINK_GENERIC, ctrl_group_id)) {
423 		dump_family(&hdr, &ghdr, gf, &nw);
424 		nlmsg_flush(&nw);
425 		return;
426 	}
427 	NL_LOG(LOG_DEBUG, "error allocating group writer");
428 }
429 
430 static const struct genl_cmd nlctrl_cmds[] = {
431 	{
432 		.cmd_num = CTRL_CMD_GETFAMILY,
433 		.cmd_name = "GETFAMILY",
434 		.cmd_cb = nlctrl_handle_getfamily,
435 		.cmd_flags = GENL_CMD_CAP_DO | GENL_CMD_CAP_DUMP, GENL_CMD_CAP_HASPOL,
436 	},
437 };
438 
439 static void
440 genl_nlctrl_init()
441 {
442 	ctrl_family_id = genl_register_family(CTRL_FAMILY_NAME, 0, 2, CTRL_ATTR_MAX);
443 	genl_register_cmds(CTRL_FAMILY_NAME, nlctrl_cmds, NL_ARRAY_LEN(nlctrl_cmds));
444 	ctrl_group_id = genl_register_group(CTRL_FAMILY_NAME, "notify");
445 }
446 
447 static void
448 genl_nlctrl_destroy()
449 {
450 	genl_unregister_family(CTRL_FAMILY_NAME);
451 }
452 
453 static const struct nlhdr_parser *all_parsers[] = { &genl_parser };
454 
455 static void
456 genl_load(void *u __unused)
457 {
458 	GENL_LOCK_INIT();
459 	NL_VERIFY_PARSERS(all_parsers);
460 	netlink_register_proto(NETLINK_GENERIC, "NETLINK_GENERIC", genl_handle_message);
461 	genl_nlctrl_init();
462 }
463 SYSINIT(genl_load, SI_SUB_PROTO_DOMAIN, SI_ORDER_THIRD, genl_load, NULL);
464 
465 static void
466 genl_unload(void *u __unused)
467 {
468 	genl_nlctrl_destroy();
469 	GENL_LOCK_DESTROY();
470 	epoch_wait_preempt(net_epoch_preempt);
471 }
472 SYSUNINIT(genl_unload, SI_SUB_PROTO_DOMAIN, SI_ORDER_THIRD, genl_unload, NULL);
473