1 /*- 2 * Copyright (c) 2004-2005 Gleb Smirnoff <glebius@FreeBSD.org> 3 * Copyright (c) 2001-2003 Roman V. Palagin <romanp@unshadow.net> 4 * All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions 8 * are met: 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 16 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 21 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 23 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 24 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 25 * SUCH DAMAGE. 26 * 27 * $SourceForge: ng_netflow.c,v 1.30 2004/09/05 11:37:43 glebius Exp $ 28 */ 29 30 static const char rcs_id[] = 31 "@(#) $FreeBSD$"; 32 33 #include <sys/param.h> 34 #include <sys/systm.h> 35 #include <sys/kernel.h> 36 #include <sys/limits.h> 37 #include <sys/mbuf.h> 38 #include <sys/socket.h> 39 #include <sys/syslog.h> 40 #include <sys/ctype.h> 41 42 #include <net/if.h> 43 #include <net/ethernet.h> 44 #include <net/if_arp.h> 45 #include <net/if_var.h> 46 #include <net/if_vlan_var.h> 47 #include <net/bpf.h> 48 #include <netinet/in.h> 49 #include <netinet/in_systm.h> 50 #include <netinet/ip.h> 51 #include <netinet/tcp.h> 52 #include <netinet/udp.h> 53 54 #include <netgraph/ng_message.h> 55 #include <netgraph/ng_parse.h> 56 #include <netgraph/netgraph.h> 57 #include <netgraph/netflow/netflow.h> 58 #include <netgraph/netflow/ng_netflow.h> 59 60 /* Netgraph methods */ 61 static ng_constructor_t ng_netflow_constructor; 62 static ng_rcvmsg_t ng_netflow_rcvmsg; 63 static ng_close_t ng_netflow_close; 64 static ng_shutdown_t ng_netflow_rmnode; 65 static ng_newhook_t ng_netflow_newhook; 66 static ng_rcvdata_t ng_netflow_rcvdata; 67 static ng_disconnect_t ng_netflow_disconnect; 68 69 /* Parse type for struct ng_netflow_info */ 70 static const struct ng_parse_struct_field ng_netflow_info_type_fields[] 71 = NG_NETFLOW_INFO_TYPE; 72 static const struct ng_parse_type ng_netflow_info_type = { 73 &ng_parse_struct_type, 74 &ng_netflow_info_type_fields 75 }; 76 77 /* Parse type for struct ng_netflow_ifinfo */ 78 static const struct ng_parse_struct_field ng_netflow_ifinfo_type_fields[] 79 = NG_NETFLOW_IFINFO_TYPE; 80 static const struct ng_parse_type ng_netflow_ifinfo_type = { 81 &ng_parse_struct_type, 82 &ng_netflow_ifinfo_type_fields 83 }; 84 85 /* Parse type for struct ng_netflow_setdlt */ 86 static const struct ng_parse_struct_field ng_netflow_setdlt_type_fields[] 87 = NG_NETFLOW_SETDLT_TYPE; 88 static const struct ng_parse_type ng_netflow_setdlt_type = { 89 &ng_parse_struct_type, 90 &ng_netflow_setdlt_type_fields 91 }; 92 93 /* Parse type for ng_netflow_setifindex */ 94 static const struct ng_parse_struct_field ng_netflow_setifindex_type_fields[] 95 = NG_NETFLOW_SETIFINDEX_TYPE; 96 static const struct ng_parse_type ng_netflow_setifindex_type = { 97 &ng_parse_struct_type, 98 &ng_netflow_setifindex_type_fields 99 }; 100 101 /* Parse type for ng_netflow_settimeouts */ 102 static const struct ng_parse_struct_field ng_netflow_settimeouts_type_fields[] 103 = NG_NETFLOW_SETTIMEOUTS_TYPE; 104 static const struct ng_parse_type ng_netflow_settimeouts_type = { 105 &ng_parse_struct_type, 106 &ng_netflow_settimeouts_type_fields 107 }; 108 109 /* List of commands and how to convert arguments to/from ASCII */ 110 static const struct ng_cmdlist ng_netflow_cmds[] = { 111 { 112 NGM_NETFLOW_COOKIE, 113 NGM_NETFLOW_INFO, 114 "info", 115 NULL, 116 &ng_netflow_info_type 117 }, 118 { 119 NGM_NETFLOW_COOKIE, 120 NGM_NETFLOW_IFINFO, 121 "ifinfo", 122 &ng_parse_uint16_type, 123 &ng_netflow_ifinfo_type 124 }, 125 { 126 NGM_NETFLOW_COOKIE, 127 NGM_NETFLOW_SETDLT, 128 "setdlt", 129 &ng_netflow_setdlt_type, 130 NULL 131 }, 132 { 133 NGM_NETFLOW_COOKIE, 134 NGM_NETFLOW_SETIFINDEX, 135 "setifindex", 136 &ng_netflow_setifindex_type, 137 NULL 138 }, 139 { 140 NGM_NETFLOW_COOKIE, 141 NGM_NETFLOW_SETTIMEOUTS, 142 "settimeouts", 143 &ng_netflow_settimeouts_type, 144 NULL 145 }, 146 { 0 } 147 }; 148 149 150 /* Netgraph node type descriptor */ 151 static struct ng_type ng_netflow_typestruct = { 152 .version = NG_ABI_VERSION, 153 .name = NG_NETFLOW_NODE_TYPE, 154 .constructor = ng_netflow_constructor, 155 .rcvmsg = ng_netflow_rcvmsg, 156 .close = ng_netflow_close, 157 .shutdown = ng_netflow_rmnode, 158 .newhook = ng_netflow_newhook, 159 .rcvdata = ng_netflow_rcvdata, 160 .disconnect = ng_netflow_disconnect, 161 .cmdlist = ng_netflow_cmds, 162 }; 163 NETGRAPH_INIT(netflow, &ng_netflow_typestruct); 164 165 /* Called at node creation */ 166 static int 167 ng_netflow_constructor(node_p node) 168 { 169 priv_p priv; 170 int error = 0; 171 172 /* Initialize private data */ 173 MALLOC(priv, priv_p, sizeof(*priv), M_NETGRAPH, M_NOWAIT); 174 if (priv == NULL) 175 return (ENOMEM); 176 bzero(priv, sizeof(*priv)); 177 178 /* Make node and its data point at each other */ 179 NG_NODE_SET_PRIVATE(node, priv); 180 priv->node = node; 181 182 /* Initialize timeouts to default values */ 183 priv->info.nfinfo_inact_t = INACTIVE_TIMEOUT; 184 priv->info.nfinfo_act_t = ACTIVE_TIMEOUT; 185 186 /* Initialize callout handle */ 187 callout_init(&priv->exp_callout, 1); 188 189 /* Allocate memory and set up flow cache */ 190 if ((error = ng_netflow_cache_init(priv))) 191 return (error); 192 193 return (0); 194 } 195 196 /* 197 * ng_netflow supports two hooks: data and export. 198 * Incoming traffic is expected on data, and expired 199 * netflow datagrams are sent to export. 200 */ 201 static int 202 ng_netflow_newhook(node_p node, hook_p hook, const char *name) 203 { 204 const priv_p priv = NG_NODE_PRIVATE(node); 205 206 if (strncmp(name, NG_NETFLOW_HOOK_DATA, /* an iface hook? */ 207 strlen(NG_NETFLOW_HOOK_DATA)) == 0) { 208 iface_p iface; 209 int ifnum = -1; 210 const char *cp; 211 char *eptr; 212 213 cp = name + strlen(NG_NETFLOW_HOOK_DATA); 214 if (!isdigit(*cp) || (cp[0] == '0' && cp[1] != '\0')) 215 return (EINVAL); 216 217 ifnum = (int)strtoul(cp, &eptr, 10); 218 if (*eptr != '\0' || ifnum < 0 || ifnum >= NG_NETFLOW_MAXIFACES) 219 return (EINVAL); 220 221 /* See if hook is already connected */ 222 if (priv->ifaces[ifnum].hook != NULL) 223 return (EISCONN); 224 225 iface = &priv->ifaces[ifnum]; 226 227 /* Link private info and hook together */ 228 NG_HOOK_SET_PRIVATE(hook, iface); 229 iface->hook = hook; 230 231 /* 232 * In most cases traffic accounting is done on an 233 * Ethernet interface, so default data link type 234 * will be DLT_EN10MB. 235 */ 236 iface->info.ifinfo_dlt = DLT_EN10MB; 237 238 } else if (strncmp(name, NG_NETFLOW_HOOK_OUT, 239 strlen(NG_NETFLOW_HOOK_OUT)) == 0) { 240 iface_p iface; 241 int ifnum = -1; 242 const char *cp; 243 char *eptr; 244 245 cp = name + strlen(NG_NETFLOW_HOOK_OUT); 246 if (!isdigit(*cp) || (cp[0] == '0' && cp[1] != '\0')) 247 return (EINVAL); 248 249 ifnum = (int)strtoul(cp, &eptr, 10); 250 if (*eptr != '\0' || ifnum < 0 || ifnum >= NG_NETFLOW_MAXIFACES) 251 return (EINVAL); 252 253 /* See if hook is already connected */ 254 if (priv->ifaces[ifnum].out != NULL) 255 return (EISCONN); 256 257 iface = &priv->ifaces[ifnum]; 258 259 /* Link private info and hook together */ 260 NG_HOOK_SET_PRIVATE(hook, iface); 261 iface->out = hook; 262 263 } else if (strcmp(name, NG_NETFLOW_HOOK_EXPORT) == 0) { 264 265 if (priv->export != NULL) 266 return (EISCONN); 267 268 priv->export = hook; 269 270 #if 0 /* TODO: profile & test first */ 271 /* 272 * We send export dgrams in interrupt handlers and in 273 * callout threads. We'd better queue data for later 274 * netgraph ISR processing. 275 */ 276 NG_HOOK_FORCE_QUEUE(NG_HOOK_PEER(hook)); 277 #endif 278 279 /* Exporter is ready. Let's schedule expiry. */ 280 callout_reset(&priv->exp_callout, (1*hz), &ng_netflow_expire, 281 (void *)priv); 282 } else 283 return (EINVAL); 284 285 return (0); 286 } 287 288 /* Get a netgraph control message. */ 289 static int 290 ng_netflow_rcvmsg (node_p node, item_p item, hook_p lasthook) 291 { 292 const priv_p priv = NG_NODE_PRIVATE(node); 293 struct ng_mesg *resp = NULL; 294 int error = 0; 295 struct ng_mesg *msg; 296 297 NGI_GET_MSG(item, msg); 298 299 /* Deal with message according to cookie and command */ 300 switch (msg->header.typecookie) { 301 case NGM_NETFLOW_COOKIE: 302 switch (msg->header.cmd) { 303 case NGM_NETFLOW_INFO: 304 { 305 struct ng_netflow_info *i; 306 307 NG_MKRESPONSE(resp, msg, sizeof(struct ng_netflow_info), 308 M_NOWAIT); 309 i = (struct ng_netflow_info *)resp->data; 310 ng_netflow_copyinfo(priv, i); 311 312 break; 313 } 314 case NGM_NETFLOW_IFINFO: 315 { 316 struct ng_netflow_ifinfo *i; 317 const uint16_t *index; 318 319 if (msg->header.arglen != sizeof(uint16_t)) 320 ERROUT(EINVAL); 321 322 index = (uint16_t *)msg->data; 323 if (*index >= NG_NETFLOW_MAXIFACES) 324 ERROUT(EINVAL); 325 326 /* connected iface? */ 327 if (priv->ifaces[*index].hook == NULL) 328 ERROUT(EINVAL); 329 330 NG_MKRESPONSE(resp, msg, 331 sizeof(struct ng_netflow_ifinfo), M_NOWAIT); 332 i = (struct ng_netflow_ifinfo *)resp->data; 333 memcpy((void *)i, (void *)&priv->ifaces[*index].info, 334 sizeof(priv->ifaces[*index].info)); 335 336 break; 337 } 338 case NGM_NETFLOW_SETDLT: 339 { 340 struct ng_netflow_setdlt *set; 341 struct ng_netflow_iface *iface; 342 343 if (msg->header.arglen != sizeof(struct ng_netflow_setdlt)) 344 ERROUT(EINVAL); 345 346 set = (struct ng_netflow_setdlt *)msg->data; 347 if (set->iface >= NG_NETFLOW_MAXIFACES) 348 ERROUT(EINVAL); 349 iface = &priv->ifaces[set->iface]; 350 351 /* connected iface? */ 352 if (iface->hook == NULL) 353 ERROUT(EINVAL); 354 355 switch (set->dlt) { 356 case DLT_EN10MB: 357 iface->info.ifinfo_dlt = DLT_EN10MB; 358 break; 359 case DLT_RAW: 360 iface->info.ifinfo_dlt = DLT_RAW; 361 break; 362 default: 363 ERROUT(EINVAL); 364 } 365 break; 366 } 367 case NGM_NETFLOW_SETIFINDEX: 368 { 369 struct ng_netflow_setifindex *set; 370 struct ng_netflow_iface *iface; 371 372 if (msg->header.arglen != sizeof(struct ng_netflow_setifindex)) 373 ERROUT(EINVAL); 374 375 set = (struct ng_netflow_setifindex *)msg->data; 376 if (set->iface >= NG_NETFLOW_MAXIFACES) 377 ERROUT(EINVAL); 378 iface = &priv->ifaces[set->iface]; 379 380 /* connected iface? */ 381 if (iface->hook == NULL) 382 ERROUT(EINVAL); 383 384 iface->info.ifinfo_index = set->index; 385 386 break; 387 } 388 case NGM_NETFLOW_SETTIMEOUTS: 389 { 390 struct ng_netflow_settimeouts *set; 391 392 if (msg->header.arglen != sizeof(struct ng_netflow_settimeouts)) 393 ERROUT(EINVAL); 394 395 set = (struct ng_netflow_settimeouts *)msg->data; 396 397 priv->info.nfinfo_inact_t = set->inactive_timeout; 398 priv->info.nfinfo_act_t = set->active_timeout; 399 400 break; 401 } 402 case NGM_NETFLOW_SHOW: 403 { 404 uint32_t *last; 405 406 if (msg->header.arglen != sizeof(uint32_t)) 407 ERROUT(EINVAL); 408 409 last = (uint32_t *)msg->data; 410 411 NG_MKRESPONSE(resp, msg, NGRESP_SIZE, M_NOWAIT); 412 413 if (!resp) 414 ERROUT(ENOMEM); 415 416 error = ng_netflow_flow_show(priv, *last, resp); 417 418 break; 419 } 420 default: 421 ERROUT(EINVAL); /* unknown command */ 422 break; 423 } 424 break; 425 default: 426 ERROUT(EINVAL); /* incorrect cookie */ 427 break; 428 } 429 430 /* 431 * Take care of synchronous response, if any. 432 * Free memory and return. 433 */ 434 done: 435 NG_RESPOND_MSG(error, node, item, resp); 436 NG_FREE_MSG(msg); 437 438 return (error); 439 } 440 441 /* Receive data on hook. */ 442 static int 443 ng_netflow_rcvdata (hook_p hook, item_p item) 444 { 445 const node_p node = NG_HOOK_NODE(hook); 446 const priv_p priv = NG_NODE_PRIVATE(node); 447 const iface_p iface = NG_HOOK_PRIVATE(hook); 448 struct mbuf *m = NULL; 449 struct ip *ip; 450 int pullup_len = 0; 451 int error = 0; 452 453 if (hook == priv->export) { 454 /* 455 * Data arrived on export hook. 456 * This must not happen. 457 */ 458 log(LOG_ERR, "ng_netflow: incoming data on export hook!\n"); 459 ERROUT(EINVAL); 460 }; 461 462 if (hook == iface->out) { 463 /* 464 * Data arrived on out hook. Bypass it. 465 */ 466 if (iface->hook == NULL) 467 ERROUT(ENOTCONN); 468 469 NG_FWD_ITEM_HOOK(error, item, iface->hook); 470 return (error); 471 } 472 473 NGI_GET_M(item, m); 474 475 /* Increase counters. */ 476 iface->info.ifinfo_packets++; 477 478 /* 479 * Depending on interface data link type and packet contents 480 * we pullup enough data, so that ng_netflow_flow_add() does not 481 * need to know about mbuf at all. We keep current length of data 482 * needed to be contiguous in pullup_len. mtod() is done at the 483 * very end one more time, since m can had changed after pulluping. 484 * 485 * In case of unrecognized data we don't return error, but just 486 * pass data to downstream hook, if it is available. 487 */ 488 489 #define M_CHECK(length) do { \ 490 pullup_len += length; \ 491 if ((m)->m_pkthdr.len < (pullup_len)) { \ 492 error = EINVAL; \ 493 goto bypass; \ 494 } \ 495 if ((m)->m_len < (pullup_len) && \ 496 (((m) = m_pullup((m),(pullup_len))) == NULL)) { \ 497 error = ENOBUFS; \ 498 goto done; \ 499 } \ 500 } while (0) 501 502 switch (iface->info.ifinfo_dlt) { 503 case DLT_EN10MB: /* Ethernet */ 504 { 505 struct ether_header *eh; 506 uint16_t etype; 507 508 M_CHECK(sizeof(struct ether_header)); 509 eh = mtod(m, struct ether_header *); 510 511 /* Make sure this is IP frame. */ 512 etype = ntohs(eh->ether_type); 513 switch (etype) { 514 case ETHERTYPE_IP: 515 M_CHECK(sizeof(struct ip)); 516 eh = mtod(m, struct ether_header *); 517 ip = (struct ip *)(eh + 1); 518 break; 519 case ETHERTYPE_VLAN: 520 { 521 struct ether_vlan_header *evh; 522 523 M_CHECK(sizeof(struct ether_vlan_header) - 524 sizeof(struct ether_header)); 525 evh = mtod(m, struct ether_vlan_header *); 526 if (ntohs(evh->evl_proto) == ETHERTYPE_IP) { 527 M_CHECK(sizeof(struct ip)); 528 ip = (struct ip *)(evh + 1); 529 break; 530 } 531 } 532 default: 533 goto bypass; /* pass this frame */ 534 } 535 break; 536 } 537 case DLT_RAW: /* IP packets */ 538 M_CHECK(sizeof(struct ip)); 539 ip = mtod(m, struct ip *); 540 break; 541 default: 542 goto bypass; 543 break; 544 } 545 546 if ((ip->ip_off & htons(IP_OFFMASK)) == 0) { 547 /* 548 * In case of IP header with options, we haven't pulled 549 * up enough, yet. 550 */ 551 pullup_len += (ip->ip_hl << 2) - sizeof(struct ip); 552 553 switch (ip->ip_p) { 554 case IPPROTO_TCP: 555 M_CHECK(sizeof(struct tcphdr)); 556 break; 557 case IPPROTO_UDP: 558 M_CHECK(sizeof(struct udphdr)); 559 break; 560 } 561 } 562 563 switch (iface->info.ifinfo_dlt) { 564 case DLT_EN10MB: 565 { 566 struct ether_header *eh; 567 568 eh = mtod(m, struct ether_header *); 569 switch (ntohs(eh->ether_type)) { 570 case ETHERTYPE_IP: 571 ip = (struct ip *)(eh + 1); 572 break; 573 case ETHERTYPE_VLAN: 574 { 575 struct ether_vlan_header *evh; 576 577 evh = mtod(m, struct ether_vlan_header *); 578 ip = (struct ip *)(evh + 1); 579 break; 580 } 581 default: 582 panic("ng_netflow entered deadcode"); 583 } 584 break; 585 } 586 case DLT_RAW: 587 ip = mtod(m, struct ip *); 588 break; 589 default: 590 panic("ng_netflow entered deadcode"); 591 } 592 593 #undef M_CHECK 594 595 error = ng_netflow_flow_add(priv, ip, iface, m->m_pkthdr.rcvif); 596 597 bypass: 598 if (iface->out != NULL) { 599 /* XXX: error gets overwritten here */ 600 NG_FWD_NEW_DATA(error, item, iface->out, m); 601 return (error); 602 } 603 done: 604 if (item) 605 NG_FREE_ITEM(item); 606 if (m) 607 NG_FREE_M(m); 608 609 return (error); 610 } 611 612 /* We will be shut down in a moment */ 613 static int 614 ng_netflow_close(node_p node) 615 { 616 const priv_p priv = NG_NODE_PRIVATE(node); 617 618 callout_drain(&priv->exp_callout); 619 ng_netflow_cache_flush(priv); 620 621 return (0); 622 } 623 624 /* Do local shutdown processing. */ 625 static int 626 ng_netflow_rmnode(node_p node) 627 { 628 const priv_p priv = NG_NODE_PRIVATE(node); 629 630 NG_NODE_SET_PRIVATE(node, NULL); 631 NG_NODE_UNREF(priv->node); 632 633 FREE(priv, M_NETGRAPH); 634 635 return (0); 636 } 637 638 /* Hook disconnection. */ 639 static int 640 ng_netflow_disconnect(hook_p hook) 641 { 642 node_p node = NG_HOOK_NODE(hook); 643 priv_p priv = NG_NODE_PRIVATE(node); 644 iface_p iface = NG_HOOK_PRIVATE(hook); 645 646 if (iface != NULL) { 647 if (iface->hook == hook) 648 iface->hook = NULL; 649 if (iface->out == hook) 650 iface->out = NULL; 651 } 652 653 /* if export hook disconnected stop running expire(). */ 654 if (hook == priv->export) { 655 callout_drain(&priv->exp_callout); 656 priv->export = NULL; 657 } 658 659 /* Removal of the last link destroys the node. */ 660 if (NG_NODE_NUMHOOKS(node) == 0) 661 ng_rmnode_self(node); 662 663 return (0); 664 } 665