1 /*- 2 * Copyright (c) 2004 Gleb Smirnoff <glebius@cell.sick.ru> 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 * 3. All advertising materials mentioning features or use of this software 15 * must display the following acknowledgement: 16 * This product includes software developed by Gleb Smirnoff and 17 * contributors. 18 * 4. Neither the name of the author nor the names of its contributors 19 * may be used to endorse or promote products derived from this software 20 * without specific prior written permission. 21 * 22 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 23 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 24 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 25 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 26 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 27 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 28 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 29 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 30 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 31 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 32 * SUCH DAMAGE. 33 * 34 * $SourceForge: ng_netflow.c,v 1.30 2004/09/05 11:37:43 glebius Exp $ 35 */ 36 37 static const char rcs_id[] = 38 "@(#) $FreeBSD$"; 39 40 #include <sys/param.h> 41 #include <sys/systm.h> 42 #include <sys/kernel.h> 43 #include <sys/mbuf.h> 44 #include <sys/socket.h> 45 #include <sys/ctype.h> 46 47 #include <net/if.h> 48 #include <net/ethernet.h> 49 #include <net/if_arp.h> 50 #include <net/if_var.h> 51 #include <net/bpf.h> 52 #include <netinet/in.h> 53 #include <netinet/in_systm.h> 54 #include <netinet/ip.h> 55 56 #include <netgraph/ng_message.h> 57 #include <netgraph/ng_parse.h> 58 #include <netgraph/netgraph.h> 59 #include <netgraph/netflow/netflow.h> 60 #include <netgraph/netflow/ng_netflow.h> 61 62 /* Netgraph methods */ 63 static ng_constructor_t ng_netflow_constructor; 64 static ng_rcvmsg_t ng_netflow_rcvmsg; 65 static ng_close_t ng_netflow_close; 66 static ng_shutdown_t ng_netflow_rmnode; 67 static ng_newhook_t ng_netflow_newhook; 68 static ng_rcvdata_t ng_netflow_rcvdata; 69 static ng_disconnect_t ng_netflow_disconnect; 70 71 /* Parse type for struct ng_netflow_info */ 72 static const struct ng_parse_struct_field ng_netflow_info_type_fields[] 73 = NG_NETFLOW_INFO_TYPE; 74 static const struct ng_parse_type ng_netflow_info_type = { 75 &ng_parse_struct_type, 76 &ng_netflow_info_type_fields 77 }; 78 79 /* Parse type for struct ng_netflow_ifinfo */ 80 static const struct ng_parse_struct_field ng_netflow_ifinfo_type_fields[] 81 = NG_NETFLOW_IFINFO_TYPE; 82 static const struct ng_parse_type ng_netflow_ifinfo_type = { 83 &ng_parse_struct_type, 84 &ng_netflow_ifinfo_type_fields 85 }; 86 87 /* Parse type for struct ng_netflow_setdlt */ 88 static const struct ng_parse_struct_field ng_netflow_setdlt_type_fields[] 89 = NG_NETFLOW_SETDLT_TYPE; 90 static const struct ng_parse_type ng_netflow_setdlt_type = { 91 &ng_parse_struct_type, 92 &ng_netflow_setdlt_type_fields 93 }; 94 95 /* Parse type for ng_netflow_setifindex */ 96 static const struct ng_parse_struct_field ng_netflow_setifindex_type_fields[] 97 = NG_NETFLOW_SETIFINDEX_TYPE; 98 static const struct ng_parse_type ng_netflow_setifindex_type = { 99 &ng_parse_struct_type, 100 &ng_netflow_setifindex_type_fields 101 }; 102 103 /* Parse type for ng_netflow_settimeouts */ 104 static const struct ng_parse_struct_field ng_netflow_settimeouts_type_fields[] 105 = NG_NETFLOW_SETTIMEOUTS_TYPE; 106 static const struct ng_parse_type ng_netflow_settimeouts_type = { 107 &ng_parse_struct_type, 108 &ng_netflow_settimeouts_type_fields 109 }; 110 111 /* List of commands and how to convert arguments to/from ASCII */ 112 static const struct ng_cmdlist ng_netflow_cmds[] = { 113 { 114 NGM_NETFLOW_COOKIE, 115 NGM_NETFLOW_INFO, 116 "info", 117 NULL, 118 &ng_netflow_info_type 119 }, 120 { 121 NGM_NETFLOW_COOKIE, 122 NGM_NETFLOW_IFINFO, 123 "ifinfo", 124 &ng_parse_uint8_type, 125 &ng_netflow_ifinfo_type 126 }, 127 { 128 NGM_NETFLOW_COOKIE, 129 NGM_NETFLOW_SETDLT, 130 "setdlt", 131 &ng_netflow_setdlt_type, 132 NULL 133 }, 134 { 135 NGM_NETFLOW_COOKIE, 136 NGM_NETFLOW_SETIFINDEX, 137 "setifindex", 138 &ng_netflow_setifindex_type, 139 NULL 140 }, 141 { 142 NGM_NETFLOW_COOKIE, 143 NGM_NETFLOW_SETTIMEOUTS, 144 "settimeouts", 145 &ng_netflow_settimeouts_type, 146 NULL 147 }, 148 { 0 } 149 }; 150 151 152 /* Netgraph node type descriptor */ 153 static struct ng_type ng_netflow_typestruct = { 154 .version = NG_ABI_VERSION, 155 .name = NG_NETFLOW_NODE_TYPE, 156 .constructor = ng_netflow_constructor, 157 .rcvmsg = ng_netflow_rcvmsg, 158 .close = ng_netflow_close, 159 .shutdown = ng_netflow_rmnode, 160 .newhook = ng_netflow_newhook, 161 .rcvdata = ng_netflow_rcvdata, 162 .disconnect = ng_netflow_disconnect, 163 .cmdlist = ng_netflow_cmds, 164 }; 165 NETGRAPH_INIT(netflow, &ng_netflow_typestruct); 166 167 /* Called at node creation */ 168 static int 169 ng_netflow_constructor (node_p node) 170 { 171 priv_p priv; 172 int error = 0; 173 174 /* Initialize private data */ 175 MALLOC(priv, priv_p, sizeof(*priv), M_NETGRAPH, M_NOWAIT); 176 if (priv == NULL) 177 return (ENOMEM); 178 bzero(priv, sizeof(*priv)); 179 180 /* Make node and its data point at each other */ 181 NG_NODE_SET_PRIVATE(node, priv); 182 priv->node = node; 183 184 /* Initialize timeouts to default values */ 185 priv->info.nfinfo_inact_t = INACTIVE_TIMEOUT; 186 priv->info.nfinfo_act_t = ACTIVE_TIMEOUT; 187 188 /* Initialize callout handle */ 189 callout_init(&priv->exp_callout, 1); 190 191 /* Allocate memory and set up flow cache */ 192 if ((error = ng_netflow_cache_init(priv))) 193 return (error); 194 195 priv->dgram.header.version = htons(NETFLOW_V5); 196 197 return (0); 198 } 199 200 /* 201 * ng_netflow supports two hooks: data and export. 202 * Incoming traffic is expected on data, and expired 203 * netflow datagrams are sent to export. 204 */ 205 static int 206 ng_netflow_newhook(node_p node, hook_p hook, const char *name) 207 { 208 const priv_p priv = NG_NODE_PRIVATE(node); 209 210 if (strncmp(name, NG_NETFLOW_HOOK_DATA, /* an iface hook? */ 211 strlen(NG_NETFLOW_HOOK_DATA)) == 0) { 212 iface_p iface; 213 int ifnum = -1; 214 const char *cp; 215 char *eptr; 216 217 cp = name + strlen(NG_NETFLOW_HOOK_DATA); 218 if (!isdigit(*cp) || (cp[0] == '0' && cp[1] != '\0')) 219 return (EINVAL); 220 221 ifnum = (int)strtoul(cp, &eptr, 10); 222 if (*eptr != '\0' || ifnum < 0 || ifnum >= NG_NETFLOW_MAXIFACES) 223 return (EINVAL); 224 225 /* See if hook is already connected */ 226 if (priv->ifaces[ifnum].hook != NULL) 227 return (EISCONN); 228 229 iface = &priv->ifaces[ifnum]; 230 231 /* Link private info and hook together */ 232 NG_HOOK_SET_PRIVATE(hook, iface); 233 iface->hook = hook; 234 235 /* 236 * In most cases traffic accounting is done on an 237 * Ethernet interface, so default data link type 238 * will be DLT_EN10MB. 239 */ 240 iface->info.ifinfo_dlt = DLT_EN10MB; 241 242 } else if (strcmp(name, NG_NETFLOW_HOOK_EXPORT) == 0) { 243 244 if (priv->export != NULL) 245 return (EISCONN); 246 247 priv->export = hook; 248 249 /* Exporter is ready. Let's schedule expiry. */ 250 callout_reset(&priv->exp_callout, (1*hz), &ng_netflow_expire, 251 (void *)priv); 252 } else 253 return (EINVAL); 254 255 return (0); 256 } 257 258 /* Get a netgraph control message. */ 259 static int 260 ng_netflow_rcvmsg (node_p node, item_p item, hook_p lasthook) 261 { 262 const priv_p priv = NG_NODE_PRIVATE(node); 263 struct ng_mesg *resp = NULL; 264 int error = 0; 265 struct ng_mesg *msg; 266 267 NGI_GET_MSG(item, msg); 268 269 /* Deal with message according to cookie and command */ 270 switch (msg->header.typecookie) { 271 case NGM_NETFLOW_COOKIE: 272 switch (msg->header.cmd) { 273 case NGM_NETFLOW_INFO: 274 { 275 struct ng_netflow_info *i; 276 277 NG_MKRESPONSE(resp, msg, sizeof(struct ng_netflow_info), 278 M_NOWAIT); 279 i = (struct ng_netflow_info *)resp->data; 280 ng_netflow_copyinfo(priv, i); 281 282 break; 283 } 284 case NGM_NETFLOW_IFINFO: 285 { 286 struct ng_netflow_ifinfo *i; 287 const uint8_t *index; 288 289 if (msg->header.arglen != sizeof(uint8_t)) 290 ERROUT(EINVAL); 291 292 index = (uint8_t *)msg->data; 293 294 /* connected iface? */ 295 if (priv->ifaces[*index].hook == NULL) 296 ERROUT(EINVAL); 297 298 NG_MKRESPONSE(resp, msg, 299 sizeof(struct ng_netflow_ifinfo), M_NOWAIT); 300 i = (struct ng_netflow_ifinfo *)resp->data; 301 memcpy((void *)i, (void *)&priv->ifaces[*index].info, 302 sizeof(priv->ifaces[*index].info)); 303 304 break; 305 } 306 case NGM_NETFLOW_SETDLT: 307 { 308 struct ng_netflow_setdlt *set; 309 struct ng_netflow_iface *iface; 310 311 if (msg->header.arglen != sizeof(struct ng_netflow_setdlt)) 312 ERROUT(EINVAL); 313 314 set = (struct ng_netflow_setdlt *)msg->data; 315 iface = &priv->ifaces[set->iface]; 316 317 /* connected iface? */ 318 if (iface->hook == NULL) 319 ERROUT(EINVAL); 320 321 switch (set->dlt) { 322 case DLT_EN10MB: 323 iface->info.ifinfo_dlt = DLT_EN10MB; 324 break; 325 case DLT_RAW: 326 iface->info.ifinfo_dlt = DLT_RAW; 327 break; 328 default: 329 ERROUT(EINVAL); 330 } 331 break; 332 } 333 case NGM_NETFLOW_SETIFINDEX: 334 { 335 struct ng_netflow_setifindex *set; 336 struct ng_netflow_iface *iface; 337 338 if (msg->header.arglen != sizeof(struct ng_netflow_setifindex)) 339 ERROUT(EINVAL); 340 341 set = (struct ng_netflow_setifindex *)msg->data; 342 iface = &priv->ifaces[set->iface]; 343 344 /* connected iface? */ 345 if (iface->hook == NULL) 346 ERROUT(EINVAL); 347 348 iface->info.ifinfo_index = set->index; 349 350 break; 351 } 352 case NGM_NETFLOW_SETTIMEOUTS: 353 { 354 struct ng_netflow_settimeouts *set; 355 356 if (msg->header.arglen != sizeof(struct ng_netflow_settimeouts)) 357 ERROUT(EINVAL); 358 359 set = (struct ng_netflow_settimeouts *)msg->data; 360 361 priv->info.nfinfo_inact_t = set->inactive_timeout; 362 priv->info.nfinfo_act_t = set->active_timeout; 363 364 break; 365 } 366 case NGM_NETFLOW_SHOW: 367 { 368 uint32_t *last; 369 370 if (msg->header.arglen != sizeof(uint32_t)) 371 ERROUT(EINVAL); 372 373 last = (uint32_t *)msg->data; 374 375 NG_MKRESPONSE(resp, msg, NGRESP_SIZE, M_NOWAIT); 376 377 if (!resp) 378 ERROUT(ENOMEM); 379 380 error = ng_netflow_flow_show(priv, *last, resp); 381 382 break; 383 } 384 default: 385 ERROUT(EINVAL); /* unknown command */ 386 break; 387 } 388 break; 389 default: 390 ERROUT(EINVAL); /* incorrect cookie */ 391 break; 392 } 393 394 /* 395 * Take care of synchronous response, if any. 396 * Free memory and return. 397 */ 398 done: 399 NG_RESPOND_MSG(error, node, item, resp); 400 NG_FREE_MSG(msg); 401 402 return (error); 403 } 404 405 /* Receive data on hook. */ 406 static int 407 ng_netflow_rcvdata (hook_p hook, item_p item) 408 { 409 const node_p node = NG_HOOK_NODE(hook); 410 const priv_p priv = NG_NODE_PRIVATE(node); 411 const iface_p iface = NG_HOOK_PRIVATE(hook); 412 struct mbuf *m; 413 int error = 0; 414 415 NGI_GET_M(item, m); 416 if (hook == priv->export) { 417 /* 418 * Data arrived on export hook. 419 * This must not happen. 420 */ 421 printf("ng_netflow: incoming data on export hook!\n"); 422 ERROUT(EINVAL); 423 }; 424 425 /* increase counters */ 426 iface->info.ifinfo_packets++; 427 428 switch (iface->info.ifinfo_dlt) { 429 case DLT_EN10MB: /* Ethernet */ 430 { 431 struct ether_header *eh; 432 uint16_t etype; 433 434 if (CHECK_MLEN(m, (sizeof(struct ether_header)))) 435 ERROUT(EINVAL); 436 437 if (CHECK_PULLUP(m, (sizeof(struct ether_header)))) 438 ERROUT(ENOBUFS); 439 440 eh = mtod(m, struct ether_header *); 441 442 /* make sure this is IP frame */ 443 etype = ntohs(eh->ether_type); 444 switch (etype) { 445 case ETHERTYPE_IP: 446 m_adj(m, sizeof(struct ether_header)); 447 break; 448 default: 449 ERROUT(EINVAL); /* ignore this frame */ 450 } 451 452 break; 453 } 454 case DLT_RAW: 455 break; 456 default: 457 ERROUT(EINVAL); 458 break; 459 } 460 461 if (CHECK_MLEN(m, sizeof(struct ip))) 462 ERROUT(EINVAL); 463 464 if (CHECK_PULLUP(m, sizeof(struct ip))) 465 ERROUT(ENOBUFS); 466 467 error = ng_netflow_flow_add(priv, &m, iface); 468 469 done: 470 if (m) { 471 if (item) 472 NG_FREE_ITEM(item); 473 NG_FREE_M(m); 474 } 475 476 return (error); 477 } 478 479 /* We will be shut down in a moment */ 480 static int 481 ng_netflow_close(node_p node) 482 { 483 const priv_p priv = NG_NODE_PRIVATE(node); 484 485 callout_drain(&priv->exp_callout); 486 ng_netflow_cache_flush(priv); 487 488 return (0); 489 } 490 491 /* Do local shutdown processing. */ 492 static int 493 ng_netflow_rmnode(node_p node) 494 { 495 const priv_p priv = NG_NODE_PRIVATE(node); 496 497 NG_NODE_SET_PRIVATE(node, NULL); 498 NG_NODE_UNREF(priv->node); 499 500 FREE(priv, M_NETGRAPH); 501 502 return (0); 503 } 504 505 /* Hook disconnection. */ 506 static int 507 ng_netflow_disconnect(hook_p hook) 508 { 509 node_p node = NG_HOOK_NODE(hook); 510 priv_p priv = NG_NODE_PRIVATE(node); 511 iface_p iface = NG_HOOK_PRIVATE(hook); 512 513 if (iface != NULL) 514 iface->hook = NULL; 515 516 /* if export hook disconnected stop running expire(). */ 517 if (hook == priv->export) { 518 callout_drain(&priv->exp_callout); 519 priv->export = NULL; 520 } 521 522 /* Removal of the last link destroys the node. */ 523 if (NG_NODE_NUMHOOKS(node) == 0) 524 ng_rmnode_self(node); 525 526 return (0); 527 } 528