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