Index: head/ObsoleteFiles.inc =================================================================== --- head/ObsoleteFiles.inc +++ head/ObsoleteFiles.inc @@ -38,6 +38,12 @@ # xargs -n1 | sort | uniq -d; # done +# 20190131: pfil(9) changed +OLD_FILES+=usr/share/man/man9/pfil_hook_get.9 +OLD_FILES+=usr/share/man/man9/pfil_rlock.9 +OLD_FILES+=usr/share/man/man9/pfil_runlock.9 +OLD_FILES+=usr/share/man/man9/pfil_wlock.9 +OLD_FILES+=usr/share/man/man9/pfil_wunlock.9 # 20190126: adv(4) / adw(4) removal OLD_FILES+=usr/share/man/man4/adv.4.gz OLD_FILES+=usr/share/man/man4/adw.4.gz Index: head/sbin/Makefile =================================================================== --- head/sbin/Makefile +++ head/sbin/Makefile @@ -52,6 +52,7 @@ newfs_msdos \ nfsiod \ nos-tun \ + pfilctl \ ping \ rcorder \ reboot \ Index: head/sbin/pfilctl/Makefile =================================================================== --- head/sbin/pfilctl/Makefile +++ head/sbin/pfilctl/Makefile @@ -0,0 +1,9 @@ +# $FreeBSD$ + +PROG= pfilctl +SRCS= pfilctl.c +WARNS?= 6 + +MAN= pfilctl.8 + +.include Index: head/sbin/pfilctl/pfilctl.8 =================================================================== --- head/sbin/pfilctl/pfilctl.8 +++ head/sbin/pfilctl/pfilctl.8 @@ -0,0 +1,117 @@ +.\" Copyright (c) 2019 Gleb Smirnoff +.\" +.\" 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. +.\" +.\" $FreeBSD$ +.\" +.Dd January 28, 2019 +.Dt PFILCTL 8 +.Os +.Sh NAME +.Nm pfilctl +.Nd pfil(9) control utility +.Sh SYNOPSIS +.Nm +.Cm heads +.Nm +.Cm hooks +.Nm +.Cm link +.Aq Fl i | Fl o +.Op Fl a +.Ar hook Ar head +.Nm +.Cm unlink +.Aq Fl i | Fl o +.Ar hook Ar head +.Sh DESCRIPTION +The +.Nm +utility is intended to view and change configuration of the +.Xr pfil 9 +packet filtering hooks and filters on them. +.Sh COMMANDS +.Bl -tag -width "unlink" +.It Cm heads +List available packet filtering points. +.It Cm hooks +List available packet filters. +.It Xo +.Cm link +.Aq Fl i | Fl o +.Op Fl a +.Ar hook Ar head +.Xc +Link +.Ar hook +to +.Ar head . +With the +.Fl i +flag the hook will be connected as input and with +.Fl o +as output hook. +At least one of +.Fl i +or +.Fl o +is required. +By default +.Nm +will prepend the hook in front of other hooks if any present: +new hook will be as close to the wire as possible, so that on input +it will be the first filter and on output it will be the last. +Adding the +.Fl a +flag switches to appending new hook instead of prepending. +.It Xo +.Cm unlink +.Aq Fl i | Fl o +.Ar hook Ar head +.Xc +Unlink +.Ar hook +on +.Ar head . +At least one of +.Fl i +or +.Fl o +is required. +With the +.Fl i +flag the hook will be removed from the input list of hooks +and with +.Fl o +on output list. +.El +.Sh SEE ALSO +.Xr ipfilter 4 , +.Xr ipfw 4 , +.Xr pf 4 , +.Xr pfil 9 +.Sh AUTHORS +.An -nosplit +The +.Nm +utility was written by +.An Gleb Smirnoff Aq Mt glebius@FreeBSD.org . Index: head/sbin/pfilctl/pfilctl.c =================================================================== --- head/sbin/pfilctl/pfilctl.c +++ head/sbin/pfilctl/pfilctl.c @@ -0,0 +1,230 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2019 Gleb Smirnoff + * + * 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. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +static int dev; + +static const char * const typenames[] = { + [PFIL_TYPE_IP4] = "IPv4", + [PFIL_TYPE_IP6] = "IPv6", + [PFIL_TYPE_ETHERNET] = "Ethernet", +}; + +static void listheads(int argc, char *argv[]); +static void listhooks(int argc, char *argv[]); +static void hook(int argc, char *argv[]); +static void help(void); + +static const struct cmd { + const char *cmd_name; + void (*cmd_func)(int argc, char *argv[]); +} cmds[] = { + { "heads", listheads }, + { "hooks", listhooks }, + { "link", hook }, + { "unlink", hook }, + { NULL, NULL }, +}; + +int +main(int argc __unused, char *argv[] __unused) +{ + int cmd = -1; + + if (--argc == 0) + help(); + argv++; + + for (int i = 0; cmds[i].cmd_name != NULL; i++) + if (!strncmp(argv[0], cmds[i].cmd_name, strlen(argv[0]))) { + if (cmd != -1) + errx(1, "ambiguous command: %s", argv[0]); + cmd = i; + } + if (cmd == -1) + errx(1, "unknown command: %s", argv[0]); + + dev = open("/dev/" PFILDEV, O_RDWR); + if (dev == -1) + err(1, "open(%s)", "/dev/" PFILDEV); + + (*cmds[cmd].cmd_func)(argc, argv); + + return (0); +} + +static void +help(void) +{ + extern char *__progname; + + fprintf(stderr, "usage: %s (heads|hooks|link|unlink)\n", __progname); + exit(0); +} + +static void +listheads(int argc __unused, char *argv[] __unused) +{ + struct pfilioc_list plh; + u_int nheads, nhooks, i; + int j, h; + + plh.pio_nheads = 0; + plh.pio_nhooks = 0; + if (ioctl(dev, PFILIOC_LISTHEADS, &plh) != 0) + err(1, "ioctl(PFILIOC_LISTHEADS)"); + +retry: + plh.pio_heads = calloc(plh.pio_nheads, sizeof(struct pfilioc_head)); + if (plh.pio_heads == NULL) + err(1, "malloc"); + plh.pio_hooks = calloc(plh.pio_nhooks, sizeof(struct pfilioc_hook)); + if (plh.pio_hooks == NULL) + err(1, "malloc"); + + nheads = plh.pio_nheads; + nhooks = plh.pio_nhooks; + + if (ioctl(dev, PFILIOC_LISTHEADS, &plh) != 0) + err(1, "ioctl(PFILIOC_LISTHEADS)"); + + if (plh.pio_nheads > nheads || plh.pio_nhooks > nhooks) { + free(plh.pio_heads); + free(plh.pio_hooks); + goto retry; + } + +#define FMTHD "%16s %8s\n" +#define FMTHK "%29s %16s %16s\n" + printf(FMTHD, "Intercept point", "Type"); + for (i = 0, h = 0; i < plh.pio_nheads; i++) { + printf(FMTHD, plh.pio_heads[i].pio_name, + typenames[plh.pio_heads[i].pio_type]); + for (j = 0; j < plh.pio_heads[i].pio_nhooksin; j++, h++) + printf(FMTHK, "In", plh.pio_hooks[h].pio_module, + plh.pio_hooks[h].pio_ruleset); + for (j = 0; j < plh.pio_heads[i].pio_nhooksout; j++, h++) + printf(FMTHK, "Out", plh.pio_hooks[h].pio_module, + plh.pio_hooks[h].pio_ruleset); + } +} + +static void +listhooks(int argc __unused, char *argv[] __unused) +{ + struct pfilioc_list plh; + u_int nhooks, i; + + plh.pio_nhooks = 0; + if (ioctl(dev, PFILIOC_LISTHEADS, &plh) != 0) + err(1, "ioctl(PFILIOC_LISTHEADS)"); +retry: + plh.pio_hooks = calloc(plh.pio_nhooks, sizeof(struct pfilioc_hook)); + if (plh.pio_hooks == NULL) + err(1, "malloc"); + + nhooks = plh.pio_nhooks; + + if (ioctl(dev, PFILIOC_LISTHOOKS, &plh) != 0) + err(1, "ioctl(PFILIOC_LISTHOOKS)"); + + if (plh.pio_nhooks > nhooks) { + free(plh.pio_hooks); + goto retry; + } + + printf("Available hooks:\n"); + for (i = 0; i < plh.pio_nhooks; i++) { + printf("\t%s:%s %s\n", plh.pio_hooks[i].pio_module, + plh.pio_hooks[i].pio_ruleset, + typenames[plh.pio_hooks[i].pio_type]); + } +} + +static void +hook(int argc, char *argv[]) +{ + struct pfilioc_link req; + int c; + char *ruleset; + + if (argv[0][0] == 'u') + req.pio_flags = PFIL_UNLINK; + else + req.pio_flags = 0; + + while ((c = getopt(argc, argv, "ioa")) != -1) + switch (c) { + case 'i': + req.pio_flags |= PFIL_IN; + break; + case 'o': + req.pio_flags |= PFIL_OUT; + break; + case 'a': + req.pio_flags |= PFIL_APPEND; + break; + default: + help(); + } + + if (!PFIL_DIR(req.pio_flags)) + help(); + + argc -= optind; + argv += optind; + + if (argc != 2) + help(); + + /* link mod:ruleset head */ + if ((ruleset = strchr(argv[0], ':')) == NULL) + help(); + *ruleset = '\0'; + ruleset++; + + strlcpy(req.pio_name, argv[1], sizeof(req.pio_name)); + strlcpy(req.pio_module, argv[0], sizeof(req.pio_module)); + strlcpy(req.pio_ruleset, ruleset, sizeof(req.pio_ruleset)); + + if (ioctl(dev, PFILIOC_LINK, &req) != 0) + err(1, "ioctl(PFILIOC_LINK)"); +} Index: head/share/man/man9/Makefile =================================================================== --- head/share/man/man9/Makefile +++ head/share/man/man9/Makefile @@ -1635,13 +1635,9 @@ MLINKS+=pfil.9 pfil_add_hook.9 \ pfil.9 pfil_head_register.9 \ pfil.9 pfil_head_unregister.9 \ - pfil.9 pfil_hook_get.9 \ pfil.9 pfil_remove_hook.9 \ - pfil.9 pfil_rlock.9 \ pfil.9 pfil_run_hooks.9 \ - pfil.9 pfil_runlock.9 \ - pfil.9 pfil_wlock.9 \ - pfil.9 pfil_wunlock.9 + pfil.9 pfil_link.9 MLINKS+=pfind.9 zpfind.9 MLINKS+=PHOLD.9 PRELE.9 \ PHOLD.9 _PHOLD.9 \ Index: head/share/man/man9/pfil.9 =================================================================== --- head/share/man/man9/pfil.9 +++ head/share/man/man9/pfil.9 @@ -1,5 +1,6 @@ .\" $NetBSD: pfil.9,v 1.22 2003/07/01 13:04:06 wiz Exp $ .\" +.\" Copyright (c) 2019 Gleb Smirnoff .\" Copyright (c) 1996 Matthew R. Green .\" All rights reserved. .\" @@ -28,194 +29,127 @@ .\" .\" $FreeBSD$ .\" -.Dd March 10, 2018 +.Dd January 28, 2019 .Dt PFIL 9 .Os .Sh NAME .Nm pfil , .Nm pfil_head_register , .Nm pfil_head_unregister , -.Nm pfil_head_get , -.Nm pfil_add_hook , -.Nm pfil_add_hook_flags , -.Nm pfil_remove_hook , -.Nm pfil_remove_hook_flags , -.Nm pfil_run_hooks , -.Nm pfil_rlock , -.Nm pfil_runlock , -.Nm pfil_wlock , -.Nm pfil_wunlock +.Nm pfil_link , +.Nm pfil_run_hooks .Nd packet filter interface .Sh SYNOPSIS .In sys/param.h .In sys/mbuf.h -.In net/if.h .In net/pfil.h -.Bd -literal -typedef int (*pfil_func_t)(void *arg, struct mbuf **mp, struct ifnet *, int dir, struct inpcb); -.Bd -literal -typedef int (*pfil_func_flags_t)(void *arg, struct mbuf **mp, struct ifnet *, int dir, int flags, struct inpcb); -.Ft int -.Fn pfil_head_register "struct pfil_head *head" -.Ft int -.Fn pfil_head_unregister "struct pfil_head *head" -.Ft "struct pfil_head *" -.Fn pfil_head_get "int af" "u_long dlt" -.Ft int -.Fn pfil_add_hook "pfil_func_t" "void *arg" "struct pfil_head *" -.Ft int -.Fn pfil_add_hook_flags "pfil_func_flags_t" "void *arg" "int flags" "struct pfil_head *" -.Ft int -.Fn pfil_remove_hook "pfil_func_t" "void *arg" "struct pfil_head *" -.Ft int -.Fn pfil_remove_hook_flags "pfil_func_flags_t" "void *arg" "int flags" "struct pfil_head *" -.Ft int -.Fn pfil_run_hooks "struct pfil_head *head" "struct mbuf **mp" "struct ifnet *" "int dir" "int flags" "struct inpcb *" +.Ft pfil_head_t +.Fn pfil_head_register "struct pfil_head_args *args" .Ft void -.Fn pfil_rlock "struct pfil_head *" "struct rm_priotracker *" +.Fn pfil_head_unregister "struct pfil_head_t *head" +.Ft pfil_hook_t +.Fn pfil_add_hook "struct pfil_hook_args *" .Ft void -.Fn pfil_runlock "struct pfil_head *" "struct rm_priotracker *" -.Ft void -.Fn pfil_wlock "struct pfil_head *" -.Ft void -.Fn pfil_wunlock "struct pfil_head *" -.Ed +.Fn pfil_remove_hook "pfil_hook_t" +.Ft int +.Fn pfil_link "struct pfil_link_args *args" +.Ft int +.Fn pfil_run_hooks "phil_head_t *" "pfil_packet_t" "struct ifnet *" "int" "struct inpcb *" .Sh DESCRIPTION The .Nm -framework allows for a specified function to be invoked for every -incoming or outgoing packet for a particular network I/O stream. +framework allows for a specified function or a list of functions +to be invoked for every incoming or outgoing packet for a particular +network I/O stream. These hooks may be used to implement a firewall or perform packet transformations. .Pp -Packet filtering points are registered with +Packet filtering points, for historical reasons named +.Em heads , +are registered with .Fn pfil_head_register . -Filtering points are identified by a key -.Pq Vt "void *" -and a data link type -.Pq Vt int -in the -.Vt pfil_head -structure. -Packet filters use the key and data link type to look up the filtering -point with which they register themselves. -The key is unique to the filtering point. -The data link type is a -.Xr bpf 4 -DLT constant indicating what kind of header is present on the packet -at the filtering point. -Each filtering point uses common per-VNET rmlock by default. -This can be changed by specifying -.Vt PFIL_FLAG_PRIVATE_LOCK -as -.Vt "flags" -field in the -.Vt pfil_head -structure. -Note that specifying private lock can break filters sharing the same -ruleset and/or state between different data link types. -Filtering points may be unregistered with the -.Fn pfil_head_unregister -function. +The function is supplied with special versioned +.Vt struct pfil_head_args +structure that specifies type and features of the head as well as +human readable name. +If the filtering point to be ever destroyed, the subsystem that +created it must unregister it with call to +.Fn pfil_head_unregister . .Pp -Packet filters register/unregister themselves with a filtering point -with the +Packet filtering systems may register arbitrary number of filters, +for historical reasons named +.Em hooks . +To register a new hook .Fn pfil_add_hook -and +with special versioned +.Vt struct pfil_hook_args +structure is called. +The structure specifies type and features of the hook, pointer to +the actual filtering function and user readable name of the filtering +module and ruleset name. +Later hooks can be removed with .Fn pfil_remove_hook -functions, respectively. -.I -The head is looked up using the -.Fn pfil_head_get -function, which takes the key and data link type that the packet filter -expects. -Filters may provide an argument to be passed to the filter when -invoked on a packet. +functions. .Pp -When a filter is invoked, the packet appears just as if it -.Dq came off the wire . -That is, all protocol fields are in network byte order. -The filter is called with its specified argument, the pointer to the -pointer to the -.Vt mbuf -containing the packet, the pointer to the network -interface that the packet is traversing, and the direction -.Dv ( PFIL_IN -or -.Dv PFIL_OUT ) -that the packet is traveling. -The -.Vt flags -argument will indicate if an outgoing packet is simply being forwarded with the -value PFIL_FWD. -The filter may change which mbuf the -.Vt "mbuf\ **" -argument references. -The filter returns an error (errno) if the packet processing is to stop, or 0 -if the processing is to continue. -If the packet processing is to stop, it is the responsibility of the -filter to free the packet. -.Pp -Every filter hook is called with +To connect existing +.Em hook +to an existing +.Em head +function +.Fn pfil_link +shall be used. +The function is supplied with versioned +.Vt struct pfil_link_args +structure that specifies either literal names of hook and head or +pointers to them. +Typically +.Fn pfil_link +is called by filtering modules to autoregister their default ruleset +and default filtering points. +It also serves on the kernel side of +.Xr ioctl 2 +when user changes .Nm -read lock held. -All heads uses the same lock within the same VNET instance. -Packet filter can use this lock instead of own locking model to -improve performance. -Since -.Nm -uses -.Xr rmlock 9 -.Fn pfil_rlock -and -.Fn pfil_runlock -require -.Va struct rm_priotracker -to be passed as argument. -Filter can acquire and release writer lock via -.Fn pfil_wlock -and -.Fn pfil_wunlock -functions. -See -.Xr rmlock 9 -for more details. -.Sh FILTERING POINTS -Currently, filtering points are implemented for the following link types: +configuration with help of +.Xr pfilctl 8 +utility. .Pp -.Bl -tag -width "AF_INET6" -offset XXX -compact -.It AF_INET +For every packet traveling through a +.Em head +the latter shall invoke +.Fn pfil_run_hooks . +The function can accept either +.Vt struct mbuf * +pointer or a +.Vt void * +pointer and length. +In case if a hooked filtering module cannot understand +.Vt void * +pointer +.Nm +will provide it with a fake one. +All calls to +.Fn pfil_run_hooks +are performed in network +.Xr epoch 9 . +.Sh HEADS (filtering points) +By default kernel creates the following heads: +.Bl -tag -width "ethernet" +.It inet IPv4 packets. -.It AF_INET6 +.It inet6 IPv6 packets. -.It AF_LINK +.It ethernet Link-layer packets. .El -.Sh RETURN VALUES -If successful, -.Fn pfil_head_get -returns the -.Vt pfil_head -structure for the given key/dlt. -The -.Fn pfil_add_hook -and -.Fn pfil_remove_hook -functions -return 0 if successful. -If called with flag -.Dv PFIL_WAITOK , -.Fn pfil_remove_hook -is expected to always succeed. .Pp -The -.Fn pfil_head_unregister -function -might sleep! +Default rulesets are automatically linked to these heads to preserve +historical behavavior. .Sh SEE ALSO -.Xr bpf 4 , -.Xr if_bridge 4 , -.Xr rmlock 9 +.Xr ipfilter 4 , +.Xr ipfw 4 , +.Xr pf 4 , +.Xr pfilctl 8 .Sh HISTORY The .Nm @@ -223,45 +157,8 @@ .Nx 1.3 . The .Nm -input and output lists were originally implemented as -.In sys/queue.h -.Dv LIST -structures; -however this was changed in -.Nx 1.4 -to -.Dv TAILQ -structures. -This change was to allow the input and output filters to be processed in -reverse order, to allow the same path to be taken, in or out of the kernel. -.Pp -The -.Nm -interface was changed in 1.4T to accept a 3rd parameter to both -.Fn pfil_add_hook -and -.Fn pfil_remove_hook , -introducing the capability of per-protocol filtering. -This was done primarily in order to support filtering of IPv6. -.Pp -In 1.5K, the -.Nm -framework was changed to work with an arbitrary number of filtering points, -as well as be less IP-centric. -.Pp -Fine-grained locking was added in +interface was imported into .Fx 5.2 . -.Nm -lock export was added in -.Fx 10.0 . -.Sh BUGS -When a -.Vt pfil_head -is being modified, no traffic is diverted -(to avoid deadlock). -This means that traffic may be dropped unconditionally for a short period -of time. -.Fn pfil_run_hooks -will return -.Er ENOBUFS -to indicate this. +In +.Fx 13.0 +the interface was significantly rewritten. Index: head/sys/contrib/ipfilter/netinet/ip_fil_freebsd.c =================================================================== --- head/sys/contrib/ipfilter/netinet/ip_fil_freebsd.c +++ head/sys/contrib/ipfilter/netinet/ip_fil_freebsd.c @@ -25,6 +25,7 @@ # include "opt_random_ip_id.h" #endif #include +#include #include #include #include @@ -126,32 +127,33 @@ -static int -ipf_check_wrapper(void *arg, struct mbuf **mp, struct ifnet *ifp, int dir) +static pfil_return_t +ipf_check_wrapper(struct mbuf **mp, struct ifnet *ifp, int flags, + void *ruleset __unused, struct inpcb *inp) { struct ip *ip = mtod(*mp, struct ip *); - int rv; + pfil_return_t rv; CURVNET_SET(ifp->if_vnet); - rv = ipf_check(&V_ipfmain, ip, ip->ip_hl << 2, ifp, (dir == PFIL_OUT), - mp); + rv = ipf_check(&V_ipfmain, ip, ip->ip_hl << 2, ifp, (flags & PFIL_OUT), + mp); CURVNET_RESTORE(); - return rv; + return (rv == 0 ? PFIL_PASS : PFIL_DROPPED); } -# ifdef USE_INET6 -# include - -static int -ipf_check_wrapper6(void *arg, struct mbuf **mp, struct ifnet *ifp, int dir) +#ifdef USE_INET6 +static pfil_return_t +ipf_check_wrapper6(struct mbuf **mp, struct ifnet *ifp, int flags, + void *ruleset __unused, struct inpcb *inp) { - int error; + pfil_return_t rv; CURVNET_SET(ifp->if_vnet); - error = ipf_check(&V_ipfmain, mtod(*mp, struct ip *), - sizeof(struct ip6_hdr), ifp, (dir == PFIL_OUT), mp); + rv = ipf_check(&V_ipfmain, mtod(*mp, struct ip *), + sizeof(struct ip6_hdr), ifp, (flags & PFIL_OUT), mp); CURVNET_RESTORE(); - return (error); + + return (rv == 0 ? PFIL_PASS : PFIL_DROPPED); } # endif #if defined(IPFILTER_LKM) @@ -1318,53 +1320,62 @@ return error; } +VNET_DEFINE_STATIC(pfil_hook_t, ipf_inet_hook); +VNET_DEFINE_STATIC(pfil_hook_t, ipf_inet6_hook); +#define V_ipf_inet_hook VNET(ipf_inet_hook) +#define V_ipf_inet6_hook VNET(ipf_inet6_hook) + int ipf_pfil_unhook(void) { - struct pfil_head *ph_inet; + + pfil_remove_hook(V_ipf_inet_hook); + #ifdef USE_INET6 - struct pfil_head *ph_inet6; + pfil_remove_hook(V_ipf_inet6_hook); #endif - ph_inet = pfil_head_get(PFIL_TYPE_AF, AF_INET); - if (ph_inet != NULL) - pfil_remove_hook((void *)ipf_check_wrapper, NULL, - PFIL_IN|PFIL_OUT|PFIL_WAITOK, ph_inet); -# ifdef USE_INET6 - ph_inet6 = pfil_head_get(PFIL_TYPE_AF, AF_INET6); - if (ph_inet6 != NULL) - pfil_remove_hook((void *)ipf_check_wrapper6, NULL, - PFIL_IN|PFIL_OUT|PFIL_WAITOK, ph_inet6); -# endif - return (0); } int ipf_pfil_hook(void) { - struct pfil_head *ph_inet; + struct pfil_hook_args pha; + struct pfil_link_args pla; + int error, error6; + + pha.pa_version = PFIL_VERSION; + pha.pa_flags = PFIL_IN | PFIL_OUT; + pha.pa_modname = "ipfilter"; + pha.pa_rulname = "default"; + pha.pa_func = ipf_check_wrapper; + pha.pa_ruleset = NULL; + pha.pa_type = PFIL_TYPE_IP4; + V_ipf_inet_hook = pfil_add_hook(&pha); + #ifdef USE_INET6 - struct pfil_head *ph_inet6; + pha.pa_func = ipf_check_wrapper6; + pha.pa_type = PFIL_TYPE_IP6; + V_ipf_inet6_hook = pfil_add_hook(&pha); #endif - ph_inet = pfil_head_get(PFIL_TYPE_AF, AF_INET); -# ifdef USE_INET6 - ph_inet6 = pfil_head_get(PFIL_TYPE_AF, AF_INET6); -# endif - if (ph_inet == NULL -# ifdef USE_INET6 - && ph_inet6 == NULL -# endif - ) { - return ENODEV; - } + pla.pa_version = PFIL_VERSION; + pla.pa_flags = PFIL_IN | PFIL_OUT | + PFIL_HEADPTR | PFIL_HOOKPTR; + pla.pa_head = V_inet_pfil_head; + pla.pa_hook = V_ipf_inet_hook; + error = pfil_link(&pla); - if (ph_inet != NULL) - pfil_add_hook((void *)ipf_check_wrapper, NULL, - PFIL_IN|PFIL_OUT|PFIL_WAITOK, ph_inet); -# ifdef USE_INET6 - if (ph_inet6 != NULL) - pfil_add_hook((void *)ipf_check_wrapper6, NULL, - PFIL_IN|PFIL_OUT|PFIL_WAITOK, ph_inet6); -# endif - return (0); + error6 = 0; +#ifdef USE_INET6 + pla.pa_head = V_inet6_pfil_head; + pla.pa_hook = V_ipf_inet6_hook; + error6 = pfil_link(&pla); +#endif + + if (error || error6) + error = ENODEV; + else + error = 0; + + return (error); } void Index: head/sys/net/if_bridge.c =================================================================== --- head/sys/net/if_bridge.c +++ head/sys/net/if_bridge.c @@ -1970,9 +1970,9 @@ return; } - if (PFIL_HOOKED(&V_inet_pfil_hook) + if (PFIL_HOOKED_OUT(V_inet_pfil_head) #ifdef INET6 - || PFIL_HOOKED(&V_inet6_pfil_hook) + || PFIL_HOOKED_OUT(V_inet6_pfil_head) #endif ) { if (bridge_pfil(&m, sc->sc_ifp, ifp, PFIL_OUT) != 0) @@ -2230,9 +2230,9 @@ ETHER_BPF_MTAP(ifp, m); /* run the packet filter */ - if (PFIL_HOOKED(&V_inet_pfil_hook) + if (PFIL_HOOKED_IN(V_inet_pfil_head) #ifdef INET6 - || PFIL_HOOKED(&V_inet6_pfil_hook) + || PFIL_HOOKED_IN(V_inet6_pfil_head) #endif ) { BRIDGE_UNLOCK(sc); @@ -2270,9 +2270,9 @@ BRIDGE_UNLOCK(sc); - if (PFIL_HOOKED(&V_inet_pfil_hook) + if (PFIL_HOOKED_OUT(V_inet_pfil_head) #ifdef INET6 - || PFIL_HOOKED(&V_inet6_pfil_hook) + || PFIL_HOOKED_OUT(V_inet6_pfil_head) #endif ) { if (bridge_pfil(&m, ifp, dst_if, PFIL_OUT) != 0) @@ -2409,7 +2409,7 @@ #ifdef INET6 # define OR_PFIL_HOOKED_INET6 \ - || PFIL_HOOKED(&V_inet6_pfil_hook) + || PFIL_HOOKED_IN(V_inet6_pfil_head) #else # define OR_PFIL_HOOKED_INET6 #endif @@ -2427,7 +2427,7 @@ if_inc_counter(iface, IFCOUNTER_IBYTES, m->m_pkthdr.len); \ /* Filter on the physical interface. */ \ if (V_pfil_local_phys && \ - (PFIL_HOOKED(&V_inet_pfil_hook) \ + (PFIL_HOOKED_IN(V_inet_pfil_head) \ OR_PFIL_HOOKED_INET6)) { \ if (bridge_pfil(&m, NULL, ifp, \ PFIL_IN) != 0 || m == NULL) { \ @@ -2517,9 +2517,9 @@ } /* Filter on the bridge interface before broadcasting */ - if (runfilt && (PFIL_HOOKED(&V_inet_pfil_hook) + if (runfilt && (PFIL_HOOKED_OUT(V_inet_pfil_head) #ifdef INET6 - || PFIL_HOOKED(&V_inet6_pfil_hook) + || PFIL_HOOKED_OUT(V_inet6_pfil_head) #endif )) { if (bridge_pfil(&m, sc->sc_ifp, NULL, PFIL_OUT) != 0) @@ -2564,9 +2564,9 @@ * pointer so we do not redundantly filter on the bridge for * each interface we broadcast on. */ - if (runfilt && (PFIL_HOOKED(&V_inet_pfil_hook) + if (runfilt && (PFIL_HOOKED_OUT(V_inet_pfil_head) #ifdef INET6 - || PFIL_HOOKED(&V_inet6_pfil_hook) + || PFIL_HOOKED_OUT(V_inet6_pfil_head) #endif )) { if (used == 0) { @@ -3101,6 +3101,7 @@ struct ip *ip; struct llc llc1; u_int16_t ether_type; + pfil_return_t rv; snap = 0; error = -1; /* Default error if not error == 0 */ @@ -3172,14 +3173,14 @@ } /* Run the packet through pfil before stripping link headers */ - if (PFIL_HOOKED(&V_link_pfil_hook) && V_pfil_ipfw != 0 && - dir == PFIL_OUT && ifp != NULL) { - - error = pfil_run_hooks(&V_link_pfil_hook, mp, ifp, dir, 0, - NULL); - - if (*mp == NULL || error != 0) /* packet consumed by filter */ - return (error); + if (PFIL_HOOKED_OUT(V_link_pfil_head) && V_pfil_ipfw != 0 && + dir == PFIL_OUT && ifp != NULL) { + switch (pfil_run_hooks(V_link_pfil_head, mp, ifp, dir, NULL)) { + case PFIL_DROPPED: + return (EPERM); + case PFIL_CONSUMED: + return (0); + } } /* Strip off the Ethernet header and keep a copy. */ @@ -3217,6 +3218,7 @@ /* * Run the packet through pfil */ + rv = PFIL_PASS; switch (ether_type) { case ETHERTYPE_IP: /* @@ -3226,25 +3228,19 @@ * Keep the order: * in_if -> bridge_if -> out_if */ - if (V_pfil_bridge && dir == PFIL_OUT && bifp != NULL) - error = pfil_run_hooks(&V_inet_pfil_hook, mp, bifp, - dir, 0, NULL); - - if (*mp == NULL || error != 0) /* filter may consume */ + if (V_pfil_bridge && dir == PFIL_OUT && bifp != NULL && (rv = + pfil_run_hooks(V_inet_pfil_head, mp, bifp, dir, NULL)) != + PFIL_PASS) break; - if (V_pfil_member && ifp != NULL) - error = pfil_run_hooks(&V_inet_pfil_hook, mp, ifp, - dir, 0, NULL); - - if (*mp == NULL || error != 0) /* filter may consume */ + if (V_pfil_member && ifp != NULL && (rv = + pfil_run_hooks(V_inet_pfil_head, mp, ifp, dir, NULL)) != + PFIL_PASS) break; - if (V_pfil_bridge && dir == PFIL_IN && bifp != NULL) - error = pfil_run_hooks(&V_inet_pfil_hook, mp, bifp, - dir, 0, NULL); - - if (*mp == NULL || error != 0) /* filter may consume */ + if (V_pfil_bridge && dir == PFIL_IN && bifp != NULL && (rv = + pfil_run_hooks(V_inet_pfil_head, mp, bifp, dir, NULL)) != + PFIL_PASS) break; /* check if we need to fragment the packet */ @@ -3280,34 +3276,32 @@ break; #ifdef INET6 case ETHERTYPE_IPV6: - if (V_pfil_bridge && dir == PFIL_OUT && bifp != NULL) - error = pfil_run_hooks(&V_inet6_pfil_hook, mp, bifp, - dir, 0, NULL); - - if (*mp == NULL || error != 0) /* filter may consume */ + if (V_pfil_bridge && dir == PFIL_OUT && bifp != NULL && (rv = + pfil_run_hooks(V_inet6_pfil_head, mp, bifp, dir, NULL)) != + PFIL_PASS) break; - if (V_pfil_member && ifp != NULL) - error = pfil_run_hooks(&V_inet6_pfil_hook, mp, ifp, - dir, 0, NULL); - - if (*mp == NULL || error != 0) /* filter may consume */ + if (V_pfil_member && ifp != NULL && (rv = + pfil_run_hooks(V_inet6_pfil_head, mp, ifp, dir, NULL)) != + PFIL_PASS) break; - if (V_pfil_bridge && dir == PFIL_IN && bifp != NULL) - error = pfil_run_hooks(&V_inet6_pfil_hook, mp, bifp, - dir, 0, NULL); + if (V_pfil_bridge && dir == PFIL_IN && bifp != NULL && (rv = + pfil_run_hooks(V_inet6_pfil_head, mp, bifp, dir, NULL)) != + PFIL_PASS) + break; break; #endif + } + + switch (rv) { + case PFIL_CONSUMED: + return (0); + case PFIL_DROPPED: + return (EPERM); default: - error = 0; break; } - - if (*mp == NULL) - return (error); - if (error != 0) - goto bad; error = -1; Index: head/sys/net/if_enc.c =================================================================== --- head/sys/net/if_enc.c +++ head/sys/net/if_enc.c @@ -285,24 +285,24 @@ switch (hhook_id) { #ifdef INET case AF_INET: - ph = &V_inet_pfil_hook; + ph = V_inet_pfil_head; break; #endif #ifdef INET6 case AF_INET6: - ph = &V_inet6_pfil_hook; + ph = V_inet6_pfil_head; break; #endif default: ph = NULL; } - if (ph == NULL || !PFIL_HOOKED(ph)) + if (ph == NULL || (pdir == PFIL_OUT && !PFIL_HOOKED_OUT(ph)) || + (pdir == PFIL_IN && !PFIL_HOOKED_IN(ph))) return (0); /* Make a packet looks like it was received on enc(4) */ rcvif = (*ctx->mp)->m_pkthdr.rcvif; (*ctx->mp)->m_pkthdr.rcvif = ifp; - if (pfil_run_hooks(ph, ctx->mp, ifp, pdir, 0, ctx->inp) != 0 || - *ctx->mp == NULL) { + if (pfil_run_hooks(ph, ctx->mp, ifp, pdir, ctx->inp) != PFIL_PASS) { *ctx->mp = NULL; /* consumed by filter */ return (EACCES); } Index: head/sys/net/if_ethersubr.c =================================================================== --- head/sys/net/if_ethersubr.c +++ head/sys/net/if_ethersubr.c @@ -90,7 +90,7 @@ CTASSERT(sizeof (struct ether_addr) == ETHER_ADDR_LEN); #endif -VNET_DEFINE(struct pfil_head, link_pfil_hook); /* Packet filter hooks */ +VNET_DEFINE(pfil_head_t, link_pfil_head); /* Packet filter hooks */ /* netgraph node hooks for ng_ether(4) */ void (*ng_ether_input_p)(struct ifnet *ifp, struct mbuf **mp); @@ -457,7 +457,6 @@ int ether_output_frame(struct ifnet *ifp, struct mbuf *m) { - int error; uint8_t pcp; pcp = ifp->if_pcp; @@ -465,15 +464,14 @@ !ether_set_pcp(&m, ifp, pcp)) return (0); - if (PFIL_HOOKED(&V_link_pfil_hook)) { - error = pfil_run_hooks(&V_link_pfil_hook, &m, ifp, - PFIL_OUT, 0, NULL); - if (error != 0) + if (PFIL_HOOKED_OUT(V_link_pfil_head)) + switch (pfil_run_hooks(V_link_pfil_head, &m, ifp, PFIL_OUT, + NULL)) { + case PFIL_DROPPED: return (EACCES); - - if (m == NULL) + case PFIL_CONSUMED: return (0); - } + } #ifdef EXPERIMENTAL #if defined(INET6) && defined(INET) @@ -737,14 +735,14 @@ static void vnet_ether_init(__unused void *arg) { - int i; + struct pfil_head_args args; - /* Initialize packet filter hooks. */ - V_link_pfil_hook.ph_type = PFIL_TYPE_AF; - V_link_pfil_hook.ph_af = AF_LINK; - if ((i = pfil_head_register(&V_link_pfil_hook)) != 0) - printf("%s: WARNING: unable to register pfil link hook, " - "error %d\n", __func__, i); + args.pa_version = PFIL_VERSION; + args.pa_flags = PFIL_IN | PFIL_OUT; + args.pa_type = PFIL_TYPE_ETHERNET; + args.pa_headname = PFIL_ETHER_NAME; + V_link_pfil_head = pfil_head_register(&args); + #ifdef VIMAGE netisr_register_vnet(ðer_nh); #endif @@ -756,11 +754,8 @@ static void vnet_ether_pfil_destroy(__unused void *arg) { - int i; - if ((i = pfil_head_unregister(&V_link_pfil_hook)) != 0) - printf("%s: WARNING: unable to unregister pfil link hook, " - "error %d\n", __func__, i); + pfil_head_unregister(V_link_pfil_head); } VNET_SYSUNINIT(vnet_ether_pfil_uninit, SI_SUB_PROTO_PFIL, SI_ORDER_ANY, vnet_ether_pfil_destroy, NULL); @@ -818,10 +813,8 @@ KASSERT(ifp != NULL, ("%s: NULL interface pointer", __func__)); /* Do not grab PROMISC frames in case we are re-entered. */ - if (PFIL_HOOKED(&V_link_pfil_hook) && !(m->m_flags & M_PROMISC)) { - i = pfil_run_hooks(&V_link_pfil_hook, &m, ifp, PFIL_IN, 0, - NULL); - + if (PFIL_HOOKED_IN(V_link_pfil_head) && !(m->m_flags & M_PROMISC)) { + i = pfil_run_hooks(V_link_pfil_head, &m, ifp, PFIL_IN, NULL); if (i != 0 || m == NULL) return; } Index: head/sys/net/if_var.h =================================================================== --- head/sys/net/if_var.h +++ head/sys/net/if_var.h @@ -95,8 +95,9 @@ CK_STAILQ_HEAD(ifgrouphead, ifg_group); #ifdef _KERNEL -VNET_DECLARE(struct pfil_head, link_pfil_hook); /* packet filter hooks */ -#define V_link_pfil_hook VNET(link_pfil_hook) +VNET_DECLARE(struct pfil_head *, link_pfil_head); +#define V_link_pfil_head VNET(link_pfil_head) +#define PFIL_ETHER_NAME "ethernet" #define HHOOK_IPSEC_INET 0 #define HHOOK_IPSEC_INET6 1 Index: head/sys/net/pfil.h =================================================================== --- head/sys/net/pfil.h +++ head/sys/net/pfil.h @@ -4,6 +4,7 @@ /*- * SPDX-License-Identifier: BSD-3-Clause * + * Copyright (c) 2019 Gleb Smirnoff * Copyright (c) 1996 Matthew R. Green * All rights reserved. * @@ -34,94 +35,158 @@ #ifndef _NET_PFIL_H_ #define _NET_PFIL_H_ -#include -#include -#include -#include -#include -#include +#include +enum pfil_types { + PFIL_TYPE_IP4, + PFIL_TYPE_IP6, + PFIL_TYPE_ETHERNET, +}; + +#define MAXPFILNAME 64 + +struct pfilioc_head { + char pio_name[MAXPFILNAME]; + int pio_nhooksin; + int pio_nhooksout; + enum pfil_types pio_type; +}; + +struct pfilioc_hook { + char pio_module[MAXPFILNAME]; + char pio_ruleset[MAXPFILNAME]; + int pio_flags; + enum pfil_types pio_type; +}; + +struct pfilioc_list { + u_int pio_nheads; + u_int pio_nhooks; + struct pfilioc_head *pio_heads; + struct pfilioc_hook *pio_hooks; +}; + +struct pfilioc_link { + char pio_name[MAXPFILNAME]; + char pio_module[MAXPFILNAME]; + char pio_ruleset[MAXPFILNAME]; + int pio_flags; +}; + +#define PFILDEV "pfil" +#define PFILIOC_LISTHEADS _IOWR('P', 1, struct pfilioc_list) +#define PFILIOC_LISTHOOKS _IOWR('P', 2, struct pfilioc_list) +#define PFILIOC_LINK _IOW('P', 3, struct pfilioc_link) + +#define PFIL_IN 0x00010000 +#define PFIL_OUT 0x00020000 +#define PFIL_FWD 0x00040000 +#define PFIL_DIR(f) ((f) & (PFIL_IN|PFIL_OUT)) +#define PFIL_MEMPTR 0x00080000 +#define PFIL_HEADPTR 0x00100000 +#define PFIL_HOOKPTR 0x00200000 +#define PFIL_APPEND 0x00400000 +#define PFIL_UNLINK 0x00800000 +#define PFIL_LENMASK 0x0000ffff +#define PFIL_LENGTH(f) ((f) & PFIL_LENMASK) + +#ifdef _KERNEL struct mbuf; struct ifnet; struct inpcb; -typedef int (*pfil_func_t)(void *, struct mbuf **, struct ifnet *, int, - struct inpcb *); -typedef int (*pfil_func_flags_t)(void *, struct mbuf **, struct ifnet *, - int, int, struct inpcb *); +typedef union { + struct mbuf **m; + void *mem; +} pfil_packet_t __attribute__((__transparent_union__)); +typedef enum { + PFIL_PASS = 0, + PFIL_DROPPED, + PFIL_CONSUMED, + PFIL_REALLOCED, +} pfil_return_t; + +typedef pfil_return_t (*pfil_func_t)(pfil_packet_t, struct ifnet *, int, + void *, struct inpcb *); /* - * The packet filter hooks are designed for anything to call them to - * possibly intercept the packet. Multiple filter hooks are chained - * together and after each other in the specified order. + * A pfil head is created by a packet intercept point. + * + * A pfil hook is created by a packet filter. + * + * Hooks are chained on heads. Historically some hooking happens + * automatically, e.g. ipfw(4), pf(4) and ipfilter(4) would register + * theirselves on IPv4 and IPv6 input/output. */ -struct packet_filter_hook { - TAILQ_ENTRY(packet_filter_hook) pfil_chain; - pfil_func_t pfil_func; - pfil_func_flags_t pfil_func_flags; - void *pfil_arg; -}; -#define PFIL_IN 0x00000001 -#define PFIL_OUT 0x00000002 -#define PFIL_WAITOK 0x00000004 -#define PFIL_FWD 0x00000008 -#define PFIL_ALL (PFIL_IN|PFIL_OUT) +typedef struct pfil_hook * pfil_hook_t; +typedef struct pfil_head * pfil_head_t; -typedef TAILQ_HEAD(pfil_chain, packet_filter_hook) pfil_chain_t; +/* + * Give us a chance to modify pfil_xxx_args structures in future. + */ +#define PFIL_VERSION 1 -#define PFIL_TYPE_AF 1 /* key is AF_* type */ -#define PFIL_TYPE_IFNET 2 /* key is ifnet pointer */ +/* Argument structure used by packet filters to register themselves. */ +struct pfil_hook_args { + int pa_version; + int pa_flags; + enum pfil_types pa_type; + pfil_func_t pa_func; + void *pa_ruleset; + const char *pa_modname; + const char *pa_rulname; +}; -#define PFIL_FLAG_PRIVATE_LOCK 0x01 /* Personal lock instead of global */ +/* Public functions for pfil hook management by packet filters. */ +pfil_hook_t pfil_add_hook(struct pfil_hook_args *); +void pfil_remove_hook(pfil_hook_t); -/* - * A pfil head is created by each protocol or packet intercept point. - * For packet is then run through the hook chain for inspection. - */ -struct pfil_head { - pfil_chain_t ph_in; - pfil_chain_t ph_out; - int ph_type; - int ph_nhooks; -#if defined( __linux__ ) || defined( _WIN32 ) - rwlock_t ph_mtx; -#else - struct rmlock *ph_plock; /* Pointer to the used lock */ - struct rmlock ph_lock; /* Private lock storage */ - int flags; -#endif +/* Argument structure used by ioctl() and packet filters to set filters. */ +struct pfil_link_args { + int pa_version; + int pa_flags; union { - u_long phu_val; - void *phu_ptr; - } ph_un; -#define ph_af ph_un.phu_val -#define ph_ifnet ph_un.phu_ptr - LIST_ENTRY(pfil_head) ph_list; + const char *pa_headname; + pfil_head_t pa_head; + }; + union { + struct { + const char *pa_modname; + const char *pa_rulname; + }; + pfil_hook_t pa_hook; + }; }; -/* Public functions for pfil hook management by packet filters. */ -struct pfil_head *pfil_head_get(int, u_long); -int pfil_add_hook_flags(pfil_func_flags_t, void *, int, struct pfil_head *); -int pfil_add_hook(pfil_func_t, void *, int, struct pfil_head *); -int pfil_remove_hook_flags(pfil_func_flags_t, void *, int, struct pfil_head *); -int pfil_remove_hook(pfil_func_t, void *, int, struct pfil_head *); -#define PFIL_HOOKED(p) ((p)->ph_nhooks > 0) +/* Public function to configure filter chains. Used by ioctl() and filters. */ +int pfil_link(struct pfil_link_args *); -/* Public functions to run the packet inspection by protocols. */ -int pfil_run_hooks(struct pfil_head *, struct mbuf **, struct ifnet *, int, - int, struct inpcb *inp); +/* Argument structure used by inspection points to register themselves. */ +struct pfil_head_args { + int pa_version; + int pa_flags; + enum pfil_types pa_type; + const char *pa_headname; +}; -/* Public functions for pfil head management by protocols. */ -int pfil_head_register(struct pfil_head *); -int pfil_head_unregister(struct pfil_head *); +/* Public functions for pfil head management by inspection points. */ +pfil_head_t pfil_head_register(struct pfil_head_args *); +void pfil_head_unregister(pfil_head_t); -/* Public pfil locking functions for self managed locks by packet filters. */ -int pfil_try_rlock(struct pfil_head *, struct rm_priotracker *); -void pfil_rlock(struct pfil_head *, struct rm_priotracker *); -void pfil_runlock(struct pfil_head *, struct rm_priotracker *); -void pfil_wlock(struct pfil_head *); -void pfil_wunlock(struct pfil_head *); -int pfil_wowned(struct pfil_head *ph); +/* Public functions to run the packet inspection by inspection points. */ +int pfil_run_hooks(struct pfil_head *, pfil_packet_t, struct ifnet *, int, + struct inpcb *inp); +/* + * Minimally exposed structure to avoid function call in case of absence + * of any filters by protocols and macros to do the check. + */ +struct _pfil_head { + int head_nhooksin; + int head_nhooksout; +}; +#define PFIL_HOOKED_IN(p) (((struct _pfil_head *)(p))->head_nhooksin > 0) +#define PFIL_HOOKED_OUT(p) (((struct _pfil_head *)(p))->head_nhooksout > 0) +#endif /* _KERNEL */ #endif /* _NET_PFIL_H_ */ Index: head/sys/net/pfil.c =================================================================== --- head/sys/net/pfil.c +++ head/sys/net/pfil.c @@ -4,6 +4,7 @@ /*- * SPDX-License-Identifier: BSD-3-Clause * + * Copyright (c) 2019 Gleb Smirnoff * Copyright (c) 1996 Matthew R. Green * All rights reserved. * @@ -32,15 +33,15 @@ */ #include +#include #include +#include #include #include #include -#include #include #include #include -#include #include #include #include @@ -50,426 +51,609 @@ #include #include -static struct mtx pfil_global_lock; +static MALLOC_DEFINE(M_PFIL, "pfil", "pfil(9) packet filter hooks"); -MTX_SYSINIT(pfil_heads_lock, &pfil_global_lock, "pfil_head_list lock", - MTX_DEF); +static int pfil_ioctl(struct cdev *, u_long, caddr_t, int, struct thread *); +static struct cdevsw pfil_cdevsw = { + .d_ioctl = pfil_ioctl, + .d_name = PFILDEV, + .d_version = D_VERSION, +}; +static struct cdev *pfil_dev; -static struct packet_filter_hook *pfil_chain_get(int, struct pfil_head *); -static int pfil_chain_add(pfil_chain_t *, struct packet_filter_hook *, int); -static int pfil_chain_remove(pfil_chain_t *, void *, void *); -static int pfil_add_hook_priv(void *, void *, int, struct pfil_head *, bool); +static struct mtx pfil_lock; +MTX_SYSINIT(pfil_mtxinit, &pfil_lock, "pfil(9) lock", MTX_DEF); +#define PFIL_LOCK() mtx_lock(&pfil_lock) +#define PFIL_UNLOCK() mtx_unlock(&pfil_lock) +#define PFIL_LOCK_ASSERT() mtx_assert(&pfil_lock, MA_OWNED) +#define PFIL_EPOCH net_epoch_preempt +#define PFIL_EPOCH_ENTER(et) epoch_enter_preempt(net_epoch_preempt, &(et)) +#define PFIL_EPOCH_EXIT(et) epoch_exit_preempt(net_epoch_preempt, &(et)) + +struct pfil_hook { + pfil_func_t hook_func; + void *hook_ruleset; + int hook_flags; + int hook_links; + enum pfil_types hook_type; + const char *hook_modname; + const char *hook_rulname; + LIST_ENTRY(pfil_hook) hook_list; +}; + +struct pfil_link { + CK_STAILQ_ENTRY(pfil_link) link_chain; + pfil_func_t link_func; + void *link_ruleset; + int link_flags; + struct pfil_hook *link_hook; + struct epoch_context link_epoch_ctx; +}; + +typedef CK_STAILQ_HEAD(pfil_chain, pfil_link) pfil_chain_t; +struct pfil_head { + int head_nhooksin; + int head_nhooksout; + pfil_chain_t head_in; + pfil_chain_t head_out; + int head_flags; + enum pfil_types head_type; + LIST_ENTRY(pfil_head) head_list; + const char *head_name; +}; + LIST_HEAD(pfilheadhead, pfil_head); -VNET_DEFINE(struct pfilheadhead, pfil_head_list); +VNET_DEFINE_STATIC(struct pfilheadhead, pfil_head_list) = + LIST_HEAD_INITIALIZER(pfil_head_list); #define V_pfil_head_list VNET(pfil_head_list) -VNET_DEFINE(struct rmlock, pfil_lock); -#define V_pfil_lock VNET(pfil_lock) -#define PFIL_LOCK_INIT_REAL(l, t) \ - rm_init_flags(l, "PFil " t " rmlock", RM_RECURSE) -#define PFIL_LOCK_DESTROY_REAL(l) \ - rm_destroy(l) -#define PFIL_LOCK_INIT(p) do { \ - if ((p)->flags & PFIL_FLAG_PRIVATE_LOCK) { \ - PFIL_LOCK_INIT_REAL(&(p)->ph_lock, "private"); \ - (p)->ph_plock = &(p)->ph_lock; \ - } else \ - (p)->ph_plock = &V_pfil_lock; \ -} while (0) -#define PFIL_LOCK_DESTROY(p) do { \ - if ((p)->flags & PFIL_FLAG_PRIVATE_LOCK) \ - PFIL_LOCK_DESTROY_REAL((p)->ph_plock); \ -} while (0) +LIST_HEAD(pfilhookhead, pfil_hook); +VNET_DEFINE_STATIC(struct pfilhookhead, pfil_hook_list) = + LIST_HEAD_INITIALIZER(pfil_hook_list); +#define V_pfil_hook_list VNET(pfil_hook_list) -#define PFIL_TRY_RLOCK(p, t) rm_try_rlock((p)->ph_plock, (t)) -#define PFIL_RLOCK(p, t) rm_rlock((p)->ph_plock, (t)) -#define PFIL_WLOCK(p) rm_wlock((p)->ph_plock) -#define PFIL_RUNLOCK(p, t) rm_runlock((p)->ph_plock, (t)) -#define PFIL_WUNLOCK(p) rm_wunlock((p)->ph_plock) -#define PFIL_WOWNED(p) rm_wowned((p)->ph_plock) +static struct pfil_link *pfil_link_remove(pfil_chain_t *, pfil_hook_t ); +static void pfil_link_free(epoch_context_t); -#define PFIL_HEADLIST_LOCK() mtx_lock(&pfil_global_lock) -#define PFIL_HEADLIST_UNLOCK() mtx_unlock(&pfil_global_lock) +static __noinline int +pfil_fake_mbuf(pfil_func_t func, void *mem, struct ifnet *ifp, int flags, + void *ruleset, struct inpcb *inp) +{ + struct mbuf m, *mp; + pfil_return_t rv; + (void)m_init(&m, M_NOWAIT, MT_DATA, M_NOFREE | M_PKTHDR); + m_extadd(&m, mem, PFIL_LENGTH(flags), NULL, NULL, NULL, 0, EXT_RXRING); + m.m_len = m.m_pkthdr.len = PFIL_LENGTH(flags); + mp = &m; + flags &= ~(PFIL_MEMPTR | PFIL_LENMASK); + + rv = func(&mp, ifp, flags, ruleset, inp); + if (rv == PFIL_PASS && mp != &m) { + /* + * Firewalls that need pfil_fake_mbuf() most likely don't + * know to return PFIL_REALLOCED. + */ + rv = PFIL_REALLOCED; + *(struct mbuf **)mem = mp; + } + + return (rv); +} + /* * pfil_run_hooks() runs the specified packet filter hook chain. */ int -pfil_run_hooks(struct pfil_head *ph, struct mbuf **mp, struct ifnet *ifp, - int dir, int flags, struct inpcb *inp) +pfil_run_hooks(struct pfil_head *head, pfil_packet_t p, struct ifnet *ifp, + int flags, struct inpcb *inp) { - struct rm_priotracker rmpt; - struct packet_filter_hook *pfh; - struct mbuf *m = *mp; - int rv = 0; + struct epoch_tracker et; + pfil_chain_t *pch; + struct pfil_link *link; + pfil_return_t rv, rvi; - PFIL_RLOCK(ph, &rmpt); - KASSERT(ph->ph_nhooks >= 0, ("Pfil hook count dropped < 0")); - for (pfh = pfil_chain_get(dir, ph); pfh != NULL; - pfh = TAILQ_NEXT(pfh, pfil_chain)) { - if (pfh->pfil_func_flags != NULL) { - rv = (*pfh->pfil_func_flags)(pfh->pfil_arg, &m, ifp, - dir, flags, inp); - if (rv != 0 || m == NULL) - break; + if (PFIL_DIR(flags) == PFIL_IN) + pch = &head->head_in; + else if (__predict_true(PFIL_DIR(flags) == PFIL_OUT)) + pch = &head->head_out; + else + panic("%s: bogus flags %d", __func__, flags); + + rv = PFIL_PASS; + PFIL_EPOCH_ENTER(et); + CK_STAILQ_FOREACH(link, pch, link_chain) { + if ((flags & PFIL_MEMPTR) && !(link->link_flags & PFIL_MEMPTR)) + rvi = pfil_fake_mbuf(link->link_func, p.mem, ifp, + flags, link->link_ruleset, inp); + else + rvi = (*link->link_func)(p, ifp, flags, + link->link_ruleset, inp); + if (rvi == PFIL_DROPPED || rvi == PFIL_CONSUMED) { + rv = rvi; + break; + } else if (rv == PFIL_REALLOCED) { + flags &= ~(PFIL_MEMPTR | PFIL_LENMASK); + rv = rvi; } - if (pfh->pfil_func != NULL) { - rv = (*pfh->pfil_func)(pfh->pfil_arg, &m, ifp, dir, - inp); - if (rv != 0 || m == NULL) - break; - } } - PFIL_RUNLOCK(ph, &rmpt); - *mp = m; - return (rv); + PFIL_EPOCH_EXIT(et); + return (rvi); } -static struct packet_filter_hook * -pfil_chain_get(int dir, struct pfil_head *ph) -{ - - if (dir == PFIL_IN) - return (TAILQ_FIRST(&ph->ph_in)); - else if (dir == PFIL_OUT) - return (TAILQ_FIRST(&ph->ph_out)); - else - return (NULL); -} - /* - * pfil_try_rlock() acquires rm reader lock for specified head - * if this is immediately possible. + * pfil_head_register() registers a pfil_head with the packet filter hook + * mechanism. */ -int -pfil_try_rlock(struct pfil_head *ph, struct rm_priotracker *tracker) +pfil_head_t +pfil_head_register(struct pfil_head_args *pa) { + struct pfil_head *head, *list; - return (PFIL_TRY_RLOCK(ph, tracker)); + MPASS(pa->pa_version == PFIL_VERSION); + + head = malloc(sizeof(struct pfil_head), M_PFIL, M_WAITOK); + + head->head_nhooksin = head->head_nhooksout = 0; + head->head_flags = pa->pa_flags; + head->head_type = pa->pa_type; + head->head_name = pa->pa_headname; + CK_STAILQ_INIT(&head->head_in); + CK_STAILQ_INIT(&head->head_out); + + PFIL_LOCK(); + LIST_FOREACH(list, &V_pfil_head_list, head_list) + if (strcmp(pa->pa_headname, list->head_name) == 0) { + printf("pfil: duplicate head \"%s\"\n", + pa->pa_headname); + } + LIST_INSERT_HEAD(&V_pfil_head_list, head, head_list); + PFIL_UNLOCK(); + + return (head); } /* - * pfil_rlock() acquires rm reader lock for specified head. + * pfil_head_unregister() removes a pfil_head from the packet filter hook + * mechanism. The producer of the hook promises that all outstanding + * invocations of the hook have completed before it unregisters the hook. */ void -pfil_rlock(struct pfil_head *ph, struct rm_priotracker *tracker) +pfil_head_unregister(pfil_head_t ph) { + struct pfil_link *link, *next; - PFIL_RLOCK(ph, tracker); + PFIL_LOCK(); + LIST_REMOVE(ph, head_list); + + CK_STAILQ_FOREACH_SAFE(link, &ph->head_in, link_chain, next) { + link->link_hook->hook_links--; + free(link, M_PFIL); + } + CK_STAILQ_FOREACH_SAFE(link, &ph->head_out, link_chain, next) { + link->link_hook->hook_links--; + free(link, M_PFIL); + } + PFIL_UNLOCK(); } -/* - * pfil_runlock() releases reader lock for specified head. - */ -void -pfil_runlock(struct pfil_head *ph, struct rm_priotracker *tracker) +pfil_hook_t +pfil_add_hook(struct pfil_hook_args *pa) { + struct pfil_hook *hook, *list; - PFIL_RUNLOCK(ph, tracker); + MPASS(pa->pa_version == PFIL_VERSION); + + hook = malloc(sizeof(struct pfil_hook), M_PFIL, M_WAITOK | M_ZERO); + hook->hook_func = pa->pa_func; + hook->hook_ruleset = pa->pa_ruleset; + hook->hook_flags = pa->pa_flags; + hook->hook_type = pa->pa_type; + hook->hook_modname = pa->pa_modname; + hook->hook_rulname = pa->pa_rulname; + + PFIL_LOCK(); + LIST_FOREACH(list, &V_pfil_hook_list, hook_list) + if (strcmp(pa->pa_modname, list->hook_modname) == 0 && + strcmp(pa->pa_rulname, list->hook_rulname) == 0) { + printf("pfil: duplicate hook \"%s:%s\"\n", + pa->pa_modname, pa->pa_rulname); + } + LIST_INSERT_HEAD(&V_pfil_hook_list, hook, hook_list); + PFIL_UNLOCK(); + + return (hook); } -/* - * pfil_wlock() acquires writer lock for specified head. - */ -void -pfil_wlock(struct pfil_head *ph) +static int +pfil_unlink(struct pfil_link_args *pa, pfil_head_t head, pfil_hook_t hook) { + struct pfil_link *in, *out; - PFIL_WLOCK(ph); + PFIL_LOCK_ASSERT(); + + if (pa->pa_flags & PFIL_IN) { + in = pfil_link_remove(&head->head_in, hook); + if (in != NULL) { + head->head_nhooksin--; + hook->hook_links--; + } + } else + in = NULL; + if (pa->pa_flags & PFIL_OUT) { + out = pfil_link_remove(&head->head_out, hook); + if (out != NULL) { + head->head_nhooksout--; + hook->hook_links--; + } + } else + out = NULL; + PFIL_UNLOCK(); + + if (in != NULL) + epoch_call(PFIL_EPOCH, &in->link_epoch_ctx, pfil_link_free); + if (out != NULL) + epoch_call(PFIL_EPOCH, &out->link_epoch_ctx, pfil_link_free); + + if (in == NULL && out == NULL) + return (ENOENT); + else + return (0); } -/* - * pfil_wunlock() releases writer lock for specified head. - */ -void -pfil_wunlock(struct pfil_head *ph) +int +pfil_link(struct pfil_link_args *pa) { + struct pfil_link *in, *out, *link; + struct pfil_head *head; + struct pfil_hook *hook; + int error; - PFIL_WUNLOCK(ph); + MPASS(pa->pa_version == PFIL_VERSION); + + if ((pa->pa_flags & (PFIL_IN | PFIL_UNLINK)) == PFIL_IN) + in = malloc(sizeof(*in), M_PFIL, M_WAITOK | M_ZERO); + else + in = NULL; + if ((pa->pa_flags & (PFIL_OUT | PFIL_UNLINK)) == PFIL_OUT) + out = malloc(sizeof(*out), M_PFIL, M_WAITOK | M_ZERO); + else + out = NULL; + + PFIL_LOCK(); + if (pa->pa_flags & PFIL_HEADPTR) + head = pa->pa_head; + else + LIST_FOREACH(head, &V_pfil_head_list, head_list) + if (strcmp(pa->pa_headname, head->head_name) == 0) + break; + if (pa->pa_flags & PFIL_HOOKPTR) + hook = pa->pa_hook; + else + LIST_FOREACH(hook, &V_pfil_hook_list, hook_list) + if (strcmp(pa->pa_modname, hook->hook_modname) == 0 && + strcmp(pa->pa_rulname, hook->hook_rulname) == 0) + break; + if (head == NULL || hook == NULL) { + error = ENOENT; + goto fail; + } + + if (pa->pa_flags & PFIL_UNLINK) + return (pfil_unlink(pa, head, hook)); + + if (head->head_type != hook->hook_type || + ((hook->hook_flags & pa->pa_flags) & ~head->head_flags)) { + error = EINVAL; + goto fail; + } + + if (pa->pa_flags & PFIL_IN) + CK_STAILQ_FOREACH(link, &head->head_in, link_chain) + if (link->link_hook == hook) { + error = EEXIST; + goto fail; + } + if (pa->pa_flags & PFIL_OUT) + CK_STAILQ_FOREACH(link, &head->head_out, link_chain) + if (link->link_hook == hook) { + error = EEXIST; + goto fail; + } + + if (pa->pa_flags & PFIL_IN) { + in->link_hook = hook; + in->link_func = hook->hook_func; + in->link_flags = hook->hook_flags; + in->link_ruleset = hook->hook_ruleset; + if (pa->pa_flags & PFIL_APPEND) + CK_STAILQ_INSERT_TAIL(&head->head_in, in, link_chain); + else + CK_STAILQ_INSERT_HEAD(&head->head_in, in, link_chain); + hook->hook_links++; + head->head_nhooksin++; + } + if (pa->pa_flags & PFIL_OUT) { + out->link_hook = hook; + out->link_func = hook->hook_func; + out->link_flags = hook->hook_flags; + out->link_ruleset = hook->hook_ruleset; + if (pa->pa_flags & PFIL_APPEND) + CK_STAILQ_INSERT_HEAD(&head->head_out, out, link_chain); + else + CK_STAILQ_INSERT_TAIL(&head->head_out, out, link_chain); + hook->hook_links++; + head->head_nhooksout++; + } + PFIL_UNLOCK(); + + return (0); + +fail: + PFIL_UNLOCK(); + free(in, M_PFIL); + free(out, M_PFIL); + return (error); } -/* - * pfil_wowned() returns a non-zero value if the current thread owns - * an exclusive lock. - */ -int -pfil_wowned(struct pfil_head *ph) +static void +pfil_link_free(epoch_context_t ctx) { + struct pfil_link *link; - return (PFIL_WOWNED(ph)); + link = __containerof(ctx, struct pfil_link, link_epoch_ctx); + free(link, M_PFIL); } /* - * pfil_head_register() registers a pfil_head with the packet filter hook - * mechanism. + * pfil_remove_hook removes a filter from all filtering points. */ -int -pfil_head_register(struct pfil_head *ph) +void +pfil_remove_hook(pfil_hook_t hook) { - struct pfil_head *lph; + struct pfil_head *head; + struct pfil_link *in, *out; - PFIL_HEADLIST_LOCK(); - LIST_FOREACH(lph, &V_pfil_head_list, ph_list) { - if (ph->ph_type == lph->ph_type && - ph->ph_un.phu_val == lph->ph_un.phu_val) { - PFIL_HEADLIST_UNLOCK(); - return (EEXIST); + PFIL_LOCK(); + LIST_FOREACH(head, &V_pfil_head_list, head_list) { +retry: + in = pfil_link_remove(&head->head_in, hook); + if (in != NULL) { + head->head_nhooksin--; + hook->hook_links--; + epoch_call(PFIL_EPOCH, &in->link_epoch_ctx, + pfil_link_free); } + out = pfil_link_remove(&head->head_out, hook); + if (out != NULL) { + head->head_nhooksout--; + hook->hook_links--; + epoch_call(PFIL_EPOCH, &out->link_epoch_ctx, + pfil_link_free); + } + if (in != NULL || out != NULL) + /* What if some stupid admin put same filter twice? */ + goto retry; } - PFIL_LOCK_INIT(ph); - ph->ph_nhooks = 0; - TAILQ_INIT(&ph->ph_in); - TAILQ_INIT(&ph->ph_out); - LIST_INSERT_HEAD(&V_pfil_head_list, ph, ph_list); - PFIL_HEADLIST_UNLOCK(); - return (0); + LIST_REMOVE(hook, hook_list); + PFIL_UNLOCK(); + MPASS(hook->hook_links == 0); + free(hook, M_PFIL); } /* - * pfil_head_unregister() removes a pfil_head from the packet filter hook - * mechanism. The producer of the hook promises that all outstanding - * invocations of the hook have completed before it unregisters the hook. + * Internal: Remove a pfil hook from a hook chain. */ -int -pfil_head_unregister(struct pfil_head *ph) +static struct pfil_link * +pfil_link_remove(pfil_chain_t *chain, pfil_hook_t hook) { - struct packet_filter_hook *pfh, *pfnext; - - PFIL_HEADLIST_LOCK(); - LIST_REMOVE(ph, ph_list); - PFIL_HEADLIST_UNLOCK(); - TAILQ_FOREACH_SAFE(pfh, &ph->ph_in, pfil_chain, pfnext) - free(pfh, M_IFADDR); - TAILQ_FOREACH_SAFE(pfh, &ph->ph_out, pfil_chain, pfnext) - free(pfh, M_IFADDR); - PFIL_LOCK_DESTROY(ph); - return (0); + struct pfil_link *link; + + PFIL_LOCK_ASSERT(); + + CK_STAILQ_FOREACH(link, chain, link_chain) + if (link->link_hook == hook) { + CK_STAILQ_REMOVE(chain, link, pfil_link, link_chain); + return (link); + } + + return (NULL); } -/* - * pfil_head_get() returns the pfil_head for a given key/dlt. - */ -struct pfil_head * -pfil_head_get(int type, u_long val) +static void +pfil_init(const void *unused __unused) { - struct pfil_head *ph; + struct make_dev_args args; + int error; - PFIL_HEADLIST_LOCK(); - LIST_FOREACH(ph, &V_pfil_head_list, ph_list) - if (ph->ph_type == type && ph->ph_un.phu_val == val) - break; - PFIL_HEADLIST_UNLOCK(); - return (ph); + make_dev_args_init(&args); + args.mda_flags = MAKEDEV_WAITOK | MAKEDEV_CHECKNAME; + args.mda_devsw = &pfil_cdevsw; + args.mda_uid = UID_ROOT; + args.mda_gid = GID_WHEEL; + args.mda_mode = 0600; + error = make_dev_s(&args, &pfil_dev, PFILDEV); + KASSERT(error == 0, ("%s: failed to create dev: %d", __func__, error)); } - /* - * pfil_add_hook_flags() adds a function to the packet filter hook. the - * flags are: - * PFIL_IN call me on incoming packets - * PFIL_OUT call me on outgoing packets - * PFIL_ALL call me on all of the above - * PFIL_WAITOK OK to call malloc with M_WAITOK. + * Make sure the pfil bits are first before any possible subsystem which + * might piggyback on the SI_SUB_PROTO_PFIL. */ -int -pfil_add_hook_flags(pfil_func_flags_t func, void *arg, int flags, - struct pfil_head *ph) -{ - return (pfil_add_hook_priv(func, arg, flags, ph, true)); -} +SYSINIT(pfil_init, SI_SUB_PROTO_PFIL, SI_ORDER_FIRST, pfil_init, NULL); /* - * pfil_add_hook() adds a function to the packet filter hook. the - * flags are: - * PFIL_IN call me on incoming packets - * PFIL_OUT call me on outgoing packets - * PFIL_ALL call me on all of the above - * PFIL_WAITOK OK to call malloc with M_WAITOK. + * User control interface. */ -int -pfil_add_hook(pfil_func_t func, void *arg, int flags, struct pfil_head *ph) +static int pfilioc_listheads(struct pfilioc_list *); +static int pfilioc_listhooks(struct pfilioc_list *); +static int pfilioc_link(struct pfilioc_link *); + +static int +pfil_ioctl(struct cdev *dev, u_long cmd, caddr_t addr, int flags, + struct thread *td) { - return (pfil_add_hook_priv(func, arg, flags, ph, false)); + int error; + + error = 0; + switch (cmd) { + case PFILIOC_LISTHEADS: + error = pfilioc_listheads((struct pfilioc_list *)addr); + break; + case PFILIOC_LISTHOOKS: + error = pfilioc_listhooks((struct pfilioc_list *)addr); + break; + case PFILIOC_LINK: + error = pfilioc_link((struct pfilioc_link *)addr); + break; + default: + return (EINVAL); + } + + return (error); } static int -pfil_add_hook_priv(void *func, void *arg, int flags, - struct pfil_head *ph, bool hasflags) +pfilioc_listheads(struct pfilioc_list *req) { - struct packet_filter_hook *pfh1 = NULL; - struct packet_filter_hook *pfh2 = NULL; - int err; + struct pfil_head *head; + struct pfil_link *link; + struct pfilioc_head *iohead; + struct pfilioc_hook *iohook; + u_int nheads, nhooks, hd, hk; + int error; - if (flags & PFIL_IN) { - pfh1 = (struct packet_filter_hook *)malloc(sizeof(*pfh1), - M_IFADDR, (flags & PFIL_WAITOK) ? M_WAITOK : M_NOWAIT); - if (pfh1 == NULL) { - err = ENOMEM; - goto error; - } + PFIL_LOCK(); +restart: + nheads = nhooks = 0; + LIST_FOREACH(head, &V_pfil_head_list, head_list) { + nheads++; + nhooks += head->head_nhooksin + head->head_nhooksout; } - if (flags & PFIL_OUT) { - pfh2 = (struct packet_filter_hook *)malloc(sizeof(*pfh1), - M_IFADDR, (flags & PFIL_WAITOK) ? M_WAITOK : M_NOWAIT); - if (pfh2 == NULL) { - err = ENOMEM; - goto error; - } + PFIL_UNLOCK(); + + if (req->pio_nheads < nheads || req->pio_nhooks < nhooks) { + req->pio_nheads = nheads; + req->pio_nhooks = nhooks; + return (0); } - PFIL_WLOCK(ph); - if (flags & PFIL_IN) { - pfh1->pfil_func_flags = hasflags ? func : NULL; - pfh1->pfil_func = hasflags ? NULL : func; - pfh1->pfil_arg = arg; - err = pfil_chain_add(&ph->ph_in, pfh1, flags & ~PFIL_OUT); - if (err) - goto locked_error; - ph->ph_nhooks++; - } - if (flags & PFIL_OUT) { - pfh2->pfil_func_flags = hasflags ? func : NULL; - pfh2->pfil_func = hasflags ? NULL : func; - pfh2->pfil_arg = arg; - err = pfil_chain_add(&ph->ph_out, pfh2, flags & ~PFIL_IN); - if (err) { - if (flags & PFIL_IN) - pfil_chain_remove(&ph->ph_in, func, arg); - goto locked_error; + + iohead = malloc(sizeof(*iohead) * nheads, M_TEMP, M_WAITOK); + iohook = malloc(sizeof(*iohook) * nhooks, M_TEMP, M_WAITOK); + + hd = hk = 0; + PFIL_LOCK(); + LIST_FOREACH(head, &V_pfil_head_list, head_list) { + if (hd + 1 > nheads || + hk + head->head_nhooksin + head->head_nhooksout > nhooks) { + /* Configuration changed during malloc(). */ + free(iohead, M_TEMP); + free(iohook, M_TEMP); + goto restart; } - ph->ph_nhooks++; + strlcpy(iohead[hd].pio_name, head->head_name, + sizeof(iohead[0].pio_name)); + iohead[hd].pio_nhooksin = head->head_nhooksin; + iohead[hd].pio_nhooksout = head->head_nhooksout; + iohead[hd].pio_type = head->head_type; + CK_STAILQ_FOREACH(link, &head->head_in, link_chain) { + strlcpy(iohook[hk].pio_module, + link->link_hook->hook_modname, + sizeof(iohook[0].pio_module)); + strlcpy(iohook[hk].pio_ruleset, + link->link_hook->hook_rulname, + sizeof(iohook[0].pio_ruleset)); + hk++; + } + CK_STAILQ_FOREACH(link, &head->head_out, link_chain) { + strlcpy(iohook[hk].pio_module, + link->link_hook->hook_modname, + sizeof(iohook[0].pio_module)); + strlcpy(iohook[hk].pio_ruleset, + link->link_hook->hook_rulname, + sizeof(iohook[0].pio_ruleset)); + hk++; + } + hd++; } - PFIL_WUNLOCK(ph); - return (0); -locked_error: - PFIL_WUNLOCK(ph); -error: - if (pfh1 != NULL) - free(pfh1, M_IFADDR); - if (pfh2 != NULL) - free(pfh2, M_IFADDR); - return (err); -} + PFIL_UNLOCK(); -/* - * pfil_remove_hook_flags removes a specific function from the packet filter hook - * chain. - */ -int -pfil_remove_hook_flags(pfil_func_flags_t func, void *arg, int flags, - struct pfil_head *ph) -{ - return (pfil_remove_hook((pfil_func_t)func, arg, flags, ph)); -} + error = copyout(iohead, req->pio_heads, + sizeof(*iohead) * min(hd, req->pio_nheads)); + if (error == 0) + error = copyout(iohook, req->pio_hooks, + sizeof(*iohook) * min(req->pio_nhooks, hk)); -/* - * pfil_remove_hook removes a specific function from the packet filter hook - * chain. - */ -int -pfil_remove_hook(pfil_func_t func, void *arg, int flags, struct pfil_head *ph) -{ - int err = 0; + req->pio_nheads = hd; + req->pio_nhooks = hk; - PFIL_WLOCK(ph); - if (flags & PFIL_IN) { - err = pfil_chain_remove(&ph->ph_in, func, arg); - if (err == 0) - ph->ph_nhooks--; - } - if ((err == 0) && (flags & PFIL_OUT)) { - err = pfil_chain_remove(&ph->ph_out, func, arg); - if (err == 0) - ph->ph_nhooks--; - } - PFIL_WUNLOCK(ph); - return (err); + free(iohead, M_TEMP); + free(iohook, M_TEMP); + + return (error); } -/* - * Internal: Add a new pfil hook into a hook chain. - */ static int -pfil_chain_add(pfil_chain_t *chain, struct packet_filter_hook *pfh1, int flags) +pfilioc_listhooks(struct pfilioc_list *req) { - struct packet_filter_hook *pfh; + struct pfil_hook *hook; + struct pfilioc_hook *iohook; + u_int nhooks, hk; + int error; - /* - * First make sure the hook is not already there. - */ - TAILQ_FOREACH(pfh, chain, pfil_chain) - if (((pfh->pfil_func != NULL && pfh->pfil_func == pfh1->pfil_func) || - (pfh->pfil_func_flags != NULL && - pfh->pfil_func_flags == pfh1->pfil_func_flags)) && - pfh->pfil_arg == pfh1->pfil_arg) - return (EEXIST); + PFIL_LOCK(); +restart: + nhooks = 0; + LIST_FOREACH(hook, &V_pfil_hook_list, hook_list) + nhooks++; + PFIL_UNLOCK(); - /* - * Insert the input list in reverse order of the output list so that - * the same path is followed in or out of the kernel. - */ - if (flags & PFIL_IN) - TAILQ_INSERT_HEAD(chain, pfh1, pfil_chain); - else - TAILQ_INSERT_TAIL(chain, pfh1, pfil_chain); - return (0); -} + if (req->pio_nhooks < nhooks) { + req->pio_nhooks = nhooks; + return (0); + } -/* - * Internal: Remove a pfil hook from a hook chain. - */ -static int -pfil_chain_remove(pfil_chain_t *chain, void *func, void *arg) -{ - struct packet_filter_hook *pfh; + iohook = malloc(sizeof(*iohook) * nhooks, M_TEMP, M_WAITOK); - TAILQ_FOREACH(pfh, chain, pfil_chain) - if ((pfh->pfil_func == func || pfh->pfil_func_flags == func) && - pfh->pfil_arg == arg) { - TAILQ_REMOVE(chain, pfh, pfil_chain); - free(pfh, M_IFADDR); - return (0); + hk = 0; + PFIL_LOCK(); + LIST_FOREACH(hook, &V_pfil_hook_list, hook_list) { + if (hk + 1 > nhooks) { + /* Configuration changed during malloc(). */ + free(iohook, M_TEMP); + goto restart; } - return (ENOENT); -} + strlcpy(iohook[hk].pio_module, hook->hook_modname, + sizeof(iohook[0].pio_module)); + strlcpy(iohook[hk].pio_ruleset, hook->hook_rulname, + sizeof(iohook[0].pio_ruleset)); + iohook[hk].pio_type = hook->hook_type; + iohook[hk].pio_flags = hook->hook_flags; + hk++; + } + PFIL_UNLOCK(); -/* - * Stuff that must be initialized for every instance (including the first of - * course). - */ -static void -vnet_pfil_init(const void *unused __unused) -{ + error = copyout(iohook, req->pio_hooks, + sizeof(*iohook) * min(req->pio_nhooks, hk)); + req->pio_nhooks = hk; + free(iohook, M_TEMP); - LIST_INIT(&V_pfil_head_list); - PFIL_LOCK_INIT_REAL(&V_pfil_lock, "shared"); + return (error); } -/* - * Called for the removal of each instance. - */ -static void -vnet_pfil_uninit(const void *unused __unused) +static int +pfilioc_link(struct pfilioc_link *req) { + struct pfil_link_args args; - KASSERT(LIST_EMPTY(&V_pfil_head_list), - ("%s: pfil_head_list %p not empty", __func__, &V_pfil_head_list)); - PFIL_LOCK_DESTROY_REAL(&V_pfil_lock); -} + if (req->pio_flags & ~(PFIL_IN | PFIL_OUT | PFIL_UNLINK | PFIL_APPEND)) + return (EINVAL); -/* - * Starting up. - * - * VNET_SYSINIT is called for each existing vnet and each new vnet. - * Make sure the pfil bits are first before any possible subsystem which - * might piggyback on the SI_SUB_PROTO_PFIL. - */ -VNET_SYSINIT(vnet_pfil_init, SI_SUB_PROTO_PFIL, SI_ORDER_FIRST, - vnet_pfil_init, NULL); - -/* - * Closing up shop. These are done in REVERSE ORDER. Not called on reboot. - * - * VNET_SYSUNINIT is called for each exiting vnet as it exits. - */ -VNET_SYSUNINIT(vnet_pfil_uninit, SI_SUB_PROTO_PFIL, SI_ORDER_FIRST, - vnet_pfil_uninit, NULL); + args.pa_version = PFIL_VERSION; + args.pa_flags = req->pio_flags; + args.pa_headname = req->pio_name; + args.pa_modname = req->pio_module; + args.pa_rulname = req->pio_ruleset; + + return (pfil_link(&args)); +} Index: head/sys/netinet/ip_fastfwd.c =================================================================== --- head/sys/netinet/ip_fastfwd.c +++ head/sys/netinet/ip_fastfwd.c @@ -90,11 +90,11 @@ #include #include -#include #include #include #include #include +#include #include #include @@ -228,12 +228,11 @@ /* * Run through list of ipfilter hooks for input packets */ - if (!PFIL_HOOKED(&V_inet_pfil_hook)) + if (!PFIL_HOOKED_IN(V_inet_pfil_head)) goto passin; - if (pfil_run_hooks( - &V_inet_pfil_hook, &m, m->m_pkthdr.rcvif, PFIL_IN, 0, NULL) || - m == NULL) + if (pfil_run_hooks(V_inet_pfil_head, &m, m->m_pkthdr.rcvif, PFIL_IN, + NULL) != PFIL_PASS) goto drop; M_ASSERTVALID(m); @@ -321,13 +320,12 @@ /* * Step 5: outgoing firewall packet processing */ - if (!PFIL_HOOKED(&V_inet_pfil_hook)) + if (!PFIL_HOOKED_OUT(V_inet_pfil_head)) goto passout; - if (pfil_run_hooks(&V_inet_pfil_hook, &m, nh.nh_ifp, PFIL_OUT, PFIL_FWD, - NULL) || m == NULL) { + if (pfil_run_hooks(V_inet_pfil_head, &m, nh.nh_ifp, + PFIL_OUT | PFIL_FWD, NULL) != PFIL_PASS) goto drop; - } M_ASSERTVALID(m); M_ASSERTPKTHDR(m); Index: head/sys/netinet/ip_input.c =================================================================== --- head/sys/netinet/ip_input.c +++ head/sys/netinet/ip_input.c @@ -57,11 +57,11 @@ #include #include -#include #include #include #include #include +#include #include #include #include @@ -134,7 +134,7 @@ &VNET_NAME(ip_checkinterface), 0, "Verify packet arrives on correct interface"); -VNET_DEFINE(struct pfil_head, inet_pfil_hook); /* Packet filter hooks */ +VNET_DEFINE(pfil_head_t, inet_pfil_head); /* Packet filter hooks */ static struct netisr_handler ip_nh = { .nh_name = "ip", @@ -301,6 +301,7 @@ void ip_init(void) { + struct pfil_head_args args; struct protosw *pr; int i; @@ -311,11 +312,11 @@ ipreass_init(); /* Initialize packet filter hooks. */ - V_inet_pfil_hook.ph_type = PFIL_TYPE_AF; - V_inet_pfil_hook.ph_af = AF_INET; - if ((i = pfil_head_register(&V_inet_pfil_hook)) != 0) - printf("%s: WARNING: unable to register pfil hook, " - "error %d\n", __func__, i); + args.pa_version = PFIL_VERSION; + args.pa_flags = PFIL_IN | PFIL_OUT; + args.pa_type = PFIL_TYPE_IP4; + args.pa_headname = PFIL_INET_NAME; + V_inet_pfil_head = pfil_head_register(&args); if (hhook_head_register(HHOOK_TYPE_IPSEC_IN, AF_INET, &V_ipsec_hhh_in[HHOOK_IPSEC_INET], @@ -377,10 +378,7 @@ #endif netisr_unregister_vnet(&ip_nh); - if ((error = pfil_head_unregister(&V_inet_pfil_hook)) != 0) - printf("%s: WARNING: unable to unregister pfil hook, " - "error %d\n", __func__, error); - + pfil_head_unregister(V_inet_pfil_head); error = hhook_head_deregister(V_ipsec_hhh_in[HHOOK_IPSEC_INET]); if (error != 0) { printf("%s: WARNING: unable to deregister input helper hook " @@ -599,11 +597,12 @@ */ /* Jump over all PFIL processing if hooks are not active. */ - if (!PFIL_HOOKED(&V_inet_pfil_hook)) + if (!PFIL_HOOKED_IN(V_inet_pfil_head)) goto passin; odst = ip->ip_dst; - if (pfil_run_hooks(&V_inet_pfil_hook, &m, ifp, PFIL_IN, 0, NULL) != 0) + if (pfil_run_hooks(V_inet_pfil_head, &m, ifp, PFIL_IN, NULL) != + PFIL_PASS) return; if (m == NULL) /* consumed by filter */ return; Index: head/sys/netinet/ip_output.c =================================================================== --- head/sys/netinet/ip_output.c +++ head/sys/netinet/ip_output.c @@ -121,11 +121,16 @@ /* Run through list of hooks for output packets. */ odst.s_addr = ip->ip_dst.s_addr; - *error = pfil_run_hooks(&V_inet_pfil_hook, mp, ifp, PFIL_OUT, 0, inp); - m = *mp; - if ((*error) != 0 || m == NULL) + switch (pfil_run_hooks(V_inet_pfil_head, mp, ifp, PFIL_OUT, inp)) { + case PFIL_DROPPED: + *error = EPERM; + /* FALLTHROUGH */ + case PFIL_CONSUMED: return 1; /* Finished */ - + case PFIL_PASS: + *error = 0; + } + m = *mp; ip = mtod(m, struct ip *); /* See if destination IP address was changed by packet filter. */ @@ -568,7 +573,7 @@ #endif /* IPSEC */ /* Jump over all PFIL processing if hooks are not active. */ - if (PFIL_HOOKED(&V_inet_pfil_hook)) { + if (PFIL_HOOKED_OUT(V_inet_pfil_head)) { switch (ip_output_pfil(&m, ifp, inp, dst, &fibnum, &error)) { case 1: /* Finished */ goto done; Index: head/sys/netinet/ip_var.h =================================================================== --- head/sys/netinet/ip_var.h +++ head/sys/netinet/ip_var.h @@ -241,8 +241,9 @@ extern void (*ip_rsvp_force_done)(struct socket *); extern int (*rsvp_input_p)(struct mbuf **, int *, int); -VNET_DECLARE(struct pfil_head, inet_pfil_hook); /* packet filter hooks */ -#define V_inet_pfil_hook VNET(inet_pfil_hook) +VNET_DECLARE(struct pfil_head *, inet_pfil_head); +#define V_inet_pfil_head VNET(inet_pfil_head) +#define PFIL_INET_NAME "inet" void in_delayed_cksum(struct mbuf *m); Index: head/sys/netinet/siftr.c =================================================================== --- head/sys/netinet/siftr.c +++ head/sys/netinet/siftr.c @@ -94,10 +94,12 @@ #include #include #include +#include #include #ifdef SIFTR_IPV6 #include +#include #include #endif /* SIFTR_IPV6 */ @@ -831,9 +833,9 @@ * It's very important to use the M_NOWAIT flag with all function calls * that support it so that they won't sleep, otherwise you get a panic. */ -static int -siftr_chkpkt(void *arg, struct mbuf **m, struct ifnet *ifp, int dir, - struct inpcb *inp) +static pfil_return_t +siftr_chkpkt(struct mbuf **m, struct ifnet *ifp, int flags, + void *ruleset __unused, struct inpcb *inp) { struct pkt_node *pn; struct ip *ip; @@ -841,9 +843,10 @@ struct tcpcb *tp; struct siftr_stats *ss; unsigned int ip_hl; - int inp_locally_locked; + int inp_locally_locked, dir; inp_locally_locked = 0; + dir = PFIL_DIR(flags); ss = DPCPU_PTR(ss); /* @@ -1007,15 +1010,13 @@ INP_RUNLOCK(inp); ret: - /* Returning 0 ensures pfil will not discard the pkt */ - return (0); + return (PFIL_PASS); } #ifdef SIFTR_IPV6 static int -siftr_chkpkt6(void *arg, struct mbuf **m, struct ifnet *ifp, int dir, - struct inpcb *inp) +siftr_chkpkt6(struct mbuf **m, struct ifnet *ifp, int flags, struct inpcb *inp) { struct pkt_node *pn; struct ip6_hdr *ip6; @@ -1023,9 +1024,10 @@ struct tcpcb *tp; struct siftr_stats *ss; unsigned int ip6_hl; - int inp_locally_locked; + int inp_locally_locked, dir; inp_locally_locked = 0; + dir = PFIL_DIR(flags); ss = DPCPU_PTR(ss); /* @@ -1138,37 +1140,53 @@ } #endif /* #ifdef SIFTR_IPV6 */ - +VNET_DEFINE_STATIC(pfil_hook_t, siftr_inet_hook); +#define V_siftr_inet_hook VNET(siftr_inet_hook) +#ifdef INET6 +VNET_DEFINE_STATIC(pfil_hook_t, siftr_inet6_hook); +#define V_siftr_inet6_hook VNET(siftr_inet6_hook) +#endif static int siftr_pfil(int action) { - struct pfil_head *pfh_inet; -#ifdef SIFTR_IPV6 - struct pfil_head *pfh_inet6; -#endif + struct pfil_hook_args pha; + struct pfil_link_args pla; + + pha.pa_version = PFIL_VERSION; + pha.pa_flags = PFIL_IN | PFIL_OUT; + pha.pa_modname = "siftr"; + pha.pa_ruleset = NULL; + pha.pa_rulname = "default"; + + pla.pa_version = PFIL_VERSION; + pla.pa_flags = PFIL_IN | PFIL_OUT | + PFIL_HEADPTR | PFIL_HOOKPTR; + VNET_ITERATOR_DECL(vnet_iter); VNET_LIST_RLOCK(); VNET_FOREACH(vnet_iter) { CURVNET_SET(vnet_iter); - pfh_inet = pfil_head_get(PFIL_TYPE_AF, AF_INET); -#ifdef SIFTR_IPV6 - pfh_inet6 = pfil_head_get(PFIL_TYPE_AF, AF_INET6); -#endif if (action == HOOK) { - pfil_add_hook(siftr_chkpkt, NULL, - PFIL_IN | PFIL_OUT | PFIL_WAITOK, pfh_inet); + pha.pa_func = siftr_chkpkt; + pha.pa_type = PFIL_TYPE_IP4; + V_siftr_inet_hook = pfil_add_hook(&pha); + pla.pa_hook = V_siftr_inet_hook; + pla.pa_head = V_inet_pfil_head; + (void)pfil_link(&pla); #ifdef SIFTR_IPV6 - pfil_add_hook(siftr_chkpkt6, NULL, - PFIL_IN | PFIL_OUT | PFIL_WAITOK, pfh_inet6); + pha.pa_func = siftr_chkpkt6; + pha.pa_type = PFIL_TYPE_IP6; + V_siftr_inet6_hook = pfil_add_hook(&pha); + pla.pa_hook = V_siftr_inet6_hook; + pla.pa_head = V_inet6_pfil_head; + (void)pfil_link(&pla); #endif } else if (action == UNHOOK) { - pfil_remove_hook(siftr_chkpkt, NULL, - PFIL_IN | PFIL_OUT | PFIL_WAITOK, pfh_inet); + pfil_remove_hook(V_siftr_inet_hook); #ifdef SIFTR_IPV6 - pfil_remove_hook(siftr_chkpkt6, NULL, - PFIL_IN | PFIL_OUT | PFIL_WAITOK, pfh_inet6); + pfil_remove_hook(V_siftr_inet6_hook); #endif } CURVNET_RESTORE(); Index: head/sys/netinet6/ip6_fastfwd.c =================================================================== --- head/sys/netinet6/ip6_fastfwd.c +++ head/sys/netinet6/ip6_fastfwd.c @@ -156,10 +156,10 @@ /* * Incoming packet firewall processing. */ - if (!PFIL_HOOKED(&V_inet6_pfil_hook)) + if (!PFIL_HOOKED_IN(V_inet6_pfil_head)) goto passin; - if (pfil_run_hooks(&V_inet6_pfil_hook, &m, rcvif, PFIL_IN, 0, - NULL) != 0 || m == NULL) + if (pfil_run_hooks(V_inet6_pfil_head, &m, rcvif, PFIL_IN, NULL) != + PFIL_PASS) goto dropin; /* * If packet filter sets the M_FASTFWD_OURS flag, this means @@ -195,7 +195,7 @@ in6_ifstat_inc(rcvif, ifs6_in_noroute); goto dropin; } - if (!PFIL_HOOKED(&V_inet6_pfil_hook)) { + if (!PFIL_HOOKED_OUT(V_inet6_pfil_head)) { if (m->m_pkthdr.len > nh.nh_mtu) { in6_ifstat_inc(nh.nh_ifp, ifs6_in_toobig); icmp6_error(m, ICMP6_PACKET_TOO_BIG, 0, nh.nh_mtu); @@ -208,8 +208,8 @@ /* * Outgoing packet firewall processing. */ - if (pfil_run_hooks(&V_inet6_pfil_hook, &m, nh.nh_ifp, PFIL_OUT, - PFIL_FWD, NULL) != 0 || m == NULL) + if (pfil_run_hooks(V_inet6_pfil_head, &m, nh.nh_ifp, PFIL_OUT | + PFIL_FWD, NULL) != PFIL_PASS) goto dropout; /* Index: head/sys/netinet6/ip6_forward.c =================================================================== --- head/sys/netinet6/ip6_forward.c +++ head/sys/netinet6/ip6_forward.c @@ -320,15 +320,14 @@ in6_clearscope(&ip6->ip6_dst); /* Jump over all PFIL processing if hooks are not active. */ - if (!PFIL_HOOKED(&V_inet6_pfil_hook)) + if (!PFIL_HOOKED_OUT(V_inet6_pfil_head)) goto pass; odst = ip6->ip6_dst; /* Run through list of hooks for forwarded packets. */ - error = pfil_run_hooks(&V_inet6_pfil_hook, &m, rt->rt_ifp, PFIL_OUT, - PFIL_FWD, NULL); - if (error != 0 || m == NULL) - goto freecopy; /* consumed by filter */ + if (pfil_run_hooks(V_inet6_pfil_head, &m, rt->rt_ifp, PFIL_OUT | + PFIL_FWD, NULL) != PFIL_PASS) + goto freecopy; ip6 = mtod(m, struct ip6_hdr *); /* See if destination IP address was changed by packet filter. */ Index: head/sys/netinet6/ip6_input.c =================================================================== --- head/sys/netinet6/ip6_input.c +++ head/sys/netinet6/ip6_input.c @@ -191,7 +191,7 @@ #endif -VNET_DEFINE(struct pfil_head, inet6_pfil_hook); +VNET_DEFINE(pfil_head_t, inet6_pfil_head); VNET_PCPUSTAT_DEFINE(struct ip6stat, ip6stat); VNET_PCPUSTAT_SYSINIT(ip6stat); @@ -214,6 +214,7 @@ void ip6_init(void) { + struct pfil_head_args args; struct protosw *pr; int i; @@ -227,11 +228,11 @@ &V_in6_ifaddrhmask); /* Initialize packet filter hooks. */ - V_inet6_pfil_hook.ph_type = PFIL_TYPE_AF; - V_inet6_pfil_hook.ph_af = AF_INET6; - if ((i = pfil_head_register(&V_inet6_pfil_hook)) != 0) - printf("%s: WARNING: unable to register pfil hook, " - "error %d\n", __func__, i); + args.pa_version = PFIL_VERSION; + args.pa_flags = PFIL_IN | PFIL_OUT; + args.pa_type = PFIL_TYPE_IP6; + args.pa_headname = PFIL_INET6_NAME; + V_inet6_pfil_head = pfil_head_register(&args); if (hhook_head_register(HHOOK_TYPE_IPSEC_IN, AF_INET6, &V_ipsec_hhh_in[HHOOK_IPSEC_INET6], @@ -359,9 +360,7 @@ #endif netisr_unregister_vnet(&ip6_nh); - if ((error = pfil_head_unregister(&V_inet6_pfil_hook)) != 0) - printf("%s: WARNING: unable to unregister pfil hook, " - "error %d\n", __func__, error); + pfil_head_unregister(V_inet6_pfil_head); error = hhook_head_deregister(V_ipsec_hhh_in[HHOOK_IPSEC_INET6]); if (error != 0) { printf("%s: WARNING: unable to deregister input helper hook " @@ -758,14 +757,12 @@ */ /* Jump over all PFIL processing if hooks are not active. */ - if (!PFIL_HOOKED(&V_inet6_pfil_hook)) + if (!PFIL_HOOKED_IN(V_inet6_pfil_head)) goto passin; odst = ip6->ip6_dst; - if (pfil_run_hooks(&V_inet6_pfil_hook, &m, - m->m_pkthdr.rcvif, PFIL_IN, 0, NULL)) - return; - if (m == NULL) /* consumed by filter */ + if (pfil_run_hooks(V_inet6_pfil_head, &m, m->m_pkthdr.rcvif, PFIL_IN, + NULL) != PFIL_PASS) return; ip6 = mtod(m, struct ip6_hdr *); srcrt = !IN6_ARE_ADDR_EQUAL(&odst, &ip6->ip6_dst); Index: head/sys/netinet6/ip6_output.c =================================================================== --- head/sys/netinet6/ip6_output.c +++ head/sys/netinet6/ip6_output.c @@ -792,16 +792,21 @@ } /* Jump over all PFIL processing if hooks are not active. */ - if (!PFIL_HOOKED(&V_inet6_pfil_hook)) + if (!PFIL_HOOKED_OUT(V_inet6_pfil_head)) goto passout; odst = ip6->ip6_dst; /* Run through list of hooks for output packets. */ - error = pfil_run_hooks(&V_inet6_pfil_hook, &m, ifp, PFIL_OUT, 0, inp); - if (error != 0 || m == NULL) + switch (pfil_run_hooks(V_inet6_pfil_head, &m, ifp, PFIL_OUT, inp)) { + case PFIL_PASS: + ip6 = mtod(m, struct ip6_hdr *); + break; + case PFIL_DROPPED: + error = EPERM; + /* FALLTHROUGH */ + case PFIL_CONSUMED: goto done; - /* adjust pointer */ - ip6 = mtod(m, struct ip6_hdr *); + } needfiblookup = 0; /* See if destination IP address was changed by packet filter. */ Index: head/sys/netinet6/ip6_var.h =================================================================== --- head/sys/netinet6/ip6_var.h +++ head/sys/netinet6/ip6_var.h @@ -346,8 +346,10 @@ * zone when unspecified */ #define V_ip6_use_defzone VNET(ip6_use_defzone) -VNET_DECLARE (struct pfil_head, inet6_pfil_hook); /* packet filter hooks */ -#define V_inet6_pfil_hook VNET(inet6_pfil_hook) +VNET_DECLARE(struct pfil_head *, inet6_pfil_head); +#define V_inet6_pfil_head VNET(inet6_pfil_head) +#define PFIL_INET6_NAME "inet6" + #ifdef IPSTEALTH VNET_DECLARE(int, ip6stealth); #define V_ip6stealth VNET(ip6stealth) Index: head/sys/netpfil/ipfw/ip_fw_eaction.c =================================================================== --- head/sys/netpfil/ipfw/ip_fw_eaction.c +++ head/sys/netpfil/ipfw/ip_fw_eaction.c @@ -38,9 +38,9 @@ #include #include #include -#include #include /* ip_fw.h requires IFNAMSIZ */ +#include #include #include /* struct ipfw_rule_ref */ #include Index: head/sys/netpfil/ipfw/ip_fw_pfil.c =================================================================== --- head/sys/netpfil/ipfw/ip_fw_pfil.c +++ head/sys/netpfil/ipfw/ip_fw_pfil.c @@ -48,6 +48,7 @@ #include #include +#include #include #include #include @@ -85,10 +86,6 @@ /* Forward declarations. */ static int ipfw_divert(struct mbuf **, int, struct ipfw_rule_ref *, int); -int ipfw_check_packet(void *, struct mbuf **, struct ifnet *, int, - struct inpcb *); -int ipfw_check_frame(void *, struct mbuf **, struct ifnet *, int, - struct inpcb *); #ifdef SYSCTL_NODE @@ -120,16 +117,17 @@ * dummynet, divert, netgraph or other modules. * The packet may be consumed. */ -int -ipfw_check_packet(void *arg, struct mbuf **m0, struct ifnet *ifp, int dir, - struct inpcb *inp) +static pfil_return_t +ipfw_check_packet(struct mbuf **m0, struct ifnet *ifp, int dir, + void *ruleset __unused, struct inpcb *inp) { struct ip_fw_args args; struct m_tag *tag; - int ipfw, ret; + pfil_return_t ret; + int ipfw; /* convert dir to IPFW values */ - dir = (dir == PFIL_IN) ? DIR_IN : DIR_OUT; + dir = (dir & PFIL_IN) ? DIR_IN : DIR_OUT; args.flags = 0; again: /* @@ -155,17 +153,15 @@ KASSERT(*m0 != NULL || ipfw == IP_FW_DENY, ("%s: m0 is NULL", __func__)); - /* breaking out of the switch means drop */ + ret = PFIL_PASS; switch (ipfw) { case IP_FW_PASS: /* next_hop may be set by ipfw_chk */ if ((args.flags & (IPFW_ARGS_NH4 | IPFW_ARGS_NH4PTR | - IPFW_ARGS_NH6 | IPFW_ARGS_NH6PTR)) == 0) { - ret = 0; + IPFW_ARGS_NH6 | IPFW_ARGS_NH6PTR)) == 0) break; - } #if (!defined(INET6) && !defined(INET)) - ret = EACCES; + ret = PFIL_DROPPED; #else { void *psa; @@ -210,8 +206,8 @@ tag = m_tag_get(PACKET_TAG_IPFORWARD, len, M_NOWAIT); if (tag == NULL) { - ret = EACCES; - break; /* i.e. drop */ + ret = PFIL_DROPPED; + break; } } if ((args.flags & IPFW_ARGS_NH6) == 0) @@ -238,7 +234,7 @@ * comparisons. */ if (sa6_embedscope(sa6, V_ip6_use_defzone) != 0) { - ret = EACCES; + ret = PFIL_DROPPED; break; } if (in6_localip(&sa6->sin6_addr)) @@ -250,20 +246,23 @@ break; case IP_FW_DENY: - ret = EACCES; - break; /* i.e. drop */ + ret = PFIL_DROPPED; + break; case IP_FW_DUMMYNET: - ret = EACCES; - if (ip_dn_io_ptr == NULL) - break; /* i.e. drop */ + if (ip_dn_io_ptr == NULL) { + ret = PFIL_DROPPED; + break; + } MPASS(args.flags & IPFW_ARGS_REF); if (mtod(*m0, struct ip *)->ip_v == 4) - ret = ip_dn_io_ptr(m0, dir, &args); + (void )ip_dn_io_ptr(m0, dir, &args); else if (mtod(*m0, struct ip *)->ip_v == 6) - ret = ip_dn_io_ptr(m0, dir | PROTO_IPV6, &args); - else - break; /* drop it */ + (void )ip_dn_io_ptr(m0, dir | PROTO_IPV6, &args); + else { + ret = PFIL_DROPPED; + break; + } /* * XXX should read the return value. * dummynet normally eats the packet and sets *m0=NULL @@ -273,41 +272,42 @@ */ if (*m0 != NULL) goto again; + ret = PFIL_CONSUMED; break; case IP_FW_TEE: case IP_FW_DIVERT: if (ip_divert_ptr == NULL) { - ret = EACCES; - break; /* i.e. drop */ + ret = PFIL_DROPPED; + break; } MPASS(args.flags & IPFW_ARGS_REF); - ret = ipfw_divert(m0, dir, &args.rule, + (void )ipfw_divert(m0, dir, &args.rule, (ipfw == IP_FW_TEE) ? 1 : 0); /* continue processing for the original packet (tee). */ if (*m0) goto again; + ret = PFIL_CONSUMED; break; case IP_FW_NGTEE: case IP_FW_NETGRAPH: if (ng_ipfw_input_p == NULL) { - ret = EACCES; - break; /* i.e. drop */ + ret = PFIL_DROPPED; + break; } MPASS(args.flags & IPFW_ARGS_REF); - ret = ng_ipfw_input_p(m0, dir, &args, + (void )ng_ipfw_input_p(m0, dir, &args, (ipfw == IP_FW_NGTEE) ? 1 : 0); if (ipfw == IP_FW_NGTEE) /* ignore errors for NGTEE */ goto again; /* continue with packet */ + ret = PFIL_CONSUMED; break; case IP_FW_NAT: /* honor one-pass in case of successful nat */ - if (V_fw_one_pass) { - ret = 0; + if (V_fw_one_pass) break; - } goto again; case IP_FW_REASS: @@ -317,7 +317,7 @@ KASSERT(0, ("%s: unknown retval", __func__)); } - if (ret != 0) { + if (ret != PFIL_PASS) { if (*m0) FREE_PKT(*m0); *m0 = NULL; @@ -329,16 +329,17 @@ /* * ipfw processing for ethernet packets (in and out). */ -int -ipfw_check_frame(void *arg, struct mbuf **m0, struct ifnet *ifp, int dir, - struct inpcb *inp) +static pfil_return_t +ipfw_check_frame(struct mbuf **m0, struct ifnet *ifp, int dir, + void *ruleset __unused, struct inpcb *inp) { struct ip_fw_args args; struct ether_header save_eh; struct ether_header *eh; struct m_tag *mtag; struct mbuf *m; - int i, ret; + pfil_return_t ret; + int i; args.flags = IPFW_ARGS_ETHER; again: @@ -367,7 +368,7 @@ m_adj(m, ETHER_HDR_LEN); /* strip ethernet header */ args.m = m; /* the packet we are looking at */ - args.oif = dir == PFIL_OUT ? ifp: NULL; /* destination, if any */ + args.oif = dir & PFIL_OUT ? ifp: NULL; /* destination, if any */ args.eh = &save_eh; /* MAC header for bridged/MAC packets */ args.inp = inp; /* used by ipfw uid/gid/jail rules */ i = ipfw_chk(&args); @@ -388,46 +389,46 @@ } *m0 = m; - ret = 0; + ret = PFIL_PASS; /* Check result of ipfw_chk() */ switch (i) { case IP_FW_PASS: break; case IP_FW_DENY: - ret = EACCES; - break; /* i.e. drop */ + ret = PFIL_DROPPED; + break; case IP_FW_DUMMYNET: - ret = EACCES; - - if (ip_dn_io_ptr == NULL) - break; /* i.e. drop */ - + if (ip_dn_io_ptr == NULL) { + ret = PFIL_DROPPED; + break; + } *m0 = NULL; - dir = (dir == PFIL_IN) ? DIR_IN : DIR_OUT; + dir = (dir & PFIL_IN) ? DIR_IN : DIR_OUT; MPASS(args.flags & IPFW_ARGS_REF); ip_dn_io_ptr(&m, dir | PROTO_LAYER2, &args); - return 0; + return (PFIL_CONSUMED); case IP_FW_NGTEE: case IP_FW_NETGRAPH: if (ng_ipfw_input_p == NULL) { - ret = EACCES; - break; /* i.e. drop */ + ret = PFIL_DROPPED; + break; } MPASS(args.flags & IPFW_ARGS_REF); - ret = ng_ipfw_input_p(m0, (dir == PFIL_IN) ? DIR_IN : DIR_OUT, + (void )ng_ipfw_input_p(m0, (dir & PFIL_IN) ? DIR_IN : DIR_OUT, &args, (i == IP_FW_NGTEE) ? 1 : 0); if (i == IP_FW_NGTEE) /* ignore errors for NGTEE */ goto again; /* continue with packet */ + ret = PFIL_CONSUMED; break; default: KASSERT(0, ("%s: unknown retval", __func__)); } - if (ret != 0) { + if (ret != PFIL_PASS) { if (*m0) FREE_PKT(*m0); *m0 = NULL; @@ -531,20 +532,62 @@ /* * attach or detach hooks for a given protocol family */ +VNET_DEFINE_STATIC(pfil_hook_t, ipfw_inet_hook); +VNET_DEFINE_STATIC(pfil_hook_t, ipfw_inet6_hook); +VNET_DEFINE_STATIC(pfil_hook_t, ipfw_link_hook); +#define V_ipfw_inet_hook VNET(ipfw_inet_hook) +#define V_ipfw_inet6_hook VNET(ipfw_inet6_hook) +#define V_ipfw_link_hook VNET(ipfw_link_hook) + static int ipfw_hook(int onoff, int pf) { - struct pfil_head *pfh; - pfil_func_t hook_func; + struct pfil_hook_args pha; + struct pfil_link_args pla; + pfil_hook_t *h; - pfh = pfil_head_get(PFIL_TYPE_AF, pf); - if (pfh == NULL) - return ENOENT; + pha.pa_version = PFIL_VERSION; + pha.pa_flags = PFIL_IN | PFIL_OUT; + pha.pa_modname = "ipfw"; + pha.pa_ruleset = NULL; - hook_func = (pf == AF_LINK) ? ipfw_check_frame : ipfw_check_packet; + pla.pa_version = PFIL_VERSION; + pla.pa_flags = PFIL_IN | PFIL_OUT | + PFIL_HEADPTR | PFIL_HOOKPTR; - (void) (onoff ? pfil_add_hook : pfil_remove_hook) - (hook_func, NULL, PFIL_IN | PFIL_OUT | PFIL_WAITOK, pfh); + switch (pf) { + case AF_INET: + pha.pa_func = ipfw_check_packet; + pha.pa_type = PFIL_TYPE_IP4; + pha.pa_rulname = "default"; + h = &V_ipfw_inet_hook; + pla.pa_head = V_inet_pfil_head; + break; +#ifdef INET6 + case AF_INET6: + pha.pa_func = ipfw_check_packet; + pha.pa_type = PFIL_TYPE_IP6; + pha.pa_rulname = "default6"; + h = &V_ipfw_inet6_hook; + pla.pa_head = V_inet6_pfil_head; + break; +#endif + case AF_LINK: + pha.pa_func = ipfw_check_frame; + pha.pa_type = PFIL_TYPE_ETHERNET; + pha.pa_rulname = "default-link"; + h = &V_ipfw_link_hook; + pla.pa_head = V_link_pfil_head; + break; + } + + if (onoff) { + *h = pfil_add_hook(&pha); + pla.pa_hook = *h; + (void)pfil_link(&pla); + } else + if (*h != NULL) + pfil_remove_hook(*h); return 0; } Index: head/sys/netpfil/pf/pf_ioctl.c =================================================================== --- head/sys/netpfil/pf/pf_ioctl.c +++ head/sys/netpfil/pf/pf_ioctl.c @@ -169,16 +169,16 @@ * Wrapper functions for pfil(9) hooks */ #ifdef INET -static int pf_check_in(void *arg, struct mbuf **m, struct ifnet *ifp, - int dir, int flags, struct inpcb *inp); -static int pf_check_out(void *arg, struct mbuf **m, struct ifnet *ifp, - int dir, int flags, struct inpcb *inp); +static pfil_return_t pf_check_in(struct mbuf **m, struct ifnet *ifp, + int flags, void *ruleset __unused, struct inpcb *inp); +static pfil_return_t pf_check_out(struct mbuf **m, struct ifnet *ifp, + int flags, void *ruleset __unused, struct inpcb *inp); #endif #ifdef INET6 -static int pf_check6_in(void *arg, struct mbuf **m, struct ifnet *ifp, - int dir, int flags, struct inpcb *inp); -static int pf_check6_out(void *arg, struct mbuf **m, struct ifnet *ifp, - int dir, int flags, struct inpcb *inp); +static pfil_return_t pf_check6_in(struct mbuf **m, struct ifnet *ifp, + int flags, void *ruleset __unused, struct inpcb *inp); +static pfil_return_t pf_check6_out(struct mbuf **m, struct ifnet *ifp, + int flags, void *ruleset __unused, struct inpcb *inp); #endif static int hook_pf(void); @@ -4003,9 +4003,9 @@ } #ifdef INET -static int -pf_check_in(void *arg, struct mbuf **m, struct ifnet *ifp, int dir, int flags, - struct inpcb *inp) +static pfil_return_t +pf_check_in(struct mbuf **m, struct ifnet *ifp, int flags, + void *ruleset __unused, struct inpcb *inp) { int chk; @@ -4015,14 +4015,12 @@ *m = NULL; } - if (chk != PF_PASS) - return (EACCES); - return (0); + return (chk == PF_PASS ? PFIL_PASS : PFIL_DROPPED); } -static int -pf_check_out(void *arg, struct mbuf **m, struct ifnet *ifp, int dir, int flags, - struct inpcb *inp) +static pfil_return_t +pf_check_out(struct mbuf **m, struct ifnet *ifp, int flags, + void *ruleset __unused, struct inpcb *inp) { int chk; @@ -4032,16 +4030,14 @@ *m = NULL; } - if (chk != PF_PASS) - return (EACCES); - return (0); + return (chk == PF_PASS ? PFIL_PASS : PFIL_DROPPED); } #endif #ifdef INET6 -static int -pf_check6_in(void *arg, struct mbuf **m, struct ifnet *ifp, int dir, int flags, - struct inpcb *inp) +static pfil_return_t +pf_check6_in(struct mbuf **m, struct ifnet *ifp, int flags, + void *ruleset __unused, struct inpcb *inp) { int chk; @@ -4057,14 +4053,13 @@ m_freem(*m); *m = NULL; } - if (chk != PF_PASS) - return (EACCES); - return (0); + + return (chk == PF_PASS ? PFIL_PASS : PFIL_DROPPED); } -static int -pf_check6_out(void *arg, struct mbuf **m, struct ifnet *ifp, int dir, int flags, - struct inpcb *inp) +static pfil_return_t +pf_check6_out(struct mbuf **m, struct ifnet *ifp, int flags, + void *ruleset __unused, struct inpcb *inp) { int chk; @@ -4075,46 +4070,77 @@ m_freem(*m); *m = NULL; } - if (chk != PF_PASS) - return (EACCES); - return (0); + + return (chk == PF_PASS ? PFIL_PASS : PFIL_DROPPED); } #endif /* INET6 */ -static int -hook_pf(void) -{ #ifdef INET - struct pfil_head *pfh_inet; +VNET_DEFINE_STATIC(pfil_hook_t, pf_ip4_in_hook); +VNET_DEFINE_STATIC(pfil_hook_t, pf_ip4_out_hook); +#define V_pf_ip4_in_hook VNET(pf_ip4_in_hook) +#define V_pf_ip4_out_hook VNET(pf_ip4_out_hook) #endif #ifdef INET6 - struct pfil_head *pfh_inet6; +VNET_DEFINE_STATIC(pfil_hook_t, pf_ip6_in_hook); +VNET_DEFINE_STATIC(pfil_hook_t, pf_ip6_out_hook); +#define V_pf_ip6_in_hook VNET(pf_ip6_in_hook) +#define V_pf_ip6_out_hook VNET(pf_ip6_out_hook) #endif +static int +hook_pf(void) +{ + struct pfil_hook_args pha; + struct pfil_link_args pla; + if (V_pf_pfil_hooked) return (0); + pha.pa_version = PFIL_VERSION; + pha.pa_modname = "pf"; + pha.pa_ruleset = NULL; + + pla.pa_version = PFIL_VERSION; + #ifdef INET - pfh_inet = pfil_head_get(PFIL_TYPE_AF, AF_INET); - if (pfh_inet == NULL) - return (ESRCH); /* XXX */ - pfil_add_hook_flags(pf_check_in, NULL, PFIL_IN | PFIL_WAITOK, pfh_inet); - pfil_add_hook_flags(pf_check_out, NULL, PFIL_OUT | PFIL_WAITOK, pfh_inet); + pha.pa_type = PFIL_TYPE_IP4; + pha.pa_func = pf_check_in; + pha.pa_flags = PFIL_IN; + pha.pa_rulname = "default-in"; + V_pf_ip4_in_hook = pfil_add_hook(&pha); + pla.pa_flags = PFIL_IN | PFIL_HEADPTR | PFIL_HOOKPTR; + pla.pa_head = V_inet_pfil_head; + pla.pa_hook = V_pf_ip4_in_hook; + (void)pfil_link(&pla); + pha.pa_func = pf_check_out; + pha.pa_flags = PFIL_OUT; + pha.pa_rulname = "default-out"; + V_pf_ip4_out_hook = pfil_add_hook(&pha); + pla.pa_flags = PFIL_OUT | PFIL_HEADPTR | PFIL_HOOKPTR; + pla.pa_head = V_inet_pfil_head; + pla.pa_hook = V_pf_ip4_out_hook; + (void)pfil_link(&pla); #endif #ifdef INET6 - pfh_inet6 = pfil_head_get(PFIL_TYPE_AF, AF_INET6); - if (pfh_inet6 == NULL) { -#ifdef INET - pfil_remove_hook_flags(pf_check_in, NULL, PFIL_IN | PFIL_WAITOK, - pfh_inet); - pfil_remove_hook_flags(pf_check_out, NULL, PFIL_OUT | PFIL_WAITOK, - pfh_inet); + pha.pa_type = PFIL_TYPE_IP6; + pha.pa_func = pf_check6_in; + pha.pa_flags = PFIL_IN; + pha.pa_rulname = "default-in6"; + V_pf_ip6_in_hook = pfil_add_hook(&pha); + pla.pa_flags = PFIL_IN | PFIL_HEADPTR | PFIL_HOOKPTR; + pla.pa_head = V_inet6_pfil_head; + pla.pa_hook = V_pf_ip6_in_hook; + (void)pfil_link(&pla); + pha.pa_func = pf_check6_out; + pha.pa_rulname = "default-out6"; + pha.pa_flags = PFIL_OUT; + V_pf_ip6_out_hook = pfil_add_hook(&pha); + pla.pa_flags = PFIL_OUT | PFIL_HEADPTR | PFIL_HOOKPTR; + pla.pa_head = V_inet6_pfil_head; + pla.pa_hook = V_pf_ip6_out_hook; + (void)pfil_link(&pla); #endif - return (ESRCH); /* XXX */ - } - pfil_add_hook_flags(pf_check6_in, NULL, PFIL_IN | PFIL_WAITOK, pfh_inet6); - pfil_add_hook_flags(pf_check6_out, NULL, PFIL_OUT | PFIL_WAITOK, pfh_inet6); -#endif V_pf_pfil_hooked = 1; return (0); @@ -4123,33 +4149,17 @@ static int dehook_pf(void) { -#ifdef INET - struct pfil_head *pfh_inet; -#endif -#ifdef INET6 - struct pfil_head *pfh_inet6; -#endif if (V_pf_pfil_hooked == 0) return (0); #ifdef INET - pfh_inet = pfil_head_get(PFIL_TYPE_AF, AF_INET); - if (pfh_inet == NULL) - return (ESRCH); /* XXX */ - pfil_remove_hook_flags(pf_check_in, NULL, PFIL_IN | PFIL_WAITOK, - pfh_inet); - pfil_remove_hook_flags(pf_check_out, NULL, PFIL_OUT | PFIL_WAITOK, - pfh_inet); + pfil_remove_hook(V_pf_ip4_in_hook); + pfil_remove_hook(V_pf_ip4_out_hook); #endif #ifdef INET6 - pfh_inet6 = pfil_head_get(PFIL_TYPE_AF, AF_INET6); - if (pfh_inet6 == NULL) - return (ESRCH); /* XXX */ - pfil_remove_hook_flags(pf_check6_in, NULL, PFIL_IN | PFIL_WAITOK, - pfh_inet6); - pfil_remove_hook_flags(pf_check6_out, NULL, PFIL_OUT | PFIL_WAITOK, - pfh_inet6); + pfil_remove_hook(V_pf_ip6_in_hook); + pfil_remove_hook(V_pf_ip6_out_hook); #endif V_pf_pfil_hooked = 0;