From b732b9a1c5a7780b2e1d5abbe3c14df81e3d83fc Mon Sep 17 00:00:00 2001
From: glebius <glebius@FreeBSD.org>
Date: Wed, 2 Mar 2011 16:15:11 +0000
Subject: Add support for NetFlow version 9 into ng_netflow(4) node.

Submitted by:	Alexander V. Chernikov <melifaro ipfw.ru>
---
 sys/netgraph/netflow/ng_netflow.c | 366 +++++++++++++++++++++++++++++++++-----
 1 file changed, 325 insertions(+), 41 deletions(-)

(limited to 'sys/netgraph/netflow/ng_netflow.c')

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);
-- 
cgit v1.1