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