xref: /freebsd/lib/libsysdecode/netlink.c (revision 4db3872aabc33088cf180599c5eaa23b6f58e6d1)
1 /*
2  * Copyright (c) 2026 Ishan Agrawal
3  *
4  * SPDX-License-Identifier: BSD-2-Clause
5  */
6 
7 #include <sys/param.h>
8 #include <netlink/netlink.h>
9 #include <netlink/netlink_generic.h>
10 #include <netlink/netlink_snl.h>
11 
12 #include <stdio.h>
13 #include <stdbool.h>
14 #include <stddef.h>
15 #include <stdlib.h>
16 #include <string.h>
17 
18 #include "sysdecode.h"
19 #include "support.h"
20 
21 /*
22  * Decodes a buffer as a Netlink message stream.
23  *
24  * Returns true if the data was successfully decoded as Netlink.
25  * Returns false if the data is malformed, allowing the caller
26  * to fallback to a standard hex/string dump.
27  */
28 
29 static struct name_table *family_table = NULL;
30 static size_t num_family = 0;
31 
32 static void
33 sysdecode_netlink_pf(FILE *fp, const struct genlmsghdr *genl)
34 {
35 	uint8_t cmd = genl->cmd;
36 	const char *cmd_name = sysdecode_pfnl_cmd(cmd);
37 
38 	if (cmd_name != NULL)
39 		fprintf(fp, "cmd=%s", cmd_name);
40 	else
41 		fprintf(fp, "cmd=%u", cmd);
42 }
43 
44 bool
45 sysdecode_netlink(FILE *fp, const void *buf, size_t len, int protocol)
46 {
47 	const struct nlmsghdr *nl = buf;
48 	size_t remaining = len;
49 	bool first = true;
50 
51 	/* Basic sanity check: Buffer must be at least one header size. */
52 	if (remaining < sizeof(struct nlmsghdr))
53 		return (false);
54 
55 	/* * Protocol Sanity Check:
56 	 * The first message length must be valid (>= header) and fit
57 	 * inside the provided buffer snapshot.
58 	 */
59 	if (nl->nlmsg_len < sizeof(struct nlmsghdr) || nl->nlmsg_len > remaining)
60 		return (false);
61 
62 	if (family_table == NULL) {
63 		family_table = malloc((num_family + 1) *
64 		    sizeof(struct name_table));
65 		family_table[num_family] = (struct name_table){0, NULL};
66 	}
67 
68 	fprintf(fp, "netlink{");
69 
70 	while (remaining >= sizeof(struct nlmsghdr)) {
71 		if (!first)
72 			fprintf(fp, ",");
73 
74 		/* Safety check for current message. */
75 		if (nl->nlmsg_len < sizeof(struct nlmsghdr) ||
76 		    nl->nlmsg_len > remaining) {
77 			fprintf(fp, "<truncated>");
78 			break;
79 		}
80 
81 		fprintf(fp, "flags=");
82 		const char *nlm_f = sysdecode_nlm_flag(nl->nlmsg_flags);
83 		if (nlm_f != NULL)
84 			fprintf(fp, "%s", nlm_f);
85 		else
86 			fprintf(fp, "0x%x", nl->nlmsg_flags);
87 
88 		fprintf(fp, ",seq=%u,pid=%u", nl->nlmsg_seq, nl->nlmsg_pid);
89 
90 		fprintf(fp, ",len=%u,type=", nl->nlmsg_len);
91 
92 		/* Decode Standard Message Types. */
93 		switch (nl->nlmsg_type) {
94 		case NLMSG_NOOP:
95 			fprintf(fp, "NLMSG_NOOP");
96 			break;
97 		case NLMSG_ERROR:
98 			fprintf(fp, "NLMSG_ERROR");
99 			break;
100 		case NLMSG_DONE:
101 			fprintf(fp, "NLMSG_DONE");
102 			break;
103 		case NLMSG_OVERRUN:
104 			fprintf(fp, "NLMSG_OVERRUN");
105 			break;
106 		case GENL_ID_CTRL:
107 			if (protocol != NETLINK_GENERIC)
108 				break;
109 
110 			fprintf(fp, "GENL_ID_CTRL");
111 
112 			const struct genlmsghdr *genl =
113 			    (const struct genlmsghdr *)(const void *)
114 			    ((const char *)nl + sizeof(struct nlmsghdr));
115 
116 			uint16_t family_id = 0;
117 			const char *family_name = NULL;
118 
119 			fprintf(fp,
120 			    ",genl={cmd=%u,"
121 			    "ver=%u,reserve=%u",
122 			    genl->cmd,
123 			    genl->version, genl->reserved);
124 
125 			size_t cur_len = (sizeof(struct nlmsghdr)
126 			    + sizeof(struct genlmsghdr));
127 			size_t nla_len = nl->nlmsg_len - cur_len;
128 
129 			const struct nlattr *nla;
130 			const struct nlattr *nla_head = (const struct nlattr *)
131 			    (const void *)((const char *)nl + cur_len);
132 
133 			NLA_FOREACH_CONST(nla, nla_head, nla_len) {
134 				switch (nla->nla_type) {
135 				case CTRL_ATTR_FAMILY_ID:
136 					memcpy(&family_id, NLA_DATA_CONST(nla),
137 					    sizeof(family_id));
138 					fprintf(fp, ",family_id=%u", family_id);
139 					break;
140 				case CTRL_ATTR_FAMILY_NAME:
141 					family_name =
142 					    ((const char *)NLA_DATA_CONST(nla));
143 					fprintf(fp, ",family_name=%s",
144 					    family_name);
145 					break;
146 				default:
147 					break;
148 				}
149 			}
150 
151 			if (family_name && family_id &&
152 			    !lookup_value(family_table, family_id)) {
153 				num_family++;
154 
155 				family_table = realloc(family_table,
156 				    (num_family + 1) *
157 				    sizeof(struct name_table));
158 				family_table[num_family - 1].val =
159 				    family_id;
160 				family_table[num_family - 1].str =
161 				    strdup(family_name);
162 
163 				family_table[num_family] =
164 				    (struct name_table){0, NULL};
165 			}
166 
167 			fprintf(fp, "}");
168 			break;
169 		default:
170 			fprintf(fp, "%u", nl->nlmsg_type);
171 			break;
172 		}
173 
174 		const char *family = lookup_value(family_table, nl->nlmsg_type);
175 
176 		if (family != NULL && protocol == NETLINK_GENERIC) {
177 			fprintf(fp, ",%s={", family);
178 
179 			if (strcmp(family, "pfctl") == 0) {
180 				const struct genlmsghdr *genl =
181 				    (const struct genlmsghdr *)(const void *)
182 				    ((const char *)nl +
183 				    sizeof(struct nlmsghdr));
184 
185 				sysdecode_netlink_pf(fp, genl);
186 			}
187 
188 			fprintf(fp, "}");
189 		}
190 
191 		/* Handle Alignment (Netlink messages are 4-byte aligned). */
192 		size_t aligned_len = NLMSG_ALIGN(nl->nlmsg_len);
193 		if (aligned_len > remaining)
194 			remaining = 0;
195 		else
196 			remaining -= aligned_len;
197 
198 		nl = (const struct nlmsghdr *)(const void *)((const char *)nl + aligned_len);
199 		first = false;
200 	}
201 
202 	fprintf(fp, "}");
203 	return (true);
204 }
205