diff --git a/sbin/pflowctl/pflowctl.8 b/sbin/pflowctl/pflowctl.8 index e2e19b7ddfa0..5140d5febb84 100644 --- a/sbin/pflowctl/pflowctl.8 +++ b/sbin/pflowctl/pflowctl.8 @@ -1,91 +1,93 @@ .\" $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. +.It Cm domain +set the observation domain. 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 index 046919867ff2..7be85d8b6ad4 100644 --- a/sbin/pflowctl/pflowctl.c +++ b/sbin/pflowctl/pflowctl.c @@ -1,548 +1,561 @@ /*- * 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; + uint32_t obs_dom; }; #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 }, + { .type = PFLOWNL_GET_OBSERVATION_DOMAIN, .off = _OUT(obs_dom), .cb = snl_attr_get_uint32 }, }; 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); + printf("pflow%d: version %d domain %d", g.id, g.version, g.obs_dom); 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; + uint32_t obs_dom; }; 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); + if (s->obs_dom != 0) + snl_add_msg_attr_u32(&nw, PFLOWNL_SET_OBSERVATION_DOMAIN, s->obs_dom); 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 if (strcmp(argv[0], "domain") == 0) { + if (argc < 2) + usage(); + + s.obs_dom = 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/sys/net/pflow.h b/sys/net/pflow.h index 2b7dfe24b5fc..4a63f7640629 100644 --- a/sys/net/pflow.h +++ b/sys/net/pflow.h @@ -1,332 +1,335 @@ /* $OpenBSD: if_pflow.h,v 1.19 2022/11/23 15:12:27 mvs Exp $ */ /* * Copyright (c) 2008 Henning Brauer * Copyright (c) 2008 Joerg Goltermann * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER IN * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef _NET_IF_PFLOW_H_ #define _NET_IF_PFLOW_H_ #include #include #include #include #ifdef _KERNEL #include #include #include #include #include #include #include #include #include #endif #define PFLOW_ID_LEN sizeof(u_int64_t) #define PFLOW_MAXFLOWS 30 #define PFLOW_ENGINE_TYPE 42 #define PFLOW_ENGINE_ID 42 #define PFLOW_MAXBYTES 0xffffffff #define PFLOW_TIMEOUT 30 #define PFLOW_TMPL_TIMEOUT 30 /* rfc 5101 10.3.6 (p.40) recommends 600 */ #define PFLOW_IPFIX_TMPL_SET_ID 2 /* RFC 5102 Information Element Identifiers */ #define PFIX_IE_octetDeltaCount 1 #define PFIX_IE_packetDeltaCount 2 #define PFIX_IE_protocolIdentifier 4 #define PFIX_IE_ipClassOfService 5 #define PFIX_IE_sourceTransportPort 7 #define PFIX_IE_sourceIPv4Address 8 #define PFIX_IE_ingressInterface 10 #define PFIX_IE_destinationTransportPort 11 #define PFIX_IE_destinationIPv4Address 12 #define PFIX_IE_egressInterface 14 #define PFIX_IE_flowEndSysUpTime 21 #define PFIX_IE_flowStartSysUpTime 22 #define PFIX_IE_sourceIPv6Address 27 #define PFIX_IE_destinationIPv6Address 28 #define PFIX_IE_flowStartMilliseconds 152 #define PFIX_IE_flowEndMilliseconds 153 struct pflow_flow { u_int32_t src_ip; u_int32_t dest_ip; u_int32_t nexthop_ip; u_int16_t if_index_in; u_int16_t if_index_out; u_int32_t flow_packets; u_int32_t flow_octets; u_int32_t flow_start; u_int32_t flow_finish; u_int16_t src_port; u_int16_t dest_port; u_int8_t pad1; u_int8_t tcp_flags; u_int8_t protocol; u_int8_t tos; u_int16_t src_as; u_int16_t dest_as; u_int8_t src_mask; u_int8_t dest_mask; u_int16_t pad2; } __packed; struct pflow_set_header { u_int16_t set_id; u_int16_t set_length; /* total length of the set, in octets, including the set header */ } __packed; #define PFLOW_SET_HDRLEN sizeof(struct pflow_set_header) struct pflow_tmpl_hdr { u_int16_t tmpl_id; u_int16_t field_count; } __packed; struct pflow_tmpl_fspec { u_int16_t field_id; u_int16_t len; } __packed; /* update pflow_clone_create() when changing pflow_ipfix_tmpl_ipv4 */ struct pflow_ipfix_tmpl_ipv4 { struct pflow_tmpl_hdr h; struct pflow_tmpl_fspec src_ip; struct pflow_tmpl_fspec dest_ip; struct pflow_tmpl_fspec if_index_in; struct pflow_tmpl_fspec if_index_out; struct pflow_tmpl_fspec packets; struct pflow_tmpl_fspec octets; struct pflow_tmpl_fspec start; struct pflow_tmpl_fspec finish; struct pflow_tmpl_fspec src_port; struct pflow_tmpl_fspec dest_port; struct pflow_tmpl_fspec tos; struct pflow_tmpl_fspec protocol; #define PFLOW_IPFIX_TMPL_IPV4_FIELD_COUNT 12 #define PFLOW_IPFIX_TMPL_IPV4_ID 256 } __packed; /* update pflow_clone_create() when changing pflow_ipfix_tmpl_v6 */ struct pflow_ipfix_tmpl_ipv6 { struct pflow_tmpl_hdr h; struct pflow_tmpl_fspec src_ip; struct pflow_tmpl_fspec dest_ip; struct pflow_tmpl_fspec if_index_in; struct pflow_tmpl_fspec if_index_out; struct pflow_tmpl_fspec packets; struct pflow_tmpl_fspec octets; struct pflow_tmpl_fspec start; struct pflow_tmpl_fspec finish; struct pflow_tmpl_fspec src_port; struct pflow_tmpl_fspec dest_port; struct pflow_tmpl_fspec tos; struct pflow_tmpl_fspec protocol; #define PFLOW_IPFIX_TMPL_IPV6_FIELD_COUNT 12 #define PFLOW_IPFIX_TMPL_IPV6_ID 257 } __packed; struct pflow_ipfix_tmpl { struct pflow_set_header set_header; struct pflow_ipfix_tmpl_ipv4 ipv4_tmpl; struct pflow_ipfix_tmpl_ipv6 ipv6_tmpl; } __packed; struct pflow_ipfix_flow4 { u_int32_t src_ip; /* sourceIPv4Address*/ u_int32_t dest_ip; /* destinationIPv4Address */ u_int32_t if_index_in; /* ingressInterface */ u_int32_t if_index_out; /* egressInterface */ u_int64_t flow_packets; /* packetDeltaCount */ u_int64_t flow_octets; /* octetDeltaCount */ int64_t flow_start; /* flowStartMilliseconds */ int64_t flow_finish; /* flowEndMilliseconds */ u_int16_t src_port; /* sourceTransportPort */ u_int16_t dest_port; /* destinationTransportPort */ u_int8_t tos; /* ipClassOfService */ u_int8_t protocol; /* protocolIdentifier */ /* XXX padding needed? */ } __packed; struct pflow_ipfix_flow6 { struct in6_addr src_ip; /* sourceIPv6Address */ struct in6_addr dest_ip; /* destinationIPv6Address */ u_int32_t if_index_in; /* ingressInterface */ u_int32_t if_index_out; /* egressInterface */ u_int64_t flow_packets; /* packetDeltaCount */ u_int64_t flow_octets; /* octetDeltaCount */ int64_t flow_start; /* flowStartMilliseconds */ int64_t flow_finish; /* flowEndMilliseconds */ u_int16_t src_port; /* sourceTransportPort */ u_int16_t dest_port; /* destinationTransportPort */ u_int8_t tos; /* ipClassOfService */ u_int8_t protocol; /* protocolIdentifier */ /* XXX padding needed? */ } __packed; #ifdef _KERNEL struct pflow_softc { int sc_id; struct mtx sc_lock; int sc_dying; /* [N] */ struct vnet *sc_vnet; unsigned int sc_count; unsigned int sc_count4; unsigned int sc_count6; unsigned int sc_maxcount; unsigned int sc_maxcount4; unsigned int sc_maxcount6; u_int64_t sc_gcounter; u_int32_t sc_sequence; struct callout sc_tmo; struct callout sc_tmo6; struct callout sc_tmo_tmpl; struct intr_event *sc_swi_ie; void *sc_swi_cookie; struct mbufq sc_outputqueue; struct task sc_outputtask; struct socket *so; /* [p] */ struct sockaddr *sc_flowsrc; struct sockaddr *sc_flowdst; struct pflow_ipfix_tmpl sc_tmpl_ipfix; u_int8_t sc_version; + u_int32_t sc_observation_dom; 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 */ + PFLOWNL_GET_OBSERVATION_DOMAIN = 5, /* u32 */ }; 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 */ + PFLOWNL_SET_OBSERVATION_DOMAIN = 5, /* u32 */ }; #ifdef _KERNEL int pflow_sysctl(int *, u_int, void *, size_t *, void *, size_t); #endif /* _KERNEL */ #endif /* _NET_IF_PFLOW_H_ */ diff --git a/sys/netpfil/pf/pflow.c b/sys/netpfil/pf/pflow.c index 9a192c396c2d..398851bf17d0 100644 --- a/sys/netpfil/pf/pflow.c +++ b/sys/netpfil/pf/pflow.c @@ -1,1587 +1,1595 @@ /* $OpenBSD: if_pflow.c,v 1.100 2023/11/09 08:53:20 mvs Exp $ */ /* * Copyright (c) 2023 Rubicon Communications, LLC (Netgate) * Copyright (c) 2011 Florian Obser * Copyright (c) 2011 Sebastian Benoit * Copyright (c) 2008 Henning Brauer * Copyright (c) 2008 Joerg Goltermann * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER IN * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "net/if_var.h" #define PFLOW_MINMTU \ (sizeof(struct pflow_header) + sizeof(struct pflow_flow)) #ifdef PFLOWDEBUG #define DPRINTF(x) do { printf x ; } while (0) #else #define DPRINTF(x) #endif static void pflow_output_process(void *); static int pflow_create(int); static int pflow_destroy(int, bool); static int pflow_calc_mtu(struct pflow_softc *, int, int); static void pflow_setmtu(struct pflow_softc *, int); static int pflowvalidsockaddr(const struct sockaddr *, int); static struct mbuf *pflow_get_mbuf(struct pflow_softc *, u_int16_t); static void pflow_flush(struct pflow_softc *); static int pflow_sendout_v5(struct pflow_softc *); static int pflow_sendout_ipfix(struct pflow_softc *, sa_family_t); static int pflow_sendout_ipfix_tmpl(struct pflow_softc *); static int pflow_sendout_mbuf(struct pflow_softc *, struct mbuf *); static void pflow_timeout(void *); static void pflow_timeout6(void *); static void pflow_timeout_tmpl(void *); static void copy_flow_data(struct pflow_flow *, struct pflow_flow *, const struct pf_kstate *, struct pf_state_key *, int, int); static void copy_flow_ipfix_4_data(struct pflow_ipfix_flow4 *, struct pflow_ipfix_flow4 *, const 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 *, const struct pf_kstate *, struct pf_state_key *, struct pflow_softc *, int, int); static int pflow_pack_flow(const struct pf_kstate *, struct pf_state_key *, struct pflow_softc *); static int pflow_pack_flow_ipfix(const struct pf_kstate *, struct pf_state_key *, struct pflow_softc *); static void export_pflow(const struct pf_kstate *); static int export_pflow_if(const 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, pflowstat); #define V_pflowstats VNET(pflowstat) #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) SYSCTL_NODE(_net, OID_AUTO, pflow, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, "PFLOW"); SYSCTL_STRUCT(_net_pflow, OID_AUTO, stats, CTLFLAG_VNET | CTLFLAG_RW, &VNET_NAME(pflowstat), pflowstats, "PFLOW statistics (struct pflowstats, net/if_pflow.h)"); 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; + pflowif->sc_observation_dom = PFLOW_ENGINE_TYPE; /* 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); V_pflow_export_state_ptr = export_pflow; 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); if (CK_LIST_EMPTY(&V_pflowif_list)) V_pflow_export_state_ptr = NULL; 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 +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, const 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); flow1->flow_finish = flow2->flow_finish = st->expire < 0 ? htonl(0) : htonl(st->expire); 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, const 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((pf_get_time() - (pf_get_uptime() - st->creation))); flow1->flow_finish = flow2->flow_finish = htobe64((pf_get_time() - (pf_get_uptime() - st->expire))); 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, const 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((pf_get_time() - (pf_get_uptime() - st->creation))); flow1->flow_finish = flow2->flow_finish = htobe64((pf_get_time() - (pf_get_uptime() - st->expire))); flow1->protocol = flow2->protocol = sk->proto; flow1->tos = flow2->tos = st->rule.ptr->tos; } static void export_pflow(const 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); } } static int export_pflow_if(const 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(const 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(const 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); + h10->observation_dom = htonl(sc->sc_observation_dom); 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); + h10->observation_dom = htonl(sc->sc_observation_dom); 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); + nlattr_add_u32(nw, PFLOWNL_GET_OBSERVATION_DOMAIN, + sc->sc_observation_dom); 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; + uint32_t observation_dom; }; #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 }, + { .type = PFLOWNL_SET_OBSERVATION_DOMAIN, .off = _OUT(observation_dom), .cb = nlattr_get_uint32 }, }; 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; } + if (pflowr->observation_dom != 0) + sc->sc_observation_dom = pflowr->observation_dom; + /* 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); diff --git a/tests/sys/netpfil/pf/pflow.sh b/tests/sys/netpfil/pf/pflow.sh index 73e041fca693..12d194d84c14 100644 --- a/tests/sys/netpfil/pf/pflow.sh +++ b/tests/sys/netpfil/pf/pflow.sh @@ -1,142 +1,148 @@ # # 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: # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. 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 AUTHOR 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 AUTHOR 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. . $(atf_get_srcdir)/utils.subr atf_test_case "basic" "cleanup" basic_head() { atf_set descr 'Basic pflow test' atf_set require.user root } basic_body() { pflow_init epair=$(vnet_mkepair) ifconfig ${epair}a 192.0.2.2/24 up vnet_mkjail alcatraz ${epair}b jexec alcatraz ifconfig ${epair}b 192.0.2.1/24 up # Sanity check atf_check -s exit:0 -o ignore ping -c 1 192.0.2.1 pflow=$(jexec alcatraz pflowctl -c) # Reject invalid flow destinations atf_check -s exit:1 -e ignore \ jexec alcatraz pflowctl -s ${pflow} dst 256.0.0.1:4000 atf_check -s exit:1 -e ignore \ jexec alcatraz pflowctl -s ${pflow} dst 192.0.0.2:400000 # A valid destination is accepted atf_check -s exit:0 \ jexec alcatraz pflowctl -s ${pflow} dst 192.0.2.2:4000 # Reject invalid version numbers atf_check -s exit:1 -e ignore \ jexec alcatraz pflowctl -s ${pflow} proto 9 # Valid version passes atf_check -s exit:0 \ jexec alcatraz pflowctl -s ${pflow} proto 5 atf_check -s exit:0 \ jexec alcatraz pflowctl -s ${pflow} proto 10 + + # We can change the observation domain + atf_check -s exit:0 \ + jexec alcatraz pflowctl -s ${pflow} domain 13 + atf_check -s exit:0 -o match:".*domain 13.*" \ + jexec alcatraz pflowctl -l } basic_cleanup() { pft_cleanup } atf_test_case "state_defaults" "cleanup" state_defaults_head() { atf_set descr 'Test set state-defaults pflow' atf_set require.user root atf_set require.progs scapy } state_defaults_body() { pflow_init epair=$(vnet_mkepair) ifconfig ${epair}a 192.0.2.2/24 up vnet_mkjail alcatraz ${epair}b jexec alcatraz ifconfig ${epair}b 192.0.2.1/24 up # Sanity check atf_check -s exit:0 -o ignore ping -c 1 192.0.2.1 jexec alcatraz pfctl -e pft_set_rules alcatraz \ "pass" pflow=$(jexec alcatraz pflowctl -c) jexec alcatraz pflowctl -s ${pflow} dst 192.0.2.2:2055 # No flow data is generated because no states are marked for it. ping -c 1 192.0.2.1 # Flush states to force pflow creation jexec alcatraz pfctl -Fstates atf_check -o match:"No data" \ $(atf_get_srcdir)/pft_read_ipfix.py --recvif ${epair}a --port 2055 # Expect pflow output with state-defaults pflow pft_set_rules alcatraz \ "set state-defaults pflow" \ "pass" ping -c 1 192.0.2.1 # We default to version 5 atf_check -o match:"^v=5.*" \ $(atf_get_srcdir)/pft_read_ipfix.py --recvif ${epair}a --port 2055 # Switch to version 10 jexec alcatraz pflowctl -s ${pflow} proto 10 ping -c 1 192.0.2.1 atf_check -o match:"^v=10.*" \ $(atf_get_srcdir)/pft_read_ipfix.py --recvif ${epair}a --port 2055 } state_defaults_cleanup() { pft_cleanup } atf_init_test_cases() { atf_add_test_case "basic" atf_add_test_case "state_defaults" }