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