netflow.c revision 143923
1135332Sglebius/*- 2143923Sglebius * Copyright (c) 2004-2005 Gleb Smirnoff <glebius@FreeBSD.org> 3135332Sglebius * Copyright (c) 2001-2003 Roman V. Palagin <romanp@unshadow.net> 4135332Sglebius * All rights reserved. 5135332Sglebius * 6135332Sglebius * Redistribution and use in source and binary forms, with or without 7135332Sglebius * modification, are permitted provided that the following conditions 8135332Sglebius * are met: 9135332Sglebius * 1. Redistributions of source code must retain the above copyright 10135332Sglebius * notice, this list of conditions and the following disclaimer. 11135332Sglebius * 2. Redistributions in binary form must reproduce the above copyright 12135332Sglebius * notice, this list of conditions and the following disclaimer in the 13135332Sglebius * documentation and/or other materials provided with the distribution. 14135332Sglebius * 15135332Sglebius * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 16135332Sglebius * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17135332Sglebius * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18135332Sglebius * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 19135332Sglebius * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20135332Sglebius * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 21135332Sglebius * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22135332Sglebius * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 23135332Sglebius * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 24135332Sglebius * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 25135332Sglebius * SUCH DAMAGE. 26135332Sglebius * 27135332Sglebius * $SourceForge: netflow.c,v 1.41 2004/09/05 11:41:10 glebius Exp $ 28135332Sglebius */ 29135332Sglebius 30135332Sglebiusstatic const char rcs_id[] = 31135332Sglebius "@(#) $FreeBSD: head/sys/netgraph/netflow/netflow.c 143923 2005-03-21 15:34:03Z glebius $"; 32135332Sglebius 33135332Sglebius#include <sys/param.h> 34135332Sglebius#include <sys/kernel.h> 35135332Sglebius#include <sys/limits.h> 36135332Sglebius#include <sys/mbuf.h> 37140511Sglebius#include <sys/syslog.h> 38135332Sglebius#include <sys/systm.h> 39135332Sglebius#include <sys/socket.h> 40135332Sglebius 41135332Sglebius#include <net/if.h> 42135332Sglebius#include <net/if_var.h> 43135332Sglebius#include <net/if_dl.h> 44135332Sglebius#include <net/route.h> 45135332Sglebius#include <netinet/in.h> 46135332Sglebius#include <netinet/in_systm.h> 47135332Sglebius#include <netinet/ip.h> 48135332Sglebius#include <netinet/tcp.h> 49135332Sglebius#include <netinet/udp.h> 50135332Sglebius 51135332Sglebius#include <netgraph/ng_message.h> 52135332Sglebius#include <netgraph/netgraph.h> 53135332Sglebius 54135332Sglebius#include <netgraph/netflow/netflow.h> 55135332Sglebius#include <netgraph/netflow/ng_netflow.h> 56135332Sglebius 57135332Sglebius#define NBUCKETS (4096) /* must be power of 2 */ 58135332Sglebius 59135332Sglebius/* This hash is for TCP or UDP packets */ 60135332Sglebius#define FULL_HASH(addr1,addr2,port1,port2)\ 61135332Sglebius (((addr1 >> 16) ^ \ 62135332Sglebius (addr2 & 0x00FF) ^ \ 63135332Sglebius ((port1 ^ port2) << 8) )& \ 64135332Sglebius (NBUCKETS - 1)) 65135332Sglebius 66135332Sglebius/* This hash for all other IP packets */ 67135332Sglebius#define ADDR_HASH(addr1,addr2)\ 68135332Sglebius (((addr1 >> 16) ^ \ 69135332Sglebius (addr2 & 0x00FF) )& \ 70135332Sglebius (NBUCKETS - 1)) 71135332Sglebius 72135332Sglebius/* Macros to shorten logical constructions */ 73135332Sglebius/* XXX: priv must exist in namespace */ 74135332Sglebius#define INACTIVE(fle) (time_uptime - fle->f.last > priv->info.nfinfo_inact_t) 75135332Sglebius#define AGED(fle) (time_uptime - fle->f.first > priv->info.nfinfo_act_t) 76135332Sglebius#define ISFREE(fle) (fle->f.packets == 0) 77135332Sglebius 78135332Sglebius/* 79135332Sglebius * 4 is a magical number: statistically number of 4-packet flows is 80135332Sglebius * bigger than 5,6,7...-packet flows by an order of magnitude. Most UDP/ICMP 81135332Sglebius * scans are 1 packet (~ 90% of flow cache). TCP scans are 2-packet in case 82135332Sglebius * of reachable host and 4-packet otherwise. 83135332Sglebius */ 84135332Sglebius#define SMALL(fle) (fle->f.packets <= 4) 85143103Sglebius 86143103Sglebius/* 87143103Sglebius * Cisco uses milliseconds for uptime. Bad idea, since it overflows 88143103Sglebius * every 48+ days. But we will do same to keep compatibility. This macro 89143103Sglebius * does overflowable multiplication to 1000. 90143103Sglebius */ 91143103Sglebius#define MILLIUPTIME(t) (((t) << 9) + /* 512 */ \ 92143103Sglebius ((t) << 8) + /* 256 */ \ 93143103Sglebius ((t) << 7) + /* 128 */ \ 94143103Sglebius ((t) << 6) + /* 64 */ \ 95143103Sglebius ((t) << 5) + /* 32 */ \ 96143103Sglebius ((t) << 3)) /* 8 */ 97143103Sglebius 98135332SglebiusMALLOC_DECLARE(M_NETFLOW); 99135332SglebiusMALLOC_DEFINE(M_NETFLOW, "NetFlow", "flow cache"); 100135332Sglebius 101135332Sglebiusstatic int export_add(priv_p , struct flow_entry *); 102135332Sglebiusstatic int export_send(priv_p ); 103135332Sglebius 104135332Sglebius/* Generate hash for a given flow record */ 105135332Sglebiusstatic __inline uint32_t 106135332Sglebiusip_hash(struct flow_rec *r) 107135332Sglebius{ 108135332Sglebius switch (r->r_ip_p) { 109135332Sglebius case IPPROTO_TCP: 110135332Sglebius case IPPROTO_UDP: 111135332Sglebius return FULL_HASH(r->r_src.s_addr, r->r_dst.s_addr, 112135332Sglebius r->r_sport, r->r_dport); 113135332Sglebius default: 114135332Sglebius return ADDR_HASH(r->r_src.s_addr, r->r_dst.s_addr); 115135332Sglebius } 116135332Sglebius} 117135332Sglebius 118135332Sglebius/* Lookup for record in given slot */ 119135332Sglebiusstatic __inline struct flow_entry * 120135332Sglebiushash_lookup(struct flow_hash_entry *h, int slot, struct flow_rec *r) 121135332Sglebius{ 122135332Sglebius struct flow_entry *fle; 123135332Sglebius 124135332Sglebius LIST_FOREACH(fle, &(h[slot].head), fle_hash) 125135332Sglebius if (bcmp(r, &fle->f.r, sizeof(struct flow_rec)) == 0) 126135332Sglebius return (fle); 127135332Sglebius 128135332Sglebius return (NULL); 129135332Sglebius} 130135332Sglebius 131135332Sglebius/* Get a flow entry from free list */ 132135332Sglebiusstatic __inline struct flow_entry * 133135332Sglebiusalloc_flow(priv_p priv, int *flows) 134135332Sglebius{ 135135332Sglebius register struct flow_entry *fle; 136135332Sglebius 137135332Sglebius mtx_lock(&priv->free_mtx); 138135332Sglebius 139135332Sglebius if (SLIST_EMPTY(&priv->free_list)) { 140135332Sglebius mtx_unlock(&priv->free_mtx); 141135332Sglebius return(NULL); 142135332Sglebius } 143135332Sglebius 144135332Sglebius fle = SLIST_FIRST(&priv->free_list); 145135332Sglebius SLIST_REMOVE_HEAD(&priv->free_list, fle_free); 146135332Sglebius 147135332Sglebius priv->info.nfinfo_used++; 148135332Sglebius priv->info.nfinfo_free--; 149135332Sglebius 150135332Sglebius if (flows != NULL) 151135332Sglebius *flows = priv->info.nfinfo_used; 152135332Sglebius 153135332Sglebius mtx_unlock(&priv->free_mtx); 154135332Sglebius 155135332Sglebius return (fle); 156135332Sglebius} 157135332Sglebius 158135332Sglebius/* Insert flow entry into a free list. */ 159135332Sglebiusstatic __inline int 160135332Sglebiusfree_flow(priv_p priv, struct flow_entry *fle) 161135332Sglebius{ 162135332Sglebius int flows; 163135332Sglebius 164135332Sglebius mtx_lock(&priv->free_mtx); 165135332Sglebius fle->f.packets = 0; 166135332Sglebius SLIST_INSERT_HEAD(&priv->free_list, fle, fle_free); 167135332Sglebius flows = priv->info.nfinfo_used--; 168135332Sglebius priv->info.nfinfo_free++; 169135332Sglebius mtx_unlock(&priv->free_mtx); 170135332Sglebius 171135332Sglebius return flows; 172135332Sglebius} 173135332Sglebius 174135332Sglebius#define NGNF_GETUSED(priv, rval) do { \ 175135332Sglebius mtx_lock(&priv->free_mtx); \ 176135332Sglebius rval = priv->info.nfinfo_used; \ 177135332Sglebius mtx_unlock(&priv->free_mtx); \ 178135332Sglebius } while (0) 179135332Sglebius 180135332Sglebius/* Insert flow entry into expire list. */ 181135332Sglebius/* XXX: Flow must be detached from work queue, but not from cache */ 182135332Sglebiusstatic __inline void 183135332Sglebiusexpire_flow(priv_p priv, struct flow_entry *fle) 184135332Sglebius{ 185135332Sglebius mtx_assert(&priv->work_mtx, MA_OWNED); 186135332Sglebius LIST_REMOVE(fle, fle_hash); 187135332Sglebius 188135332Sglebius mtx_lock(&priv->expire_mtx); 189135332Sglebius SLIST_INSERT_HEAD(&priv->expire_list, fle, fle_free); 190135332Sglebius mtx_unlock(&priv->expire_mtx); 191135332Sglebius} 192135332Sglebius 193135332Sglebius/* Get a snapshot of node statistics */ 194135332Sglebiusvoid 195135332Sglebiusng_netflow_copyinfo(priv_p priv, struct ng_netflow_info *i) 196135332Sglebius{ 197135332Sglebius mtx_lock(&priv->free_mtx); 198135332Sglebius memcpy((void *)i, (void *)&priv->info, sizeof(priv->info)); 199135332Sglebius mtx_unlock(&priv->free_mtx); 200135332Sglebius} 201135332Sglebius 202135332Sglebius/* Calculate number of bits in netmask */ 203135332Sglebius#define g21 0x55555555ul /* = 0101_0101_0101_0101_0101_0101_0101_0101 */ 204135332Sglebius#define g22 0x33333333ul /* = 0011_0011_0011_0011_0011_0011_0011_0011 */ 205135332Sglebius#define g23 0x0f0f0f0ful /* = 0000_1111_0000_1111_0000_1111_0000_1111 */ 206135332Sglebiusstatic __inline u_char 207135332Sglebiusbit_count(uint32_t v) 208135332Sglebius{ 209135332Sglebius v = (v & g21) + ((v >> 1) & g21); 210135332Sglebius v = (v & g22) + ((v >> 2) & g22); 211135332Sglebius v = (v + (v >> 4)) & g23; 212135332Sglebius return (v + (v >> 8) + (v >> 16) + (v >> 24)) & 0x3f; 213135332Sglebius} 214135332Sglebius 215135332Sglebius/* 216135332Sglebius * Insert a record into defined slot. 217135332Sglebius * 218135332Sglebius * First we get for us a free flow entry, then fill in all 219135332Sglebius * possible fields in it. Then obtain lock on flow cache 220135332Sglebius * and insert flow entry. 221135332Sglebius */ 222135332Sglebiusstatic __inline int 223143890Sglebiushash_insert(priv_p priv, int slot, struct flow_rec *r, int plen, 224143890Sglebius uint8_t tcp_flags) 225135332Sglebius{ 226135332Sglebius struct flow_hash_entry *h = priv->hash; 227135332Sglebius struct flow_entry *fle; 228135332Sglebius struct route ro; 229135332Sglebius struct sockaddr_in *sin; 230135332Sglebius 231135332Sglebius fle = alloc_flow(priv, NULL); 232135332Sglebius if (fle == NULL) 233135332Sglebius return (ENOMEM); 234135332Sglebius 235135332Sglebius /* 236135332Sglebius * Now fle is totally ours. It is detached from all lists, 237135332Sglebius * we can safely edit it. 238135332Sglebius */ 239135332Sglebius 240135332Sglebius bcopy(r, &fle->f.r, sizeof(struct flow_rec)); 241135332Sglebius fle->f.bytes = plen; 242135332Sglebius fle->f.packets = 1; 243143890Sglebius fle->f.tcp_flags = tcp_flags; 244135332Sglebius 245135332Sglebius fle->f.first = fle->f.last = time_uptime; 246135332Sglebius 247135332Sglebius /* 248135332Sglebius * First we do route table lookup on destination address. So we can 249135332Sglebius * fill in out_ifx, dst_mask, nexthop, and dst_as in future releases. 250135332Sglebius */ 251135332Sglebius bzero((caddr_t)&ro, sizeof(ro)); 252135332Sglebius sin = (struct sockaddr_in *)&ro.ro_dst; 253135332Sglebius sin->sin_len = sizeof(*sin); 254135332Sglebius sin->sin_family = AF_INET; 255135332Sglebius sin->sin_addr = fle->f.r.r_dst; 256135332Sglebius rtalloc_ign(&ro, RTF_CLONING); 257135332Sglebius if (ro.ro_rt != NULL) { 258135332Sglebius struct rtentry *rt = ro.ro_rt; 259135332Sglebius 260135332Sglebius fle->f.fle_o_ifx = rt->rt_ifp->if_index; 261135332Sglebius 262135332Sglebius if (rt->rt_flags & RTF_GATEWAY && 263135332Sglebius rt->rt_gateway->sa_family == AF_INET) 264135332Sglebius fle->f.next_hop = 265135332Sglebius ((struct sockaddr_in *)(rt->rt_gateway))->sin_addr; 266135332Sglebius 267135332Sglebius if (rt_mask(rt)) 268135332Sglebius fle->f.dst_mask = 269135332Sglebius bit_count(((struct sockaddr_in *)rt_mask(rt))->sin_addr.s_addr); 270135332Sglebius else if (rt->rt_flags & RTF_HOST) 271135332Sglebius /* Give up. We can't determine mask :( */ 272135332Sglebius fle->f.dst_mask = 32; 273135332Sglebius 274135332Sglebius RTFREE(ro.ro_rt); 275135332Sglebius } 276135332Sglebius 277135332Sglebius /* Do route lookup on source address, to fill in src_mask. */ 278135332Sglebius 279135332Sglebius bzero((caddr_t)&ro, sizeof(ro)); 280135332Sglebius sin = (struct sockaddr_in *)&ro.ro_dst; 281135332Sglebius sin->sin_len = sizeof(*sin); 282135332Sglebius sin->sin_family = AF_INET; 283135332Sglebius sin->sin_addr = fle->f.r.r_src; 284135332Sglebius rtalloc_ign(&ro, RTF_CLONING); 285135332Sglebius if (ro.ro_rt != NULL) { 286135332Sglebius struct rtentry *rt = ro.ro_rt; 287135332Sglebius 288135332Sglebius if (rt_mask(rt)) 289135332Sglebius fle->f.src_mask = 290135332Sglebius bit_count(((struct sockaddr_in *)rt_mask(rt))->sin_addr.s_addr); 291135332Sglebius else if (rt->rt_flags & RTF_HOST) 292135332Sglebius /* Give up. We can't determine mask :( */ 293135332Sglebius fle->f.src_mask = 32; 294135332Sglebius 295135332Sglebius RTFREE(ro.ro_rt); 296135332Sglebius } 297135332Sglebius 298135332Sglebius /* Push new flow entry into flow cache */ 299135332Sglebius mtx_lock(&priv->work_mtx); 300135332Sglebius LIST_INSERT_HEAD(&(h[slot].head), fle, fle_hash); 301135332Sglebius TAILQ_INSERT_TAIL(&priv->work_queue, fle, fle_work); 302135332Sglebius mtx_unlock(&priv->work_mtx); 303135332Sglebius 304135332Sglebius return (0); 305135332Sglebius} 306135332Sglebius 307135332Sglebius 308135332Sglebius/* 309135332Sglebius * Non-static functions called from ng_netflow.c 310135332Sglebius */ 311135332Sglebius 312135332Sglebius/* Allocate memory and set up flow cache */ 313135332Sglebiusint 314135332Sglebiusng_netflow_cache_init(priv_p priv) 315135332Sglebius{ 316135332Sglebius struct flow_entry *fle; 317135332Sglebius int i; 318135332Sglebius 319135332Sglebius /* allocate cache */ 320135332Sglebius MALLOC(priv->cache, struct flow_entry *, 321135332Sglebius CACHESIZE * sizeof(struct flow_entry), 322135332Sglebius M_NETFLOW, M_WAITOK | M_ZERO); 323135332Sglebius 324135332Sglebius if (priv->cache == NULL) 325135332Sglebius return (ENOMEM); 326135332Sglebius 327135332Sglebius /* allocate hash */ 328135332Sglebius MALLOC(priv->hash, struct flow_hash_entry *, 329135332Sglebius NBUCKETS * sizeof(struct flow_hash_entry), 330135332Sglebius M_NETFLOW, M_WAITOK | M_ZERO); 331135332Sglebius 332139374Sglebius if (priv->hash == NULL) { 333139374Sglebius FREE(priv->cache, M_NETFLOW); 334135332Sglebius return (ENOMEM); 335139374Sglebius } 336135332Sglebius 337135332Sglebius TAILQ_INIT(&priv->work_queue); 338135332Sglebius SLIST_INIT(&priv->free_list); 339135332Sglebius SLIST_INIT(&priv->expire_list); 340135332Sglebius 341135332Sglebius mtx_init(&priv->work_mtx, "ng_netflow cache mutex", NULL, MTX_DEF); 342135332Sglebius mtx_init(&priv->free_mtx, "ng_netflow free mutex", NULL, MTX_DEF); 343135332Sglebius mtx_init(&priv->expire_mtx, "ng_netflow expire mutex", NULL, MTX_DEF); 344135332Sglebius 345135332Sglebius /* build free list */ 346135332Sglebius for (i = 0, fle = priv->cache; i < CACHESIZE; i++, fle++) 347135332Sglebius SLIST_INSERT_HEAD(&priv->free_list, fle, fle_free); 348135332Sglebius 349135332Sglebius priv->info.nfinfo_free = CACHESIZE; 350135332Sglebius 351135332Sglebius return (0); 352135332Sglebius} 353135332Sglebius 354135332Sglebius/* Free all flow cache memory. Called from node close method. */ 355135332Sglebiusvoid 356135332Sglebiusng_netflow_cache_flush(priv_p priv) 357135332Sglebius{ 358135332Sglebius register struct flow_entry *fle; 359135332Sglebius int i; 360135332Sglebius 361135332Sglebius /* 362135332Sglebius * We are going to free probably billable data. 363135332Sglebius * Expire everything before freeing it. 364135332Sglebius * No locking is required since callout is already drained. 365135332Sglebius */ 366135332Sglebius 367135332Sglebius for (i = 0, fle = priv->cache; i < CACHESIZE; i++, fle++) 368135332Sglebius if (!ISFREE(fle)) 369135332Sglebius /* ignore errors now */ 370135332Sglebius (void )export_add(priv, fle); 371135332Sglebius 372135332Sglebius mtx_destroy(&priv->work_mtx); 373135332Sglebius mtx_destroy(&priv->free_mtx); 374135332Sglebius mtx_destroy(&priv->expire_mtx); 375135332Sglebius 376135332Sglebius /* free hash memory */ 377135332Sglebius if (priv->hash) 378135332Sglebius FREE(priv->hash, M_NETFLOW); 379135332Sglebius 380135332Sglebius /* free flow cache */ 381135332Sglebius if (priv->cache) 382135332Sglebius FREE(priv->cache, M_NETFLOW); 383135332Sglebius 384135332Sglebius} 385135332Sglebius 386135332Sglebius/* Insert packet from &m into flow cache. */ 387135332Sglebiusint 388143923Sglebiusng_netflow_flow_add(priv_p priv, struct ip *ip, iface_p iface, 389143923Sglebius struct ifnet *ifp) 390135332Sglebius{ 391135332Sglebius struct flow_hash_entry *h = priv->hash; 392135332Sglebius register struct flow_entry *fle; 393135332Sglebius struct flow_rec r; 394143923Sglebius int hlen, plen; 395135332Sglebius uint32_t slot; 396135332Sglebius uint8_t tcp_flags = 0; 397135332Sglebius 398143923Sglebius /* Try to fill flow_rec r */ 399135332Sglebius bzero(&r, sizeof(r)); 400143923Sglebius /* check version */ 401143923Sglebius if (ip->ip_v != IPVERSION) 402143923Sglebius return (EINVAL); 403135332Sglebius 404143923Sglebius /* verify min header length */ 405143923Sglebius hlen = ip->ip_hl << 2; 406143923Sglebius 407143923Sglebius if (hlen < sizeof(struct ip)) 408143923Sglebius return (EINVAL); 409143923Sglebius 410143923Sglebius r.r_src = ip->ip_src; 411143923Sglebius r.r_dst = ip->ip_dst; 412143923Sglebius 413143923Sglebius /* save packet length */ 414143923Sglebius plen = ntohs(ip->ip_len); 415143923Sglebius 416143923Sglebius r.r_ip_p = ip->ip_p; 417143923Sglebius r.r_tos = ip->ip_tos; 418143923Sglebius 419143923Sglebius /* Configured in_ifx overrides mbuf's */ 420143923Sglebius if (iface->info.ifinfo_index == 0) { 421143923Sglebius if (ifp != NULL) 422143923Sglebius r.r_i_ifx = ifp->if_index; 423143923Sglebius } else 424143923Sglebius r.r_i_ifx = iface->info.ifinfo_index; 425143923Sglebius 426143923Sglebius /* 427143923Sglebius * XXX NOTE: only first fragment of fragmented TCP, UDP and 428143923Sglebius * ICMP packet will be recorded with proper s_port and d_port. 429143923Sglebius * Following fragments will be recorded simply as IP packet with 430143923Sglebius * ip_proto = ip->ip_p and s_port, d_port set to zero. 431143923Sglebius * I know, it looks like bug. But I don't want to re-implement 432143923Sglebius * ip packet assebmling here. Anyway, (in)famous trafd works this way - 433143923Sglebius * and nobody complains yet :) 434143923Sglebius */ 435143923Sglebius if(ip->ip_off & htons(IP_OFFMASK)) 436143923Sglebius goto flow_rec_done; 437143923Sglebius 438143923Sglebius switch(r.r_ip_p) { 439143923Sglebius case IPPROTO_TCP: 440143923Sglebius { 441143923Sglebius register struct tcphdr *tcp; 442143923Sglebius 443143923Sglebius tcp = (struct tcphdr *)((caddr_t )ip + hlen); 444143923Sglebius r.r_sport = tcp->th_sport; 445143923Sglebius r.r_dport = tcp->th_dport; 446143923Sglebius tcp_flags = tcp->th_flags; 447143923Sglebius break; 448143923Sglebius } 449143923Sglebius case IPPROTO_UDP: 450143923Sglebius r.r_ports = *(uint32_t *)((caddr_t )ip + hlen); 451143923Sglebius break; 452143923Sglebius } 453143923Sglebius 454143923Sglebiusflow_rec_done: 455143923Sglebius 456135332Sglebius slot = ip_hash(&r); 457135332Sglebius 458135332Sglebius mtx_lock(&priv->work_mtx); 459139374Sglebius 460139374Sglebius /* Update node statistics. */ 461139374Sglebius priv->info.nfinfo_packets ++; 462139374Sglebius priv->info.nfinfo_bytes += plen; 463139374Sglebius 464135332Sglebius fle = hash_lookup(h, slot, &r); /* New flow entry or existent? */ 465135332Sglebius 466135332Sglebius if (fle) { /* an existent entry */ 467135332Sglebius 468135332Sglebius TAILQ_REMOVE(&priv->work_queue, fle, fle_work); 469135332Sglebius 470135332Sglebius fle->f.bytes += plen; 471135332Sglebius fle->f.packets ++; 472135332Sglebius fle->f.tcp_flags |= tcp_flags; 473135332Sglebius fle->f.last = time_uptime; 474135332Sglebius 475135332Sglebius /* 476135332Sglebius * We have the following reasons to expire flow in active way: 477135332Sglebius * - it hit active timeout 478135332Sglebius * - a TCP connection closed 479135332Sglebius * - it is going to overflow counter 480135332Sglebius */ 481135332Sglebius if (tcp_flags & TH_FIN || tcp_flags & TH_RST || AGED(fle) || 482135332Sglebius (fle->f.bytes >= (UINT_MAX - IF_MAXMTU)) ) 483135332Sglebius expire_flow(priv, fle); 484135332Sglebius else 485135332Sglebius TAILQ_INSERT_TAIL(&priv->work_queue, fle, fle_work); 486135332Sglebius 487135332Sglebius mtx_unlock(&priv->work_mtx); 488135332Sglebius 489135332Sglebius } else { /* a new flow entry */ 490135332Sglebius 491135332Sglebius mtx_unlock(&priv->work_mtx); 492143890Sglebius return hash_insert(priv, slot, &r, plen, tcp_flags); 493135332Sglebius 494135332Sglebius } 495135332Sglebius 496135332Sglebius mtx_assert(&priv->work_mtx, MA_NOTOWNED); 497135332Sglebius mtx_assert(&priv->expire_mtx, MA_NOTOWNED); 498135332Sglebius mtx_assert(&priv->free_mtx, MA_NOTOWNED); 499139374Sglebius 500135332Sglebius return (0); 501135332Sglebius} 502135332Sglebius 503135332Sglebius/* 504135332Sglebius * Return records from cache. netgraph(4) guarantees us that we 505135332Sglebius * are locked against ng_netflow_rcvdata(). However we can 506135332Sglebius * work with ng_netflow_expire() in parrallel. XXX: Is it dangerous? 507135332Sglebius * 508135332Sglebius * TODO: matching particular IP should be done in kernel, here. 509135332Sglebius */ 510135332Sglebiusint 511135332Sglebiusng_netflow_flow_show(priv_p priv, uint32_t last, struct ng_mesg *resp) 512135332Sglebius{ 513135332Sglebius struct flow_entry *fle; 514135332Sglebius struct ngnf_flows *data; 515135332Sglebius 516135332Sglebius data = (struct ngnf_flows *)resp->data; 517135332Sglebius data->last = 0; 518135332Sglebius data->nentries = 0; 519135332Sglebius 520135332Sglebius /* Check if this is a first run */ 521135332Sglebius if (last == 0) 522135332Sglebius fle = priv->cache; 523135332Sglebius else { 524135332Sglebius if (last > CACHESIZE-1) 525135332Sglebius return (EINVAL); 526135332Sglebius fle = priv->cache + last; 527135332Sglebius } 528135332Sglebius 529135332Sglebius /* 530135332Sglebius * We will transfer not more than NREC_AT_ONCE. More data 531135332Sglebius * will come in next message. 532135332Sglebius * We send current stop point to userland, and userland should return 533135332Sglebius * it back to us. 534135332Sglebius */ 535135332Sglebius for (; last < CACHESIZE; fle++, last++) { 536135332Sglebius if (ISFREE(fle)) 537135332Sglebius continue; 538141343Sglebius bcopy(&fle->f, &(data->entries[data->nentries]), 539141343Sglebius sizeof(fle->f)); 540135332Sglebius data->nentries ++; 541135332Sglebius if (data->nentries == NREC_AT_ONCE) { 542135332Sglebius if (++last < CACHESIZE) 543135332Sglebius data->last = (++fle - priv->cache); 544135332Sglebius return (0); 545135332Sglebius } 546135332Sglebius } 547135332Sglebius 548135332Sglebius return (0); 549135332Sglebius} 550135332Sglebius 551135332Sglebius/* We have full datagram in privdata. Send it to export hook. */ 552135332Sglebiusstatic int 553135332Sglebiusexport_send(priv_p priv) 554135332Sglebius{ 555135332Sglebius struct netflow_v5_header *header = &priv->dgram.header; 556135332Sglebius struct timespec ts; 557135332Sglebius struct mbuf *m; 558135332Sglebius int error = 0; 559135332Sglebius int mlen; 560135332Sglebius 561143103Sglebius header->sys_uptime = htonl(MILLIUPTIME(time_uptime)); 562135332Sglebius 563135332Sglebius getnanotime(&ts); 564135332Sglebius header->unix_secs = htonl(ts.tv_sec); 565135332Sglebius header->unix_nsecs = htonl(ts.tv_nsec); 566135332Sglebius 567135332Sglebius /* Flow sequence contains number of first record */ 568135332Sglebius header->flow_seq = htonl(priv->flow_seq - header->count); 569135332Sglebius 570135332Sglebius mlen = sizeof(struct netflow_v5_header) + 571135332Sglebius sizeof(struct netflow_v5_record) * header->count; 572135332Sglebius 573135332Sglebius header->count = htons(header->count); 574135332Sglebius if ((m = m_devget((caddr_t)header, mlen, 0, NULL, NULL)) == NULL) { 575140511Sglebius log(LOG_CRIT, "ng_netflow: m_devget() failed, losing export " 576140511Sglebius "dgram\n"); 577135332Sglebius header->count = 0; 578135332Sglebius return(ENOBUFS); 579135332Sglebius } 580135332Sglebius 581135332Sglebius header->count = 0; 582135332Sglebius 583135332Sglebius /* Giant is required in sosend() at this moment. */ 584135332Sglebius NET_LOCK_GIANT(); 585135332Sglebius NG_SEND_DATA_ONLY(error, priv->export, m); 586135332Sglebius NET_UNLOCK_GIANT(); 587135332Sglebius 588135332Sglebius if (error) 589135332Sglebius NG_FREE_M(m); 590135332Sglebius 591135332Sglebius return (error); 592135332Sglebius} 593135332Sglebius 594135332Sglebius 595135332Sglebius/* Create export datagram. */ 596135332Sglebiusstatic int 597135332Sglebiusexport_add(priv_p priv, struct flow_entry *fle) 598135332Sglebius{ 599135332Sglebius struct netflow_v5_header *header = &priv->dgram.header; 600135332Sglebius struct netflow_v5_record *rec; 601135332Sglebius 602135332Sglebius if (header->count == 0 ) { /* first record */ 603135332Sglebius rec = &priv->dgram.r[0]; 604135332Sglebius header->count = 1; 605135332Sglebius } else { /* continue filling datagram */ 606135332Sglebius rec = &priv->dgram.r[header->count]; 607135332Sglebius header->count ++; 608135332Sglebius } 609135332Sglebius 610135332Sglebius /* Fill in export record */ 611135332Sglebius rec->src_addr = fle->f.r.r_src.s_addr; 612135332Sglebius rec->dst_addr = fle->f.r.r_dst.s_addr; 613135332Sglebius rec->next_hop = fle->f.next_hop.s_addr; 614135332Sglebius rec->i_ifx = htons(fle->f.fle_i_ifx); 615135332Sglebius rec->o_ifx = htons(fle->f.fle_o_ifx); 616135332Sglebius rec->packets = htonl(fle->f.packets); 617135332Sglebius rec->octets = htonl(fle->f.bytes); 618143103Sglebius rec->first = htonl(MILLIUPTIME(fle->f.first)); 619143103Sglebius rec->last = htonl(MILLIUPTIME(fle->f.last)); 620135332Sglebius rec->s_port = fle->f.r.r_sport; 621135332Sglebius rec->d_port = fle->f.r.r_dport; 622135332Sglebius rec->flags = fle->f.tcp_flags; 623135332Sglebius rec->prot = fle->f.r.r_ip_p; 624135332Sglebius rec->tos = fle->f.r.r_tos; 625135332Sglebius rec->dst_mask = fle->f.dst_mask; 626135332Sglebius rec->src_mask = fle->f.src_mask; 627135332Sglebius 628135332Sglebius priv->flow_seq++; 629135332Sglebius 630135332Sglebius if (header->count == NETFLOW_V5_MAX_RECORDS) /* end of datagram */ 631135332Sglebius return export_send(priv); 632135332Sglebius 633135332Sglebius return (0); 634135332Sglebius} 635135332Sglebius 636135332Sglebius/* Periodic flow expiry run. */ 637135332Sglebiusvoid 638135332Sglebiusng_netflow_expire(void *arg) 639135332Sglebius{ 640135332Sglebius register struct flow_entry *fle, *fle1; 641135332Sglebius priv_p priv = (priv_p )arg; 642135332Sglebius uint32_t used; 643135332Sglebius int error = 0; 644135332Sglebius 645135332Sglebius /* First pack actively expired entries */ 646135332Sglebius mtx_lock(&priv->expire_mtx); 647135332Sglebius while (!SLIST_EMPTY(&(priv->expire_list))) { 648135332Sglebius fle = SLIST_FIRST(&(priv->expire_list)); 649135332Sglebius SLIST_REMOVE_HEAD(&(priv->expire_list), fle_free); 650135332Sglebius mtx_unlock(&priv->expire_mtx); 651135332Sglebius 652135332Sglebius /* 653135332Sglebius * While we have dropped the lock, expire_flow() may 654135332Sglebius * insert another flow into top of the list. 655135332Sglebius * This is not harmful for us, since we have already 656135332Sglebius * detached our own. 657135332Sglebius */ 658135332Sglebius 659135332Sglebius if ((error = export_add(priv, fle)) != 0) 660140511Sglebius log(LOG_CRIT, "ng_netflow: export_add() failed: %u\n", 661140511Sglebius error); 662135332Sglebius (void )free_flow(priv, fle); 663135332Sglebius 664135332Sglebius mtx_lock(&priv->expire_mtx); 665135332Sglebius } 666135332Sglebius mtx_unlock(&priv->expire_mtx); 667135332Sglebius 668135332Sglebius NGNF_GETUSED(priv, used); 669135332Sglebius mtx_lock(&priv->work_mtx); 670135332Sglebius TAILQ_FOREACH_SAFE(fle, &(priv->work_queue), fle_work, fle1) { 671135332Sglebius /* 672141343Sglebius * When cache size has not reached CACHELOWAT yet, we keep 673141343Sglebius * both inactive and active flows in cache. Doing this, we 674141343Sglebius * reduce number of exports, since many inactive flows may 675141343Sglebius * wake up and continue their life. However, we make an 676141343Sglebius * exclusion for scans. It is very rare situation that 677141343Sglebius * inactive 1-packet flow will wake up. 678141343Sglebius * When cache has reached CACHELOWAT, we expire all inactive 679141343Sglebius * flows, until cache gets to a sane size. 680135332Sglebius */ 681135332Sglebius if (used <= CACHELOWAT && !INACTIVE(fle)) 682135332Sglebius goto finish; 683135332Sglebius 684141348Sglebius if ((INACTIVE(fle) && (SMALL(fle) || (used > CACHELOWAT))) || 685141348Sglebius AGED(fle)) { 686135332Sglebius 687135332Sglebius /* Detach flow entry from cache */ 688135332Sglebius LIST_REMOVE(fle, fle_hash); 689135332Sglebius TAILQ_REMOVE(&priv->work_queue, fle, fle_work); 690135332Sglebius 691135332Sglebius /* 692135332Sglebius * While we are sending to collector, unlock cache. 693135332Sglebius * XXX: it can happen, however with a small probability, 694141343Sglebius * that item, we are holding now, can be moved to the 695141343Sglebius * top of flow cache by node thread. In this case our 696141343Sglebius * expire thread stops checking. Since this is not 697141343Sglebius * fatal we will just ignore it now. 698135332Sglebius */ 699135332Sglebius mtx_unlock(&priv->work_mtx); 700135332Sglebius 701135332Sglebius if ((error = export_add(priv, fle)) != 0) 702140511Sglebius log(LOG_CRIT, "ng_netflow: export_add() " 703140511Sglebius "failed: %u\n", error); 704135332Sglebius 705135332Sglebius used = free_flow(priv, fle); 706135332Sglebius 707135332Sglebius mtx_lock(&priv->work_mtx); 708135332Sglebius } 709135332Sglebius } 710135332Sglebius 711135332Sglebiusfinish: 712135332Sglebius mtx_unlock(&priv->work_mtx); 713135332Sglebius 714135332Sglebius mtx_assert(&priv->expire_mtx, MA_NOTOWNED); 715135332Sglebius mtx_assert(&priv->free_mtx, MA_NOTOWNED); 716135332Sglebius 717135332Sglebius /* schedule next expire */ 718135332Sglebius callout_reset(&priv->exp_callout, (1*hz), &ng_netflow_expire, 719135332Sglebius (void *)priv); 720135332Sglebius 721135332Sglebius} 722