diff options
author | glebius <glebius@FreeBSD.org> | 2004-09-16 20:24:23 +0000 |
---|---|---|
committer | glebius <glebius@FreeBSD.org> | 2004-09-16 20:24:23 +0000 |
commit | 8546c37afed4c64f225d8ec619d1ed59cdb2caec (patch) | |
tree | 2ec8b73fd58b00a1b3df300b870676e6cecb8622 /sys/netgraph | |
parent | 4b598bb10d0d33b9af9e0476855de5ae7b1c54d5 (diff) | |
download | FreeBSD-src-8546c37afed4c64f225d8ec619d1ed59cdb2caec.zip FreeBSD-src-8546c37afed4c64f225d8ec619d1ed59cdb2caec.tar.gz |
A netgraph node implementing Netflow version 5.
Supported by: Bestcom ISP, Rinet ISP
Approved by: julian (mentor)
Diffstat (limited to 'sys/netgraph')
-rw-r--r-- | sys/netgraph/netflow/netflow.c | 736 | ||||
-rw-r--r-- | sys/netgraph/netflow/netflow.h | 125 | ||||
-rw-r--r-- | sys/netgraph/netflow/ng_netflow.c | 527 | ||||
-rw-r--r-- | sys/netgraph/netflow/ng_netflow.h | 282 |
4 files changed, 1670 insertions, 0 deletions
diff --git a/sys/netgraph/netflow/netflow.c b/sys/netgraph/netflow/netflow.c new file mode 100644 index 0000000..1075838 --- /dev/null +++ b/sys/netgraph/netflow/netflow.c @@ -0,0 +1,736 @@ +/*- + * Copyright (c) 2004 Gleb Smirnoff <glebius@cell.sick.ru> + * Copyright (c) 2001-2003 Roman V. Palagin <romanp@unshadow.net> + * 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. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Gleb Smirnoff and + * contributors. + * 4. Neither the name of the author nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * 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. + * + * $SourceForge: netflow.c,v 1.41 2004/09/05 11:41:10 glebius Exp $ + */ + +static const char rcs_id[] = + "@(#) $FreeBSD$"; + +#include <sys/param.h> +#include <sys/kernel.h> +#include <sys/limits.h> +#include <sys/mbuf.h> +#include <sys/systm.h> +#include <sys/socket.h> + +#include <net/if.h> +#include <net/if_var.h> +#include <net/if_dl.h> +#include <net/route.h> +#include <netinet/in.h> +#include <netinet/in_systm.h> +#include <netinet/ip.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> + +#define NBUCKETS (4096) /* must be power of 2 */ + +/* This hash is for TCP or UDP packets */ +#define FULL_HASH(addr1,addr2,port1,port2)\ + (((addr1 >> 16) ^ \ + (addr2 & 0x00FF) ^ \ + ((port1 ^ port2) << 8) )& \ + (NBUCKETS - 1)) + +/* This hash for all other IP packets */ +#define ADDR_HASH(addr1,addr2)\ + (((addr1 >> 16) ^ \ + (addr2 & 0x00FF) )& \ + (NBUCKETS - 1)) + +/* Macros to shorten logical constructions */ +/* XXX: priv must exist in namespace */ +#define INACTIVE(fle) (time_uptime - fle->f.last > priv->info.nfinfo_inact_t) +#define AGED(fle) (time_uptime - fle->f.first > priv->info.nfinfo_act_t) +#define ISFREE(fle) (fle->f.packets == 0) + +/* + * 4 is a magical number: statistically number of 4-packet flows is + * bigger than 5,6,7...-packet flows by an order of magnitude. Most UDP/ICMP + * scans are 1 packet (~ 90% of flow cache). TCP scans are 2-packet in case + * of reachable host and 4-packet otherwise. + */ +#define SMALL(fle) (fle->f.packets <= 4) + +MALLOC_DECLARE(M_NETFLOW); +MALLOC_DEFINE(M_NETFLOW, "NetFlow", "flow cache"); + +static int export_add(priv_p , struct flow_entry *); +static int export_send(priv_p ); + +/* Generate hash for a given flow record */ +static __inline uint32_t +ip_hash(struct flow_rec *r) +{ + switch (r->r_ip_p) { + case IPPROTO_TCP: + case IPPROTO_UDP: + return FULL_HASH(r->r_src.s_addr, r->r_dst.s_addr, + r->r_sport, r->r_dport); + default: + return ADDR_HASH(r->r_src.s_addr, r->r_dst.s_addr); + } +} + +/* Lookup for record in given slot */ +static __inline struct flow_entry * +hash_lookup(struct flow_hash_entry *h, int slot, struct flow_rec *r) +{ + struct flow_entry *fle; + + LIST_FOREACH(fle, &(h[slot].head), fle_hash) + if (bcmp(r, &fle->f.r, sizeof(struct flow_rec)) == 0) + return (fle); + + return (NULL); +} + +/* Get a flow entry from free list */ +static __inline struct flow_entry * +alloc_flow(priv_p priv, int *flows) +{ + register struct flow_entry *fle; + + mtx_lock(&priv->free_mtx); + + if (SLIST_EMPTY(&priv->free_list)) { + mtx_unlock(&priv->free_mtx); + return(NULL); + } + + fle = SLIST_FIRST(&priv->free_list); + SLIST_REMOVE_HEAD(&priv->free_list, fle_free); + + priv->info.nfinfo_used++; + priv->info.nfinfo_free--; + + if (flows != NULL) + *flows = priv->info.nfinfo_used; + + mtx_unlock(&priv->free_mtx); + + return (fle); +} + +/* Insert flow entry into a free list. */ +static __inline int +free_flow(priv_p priv, struct flow_entry *fle) +{ + int flows; + + mtx_lock(&priv->free_mtx); + fle->f.packets = 0; + SLIST_INSERT_HEAD(&priv->free_list, fle, fle_free); + flows = priv->info.nfinfo_used--; + priv->info.nfinfo_free++; + mtx_unlock(&priv->free_mtx); + + return flows; +} + +#define NGNF_GETUSED(priv, rval) do { \ + mtx_lock(&priv->free_mtx); \ + rval = priv->info.nfinfo_used; \ + mtx_unlock(&priv->free_mtx); \ + } while (0) + +/* Insert flow entry into expire list. */ +/* XXX: Flow must be detached from work queue, but not from cache */ +static __inline void +expire_flow(priv_p priv, struct flow_entry *fle) +{ + mtx_assert(&priv->work_mtx, MA_OWNED); + LIST_REMOVE(fle, fle_hash); + + mtx_lock(&priv->expire_mtx); + SLIST_INSERT_HEAD(&priv->expire_list, fle, fle_free); + mtx_unlock(&priv->expire_mtx); +} + +/* Get a snapshot of node statistics */ +void +ng_netflow_copyinfo(priv_p priv, struct ng_netflow_info *i) +{ + mtx_lock(&priv->free_mtx); + memcpy((void *)i, (void *)&priv->info, sizeof(priv->info)); + mtx_unlock(&priv->free_mtx); +} + +/* Calculate number of bits in netmask */ +#define g21 0x55555555ul /* = 0101_0101_0101_0101_0101_0101_0101_0101 */ +#define g22 0x33333333ul /* = 0011_0011_0011_0011_0011_0011_0011_0011 */ +#define g23 0x0f0f0f0ful /* = 0000_1111_0000_1111_0000_1111_0000_1111 */ +static __inline u_char +bit_count(uint32_t v) +{ + v = (v & g21) + ((v >> 1) & g21); + v = (v & g22) + ((v >> 2) & g22); + v = (v + (v >> 4)) & g23; + return (v + (v >> 8) + (v >> 16) + (v >> 24)) & 0x3f; +} + +/* + * Insert a record into defined slot. + * + * First we get for us a free flow entry, then fill in all + * possible fields in it. Then obtain lock on flow cache + * and insert flow entry. + */ +static __inline int +hash_insert(priv_p priv, int slot, struct flow_rec *r, int plen) +{ + struct flow_hash_entry *h = priv->hash; + struct flow_entry *fle; + struct route ro; + struct sockaddr_in *sin; + + fle = alloc_flow(priv, NULL); + if (fle == NULL) + return (ENOMEM); + + /* + * Now fle is totally ours. It is detached from all lists, + * we can safely edit it. + */ + + bcopy(r, &fle->f.r, sizeof(struct flow_rec)); + fle->f.bytes = plen; + fle->f.packets = 1; + + priv->info.nfinfo_bytes += plen; + + fle->f.first = fle->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((caddr_t)&ro, sizeof(ro)); + sin = (struct sockaddr_in *)&ro.ro_dst; + sin->sin_len = sizeof(*sin); + sin->sin_family = AF_INET; + sin->sin_addr = fle->f.r.r_dst; + rtalloc_ign(&ro, RTF_CLONING); + if (ro.ro_rt != NULL) { + struct rtentry *rt = ro.ro_rt; + + fle->f.fle_o_ifx = rt->rt_ifp->if_index; + + if (rt->rt_flags & RTF_GATEWAY && + rt->rt_gateway->sa_family == AF_INET) + fle->f.next_hop = + ((struct sockaddr_in *)(rt->rt_gateway))->sin_addr; + + if (rt_mask(rt)) + fle->f.dst_mask = + bit_count(((struct sockaddr_in *)rt_mask(rt))->sin_addr.s_addr); + else if (rt->rt_flags & RTF_HOST) + /* Give up. We can't determine mask :( */ + fle->f.dst_mask = 32; + + RTFREE(ro.ro_rt); + } + + /* Do route lookup on source address, to fill in src_mask. */ + + bzero((caddr_t)&ro, sizeof(ro)); + sin = (struct sockaddr_in *)&ro.ro_dst; + sin->sin_len = sizeof(*sin); + sin->sin_family = AF_INET; + sin->sin_addr = fle->f.r.r_src; + rtalloc_ign(&ro, RTF_CLONING); + if (ro.ro_rt != NULL) { + struct rtentry *rt = ro.ro_rt; + + if (rt_mask(rt)) + fle->f.src_mask = + bit_count(((struct sockaddr_in *)rt_mask(rt))->sin_addr.s_addr); + else if (rt->rt_flags & RTF_HOST) + /* Give up. We can't determine mask :( */ + fle->f.src_mask = 32; + + RTFREE(ro.ro_rt); + } + + /* Push new flow entry into flow cache */ + mtx_lock(&priv->work_mtx); + LIST_INSERT_HEAD(&(h[slot].head), fle, fle_hash); + TAILQ_INSERT_TAIL(&priv->work_queue, fle, fle_work); + mtx_unlock(&priv->work_mtx); + + return (0); +} + +static __inline int +make_flow_rec(struct mbuf **m, int *plen, struct flow_rec *r, uint8_t *tcp_flags, + u_int16_t i_ifx) +{ + register struct ip *ip; + int hlen; + int error = 0; + + ip = mtod(*m, struct ip*); + + /* check version */ + if (ip->ip_v != IPVERSION) + return (EINVAL); + + /* verify min header length */ + hlen = ip->ip_hl << 2; + + if (hlen < sizeof(struct ip)) + return (EINVAL); + + r->r_src = ip->ip_src; + r->r_dst = ip->ip_dst; + + /* save packet length */ + *plen = ntohs(ip->ip_len); + + r->r_ip_p = ip->ip_p; + r->r_tos = ip->ip_tos; + + if ((*m)->m_pkthdr.rcvif) + r->r_i_ifx = (*m)->m_pkthdr.rcvif->if_index; + else + r->r_i_ifx = i_ifx; + + /* + * XXX NOTE: only first fragment of fragmented TCP, UDP and + * ICMP packet will be recorded with proper s_port and d_port. + * Following fragments will be recorded simply as IP packet with + * ip_proto = ip->ip_p and s_port, d_port set to zero. + * I know, it looks like bug. But I don't want to re-implement + * ip packet assebmling here. Anyway, (in)famous trafd works this way - + * and nobody complains yet :) + */ + if(ip->ip_off & htons(IP_OFFMASK)) + return (0); + + /* skip IP header */ + m_adj(*m, hlen); + + switch(r->r_ip_p) { + case IPPROTO_TCP: + { + register struct tcphdr *tcp; + + /* verify that packet is not truncated */ + if (CHECK_MLEN(*m, sizeof(struct tcphdr))) + ERROUT(EINVAL); + + if (CHECK_PULLUP(*m, sizeof(struct tcphdr))) + ERROUT(ENOBUFS); + + tcp = mtod(*m, struct tcphdr*); + r->r_sport = tcp->th_sport; + r->r_dport = tcp->th_dport; + *tcp_flags = tcp->th_flags; + break; + } + case IPPROTO_UDP: + /* verify that packet is not truncated */ + if (CHECK_MLEN(*m, sizeof(struct udphdr))) + ERROUT(EINVAL); + + if (CHECK_PULLUP(*m, sizeof(struct udphdr))) + ERROUT(ENOBUFS); + + r->r_ports = *(mtod(*m, uint32_t *)); + break; + } + +done: + return (error); +} + +/* + * Non-static functions called from ng_netflow.c + */ + +/* Allocate memory and set up flow cache */ +int +ng_netflow_cache_init(priv_p priv) +{ + struct flow_entry *fle; + int i; + + /* allocate cache */ + MALLOC(priv->cache, struct flow_entry *, + CACHESIZE * sizeof(struct flow_entry), + M_NETFLOW, M_WAITOK | M_ZERO); + + if (priv->cache == NULL) + return (ENOMEM); + + /* allocate hash */ + MALLOC(priv->hash, struct flow_hash_entry *, + NBUCKETS * sizeof(struct flow_hash_entry), + M_NETFLOW, M_WAITOK | M_ZERO); + + if (priv->hash == NULL) + return (ENOMEM); + + TAILQ_INIT(&priv->work_queue); + SLIST_INIT(&priv->free_list); + SLIST_INIT(&priv->expire_list); + + mtx_init(&priv->work_mtx, "ng_netflow cache mutex", NULL, MTX_DEF); + mtx_init(&priv->free_mtx, "ng_netflow free mutex", NULL, MTX_DEF); + mtx_init(&priv->expire_mtx, "ng_netflow expire mutex", NULL, MTX_DEF); + + /* build free list */ + for (i = 0, fle = priv->cache; i < CACHESIZE; i++, fle++) + SLIST_INSERT_HEAD(&priv->free_list, fle, fle_free); + + priv->info.nfinfo_free = CACHESIZE; + + return (0); +} + +/* Free all flow cache memory. Called from node close method. */ +void +ng_netflow_cache_flush(priv_p priv) +{ + register struct flow_entry *fle; + int i; + + /* + * We are going to free probably billable data. + * Expire everything before freeing it. + * No locking is required since callout is already drained. + */ + + for (i = 0, fle = priv->cache; i < CACHESIZE; i++, fle++) + if (!ISFREE(fle)) + /* ignore errors now */ + (void )export_add(priv, fle); + + mtx_destroy(&priv->work_mtx); + mtx_destroy(&priv->free_mtx); + mtx_destroy(&priv->expire_mtx); + + /* free hash memory */ + if (priv->hash) + FREE(priv->hash, M_NETFLOW); + + /* free flow cache */ + if (priv->cache) + FREE(priv->cache, M_NETFLOW); + +} + +/* Insert packet from &m into flow cache. */ +int +ng_netflow_flow_add(priv_p priv, struct mbuf **m, iface_p iface) +{ + struct flow_hash_entry *h = priv->hash; + register struct flow_entry *fle; + struct flow_rec r; + int plen; + int error = 1; + uint32_t slot; + uint8_t tcp_flags = 0; + + priv->info.nfinfo_packets ++; + + /* Try to fill *rec */ + bzero(&r, sizeof(r)); + if ((error = make_flow_rec(m, &plen, &r, &tcp_flags, iface->info.ifinfo_index))) + return (error); + + slot = ip_hash(&r); + + mtx_lock(&priv->work_mtx); + fle = hash_lookup(h, slot, &r); /* New flow entry or existent? */ + + if (fle) { /* an existent entry */ + + TAILQ_REMOVE(&priv->work_queue, fle, fle_work); + + fle->f.bytes += plen; + fle->f.packets ++; + fle->f.tcp_flags |= tcp_flags; + fle->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(fle) || + (fle->f.bytes >= (UINT_MAX - IF_MAXMTU)) ) + expire_flow(priv, fle); + else + TAILQ_INSERT_TAIL(&priv->work_queue, fle, fle_work); + + mtx_unlock(&priv->work_mtx); + priv->info.nfinfo_bytes += plen; + + } else { /* a new flow entry */ + + mtx_unlock(&priv->work_mtx); + return hash_insert(priv, slot, &r, plen); + + } + + mtx_assert(&priv->work_mtx, MA_NOTOWNED); + mtx_assert(&priv->expire_mtx, MA_NOTOWNED); + mtx_assert(&priv->free_mtx, MA_NOTOWNED); + return (0); +} + +/* + * Return records from cache. netgraph(4) guarantees us that we + * are locked against ng_netflow_rcvdata(). However we can + * work with ng_netflow_expire() in parrallel. XXX: Is it dangerous? + * + * TODO: matching particular IP should be done in kernel, here. + */ +int +ng_netflow_flow_show(priv_p priv, uint32_t last, struct ng_mesg *resp) +{ + struct flow_entry *fle; + struct ngnf_flows *data; + + data = (struct ngnf_flows *)resp->data; + data->last = 0; + data->nentries = 0; + + /* Check if this is a first run */ + if (last == 0) + fle = priv->cache; + else { + if (last > CACHESIZE-1) + return (EINVAL); + fle = priv->cache + last; + } + + /* + * We will transfer not more than NREC_AT_ONCE. More data + * will come in next message. + * We send current stop point to userland, and userland should return + * it back to us. + */ + for (; last < CACHESIZE; fle++, last++) { + if (ISFREE(fle)) + continue; + bcopy(&fle->f, &(data->entries[data->nentries]), sizeof(fle->f)); + data->nentries ++; + if (data->nentries == NREC_AT_ONCE) { + if (++last < CACHESIZE) + data->last = (++fle - priv->cache); + return (0); + } + } + + return (0); +} + +/* We have full datagram in privdata. Send it to export hook. */ +static int +export_send(priv_p priv) +{ + struct netflow_v5_header *header = &priv->dgram.header; + struct timespec ts; + struct mbuf *m; + int error = 0; + int mlen; + + header->sys_uptime = htonl(time_uptime); + + getnanotime(&ts); + header->unix_secs = htonl(ts.tv_sec); + header->unix_nsecs = htonl(ts.tv_nsec); + + /* Flow sequence contains number of first record */ + header->flow_seq = htonl(priv->flow_seq - header->count); + + mlen = sizeof(struct netflow_v5_header) + + sizeof(struct netflow_v5_record) * header->count; + + header->count = htons(header->count); + if ((m = m_devget((caddr_t)header, mlen, 0, NULL, NULL)) == NULL) { + printf("ng_netflow: m_devget() failed, losing export dgram\n"); + header->count = 0; + return(ENOBUFS); + } + + header->count = 0; + + /* Giant is required in sosend() at this moment. */ + NET_LOCK_GIANT(); + NG_SEND_DATA_ONLY(error, priv->export, m); + NET_UNLOCK_GIANT(); + + if (error) + NG_FREE_M(m); + + return (error); +} + + +/* Create export datagram. */ +static int +export_add(priv_p priv, struct flow_entry *fle) +{ + struct netflow_v5_header *header = &priv->dgram.header; + struct netflow_v5_record *rec; + + if (header->count == 0 ) { /* first record */ + rec = &priv->dgram.r[0]; + header->count = 1; + } else { /* continue filling datagram */ + rec = &priv->dgram.r[header->count]; + header->count ++; + } + + /* Fill in export record */ + rec->src_addr = fle->f.r.r_src.s_addr; + rec->dst_addr = fle->f.r.r_dst.s_addr; + rec->next_hop = fle->f.next_hop.s_addr; + rec->i_ifx = htons(fle->f.fle_i_ifx); + rec->o_ifx = htons(fle->f.fle_o_ifx); + rec->packets = htonl(fle->f.packets); + rec->octets = htonl(fle->f.bytes); + rec->first = htonl(fle->f.first); + rec->last = htonl(fle->f.last); + rec->s_port = fle->f.r.r_sport; + rec->d_port = fle->f.r.r_dport; + rec->flags = fle->f.tcp_flags; + rec->prot = fle->f.r.r_ip_p; + rec->tos = fle->f.r.r_tos; + rec->dst_mask = fle->f.dst_mask; + rec->src_mask = fle->f.src_mask; + + priv->flow_seq++; + + if (header->count == NETFLOW_V5_MAX_RECORDS) /* end of datagram */ + return export_send(priv); + + return (0); +} + +/* Periodic flow expiry run. */ +void +ng_netflow_expire(void *arg) +{ + register struct flow_entry *fle, *fle1; + priv_p priv = (priv_p )arg; + uint32_t used; + int error = 0; + + /* First pack actively expired entries */ + mtx_lock(&priv->expire_mtx); + while (!SLIST_EMPTY(&(priv->expire_list))) { + fle = SLIST_FIRST(&(priv->expire_list)); + SLIST_REMOVE_HEAD(&(priv->expire_list), fle_free); + mtx_unlock(&priv->expire_mtx); + + /* + * While we have dropped the lock, expire_flow() may + * insert another flow into top of the list. + * This is not harmful for us, since we have already + * detached our own. + */ + + if ((error = export_add(priv, fle)) != 0) + printf("ng_netflow: export_add() failed: %u\n", error); + (void )free_flow(priv, fle); + + mtx_lock(&priv->expire_mtx); + } + mtx_unlock(&priv->expire_mtx); + + NGNF_GETUSED(priv, used); + mtx_lock(&priv->work_mtx); + TAILQ_FOREACH_SAFE(fle, &(priv->work_queue), fle_work, fle1) { + /* + * When cache size has not reached CACHELOWAT yet, we keep both + * inactive and active flows in cache. Doing this, we reduce number + * of exports, since many inactive flows may wake up and continue + * their life. However, we make an exclusion for scans. It is very + * rare situation that inactive 1-packet flow will wake up. + * When cache has reached CACHELOWAT, we expire all inactive flows, + * until cache gets of sane size. + * + * When this record's refcount is > 0, we skip it. (XXX) + */ + if (used <= CACHELOWAT && !INACTIVE(fle)) + goto finish; + + if (INACTIVE(fle) && (SMALL(fle) || (used > CACHELOWAT))) { + + /* Detach flow entry from cache */ + LIST_REMOVE(fle, fle_hash); + TAILQ_REMOVE(&priv->work_queue, fle, fle_work); + + /* + * While we are sending to collector, unlock cache. + * XXX: it can happen, however with a small probability, + * that item, we are holding now, can be moved to the top + * of flow cache by node thread. In this case our expire + * thread stops checking. Since this is not fatal we will + * just ignore it now. + */ + mtx_unlock(&priv->work_mtx); + + if ((error = export_add(priv, fle)) != 0) + printf("ng_netflow: export_add() failed: %u\n", + error); + + used = free_flow(priv, fle); + + mtx_lock(&priv->work_mtx); + } + } + +finish: + mtx_unlock(&priv->work_mtx); + + mtx_assert(&priv->expire_mtx, MA_NOTOWNED); + mtx_assert(&priv->free_mtx, MA_NOTOWNED); + + /* schedule next expire */ + callout_reset(&priv->exp_callout, (1*hz), &ng_netflow_expire, + (void *)priv); + +} diff --git a/sys/netgraph/netflow/netflow.h b/sys/netgraph/netflow/netflow.h new file mode 100644 index 0000000..a530f92 --- /dev/null +++ b/sys/netgraph/netflow/netflow.h @@ -0,0 +1,125 @@ +/*- + * Copyright (c) 2004 Gleb Smirnoff <glebius@cell.sick.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. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Gleb Smirnoff and + * contributors. + * 4. Neither the name of the author nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * 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. + * + * $SourceForge: netflow.h,v 1.8 2004/09/16 17:05:11 glebius Exp $ + * $FreeBSD$ + */ + +/* netflow timeouts in seconds */ + +#define ACTIVE_TIMEOUT (30*60) /* maximum flow lifetime is 30 min */ +#define INACTIVE_TIMEOUT 15 + +/* More info can be found in these two Cisco documents: + * http://www.cisco.com/warp/public/cc/pd/iosw/ioft/neflct/tech/napps_wp.htm + * http://www.cisco.com/en/US/products/sw/netmgtsw/ps1964/prod_installation_guide09186a00800fea56.html#wp1006186 + * However, they say quite different things. + */ + +#define NETFLOW_V1 1 +#define NETFLOW_V5 5 + +struct netflow_v1_header +{ + uint16_t version; /* NetFlow version */ + uint16_t count; /* Number of records in flow */ + uint32_t sys_uptime; /* System uptime */ + uint32_t unix_secs; /* Current seconds since 0000 UTC 1970 */ + uint32_t unix_nsecs; /* Remaining nanoseconds since 0000 UTC 1970 */ +} __attribute__((__packed__)); + +struct netflow_v5_header +{ + uint16_t version; /* NetFlow version */ + uint16_t count; /* Number of records in flow */ + uint32_t sys_uptime; /* System uptime */ + uint32_t unix_secs; /* Current seconds since 0000 UTC 1970 */ + uint32_t unix_nsecs; /* Remaining nanoseconds since 0000 UTC 1970 */ + uint32_t flow_seq; /* Sequence number of the first record */ + uint8_t engine_type; /* Type of flow switching engine (RP,VIP,etc.) */ + uint8_t engine_id; /* Slot number of the flow switching engine */ + uint16_t pad; /* Pad to word boundary */ +} __attribute__((__packed__)); + +struct netflow_v1_record +{ + uint32_t src_addr; /* Source IP address */ + uint32_t dst_addr; /* Destination IP address */ + uint32_t next_hop; /* Next hop IP address */ + uint16_t in_ifx; /* Source interface index */ + uint16_t out_ifx; /* Destination interface index */ + uint32_t packets; /* Number of packets in a flow */ + uint32_t octets; /* Number of octets in a flow */ + uint32_t first; /* System uptime at start of a flow */ + uint32_t last; /* System uptime at end of a flow */ + uint16_t s_port; /* Source port */ + uint16_t d_port; /* Destination port */ + uint16_t pad1; /* Pad to word boundary */ + uint8_t prot; /* IP protocol */ + uint8_t tos; /* IP type of service */ + uint8_t flags; /* Cumulative OR of tcp flags */ + uint8_t pad2; /* pad to word boundary */ + uint16_t pad3; /* Pad to word boundary */ + uint8_t reserved[5]; /* Reserved for future use */ +} __attribute__((__packed__)); + +struct netflow_v5_record +{ + uint32_t src_addr; /* Source IP address */ + uint32_t dst_addr; /* Destination IP address */ + uint32_t next_hop; /* Next hop IP address */ + uint16_t i_ifx; /* Source interface index */ + uint16_t o_ifx; /* Destination interface index */ + uint32_t packets; /* Number of packets in a flow */ + uint32_t octets; /* Number of octets in a flow */ + uint32_t first; /* System uptime at start of a flow */ + uint32_t last; /* System uptime at end of a flow */ + uint16_t s_port; /* Source port */ + uint16_t d_port; /* Destination port */ + uint8_t pad1; /* pad to word boundary */ + uint8_t flags; /* Cumulative OR of tcp flags */ + uint8_t prot; /* IP protocol */ + uint8_t tos; /* IP type of service */ + uint16_t src_as; /* Src peer/origin Autonomous System */ + uint16_t dst_as; /* Dst peer/origin Autonomous System */ + uint8_t src_mask; /* Source route's mask bits */ + uint8_t dst_mask; /* Destination route's mask bits */ + uint16_t pad2; /* Pad to word boundary */ +} __attribute__((__packed__)); + +#define NETFLOW_V1_MAX_RECORDS 24 +#define NETFLOW_V5_MAX_RECORDS 30 + +#define NETFLOW_V1_MAX_SIZE (sizeof(netflow_v1_header)+ \ + sizeof(netflow_v1_record)*NETFLOW_V1_MAX_RECORDS) +#define NETFLOW_V5_MAX_SIZE (sizeof(netflow_v5_header)+ \ + sizeof(netflow_v5_record)*NETFLOW_V5_MAX_RECORDS) diff --git a/sys/netgraph/netflow/ng_netflow.c b/sys/netgraph/netflow/ng_netflow.c new file mode 100644 index 0000000..4cfbe08 --- /dev/null +++ b/sys/netgraph/netflow/ng_netflow.c @@ -0,0 +1,527 @@ +/*- + * Copyright (c) 2004 Gleb Smirnoff <glebius@cell.sick.ru> + * Copyright (c) 2001-2003 Roman V. Palagin <romanp@unshadow.net> + * 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. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Gleb Smirnoff and + * contributors. + * 4. Neither the name of the author nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * 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. + * + * $SourceForge: ng_netflow.c,v 1.30 2004/09/05 11:37:43 glebius Exp $ + */ + +static const char rcs_id[] = + "@(#) $FreeBSD$"; + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/mbuf.h> +#include <sys/socket.h> +#include <sys/ctype.h> + +#include <net/if.h> +#include <net/ethernet.h> +#include <net/if_arp.h> +#include <net/if_var.h> +#include <net/bpf.h> +#include <netinet/in.h> +#include <netinet/in_systm.h> +#include <netinet/ip.h> + +#include <netgraph/ng_message.h> +#include <netgraph/ng_parse.h> +#include <netgraph/netgraph.h> +#include <netgraph/netflow/netflow.h> +#include <netgraph/netflow/ng_netflow.h> + +/* Netgraph methods */ +static ng_constructor_t ng_netflow_constructor; +static ng_rcvmsg_t ng_netflow_rcvmsg; +static ng_close_t ng_netflow_close; +static ng_shutdown_t ng_netflow_rmnode; +static ng_newhook_t ng_netflow_newhook; +static ng_rcvdata_t ng_netflow_rcvdata; +static ng_disconnect_t ng_netflow_disconnect; + +/* Parse type for struct ng_netflow_info */ +static const struct ng_parse_struct_field ng_netflow_info_type_fields[] + = NG_NETFLOW_INFO_TYPE; +static const struct ng_parse_type ng_netflow_info_type = { + &ng_parse_struct_type, + &ng_netflow_info_type_fields +}; + +/* Parse type for struct ng_netflow_ifinfo */ +static const struct ng_parse_struct_field ng_netflow_ifinfo_type_fields[] + = NG_NETFLOW_IFINFO_TYPE; +static const struct ng_parse_type ng_netflow_ifinfo_type = { + &ng_parse_struct_type, + &ng_netflow_ifinfo_type_fields +}; + +/* Parse type for struct ng_netflow_setdlt */ +static const struct ng_parse_struct_field ng_netflow_setdlt_type_fields[] + = NG_NETFLOW_SETDLT_TYPE; +static const struct ng_parse_type ng_netflow_setdlt_type = { + &ng_parse_struct_type, + &ng_netflow_setdlt_type_fields +}; + +/* Parse type for ng_netflow_setifindex */ +static const struct ng_parse_struct_field ng_netflow_setifindex_type_fields[] + = NG_NETFLOW_SETIFINDEX_TYPE; +static const struct ng_parse_type ng_netflow_setifindex_type = { + &ng_parse_struct_type, + &ng_netflow_setifindex_type_fields +}; + +/* Parse type for ng_netflow_settimeouts */ +static const struct ng_parse_struct_field ng_netflow_settimeouts_type_fields[] + = NG_NETFLOW_SETTIMEOUTS_TYPE; +static const struct ng_parse_type ng_netflow_settimeouts_type = { + &ng_parse_struct_type, + &ng_netflow_settimeouts_type_fields +}; + +/* List of commands and how to convert arguments to/from ASCII */ +static const struct ng_cmdlist ng_netflow_cmds[] = { + { + NGM_NETFLOW_COOKIE, + NGM_NETFLOW_INFO, + "info", + NULL, + &ng_netflow_info_type + }, + { + NGM_NETFLOW_COOKIE, + NGM_NETFLOW_IFINFO, + "ifinfo", + &ng_parse_uint8_type, + &ng_netflow_ifinfo_type + }, + { + NGM_NETFLOW_COOKIE, + NGM_NETFLOW_SETDLT, + "setdlt", + &ng_netflow_setdlt_type, + NULL + }, + { + NGM_NETFLOW_COOKIE, + NGM_NETFLOW_SETIFINDEX, + "setifindex", + &ng_netflow_setifindex_type, + NULL + }, + { + NGM_NETFLOW_COOKIE, + NGM_NETFLOW_SETTIMEOUTS, + "settimeouts", + &ng_netflow_settimeouts_type, + NULL + }, + { 0 } +}; + + +/* Netgraph node type descriptor */ +static struct ng_type ng_netflow_typestruct = { + .version = NG_ABI_VERSION, + .name = NG_NETFLOW_NODE_TYPE, + .constructor = ng_netflow_constructor, + .rcvmsg = ng_netflow_rcvmsg, + .close = ng_netflow_close, + .shutdown = ng_netflow_rmnode, + .newhook = ng_netflow_newhook, + .rcvdata = ng_netflow_rcvdata, + .disconnect = ng_netflow_disconnect, + .cmdlist = ng_netflow_cmds, +}; +NETGRAPH_INIT(netflow, &ng_netflow_typestruct); + +/* Called at node creation */ +static int +ng_netflow_constructor (node_p node) +{ + priv_p priv; + int error = 0; + + /* Initialize private data */ + MALLOC(priv, priv_p, sizeof(*priv), M_NETGRAPH, M_NOWAIT); + if (priv == NULL) + return (ENOMEM); + bzero(priv, sizeof(*priv)); + + /* Make node and its data point at each other */ + NG_NODE_SET_PRIVATE(node, priv); + priv->node = node; + + /* Initialize timeouts to default values */ + priv->info.nfinfo_inact_t = INACTIVE_TIMEOUT; + priv->info.nfinfo_act_t = ACTIVE_TIMEOUT; + + /* Initialize callout handle */ + callout_init(&priv->exp_callout, 1); + + /* Allocate memory and set up flow cache */ + if ((error = ng_netflow_cache_init(priv))) + return (error); + + priv->dgram.header.version = htons(NETFLOW_V5); + + return (0); +} + +/* + * ng_netflow supports two hooks: data and export. + * Incoming traffic is expected on data, and expired + * netflow datagrams are sent to export. + */ +static int +ng_netflow_newhook(node_p node, hook_p hook, const char *name) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + + if (strncmp(name, NG_NETFLOW_HOOK_DATA, /* an iface hook? */ + strlen(NG_NETFLOW_HOOK_DATA)) == 0) { + iface_p iface; + int ifnum = -1; + const char *cp; + char *eptr; + + cp = name + strlen(NG_NETFLOW_HOOK_DATA); + if (!isdigit(*cp) || (cp[0] == '0' && cp[1] != '\0')) + return (EINVAL); + + ifnum = (int)strtoul(cp, &eptr, 10); + if (*eptr != '\0' || ifnum < 0 || ifnum >= NG_NETFLOW_MAXIFACES) + return (EINVAL); + + /* See if hook is already connected */ + if (priv->ifaces[ifnum].hook != NULL) + return (EISCONN); + + iface = &priv->ifaces[ifnum]; + + /* Link private info and hook together */ + NG_HOOK_SET_PRIVATE(hook, iface); + iface->hook = hook; + + /* + * In most cases traffic accounting is done on an + * Ethernet interface, so default data link type + * will be DLT_EN10MB. + */ + iface->info.ifinfo_dlt = DLT_EN10MB; + + } else if (strcmp(name, NG_NETFLOW_HOOK_EXPORT) == 0) { + + if (priv->export != NULL) + return (EISCONN); + + priv->export = hook; + + /* Exporter is ready. Let's schedule expiry. */ + callout_reset(&priv->exp_callout, (1*hz), &ng_netflow_expire, + (void *)priv); + } else + return (EINVAL); + + return (0); +} + +/* Get a netgraph control message. */ +static int +ng_netflow_rcvmsg (node_p node, item_p item, hook_p lasthook) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + struct ng_mesg *resp = NULL; + int error = 0; + struct ng_mesg *msg; + + NGI_GET_MSG(item, msg); + + /* Deal with message according to cookie and command */ + switch (msg->header.typecookie) { + case NGM_NETFLOW_COOKIE: + switch (msg->header.cmd) { + case NGM_NETFLOW_INFO: + { + struct ng_netflow_info *i; + + NG_MKRESPONSE(resp, msg, sizeof(struct ng_netflow_info), + M_NOWAIT); + i = (struct ng_netflow_info *)resp->data; + ng_netflow_copyinfo(priv, i); + + break; + } + case NGM_NETFLOW_IFINFO: + { + struct ng_netflow_ifinfo *i; + const uint8_t *index; + + if (msg->header.arglen != sizeof(uint8_t)) + ERROUT(EINVAL); + + index = (uint8_t *)msg->data; + + /* connected iface? */ + if (priv->ifaces[*index].hook == NULL) + ERROUT(EINVAL); + + NG_MKRESPONSE(resp, msg, + sizeof(struct ng_netflow_ifinfo), M_NOWAIT); + i = (struct ng_netflow_ifinfo *)resp->data; + memcpy((void *)i, (void *)&priv->ifaces[*index].info, + sizeof(priv->ifaces[*index].info)); + + break; + } + case NGM_NETFLOW_SETDLT: + { + struct ng_netflow_setdlt *set; + struct ng_netflow_iface *iface; + + if (msg->header.arglen != sizeof(struct ng_netflow_setdlt)) + ERROUT(EINVAL); + + set = (struct ng_netflow_setdlt *)msg->data; + iface = &priv->ifaces[set->iface]; + + /* connected iface? */ + if (iface->hook == NULL) + ERROUT(EINVAL); + + switch (set->dlt) { + case DLT_EN10MB: + iface->info.ifinfo_dlt = DLT_EN10MB; + break; + case DLT_RAW: + iface->info.ifinfo_dlt = DLT_RAW; + break; + default: + ERROUT(EINVAL); + } + break; + } + case NGM_NETFLOW_SETIFINDEX: + { + struct ng_netflow_setifindex *set; + struct ng_netflow_iface *iface; + + if (msg->header.arglen != sizeof(struct ng_netflow_setifindex)) + ERROUT(EINVAL); + + set = (struct ng_netflow_setifindex *)msg->data; + iface = &priv->ifaces[set->iface]; + + /* connected iface? */ + if (iface->hook == NULL) + ERROUT(EINVAL); + + iface->info.ifinfo_index = set->index; + + break; + } + case NGM_NETFLOW_SETTIMEOUTS: + { + struct ng_netflow_settimeouts *set; + + if (msg->header.arglen != sizeof(struct ng_netflow_settimeouts)) + ERROUT(EINVAL); + + set = (struct ng_netflow_settimeouts *)msg->data; + + priv->info.nfinfo_inact_t = set->inactive_timeout; + priv->info.nfinfo_act_t = set->active_timeout; + + break; + } + case NGM_NETFLOW_SHOW: + { + uint32_t *last; + + if (msg->header.arglen != sizeof(uint32_t)) + ERROUT(EINVAL); + + last = (uint32_t *)msg->data; + + NG_MKRESPONSE(resp, msg, NGRESP_SIZE, M_NOWAIT); + + if (!resp) + ERROUT(ENOMEM); + + error = ng_netflow_flow_show(priv, *last, resp); + + break; + } + default: + ERROUT(EINVAL); /* unknown command */ + break; + } + break; + default: + ERROUT(EINVAL); /* incorrect cookie */ + break; + } + + /* + * Take care of synchronous response, if any. + * Free memory and return. + */ +done: + NG_RESPOND_MSG(error, node, item, resp); + NG_FREE_MSG(msg); + + return (error); +} + +/* Receive data on hook. */ +static int +ng_netflow_rcvdata (hook_p hook, item_p item) +{ + const node_p node = NG_HOOK_NODE(hook); + const priv_p priv = NG_NODE_PRIVATE(node); + const iface_p iface = NG_HOOK_PRIVATE(hook); + struct mbuf *m; + int error = 0; + + NGI_GET_M(item, m); + if (hook == priv->export) { + /* + * Data arrived on export hook. + * This must not happen. + */ + printf("ng_netflow: incoming data on export hook!\n"); + ERROUT(EINVAL); + }; + + /* increase counters */ + iface->info.ifinfo_packets++; + + switch (iface->info.ifinfo_dlt) { + case DLT_EN10MB: /* Ethernet */ + { + struct ether_header *eh; + uint16_t etype; + + if (CHECK_MLEN(m, (sizeof(struct ether_header)))) + ERROUT(EINVAL); + + if (CHECK_PULLUP(m, (sizeof(struct ether_header)))) + ERROUT(ENOBUFS); + + eh = mtod(m, struct ether_header *); + + /* make sure this is IP frame */ + etype = ntohs(eh->ether_type); + switch (etype) { + case ETHERTYPE_IP: + m_adj(m, sizeof(struct ether_header)); + break; + default: + ERROUT(EINVAL); /* ignore this frame */ + } + + break; + } + case DLT_RAW: + break; + default: + ERROUT(EINVAL); + break; + } + + if (CHECK_MLEN(m, sizeof(struct ip))) + ERROUT(EINVAL); + + if (CHECK_PULLUP(m, sizeof(struct ip))) + ERROUT(ENOBUFS); + + error = ng_netflow_flow_add(priv, &m, iface); + +done: + if (m) { + if (item) + NG_FREE_ITEM(item); + NG_FREE_M(m); + } + + return (error); +} + +/* We will be shut down in a moment */ +static int +ng_netflow_close(node_p node) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + + callout_drain(&priv->exp_callout); + ng_netflow_cache_flush(priv); + + return (0); +} + +/* Do local shutdown processing. */ +static int +ng_netflow_rmnode(node_p node) +{ + const priv_p priv = NG_NODE_PRIVATE(node); + + NG_NODE_SET_PRIVATE(node, NULL); + NG_NODE_UNREF(priv->node); + + FREE(priv, M_NETGRAPH); + + return (0); +} + +/* Hook disconnection. */ +static int +ng_netflow_disconnect(hook_p hook) +{ + node_p node = NG_HOOK_NODE(hook); + priv_p priv = NG_NODE_PRIVATE(node); + iface_p iface = NG_HOOK_PRIVATE(hook); + + if (iface != NULL) + iface->hook = NULL; + + /* if export hook disconnected stop running expire(). */ + if (hook == priv->export) { + callout_drain(&priv->exp_callout); + priv->export = NULL; + } + + /* Removal of the last link destroys the node. */ + if (NG_NODE_NUMHOOKS(node) == 0) + ng_rmnode_self(node); + + return (0); +} diff --git a/sys/netgraph/netflow/ng_netflow.h b/sys/netgraph/netflow/ng_netflow.h new file mode 100644 index 0000000..f668b5d --- /dev/null +++ b/sys/netgraph/netflow/ng_netflow.h @@ -0,0 +1,282 @@ +/*- + * Copyright (c) 2004 Gleb Smirnoff <glebius@cell.sick.ru> + * Copyright (c) 2001-2003 Roman V. Palagin <romanp@unshadow.net> + * 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. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Gleb Smirnoff and + * contributors. + * 4. Neither the name of the author nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * 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. + * + * $SourceForge: ng_netflow.h,v 1.26 2004/09/04 15:44:55 glebius Exp $ + * $FreeBSD$ + */ + +#ifndef _NG_NETFLOW_H_ +#define _NG_NETFLOW_H_ + +#define NG_NETFLOW_NODE_TYPE "netflow" +#define NGM_NETFLOW_COOKIE 1095355665 + +#define NG_NETFLOW_MAXIFACES 64 + +/* Hook names */ + +#define NG_NETFLOW_HOOK_DATA "iface" +#define NG_NETFLOW_HOOK_EXPORT "export" + +/* Netgraph commands understood by netflow node */ +enum { + NGM_NETFLOW_INFO = 1, /* get node info */ + NGM_NETFLOW_IFINFO, /* get iface info */ + NGM_NETFLOW_SHOW, /* show ip cache flow */ + NGM_NETFLOW_SETDLT, /* set data-link type */ + NGM_NETFLOW_SETIFINDEX, /* set interface index */ + NGM_NETFLOW_SETTIMEOUTS, /* set active/inactive flow timeouts */ +}; + +/* This structure is returned by the NGM_NETFLOW_INFO message */ +struct ng_netflow_info { + uint64_t nfinfo_bytes; /* total number of accounted bytes */ + uint32_t nfinfo_packets; /* total number of accounted packets */ + uint32_t nfinfo_used; /* number of used cache records */ + uint32_t nfinfo_free; /* number of free records */ + uint32_t nfinfo_inact_t; /* flow inactive timeout */ + uint32_t nfinfo_act_t; /* flow active timeout */ +}; + +/* This structure is returned by the NGM_NETFLOW_IFINFO message */ +struct ng_netflow_ifinfo { + uint32_t ifinfo_packets; /* number of packets for this iface */ + uint8_t ifinfo_dlt; /* Data Link Type, DLT_XXX */ +#define MAXDLTNAMELEN 20 + u_int16_t ifinfo_index; /* connected iface index */ +}; + + +/* This structure is passed to NGM_NETFLOW_SETDLT message */ +struct ng_netflow_setdlt { + uint16_t iface; /* which iface dlt change */ + uint8_t dlt; /* DLT_XXX from bpf.h */ +}; + +/* This structure is passed to NGM_NETFLOW_SETIFINDEX */ +struct ng_netflow_setifindex { + u_int16_t iface; /* which iface index change */ + u_int16_t index; /* new index */ +}; + +/* This structure is passed to NGM_NETFLOW_SETTIMEOUTS */ +struct ng_netflow_settimeouts { + uint32_t inactive_timeout; /* flow inactive timeout */ + uint32_t active_timeout; /* flow active timeout */ +}; + +/* This is unique data, which identifies flow */ +struct flow_rec { + struct in_addr r_src; + struct in_addr r_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 +#define r_misc misc.all +#define r_ports ports.both +#define r_sport ports.dir.s_port +#define r_dport ports.dir.d_port + +/* A flow entry which accumulates statistics */ +struct flow_entry_data { + struct flow_rec r; + struct in_addr next_hop; + 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 + */ +#define NREC_AT_ONCE 1000 +#define NGRESP_SIZE (sizeof(struct ngnf_flows) + (NREC_AT_ONCE * \ + sizeof(struct flow_entry_data))) +#define SORCVBUF_SIZE (NGRESP_SIZE + 2 * sizeof(struct ng_mesg)) + +/* This struct is returned to userland, when "show cache ip flow" */ +struct ngnf_flows { + uint32_t nentries; + uint32_t last; + struct flow_entry_data entries[0]; +}; + +/* Everything below is for kernel */ + +#ifdef _KERNEL + +struct flow_entry { + struct flow_entry_data f; + + LIST_ENTRY(flow_entry) fle_hash; /* entries in one hash item */ + TAILQ_ENTRY(flow_entry) fle_work; /* entries in work queue*/ + SLIST_ENTRY(flow_entry) fle_free; /* entries in free stack */ +}; + +/* 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 },\ + { "Records free", &ng_parse_uint32_type },\ + { "Inactive timeout", &ng_parse_uint32_type },\ + { "Active timeout", &ng_parse_uint32_type },\ + { NULL } \ +} + +/* Parse the ifinfo structure */ +#define NG_NETFLOW_IFINFO_TYPE { \ + { "packets", &ng_parse_uint32_type }, \ + { "data link type", &ng_parse_uint8_type }, \ + { "index", &ng_parse_uint16_type }, \ + { NULL } \ +} + +/* Parse the setdlt structure */ +#define NG_NETFLOW_SETDLT_TYPE { \ + { "iface", &ng_parse_uint16_type }, \ + { "dlt", &ng_parse_uint8_type }, \ + { NULL } \ +} + +/* Parse the setifindex structure */ +#define NG_NETFLOW_SETIFINDEX_TYPE { \ + { "iface", &ng_parse_uint16_type }, \ + { "index", &ng_parse_uint16_type }, \ + { NULL } \ +} + +/* Parse the settimeouts structure */ +#define NG_NETFLOW_SETTIMEOUTS_TYPE { \ + { "inactive", &ng_parse_uint32_type }, \ + { "active", &ng_parse_uint32_type }, \ + { NULL } \ +} + +/* Private hook data */ +struct ng_netflow_iface { + hook_p hook; /* NULL when disconnected */ + struct ng_netflow_ifinfo info; +}; + +typedef struct ng_netflow_iface *iface_p; +typedef struct ng_netflow_ifinfo *ifinfo_p; + +/* Structure describing our flow engine */ +struct netflow { + node_p node; /* link to the node itself */ + + struct ng_netflow_iface ifaces[NG_NETFLOW_MAXIFACES]; /* incoming */ + hook_p export; /* export data goes there */ + + struct ng_netflow_info info; + uint32_t flow_seq; /* current flow sequence */ + + struct callout exp_callout; + + /* Flow cache is a big chunk of memory referenced by 'cache'. + * Accounting engine searches for its record using hashing index + * 'hash'. Expiry engine searches for its record from begining of + * tail queue 'expire_q'. Allocation is performed using last free + * stack held in singly linked list 'free_l' */ +#define CACHESIZE 65536 +#define CACHELOWAT (CACHESIZE * 3/4) +#define CACHEHIGHWAT (CACHESIZE * 9/10) + struct flow_entry *cache; + struct flow_hash_entry *hash; + TAILQ_HEAD( , flow_entry) work_queue; + SLIST_HEAD( , flow_entry) free_list; + SLIST_HEAD( , flow_entry) expire_list; + + /* Mutexes to protect above lists */ + struct mtx work_mtx; + struct mtx free_mtx; + struct mtx expire_mtx; + + /* ng_netflow_export_send() forms its datagram here. */ + struct netflow_export_dgram { + struct netflow_v5_header header; + struct netflow_v5_record r[NETFLOW_V5_MAX_RECORDS]; + } __attribute__((__packed__)) dgram; +}; + +typedef struct netflow *priv_p; + +/* Header of a small list in hash cell */ +struct flow_hash_entry { + LIST_HEAD( ,flow_entry) head; +}; + +/* Make sure packet large enough to contain len bytes */ +#define CHECK_MLEN(m, length) ((m)->m_pkthdr.len < (length)) +#define CHECK_PULLUP(m, length) ((m)->m_len < (length) && \ + (((m) = m_pullup((m),(length))) == NULL)) + +#define ERROUT(x) { error = (x); goto done; } + +/* Prototypes for netflow.c */ +int ng_netflow_cache_init(priv_p); +void ng_netflow_cache_flush(priv_p); +void ng_netflow_copyinfo(priv_p, struct ng_netflow_info *); +timeout_t ng_netflow_expire; +int ng_netflow_flow_add(priv_p, struct mbuf **, iface_p); +int ng_netflow_flow_show(priv_p, uint32_t last, struct ng_mesg *); + +#endif /* _KERNEL */ +#endif /* _NG_NETFLOW_H_ */ |