diff options
author | Henning Brauer <henning@cvs.openbsd.org> | 2008-09-09 13:56:40 +0000 |
---|---|---|
committer | Henning Brauer <henning@cvs.openbsd.org> | 2008-09-09 13:56:40 +0000 |
commit | eafa0437596580a038262a0363fdd2115f6f1726 (patch) | |
tree | 508e9ede5b29e41966fa0aa869df62cbd54600a3 | |
parent | 456e9ebf40a66cc20f4da77dbd097479607938d9 (diff) |
welcome pflow(4), a netflow v5 compatible flow export interface.
flows export data gathered from pf states.
initial implementation by Joerg Goltermann <jg@osn.de>, guidance and many
changes by me. 'put it in' theo
-rw-r--r-- | sbin/ifconfig/ifconfig.c | 136 | ||||
-rw-r--r-- | sbin/pfctl/parse.y | 25 | ||||
-rw-r--r-- | sbin/pfctl/pf_print_state.c | 4 | ||||
-rw-r--r-- | sbin/pfctl/pfctl_parser.c | 8 | ||||
-rw-r--r-- | share/man/man4/pflow.4 | 87 | ||||
-rw-r--r-- | sys/conf/files | 5 | ||||
-rw-r--r-- | sys/net/if_pflow.c | 622 | ||||
-rw-r--r-- | sys/net/if_pflow.h | 123 | ||||
-rw-r--r-- | sys/net/if_types.h | 3 | ||||
-rw-r--r-- | sys/net/pf.c | 10 | ||||
-rw-r--r-- | sys/net/pfvar.h | 10 | ||||
-rw-r--r-- | sys/sys/sockio.h | 5 |
12 files changed, 1024 insertions, 14 deletions
diff --git a/sbin/ifconfig/ifconfig.c b/sbin/ifconfig/ifconfig.c index 03b40456482..72103b1c197 100644 --- a/sbin/ifconfig/ifconfig.c +++ b/sbin/ifconfig/ifconfig.c @@ -1,4 +1,4 @@ -/* $OpenBSD: ifconfig.c,v 1.203 2008/09/07 02:22:34 deraadt Exp $ */ +/* $OpenBSD: ifconfig.c,v 1.204 2008/09/09 13:56:38 henning Exp $ */ /* $NetBSD: ifconfig.c,v 1.40 1997/10/01 02:19:43 enami Exp $ */ /* @@ -79,6 +79,7 @@ #include <net80211/ieee80211_ioctl.h> #include <net/pfvar.h> #include <net/if_pfsync.h> +#include <net/if_pflow.h> #include <net/if_pppoe.h> #include <net/if_trunk.h> #include <net/if_sppp.h> @@ -233,6 +234,14 @@ void trunk_status(void); int main(int, char *[]); int prefix(void *val, int); +#ifndef SMALL +void pflow_status(void); +void setpflow_sender(const char *, int); +void unsetpflow_sender(const char *, int); +void setpflow_receiver(const char *, int); +void unsetpflow_receiver(const char *, int); +#endif + /* * Media stuff. Whenever a media command is first performed, the * currently select media is grabbed for this interface. If `media' @@ -395,6 +404,12 @@ const struct cmd { { "descr", NEXTARG, 0, setifdesc }, { "-description", 1, 0, unsetifdesc }, { "-descr", 1, 0, unsetifdesc }, +#ifndef SMALL + { "flowsrc", NEXTARG, 0, setpflow_sender }, + { "-flowsrc", 1, 0, unsetpflow_sender }, + { "flowdst", NEXTARG, 0, setpflow_receiver }, + { "-flowdst", 1, 0, unsetpflow_receiver }, +#endif { NULL, /*src*/ 0, 0, setifaddr }, { NULL, /*dst*/ 0, 0, setifdstaddr }, { NULL, /*illegal*/0, 0, NULL }, @@ -2592,6 +2607,7 @@ status(int link, struct sockaddr_dl *sdl) sppp_status(); trunk_status(); mpe_status(); + pflow_status(); #endif getifgroups(); @@ -3674,6 +3690,124 @@ pfsync_status(void) } } +#ifndef SMALL +void +pflow_status(void) +{ + struct pflowreq preq; + + bzero((char *)&preq, sizeof(struct pflowreq)); + ifr.ifr_data = (caddr_t)&preq; + + if (ioctl(s, SIOCGETPFLOW, (caddr_t)&ifr) == -1) + return; + + printf("\tpflow: sender: %s ", inet_ntoa(preq.sender_ip)); + printf("receiver: %s:%u\n", inet_ntoa(preq.receiver_ip), + ntohs(preq.receiver_port)); +} + +/* ARGSUSED */ +void +setpflow_sender(const char *val, int d) +{ + struct pflowreq preq; + struct addrinfo hints, *sender; + int ecode; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_DGRAM; /*dummy*/ + + if ((ecode = getaddrinfo(val, NULL, &hints, &sender)) != 0) + errx(1, "error in parsing address string: %s", + gai_strerror(ecode)); + + if (sender->ai_addr->sa_family != AF_INET) + errx(1, "only IPv4 addresses supported for the sender"); + + bzero((char *)&preq, sizeof(struct pflowreq)); + ifr.ifr_data = (caddr_t)&preq; + preq.addrmask |= PFLOW_MASK_SRCIP; + preq.sender_ip.s_addr = ((struct sockaddr_in *) + sender->ai_addr)->sin_addr.s_addr; + + + if (ioctl(s, SIOCSETPFLOW, (caddr_t)&ifr) == -1) + err(1, "SIOCSETPFLOW"); + + freeaddrinfo(sender); +} + +void +unsetpflow_sender(const char *val, int d) +{ + struct pflowreq preq; + + bzero((char *)&preq, sizeof(struct pflowreq)); + preq.addrmask |= PFLOW_MASK_SRCIP; + ifr.ifr_data = (caddr_t)&preq; + if (ioctl(s, SIOCSETPFLOW, (caddr_t)&ifr) == -1) + err(1, "SIOCSETPFLOW"); +} + +/* ARGSUSED */ +void +setpflow_receiver(const char *val, int d) +{ + const char *errmsg = NULL; + struct pflowreq preq; + struct addrinfo hints, *receiver; + int ecode; + char *ip, *port, buf[MAXHOSTNAMELEN+sizeof (":65535")]; + + if (strchr (val, ':') == NULL) + errx(1, "%s bad value", val); + + if (strlcpy(buf, val, sizeof(buf)) >= sizeof(buf)) + errx(1, "%s bad value", val); + port = strchr(buf, ':'); + *port++ = '\0'; + ip = buf; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_DGRAM; /*dummy*/ + + if ((ecode = getaddrinfo(ip, port, &hints, &receiver)) != 0) + errx(1, "error in parsing address string: %s", + gai_strerror(ecode)); + + if (receiver->ai_addr->sa_family != AF_INET) + errx(1, "only IPv4 addresses supported for the receiver"); + + bzero((char *)&preq, sizeof(struct pflowreq)); + ifr.ifr_data = (caddr_t)&preq; + preq.addrmask |= PFLOW_MASK_DSTIP | PFLOW_MASK_DSTPRT; + preq.receiver_ip.s_addr = ((struct sockaddr_in *) + receiver->ai_addr)->sin_addr.s_addr; + preq.receiver_port = (u_int16_t) ((struct sockaddr_in *) + receiver->ai_addr)->sin_port; + + if (ioctl(s, SIOCSETPFLOW, (caddr_t)&ifr) == -1) + err(1, "SIOCSETPFLOW"); + + freeaddrinfo(receiver); +} + +void +unsetpflow_receiver(const char *val, int d) +{ + struct pflowreq preq; + + bzero((char *)&preq, sizeof(struct pflowreq)); + ifr.ifr_data = (caddr_t)&preq; + preq.addrmask |= PFLOW_MASK_DSTIP | PFLOW_MASK_DSTPRT; + if (ioctl(s, SIOCSETPFLOW, (caddr_t)&ifr) == -1) + err(1, "SIOCSETPFLOW"); +} +#endif /* SMALL */ + void pppoe_status(void) { diff --git a/sbin/pfctl/parse.y b/sbin/pfctl/parse.y index c9ccec6f2c3..fc075f9b0ae 100644 --- a/sbin/pfctl/parse.y +++ b/sbin/pfctl/parse.y @@ -1,4 +1,4 @@ -/* $OpenBSD: parse.y,v 1.550 2008/08/07 18:29:32 henning Exp $ */ +/* $OpenBSD: parse.y,v 1.551 2008/09/09 13:56:38 henning Exp $ */ /* * Copyright (c) 2001 Markus Friedl. All rights reserved. @@ -153,7 +153,8 @@ enum { PF_STATE_OPT_MAX, PF_STATE_OPT_NOSYNC, PF_STATE_OPT_SRCTRACK, PF_STATE_OPT_MAX_SRC_STATES, PF_STATE_OPT_MAX_SRC_CONN, PF_STATE_OPT_MAX_SRC_CONN_RATE, PF_STATE_OPT_MAX_SRC_NODES, PF_STATE_OPT_OVERLOAD, PF_STATE_OPT_STATELOCK, - PF_STATE_OPT_TIMEOUT, PF_STATE_OPT_SLOPPY }; + PF_STATE_OPT_TIMEOUT, PF_STATE_OPT_SLOPPY, + PF_STATE_OPT_PFLOW }; enum { PF_SRCTRACK_NONE, PF_SRCTRACK, PF_SRCTRACK_GLOBAL, PF_SRCTRACK_RULE }; @@ -442,7 +443,7 @@ int parseport(char *, struct range *r, int); %token QUEUE PRIORITY QLIMIT RTABLE %token LOAD RULESET_OPTIMIZATION %token STICKYADDRESS MAXSRCSTATES MAXSRCNODES SOURCETRACK GLOBAL RULE -%token MAXSRCCONN MAXSRCCONNRATE OVERLOAD FLUSH SLOPPY +%token MAXSRCCONN MAXSRCCONNRATE OVERLOAD FLUSH SLOPPY PFLOW %token TAGGED TAG IFBOUND FLOATING STATEPOLICY ROUTE SETTOS %token DIVERTTO DIVERTREPLY %token <v.string> STRING @@ -2061,6 +2062,15 @@ pfrule : action dir logquick interface route af proto fromto } r.rule_flag |= PFRULE_STATESLOPPY; break; + case PF_STATE_OPT_PFLOW: + if (r.rule_flag & PFRULE_PFLOW) { + yyerror("state pflow " + "option: multiple " + "definitions"); + YYERROR; + } + r.rule_flag |= PFRULE_PFLOW; + break; case PF_STATE_OPT_TIMEOUT: if (o->data.timeout.number == PFTM_ADAPTIVE_START || @@ -3541,6 +3551,14 @@ state_opt_item : MAXIMUM NUMBER { $$->next = NULL; $$->tail = $$; } + | PFLOW { + $$ = calloc(1, sizeof(struct node_state_opt)); + if ($$ == NULL) + err(1, "state_opt_item: calloc"); + $$->type = PF_STATE_OPT_PFLOW; + $$->next = NULL; + $$->tail = $$; + } | STRING NUMBER { int i; @@ -5256,6 +5274,7 @@ lookup(char *s) { "out", OUT}, { "overload", OVERLOAD}, { "pass", PASS}, + { "pflow", PFLOW}, { "port", PORT}, { "priority", PRIORITY}, { "priq", PRIQ}, diff --git a/sbin/pfctl/pf_print_state.c b/sbin/pfctl/pf_print_state.c index 73eef1199c7..b20fc941c16 100644 --- a/sbin/pfctl/pf_print_state.c +++ b/sbin/pfctl/pf_print_state.c @@ -1,4 +1,4 @@ -/* $OpenBSD: pf_print_state.c,v 1.52 2008/08/12 16:40:18 david Exp $ */ +/* $OpenBSD: pf_print_state.c,v 1.53 2008/09/09 13:56:38 henning Exp $ */ /* * Copyright (c) 2001 Daniel Hartmeier @@ -325,6 +325,8 @@ print_state(struct pfsync_state *s, int opts) printf(", rule %u", ntohl(s->rule)); if (s->state_flags & PFSTATE_SLOPPY) printf(", sloppy"); + if (s->state_flags & PFSTATE_PFLOW) + printf(", pflow"); if (s->sync_flags & PFSYNC_FLAG_SRCNODE) printf(", source-track"); if (s->sync_flags & PFSYNC_FLAG_NATSRCNODE) diff --git a/sbin/pfctl/pfctl_parser.c b/sbin/pfctl/pfctl_parser.c index 7368dbe7d3c..86c5f90ffeb 100644 --- a/sbin/pfctl/pfctl_parser.c +++ b/sbin/pfctl/pfctl_parser.c @@ -1,4 +1,4 @@ -/* $OpenBSD: pfctl_parser.c,v 1.240 2008/06/10 20:55:02 mcbride Exp $ */ +/* $OpenBSD: pfctl_parser.c,v 1.241 2008/09/09 13:56:38 henning Exp $ */ /* * Copyright (c) 2001 Daniel Hartmeier @@ -934,6 +934,12 @@ print_rule(struct pf_rule *r, const char *anchor_call, int verbose) printf("sloppy"); opts = 0; } + if (r->rule_flag & PFRULE_PFLOW) { + if (!opts) + printf(", "); + printf("pflow"); + opts = 0; + } for (i = 0; i < PFTM_MAX; ++i) if (r->timeout[i]) { int j; diff --git a/share/man/man4/pflow.4 b/share/man/man4/pflow.4 new file mode 100644 index 00000000000..46e9666fcca --- /dev/null +++ b/share/man/man4/pflow.4 @@ -0,0 +1,87 @@ +.\" $OpenBSD: pflow.4,v 1.1 2008/09/09 13:56:38 henning Exp $ +.\" +.\" Copyright (c) 2008 Henning Brauer <henning@openbsd.org> +.\" Copyright (c) 2008 Joerg Goltermann <jg@osn.de> +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALLWARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BELIABLE 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, ARISINGOUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.Dd $Mdocdate: September 9 2008 $ +.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 +interface is a pseudo-device which exports pflow +accounting data from the kernel using +.Xr udp 4 +packets. +pflow is compatible with netflow v5. +The data is extracted from the +.Xr pf 4 +state table. +.Pp +Only states created by a rule marked with the +.Ar pflow +keyword are exported by the +.Nm +interface. +.Pp +The +.Nm +interface will attempt to export multiple pflow records in one +UDP packet, but will not hold a record for longer than 30 seconds. +The maximum number of flows is controlled by the +.Ar mtu +parameter of ifconfig +(see the +.Xr ifconfig 8 +.Cm mtu +parameter). +.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 +.Aq Pa net/if_pflow.h . +.Pp +The pflow source and destination addresses are controlled by +.Xr ifconfig 8 . +flowsrc is the sender IP address of the UDP packet which can be used +to identify the source of the data on the pflow collector. +flowdst defines the collector IP address and the port. +The flowdst 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 +# ifconfig pflow0 flowsrc 10.0.0.1 flowdst 10.0.0.2:1234 +.Ed +.Sh SEE ALSO +.Xr netintro 4 , +.Xr pf 4 , +.Xr udp 4 , +.Xr pf.conf 5 , +.Xr ifconfig 8 , +.Xr tcpdump 8 +.Sh HISTORY +The +.Nm +device first appeared in +.Ox 4.5 . diff --git a/sys/conf/files b/sys/conf/files index 1443adf21d2..3644ee064f1 100644 --- a/sys/conf/files +++ b/sys/conf/files @@ -1,4 +1,4 @@ -# $OpenBSD: files,v 1.441 2008/08/15 19:42:03 miod Exp $ +# $OpenBSD: files,v 1.442 2008/09/09 13:56:39 henning Exp $ # $NetBSD: files,v 1.87 1996/05/19 17:17:50 jonathan Exp $ # @(#)files.newconf 7.5 (Berkeley) 5/10/93 @@ -501,6 +501,9 @@ file net/if_pflog.c pflog needs-flag pseudo-device pfsync: ifnet file net/if_pfsync.c pfsync needs-flag +pseudo-device pflow: ifnet +file net/if_pflow.c pflow needs-flag + pseudo-device bio file dev/bio.c bio needs-flag diff --git a/sys/net/if_pflow.c b/sys/net/if_pflow.c new file mode 100644 index 00000000000..87e0a094ab0 --- /dev/null +++ b/sys/net/if_pflow.c @@ -0,0 +1,622 @@ +/* $OpenBSD: if_pflow.c,v 1.1 2008/09/09 13:56:39 henning Exp $ */ + +/* + * Copyright (c) 2008 Henning Brauer <henning@openbsd.org> + * Copyright (c) 2008 Joerg Goltermann <jg@osn.de> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER IN + * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT + * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/types.h> +#include <sys/malloc.h> +#include <sys/param.h> +#include <sys/mbuf.h> +#include <sys/socket.h> +#include <sys/ioctl.h> +#include <sys/kernel.h> +#include <sys/sysctl.h> +#include <dev/rndvar.h> + +#include <net/if.h> +#include <net/if_types.h> +#include <net/bpf.h> +#include <net/route.h> +#include <netinet/in.h> +#include <netinet/if_ether.h> +#include <netinet/tcp.h> + +#ifdef INET +#include <netinet/in.h> +#include <netinet/in_var.h> +#include <netinet/in_systm.h> +#include <netinet/ip.h> +#include <netinet/ip_var.h> +#include <netinet/udp.h> +#include <netinet/udp_var.h> +#include <netinet/in_pcb.h> +#endif /* INET */ + +#include <net/pfvar.h> +#include <net/if_pflow.h> + +#include "bpfilter.h" +#include "pflow.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 + +struct pflow_softc *pflowif = NULL; +struct pflowstats pflowstats; + +void pflowattach(int); +int pflow_clone_create(struct if_clone *, int); +int pflow_clone_destroy(struct ifnet *); +void pflow_setmtu(struct pflow_softc *, int); +int pflowoutput(struct ifnet *, struct mbuf *, struct sockaddr *, + struct rtentry *); +int pflowioctl(struct ifnet *, u_long, caddr_t); +void pflowstart(struct ifnet *); + +struct mbuf *pflow_get_mbuf(struct pflow_softc *, void **); +int pflow_sendout(struct pflow_softc *); +int pflow_sendout_mbuf(struct pflow_softc *, struct mbuf *); +void pflow_timeout(void *); +void copy_flow_data(struct pflow_flow *, struct pflow_flow *, + struct pf_state *, int, int); +int pflow_pack_flow(struct pf_state *); +int pflow_get_dynport(void); + +struct if_clone pflow_cloner = + IF_CLONE_INITIALIZER("pflow", pflow_clone_create, + pflow_clone_destroy); + +/* from in_pcb.c */ +extern int ipport_hifirstauto; +extern int ipport_hilastauto; + +void +pflowattach(int npflow) +{ + if_clone_attach(&pflow_cloner); +} + +int +pflow_clone_create(struct if_clone *ifc, int unit) +{ + struct ifnet *ifp; + + if (unit != 0) + return (EINVAL); + + if ((pflowif = malloc(sizeof(*pflowif), + M_DEVBUF, M_NOWAIT|M_ZERO)) == NULL) + return (ENOMEM); + + pflowif->sc_sender_ip.s_addr = INADDR_ANY; + pflowif->sc_sender_port = pflow_get_dynport(); + + pflowif->sc_imo.imo_membership = malloc( + (sizeof(struct in_multi *) * IP_MIN_MEMBERSHIPS), M_IPMOPTS, + M_WAITOK|M_ZERO); + pflowif->sc_imo.imo_max_memberships = IP_MIN_MEMBERSHIPS; + pflowif->sc_receiver_ip.s_addr = 0; + pflowif->sc_receiver_port = 0; + pflowif->sc_sender_ip.s_addr = INADDR_ANY; + pflowif->sc_sender_port = pflow_get_dynport(); + ifp = &pflowif->sc_if; + snprintf(ifp->if_xname, sizeof ifp->if_xname, "pflow%d", unit); + ifp->if_softc = pflowif; + ifp->if_ioctl = pflowioctl; + ifp->if_output = pflowoutput; + ifp->if_start = pflowstart; + ifp->if_type = IFT_PFLOW; + ifp->if_snd.ifq_maxlen = ifqmaxlen; + ifp->if_hdrlen = PFLOW_HDRLEN; + ifp->if_flags = IFF_UP; + ifp->if_flags &= ~IFF_RUNNING; /* not running, need receiver */ + pflow_setmtu(pflowif, ETHERMTU); + timeout_set(&pflowif->sc_tmo, pflow_timeout, pflowif); + if_attach(ifp); + if_alloc_sadl(ifp); + + return (0); +} + +int +pflow_clone_destroy(struct ifnet *ifp) +{ + struct pflow_softc *sc = ifp->if_softc; + + timeout_del(&sc->sc_tmo); + +#if NBPFILTER > 0 + bpfdetach(ifp); +#endif + if_detach(ifp); + free(pflowif->sc_imo.imo_membership, M_IPMOPTS); + free(pflowif, M_DEVBUF); + pflowif = NULL; + return (0); +} + +/* + * Start output on the pflow interface. + */ +void +pflowstart(struct ifnet *ifp) +{ + struct mbuf *m; + int s; + + for (;;) { + s = splnet(); + IF_DROP(&ifp->if_snd); + IF_DEQUEUE(&ifp->if_snd, m); + splx(s); + + if (m == NULL) + return; + m_freem(m); + } +} + +int +pflowoutput(struct ifnet *ifp, struct mbuf *m, struct sockaddr *dst, + struct rtentry *rt) +{ + m_freem(m); + return (0); +} + +/* ARGSUSED */ +int +pflowioctl(struct ifnet *ifp, u_long cmd, caddr_t data) +{ + struct proc *p = curproc; + struct pflow_softc *sc = ifp->if_softc; + struct ifreq *ifr = (struct ifreq *)data; + struct pflowreq pflowr; + int s, error; + + switch (cmd) { + case SIOCSIFADDR: + case SIOCAIFADDR: + case SIOCSIFDSTADDR: + case SIOCSIFFLAGS: + if ((ifp->if_flags & IFF_UP) && + sc->sc_receiver_ip.s_addr != 0 && + sc->sc_receiver_port != 0) + ifp->if_flags |= IFF_RUNNING; + else + ifp->if_flags &= ~IFF_RUNNING; + break; + case SIOCSIFMTU: + if (ifr->ifr_mtu < PFLOW_MINMTU) + return (EINVAL); + if (ifr->ifr_mtu > MCLBYTES) + ifr->ifr_mtu = MCLBYTES; + s = splnet(); + if (ifr->ifr_mtu < ifp->if_mtu) + pflow_sendout(sc); + pflow_setmtu(sc, ifr->ifr_mtu); + splx(s); + break; + + case SIOCGETPFLOW: + bzero(&pflowr, sizeof(pflowr)); + + pflowr.sender_ip = sc->sc_sender_ip; + pflowr.receiver_ip = sc->sc_receiver_ip; + pflowr.receiver_port = sc->sc_receiver_port; + + if ((error = copyout(&pflowr, ifr->ifr_data, + sizeof(pflowr)))) + return (error); + break; + + case SIOCSETPFLOW: + if ((error = suser(p, p->p_acflag)) != 0) + return (error); + if ((error = copyin(ifr->ifr_data, &pflowr, + sizeof(pflowr)))) + return (error); + + if ((ifp->if_flags & IFF_UP) && sc->sc_receiver_ip.s_addr != 0 + && sc->sc_receiver_port != 0) { + s = splnet(); + pflow_sendout(sc); + splx(s); + } + + if (pflowr.addrmask & PFLOW_MASK_DSTIP) + sc->sc_receiver_ip = pflowr.receiver_ip; + if (pflowr.addrmask & PFLOW_MASK_DSTPRT) + sc->sc_receiver_port = pflowr.receiver_port; + if (pflowr.addrmask & PFLOW_MASK_SRCIP) + sc->sc_sender_ip.s_addr = pflowr.sender_ip.s_addr; + + if ((ifp->if_flags & IFF_UP) && + sc->sc_receiver_ip.s_addr != 0 && + sc->sc_receiver_port != 0) + ifp->if_flags |= IFF_RUNNING; + else + ifp->if_flags &= ~IFF_RUNNING; + + break; + + default: + return (ENOTTY); + } + return (0); +} + +void +pflow_setmtu(struct pflow_softc *sc, int mtu_req) +{ + int mtu; + + if (sc->sc_pflow_ifp && sc->sc_pflow_ifp->if_mtu < mtu_req) + mtu = sc->sc_pflow_ifp->if_mtu; + else + mtu = mtu_req; + + sc->sc_maxcount = (mtu - sizeof(struct pflow_header)) / + sizeof(struct pflow_flow); + if (sc->sc_maxcount > PFLOW_MAXFLOWS) + sc->sc_maxcount = PFLOW_MAXFLOWS; + sc->sc_if.if_mtu = sizeof(struct pflow_header) + + sc->sc_maxcount * sizeof(struct pflow_flow); +} + +struct mbuf * +pflow_get_mbuf(struct pflow_softc *sc, void **sp) +{ + struct pflow_header *h; + struct mbuf *m, *top = NULL, **mp = ⊤ + int len, totlen; + + MGETHDR(m, M_DONTWAIT, MT_DATA); + if (m == NULL) { + sc->sc_if.if_oerrors++; + return (NULL); + } + + len = MHLEN; + totlen = (sc->sc_maxcount * sizeof(struct pflow_flow)) + + sizeof(struct pflow_header); + + while (totlen > 0) { + if (top) { + MGET(m, M_DONTWAIT, MT_DATA); + if (m == 0) { + m_freem(top); + sc->sc_if.if_oerrors++; + return (NULL); + } + len = MLEN; + } + if (totlen >= MINCLSIZE) { + MCLGET(m, M_DONTWAIT); + if (m->m_flags & M_EXT) + len = MCLBYTES; + else { + m_free(m); + m_freem(top); + sc->sc_if.if_oerrors++; + return (NULL); + } + } + m->m_len = len = min(totlen, len); + totlen -= len; + *mp = m; + mp = &m->m_next; + } + + top->m_pkthdr.rcvif = NULL; + top->m_len = m->m_pkthdr.len = sizeof(struct pflow_header); + + /* populate pflow_header */ + h = mtod(top, struct pflow_header *); + h->reserved1 = 0; + h->reserved2 = 0; + h->count = 0; + h->version = htons(PFLOW_VERSION); + h->flow_sequence = htonl(sc->sc_gcounter); + h->engine_type = PFLOW_ENGINE_TYPE; + h->engine_id = PFLOW_ENGINE_ID; + + sc->sc_count = 0; + *sp = (void *)((char *)h + PFLOW_HDRLEN); + timeout_add_sec(&sc->sc_tmo, PFLOW_TIMEOUT); + return (top); +} + +void +copy_flow_data(struct pflow_flow *flow1, struct pflow_flow *flow2, + struct pf_state *st, int src, int dst) +{ + struct pf_state_key *sk = st->key[PF_SK_WIRE]; + + flow1->src_ip = flow2->dest_ip = + st->key[PF_SK_WIRE]->addr[src].v4.s_addr; + flow1->src_port = flow2->dest_port = st->key[PF_SK_WIRE]->port[src]; + flow1->dest_ip = flow2->src_ip = + st->key[PF_SK_WIRE]->addr[dst].v4.s_addr; + flow1->dest_port = flow2->src_port = st->key[PF_SK_WIRE]->port[dst]; + + flow1->dest_as = flow2->src_as = + flow1->src_as = flow2->dest_as = 0; + flow1->if_index_out = flow2->if_index_in = + flow1->if_index_in = flow2->if_index_out = 0; + 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]); + + flow1->flow_start = flow2->flow_start = htonl(st->creation * 1000); + flow1->flow_finish = flow2->flow_finish = htonl(time_second * 1000); + flow1->tcp_flags = flow2->tcp_flags = 0; + flow1->protocol = flow2->protocol = sk->proto; + flow1->tos = flow2->tos = st->rule.ptr->tos; +} + +int +export_pflow(struct pf_state *st) +{ + struct pf_state pfs_copy; + struct pflow_softc *sc = pflowif; + struct ifnet *ifp = NULL; + u_int64_t bytes[2]; + int ret = 0; + + if (sc == NULL) + return (0); + + ifp = &sc->sc_if; + if (!(ifp->if_flags & IFF_UP)) + return (0); + + if ((st->bytes[0] < (u_int64_t)PFLOW_MAXBYTES) + && (st->bytes[1] < (u_int64_t)PFLOW_MAXBYTES)) + return pflow_pack_flow(st); + + /* 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)) != 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)) != 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)); +} + +int +pflow_pack_flow(struct pf_state *st) +{ + struct pflow_softc *sc = pflowif; + struct pflow_flow *flow1 = NULL; + struct pflow_flow flow2; + struct pf_state_key *sk = st->key[PF_SK_WIRE]; + int s, ret = 0; + + if (sk->af != AF_INET) + return (0); + + s = splnet(); + + if (sc->sc_mbuf == NULL) { + if ((sc->sc_mbuf = pflow_get_mbuf(sc, + (void **)&sc->sc_flowp.s)) == NULL) { + splx(s); + return (ENOMEM); + } + } + + pflowstats.pflow_flows++; + sc->sc_gcounter++; + sc->sc_count++; + + flow1 = sc->sc_flowp.s++; + sc->sc_mbuf->m_pkthdr.len = + sc->sc_mbuf->m_len += sizeof(struct pflow_flow); + bzero(flow1, sizeof(*flow1)); + bzero(&flow2, sizeof(flow2)); + + if (st->direction == PF_OUT) + copy_flow_data(flow1, &flow2, st, 1, 0); + else + copy_flow_data(flow1, &flow2, st, 0, 1); + + if (st->bytes[0] != 0) { /* first flow from state */ + if (sc->sc_count >= sc->sc_maxcount) + ret = pflow_sendout(sc); + + if (st->bytes[1] != 0) { + /* one more flow, second part from state */ + if (sc->sc_mbuf == NULL) { + if ((sc->sc_mbuf = pflow_get_mbuf(sc, + (void **)&sc->sc_flowp.s)) == NULL) { + splx(s); + return (ENOMEM); + } + } + + pflowstats.pflow_flows++; + sc->sc_gcounter++; + sc->sc_count++; + + flow1 = sc->sc_flowp.s++; + sc->sc_mbuf->m_pkthdr.len = + sc->sc_mbuf->m_len += sizeof(struct pflow_flow); + bzero(flow1, sizeof(*flow1)); + } + } + + if (st->bytes[1] != 0) { /* second flow from state */ + bcopy(&flow2, flow1, sizeof(*flow1)); + if (sc->sc_count >= sc->sc_maxcount) + ret = pflow_sendout(sc); + } + + splx(s); + return (ret); +} + +void +pflow_timeout(void *v) +{ + struct pflow_softc *sc = v; + int s; + + s = splnet(); + pflow_sendout(sc); + splx(s); +} + +/* This must be called in splnet() */ +int +pflow_sendout(struct pflow_softc *sc) +{ + struct mbuf *m; + struct pflow_header *h; +#if NBPFILTER > 0 + struct ifnet *ifp = &sc->sc_if; +#endif + + timeout_del(&sc->sc_tmo); + + if (sc->sc_mbuf == NULL) + return (0); + + pflowstats.pflow_packets++; + + if (!(ifp->if_flags & IFF_RUNNING)) { + m_freem(m); + return (0); + } + + m = sc->sc_mbuf; + sc->sc_mbuf = NULL; + sc->sc_flowp.s = NULL; + h = mtod(m, struct pflow_header *); + h->count = htons(sc->sc_count); + + /* populate pflow_header */ + h->uptime_ms = htonl(time_uptime * 1000); + h->time_sec = htonl(time_second); + h->time_nanosec = htonl(ticks); + +#if NBPFILTER > 0 + if (ifp->if_bpf) + bpf_mtap(ifp->if_bpf, m, BPF_DIRECTION_OUT); +#endif + + return (pflow_sendout_mbuf(sc, m)); +} + +int +pflow_sendout_mbuf(struct pflow_softc *sc, struct mbuf *m) +{ + struct udpiphdr *ui; + int len = m->m_pkthdr.len; + + /* UDP Header*/ + M_PREPEND(m, sizeof(struct udpiphdr), M_DONTWAIT); + if (m == NULL) { + pflowstats.pflow_onomem++; + return (0); + } + + ui = mtod(m, struct udpiphdr *); + ui->ui_pr = IPPROTO_UDP; + ui->ui_len = htons((u_int16_t) len + sizeof (struct udphdr)); + ui->ui_src = sc->sc_sender_ip; + ui->ui_sport = sc->sc_sender_port; + ui->ui_dst = sc->sc_receiver_ip; + ui->ui_dport = sc->sc_receiver_port; + ui->ui_ulen = ui->ui_len; + + ((struct ip *)ui)->ip_v = IPVERSION; + ((struct ip *)ui)->ip_hl = sizeof(struct ip) >> 2; + ((struct ip *)ui)->ip_id = htons(ip_randomid()); + ((struct ip *)ui)->ip_off = htons(IP_DF); + ((struct ip *)ui)->ip_tos = IPTOS_LOWDELAY; + ((struct ip *)ui)->ip_ttl = IPDEFTTL; + ((struct ip *)ui)->ip_len = htons(sizeof (struct udpiphdr) + len); + + /* + * Compute the pseudo-header checksum; defer further checksumming + * until ip_output() or hardware (if it exists). + */ + m->m_pkthdr.csum_flags |= M_UDPV4_CSUM_OUT; + ui->ui_sum = in_cksum_phdr(ui->ui_src.s_addr, + ui->ui_dst.s_addr, htons((u_int16_t)len + + sizeof(struct udphdr) + IPPROTO_UDP)); + + if (ip_output(m, NULL, NULL, IP_RAWOUTPUT, &sc->sc_imo, NULL)) + pflowstats.pflow_oerrors++; + return (0); +} + +int +pflow_get_dynport(void) +{ + u_int16_t tmp, low, high, cut; + + low = ipport_hifirstauto; /* sysctl */ + high = ipport_hilastauto; + + cut = arc4random_uniform(1 + high - low) + low; + + for (tmp = cut; tmp <= high; ++(tmp)) { + if (!in_baddynamic(tmp, IPPROTO_UDP)) + return (htons(tmp)); + } + + for (tmp = cut - 1; tmp >= low; --(tmp)) { + if (!in_baddynamic(tmp, IPPROTO_UDP)) + return (htons(tmp)); + } + + return (htons(ipport_hilastauto)); /* XXX */ +} diff --git a/sys/net/if_pflow.h b/sys/net/if_pflow.h new file mode 100644 index 00000000000..cd9d2dcc145 --- /dev/null +++ b/sys/net/if_pflow.h @@ -0,0 +1,123 @@ +/* $OpenBSD: if_pflow.h,v 1.1 2008/09/09 13:56:39 henning Exp $ */ + +/* + * Copyright (c) 2008 Henning Brauer <henning@openbsd.org> + * Copyright (c) 2008 Joerg Goltermann <jg@osn.de> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER IN + * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT + * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _NET_IF_PFLOW_H_ +#define _NET_IF_PFLOW_H_ + +#define PFLOW_ID_LEN sizeof(u_int64_t) + +#define PFLOW_MAXFLOWS 30 +#define PFLOW_VERSION 5 +#define PFLOW_ENGINE_TYPE 42 +#define PFLOW_ENGINE_ID 42 +#define PFLOW_MAXBYTES 0xffffffff +#define PFLOW_TIMEOUT 30 + +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; + +#ifdef _KERNEL + +extern int pflow_ok; + +union sc_flowp { + struct pflow_flow *s; +}; + +struct pflow_softc { + struct ifnet sc_if; + struct ifnet *sc_pflow_ifp; + + unsigned int sc_count; + unsigned int sc_maxcount; + u_int32_t sc_gcounter; + struct ip_moptions sc_imo; + struct timeout sc_tmo; + struct in_addr sc_sender_ip; + u_int16_t sc_sender_port; + struct in_addr sc_receiver_ip; + u_int16_t sc_receiver_port; + union sc_flowp sc_flowp; + struct mbuf *sc_mbuf; /* current cumulative mbuf */ +}; + +extern struct pflow_softc *pflowif; + +#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 pflowstats { + u_int64_t pflow_flows; + u_int64_t pflow_packets; + u_int64_t pflow_onomem; + u_int64_t pflow_oerrors; +}; + +/* + * Configuration structure for SIOCSETPFLOW SIOCGETPFLOW + */ +struct pflowreq { + struct in_addr sender_ip; + struct in_addr receiver_ip; + u_int16_t receiver_port; + u_int16_t addrmask; +#define PFLOW_MASK_SRCIP 0x01 +#define PFLOW_MASK_DSTIP 0x02 +#define PFLOW_MASK_DSTPRT 0x04 +}; + +#ifdef _KERNEL +int export_pflow(struct pf_state *); +#endif /* _KERNEL */ + +#endif /* _NET_IF_PFLOW_H_ */ diff --git a/sys/net/if_types.h b/sys/net/if_types.h index bac91ccfc73..2de5f6b47aa 100644 --- a/sys/net/if_types.h +++ b/sys/net/if_types.h @@ -1,4 +1,4 @@ -/* $OpenBSD: if_types.h,v 1.18 2005/01/14 12:04:02 grange Exp $ */ +/* $OpenBSD: if_types.h,v 1.19 2008/09/09 13:56:39 henning Exp $ */ /* $NetBSD: if_types.h,v 1.17 2000/10/26 06:51:31 onoe Exp $ */ /* @@ -267,5 +267,6 @@ #define IFT_PFSYNC 0xf6 /* Packet filter state syncing */ #define IFT_CARP 0xf7 /* Common Address Redundancy Protocol */ #define IFT_BLUETOOTH 0xf8 /* Bluetooth */ +#define IFT_PFLOW 0xf9 /* pflow */ #endif /* _NET_IF_TYPES_H_ */ diff --git a/sys/net/pf.c b/sys/net/pf.c index ed0831a698f..61f63b837a3 100644 --- a/sys/net/pf.c +++ b/sys/net/pf.c @@ -1,4 +1,4 @@ -/* $OpenBSD: pf.c,v 1.618 2008/09/03 12:57:19 henning Exp $ */ +/* $OpenBSD: pf.c,v 1.619 2008/09/09 13:56:39 henning Exp $ */ /* * Copyright (c) 2001 Daniel Hartmeier @@ -38,6 +38,7 @@ #include "bpfilter.h" #include "pflog.h" #include "pfsync.h" +#include "pflow.h" #include <sys/param.h> #include <sys/systm.h> @@ -78,6 +79,7 @@ #include <dev/rndvar.h> #include <net/pfvar.h> #include <net/if_pflog.h> +#include <net/if_pflow.h> #if NPFSYNC > 0 #include <net/if_pfsync.h> @@ -1090,6 +1092,10 @@ pf_unlink_state(struct pf_state *cur) TH_RST|TH_ACK, 0, 0, 0, 1, cur->tag, NULL, NULL); } RB_REMOVE(pf_state_tree_id, &tree_id, cur); +#if NPFLOW + if (cur->state_flags & PFSTATE_PFLOW) + export_pflow(cur); +#endif #if NPFSYNC if (cur->creatorid == pf_status.hostid) pfsync_delete_state(cur); @@ -3453,6 +3459,8 @@ pf_create_state(struct pf_rule *r, struct pf_rule *nr, struct pf_rule *a, s->state_flags |= PFSTATE_ALLOWOPTS; if (r->rule_flag & PFRULE_STATESLOPPY) s->state_flags |= PFSTATE_SLOPPY; + if (r->rule_flag & PFRULE_PFLOW) + s->state_flags |= PFSTATE_PFLOW; s->log = r->log & PF_LOG_ALL; if (nr != NULL) s->log |= nr->log & PF_LOG_ALL; diff --git a/sys/net/pfvar.h b/sys/net/pfvar.h index 2e5b13a3e42..3354151eba8 100644 --- a/sys/net/pfvar.h +++ b/sys/net/pfvar.h @@ -1,4 +1,4 @@ -/* $OpenBSD: pfvar.h,v 1.277 2008/08/26 12:17:10 henning Exp $ */ +/* $OpenBSD: pfvar.h,v 1.278 2008/09/09 13:56:39 henning Exp $ */ /* * Copyright (c) 2001 Daniel Hartmeier @@ -627,6 +627,7 @@ struct pf_rule { /* rule flags again */ #define PFRULE_IFBOUND 0x00010000 /* if-bound */ #define PFRULE_STATESLOPPY 0x00020000 /* sloppy state tracking */ +#define PFRULE_PFLOW 0x00040000 #define PFSTATE_HIWAT 10000 /* default state table size */ #define PFSTATE_ADAPT_START 6000 /* default adaptive timeout start */ @@ -766,9 +767,10 @@ struct pf_state { #define PFSTATE_SLOPPY 0x02 u_int8_t timeout; u_int8_t sync_flags; -#define PFSTATE_NOSYNC 0x01 -#define PFSTATE_FROMSYNC 0x02 -#define PFSTATE_STALE 0x04 +#define PFSTATE_NOSYNC 0x01 +#define PFSTATE_FROMSYNC 0x02 +#define PFSTATE_STALE 0x04 +#define PFSTATE_PFLOW 0x08 }; /* diff --git a/sys/sys/sockio.h b/sys/sys/sockio.h index e5dfde95c4e..f3a4a722368 100644 --- a/sys/sys/sockio.h +++ b/sys/sys/sockio.h @@ -1,4 +1,4 @@ -/* $OpenBSD: sockio.h,v 1.40 2008/05/06 13:33:50 pyr Exp $ */ +/* $OpenBSD: sockio.h,v 1.41 2008/09/09 13:56:39 henning Exp $ */ /* $NetBSD: sockio.h,v 1.5 1995/08/23 00:40:47 thorpej Exp $ */ /*- @@ -173,4 +173,7 @@ #define SIOCSETPFSYNC _IOW('i', 247, struct ifreq) #define SIOCGETPFSYNC _IOWR('i', 248, struct ifreq) +#define SIOCSETPFLOW _IOW('i', 249, struct ifreq) +#define SIOCGETPFLOW _IOWR('i', 250, struct ifreq) + #endif /* !_SYS_SOCKIO_H_ */ |