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