1 /* 2 * SPDX-License-Identifier: BSD-2-Clause 3 * 4 * Copyright 2023 Baptiste Daroussin <bapt@FreeBSD.org> 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted providing that the following conditions~ 8 * are met: 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 16 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 19 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 21 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 23 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING 24 * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 25 * POSSIBILITY OF SUCH DAMAGE. 26 */ 27 28 #include <sys/param.h> 29 #include <sys/module.h> 30 #include <sys/socket.h> 31 32 #include <stdint.h> 33 #include <stdlib.h> 34 #include <unistd.h> 35 #include <err.h> 36 #include <stdio.h> 37 #include <poll.h> 38 39 #include <netlink/netlink.h> 40 #include <netlink/netlink_generic.h> 41 #include <netlink/netlink_snl.h> 42 #include <netlink/netlink_snl_generic.h> 43 #include <netlink/netlink_sysevent.h> 44 45 #include "genl.h" 46 47 static int monitor_mcast(int argc, char **argv); 48 static int list_families(int argc, char **argv); 49 50 static struct commands { 51 const char *name; 52 const char *usage; 53 int (*cmd)(int argc, char **argv); 54 } cmds[] = { 55 { "monitor", "monitor <family> [multicast group]", monitor_mcast }, 56 { "list", "list", list_families }, 57 }; 58 59 static monitor_parser_t parser_nlctrl_notify; 60 static monitor_parser_t parser_nlsysevent; 61 62 static struct mcast_parsers { 63 const char *family; 64 monitor_parser_t *parser; 65 } mcast_parsers [] = { 66 { "nlctrl", parser_nlctrl_notify }, 67 { "nlsysevent", parser_nlsysevent }, 68 { "rpc", parser_rpc }, 69 }; 70 71 struct nlevent { 72 const char *name; 73 const char *subsystem; 74 const char *type; 75 const char *data; 76 }; 77 #define _OUT(_field) offsetof(struct nlevent, _field) 78 static struct snl_attr_parser ap_nlevent_get[] = { 79 { .type = NLSE_ATTR_SYSTEM, .off = _OUT(name), .cb = snl_attr_get_string }, 80 { .type = NLSE_ATTR_SUBSYSTEM, .off = _OUT(subsystem), .cb = snl_attr_get_string }, 81 { .type = NLSE_ATTR_TYPE, .off = _OUT(type), .cb = snl_attr_get_string }, 82 { .type = NLSE_ATTR_DATA, .off = _OUT(data), .cb = snl_attr_get_string }, 83 }; 84 #undef _OUT 85 SNL_DECLARE_GENL_PARSER(nlevent_get_parser, ap_nlevent_get); 86 87 /* 88 * We run our own parser(s) for CTRL_CMD_GETFAMILY instead of interfaces 89 * provided by <netlink_snl_generic.h>. One reason is that we want the parsed 90 * string attributes to point into long living memory, instead of inside of 91 * just received message, hence we use snl_attr_get_stringn() instead of the 92 * snl_attr_get_string(). The shared library uses the latter to avoid creating 93 * ambiguous memory leaks. Second reason is that genl(1) usage of the data is 94 * more extended than typical application that cares only of its own family and 95 * usually one group. We are going to cycle around all available. 96 */ 97 struct genl_ctrl_op { 98 uint32_t id; 99 uint32_t flags; 100 }; 101 struct genl_ctrl_ops { 102 uint32_t num_ops; 103 struct genl_ctrl_op **ops; 104 }; 105 static struct snl_attr_parser nla_p_getops[] = { 106 #define _OUT(_field) offsetof(struct genl_ctrl_op, _field) 107 { 108 .type = CTRL_ATTR_OP_ID, 109 .off = _OUT(id), 110 .cb = snl_attr_get_uint32 111 }, 112 { 113 .type = CTRL_ATTR_OP_FLAGS, 114 .off = _OUT(flags), 115 .cb = snl_attr_get_uint32 116 }, 117 #undef _OUT 118 }; 119 SNL_DECLARE_ATTR_PARSER_EXT(genl_ctrl_op_parser, sizeof(struct genl_ctrl_op), 120 nla_p_getops, NULL); 121 122 struct genl_mcast_group { 123 uint32_t id; 124 const char *name; 125 }; 126 struct genl_mcast_groups { 127 uint32_t num_groups; 128 struct genl_mcast_group **groups; 129 }; 130 static struct snl_attr_parser nla_p_getmc[] = { 131 #define _OUT(_field) offsetof(struct genl_mcast_group, _field) 132 { 133 .type = CTRL_ATTR_MCAST_GRP_NAME, 134 .off = _OUT(name), 135 .cb = snl_attr_get_stringn, 136 }, 137 { 138 .type = CTRL_ATTR_MCAST_GRP_ID, 139 .off = _OUT(id), 140 .cb = snl_attr_get_uint32, 141 }, 142 #undef _OUT 143 }; 144 SNL_DECLARE_ATTR_PARSER_EXT(genl_mc_parser, sizeof(struct genl_mcast_group), 145 nla_p_getmc, NULL); 146 147 struct genl_family { 148 uint16_t id; 149 const char *name; 150 uint32_t version; 151 uint32_t hdrsize; 152 uint32_t max_attr; 153 struct genl_mcast_groups mcast_groups; 154 struct genl_ctrl_ops ops; 155 }; 156 157 static struct snl_attr_parser nla_p_getfamily[] = { 158 #define _OUT(_field) offsetof(struct genl_family, _field) 159 { 160 .type = CTRL_ATTR_FAMILY_ID, 161 .off = _OUT(id), 162 .cb = snl_attr_get_uint16, 163 }, 164 { 165 .type = CTRL_ATTR_FAMILY_NAME, 166 .off = _OUT(name), 167 .cb = snl_attr_get_stringn, 168 }, 169 { 170 .type = CTRL_ATTR_VERSION, 171 .off = _OUT(version), 172 .cb = snl_attr_get_uint32, 173 }, 174 { 175 .type = CTRL_ATTR_VERSION, 176 .off = _OUT(hdrsize), 177 .cb = snl_attr_get_uint32, 178 }, 179 { 180 .type = CTRL_ATTR_MAXATTR, 181 .off = _OUT(max_attr), 182 .cb = snl_attr_get_uint32, 183 }, 184 { 185 .type = CTRL_ATTR_OPS, 186 .off = _OUT(ops), 187 .cb = snl_attr_get_parray, 188 .arg = &genl_ctrl_op_parser, 189 }, 190 { 191 .type = CTRL_ATTR_MCAST_GROUPS, 192 .off = _OUT(mcast_groups), 193 .cb = snl_attr_get_parray, 194 .arg = &genl_mc_parser, 195 }, 196 #undef _OUT 197 }; 198 SNL_DECLARE_GENL_PARSER(genl_family_parser, nla_p_getfamily); 199 200 static struct op_capability { 201 uint32_t flag; 202 const char *str; 203 } op_caps[] = { 204 { GENL_ADMIN_PERM, "requires admin permission" }, 205 { GENL_CMD_CAP_DO, "can modify" }, 206 { GENL_CMD_CAP_DUMP, "can get/dump" }, 207 { GENL_CMD_CAP_HASPOL, "has policy" }, 208 }; 209 210 static void 211 dump_operations(struct genl_ctrl_ops *ops) 212 { 213 if (ops->num_ops == 0) 214 return; 215 printf("\tsupported operations: \n"); 216 for (uint32_t i = 0; i < ops->num_ops; i++) { 217 bool p = true; 218 219 printf("\t - ID: %#02x, Capabilities: %#02x", 220 ops->ops[i]->id, 221 ops->ops[i]->flags); 222 for (size_t j = 0; j < nitems(op_caps); j++) 223 if ((ops->ops[i]->flags & op_caps[j].flag) == 224 op_caps[j].flag) { 225 printf("%s%s", p ? " (" : "; ", 226 op_caps[j].str); 227 p = false; 228 } 229 printf("%s\n", p ? "" : ")"); 230 } 231 } 232 233 static void 234 dump_mcast_groups(struct genl_mcast_groups *mcast_groups) 235 { 236 if (mcast_groups->num_groups == 0) 237 return; 238 printf("\tmulticast groups: \n"); 239 for (uint32_t i = 0; i < mcast_groups->num_groups; i++) 240 printf("\t - ID: %#02x, Name: %s\n", 241 mcast_groups->groups[i]->id, 242 mcast_groups->groups[i]->name); 243 } 244 245 static void 246 usage(void) 247 { 248 fprintf(stderr, "Usage: %s\n", getprogname()); 249 for (size_t i = 0; i < nitems(cmds); i++) 250 fprintf(stderr, " %s %s\n", getprogname(), cmds[i].usage); 251 } 252 253 static void 254 dump_family(struct genl_family *family) 255 { 256 printf("Name: %s\n\tID: %#02hx, Version: %#02x, " 257 "header size: %d, max attributes: %d\n", 258 family->name, family->id, family->version, 259 family->hdrsize, family->max_attr); 260 dump_operations(&family->ops); 261 dump_mcast_groups(&family->mcast_groups); 262 } 263 264 static void 265 parser_nlctrl_notify(struct snl_state *ss, struct nlmsghdr *hdr) 266 { 267 struct genl_family family = {}; 268 269 if (snl_parse_nlmsg(ss, hdr, &genl_family_parser, 270 &family)) 271 dump_family(&family); 272 } 273 274 static void 275 parser_nlsysevent(struct snl_state *ss, struct nlmsghdr *hdr) 276 { 277 struct nlevent ne = {}; 278 if (snl_parse_nlmsg(ss, hdr, &nlevent_get_parser, &ne)) { 279 printf("system=%s subsystem=%s type=%s", ne.name, ne.subsystem, ne.type); 280 if (ne.data) { 281 printf(" %s", ne.data); 282 if (ne.data[strlen(ne.data) -1] != '\n') 283 printf("\n"); 284 } 285 } 286 } 287 288 static void 289 parser_fallback(struct snl_state *ss __unused, struct nlmsghdr *hdr) 290 { 291 printf("Unknown message: type 0x%x, length %u\n", 292 hdr->nlmsg_type, hdr->nlmsg_len); 293 } 294 295 /* Populated by monitor_mcast() and may be used by protocol parser callbacks. */ 296 static struct genl_family attrs; 297 298 const char * 299 group_name(uint32_t id) 300 { 301 for (u_int i = 0; i < attrs.mcast_groups.num_groups; i++) 302 if (attrs.mcast_groups.groups[i]->id == id) 303 return (attrs.mcast_groups.groups[i]->name); 304 return ("???"); 305 } 306 307 static int 308 monitor_mcast(int argc, char **argv) 309 { 310 struct snl_state ss; 311 struct snl_writer nw; 312 struct nlmsghdr *hdr; 313 struct pollfd pfd; 314 bool found = false; 315 bool all = false; 316 monitor_parser_t *parser; 317 318 if (argc < 1 || argc > 2) { 319 usage(); 320 return (EXIT_FAILURE); 321 } 322 323 if (!snl_init(&ss, NETLINK_GENERIC)) 324 err(EXIT_FAILURE, "snl_init()"); 325 snl_init_writer(&ss, &nw); 326 snl_create_genl_msg_request(&nw, GENL_ID_CTRL, CTRL_CMD_GETFAMILY); 327 snl_add_msg_attr_string(&nw, CTRL_ATTR_FAMILY_NAME, argv[0]); 328 if ((hdr = snl_finalize_msg(&nw)) == NULL) 329 err(EXIT_FAILURE, "snl_finalize_msg"); 330 if (!snl_send_message(&ss, hdr)) 331 err(EXIT_FAILURE, "snl_send_message"); 332 hdr = snl_read_reply(&ss, hdr->nlmsg_seq); 333 if (hdr == NULL) 334 err(EXIT_FAILURE, "snl_read_reply"); 335 if (hdr->nlmsg_type == NLMSG_ERROR) 336 err(EXIT_FAILURE, "netlink(4) returned error"); 337 memset(&attrs, 0, sizeof(attrs)); 338 if (!snl_parse_nlmsg(&ss, hdr, &genl_family_parser, &attrs)) 339 err(EXIT_FAILURE, "snl_parse_nlmsg CTRL_CMD_GETFAMILY"); 340 341 if (argc == 1) 342 all = true; 343 for (u_int i = 0; i < attrs.mcast_groups.num_groups; i++) { 344 if (all || 345 strcmp(attrs.mcast_groups.groups[i]->name, argv[1]) == 0) { 346 found = true; 347 if (setsockopt(ss.fd, SOL_NETLINK, 348 NETLINK_ADD_MEMBERSHIP, 349 &attrs.mcast_groups.groups[i]->id, 350 sizeof(attrs.mcast_groups.groups[i]->id)) 351 == -1) 352 err(EXIT_FAILURE, "Cannot subscribe to command " 353 "notify"); 354 if (!all) 355 break; 356 } 357 } 358 if (!found) 359 errx(EXIT_FAILURE, "No such multicast group '%s'" 360 " in family '%s'", argv[1], argv[0]); 361 parser = parser_fallback; 362 for (size_t i= 0; i < nitems(mcast_parsers); i++) { 363 if (strcmp(mcast_parsers[i].family, argv[0]) == 0) { 364 parser = mcast_parsers[i].parser; 365 break; 366 } 367 } 368 memset(&pfd, 0, sizeof(pfd)); 369 pfd.fd = ss.fd; 370 pfd.events = POLLIN | POLLERR; 371 while (true) { 372 pfd.revents = 0; 373 if (poll(&pfd, 1, -1) == -1) { 374 if (errno == EINTR) 375 continue; 376 err(EXIT_FAILURE, "poll()"); 377 } 378 hdr = snl_read_message(&ss); 379 if (hdr != NULL && hdr->nlmsg_type != NLMSG_ERROR) 380 parser(&ss, hdr); 381 382 } 383 384 return (EXIT_SUCCESS); 385 } 386 387 int 388 list_families(int argc, char **argv __unused) 389 { 390 struct snl_state ss; 391 struct snl_writer nw; 392 struct nlmsghdr *hdr; 393 struct snl_errmsg_data e = {}; 394 uint32_t seq_id; 395 396 if (argc != 0) { 397 usage(); 398 return (EXIT_FAILURE); 399 } 400 if (!snl_init(&ss, NETLINK_GENERIC)) 401 err(EXIT_FAILURE, "snl_init()"); 402 403 snl_init_writer(&ss, &nw); 404 hdr = snl_create_genl_msg_request(&nw, GENL_ID_CTRL, 405 CTRL_CMD_GETFAMILY); 406 if ((hdr = snl_finalize_msg(&nw)) == NULL) 407 err(EXIT_FAILURE, "snl_finalize_msg"); 408 seq_id = hdr->nlmsg_seq; 409 if (!snl_send_message(&ss, hdr)) 410 err(EXIT_FAILURE, "snl_send_message"); 411 412 while ((hdr = snl_read_reply_multi(&ss, seq_id, &e)) != NULL) { 413 if (e.error != 0) { 414 err(EXIT_FAILURE, "Error reading generic netlink"); 415 } 416 struct genl_family family = {}; 417 if (snl_parse_nlmsg(&ss, hdr, &genl_family_parser, &family)) 418 dump_family(&family); 419 } 420 421 return (EXIT_SUCCESS); 422 } 423 424 int 425 main(int argc, char **argv) 426 { 427 if (modfind("netlink") == -1) 428 err(EXIT_FAILURE, "require netlink module to be loaded"); 429 430 if (argc == 1) 431 return (list_families(0, NULL)); 432 433 for (size_t i = 0; i < nitems(cmds); i++) { 434 if (strcmp(argv[1], cmds[i].name) == 0) 435 return (cmds[i].cmd(argc - 2, argv + 2)); 436 } 437 usage(); 438 439 return (EXIT_FAILURE); 440 } 441