925d7c19050dc8646f33c4ffb3a4c404ba5d1a33
[dragonfly.git] / sys / netgraph7 / ng_bpf.c
1 /*
2  * ng_bpf.c
3  */
4
5 /*-
6  * Copyright (c) 1999 Whistle Communications, Inc.
7  * All rights reserved.
8  * 
9  * Subject to the following obligations and disclaimer of warranty, use and
10  * redistribution of this software, in source or object code forms, with or
11  * without modifications are expressly permitted by Whistle Communications;
12  * provided, however, that:
13  * 1. Any and all reproductions of the source or object code must include the
14  *    copyright notice above and the following disclaimer of warranties; and
15  * 2. No rights are granted, in any manner or form, to use Whistle
16  *    Communications, Inc. trademarks, including the mark "WHISTLE
17  *    COMMUNICATIONS" on advertising, endorsements, or otherwise except as
18  *    such appears in the above copyright notice or in the software.
19  * 
20  * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND
21  * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO
22  * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE,
23  * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF
24  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT.
25  * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY
26  * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS
27  * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE.
28  * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES
29  * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING
30  * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
31  * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR
32  * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY
33  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
34  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
35  * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY
36  * OF SUCH DAMAGE.
37  *
38  * Author: Archie Cobbs <archie@freebsd.org>
39  *
40  * $FreeBSD: src/sys/netgraph/ng_bpf.c,v 1.24 2008/02/04 19:26:53 mav Exp $
41  * $DragonFly: src/sys/netgraph7/ng_bpf.c,v 1.2 2008/06/26 23:05:35 dillon Exp $
42  * $Whistle: ng_bpf.c,v 1.3 1999/12/03 20:30:23 archie Exp $
43  */
44
45 /*
46  * BPF NETGRAPH NODE TYPE
47  *
48  * This node type accepts any number of hook connections.  With each hook
49  * is associated a bpf(4) filter program, and two hook names (each possibly
50  * the empty string).  Incoming packets are compared against the filter;
51  * matching packets are delivered out the first named hook (or dropped if
52  * the empty string), and non-matching packets are delivered out the second
53  * named hook (or dropped if the empty string).
54  *
55  * Each hook also keeps statistics about how many packets have matched, etc.
56  */
57
58 #include "opt_bpf.h"
59
60 #include <sys/param.h>
61 #include <sys/systm.h>
62 #include <sys/errno.h>
63 #include <sys/kernel.h>
64 #include <sys/malloc.h>
65 #include <sys/mbuf.h>
66
67 #include <net/bpf.h>
68 #ifdef BPF_JITTER
69 #include <net/bpf_jitter.h>
70 #endif
71
72 #include "ng_message.h"
73 #include "netgraph.h"
74 #include "ng_parse.h"
75 #include "ng_bpf.h"
76
77 #ifdef NG_SEPARATE_MALLOC
78 MALLOC_DEFINE(M_NETGRAPH_BPF, "netgraph_bpf", "netgraph bpf node ");
79 #else
80 #define M_NETGRAPH_BPF M_NETGRAPH
81 #endif
82
83 #define OFFSETOF(s, e) ((char *)&((s *)0)->e - (char *)((s *)0))
84
85 #define ERROUT(x)       do { error = (x); goto done; } while (0)
86
87 /* Per hook private info */
88 struct ng_bpf_hookinfo {
89         hook_p                  hook;
90         hook_p                  match;
91         hook_p                  nomatch;
92         struct ng_bpf_hookprog  *prog;
93 #ifdef BPF_JITTER
94         bpf_jit_filter          *jit_prog;
95 #endif
96         struct ng_bpf_hookstat  stats;
97 };
98 typedef struct ng_bpf_hookinfo *hinfo_p;
99
100 /* Netgraph methods */
101 static ng_constructor_t ng_bpf_constructor;
102 static ng_rcvmsg_t      ng_bpf_rcvmsg;
103 static ng_shutdown_t    ng_bpf_shutdown;
104 static ng_newhook_t     ng_bpf_newhook;
105 static ng_rcvdata_t     ng_bpf_rcvdata;
106 static ng_disconnect_t  ng_bpf_disconnect;
107
108 /* Internal helper functions */
109 static int      ng_bpf_setprog(hook_p hook, const struct ng_bpf_hookprog *hp);
110
111 /* Parse type for one struct bfp_insn */
112 static const struct ng_parse_struct_field ng_bpf_insn_type_fields[] = {
113         { "code",       &ng_parse_hint16_type   },
114         { "jt",         &ng_parse_uint8_type    },
115         { "jf",         &ng_parse_uint8_type    },
116         { "k",          &ng_parse_uint32_type   },
117         { NULL }
118 };
119 static const struct ng_parse_type ng_bpf_insn_type = {
120         &ng_parse_struct_type,
121         &ng_bpf_insn_type_fields
122 };
123
124 /* Parse type for the field 'bpf_prog' in struct ng_bpf_hookprog */
125 static int
126 ng_bpf_hookprogary_getLength(const struct ng_parse_type *type,
127         const u_char *start, const u_char *buf)
128 {
129         const struct ng_bpf_hookprog *hp;
130
131         hp = (const struct ng_bpf_hookprog *)
132             (buf - OFFSETOF(struct ng_bpf_hookprog, bpf_prog));
133         return hp->bpf_prog_len;
134 }
135
136 static const struct ng_parse_array_info ng_bpf_hookprogary_info = {
137         &ng_bpf_insn_type,
138         &ng_bpf_hookprogary_getLength,
139         NULL
140 };
141 static const struct ng_parse_type ng_bpf_hookprogary_type = {
142         &ng_parse_array_type,
143         &ng_bpf_hookprogary_info
144 };
145
146 /* Parse type for struct ng_bpf_hookprog */
147 static const struct ng_parse_struct_field ng_bpf_hookprog_type_fields[]
148         = NG_BPF_HOOKPROG_TYPE_INFO(&ng_bpf_hookprogary_type);
149 static const struct ng_parse_type ng_bpf_hookprog_type = {
150         &ng_parse_struct_type,
151         &ng_bpf_hookprog_type_fields
152 };
153
154 /* Parse type for struct ng_bpf_hookstat */
155 static const struct ng_parse_struct_field ng_bpf_hookstat_type_fields[]
156         = NG_BPF_HOOKSTAT_TYPE_INFO;
157 static const struct ng_parse_type ng_bpf_hookstat_type = {
158         &ng_parse_struct_type,
159         &ng_bpf_hookstat_type_fields
160 };
161
162 /* List of commands and how to convert arguments to/from ASCII */
163 static const struct ng_cmdlist ng_bpf_cmdlist[] = {
164         {
165           NGM_BPF_COOKIE,
166           NGM_BPF_SET_PROGRAM,
167           "setprogram",
168           &ng_bpf_hookprog_type,
169           NULL
170         },
171         {
172           NGM_BPF_COOKIE,
173           NGM_BPF_GET_PROGRAM,
174           "getprogram",
175           &ng_parse_hookbuf_type,
176           &ng_bpf_hookprog_type
177         },
178         {
179           NGM_BPF_COOKIE,
180           NGM_BPF_GET_STATS,
181           "getstats",
182           &ng_parse_hookbuf_type,
183           &ng_bpf_hookstat_type
184         },
185         {
186           NGM_BPF_COOKIE,
187           NGM_BPF_CLR_STATS,
188           "clrstats",
189           &ng_parse_hookbuf_type,
190           NULL
191         },
192         {
193           NGM_BPF_COOKIE,
194           NGM_BPF_GETCLR_STATS,
195           "getclrstats",
196           &ng_parse_hookbuf_type,
197           &ng_bpf_hookstat_type
198         },
199         { 0 }
200 };
201
202 /* Netgraph type descriptor */
203 static struct ng_type typestruct = {
204         .version =      NG_ABI_VERSION,
205         .name =         NG_BPF_NODE_TYPE,
206         .constructor =  ng_bpf_constructor,
207         .rcvmsg =       ng_bpf_rcvmsg,
208         .shutdown =     ng_bpf_shutdown,
209         .newhook =      ng_bpf_newhook,
210         .rcvdata =      ng_bpf_rcvdata,
211         .disconnect =   ng_bpf_disconnect,
212         .cmdlist =      ng_bpf_cmdlist,
213 };
214 NETGRAPH_INIT(bpf, &typestruct);
215
216 /* Default BPF program for a hook that matches nothing */
217 static const struct ng_bpf_hookprog ng_bpf_default_prog = {
218         { '\0' },               /* to be filled in at hook creation time */
219         { '\0' },
220         { '\0' },
221         1,
222         { BPF_STMT(BPF_RET+BPF_K, 0) }
223 };
224
225 /*
226  * Node constructor
227  *
228  * We don't keep any per-node private data
229  * We go via the hooks.
230  */
231 static int
232 ng_bpf_constructor(node_p node)
233 {
234         NG_NODE_SET_PRIVATE(node, NULL);
235         return (0);
236 }
237
238 /*
239  * Callback functions to be used by NG_NODE_FOREACH_HOOK() macro.
240  */
241 static int
242 ng_bpf_addrefs(hook_p hook, void* arg)
243 {
244         hinfo_p hip = NG_HOOK_PRIVATE(hook);
245         hook_p h = (hook_p)arg;
246
247         if (strcmp(hip->prog->ifMatch, NG_HOOK_NAME(h)) == 0)
248             hip->match = h;
249         if (strcmp(hip->prog->ifNotMatch, NG_HOOK_NAME(h)) == 0)
250             hip->nomatch = h;
251         return (1);
252 }
253
254 static int
255 ng_bpf_remrefs(hook_p hook, void* arg)
256 {
257         hinfo_p hip = NG_HOOK_PRIVATE(hook);
258         hook_p h = (hook_p)arg;
259
260         if (hip->match == h)
261             hip->match = NULL;
262         if (hip->nomatch == h)
263             hip->nomatch = NULL;
264         return (1);
265 }
266
267 /*
268  * Add a hook
269  */
270 static int
271 ng_bpf_newhook(node_p node, hook_p hook, const char *name)
272 {
273         hinfo_p hip;
274         hook_p tmp;
275         int error;
276
277         /* Create hook private structure */
278         MALLOC(hip, hinfo_p, sizeof(*hip), M_NETGRAPH_BPF, M_WAITOK | M_NULLOK | M_ZERO);
279         if (hip == NULL)
280                 return (ENOMEM);
281         hip->hook = hook;
282         NG_HOOK_SET_PRIVATE(hook, hip);
283
284         /* Add our reference into other hooks data. */
285         NG_NODE_FOREACH_HOOK(node, ng_bpf_addrefs, hook, tmp);
286
287         /* Attach the default BPF program */
288         if ((error = ng_bpf_setprog(hook, &ng_bpf_default_prog)) != 0) {
289                 FREE(hip, M_NETGRAPH_BPF);
290                 NG_HOOK_SET_PRIVATE(hook, NULL);
291                 return (error);
292         }
293
294         /* Set hook name */
295         strlcpy(hip->prog->thisHook, name, sizeof(hip->prog->thisHook));
296         return (0);
297 }
298
299 /*
300  * Receive a control message
301  */
302 static int
303 ng_bpf_rcvmsg(node_p node, item_p item, hook_p lasthook)
304 {
305         struct ng_mesg *msg;
306         struct ng_mesg *resp = NULL;
307         int error = 0;
308
309         NGI_GET_MSG(item, msg);
310         switch (msg->header.typecookie) {
311         case NGM_BPF_COOKIE:
312                 switch (msg->header.cmd) {
313                 case NGM_BPF_SET_PROGRAM:
314                     {
315                         struct ng_bpf_hookprog *const
316                             hp = (struct ng_bpf_hookprog *)msg->data;
317                         hook_p hook;
318
319                         /* Sanity check */
320                         if (msg->header.arglen < sizeof(*hp)
321                             || msg->header.arglen
322                               != NG_BPF_HOOKPROG_SIZE(hp->bpf_prog_len))
323                                 ERROUT(EINVAL);
324
325                         /* Find hook */
326                         if ((hook = ng_findhook(node, hp->thisHook)) == NULL)
327                                 ERROUT(ENOENT);
328
329                         /* Set new program */
330                         if ((error = ng_bpf_setprog(hook, hp)) != 0)
331                                 ERROUT(error);
332                         break;
333                     }
334
335                 case NGM_BPF_GET_PROGRAM:
336                     {
337                         struct ng_bpf_hookprog *hp;
338                         hook_p hook;
339
340                         /* Sanity check */
341                         if (msg->header.arglen == 0)
342                                 ERROUT(EINVAL);
343                         msg->data[msg->header.arglen - 1] = '\0';
344
345                         /* Find hook */
346                         if ((hook = ng_findhook(node, msg->data)) == NULL)
347                                 ERROUT(ENOENT);
348
349                         /* Build response */
350                         hp = ((hinfo_p)NG_HOOK_PRIVATE(hook))->prog;
351                         NG_MKRESPONSE(resp, msg,
352                             NG_BPF_HOOKPROG_SIZE(hp->bpf_prog_len), M_WAITOK | M_NULLOK);
353                         if (resp == NULL)
354                                 ERROUT(ENOMEM);
355                         bcopy(hp, resp->data,
356                            NG_BPF_HOOKPROG_SIZE(hp->bpf_prog_len));
357                         break;
358                     }
359
360                 case NGM_BPF_GET_STATS:
361                 case NGM_BPF_CLR_STATS:
362                 case NGM_BPF_GETCLR_STATS:
363                     {
364                         struct ng_bpf_hookstat *stats;
365                         hook_p hook;
366
367                         /* Sanity check */
368                         if (msg->header.arglen == 0)
369                                 ERROUT(EINVAL);
370                         msg->data[msg->header.arglen - 1] = '\0';
371
372                         /* Find hook */
373                         if ((hook = ng_findhook(node, msg->data)) == NULL)
374                                 ERROUT(ENOENT);
375                         stats = &((hinfo_p)NG_HOOK_PRIVATE(hook))->stats;
376
377                         /* Build response (if desired) */
378                         if (msg->header.cmd != NGM_BPF_CLR_STATS) {
379                                 NG_MKRESPONSE(resp,
380                                     msg, sizeof(*stats), M_WAITOK | M_NULLOK);
381                                 if (resp == NULL)
382                                         ERROUT(ENOMEM);
383                                 bcopy(stats, resp->data, sizeof(*stats));
384                         }
385
386                         /* Clear stats (if desired) */
387                         if (msg->header.cmd != NGM_BPF_GET_STATS)
388                                 bzero(stats, sizeof(*stats));
389                         break;
390                     }
391
392                 default:
393                         error = EINVAL;
394                         break;
395                 }
396                 break;
397         default:
398                 error = EINVAL;
399                 break;
400         }
401 done:
402         NG_RESPOND_MSG(error, node, item, resp);
403         NG_FREE_MSG(msg);
404         return (error);
405 }
406
407 /*
408  * Receive data on a hook
409  *
410  * Apply the filter, and then drop or forward packet as appropriate.
411  */
412 static int
413 ng_bpf_rcvdata(hook_p hook, item_p item)
414 {
415         const hinfo_p hip = NG_HOOK_PRIVATE(hook);
416         int totlen;
417         int needfree = 0, error = 0, usejit = 0;
418         u_char *data = NULL;
419         hinfo_p dhip;
420         hook_p dest;
421         u_int len;
422         struct mbuf *m;
423
424         m = NGI_M(item);        /* 'item' still owns it.. we are peeking */ 
425         totlen = m->m_pkthdr.len;
426         /* Update stats on incoming hook. XXX Can we do 64 bits atomically? */
427         /* atomic_add_int64(&hip->stats.recvFrames, 1); */
428         /* atomic_add_int64(&hip->stats.recvOctets, totlen); */
429         hip->stats.recvFrames++; 
430         hip->stats.recvOctets += totlen;
431
432         /* Don't call bpf_filter() with totlen == 0! */
433         if (totlen == 0) {
434                 len = 0;
435                 goto ready;
436         }
437
438 #ifdef BPF_JITTER
439         if (bpf_jitter_enable != 0 && hip->jit_prog != NULL)
440                 usejit = 1;
441 #endif
442
443         /* Need to put packet in contiguous memory for bpf */
444         if (m->m_next != NULL && totlen > MHLEN) {
445                 if (usejit) {
446                         MALLOC(data, u_char *, totlen, M_NETGRAPH_BPF, M_NOWAIT);
447                         if (data == NULL) {
448                                 NG_FREE_ITEM(item);
449                                 return (ENOMEM);
450                         }
451                         needfree = 1;
452                         m_copydata(m, 0, totlen, (caddr_t)data);
453                 }
454         } else {
455                 if (m->m_next != NULL) {
456                         NGI_M(item) = m = m_pullup(m, totlen);
457                         if (m == NULL) {
458                                 NG_FREE_ITEM(item);
459                                 return (ENOBUFS);
460                         }
461                 }
462                 data = mtod(m, u_char *);
463         }
464
465         /* Run packet through filter */
466 #ifdef BPF_JITTER
467         if (usejit)
468                 len = (*(hip->jit_prog->func))(data, totlen, totlen);
469         else
470 #endif
471         if (data)
472                 len = bpf_filter(hip->prog->bpf_prog, data, totlen, totlen);
473         else
474                 len = bpf_filter(hip->prog->bpf_prog, (u_char *)m, totlen, 0);
475         if (needfree)
476                 FREE(data, M_NETGRAPH_BPF);
477 ready:
478         /* See if we got a match and find destination hook */
479         if (len > 0) {
480
481                 /* Update stats */
482                 /* XXX atomically? */
483                 hip->stats.recvMatchFrames++;
484                 hip->stats.recvMatchOctets += totlen;
485
486                 /* Truncate packet length if required by the filter */
487                 /* Assume this never changes m */
488                 if (len < totlen) {
489                         m_adj(m, -(totlen - len));
490                         totlen = len;
491                 }
492                 dest = hip->match;
493         } else
494                 dest = hip->nomatch;
495         if (dest == NULL) {
496                 NG_FREE_ITEM(item);
497                 return (0);
498         }
499
500         /* Deliver frame out destination hook */
501         dhip = NG_HOOK_PRIVATE(dest);
502         dhip->stats.xmitOctets += totlen;
503         dhip->stats.xmitFrames++;
504         NG_FWD_ITEM_HOOK(error, item, dest);
505         return (error);
506 }
507
508 /*
509  * Shutdown processing
510  */
511 static int
512 ng_bpf_shutdown(node_p node)
513 {
514         NG_NODE_UNREF(node);
515         return (0);
516 }
517
518 /*
519  * Hook disconnection
520  */
521 static int
522 ng_bpf_disconnect(hook_p hook)
523 {
524         const node_p node = NG_HOOK_NODE(hook);
525         const hinfo_p hip = NG_HOOK_PRIVATE(hook);
526         hook_p tmp;
527
528         KASSERT(hip != NULL, ("%s: null info", __func__));
529
530         /* Remove our reference from other hooks data. */
531         NG_NODE_FOREACH_HOOK(node, ng_bpf_remrefs, hook, tmp);
532
533         FREE(hip->prog, M_NETGRAPH_BPF);
534 #ifdef BPF_JITTER
535         if (hip->jit_prog != NULL)
536                 bpf_destroy_jit_filter(hip->jit_prog);
537 #endif
538         FREE(hip, M_NETGRAPH_BPF);
539         if ((NG_NODE_NUMHOOKS(node) == 0) &&
540             (NG_NODE_IS_VALID(node))) {
541                 ng_rmnode_self(node);
542         }
543         return (0);
544 }
545
546 /************************************************************************
547                         HELPER STUFF
548  ************************************************************************/
549
550 /*
551  * Set the BPF program associated with a hook
552  */
553 static int
554 ng_bpf_setprog(hook_p hook, const struct ng_bpf_hookprog *hp0)
555 {
556         const hinfo_p hip = NG_HOOK_PRIVATE(hook);
557         struct ng_bpf_hookprog *hp;
558 #ifdef BPF_JITTER
559         bpf_jit_filter *jit_prog;
560 #endif
561         int size;
562
563         /* Check program for validity */
564         if (!bpf_validate(hp0->bpf_prog, hp0->bpf_prog_len))
565                 return (EINVAL);
566
567         /* Make a copy of the program */
568         size = NG_BPF_HOOKPROG_SIZE(hp0->bpf_prog_len);
569         MALLOC(hp, struct ng_bpf_hookprog *, size, M_NETGRAPH_BPF, M_NOWAIT);
570         if (hp == NULL)
571                 return (ENOMEM);
572         bcopy(hp0, hp, size);
573 #ifdef BPF_JITTER
574         jit_prog = bpf_jitter(hp->bpf_prog, hp->bpf_prog_len);
575 #endif
576
577         /* Free previous program, if any, and assign new one */
578         if (hip->prog != NULL)
579                 FREE(hip->prog, M_NETGRAPH_BPF);
580         hip->prog = hp;
581 #ifdef BPF_JITTER
582         if (hip->jit_prog != NULL)
583                 bpf_destroy_jit_filter(hip->jit_prog);
584         hip->jit_prog = jit_prog;
585 #endif
586
587         /* Prepare direct references on target hooks. */
588         hip->match = ng_findhook(NG_HOOK_NODE(hook), hip->prog->ifMatch);
589         hip->nomatch = ng_findhook(NG_HOOK_NODE(hook), hip->prog->ifNotMatch);
590         return (0);
591 }