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 struct genl_ctrl_op { 85 uint32_t id; 86 uint32_t flags; 87 }; 88 89 struct genl_ctrl_ops { 90 uint32_t num_ops; 91 struct genl_ctrl_op **ops; 92 }; 93 94 #define _OUT(_field) offsetof(struct genl_ctrl_op, _field) 95 static struct snl_attr_parser _nla_p_getops[] = { 96 { .type = CTRL_ATTR_OP_ID, .off = _OUT(id), .cb = snl_attr_get_uint32}, 97 { .type = CTRL_ATTR_OP_FLAGS, .off = _OUT(flags), .cb = snl_attr_get_uint32 }, 98 }; 99 #undef _OUT 100 SNL_DECLARE_ATTR_PARSER_EXT(genl_ctrl_op_parser, 101 sizeof(struct genl_ctrl_op), 102 _nla_p_getops, NULL); 103 104 struct genl_family { 105 uint16_t id; 106 char *name; 107 uint32_t version; 108 uint32_t hdrsize; 109 uint32_t max_attr; 110 struct snl_genl_ctrl_mcast_groups mcast_groups; 111 struct genl_ctrl_ops ops; 112 }; 113 114 #define _OUT(_field) offsetof(struct genl_family, _field) 115 static struct snl_attr_parser _nla_p_getfamily[] = { 116 { .type = CTRL_ATTR_FAMILY_ID , .off = _OUT(id), .cb = snl_attr_get_uint16 }, 117 { .type = CTRL_ATTR_FAMILY_NAME, .off = _OUT(name), .cb = snl_attr_get_string }, 118 { .type = CTRL_ATTR_VERSION, .off = _OUT(version), .cb = snl_attr_get_uint32 }, 119 { .type = CTRL_ATTR_VERSION, .off = _OUT(hdrsize), .cb = snl_attr_get_uint32 }, 120 { .type = CTRL_ATTR_MAXATTR, .off = _OUT(max_attr), .cb = snl_attr_get_uint32 }, 121 { 122 .type = CTRL_ATTR_OPS, 123 .off = _OUT(ops), 124 .cb = snl_attr_get_parray, 125 .arg = &genl_ctrl_op_parser, 126 }, 127 { 128 .type = CTRL_ATTR_MCAST_GROUPS, 129 .off = _OUT(mcast_groups), 130 .cb = snl_attr_get_parray, 131 .arg = &_genl_ctrl_mc_parser, 132 }, 133 }; 134 #undef _OUT 135 SNL_DECLARE_GENL_PARSER(genl_family_parser, _nla_p_getfamily); 136 137 static struct op_capability { 138 uint32_t flag; 139 const char *str; 140 } op_caps[] = { 141 { GENL_ADMIN_PERM, "requires admin permission" }, 142 { GENL_CMD_CAP_DO, "can modify" }, 143 { GENL_CMD_CAP_DUMP, "can get/dump" }, 144 { GENL_CMD_CAP_HASPOL, "has policy" }, 145 }; 146 147 static void 148 dump_operations(struct genl_ctrl_ops *ops) 149 { 150 if (ops->num_ops == 0) 151 return; 152 printf("\tsupported operations: \n"); 153 for (uint32_t i = 0; i < ops->num_ops; i++) { 154 printf("\t - ID: %#02x, Capabilities: %#02x (", 155 ops->ops[i]->id, 156 ops->ops[i]->flags); 157 for (size_t j = 0; j < nitems(op_caps); j++) 158 if ((ops->ops[i]->flags & op_caps[j].flag) == op_caps[j].flag) 159 printf("%s; ", op_caps[j].str); 160 printf("\b\b)\n"); 161 } 162 } 163 164 static void 165 dump_mcast_groups( struct snl_genl_ctrl_mcast_groups *mcast_groups) 166 { 167 if (mcast_groups->num_groups == 0) 168 return; 169 printf("\tmulticast groups: \n"); 170 for (uint32_t i = 0; i < mcast_groups->num_groups; i++) 171 printf("\t - ID: %#02x, Name: %s\n", 172 mcast_groups->groups[i]->mcast_grp_id, 173 mcast_groups->groups[i]->mcast_grp_name); 174 } 175 176 static void 177 usage(void) 178 { 179 fprintf(stderr, "Usage: %s\n", getprogname()); 180 for (size_t i = 0; i < nitems(cmds); i++) 181 fprintf(stderr, " %s %s\n", getprogname(), cmds[i].usage); 182 } 183 184 static void 185 dump_family(struct genl_family *family) 186 { 187 printf("Name: %s\n\tID: %#02hx, Version: %#02x, " 188 "header size: %d, max attributes: %d\n", 189 family->name, family->id, family->version, 190 family->hdrsize, family->max_attr); 191 dump_operations(&family->ops); 192 dump_mcast_groups(&family->mcast_groups); 193 } 194 195 void 196 parser_nlctrl_notify(struct snl_state *ss, struct nlmsghdr *hdr) 197 { 198 struct genl_family family = {}; 199 200 if (snl_parse_nlmsg(ss, hdr, &genl_family_parser, 201 &family)) 202 dump_family(&family); 203 } 204 205 void 206 parser_nlsysevent(struct snl_state *ss, struct nlmsghdr *hdr) 207 { 208 struct nlevent ne = {}; 209 if (snl_parse_nlmsg(ss, hdr, &nlevent_get_parser, &ne)) { 210 printf("system=%s subsystem=%s type=%s", ne.name, ne.subsystem, ne.type); 211 if (ne.data) { 212 printf(" %s", ne.data); 213 if (ne.data[strlen(ne.data) -1] != '\n') 214 printf("\n"); 215 } 216 } 217 } 218 219 void 220 parser_fallback(struct snl_state *ss __unused, struct nlmsghdr *hdr __unused) 221 { 222 printf("New unknown message\n"); 223 } 224 225 int 226 monitor_mcast(int argc __unused, char **argv) 227 { 228 struct snl_state ss; 229 struct nlmsghdr *hdr; 230 struct _getfamily_attrs attrs; 231 struct pollfd pfd; 232 bool found = false; 233 bool all = false; 234 void (*parser)(struct snl_state *ss, struct nlmsghdr *hdr); 235 236 parser = parser_fallback; 237 238 if (!snl_init(&ss, NETLINK_GENERIC)) 239 err(EXIT_FAILURE, "snl_init()"); 240 241 if (argc < 1 || argc > 2) { 242 usage(); 243 return (EXIT_FAILURE); 244 } 245 246 if (!snl_get_genl_family_info(&ss, argv[0], &attrs)) 247 errx(EXIT_FAILURE, "Unknown family '%s'", argv[0]); 248 if (argc == 1) 249 all = true; 250 for (unsigned int i = 0; i < attrs.mcast_groups.num_groups; i++) { 251 if (all || strcmp(attrs.mcast_groups.groups[i]->mcast_grp_name, 252 argv[1]) == 0) { 253 found = true; 254 if (setsockopt(ss.fd, SOL_NETLINK, 255 NETLINK_ADD_MEMBERSHIP, 256 &attrs.mcast_groups.groups[i]->mcast_grp_id, 257 sizeof(attrs.mcast_groups.groups[i]->mcast_grp_id)) 258 == -1) 259 err(EXIT_FAILURE, "Cannot subscribe to command " 260 "notify"); 261 if (!all) 262 break; 263 } 264 } 265 if (!found) 266 errx(EXIT_FAILURE, "No such multicat group '%s'" 267 " in family '%s'", argv[1], argv[0]); 268 for (size_t i= 0; i < nitems(mcast_parsers); i++) { 269 if (strcmp(mcast_parsers[i].family, argv[0]) == 0) { 270 parser = mcast_parsers[i].parser; 271 break; 272 } 273 } 274 memset(&pfd, 0, sizeof(pfd)); 275 pfd.fd = ss.fd; 276 pfd.events = POLLIN | POLLERR; 277 while (true) { 278 pfd.revents = 0; 279 if (poll(&pfd, 1, -1) == -1) { 280 if (errno == EINTR) 281 continue; 282 err(EXIT_FAILURE, "poll()"); 283 } 284 hdr = snl_read_message(&ss); 285 if (hdr != NULL && hdr->nlmsg_type != NLMSG_ERROR) 286 parser(&ss, hdr); 287 288 } 289 290 return (EXIT_SUCCESS); 291 } 292 293 int 294 list_families(int argc, char **argv __unused) 295 { 296 struct snl_state ss; 297 struct snl_writer nw; 298 struct nlmsghdr *hdr; 299 struct snl_errmsg_data e = {}; 300 uint32_t seq_id; 301 302 if (argc != 0) { 303 usage(); 304 return (EXIT_FAILURE); 305 } 306 if (!snl_init(&ss, NETLINK_GENERIC)) 307 err(EXIT_FAILURE, "snl_init()"); 308 309 snl_init_writer(&ss, &nw); 310 hdr = snl_create_genl_msg_request(&nw, GENL_ID_CTRL, 311 CTRL_CMD_GETFAMILY); 312 if ((hdr = snl_finalize_msg(&nw)) == NULL) 313 err(EXIT_FAILURE, "snl_finalize_msg"); 314 seq_id = hdr->nlmsg_seq; 315 if (!snl_send_message(&ss, hdr)) 316 err(EXIT_FAILURE, "snl_send_message"); 317 318 while ((hdr = snl_read_reply_multi(&ss, seq_id, &e)) != NULL) { 319 if (e.error != 0) { 320 err(EXIT_FAILURE, "Error reading generic netlink"); 321 } 322 struct genl_family family = {}; 323 if (snl_parse_nlmsg(&ss, hdr, &genl_family_parser, &family)) 324 dump_family(&family); 325 } 326 327 return (EXIT_SUCCESS); 328 } 329 330 int 331 main(int argc, char **argv) 332 { 333 if (modfind("netlink") == -1) 334 err(EXIT_FAILURE, "require netlink module to be loaded"); 335 336 if (argc == 1) 337 return (list_families(0, NULL)); 338 339 for (size_t i = 0; i < nitems(cmds); i++) { 340 if (strcmp(argv[1], cmds[i].name) == 0) 341 return (cmds[i].cmd(argc - 2, argv + 2)); 342 } 343 usage(); 344 345 return (EXIT_FAILURE); 346 } 347