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