2 /* $NetBSD: pfil.c,v 1.20 2001/11/12 23:49:46 lukem Exp $ */
5 * SPDX-License-Identifier: BSD-3-Clause
7 * Copyright (c) 2019 Gleb Smirnoff <glebius@FreeBSD.org>
8 * Copyright (c) 1996 Matthew R. Green
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 * 3. The name of the author may not be used to endorse or promote products
20 * derived from this software without specific prior written permission.
22 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
23 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
24 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
25 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
26 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
27 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
29 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
30 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
31 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
35 #include <sys/param.h>
37 #include <sys/kernel.h>
38 #include <sys/epoch.h>
39 #include <sys/errno.h>
41 #include <sys/malloc.h>
42 #include <sys/socket.h>
43 #include <sys/socketvar.h>
44 #include <sys/systm.h>
46 #include <sys/mutex.h>
48 #include <sys/queue.h>
49 #include <sys/ucred.h>
53 #include <net/if_var.h>
56 static MALLOC_DEFINE(M_PFIL, "pfil", "pfil(9) packet filter hooks");
58 static int pfil_ioctl(struct cdev *, u_long, caddr_t, int, struct thread *);
59 static struct cdevsw pfil_cdevsw = {
60 .d_ioctl = pfil_ioctl,
62 .d_version = D_VERSION,
64 static struct cdev *pfil_dev;
66 static struct mtx pfil_lock;
67 MTX_SYSINIT(pfil_mtxinit, &pfil_lock, "pfil(9) lock", MTX_DEF);
68 #define PFIL_LOCK() mtx_lock(&pfil_lock)
69 #define PFIL_UNLOCK() mtx_unlock(&pfil_lock)
70 #define PFIL_LOCK_ASSERT() mtx_assert(&pfil_lock, MA_OWNED)
72 #define PFIL_EPOCH net_epoch_preempt
73 #define PFIL_EPOCH_ENTER(et) epoch_enter_preempt(net_epoch_preempt, &(et))
74 #define PFIL_EPOCH_EXIT(et) epoch_exit_preempt(net_epoch_preempt, &(et))
77 pfil_func_t hook_func;
81 enum pfil_types hook_type;
82 const char *hook_modname;
83 const char *hook_rulname;
84 LIST_ENTRY(pfil_hook) hook_list;
88 CK_STAILQ_ENTRY(pfil_link) link_chain;
89 pfil_func_t link_func;
92 struct pfil_hook *link_hook;
93 struct epoch_context link_epoch_ctx;
96 typedef CK_STAILQ_HEAD(pfil_chain, pfil_link) pfil_chain_t;
100 pfil_chain_t head_in;
101 pfil_chain_t head_out;
103 enum pfil_types head_type;
104 LIST_ENTRY(pfil_head) head_list;
105 const char *head_name;
108 LIST_HEAD(pfilheadhead, pfil_head);
109 VNET_DEFINE_STATIC(struct pfilheadhead, pfil_head_list) =
110 LIST_HEAD_INITIALIZER(pfil_head_list);
111 #define V_pfil_head_list VNET(pfil_head_list)
113 LIST_HEAD(pfilhookhead, pfil_hook);
114 VNET_DEFINE_STATIC(struct pfilhookhead, pfil_hook_list) =
115 LIST_HEAD_INITIALIZER(pfil_hook_list);
116 #define V_pfil_hook_list VNET(pfil_hook_list)
118 static struct pfil_link *pfil_link_remove(pfil_chain_t *, pfil_hook_t );
119 static void pfil_link_free(epoch_context_t);
122 pfil_realloc(pfil_packet_t *p, int flags, struct ifnet *ifp)
126 MPASS(flags & PFIL_MEMPTR);
128 if ((m = m_devget(p->mem, PFIL_LENGTH(flags), 0, ifp, NULL)) == NULL)
130 *p = pfil_packet_align(*p);
136 static __noinline int
137 pfil_fake_mbuf(pfil_func_t func, pfil_packet_t *p, struct ifnet *ifp, int flags,
138 void *ruleset, struct inpcb *inp)
143 (void)m_init(&m, M_NOWAIT, MT_DATA, M_NOFREE | M_PKTHDR);
144 m_extadd(&m, p->mem, PFIL_LENGTH(flags), NULL, NULL, NULL, 0,
146 m.m_len = m.m_pkthdr.len = PFIL_LENGTH(flags);
148 flags &= ~(PFIL_MEMPTR | PFIL_LENMASK);
150 rv = func(&mp, ifp, flags, ruleset, inp);
151 if (rv == PFIL_PASS && mp != &m) {
153 * Firewalls that need pfil_fake_mbuf() most likely don't
154 * know they need return PFIL_REALLOCED.
157 *p = pfil_packet_align(*p);
165 * pfil_run_hooks() runs the specified packet filter hook chain.
168 pfil_run_hooks(struct pfil_head *head, pfil_packet_t p, struct ifnet *ifp,
169 int flags, struct inpcb *inp)
171 struct epoch_tracker et;
173 struct pfil_link *link;
175 bool realloc = false;
177 if (PFIL_DIR(flags) == PFIL_IN)
178 pch = &head->head_in;
179 else if (__predict_true(PFIL_DIR(flags) == PFIL_OUT))
180 pch = &head->head_out;
182 panic("%s: bogus flags %d", __func__, flags);
185 PFIL_EPOCH_ENTER(et);
186 CK_STAILQ_FOREACH(link, pch, link_chain) {
187 if ((flags & PFIL_MEMPTR) && !(link->link_flags & PFIL_MEMPTR))
188 rv = pfil_fake_mbuf(link->link_func, &p, ifp, flags,
189 link->link_ruleset, inp);
191 rv = (*link->link_func)(p, ifp, flags,
192 link->link_ruleset, inp);
193 if (rv == PFIL_DROPPED || rv == PFIL_CONSUMED)
195 else if (rv == PFIL_REALLOCED) {
196 flags &= ~(PFIL_MEMPTR | PFIL_LENMASK);
201 if (realloc && rv == PFIL_PASS)
207 * pfil_head_register() registers a pfil_head with the packet filter hook
211 pfil_head_register(struct pfil_head_args *pa)
213 struct pfil_head *head, *list;
215 MPASS(pa->pa_version == PFIL_VERSION);
217 head = malloc(sizeof(struct pfil_head), M_PFIL, M_WAITOK);
219 head->head_nhooksin = head->head_nhooksout = 0;
220 head->head_flags = pa->pa_flags;
221 head->head_type = pa->pa_type;
222 head->head_name = pa->pa_headname;
223 CK_STAILQ_INIT(&head->head_in);
224 CK_STAILQ_INIT(&head->head_out);
227 LIST_FOREACH(list, &V_pfil_head_list, head_list)
228 if (strcmp(pa->pa_headname, list->head_name) == 0) {
229 printf("pfil: duplicate head \"%s\"\n",
232 LIST_INSERT_HEAD(&V_pfil_head_list, head, head_list);
239 * pfil_head_unregister() removes a pfil_head from the packet filter hook
240 * mechanism. The producer of the hook promises that all outstanding
241 * invocations of the hook have completed before it unregisters the hook.
244 pfil_head_unregister(pfil_head_t ph)
246 struct pfil_link *link, *next;
249 LIST_REMOVE(ph, head_list);
251 CK_STAILQ_FOREACH_SAFE(link, &ph->head_in, link_chain, next) {
252 link->link_hook->hook_links--;
255 CK_STAILQ_FOREACH_SAFE(link, &ph->head_out, link_chain, next) {
256 link->link_hook->hook_links--;
263 pfil_add_hook(struct pfil_hook_args *pa)
265 struct pfil_hook *hook, *list;
267 MPASS(pa->pa_version == PFIL_VERSION);
269 hook = malloc(sizeof(struct pfil_hook), M_PFIL, M_WAITOK | M_ZERO);
270 hook->hook_func = pa->pa_func;
271 hook->hook_ruleset = pa->pa_ruleset;
272 hook->hook_flags = pa->pa_flags;
273 hook->hook_type = pa->pa_type;
274 hook->hook_modname = pa->pa_modname;
275 hook->hook_rulname = pa->pa_rulname;
278 LIST_FOREACH(list, &V_pfil_hook_list, hook_list)
279 if (strcmp(pa->pa_modname, list->hook_modname) == 0 &&
280 strcmp(pa->pa_rulname, list->hook_rulname) == 0) {
281 printf("pfil: duplicate hook \"%s:%s\"\n",
282 pa->pa_modname, pa->pa_rulname);
284 LIST_INSERT_HEAD(&V_pfil_hook_list, hook, hook_list);
291 pfil_unlink(struct pfil_link_args *pa, pfil_head_t head, pfil_hook_t hook)
293 struct pfil_link *in, *out;
297 if (pa->pa_flags & PFIL_IN) {
298 in = pfil_link_remove(&head->head_in, hook);
300 head->head_nhooksin--;
305 if (pa->pa_flags & PFIL_OUT) {
306 out = pfil_link_remove(&head->head_out, hook);
308 head->head_nhooksout--;
316 epoch_call(PFIL_EPOCH, &in->link_epoch_ctx, pfil_link_free);
318 epoch_call(PFIL_EPOCH, &out->link_epoch_ctx, pfil_link_free);
320 if (in == NULL && out == NULL)
327 pfil_link(struct pfil_link_args *pa)
329 struct pfil_link *in, *out, *link;
330 struct pfil_head *head;
331 struct pfil_hook *hook;
334 MPASS(pa->pa_version == PFIL_VERSION);
336 if ((pa->pa_flags & (PFIL_IN | PFIL_UNLINK)) == PFIL_IN)
337 in = malloc(sizeof(*in), M_PFIL, M_WAITOK | M_ZERO);
340 if ((pa->pa_flags & (PFIL_OUT | PFIL_UNLINK)) == PFIL_OUT)
341 out = malloc(sizeof(*out), M_PFIL, M_WAITOK | M_ZERO);
346 if (pa->pa_flags & PFIL_HEADPTR)
349 LIST_FOREACH(head, &V_pfil_head_list, head_list)
350 if (strcmp(pa->pa_headname, head->head_name) == 0)
352 if (pa->pa_flags & PFIL_HOOKPTR)
355 LIST_FOREACH(hook, &V_pfil_hook_list, hook_list)
356 if (strcmp(pa->pa_modname, hook->hook_modname) == 0 &&
357 strcmp(pa->pa_rulname, hook->hook_rulname) == 0)
359 if (head == NULL || hook == NULL) {
364 if (pa->pa_flags & PFIL_UNLINK)
365 return (pfil_unlink(pa, head, hook));
367 if (head->head_type != hook->hook_type ||
368 ((hook->hook_flags & pa->pa_flags) & ~head->head_flags)) {
373 if (pa->pa_flags & PFIL_IN)
374 CK_STAILQ_FOREACH(link, &head->head_in, link_chain)
375 if (link->link_hook == hook) {
379 if (pa->pa_flags & PFIL_OUT)
380 CK_STAILQ_FOREACH(link, &head->head_out, link_chain)
381 if (link->link_hook == hook) {
386 if (pa->pa_flags & PFIL_IN) {
387 in->link_hook = hook;
388 in->link_func = hook->hook_func;
389 in->link_flags = hook->hook_flags;
390 in->link_ruleset = hook->hook_ruleset;
391 if (pa->pa_flags & PFIL_APPEND)
392 CK_STAILQ_INSERT_TAIL(&head->head_in, in, link_chain);
394 CK_STAILQ_INSERT_HEAD(&head->head_in, in, link_chain);
396 head->head_nhooksin++;
398 if (pa->pa_flags & PFIL_OUT) {
399 out->link_hook = hook;
400 out->link_func = hook->hook_func;
401 out->link_flags = hook->hook_flags;
402 out->link_ruleset = hook->hook_ruleset;
403 if (pa->pa_flags & PFIL_APPEND)
404 CK_STAILQ_INSERT_HEAD(&head->head_out, out, link_chain);
406 CK_STAILQ_INSERT_TAIL(&head->head_out, out, link_chain);
408 head->head_nhooksout++;
422 pfil_link_free(epoch_context_t ctx)
424 struct pfil_link *link;
426 link = __containerof(ctx, struct pfil_link, link_epoch_ctx);
431 * pfil_remove_hook removes a filter from all filtering points.
434 pfil_remove_hook(pfil_hook_t hook)
436 struct pfil_head *head;
437 struct pfil_link *in, *out;
440 LIST_FOREACH(head, &V_pfil_head_list, head_list) {
442 in = pfil_link_remove(&head->head_in, hook);
444 head->head_nhooksin--;
446 epoch_call(PFIL_EPOCH, &in->link_epoch_ctx,
449 out = pfil_link_remove(&head->head_out, hook);
451 head->head_nhooksout--;
453 epoch_call(PFIL_EPOCH, &out->link_epoch_ctx,
456 if (in != NULL || out != NULL)
457 /* What if some stupid admin put same filter twice? */
460 LIST_REMOVE(hook, hook_list);
462 MPASS(hook->hook_links == 0);
467 * Internal: Remove a pfil hook from a hook chain.
469 static struct pfil_link *
470 pfil_link_remove(pfil_chain_t *chain, pfil_hook_t hook)
472 struct pfil_link *link;
476 CK_STAILQ_FOREACH(link, chain, link_chain)
477 if (link->link_hook == hook) {
478 CK_STAILQ_REMOVE(chain, link, pfil_link, link_chain);
486 pfil_init(const void *unused __unused)
488 struct make_dev_args args;
491 make_dev_args_init(&args);
492 args.mda_flags = MAKEDEV_WAITOK | MAKEDEV_CHECKNAME;
493 args.mda_devsw = &pfil_cdevsw;
494 args.mda_uid = UID_ROOT;
495 args.mda_gid = GID_WHEEL;
496 args.mda_mode = 0600;
497 error = make_dev_s(&args, &pfil_dev, PFILDEV);
498 KASSERT(error == 0, ("%s: failed to create dev: %d", __func__, error));
501 * Make sure the pfil bits are first before any possible subsystem which
502 * might piggyback on the SI_SUB_PROTO_PFIL.
504 SYSINIT(pfil_init, SI_SUB_PROTO_PFIL, SI_ORDER_FIRST, pfil_init, NULL);
507 * User control interface.
509 static int pfilioc_listheads(struct pfilioc_list *);
510 static int pfilioc_listhooks(struct pfilioc_list *);
511 static int pfilioc_link(struct pfilioc_link *);
514 pfil_ioctl(struct cdev *dev, u_long cmd, caddr_t addr, int flags,
519 CURVNET_SET(TD_TO_VNET(td));
522 case PFILIOC_LISTHEADS:
523 error = pfilioc_listheads((struct pfilioc_list *)addr);
525 case PFILIOC_LISTHOOKS:
526 error = pfilioc_listhooks((struct pfilioc_list *)addr);
529 error = pfilioc_link((struct pfilioc_link *)addr);
540 pfilioc_listheads(struct pfilioc_list *req)
542 struct pfil_head *head;
543 struct pfil_link *link;
544 struct pfilioc_head *iohead;
545 struct pfilioc_hook *iohook;
546 u_int nheads, nhooks, hd, hk;
552 LIST_FOREACH(head, &V_pfil_head_list, head_list) {
554 nhooks += head->head_nhooksin + head->head_nhooksout;
558 if (req->pio_nheads < nheads || req->pio_nhooks < nhooks) {
559 req->pio_nheads = nheads;
560 req->pio_nhooks = nhooks;
564 iohead = malloc(sizeof(*iohead) * nheads, M_TEMP, M_WAITOK);
565 iohook = malloc(sizeof(*iohook) * nhooks, M_TEMP, M_WAITOK);
569 LIST_FOREACH(head, &V_pfil_head_list, head_list) {
570 if (hd + 1 > nheads ||
571 hk + head->head_nhooksin + head->head_nhooksout > nhooks) {
572 /* Configuration changed during malloc(). */
573 free(iohead, M_TEMP);
574 free(iohook, M_TEMP);
577 strlcpy(iohead[hd].pio_name, head->head_name,
578 sizeof(iohead[0].pio_name));
579 iohead[hd].pio_nhooksin = head->head_nhooksin;
580 iohead[hd].pio_nhooksout = head->head_nhooksout;
581 iohead[hd].pio_type = head->head_type;
582 CK_STAILQ_FOREACH(link, &head->head_in, link_chain) {
583 strlcpy(iohook[hk].pio_module,
584 link->link_hook->hook_modname,
585 sizeof(iohook[0].pio_module));
586 strlcpy(iohook[hk].pio_ruleset,
587 link->link_hook->hook_rulname,
588 sizeof(iohook[0].pio_ruleset));
591 CK_STAILQ_FOREACH(link, &head->head_out, link_chain) {
592 strlcpy(iohook[hk].pio_module,
593 link->link_hook->hook_modname,
594 sizeof(iohook[0].pio_module));
595 strlcpy(iohook[hk].pio_ruleset,
596 link->link_hook->hook_rulname,
597 sizeof(iohook[0].pio_ruleset));
604 error = copyout(iohead, req->pio_heads,
605 sizeof(*iohead) * min(hd, req->pio_nheads));
607 error = copyout(iohook, req->pio_hooks,
608 sizeof(*iohook) * min(req->pio_nhooks, hk));
610 req->pio_nheads = hd;
611 req->pio_nhooks = hk;
613 free(iohead, M_TEMP);
614 free(iohook, M_TEMP);
620 pfilioc_listhooks(struct pfilioc_list *req)
622 struct pfil_hook *hook;
623 struct pfilioc_hook *iohook;
630 LIST_FOREACH(hook, &V_pfil_hook_list, hook_list)
634 if (req->pio_nhooks < nhooks) {
635 req->pio_nhooks = nhooks;
639 iohook = malloc(sizeof(*iohook) * nhooks, M_TEMP, M_WAITOK);
643 LIST_FOREACH(hook, &V_pfil_hook_list, hook_list) {
644 if (hk + 1 > nhooks) {
645 /* Configuration changed during malloc(). */
646 free(iohook, M_TEMP);
649 strlcpy(iohook[hk].pio_module, hook->hook_modname,
650 sizeof(iohook[0].pio_module));
651 strlcpy(iohook[hk].pio_ruleset, hook->hook_rulname,
652 sizeof(iohook[0].pio_ruleset));
653 iohook[hk].pio_type = hook->hook_type;
654 iohook[hk].pio_flags = hook->hook_flags;
659 error = copyout(iohook, req->pio_hooks,
660 sizeof(*iohook) * min(req->pio_nhooks, hk));
661 req->pio_nhooks = hk;
662 free(iohook, M_TEMP);
668 pfilioc_link(struct pfilioc_link *req)
670 struct pfil_link_args args;
672 if (req->pio_flags & ~(PFIL_IN | PFIL_OUT | PFIL_UNLINK | PFIL_APPEND))
675 args.pa_version = PFIL_VERSION;
676 args.pa_flags = req->pio_flags;
677 args.pa_headname = req->pio_name;
678 args.pa_modname = req->pio_module;
679 args.pa_rulname = req->pio_ruleset;
681 return (pfil_link(&args));