xref: /freebsd/lib/libsysdecode/netlink.c (revision 6563dcb6b1f57e51db63854f3774b52e672232ed)
1 /*
2  * Copyright (c) 2026 Ishan Agrawal
3  *
4  * SPDX-License-Identifier: BSD-2-Clause
5  */
6 
7 #include <sys/param.h>
8 #include <sys/types.h>
9 #include <sys/socket.h>
10 #include <netinet/in.h>
11 #include <arpa/inet.h>
12 #include <netlink/netlink.h>
13 #include <netlink/netlink_generic.h>
14 #include <netlink/netlink_snl.h>
15 #include <netpfil/pf/pf_nl.h>
16 
17 #include <stdio.h>
18 #include <stdbool.h>
19 #include <stddef.h>
20 #include <stdlib.h>
21 #include <string.h>
22 
23 #include "sysdecode.h"
24 #include "support.h"
25 
26 /*
27  * Decodes a buffer as a Netlink message stream.
28  *
29  * Returns true if the data was successfully decoded as Netlink.
30  * Returns false if the data is malformed, allowing the caller
31  * to fallback to a standard hex/string dump.
32  */
33 
34 static struct name_table *family_table = NULL;
35 static size_t num_family = 0;
36 
37 typedef void decode_attr_f(FILE *fp, const struct nlattr *attr,
38     const char *attr_name, const void *args);
39 struct nlattr_decoder {
40 	uint16_t	type;		/* Attribute type */
41 	const char	*attr_name;	/* Attribute name*/
42 	decode_attr_f	*cb;		/* decoder function to call */
43 	const void	*args;		/* nested decoder */
44 };
45 
46 struct nlattr_decoder_set {
47 	const struct nlattr_decoder	*decoders;	/* PFNL CMD Decoders */
48 	size_t				count;		/*Attribute Count*/
49 };
50 
51 struct pfnl_cmd_decoder {
52 	int				cmd_num;	/* PFNL CMD */
53 	const struct nlattr_decoder_set	*ds;		/* PFNL CMD Decoder set */
54 };
55 
56 #define	NL_DECLARE_ATTR_DECODER(_name, _np)			\
57 static const struct nlattr_decoder_set _name = {			\
58 	.decoders = &((_np)[0]),					\
59 	.count = nitems(_np),						\
60 }
61 
62 static void nl_decode_attrs_raw(FILE *fp, const struct nlattr *nla_head,
63     size_t len, const struct nlattr_decoder *ps, size_t pslen);
64 static void sysdecode_netlink_pf_constructor(void) __attribute__ ((__constructor__));
65 
66 static void
67 nlattr_decode_in6_addr(FILE *fp, const struct nlattr *attr,
68     const char *attr_name, const void *args)
69 {
70 	(void)args;
71 
72 	struct in6_addr target;
73 
74 	if (NLA_DATA_LEN(attr) < (int)sizeof(target))
75 		return;
76 
77 	memcpy(&target, NLA_DATA_CONST(attr), sizeof(struct in6_addr));
78 
79 	fprintf(fp, "%s=", attr_name);
80 
81 	char buf[INET6_ADDRSTRLEN];
82 
83 	if (inet_ntop(AF_INET6, &target, buf, sizeof(buf)) != NULL)
84 		fprintf(fp, "%s", buf);
85 }
86 
87 static void
88 nlattr_decode_uint64(FILE *fp, const struct nlattr *attr, const char *attr_name,
89     const void *args __unused)
90 {
91 	uint64_t target;
92 
93 	if (NLA_DATA_LEN(attr) < (int)sizeof(target))
94 		return;
95 
96 	memcpy(&target, NLA_DATA_CONST(attr), sizeof(uint64_t));
97 
98 	fprintf(fp, "%s=%ju", attr_name, target);
99 }
100 
101 static void
102 nlattr_decode_uint32(FILE *fp, const struct nlattr *attr, const char *attr_name,
103     const void *args)
104 {
105 	(void)args;
106 
107 	uint32_t target;
108 
109 	if (NLA_DATA_LEN(attr) < (int)sizeof(target))
110 		return;
111 
112 	memcpy(&target, NLA_DATA_CONST(attr), sizeof(uint32_t));
113 
114 	fprintf(fp, "%s=%u", attr_name, target);
115 }
116 
117 static void
118 nlattr_decode_uint16(FILE *fp, const struct nlattr *attr, const char *attr_name,
119     const void *args __unused)
120 {
121 	uint16_t target;
122 
123 	if (NLA_DATA_LEN(attr) < (int)sizeof(target))
124 		return;
125 
126 	memcpy(&target, NLA_DATA_CONST(attr), sizeof(uint16_t));
127 
128 	fprintf(fp, "%s=%u", attr_name, target);
129 }
130 
131 static void
132 nlattr_decode_uint8(FILE *fp, const struct nlattr *attr, const char *attr_name,
133     const void *args)
134 {
135 	(void)args;
136 
137 	uint8_t target;
138 
139 	if (NLA_DATA_LEN(attr) < (int)sizeof(target))
140 		return;
141 
142 	memcpy(&target, NLA_DATA_CONST(attr), sizeof(uint8_t));
143 
144 	fprintf(fp, "%s=%u", attr_name, target);
145 }
146 
147 static void
148 nlattr_decode_bool(FILE *fp, const struct nlattr *attr, const char *attr_name,
149     const void *args __unused)
150 {
151 	bool target;
152 
153 	if (NLA_DATA_LEN(attr) < (int)sizeof(target))
154 		return;
155 
156 	memcpy(&target, NLA_DATA_CONST(attr), sizeof(bool));
157 
158 	fprintf(fp, "%s=", attr_name);
159 
160 	if (target)
161 		fprintf(fp, "TRUE");
162 	else
163 		fprintf(fp, "FALSE");
164 }
165 
166 static void
167 nlattr_decode_string(FILE *fp, const struct nlattr *attr, const char *attr_name,
168     const void *args)
169 {
170 	(void)args;
171 
172 	if (NLA_DATA_LEN(attr) == 0)
173 		return;
174 
175 	const char *target = (const char *)NLA_DATA_CONST(attr);
176 
177 	fprintf(fp, "%s=%s", attr_name, target);
178 }
179 
180 static void
181 nlattr_decode_nested(FILE *fp, const struct nlattr *attr, const char *attr_name,
182     const void *args)
183 {
184 	const struct nlattr_decoder_set *set = args;
185 
186 	if (set == NULL)
187 		return;
188 
189 	fprintf(fp, "%s=", attr_name);
190 
191 	nl_decode_attrs_raw(fp, NLA_DATA_CONST(attr), NLA_DATA_LEN(attr),
192 	    set->decoders, set->count);
193 }
194 
195 static const struct nlattr_decoder *
196 search_decoders(const struct nlattr_decoder *ps, size_t pslen, int key)
197 {
198 	size_t left_i = 0, right_i = pslen - 1;
199 
200 	if (pslen == 0)
201 		return (NULL);
202 
203 	if (key < ps[0].type || key > ps[pslen - 1].type)
204 		return (NULL);
205 
206 	while (left_i + 1 < right_i) {
207 		size_t mid_i = (left_i + right_i) / 2;
208 		if (key < ps[mid_i].type)
209 			right_i = mid_i;
210 		else if (key > ps[mid_i].type)
211 			left_i = mid_i + 1;
212 		else
213 			return (&ps[mid_i]);
214 	}
215 	if (ps[left_i].type == key)
216 		return (&ps[left_i]);
217 	else if (ps[right_i].type == key)
218 		return (&ps[right_i]);
219 	return (NULL);
220 }
221 
222 static const struct pfnl_cmd_decoder *
223 search_cmd_decoders(const struct pfnl_cmd_decoder *ps, size_t pslen, int key)
224 {
225 	size_t left_i = 0, right_i = pslen - 1;
226 
227 	if (pslen == 0)
228 		return (NULL);
229 
230 	if (key < ps[0].cmd_num || key > ps[pslen - 1].cmd_num)
231 		return (NULL);
232 
233 	while (left_i + 1 < right_i) {
234 		size_t mid_i = (left_i + right_i) / 2;
235 		if (key < ps[mid_i].cmd_num)
236 			right_i = mid_i;
237 		else if (key > ps[mid_i].cmd_num)
238 			left_i = mid_i + 1;
239 		else
240 			return (&ps[mid_i]);
241 	}
242 	if (ps[left_i].cmd_num == key)
243 		return (&ps[left_i]);
244 	else if (ps[right_i].cmd_num == key)
245 		return (&ps[right_i]);
246 	return (NULL);
247 }
248 
249 static void
250 nl_decode_attrs_raw(FILE *fp, const struct nlattr *nla_head, size_t len,
251     const struct nlattr_decoder *ps, size_t pslen)
252 {
253 	const struct nlattr_decoder *s;
254 	const struct nlattr *nla;
255 	bool first = true;
256 
257 	fprintf(fp, "{");
258 
259 	NLA_FOREACH_CONST(nla, nla_head, len) {
260 		s = search_decoders(ps, pslen, nla->nla_type & NLA_TYPE_MASK);
261 
262 		if (s != NULL && s->cb != NULL) {
263 			if (!first)
264 				fprintf(fp, ",");
265 
266 			s->cb(fp, nla, s->attr_name, s->args);
267 		}
268 		first = false;
269 	}
270 
271 	fprintf(fp, "}");
272 }
273 
274 static const struct nlattr_decoder nla_d_getrules[] = {
275 	{ .type = PF_GR_ANCHOR, .attr_name = "anchor", .cb = nlattr_decode_string },
276 	{ .type = PF_GR_ACTION, .attr_name = "action", .cb = nlattr_decode_uint8 },
277 	{ .type = PF_GR_NR, .attr_name = "nr", .cb = nlattr_decode_uint32 },
278 	{ .type = PF_GR_TICKET, .attr_name = "ticket", .cb = nlattr_decode_uint32 },
279 	{ .type = PF_GR_CLEAR, .attr_name = "clear", .cb = nlattr_decode_uint8 },
280 };
281 NL_DECLARE_ATTR_DECODER(getrules_decoder, nla_d_getrules);
282 
283 static const struct nlattr_decoder nla_d_set_limit[] = {
284 	{ .type = PF_LI_INDEX, .attr_name = "index", .cb = nlattr_decode_uint32 },
285 	{ .type = PF_LI_LIMIT, .attr_name = "limit", .cb = nlattr_decode_uint32 },
286 };
287 NL_DECLARE_ATTR_DECODER(set_limit_decoder, nla_d_set_limit);
288 
289 static const struct nlattr_decoder nla_d_addr_wrap[] = {
290 	{ .type = PF_AT_ADDR, .attr_name = "addr", .cb = nlattr_decode_in6_addr },
291 	{ .type = PF_AT_MASK, .attr_name = "mask", .cb = nlattr_decode_in6_addr },
292 	{ .type = PF_AT_IFNAME, .attr_name = "ifname", .cb = nlattr_decode_string },
293 	{ .type = PF_AT_TABLENAME, .attr_name = "tablename", .cb = nlattr_decode_string },
294 	{ .type = PF_AT_TYPE, .attr_name = "type", .cb = nlattr_decode_uint8 },
295 	{ .type = PF_AT_IFLAGS, .attr_name = "iflags", .cb = nlattr_decode_uint8 },
296 };
297 NL_DECLARE_ATTR_DECODER(addr_wrap_decoder, nla_d_addr_wrap);
298 
299 static const struct nlattr_decoder nla_d_pool_addr[] = {
300 	{ .type = PF_PA_ADDR, .attr_name = "addr", .cb = nlattr_decode_nested, .args = &addr_wrap_decoder},
301 	{ .type = PF_PA_IFNAME, .attr_name = "ifname", .cb = nlattr_decode_string },
302 };
303 NL_DECLARE_ATTR_DECODER(pool_addr_decoder, nla_d_pool_addr);
304 
305 static const struct nlattr_decoder nla_d_add_addr[] = {
306 	{ .type = PF_AA_ACTION, .attr_name = "action", .cb = nlattr_decode_uint32 },
307 	{ .type = PF_AA_TICKET, .attr_name = "ticket", .cb = nlattr_decode_uint32 },
308 	{ .type = PF_AA_NR, .attr_name = "nr", .cb = nlattr_decode_uint32 },
309 	{ .type = PF_AA_R_NUM, .attr_name = "r_num", .cb = nlattr_decode_uint32 },
310 	{ .type = PF_AA_R_ACTION, .attr_name = "r_action", .cb = nlattr_decode_uint8 },
311 	{ .type = PF_AA_R_LAST, .attr_name = "r_last", .cb = nlattr_decode_uint8 },
312 	{ .type = PF_AA_AF, .attr_name = "af", .cb = nlattr_decode_uint8 },
313 	{ .type = PF_AA_ANCHOR, .attr_name = "anchor", .cb = nlattr_decode_string },
314 	{ .type = PF_AA_ADDR, .attr_name = "addr", .cb = nlattr_decode_nested , .args = &pool_addr_decoder},
315 	{ .type = PF_AA_WHICH, .attr_name = "which", .cb = nlattr_decode_uint32 },
316 };
317 NL_DECLARE_ATTR_DECODER(addr_decoder, nla_d_add_addr);
318 
319 static const struct nlattr_decoder nla_d_ruleaddr[] = {
320 	{ .type = PF_RAT_ADDR, .attr_name = "addr", .cb = nlattr_decode_nested, .args = &addr_wrap_decoder },
321 	{ .type = PF_RAT_SRC_PORT, .attr_name = "src_port", .cb = nlattr_decode_uint16 },
322 	{ .type = PF_RAT_DST_PORT, .attr_name = "dst_port", .cb = nlattr_decode_uint16 },
323 	{ .type = PF_RAT_NEG, .attr_name = "neg", .cb = nlattr_decode_uint8 },
324 	{ .type = PF_RAT_OP, .attr_name = "op", .cb = nlattr_decode_uint8 },
325 };
326 NL_DECLARE_ATTR_DECODER(rule_addr_decoder, nla_d_ruleaddr);
327 
328 static const struct nlattr_decoder nla_d_clear_states[] = {
329 	{ .type = PF_CS_CMP_ID, .attr_name = "cmp_id", .cb = nlattr_decode_uint64 },
330 	{ .type = PF_CS_CMP_CREATORID, .attr_name = "cmp_creatorid", .cb = nlattr_decode_uint32 },
331 	{ .type = PF_CS_CMP_DIR, .attr_name = "cmp_dir", .cb = nlattr_decode_uint8 },
332 	{ .type = PF_CS_AF, .attr_name = "af", .cb = nlattr_decode_uint8 },
333 	{ .type = PF_CS_PROTO, .attr_name = "proto", .cb = nlattr_decode_uint8 },
334 	{ .type = PF_CS_SRC, .attr_name = "src", .cb = nlattr_decode_nested, .args = &rule_addr_decoder },
335 	{ .type = PF_CS_DST, .attr_name = "dst", .cb = nlattr_decode_nested, .args = &rule_addr_decoder },
336 	{ .type = PF_CS_RT_ADDR, .attr_name = "rt_addr", .cb = nlattr_decode_nested, .args = &rule_addr_decoder },
337 	{ .type = PF_CS_IFNAME, .attr_name = "ifname", .cb = nlattr_decode_string },
338 	{ .type = PF_CS_LABEL, .attr_name = "label", .cb = nlattr_decode_string },
339 	{ .type = PF_CS_KILL_MATCH, .attr_name = "kill_match", .cb = nlattr_decode_bool },
340 	{ .type = PF_CS_NAT, .attr_name = "nat", .cb = nlattr_decode_bool },
341 };
342 NL_DECLARE_ATTR_DECODER(killclear_states_decoder, nla_d_clear_states);
343 
344 static inline void
345 nl_verify_decoders(const struct nlattr_decoder_set **decoder, size_t count)
346 {
347 	for (size_t i = 0; i < count; i++) {
348 		const struct nlattr_decoder_set *p = decoder[i];
349 		for (size_t j = 1; j < p->count; j++) {
350 			assert(p->decoders[j].type > p->decoders[j-1].type);
351 		}
352 	}
353 }
354 #define	NL_VERIFY_DECODERS(_p)	nl_verify_decoders((_p), nitems(_p))
355 
356 static const struct nlattr_decoder_set *all_decoders[] = {
357 	&getrules_decoder,
358 	&set_limit_decoder,
359 	&addr_wrap_decoder,
360 	&pool_addr_decoder,
361 	&addr_decoder,
362 	&rule_addr_decoder,
363 	&killclear_states_decoder,
364 };
365 
366 static const struct pfnl_cmd_decoder cmd_decoder[] = {
367 	{ .cmd_num = PFNL_CMD_GETRULES, .ds = &getrules_decoder },
368 	{ .cmd_num = PFNL_CMD_KILLSTATES, .ds = &killclear_states_decoder },
369 	{ .cmd_num = PFNL_CMD_GET_LIMIT, .ds = &set_limit_decoder },
370 	{ .cmd_num = PFNL_CMD_GET_ADDRS, .ds = &addr_decoder },
371 	{ .cmd_num = PFNL_CMD_GET_ADDR, .ds = &addr_decoder },
372 };
373 
374 static inline void
375 pfnl_verify_cmd_decoders(const struct pfnl_cmd_decoder *cmds, size_t count)
376 {
377 	int num = cmds[0].cmd_num;
378 
379 	for (size_t i = 1; i < count; i++) {
380 		const struct pfnl_cmd_decoder *p = &cmds[i];
381 		assert(p->cmd_num > num);
382 		num = p->cmd_num;
383 	}
384 }
385 
386 static void
387 sysdecode_netlink_pf(FILE *fp, const struct genlmsghdr *genl, size_t nlm_len)
388 {
389 	uint8_t cmd = genl->cmd;
390 	const char *cmd_name = sysdecode_pfnl_cmd(cmd);
391 
392 	if (cmd_name != NULL)
393 		fprintf(fp, "cmd=%s", cmd_name);
394 	else
395 		fprintf(fp, "cmd=%u", cmd);
396 
397 	const struct nlattr *nla = (const struct nlattr *)(const void *)
398 	    ((const char *)genl + sizeof(struct genlmsghdr));
399 
400 	const struct pfnl_cmd_decoder *d;
401 
402 	d = search_cmd_decoders(cmd_decoder, nitems(cmd_decoder), cmd);
403 	if (d != NULL) {
404 		nl_decode_attrs_raw(fp, nla, nlm_len,
405 			d->ds->decoders, d->ds->count);
406 	}
407 }
408 
409 bool
410 sysdecode_netlink(FILE *fp, const void *buf, size_t len, int protocol)
411 {
412 	const struct nlmsghdr *nl = buf;
413 	size_t remaining = len;
414 	bool first = true;
415 
416 	/* Basic sanity check: Buffer must be at least one header size. */
417 	if (remaining < sizeof(struct nlmsghdr))
418 		return (false);
419 
420 	/* * Protocol Sanity Check:
421 	 * The first message length must be valid (>= header) and fit
422 	 * inside the provided buffer snapshot.
423 	 */
424 	if (nl->nlmsg_len < sizeof(struct nlmsghdr) || nl->nlmsg_len > remaining)
425 		return (false);
426 
427 	if (family_table == NULL) {
428 		family_table = malloc((num_family + 1) *
429 		    sizeof(struct name_table));
430 		family_table[num_family] = (struct name_table){0, NULL};
431 	}
432 
433 	fprintf(fp, "netlink{");
434 
435 	while (remaining >= sizeof(struct nlmsghdr)) {
436 		if (!first)
437 			fprintf(fp, ",");
438 
439 		/* Safety check for current message. */
440 		if (nl->nlmsg_len < sizeof(struct nlmsghdr) ||
441 		    nl->nlmsg_len > remaining) {
442 			fprintf(fp, "<truncated>");
443 			break;
444 		}
445 
446 		fprintf(fp, "flags=");
447 		sysdecode_nlm_flag(fp, nl->nlmsg_flags);
448 
449 		fprintf(fp, ",seq=%u,pid=%u", nl->nlmsg_seq, nl->nlmsg_pid);
450 
451 		fprintf(fp, ",len=%u,type=", nl->nlmsg_len);
452 
453 		/* Decode Standard Message Types. */
454 		switch (nl->nlmsg_type) {
455 		case NLMSG_NOOP:
456 			fprintf(fp, "NLMSG_NOOP");
457 			break;
458 		case NLMSG_ERROR:
459 			fprintf(fp, "NLMSG_ERROR");
460 			break;
461 		case NLMSG_DONE:
462 			fprintf(fp, "NLMSG_DONE");
463 			break;
464 		case NLMSG_OVERRUN:
465 			fprintf(fp, "NLMSG_OVERRUN");
466 			break;
467 		case GENL_ID_CTRL:
468 			if (protocol != NETLINK_GENERIC)
469 				break;
470 
471 			fprintf(fp, "GENL_ID_CTRL");
472 
473 			const struct genlmsghdr *genl =
474 			    (const struct genlmsghdr *)(const void *)
475 			    ((const char *)nl + sizeof(struct nlmsghdr));
476 
477 			uint16_t family_id = 0;
478 			const char *family_name = NULL;
479 
480 			fprintf(fp,
481 			    ",genl={cmd=%u,"
482 			    "ver=%u,reserve=%u",
483 			    genl->cmd,
484 			    genl->version, genl->reserved);
485 
486 			size_t cur_len = (sizeof(struct nlmsghdr)
487 			    + sizeof(struct genlmsghdr));
488 			size_t nla_len = nl->nlmsg_len - cur_len;
489 
490 			const struct nlattr *nla;
491 			const struct nlattr *nla_head = (const struct nlattr *)
492 			    (const void *)((const char *)nl + cur_len);
493 
494 			NLA_FOREACH_CONST(nla, nla_head, nla_len) {
495 				switch (nla->nla_type) {
496 				case CTRL_ATTR_FAMILY_ID:
497 					memcpy(&family_id, NLA_DATA_CONST(nla),
498 					    sizeof(family_id));
499 					fprintf(fp, ",family_id=%u", family_id);
500 					break;
501 				case CTRL_ATTR_FAMILY_NAME:
502 					family_name =
503 					    ((const char *)NLA_DATA_CONST(nla));
504 					fprintf(fp, ",family_name=%s",
505 					    family_name);
506 					break;
507 				default:
508 					break;
509 				}
510 			}
511 
512 			if (family_name && family_id &&
513 			    !lookup_value(family_table, family_id)) {
514 				num_family++;
515 
516 				family_table = realloc(family_table,
517 				    (num_family + 1) *
518 				    sizeof(struct name_table));
519 				family_table[num_family - 1].val =
520 				    family_id;
521 				family_table[num_family - 1].str =
522 				    strdup(family_name);
523 
524 				family_table[num_family] =
525 				    (struct name_table){0, NULL};
526 			}
527 			fprintf(fp, "}");
528 			break;
529 		default:
530 			fprintf(fp, "%u", nl->nlmsg_type);
531 			break;
532 		}
533 
534 		const char *family = lookup_value(family_table, nl->nlmsg_type);
535 
536 		if (family != NULL && protocol == NETLINK_GENERIC) {
537 			fprintf(fp, ",%s={", family);
538 
539 			if (strcmp(family, "pfctl") == 0) {
540 				const struct genlmsghdr *genl =
541 				    (const struct genlmsghdr *)(const void *)
542 				    ((const char *)nl +
543 				    sizeof(struct nlmsghdr));
544 				const size_t nlm_len = nl->nlmsg_len -
545 				    (sizeof(struct nlmsghdr) +
546 				    sizeof(struct genlmsghdr));
547 
548 				sysdecode_netlink_pf(fp, genl, nlm_len);
549 			}
550 
551 			fprintf(fp, "}");
552 		}
553 
554 		/* Handle Alignment (Netlink messages are 4-byte aligned). */
555 		size_t aligned_len = NLMSG_ALIGN(nl->nlmsg_len);
556 		if (aligned_len > remaining)
557 			remaining = 0;
558 		else
559 			remaining -= aligned_len;
560 
561 		nl = (const struct nlmsghdr *)(const void *)((const char *)nl + aligned_len);
562 		first = false;
563 	}
564 
565 	fprintf(fp, "}");
566 	return (true);
567 }
568 
569 static void
570 sysdecode_netlink_pf_constructor(void)
571 {
572 	NL_VERIFY_DECODERS(all_decoders);
573 	pfnl_verify_cmd_decoders(cmd_decoder, nitems(cmd_decoder));
574 }
575