1 /*- 2 * Copyright (c) 2004 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/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/bpf.h> 45 #include <netinet/in.h> 46 #include <netinet/in_systm.h> 47 #include <netinet/ip.h> 48 49 #include <netgraph/ng_message.h> 50 #include <netgraph/ng_parse.h> 51 #include <netgraph/netgraph.h> 52 #include <netgraph/netflow/netflow.h> 53 #include <netgraph/netflow/ng_netflow.h> 54 55 /* Netgraph methods */ 56 static ng_constructor_t ng_netflow_constructor; 57 static ng_rcvmsg_t ng_netflow_rcvmsg; 58 static ng_close_t ng_netflow_close; 59 static ng_shutdown_t ng_netflow_rmnode; 60 static ng_newhook_t ng_netflow_newhook; 61 static ng_rcvdata_t ng_netflow_rcvdata; 62 static ng_disconnect_t ng_netflow_disconnect; 63 64 /* Parse type for struct ng_netflow_info */ 65 static const struct ng_parse_struct_field ng_netflow_info_type_fields[] 66 = NG_NETFLOW_INFO_TYPE; 67 static const struct ng_parse_type ng_netflow_info_type = { 68 &ng_parse_struct_type, 69 &ng_netflow_info_type_fields 70 }; 71 72 /* Parse type for struct ng_netflow_ifinfo */ 73 static const struct ng_parse_struct_field ng_netflow_ifinfo_type_fields[] 74 = NG_NETFLOW_IFINFO_TYPE; 75 static const struct ng_parse_type ng_netflow_ifinfo_type = { 76 &ng_parse_struct_type, 77 &ng_netflow_ifinfo_type_fields 78 }; 79 80 /* Parse type for struct ng_netflow_setdlt */ 81 static const struct ng_parse_struct_field ng_netflow_setdlt_type_fields[] 82 = NG_NETFLOW_SETDLT_TYPE; 83 static const struct ng_parse_type ng_netflow_setdlt_type = { 84 &ng_parse_struct_type, 85 &ng_netflow_setdlt_type_fields 86 }; 87 88 /* Parse type for ng_netflow_setifindex */ 89 static const struct ng_parse_struct_field ng_netflow_setifindex_type_fields[] 90 = NG_NETFLOW_SETIFINDEX_TYPE; 91 static const struct ng_parse_type ng_netflow_setifindex_type = { 92 &ng_parse_struct_type, 93 &ng_netflow_setifindex_type_fields 94 }; 95 96 /* Parse type for ng_netflow_settimeouts */ 97 static const struct ng_parse_struct_field ng_netflow_settimeouts_type_fields[] 98 = NG_NETFLOW_SETTIMEOUTS_TYPE; 99 static const struct ng_parse_type ng_netflow_settimeouts_type = { 100 &ng_parse_struct_type, 101 &ng_netflow_settimeouts_type_fields 102 }; 103 104 /* List of commands and how to convert arguments to/from ASCII */ 105 static const struct ng_cmdlist ng_netflow_cmds[] = { 106 { 107 NGM_NETFLOW_COOKIE, 108 NGM_NETFLOW_INFO, 109 "info", 110 NULL, 111 &ng_netflow_info_type 112 }, 113 { 114 NGM_NETFLOW_COOKIE, 115 NGM_NETFLOW_IFINFO, 116 "ifinfo", 117 &ng_parse_uint16_type, 118 &ng_netflow_ifinfo_type 119 }, 120 { 121 NGM_NETFLOW_COOKIE, 122 NGM_NETFLOW_SETDLT, 123 "setdlt", 124 &ng_netflow_setdlt_type, 125 NULL 126 }, 127 { 128 NGM_NETFLOW_COOKIE, 129 NGM_NETFLOW_SETIFINDEX, 130 "setifindex", 131 &ng_netflow_setifindex_type, 132 NULL 133 }, 134 { 135 NGM_NETFLOW_COOKIE, 136 NGM_NETFLOW_SETTIMEOUTS, 137 "settimeouts", 138 &ng_netflow_settimeouts_type, 139 NULL 140 }, 141 { 0 } 142 }; 143 144 145 /* Netgraph node type descriptor */ 146 static struct ng_type ng_netflow_typestruct = { 147 .version = NG_ABI_VERSION, 148 .name = NG_NETFLOW_NODE_TYPE, 149 .constructor = ng_netflow_constructor, 150 .rcvmsg = ng_netflow_rcvmsg, 151 .close = ng_netflow_close, 152 .shutdown = ng_netflow_rmnode, 153 .newhook = ng_netflow_newhook, 154 .rcvdata = ng_netflow_rcvdata, 155 .disconnect = ng_netflow_disconnect, 156 .cmdlist = ng_netflow_cmds, 157 }; 158 NETGRAPH_INIT(netflow, &ng_netflow_typestruct); 159 160 /* Called at node creation */ 161 static int 162 ng_netflow_constructor (node_p node) 163 { 164 priv_p priv; 165 int error = 0; 166 167 /* Initialize private data */ 168 MALLOC(priv, priv_p, sizeof(*priv), M_NETGRAPH, M_NOWAIT); 169 if (priv == NULL) 170 return (ENOMEM); 171 bzero(priv, sizeof(*priv)); 172 173 /* Make node and its data point at each other */ 174 NG_NODE_SET_PRIVATE(node, priv); 175 priv->node = node; 176 177 /* Initialize timeouts to default values */ 178 priv->info.nfinfo_inact_t = INACTIVE_TIMEOUT; 179 priv->info.nfinfo_act_t = ACTIVE_TIMEOUT; 180 181 /* Initialize callout handle */ 182 callout_init(&priv->exp_callout, 1); 183 184 /* Allocate memory and set up flow cache */ 185 if ((error = ng_netflow_cache_init(priv))) 186 return (error); 187 188 priv->dgram.header.version = htons(NETFLOW_V5); 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 (strcmp(name, NG_NETFLOW_HOOK_EXPORT) == 0) { 236 237 if (priv->export != NULL) 238 return (EISCONN); 239 240 priv->export = hook; 241 242 /* Exporter is ready. Let's schedule expiry. */ 243 callout_reset(&priv->exp_callout, (1*hz), &ng_netflow_expire, 244 (void *)priv); 245 } else 246 return (EINVAL); 247 248 return (0); 249 } 250 251 /* Get a netgraph control message. */ 252 static int 253 ng_netflow_rcvmsg (node_p node, item_p item, hook_p lasthook) 254 { 255 const priv_p priv = NG_NODE_PRIVATE(node); 256 struct ng_mesg *resp = NULL; 257 int error = 0; 258 struct ng_mesg *msg; 259 260 NGI_GET_MSG(item, msg); 261 262 /* Deal with message according to cookie and command */ 263 switch (msg->header.typecookie) { 264 case NGM_NETFLOW_COOKIE: 265 switch (msg->header.cmd) { 266 case NGM_NETFLOW_INFO: 267 { 268 struct ng_netflow_info *i; 269 270 NG_MKRESPONSE(resp, msg, sizeof(struct ng_netflow_info), 271 M_NOWAIT); 272 i = (struct ng_netflow_info *)resp->data; 273 ng_netflow_copyinfo(priv, i); 274 275 break; 276 } 277 case NGM_NETFLOW_IFINFO: 278 { 279 struct ng_netflow_ifinfo *i; 280 const uint16_t *index; 281 282 if (msg->header.arglen != sizeof(uint16_t)) 283 ERROUT(EINVAL); 284 285 index = (uint16_t *)msg->data; 286 if (*index > NG_NETFLOW_MAXIFACES) 287 ERROUT(EINVAL); 288 289 /* connected iface? */ 290 if (priv->ifaces[*index].hook == NULL) 291 ERROUT(EINVAL); 292 293 NG_MKRESPONSE(resp, msg, 294 sizeof(struct ng_netflow_ifinfo), M_NOWAIT); 295 i = (struct ng_netflow_ifinfo *)resp->data; 296 memcpy((void *)i, (void *)&priv->ifaces[*index].info, 297 sizeof(priv->ifaces[*index].info)); 298 299 break; 300 } 301 case NGM_NETFLOW_SETDLT: 302 { 303 struct ng_netflow_setdlt *set; 304 struct ng_netflow_iface *iface; 305 306 if (msg->header.arglen != sizeof(struct ng_netflow_setdlt)) 307 ERROUT(EINVAL); 308 309 set = (struct ng_netflow_setdlt *)msg->data; 310 if (set->iface > NG_NETFLOW_MAXIFACES) 311 ERROUT(EINVAL); 312 iface = &priv->ifaces[set->iface]; 313 314 /* connected iface? */ 315 if (iface->hook == NULL) 316 ERROUT(EINVAL); 317 318 switch (set->dlt) { 319 case DLT_EN10MB: 320 iface->info.ifinfo_dlt = DLT_EN10MB; 321 break; 322 case DLT_RAW: 323 iface->info.ifinfo_dlt = DLT_RAW; 324 break; 325 default: 326 ERROUT(EINVAL); 327 } 328 break; 329 } 330 case NGM_NETFLOW_SETIFINDEX: 331 { 332 struct ng_netflow_setifindex *set; 333 struct ng_netflow_iface *iface; 334 335 if (msg->header.arglen != sizeof(struct ng_netflow_setifindex)) 336 ERROUT(EINVAL); 337 338 set = (struct ng_netflow_setifindex *)msg->data; 339 if (set->iface > NG_NETFLOW_MAXIFACES) 340 ERROUT(EINVAL); 341 iface = &priv->ifaces[set->iface]; 342 343 /* connected iface? */ 344 if (iface->hook == NULL) 345 ERROUT(EINVAL); 346 347 iface->info.ifinfo_index = set->index; 348 349 break; 350 } 351 case NGM_NETFLOW_SETTIMEOUTS: 352 { 353 struct ng_netflow_settimeouts *set; 354 355 if (msg->header.arglen != sizeof(struct ng_netflow_settimeouts)) 356 ERROUT(EINVAL); 357 358 set = (struct ng_netflow_settimeouts *)msg->data; 359 360 priv->info.nfinfo_inact_t = set->inactive_timeout; 361 priv->info.nfinfo_act_t = set->active_timeout; 362 363 break; 364 } 365 case NGM_NETFLOW_SHOW: 366 { 367 uint32_t *last; 368 369 if (msg->header.arglen != sizeof(uint32_t)) 370 ERROUT(EINVAL); 371 372 last = (uint32_t *)msg->data; 373 374 NG_MKRESPONSE(resp, msg, NGRESP_SIZE, M_NOWAIT); 375 376 if (!resp) 377 ERROUT(ENOMEM); 378 379 error = ng_netflow_flow_show(priv, *last, resp); 380 381 break; 382 } 383 default: 384 ERROUT(EINVAL); /* unknown command */ 385 break; 386 } 387 break; 388 default: 389 ERROUT(EINVAL); /* incorrect cookie */ 390 break; 391 } 392 393 /* 394 * Take care of synchronous response, if any. 395 * Free memory and return. 396 */ 397 done: 398 NG_RESPOND_MSG(error, node, item, resp); 399 NG_FREE_MSG(msg); 400 401 return (error); 402 } 403 404 /* Receive data on hook. */ 405 static int 406 ng_netflow_rcvdata (hook_p hook, item_p item) 407 { 408 const node_p node = NG_HOOK_NODE(hook); 409 const priv_p priv = NG_NODE_PRIVATE(node); 410 const iface_p iface = NG_HOOK_PRIVATE(hook); 411 struct mbuf *m; 412 int error = 0; 413 414 NGI_GET_M(item, m); 415 if (hook == priv->export) { 416 /* 417 * Data arrived on export hook. 418 * This must not happen. 419 */ 420 printf("ng_netflow: incoming data on export hook!\n"); 421 ERROUT(EINVAL); 422 }; 423 424 /* increase counters */ 425 iface->info.ifinfo_packets++; 426 427 switch (iface->info.ifinfo_dlt) { 428 case DLT_EN10MB: /* Ethernet */ 429 { 430 struct ether_header *eh; 431 uint16_t etype; 432 433 if (CHECK_MLEN(m, (sizeof(struct ether_header)))) 434 ERROUT(EINVAL); 435 436 if (CHECK_PULLUP(m, (sizeof(struct ether_header)))) 437 ERROUT(ENOBUFS); 438 439 eh = mtod(m, struct ether_header *); 440 441 /* make sure this is IP frame */ 442 etype = ntohs(eh->ether_type); 443 switch (etype) { 444 case ETHERTYPE_IP: 445 m_adj(m, sizeof(struct ether_header)); 446 break; 447 default: 448 ERROUT(EINVAL); /* ignore this frame */ 449 } 450 451 break; 452 } 453 case DLT_RAW: 454 break; 455 default: 456 ERROUT(EINVAL); 457 break; 458 } 459 460 if (CHECK_MLEN(m, sizeof(struct ip))) 461 ERROUT(EINVAL); 462 463 if (CHECK_PULLUP(m, sizeof(struct ip))) 464 ERROUT(ENOBUFS); 465 466 error = ng_netflow_flow_add(priv, &m, iface); 467 468 done: 469 if (m) { 470 if (item) 471 NG_FREE_ITEM(item); 472 NG_FREE_M(m); 473 } 474 475 return (error); 476 } 477 478 /* We will be shut down in a moment */ 479 static int 480 ng_netflow_close(node_p node) 481 { 482 const priv_p priv = NG_NODE_PRIVATE(node); 483 484 callout_drain(&priv->exp_callout); 485 ng_netflow_cache_flush(priv); 486 487 return (0); 488 } 489 490 /* Do local shutdown processing. */ 491 static int 492 ng_netflow_rmnode(node_p node) 493 { 494 const priv_p priv = NG_NODE_PRIVATE(node); 495 496 NG_NODE_SET_PRIVATE(node, NULL); 497 NG_NODE_UNREF(priv->node); 498 499 FREE(priv, M_NETGRAPH); 500 501 return (0); 502 } 503 504 /* Hook disconnection. */ 505 static int 506 ng_netflow_disconnect(hook_p hook) 507 { 508 node_p node = NG_HOOK_NODE(hook); 509 priv_p priv = NG_NODE_PRIVATE(node); 510 iface_p iface = NG_HOOK_PRIVATE(hook); 511 512 if (iface != NULL) 513 iface->hook = NULL; 514 515 /* if export hook disconnected stop running expire(). */ 516 if (hook == priv->export) { 517 callout_drain(&priv->exp_callout); 518 priv->export = NULL; 519 } 520 521 /* Removal of the last link destroys the node. */ 522 if (NG_NODE_NUMHOOKS(node) == 0) 523 ng_rmnode_self(node); 524 525 return (0); 526 } 527