xref: /freebsd/lib/libsysdecode/netlink.c (revision 2bacbbecb165dd761ea7ec2fc35630db61508cdf)
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 #define	NL_DECLARE_ATTR_DECODER(_name, _np)			\
52 static const struct nlattr_decoder_set _name = {			\
53 	.decoders = &((_np)[0]),					\
54 	.count = nitems(_np),						\
55 }
56 
57 static void nl_decode_attrs_raw(FILE *fp, const struct nlattr *nla_head,
58     size_t len, const struct nlattr_decoder *ps, size_t pslen);
59 
60 static void
61 nlattr_decode_in6_addr(FILE *fp, const struct nlattr *attr,
62     const char *attr_name, const void *args)
63 {
64 	(void)args;
65 
66 	struct in6_addr target;
67 
68 	if (NLA_DATA_LEN(attr) < (int)sizeof(target))
69 		return;
70 
71 	memcpy(&target, NLA_DATA_CONST(attr), sizeof(struct in6_addr));
72 
73 	fprintf(fp, "%s=", attr_name);
74 
75 	char buf[INET6_ADDRSTRLEN];
76 
77 	if (inet_ntop(AF_INET6, &target, buf, sizeof(buf)) != NULL)
78 		fprintf(fp, "%s", buf);
79 }
80 
81 static void
82 nlattr_decode_uint32(FILE *fp, const struct nlattr *attr, const char *attr_name,
83     const void *args)
84 {
85 	(void)args;
86 
87 	uint32_t target;
88 
89 	if (NLA_DATA_LEN(attr) < (int)sizeof(target))
90 		return;
91 
92 	memcpy(&target, NLA_DATA_CONST(attr), sizeof(uint32_t));
93 
94 	fprintf(fp, "%s=%u", attr_name, target);
95 }
96 
97 static void
98 nlattr_decode_uint8(FILE *fp, const struct nlattr *attr, const char *attr_name,
99     const void *args)
100 {
101 	(void)args;
102 
103 	uint8_t target;
104 
105 	if (NLA_DATA_LEN(attr) < (int)sizeof(target))
106 		return;
107 
108 	memcpy(&target, NLA_DATA_CONST(attr), sizeof(uint8_t));
109 
110 	fprintf(fp, "%s=%u", attr_name, target);
111 }
112 
113 static void
114 nlattr_decode_string(FILE *fp, const struct nlattr *attr, const char *attr_name,
115     const void *args)
116 {
117 	(void)args;
118 
119 	if (NLA_DATA_LEN(attr) == 0)
120 		return;
121 
122 	const char *target = (const char *)NLA_DATA_CONST(attr);
123 
124 	fprintf(fp, "%s=%s", attr_name, target);
125 }
126 
127 static void
128 nlattr_decode_nested(FILE *fp, const struct nlattr *attr, const char *attr_name,
129     const void *args)
130 {
131 	const struct nlattr_decoder_set *set = args;
132 
133 	if (set == NULL)
134 		return;
135 
136 	fprintf(fp, "%s=", attr_name);
137 
138 	nl_decode_attrs_raw(fp, NLA_DATA_CONST(attr), NLA_DATA_LEN(attr),
139 	    set->decoders, set->count);
140 }
141 
142 static const struct nlattr_decoder *
143 search_decoders(const struct nlattr_decoder *ps, size_t pslen, int key)
144 {
145 	size_t left_i = 0, right_i = pslen - 1;
146 
147 	if (pslen == 0)
148 		return (NULL);
149 
150 	if (key < ps[0].type || key > ps[pslen - 1].type)
151 		return (NULL);
152 
153 	while (left_i + 1 < right_i) {
154 		size_t mid_i = (left_i + right_i) / 2;
155 		if (key < ps[mid_i].type)
156 			right_i = mid_i;
157 		else if (key > ps[mid_i].type)
158 			left_i = mid_i + 1;
159 		else
160 			return (&ps[mid_i]);
161 	}
162 	if (ps[left_i].type == key)
163 		return (&ps[left_i]);
164 	else if (ps[right_i].type == key)
165 		return (&ps[right_i]);
166 	return (NULL);
167 }
168 
169 static void
170 nl_decode_attrs_raw(FILE *fp, const struct nlattr *nla_head, size_t len,
171     const struct nlattr_decoder *ps, size_t pslen)
172 {
173 	const struct nlattr_decoder *s;
174 	const struct nlattr *nla;
175 	bool first = true;
176 
177 	fprintf(fp, "{");
178 
179 	NLA_FOREACH_CONST(nla, nla_head, len) {
180 		if (!first)
181 			fprintf(fp, ",");
182 
183 		s = search_decoders(ps, pslen, nla->nla_type & NLA_TYPE_MASK);
184 		if (s != NULL && s->cb != NULL)
185 			s->cb(fp, nla, s->attr_name, s->args);
186 
187 		first = false;
188 	}
189 
190 	fprintf(fp, "}");
191 }
192 
193 static const struct nlattr_decoder nla_d_addr_wrap[] = {
194 	{ .type = PF_AT_ADDR, .attr_name = "addr", .cb = nlattr_decode_in6_addr },
195 	{ .type = PF_AT_MASK, .attr_name = "mask", .cb = nlattr_decode_in6_addr },
196 	{ .type = PF_AT_IFNAME, .attr_name = "ifname", .cb = nlattr_decode_string },
197 	{ .type = PF_AT_TABLENAME, .attr_name = "tablename", .cb = nlattr_decode_string },
198 	{ .type = PF_AT_TYPE, .attr_name = "type", .cb = nlattr_decode_uint8 },
199 	{ .type = PF_AT_IFLAGS, .attr_name = "iflags", .cb = nlattr_decode_uint8 },
200 };
201 NL_DECLARE_ATTR_DECODER(addr_wrap_decoder, nla_d_addr_wrap);
202 
203 static const struct nlattr_decoder nla_d_pool_addr[] = {
204 	{ .type = PF_PA_ADDR, .attr_name = "addr", .cb = nlattr_decode_nested, .args = &addr_wrap_decoder},
205 	{ .type = PF_PA_IFNAME, .attr_name = "ifname", .cb = nlattr_decode_string },
206 };
207 NL_DECLARE_ATTR_DECODER(pool_addr_decoder, nla_d_pool_addr);
208 
209 static const struct nlattr_decoder nla_d_add_addr[] = {
210 	{ .type = PF_AA_ACTION, .attr_name = "action", .cb = nlattr_decode_uint32 },
211 	{ .type = PF_AA_TICKET, .attr_name = "ticket", .cb = nlattr_decode_uint32 },
212 	{ .type = PF_AA_NR, .attr_name = "nr", .cb = nlattr_decode_uint32 },
213 	{ .type = PF_AA_R_NUM, .attr_name = "r_num", .cb = nlattr_decode_uint32 },
214 	{ .type = PF_AA_R_ACTION, .attr_name = "r_action", .cb = nlattr_decode_uint8 },
215 	{ .type = PF_AA_R_LAST, .attr_name = "r_last", .cb = nlattr_decode_uint8 },
216 	{ .type = PF_AA_AF, .attr_name = "af", .cb = nlattr_decode_uint8 },
217 	{ .type = PF_AA_ANCHOR, .attr_name = "anchor", .cb = nlattr_decode_string },
218 	{ .type = PF_AA_ADDR, .attr_name = "addr", .cb = nlattr_decode_nested , .args = &pool_addr_decoder},
219 	{ .type = PF_AA_WHICH, .attr_name = "which", .cb = nlattr_decode_uint32 },
220 };
221 NL_DECLARE_ATTR_DECODER(addr_parser, nla_d_add_addr);
222 
223 
224 static void
225 sysdecode_netlink_pf(FILE *fp, const struct genlmsghdr *genl, size_t nlm_len)
226 {
227 	uint8_t cmd = genl->cmd;
228 	const char *cmd_name = sysdecode_pfnl_cmd(cmd);
229 
230 	if (cmd_name != NULL)
231 		fprintf(fp, "cmd=%s", cmd_name);
232 	else
233 		fprintf(fp, "cmd=%u", cmd);
234 
235 	const struct nlattr *nla = (const struct nlattr *)(const void *)
236 	    ((const char *)genl + sizeof(struct genlmsghdr));
237 
238 	switch (cmd) {
239 	case PFNL_CMD_GET_ADDR:
240 			nl_decode_attrs_raw(fp, nla, nlm_len,
241 			    addr_parser.decoders, addr_parser.count);
242 		break;
243 	case PFNL_CMD_GET_ADDRS:
244 			nl_decode_attrs_raw(fp, nla, nlm_len,
245 			    addr_parser.decoders, addr_parser.count);
246 		break;
247 	default:
248 		break;
249 	}
250 }
251 
252 bool
253 sysdecode_netlink(FILE *fp, const void *buf, size_t len, int protocol)
254 {
255 	const struct nlmsghdr *nl = buf;
256 	size_t remaining = len;
257 	bool first = true;
258 
259 	/* Basic sanity check: Buffer must be at least one header size. */
260 	if (remaining < sizeof(struct nlmsghdr))
261 		return (false);
262 
263 	/* * Protocol Sanity Check:
264 	 * The first message length must be valid (>= header) and fit
265 	 * inside the provided buffer snapshot.
266 	 */
267 	if (nl->nlmsg_len < sizeof(struct nlmsghdr) || nl->nlmsg_len > remaining)
268 		return (false);
269 
270 	if (family_table == NULL) {
271 		family_table = malloc((num_family + 1) *
272 		    sizeof(struct name_table));
273 		family_table[num_family] = (struct name_table){0, NULL};
274 	}
275 
276 	fprintf(fp, "netlink{");
277 
278 	while (remaining >= sizeof(struct nlmsghdr)) {
279 		if (!first)
280 			fprintf(fp, ",");
281 
282 		/* Safety check for current message. */
283 		if (nl->nlmsg_len < sizeof(struct nlmsghdr) ||
284 		    nl->nlmsg_len > remaining) {
285 			fprintf(fp, "<truncated>");
286 			break;
287 		}
288 
289 		fprintf(fp, "flags=");
290 		sysdecode_nlm_flag(fp, nl->nlmsg_flags);
291 
292 		fprintf(fp, ",seq=%u,pid=%u", nl->nlmsg_seq, nl->nlmsg_pid);
293 
294 		fprintf(fp, ",len=%u,type=", nl->nlmsg_len);
295 
296 		/* Decode Standard Message Types. */
297 		switch (nl->nlmsg_type) {
298 		case NLMSG_NOOP:
299 			fprintf(fp, "NLMSG_NOOP");
300 			break;
301 		case NLMSG_ERROR:
302 			fprintf(fp, "NLMSG_ERROR");
303 			break;
304 		case NLMSG_DONE:
305 			fprintf(fp, "NLMSG_DONE");
306 			break;
307 		case NLMSG_OVERRUN:
308 			fprintf(fp, "NLMSG_OVERRUN");
309 			break;
310 		case GENL_ID_CTRL:
311 			if (protocol != NETLINK_GENERIC)
312 				break;
313 
314 			fprintf(fp, "GENL_ID_CTRL");
315 
316 			const struct genlmsghdr *genl =
317 			    (const struct genlmsghdr *)(const void *)
318 			    ((const char *)nl + sizeof(struct nlmsghdr));
319 
320 			uint16_t family_id = 0;
321 			const char *family_name = NULL;
322 
323 			fprintf(fp,
324 			    ",genl={cmd=%u,"
325 			    "ver=%u,reserve=%u",
326 			    genl->cmd,
327 			    genl->version, genl->reserved);
328 
329 			size_t cur_len = (sizeof(struct nlmsghdr)
330 			    + sizeof(struct genlmsghdr));
331 			size_t nla_len = nl->nlmsg_len - cur_len;
332 
333 			const struct nlattr *nla;
334 			const struct nlattr *nla_head = (const struct nlattr *)
335 			    (const void *)((const char *)nl + cur_len);
336 
337 			NLA_FOREACH_CONST(nla, nla_head, nla_len) {
338 				switch (nla->nla_type) {
339 				case CTRL_ATTR_FAMILY_ID:
340 					memcpy(&family_id, NLA_DATA_CONST(nla),
341 					    sizeof(family_id));
342 					fprintf(fp, ",family_id=%u", family_id);
343 					break;
344 				case CTRL_ATTR_FAMILY_NAME:
345 					family_name =
346 					    ((const char *)NLA_DATA_CONST(nla));
347 					fprintf(fp, ",family_name=%s",
348 					    family_name);
349 					break;
350 				default:
351 					break;
352 				}
353 			}
354 
355 			if (family_name && family_id &&
356 			    !lookup_value(family_table, family_id)) {
357 				num_family++;
358 
359 				family_table = realloc(family_table,
360 				    (num_family + 1) *
361 				    sizeof(struct name_table));
362 				family_table[num_family - 1].val =
363 				    family_id;
364 				family_table[num_family - 1].str =
365 				    strdup(family_name);
366 
367 				family_table[num_family] =
368 				    (struct name_table){0, NULL};
369 			}
370 			fprintf(fp, "}");
371 			break;
372 		default:
373 			fprintf(fp, "%u", nl->nlmsg_type);
374 			break;
375 		}
376 
377 		const char *family = lookup_value(family_table, nl->nlmsg_type);
378 
379 		if (family != NULL && protocol == NETLINK_GENERIC) {
380 			fprintf(fp, ",%s={", family);
381 
382 			if (strcmp(family, "pfctl") == 0) {
383 				const struct genlmsghdr *genl =
384 				    (const struct genlmsghdr *)(const void *)
385 				    ((const char *)nl +
386 				    sizeof(struct nlmsghdr));
387 				const size_t nlm_len = nl->nlmsg_len -
388 				    (sizeof(struct nlmsghdr) +
389 				    sizeof(struct genlmsghdr));
390 
391 				sysdecode_netlink_pf(fp, genl, nlm_len);
392 			}
393 
394 			fprintf(fp, "}");
395 		}
396 
397 		/* Handle Alignment (Netlink messages are 4-byte aligned). */
398 		size_t aligned_len = NLMSG_ALIGN(nl->nlmsg_len);
399 		if (aligned_len > remaining)
400 			remaining = 0;
401 		else
402 			remaining -= aligned_len;
403 
404 		nl = (const struct nlmsghdr *)(const void *)((const char *)nl + aligned_len);
405 		first = false;
406 	}
407 
408 	fprintf(fp, "}");
409 	return (true);
410 }
411