diff options
Diffstat (limited to 'sbin/pflowctl/pflowctl.c')
-rw-r--r-- | sbin/pflowctl/pflowctl.c | 602 |
1 files changed, 602 insertions, 0 deletions
diff --git a/sbin/pflowctl/pflowctl.c b/sbin/pflowctl/pflowctl.c new file mode 100644 index 000000000000..b6561f7787c0 --- /dev/null +++ b/sbin/pflowctl/pflowctl.c @@ -0,0 +1,602 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2023 Rubicon Communications, LLC (Netgate) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ +#include <sys/cdefs.h> + +#include <err.h> +#include <errno.h> +#include <netdb.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +#include <net/pflow.h> + +#include <netlink/netlink.h> +#include <netlink/netlink_generic.h> +#include <netlink/netlink_snl.h> +#include <netlink/netlink_snl_generic.h> +#include <netlink/netlink_snl_route.h> + +static int get(int id); + +static bool verbose = false; + +extern char *__progname; + +static void +usage(void) +{ + fprintf(stderr, +"usage: %s [-lc] [-d id] [-s id ...] [-v]\n", + __progname); + + exit(1); +} + +static int +pflow_to_id(const char *name) +{ + int ret, id; + + ret = sscanf(name, "pflow%d", &id); + if (ret == 1) + return (id); + + ret = sscanf(name, "%d", &id); + if (ret == 1) + return (id); + + return (-1); +} + +struct pflowctl_list { + int id; +}; +#define _IN(_field) offsetof(struct genlmsghdr, _field) +#define _OUT(_field) offsetof(struct pflowctl_list, _field) +static struct snl_attr_parser ap_list[] = { + { .type = PFLOWNL_L_ID, .off = _OUT(id), .cb = snl_attr_get_int32 }, +}; +static struct snl_field_parser fp_list[] = {}; +#undef _IN +#undef _OUT +SNL_DECLARE_PARSER(list_parser, struct genlmsghdr, fp_list, ap_list); + +static int +list(void) +{ + struct snl_state ss = {}; + struct snl_errmsg_data e = {}; + struct pflowctl_list l = {}; + struct snl_writer nw; + struct nlmsghdr *hdr; + uint32_t seq_id; + int family_id; + + snl_init(&ss, NETLINK_GENERIC); + family_id = snl_get_genl_family(&ss, PFLOWNL_FAMILY_NAME); + if (family_id == 0) + errx(1, "pflow.ko is not loaded."); + + snl_init_writer(&ss, &nw); + hdr = snl_create_genl_msg_request(&nw, family_id, PFLOWNL_CMD_LIST); + + hdr = snl_finalize_msg(&nw); + if (hdr == NULL) + return (ENOMEM); + seq_id = hdr->nlmsg_seq; + + snl_send_message(&ss, hdr); + + while ((hdr = snl_read_reply_multi(&ss, seq_id, &e)) != NULL) { + if (! snl_parse_nlmsg(&ss, hdr, &list_parser, &l)) + continue; + + get(l.id); + } + + if (e.error) + errc(1, e.error, "failed to list"); + + return (0); +} + +struct pflowctl_create { + int id; +}; +#define _IN(_field) offsetof(struct genlmsghsdr, _field) +#define _OUT(_field) offsetof(struct pflowctl_create, _field) +static struct snl_attr_parser ap_create[] = { + { .type = PFLOWNL_CREATE_ID, .off = _OUT(id), .cb = snl_attr_get_int32 }, +}; +static struct snl_field_parser pf_create[] = {}; +#undef _IN +#undef _OUT +SNL_DECLARE_PARSER(create_parser, struct genlmsghdr, pf_create, ap_create); + +static int +create(void) +{ + struct snl_state ss = {}; + struct snl_errmsg_data e = {}; + struct pflowctl_create c = {}; + struct snl_writer nw; + struct nlmsghdr *hdr; + uint32_t seq_id; + int family_id; + + snl_init(&ss, NETLINK_GENERIC); + family_id = snl_get_genl_family(&ss, PFLOWNL_FAMILY_NAME); + if (family_id == 0) + errx(1, "pflow.ko is not loaded."); + + snl_init_writer(&ss, &nw); + hdr = snl_create_genl_msg_request(&nw, family_id, PFLOWNL_CMD_CREATE); + + hdr = snl_finalize_msg(&nw); + if (hdr == NULL) + return (ENOMEM); + seq_id = hdr->nlmsg_seq; + + snl_send_message(&ss, hdr); + + while ((hdr = snl_read_reply_multi(&ss, seq_id, &e)) != NULL) { + if (! snl_parse_nlmsg(&ss, hdr, &create_parser, &c)) + continue; + + printf("pflow%d\n", c.id); + } + + if (e.error) + errc(1, e.error, "failed to create"); + + return (0); +} + +static int +del(char *idstr) +{ + struct snl_state ss = {}; + struct snl_errmsg_data e = {}; + struct snl_writer nw; + struct nlmsghdr *hdr; + int family_id; + int id; + + id = pflow_to_id(idstr); + if (id < 0) + return (EINVAL); + + snl_init(&ss, NETLINK_GENERIC); + family_id = snl_get_genl_family(&ss, PFLOWNL_FAMILY_NAME); + if (family_id == 0) + errx(1, "pflow.ko is not loaded."); + + snl_init_writer(&ss, &nw); + hdr = snl_create_genl_msg_request(&nw, family_id, PFLOWNL_CMD_DEL); + + snl_add_msg_attr_s32(&nw, PFLOWNL_DEL_ID, id); + + hdr = snl_finalize_msg(&nw); + if (hdr == NULL) + return (ENOMEM); + + snl_send_message(&ss, hdr); + snl_read_reply_code(&ss, hdr->nlmsg_seq, &e); + + if (e.error) + errc(1, e.error, "failed to delete"); + + return (0); +} + +struct pflowctl_sockaddr { + union { + struct sockaddr_in in; + struct sockaddr_in6 in6; + struct sockaddr_storage storage; + }; +}; +static bool +pflowctl_post_sockaddr(struct snl_state* ss __unused, void *target) +{ + struct pflowctl_sockaddr *s = (struct pflowctl_sockaddr *)target; + + if (s->storage.ss_family == AF_INET) + s->storage.ss_len = sizeof(struct sockaddr_in); + else if (s->storage.ss_family == AF_INET6) + s->storage.ss_len = sizeof(struct sockaddr_in6); + else + return (false); + + return (true); +} +#define _OUT(_field) offsetof(struct pflowctl_sockaddr, _field) +static struct snl_attr_parser nla_p_sockaddr[] = { + { .type = PFLOWNL_ADDR_FAMILY, .off = _OUT(in.sin_family), .cb = snl_attr_get_uint8 }, + { .type = PFLOWNL_ADDR_PORT, .off = _OUT(in.sin_port), .cb = snl_attr_get_uint16 }, + { .type = PFLOWNL_ADDR_IP, .off = _OUT(in.sin_addr), .cb = snl_attr_get_in_addr }, + { .type = PFLOWNL_ADDR_IP6, .off = _OUT(in6.sin6_addr), .cb = snl_attr_get_in6_addr }, +}; +SNL_DECLARE_ATTR_PARSER_EXT(sockaddr_parser, 0, nla_p_sockaddr, pflowctl_post_sockaddr); +#undef _OUT + +struct pflowctl_get { + int id; + int version; + struct pflowctl_sockaddr src; + struct pflowctl_sockaddr dst; + uint32_t obs_dom; + uint8_t so_status; +}; +#define _IN(_field) offsetof(struct genlmsghdr, _field) +#define _OUT(_field) offsetof(struct pflowctl_get, _field) +static struct snl_attr_parser ap_get[] = { + { .type = PFLOWNL_GET_ID, .off = _OUT(id), .cb = snl_attr_get_int32 }, + { .type = PFLOWNL_GET_VERSION, .off = _OUT(version), .cb = snl_attr_get_int16 }, + { .type = PFLOWNL_GET_SRC, .off = _OUT(src), .arg = &sockaddr_parser, .cb = snl_attr_get_nested }, + { .type = PFLOWNL_GET_DST, .off = _OUT(dst), .arg = &sockaddr_parser, .cb = snl_attr_get_nested }, + { .type = PFLOWNL_GET_OBSERVATION_DOMAIN, .off = _OUT(obs_dom), .cb = snl_attr_get_uint32 }, + { .type = PFLOWNL_GET_SOCKET_STATUS, .off = _OUT(so_status), .cb = snl_attr_get_uint8 }, +}; +static struct snl_field_parser fp_get[] = {}; +#undef _IN +#undef _OUT +SNL_DECLARE_PARSER(get_parser, struct genlmsghdr, fp_get, ap_get); + +static void +print_sockaddr(const char *prefix, const struct sockaddr_storage *s) +{ + char buf[INET6_ADDRSTRLEN]; + int error; + + if (s->ss_family != AF_INET && s->ss_family != AF_INET6) + return; + + if (s->ss_family == AF_INET || + s->ss_family == AF_INET6) { + error = getnameinfo((const struct sockaddr *)s, + s->ss_len, buf, sizeof(buf), NULL, 0, + NI_NUMERICHOST); + if (error) + err(1, "sender: %s", gai_strerror(error)); + } + + printf("%s", prefix); + switch (s->ss_family) { + case AF_INET: { + const struct sockaddr_in *sin = (const struct sockaddr_in *)s; + if (sin->sin_addr.s_addr != INADDR_ANY) { + printf("%s", buf); + if (sin->sin_port != 0) + printf(":%u", ntohs(sin->sin_port)); + } + break; + } + case AF_INET6: { + const struct sockaddr_in6 *sin6 = (const struct sockaddr_in6 *)s; + if (!IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr)) { + printf("[%s]", buf); + if (sin6->sin6_port != 0) + printf(":%u", ntohs(sin6->sin6_port)); + } + break; + } + } +} + +static int +get(int id) +{ + struct snl_state ss = {}; + struct snl_errmsg_data e = {}; + struct pflowctl_get g = {}; + struct snl_writer nw; + struct nlmsghdr *hdr; + uint32_t seq_id; + int family_id; + + snl_init(&ss, NETLINK_GENERIC); + family_id = snl_get_genl_family(&ss, PFLOWNL_FAMILY_NAME); + if (family_id == 0) + errx(1, "pflow.ko is not loaded."); + + snl_init_writer(&ss, &nw); + hdr = snl_create_genl_msg_request(&nw, family_id, PFLOWNL_CMD_GET); + snl_add_msg_attr_s32(&nw, PFLOWNL_GET_ID, id); + + hdr = snl_finalize_msg(&nw); + if (hdr == NULL) + return (ENOMEM); + seq_id = hdr->nlmsg_seq; + + snl_send_message(&ss, hdr); + + while ((hdr = snl_read_reply_multi(&ss, seq_id, &e)) != NULL) { + if (! snl_parse_nlmsg(&ss, hdr, &get_parser, &g)) + continue; + + printf("pflow%d: version %d domain %u", g.id, g.version, g.obs_dom); + print_sockaddr(" src ", &g.src.storage); + print_sockaddr(" dst ", &g.dst.storage); + printf("\n"); + if (verbose) { + printf("\tsocket: %s\n", + g.so_status ? "connected" : "disconnected"); + } + } + + if (e.error) + errc(1, e.error, "failed to get"); + + return (0); +} + +struct pflowctl_set { + int id; + uint16_t version; + struct sockaddr_storage src; + struct sockaddr_storage dst; + uint32_t obs_dom; +}; +static inline bool +snl_add_msg_attr_sockaddr(struct snl_writer *nw, int attrtype, struct sockaddr_storage *s) +{ + int off = snl_add_msg_attr_nested(nw, attrtype); + + snl_add_msg_attr_u8(nw, PFLOWNL_ADDR_FAMILY, s->ss_family); + + switch (s->ss_family) { + case AF_INET: { + const struct sockaddr_in *in = (const struct sockaddr_in *)s; + snl_add_msg_attr_u16(nw, PFLOWNL_ADDR_PORT, in->sin_port); + snl_add_msg_attr_ip4(nw, PFLOWNL_ADDR_IP, &in->sin_addr); + break; + } + case AF_INET6: { + const struct sockaddr_in6 *in6 = (const struct sockaddr_in6 *)s; + snl_add_msg_attr_u16(nw, PFLOWNL_ADDR_PORT, in6->sin6_port); + snl_add_msg_attr_ip6(nw, PFLOWNL_ADDR_IP6, &in6->sin6_addr); + break; + } + default: + return (false); + } + snl_end_attr_nested(nw, off); + + return (true); +} + +static int +do_set(struct pflowctl_set *s) +{ + struct snl_state ss = {}; + struct snl_errmsg_data e = {}; + struct snl_writer nw; + struct nlmsghdr *hdr; + int family_id; + + snl_init(&ss, NETLINK_GENERIC); + family_id = snl_get_genl_family(&ss, PFLOWNL_FAMILY_NAME); + if (family_id == 0) + errx(1, "pflow.ko is not loaded."); + + snl_init_writer(&ss, &nw); + snl_create_genl_msg_request(&nw, family_id, PFLOWNL_CMD_SET); + + snl_add_msg_attr_s32(&nw, PFLOWNL_SET_ID, s->id); + if (s->version != 0) + snl_add_msg_attr_u16(&nw, PFLOWNL_SET_VERSION, s->version); + if (s->src.ss_len != 0) + snl_add_msg_attr_sockaddr(&nw, PFLOWNL_SET_SRC, &s->src); + if (s->dst.ss_len != 0) + snl_add_msg_attr_sockaddr(&nw, PFLOWNL_SET_DST, &s->dst); + if (s->obs_dom != 0) + snl_add_msg_attr_u32(&nw, PFLOWNL_SET_OBSERVATION_DOMAIN, s->obs_dom); + + hdr = snl_finalize_msg(&nw); + if (hdr == NULL) + return (1); + + snl_send_message(&ss, hdr); + snl_read_reply_code(&ss, hdr->nlmsg_seq, &e); + + if (e.error) + errc(1, e.error, "failed to set"); + + return (0); +} + +static void +pflowctl_addr(const char *val, struct sockaddr_storage *ss) +{ + struct addrinfo *res0; + int error; + bool flag; + char *ip, *port; + char buf[sysconf(_SC_HOST_NAME_MAX) + 1 + sizeof(":65535")]; + struct addrinfo hints = { + .ai_family = AF_UNSPEC, + .ai_socktype = SOCK_DGRAM, /*dummy*/ + .ai_flags = AI_NUMERICHOST, + }; + + if (strlcpy(buf, val, sizeof(buf)) >= sizeof(buf)) + errx(1, "%s bad value", val); + + port = NULL; + flag = *buf == '['; + + for (char *cp = buf; *cp; ++cp) { + if (*cp == ']' && *(cp + 1) == ':' && flag) { + *cp = '\0'; + *(cp + 1) = '\0'; + port = cp + 2; + break; + } + if (*cp == ']' && *(cp + 1) == '\0' && flag) { + *cp = '\0'; + port = NULL; + break; + } + if (*cp == ':' && !flag) { + *cp = '\0'; + port = cp + 1; + break; + } + } + + ip = buf; + if (flag) + ip++; + + if ((error = getaddrinfo(ip, port, &hints, &res0)) != 0) + errx(1, "error in parsing address string: %s", + gai_strerror(error)); + + memcpy(ss, res0->ai_addr, res0->ai_addr->sa_len); + freeaddrinfo(res0); +} + +static int +set(char *idstr, int argc, char *argv[]) +{ + struct pflowctl_set s = {}; + + s.id = pflow_to_id(idstr); + if (s.id < 0) + return (EINVAL); + + while (argc > 0) { + if (strcmp(argv[0], "src") == 0) { + if (argc < 2) + usage(); + + pflowctl_addr(argv[1], &s.src); + + argc -= 2; + argv += 2; + } else if (strcmp(argv[0], "dst") == 0) { + if (argc < 2) + usage(); + + pflowctl_addr(argv[1], &s.dst); + + argc -= 2; + argv += 2; + } else if (strcmp(argv[0], "proto") == 0) { + if (argc < 2) + usage(); + + s.version = strtol(argv[1], NULL, 10); + + argc -= 2; + argv += 2; + } else if (strcmp(argv[0], "domain") == 0) { + if (argc < 2) + usage(); + + s.obs_dom = strtol(argv[1], NULL, 10); + + argc -= 2; + argv += 2; + } else { + usage(); + } + } + + return (do_set(&s)); +} + +static const struct snl_hdr_parser *all_parsers[] = { + &list_parser, + &get_parser, +}; + +enum pflowctl_op_t { + OP_HELP, + OP_LIST, + OP_CREATE, + OP_DELETE, + OP_SET, +}; +int +main(int argc, char *argv[]) +{ + int ch; + enum pflowctl_op_t op = OP_HELP; + char **set_args = NULL; + size_t set_arg_count = 0; + + SNL_VERIFY_PARSERS(all_parsers); + + if (argc < 2) + usage(); + + while ((ch = getopt(argc, argv, + "lcd:s:v")) != -1) { + switch (ch) { + case 'l': + op = OP_LIST; + break; + case 'c': + op = OP_CREATE; + break; + case 'd': + op = OP_DELETE; + break; + case 's': + op = OP_SET; + set_arg_count = argc - optind; + set_args = argv + optind; + break; + case 'v': + verbose = true; + break; + } + } + + switch (op) { + case OP_LIST: + return (list()); + case OP_CREATE: + return (create()); + case OP_DELETE: + return (del(optarg)); + case OP_SET: + return (set(optarg, set_arg_count, set_args)); + case OP_HELP: + usage(); + break; + } + + return (0); +} |