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