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