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