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 * $Whistle: ng_bpf.c,v 1.3 1999/12/03 20:30:23 archie Exp $ 40 */ 41 42 /* 43 * BPF NETGRAPH NODE TYPE 44 * 45 * This node type accepts any number of hook connections. With each hook 46 * is associated a bpf(4) filter program, and two hook names (each possibly 47 * the empty string). Incoming packets are compared against the filter; 48 * matching packets are delivered out the first named hook (or dropped if 49 * the empty string), and non-matching packets are delivered out the second 50 * named hook (or dropped if the empty string). 51 * 52 * Each hook also keeps statistics about how many packets have matched, etc. 53 */ 54 55 #include "opt_bpf.h" 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 #ifdef BPF_JITTER 66 #include <net/bpf_jitter.h> 67 #endif 68 69 #include <netgraph/ng_message.h> 70 #include <netgraph/netgraph.h> 71 #include <netgraph/ng_parse.h> 72 #include <netgraph/ng_bpf.h> 73 74 #ifdef NG_SEPARATE_MALLOC 75 static MALLOC_DEFINE(M_NETGRAPH_BPF, "netgraph_bpf", "netgraph bpf node"); 76 #else 77 #define M_NETGRAPH_BPF M_NETGRAPH 78 #endif 79 80 #define OFFSETOF(s, e) ((char *)&((s *)0)->e - (char *)((s *)0)) 81 82 #define ERROUT(x) do { error = (x); goto done; } while (0) 83 84 /* Per hook private info */ 85 struct ng_bpf_hookinfo { 86 hook_p hook; 87 hook_p match; 88 hook_p nomatch; 89 struct ng_bpf_hookprog *prog; 90 #ifdef BPF_JITTER 91 bpf_jit_filter *jit_prog; 92 #endif 93 struct ng_bpf_hookstat stats; 94 }; 95 typedef struct ng_bpf_hookinfo *hinfo_p; 96 97 /* Netgraph methods */ 98 static ng_constructor_t ng_bpf_constructor; 99 static ng_rcvmsg_t ng_bpf_rcvmsg; 100 static ng_shutdown_t ng_bpf_shutdown; 101 static ng_newhook_t ng_bpf_newhook; 102 static ng_rcvdata_t ng_bpf_rcvdata; 103 static ng_disconnect_t ng_bpf_disconnect; 104 105 /* Maximum bpf program instructions */ 106 extern int bpf_maxinsns; 107 108 /* Internal helper functions */ 109 static int ng_bpf_setprog(hook_p hook, const struct ng_bpf_hookprog *hp); 110 111 /* Parse type for one struct bfp_insn */ 112 static const struct ng_parse_struct_field ng_bpf_insn_type_fields[] = { 113 { "code", &ng_parse_hint16_type }, 114 { "jt", &ng_parse_uint8_type }, 115 { "jf", &ng_parse_uint8_type }, 116 { "k", &ng_parse_uint32_type }, 117 { NULL } 118 }; 119 static const struct ng_parse_type ng_bpf_insn_type = { 120 &ng_parse_struct_type, 121 &ng_bpf_insn_type_fields 122 }; 123 124 /* Parse type for the field 'bpf_prog' in struct ng_bpf_hookprog */ 125 static int 126 ng_bpf_hookprogary_getLength(const struct ng_parse_type *type, 127 const u_char *start, const u_char *buf) 128 { 129 const struct ng_bpf_hookprog *hp; 130 131 hp = (const struct ng_bpf_hookprog *) 132 (buf - OFFSETOF(struct ng_bpf_hookprog, bpf_prog)); 133 return hp->bpf_prog_len; 134 } 135 136 static const struct ng_parse_array_info ng_bpf_hookprogary_info = { 137 &ng_bpf_insn_type, 138 &ng_bpf_hookprogary_getLength, 139 NULL 140 }; 141 static const struct ng_parse_type ng_bpf_hookprogary_type = { 142 &ng_parse_array_type, 143 &ng_bpf_hookprogary_info 144 }; 145 146 /* Parse type for struct ng_bpf_hookprog */ 147 static const struct ng_parse_struct_field ng_bpf_hookprog_type_fields[] 148 = NG_BPF_HOOKPROG_TYPE_INFO(&ng_bpf_hookprogary_type); 149 static const struct ng_parse_type ng_bpf_hookprog_type = { 150 &ng_parse_struct_type, 151 &ng_bpf_hookprog_type_fields 152 }; 153 154 /* Parse type for struct ng_bpf_hookstat */ 155 static const struct ng_parse_struct_field ng_bpf_hookstat_type_fields[] 156 = NG_BPF_HOOKSTAT_TYPE_INFO; 157 static const struct ng_parse_type ng_bpf_hookstat_type = { 158 &ng_parse_struct_type, 159 &ng_bpf_hookstat_type_fields 160 }; 161 162 /* List of commands and how to convert arguments to/from ASCII */ 163 static const struct ng_cmdlist ng_bpf_cmdlist[] = { 164 { 165 NGM_BPF_COOKIE, 166 NGM_BPF_SET_PROGRAM, 167 "setprogram", 168 &ng_bpf_hookprog_type, 169 NULL 170 }, 171 { 172 NGM_BPF_COOKIE, 173 NGM_BPF_GET_PROGRAM, 174 "getprogram", 175 &ng_parse_hookbuf_type, 176 &ng_bpf_hookprog_type 177 }, 178 { 179 NGM_BPF_COOKIE, 180 NGM_BPF_GET_STATS, 181 "getstats", 182 &ng_parse_hookbuf_type, 183 &ng_bpf_hookstat_type 184 }, 185 { 186 NGM_BPF_COOKIE, 187 NGM_BPF_CLR_STATS, 188 "clrstats", 189 &ng_parse_hookbuf_type, 190 NULL 191 }, 192 { 193 NGM_BPF_COOKIE, 194 NGM_BPF_GETCLR_STATS, 195 "getclrstats", 196 &ng_parse_hookbuf_type, 197 &ng_bpf_hookstat_type 198 }, 199 { 0 } 200 }; 201 202 /* Netgraph type descriptor */ 203 static struct ng_type typestruct = { 204 .version = NG_ABI_VERSION, 205 .name = NG_BPF_NODE_TYPE, 206 .constructor = ng_bpf_constructor, 207 .rcvmsg = ng_bpf_rcvmsg, 208 .shutdown = ng_bpf_shutdown, 209 .newhook = ng_bpf_newhook, 210 .rcvdata = ng_bpf_rcvdata, 211 .disconnect = ng_bpf_disconnect, 212 .cmdlist = ng_bpf_cmdlist, 213 }; 214 NETGRAPH_INIT(bpf, &typestruct); 215 216 /* Default BPF program for a hook that matches nothing */ 217 static const struct ng_bpf_hookprog ng_bpf_default_prog = { 218 { '\0' }, /* to be filled in at hook creation time */ 219 { '\0' }, 220 { '\0' }, 221 1, 222 { BPF_STMT(BPF_RET+BPF_K, 0) } 223 }; 224 225 /* 226 * Node constructor 227 * 228 * We don't keep any per-node private data 229 * We go via the hooks. 230 */ 231 static int 232 ng_bpf_constructor(node_p node) 233 { 234 NG_NODE_SET_PRIVATE(node, NULL); 235 return (0); 236 } 237 238 /* 239 * Callback functions to be used by NG_NODE_FOREACH_HOOK() macro. 240 */ 241 static int 242 ng_bpf_addrefs(hook_p hook, void* arg) 243 { 244 hinfo_p hip = NG_HOOK_PRIVATE(hook); 245 hook_p h = (hook_p)arg; 246 247 if (strcmp(hip->prog->ifMatch, NG_HOOK_NAME(h)) == 0) 248 hip->match = h; 249 if (strcmp(hip->prog->ifNotMatch, NG_HOOK_NAME(h)) == 0) 250 hip->nomatch = h; 251 return (1); 252 } 253 254 static int 255 ng_bpf_remrefs(hook_p hook, void* arg) 256 { 257 hinfo_p hip = NG_HOOK_PRIVATE(hook); 258 hook_p h = (hook_p)arg; 259 260 if (hip->match == h) 261 hip->match = NULL; 262 if (hip->nomatch == h) 263 hip->nomatch = NULL; 264 return (1); 265 } 266 267 /* 268 * Add a hook 269 */ 270 static int 271 ng_bpf_newhook(node_p node, hook_p hook, const char *name) 272 { 273 hinfo_p hip; 274 int error; 275 276 /* Create hook private structure */ 277 hip = malloc(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); 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 data = malloc(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 /* Update stats */ 480 /* XXX atomically? */ 481 hip->stats.recvMatchFrames++; 482 hip->stats.recvMatchOctets += totlen; 483 484 /* Truncate packet length if required by the filter */ 485 /* Assume this never changes m */ 486 if (len < totlen) { 487 m_adj(m, -(totlen - len)); 488 totlen = len; 489 } 490 dest = hip->match; 491 } else 492 dest = hip->nomatch; 493 if (dest == NULL) { 494 NG_FREE_ITEM(item); 495 return (0); 496 } 497 498 /* Deliver frame out destination hook */ 499 dhip = NG_HOOK_PRIVATE(dest); 500 dhip->stats.xmitOctets += totlen; 501 dhip->stats.xmitFrames++; 502 NG_FWD_ITEM_HOOK(error, item, dest); 503 return (error); 504 } 505 506 /* 507 * Shutdown processing 508 */ 509 static int 510 ng_bpf_shutdown(node_p node) 511 { 512 NG_NODE_UNREF(node); 513 return (0); 514 } 515 516 /* 517 * Hook disconnection 518 */ 519 static int 520 ng_bpf_disconnect(hook_p hook) 521 { 522 const node_p node = NG_HOOK_NODE(hook); 523 const hinfo_p hip = NG_HOOK_PRIVATE(hook); 524 525 KASSERT(hip != NULL, ("%s: null info", __func__)); 526 527 /* Remove our reference from other hooks data. */ 528 NG_NODE_FOREACH_HOOK(node, ng_bpf_remrefs, hook); 529 530 free(hip->prog, M_NETGRAPH_BPF); 531 #ifdef BPF_JITTER 532 if (hip->jit_prog != NULL) 533 bpf_destroy_jit_filter(hip->jit_prog); 534 #endif 535 free(hip, M_NETGRAPH_BPF); 536 if ((NG_NODE_NUMHOOKS(node) == 0) && 537 (NG_NODE_IS_VALID(node))) { 538 ng_rmnode_self(node); 539 } 540 return (0); 541 } 542 543 /************************************************************************ 544 HELPER STUFF 545 ************************************************************************/ 546 547 /* 548 * Set the BPF program associated with a hook 549 */ 550 static int 551 ng_bpf_setprog(hook_p hook, const struct ng_bpf_hookprog *hp0) 552 { 553 const hinfo_p hip = NG_HOOK_PRIVATE(hook); 554 struct ng_bpf_hookprog *hp; 555 #ifdef BPF_JITTER 556 bpf_jit_filter *jit_prog; 557 #endif 558 int size; 559 560 /* Check program for validity */ 561 if (hp0->bpf_prog_len > bpf_maxinsns || 562 !bpf_validate(hp0->bpf_prog, hp0->bpf_prog_len)) 563 return (EINVAL); 564 565 /* Make a copy of the program */ 566 size = NG_BPF_HOOKPROG_SIZE(hp0->bpf_prog_len); 567 hp = malloc(size, M_NETGRAPH_BPF, M_NOWAIT); 568 if (hp == NULL) 569 return (ENOMEM); 570 bcopy(hp0, hp, size); 571 #ifdef BPF_JITTER 572 jit_prog = bpf_jitter(hp->bpf_prog, hp->bpf_prog_len); 573 #endif 574 575 /* Free previous program, if any, and assign new one */ 576 if (hip->prog != NULL) 577 free(hip->prog, M_NETGRAPH_BPF); 578 hip->prog = hp; 579 #ifdef BPF_JITTER 580 if (hip->jit_prog != NULL) 581 bpf_destroy_jit_filter(hip->jit_prog); 582 hip->jit_prog = jit_prog; 583 #endif 584 585 /* Prepare direct references on target hooks. */ 586 hip->match = ng_findhook(NG_HOOK_NODE(hook), hip->prog->ifMatch); 587 hip->nomatch = ng_findhook(NG_HOOK_NODE(hook), hip->prog->ifNotMatch); 588 return (0); 589 } 590