Add support for NetFlow version 9 into ng_netflow(4) node.
Submitted by: Alexander V. Chernikov <melifaro ipfw.ru>
This commit is contained in:
parent
8cfdf36d27
commit
5dcd9c1061
Notes:
svn2git
2020-12-20 02:59:44 +00:00
svn path=/head/; revision=219182
@ -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
|
||||
|
@ -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>
|
||||
|
@ -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);
|
||||
|
||||
/* Generate hash for a given flow record. */
|
||||
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.
|
||||
*
|
||||
* 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);
|
||||
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 (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);
|
||||
}
|
||||
|
||||
if (version == IPVERSION)
|
||||
uma_zfree_arg(priv->zone, fle, priv);
|
||||
return;
|
||||
}
|
||||
if (export_add(*item, fle) > 0) {
|
||||
export_send(priv, *item, flags);
|
||||
*item = NULL;
|
||||
}
|
||||
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,31 +500,81 @@ 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);
|
||||
}
|
||||
|
||||
@ -357,9 +584,16 @@ 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);
|
||||
|
||||
mtx_destroy(&priv->export_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);
|
||||
|
||||
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,
|
||||
|
@ -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 */
|
||||
|
483
sys/netgraph/netflow/netflow_v9.c
Normal file
483
sys/netgraph/netflow/netflow_v9.c
Normal file
@ -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);
|
||||
}
|
148
sys/netgraph/netflow/netflow_v9.h
Normal file
148
sys/netgraph/netflow/netflow_v9.h
Normal file
@ -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
|
@ -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,8 +322,22 @@ 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);
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
switch (iface->info.ifinfo_dlt) {
|
||||
case DLT_EN10MB:
|
||||
{
|
||||
struct ether_header *eh;
|
||||
|
||||
eh = mtod(m, struct ether_header *);
|
||||
switch (ntohs(eh->ether_type)) {
|
||||
case ETHERTYPE_IP:
|
||||
ip = (struct ip *)(eh + 1);
|
||||
case IPPROTO_SCTP:
|
||||
M_CHECK(sizeof(struct sctphdr));
|
||||
break;
|
||||
case ETHERTYPE_VLAN:
|
||||
{
|
||||
struct ether_vlan_header *evh;
|
||||
|
||||
evh = mtod(m, struct ether_vlan_header *);
|
||||
ip = (struct ip *)(evh + 1);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
panic("ng_netflow entered deadcode");
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DLT_RAW:
|
||||
ip = mtod(m, struct ip *);
|
||||
break;
|
||||
default:
|
||||
panic("ng_netflow entered deadcode");
|
||||
}
|
||||
} 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;
|
||||
|
||||
/* 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;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
off += hdr_off;
|
||||
cur = upper_proto;
|
||||
}
|
||||
#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);
|
||||
|
@ -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_ */
|
||||
|
Loading…
Reference in New Issue
Block a user