xref: /freebsd/sys/netgraph/ng_bpf.c (revision c68159a6d8eede11766cf13896d0f7670dbd51aa)
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$
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_shutdown;
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_info ng_bpf_insn_type_info = {
96     {
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 };
104 static const struct ng_parse_type ng_bpf_insn_type = {
105 	&ng_parse_struct_type,
106 	&ng_bpf_insn_type_info
107 };
108 
109 /* Parse type for the field 'bpf_prog' in struct ng_bpf_hookprog */
110 static int
111 ng_bpf_hookprogary_getLength(const struct ng_parse_type *type,
112 	const u_char *start, const u_char *buf)
113 {
114 	const struct ng_bpf_hookprog *hp;
115 
116 	hp = (const struct ng_bpf_hookprog *)
117 	    (buf - OFFSETOF(struct ng_bpf_hookprog, bpf_prog));
118 	return hp->bpf_prog_len;
119 }
120 
121 static const struct ng_parse_array_info ng_bpf_hookprogary_info = {
122 	&ng_bpf_insn_type,
123 	&ng_bpf_hookprogary_getLength,
124 	NULL
125 };
126 static const struct ng_parse_type ng_bpf_hookprogary_type = {
127 	&ng_parse_array_type,
128 	&ng_bpf_hookprogary_info
129 };
130 
131 /* Parse type for struct ng_bpf_hookprog */
132 static const struct ng_parse_struct_info ng_bpf_hookprog_type_info
133 	= NG_BPF_HOOKPROG_TYPE_INFO(&ng_bpf_hookprogary_type);
134 static const struct ng_parse_type ng_bpf_hookprog_type = {
135 	&ng_parse_struct_type,
136 	&ng_bpf_hookprog_type_info
137 };
138 
139 /* Parse type for struct ng_bpf_hookstat */
140 static const struct ng_parse_struct_info
141 	ng_bpf_hookstat_type_info = NG_BPF_HOOKSTAT_TYPE_INFO;
142 static const struct ng_parse_type ng_bpf_hookstat_type = {
143 	&ng_parse_struct_type,
144 	&ng_bpf_hookstat_type_info
145 };
146 
147 /* List of commands and how to convert arguments to/from ASCII */
148 static const struct ng_cmdlist ng_bpf_cmdlist[] = {
149 	{
150 	  NGM_BPF_COOKIE,
151 	  NGM_BPF_SET_PROGRAM,
152 	  "setprogram",
153 	  &ng_bpf_hookprog_type,
154 	  NULL
155 	},
156 	{
157 	  NGM_BPF_COOKIE,
158 	  NGM_BPF_GET_PROGRAM,
159 	  "getprogram",
160 	  &ng_parse_hookbuf_type,
161 	  &ng_bpf_hookprog_type
162 	},
163 	{
164 	  NGM_BPF_COOKIE,
165 	  NGM_BPF_GET_STATS,
166 	  "getstats",
167 	  &ng_parse_hookbuf_type,
168 	  &ng_bpf_hookstat_type
169 	},
170 	{
171 	  NGM_BPF_COOKIE,
172 	  NGM_BPF_CLR_STATS,
173 	  "clrstats",
174 	  &ng_parse_hookbuf_type,
175 	  NULL
176 	},
177 	{
178 	  NGM_BPF_COOKIE,
179 	  NGM_BPF_GETCLR_STATS,
180 	  "getclrstats",
181 	  &ng_parse_hookbuf_type,
182 	  &ng_bpf_hookstat_type
183 	},
184 	{ 0 }
185 };
186 
187 /* Netgraph type descriptor */
188 static struct ng_type typestruct = {
189 	NG_ABI_VERSION,
190 	NG_BPF_NODE_TYPE,
191 	NULL,
192 	ng_bpf_constructor,
193 	ng_bpf_rcvmsg,
194 	ng_bpf_shutdown,
195 	ng_bpf_newhook,
196 	NULL,
197 	NULL,
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  * We go via the hooks.
218  */
219 static int
220 ng_bpf_constructor(node_p node)
221 {
222 	node->private = NULL;
223 	return (0);
224 }
225 
226 /*
227  * Add a hook
228  */
229 static int
230 ng_bpf_newhook(node_p node, hook_p hook, const char *name)
231 {
232 	hinfo_p hip;
233 	int error;
234 
235 	/* Create hook private structure */
236 	MALLOC(hip, hinfo_p, sizeof(*hip), M_NETGRAPH, M_NOWAIT | M_ZERO);
237 	if (hip == NULL)
238 		return (ENOMEM);
239 	hip->hook = hook;
240 	hook->private = hip;
241 	hip->node = node;
242 
243 	/* Attach the default BPF program */
244 	if ((error = ng_bpf_setprog(hook, &ng_bpf_default_prog)) != 0) {
245 		FREE(hip, M_NETGRAPH);
246 		hook->private = NULL;
247 		return (error);
248 	}
249 
250 	/* Set hook name */
251 	strncpy(hip->prog->thisHook, name, sizeof(hip->prog->thisHook) - 1);
252 	hip->prog->thisHook[sizeof(hip->prog->thisHook) - 1] = '\0';
253 	return (0);
254 }
255 
256 /*
257  * Receive a control message
258  */
259 static int
260 ng_bpf_rcvmsg(node_p node, item_p item, hook_p lasthook)
261 {
262 	struct ng_mesg *msg;
263 	struct ng_mesg *resp = NULL;
264 	int error = 0;
265 
266 	NGI_GET_MSG(item, msg);
267 	switch (msg->header.typecookie) {
268 	case NGM_BPF_COOKIE:
269 		switch (msg->header.cmd) {
270 		case NGM_BPF_SET_PROGRAM:
271 		    {
272 			struct ng_bpf_hookprog *const
273 			    hp = (struct ng_bpf_hookprog *)msg->data;
274 			hook_p hook;
275 
276 			/* Sanity check */
277 			if (msg->header.arglen < sizeof(*hp)
278 			    || msg->header.arglen
279 			      != NG_BPF_HOOKPROG_SIZE(hp->bpf_prog_len))
280 				ERROUT(EINVAL);
281 
282 			/* Find hook */
283 			if ((hook = ng_findhook(node, hp->thisHook)) == NULL)
284 				ERROUT(ENOENT);
285 
286 			/* Set new program */
287 			if ((error = ng_bpf_setprog(hook, hp)) != 0)
288 				ERROUT(error);
289 			break;
290 		    }
291 
292 		case NGM_BPF_GET_PROGRAM:
293 		    {
294 			struct ng_bpf_hookprog *hp;
295 			hook_p hook;
296 
297 			/* Sanity check */
298 			if (msg->header.arglen == 0)
299 				ERROUT(EINVAL);
300 			msg->data[msg->header.arglen - 1] = '\0';
301 
302 			/* Find hook */
303 			if ((hook = ng_findhook(node, msg->data)) == NULL)
304 				ERROUT(ENOENT);
305 
306 			/* Build response */
307 			hp = ((hinfo_p)hook->private)->prog;
308 			NG_MKRESPONSE(resp, msg,
309 			    NG_BPF_HOOKPROG_SIZE(hp->bpf_prog_len), M_NOWAIT);
310 			if (resp == NULL)
311 				ERROUT(ENOMEM);
312 			bcopy(hp, resp->data,
313 			   NG_BPF_HOOKPROG_SIZE(hp->bpf_prog_len));
314 			break;
315 		    }
316 
317 		case NGM_BPF_GET_STATS:
318 		case NGM_BPF_CLR_STATS:
319 		case NGM_BPF_GETCLR_STATS:
320 		    {
321 			struct ng_bpf_hookstat *stats;
322 			hook_p hook;
323 
324 			/* Sanity check */
325 			if (msg->header.arglen == 0)
326 				ERROUT(EINVAL);
327 			msg->data[msg->header.arglen - 1] = '\0';
328 
329 			/* Find hook */
330 			if ((hook = ng_findhook(node, msg->data)) == NULL)
331 				ERROUT(ENOENT);
332 			stats = &((hinfo_p)hook->private)->stats;
333 
334 			/* Build response (if desired) */
335 			if (msg->header.cmd != NGM_BPF_CLR_STATS) {
336 				NG_MKRESPONSE(resp,
337 				    msg, sizeof(*stats), M_NOWAIT);
338 				if (resp == NULL)
339 					ERROUT(ENOMEM);
340 				bcopy(stats, resp->data, sizeof(*stats));
341 			}
342 
343 			/* Clear stats (if desired) */
344 			if (msg->header.cmd != NGM_BPF_GET_STATS)
345 				bzero(stats, sizeof(*stats));
346 			break;
347 		    }
348 
349 		default:
350 			error = EINVAL;
351 			break;
352 		}
353 		break;
354 	default:
355 		error = EINVAL;
356 		break;
357 	}
358 	NG_RESPOND_MSG(error, node, item, resp);
359 done:
360 	if (item)
361 		NG_FREE_ITEM(item);
362 	NG_FREE_MSG(msg);
363 	return (error);
364 }
365 
366 /*
367  * Receive data on a hook
368  *
369  * Apply the filter, and then drop or forward packet as appropriate.
370  */
371 static int
372 ng_bpf_rcvdata(hook_p hook, item_p item)
373 {
374 	const hinfo_p hip = hook->private;
375 	int totlen;
376 	int needfree = 0, error = 0;
377 	u_char *data, buf[256];
378 	hinfo_p dhip;
379 	hook_p dest;
380 	u_int len;
381 	struct mbuf *m;
382 
383 	m = NGI_M(item);	/* 'item' still owns it.. we are peeking */
384 	totlen = m->m_pkthdr.len;
385 	/* Update stats on incoming hook. XXX Can we do 64 bits atomically? */
386 	/* atomic_add_int64(&hip->stats.recvFrames, 1); */
387 	/* atomic_add_int64(&hip->stats.recvOctets, totlen); */
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_ITEM(item);
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 		/* XXX atomically? */
416 		hip->stats.recvMatchFrames++;
417 		hip->stats.recvMatchOctets += totlen;
418 
419 		/* Truncate packet length if required by the filter */
420 		/* Assume this never changes m */
421 		if (len < totlen) {
422 			m_adj(m, -(totlen - len));
423 			totlen -= len;
424 		}
425 		dest = ng_findhook(hip->node, hip->prog->ifMatch);
426 	} else
427 		dest = ng_findhook(hip->node, hip->prog->ifNotMatch);
428 	if (dest == NULL) {
429 		NG_FREE_ITEM(item);
430 		return (0);
431 	}
432 
433 	/* Deliver frame out destination hook */
434 	dhip = (hinfo_p)dest->private;
435 	dhip->stats.xmitOctets += totlen;
436 	dhip->stats.xmitFrames++;
437 	NG_FWD_DATA(error, item, dest);
438 	return (error);
439 }
440 
441 /*
442  * Shutdown processing
443  */
444 static int
445 ng_bpf_shutdown(node_p node)
446 {
447 	node->flags |= NG_INVALID;
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 	&& ((hook->node->flags && NG_INVALID) == 0)) {
467 		ng_rmnode_self(hook->node);
468 	}
469 	return (0);
470 }
471 
472 /************************************************************************
473 			HELPER STUFF
474  ************************************************************************/
475 
476 /*
477  * Set the BPF program associated with a hook
478  */
479 static int
480 ng_bpf_setprog(hook_p hook, const struct ng_bpf_hookprog *hp0)
481 {
482 	const hinfo_p hip = hook->private;
483 	struct ng_bpf_hookprog *hp;
484 	int size;
485 
486 	/* Check program for validity */
487 	if (!bpf_validate(hp0->bpf_prog, hp0->bpf_prog_len))
488 		return (EINVAL);
489 
490 	/* Make a copy of the program */
491 	size = NG_BPF_HOOKPROG_SIZE(hp0->bpf_prog_len);
492 	MALLOC(hp, struct ng_bpf_hookprog *, size, M_NETGRAPH, M_NOWAIT);
493 	if (hp == NULL)
494 		return (ENOMEM);
495 	bcopy(hp0, hp, size);
496 
497 	/* Free previous program, if any, and assign new one */
498 	if (hip->prog != NULL)
499 		FREE(hip->prog, M_NETGRAPH);
500 	hip->prog = hp;
501 	return (0);
502 }
503 
504