1/*	$NetBSD: pfil.c,v 1.26 2008/06/23 00:56:08 dyoung Exp $	*/
2
3/*
4 * Copyright (c) 1996 Matthew R. Green
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 *    notice, this list of conditions and the following disclaimer in the
14 *    documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
21 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
23 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26 * SUCH DAMAGE.
27 */
28
29#include <sys/cdefs.h>
30__KERNEL_RCSID(0, "$NetBSD: pfil.c,v 1.26 2008/06/23 00:56:08 dyoung Exp $");
31
32#include <sys/param.h>
33#include <sys/errno.h>
34#include <sys/malloc.h>
35#include <sys/socket.h>
36#include <sys/socketvar.h>
37#include <sys/systm.h>
38#include <sys/proc.h>
39#include <sys/queue.h>
40
41#include <net/if.h>
42#include <net/pfil.h>
43
44static int pfil_list_add(pfil_list_t *,
45    int (*)(void *, struct mbuf **, struct ifnet *, int), void *, int);
46
47static int pfil_list_remove(pfil_list_t *,
48    int (*)(void *, struct mbuf **, struct ifnet *, int), void *);
49
50LIST_HEAD(, pfil_head) pfil_head_list =
51    LIST_HEAD_INITIALIZER(&pfil_head_list);
52
53/*
54 * pfil_run_hooks() runs the specified packet filter hooks.
55 */
56int
57pfil_run_hooks(struct pfil_head *ph, struct mbuf **mp, struct ifnet *ifp,
58    int dir)
59{
60	struct packet_filter_hook *pfh;
61	struct mbuf *m = NULL;
62	int rv = 0;
63
64	if ((dir & PFIL_ALL) && mp)
65		m = *mp;
66	for (pfh = pfil_hook_get(dir, ph); pfh != NULL;
67	     pfh = TAILQ_NEXT(pfh, pfil_link)) {
68		if (pfh->pfil_func != NULL) {
69			if (pfh->pfil_flags & PFIL_ALL) {
70				rv = (*pfh->pfil_func)(pfh->pfil_arg, &m, ifp,
71				    dir);
72				if (rv != 0 || m == NULL)
73					break;
74			} else {
75				rv = (*pfh->pfil_func)(pfh->pfil_arg, mp, ifp,
76				    dir);
77				if (rv != 0)
78					break;
79			}
80		}
81	}
82
83	if ((dir & PFIL_ALL) && mp)
84		*mp = m;
85	return (rv);
86}
87
88/*
89 * pfil_head_register() registers a pfil_head with the packet filter
90 * hook mechanism.
91 */
92int
93pfil_head_register(struct pfil_head *ph)
94{
95	struct pfil_head *lph;
96
97	LIST_FOREACH(lph, &pfil_head_list, ph_list) {
98		if (ph->ph_type == lph->ph_type &&
99		    ph->ph_un.phu_val == lph->ph_un.phu_val)
100			return EEXIST;
101	}
102
103	TAILQ_INIT(&ph->ph_in);
104	TAILQ_INIT(&ph->ph_out);
105	TAILQ_INIT(&ph->ph_ifaddr);
106	TAILQ_INIT(&ph->ph_ifnetevent);
107
108	LIST_INSERT_HEAD(&pfil_head_list, ph, ph_list);
109
110	return (0);
111}
112
113/*
114 * pfil_head_unregister() removes a pfil_head from the packet filter
115 * hook mechanism.
116 */
117int
118pfil_head_unregister(struct pfil_head *pfh)
119{
120
121	LIST_REMOVE(pfh, ph_list);
122	return (0);
123}
124
125/*
126 * pfil_head_get() returns the pfil_head for a given key/dlt.
127 */
128struct pfil_head *
129pfil_head_get(int type, u_long val)
130{
131	struct pfil_head *ph;
132
133	LIST_FOREACH(ph, &pfil_head_list, ph_list) {
134		if (ph->ph_type == type && ph->ph_un.phu_val == val)
135			break;
136	}
137
138	return (ph);
139}
140
141/*
142 * pfil_add_hook() adds a function to the packet filter hook.  the
143 * flags are:
144 *	PFIL_IN		call me on incoming packets
145 *	PFIL_OUT	call me on outgoing packets
146 *	PFIL_ALL	call me on all of the above
147 *	PFIL_IFADDR  	call me on interface reconfig (mbuf ** is ioctl #)
148 *	PFIL_IFNET  	call me on interface attach/detach
149 *			(mbuf ** is PFIL_IFNET_*)
150 *	PFIL_WAITOK	OK to call malloc with M_WAITOK.
151 */
152int
153pfil_add_hook(int (*func)(void *, struct mbuf **, struct ifnet *, int),
154    void *arg, int flags, struct pfil_head *ph)
155{
156	int err = 0;
157
158	if (flags & PFIL_IN) {
159		err = pfil_list_add(&ph->ph_in, func, arg, flags & ~PFIL_OUT);
160		if (err)
161			return err;
162	}
163	if (flags & PFIL_OUT) {
164		err = pfil_list_add(&ph->ph_out, func, arg, flags & ~PFIL_IN);
165		if (err) {
166			if (flags & PFIL_IN)
167				pfil_list_remove(&ph->ph_in, func, arg);
168			return err;
169		}
170	}
171	if (flags & PFIL_IFADDR) {
172		err = pfil_list_add(&ph->ph_ifaddr, func, arg, flags);
173		if (err) {
174			if (flags & PFIL_IN)
175				pfil_list_remove(&ph->ph_in, func, arg);
176			if (flags & PFIL_OUT)
177				pfil_list_remove(&ph->ph_out, func, arg);
178			return err;
179		}
180	}
181	if (flags & PFIL_IFNET) {
182		err = pfil_list_add(&ph->ph_ifnetevent, func, arg, flags);
183		if (err) {
184			if (flags & PFIL_IN)
185				pfil_list_remove(&ph->ph_in, func, arg);
186			if (flags & PFIL_OUT)
187				pfil_list_remove(&ph->ph_out, func, arg);
188			if (flags & PFIL_IFADDR)
189				pfil_list_remove(&ph->ph_ifaddr, func, arg);
190			return err;
191		}
192	}
193	return 0;
194}
195
196static int
197pfil_list_add(pfil_list_t *list,
198    int (*func)(void *, struct mbuf **, struct ifnet *, int), void *arg,
199    int flags)
200{
201	struct packet_filter_hook *pfh;
202
203	/*
204	 * First make sure the hook is not already there.
205	 */
206	TAILQ_FOREACH(pfh, list, pfil_link) {
207		if (pfh->pfil_func == func && pfh->pfil_arg == arg)
208			return EEXIST;
209	}
210
211	pfh = (struct packet_filter_hook *)malloc(sizeof(*pfh), M_IFADDR,
212	    (flags & PFIL_WAITOK) ? M_WAITOK : M_NOWAIT);
213	if (pfh == NULL)
214		return ENOMEM;
215
216	pfh->pfil_func = func;
217	pfh->pfil_arg  = arg;
218	pfh->pfil_flags = flags;
219
220	/*
221	 * insert the input list in reverse order of the output list
222	 * so that the same path is followed in or out of the kernel.
223	 */
224	if (flags & PFIL_IN)
225		TAILQ_INSERT_HEAD(list, pfh, pfil_link);
226	else
227		TAILQ_INSERT_TAIL(list, pfh, pfil_link);
228
229	return 0;
230}
231
232/*
233 * pfil_remove_hook removes a specific function from the packet filter
234 * hook list.
235 */
236int
237pfil_remove_hook(int (*func)(void *, struct mbuf **, struct ifnet *, int),
238    void *arg, int flags, struct pfil_head *ph)
239{
240	int err = 0;
241
242	if (flags & PFIL_IN)
243		err = pfil_list_remove(&ph->ph_in, func, arg);
244	if ((err == 0) && (flags & PFIL_OUT))
245		err = pfil_list_remove(&ph->ph_out, func, arg);
246	if ((err == 0) && (flags & PFIL_IFADDR))
247		err = pfil_list_remove(&ph->ph_ifaddr, func, arg);
248	if ((err == 0) && (flags & PFIL_IFNET))
249		err = pfil_list_remove(&ph->ph_ifnetevent, func, arg);
250	return err;
251}
252
253/*
254 * pfil_list_remove is an internal function that takes a function off the
255 * specified list.
256 */
257static int
258pfil_list_remove(pfil_list_t *list,
259    int (*func)(void *, struct mbuf **, struct ifnet *, int), void *arg)
260{
261	struct packet_filter_hook *pfh;
262
263	TAILQ_FOREACH(pfh, list, pfil_link) {
264		if (pfh->pfil_func == func && pfh->pfil_arg == arg) {
265			TAILQ_REMOVE(list, pfh, pfil_link);
266			free(pfh, M_IFADDR);
267			return 0;
268		}
269	}
270	return ENOENT;
271}
272