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/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 <netgraph/ng_message.h> 53 #include <netgraph/ng_parse.h> 54 #include <netgraph/netgraph.h> 55 #include <netgraph/netflow/netflow.h> 56 #include <netgraph/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 MALLOC(priv, priv_p, sizeof(*priv), M_NETGRAPH, M_NOWAIT); 172 if (priv == NULL) 173 return (ENOMEM); 174 bzero(priv, sizeof(*priv)); 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, 1); 186 187 /* Allocate memory and set up flow cache */ 188 if ((error = ng_netflow_cache_init(priv))) 189 return (error); 190 191 priv->dgram.header.version = htons(NETFLOW_V5); 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 /* Exporter is ready. Let's schedule expiry. */ 271 callout_reset(&priv->exp_callout, (1*hz), &ng_netflow_expire, 272 (void *)priv); 273 } else 274 return (EINVAL); 275 276 return (0); 277 } 278 279 /* Get a netgraph control message. */ 280 static int 281 ng_netflow_rcvmsg (node_p node, item_p item, hook_p lasthook) 282 { 283 const priv_p priv = NG_NODE_PRIVATE(node); 284 struct ng_mesg *resp = NULL; 285 int error = 0; 286 struct ng_mesg *msg; 287 288 NGI_GET_MSG(item, msg); 289 290 /* Deal with message according to cookie and command */ 291 switch (msg->header.typecookie) { 292 case NGM_NETFLOW_COOKIE: 293 switch (msg->header.cmd) { 294 case NGM_NETFLOW_INFO: 295 { 296 struct ng_netflow_info *i; 297 298 NG_MKRESPONSE(resp, msg, sizeof(struct ng_netflow_info), 299 M_NOWAIT); 300 i = (struct ng_netflow_info *)resp->data; 301 ng_netflow_copyinfo(priv, i); 302 303 break; 304 } 305 case NGM_NETFLOW_IFINFO: 306 { 307 struct ng_netflow_ifinfo *i; 308 const uint16_t *index; 309 310 if (msg->header.arglen != sizeof(uint16_t)) 311 ERROUT(EINVAL); 312 313 index = (uint16_t *)msg->data; 314 if (*index > NG_NETFLOW_MAXIFACES) 315 ERROUT(EINVAL); 316 317 /* connected iface? */ 318 if (priv->ifaces[*index].hook == NULL) 319 ERROUT(EINVAL); 320 321 NG_MKRESPONSE(resp, msg, 322 sizeof(struct ng_netflow_ifinfo), M_NOWAIT); 323 i = (struct ng_netflow_ifinfo *)resp->data; 324 memcpy((void *)i, (void *)&priv->ifaces[*index].info, 325 sizeof(priv->ifaces[*index].info)); 326 327 break; 328 } 329 case NGM_NETFLOW_SETDLT: 330 { 331 struct ng_netflow_setdlt *set; 332 struct ng_netflow_iface *iface; 333 334 if (msg->header.arglen != sizeof(struct ng_netflow_setdlt)) 335 ERROUT(EINVAL); 336 337 set = (struct ng_netflow_setdlt *)msg->data; 338 if (set->iface > NG_NETFLOW_MAXIFACES) 339 ERROUT(EINVAL); 340 iface = &priv->ifaces[set->iface]; 341 342 /* connected iface? */ 343 if (iface->hook == NULL) 344 ERROUT(EINVAL); 345 346 switch (set->dlt) { 347 case DLT_EN10MB: 348 iface->info.ifinfo_dlt = DLT_EN10MB; 349 break; 350 case DLT_RAW: 351 iface->info.ifinfo_dlt = DLT_RAW; 352 break; 353 default: 354 ERROUT(EINVAL); 355 } 356 break; 357 } 358 case NGM_NETFLOW_SETIFINDEX: 359 { 360 struct ng_netflow_setifindex *set; 361 struct ng_netflow_iface *iface; 362 363 if (msg->header.arglen != sizeof(struct ng_netflow_setifindex)) 364 ERROUT(EINVAL); 365 366 set = (struct ng_netflow_setifindex *)msg->data; 367 if (set->iface > NG_NETFLOW_MAXIFACES) 368 ERROUT(EINVAL); 369 iface = &priv->ifaces[set->iface]; 370 371 /* connected iface? */ 372 if (iface->hook == NULL) 373 ERROUT(EINVAL); 374 375 iface->info.ifinfo_index = set->index; 376 377 break; 378 } 379 case NGM_NETFLOW_SETTIMEOUTS: 380 { 381 struct ng_netflow_settimeouts *set; 382 383 if (msg->header.arglen != sizeof(struct ng_netflow_settimeouts)) 384 ERROUT(EINVAL); 385 386 set = (struct ng_netflow_settimeouts *)msg->data; 387 388 priv->info.nfinfo_inact_t = set->inactive_timeout; 389 priv->info.nfinfo_act_t = set->active_timeout; 390 391 break; 392 } 393 case NGM_NETFLOW_SHOW: 394 { 395 uint32_t *last; 396 397 if (msg->header.arglen != sizeof(uint32_t)) 398 ERROUT(EINVAL); 399 400 last = (uint32_t *)msg->data; 401 402 NG_MKRESPONSE(resp, msg, NGRESP_SIZE, M_NOWAIT); 403 404 if (!resp) 405 ERROUT(ENOMEM); 406 407 error = ng_netflow_flow_show(priv, *last, resp); 408 409 break; 410 } 411 default: 412 ERROUT(EINVAL); /* unknown command */ 413 break; 414 } 415 break; 416 default: 417 ERROUT(EINVAL); /* incorrect cookie */ 418 break; 419 } 420 421 /* 422 * Take care of synchronous response, if any. 423 * Free memory and return. 424 */ 425 done: 426 NG_RESPOND_MSG(error, node, item, resp); 427 NG_FREE_MSG(msg); 428 429 return (error); 430 } 431 432 /* Receive data on hook. */ 433 static int 434 ng_netflow_rcvdata (hook_p hook, item_p item) 435 { 436 const node_p node = NG_HOOK_NODE(hook); 437 const priv_p priv = NG_NODE_PRIVATE(node); 438 const iface_p iface = NG_HOOK_PRIVATE(hook); 439 struct mbuf *m = NULL; 440 struct ip *ip; 441 int pullup_len = 0; 442 int error = 0; 443 444 if (hook == priv->export) { 445 /* 446 * Data arrived on export hook. 447 * This must not happen. 448 */ 449 log(LOG_ERR, "ng_netflow: incoming data on export hook!\n"); 450 ERROUT(EINVAL); 451 }; 452 453 if (hook == iface->out) { 454 /* 455 * Data arrived on out hook. Bypass it. 456 */ 457 if (iface->hook == NULL) 458 ERROUT(ENOTCONN); 459 460 NG_FWD_ITEM_HOOK(error, item, iface->hook); 461 return (error); 462 } 463 464 NGI_GET_M(item, m); 465 466 /* Increase counters. */ 467 iface->info.ifinfo_packets++; 468 469 /* 470 * Depending on interface data link type and packet contents 471 * we pullup enough data, so that ng_netflow_flow_add() does not 472 * need to know about mbuf at all. We keep current length of data 473 * needed to be contiguous in pullup_len. mtod() is done at the 474 * very end one more time, since m can had changed after pulluping. 475 * 476 * In case of unrecognized data we don't return error, but just 477 * pass data to downstream hook, if it is available. 478 */ 479 480 #define M_CHECK(length) do { \ 481 pullup_len += length; \ 482 if ((m)->m_pkthdr.len < (pullup_len)) { \ 483 error = EINVAL; \ 484 goto bypass; \ 485 } \ 486 if ((m)->m_len < (pullup_len) && \ 487 (((m) = m_pullup((m),(pullup_len))) == NULL)) { \ 488 error = ENOBUFS; \ 489 goto done; \ 490 } \ 491 } while (0) 492 493 switch (iface->info.ifinfo_dlt) { 494 case DLT_EN10MB: /* Ethernet */ 495 { 496 struct ether_header *eh; 497 uint16_t etype; 498 499 M_CHECK(sizeof(struct ether_header)); 500 eh = mtod(m, struct ether_header *); 501 502 /* Make sure this is IP frame. */ 503 etype = ntohs(eh->ether_type); 504 switch (etype) { 505 case ETHERTYPE_IP: 506 M_CHECK(sizeof(struct ip)); 507 eh = mtod(m, struct ether_header *); 508 ip = (struct ip *)(eh + 1); 509 break; 510 default: 511 goto bypass; /* pass this frame */ 512 } 513 break; 514 } 515 case DLT_RAW: /* IP packets */ 516 M_CHECK(sizeof(struct ip)); 517 ip = mtod(m, struct ip *); 518 break; 519 default: 520 goto bypass; 521 break; 522 } 523 524 /* 525 * In case of IP header with options, we haven't pulled 526 * up enough, yet. 527 */ 528 pullup_len += (ip->ip_hl << 2) - sizeof(struct ip); 529 530 switch (ip->ip_p) { 531 case IPPROTO_TCP: 532 M_CHECK(sizeof(struct tcphdr)); 533 break; 534 case IPPROTO_UDP: 535 M_CHECK(sizeof(struct udphdr)); 536 break; 537 } 538 539 switch (iface->info.ifinfo_dlt) { 540 case DLT_EN10MB: 541 { 542 struct ether_header *eh; 543 544 eh = mtod(m, struct ether_header *); 545 ip = (struct ip *)(eh + 1); 546 break; 547 } 548 case DLT_RAW: 549 ip = mtod(m, struct ip *); 550 break; 551 default: 552 panic("ng_netflow entered deadcode"); 553 } 554 555 #undef M_CHECK 556 557 error = ng_netflow_flow_add(priv, ip, iface, m->m_pkthdr.rcvif); 558 559 bypass: 560 if (iface->out != NULL) { 561 /* XXX: error gets overwritten here */ 562 NG_FWD_NEW_DATA(error, item, iface->out, m); 563 return (error); 564 } 565 done: 566 if (item) 567 NG_FREE_ITEM(item); 568 if (m) 569 NG_FREE_M(m); 570 571 return (error); 572 } 573 574 /* We will be shut down in a moment */ 575 static int 576 ng_netflow_close(node_p node) 577 { 578 const priv_p priv = NG_NODE_PRIVATE(node); 579 580 callout_drain(&priv->exp_callout); 581 ng_netflow_cache_flush(priv); 582 583 return (0); 584 } 585 586 /* Do local shutdown processing. */ 587 static int 588 ng_netflow_rmnode(node_p node) 589 { 590 const priv_p priv = NG_NODE_PRIVATE(node); 591 592 NG_NODE_SET_PRIVATE(node, NULL); 593 NG_NODE_UNREF(priv->node); 594 595 FREE(priv, M_NETGRAPH); 596 597 return (0); 598 } 599 600 /* Hook disconnection. */ 601 static int 602 ng_netflow_disconnect(hook_p hook) 603 { 604 node_p node = NG_HOOK_NODE(hook); 605 priv_p priv = NG_NODE_PRIVATE(node); 606 iface_p iface = NG_HOOK_PRIVATE(hook); 607 608 if (iface != NULL) 609 iface->hook = NULL; 610 611 /* if export hook disconnected stop running expire(). */ 612 if (hook == priv->export) { 613 callout_drain(&priv->exp_callout); 614 priv->export = NULL; 615 } 616 617 /* Removal of the last link destroys the node. */ 618 if (NG_NODE_NUMHOOKS(node) == 0) 619 ng_rmnode_self(node); 620 621 return (0); 622 } 623