From nobody Tue Jan 16 08:51:52 2024 X-Original-To: dev-commits-src-all@mlmmj.nyi.freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2610:1c1:1:606c::19:1]) by mlmmj.nyi.freebsd.org (Postfix) with ESMTP id 4TDjRr6BdJz579ft; Tue, 16 Jan 2024 08:51:52 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from mxrelay.nyi.freebsd.org (mxrelay.nyi.freebsd.org [IPv6:2610:1c1:1:606c::19:3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256 client-signature RSA-PSS (4096 bits) client-digest SHA256) (Client CN "mxrelay.nyi.freebsd.org", Issuer "R3" (verified OK)) by mx1.freebsd.org (Postfix) with ESMTPS id 4TDjRr5wjbz4Ptm; Tue, 16 Jan 2024 08:51:52 +0000 (UTC) (envelope-from git@FreeBSD.org) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1705395112; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=on80Lqf2GWyNR1JeEstCOrllspQDKz3KB77SoQ8/NsI=; b=Kqgbbeg+B9+8HOsI72uh2Y7WCgdIXoRdDPCEmVDHAtxjC4LHGLaloSbbKgLJ1RSJ/wtcnz sp0ztj8zMum11dKkmN/vy0sHRQdZEWreELej0i47RNknch/mvzZlpt753aWKT2rMfMWzdf 8lb/l3RNHp7hsTXZqesKi13uUgGCehjqg1km6QVf4IdLRfF6lPuiVhUFuz0la/i0KQqJNP y7q3vYCWrzPWvPE5ST2BBesY0xHE2vk9cBoktG977Gq9KEwJgQZ96KwV1B88hVJSMpPFVK OOFk59o0X8dbjHqsaRsX9/Gc99VRisOCK/Rq660ziX1BPSmvEkdHR+DO51J+LQ== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1705395112; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=on80Lqf2GWyNR1JeEstCOrllspQDKz3KB77SoQ8/NsI=; b=LEOBUbR97uAwnftRKROErEADwyvzfvtHe0GiPTKfAqW2uijfyJ8qbo/ivgMsvFNoj0TmiR OrblhjLFiGUkKH7moweLeTMrAnRwybjlSrbCsvze4kBVyA+qema+jX0wdBBJgX7ZtogEm6 1pQbF4yewfWNZd5YDnHX6Y3Hlgs5v2SJr7zYsg04lqS9yWyvrlc1yj2DejQ5KxDT2+b9IK 4wckSTymWbzq9cSwg3L5bgSfT1tn4rLEVVrx7+DoGtUc9jD3wLHR3NhysZIVKYKdCKzmIt g7cjuK6kkQDt0pkbeJayJtIy2lTxGyGvsZ7owZDlCrbDHcaJjcMuk5ROSO+cOw== ARC-Authentication-Results: i=1; mx1.freebsd.org; none ARC-Seal: i=1; s=dkim; d=freebsd.org; t=1705395112; a=rsa-sha256; cv=none; b=F4YmsYdQGv4tcXTuRH9MELaUmWSs2F4S/VeLYobZEkDWThJE5CGhbsd6c4k1OzayCs0ypS Jyk1cXd7zZeM+rYumOFym+U/JfuF92Jry7B8+hiuOEmAlIX0FwSBbTaOOQ2pV2aDWJgVNg usiNAy4b5j9DoPm2BAaO+bn/kdmRp2Sgrk+Rqh93LDS86fkoI5xdkTT569dAeH2t0s4wOh Jl5emtmKoIzF/46dpfxmSEdrYVN6V2+lOfSRK13Q6Dhps4tzv0NDryGCtxZRQBEjBwxKIn 2IjiuKWxIcv02oAuxdkfn56vMek4LWuu7ZXp1DRjsZsOVUtOQe78jk07CXd+DQ== Received: from gitrepo.freebsd.org (gitrepo.freebsd.org [IPv6:2610:1c1:1:6068::e6a:5]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (Client did not present a certificate) by mxrelay.nyi.freebsd.org (Postfix) with ESMTPS id 4TDjRr4zYZzZLZ; Tue, 16 Jan 2024 08:51:52 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from gitrepo.freebsd.org ([127.0.1.44]) by gitrepo.freebsd.org (8.17.1/8.17.1) with ESMTP id 40G8pqIZ075681; Tue, 16 Jan 2024 08:51:52 GMT (envelope-from git@gitrepo.freebsd.org) Received: (from git@localhost) by gitrepo.freebsd.org (8.17.1/8.17.1/Submit) id 40G8pqXN075678; Tue, 16 Jan 2024 08:51:52 GMT (envelope-from git) Date: Tue, 16 Jan 2024 08:51:52 GMT Message-Id: <202401160851.40G8pqXN075678@gitrepo.freebsd.org> To: src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org From: Kristof Provost Subject: git: f92d9b1aad73 - main - pflow: import from OpenBSD List-Id: Commit messages for all branches of the src repository List-Archive: https://lists.freebsd.org/archives/dev-commits-src-all List-Help: List-Post: List-Subscribe: List-Unsubscribe: Sender: owner-dev-commits-src-all@freebsd.org X-BeenThere: dev-commits-src-all@freebsd.org MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit X-Git-Committer: kp X-Git-Repository: src X-Git-Refname: refs/heads/main X-Git-Reftype: branch X-Git-Commit: f92d9b1aad73fc47f8f0b960808ca2c1a938e9e7 Auto-Submitted: auto-generated The branch main has been updated by kp: URL: https://cgit.FreeBSD.org/src/commit/?id=f92d9b1aad73fc47f8f0b960808ca2c1a938e9e7 commit f92d9b1aad73fc47f8f0b960808ca2c1a938e9e7 Author: Kristof Provost AuthorDate: 2023-11-28 13:00:16 +0000 Commit: Kristof Provost CommitDate: 2024-01-16 08:45:53 +0000 pflow: import from OpenBSD pflow is a pseudo device to export flow accounting data over UDP. It's compatible with netflow version 5 and IPFIX (10). The data is extracted from the pf state table. States are exported once they are removed. Reviewed by: melifaro Obtained from: OpenBSD Sponsored by: Rubicon Communications, LLC ("Netgate") Differential Revision: https://reviews.freebsd.org/D43106 --- sbin/Makefile | 1 + sbin/pflowctl/Makefile | 10 + sbin/pflowctl/pflowctl.8 | 91 ++ sbin/pflowctl/pflowctl.c | 548 ++++++++++++ share/man/man4/Makefile | 2 + share/man/man4/pflow.4 | 123 +++ sys/conf/files | 1 + sys/modules/Makefile | 2 + sys/modules/pflow/Makefile | 16 + sys/net/pflow.h | 333 +++++++ sys/net/pfvar.h | 4 +- sys/netlink/netlink_message_parser.h | 6 +- sys/netpfil/pf/pf.c | 24 + sys/netpfil/pf/pf_ioctl.c | 14 +- sys/netpfil/pf/pflow.c | 1578 ++++++++++++++++++++++++++++++++++ 15 files changed, 2749 insertions(+), 4 deletions(-) diff --git a/sbin/Makefile b/sbin/Makefile index 0c648f29badb..342f385f090b 100644 --- a/sbin/Makefile +++ b/sbin/Makefile @@ -81,6 +81,7 @@ SUBDIR.${MK_NVME}+= nvmecontrol SUBDIR.${MK_OPENSSL}+= decryptcore SUBDIR.${MK_PF}+= pfctl SUBDIR.${MK_PF}+= pflogd +SUBDIR.${MK_PF}+= pflowctl SUBDIR.${MK_QUOTAS}+= quotacheck SUBDIR.${MK_ROUTED}+= routed SUBDIR.${MK_VERIEXEC}+= veriexec diff --git a/sbin/pflowctl/Makefile b/sbin/pflowctl/Makefile new file mode 100644 index 000000000000..35cf8cc020f4 --- /dev/null +++ b/sbin/pflowctl/Makefile @@ -0,0 +1,10 @@ + +.include + +PACKAGE=pf +PROG= pflowctl +MAN= pflowctl.8 + +SRCS = pflowctl.c + +.include diff --git a/sbin/pflowctl/pflowctl.8 b/sbin/pflowctl/pflowctl.8 new file mode 100644 index 000000000000..e2e19b7ddfa0 --- /dev/null +++ b/sbin/pflowctl/pflowctl.8 @@ -0,0 +1,91 @@ +.\" $OpenBSD: pflow.4,v 1.19 2014/03/29 11:26:03 florian Exp $ +.\" +.\" Copyright (c) 2008 Henning Brauer +.\" Copyright (c) 2008 Joerg Goltermann +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.Dd $Mdocdate: January 08 2024 $ +.Dt PFLOWCTL 8 +.Os +.Sh NAME +.Nm pflowctl +.Nd control pflow data export +.Sh SYNOPSIS +.Nm pflowctl +.Bk -words +.Op Fl lc +.Op Fl d Ar id +.Op Fl s Ar id ... +.Ek +.Sh DESCRIPTION +The +.Nm +utility creates, configures and deletes netflow accounting data export using the +.Xr pflow 4 +subsystem. +.Pp +The +.Nm +utility provides several commands. +The options are as follows: +.Bl -tag -width Ds +.It Fl c +Create a new +.Xr pflow 4 +exporter. +.It Fl d Ar id +Remove an existing +.Xr pflow 4 +exporter. +The +.Ar id +may be either numeric or the full pflowX name. +.It Fl l +List all existing +.Xr pflow 4 +exporters. +.It Fl s Ar id ... +Configure an existing +.Xr pflow 4 +exporter. +This takes the following keywords: +.Pp +.Bl -tag -width xxxxxxxxxxxx -compact +.It Cm src +set the source IP address (and optionally port). +.It Cm dst +set the destination IP address (and optionally port). +.It Cm proto +set the protocol version. +Valid values are 5 and 10. +.El +.Pp +Multiple keywords may be passed in the same command invocation. +.Pp +For example, the following command sets 10.0.0.1 as the source +and 10.0.0.2:1234 as destination: +.Bd -literal -offset indent +# pflowctl -s pflow0 src 10.0.0.1 dst 10.0.0.2:1234 +.Ed +.Sh SEE ALSO +.Xr netintro 4 , +.Xr pf 4 , +.Xr pflow 4 , +.Xr udp 4 , +.Xr pf.conf 5 +.Sh HISTORY +The +.Nm +command first appeared in +.Fx 15.0 . diff --git a/sbin/pflowctl/pflowctl.c b/sbin/pflowctl/pflowctl.c new file mode 100644 index 000000000000..046919867ff2 --- /dev/null +++ b/sbin/pflowctl/pflowctl.c @@ -0,0 +1,548 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2023 Rubicon Communications, LLC (Netgate) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +static int get(int id); + +static void +usage(void) +{ + extern char *__progname; + + fprintf(stderr, +"usage: %s [-la] [-d id]\n", + __progname); + + exit(1); +} + +static int +pflow_to_id(const char *name) +{ + int ret, id; + + ret = sscanf(name, "pflow%d", &id); + if (ret == 1) + return (id); + + ret = sscanf(name, "%d", &id); + if (ret == 1) + return (id); + + return (-1); +} + +struct pflowctl_list { + int id; +}; +#define _IN(_field) offsetof(struct genlmsghdr, _field) +#define _OUT(_field) offsetof(struct pflowctl_list, _field) +static struct snl_attr_parser ap_list[] = { + { .type = PFLOWNL_L_ID, .off = _OUT(id), .cb = snl_attr_get_int32 }, +}; +static struct snl_field_parser fp_list[] = {}; +#undef _IN +#undef _OUT +SNL_DECLARE_PARSER(list_parser, struct genlmsghdr, fp_list, ap_list); + +static int +list(void) +{ + struct snl_state ss = {}; + struct snl_errmsg_data e = {}; + struct pflowctl_list l = {}; + struct snl_writer nw; + struct nlmsghdr *hdr; + uint32_t seq_id; + int family_id; + + snl_init(&ss, NETLINK_GENERIC); + family_id = snl_get_genl_family(&ss, PFLOWNL_FAMILY_NAME); + if (family_id == 0) + errx(1, "pflow.ko is not loaded."); + + snl_init_writer(&ss, &nw); + hdr = snl_create_genl_msg_request(&nw, family_id, PFLOWNL_CMD_LIST); + + hdr = snl_finalize_msg(&nw); + if (hdr == NULL) + return (ENOMEM); + seq_id = hdr->nlmsg_seq; + + snl_send_message(&ss, hdr); + + while ((hdr = snl_read_reply_multi(&ss, seq_id, &e)) != NULL) { + if (! snl_parse_nlmsg(&ss, hdr, &list_parser, &l)) + continue; + + get(l.id); + } + + if (e.error) + errc(1, e.error, "failed to list"); + + return (0); +} + +struct pflowctl_create { + int id; +}; +#define _IN(_field) offsetof(struct genlmsghsdr, _field) +#define _OUT(_field) offsetof(struct pflowctl_create, _field) +static struct snl_attr_parser ap_create[] = { + { .type = PFLOWNL_CREATE_ID, .off = _OUT(id), .cb = snl_attr_get_int32 }, +}; +static struct snl_field_parser pf_create[] = {}; +#undef _IN +#undef _OUT +SNL_DECLARE_PARSER(create_parser, struct genlmsghdr, pf_create, ap_create); + +static int +create(void) +{ + struct snl_state ss = {}; + struct snl_errmsg_data e = {}; + struct pflowctl_create c = {}; + struct snl_writer nw; + struct nlmsghdr *hdr; + uint32_t seq_id; + int family_id; + + snl_init(&ss, NETLINK_GENERIC); + family_id = snl_get_genl_family(&ss, PFLOWNL_FAMILY_NAME); + if (family_id == 0) + errx(1, "pflow.ko is not loaded."); + + snl_init_writer(&ss, &nw); + hdr = snl_create_genl_msg_request(&nw, family_id, PFLOWNL_CMD_CREATE); + + hdr = snl_finalize_msg(&nw); + if (hdr == NULL) + return (ENOMEM); + seq_id = hdr->nlmsg_seq; + + snl_send_message(&ss, hdr); + + while ((hdr = snl_read_reply_multi(&ss, seq_id, &e)) != NULL) { + if (! snl_parse_nlmsg(&ss, hdr, &create_parser, &c)) + continue; + + printf("pflow%d\n", c.id); + } + + if (e.error) + errc(1, e.error, "failed to create"); + + return (0); +} + +static int +del(char *idstr) +{ + struct snl_state ss = {}; + struct snl_errmsg_data e = {}; + struct snl_writer nw; + struct nlmsghdr *hdr; + int family_id; + int id; + + id = pflow_to_id(idstr); + if (id < 0) + return (EINVAL); + + snl_init(&ss, NETLINK_GENERIC); + family_id = snl_get_genl_family(&ss, PFLOWNL_FAMILY_NAME); + if (family_id == 0) + errx(1, "pflow.ko is not loaded."); + + snl_init_writer(&ss, &nw); + hdr = snl_create_genl_msg_request(&nw, family_id, PFLOWNL_CMD_DEL); + + snl_add_msg_attr_s32(&nw, PFLOWNL_DEL_ID, id); + + hdr = snl_finalize_msg(&nw); + if (hdr == NULL) + return (ENOMEM); + + snl_send_message(&ss, hdr); + snl_read_reply_code(&ss, hdr->nlmsg_seq, &e); + + if (e.error) + errc(1, e.error, "failed to delete"); + + return (0); +} + +struct pflowctl_sockaddr { + union { + struct sockaddr_in in; + struct sockaddr_in6 in6; + struct sockaddr_storage storage; + }; +}; +static bool +pflowctl_post_sockaddr(struct snl_state* ss __unused, void *target) +{ + struct pflowctl_sockaddr *s = (struct pflowctl_sockaddr *)target; + + if (s->storage.ss_family == AF_INET) + s->storage.ss_len = sizeof(struct sockaddr_in); + else if (s->storage.ss_family == AF_INET6) + s->storage.ss_len = sizeof(struct sockaddr_in6); + else + return (false); + + return (true); +} +#define _OUT(_field) offsetof(struct pflowctl_sockaddr, _field) +static struct snl_attr_parser nla_p_sockaddr[] = { + { .type = PFLOWNL_ADDR_FAMILY, .off = _OUT(in.sin_family), .cb = snl_attr_get_uint8 }, + { .type = PFLOWNL_ADDR_PORT, .off = _OUT(in.sin_port), .cb = snl_attr_get_uint16 }, + { .type = PFLOWNL_ADDR_IP, .off = _OUT(in.sin_addr), .cb = snl_attr_get_in_addr }, + { .type = PFLOWNL_ADDR_IP6, .off = _OUT(in6.sin6_addr), .cb = snl_attr_get_in6_addr }, +}; +SNL_DECLARE_ATTR_PARSER_EXT(sockaddr_parser, 0, nla_p_sockaddr, pflowctl_post_sockaddr); +#undef _OUT + +struct pflowctl_get { + int id; + int version; + struct pflowctl_sockaddr src; + struct pflowctl_sockaddr dst; +}; +#define _IN(_field) offsetof(struct genlmsghdr, _field) +#define _OUT(_field) offsetof(struct pflowctl_get, _field) +static struct snl_attr_parser ap_get[] = { + { .type = PFLOWNL_GET_ID, .off = _OUT(id), .cb = snl_attr_get_int32 }, + { .type = PFLOWNL_GET_VERSION, .off = _OUT(version), .cb = snl_attr_get_int16 }, + { .type = PFLOWNL_GET_SRC, .off = _OUT(src), .arg = &sockaddr_parser, .cb = snl_attr_get_nested }, + { .type = PFLOWNL_GET_DST, .off = _OUT(dst), .arg = &sockaddr_parser, .cb = snl_attr_get_nested }, +}; +static struct snl_field_parser fp_get[] = {}; +#undef _IN +#undef _OUT +SNL_DECLARE_PARSER(get_parser, struct genlmsghdr, fp_get, ap_get); + +static void +print_sockaddr(const char *prefix, const struct sockaddr_storage *s) +{ + char buf[INET6_ADDRSTRLEN]; + int error; + + if (s->ss_family != AF_INET && s->ss_family != AF_INET6) + return; + + if (s->ss_family == AF_INET || + s->ss_family == AF_INET6) { + error = getnameinfo((const struct sockaddr *)s, + s->ss_len, buf, sizeof(buf), NULL, 0, + NI_NUMERICHOST); + if (error) + err(1, "sender: %s", gai_strerror(error)); + } + + printf("%s", prefix); + switch (s->ss_family) { + case AF_INET: { + const struct sockaddr_in *sin = (const struct sockaddr_in *)s; + if (sin->sin_addr.s_addr != INADDR_ANY) { + printf("%s", buf); + if (sin->sin_port != 0) + printf(":%u", ntohs(sin->sin_port)); + } + break; + } + case AF_INET6: { + const struct sockaddr_in6 *sin6 = (const struct sockaddr_in6 *)s; + if (!IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr)) { + printf("[%s]", buf); + if (sin6->sin6_port != 0) + printf(":%u", ntohs(sin6->sin6_port)); + } + break; + } + } +} + +static int +get(int id) +{ + struct snl_state ss = {}; + struct snl_errmsg_data e = {}; + struct pflowctl_get g = {}; + struct snl_writer nw; + struct nlmsghdr *hdr; + uint32_t seq_id; + int family_id; + + snl_init(&ss, NETLINK_GENERIC); + family_id = snl_get_genl_family(&ss, PFLOWNL_FAMILY_NAME); + if (family_id == 0) + errx(1, "pflow.ko is not loaded."); + + snl_init_writer(&ss, &nw); + hdr = snl_create_genl_msg_request(&nw, family_id, PFLOWNL_CMD_GET); + snl_add_msg_attr_s32(&nw, PFLOWNL_GET_ID, id); + + hdr = snl_finalize_msg(&nw); + if (hdr == NULL) + return (ENOMEM); + seq_id = hdr->nlmsg_seq; + + snl_send_message(&ss, hdr); + + while ((hdr = snl_read_reply_multi(&ss, seq_id, &e)) != NULL) { + if (! snl_parse_nlmsg(&ss, hdr, &get_parser, &g)) + continue; + + printf("pflow%d: version %d", g.id, g.version); + print_sockaddr(" src ", &g.src.storage); + print_sockaddr(" dst ", &g.dst.storage); + printf("\n"); + } + + if (e.error) + errc(1, e.error, "failed to get"); + + return (0); +} + +struct pflowctl_set { + int id; + uint16_t version; + struct sockaddr_storage src; + struct sockaddr_storage dst; +}; +static inline bool +snl_add_msg_attr_sockaddr(struct snl_writer *nw, int attrtype, struct sockaddr_storage *s) +{ + int off = snl_add_msg_attr_nested(nw, attrtype); + + snl_add_msg_attr_u8(nw, PFLOWNL_ADDR_FAMILY, s->ss_family); + + switch (s->ss_family) { + case AF_INET: { + const struct sockaddr_in *in = (const struct sockaddr_in *)s; + snl_add_msg_attr_u16(nw, PFLOWNL_ADDR_PORT, in->sin_port); + snl_add_msg_attr_ip4(nw, PFLOWNL_ADDR_IP, &in->sin_addr); + break; + } + case AF_INET6: { + const struct sockaddr_in6 *in6 = (const struct sockaddr_in6 *)s; + snl_add_msg_attr_u16(nw, PFLOWNL_ADDR_PORT, in6->sin6_port); + snl_add_msg_attr_ip6(nw, PFLOWNL_ADDR_IP6, &in6->sin6_addr); + break; + } + default: + return (false); + } + snl_end_attr_nested(nw, off); + + return (true); +} + +static int +do_set(struct pflowctl_set *s) +{ + struct snl_state ss = {}; + struct snl_errmsg_data e = {}; + struct snl_writer nw; + struct nlmsghdr *hdr; + int family_id; + + snl_init(&ss, NETLINK_GENERIC); + family_id = snl_get_genl_family(&ss, PFLOWNL_FAMILY_NAME); + if (family_id == 0) + errx(1, "pflow.ko is not loaded."); + + snl_init_writer(&ss, &nw); + snl_create_genl_msg_request(&nw, family_id, PFLOWNL_CMD_SET); + + snl_add_msg_attr_s32(&nw, PFLOWNL_SET_ID, s->id); + if (s->version != 0) + snl_add_msg_attr_u16(&nw, PFLOWNL_SET_VERSION, s->version); + if (s->src.ss_len != 0) + snl_add_msg_attr_sockaddr(&nw, PFLOWNL_SET_SRC, &s->src); + if (s->dst.ss_len != 0) + snl_add_msg_attr_sockaddr(&nw, PFLOWNL_SET_DST, &s->dst); + + hdr = snl_finalize_msg(&nw); + if (hdr == NULL) + return (1); + + snl_send_message(&ss, hdr); + snl_read_reply_code(&ss, hdr->nlmsg_seq, &e); + + if (e.error) + errc(1, e.error, "failed to set"); + + return (0); +} + +static void +pflowctl_addr(const char *val, struct sockaddr_storage *ss) +{ + struct addrinfo *res0; + int error; + bool flag; + char *ip, *port; + char buf[sysconf(_SC_HOST_NAME_MAX) + 1 + sizeof(":65535")]; + struct addrinfo hints = { + .ai_family = AF_UNSPEC, + .ai_socktype = SOCK_DGRAM, /*dummy*/ + .ai_flags = AI_NUMERICHOST, + }; + + if (strlcpy(buf, val, sizeof(buf)) >= sizeof(buf)) + errx(1, "%s bad value", val); + + port = NULL; + flag = *buf == '['; + + for (char *cp = buf; *cp; ++cp) { + if (*cp == ']' && *(cp + 1) == ':' && flag) { + *cp = '\0'; + *(cp + 1) = '\0'; + port = cp + 2; + break; + } + if (*cp == ']' && *(cp + 1) == '\0' && flag) { + *cp = '\0'; + port = NULL; + break; + } + if (*cp == ':' && !flag) { + *cp = '\0'; + port = cp + 1; + break; + } + } + + ip = buf; + if (flag) + ip++; + + if ((error = getaddrinfo(ip, port, &hints, &res0)) != 0) + errx(1, "error in parsing address string: %s", + gai_strerror(error)); + + memcpy(ss, res0->ai_addr, res0->ai_addr->sa_len); + freeaddrinfo(res0); +} + +static int +set(char *idstr, int argc, char *argv[]) +{ + struct pflowctl_set s = {}; + + s.id = pflow_to_id(idstr); + if (s.id < 0) + return (EINVAL); + + while (argc > 0) { + if (strcmp(argv[0], "src") == 0) { + if (argc < 2) + usage(); + + pflowctl_addr(argv[1], &s.src); + + argc -= 2; + argv += 2; + } else if (strcmp(argv[0], "dst") == 0) { + if (argc < 2) + usage(); + + pflowctl_addr(argv[1], &s.dst); + + argc -= 2; + argv += 2; + } else if (strcmp(argv[0], "proto") == 0) { + if (argc < 2) + usage(); + + s.version = strtol(argv[1], NULL, 10); + + argc -= 2; + argv += 2; + } else { + usage(); + } + } + + return (do_set(&s)); +} + +static const struct snl_hdr_parser *all_parsers[] = { + &list_parser, + &get_parser, +}; + +int +main(int argc, char *argv[]) +{ + int ch; + + SNL_VERIFY_PARSERS(all_parsers); + + if (argc < 2) + usage(); + + while ((ch = getopt(argc, argv, + "lcd:s:")) != -1) { + switch (ch) { + case 'l': + return (list()); + case 'c': + return (create()); + case 'd': + return (del(optarg)); + case 's': + return (set(optarg, argc - optind, argv + optind)); + } + } + + return (0); +} diff --git a/share/man/man4/Makefile b/share/man/man4/Makefile index 978ec6887f85..ab951b107f27 100644 --- a/share/man/man4/Makefile +++ b/share/man/man4/Makefile @@ -433,6 +433,7 @@ MAN= aac.4 \ pcm.4 \ ${_pf.4} \ ${_pflog.4} \ + ${_pflow.4} \ ${_pfsync.4} \ pim.4 \ pms.4 \ @@ -968,6 +969,7 @@ _atf_test_case.4= atf-test-case.4 .if ${MK_PF} != "no" _pf.4= pf.4 _pflog.4= pflog.4 +_pflow.4= pflow.4 _pfsync.4= pfsync.4 .endif diff --git a/share/man/man4/pflow.4 b/share/man/man4/pflow.4 new file mode 100644 index 000000000000..320a7527dc2d --- /dev/null +++ b/share/man/man4/pflow.4 @@ -0,0 +1,123 @@ +.\" $OpenBSD: pflow.4,v 1.19 2014/03/29 11:26:03 florian Exp $ +.\" +.\" Copyright (c) 2008 Henning Brauer +.\" Copyright (c) 2008 Joerg Goltermann +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.Dd $Mdocdate: January 08 2024 $ +.Dt PFLOW 4 +.Os +.Sh NAME +.Nm pflow +.Nd kernel interface for pflow data export +.Sh SYNOPSIS +.Cd "pseudo-device pflow" +.Sh DESCRIPTION +The +.Nm +subsystem exports +.Nm +accounting data from the kernel using +.Xr udp 4 +packets. +.Nm +is compatible with netflow version 5 and IPFIX (10). +The data is extracted from the +.Xr pf 4 +state table. +.Pp +Multiple +.Nm +interfaces can be created at runtime using the +.Ic pflowctl Ns Ar N Ic -c +command. +Each interface must be configured with a flow receiver IP address +and a flow receiver port number. +.Pp +Only states created by a rule marked with the +.Ar pflow +keyword are exported by +.Nm . +.Pp +.Nm +will attempt to export multiple +.Nm +records in one +UDP packet, but will not hold a record for longer than 30 seconds. +.Pp +Each packet seen on this interface has one header and a variable number of +flows. +The header indicates the version of the protocol, number of +flows in the packet, a unique sequence number, system time, and an engine +ID and type. +Header and flow structs are defined in +.In net/pflow.h . +.Pp +The +.Nm +source and destination addresses are controlled by +.Xr pflowctl 8 . +.Cm src +is the sender IP address of the UDP packet which can be used +to identify the source of the data on the +.Nm +collector. +.Cm dst +defines the collector IP address and the port. +The +.Cm dst +IP address and port must be defined to enable the export of flows. +.Pp +For example, the following command sets 10.0.0.1 as the source +and 10.0.0.2:1234 as destination: +.Bd -literal -offset indent +# pflowctl -s pflow0 src 10.0.0.1 dst 10.0.0.2:1234 +.Ed +.Pp +The protocol is set to IPFIX with the following command: +.Bd -literal -offset indent +# pflowctl -s pflow0 proto 10 +.Ed +.Sh SEE ALSO +.Xr netintro 4 , +.Xr pf 4 , +.Xr udp 4 , +.Xr pf.conf 5 , +.Xr pflowctl 8 , +.Xr tcpdump 8 +.Sh STANDARDS +.Rs +.%A B. Claise +.%D January 2008 +.%R RFC 5101 +.%T "Specification of the IP Flow Information Export (IPFIX) Protocol for the Exchange of IP Traffic Flow Information" +.Re +.Sh HISTORY +The +.Nm +device first appeared in +.Ox 4.5 +and was imported into +FreeBSD 15.0 . +.Sh BUGS +A state created by +.Xr pfsync 4 +can have a creation or expiration time before the machine came up. +In this case, +.Nm +pretends such flows were created or expired when the machine came up. +.Pp +The IPFIX implementation is incomplete: +The required transport protocol SCTP is not supported. +Transport over TCP and DTLS protected flow export is also not supported. diff --git a/sys/conf/files b/sys/conf/files index 484ec90beb00..9f0b3cf3831a 100644 --- a/sys/conf/files +++ b/sys/conf/files @@ -4511,6 +4511,7 @@ netpfil/pf/pf_osfp.c optional pf inet netpfil/pf/pf_ruleset.c optional pf inet netpfil/pf/pf_syncookies.c optional pf inet netpfil/pf/pf_table.c optional pf inet +netpfil/pf/pflow.c optional pflow pf inet netpfil/pf/pfsync_nv.c optional pfsync pf inet netpfil/pf/in4_cksum.c optional pf inet netsmb/smb_conn.c optional netsmb diff --git a/sys/modules/Makefile b/sys/modules/Makefile index c14933eebda4..606ab4cb0536 100644 --- a/sys/modules/Makefile +++ b/sys/modules/Makefile @@ -306,6 +306,7 @@ SUBDIR= \ ${_pcfclock} \ ${_pf} \ ${_pflog} \ + ${_pflow} \ ${_pfsync} \ plip \ ${_pms} \ @@ -611,6 +612,7 @@ _netgraph= netgraph ${MK_INET6_SUPPORT} != "no")) || defined(ALL_MODULES) _pf= pf _pflog= pflog +_pflow= pflow .if ${MK_INET_SUPPORT} != "no" _pfsync= pfsync .endif diff --git a/sys/modules/pflow/Makefile b/sys/modules/pflow/Makefile new file mode 100644 index 000000000000..674ca8970607 --- /dev/null +++ b/sys/modules/pflow/Makefile @@ -0,0 +1,16 @@ +.PATH: ${SRCTOP}/sys/netpfil/pf + +KMOD= pflow +SRCS= pflow.c \ + opt_pf.h opt_inet.h opt_inet6.h opt_global.h +SRCS+= bus_if.h device_if.h + +.if !defined(KERNBUILDDIR) +.if defined(VIMAGE) +opt_global.h: + echo "#define VIMAGE 1" >> ${.TARGET} +CFLAGS+= -include opt_global.h +.endif +.endif + +.include diff --git a/sys/net/pflow.h b/sys/net/pflow.h new file mode 100644 index 000000000000..fcf24e091b57 --- /dev/null +++ b/sys/net/pflow.h @@ -0,0 +1,333 @@ +/* $OpenBSD: if_pflow.h,v 1.19 2022/11/23 15:12:27 mvs Exp $ */ + +/* + * Copyright (c) 2008 Henning Brauer + * Copyright (c) 2008 Joerg Goltermann + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER IN + * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT + * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _NET_IF_PFLOW_H_ +#define _NET_IF_PFLOW_H_ + +#include +#include +#include + +#include + +#ifdef _KERNEL +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#endif + +#define PFLOW_ID_LEN sizeof(u_int64_t) + +#define PFLOW_MAXFLOWS 30 +#define PFLOW_ENGINE_TYPE 42 +#define PFLOW_ENGINE_ID 42 +#define PFLOW_MAXBYTES 0xffffffff +#define PFLOW_TIMEOUT 30 +#define PFLOW_TMPL_TIMEOUT 30 /* rfc 5101 10.3.6 (p.40) recommends 600 */ + +#define PFLOW_IPFIX_TMPL_SET_ID 2 + +/* RFC 5102 Information Element Identifiers */ + +#define PFIX_IE_octetDeltaCount 1 +#define PFIX_IE_packetDeltaCount 2 +#define PFIX_IE_protocolIdentifier 4 +#define PFIX_IE_ipClassOfService 5 +#define PFIX_IE_sourceTransportPort 7 *** 2076 LINES SKIPPED ***