Merge llvm-project release/18.x llvmorg-18-init-18361-g22683463740e
[freebsd.git] / sys / net / pfil.c
1 /*      $NetBSD: pfil.c,v 1.20 2001/11/12 23:49:46 lukem Exp $  */
2
3 /*-
4  * SPDX-License-Identifier: BSD-3-Clause
5  *
6  * Copyright (c) 2019 Gleb Smirnoff <glebius@FreeBSD.org>
7  * Copyright (c) 1996 Matthew R. Green
8  * All rights reserved.
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  * 3. The name of the author may not be used to endorse or promote products
19  *    derived from this software without specific prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
22  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
23  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
24  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
25  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
26  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
27  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
28  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
29  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31  * SUCH DAMAGE.
32  */
33
34 #include <sys/param.h>
35 #include <sys/conf.h>
36 #include <sys/kernel.h>
37 #include <sys/epoch.h>
38 #include <sys/errno.h>
39 #include <sys/lock.h>
40 #include <sys/malloc.h>
41 #include <sys/socket.h>
42 #include <sys/socketvar.h>
43 #include <sys/systm.h>
44 #include <sys/lock.h>
45 #include <sys/mutex.h>
46 #include <sys/proc.h>
47 #include <sys/queue.h>
48 #include <sys/ucred.h>
49 #include <sys/jail.h>
50
51 #include <net/if.h>
52 #include <net/if_var.h>
53 #include <net/pfil.h>
54
55 static MALLOC_DEFINE(M_PFIL, "pfil", "pfil(9) packet filter hooks");
56
57 static int pfil_ioctl(struct cdev *, u_long, caddr_t, int, struct thread *);
58 static struct cdevsw pfil_cdevsw = {
59         .d_ioctl =      pfil_ioctl,
60         .d_name =       PFILDEV,
61         .d_version =    D_VERSION,
62 };
63 static struct cdev *pfil_dev;
64
65 static struct mtx pfil_lock;
66 MTX_SYSINIT(pfil_mtxinit, &pfil_lock, "pfil(9) lock", MTX_DEF);
67 #define PFIL_LOCK()     mtx_lock(&pfil_lock)
68 #define PFIL_UNLOCK()   mtx_unlock(&pfil_lock)
69 #define PFIL_LOCK_ASSERT()      mtx_assert(&pfil_lock, MA_OWNED)
70
71 struct pfil_hook {
72         pfil_mbuf_chk_t  hook_mbuf_chk;
73         pfil_mem_chk_t   hook_mem_chk;
74         void            *hook_ruleset;
75         int              hook_flags;
76         int              hook_links;
77         enum pfil_types  hook_type;
78         const char      *hook_modname;
79         const char      *hook_rulname;
80         LIST_ENTRY(pfil_hook) hook_list;
81 };
82
83 struct pfil_link {
84         CK_STAILQ_ENTRY(pfil_link) link_chain;
85         pfil_mbuf_chk_t          link_mbuf_chk;
86         pfil_mem_chk_t           link_mem_chk;
87         void                    *link_ruleset;
88         int                      link_flags;
89         struct pfil_hook        *link_hook;
90         struct epoch_context     link_epoch_ctx;
91 };
92
93 typedef CK_STAILQ_HEAD(pfil_chain, pfil_link)   pfil_chain_t;
94 struct pfil_head {
95         int              head_nhooksin;
96         int              head_nhooksout;
97         pfil_chain_t     head_in;
98         pfil_chain_t     head_out;
99         int              head_flags;
100         enum pfil_types  head_type;
101         LIST_ENTRY(pfil_head) head_list;
102         const char      *head_name;
103 };
104
105 LIST_HEAD(pfilheadhead, pfil_head);
106 VNET_DEFINE_STATIC(struct pfilheadhead, pfil_head_list) =
107     LIST_HEAD_INITIALIZER(pfil_head_list);
108 #define V_pfil_head_list        VNET(pfil_head_list)
109
110 LIST_HEAD(pfilhookhead, pfil_hook);
111 VNET_DEFINE_STATIC(struct pfilhookhead, pfil_hook_list) =
112     LIST_HEAD_INITIALIZER(pfil_hook_list);
113 #define V_pfil_hook_list        VNET(pfil_hook_list)
114
115 static struct pfil_link *pfil_link_remove(pfil_chain_t *, pfil_hook_t );
116 static void pfil_link_free(epoch_context_t);
117
118 /*
119  * To couple a filtering point that provides memory pointer with a filter that
120  * works on mbufs only.
121  */
122 static __noinline int
123 pfil_fake_mbuf(pfil_mbuf_chk_t func, void *mem, u_int len, struct ifnet *ifp,
124     int flags, void *ruleset, struct mbuf **mp)
125 {
126         struct mbuf m;
127         pfil_return_t rv;
128
129         (void)m_init(&m, M_NOWAIT, MT_DATA, M_NOFREE | M_PKTHDR);
130         m_extadd(&m, mem, len, NULL, NULL, NULL, 0, EXT_RXRING);
131         m.m_len = m.m_pkthdr.len = len;
132         *mp = &m;
133
134         rv = func(mp, ifp, flags, ruleset, NULL);
135         if (rv == PFIL_PASS && *mp != &m) {
136                 /*
137                  * Firewalls that need pfil_fake_mbuf() most likely don't
138                  * know they need return PFIL_REALLOCED.
139                  */
140                 rv = PFIL_REALLOCED;
141         }
142
143         return (rv);
144 }
145
146 static __always_inline int
147 pfil_mem_common(pfil_chain_t *pch, void *mem, u_int len, int flags,
148     struct ifnet *ifp, struct mbuf **m)
149 {
150         struct pfil_link *link;
151         pfil_return_t rv;
152         bool realloc = false;
153
154         NET_EPOCH_ASSERT();
155         KASSERT(flags == PFIL_IN || flags == PFIL_OUT,
156             ("%s: unsupported flags %d", __func__, flags));
157
158         rv = PFIL_PASS;
159         CK_STAILQ_FOREACH(link, pch, link_chain) {
160                 if (__predict_true(link->link_mem_chk != NULL && !realloc))
161                         rv = link->link_mem_chk(mem, len, flags, ifp,
162                             link->link_ruleset, m);
163                 else if (!realloc)
164                         rv = pfil_fake_mbuf(link->link_mbuf_chk, mem, len, ifp,
165                             flags, link->link_ruleset, m);
166                 else
167                         rv = link->link_mbuf_chk(m, ifp, flags,
168                             link->link_ruleset, NULL);
169
170                 if (rv == PFIL_DROPPED || rv == PFIL_CONSUMED)
171                         break;
172                 else if (rv == PFIL_REALLOCED)
173                         realloc = true;
174         }
175         if (realloc && rv == PFIL_PASS)
176                 rv = PFIL_REALLOCED;
177         return (rv);
178 }
179
180 int
181 pfil_mem_in(struct pfil_head *head, void *mem, u_int len, struct ifnet *ifp,
182     struct mbuf **m)
183 {
184
185         return (pfil_mem_common(&head->head_in, mem, len, PFIL_IN, ifp, m));
186 }
187
188 int
189 pfil_mem_out(struct pfil_head *head, void *mem, u_int len, struct ifnet *ifp,
190     struct mbuf **m)
191 {
192
193         return (pfil_mem_common(&head->head_out, mem, len, PFIL_OUT, ifp, m));
194 }
195
196 static __always_inline int
197 pfil_mbuf_common(pfil_chain_t *pch, struct mbuf **m, struct ifnet *ifp,
198     int flags, struct inpcb *inp)
199 {
200         struct pfil_link *link;
201         pfil_return_t rv;
202
203         NET_EPOCH_ASSERT();
204         KASSERT((flags & ~(PFIL_IN|PFIL_OUT|PFIL_FWD)) == 0,
205             ("%s: unsupported flags %#x", __func__, flags));
206         KASSERT((flags & ~PFIL_FWD) == PFIL_IN ||
207             (flags & ~PFIL_FWD) == PFIL_OUT,
208             ("%s: conflicting directions %#x", __func__, flags));
209
210         rv = PFIL_PASS;
211         CK_STAILQ_FOREACH(link, pch, link_chain) {
212                 rv = link->link_mbuf_chk(m, ifp, flags, link->link_ruleset,
213                     inp);
214                 if (rv == PFIL_DROPPED || rv == PFIL_CONSUMED) {
215                         MPASS(*m == NULL);
216                         break;
217                 } else {
218                         MPASS(*m != NULL);
219                 }
220         }
221
222         return (rv);
223 }
224
225 int
226 pfil_mbuf_in(struct pfil_head *head, struct mbuf **m, struct ifnet *ifp,
227    struct inpcb *inp)
228 {
229
230         return (pfil_mbuf_common(&head->head_in, m, ifp, PFIL_IN, inp));
231 }
232
233 int
234 pfil_mbuf_out(struct pfil_head *head, struct mbuf **m, struct ifnet *ifp,
235     struct inpcb *inp)
236 {
237
238         return (pfil_mbuf_common(&head->head_out, m, ifp, PFIL_OUT, inp));
239 }
240
241 int
242 pfil_mbuf_fwd(struct pfil_head *head, struct mbuf **m, struct ifnet *ifp,
243     struct inpcb *inp)
244 {
245
246         return (pfil_mbuf_common(&head->head_out, m, ifp, PFIL_OUT | PFIL_FWD, inp));
247 }
248
249 /*
250  * pfil_head_register() registers a pfil_head with the packet filter hook
251  * mechanism.
252  */
253 pfil_head_t
254 pfil_head_register(struct pfil_head_args *pa)
255 {
256         struct pfil_head *head, *list;
257
258         MPASS(pa->pa_version == PFIL_VERSION);
259
260         head = malloc(sizeof(struct pfil_head), M_PFIL, M_WAITOK);
261
262         head->head_nhooksin = head->head_nhooksout = 0;
263         head->head_flags = pa->pa_flags;
264         head->head_type = pa->pa_type;
265         head->head_name = pa->pa_headname;
266         CK_STAILQ_INIT(&head->head_in);
267         CK_STAILQ_INIT(&head->head_out);
268
269         PFIL_LOCK();
270         LIST_FOREACH(list, &V_pfil_head_list, head_list)
271                 if (strcmp(pa->pa_headname, list->head_name) == 0) {
272                         printf("pfil: duplicate head \"%s\"\n",
273                             pa->pa_headname);
274                 }
275         LIST_INSERT_HEAD(&V_pfil_head_list, head, head_list);
276         PFIL_UNLOCK();
277
278         return (head);
279 }
280
281 /*
282  * pfil_head_unregister() removes a pfil_head from the packet filter hook
283  * mechanism.  The producer of the hook promises that all outstanding
284  * invocations of the hook have completed before it unregisters the hook.
285  */
286 void
287 pfil_head_unregister(pfil_head_t ph)
288 {
289         struct pfil_link *link, *next;
290
291         PFIL_LOCK();
292         LIST_REMOVE(ph, head_list);
293
294         CK_STAILQ_FOREACH_SAFE(link, &ph->head_in, link_chain, next) {
295                 link->link_hook->hook_links--;
296                 free(link, M_PFIL);
297         }
298         CK_STAILQ_FOREACH_SAFE(link, &ph->head_out, link_chain, next) {
299                 link->link_hook->hook_links--;
300                 free(link, M_PFIL);
301         }
302         PFIL_UNLOCK();
303         free(ph, M_PFIL);
304 }
305
306 pfil_hook_t
307 pfil_add_hook(struct pfil_hook_args *pa)
308 {
309         struct pfil_hook *hook, *list;
310
311         MPASS(pa->pa_version == PFIL_VERSION);
312
313         hook = malloc(sizeof(struct pfil_hook), M_PFIL, M_WAITOK | M_ZERO);
314         hook->hook_mbuf_chk = pa->pa_mbuf_chk;
315         hook->hook_mem_chk = pa->pa_mem_chk;
316         hook->hook_ruleset = pa->pa_ruleset;
317         hook->hook_flags = pa->pa_flags;
318         hook->hook_type = pa->pa_type;
319         hook->hook_modname = pa->pa_modname;
320         hook->hook_rulname = pa->pa_rulname;
321
322         PFIL_LOCK();
323         LIST_FOREACH(list, &V_pfil_hook_list, hook_list)
324                 if (strcmp(pa->pa_modname, list->hook_modname) == 0 &&
325                     strcmp(pa->pa_rulname, list->hook_rulname) == 0) {
326                         printf("pfil: duplicate hook \"%s:%s\"\n",
327                             pa->pa_modname, pa->pa_rulname);
328                 }
329         LIST_INSERT_HEAD(&V_pfil_hook_list, hook, hook_list);
330         PFIL_UNLOCK();
331
332         return (hook);
333 }
334
335 static int
336 pfil_unlink(struct pfil_link_args *pa, pfil_head_t head, pfil_hook_t hook)
337 {
338         struct pfil_link *in, *out;
339
340         PFIL_LOCK_ASSERT();
341
342         if (pa->pa_flags & PFIL_IN) {
343                 in = pfil_link_remove(&head->head_in, hook);
344                 if (in != NULL) {
345                         head->head_nhooksin--;
346                         hook->hook_links--;
347                 }
348         } else
349                 in = NULL;
350         if (pa->pa_flags & PFIL_OUT) {
351                 out = pfil_link_remove(&head->head_out, hook);
352                 if (out != NULL) {
353                         head->head_nhooksout--;
354                         hook->hook_links--;
355                 }
356         } else
357                 out = NULL;
358         PFIL_UNLOCK();
359
360         if (in != NULL)
361                 NET_EPOCH_CALL(pfil_link_free, &in->link_epoch_ctx);
362         if (out != NULL)
363                 NET_EPOCH_CALL(pfil_link_free, &out->link_epoch_ctx);
364
365         if (in == NULL && out == NULL)
366                 return (ENOENT);
367         else
368                 return (0);
369 }
370
371 int
372 pfil_link(struct pfil_link_args *pa)
373 {
374         struct pfil_link *in, *out, *link;
375         struct pfil_head *head;
376         struct pfil_hook *hook;
377         int error;
378
379         MPASS(pa->pa_version == PFIL_VERSION);
380
381         if ((pa->pa_flags & (PFIL_IN | PFIL_UNLINK)) == PFIL_IN)
382                 in = malloc(sizeof(*in), M_PFIL, M_WAITOK | M_ZERO);
383         else
384                 in = NULL;
385         if ((pa->pa_flags & (PFIL_OUT | PFIL_UNLINK)) == PFIL_OUT)
386                 out = malloc(sizeof(*out), M_PFIL, M_WAITOK | M_ZERO);
387         else
388                 out = NULL;
389
390         PFIL_LOCK();
391         if (pa->pa_flags & PFIL_HEADPTR)
392                 head = pa->pa_head;
393         else
394                 LIST_FOREACH(head, &V_pfil_head_list, head_list)
395                         if (strcmp(pa->pa_headname, head->head_name) == 0)
396                                 break;
397         if (pa->pa_flags & PFIL_HOOKPTR)
398                 hook = pa->pa_hook;
399         else
400                 LIST_FOREACH(hook, &V_pfil_hook_list, hook_list)
401                         if (strcmp(pa->pa_modname, hook->hook_modname) == 0 &&
402                             strcmp(pa->pa_rulname, hook->hook_rulname) == 0)
403                                 break;
404         if (head == NULL || hook == NULL) {
405                 error = ENOENT;
406                 goto fail;
407         }
408
409         if (pa->pa_flags & PFIL_UNLINK)
410                 return (pfil_unlink(pa, head, hook));
411
412         if (head->head_type != hook->hook_type ||
413             ((hook->hook_flags & pa->pa_flags) & ~head->head_flags)) {
414                 error = EINVAL;
415                 goto fail;
416         }
417
418         if (pa->pa_flags & PFIL_IN)
419                 CK_STAILQ_FOREACH(link, &head->head_in, link_chain)
420                         if (link->link_hook == hook) {
421                                 error = EEXIST;
422                                 goto fail;
423                         }
424         if (pa->pa_flags & PFIL_OUT)
425                 CK_STAILQ_FOREACH(link, &head->head_out, link_chain)
426                         if (link->link_hook == hook) {
427                                 error = EEXIST;
428                                 goto fail;
429                         }
430
431         if (pa->pa_flags & PFIL_IN) {
432                 in->link_hook = hook;
433                 in->link_mbuf_chk = hook->hook_mbuf_chk;
434                 in->link_mem_chk = hook->hook_mem_chk;
435                 in->link_flags = hook->hook_flags;
436                 in->link_ruleset = hook->hook_ruleset;
437                 if (pa->pa_flags & PFIL_APPEND)
438                         CK_STAILQ_INSERT_TAIL(&head->head_in, in, link_chain);
439                 else
440                         CK_STAILQ_INSERT_HEAD(&head->head_in, in, link_chain);
441                 hook->hook_links++;
442                 head->head_nhooksin++;
443         }
444         if (pa->pa_flags & PFIL_OUT) {
445                 out->link_hook = hook;
446                 out->link_mbuf_chk = hook->hook_mbuf_chk;
447                 out->link_mem_chk = hook->hook_mem_chk;
448                 out->link_flags = hook->hook_flags;
449                 out->link_ruleset = hook->hook_ruleset;
450                 if (pa->pa_flags & PFIL_APPEND)
451                         CK_STAILQ_INSERT_HEAD(&head->head_out, out, link_chain);
452                 else
453                         CK_STAILQ_INSERT_TAIL(&head->head_out, out, link_chain);
454                 hook->hook_links++;
455                 head->head_nhooksout++;
456         }
457         PFIL_UNLOCK();
458
459         return (0);
460
461 fail:
462         PFIL_UNLOCK();
463         free(in, M_PFIL);
464         free(out, M_PFIL);
465         return (error);
466 }
467
468 static void
469 pfil_link_free(epoch_context_t ctx)
470 {
471         struct pfil_link *link;
472
473         link = __containerof(ctx, struct pfil_link, link_epoch_ctx);
474         free(link, M_PFIL);
475 }
476
477 /*
478  * pfil_remove_hook removes a filter from all filtering points.
479  */
480 void
481 pfil_remove_hook(pfil_hook_t hook)
482 {
483         struct pfil_head *head;
484         struct pfil_link *in, *out;
485
486         PFIL_LOCK();
487         LIST_FOREACH(head, &V_pfil_head_list, head_list) {
488 retry:
489                 in = pfil_link_remove(&head->head_in, hook);
490                 if (in != NULL) {
491                         head->head_nhooksin--;
492                         hook->hook_links--;
493                         NET_EPOCH_CALL(pfil_link_free, &in->link_epoch_ctx);
494                 }
495                 out = pfil_link_remove(&head->head_out, hook);
496                 if (out != NULL) {
497                         head->head_nhooksout--;
498                         hook->hook_links--;
499                         NET_EPOCH_CALL(pfil_link_free, &out->link_epoch_ctx);
500                 }
501                 if (in != NULL || out != NULL)
502                         /* What if some stupid admin put same filter twice? */
503                         goto retry;
504         }
505         LIST_REMOVE(hook, hook_list);
506         PFIL_UNLOCK();
507         MPASS(hook->hook_links == 0);
508         free(hook, M_PFIL);
509 }
510
511 /*
512  * Internal: Remove a pfil hook from a hook chain.
513  */
514 static struct pfil_link *
515 pfil_link_remove(pfil_chain_t *chain, pfil_hook_t hook)
516 {
517         struct pfil_link *link;
518
519         PFIL_LOCK_ASSERT();
520
521         CK_STAILQ_FOREACH(link, chain, link_chain)
522                 if (link->link_hook == hook) {
523                         CK_STAILQ_REMOVE(chain, link, pfil_link, link_chain);
524                         return (link);
525                 }
526
527         return (NULL);
528 }
529
530 static void
531 pfil_init(const void *unused __unused)
532 {
533         struct make_dev_args args;
534         int error __diagused;
535
536         make_dev_args_init(&args);
537         args.mda_flags = MAKEDEV_WAITOK | MAKEDEV_CHECKNAME;
538         args.mda_devsw = &pfil_cdevsw;
539         args.mda_uid = UID_ROOT;
540         args.mda_gid = GID_WHEEL;
541         args.mda_mode = 0600;
542         error = make_dev_s(&args, &pfil_dev, PFILDEV);
543         KASSERT(error == 0, ("%s: failed to create dev: %d", __func__, error));
544 }
545 /*
546  * Make sure the pfil bits are first before any possible subsystem which
547  * might piggyback on the SI_SUB_PROTO_PFIL.
548  */
549 SYSINIT(pfil_init, SI_SUB_PROTO_PFIL, SI_ORDER_FIRST, pfil_init, NULL);
550
551 /*
552  * User control interface.
553  */
554 static int pfilioc_listheads(struct pfilioc_list *);
555 static int pfilioc_listhooks(struct pfilioc_list *);
556 static int pfilioc_link(struct pfilioc_link *);
557
558 static int
559 pfil_ioctl(struct cdev *dev, u_long cmd, caddr_t addr, int flags,
560     struct thread *td)
561 {
562         int error;
563
564         CURVNET_SET(TD_TO_VNET(td));
565         error = 0;
566         switch (cmd) {
567         case PFILIOC_LISTHEADS:
568                 error = pfilioc_listheads((struct pfilioc_list *)addr);
569                 break;
570         case PFILIOC_LISTHOOKS:
571                 error = pfilioc_listhooks((struct pfilioc_list *)addr);
572                 break;
573         case PFILIOC_LINK:
574                 error = pfilioc_link((struct pfilioc_link *)addr);
575                 break;
576         default:
577                 error = EINVAL;
578                 break;
579         }
580         CURVNET_RESTORE();
581         return (error);
582 }
583
584 static int
585 pfilioc_listheads(struct pfilioc_list *req)
586 {
587         struct pfil_head *head;
588         struct pfil_link *link;
589         struct pfilioc_head *iohead;
590         struct pfilioc_hook *iohook;
591         u_int nheads, nhooks, hd, hk;
592         int error;
593
594         PFIL_LOCK();
595 restart:
596         nheads = nhooks = 0;
597         LIST_FOREACH(head, &V_pfil_head_list, head_list) {
598                 nheads++;
599                 nhooks += head->head_nhooksin + head->head_nhooksout;
600         }
601         PFIL_UNLOCK();
602
603         if (req->pio_nheads < nheads || req->pio_nhooks < nhooks) {
604                 req->pio_nheads = nheads;
605                 req->pio_nhooks = nhooks;
606                 return (0);
607         }
608
609         iohead = malloc(sizeof(*iohead) * nheads, M_TEMP, M_WAITOK);
610         iohook = malloc(sizeof(*iohook) * nhooks, M_TEMP, M_WAITOK);
611
612         hd = hk = 0;
613         PFIL_LOCK();
614         LIST_FOREACH(head, &V_pfil_head_list, head_list) {
615                 if (hd + 1 > nheads ||
616                     hk + head->head_nhooksin + head->head_nhooksout > nhooks) {
617                         /* Configuration changed during malloc(). */
618                         free(iohead, M_TEMP);
619                         free(iohook, M_TEMP);
620                         goto restart;
621                 }
622                 strlcpy(iohead[hd].pio_name, head->head_name,
623                         sizeof(iohead[0].pio_name));
624                 iohead[hd].pio_nhooksin = head->head_nhooksin;
625                 iohead[hd].pio_nhooksout = head->head_nhooksout;
626                 iohead[hd].pio_type = head->head_type;
627                 CK_STAILQ_FOREACH(link, &head->head_in, link_chain) {
628                         strlcpy(iohook[hk].pio_module,
629                             link->link_hook->hook_modname,
630                             sizeof(iohook[0].pio_module));
631                         strlcpy(iohook[hk].pio_ruleset,
632                             link->link_hook->hook_rulname,
633                             sizeof(iohook[0].pio_ruleset));
634                         hk++;
635                 }
636                 CK_STAILQ_FOREACH(link, &head->head_out, link_chain) {
637                         strlcpy(iohook[hk].pio_module,
638                             link->link_hook->hook_modname,
639                             sizeof(iohook[0].pio_module));
640                         strlcpy(iohook[hk].pio_ruleset,
641                             link->link_hook->hook_rulname,
642                             sizeof(iohook[0].pio_ruleset));
643                         hk++;
644                 }
645                 hd++;
646         }
647         PFIL_UNLOCK();
648
649         error = copyout(iohead, req->pio_heads,
650             sizeof(*iohead) * min(hd, req->pio_nheads));
651         if (error == 0)
652                 error = copyout(iohook, req->pio_hooks,
653                     sizeof(*iohook) * min(req->pio_nhooks, hk));
654
655         req->pio_nheads = hd;
656         req->pio_nhooks = hk;
657
658         free(iohead, M_TEMP);
659         free(iohook, M_TEMP);
660
661         return (error);
662 }
663
664 static int
665 pfilioc_listhooks(struct pfilioc_list *req)
666 {
667         struct pfil_hook *hook;
668         struct pfilioc_hook *iohook;
669         u_int nhooks, hk;
670         int error;
671
672         PFIL_LOCK();
673 restart:
674         nhooks = 0;
675         LIST_FOREACH(hook, &V_pfil_hook_list, hook_list)
676                 nhooks++;
677         PFIL_UNLOCK();
678
679         if (req->pio_nhooks < nhooks) {
680                 req->pio_nhooks = nhooks;
681                 return (0);
682         }
683
684         iohook = malloc(sizeof(*iohook) * nhooks, M_TEMP, M_WAITOK);
685
686         hk = 0;
687         PFIL_LOCK();
688         LIST_FOREACH(hook, &V_pfil_hook_list, hook_list) {
689                 if (hk + 1 > nhooks) {
690                         /* Configuration changed during malloc(). */
691                         free(iohook, M_TEMP);
692                         goto restart;
693                 }
694                 strlcpy(iohook[hk].pio_module, hook->hook_modname,
695                     sizeof(iohook[0].pio_module));
696                 strlcpy(iohook[hk].pio_ruleset, hook->hook_rulname,
697                     sizeof(iohook[0].pio_ruleset));
698                 iohook[hk].pio_type = hook->hook_type;
699                 iohook[hk].pio_flags = hook->hook_flags;
700                 hk++;
701         }
702         PFIL_UNLOCK();
703
704         error = copyout(iohook, req->pio_hooks,
705             sizeof(*iohook) * min(req->pio_nhooks, hk));
706         req->pio_nhooks = hk;
707         free(iohook, M_TEMP);
708
709         return (error);
710 }
711
712 static int
713 pfilioc_link(struct pfilioc_link *req)
714 {
715         struct pfil_link_args args;
716
717         if (req->pio_flags & ~(PFIL_IN | PFIL_OUT | PFIL_UNLINK | PFIL_APPEND))
718                 return (EINVAL);
719
720         args.pa_version = PFIL_VERSION;
721         args.pa_flags = req->pio_flags;
722         args.pa_headname = req->pio_name;
723         args.pa_modname = req->pio_module;
724         args.pa_rulname = req->pio_ruleset;
725
726         return (pfil_link(&args));
727 }