Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F108595288
D43106.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
78 KB
Referenced Files
None
Subscribers
None
D43106.diff
View Options
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
Details
Attached
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)
Attached To
Mode
D43106: pflow: import from OpenBSD
Attached
Detach File
Event Timeline
Log In to Comment