2 * Copyright (c) 2004-2005 Gleb Smirnoff <glebius@FreeBSD.org>
3 * Copyright (c) 2001-2003 Roman V. Palagin <romanp@unshadow.net>
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
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.
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
27 * $SourceForge: ng_netflow.c,v 1.30 2004/09/05 11:37:43 glebius Exp $
30 static const char rcs_id[] =
33 #include <sys/param.h>
34 #include <sys/systm.h>
35 #include <sys/kernel.h>
36 #include <sys/limits.h>
38 #include <sys/socket.h>
39 #include <sys/syslog.h>
40 #include <sys/ctype.h>
43 #include <net/ethernet.h>
44 #include <net/if_arp.h>
45 #include <net/if_var.h>
46 #include <net/if_vlan_var.h>
48 #include <netinet/in.h>
49 #include <netinet/in_systm.h>
50 #include <netinet/ip.h>
51 #include <netinet/tcp.h>
52 #include <netinet/udp.h>
54 #include <netgraph/ng_message.h>
55 #include <netgraph/ng_parse.h>
56 #include <netgraph/netgraph.h>
57 #include <netgraph/netflow/netflow.h>
58 #include <netgraph/netflow/ng_netflow.h>
60 /* Netgraph methods */
61 static ng_constructor_t ng_netflow_constructor;
62 static ng_rcvmsg_t ng_netflow_rcvmsg;
63 static ng_close_t ng_netflow_close;
64 static ng_shutdown_t ng_netflow_rmnode;
65 static ng_newhook_t ng_netflow_newhook;
66 static ng_rcvdata_t ng_netflow_rcvdata;
67 static ng_disconnect_t ng_netflow_disconnect;
69 /* Parse type for struct ng_netflow_info */
70 static const struct ng_parse_struct_field ng_netflow_info_type_fields[]
71 = NG_NETFLOW_INFO_TYPE;
72 static const struct ng_parse_type ng_netflow_info_type = {
73 &ng_parse_struct_type,
74 &ng_netflow_info_type_fields
77 /* Parse type for struct ng_netflow_ifinfo */
78 static const struct ng_parse_struct_field ng_netflow_ifinfo_type_fields[]
79 = NG_NETFLOW_IFINFO_TYPE;
80 static const struct ng_parse_type ng_netflow_ifinfo_type = {
81 &ng_parse_struct_type,
82 &ng_netflow_ifinfo_type_fields
85 /* Parse type for struct ng_netflow_setdlt */
86 static const struct ng_parse_struct_field ng_netflow_setdlt_type_fields[]
87 = NG_NETFLOW_SETDLT_TYPE;
88 static const struct ng_parse_type ng_netflow_setdlt_type = {
89 &ng_parse_struct_type,
90 &ng_netflow_setdlt_type_fields
93 /* Parse type for ng_netflow_setifindex */
94 static const struct ng_parse_struct_field ng_netflow_setifindex_type_fields[]
95 = NG_NETFLOW_SETIFINDEX_TYPE;
96 static const struct ng_parse_type ng_netflow_setifindex_type = {
97 &ng_parse_struct_type,
98 &ng_netflow_setifindex_type_fields
101 /* Parse type for ng_netflow_settimeouts */
102 static const struct ng_parse_struct_field ng_netflow_settimeouts_type_fields[]
103 = NG_NETFLOW_SETTIMEOUTS_TYPE;
104 static const struct ng_parse_type ng_netflow_settimeouts_type = {
105 &ng_parse_struct_type,
106 &ng_netflow_settimeouts_type_fields
109 /* Parse type for ng_netflow_setconfig */
110 static const struct ng_parse_struct_field ng_netflow_setconfig_type_fields[]
111 = NG_NETFLOW_SETCONFIG_TYPE;
112 static const struct ng_parse_type ng_netflow_setconfig_type = {
113 &ng_parse_struct_type,
114 &ng_netflow_setconfig_type_fields
117 /* List of commands and how to convert arguments to/from ASCII */
118 static const struct ng_cmdlist ng_netflow_cmds[] = {
124 &ng_netflow_info_type
130 &ng_parse_uint16_type,
131 &ng_netflow_ifinfo_type
137 &ng_netflow_setdlt_type,
142 NGM_NETFLOW_SETIFINDEX,
144 &ng_netflow_setifindex_type,
149 NGM_NETFLOW_SETTIMEOUTS,
151 &ng_netflow_settimeouts_type,
156 NGM_NETFLOW_SETCONFIG,
158 &ng_netflow_setconfig_type,
165 /* Netgraph node type descriptor */
166 static struct ng_type ng_netflow_typestruct = {
167 .version = NG_ABI_VERSION,
168 .name = NG_NETFLOW_NODE_TYPE,
169 .constructor = ng_netflow_constructor,
170 .rcvmsg = ng_netflow_rcvmsg,
171 .close = ng_netflow_close,
172 .shutdown = ng_netflow_rmnode,
173 .newhook = ng_netflow_newhook,
174 .rcvdata = ng_netflow_rcvdata,
175 .disconnect = ng_netflow_disconnect,
176 .cmdlist = ng_netflow_cmds,
178 NETGRAPH_INIT(netflow, &ng_netflow_typestruct);
180 /* Called at node creation */
182 ng_netflow_constructor(node_p node)
187 /* Initialize private data */
188 priv = malloc(sizeof(*priv), M_NETGRAPH, M_NOWAIT);
191 bzero(priv, sizeof(*priv));
193 /* Make node and its data point at each other */
194 NG_NODE_SET_PRIVATE(node, priv);
197 /* Initialize timeouts to default values */
198 priv->info.nfinfo_inact_t = INACTIVE_TIMEOUT;
199 priv->info.nfinfo_act_t = ACTIVE_TIMEOUT;
201 /* Set default config */
202 for (i = 0; i < NG_NETFLOW_MAXIFACES; i++)
203 priv->ifaces[i].info.conf = NG_NETFLOW_CONF_INGRESS;
205 /* Initialize callout handle */
206 callout_init(&priv->exp_callout, CALLOUT_MPSAFE);
208 /* Allocate memory and set up flow cache */
209 if ((error = ng_netflow_cache_init(priv)))
216 * ng_netflow supports two hooks: data and export.
217 * Incoming traffic is expected on data, and expired
218 * netflow datagrams are sent to export.
221 ng_netflow_newhook(node_p node, hook_p hook, const char *name)
223 const priv_p priv = NG_NODE_PRIVATE(node);
225 if (strncmp(name, NG_NETFLOW_HOOK_DATA, /* an iface hook? */
226 strlen(NG_NETFLOW_HOOK_DATA)) == 0) {
232 cp = name + strlen(NG_NETFLOW_HOOK_DATA);
233 if (!isdigit(*cp) || (cp[0] == '0' && cp[1] != '\0'))
236 ifnum = (int)strtoul(cp, &eptr, 10);
237 if (*eptr != '\0' || ifnum < 0 || ifnum >= NG_NETFLOW_MAXIFACES)
240 /* See if hook is already connected */
241 if (priv->ifaces[ifnum].hook != NULL)
244 iface = &priv->ifaces[ifnum];
246 /* Link private info and hook together */
247 NG_HOOK_SET_PRIVATE(hook, iface);
251 * In most cases traffic accounting is done on an
252 * Ethernet interface, so default data link type
253 * will be DLT_EN10MB.
255 iface->info.ifinfo_dlt = DLT_EN10MB;
257 } else if (strncmp(name, NG_NETFLOW_HOOK_OUT,
258 strlen(NG_NETFLOW_HOOK_OUT)) == 0) {
264 cp = name + strlen(NG_NETFLOW_HOOK_OUT);
265 if (!isdigit(*cp) || (cp[0] == '0' && cp[1] != '\0'))
268 ifnum = (int)strtoul(cp, &eptr, 10);
269 if (*eptr != '\0' || ifnum < 0 || ifnum >= NG_NETFLOW_MAXIFACES)
272 /* See if hook is already connected */
273 if (priv->ifaces[ifnum].out != NULL)
276 iface = &priv->ifaces[ifnum];
278 /* Link private info and hook together */
279 NG_HOOK_SET_PRIVATE(hook, iface);
282 } else if (strcmp(name, NG_NETFLOW_HOOK_EXPORT) == 0) {
284 if (priv->export != NULL)
289 /* Exporter is ready. Let's schedule expiry. */
290 callout_reset(&priv->exp_callout, (1*hz), &ng_netflow_expire,
298 /* Get a netgraph control message. */
300 ng_netflow_rcvmsg (node_p node, item_p item, hook_p lasthook)
302 const priv_p priv = NG_NODE_PRIVATE(node);
303 struct ng_mesg *resp = NULL;
307 NGI_GET_MSG(item, msg);
309 /* Deal with message according to cookie and command */
310 switch (msg->header.typecookie) {
311 case NGM_NETFLOW_COOKIE:
312 switch (msg->header.cmd) {
313 case NGM_NETFLOW_INFO:
315 struct ng_netflow_info *i;
317 NG_MKRESPONSE(resp, msg, sizeof(struct ng_netflow_info),
319 i = (struct ng_netflow_info *)resp->data;
320 ng_netflow_copyinfo(priv, i);
324 case NGM_NETFLOW_IFINFO:
326 struct ng_netflow_ifinfo *i;
327 const uint16_t *index;
329 if (msg->header.arglen != sizeof(uint16_t))
332 index = (uint16_t *)msg->data;
333 if (*index >= NG_NETFLOW_MAXIFACES)
336 /* connected iface? */
337 if (priv->ifaces[*index].hook == NULL)
340 NG_MKRESPONSE(resp, msg,
341 sizeof(struct ng_netflow_ifinfo), M_NOWAIT);
342 i = (struct ng_netflow_ifinfo *)resp->data;
343 memcpy((void *)i, (void *)&priv->ifaces[*index].info,
344 sizeof(priv->ifaces[*index].info));
348 case NGM_NETFLOW_SETDLT:
350 struct ng_netflow_setdlt *set;
351 struct ng_netflow_iface *iface;
353 if (msg->header.arglen != sizeof(struct ng_netflow_setdlt))
356 set = (struct ng_netflow_setdlt *)msg->data;
357 if (set->iface >= NG_NETFLOW_MAXIFACES)
359 iface = &priv->ifaces[set->iface];
361 /* connected iface? */
362 if (iface->hook == NULL)
367 iface->info.ifinfo_dlt = DLT_EN10MB;
370 iface->info.ifinfo_dlt = DLT_RAW;
377 case NGM_NETFLOW_SETIFINDEX:
379 struct ng_netflow_setifindex *set;
380 struct ng_netflow_iface *iface;
382 if (msg->header.arglen != sizeof(struct ng_netflow_setifindex))
385 set = (struct ng_netflow_setifindex *)msg->data;
386 if (set->iface >= NG_NETFLOW_MAXIFACES)
388 iface = &priv->ifaces[set->iface];
390 /* connected iface? */
391 if (iface->hook == NULL)
394 iface->info.ifinfo_index = set->index;
398 case NGM_NETFLOW_SETTIMEOUTS:
400 struct ng_netflow_settimeouts *set;
402 if (msg->header.arglen != sizeof(struct ng_netflow_settimeouts))
405 set = (struct ng_netflow_settimeouts *)msg->data;
407 priv->info.nfinfo_inact_t = set->inactive_timeout;
408 priv->info.nfinfo_act_t = set->active_timeout;
412 case NGM_NETFLOW_SETCONFIG:
414 struct ng_netflow_setconfig *set;
416 if (msg->header.arglen != sizeof(struct ng_netflow_setconfig))
419 set = (struct ng_netflow_setconfig *)msg->data;
421 if (set->iface >= NG_NETFLOW_MAXIFACES)
424 priv->ifaces[set->iface].info.conf = set->conf;
428 case NGM_NETFLOW_SHOW:
432 if (msg->header.arglen != sizeof(uint32_t))
435 last = (uint32_t *)msg->data;
437 NG_MKRESPONSE(resp, msg, NGRESP_SIZE, M_NOWAIT);
442 error = ng_netflow_flow_show(priv, *last, resp);
447 ERROUT(EINVAL); /* unknown command */
452 ERROUT(EINVAL); /* incorrect cookie */
457 * Take care of synchronous response, if any.
458 * Free memory and return.
461 NG_RESPOND_MSG(error, node, item, resp);
467 /* Receive data on hook. */
469 ng_netflow_rcvdata (hook_p hook, item_p item)
471 const node_p node = NG_HOOK_NODE(hook);
472 const priv_p priv = NG_NODE_PRIVATE(node);
473 const iface_p iface = NG_HOOK_PRIVATE(hook);
475 struct mbuf *m = NULL;
479 int error = 0, bypass = 0;
480 unsigned int src_if_index;
482 if (hook == priv->export) {
484 * Data arrived on export hook.
485 * This must not happen.
487 log(LOG_ERR, "ng_netflow: incoming data on export hook!\n");
491 if (hook == iface->hook) {
492 if ((iface->info.conf & NG_NETFLOW_CONF_INGRESS) == 0)
495 } else if (hook == iface->out) {
496 if ((iface->info.conf & NG_NETFLOW_CONF_EGRESS) == 0)
503 (iface->info.conf & (NG_NETFLOW_CONF_ONCE | NG_NETFLOW_CONF_THISONCE))) {
504 mtag = m_tag_locate(NGI_M(item), MTAG_NETFLOW,
505 MTAG_NETFLOW_CALLED, NULL);
506 while (mtag != NULL) {
507 if ((iface->info.conf & NG_NETFLOW_CONF_ONCE) ||
508 ((ng_ID_t *)(mtag + 1))[0] == NG_NODE_ID(node)) {
512 mtag = m_tag_locate(NGI_M(item), MTAG_NETFLOW,
513 MTAG_NETFLOW_CALLED, mtag);
521 NG_FWD_ITEM_HOOK(error, item, out);
525 if (iface->info.conf & (NG_NETFLOW_CONF_ONCE | NG_NETFLOW_CONF_THISONCE)) {
526 mtag = m_tag_alloc(MTAG_NETFLOW, MTAG_NETFLOW_CALLED,
527 sizeof(ng_ID_t), M_NOWAIT);
529 ((ng_ID_t *)(mtag + 1))[0] = NG_NODE_ID(node);
530 m_tag_prepend(NGI_M(item), mtag);
536 /* Increase counters. */
537 iface->info.ifinfo_packets++;
540 * Depending on interface data link type and packet contents
541 * we pullup enough data, so that ng_netflow_flow_add() does not
542 * need to know about mbuf at all. We keep current length of data
543 * needed to be contiguous in pullup_len. mtod() is done at the
544 * very end one more time, since m can had changed after pulluping.
546 * In case of unrecognized data we don't return error, but just
547 * pass data to downstream hook, if it is available.
550 #define M_CHECK(length) do { \
551 pullup_len += length; \
552 if ((m)->m_pkthdr.len < (pullup_len)) { \
556 if ((m)->m_len < (pullup_len) && \
557 (((m) = m_pullup((m),(pullup_len))) == NULL)) { \
563 switch (iface->info.ifinfo_dlt) {
564 case DLT_EN10MB: /* Ethernet */
566 struct ether_header *eh;
569 M_CHECK(sizeof(struct ether_header));
570 eh = mtod(m, struct ether_header *);
572 /* Make sure this is IP frame. */
573 etype = ntohs(eh->ether_type);
576 M_CHECK(sizeof(struct ip));
577 eh = mtod(m, struct ether_header *);
578 ip = (struct ip *)(eh + 1);
582 struct ether_vlan_header *evh;
584 M_CHECK(sizeof(struct ether_vlan_header) -
585 sizeof(struct ether_header));
586 evh = mtod(m, struct ether_vlan_header *);
587 if (ntohs(evh->evl_proto) == ETHERTYPE_IP) {
588 M_CHECK(sizeof(struct ip));
589 ip = (struct ip *)(evh + 1);
594 goto bypass; /* pass this frame */
598 case DLT_RAW: /* IP packets */
599 M_CHECK(sizeof(struct ip));
600 ip = mtod(m, struct ip *);
607 if ((ip->ip_off & htons(IP_OFFMASK)) == 0) {
609 * In case of IP header with options, we haven't pulled
612 pullup_len += (ip->ip_hl << 2) - sizeof(struct ip);
616 M_CHECK(sizeof(struct tcphdr));
619 M_CHECK(sizeof(struct udphdr));
624 switch (iface->info.ifinfo_dlt) {
627 struct ether_header *eh;
629 eh = mtod(m, struct ether_header *);
630 switch (ntohs(eh->ether_type)) {
632 ip = (struct ip *)(eh + 1);
636 struct ether_vlan_header *evh;
638 evh = mtod(m, struct ether_vlan_header *);
639 ip = (struct ip *)(evh + 1);
643 panic("ng_netflow entered deadcode");
648 ip = mtod(m, struct ip *);
651 panic("ng_netflow entered deadcode");
656 /* Determine packet input interface. Prefer configured. */
658 if (hook == iface->out || iface->info.ifinfo_index == 0) {
659 if (m->m_pkthdr.rcvif != NULL)
660 src_if_index = m->m_pkthdr.rcvif->if_index;
662 src_if_index = iface->info.ifinfo_index;
664 error = ng_netflow_flow_add(priv, ip, src_if_index);
668 /* XXX: error gets overwritten here */
669 NG_FWD_NEW_DATA(error, item, out, m);
681 /* We will be shut down in a moment */
683 ng_netflow_close(node_p node)
685 const priv_p priv = NG_NODE_PRIVATE(node);
687 callout_drain(&priv->exp_callout);
688 ng_netflow_cache_flush(priv);
693 /* Do local shutdown processing. */
695 ng_netflow_rmnode(node_p node)
697 const priv_p priv = NG_NODE_PRIVATE(node);
699 NG_NODE_SET_PRIVATE(node, NULL);
700 NG_NODE_UNREF(priv->node);
702 free(priv, M_NETGRAPH);
707 /* Hook disconnection. */
709 ng_netflow_disconnect(hook_p hook)
711 node_p node = NG_HOOK_NODE(hook);
712 priv_p priv = NG_NODE_PRIVATE(node);
713 iface_p iface = NG_HOOK_PRIVATE(hook);
716 if (iface->hook == hook)
718 if (iface->out == hook)
722 /* if export hook disconnected stop running expire(). */
723 if (hook == priv->export) {
724 callout_drain(&priv->exp_callout);
728 /* Removal of the last link destroys the node. */
729 if (NG_NODE_NUMHOOKS(node) == 0)
730 ng_rmnode_self(node);