1 /*- 2 * Copyright (c) 2004 Gleb Smirnoff <glebius@cell.sick.ru> 3 * Copyright (c) 2001-2003 Roman V. Palagin <romanp@unshadow.net> 4 * All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions 8 * are met: 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 3. All advertising materials mentioning features or use of this software 15 * must display the following acknowledgement: 16 * This product includes software developed by Gleb Smirnoff and 17 * contributors. 18 * 4. Neither the name of the author nor the names of its contributors 19 * may be used to endorse or promote products derived from this software 20 * without specific prior written permission. 21 * 22 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 23 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 24 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 25 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 26 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 27 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 28 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 29 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 30 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 31 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 32 * SUCH DAMAGE. 33 * 34 * $SourceForge: netflow.c,v 1.41 2004/09/05 11:41:10 glebius Exp $ 35 */ 36 37 static const char rcs_id[] = 38 "@(#) $FreeBSD$"; 39 40 #include <sys/param.h> 41 #include <sys/kernel.h> 42 #include <sys/limits.h> 43 #include <sys/mbuf.h> 44 #include <sys/systm.h> 45 #include <sys/socket.h> 46 47 #include <net/if.h> 48 #include <net/if_var.h> 49 #include <net/if_dl.h> 50 #include <net/route.h> 51 #include <netinet/in.h> 52 #include <netinet/in_systm.h> 53 #include <netinet/ip.h> 54 #include <netinet/tcp.h> 55 #include <netinet/udp.h> 56 57 #include <netgraph/ng_message.h> 58 #include <netgraph/netgraph.h> 59 60 #include <netgraph/netflow/netflow.h> 61 #include <netgraph/netflow/ng_netflow.h> 62 63 #define NBUCKETS (4096) /* must be power of 2 */ 64 65 /* This hash is for TCP or UDP packets */ 66 #define FULL_HASH(addr1,addr2,port1,port2)\ 67 (((addr1 >> 16) ^ \ 68 (addr2 & 0x00FF) ^ \ 69 ((port1 ^ port2) << 8) )& \ 70 (NBUCKETS - 1)) 71 72 /* This hash for all other IP packets */ 73 #define ADDR_HASH(addr1,addr2)\ 74 (((addr1 >> 16) ^ \ 75 (addr2 & 0x00FF) )& \ 76 (NBUCKETS - 1)) 77 78 /* Macros to shorten logical constructions */ 79 /* XXX: priv must exist in namespace */ 80 #define INACTIVE(fle) (time_uptime - fle->f.last > priv->info.nfinfo_inact_t) 81 #define AGED(fle) (time_uptime - fle->f.first > priv->info.nfinfo_act_t) 82 #define ISFREE(fle) (fle->f.packets == 0) 83 84 /* 85 * 4 is a magical number: statistically number of 4-packet flows is 86 * bigger than 5,6,7...-packet flows by an order of magnitude. Most UDP/ICMP 87 * scans are 1 packet (~ 90% of flow cache). TCP scans are 2-packet in case 88 * of reachable host and 4-packet otherwise. 89 */ 90 #define SMALL(fle) (fle->f.packets <= 4) 91 92 MALLOC_DECLARE(M_NETFLOW); 93 MALLOC_DEFINE(M_NETFLOW, "NetFlow", "flow cache"); 94 95 static int export_add(priv_p , struct flow_entry *); 96 static int export_send(priv_p ); 97 98 /* Generate hash for a given flow record */ 99 static __inline uint32_t 100 ip_hash(struct flow_rec *r) 101 { 102 switch (r->r_ip_p) { 103 case IPPROTO_TCP: 104 case IPPROTO_UDP: 105 return FULL_HASH(r->r_src.s_addr, r->r_dst.s_addr, 106 r->r_sport, r->r_dport); 107 default: 108 return ADDR_HASH(r->r_src.s_addr, r->r_dst.s_addr); 109 } 110 } 111 112 /* Lookup for record in given slot */ 113 static __inline struct flow_entry * 114 hash_lookup(struct flow_hash_entry *h, int slot, struct flow_rec *r) 115 { 116 struct flow_entry *fle; 117 118 LIST_FOREACH(fle, &(h[slot].head), fle_hash) 119 if (bcmp(r, &fle->f.r, sizeof(struct flow_rec)) == 0) 120 return (fle); 121 122 return (NULL); 123 } 124 125 /* Get a flow entry from free list */ 126 static __inline struct flow_entry * 127 alloc_flow(priv_p priv, int *flows) 128 { 129 register struct flow_entry *fle; 130 131 mtx_lock(&priv->free_mtx); 132 133 if (SLIST_EMPTY(&priv->free_list)) { 134 mtx_unlock(&priv->free_mtx); 135 return(NULL); 136 } 137 138 fle = SLIST_FIRST(&priv->free_list); 139 SLIST_REMOVE_HEAD(&priv->free_list, fle_free); 140 141 priv->info.nfinfo_used++; 142 priv->info.nfinfo_free--; 143 144 if (flows != NULL) 145 *flows = priv->info.nfinfo_used; 146 147 mtx_unlock(&priv->free_mtx); 148 149 return (fle); 150 } 151 152 /* Insert flow entry into a free list. */ 153 static __inline int 154 free_flow(priv_p priv, struct flow_entry *fle) 155 { 156 int flows; 157 158 mtx_lock(&priv->free_mtx); 159 fle->f.packets = 0; 160 SLIST_INSERT_HEAD(&priv->free_list, fle, fle_free); 161 flows = priv->info.nfinfo_used--; 162 priv->info.nfinfo_free++; 163 mtx_unlock(&priv->free_mtx); 164 165 return flows; 166 } 167 168 #define NGNF_GETUSED(priv, rval) do { \ 169 mtx_lock(&priv->free_mtx); \ 170 rval = priv->info.nfinfo_used; \ 171 mtx_unlock(&priv->free_mtx); \ 172 } while (0) 173 174 /* Insert flow entry into expire list. */ 175 /* XXX: Flow must be detached from work queue, but not from cache */ 176 static __inline void 177 expire_flow(priv_p priv, struct flow_entry *fle) 178 { 179 mtx_assert(&priv->work_mtx, MA_OWNED); 180 LIST_REMOVE(fle, fle_hash); 181 182 mtx_lock(&priv->expire_mtx); 183 SLIST_INSERT_HEAD(&priv->expire_list, fle, fle_free); 184 mtx_unlock(&priv->expire_mtx); 185 } 186 187 /* Get a snapshot of node statistics */ 188 void 189 ng_netflow_copyinfo(priv_p priv, struct ng_netflow_info *i) 190 { 191 mtx_lock(&priv->free_mtx); 192 memcpy((void *)i, (void *)&priv->info, sizeof(priv->info)); 193 mtx_unlock(&priv->free_mtx); 194 } 195 196 /* Calculate number of bits in netmask */ 197 #define g21 0x55555555ul /* = 0101_0101_0101_0101_0101_0101_0101_0101 */ 198 #define g22 0x33333333ul /* = 0011_0011_0011_0011_0011_0011_0011_0011 */ 199 #define g23 0x0f0f0f0ful /* = 0000_1111_0000_1111_0000_1111_0000_1111 */ 200 static __inline u_char 201 bit_count(uint32_t v) 202 { 203 v = (v & g21) + ((v >> 1) & g21); 204 v = (v & g22) + ((v >> 2) & g22); 205 v = (v + (v >> 4)) & g23; 206 return (v + (v >> 8) + (v >> 16) + (v >> 24)) & 0x3f; 207 } 208 209 /* 210 * Insert a record into defined slot. 211 * 212 * First we get for us a free flow entry, then fill in all 213 * possible fields in it. Then obtain lock on flow cache 214 * and insert flow entry. 215 */ 216 static __inline int 217 hash_insert(priv_p priv, int slot, struct flow_rec *r, int plen) 218 { 219 struct flow_hash_entry *h = priv->hash; 220 struct flow_entry *fle; 221 struct route ro; 222 struct sockaddr_in *sin; 223 224 fle = alloc_flow(priv, NULL); 225 if (fle == NULL) 226 return (ENOMEM); 227 228 /* 229 * Now fle is totally ours. It is detached from all lists, 230 * we can safely edit it. 231 */ 232 233 bcopy(r, &fle->f.r, sizeof(struct flow_rec)); 234 fle->f.bytes = plen; 235 fle->f.packets = 1; 236 237 priv->info.nfinfo_bytes += plen; 238 239 fle->f.first = fle->f.last = time_uptime; 240 241 /* 242 * First we do route table lookup on destination address. So we can 243 * fill in out_ifx, dst_mask, nexthop, and dst_as in future releases. 244 */ 245 bzero((caddr_t)&ro, sizeof(ro)); 246 sin = (struct sockaddr_in *)&ro.ro_dst; 247 sin->sin_len = sizeof(*sin); 248 sin->sin_family = AF_INET; 249 sin->sin_addr = fle->f.r.r_dst; 250 rtalloc_ign(&ro, RTF_CLONING); 251 if (ro.ro_rt != NULL) { 252 struct rtentry *rt = ro.ro_rt; 253 254 fle->f.fle_o_ifx = rt->rt_ifp->if_index; 255 256 if (rt->rt_flags & RTF_GATEWAY && 257 rt->rt_gateway->sa_family == AF_INET) 258 fle->f.next_hop = 259 ((struct sockaddr_in *)(rt->rt_gateway))->sin_addr; 260 261 if (rt_mask(rt)) 262 fle->f.dst_mask = 263 bit_count(((struct sockaddr_in *)rt_mask(rt))->sin_addr.s_addr); 264 else if (rt->rt_flags & RTF_HOST) 265 /* Give up. We can't determine mask :( */ 266 fle->f.dst_mask = 32; 267 268 RTFREE(ro.ro_rt); 269 } 270 271 /* Do route lookup on source address, to fill in src_mask. */ 272 273 bzero((caddr_t)&ro, sizeof(ro)); 274 sin = (struct sockaddr_in *)&ro.ro_dst; 275 sin->sin_len = sizeof(*sin); 276 sin->sin_family = AF_INET; 277 sin->sin_addr = fle->f.r.r_src; 278 rtalloc_ign(&ro, RTF_CLONING); 279 if (ro.ro_rt != NULL) { 280 struct rtentry *rt = ro.ro_rt; 281 282 if (rt_mask(rt)) 283 fle->f.src_mask = 284 bit_count(((struct sockaddr_in *)rt_mask(rt))->sin_addr.s_addr); 285 else if (rt->rt_flags & RTF_HOST) 286 /* Give up. We can't determine mask :( */ 287 fle->f.src_mask = 32; 288 289 RTFREE(ro.ro_rt); 290 } 291 292 /* Push new flow entry into flow cache */ 293 mtx_lock(&priv->work_mtx); 294 LIST_INSERT_HEAD(&(h[slot].head), fle, fle_hash); 295 TAILQ_INSERT_TAIL(&priv->work_queue, fle, fle_work); 296 mtx_unlock(&priv->work_mtx); 297 298 return (0); 299 } 300 301 static __inline int 302 make_flow_rec(struct mbuf **m, int *plen, struct flow_rec *r, uint8_t *tcp_flags, 303 u_int16_t i_ifx) 304 { 305 register struct ip *ip; 306 int hlen; 307 int error = 0; 308 309 ip = mtod(*m, struct ip*); 310 311 /* check version */ 312 if (ip->ip_v != IPVERSION) 313 return (EINVAL); 314 315 /* verify min header length */ 316 hlen = ip->ip_hl << 2; 317 318 if (hlen < sizeof(struct ip)) 319 return (EINVAL); 320 321 r->r_src = ip->ip_src; 322 r->r_dst = ip->ip_dst; 323 324 /* save packet length */ 325 *plen = ntohs(ip->ip_len); 326 327 r->r_ip_p = ip->ip_p; 328 r->r_tos = ip->ip_tos; 329 330 if ((*m)->m_pkthdr.rcvif) 331 r->r_i_ifx = (*m)->m_pkthdr.rcvif->if_index; 332 else 333 r->r_i_ifx = i_ifx; 334 335 /* 336 * XXX NOTE: only first fragment of fragmented TCP, UDP and 337 * ICMP packet will be recorded with proper s_port and d_port. 338 * Following fragments will be recorded simply as IP packet with 339 * ip_proto = ip->ip_p and s_port, d_port set to zero. 340 * I know, it looks like bug. But I don't want to re-implement 341 * ip packet assebmling here. Anyway, (in)famous trafd works this way - 342 * and nobody complains yet :) 343 */ 344 if(ip->ip_off & htons(IP_OFFMASK)) 345 return (0); 346 347 /* skip IP header */ 348 m_adj(*m, hlen); 349 350 switch(r->r_ip_p) { 351 case IPPROTO_TCP: 352 { 353 register struct tcphdr *tcp; 354 355 /* verify that packet is not truncated */ 356 if (CHECK_MLEN(*m, sizeof(struct tcphdr))) 357 ERROUT(EINVAL); 358 359 if (CHECK_PULLUP(*m, sizeof(struct tcphdr))) 360 ERROUT(ENOBUFS); 361 362 tcp = mtod(*m, struct tcphdr*); 363 r->r_sport = tcp->th_sport; 364 r->r_dport = tcp->th_dport; 365 *tcp_flags = tcp->th_flags; 366 break; 367 } 368 case IPPROTO_UDP: 369 /* verify that packet is not truncated */ 370 if (CHECK_MLEN(*m, sizeof(struct udphdr))) 371 ERROUT(EINVAL); 372 373 if (CHECK_PULLUP(*m, sizeof(struct udphdr))) 374 ERROUT(ENOBUFS); 375 376 r->r_ports = *(mtod(*m, uint32_t *)); 377 break; 378 } 379 380 done: 381 return (error); 382 } 383 384 /* 385 * Non-static functions called from ng_netflow.c 386 */ 387 388 /* Allocate memory and set up flow cache */ 389 int 390 ng_netflow_cache_init(priv_p priv) 391 { 392 struct flow_entry *fle; 393 int i; 394 395 /* allocate cache */ 396 MALLOC(priv->cache, struct flow_entry *, 397 CACHESIZE * sizeof(struct flow_entry), 398 M_NETFLOW, M_WAITOK | M_ZERO); 399 400 if (priv->cache == NULL) 401 return (ENOMEM); 402 403 /* allocate hash */ 404 MALLOC(priv->hash, struct flow_hash_entry *, 405 NBUCKETS * sizeof(struct flow_hash_entry), 406 M_NETFLOW, M_WAITOK | M_ZERO); 407 408 if (priv->hash == NULL) 409 return (ENOMEM); 410 411 TAILQ_INIT(&priv->work_queue); 412 SLIST_INIT(&priv->free_list); 413 SLIST_INIT(&priv->expire_list); 414 415 mtx_init(&priv->work_mtx, "ng_netflow cache mutex", NULL, MTX_DEF); 416 mtx_init(&priv->free_mtx, "ng_netflow free mutex", NULL, MTX_DEF); 417 mtx_init(&priv->expire_mtx, "ng_netflow expire mutex", NULL, MTX_DEF); 418 419 /* build free list */ 420 for (i = 0, fle = priv->cache; i < CACHESIZE; i++, fle++) 421 SLIST_INSERT_HEAD(&priv->free_list, fle, fle_free); 422 423 priv->info.nfinfo_free = CACHESIZE; 424 425 return (0); 426 } 427 428 /* Free all flow cache memory. Called from node close method. */ 429 void 430 ng_netflow_cache_flush(priv_p priv) 431 { 432 register struct flow_entry *fle; 433 int i; 434 435 /* 436 * We are going to free probably billable data. 437 * Expire everything before freeing it. 438 * No locking is required since callout is already drained. 439 */ 440 441 for (i = 0, fle = priv->cache; i < CACHESIZE; i++, fle++) 442 if (!ISFREE(fle)) 443 /* ignore errors now */ 444 (void )export_add(priv, fle); 445 446 mtx_destroy(&priv->work_mtx); 447 mtx_destroy(&priv->free_mtx); 448 mtx_destroy(&priv->expire_mtx); 449 450 /* free hash memory */ 451 if (priv->hash) 452 FREE(priv->hash, M_NETFLOW); 453 454 /* free flow cache */ 455 if (priv->cache) 456 FREE(priv->cache, M_NETFLOW); 457 458 } 459 460 /* Insert packet from &m into flow cache. */ 461 int 462 ng_netflow_flow_add(priv_p priv, struct mbuf **m, iface_p iface) 463 { 464 struct flow_hash_entry *h = priv->hash; 465 register struct flow_entry *fle; 466 struct flow_rec r; 467 int plen; 468 int error = 1; 469 uint32_t slot; 470 uint8_t tcp_flags = 0; 471 472 priv->info.nfinfo_packets ++; 473 474 /* Try to fill *rec */ 475 bzero(&r, sizeof(r)); 476 if ((error = make_flow_rec(m, &plen, &r, &tcp_flags, iface->info.ifinfo_index))) 477 return (error); 478 479 slot = ip_hash(&r); 480 481 mtx_lock(&priv->work_mtx); 482 fle = hash_lookup(h, slot, &r); /* New flow entry or existent? */ 483 484 if (fle) { /* an existent entry */ 485 486 TAILQ_REMOVE(&priv->work_queue, fle, fle_work); 487 488 fle->f.bytes += plen; 489 fle->f.packets ++; 490 fle->f.tcp_flags |= tcp_flags; 491 fle->f.last = time_uptime; 492 493 /* 494 * We have the following reasons to expire flow in active way: 495 * - it hit active timeout 496 * - a TCP connection closed 497 * - it is going to overflow counter 498 */ 499 if (tcp_flags & TH_FIN || tcp_flags & TH_RST || AGED(fle) || 500 (fle->f.bytes >= (UINT_MAX - IF_MAXMTU)) ) 501 expire_flow(priv, fle); 502 else 503 TAILQ_INSERT_TAIL(&priv->work_queue, fle, fle_work); 504 505 mtx_unlock(&priv->work_mtx); 506 priv->info.nfinfo_bytes += plen; 507 508 } else { /* a new flow entry */ 509 510 mtx_unlock(&priv->work_mtx); 511 return hash_insert(priv, slot, &r, plen); 512 513 } 514 515 mtx_assert(&priv->work_mtx, MA_NOTOWNED); 516 mtx_assert(&priv->expire_mtx, MA_NOTOWNED); 517 mtx_assert(&priv->free_mtx, MA_NOTOWNED); 518 return (0); 519 } 520 521 /* 522 * Return records from cache. netgraph(4) guarantees us that we 523 * are locked against ng_netflow_rcvdata(). However we can 524 * work with ng_netflow_expire() in parrallel. XXX: Is it dangerous? 525 * 526 * TODO: matching particular IP should be done in kernel, here. 527 */ 528 int 529 ng_netflow_flow_show(priv_p priv, uint32_t last, struct ng_mesg *resp) 530 { 531 struct flow_entry *fle; 532 struct ngnf_flows *data; 533 534 data = (struct ngnf_flows *)resp->data; 535 data->last = 0; 536 data->nentries = 0; 537 538 /* Check if this is a first run */ 539 if (last == 0) 540 fle = priv->cache; 541 else { 542 if (last > CACHESIZE-1) 543 return (EINVAL); 544 fle = priv->cache + last; 545 } 546 547 /* 548 * We will transfer not more than NREC_AT_ONCE. More data 549 * will come in next message. 550 * We send current stop point to userland, and userland should return 551 * it back to us. 552 */ 553 for (; last < CACHESIZE; fle++, last++) { 554 if (ISFREE(fle)) 555 continue; 556 bcopy(&fle->f, &(data->entries[data->nentries]), sizeof(fle->f)); 557 data->nentries ++; 558 if (data->nentries == NREC_AT_ONCE) { 559 if (++last < CACHESIZE) 560 data->last = (++fle - priv->cache); 561 return (0); 562 } 563 } 564 565 return (0); 566 } 567 568 /* We have full datagram in privdata. Send it to export hook. */ 569 static int 570 export_send(priv_p priv) 571 { 572 struct netflow_v5_header *header = &priv->dgram.header; 573 struct timespec ts; 574 struct mbuf *m; 575 int error = 0; 576 int mlen; 577 578 header->sys_uptime = htonl(time_uptime); 579 580 getnanotime(&ts); 581 header->unix_secs = htonl(ts.tv_sec); 582 header->unix_nsecs = htonl(ts.tv_nsec); 583 584 /* Flow sequence contains number of first record */ 585 header->flow_seq = htonl(priv->flow_seq - header->count); 586 587 mlen = sizeof(struct netflow_v5_header) + 588 sizeof(struct netflow_v5_record) * header->count; 589 590 header->count = htons(header->count); 591 if ((m = m_devget((caddr_t)header, mlen, 0, NULL, NULL)) == NULL) { 592 printf("ng_netflow: m_devget() failed, losing export dgram\n"); 593 header->count = 0; 594 return(ENOBUFS); 595 } 596 597 header->count = 0; 598 599 /* Giant is required in sosend() at this moment. */ 600 NET_LOCK_GIANT(); 601 NG_SEND_DATA_ONLY(error, priv->export, m); 602 NET_UNLOCK_GIANT(); 603 604 if (error) 605 NG_FREE_M(m); 606 607 return (error); 608 } 609 610 611 /* Create export datagram. */ 612 static int 613 export_add(priv_p priv, struct flow_entry *fle) 614 { 615 struct netflow_v5_header *header = &priv->dgram.header; 616 struct netflow_v5_record *rec; 617 618 if (header->count == 0 ) { /* first record */ 619 rec = &priv->dgram.r[0]; 620 header->count = 1; 621 } else { /* continue filling datagram */ 622 rec = &priv->dgram.r[header->count]; 623 header->count ++; 624 } 625 626 /* Fill in export record */ 627 rec->src_addr = fle->f.r.r_src.s_addr; 628 rec->dst_addr = fle->f.r.r_dst.s_addr; 629 rec->next_hop = fle->f.next_hop.s_addr; 630 rec->i_ifx = htons(fle->f.fle_i_ifx); 631 rec->o_ifx = htons(fle->f.fle_o_ifx); 632 rec->packets = htonl(fle->f.packets); 633 rec->octets = htonl(fle->f.bytes); 634 rec->first = htonl(fle->f.first); 635 rec->last = htonl(fle->f.last); 636 rec->s_port = fle->f.r.r_sport; 637 rec->d_port = fle->f.r.r_dport; 638 rec->flags = fle->f.tcp_flags; 639 rec->prot = fle->f.r.r_ip_p; 640 rec->tos = fle->f.r.r_tos; 641 rec->dst_mask = fle->f.dst_mask; 642 rec->src_mask = fle->f.src_mask; 643 644 priv->flow_seq++; 645 646 if (header->count == NETFLOW_V5_MAX_RECORDS) /* end of datagram */ 647 return export_send(priv); 648 649 return (0); 650 } 651 652 /* Periodic flow expiry run. */ 653 void 654 ng_netflow_expire(void *arg) 655 { 656 register struct flow_entry *fle, *fle1; 657 priv_p priv = (priv_p )arg; 658 uint32_t used; 659 int error = 0; 660 661 /* First pack actively expired entries */ 662 mtx_lock(&priv->expire_mtx); 663 while (!SLIST_EMPTY(&(priv->expire_list))) { 664 fle = SLIST_FIRST(&(priv->expire_list)); 665 SLIST_REMOVE_HEAD(&(priv->expire_list), fle_free); 666 mtx_unlock(&priv->expire_mtx); 667 668 /* 669 * While we have dropped the lock, expire_flow() may 670 * insert another flow into top of the list. 671 * This is not harmful for us, since we have already 672 * detached our own. 673 */ 674 675 if ((error = export_add(priv, fle)) != 0) 676 printf("ng_netflow: export_add() failed: %u\n", error); 677 (void )free_flow(priv, fle); 678 679 mtx_lock(&priv->expire_mtx); 680 } 681 mtx_unlock(&priv->expire_mtx); 682 683 NGNF_GETUSED(priv, used); 684 mtx_lock(&priv->work_mtx); 685 TAILQ_FOREACH_SAFE(fle, &(priv->work_queue), fle_work, fle1) { 686 /* 687 * When cache size has not reached CACHELOWAT yet, we keep both 688 * inactive and active flows in cache. Doing this, we reduce number 689 * of exports, since many inactive flows may wake up and continue 690 * their life. However, we make an exclusion for scans. It is very 691 * rare situation that inactive 1-packet flow will wake up. 692 * When cache has reached CACHELOWAT, we expire all inactive flows, 693 * until cache gets of sane size. 694 * 695 * When this record's refcount is > 0, we skip it. (XXX) 696 */ 697 if (used <= CACHELOWAT && !INACTIVE(fle)) 698 goto finish; 699 700 if (INACTIVE(fle) && (SMALL(fle) || (used > CACHELOWAT))) { 701 702 /* Detach flow entry from cache */ 703 LIST_REMOVE(fle, fle_hash); 704 TAILQ_REMOVE(&priv->work_queue, fle, fle_work); 705 706 /* 707 * While we are sending to collector, unlock cache. 708 * XXX: it can happen, however with a small probability, 709 * that item, we are holding now, can be moved to the top 710 * of flow cache by node thread. In this case our expire 711 * thread stops checking. Since this is not fatal we will 712 * just ignore it now. 713 */ 714 mtx_unlock(&priv->work_mtx); 715 716 if ((error = export_add(priv, fle)) != 0) 717 printf("ng_netflow: export_add() failed: %u\n", 718 error); 719 720 used = free_flow(priv, fle); 721 722 mtx_lock(&priv->work_mtx); 723 } 724 } 725 726 finish: 727 mtx_unlock(&priv->work_mtx); 728 729 mtx_assert(&priv->expire_mtx, MA_NOTOWNED); 730 mtx_assert(&priv->free_mtx, MA_NOTOWNED); 731 732 /* schedule next expire */ 733 callout_reset(&priv->exp_callout, (1*hz), &ng_netflow_expire, 734 (void *)priv); 735 736 } 737