From 9978a7d9242f744fc7473e287332c8df88e33e3e Mon Sep 17 00:00:00 2001 From: glebius Date: Thu, 31 Jan 2019 23:01:03 +0000 Subject: [PATCH] New pfil(9) KPI together with newborn pfil API and control utility. The KPI have been reviewed and cleansed of features that were planned back 20 years ago and never implemented. The pfil(9) internals have been made opaque to protocols with only returned types and function declarations exposed. The KPI is made more strict, but at the same time more extensible, as kernel uses same command structures that userland ioctl uses. In nutshell [KA]PI is about declaring filtering points, declaring filters and linking and unlinking them together. New [KA]PI makes it possible to reconfigure pfil(9) configuration: change order of hooks, rehook filter from one filtering point to a different one, disconnect a hook on output leaving it on input only, prepend/append a filter to existing list of filters. Now it possible for a single packet filter to provide multiple rulesets that may be linked to different points. Think of per-interface ACLs in Cisco or Juniper. None of existing packet filters yet support that, however limited usage is already possible, e.g. default ruleset can be moved to single interface, as soon as interface would pride their filtering points. Another future feature is possiblity to create pfil heads, that provide not an mbuf pointer but just a memory pointer with length. That would allow filtering at very early stages of a packet lifecycle, e.g. when packet has just been received by a NIC and no mbuf was yet allocated. Differential Revision: https://reviews.freebsd.org/D18951 --- ObsoleteFiles.inc | 6 + sbin/Makefile | 1 + sbin/pfilctl/Makefile | 9 + sbin/pfilctl/pfilctl.8 | 117 +++ sbin/pfilctl/pfilctl.c | 230 +++++ share/man/man9/Makefile | 6 +- share/man/man9/pfil.9 | 293 ++---- sys/contrib/ipfilter/netinet/ip_fil_freebsd.c | 115 ++- sys/net/if_bridge.c | 104 +- sys/net/if_enc.c | 10 +- sys/net/if_ethersubr.c | 41 +- sys/net/if_var.h | 5 +- sys/net/pfil.c | 892 +++++++++++------- sys/net/pfil.h | 213 +++-- sys/netinet/ip_fastfwd.c | 16 +- sys/netinet/ip_input.c | 25 +- sys/netinet/ip_output.c | 15 +- sys/netinet/ip_var.h | 5 +- sys/netinet/siftr.c | 70 +- sys/netinet6/ip6_fastfwd.c | 12 +- sys/netinet6/ip6_forward.c | 9 +- sys/netinet6/ip6_input.c | 25 +- sys/netinet6/ip6_output.c | 15 +- sys/netinet6/ip6_var.h | 6 +- sys/netpfil/ipfw/ip_fw_eaction.c | 2 +- sys/netpfil/ipfw/ip_fw_pfil.c | 169 ++-- sys/netpfil/pf/pf_ioctl.c | 160 ++-- 27 files changed, 1576 insertions(+), 995 deletions(-) create mode 100644 sbin/pfilctl/Makefile create mode 100644 sbin/pfilctl/pfilctl.8 create mode 100644 sbin/pfilctl/pfilctl.c diff --git a/ObsoleteFiles.inc b/ObsoleteFiles.inc index 3a872c3ca3ff..1cdb447200a3 100644 --- a/ObsoleteFiles.inc +++ b/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 diff --git a/sbin/Makefile b/sbin/Makefile index 4f08a82fe572..2c4b042d91ec 100644 --- a/sbin/Makefile +++ b/sbin/Makefile @@ -52,6 +52,7 @@ SUBDIR=adjkerntz \ newfs_msdos \ nfsiod \ nos-tun \ + pfilctl \ ping \ rcorder \ reboot \ diff --git a/sbin/pfilctl/Makefile b/sbin/pfilctl/Makefile new file mode 100644 index 000000000000..04f0a622ce14 --- /dev/null +++ b/sbin/pfilctl/Makefile @@ -0,0 +1,9 @@ +# $FreeBSD$ + +PROG= pfilctl +SRCS= pfilctl.c +WARNS?= 6 + +MAN= pfilctl.8 + +.include diff --git a/sbin/pfilctl/pfilctl.8 b/sbin/pfilctl/pfilctl.8 new file mode 100644 index 000000000000..d0a50e489a03 --- /dev/null +++ b/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 . diff --git a/sbin/pfilctl/pfilctl.c b/sbin/pfilctl/pfilctl.c new file mode 100644 index 000000000000..363feabca116 --- /dev/null +++ b/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)"); +} diff --git a/share/man/man9/Makefile b/share/man/man9/Makefile index 3034c24c6adb..bd816bf519fa 100644 --- a/share/man/man9/Makefile +++ b/share/man/man9/Makefile @@ -1635,13 +1635,9 @@ MLINKS+=pci_iov_schema.9 pci_iov_schema_alloc_node.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 \ diff --git a/share/man/man9/pfil.9 b/share/man/man9/pfil.9 index 843191e0b4ab..c2186cf1b540 100644 --- a/share/man/man9/pfil.9 +++ b/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. -.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 -.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: .Pp -.Bl -tag -width "AF_INET6" -offset XXX -compact -.It AF_INET +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 +configuration with help of +.Xr pfilctl 8 +utility. +.Pp +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 @@ interface first appeared in .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. diff --git a/sys/contrib/ipfilter/netinet/ip_fil_freebsd.c b/sys/contrib/ipfilter/netinet/ip_fil_freebsd.c index 309027b500cc..292a119e2c43 100644 --- a/sys/contrib/ipfilter/netinet/ip_fil_freebsd.c +++ b/sys/contrib/ipfilter/netinet/ip_fil_freebsd.c @@ -25,6 +25,7 @@ static const char rcsid[] = "@(#)$Id$"; # include "opt_random_ip_id.h" #endif #include +#include #include #include #include @@ -126,32 +127,33 @@ static void ipf_ifevent(arg, ifp) -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 @@ ipf_inject(fin, m) return error; } -int ipf_pfil_unhook(void) { - struct pfil_head *ph_inet; -#ifdef USE_INET6 - struct pfil_head *ph_inet6; -#endif +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) - 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 +int ipf_pfil_unhook(void) { + + pfil_remove_hook(V_ipf_inet_hook); + +#ifdef USE_INET6 + pfil_remove_hook(V_ipf_inet6_hook); +#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 diff --git a/sys/net/if_bridge.c b/sys/net/if_bridge.c index 578e0756498b..96dc046840d4 100644 --- a/sys/net/if_bridge.c +++ b/sys/net/if_bridge.c @@ -1970,9 +1970,9 @@ bridge_dummynet(struct mbuf *m, struct ifnet *ifp) 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 @@ bridge_forward(struct bridge_softc *sc, struct bridge_iflist *sbif, 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_forward(struct bridge_softc *sc, struct bridge_iflist *sbif, 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 @@ bridge_input(struct ifnet *ifp, struct mbuf *m) #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 @@ bridge_input(struct ifnet *ifp, struct mbuf *m) 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 @@ bridge_broadcast(struct bridge_softc *sc, struct ifnet *src_if, } /* 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 @@ bridge_broadcast(struct bridge_softc *sc, struct ifnet *src_if, * 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 @@ bridge_pfil(struct mbuf **mp, struct ifnet *bifp, struct ifnet *ifp, int dir) 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 @@ bridge_pfil(struct mbuf **mp, struct ifnet *bifp, struct ifnet *ifp, int dir) } /* 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 @@ bridge_pfil(struct mbuf **mp, struct ifnet *bifp, struct ifnet *ifp, int dir) /* * Run the packet through pfil */ + rv = PFIL_PASS; switch (ether_type) { case ETHERTYPE_IP: /* @@ -3226,25 +3228,19 @@ bridge_pfil(struct mbuf **mp, struct ifnet *bifp, struct ifnet *ifp, int dir) * 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 @@ bridge_pfil(struct mbuf **mp, struct ifnet *bifp, struct ifnet *ifp, int dir) 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 - default: - error = 0; - break; } - if (*mp == NULL) - return (error); - if (error != 0) - goto bad; + switch (rv) { + case PFIL_CONSUMED: + return (0); + case PFIL_DROPPED: + return (EPERM); + default: + break; + } error = -1; diff --git a/sys/net/if_enc.c b/sys/net/if_enc.c index 01e416535dbd..7bb196b672c1 100644 --- a/sys/net/if_enc.c +++ b/sys/net/if_enc.c @@ -285,24 +285,24 @@ enc_hhook(int32_t hhook_type, int32_t hhook_id, void *udata, void *ctx_data, 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); } diff --git a/sys/net/if_ethersubr.c b/sys/net/if_ethersubr.c index 628e32b17642..ff7705b2e21d 100644 --- a/sys/net/if_ethersubr.c +++ b/sys/net/if_ethersubr.c @@ -90,7 +90,7 @@ CTASSERT(sizeof (struct ether_header) == ETHER_ADDR_LEN * 2 + 2); 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 @@ ether_set_pcp(struct mbuf **mp, struct ifnet *ifp, uint8_t pcp) int ether_output_frame(struct ifnet *ifp, struct mbuf *m) { - int error; uint8_t pcp; pcp = ifp->if_pcp; @@ -465,15 +464,14 @@ ether_output_frame(struct ifnet *ifp, struct mbuf *m) !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 @@ SYSINIT(ether, SI_SUB_INIT_IF, SI_ORDER_ANY, ether_init, NULL); static void vnet_ether_init(__unused void *arg) { - int i; + struct pfil_head_args args; + + 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); - /* 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); #ifdef VIMAGE netisr_register_vnet(ðer_nh); #endif @@ -756,11 +754,8 @@ VNET_SYSINIT(vnet_ether_init, SI_SUB_PROTO_IF, SI_ORDER_ANY, 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 @@ ether_demux(struct ifnet *ifp, struct mbuf *m) 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; } diff --git a/sys/net/if_var.h b/sys/net/if_var.h index 42241e74fb81..22cac21b2136 100644 --- a/sys/net/if_var.h +++ b/sys/net/if_var.h @@ -95,8 +95,9 @@ CK_STAILQ_HEAD(ifmultihead, ifmultiaddr); 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 diff --git a/sys/net/pfil.c b/sys/net/pfil.c index 96069123a935..acfb25467fee 100644 --- a/sys/net/pfil.c +++ b/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,180 +51,167 @@ #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 (pfh->pfil_func != NULL) { - rv = (*pfh->pfil_func)(pfh->pfil_arg, &m, ifp, dir, - 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; } } - PFIL_RUNLOCK(ph, &rmpt); - *mp = m; - return (rv); -} - -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. - */ -int -pfil_try_rlock(struct pfil_head *ph, struct rm_priotracker *tracker) -{ - - return (PFIL_TRY_RLOCK(ph, tracker)); -} - -/* - * pfil_rlock() acquires rm reader lock for specified head. - */ -void -pfil_rlock(struct pfil_head *ph, struct rm_priotracker *tracker) -{ - - PFIL_RLOCK(ph, tracker); -} - -/* - * pfil_runlock() releases reader lock for specified head. - */ -void -pfil_runlock(struct pfil_head *ph, struct rm_priotracker *tracker) -{ - - PFIL_RUNLOCK(ph, tracker); -} - -/* - * pfil_wlock() acquires writer lock for specified head. - */ -void -pfil_wlock(struct pfil_head *ph) -{ - - PFIL_WLOCK(ph); -} - -/* - * pfil_wunlock() releases writer lock for specified head. - */ -void -pfil_wunlock(struct pfil_head *ph) -{ - - PFIL_WUNLOCK(ph); -} - -/* - * pfil_wowned() returns a non-zero value if the current thread owns - * an exclusive lock. - */ -int -pfil_wowned(struct pfil_head *ph) -{ - - return (PFIL_WOWNED(ph)); + PFIL_EPOCH_EXIT(et); + return (rvi); } /* * pfil_head_register() registers a pfil_head with the packet filter hook * mechanism. */ -int -pfil_head_register(struct pfil_head *ph) +pfil_head_t +pfil_head_register(struct pfil_head_args *pa) { - struct pfil_head *lph; + struct pfil_head *head, *list; - 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); + 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); } - } - 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_INSERT_HEAD(&V_pfil_head_list, head, head_list); + PFIL_UNLOCK(); + + return (head); } /* @@ -231,245 +219,441 @@ pfil_head_register(struct pfil_head *ph) * mechanism. The producer of the hook promises that all outstanding * invocations of the hook have completed before it unregisters the hook. */ -int -pfil_head_unregister(struct pfil_head *ph) +void +pfil_head_unregister(pfil_head_t ph) { - 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, *next; + + 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_head_get() returns the pfil_head for a given key/dlt. - */ -struct pfil_head * -pfil_head_get(int type, u_long val) +pfil_hook_t +pfil_add_hook(struct pfil_hook_args *pa) { - struct pfil_head *ph; + struct pfil_hook *hook, *list; - 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); -} + MPASS(pa->pa_version == PFIL_VERSION); -/* - * 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. - */ -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)); -} + 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_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. - */ -int -pfil_add_hook(pfil_func_t func, void *arg, int flags, struct pfil_head *ph) -{ - return (pfil_add_hook_priv(func, arg, flags, ph, false)); + 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); } static int -pfil_add_hook_priv(void *func, void *arg, int flags, - struct pfil_head *ph, bool hasflags) +pfil_unlink(struct pfil_link_args *pa, pfil_head_t head, pfil_hook_t hook) { - struct packet_filter_hook *pfh1 = NULL; - struct packet_filter_hook *pfh2 = NULL; - int err; + struct pfil_link *in, *out; - 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_ASSERT(); + + if (pa->pa_flags & PFIL_IN) { + in = pfil_link_remove(&head->head_in, hook); + if (in != NULL) { + head->head_nhooksin--; + hook->hook_links--; } - } - 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; + } 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--; } - } - 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; - } - ph->ph_nhooks++; - } - 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); -} + } else + out = NULL; + 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)); -} + 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); -/* - * 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; - - 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); -} - -/* - * 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) -{ - struct packet_filter_hook *pfh; - - /* - * 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); - - /* - * 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); + if (in == NULL && out == NULL) + return (ENOENT); else - TAILQ_INSERT_TAIL(chain, pfh1, pfil_chain); + return (0); +} + +int +pfil_link(struct pfil_link_args *pa) +{ + struct pfil_link *in, *out, *link; + struct pfil_head *head; + struct pfil_hook *hook; + int error; + + 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); +} + +static void +pfil_link_free(epoch_context_t ctx) +{ + struct pfil_link *link; + + link = __containerof(ctx, struct pfil_link, link_epoch_ctx); + free(link, M_PFIL); +} + +/* + * pfil_remove_hook removes a filter from all filtering points. + */ +void +pfil_remove_hook(pfil_hook_t hook) +{ + struct pfil_head *head; + struct pfil_link *in, *out; + + 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; + } + LIST_REMOVE(hook, hook_list); + PFIL_UNLOCK(); + MPASS(hook->hook_links == 0); + free(hook, M_PFIL); } /* * Internal: Remove a pfil hook from a hook chain. */ -static int -pfil_chain_remove(pfil_chain_t *chain, void *func, void *arg) +static struct pfil_link * +pfil_link_remove(pfil_chain_t *chain, pfil_hook_t hook) { - struct packet_filter_hook *pfh; + struct pfil_link *link; - 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); + 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 (ENOENT); + + return (NULL); } -/* - * Stuff that must be initialized for every instance (including the first of - * course). - */ static void -vnet_pfil_init(const void *unused __unused) +pfil_init(const void *unused __unused) { + struct make_dev_args args; + int error; - LIST_INIT(&V_pfil_head_list); - PFIL_LOCK_INIT_REAL(&V_pfil_lock, "shared"); + 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)); } - /* - * Called for the removal of each instance. - */ -static void -vnet_pfil_uninit(const void *unused __unused) -{ - - 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); -} - -/* - * 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); - +SYSINIT(pfil_init, SI_SUB_PROTO_PFIL, SI_ORDER_FIRST, 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. + * User control interface. */ -VNET_SYSUNINIT(vnet_pfil_uninit, SI_SUB_PROTO_PFIL, SI_ORDER_FIRST, - vnet_pfil_uninit, NULL); +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) +{ + 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 +pfilioc_listheads(struct pfilioc_list *req) +{ + struct pfil_head *head; + struct pfil_link *link; + struct pfilioc_head *iohead; + struct pfilioc_hook *iohook; + u_int nheads, nhooks, hd, hk; + int error; + + PFIL_LOCK(); +restart: + nheads = nhooks = 0; + LIST_FOREACH(head, &V_pfil_head_list, head_list) { + nheads++; + nhooks += head->head_nhooksin + head->head_nhooksout; + } + PFIL_UNLOCK(); + + if (req->pio_nheads < nheads || req->pio_nhooks < nhooks) { + req->pio_nheads = nheads; + req->pio_nhooks = nhooks; + return (0); + } + + 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; + } + 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_UNLOCK(); + + 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)); + + req->pio_nheads = hd; + req->pio_nhooks = hk; + + free(iohead, M_TEMP); + free(iohook, M_TEMP); + + return (error); +} + +static int +pfilioc_listhooks(struct pfilioc_list *req) +{ + struct pfil_hook *hook; + struct pfilioc_hook *iohook; + u_int nhooks, hk; + int error; + + PFIL_LOCK(); +restart: + nhooks = 0; + LIST_FOREACH(hook, &V_pfil_hook_list, hook_list) + nhooks++; + PFIL_UNLOCK(); + + if (req->pio_nhooks < nhooks) { + req->pio_nhooks = nhooks; + return (0); + } + + iohook = malloc(sizeof(*iohook) * nhooks, M_TEMP, M_WAITOK); + + 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; + } + 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(); + + error = copyout(iohook, req->pio_hooks, + sizeof(*iohook) * min(req->pio_nhooks, hk)); + req->pio_nhooks = hk; + free(iohook, M_TEMP); + + return (error); +} + +static int +pfilioc_link(struct pfilioc_link *req) +{ + struct pfil_link_args args; + + if (req->pio_flags & ~(PFIL_IN | PFIL_OUT | PFIL_UNLINK | PFIL_APPEND)) + return (EINVAL); + + 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)); +} diff --git a/sys/net/pfil.h b/sys/net/pfil.h index bfe108a1f1fe..13d78e6a277f 100644 --- a/sys/net/pfil.h +++ b/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 *); +/* + * 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. + */ + +typedef struct pfil_hook * pfil_hook_t; +typedef struct pfil_head * pfil_head_t; /* - * 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. + * Give us a chance to modify pfil_xxx_args structures in future. */ -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_VERSION 1 -#define PFIL_IN 0x00000001 -#define PFIL_OUT 0x00000002 -#define PFIL_WAITOK 0x00000004 -#define PFIL_FWD 0x00000008 -#define PFIL_ALL (PFIL_IN|PFIL_OUT) - -typedef TAILQ_HEAD(pfil_chain, packet_filter_hook) pfil_chain_t; - -#define PFIL_TYPE_AF 1 /* key is AF_* type */ -#define PFIL_TYPE_IFNET 2 /* key is ifnet pointer */ - -#define PFIL_FLAG_PRIVATE_LOCK 0x01 /* Personal lock instead of global */ - -/* - * 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 - 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; +/* 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; }; /* 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) +pfil_hook_t pfil_add_hook(struct pfil_hook_args *); +void pfil_remove_hook(pfil_hook_t); -/* 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 ioctl() and packet filters to set filters. */ +struct pfil_link_args { + int pa_version; + int pa_flags; + union { + 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 head management by protocols. */ -int pfil_head_register(struct pfil_head *); -int pfil_head_unregister(struct pfil_head *); +/* Public function to configure filter chains. Used by ioctl() and filters. */ +int pfil_link(struct pfil_link_args *); -/* 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); +/* 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 inspection points. */ +pfil_head_t pfil_head_register(struct pfil_head_args *); +void pfil_head_unregister(pfil_head_t); + +/* 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_ */ diff --git a/sys/netinet/ip_fastfwd.c b/sys/netinet/ip_fastfwd.c index 643a75e2294b..77a08b1a8af1 100644 --- a/sys/netinet/ip_fastfwd.c +++ b/sys/netinet/ip_fastfwd.c @@ -90,11 +90,11 @@ __FBSDID("$FreeBSD$"); #include #include -#include #include #include #include #include +#include #include #include @@ -228,12 +228,11 @@ ip_tryforward(struct mbuf *m) /* * 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 @@ ip_tryforward(struct mbuf *m) /* * 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); diff --git a/sys/netinet/ip_input.c b/sys/netinet/ip_input.c index dd00d13a4d71..a1ec5935a826 100644 --- a/sys/netinet/ip_input.c +++ b/sys/netinet/ip_input.c @@ -57,11 +57,11 @@ __FBSDID("$FreeBSD$"); #include #include -#include #include #include #include #include +#include #include #include #include @@ -134,7 +134,7 @@ SYSCTL_INT(_net_inet_ip, OID_AUTO, check_interface, CTLFLAG_VNET | CTLFLAG_RW, &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 @@ SYSCTL_PROC(_net_inet_ip, IPCTL_INTRDQDROPS, intr_direct_queue_drops, void ip_init(void) { + struct pfil_head_args args; struct protosw *pr; int i; @@ -311,11 +312,11 @@ ip_init(void) 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 @@ ip_destroy(void *unused __unused) #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 @@ ip_input(struct mbuf *m) */ /* 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; diff --git a/sys/netinet/ip_output.c b/sys/netinet/ip_output.c index 7595c3f90535..0c7a26503d07 100644 --- a/sys/netinet/ip_output.c +++ b/sys/netinet/ip_output.c @@ -121,11 +121,16 @@ ip_output_pfil(struct mbuf **mp, struct ifnet *ifp, struct inpcb *inp, /* 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 @@ ip_output(struct mbuf *m, struct mbuf *opt, struct route *ro, int flags, #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; diff --git a/sys/netinet/ip_var.h b/sys/netinet/ip_var.h index 86615a15ad26..d55a18bba91d 100644 --- a/sys/netinet/ip_var.h +++ b/sys/netinet/ip_var.h @@ -241,8 +241,9 @@ extern int (*ip_rsvp_vif)(struct socket *, struct sockopt *); 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); diff --git a/sys/netinet/siftr.c b/sys/netinet/siftr.c index 4d063c360386..68d8f8e16fa1 100644 --- a/sys/netinet/siftr.c +++ b/sys/netinet/siftr.c @@ -94,10 +94,12 @@ __FBSDID("$FreeBSD$"); #include #include #include +#include #include #ifdef SIFTR_IPV6 #include +#include #include #endif /* SIFTR_IPV6 */ @@ -831,9 +833,9 @@ siftr_siftdata(struct pkt_node *pn, struct inpcb *inp, struct tcpcb *tp, * 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 @@ siftr_chkpkt(void *arg, struct mbuf **m, struct ifnet *ifp, int dir, 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 @@ siftr_chkpkt(void *arg, struct mbuf **m, struct ifnet *ifp, int dir, 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 @@ siftr_chkpkt6(void *arg, struct mbuf **m, struct ifnet *ifp, int dir, 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 @@ siftr_chkpkt6(void *arg, struct mbuf **m, struct ifnet *ifp, int dir, } #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(); diff --git a/sys/netinet6/ip6_fastfwd.c b/sys/netinet6/ip6_fastfwd.c index 8f8b176607c5..11eb0e7548d5 100644 --- a/sys/netinet6/ip6_fastfwd.c +++ b/sys/netinet6/ip6_fastfwd.c @@ -156,10 +156,10 @@ ip6_tryforward(struct mbuf *m) /* * 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 @@ ip6_tryforward(struct mbuf *m) 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 @@ ip6_tryforward(struct mbuf *m) /* * 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; /* diff --git a/sys/netinet6/ip6_forward.c b/sys/netinet6/ip6_forward.c index ed743f65c867..0676a58225a3 100644 --- a/sys/netinet6/ip6_forward.c +++ b/sys/netinet6/ip6_forward.c @@ -320,15 +320,14 @@ ip6_forward(struct mbuf *m, int srcrt) 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. */ diff --git a/sys/netinet6/ip6_input.c b/sys/netinet6/ip6_input.c index 712b9923d8e7..531cfff43f0e 100644 --- a/sys/netinet6/ip6_input.c +++ b/sys/netinet6/ip6_input.c @@ -191,7 +191,7 @@ SYSCTL_PROC(_net_inet6_ip6, IPV6CTL_INTRDQMAXLEN, intr_direct_queue_maxlen, #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 @@ static struct mbuf *ip6_pullexthdr(struct mbuf *, size_t, int); void ip6_init(void) { + struct pfil_head_args args; struct protosw *pr; int i; @@ -227,11 +228,11 @@ ip6_init(void) &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 @@ ip6_destroy(void *unused __unused) #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 @@ ip6_input(struct mbuf *m) */ /* 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); diff --git a/sys/netinet6/ip6_output.c b/sys/netinet6/ip6_output.c index 741521abb8a1..e36beb355b38 100644 --- a/sys/netinet6/ip6_output.c +++ b/sys/netinet6/ip6_output.c @@ -792,16 +792,21 @@ ip6_output(struct mbuf *m0, struct ip6_pktopts *opt, } /* 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. */ diff --git a/sys/netinet6/ip6_var.h b/sys/netinet6/ip6_var.h index f235572dd03e..bf15f833b326 100644 --- a/sys/netinet6/ip6_var.h +++ b/sys/netinet6/ip6_var.h @@ -346,8 +346,10 @@ VNET_DECLARE(int, ip6_use_defzone); /* Whether to use the default scope * 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) diff --git a/sys/netpfil/ipfw/ip_fw_eaction.c b/sys/netpfil/ipfw/ip_fw_eaction.c index 05cc174cb283..1cb2f812936c 100644 --- a/sys/netpfil/ipfw/ip_fw_eaction.c +++ b/sys/netpfil/ipfw/ip_fw_eaction.c @@ -38,9 +38,9 @@ __FBSDID("$FreeBSD$"); #include #include #include -#include #include /* ip_fw.h requires IFNAMSIZ */ +#include #include #include /* struct ipfw_rule_ref */ #include diff --git a/sys/netpfil/ipfw/ip_fw_pfil.c b/sys/netpfil/ipfw/ip_fw_pfil.c index feb4a20f9b69..5a3cd052bb2d 100644 --- a/sys/netpfil/ipfw/ip_fw_pfil.c +++ b/sys/netpfil/ipfw/ip_fw_pfil.c @@ -48,6 +48,7 @@ __FBSDID("$FreeBSD$"); #include #include +#include #include #include #include @@ -85,10 +86,6 @@ int ipfw_chg_hook(SYSCTL_HANDLER_ARGS); /* 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 @@ SYSEND * 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 @@ ipfw_check_packet(void *arg, struct mbuf **m0, struct ifnet *ifp, int dir, 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 @@ ipfw_check_packet(void *arg, struct mbuf **m0, struct ifnet *ifp, int dir, 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 @@ ipfw_check_packet(void *arg, struct mbuf **m0, struct ifnet *ifp, int dir, * 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 @@ ipfw_check_packet(void *arg, struct mbuf **m0, struct ifnet *ifp, int dir, 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 @@ ipfw_check_packet(void *arg, struct mbuf **m0, struct ifnet *ifp, int dir, */ 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 @@ ipfw_check_packet(void *arg, struct mbuf **m0, struct ifnet *ifp, int dir, 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_check_packet(void *arg, struct mbuf **m0, struct ifnet *ifp, int dir, /* * 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 @@ ipfw_check_frame(void *arg, struct mbuf **m0, struct ifnet *ifp, int dir, 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 @@ ipfw_check_frame(void *arg, struct mbuf **m0, struct ifnet *ifp, int dir, } *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 @@ ipfw_divert(struct mbuf **m0, int incoming, struct ipfw_rule_ref *rule, /* * 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; } diff --git a/sys/netpfil/pf/pf_ioctl.c b/sys/netpfil/pf/pf_ioctl.c index eba8a7f64a3c..91b2c1d33839 100644 --- a/sys/netpfil/pf/pf_ioctl.c +++ b/sys/netpfil/pf/pf_ioctl.c @@ -169,16 +169,16 @@ static void pf_tbladdr_copyout(struct pf_addr_wrap *); * 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 @@ shutdown_pf(void) } #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 @@ pf_check_in(void *arg, struct mbuf **m, struct ifnet *ifp, int dir, int flags, *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 @@ pf_check_out(void *arg, struct mbuf **m, struct ifnet *ifp, int dir, int flags, *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 @@ pf_check6_in(void *arg, struct mbuf **m, struct ifnet *ifp, int dir, int flags, 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,45 +4070,76 @@ pf_check6_out(void *arg, struct mbuf **m, struct ifnet *ifp, int dir, int flags, m_freem(*m); *m = NULL; } - if (chk != PF_PASS) - return (EACCES); - return (0); + + return (chk == PF_PASS ? PFIL_PASS : PFIL_DROPPED); } #endif /* INET6 */ +#ifdef 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 +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) { -#ifdef INET - struct pfil_head *pfh_inet; -#endif -#ifdef INET6 - struct pfil_head *pfh_inet6; -#endif + 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); -#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); + 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 V_pf_pfil_hooked = 1; @@ -4123,33 +4149,17 @@ hook_pf(void) 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;