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 static int monitor_mcast(int argc, char **argv); 46 static int list_families(int argc, char **argv); 47 static void parser_nlctrl_notify(struct snl_state *ss, struct nlmsghdr *hdr); 48 static void parser_nlsysevent(struct snl_state *ss, struct nlmsghdr *hdr); 49 static void parser_fallback(struct snl_state *ss, struct nlmsghdr *hdr); 50 51 static struct commands { 52 const char *name; 53 const char *usage; 54 int (*cmd)(int argc, char **argv); 55 } cmds[] = { 56 { "monitor", "monitor <family> [multicast group]", monitor_mcast }, 57 { "list", "list", list_families }, 58 }; 59 60 static struct mcast_parsers { 61 const char *family; 62 void (*parser)(struct snl_state *ss, struct nlmsghdr *hdr); 63 } mcast_parsers [] = { 64 { "nlctrl", parser_nlctrl_notify }, 65 { "nlsysevent", parser_nlsysevent }, 66 }; 67 68 struct nlevent { 69 const char *name; 70 const char *subsystem; 71 const char *type; 72 const char *data; 73 }; 74 #define _OUT(_field) offsetof(struct nlevent, _field) 75 static struct snl_attr_parser ap_nlevent_get[] = { 76 { .type = NLSE_ATTR_SYSTEM, .off = _OUT(name), .cb = snl_attr_get_string }, 77 { .type = NLSE_ATTR_SUBSYSTEM, .off = _OUT(subsystem), .cb = snl_attr_get_string }, 78 { .type = NLSE_ATTR_TYPE, .off = _OUT(type), .cb = snl_attr_get_string }, 79 { .type = NLSE_ATTR_DATA, .off = _OUT(data), .cb = snl_attr_get_string }, 80 }; 81 #undef _OUT 82 SNL_DECLARE_GENL_PARSER(nlevent_get_parser, ap_nlevent_get); 83 84 /* 85 * We run our own parser(s) for CTRL_CMD_GETFAMILY instead of interfaces 86 * provided by <netlink_snl_generic.h>. One reason is that we want the parsed 87 * string attributes to point into long living memory, instead of inside of 88 * just received message, hence we use snl_attr_get_stringn() instead of the 89 * snl_attr_get_string(). The shared library uses the latter to avoid creating 90 * ambiguous memory leaks. Second reason is that genl(1) usage of the data is 91 * more extended than typical application that cares only of its own family and 92 * usually one group. We are going to cycle around all available. 93 */ 94 struct genl_ctrl_op { 95 uint32_t id; 96 uint32_t flags; 97 }; 98 struct genl_ctrl_ops { 99 uint32_t num_ops; 100 struct genl_ctrl_op **ops; 101 }; 102 static struct snl_attr_parser nla_p_getops[] = { 103 #define _OUT(_field) offsetof(struct genl_ctrl_op, _field) 104 { 105 .type = CTRL_ATTR_OP_ID, 106 .off = _OUT(id), 107 .cb = snl_attr_get_uint32 108 }, 109 { 110 .type = CTRL_ATTR_OP_FLAGS, 111 .off = _OUT(flags), 112 .cb = snl_attr_get_uint32 113 }, 114 #undef _OUT 115 }; 116 SNL_DECLARE_ATTR_PARSER_EXT(genl_ctrl_op_parser, sizeof(struct genl_ctrl_op), 117 nla_p_getops, NULL); 118 119 struct genl_mcast_group { 120 uint32_t id; 121 const char *name; 122 }; 123 struct genl_mcast_groups { 124 uint32_t num_groups; 125 struct genl_mcast_group **groups; 126 }; 127 static struct snl_attr_parser nla_p_getmc[] = { 128 #define _OUT(_field) offsetof(struct genl_mcast_group, _field) 129 { 130 .type = CTRL_ATTR_MCAST_GRP_NAME, 131 .off = _OUT(name), 132 .cb = snl_attr_get_stringn, 133 }, 134 { 135 .type = CTRL_ATTR_MCAST_GRP_ID, 136 .off = _OUT(id), 137 .cb = snl_attr_get_uint32, 138 }, 139 #undef _OUT 140 }; 141 SNL_DECLARE_ATTR_PARSER_EXT(genl_mc_parser, sizeof(struct genl_mcast_group), 142 nla_p_getmc, NULL); 143 144 struct genl_family { 145 uint16_t id; 146 const char *name; 147 uint32_t version; 148 uint32_t hdrsize; 149 uint32_t max_attr; 150 struct genl_mcast_groups mcast_groups; 151 struct genl_ctrl_ops ops; 152 }; 153 154 static struct snl_attr_parser nla_p_getfamily[] = { 155 #define _OUT(_field) offsetof(struct genl_family, _field) 156 { 157 .type = CTRL_ATTR_FAMILY_ID, 158 .off = _OUT(id), 159 .cb = snl_attr_get_uint16, 160 }, 161 { 162 .type = CTRL_ATTR_FAMILY_NAME, 163 .off = _OUT(name), 164 .cb = snl_attr_get_stringn, 165 }, 166 { 167 .type = CTRL_ATTR_VERSION, 168 .off = _OUT(version), 169 .cb = snl_attr_get_uint32, 170 }, 171 { 172 .type = CTRL_ATTR_VERSION, 173 .off = _OUT(hdrsize), 174 .cb = snl_attr_get_uint32, 175 }, 176 { 177 .type = CTRL_ATTR_MAXATTR, 178 .off = _OUT(max_attr), 179 .cb = snl_attr_get_uint32, 180 }, 181 { 182 .type = CTRL_ATTR_OPS, 183 .off = _OUT(ops), 184 .cb = snl_attr_get_parray, 185 .arg = &genl_ctrl_op_parser, 186 }, 187 { 188 .type = CTRL_ATTR_MCAST_GROUPS, 189 .off = _OUT(mcast_groups), 190 .cb = snl_attr_get_parray, 191 .arg = &genl_mc_parser, 192 }, 193 #undef _OUT 194 }; 195 SNL_DECLARE_GENL_PARSER(genl_family_parser, nla_p_getfamily); 196 197 static struct op_capability { 198 uint32_t flag; 199 const char *str; 200 } op_caps[] = { 201 { GENL_ADMIN_PERM, "requires admin permission" }, 202 { GENL_CMD_CAP_DO, "can modify" }, 203 { GENL_CMD_CAP_DUMP, "can get/dump" }, 204 { GENL_CMD_CAP_HASPOL, "has policy" }, 205 }; 206 207 static void 208 dump_operations(struct genl_ctrl_ops *ops) 209 { 210 if (ops->num_ops == 0) 211 return; 212 printf("\tsupported operations: \n"); 213 for (uint32_t i = 0; i < ops->num_ops; i++) { 214 printf("\t - ID: %#02x, Capabilities: %#02x (", 215 ops->ops[i]->id, 216 ops->ops[i]->flags); 217 for (size_t j = 0; j < nitems(op_caps); j++) 218 if ((ops->ops[i]->flags & op_caps[j].flag) == op_caps[j].flag) 219 printf("%s; ", op_caps[j].str); 220 printf("\b\b)\n"); 221 } 222 } 223 224 static void 225 dump_mcast_groups(struct genl_mcast_groups *mcast_groups) 226 { 227 if (mcast_groups->num_groups == 0) 228 return; 229 printf("\tmulticast groups: \n"); 230 for (uint32_t i = 0; i < mcast_groups->num_groups; i++) 231 printf("\t - ID: %#02x, Name: %s\n", 232 mcast_groups->groups[i]->id, 233 mcast_groups->groups[i]->name); 234 } 235 236 static void 237 usage(void) 238 { 239 fprintf(stderr, "Usage: %s\n", getprogname()); 240 for (size_t i = 0; i < nitems(cmds); i++) 241 fprintf(stderr, " %s %s\n", getprogname(), cmds[i].usage); 242 } 243 244 static void 245 dump_family(struct genl_family *family) 246 { 247 printf("Name: %s\n\tID: %#02hx, Version: %#02x, " 248 "header size: %d, max attributes: %d\n", 249 family->name, family->id, family->version, 250 family->hdrsize, family->max_attr); 251 dump_operations(&family->ops); 252 dump_mcast_groups(&family->mcast_groups); 253 } 254 255 void 256 parser_nlctrl_notify(struct snl_state *ss, struct nlmsghdr *hdr) 257 { 258 struct genl_family family = {}; 259 260 if (snl_parse_nlmsg(ss, hdr, &genl_family_parser, 261 &family)) 262 dump_family(&family); 263 } 264 265 void 266 parser_nlsysevent(struct snl_state *ss, struct nlmsghdr *hdr) 267 { 268 struct nlevent ne = {}; 269 if (snl_parse_nlmsg(ss, hdr, &nlevent_get_parser, &ne)) { 270 printf("system=%s subsystem=%s type=%s", ne.name, ne.subsystem, ne.type); 271 if (ne.data) { 272 printf(" %s", ne.data); 273 if (ne.data[strlen(ne.data) -1] != '\n') 274 printf("\n"); 275 } 276 } 277 } 278 279 void 280 parser_fallback(struct snl_state *ss __unused, struct nlmsghdr *hdr __unused) 281 { 282 printf("New unknown message\n"); 283 } 284 285 /* Populated by monitor_mcast() and may be used by protocol parser callbacks. */ 286 static struct genl_family attrs; 287 288 static int 289 monitor_mcast(int argc, char **argv) 290 { 291 struct snl_state ss; 292 struct snl_writer nw; 293 struct nlmsghdr *hdr; 294 struct pollfd pfd; 295 bool found = false; 296 bool all = false; 297 void (*parser)(struct snl_state *ss, struct nlmsghdr *hdr); 298 299 if (argc < 1 || argc > 2) { 300 usage(); 301 return (EXIT_FAILURE); 302 } 303 304 if (!snl_init(&ss, NETLINK_GENERIC)) 305 err(EXIT_FAILURE, "snl_init()"); 306 snl_init_writer(&ss, &nw); 307 snl_create_genl_msg_request(&nw, GENL_ID_CTRL, CTRL_CMD_GETFAMILY); 308 snl_add_msg_attr_string(&nw, CTRL_ATTR_FAMILY_NAME, argv[0]); 309 if ((hdr = snl_finalize_msg(&nw)) == NULL) 310 err(EXIT_FAILURE, "snl_finalize_msg"); 311 if (!snl_send_message(&ss, hdr)) 312 err(EXIT_FAILURE, "snl_send_message"); 313 hdr = snl_read_reply(&ss, hdr->nlmsg_seq); 314 if (hdr == NULL) 315 err(EXIT_FAILURE, "snl_read_reply"); 316 if (hdr->nlmsg_type == NLMSG_ERROR) 317 err(EXIT_FAILURE, "netlink(4) returned error"); 318 memset(&attrs, 0, sizeof(attrs)); 319 if (!snl_parse_nlmsg(&ss, hdr, &genl_family_parser, &attrs)) 320 err(EXIT_FAILURE, "snl_parse_nlmsg CTRL_CMD_GETFAMILY"); 321 322 if (argc == 1) 323 all = true; 324 for (u_int i = 0; i < attrs.mcast_groups.num_groups; i++) { 325 if (all || 326 strcmp(attrs.mcast_groups.groups[i]->name, argv[1]) == 0) { 327 found = true; 328 if (setsockopt(ss.fd, SOL_NETLINK, 329 NETLINK_ADD_MEMBERSHIP, 330 &attrs.mcast_groups.groups[i]->id, 331 sizeof(attrs.mcast_groups.groups[i]->id)) 332 == -1) 333 err(EXIT_FAILURE, "Cannot subscribe to command " 334 "notify"); 335 if (!all) 336 break; 337 } 338 } 339 if (!found) 340 errx(EXIT_FAILURE, "No such multicast group '%s'" 341 " in family '%s'", argv[1], argv[0]); 342 parser = parser_fallback; 343 for (size_t i= 0; i < nitems(mcast_parsers); i++) { 344 if (strcmp(mcast_parsers[i].family, argv[0]) == 0) { 345 parser = mcast_parsers[i].parser; 346 break; 347 } 348 } 349 memset(&pfd, 0, sizeof(pfd)); 350 pfd.fd = ss.fd; 351 pfd.events = POLLIN | POLLERR; 352 while (true) { 353 pfd.revents = 0; 354 if (poll(&pfd, 1, -1) == -1) { 355 if (errno == EINTR) 356 continue; 357 err(EXIT_FAILURE, "poll()"); 358 } 359 hdr = snl_read_message(&ss); 360 if (hdr != NULL && hdr->nlmsg_type != NLMSG_ERROR) 361 parser(&ss, hdr); 362 363 } 364 365 return (EXIT_SUCCESS); 366 } 367 368 int 369 list_families(int argc, char **argv __unused) 370 { 371 struct snl_state ss; 372 struct snl_writer nw; 373 struct nlmsghdr *hdr; 374 struct snl_errmsg_data e = {}; 375 uint32_t seq_id; 376 377 if (argc != 0) { 378 usage(); 379 return (EXIT_FAILURE); 380 } 381 if (!snl_init(&ss, NETLINK_GENERIC)) 382 err(EXIT_FAILURE, "snl_init()"); 383 384 snl_init_writer(&ss, &nw); 385 hdr = snl_create_genl_msg_request(&nw, GENL_ID_CTRL, 386 CTRL_CMD_GETFAMILY); 387 if ((hdr = snl_finalize_msg(&nw)) == NULL) 388 err(EXIT_FAILURE, "snl_finalize_msg"); 389 seq_id = hdr->nlmsg_seq; 390 if (!snl_send_message(&ss, hdr)) 391 err(EXIT_FAILURE, "snl_send_message"); 392 393 while ((hdr = snl_read_reply_multi(&ss, seq_id, &e)) != NULL) { 394 if (e.error != 0) { 395 err(EXIT_FAILURE, "Error reading generic netlink"); 396 } 397 struct genl_family family = {}; 398 if (snl_parse_nlmsg(&ss, hdr, &genl_family_parser, &family)) 399 dump_family(&family); 400 } 401 402 return (EXIT_SUCCESS); 403 } 404 405 int 406 main(int argc, char **argv) 407 { 408 if (modfind("netlink") == -1) 409 err(EXIT_FAILURE, "require netlink module to be loaded"); 410 411 if (argc == 1) 412 return (list_families(0, NULL)); 413 414 for (size_t i = 0; i < nitems(cmds); i++) { 415 if (strcmp(argv[1], cmds[i].name) == 0) 416 return (cmds[i].cmd(argc - 2, argv + 2)); 417 } 418 usage(); 419 420 return (EXIT_FAILURE); 421 } 422