54aaa8ae408263ef579d03273c5bac9e92903d6d
[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.13 2008/09/16 11:57:30 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 /*
46  * The packet filter hooks are designed for anything to call them to
47  * possibly intercept the packet.
48  */
49 struct packet_filter_hook {
50         TAILQ_ENTRY(packet_filter_hook) pfil_link;
51         pfil_func_t     pfil_func;
52         void            *pfil_arg;
53         int             pfil_flags;
54 };
55
56 #define PFIL_CFGPORT    cpu_portfn(0)
57
58 struct netmsg_pfil {
59         struct netmsg           pfil_nmsg;
60         pfil_func_t             pfil_func;
61         void                    *pfil_arg;
62         int                     pfil_flags;
63         struct pfil_head        *pfil_ph;
64 };
65
66 static LIST_HEAD(, pfil_head) pfil_head_list =
67         LIST_HEAD_INITIALIZER(&pfil_head_list);
68
69 static pfil_list_t      *pfil_list_alloc(void);
70 static void             pfil_list_free(pfil_list_t *);
71 static void             pfil_list_dup(const pfil_list_t *, pfil_list_t *,
72                                       const struct packet_filter_hook *);
73 static void             pfil_list_add(pfil_list_t *, pfil_func_t, void *, int);
74 static struct packet_filter_hook *
75                         pfil_list_find(const pfil_list_t *, pfil_func_t,
76                                        const void *);
77
78 static void             pfil_remove_hook_dispatch(struct netmsg *);
79 static void             pfil_add_hook_dispatch(struct netmsg *);
80
81 /*
82  * pfil_run_hooks() runs the specified packet filter hooks.
83  */
84 int
85 pfil_run_hooks(struct pfil_head *ph, struct mbuf **mp, struct ifnet *ifp,
86     int dir)
87 {
88         struct packet_filter_hook *pfh;
89         struct mbuf *m = *mp;
90         pfil_list_t *list;
91         int rv = 0;
92
93         if (dir == PFIL_IN)
94                 list = ph->ph_in;
95         else if (dir == PFIL_OUT)
96                 list = ph->ph_out;
97         else
98                 return 0; /* XXX panic? */
99
100         TAILQ_FOREACH(pfh, list, pfil_link) {
101                 if (pfh->pfil_func != NULL) {
102                         rv = (*pfh->pfil_func)(pfh->pfil_arg, &m, ifp, dir);
103                         if (rv != 0 || m == NULL)
104                                 break;
105                 }
106         }
107
108         *mp = m;
109         return (rv);
110 }
111
112 /*
113  * pfil_head_register() registers a pfil_head with the packet filter
114  * hook mechanism.
115  */
116 int
117 pfil_head_register(struct pfil_head *ph)
118 {
119         struct pfil_head *lph;
120
121         LIST_FOREACH(lph, &pfil_head_list, ph_list) {
122                 if (ph->ph_type == lph->ph_type &&
123                     ph->ph_un.phu_val == lph->ph_un.phu_val)
124                         return EEXIST;
125         }
126
127         ph->ph_in = pfil_list_alloc();
128         ph->ph_out = pfil_list_alloc();
129         ph->ph_hashooks = 0;
130
131         LIST_INSERT_HEAD(&pfil_head_list, ph, ph_list);
132
133         return (0);
134 }
135
136 /*
137  * pfil_head_unregister() removes a pfil_head from the packet filter
138  * hook mechanism.
139  */
140 int
141 pfil_head_unregister(struct pfil_head *pfh)
142 {
143         LIST_REMOVE(pfh, ph_list);
144         return (0);
145 }
146
147 /*
148  * pfil_head_get() returns the pfil_head for a given key/dlt.
149  */
150 struct pfil_head *
151 pfil_head_get(int type, u_long val)
152 {
153         struct pfil_head *ph;
154
155         LIST_FOREACH(ph, &pfil_head_list, ph_list) {
156                 if (ph->ph_type == type && ph->ph_un.phu_val == val)
157                         break;
158         }
159         return (ph);
160 }
161
162 static void
163 pfil_add_hook_dispatch(struct netmsg *nmsg)
164 {
165         struct netmsg_pfil *pfilmsg = (struct netmsg_pfil *)nmsg;
166         pfil_func_t func = pfilmsg->pfil_func;
167         void *arg = pfilmsg->pfil_arg;
168         int flags = pfilmsg->pfil_flags;
169         struct pfil_head *ph = pfilmsg->pfil_ph;
170         const struct packet_filter_hook *pfh;
171         pfil_list_t *list_in = NULL, *list_out = NULL;
172         pfil_list_t *old_list_in = NULL, *old_list_out = NULL;
173         int err = 0;
174
175         /* This probably should not happen ... */
176         if ((flags & (PFIL_IN | PFIL_OUT)) == 0)
177                 goto reply; /* XXX panic? */
178
179         /*
180          * If pfil hooks exist on any of the requested lists,
181          * then bail out.
182          */
183         if (flags & PFIL_IN) {
184                 pfh = pfil_list_find(ph->ph_in, func, arg);
185                 if (pfh != NULL) {
186                         err = EEXIST;
187                         goto reply;
188                 }
189         }
190         if (flags & PFIL_OUT) {
191                 pfh = pfil_list_find(ph->ph_out, func, arg);
192                 if (pfh != NULL) {
193                         err = EEXIST;
194                         goto reply;
195                 }
196         }
197
198         /*
199          * Duplicate the requested lists, install new hooks
200          * on the duplication
201          */
202         if (flags & PFIL_IN) {
203                 list_in = pfil_list_alloc();
204                 pfil_list_dup(ph->ph_in, list_in, NULL);
205                 pfil_list_add(list_in, func, arg, flags & ~PFIL_OUT);
206         }
207         if (flags & PFIL_OUT) {
208                 list_out = pfil_list_alloc();
209                 pfil_list_dup(ph->ph_out, list_out, NULL);
210                 pfil_list_add(list_out, func, arg, flags & ~PFIL_IN);
211         }
212
213         /*
214          * Switch list pointers, but keep the old ones
215          */
216         if (list_in != NULL) {
217                 old_list_in = ph->ph_in;
218                 ph->ph_in = list_in;
219         }
220         if (list_out != NULL) {
221                 old_list_out = ph->ph_out;
222                 ph->ph_out = list_out;
223         }
224
225         /*
226          * Wait until everyone has finished the old lists iteration
227          */
228         netmsg_service_sync();
229         ph->ph_hashooks = 1;
230
231         /*
232          * Now it is safe to free the old lists, since no one sees it
233          */
234         if (old_list_in != NULL)
235                 pfil_list_free(old_list_in);
236         if (old_list_out != NULL)
237                 pfil_list_free(old_list_out);
238 reply:
239         lwkt_replymsg(&nmsg->nm_lmsg, err);
240 }
241
242 /*
243  * pfil_add_hook() adds a function to the packet filter hook.  the
244  * flags are:
245  *      PFIL_IN         call me on incoming packets
246  *      PFIL_OUT        call me on outgoing packets
247  *      PFIL_ALL        call me on all of the above
248  */
249 int
250 pfil_add_hook(pfil_func_t func, void *arg, int flags, struct pfil_head *ph)
251 {
252         struct netmsg_pfil pfilmsg;
253         struct netmsg *nmsg;
254
255         nmsg = &pfilmsg.pfil_nmsg;
256         netmsg_init(nmsg, &curthread->td_msgport, 0, pfil_add_hook_dispatch);
257         pfilmsg.pfil_func = func;
258         pfilmsg.pfil_arg = arg;
259         pfilmsg.pfil_flags = flags;
260         pfilmsg.pfil_ph = ph;
261
262         return lwkt_domsg(PFIL_CFGPORT, &nmsg->nm_lmsg, 0);
263 }
264
265 static void
266 pfil_remove_hook_dispatch(struct netmsg *nmsg)
267 {
268         struct netmsg_pfil *pfilmsg = (struct netmsg_pfil *)nmsg;
269         pfil_func_t func = pfilmsg->pfil_func;
270         void *arg = pfilmsg->pfil_arg;
271         int flags = pfilmsg->pfil_flags;
272         struct pfil_head *ph = pfilmsg->pfil_ph;
273         struct packet_filter_hook *skip_in = NULL, *skip_out = NULL;
274         pfil_list_t *list_in = NULL, *list_out = NULL;
275         pfil_list_t *old_list_in = NULL, *old_list_out = NULL;
276         int err = 0;
277
278         /* This probably should not happen ... */
279         if ((flags & (PFIL_IN | PFIL_OUT)) == 0)
280                 goto reply; /* XXX panic? */
281
282         /*
283          * The pfil hook should exist on all requested lists,
284          * if not just bail out
285          */
286         if (flags & PFIL_IN) {
287                 skip_in = pfil_list_find(ph->ph_in, func, arg);
288                 if (!skip_in) {
289                         err = ENOENT;
290                         goto reply;
291                 }
292         }
293         if (flags & PFIL_OUT) {
294                 skip_out = pfil_list_find(ph->ph_out, func, arg);
295                 if (!skip_out) {
296                         err = ENOENT;
297                         goto reply;
298                 }
299         }
300
301         /*
302          * Duplicate the requested lists, but the pfil hook to
303          * be deleted is not copied
304          */
305         if (flags & PFIL_IN) {
306                 KKASSERT(skip_in != NULL);
307                 list_in = pfil_list_alloc();
308                 pfil_list_dup(ph->ph_in, list_in, skip_in);
309         }
310         if (flags & PFIL_OUT) {
311                 KKASSERT(skip_out != NULL);
312                 list_out = pfil_list_alloc();
313                 pfil_list_dup(ph->ph_out, list_out, skip_out);
314         }
315
316         /*
317          * Switch list pointers, but keep the old ones
318          */
319         if (list_in != NULL) {
320                 old_list_in = ph->ph_in;
321                 ph->ph_in = list_in;
322         }
323         if (list_out != NULL) {
324                 old_list_out = ph->ph_out;
325                 ph->ph_out = list_out;
326         }
327
328         /*
329          * Wait until everyone has finished the old lists iteration
330          */
331         if (TAILQ_EMPTY(ph->ph_in) && TAILQ_EMPTY(ph->ph_out))
332                 ph->ph_hashooks = 0;
333         netmsg_service_sync();
334
335         /*
336          * Now it is safe to free the old lists, since no one sees it
337          */
338         if (old_list_in != NULL)
339                 pfil_list_free(old_list_in);
340         if (old_list_out != NULL)
341                 pfil_list_free(old_list_out);
342 reply:
343         lwkt_replymsg(&nmsg->nm_lmsg, err);
344 }
345
346 /*
347  * pfil_remove_hook removes a specific function from the packet filter
348  * hook list.
349  */
350 int
351 pfil_remove_hook(pfil_func_t func, void *arg, int flags, struct pfil_head *ph)
352 {
353         struct netmsg_pfil pfilmsg;
354         struct netmsg *nmsg;
355
356         nmsg = &pfilmsg.pfil_nmsg;
357         netmsg_init(nmsg, &curthread->td_msgport, 0, pfil_remove_hook_dispatch);
358         pfilmsg.pfil_func = func;
359         pfilmsg.pfil_arg = arg;
360         pfilmsg.pfil_flags = flags;
361         pfilmsg.pfil_ph = ph;
362
363         return lwkt_domsg(PFIL_CFGPORT, &nmsg->nm_lmsg, 0);
364 }
365
366 static void
367 pfil_list_add(pfil_list_t *list, pfil_func_t func, void *arg, int flags)
368 {
369         struct packet_filter_hook *pfh;
370
371         KKASSERT(&curthread->td_msgport == PFIL_CFGPORT);
372
373         pfh = kmalloc(sizeof(*pfh), M_IFADDR, M_WAITOK);
374
375         pfh->pfil_func = func;
376         pfh->pfil_arg  = arg;
377         pfh->pfil_flags = flags;
378
379         /*
380          * Insert the input list in reverse order of the output list
381          * so that the same path is followed in or out of the kernel.
382          */
383         if (flags & PFIL_IN)
384                 TAILQ_INSERT_HEAD(list, pfh, pfil_link);
385         else
386                 TAILQ_INSERT_TAIL(list, pfh, pfil_link);
387 }
388
389 static void
390 pfil_list_dup(const pfil_list_t *from, pfil_list_t *to,
391               const struct packet_filter_hook *skip)
392 {
393         struct packet_filter_hook *pfh_to, *pfh_from;
394
395         KKASSERT(&curthread->td_msgport == PFIL_CFGPORT);
396         KKASSERT(TAILQ_EMPTY(to));
397
398         TAILQ_FOREACH(pfh_from, from, pfil_link) {
399                 if (pfh_from == skip)
400                         continue;
401
402                 pfh_to = kmalloc(sizeof(*pfh_to), M_IFADDR, M_WAITOK);
403                 bcopy(pfh_from, pfh_to, sizeof(*pfh_to));
404
405                 TAILQ_INSERT_TAIL(to, pfh_to, pfil_link);
406         }
407 }
408
409 static pfil_list_t *
410 pfil_list_alloc(void)
411 {
412         pfil_list_t *list;
413
414         list = kmalloc(sizeof(*list), M_IFADDR, M_WAITOK);
415         TAILQ_INIT(list);
416         return list;
417 }
418
419 static void
420 pfil_list_free(pfil_list_t *list)
421 {
422         struct packet_filter_hook *pfh;
423
424         while ((pfh = TAILQ_FIRST(list)) != NULL) {
425                 TAILQ_REMOVE(list, pfh, pfil_link);
426                 kfree(pfh, M_IFADDR);
427         }
428         kfree(list, M_IFADDR);
429 }
430
431 static struct packet_filter_hook *
432 pfil_list_find(const pfil_list_t *list, pfil_func_t func, const void *arg)
433 {
434         struct packet_filter_hook *pfh;
435
436         KKASSERT(&curthread->td_msgport == PFIL_CFGPORT);
437
438         TAILQ_FOREACH(pfh, list, pfil_link) {
439                 if (pfh->pfil_func == func && pfh->pfil_arg == arg)
440                         return pfh;
441         }
442         return NULL;
443 }