82125c37cbfd01b89b98227a5d4ec3162dc097db
[dragonfly.git] / sys / net / pfil.c
1 /*      $NetBSD: pfil.c,v 1.20 2001/11/12 23:49:46 lukem Exp $  */
2 /* $DragonFly: src/sys/net/pfil.c,v 1.11 2008/09/14 11:13:00 sephe Exp $ */
3
4 /*
5  * Copyright (c) 1996 Matthew R. Green
6  * All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  * 3. The name of the author may not be used to endorse or promote products
17  *    derived from this software without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
20  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
21  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
22  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
23  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
24  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
26  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29  * SUCH DAMAGE.
30  */
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 #include <net/netmsg2.h>
44
45 #define PFIL_CFGPORT    cpu_portfn(0)
46
47 struct netmsg_pfil {
48         struct netmsg           pfil_nmsg;
49         pfil_func_t             pfil_func;
50         void                    *pfil_arg;
51         int                     pfil_flags;
52         struct pfil_head        *pfil_ph;
53 };
54
55 static LIST_HEAD(, pfil_head) pfil_head_list =
56         LIST_HEAD_INITIALIZER(&pfil_head_list);
57
58 static pfil_list_t      *pfil_list_alloc(void);
59 static void             pfil_list_free(pfil_list_t *);
60 static void             pfil_list_dup(const pfil_list_t *, pfil_list_t *,
61                                       const struct packet_filter_hook *);
62 static void             pfil_list_add(pfil_list_t *, pfil_func_t, void *, int);
63 static struct packet_filter_hook *
64                         pfil_list_find(const pfil_list_t *, pfil_func_t,
65                                        const void *);
66
67 static void             pfil_remove_hook_dispatch(struct netmsg *);
68 static void             pfil_add_hook_dispatch(struct netmsg *);
69
70 /*
71  * pfil_run_hooks() runs the specified packet filter hooks.
72  */
73 int
74 pfil_run_hooks(struct pfil_head *ph, struct mbuf **mp, struct ifnet *ifp,
75     int dir)
76 {
77         struct packet_filter_hook *pfh;
78         struct mbuf *m = *mp;
79         pfil_list_t *list;
80         int rv = 0;
81
82         if (dir == PFIL_IN)
83                 list = ph->ph_in;
84         else if (dir == PFIL_OUT)
85                 list = ph->ph_out;
86         else
87                 return 0; /* XXX panic? */
88
89         TAILQ_FOREACH(pfh, list, pfil_link) {
90                 if (pfh->pfil_func != NULL) {
91                         rv = (*pfh->pfil_func)(pfh->pfil_arg, &m, ifp, dir);
92                         if (rv != 0 || m == NULL)
93                                 break;
94                 }
95         }
96
97         *mp = m;
98         return (rv);
99 }
100
101 /*
102  * pfil_head_register() registers a pfil_head with the packet filter
103  * hook mechanism.
104  */
105 int
106 pfil_head_register(struct pfil_head *ph)
107 {
108         struct pfil_head *lph;
109
110         LIST_FOREACH(lph, &pfil_head_list, ph_list) {
111                 if (ph->ph_type == lph->ph_type &&
112                     ph->ph_un.phu_val == lph->ph_un.phu_val)
113                         return EEXIST;
114         }
115
116         ph->ph_in = pfil_list_alloc();
117         ph->ph_out = pfil_list_alloc();
118         ph->ph_hashooks = 0;
119
120         LIST_INSERT_HEAD(&pfil_head_list, ph, ph_list);
121
122         return (0);
123 }
124
125 /*
126  * pfil_head_unregister() removes a pfil_head from the packet filter
127  * hook mechanism.
128  */
129 int
130 pfil_head_unregister(struct pfil_head *pfh)
131 {
132         LIST_REMOVE(pfh, ph_list);
133         return (0);
134 }
135
136 /*
137  * pfil_head_get() returns the pfil_head for a given key/dlt.
138  */
139 struct pfil_head *
140 pfil_head_get(int type, u_long val)
141 {
142         struct pfil_head *ph;
143
144         LIST_FOREACH(ph, &pfil_head_list, ph_list) {
145                 if (ph->ph_type == type && ph->ph_un.phu_val == val)
146                         break;
147         }
148         return (ph);
149 }
150
151 static void
152 pfil_add_hook_dispatch(struct netmsg *nmsg)
153 {
154         struct netmsg_pfil *pfilmsg = (struct netmsg_pfil *)nmsg;
155         pfil_func_t func = pfilmsg->pfil_func;
156         void *arg = pfilmsg->pfil_arg;
157         int flags = pfilmsg->pfil_flags;
158         struct pfil_head *ph = pfilmsg->pfil_ph;
159         const struct packet_filter_hook *pfh;
160         pfil_list_t *list_in = NULL, *list_out = NULL;
161         pfil_list_t *old_list_in = NULL, *old_list_out = NULL;
162         int err = 0;
163
164         /* This probably should not happen ... */
165         if ((flags & (PFIL_IN | PFIL_OUT)) == 0)
166                 goto reply; /* XXX panic? */
167
168         /*
169          * If pfil hooks exist on any of the requested lists,
170          * then bail out.
171          */
172         if (flags & PFIL_IN) {
173                 pfh = pfil_list_find(ph->ph_in, func, arg);
174                 if (pfh != NULL) {
175                         err = EEXIST;
176                         goto reply;
177                 }
178         }
179         if (flags & PFIL_OUT) {
180                 pfh = pfil_list_find(ph->ph_out, func, arg);
181                 if (pfh != NULL) {
182                         err = EEXIST;
183                         goto reply;
184                 }
185         }
186
187         /*
188          * Duplicate the requested lists, install new hooks
189          * on the duplication
190          */
191         if (flags & PFIL_IN) {
192                 list_in = pfil_list_alloc();
193                 pfil_list_dup(ph->ph_in, list_in, NULL);
194                 pfil_list_add(list_in, func, arg, flags & ~PFIL_OUT);
195         }
196         if (flags & PFIL_OUT) {
197                 list_out = pfil_list_alloc();
198                 pfil_list_dup(ph->ph_out, list_out, NULL);
199                 pfil_list_add(list_out, func, arg, flags & ~PFIL_IN);
200         }
201
202         /*
203          * Switch list pointers, but keep the old ones
204          */
205         if (list_in != NULL) {
206                 old_list_in = ph->ph_in;
207                 ph->ph_in = list_in;
208         }
209         if (list_out != NULL) {
210                 old_list_out = ph->ph_out;
211                 ph->ph_out = list_out;
212         }
213
214         /*
215          * Wait until everyone has finished the old lists iteration
216          */
217         netmsg_service_sync();
218         ph->ph_hashooks = 1;
219
220         /*
221          * Now it is safe to free the old lists, since no one sees it
222          */
223         if (old_list_in != NULL)
224                 pfil_list_free(old_list_in);
225         if (old_list_out != NULL)
226                 pfil_list_free(old_list_out);
227 reply:
228         lwkt_replymsg(&nmsg->nm_lmsg, err);
229 }
230
231 /*
232  * pfil_add_hook() adds a function to the packet filter hook.  the
233  * flags are:
234  *      PFIL_IN         call me on incoming packets
235  *      PFIL_OUT        call me on outgoing packets
236  *      PFIL_ALL        call me on all of the above
237  */
238 int
239 pfil_add_hook(pfil_func_t func, void *arg, int flags, struct pfil_head *ph)
240 {
241         struct netmsg_pfil pfilmsg;
242         struct netmsg *nmsg;
243
244         nmsg = &pfilmsg.pfil_nmsg;
245         netmsg_init(nmsg, &curthread->td_msgport, 0, pfil_add_hook_dispatch);
246         pfilmsg.pfil_func = func;
247         pfilmsg.pfil_arg = arg;
248         pfilmsg.pfil_flags = flags;
249         pfilmsg.pfil_ph = ph;
250
251         return lwkt_domsg(PFIL_CFGPORT, &nmsg->nm_lmsg, 0);
252 }
253
254 static void
255 pfil_remove_hook_dispatch(struct netmsg *nmsg)
256 {
257         struct netmsg_pfil *pfilmsg = (struct netmsg_pfil *)nmsg;
258         pfil_func_t func = pfilmsg->pfil_func;
259         void *arg = pfilmsg->pfil_arg;
260         int flags = pfilmsg->pfil_flags;
261         struct pfil_head *ph = pfilmsg->pfil_ph;
262         struct packet_filter_hook *skip_in = NULL, *skip_out = NULL;
263         pfil_list_t *list_in = NULL, *list_out = NULL;
264         pfil_list_t *old_list_in = NULL, *old_list_out = NULL;
265         int err = 0;
266
267         /* This probably should not happen ... */
268         if ((flags & (PFIL_IN | PFIL_OUT)) == 0)
269                 goto reply; /* XXX panic? */
270
271         /*
272          * The pfil hook should exist on all requested lists,
273          * if not just bail out
274          */
275         if (flags & PFIL_IN) {
276                 skip_in = pfil_list_find(ph->ph_in, func, arg);
277                 if (!skip_in) {
278                         err = ENOENT;
279                         goto reply;
280                 }
281         }
282         if (flags & PFIL_OUT) {
283                 skip_out = pfil_list_find(ph->ph_out, func, arg);
284                 if (!skip_out) {
285                         err = ENOENT;
286                         goto reply;
287                 }
288         }
289
290         /*
291          * Duplicate the requested lists, but the pfil hook to
292          * be deleted is not copied
293          */
294         if (flags & PFIL_IN) {
295                 KKASSERT(skip_in != NULL);
296                 list_in = pfil_list_alloc();
297                 pfil_list_dup(ph->ph_in, list_in, skip_in);
298         }
299         if (flags & PFIL_OUT) {
300                 KKASSERT(skip_out != NULL);
301                 list_out = pfil_list_alloc();
302                 pfil_list_dup(ph->ph_out, list_out, skip_out);
303         }
304
305         /*
306          * Switch list pointers, but keep the old ones
307          */
308         if (list_in != NULL) {
309                 old_list_in = ph->ph_in;
310                 ph->ph_in = list_in;
311         }
312         if (list_out != NULL) {
313                 old_list_out = ph->ph_out;
314                 ph->ph_out = list_out;
315         }
316
317         /*
318          * Wait until everyone has finished the old lists iteration
319          */
320         if (TAILQ_EMPTY(ph->ph_in) && TAILQ_EMPTY(ph->ph_out))
321                 ph->ph_hashooks = 0;
322         netmsg_service_sync();
323
324         /*
325          * Now it is safe to free the old lists, since no one sees it
326          */
327         if (old_list_in != NULL)
328                 pfil_list_free(old_list_in);
329         if (old_list_out != NULL)
330                 pfil_list_free(old_list_out);
331 reply:
332         lwkt_replymsg(&nmsg->nm_lmsg, err);
333 }
334
335 /*
336  * pfil_remove_hook removes a specific function from the packet filter
337  * hook list.
338  */
339 int
340 pfil_remove_hook(pfil_func_t func, void *arg, int flags, struct pfil_head *ph)
341 {
342         struct netmsg_pfil pfilmsg;
343         struct netmsg *nmsg;
344
345         nmsg = &pfilmsg.pfil_nmsg;
346         netmsg_init(nmsg, &curthread->td_msgport, 0, pfil_remove_hook_dispatch);
347         pfilmsg.pfil_func = func;
348         pfilmsg.pfil_arg = arg;
349         pfilmsg.pfil_flags = flags;
350         pfilmsg.pfil_ph = ph;
351
352         return lwkt_domsg(PFIL_CFGPORT, &nmsg->nm_lmsg, 0);
353 }
354
355 static void
356 pfil_list_add(pfil_list_t *list, pfil_func_t func, void *arg, int flags)
357 {
358         struct packet_filter_hook *pfh;
359
360         KKASSERT(&curthread->td_msgport == PFIL_CFGPORT);
361
362         pfh = kmalloc(sizeof(*pfh), M_IFADDR, M_WAITOK);
363
364         pfh->pfil_func = func;
365         pfh->pfil_arg  = arg;
366
367         /*
368          * Insert the input list in reverse order of the output list
369          * so that the same path is followed in or out of the kernel.
370          */
371         if (flags & PFIL_IN)
372                 TAILQ_INSERT_HEAD(list, pfh, pfil_link);
373         else
374                 TAILQ_INSERT_TAIL(list, pfh, pfil_link);
375 }
376
377 static void
378 pfil_list_dup(const pfil_list_t *from, pfil_list_t *to,
379               const struct packet_filter_hook *skip)
380 {
381         struct packet_filter_hook *pfh_to, *pfh_from;
382
383         KKASSERT(&curthread->td_msgport == PFIL_CFGPORT);
384         KKASSERT(TAILQ_EMPTY(to));
385
386         TAILQ_FOREACH(pfh_from, from, pfil_link) {
387                 if (pfh_from == skip)
388                         continue;
389
390                 pfh_to = kmalloc(sizeof(*pfh_to), M_IFADDR, M_WAITOK);
391                 bcopy(pfh_from, pfh_to, sizeof(*pfh_to));
392
393                 TAILQ_INSERT_TAIL(to, pfh_to, pfil_link);
394         }
395 }
396
397 static pfil_list_t *
398 pfil_list_alloc(void)
399 {
400         pfil_list_t *list;
401
402         list = kmalloc(sizeof(*list), M_IFADDR, M_WAITOK);
403         TAILQ_INIT(list);
404         return list;
405 }
406
407 static void
408 pfil_list_free(pfil_list_t *list)
409 {
410         struct packet_filter_hook *pfh;
411
412         while ((pfh = TAILQ_FIRST(list)) != NULL) {
413                 TAILQ_REMOVE(list, pfh, pfil_link);
414                 kfree(pfh, M_IFADDR);
415         }
416         kfree(list, M_IFADDR);
417 }
418
419 static struct packet_filter_hook *
420 pfil_list_find(const pfil_list_t *list, pfil_func_t func, const void *arg)
421 {
422         struct packet_filter_hook *pfh;
423
424         KKASSERT(&curthread->td_msgport == PFIL_CFGPORT);
425
426         TAILQ_FOREACH(pfh, list, pfil_link) {
427                 if (pfh->pfil_func == func && pfh->pfil_arg == arg)
428                         return pfh;
429         }
430         return NULL;
431 }