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