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
55static MALLOC_DEFINE(M_PFIL, "pfil", "pfil(9) packet filter hooks");
56
57static int pfil_ioctl(struct cdev *, u_long, caddr_t, int, struct thread *);
58static struct cdevsw pfil_cdevsw = {
59	.d_ioctl =	pfil_ioctl,
60	.d_name =	PFILDEV,
61	.d_version =	D_VERSION,
62};
63static struct cdev *pfil_dev;
64
65static struct mtx pfil_lock;
66MTX_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
71struct 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
83struct 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
93typedef CK_STAILQ_HEAD(pfil_chain, pfil_link)	pfil_chain_t;
94struct 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
105LIST_HEAD(pfilheadhead, pfil_head);
106VNET_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
110LIST_HEAD(pfilhookhead, pfil_hook);
111VNET_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
115static struct pfil_link *pfil_link_remove(pfil_chain_t *, pfil_hook_t );
116static 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 */
122static __noinline int
123pfil_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
146static __always_inline int
147pfil_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
180int
181pfil_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
188int
189pfil_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
196static __always_inline int
197pfil_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
225int
226pfil_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
233int
234pfil_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
241int
242pfil_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 */
253pfil_head_t
254pfil_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 */
286void
287pfil_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
306pfil_hook_t
307pfil_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
335static int
336pfil_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
371int
372pfil_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
461fail:
462	PFIL_UNLOCK();
463	free(in, M_PFIL);
464	free(out, M_PFIL);
465	return (error);
466}
467
468static void
469pfil_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 */
480void
481pfil_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) {
488retry:
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 */
514static struct pfil_link *
515pfil_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
530static void
531pfil_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 */
549SYSINIT(pfil_init, SI_SUB_PROTO_PFIL, SI_ORDER_FIRST, pfil_init, NULL);
550
551/*
552 * User control interface.
553 */
554static int pfilioc_listheads(struct pfilioc_list *);
555static int pfilioc_listhooks(struct pfilioc_list *);
556static int pfilioc_link(struct pfilioc_link *);
557
558static int
559pfil_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
584static int
585pfilioc_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();
595restart:
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
664static int
665pfilioc_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();
673restart:
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
712static int
713pfilioc_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}
728