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