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$ 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 "opt_bpf.h" 58 59 #include <sys/param.h> 60 #include <sys/systm.h> 61 #include <sys/errno.h> 62 #include <sys/kernel.h> 63 #include <sys/malloc.h> 64 #include <sys/mbuf.h> 65 66 #include <net/bpf.h> 67 #ifdef BPF_JITTER 68 #include <net/bpf_jitter.h> 69 #endif 70 71 #include <netgraph/ng_message.h> 72 #include <netgraph/netgraph.h> 73 #include <netgraph/ng_parse.h> 74 #include <netgraph/ng_bpf.h> 75 76 #ifdef NG_SEPARATE_MALLOC 77 MALLOC_DEFINE(M_NETGRAPH_BPF, "netgraph_bpf", "netgraph bpf node "); 78 #else 79 #define M_NETGRAPH_BPF M_NETGRAPH 80 #endif 81 82 #define OFFSETOF(s, e) ((char *)&((s *)0)->e - (char *)((s *)0)) 83 84 #define ERROUT(x) do { error = (x); goto done; } while (0) 85 86 /* Per hook private info */ 87 struct ng_bpf_hookinfo { 88 node_p node; 89 hook_p hook; 90 struct ng_bpf_hookprog *prog; 91 #ifdef BPF_JITTER 92 bpf_jit_filter *jit_prog; 93 #endif 94 struct ng_bpf_hookstat stats; 95 }; 96 typedef struct ng_bpf_hookinfo *hinfo_p; 97 98 /* Netgraph methods */ 99 static ng_constructor_t ng_bpf_constructor; 100 static ng_rcvmsg_t ng_bpf_rcvmsg; 101 static ng_shutdown_t ng_bpf_shutdown; 102 static ng_newhook_t ng_bpf_newhook; 103 static ng_rcvdata_t ng_bpf_rcvdata; 104 static ng_disconnect_t ng_bpf_disconnect; 105 106 /* Internal helper functions */ 107 static int ng_bpf_setprog(hook_p hook, const struct ng_bpf_hookprog *hp); 108 109 /* Parse type for one struct bfp_insn */ 110 static const struct ng_parse_struct_field ng_bpf_insn_type_fields[] = { 111 { "code", &ng_parse_hint16_type }, 112 { "jt", &ng_parse_uint8_type }, 113 { "jf", &ng_parse_uint8_type }, 114 { "k", &ng_parse_uint32_type }, 115 { NULL } 116 }; 117 static const struct ng_parse_type ng_bpf_insn_type = { 118 &ng_parse_struct_type, 119 &ng_bpf_insn_type_fields 120 }; 121 122 /* Parse type for the field 'bpf_prog' in struct ng_bpf_hookprog */ 123 static int 124 ng_bpf_hookprogary_getLength(const struct ng_parse_type *type, 125 const u_char *start, const u_char *buf) 126 { 127 const struct ng_bpf_hookprog *hp; 128 129 hp = (const struct ng_bpf_hookprog *) 130 (buf - OFFSETOF(struct ng_bpf_hookprog, bpf_prog)); 131 return hp->bpf_prog_len; 132 } 133 134 static const struct ng_parse_array_info ng_bpf_hookprogary_info = { 135 &ng_bpf_insn_type, 136 &ng_bpf_hookprogary_getLength, 137 NULL 138 }; 139 static const struct ng_parse_type ng_bpf_hookprogary_type = { 140 &ng_parse_array_type, 141 &ng_bpf_hookprogary_info 142 }; 143 144 /* Parse type for struct ng_bpf_hookprog */ 145 static const struct ng_parse_struct_field ng_bpf_hookprog_type_fields[] 146 = NG_BPF_HOOKPROG_TYPE_INFO(&ng_bpf_hookprogary_type); 147 static const struct ng_parse_type ng_bpf_hookprog_type = { 148 &ng_parse_struct_type, 149 &ng_bpf_hookprog_type_fields 150 }; 151 152 /* Parse type for struct ng_bpf_hookstat */ 153 static const struct ng_parse_struct_field ng_bpf_hookstat_type_fields[] 154 = NG_BPF_HOOKSTAT_TYPE_INFO; 155 static const struct ng_parse_type ng_bpf_hookstat_type = { 156 &ng_parse_struct_type, 157 &ng_bpf_hookstat_type_fields 158 }; 159 160 /* List of commands and how to convert arguments to/from ASCII */ 161 static const struct ng_cmdlist ng_bpf_cmdlist[] = { 162 { 163 NGM_BPF_COOKIE, 164 NGM_BPF_SET_PROGRAM, 165 "setprogram", 166 &ng_bpf_hookprog_type, 167 NULL 168 }, 169 { 170 NGM_BPF_COOKIE, 171 NGM_BPF_GET_PROGRAM, 172 "getprogram", 173 &ng_parse_hookbuf_type, 174 &ng_bpf_hookprog_type 175 }, 176 { 177 NGM_BPF_COOKIE, 178 NGM_BPF_GET_STATS, 179 "getstats", 180 &ng_parse_hookbuf_type, 181 &ng_bpf_hookstat_type 182 }, 183 { 184 NGM_BPF_COOKIE, 185 NGM_BPF_CLR_STATS, 186 "clrstats", 187 &ng_parse_hookbuf_type, 188 NULL 189 }, 190 { 191 NGM_BPF_COOKIE, 192 NGM_BPF_GETCLR_STATS, 193 "getclrstats", 194 &ng_parse_hookbuf_type, 195 &ng_bpf_hookstat_type 196 }, 197 { 0 } 198 }; 199 200 /* Netgraph type descriptor */ 201 static struct ng_type typestruct = { 202 .version = NG_ABI_VERSION, 203 .name = NG_BPF_NODE_TYPE, 204 .constructor = ng_bpf_constructor, 205 .rcvmsg = ng_bpf_rcvmsg, 206 .shutdown = ng_bpf_shutdown, 207 .newhook = ng_bpf_newhook, 208 .rcvdata = ng_bpf_rcvdata, 209 .disconnect = ng_bpf_disconnect, 210 .cmdlist = ng_bpf_cmdlist, 211 }; 212 NETGRAPH_INIT(bpf, &typestruct); 213 214 /* Default BPF program for a hook that matches nothing */ 215 static const struct ng_bpf_hookprog ng_bpf_default_prog = { 216 { '\0' }, /* to be filled in at hook creation time */ 217 { '\0' }, 218 { '\0' }, 219 1, 220 { BPF_STMT(BPF_RET+BPF_K, 0) } 221 }; 222 223 /* 224 * Node constructor 225 * 226 * We don't keep any per-node private data 227 * We go via the hooks. 228 */ 229 static int 230 ng_bpf_constructor(node_p node) 231 { 232 NG_NODE_SET_PRIVATE(node, NULL); 233 return (0); 234 } 235 236 /* 237 * Add a hook 238 */ 239 static int 240 ng_bpf_newhook(node_p node, hook_p hook, const char *name) 241 { 242 hinfo_p hip; 243 int error; 244 245 /* Create hook private structure */ 246 MALLOC(hip, hinfo_p, sizeof(*hip), M_NETGRAPH_BPF, M_NOWAIT | M_ZERO); 247 if (hip == NULL) 248 return (ENOMEM); 249 hip->hook = hook; 250 NG_HOOK_SET_PRIVATE(hook, hip); 251 hip->node = node; 252 253 /* Attach the default BPF program */ 254 if ((error = ng_bpf_setprog(hook, &ng_bpf_default_prog)) != 0) { 255 FREE(hip, M_NETGRAPH_BPF); 256 NG_HOOK_SET_PRIVATE(hook, NULL); 257 return (error); 258 } 259 260 /* Set hook name */ 261 strncpy(hip->prog->thisHook, name, sizeof(hip->prog->thisHook) - 1); 262 hip->prog->thisHook[sizeof(hip->prog->thisHook) - 1] = '\0'; 263 return (0); 264 } 265 266 /* 267 * Receive a control message 268 */ 269 static int 270 ng_bpf_rcvmsg(node_p node, item_p item, hook_p lasthook) 271 { 272 struct ng_mesg *msg; 273 struct ng_mesg *resp = NULL; 274 int error = 0; 275 276 NGI_GET_MSG(item, msg); 277 switch (msg->header.typecookie) { 278 case NGM_BPF_COOKIE: 279 switch (msg->header.cmd) { 280 case NGM_BPF_SET_PROGRAM: 281 { 282 struct ng_bpf_hookprog *const 283 hp = (struct ng_bpf_hookprog *)msg->data; 284 hook_p hook; 285 286 /* Sanity check */ 287 if (msg->header.arglen < sizeof(*hp) 288 || msg->header.arglen 289 != NG_BPF_HOOKPROG_SIZE(hp->bpf_prog_len)) 290 ERROUT(EINVAL); 291 292 /* Find hook */ 293 if ((hook = ng_findhook(node, hp->thisHook)) == NULL) 294 ERROUT(ENOENT); 295 296 /* Set new program */ 297 if ((error = ng_bpf_setprog(hook, hp)) != 0) 298 ERROUT(error); 299 break; 300 } 301 302 case NGM_BPF_GET_PROGRAM: 303 { 304 struct ng_bpf_hookprog *hp; 305 hook_p hook; 306 307 /* Sanity check */ 308 if (msg->header.arglen == 0) 309 ERROUT(EINVAL); 310 msg->data[msg->header.arglen - 1] = '\0'; 311 312 /* Find hook */ 313 if ((hook = ng_findhook(node, msg->data)) == NULL) 314 ERROUT(ENOENT); 315 316 /* Build response */ 317 hp = ((hinfo_p)NG_HOOK_PRIVATE(hook))->prog; 318 NG_MKRESPONSE(resp, msg, 319 NG_BPF_HOOKPROG_SIZE(hp->bpf_prog_len), M_NOWAIT); 320 if (resp == NULL) 321 ERROUT(ENOMEM); 322 bcopy(hp, resp->data, 323 NG_BPF_HOOKPROG_SIZE(hp->bpf_prog_len)); 324 break; 325 } 326 327 case NGM_BPF_GET_STATS: 328 case NGM_BPF_CLR_STATS: 329 case NGM_BPF_GETCLR_STATS: 330 { 331 struct ng_bpf_hookstat *stats; 332 hook_p hook; 333 334 /* Sanity check */ 335 if (msg->header.arglen == 0) 336 ERROUT(EINVAL); 337 msg->data[msg->header.arglen - 1] = '\0'; 338 339 /* Find hook */ 340 if ((hook = ng_findhook(node, msg->data)) == NULL) 341 ERROUT(ENOENT); 342 stats = &((hinfo_p)NG_HOOK_PRIVATE(hook))->stats; 343 344 /* Build response (if desired) */ 345 if (msg->header.cmd != NGM_BPF_CLR_STATS) { 346 NG_MKRESPONSE(resp, 347 msg, sizeof(*stats), M_NOWAIT); 348 if (resp == NULL) 349 ERROUT(ENOMEM); 350 bcopy(stats, resp->data, sizeof(*stats)); 351 } 352 353 /* Clear stats (if desired) */ 354 if (msg->header.cmd != NGM_BPF_GET_STATS) 355 bzero(stats, sizeof(*stats)); 356 break; 357 } 358 359 default: 360 error = EINVAL; 361 break; 362 } 363 break; 364 default: 365 error = EINVAL; 366 break; 367 } 368 done: 369 NG_RESPOND_MSG(error, node, item, resp); 370 NG_FREE_MSG(msg); 371 return (error); 372 } 373 374 /* 375 * Receive data on a hook 376 * 377 * Apply the filter, and then drop or forward packet as appropriate. 378 */ 379 static int 380 ng_bpf_rcvdata(hook_p hook, item_p item) 381 { 382 const hinfo_p hip = NG_HOOK_PRIVATE(hook); 383 int totlen; 384 int needfree = 0, error = 0; 385 u_char *data; 386 hinfo_p dhip; 387 hook_p dest; 388 u_int len; 389 struct mbuf *m; 390 391 m = NGI_M(item); /* 'item' still owns it.. we are peeking */ 392 totlen = m->m_pkthdr.len; 393 /* Update stats on incoming hook. XXX Can we do 64 bits atomically? */ 394 /* atomic_add_int64(&hip->stats.recvFrames, 1); */ 395 /* atomic_add_int64(&hip->stats.recvOctets, totlen); */ 396 hip->stats.recvFrames++; 397 hip->stats.recvOctets += totlen; 398 399 /* Need to put packet in contiguous memory for bpf */ 400 if (m->m_next != NULL) { 401 if (totlen > MHLEN) { 402 MALLOC(data, u_char *, totlen, M_NETGRAPH_BPF, M_NOWAIT); 403 if (data == NULL) { 404 NG_FREE_ITEM(item); 405 return (ENOMEM); 406 } 407 needfree = 1; 408 m_copydata(m, 0, totlen, (caddr_t)data); 409 } else { 410 NGI_M(item) = m = m_pullup(m, totlen); 411 if (m == NULL) { 412 NG_FREE_ITEM(item); 413 return (ENOBUFS); 414 } 415 data = mtod(m, u_char *); 416 } 417 } else 418 data = mtod(m, u_char *); 419 420 /* Run packet through filter */ 421 if (totlen == 0) 422 len = 0; /* don't call bpf_filter() with totlen == 0! */ 423 else { 424 #ifdef BPF_JITTER 425 if (bpf_jitter_enable != 0 && hip->jit_prog != NULL) 426 len = (*(hip->jit_prog->func))(data, totlen, totlen); 427 else 428 #endif 429 len = bpf_filter(hip->prog->bpf_prog, data, totlen, totlen); 430 } 431 if (needfree) 432 FREE(data, M_NETGRAPH_BPF); 433 434 /* See if we got a match and find destination hook */ 435 if (len > 0) { 436 437 /* Update stats */ 438 /* XXX atomically? */ 439 hip->stats.recvMatchFrames++; 440 hip->stats.recvMatchOctets += totlen; 441 442 /* Truncate packet length if required by the filter */ 443 /* Assume this never changes m */ 444 if (len < totlen) { 445 m_adj(m, -(totlen - len)); 446 totlen -= len; 447 } 448 dest = ng_findhook(hip->node, hip->prog->ifMatch); 449 } else 450 dest = ng_findhook(hip->node, hip->prog->ifNotMatch); 451 if (dest == NULL) { 452 NG_FREE_ITEM(item); 453 return (0); 454 } 455 456 /* Deliver frame out destination hook */ 457 dhip = NG_HOOK_PRIVATE(dest); 458 dhip->stats.xmitOctets += totlen; 459 dhip->stats.xmitFrames++; 460 NG_FWD_ITEM_HOOK(error, item, dest); 461 return (error); 462 } 463 464 /* 465 * Shutdown processing 466 */ 467 static int 468 ng_bpf_shutdown(node_p node) 469 { 470 NG_NODE_UNREF(node); 471 return (0); 472 } 473 474 /* 475 * Hook disconnection 476 */ 477 static int 478 ng_bpf_disconnect(hook_p hook) 479 { 480 const hinfo_p hip = NG_HOOK_PRIVATE(hook); 481 482 KASSERT(hip != NULL, ("%s: null info", __func__)); 483 FREE(hip->prog, M_NETGRAPH_BPF); 484 #ifdef BPF_JITTER 485 if (hip->jit_prog != NULL) 486 bpf_destroy_jit_filter(hip->jit_prog); 487 #endif 488 bzero(hip, sizeof(*hip)); 489 FREE(hip, M_NETGRAPH_BPF); 490 NG_HOOK_SET_PRIVATE(hook, NULL); /* for good measure */ 491 if ((NG_NODE_NUMHOOKS(NG_HOOK_NODE(hook)) == 0) 492 && (NG_NODE_IS_VALID(NG_HOOK_NODE(hook)))) { 493 ng_rmnode_self(NG_HOOK_NODE(hook)); 494 } 495 return (0); 496 } 497 498 /************************************************************************ 499 HELPER STUFF 500 ************************************************************************/ 501 502 /* 503 * Set the BPF program associated with a hook 504 */ 505 static int 506 ng_bpf_setprog(hook_p hook, const struct ng_bpf_hookprog *hp0) 507 { 508 const hinfo_p hip = NG_HOOK_PRIVATE(hook); 509 struct ng_bpf_hookprog *hp; 510 #ifdef BPF_JITTER 511 bpf_jit_filter *jit_prog; 512 #endif 513 int size; 514 515 /* Check program for validity */ 516 if (!bpf_validate(hp0->bpf_prog, hp0->bpf_prog_len)) 517 return (EINVAL); 518 519 /* Make a copy of the program */ 520 size = NG_BPF_HOOKPROG_SIZE(hp0->bpf_prog_len); 521 MALLOC(hp, struct ng_bpf_hookprog *, size, M_NETGRAPH_BPF, M_NOWAIT); 522 if (hp == NULL) 523 return (ENOMEM); 524 bcopy(hp0, hp, size); 525 #ifdef BPF_JITTER 526 jit_prog = bpf_jitter(hp->bpf_prog, hp->bpf_prog_len); 527 #endif 528 529 /* Free previous program, if any, and assign new one */ 530 if (hip->prog != NULL) 531 FREE(hip->prog, M_NETGRAPH_BPF); 532 hip->prog = hp; 533 #ifdef BPF_JITTER 534 if (hip->jit_prog != NULL) 535 bpf_destroy_jit_filter(hip->jit_prog); 536 hip->jit_prog = jit_prog; 537 #endif 538 return (0); 539 } 540