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
48static int get(int id);
49
50static bool verbose = false;
51
52extern char *__progname;
53
54static void
55usage(void)
56{
57	fprintf(stderr,
58"usage: %s [-lc] [-d id] [-s id ...] [-v]\n",
59	    __progname);
60
61	exit(1);
62}
63
64static int
65pflow_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
80struct pflowctl_list {
81	int id;
82};
83#define	_IN(_field)	offsetof(struct genlmsghdr, _field)
84#define	_OUT(_field)	offsetof(struct pflowctl_list, _field)
85static struct snl_attr_parser ap_list[] = {
86	{ .type = PFLOWNL_L_ID, .off = _OUT(id), .cb = snl_attr_get_int32 },
87};
88static struct snl_field_parser fp_list[] = {};
89#undef _IN
90#undef _OUT
91SNL_DECLARE_PARSER(list_parser, struct genlmsghdr, fp_list, ap_list);
92
93static int
94list(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
132struct pflowctl_create {
133	int id;
134};
135#define	_IN(_field)	offsetof(struct genlmsghsdr, _field)
136#define	_OUT(_field)	offsetof(struct pflowctl_create, _field)
137static struct snl_attr_parser ap_create[] = {
138	{ .type = PFLOWNL_CREATE_ID, .off = _OUT(id), .cb = snl_attr_get_int32 },
139};
140static struct snl_field_parser pf_create[] = {};
141#undef _IN
142#undef _OUT
143SNL_DECLARE_PARSER(create_parser, struct genlmsghdr, pf_create, ap_create);
144
145static int
146create(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
184static int
185del(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
221struct pflowctl_sockaddr {
222	union {
223		struct sockaddr_in in;
224		struct sockaddr_in6 in6;
225		struct sockaddr_storage storage;
226	};
227};
228static bool
229pflowctl_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)
243static 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};
249SNL_DECLARE_ATTR_PARSER_EXT(sockaddr_parser, 0, nla_p_sockaddr, pflowctl_post_sockaddr);
250#undef _OUT
251
252struct 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)
262static 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};
270static struct snl_field_parser fp_get[] = {};
271#undef _IN
272#undef _OUT
273SNL_DECLARE_PARSER(get_parser, struct genlmsghdr, fp_get, ap_get);
274
275static void
276print_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
316static int
317get(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
363struct 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};
370static inline bool
371snl_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
398static int
399do_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
438static void
439pflowctl_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
489static int
490set(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
539static const struct snl_hdr_parser *all_parsers[] = {
540	&list_parser,
541	&get_parser,
542};
543
544enum pflowctl_op_t {
545	OP_HELP,
546	OP_LIST,
547	OP_CREATE,
548	OP_DELETE,
549	OP_SET,
550};
551int
552main(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