1 2 /* 3 * ng_bpf.c 4 * 5 * Copyright (c) 1999 Whistle Communications, Inc. 6 * All rights reserved. 7 * 8 * Subject to the following obligations and disclaimer of warranty, use and 9 * redistribution of this software, in source or object code forms, with or 10 * without modifications are expressly permitted by Whistle Communications; 11 * provided, however, that: 12 * 1. Any and all reproductions of the source or object code must include the 13 * copyright notice above and the following disclaimer of warranties; and 14 * 2. No rights are granted, in any manner or form, to use Whistle 15 * Communications, Inc. trademarks, including the mark "WHISTLE 16 * COMMUNICATIONS" on advertising, endorsements, or otherwise except as 17 * such appears in the above copyright notice or in the software. 18 * 19 * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND 20 * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO 21 * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, 22 * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF 23 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. 24 * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY 25 * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS 26 * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. 27 * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES 28 * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING 29 * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, 30 * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR 31 * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY 32 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 33 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 34 * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY 35 * OF SUCH DAMAGE. 36 * 37 * Author: Archie Cobbs <archie@freebsd.org> 38 * 39 * $FreeBSD$ 40 * $Whistle: ng_bpf.c,v 1.3 1999/12/03 20:30:23 archie Exp $ 41 */ 42 43 /* 44 * BPF NETGRAPH NODE TYPE 45 * 46 * This node type accepts any number of hook connections. With each hook 47 * is associated a bpf(4) filter program, and two hook names (each possibly 48 * the empty string). Incoming packets are compared against the filter; 49 * matching packets are delivered out the first named hook (or dropped if 50 * the empty string), and non-matching packets are delivered out the second 51 * named hook (or dropped if the empty string). 52 * 53 * Each hook also keeps statistics about how many packets have matched, etc. 54 */ 55 56 #include <sys/param.h> 57 #include <sys/systm.h> 58 #include <sys/errno.h> 59 #include <sys/kernel.h> 60 #include <sys/malloc.h> 61 #include <sys/mbuf.h> 62 63 #include <net/bpf.h> 64 65 #include <netgraph/ng_message.h> 66 #include <netgraph/netgraph.h> 67 #include <netgraph/ng_parse.h> 68 #include <netgraph/ng_bpf.h> 69 70 #ifdef NG_SEPARATE_MALLOC 71 MALLOC_DEFINE(M_NETGRAPH_BPF, "netgraph_bpf", "netgraph bpf node "); 72 #else 73 #define M_NETGRAPH_BPF M_NETGRAPH 74 #endif 75 76 #define OFFSETOF(s, e) ((char *)&((s *)0)->e - (char *)((s *)0)) 77 78 #define ERROUT(x) do { error = (x); goto done; } while (0) 79 80 /* Per hook private info */ 81 struct ng_bpf_hookinfo { 82 node_p node; 83 hook_p hook; 84 struct ng_bpf_hookprog *prog; 85 struct ng_bpf_hookstat stats; 86 }; 87 typedef struct ng_bpf_hookinfo *hinfo_p; 88 89 /* Netgraph methods */ 90 static ng_constructor_t ng_bpf_constructor; 91 static ng_rcvmsg_t ng_bpf_rcvmsg; 92 static ng_shutdown_t ng_bpf_shutdown; 93 static ng_newhook_t ng_bpf_newhook; 94 static ng_rcvdata_t ng_bpf_rcvdata; 95 static ng_disconnect_t ng_bpf_disconnect; 96 97 /* Internal helper functions */ 98 static int ng_bpf_setprog(hook_p hook, const struct ng_bpf_hookprog *hp); 99 100 /* Parse type for one struct bfp_insn */ 101 static const struct ng_parse_struct_field ng_bpf_insn_type_fields[] = { 102 { "code", &ng_parse_hint16_type }, 103 { "jt", &ng_parse_uint8_type }, 104 { "jf", &ng_parse_uint8_type }, 105 { "k", &ng_parse_uint32_type }, 106 { NULL } 107 }; 108 static const struct ng_parse_type ng_bpf_insn_type = { 109 &ng_parse_struct_type, 110 &ng_bpf_insn_type_fields 111 }; 112 113 /* Parse type for the field 'bpf_prog' in struct ng_bpf_hookprog */ 114 static int 115 ng_bpf_hookprogary_getLength(const struct ng_parse_type *type, 116 const u_char *start, const u_char *buf) 117 { 118 const struct ng_bpf_hookprog *hp; 119 120 hp = (const struct ng_bpf_hookprog *) 121 (buf - OFFSETOF(struct ng_bpf_hookprog, bpf_prog)); 122 return hp->bpf_prog_len; 123 } 124 125 static const struct ng_parse_array_info ng_bpf_hookprogary_info = { 126 &ng_bpf_insn_type, 127 &ng_bpf_hookprogary_getLength, 128 NULL 129 }; 130 static const struct ng_parse_type ng_bpf_hookprogary_type = { 131 &ng_parse_array_type, 132 &ng_bpf_hookprogary_info 133 }; 134 135 /* Parse type for struct ng_bpf_hookprog */ 136 static const struct ng_parse_struct_field ng_bpf_hookprog_type_fields[] 137 = NG_BPF_HOOKPROG_TYPE_INFO(&ng_bpf_hookprogary_type); 138 static const struct ng_parse_type ng_bpf_hookprog_type = { 139 &ng_parse_struct_type, 140 &ng_bpf_hookprog_type_fields 141 }; 142 143 /* Parse type for struct ng_bpf_hookstat */ 144 static const struct ng_parse_struct_field ng_bpf_hookstat_type_fields[] 145 = NG_BPF_HOOKSTAT_TYPE_INFO; 146 static const struct ng_parse_type ng_bpf_hookstat_type = { 147 &ng_parse_struct_type, 148 &ng_bpf_hookstat_type_fields 149 }; 150 151 /* List of commands and how to convert arguments to/from ASCII */ 152 static const struct ng_cmdlist ng_bpf_cmdlist[] = { 153 { 154 NGM_BPF_COOKIE, 155 NGM_BPF_SET_PROGRAM, 156 "setprogram", 157 &ng_bpf_hookprog_type, 158 NULL 159 }, 160 { 161 NGM_BPF_COOKIE, 162 NGM_BPF_GET_PROGRAM, 163 "getprogram", 164 &ng_parse_hookbuf_type, 165 &ng_bpf_hookprog_type 166 }, 167 { 168 NGM_BPF_COOKIE, 169 NGM_BPF_GET_STATS, 170 "getstats", 171 &ng_parse_hookbuf_type, 172 &ng_bpf_hookstat_type 173 }, 174 { 175 NGM_BPF_COOKIE, 176 NGM_BPF_CLR_STATS, 177 "clrstats", 178 &ng_parse_hookbuf_type, 179 NULL 180 }, 181 { 182 NGM_BPF_COOKIE, 183 NGM_BPF_GETCLR_STATS, 184 "getclrstats", 185 &ng_parse_hookbuf_type, 186 &ng_bpf_hookstat_type 187 }, 188 { 0 } 189 }; 190 191 /* Netgraph type descriptor */ 192 static struct ng_type typestruct = { 193 .version = NG_ABI_VERSION, 194 .name = NG_BPF_NODE_TYPE, 195 .constructor = ng_bpf_constructor, 196 .rcvmsg = ng_bpf_rcvmsg, 197 .shutdown = ng_bpf_shutdown, 198 .newhook = ng_bpf_newhook, 199 .rcvdata = ng_bpf_rcvdata, 200 .disconnect = ng_bpf_disconnect, 201 .cmdlist = ng_bpf_cmdlist, 202 }; 203 NETGRAPH_INIT(bpf, &typestruct); 204 205 /* Default BPF program for a hook that matches nothing */ 206 static const struct ng_bpf_hookprog ng_bpf_default_prog = { 207 { '\0' }, /* to be filled in at hook creation time */ 208 { '\0' }, 209 { '\0' }, 210 1, 211 { BPF_STMT(BPF_RET+BPF_K, 0) } 212 }; 213 214 /* 215 * Node constructor 216 * 217 * We don't keep any per-node private data 218 * We go via the hooks. 219 */ 220 static int 221 ng_bpf_constructor(node_p node) 222 { 223 NG_NODE_SET_PRIVATE(node, NULL); 224 return (0); 225 } 226 227 /* 228 * Add a hook 229 */ 230 static int 231 ng_bpf_newhook(node_p node, hook_p hook, const char *name) 232 { 233 hinfo_p hip; 234 int error; 235 236 /* Create hook private structure */ 237 MALLOC(hip, hinfo_p, sizeof(*hip), M_NETGRAPH_BPF, M_NOWAIT | M_ZERO); 238 if (hip == NULL) 239 return (ENOMEM); 240 hip->hook = hook; 241 NG_HOOK_SET_PRIVATE(hook, hip); 242 hip->node = node; 243 244 /* Attach the default BPF program */ 245 if ((error = ng_bpf_setprog(hook, &ng_bpf_default_prog)) != 0) { 246 FREE(hip, M_NETGRAPH_BPF); 247 NG_HOOK_SET_PRIVATE(hook, NULL); 248 return (error); 249 } 250 251 /* Set hook name */ 252 strncpy(hip->prog->thisHook, name, sizeof(hip->prog->thisHook) - 1); 253 hip->prog->thisHook[sizeof(hip->prog->thisHook) - 1] = '\0'; 254 return (0); 255 } 256 257 /* 258 * Receive a control message 259 */ 260 static int 261 ng_bpf_rcvmsg(node_p node, item_p item, hook_p lasthook) 262 { 263 struct ng_mesg *msg; 264 struct ng_mesg *resp = NULL; 265 int error = 0; 266 267 NGI_GET_MSG(item, msg); 268 switch (msg->header.typecookie) { 269 case NGM_BPF_COOKIE: 270 switch (msg->header.cmd) { 271 case NGM_BPF_SET_PROGRAM: 272 { 273 struct ng_bpf_hookprog *const 274 hp = (struct ng_bpf_hookprog *)msg->data; 275 hook_p hook; 276 277 /* Sanity check */ 278 if (msg->header.arglen < sizeof(*hp) 279 || msg->header.arglen 280 != NG_BPF_HOOKPROG_SIZE(hp->bpf_prog_len)) 281 ERROUT(EINVAL); 282 283 /* Find hook */ 284 if ((hook = ng_findhook(node, hp->thisHook)) == NULL) 285 ERROUT(ENOENT); 286 287 /* Set new program */ 288 if ((error = ng_bpf_setprog(hook, hp)) != 0) 289 ERROUT(error); 290 break; 291 } 292 293 case NGM_BPF_GET_PROGRAM: 294 { 295 struct ng_bpf_hookprog *hp; 296 hook_p hook; 297 298 /* Sanity check */ 299 if (msg->header.arglen == 0) 300 ERROUT(EINVAL); 301 msg->data[msg->header.arglen - 1] = '\0'; 302 303 /* Find hook */ 304 if ((hook = ng_findhook(node, msg->data)) == NULL) 305 ERROUT(ENOENT); 306 307 /* Build response */ 308 hp = ((hinfo_p)NG_HOOK_PRIVATE(hook))->prog; 309 NG_MKRESPONSE(resp, msg, 310 NG_BPF_HOOKPROG_SIZE(hp->bpf_prog_len), M_NOWAIT); 311 if (resp == NULL) 312 ERROUT(ENOMEM); 313 bcopy(hp, resp->data, 314 NG_BPF_HOOKPROG_SIZE(hp->bpf_prog_len)); 315 break; 316 } 317 318 case NGM_BPF_GET_STATS: 319 case NGM_BPF_CLR_STATS: 320 case NGM_BPF_GETCLR_STATS: 321 { 322 struct ng_bpf_hookstat *stats; 323 hook_p hook; 324 325 /* Sanity check */ 326 if (msg->header.arglen == 0) 327 ERROUT(EINVAL); 328 msg->data[msg->header.arglen - 1] = '\0'; 329 330 /* Find hook */ 331 if ((hook = ng_findhook(node, msg->data)) == NULL) 332 ERROUT(ENOENT); 333 stats = &((hinfo_p)NG_HOOK_PRIVATE(hook))->stats; 334 335 /* Build response (if desired) */ 336 if (msg->header.cmd != NGM_BPF_CLR_STATS) { 337 NG_MKRESPONSE(resp, 338 msg, sizeof(*stats), M_NOWAIT); 339 if (resp == NULL) 340 ERROUT(ENOMEM); 341 bcopy(stats, resp->data, sizeof(*stats)); 342 } 343 344 /* Clear stats (if desired) */ 345 if (msg->header.cmd != NGM_BPF_GET_STATS) 346 bzero(stats, sizeof(*stats)); 347 break; 348 } 349 350 default: 351 error = EINVAL; 352 break; 353 } 354 break; 355 default: 356 error = EINVAL; 357 break; 358 } 359 done: 360 NG_RESPOND_MSG(error, node, item, resp); 361 NG_FREE_MSG(msg); 362 return (error); 363 } 364 365 /* 366 * Receive data on a hook 367 * 368 * Apply the filter, and then drop or forward packet as appropriate. 369 */ 370 static int 371 ng_bpf_rcvdata(hook_p hook, item_p item) 372 { 373 const hinfo_p hip = NG_HOOK_PRIVATE(hook); 374 int totlen; 375 int needfree = 0, error = 0; 376 u_char *data, buf[256]; 377 hinfo_p dhip; 378 hook_p dest; 379 u_int len; 380 struct mbuf *m; 381 382 m = NGI_M(item); /* 'item' still owns it.. we are peeking */ 383 totlen = m->m_pkthdr.len; 384 /* Update stats on incoming hook. XXX Can we do 64 bits atomically? */ 385 /* atomic_add_int64(&hip->stats.recvFrames, 1); */ 386 /* atomic_add_int64(&hip->stats.recvOctets, totlen); */ 387 hip->stats.recvFrames++; 388 hip->stats.recvOctets += totlen; 389 390 /* Need to put packet in contiguous memory for bpf */ 391 if (m->m_next != NULL) { 392 if (totlen > sizeof(buf)) { 393 MALLOC(data, u_char *, totlen, M_NETGRAPH_BPF, M_NOWAIT); 394 if (data == NULL) { 395 NG_FREE_ITEM(item); 396 return (ENOMEM); 397 } 398 needfree = 1; 399 } else 400 data = buf; 401 m_copydata(m, 0, totlen, (caddr_t)data); 402 } else 403 data = mtod(m, u_char *); 404 405 /* Run packet through filter */ 406 if (totlen == 0) 407 len = 0; /* don't call bpf_filter() with totlen == 0! */ 408 else 409 len = bpf_filter(hip->prog->bpf_prog, data, totlen, totlen); 410 if (needfree) 411 FREE(data, M_NETGRAPH_BPF); 412 413 /* See if we got a match and find destination hook */ 414 if (len > 0) { 415 416 /* Update stats */ 417 /* XXX atomically? */ 418 hip->stats.recvMatchFrames++; 419 hip->stats.recvMatchOctets += totlen; 420 421 /* Truncate packet length if required by the filter */ 422 /* Assume this never changes m */ 423 if (len < totlen) { 424 m_adj(m, -(totlen - len)); 425 totlen -= len; 426 } 427 dest = ng_findhook(hip->node, hip->prog->ifMatch); 428 } else 429 dest = ng_findhook(hip->node, hip->prog->ifNotMatch); 430 if (dest == NULL) { 431 NG_FREE_ITEM(item); 432 return (0); 433 } 434 435 /* Deliver frame out destination hook */ 436 dhip = NG_HOOK_PRIVATE(dest); 437 dhip->stats.xmitOctets += totlen; 438 dhip->stats.xmitFrames++; 439 NG_FWD_ITEM_HOOK(error, item, dest); 440 return (error); 441 } 442 443 /* 444 * Shutdown processing 445 */ 446 static int 447 ng_bpf_shutdown(node_p node) 448 { 449 NG_NODE_UNREF(node); 450 return (0); 451 } 452 453 /* 454 * Hook disconnection 455 */ 456 static int 457 ng_bpf_disconnect(hook_p hook) 458 { 459 const hinfo_p hip = NG_HOOK_PRIVATE(hook); 460 461 KASSERT(hip != NULL, ("%s: null info", __func__)); 462 FREE(hip->prog, M_NETGRAPH_BPF); 463 bzero(hip, sizeof(*hip)); 464 FREE(hip, M_NETGRAPH_BPF); 465 NG_HOOK_SET_PRIVATE(hook, NULL); /* for good measure */ 466 if ((NG_NODE_NUMHOOKS(NG_HOOK_NODE(hook)) == 0) 467 && (NG_NODE_IS_VALID(NG_HOOK_NODE(hook)))) { 468 ng_rmnode_self(NG_HOOK_NODE(hook)); 469 } 470 return (0); 471 } 472 473 /************************************************************************ 474 HELPER STUFF 475 ************************************************************************/ 476 477 /* 478 * Set the BPF program associated with a hook 479 */ 480 static int 481 ng_bpf_setprog(hook_p hook, const struct ng_bpf_hookprog *hp0) 482 { 483 const hinfo_p hip = NG_HOOK_PRIVATE(hook); 484 struct ng_bpf_hookprog *hp; 485 int size; 486 487 /* Check program for validity */ 488 if (!bpf_validate(hp0->bpf_prog, hp0->bpf_prog_len)) 489 return (EINVAL); 490 491 /* Make a copy of the program */ 492 size = NG_BPF_HOOKPROG_SIZE(hp0->bpf_prog_len); 493 MALLOC(hp, struct ng_bpf_hookprog *, size, M_NETGRAPH_BPF, M_NOWAIT); 494 if (hp == NULL) 495 return (ENOMEM); 496 bcopy(hp0, hp, size); 497 498 /* Free previous program, if any, and assign new one */ 499 if (hip->prog != NULL) 500 FREE(hip->prog, M_NETGRAPH_BPF); 501 hip->prog = hp; 502 return (0); 503 } 504 505