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
This commit is contained in:
glebius 2019-01-31 23:01:03 +00:00
parent 4e4796faba
commit 9978a7d924
27 changed files with 1576 additions and 995 deletions

View File

@ -38,6 +38,12 @@
# xargs -n1 | sort | uniq -d; # xargs -n1 | sort | uniq -d;
# done # 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 # 20190126: adv(4) / adw(4) removal
OLD_FILES+=usr/share/man/man4/adv.4.gz OLD_FILES+=usr/share/man/man4/adv.4.gz
OLD_FILES+=usr/share/man/man4/adw.4.gz OLD_FILES+=usr/share/man/man4/adw.4.gz

View File

@ -52,6 +52,7 @@ SUBDIR=adjkerntz \
newfs_msdos \ newfs_msdos \
nfsiod \ nfsiod \
nos-tun \ nos-tun \
pfilctl \
ping \ ping \
rcorder \ rcorder \
reboot \ reboot \

9
sbin/pfilctl/Makefile Normal file
View File

@ -0,0 +1,9 @@
# $FreeBSD$
PROG= pfilctl
SRCS= pfilctl.c
WARNS?= 6
MAN= pfilctl.8
.include <bsd.prog.mk>

117
sbin/pfilctl/pfilctl.8 Normal file
View File

@ -0,0 +1,117 @@
.\" Copyright (c) 2019 Gleb Smirnoff <glebius@FreeBSD.org>
.\"
.\" 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 .

230
sbin/pfilctl/pfilctl.c Normal file
View File

@ -0,0 +1,230 @@
/*-
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
*
* Copyright (c) 2019 Gleb Smirnoff <glebius@FreeBSD.org>
*
* 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 <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <sys/param.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <net/pfil.h>
#include <err.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
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)");
}

View File

@ -1635,13 +1635,9 @@ MLINKS+=pci_iov_schema.9 pci_iov_schema_alloc_node.9 \
MLINKS+=pfil.9 pfil_add_hook.9 \ MLINKS+=pfil.9 pfil_add_hook.9 \
pfil.9 pfil_head_register.9 \ pfil.9 pfil_head_register.9 \
pfil.9 pfil_head_unregister.9 \ pfil.9 pfil_head_unregister.9 \
pfil.9 pfil_hook_get.9 \
pfil.9 pfil_remove_hook.9 \ pfil.9 pfil_remove_hook.9 \
pfil.9 pfil_rlock.9 \
pfil.9 pfil_run_hooks.9 \ pfil.9 pfil_run_hooks.9 \
pfil.9 pfil_runlock.9 \ pfil.9 pfil_link.9
pfil.9 pfil_wlock.9 \
pfil.9 pfil_wunlock.9
MLINKS+=pfind.9 zpfind.9 MLINKS+=pfind.9 zpfind.9
MLINKS+=PHOLD.9 PRELE.9 \ MLINKS+=PHOLD.9 PRELE.9 \
PHOLD.9 _PHOLD.9 \ PHOLD.9 _PHOLD.9 \

View File

@ -1,5 +1,6 @@
.\" $NetBSD: pfil.9,v 1.22 2003/07/01 13:04:06 wiz Exp $ .\" $NetBSD: pfil.9,v 1.22 2003/07/01 13:04:06 wiz Exp $
.\" .\"
.\" Copyright (c) 2019 Gleb Smirnoff <glebius@FreeBSD.org>
.\" Copyright (c) 1996 Matthew R. Green .\" Copyright (c) 1996 Matthew R. Green
.\" All rights reserved. .\" All rights reserved.
.\" .\"
@ -28,194 +29,127 @@
.\" .\"
.\" $FreeBSD$ .\" $FreeBSD$
.\" .\"
.Dd March 10, 2018 .Dd January 28, 2019
.Dt PFIL 9 .Dt PFIL 9
.Os .Os
.Sh NAME .Sh NAME
.Nm pfil , .Nm pfil ,
.Nm pfil_head_register , .Nm pfil_head_register ,
.Nm pfil_head_unregister , .Nm pfil_head_unregister ,
.Nm pfil_head_get , .Nm pfil_link ,
.Nm pfil_add_hook , .Nm pfil_run_hooks
.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
.Nd packet filter interface .Nd packet filter interface
.Sh SYNOPSIS .Sh SYNOPSIS
.In sys/param.h .In sys/param.h
.In sys/mbuf.h .In sys/mbuf.h
.In net/if.h
.In net/pfil.h .In net/pfil.h
.Bd -literal .Ft pfil_head_t
typedef int (*pfil_func_t)(void *arg, struct mbuf **mp, struct ifnet *, int dir, struct inpcb); .Fn pfil_head_register "struct pfil_head_args *args"
.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 void .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 .Ft void
.Fn pfil_runlock "struct pfil_head *" "struct rm_priotracker *" .Fn pfil_remove_hook "pfil_hook_t"
.Ft void .Ft int
.Fn pfil_wlock "struct pfil_head *" .Fn pfil_link "struct pfil_link_args *args"
.Ft void .Ft int
.Fn pfil_wunlock "struct pfil_head *" .Fn pfil_run_hooks "phil_head_t *" "pfil_packet_t" "struct ifnet *" "int" "struct inpcb *"
.Ed
.Sh DESCRIPTION .Sh DESCRIPTION
The The
.Nm .Nm
framework allows for a specified function to be invoked for every framework allows for a specified function or a list of functions
incoming or outgoing packet for a particular network I/O stream. 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 These hooks may be used to implement a firewall or perform packet
transformations. transformations.
.Pp .Pp
Packet filtering points are registered with Packet filtering points, for historical reasons named
.Em heads ,
are registered with
.Fn pfil_head_register . .Fn pfil_head_register .
Filtering points are identified by a key The function is supplied with special versioned
.Pq Vt "void *" .Vt struct pfil_head_args
and a data link type structure that specifies type and features of the head as well as
.Pq Vt int human readable name.
in the If the filtering point to be ever destroyed, the subsystem that
.Vt pfil_head created it must unregister it with call to
structure. .Fn pfil_head_unregister .
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.
.Pp .Pp
Packet filters register/unregister themselves with a filtering point Packet filtering systems may register arbitrary number of filters,
with the for historical reasons named
.Em hooks .
To register a new hook
.Fn pfil_add_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 .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. functions.
See
.Xr rmlock 9
for more details.
.Sh FILTERING POINTS
Currently, filtering points are implemented for the following link types:
.Pp .Pp
.Bl -tag -width "AF_INET6" -offset XXX -compact To connect existing
.It AF_INET .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. IPv4 packets.
.It AF_INET6 .It inet6
IPv6 packets. IPv6 packets.
.It AF_LINK .It ethernet
Link-layer packets. Link-layer packets.
.El .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 .Pp
The Default rulesets are automatically linked to these heads to preserve
.Fn pfil_head_unregister historical behavavior.
function
might sleep!
.Sh SEE ALSO .Sh SEE ALSO
.Xr bpf 4 , .Xr ipfilter 4 ,
.Xr if_bridge 4 , .Xr ipfw 4 ,
.Xr rmlock 9 .Xr pf 4 ,
.Xr pfilctl 8
.Sh HISTORY .Sh HISTORY
The The
.Nm .Nm
@ -223,45 +157,8 @@ interface first appeared in
.Nx 1.3 . .Nx 1.3 .
The The
.Nm .Nm
input and output lists were originally implemented as interface was imported into
.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
.Fx 5.2 . .Fx 5.2 .
.Nm In
lock export was added in .Fx 13.0
.Fx 10.0 . the interface was significantly rewritten.
.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.

View File

@ -25,6 +25,7 @@ static const char rcsid[] = "@(#)$Id$";
# include "opt_random_ip_id.h" # include "opt_random_ip_id.h"
#endif #endif
#include <sys/param.h> #include <sys/param.h>
#include <sys/conf.h>
#include <sys/errno.h> #include <sys/errno.h>
#include <sys/types.h> #include <sys/types.h>
#include <sys/file.h> #include <sys/file.h>
@ -126,32 +127,33 @@ static void ipf_ifevent(arg, ifp)
static int static pfil_return_t
ipf_check_wrapper(void *arg, struct mbuf **mp, struct ifnet *ifp, int dir) ipf_check_wrapper(struct mbuf **mp, struct ifnet *ifp, int flags,
void *ruleset __unused, struct inpcb *inp)
{ {
struct ip *ip = mtod(*mp, struct ip *); struct ip *ip = mtod(*mp, struct ip *);
int rv; pfil_return_t rv;
CURVNET_SET(ifp->if_vnet); CURVNET_SET(ifp->if_vnet);
rv = ipf_check(&V_ipfmain, ip, ip->ip_hl << 2, ifp, (dir == PFIL_OUT), rv = ipf_check(&V_ipfmain, ip, ip->ip_hl << 2, ifp, (flags & PFIL_OUT),
mp); mp);
CURVNET_RESTORE(); CURVNET_RESTORE();
return rv; return (rv == 0 ? PFIL_PASS : PFIL_DROPPED);
} }
# ifdef USE_INET6 #ifdef USE_INET6
# include <netinet/ip6.h> static pfil_return_t
ipf_check_wrapper6(struct mbuf **mp, struct ifnet *ifp, int flags,
static int void *ruleset __unused, struct inpcb *inp)
ipf_check_wrapper6(void *arg, struct mbuf **mp, struct ifnet *ifp, int dir)
{ {
int error; pfil_return_t rv;
CURVNET_SET(ifp->if_vnet); CURVNET_SET(ifp->if_vnet);
error = ipf_check(&V_ipfmain, mtod(*mp, struct ip *), rv = ipf_check(&V_ipfmain, mtod(*mp, struct ip *),
sizeof(struct ip6_hdr), ifp, (dir == PFIL_OUT), mp); sizeof(struct ip6_hdr), ifp, (flags & PFIL_OUT), mp);
CURVNET_RESTORE(); CURVNET_RESTORE();
return (error);
return (rv == 0 ? PFIL_PASS : PFIL_DROPPED);
} }
# endif # endif
#if defined(IPFILTER_LKM) #if defined(IPFILTER_LKM)
@ -1318,53 +1320,62 @@ ipf_inject(fin, m)
return error; return error;
} }
int ipf_pfil_unhook(void) { VNET_DEFINE_STATIC(pfil_hook_t, ipf_inet_hook);
struct pfil_head *ph_inet; VNET_DEFINE_STATIC(pfil_hook_t, ipf_inet6_hook);
#ifdef USE_INET6 #define V_ipf_inet_hook VNET(ipf_inet_hook)
struct pfil_head *ph_inet6; #define V_ipf_inet6_hook VNET(ipf_inet6_hook)
#endif
ph_inet = pfil_head_get(PFIL_TYPE_AF, AF_INET); int ipf_pfil_unhook(void) {
if (ph_inet != NULL)
pfil_remove_hook((void *)ipf_check_wrapper, NULL, pfil_remove_hook(V_ipf_inet_hook);
PFIL_IN|PFIL_OUT|PFIL_WAITOK, ph_inet);
# ifdef USE_INET6 #ifdef USE_INET6
ph_inet6 = pfil_head_get(PFIL_TYPE_AF, AF_INET6); pfil_remove_hook(V_ipf_inet6_hook);
if (ph_inet6 != NULL) #endif
pfil_remove_hook((void *)ipf_check_wrapper6, NULL,
PFIL_IN|PFIL_OUT|PFIL_WAITOK, ph_inet6);
# endif
return (0); return (0);
} }
int ipf_pfil_hook(void) { 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 #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 #endif
ph_inet = pfil_head_get(PFIL_TYPE_AF, AF_INET); pla.pa_version = PFIL_VERSION;
# ifdef USE_INET6 pla.pa_flags = PFIL_IN | PFIL_OUT |
ph_inet6 = pfil_head_get(PFIL_TYPE_AF, AF_INET6); PFIL_HEADPTR | PFIL_HOOKPTR;
# endif pla.pa_head = V_inet_pfil_head;
if (ph_inet == NULL pla.pa_hook = V_ipf_inet_hook;
# ifdef USE_INET6 error = pfil_link(&pla);
&& ph_inet6 == NULL
# endif
) {
return ENODEV;
}
if (ph_inet != NULL) error6 = 0;
pfil_add_hook((void *)ipf_check_wrapper, NULL, #ifdef USE_INET6
PFIL_IN|PFIL_OUT|PFIL_WAITOK, ph_inet); pla.pa_head = V_inet6_pfil_head;
# ifdef USE_INET6 pla.pa_hook = V_ipf_inet6_hook;
if (ph_inet6 != NULL) error6 = pfil_link(&pla);
pfil_add_hook((void *)ipf_check_wrapper6, NULL, #endif
PFIL_IN|PFIL_OUT|PFIL_WAITOK, ph_inet6);
# endif if (error || error6)
return (0); error = ENODEV;
else
error = 0;
return (error);
} }
void void

View File

@ -1970,9 +1970,9 @@ bridge_dummynet(struct mbuf *m, struct ifnet *ifp)
return; return;
} }
if (PFIL_HOOKED(&V_inet_pfil_hook) if (PFIL_HOOKED_OUT(V_inet_pfil_head)
#ifdef INET6 #ifdef INET6
|| PFIL_HOOKED(&V_inet6_pfil_hook) || PFIL_HOOKED_OUT(V_inet6_pfil_head)
#endif #endif
) { ) {
if (bridge_pfil(&m, sc->sc_ifp, ifp, PFIL_OUT) != 0) 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); ETHER_BPF_MTAP(ifp, m);
/* run the packet filter */ /* run the packet filter */
if (PFIL_HOOKED(&V_inet_pfil_hook) if (PFIL_HOOKED_IN(V_inet_pfil_head)
#ifdef INET6 #ifdef INET6
|| PFIL_HOOKED(&V_inet6_pfil_hook) || PFIL_HOOKED_IN(V_inet6_pfil_head)
#endif #endif
) { ) {
BRIDGE_UNLOCK(sc); BRIDGE_UNLOCK(sc);
@ -2270,9 +2270,9 @@ bridge_forward(struct bridge_softc *sc, struct bridge_iflist *sbif,
BRIDGE_UNLOCK(sc); BRIDGE_UNLOCK(sc);
if (PFIL_HOOKED(&V_inet_pfil_hook) if (PFIL_HOOKED_OUT(V_inet_pfil_head)
#ifdef INET6 #ifdef INET6
|| PFIL_HOOKED(&V_inet6_pfil_hook) || PFIL_HOOKED_OUT(V_inet6_pfil_head)
#endif #endif
) { ) {
if (bridge_pfil(&m, ifp, dst_if, PFIL_OUT) != 0) if (bridge_pfil(&m, ifp, dst_if, PFIL_OUT) != 0)
@ -2409,7 +2409,7 @@ bridge_input(struct ifnet *ifp, struct mbuf *m)
#ifdef INET6 #ifdef INET6
# define OR_PFIL_HOOKED_INET6 \ # define OR_PFIL_HOOKED_INET6 \
|| PFIL_HOOKED(&V_inet6_pfil_hook) || PFIL_HOOKED_IN(V_inet6_pfil_head)
#else #else
# define OR_PFIL_HOOKED_INET6 # define OR_PFIL_HOOKED_INET6
#endif #endif
@ -2427,7 +2427,7 @@ bridge_input(struct ifnet *ifp, struct mbuf *m)
if_inc_counter(iface, IFCOUNTER_IBYTES, m->m_pkthdr.len); \ if_inc_counter(iface, IFCOUNTER_IBYTES, m->m_pkthdr.len); \
/* Filter on the physical interface. */ \ /* Filter on the physical interface. */ \
if (V_pfil_local_phys && \ if (V_pfil_local_phys && \
(PFIL_HOOKED(&V_inet_pfil_hook) \ (PFIL_HOOKED_IN(V_inet_pfil_head) \
OR_PFIL_HOOKED_INET6)) { \ OR_PFIL_HOOKED_INET6)) { \
if (bridge_pfil(&m, NULL, ifp, \ if (bridge_pfil(&m, NULL, ifp, \
PFIL_IN) != 0 || m == NULL) { \ 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 */ /* 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 #ifdef INET6
|| PFIL_HOOKED(&V_inet6_pfil_hook) || PFIL_HOOKED_OUT(V_inet6_pfil_head)
#endif #endif
)) { )) {
if (bridge_pfil(&m, sc->sc_ifp, NULL, PFIL_OUT) != 0) 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 * pointer so we do not redundantly filter on the bridge for
* each interface we broadcast on. * each interface we broadcast on.
*/ */
if (runfilt && (PFIL_HOOKED(&V_inet_pfil_hook) if (runfilt && (PFIL_HOOKED_OUT(V_inet_pfil_head)
#ifdef INET6 #ifdef INET6
|| PFIL_HOOKED(&V_inet6_pfil_hook) || PFIL_HOOKED_OUT(V_inet6_pfil_head)
#endif #endif
)) { )) {
if (used == 0) { if (used == 0) {
@ -3101,6 +3101,7 @@ bridge_pfil(struct mbuf **mp, struct ifnet *bifp, struct ifnet *ifp, int dir)
struct ip *ip; struct ip *ip;
struct llc llc1; struct llc llc1;
u_int16_t ether_type; u_int16_t ether_type;
pfil_return_t rv;
snap = 0; snap = 0;
error = -1; /* Default error if not error == 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 */ /* Run the packet through pfil before stripping link headers */
if (PFIL_HOOKED(&V_link_pfil_hook) && V_pfil_ipfw != 0 && if (PFIL_HOOKED_OUT(V_link_pfil_head) && V_pfil_ipfw != 0 &&
dir == PFIL_OUT && ifp != NULL) { dir == PFIL_OUT && ifp != NULL) {
switch (pfil_run_hooks(V_link_pfil_head, mp, ifp, dir, NULL)) {
error = pfil_run_hooks(&V_link_pfil_hook, mp, ifp, dir, 0, case PFIL_DROPPED:
NULL); return (EPERM);
case PFIL_CONSUMED:
if (*mp == NULL || error != 0) /* packet consumed by filter */ return (0);
return (error); }
} }
/* Strip off the Ethernet header and keep a copy. */ /* 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 * Run the packet through pfil
*/ */
rv = PFIL_PASS;
switch (ether_type) { switch (ether_type) {
case ETHERTYPE_IP: case ETHERTYPE_IP:
/* /*
@ -3226,25 +3228,19 @@ bridge_pfil(struct mbuf **mp, struct ifnet *bifp, struct ifnet *ifp, int dir)
* Keep the order: * Keep the order:
* in_if -> bridge_if -> out_if * in_if -> bridge_if -> out_if
*/ */
if (V_pfil_bridge && dir == PFIL_OUT && bifp != NULL) if (V_pfil_bridge && dir == PFIL_OUT && bifp != NULL && (rv =
error = pfil_run_hooks(&V_inet_pfil_hook, mp, bifp, pfil_run_hooks(V_inet_pfil_head, mp, bifp, dir, NULL)) !=
dir, 0, NULL); PFIL_PASS)
if (*mp == NULL || error != 0) /* filter may consume */
break; break;
if (V_pfil_member && ifp != NULL) if (V_pfil_member && ifp != NULL && (rv =
error = pfil_run_hooks(&V_inet_pfil_hook, mp, ifp, pfil_run_hooks(V_inet_pfil_head, mp, ifp, dir, NULL)) !=
dir, 0, NULL); PFIL_PASS)
if (*mp == NULL || error != 0) /* filter may consume */
break; break;
if (V_pfil_bridge && dir == PFIL_IN && bifp != NULL) if (V_pfil_bridge && dir == PFIL_IN && bifp != NULL && (rv =
error = pfil_run_hooks(&V_inet_pfil_hook, mp, bifp, pfil_run_hooks(V_inet_pfil_head, mp, bifp, dir, NULL)) !=
dir, 0, NULL); PFIL_PASS)
if (*mp == NULL || error != 0) /* filter may consume */
break; break;
/* check if we need to fragment the packet */ /* 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; break;
#ifdef INET6 #ifdef INET6
case ETHERTYPE_IPV6: case ETHERTYPE_IPV6:
if (V_pfil_bridge && dir == PFIL_OUT && bifp != NULL) if (V_pfil_bridge && dir == PFIL_OUT && bifp != NULL && (rv =
error = pfil_run_hooks(&V_inet6_pfil_hook, mp, bifp, pfil_run_hooks(V_inet6_pfil_head, mp, bifp, dir, NULL)) !=
dir, 0, NULL); PFIL_PASS)
if (*mp == NULL || error != 0) /* filter may consume */
break; break;
if (V_pfil_member && ifp != NULL) if (V_pfil_member && ifp != NULL && (rv =
error = pfil_run_hooks(&V_inet6_pfil_hook, mp, ifp, pfil_run_hooks(V_inet6_pfil_head, mp, ifp, dir, NULL)) !=
dir, 0, NULL); PFIL_PASS)
if (*mp == NULL || error != 0) /* filter may consume */
break; break;
if (V_pfil_bridge && dir == PFIL_IN && bifp != NULL) if (V_pfil_bridge && dir == PFIL_IN && bifp != NULL && (rv =
error = pfil_run_hooks(&V_inet6_pfil_hook, mp, bifp, pfil_run_hooks(V_inet6_pfil_head, mp, bifp, dir, NULL)) !=
dir, 0, NULL); PFIL_PASS)
break;
break; break;
#endif #endif
default:
error = 0;
break;
} }
if (*mp == NULL) switch (rv) {
return (error); case PFIL_CONSUMED:
if (error != 0) return (0);
goto bad; case PFIL_DROPPED:
return (EPERM);
default:
break;
}
error = -1; error = -1;

View File

@ -285,24 +285,24 @@ enc_hhook(int32_t hhook_type, int32_t hhook_id, void *udata, void *ctx_data,
switch (hhook_id) { switch (hhook_id) {
#ifdef INET #ifdef INET
case AF_INET: case AF_INET:
ph = &V_inet_pfil_hook; ph = V_inet_pfil_head;
break; break;
#endif #endif
#ifdef INET6 #ifdef INET6
case AF_INET6: case AF_INET6:
ph = &V_inet6_pfil_hook; ph = V_inet6_pfil_head;
break; break;
#endif #endif
default: default:
ph = NULL; 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); return (0);
/* Make a packet looks like it was received on enc(4) */ /* Make a packet looks like it was received on enc(4) */
rcvif = (*ctx->mp)->m_pkthdr.rcvif; rcvif = (*ctx->mp)->m_pkthdr.rcvif;
(*ctx->mp)->m_pkthdr.rcvif = ifp; (*ctx->mp)->m_pkthdr.rcvif = ifp;
if (pfil_run_hooks(ph, ctx->mp, ifp, pdir, 0, ctx->inp) != 0 || if (pfil_run_hooks(ph, ctx->mp, ifp, pdir, ctx->inp) != PFIL_PASS) {
*ctx->mp == NULL) {
*ctx->mp = NULL; /* consumed by filter */ *ctx->mp = NULL; /* consumed by filter */
return (EACCES); return (EACCES);
} }

View File

@ -90,7 +90,7 @@ CTASSERT(sizeof (struct ether_header) == ETHER_ADDR_LEN * 2 + 2);
CTASSERT(sizeof (struct ether_addr) == ETHER_ADDR_LEN); CTASSERT(sizeof (struct ether_addr) == ETHER_ADDR_LEN);
#endif #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) */ /* netgraph node hooks for ng_ether(4) */
void (*ng_ether_input_p)(struct ifnet *ifp, struct mbuf **mp); 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 int
ether_output_frame(struct ifnet *ifp, struct mbuf *m) ether_output_frame(struct ifnet *ifp, struct mbuf *m)
{ {
int error;
uint8_t pcp; uint8_t pcp;
pcp = ifp->if_pcp; pcp = ifp->if_pcp;
@ -465,15 +464,14 @@ ether_output_frame(struct ifnet *ifp, struct mbuf *m)
!ether_set_pcp(&m, ifp, pcp)) !ether_set_pcp(&m, ifp, pcp))
return (0); return (0);
if (PFIL_HOOKED(&V_link_pfil_hook)) { if (PFIL_HOOKED_OUT(V_link_pfil_head))
error = pfil_run_hooks(&V_link_pfil_hook, &m, ifp, switch (pfil_run_hooks(V_link_pfil_head, &m, ifp, PFIL_OUT,
PFIL_OUT, 0, NULL); NULL)) {
if (error != 0) case PFIL_DROPPED:
return (EACCES); return (EACCES);
case PFIL_CONSUMED:
if (m == NULL)
return (0); return (0);
} }
#ifdef EXPERIMENTAL #ifdef EXPERIMENTAL
#if defined(INET6) && defined(INET) #if defined(INET6) && defined(INET)
@ -737,14 +735,14 @@ SYSINIT(ether, SI_SUB_INIT_IF, SI_ORDER_ANY, ether_init, NULL);
static void static void
vnet_ether_init(__unused void *arg) 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 #ifdef VIMAGE
netisr_register_vnet(&ether_nh); netisr_register_vnet(&ether_nh);
#endif #endif
@ -756,11 +754,8 @@ VNET_SYSINIT(vnet_ether_init, SI_SUB_PROTO_IF, SI_ORDER_ANY,
static void static void
vnet_ether_pfil_destroy(__unused void *arg) vnet_ether_pfil_destroy(__unused void *arg)
{ {
int i;
if ((i = pfil_head_unregister(&V_link_pfil_hook)) != 0) pfil_head_unregister(V_link_pfil_head);
printf("%s: WARNING: unable to unregister pfil link hook, "
"error %d\n", __func__, i);
} }
VNET_SYSUNINIT(vnet_ether_pfil_uninit, SI_SUB_PROTO_PFIL, SI_ORDER_ANY, VNET_SYSUNINIT(vnet_ether_pfil_uninit, SI_SUB_PROTO_PFIL, SI_ORDER_ANY,
vnet_ether_pfil_destroy, NULL); 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__)); KASSERT(ifp != NULL, ("%s: NULL interface pointer", __func__));
/* Do not grab PROMISC frames in case we are re-entered. */ /* Do not grab PROMISC frames in case we are re-entered. */
if (PFIL_HOOKED(&V_link_pfil_hook) && !(m->m_flags & M_PROMISC)) { if (PFIL_HOOKED_IN(V_link_pfil_head) && !(m->m_flags & M_PROMISC)) {
i = pfil_run_hooks(&V_link_pfil_hook, &m, ifp, PFIL_IN, 0, i = pfil_run_hooks(V_link_pfil_head, &m, ifp, PFIL_IN, NULL);
NULL);
if (i != 0 || m == NULL) if (i != 0 || m == NULL)
return; return;
} }

View File

@ -95,8 +95,9 @@ CK_STAILQ_HEAD(ifmultihead, ifmultiaddr);
CK_STAILQ_HEAD(ifgrouphead, ifg_group); CK_STAILQ_HEAD(ifgrouphead, ifg_group);
#ifdef _KERNEL #ifdef _KERNEL
VNET_DECLARE(struct pfil_head, link_pfil_hook); /* packet filter hooks */ VNET_DECLARE(struct pfil_head *, link_pfil_head);
#define V_link_pfil_hook VNET(link_pfil_hook) #define V_link_pfil_head VNET(link_pfil_head)
#define PFIL_ETHER_NAME "ethernet"
#define HHOOK_IPSEC_INET 0 #define HHOOK_IPSEC_INET 0
#define HHOOK_IPSEC_INET6 1 #define HHOOK_IPSEC_INET6 1

View File

@ -4,6 +4,7 @@
/*- /*-
* SPDX-License-Identifier: BSD-3-Clause * SPDX-License-Identifier: BSD-3-Clause
* *
* Copyright (c) 2019 Gleb Smirnoff <glebius@FreeBSD.org>
* Copyright (c) 1996 Matthew R. Green * Copyright (c) 1996 Matthew R. Green
* All rights reserved. * All rights reserved.
* *
@ -32,15 +33,15 @@
*/ */
#include <sys/param.h> #include <sys/param.h>
#include <sys/conf.h>
#include <sys/kernel.h> #include <sys/kernel.h>
#include <sys/epoch.h>
#include <sys/errno.h> #include <sys/errno.h>
#include <sys/lock.h> #include <sys/lock.h>
#include <sys/malloc.h> #include <sys/malloc.h>
#include <sys/rmlock.h>
#include <sys/socket.h> #include <sys/socket.h>
#include <sys/socketvar.h> #include <sys/socketvar.h>
#include <sys/systm.h> #include <sys/systm.h>
#include <sys/condvar.h>
#include <sys/lock.h> #include <sys/lock.h>
#include <sys/mutex.h> #include <sys/mutex.h>
#include <sys/proc.h> #include <sys/proc.h>
@ -50,180 +51,167 @@
#include <net/if_var.h> #include <net/if_var.h>
#include <net/pfil.h> #include <net/pfil.h>
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", static int pfil_ioctl(struct cdev *, u_long, caddr_t, int, struct thread *);
MTX_DEF); 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 struct mtx pfil_lock;
static int pfil_chain_add(pfil_chain_t *, struct packet_filter_hook *, int); MTX_SYSINIT(pfil_mtxinit, &pfil_lock, "pfil(9) lock", MTX_DEF);
static int pfil_chain_remove(pfil_chain_t *, void *, void *); #define PFIL_LOCK() mtx_lock(&pfil_lock)
static int pfil_add_hook_priv(void *, void *, int, struct pfil_head *, bool); #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); 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) #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) \ LIST_HEAD(pfilhookhead, pfil_hook);
rm_init_flags(l, "PFil " t " rmlock", RM_RECURSE) VNET_DEFINE_STATIC(struct pfilhookhead, pfil_hook_list) =
#define PFIL_LOCK_DESTROY_REAL(l) \ LIST_HEAD_INITIALIZER(pfil_hook_list);
rm_destroy(l) #define V_pfil_hook_list VNET(pfil_hook_list)
#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)
#define PFIL_TRY_RLOCK(p, t) rm_try_rlock((p)->ph_plock, (t)) static struct pfil_link *pfil_link_remove(pfil_chain_t *, pfil_hook_t );
#define PFIL_RLOCK(p, t) rm_rlock((p)->ph_plock, (t)) static void pfil_link_free(epoch_context_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)
#define PFIL_HEADLIST_LOCK() mtx_lock(&pfil_global_lock) static __noinline int
#define PFIL_HEADLIST_UNLOCK() mtx_unlock(&pfil_global_lock) 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. * pfil_run_hooks() runs the specified packet filter hook chain.
*/ */
int int
pfil_run_hooks(struct pfil_head *ph, struct mbuf **mp, struct ifnet *ifp, pfil_run_hooks(struct pfil_head *head, pfil_packet_t p, struct ifnet *ifp,
int dir, int flags, struct inpcb *inp) int flags, struct inpcb *inp)
{ {
struct rm_priotracker rmpt; struct epoch_tracker et;
struct packet_filter_hook *pfh; pfil_chain_t *pch;
struct mbuf *m = *mp; struct pfil_link *link;
int rv = 0; pfil_return_t rv, rvi;
PFIL_RLOCK(ph, &rmpt); if (PFIL_DIR(flags) == PFIL_IN)
KASSERT(ph->ph_nhooks >= 0, ("Pfil hook count dropped < 0")); pch = &head->head_in;
for (pfh = pfil_chain_get(dir, ph); pfh != NULL; else if (__predict_true(PFIL_DIR(flags) == PFIL_OUT))
pfh = TAILQ_NEXT(pfh, pfil_chain)) { pch = &head->head_out;
if (pfh->pfil_func_flags != NULL) { else
rv = (*pfh->pfil_func_flags)(pfh->pfil_arg, &m, ifp, panic("%s: bogus flags %d", __func__, flags);
dir, flags, inp);
if (rv != 0 || m == NULL) rv = PFIL_PASS;
break; PFIL_EPOCH_ENTER(et);
} CK_STAILQ_FOREACH(link, pch, link_chain) {
if (pfh->pfil_func != NULL) { if ((flags & PFIL_MEMPTR) && !(link->link_flags & PFIL_MEMPTR))
rv = (*pfh->pfil_func)(pfh->pfil_arg, &m, ifp, dir, rvi = pfil_fake_mbuf(link->link_func, p.mem, ifp,
inp); flags, link->link_ruleset, inp);
if (rv != 0 || m == NULL) else
break; 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); PFIL_EPOCH_EXIT(et);
*mp = m; return (rvi);
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_head_register() registers a pfil_head with the packet filter hook * pfil_head_register() registers a pfil_head with the packet filter hook
* mechanism. * mechanism.
*/ */
int pfil_head_t
pfil_head_register(struct pfil_head *ph) pfil_head_register(struct pfil_head_args *pa)
{ {
struct pfil_head *lph; struct pfil_head *head, *list;
PFIL_HEADLIST_LOCK(); MPASS(pa->pa_version == PFIL_VERSION);
LIST_FOREACH(lph, &V_pfil_head_list, ph_list) {
if (ph->ph_type == lph->ph_type && head = malloc(sizeof(struct pfil_head), M_PFIL, M_WAITOK);
ph->ph_un.phu_val == lph->ph_un.phu_val) {
PFIL_HEADLIST_UNLOCK(); head->head_nhooksin = head->head_nhooksout = 0;
return (EEXIST); head->head_flags = pa->pa_flags;
head->head_type = pa->pa_type;
head->head_name = pa->pa_headname;
CK_STAILQ_INIT(&head->head_in);
CK_STAILQ_INIT(&head->head_out);
PFIL_LOCK();
LIST_FOREACH(list, &V_pfil_head_list, head_list)
if (strcmp(pa->pa_headname, list->head_name) == 0) {
printf("pfil: duplicate head \"%s\"\n",
pa->pa_headname);
} }
} LIST_INSERT_HEAD(&V_pfil_head_list, head, head_list);
PFIL_LOCK_INIT(ph); PFIL_UNLOCK();
ph->ph_nhooks = 0;
TAILQ_INIT(&ph->ph_in); return (head);
TAILQ_INIT(&ph->ph_out);
LIST_INSERT_HEAD(&V_pfil_head_list, ph, ph_list);
PFIL_HEADLIST_UNLOCK();
return (0);
} }
/* /*
@ -231,245 +219,441 @@ pfil_head_register(struct pfil_head *ph)
* mechanism. The producer of the hook promises that all outstanding * mechanism. The producer of the hook promises that all outstanding
* invocations of the hook have completed before it unregisters the hook. * invocations of the hook have completed before it unregisters the hook.
*/ */
int void
pfil_head_unregister(struct pfil_head *ph) pfil_head_unregister(pfil_head_t ph)
{ {
struct packet_filter_hook *pfh, *pfnext; struct pfil_link *link, *next;
PFIL_HEADLIST_LOCK(); PFIL_LOCK();
LIST_REMOVE(ph, ph_list); LIST_REMOVE(ph, head_list);
PFIL_HEADLIST_UNLOCK();
TAILQ_FOREACH_SAFE(pfh, &ph->ph_in, pfil_chain, pfnext) CK_STAILQ_FOREACH_SAFE(link, &ph->head_in, link_chain, next) {
free(pfh, M_IFADDR); link->link_hook->hook_links--;
TAILQ_FOREACH_SAFE(pfh, &ph->ph_out, pfil_chain, pfnext) free(link, M_PFIL);
free(pfh, M_IFADDR); }
PFIL_LOCK_DESTROY(ph); CK_STAILQ_FOREACH_SAFE(link, &ph->head_out, link_chain, next) {
return (0); link->link_hook->hook_links--;
free(link, M_PFIL);
}
PFIL_UNLOCK();
} }
/* pfil_hook_t
* pfil_head_get() returns the pfil_head for a given key/dlt. pfil_add_hook(struct pfil_hook_args *pa)
*/
struct pfil_head *
pfil_head_get(int type, u_long val)
{ {
struct pfil_head *ph; struct pfil_hook *hook, *list;
PFIL_HEADLIST_LOCK(); MPASS(pa->pa_version == PFIL_VERSION);
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);
}
/* hook = malloc(sizeof(struct pfil_hook), M_PFIL, M_WAITOK | M_ZERO);
* pfil_add_hook_flags() adds a function to the packet filter hook. the hook->hook_func = pa->pa_func;
* flags are: hook->hook_ruleset = pa->pa_ruleset;
* PFIL_IN call me on incoming packets hook->hook_flags = pa->pa_flags;
* PFIL_OUT call me on outgoing packets hook->hook_type = pa->pa_type;
* PFIL_ALL call me on all of the above hook->hook_modname = pa->pa_modname;
* PFIL_WAITOK OK to call malloc with M_WAITOK. hook->hook_rulname = pa->pa_rulname;
*/
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));
}
/* PFIL_LOCK();
* pfil_add_hook() adds a function to the packet filter hook. the LIST_FOREACH(list, &V_pfil_hook_list, hook_list)
* flags are: if (strcmp(pa->pa_modname, list->hook_modname) == 0 &&
* PFIL_IN call me on incoming packets strcmp(pa->pa_rulname, list->hook_rulname) == 0) {
* PFIL_OUT call me on outgoing packets printf("pfil: duplicate hook \"%s:%s\"\n",
* PFIL_ALL call me on all of the above pa->pa_modname, pa->pa_rulname);
* PFIL_WAITOK OK to call malloc with M_WAITOK. }
*/ LIST_INSERT_HEAD(&V_pfil_hook_list, hook, hook_list);
int PFIL_UNLOCK();
pfil_add_hook(pfil_func_t func, void *arg, int flags, struct pfil_head *ph)
{ return (hook);
return (pfil_add_hook_priv(func, arg, flags, ph, false));
} }
static int static int
pfil_add_hook_priv(void *func, void *arg, int flags, pfil_unlink(struct pfil_link_args *pa, pfil_head_t head, pfil_hook_t hook)
struct pfil_head *ph, bool hasflags)
{ {
struct packet_filter_hook *pfh1 = NULL; struct pfil_link *in, *out;
struct packet_filter_hook *pfh2 = NULL;
int err;
if (flags & PFIL_IN) { PFIL_LOCK_ASSERT();
pfh1 = (struct packet_filter_hook *)malloc(sizeof(*pfh1),
M_IFADDR, (flags & PFIL_WAITOK) ? M_WAITOK : M_NOWAIT); if (pa->pa_flags & PFIL_IN) {
if (pfh1 == NULL) { in = pfil_link_remove(&head->head_in, hook);
err = ENOMEM; if (in != NULL) {
goto error; head->head_nhooksin--;
hook->hook_links--;
} }
} } else
if (flags & PFIL_OUT) { in = NULL;
pfh2 = (struct packet_filter_hook *)malloc(sizeof(*pfh1), if (pa->pa_flags & PFIL_OUT) {
M_IFADDR, (flags & PFIL_WAITOK) ? M_WAITOK : M_NOWAIT); out = pfil_link_remove(&head->head_out, hook);
if (pfh2 == NULL) { if (out != NULL) {
err = ENOMEM; head->head_nhooksout--;
goto error; hook->hook_links--;
} }
} } else
PFIL_WLOCK(ph); out = NULL;
if (flags & PFIL_IN) { PFIL_UNLOCK();
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);
}
/* if (in != NULL)
* pfil_remove_hook_flags removes a specific function from the packet filter hook epoch_call(PFIL_EPOCH, &in->link_epoch_ctx, pfil_link_free);
* chain. if (out != NULL)
*/ epoch_call(PFIL_EPOCH, &out->link_epoch_ctx, pfil_link_free);
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 && out == NULL)
* pfil_remove_hook removes a specific function from the packet filter hook return (ENOENT);
* 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);
else 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); 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. * Internal: Remove a pfil hook from a hook chain.
*/ */
static int static struct pfil_link *
pfil_chain_remove(pfil_chain_t *chain, void *func, void *arg) 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) PFIL_LOCK_ASSERT();
if ((pfh->pfil_func == func || pfh->pfil_func_flags == func) &&
pfh->pfil_arg == arg) { CK_STAILQ_FOREACH(link, chain, link_chain)
TAILQ_REMOVE(chain, pfh, pfil_chain); if (link->link_hook == hook) {
free(pfh, M_IFADDR); CK_STAILQ_REMOVE(chain, link, pfil_link, link_chain);
return (0); return (link);
} }
return (ENOENT);
return (NULL);
} }
/*
* Stuff that must be initialized for every instance (including the first of
* course).
*/
static void 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); make_dev_args_init(&args);
PFIL_LOCK_INIT_REAL(&V_pfil_lock, "shared"); 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 * Make sure the pfil bits are first before any possible subsystem which
* might piggyback on the SI_SUB_PROTO_PFIL. * might piggyback on the SI_SUB_PROTO_PFIL.
*/ */
VNET_SYSINIT(vnet_pfil_init, SI_SUB_PROTO_PFIL, SI_ORDER_FIRST, SYSINIT(pfil_init, SI_SUB_PROTO_PFIL, SI_ORDER_FIRST, pfil_init, NULL);
vnet_pfil_init, NULL);
/* /*
* Closing up shop. These are done in REVERSE ORDER. Not called on reboot. * User control interface.
*
* VNET_SYSUNINIT is called for each exiting vnet as it exits.
*/ */
VNET_SYSUNINIT(vnet_pfil_uninit, SI_SUB_PROTO_PFIL, SI_ORDER_FIRST, static int pfilioc_listheads(struct pfilioc_list *);
vnet_pfil_uninit, NULL); 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));
}

View File

@ -4,6 +4,7 @@
/*- /*-
* SPDX-License-Identifier: BSD-3-Clause * SPDX-License-Identifier: BSD-3-Clause
* *
* Copyright (c) 2019 Gleb Smirnoff <glebius@FreeBSD.org>
* Copyright (c) 1996 Matthew R. Green * Copyright (c) 1996 Matthew R. Green
* All rights reserved. * All rights reserved.
* *
@ -34,94 +35,158 @@
#ifndef _NET_PFIL_H_ #ifndef _NET_PFIL_H_
#define _NET_PFIL_H_ #define _NET_PFIL_H_
#include <sys/systm.h> #include <sys/ioccom.h>
#include <sys/queue.h>
#include <sys/_lock.h>
#include <sys/_mutex.h>
#include <sys/lock.h>
#include <sys/rmlock.h>
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 mbuf;
struct ifnet; struct ifnet;
struct inpcb; struct inpcb;
typedef int (*pfil_func_t)(void *, struct mbuf **, struct ifnet *, int, typedef union {
struct inpcb *); struct mbuf **m;
typedef int (*pfil_func_flags_t)(void *, struct mbuf **, struct ifnet *, void *mem;
int, int, struct inpcb *); } 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 * Give us a chance to modify pfil_xxx_args structures in future.
* possibly intercept the packet. Multiple filter hooks are chained
* together and after each other in the specified order.
*/ */
struct packet_filter_hook { #define PFIL_VERSION 1
TAILQ_ENTRY(packet_filter_hook) pfil_chain;
pfil_func_t pfil_func;
pfil_func_flags_t pfil_func_flags;
void *pfil_arg;
};
#define PFIL_IN 0x00000001 /* Argument structure used by packet filters to register themselves. */
#define PFIL_OUT 0x00000002 struct pfil_hook_args {
#define PFIL_WAITOK 0x00000004 int pa_version;
#define PFIL_FWD 0x00000008 int pa_flags;
#define PFIL_ALL (PFIL_IN|PFIL_OUT) enum pfil_types pa_type;
pfil_func_t pa_func;
typedef TAILQ_HEAD(pfil_chain, packet_filter_hook) pfil_chain_t; void *pa_ruleset;
const char *pa_modname;
#define PFIL_TYPE_AF 1 /* key is AF_* type */ const char *pa_rulname;
#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;
}; };
/* Public functions for pfil hook management by packet filters. */ /* Public functions for pfil hook management by packet filters. */
struct pfil_head *pfil_head_get(int, u_long); pfil_hook_t pfil_add_hook(struct pfil_hook_args *);
int pfil_add_hook_flags(pfil_func_flags_t, void *, int, struct pfil_head *); void pfil_remove_hook(pfil_hook_t);
int pfil_add_hook(pfil_func_t, void *, int, struct pfil_head *);
int pfil_remove_hook_flags(pfil_func_flags_t, void *, int, struct pfil_head *);
int pfil_remove_hook(pfil_func_t, void *, int, struct pfil_head *);
#define PFIL_HOOKED(p) ((p)->ph_nhooks > 0)
/* Public functions to run the packet inspection by protocols. */ /* Argument structure used by ioctl() and packet filters to set filters. */
int pfil_run_hooks(struct pfil_head *, struct mbuf **, struct ifnet *, int, struct pfil_link_args {
int, struct inpcb *inp); 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. */ /* Public function to configure filter chains. Used by ioctl() and filters. */
int pfil_head_register(struct pfil_head *); int pfil_link(struct pfil_link_args *);
int pfil_head_unregister(struct pfil_head *);
/* Public pfil locking functions for self managed locks by packet filters. */ /* Argument structure used by inspection points to register themselves. */
int pfil_try_rlock(struct pfil_head *, struct rm_priotracker *); struct pfil_head_args {
void pfil_rlock(struct pfil_head *, struct rm_priotracker *); int pa_version;
void pfil_runlock(struct pfil_head *, struct rm_priotracker *); int pa_flags;
void pfil_wlock(struct pfil_head *); enum pfil_types pa_type;
void pfil_wunlock(struct pfil_head *); const char *pa_headname;
int pfil_wowned(struct pfil_head *ph); };
/* 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_ */ #endif /* _NET_PFIL_H_ */

View File

@ -90,11 +90,11 @@ __FBSDID("$FreeBSD$");
#include <sys/socket.h> #include <sys/socket.h>
#include <sys/sysctl.h> #include <sys/sysctl.h>
#include <net/pfil.h>
#include <net/if.h> #include <net/if.h>
#include <net/if_types.h> #include <net/if_types.h>
#include <net/if_var.h> #include <net/if_var.h>
#include <net/if_dl.h> #include <net/if_dl.h>
#include <net/pfil.h>
#include <net/route.h> #include <net/route.h>
#include <net/vnet.h> #include <net/vnet.h>
@ -228,12 +228,11 @@ ip_tryforward(struct mbuf *m)
/* /*
* Run through list of ipfilter hooks for input packets * 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; goto passin;
if (pfil_run_hooks( if (pfil_run_hooks(V_inet_pfil_head, &m, m->m_pkthdr.rcvif, PFIL_IN,
&V_inet_pfil_hook, &m, m->m_pkthdr.rcvif, PFIL_IN, 0, NULL) || NULL) != PFIL_PASS)
m == NULL)
goto drop; goto drop;
M_ASSERTVALID(m); M_ASSERTVALID(m);
@ -321,13 +320,12 @@ ip_tryforward(struct mbuf *m)
/* /*
* Step 5: outgoing firewall packet processing * Step 5: outgoing firewall packet processing
*/ */
if (!PFIL_HOOKED(&V_inet_pfil_hook)) if (!PFIL_HOOKED_OUT(V_inet_pfil_head))
goto passout; goto passout;
if (pfil_run_hooks(&V_inet_pfil_hook, &m, nh.nh_ifp, PFIL_OUT, PFIL_FWD, if (pfil_run_hooks(V_inet_pfil_head, &m, nh.nh_ifp,
NULL) || m == NULL) { PFIL_OUT | PFIL_FWD, NULL) != PFIL_PASS)
goto drop; goto drop;
}
M_ASSERTVALID(m); M_ASSERTVALID(m);
M_ASSERTPKTHDR(m); M_ASSERTPKTHDR(m);

View File

@ -57,11 +57,11 @@ __FBSDID("$FreeBSD$");
#include <sys/syslog.h> #include <sys/syslog.h>
#include <sys/sysctl.h> #include <sys/sysctl.h>
#include <net/pfil.h>
#include <net/if.h> #include <net/if.h>
#include <net/if_types.h> #include <net/if_types.h>
#include <net/if_var.h> #include <net/if_var.h>
#include <net/if_dl.h> #include <net/if_dl.h>
#include <net/pfil.h>
#include <net/route.h> #include <net/route.h>
#include <net/netisr.h> #include <net/netisr.h>
#include <net/rss_config.h> #include <net/rss_config.h>
@ -134,7 +134,7 @@ SYSCTL_INT(_net_inet_ip, OID_AUTO, check_interface, CTLFLAG_VNET | CTLFLAG_RW,
&VNET_NAME(ip_checkinterface), 0, &VNET_NAME(ip_checkinterface), 0,
"Verify packet arrives on correct interface"); "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 = { static struct netisr_handler ip_nh = {
.nh_name = "ip", .nh_name = "ip",
@ -301,6 +301,7 @@ SYSCTL_PROC(_net_inet_ip, IPCTL_INTRDQDROPS, intr_direct_queue_drops,
void void
ip_init(void) ip_init(void)
{ {
struct pfil_head_args args;
struct protosw *pr; struct protosw *pr;
int i; int i;
@ -311,11 +312,11 @@ ip_init(void)
ipreass_init(); ipreass_init();
/* Initialize packet filter hooks. */ /* Initialize packet filter hooks. */
V_inet_pfil_hook.ph_type = PFIL_TYPE_AF; args.pa_version = PFIL_VERSION;
V_inet_pfil_hook.ph_af = AF_INET; args.pa_flags = PFIL_IN | PFIL_OUT;
if ((i = pfil_head_register(&V_inet_pfil_hook)) != 0) args.pa_type = PFIL_TYPE_IP4;
printf("%s: WARNING: unable to register pfil hook, " args.pa_headname = PFIL_INET_NAME;
"error %d\n", __func__, i); V_inet_pfil_head = pfil_head_register(&args);
if (hhook_head_register(HHOOK_TYPE_IPSEC_IN, AF_INET, if (hhook_head_register(HHOOK_TYPE_IPSEC_IN, AF_INET,
&V_ipsec_hhh_in[HHOOK_IPSEC_INET], &V_ipsec_hhh_in[HHOOK_IPSEC_INET],
@ -377,10 +378,7 @@ ip_destroy(void *unused __unused)
#endif #endif
netisr_unregister_vnet(&ip_nh); netisr_unregister_vnet(&ip_nh);
if ((error = pfil_head_unregister(&V_inet_pfil_hook)) != 0) pfil_head_unregister(V_inet_pfil_head);
printf("%s: WARNING: unable to unregister pfil hook, "
"error %d\n", __func__, error);
error = hhook_head_deregister(V_ipsec_hhh_in[HHOOK_IPSEC_INET]); error = hhook_head_deregister(V_ipsec_hhh_in[HHOOK_IPSEC_INET]);
if (error != 0) { if (error != 0) {
printf("%s: WARNING: unable to deregister input helper hook " 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. */ /* 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; goto passin;
odst = ip->ip_dst; 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; return;
if (m == NULL) /* consumed by filter */ if (m == NULL) /* consumed by filter */
return; return;

View File

@ -121,11 +121,16 @@ ip_output_pfil(struct mbuf **mp, struct ifnet *ifp, struct inpcb *inp,
/* Run through list of hooks for output packets. */ /* Run through list of hooks for output packets. */
odst.s_addr = ip->ip_dst.s_addr; odst.s_addr = ip->ip_dst.s_addr;
*error = pfil_run_hooks(&V_inet_pfil_hook, mp, ifp, PFIL_OUT, 0, inp); switch (pfil_run_hooks(V_inet_pfil_head, mp, ifp, PFIL_OUT, inp)) {
m = *mp; case PFIL_DROPPED:
if ((*error) != 0 || m == NULL) *error = EPERM;
/* FALLTHROUGH */
case PFIL_CONSUMED:
return 1; /* Finished */ return 1; /* Finished */
case PFIL_PASS:
*error = 0;
}
m = *mp;
ip = mtod(m, struct ip *); ip = mtod(m, struct ip *);
/* See if destination IP address was changed by packet filter. */ /* 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 */ #endif /* IPSEC */
/* Jump over all PFIL processing if hooks are not active. */ /* 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)) { switch (ip_output_pfil(&m, ifp, inp, dst, &fibnum, &error)) {
case 1: /* Finished */ case 1: /* Finished */
goto done; goto done;

View File

@ -241,8 +241,9 @@ extern int (*ip_rsvp_vif)(struct socket *, struct sockopt *);
extern void (*ip_rsvp_force_done)(struct socket *); extern void (*ip_rsvp_force_done)(struct socket *);
extern int (*rsvp_input_p)(struct mbuf **, int *, int); extern int (*rsvp_input_p)(struct mbuf **, int *, int);
VNET_DECLARE(struct pfil_head, inet_pfil_hook); /* packet filter hooks */ VNET_DECLARE(struct pfil_head *, inet_pfil_head);
#define V_inet_pfil_hook VNET(inet_pfil_hook) #define V_inet_pfil_head VNET(inet_pfil_head)
#define PFIL_INET_NAME "inet"
void in_delayed_cksum(struct mbuf *m); void in_delayed_cksum(struct mbuf *m);

View File

@ -94,10 +94,12 @@ __FBSDID("$FreeBSD$");
#include <netinet/in_systm.h> #include <netinet/in_systm.h>
#include <netinet/in_var.h> #include <netinet/in_var.h>
#include <netinet/ip.h> #include <netinet/ip.h>
#include <netinet/ip_var.h>
#include <netinet/tcp_var.h> #include <netinet/tcp_var.h>
#ifdef SIFTR_IPV6 #ifdef SIFTR_IPV6
#include <netinet/ip6.h> #include <netinet/ip6.h>
#include <netinet/ip6_var.h>
#include <netinet6/in6_pcb.h> #include <netinet6/in6_pcb.h>
#endif /* SIFTR_IPV6 */ #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 * 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. * that support it so that they won't sleep, otherwise you get a panic.
*/ */
static int static pfil_return_t
siftr_chkpkt(void *arg, struct mbuf **m, struct ifnet *ifp, int dir, siftr_chkpkt(struct mbuf **m, struct ifnet *ifp, int flags,
struct inpcb *inp) void *ruleset __unused, struct inpcb *inp)
{ {
struct pkt_node *pn; struct pkt_node *pn;
struct ip *ip; struct ip *ip;
@ -841,9 +843,10 @@ siftr_chkpkt(void *arg, struct mbuf **m, struct ifnet *ifp, int dir,
struct tcpcb *tp; struct tcpcb *tp;
struct siftr_stats *ss; struct siftr_stats *ss;
unsigned int ip_hl; unsigned int ip_hl;
int inp_locally_locked; int inp_locally_locked, dir;
inp_locally_locked = 0; inp_locally_locked = 0;
dir = PFIL_DIR(flags);
ss = DPCPU_PTR(ss); ss = DPCPU_PTR(ss);
/* /*
@ -1007,15 +1010,13 @@ siftr_chkpkt(void *arg, struct mbuf **m, struct ifnet *ifp, int dir,
INP_RUNLOCK(inp); INP_RUNLOCK(inp);
ret: ret:
/* Returning 0 ensures pfil will not discard the pkt */ return (PFIL_PASS);
return (0);
} }
#ifdef SIFTR_IPV6 #ifdef SIFTR_IPV6
static int static int
siftr_chkpkt6(void *arg, struct mbuf **m, struct ifnet *ifp, int dir, siftr_chkpkt6(struct mbuf **m, struct ifnet *ifp, int flags, struct inpcb *inp)
struct inpcb *inp)
{ {
struct pkt_node *pn; struct pkt_node *pn;
struct ip6_hdr *ip6; struct ip6_hdr *ip6;
@ -1023,9 +1024,10 @@ siftr_chkpkt6(void *arg, struct mbuf **m, struct ifnet *ifp, int dir,
struct tcpcb *tp; struct tcpcb *tp;
struct siftr_stats *ss; struct siftr_stats *ss;
unsigned int ip6_hl; unsigned int ip6_hl;
int inp_locally_locked; int inp_locally_locked, dir;
inp_locally_locked = 0; inp_locally_locked = 0;
dir = PFIL_DIR(flags);
ss = DPCPU_PTR(ss); ss = DPCPU_PTR(ss);
/* /*
@ -1138,37 +1140,53 @@ siftr_chkpkt6(void *arg, struct mbuf **m, struct ifnet *ifp, int dir,
} }
#endif /* #ifdef SIFTR_IPV6 */ #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 static int
siftr_pfil(int action) siftr_pfil(int action)
{ {
struct pfil_head *pfh_inet; struct pfil_hook_args pha;
#ifdef SIFTR_IPV6 struct pfil_link_args pla;
struct pfil_head *pfh_inet6;
#endif 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_ITERATOR_DECL(vnet_iter);
VNET_LIST_RLOCK(); VNET_LIST_RLOCK();
VNET_FOREACH(vnet_iter) { VNET_FOREACH(vnet_iter) {
CURVNET_SET(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) { if (action == HOOK) {
pfil_add_hook(siftr_chkpkt, NULL, pha.pa_func = siftr_chkpkt;
PFIL_IN | PFIL_OUT | PFIL_WAITOK, pfh_inet); 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 #ifdef SIFTR_IPV6
pfil_add_hook(siftr_chkpkt6, NULL, pha.pa_func = siftr_chkpkt6;
PFIL_IN | PFIL_OUT | PFIL_WAITOK, pfh_inet6); 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 #endif
} else if (action == UNHOOK) { } else if (action == UNHOOK) {
pfil_remove_hook(siftr_chkpkt, NULL, pfil_remove_hook(V_siftr_inet_hook);
PFIL_IN | PFIL_OUT | PFIL_WAITOK, pfh_inet);
#ifdef SIFTR_IPV6 #ifdef SIFTR_IPV6
pfil_remove_hook(siftr_chkpkt6, NULL, pfil_remove_hook(V_siftr_inet6_hook);
PFIL_IN | PFIL_OUT | PFIL_WAITOK, pfh_inet6);
#endif #endif
} }
CURVNET_RESTORE(); CURVNET_RESTORE();

View File

@ -156,10 +156,10 @@ ip6_tryforward(struct mbuf *m)
/* /*
* Incoming packet firewall processing. * Incoming packet firewall processing.
*/ */
if (!PFIL_HOOKED(&V_inet6_pfil_hook)) if (!PFIL_HOOKED_IN(V_inet6_pfil_head))
goto passin; goto passin;
if (pfil_run_hooks(&V_inet6_pfil_hook, &m, rcvif, PFIL_IN, 0, if (pfil_run_hooks(V_inet6_pfil_head, &m, rcvif, PFIL_IN, NULL) !=
NULL) != 0 || m == NULL) PFIL_PASS)
goto dropin; goto dropin;
/* /*
* If packet filter sets the M_FASTFWD_OURS flag, this means * 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); in6_ifstat_inc(rcvif, ifs6_in_noroute);
goto dropin; 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) { if (m->m_pkthdr.len > nh.nh_mtu) {
in6_ifstat_inc(nh.nh_ifp, ifs6_in_toobig); in6_ifstat_inc(nh.nh_ifp, ifs6_in_toobig);
icmp6_error(m, ICMP6_PACKET_TOO_BIG, 0, nh.nh_mtu); icmp6_error(m, ICMP6_PACKET_TOO_BIG, 0, nh.nh_mtu);
@ -208,8 +208,8 @@ ip6_tryforward(struct mbuf *m)
/* /*
* Outgoing packet firewall processing. * Outgoing packet firewall processing.
*/ */
if (pfil_run_hooks(&V_inet6_pfil_hook, &m, nh.nh_ifp, PFIL_OUT, if (pfil_run_hooks(V_inet6_pfil_head, &m, nh.nh_ifp, PFIL_OUT |
PFIL_FWD, NULL) != 0 || m == NULL) PFIL_FWD, NULL) != PFIL_PASS)
goto dropout; goto dropout;
/* /*

View File

@ -320,15 +320,14 @@ ip6_forward(struct mbuf *m, int srcrt)
in6_clearscope(&ip6->ip6_dst); in6_clearscope(&ip6->ip6_dst);
/* Jump over all PFIL processing if hooks are not active. */ /* 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; goto pass;
odst = ip6->ip6_dst; odst = ip6->ip6_dst;
/* Run through list of hooks for forwarded packets. */ /* Run through list of hooks for forwarded packets. */
error = pfil_run_hooks(&V_inet6_pfil_hook, &m, rt->rt_ifp, PFIL_OUT, if (pfil_run_hooks(V_inet6_pfil_head, &m, rt->rt_ifp, PFIL_OUT |
PFIL_FWD, NULL); PFIL_FWD, NULL) != PFIL_PASS)
if (error != 0 || m == NULL) goto freecopy;
goto freecopy; /* consumed by filter */
ip6 = mtod(m, struct ip6_hdr *); ip6 = mtod(m, struct ip6_hdr *);
/* See if destination IP address was changed by packet filter. */ /* See if destination IP address was changed by packet filter. */

View File

@ -191,7 +191,7 @@ SYSCTL_PROC(_net_inet6_ip6, IPV6CTL_INTRDQMAXLEN, intr_direct_queue_maxlen,
#endif #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_DEFINE(struct ip6stat, ip6stat);
VNET_PCPUSTAT_SYSINIT(ip6stat); VNET_PCPUSTAT_SYSINIT(ip6stat);
@ -214,6 +214,7 @@ static struct mbuf *ip6_pullexthdr(struct mbuf *, size_t, int);
void void
ip6_init(void) ip6_init(void)
{ {
struct pfil_head_args args;
struct protosw *pr; struct protosw *pr;
int i; int i;
@ -227,11 +228,11 @@ ip6_init(void)
&V_in6_ifaddrhmask); &V_in6_ifaddrhmask);
/* Initialize packet filter hooks. */ /* Initialize packet filter hooks. */
V_inet6_pfil_hook.ph_type = PFIL_TYPE_AF; args.pa_version = PFIL_VERSION;
V_inet6_pfil_hook.ph_af = AF_INET6; args.pa_flags = PFIL_IN | PFIL_OUT;
if ((i = pfil_head_register(&V_inet6_pfil_hook)) != 0) args.pa_type = PFIL_TYPE_IP6;
printf("%s: WARNING: unable to register pfil hook, " args.pa_headname = PFIL_INET6_NAME;
"error %d\n", __func__, i); V_inet6_pfil_head = pfil_head_register(&args);
if (hhook_head_register(HHOOK_TYPE_IPSEC_IN, AF_INET6, if (hhook_head_register(HHOOK_TYPE_IPSEC_IN, AF_INET6,
&V_ipsec_hhh_in[HHOOK_IPSEC_INET6], &V_ipsec_hhh_in[HHOOK_IPSEC_INET6],
@ -359,9 +360,7 @@ ip6_destroy(void *unused __unused)
#endif #endif
netisr_unregister_vnet(&ip6_nh); netisr_unregister_vnet(&ip6_nh);
if ((error = pfil_head_unregister(&V_inet6_pfil_hook)) != 0) pfil_head_unregister(V_inet6_pfil_head);
printf("%s: WARNING: unable to unregister pfil hook, "
"error %d\n", __func__, error);
error = hhook_head_deregister(V_ipsec_hhh_in[HHOOK_IPSEC_INET6]); error = hhook_head_deregister(V_ipsec_hhh_in[HHOOK_IPSEC_INET6]);
if (error != 0) { if (error != 0) {
printf("%s: WARNING: unable to deregister input helper hook " 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. */ /* 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; goto passin;
odst = ip6->ip6_dst; odst = ip6->ip6_dst;
if (pfil_run_hooks(&V_inet6_pfil_hook, &m, if (pfil_run_hooks(V_inet6_pfil_head, &m, m->m_pkthdr.rcvif, PFIL_IN,
m->m_pkthdr.rcvif, PFIL_IN, 0, NULL)) NULL) != PFIL_PASS)
return;
if (m == NULL) /* consumed by filter */
return; return;
ip6 = mtod(m, struct ip6_hdr *); ip6 = mtod(m, struct ip6_hdr *);
srcrt = !IN6_ARE_ADDR_EQUAL(&odst, &ip6->ip6_dst); srcrt = !IN6_ARE_ADDR_EQUAL(&odst, &ip6->ip6_dst);

View File

@ -792,16 +792,21 @@ ip6_output(struct mbuf *m0, struct ip6_pktopts *opt,
} }
/* Jump over all PFIL processing if hooks are not active. */ /* 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; goto passout;
odst = ip6->ip6_dst; odst = ip6->ip6_dst;
/* Run through list of hooks for output packets. */ /* Run through list of hooks for output packets. */
error = pfil_run_hooks(&V_inet6_pfil_hook, &m, ifp, PFIL_OUT, 0, inp); switch (pfil_run_hooks(V_inet6_pfil_head, &m, ifp, PFIL_OUT, inp)) {
if (error != 0 || m == NULL) case PFIL_PASS:
ip6 = mtod(m, struct ip6_hdr *);
break;
case PFIL_DROPPED:
error = EPERM;
/* FALLTHROUGH */
case PFIL_CONSUMED:
goto done; goto done;
/* adjust pointer */ }
ip6 = mtod(m, struct ip6_hdr *);
needfiblookup = 0; needfiblookup = 0;
/* See if destination IP address was changed by packet filter. */ /* See if destination IP address was changed by packet filter. */

View File

@ -346,8 +346,10 @@ VNET_DECLARE(int, ip6_use_defzone); /* Whether to use the default scope
* zone when unspecified */ * zone when unspecified */
#define V_ip6_use_defzone VNET(ip6_use_defzone) #define V_ip6_use_defzone VNET(ip6_use_defzone)
VNET_DECLARE (struct pfil_head, inet6_pfil_hook); /* packet filter hooks */ VNET_DECLARE(struct pfil_head *, inet6_pfil_head);
#define V_inet6_pfil_hook VNET(inet6_pfil_hook) #define V_inet6_pfil_head VNET(inet6_pfil_head)
#define PFIL_INET6_NAME "inet6"
#ifdef IPSTEALTH #ifdef IPSTEALTH
VNET_DECLARE(int, ip6stealth); VNET_DECLARE(int, ip6stealth);
#define V_ip6stealth VNET(ip6stealth) #define V_ip6stealth VNET(ip6stealth)

View File

@ -38,9 +38,9 @@ __FBSDID("$FreeBSD$");
#include <sys/socket.h> #include <sys/socket.h>
#include <sys/socketvar.h> #include <sys/socketvar.h>
#include <sys/queue.h> #include <sys/queue.h>
#include <net/pfil.h>
#include <net/if.h> /* ip_fw.h requires IFNAMSIZ */ #include <net/if.h> /* ip_fw.h requires IFNAMSIZ */
#include <net/pfil.h>
#include <netinet/in.h> #include <netinet/in.h>
#include <netinet/ip_var.h> /* struct ipfw_rule_ref */ #include <netinet/ip_var.h> /* struct ipfw_rule_ref */
#include <netinet/ip_fw.h> #include <netinet/ip_fw.h>

View File

@ -48,6 +48,7 @@ __FBSDID("$FreeBSD$");
#include <sys/sysctl.h> #include <sys/sysctl.h>
#include <net/if.h> #include <net/if.h>
#include <net/if_var.h>
#include <net/route.h> #include <net/route.h>
#include <net/ethernet.h> #include <net/ethernet.h>
#include <net/pfil.h> #include <net/pfil.h>
@ -85,10 +86,6 @@ int ipfw_chg_hook(SYSCTL_HANDLER_ARGS);
/* Forward declarations. */ /* Forward declarations. */
static int ipfw_divert(struct mbuf **, int, struct ipfw_rule_ref *, int); 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 #ifdef SYSCTL_NODE
@ -120,16 +117,17 @@ SYSEND
* dummynet, divert, netgraph or other modules. * dummynet, divert, netgraph or other modules.
* The packet may be consumed. * The packet may be consumed.
*/ */
int static pfil_return_t
ipfw_check_packet(void *arg, struct mbuf **m0, struct ifnet *ifp, int dir, ipfw_check_packet(struct mbuf **m0, struct ifnet *ifp, int dir,
struct inpcb *inp) void *ruleset __unused, struct inpcb *inp)
{ {
struct ip_fw_args args; struct ip_fw_args args;
struct m_tag *tag; struct m_tag *tag;
int ipfw, ret; pfil_return_t ret;
int ipfw;
/* convert dir to IPFW values */ /* convert dir to IPFW values */
dir = (dir == PFIL_IN) ? DIR_IN : DIR_OUT; dir = (dir & PFIL_IN) ? DIR_IN : DIR_OUT;
args.flags = 0; args.flags = 0;
again: 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", KASSERT(*m0 != NULL || ipfw == IP_FW_DENY, ("%s: m0 is NULL",
__func__)); __func__));
/* breaking out of the switch means drop */ ret = PFIL_PASS;
switch (ipfw) { switch (ipfw) {
case IP_FW_PASS: case IP_FW_PASS:
/* next_hop may be set by ipfw_chk */ /* next_hop may be set by ipfw_chk */
if ((args.flags & (IPFW_ARGS_NH4 | IPFW_ARGS_NH4PTR | if ((args.flags & (IPFW_ARGS_NH4 | IPFW_ARGS_NH4PTR |
IPFW_ARGS_NH6 | IPFW_ARGS_NH6PTR)) == 0) { IPFW_ARGS_NH6 | IPFW_ARGS_NH6PTR)) == 0)
ret = 0;
break; break;
}
#if (!defined(INET6) && !defined(INET)) #if (!defined(INET6) && !defined(INET))
ret = EACCES; ret = PFIL_DROPPED;
#else #else
{ {
void *psa; 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, tag = m_tag_get(PACKET_TAG_IPFORWARD, len,
M_NOWAIT); M_NOWAIT);
if (tag == NULL) { if (tag == NULL) {
ret = EACCES; ret = PFIL_DROPPED;
break; /* i.e. drop */ break;
} }
} }
if ((args.flags & IPFW_ARGS_NH6) == 0) 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. * comparisons.
*/ */
if (sa6_embedscope(sa6, V_ip6_use_defzone) != 0) { if (sa6_embedscope(sa6, V_ip6_use_defzone) != 0) {
ret = EACCES; ret = PFIL_DROPPED;
break; break;
} }
if (in6_localip(&sa6->sin6_addr)) if (in6_localip(&sa6->sin6_addr))
@ -250,20 +246,23 @@ ipfw_check_packet(void *arg, struct mbuf **m0, struct ifnet *ifp, int dir,
break; break;
case IP_FW_DENY: case IP_FW_DENY:
ret = EACCES; ret = PFIL_DROPPED;
break; /* i.e. drop */ break;
case IP_FW_DUMMYNET: case IP_FW_DUMMYNET:
ret = EACCES; if (ip_dn_io_ptr == NULL) {
if (ip_dn_io_ptr == NULL) ret = PFIL_DROPPED;
break; /* i.e. drop */ break;
}
MPASS(args.flags & IPFW_ARGS_REF); MPASS(args.flags & IPFW_ARGS_REF);
if (mtod(*m0, struct ip *)->ip_v == 4) 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) else if (mtod(*m0, struct ip *)->ip_v == 6)
ret = ip_dn_io_ptr(m0, dir | PROTO_IPV6, &args); (void )ip_dn_io_ptr(m0, dir | PROTO_IPV6, &args);
else else {
break; /* drop it */ ret = PFIL_DROPPED;
break;
}
/* /*
* XXX should read the return value. * XXX should read the return value.
* dummynet normally eats the packet and sets *m0=NULL * 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) if (*m0 != NULL)
goto again; goto again;
ret = PFIL_CONSUMED;
break; break;
case IP_FW_TEE: case IP_FW_TEE:
case IP_FW_DIVERT: case IP_FW_DIVERT:
if (ip_divert_ptr == NULL) { if (ip_divert_ptr == NULL) {
ret = EACCES; ret = PFIL_DROPPED;
break; /* i.e. drop */ break;
} }
MPASS(args.flags & IPFW_ARGS_REF); 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); (ipfw == IP_FW_TEE) ? 1 : 0);
/* continue processing for the original packet (tee). */ /* continue processing for the original packet (tee). */
if (*m0) if (*m0)
goto again; goto again;
ret = PFIL_CONSUMED;
break; break;
case IP_FW_NGTEE: case IP_FW_NGTEE:
case IP_FW_NETGRAPH: case IP_FW_NETGRAPH:
if (ng_ipfw_input_p == NULL) { if (ng_ipfw_input_p == NULL) {
ret = EACCES; ret = PFIL_DROPPED;
break; /* i.e. drop */ break;
} }
MPASS(args.flags & IPFW_ARGS_REF); 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); (ipfw == IP_FW_NGTEE) ? 1 : 0);
if (ipfw == IP_FW_NGTEE) /* ignore errors for NGTEE */ if (ipfw == IP_FW_NGTEE) /* ignore errors for NGTEE */
goto again; /* continue with packet */ goto again; /* continue with packet */
ret = PFIL_CONSUMED;
break; break;
case IP_FW_NAT: case IP_FW_NAT:
/* honor one-pass in case of successful nat */ /* honor one-pass in case of successful nat */
if (V_fw_one_pass) { if (V_fw_one_pass)
ret = 0;
break; break;
}
goto again; goto again;
case IP_FW_REASS: 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__)); KASSERT(0, ("%s: unknown retval", __func__));
} }
if (ret != 0) { if (ret != PFIL_PASS) {
if (*m0) if (*m0)
FREE_PKT(*m0); FREE_PKT(*m0);
*m0 = NULL; *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). * ipfw processing for ethernet packets (in and out).
*/ */
int static pfil_return_t
ipfw_check_frame(void *arg, struct mbuf **m0, struct ifnet *ifp, int dir, ipfw_check_frame(struct mbuf **m0, struct ifnet *ifp, int dir,
struct inpcb *inp) void *ruleset __unused, struct inpcb *inp)
{ {
struct ip_fw_args args; struct ip_fw_args args;
struct ether_header save_eh; struct ether_header save_eh;
struct ether_header *eh; struct ether_header *eh;
struct m_tag *mtag; struct m_tag *mtag;
struct mbuf *m; struct mbuf *m;
int i, ret; pfil_return_t ret;
int i;
args.flags = IPFW_ARGS_ETHER; args.flags = IPFW_ARGS_ETHER;
again: 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 */ m_adj(m, ETHER_HDR_LEN); /* strip ethernet header */
args.m = m; /* the packet we are looking at */ 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.eh = &save_eh; /* MAC header for bridged/MAC packets */
args.inp = inp; /* used by ipfw uid/gid/jail rules */ args.inp = inp; /* used by ipfw uid/gid/jail rules */
i = ipfw_chk(&args); i = ipfw_chk(&args);
@ -388,46 +389,46 @@ ipfw_check_frame(void *arg, struct mbuf **m0, struct ifnet *ifp, int dir,
} }
*m0 = m; *m0 = m;
ret = 0; ret = PFIL_PASS;
/* Check result of ipfw_chk() */ /* Check result of ipfw_chk() */
switch (i) { switch (i) {
case IP_FW_PASS: case IP_FW_PASS:
break; break;
case IP_FW_DENY: case IP_FW_DENY:
ret = EACCES; ret = PFIL_DROPPED;
break; /* i.e. drop */ break;
case IP_FW_DUMMYNET: case IP_FW_DUMMYNET:
ret = EACCES; if (ip_dn_io_ptr == NULL) {
ret = PFIL_DROPPED;
if (ip_dn_io_ptr == NULL) break;
break; /* i.e. drop */ }
*m0 = NULL; *m0 = NULL;
dir = (dir == PFIL_IN) ? DIR_IN : DIR_OUT; dir = (dir & PFIL_IN) ? DIR_IN : DIR_OUT;
MPASS(args.flags & IPFW_ARGS_REF); MPASS(args.flags & IPFW_ARGS_REF);
ip_dn_io_ptr(&m, dir | PROTO_LAYER2, &args); ip_dn_io_ptr(&m, dir | PROTO_LAYER2, &args);
return 0; return (PFIL_CONSUMED);
case IP_FW_NGTEE: case IP_FW_NGTEE:
case IP_FW_NETGRAPH: case IP_FW_NETGRAPH:
if (ng_ipfw_input_p == NULL) { if (ng_ipfw_input_p == NULL) {
ret = EACCES; ret = PFIL_DROPPED;
break; /* i.e. drop */ break;
} }
MPASS(args.flags & IPFW_ARGS_REF); 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); &args, (i == IP_FW_NGTEE) ? 1 : 0);
if (i == IP_FW_NGTEE) /* ignore errors for NGTEE */ if (i == IP_FW_NGTEE) /* ignore errors for NGTEE */
goto again; /* continue with packet */ goto again; /* continue with packet */
ret = PFIL_CONSUMED;
break; break;
default: default:
KASSERT(0, ("%s: unknown retval", __func__)); KASSERT(0, ("%s: unknown retval", __func__));
} }
if (ret != 0) { if (ret != PFIL_PASS) {
if (*m0) if (*m0)
FREE_PKT(*m0); FREE_PKT(*m0);
*m0 = NULL; *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 * 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 static int
ipfw_hook(int onoff, int pf) ipfw_hook(int onoff, int pf)
{ {
struct pfil_head *pfh; struct pfil_hook_args pha;
pfil_func_t hook_func; struct pfil_link_args pla;
pfil_hook_t *h;
pfh = pfil_head_get(PFIL_TYPE_AF, pf); pha.pa_version = PFIL_VERSION;
if (pfh == NULL) pha.pa_flags = PFIL_IN | PFIL_OUT;
return ENOENT; 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) switch (pf) {
(hook_func, NULL, PFIL_IN | PFIL_OUT | PFIL_WAITOK, pfh); 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; return 0;
} }

View File

@ -169,16 +169,16 @@ static void pf_tbladdr_copyout(struct pf_addr_wrap *);
* Wrapper functions for pfil(9) hooks * Wrapper functions for pfil(9) hooks
*/ */
#ifdef INET #ifdef INET
static int pf_check_in(void *arg, struct mbuf **m, struct ifnet *ifp, static pfil_return_t pf_check_in(struct mbuf **m, struct ifnet *ifp,
int dir, int flags, struct inpcb *inp); int flags, void *ruleset __unused, struct inpcb *inp);
static int pf_check_out(void *arg, struct mbuf **m, struct ifnet *ifp, static pfil_return_t pf_check_out(struct mbuf **m, struct ifnet *ifp,
int dir, int flags, struct inpcb *inp); int flags, void *ruleset __unused, struct inpcb *inp);
#endif #endif
#ifdef INET6 #ifdef INET6
static int pf_check6_in(void *arg, struct mbuf **m, struct ifnet *ifp, static pfil_return_t pf_check6_in(struct mbuf **m, struct ifnet *ifp,
int dir, int flags, struct inpcb *inp); int flags, void *ruleset __unused, struct inpcb *inp);
static int pf_check6_out(void *arg, struct mbuf **m, struct ifnet *ifp, static pfil_return_t pf_check6_out(struct mbuf **m, struct ifnet *ifp,
int dir, int flags, struct inpcb *inp); int flags, void *ruleset __unused, struct inpcb *inp);
#endif #endif
static int hook_pf(void); static int hook_pf(void);
@ -4003,9 +4003,9 @@ shutdown_pf(void)
} }
#ifdef INET #ifdef INET
static int static pfil_return_t
pf_check_in(void *arg, struct mbuf **m, struct ifnet *ifp, int dir, int flags, pf_check_in(struct mbuf **m, struct ifnet *ifp, int flags,
struct inpcb *inp) void *ruleset __unused, struct inpcb *inp)
{ {
int chk; int chk;
@ -4015,14 +4015,12 @@ pf_check_in(void *arg, struct mbuf **m, struct ifnet *ifp, int dir, int flags,
*m = NULL; *m = NULL;
} }
if (chk != PF_PASS) return (chk == PF_PASS ? PFIL_PASS : PFIL_DROPPED);
return (EACCES);
return (0);
} }
static int static pfil_return_t
pf_check_out(void *arg, struct mbuf **m, struct ifnet *ifp, int dir, int flags, pf_check_out(struct mbuf **m, struct ifnet *ifp, int flags,
struct inpcb *inp) void *ruleset __unused, struct inpcb *inp)
{ {
int chk; int chk;
@ -4032,16 +4030,14 @@ pf_check_out(void *arg, struct mbuf **m, struct ifnet *ifp, int dir, int flags,
*m = NULL; *m = NULL;
} }
if (chk != PF_PASS) return (chk == PF_PASS ? PFIL_PASS : PFIL_DROPPED);
return (EACCES);
return (0);
} }
#endif #endif
#ifdef INET6 #ifdef INET6
static int static pfil_return_t
pf_check6_in(void *arg, struct mbuf **m, struct ifnet *ifp, int dir, int flags, pf_check6_in(struct mbuf **m, struct ifnet *ifp, int flags,
struct inpcb *inp) void *ruleset __unused, struct inpcb *inp)
{ {
int chk; 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_freem(*m);
*m = NULL; *m = NULL;
} }
if (chk != PF_PASS)
return (EACCES); return (chk == PF_PASS ? PFIL_PASS : PFIL_DROPPED);
return (0);
} }
static int static pfil_return_t
pf_check6_out(void *arg, struct mbuf **m, struct ifnet *ifp, int dir, int flags, pf_check6_out(struct mbuf **m, struct ifnet *ifp, int flags,
struct inpcb *inp) void *ruleset __unused, struct inpcb *inp)
{ {
int chk; 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_freem(*m);
*m = NULL; *m = NULL;
} }
if (chk != PF_PASS)
return (EACCES); return (chk == PF_PASS ? PFIL_PASS : PFIL_DROPPED);
return (0);
} }
#endif /* INET6 */ #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 static int
hook_pf(void) hook_pf(void)
{ {
#ifdef INET struct pfil_hook_args pha;
struct pfil_head *pfh_inet; struct pfil_link_args pla;
#endif
#ifdef INET6
struct pfil_head *pfh_inet6;
#endif
if (V_pf_pfil_hooked) if (V_pf_pfil_hooked)
return (0); return (0);
pha.pa_version = PFIL_VERSION;
pha.pa_modname = "pf";
pha.pa_ruleset = NULL;
pla.pa_version = PFIL_VERSION;
#ifdef INET #ifdef INET
pfh_inet = pfil_head_get(PFIL_TYPE_AF, AF_INET); pha.pa_type = PFIL_TYPE_IP4;
if (pfh_inet == NULL) pha.pa_func = pf_check_in;
return (ESRCH); /* XXX */ pha.pa_flags = PFIL_IN;
pfil_add_hook_flags(pf_check_in, NULL, PFIL_IN | PFIL_WAITOK, pfh_inet); pha.pa_rulname = "default-in";
pfil_add_hook_flags(pf_check_out, NULL, PFIL_OUT | PFIL_WAITOK, pfh_inet); 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 #endif
#ifdef INET6 #ifdef INET6
pfh_inet6 = pfil_head_get(PFIL_TYPE_AF, AF_INET6); pha.pa_type = PFIL_TYPE_IP6;
if (pfh_inet6 == NULL) { pha.pa_func = pf_check6_in;
#ifdef INET pha.pa_flags = PFIL_IN;
pfil_remove_hook_flags(pf_check_in, NULL, PFIL_IN | PFIL_WAITOK, pha.pa_rulname = "default-in6";
pfh_inet); V_pf_ip6_in_hook = pfil_add_hook(&pha);
pfil_remove_hook_flags(pf_check_out, NULL, PFIL_OUT | PFIL_WAITOK, pla.pa_flags = PFIL_IN | PFIL_HEADPTR | PFIL_HOOKPTR;
pfh_inet); pla.pa_head = V_inet6_pfil_head;
#endif pla.pa_hook = V_pf_ip6_in_hook;
return (ESRCH); /* XXX */ (void)pfil_link(&pla);
} pha.pa_func = pf_check6_out;
pfil_add_hook_flags(pf_check6_in, NULL, PFIL_IN | PFIL_WAITOK, pfh_inet6); pha.pa_rulname = "default-out6";
pfil_add_hook_flags(pf_check6_out, NULL, PFIL_OUT | PFIL_WAITOK, pfh_inet6); 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 #endif
V_pf_pfil_hooked = 1; V_pf_pfil_hooked = 1;
@ -4123,33 +4149,17 @@ hook_pf(void)
static int static int
dehook_pf(void) 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) if (V_pf_pfil_hooked == 0)
return (0); return (0);
#ifdef INET #ifdef INET
pfh_inet = pfil_head_get(PFIL_TYPE_AF, AF_INET); pfil_remove_hook(V_pf_ip4_in_hook);
if (pfh_inet == NULL) pfil_remove_hook(V_pf_ip4_out_hook);
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);
#endif #endif
#ifdef INET6 #ifdef INET6
pfh_inet6 = pfil_head_get(PFIL_TYPE_AF, AF_INET6); pfil_remove_hook(V_pf_ip6_in_hook);
if (pfh_inet6 == NULL) pfil_remove_hook(V_pf_ip6_out_hook);
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);
#endif #endif
V_pf_pfil_hooked = 0; V_pf_pfil_hooked = 0;