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 * $FreeBSD: src/sys/netgraph/netflow/ng_netflow.c,v 1.17 2008/04/16 16:47:14 kris Exp $ 29 */ 30 31 #include <sys/param.h> 32 #include <sys/systm.h> 33 #include <sys/kernel.h> 34 #include <sys/limits.h> 35 #include <sys/malloc.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 "ng_message.h" 54 #include "ng_parse.h" 55 #include "netgraph.h" 56 #include "netflow/netflow.h" 57 #include "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 priv = kmalloc(sizeof(*priv), M_NETGRAPH, M_WAITOK | M_NULLOK | M_ZERO); 173 if (priv == NULL) 174 return (ENOMEM); 175 176 /* Make node and its data point at each other */ 177 NG_NODE_SET_PRIVATE(node, priv); 178 priv->node = node; 179 180 /* Initialize timeouts to default values */ 181 priv->info.nfinfo_inact_t = INACTIVE_TIMEOUT; 182 priv->info.nfinfo_act_t = ACTIVE_TIMEOUT; 183 184 /* Initialize callout handle */ 185 callout_init(&priv->exp_callout, CALLOUT_MPSAFE); 186 187 /* Allocate memory and set up flow cache */ 188 if ((error = ng_netflow_cache_init(priv))) 189 return (error); 190 191 return (0); 192 } 193 194 /* 195 * ng_netflow supports two hooks: data and export. 196 * Incoming traffic is expected on data, and expired 197 * netflow datagrams are sent to export. 198 */ 199 static int 200 ng_netflow_newhook(node_p node, hook_p hook, const char *name) 201 { 202 const priv_p priv = NG_NODE_PRIVATE(node); 203 204 if (strncmp(name, NG_NETFLOW_HOOK_DATA, /* an iface hook? */ 205 strlen(NG_NETFLOW_HOOK_DATA)) == 0) { 206 iface_p iface; 207 int ifnum = -1; 208 const char *cp; 209 char *eptr; 210 211 cp = name + strlen(NG_NETFLOW_HOOK_DATA); 212 if (!isdigit(*cp) || (cp[0] == '0' && cp[1] != '\0')) 213 return (EINVAL); 214 215 ifnum = (int)strtoul(cp, &eptr, 10); 216 if (*eptr != '\0' || ifnum < 0 || ifnum >= NG_NETFLOW_MAXIFACES) 217 return (EINVAL); 218 219 /* See if hook is already connected */ 220 if (priv->ifaces[ifnum].hook != NULL) 221 return (EISCONN); 222 223 iface = &priv->ifaces[ifnum]; 224 225 /* Link private info and hook together */ 226 NG_HOOK_SET_PRIVATE(hook, iface); 227 iface->hook = hook; 228 229 /* 230 * In most cases traffic accounting is done on an 231 * Ethernet interface, so default data link type 232 * will be DLT_EN10MB. 233 */ 234 iface->info.ifinfo_dlt = DLT_EN10MB; 235 236 } else if (strncmp(name, NG_NETFLOW_HOOK_OUT, 237 strlen(NG_NETFLOW_HOOK_OUT)) == 0) { 238 iface_p iface; 239 int ifnum = -1; 240 const char *cp; 241 char *eptr; 242 243 cp = name + strlen(NG_NETFLOW_HOOK_OUT); 244 if (!isdigit(*cp) || (cp[0] == '0' && cp[1] != '\0')) 245 return (EINVAL); 246 247 ifnum = (int)strtoul(cp, &eptr, 10); 248 if (*eptr != '\0' || ifnum < 0 || ifnum >= NG_NETFLOW_MAXIFACES) 249 return (EINVAL); 250 251 /* See if hook is already connected */ 252 if (priv->ifaces[ifnum].out != NULL) 253 return (EISCONN); 254 255 iface = &priv->ifaces[ifnum]; 256 257 /* Link private info and hook together */ 258 NG_HOOK_SET_PRIVATE(hook, iface); 259 iface->out = hook; 260 261 } else if (strcmp(name, NG_NETFLOW_HOOK_EXPORT) == 0) { 262 263 if (priv->export != NULL) 264 return (EISCONN); 265 266 priv->export = hook; 267 268 #if 0 /* TODO: profile & test first */ 269 /* 270 * We send export dgrams in interrupt handlers and in 271 * callout threads. We'd better queue data for later 272 * netgraph ISR processing. 273 */ 274 NG_HOOK_FORCE_QUEUE(NG_HOOK_PEER(hook)); 275 #endif 276 277 /* Exporter is ready. Let's schedule expiry. */ 278 callout_reset(&priv->exp_callout, (1*hz), &ng_netflow_expire, 279 (void *)priv); 280 } else 281 return (EINVAL); 282 283 return (0); 284 } 285 286 /* Get a netgraph control message. */ 287 static int 288 ng_netflow_rcvmsg (node_p node, item_p item, hook_p lasthook) 289 { 290 const priv_p priv = NG_NODE_PRIVATE(node); 291 struct ng_mesg *resp = NULL; 292 int error = 0; 293 struct ng_mesg *msg; 294 295 NGI_GET_MSG(item, msg); 296 297 /* Deal with message according to cookie and command */ 298 switch (msg->header.typecookie) { 299 case NGM_NETFLOW_COOKIE: 300 switch (msg->header.cmd) { 301 case NGM_NETFLOW_INFO: 302 { 303 struct ng_netflow_info *i; 304 305 NG_MKRESPONSE(resp, msg, sizeof(struct ng_netflow_info), 306 M_WAITOK | M_NULLOK); 307 i = (struct ng_netflow_info *)resp->data; 308 ng_netflow_copyinfo(priv, i); 309 310 break; 311 } 312 case NGM_NETFLOW_IFINFO: 313 { 314 struct ng_netflow_ifinfo *i; 315 const uint16_t *index; 316 317 if (msg->header.arglen != sizeof(uint16_t)) 318 ERROUT(EINVAL); 319 320 index = (uint16_t *)msg->data; 321 if (*index >= NG_NETFLOW_MAXIFACES) 322 ERROUT(EINVAL); 323 324 /* connected iface? */ 325 if (priv->ifaces[*index].hook == NULL) 326 ERROUT(EINVAL); 327 328 NG_MKRESPONSE(resp, msg, 329 sizeof(struct ng_netflow_ifinfo), M_WAITOK | M_NULLOK); 330 i = (struct ng_netflow_ifinfo *)resp->data; 331 memcpy((void *)i, (void *)&priv->ifaces[*index].info, 332 sizeof(priv->ifaces[*index].info)); 333 334 break; 335 } 336 case NGM_NETFLOW_SETDLT: 337 { 338 struct ng_netflow_setdlt *set; 339 struct ng_netflow_iface *iface; 340 341 if (msg->header.arglen != sizeof(struct ng_netflow_setdlt)) 342 ERROUT(EINVAL); 343 344 set = (struct ng_netflow_setdlt *)msg->data; 345 if (set->iface >= NG_NETFLOW_MAXIFACES) 346 ERROUT(EINVAL); 347 iface = &priv->ifaces[set->iface]; 348 349 /* connected iface? */ 350 if (iface->hook == NULL) 351 ERROUT(EINVAL); 352 353 switch (set->dlt) { 354 case DLT_EN10MB: 355 iface->info.ifinfo_dlt = DLT_EN10MB; 356 break; 357 case DLT_RAW: 358 iface->info.ifinfo_dlt = DLT_RAW; 359 break; 360 default: 361 ERROUT(EINVAL); 362 } 363 break; 364 } 365 case NGM_NETFLOW_SETIFINDEX: 366 { 367 struct ng_netflow_setifindex *set; 368 struct ng_netflow_iface *iface; 369 370 if (msg->header.arglen != sizeof(struct ng_netflow_setifindex)) 371 ERROUT(EINVAL); 372 373 set = (struct ng_netflow_setifindex *)msg->data; 374 if (set->iface >= NG_NETFLOW_MAXIFACES) 375 ERROUT(EINVAL); 376 iface = &priv->ifaces[set->iface]; 377 378 /* connected iface? */ 379 if (iface->hook == NULL) 380 ERROUT(EINVAL); 381 382 iface->info.ifinfo_index = set->index; 383 384 break; 385 } 386 case NGM_NETFLOW_SETTIMEOUTS: 387 { 388 struct ng_netflow_settimeouts *set; 389 390 if (msg->header.arglen != sizeof(struct ng_netflow_settimeouts)) 391 ERROUT(EINVAL); 392 393 set = (struct ng_netflow_settimeouts *)msg->data; 394 395 priv->info.nfinfo_inact_t = set->inactive_timeout; 396 priv->info.nfinfo_act_t = set->active_timeout; 397 398 break; 399 } 400 case NGM_NETFLOW_SHOW: 401 { 402 uint32_t *last; 403 404 if (msg->header.arglen != sizeof(uint32_t)) 405 ERROUT(EINVAL); 406 407 last = (uint32_t *)msg->data; 408 409 NG_MKRESPONSE(resp, msg, NGRESP_SIZE, M_WAITOK | M_NULLOK); 410 411 if (!resp) 412 ERROUT(ENOMEM); 413 414 error = ng_netflow_flow_show(priv, *last, resp); 415 416 break; 417 } 418 default: 419 ERROUT(EINVAL); /* unknown command */ 420 break; 421 } 422 break; 423 default: 424 ERROUT(EINVAL); /* incorrect cookie */ 425 break; 426 } 427 428 /* 429 * Take care of synchronous response, if any. 430 * Free memory and return. 431 */ 432 done: 433 NG_RESPOND_MSG(error, node, item, resp); 434 NG_FREE_MSG(msg); 435 436 return (error); 437 } 438 439 /* Receive data on hook. */ 440 static int 441 ng_netflow_rcvdata (hook_p hook, item_p item) 442 { 443 const node_p node = NG_HOOK_NODE(hook); 444 const priv_p priv = NG_NODE_PRIVATE(node); 445 const iface_p iface = NG_HOOK_PRIVATE(hook); 446 struct mbuf *m = NULL; 447 struct ip *ip; 448 int pullup_len = 0; 449 int error = 0; 450 451 if (hook == priv->export) { 452 /* 453 * Data arrived on export hook. 454 * This must not happen. 455 */ 456 log(LOG_ERR, "ng_netflow: incoming data on export hook!\n"); 457 ERROUT(EINVAL); 458 } 459 460 if (hook == iface->out) { 461 /* 462 * Data arrived on out hook. Bypass it. 463 */ 464 if (iface->hook == NULL) 465 ERROUT(ENOTCONN); 466 467 NG_FWD_ITEM_HOOK(error, item, iface->hook); 468 return (error); 469 } 470 471 NGI_GET_M(item, m); 472 473 /* Increase counters. */ 474 iface->info.ifinfo_packets++; 475 476 /* 477 * Depending on interface data link type and packet contents 478 * we pullup enough data, so that ng_netflow_flow_add() does not 479 * need to know about mbuf at all. We keep current length of data 480 * needed to be contiguous in pullup_len. mtod() is done at the 481 * very end one more time, since m can had changed after pulluping. 482 * 483 * In case of unrecognized data we don't return error, but just 484 * pass data to downstream hook, if it is available. 485 */ 486 487 #define M_CHECK(length) do { \ 488 pullup_len += length; \ 489 if ((m)->m_pkthdr.len < (pullup_len)) { \ 490 error = EINVAL; \ 491 goto bypass; \ 492 } \ 493 if ((m)->m_len < (pullup_len) && \ 494 (((m) = m_pullup((m),(pullup_len))) == NULL)) { \ 495 error = ENOBUFS; \ 496 goto done; \ 497 } \ 498 } while (0) 499 500 switch (iface->info.ifinfo_dlt) { 501 case DLT_EN10MB: /* Ethernet */ 502 { 503 struct ether_header *eh; 504 uint16_t etype; 505 506 M_CHECK(sizeof(struct ether_header)); 507 eh = mtod(m, struct ether_header *); 508 509 /* Make sure this is IP frame. */ 510 etype = ntohs(eh->ether_type); 511 switch (etype) { 512 case ETHERTYPE_IP: 513 M_CHECK(sizeof(struct ip)); 514 eh = mtod(m, struct ether_header *); 515 ip = (struct ip *)(eh + 1); 516 break; 517 case ETHERTYPE_VLAN: 518 { 519 struct ether_vlan_header *evh; 520 521 M_CHECK(sizeof(struct ether_vlan_header) - 522 sizeof(struct ether_header)); 523 evh = mtod(m, struct ether_vlan_header *); 524 if (ntohs(evh->evl_proto) == ETHERTYPE_IP) { 525 M_CHECK(sizeof(struct ip)); 526 ip = (struct ip *)(evh + 1); 527 break; 528 } 529 } 530 default: 531 goto bypass; /* pass this frame */ 532 } 533 break; 534 } 535 case DLT_RAW: /* IP packets */ 536 M_CHECK(sizeof(struct ip)); 537 ip = mtod(m, struct ip *); 538 break; 539 default: 540 goto bypass; 541 break; 542 } 543 544 if ((ip->ip_off & htons(IP_OFFMASK)) == 0) { 545 /* 546 * In case of IP header with options, we haven't pulled 547 * up enough, yet. 548 */ 549 pullup_len += (ip->ip_hl << 2) - sizeof(struct ip); 550 551 switch (ip->ip_p) { 552 case IPPROTO_TCP: 553 M_CHECK(sizeof(struct tcphdr)); 554 break; 555 case IPPROTO_UDP: 556 M_CHECK(sizeof(struct udphdr)); 557 break; 558 } 559 } 560 561 switch (iface->info.ifinfo_dlt) { 562 case DLT_EN10MB: 563 { 564 struct ether_header *eh; 565 566 eh = mtod(m, struct ether_header *); 567 switch (ntohs(eh->ether_type)) { 568 case ETHERTYPE_IP: 569 ip = (struct ip *)(eh + 1); 570 break; 571 case ETHERTYPE_VLAN: 572 { 573 struct ether_vlan_header *evh; 574 575 evh = mtod(m, struct ether_vlan_header *); 576 ip = (struct ip *)(evh + 1); 577 break; 578 } 579 default: 580 panic("ng_netflow entered deadcode"); 581 } 582 break; 583 } 584 case DLT_RAW: 585 ip = mtod(m, struct ip *); 586 break; 587 default: 588 panic("ng_netflow entered deadcode"); 589 } 590 591 #undef M_CHECK 592 593 error = ng_netflow_flow_add(priv, ip, iface, m->m_pkthdr.rcvif); 594 595 bypass: 596 if (iface->out != NULL) { 597 /* XXX: error gets overwritten here */ 598 NG_FWD_NEW_DATA(error, item, iface->out, m); 599 return (error); 600 } 601 done: 602 if (item) 603 NG_FREE_ITEM(item); 604 if (m) 605 NG_FREE_M(m); 606 607 return (error); 608 } 609 610 /* We will be shut down in a moment */ 611 static int 612 ng_netflow_close(node_p node) 613 { 614 const priv_p priv = NG_NODE_PRIVATE(node); 615 616 callout_drain(&priv->exp_callout); 617 ng_netflow_cache_flush(priv); 618 619 return (0); 620 } 621 622 /* Do local shutdown processing. */ 623 static int 624 ng_netflow_rmnode(node_p node) 625 { 626 const priv_p priv = NG_NODE_PRIVATE(node); 627 628 NG_NODE_SET_PRIVATE(node, NULL); 629 NG_NODE_UNREF(priv->node); 630 631 kfree(priv, M_NETGRAPH); 632 633 return (0); 634 } 635 636 /* Hook disconnection. */ 637 static int 638 ng_netflow_disconnect(hook_p hook) 639 { 640 node_p node = NG_HOOK_NODE(hook); 641 priv_p priv = NG_NODE_PRIVATE(node); 642 iface_p iface = NG_HOOK_PRIVATE(hook); 643 644 if (iface != NULL) { 645 if (iface->hook == hook) 646 iface->hook = NULL; 647 if (iface->out == hook) 648 iface->out = NULL; 649 } 650 651 /* if export hook disconnected stop running expire(). */ 652 if (hook == priv->export) { 653 callout_drain(&priv->exp_callout); 654 priv->export = NULL; 655 } 656 657 /* Removal of the last link destroys the node. */ 658 if (NG_NODE_NUMHOOKS(node) == 0) 659 ng_rmnode_self(node); 660 661 return (0); 662 } 663