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