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/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 50 #include <netgraph/ng_message.h> 51 #include <netgraph/ng_parse.h> 52 #include <netgraph/netgraph.h> 53 #include <netgraph/netflow/netflow.h> 54 #include <netgraph/netflow/ng_netflow.h> 55 56 /* Netgraph methods */ 57 static ng_constructor_t ng_netflow_constructor; 58 static ng_rcvmsg_t ng_netflow_rcvmsg; 59 static ng_close_t ng_netflow_close; 60 static ng_shutdown_t ng_netflow_rmnode; 61 static ng_newhook_t ng_netflow_newhook; 62 static ng_rcvdata_t ng_netflow_rcvdata; 63 static ng_disconnect_t ng_netflow_disconnect; 64 65 /* Parse type for struct ng_netflow_info */ 66 static const struct ng_parse_struct_field ng_netflow_info_type_fields[] 67 = NG_NETFLOW_INFO_TYPE; 68 static const struct ng_parse_type ng_netflow_info_type = { 69 &ng_parse_struct_type, 70 &ng_netflow_info_type_fields 71 }; 72 73 /* Parse type for struct ng_netflow_ifinfo */ 74 static const struct ng_parse_struct_field ng_netflow_ifinfo_type_fields[] 75 = NG_NETFLOW_IFINFO_TYPE; 76 static const struct ng_parse_type ng_netflow_ifinfo_type = { 77 &ng_parse_struct_type, 78 &ng_netflow_ifinfo_type_fields 79 }; 80 81 /* Parse type for struct ng_netflow_setdlt */ 82 static const struct ng_parse_struct_field ng_netflow_setdlt_type_fields[] 83 = NG_NETFLOW_SETDLT_TYPE; 84 static const struct ng_parse_type ng_netflow_setdlt_type = { 85 &ng_parse_struct_type, 86 &ng_netflow_setdlt_type_fields 87 }; 88 89 /* Parse type for ng_netflow_setifindex */ 90 static const struct ng_parse_struct_field ng_netflow_setifindex_type_fields[] 91 = NG_NETFLOW_SETIFINDEX_TYPE; 92 static const struct ng_parse_type ng_netflow_setifindex_type = { 93 &ng_parse_struct_type, 94 &ng_netflow_setifindex_type_fields 95 }; 96 97 /* Parse type for ng_netflow_settimeouts */ 98 static const struct ng_parse_struct_field ng_netflow_settimeouts_type_fields[] 99 = NG_NETFLOW_SETTIMEOUTS_TYPE; 100 static const struct ng_parse_type ng_netflow_settimeouts_type = { 101 &ng_parse_struct_type, 102 &ng_netflow_settimeouts_type_fields 103 }; 104 105 /* List of commands and how to convert arguments to/from ASCII */ 106 static const struct ng_cmdlist ng_netflow_cmds[] = { 107 { 108 NGM_NETFLOW_COOKIE, 109 NGM_NETFLOW_INFO, 110 "info", 111 NULL, 112 &ng_netflow_info_type 113 }, 114 { 115 NGM_NETFLOW_COOKIE, 116 NGM_NETFLOW_IFINFO, 117 "ifinfo", 118 &ng_parse_uint16_type, 119 &ng_netflow_ifinfo_type 120 }, 121 { 122 NGM_NETFLOW_COOKIE, 123 NGM_NETFLOW_SETDLT, 124 "setdlt", 125 &ng_netflow_setdlt_type, 126 NULL 127 }, 128 { 129 NGM_NETFLOW_COOKIE, 130 NGM_NETFLOW_SETIFINDEX, 131 "setifindex", 132 &ng_netflow_setifindex_type, 133 NULL 134 }, 135 { 136 NGM_NETFLOW_COOKIE, 137 NGM_NETFLOW_SETTIMEOUTS, 138 "settimeouts", 139 &ng_netflow_settimeouts_type, 140 NULL 141 }, 142 { 0 } 143 }; 144 145 146 /* Netgraph node type descriptor */ 147 static struct ng_type ng_netflow_typestruct = { 148 .version = NG_ABI_VERSION, 149 .name = NG_NETFLOW_NODE_TYPE, 150 .constructor = ng_netflow_constructor, 151 .rcvmsg = ng_netflow_rcvmsg, 152 .close = ng_netflow_close, 153 .shutdown = ng_netflow_rmnode, 154 .newhook = ng_netflow_newhook, 155 .rcvdata = ng_netflow_rcvdata, 156 .disconnect = ng_netflow_disconnect, 157 .cmdlist = ng_netflow_cmds, 158 }; 159 NETGRAPH_INIT(netflow, &ng_netflow_typestruct); 160 161 /* Called at node creation */ 162 static int 163 ng_netflow_constructor (node_p node) 164 { 165 priv_p priv; 166 int error = 0; 167 168 /* Initialize private data */ 169 MALLOC(priv, priv_p, sizeof(*priv), M_NETGRAPH, M_NOWAIT); 170 if (priv == NULL) 171 return (ENOMEM); 172 bzero(priv, sizeof(*priv)); 173 174 /* Make node and its data point at each other */ 175 NG_NODE_SET_PRIVATE(node, priv); 176 priv->node = node; 177 178 /* Initialize timeouts to default values */ 179 priv->info.nfinfo_inact_t = INACTIVE_TIMEOUT; 180 priv->info.nfinfo_act_t = ACTIVE_TIMEOUT; 181 182 /* Initialize callout handle */ 183 callout_init(&priv->exp_callout, 1); 184 185 /* Allocate memory and set up flow cache */ 186 if ((error = ng_netflow_cache_init(priv))) 187 return (error); 188 189 priv->dgram.header.version = htons(NETFLOW_V5); 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 (strcmp(name, NG_NETFLOW_HOOK_EXPORT) == 0) { 237 238 if (priv->export != NULL) 239 return (EISCONN); 240 241 priv->export = hook; 242 243 /* Exporter is ready. Let's schedule expiry. */ 244 callout_reset(&priv->exp_callout, (1*hz), &ng_netflow_expire, 245 (void *)priv); 246 } else 247 return (EINVAL); 248 249 return (0); 250 } 251 252 /* Get a netgraph control message. */ 253 static int 254 ng_netflow_rcvmsg (node_p node, item_p item, hook_p lasthook) 255 { 256 const priv_p priv = NG_NODE_PRIVATE(node); 257 struct ng_mesg *resp = NULL; 258 int error = 0; 259 struct ng_mesg *msg; 260 261 NGI_GET_MSG(item, msg); 262 263 /* Deal with message according to cookie and command */ 264 switch (msg->header.typecookie) { 265 case NGM_NETFLOW_COOKIE: 266 switch (msg->header.cmd) { 267 case NGM_NETFLOW_INFO: 268 { 269 struct ng_netflow_info *i; 270 271 NG_MKRESPONSE(resp, msg, sizeof(struct ng_netflow_info), 272 M_NOWAIT); 273 i = (struct ng_netflow_info *)resp->data; 274 ng_netflow_copyinfo(priv, i); 275 276 break; 277 } 278 case NGM_NETFLOW_IFINFO: 279 { 280 struct ng_netflow_ifinfo *i; 281 const uint16_t *index; 282 283 if (msg->header.arglen != sizeof(uint16_t)) 284 ERROUT(EINVAL); 285 286 index = (uint16_t *)msg->data; 287 if (*index > NG_NETFLOW_MAXIFACES) 288 ERROUT(EINVAL); 289 290 /* connected iface? */ 291 if (priv->ifaces[*index].hook == NULL) 292 ERROUT(EINVAL); 293 294 NG_MKRESPONSE(resp, msg, 295 sizeof(struct ng_netflow_ifinfo), M_NOWAIT); 296 i = (struct ng_netflow_ifinfo *)resp->data; 297 memcpy((void *)i, (void *)&priv->ifaces[*index].info, 298 sizeof(priv->ifaces[*index].info)); 299 300 break; 301 } 302 case NGM_NETFLOW_SETDLT: 303 { 304 struct ng_netflow_setdlt *set; 305 struct ng_netflow_iface *iface; 306 307 if (msg->header.arglen != sizeof(struct ng_netflow_setdlt)) 308 ERROUT(EINVAL); 309 310 set = (struct ng_netflow_setdlt *)msg->data; 311 if (set->iface > NG_NETFLOW_MAXIFACES) 312 ERROUT(EINVAL); 313 iface = &priv->ifaces[set->iface]; 314 315 /* connected iface? */ 316 if (iface->hook == NULL) 317 ERROUT(EINVAL); 318 319 switch (set->dlt) { 320 case DLT_EN10MB: 321 iface->info.ifinfo_dlt = DLT_EN10MB; 322 break; 323 case DLT_RAW: 324 iface->info.ifinfo_dlt = DLT_RAW; 325 break; 326 default: 327 ERROUT(EINVAL); 328 } 329 break; 330 } 331 case NGM_NETFLOW_SETIFINDEX: 332 { 333 struct ng_netflow_setifindex *set; 334 struct ng_netflow_iface *iface; 335 336 if (msg->header.arglen != sizeof(struct ng_netflow_setifindex)) 337 ERROUT(EINVAL); 338 339 set = (struct ng_netflow_setifindex *)msg->data; 340 if (set->iface > NG_NETFLOW_MAXIFACES) 341 ERROUT(EINVAL); 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 log(LOG_ERR, "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