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 #if 0 /* TODO: profile & test first */
291 * We send export dgrams in interrupt handlers and in
292 * callout threads. We'd better queue data for later
293 * netgraph ISR processing.
295 NG_HOOK_FORCE_QUEUE(NG_HOOK_PEER(hook));
298 /* Exporter is ready. Let's schedule expiry. */
299 callout_reset(&priv->exp_callout, (1*hz), &ng_netflow_expire,
307 /* Get a netgraph control message. */
309 ng_netflow_rcvmsg (node_p node, item_p item, hook_p lasthook)
311 const priv_p priv = NG_NODE_PRIVATE(node);
312 struct ng_mesg *resp = NULL;
316 NGI_GET_MSG(item, msg);
318 /* Deal with message according to cookie and command */
319 switch (msg->header.typecookie) {
320 case NGM_NETFLOW_COOKIE:
321 switch (msg->header.cmd) {
322 case NGM_NETFLOW_INFO:
324 struct ng_netflow_info *i;
326 NG_MKRESPONSE(resp, msg, sizeof(struct ng_netflow_info),
328 i = (struct ng_netflow_info *)resp->data;
329 ng_netflow_copyinfo(priv, i);
333 case NGM_NETFLOW_IFINFO:
335 struct ng_netflow_ifinfo *i;
336 const uint16_t *index;
338 if (msg->header.arglen != sizeof(uint16_t))
341 index = (uint16_t *)msg->data;
342 if (*index >= NG_NETFLOW_MAXIFACES)
345 /* connected iface? */
346 if (priv->ifaces[*index].hook == NULL)
349 NG_MKRESPONSE(resp, msg,
350 sizeof(struct ng_netflow_ifinfo), M_NOWAIT);
351 i = (struct ng_netflow_ifinfo *)resp->data;
352 memcpy((void *)i, (void *)&priv->ifaces[*index].info,
353 sizeof(priv->ifaces[*index].info));
357 case NGM_NETFLOW_SETDLT:
359 struct ng_netflow_setdlt *set;
360 struct ng_netflow_iface *iface;
362 if (msg->header.arglen != sizeof(struct ng_netflow_setdlt))
365 set = (struct ng_netflow_setdlt *)msg->data;
366 if (set->iface >= NG_NETFLOW_MAXIFACES)
368 iface = &priv->ifaces[set->iface];
370 /* connected iface? */
371 if (iface->hook == NULL)
376 iface->info.ifinfo_dlt = DLT_EN10MB;
379 iface->info.ifinfo_dlt = DLT_RAW;
386 case NGM_NETFLOW_SETIFINDEX:
388 struct ng_netflow_setifindex *set;
389 struct ng_netflow_iface *iface;
391 if (msg->header.arglen != sizeof(struct ng_netflow_setifindex))
394 set = (struct ng_netflow_setifindex *)msg->data;
395 if (set->iface >= NG_NETFLOW_MAXIFACES)
397 iface = &priv->ifaces[set->iface];
399 /* connected iface? */
400 if (iface->hook == NULL)
403 iface->info.ifinfo_index = set->index;
407 case NGM_NETFLOW_SETTIMEOUTS:
409 struct ng_netflow_settimeouts *set;
411 if (msg->header.arglen != sizeof(struct ng_netflow_settimeouts))
414 set = (struct ng_netflow_settimeouts *)msg->data;
416 priv->info.nfinfo_inact_t = set->inactive_timeout;
417 priv->info.nfinfo_act_t = set->active_timeout;
421 case NGM_NETFLOW_SETCONFIG:
423 struct ng_netflow_setconfig *set;
425 if (msg->header.arglen != sizeof(struct ng_netflow_settimeouts))
428 set = (struct ng_netflow_setconfig *)msg->data;
430 if (set->iface >= NG_NETFLOW_MAXIFACES)
433 priv->ifaces[set->iface].info.conf = set->conf;
437 case NGM_NETFLOW_SHOW:
441 if (msg->header.arglen != sizeof(uint32_t))
444 last = (uint32_t *)msg->data;
446 NG_MKRESPONSE(resp, msg, NGRESP_SIZE, M_NOWAIT);
451 error = ng_netflow_flow_show(priv, *last, resp);
456 ERROUT(EINVAL); /* unknown command */
461 ERROUT(EINVAL); /* incorrect cookie */
466 * Take care of synchronous response, if any.
467 * Free memory and return.
470 NG_RESPOND_MSG(error, node, item, resp);
476 /* Receive data on hook. */
478 ng_netflow_rcvdata (hook_p hook, item_p item)
480 const node_p node = NG_HOOK_NODE(hook);
481 const priv_p priv = NG_NODE_PRIVATE(node);
482 const iface_p iface = NG_HOOK_PRIVATE(hook);
484 struct mbuf *m = NULL;
488 int error = 0, bypass = 0;
489 unsigned int src_if_index;
491 if (hook == priv->export) {
493 * Data arrived on export hook.
494 * This must not happen.
496 log(LOG_ERR, "ng_netflow: incoming data on export hook!\n");
500 if (hook == iface->hook) {
501 if ((iface->info.conf & NG_NETFLOW_CONF_INGRESS) == 0)
504 } else if (hook == iface->out) {
505 if ((iface->info.conf & NG_NETFLOW_CONF_EGRESS) == 0)
512 (iface->info.conf & (NG_NETFLOW_CONF_ONCE | NG_NETFLOW_CONF_THISONCE))) {
513 mtag = m_tag_locate(NGI_M(item), MTAG_NETFLOW,
514 MTAG_NETFLOW_CALLED, NULL);
515 while (mtag != NULL) {
516 if ((iface->info.conf & NG_NETFLOW_CONF_ONCE) ||
517 ((ng_ID_t *)(mtag + 1))[0] == NG_NODE_ID(node)) {
521 mtag = m_tag_locate(NGI_M(item), MTAG_NETFLOW,
522 MTAG_NETFLOW_CALLED, mtag);
530 NG_FWD_ITEM_HOOK(error, item, out);
534 if (iface->info.conf & (NG_NETFLOW_CONF_ONCE | NG_NETFLOW_CONF_THISONCE)) {
535 mtag = m_tag_alloc(MTAG_NETFLOW, MTAG_NETFLOW_CALLED,
536 sizeof(ng_ID_t), M_NOWAIT);
538 ((ng_ID_t *)(mtag + 1))[0] = NG_NODE_ID(node);
539 m_tag_prepend(NGI_M(item), mtag);
545 /* Increase counters. */
546 iface->info.ifinfo_packets++;
549 * Depending on interface data link type and packet contents
550 * we pullup enough data, so that ng_netflow_flow_add() does not
551 * need to know about mbuf at all. We keep current length of data
552 * needed to be contiguous in pullup_len. mtod() is done at the
553 * very end one more time, since m can had changed after pulluping.
555 * In case of unrecognized data we don't return error, but just
556 * pass data to downstream hook, if it is available.
559 #define M_CHECK(length) do { \
560 pullup_len += length; \
561 if ((m)->m_pkthdr.len < (pullup_len)) { \
565 if ((m)->m_len < (pullup_len) && \
566 (((m) = m_pullup((m),(pullup_len))) == NULL)) { \
572 switch (iface->info.ifinfo_dlt) {
573 case DLT_EN10MB: /* Ethernet */
575 struct ether_header *eh;
578 M_CHECK(sizeof(struct ether_header));
579 eh = mtod(m, struct ether_header *);
581 /* Make sure this is IP frame. */
582 etype = ntohs(eh->ether_type);
585 M_CHECK(sizeof(struct ip));
586 eh = mtod(m, struct ether_header *);
587 ip = (struct ip *)(eh + 1);
591 struct ether_vlan_header *evh;
593 M_CHECK(sizeof(struct ether_vlan_header) -
594 sizeof(struct ether_header));
595 evh = mtod(m, struct ether_vlan_header *);
596 if (ntohs(evh->evl_proto) == ETHERTYPE_IP) {
597 M_CHECK(sizeof(struct ip));
598 ip = (struct ip *)(evh + 1);
603 goto bypass; /* pass this frame */
607 case DLT_RAW: /* IP packets */
608 M_CHECK(sizeof(struct ip));
609 ip = mtod(m, struct ip *);
616 if ((ip->ip_off & htons(IP_OFFMASK)) == 0) {
618 * In case of IP header with options, we haven't pulled
621 pullup_len += (ip->ip_hl << 2) - sizeof(struct ip);
625 M_CHECK(sizeof(struct tcphdr));
628 M_CHECK(sizeof(struct udphdr));
633 switch (iface->info.ifinfo_dlt) {
636 struct ether_header *eh;
638 eh = mtod(m, struct ether_header *);
639 switch (ntohs(eh->ether_type)) {
641 ip = (struct ip *)(eh + 1);
645 struct ether_vlan_header *evh;
647 evh = mtod(m, struct ether_vlan_header *);
648 ip = (struct ip *)(evh + 1);
652 panic("ng_netflow entered deadcode");
657 ip = mtod(m, struct ip *);
660 panic("ng_netflow entered deadcode");
665 /* Determine packet input interface. Prefer configured. */
667 if (hook == iface->out || iface->info.ifinfo_index == 0) {
668 if (m->m_pkthdr.rcvif != NULL)
669 src_if_index = m->m_pkthdr.rcvif->if_index;
671 src_if_index = iface->info.ifinfo_index;
673 error = ng_netflow_flow_add(priv, ip, src_if_index);
677 /* XXX: error gets overwritten here */
678 NG_FWD_NEW_DATA(error, item, out, m);
690 /* We will be shut down in a moment */
692 ng_netflow_close(node_p node)
694 const priv_p priv = NG_NODE_PRIVATE(node);
696 callout_drain(&priv->exp_callout);
697 ng_netflow_cache_flush(priv);
702 /* Do local shutdown processing. */
704 ng_netflow_rmnode(node_p node)
706 const priv_p priv = NG_NODE_PRIVATE(node);
708 NG_NODE_SET_PRIVATE(node, NULL);
709 NG_NODE_UNREF(priv->node);
711 free(priv, M_NETGRAPH);
716 /* Hook disconnection. */
718 ng_netflow_disconnect(hook_p hook)
720 node_p node = NG_HOOK_NODE(hook);
721 priv_p priv = NG_NODE_PRIVATE(node);
722 iface_p iface = NG_HOOK_PRIVATE(hook);
725 if (iface->hook == hook)
727 if (iface->out == hook)
731 /* if export hook disconnected stop running expire(). */
732 if (hook == priv->export) {
733 callout_drain(&priv->exp_callout);
737 /* Removal of the last link destroys the node. */
738 if (NG_NODE_NUMHOOKS(node) == 0)
739 ng_rmnode_self(node);