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