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 int error; 277 278 /* Create hook private structure */ 279 hip = malloc(sizeof(*hip), M_NETGRAPH_BPF, M_NOWAIT | M_ZERO); 280 if (hip == NULL) 281 return (ENOMEM); 282 hip->hook = hook; 283 NG_HOOK_SET_PRIVATE(hook, hip); 284 285 /* Add our reference into other hooks data. */ 286 NG_NODE_FOREACH_HOOK(node, ng_bpf_addrefs, hook); 287 288 /* Attach the default BPF program */ 289 if ((error = ng_bpf_setprog(hook, &ng_bpf_default_prog)) != 0) { 290 free(hip, M_NETGRAPH_BPF); 291 NG_HOOK_SET_PRIVATE(hook, NULL); 292 return (error); 293 } 294 295 /* Set hook name */ 296 strlcpy(hip->prog->thisHook, name, sizeof(hip->prog->thisHook)); 297 return (0); 298 } 299 300 /* 301 * Receive a control message 302 */ 303 static int 304 ng_bpf_rcvmsg(node_p node, item_p item, hook_p lasthook) 305 { 306 struct ng_mesg *msg; 307 struct ng_mesg *resp = NULL; 308 int error = 0; 309 310 NGI_GET_MSG(item, msg); 311 switch (msg->header.typecookie) { 312 case NGM_BPF_COOKIE: 313 switch (msg->header.cmd) { 314 case NGM_BPF_SET_PROGRAM: 315 { 316 struct ng_bpf_hookprog *const 317 hp = (struct ng_bpf_hookprog *)msg->data; 318 hook_p hook; 319 320 /* Sanity check */ 321 if (msg->header.arglen < sizeof(*hp) 322 || msg->header.arglen 323 != NG_BPF_HOOKPROG_SIZE(hp->bpf_prog_len)) 324 ERROUT(EINVAL); 325 326 /* Find hook */ 327 if ((hook = ng_findhook(node, hp->thisHook)) == NULL) 328 ERROUT(ENOENT); 329 330 /* Set new program */ 331 if ((error = ng_bpf_setprog(hook, hp)) != 0) 332 ERROUT(error); 333 break; 334 } 335 336 case NGM_BPF_GET_PROGRAM: 337 { 338 struct ng_bpf_hookprog *hp; 339 hook_p hook; 340 341 /* Sanity check */ 342 if (msg->header.arglen == 0) 343 ERROUT(EINVAL); 344 msg->data[msg->header.arglen - 1] = '\0'; 345 346 /* Find hook */ 347 if ((hook = ng_findhook(node, msg->data)) == NULL) 348 ERROUT(ENOENT); 349 350 /* Build response */ 351 hp = ((hinfo_p)NG_HOOK_PRIVATE(hook))->prog; 352 NG_MKRESPONSE(resp, msg, 353 NG_BPF_HOOKPROG_SIZE(hp->bpf_prog_len), M_NOWAIT); 354 if (resp == NULL) 355 ERROUT(ENOMEM); 356 bcopy(hp, resp->data, 357 NG_BPF_HOOKPROG_SIZE(hp->bpf_prog_len)); 358 break; 359 } 360 361 case NGM_BPF_GET_STATS: 362 case NGM_BPF_CLR_STATS: 363 case NGM_BPF_GETCLR_STATS: 364 { 365 struct ng_bpf_hookstat *stats; 366 hook_p hook; 367 368 /* Sanity check */ 369 if (msg->header.arglen == 0) 370 ERROUT(EINVAL); 371 msg->data[msg->header.arglen - 1] = '\0'; 372 373 /* Find hook */ 374 if ((hook = ng_findhook(node, msg->data)) == NULL) 375 ERROUT(ENOENT); 376 stats = &((hinfo_p)NG_HOOK_PRIVATE(hook))->stats; 377 378 /* Build response (if desired) */ 379 if (msg->header.cmd != NGM_BPF_CLR_STATS) { 380 NG_MKRESPONSE(resp, 381 msg, sizeof(*stats), M_NOWAIT); 382 if (resp == NULL) 383 ERROUT(ENOMEM); 384 bcopy(stats, resp->data, sizeof(*stats)); 385 } 386 387 /* Clear stats (if desired) */ 388 if (msg->header.cmd != NGM_BPF_GET_STATS) 389 bzero(stats, sizeof(*stats)); 390 break; 391 } 392 393 default: 394 error = EINVAL; 395 break; 396 } 397 break; 398 default: 399 error = EINVAL; 400 break; 401 } 402 done: 403 NG_RESPOND_MSG(error, node, item, resp); 404 NG_FREE_MSG(msg); 405 return (error); 406 } 407 408 /* 409 * Receive data on a hook 410 * 411 * Apply the filter, and then drop or forward packet as appropriate. 412 */ 413 static int 414 ng_bpf_rcvdata(hook_p hook, item_p item) 415 { 416 const hinfo_p hip = NG_HOOK_PRIVATE(hook); 417 int totlen; 418 int needfree = 0, error = 0, usejit = 0; 419 u_char *data = NULL; 420 hinfo_p dhip; 421 hook_p dest; 422 u_int len; 423 struct mbuf *m; 424 425 m = NGI_M(item); /* 'item' still owns it.. we are peeking */ 426 totlen = m->m_pkthdr.len; 427 /* Update stats on incoming hook. XXX Can we do 64 bits atomically? */ 428 /* atomic_add_int64(&hip->stats.recvFrames, 1); */ 429 /* atomic_add_int64(&hip->stats.recvOctets, totlen); */ 430 hip->stats.recvFrames++; 431 hip->stats.recvOctets += totlen; 432 433 /* Don't call bpf_filter() with totlen == 0! */ 434 if (totlen == 0) { 435 len = 0; 436 goto ready; 437 } 438 439 #ifdef BPF_JITTER 440 if (bpf_jitter_enable != 0 && hip->jit_prog != NULL) 441 usejit = 1; 442 #endif 443 444 /* Need to put packet in contiguous memory for bpf */ 445 if (m->m_next != NULL && totlen > MHLEN) { 446 if (usejit) { 447 data = malloc(totlen, M_NETGRAPH_BPF, M_NOWAIT); 448 if (data == NULL) { 449 NG_FREE_ITEM(item); 450 return (ENOMEM); 451 } 452 needfree = 1; 453 m_copydata(m, 0, totlen, (caddr_t)data); 454 } 455 } else { 456 if (m->m_next != NULL) { 457 NGI_M(item) = m = m_pullup(m, totlen); 458 if (m == NULL) { 459 NG_FREE_ITEM(item); 460 return (ENOBUFS); 461 } 462 } 463 data = mtod(m, u_char *); 464 } 465 466 /* Run packet through filter */ 467 #ifdef BPF_JITTER 468 if (usejit) 469 len = (*(hip->jit_prog->func))(data, totlen, totlen); 470 else 471 #endif 472 if (data) 473 len = bpf_filter(hip->prog->bpf_prog, data, totlen, totlen); 474 else 475 len = bpf_filter(hip->prog->bpf_prog, (u_char *)m, totlen, 0); 476 if (needfree) 477 free(data, M_NETGRAPH_BPF); 478 ready: 479 /* See if we got a match and find destination hook */ 480 if (len > 0) { 481 /* Update stats */ 482 /* XXX atomically? */ 483 hip->stats.recvMatchFrames++; 484 hip->stats.recvMatchOctets += totlen; 485 486 /* Truncate packet length if required by the filter */ 487 /* Assume this never changes m */ 488 if (len < totlen) { 489 m_adj(m, -(totlen - len)); 490 totlen = len; 491 } 492 dest = hip->match; 493 } else 494 dest = hip->nomatch; 495 if (dest == NULL) { 496 NG_FREE_ITEM(item); 497 return (0); 498 } 499 500 /* Deliver frame out destination hook */ 501 dhip = NG_HOOK_PRIVATE(dest); 502 dhip->stats.xmitOctets += totlen; 503 dhip->stats.xmitFrames++; 504 NG_FWD_ITEM_HOOK(error, item, dest); 505 return (error); 506 } 507 508 /* 509 * Shutdown processing 510 */ 511 static int 512 ng_bpf_shutdown(node_p node) 513 { 514 NG_NODE_UNREF(node); 515 return (0); 516 } 517 518 /* 519 * Hook disconnection 520 */ 521 static int 522 ng_bpf_disconnect(hook_p hook) 523 { 524 const node_p node = NG_HOOK_NODE(hook); 525 const hinfo_p hip = NG_HOOK_PRIVATE(hook); 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); 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 (hp0->bpf_prog_len > bpf_maxinsns || 564 !bpf_validate(hp0->bpf_prog, hp0->bpf_prog_len)) 565 return (EINVAL); 566 567 /* Make a copy of the program */ 568 size = NG_BPF_HOOKPROG_SIZE(hp0->bpf_prog_len); 569 hp = malloc(size, M_NETGRAPH_BPF, M_NOWAIT); 570 if (hp == NULL) 571 return (ENOMEM); 572 bcopy(hp0, hp, size); 573 #ifdef BPF_JITTER 574 jit_prog = bpf_jitter(hp->bpf_prog, hp->bpf_prog_len); 575 #endif 576 577 /* Free previous program, if any, and assign new one */ 578 if (hip->prog != NULL) 579 free(hip->prog, M_NETGRAPH_BPF); 580 hip->prog = hp; 581 #ifdef BPF_JITTER 582 if (hip->jit_prog != NULL) 583 bpf_destroy_jit_filter(hip->jit_prog); 584 hip->jit_prog = jit_prog; 585 #endif 586 587 /* Prepare direct references on target hooks. */ 588 hip->match = ng_findhook(NG_HOOK_NODE(hook), hip->prog->ifMatch); 589 hip->nomatch = ng_findhook(NG_HOOK_NODE(hook), hip->prog->ifNotMatch); 590 return (0); 591 } 592