diff --git a/sbin/Makefile b/sbin/Makefile index 0c648f29badb..342f385f090b 100644 --- a/sbin/Makefile +++ b/sbin/Makefile @@ -81,6 +81,7 @@ SUBDIR.${MK_NVME}+= nvmecontrol SUBDIR.${MK_OPENSSL}+= decryptcore SUBDIR.${MK_PF}+= pfctl SUBDIR.${MK_PF}+= pflogd +SUBDIR.${MK_PF}+= pflowctl SUBDIR.${MK_QUOTAS}+= quotacheck SUBDIR.${MK_ROUTED}+= routed SUBDIR.${MK_VERIEXEC}+= veriexec diff --git a/sbin/pflowctl/Makefile b/sbin/pflowctl/Makefile new file mode 100644 index 000000000000..35cf8cc020f4 --- /dev/null +++ b/sbin/pflowctl/Makefile @@ -0,0 +1,10 @@ + +.include + +PACKAGE=pf +PROG= pflowctl +MAN= pflowctl.8 + +SRCS = pflowctl.c + +.include diff --git a/sbin/pflowctl/pflowctl.8 b/sbin/pflowctl/pflowctl.8 new file mode 100644 index 000000000000..e2e19b7ddfa0 --- /dev/null +++ b/sbin/pflowctl/pflowctl.8 @@ -0,0 +1,91 @@ +.\" $OpenBSD: pflow.4,v 1.19 2014/03/29 11:26:03 florian Exp $ +.\" +.\" Copyright (c) 2008 Henning Brauer +.\" Copyright (c) 2008 Joerg Goltermann +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.Dd $Mdocdate: January 08 2024 $ +.Dt PFLOWCTL 8 +.Os +.Sh NAME +.Nm pflowctl +.Nd control pflow data export +.Sh SYNOPSIS +.Nm pflowctl +.Bk -words +.Op Fl lc +.Op Fl d Ar id +.Op Fl s Ar id ... +.Ek +.Sh DESCRIPTION +The +.Nm +utility creates, configures and deletes netflow accounting data export using the +.Xr pflow 4 +subsystem. +.Pp +The +.Nm +utility provides several commands. +The options are as follows: +.Bl -tag -width Ds +.It Fl c +Create a new +.Xr pflow 4 +exporter. +.It Fl d Ar id +Remove an existing +.Xr pflow 4 +exporter. +The +.Ar id +may be either numeric or the full pflowX name. +.It Fl l +List all existing +.Xr pflow 4 +exporters. +.It Fl s Ar id ... +Configure an existing +.Xr pflow 4 +exporter. +This takes the following keywords: +.Pp +.Bl -tag -width xxxxxxxxxxxx -compact +.It Cm src +set the source IP address (and optionally port). +.It Cm dst +set the destination IP address (and optionally port). +.It Cm proto +set the protocol version. +Valid values are 5 and 10. +.El +.Pp +Multiple keywords may be passed in the same command invocation. +.Pp +For example, the following command sets 10.0.0.1 as the source +and 10.0.0.2:1234 as destination: +.Bd -literal -offset indent +# pflowctl -s pflow0 src 10.0.0.1 dst 10.0.0.2:1234 +.Ed +.Sh SEE ALSO +.Xr netintro 4 , +.Xr pf 4 , +.Xr pflow 4 , +.Xr udp 4 , +.Xr pf.conf 5 +.Sh HISTORY +The +.Nm +command first appeared in +.Fx 15.0 . diff --git a/sbin/pflowctl/pflowctl.c b/sbin/pflowctl/pflowctl.c new file mode 100644 index 000000000000..046919867ff2 --- /dev/null +++ b/sbin/pflowctl/pflowctl.c @@ -0,0 +1,548 @@ +/*- + * 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 + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +static int get(int id); + +static void +usage(void) +{ + extern char *__progname; + + fprintf(stderr, +"usage: %s [-la] [-d id]\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; +}; +#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 }, +}; +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", g.id, g.version); + print_sockaddr(" src ", &g.src.storage); + print_sockaddr(" dst ", &g.dst.storage); + printf("\n"); + } + + 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; +}; +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); + + 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 { + usage(); + } + } + + return (do_set(&s)); +} + +static const struct snl_hdr_parser *all_parsers[] = { + &list_parser, + &get_parser, +}; + +int +main(int argc, char *argv[]) +{ + int ch; + + SNL_VERIFY_PARSERS(all_parsers); + + if (argc < 2) + usage(); + + while ((ch = getopt(argc, argv, + "lcd:s:")) != -1) { + switch (ch) { + case 'l': + return (list()); + case 'c': + return (create()); + case 'd': + return (del(optarg)); + case 's': + return (set(optarg, argc - optind, argv + optind)); + } + } + + return (0); +} diff --git a/share/man/man4/Makefile b/share/man/man4/Makefile index 978ec6887f85..ab951b107f27 100644 --- a/share/man/man4/Makefile +++ b/share/man/man4/Makefile @@ -433,6 +433,7 @@ MAN= aac.4 \ pcm.4 \ ${_pf.4} \ ${_pflog.4} \ + ${_pflow.4} \ ${_pfsync.4} \ pim.4 \ pms.4 \ @@ -968,6 +969,7 @@ _atf_test_case.4= atf-test-case.4 .if ${MK_PF} != "no" _pf.4= pf.4 _pflog.4= pflog.4 +_pflow.4= pflow.4 _pfsync.4= pfsync.4 .endif diff --git a/share/man/man4/pflow.4 b/share/man/man4/pflow.4 new file mode 100644 index 000000000000..320a7527dc2d --- /dev/null +++ b/share/man/man4/pflow.4 @@ -0,0 +1,123 @@ +.\" $OpenBSD: pflow.4,v 1.19 2014/03/29 11:26:03 florian Exp $ +.\" +.\" Copyright (c) 2008 Henning Brauer +.\" Copyright (c) 2008 Joerg Goltermann +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.Dd $Mdocdate: January 08 2024 $ +.Dt PFLOW 4 +.Os +.Sh NAME +.Nm pflow +.Nd kernel interface for pflow data export +.Sh SYNOPSIS +.Cd "pseudo-device pflow" +.Sh DESCRIPTION +The +.Nm +subsystem exports +.Nm +accounting data from the kernel using +.Xr udp 4 +packets. +.Nm +is compatible with netflow version 5 and IPFIX (10). +The data is extracted from the +.Xr pf 4 +state table. +.Pp +Multiple +.Nm +interfaces can be created at runtime using the +.Ic pflowctl Ns Ar N Ic -c +command. +Each interface must be configured with a flow receiver IP address +and a flow receiver port number. +.Pp +Only states created by a rule marked with the +.Ar pflow +keyword are exported by +.Nm . +.Pp +.Nm +will attempt to export multiple +.Nm +records in one +UDP packet, but will not hold a record for longer than 30 seconds. +.Pp +Each packet seen on this interface has one header and a variable number of +flows. +The header indicates the version of the protocol, number of +flows in the packet, a unique sequence number, system time, and an engine +ID and type. +Header and flow structs are defined in +.In net/pflow.h . +.Pp +The +.Nm +source and destination addresses are controlled by +.Xr pflowctl 8 . +.Cm src +is the sender IP address of the UDP packet which can be used +to identify the source of the data on the +.Nm +collector. +.Cm dst +defines the collector IP address and the port. +The +.Cm dst +IP address and port must be defined to enable the export of flows. +.Pp +For example, the following command sets 10.0.0.1 as the source +and 10.0.0.2:1234 as destination: +.Bd -literal -offset indent +# pflowctl -s pflow0 src 10.0.0.1 dst 10.0.0.2:1234 +.Ed +.Pp +The protocol is set to IPFIX with the following command: +.Bd -literal -offset indent +# pflowctl -s pflow0 proto 10 +.Ed +.Sh SEE ALSO +.Xr netintro 4 , +.Xr pf 4 , +.Xr udp 4 , +.Xr pf.conf 5 , +.Xr pflowctl 8 , +.Xr tcpdump 8 +.Sh STANDARDS +.Rs +.%A B. Claise +.%D January 2008 +.%R RFC 5101 +.%T "Specification of the IP Flow Information Export (IPFIX) Protocol for the Exchange of IP Traffic Flow Information" +.Re +.Sh HISTORY +The +.Nm +device first appeared in +.Ox 4.5 +and was imported into +FreeBSD 15.0 . +.Sh BUGS +A state created by +.Xr pfsync 4 +can have a creation or expiration time before the machine came up. +In this case, +.Nm +pretends such flows were created or expired when the machine came up. +.Pp +The IPFIX implementation is incomplete: +The required transport protocol SCTP is not supported. +Transport over TCP and DTLS protected flow export is also not supported. diff --git a/sys/conf/files b/sys/conf/files index 484ec90beb00..9f0b3cf3831a 100644 --- a/sys/conf/files +++ b/sys/conf/files @@ -4511,6 +4511,7 @@ netpfil/pf/pf_osfp.c optional pf inet netpfil/pf/pf_ruleset.c optional pf inet netpfil/pf/pf_syncookies.c optional pf inet netpfil/pf/pf_table.c optional pf inet +netpfil/pf/pflow.c optional pflow pf inet netpfil/pf/pfsync_nv.c optional pfsync pf inet netpfil/pf/in4_cksum.c optional pf inet netsmb/smb_conn.c optional netsmb diff --git a/sys/modules/Makefile b/sys/modules/Makefile index c14933eebda4..606ab4cb0536 100644 --- a/sys/modules/Makefile +++ b/sys/modules/Makefile @@ -306,6 +306,7 @@ SUBDIR= \ ${_pcfclock} \ ${_pf} \ ${_pflog} \ + ${_pflow} \ ${_pfsync} \ plip \ ${_pms} \ @@ -611,6 +612,7 @@ _netgraph= netgraph ${MK_INET6_SUPPORT} != "no")) || defined(ALL_MODULES) _pf= pf _pflog= pflog +_pflow= pflow .if ${MK_INET_SUPPORT} != "no" _pfsync= pfsync .endif diff --git a/sys/modules/pflow/Makefile b/sys/modules/pflow/Makefile new file mode 100644 index 000000000000..674ca8970607 --- /dev/null +++ b/sys/modules/pflow/Makefile @@ -0,0 +1,16 @@ +.PATH: ${SRCTOP}/sys/netpfil/pf + +KMOD= pflow +SRCS= pflow.c \ + opt_pf.h opt_inet.h opt_inet6.h opt_global.h +SRCS+= bus_if.h device_if.h + +.if !defined(KERNBUILDDIR) +.if defined(VIMAGE) +opt_global.h: + echo "#define VIMAGE 1" >> ${.TARGET} +CFLAGS+= -include opt_global.h +.endif +.endif + +.include diff --git a/sys/net/pflow.h b/sys/net/pflow.h new file mode 100644 index 000000000000..fcf24e091b57 --- /dev/null +++ b/sys/net/pflow.h @@ -0,0 +1,333 @@ +/* $OpenBSD: if_pflow.h,v 1.19 2022/11/23 15:12:27 mvs Exp $ */ + +/* + * Copyright (c) 2008 Henning Brauer + * Copyright (c) 2008 Joerg Goltermann + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER IN + * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT + * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _NET_IF_PFLOW_H_ +#define _NET_IF_PFLOW_H_ + +#include +#include +#include + +#include + +#ifdef _KERNEL +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#endif + +#define PFLOW_ID_LEN sizeof(u_int64_t) + +#define PFLOW_MAXFLOWS 30 +#define PFLOW_ENGINE_TYPE 42 +#define PFLOW_ENGINE_ID 42 +#define PFLOW_MAXBYTES 0xffffffff +#define PFLOW_TIMEOUT 30 +#define PFLOW_TMPL_TIMEOUT 30 /* rfc 5101 10.3.6 (p.40) recommends 600 */ + +#define PFLOW_IPFIX_TMPL_SET_ID 2 + +/* RFC 5102 Information Element Identifiers */ + +#define PFIX_IE_octetDeltaCount 1 +#define PFIX_IE_packetDeltaCount 2 +#define PFIX_IE_protocolIdentifier 4 +#define PFIX_IE_ipClassOfService 5 +#define PFIX_IE_sourceTransportPort 7 +#define PFIX_IE_sourceIPv4Address 8 +#define PFIX_IE_ingressInterface 10 +#define PFIX_IE_destinationTransportPort 11 +#define PFIX_IE_destinationIPv4Address 12 +#define PFIX_IE_egressInterface 14 +#define PFIX_IE_flowEndSysUpTime 21 +#define PFIX_IE_flowStartSysUpTime 22 +#define PFIX_IE_sourceIPv6Address 27 +#define PFIX_IE_destinationIPv6Address 28 +#define PFIX_IE_flowStartMilliseconds 152 +#define PFIX_IE_flowEndMilliseconds 153 + +struct pflow_flow { + u_int32_t src_ip; + u_int32_t dest_ip; + u_int32_t nexthop_ip; + u_int16_t if_index_in; + u_int16_t if_index_out; + u_int32_t flow_packets; + u_int32_t flow_octets; + u_int32_t flow_start; + u_int32_t flow_finish; + u_int16_t src_port; + u_int16_t dest_port; + u_int8_t pad1; + u_int8_t tcp_flags; + u_int8_t protocol; + u_int8_t tos; + u_int16_t src_as; + u_int16_t dest_as; + u_int8_t src_mask; + u_int8_t dest_mask; + u_int16_t pad2; +} __packed; + +struct pflow_set_header { + u_int16_t set_id; + u_int16_t set_length; /* total length of the set, + in octets, including the set header */ +} __packed; + +#define PFLOW_SET_HDRLEN sizeof(struct pflow_set_header) + +struct pflow_tmpl_hdr { + u_int16_t tmpl_id; + u_int16_t field_count; +} __packed; + +struct pflow_tmpl_fspec { + u_int16_t field_id; + u_int16_t len; +} __packed; + +/* update pflow_clone_create() when changing pflow_ipfix_tmpl_ipv4 */ +struct pflow_ipfix_tmpl_ipv4 { + struct pflow_tmpl_hdr h; + struct pflow_tmpl_fspec src_ip; + struct pflow_tmpl_fspec dest_ip; + struct pflow_tmpl_fspec if_index_in; + struct pflow_tmpl_fspec if_index_out; + struct pflow_tmpl_fspec packets; + struct pflow_tmpl_fspec octets; + struct pflow_tmpl_fspec start; + struct pflow_tmpl_fspec finish; + struct pflow_tmpl_fspec src_port; + struct pflow_tmpl_fspec dest_port; + struct pflow_tmpl_fspec tos; + struct pflow_tmpl_fspec protocol; +#define PFLOW_IPFIX_TMPL_IPV4_FIELD_COUNT 12 +#define PFLOW_IPFIX_TMPL_IPV4_ID 256 +} __packed; + +/* update pflow_clone_create() when changing pflow_ipfix_tmpl_v6 */ +struct pflow_ipfix_tmpl_ipv6 { + struct pflow_tmpl_hdr h; + struct pflow_tmpl_fspec src_ip; + struct pflow_tmpl_fspec dest_ip; + struct pflow_tmpl_fspec if_index_in; + struct pflow_tmpl_fspec if_index_out; + struct pflow_tmpl_fspec packets; + struct pflow_tmpl_fspec octets; + struct pflow_tmpl_fspec start; + struct pflow_tmpl_fspec finish; + struct pflow_tmpl_fspec src_port; + struct pflow_tmpl_fspec dest_port; + struct pflow_tmpl_fspec tos; + struct pflow_tmpl_fspec protocol; +#define PFLOW_IPFIX_TMPL_IPV6_FIELD_COUNT 12 +#define PFLOW_IPFIX_TMPL_IPV6_ID 257 +} __packed; + +struct pflow_ipfix_tmpl { + struct pflow_set_header set_header; + struct pflow_ipfix_tmpl_ipv4 ipv4_tmpl; + struct pflow_ipfix_tmpl_ipv6 ipv6_tmpl; +} __packed; + +struct pflow_ipfix_flow4 { + u_int32_t src_ip; /* sourceIPv4Address*/ + u_int32_t dest_ip; /* destinationIPv4Address */ + u_int32_t if_index_in; /* ingressInterface */ + u_int32_t if_index_out; /* egressInterface */ + u_int64_t flow_packets; /* packetDeltaCount */ + u_int64_t flow_octets; /* octetDeltaCount */ + int64_t flow_start; /* flowStartMilliseconds */ + int64_t flow_finish; /* flowEndMilliseconds */ + u_int16_t src_port; /* sourceTransportPort */ + u_int16_t dest_port; /* destinationTransportPort */ + u_int8_t tos; /* ipClassOfService */ + u_int8_t protocol; /* protocolIdentifier */ + /* XXX padding needed? */ +} __packed; + +struct pflow_ipfix_flow6 { + struct in6_addr src_ip; /* sourceIPv6Address */ + struct in6_addr dest_ip; /* destinationIPv6Address */ + u_int32_t if_index_in; /* ingressInterface */ + u_int32_t if_index_out; /* egressInterface */ + u_int64_t flow_packets; /* packetDeltaCount */ + u_int64_t flow_octets; /* octetDeltaCount */ + int64_t flow_start; /* flowStartMilliseconds */ + int64_t flow_finish; /* flowEndMilliseconds */ + u_int16_t src_port; /* sourceTransportPort */ + u_int16_t dest_port; /* destinationTransportPort */ + u_int8_t tos; /* ipClassOfService */ + u_int8_t protocol; /* protocolIdentifier */ + /* XXX padding needed? */ +} __packed; + +#ifdef _KERNEL + +struct pflow_softc { + int sc_id; + + struct mtx sc_lock; + + int sc_dying; /* [N] */ + struct vnet *sc_vnet; + + unsigned int sc_count; + unsigned int sc_count4; + unsigned int sc_count6; + unsigned int sc_maxcount; + unsigned int sc_maxcount4; + unsigned int sc_maxcount6; + u_int64_t sc_gcounter; + u_int32_t sc_sequence; + struct callout sc_tmo; + struct callout sc_tmo6; + struct callout sc_tmo_tmpl; + struct intr_event *sc_swi_ie; + void *sc_swi_cookie; + struct mbufq sc_outputqueue; + struct task sc_outputtask; + struct socket *so; /* [p] */ + struct sockaddr *sc_flowsrc; + struct sockaddr *sc_flowdst; + struct pflow_ipfix_tmpl sc_tmpl_ipfix; + u_int8_t sc_version; + struct mbuf *sc_mbuf; /* current cumulative mbuf */ + struct mbuf *sc_mbuf6; /* current cumulative mbuf */ + CK_LIST_ENTRY(pflow_softc) sc_next; + struct epoch_context sc_epoch_ctx; +}; + +#endif /* _KERNEL */ + +struct pflow_header { + u_int16_t version; + u_int16_t count; + u_int32_t uptime_ms; + u_int32_t time_sec; + u_int32_t time_nanosec; + u_int32_t flow_sequence; + u_int8_t engine_type; + u_int8_t engine_id; + u_int8_t reserved1; + u_int8_t reserved2; +} __packed; + +#define PFLOW_HDRLEN sizeof(struct pflow_header) + +struct pflow_v10_header { + u_int16_t version; + u_int16_t length; + u_int32_t time_sec; + u_int32_t flow_sequence; + u_int32_t observation_dom; +} __packed; + +#define PFLOW_IPFIX_HDRLEN sizeof(struct pflow_v10_header) + +struct pflowstats { + u_int64_t pflow_flows; + u_int64_t pflow_packets; + u_int64_t pflow_onomem; + u_int64_t pflow_oerrors; +}; + +/* Supported flow protocols */ +#define PFLOW_PROTO_5 5 /* original pflow */ +#define PFLOW_PROTO_10 10 /* ipfix */ +#define PFLOW_PROTO_MAX 11 + +#define PFLOW_PROTO_DEFAULT PFLOW_PROTO_5 + +struct pflow_protos { + const char *ppr_name; + u_int8_t ppr_proto; +}; + +#define PFLOW_PROTOS { \ + { "5", PFLOW_PROTO_5 }, \ + { "10", PFLOW_PROTO_10 }, \ +} + +#define PFLOWNL_FAMILY_NAME "pflow" + +enum { + PFLOWNL_CMD_UNSPEC = 0, + PFLOWNL_CMD_LIST = 1, + PFLOWNL_CMD_CREATE = 2, + PFLOWNL_CMD_DEL = 3, + PFLOWNL_CMD_SET = 4, + PFLOWNL_CMD_GET = 5, + __PFLOWNL_CMD_MAX, +}; +#define PFLOWNL_CMD_MAX (__PFLOWNL_CMD_MAX - 1) + +enum pflow_list_type_t { + PFLOWNL_L_UNSPEC, + PFLOWNL_L_ID = 1, /* u32 */ +}; + +enum pflow_create_type_t { + PFLOWNL_CREATE_UNSPEC, + PFLOWNL_CREATE_ID = 1, /* u32 */ +}; + +enum pflow_del_type_t { + PFLOWNL_DEL_UNSPEC, + PFLOWNL_DEL_ID = 1, /* u32 */ +}; + +enum pflow_addr_type_t { + PFLOWNL_ADDR_UNSPEC, + PFLOWNL_ADDR_FAMILY = 1, /* u8 */ + PFLOWNL_ADDR_PORT = 2, /* u16 */ + PFLOWNL_ADDR_IP = 3, /* struct in_addr */ + PFLOWNL_ADDR_IP6 = 4, /* struct in6_addr */ +}; + +enum pflow_get_type_t { + PFLOWNL_GET_UNSPEC, + PFLOWNL_GET_ID = 1, /* u32 */ + PFLOWNL_GET_VERSION = 2, /* u16 */ + PFLOWNL_GET_SRC = 3, /* struct sockaddr_storage */ + PFLOWNL_GET_DST = 4, /* struct sockaddr_storage */ +}; + +enum pflow_set_type_t { + PFLOWNL_SET_UNSPEC, + PFLOWNL_SET_ID = 1, /* u32 */ + PFLOWNL_SET_VERSION = 2, /* u16 */ + PFLOWNL_SET_SRC = 3, /* struct sockaddr_storage */ + PFLOWNL_SET_DST = 4, /* struct sockaddr_storage */ +}; + +#ifdef _KERNEL +int export_pflow(struct pf_kstate *); +int pflow_sysctl(int *, u_int, void *, size_t *, void *, size_t); +#endif /* _KERNEL */ + +#endif /* _NET_IF_PFLOW_H_ */ diff --git a/sys/net/pfvar.h b/sys/net/pfvar.h index 020b79ded94c..d6852244ce9b 100644 --- a/sys/net/pfvar.h +++ b/sys/net/pfvar.h @@ -1067,12 +1067,14 @@ struct pf_kstate { struct pf_rule_actions act; u_int16_t tag; u_int8_t rt; + u_int16_t if_index_in; + u_int16_t if_index_out; }; /* * Size <= fits 11 objects per page on LP64. Try to not grow the struct beyond that. */ -_Static_assert(sizeof(struct pf_kstate) <= 368, "pf_kstate size crosses 368 bytes"); +_Static_assert(sizeof(struct pf_kstate) <= 372, "pf_kstate size crosses 372 bytes"); #endif /* diff --git a/sys/netlink/netlink_message_parser.h b/sys/netlink/netlink_message_parser.h index c682973d3e33..7a417c00fc46 100644 --- a/sys/netlink/netlink_message_parser.h +++ b/sys/netlink/netlink_message_parser.h @@ -150,12 +150,16 @@ static const struct nlhdr_parser _name = { \ .np_size = NL_ARRAY_LEN(_np), \ } -#define NL_DECLARE_ATTR_PARSER(_name, _np) \ +#define NL_DECLARE_ATTR_PARSER_EXT(_name, _np, _pp) \ static const struct nlhdr_parser _name = { \ .np = &((_np)[0]), \ .np_size = NL_ARRAY_LEN(_np), \ + .post_parse = (_pp) \ } +#define NL_DECLARE_ATTR_PARSER(_name, _np) \ + NL_DECLARE_ATTR_PARSER_EXT(_name, _np, NULL) + #define NL_ATTR_BMASK_SIZE 128 BITSET_DEFINE(nlattr_bmask, NL_ATTR_BMASK_SIZE); diff --git a/sys/netpfil/pf/pf.c b/sys/netpfil/pf/pf.c index 9489da0b3e53..df93cc1bebc3 100644 --- a/sys/netpfil/pf/pf.c +++ b/sys/netpfil/pf/pf.c @@ -1226,6 +1226,7 @@ pf_state_key_attach(struct pf_state_key *skw, struct pf_state_key *sks, struct pf_kstate *si, *olds = NULL; int idx; + NET_EPOCH_ASSERT(); KASSERT(s->refs == 0, ("%s: state not pristine", __func__)); KASSERT(s->key[PF_SK_WIRE] == NULL, ("%s: state has key", __func__)); KASSERT(s->key[PF_SK_STACK] == NULL, ("%s: state has key", __func__)); @@ -1392,6 +1393,8 @@ pf_detach_state(struct pf_kstate *s) struct pf_state_key *sks = s->key[PF_SK_STACK]; struct pf_keyhash *kh; + NET_EPOCH_ASSERT(); + pf_sctp_multihome_detach_addr(s); if (sks != NULL) { @@ -1491,6 +1494,8 @@ pf_state_insert(struct pfi_kkif *kif, struct pfi_kkif *orig_kif, struct pf_kstate *cur; int error; + NET_EPOCH_ASSERT(); + KASSERT(TAILQ_EMPTY(&sks->states[0]) && TAILQ_EMPTY(&sks->states[1]), ("%s: sks not pristine", __func__)); KASSERT(TAILQ_EMPTY(&skw->states[0]) && TAILQ_EMPTY(&skw->states[1]), @@ -1915,6 +1920,8 @@ pf_counter_u64_periodic_main(void) void pf_purge_thread(void *unused __unused) { + struct epoch_tracker et; + VNET_ITERATOR_DECL(vnet_iter); sx_xlock(&pf_end_lock); @@ -1922,6 +1929,7 @@ pf_purge_thread(void *unused __unused) sx_sleep(pf_purge_thread, &pf_end_lock, 0, "pftm", pf_purge_thread_period); VNET_LIST_RLOCK(); + NET_EPOCH_ENTER(et); VNET_FOREACH(vnet_iter) { CURVNET_SET(vnet_iter); @@ -1958,6 +1966,7 @@ pf_purge_thread(void *unused __unused) } CURVNET_RESTORE(); } + NET_EPOCH_EXIT(et); VNET_LIST_RUNLOCK(); } @@ -2097,6 +2106,7 @@ pf_unlink_state(struct pf_kstate *s) { struct pf_idhash *ih = &V_pf_idhash[PF_IDHASH(s)]; + NET_EPOCH_ASSERT(); PF_HASHROW_ASSERT(ih); if (s->timeout == PFTM_UNLINKED) { @@ -8434,6 +8444,13 @@ done: SDT_PROBE4(pf, ip, test, done, action, reason, r, s); + if (s && action != PF_DROP) { + if (!s->if_index_in && dir == PF_IN) + s->if_index_in = ifp->if_index; + else if (!s->if_index_out && dir == PF_OUT) + s->if_index_out = ifp->if_index; + } + if (s) PF_STATE_UNLOCK(s); @@ -8986,6 +9003,13 @@ done: break; } + if (s && action != PF_DROP) { + if (!s->if_index_in && dir == PF_IN) + s->if_index_in = ifp->if_index; + else if (!s->if_index_out && dir == PF_OUT) + s->if_index_out = ifp->if_index; + } + if (s) PF_STATE_UNLOCK(s); diff --git a/sys/netpfil/pf/pf_ioctl.c b/sys/netpfil/pf/pf_ioctl.c index 007327b1932d..e09b7b71920e 100644 --- a/sys/netpfil/pf/pf_ioctl.c +++ b/sys/netpfil/pf/pf_ioctl.c @@ -5784,9 +5784,11 @@ done: static void pf_clear_all_states(void) { + struct epoch_tracker et; struct pf_kstate *s; u_int i; + NET_EPOCH_ENTER(et); for (i = 0; i <= pf_hashmask; i++) { struct pf_idhash *ih = &V_pf_idhash[i]; relock: @@ -5800,6 +5802,7 @@ relock: } PF_HASHROW_UNLOCK(ih); } + NET_EPOCH_EXIT(et); } static int @@ -5943,6 +5946,8 @@ pf_clear_states(const struct pf_kstate_kill *kill) int idx; unsigned int killed = 0, dir; + NET_EPOCH_ASSERT(); + for (unsigned int i = 0; i <= pf_hashmask; i++) { struct pf_idhash *ih = &V_pf_idhash[i]; @@ -6006,6 +6011,7 @@ pf_killstates(struct pf_kstate_kill *kill, unsigned int *killed) { struct pf_kstate *s; + NET_EPOCH_ASSERT(); if (kill->psk_pfcmp.id) { if (kill->psk_pfcmp.creatorid == 0) kill->psk_pfcmp.creatorid = V_pf_status.hostid; @@ -6019,14 +6025,13 @@ pf_killstates(struct pf_kstate_kill *kill, unsigned int *killed) for (unsigned int i = 0; i <= pf_hashmask; i++) *killed += pf_killstates_row(kill, &V_pf_idhash[i]); - - return; } static int pf_killstates_nv(struct pfioc_nv *nv) { struct pf_kstate_kill kill; + struct epoch_tracker et; nvlist_t *nvl = NULL; void *nvlpacked = NULL; int error = 0; @@ -6053,7 +6058,9 @@ pf_killstates_nv(struct pfioc_nv *nv) if (error) ERROUT(error); + NET_EPOCH_ENTER(et); pf_killstates(&kill, &killed); + NET_EPOCH_EXIT(et); free(nvlpacked, M_NVLIST); nvlpacked = NULL; @@ -6085,6 +6092,7 @@ static int pf_clearstates_nv(struct pfioc_nv *nv) { struct pf_kstate_kill kill; + struct epoch_tracker et; nvlist_t *nvl = NULL; void *nvlpacked = NULL; int error = 0; @@ -6111,7 +6119,9 @@ pf_clearstates_nv(struct pfioc_nv *nv) if (error) ERROUT(error); + NET_EPOCH_ENTER(et); killed = pf_clear_states(&kill); + NET_EPOCH_EXIT(et); free(nvlpacked, M_NVLIST); nvlpacked = NULL; diff --git a/sys/netpfil/pf/pflow.c b/sys/netpfil/pf/pflow.c new file mode 100644 index 000000000000..855ebe5ca98c --- /dev/null +++ b/sys/netpfil/pf/pflow.c @@ -0,0 +1,1578 @@ +/* $OpenBSD: if_pflow.c,v 1.100 2023/11/09 08:53:20 mvs Exp $ */ + +/* + * Copyright (c) 2023 Rubicon Communications, LLC (Netgate) + * Copyright (c) 2011 Florian Obser + * Copyright (c) 2011 Sebastian Benoit + * Copyright (c) 2008 Henning Brauer + * Copyright (c) 2008 Joerg Goltermann + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER IN + * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT + * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include "net/if_var.h" + +#define PFLOW_MINMTU \ + (sizeof(struct pflow_header) + sizeof(struct pflow_flow)) + +#ifdef PFLOWDEBUG +#define DPRINTF(x) do { printf x ; } while (0) +#else +#define DPRINTF(x) +#endif + +static void pflow_output_process(void *); +static int pflow_create(int); +static int pflow_destroy(int, bool); +static int pflow_calc_mtu(struct pflow_softc *, int, int); +static void pflow_setmtu(struct pflow_softc *, int); +static int pflowvalidsockaddr(const struct sockaddr *, int); + +static struct mbuf *pflow_get_mbuf(struct pflow_softc *, u_int16_t); +static void pflow_flush(struct pflow_softc *); +static int pflow_sendout_v5(struct pflow_softc *); +static int pflow_sendout_ipfix(struct pflow_softc *, sa_family_t); +static int pflow_sendout_ipfix_tmpl(struct pflow_softc *); +static int pflow_sendout_mbuf(struct pflow_softc *, struct mbuf *); +static void pflow_timeout(void *); +static void pflow_timeout6(void *); +static void pflow_timeout_tmpl(void *); +static void copy_flow_data(struct pflow_flow *, struct pflow_flow *, + struct pf_kstate *, struct pf_state_key *, int, int); +static void copy_flow_ipfix_4_data(struct pflow_ipfix_flow4 *, + struct pflow_ipfix_flow4 *, struct pf_kstate *, struct pf_state_key *, + struct pflow_softc *, int, int); +static void copy_flow_ipfix_6_data(struct pflow_ipfix_flow6 *, + struct pflow_ipfix_flow6 *, struct pf_kstate *, struct pf_state_key *, + struct pflow_softc *, int, int); +static int pflow_pack_flow(struct pf_kstate *, struct pf_state_key *, + struct pflow_softc *); +static int pflow_pack_flow_ipfix(struct pf_kstate *, struct pf_state_key *, + struct pflow_softc *); +static int export_pflow_if(struct pf_kstate*, struct pf_state_key *, + struct pflow_softc *); +static int copy_flow_to_m(struct pflow_flow *flow, struct pflow_softc *sc); +static int copy_flow_ipfix_4_to_m(struct pflow_ipfix_flow4 *flow, + struct pflow_softc *sc); +static int copy_flow_ipfix_6_to_m(struct pflow_ipfix_flow6 *flow, + struct pflow_softc *sc); + +static const char pflowname[] = "pflow"; + +/** + * Locking concept + * + * The list of pflow devices (V_pflowif_list) is managed through epoch. + * It is safe to read the list without locking (while in NET_EPOCH). + * There may only be one simultaneous modifier, hence we need V_pflow_list_mtx + * on every add/delete. + * + * Each pflow interface protects its own data with the sc_lock mutex. + * + * We do not require any pf locks, and in fact expect to be called without + * hashrow locks held. + **/ + +VNET_DEFINE(struct unrhdr *, pflow_unr); +#define V_pflow_unr VNET(pflow_unr) +VNET_DEFINE(CK_LIST_HEAD(, pflow_softc), pflowif_list); +#define V_pflowif_list VNET(pflowif_list) +VNET_DEFINE(struct mtx, pflowif_list_mtx); +#define V_pflowif_list_mtx VNET(pflowif_list_mtx) +VNET_DEFINE(struct pflowstats, pflowstats); +#define V_pflowstats VNET(pflowstats) + +#define PFLOW_LOCK(_sc) mtx_lock(&(_sc)->sc_lock) +#define PFLOW_UNLOCK(_sc) mtx_unlock(&(_sc)->sc_lock) +#define PFLOW_ASSERT(_sc) mtx_assert(&(_sc)->sc_lock, MA_OWNED) + +static void +vnet_pflowattach(void) +{ + CK_LIST_INIT(&V_pflowif_list); + mtx_init(&V_pflowif_list_mtx, "pflow interface list mtx", NULL, MTX_DEF); + + V_pflow_unr = new_unrhdr(0, INT_MAX, &V_pflowif_list_mtx); +} +VNET_SYSINIT(vnet_pflowattach, SI_SUB_PROTO_FIREWALL, SI_ORDER_ANY, + vnet_pflowattach, NULL); + +static void +vnet_pflowdetach(void) +{ + struct pflow_softc *sc; + + CK_LIST_FOREACH(sc, &V_pflowif_list, sc_next) { + pflow_destroy(sc->sc_id, false); + } + + MPASS(CK_LIST_EMPTY(&V_pflowif_list)); + delete_unrhdr(V_pflow_unr); + mtx_destroy(&V_pflowif_list_mtx); +} +VNET_SYSUNINIT(vnet_pflowdetach, SI_SUB_PROTO_FIREWALL, SI_ORDER_FOURTH, + vnet_pflowdetach, NULL); + +static void +vnet_pflow_finalise(void) +{ + /* + * Ensure we've freed all interfaces, and do not have pending + * epoch cleanup calls. + */ + NET_EPOCH_DRAIN_CALLBACKS(); +} +VNET_SYSUNINIT(vnet_pflow_finalise, SI_SUB_PROTO_FIREWALL, SI_ORDER_THIRD, + vnet_pflow_finalise, NULL); + +static void +pflow_output_process(void *arg) +{ + struct mbufq ml; + struct pflow_softc *sc = arg; + struct mbuf *m; + + mbufq_init(&ml, 0); + + PFLOW_LOCK(sc); + mbufq_concat(&ml, &sc->sc_outputqueue); + PFLOW_UNLOCK(sc); + + CURVNET_SET(sc->sc_vnet); + while ((m = mbufq_dequeue(&ml)) != NULL) { + pflow_sendout_mbuf(sc, m); + } + CURVNET_RESTORE(); +} + +static int +pflow_create(int unit) +{ + struct pflow_softc *pflowif; + int error; + + pflowif = malloc(sizeof(*pflowif), M_DEVBUF, M_WAITOK|M_ZERO); + mtx_init(&pflowif->sc_lock, "pflowlk", NULL, MTX_DEF); + pflowif->sc_version = PFLOW_PROTO_DEFAULT; + + /* ipfix template init */ + bzero(&pflowif->sc_tmpl_ipfix,sizeof(pflowif->sc_tmpl_ipfix)); + pflowif->sc_tmpl_ipfix.set_header.set_id = + htons(PFLOW_IPFIX_TMPL_SET_ID); + pflowif->sc_tmpl_ipfix.set_header.set_length = + htons(sizeof(struct pflow_ipfix_tmpl)); + + /* ipfix IPv4 template */ + pflowif->sc_tmpl_ipfix.ipv4_tmpl.h.tmpl_id = + htons(PFLOW_IPFIX_TMPL_IPV4_ID); + pflowif->sc_tmpl_ipfix.ipv4_tmpl.h.field_count + = htons(PFLOW_IPFIX_TMPL_IPV4_FIELD_COUNT); + pflowif->sc_tmpl_ipfix.ipv4_tmpl.src_ip.field_id = + htons(PFIX_IE_sourceIPv4Address); + pflowif->sc_tmpl_ipfix.ipv4_tmpl.src_ip.len = htons(4); + pflowif->sc_tmpl_ipfix.ipv4_tmpl.dest_ip.field_id = + htons(PFIX_IE_destinationIPv4Address); + pflowif->sc_tmpl_ipfix.ipv4_tmpl.dest_ip.len = htons(4); + pflowif->sc_tmpl_ipfix.ipv4_tmpl.if_index_in.field_id = + htons(PFIX_IE_ingressInterface); + pflowif->sc_tmpl_ipfix.ipv4_tmpl.if_index_in.len = htons(4); + pflowif->sc_tmpl_ipfix.ipv4_tmpl.if_index_out.field_id = + htons(PFIX_IE_egressInterface); + pflowif->sc_tmpl_ipfix.ipv4_tmpl.if_index_out.len = htons(4); + pflowif->sc_tmpl_ipfix.ipv4_tmpl.packets.field_id = + htons(PFIX_IE_packetDeltaCount); + pflowif->sc_tmpl_ipfix.ipv4_tmpl.packets.len = htons(8); + pflowif->sc_tmpl_ipfix.ipv4_tmpl.octets.field_id = + htons(PFIX_IE_octetDeltaCount); + pflowif->sc_tmpl_ipfix.ipv4_tmpl.octets.len = htons(8); + pflowif->sc_tmpl_ipfix.ipv4_tmpl.start.field_id = + htons(PFIX_IE_flowStartMilliseconds); + pflowif->sc_tmpl_ipfix.ipv4_tmpl.start.len = htons(8); + pflowif->sc_tmpl_ipfix.ipv4_tmpl.finish.field_id = + htons(PFIX_IE_flowEndMilliseconds); + pflowif->sc_tmpl_ipfix.ipv4_tmpl.finish.len = htons(8); + pflowif->sc_tmpl_ipfix.ipv4_tmpl.src_port.field_id = + htons(PFIX_IE_sourceTransportPort); + pflowif->sc_tmpl_ipfix.ipv4_tmpl.src_port.len = htons(2); + pflowif->sc_tmpl_ipfix.ipv4_tmpl.dest_port.field_id = + htons(PFIX_IE_destinationTransportPort); + pflowif->sc_tmpl_ipfix.ipv4_tmpl.dest_port.len = htons(2); + pflowif->sc_tmpl_ipfix.ipv4_tmpl.tos.field_id = + htons(PFIX_IE_ipClassOfService); + pflowif->sc_tmpl_ipfix.ipv4_tmpl.tos.len = htons(1); + pflowif->sc_tmpl_ipfix.ipv4_tmpl.protocol.field_id = + htons(PFIX_IE_protocolIdentifier); + pflowif->sc_tmpl_ipfix.ipv4_tmpl.protocol.len = htons(1); + + /* ipfix IPv6 template */ + pflowif->sc_tmpl_ipfix.ipv6_tmpl.h.tmpl_id = + htons(PFLOW_IPFIX_TMPL_IPV6_ID); + pflowif->sc_tmpl_ipfix.ipv6_tmpl.h.field_count = + htons(PFLOW_IPFIX_TMPL_IPV6_FIELD_COUNT); + pflowif->sc_tmpl_ipfix.ipv6_tmpl.src_ip.field_id = + htons(PFIX_IE_sourceIPv6Address); + pflowif->sc_tmpl_ipfix.ipv6_tmpl.src_ip.len = htons(16); + pflowif->sc_tmpl_ipfix.ipv6_tmpl.dest_ip.field_id = + htons(PFIX_IE_destinationIPv6Address); + pflowif->sc_tmpl_ipfix.ipv6_tmpl.dest_ip.len = htons(16); + pflowif->sc_tmpl_ipfix.ipv6_tmpl.if_index_in.field_id = + htons(PFIX_IE_ingressInterface); + pflowif->sc_tmpl_ipfix.ipv6_tmpl.if_index_in.len = htons(4); + pflowif->sc_tmpl_ipfix.ipv6_tmpl.if_index_out.field_id = + htons(PFIX_IE_egressInterface); + pflowif->sc_tmpl_ipfix.ipv6_tmpl.if_index_out.len = htons(4); + pflowif->sc_tmpl_ipfix.ipv6_tmpl.packets.field_id = + htons(PFIX_IE_packetDeltaCount); + pflowif->sc_tmpl_ipfix.ipv6_tmpl.packets.len = htons(8); + pflowif->sc_tmpl_ipfix.ipv6_tmpl.octets.field_id = + htons(PFIX_IE_octetDeltaCount); + pflowif->sc_tmpl_ipfix.ipv6_tmpl.octets.len = htons(8); + pflowif->sc_tmpl_ipfix.ipv6_tmpl.start.field_id = + htons(PFIX_IE_flowStartMilliseconds); + pflowif->sc_tmpl_ipfix.ipv6_tmpl.start.len = htons(8); + pflowif->sc_tmpl_ipfix.ipv6_tmpl.finish.field_id = + htons(PFIX_IE_flowEndMilliseconds); + pflowif->sc_tmpl_ipfix.ipv6_tmpl.finish.len = htons(8); + pflowif->sc_tmpl_ipfix.ipv6_tmpl.src_port.field_id = + htons(PFIX_IE_sourceTransportPort); + pflowif->sc_tmpl_ipfix.ipv6_tmpl.src_port.len = htons(2); + pflowif->sc_tmpl_ipfix.ipv6_tmpl.dest_port.field_id = + htons(PFIX_IE_destinationTransportPort); + pflowif->sc_tmpl_ipfix.ipv6_tmpl.dest_port.len = htons(2); + pflowif->sc_tmpl_ipfix.ipv6_tmpl.tos.field_id = + htons(PFIX_IE_ipClassOfService); + pflowif->sc_tmpl_ipfix.ipv6_tmpl.tos.len = htons(1); + pflowif->sc_tmpl_ipfix.ipv6_tmpl.protocol.field_id = + htons(PFIX_IE_protocolIdentifier); + pflowif->sc_tmpl_ipfix.ipv6_tmpl.protocol.len = htons(1); + + pflowif->sc_id = unit; + pflowif->sc_vnet = curvnet; + + mbufq_init(&pflowif->sc_outputqueue, 8192); + pflow_setmtu(pflowif, ETHERMTU); + + callout_init_mtx(&pflowif->sc_tmo, &pflowif->sc_lock, 0); + callout_init_mtx(&pflowif->sc_tmo6, &pflowif->sc_lock, 0); + callout_init_mtx(&pflowif->sc_tmo_tmpl, &pflowif->sc_lock, 0); + + error = swi_add(&pflowif->sc_swi_ie, pflowname, pflow_output_process, + pflowif, SWI_NET, INTR_MPSAFE, &pflowif->sc_swi_cookie); + if (error) { + free(pflowif, M_DEVBUF); + return (error); + } + + /* Insert into list of pflows */ + mtx_lock(&V_pflowif_list_mtx); + CK_LIST_INSERT_HEAD(&V_pflowif_list, pflowif, sc_next); + mtx_unlock(&V_pflowif_list_mtx); + + return (0); +} + +static void +pflow_free_cb(struct epoch_context *ctx) +{ + struct pflow_softc *sc; + + sc = __containerof(ctx, struct pflow_softc, sc_epoch_ctx); + + free(sc, M_DEVBUF); +} + +static int +pflow_destroy(int unit, bool drain) +{ + struct pflow_softc *sc; + int error __diagused; + + mtx_lock(&V_pflowif_list_mtx); + CK_LIST_FOREACH(sc, &V_pflowif_list, sc_next) { + if (sc->sc_id == unit) + break; + } + if (sc == NULL) { + mtx_unlock(&V_pflowif_list_mtx); + return (ENOENT); + } + CK_LIST_REMOVE(sc, sc_next); + mtx_unlock(&V_pflowif_list_mtx); + + sc->sc_dying = 1; + + if (drain) { + /* Let's be sure no one is using this interface any more. */ + NET_EPOCH_DRAIN_CALLBACKS(); + } + + error = swi_remove(sc->sc_swi_cookie); + MPASS(error == 0); + error = intr_event_destroy(sc->sc_swi_ie); + MPASS(error == 0); + + callout_drain(&sc->sc_tmo); + callout_drain(&sc->sc_tmo6); + callout_drain(&sc->sc_tmo_tmpl); + + m_freem(sc->sc_mbuf); + m_freem(sc->sc_mbuf6); + + PFLOW_LOCK(sc); + mbufq_drain(&sc->sc_outputqueue); + if (sc->so != NULL) { + soclose(sc->so); + sc->so = NULL; + } + if (sc->sc_flowdst != NULL) + free(sc->sc_flowdst, M_DEVBUF); + if (sc->sc_flowsrc != NULL) + free(sc->sc_flowsrc, M_DEVBUF); + PFLOW_UNLOCK(sc); + + mtx_destroy(&sc->sc_lock); + + free_unr(V_pflow_unr, unit); + + NET_EPOCH_CALL(pflow_free_cb, &sc->sc_epoch_ctx); + + return (0); +} + +static int +pflowvalidsockaddr(const struct sockaddr *sa, int ignore_port) +{ + const struct sockaddr_in6 *sin6; + const struct sockaddr_in *sin; + + if (sa == NULL) + return (0); + switch(sa->sa_family) { + case AF_INET: + sin = (const struct sockaddr_in *)sa; + return (sin->sin_addr.s_addr != INADDR_ANY && + (ignore_port || sin->sin_port != 0)); + case AF_INET6: + sin6 = (const struct sockaddr_in6 *)sa; + return (!IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr) && + (ignore_port || sin6->sin6_port != 0)); + default: + return (0); + } +} + +static int +pflow_calc_mtu(struct pflow_softc *sc, int mtu, int hdrsz) +{ + + sc->sc_maxcount4 = (mtu - hdrsz - + sizeof(struct udpiphdr)) / sizeof(struct pflow_ipfix_flow4); + sc->sc_maxcount6 = (mtu - hdrsz - + sizeof(struct udpiphdr)) / sizeof(struct pflow_ipfix_flow6); + if (sc->sc_maxcount4 > PFLOW_MAXFLOWS) + sc->sc_maxcount4 = PFLOW_MAXFLOWS; + if (sc->sc_maxcount6 > PFLOW_MAXFLOWS) + sc->sc_maxcount6 = PFLOW_MAXFLOWS; + return (hdrsz + sizeof(struct udpiphdr) + + MIN(sc->sc_maxcount4 * sizeof(struct pflow_ipfix_flow4), + sc->sc_maxcount6 * sizeof(struct pflow_ipfix_flow6))); +} + +static void +pflow_setmtu(struct pflow_softc *sc, int mtu_req) +{ + int mtu; + + mtu = mtu_req; + + switch (sc->sc_version) { + case PFLOW_PROTO_5: + sc->sc_maxcount = (mtu - sizeof(struct pflow_header) - + sizeof(struct udpiphdr)) / sizeof(struct pflow_flow); + if (sc->sc_maxcount > PFLOW_MAXFLOWS) + sc->sc_maxcount = PFLOW_MAXFLOWS; + break; + case PFLOW_PROTO_10: + pflow_calc_mtu(sc, mtu, sizeof(struct pflow_v10_header)); + break; + default: /* NOTREACHED */ + break; + } +} + +static struct mbuf * +pflow_get_mbuf(struct pflow_softc *sc, u_int16_t set_id) +{ + struct pflow_set_header set_hdr; + struct pflow_header h; + struct mbuf *m; + + MGETHDR(m, M_NOWAIT, MT_DATA); + if (m == NULL) { + V_pflowstats.pflow_onomem++; + return (NULL); + } + + MCLGET(m, M_NOWAIT); + if ((m->m_flags & M_EXT) == 0) { + m_free(m); + V_pflowstats.pflow_onomem++; + return (NULL); + } + + m->m_len = m->m_pkthdr.len = 0; + + if (sc == NULL) /* get only a new empty mbuf */ + return (m); + + switch (sc->sc_version) { + case PFLOW_PROTO_5: + /* populate pflow_header */ + h.reserved1 = 0; + h.reserved2 = 0; + h.count = 0; + h.version = htons(PFLOW_PROTO_5); + h.flow_sequence = htonl(sc->sc_gcounter); + h.engine_type = PFLOW_ENGINE_TYPE; + h.engine_id = PFLOW_ENGINE_ID; + m_copyback(m, 0, PFLOW_HDRLEN, (caddr_t)&h); + + sc->sc_count = 0; + callout_reset(&sc->sc_tmo, PFLOW_TIMEOUT * hz, + pflow_timeout, sc); + break; + case PFLOW_PROTO_10: + /* populate pflow_set_header */ + set_hdr.set_length = 0; + set_hdr.set_id = htons(set_id); + m_copyback(m, 0, PFLOW_SET_HDRLEN, (caddr_t)&set_hdr); + break; + default: /* NOTREACHED */ + break; + } + + return (m); +} + +static void +copy_flow_data(struct pflow_flow *flow1, struct pflow_flow *flow2, + struct pf_kstate *st, struct pf_state_key *sk, int src, int dst) +{ + flow1->src_ip = flow2->dest_ip = sk->addr[src].v4.s_addr; + flow1->src_port = flow2->dest_port = sk->port[src]; + flow1->dest_ip = flow2->src_ip = sk->addr[dst].v4.s_addr; + flow1->dest_port = flow2->src_port = sk->port[dst]; + + flow1->dest_as = flow2->src_as = + flow1->src_as = flow2->dest_as = 0; + flow1->if_index_in = htons(st->if_index_in); + flow1->if_index_out = htons(st->if_index_out); + flow2->if_index_in = htons(st->if_index_out); + flow2->if_index_out = htons(st->if_index_in); + flow1->dest_mask = flow2->src_mask = + flow1->src_mask = flow2->dest_mask = 0; + + flow1->flow_packets = htonl(st->packets[0]); + flow2->flow_packets = htonl(st->packets[1]); + flow1->flow_octets = htonl(st->bytes[0]); + flow2->flow_octets = htonl(st->bytes[1]); + + /* + * Pretend the flow was created or expired when the machine came up + * when creation is in the future of the last time a package was seen + * or was created / expired before this machine came up due to pfsync. + */ + flow1->flow_start = flow2->flow_start = st->creation < 0 || + st->creation > st->expire ? htonl(0) : htonl(st->creation * 1000); + flow1->flow_finish = flow2->flow_finish = st->expire < 0 ? htonl(0) : + htonl(st->expire * 1000); + flow1->tcp_flags = flow2->tcp_flags = 0; + flow1->protocol = flow2->protocol = sk->proto; + flow1->tos = flow2->tos = st->rule.ptr->tos; +} + +static void +copy_flow_ipfix_4_data(struct pflow_ipfix_flow4 *flow1, + struct pflow_ipfix_flow4 *flow2, struct pf_kstate *st, + struct pf_state_key *sk, struct pflow_softc *sc, int src, int dst) +{ + flow1->src_ip = flow2->dest_ip = sk->addr[src].v4.s_addr; + flow1->src_port = flow2->dest_port = sk->port[src]; + flow1->dest_ip = flow2->src_ip = sk->addr[dst].v4.s_addr; + flow1->dest_port = flow2->src_port = sk->port[dst]; + + flow1->if_index_in = htonl(st->if_index_in); + flow1->if_index_out = htonl(st->if_index_out); + flow2->if_index_in = htonl(st->if_index_out); + flow2->if_index_out = htonl(st->if_index_in); + + flow1->flow_packets = htobe64(st->packets[0]); + flow2->flow_packets = htobe64(st->packets[1]); + flow1->flow_octets = htobe64(st->bytes[0]); + flow2->flow_octets = htobe64(st->bytes[1]); + + /* + * Pretend the flow was created when the machine came up when creation + * is in the future of the last time a package was seen due to pfsync. + */ + if (st->creation > st->expire) + flow1->flow_start = flow2->flow_start = htobe64((time_second - + time_uptime)*1000); + else + flow1->flow_start = flow2->flow_start = htobe64((time_second - + (time_uptime - st->creation))*1000); + flow1->flow_finish = flow2->flow_finish = htobe64((time_second - + (time_uptime - st->expire))*1000); + + flow1->protocol = flow2->protocol = sk->proto; + flow1->tos = flow2->tos = st->rule.ptr->tos; +} + +static void +copy_flow_ipfix_6_data(struct pflow_ipfix_flow6 *flow1, + struct pflow_ipfix_flow6 *flow2, struct pf_kstate *st, + struct pf_state_key *sk, struct pflow_softc *sc, int src, int dst) +{ + bcopy(&sk->addr[src].v6, &flow1->src_ip, sizeof(flow1->src_ip)); + bcopy(&sk->addr[src].v6, &flow2->dest_ip, sizeof(flow2->dest_ip)); + flow1->src_port = flow2->dest_port = sk->port[src]; + bcopy(&sk->addr[dst].v6, &flow1->dest_ip, sizeof(flow1->dest_ip)); + bcopy(&sk->addr[dst].v6, &flow2->src_ip, sizeof(flow2->src_ip)); + flow1->dest_port = flow2->src_port = sk->port[dst]; + + flow1->if_index_in = htonl(st->if_index_in); + flow1->if_index_out = htonl(st->if_index_out); + flow2->if_index_in = htonl(st->if_index_out); + flow2->if_index_out = htonl(st->if_index_in); + + flow1->flow_packets = htobe64(st->packets[0]); + flow2->flow_packets = htobe64(st->packets[1]); + flow1->flow_octets = htobe64(st->bytes[0]); + flow2->flow_octets = htobe64(st->bytes[1]); + + /* + * Pretend the flow was created when the machine came up when creation + * is in the future of the last time a package was seen due to pfsync. + */ + if (st->creation > st->expire) + flow1->flow_start = flow2->flow_start = htobe64((time_second - + time_uptime)*1000); + else + flow1->flow_start = flow2->flow_start = htobe64((time_second - + (time_uptime - st->creation))*1000); + flow1->flow_finish = flow2->flow_finish = htobe64((time_second - + (time_uptime - st->expire))*1000); + + flow1->protocol = flow2->protocol = sk->proto; + flow1->tos = flow2->tos = st->rule.ptr->tos; +} + +int +export_pflow(struct pf_kstate *st) +{ + struct pflow_softc *sc = NULL; + struct pf_state_key *sk; + + NET_EPOCH_ASSERT(); + + sk = st->key[st->direction == PF_IN ? PF_SK_WIRE : PF_SK_STACK]; + + CK_LIST_FOREACH(sc, &V_pflowif_list, sc_next) { + PFLOW_LOCK(sc); + switch (sc->sc_version) { + case PFLOW_PROTO_5: + if (sk->af == AF_INET) + export_pflow_if(st, sk, sc); + break; + case PFLOW_PROTO_10: + if (sk->af == AF_INET || sk->af == AF_INET6) + export_pflow_if(st, sk, sc); + break; + default: /* NOTREACHED */ + break; + } + PFLOW_UNLOCK(sc); + } + + return (0); +} + +static int +export_pflow_if(struct pf_kstate *st, struct pf_state_key *sk, + struct pflow_softc *sc) +{ + struct pf_kstate pfs_copy; + u_int64_t bytes[2]; + int ret = 0; + + if (sc->sc_version == PFLOW_PROTO_10) + return (pflow_pack_flow_ipfix(st, sk, sc)); + + /* PFLOW_PROTO_5 */ + if ((st->bytes[0] < (u_int64_t)PFLOW_MAXBYTES) + && (st->bytes[1] < (u_int64_t)PFLOW_MAXBYTES)) + return (pflow_pack_flow(st, sk, sc)); + + /* flow > PFLOW_MAXBYTES need special handling */ + bcopy(st, &pfs_copy, sizeof(pfs_copy)); + bytes[0] = pfs_copy.bytes[0]; + bytes[1] = pfs_copy.bytes[1]; + + while (bytes[0] > PFLOW_MAXBYTES) { + pfs_copy.bytes[0] = PFLOW_MAXBYTES; + pfs_copy.bytes[1] = 0; + + if ((ret = pflow_pack_flow(&pfs_copy, sk, sc)) != 0) + return (ret); + if ((bytes[0] - PFLOW_MAXBYTES) > 0) + bytes[0] -= PFLOW_MAXBYTES; + } + + while (bytes[1] > (u_int64_t)PFLOW_MAXBYTES) { + pfs_copy.bytes[1] = PFLOW_MAXBYTES; + pfs_copy.bytes[0] = 0; + + if ((ret = pflow_pack_flow(&pfs_copy, sk, sc)) != 0) + return (ret); + if ((bytes[1] - PFLOW_MAXBYTES) > 0) + bytes[1] -= PFLOW_MAXBYTES; + } + + pfs_copy.bytes[0] = bytes[0]; + pfs_copy.bytes[1] = bytes[1]; + + return (pflow_pack_flow(&pfs_copy, sk, sc)); +} + +static int +copy_flow_to_m(struct pflow_flow *flow, struct pflow_softc *sc) +{ + int ret = 0; + + PFLOW_ASSERT(sc); + + if (sc->sc_mbuf == NULL) { + if ((sc->sc_mbuf = pflow_get_mbuf(sc, 0)) == NULL) + return (ENOBUFS); + } + m_copyback(sc->sc_mbuf, PFLOW_HDRLEN + + (sc->sc_count * sizeof(struct pflow_flow)), + sizeof(struct pflow_flow), (caddr_t)flow); + + if (V_pflowstats.pflow_flows == sc->sc_gcounter) + V_pflowstats.pflow_flows++; + sc->sc_gcounter++; + sc->sc_count++; + + if (sc->sc_count >= sc->sc_maxcount) + ret = pflow_sendout_v5(sc); + + return(ret); +} + +static int +copy_flow_ipfix_4_to_m(struct pflow_ipfix_flow4 *flow, struct pflow_softc *sc) +{ + int ret = 0; + + PFLOW_ASSERT(sc); + + if (sc->sc_mbuf == NULL) { + if ((sc->sc_mbuf = + pflow_get_mbuf(sc, PFLOW_IPFIX_TMPL_IPV4_ID)) == NULL) { + return (ENOBUFS); + } + sc->sc_count4 = 0; + callout_reset(&sc->sc_tmo, PFLOW_TIMEOUT * hz, + pflow_timeout, sc); + } + m_copyback(sc->sc_mbuf, PFLOW_SET_HDRLEN + + (sc->sc_count4 * sizeof(struct pflow_ipfix_flow4)), + sizeof(struct pflow_ipfix_flow4), (caddr_t)flow); + + if (V_pflowstats.pflow_flows == sc->sc_gcounter) + V_pflowstats.pflow_flows++; + sc->sc_gcounter++; + sc->sc_count4++; + + if (sc->sc_count4 >= sc->sc_maxcount4) + ret = pflow_sendout_ipfix(sc, AF_INET); + return(ret); +} + +static int +copy_flow_ipfix_6_to_m(struct pflow_ipfix_flow6 *flow, struct pflow_softc *sc) +{ + int ret = 0; + + PFLOW_ASSERT(sc); + + if (sc->sc_mbuf6 == NULL) { + if ((sc->sc_mbuf6 = + pflow_get_mbuf(sc, PFLOW_IPFIX_TMPL_IPV6_ID)) == NULL) { + return (ENOBUFS); + } + sc->sc_count6 = 0; + callout_reset(&sc->sc_tmo6, PFLOW_TIMEOUT * hz, + pflow_timeout6, sc); + } + m_copyback(sc->sc_mbuf6, PFLOW_SET_HDRLEN + + (sc->sc_count6 * sizeof(struct pflow_ipfix_flow6)), + sizeof(struct pflow_ipfix_flow6), (caddr_t)flow); + + if (V_pflowstats.pflow_flows == sc->sc_gcounter) + V_pflowstats.pflow_flows++; + sc->sc_gcounter++; + sc->sc_count6++; + + if (sc->sc_count6 >= sc->sc_maxcount6) + ret = pflow_sendout_ipfix(sc, AF_INET6); + + return(ret); +} + +static int +pflow_pack_flow(struct pf_kstate *st, struct pf_state_key *sk, + struct pflow_softc *sc) +{ + struct pflow_flow flow1; + struct pflow_flow flow2; + int ret = 0; + + bzero(&flow1, sizeof(flow1)); + bzero(&flow2, sizeof(flow2)); + + if (st->direction == PF_OUT) + copy_flow_data(&flow1, &flow2, st, sk, 1, 0); + else + copy_flow_data(&flow1, &flow2, st, sk, 0, 1); + + if (st->bytes[0] != 0) /* first flow from state */ + ret = copy_flow_to_m(&flow1, sc); + + if (st->bytes[1] != 0) /* second flow from state */ + ret = copy_flow_to_m(&flow2, sc); + + return (ret); +} + +static int +pflow_pack_flow_ipfix(struct pf_kstate *st, struct pf_state_key *sk, + struct pflow_softc *sc) +{ + struct pflow_ipfix_flow4 flow4_1, flow4_2; + struct pflow_ipfix_flow6 flow6_1, flow6_2; + int ret = 0; + if (sk->af == AF_INET) { + bzero(&flow4_1, sizeof(flow4_1)); + bzero(&flow4_2, sizeof(flow4_2)); + + if (st->direction == PF_OUT) + copy_flow_ipfix_4_data(&flow4_1, &flow4_2, st, sk, sc, + 1, 0); + else + copy_flow_ipfix_4_data(&flow4_1, &flow4_2, st, sk, sc, + 0, 1); + + if (st->bytes[0] != 0) /* first flow from state */ + ret = copy_flow_ipfix_4_to_m(&flow4_1, sc); + + if (st->bytes[1] != 0) /* second flow from state */ + ret = copy_flow_ipfix_4_to_m(&flow4_2, sc); + } else if (sk->af == AF_INET6) { + bzero(&flow6_1, sizeof(flow6_1)); + bzero(&flow6_2, sizeof(flow6_2)); + + if (st->direction == PF_OUT) + copy_flow_ipfix_6_data(&flow6_1, &flow6_2, st, sk, sc, + 1, 0); + else + copy_flow_ipfix_6_data(&flow6_1, &flow6_2, st, sk, sc, + 0, 1); + + if (st->bytes[0] != 0) /* first flow from state */ + ret = copy_flow_ipfix_6_to_m(&flow6_1, sc); + + if (st->bytes[1] != 0) /* second flow from state */ + ret = copy_flow_ipfix_6_to_m(&flow6_2, sc); + } + return (ret); +} + +static void +pflow_timeout(void *v) +{ + struct pflow_softc *sc = v; + + PFLOW_ASSERT(sc); + CURVNET_SET(sc->sc_vnet); + + switch (sc->sc_version) { + case PFLOW_PROTO_5: + pflow_sendout_v5(sc); + break; + case PFLOW_PROTO_10: + pflow_sendout_ipfix(sc, AF_INET); + break; + default: /* NOTREACHED */ + panic("Unsupported version %d", sc->sc_version); + break; + } + + CURVNET_RESTORE(); +} + +static void +pflow_timeout6(void *v) +{ + struct pflow_softc *sc = v; + + PFLOW_ASSERT(sc); + + if (sc->sc_version != PFLOW_PROTO_10) + return; + + CURVNET_SET(sc->sc_vnet); + pflow_sendout_ipfix(sc, AF_INET6); + CURVNET_RESTORE(); +} + +static void +pflow_timeout_tmpl(void *v) +{ + struct pflow_softc *sc = v; + + PFLOW_ASSERT(sc); + + if (sc->sc_version != PFLOW_PROTO_10) + return; + + CURVNET_SET(sc->sc_vnet); + pflow_sendout_ipfix_tmpl(sc); + CURVNET_RESTORE(); +} + +static void +pflow_flush(struct pflow_softc *sc) +{ + PFLOW_ASSERT(sc); + + switch (sc->sc_version) { + case PFLOW_PROTO_5: + pflow_sendout_v5(sc); + break; + case PFLOW_PROTO_10: + pflow_sendout_ipfix(sc, AF_INET); + pflow_sendout_ipfix(sc, AF_INET6); + break; + default: /* NOTREACHED */ + break; + } +} + +static int +pflow_sendout_v5(struct pflow_softc *sc) +{ + struct mbuf *m = sc->sc_mbuf; + struct pflow_header *h; + struct timespec tv; + + PFLOW_ASSERT(sc); + + if (m == NULL) + return (0); + + sc->sc_mbuf = NULL; + + V_pflowstats.pflow_packets++; + h = mtod(m, struct pflow_header *); + h->count = htons(sc->sc_count); + + /* populate pflow_header */ + h->uptime_ms = htonl(time_uptime * 1000); + + getnanotime(&tv); + h->time_sec = htonl(tv.tv_sec); /* XXX 2038 */ + h->time_nanosec = htonl(tv.tv_nsec); + if (mbufq_enqueue(&sc->sc_outputqueue, m) == 0) + swi_sched(sc->sc_swi_cookie, 0); + + return (0); +} + +static int +pflow_sendout_ipfix(struct pflow_softc *sc, sa_family_t af) +{ + struct mbuf *m; + struct pflow_v10_header *h10; + struct pflow_set_header *set_hdr; + u_int32_t count; + int set_length; + + PFLOW_ASSERT(sc); + + switch (af) { + case AF_INET: + m = sc->sc_mbuf; + callout_stop(&sc->sc_tmo); + if (m == NULL) + return (0); + sc->sc_mbuf = NULL; + count = sc->sc_count4; + set_length = sizeof(struct pflow_set_header) + + sc->sc_count4 * sizeof(struct pflow_ipfix_flow4); + break; + case AF_INET6: + m = sc->sc_mbuf6; + callout_stop(&sc->sc_tmo6); + if (m == NULL) + return (0); + sc->sc_mbuf6 = NULL; + count = sc->sc_count6; + set_length = sizeof(struct pflow_set_header) + + sc->sc_count6 * sizeof(struct pflow_ipfix_flow6); + break; + default: + panic("Unsupported AF %d", af); + } + + V_pflowstats.pflow_packets++; + set_hdr = mtod(m, struct pflow_set_header *); + set_hdr->set_length = htons(set_length); + + /* populate pflow_header */ + M_PREPEND(m, sizeof(struct pflow_v10_header), M_NOWAIT); + if (m == NULL) { + V_pflowstats.pflow_onomem++; + return (ENOBUFS); + } + h10 = mtod(m, struct pflow_v10_header *); + h10->version = htons(PFLOW_PROTO_10); + h10->length = htons(PFLOW_IPFIX_HDRLEN + set_length); + h10->time_sec = htonl(time_second); /* XXX 2038 */ + h10->flow_sequence = htonl(sc->sc_sequence); + sc->sc_sequence += count; + h10->observation_dom = htonl(PFLOW_ENGINE_TYPE); + if (mbufq_enqueue(&sc->sc_outputqueue, m) == 0) + swi_sched(sc->sc_swi_cookie, 0); + + return (0); +} + +static int +pflow_sendout_ipfix_tmpl(struct pflow_softc *sc) +{ + struct mbuf *m; + struct pflow_v10_header *h10; + + PFLOW_ASSERT(sc); + + m = pflow_get_mbuf(sc, 0); + if (m == NULL) + return (0); + m_copyback(m, 0, sizeof(struct pflow_ipfix_tmpl), + (caddr_t)&sc->sc_tmpl_ipfix); + + V_pflowstats.pflow_packets++; + + /* populate pflow_header */ + M_PREPEND(m, sizeof(struct pflow_v10_header), M_NOWAIT); + if (m == NULL) { + V_pflowstats.pflow_onomem++; + return (ENOBUFS); + } + h10 = mtod(m, struct pflow_v10_header *); + h10->version = htons(PFLOW_PROTO_10); + h10->length = htons(PFLOW_IPFIX_HDRLEN + sizeof(struct + pflow_ipfix_tmpl)); + h10->time_sec = htonl(time_second); /* XXX 2038 */ + h10->flow_sequence = htonl(sc->sc_sequence); + h10->observation_dom = htonl(PFLOW_ENGINE_TYPE); + + callout_reset(&sc->sc_tmo_tmpl, PFLOW_TMPL_TIMEOUT * hz, + pflow_timeout_tmpl, sc); + if (mbufq_enqueue(&sc->sc_outputqueue, m) == 0) + swi_sched(sc->sc_swi_cookie, 0); + + return (0); +} + +static int +pflow_sendout_mbuf(struct pflow_softc *sc, struct mbuf *m) +{ + if (sc->so == NULL) { + m_freem(m); + return (EINVAL); + } + return (sosend(sc->so, sc->sc_flowdst, NULL, m, NULL, 0, curthread)); +} + +static int +pflow_nl_list(struct nlmsghdr *hdr, struct nl_pstate *npt) +{ + struct epoch_tracker et; + struct pflow_softc *sc = NULL; + struct nl_writer *nw = npt->nw; + int error = 0; + + hdr->nlmsg_flags |= NLM_F_MULTI; + + NET_EPOCH_ENTER(et); + CK_LIST_FOREACH(sc, &V_pflowif_list, sc_next) { + if (!nlmsg_reply(nw, hdr, sizeof(struct genlmsghdr))) { + error = ENOMEM; + goto out; + } + + struct genlmsghdr *ghdr_new = nlmsg_reserve_object(nw, struct genlmsghdr); + ghdr_new->cmd = PFLOWNL_CMD_LIST; + ghdr_new->version = 0; + ghdr_new->reserved = 0; + + nlattr_add_u32(nw, PFLOWNL_L_ID, sc->sc_id); + + if (! nlmsg_end(nw)) { + error = ENOMEM; + goto out; + } + } + +out: + NET_EPOCH_EXIT(et); + + if (error != 0) + nlmsg_abort(nw); + + return (error); +} + +static int +pflow_nl_create(struct nlmsghdr *hdr, struct nl_pstate *npt) +{ + struct nl_writer *nw = npt->nw; + int error = 0; + int unit; + + if (! nlmsg_reply(nw, hdr, sizeof(struct genlmsghdr))) { + return (ENOMEM); + } + + struct genlmsghdr *ghdr_new = nlmsg_reserve_object(nw, struct genlmsghdr); + ghdr_new->cmd = PFLOWNL_CMD_CREATE; + ghdr_new->version = 0; + ghdr_new->reserved = 0; + + unit = alloc_unr(V_pflow_unr); + + error = pflow_create(unit); + if (error != 0) { + free_unr(V_pflow_unr, unit); + nlmsg_abort(nw); + return (error); + } + + nlattr_add_s32(nw, PFLOWNL_CREATE_ID, unit); + + if (! nlmsg_end(nw)) { + pflow_destroy(unit, true); + return (ENOMEM); + } + + return (0); +} + +struct pflow_parsed_del { + int id; +}; +#define _IN(_field) offsetof(struct genlmsghdr, _field) +#define _OUT(_field) offsetof(struct pflow_parsed_del, _field) +static const struct nlattr_parser nla_p_del[] = { + { .type = PFLOWNL_DEL_ID, .off = _OUT(id), .cb = nlattr_get_uint32 }, +}; +static const struct nlfield_parser nlf_p_del[] = {}; +#undef _IN +#undef _OUT +NL_DECLARE_PARSER(del_parser, struct genlmsghdr, nlf_p_del, nla_p_del); + +static int +pflow_nl_del(struct nlmsghdr *hdr, struct nl_pstate *npt) +{ + struct pflow_parsed_del d = {}; + int error; + + error = nl_parse_nlmsg(hdr, &del_parser, npt, &d); + if (error != 0) + return (error); + + error = pflow_destroy(d.id, true); + + return (error); +} + +struct pflow_parsed_get { + int id; +}; +#define _IN(_field) offsetof(struct genlmsghdr, _field) +#define _OUT(_field) offsetof(struct pflow_parsed_get, _field) +static const struct nlattr_parser nla_p_get[] = { + { .type = PFLOWNL_GET_ID, .off = _OUT(id), .cb = nlattr_get_uint32 }, +}; +static const struct nlfield_parser nlf_p_get[] = {}; +#undef _IN +#undef _OUT +NL_DECLARE_PARSER(get_parser, struct genlmsghdr, nlf_p_get, nla_p_get); + +static bool +nlattr_add_sockaddr(struct nl_writer *nw, int attr, const struct sockaddr *s) +{ + int off = nlattr_add_nested(nw, attr); + if (off == 0) + return (false); + + nlattr_add_u8(nw, PFLOWNL_ADDR_FAMILY, s->sa_family); + + switch (s->sa_family) { + case AF_INET: { + const struct sockaddr_in *in = (const struct sockaddr_in *)s; + nlattr_add_u16(nw, PFLOWNL_ADDR_PORT, in->sin_port); + nlattr_add_in_addr(nw, PFLOWNL_ADDR_IP, &in->sin_addr); + break; + } + case AF_INET6: { + const struct sockaddr_in6 *in6 = (const struct sockaddr_in6 *)s; + nlattr_add_u16(nw, PFLOWNL_ADDR_PORT, in6->sin6_port); + nlattr_add_in6_addr(nw, PFLOWNL_ADDR_IP6, &in6->sin6_addr); + break; + } + default: + panic("Unknown address family %d", s->sa_family); + } + + nlattr_set_len(nw, off); + return (true); +} + +static int +pflow_nl_get(struct nlmsghdr *hdr, struct nl_pstate *npt) +{ + struct epoch_tracker et; + struct pflow_parsed_get g = {}; + struct pflow_softc *sc = NULL; + struct nl_writer *nw = npt->nw; + struct genlmsghdr *ghdr_new; + int error; + + error = nl_parse_nlmsg(hdr, &get_parser, npt, &g); + if (error != 0) + return (error); + + NET_EPOCH_ENTER(et); + CK_LIST_FOREACH(sc, &V_pflowif_list, sc_next) { + if (sc->sc_id == g.id) + break; + } + if (sc == NULL) { + error = ENOENT; + goto out; + } + + if (! nlmsg_reply(nw, hdr, sizeof(struct genlmsghdr))) { + nlmsg_abort(nw); + error = ENOMEM; + goto out; + } + + ghdr_new = nlmsg_reserve_object(nw, struct genlmsghdr); + if (ghdr_new == NULL) { + nlmsg_abort(nw); + error = ENOMEM; + goto out; + } + + ghdr_new->cmd = PFLOWNL_CMD_GET; + ghdr_new->version = 0; + ghdr_new->reserved = 0; + + nlattr_add_u32(nw, PFLOWNL_GET_ID, sc->sc_id); + nlattr_add_u16(nw, PFLOWNL_GET_VERSION, sc->sc_version); + if (sc->sc_flowsrc) + nlattr_add_sockaddr(nw, PFLOWNL_GET_SRC, sc->sc_flowsrc); + if (sc->sc_flowdst) + nlattr_add_sockaddr(nw, PFLOWNL_GET_DST, sc->sc_flowdst); + + if (! nlmsg_end(nw)) { + nlmsg_abort(nw); + error = ENOMEM; + } + +out: + NET_EPOCH_EXIT(et); + + return (error); +} + +struct pflow_sockaddr { + union { + struct sockaddr_in in; + struct sockaddr_in6 in6; + struct sockaddr_storage storage; + }; +}; +static bool +pflow_postparse_sockaddr(void *parsed_args, struct nl_pstate *npt __unused) +{ + struct pflow_sockaddr *s = (struct pflow_sockaddr *)parsed_args; + + 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 pflow_sockaddr, _field) +static struct nlattr_parser nla_p_sockaddr[] = { + { .type = PFLOWNL_ADDR_FAMILY, .off = _OUT(in.sin_family), .cb = nlattr_get_uint8 }, + { .type = PFLOWNL_ADDR_PORT, .off = _OUT(in.sin_port), .cb = nlattr_get_uint16 }, + { .type = PFLOWNL_ADDR_IP, .off = _OUT(in.sin_addr), .cb = nlattr_get_in_addr }, + { .type = PFLOWNL_ADDR_IP6, .off = _OUT(in6.sin6_addr), .cb = nlattr_get_in6_addr }, +}; +NL_DECLARE_ATTR_PARSER_EXT(addr_parser, nla_p_sockaddr, pflow_postparse_sockaddr); +#undef _OUT + +struct pflow_parsed_set { + int id; + uint16_t version; + struct sockaddr_storage src; + struct sockaddr_storage dst; +}; +#define _IN(_field) offsetof(struct genlmsghdr, _field) +#define _OUT(_field) offsetof(struct pflow_parsed_set, _field) +static const struct nlattr_parser nla_p_set[] = { + { .type = PFLOWNL_SET_ID, .off = _OUT(id), .cb = nlattr_get_uint32 }, + { .type = PFLOWNL_SET_VERSION, .off = _OUT(version), .cb = nlattr_get_uint16 }, + { .type = PFLOWNL_SET_SRC, .off = _OUT(src), .arg = &addr_parser, .cb = nlattr_get_nested }, + { .type = PFLOWNL_SET_DST, .off = _OUT(dst), .arg = &addr_parser, .cb = nlattr_get_nested }, +}; +static const struct nlfield_parser nlf_p_set[] = {}; +#undef _IN +#undef _OUT +NL_DECLARE_PARSER(set_parser, struct genlmsghdr, nlf_p_set, nla_p_set); + +static int +pflow_set(struct pflow_softc *sc, const struct pflow_parsed_set *pflowr, struct ucred *cred) +{ + struct thread *td; + struct socket *so; + int error = 0; + + td = curthread; + + PFLOW_ASSERT(sc); + + if (pflowr->version != 0) { + switch(pflowr->version) { + case PFLOW_PROTO_5: + case PFLOW_PROTO_10: + break; + default: + return(EINVAL); + } + } + + pflow_flush(sc); + + if (pflowr->dst.ss_len != 0) { + if (sc->sc_flowdst != NULL && + sc->sc_flowdst->sa_family != pflowr->dst.ss_family) { + free(sc->sc_flowdst, M_DEVBUF); + sc->sc_flowdst = NULL; + if (sc->so != NULL) { + soclose(sc->so); + sc->so = NULL; + } + } + + switch (pflowr->dst.ss_family) { + case AF_INET: + if (sc->sc_flowdst == NULL) { + if ((sc->sc_flowdst = malloc( + sizeof(struct sockaddr_in), + M_DEVBUF, M_NOWAIT)) == NULL) + return (ENOMEM); + } + memcpy(sc->sc_flowdst, &pflowr->dst, + sizeof(struct sockaddr_in)); + sc->sc_flowdst->sa_len = sizeof(struct + sockaddr_in); + break; + case AF_INET6: + if (sc->sc_flowdst == NULL) { + if ((sc->sc_flowdst = malloc( + sizeof(struct sockaddr_in6), + M_DEVBUF, M_NOWAIT)) == NULL) + return (ENOMEM); + } + memcpy(sc->sc_flowdst, &pflowr->dst, + sizeof(struct sockaddr_in6)); + sc->sc_flowdst->sa_len = sizeof(struct + sockaddr_in6); + break; + default: + break; + } + } + + if (pflowr->src.ss_len != 0) { + if (sc->sc_flowsrc != NULL) + free(sc->sc_flowsrc, M_DEVBUF); + sc->sc_flowsrc = NULL; + if (sc->so != NULL) { + soclose(sc->so); + sc->so = NULL; + } + switch(pflowr->src.ss_family) { + case AF_INET: + if ((sc->sc_flowsrc = malloc( + sizeof(struct sockaddr_in), + M_DEVBUF, M_NOWAIT)) == NULL) + return (ENOMEM); + memcpy(sc->sc_flowsrc, &pflowr->src, + sizeof(struct sockaddr_in)); + sc->sc_flowsrc->sa_len = sizeof(struct + sockaddr_in); + break; + case AF_INET6: + if ((sc->sc_flowsrc = malloc( + sizeof(struct sockaddr_in6), + M_DEVBUF, M_NOWAIT)) == NULL) + return (ENOMEM); + memcpy(sc->sc_flowsrc, &pflowr->src, + sizeof(struct sockaddr_in6)); + sc->sc_flowsrc->sa_len = sizeof(struct + sockaddr_in6); + break; + default: + break; + } + } + + if (sc->so == NULL) { + if (pflowvalidsockaddr(sc->sc_flowdst, 0)) { + error = socreate(sc->sc_flowdst->sa_family, + &so, SOCK_DGRAM, IPPROTO_UDP, cred, td); + if (error) + return (error); + if (pflowvalidsockaddr(sc->sc_flowsrc, 1)) { + error = sobind(so, sc->sc_flowsrc, td); + if (error) { + soclose(so); + return (error); + } + } + sc->so = so; + } + } else if (!pflowvalidsockaddr(sc->sc_flowdst, 0)) { + soclose(sc->so); + sc->so = NULL; + } + + /* error check is above */ + if (pflowr->version != 0) + sc->sc_version = pflowr->version; + + pflow_setmtu(sc, ETHERMTU); + + switch (sc->sc_version) { + case PFLOW_PROTO_5: + callout_stop(&sc->sc_tmo6); + callout_stop(&sc->sc_tmo_tmpl); + break; + case PFLOW_PROTO_10: + callout_reset(&sc->sc_tmo_tmpl, PFLOW_TMPL_TIMEOUT * hz, + pflow_timeout_tmpl, sc); + break; + default: /* NOTREACHED */ + break; + } + + return (0); +} + +static int +pflow_nl_set(struct nlmsghdr *hdr, struct nl_pstate *npt) +{ + struct epoch_tracker et; + struct pflow_parsed_set s = {}; + struct pflow_softc *sc = NULL; + int error; + + error = nl_parse_nlmsg(hdr, &set_parser, npt, &s); + if (error != 0) + return (error); + + NET_EPOCH_ENTER(et); + CK_LIST_FOREACH(sc, &V_pflowif_list, sc_next) { + if (sc->sc_id == s.id) + break; + } + if (sc == NULL) { + error = ENOENT; + goto out; + } + + PFLOW_LOCK(sc); + error = pflow_set(sc, &s, nlp_get_cred(npt->nlp)); + PFLOW_UNLOCK(sc); + +out: + NET_EPOCH_EXIT(et); + return (error); +} + +static const struct genl_cmd pflow_cmds[] = { + { + .cmd_num = PFLOWNL_CMD_LIST, + .cmd_name = "LIST", + .cmd_cb = pflow_nl_list, + .cmd_flags = GENL_CMD_CAP_DO | GENL_CMD_CAP_DUMP | GENL_CMD_CAP_HASPOL, + .cmd_priv = PRIV_NETINET_PF, + }, + { + .cmd_num = PFLOWNL_CMD_CREATE, + .cmd_name = "CREATE", + .cmd_cb = pflow_nl_create, + .cmd_flags = GENL_CMD_CAP_DO | GENL_CMD_CAP_DUMP | GENL_CMD_CAP_HASPOL, + .cmd_priv = PRIV_NETINET_PF, + }, + { + .cmd_num = PFLOWNL_CMD_DEL, + .cmd_name = "DEL", + .cmd_cb = pflow_nl_del, + .cmd_flags = GENL_CMD_CAP_DO | GENL_CMD_CAP_DUMP | GENL_CMD_CAP_HASPOL, + .cmd_priv = PRIV_NETINET_PF, + }, + { + .cmd_num = PFLOWNL_CMD_GET, + .cmd_name = "GET", + .cmd_cb = pflow_nl_get, + .cmd_flags = GENL_CMD_CAP_DUMP | GENL_CMD_CAP_HASPOL, + .cmd_priv = PRIV_NETINET_PF, + }, + { + .cmd_num = PFLOWNL_CMD_SET, + .cmd_name = "SET", + .cmd_cb = pflow_nl_set, + .cmd_flags = GENL_CMD_CAP_DO | GENL_CMD_CAP_DUMP | GENL_CMD_CAP_HASPOL, + .cmd_priv = PRIV_NETINET_PF, + }, +}; + +static const struct nlhdr_parser *all_parsers[] = { + &del_parser, + &get_parser, + &set_parser, +}; + +static int +pflow_init(void) +{ + bool ret; + int family_id __diagused; + + NL_VERIFY_PARSERS(all_parsers); + + family_id = genl_register_family(PFLOWNL_FAMILY_NAME, 0, 2, PFLOWNL_CMD_MAX); + MPASS(family_id != 0); + ret = genl_register_cmds(PFLOWNL_FAMILY_NAME, pflow_cmds, NL_ARRAY_LEN(pflow_cmds)); + + return (ret ? 0 : ENODEV); +} + +static void +pflow_uninit(void) +{ + genl_unregister_family(PFLOWNL_FAMILY_NAME); +} + +static int +pflow_modevent(module_t mod, int type, void *data) +{ + int error = 0; + + switch (type) { + case MOD_LOAD: + error = pflow_init(); + break; + case MOD_UNLOAD: + pflow_uninit(); + break; + default: + error = EINVAL; + break; + } + + return (error); +} + +static moduledata_t pflow_mod = { + pflowname, + pflow_modevent, + 0 +}; + +DECLARE_MODULE(pflow, pflow_mod, SI_SUB_PROTO_FIREWALL, SI_ORDER_ANY); +MODULE_VERSION(pflow, 1); +MODULE_DEPEND(pflow, pf, PF_MODVER, PF_MODVER, PF_MODVER);