xref: /freebsd/sys/net/pfil.c (revision 2008043f386721d58158e37e0d7e50df8095942d)
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 			break;
216 	}
217 	return (rv);
218 }
219 
220 int
221 pfil_mbuf_in(struct pfil_head *head, struct mbuf **m, struct ifnet *ifp,
222    struct inpcb *inp)
223 {
224 
225 	return (pfil_mbuf_common(&head->head_in, m, ifp, PFIL_IN, inp));
226 }
227 
228 int
229 pfil_mbuf_out(struct pfil_head *head, struct mbuf **m, struct ifnet *ifp,
230     struct inpcb *inp)
231 {
232 
233 	return (pfil_mbuf_common(&head->head_out, m, ifp, PFIL_OUT, inp));
234 }
235 
236 int
237 pfil_mbuf_fwd(struct pfil_head *head, struct mbuf **m, struct ifnet *ifp,
238     struct inpcb *inp)
239 {
240 
241 	return (pfil_mbuf_common(&head->head_out, m, ifp, PFIL_OUT | PFIL_FWD, inp));
242 }
243 
244 /*
245  * pfil_head_register() registers a pfil_head with the packet filter hook
246  * mechanism.
247  */
248 pfil_head_t
249 pfil_head_register(struct pfil_head_args *pa)
250 {
251 	struct pfil_head *head, *list;
252 
253 	MPASS(pa->pa_version == PFIL_VERSION);
254 
255 	head = malloc(sizeof(struct pfil_head), M_PFIL, M_WAITOK);
256 
257 	head->head_nhooksin = head->head_nhooksout = 0;
258 	head->head_flags = pa->pa_flags;
259 	head->head_type = pa->pa_type;
260 	head->head_name = pa->pa_headname;
261 	CK_STAILQ_INIT(&head->head_in);
262 	CK_STAILQ_INIT(&head->head_out);
263 
264 	PFIL_LOCK();
265 	LIST_FOREACH(list, &V_pfil_head_list, head_list)
266 		if (strcmp(pa->pa_headname, list->head_name) == 0) {
267 			printf("pfil: duplicate head \"%s\"\n",
268 			    pa->pa_headname);
269 		}
270 	LIST_INSERT_HEAD(&V_pfil_head_list, head, head_list);
271 	PFIL_UNLOCK();
272 
273 	return (head);
274 }
275 
276 /*
277  * pfil_head_unregister() removes a pfil_head from the packet filter hook
278  * mechanism.  The producer of the hook promises that all outstanding
279  * invocations of the hook have completed before it unregisters the hook.
280  */
281 void
282 pfil_head_unregister(pfil_head_t ph)
283 {
284 	struct pfil_link *link, *next;
285 
286 	PFIL_LOCK();
287 	LIST_REMOVE(ph, head_list);
288 
289 	CK_STAILQ_FOREACH_SAFE(link, &ph->head_in, link_chain, next) {
290 		link->link_hook->hook_links--;
291 		free(link, M_PFIL);
292 	}
293 	CK_STAILQ_FOREACH_SAFE(link, &ph->head_out, link_chain, next) {
294 		link->link_hook->hook_links--;
295 		free(link, M_PFIL);
296 	}
297 	PFIL_UNLOCK();
298 }
299 
300 pfil_hook_t
301 pfil_add_hook(struct pfil_hook_args *pa)
302 {
303 	struct pfil_hook *hook, *list;
304 
305 	MPASS(pa->pa_version == PFIL_VERSION);
306 
307 	hook = malloc(sizeof(struct pfil_hook), M_PFIL, M_WAITOK | M_ZERO);
308 	hook->hook_mbuf_chk = pa->pa_mbuf_chk;
309 	hook->hook_mem_chk = pa->pa_mem_chk;
310 	hook->hook_ruleset = pa->pa_ruleset;
311 	hook->hook_flags = pa->pa_flags;
312 	hook->hook_type = pa->pa_type;
313 	hook->hook_modname = pa->pa_modname;
314 	hook->hook_rulname = pa->pa_rulname;
315 
316 	PFIL_LOCK();
317 	LIST_FOREACH(list, &V_pfil_hook_list, hook_list)
318 		if (strcmp(pa->pa_modname, list->hook_modname) == 0 &&
319 		    strcmp(pa->pa_rulname, list->hook_rulname) == 0) {
320 			printf("pfil: duplicate hook \"%s:%s\"\n",
321 			    pa->pa_modname, pa->pa_rulname);
322 		}
323 	LIST_INSERT_HEAD(&V_pfil_hook_list, hook, hook_list);
324 	PFIL_UNLOCK();
325 
326 	return (hook);
327 }
328 
329 static int
330 pfil_unlink(struct pfil_link_args *pa, pfil_head_t head, pfil_hook_t hook)
331 {
332 	struct pfil_link *in, *out;
333 
334 	PFIL_LOCK_ASSERT();
335 
336 	if (pa->pa_flags & PFIL_IN) {
337 		in = pfil_link_remove(&head->head_in, hook);
338 		if (in != NULL) {
339 			head->head_nhooksin--;
340 			hook->hook_links--;
341 		}
342 	} else
343 		in = NULL;
344 	if (pa->pa_flags & PFIL_OUT) {
345 		out = pfil_link_remove(&head->head_out, hook);
346 		if (out != NULL) {
347 			head->head_nhooksout--;
348 			hook->hook_links--;
349 		}
350 	} else
351 		out = NULL;
352 	PFIL_UNLOCK();
353 
354 	if (in != NULL)
355 		NET_EPOCH_CALL(pfil_link_free, &in->link_epoch_ctx);
356 	if (out != NULL)
357 		NET_EPOCH_CALL(pfil_link_free, &out->link_epoch_ctx);
358 
359 	if (in == NULL && out == NULL)
360 		return (ENOENT);
361 	else
362 		return (0);
363 }
364 
365 int
366 pfil_link(struct pfil_link_args *pa)
367 {
368 	struct pfil_link *in, *out, *link;
369 	struct pfil_head *head;
370 	struct pfil_hook *hook;
371 	int error;
372 
373 	MPASS(pa->pa_version == PFIL_VERSION);
374 
375 	if ((pa->pa_flags & (PFIL_IN | PFIL_UNLINK)) == PFIL_IN)
376 		in = malloc(sizeof(*in), M_PFIL, M_WAITOK | M_ZERO);
377 	else
378 		in = NULL;
379 	if ((pa->pa_flags & (PFIL_OUT | PFIL_UNLINK)) == PFIL_OUT)
380 		out = malloc(sizeof(*out), M_PFIL, M_WAITOK | M_ZERO);
381 	else
382 		out = NULL;
383 
384 	PFIL_LOCK();
385 	if (pa->pa_flags & PFIL_HEADPTR)
386 		head = pa->pa_head;
387 	else
388 		LIST_FOREACH(head, &V_pfil_head_list, head_list)
389 			if (strcmp(pa->pa_headname, head->head_name) == 0)
390 				break;
391 	if (pa->pa_flags & PFIL_HOOKPTR)
392 		hook = pa->pa_hook;
393 	else
394 		LIST_FOREACH(hook, &V_pfil_hook_list, hook_list)
395 			if (strcmp(pa->pa_modname, hook->hook_modname) == 0 &&
396 			    strcmp(pa->pa_rulname, hook->hook_rulname) == 0)
397 				break;
398 	if (head == NULL || hook == NULL) {
399 		error = ENOENT;
400 		goto fail;
401 	}
402 
403 	if (pa->pa_flags & PFIL_UNLINK)
404 		return (pfil_unlink(pa, head, hook));
405 
406 	if (head->head_type != hook->hook_type ||
407 	    ((hook->hook_flags & pa->pa_flags) & ~head->head_flags)) {
408 		error = EINVAL;
409 		goto fail;
410 	}
411 
412 	if (pa->pa_flags & PFIL_IN)
413 		CK_STAILQ_FOREACH(link, &head->head_in, link_chain)
414 			if (link->link_hook == hook) {
415 				error = EEXIST;
416 				goto fail;
417 			}
418 	if (pa->pa_flags & PFIL_OUT)
419 		CK_STAILQ_FOREACH(link, &head->head_out, link_chain)
420 			if (link->link_hook == hook) {
421 				error = EEXIST;
422 				goto fail;
423 			}
424 
425 	if (pa->pa_flags & PFIL_IN) {
426 		in->link_hook = hook;
427 		in->link_mbuf_chk = hook->hook_mbuf_chk;
428 		in->link_mem_chk = hook->hook_mem_chk;
429 		in->link_flags = hook->hook_flags;
430 		in->link_ruleset = hook->hook_ruleset;
431 		if (pa->pa_flags & PFIL_APPEND)
432 			CK_STAILQ_INSERT_TAIL(&head->head_in, in, link_chain);
433 		else
434 			CK_STAILQ_INSERT_HEAD(&head->head_in, in, link_chain);
435 		hook->hook_links++;
436 		head->head_nhooksin++;
437 	}
438 	if (pa->pa_flags & PFIL_OUT) {
439 		out->link_hook = hook;
440 		out->link_mbuf_chk = hook->hook_mbuf_chk;
441 		out->link_mem_chk = hook->hook_mem_chk;
442 		out->link_flags = hook->hook_flags;
443 		out->link_ruleset = hook->hook_ruleset;
444 		if (pa->pa_flags & PFIL_APPEND)
445 			CK_STAILQ_INSERT_HEAD(&head->head_out, out, link_chain);
446 		else
447 			CK_STAILQ_INSERT_TAIL(&head->head_out, out, link_chain);
448 		hook->hook_links++;
449 		head->head_nhooksout++;
450 	}
451 	PFIL_UNLOCK();
452 
453 	return (0);
454 
455 fail:
456 	PFIL_UNLOCK();
457 	free(in, M_PFIL);
458 	free(out, M_PFIL);
459 	return (error);
460 }
461 
462 static void
463 pfil_link_free(epoch_context_t ctx)
464 {
465 	struct pfil_link *link;
466 
467 	link = __containerof(ctx, struct pfil_link, link_epoch_ctx);
468 	free(link, M_PFIL);
469 }
470 
471 /*
472  * pfil_remove_hook removes a filter from all filtering points.
473  */
474 void
475 pfil_remove_hook(pfil_hook_t hook)
476 {
477 	struct pfil_head *head;
478 	struct pfil_link *in, *out;
479 
480 	PFIL_LOCK();
481 	LIST_FOREACH(head, &V_pfil_head_list, head_list) {
482 retry:
483 		in = pfil_link_remove(&head->head_in, hook);
484 		if (in != NULL) {
485 			head->head_nhooksin--;
486 			hook->hook_links--;
487 			NET_EPOCH_CALL(pfil_link_free, &in->link_epoch_ctx);
488 		}
489 		out = pfil_link_remove(&head->head_out, hook);
490 		if (out != NULL) {
491 			head->head_nhooksout--;
492 			hook->hook_links--;
493 			NET_EPOCH_CALL(pfil_link_free, &out->link_epoch_ctx);
494 		}
495 		if (in != NULL || out != NULL)
496 			/* What if some stupid admin put same filter twice? */
497 			goto retry;
498 	}
499 	LIST_REMOVE(hook, hook_list);
500 	PFIL_UNLOCK();
501 	MPASS(hook->hook_links == 0);
502 	free(hook, M_PFIL);
503 }
504 
505 /*
506  * Internal: Remove a pfil hook from a hook chain.
507  */
508 static struct pfil_link *
509 pfil_link_remove(pfil_chain_t *chain, pfil_hook_t hook)
510 {
511 	struct pfil_link *link;
512 
513 	PFIL_LOCK_ASSERT();
514 
515 	CK_STAILQ_FOREACH(link, chain, link_chain)
516 		if (link->link_hook == hook) {
517 			CK_STAILQ_REMOVE(chain, link, pfil_link, link_chain);
518 			return (link);
519 		}
520 
521 	return (NULL);
522 }
523 
524 static void
525 pfil_init(const void *unused __unused)
526 {
527 	struct make_dev_args args;
528 	int error __diagused;
529 
530 	make_dev_args_init(&args);
531 	args.mda_flags = MAKEDEV_WAITOK | MAKEDEV_CHECKNAME;
532 	args.mda_devsw = &pfil_cdevsw;
533 	args.mda_uid = UID_ROOT;
534 	args.mda_gid = GID_WHEEL;
535 	args.mda_mode = 0600;
536 	error = make_dev_s(&args, &pfil_dev, PFILDEV);
537 	KASSERT(error == 0, ("%s: failed to create dev: %d", __func__, error));
538 }
539 /*
540  * Make sure the pfil bits are first before any possible subsystem which
541  * might piggyback on the SI_SUB_PROTO_PFIL.
542  */
543 SYSINIT(pfil_init, SI_SUB_PROTO_PFIL, SI_ORDER_FIRST, pfil_init, NULL);
544 
545 /*
546  * User control interface.
547  */
548 static int pfilioc_listheads(struct pfilioc_list *);
549 static int pfilioc_listhooks(struct pfilioc_list *);
550 static int pfilioc_link(struct pfilioc_link *);
551 
552 static int
553 pfil_ioctl(struct cdev *dev, u_long cmd, caddr_t addr, int flags,
554     struct thread *td)
555 {
556 	int error;
557 
558 	CURVNET_SET(TD_TO_VNET(td));
559 	error = 0;
560 	switch (cmd) {
561 	case PFILIOC_LISTHEADS:
562 		error = pfilioc_listheads((struct pfilioc_list *)addr);
563 		break;
564 	case PFILIOC_LISTHOOKS:
565 		error = pfilioc_listhooks((struct pfilioc_list *)addr);
566 		break;
567 	case PFILIOC_LINK:
568 		error = pfilioc_link((struct pfilioc_link *)addr);
569 		break;
570 	default:
571 		error = EINVAL;
572 		break;
573 	}
574 	CURVNET_RESTORE();
575 	return (error);
576 }
577 
578 static int
579 pfilioc_listheads(struct pfilioc_list *req)
580 {
581 	struct pfil_head *head;
582 	struct pfil_link *link;
583 	struct pfilioc_head *iohead;
584 	struct pfilioc_hook *iohook;
585 	u_int nheads, nhooks, hd, hk;
586 	int error;
587 
588 	PFIL_LOCK();
589 restart:
590 	nheads = nhooks = 0;
591 	LIST_FOREACH(head, &V_pfil_head_list, head_list) {
592 		nheads++;
593 		nhooks += head->head_nhooksin + head->head_nhooksout;
594 	}
595 	PFIL_UNLOCK();
596 
597 	if (req->pio_nheads < nheads || req->pio_nhooks < nhooks) {
598 		req->pio_nheads = nheads;
599 		req->pio_nhooks = nhooks;
600 		return (0);
601 	}
602 
603 	iohead = malloc(sizeof(*iohead) * nheads, M_TEMP, M_WAITOK);
604 	iohook = malloc(sizeof(*iohook) * nhooks, M_TEMP, M_WAITOK);
605 
606 	hd = hk = 0;
607 	PFIL_LOCK();
608 	LIST_FOREACH(head, &V_pfil_head_list, head_list) {
609 		if (hd + 1 > nheads ||
610 		    hk + head->head_nhooksin + head->head_nhooksout > nhooks) {
611 			/* Configuration changed during malloc(). */
612 			free(iohead, M_TEMP);
613 			free(iohook, M_TEMP);
614 			goto restart;
615 		}
616 		strlcpy(iohead[hd].pio_name, head->head_name,
617 			sizeof(iohead[0].pio_name));
618 		iohead[hd].pio_nhooksin = head->head_nhooksin;
619 		iohead[hd].pio_nhooksout = head->head_nhooksout;
620 		iohead[hd].pio_type = head->head_type;
621 		CK_STAILQ_FOREACH(link, &head->head_in, link_chain) {
622 			strlcpy(iohook[hk].pio_module,
623 			    link->link_hook->hook_modname,
624 			    sizeof(iohook[0].pio_module));
625 			strlcpy(iohook[hk].pio_ruleset,
626 			    link->link_hook->hook_rulname,
627 			    sizeof(iohook[0].pio_ruleset));
628 			hk++;
629 		}
630 		CK_STAILQ_FOREACH(link, &head->head_out, link_chain) {
631 			strlcpy(iohook[hk].pio_module,
632 			    link->link_hook->hook_modname,
633 			    sizeof(iohook[0].pio_module));
634 			strlcpy(iohook[hk].pio_ruleset,
635 			    link->link_hook->hook_rulname,
636 			    sizeof(iohook[0].pio_ruleset));
637 			hk++;
638 		}
639 		hd++;
640 	}
641 	PFIL_UNLOCK();
642 
643 	error = copyout(iohead, req->pio_heads,
644 	    sizeof(*iohead) * min(hd, req->pio_nheads));
645 	if (error == 0)
646 		error = copyout(iohook, req->pio_hooks,
647 		    sizeof(*iohook) * min(req->pio_nhooks, hk));
648 
649 	req->pio_nheads = hd;
650 	req->pio_nhooks = hk;
651 
652 	free(iohead, M_TEMP);
653 	free(iohook, M_TEMP);
654 
655 	return (error);
656 }
657 
658 static int
659 pfilioc_listhooks(struct pfilioc_list *req)
660 {
661 	struct pfil_hook *hook;
662 	struct pfilioc_hook *iohook;
663 	u_int nhooks, hk;
664 	int error;
665 
666 	PFIL_LOCK();
667 restart:
668 	nhooks = 0;
669 	LIST_FOREACH(hook, &V_pfil_hook_list, hook_list)
670 		nhooks++;
671 	PFIL_UNLOCK();
672 
673 	if (req->pio_nhooks < nhooks) {
674 		req->pio_nhooks = nhooks;
675 		return (0);
676 	}
677 
678 	iohook = malloc(sizeof(*iohook) * nhooks, M_TEMP, M_WAITOK);
679 
680 	hk = 0;
681 	PFIL_LOCK();
682 	LIST_FOREACH(hook, &V_pfil_hook_list, hook_list) {
683 		if (hk + 1 > nhooks) {
684 			/* Configuration changed during malloc(). */
685 			free(iohook, M_TEMP);
686 			goto restart;
687 		}
688 		strlcpy(iohook[hk].pio_module, hook->hook_modname,
689 		    sizeof(iohook[0].pio_module));
690 		strlcpy(iohook[hk].pio_ruleset, hook->hook_rulname,
691 		    sizeof(iohook[0].pio_ruleset));
692 		iohook[hk].pio_type = hook->hook_type;
693 		iohook[hk].pio_flags = hook->hook_flags;
694 		hk++;
695 	}
696 	PFIL_UNLOCK();
697 
698 	error = copyout(iohook, req->pio_hooks,
699 	    sizeof(*iohook) * min(req->pio_nhooks, hk));
700 	req->pio_nhooks = hk;
701 	free(iohook, M_TEMP);
702 
703 	return (error);
704 }
705 
706 static int
707 pfilioc_link(struct pfilioc_link *req)
708 {
709 	struct pfil_link_args args;
710 
711 	if (req->pio_flags & ~(PFIL_IN | PFIL_OUT | PFIL_UNLINK | PFIL_APPEND))
712 		return (EINVAL);
713 
714 	args.pa_version = PFIL_VERSION;
715 	args.pa_flags = req->pio_flags;
716 	args.pa_headname = req->pio_name;
717 	args.pa_modname = req->pio_module;
718 	args.pa_rulname = req->pio_ruleset;
719 
720 	return (pfil_link(&args));
721 }
722