netflow.c revision 163241
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 163241 2006-10-11 13:28:37Z 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 41146092Sglebius#include <machine/atomic.h> 42146092Sglebius 43135332Sglebius#include <net/if.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 57146092Sglebius#define NBUCKETS (65536) /* must be power of 2 */ 58135332Sglebius 59163238Sglebius/* This hash is for TCP or UDP packets. */ 60163238Sglebius#define FULL_HASH(addr1, addr2, port1, port2) \ 61163238Sglebius (((addr1 ^ (addr1 >> 16) ^ \ 62163238Sglebius htons(addr2 ^ (addr2 >> 16))) ^ \ 63163241Sglebius port1 ^ htons(port2)) & \ 64163238Sglebius (NBUCKETS - 1)) 65135332Sglebius 66163238Sglebius/* This hash is for all other IP packets. */ 67163238Sglebius#define ADDR_HASH(addr1, addr2) \ 68163238Sglebius ((addr1 ^ (addr1 >> 16) ^ \ 69163238Sglebius htons(addr2 ^ (addr2 >> 16))) & \ 70163238Sglebius (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 98146092SglebiusMALLOC_DECLARE(M_NETFLOW_HASH); 99151897SrwatsonMALLOC_DEFINE(M_NETFLOW_HASH, "netflow_hash", "NetFlow hash"); 100135332Sglebius 101146092Sglebiusstatic int export_add(item_p, struct flow_entry *); 102154277Sglebiusstatic int export_send(priv_p, item_p, int flags); 103135332Sglebius 104146092Sglebius/* 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 118146092Sglebius/* This is callback from uma(9), called on alloc. */ 119146092Sglebiusstatic int 120146092Sglebiusuma_ctor_flow(void *mem, int size, void *arg, int how) 121135332Sglebius{ 122146092Sglebius priv_p priv = (priv_p )arg; 123135332Sglebius 124146092Sglebius if (atomic_load_acq_32(&priv->info.nfinfo_used) >= CACHESIZE) 125146092Sglebius return (ENOMEM); 126135332Sglebius 127146092Sglebius atomic_add_32(&priv->info.nfinfo_used, 1); 128146092Sglebius 129146092Sglebius return (0); 130135332Sglebius} 131135332Sglebius 132146092Sglebius/* This is callback from uma(9), called on free. */ 133146092Sglebiusstatic void 134146092Sglebiusuma_dtor_flow(void *mem, int size, void *arg) 135135332Sglebius{ 136146092Sglebius priv_p priv = (priv_p )arg; 137135332Sglebius 138146092Sglebius atomic_subtract_32(&priv->info.nfinfo_used, 1); 139146092Sglebius} 140135332Sglebius 141146092Sglebius/* 142146092Sglebius * Detach export datagram from priv, if there is any. 143146092Sglebius * If there is no, allocate a new one. 144146092Sglebius */ 145146092Sglebiusstatic item_p 146146092Sglebiusget_export_dgram(priv_p priv) 147146092Sglebius{ 148146092Sglebius item_p item = NULL; 149146092Sglebius 150146092Sglebius mtx_lock(&priv->export_mtx); 151146092Sglebius if (priv->export_item != NULL) { 152146092Sglebius item = priv->export_item; 153146092Sglebius priv->export_item = NULL; 154135332Sglebius } 155146092Sglebius mtx_unlock(&priv->export_mtx); 156135332Sglebius 157146092Sglebius if (item == NULL) { 158146092Sglebius struct netflow_v5_export_dgram *dgram; 159146092Sglebius struct mbuf *m; 160135332Sglebius 161146092Sglebius m = m_getcl(M_DONTWAIT, MT_DATA, M_PKTHDR); 162146092Sglebius if (m == NULL) 163146092Sglebius return (NULL); 164146285Sglebius item = ng_package_data(m, NG_NOFLAGS); 165146092Sglebius if (item == NULL) 166146092Sglebius return (NULL); 167146092Sglebius dgram = mtod(m, struct netflow_v5_export_dgram *); 168146092Sglebius dgram->header.count = 0; 169146092Sglebius dgram->header.version = htons(NETFLOW_V5); 170135332Sglebius 171146092Sglebius } 172135332Sglebius 173146092Sglebius return (item); 174135332Sglebius} 175135332Sglebius 176146092Sglebius/* 177146092Sglebius * Re-attach incomplete datagram back to priv. 178146092Sglebius * If there is already another one, then send incomplete. */ 179146092Sglebiusstatic void 180154277Sglebiusreturn_export_dgram(priv_p priv, item_p item, int flags) 181135332Sglebius{ 182146092Sglebius /* 183146092Sglebius * It may happen on SMP, that some thread has already 184146092Sglebius * put its item there, in this case we bail out and 185146092Sglebius * send what we have to collector. 186146092Sglebius */ 187146092Sglebius mtx_lock(&priv->export_mtx); 188146092Sglebius if (priv->export_item == NULL) { 189146092Sglebius priv->export_item = item; 190146092Sglebius mtx_unlock(&priv->export_mtx); 191146092Sglebius } else { 192146092Sglebius mtx_unlock(&priv->export_mtx); 193154277Sglebius export_send(priv, item, flags); 194146092Sglebius } 195135332Sglebius} 196135332Sglebius 197146092Sglebius/* 198146092Sglebius * The flow is over. Call export_add() and free it. If datagram is 199146092Sglebius * full, then call export_send(). 200146092Sglebius */ 201135332Sglebiusstatic __inline void 202154277Sglebiusexpire_flow(priv_p priv, item_p *item, struct flow_entry *fle, int flags) 203135332Sglebius{ 204146092Sglebius if (*item == NULL) 205146092Sglebius *item = get_export_dgram(priv); 206146092Sglebius if (*item == NULL) { 207146139Sglebius atomic_add_32(&priv->info.nfinfo_export_failed, 1); 208146092Sglebius uma_zfree_arg(priv->zone, fle, priv); 209146092Sglebius return; 210146092Sglebius } 211146092Sglebius if (export_add(*item, fle) > 0) { 212154277Sglebius export_send(priv, *item, flags); 213146092Sglebius *item = NULL; 214146092Sglebius } 215146092Sglebius uma_zfree_arg(priv->zone, fle, priv); 216135332Sglebius} 217135332Sglebius 218135332Sglebius/* Get a snapshot of node statistics */ 219135332Sglebiusvoid 220135332Sglebiusng_netflow_copyinfo(priv_p priv, struct ng_netflow_info *i) 221135332Sglebius{ 222146092Sglebius /* XXX: atomic */ 223135332Sglebius memcpy((void *)i, (void *)&priv->info, sizeof(priv->info)); 224135332Sglebius} 225135332Sglebius 226135332Sglebius/* 227135332Sglebius * Insert a record into defined slot. 228135332Sglebius * 229135332Sglebius * First we get for us a free flow entry, then fill in all 230146092Sglebius * possible fields in it. 231146092Sglebius * 232146092Sglebius * TODO: consider dropping hash mutex while filling in datagram, 233146092Sglebius * as this was done in previous version. Need to test & profile 234146092Sglebius * to be sure. 235135332Sglebius */ 236135332Sglebiusstatic __inline int 237146092Sglebiushash_insert(priv_p priv, struct flow_hash_entry *hsh, struct flow_rec *r, 238146092Sglebius int plen, uint8_t tcp_flags) 239135332Sglebius{ 240135332Sglebius struct flow_entry *fle; 241135332Sglebius struct route ro; 242135332Sglebius struct sockaddr_in *sin; 243135332Sglebius 244146092Sglebius mtx_assert(&hsh->mtx, MA_OWNED); 245146092Sglebius 246146092Sglebius fle = uma_zalloc_arg(priv->zone, priv, M_NOWAIT); 247146092Sglebius if (fle == NULL) { 248146139Sglebius atomic_add_32(&priv->info.nfinfo_alloc_failed, 1); 249135332Sglebius return (ENOMEM); 250146092Sglebius } 251135332Sglebius 252135332Sglebius /* 253135332Sglebius * Now fle is totally ours. It is detached from all lists, 254135332Sglebius * we can safely edit it. 255135332Sglebius */ 256135332Sglebius 257135332Sglebius bcopy(r, &fle->f.r, sizeof(struct flow_rec)); 258135332Sglebius fle->f.bytes = plen; 259135332Sglebius fle->f.packets = 1; 260143890Sglebius fle->f.tcp_flags = tcp_flags; 261135332Sglebius 262135332Sglebius fle->f.first = fle->f.last = time_uptime; 263135332Sglebius 264135332Sglebius /* 265135332Sglebius * First we do route table lookup on destination address. So we can 266135332Sglebius * fill in out_ifx, dst_mask, nexthop, and dst_as in future releases. 267135332Sglebius */ 268135332Sglebius bzero((caddr_t)&ro, sizeof(ro)); 269135332Sglebius sin = (struct sockaddr_in *)&ro.ro_dst; 270135332Sglebius sin->sin_len = sizeof(*sin); 271135332Sglebius sin->sin_family = AF_INET; 272135332Sglebius sin->sin_addr = fle->f.r.r_dst; 273135332Sglebius rtalloc_ign(&ro, RTF_CLONING); 274135332Sglebius if (ro.ro_rt != NULL) { 275135332Sglebius struct rtentry *rt = ro.ro_rt; 276135332Sglebius 277135332Sglebius fle->f.fle_o_ifx = rt->rt_ifp->if_index; 278135332Sglebius 279135332Sglebius if (rt->rt_flags & RTF_GATEWAY && 280135332Sglebius rt->rt_gateway->sa_family == AF_INET) 281135332Sglebius fle->f.next_hop = 282135332Sglebius ((struct sockaddr_in *)(rt->rt_gateway))->sin_addr; 283135332Sglebius 284135332Sglebius if (rt_mask(rt)) 285163234Sglebius fle->f.dst_mask = bitcount32(((struct sockaddr_in *) 286163234Sglebius rt_mask(rt))->sin_addr.s_addr); 287135332Sglebius else if (rt->rt_flags & RTF_HOST) 288135332Sglebius /* Give up. We can't determine mask :( */ 289135332Sglebius fle->f.dst_mask = 32; 290135332Sglebius 291135332Sglebius RTFREE(ro.ro_rt); 292135332Sglebius } 293135332Sglebius 294135332Sglebius /* Do route lookup on source address, to fill in src_mask. */ 295135332Sglebius 296135332Sglebius bzero((caddr_t)&ro, sizeof(ro)); 297135332Sglebius sin = (struct sockaddr_in *)&ro.ro_dst; 298135332Sglebius sin->sin_len = sizeof(*sin); 299135332Sglebius sin->sin_family = AF_INET; 300135332Sglebius sin->sin_addr = fle->f.r.r_src; 301135332Sglebius rtalloc_ign(&ro, RTF_CLONING); 302135332Sglebius if (ro.ro_rt != NULL) { 303135332Sglebius struct rtentry *rt = ro.ro_rt; 304135332Sglebius 305135332Sglebius if (rt_mask(rt)) 306163234Sglebius fle->f.src_mask = bitcount32(((struct sockaddr_in *) 307163234Sglebius rt_mask(rt))->sin_addr.s_addr); 308135332Sglebius else if (rt->rt_flags & RTF_HOST) 309135332Sglebius /* Give up. We can't determine mask :( */ 310135332Sglebius fle->f.src_mask = 32; 311135332Sglebius 312135332Sglebius RTFREE(ro.ro_rt); 313135332Sglebius } 314135332Sglebius 315146092Sglebius /* Push new flow at the and of hash. */ 316146092Sglebius TAILQ_INSERT_TAIL(&hsh->head, fle, fle_hash); 317135332Sglebius 318135332Sglebius return (0); 319135332Sglebius} 320135332Sglebius 321135332Sglebius 322135332Sglebius/* 323135332Sglebius * Non-static functions called from ng_netflow.c 324135332Sglebius */ 325135332Sglebius 326135332Sglebius/* Allocate memory and set up flow cache */ 327135332Sglebiusint 328135332Sglebiusng_netflow_cache_init(priv_p priv) 329135332Sglebius{ 330146092Sglebius struct flow_hash_entry *hsh; 331135332Sglebius int i; 332135332Sglebius 333146092Sglebius /* Initialize cache UMA zone. */ 334146092Sglebius priv->zone = uma_zcreate("NetFlow cache", sizeof(struct flow_entry), 335146092Sglebius uma_ctor_flow, uma_dtor_flow, NULL, NULL, UMA_ALIGN_CACHE, 0); 336146092Sglebius uma_zone_set_max(priv->zone, CACHESIZE); 337135332Sglebius 338146092Sglebius /* Allocate hash. */ 339135332Sglebius MALLOC(priv->hash, struct flow_hash_entry *, 340135332Sglebius NBUCKETS * sizeof(struct flow_hash_entry), 341146092Sglebius M_NETFLOW_HASH, M_WAITOK | M_ZERO); 342135332Sglebius 343139374Sglebius if (priv->hash == NULL) { 344146092Sglebius uma_zdestroy(priv->zone); 345135332Sglebius return (ENOMEM); 346139374Sglebius } 347135332Sglebius 348146092Sglebius /* Initialize hash. */ 349146092Sglebius for (i = 0, hsh = priv->hash; i < NBUCKETS; i++, hsh++) { 350146092Sglebius mtx_init(&hsh->mtx, "hash mutex", NULL, MTX_DEF); 351146092Sglebius TAILQ_INIT(&hsh->head); 352146092Sglebius } 353135332Sglebius 354146092Sglebius mtx_init(&priv->export_mtx, "export dgram lock", NULL, MTX_DEF); 355135332Sglebius 356135332Sglebius return (0); 357135332Sglebius} 358135332Sglebius 359135332Sglebius/* Free all flow cache memory. Called from node close method. */ 360135332Sglebiusvoid 361135332Sglebiusng_netflow_cache_flush(priv_p priv) 362135332Sglebius{ 363146092Sglebius struct flow_entry *fle, *fle1; 364146092Sglebius struct flow_hash_entry *hsh; 365146092Sglebius item_p item = NULL; 366135332Sglebius int i; 367135332Sglebius 368135332Sglebius /* 369135332Sglebius * We are going to free probably billable data. 370135332Sglebius * Expire everything before freeing it. 371135332Sglebius * No locking is required since callout is already drained. 372135332Sglebius */ 373146092Sglebius for (hsh = priv->hash, i = 0; i < NBUCKETS; hsh++, i++) 374146092Sglebius TAILQ_FOREACH_SAFE(fle, &hsh->head, fle_hash, fle1) { 375146092Sglebius TAILQ_REMOVE(&hsh->head, fle, fle_hash); 376154277Sglebius expire_flow(priv, &item, fle, NG_QUEUE); 377146092Sglebius } 378135332Sglebius 379146092Sglebius if (item != NULL) 380154277Sglebius export_send(priv, item, NG_QUEUE); 381135332Sglebius 382146092Sglebius uma_zdestroy(priv->zone); 383135332Sglebius 384146092Sglebius /* Destroy hash mutexes. */ 385146092Sglebius for (i = 0, hsh = priv->hash; i < NBUCKETS; i++, hsh++) 386146092Sglebius mtx_destroy(&hsh->mtx); 387146092Sglebius 388146092Sglebius /* Free hash memory. */ 389135332Sglebius if (priv->hash) 390146092Sglebius FREE(priv->hash, M_NETFLOW_HASH); 391135332Sglebius 392146092Sglebius mtx_destroy(&priv->export_mtx); 393135332Sglebius} 394135332Sglebius 395146092Sglebius/* Insert packet from into flow cache. */ 396135332Sglebiusint 397143923Sglebiusng_netflow_flow_add(priv_p priv, struct ip *ip, iface_p iface, 398143923Sglebius struct ifnet *ifp) 399135332Sglebius{ 400146092Sglebius register struct flow_entry *fle, *fle1; 401146092Sglebius struct flow_hash_entry *hsh; 402135332Sglebius struct flow_rec r; 403146092Sglebius item_p item = NULL; 404143923Sglebius int hlen, plen; 405146092Sglebius int error = 0; 406135332Sglebius uint8_t tcp_flags = 0; 407135332Sglebius 408143923Sglebius /* Try to fill flow_rec r */ 409135332Sglebius bzero(&r, sizeof(r)); 410143923Sglebius /* check version */ 411143923Sglebius if (ip->ip_v != IPVERSION) 412143923Sglebius return (EINVAL); 413135332Sglebius 414143923Sglebius /* verify min header length */ 415143923Sglebius hlen = ip->ip_hl << 2; 416143923Sglebius 417143923Sglebius if (hlen < sizeof(struct ip)) 418143923Sglebius return (EINVAL); 419143923Sglebius 420143923Sglebius r.r_src = ip->ip_src; 421143923Sglebius r.r_dst = ip->ip_dst; 422143923Sglebius 423143923Sglebius /* save packet length */ 424143923Sglebius plen = ntohs(ip->ip_len); 425143923Sglebius 426143923Sglebius r.r_ip_p = ip->ip_p; 427143923Sglebius r.r_tos = ip->ip_tos; 428143923Sglebius 429143923Sglebius /* Configured in_ifx overrides mbuf's */ 430143923Sglebius if (iface->info.ifinfo_index == 0) { 431143923Sglebius if (ifp != NULL) 432143923Sglebius r.r_i_ifx = ifp->if_index; 433143923Sglebius } else 434143923Sglebius r.r_i_ifx = iface->info.ifinfo_index; 435143923Sglebius 436143923Sglebius /* 437143923Sglebius * XXX NOTE: only first fragment of fragmented TCP, UDP and 438143923Sglebius * ICMP packet will be recorded with proper s_port and d_port. 439143923Sglebius * Following fragments will be recorded simply as IP packet with 440143923Sglebius * ip_proto = ip->ip_p and s_port, d_port set to zero. 441143923Sglebius * I know, it looks like bug. But I don't want to re-implement 442143923Sglebius * ip packet assebmling here. Anyway, (in)famous trafd works this way - 443143923Sglebius * and nobody complains yet :) 444143923Sglebius */ 445144901Sglebius if ((ip->ip_off & htons(IP_OFFMASK)) == 0) 446144901Sglebius switch(r.r_ip_p) { 447144901Sglebius case IPPROTO_TCP: 448144901Sglebius { 449144901Sglebius register struct tcphdr *tcp; 450143923Sglebius 451144901Sglebius tcp = (struct tcphdr *)((caddr_t )ip + hlen); 452144901Sglebius r.r_sport = tcp->th_sport; 453144901Sglebius r.r_dport = tcp->th_dport; 454144901Sglebius tcp_flags = tcp->th_flags; 455144901Sglebius break; 456144901Sglebius } 457144901Sglebius case IPPROTO_UDP: 458144901Sglebius r.r_ports = *(uint32_t *)((caddr_t )ip + hlen); 459144901Sglebius break; 460144901Sglebius } 461143923Sglebius 462146092Sglebius /* Update node statistics. XXX: race... */ 463139374Sglebius priv->info.nfinfo_packets ++; 464139374Sglebius priv->info.nfinfo_bytes += plen; 465139374Sglebius 466146092Sglebius /* Find hash slot. */ 467146092Sglebius hsh = &priv->hash[ip_hash(&r)]; 468135332Sglebius 469146092Sglebius mtx_lock(&hsh->mtx); 470135332Sglebius 471146092Sglebius /* 472146092Sglebius * Go through hash and find our entry. If we encounter an 473146092Sglebius * entry, that should be expired, purge it. We do a reverse 474146092Sglebius * search since most active entries are first, and most 475146092Sglebius * searches are done on most active entries. 476146092Sglebius */ 477146092Sglebius TAILQ_FOREACH_REVERSE_SAFE(fle, &hsh->head, fhead, fle_hash, fle1) { 478146092Sglebius if (bcmp(&r, &fle->f.r, sizeof(struct flow_rec)) == 0) 479146092Sglebius break; 480146092Sglebius if ((INACTIVE(fle) && SMALL(fle)) || AGED(fle)) { 481146092Sglebius TAILQ_REMOVE(&hsh->head, fle, fle_hash); 482154277Sglebius expire_flow(priv, &item, fle, NG_QUEUE); 483146092Sglebius atomic_add_32(&priv->info.nfinfo_act_exp, 1); 484146092Sglebius } 485146092Sglebius } 486135332Sglebius 487146092Sglebius if (fle) { /* An existent entry. */ 488146092Sglebius 489135332Sglebius fle->f.bytes += plen; 490135332Sglebius fle->f.packets ++; 491135332Sglebius fle->f.tcp_flags |= tcp_flags; 492135332Sglebius fle->f.last = time_uptime; 493135332Sglebius 494135332Sglebius /* 495135332Sglebius * We have the following reasons to expire flow in active way: 496135332Sglebius * - it hit active timeout 497135332Sglebius * - a TCP connection closed 498135332Sglebius * - it is going to overflow counter 499135332Sglebius */ 500135332Sglebius if (tcp_flags & TH_FIN || tcp_flags & TH_RST || AGED(fle) || 501146092Sglebius (fle->f.bytes >= (UINT_MAX - IF_MAXMTU)) ) { 502146092Sglebius TAILQ_REMOVE(&hsh->head, fle, fle_hash); 503154277Sglebius expire_flow(priv, &item, fle, NG_QUEUE); 504154277Sglebius atomic_add_32(&priv->info.nfinfo_act_exp, 1); 505146092Sglebius } else { 506146092Sglebius /* 507146092Sglebius * It is the newest, move it to the tail, 508146092Sglebius * if it isn't there already. Next search will 509146092Sglebius * locate it quicker. 510146092Sglebius */ 511146092Sglebius if (fle != TAILQ_LAST(&hsh->head, fhead)) { 512146092Sglebius TAILQ_REMOVE(&hsh->head, fle, fle_hash); 513146092Sglebius TAILQ_INSERT_TAIL(&hsh->head, fle, fle_hash); 514146092Sglebius } 515146092Sglebius } 516146092Sglebius } else /* A new flow entry. */ 517146092Sglebius error = hash_insert(priv, hsh, &r, plen, tcp_flags); 518135332Sglebius 519146092Sglebius mtx_unlock(&hsh->mtx); 520135332Sglebius 521146092Sglebius if (item != NULL) 522154277Sglebius return_export_dgram(priv, item, NG_QUEUE); 523135332Sglebius 524146092Sglebius return (error); 525135332Sglebius} 526135332Sglebius 527135332Sglebius/* 528146092Sglebius * Return records from cache to userland. 529135332Sglebius * 530135332Sglebius * TODO: matching particular IP should be done in kernel, here. 531135332Sglebius */ 532135332Sglebiusint 533135332Sglebiusng_netflow_flow_show(priv_p priv, uint32_t last, struct ng_mesg *resp) 534135332Sglebius{ 535146092Sglebius struct flow_hash_entry *hsh; 536135332Sglebius struct flow_entry *fle; 537135332Sglebius struct ngnf_flows *data; 538146092Sglebius int i; 539135332Sglebius 540135332Sglebius data = (struct ngnf_flows *)resp->data; 541135332Sglebius data->last = 0; 542135332Sglebius data->nentries = 0; 543135332Sglebius 544135332Sglebius /* Check if this is a first run */ 545146092Sglebius if (last == 0) { 546146092Sglebius hsh = priv->hash; 547146092Sglebius i = 0; 548146092Sglebius } else { 549146092Sglebius if (last > NBUCKETS-1) 550135332Sglebius return (EINVAL); 551146092Sglebius hsh = priv->hash + last; 552146092Sglebius i = last; 553135332Sglebius } 554135332Sglebius 555135332Sglebius /* 556135332Sglebius * We will transfer not more than NREC_AT_ONCE. More data 557135332Sglebius * will come in next message. 558146092Sglebius * We send current hash index to userland, and userland should 559146092Sglebius * return it back to us. Then, we will restart with new entry. 560146092Sglebius * 561146092Sglebius * The resulting cache snapshot is inaccurate for the 562146092Sglebius * following reasons: 563146092Sglebius * - we skip locked hash entries 564146092Sglebius * - we bail out, if someone wants our entry 565146092Sglebius * - we skip rest of entry, when hit NREC_AT_ONCE 566135332Sglebius */ 567146092Sglebius for (; i < NBUCKETS; hsh++, i++) { 568146092Sglebius if (mtx_trylock(&hsh->mtx) == 0) 569135332Sglebius continue; 570146092Sglebius 571146092Sglebius TAILQ_FOREACH(fle, &hsh->head, fle_hash) { 572146092Sglebius if (hsh->mtx.mtx_lock & MTX_CONTESTED) 573146092Sglebius break; 574146092Sglebius 575146092Sglebius bcopy(&fle->f, &(data->entries[data->nentries]), 576146092Sglebius sizeof(fle->f)); 577146092Sglebius data->nentries++; 578146092Sglebius if (data->nentries == NREC_AT_ONCE) { 579146092Sglebius mtx_unlock(&hsh->mtx); 580146092Sglebius if (++i < NBUCKETS) 581146092Sglebius data->last = i; 582146092Sglebius return (0); 583146092Sglebius } 584135332Sglebius } 585146092Sglebius mtx_unlock(&hsh->mtx); 586146092Sglebius } 587135332Sglebius 588135332Sglebius return (0); 589135332Sglebius} 590135332Sglebius 591135332Sglebius/* We have full datagram in privdata. Send it to export hook. */ 592135332Sglebiusstatic int 593154277Sglebiusexport_send(priv_p priv, item_p item, int flags) 594135332Sglebius{ 595146092Sglebius struct mbuf *m = NGI_M(item); 596146092Sglebius struct netflow_v5_export_dgram *dgram = mtod(m, 597146092Sglebius struct netflow_v5_export_dgram *); 598146092Sglebius struct netflow_v5_header *header = &dgram->header; 599135332Sglebius struct timespec ts; 600135332Sglebius int error = 0; 601135332Sglebius 602146092Sglebius /* Fill mbuf header. */ 603146092Sglebius m->m_len = m->m_pkthdr.len = sizeof(struct netflow_v5_record) * 604146092Sglebius header->count + sizeof(struct netflow_v5_header); 605146092Sglebius 606146092Sglebius /* Fill export header. */ 607143103Sglebius header->sys_uptime = htonl(MILLIUPTIME(time_uptime)); 608135332Sglebius getnanotime(&ts); 609135332Sglebius header->unix_secs = htonl(ts.tv_sec); 610135332Sglebius header->unix_nsecs = htonl(ts.tv_nsec); 611158027Smaxim header->engine_type = 0; 612158027Smaxim header->engine_id = 0; 613158027Smaxim header->pad = 0; 614152847Sglebius header->flow_seq = htonl(atomic_fetchadd_32(&priv->flow_seq, 615152847Sglebius header->count)); 616135332Sglebius header->count = htons(header->count); 617135332Sglebius 618146092Sglebius if (priv->export != NULL) 619146092Sglebius /* Should also NET_LOCK_GIANT(). */ 620154277Sglebius NG_FWD_ITEM_HOOK_FLAGS(error, item, priv->export, flags); 621135332Sglebius 622135332Sglebius return (error); 623135332Sglebius} 624135332Sglebius 625135332Sglebius 626146092Sglebius/* Add export record to dgram. */ 627135332Sglebiusstatic int 628146092Sglebiusexport_add(item_p item, struct flow_entry *fle) 629135332Sglebius{ 630146092Sglebius struct netflow_v5_export_dgram *dgram = mtod(NGI_M(item), 631146092Sglebius struct netflow_v5_export_dgram *); 632146092Sglebius struct netflow_v5_header *header = &dgram->header; 633135332Sglebius struct netflow_v5_record *rec; 634135332Sglebius 635135332Sglebius if (header->count == 0 ) { /* first record */ 636146092Sglebius rec = &dgram->r[0]; 637135332Sglebius header->count = 1; 638135332Sglebius } else { /* continue filling datagram */ 639146092Sglebius rec = &dgram->r[header->count]; 640135332Sglebius header->count ++; 641135332Sglebius } 642135332Sglebius 643146092Sglebius KASSERT(header->count <= NETFLOW_V5_MAX_RECORDS, 644146092Sglebius ("ng_netflow: export too big")); 645146092Sglebius 646146092Sglebius /* Fill in export record. */ 647135332Sglebius rec->src_addr = fle->f.r.r_src.s_addr; 648135332Sglebius rec->dst_addr = fle->f.r.r_dst.s_addr; 649135332Sglebius rec->next_hop = fle->f.next_hop.s_addr; 650135332Sglebius rec->i_ifx = htons(fle->f.fle_i_ifx); 651135332Sglebius rec->o_ifx = htons(fle->f.fle_o_ifx); 652135332Sglebius rec->packets = htonl(fle->f.packets); 653135332Sglebius rec->octets = htonl(fle->f.bytes); 654143103Sglebius rec->first = htonl(MILLIUPTIME(fle->f.first)); 655143103Sglebius rec->last = htonl(MILLIUPTIME(fle->f.last)); 656135332Sglebius rec->s_port = fle->f.r.r_sport; 657135332Sglebius rec->d_port = fle->f.r.r_dport; 658135332Sglebius rec->flags = fle->f.tcp_flags; 659135332Sglebius rec->prot = fle->f.r.r_ip_p; 660135332Sglebius rec->tos = fle->f.r.r_tos; 661135332Sglebius rec->dst_mask = fle->f.dst_mask; 662135332Sglebius rec->src_mask = fle->f.src_mask; 663135332Sglebius 664146092Sglebius /* Not supported fields. */ 665146092Sglebius rec->src_as = rec->dst_as = 0; 666135332Sglebius 667146092Sglebius if (header->count == NETFLOW_V5_MAX_RECORDS) 668146092Sglebius return (1); /* end of datagram */ 669146092Sglebius else 670146092Sglebius return (0); 671135332Sglebius} 672135332Sglebius 673135332Sglebius/* Periodic flow expiry run. */ 674135332Sglebiusvoid 675135332Sglebiusng_netflow_expire(void *arg) 676135332Sglebius{ 677146092Sglebius struct flow_entry *fle, *fle1; 678146092Sglebius struct flow_hash_entry *hsh; 679146092Sglebius priv_p priv = (priv_p )arg; 680146092Sglebius item_p item = NULL; 681146092Sglebius uint32_t used; 682146092Sglebius int i; 683135332Sglebius 684146092Sglebius /* 685146092Sglebius * Going through all the cache. 686146092Sglebius */ 687146092Sglebius for (hsh = priv->hash, i = 0; i < NBUCKETS; hsh++, i++) { 688135332Sglebius /* 689146092Sglebius * Skip entries, that are already being worked on. 690135332Sglebius */ 691146092Sglebius if (mtx_trylock(&hsh->mtx) == 0) 692146092Sglebius continue; 693135332Sglebius 694146092Sglebius used = atomic_load_acq_32(&priv->info.nfinfo_used); 695146092Sglebius TAILQ_FOREACH_SAFE(fle, &hsh->head, fle_hash, fle1) { 696146092Sglebius /* 697146092Sglebius * Interrupt thread wants this entry! 698146092Sglebius * Quick! Quick! Bail out! 699146092Sglebius */ 700146092Sglebius if (hsh->mtx.mtx_lock & MTX_CONTESTED) 701146092Sglebius break; 702135332Sglebius 703135332Sglebius /* 704146092Sglebius * Don't expire aggressively while hash collision 705146092Sglebius * ratio is predicted small. 706135332Sglebius */ 707146092Sglebius if (used <= (NBUCKETS*2) && !INACTIVE(fle)) 708146092Sglebius break; 709135332Sglebius 710163239Sglebius if ((INACTIVE(fle) && (SMALL(fle) || 711163240Sglebius (used > (NBUCKETS*2)))) || AGED(fle)) { 712146092Sglebius TAILQ_REMOVE(&hsh->head, fle, fle_hash); 713154277Sglebius expire_flow(priv, &item, fle, NG_NOFLAGS); 714146092Sglebius used--; 715146092Sglebius atomic_add_32(&priv->info.nfinfo_inact_exp, 1); 716146092Sglebius } 717135332Sglebius } 718146092Sglebius mtx_unlock(&hsh->mtx); 719146092Sglebius } 720135332Sglebius 721146092Sglebius if (item != NULL) 722154277Sglebius return_export_dgram(priv, item, NG_NOFLAGS); 723135332Sglebius 724146092Sglebius /* Schedule next expire. */ 725135332Sglebius callout_reset(&priv->exp_callout, (1*hz), &ng_netflow_expire, 726135332Sglebius (void *)priv); 727135332Sglebius} 728