Page MenuHomeFreeBSD

D43106.diff
No OneTemporary

D43106.diff

diff --git a/sbin/Makefile b/sbin/Makefile
--- a/sbin/Makefile
+++ b/sbin/Makefile
@@ -81,6 +81,7 @@
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
--- /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
--- /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
--- /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
--- a/share/man/man4/Makefile
+++ b/share/man/man4/Makefile
@@ -433,6 +433,7 @@
pcm.4 \
${_pf.4} \
${_pflog.4} \
+ ${_pflow.4} \
${_pfsync.4} \
pim.4 \
pms.4 \
@@ -968,6 +969,7 @@
.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
--- /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
--- a/sys/conf/files
+++ b/sys/conf/files
@@ -4511,6 +4511,7 @@
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
--- a/sys/modules/Makefile
+++ b/sys/modules/Makefile
@@ -306,6 +306,7 @@
${_pcfclock} \
${_pf} \
${_pflog} \
+ ${_pflow} \
${_pfsync} \
plip \
${_pms} \
@@ -611,6 +612,7 @@
${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
--- /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
--- /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
+#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
--- a/sys/net/pfvar.h
+++ b/sys/net/pfvar.h
@@ -1067,12 +1067,14 @@
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
--- a/sys/netlink/netlink_message_parser.h
+++ b/sys/netlink/netlink_message_parser.h
@@ -150,12 +150,16 @@
.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
--- a/sys/netpfil/pf/pf.c
+++ b/sys/netpfil/pf/pf.c
@@ -1226,6 +1226,7 @@
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 @@
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 @@
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 @@
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 @@
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 @@
}
CURVNET_RESTORE();
}
+ NET_EPOCH_EXIT(et);
VNET_LIST_RUNLOCK();
}
@@ -2097,6 +2106,7 @@
{
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 @@
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 @@
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
--- a/sys/netpfil/pf/pf_ioctl.c
+++ b/sys/netpfil/pf/pf_ioctl.c
@@ -5784,9 +5784,11 @@
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 @@
}
PF_HASHROW_UNLOCK(ih);
}
+ NET_EPOCH_EXIT(et);
}
static int
@@ -5943,6 +5946,8 @@
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 @@
{
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 @@
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 @@
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 @@
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 @@
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
--- /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 <florian@narrans.de>
+ * Copyright (c) 2011 Sebastian Benoit <benoit-lists@fb12.de>
+ * 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.
+ */
+
+#include <sys/cdefs.h>
+#include <sys/param.h>
+#include <sys/bus.h>
+#include <sys/callout.h>
+#include <sys/endian.h>
+#include <sys/interrupt.h>
+#include <sys/kernel.h>
+#include <sys/malloc.h>
+#include <sys/module.h>
+#include <sys/mbuf.h>
+#include <sys/socket.h>
+#include <sys/socketvar.h>
+#include <sys/sockio.h>
+#include <sys/sysctl.h>
+#include <sys/systm.h>
+#include <sys/priv.h>
+
+#include <net/if.h>
+#include <net/if_types.h>
+#include <net/bpf.h>
+#include <net/route.h>
+#include <netinet/in.h>
+#include <netinet/if_ether.h>
+#include <netinet/tcp.h>
+
+#include <netinet/ip.h>
+#include <netinet/ip_icmp.h>
+#include <netinet/ip_var.h>
+#include <netinet/udp.h>
+#include <netinet/udp_var.h>
+#include <netinet/in_pcb.h>
+
+#include <netlink/netlink.h>
+#include <netlink/netlink_ctl.h>
+#include <netlink/netlink_generic.h>
+#include <netlink/netlink_message_writer.h>
+
+#include <net/pfvar.h>
+#include <net/pflow.h>
+#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);

File Metadata

Mime Type
text/plain
Expires
Mon, Jan 27, 6:25 PM (5 h, 52 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
16204310
Default Alt Text
D43106.diff (78 KB)

Event Timeline