summaryrefslogtreecommitdiffstats
path: root/sys/netgraph/netflow/ng_netflow.c
diff options
context:
space:
mode:
authorglebius <glebius@FreeBSD.org>2011-03-02 16:15:11 +0000
committerglebius <glebius@FreeBSD.org>2011-03-02 16:15:11 +0000
commitb732b9a1c5a7780b2e1d5abbe3c14df81e3d83fc (patch)
tree9e6eedbe157d7b248793d47bc34864b0369f5d8a /sys/netgraph/netflow/ng_netflow.c
parent1319d944839227480c97d3d5eee61fe5507e169e (diff)
downloadFreeBSD-src-b732b9a1c5a7780b2e1d5abbe3c14df81e3d83fc.zip
FreeBSD-src-b732b9a1c5a7780b2e1d5abbe3c14df81e3d83fc.tar.gz
Add support for NetFlow version 9 into ng_netflow(4) node.
Submitted by: Alexander V. Chernikov <melifaro ipfw.ru>
Diffstat (limited to 'sys/netgraph/netflow/ng_netflow.c')
-rw-r--r--sys/netgraph/netflow/ng_netflow.c366
1 files changed, 325 insertions, 41 deletions
diff --git a/sys/netgraph/netflow/ng_netflow.c b/sys/netgraph/netflow/ng_netflow.c
index 8bf4845..53d0d6f 100644
--- a/sys/netgraph/netflow/ng_netflow.c
+++ b/sys/netgraph/netflow/ng_netflow.c
@@ -1,4 +1,5 @@
/*-
+ * Copyright (c) 2010-2011 Alexander V. Chernikov <melifaro@ipfw.ru>
* Copyright (c) 2004-2005 Gleb Smirnoff <glebius@FreeBSD.org>
* Copyright (c) 2001-2003 Roman V. Palagin <romanp@unshadow.net>
* All rights reserved.
@@ -30,6 +31,9 @@
static const char rcs_id[] =
"@(#) $FreeBSD$";
+#include "opt_inet6.h"
+#include "opt_route.h"
+
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
@@ -41,6 +45,7 @@ static const char rcs_id[] =
#include <net/if.h>
#include <net/ethernet.h>
+#include <net/route.h>
#include <net/if_arp.h>
#include <net/if_var.h>
#include <net/if_vlan_var.h>
@@ -48,13 +53,16 @@ static const char rcs_id[] =
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
+#include <netinet/ip6.h>
#include <netinet/tcp.h>
#include <netinet/udp.h>
+#include <netinet/sctp.h>
#include <netgraph/ng_message.h>
#include <netgraph/ng_parse.h>
#include <netgraph/netgraph.h>
#include <netgraph/netflow/netflow.h>
+#include <netgraph/netflow/netflow_v9.h>
#include <netgraph/netflow/ng_netflow.h>
/* Netgraph methods */
@@ -114,6 +122,22 @@ static const struct ng_parse_type ng_netflow_setconfig_type = {
&ng_netflow_setconfig_type_fields
};
+/* Parse type for ng_netflow_settemplate */
+static const struct ng_parse_struct_field ng_netflow_settemplate_type_fields[]
+ = NG_NETFLOW_SETTEMPLATE_TYPE;
+static const struct ng_parse_type ng_netflow_settemplate_type = {
+ &ng_parse_struct_type,
+ &ng_netflow_settemplate_type_fields
+};
+
+/* Parse type for ng_netflow_setmtu */
+static const struct ng_parse_struct_field ng_netflow_setmtu_type_fields[]
+ = NG_NETFLOW_SETMTU_TYPE;
+static const struct ng_parse_type ng_netflow_setmtu_type = {
+ &ng_parse_struct_type,
+ &ng_netflow_setmtu_type_fields
+};
+
/* List of commands and how to convert arguments to/from ASCII */
static const struct ng_cmdlist ng_netflow_cmds[] = {
{
@@ -158,6 +182,20 @@ static const struct ng_cmdlist ng_netflow_cmds[] = {
&ng_netflow_setconfig_type,
NULL
},
+ {
+ NGM_NETFLOW_COOKIE,
+ NGM_NETFLOW_SETTEMPLATE,
+ "settemplate",
+ &ng_netflow_settemplate_type,
+ NULL
+ },
+ {
+ NGM_NETFLOW_COOKIE,
+ NGM_NETFLOW_SETMTU,
+ "setmtu",
+ &ng_netflow_setmtu_type,
+ NULL
+ },
{ 0 }
};
@@ -284,11 +322,25 @@ ng_netflow_newhook(node_p node, hook_p hook, const char *name)
if (priv->export != NULL)
return (EISCONN);
+ /* Netflow version 5 supports 32-bit counters only */
+ if (CNTR_MAX == UINT64_MAX)
+ return (EINVAL);
+
priv->export = hook;
/* Exporter is ready. Let's schedule expiry. */
callout_reset(&priv->exp_callout, (1*hz), &ng_netflow_expire,
(void *)priv);
+ } else if (strcmp(name, NG_NETFLOW_HOOK_EXPORT9) == 0) {
+
+ if (priv->export9 != NULL)
+ return (EISCONN);
+
+ priv->export9 = hook;
+
+ /* Exporter is ready. Let's schedule expiry. */
+ callout_reset(&priv->exp_callout, (1*hz), &ng_netflow_expire,
+ (void *)priv);
} else
return (EINVAL);
@@ -425,6 +477,35 @@ ng_netflow_rcvmsg (node_p node, item_p item, hook_p lasthook)
break;
}
+ case NGM_NETFLOW_SETTEMPLATE:
+ {
+ struct ng_netflow_settemplate *set;
+
+ if (msg->header.arglen != sizeof(struct ng_netflow_settemplate))
+ ERROUT(EINVAL);
+
+ set = (struct ng_netflow_settemplate *)msg->data;
+
+ priv->templ_packets = set->packets;
+ priv->templ_time = set->time;
+
+ break;
+ }
+ case NGM_NETFLOW_SETMTU:
+ {
+ struct ng_netflow_setmtu *set;
+
+ if (msg->header.arglen != sizeof(struct ng_netflow_setmtu))
+ ERROUT(EINVAL);
+
+ set = (struct ng_netflow_setmtu *)msg->data;
+ if ((set->mtu < MIN_MTU) || (set->mtu > MAX_MTU))
+ ERROUT(EINVAL);
+
+ priv->mtu = set->mtu;
+
+ break;
+ }
case NGM_NETFLOW_SHOW:
{
uint32_t *last;
@@ -472,14 +553,19 @@ ng_netflow_rcvdata (hook_p hook, item_p item)
const priv_p priv = NG_NODE_PRIVATE(node);
const iface_p iface = NG_HOOK_PRIVATE(hook);
hook_p out;
- struct mbuf *m = NULL;
- struct ip *ip;
+ struct mbuf *m = NULL, *m_old = NULL;
+ struct ip *ip = NULL;
+ struct ip6_hdr *ip6 = NULL;
struct m_tag *mtag;
- int pullup_len = 0;
- int error = 0, bypass = 0;
+ int pullup_len = 0, off;
+ uint8_t upper_proto = 0, is_frag = 0;
+ int error = 0, bypass = 0, acct = 0;
unsigned int src_if_index;
+ caddr_t upper_ptr = NULL;
+ fib_export_p fe;
+ uint32_t fib;
- if (hook == priv->export) {
+ if ((hook == priv->export) || (hook == priv->export9)) {
/*
* Data arrived on export hook.
* This must not happen.
@@ -496,9 +582,9 @@ ng_netflow_rcvdata (hook_p hook, item_p item)
if ((iface->info.conf & NG_NETFLOW_CONF_EGRESS) == 0)
bypass = 1;
out = iface->hook;
- } else
+ } else
ERROUT(EINVAL);
-
+
if ((!bypass) &&
(iface->info.conf & (NG_NETFLOW_CONF_ONCE | NG_NETFLOW_CONF_THISONCE))) {
mtag = m_tag_locate(NGI_M(item), MTAG_NETFLOW,
@@ -532,6 +618,7 @@ ng_netflow_rcvdata (hook_p hook, item_p item)
}
NGI_GET_M(item, m);
+ m_old = m;
/* Increase counters. */
iface->info.ifinfo_packets++;
@@ -549,7 +636,8 @@ ng_netflow_rcvdata (hook_p hook, item_p item)
#define M_CHECK(length) do { \
pullup_len += length; \
- if ((m)->m_pkthdr.len < (pullup_len)) { \
+ if (((m)->m_pkthdr.len < (pullup_len)) || \
+ ((pullup_len) > MHLEN)) { \
error = EINVAL; \
goto bypass; \
} \
@@ -577,6 +665,17 @@ ng_netflow_rcvdata (hook_p hook, item_p item)
eh = mtod(m, struct ether_header *);
ip = (struct ip *)(eh + 1);
break;
+#ifdef INET6
+ case ETHERTYPE_IPV6:
+ /*
+ * m_pullup() called by M_CHECK() pullups
+ * kern.ipc.max_protohdr (default 60 bytes) which is enough
+ */
+ M_CHECK(sizeof(struct ip6_hdr));
+ eh = mtod(m, struct ether_header *);
+ ip6 = (struct ip6_hdr *)(eh + 1);
+ break;
+#endif
case ETHERTYPE_VLAN:
{
struct ether_vlan_header *evh;
@@ -584,10 +683,18 @@ ng_netflow_rcvdata (hook_p hook, item_p item)
M_CHECK(sizeof(struct ether_vlan_header) -
sizeof(struct ether_header));
evh = mtod(m, struct ether_vlan_header *);
- if (ntohs(evh->evl_proto) == ETHERTYPE_IP) {
+ etype = ntohs(evh->evl_proto);
+
+ if (etype == ETHERTYPE_IP) {
M_CHECK(sizeof(struct ip));
ip = (struct ip *)(evh + 1);
break;
+#ifdef INET6
+ } else if (etype == ETHERTYPE_IPV6) {
+ M_CHECK(sizeof(struct ip6_hdr));
+ ip6 = (struct ip6_hdr *)(evh + 1);
+ break;
+#endif
}
}
default:
@@ -598,19 +705,41 @@ ng_netflow_rcvdata (hook_p hook, item_p item)
case DLT_RAW: /* IP packets */
M_CHECK(sizeof(struct ip));
ip = mtod(m, struct ip *);
+#ifdef INET6
+ /* If INET6 is not defined IPv6 packets will be discarded in ng_netflow_flow_add() */
+ if (ip->ip_v == IP6VERSION) {
+ /* IPv6 packet */
+ ip = NULL;
+ M_CHECK(sizeof(struct ip6_hdr));
+ ip6 = mtod(m, struct ip6_hdr *);
+ }
+#endif
break;
default:
goto bypass;
break;
}
- if ((ip->ip_off & htons(IP_OFFMASK)) == 0) {
+ off = pullup_len;
+
+ if ((ip != NULL) && ((ip->ip_off & htons(IP_OFFMASK)) == 0)) {
+ if ((ip->ip_v != IPVERSION) ||
+ ((ip->ip_hl << 2) < sizeof(struct ip)))
+ goto bypass;
/*
- * In case of IP header with options, we haven't pulled
+ * In case of IPv4 header with options, we haven't pulled
* up enough, yet.
*/
- pullup_len += (ip->ip_hl << 2) - sizeof(struct ip);
+ M_CHECK((ip->ip_hl << 2) - sizeof(struct ip));
+
+ /* Save upper layer offset and proto */
+ off = pullup_len;
+ upper_proto = ip->ip_p;
+ /*
+ * XXX: in case of wrong upper layer header we will forward this packet
+ * but skip this record in netflow
+ */
switch (ip->ip_p) {
case IPPROTO_TCP:
M_CHECK(sizeof(struct tcphdr));
@@ -618,41 +747,153 @@ ng_netflow_rcvdata (hook_p hook, item_p item)
case IPPROTO_UDP:
M_CHECK(sizeof(struct udphdr));
break;
+ case IPPROTO_SCTP:
+ M_CHECK(sizeof(struct sctphdr));
+ break;
}
- }
+ } else if (ip != NULL) {
+ /* Nothing to save except upper layer proto, since this is packet fragment */
+ is_frag = 1;
+ upper_proto = ip->ip_p;
+ if ((ip->ip_v != IPVERSION) ||
+ ((ip->ip_hl << 2) < sizeof(struct ip)))
+ goto bypass;
+#ifdef INET6
+ } else if (ip6 != NULL) {
+ /* Check if we can export */
+ if (priv->export9 == NULL)
+ goto bypass;
+
+ /* Loop thru IPv6 extended headers to get upper layer header / frag */
+ int cur = ip6->ip6_nxt, hdr_off = 0;
+ struct ip6_ext *ip6e;
+ struct ip6_frag *ip6f;
+
+ /* Save upper layer info */
+ off = pullup_len;
+ upper_proto = cur;
+
+ if ((ip6->ip6_vfc & IPV6_VERSION_MASK) != IPV6_VERSION)
+ goto bypass;
+
+ while (42) {
+ switch (cur) {
+ /*
+ * Same as in IPv4, we can forward 'bad' packet without accounting
+ */
+ case IPPROTO_TCP:
+ M_CHECK(sizeof(struct tcphdr));
+ goto loopend;
+ case IPPROTO_UDP:
+ M_CHECK(sizeof(struct udphdr));
+ goto loopend;
+ case IPPROTO_SCTP:
+ M_CHECK(sizeof(struct sctphdr));
+ goto loopend;
+
+ /* Loop until 'real' upper layer headers */
+ case IPPROTO_HOPOPTS:
+ case IPPROTO_ROUTING:
+ case IPPROTO_DSTOPTS:
+ M_CHECK(sizeof(struct ip6_ext));
+ ip6e = (struct ip6_ext *)(mtod(m, caddr_t) + off);
+ upper_proto = ip6e->ip6e_nxt;
+ hdr_off = (ip6e->ip6e_len + 1) << 3;
+ break;
- switch (iface->info.ifinfo_dlt) {
- case DLT_EN10MB:
- {
- struct ether_header *eh;
+ /* RFC4302, can be before DSTOPTS */
+ case IPPROTO_AH:
+ M_CHECK(sizeof(struct ip6_ext));
+ ip6e = (struct ip6_ext *)(mtod(m, caddr_t) + off);
+ upper_proto = ip6e->ip6e_nxt;
+ hdr_off = (ip6e->ip6e_len + 2) << 2;
+ break;
- eh = mtod(m, struct ether_header *);
- switch (ntohs(eh->ether_type)) {
- case ETHERTYPE_IP:
- ip = (struct ip *)(eh + 1);
- break;
- case ETHERTYPE_VLAN:
- {
- struct ether_vlan_header *evh;
+ case IPPROTO_FRAGMENT:
+ M_CHECK(sizeof(struct ip6_frag));
+ ip6f = (struct ip6_frag *)(mtod(m, caddr_t) + off);
+ upper_proto = ip6f->ip6f_nxt;
+ hdr_off = sizeof(struct ip6_frag);
+ off += hdr_off;
+ is_frag = 1;
+ goto loopend;
+
+#if 0
+ case IPPROTO_NONE:
+ goto loopend;
+#endif
+ /* Any unknow header (new extension or IPv6/IPv4 header for tunnels) */
+ default:
+ goto loopend;
+ }
- evh = mtod(m, struct ether_vlan_header *);
- ip = (struct ip *)(evh + 1);
- break;
- }
- default:
- panic("ng_netflow entered deadcode");
+ off += hdr_off;
+ cur = upper_proto;
}
- break;
- }
- case DLT_RAW:
- ip = mtod(m, struct ip *);
- break;
- default:
- panic("ng_netflow entered deadcode");
+#endif
}
-
#undef M_CHECK
+#ifdef INET6
+loopend:
+#endif
+ /* Just in case of real reallocation in M_CHECK() / m_pullup() */
+ if (m != m_old) {
+ atomic_fetchadd_32(&priv->info.nfinfo_realloc_mbuf, 1);
+ ip = NULL;
+ ip6 = NULL;
+ switch (iface->info.ifinfo_dlt) {
+ case DLT_EN10MB: /* Ethernet */
+ {
+ struct ether_header *eh;
+
+ eh = mtod(m, struct ether_header *);
+ switch (ntohs(eh->ether_type)) {
+ case ETHERTYPE_IP:
+ ip = (struct ip *)(eh + 1);
+ break;
+#ifdef INET6
+ case ETHERTYPE_IPV6:
+ ip6 = (struct ip6_hdr *)(eh + 1);
+ break;
+#endif
+ case ETHERTYPE_VLAN:
+ {
+ struct ether_vlan_header *evh;
+
+ evh = mtod(m, struct ether_vlan_header *);
+ if (ntohs(evh->evl_proto) == ETHERTYPE_IP) {
+ ip = (struct ip *)(evh + 1);
+ break;
+#ifdef INET6
+ } else if (ntohs(evh->evl_proto) == ETHERTYPE_IPV6) {
+ ip6 = (struct ip6_hdr *)(evh + 1);
+ break;
+#endif
+ }
+ }
+ default:
+ panic("ng_netflow entered deadcode");
+ }
+ break;
+ }
+ case DLT_RAW: /* IP packets */
+ ip = mtod(m, struct ip *);
+#ifdef INET6
+ if (ip->ip_v == IP6VERSION) {
+ /* IPv6 packet */
+ ip = NULL;
+ ip6 = mtod(m, struct ip6_hdr *);
+ }
+#endif
+ break;
+ default:
+ panic("ng_netflow entered deadcode");
+ }
+ }
+
+ upper_ptr = (caddr_t)(mtod(m, caddr_t) + off);
+
/* Determine packet input interface. Prefer configured. */
src_if_index = 0;
if (hook == iface->out || iface->info.ifinfo_index == 0) {
@@ -660,11 +901,47 @@ ng_netflow_rcvdata (hook_p hook, item_p item)
src_if_index = m->m_pkthdr.rcvif->if_index;
} else
src_if_index = iface->info.ifinfo_index;
+
+ /* Check packet FIB */
+ fib = M_GETFIB(m);
+ if (fib >= RT_NUMFIBS) {
+ CTR2(KTR_NET, "ng_netflow_rcvdata(): packet fib %d is out of range of available fibs: 0 .. %d", fib, RT_NUMFIBS);
+ goto bypass;
+ }
- error = ng_netflow_flow_add(priv, ip, src_if_index);
+ if ((fe = priv_to_fib(priv, fib)) == NULL) {
+ /* Setup new FIB */
+ if (ng_netflow_fib_init(priv, fib) != 0) {
+ /* malloc() failed */
+ goto bypass;
+ }
+ fe = priv_to_fib(priv, fib);
+ }
+
+ if (ip != NULL)
+ error = ng_netflow_flow_add(priv, fe, ip, upper_ptr, upper_proto, is_frag, src_if_index);
+#ifdef INET6
+ else if (ip6 != NULL)
+ error = ng_netflow_flow6_add(priv, fe, ip6, upper_ptr, upper_proto, is_frag, src_if_index);
+#endif
+ else
+ goto bypass;
+
+ acct = 1;
bypass:
if (out != NULL) {
+ if (acct == 0) {
+ /* Accounting failure */
+ if (ip != NULL) {
+ atomic_fetchadd_32(&priv->info.nfinfo_spackets, 1);
+ priv->info.nfinfo_sbytes += m_length(m, NULL);
+ } else if (ip6 != NULL) {
+ atomic_fetchadd_32(&priv->info.nfinfo_spackets6, 1);
+ priv->info.nfinfo_sbytes6 += m_length(m, NULL);
+ }
+ }
+
/* XXX: error gets overwritten here */
NG_FWD_NEW_DATA(error, item, out, m);
return (error);
@@ -721,10 +998,17 @@ ng_netflow_disconnect(hook_p hook)
/* if export hook disconnected stop running expire(). */
if (hook == priv->export) {
- callout_drain(&priv->exp_callout);
+ if (priv->export9 == NULL)
+ callout_drain(&priv->exp_callout);
priv->export = NULL;
}
+ if (hook == priv->export9) {
+ if (priv->export == NULL)
+ callout_drain(&priv->exp_callout);
+ priv->export9 = NULL;
+ }
+
/* Removal of the last link destroys the node. */
if (NG_NODE_NUMHOOKS(node) == 0)
ng_rmnode_self(node);
OpenPOWER on IntegriCloud