diff options
Diffstat (limited to 'sys')
-rw-r--r-- | sys/conf/files | 1 | ||||
-rw-r--r-- | sys/modules/netgraph/netflow/Makefile | 15 | ||||
-rw-r--r-- | sys/netgraph/netflow/netflow.c | 584 | ||||
-rw-r--r-- | sys/netgraph/netflow/netflow.h | 85 | ||||
-rw-r--r-- | sys/netgraph/netflow/netflow_v9.c | 483 | ||||
-rw-r--r-- | sys/netgraph/netflow/netflow_v9.h | 148 | ||||
-rw-r--r-- | sys/netgraph/netflow/ng_netflow.c | 366 | ||||
-rw-r--r-- | sys/netgraph/netflow/ng_netflow.h | 205 |
8 files changed, 1754 insertions, 133 deletions
diff --git a/sys/conf/files b/sys/conf/files index e3d88b0..f61a753 100644 --- a/sys/conf/files +++ b/sys/conf/files @@ -2556,6 +2556,7 @@ netgraph/bluetooth/socket/ng_btsocket_l2cap_raw.c optional netgraph_bluetooth_so netgraph/bluetooth/socket/ng_btsocket_rfcomm.c optional netgraph_bluetooth_socket netgraph/bluetooth/socket/ng_btsocket_sco.c optional netgraph_bluetooth_socket netgraph/netflow/netflow.c optional netgraph_netflow +netgraph/netflow/netflow_v9.c optional netgraph_netflow netgraph/netflow/ng_netflow.c optional netgraph_netflow netgraph/ng_UI.c optional netgraph_UI netgraph/ng_async.c optional netgraph_async diff --git a/sys/modules/netgraph/netflow/Makefile b/sys/modules/netgraph/netflow/Makefile index b1b9ba5..8330268 100644 --- a/sys/modules/netgraph/netflow/Makefile +++ b/sys/modules/netgraph/netflow/Makefile @@ -3,9 +3,22 @@ # Author: Gleb Smirnoff <glebius@freebsd.org> # +.include <bsd.own.mk> + .PATH: ${.CURDIR}/../../../netgraph/netflow KMOD= ng_netflow -SRCS= ng_netflow.c netflow.c +SRCS= ng_netflow.c netflow.c netflow_v9.c opt_inet6.h opt_route.h + +.if !defined(KERNBUILDDIR) + +.if ${MK_INET6_SUPPORT} != "no" +opt_inet6.h: + echo "#define INET6 1" > ${.TARGET} +.endif + +opt_route.h: + echo "#define ROUTETABLES RT_MAXFIBS" > ${.TARGET} +.endif .include <bsd.kmod.mk> diff --git a/sys/netgraph/netflow/netflow.c b/sys/netgraph/netflow/netflow.c index 39dd9d5..d6097da 100644 --- a/sys/netgraph/netflow/netflow.c +++ b/sys/netgraph/netflow/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,8 @@ static const char rcs_id[] = "@(#) $FreeBSD$"; +#include "opt_inet6.h" +#include "opt_route.h" #include <sys/param.h> #include <sys/kernel.h> #include <sys/limits.h> @@ -37,14 +40,18 @@ static const char rcs_id[] = #include <sys/syslog.h> #include <sys/systm.h> #include <sys/socket.h> +#include <sys/endian.h> #include <machine/atomic.h> +#include <machine/stdarg.h> #include <net/if.h> #include <net/route.h> +#include <net/ethernet.h> #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> @@ -52,6 +59,7 @@ static const char rcs_id[] = #include <netgraph/netgraph.h> #include <netgraph/netflow/netflow.h> +#include <netgraph/netflow/netflow_v9.h> #include <netgraph/netflow/ng_netflow.h> #define NBUCKETS (65536) /* must be power of 2 */ @@ -83,25 +91,28 @@ static const char rcs_id[] = */ #define SMALL(fle) (fle->f.packets <= 4) -/* - * Cisco uses milliseconds for uptime. Bad idea, since it overflows - * every 48+ days. But we will do same to keep compatibility. This macro - * does overflowable multiplication to 1000. - */ -#define MILLIUPTIME(t) (((t) << 9) + /* 512 */ \ - ((t) << 8) + /* 256 */ \ - ((t) << 7) + /* 128 */ \ - ((t) << 6) + /* 64 */ \ - ((t) << 5) + /* 32 */ \ - ((t) << 3)) /* 8 */ MALLOC_DECLARE(M_NETFLOW_HASH); MALLOC_DEFINE(M_NETFLOW_HASH, "netflow_hash", "NetFlow hash"); static int export_add(item_p, struct flow_entry *); -static int export_send(priv_p, item_p, int flags); +static int export_send(priv_p, fib_export_p, item_p, int); + +static int hash_insert(priv_p, struct flow_hash_entry *, struct flow_rec *, int, uint8_t); +static int hash6_insert(priv_p, struct flow6_hash_entry *, struct flow6_rec *, int, uint8_t); + +static __inline void expire_flow(priv_p, fib_export_p, struct flow_entry *, int); -/* Generate hash for a given flow record. */ +/* + * Generate hash for a given flow record. + * + * FIB is not used here, because: + * most VRFS will carry public IPv4 addresses which are unique even + * without FIB private addresses can overlap, but this is worked out + * via flow_rec bcmp() containing fib id. In IPv6 world addresses are + * all globally unique (it's not fully true, there is FC00::/7 for example, + * but chances of address overlap are MUCH smaller) + */ static __inline uint32_t ip_hash(struct flow_rec *r) { @@ -115,6 +126,24 @@ ip_hash(struct flow_rec *r) } } +#ifdef INET6 +/* Generate hash for a given flow6 record. Use lower 4 octets from v6 addresses */ +static __inline uint32_t +ip6_hash(struct flow6_rec *r) +{ + switch (r->r_ip_p) { + case IPPROTO_TCP: + case IPPROTO_UDP: + return FULL_HASH(r->src.r_src6.__u6_addr.__u6_addr32[3], + r->dst.r_dst6.__u6_addr.__u6_addr32[3], r->r_sport, + r->r_dport); + default: + return ADDR_HASH(r->src.r_src6.__u6_addr.__u6_addr32[3], + r->dst.r_dst6.__u6_addr.__u6_addr32[3]); + } +} +#endif + /* This is callback from uma(9), called on alloc. */ static int uma_ctor_flow(void *mem, int size, void *arg, int how) @@ -138,21 +167,46 @@ uma_dtor_flow(void *mem, int size, void *arg) atomic_subtract_32(&priv->info.nfinfo_used, 1); } +#ifdef INET6 +/* This is callback from uma(9), called on alloc. */ +static int +uma_ctor_flow6(void *mem, int size, void *arg, int how) +{ + priv_p priv = (priv_p )arg; + + if (atomic_load_acq_32(&priv->info.nfinfo_used6) >= CACHESIZE) + return (ENOMEM); + + atomic_add_32(&priv->info.nfinfo_used6, 1); + + return (0); +} + +/* This is callback from uma(9), called on free. */ +static void +uma_dtor_flow6(void *mem, int size, void *arg) +{ + priv_p priv = (priv_p )arg; + + atomic_subtract_32(&priv->info.nfinfo_used6, 1); +} +#endif + /* * Detach export datagram from priv, if there is any. * If there is no, allocate a new one. */ static item_p -get_export_dgram(priv_p priv) +get_export_dgram(priv_p priv, fib_export_p fe) { item_p item = NULL; - mtx_lock(&priv->export_mtx); - if (priv->export_item != NULL) { - item = priv->export_item; - priv->export_item = NULL; + mtx_lock(&fe->export_mtx); + if (fe->exp.item != NULL) { + item = fe->exp.item; + fe->exp.item = NULL; } - mtx_unlock(&priv->export_mtx); + mtx_unlock(&fe->export_mtx); if (item == NULL) { struct netflow_v5_export_dgram *dgram; @@ -178,20 +232,20 @@ get_export_dgram(priv_p priv) * Re-attach incomplete datagram back to priv. * If there is already another one, then send incomplete. */ static void -return_export_dgram(priv_p priv, item_p item, int flags) +return_export_dgram(priv_p priv, fib_export_p fe, item_p item, int flags) { /* * It may happen on SMP, that some thread has already * put its item there, in this case we bail out and * send what we have to collector. */ - mtx_lock(&priv->export_mtx); - if (priv->export_item == NULL) { - priv->export_item = item; - mtx_unlock(&priv->export_mtx); + mtx_lock(&fe->export_mtx); + if (fe->exp.item == NULL) { + fe->exp.item = item; + mtx_unlock(&fe->export_mtx); } else { - mtx_unlock(&priv->export_mtx); - export_send(priv, item, flags); + mtx_unlock(&fe->export_mtx); + export_send(priv, fe, item, flags); } } @@ -200,20 +254,51 @@ return_export_dgram(priv_p priv, item_p item, int flags) * full, then call export_send(). */ static __inline void -expire_flow(priv_p priv, item_p *item, struct flow_entry *fle, int flags) +expire_flow(priv_p priv, fib_export_p fe, struct flow_entry *fle, int flags) { - if (*item == NULL) - *item = get_export_dgram(priv); - if (*item == NULL) { - atomic_add_32(&priv->info.nfinfo_export_failed, 1); - uma_zfree_arg(priv->zone, fle, priv); - return; + struct netflow_export_item exp; + uint16_t version = fle->f.version; + + if ((priv->export != NULL) && (version == IPVERSION)) { + exp.item = get_export_dgram(priv, fe); + if (exp.item == NULL) { + atomic_add_32(&priv->info.nfinfo_export_failed, 1); + if (priv->export9 != NULL) + atomic_add_32(&priv->info.nfinfo_export9_failed, 1); + /* fle definitely contains IPv4 flow */ + uma_zfree_arg(priv->zone, fle, priv); + return; + } + + if (export_add(exp.item, fle) > 0) + export_send(priv, fe, exp.item, flags); + else + return_export_dgram(priv, fe, exp.item, NG_QUEUE); } - if (export_add(*item, fle) > 0) { - export_send(priv, *item, flags); - *item = NULL; + + if (priv->export9 != NULL) { + exp.item9 = get_export9_dgram(priv, fe, &exp.item9_opt); + if (exp.item9 == NULL) { + atomic_add_32(&priv->info.nfinfo_export9_failed, 1); + if (version == IPVERSION) + uma_zfree_arg(priv->zone, fle, priv); + else if (version == IP6VERSION) + uma_zfree_arg(priv->zone6, fle, priv); + else + panic("ng_netflow: Unknown IP proto: %d", version); + return; + } + + if (export9_add(exp.item9, exp.item9_opt, fle) > 0) + export9_send(priv, fe, exp.item9, exp.item9_opt, flags); + else + return_export9_dgram(priv, fe, exp.item9, exp.item9_opt, NG_QUEUE); } - uma_zfree_arg(priv->zone, fle, priv); + + if (version == IPVERSION) + uma_zfree_arg(priv->zone, fle, priv); + else if (version == IP6VERSION) + uma_zfree_arg(priv->zone6, fle, priv); } /* Get a snapshot of node statistics */ @@ -235,7 +320,7 @@ ng_netflow_copyinfo(priv_p priv, struct ng_netflow_info *i) * to be sure. */ static __inline int -hash_insert(priv_p priv, struct flow_hash_entry *hsh, struct flow_rec *r, +hash_insert(priv_p priv, struct flow_hash_entry *hsh, struct flow_rec *r, int plen, uint8_t tcp_flags) { struct flow_entry *fle; @@ -255,6 +340,7 @@ hash_insert(priv_p priv, struct flow_hash_entry *hsh, struct flow_rec *r, * we can safely edit it. */ + fle->f.version = IPVERSION; bcopy(r, &fle->f.r, sizeof(struct flow_rec)); fle->f.bytes = plen; fle->f.packets = 1; @@ -270,8 +356,7 @@ hash_insert(priv_p priv, struct flow_hash_entry *hsh, struct flow_rec *r, sin.sin_len = sizeof(struct sockaddr_in); sin.sin_family = AF_INET; sin.sin_addr = fle->f.r.r_dst; - /* XXX MRT 0 as a default.. need the m here to get fib */ - rt = rtalloc1_fib((struct sockaddr *)&sin, 0, 0, 0); + rt = rtalloc1_fib((struct sockaddr *)&sin, 0, 0, r->fib); if (rt != NULL) { fle->f.fle_o_ifx = rt->rt_ifp->if_index; @@ -295,8 +380,7 @@ hash_insert(priv_p priv, struct flow_hash_entry *hsh, struct flow_rec *r, sin.sin_len = sizeof(struct sockaddr_in); sin.sin_family = AF_INET; sin.sin_addr = fle->f.r.r_src; - /* XXX MRT 0 as a default revisit. need the mbuf for fib*/ - rt = rtalloc1_fib((struct sockaddr *)&sin, 0, 0, 0); + rt = rtalloc1_fib((struct sockaddr *)&sin, 0, 0, r->fib); if (rt != NULL) { if (rt_mask(rt)) fle->f.src_mask = bitcount32(((struct sockaddr_in *) @@ -314,6 +398,99 @@ hash_insert(priv_p priv, struct flow_hash_entry *hsh, struct flow_rec *r, return (0); } +#ifdef INET6 +/* XXX: make normal function, instead of.. */ +#define ipv6_masklen(x) bitcount32((x).__u6_addr.__u6_addr32[0]) + \ + bitcount32((x).__u6_addr.__u6_addr32[1]) + \ + bitcount32((x).__u6_addr.__u6_addr32[2]) + \ + bitcount32((x).__u6_addr.__u6_addr32[3]) +/* XXX: Do we need inline here ? */ +static __inline int +hash6_insert(priv_p priv, struct flow6_hash_entry *hsh6, struct flow6_rec *r, + int plen, uint8_t tcp_flags) +{ + struct flow6_entry *fle6; + struct sockaddr_in6 *src, *dst; + struct rtentry *rt; + struct route_in6 rin6; + + mtx_assert(&hsh6->mtx, MA_OWNED); + + fle6 = uma_zalloc_arg(priv->zone6, priv, M_NOWAIT); + if (fle6 == NULL) { + atomic_add_32(&priv->info.nfinfo_alloc_failed, 1); + return (ENOMEM); + } + + /* + * Now fle is totally ours. It is detached from all lists, + * we can safely edit it. + */ + + fle6->f.version = IP6VERSION; + bcopy(r, &fle6->f.r, sizeof(struct flow6_rec)); + fle6->f.bytes = plen; + fle6->f.packets = 1; + fle6->f.tcp_flags = tcp_flags; + + fle6->f.first = fle6->f.last = time_uptime; + + /* + * First we do route table lookup on destination address. So we can + * fill in out_ifx, dst_mask, nexthop, and dst_as in future releases. + */ + bzero(&rin6, sizeof(struct route_in6)); + dst = (struct sockaddr_in6 *)&rin6.ro_dst; + dst->sin6_len = sizeof(struct sockaddr_in6); + dst->sin6_family = AF_INET6; + dst->sin6_addr = r->dst.r_dst6; + + rin6.ro_rt = rtalloc1_fib((struct sockaddr *)dst, 0, 0, r->fib); + + if (rin6.ro_rt != NULL) { + rt = rin6.ro_rt; + fle6->f.fle_o_ifx = rt->rt_ifp->if_index; + + if (rt->rt_flags & RTF_GATEWAY && + rt->rt_gateway->sa_family == AF_INET6) + fle6->f.n.next_hop6 = + ((struct sockaddr_in6 *)(rt->rt_gateway))->sin6_addr; + + if (rt_mask(rt)) + fle6->f.dst_mask = ipv6_masklen(((struct sockaddr_in6 *)rt_mask(rt))->sin6_addr); + else + fle6->f.dst_mask = 128; + + RTFREE_LOCKED(rt); + } + + /* Do route lookup on source address, to fill in src_mask. */ + bzero(&rin6, sizeof(struct route_in6)); + src = (struct sockaddr_in6 *)&rin6.ro_dst; + src->sin6_len = sizeof(struct sockaddr_in6); + src->sin6_family = AF_INET6; + src->sin6_addr = r->src.r_src6; + + rin6.ro_rt = rtalloc1_fib((struct sockaddr *)src, 0, 0, r->fib); + + if (rin6.ro_rt != NULL) { + rt = rin6.ro_rt; + + if (rt_mask(rt)) + fle6->f.src_mask = ipv6_masklen(((struct sockaddr_in6 *)rt_mask(rt))->sin6_addr); + else + fle6->f.src_mask = 128; + + RTFREE_LOCKED(rt); + } + + /* Push new flow at the and of hash. */ + TAILQ_INSERT_TAIL(&hsh6->head, fle6, fle6_hash); + + return (0); +} +#endif + /* * Non-static functions called from ng_netflow.c @@ -323,43 +500,100 @@ hash_insert(priv_p priv, struct flow_hash_entry *hsh, struct flow_rec *r, int ng_netflow_cache_init(priv_p priv) { - struct flow_hash_entry *hsh; + struct flow_hash_entry *hsh; +#ifdef INET6 + struct flow6_hash_entry *hsh6; +#endif int i; /* Initialize cache UMA zone. */ - priv->zone = uma_zcreate("NetFlow cache", sizeof(struct flow_entry), + priv->zone = uma_zcreate("NetFlow IPv4 cache", sizeof(struct flow_entry), uma_ctor_flow, uma_dtor_flow, NULL, NULL, UMA_ALIGN_CACHE, 0); uma_zone_set_max(priv->zone, CACHESIZE); +#ifdef INET6 + priv->zone6 = uma_zcreate("NetFlow IPv6 cache", sizeof(struct flow6_entry), + uma_ctor_flow6, uma_dtor_flow6, NULL, NULL, UMA_ALIGN_CACHE, 0); + uma_zone_set_max(priv->zone6, CACHESIZE); +#endif /* Allocate hash. */ priv->hash = malloc(NBUCKETS * sizeof(struct flow_hash_entry), M_NETFLOW_HASH, M_WAITOK | M_ZERO); - if (priv->hash == NULL) { - uma_zdestroy(priv->zone); - return (ENOMEM); - } - /* Initialize hash. */ for (i = 0, hsh = priv->hash; i < NBUCKETS; i++, hsh++) { mtx_init(&hsh->mtx, "hash mutex", NULL, MTX_DEF); TAILQ_INIT(&hsh->head); } - mtx_init(&priv->export_mtx, "export dgram lock", NULL, MTX_DEF); +#ifdef INET6 + /* Allocate hash. */ + priv->hash6 = malloc(NBUCKETS * sizeof(struct flow6_hash_entry), + M_NETFLOW_HASH, M_WAITOK | M_ZERO); + + /* Initialize hash. */ + for (i = 0, hsh6 = priv->hash6; i < NBUCKETS; i++, hsh6++) { + mtx_init(&hsh6->mtx, "hash mutex", NULL, MTX_DEF); + TAILQ_INIT(&hsh6->head); + } +#endif + + ng_netflow_v9_cache_init(priv); + CTR0(KTR_NET, "ng_netflow startup()"); return (0); } +/* Initialize new FIB table for v5 and v9 */ +int +ng_netflow_fib_init(priv_p priv, int fib) +{ + fib_export_p fe = priv_to_fib(priv, fib); + + CTR1(KTR_NET, "ng_netflow(): fib init: %d", fib); + + if (fe != NULL) + return (0); + + if ((fe = malloc(sizeof(struct fib_export), M_NETGRAPH, M_NOWAIT | M_ZERO)) == NULL) + return (1); + + mtx_init(&fe->export_mtx, "export dgram lock", NULL, MTX_DEF); + mtx_init(&fe->export9_mtx, "export9 dgram lock", NULL, MTX_DEF); + fe->fib = fib; + fe->domain_id = fib; + + if (atomic_cmpset_ptr((volatile uintptr_t *)&priv->fib_data[fib], (uintptr_t)NULL, (uintptr_t)fe) == 0) { + /* FIB already set up by other ISR */ + CTR3(KTR_NET, "ng_netflow(): fib init: %d setup %p but got %p", fib, fe, priv_to_fib(priv, fib)); + mtx_destroy(&fe->export_mtx); + mtx_destroy(&fe->export9_mtx); + free(fe, M_NETGRAPH); + } else { + /* Increase counter for statistics */ + CTR3(KTR_NET, "ng_netflow(): fib %d setup to %p (%p)", fib, fe, priv_to_fib(priv, fib)); + atomic_fetchadd_32(&priv->info.nfinfo_alloc_fibs, 1); + } + + return (0); +} + /* Free all flow cache memory. Called from node close method. */ void ng_netflow_cache_flush(priv_p priv) { struct flow_entry *fle, *fle1; struct flow_hash_entry *hsh; - item_p item = NULL; +#ifdef INET6 + struct flow6_entry *fle6, *fle61; + struct flow6_hash_entry *hsh6; +#endif + struct netflow_export_item exp; + fib_export_p fe; int i; + bzero(&exp, sizeof(exp)); + /* * We are going to free probably billable data. * Expire everything before freeing it. @@ -368,36 +602,67 @@ ng_netflow_cache_flush(priv_p priv) for (hsh = priv->hash, i = 0; i < NBUCKETS; hsh++, i++) TAILQ_FOREACH_SAFE(fle, &hsh->head, fle_hash, fle1) { TAILQ_REMOVE(&hsh->head, fle, fle_hash); - expire_flow(priv, &item, fle, NG_QUEUE); + fe = priv_to_fib(priv, fle->f.r.fib); + expire_flow(priv, fe, fle, NG_QUEUE); } - - if (item != NULL) - export_send(priv, item, NG_QUEUE); +#ifdef INET6 + for (hsh6 = priv->hash6, i = 0; i < NBUCKETS; hsh6++, i++) + TAILQ_FOREACH_SAFE(fle6, &hsh6->head, fle6_hash, fle61) { + TAILQ_REMOVE(&hsh6->head, fle6, fle6_hash); + fe = priv_to_fib(priv, fle6->f.r.fib); + expire_flow(priv, fe, (struct flow_entry *)fle6, NG_QUEUE); + } +#endif uma_zdestroy(priv->zone); - /* Destroy hash mutexes. */ for (i = 0, hsh = priv->hash; i < NBUCKETS; i++, hsh++) mtx_destroy(&hsh->mtx); /* Free hash memory. */ - if (priv->hash) + if (priv->hash != NULL) free(priv->hash, M_NETFLOW_HASH); +#ifdef INET6 + uma_zdestroy(priv->zone6); + /* Destroy hash mutexes. */ + for (i = 0, hsh6 = priv->hash6; i < NBUCKETS; i++, hsh6++) + mtx_destroy(&hsh6->mtx); + + /* Free hash memory. */ + if (priv->hash6 != NULL) + free(priv->hash6, M_NETFLOW_HASH); +#endif + + for (i = 0; i < RT_NUMFIBS; i++) { + if ((fe = priv_to_fib(priv, i)) == NULL) + continue; + + if (fe->exp.item != NULL) + export_send(priv, fe, fe->exp.item, NG_QUEUE); - mtx_destroy(&priv->export_mtx); + if (fe->exp.item9 != NULL) + export9_send(priv, fe, fe->exp.item9, fe->exp.item9_opt, NG_QUEUE); + + mtx_destroy(&fe->export_mtx); + mtx_destroy(&fe->export9_mtx); + free(fe, M_NETGRAPH); + } + + ng_netflow_v9_cache_flush(priv); } /* Insert packet from into flow cache. */ int -ng_netflow_flow_add(priv_p priv, struct ip *ip, unsigned int src_if_index) +ng_netflow_flow_add(priv_p priv, fib_export_p fe, struct ip *ip, caddr_t upper_ptr, uint8_t upper_proto, + uint8_t is_frag, unsigned int src_if_index) { register struct flow_entry *fle, *fle1; - struct flow_hash_entry *hsh; + struct flow_hash_entry *hsh; struct flow_rec r; - item_p item = NULL; int hlen, plen; int error = 0; uint8_t tcp_flags = 0; + uint16_t eproto; /* Try to fill flow_rec r */ bzero(&r, sizeof(r)); @@ -411,8 +676,13 @@ ng_netflow_flow_add(priv_p priv, struct ip *ip, unsigned int src_if_index) if (hlen < sizeof(struct ip)) return (EINVAL); + eproto = ETHERTYPE_IP; + /* Assume L4 template by default */ + r.flow_type = NETFLOW_V9_FLOW_V4_L4; + r.r_src = ip->ip_src; r.r_dst = ip->ip_dst; + r.fib = fe->fib; /* save packet length */ plen = ntohs(ip->ip_len); @@ -448,8 +718,8 @@ ng_netflow_flow_add(priv_p priv, struct ip *ip, unsigned int src_if_index) break; } - /* Update node statistics. XXX: race... */ - priv->info.nfinfo_packets ++; + atomic_fetchadd_32(&priv->info.nfinfo_packets, 1); + /* XXX: atomic */ priv->info.nfinfo_bytes += plen; /* Find hash slot. */ @@ -468,7 +738,7 @@ ng_netflow_flow_add(priv_p priv, struct ip *ip, unsigned int src_if_index) break; if ((INACTIVE(fle) && SMALL(fle)) || AGED(fle)) { TAILQ_REMOVE(&hsh->head, fle, fle_hash); - expire_flow(priv, &item, fle, NG_QUEUE); + expire_flow(priv, priv_to_fib(priv, fle->f.r.fib), fle, NG_QUEUE); atomic_add_32(&priv->info.nfinfo_act_exp, 1); } } @@ -487,9 +757,9 @@ ng_netflow_flow_add(priv_p priv, struct ip *ip, unsigned int src_if_index) * - it is going to overflow counter */ if (tcp_flags & TH_FIN || tcp_flags & TH_RST || AGED(fle) || - (fle->f.bytes >= (UINT_MAX - IF_MAXMTU)) ) { + (fle->f.bytes >= (CNTR_MAX - IF_MAXMTU)) ) { TAILQ_REMOVE(&hsh->head, fle, fle_hash); - expire_flow(priv, &item, fle, NG_QUEUE); + expire_flow(priv, priv_to_fib(priv, fle->f.r.fib), fle, NG_QUEUE); atomic_add_32(&priv->info.nfinfo_act_exp, 1); } else { /* @@ -507,24 +777,144 @@ ng_netflow_flow_add(priv_p priv, struct ip *ip, unsigned int src_if_index) mtx_unlock(&hsh->mtx); - if (item != NULL) - return_export_dgram(priv, item, NG_QUEUE); + return (error); +} + +#ifdef INET6 +/* Insert IPv6 packet from into flow cache. */ +int +ng_netflow_flow6_add(priv_p priv, fib_export_p fe, struct ip6_hdr *ip6, caddr_t upper_ptr, uint8_t upper_proto, + uint8_t is_frag, unsigned int src_if_index) +{ + register struct flow6_entry *fle6 = NULL, *fle61; + struct flow6_hash_entry *hsh6; + struct flow6_rec r; + int plen; + int error = 0; + uint8_t tcp_flags = 0; + + /* check version */ + if ((ip6->ip6_vfc & IPV6_VERSION_MASK) != IPV6_VERSION) + return (EINVAL); + + bzero(&r, sizeof(r)); + + r.src.r_src6 = ip6->ip6_src; + r.dst.r_dst6 = ip6->ip6_dst; + r.fib = fe->fib; + + /* Assume L4 template by default */ + r.flow_type = NETFLOW_V9_FLOW_V6_L4; + + /* save packet length */ + plen = ntohs(ip6->ip6_plen) + sizeof(struct ip6_hdr); + + /* XXX: set DSCP/CoS value */ +#if 0 + r.r_tos = ip->ip_tos; +#endif + if (is_frag == 0) { + switch(upper_proto) { + case IPPROTO_TCP: + { + register struct tcphdr *tcp; + + tcp = (struct tcphdr *)upper_ptr; + r.r_ports = *(uint32_t *)upper_ptr; + tcp_flags = tcp->th_flags; + break; + } + case IPPROTO_UDP: + case IPPROTO_SCTP: + { + r.r_ports = *(uint32_t *)upper_ptr; + break; + } + + } + } + + r.r_ip_p = upper_proto; + r.r_i_ifx = src_if_index; + + atomic_fetchadd_32(&priv->info.nfinfo_packets6, 1); + /* XXX: atomic */ + priv->info.nfinfo_bytes6 += plen; + + /* Find hash slot. */ + hsh6 = &priv->hash6[ip6_hash(&r)]; + + mtx_lock(&hsh6->mtx); + + /* + * Go through hash and find our entry. If we encounter an + * entry, that should be expired, purge it. We do a reverse + * search since most active entries are first, and most + * searches are done on most active entries. + */ + TAILQ_FOREACH_REVERSE_SAFE(fle6, &hsh6->head, f6head, fle6_hash, fle61) { + if (fle6->f.version != IP6VERSION) + continue; + if (bcmp(&r, &fle6->f.r, sizeof(struct flow6_rec)) == 0) + break; + if ((INACTIVE(fle6) && SMALL(fle6)) || AGED(fle6)) { + TAILQ_REMOVE(&hsh6->head, fle6, fle6_hash); + expire_flow(priv, priv_to_fib(priv, fle6->f.r.fib), (struct flow_entry *)fle6, NG_QUEUE); + atomic_add_32(&priv->info.nfinfo_act_exp, 1); + } + } + + if (fle6 != NULL) { /* An existent entry. */ + + fle6->f.bytes += plen; + fle6->f.packets ++; + fle6->f.tcp_flags |= tcp_flags; + fle6->f.last = time_uptime; + + /* + * We have the following reasons to expire flow in active way: + * - it hit active timeout + * - a TCP connection closed + * - it is going to overflow counter + */ + if (tcp_flags & TH_FIN || tcp_flags & TH_RST || AGED(fle6) || + (fle6->f.bytes >= (CNTR_MAX - IF_MAXMTU)) ) { + TAILQ_REMOVE(&hsh6->head, fle6, fle6_hash); + expire_flow(priv, priv_to_fib(priv, fle6->f.r.fib), (struct flow_entry *)fle6, NG_QUEUE); + atomic_add_32(&priv->info.nfinfo_act_exp, 1); + } else { + /* + * It is the newest, move it to the tail, + * if it isn't there already. Next search will + * locate it quicker. + */ + if (fle6 != TAILQ_LAST(&hsh6->head, f6head)) { + TAILQ_REMOVE(&hsh6->head, fle6, fle6_hash); + TAILQ_INSERT_TAIL(&hsh6->head, fle6, fle6_hash); + } + } + } else /* A new flow entry. */ + error = hash6_insert(priv, hsh6, &r, plen, tcp_flags); + + mtx_unlock(&hsh6->mtx); return (error); } +#endif /* * Return records from cache to userland. * * TODO: matching particular IP should be done in kernel, here. + * XXX: IPv6 flows will return random data */ int ng_netflow_flow_show(priv_p priv, uint32_t last, struct ng_mesg *resp) { - struct flow_hash_entry *hsh; - struct flow_entry *fle; - struct ngnf_flows *data; - int i; + struct flow_hash_entry *hsh; + struct flow_entry *fle; + struct ngnf_flows *data; + int i; data = (struct ngnf_flows *)resp->data; data->last = 0; @@ -579,7 +969,7 @@ ng_netflow_flow_show(priv_p priv, uint32_t last, struct ng_mesg *resp) /* We have full datagram in privdata. Send it to export hook. */ static int -export_send(priv_p priv, item_p item, int flags) +export_send(priv_p priv, fib_export_p fe, item_p item, int flags) { struct mbuf *m = NGI_M(item); struct netflow_v5_export_dgram *dgram = mtod(m, @@ -598,9 +988,9 @@ export_send(priv_p priv, item_p item, int flags) header->unix_secs = htonl(ts.tv_sec); header->unix_nsecs = htonl(ts.tv_nsec); header->engine_type = 0; - header->engine_id = 0; + header->engine_id = fe->domain_id; header->pad = 0; - header->flow_seq = htonl(atomic_fetchadd_32(&priv->flow_seq, + header->flow_seq = htonl(atomic_fetchadd_32(&fe->flow_seq, header->count)); header->count = htons(header->count); @@ -663,8 +1053,11 @@ ng_netflow_expire(void *arg) { struct flow_entry *fle, *fle1; struct flow_hash_entry *hsh; +#ifdef INET6 + struct flow6_entry *fle6, *fle61; + struct flow6_hash_entry *hsh6; +#endif priv_p priv = (priv_p )arg; - item_p item = NULL; uint32_t used; int i; @@ -697,7 +1090,7 @@ ng_netflow_expire(void *arg) if ((INACTIVE(fle) && (SMALL(fle) || (used > (NBUCKETS*2)))) || AGED(fle)) { TAILQ_REMOVE(&hsh->head, fle, fle_hash); - expire_flow(priv, &item, fle, NG_NOFLAGS); + expire_flow(priv, priv_to_fib(priv, fle->f.r.fib), fle, NG_NOFLAGS); used--; atomic_add_32(&priv->info.nfinfo_inact_exp, 1); } @@ -705,8 +1098,41 @@ ng_netflow_expire(void *arg) mtx_unlock(&hsh->mtx); } - if (item != NULL) - return_export_dgram(priv, item, NG_NOFLAGS); +#ifdef INET6 + for (hsh6 = priv->hash6, i = 0; i < NBUCKETS; hsh6++, i++) { + /* + * Skip entries, that are already being worked on. + */ + if (mtx_trylock(&hsh6->mtx) == 0) + continue; + + used = atomic_load_acq_32(&priv->info.nfinfo_used6); + TAILQ_FOREACH_SAFE(fle6, &hsh6->head, fle6_hash, fle61) { + /* + * Interrupt thread wants this entry! + * Quick! Quick! Bail out! + */ + if (hsh6->mtx.mtx_lock & MTX_CONTESTED) + break; + + /* + * Don't expire aggressively while hash collision + * ratio is predicted small. + */ + if (used <= (NBUCKETS*2) && !INACTIVE(fle6)) + break; + + if ((INACTIVE(fle6) && (SMALL(fle6) || + (used > (NBUCKETS*2)))) || AGED(fle6)) { + TAILQ_REMOVE(&hsh6->head, fle6, fle6_hash); + expire_flow(priv, priv_to_fib(priv, fle6->f.r.fib), (struct flow_entry *)fle6, NG_NOFLAGS); + used--; + atomic_add_32(&priv->info.nfinfo_inact_exp, 1); + } + } + mtx_unlock(&hsh6->mtx); + } +#endif /* Schedule next expire. */ callout_reset(&priv->exp_callout, (1*hz), &ng_netflow_expire, diff --git a/sys/netgraph/netflow/netflow.h b/sys/netgraph/netflow/netflow.h index ef709bc..fb13cf7 100644 --- a/sys/netgraph/netflow/netflow.h +++ b/sys/netgraph/netflow/netflow.h @@ -1,4 +1,5 @@ /*- + * Copyright (c) 2010-2011 Alexander V. Chernikov <melifaro@ipfw.ru> * Copyright (c) 2004 Gleb Smirnoff <glebius@FreeBSD.org> * All rights reserved. * @@ -42,10 +43,14 @@ * Datagram Formats. * http://www.cisco.com/en/US/products/sw/netmgtsw/ps1964/products_user_guide_chapter09186a00803f3147.html#wp26453 * + * Cisco Systems NetFlow Services Export Version 9 + * http://www.ietf.org/rfc/rfc3954.txt + * */ #define NETFLOW_V1 1 #define NETFLOW_V5 5 +#define NETFLOW_V9 9 struct netflow_v1_header { @@ -69,6 +74,16 @@ struct netflow_v5_header uint16_t pad; /* Pad to word boundary */ } __attribute__((__packed__)); +struct netflow_v9_header +{ + uint16_t version; /* NetFlow version */ + uint16_t count; /* Total number of records in packet */ + uint32_t sys_uptime; /* System uptime */ + uint32_t unix_secs; /* Current seconds since 0000 UTC 1970 */ + uint32_t seq_num; /* Sequence number */ + uint32_t source_id; /* Observation Domain id */ +} __attribute__((__packed__)); + struct netflow_v1_record { uint32_t src_addr; /* Source IP address */ @@ -127,3 +142,73 @@ struct netflow_v5_export_dgram { struct netflow_v5_header header; struct netflow_v5_record r[NETFLOW_V5_MAX_RECORDS]; } __attribute__((__packed__)); + + +/* RFC3954 field definitions */ +#define NETFLOW_V9_FIELD_IN_BYTES 1 /* Input bytes count for a flow. Default 4, can be 8 */ +#define NETFLOW_V9_FIELD_IN_PKTS 2 /* Incoming counter with number of packets associated with an IP Flow. Default 4 */ +#define NETFLOW_V9_FIELD_FLOWS 3 /* Number of Flows that were aggregated. Default 4 */ +#define NETFLOW_V9_FIELD_PROTOCOL 4 /* IP protocol byte. 1 */ +#define NETFLOW_V9_FIELD_TOS 5 /* Type of service byte setting when entering the incoming interface. 1 */ +#define NETFLOW_V9_FIELD_TCP_FLAGS 6 /* TCP flags; cumulative of all the TCP flags seen in this Flow. 1 */ +#define NETFLOW_V9_FIELD_L4_SRC_PORT 7 /* TCP/UDP source port number. 2 */ +#define NETFLOW_V9_FIELD_IPV4_SRC_ADDR 8 /* IPv4 source address. 4 */ +#define NETFLOW_V9_FIELD_SRC_MASK 9 /* The number of contiguous bits in the source subnet mask (i.e., the mask in slash notation). 1 */ +#define NETFLOW_V9_FIELD_INPUT_SNMP 10 /* Input interface index. Default 2 */ +#define NETFLOW_V9_FIELD_L4_DST_PORT 11 /* TCP/UDP destination port number. 2 */ +#define NETFLOW_V9_FIELD_IPV4_DST_ADDR 12 /* IPv4 destination address. 4 */ +#define NETFLOW_V9_FIELD_DST_MASK 13 /* The number of contiguous bits in the destination subnet mask (i.e., the mask in slash notation). 1 */ +#define NETFLOW_V9_FIELD_OUTPUT_SNMP 14 /* Output interface index. Default 2 */ +#define NETFLOW_V9_FIELD_IPV4_NEXT_HOP 15 /* IPv4 address of the next-hop router. 4 */ +#define NETFLOW_V9_FIELD_SRC_AS 16 /* Source BGP autonomous system number. Default 2, can be 4 */ +#define NETFLOW_V9_FIELD_DST_AS 17 /* Destination BGP autonomous system number. Default 2, can be 4 */ +#define NETFLOW_V9_FIELD_BGP_IPV4_NEXT_HOP 18 /* Next-hop router's IP address in the BGP domain. 4 */ +#define NETFLOW_V9_FIELD_MUL_DST_PKTS 19 /* IP multicast outgoing packet counter for packets associated with IP flow. Default 4 */ +#define NETFLOW_V9_FIELD_MUL_DST_BYTES 20 /* IP multicast outgoing Octet (byte) counter for the number of bytes associated with IP flow. Default 4 */ +#define NETFLOW_V9_FIELD_LAST_SWITCHED 21 /* sysUptime in msec at which the last packet of this Flow was switched. 4 */ +#define NETFLOW_V9_FIELD_FIRST_SWITCHED 22 /* sysUptime in msec at which the first packet of this Flow was switched. 4 */ +#define NETFLOW_V9_FIELD_OUT_BYTES 23 /* Outgoing counter for the number of bytes associated with an IP Flow. Default 4 */ +#define NETFLOW_V9_FIELD_OUT_PKTS 24 /* Outgoing counter for the number of packets associated with an IP Flow. Default 4 */ +#define NETFLOW_V9_FIELD_IPV6_SRC_ADDR 27 /* IPv6 source address. 16 */ +#define NETFLOW_V9_FIELD_IPV6_DST_ADDR 28 /* IPv6 destination address. 16 */ +#define NETFLOW_V9_FIELD_IPV6_SRC_MASK 29 /* Length of the IPv6 source mask in contiguous bits. 1 */ +#define NETFLOW_V9_FIELD_IPV6_DST_MASK 30 /* Length of the IPv6 destination mask in contiguous bits. 1 */ +#define NETFLOW_V9_FIELD_IPV6_FLOW_LABEL 31 /* IPv6 flow label as per RFC 2460 definition. 3 */ +#define NETFLOW_V9_FIELD_ICMP_TYPE 32 /* Internet Control Message Protocol (ICMP) packet type; reported as ICMP Type * 256 + ICMP code. 2 */ +#define NETFLOW_V9_FIELD_MUL_IGMP_TYPE 33 /* Internet Group Management Protocol (IGMP) packet type. 1 */ +#define NETFLOW_V9_FIELD_SAMPLING_INTERVAL 34 /* When using sampled NetFlow, the rate at which packets are sampled; for example, a value of 100 indicates that one of every hundred packets is sampled. 4 */ +#define NETFLOW_V9_FIELD_SAMPLING_ALGORITHM 35 /* For sampled NetFlow platform-wide: 0x01 deterministic sampling 0x02 random sampling. 1 */ +#define NETFLOW_V9_FIELD_FLOW_ACTIVE_TIMEOUT 36 /* Timeout value (in seconds) for active flow entries in the NetFlow cache. 2 */ +#define NETFLOW_V9_FIELD_FLOW_INACTIVE_TIMEOUT 37 /* Timeout value (in seconds) for inactive Flow entries in the NetFlow cache. 2 */ +#define NETFLOW_V9_FIELD_ENGINE_TYPE 38 /* Type of Flow switching engine (route processor, linecard, etc...). 1 */ +#define NETFLOW_V9_FIELD_ENGINE_ID 39 /* ID number of the Flow switching engine. 1 */ +#define NETFLOW_V9_FIELD_TOTAL_BYTES_EXP 40 /* Counter with for the number of bytes exported by the Observation Domain. Default 4 */ +#define NETFLOW_V9_FIELD_TOTAL_PKTS_EXP 41 /* Counter with for the number of packets exported by the Observation Domain. Default 4 */ +#define NETFLOW_V9_FIELD_TOTAL_FLOWS_EXP 42 /* Counter with for the number of flows exported by the Observation Domain. Default 4 */ +#define NETFLOW_V9_FIELD_MPLS_TOP_LABEL_TYPE 46 /* MPLS Top Label Type. 1 */ +#define NETFLOW_V9_FIELD_MPLS_TOP_LABEL_IP_ADDR 47 /* Forwarding Equivalent Class corresponding to the MPLS Top Label. 4 */ +#define NETFLOW_V9_FIELD_FLOW_SAMPLER_ID 48 /* Identifier shown in "show flow-sampler". 1 */ +#define NETFLOW_V9_FIELD_FLOW_SAMPLER_MODE 49 /* The type of algorithm used for sampling data. 2 */ +#define NETFLOW_V9_FIELD_FLOW_SAMPLER_RANDOM_INTERVAL 50 /* Packet interval at which to sample. 4. */ +#define NETFLOW_V9_FIELD_DST_TOS 55 /* Type of Service byte setting when exiting outgoing interface. 1. */ +#define NETFLOW_V9_FIELD_SRC_MAC 56 /* Source MAC Address. 6 */ +#define NETFLOW_V9_FIELD_DST_MAC 57 /* Destination MAC Address. 6 */ +#define NETFLOW_V9_FIELD_SRC_VLAN 58 /* Virtual LAN identifier associated with ingress interface. 2 */ +#define NETFLOW_V9_FIELD_DST_VLAN 59 /* Virtual LAN identifier associated with egress interface. 2 */ +#define NETFLOW_V9_FIELD_IP_PROTOCOL_VERSION 60 /* Internet Protocol Version. Set to 4 for IPv4, set to 6 for IPv6. If not present in the template, then version 4 is assumed. 1. */ +#define NETFLOW_V9_FIELD_DIRECTION 61 /* Flow direction: 0 - ingress flow 1 - egress flow. 1 */ +#define NETFLOW_V9_FIELD_IPV6_NEXT_HOP 62 /* IPv6 address of the next-hop router. 16 */ +#define NETFLOW_V9_FIELD_BGP_IPV6_NEXT_HOP 63 /* Next-hop router in the BGP domain. 16 */ +#define NETFLOW_V9_FIELD_IPV6_OPTION_HEADERS 64 /* Bit-encoded field identifying IPv6 option headers found in the flow */ +#define NETFLOW_V9_FIELD_MPLS_LABEL_1 70 /* MPLS label at position 1 in the stack. 3 */ +#define NETFLOW_V9_FIELD_MPLS_LABEL_2 71 /* MPLS label at position 2 in the stack. 3 */ +#define NETFLOW_V9_FIELD_MPLS_LABEL_3 72 /* MPLS label at position 3 in the stack. 3 */ +#define NETFLOW_V9_FIELD_MPLS_LABEL_4 73 /* MPLS label at position 4 in the stack. 3 */ +#define NETFLOW_V9_FIELD_MPLS_LABEL_5 74 /* MPLS label at position 5 in the stack. 3 */ +#define NETFLOW_V9_FIELD_MPLS_LABEL_6 75 /* MPLS label at position 6 in the stack. 3 */ +#define NETFLOW_V9_FIELD_MPLS_LABEL_7 76 /* MPLS label at position 7 in the stack. 3 */ +#define NETFLOW_V9_FIELD_MPLS_LABEL_8 77 /* MPLS label at position 8 in the stack. 3 */ +#define NETFLOW_V9_FIELD_MPLS_LABEL_9 78 /* MPLS label at position 9 in the stack. 3 */ +#define NETFLOW_V9_FIELD_MPLS_LABEL_10 79 /* MPLS label at position 10 in the stack. 3 */ + +#define NETFLOW_V9_MAX_RESERVED_FLOWSET 0xFF /* Clause 5.2 */ diff --git a/sys/netgraph/netflow/netflow_v9.c b/sys/netgraph/netflow/netflow_v9.c new file mode 100644 index 0000000..2055a62 --- /dev/null +++ b/sys/netgraph/netflow/netflow_v9.c @@ -0,0 +1,483 @@ +/*- + * Copyright (c) 2010 Alexander V. Chernikov <melifaro@ipfw.ru> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +static const char rcs_id[] = + "@(#) $FreeBSD$"; + +#include "opt_inet6.h" +#include "opt_route.h" +#include <sys/param.h> +#include <sys/kernel.h> +#include <sys/limits.h> +#include <sys/mbuf.h> +#include <sys/syslog.h> +#include <sys/systm.h> +#include <sys/socket.h> +#include <sys/endian.h> + +#include <machine/atomic.h> +#include <machine/stdarg.h> + +#include <net/if.h> +#include <net/route.h> +#include <net/ethernet.h> +#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 <netgraph/ng_message.h> +#include <netgraph/netgraph.h> + +#include <netgraph/netflow/netflow.h> +#include <netgraph/netflow/ng_netflow.h> +#include <netgraph/netflow/netflow_v9.h> + +MALLOC_DECLARE(M_NETFLOW_GENERAL); +MALLOC_DEFINE(M_NETFLOW_GENERAL, "netflow_general", "plog, V9 templates data"); + +/* + * Base V9 templates for L4+ IPv4/IPv6 protocols + */ +struct netflow_v9_template _netflow_v9_record_ipv4_tcp[] = +{ + { NETFLOW_V9_FIELD_IPV4_SRC_ADDR, 4}, + { NETFLOW_V9_FIELD_IPV4_DST_ADDR, 4}, + { NETFLOW_V9_FIELD_IPV4_NEXT_HOP, 4}, + { NETFLOW_V9_FIELD_INPUT_SNMP, 2}, + { NETFLOW_V9_FIELD_OUTPUT_SNMP, 2}, + { NETFLOW_V9_FIELD_IN_PKTS, sizeof(CNTR)}, + { NETFLOW_V9_FIELD_IN_BYTES, sizeof(CNTR)}, + { NETFLOW_V9_FIELD_OUT_PKTS, sizeof(CNTR)}, + { NETFLOW_V9_FIELD_OUT_BYTES, sizeof(CNTR)}, + { NETFLOW_V9_FIELD_FIRST_SWITCHED, 4}, + { NETFLOW_V9_FIELD_LAST_SWITCHED, 4}, + { NETFLOW_V9_FIELD_L4_SRC_PORT, 2}, + { NETFLOW_V9_FIELD_L4_DST_PORT, 2}, + { NETFLOW_V9_FIELD_TCP_FLAGS, 1}, + { NETFLOW_V9_FIELD_PROTOCOL, 1}, + { NETFLOW_V9_FIELD_TOS, 1}, + { NETFLOW_V9_FIELD_SRC_AS, 4}, + { NETFLOW_V9_FIELD_DST_AS, 4}, + { NETFLOW_V9_FIELD_SRC_MASK, 1}, + { NETFLOW_V9_FIELD_DST_MASK, 1}, + {0, 0} +}; + +struct netflow_v9_template _netflow_v9_record_ipv6_tcp[] = +{ + { NETFLOW_V9_FIELD_IPV6_SRC_ADDR, 16}, + { NETFLOW_V9_FIELD_IPV6_DST_ADDR, 16}, + { NETFLOW_V9_FIELD_IPV6_NEXT_HOP, 16}, + { NETFLOW_V9_FIELD_INPUT_SNMP, 2}, + { NETFLOW_V9_FIELD_OUTPUT_SNMP, 2}, + { NETFLOW_V9_FIELD_IN_PKTS, sizeof(CNTR)}, + { NETFLOW_V9_FIELD_IN_BYTES, sizeof(CNTR)}, + { NETFLOW_V9_FIELD_OUT_PKTS, sizeof(CNTR)}, + { NETFLOW_V9_FIELD_OUT_BYTES, sizeof(CNTR)}, + { NETFLOW_V9_FIELD_FIRST_SWITCHED, 4}, + { NETFLOW_V9_FIELD_LAST_SWITCHED, 4}, + { NETFLOW_V9_FIELD_L4_SRC_PORT, 2}, + { NETFLOW_V9_FIELD_L4_DST_PORT, 2}, + { NETFLOW_V9_FIELD_TCP_FLAGS, 1}, + { NETFLOW_V9_FIELD_PROTOCOL, 1}, + { NETFLOW_V9_FIELD_TOS, 1}, + { NETFLOW_V9_FIELD_SRC_AS, 4}, + { NETFLOW_V9_FIELD_DST_AS, 4}, + { NETFLOW_V9_FIELD_SRC_MASK, 1}, + { NETFLOW_V9_FIELD_DST_MASK, 1}, + {0, 0} +}; + +/* + * Pre-compiles flow exporter for all possible FlowSets + * so we can add flowset to packet via simple memcpy() + */ +static void +generate_v9_templates(priv_p priv) +{ + uint16_t *p, *template_fields_cnt; + int cnt; + + int flowset_size = sizeof(struct netflow_v9_flowset_header) + + _NETFLOW_V9_TEMPLATE_SIZE(_netflow_v9_record_ipv4_tcp) + /* netflow_v9_record_ipv4_tcp */ + _NETFLOW_V9_TEMPLATE_SIZE(_netflow_v9_record_ipv6_tcp); /* netflow_v9_record_ipv6_tcp */ + + priv->v9_flowsets[0] = malloc(flowset_size, M_NETFLOW_GENERAL, M_WAITOK | M_ZERO); + + if (flowset_size % 4) + flowset_size += 4 - (flowset_size % 4); /* Padding to 4-byte boundary */ + + priv->flowsets_count = 1; + p = (uint16_t *)priv->v9_flowsets[0]; + *p++ = 0; /* Flowset ID, 0 is reserved for Template FlowSets */ + *p++ = htons(flowset_size); /* Total FlowSet length */ + + /* + * Most common TCP/UDP IPv4 template, ID = 256 + */ + *p++ = htons(NETFLOW_V9_MAX_RESERVED_FLOWSET + NETFLOW_V9_FLOW_V4_L4); + template_fields_cnt = p++; + for (cnt = 0; _netflow_v9_record_ipv4_tcp[cnt].field_id != 0; cnt++) { + *p++ = htons(_netflow_v9_record_ipv4_tcp[cnt].field_id); + *p++ = htons(_netflow_v9_record_ipv4_tcp[cnt].field_length); + } + *template_fields_cnt = htons(cnt); + + /* + * TCP/UDP IPv6 template, ID = 257 + */ + *p++ = htons(NETFLOW_V9_MAX_RESERVED_FLOWSET + NETFLOW_V9_FLOW_V6_L4); + template_fields_cnt = p++; + for (cnt = 0; _netflow_v9_record_ipv6_tcp[cnt].field_id != 0; cnt++) { + *p++ = htons(_netflow_v9_record_ipv6_tcp[cnt].field_id); + *p++ = htons(_netflow_v9_record_ipv6_tcp[cnt].field_length); + } + *template_fields_cnt = htons(cnt); + + priv->flowset_records[0] = 2; +} + +/* Closes current data flowset */ +static void inline +close_flowset(struct mbuf *m, struct netflow_v9_packet_opt *t) +{ + struct mbuf *m_old; + uint32_t zero = 0; + int offset = 0; + uint16_t *flowset_length, len; + + /* Hack to ensure we are not crossing mbuf boundary, length is uint16_t */ + m_old = m_getptr(m, t->flow_header + offsetof(struct netflow_v9_flowset_header, length), &offset); + flowset_length = (uint16_t *)(mtod(m_old, char *) + offset); + + len = (uint16_t)(m_pktlen(m) - t->flow_header); + /* Align on 4-byte boundary (RFC 3954, Clause 5.3) */ + if (len % 4) { + if (m_append(m, 4 - (len % 4), (void *)&zero) != 1) + panic("ng_netflow: m_append() failed!"); + + len += 4 - (len % 4); + } + + *flowset_length = htons(len); +} + +/* + * Non-static functions called from ng_netflow.c + */ + +/* We have full datagram in fib data. Send it to export hook. */ +int +export9_send(priv_p priv, fib_export_p fe, item_p item, struct netflow_v9_packet_opt *t, int flags) +{ + struct mbuf *m = NGI_M(item); + struct netflow_v9_export_dgram *dgram = mtod(m, + struct netflow_v9_export_dgram *); + struct netflow_v9_header *header = &dgram->header; + struct timespec ts; + int error = 0; + + if (t == NULL) { + CTR0(KTR_NET, "export9_send(): V9 export packet without tag"); + NG_FREE_ITEM(item); + return (0); + } + + /* Close flowset if not closed already */ + if (m_pktlen(m) != t->flow_header) + close_flowset(m, t); + + /* Fill export header. */ + header->count = t->count; + header->sys_uptime = htonl(MILLIUPTIME(time_uptime)); + getnanotime(&ts); + header->unix_secs = htonl(ts.tv_sec); + header->seq_num = htonl(atomic_fetchadd_32(&fe->flow9_seq, 1)); + header->count = htons(t->count); + header->source_id = htonl(NG_NODE_ID(priv->node)); + + if (priv->export9 != NULL) + NG_FWD_ITEM_HOOK_FLAGS(error, item, priv->export9, flags); + else + NG_FREE_ITEM(item); + + free(t, M_NETFLOW_GENERAL); + + return (error); +} + + + +/* Add V9 record to dgram. */ +int +export9_add(item_p item, struct netflow_v9_packet_opt *t, struct flow_entry *fle) +{ + size_t len = 0; + struct netflow_v9_flowset_header fsh; + struct netflow_v9_record_general rg; + struct mbuf *m = NGI_M(item); + uint16_t flow_type; + struct flow_entry_data *fed; +#ifdef INET6 + struct flow6_entry_data *fed6; +#endif + if (t == NULL) { + CTR0(KTR_NET, "ng_netflow: V9 export packet without tag!"); + return (0); + } + + /* Prepare flow record */ + fed = (struct flow_entry_data *)&fle->f; + fed6 = (struct flow6_entry_data *)&fle->f; + /* We can use flow_type field since fle6 offset is equal to fle */ + flow_type = fed->r.flow_type; + + switch (flow_type) { + case NETFLOW_V9_FLOW_V4_L4: + { + /* IPv4 TCP/UDP/[SCTP] */ + struct netflow_v9_record_ipv4_tcp *rec = &rg.rec.v4_tcp; + + rec->src_addr = fed->r.r_src.s_addr; + rec->dst_addr = fed->r.r_dst.s_addr; + rec->next_hop = fed->next_hop.s_addr; + rec->i_ifx = htons(fed->fle_i_ifx); + rec->o_ifx = htons(fed->fle_o_ifx); + rec->i_packets = htonl(fed->packets); + rec->i_octets = htonl(fed->bytes); + rec->o_packets = htonl(0); + rec->o_octets = htonl(0); + rec->first = htonl(MILLIUPTIME(fed->first)); + rec->last = htonl(MILLIUPTIME(fed->last)); + rec->s_port = fed->r.r_sport; + rec->d_port = fed->r.r_dport; + rec->flags = fed->tcp_flags; + rec->prot = fed->r.r_ip_p; + rec->tos = fed->r.r_tos; + rec->dst_mask = fed->dst_mask; + rec->src_mask = fed->src_mask; + + /* Not supported fields. */ + rec->src_as = rec->dst_as = 0; + + len = sizeof(struct netflow_v9_record_ipv4_tcp); + break; + } +#ifdef INET6 + case NETFLOW_V9_FLOW_V6_L4: + { + /* IPv6 TCP/UDP/[SCTP] */ + struct netflow_v9_record_ipv6_tcp *rec = &rg.rec.v6_tcp; + + rec->src_addr = fed6->r.src.r_src6; + rec->dst_addr = fed6->r.dst.r_dst6; + rec->next_hop = fed6->n.next_hop6; + rec->i_ifx = htons(fed6->fle_i_ifx); + rec->o_ifx = htons(fed6->fle_o_ifx); + rec->i_packets = htonl(fed6->packets); + rec->i_octets = htonl(fed6->bytes); + rec->o_packets = htonl(0); + rec->o_octets = htonl(0); + rec->first = htonl(MILLIUPTIME(fed6->first)); + rec->last = htonl(MILLIUPTIME(fed6->last)); + rec->s_port = fed6->r.r_sport; + rec->d_port = fed6->r.r_dport; + rec->flags = fed6->tcp_flags; + rec->prot = fed6->r.r_ip_p; + rec->tos = fed6->r.r_tos; + rec->dst_mask = fed6->dst_mask; + rec->src_mask = fed6->src_mask; + + /* Not supported fields. */ + rec->src_as = rec->dst_as = 0; + + len = sizeof(struct netflow_v9_record_ipv6_tcp); + break; + } +#endif + default: + { + CTR1(KTR_NET, "export9_add(): Don't know what to do with %d flow type!", flow_type); + return (0); + } + } + + /* Check if new records has the same template */ + if (flow_type != t->flow_type) { + /* close old flowset */ + if (t->flow_type != 0) + close_flowset(m, t); + + t->flow_type = flow_type; + t->flow_header = m_pktlen(m); + + /* Generate data flowset ID */ + fsh.id = htons(NETFLOW_V9_MAX_RESERVED_FLOWSET + flow_type); + fsh.length = 0; + + /* m_append should not fail since all data is already allocated */ + if (m_append(m, sizeof(fsh), (void *)&fsh) != 1) + panic("ng_netflow: m_append() failed"); + + } + + if (m_append(m, len, (void *)&rg.rec) != 1) + panic("ng_netflow: m_append() failed"); + + t->count++; + + if (m_pktlen(m) + sizeof(struct netflow_v9_record_general) + sizeof(struct netflow_v9_flowset_header) >= _NETFLOW_V9_MAX_SIZE(t->mtu)) + return (1); /* end of datagram */ + return (0); +} + +/* + * Detach export datagram from fib instance, if there is any. + * If there is no, allocate a new one. + */ +item_p +get_export9_dgram(priv_p priv, fib_export_p fe, struct netflow_v9_packet_opt **tt) +{ + item_p item = NULL; + struct netflow_v9_packet_opt *t = NULL; + + mtx_lock(&fe->export9_mtx); + if (fe->exp.item9 != NULL) { + item = fe->exp.item9; + fe->exp.item9 = NULL; + t = fe->exp.item9_opt; + fe->exp.item9_opt = NULL; + } + mtx_unlock(&fe->export9_mtx); + + if (item == NULL) { + struct netflow_v9_export_dgram *dgram; + struct mbuf *m; + uint16_t mtu = priv->mtu; + + /* Allocate entire packet at once, allowing easy m_append() calls */ + m = m_getm(NULL, mtu, M_DONTWAIT, MT_DATA); + if (m == NULL) + return (NULL); + + t = malloc(sizeof(struct netflow_v9_packet_opt), M_NETFLOW_GENERAL, M_NOWAIT | M_ZERO); + if (t == NULL) { + m_free(m); + return (NULL); + } + + item = ng_package_data(m, NG_NOFLAGS); + if (item == NULL) { + m_free(m); + free(t, M_NETFLOW_GENERAL); + return (NULL); + } + + dgram = mtod(m, struct netflow_v9_export_dgram *); + dgram->header.count = 0; + dgram->header.version = htons(NETFLOW_V9); + /* Set mbuf current data length */ + m->m_len = m->m_pkthdr.len = sizeof(struct netflow_v9_header); + + t->count = 0; + t->mtu = mtu; + t->flow_header = m->m_len; + + /* + * Check if we need to insert templates into packet + */ + + struct timespec ts; + struct netflow_v9_flowset_header *fl; + + getnanotime(&ts); + if ((ts.tv_sec >= priv->templ_time + fe->templ_last_ts) || + (fe->sent_packets >= priv->templ_packets + fe->templ_last_pkt)) { + + atomic_store_rel_32(&fe->templ_last_ts, ts.tv_sec); + atomic_store_rel_32(&fe->templ_last_pkt, fe->sent_packets); + + fl = priv->v9_flowsets[0]; + m_append(m, ntohs(fl->length), (void *)fl); + t->flow_header = m->m_len; + t->count += priv->flowset_records[0]; + } + + } + + *tt = t; + return (item); +} + +/* + * Re-attach incomplete datagram back to fib instance. + * If there is already another one, then send incomplete. + */ +void +return_export9_dgram(priv_p priv, fib_export_p fe, item_p item, struct netflow_v9_packet_opt *t, int flags) +{ + /* + * It may happen on SMP, that some thread has already + * put its item there, in this case we bail out and + * send what we have to collector. + */ + mtx_lock(&fe->export9_mtx); + if (fe->exp.item9 == NULL) { + fe->exp.item9 = item; + fe->exp.item9_opt = t; + mtx_unlock(&fe->export9_mtx); + } else { + mtx_unlock(&fe->export9_mtx); + export9_send(priv, fe, item, t, flags); + } +} + +/* Allocate memory and set up flow cache */ +void +ng_netflow_v9_cache_init(priv_p priv) +{ + generate_v9_templates(priv); + + priv->templ_time = NETFLOW_V9_MAX_TIME_TEMPL; + priv->templ_packets = NETFLOW_V9_MAX_PACKETS_TEMPL; + priv->mtu = BASE_MTU; +} + +/* Free all flow cache memory. Called from ng_netflow_cache_flush() */ +void +ng_netflow_v9_cache_flush(priv_p priv) +{ + int i; + + /* Free flowsets*/ + for (i = 0; i < priv->flowsets_count; i++) + free(priv->v9_flowsets[i], M_NETFLOW_GENERAL); +} diff --git a/sys/netgraph/netflow/netflow_v9.h b/sys/netgraph/netflow/netflow_v9.h new file mode 100644 index 0000000..5d7985b --- /dev/null +++ b/sys/netgraph/netflow/netflow_v9.h @@ -0,0 +1,148 @@ +/*- + * Copyright (c) 2010 Alexander V. Chernikov <melifaro@ipfw.ru> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#ifndef _NETFLOW_V9_H_ +#define _NETFLOW_V9_H_ + +#ifdef COUNTERS_64 +#define CNTR uint64_t +#define CNTR_MAX UINT64_MAX +#else +#define CNTR uint32_t +#define CNTR_MAX UINT_MAX +#endif + +struct netflow_v9_template +{ + int field_id; + int field_length; +}; + +/* Template ID for tcp/udp v4 streams ID:257 (0x100 + NETFLOW_V9_FLOW_V4_L4) */ +struct netflow_v9_record_ipv4_tcp +{ + uint32_t src_addr; /* Source IPv4 address (IPV4_SRC_ADDR) */ + uint32_t dst_addr; /* Destination IPv4 address (IPV4_DST_ADDR) */ + uint32_t next_hop; /* Next hop IPv4 address (IPV4_NEXT_HOP) */ + uint16_t i_ifx; /* Source interface index (INPUT_SNMP) */ + uint16_t o_ifx; /* Destination interface index (OUTPUT_SNMP) */ + CNTR i_packets; /* Number of incoming packets in a flow (IN_PKTS) */ + CNTR i_octets; /* Number of incoming octets in a flow (IN_BYTES) */ + CNTR o_packets; /* Number of outgoing packets in a flow (OUT_PKTS) */ + CNTR o_octets; /* Number of outgoing octets in a flow (OUT_BYTES) */ + uint32_t first; /* System uptime at start of a flow (FIRST_SWITCHED) */ + uint32_t last; /* System uptime at end of a flow (LAST_SWITCHED) */ + uint16_t s_port; /* Source port (L4_SRC_PORT) */ + uint16_t d_port; /* Destination port (L4_DST_PORT) */ + uint8_t flags; /* Cumulative OR of tcp flags (TCP_FLAGS) */ + uint8_t prot; /* IP protocol */ + uint8_t tos; /* IP type of service IN (or OUT) (TOS) */ + uint32_t src_as; /* Src peer/origin Autonomous System (SRC_AS) */ + uint32_t dst_as; /* Dst peer/origin Autonomous System (DST_AS) */ + uint8_t src_mask; /* Source route's mask bits (SRC_MASK) */ + uint8_t dst_mask; /* Destination route's mask bits (DST_MASK) */ +} __attribute__((__packed__)); + +/* Template ID for tcp/udp v6 streams ID: 260 (0x100 + NETFLOW_V9_FLOW_V6_L4) */ +struct netflow_v9_record_ipv6_tcp +{ + struct in6_addr src_addr; /* Source IPv6 address (IPV6_SRC_ADDR) */ + struct in6_addr dst_addr; /* Destination IPv6 address (IPV6_DST_ADDR) */ + struct in6_addr next_hop; /* Next hop IPv6 address (IPV6_NEXT_HOP) */ + uint16_t i_ifx; /* Source interface index (INPUT_SNMP) */ + uint16_t o_ifx; /* Destination interface index (OUTPUT_SNMP) */ + CNTR i_packets; /* Number of incoming packets in a flow (IN_PKTS) */ + CNTR i_octets; /* Number of incoming octets in a flow (IN_BYTES) */ + CNTR o_packets; /* Number of outgoing packets in a flow (OUT_PKTS) */ + CNTR o_octets; /* Number of outgoing octets in a flow (OUT_BYTES) */ + uint32_t first; /* System uptime at start of a flow (FIRST_SWITCHED) */ + uint32_t last; /* System uptime at end of a flow (LAST_SWITCHED) */ + uint16_t s_port; /* Source port (L4_SRC_PORT) */ + uint16_t d_port; /* Destination port (L4_DST_PORT) */ + uint8_t flags; /* Cumulative OR of tcp flags (TCP_FLAGS) */ + uint8_t prot; /* IP protocol */ + uint8_t tos; /* IP type of service IN (or OUT) (TOS) */ + uint32_t src_as; /* Src peer/origin Autonomous System (SRC_AS) */ + uint32_t dst_as; /* Dst peer/origin Autonomous System (DST_AS) */ + uint8_t src_mask; /* Source route's mask bits (SRC_MASK) */ + uint8_t dst_mask; /* Destination route's mask bits (DST_MASK) */ +} __attribute__((__packed__)); + +/* Used in export9_add to determine max record size */ +struct netflow_v9_record_general +{ + union { + struct netflow_v9_record_ipv4_tcp v4_tcp; + struct netflow_v9_record_ipv6_tcp v6_tcp; + } rec; +}; + +#define BASE_MTU 1500 +#define MIN_MTU sizeof(struct netflow_v5_header) +#define MAX_MTU 16384 +#define NETFLOW_V9_MAX_SIZE _NETFLOW_V9_MAX_SIZE(BASE_MTU) +/* Decrease MSS by 16 since there can be some IPv[46] header options */ +#define _NETFLOW_V9_MAX_SIZE(x) (x) - sizeof(struct ip6_hdr) - sizeof(struct udphdr) - 16 + +/* #define NETFLOW_V9_MAX_FLOWSETS 2 */ + +#define NETFLOW_V9_MAX_RECORD_SIZE sizeof(struct netflow_v9_record_ipv6_tcp) +#define NETFLOW_V9_MAX_PACKETS_TEMPL 500 /* Send data templates every ... packets */ +#define NETFLOW_V9_MAX_TIME_TEMPL 600 /* Send data templates every ... seconds */ +#define NETFLOW_V9_MAX_TEMPLATES 16 /* Not a real value */ +#define _NETFLOW_V9_TEMPLATE_SIZE(x) (sizeof(x) / sizeof(struct netflow_v9_template)) * 4 +//#define _NETFLOW_V9_TEMPLATE_SIZE(x) ((x) + 1) * 4 + +/* Flow Templates */ +#define NETFLOW_V9_FLOW_V4_L4 1 /* IPv4 TCP/UDP packet */ +#define NETFLOW_V9_FLOW_V4_ICMP 2 /* IPv4 ICMP packet, currently unused */ +#define NETFLOW_V9_FLOW_V4_L3 3 /* IPv4 IP packet */ +#define NETFLOW_V9_FLOW_V6_L4 4 /* IPv6 TCP/UDP packet */ +#define NETFLOW_V9_FLOW_V6_ICMP 5 /* IPv6 ICMP packet, currently unused */ +#define NETFLOW_V9_FLOW_V6_L3 6 /* IPv6 IP packet */ + +#define NETFLOW_V9_FLOW_FAKE 65535 /* Not uset used in real flowsets! */ + +struct netflow_v9_export_dgram { + struct netflow_v9_header header; + char *data; /* MTU can change, record length is dynamic */ +}; + +struct netflow_v9_flowset_header { + uint16_t id; /* FlowSet id */ + uint16_t length; /* FlowSet length */ +} __attribute__((__packed__)); + +struct netflow_v9_packet_opt { + uint16_t length; /* current packet length */ + uint16_t count; /* current records count */ + uint16_t mtu; /* max MTU shapshot */ + uint16_t flow_type; /* current flowset */ + uint16_t flow_header; /* offset pointing to current flow header */ +}; +#endif 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); diff --git a/sys/netgraph/netflow/ng_netflow.h b/sys/netgraph/netflow/ng_netflow.h index a7f52f3..8119ecc 100644 --- a/sys/netgraph/netflow/ng_netflow.h +++ b/sys/netgraph/netflow/ng_netflow.h @@ -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. @@ -32,7 +33,7 @@ #define _NG_NETFLOW_H_ #define NG_NETFLOW_NODE_TYPE "netflow" -#define NGM_NETFLOW_COOKIE 1137078102 +#define NGM_NETFLOW_COOKIE 1299079728 #define NG_NETFLOW_MAXIFACES USHRT_MAX @@ -41,6 +42,10 @@ #define NG_NETFLOW_HOOK_DATA "iface" #define NG_NETFLOW_HOOK_OUT "out" #define NG_NETFLOW_HOOK_EXPORT "export" +#define NG_NETFLOW_HOOK_EXPORT9 "export9" + +/* This define effectively disable (v5) netflow export hook! */ +/* #define COUNTERS_64 */ /* Netgraph commands understood by netflow node */ enum { @@ -51,15 +56,27 @@ enum { NGM_NETFLOW_SETIFINDEX = 5, /* set interface index */ NGM_NETFLOW_SETTIMEOUTS = 6, /* set active/inactive flow timeouts */ NGM_NETFLOW_SETCONFIG = 7, /* set flow generation options */ + NGM_NETFLOW_SETTEMPLATE = 8, /* set v9 flow template periodic */ + NGM_NETFLOW_SETMTU = 9, /* set outgoing interface MTU */ }; /* This structure is returned by the NGM_NETFLOW_INFO message */ struct ng_netflow_info { - uint64_t nfinfo_bytes; /* accounted bytes */ - uint32_t nfinfo_packets; /* accounted packets */ + uint64_t nfinfo_bytes; /* accounted IPv4 bytes */ + uint32_t nfinfo_packets; /* accounted IPv4 packets */ + uint64_t nfinfo_bytes6; /* accounted IPv6 bytes */ + uint32_t nfinfo_packets6; /* accounted IPv6 packets */ + uint64_t nfinfo_sbytes; /* skipped IPv4 bytes */ + uint32_t nfinfo_spackets; /* skipped IPv4 packets */ + uint64_t nfinfo_sbytes6; /* skipped IPv6 bytes */ + uint32_t nfinfo_spackets6; /* skipped IPv6 packets */ uint32_t nfinfo_used; /* used cache records */ + uint32_t nfinfo_used6; /* used IPv6 cache records */ uint32_t nfinfo_alloc_failed; /* failed allocations */ uint32_t nfinfo_export_failed; /* failed exports */ + uint32_t nfinfo_export9_failed; /* failed exports */ + uint32_t nfinfo_realloc_mbuf; /* reallocated mbufs */ + uint32_t nfinfo_alloc_fibs; /* fibs allocated */ uint32_t nfinfo_act_exp; /* active expiries */ uint32_t nfinfo_inact_exp; /* inactive expiries */ uint32_t nfinfo_inact_t; /* flow inactive timeout */ @@ -105,8 +122,22 @@ struct ng_netflow_setconfig { u_int32_t conf; /* new config */ }; +/* This structure is passed to NGM_NETFLOW_SETTEMPLATE */ +struct ng_netflow_settemplate { + uint16_t time; /* max time between announce */ + uint16_t packets; /* max packets between announce */ +}; + +/* This structure is passed to NGM_NETFLOW_SETMTU */ +struct ng_netflow_setmtu { + uint16_t mtu; /* MTU for packet */ +}; + + /* This is unique data, which identifies flow */ struct flow_rec { + uint16_t flow_type; /* IPv4 L4/L3 flow, see NETFLOW_V9_FLOW* */ + uint16_t fib; struct in_addr r_src; struct in_addr r_dst; union { @@ -126,6 +157,35 @@ struct flow_rec { } misc; }; +/* This is unique data, which identifies flow */ +struct flow6_rec { + uint16_t flow_type; /* IPv4 L4/L3 Ipv6 L4/L3 flow, see NETFLOW_V9_FLOW* */ + uint16_t fib; + union { + struct in_addr r_src; + struct in6_addr r_src6; + } src; + union { + struct in_addr r_dst; + struct in6_addr r_dst6; + } dst; + union { + struct { + uint16_t s_port; /* source TCP/UDP port */ + uint16_t d_port; /* destination TCP/UDP port */ + } dir; + uint32_t both; + } ports; + union { + struct { + u_char prot; /* IP protocol */ + u_char tos; /* IP TOS */ + uint16_t i_ifx; /* input interface index */ + } i; + uint32_t all; + } misc; +}; + #define r_ip_p misc.i.prot #define r_tos misc.i.tos #define r_i_ifx misc.i.i_ifx @@ -136,6 +196,7 @@ struct flow_rec { /* A flow entry which accumulates statistics */ struct flow_entry_data { + uint16_t version; /* Protocol version */ struct flow_rec r; struct in_addr next_hop; uint16_t fle_o_ifx; /* output interface index */ @@ -149,6 +210,24 @@ struct flow_entry_data { u_char tcp_flags; /* cumulative OR */ }; +struct flow6_entry_data { + uint16_t version; /* Protocol version */ + struct flow6_rec r; + union { + struct in_addr next_hop; + struct in6_addr next_hop6; + } n; + uint16_t fle_o_ifx; /* output interface index */ +#define fle_i_ifx r.misc.i.i_ifx + uint8_t dst_mask; /* destination route mask bits */ + uint8_t src_mask; /* source route mask bits */ + u_long packets; + u_long bytes; + long first; /* uptime on first packet */ + long last; /* uptime on last packet */ + u_char tcp_flags; /* cumulative OR */ +}; + /* * How many flow records we will transfer at once * without overflowing socket receive buffer @@ -174,15 +253,29 @@ struct flow_entry { TAILQ_ENTRY(flow_entry) fle_hash; /* entries in hash slot */ }; +struct flow6_entry { + struct flow6_entry_data f; + TAILQ_ENTRY(flow6_entry) fle6_hash; /* entries in hash slot */ +}; /* Parsing declarations */ /* Parse the info structure */ #define NG_NETFLOW_INFO_TYPE { \ - { "Bytes", &ng_parse_uint64_type }, \ - { "Packets", &ng_parse_uint32_type }, \ - { "Records used", &ng_parse_uint32_type },\ + { "IPv4 bytes", &ng_parse_uint64_type }, \ + { "IPv4 packets", &ng_parse_uint32_type }, \ + { "IPv6 bytes", &ng_parse_uint64_type }, \ + { "IPv6 packets", &ng_parse_uint32_type }, \ + { "IPv4 skipped bytes", &ng_parse_uint64_type }, \ + { "IPv4 skipped packets", &ng_parse_uint32_type }, \ + { "IPv6 skipped bytes", &ng_parse_uint64_type }, \ + { "IPv6 skipped packets", &ng_parse_uint32_type }, \ + { "IPv4 records used", &ng_parse_uint32_type },\ + { "IPv6 records used", &ng_parse_uint32_type },\ { "Failed allocations", &ng_parse_uint32_type },\ - { "Failed exports", &ng_parse_uint32_type },\ + { "V5 failed exports", &ng_parse_uint32_type },\ + { "V9 failed exports", &ng_parse_uint32_type },\ + { "mbuf reallocations", &ng_parse_uint32_type },\ + { "fibs allocated", &ng_parse_uint32_type },\ { "Active expiries", &ng_parse_uint32_type },\ { "Inactive expiries", &ng_parse_uint32_type },\ { "Inactive timeout", &ng_parse_uint32_type },\ @@ -227,6 +320,19 @@ struct flow_entry { { NULL } \ } +/* Parse the settemplate structure */ +#define NG_NETFLOW_SETTEMPLATE_TYPE { \ + { "time", &ng_parse_uint16_type }, \ + { "packets", &ng_parse_uint16_type }, \ + { NULL } \ +} + +/* Parse the setmtu structure */ +#define NG_NETFLOW_SETMTU_TYPE { \ + { "mtu", &ng_parse_uint16_type }, \ + { NULL } \ +} + /* Private hook data */ struct ng_netflow_iface { hook_p hook; /* NULL when disconnected */ @@ -237,10 +343,35 @@ struct ng_netflow_iface { typedef struct ng_netflow_iface *iface_p; typedef struct ng_netflow_ifinfo *ifinfo_p; +struct netflow_export_item { + item_p item; + item_p item9; + struct netflow_v9_packet_opt *item9_opt; +}; + +/* Structure contatining fib-specific data */ +struct fib_export { + uint32_t fib; /* kernel fib id */ + struct netflow_export_item exp; /* Various data used for export */ + struct mtx export_mtx; /* exp.item mutex */ + struct mtx export9_mtx; /* exp.item9 mutex */ + uint32_t flow_seq; /* current V5 flow sequence */ + uint32_t flow9_seq; /* current V9 flow sequence */ + uint32_t domain_id; /* Observartion domain id */ + /* Netflow V9 counters */ + uint32_t templ_last_ts; /* unixtime of last template announce */ + uint32_t templ_last_pkt; /* packets count on last template announce */ + uint32_t sent_packets; /* packets sent by exporter; */ + struct netflow_v9_packet_opt *export9_opt; /* current packet specific options */ +}; + +typedef struct fib_export *fib_export_p; + /* Structure describing our flow engine */ struct netflow { node_p node; /* link to the node itself */ hook_p export; /* export data goes there */ + hook_p export9; /* Netflow V9 export data goes there */ struct ng_netflow_info info; struct callout exp_callout; /* expiry periodic job */ @@ -264,12 +395,29 @@ struct netflow { * and works with it. If the export is full it is sent, and * a new one is allocated. Before exiting thread re-attaches * its current item back to priv. If there is item already, - * current incomplete datagram is sent. + * current incomplete datagram is sent. * export_mtx is used for attaching/detaching. */ - item_p export_item; - struct mtx export_mtx; - uint32_t flow_seq; /* current flow sequence */ + + /* IPv6 support */ +#ifdef INET6 + uma_zone_t zone6; + struct flow6_hash_entry *hash6; +#endif + /* Multiple FIB support */ + fib_export_p fib_data[RT_NUMFIBS]; /* array of pointers to fib-specific data */ + + /* + * RFC 3954 clause 7.3 + * "Both options MUST be configurable by the user on the Exporter." + */ + uint16_t templ_time; /* time between sending templates */ + uint16_t templ_packets; /* packets between sending templates */ +#define NETFLOW_V9_MAX_FLOWSETS 2 + u_char flowsets_count; /* current flowsets used */ + u_char flowset_records[NETFLOW_V9_MAX_FLOWSETS - 1]; /* Count of records in each flowset */ + uint16_t mtu; /* export interface MTU */ + struct netflow_v9_flowset_header *v9_flowsets[NETFLOW_V9_MAX_FLOWSETS - 1]; /* Pointers to pre-compiled flowsets */ struct ng_netflow_iface ifaces[NG_NETFLOW_MAXIFACES]; }; @@ -282,18 +430,51 @@ struct flow_hash_entry { TAILQ_HEAD(fhead, flow_entry) head; }; +struct flow6_hash_entry { + struct mtx mtx; + TAILQ_HEAD(f6head, flow6_entry) head; +}; + #define ERROUT(x) { error = (x); goto done; } #define MTAG_NETFLOW 1221656444 #define MTAG_NETFLOW_CALLED 0 +#define m_pktlen(m) ((m)->m_pkthdr.len) +#define IP6VERSION 6 + +#define priv_to_fib(priv, fib) (priv)->fib_data[(fib)] + +/* + * Cisco uses milliseconds for uptime. Bad idea, since it overflows + * every 48+ days. But we will do same to keep compatibility. This macro + * does overflowable multiplication to 1000. + */ +#define MILLIUPTIME(t) (((t) << 9) + /* 512 */ \ + ((t) << 8) + /* 256 */ \ + ((t) << 7) + /* 128 */ \ + ((t) << 6) + /* 64 */ \ + ((t) << 5) + /* 32 */ \ + ((t) << 3)) /* 8 */ + /* Prototypes for netflow.c */ int ng_netflow_cache_init(priv_p); void ng_netflow_cache_flush(priv_p); +int ng_netflow_fib_init(priv_p priv, int fib); void ng_netflow_copyinfo(priv_p, struct ng_netflow_info *); timeout_t ng_netflow_expire; -int ng_netflow_flow_add(priv_p, struct ip *, unsigned int src_if_index); +int ng_netflow_flow_add(priv_p, fib_export_p, struct ip *, caddr_t, uint8_t, uint8_t, unsigned int); +int ng_netflow_flow6_add(priv_p, fib_export_p, struct ip6_hdr *, caddr_t , uint8_t, uint8_t, unsigned int); int ng_netflow_flow_show(priv_p, uint32_t last, struct ng_mesg *); +void ng_netflow_v9_cache_init(priv_p); +void ng_netflow_v9_cache_flush(priv_p); +item_p get_export9_dgram(priv_p, fib_export_p, struct netflow_v9_packet_opt **); +void return_export9_dgram(priv_p, fib_export_p, item_p, + struct netflow_v9_packet_opt *, int); +int export9_add(item_p, struct netflow_v9_packet_opt *, struct flow_entry *); +int export9_send(priv_p, fib_export_p, item_p, struct netflow_v9_packet_opt *, + int); + #endif /* _KERNEL */ #endif /* _NG_NETFLOW_H_ */ |