1 /*-
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright (c) 2023 Rubicon Communications, LLC (Netgate)
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 *
10 * - Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * - Redistributions in binary form must reproduce the above
13 * copyright notice, this list of conditions and the following
14 * disclaimer in the documentation and/or other materials provided
15 * with the distribution.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
20 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
21 * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
22 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
23 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
25 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
27 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28 * POSSIBILITY OF SUCH DAMAGE.
29 *
30 */
31 #include <sys/cdefs.h>
32
33 #include <err.h>
34 #include <errno.h>
35 #include <netdb.h>
36 #include <stdio.h>
37 #include <stdlib.h>
38 #include <unistd.h>
39
40 #include <net/pflow.h>
41
42 #include <netlink/netlink.h>
43 #include <netlink/netlink_generic.h>
44 #include <netlink/netlink_snl.h>
45 #include <netlink/netlink_snl_generic.h>
46 #include <netlink/netlink_snl_route.h>
47
48 static int get(int id);
49
50 static bool verbose = false;
51
52 extern char *__progname;
53
54 static void
usage(void)55 usage(void)
56 {
57 fprintf(stderr,
58 "usage: %s [-lc] [-d id] [-s id ...] [-v]\n",
59 __progname);
60
61 exit(1);
62 }
63
64 static int
pflow_to_id(const char * name)65 pflow_to_id(const char *name)
66 {
67 int ret, id;
68
69 ret = sscanf(name, "pflow%d", &id);
70 if (ret == 1)
71 return (id);
72
73 ret = sscanf(name, "%d", &id);
74 if (ret == 1)
75 return (id);
76
77 return (-1);
78 }
79
80 struct pflowctl_list {
81 int id;
82 };
83 #define _IN(_field) offsetof(struct genlmsghdr, _field)
84 #define _OUT(_field) offsetof(struct pflowctl_list, _field)
85 static struct snl_attr_parser ap_list[] = {
86 { .type = PFLOWNL_L_ID, .off = _OUT(id), .cb = snl_attr_get_int32 },
87 };
88 static struct snl_field_parser fp_list[] = {};
89 #undef _IN
90 #undef _OUT
91 SNL_DECLARE_PARSER(list_parser, struct genlmsghdr, fp_list, ap_list);
92
93 static int
list(void)94 list(void)
95 {
96 struct snl_state ss = {};
97 struct snl_errmsg_data e = {};
98 struct pflowctl_list l = {};
99 struct snl_writer nw;
100 struct nlmsghdr *hdr;
101 uint32_t seq_id;
102 int family_id;
103
104 snl_init(&ss, NETLINK_GENERIC);
105 family_id = snl_get_genl_family(&ss, PFLOWNL_FAMILY_NAME);
106 if (family_id == 0)
107 errx(1, "pflow.ko is not loaded.");
108
109 snl_init_writer(&ss, &nw);
110 hdr = snl_create_genl_msg_request(&nw, family_id, PFLOWNL_CMD_LIST);
111
112 hdr = snl_finalize_msg(&nw);
113 if (hdr == NULL)
114 return (ENOMEM);
115 seq_id = hdr->nlmsg_seq;
116
117 snl_send_message(&ss, hdr);
118
119 while ((hdr = snl_read_reply_multi(&ss, seq_id, &e)) != NULL) {
120 if (! snl_parse_nlmsg(&ss, hdr, &list_parser, &l))
121 continue;
122
123 get(l.id);
124 }
125
126 if (e.error)
127 errc(1, e.error, "failed to list");
128
129 return (0);
130 }
131
132 struct pflowctl_create {
133 int id;
134 };
135 #define _IN(_field) offsetof(struct genlmsghsdr, _field)
136 #define _OUT(_field) offsetof(struct pflowctl_create, _field)
137 static struct snl_attr_parser ap_create[] = {
138 { .type = PFLOWNL_CREATE_ID, .off = _OUT(id), .cb = snl_attr_get_int32 },
139 };
140 static struct snl_field_parser pf_create[] = {};
141 #undef _IN
142 #undef _OUT
143 SNL_DECLARE_PARSER(create_parser, struct genlmsghdr, pf_create, ap_create);
144
145 static int
create(void)146 create(void)
147 {
148 struct snl_state ss = {};
149 struct snl_errmsg_data e = {};
150 struct pflowctl_create c = {};
151 struct snl_writer nw;
152 struct nlmsghdr *hdr;
153 uint32_t seq_id;
154 int family_id;
155
156 snl_init(&ss, NETLINK_GENERIC);
157 family_id = snl_get_genl_family(&ss, PFLOWNL_FAMILY_NAME);
158 if (family_id == 0)
159 errx(1, "pflow.ko is not loaded.");
160
161 snl_init_writer(&ss, &nw);
162 hdr = snl_create_genl_msg_request(&nw, family_id, PFLOWNL_CMD_CREATE);
163
164 hdr = snl_finalize_msg(&nw);
165 if (hdr == NULL)
166 return (ENOMEM);
167 seq_id = hdr->nlmsg_seq;
168
169 snl_send_message(&ss, hdr);
170
171 while ((hdr = snl_read_reply_multi(&ss, seq_id, &e)) != NULL) {
172 if (! snl_parse_nlmsg(&ss, hdr, &create_parser, &c))
173 continue;
174
175 printf("pflow%d\n", c.id);
176 }
177
178 if (e.error)
179 errc(1, e.error, "failed to create");
180
181 return (0);
182 }
183
184 static int
del(char * idstr)185 del(char *idstr)
186 {
187 struct snl_state ss = {};
188 struct snl_errmsg_data e = {};
189 struct snl_writer nw;
190 struct nlmsghdr *hdr;
191 int family_id;
192 int id;
193
194 id = pflow_to_id(idstr);
195 if (id < 0)
196 return (EINVAL);
197
198 snl_init(&ss, NETLINK_GENERIC);
199 family_id = snl_get_genl_family(&ss, PFLOWNL_FAMILY_NAME);
200 if (family_id == 0)
201 errx(1, "pflow.ko is not loaded.");
202
203 snl_init_writer(&ss, &nw);
204 hdr = snl_create_genl_msg_request(&nw, family_id, PFLOWNL_CMD_DEL);
205
206 snl_add_msg_attr_s32(&nw, PFLOWNL_DEL_ID, id);
207
208 hdr = snl_finalize_msg(&nw);
209 if (hdr == NULL)
210 return (ENOMEM);
211
212 snl_send_message(&ss, hdr);
213 snl_read_reply_code(&ss, hdr->nlmsg_seq, &e);
214
215 if (e.error)
216 errc(1, e.error, "failed to delete");
217
218 return (0);
219 }
220
221 struct pflowctl_sockaddr {
222 union {
223 struct sockaddr_in in;
224 struct sockaddr_in6 in6;
225 struct sockaddr_storage storage;
226 };
227 };
228 static bool
pflowctl_post_sockaddr(struct snl_state * ss __unused,void * target)229 pflowctl_post_sockaddr(struct snl_state* ss __unused, void *target)
230 {
231 struct pflowctl_sockaddr *s = (struct pflowctl_sockaddr *)target;
232
233 if (s->storage.ss_family == AF_INET)
234 s->storage.ss_len = sizeof(struct sockaddr_in);
235 else if (s->storage.ss_family == AF_INET6)
236 s->storage.ss_len = sizeof(struct sockaddr_in6);
237 else
238 return (false);
239
240 return (true);
241 }
242 #define _OUT(_field) offsetof(struct pflowctl_sockaddr, _field)
243 static struct snl_attr_parser nla_p_sockaddr[] = {
244 { .type = PFLOWNL_ADDR_FAMILY, .off = _OUT(in.sin_family), .cb = snl_attr_get_uint8 },
245 { .type = PFLOWNL_ADDR_PORT, .off = _OUT(in.sin_port), .cb = snl_attr_get_uint16 },
246 { .type = PFLOWNL_ADDR_IP, .off = _OUT(in.sin_addr), .cb = snl_attr_get_in_addr },
247 { .type = PFLOWNL_ADDR_IP6, .off = _OUT(in6.sin6_addr), .cb = snl_attr_get_in6_addr },
248 };
249 SNL_DECLARE_ATTR_PARSER_EXT(sockaddr_parser, 0, nla_p_sockaddr, pflowctl_post_sockaddr);
250 #undef _OUT
251
252 struct pflowctl_get {
253 int id;
254 int version;
255 struct pflowctl_sockaddr src;
256 struct pflowctl_sockaddr dst;
257 uint32_t obs_dom;
258 uint8_t so_status;
259 };
260 #define _IN(_field) offsetof(struct genlmsghdr, _field)
261 #define _OUT(_field) offsetof(struct pflowctl_get, _field)
262 static struct snl_attr_parser ap_get[] = {
263 { .type = PFLOWNL_GET_ID, .off = _OUT(id), .cb = snl_attr_get_int32 },
264 { .type = PFLOWNL_GET_VERSION, .off = _OUT(version), .cb = snl_attr_get_int16 },
265 { .type = PFLOWNL_GET_SRC, .off = _OUT(src), .arg = &sockaddr_parser, .cb = snl_attr_get_nested },
266 { .type = PFLOWNL_GET_DST, .off = _OUT(dst), .arg = &sockaddr_parser, .cb = snl_attr_get_nested },
267 { .type = PFLOWNL_GET_OBSERVATION_DOMAIN, .off = _OUT(obs_dom), .cb = snl_attr_get_uint32 },
268 { .type = PFLOWNL_GET_SOCKET_STATUS, .off = _OUT(so_status), .cb = snl_attr_get_uint8 },
269 };
270 static struct snl_field_parser fp_get[] = {};
271 #undef _IN
272 #undef _OUT
273 SNL_DECLARE_PARSER(get_parser, struct genlmsghdr, fp_get, ap_get);
274
275 static void
print_sockaddr(const char * prefix,const struct sockaddr_storage * s)276 print_sockaddr(const char *prefix, const struct sockaddr_storage *s)
277 {
278 char buf[INET6_ADDRSTRLEN];
279 int error;
280
281 if (s->ss_family != AF_INET && s->ss_family != AF_INET6)
282 return;
283
284 if (s->ss_family == AF_INET ||
285 s->ss_family == AF_INET6) {
286 error = getnameinfo((const struct sockaddr *)s,
287 s->ss_len, buf, sizeof(buf), NULL, 0,
288 NI_NUMERICHOST);
289 if (error)
290 err(1, "sender: %s", gai_strerror(error));
291 }
292
293 printf("%s", prefix);
294 switch (s->ss_family) {
295 case AF_INET: {
296 const struct sockaddr_in *sin = (const struct sockaddr_in *)s;
297 if (sin->sin_addr.s_addr != INADDR_ANY) {
298 printf("%s", buf);
299 if (sin->sin_port != 0)
300 printf(":%u", ntohs(sin->sin_port));
301 }
302 break;
303 }
304 case AF_INET6: {
305 const struct sockaddr_in6 *sin6 = (const struct sockaddr_in6 *)s;
306 if (!IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr)) {
307 printf("[%s]", buf);
308 if (sin6->sin6_port != 0)
309 printf(":%u", ntohs(sin6->sin6_port));
310 }
311 break;
312 }
313 }
314 }
315
316 static int
get(int id)317 get(int id)
318 {
319 struct snl_state ss = {};
320 struct snl_errmsg_data e = {};
321 struct pflowctl_get g = {};
322 struct snl_writer nw;
323 struct nlmsghdr *hdr;
324 uint32_t seq_id;
325 int family_id;
326
327 snl_init(&ss, NETLINK_GENERIC);
328 family_id = snl_get_genl_family(&ss, PFLOWNL_FAMILY_NAME);
329 if (family_id == 0)
330 errx(1, "pflow.ko is not loaded.");
331
332 snl_init_writer(&ss, &nw);
333 hdr = snl_create_genl_msg_request(&nw, family_id, PFLOWNL_CMD_GET);
334 snl_add_msg_attr_s32(&nw, PFLOWNL_GET_ID, id);
335
336 hdr = snl_finalize_msg(&nw);
337 if (hdr == NULL)
338 return (ENOMEM);
339 seq_id = hdr->nlmsg_seq;
340
341 snl_send_message(&ss, hdr);
342
343 while ((hdr = snl_read_reply_multi(&ss, seq_id, &e)) != NULL) {
344 if (! snl_parse_nlmsg(&ss, hdr, &get_parser, &g))
345 continue;
346
347 printf("pflow%d: version %d domain %u", g.id, g.version, g.obs_dom);
348 print_sockaddr(" src ", &g.src.storage);
349 print_sockaddr(" dst ", &g.dst.storage);
350 printf("\n");
351 if (verbose) {
352 printf("\tsocket: %s\n",
353 g.so_status ? "connected" : "disconnected");
354 }
355 }
356
357 if (e.error)
358 errc(1, e.error, "failed to get");
359
360 return (0);
361 }
362
363 struct pflowctl_set {
364 int id;
365 uint16_t version;
366 struct sockaddr_storage src;
367 struct sockaddr_storage dst;
368 uint32_t obs_dom;
369 };
370 static inline bool
snl_add_msg_attr_sockaddr(struct snl_writer * nw,int attrtype,struct sockaddr_storage * s)371 snl_add_msg_attr_sockaddr(struct snl_writer *nw, int attrtype, struct sockaddr_storage *s)
372 {
373 int off = snl_add_msg_attr_nested(nw, attrtype);
374
375 snl_add_msg_attr_u8(nw, PFLOWNL_ADDR_FAMILY, s->ss_family);
376
377 switch (s->ss_family) {
378 case AF_INET: {
379 const struct sockaddr_in *in = (const struct sockaddr_in *)s;
380 snl_add_msg_attr_u16(nw, PFLOWNL_ADDR_PORT, in->sin_port);
381 snl_add_msg_attr_ip4(nw, PFLOWNL_ADDR_IP, &in->sin_addr);
382 break;
383 }
384 case AF_INET6: {
385 const struct sockaddr_in6 *in6 = (const struct sockaddr_in6 *)s;
386 snl_add_msg_attr_u16(nw, PFLOWNL_ADDR_PORT, in6->sin6_port);
387 snl_add_msg_attr_ip6(nw, PFLOWNL_ADDR_IP6, &in6->sin6_addr);
388 break;
389 }
390 default:
391 return (false);
392 }
393 snl_end_attr_nested(nw, off);
394
395 return (true);
396 }
397
398 static int
do_set(struct pflowctl_set * s)399 do_set(struct pflowctl_set *s)
400 {
401 struct snl_state ss = {};
402 struct snl_errmsg_data e = {};
403 struct snl_writer nw;
404 struct nlmsghdr *hdr;
405 int family_id;
406
407 snl_init(&ss, NETLINK_GENERIC);
408 family_id = snl_get_genl_family(&ss, PFLOWNL_FAMILY_NAME);
409 if (family_id == 0)
410 errx(1, "pflow.ko is not loaded.");
411
412 snl_init_writer(&ss, &nw);
413 snl_create_genl_msg_request(&nw, family_id, PFLOWNL_CMD_SET);
414
415 snl_add_msg_attr_s32(&nw, PFLOWNL_SET_ID, s->id);
416 if (s->version != 0)
417 snl_add_msg_attr_u16(&nw, PFLOWNL_SET_VERSION, s->version);
418 if (s->src.ss_len != 0)
419 snl_add_msg_attr_sockaddr(&nw, PFLOWNL_SET_SRC, &s->src);
420 if (s->dst.ss_len != 0)
421 snl_add_msg_attr_sockaddr(&nw, PFLOWNL_SET_DST, &s->dst);
422 if (s->obs_dom != 0)
423 snl_add_msg_attr_u32(&nw, PFLOWNL_SET_OBSERVATION_DOMAIN, s->obs_dom);
424
425 hdr = snl_finalize_msg(&nw);
426 if (hdr == NULL)
427 return (1);
428
429 snl_send_message(&ss, hdr);
430 snl_read_reply_code(&ss, hdr->nlmsg_seq, &e);
431
432 if (e.error)
433 errc(1, e.error, "failed to set");
434
435 return (0);
436 }
437
438 static void
pflowctl_addr(const char * val,struct sockaddr_storage * ss)439 pflowctl_addr(const char *val, struct sockaddr_storage *ss)
440 {
441 struct addrinfo *res0;
442 int error;
443 bool flag;
444 char *ip, *port;
445 char buf[sysconf(_SC_HOST_NAME_MAX) + 1 + sizeof(":65535")];
446 struct addrinfo hints = {
447 .ai_family = AF_UNSPEC,
448 .ai_socktype = SOCK_DGRAM, /*dummy*/
449 .ai_flags = AI_NUMERICHOST,
450 };
451
452 if (strlcpy(buf, val, sizeof(buf)) >= sizeof(buf))
453 errx(1, "%s bad value", val);
454
455 port = NULL;
456 flag = *buf == '[';
457
458 for (char *cp = buf; *cp; ++cp) {
459 if (*cp == ']' && *(cp + 1) == ':' && flag) {
460 *cp = '\0';
461 *(cp + 1) = '\0';
462 port = cp + 2;
463 break;
464 }
465 if (*cp == ']' && *(cp + 1) == '\0' && flag) {
466 *cp = '\0';
467 port = NULL;
468 break;
469 }
470 if (*cp == ':' && !flag) {
471 *cp = '\0';
472 port = cp + 1;
473 break;
474 }
475 }
476
477 ip = buf;
478 if (flag)
479 ip++;
480
481 if ((error = getaddrinfo(ip, port, &hints, &res0)) != 0)
482 errx(1, "error in parsing address string: %s",
483 gai_strerror(error));
484
485 memcpy(ss, res0->ai_addr, res0->ai_addr->sa_len);
486 freeaddrinfo(res0);
487 }
488
489 static int
set(char * idstr,int argc,char * argv[])490 set(char *idstr, int argc, char *argv[])
491 {
492 struct pflowctl_set s = {};
493
494 s.id = pflow_to_id(idstr);
495 if (s.id < 0)
496 return (EINVAL);
497
498 while (argc > 0) {
499 if (strcmp(argv[0], "src") == 0) {
500 if (argc < 2)
501 usage();
502
503 pflowctl_addr(argv[1], &s.src);
504
505 argc -= 2;
506 argv += 2;
507 } else if (strcmp(argv[0], "dst") == 0) {
508 if (argc < 2)
509 usage();
510
511 pflowctl_addr(argv[1], &s.dst);
512
513 argc -= 2;
514 argv += 2;
515 } else if (strcmp(argv[0], "proto") == 0) {
516 if (argc < 2)
517 usage();
518
519 s.version = strtol(argv[1], NULL, 10);
520
521 argc -= 2;
522 argv += 2;
523 } else if (strcmp(argv[0], "domain") == 0) {
524 if (argc < 2)
525 usage();
526
527 s.obs_dom = strtol(argv[1], NULL, 10);
528
529 argc -= 2;
530 argv += 2;
531 } else {
532 usage();
533 }
534 }
535
536 return (do_set(&s));
537 }
538
539 static const struct snl_hdr_parser *all_parsers[] = {
540 &list_parser,
541 &get_parser,
542 };
543
544 enum pflowctl_op_t {
545 OP_HELP,
546 OP_LIST,
547 OP_CREATE,
548 OP_DELETE,
549 OP_SET,
550 };
551 int
main(int argc,char * argv[])552 main(int argc, char *argv[])
553 {
554 int ch;
555 enum pflowctl_op_t op = OP_HELP;
556 char **set_args = NULL;
557 size_t set_arg_count = 0;
558
559 SNL_VERIFY_PARSERS(all_parsers);
560
561 if (argc < 2)
562 usage();
563
564 while ((ch = getopt(argc, argv,
565 "lcd:s:v")) != -1) {
566 switch (ch) {
567 case 'l':
568 op = OP_LIST;
569 break;
570 case 'c':
571 op = OP_CREATE;
572 break;
573 case 'd':
574 op = OP_DELETE;
575 break;
576 case 's':
577 op = OP_SET;
578 set_arg_count = argc - optind;
579 set_args = argv + optind;
580 break;
581 case 'v':
582 verbose = true;
583 break;
584 }
585 }
586
587 switch (op) {
588 case OP_LIST:
589 return (list());
590 case OP_CREATE:
591 return (create());
592 case OP_DELETE:
593 return (del(optarg));
594 case OP_SET:
595 return (set(optarg, set_arg_count, set_args));
596 case OP_HELP:
597 usage();
598 break;
599 }
600
601 return (0);
602 }
603