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