git: f92d9b1aad73 - main - pflow: import from OpenBSD

From: Kristof Provost <kp_at_FreeBSD.org>
Date: Tue, 16 Jan 2024 08:51:52 UTC
The branch main has been updated by kp:

URL: https://cgit.FreeBSD.org/src/commit/?id=f92d9b1aad73fc47f8f0b960808ca2c1a938e9e7

commit f92d9b1aad73fc47f8f0b960808ca2c1a938e9e7
Author:     Kristof Provost <kp@FreeBSD.org>
AuthorDate: 2023-11-28 13:00:16 +0000
Commit:     Kristof Provost <kp@FreeBSD.org>
CommitDate: 2024-01-16 08:45:53 +0000

    pflow: import from OpenBSD
    
    pflow is a pseudo device to export flow accounting data over UDP.
    It's compatible with netflow version 5 and IPFIX (10).
    
    The data is extracted from the pf state table. States are exported once
    they are removed.
    
    Reviewed by:    melifaro
    Obtained from:  OpenBSD
    Sponsored by:   Rubicon Communications, LLC ("Netgate")
    Differential Revision:  https://reviews.freebsd.org/D43106
---
 sbin/Makefile                        |    1 +
 sbin/pflowctl/Makefile               |   10 +
 sbin/pflowctl/pflowctl.8             |   91 ++
 sbin/pflowctl/pflowctl.c             |  548 ++++++++++++
 share/man/man4/Makefile              |    2 +
 share/man/man4/pflow.4               |  123 +++
 sys/conf/files                       |    1 +
 sys/modules/Makefile                 |    2 +
 sys/modules/pflow/Makefile           |   16 +
 sys/net/pflow.h                      |  333 +++++++
 sys/net/pfvar.h                      |    4 +-
 sys/netlink/netlink_message_parser.h |    6 +-
 sys/netpfil/pf/pf.c                  |   24 +
 sys/netpfil/pf/pf_ioctl.c            |   14 +-
 sys/netpfil/pf/pflow.c               | 1578 ++++++++++++++++++++++++++++++++++
 15 files changed, 2749 insertions(+), 4 deletions(-)

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 <src.opts.mk>
+
+PACKAGE=pf
+PROG=	pflowctl
+MAN=	pflowctl.8
+
+SRCS = pflowctl.c
+
+.include <bsd.prog.mk>
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 <henning@openbsd.org>
+.\" Copyright (c) 2008 Joerg Goltermann <jg@osn.de>
+.\"
+.\" 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 <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 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 <henning@openbsd.org>
+.\" Copyright (c) 2008 Joerg Goltermann <jg@osn.de>
+.\"
+.\" 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 <bsd.kmod.mk>
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 <henning@openbsd.org>
+ * Copyright (c) 2008 Joerg Goltermann <jg@osn.de>
+ *
+ * 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 <sys/cdefs.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <netinet/in.h>
+
+#ifdef _KERNEL
+#include <sys/param.h>
+#include <sys/lock.h>
+#include <sys/rmlock.h>
+#include <sys/interrupt.h>
+#include <net/if.h>
+#include <net/if_var.h>
+#include <net/if_private.h>
+#include <net/pfvar.h>
+
+#include <netinet/ip.h>
+#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
*** 2076 LINES SKIPPED ***