pax_global_header00006660000000000000000000000064131353405750014520gustar00rootroot0000000000000052 comment=630e1d759417fe8ad31881252c471e38e5d51bfd fastnetmon-1.1.3+dfsg/000077500000000000000000000000001313534057500146175ustar00rootroot00000000000000fastnetmon-1.1.3+dfsg/.travis.yml000066400000000000000000000006261313534057500167340ustar00rootroot00000000000000language: cpp compiler: - gcc - clang before_install: - sudo apt-get update; true before_script: - pwd > /tmp/old_current_path - sudo perl src/fastnetmon_install.pl --use-git-master - cd `cat /tmp/old_current_path` - cd src script: mkdir -p build; cd build; cmake ..; make notifications: email: recipients: - pavel.odintsov@gmail.com on_success: change on_failure: always fastnetmon-1.1.3+dfsg/LICENSE000066400000000000000000000432301313534057500156260ustar00rootroot00000000000000GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. FastNetMon - High Performance Network Load Analyzer with PCAP/ULOG2 support Copyright (C) 2013 FastVPSEestiOu This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. {signature of Ty Coon}, 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. fastnetmon-1.1.3+dfsg/README.md000066400000000000000000000121061313534057500160760ustar00rootroot00000000000000FastNetMon =========== FastNetMon - A high performance DoS/DDoS load analyzer built on top of multiple packet capture engines (NetFlow, IPFIX, sFLOW, SnabbSwitch, netmap, PF_RING, PCAP). What can we do? We can detect hosts in our networks sending or receiving large volumes of packets/bytes/flows per second. We can call an external script to notify you, switch off a server, or blackhole the client. To enable sFLOW, simply specify IP of the server running FastNetMon and specify (configurable) port 6343 To enable netflow, simply specify IP of the server running FastNetMon and specify (configurable) port 2055 Why did we write this? Because we can't find any software for solving this problem in the open source world! What is a "flow" in FastNetMon terms? It's one or multiple UDP, TCP, or ICMP connections with unique src IP, dst IP, src port, dst port, and protocol. License: GPLv2 [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/FastVPSEestiOu/fastnetmon?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) Project ------- - [Mailing list](https://groups.google.com/forum/#!forum/fastnetmon) - [Roadmap](docs/ROADMAP.md) - [Release Notes](docs/RELEASENOTES.md) - Chat: #fastnetmon at irc.freenode.net [web client](https://webchat.freenode.net/) - [Please fill out the survey, we need your voice!](https://docs.google.com/forms/d/1YoXQImMeEjBH-JPz3KYtcDwknHs8xrI538ObwSy9uZo/viewform) - Detailed reference in Russian: [link](docs/FastNetMon_Reference_Russian.pdf) Supported packet capture engines -------------------------------- - NetFlow v5, v9 - IPFIX - ![sFLOW](http://sflow.org/images/sflowlogo.gif) v4 (dev branch only), v5 - Port mirror/SPAN capture with PF_RING (with ZC/DNA mode support [need license](http://www.ntop.org/products/pf_ring/)), SnabbSwitch, NETMAP and PCAP You can check out the [comparison table](docs/CAPTURE_BACKENDS.md) for all available packet capture engines. Features -------- - Complete [BGP Flow Spec support](docs/BGP_FLOW_SPEC.md), RFC 5575 - Process and distinguish incoming and/or outgoing traffic - Trigger block/notify script if an IP exceeds defined thresholds for packets/bytes/flows per second - Thresholds can be configured per-subnet with the hostgroups feature - [Announce blocked IPs](docs/EXABGP_INTEGRATION.md) via BGP to routers with [ExaBGP](https://github.com/Exa-Networks/exabgp) - GoBGP [integration](docs/GOBGP.md) for unicast IPv4 announcements - Full integration with [Graphite](docs/GRAPHITE_INTEGRATION.md) and [InfluxDB](docs/INFLUXDB_INTEGRATION.md) - API - Redis integration - MongoDB integration - Deep packet inspection for attack traffic - netmap support (open source; wire speed processing; only Intel hardware NICs or any hypervisor VM type) - SnabbSwitch support (open source, very flexible, LUA driven, very-very-very fast) - Filter NetFlow v5 flows or sFLOW packets with LUA scripts (useful for excluding particular ports) - Supports L2TP decapsulation, VLAN untagging and MPLS processing in mirror mode - Works on server/soft-router - Detects DoS/DDoS in as little as 1-2 seconds - [Tested](docs/PERFORMANCE_TESTS.md) up to 10Gb with 12 Mpps on Intel i7 3820 with Intel NIC 82599 - Complete plugin support - Captures attack fingerprints in PCAP format - [Complete support](docs/DETECTED_ATTACK_TYPES.md) for most popular attack types Running Fastnetmon ------------------ ### Supported platforms - Linux (Debian 6/7/8, CentOS 6/7, Ubuntu 12+) - FreeBSD 9, 10, 11 - Mac OS X Yosemite ### Supported architectures - x86 64 bit (recommended) - x86 32 bit ### Router integration instructions - [Juniper MX Routers](docs/JUNOS_INTEGRATION.md) ### Distributions supported - We are part of the [CloudRouter](https://cloudrouter.org/cloudrouter/2015/07/09/fastnetmon.html) distribution - We are part in the [official FreeBSD ports collection](https://freshports.org/net-mgmt/fastnetmon/), [manual install](docs/FreeBSD_INSTALL.md) - [Amazon AMI image](docs/AMAZON.md) - [VyOS based ISO image with bundled FastNetMon](docs/VYOS_BINARY_ISO_IMAGE.md) - [Docker image](docs/DOCKER_INSTALL.md) - [Automatic install script for Debian/Ubuntu/CentOS/Fedora/Gentoo](docs/INSTALL.md) - [Automatic install script for Mac OS X](docs/MAC_OS_INSTALL.md) - [Manual install on Slackware](docs/SLACKWARE_INSTALL.md) - [Manual install for VyOS](docs/VyOS_INSTALL.md) Screenshoots ------------ Main program screenshot: ![Main screen image](docs/images/fastnetmon_screen.png) Example CPU load on Intel i7 2600 with Intel X540/82599 NIC at 400 kpps load: ![Cpu consumption](docs/images/fastnetmon_stats.png) Example deployment scheme: ![Network diagramm](docs/images/network_map.png) Example of [notification email](docs/ATTACK_REPORT_EXAMPLE.md) about detected attack. How I can help project? ----------------------- - We are looking for a maintainer for the Debian and Fedora/EPEL packages - Test it! - Share your experience - Share your use cases - Share your improvements - Test it with different equipment - Create feature requests Author: [Pavel Odintsov](http://ru.linkedin.com/in/podintsov/) pavel.odintsov at gmail.com [Follow my Twitter](https://twitter.com/odintsov_pavel) fastnetmon-1.1.3+dfsg/debian/000077500000000000000000000000001313534057500160415ustar00rootroot00000000000000fastnetmon-1.1.3+dfsg/debian/changelog000066400000000000000000000002461313534057500177150ustar00rootroot00000000000000fastnetmon (1.1-2) UNRELEASED; urgency=medium * Initial release of Debian package -- Pavel Odintsov Sat, 30 May 2015 15:10:21 -0400 fastnetmon-1.1.3+dfsg/debian/compat000066400000000000000000000000021313534057500172370ustar00rootroot000000000000008 fastnetmon-1.1.3+dfsg/debian/control000066400000000000000000000012561313534057500174500ustar00rootroot00000000000000Source: fastnetmon Maintainer: Pavel Odintsov Section: misc Priority: optional Standards-Version: 3.9.6 # debhelper 8 - squeeze, 9 - wheezy/jessie Build-Depends: debhelper (>= 8), cmake, libboost-thread-dev, libboost-system-dev, libboost-regex-dev, libpcap-dev, liblog4cpp5-dev, libboost-all-dev, libgpm-dev, libncurses5-dev, libgeoip-dev, clang, cmake Package: fastnetmon Architecture: any Depends: ${shlibs:Depends}, ${misc:Depends}, libboost-thread-dev, libboost-system-dev, libboost-regex-dev, libpcap-dev, liblog4cpp5-dev Description: Very fast DDoS analyzer with sflow/netflow/mirror support FastNetMon - A high performance DoS/DDoS attack sensor. fastnetmon-1.1.3+dfsg/debian/copyright000066400000000000000000000000671313534057500177770ustar00rootroot00000000000000Pavel Odintsov pavel.odintsov@gmail.com GPLv2 license fastnetmon-1.1.3+dfsg/debian/rules000077500000000000000000000002271313534057500171220ustar00rootroot00000000000000#!/usr/bin/make -f %: dh $@ --buildsystem=cmake --sourcedirectory=src override_dh_auto_configure: dh_auto_configure -- -DDISABLE_PF_RING_SUPPORT=yes fastnetmon-1.1.3+dfsg/debian/source/000077500000000000000000000000001313534057500173415ustar00rootroot00000000000000fastnetmon-1.1.3+dfsg/debian/source/format000066400000000000000000000000141313534057500205470ustar00rootroot000000000000003.0 (quilt) fastnetmon-1.1.3+dfsg/docs/000077500000000000000000000000001313534057500155475ustar00rootroot00000000000000fastnetmon-1.1.3+dfsg/docs/AMAZON.md000066400000000000000000000005471313534057500170640ustar00rootroot00000000000000### We have Amazon AMI images Please use this community image: ```bash fastnetmon-git 1443707118 ami-f2d3d1ef ``` This image prepared from Git commit: 9d50c20ac9fa23903b6f05ac141561b2d234a3d5 (upcoming 1.1.3 branch). We have copied this image to following locations: - EU Frankfurt - US East - US West Please use ubuntu login when connecting to Instance. fastnetmon-1.1.3+dfsg/docs/API.md000066400000000000000000000007341313534057500165060ustar00rootroot00000000000000### We have API built on top of gRPC framework Enable API in configuration file: ```bash # Enable gRPC api (required for fastnetmon_api_client tool) enable_api = on ``` You could ban IP: ```bash /opt/fastnetmon/fastnetmon_api_client ban 192.168.1.1 ``` You could unban IP: ```bash /opt/fastnetmon/fastnetmon_api_client unban 192.168.1.1 ``` You could check banlist: ```bash /opt/fastnetmon/fastnetmon_api_client get_banlist ``` Sample output: ```bash 192.168.1.1/32 ``` fastnetmon-1.1.3+dfsg/docs/ATTACK_REPORT_EXAMPLE.md000066400000000000000000000107341313534057500213130ustar00rootroot00000000000000```bash IP: 10.10.10.221 Attack type: syn_flood Initial attack power: 99059 packets per second Peak attack power: 99059 packets per second Attack direction: incoming Attack protocol: tcp Total incoming traffic: 45 mbps Total outgoing traffic: 0 mbps Total incoming pps: 99059 packets per second Total outgoing pps: 0 packets per second Total incoming flows: 98926 flows per second Total outgoing flows: 0 flows per second Average incoming traffic: 45 mbps Average outgoing traffic: 0 mbps Average incoming pps: 99059 packets per second Average outgoing pps: 0 packets per second Average incoming flows: 98926 flows per second Average outgoing flows: 0 flows per second Incoming ip fragmented traffic: 250 mbps Outgoing ip fragmented traffic: 0 mbps Incoming ip fragmented pps: 546475 packets per second Outgoing ip fragmented pps: 0 packets per second Incoming tcp traffic: 250 mbps Outgoing tcp traffic: 0 mbps Incoming tcp pps: 546475 packets per second Outgoing tcp pps: 0 packets per second Incoming syn tcp traffic: 250 mbps Outgoing syn tcp traffic: 0 mbps Incoming syn tcp pps: 546475 packets per second Outgoing syn tcp pps: 0 packets per second Incoming udp traffic: 0 mbps Outgoing udp traffic: 0 mbps Incoming udp pps: 0 packets per second Outgoing udp pps: 0 packets per second Incoming icmp traffic: 0 mbps Outgoing icmp traffic: 0 mbps Incoming icmp pps: 0 packets per second Outgoing icmp pps: 0 packets per second Average packet size for incoming traffic: 60.0 bytes Average packet size for outgoing traffic: 0.0 bytes Incoming TCP 10.10.10.221:1024 < 10.138.0.0:1025 60 bytes 1 packets 10.10.10.221:1024 < 10.149.0.0:1025 60 bytes 1 packets 10.10.10.221:1024 < 10.160.0.0:1025 60 bytes 1 packets 10.10.10.221:1024 < 10.161.0.0:1025 60 bytes 1 packets 10.10.10.221:1024 < 10.174.0.0:1025 60 bytes 1 packets 10.10.10.221:1024 < 10.178.0.0:1025 60 bytes 1 packets 10.10.10.221:1024 < 10.187.0.0:1025 60 bytes 1 packets 10.10.10.221:1024 < 10.188.0.0:1025 60 bytes 1 packets 10.10.10.221:1024 < 10.190.0.0:1025 60 bytes 1 packets 10.10.10.221:1024 < 10.192.0.0:1025 60 bytes 1 packets 10.10.10.221:1024 < 10.201.0.0:1025 60 bytes 1 packets 10.10.10.221:1024 < 10.203.0.0:1025 60 bytes 1 packets 10.10.10.221:1024 < 10.216.0.0:1025 60 bytes 1 packets 10.10.10.221:1024 < 10.221.0.0:1025 60 bytes 1 packets 10.10.10.221:1024 < 10.0.1.0:1025 60 bytes 1 packets 10.10.10.221:1024 < 10.121.1.0:1025 60 bytes 1 packets 10.10.10.221:1024 < 10.126.1.0:1025 60 bytes 1 packets 10.10.10.221:1024 < 10.148.1.0:1025 60 bytes 1 packets 10.10.10.221:1024 < 10.149.1.0:1025 60 bytes 1 packets 10.10.10.221:1024 < 10.156.1.0:1025 60 bytes 1 packets 10.10.10.221:1024 < 10.160.1.0:1025 60 bytes 1 packets 10.10.10.221:1024 < 10.174.1.0:1025 60 bytes 1 packets 10.10.10.221:1024 < 10.178.1.0:1025 60 bytes 1 packets 10.10.10.221:1024 < 10.187.1.0:1025 60 bytes 1 packets 10.10.10.221:1024 < 10.190.1.0:1025 60 bytes 1 packets 10.10.10.221:1024 < 10.192.1.0:1025 60 bytes 1 packets 10.10.10.221:1024 < 10.201.1.0:1025 60 bytes 1 packets 10.10.10.221:1024 < 10.203.1.0:1025 60 bytes 1 packets 10.10.10.221:1024 < 10.212.1.0:1025 60 bytes 1 packets 10.10.10.221:1024 < 10.216.1.0:1025 60 bytes 1 packets 10.10.10.221:1024 < 10.221.1.0:1025 60 bytes 1 packets 10.10.10.221:1024 < 10.224.1.0:1025 60 bytes 1 packets 10.10.10.221:1024 < 10.0.2.0:1025 60 bytes 1 packets 10.10.10.221:1024 < 10.121.2.0:1025 60 bytes 1 packets 10.10.10.221:1024 < 10.126.2.0:1025 60 bytes 1 packets 10.10.10.221:1024 < 10.148.2.0:1025 60 bytes 1 packets 10.10.10.221:1024 < 10.156.2.0:1025 60 bytes 1 packets 10.10.10.221:1024 < 10.160.2.0:1025 60 bytes 1 packets 10.10.10.221:1024 < 10.187.2.0:1025 60 bytes 1 packets 10.10.10.221:1024 < 10.203.2.0:1025 60 bytes 1 packets 10.10.10.221:1024 < 10.210.2.0:1025 60 bytes 1 packets 10.10.10.221:1024 < 10.212.2.0:1025 60 bytes 1 packets 10.10.10.221:1024 < 10.221.2.0:1025 60 bytes 1 packets 10.10.10.221:1024 < 10.224.2.0:1025 60 bytes 1 packets 10.10.10.221:1024 < 10.240.2.0:1025 60 bytes 1 packets 10.10.10.221:1024 < 10.0.3.0:1025 60 bytes 1 packets 10.10.10.221:1024 < 10.90.3.0:1025 60 bytes 1 packets 10.10.10.221:1024 < 10.96.3.0:1025 60 bytes 1 packets 10.10.10.221:1024 < 10.121.3.0:1025 60 bytes 1 packets 10.10.10.221:1024 < 10.126.3.0:1025 60 bytes 1 packets 10.10.10.221:1024 < 10.137.3.0:1025 60 bytes 1 packets 10.10.10.221:1024 < 10.148.3.0:1025 60 bytes 1 packets 10.10.10.221:1024 < 10.156.3.0:1025 60 bytes 1 packets 10.10.10.221:1024 < 10.160.3.0:1025 60 bytes 1 packets 10.10.10.221:1024 < 10.210.3.0:1025 60 bytes 1 packets ``` fastnetmon-1.1.3+dfsg/docs/BGP_FLOW_SPEC.md000066400000000000000000000045501313534057500201460ustar00rootroot00000000000000### All this docs about ExaBGP 4.0 (Git master branch) Clone code: ```bash cd /usr/src/ git clone https://github.com/Exa-Networks/exabgp.git ``` They are not compatible with ExaBGP 3.0 vim /root/announcer.py: ```bash #!/usr/bin/python fl=open("/var/run/exabgp.cmd", "w") fl.write("announce flow route source 4.0.0.0/24 destination 127.0.0.0/24 protocol [ udp ] source-port [ =53 ] destination-port [ =80 ] packet-length [ =777 =1122 ] fragment [ is-fragment dont-fragment ] rate-limit 1024" + '\n') fl.flush() fl.close ``` Please be careful about flush and trailing '\n'!!! vim /etc/exabgp_flowspec.conf: ```bash process announce-routes { run /usr/bin/socat stdout pipe:/var/run/exabgp.cmd; encoder json; } neighbor 127.0.0.1 { router-id 1.2.3.4; local-address 127.0.0.1; local-as 1; peer-as 1; group-updates false; family { ipv4 flow; } api { processes [ anounce-routes ]; } } ``` Run it: ```bash cd /usr/src/exabgp env exabgp.api.file=/tmp/exabgp.cmd exabgp.daemon.user=root exabgp.daemon.daemonize=false exabgp.daemon.pid=/var/run/exabgp.pid exabgp.log.destination=/var/log/exabgp.log sbin/exabgp --debug /etc/exabgp_flowspec.conf ``` Then, please install Git version of FastNetMon (stable version do not support this features yet): ```bash wget https://raw.githubusercontent.com/FastVPSEestiOu/fastnetmon/master/src/fastnetmon_install.pl -Ofastnetmon_install.pl sudo perl fastnetmon_install.pl --use-git-master ``` FastNetMon configuration /etc/fastnetmon.conf: ```bash # This options are mandatory for Flow Spec attack detector collect_attack_pcap_dumps = on process_pcap_attack_dumps_with_dpi = on exabgp = on exabgp_command_pipe = /var/run/exabgp.cmd exabgp_community = 65001:666 exabgp_next_hop = 10.0.3.114 exabgp_flow_spec_announces = on # Please switch off unicast BGP announces with ExaBGP because they are not compatible with Flow Spec exabgp_announce_whole_subnet = no exabgp_announce_host = no ``` Be aware! We will announce rules with discard option! Currently we support only most popular amplification attack types: - DNS amplification (we drop all udp traffic originating from 53 port) - NTP amplification (we drop all udp traffic originating from 123 port) - SSDP amplification (we drop all udp traffic originating from 1900 port) - SNMP amplification (we drop all udp traffic originating from 161 port) fastnetmon-1.1.3+dfsg/docs/BUILDING_DEB_PACKAGE.md000066400000000000000000000015161313534057500210560ustar00rootroot00000000000000# Short reference about building deb packages ```bash mkdir /usr/src/fastnetmon_deb cd /usr/src/fastnetmon_deb export PACKAGE_VERSION=1.1 wget https://github.com/FastVPSEestiOu/fastnetmon/archive/master.tar.gz -O"fastnetmon_$PACKAGE_VERSION.orig.tar.gz" tar -xf "fastnetmon_$PACKAGE_VERSION.orig.tar.gz" mv fastnetmon-master "fastnetmon-$PACKAGE_VERSION" cd "fastnetmon-$PACKAGE_VERSION" # Create symlinks for init files for systev and systemd ln -s ../src/fastnetmon_init_script_debian_6_7 debian/fastnetmon.init ln -s ../src/fastnetmon.service debian/fastnetmon.service # We need this for Debian https://lintian.debian.org/tags/systemd-service-file-refers-to-obsolete-target.html # But RHEL7 still uses it sed -i 's/syslog.target //' src/fastnetmon.service dpkg-source --commit . fix_systemd_service # enter any patch name debuild -us -uc ``` fastnetmon-1.1.3+dfsg/docs/BUILDING_FREEBSD_KERNEL_FOR_NETMAP.md000066400000000000000000000017051313534057500232350ustar00rootroot00000000000000Installing netmap in FreeBSD Try to build kernel module for current kernel: ```bash cd /usr/src/sys/modules/netmap make make install kldload netmap ``` But you could hit this bug: ```bash KLD netmap.ko: depends on kernel - not available or version mismatch linker_load_file: Unsupported file type ``` Enable netmap startup on server load: ```bash echo 'netmap_load="YES"' >> /boot/loader.conf ``` To activate Netmap on your server you have to turn your interface on promiscuous mode: ifconfig promisc And should rebuild kernel manually. Install SVN: ```bash pkg install devel/subversion ``` Download base repository for FreeBSD 10 stable (replace 10 by your FreeBSD version): ```svn checkput https://svn0.ru.freebsd.org/base/stable/10 /usr/src``` Build and install new kernel: ```bash cd /usr/src/sys/amd64/conf cp GENERIC KERNELWITHNETMAP cd /usr/src make buildkernel KERNCONF=KERNELWITHNETMAP make installkernel KERNCONF=KERNELWITHNETMAP ``` fastnetmon-1.1.3+dfsg/docs/BUILDING_VYOS_ISO.md000066400000000000000000000040361313534057500206630ustar00rootroot00000000000000### This guide fill describe how build iso image of VyOS with bundled FastNetMon First af all, you need _only_ Debian Squeeze for building this image! Install VyOS keyring: ```bash apt-get install debian-archive-keyring wget http://vyos.net/so3group_maintainers.key gpg --import so3group_maintainers.key ``` Enable Backports repo and install Squashfs tools: ```bash echo "deb http://backports.debian.org/debian-backports squeeze-backports main" >> /etc/apt/sources.list apt-get update apt-get -t squeeze-backports install squashfs-tools ``` Install packages for building iso image: ```bash apt-get install git autoconf automake dpkg-dev live-helper syslinux genisoimage ``` Install FastNetMon build deps: ```bash apt-get install -y cmake libboost-thread-dev libboost-system-dev libboost-regex-dev libpcap-dev libnuma-dev liblog4cpp5-dev libboost-all-dev libgpm-dev libncurses5-dev libgeoip-dev clang ``` New manual: ```bash git clone https://github.com/pavel-odintsov/build-iso.git cd build-iso git submodule update --init pkgs/fastnetmon/ export PATH=/sbin:/usr/sbin:$PATH autoreconf -i echo -e "libboost-regex1.42.0\nlibboost-thread1.42.0\nliblog4cpp5\nlibpcap0.8\nfastnetmon" >> livecd/config.vyatta/chroot_local-packageslists/vyatta-full.list ``` Replace depends for ```vim pkgs/fastnetmon/debian/control``` ```bash Depends: ${shlibs:Depends}, ${misc:Depends}, libboost-thread1.42.0, libboost-system1.42.0 , libboost-regex1.42.0, libpcap0.8, liblog4cpp5 ``` ```bash cd pkgs/fastnetmon ln -s ../src/fastnetmon_init_script_debian_6_7 debian/fastnetmon.init ln -s ../src/fastnetmon.service debian/fastnetmon.service cd .. cd .. make fastnetmon cd pkgs wget http://ftp.us.debian.org/debian/pool/main/b/boost1.42/libboost-regex1.42.0_1.42.0-4_amd64.deb wget http://ftp.us.debian.org/debian/pool/main/b/boost1.42/libboost-thread1.42.0_1.42.0-4_amd64.deb wget http://ftp.us.debian.org/debian/pool/main/l/log4cpp/liblog4cpp5_1.0-4_amd64.deb wget http://ftp.us.debian.org/debian/pool/main/libp/libpcap/libpcap0.8_1.1.1-2+squeeze1_amd64.deb cd .. ./configure make iso ``` fastnetmon-1.1.3+dfsg/docs/BUILD_BOOST.md000066400000000000000000000025021313534057500176750ustar00rootroot00000000000000### We will describe here how to build Boost Build builder: ```bash cd /usr/src wget https://github.com/boostorg/build/archive/boost-1.58.0.tar.gz -Oboost-1.58.0.tar.gz tar -xf boost-1.58.0.tar.gz cd build-boost-1.58.0/ ./bootstrap.sh ./b2 install --prefix=/opt/boost_build1.5.8 ``` Download Boost source code: ```bash cd /usr/src wget 'http://downloads.sourceforge.net/project/boost/boost/1.58.0/boost_1_58_0.tar.gz?r=http%3A%2F%2Fwww.boost.org%2Fusers%2Fhistory%2Fversion_1_58_0.html&ts=1439207367&use_mirror=cznic' -Oboost_1_58_0.tar.gz tar -xf boost_1_58_0.tar.gz cd boost_1_58_0/ ``` Start build process: ```bash /opt/boost_build1.5.8/bin/b2 --build-dir=/tmp/boosе_build_temp_directory_1_5_8 toolset=gcc --without-test --without-python --without-wave --without-graph --without-coroutine --without-math --without-log --without-graph_parallel --without-mpi ``` Add Boost library path to system path: ```bash echo "/usr/src/boost_1_58_0/stage/lib" > /etc/ld.so.conf.d/boost.conf ldconfig ``` Build time need about 5 minutes on i7 CPU. For tests we could try to remove standard version of Boost. Be careful before this actions! ```apt-get remove libboost1.55-dev``` And add Boost paths to top of CMakeLists.txt file: ```bash set(BOOST_INCLUDEDIR "/usr/src/boost_1_58_0") set(BOOST_LIBRARYDIR "/usr/src/boost_1_58_0/stage/lib/") ``` fastnetmon-1.1.3+dfsg/docs/CAPTURE_BACKENDS.md000066400000000000000000000030461313534057500204310ustar00rootroot00000000000000|Name | Capture speed |Installation | CPU load | Platforms | Cost | Accuracy of attack detection | Speed of attack detection |-----|:-------------:|:-------:|:--:|:--:|:------:|:----:|:---:| |netmap | Up to wire speed (10GE, 14 MPPS) | Need kernel module and NIC driver patch [ixgbe provided](https://github.com/pavel-odintsov/ixgbe-linux-netmap). For FreeBSD could need kernel rebuild but patches are included to kernel |Normal |Linux, FreeBSD | BSD | Very accurate | Very fast| |PF_RING | Up to 2-3 MPPS, 2-3 GE |Need kernel module install |Very big| Linux only | GPLv2 | Enough accurate | Very fast| |PF_RING ZC | Up to wire speed (10GE, 14 MPPS) | Need kernel module + patched drivers (provided in package)|Normal| Linux only | Commercial ~200 euro | Very accurate | Very fast| | pcap | very slow, 10-100 mbps | Simple | huge | FreeBSD, Linux | GPL | Not so accurate | Very fast| | sFLOW | Up to 40-100GE | Very simple | Small | Linux, FreeBSD, MacOS | Free | Accurate but depends on sampling rate (1-128 sampling rate recommended but significantly depends on traffic in network) | Very fast| | NetFlow | Up to 40-100GE | Very simple | Small for FastNetMon but could be huge for network equpment if implemented in software way | Linux, FreeBSD, MacOS | Free but could require additional licenses or hardware from network equipment vendor | Not so accurate | So slow, up to multiple minutes depends on flow timeout configuration on routers| | AF_PACKET | Up to 2 MPPS/5-10GE | Very simple | Normal-huge | Linux (since 3.6 kernel) | GPLv2 | Very accurate | Very fast| fastnetmon-1.1.3+dfsg/docs/DETECTED_ATTACK_TYPES.md000066400000000000000000000006601313534057500212670ustar00rootroot00000000000000### We could detect really any attack targeted to channel overflow But for very popular attack types we prepared algorithm which could give name for every attack of following type: - syn_flood: TCP packets with enabled SYN flag - udp_flood: flood with UDP packets (so recently in result of amplification) - icmp flood: flood with ICMP packets - ip_fragmentation_flood: IP packets with MF flag set or with non zero fragment offset fastnetmon-1.1.3+dfsg/docs/DEV_VERSION.md000066400000000000000000000007571313534057500177250ustar00rootroot00000000000000### How I can switch to current Git version? First of all, please install FastNetMon with automatic installer [here](https://github.com/FastVPSEestiOu/fastnetmon/blob/master/docs/INSTALL.md) Than switch to master branch and rebuild toolkit: ```bash cd /usr/src/fastnetmon git checkout master cd src/build cmake .. make ./fastnetmon ``` You could use ```git log``` command for checking about last commits and compare with [GitHub](https://github.com/FastVPSEestiOu/fastnetmon/commits/master) fastnetmon-1.1.3+dfsg/docs/DOCKER_INSTALL.md000066400000000000000000000073411313534057500202330ustar00rootroot00000000000000 You can run docker from pre-built image: ``` docker pull robertoberto/fastnetmon ``` First, get fastnetmon.conf from github, edit it after download. ``` wget https://raw.githubusercontent.com/FastVPSEestiOu/fastnetmon/master/src/fastnetmon.conf -O /etc/fastnetmon.conf ``` Now create networks.list. Include all your networks CIDR ``` echo "10.10.0.0/20 10.200.0.0/19" > /etc/networks.list ```` Add your whitelist networks: ``` echo "10.240.0.0/24" > /etc/networks_whitelist ``` Now create log files to access them outside cointainer ``` touch /var/log/fastnetmon.log chmod 0644 /var/log/fastnetmon.log mkdir /var/log/fastnetmon_attacks chmod 0700 /var/log/fastnetmon_attacks ``` Downloading image ``` docker pull robertoberto/fastnetmon ``` You can run docker manually to test it, or run from a screen. In this case we're mapping IPFIX to container. Replace IPFIX1 and IPFIX2 with your local network interface ip which listen to IPFIX from your routers. You can use only one IPFIX interface or more. ``` docker run -a stdin -a stdout -i \ -v /var/log/fastnetmon_attacks:/var/log/fastnetmon_attacks \ -v /var/log/fastnetmon.log:/var/log/fastnetmon.log \ -v /etc/networks_list:/etc/networks_list \ -v /etc/networks_whitelist:/etc/networks_whitelist \ -v /etc/fastnetmon.conf:/etc/fastnetmon.conf \ -p IPFIX1:2055:2055/udp \ -p IPFIX2:2055:2055/udp \ -t robertoberto/fastnetmon /bin/bash ``` Now you're inside container. Run ``` fastnetmon & fastnetmon_client ``` Also you can build your own image using Dockerfile at packages/docker ``` cd packages/docker docker build . ``` To send email, we recommend you to use a external and linked postfix container such: ``` docker pull panubo/postfix docker run \ -e MAILNAME="example.com" \ -e MYNETWORKS="127.0.0.0/8, 172.16.0.0/12" \ --name postfix \ -t panubo/postfix ``` When you link another container with docker other container name will be added to /etc/hosts pointing to its internal IP. So you can use python script notify (https://github.com/FastVPSEestiOu/fastnetmon/blob/master/src/scripts/fastnetmon_notify.py), instead of bash one. Just change MAIL_HOSTNAME="localhost" to MAIL_HOSTNAME="postfix" if you start fastnetmon docker container with --link postfix:postfix and create another docker instance with panubo/postfix as --name postfix, for example. A full example of running fastnetmon linked to postfix: ``` docker run -a stdin -a stdout -i \ -v /var/log/fastnetmon_attacks:/var/log/fastnetmon_attacks \ -v /var/log/fastnetmon.log:/var/log/fastnetmon.log \ -v /etc/networks_list:/etc/networks_list \ -v /etc/fastnetmon.conf:/etc/fastnetmon.conf \ -v /etc/networks_whitelist:/etc/networks_whitelist \ -v /usr/local/fastnetmon:/usr/local/fastnetmon \ -v /etc/exabgp_blackhole.conf:/etc/exabgp_blackhole.conf \ -v /var/log/fastnetmon-notify.log:/var/log/fastnetmon-notify.log \ -p 10.100.20.2:2055:2055/udp \ -p 10.100.20.6:2055:2055/udp \ -p 10.100.20.2:179:179/tcp \ --name fastnetmon \ --link postfix:postfix \ -t robertoberto/fastnetmon:latest /bin/bash ``` First, you need to create all those files and dirs in main Linux system. ``` mkdir /usr/local/fastnetmon touch /var/log/fastnetmon_attacks /var/log/fastnetmon.log /etc/networks_list /etc/networks_whitelist /etc/fastnetmon.conf /etc/exabgp_blackhole.conf/var/log/fastnetmon-notify.log cp /etc/fastnetmon.conf /etc/fastnetmon.conf.bkp cp /usr/local/fastnetmon/fastnetmon_notify.py /usr/local/fastnetmon/fastnetmon_notify.py.bkp wget https://raw.githubusercontent.com/FastVPSEestiOu/fastnetmon/master/src/scripts/fastnetmon_notify.py -O /usr/local/fastnetmon/fastnetmon_notify.py chmod +x /usr/local/fastnetmon/fastnetmon_notify.py wget https://raw.githubusercontent.com/FastVPSEestiOu/fastnetmon/master/src/fastnetmon.conf -O /etc/fastnetmon.conf ``` fastnetmon-1.1.3+dfsg/docs/EXABGP_INTEGRATION.md000066400000000000000000000035661313534057500207140ustar00rootroot00000000000000# FastNetMon and ExaBGP integration FastNetMon could enable/disable announce of blackholed IPs (/32) to BGP core router (Cisco, Juniper, Quagga). This feature implemented with ExaBGP toolkit. If you want to use this capability, please set following params in /etc/fastnetmon.conf and tune they to values suitable in your network: ```bash exabgp = on exabgp_command_pipe = /var/run/exabgp.cmd exabgp_community = 65001:666 exabgp_next_hop = 10.0.3.114 exabgp_announce_host = on ``` Secondly, you should install, configure and run ExaBGP toolkit. Install ExaBGP: ```bash apt-get install python-pip pip install exabgp ``` Install socat (if you haven't socat for your platform, please check this [manual](EXABGP_INTEGRATION_WITHOUT_SOCAT.md)): ```bash apt-get install -y socat yum install -y socat ``` Create example configuration: ```vim /etc/exabgp_blackhole.conf``` Example here (please fix this configuration to your network): ```bash group Core_v4 { hold-time 180; # local AS number local-as 65001; # Remote AS number peer-as 1234; # ID for this ExaBGP router router-id 10.0.3.114; graceful-restart 1200; # Remote peer neighbor 10.0.3.115 { # Local IP addess which used for connections to this peer local-address 10.0.3.114; description "Quagga"; } # Add this line for process management process service-dynamic { run /usr/bin/socat stdout pipe:/var/run/exabgp.cmd; } } ``` Run ExaBGP: ```bash env exabgp.daemon.user=root exabgp.daemon.daemonize=true exabgp.daemon.pid=/var/run/exabgp.pid exabgp.log.destination=/var/log/exabgp.log exabgp /etc/exabgp_blackhole.conf ``` You could read my articles about ExaBGP configuration too: [first](http://www.stableit.ru/2015/04/quagga-bgp-and-exabgp-work-together-for.html) and [second](http://www.stableit.ru/2015/04/how-to-control-exabgp-from-external-tool.html) fastnetmon-1.1.3+dfsg/docs/EXABGP_INTEGRATION_WITHOUT_SOCAT.md000066400000000000000000000022471313534057500230630ustar00rootroot00000000000000### With this guide you could integrate ExaBGP and FastNetMon without socat tool Create example configuration: ```vim /etc/exabgp_blackhole.conf``` Example here (please fix this configuration to your network): ```bash group Core_v4 { hold-time 180; local-as 65001; peer-as 1234; router-id 10.0.3.114; graceful-restart 1200; # Static announce is not used # static { # route 10.10.10.1/32 next-hop 10.0.3.114 community 65001:666; # } neighbor 10.0.3.115 { local-address 10.0.3.114; description "Quagga"; } # Add this line for process management process service-dynamic { run /etc/exabgp/exabgp_pipe_provider.sh; } } ``` For PIPE API we need create this script: ```vim /etc/exabgp/exabgp_pipe_provider.sh``` Script code here: ```bash ```#!/bin/sh FIFO="/var/run/exabgp.cmd" rm -f $FIFO mkfifo $FIFO tail -f $FIFO ``` Set exec flag for script: ```chmod +x /etc/exabgp/exabgp_pipe_provider.sh``` Run ExaBGP: ```bash env exabgp.daemon.user=root exabgp.daemon.daemonize=true exabgp.daemon.pid=/var/run/exabgp.pid exabgp.log.destination=/var/log/exabgp.log exabgp /etc/exabgp_blackhole.conf ``` fastnetmon-1.1.3+dfsg/docs/FINE_TUNING.md000066400000000000000000000101211313534057500176710ustar00rootroot00000000000000I recommend you to disable CPU freq scaling for gain max performance (max frequency): ```bash echo performance | tee /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor ``` You can use script [irq_balance_manually.sh](https://github.com/FastVPSEestiOu/fastnetmon/blob/master/src/irq_balance_manually.sh) for irq balancing on heavy loaded networks. Running tool without root permissions: ```bash useradd fastnetmon setcap cap_net_admin+eip fastnetmon su fastnetmon ./fastnetmon eth0,eth1 ``` Please keep in mind when run tool on OpenVZ because without root permissions tool can't get all VE ips and you should pass it explicitly. Debugging flags. DUMP_ALL_PACKETS will enable all packets dumping to /var/log/fastnetmon.log. It's very useful for testing tool on non standard platforms. ```bash DUMP_ALL_PACKETS=yes ./fastnetmon ``` If you want to dump only "other" (we could not detect direction for this packets) packets, please use: DUMP_OTHER_PACKETS. Recommended configuration options for ixgbe Intel X540 driver (netmap mode): ```bash cat /etc/modprobe.d/ixgbe.conf options ixgbe IntMode=2,2 MQ=1,1 DCA=2,2 RSS=8,8 VMDQ=0,0 max_vfs=0,0 L2LBen=0,0 InterruptThrottleRate=1,1 FCoE=0,0 LRO=1,1 allow_unsupported_sfp=0,0 ``` I got very big packet size (more than mtu) in attack log? In PF_RING this behaviour will be related with offload features of NIC. For Intel 82599 I recommend disable all offload: ```bash ethtool -K eth0 gro off gso off tso off ``` How I can compile FastNetMon without PF_RING support? ```bash cmake .. -DDISABLE_PF_RING_SUPPORT=ON ``` If you saw intel_idle in perf top with red higlihting you can disable it with following kernel params (more details you can find Performance_Tuning_Guide_for_Mellanox_Network_Adapters.pdf): ```bash intel_idle.max_cstate=0 processor.max_cstate=1 ``` If you want build with clang: ```bash cmake -DCMAKE_C_COMPILER=/usr/bin/clang -DCMAKE_CXX_COMPILER=/usr/bin/clang++ .. ``` If tou want build tool with debug info: ```bash cmake -DCMAKE_BUILD_TYPE=Debug .. ``` If you want speedup build process please build with ninja instead of make: ```bash apt-get install -y ninja-build cd build cmake -GNinja .. ninja ``` Ninja use all CPUs for build process: ```bash 1 [||||||||||||||||||||||||||||||||||||||||||||||100.0%] Tasks: 53, 103 thr, 64 kthr; 6 running 2 [||||||||||||||||||||||||||||||||||||||||||||||100.0%] Load average: 1.32 0.45 0.19 3 [||||||||||||||||||||||||||||||||||||||||||||||100.0%] Uptime: 1 day, 12:58:40 4 [||||||||||||||||||||||||||||||||||||||||||||||100.0%] ``` Build script for reading Netflow (v5, v9, ipfix) data from pcap dump: ```bash cmake .. -DBUILD_PCAP_READER=ON ``` Run pcap data: ```bash ./fastnetmon_pcap_reader sflow dump.pcap ./fastnetmon_pcap_reader netflow dump.pcap ``` How to run tests? Compile and install Google Test Library: ```bash cd /usr/src/ wget https://googletest.googlecode.com/files/gtest-1.7.0.zip unzip gtest-1.7.0.zip cd gtest-1.7.0 mkdir build cd build cmake .. mkdir /opt/gtest mkdir /opt/gtest/lib cp -R ../include/ /opt/gtest/ cp libgtest_main.a libgtest.a /opt/gtest/lib/ ``` Build and run tests: ```bash cmake -DBUILD_TESTS=ON .. ./fastnetmon_tests ``` Build script for running packet capture plugins without analyzer backend: ```bash cmake .. -DBUILD_PLUGIN_RUNNER=ON ``` Examples for different plugins (plugin name could be netflow, netmap, sflow, pfring, pcap): ```bash ./fastnetmon_plugin_runner netflow ``` How to collect data for debugging netflow: ```bash tcpdump -w /root/netflow_data.pcap -n 'udp dst port 2055' ``` How to collect data for debugging sFLOW: ```bash tcpdump -w /root/sflow_data.pcap -n 'udp dst port 6343' ``` Performance tuning: - Do not use short prefixes (lesser then /24) - Do not use extremely big prefixes (/8, /16) because memory consumption will be very big How I can enable ZC support for PF_RING? Please install DNA/ZC dreivers, load they and add interface name with zc prefix in config file (i.e. zc:eth3) For development new code, please check .clang-format as code guide example. You can find more info and graphics [here](http://forum.nag.ru/forum/index.php?showtopic=89703) fastnetmon-1.1.3+dfsg/docs/FastNetMon_Reference_Russian.pdf000066400000000000000000004117101313534057500237460ustar00rootroot00000000000000%PDF-1.3 % 4 0 obj << /Length 5 0 R /Filter /FlateDecode >> stream x\h0HsY 66ݐTũYw׸ {t.Iʒ_[|c^kͪڛ}]s{ebṅ_)W^xZl?r*ׇþ yw~*ա2va+֕mZ.nxo/ wY❀Y6.Z+sqi6Oqqm>dwƾw95?GY92l]=6l55U 9ʀ6-R3_ԱN.^|;W/jLX 7t..wO 5Óp_5(5#vqX{,WX({MwcbCB2QV_~xө95lLS YWC)ʮ$!Xj^.APͰ-Q]mOI40,A;0s7<N`/G))Zw!B+fi si{8=S18md}:e]ա,%p/7ؠC.Uq6ӂ#lo0)X8&oyH3Y[Auy=@:A (,,w!%}i)S Pk(W[ +&^+jtF.\P O&£ .Upz>i$uOH@q>X{P3xVcW oe`C)&A~7@wQa^+G/ΰ6Yg߸L)@-,2Q#3)vYWC0"( 9w\rZ8ll+q4,9j4Y2,ϼ W1 `* Kc]7rS& Eb.nױcFDٴw~]іM~*^)˒y1/ |n$ c)Չ{,bBJ)$ģE)P@a%p3npt c=`=k>E^ dW$+‚p5I (A:P蹒pDyK6LتWBSLH ,$@teKH.PC8<3SYVa?h }0c1eEvv=5ZT dEN%)̩|oXI_0RUEnT[(G[O =ޤQnG !@t$Ep2)E\ `{!X(EK7Pvw Lhqv~a\ů "=* bYlGf}] hq~q 5 y$Mid0p%2GߌquaF7n@%ܒlD7:6iOs\$Se TX.wK,W,lx"BxםJk,Wrby(eE= cti%'Hps^ 8 oc7sԨu3|baq⭨e s4drbQr'F*>!|M8czp< (MDMvkZzOy 33dҥ,B&-0@ěBL-f5{i{<^` 5Weï( 5L̓e* 1|5Gƍ+X[d:C׋X0hS4=DPۥ/R;/;|,!~2.JK_ܗv1*,UNvE8Vnm`'Px㙯~Eǩ/:D.RlKXap)=<ְ+ WPb,7sz?;Ea-[GGRpdkrrp\ j[BKw&S-'5upR!I#V;u}Q͇oȔ/v5iI̟qw$ұ:iNގ()w:3%hf.qBz)r(0{dl ܫY&7@ZxSlI}c "#,,:6՛qPu%e,N \B\牸F><$%ONM[m?/PC^/9=g .#p|c `r @{a@}h\BvyEIvdxS;`yć@`>toGlR h+Fi`O56t`phQne*n@U9X8˞=V]FE3iFXV=GS?h44Q2 0wX4@)Ðϫ}2>/Z?أ-,}G79T%}ҵt=<Ǽo1><9oo*bڀ΢Vu)M>u s?|LB7l/^8p'yL:m|. #XHnYr>»ׁBpVxu¹|gONc ( }~AV~{hײȘ$ivq~F´s $}hojI'|a7WzLE@"rG=NV&2l½"i+L _}G&B K_Q!?S3t<1#p2pd; j(yb3}h1"~f &]H+f}1G+!!J9R5jmkUڈxܟ3#Vivq:OŖ؁=&JP+Vnl&sn#'b3 ⬭DY۞$jA6擩FIcGX2qaY􁄐$HR\L 9@$I1>,vcz1ssԸH%LGL\5m}^Vecw@D7s61p~x61" -!?!c~e1\킈vgϔ/IRRþQ>e&4WMI)9Q24{xs9J·AigWxvc]oݥ~<_)RgjvY(?B1q{{.GdyȯWI3?Em,B TrثO/ac}.Ώ `8(6C!3E79a, l)Ndb'aTsPF:7_A柜 T`lMs]D]7.F[ջXRhIĒ̊λ=ʷc`=9Yv?ΛH'&'љ endstream endobj 5 0 obj 4200 endobj 2 0 obj << /Type /Page /Parent 3 0 R /Resources 6 0 R /Contents 4 0 R /MediaBox [0 0 595.28 841.89] /Annots 13 0 R >> endobj 6 0 obj << /ProcSet [ /PDF /Text ] /ColorSpace << /Cs1 7 0 R /Cs2 10 0 R >> /Font << /TT3 11 0 R /TT4 12 0 R /TT1 8 0 R /TT2 9 0 R >> >> endobj 13 0 obj [ 14 0 R ] endobj 15 0 obj << /Length 16 0 R /N 3 /Alternate /DeviceRGB /Filter /FlateDecode >> stream xwTSϽ7" %z ;HQIP&vDF)VdTG"cE b PQDE݌k 5ޚYg}׺PtX4X\XffGD=HƳ.d,P&s"7C$ E6<~&S2)212 "įl+ɘ&Y4Pޚ%ᣌ\%g|eTI(L0_&l2E9r9hxgIbטifSb1+MxL 0oE%YmhYh~S=zU&ϞAYl/$ZUm@O ޜl^ ' lsk.+7oʿ9V;?#I3eE妧KD d9i,UQ h A1vjpԁzN6p\W p G@ K0ށiABZyCAP8C@&*CP=#t] 4}a ٰ;GDxJ>,_“@FXDBX$!k"EHqaYbVabJ0՘cVL6f3bձX'?v 6-V``[a;p~\2n5׌ &x*sb|! ߏƿ' Zk! $l$T4QOt"y\b)AI&NI$R$)TIj"]&=&!:dGrY@^O$ _%?P(&OJEBN9J@y@yCR nXZOD}J}/G3ɭk{%Oחw_.'_!JQ@SVF=IEbbbb5Q%O@%!BӥyҸM:e0G7ӓ e%e[(R0`3R46i^)*n*|"fLUo՝mO0j&jajj.ϧwϝ_4갺zj=U45nɚ4ǴhZ ZZ^0Tf%9->ݫ=cXgN].[7A\SwBOK/X/_Q>QG[ `Aaac#*Z;8cq>[&IIMST`ϴ kh&45ǢYYF֠9<|y+ =X_,,S-,Y)YXmĚk]c}džjcΦ浭-v};]N"&1=xtv(}'{'IߝY) Σ -rqr.d._xpUەZM׍vm=+KGǔ ^WWbj>:>>>v}/avO8 FV> 2 u/_$\BCv< 5 ]s.,4&yUx~xw-bEDCĻHGKwFGEGME{EEKX,YFZ ={$vrK .3\rϮ_Yq*©L_wד+]eD]cIIIOAu_䩔)3ѩiB%a+]3='/40CiU@ёL(sYfLH$%Y jgGeQn~5f5wugv5k֮\۹Nw]m mHFˍenQQ`hBBQ-[lllfjۗ"^bO%ܒY}WwvwXbY^Ю]WVa[q`id2JjGէ{׿m>PkAma꺿g_DHGGu;776ƱqoC{P38!9 ҝˁ^r۽Ug9];}}_~imp㭎}]/}.{^=}^?z8hc' O*?f`ϳgC/Oϩ+FFGGόzˌㅿ)ѫ~wgbk?Jި9mdwi獵ޫ?cǑOO?w| x&mf endstream endobj 16 0 obj 2612 endobj 7 0 obj [ /ICCBased 15 0 R ] endobj 17 0 obj << /Length 18 0 R /N 1 /Alternate /DeviceGray /Filter /FlateDecode >> stream xWTS[@ W)#̀tDJ 6bH Ć.łeEE]l+kłŊe-tQPł W5rp矿,RH2v2+s|frh<W$e%&A )׿WBP^3_Pă\( @@h܄tr)Cl.kd`H2qKlnAHjb++E)!@ @xHC|_* q "?YT!/* qG05b(C%S _Kȱ8W3eBȓ=d gH!~.sRU+*N!6|I03yܘD!M fU*O$d9["'tA\!(R3raj4{@֗RrD(qB,C R`L2E2;˕E!H"J;A:P¿< ݀+Q.878ؐK r (2@"\-9W T|8g\"S{etrTRW0W!WڧNRZ%&8 GLHd>FIUGhb;aigӀ+Uu߉8!U (ZJޭj\fpJ$q&)-Vư\6EĻt`oU䋠 UzouIvw b}[aS9ح|NiU2}e=}h `oNR/DIws/yPeSfLIXɂ+W -2W( H(w%굤L|UҞ fj6()D{hQudT:lH ?W܍|y;8}2S> Dõ~f>O9!!?1oS,Rם@Ut ݅Cg |amqtMwGG|ǧ!"MeB+?Uɔa#D2iDh44S/T5th ƕeU!0ѽ`B&(s^s:s`GK aØrcC8Uźp5G$;#Nue!GQA$Q.`/^(-rr L bz¯q!xx  H$WR CO%M'UIuӰ^dc?Lry&y1y#y82yBQ\) "TPQvSQPz(o445444$;5j\x1,\MEfUD Rk ;ZZZZZIZ"Zkjz=Q[D{q[/h4#-EӖi'ihot:ΧϡWOu4utX:uJuu\u ֭mֽ;g穗WXo9^}~>_V 0;p|m z ɆN<* ; 􍼍ҍf12ƌ9b34aL4\1ym:4T`ZiǴ,,l!渹ytMGQ9b_-P d[---,ٖRu'-¬VY6Y>fa`1ČS h!['4r=wvv9vZٗAAơᵣcBCNNR]NwiΡӜk$?rK.Х+*rzٍ&qur/vaQq(QYj/mO}r^.^<kiF4{Mq> }Z}6emol)`lo}r;i`̶1m[B!!߇tڄrCkCمFXYO2:<0|V,Q>^mTnԮ{&x4):6zy %ǩ ̊9>~K,e:.fqw%@'aeDi?'j&{&%LIٙ*uliit3"2Vdt5~ 晢̦,JVzV] 'LX1$I3&l>Y<))I;sykxOaU>A`QNpΊܕ}Pa_.Z/z9u~B f$_rЪpFeB5-piXY]R4In)lW8+Qt> %.%JF0ɛZfS6{k֖쩳[Y0g.{yy~)g(9?c~s<ͮ z ſ}۱hu>T+W1/->wk$gIRߥI]_| +qcU媗>W]y ubMڸM-[~p}gؚ=,6,z#Ma6[n{74:Vo%o-p[3sd{׎wZ\ ݥշ{K?Fްe񞪽`b}z@O?m8hpi,i8$<Քt9%?o?lsёGGxq'Ni}rkNu=}Lԙmcg>xC|/4痃.6] ryWBq5ε 񝗯]yc⍮ķZz^yWn={=]]G#ܿoEY^}Q}OxDdw7> stream x$GuǿS^j`z5ӳ!-!@x'^!-#$]Ir<YY̺ `G8T7oovWs}>W}U{ozC}sln:pe k7퟼ nnNW7'wunܽߺ;Um}yuw/ð4aersjΌ FYDwYvG"(8u $)KVSr]'YͷABռy8~:гO]LdϿҥS]y#x|lհÃ&!jK D =ͽ\zw`L7s{J+|C/IPdDe|?p,p9]s4׽5Y"!):xRVN׽`İn~4wr:ďX3lp8e.[)gtoϞOB _qمjߣIg`[م_}`a]e+$\?f:+̓Dq=SZ$Y;ǭ(9s'K `G)nQ2~bhjYHf%y%E }$ T)",Ty dR%6CoɎ8g.A.gR@hf˖VWKbQSA6Ϛ_|I?WRl4o.i۸JФi;vHl2h&NX?:!K:Ql4h>_5ІBYL yC.5٤Iw8W:l pj}Q7`q.=b]6p <v,ѼD}Ra%tj,tc8Ҭ$lqhiQz޿'~β%J{oUfqUN`EιV:kQ,@ `v3\&֚Fi=Z1Fy+č)N;I WvƜ3NnEQFy//?[(w)J DKIUxܟkS=ӽΕK>0V̵T¦&*jdڔ؛UK]4`ZݥY)agɧe[}AMjq1>%O8 tyvGN^g̬50!lH3%=+ ZT؎s}Oe 8E4hy>~l'U4pbACTG=I, `FUn~2abUoIUL՘r!^!Fp*7heEXbwnh5jWHtۡJ0-1lw"ܞ9Fۥ}Q:dk ҤW6qEjWxt 5Bfx4ъhT@54oC׉:#wւGو@i\̕O7͇AV&iE䂱m8)|˾'p7gla)$aځh, $aieP0eM[nrbbX6k~[e4η*!.?NxF n\*|g01'xW =JuC}aBc9FBS !4D  S}M8j 4p2t\dB,+0]ap(iEsó I%gkLgc!ėnn*A`pÁc'qXғ;^.t=jjY9H*gb Z#YQ10yӦY` TFLr*W4Xoּ;H}+ĽeJO xI˴_t+jMB >3ɣiWD3`].|cFgm IiQ dT"Ynm;a]gqXD\5̶ 794. Xb%!(0?a3$ Mem1"ؔ8\Z2T Yf8}*a-Et9q&ʣ-iشZe @BqS d$0L%摌a&Pw%{P=?;[oxҒs]yԔ{",`|s&nͫI,sœE ʑ*j< U ;WCE0}rz$'ա5X$6"iUV4(ozY:jJ$m%q_ťzd~LҢ+M6 H> i&7;Hƒ±llraɨ4a #о<[s/7bHLR :-u܎m{vdR-M冘O M0o1{ >"GZ< {2ڻjIBb &wg.o0XŢ tr T3fc P?xXbȥ;¸%7y"B7Y΅88Mڀ Q o ZڼZ{Y|BzΥ6u{y{L˲m8q\#Osܚ =ߡ B ͢+xyimr J5]i44bQ~@"/&ٸ޸P oQTX-Sn&Ot==1 1b$Jȴ|K[ŀmh9 ^ӫ}T^JmN|~;+ ~ڡy\+Db^:+i8ɁVꔩm/$K`{GnK{Nv܍vco͊^cnaWm&|tFC[]ĨJewܱq毤< \0Q<3d' -Z=Lӛ|H_cb hnP_7i$Pל A/Myغ<[ 7 Z0@:%8SI/%"40 V6@1rroڠ{VSa+QҐ*Q)4 \_TdAXJOR EHgȚǭa+^ ~P/u(^ClHNJfJ3"aʢNiNYZ:^3KR5zXK oDVi k϶FLVI2h.kWqT=-}4ѲE%T.EB85q0; :8n0+ %I4"( .3n]P79CJ "c(\+n?FP.N˶,ȼ%,˽r7vPQ)<2jԨO9W#Ʃa>v*$ zެ8Qt)iaӄQ$8/ 4^ՈQ"$ub߮uA~d<+ F蝒O{,6ݽ톦{D)8ao%B*|edi`eE-֓>hWog٢ٙGtR?Bt<ƙ2h-Zků [",|m2_ E>b4!lpI gܯw:B_$RS_0eȽ eˣ:a<˾plrSs_w_VsNR6;TmЊfFqjarȹ+&@lNkLG7".i[?bs2: l{~$]~c55*,@`"YX:>KzMBqT0hߦw>1Ln8k &C$1^ 5ά]StKי{x 8Q@7θk_iٖ K>1UX2w؊LW}|N,w$dư}uG2_>D<]1@$kDjmq]4)}T "q/>h* oʩTڪTas S7иwƕ'< endstream endobj 21 0 obj 6143 endobj 19 0 obj << /Type /Page /Parent 3 0 R /Resources 22 0 R /Contents 20 0 R /MediaBox [0 0 595.28 841.89] /Annots 23 0 R >> endobj 22 0 obj << /ProcSet [ /PDF /Text ] /ColorSpace << /Cs1 7 0 R /Cs2 10 0 R >> /Font << /TT1 8 0 R /TT2 9 0 R /TT3 11 0 R /TT4 12 0 R >> >> endobj 23 0 obj [ 24 0 R ] endobj 26 0 obj << /Length 27 0 R /Filter /FlateDecode >> stream x\OI`цazfz.C*xRS)XlkKLZɫH:w-͐E:\$wwZv9]:[on¿ky۬F^?j9}S?;~fڝVis^~zw|M}6C& MMnuOMU <Z+*MNfzRvw=ݛ?~?>~/}^6FSG ?tq7p 9^ن[m ?NPiXc}$um&WKT2Jqߧ8d{IGR[rj?nƁêմ@,xkWA(YHb =.j^3hP26~Xq/ lDS嘷=e8R(Mg 3#ƺOvtZ'h,z\BZ/Q/* x1iĀ̌6s-$` :CRNajG L$PM]*)%tVQujuM*8ׅ/Q=C&U" E]m)&a0  ݂_"=O<+wA{2\fSIGS]I1B_A5+I4RaYZ̈́vmu xX%h";+YmXIKKP~O립0)=h!Ӛ- > YEYr"1ˮu%-]@bIS=t5 ^1 /ӵ^>[%jnFK* ˱*ƛB3dq*U0,H bs£s*|k!&a,'O`ө~Rjmn5#߅! @g^E"6/q[eބʪj .:+9 /W^Fc+\j8,J0Yr'r0ACaׁ|?(te3C+7LUiZm}u4a=Nu׳fS@5u-20/%tUȢ`ek>'O!PrB=5-9A49Z5B ŐZSĐFCȑR%5l?XZ%ɇC5CmX௽Ox̹h L_ Bvhi˥&\qE(5#҆ jHN2FZhMyhts|ApO/TC"(eˋAmWϹ+)lm* d&bTxEkMŨ`&-1EѯS0w|6EWMY঳>&趨HM]@yƭ(AYT k:{4LvuZ4|3@p!EJp 9_ JROGNbxϨoYqVnzt F~t.v*K(2Lʘ#[6HXMg g!WboZ!ؚaxl =3dba>Gٹ䌚 2wyK2SN$A ^iY9DvyvF,2 w9(l4HWw{8'pFVlY-2c`cEzaH`W…R^&]B֍ժ.5{WȭڞHKy-{,󶚱-mnGҕ Z Hit.-qf)TwY7V4["ёX%AccJ+e`8/G4VItksJ*"Kl2IьqʙBQUe:oף溘1_=qIe8~SItΆ&7%D +?ɰz,E DNpŋo/V!^χWjwOȒO} =gS@1ec,F9r  QIF߃z3eU\C)-% k^fWd{ZSrvBb5},gA0rkiY>qG}3Y'gfzCqLԗm?}hO PLǡY<1m'ЍCqKWX\\V{oY^]Qp=1njQwyGc'{4E ߅.xw { O 0JhZ r6>cX|9=?m=;9k9N&W31<& D)JCV_߉2Y9|a]Q>@xY0~PdZwtn]hB&&!b[ EV=:l9Rő ,RT eJ~,JRI}2UAa;5sz790Ș-]*YĹh0 ;'9-m5ĽGI}=פJ.qÌQ- 7z8>|#{KroHX>\ȭH4ڄ΅4)ٙr6ȶFc g 48g*ɇ{<#Һj3M>}@c̣#kŅDeTY׎  +ch%}v 36F@0|OPخ8SFo>#¯&'Б{NڛeZA69s~O!W!iL%d.uBeS)E"8WBDIfiQ]kqi1/1:gȩld;ds6r(xb۱K)(PKuyu GE{aZM4 s}tѝ? D%ү:@^⣅ 㬢[B&P E `Pt#5 _k$٧Ƈvu3p2*gK[}{9MI;JX5) Gmх>2P endstream endobj 27 0 obj 4846 endobj 25 0 obj << /Type /Page /Parent 3 0 R /Resources 28 0 R /Contents 26 0 R /MediaBox [0 0 595.28 841.89] /Annots 29 0 R >> endobj 28 0 obj << /ProcSet [ /PDF /Text ] /ColorSpace << /Cs1 7 0 R /Cs2 10 0 R >> /Font << /TT1 8 0 R /TT2 9 0 R /TT3 11 0 R /TT4 12 0 R >> >> endobj 29 0 obj [ 30 0 R ] endobj 32 0 obj << /Length 33 0 R /Filter /FlateDecode >> stream x]H7TTa`=;M&x`7lόM,ūI|I)o ~AG˺dw.R}~~pns{>v7KeMw]N}-pcJnOlx/};nmeط䞺}+%T?y!no|:00|piݓo]y g~=t. .<8&x1@8r[M |fFY_>G=xе;z `MM !1L p"m?[ye;onӥgF,ZqUٌ^lj9q k b?"IߝieCBH{Pc,sh#8a$ tl&_(O{Dպ(wბuztC^1j;NDIS t"9RqUfnGsP֝0+R*lqq3_/a!i.ԚwZ&| L bAb ]^,T$ b A/1a4@NNj2uHL\)| ̵zc'6 J8k#UVE4=^ 3w# lв,$4>&(ϧyEvJa y%mz $0(+$fgv1YԶTc* L8F Q޷=JGYpBYѲA֒4B=% yP{GBNݗ08+Np;ݨ RtP"$IG i٢H Q[s6y.(R5 X0C cXzsZ]AA}JCw0܉6 sQQoʲh8(&HUTiXdzJyP" ^DfN}Nt7 r20ҏB>2y+:\Ul R NI*xP1sn]:f,BVx<%L BJ Vx4Ė&mpX0 ϟnG7.:˧144f#V57;<F^_D%]I50JQ1Qb`.`8<$R'8 B{ߔJEq_̚'B0 qw$l궁6xܢDbx^TWڴ GyW]CcgWmw{sbl/p3ǔݗ8k˅A/Mxϯ t5fT@7g͸SoS͔OA؉J2(BZidzah?߈$YVhlL*'O89|KԔ>۸9 =d6!24Ӽ@tv,q(%tAqKL0פ%Q$) xs,s*gE՚*hduua׊Zʁ8S@)cڠ.H/2xV!Dutw72,񚼏>@g[d{?R=uExOScYrulNV xnV@ia/׆ef\'9Xz%t]ظ0E:fbYYGdx/,3U3U6QHI Y($T3vPAdvVE/̾v;ϞH*j[ݸ=z|LMX'8W7`2JEK1 ISύ^Q}Bؽv^B+qp``NBbBn 9g2$ztc0Lrԗ6UN f =: 6L=ӷ_{=K$3I]?i߁78x?k:Tڡ#[!z{h>O۰R.TeɚxC.a=+A̔ .ÎnegH-PK+5}6cO:oldج<̇2OT~2SWk\ȢZATY@:)~*׭ -0#S-I0:TX#``& 6q1f+^(uqQKqcCLy7s2ePz̲'\BHh:lM(GB / Vu$;Fyr tm᪣(3Xm" 8 $t hL cѤHH0S:0qPoj djcN5ҴZ| .:]|Ɓ@}p8me-Y{dHFx!3aS i k]61-JG2=^ʪRk߬T,+F /Q-N̸G0oNiPS)/r,yq\3푦-1d" z ^jlCJ'*Æ'80{4_5$1?|U\\4S6-Fh6s):NxfK a{Sg?H=X2azqkAʆg E.n/ Zb3ۉ @ C/n?X8>hr% "|k4-J~\`3SufX'm4/HMdfCXY8X{q /b5E˕gw ?n_|ƩW8Ã8dMyPJ{/v8^ū_?`r5\c װfQֱ-ӕgi!MI[\9'vRyxڣ4++2pM} $Z)viZ}[ X Ka~C\xlN b~G-,(Ez&qsKMERFw@LhŪ&,c)G IX5Oƫ$^"ed}|\ݓux(k-l6H/XTղIMrԞW>NkK;"\Qm`Pye~Q tKrM6C&-u`'2@No/2%C21 {={Նa-ZNNN-آC`ܥ&;2mEu:~7+@*)\5A ?E˺`^۔A+tUo7,r=4[2wW`ȩ)\R'tŬֶN#Ժ9ֿ[R^i-znr\Y|թUk~SYO%-{3|B TP:<( L&*(tڨ8v(4$1gR:b_T%jH)h*~WT_ k5cv.Re>v h!)]aFrXT᭓ : "Qyfri+Nw8V~N"^h#8ƞX%(AI.GDE*"UFvrJoP3C,oi |o-mG,#* /mOz$Z[;q3x}t tZd1sof2mtqJ:Ԯ)^cicPHqAh^1ΫI&|h|R .ޢdd 3`_ZiQߴ($u'@.^_m /7GyUX q<7L4({Exah.^2m@ܟ%=tPq#>E,ڿLq}S.N]3;31Z'Wg}a5w'3鍱k\ VPK6b 91Yml,fk$LиPg]ꂸ7q~3;\b&~Α8=8eu/Ť8:wS q͵:FLj̰'DZ4,, 4*S*g<Ý{ oHP`z,F(ٓ@z48ĥQ*9wqC A*:0RFYF "hS1&1lQu9;ɸg)Wj | Byjp+!*~Ʊ;ξg}e;̖~Ra ,4]w%]9#ȧ/5gHھG8~-.hVql׺ԝuiD⎩:5t)zZ t/ R$Uҹ*cd.)H.I$**r%lb,I+S,:ZDcy&ʧW!uGJxѦ\ S$~(,F ήzˎ wl.}h#816fhvƢie kᆬޜ֫X_KK{)*.Ť5h@-SEkS`JQpsNĂ.$+8&kxV̍"P-:} ĒŒܨ4#4 VT6R4O0t -KEAv8V_E&7bȑ&nYX *SiWk?qg~`)c|^e"%)?Xu@y1Ҫ(fծLcvg@4JYN9R.Uwzپym~@9Hɫ`zmԏbSYAeP@w%P{R>{QjhQ@u6WB8 i^<ο"ĉH6x|`n^{F } 2\0Ѣ\aP2o lt8DF&uP endstream endobj 33 0 obj 6318 endobj 31 0 obj << /Type /Page /Parent 3 0 R /Resources 34 0 R /Contents 32 0 R /MediaBox [0 0 595.28 841.89] /Annots 36 0 R >> endobj 34 0 obj << /ProcSet [ /PDF /Text ] /ColorSpace << /Cs1 7 0 R /Cs2 10 0 R >> /Font << /TT3 11 0 R /TT4 12 0 R /TT5 35 0 R /TT1 8 0 R /TT2 9 0 R >> >> endobj 36 0 obj [ 37 0 R ] endobj 39 0 obj << /Length 40 0 R /Filter /FlateDecode >> stream x]i䶑_A6*5U]m%[ [p#cY_%@u漽}= 9dzt:lO}q]Vx,w^3)}Aƹko# £/qs| }  F~goe<8@yIr:gϚVJ?~)94CR@ajL1gΒ=G^Ď7.1V5K1Krܓ,'j\x"c.,nDwM uf?U.GSt.WD[%s)u{:.,p8/)Q7bxV$KL 3_aG+MЗx1P0xEiC/4[`"nT[g5dF_7Z_mpHb=0ox1? L<<n­MxAa㡔g9}LsiEFV 2X i[Y&<]WD}y$WHB)aXtW+h$i*4HF[lX~zAe*5~,k%^崄}Ӑ>&Qpfh5qҸel=Iz%Zb5F "p,XE#6 1Y=)J aUqVO|Z"6Rz;-ݾܚ2K!X4VȚnjO=ޡnR;Pɵ5YRYIwzY$a{/-YE s{ɺB0xFc= =3L11s`JP|=6{Sϰ"Jwy*5SzX10@wL"I_$ѼtJ sңJr()lΗaGLV2BZ-Ru2f ![h]ć $ r' K;sFzH*noϧ3ܙ3Uy k>+1i>p1ԕF,q_5&l; r_hX }_e0X`(((bMcnI0@-SL+1YiٜO%6Q*.'6Fl:Kĝ-PR9E! <#L UdHơE8IE 3 9] V>qo{L=hHl%"tt@ 63x *æl|WޣjǬF=jeʰ9EϘzl 2y +nxݐŊj -ЯL5pQ8 /HzT?O=]Nҁ?m87,RV ^GX\m7 B_+Y(?b+f"- /%QV 'Pnj3W ӢY3H~3XѪaM~eF q%˒kޖۤrO*t*3XKd؊ۘQcG3RWUPT^nXjfxkIB@N2\&SgMݭ|бtJ<&H-W)Pwxf~0'w iA)*0-)<\²l"!s4^΅; H$ZŽH8ohUU7̲{g}Tro&S8LQ]3)+MOoшM.`,J<庾< ȱ). -( j S!1(kq>eڡ)i1'8yaFbp(`aSxv [xO5ʊ B*2tcJPgtT1g jVlSTEbQ;Xȍϸ2"i0~;${* $9.A))seVH޿;8E\TՁ{8[L b8$}$VsfqLN'hN ajqECZVp2X$k嶞'&<9* G0筝~,*qO\=:]F3z0x\f'+&=nzҌT(r`cMV4DZ%7b)(תew8璲eE 'g ~uʸ6Q :u'l6꘥g<7 961ؠ$ 0&>aoT`Y˪*en{BK>CU''R~߅} xl˫.DCqD.mwx v|AuFH4B1qOFc_ʓ)'2}`zY9U+8>G&!@;ѯpk0Dֆlj3%ys0ЯXj2I bcRTD-ox!tWSy Ff nͷ)G)RԎ&E*1rz ҉zbqm~ ~ :I&ҷJ`2U&1cɠ;(H!6S)NWR%%CIZDcء)f.%Hm9O!/ ڇ<QݪqK1Jq0SMF>w, US ve.eF:d5Y^#Vڒ/@NmA@}EBvZ>׽e Z[8cGΐn  9#@/-ZүWZ|˪WU{abV)Z.~YBea)z> endobj 41 0 obj << /ProcSet [ /PDF /Text ] /ColorSpace << /Cs1 7 0 R /Cs2 10 0 R >> /Font << /TT1 8 0 R /TT2 9 0 R /TT3 11 0 R /TT4 12 0 R >> >> endobj 42 0 obj [ 43 0 R 44 0 R ] endobj 46 0 obj << /Length 47 0 R /Filter /FlateDecode >> stream x]ۖq}h_`>e$9Kf(6o"]w /,=y(TU(1߷_߷;o =}}}凮}>·vuky6n]JN]vҷQ.N]{Cߞih&{vtH/;qlt:ܞ}<m_{r[OOϮƫC{4oP{vpz1^.kclOɪ R@4>w0? T7?ċ5]йX+L^ggwx p3\+5vAe2(5!†p[_V_0Qm 7 ex 25ς:Љpi~tǎcM<J~-sZw[{%`1xGzU۝n-Jo]o4;/S"+NyCd6#͚^U!ZhGzHꁳʮ#@$$*#1#12~X229!7{Ulna( haPGD[9!=mmB+SF\p+zA# ١;%Vk@ߔVR?ާn 9/80y=]PX1}}{4WJy<'87p1Garxq$W$آK.5tMU{5%,FZ\Q\),ˡbNsI4B5}M9AH21˻'g.%w 4`wu&OUn zQntShX-]&e!#8e=]V8vz sZ* :j^Sso-< n 3aM>d^)|#;O7r19S6'H',ЧRPB 14FM),V4:R8Ss=<MّeZDpV=7E).8rC4}83!z۸5w8%Qi0XV [W΃0 CF90tgcyCj|crN@ќ4- r|d?*KX#z+H^ٌ ~q %t3ɦPu;R-BF~J.d" I@"Ie]A'=+_ ZRC4!xN눑&o  \NS12 4,XyEJSF+Ib7S›bl$q(R̘~*n2uShiŠX20tE/-(!IM[6"3'N |FhJq(P FsQ鵅JzY ׃3V*lr(s?ʆÝhYCr(-GT* KUf/eFMziVLlAـb;&qЕC|/nEh>c%9 cvWL3 p B)[حBb0 Tiy 2[#vDЊe݃F,rl'e/VjCٜ'h`Z gNqG4MM5rljfDbҶmQ6کBQapgC4"*4;A&YJ1SgEЎ c>W#IXG/l{c Pe*{D?g+X `(nas;_}خ*c!e9$RKF| av BZg mXu bm(,Eveq٫qWd{P2r6DgROB6Bpڴ9቉Z4kLEE3[Qɣ-v83L*w?K@;nh>q$_|fp§ /%a]N}^O˻.Fіd(?"I.0Ik6pq/kQ^š~ /-<;G w><dzኲyMaCK<# @(CO|B]Y&P,>EI9p OpW:[S3&͕R^>:R'[ʟNIŪIӡNB\,8pVeAkvظ;v/?YCP3yeRTd'>Mae4o|9bΜz8έ :98&2ãTn~-q]2&;  﫿pn(E].=Ň3#AU*6[doY:HT%6YE5l)Z'˭F`1$&-]t9}+ t6uhX-ޣ(? 4d ]rp8߂OFůC} |b c@=>K~0/Hm^;LlߴevgNlc KKؚix#c`%5 EF,Aum|ySoMǕ $kee}l0]T?5>* i5p|4ySF"aq_2=KI0 9s' 쪠DB;8V_[('M_J9RT[4uÇG5cHaJg̾x֙B `ChiD ]~^D\h d`/9޾>-Г+~3>>ZЬ{[zX!"^'^maE|3:`vZP ࠅl5[2 lOŘKK1 Af l)& eqzpBz mrp0Zj(_ ث 3#]u'GKڇY &(Ԇ6Ur[L^້z:ex"Qʆ1I'LKH#,hm P:W4ݲ !-nA'u;}LdXrXk%3lL[A /x"O.9ު}> c6"Ud$1;y Wꚩv61ad: į[ZXI̹cнBV',DS_ d26H{݋9acfKZ UQ6d(#@Kmâ7g `URNyRP}ǟ|c-+!i0/e \|gYEZC+>4a#/塀nFL@e0%DZ:@QD| 6RdSrwYA{xbr#0G>&k%v(;r#RZR`hag,'&Vz , Cw' 2)WRJZgS=Y kJEG3xW3Vڏ64[2Fe[ݚC_mS1 i450#db?C 1V0f2;=Da@Wdݾ߲ vbUBda~^ʝ"c&-Ƞ$C_Ha+tDK$ظ׍d'2&f}ѲԌcX]>Ky/G9sOB*E8@Lm -Lۏ/b+ŅDscCK{wU[2wuKK|Y$a_ (>- gI# t(熆h8>yM:2z-:`:y L?|))-防Q+:h yzWda>@kj? J80hQQM(N _8}^&+mUOc:GZI- cS .OyzaygFTѥbWW;fe=Vd22Zپw8\V/RPFcЭΞyx? /q5}u|-QqgJd1`Yzw?hou)zOQj\p{TcgUX mHOC[7*` vùd,\L~|ԪQIֻv4X[8$NQ\S'rmѷf< &֡0ChqGyZ6SD#fU7JAzJm-P_\ SEG?m I+xZ"Vީi>q\mH%m8 i(m%͗@6h7 iv"8ycG&P `"RCr#IxlSҼ5:g+6:duS!r2K9Y}s<ԜdȘr_Td4'[*)#~`7 B9ꎐBy+7Rg_∓|+,[IQdI^L(_#j)tV‰zflTYLv@ |!S4eTۿno"̍ U2+۩-op r|>y-3^7bxVN_QL+BoI1I1t`?kf[ʄ'>ВB!R0&i$~JNIN.Z}@Ntk.hE^%6lC@SAE<^ڎ&Crm|JV QUFrbiXS d>7vGR@pqٚ5EW:_#ʌͺeWPa3$6]Ov>wtA3xҭMMpO,LV] !u!~ endstream endobj 47 0 obj 6219 endobj 45 0 obj << /Type /Page /Parent 3 0 R /Resources 48 0 R /Contents 46 0 R /MediaBox [0 0 595.28 841.89] /Annots 49 0 R >> endobj 48 0 obj << /ProcSet [ /PDF /Text ] /ColorSpace << /Cs1 7 0 R /Cs2 10 0 R >> /Font << /TT1 8 0 R /TT2 9 0 R /TT3 11 0 R /TT4 12 0 R >> >> endobj 49 0 obj [ 50 0 R ] endobj 52 0 obj << /Length 53 0 R /Filter /FlateDecode >> stream x]ْ}W`ݴtҀ#`  [W\sZ-YY*~yOi?\O/7?'0b;qw3#{6x8ǩfo^yiwߏ籟8zwޏq7\n5gp{pv$q<'Anwow_͟9\`?{wԻ[qa߁o܅74W̯ NYN9n1n`2%߷֨Āߊ0+ԄSD-TtP9l_L J7pE8 θLEaUN43մDTKKAq8҅ b&]*'HN&%lٟB3s+]>twσbKDvvS-% 5&8oq;μ%o|0gM]5SzxXK@$ՋMzU/?Wbfs05` ' Pb:6@1U U#JԆkp>@8 ܱ˹ KiWF8%Aә.ۢDU:2EP@81|ɞH$rl!j]"6B:,L9`R*7\ZZwȓ&JK"8[ †iJ*X"(PG[e拾%g-gRTO?BZ(v3‚,'qM#s0vh: [pQ*6e>@q|qՙ[ `; CS{ɡik4׆ruL@DGnނ/FFDU]i`tMofÄH`38*aN sW0q'AӿqgjF&J{lL>'Ÿx^Ԑ4': +E{QP8CO5U&nP*-%곈-L.kaeeM-:#C9ЇTRJ]=D3BMW RI+2jo`POI)BW9HV3h-ˈP[5)v [!T-U/K.gudtfݵtd>:.+A@wJ^GۃQ>Vb(5Rʷ4ȶ%pB+BiϩHL:5bOyWZv_ W ڊc^z4t$ȚF!ʁf0a #BEݎZq#ŦA2k4ct4CCgjd䉎c{i6WH+r:u uZoڈסvED־mˊdTk|s:B9p8qܤOH?UpȠ)MTyl2F+csPaJ]tSvLN)zG2 UߌRS3ɜ]0hnqԦjR )ɖĠaRIdbzWr1T;k84bUPuL<|R 9ǭeR2}\W Z-*p:{h&jWeJ/uhmv ;+Ѿ2ʹ{ {#'⒕F#Ru*|rNy.b~.c[HDV.jkVn~ݼijmX"tk8NHZP  $#JoviaLSX94"?2 jEWy$쏖 !Sͬe@z3#|*}2ˍXiy@ ǽŸIPv Vo3*Dq[n5},;IZaI-;A {kWx16,r'bcwz ʎ\di?bj;ˆoVf-$ ek5#@e8$0ަ@Y$ iƜM5& ɩd@/lδT(cd-^ag}q ;gUFh$!AkI`*KT- #x[0z] 5 3.+ >S #3BF8VPk| ߑo8kХ`nd:b &d">9X/"[a-qЂ| c6#e,&HV;t|5U-l>y&0e.sX#Yho fR vﴵ2Fpjt iv"ţqS~ȣ "U r,=X.O\X*TqW(IDC]CmJޠmjݵ-zNj SA| 0A唞\Bt{x( ΠDM Ƈk~xrB;Ob<8 17\1bم U Zo&ދo%̈́o?k)`yD3V몕1ux(3hkא|ȗiبúiv#VT&vdVG]c߱XcACf: @ؗF go㼇OO-Cһ/-agU '=\kr(U7䥗,}D $Vb6n5)h+mҼkۡMZj%)|q @ݛ=Tc_thc&:-V藋4ުhZg0E]݆чfA}x`ȫ l.f7\+c,~f)mmFgŸs|qu?R M6. m"7T8 Z>Z3y-_CCv~&TGVZ*Q endstream endobj 53 0 obj 5247 endobj 51 0 obj << /Type /Page /Parent 3 0 R /Resources 54 0 R /Contents 52 0 R /MediaBox [0 0 595.28 841.89] >> endobj 54 0 obj << /ProcSet [ /PDF /Text ] /ColorSpace << /Cs1 7 0 R /Cs2 10 0 R >> /Font << /TT1 8 0 R /TT2 9 0 R /TT3 11 0 R /TT4 12 0 R >> >> endobj 56 0 obj << /Length 57 0 R /Filter /FlateDecode >> stream x]ے$q}(H3ͮe"ЃWKYK_qsU2VUuMaw]nۇo7{=vcۅKu}׾!'yy]ηPz%0 nv8ɏs2?c;NL90|w鶽~xNGK]w9]8;FsyU[Cк}ʯ)I0;%D\Nj+<B+fME6mԭ{q9?s5K_se51i=8Vm&O#JmhDD+]׺ b (PMĨ$2\cÆ̭l4w6x^\U[6ɩMKLaщTopwNHg ?|~?<&~n e<Шx\.7\0\ Lm"  NV(@@N?#J@M=+{z5Wx9{>MQ\`%Ѷ^A?jK44R Z%H…?i$C\jP%6s\1 ) qhUapMH40 ׅ)u \6t=|t;Q ׷Fa&"*q&4`zєs"JŪH;EYَ}e(]o㐑K>55c%JqP5ȴmOa=Xq>?zIfM2+!v5 254{Z$Dؕ Z}3zAu:q|qB(Y*"S?L՗ї,AxstbF QyV[εԟ Wvwh.xsS)p0~*d>%~=p4o=Ҕuy`Y}Ȟ}-a]`YY5_qQ~l9yÅ8XFK".鸿+xOjƗ&/{RT. V0\ x 5,ox 7,}xq5 J:gZu)5!k-KBS[}^^$!; ipTy\zfV}UCˡp1ϤϚrjaª,k\ :,@\J;0ްR)ד/jj dEN"j |"SL-U*'iXOV) ޏzȫ rnGt>(~EKi1[$:a޼{lHeXmOޗ0jpE}'ew]Eρfka\+` M64etA ǡ QsL8f%x͠6[DH]/1TV{o`KfcO_MjИP$PeaE$LS Z2#FPEl,)Pz;RFu;QGi+=h*flUD~"(5M k*Y2jOY-9QlpO)K*o0X#/NbtJ92'flh1\fteDEP!ߡu::M4 a?Gxoi9%eJ`\~mׯ?õ7n>R>rl-DOM'>׼3f\nϒi&xEj_Ovv8'2x hR &=,pHbl Un^fd /L(gx w93򂬁%[ze}_zF_ F= 6Ta MaCn`?a+(Kȱ)fNt< T`Di1J2v̼]G}Fjy :"fqg]Y[.r:*ljf .<%B.D]ذfKQ)nLkb{9  P#7xT/o_GGfGoH]:]'?iH3}NfoCx/.CȬv0TH`/ ٘[C6pRX|bBUsI$}" >qeL$ʽkKYL6p#**8f5Mc.gZ>Ʊ>T!GYX?8`@mlAȏI9e99H^Ia #a;;4ةEWE.Q0K5%gBb3*|!{w*t3U~6yA[/K?" &UINjǻܗfRci8V>0YE S2 ՖGؖ<]K;749-s T/m3#7F άÙpQbHMІ>AՕ4}@>%WQCSC] z}N=>gg׾V )RV_H^~ǧOuq:E \gq35 럊Y?u@$$UU^RsK)UNByvj-8qXA+g.YqzF 7H\ٺq)vVY~f8a"q" d,0j<0ʸqgBاQ|`Iq> !"lt@ Nf- *5RSppK^D"Ќ7%xӑgNGs#0@LlQ*lDQf^%\&q ͧ[w̾v3pH2U&\ݨizϠ=9^f M$*LGĽp64Aēd|cJH"ɡk=-Vw\ʈO4`"W/Jñ0ZdduEPW"X9V `Ώn ~nU؉[^Oy@P΃5jmkdѲ#c{_ [D%FU~L _- 0삖˜( 4zېsLl0|_GcOSńa3 K9|]\4&8on8cMJ_ݨr_'$yXnUi{.(LJ(=  QjJ;[$(6V`vV.B Ȳ&> e_!^/)A=)R&d`COت"e[즻LqHV!T:CLy@}q^5'#Vj/&SIO-%š$E`ےŒ0:Ԇ1܂0W~շnD׹XaՈYǩpȁoxԢi~ I joVcLߣ35Wh6Y`B=br:%(Y @ `7堢B$yx%O2km恟!!=~p%Bp=|^!du5sܝ ..p+w;A=Vdtyºer!o ~"C|YU ;@|CQb/i̯E-Qq7F 8Ȕk3BB _W$BӴ}S +cm^c-ڶ k`d'(ND\\:9Ot.kV&&`wwY[c0 endstream endobj 57 0 obj 5118 endobj 55 0 obj << /Type /Page /Parent 3 0 R /Resources 58 0 R /Contents 56 0 R /MediaBox [0 0 595.28 841.89] >> endobj 58 0 obj << /ProcSet [ /PDF /Text ] /ColorSpace << /Cs1 7 0 R /Cs2 10 0 R >> /Font << /TT1 8 0 R /TT2 9 0 R /TT3 11 0 R /TT4 12 0 R >> >> endobj 61 0 obj << /Length 62 0 R /Filter /FlateDecode >> stream x]P{0Ҍ! 8o.U!2585/+mίwIҽ;nT+?)ي2|`} W&:;Ýy\tKpK 6.~NPnhJwo4ؖvߵ`rGl6Rq[xȐc;n;c%aK=QECjN&ЀMw­p! :KRlU[v!Nޞ/:|ai1"A.AIaFNŒ&FBuloԨi}ߍP8/` ՝TAl"P7̇o-qVen%zo-m3@~>^̉JQ_]F zlʟ|Cabx\nXW]]ȽF0ժ=ICS6~p,ٱŶthc7d#br+ɷeɪ֒+vޓ$`+Sf 6M##EMIm\EY:9K,凷 icpn^!sڇeՠa'yG"Bz岆o g"AȎQ&[,eǤ9l"(wrH9=e,ހÆaɵiH*_jHŲVov>`D,ƍɝŅ{qQe...qq&jK͊a?56``G/ J ?rd  f4-rM_,F•7CMKM. E[[ !đ>;_ձ\n$h1䜋L`": rqr`qY*Y<0XMDif-(B# IaQkKiAS.p:x r-\C/iʹMҢN3xA`VoC mR4- Aڳju9PlJ9T@H~H<2 $vH5zPMŢ+֛ɘbM{5 R4 w'NΙ9An[YA=J,7;"(X ]VAz6N;Ap5q9-G~M*zSpL it->, 4-.Ac)0eKEp4S[ޜ0:_jFN|y|!IOӡ6MLK 4Ej荰 JHfbs%,qFY/xP, μ4?/.?Na WZ^"o{ݟp+L$y X[bGTnm0-|@>C F⸇AŔV5,}S.'[oNa,6[Iox{D2u>n"4]ӱhmw_lb+ƶl^⅟U5 F)P1mp]E$Wu -L3:tį沨LUǼ"O!J%h VZx7]{=%8x'I\NNJS(-1q)Uɉ*jIlY NA4Y<1Kty% N5@BZBӭ 0ub6J{YDmUpd%/yW$}WwƈTC:[TU:qEAO*佰Hg9L,]9,NCy@^H V┙;E٦#-k1FRaxԕBΫBj{3' '݄e/#<7a0u=peDX>eEf2j4Bzт CZdo s}p= gg!k[ZPU| RјcHbbWQ+ʭ)aW_`XGOky+CS\,'9kp7 $6i9:00<,*fju)V}Ԥ1G`R9p+c$VɬWKU LdU:<>͢KTP 7hl<93^Mmosm;T{.B@ {-sgA2HZDAb \ +e Vh/gS0;ǟ pCQOb:DD॓1eQc.'1ƠM@)o@_ 9.XS2c pEWIGĀ!v r>C(9}X_X s1ݷp,=v! 2Fc_ c+՜#]X3 K:N.WNxj:ؓxg[汓;h닀G^RaD9't %Q]S1;XQgdn3_B#,52 2d>;ҍQf&4E,!3WfV>PʯjtK ? hվZtD( 9dܞYnBBkAO{F9x V#/0xc /p̝p IG'L DZ-J 6kwtbF8o9rEYO'g,'da]j IBRQ 6kR%Sinf|LM^"P$y>!Ы <.o?fFk41BQ[4*BQ%zI9]@kszGJČ8Oj)ى`F&!m-%gbC R>71gO ٷS{'Uo$'Z_bpu6Uɂj]1(`Z!8h~8XΑ K(ns.C2Pl6BMIt"|*BI1R1mS,8FA `,D(d`D_?Uq7ktאhe5]߹JT2I,VjWC晖 &}h>󔮶ӲL X*(ͪ(Ҫ~Lq@4h'RDARVaD]_!(hyu.\+1,TZwk+ǔxPU*C&nm„(/oB4,qh4ю,ODwS]w?@d6=[TVMe $3 yBSbx ӁAμj8}>n fN- kLH> endobj 63 0 obj << /ProcSet [ /PDF /Text ] /ColorSpace << /Cs1 7 0 R /Cs2 10 0 R >> /Font << /TT1 8 0 R /TT2 9 0 R /TT3 11 0 R /TT4 12 0 R >> >> endobj 64 0 obj [ 65 0 R ] endobj 67 0 obj << /Length 68 0 R /Filter /FlateDecode >> stream x]Fv^kJUv6gp8 x ?Oʆ2HTKȈ|~~ծrXmqׯ݋}{syn|r[6^W6kyz/|vm.7_n04#VJ/qr$q8lWm߿n{}m__om HhW# ]?i;7\o|_4ݻe.[o럏S*2e;R/JndgJg~|D8){h?~!_)_@iCvS"b@O86oS:~npIa492L:wn^'JE$f6NaOlׇհZ29E_֡w4rlˉY"g-wɽs(喏QwC.pócF( 1`np#[ Y}tqދ,4`w?p,+d11P5;' ܰH5$z:cɥ(a y"rP 0;\qb:-ע")X1sfxp`}%йdzj` 4fĉ Ki.Vɳ Jz6a@yYA4!0v ..It8j sآ!ita@lnh6dͤZrJuv}UQ#p9iP_OԐ^d,(uyUT M8"ɨi[ȒR%c9teGr֘MW:vk}ۅuv%&5%J^KlsE3QlԹt8M"Nah3$hNfxn*9S_U! kӺG:XfyYq`&P[kI\G 8AjRU~%' 7̰bQ>=#>lu~41+ٱR$ 8ȝ|+]CU2.cG(] >|++y/ .LY \niߝkK9( R-R0`!`<\ͻr.MB;ǣ5θAzsW@Ї~((y*jł";E8BrR~-` $-&b\ŕB7?KhhL:"2NVOoPJHS$y1__&6;h9k4G';-`CQo1HN'b4b"ufs)q ~䄕EJނ{>s'Ω/%-n9G:ɪOÚ-~q"_:kcp|v&g'A$v!= wPQM1rFFiwu{XG6jYԼGzRr$g' '˻R=HOT^|''1s5y wYgkhȈtz^\l?z9pw`S/U:X\$Q:q.>COE&Aq꾈 6-lHG!.<, N~A$VQ+siFЅ2yV{teVgm$eL"Ss;WycI/-oo{D"H>!v]8%3:GkB ; b9p43j3k<8OEk"I'q(tPō@ dUI4 ~|6󴃼UUhm<C 1iMMQ.siE}Aݵn.9VFT+PɈDU5NV4[A; +iq\-u _IQV` R4_6\[~dB ['!I@p$kULOt#[rχ-U50iqUiDߓ?2J0J`4zlvʕqdH0ͫʦγ3ak"@=Nvw+cx^A>5d"%&g2J׭pZD4%,c).rZMj< : ocaz!-!g0`~ZRzɋ[u4<ںEu4ILi-h;R3NM9&# bn &n iT)p/xXp ܼ-ȉm l6lg)['Xmp;z7pyvPC.E~rsM7si9奦WC$SDjkob&ݙԝq?tNEX J"n\xVND #rtp5ek!ߞξ}?ȼUQ h.f/%@wCXɱ]jolK;ð|-DIBB֨M^pObKbUQl7 rKh9,VNnZKh& f|HPͶē_Ĉ [>#O4\Sٰv>v| 1M\` t|(xtnMqp2%z+p-IyFML]-)E2AF5Ŭs*3$Mm?yqcVLc6^oAPz>ՋϾ_9@Nމꦑo:nsi[ZpKGFzipczW; ۦQfJ47)k\aRP61Dbi'b%$uwΊf2A3q 5Э3R[JE%3-0r0X@1L_)ΎP ^m~3|,j_88z y(Șu7FJMD#C%WFXr,\-]q1ryƬhD)*>[y~BӽwS09 }@e8/nX@ f=gO`^7> endobj 69 0 obj << /ProcSet [ /PDF /Text ] /ColorSpace << /Cs1 7 0 R /Cs2 10 0 R >> /Font << /TT1 8 0 R /TT2 9 0 R /TT3 11 0 R /TT4 12 0 R >> >> endobj 70 0 obj [ 71 0 R ] endobj 73 0 obj << /Length 74 0 R /Filter /FlateDecode >> stream x]ےG}hxw<=3=q1D^dsʶ o!+ꮪ D@v4]'{ZU_{jWc}5~E~}SMV;6i=[{^m֧Vx)'nvmNݯӡwj{mvڴ^u-7J86?ַwzZEۺنS?]͍g{E}M'Ԡih* WN{صuU@„?A읊Nh6|a `8? /:#Oo*&.>lmmCo'ߨJ7z(3=6:Oy4߸֎^q6*oYaj2C^|O?P _0%8CE&Mq\mXouT*x[x >@3`oŒl$ѵm ~@[;ڬ۪Azl8ZWӸ#NLM UN"a985ouFD']f 䡬S.ڤ)o`4jѤ(5~[!yMq&Zs=aA z}%Ow.ux*Wa5̘O<)܂H}>4%$^oё$Ҫ2Pձ]DgusXjwz,z jBf4r;#Tܥ9T<%;ƕ7>Y{oU !Yʞd*'z4=O(^$ZeƠGEaTjDj=ĭc0#gbj/`O\C/z\7=L%S 5G;t/HK Y ekЙS*IYjYFd4l։s(o:+M7w^ojS8 RKÙ/s&!tt";U`Rrt^~&G dud8%.lP~NE*1C=[mn(IIRh_BSO&,ǜ&I?"L4%?@AF.Q3ES!J`)"{~DMrX$(d l)>|,辫I}S^0%G=a@`KA@ !, & TŘbMB=^yR-ϋB}ݣ?NM>WPW`L7(8]։q}.GdF]Vn xn642:CJaqDfWcx ڜ-w x{~d>~$BEIxFѽhep %,9dO tQ&óCsl}<jsKuYʚlh*%oqK!1@ C#ŢڣK>.X}'NT0f6q 6*s)mB]oTeAh|Q{۶ޑpԌEZ͐.2Q_4T@ʼ$koc5ТXVMUIDiŨ֤CWlH1Rcy_̬@T,@Y'A'nND=C{` @"G |cLL#(sf+2N"臣87jV֝NlH_E>d)\Be'Fv&ĐN^ nhEf;VH&e>2,sU?x>:3gbd[X&` @hFV/)Pr[`eak>tH;6C"2Km h-a<rY*|tngG4q~gŬ0RM&հ#T6Eۜ>'NQ0η@U\ #I_o) $?zi kDʔpYsC̎*p¶Z?U)N\L&4I4UQ]ׁ[@ L鄊"CSvʵREo!ڽ!]_p*a:1'ЧUdHjqI "i9)/e_48`mDn:8BUb樲5]s&/z,/6f%I3[,Üqo.D)R(*8"bN9sTZ5qh7K4ML,;y /zs9[ vmy'zBi19z`hPK mAAGF\= ^ FnM<5شif iF௭C7tfL5D݌elLʐ,&7*\mbj #W>C+%O +b~`ݜW:2ƫ+/z M7,挓+$K `!ae) %b0}Vň3b,BMi +JC^u<2X'6uՐl~wq-p R_"^g|c?HO劌fqزV;酘$F)@56AiS0vC[T#r5Q~fW9u4-Ǝ]%a5X٩ĺH1a~:v#)l)7}bö^Z^ ٥n̑,!F*dzqk@.&>n<2s/alSJj7J#¡[xG'ej s<}%ecKmE_֫a/QPLUςl퐩`#Gu QxY/Sִ:|o͘2f-UX;&v޺ݩ_$Euhe2 4È O9O A NJONnȌ5{9^iONҔCzL3g)爠# lI7W][FSaLk=o[IT2(fZ5!qQk6^G+&Kc*(9k EH0ݿxR!_\Բo>|U^*rT\ٖ\cs<2Xo߹cõѻƧ`hmz 1[@vkFK'g瀓T@V ."T]+3EDJ 8o#2]7 ȱ蝌k\5A IB#$6)xT@΢DPj}CP# B#e+5JL:ށ'\ZD* v͸:\^d%-tX6ʞ.,?ECԬ\{FTo56DgIϟ>lo0dnEʪ}` 'SHX,gVI-1=1>>l06G`*ϡ-qL&7 7UVW<>sׄ[|a ɓ+$CAX_-EvaT "̦捬q0baL"\VUnX9,/pHɦ8! wD$8sˠxL Q+ ,ZbIzkF'^Q:99 3'v^{H .nƸܧcON/ ?i-!>hr yy2ÓH(_SJr\GM褩K'DoCT!Hv ^t\bb,o@)k}x# /4|<s6%2JWoݡ_k[UoA/05[L&o6 'uYHLtX sA6?zߨR(#Ga}EgbYe`lXՒ uoR9M7C9\׬HT`Ϡ Hg9@LeU|.j`H@$57,yDQ壨%mu9=iyV{N AY5Y^GLK(AJ0ѲY~`jDW:k銏$N߁d1@aP1*ic};Dw/P1=r=>K*meSCF]Y5/a/4pG(؆KmC^d_A7Lʼ{OsVۯX|(21&U60RhUHl19pRt/ӱ8[J1L UON,G6rS۾YH| `f));}jͼnW~9u*r}eۥ$P[E7JGC$^\9z*f}c3^6ӋS.({z˽r=/|/ oF3! < @w\ .dN*8ԴP&.pϺ0Kݓd/`ϓX:5d:I7iݹi*:M۳TV2!&5> endobj 75 0 obj << /ProcSet [ /PDF /Text ] /ColorSpace << /Cs1 7 0 R /Cs2 10 0 R >> /Font << /TT3 11 0 R /TT4 12 0 R /TT1 8 0 R /TT2 9 0 R >> >> endobj 76 0 obj [ 77 0 R ] endobj 79 0 obj << /Length 80 0 R /Filter /FlateDecode >> stream xZ]6}ׯ!٨ɴײ-=@B /x  P;vrq}h,]]{o] zl5NUo}6{~CTP7^S몵t~-'/lXٱzy8Xݍ)CN&Z&+^GGUmIK7m5\OmxpҴ/ h9@_L-q| C<\eۣ #6=2|Q\,/|s_VelF6'o# 9b?_Eԯ .<95r33Q]bԍ @w{1$K)aNx8=[i@#5[-sNԼd) ȶ(1G<$Fn N%k (ܟ/i% XF>dUgaBVr]aad5WI̒D *6T!;xI`kxV$E@㔡@ D7``'yvzV㧳|/wJ,nY7Pe5%?!ȘgtN!ȀZ Jᆒ=k;D:i#E4ع89I)G}4m PFO2Tl.9Wyw7/,3/iue"|W(iOVuWcRUƾ'2VŪ3٩@d/Es2^`hwh$ʂTPJ$C7e<*ЗA$)܊Ib͍[pͰ%sRUvjϝx@Rܑl'+<%& RJo: i"T-fIHɜ)e 6Y)ia>}>;}"0wze}]6'Aq*bFOٛ|]:ݡp`qM ͹|"G zM! 99U5_ŸP\cqY% */L.Ŋ7X+hUP#D[I)ՅQ:ki^a-;?O__yW^˃O>_c1 ܺ+oRU7+#MW&B5P;c\y![ξ\$"v]\eۡl7T;4h{_ l ~"yaNK[fߤ60R*[sNk9 &Քn 2R h7:!WNp ʓv33l)3~lQ&f8 TNߠ:P2pj[ThCekG_0 B -QX ԃxc\X \i3V+z^lk 6FYƻ vٶM ߼Ē>I\2wuՠ aV?lr1_K+e\ugx CH픠z|Ho+i3'#;f1T~I\ց`PWrھ%7Vnlm]e+gk =9DKO"Z*ӏ[TҋBO%:εe6؆plF|3ij$xרQ:n0þ2IH7)ϯӎ/͎5MsC .MN Nޓ_sAU&J)JH|;ÒR+@b(Za+Hߕcbt9ac?`~uiM\!N#N΂K5O퟼[wɨe>#+a}ge!$&KI_Qk'tS!C.Ph E]I`oAU1"&+sN L\rI5 _qSqzC endstream endobj 80 0 obj 2147 endobj 78 0 obj << /Type /Page /Parent 60 0 R /Resources 81 0 R /Contents 79 0 R /MediaBox [0 0 595.28 841.89] /Annots 82 0 R >> endobj 81 0 obj << /ProcSet [ /PDF /Text ] /ColorSpace << /Cs1 7 0 R /Cs2 10 0 R >> /Font << /TT1 8 0 R /TT2 9 0 R /TT3 11 0 R /TT4 12 0 R >> >> endobj 82 0 obj [ 83 0 R 84 0 R 85 0 R 86 0 R 87 0 R ] endobj 3 0 obj << /Type /Pages /Parent 88 0 R /Count 8 /Kids [ 2 0 R 19 0 R 25 0 R 31 0 R 38 0 R 45 0 R 51 0 R 55 0 R ] >> endobj 60 0 obj << /Type /Pages /Parent 88 0 R /Count 4 /Kids [ 59 0 R 66 0 R 72 0 R 78 0 R ] >> endobj 88 0 obj << /Type /Pages /MediaBox [0 0 595.28 841.89] /Count 12 /Kids [ 3 0 R 60 0 R ] >> endobj 89 0 obj << /Type /Catalog /Pages 88 0 R >> endobj 87 0 obj << /A 90 0 R /Border [ 0 0 0 ] /Type /Annot /Subtype /Link /Rect [375.75 580.2114 507.3438 593.2114] >> endobj 90 0 obj << /Type /Action /S /URI /URI 91 0 R >> endobj 91 0 obj (mailto:pavel.odintsov@gmail.com) endobj 86 0 obj << /A 92 0 R /Border [ 0 0 0 ] /Type /Annot /Subtype /Link /Rect [248.5625 593.2114 325 606.2114] >> endobj 92 0 obj << /Type /Action /S /URI /URI 93 0 R >> endobj 93 0 obj (http://irc.freenode.net) endobj 85 0 obj << /A 94 0 R /Border [ 0 0 0 ] /Type /Annot /Subtype /Link /Rect [143.25 619.2114 305.2812 632.2114] >> endobj 94 0 obj << /Type /Action /S /URI /URI 95 0 R >> endobj 95 0 obj (https://twitter.com/odintsov_pavel) endobj 84 0 obj << /A 96 0 R /Border [ 0 0 0 ] /Type /Annot /Subtype /Link /Rect [302.8125 632.2114 410.4062 645.2114] >> endobj 96 0 obj << /Type /Action /S /URI /URI 97 0 R >> endobj 97 0 obj (http://bit.ly/fastnetmon) endobj 83 0 obj << /A 98 0 R /Border [ 0 0 0 ] /Type /Annot /Subtype /Link /Rect [200.9375 645.2114 456.5 658.2114] >> endobj 98 0 obj << /Type /Action /S /URI /URI 99 0 R >> endobj 99 0 obj (https://groups.google.com/forum/#!forum/fastnetmon) endobj 77 0 obj << /A 100 0 R /Border [ 0 0 0 ] /Type /Annot /Subtype /Link /Rect [56.6875 665.1964 514.0938 691.1964] >> endobj 100 0 obj << /Type /Action /S /URI /URI 101 0 R >> endobj 101 0 obj (https://github.com/FastVPSEestiOu/fastnetmon/blob/master/docs/EXABGP_INTEGRATION.md) endobj 71 0 obj << /A 102 0 R /Border [ 0 0 0 ] /Type /Annot /Subtype /Link /Rect [56.6875 707.1814 466.6562 733.1814] >> endobj 102 0 obj << /Type /Action /S /URI /URI 103 0 R >> endobj 103 0 obj (https://raw.githubusercontent.com/FastVPSEestiOu/fastnetmon/master/src/notify_about_attack.sh) endobj 65 0 obj << /A 104 0 R /Border [ 0 0 0 ] /Type /Annot /Subtype /Link /Rect [56.6875 681.1976 508.5312 707.1976] >> endobj 104 0 obj << /Type /Action /S /URI /URI 105 0 R >> endobj 105 0 obj (https://github.com/FastVPSEestiOu/fastnetmon/blob/master/docs/GRAPHITE_INTEGRATION.md) endobj 50 0 obj << /A 106 0 R /Border [ 0 0 0 ] /Type /Annot /Subtype /Link /Rect [56.6875 509.1838 490.7812 535.1838] >> endobj 106 0 obj << /Type /Action /S /URI /URI 107 0 R >> endobj 107 0 obj (http://blog.sflow.com/2009/06/sampling-rates.html) endobj 44 0 obj << /A 108 0 R /Border [ 0 0 0 ] /Type /Annot /Subtype /Link /Rect [56.6875 707.2001 530.0938 733.2001] >> endobj 108 0 obj << /Type /Action /S /URI /URI 109 0 R >> endobj 109 0 obj (http://www.stableit.ru/2014/06/pfring-debian-7-wheezy.html) endobj 43 0 obj << /A 110 0 R /Border [ 0 0 0 ] /Type /Annot /Subtype /Link /Rect [56.6875 746.2001 528.2812 772.2001] >> endobj 110 0 obj << /Type /Action /S /URI /URI 111 0 R >> endobj 111 0 obj (http://www.stableit.ru/2014/10/netmap-debian-7-wheezy-intel-82599.html) endobj 37 0 obj << /A 112 0 R /Border [ 0 0 0 ] /Type /Annot /Subtype /Link /Rect [335.5 223.1851 504.875 236.1851] >> endobj 112 0 obj << /Type /Action /S /URI /URI 113 0 R >> endobj 113 0 obj (http://www.nmon.net/shop/cart.php) endobj 30 0 obj << /A 114 0 R /Border [ 0 0 0 ] /Type /Annot /Subtype /Link /Rect [56.6875 220.2013 514 233.2013] >> endobj 114 0 obj << /Type /Action /S /URI /URI 115 0 R >> endobj 115 0 obj (https://github.com/FastVPSEestiOu/fastnetmon/blob/master/docs/CAPTURE_BACKENDS.md) endobj 24 0 obj << /A 116 0 R /Border [ 0 0 0 ] /Type /Annot /Subtype /Link /Rect [132.0312 759.1863 513.9375 772.1863] >> endobj 116 0 obj << /Type /Action /S /URI /URI 117 0 R >> endobj 117 0 obj (https://github.com/FastVPSEestiOu/fastnetmon/blob/master/docs/INSTALL.md) endobj 14 0 obj << /A 118 0 R /Border [ 0 0 0 ] /Type /Annot /Subtype /Link /Rect [56.6875 301.2025 495.4062 327.2025] >> endobj 118 0 obj << /Type /Action /S /URI /URI 119 0 R >> endobj 119 0 obj (https://github.com/FastVPSEestiOu/fastnetmon/blob/master/docs/PACKAGES_INSTALL.md) endobj 11 0 obj << /Type /Font /Subtype /TrueType /BaseFont /ONLMZF+Helvetica /FontDescriptor 120 0 R /Encoding /MacRomanEncoding /FirstChar 32 /LastChar 223 /Widths [ 278 278 0 556 0 0 0 0 333 333 0 584 278 333 278 278 556 556 556 556 556 556 556 556 556 556 278 0 0 584 0 0 1015 667 667 722 722 667 611 778 722 278 500 667 556 833 722 778 667 0 722 667 611 722 667 944 667 0 611 278 0 278 0 556 0 556 556 500 556 556 278 556 556 222 0 500 222 833 556 556 556 0 333 500 278 556 500 722 500 500 500 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 350 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 556 556 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 500 500 ] >> endobj 120 0 obj << /Type /FontDescriptor /FontName /ONLMZF+Helvetica /Flags 32 /FontBBox [-951 -481 1445 1122] /ItalicAngle 0 /Ascent 770 /Descent -230 /CapHeight 717 /StemV 98 /XHeight 523 /StemH 85 /AvgWidth 441 /MaxWidth 1500 /FontFile2 121 0 R >> endobj 121 0 obj << /Length 122 0 R /Length1 23492 /Filter /FlateDecode >> stream x |Uյ9w̝17ބ B,@̓1 ,  JbF֪XphhiR>'Ҿ>kZiZx b$-'#O?1-hj͛2?U#nJ,BgΟ5/U&Du}?{)SElGLfΞ״8Uo}ܱ7eqg(n2oF1fοTH;/}>m@%{GD$3&"8~e5oLXd|ݿq1P;g&[&@qS#u،l's`P5sНГ ̡%G@(īt !  uTxW%75:$}L'A,ҥdɢdCϓn)}~0M51+.hv(%҂G| 'uhw6x%@;ݶ?S~\Jp^ܞ K`~]({* qZ7&,Ac/}AnG_E=08|s;h;](9{P<=,ͮǑ{\Z! ]#J9RRdY6Y'˲N__{H5ز瀬vv ʂDw /=mfCӴv ,'c-̑!׮!+jkKE]notIч&k7kqo~ k6a^ܪ+u:q?,$Ѻ{9OyGIk۹Ct=čG{n^D9^G EN!~J*S;]]_v*V&aL An$Sx{kMrBH'@zi!Z:ΡWZ9t,qٵV:94yks{C Cۛ)ѳSA쌁HVOyLoy]pkT{N+I5LL=ANf)Z)dv/KN+RZWluSfkMLeɬ4e=JCGp[necC+]] Vǹ)njNNjFj"͝ Q |[ȆG->^^F^{ K+E:tePƁ`Գ54M}H-gdݴr FfA}r>*FOim}kSJ74Ӯ6*$DmN-BnoF.wOcZZ7xP]h6I)Xטnxb1rvFcc_'u; Uw'qRޘLȆZ[j[ё G!8rMQesu.D zxQ.]Zpu=G7D­G׭K_[NI턝XN[FZ$pHjd<-H_(;K/W*Cw8p^*Թq_ÉF%Am*kCp5u@;Ãw\oTr(j;D;oq5#Qk8<JAmW9G5s\ӆt2 #b>E q} (ײ5~HAṗh=FVX ҵx mF; x لcDp_Xx 2 ]r~ QJ""r&î+3k24hx6bg~Nu낏!^'Ia>PS,H\x'T*TBJI< (!U%AA d S}M t.#ȵ٥hLW>~P*K?4t|:~z}3Cp8)djN#io7טl9k-N>dio;m`?xsCjrmv[ݷ4x.fߣBP`i Cm9C2pN9 N{ VF yR3*"dlΫOA%lj '9?]~脨Y"$8'_+.WxN?=NY# HVTt82:MuA["G3( ΋߿',!%vIrqEbtM8FFnI+:`x]aҭv3йҭnɛnh|yCMn)H,nj1 <5vK:gîI٘bˌcRfM_fGu!sskR]UUe>W2'a%j*V,3:uOZjs Љ"[Vī$E"PĦRI.ڴ;6.A?A7sk \mhG&eW2aD]*Ih ݢ9{Q;}4aJ@u 3{οon Wy3՝KIEh<Jmܤ+ܸ䡺r~ba>UT,]㮥YHnJjQc# l!6# K*G{X_Qbu,i'X-]=Ue- _o㳛/:`BUp@%|R :5,!^x գ9#熳.>͇T]`8–emC۔a{+Ae.e}E}n=BNCk&$bMuf6xuӉI6# Gc JJKcHIîvQnއO_,jS?%cPn A(#<#19~u7$8nkC}aXQǓXF{:ìrlT'/5w(H{iz.I$[qulJ=S*U:0:DZD#JVmIt}"ĭ|dAµZu$MHFk9\aw3I;QK$QoLcN; Rn͢2rf r.#8l<:_0̙ KR.,qPӢA$ m^ATWBƶ؆l;; aVߙQ* ě`rXAWsr/y&ϝ;}/{DA|P1aӕPp!yk)vW<#>bNwg]gkE>TX‚ 'x QKLo5e]sYEe6٬-VӔ,G2$A>\hu%M`rʟxecAvWK^^;|7]>DoZ2e]y=_XZ?cXiU@~6X2`roϾ=W9F9:wP`/1,$OƂLQ*)07k-XKӕCwq tK r.x4'Fb͈iE^v",⍺@%+@8snjZ\8bT?Wd=B|9#r:وU8 %8n׋2l_еӆbZIwPjI-*86S #0X.ETp*%z5&)"oxcSnt}+p"guM0fل(.djYMi!M"6Ue$ƟL~k4A( -RVRʨzh$z̹K=͆Ajv'G߻Ab|6rCcBƆ,IFS=*ݬ'b6LC$E΅32IOn;2qe'掜&^P:*mU޾s3ZIY7~ͷ>''*yKqXjF+˺p<\b2cRI/X [nu*]f+>KeLΡ/wCMtN1(1_(I4b,Ȣ1Gfِcc |΢Qg<ذ1 lE'B';?#A,fU?\d9r͊-u'F ʟN(];>q+AxCn7gP~M N?ضkR!<{ щRQ} !1Xa*wPil3i9-54Ak[$k_0Y*FfCl(Dbjj+- n;V{v햋gŖ(mGoF7ŗF9Q*.xiLԏm Misl;<͑wYƳ::&oMV^.&.Z/Xz%Uȕ]uT}k\35dW/duFg=rLYTg!~l Ey,b셍(沦S\Gy۲C 0@0"W=垻6\w=Q^מ?F6ݨ=Υ|b儑V0Yzսl{ܓ9#[W.凯>!8`ȞDG4 lqgwW['KxDsK̢P~m*)Pȃ1 dߕYn4P0:%+FK(ǫQbi%! F Q0P6l#jhaSD'4=l&RRl- ٜ8bp%,T6WbUf?jG9ipwؾf"|p |T^l'}+(6! K{LZ".%_ cd!<諓 ɍ-ɑ VTop!ݟ]e_?nr N%XZz?r5i@Ow[+>Vu򔵢TGTUCu(EQ:q4& t"1@!C0`a1D, C0~\eE Aj!8ش2F_׬ޒ?;GoLi䩱wPNjI)/:]Ԋ}vQ:zkuFz HƄQڕ'>Ny  aÓI*unR2jf|5hzј;Ɯs/uB6x@|P~o:W~9NoJ~mn<3viveYm_t@݉\\RnQXldr0Møpq@\ Dhӓݫx <35ОxƬeOTBr7SqEr#?2Rv>"9~L^mnXtoc~ZY%2L?K|-fNm!1ͅjI3K>DR m($iVld=6Nw-qLzeE0T ŖoD~v[އ*u~UZ%~>a(?{+T;Y91lr>ƑK l^1jx^@TC1D1$JK$jtfub d NBX%̿3sU,vC׿7㚦!޴OA+tz *^/GhjM 3xW1dTNa*^ /ns.~yu.(iSjv+x4~*."FAK*nV mw-47Q^}NNɟuR}2q遑os'@?l~ ]RUt%iP\E^ 9Mp8y }|zk eAUn,aх+ ]ȰBEN/j/ fPacyzC٤?)7o?ChS|); U1s>qͱtߔDR0`m_j$9`1uԔf^iAk^g/&$TRsl"U1Sj`D# mˇ_(5)Mt#+Q87 `2~q*Sqp*Ab#mEyN}H8l]`[ANfFDMz.KCQG0 {X4deta|0`A51 ͩpfO- /[m.d/d-w90Sa|xqv{wԱD[a MXT}z"+vEy}v=lu!l^ILNGbRSI>65Դ(S{6\iiw3Ͻʞk煾9>$[23Y}XQ`9- F-I4SPdxnU'Gc F3dPaDʳ [;F5X{# 7OM^%`IwsMsshTO󐗲 X}8F!fj`1r`x<8!)P〯.0ҀT< {׭{:pZ [|M׭Wyn8=lCO..*.p-75Wԛ C}3*gu{l c\azqK5"q@r㣢pA㑙sEQkT(Y% "{o ݰm@GU"bNk:yӮn{\'Û2.)*T]J 5/>TT JY_9 '_1)SC4@[. >r$z9)],~CYlq{(VY\t,L@,8q F$uaaqZY~RyQsRiA0¬ $"#'?MϢ $~0%4s5 }\_ƕl\Y,Z "TlԪ` <ŊH,Nha/,!"ۋ^#OSÁ0c=;#<#Ii byU&+ FYY/I@ѦNƉW>}+L'Ӹ mh7%J][S[aT " ۚh2cktIR>h3PUƼu:ib+ W^<" ̯p7Ya=S`JZy:}V 1 >ɚyI KKHpORz-6Fs2= (!x-GyU*sq)!ɚD1nZNe8hE4S }]3`饽L)cAq̘QXCdo!֭|(Շ̰}A9ڹfB|z ./`%WY+j5Kg|tԲ?(yz_|v)׮Lolߐx_-(Dez&kh`ƌμN=5̑A'JK Aʸ0]atYZTT~DեwU ŢBe!#ħJ.-Q76n.7`4m0ܳʰn4^c{9eܹȫ[ֽ;:|ї?,vN=跶5ŗ+ HbܗlzFϰ !*)=git#qEɢ z{\y<`huKxlu2hς{nSsj1ja\1jb`;&V [q(Xm[?|z;>xb'4:S9(k}/^9e>u[i{5:DsսKȖvk^(q+E]^Օ.6ƇcEZWaj1(.FL63Z0KZ02bz1[ce_`̴ l `G"&"+^/ xrz'hh==㳵>x}Wb#]NɢDa4|\Ld_8K/7{=š+<}Z[!.Ftr T{VBXLy%Y-}Ўe;v.]<]7O~o7/x\Yq`(?Ӵ{ttȠӂ:l!d /40Y!at}?Ǒ11+7AÙiۜ $9~;Qi9A!LD Y㏋xfxbS)*J.VO,'P*ڧJK`j+T4ArVl7 C4#=3FB%ňaa; c\!fJF]W^9JC`ANG8Hb ['Ekʵ}X^j5_V|`}-QǕ?RhP5;w4"4溛<K+3cbQb}`,eH\Xhȑ4{Ө}ΜCzXΌlS.S{tV.-O}|3/7[1ϓrI@w75` Uw$$,/6)LLcԟl  pgd<TVgeրoF;'쬚xL~[\ҫVxVN 5#V_,|gūeyʎoVp<Le:ʼnx5Krq!n]]mFΆ I_#9 :c#8# SS:uot7E:&F cȊDѸ0:P`Wڠ?.\t븧}<݄V¶cڥ HëklgVkґBBɀhHڊ .|(aq=vs+SҌgK4!Q)VpC(yKxj<+b W5 [`[D|e= ᩇ3e0U1VMcX,Qcsrqv#M1pn[^|̤QoyzL59SnQ۷dM>rЛ=smBk3Ug֍eٛFnؙ|;^}vQ?W.R5M4mI_ܿ Z/I%sFxD! ؔ˒C>IwHiߧ0qq7SK7dKKW5XF3ޘ#hĆzL1»6b.nS}",'< z.ZQlxE9شmɨ>mK~~˄}osN~߆YUK*Om$089Crm"c2^L3i-ZC\fbh^e6Xmt Vj:aIzxuIŢu&&zcKd37n9ݼ! C95ۻ0 CS>K_*n>bK33gŖ#MMѦ6+ٰR4 ɥQ.a 96[>MyrdNw?+&ų`ܟ<} _ߠBRw9܊|W2Z\.6D(k &+LVK4& Q4ꀺ %l_!)1."rIڊ^ r9Z!88V_^顿4-?2s^A:r|qn 7໶t G$zylO~,xhQ/tGMNDgטt/5"H<_t"]ögJ:YiNQ'fѱqoGc%̼ʞ=1G1`/x働Ɂ6=6hfqon|Ñ/0ٻx}&=6=K-M:}ܒ ,Om{4UKրWEi+HjW#n>6ڶ\UGvq2 1(κ-Aժf3@mp$BB; ϳ2=V$(0LCܫYI+нxKo~lk,m]<_8-_"truhTEՐjcY8VS0V*[o ]"+1y!)}H2X[Hܲαgru:KZ/۷m⭎--]d< uԻN8Z-[-$;s.%.'yAMqC̹GNFÕϸꨔYH1mlBs/C~Xy" aNUk"ex=Skmy4e.7MJXg)*2M#?cԄÙ݌WX۠&}sL3# t, fD\fV`A'%C"ƘjF!R@+X$8]ݑl'r.w7d$F`&pBV&7`_ekґއqGJAuf*fN+@Ɖ׈dq({7_Sܵ&O^ƈb'y';_ůœbFN/:`E.[Oz! A8 ikA Yfk4:1^Ԗ /ԭBx*w}Vc?F\@#c(7c2|BΝm^K%Tޔ'!x%[?(gtC^3Ȉ$Y0ru}]je!FS 7 `FZF^Q. 9bao OI!?:~;A.wBó&x!"~Tw҂>{8[~~؅U9amhBXkp o>MG!.l&rދNU/9a`4IoP*Ieڒx;X5̓rP6^ޞ\?`_FbOH=Uj5_!,1E=<: 5=6Kg_؆b"d<07j<{vilƦj5jvİr͸yFӜiSp$ueM5U0Pt tt @vP&T 4Zz ::: ;(T ]j= zF#LP1t=h: 0Pt ttv2AŠZ& àg@m)9[deחz=ʑ= Wah\Uf_8ƕe|1QףQGGbWGy`r]r}2UsUY>0a=7;8D(3-te{F(QGyLq= ={oQأ|c=z( ~q|V9=7(ܣ> endobj 124 0 obj << /Length 125 0 R /Filter /FlateDecode >> stream x]n@F<,Eߐ8BE"mU`!Հ0^{48Ý;7?u?Ks)<_>.) ~0֟%Ii>\[n{y]cEt%sNǘYooM_n1ț(:$uQFG- h&}@M{NV}v([I{\H T4T "A5AP"KRt'%`Z Ԧ"]*~I8*ϕ$jJQU %Ydek KfO@`eiUmfP R Tb@JU@72)T[)? Gp[Bpsp샦EI4XNU|@^QE^"@u* T˨+2On߫˯G˺%Sؗy endstream endobj 125 0 obj 504 endobj 123 0 obj << /Type /FontDescriptor /FontName /AUYBKX+Helvetica /Flags 4 /FontBBox [-951 -481 1445 1122] /ItalicAngle 0 /Ascent 770 /Descent -230 /CapHeight 717 /StemV 98 /XHeight 523 /StemH 85 /AvgWidth 441 /MaxWidth 1500 /FontFile2 126 0 R >> endobj 126 0 obj << /Length 127 0 R /Length1 18176 /Filter /FlateDecode >> stream x| |T9ޙɾ3d&+L& $@B$d T RTu@k'XyBZ+gVK\۟ڷv|!ޙl_ O9{γ$LֿпY}2b/~?V3=6VO%v[p_O_),q<Ǣ%SKcC/;$Ge_z@Qk[%3SQ]~9Qg)E}F!~E_s!>KܙF}NGxG;5FdyÙA@}dal܊* x?cIs*yTAS3:*,ffͭH8XoNћQ_ffXI$h4FM&##CgӬl9CO,\O=$y8e,O?@5TQ5?rq^$?!*f;lߌ"]QPaE?:e?ZXT=*cQ:w埖}nKN^M֔ZLe}`8n0ŸUT7KLsnҳlMY}5sց2SkiSm)s:>W[`=4WܴvDs5\m6y."si(6+,kZK$.3Ez%Y􌍽xJOT/熱0,*`>$SGQO`{^f, WbV~^gk^`wl`ZܽǶmE{ŲfQ`_{ w1vϿMY)x쉱[1c5cO lW ,;t,g|zcl`PUdɟOJ7ӘH`If?O{}0K {}P([-0ľb,O<3WͪA%QݏY)$ aX+oJ9mcjoǞ{q-ʢrv7v+y>f|}0!E~Ex_?JB'N /;Ż{īߥBcY;gV>&փ!e+Y+IJnP8OBjeG/A~ MǒEt hkMLJL3ǚbȈ0] mHpP`FD2SybL6B -X{>v=׊[SzZsʹVu'0C`dpe Ph!u_ԍF<`(uPf/*k)MMV+5 yvVҺP2{fBDsYj{݂piaHMOvgiPuE]lm -4V`=Tjn鮕6]0ۭ-\jP60 SBW- vO1e ΅[ekgu ¬ar#5eX+SS7v)7)߸Hnץ?^8N0UOa< Τ?3Ъ>dvv>ȍFG\˺sa&dkDِ CnbWZ]W怿3I;ouh4; ml5=x]nhx)Yp;|lF GW6v Zg)G#51s9a0TzPn2IfčtppQ11b88x݇1:(K蔑TkX`,[K!źEhnc#zY1;;u.3% Q bơ!sQh841DMi;8~`ԅXux!h2Fi6TڭQ5sƓy6Wó g'c:98<). $U{pwpwp8S8\ˈs}qd59\=qxwpwqLp8'/qx/H.2|O^]888/΍Fd;<80rxJ fSm`vLp岞!SbfcƯu.a\ւ뫥^P!,_zE]BgIqc,BUW W*侌e娇HЧKQϾ9Pat]`.G JAۈ(/K3O98Dcz0#.Kdf ^t#]2C T"N _ FNL-g1s#)!-E)ZjSy TO555H\{+5cTyO{׿o+P!H?BjcWODɕy;p0v@mPGA)|S-I>T(3,YxM1U]+qH#ИPb4mHϣ<􇯝˄? w,13kg<1 dK!Cb{2@h*C!P-!b(4:[ ^6adB7 WTA'+F1И-hC1ϘY(dzLr7/Sxݣ[/SkkS8.ql:ͅg笪\=')UJЬfe"VP&hh d4s5OT ~K]YV/Nd]:?AσΔptI߂oZ[5W'V"#v:xLG~+n<)3N34~-04\LKw3[3JZgjzgjzgjZ@js5h + |6@G.l6! }VC.+2,||9 B)K^jVPSL-f2 ܳhEATn5+ éu]omf,o{]>ZzW6kzjG% ZD47Xj{u 9wmzgVzK6wn)e%;xxE1\WM'y/=Xny[ /Xo,g iR.dfqN7$ xY~p}5tz / ʒPTk.eP&5AxGF}yIua4[#?v 0y#sbmӁ0E/J~ԁacs9D 4Iѿzg/۷2*>ۨO+{ yYt~F) o_qͦЮ>l"d2Q䠬Az%Bd)Xoyx%r2b$'ǐK^(|!QN_׾DTY:xWqt,Pd*Oʰ4gQywlR68=s'|Z/M]z33,1'Jg$Sl-U vr|l. zA_P'+v(T=$t/H& XC\?-rwt˖dj^,4$}|/ԙ'<x_nNe3YrJ׳\-(sҤQ,+ɄooٛU HpQЗ&!>zhCEcI?^peYs_fKWʊ$>KТ:L{xntW6οc}ь7XKUFnU9E"ՠ*@E WWz,G=TcQ/T,u!V[!V[!V[!V[!V[!V[!V[!V[!V[!V[!V[!V[{bʖb_ <|JvX4{c:~C2#+37Oe^t"P3r61j K9ҾkEBSeRLMs_ea̙ xje ۶xg6dDeU$GW͝#|ٶlypJܭ 0'#j{km]1f.^ZV O]FkdzҋYԃ*VWVWmu,\lulululululululululululU` #փeɨ'^H+ H+ H+ H-HhGZ ^SƧ5Xr\~ׁ͗7::eOKe& " ~!vjeو/ ST@xBavMΕ;6xgOlIik׶&}3Vtl)/6FG/kf$;܌-qWtj|< wY0oٶҞݫ*fFVvj}ui!U>#66%g-hkRy[03|&Hr$;dZKL\W $Kn)dc H6dc H6dc H6%gC1LPW Sћ!C r`H9x !T"^ǔ;І5#q?+\szg*[gt[+M]љEFRRz840>(9tUmJ~?5x-krSC>_ǩwH'F6Ct^x>R/;J2QABT#8eSCу!kE;yz J:VA-odbQƁPΉ{3 9.$iҙS#=>#XyG6;Ÿ_|gKd:Ӏ 0S#4Le)2TbЏ<93P@ &)FY~ ģ*F U]?W\ٲ~Sgn O.fKS lE2T|I|ζw.ϏY kp&ߏ]Iۭ Mr "Eɺ sI3egKLْTzMP;Glv#>0Ν\n LKbEV'4+șC]GѮ\X4P7EH:ăα;?` |/r>~.zru}x#g$vܝF\C.y}3~uqUsiȤr@ Pa z) E '0x2 '0x2 ()@~FFR|S/^Mz7E>0pp W  ?%2Yɣ# $}{KH7.Q:8,-t awu͙uW㲻{7̟ͽ{ޠ2԰|r عs=Sr=*0Hɋ*1 ۇŃ#t(8>\&%C>8Lr'3D` ಠ r3=f !73f̐r3Cnf  ,͌'t*H{f!zY0X;ՒGa3+ =Gfi,9ú vx`C ʛ^m%۟X2Wr<3w}eMwELLEwM1RaiG>>XzvxcxxL|Lem+dN& :iN& :iN& :ir :i .KJ `(u*W!`'y@.4lK;V5$>Tc-Q+]ڜP4cIiby}q|h]?֑؂K%nQ}{VJ Ashy%E 77 A$d̑3KaA^AUGs֊;؝b : S̕-⡿Qs>;3kN%!50h!(Vqdzoǒz7Ye6BjJbQ'dڄ ^eW*](GY A>jQ`4w4ԩ\騯@݂⺿fnyʠBq yzv. WHW+quVZ7WK rgve녠 ܮE)@<6MdeJ<^Q %=LԫqZJ򝰌ssM-E.rG+:ݘGq[.`AnV):HoyHRʼK%l)Y:r`yqRHy2k'$ʴzk.}>jQ[YE&ߪCo^v뎮Zpf ~_ЧvUF#֤;]{,T| :$:{^Jv#%pGO_<ʷ:7 fycwonŊ]!DvE(' X<#N)¦[dՐe”Z9f*7+@eÒ#J6qÈ,H1?dq"aºfV@yEQ3-ahްxCuQWtknijŌVK@KxK^!)qZO*# yU<o$v+<y0Ko$)3kO'v7b\zLs*1 e6ٷ7sYZ5p),V:k>c}rO~(q~x p_8(<ׇA|w?ib '?Tw{}}㝽WM;ǷcD+guoq|۽o@sq> 8ˊ7ڪ+ڻ:W=Cwgؑcs0-[o>|E3@` 1%ۀ_{tL@p (x 630xݘC9SځATNi\iJ[7> |Jim:<_6%"&7Lk0~̴6M?:1mΜF.h`asgNkSZxx Lkӯ&W4m.ֶMkLkNkMkOkr >ڕUdMkNkSkro?5 endstream endobj 127 0 obj 10562 endobj 8 0 obj << /Type /Font /Subtype /TrueType /BaseFont /LIAKPG+Helvetica-Bold /FontDescriptor 128 0 R /Encoding /MacRomanEncoding /FirstChar 32 /LastChar 223 /Widths [ 278 333 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 722 722 722 0 611 778 0 278 0 0 611 833 722 778 667 0 722 667 0 0 0 944 0 0 0 0 0 0 0 0 0 556 0 556 611 556 333 0 611 278 0 0 278 889 611 611 611 0 389 556 333 611 0 778 556 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 611 ] >> endobj 128 0 obj << /Type /FontDescriptor /FontName /LIAKPG+Helvetica-Bold /Flags 32 /FontBBox [-1018 -481 1436 1159] /ItalicAngle 0 /Ascent 770 /Descent -230 /CapHeight 720 /StemV 149 /XHeight 532 /StemH 124 /AvgWidth 621 /MaxWidth 1500 /FontFile2 129 0 R >> endobj 129 0 obj << /Length 130 0 R /Length1 13548 /Filter /FlateDecode >> stream x{ tSU94ͣyi&iEK MK[ PZ -e  <-o|1>P4 ?à0adoRiY.\}[ooE1h%Q|Ϙ2?Z;^?cBG̦#D57ea;B]qB}m-3*mP-\Hm.B[fBy̼%#9nnOHSrWjx̝kko\}0Ժ2@s(0(;΋h‹H+@BO{3W/ҥW;O!' *"~LyG_ i%;B`@./mx ?z lgoLięU RXUhjUEjW#;.Cn9-rh]n$ã U` ( ptՂ_]dζ/e?T ]ɻw_-Ho/fB]}k1$; ?Zy''OCxCI??Jnx$]djy[Bԋ;"(MF>8=]*X!{%N=-c6ݽ۞!X OQ{{H+p<IQMfx_ϡQ(OAضT?e_Ɇm)}&oVxWxBxb gu`>Oy'qNjA% p#PB/iD8 I8<9HD(48tqQH7B//7f4s6͌mG+k냛m kd[KʚjT*kv4-j3WNw8[JA:y6ZK\5{'WV4lbIҮ@Ma[uYŸ~s훫UF sU ki&s#s#sUfז vҲ{QۉlL !9 pRm<>#H7 '8qǡWv;@|6crn>6C" C_#+Zg:Ms@|tz"2GEp,2!ldyeT:>c=Ev,鑭ȌƢŠBa|t kq3,9 5~mF;9fmTE{Wl_a;Nix֖muhНh=ڈȽ3 /EtA@;Of!q-x;*Zh?13y pCkSu}>GP7 18r_`ZAK ϬcfwGǑDCKQ+ܳv{mElOX(<߄&>O,`씋ʢ|wQk-TꦵBz10MOB.'HK'""/F(e*Ckf&"zuGW<Kۉs\'s<{0܌wntWRPMT  h=LS |Oo3Y,g:l;Na糏;'yq&n;/z^I{vHݣ|(H: Fo+2ڃ6$фɑP(:z*DRDiΉ\/5śq{-!j18NUǨB9)\͎`rsIvUTd*Zh:Mz=ўX(D2#x)!_Ur9y^pJA( /j(k.Hǻ` Čt Rhd Ph$QJ˂䡍 VO/+w:2҃x rcz_'鳃?Z隹.@ӛI>H4f26-hrMN.^˕{]c򔷴vLJ-BRw7݀AB=4qW[ǜf9e҆ ,r!#}aN ʮbs߮;} ieM]05 :f$@ uyA3jـ D{gtKpem/-m)RX]*i@q9:."ଫ5-5GsF> kE~z$]m}dVCe.neÙ^B!#wPmzThN#7Bz:T ar"GrGShhhYzg0ߗmmh dq#R*+ +aU+Kチ :߆5Sxls/ـ DGf% AƬw9{::;W-<"[B pw!q9e;]N@t05)ׇ7.(ɉmL_“n“o}p\O(< ^vѯoG_&ySB멀~Liɛo-7D}# y:!$oݏ6P~YlF_A)F7h#GuжZoFP u$ 䟁%2a_Y0Zu $e Jz1PB[;쀝G4En9K"-KDb Fr}iJ(v~&Y`/mEȉ גra%T#L1qj1󼑟Ѳ9vBPE$Nr=U9L(x ؟6F,g}h$)mYfB) PX p!AN`L!vO#BL͕ ee_+@g4|EETw߿"wYfME;FUEكN- 'ˉWiYZ][/ɟ>-/(-M_Ѩ.Ϥ2{ݣ~4KG:ﱩk2S~,+?tmae_H,>?,7Q Yb}!)W}n>Yc aeeNӺbn,0 D-H89L]w"k/di;&ə O q@'o7vl _K&t]gH;wf;zvoKs~/}xnƍ2M6Xy{U9̽tӡ[Og1Ϡ)h1X̖vls.&LӍ-ג]ΠTn%u߇xsՐEOJ]ứޫ#,zknуR7ahG+x(h[Q|pruEJlrod a^*Җ ? |ƾžBw* ?Y/;T*W oY~T(.]틍1.;b¥u14W|K/=!m-KVgg 2܏?ZjY AZP?Á.a ˽8q޵"{81v/| ]F~pe10Y8SqS[ncCc 4dβYu*]ڢT V3H{.4~wO5\^:d|hu=ى#75ԣ"~y&0G_ -JKiJjt*CI5XC C >L??gx9#V$cPG3i,YZxk=_;gZe3=&JWJmȇ z5.*bhۑ4yk9̎!0x:rǼ;O?tO]?JխH/5&s)DWYKi4ku OE̟fR@&ɀ[:<'pAdXV4c2Big;Tkbc|a>hˈc-1XZgf5('(9-3&Y^,bqѯ1WwwطϘOOY4{S:v27Y;bgqmYZ%ʢ2j^U֞R^/*U*E"zt ^QG"OaEVRW"˩h5ьVSDɊo1l8Cgѷ՜*|d"k*bJ&f@eAYL]s-G`j*u?[`@ة͗,.{%#MZ]]TvN5]z*I\}XE`0`GCèx-f>M:֥aWO5 ~;b33}W&ӇyLQW̡yBkU|PgVlwN#MIЩS&P.~"QJbp(d2OM𯝐w怠k@cIJ_zA)ޒ CO4՗(J݊Q@mQtBnHj\_|J5(Tftϋւs0jԛ*w̐s'L6Ä9@cT{m׵閈Ku!~LqCƝhjQ٩r)5vhYBbw;+]iY1tkB #D"Qy45sPy 9Q]{ 0kóK{ǒkNkn<2xeCBg&ymE #j`ugT&QmnQiz;gn͏$;+ʮ[B$(1?-P2Yʃ}R"(;$Բ S:7|#];ZGԬ}Rqr%ly ޺'~o YPlG_~10\hď ,alf|wl]Ioԛ*ƱFci8>Ɯ}Ќ5* C#Էi:s'9y.=^i$:ߴ<)YREnʮ~$r)z> {YQrMkkn٫e4 n~Mk Tȵ /H4arʽWߗb8z,ܬ[VV޷$5'~P' i< X )wgAF v4iC z**j,Pk؋i &@^h V@TzN=g)u7tikH+o0S9iwK'8lD7M<ҷSJt`S!`BEKyzu3m5(V^ guxA 6" 摽@lAjG?eI'z-]:F<3Z^1·+×URpƢ5ы@Y`E<Z$ZGQQQ8r2lN!XW |\AS,؋- 7lUգ[! KJػldiU0U4xfClnp{) ] iA/D yAbFyzvR$dJ'l8Т*D^^H-3.n i@^z )Q_ $ t/@[]ce44 DR{%Zas^3 yYWTp LR-@2vzbjmK L:w`pusFb  d؀=+۔i8>n5#|S,eh>T#v2ާ[~AIލbyZtɷK}Av}lqp9)sp8SB\ҨFeTRL4TkTk4);+f8^V+f7YBbm},j[FZn Ձ :w=+V2oǁGtmڴuM]q?Sg@R-d3bL դ+vVNtcX8{XI4nQU#[>#;V3OGT+ɛLBQvC[ ka>xפ 4fN[E=㖼Qz|eOJ do.PWFЈcv!Fp!<*`1JtPEȎ]=mYdpԁl&&pd$K3RyivL#q_*|)yc^H?gZPh;.C+RY)Vr:*RЂ-ɡccx&=Ek6cUoSK6NФUƀGXQxC's9 {  GRps @GbĈh~`5֭[&D;I ,_=z{ S16)FqqsV^DQ#7ZcDլ&|%>gFK*|X9!l_'8\ TxufQ`Eq,MqJ 4EydQb^JH6weާŕգ*Z.j]8{FKF-sg}rZ{lO@'  P 0`&Bl0IP @ `"L`_Q_5_v( (a~(PMu_qe1nʓ[g (='u6<{@y@׍w[(:|ۀ2B?oP^< E endstream endobj 130 0 obj 8662 endobj 9 0 obj << /Type /Font /Subtype /TrueType /BaseFont /RPKNOW+Helvetica-Bold /FontDescriptor 131 0 R /ToUnicode 132 0 R /FirstChar 33 /LastChar 72 /Widths [ 722 596 556 639 611 611 667 556 500 556 612 611 612 611 611 611 611 444 722 556 834 833 722 804 722 556 832 611 778 833 556 500 667 652 578 722 833 721 611 722 ] >> endobj 132 0 obj << /Length 133 0 R /Filter /FlateDecode >> stream x]j@E^&ABB $ubI?V25-ҧn֐Znok1.vKg0&yЭ_gݱq7S$7)u8vKei'<5dI]h=^Gsz. Yn]f LDF9R7E;MgK3,^^_hhE^WRVETE8n@*Z, DDYI \؀˙hD`# Oe *SEbN"9 5?.bN"ףø.)9`Nbu.QW(^QJ6IsJZtyx@)Őv]JSxj*lȕ_h=Sẓ1< RįD#Su|,> endobj 134 0 obj << /Length 135 0 R /Length1 15588 /Filter /FlateDecode >> stream x{ xTչZ{&L2L&%3&3J2 IHȅBL"H*Z[T~jx)bi(Bk9Z7ؿO {λ}ߧ'Zk]=3aC"<\H!/Z3^nrdhӘ]n+mD=j xqhdxpOܦ(]#k6mc+ʧV 'ݵfpsh~rm5Zn.m"Za8t?;7I4d5Q!zY?hɊeU'qji,U??̨FܧgxF-fM˨'ɒI jUA9Ab_CmgS2,Ueڵ)VC[H@6TOM &JS"~M6UvtRMgmjlw~ΕγE͜Iſ&ꏵi{4bim"&ljQ=U=ҕɑ'\0+۾:!?x\Gh RI5 rr7xh[d=C ||=_רhj)${9HhnyI"xDG.8Z(T?)p%GA4Iq 9l nsBNbP͠ qX[ !|֐Qr<'ݵ\HQ$`z҂[CW8lZG7s?*b7@Ey⢋ >G_+\)7ȗm1_ *ab{A!x>x<[I/$=dd- Py#ۋ"R=-Et7[}e3p6r0^9n;}c64+ W)S-ŋK">.T @q  ȳy2A&h9y||B>RqP-t!8\MQpxߣ#w5|Db4\>w37m y{G'wY|>/ o9W!]n £·)9}o(+Tߔ=ʧ/*QUŪGU"M/vN^$uh\zMނ~L\#ENoT"L'd>H%+<=Gx ?Xn+dgefi͚bI6'8}.&:JQ (kp6!ԔA\@K3 sqwu_U*?;퓴oa;{큏zTPǠp{CH=@ M# y6? O2wp+h$;f'W:4[;\ κ@lnq`zg /zsžI?Y>jKz`o`sLKIp7tƀ`@g*Z-cB]^MbhuΑ[9H'78{g7KI+D~m~-++I?;ʤeKgv6̀} Z.#Ce?sTO(қw- 18Rb 9Yڗzqz}u~WCWQ 73[Hso$jI 7\@[8Z&NIRz l)sc:4p!ǁ8hDL3FTJHJt E=t8^K:[q 8xb[B#.N¼*gaOzK_ Сħ;zoM \9ȣ C􎏳18qfur ЅInaOһ:, "Ap:V/ +pIo<%¥_e_/pEW p?93D~p[#!\%!\Ep:\n!mnn"}!#@xxngw^8` .^%!E !t½็!Ao ].3 _!,\/˾$_Ng@>3WB>|Dr :PFʕG~e9C{['\"fԋ\i'{$J!zoO+HƐܷۗ7^\MHj0Z-x@WTY1]VF4Wjߎ讘#1U&>Oӿ .\GIE29JV!*qL偽<,fP/h_A'@obOByD 6'Z?Il&I<ȃ|9 ))LH2eJc:NeYt;<\Iq%#Oϊ364v8snҭYl,qTZE̲ʤeÅ>BL*jrb/A&O[)1)!؜$ќć8,%#:4*1A?u*ngrS\?{Lds/)0W*OLk;3$h[P2fcrn9L䭼S-@BRCvM:ߢlY*KazA#pᇿzCzGӈR/b@OH9xI?ϋKS6_\2b6w䬼,L? $ 0a 0jafX`afX`afXQ16$',AݠM{A@π~ ;Q0f3!f) bE.2\^`䔂3͕ѰC=$U|'W=ܫK[(KKW}4M>iYOXZģ{N]{bOkAQ }P2j ̀|Xx&yٻ8̳aLg~ k5P$N]z̥ɳ%c&(92$;dg΀ 3@v ;dg 3@vIv Ό2w 4‡2 2+ f)m(1 :G/*?PUmk [lhf<;1?:?:?:??:@1Hrg{*sZpaW`^V ^^Ш6.KihYkfǞ9{SSoS]O~u&L(-$2o;Wn^޺57"]xvLOlX7nzv8v?ApLi瘎$H:b^k3ZC*vMHLwb 朘?a-_RW*{dJLiM}Jh;M%oȪuR!q~~|W=V8Bh%؆llBh sem`ڈ]GMbe)/{?T!U?TRC~HRC~+0Z; !PGh7HNd_pCЦG-̕WO|)fTMW ,*#DmpԴlji[@bVc\\OyCV<x<_yXOo+,[Uo_TXԵ6x@xRB 6-.g{m.}#SQ9 ֶ}xݙC˞rr| :(A T Ƕ8 8r Bf,+`X/ Y/Fv5S<*?fd!zLTOg"F|0Nw J]v$߲vy%U3d?={={={a^س={a^س={%{֣V$b9LOӑxJwȎ(ݒZώG(+!OôT%RE;"2#`2λՇVS5iS'rlGOcnqCV,[`.sƾ*-.)_#d<=UJE%,0728eqBHk۵dWk%o>+Di?Vj3W~3c?RĩztR"xE8#"CYw#tÌB{b>nv 0>,d\Xl#|rg7lÂ=4HDǯŔ ̬+<5 XVx@+< hZV)d9 aPFDFFDFDFDFDFDFDFDFDFDF <*@3̡L@! %@5=]g?kL.WUí+-c~۷`wowW,r<3=̠>KXHy!@֞ *a1lMdԓ%I#_RDa!Q7$DݐuCnH !Q7$Dݐ[cb.LeG6öT@TpI6X>IRb  Tq:!߾sp_}VngA ȏ|ŭ>Q\T;T4 1dW 3jQh+ۚ[}V>BrYi>]OBZ~jZ~jZ~jڰ~jZ / i1L2C0L2 hl 1q3+)Fu5M4Ӑ׼k~sn:H06TbS!fE=0V{p>['4w?#g,Ǘr%)<nϋR?Ul1Y>π&? ycp%\(w^-o]٘R˯N׋9.k3byN+ [;>r)RA*Ŗx[΁P"vq?*•ptpA2ILF,g"cvE=%uG7rw'n(!tgT~q&|]sHĝƅzs _]^ۛVWKh諞N]}?nS+sr*:wnM~^y=I^+s,(%ٓ/oŞ/A4g1 ;q/nI$Xu_H-iŢ2xnsݽahry%_ 1S6@եO7Hv>W D! )K&^a偉|C晽eD±8K I{{%Z:-p8=W  IH@_tGD}\GpKҵA&t[k-i1NZ:jK򖜗kֻsz,YiAIy>V[fqd7h 0c`#, $TΤMX9M +7a&܄rVnMX +7a&܄0*@9Q❠(v]Y̕ƒ7,j$db{뤘$~S$$Yy}#lG>`}#XG):;#`6@(=,ŷ^"E,GuW"^rF LDZ&siOuuoYrrYouuOZ wsxguRq>+m״ʍ]67۹~sQ~w@OyNƨ |Ұ4\+3bDZň9#*Fo76=:1( #* ? _(F! 1`!$*<I(+žEcx@UjVtd(:#b%?1禲TNTz|ou 06=;;>&Vͥ䕥j*w:KL8ٙYx)kL5fb*nw4yR^% (Q _|_6\(7TA͆j(McWz.#0HM4,*vii 2hvhˆիGÑj`ڰz&\,״O5l\mvEn,_L<ҭ4]]5]]1]TԵwNWGGSĎnYa,:=#k ߫YiO=У#I@>C %?E"u~6?<;Ar)Ymč3j;ffYYmFŎm;Kf}ڥe,KqYY9ճlqB endstream endobj 135 0 obj 9114 endobj 35 0 obj << /Type /Font /Subtype /TrueType /BaseFont /HUTNIU+Helvetica-Oblique /FontDescriptor 136 0 R /Encoding /MacRomanEncoding /FirstChar 97 /LastChar 116 /Widths [ 556 0 500 0 556 278 0 0 222 0 0 0 0 556 0 0 0 333 500 278 ] >> endobj 136 0 obj << /Type /FontDescriptor /FontName /HUTNIU+Helvetica-Oblique /Flags 96 /FontBBox [-933 -481 1571 1138] /ItalicAngle -6 /Ascent 770 /Descent -230 /CapHeight 717 /StemV 98 /XHeight 523 /StemH 85 /AvgWidth -441 /MaxWidth 1500 /FontFile2 137 0 R >> endobj 137 0 obj << /Length 138 0 R /Length1 7860 /Filter /FlateDecode >> stream xY tTe[** I%JU奲D4*$"JBB#K[J@lYڊD"T@ ́9m+ڭ8ntfqTR*!I[{W֬oH *kښHCZCk][s}=XO$hj[­+Vm8lKsc`;}`݂2uzLʌUw4cquw֧PkmD ~rXNu ʒ5Hᴗ´^pn$㖍%E9k|mz#aI$`P^23"G[O;IXCde0 =զ;ͺ6uܠ3~ivD9i _zg]=DF`N4NgG\te8'i &/ LRC~mkj=%jB~|Zow_ffUΪV[jgV@whj,Ξ`MFS< ٬vV*yYר)nukƍhn5yVkr5]=^_vZZS*哭kkFU˛Z|ZV^[i*]i̲Ujj^us&:#gPHy  zt%j[30I$Ek/ CtQh?3;☐O5 7M,󴇎Qƴ zBOR~NghfI -Et07cBR、c-hyf)Y%[,f~Wli{k6h\|1Z FaϥؠaVnz݇:a.a#l:l3p 9/]cf$/0,X-iv NcY%~DzEBpppU+.7Jyl}:p)v 'ĭ6tId:faN6pwW8zY.ŸGB0F .",~)N[W gJi.-:XF`G t>h sE]1{PpX9VY}^? lڋmqmh@76^x;;efJ44vz`U5pXMdމ'σ! +C3Dyi\Z1z~NR]n/V^/zj CuP蜟,xN>gUP:;:y~F#O :*1H|PP1!=Qx~bh[!\!ڢvo!+;y@~ ,F2C&.0ԣ4G!#(c/à"8>^?QQ '*5NIJ0.(pv'\辰'yOEn_* lK;].>P[0 ֆ̡J\ib!XPTeM'?&e$FAUNxo!oZPbSp4!+MYiBt&D aBVrmSXpkRb4gذ;ĔGN><Ɣɕ30)om \3Gƕܾm3 ɫAA"싀}E>I3E ZՕnxldML {zYQ8~OfKh,]e- e`_̆E(2_BK|l@]2 7T?`xme -|@[`[`[doA[doA[N#=8 RJ(lcq8V̊IwŌ.Ji}`sI36Xm0jde 5ot$VY3nɒ[=qԔ'3ʲby9_>|S> endobj xref 0 143 0000000000 65535 f 0000133005 00000 n 0000004316 00000 n 0000074589 00000 n 0000000022 00000 n 0000004296 00000 n 0000004441 00000 n 0000007348 00000 n 0000107696 00000 n 0000117364 00000 n 0000010826 00000 n 0000078791 00000 n 0000095793 00000 n 0000004585 00000 n 0000078510 00000 n 0000004612 00000 n 0000007327 00000 n 0000007384 00000 n 0000010805 00000 n 0000017103 00000 n 0000010863 00000 n 0000017082 00000 n 0000017231 00000 n 0000017376 00000 n 0000078237 00000 n 0000022346 00000 n 0000017403 00000 n 0000022325 00000 n 0000022474 00000 n 0000022619 00000 n 0000077961 00000 n 0000029061 00000 n 0000022646 00000 n 0000029040 00000 n 0000029189 00000 n 0000127733 00000 n 0000029346 00000 n 0000077731 00000 n 0000035847 00000 n 0000029373 00000 n 0000035826 00000 n 0000035975 00000 n 0000036120 00000 n 0000077461 00000 n 0000077203 00000 n 0000042470 00000 n 0000036154 00000 n 0000042449 00000 n 0000042598 00000 n 0000042743 00000 n 0000076954 00000 n 0000048114 00000 n 0000042770 00000 n 0000048093 00000 n 0000048227 00000 n 0000053587 00000 n 0000048372 00000 n 0000053566 00000 n 0000053700 00000 n 0000058949 00000 n 0000074712 00000 n 0000053845 00000 n 0000058928 00000 n 0000059078 00000 n 0000059223 00000 n 0000076669 00000 n 0000065309 00000 n 0000059250 00000 n 0000065288 00000 n 0000065438 00000 n 0000065583 00000 n 0000076376 00000 n 0000071715 00000 n 0000065610 00000 n 0000071694 00000 n 0000071844 00000 n 0000071989 00000 n 0000076093 00000 n 0000074260 00000 n 0000072016 00000 n 0000074239 00000 n 0000074389 00000 n 0000074534 00000 n 0000075849 00000 n 0000075628 00000 n 0000075399 00000 n 0000075184 00000 n 0000074958 00000 n 0000074809 00000 n 0000074907 00000 n 0000075078 00000 n 0000075134 00000 n 0000075301 00000 n 0000075357 00000 n 0000075519 00000 n 0000075575 00000 n 0000075750 00000 n 0000075806 00000 n 0000075968 00000 n 0000076024 00000 n 0000076215 00000 n 0000076273 00000 n 0000076498 00000 n 0000076556 00000 n 0000076791 00000 n 0000076849 00000 n 0000077076 00000 n 0000077134 00000 n 0000077325 00000 n 0000077383 00000 n 0000077583 00000 n 0000077641 00000 n 0000077850 00000 n 0000077908 00000 n 0000078078 00000 n 0000078136 00000 n 0000078360 00000 n 0000078418 00000 n 0000078632 00000 n 0000078690 00000 n 0000079507 00000 n 0000079759 00000 n 0000095770 00000 n 0000096767 00000 n 0000096164 00000 n 0000096746 00000 n 0000097018 00000 n 0000107673 00000 n 0000108327 00000 n 0000108587 00000 n 0000117342 00000 n 0000118245 00000 n 0000117691 00000 n 0000118224 00000 n 0000118504 00000 n 0000127711 00000 n 0000127972 00000 n 0000128234 00000 n 0000132817 00000 n 0000132839 00000 n 0000132883 00000 n 0000132937 00000 n 0000132962 00000 n trailer << /Size 143 /Root 89 0 R /Info 1 0 R /ID [ <4d2a420e8089c4fab91a0ea1fe79b627> <4d2a420e8089c4fab91a0ea1fe79b627> ] >> startxref 133115 %%EOF fastnetmon-1.1.3+dfsg/docs/FreeBSD_INSTALL.md000066400000000000000000000022551313534057500204750ustar00rootroot00000000000000FreeBSD 9, 10, 11 and Dragonfly BSD 4.0 Stable version 1.1.2 is already in [official FreeBSD ports](https://freshports.org/net-mgmt/fastnetmon/) but if you want to hack it or install development version, please use this script. Please install wget: ```bash pkg install -y wget perl5 ``` Install stable 1.1.2 version: ```bash wget --no-check-certificate https://raw.githubusercontent.com/pavel-odintsov/fastnetmon/master/src/fastnetmon_install.pl -Ofastnetmon_install.pl sudo perl fastnetmon_install.pl ``` Install development version: ``` wget --no-check-certificate https://raw.githubusercontent.com/pavel-odintsov/fastnetmon/master/src/fastnetmon_install.pl -Ofastnetmon_install.pl sudo perl fastnetmon_install.pl --use-git-master ``` And please switch capture interface to promisc mode. Add into /etc/rc.conf following line (for applying this option at boot time): ```bash ifconfig_ix1="up promisc" ``` And switch it with ifconfig for already running system: ```bash ifconfig ix1 promisc ``` Please put your networks in CIDR format here: /usr/local/etc/networks_list. For netmap support you may need compile kernel manually with this [manual](BUILDING_FREEBSD_KERNEL_FOR_NETMAP.md). fastnetmon-1.1.3+dfsg/docs/GOBGP.md000066400000000000000000000030571313534057500167340ustar00rootroot00000000000000### GoBGP integration We have complete GoBGP integration for unicast IPv4. We have following configuration options for GoBGP: ```bash gobgp = off gobgp_next_hop = 0.0.0.0 gobgp_announce_host = on gobgp_announce_whole_subnet = off ``` We haven't enabled GoBGP build by default because it needs really huge dependency list. Please use following reference: ```bash wget https://raw.githubusercontent.com/FastVPSEestiOu/fastnetmon/master/src/fastnetmon_install.pl -Ofastnetmon_install.pl ``` Open fastnetmon_install.pl file and replace ```my $enable_gobgp_backend = '';``` by ```my $enable_gobgp_backend = '1';```. ```bash sudo perl fastnetmon_install.pl --use-git-master ``` Create example configuration for GoBGPD in gobgpd.conf file in current directory: ```bash [global.config] as = 65001 router-id = "213.133.111.200" [[neighbors]] [neighbors.config] neighbor-address = "10.10.10.250" peer-as = 65001 [neighbors.ebgp-multihop.config] enabled = true [[neighbors.afi-safis]] afi-safi-name = "ipv4-unicast" ``` Run it: ```bash /opt/gobgp_1_0_0/gobgpd -f gobgpd.conf ``` Check announced routes: ```bash /opt/gobgp_1_0_0/gobgp global rib Network Next Hop AS_PATH Age Attrs *> 192.168.1.1/32 0.0.0.0 00:00:08 [{Origin: ?}] ``` Announce custom route: ```bash gobgp global rib add 10.33.0.0/24 -a ipv4 ``` Withdraw route (please be careful! FastNetMon do not expect this from your side): ```bash gobgp global rib del 10.33.0.0/24 -a ipv4 ``` fastnetmon-1.1.3+dfsg/docs/GRAPHITE_INTEGRATION.md000066400000000000000000000063631313534057500211470ustar00rootroot00000000000000### Graphite integration Example screen: ![Graphite](images/fastnetmon_graphite.png) We could store pps/bps/flow number for top 7 (could be configured) host in incoming and outgoung directions. In addition to this we export total pps/bps/flow number which flow over FastNetMon. Configuration from FastNetMon side is very simple, please put following fields to /etc/fastnetmon.conf: ```bash graphite = on graphite_host = 127.0.0.1 graphite_port = 2003 graphite_prefix = fastnetmon ``` ### Install Graphite Debian 8 Jessie First of all, please install all packages: ```bash apt-get install -y python-whisper graphite-carbon ``` Whisper - it's database for data. Graphite - service for storing and retrieving data from database. Install web frontend: ```bash apt-get install -y graphite-web ``` Create database, specify login/password and email here: ```bash graphite-manage syncdb ``` Specify your timezone in file /etc/graphite/local_settings.py on line TIME_ZONE. Change owner: ```bash chown _graphite:_graphite /var/lib/graphite/graphite.db ``` Run it with apache: ```bash apt-get install -y libapache2-mod-wsgi apache2-mpm-prefork cp /usr/share/graphite-web/apache2-graphite.conf /etc/apache2/sites-available/graphite-web.conf a2dissite 000-default.conf a2ensite graphite-web a2enmod wsgi ``` Enable load on startup: ```bash systemctl enable apache2.service systemctl restart apache2.service ``` Open site: http://ip.ad.dr.es Put test data to Graphite: ```echo "test.bash.stats 42 `date +%s`" | nc -q0 127.0.0.1 2003``` If you have issues with Carbon like this: ```bash /etc/init.d/carbon-cache start * Starting Graphite backend daemon carbon-cache Traceback (most recent call last): File "/usr/bin/carbon-cache", line 32, in run_twistd_plugin(__file__) File "/usr/lib/python2.7/dist-packages/carbon/util.py", line 90, in run_twistd_plugin config.parseOptions(twistd_options) File "/usr/local/lib/python2.7/dist-packages/twisted/application/app.py", line 619, in parseOptions usage.Options.parseOptions(self, options) File "/usr/local/lib/python2.7/dist-packages/twisted/python/usage.py", line 270, in parseOptions raise UsageError("Unknown command: %s" % sub) twisted.python.usage.UsageError: Unknown command: carbon-cache ``` Please check this [link](http://stackoverflow.com/questions/27951317/install-graphite-statsd-getting-error-unknown-carbon-cache) Some useful graphics for Graphite. Total load: ```bash fastnetmon.incoming.pps fastnetmon.outgoing.pps fastnetmon.incoming.mbps fastnetmon.outgoing.mbps ``` Top talkers: ```bash highestMax(fastnetmon.*.outgoing.average.pps, 10) highestMax(fastnetmon.*.outgoing.average.mbps, 10) highestMax(fastnetmon.*.incoming.average.pps, 10) highestMax(fastnetmon.*.incoming.average.mbps, 10) ``` Also I recommend to configure Graphite this way. For big networks please enlarge number of created file in /etc/carbon/carbon.conf: ```bash MAX_CREATES_PER_MINUTE = 5000 ``` And if you want to store data every 5 seconds for 1 months please do following _before_ starting collection to Graphite in file /etc/carbon/storage-schemas.conf: ```bash [default_5sec_for_1month] pattern = .* retentions = 5s:31d ``` fastnetmon-1.1.3+dfsg/docs/HAPPY_CUSTOMERS.md000066400000000000000000000005721313534057500204220ustar00rootroot00000000000000### Hello! Here you could find happy tool customers! - FastVPS Eesti OU - Domain Name Registrar REG.RU - Moscow Datacenter COLOCAT.RU - Internet Service Provider MYVIRTUALSERVER.COM - Crea Nova Datacenter Finland, CREANOVA.ORG - Datahata Datacenter Belarus, DATAHATA.BY - SysEleven GmbH, SYSELEVEN.DE Do not hesitate to add your company! Pull requests with new companies! :) fastnetmon-1.1.3+dfsg/docs/INFLUXDB_INTEGRATION.md000066400000000000000000000047641313534057500211620ustar00rootroot00000000000000### InfluxDB integration InfluxDB is a very fast time series database written in awesome Go language. You could find some performance tests for InfluxDB and Graphite [here](https://groups.google.com/forum/#!topic/influxdb/0VeUQCqzgVg). You could install InfluxDB from [binary packages](https://influxdb.com/download/index.html). For Debian 8 Jessie I could offer part of this manual here: Recommended version: >=0.9.4 with support for graphite/batch ```bash wget https://s3.amazonaws.com/influxdb/influxdb_0.9.4.2_amd64.deb dpkg -i influxdb_0.9.4.2_amd64.deb ``` Then we should enable graphite protocol emulation in configuration file: /etc/opt/influxdb/influxdb.conf As well enable batch for avoid metric loss under load, and add templates for converting graphite metrics to InfluxDB measurements, with its proper tags. ```bash [[graphite]] enabled = true bind-address = ":2003" protocol = "tcp" consistency-level = "one" name-separator = "." # batch-size / batch-timeout requires InfluxDB >= 0.9.3 batch-size = 5000 # will flush if this many points get buffered batch-timeout = "1s" # will flush at least this often even if we haven't hit buffer limit templates = [ "fastnetmon.hosts.* app.measurement.cidr.direction.function.resource", "fastnetmon.networks.* app.measurement.cidr.direction.resource", "fastnetmon.total.* app.measurement.direction.resource" ] ``` And disable Graphite daemons if you use they before: ```bash systemctl stop carbon-cache ``` And start InfluxDB: ```bash systemctl restart influxdb ``` You will got web frontend on 8083 port and query API interface on 8086. Then we need fix some parts of /etc/fastnetmon.conf configuration file: ```bash graphite = on graphite_host = 127.0.0.1 graphite_port = 2003 graphite_prefix = fastnetmon ``` And apply changes to configuration file: ```bash systemctl restart fastnetmon ``` Finally you could query data from InfluxDB with CLI tool /opt/influxdb/influx: ```bash Connected to http://localhost:8086 version 0.9.3-nightly-c2dbf16 InfluxDB shell 0.9.3-nightly-c2dbf16 > use graphite Using database graphite > show measurements name: measurements ------------------ hosts networks total > > select mean(value) from networks where direction = 'incoming' and resource = 'bps' group by * name: networks tags: app=fastnetmon, cidr=10.20.30.40_24, direction=incoming, resource=bps time mean ---- ---- 1970-01-01T00:00:00Z 408540.85148584365 ``` Or you could install [Grafana](http://grafana.org) and make awesome Dashboard ;) fastnetmon-1.1.3+dfsg/docs/INSTALL.md000066400000000000000000000046121313534057500172020ustar00rootroot00000000000000For Debian 6, 7, 8 and CentOS 6, 7 and Fedora and Gentoo you should use the automatic installer: ```bash wget https://raw.githubusercontent.com/pavel-odintsov/fastnetmon/master/src/fastnetmon_install.pl -Ofastnetmon_install.pl sudo perl fastnetmon_install.pl ``` Please keep in mind! We track some information about your machine (os type and distro version). If you do not want to share this information, please add flag --do-not-track-me to intsall script call. But in this case we can't improve FastNetMon for your distribution. If you want to use netmap module, please install it: [netmap install](NETMAP_INSTALL.md) It's REQUIRED to add all of your networks in CIDR notation (11.22.33.0/24) to the file /etc/networks_list in the form of one prefix per line. If you are running this software on an OpenVZ node, you may not need to specify networks explicitly, as we can read them from /proc/vz/veip. You can whitelist prefixes by adding them to /etc/networks_whitelist (CIDR notation too). Start main process: ```bash /opt/fastnetmon/fastnetmon ``` Start the client process in another console: ```bash /opt/fastnetmon/fastnetmon_client ``` To enable fastnetmon to start on server startup, please add the following line to /etc/rc.local: ```bash /opt/fastnetmon/fastnetmon --daemonize ``` If something goes wrong, please check logs: ```bash tail -f /var/log/fastnetmon.log ``` When an incoming or outgoing attack occurs, the program calls a bash script twice (if it exists): ```bash /usr/local/bin/notify_about_attack.sh ``` The first time when threshold exceed (at this step we know IP, direction and power of attack). Second when we collect 100 packets for detailed audit of what happened. A sample script is provided and can be installed as follows: ```bash cp /usr/src/fastnetmon/src/notify_about_attack.sh /usr/local/bin/notify_about_attack.sh chmod 755 /usr/local/bin/notify_about_attack.sh ``` After copying the file, you need to open it and configure the 'email_notify' option as required. You can use an alternative python script: /usr/src/fastnetmon/src/scripts/fastnetmon_notify.py Guide for manual install (for unsupported platforms): [link](MANUAL_INSTALL.md) If you want unstable development branch, please use this syntax: ```bash wget https://raw.githubusercontent.com/pavel-odintsov/fastnetmon/master/src/fastnetmon_install.pl -Ofastnetmon_install.pl sudo perl fastnetmon_install.pl --use-git-master ``` fastnetmon-1.1.3+dfsg/docs/INSTALL_RPM_PACKAGES.md000066400000000000000000000041401313534057500211520ustar00rootroot00000000000000Please do not use this packages because they are outdated. Please use installer script instead ========== We have precompiled packages for CentOS 6, CentOS 7 and Fedora 21 (unfortunately without PF_RING support). First of all, please install Epel repo for CentOS 6 and 7: - CentOS 6: ```rpm -ihv http://fedora-mirror01.rbc.ru/pub/epel/6/i386/epel-release-6-8.noarch.rpm``` - CentOS 7: ```yum install -y epel-release``` Enable ntop repo (need for PF_RING kernel module and library): ```vim /etc/yum.repos.d/ntop.repo``` Repo file (it's nightbuild version. Stable did not work correctly and haven't headers for ZC version): ```bash [ntop] name=ntop packages baseurl=http://www.nmon.net/centos/$releasever/$basearch/ enabled=1 gpgcheck=1 gpgkey=http://www.nmon.net/centos/RPM-GPG-KEY-deri [ntop-noarch] name=ntop packages baseurl=http://www.nmon.net/centos/$releasever/noarch/ enabled=1 gpgcheck=1 gpgkey=http://www.nmon.net/centos/RPM-GPG-KEY-deri ``` And install pfring kernel module and libs: ```bash yum install -y pfring ``` Install log4cpp from RPM on CentOS 7 (this package will be in EPEL 7 soon): ```bash yum install -y https://github.com/FastVPSEestiOu/fastnetmon/raw/master/packages/CentOS7/log4cpp-1.1.1-1.el7.x86_64.rpm yum install -y https://github.com/FastVPSEestiOu/fastnetmon/raw/master/packages/CentOS7/log4cpp-devel-1.1.1-1.el7.x86_64.rpm ``` Install FastNetMon on CentOS 6: ```bash yum install -y https://raw.githubusercontent.com/FastVPSEestiOu/fastnetmon/master/packages/CentOS6/fastnetmon-1.1.1-1.x86_64.rpm ``` Install FastNetMon on CentOS 7: ```bash yum install -y https://raw.githubusercontent.com/FastVPSEestiOu/fastnetmon/master/packages/CentOS7/fastnetmon-1.1.1-1.el7.centos.x86_64.rpm ``` Install FastNetMon on Fedora 21: ```bash yum install -y https://raw.githubusercontent.com/FastVPSEestiOu/fastnetmon/master/packages/Fedora21/fastnetmon-1.1.1-1.fc21.x86_64.rpm ``` If you want build rpm package manually please use script [build_rpm.sh](https://raw.githubusercontent.com/FastVPSEestiOu/fastnetmon/master/src/build_rpm.sh). Please build on same Linux distro version as target platform for best results. fastnetmon-1.1.3+dfsg/docs/JUNOS_INTEGRATION.md000066400000000000000000000115631313534057500206400ustar00rootroot00000000000000Documentation to integrate Fastnetmon with inline jflow using Juniper MX Series routers (MX5, MX10, MX40, MX80, MX104, MX120, MX240, MX480, MX960). In this example, we use rate=10, but you can change to rate=100, depending on your traffic. You need to change fastnetmon.conf netflow_sampling_ratio with same rate you setup on your MX router. Our topology is two MX80 routers, named r1 and r2. From each router connected directly to Fastnetmon server. Fastnetmon server have 2 interfaces: 10.50.1.2/30 - connected to r1 10.50.1.6/30 - connected to r2 c R1 have 1 transit connected to ge-1/0/0.0 R2 have 1 transit connected to ge-1/0/0.0 Setting sampling on transit interfaces. Run that on those interfaces on each router. ``` set interfaces ge-1/0/0.0 family inet sampling input ``` You can check https://github.com/FastVPSEestiOu/fastnetmon/blob/master/docs/DOCKER_INSTALL.md to see how to configure Fastnetmon to work with inline-jflow ``` r1# show interfaces ge-1/0/4 unit 0 { description netflow-collector; family inet { address 10.50.1.1/30; } } r1# show interfaces ge-1/0/4 | display set set interfaces ge-1/0/4 unit 0 description netflow-collector set interfaces ge-1/0/4 unit 0 family inet address 10.50.1.1/30 r2# show interfaces ge-1/0/4 unit 0 { description netflow-collector; family inet { address 10.50.1.5/30; } } r2# show interfaces ge-1/0/4 | display set set interfaces ge-1/0/4 unit 0 description netflow-collector set interfaces ge-1/0/4 unit 0 family inet address 10.50.1.5/30 ``` Now add templates configuration on r1 and r2. Take care of flow-active-timeout and flow-inactive-timeout it should be less than average_calculation_time. Try average_calculation_time=20 where flow-active-timeout=10: ``` set services flow-monitoring version-ipfix template ipv4 flow-active-timeout 10 set services flow-monitoring version-ipfix template ipv4 flow-inactive-timeout 10 set services flow-monitoring version-ipfix template ipv4 template-refresh-rate packets 1000 set services flow-monitoring version-ipfix template ipv4 template-refresh-rate seconds 10 set services flow-monitoring version-ipfix template ipv4 option-refresh-rate packets 1000 set services flow-monitoring version-ipfix template ipv4 option-refresh-rate seconds 10 set services flow-monitoring version-ipfix template ipv4 ipv4-template set chassis tfeb slot 0 sampling-instance ipfix flow-monitoring { version-ipfix { template ipv4 { flow-active-timeout 60; flow-inactive-timeout 60; template-refresh-rate { packets 1000; seconds 10; } option-refresh-rate { packets 1000; seconds 10; } ipv4-template; } } } slot 0 { sampling-instance ipfix; } ``` Now setup ipfix exports: ``` r1# show forwarding-options sampling { instance { ipfix { input { rate 10; } family inet { output { flow-server 10.50.1.2 { port 2055; version-ipfix { template { ipv4; } } } inline-jflow { source-address 10.50.1.1; } } } } } } r1# show forwarding-options | display set set forwarding-options sampling instance ipfix input rate 10 set forwarding-options sampling instance ipfix family inet output flow-server 10.50.1.2 port 2055 set forwarding-options sampling instance ipfix family inet output flow-server 10.50.1.2 version-ipfix template ipv4 set forwarding-options sampling instance ipfix family inet output inline-jflow source-address 10.50.1.1 r2# show forwarding-options sampling { instance { ipfix { input { rate 10; } family inet { output { flow-server 10.50.1.6 { port 2055; version-ipfix { template { ipv4; } } } inline-jflow { source-address 10.50.1.5; } } } } } } r2# show forwarding-options | display set set forwarding-options sampling instance ipfix input rate 10 set forwarding-options sampling instance ipfix family inet output flow-server 10.50.1.6 port 2055 set forwarding-options sampling instance ipfix family inet output flow-server 10.50.1.6 version-ipfix template ipv4 set forwarding-options sampling instance ipfix family inet output inline-jflow source-address 10.50.1.5 ``` ``` fastnetmon-1.1.3+dfsg/docs/LUA_SUPPORT.md000066400000000000000000000015171313534057500177520ustar00rootroot00000000000000### We have LUA support for processing NetFlow flows It supported only for NetFlow v5 and sFLOW at this moment. It's not compiled by default and you need build it explicitly. Install dependency list (Debian 8 and Ubuntu 14.04 has it): ```bash apt-get install -y libluajit-5.1-dev luajit lua-json ``` Build it: ``` cmake -DENABLE_LUA_SUPPORT=ON .. make ``` Please be aware! In Ubuntu 14.04 lua-json 1.3.1 is [buggy](https://bugs.launchpad.net/ubuntu/+source/lua-json/+bug/1443288) and should be upgraded to 1.3.2. Fast and dirty fix for lua-json for Ubuntu 14.04: ```bash wget https://raw.githubusercontent.com/harningt/luajson/1.3.3/lua/json/decode/util.lua -O/usr/share/lua/5.1/json/decode/util.lua wget https://raw.githubusercontent.com/harningt/luajson/1.3.3/lua/json/decode/strings.lua -O/usr/share/lua/5.1/json/decode/strings.lua ``` fastnetmon-1.1.3+dfsg/docs/MAC_OS_INSTALL.md000066400000000000000000000014311313534057500202570ustar00rootroot00000000000000Build on Mac OS 10.10 Yosemite. - Install XCode https://itunes.apple.com/us/app/xcode/id497799835 with Mac App Store - Update XCode to latest version with App Store - Agree to Xcode license in Terminal: ```sudo xcodebuild -license``` - Install Mac Ports: https://www.macports.org/install.php - Install dependencies: ```sudo port install boost log4cpp cmake``` Run installer script for stable branch: ```bash wget https://raw.githubusercontent.com/pavel-odintsov/fastnetmon/master/src/fastnetmon_install.pl -Ofastnetmon_install.pl sudo perl fastnetmon_install.pl ``` Run installer script for master branch: ```bash wget https://raw.githubusercontent.com/pavel-odintsov/fastnetmon/master/src/fastnetmon_install.pl -Ofastnetmon_install.pl sudo perl fastnetmon_install.pl --use-git-master ``` fastnetmon-1.1.3+dfsg/docs/MANUAL_INSTALL.md000066400000000000000000000061501313534057500202360ustar00rootroot00000000000000At first you should install PF_RING (you can install any latest version ```bash cd /usr/src wget 'http://downloads.sourceforge.net/project/ntop/PF_RING/PF_RING-6.0.3.tar.gz?r=http%3A%2F%2Fsourceforge.net%2Fprojects%2Fntop%2Ffiles%2FPF_RING%2F&ts=1402307916&use_mirror=cznic' -OPF_RING-6.0.3.tar.gz tar -xf PF_RING-6.0.3.tar.gz cd PF_RING-6.0.3 # Debian way apt-get install build-essential bison flex linux-headers-$(uname -r) libnuma-dev # CentOS yum install -y make bison flex kernel-devel gcc gcc-c++ # CentOS openvz case yum install -y make bison flex vzkernel-devel gcc gcc-c++ ``` Build PF_RING kernel module: ```bash cd kernel make make install modprobe pf_ring ``` Build lib: ```bash # Debian apt-get install -y libnuma-dev # CentOS yum install -y numactl-devel cd /usr/src/PF_RING-6.0.3/userland/lib ./configure --prefix=/opt/pf_ring make make install ``` Install FastNetMon: ```bash # Debian 7 Wheezy apt-get install -y git g++ gcc libboost-all-dev make libgpm-dev libncurses5-dev liblog4cpp5-dev libnuma-dev libgeoip-dev libhiredis-dev libpcap-dev # CentOS yum install -y git make gcc gcc-c++ boost-devel GeoIP-devel log4cpp-devel ncurses-devel glibc-static ncurses-static gpm-static gpm-devel # For compiling on CentOS please remove line "STATIC = -static" from file Makefile and replace line "LIBS += -lboost_thread" by line "LIBS += -lboost_thread-mt" cd /usr/src git clone https://github.com/FastVPSEestiOu/fastnetmon.git cd fastnetmon/src ``` Build FastNetMon with cmake: ```bash cd /usr/src/fastnetmon/cmake mkdir build cd build cmake .. make ``` You should start fastnetmon using this options: ```bash LD_LIBRARY_PATH=/opt/pf_ring/lib/ ./fastnetmon eth3,eth4 ``` If you want to avoid LD_LIBRARY_PATH on every call you should add pf_ring path to system: ```bash echo "/opt/pf_ring/lib" > /etc/ld.so.conf.d/pf_ring.conf ldconfig -v ``` It's REQUIRED to add all your networks in CIDR form to file /etc/networks_list if form when one subnet on one line. Please aggregate your networks because long networks list will significatly slow down programm. And please change REDIS_SUPPORT = yes to no in Makefile if you do not need traffic counting feature. When you running this software in OpenVZ node you may did not specify networks explicitly, we can read it from file /proc/vz/veip. You can add whitelist subnets in similar form to /etc/networks_whitelist (CIDR masks too). Copy standard config file to /etc: ```bash cp fastnetmon.conf /etc/fastnetmon.conf ``` Start it: ```bash ./fastnetmon eth1,eth2 ``` Enable programm start on server startup, please add to /etc/rc.local this lines: ```bash screen -S fastnetmon -d -m /root/fastnetmon/fastnetmon ``` When incoming or outgoing attack arrives programm call bash script (when it exists): /usr/local/bin/notify_about_attack.sh two times. First time when threshold exceed (at this step we know IP, direction and power of attack). Second when we collect 100 packets for detailed audit what did happens. ==Command Line Reference * --version gives FNM version * --daemonize start in daemon mode * --configuration_file CONFIG_FILE specify alternative config file to read fastnetmon-1.1.3+dfsg/docs/MONGODB.md000066400000000000000000000032401313534057500171550ustar00rootroot00000000000000### This article describes everything about ongoing MongoDB integration Debian 8 Jessie. Install MongoDB itself: ```bash apt-get install -y mongodb-server mongodb-clients ``` Build FastNetMon from Git's master branch. Enable it in configuration file: ```bash mongodb_enabled = on ``` Query data about attacks: ```bash > use fastnetmon switched to db fastnetmon > show collections attacks system.indexes > db.attacks.find() { "_id" : ObjectId("560bf6f5d6db1e6921740261"), "192_168_1_1_information_30_09_15_16:51:33" : { "ip" : "192.168.1.1", "attack_details" : { "attack_type" : "syn_flood", "initial_attack_power" : 11495, "peak_attack_power" : 11495, "attack_direction" : "incoming", "attack_protocol" : "tcp", "total_incoming_traffic" : 689822, "total_outgoing_traffic" : 0, "total_incoming_pps" : 11495, "total_outgoing_pps" : 0, "total_incoming_flows" : 0, "total_outgoing_flows" : 0, "average_incoming_traffic" : 689822, "average_outgoing_traffic" : 0, "average_incoming_pps" : 11495, "average_outgoing_pps" : 0, "average_incoming_flows" : 0, "average_outgoing_flows" : 0, "incoming_ip_fragmented_traffic" : 0, "outgoing_ip_fragmented_traffic" : 0, "incoming_ip_fragmented_pps" : 0, "outgoing_ip_fragmented_pps" : 0, "incoming_tcp_traffic" : 43615380, "outgoing_tcp_traffic" : 0, "incoming_tcp_pps" : 726922, "outgoing_tcp_pps" : 0, "incoming_syn_tcp_traffic" : 43615380, "outgoing_syn_tcp_traffic" : 0, "incoming_syn_tcp_pps" : 726923, "outgoing_syn_tcp_pps" : 0, "incoming_udp_traffic" : 0, "outgoing_udp_traffic" : 0, "incoming_udp_pps" : 0, "outgoing_udp_pps" : 0, "incoming_icmp_traffic" : 0, "outgoing_icmp_traffic" : 0, "incoming_icmp_pps" : 0, "outgoing_icmp_pps" : 0 } } } ``` fastnetmon-1.1.3+dfsg/docs/MULTIPE_INSTANCES.md000066400000000000000000000015731313534057500206250ustar00rootroot00000000000000### We could run multiple instances of FastNetMon on same server First of all please create multiple configuration files for each instance. In each file you should specify uniq paths for following options for each separate instance: ```bash # Path to pid file for checking process liveness pid_path = /var/run/fastnetmon.pid # Path to file where we store information for fastnetmon_client cli_stats_file_path = /tmp/fastnetmon.dat ``` Next you need to specify custom path to configuration file: ```bash ./fastnetmon --configuration_file=/etc/fastnetmon_mayflower.conf --daemonize ./fastnetmon --configuration_file=/etc/fastnetmon_hetzner.conf --daemonize ``` FastNetMon cli client expects stats file with default path /tmp/fastnetmon.dat so you need to set custom path with environment variable: ```bash cli_stats_file_path=/tmp/fastnetmon_second_instance.dat ./fastnetmon_client ``` fastnetmon-1.1.3+dfsg/docs/NETMAP_INSTALL.md000066400000000000000000000021031313534057500202370ustar00rootroot00000000000000Build netmap on Debian 7 Wheezy with ixgbe 10GE NIC (82599): If you want _stable_ driver with all modern features, please use this reference instead [here](http://www.stableit.ru/2014/10/netmap-debian-7-wheezy-intel-82599.html) Get kernel sources: ```bash cd /usr/src apt-get source linux-image-3.2.0-4-amd64 ``` Download netmap kernel module code: ```bash cd /usr/src git clone https://code.google.com/p/netmap/ cd netmap/LINUX/ ``` Build netmap with drivers: ``` ./configure --kernel-sources=/usr/src/linux-3.2.65 --drivers=ixgbe make make install ``` Load modules: ``` insmod ./netmap.ko modprobe mdio modprobe ptp modprobe dca insmod ixgbe/ixgbe.ko ``` Enable interfaces: ```bash ifconfig eth0 up ifconfig eth0 promisc ``` Add to /etc/rc.local: ```bash rmmod ixgbe insmod /usr/src/netmap/LINUX/netmap.ko modprobe mdio modprobe ptp modprobe dca insmod /usr/src/netmap/LINUX/ixgbe/ixgbe.ko ifconfig eth0 up ifconfig eth0 promisc ``` You could use this script for automatic install netmap with all required drivers: https://gist.github.com/pavel-odintsov/6353bfd3bfd7dba2d99a fastnetmon-1.1.3+dfsg/docs/PACKAGES_INSTALL.md000066400000000000000000000073741313534057500204500ustar00rootroot00000000000000### It's your favorite place if you are hate any "installer scripts" So we spent so much time and would like to offer rpm and deb packages for all most popular distros! We have only x86_64 paсkages. Please be aware! It's beta packages builded from current (25 aug 2015) version of Git master! Dear Debian and Ubuntu user, please do not panic if you got error from dpkg command about missing deps! It's OK! It will be fixed by second call of apt-get install -f. #### Initial configuration It's REQUIRED to add all of your networks in CIDR notation (11.22.33.0/24) to the file /etc/networks_list in the form of one prefix per line. After this you could run /opt/fastnetmon/fastnetmon_client and check output. #### Ubuntu LTS 12.04 ```bash wget http://178.62.227.110/fastnetmon_binary_repository/test_package_build/fastnetmon-git-447aa5b86bb5a248e310c15a4d5945e72594d6cf-ubuntu-12.04-x86_64.deb apt-get update dpkg -i fastnetmon-git-447aa5b86bb5a248e310c15a4d5945e72594d6cf-ubuntu-12.04-x86_64.deb apt-get install -f service fastnetmon start ``` #### Ubuntu LTS 14.04 ```bash wget http://178.62.227.110/fastnetmon_binary_repository/test_package_build/fastnetmon-git-447aa5b86bb5a248e310c15a4d5945e72594d6cf-ubuntu-14.04-x86_64.deb apt-get update dpkg -i fastnetmon-git-447aa5b86bb5a248e310c15a4d5945e72594d6cf-ubuntu-14.04-x86_64.deb apt-get install -f service fastnetmon start ``` #### CentOS 6 ```bash yum install -y http://178.62.227.110/fastnetmon_binary_repository/test_package_build/fastnetmon-git-447aa5b86bb5a248e310c15a4d5945e72594d6cf-centos-6-x86_64.rpm service fastnetmon start ``` #### CentOS 7 ```bash yum install -y http://178.62.227.110/fastnetmon_binary_repository/test_package_build/fastnetmon-git-447aa5b86bb5a248e310c15a4d5945e72594d6cf-centos-7-x86_64.rpm systemctl start fastnetmon ``` #### Debian 6 Squeeze ```bash wget http://178.62.227.110/fastnetmon_binary_repository/test_package_build/fastnetmon-git-447aa5b86bb5a248e310c15a4d5945e72594d6cf-debian-6.0-x86_64.deb apt-get update dpkg -i fastnetmon-git-447aa5b86bb5a248e310c15a4d5945e72594d6cf-debian-6.0-x86_64.deb apt-get install -f service fastnetmon start ``` #### Debian 7 Wheezy ```bash wget http://178.62.227.110/fastnetmon_binary_repository/test_package_build/fastnetmon-git-447aa5b86bb5a248e310c15a4d5945e72594d6cf-debian-7.0-x86_64.deb apt-get update dpkg -i fastnetmon-git-447aa5b86bb5a248e310c15a4d5945e72594d6cf-debian-7.0-x86_64.deb apt-get install -f service fastnetmon start ``` ### Debian 8 Jessy ```bash wget http://178.62.227.110/fastnetmon_binary_repository/test_package_build/fastnetmon-git-447aa5b86bb5a248e310c15a4d5945e72594d6cf-debian-8.0-x86_64.deb apt-get update dpkg -i fastnetmon-git-447aa5b86bb5a248e310c15a4d5945e72594d6cf-debian-8.0-x86_64.deb apt-get install -f systemctl start fastnetmon ``` #### VyOS We need to enable Squeeze repository for some dependencies. ```bash configure # squeeze set system package repository squeeze components 'main contrib non-free' set system package repository squeeze distribution 'squeeze' set system package repository squeeze url 'http://mirrors.kernel.org/debian' # lts set system package repository squeeze-lts components 'main contrib non-free' set system package repository squeeze-lts distribution 'squeeze-lts' set system package repository squeeze-lts url 'http://mirrors.kernel.org/debian' commit save exit ``` ```bash wget http://178.62.227.110/fastnetmon_binary_repository/test_package_build/fastnetmon-git-447aa5b86bb5a248e310c15a4d5945e72594d6cf-debian-6.0-x86_64.deb wget http://vyos.net/so3group_maintainers.key sudo apt-key add ./so3group_maintainers.key sudo apt-get update sudo dpkg -i fastnetmon-git-447aa5b86bb5a248e310c15a4d5945e72594d6cf-debian-6.0-x86_64.deb sudo apt-get install -f sudo insserv fastnetmon sudo service fastnetmon start ``` fastnetmon-1.1.3+dfsg/docs/PERFORMANCE_TESTS.md000066400000000000000000000070451313534057500206220ustar00rootroot00000000000000# Performance tests | Version | Packet capture engine | Achieved capture speed pps/mbps | Hardware | Software platform| Networks and host number | Packet generator speed | Tool params | |:----:|:--:| :----:|:--:| :----:|:--:| :--:| :--:| : --- :| | [Git commit](https://github.com/FastVPSEestiOu/fastnetmon/commit/d51b5f7198470668c5f19a0c57609902de984d92) | netmap | 12.6 MPPS 5795 mbps | Intel i7-3820 3.60GHz 8 core, ixgbe driver from FastNetMon 10GE load: 100% of all cores | Debian Jessie | Single /24 255 IP| 10GE wire speed 14Mpps, 10GE | Connection tracking disabled. VT-D and hardware virtualization are enabled in BIOS | | [Git commit](https://github.com/FastVPSEestiOu/fastnetmon/commit/d51b5f7198470668c5f19a0c57609902de984d92) | netmap | 12.5 MPPS 5739 mbps | KVM VM with PCI-E passthrougth + [irq_balance](https://github.com/FastVPSEestiOu/fastnetmon/blob/master/src/irq_balance_manually.sh), Intel i7-3820 3.60GHz 8 core, ixgbe driver from FastNetMon 10GE load: 100% of all cores | Debian Jessie | Single /24 255 IP| 10GE wire speed 14Mpps, 10GE | Connection tracking disabled. VT-D and hardware virtualization are enabled in BIOS | | [Git commit](https://github.com/FastVPSEestiOu/fastnetmon/commit/d51b5f7198470668c5f19a0c57609902de984d92) | netmap | 11.2 MPPS 5164 mbps | KVM VM with PCI-E passthrougth, Intel i7-3820 3.60GHz 8 core, ixgbe driver from FastNetMon 10GE load: 100% of all cores | Debian Jessie | Single /24 255 IP| 10GE wire speed 14Mpps, 10GE | Connection tracking disabled. VT-D and hardware virtualization are enabled in BIOS | | [Git commit](https://github.com/FastVPSEestiOu/fastnetmon/commit/0ab076deda7d8d0dc4739f7cc963dca84f62f9a1) | PF_RING ZC [git version](https://github.com/ntop/PF_RING/commit/b67a6f46a06e68f2bb6cc53e9d452cc2cbe5f18f) | 10 MPPS 4561 mbps | KVM VM, Intel i7-3820 3.60GHz 8 core, ixgbe ZC driver from PF_RING 10GE load: 100% of all cores | Debian Jessie | Single /24 255 IP| 10GE wire speed 14Mpps, 10GE | Connection tracking disabled. VT-D and hardware virtualization are enabled in BIOS | | [Git commit](https://github.com/FastVPSEestiOu/fastnetmon/commit/0ab076deda7d8d0dc4739f7cc963dca84f62f9a1) | netmap | 7.6 MPPS 3482 mbps | E5-2407 2.20GHz 4 core, ixgbe driver from FastNetMon 10GE load: 100% of all cores | Debian Jessie | Single /24 255 IP| 10GE wire speed 14Mpps, 10GE | Connection tracking disabled. VT-D and hardware virtualization are disabled in BIOS | | [Git commit](https://github.com/FastVPSEestiOu/fastnetmon/commit/0ab076deda7d8d0dc4739f7cc963dca84f62f9a1) | netmap | 7.5 MPPS 3423 mbps | E5-2407 2.20GHz 4 core, ixgbe driver from FastNetMon 10GE load: 100% of all cores | Debian Jessie | Single /24 255 IP| 10GE wire speed 14Mpps, 10GE | Connection tracking disabled. VT-D and hardware virtualization are enabled in BIOS | | [Git commit](https://github.com/FastVPSEestiOu/fastnetmon/commit/0ab076deda7d8d0dc4739f7cc963dca84f62f9a1) | PF_RING ZC [git version](https://github.com/ntop/PF_RING/commit/b67a6f46a06e68f2bb6cc53e9d452cc2cbe5f18f) | 5.8 MPPS 2667 mbps | E5-2407 2.20GHz 4 core, ixgbe ZC driver from PF_RING 10GE load: 100% of all cores | Debian Jessie | Single /24 255 IP| 10GE wire speed 14Mpps, 10GE | Connection tracking disabled. VT-D and hardware virtualization are enabled in BIOS | | [Git commit](https://github.com/FastVPSEestiOu/fastnetmon/commit/96510ab094e73a7a3822b6a6c65268e77cbe9b94) | AF_PACKET | 1.6 MPPS 780 mbps | i7-3820, load: 100% of all cores | Debian Jessie | Single /24 255 IP| 10GE wire speed 14Mpps, 10GE | Connection tracking disabled. VT-D and hardware virtualization are enabled in BIOS | fastnetmon-1.1.3+dfsg/docs/REDIS.md000066400000000000000000000013131313534057500167350ustar00rootroot00000000000000# Redis backend I introduced Redis support for storing information about attacks. Redis support is bundled to project installer now. Please use installer with flag --use-git-master if you want Redis support. Please call ```redis-cli``` and input following commands ```bash keys * 1) "10.10.10.200_flow_dump" 2) "10.10.10.200_information" 3) "10.10.10.200_packets_dump" ``` Basic information about attack (stored immediately) ```get 10.10.10.200_information``` Complete flow dump for attack if flow tracking enabled (stored immediately) ```get 10.10.10.200_flow_dump``` Complete per packet attack dump (stored with some delay; can be absent in some cases of slow attacks) ```get 10.10.10.200_packets_dump``` fastnetmon-1.1.3+dfsg/docs/RELEASENOTES.md000066400000000000000000000015361313534057500177670ustar00rootroot00000000000000# Change Log All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased][unreleased] - 2015-10-05 ### Added - GoBGP integration for unicast IPv4 announces Documentation: https://github.com/FastVPSEestiOu/fastnetmon/blob/master/docs/GOBGP.md ## [Unreleased][unreleased] - 2015-10-01 ### Added - MongoDB ready for production. Documentation: https://github.com/FastVPSEestiOu/fastnetmon/blob/master/docs/MONGODB.md ## [Unreleased][unreleased] - 2015-09-29 ### Fixed - Mbps volumetric attackps was not being blocked ## [Unreleased][unreleased] - 2015-09-28 ### Added - Multiple instance support. Documentation: https://github.com/FastVPSEestiOu/fastnetmon/blob/master/docs/MULTIPE_INSTANCES.md [unreleased]: https://github.com/FastVPSEestiOu/fastnetmon/commits/master fastnetmon-1.1.3+dfsg/docs/ROADMAP.md000066400000000000000000000004171313534057500171560ustar00rootroot00000000000000Roadmap of FastNetMon project - Implement flow spec management for signaling to uplink (raw support, only protocols and src/dst ports) - Implement attack analyzer for detection of packets which belongs to attack - Implement web interface - Implement API - IPv6 support fastnetmon-1.1.3+dfsg/docs/SLACKWARE_INSTALL.md000066400000000000000000000043601313534057500205760ustar00rootroot00000000000000Let us assume you have full Slackware install. It is tested with version 14.1 First we need to install libnuma. Download ftp://oss.sgi.com/www/projects/libnuma/download/numactl-2.0.10.tar.gz (or newer version if any). ```bash cd /usr/src wget ftp://oss.sgi.com/www/projects/libnuma/download/numactl-2.0.10.tar.gz tar -xvf numactl-2.0.10.tar.gz cd numactl-2.0.10/ ./autogen.sh ./configure make As root: make install ``` Now install log4cpp: ```bash cd /usr/src wget 'http://downloads.sourceforge.net/project/log4cpp/log4cpp-1.1.x%20%28new%29/log4cpp-1.1/log4cpp-1.1.1.tar.gz?r=http%3A%2F%2Fsourceforge.net%2Fprojects%2Flog4cpp%2Ffiles%2Flog4cpp-1.1.x%2520%2528new%2529%2F&ts=1422275810&use_mirror=cznic' -Olog4cpp-1.1.1.tar.gz tar -xvf log4cpp-1.1.1.tar.gz cd log4cpp ./configure make As root: make install ``` Now install PF_RING ```bash cd /usr/src wget 'http://downloads.sourceforge.net/project/ntop/PF_RING/PF_RING-6.0.3.tar.gz?r=http%3A%2F%2Fsourceforge.net%2Fprojects%2Fntop%2Ffiles%2FPF_RING%2F&ts=1402307916&use_mirror=cznic' -OPF_RING-6.0.3.tar.gz tar -xvf PF_RING-6.0.3.tar.gz cd PF_RING-6.0.3 Install kernel module: ```bash cd kernel make As root: make install modprobe pf_ring ``` Install library: ``` cd /usr/src/PF_RING-6.0.3/userland/lib ./configure --disable-bpf --prefix=/opt/pf_ring make As root: make install ``` You must add this line to /etc/ld.so.conf: ```/opt/pf_ring/lib``` Then execute command as root: ```ldconfig``` Now you have all you need for compiling fastnetmon. ```bash cd /usr/src git clone https://github.com/FastVPSEestiOu/fastnetmon.git cd fastnetmon/src In file CMakeLists.txt coment out the line: target_link_libraries(fastnetmon pcap) mkdir build cd build cmake .. make ``` If you have some 'boost' related errors it is recomended to remove your version of 'boost' and install the newest from source. If compiling finishes without errors - you have two binaries - fastnetmon and fastnetmon_client. You can put them for example in /usr/local/bin. You can put /usr/src/fastnetmon/notify_about_attack there too. Copy /usr/src/fastnetmon/fastnetmon.conf to /etc and edit it for your needs. Create /etc/networks_list with your networks in CIDR format (one per line). And you are done - you have installed fastnetmon. Author: Martin Stoyanov fastnetmon-1.1.3+dfsg/docs/THANKS.md000066400000000000000000000025621313534057500170660ustar00rootroot00000000000000Thanks file! For all peoples which helped this project :) - Luke Gorrie for SnabbSwitch and help with lightning speed packet processing :) - Vicente De Luca for redis_prefix and InfluxDB optimization - Andrei Ziltsov / FastVPS Eesti OU for testing and patience :) And for syncing GoBGP's gRPC intergration with upstream. - Luca Deri for PF_RING toolkit! - Elliot Morales Solé for improvements for ExaBGP integration - Roberto Bertó for Docker images and docs about Junos - Alfredo Cardigliano for helping me with PF_RING libraries :) - To flowd project for awesome parsers for netflow v5/v9 https://code.google.com/p/flowd/ - Roland Dobbins rdobbins at arbor.net for mitivating to add flow support - waszi for testing DNA/ZC mode - Martin Stoyanov for guides for Slackware - Andreas Begemann for debugging issue https://github.com/FastVPSEestiOu/fastnetmon/issues/90 - t0ly for VMs with FreebSD 9, 10, 11 - Cojacfar / https://github.com/Cojacfar help with documentation transaltion! - Thomas Mangin for help with ExaBGP integration - aabc for ipt_NETFLOW very useful tool for testing netflow plugin - Denis Denisov for FreeBSD rc script - Alexei Takaseev for AltLinux packages - Ben Agricola for fixed CentOS 6 init script without daemonize option - Dmitry Marakasov for FreeBSD port - Dmitry Baturin for huge help with building iso image with VyOS - mdpuma for help with Gentoo installer fastnetmon-1.1.3+dfsg/docs/UPDATE.md000066400000000000000000000016221313534057500170540ustar00rootroot00000000000000# How to update to next version? Well, if you need update from stable branch to developer branch, please follow [this reference](https://github.com/FastVPSEestiOu/fastnetmon/blob/master/docs/DEV_VERSION.md). Please be aware! Binary packages could be out of date. Automatic [install script](https://github.com/FastVPSEestiOu/fastnetmon/blob/master/docs/INSTALL.md) could be used for fresh install and upgrade. Please consult [this page](https://github.com/FastVPSEestiOu/fastnetmon/releases) about new releases. Before upgrade, please compare your config file with Git's version of NastNetMon [config file](https://github.com/FastVPSEestiOu/fastnetmon/blob/master/src/fastnetmon.conf), add new params and tune old params (if something changes). We trying do not broke backward compatibility but please be careful. After upgrade, please switch off 'ban' capability for some time and check toolkit behavour. fastnetmon-1.1.3+dfsg/docs/VYOS_BINARY_ISO_IMAGE.md000066400000000000000000000011231313534057500213460ustar00rootroot00000000000000### Here you could download prepared VyOS based (1.1.5) x86_64 iso image with bundled FastNetMon 1.1.2: Download here (243Mb): [iso](http://178.62.227.110/fastnetmon_binary_repository/vyos/VyOS_1.1.5_FastNetMon_1.1.2.iso) Sha-1 checksum: 7faf883672dd2b034ced440175a2f49ecb0080c7 Standard login and password for this image: vyos/vyos FastNetMon will start automatically with sFLOW, NetFlow and pcap support enabled. Please be aware! By default we listen remove ports for incoming connections! Just configure your netflow/sFLOW target to FastNetMon appliance and run fastnetmon_client tool. fastnetmon-1.1.3+dfsg/docs/VyOS_INSTALL.md000066400000000000000000000015661313534057500201270ustar00rootroot00000000000000# VyOS install reference This guide well tested with VyOS 1.1.5 only. First of all you should enable Debian Squeeze stable and lts repos on VyOS box: ```bash configure # squeeze set system package repository squeeze components 'main contrib non-free' set system package repository squeeze distribution 'squeeze' set system package repository squeeze url 'http://mirrors.kernel.org/debian' # lts set system package repository squeeze-lts components 'main contrib non-free' set system package repository squeeze-lts distribution 'squeeze-lts' set system package repository squeeze-lts url 'http://mirrors.kernel.org/debian' commit save exit ``` Update packages list:```sudo apt-get update``` Run installer with curl: ```bash curl https://raw.githubusercontent.com/FastVPSEestiOu/fastnetmon/master/src/fastnetmon_install.pl > fastnetmon_install.pl sudo perl fastnetmon_install.pl ``` fastnetmon-1.1.3+dfsg/docs/images/000077500000000000000000000000001313534057500170145ustar00rootroot00000000000000fastnetmon-1.1.3+dfsg/docs/images/fastnetmon_graphite.png000066400000000000000000012314651313534057500235770ustar00rootroot00000000000000PNG  IHDRE iCCPICC ProfileHTSIBJ RBo*w%@PEׂ(@E^ *+b;Io{9gg;s vl0 U [/ f%$&H0zEF-nDrj!~oQ8 Sylȇ58BQ>.hן/[*"("Yi2֔p>1Q>} SlQtI~V' ![ |9l.^ȓs +P!|'Lf/B s%;K<=X(IfHjG)!s.1L?7%gz [$h8??(fSD9Q^_BGsdqU*?2ixNt?.2rW^ftȨt18JTSfaK5AO b 1m\L bG5cpvyG "G*^V@lyc=p{ #cdp X@ k 9<d- D Ũe,"^"hPyqV(/VJ[ )l\w]PxwX co%}DTiIxJ&<"\'nʃ}(,ftQ)}ƽ~q=GOl%v;.`mX3`a'2vL3t&-J-X[[}٣ D ys% 'G8WOKgy?2$XNbXY ~o6¼͖s14}8ƻo6pyXG,*pɅ(@ u p`b@" G=dCճ|l`;j~p46p@¹^A #BBhQGtCAw ED$IC,CJRى!"G%s !@H#&{Gg##L1Dbqq5qN&>&H$u9ɍAbIE-}RYC!Rry/8<,(g("!Ǖ+Vn\>aŘFdPP) 3{7zSȟ(L5PS5j;6 F3yҒh5:):nIs&zLB2C W}ي +*TRb(Y+E(e+VګtA2IHO\y) apg}*Dc **ʪvqsT+U213\<ȼ4AkބU&Lx6QSV֨v]:KO=S}z} \Lcl*3U&NL,xpMTL3Js.˚CZZZB-Zڞk0tu:uNReyXӬA]M@]NNa=cXzz)N; t 13t2L7lx𽑱Q fjAƅLh&&&&LNLP3{tJ+樹9|y$$IIՓnZP-, ,-Z2-C-Z6[l09i&ʲmuZ:zuk3M5["Wvv<*[ 0_D Ɏ[o:8E:v:Lpv^%߮{]O1›{c7=7N^w{^]G#O}Og^^^^z[yxqYv)U=O H \x3H+T4 t5$:"QY(5 v/0\""6D܏4̍m*qjʩOGfDϊ.;fmXXqlGB $\JH'$$ MiZtEo01gƅ3f0=P2!9>yogv=5eynxgn6{}W3gFdddg5f 99sr"aoKAQhO7#%_nu.M?T|799]5Y/yyu/pׂ ) ;/Zoq%%K~_jteZk-_O?ыDE7Wؾ__ٹvՖU_KJJ>款?I]ӹam::=ז*>i#kcƷfmPfW}3exsoyhy-|H^]ٸUs몭qTyV5l^[;v6UU"*tws8RGcOɞ/5ڨuuu{5Gag#8 >ɿ8rӡÆa)nB6 67$t >z7jt*[{r|'څ'N>qT©k<rYy;q| G/:]lp#~ӡ㕖.){ 𶑑/}B{t ]:l^Z pHYs%%IR$iTXtXML:com.adobe.xmp 1800 2880 1 ˔>@IDATx dUyaa@vT-q1*F7˫Ѽ1.qk5'q+(FeQAa=s{jzVssy~9t衇ΖJeW*\>6n&''z<7;;j[rFOWcdu<k//~? ,Ej|/ g%"xGgWtYG/¢Wr7)7===oPtdd$RF#T:#~[WJE"%VٷTO@+NںΡ迩3:@iG{@YcGK'}[jo+\֕i ʧhX~g9׫tݶ?-jm("< h_rOy[co/6ckxw^ha9_[~μVO*Z*%Qm_uDIeTz ?3}Sչ_nm//vo6@_q}?a i g@lY~_D~cu%UNW>P$y݁&Uq~Oͥߚ(E7MPC?6f¨.38Ah?这{U_ ?9/<۫~zy_ZnEGPOZ/Aqh=lwtz8?qQ a_lG~EQG?₩O_P ^% ,Qڏ})ot_/Nl_HFҰc??/ RkmHlk;^S°o׼Tb8VK}w233ӌkhV/N&$7E@Tn7~J|'/[_iGGS?ð=~!eiB[211 rZFGk!hZ>w9+q~?q51AYK 7卿^e/_Ej?OCibxYK/;G (^%G?y_err2E-/E=JfÈLAT>D}`m"&v%E)Gnb#֋_/E/?Sg"?/MLA/;GR!/?߶l \GI[5nke(/DRgqP>]{7+>9uB"O^Ø?_Hf"E# ;&݆~sC:߼okΟYWWHJz/ad~QcFƘҔy/gi&ݞ/>zTTg??鳊?x/SD/ _wWpKm֭[7C|T<]v@V"a)nǧ(G_񋼤 В:~l]y ῵S /R}fi@0jj0@7w+RSy3@E jdԴo<,jſ4v󉟉*y>Kyÿ^wy_l__{fiTx2Wf^ L3lPܰ_jXXe%KiY|E!h_v`_ ?tW+>.y{.?Mb!}wyO"G5H^ɦXJ rFFFkRz_d)VjXX cÿ-XNlaXۿOL)E9?jZ6@3S{_R?k֭`F>w[<Fc&z_ tܮ(G4ǁ?+WjGz?Wr"_lGx Yc1%3l/ҷh%FDD4( 4gUMgÈ |)ķF吿,(ڝbw?/RQ{El?߼=?ow9SR>7M-i ʧh˭/6Սmy?lk|~gm(I/Gz?W[_"_:E_&_vB_?Gcm#T^hBB#iKK@I3}_o7?Ys_?oVBe(͟B ϻ7[ihZa6n~f?9foǘ?266)ZTr^AYQ# /JuHS_R>oE34nOG¿lT*~?3G~Y{EGhA\_Ň+RJ֭U!K>f;|T+47)`y/Wʧ*@K +T̗"tA?O<һ?EoƵr[j/"??/XOFSYj̗^Q.t7~ jdt%*c2mwa<Fc&z]ڜ:y/Nsl|׺Wuk׮u+VXNtw}o.Bw;IO:|Z69C݃t?֯_R'P{ZrUsSY{_o}X@~?7kocf?/[P?1ODNE%yʮGΆ!(J58 җˠ{{}nrr";>>V^gOOܫ_j}ϽE/e)'O~k_{l=ysxi gu֜f[CHnQhw uuv_<ÿ}"_lGx+YB\_\OkJs~9&PmD)yoժU;_ ztcXئv\r7o q6m 3<=Opv{~_O}S /e6%_tj_sw9[y_Wt3Sewի92{]cOwwZB.O!DCgvs?e{3S+ J3!~IтE~Se(ڧeMF@TjP7ǻ|#n]v |)87mܸ1)okz7){waaqg"Zзxl0lSO={ԣ^W dj1&~~b_l??eG_[Srm߇bt,Vs2JQ4(/ߓ$?|XowyW6N0P>K4m|?Zov?͛J~}\ AE  7$b^zߐ@>D̋b[Y(GuT~ (),MFSO}k)"ºF7]q__܃0 /xAKopU*#?oW)kMSz'2lӳ?SnlUWb//ob??ҁ4ų[u 0-rsSҾo׺!zWO>yAΖEv0 _B8 oC?zE mvsXH=,?&b!nv)r9ǰϞ1<];g~ooGpb/џf?pX^}B{?~5ohԘO4MSg:N_ 0}7tS!*?q>JPۖ{^>uyk׿uiӦ05/?)z=yOstp:D^Lm@\RSd kGS?/?ҁ4/{CrΗe= M?/{@;%mebbuZ͍BGU*c^N5jGo|Z4i`nbbC,cAW^~n͚5go7}<^_|%U8c' ep4u|&_8W2EϳN T+믃qQhIDo'c@^nʕov}SNg0Y6~!}lIOINv衇u3ovwyO}SKuLdWKӞDŽ;sƍW_0-,*)ޅkϘ﨣r|3LI#Ozw~B똥3ϟ߫R~{S/|7_tsa@/̽.]?_,DBOHOH!,ԯhHKFp1aF/(?^ڥ^Ht[no*('<}K_j׾R~_~ɏm _E>}@| Mk/#(7|3sW#t^{{g׽u??uou&O~ҽ/rz+C0T׾ ܅^R^#Cu]uS@Se~9Ԩѣ>i졇EAg?+ g7)H)T?k}0N #[uM+(ꪫz=uN`=s݋_q'ܞ{[|bӟԏDN~;Cy΅F?їo{ x;0.b<]mٲ}!nM~O ^_r%WU8w\0ؒ៭n:W/;?=]~?coy:m῿W~ÿޏ2Wps<7e_й.HKޏ@>ljdԴ+,oZ_Huq<!h`FkDP5ypJe~6mr/x (ׅэҗ0L?OvCNBGlOA;<{> }d[ )9~:NuJv_V^]uSO}SYK%T3 .jd:~k jĭ[g6ff 'o}6+W^VWhx[׿~Jݟt9;g~,~lo7^g9Ck֞-bo6f iBhMe|+Ԯ ow?ogdX?[9mc0E-@th{ZV)ߊَOSjJܼysG//' կ//u9o/ٽD|3iD|U,d_~E>FYH?pRޮݪ7׼5To=A@GL1ײk?0}>䓝vv{{/i`/P̞C+^ӮXE;rWJ/4Uz;7p[\ϩ5rWOyS7GuKI)/$?,]V)t9kż? ?-X|~߹NJfzt9k}a{O5?]FU7wϧ??G"_QSu~LU%4FF黄]۔l# ((ډRi3;RT.(Lj>گn=Sy#w1@0}FGjԬ9Z^r{)׾6L Nuz<0F]ZWyԈ^}Sw ^?/ 05a\^nɎm_O`qFYYv>+}9Ѵzf6-oWס|w+,H8}B_k+_࿿i0w~Ϡ_o6f 4{;%^ƳrOA>?^L>Dz=V.шӱ*U|=]Qi/o/NHrj*L#ž֯_UUB#SMvYgI_I/4嬦7Gw??M~:X>=7KMQvX۟iz}*\[A9)Js-PbF Zʛ* QJڒݓ+?`Kgyfv7/iO{я. Ѿؑ/vM.V)k ÿ#^,R_w_(DOo0_xw>(ZAQ@Q@czz&B0+})P Kц%1ªJ~>TOVv mQ Yeۙ7xmv^9C1oݏ0PUg)_;c|Pt_w啿h)`x>=ucr-=?d<}ckaIAo;n|}VN1}gTt!豼WYT3>RX4x+ˆp]?(4*]kS~;3>9CA>lO ^<~k ߔ??o1_xsˣy;_Kyl?Y$_՚~MCVe\.M9JK갸%[ߚ]6o6;<3-T^۱ 觶MioIis_;_@F? SܦMT~׿+Ï9]w=ܷ?qsGuTmwY8}_5᪀R t{} :*JO>ySe4̻m t =G?mkmoG3¯FAa+ӹjSr*]uUnm~V\h BnϾccc-+l4{o~Գ]vYj5̽Uc[ٰaCHSs5|64ilv*[oq^ziv駟zVϏt0ZĿ0n^u(SX_{f1_oӠ_࿿~ÿ?f hAsF_䤗S%9OɂmD6Qgf)AcClA"ߑ1k_W^{et.:Mh4?'tXP[ouVon~8SRpFizjwA *jIחH)?v VZ~C%4umES߉pr';~H۰N8ѳ #8"]_>as|0g4e7w-(~6oH\x {f! "߶j=C"E߬7$Zbmk:5M Uu+65R f۳u?T]OGԵlIϴt\I@ղ% ußΧG35-~N>d?r!NSjxy;%Mϝw9LUN˗_~yKs]tENe%^l.{prG.:Z?K=]mV)vWj*d}TZО۷ۺ0lO \7s/ϯ|+^A{2{'{tb?/Hb^9e? A_0G_LiO-СXPԺYdr9M8?N?C˲n+^|'_v!ۿ/~ \/7[/??/D_D*֡" l)QV0"4L (@jk%NJw饗 ~4o5(Z+¿FdMR=()M) -ycOrs*w~sW^`h'S0oVk`qRbo}R5TRl4|wuݿx_*}L3-8keO<=R)}[}W%5. C6Yem7m/~¯x3Qw{OGN7>~oC?|o/ MO]_vioq?Oo#u*[/!ڪ}$Yӱ '?I'>6R:/w)]Q0-uk?+W #DԔvZ8e\ cv/1o_mSPTK>#UPt0ّq^';KQ[j,h{6p UPZ#v%QR^/Vat?- I g 4,W||/ߪs7۫ϻ7[غ(v|iΟY?l_9u?oimۯ?oQtB(7ѺF)J>3>>mҌOߴ|ֳ?w_Ww{ꅿ=Y^j?ZV(Fٔ7a;=9gQ*ߝeֈP!1Wv'p˷k% ,U\ l$7fP6ݗQHό{{kO[a/dEz_^-X[i?ey۪O{sѠ_-U+Qa%Mi%]&SIz|>goArJ?ݬgf?gi{?񏹻nokOLQV^D V?q _7YlU7Tގ4 UgAY1e}(i6FjVS--slo_ak^aѵƯh3qv ^i[s ^y7ws"P7_,FK~@S?OŪn j*2559L;ϫU}ۏ|STS}oo&aXLR^{^֬YN_6ϩ.%+9n o}[svep_][)~:*~/wr==C_~}⩠q6jy+k7؈F7)H7=v1_Jr-\S]L+އoA?)0_2\f)ϯCPw:=?p׶?otm+7mYae+g)}Z58-Z_N>+\^׵)rj'ۃR󋇔t9eoz~8kE~fz~s>.}8.k[zt9tYG:3,`O|\?|m<_q_D-n$)vl:*M8ǹg<>[qM&N;ZzjjժD;raG]jzWv_vʫZv]mWaQ %/yMVގ_u=g?oC'TywmƁlvέsغ9ukemVVX?3B NAR+k-O=`ׯҵR>nOy!%^[הNcǁv04\_~ [?[ٮ ׼ۯ'?퇥7c+7{0\zw~2?b#j_~iV÷nqdr ׺\vG|o7?S~vzԣ9\/Y9$_ 'WY}#o~S^tEO @ב׿=y ~]wuaY7Ahu -d?=}s|{s^-g{ի^gӦM!*m4g?tsOi]>W]u׾[`X{a$V<7V7NgmrU/{n/|!4ۿ[w#\w!:>p=Cu^G2ų{_'~^~?_+G_B_6}Ip+N'WJC3-~! )Ȩ)]ьk>d۾/y.{2نOB拱Mūa<*mhͯ~$0_iK}M|m:ȝ}7>}׺+_Hэ7׿ m/n7»םo\#p4wyy^׆酗z}{ݳ/1<z<5O'Ž|ӏx[<o[Q3<~9-o{A/o~o9;o0?d<[?E#lĠDC@IDATrC@@vP#;h_P>?{O}g>sOFFtosmQv]w {yaDZ*_ٍr۲WN߱Tp /to|[?? AM}?k7-/{U(po~ӭ[Νw޹CW2NtT㯔ʫ(O۔~%߭Y?{k_Ԟx~;|y ~e_w_AmtGz_333}6,?f_ ÿ_^9?b_f?a2W_:|h$FGHEJhe D4]n.Q^hO|1yLs_~~y) j1F_4{^eoDG? SO=ܖ򖷸38)41-l?kZxO\u6mO?xG>=A {~ݻ.g>3LjSxӏ(;'nv;B[ncp^z{sGǺ~[{K_7/ ُp|s95,iN5F+}nÆ !ȹ;)=O:|Ve]؟2\6~A~ԭ-*۳=ֆ4{gF* L:F*VM4t}if8/݃ ;1ksrT^AT}o?G<YY'l]8mr|~GN #p7&ooA;#/9>g?ӟ{wiJM~+O|ݿ~_j)z)=~LJ)xP_|Oli{sڟsPh`}v]v x+諩| PWuO-Vo ?}I%oJ M1OZ#EgbqzEh4+`dU8 Ϧ|>_/~)Οx})i?TU+׺RzZ{mnur-=kk .ev]*^4v>uN#\rqo[n(?v<b_~{?jf?o{?w?uol211<ШZ&o  (7⇤lN{7pQTyeCl/.~?EW#x_JE꿠ͣ?/O@'C )%Qvʨ_ou৾3ZOz\AQX)+KжuE_i_)_<W(a᠄Oh1/lWG_JH_3}_6<GElW)PdYhh\T˾b%;֖)GisٲE?>{fi/ *~?vZ/-?)G?{׋K_)EZZFi~uxjd#Vy/hj6 /|x?/Gϴ|G,G?W:Cg$rYTQl=Rm~9S>Jwbߗewᇻ'?bTo _/RK?>QSSg@OOfxWiUj+QJ (QEd9wttxGyl44 2Z6RT|?5 Onwq _~Gc(E꿠bůhuP,jm ))dae?~7U7z6mڴ[=to ߽V$/0 :ڋoy!L~R~ W~v{k_O}ɟs:叿^o!?[zͤO'g[Wv,B_VEHr#|KATTN?AU7 cPÿ-g+Ey~[9=S_+w?f 2JZ N65Uw7oSEbdF;ZTqѱ0%mq#~#7W9y}NOoEk?OO_:@b_PSSSSSK??5 I vA{ȱ~AY#FnEՁj"k4fBPR@i:NJ岏R>9Is_ j/.~? ,W /Guk,aBeuGe O.DU 4M\j0nN'^?כo ?owE|^3#.oOOOY_z?|gީ; m4Z/Ie1JF*XZ?sh[l(_/G~/?hSKm>:Ρꃜr\AO-QIL>,(#H?GbÿI>oHļ/355M7g_w?o~{^ڿzN^_}S=wxGM-G}3yYך77 5unZ 2UJF5z^,HR?q'>^W}SzPG?vGGC/O`į_2%\H Q76>ZAJHnͮ^7k|߄UP҆ lД/o/.~?~C/Rz*+?K h _c?SS/h8DQQؑ`@_o6ZTAr!Q "rt:E)GEݞXJ)|)/ MGMoz':ũӺ_b30),ŁYEU|k[)gs3_H$9uB7{k3&[B _HfgLηf运@^3&[BellKj5DE#m:f|||nh/M(7G#&/őg/[/~?W/RyAL?o`9Yߎr4ߔ9??m W_ tΖOM7\Q4[F)W}|jc-¿h/}sH@_G?_/9_zJ}tzz籠Fq{>:Z /)_Z o3 _/^8\/?CQxꅸ>_?)._v#V&9gQ~pTN#\)^(]~'%)oWLky_i>3c"7OO/?,e8?"ߠzi_"r&ٱF9c12w)oϺ??^wy?ђƽrؿ='?/7 4@x?v `~1gω?O/.}fZ/nJҎz}MMMIa淏\*i=Cm|*@r*[l:5eS/܇H3@oX/cfk7oG%eZhOt:!Y.TP:22_o`CXP>m])oK"֋_1GG\q/?Sg6f qx/cf?/[?_?-?V*i 8NbɆ*WZ)n:_)Oyb/[}_~tG?Yh{ggy ???g~[JMEl4"ZR`DSIsJrEҕ~?)[<_?O??O #MOOOOG<m4~p|a)n5Ǥ@-ƯC `_lo~Br)@g81h͋?oV?zWa'$u(C-KA?b/ÿi9!sD c??Ztuޅ?ܿd/?U?TƃqᦦÈCXP>)3eK7;?_?t Mh>~ן~}q4O M?/{@*nd7+- l=nO7gRN|'4EZO-NJr_lX{Hk[.]0=Բ/|wE='[^__?i{~oVCRˠtL8\J ĕOi|k_lG~ooۺ?{ߗ=(pbv!>=}O|ۺGflttԍZE>ʭT.z}mٲ9+q~1X+#tb37Q׫🍄/?S;P_G_J_e?e#tzf:622FF*2~}֏P}cTb2͌KaN@77W|7)˞SXe/ F~1_G_SSSSS//3 $h60'Nɏ*)0 6ÿPi!QlWo}amh?y:`>igO9dJmUHIk*zd~Da,OnjuPS>oA`I|'VzTTg??⽿?x .K/ERO P]񋼤 ВP ~l]> M X/?/Rfh?Ӄa7bRőT33eq{i%qjQ`G|i4bN`񭼝׸/S?[o:4zſ]/N5B?NIH?w^?O?ů+ &bӦ5!Imx|u|g???ΜFh*7M8۱&󊟸diZO+Sg+Ru w?f'ZlGL׆To?o`vxVo?unOll_E{nsك?/[7 4hJ:Ҏz}mٲ7H1?n϶8 AV\50aNOr"_lGxAOos7@}'bv_iNnjjStMNN gUų*Q||ZŷF叿G U)v_<ÿ}"_lGx Y_?S˾y 1Qo^S*iZ8箁܆*Wj*8x=$A@ =,F7):Q/eM D}4#j٭M~UwffnVъ .~x5L;僡e߄NNnv['v<;N@@@@`4|s>~Z\Mf% ?}t}hu^ݭwG8n vc~).[еtM` @Pt0x.A@@@(Bu>\Ζt3%N~\{6^;ݟ[=N1Ѳ+2?[}']՟Oչt-]         0 K% x!:1'~=M/ჟc~f]E9Qw}sV#aiQQ:mhs\#7EsM7   43 c:~Thm|$Eo^]x9?aӝt}vuu?]Mqmyᛣf׆k.8@@@@@@@@@/\@@@`(Ѫ⇀rߴ;kC@􀵻q;U{[?;=Orx>n1wFQ:6@A3XN4]Foj-L{fw5\~_x)WݍKnrjM7fݖŠ"nC@@@@@@@@@rAtFm}WTgw@5вV<u'g~o/k(`Ӻ/cnJh8Ο'|Ko5I         AXeE1}#kv_t߸k wݿi;ܑv:­yɕu5՜_u[&} !EsH    4 AQyŨ;`sn1.ZosTS3%g'wWW\pZwon SGs;\Rv ?  i`h @P?sU@`nNJe77܆n>њſt7vޥ{+ݟxTw~z{?MLL8N~*ݒ2arP9A@@@@@@@@`G @PtG5@@@Trv.pY)Ѡ3ӮꃝkXL}׹rQJߚ:>5:ۛt5ָ]gQ]VJ99lWnW89@Pr\veQwY몪ߖ/ML֝_l[k~.w'43nR6}?1 E,n@@@`~B#FcPm͓>Yqx=niqerGF%;HM~[5^Kg?|4 D$>: @@@@@@@@@ 7 @%W0.j7G#([{`n;pSS~TƖܦ-WӢnfڟk\Qkl7r[|~6o5f.XM-Lnj&{jyvimj{mҝ#xmEm4WZ|\>+elOØnGN}9L*9M~U@tKبן[רvrW|ߎeltwѳ< @{1Wy$566I_]%@LMq>qӡ[v{zej#(ڄAE6`e3L6惚βA '4vt.e}<vTcΟ3Tk'(KS~$sݮnוկ_ 0J #%"P\RHTiv2E7lt7~ wUwc{jw: rec|en'c{GyV>(q CRyTSj$iL:4WqcnܗM3MZuWaCGȨsQnq.vY?\P4$C<F ? z}P+7YMOWmszG"Hy#p͓37oq{G++}T3B[̦Mf˝Uwk}]x՝Y^Z~[z'޻wm#D56 E[L       @[_S0cՒ{nRuܴ_y<'A#''Wwuwݍ 7j3*8;!Tjvߖ)G\Osוn>wXҖUˊQ!zu߄hOY!HGHs'SN{7DrjÉ        PH:E?nGYmٲ|? yכ_>fr%:CMJ@ (%8 -to2|d^tcbruN<%ҦS2+RH-Tg_;u$_{]+%PJ@ (%PJ@ 윔{Ĺ, ߢDC-oQZ (%P'+rmP6Ϛv*'=+%PJ@ (%PJ@ (kʊdD8e{ CfA?)D::̶NIJKS͖R!U(PGEsD'gjleȔ= 2Yo(R)3*.܌m}]IyjK$BdӘs`Mաb0n& >V,?"ͭrE*;y pL (|V"]R)#J@ (%PJ@ (%PgdG>GꎥeDwwI,.hTTFŋ%PYȂ"eaGdA*%QDm9qYl KQGp s=e☰IIEAJ e⥝` ͍x9{)sAdIyBҖKL+|梲m{KgJ e=h/4grSY^sB~IFˤ#DGRj+:^%'#ѾNs>)%P?>4ڥp)ݫPJ@ (%PJ@ (%phG GL Wp`$ sG胥I?X ,I衩XB*=6Kay,&ͥ*?JfFc)ypLƍ#g IU8($ĢdJ;cZC^AT ºZ=TF՛A])#Ғɑ&?X;)FetVXj!RHK7Z: =Z zz< ď7Z5Ql?xDNHnBIǫw28x'%' RSR.s[!5v&I^F-풵T"NIuU@ rR, DǕnz ~F= r2i/xEHsWZG:w tFw(~8=RJ` 7D[vJ@ (%PJ@ (%P/I0gN5 8s5)PPMJ=:ҕLIՑ҄V+]߲b)vOTWrtN*]e%h@pIH-qy4#-̽^)SòrfL inˈ]qf3ӚuDFČQY]J:^.yZ(~8-I)ӜtvhߠuK44>2 DCmC後y[-x ?XL"e|yEPJ@ (%PJ@ (%N 2*,K 짓zaZ ,EÐCLK˶CQSK 9EP;5JIu'RY#jǭ\ׇ~o%Ɩ҉)/qRdLՊ@FL ꢏc=4˃2>q5!)&FYER֭ ̚3&y_v5e5$d?:^3գ.jϕG5H4w+21V  ȤղxXY6wؓ5K?|Ro=?|=oB{  ێdEΛ1I2 DȖ{ɦmGMFd1x8?c%J@ (%PJ@ (%<8ˤ"f'az62,MY0-/@a9]&)Zg6 Y1_eRWW4`j\MHjxtI-R*6ΰ L䔆j2ݰe])E7uaHaDQ vF8G |$&dr'ouF|“p %A _F"Ag]+M%>]S\*y!xLL ɗwgkGEG4ٝyq~8В{LX/PJ@ (%PJ@ (%NXT,00ZII,9.t㉸$b+lLXC2!NF){g^+YkQI$en|TSݰI$0`& rtpo}y:>ݯPJ@ (%PJ@ ZݝN!4G2a tdksTIaacYYht9r6Ed|bmVΥ||{ij-2Q:lYDۓ=^YpH2OG{ 0EʹzKנG@=ǀc1ZɩHT<58 \a8\h`Qf] ;stJ<} vpnH<#K)|я! PL3BL[?H1e^hGǀi1X0q uy(~8s*%PoOx_W!dlz<2DQu㢶.h^5KnCMr%MH_.{ν-rӔsҊJYxfSoz߂^HxŹ3Hs浃&׹'ˇ޶Jk"Xu?wm_ܷNY_:w|-ʔ k`@E@qsK%~,-}|dֶ:7_:Ogi#Mw~Vɛ^(7\<7/{ƋɁƎ#tO\'3'fbdA;/1(Oٝ=:ތX7^Ixz4Ensd  ĞCdfoj1ꝛ}-Ty&˝oGiCo?όmОH>}# OdɜqkRķ/Mcwku3Ɓ 9NVZ9dsw?Զ6"ʜiYv[\!+^ԽAl{MJ@ (%PJ@ (%7 &@l@IDAT'C,O-?ww1uYl~_9(OT+~cAbKr磯?gY>WWqo ;_ܺG[_yA7u{߸/Tg@<-;7{nYTwkO}~BH pض˅EΛ1upg.oXf)7jMح}bv^oz1(jZ (%PJ@ (%?"} "I/B?h) eP>:bWNCU4 a)t|R B[xw5cPcv{-o[ɁDϼd^l>]Þ{LjyUa[$t}(~8]y)%04V{޸ ꮉbޗ>.j,܂=DxN.3 Zڷ~ns@o`I.0EmP|nꏞ6}YoYD"-G`aNCXcDkuUoPPf>,7AX1|~Cr[7Y+γ7{Y0痧o?}F]+́@˅`Iu AԝB&~`Nu5%\~%PJ@ (%PB&uύpԉ2ibL>1^(ȏ]>wѨ?n`]66lvZ=  aG9<@)Mn\ y)*Tߙni1qYM/i>}$t&&qw*n~8Tiftwm97fj,Nd$hIMі.^{\KUہ7^P&r~a9 ׺ [NC'ʱnd.nümǮ'6T_z,/Ww%se1CmĹнM˾HKGL>Gs|\~z=9eqZj8ܞAXd ;}B\׽[4CmUW>X/w9UkC} i)C/| ߉C~WDҌ\߸Tzs-Wr,up[Lr@,&QJ@ (%PJ@ (%0@Nw$:,7^5S.ZR'QL;weL(wݍ&rDnSWwj`]6۰:k)F9ab'vÜ=f]boƈ*x  KΓʷ**w/=rSeǾF3XU5I5eB`̜4mr[ g>p4n1eJx3a@x9 ::IHA]r|svw<;*O*JgwB氦2"sJ]GJmcǁFϟ?fMQp:P"1'݉BRCo1O}/Bpb"%o;OV(ΜJM,0Qj'7dMk{ɗʹKsN`O$2;ˆ}*e_lE,g:-;X=,櫖 Vc[vnj˽|bO%9)I0Ŝ~oEg}lϭە#N[- gMIsl7$&XdwAL\whW^?vg7\qCs/=߻XخNXn~["L; f??kZ3x^νPlO (ӏXXѹš݇[֢/TqBZ'zOw5Ywh?,LƸ,6%a[W!YGMl!EBQ;%\n vBhxKMuҭ;9v?OoӂظKfwVs 6_k^ךPJ@ (%PJ@ ,/uLR?&X7<-& F(ƒ~{ za}≰D= ˲e_7m`_kOuSxϙ049wupM:-[o1(s'=5Y,XhW9fޜC=pUmڑG 񱴐[7 ALL~WTt55̳ gnXx1.t8:is_po/.u9\@}ɼ֝5?7w&>9f5cg^qx[(͛Xhwv_ ]w< /W-0!eb6ZN0bv3AT!=צ|K`'P@xb[ƮW/ȿ|*5~  ~&%PJ@ (%P;[™ OV^f>@m9QzC'ߙ^쳳! w㹝k)^ىRt/tjzpdzEhJVhv_ne|CUMMxcW" k"+]U< +ۮ_&6{w${pCvn(~R[V"SNw9,0#nf0yŝsd(sܶ cxQ3Ius8V]ϝ:F7onW4_N]+/o %?%t:i\ ba-3g5kɑNՍ1 PW!,-̟ Wi")-WcxrVccۏE־O_ Ẹlޔl?Bҳ|͸VL?ۯ[Y%e.>ޖ=}L=}}d˔>^:]?.Խ~&ԿǴ{2)Z!-3$slL&|<{b2{8؛&[p>`a̶nt &2}c*-%0Udw={/'_//Օa1l"6\aE?U,{Vx5`1M|~yqCefE6H,bm{d]Ɩ. ہ+V͔;ޔ0w,15-?߰O>G؇gwz1Tx&w5͚"d9Dyum]j1u96Gbr_]uDh-_f_뤭#&;}\vlsK-c,euONטCkgqJ7DM! "Blp1K?xAҒt#fM8гkS/o: om]eaYtG[IwL'm&om7~LַyAY`\blY4".7Ӳβ/v/29n"o=(Wo#oυ.eƣ<3.+Nͩ 8.įƳy(~{%gP3"m7]?f{vWb'@(U‚˗ÍE!M4s'˟[/4kM-83x/D_azdY /S-NSr^?^̽t^`}C&xɹO/,;x_#v{7M7ەBLGSO%$|~{/D9߼(ao0~p\w\)GLO9pC~fgbe 䈢<wrLD<#x쎇6f2~crj=/1?`r TqB3/J1"kY2Ĉ0|D{ 3hX ˚W~Uw~/_1ݸ&˖ϐ.wv1z7+}ǿLN6:|YB?:PJ@ (%PJ@ (975/^Bc"Μx1͜ EZT#ad~:L KQu;-{֎*S7U=Z?xv˽sOn0;)~2#mЭ$.z'wr㕍+_ ϯ)0VLWQ4#+W C(ǂb8_o[ݺE9i\Jh5lM<0&ho9`vGRΞI$Fͮ{ /a ݉>gTcbZK]IKa{KO6/P^vyo{_޺p8G~w#&9[n+%pf8g9 쀥ݧЈhnܞZ{dlMj.P_Sv}\0 5xF0˰o^K?˿og?.xo~!%_v3\qqs¥P3 r?`诇pKַ\ Go KYOG_iSB|sr~TXSo\&_cH?{=PJ@ (%PJ@ (Ĉiq~ؙ|uny8'F6,?8}g}U% 'Wϯi% id@[_/L,3p+zY(Gi M465qPotMF0}f9҂?d}=,\+Ys3v.<,L e<ս/h]ȅb"Bgϛ,tKͦ{ڔ;ȺH] KPs߽LFy.Bc2ҝ: A(,fOiIwt+_=ran@ݷKaϩgl}|˳W7_>btzY3l r07i+FMY=ӜypJQV'כms?^\kc7B#^[x%c\'ݱ)bTػV% gs!_ KGڃhp.tm \Ju2&G ev\}zS;OJ0SDGe#,)[`M {aŸNo~o^*"N=Xߋ|v~: u>֋sdwrAFcLn\tg'GH57%ou<|բOg=[KN>d$z޴uZOwoY{mor=M~yzwmB=9癓%C[F[?Ce59&c](⟤=KW%,2X1a`l֟xDž2֯L3{` ? ~޼v?WWgXu]K0G8w&ӦfդPJ@ (%PJ!#'u r7V)7ߏ0xN9 1u95TV:F{_RՀ̌Y~}'JEt- lE,L Mpiœ!Mm\g]+2Mp2} v< CX[q 8%{{S螗m?xoZjmw'?CCթǕ8 ޴gɏrU3¥y1ݩښ2\+r;҅r|ex>3\bٝx"\c ?я7]|4yWSԲyڽj+M|r%-ϛ< :ܖBX2g"?7chRJ@ (%PJ@ (%p2 \8'u5BќpO$_n\~2ODǟü5!qycM]qD2 mP '9?59"Ӌɪ۾ޕCLx28y3dĄ ќd*xNַ_BE"nl9Θ]јxiw˲[Z.(=Om4nD*ށ":>w lی^Ϸ(`"(:X`\ѮJFrX-]}͙kǚ*D,F"4d'b{9,3ZX2}LHίLwFp=ysfJBDeϪIfwngA8(q|lJ;-GK}%\dlsgo`X9g_|rє{]6l/ኅxqm>DwK<8TL"6VN?)Ȏ =+d҄jDQ;?~#kyq_X 1f|6q&w6_h6/?>+ /En ^㵘59/N{B xn TW]Vey.g/~R$K6Qu_J@ (%PJ@ (%'5Xs~;erճ y;&Owc D2Sge޶EoeKQd e`јkoy޿RhÝئ;uu$}t MWJb K)vܧҍXt}Pv֝0oj9 O;'w 7rsJ>{u9֟f>\sTjcn˴1Jye9b&pmD\Ok 1+;!)k8WuNUa"Vb&͚(V ,!6%s?7ncf13|c߁}y,3Ku/;qak;T}<s^H*< 5aLYEi놷64cy[PJ@ (%PJ@ 6PBBHOG^OF1#!kͅ>~uvij.xtqal#TV._&<|O{9R.qB;ޟ@Kv+02o~]m[V7up)ibXUBEn4dH%XY(ڔ?ϼ=tiR(yuz Xb@HGXdZtbpoC4_GoCp7}v}(X(Wp0aZ{!?b|]HWPJ@ 잪VJ@ (%PJ@ (%p2#ƃ%H0(p=*Ip) uEN񣭐 ItDQkUĎVi8ǎ5e@}3 qyFv@%p%NŔ1vW06]y^V5*:*7FZQ`Ʋ.lkeZw½g\J6DLLT)$14%հ.2Pڲ)z>ke'SH9d2n0b2szz\Lbo<` 36T\iNjUSP^q;]/Y趉.^s_Pd9SA[ycrerellPZ8؏#-;lk:}sLl5ru=}Zj̀Vd)[b,˜Gsq#Ww9qmmb8},.%ZZpUhXB>%@݉|5:ϕu3kRJ@ (%PJ@ (%F@Otò,"Sˍ缾xtx-Z,\ =`cTY5uϤk7BG`]A|P`;XO~*|oN[w֟÷^*h956w"_>)xreE2x9e_wxSz˕ܥnɯNvJ|Ek\@KW6VFl FpP^e]%ôg0Uw:Q(a0Ri=J@ ( U~y&$KscD;Z]nGҀmԕm LɉFb"mi8__{x% '6ew>R(22p=/<հ&øl)n)f^$bG,ß`PJ@ (%PJ@ (&PUxFt$dt8* \&Cj]ʐt^@ڢ ՊГQ0EK9_PJ@ (%PJ@ (%PJ@ 8)BȌvȆV_$|ƻ>Wr nwGÕZ/QS'(y9n' eN2߸gDHYnK m_ξ.`E+B}}vi@!\}|X$.4u LKۇ8=!]cLDJ'T#_.sf+xSb],pK\iќzfos ,tͲ~?G>9|mMU0 Y-(ikeǩ>a/pc{ufeX~xm{…y+utwonM@8_\{O}1ran+%rO^fZq,~&s Cw@,\7wrN<˔{|KcʸW]K.ڗm}+äK+%PJ@ (%PJ@ (@2Ra lfGau4&dۡaYX'fLsw,&FE2.$MQlOBwGjK;K$20\]pʸf QCU@#kc[Qyʺbwni aY1FH{G:iac -19ˠ§Ri1 4G?4*`ډc6뗝T2C&˓_o1ߏ ,M2@0;ւ0~ʐO"u8*/mmݍ1y3^rxJvlMVDd|C;\ZOMe$g8ډ@xLlΊCV9p_;̩q0_͘+q+ˌxZ(y3.T!w tG:۷zGCS"0E2 K,7&x<غwiXprJ@ (%PJ@ (%PJ@ (%3(sGz`M'ǀc1TЩy/z;9wLݱ;D6DYWJ@ (%PJ@ (%PJ@ (%P,7 ?A+6t p ieIV͓)j]%OʆG>y: FtI= P~z%NmI (%PJ@ (%PJ@ })0HE%PJ@  I (%PJ@ (%PJ@ o8RN7pٺə US|^pDG7K傅cZWJ@ (%NfvܽQ~]'tuuI2ٳg_.͓L&#{=GL"NM^[Ob|dʰBeg9%Yհ_>u~hOj⩽ ypR-i|tx60ѼK&2ϋ ]4>cNG(ۍ) 2OTCx~ J +W|]Gu,Mu2yL$CSQBS>^LV=vmG "نȮYPJ@ (%PJ@ R~=y%PJ@ b;Y"M*#nRXjENS)ZQ)z^ar츷">풷$箤G`p #mϤ e0cNȕP(%FBghz M{)v ppGՃ-訯mb-y P[G'e$wTx66JK}렷,?䰲=id0Fk?J/p5)%PJ@ (%PPQ8NK (%PJ ww 66i-,`h4+贴H{{|6XʅiǻYlZ}i^ivRҜNOR a)aؙi=V 3c@aVt%APTKiEG/XṖaFnfΡLj Hu)RA%h*q5<)ISd{jҏ PL$̽JaIӲ)Dh,ЗKmD ~{O{Ѿa@IDATE5Ƒw~縺;s]X1^6yӯJ@ (%PJ@ (%H3=9zJ@ (%Ls' 1(BqCNj=zTZ[[!xrX1@snqEXO~>.7r$Zf"c{(t(z `I!ɊV O>z]ҵng:#, Ԥ+ *ȨI4&;@ cH|kgbOȠc*+H.k.)F;!YBB$~,qzzFt[K/eyx8eK\.7u$ĽQa0X&%PJ@ (%Pg:=ӯ=%PJ@ lq(t>.sg]]D)Y!N&e*]xcZ!HK!fUM #˃'-^QT XaZ8'"?>8^3q?P?-O! z5|Xar^(FPgC?aX1?.kаmB'9˺9*BuY}TV `]4aEϹaMvʑZ4 KS#BNvs@[c#L1k%PJ@ (%P#nz:PJ@ (%FiϨq6@uk-x>cn Y8N׹triQ{Q3~ Z]aV )__YbwK<keM n/%.Yh+K}A]$5:y՗~5ylͣzR1㆖Fn$odC~+|!*p0,]yb4Za%Za_P0v> ,EQtc9kl:#Ϊ1~qQ_ZbrkwJu%Q4%jڅ>);GǢc=ӯC (%PJ@ (%B`X|g/=M%PJ@ (^ +NrόIʴ(r?xlVaoe1 4\2q-b̡OS""TTT2OKj)S_D$N&O/EfOc2QzՃ+KQR 6# $BX vJKbeUjR5dsd)rtbd/czȄ= %f0_i7;3m۷U|rq^#HI.)rڔ״C>KP8cr'~O]ۥ0ږhCd_ VƹwIT7zrv m$hDHDTJ21}ҵPJ@ (%PJ@ (3@Ѣ}+`.S%q'M}tPJ@ (%0$>Q9D@8<΅qvͲ,g˔^r#&(WwPd=gJS(1%^RP{3G3&?![1#ii;XR6Ei q b8Ud)>>`xMB8]E ,R]e ]+{QdRušZV-<t{Q钶gܜ_{MZڎJ$HS+ȘXH-{UG9H* ~쾝JaL|>yr@huoF9V6=AB4Uwur 1CMˉh[~8W(LCU*:=qmXWJ@ (%PJ@ (%EemoNhd'טY*&q"LnL8_)yPJ@ (%F`ڊ}i=mGDy}6㶟;LI AEմD{P骖 !_5&Pz{ 02XȆCLug*IC 2ѓK+W/\BW?3ؖNʸ^h ugq B#^Ƽ,mRm2QQQ̃K^h!-"=:-sS?AHH NwB푮dg6O']mRDL 2l&⏎VxN<7zAFb\Wm6y[k(~: pOrKa :ԏxW4FIs|AW =.G g0~GpvKFB VHfTqzƖlHbhKPJ@ (%PJ@ (%p,Ehy?ma}CM"LXAkx?.?{w>0*;L'~n3TGPJ@ (%@>[8)Mjgg455A$!|z`iؕh&$d<+&XڒBrxB bE>/&eדbd2A #ܺ Oy,C~X {4/fb2(ײ0B>?4I,Q(ĻpHBLRC~pvU*|؈r>FEΎ$HpJ`)it:׎dœ>~X?Z|~7.c!Iq.d'XmT4K VV+{% BuH)$f]]ϴ’4qJn^1K&D&Ke " <ޟ U 6<'<Ǿw[{ V'7Ȳܥ Q#f}SֶgWuz==Ŋj{Έk!Df|S5W2c HCJ)_rd&/eɼ48|xf+ZPJ@ (%PJ@ ( (r9)mSwNٻoٞ2yTVVmNrNPeL Kb s~ 6rH8Mbx;ę Rֱ\y٧1qTQ k;la=$N^gPJ@ (%13#J>% inn6}|F{[ZZm<RC:?elj(\¥jYV)Gh=h'o5B#Dx4ND]O* /۳f$"^Xz JĻt<&nBA7 J,)Zj0;< j),u':5niIR"8ut:$ v j(JTדNHb!o-6ꈖ"n LMaDQgLeJ&2r$zH*JB#HH"0!ȧLP_b6,b#ig1CaE E#~w rgٲ!$3w3oo}r3-))FikkSDrϵI'uh@JJ`I. /gQC:zƥytE[R^. SV"֤8=L|K%sbXmPȃ]wq ^IzIȃ I"+-o8^2/dZI!'>C\$).DƄ#3MlA@}g:Uk T%)!Ļ؊|`rw7H0,fʷM/K;E+#dTn20 s ԛ0-R:7/d)mژV$EuoMX 2Qbڼ` 18肆7+\si\|8HkrpdrlnYv-bˀ6Z^h4F@#` @Ɯ̤(GH~UW ӝiMz(g:;Iԁx%僤.eCp-D 5&wh5is{(Zw~)WJQE$]/,R.i [È7* hr.u3o,(KD;AP0ߝo#W\>tD=QuGR}CH18$;S`{q܃5rsxÕ ?< d*EH.d] h&m8'A~0< #f8 gRo uj> ƲZ\CF7fmm2-Kn0U+nIqܒ---R_F)ovURX bJao.X]D#h4Fߎ@Ɔy0P"󖬓5k UVhS+籩39WUʨN ԃnMk 6-ˤG<%9A,Ơlp5 j;Q|uԈ{ ?1V{EŢ rC9M, ,M=θ@ON3.w_~Y;ZQށ8'i B47IX H9F`BkA\#(L9l T\DP,Ia !\K$]㌩~*&ch{sY,J3sHuBoH^k!: -&!W'뭫8U,Bq򖦰H&U 11hrUnڱL<@Ezꩲ;KUbOw]x K3th4F@#}Pn84Eơ2ʏSrDBKXݒkT)վ,Ke 2S )PKo HR~2nؚºy5(9҅sov AY@%Oȣ|k=P[2 װ!i'ܔ}Z~cTaǕHLPK@at cPqLq)> \F#tm TaF+1* X:V5`T& ̃a+b9?=IXZA6T>bm&zC#m0P,k](ۗȺp6,KRacq#&Ɀ}㸁q?_Dܶ<)+_;]A\IpKbKhkT5.|(+.|3{Ji%|rc^#V/p+%9q  K5RcxcwUs/lHWj:\=H=$yJx)Q#֭~%<*\8= mMo/~奉-)%eKlC_y72w< tFz_<u)2"BS.8  th4F@# !EMbS]dX{j8B,²1ȹ~SMuC2%҈M v%Ftx+&vvrUAR؀ɉ {YrVPc8ηFU{鐶(6CW^uV9ՠA䫯cjL2E>0aPW_ىЅ\=iqB&"ɸ[K֭C\*_Xޤәvd]mmmʝ w_DNXM\}cRe>h4Fl9!=iitJDN ;c H% =xǺ @Tzt䏐a)Zg[BHBJGbS) R{z)" WQ("IRS)Ȧ n}m$ A8E 68X>x6QOo3Q 2ʼn8.@tkC8R Vvʉvvr-Z4FRBU rAWQ rb\k5Q&20VRq*p&YDW! X{4zIyX: vBhm#k;H{_Mqq4eRFWN+2x)=ҁM,U7 cl2 FMp` J5्Jܱ 1A6KgE:-iBlHqSD*]ewk)#)d\ ,?5(4|Ms,@,fy6X&JMWqɍAurd'xl~3arsBKN:cps9mj^ SoWlCN׳]!˾0.0m^q iYm?tb2=Sm:YX/zKls+V[rasAkz&oTN0vbsq4CԔv9Focޗ+>9X~;1'*x\&3\DN)?ݔ>7~tٿ{oHe9&fz}F@#h4[,]qaؓI'tsE:IɁ6;␶v[ds V,虔28"`wxtL|#iJZ$8*5Y]G~'8,hٷIؚ[D'J& E3fk.je]'|R&o=YH0կ~q$4~PZxH$\~{0`;2i$߿/~ YvQ7cĢ@;ʴkjvqǩ${{$t{*=e[?f4F@#AqHj${g NZRdФPl+%[~!bq`G@$]nu@($j~-8KviYJ!$UmKR b9* Sd( QfJ/M*nd"]R ,]_򏺽gs TUTnK TJEy׾/O^ qE+.b)&pJ?рmosŽ4T ʂuen$ĥ:X9eȑʲ "lrG1`P;$u @pab+>Z %9րt$IS[i ϑޑYkYFxd: Abu$V8#Եuqa^"6,H|$:H|m?QO 7kõ2)V$e撤̮J` sz+'nd뷳lzf:diedmFȅg,kWz"X 8T>xf-yozwԽFmGWnۏVipǻoڣNE_jc+S Tw2F&'=8+Swy3O#*G~mo+.Ǝ$t;vV뵑Ĩ71Hm ?*_}?Q~}?v9c׹wYRͫw@vc|Ήi f_ǡ J=s@ρ9&f] BDL#l)l'7؜9P&γ"coJ#HGnX"3ե: ،-ΗP^j,G .M4VH7Wlܚ4M6ƈ;- [mcwJ,Vک 0APg?2HNnv=o渺{ѡGI$SI \VZ2.&M$gV$>we?>}$Z__/l6l" }Qye޼yRVMCS~g>=ӡ#*An,NVL6m": uh4FML)YGXn qS"IzA=JOzp ֕b)-s"M&dMKPAEa^;TUAY 2){3UHQ;ITXZoOʎ 2D`qZ6!E@1ʴ@LQl6kXu 6&]:VF8,XcD$)uާ(Rv֘{$b^kZ/T;B@EgsKeRp/8DF: Ew-Q4*iF,R/kyR?fz.{'g{W'/Yƍ.L~]{5aLWV flķAroĭ4Alh{2e=jgoJF&T3t$ h~؃werOZZ3O[~D:Ly5eV֔JDdF/ A찟'#~&U"Α!W@G5qc5Nu.)-U\Lզi!WA/vunBuQlxPL415s)֎se'ɂw.]},Y8:K~we٧˲Gd7_HӲe{WkZ񈵔B;OJ'eǤSu.K cXIqA,4/ UvũJdxPF;2׸KKQL~T3v:hhQxʮCdb- Mab$CC/α[WB^Xs _zw/|՘o|+>~~s*NiRX2s2 >/c)scm7λ|;0ڢk4F@#`l$l|tmPpg.ee]S H@hcQȍxp+Vhi dya퉍NX \PS" V;Q췤b~,pbqZHDB;찃Lu<LQ.8UĐ- %pPŤ,m@r$6̅VH-VYW렸i {8A&O, 7H1dV|:F3tט',OkRXmf'*\i|~ix$1HJѧqbW'a!ZXDgm!%䃥!YP'N))B&,('BJ!XQ)j [:ce@Zţ,{Hnf孇j1%:EOnt08bD|щիA!} >yw?؞ &$i9tinnnKQg~bYe+Pभ%C8VTIEY_.5C"uij~1 77))@IDAT"Ogs/Rgާ^ekC]YUUU}=+춝A½R/=9HEKv^<59|pe{}euk};]R:s59Z嗖Eww* Oտ]ūd=㽏<%5M;Fe&b565 #z]ֲ|7{c]@q̉lP">09TD5y۩rMw5 wS:VX(WU]yí2pАL _a?x[7u>OΥ՗Ƒ%.MRxX5+7w:O#h4F`D C6,HlEx!izY+f)I\\XB$PD&@cpTq:#mRZ4`=GYᑨVm [N-3ArRr;d$\)HR$ ( ͕fXQ^B '|#FⲷT?Z>) SwY8g2~0znj}6vt+B-ѵ~jJ$| {-Zd>=S-4Xg'J#G̕\҂[9 $Gtq:uÇw_t(661φ+ߧ~ZxfW½F@#"@:sHK5G{8j!K{+ET6XR~CyS(,lj\IOUV*1œfO%!jDGȋ؋FHHeg̷JC8%Q hl H- D]69ʅ(;ӉzPBCZ($@~1>&1Ie1BP+ȱA`&M#R+^'S d/kv|#*U!ܶx'WREP>'W_6 9IDPxNQ΀ɸ*iihK\OsꭟIt` D:b^纠TIc&o^Udԡ.pm\/0/9ǃɪڞ{6ekQ^5Ca٤׷'e=7G-lECĤBXgA~ *l;.bp8\+y_#ג׸NHT' AKY7bQSoxlDWz>crU%tc񧜑y:`$:=WۯUz[ dl+ƺ.Iw<|XeF6u>77uvΘgpj+˜52Ljlmi+/>[=~*CIg(ﯡV%W(K(by(+bJ/H 3CڟNoDqkύeYJ tRP5Dj~vn&.z&tI駟OzguD-ю866 n$^7%8n))r&& 01]è*l/*թ_t.B$X_-R_% k:v 7/_eQ¶7<6%1;#'>T2ivFZ֜6^ާ(pBc}exVoOa;=qH' rR1Ӿz3 w!*+`޷0K 7dlnFHpz+W JEфC\I uX X26bnv>ܸ@8`uj;@}.k%f-jE Ò[-vXQF&k)Dhu=$[>B ע1㥥"@F`imQlrTXtR 1n"pː .zHo]=|Rmc7͵1= X >%_NJK _,KRo"^TXdh)-M'c7Q^URݱZlusi;uĊX&́PT\Jėt FaM 7hw6||x[v b0,'Or%~+ƳF&/kV-W3 Rŋ` XTY?W'p˴d,Znu+C:^xAL/?uu…ʝnc5LyqIYLȲ utA+b)RObC6Sfvq;+R􅧟Y3njxmkP(@\)d,cݕόx"a~ҥ,Ӆ^(fϞ!Ds-$y6mZg^yuaa:S&8Ot:~)Œner|UtSHڟlt~&*4F@#I2%] v\)Xg@\Cezn{vJTlbtZ^{u! %!k>&~ȻѨDh#MLnȦgߚxpt>X@ 1 Xђ<-kM̘Na(j#q(0"&(7厺]Rn8@B‘t<blw2mho{bɇLIļ+Od|CF3M MxіBqxLظf]tqK@[حXv*pb.@6tc6<)SdLU $1 wA.0homnؚnt6/Ț^b㽾FIuCVy<B 6Ƭǀw<6v>nt/:+t9+H JQ;At,$Y 9 rӵi/=q,GH?JƖֆM!ƎvV1ݷ=i9y)=,mxMQY&ۉޛ:cy;ɿ>%@,NYTr^˘՟M!C$>VH_~qf\Q;}W4x?R}IJ %Yj=p͍wloRwl+fs2b2J͈D˯UUk^YuP<%_&Χt{z'6n BmzWdQF{pPyyenr_}i.Sn}5#n8cޯ:ԁ}fśl"5Kwy6ި5F@#hTr"IMR7\Ki`#ZhZ? EQEy4֍@Րu)G}Ʌ>7=|T!$.VG@#}#]R~S Snh˳~G><Vuh@6o0&-DǏQd?\8 ;vU7ߔ+2 IC*st8dz뭷䬳R$#IΝ+lqo6bOV {N.u.1jz<G}b\r|kkr~\s5N=>f6J?h4 "c"I@-[Ji wH` Gi?-VbIdNĜO'vŝe'*,C҂NXT i blWSy# Ƞ,L҉l|gxfLQl J 6]Wza 80u-hÅfKK⻵;]\b$F|`ʁ Py1! y<b*a\3Eb; þIhMjN8Rq2u@BFOc[? S!Bmb=+|G;y0Q޸V7~s{jL`vK= E ^aRFj"$_JɎ8F#hdOu0ŭ4I_+6$[]h)Z,Mb<cb8TŠp7ZqWm5Gߟr[ |9\tڋ2of0UW?V^~ *>PͣysO9Z=Co S.Lvg;eN{db>0;as=2]L{!9wO;G֫/vPJX:NK11os.;U|9T3LN>Wwn]\s9v-~n{gpebJ F1^@ZG]mbx72]{S}?OO7:ss 2 C<^y%+TS<5G+v׿#AӦΧXd*]iU7)}_٣$nRYU 26*)-WcorcL\=fhS~>%wt\ymR 8!82]6u2F@#h4-ܤ(MACh1KVڴn"76XHzqAijؤa26:EG"щ`HhCEAyІVt;[_k4F@#?@,DH<01DpInɾ8JR#BXY J~m .7 P Ih7y%$qB QT%1܀ nc?B OՎ*AU28akdeRf$+~%T:  B2*I~-Tjm/ OKax&e]S+{xz\ʪ$3" .r܋YTĠl3Ii 6B%D ʑc ݞy&n-ďl*G<ud#y;hPM7қnin+[{o&v@٧ Lw]}#ך1YZ3rGH3,;$;R3O8T^/; ;!QcN{pLS99Lw0'G 1thYd\}t #Plul|2WbƇovO!Y~(>kyϜ8}MMee~3O<,$yl(ѕ3]Jgrէ4F@#hrNσ*+J)t h]c7!XibeA .#%al$dTl4kuP%$,PLD!SD&w %n\Yaa|` 8Sa`Oi Om2e"*R$fvs=W&vҤI=nq8TJyrg!!Je޼yW_}U.LН/RH]/WF^1EPo)?1`dd͒aÆ7߬,Cu>НvکGXuIxW NF@##`gJ0t%h E::,A[4=R$)J%=\ʖ{,xY[,]"udHH&Ч EK^Kd%X$߽5rEn *R у"NKUPBLu#^YtXJHF@65dΓ ĕ`?inl\8gJ]SE@V-IʰZ &vHU@ZVH!x9HhXƊr`mF,`"y%)CqA@.OYZc];~Fnɿ7ܐE3N.:ΨovSjA|y8")Jn(~{qqӿo8qg.[03vE^|sQ7m]*tܳb4ngC=WgnQYeSq䟚nbN+?=SOH_~raGwYX eq'~| O3EN:\/>Q{yRn yW9J{pwqRu҅ʛԦ̧Ʌk}M7WtDs(csc|!~>_A|͗gX^ce=gn󦌃9}h4F@#e#= 7~ 2͓CS7L>r.D\! ӥ 婁 '+P8#,[M%Ϙ(.KDn5A !2F7H I[_T);$Pj9Xa=s*lu6o, g)Ćj]#)v,xθ@JG#HWчO6?ݘ U˸MMp><-**Y.vǍ'֭Sq>ن\kΊ633ms+mNKbEii\>h4 J56nMĂomSO۝.eiAxtIY| wf<{SnC #9®7T/ JqBCM . "J c& >T~?{@@ũk|%iF9,=]yWKnKKJƽ~ov}te5tc6wםgݘ~s.=%E˗ʼeIR3d;c}eĭ(צxkS$e֥ "q2}<-tǸ"Hhڠ^4{"O?$>7cїOPZGu|sCuF@#h4|.H`$;QGAKJ R.6aF=R#!cA=KQ=. *%+dq9i=jO ^Tn7@oBpI`F (Ёä/F2Qu4%á. %5Ҋ8J@!FJ)mՉEV,|Y:uqH;J߰`3% cJ @YZV"%[ėVN_q{ 6x?dNZ؄BLԆRz┋MÏnF@#h~hz-/ #Bcƥ9--T!Й7->Z%j2i+\TUxC#㘄]aQiZ vXN)[;KxF>`F'l`=XV͗ʕ-XR4@Xཥ-)n᪬c#A`A9 k% $1BMsM C 2@/8IqX^QKAI;HAOrup=[?9 !R"X4|gR~gpnOv1V'۩ͮr^6T$e 3bK$(J^_uXEʇk$K ѺuXn\WH%1nv;Fr߸uYgι]cMyh*Ӝ~vdCmn=?WO}_Jh+/Mĭ"4wrd5lrDG- S8x=D"PB,8mD KI{]4]#m" <*x>lC1,g#ԎQ׍HIkMn ePd l4G"J{|Wnx{]k IğqⰢ"aGźegJ@K_DT?m1!)0vl]a8hifK3}J ĵh .3mAjaw ӿtQ Ϯjx (stqxկT!qE\h;O`8q7d9h /3v\w=rg.cm}+9Vqs H7 "/Rwp nF@#h4Ful{|ۮ kgD@D@D'wx >n%lO+6!' U1{DZ3R[c¦zmG,,Q[?kV4dS[XXnIXey_{P¸}-dLTϧ 0< |Ċ> qļpzষ3ՄV« 5XT1B"vwbIǕ !Zs6:]c!Kk1C(3+- qb>?MڮgL:jg#mB+cu:)35ÖN$m0ha< zj ]Nw/\@F6 pykSEn-ⰺEG`lglR+mehQ6RVuP6d;瞺fSI3iˍ -BXW814 Htq2DrM&#eB-v*3cVtnj1EiyHNW$2GժZ lM:Qz` Ťe߯'u&#,_Ġ0E9S!P֍بo*fˑ<>ڃX[('YazBZ hB-A85 crU FgbuG~iN g<*1߅#b}56P+ג’D&V3M^0-?2 Ƽ: Ў~gIKQZb mz_ׯǵ!" " " " " " "?|p>6Eo{Q4XԏyKJ@ A~e]- 7Xrz, 'oC[U؅X#%v'*<yP[C^@M*ϊ-Z.ppugZmf! I%0XqLfZU3oɨuwwf@l8TUiz^EZY@XF]K#h*m E2qB;](,>%R$~4P*௛[#_Xy6|Z^!i``.r)4ҮyN Q4!e܎W}vlo!sP#rI%*Y+0sNϬլGy^@\a]30h3Y;VLB  +,4pEyXaf|Ʀ'mLf,\[#at$'}Xo5sN!8"0ȌBmf8bm]B팔p2$eLЕ-EpjhB(F'6 g1ѩO,z]e/ܖ!ɭV`Pk;9XV–p.E6=:b3cSV-[48ݝ;/mq EZ6QEhaɅ4\ _&ZV ºا\"V"%%5%wVi@>`8[(Gs-uB" " " " " " "phY eL}9tׯw,n*_!F=6^\54I>W'" " " "7{6CN>iScȡ:Az@I֓Cۧu)`5,VyU]o(J ܗS!" " " " " " "pjZEl0N^>eNbS |DWHM >ihQ" " " "pSID@D@D@D@D@D@D@D@KEmݺ՝^,8Ba\VMdk|_ORX wt t]RZEVAAԠ[6Gk,jUU 1R]}")30X?(ۆH~BVtYK.W5(3ogƷ@y+*SD@D@D@^w,a1޽{]ֆ +x-%ZAphz:cw[?㎆P&LjP!}y6+ci'2v[2reKaa Jq4DlXr-vDg?Pf@Z a|uuN~8X uwN!툀E`ր5Z3L`$Q8 2 gODԢ܎Djڗ[L|Be H<όX6o޼Q$.GLQ DkLj٢NU*uJlbB!*L/. kVض#ZAY\Y$[aryrg]n;,<Q'úzgBEhIKv؁$9ac89P$jxɺ{zBvdo/4q;cjw}e/;|ߚs߾/K^9vxMݤ8|웇,I]Qe;xt.oCV[֏xnM#g֛3$-+QxZT#^|pC@ ,'m~CEڲn+L=aaGS5u֢r,Xo %a:fbXVUZB-4Zefi†w-?Q> w].B-bԧlll̮Z۹sر#XE" " " " " " " " " " " " "pAhA;r@Aԭ833Y{vx]p9G֘ BW\$؝-tldi/^۷Y۲~ h&Mrz6^g=[ub n8L<=*IH"4!Vfc_ wgC8+9=u_LƆYdF?>>,D?N cǎٟɟCۿm ب/wtۿۿɕ|'" " " " " " " " " " " "pAXX;_k܉i;vmY^ QԪ!;ttɕ+jq^K.m: ܵFQ0K(MvZp:n2fz`߾..{ *r.3_\Vg:{n;׿DYLWZezDqFgaz;k\pVA1E:B"pni)>B||_"7K7@'l!dmrd]'Gŧl{\[Vs+m۶SO=e{Mos\;" " " " " " " " " " " " "p8Q; Μh14CY,A|sPZ339}?|1d7 .Fh*bXƹLYHl3Yn .\B,XB)Xm8 =K66|j"kPl b8$\> 5xp Q49>bkV5uC0)_}.šwy7ns;tnF'p\" " " " " " " " " " " " "pX(1E7,;29WL[gGbŪukB'O\t>Ehº"D(P5Abɋͥ!Y0RXN|e_(E?F](Qd2_>oHJvރ DaC+˚HDQV]g$ưVI,ۣ?njΎXYwEb/ũ )QgAbBe N MBM"l! !ިW=鳷QȞ8RA6z.xtϋS8SO#|_laN~ܴ-" " " " " " " "~KE? H鰞(Xg&W]}F䂒]q7\nlr*g\ TG: qjӝX\_j񢪈exkޥZV(Z0 Q,j¨8UVfx;w@^>(sY,|m{1yu:k' 㬟H0Vh:_v+<XX}v'J tnbfoz'Flf6g\&&'tՎԌ!hn4?e2Gg*R:es}9=[/kuUo6̽,EmÆ siGD@D@D@vsbey,}XfvwkCy" " " " " " " " R SC,mn8d"fax61 wg3v\Ѵ <2mc3+tGZ|Vu/ǛܗQĂ!" (dNVбokED3lJjEkU}umK덢ؘʯh~]o~m5\+L ޒkMLLءCltt6[fι mo!J׷GG6wvvʕ+ eb߷){7kА s;vL&mƍӵ.\ҏgpiIru""~Et-Fl6W"^$ BWr/0ʬ8A3aXV|t0D0Bi*V9 &/ZJEKnl0x`?lBXǮj[%/y 6y eڿ}_tL76md|+];fu.w8ʏ^8akpy=3vw(E/rSUQB?" " " " " " " "X(##֙h2l{ )$|ʐ0cQEcxQUZ! \1g۷l:: X0U03nx TCpvCuU؆K5q::bO'a= 6;eUl+/h _5{,$~YzAovu뚫h_D@D@D@:3–"#-C.wc=1^:EJ6-ڍ7Qco0μWnW'۠id>"*r,;>>Ba6ol eg]47x'm޽l%[f0K-f WD@D@D@D@D@D@D@ NkVgfTښ:U&tn+GJok<(;׷t(L@hK٬3 Yq<ٜ]Kk_hP֬h Ϳ*, 9M1( 6K1'5 {_haM0ŒDWЭ.zژ燢=3ϑYx+varпԛ[J{" " " "pv;]v9k__w%(2-[戢^aַL&czիI7 nr)5Qyێ;(1{YmΝNe;tKw %lʷPY勀"3;wvJm~۽ILX, 3wp°̗-փXQ>ŘDA4걾/][ !-@i5k)NM)H):uJ0JYƥ/{C2$UݙF7HZPv;tq=CN{q˱c>8?1MY/ϵ|5os$kLfo$[`>wّ#YP!D5;d[ߖ>l%;.^;RK/HǷ([1تWٓh ~E@D@D@D8(~z{[j_svm єB۝+WnݺF#-(L[ޤ-SQ8^](Q=p__Eioێ=j]w}611aMJ+W;6D.u;ixKn*^nc}~;#xN<2q׹N$|q٢e3\g_E00MsbKʙ2a^rRH.s8W:as::yaM6OoPfD[姇@u9[hSsM@iWx֛<~^j?rlu$޴O=Qb |wK깰nY+(:E[o\xioH= TC= E4n%(p+֬JX(|s۶m688hSSSƸO>biJwiDl{sVw{O>Ps16{I&rݿWUWtel.9L3yFÎe>Ryd}!|}Į5yIs8T 9kQ9s`zP)p4m2yex\f 'D6a彰׷yI,Zxy6yLܚ1nP.\X9:Tsxs{~R* OaAw)Qh nxq߻wqkY'HӶuVw.{˸LXCCC{tGʵkۃ3^lN+c0[:A.Fݙ(bb@j~6wYxGe+$ְWD@D@D@N @[q26?i+m}.oGGG?}@#kkFgޕ(2yMx N@o?B[d[6mr1FKߏ.*>1~y_/-)擢';* E?ߧ/i)w=|>nDxG=w}ssuNh?L/Ф!y<6fy?15OT3εCmDԖ4ۂqF4DmyBc' Y ehTbY6<_>{p?3l"]R$mR4 9<[xyV1s-Z ;QN+qXג|Tڟy~54o[xزe[(_r:8!@Ulj^\;xs)|r>vm~.  A4HC" " " "p"/Һ3x,| Fj}n{zzU~#_̸MKO_Ƌ,0G>?ȣ%oV?6EQ&_BtO<nDj{(XO84->_ 8`ksk8q򋷮'9ϓMVj Ղ[*x/P7sD>mVܖ-E'ɗ s.^"g :66fv2Zh^~zjCoo]s5N<ݷo}uV]Aǖ.}ƾbPA_ 8nDXtxgVu~N){yzqNj9ݣ c; pt񴭾qPېXڞyvs"w&3cSS ^z? ]M{յk:a'T2(ӦM~'' ^(t{K1ZʭyOa 02<6y%')~߷߼^湲tp{x: 4lk_k_>j)['[oԗ[Jr-vpq|>sΎ\ucc6QpU2.p¹ut GeQ_CIWcLt[8LL2.?*"#{INf /M!YHBMI?xI1_lRe3n" tݏ^ve @M޳un}Xr[|u06)ۿ[݃͛]eN(N*O-<)zQ Ǯ9U6-KY.zYXS;Z)CD@D@D@D@D@D@D@|mTn#wc6--V >֤(Nm֭X #w??.~Jx(;cЪx TTD@D@D@Dd~o+v9q-<~k[sm,7ъW&S6\Zj;6ٕ+rs˲rk9oR0m_݃E }~˴R+HC}")XbKF6.s$TqP^y"u*b'kr" " " O%<A1 LaeŦ9}56SEחwXyq}D@D@D@D@D@D@D@DLnv|SI˖sꮫ'~zf{,ϡ NeQ4xQs޽.kÆ <ܖmwAbR@/3~nF| 2^c>OlY~RLeS0Iۊ R"ciIhGGG#(ݓ=)۷nCP傅IWuv|<1uWB^`IL\'͊O% I[hM<5˶>1 GiZ7r2:=w\8}8}I+ҸM#$-B;iKB)X睋)|5J$3Yr&PyUri)T%+tɸt1@#.~NHU- [6{!K^ bb !(]b"nh&rƪ1Y; ֹ A0)cت+ G#âtq3J=_3=&/" " " " " " " " " " " "~B֟J:7 ,[WYL!&qE,.I]B%ZSfrU͞=lvB(R3Veb QWazYvY`OXǶ]kG?c׽*[10\6aYOm.eVz((4k&E;($Y!x&JJui+ʊv#p WVPF6MVهsm?k/CZ7L@>+N\ iPX(:Yl9kpz" QYzqkP$in9bV=Rd}❶vEl8qBcVX滵JS%Ζ-B0ڞokD@D@D@D@D@D@D@D@D@D@D@D@D=򕼍},4$jGƢg2Wk܉i;vmY^ QԠL::UC8L/%6|vm_`YK^&XwF;|&cIG] a]`žڄW&m")ǙΞڮmbb%촞`sxX6L'v"ʕ+紕H$ܑD"mTjNId2.xOܘcs{=X j~؆4F",ES`>g)bB[s,=SP{v{[J:y` A{o=k/Xbe0+Gp`~p0bNa܏n,r S__廘 <},?R>@2יL&'(Ο-NE]d6l_u;xJ?u¥/Kݯ|_Flը}#ߵ5kָ7olkxmmxWW7͉ss|'^K?HɟNey'>j-" " " " " " " " " " " "<(T 6V@L,BB/gp^G%d )E8,CY,Xu 73o#CvOTT M0-+hSR) m֖d)cnݺՉv1do[S&3m), L=JĦe-@0-jZrq~xct'Ps~rq ïrmIbͅyI|E*:~ AQ$ۯ8EriժUO;zh=JǺ՗ZD@D@D@D@D@D@D@D@D@D@D@D`y0xqhґ4bX3xj.OosގCxXg]׾5k[nWU>g?s?hAJ>Gfi5W*-oۿunPիW;vحKVID@D@D@D@D@D@D@D@D@D@D@D(pa))ADs>͖SzY_wnLR'Z ݻUd\HƝlkuc1,E1ã۾y-] o_bl_g^ QsS8i&k^&'M 3N\ѴTs UN=, cR ~48nk755?c{\ 3}bO *}'|"#EI//}CKK/}>gAe W\qŜy`!JV/-LS)Ձ444Ӧrp(*ՒMBt~Q0=_ҒD9J a""[\ ~^Jf3Qc cB$DR ̵sG\kj) sRDOCr`D}0q539QHlc//o.K^F BלmW^yˣK`SeX5˨իV;QYwn6m ЦR PbD>F~E3t4a匕D+eaxY-VNCv>](c\h/B$j3Z1honƺ{=Bz cfv1>{;lރͺ9:1mCZ{EZ)BYp&TL(Dl"REQR UTk-'`} |q@//d/wp?Oڮv9wǸٜw[{. C"6dPaXX^G)OΏ@IDATN #=N*ɛe` NLlβMLNCФh46jK +*)'ZḄ3XZa @]Z2p}9=4gȺu댢#0?g?kz}hWWƺ^eg\t hoM{я~m߾dgvhz;׹c*8QeBҨ=yBeKa):b!OhI\kj% L, OXp-QSvَ쀚GmxtMw]MJ?b&g@ބ cbKX/I'[XVna}_\1q' oy[{^?+w4)b~_MeM}v2w׼5vl6k_c;Lo{x+wc:?pȢp OlhE3+C걩<2+FA3aXxӍ-z" eƥ DX"h%PhX&Xm?d֦["uX\dQ;ʵqVO=o<-/_["Kw\↾oöF ` a f0@}n8!~snj(^}'_j}ty8'4 "P10@=2&w'/PvH Z;¸FKDQ'pF)J~~خ JtmLє?ʰӊ+N^:;]'?9']{n<mN~mΝsD]1 SuP; k?4S؁{pAg^_¸uztGzs!Eχ4:w3ֽ{Rlxmʑ}Cc!:ҭn$Fq!ggVKH4dYfsv /}%By[r_1FIP*vfj1\a 6fK&aյB}B8 B0%a8حj|8\Jw.zzzlڵvw8nn)l 4b>9_WbΓ6n8kB6yҖ-[_+ggev|'" " " " " " " " " " " "0?^d|5o{r{{ٷﲽٽV+ԮL_aHА=_POiIu]RtMq[v=j#Gmvzh=KCB~fpBΗP˖l.ۼb끠H/Rzom[5vEvM51ɺ#4tU(iIY,n.,h~0}pWKj$˶"uPˎy >-ۃ؟+ֵqӉ^PZ@k"K9*"VX9Wm`-w6=1bC+Z PrZf%ʼ%s.eޛlu|O8Ua9ZKkS5Dͫަ9$.GLE1Famyn۸iq֝ᓻe0DW&!za;tt.qeldbt, ֚*Rtil#Ab>onb6_eO>s{WV" " " " " " " " " " " " !@U4Tg+T)۟;:xarh"X(uW Q-TنҌV}RG ̚&k2ԴMff]cӍ؛AŦU)ɰ,:z8 T jЧ|'" " " " " " " " " " " "p:hԪ5Qg)0c ),0޽{]ֆ +x-NąVjh>Z ?nF;B0/(G\d_gAJ)gX ̨z\bR*eXR '-)["\j]|{-K0*cE9X"ms iGD@D@D@D@D@D@D@D@D@D@D@D@CiCtZU`=]S,I}>[`I3Aѧd"jQ?nG"c~-&>V!2h$vgFJf/ ٺ;,_TQ E+2Dl wں+_gU/$&| fŧMCm]Y$5yF׺<;" " " " " " " " " " " " N ^0z!)LB"Z df6k\=[\du2K3( vvq#T=h ׹af=dK!S \,=K\CP @ hj)瞵#`+@]a>8ԏF*Ek5cM'c)G [r&;fb XJ#" " " " " " " " " " " "phSn% S>!/I]B ֔\ղE'vÇJTꌕr*ŢCU^V]֬mYGb}pc8I(~;6Kv[WπcwX(&uΝI>30 ,( [Yk]]E]a%31 sd&~:Nwtd&*TsջIST52uHS$m",&o%9fYƉAy|~ &YRMG˲(fP?/+ַK.1m}snmؾ uRbeW^y\9Ī,F$@$@$@$@$@$@$@$@$@$@$@$P%FJ͖%VbloyZd95gH-41\'Z==jKxJj%6L&NڻdW㏕Yɴ*^0u|&{%E oy z%vkKTMG݄hUIV㐶JpbV+W$9Юi>tƪ4UUFjmZt=n,iG N<,Djjj?p~:EՊSO# H-Eww)KFLѴ*셈-;E2FPYקiUV`#!xILI0W4#{\)[6m% EYQMi_"F,ul>aC儚 _yHrc_3j7:iZ%/?=;$Ӓ9-e>J>ɡZPUhM M65a-:ՄP7+s5nw_.Kf/U@a {\9{t^BMg<k7^`$ÓZweP6k 历$EQ4{Cfg,#QZbaomX,fb&I&w~`)z{{%qݱҢ=*#!D$@$@$@$@$@$@$@$@$@$@$@$ d<*E*lryt)+J'ǽ-. DM'%$xT׷ĀEQ[2;g:kr,Qc it_X]kL㝪+]E]2uv-U', BU1_Wr]wɺudʰ݈*+S֯_/Guݔ#V!;w4m^s5UVIC*&6(oy[>Oճ>{X?`\2 NK?l+m5䗷p=: mhYZh5'2_z~G'`qiG祝h2??n|UNs[YsWk~D*y\YvmsNP9b ]%=B˲1E.]jDj(ÇP_'5:>鋥4h\ӄU@%9\ x:E]{4YٺuIYz2EnM'-{8#˖-3( iӦcHHHHHHHHHHHH`01s辿Ezj툅*j3 ݔQ+TϰyMg3.L@[Z} *2j,HѸ466#pUhXEѰM_e]<o4g*TFMVTS-NYDDjw1XpڼK;E?я掍("'+wCn9bg?ٜ( .ҚiŊrʇ?a7 15A$@$@$@$0Gϵy[Km]pOk]\Fpz,1\-ѮOieiIymڦv?ף<DK;w.]OZW>~rW7quV9-9_W}Q;oٰ]sx_UV9 x͠ yݬp>J Q;-}.n޼4x=`*%)u֗A!v+7'y`sR[C]tDTL˝<$ˊV֣Ƃu[Z-;wJ]|biR׼H=DP*IǍ^mujREbZNk?E3gkp aQ}kre׵#׹6ᚭYF*6cB̌D"prBPu?Dm3gδYIHHH*H<̍jly[kS JrFU4*Sj,7}9G2*{kZhlE+[LdT-y~rW7quV9-9_>qeyO o9n[ 8! /⭥]~G7jnH뗳x@D%:}>IxHkMɷcjQ*l c)ϖo˗ԫy\kxv4w3~\uGtpy\ꞟcy\=h|mǖDu֜j[uEEb |> <`w?YQ[/A/'zVaxm<3wdT?O'1{@Ne9aH$g$pdvE'/$^Xc].?TԣdzݶWzzŅx}* T?⚔Pu\'Wr-E3VMcm}F vE˴i\GEn*7xɹ;X  ;Vlc֬YГO>)>н3eV^-+V(Ԙw%8fY    "{` ( Y=υuH-Po̍Fp[u>7HxA=TnFcQc>YZkg;WcLcս_[+}]_>,o -LUGRKȆ ׇahIuI%R7`6~4@oHMS:}Wtk\Q޸Z|j%>1Xꅶ9Xkjꁠ3i6¥\kmmdXjvA3ftDS3|rW˗ecnj/ԹK Z|hPwtIrqs='g}u]򖷼}ꩧrYg%˗//ĸ=µP(d >0Oo;mA Mۋ i#Ԛ/D\OMx4\ 5{WKպӫmsJǢ⁥\8ZRŌ(4cof:cjâ}3wZ}{߈r---/w4P׿&`g1MrVm3XM]tY`z& @1#*poY1ba aZ+K bo*̸ `ZƜ7zQxtl-ʷXg8<455q wꪫK~?tM@y ÅR(-Y$}OZF"cs`"           ؿ`=jBGzWorhS ʂ9-astu@_ƘINKZ#Υ'zȨرԠ)Ee9dҤM# A4P$ڎ^II$ivwiSm_f?$CI:MiLSolݛ%nƅKi| C5=566 J&XbF|M   8SmXrٓ۶lm\FrQ˝/v}JkZ-,75yVbLʶhmXq{ٕrdLFlRd,?u9G2Ϟ1f6sם<bUNnLFmru,7yby?9 F;Q>{…xh, ,Q1E-` ^[[խmL@D{%.\!3f4#qc,R1ң?gԆh͔wiڬ6O=c|A6CZUZ.!rb\L$@$@$@$@$@$@$@$@$@$@$@$@$Pi t*DhР47bQkT zSj*M&H([Bk~>ˮ]$.&KcִeEO:6n ꈠV,2I/"hR9Lڎ?WKTMI_w̜1 QeyXNaa,Q1EGU*fnzmj[&YTEQ=5-*zUtEZq]9qE5TM%nѯN.]jCML3!*Q1Y֘.#-9b$2(@TATK1"" j %vXBHدu-3(:XHHHHHHHHHHHH-Ԯ*:yR[d,%?Fs%.$pB0֛OIh}c1qJk[(mqI=.rc=             CBbZ>[h KE*Y!1Ewa0͝;WM] mWXzD<(i0O$@$@$@$@$@$@$@$@$@$@$@$P KFk9,sBI 1#Y]RvHHHHHHHHHHHHH dfEg|a];X%[|VeMtg}            ا DEQc) Y43sÙJBijf~L-[KpBill4yw?m#h5d"餎Q.{q*NYy풽W"            @4"iX4=zhɢp)J+Owq> su 9 j9            姵q(, ,QN.]jj +޿ 20t.*ZVT-FHHHHHHHHHHHH=u~~dQmv+w<Zed&חDw$TuP>IWдQg./+fRY-ڐbdC2̒ Dd4 Kќ?Td-Z(ꆎ7o6-Z$ SȌȧ5ާm[M? /-\SgbQDQI%esptDiL4UKȗp#erțOYJ0/R/+ҺuwGZG$@$@$@$@$@$@$@$@$@$@$@$PI=#`5SYe-N=^\ TAѦp/~cvmˍ&>BE QxYR}EQ7o)I5Bg<~ٸ_<$#q2r]6F;33$@$@$@$@$@$@$@$@$@$@$@$@$0AFU?+Q5*tuzU(Z[[)ꇩ>Npv2ӫS^T\S?gf~z!xLuQ`# Xi.8{u[e޼yh"#A /;eΝkC 0?ш/ٳg󥫫K6mdg?+=,]T.\hӧYg%۷o6ڶmq 7^Jyt{P($y|_7">(_WŶs`agy9/.n2[nX|XryX#            I@z[ƣjJ\ֺ&へE &g"*hP[pͥ> "}>4EfXwjum3›M/[2%{cl'JI ?Oښb1ͦvv=jTP JM[$Y*F֫(V]{E E׹ȰQ(botĊ#*/V2 \G>}wʹk<پ+o}K\A8 \_7ڵkM8 8wikWsCCaN=?<m,bӟ }}ݹsyӛ$7x\psι!           ID b 4eiX"SփW#w*fj5gjψI50K@IDAT-;XTMYi:gj:+k~6x\#xWG~'X)>lXy.-d+>˓'OJJgcF+ԠxbjժHsBQC ֞=7a9m={Ȳeˌ5֭[ӦM3V*>lbDQSfpo?GG& 7 Tls A&1BN؆k`9sǹ!=+^qk9 /4LM׏RUY           t1DUtۗ*KB{I5juy~fye3O{{A*gNzz"4h,M=6Ԫg&[D4b),5=4Y:׫. '2w .j)hT|Xd@vHǖҷ]2}qF"/\_@׸6\n C=1.mfѮjvA@=餓rw-bذch ?,ZH p BD=so^M Yoň(V˭K9\afHHHHHHHHHHHH`f6F5Ɇ] WJO7olJ##D&xLVQ4z{KZ [5 p Dj1V% #~J[Zul锴)s!IήAi]/3-5T8LrPEN8=2$Ҏ<7DWnvcq)]6Jwo֖j)*DQgPi-HQ'*a/7lZ>$^g0?UW]er|4KUu'Npkǚ5kl^#'*8=gm}xkYBX)HaNZa9 kgyX㥞-5 LF&hv`u[NVsLeА Va'. +SiLf"hJ7̈/@$"XIkeR 5U54XTFQSSWtMb*FCZQCvsu]j'q,WBq3B&g>3+V83=iX.,8o.+up4}b~B4._FrqHHHHHHHHHHH& e3SS8hY(D&EwyZ %Kw{jӎsҵ&1c# xdڶm &#[           \rx3'׈;Kˊlڐt<ӑAg닦%:堺ؠ ٳ7&qKgP{Xv2kn 4 EG}E)xM'DԨZ‡JYI߾qi{*BOJ&5!巿mY`ANp*{FvժU,{9ABi׮]^PIf͚e>8n1O׭[7O\Sr Ey'Xp@^;G8k-cZ `m{?#Q;HHHHHHHHHHHH@' ʤg'>S+ѰW6m!>NvǏv/?kÖ+v8$@$@$@$@$@$@$@$@$@$@$@$0Y aѸ=SYh5/CM(( Ȇ.֣LXc7}~Qs93T%֟5̟ ,8T3Dќ>}:mY"H׎&]~ؽK8!`DQ;H>j1gx4hhպ3Q["^ܴCz{UmhGKô9R_SXk8cՙ9sXEqX.K\XkZ]CB,^Ν[ LNV2K4g)4N69_Q%ژu9:Q/V h9M؟[ԫB(ɐZfΘX#q@- ̔Z )CS1+I}>xT)fғjc &iƙLL50Z%ii5 ]#deըs㯓pR._/$"Pg΢m& еp2ɯⳂ"            4[Z[ԜےNЊ%nN.]jNS]f<&̨ IjkL`eɉ(kM{5j M%?趻D$@$@$@$@$@$@$@$@$@$@$@$@$/ +lLc/P2AFUw3ezecE   ^A0Dբoׅot1[k6l_]=lmӖk[֖3զ5 TE\QwvdR[%1=lbs…h2n1E3H:2 7/neZ|Vj1   8@ cnX`ى~=c$@$@$@$@$@$@$@$Pinᓖ%u9Mji=o`>I.\9h%{HҨkg#)_4Z._VD̞ͤ3> vrM$@$@$0y =#D,{JGG CD&9e&,p}k.iooH$R__/f͒ٳg؇:~kI<̙3E(ʸѨٳmX Y=mkw>m[>rw&`f/e[c|2_z~G'`qiG祝h2??n|UNs[YsWk~D*y\Yv9Z}5"娖ȝowںdKQ!͛ͮEICCpsB,L 5lݶ_;c̀oZ1hdT:"^% xZ^I7,!_JBDkȡo[TM@AQsD~eiԮ&E3}Mgvy!N&7oXfmc8KUfyi.Z3y.l^5< <0"qSݜ9F3ca hg%iRCcgFb1|>oS36}yQ6oXYhmmm.X?U7m̹xpLεmۧx%0L9KbUN!;gm]ay_5qYJy\2 <1+s"8+h#ET䀛;o.{ `?5sq.WRsj}ܔlr3.5PC')zA^s%dd`Py-.9־ʘJkYUu_vxm8(M5p64t#2:7޿[;JJS@J]o.q1%*^HtJke?JcV\? F}i Ek&$u-sA-Xh Vhngk˖-Q`=aޕxl۶ͼ<ꨣr G,/E1cU\%>jsExa;w\>}=Xu+b\ۂ JS\9 "aъLXw>SFzӱ&DUEB;FUG)Moz9ؠ?#k֬1p;gs̶g6,I{tӦM/;w4qC!ZԾ>s6{- ;z I'U)b(dc!)z?k~yw'`zo1j|SEqȟpQ Y$jȼw|qT{q]1=9w3m6d%* qY]xUx\.y\6*rBn2 kI<TF>~hdJ!pm5!%K =ᜅb?iyݗ%N5}?kH4#> ej,t`ilӗm}k<.YH_X̝$)L6]y|Al]oihj=.׳vOQWHڰTRz$p}l{:!o6hq:RLJcSe'}QjPJx?o~n/#?.h!XRa' "6شiF4|E?sرO?]~_ ^^V2 I'$=Pe@}-k׭uK.Do56Jnh/wկW\1J9gw̓ @)jq,9on,;;BmbJXBdU>mB~ժUX(k x{ _hWŇNbauyf#Šb+>TBݱJ /U JTo4CݶhRXƧ笷)A_~y!`)JzkI7OS?"IeS7^Uvˏ6<bQ^c<Ф9}Vǧ*g3L<.U9%9ˡ6:g6U~YٕRJ嗱|q?/ý>W sx=ߚE[zUM+d4)Lܼ?}+DQ0Ç~mC[eIH^P%6czqȼE-2s5](R MRqT4G7aIW}yWPI}mٵU9ceƧB+3,@,\ 7ˠQ/JoVqQ7!QmE_JpbV+W$9Ю?jDž7Lp ֦uAIosꖙΒR_oQ '~wKn&#!`%^Dy %rC>S͋[oոƃvZ?P'\!͛7/,CO8ᄜxLgb?,XNR}'Ok6Blk̷qwsvgHHJ!vAyfKOXo"|v*p/KO%}AnxBdVDgSummAr,^XbHlŇPK,܃:u)T pnŨvˎ}w2툢x1e/-w `A#.&)dzߏk} 4]yƜǿ~th; 4]EpSv^g{E9HxKQ~4y\Ky\]ucK:kpo8He9+35c2ܻds^Pt`yγԅL*3ϵ*-(f?X NFsR/T1=Z'Oq Ee㖝"z?#(|s^q5I@RS U0ȦmWʖMeE#DQ#LTEAN% 1D /0._b _yHrBoDQg=ٵJ^~{2ї;$Ӓ9-pl(DpB _͡OF5W^)vZN`[:<0N$nh<9A>!Scq{ Fkc{ .+0JPu*#<"`?K/T~w7Rm?O`u_ڼPxou]gtuR]y  (DL7Xd"?n,;!f+|>؇'h} u1|0̉~       e7ќn26]W~տgO,@BVUGv5reZ_-TUcJHXTV {~| Oj鯗AuP /"tK) 7_pT_`2%bzԊ K}SNy{4? (8Z^{mEc[[0E.ݱcG΂"pDW_}uI5 "!^?\-_.b~fB%fru[Wzx6;a jQ`DRXq2Qt<30ox|_4(^ݻ7' 5}Q!!jb'"}饗0e7Pk$hceG+cL!|"veˌ a,zWE+bt#z#&mZh2~M}.| i}}l QXE=:1^ss'>Y8'p CeIjz]# ݭ/IdPz:c|1 AD׾V:|xa^k_T}_o,*`SxQأΘ9/9[VHp˗/ƵED?ϛsp[R0Z¬;a}37|\veeulqw1ZCаx9gw{̓ @w2\sxঢ়vY`u"^x455An ŇKS!"Y" ; Y`) .t|As߁0pO-cj -:z ߆݆(I=E8}c  $)]+@E\a hxa`˗ KqWw%~Oi}/54sqyJy\*<jsE29+ñX+Tf/,1}*B(< 5tO9{͂Mgb(Ka0NKVL>8@Q s bo5x^b"7j1=ݫ *üOk$ 5^ NP TLK&41ڟSL_?jpzXV/鋆TW*:d~mjx|GnwFXH6vGaqXn‹=h"1/%XjXͅ (}VE|r^BCѦ ,!8;‹Sĭ/26V }<#MXڵKn喜 \Bxi<< Jm t5.bo+COO~5 >}M6sL7oƍ&Ƨ f̜aQd׶l}{ ntQn3BΌ3_wnsQu۲m5c1DM., ې8_S5qGuR)oչsW7qwxeg+YsWfpWX;y<Ҏsisם<(Y>qe8Պ}6dxrܼ/E^6|}݁}*$E懫ǫCnuY/z.]jN/uŜ͋dC}sNPH}ҧ*&[Q}Isdq&U^_dy4(YLђ1Rzul'L0یūgV|=GXӢLzgB"].̉f;/O Ӧ񜳭5 [<ćB +ܗ5.>n!Rª7%#}Kq m}D04qρ  !"2iQwl~VGHm{jb̎BpSvnmTLM^^Q!rÜL;7]M1; ÔxB;dlnA6n䗳e.qi-??]rz7fe1UqE0m(pE޿q -}zX9Ssb˞',H7f -oשx54 p5U=,|ҫ[ǼڴuZ}%]J֬_C3.L@[ZYuŪ=5xʨ F!L&=*4+t/ JmY S-NY#VPY4gK1 ?O$@$ppqHPx7l_%Kȱk,AoݺuƊ#̹GN:Xĭ*۶m3?pSVoX,m0#(#O>x\@O< Q߹sB]v؇y)2UP2m7;cL?fukR0 _yl>s5s8Vrb [2B9 Q>ʱ%Lq)/y\>Rj}1MKc2ϵv[ƭԙVSH(aeͻ+EeՎ9ªRjVQ4;wV>'5*5HgOD'KZ!@\VB8 uܦifua"g C4 Hf`BP*䃨YU5fVBkm;Yt5dcq s"UXb`%o族VdH,\  7 r HHe,pyMZ[[/O2.!0,MM˗%q݇]$@۽{La.̅dL@HIȫꤻuէ*>}~gwUg7Clڶ-umXn3z9OyU!_yvReOr\Ù>P-l 5J{י>.ͮafm{י>l$%fۍdl `93}<ܰ|v1Xn3nR}'?>y=xdk)yg[w8+Es(5_ S%trɇ?(u4=ꛛ(t>K.^({L,жPlܲ]-GfwGNijK~0K)3V,3Db-8+EU5JZbkFUU.7h3q23A6gYxYs"v]Ӵe>wqf_|Q_ivMv…m3 ԊS/"c o!Ea) N\ <YlYnI/B#/-!ia;عqv&wyZھ82Xqf} ,5j$W ԆFv0DL"Q6PjFf*%3_pBY9ceMn-#j5GQUK){֛8$j)V׼i+&RTE4D^Cshk$ӫ0A|WL1b}#dd!:L "^:l8d?o나mc%k!Se$@$@$P+y8~@TQ,[hKZh1 G OVŘ8ljmmN8^3苄?}/$@$@$@$@$@$@$@$P@NR7)>5eC-}L8w%IODFEj6&-}ˁ'-JWwDUЄ]׭cdlA'l)ַr([C"@b`0(W_s5rO_LdKъ<0OQ_nxz<K\?4AIR?DkknehQ>ϸSd7[A u`f*9'FĕG..l32bm.Of-&KoĈ/7Zk*skx@.RԩS㏗_~CX馛rEp{CW*wu`X1`[ɝ󪫮m\X"o-"K,>`_:f͚ܰGyu@IDAT{AwhD$@$@ `Yj Zl",5RdZ~n#m_؟$@$@$@$@$@$@$@$P  ۤjکvꩧʦMsu)bPjms,'          =e.+DZVj1+1KΎuk$.mHw-[B ƙ2,sރ3I$bKͭ2{Ti #Vfg\#~&Sҹ}U:!T(ա2(zm=hQVqiSұi}Z۬G}ִj*`Z^kLT*mjp?\^ZG-G>K|Æ zѲedN~$@$@$@$@$@$@$@$@$@$@$@"-HDȥ1CւC- I(jzRMEiI6j$ږ< M9R-BukkNNw[QXVTӊ*f|H',HB&8J|BG{eꔩe:M71GG2^X3Qt׮]Oz$ˑŋʕ+G$dug>to興1 _s!-8>U툖GͦT&8jJKS"A/qTk JTҶFuPgyɒ%us14[9놛 #=1VZ5щE,-ZdxyC8TszoLzU uM656[\iQ`\o)FYQiVv7(-ٗثˠ@i@BwL9$hf%|%           UrȻ|k cjoe9Er 7rt%:`q!ZM|!󋵿8 @~]|>4HHHHHHHHHHHHiqsƠOˋi^D8-THB bzر9sB!w6;ǞW'#           Z:Qgq!wke[z<\Qm F{{"?Z~O$@$@$@$@$@$@$@$@$@$@$@$0ObN, -[uI$oh#OGIHHHHHHHHHHH&k%*D]٘֗pZN8e1=nj̝;WZZZL٦c#x?NNw'^լٙtj \c0̓ s=4Ykщ&>޲EQ(6mu.gNgjkq]N+9=\"Mko+            J 킉(Z ]\#M4"QI-Zd|>s6iJ77n!XM71JbT;ߜ-HHHHHHHHHHHH&):S٢*ֵ0/ ?=b-#'өpIuKJ=&A1Un$=7< 6H45 3gΐkM6o̘1CO^2ُHHHHHHHHHHHFl?~yu?QN=Լ^@|}{kط~[&M4hH/՝{#ȴiG{ꫯʉ'(v<3Bx~ڼyJYoQo4b<7 /\ ϛ+_r-yk*+{G̙HHHHHHHHHHHEF32KnD+EΣJl<(3$t@-4e6ilOZo’4Z$qilnή>[r'ʔД*n0lvK"zP(s5nX$ٳ[kS43;֢!c9!25V$ U{ܔ*MjUwy\Mݪt@.~'o~X|)M(}H~OW^yE.]jG%8wh㏛:,cF~i#_^FsϚ5<az)/Xg>o[+ڷprynӟ Q'0bw!rw!2{: ʧ>)yL{CP3$@$@$@$@$@$@$@$@$@$@$@@Z]B7`׹ЧP ʐ#91=k1J7'½iNHfOlu9v{=n \nw_BzzU0Mm:ٺy9w QH4K35tT ۍǣ;S7*DRq ۤe*q龜}\85g-}au;C ^Că(DXZ&=mf]w`M{뭷g~_ڕ.\\Ҍ g?Ov\sXok0§zJ>Ȳ #DC0zj9餓L~rM7Ƀ>(W_}< W}nkD/~!*c=_宻2kP`ٸqcN;oگZ0+`O           տ ND1u{G锠iSzNI{ri앎րL yWbш~y Xf֝ސ;\OHOJhz4N>REiP#}4fiIm3H=cHM;œo|9qWŬZK LMHQ:) (spcS^ `X."V',D#&k "}.,=y|;1?яԢ.+K/"#b Bu~uC=d a jQ| _?=\Hڵk8 6(h3/? yh@r7"<weķKt'4h6v[B RwH3X (34TWjDO]"6;Cˇ~f7\ZSC K7T|uUQ4صWfwd܀̙#s,s=&|\i!."͟?ߜ/AZ:{TT +O+R |f11e kQKJN5K%|q ,!8w(X#ynW?yg!.px #h._XM Mby1 ԏO$%4ZIp s^ pQ(Z#a]IF1$ICMjl_<-@&SF5hLӸ4gz93vtzu#B|>Jٽ;Vu5*\ zRk_Zb(+`=\gHkوr{UHrucar G|=<,J%+|ɃiǺ.տrƋkY꜈l t/ \>KCj38c'          +t8ݩ:U$uaJl 0QS<MomoTϠ ZI_o:"{$IK{x|ތإS H$&--S+T$#j]: РTLriQz*w+`9Rz{pmcbc ^K%C(2JUn/U1u,$Vs}\P Za؊r{ĉ=e2< _W̭\uUBQOc @qiw@4 Q5"5)`Q:ҊDQXnٲ7o^Q ⏥:5?ZU ~s>X4 UL]cdQKeŹ+zѸK?p@:ԅ;wJ]pfEQ#a"UJm=TI%D!D!I:5g;+QbȱJT&M^pbXoC19!`ϙ;'g !(Χz\|MO?-\p1PpSu}.$@$@$@$@$@$@$@$@$@$@$@y@Ժٵe\ppl #SQ#w[-6) E,.!fP1injcZVod0CuGEul]3@-rƺ}lUur,$XfCX,&6m2Ks]ʕ+,\/ po(@&m3 X `'eU5T$v^InG"z#ihv)q@{\9cewXdn9Q+Ũ"/H{TUTBzjVnu[ܴE*(ȫ>7^6aӧO7o4W^yŔx≃kQcChL6l0i{{s=~ٻw Q78SbKYw:]#[FD}ֻsnwnc1dnL$@$@$@$@$@$@$@$@$@$@$P?-.bBByLƶ"'&;z~lGܞxSm8Mڳ[z"JgOZv땮^ ^[nNtؐIdjU*J}aue~sfTz駛nV2sua$,?|'*ZjH=(XhhVٳeefLt&X>æhgj³qags~g'/œ n!4 "&#f/'܊DQ p!&m`'ndS }qy{ m9f1* eٽmjl2Ѿ'Vv頑h@AF׳*UTꑎ'9{H@[R5#lj ]>y VW\q9cTW \r 7_|Do}[9awa Cd`Pj3_/O<#7,R.2x1\vvvi!u[5k֘6s5u_xq)z=9HHHHHHHHHHHRV9c9Յh)*RiZcz:d|7. \V%"dUO*@5JwwT2SA4(~_ۧ3zjrn[Vҭ?UiME#Jꣁ`%@A9+ti\*Ic¬DuvP)]P18amȥ^*:u/ tMu(kV~WU뮻 eZT%wW]u<㇍ʕ+r-dɒh3=7X׿=Yf=7#bJ{- nO:ܵ3si3<# $PHKitbV$wD~GBMj%t;`MAKS(-nXj~If$͕x_4ّR3%AP܍W-PEz|N5];Ȫ _@񵈿)B ݒ9]j[N|m~lkk.V=s` a0~a_.]*̌D5bxUd]677E֮[+zaDz!hZTs'mygظVPnpK\j ח$@$@$@$@$@$@$@$@$@$@$@'t .cS'j?wc[l1͓RbPA]b~Ȗ&̟"IOBzG4T[] %.m=^kp9$ivkQgJS7cC ţPΥgnC,-VxN0s65(>] U.z a(qmڵEjVX!۶m;w֣֋Cgyu_.]]]fMPHZ[[Vq`XT3r}m&===FРϾʩvꩧʦMsu)\ +^{+p$@$@$@$@$@$@$@$@$@$@$@ uU)1]xC/sf-ҵSzeG|M*`xАv*quf4/~wR<=Y9BZU b Ze+Oom. o"]TWQ1A\6)s%MjLSj,{wot]Z.ɳ)X}܄@>,4qKLc)PwL$@$@$@$@$@$@$@$@$@$@$@7N!4^NOOz:w:G>˙g$m6l W.۽/-[L/_>iؖHHHHHHHHHHH`? pEo"QHХ1CւC- I(juTSl6yƷe$ABSTP֪?5~iTED3VF5EU(b)4< K2I3'_,Б*&^:ej+N;F|rcSY/švOvΈr e mn:g>3.rS16&          IEՑ5pDL9uߣfS*fnfs5|E u<״n]6MEgDzvʎݲd閽{hGpncvٳviFpa*acx4*5L_xvު~g%%s͚5#f-c.\P׿*EK:= 5J\O8́L$@$@$@$@$@$@$@$@$@$@$@$PH`@Ih!V (/1E!Y)$mjc)ez$236M#}ŭLprK<*UaϭGpNթ' m%4! >KS"A/qTk JTҶFuPgyɒ%us14[9놛 "1ХА踺2nlQ)oѢEfxw`g2Q߈MV16uo`3Ҙ*F:7P7 aԛEh*BG x fH-ˠ@i@BwL9$hf%|%           J;.궀5р7̺d9aqu9s:یx0fL&zgĐXj Wi~u9O< #   Mm3–2۶m ~<Mp× _8|ѫ0?H-Fp_Zg۷-yhАFQ[/ŐQL3rv pז͙ؒ6\ G>.$Rk,ɷ0o6ζΞ 9\//S~g5mg=:Apʗ-THB bzر̙3% M!8ob孭 |D4'           G)f:]BlQ )ᅼEm 9D<9  `Ką6chklꍄif-{3q g=wo-mg r&}<4Kw1.ޞG;D}\'_?kÛ6\ǖDmu똂0upl(yLw˻ߥma"w2/m9>pur.[x<{٦nα'$@$@$@$@c\7fh3ި0UQ-k3MfC ,g*n|1:qg*ɷSM̹kp>Q+j0TrȎ+;L3:s)/;p_6M֣>},[*[pN$oh#OGIHHH.}Ӆ7dR"5$LH oڐZqc EcZ-tgHx>.+{)ܗ{ؾ]w6V}\Q/; }>3|)q٨*jX?+8l'aݙlT5>[ٝ|Pw[&a>/qip7V𼪷owX_f.Fgڮh|}\ܟǵc}\;|ǖDmǵjGuE9㕘3>O/֞05ho_ xgSL޷|P{UDNFE-'Ն27?K=R[˖\fHHHHHHHHHHHHH@ X0wA"QI-Zd淮a *NQtoEP=f=JbT;ׅHHHH) $0}..=^X3ۺ|h'P43Y'N7_s~پ+l?qHNGoޅJg$yTprWpf7s>?auflQԹip vcUá@{Z}3WGOSIݐDÝo'խ̺qah.Izy&+mKJ1HHHHߏG݆A0Tnf{Zn@: #}>Ïv(õǵjGaH=L'}\}4j>[}\[vtcK6gp-5LJ߈}^JRE.ݧﵬnJ2ʻwI4ؤm̩= .*k1=lbno޼y5󛇪`/iS~',oo{sr7.7=j?ۧ(3dHxxnH,% ;(OR>mp/I q_R>#!뜃]nq"r;h@ T}cɶw{-_@>mrěe3أ1sFViMI~X8}\C5?4q>^qWPqEzu9κ%^ϕ( m+wJttoRW(zhis'Rm ԬOΞmǴnDi޻MIX.pD)L!t% K[K~sD:x" M],@y @ hM}>-;"o.ͦrypg%!T="3rPgz'/''ۺL"Q~mLCP 6QG-6Oڥ>+IdL\ތO羮ݲ/?!sG5h ƜMED1!;6ʁ .{<+g4L6:xO7z&            A@%Kލ3,f2Eoj~Dt.9z4jJ&4(*P%SpeA4dceHERZ]zwKdҷ$жLRT't<7j"jPQEӭ3x}iiMkJh:ΝR\v>,F{=G-ZӾVij_Pd%᰼*s\uwwlݺU-Zc" .m۶ݻcx*={"M2EfϞ]YU裏re̜9S&Ov <s:q,jXَ]qX>4Ϲwy W;,p><# D֜iW3״ЍǙ NFa"Q1=bY!gkp$-qm-w4B(b3IIRcl^U#!69U1U)9/)k֬˗6?峟l^]{{SO=5|ߖI& _<yu{<#2mڴ^r'i&< ^8͛eVUxo|b9 x .+_r-ykJ$ӟtsF jycHHHHHHHHHHHB)V{htgH=kxRwYeQk?2D4K7{aIKJLDS467IgWz-9eJGY3.4%=(_nwHo٭5 uhX$qH;?iL jIu<:Gj\]_Ci_R[ro 2.@>ϛ"Їg?N?tyWdҥ.!v~7|S0WAmqS}Yg72X~s9߬Y̳-rJN|ˍu?/_ᄁ} *纜?I;px #6q+w}w-ʑ|{ߓZQ_tE裏5ׁ            B3A[Z=ԡXن\46ġYSac"I*q龜}b*κ~ͧ$eyoK_oXt{Bf~`! !X%Nlvm&uִz}9Wը]ʕ+͘鋞{ q5\#+V0ʕW^YH z)> ˖.3 $KNCXU^ZN:$tMW_-7ȕrmZ3n_H X׿u뮻̚ .X@ g;/~_/ĭ\9$          KMЌlr۲vY'}y1u{GŠN HK0)ѹG-/hδLjJGk@&<+hD^YQڼSb},3 k~RN3:t@IDAToH'G'Is%4h M=J'Ǣy4MY>C4$޶jezG@| qa΁ɋ7uԊ+~PB?&&QR(*Ą@ i9 Sݱc)B09ꄅ(,J`>e]kGGj ĕ% d]65AOn}衇L!A /| r:XZ&乭]ֈ!hBEȉ3g}֜}r??:0٘|!          :Z4+f貺YV}cDQ=Á|Sϸ!w? UEVY 9go]r2[%cOvw-I]$ L޾@JJ:Y\ qݙIIE.;ۖ-0br挪Ƙz* _jtU v!/as̑{Ǹ%=wߓ^՚"Iiš$+~[ ۶|+E!,ZȰ4 ۍQ&6 9$jLy'1LӘiC$3]>~^kFn35)u*ժFY]A_1WJctJ_rUCYCt83հ"i-1Pno Vf2y[En,(KmRɊ'| kZO s5,puND6w_җ/>%X6Ķ+tmIHHHHHHHHHHK BMr[6 ̆xV~\&卷UhtwgP믋7=cOC<>oFRYSUFi$f*RFxU4 РTLriͨӐ/vC`9R:,Uu;`^K=:Z^-*d C@vk*YaZr-{ĉ=eNGyDV^7E5/HHHHHHHHHHHG3\3}H_',EՔC=SE(,lb>o޼pϟLJR*Tܹ{wO{ITlknjúR{F~ت1YzRYq m4G6ua"gΝҭG,\ YQl7ݐ*QP%|5J2ujB;T:vJ(e˫H*#%JKerdž=g(PH8zr7O?\pycY5H/3m8 Yfqׁ$@$@$@$@$@$@$@$@$@$@$@)zH&mR6\% XcRE?V!(¡%L1cǴP!xF,y0CuGEu|] h*>w(e VF}.C̱,#c sS,M6Ux9A.kʕ\zq/wqk3 @] @Bmh@Bۮ;ēUd)ZCq=^U)J)P94iz77 Q},]PtXmfٸeZs}zd6cEؠtmba%Tn) +N(j*oT\;(ψ8ow+oDVbΜ9xb۷ eLn4C@DzE'M7X`mpBsYɓ;+XCx&          L >׈П2b->ܽR4Gh@!)둈ሊz}1:Q|qrKTsKRK}%GiR ,ЯGQUK)$rD1S PqPU+OXz0Ik)"//u6aӧO7o4W^yŔx≃kQcd7l`vz$ouwAS>SNj/QVN*\\9 !`-@&SC؉N+Ej=?$CGܞxSm8Mڳ[z"JgOZv땮^ ^[nNPE$݀djU*J}au\FF?V$[ aA|5'?Bq3u -6ME ^fϞ-˖.3##~3J6E˗/=Ssag>gϞ>nߙ2<;$ӟTL}e<`.Q g&           1EuL=1h uv{'nybjٹ/.oS9f1襪eٽmjl2xhV+ѬA2azMU֩.`陎'EU$MwZUaf̮ Ѥ3y9+dR4J"@PB }Z퉸 M3 Utk35KiME##ճuN'UQ0ab"FFa8T NX[Z+AXAI'$pan365|Akկ~UZ[[ͺ>O2˴mo;xԫ*g-rJ9՝rw}-"K,ܱ’W_f͚ebZkGyDc5qC/~axB( ~V><_HHHHHHHHHHH-BV,ŪC^{Q{$ԤVAl޾CvބTJ>>&Mƭ%֯uUm$3QJICM5hRЄz$Q^>o3Z怊?0JAN\^u]}-o  ݒ9]j[N|FB[ig_Xfe]f,_~eS} k>:3s<K ,3#9V q_Losssa[ZZdr뭷&_[kC DjrܹsK/5ns A[Z9W^m!|í.,]K푼xA$@$@$@$@$@$@$@$@$@$@$P3VA13%gM1F.O+XSO=U6mdK ־ΟvvuiعIHHHHHHHHHHJpZBiBnL `t,_h[:koTDTR(!?>K*quf4/~wR<=Y9BZU b Ze+Oom. o"]TGWQ1Aus@'u=KԘ;վY"`]$gKSiu,7! Mc)XJp[nx<2sr @ @ End#`Zd)SmcOy"T(jcz j1s3ThPM2VtJZҺ5(R@-Li3gAƂfRH[ekLrΑRI3We ?aܾWWLW*bB E61Qkg6Z~-5bMVX xɕЫlD;_<,4f_Zpa2 ]Angj*M&ORw?5ADB2 )GE~VvzMnk؟4"+#ӊ*f|kH#$# 4(Œ nRz앩S4cܿ V]ӟֳxbYr刄ԑLn:g>3.rS16&          K cNg2{6Y J*Eks$>? S*fnfs5|Eu N̓ "x@uy%p)9ƖU{"_-HHHHHHHHHHHH`|z&o=͸-8>*R0tttlSX7kqc            Ar!z2 (["H$544[0!G L. \k)2-,e1=nj̝;WZZZL٦c#x?N.[g2_u~lHHHHHHHHHHHH`DQE r9jrt9WFsKgl9DZsn!̐ yF7RXB ZOwDѦ&YhArk)9w` ݗ,sPKft#êT-FHHHHHHHHHHHHH C`<nrsO٢*ֵ0/?=b-#'өpIuKJ=&TwaP֣qn$=K,i8b&EԴ1qXf <            8P2!4;P.6(HͳU]jQAѦ@W(xM| ޱ}oо\2qdOkH^a ڥ%e~ݶSMHsT5OY~4HHHHHHHHHHHH"@p~03Ea|Q O^oA] PTȡ4rvhH\ހsOl~X_%%7&բ5=[A{{;*r~kklܸQ-ZCRSS#ӦMupN,ۼy477.>_2j;vE5jL0!Kٳ{>={Wƍ'#G~'o_Wn]kj{NHHHHHHHHHHHHi jDbjm^(:1={#Z!0 ֔tE],[m3B(,茱hTb᰸CTjzW2ZaZyHTxc2rHY|A\n+m.o P!"iҡDR]Q=HV6$$Qs:ryRHMmDk'|Yuu8g|Jy_ h:w| _H;W__/O=i=zvӦM2bĈ]b}\wurw[x<2z\Rϟ/'pBxzZ~,9gz{UډZn6#A> ݿkrgݻF7!_}s_Wg?Yם1HHHHHHHHHHH>Kф xP$+)⤺QҴWO1c%te2.'Z=]jKPTBj%6RtMkc˨3*n05¦ZpFJ7djtk$DۚF1UA3nB2*j*Rm]-&G{\VRX2VkJw:65H3jw O*_QE s9O+VȜ9sO}J}3V ֡O?mN/\Pƌ#=FҸ{Chczbi%6nWɄQ~.gn]j,i i늊?U4.77_X.ׯ&E-QP.jrVMp.#uq{`󉅪CE' oU u.WY*85o/!vv#2> Q:3- u`o4+|SOM l<Ȁ]%KL^y啔 q.2.29L\.䒬SsYg%s5 $fKN@ K.>T;k{G.R7n O7X^~f X>CҠ=s7ntԩ=&~ee&o>qBUtH<^\a1 w\5S[`pBduŭsUh2.yS[#Օ=;eBØT_69N8Q M}{:mVkZHSL1{XMB YGEeDҍ7wj329%p 'Gf83Ev# ? җ1O>dپ}<{Zj1̴rJV尶pf<&          (S4r5S:=$"gn`шf/J(_8.U  1uwk|ѐ ai8T^]>뽺(!&Yօ]S]UVu6*\bTR͕oNf QBj@hi-Wono=\Ǐon}^)SKm\ɊsLkZb| kYKҔl ~k__]p6a-\ta">+ $@$@$@$@$@$@$@$@$@$@$@Ã@ܕ՝KZ1Eq{GH#5 ݨvܽWFߴM>*hE#xPvI8 KjOU5`HjjŧVHFs8B 5p.xVL7Fbʞjq*.-K@Eڙ\5p UUUt\c[Ǫ:u+ 9R kdakU{… 瞓&N쩧*Vk aԟ6m[?[5$s#->MG+ѮiS*r6m&pt괩REE Z) K>}[9:5;Tw V(m)l,ώkߗ])7쉓&,D!BE㏗ .)~ϖK CK @U2 OZ =hx\QqfѻIYqOz٧RE?0(vaKQ%3KcDǨia ӈF̎ ԛLQm8!6ΊTɺEw9edtP+ ɺu jd93!ܹSa-[HWW8'  B ]cMl-2|6ml2:ܓ @ BԼ ]Ոށ/Rt0bzjxC*cFO|_e:7D}V׼q+FUUQ4׃wv,4acƌ1ݼ=b S>KQbCȃhLW6"n}}`$ouF`E_}U3;nPPC̞9spFea95D$@$@!`ؿc+FfwCm7Wc?-+Cwȳ$@$@$@$@$@$@$@$?=y>m`I r!?ةcYx!H[Pvpcgtt+%-{JS[\=ҭ&\zjur%,:,}IԪTBnC)9ˬy瘉*<MG}~>/_n aALq'43Igb'L s5]"~37E ,5S(Eu{=Ȏ;S27)ý1cs]weo/HHHҟzY|:v8:$@$@$@$@$@$@$@$P o&=*/FkWaC2#6J'nyQiiX"\sQ&˖mC㊺R`RZ&G4ߟ@HJU*jPS2d0knbp9'p!]t' 3XA~ӟ~rg!>Opc[sqCyg|_7B[n4W2($ re Ƽꪫdʔ)&&btZK??%W^i%\"/eF 1' "'eK裏QFj}=HHHI BplŸ&7 wtc]G{k!/#m߾ݴ19x+^$;Z#|`KO$9_<\SM;&mYf籭޶w}x֪-Ze{g0|',̽Yߖs;ÍwH8.E s7:.:.-_;ױ%Q=qifuI\ٛofޮm9g96עjCfK_kyG}{e}AhczAϏvW]']= KXEƑ_N˵ZimV̘D+kUzjrwyXn]vn ",LS5h;(Xfh6#Xڟ6QkSխ&3Yu zbo18amyw'?I$^Λ7Oz-^^s5=(A\^~LoX/`hb K"|3r}˪Wɒ%K(6]2k,{8}_ /r_& b:::R8:$@$@$/M(}>e?oQ|騣2A1F^hglڴIz)qiԩr9c(m3u刭 a_G?* f~V\vL"^{mj`x0 @bvЁL Q@k=RUV߲UiUJ>>}Q5HKG54/T rI}DrΙb3ԱG+4FZm(jwʫ իp(WT(UKc>.'",A˾v̬ /V]xL2ul_L6[<Ȝ9se&,5!]}Zdk_Hu[]]9,X-_&7paD^hZ̔}4iF7ogyfS/t!Z}v $@$@$$`/o!ZK]vɟgw $DN|&-lﯿouX\9ٴgYt /ZB#h'݋~n_S~Xu^ M<ټʨRRoo(&^S)k$/-zT ШV]؟K4gWWPC xN̬owqydɾ^yp2DY8<63?ێu+̶m l#uBkeϴBhuIQ 3gg\9qQ0י7 𼬛0gy=>%I{^ZZ lg~WՍ p?m*/ıfC$m{R,"kBPRqufwK{2aZL,.!~*ジHp)#?/dXg$(]~u< bn*_E\ݚsL1 xW$3GŤ}ue;Zfw|nY\ݟ=@ 5G"~DO|ξM87bqK{"7R[=㎿E`븴w븴|m\ǖDi\ǥ+q&sgfoN7, yQ&1Q*Fw"筘˃>j6gz`m˙^"M}NY:5g _(Zꘞ}q+v"hkP2⓵l՘ĭEvtUgc^ P8TtiZ&m\[1bjY1breܽRּ4VKӖҮz@1JQŞ<<.:W6/|R6FsΕ SuHHH`l%/[nSN9Ÿ}ᇥx_Dm}m?CDPJP/27n4\M)H!:/L}Dmi;|E#}G'>>`ꛀ(2>DT@B> g?} \mӰz1SOK.2`gUHMȬcIsb?|Y67QT lnuodju/\W:.]^ Tp%c2T t! GBm FdCe1HjQik)aqUW]e,5[2Q>g:2sLYdI +i"7tE~ce  J4HC LAw5VyE!_lx~A_(ClN=@IDATqЅ%'HicxGSmlvN ⫵=餓dԩrJ6$>},\X 0DP|Ѐ׸0)v.Eae41/%W36>#`AŢn'#ɇdޟ}=0Zƿ=.7:w_K'uΣGv]-^6a ,=]Şǁqi0qi޹-칎K5WL"=:..,_~ !bI36/?r֗t52]L/"#k<+QL֞/r,E /*KRKQs(XpZt}V.#<_c  (kvΝrr9H$ӣn?X&C/9 BD\ &^}駟ns^yIn7o @1 @A2I%O|c}-E}d_:3Na 1eE\JSvwB@kmmwVT8uP%|K_=n\-n<2?KvwXh\r_?ŷG=qT}uڷL_5ۼ -5kVMKV%+6^RNws! Lxփ( 5E7c ״/Rb{gC5"$xiO{<e [7˩" ֫W#GVW3!֮]+0qD%+&bZe^&MXwR~v PU93$Aʬo O>[' d ė8ax7f }ab:8z~?ޘīo {/iMvo}\O\=븘4suM1pb}ph 5Bﶖ/ǐeP3>{ye9/5BQ|l(n;bײL}߄aX#oQ.\?c s98p1'`oMaS(7>T09R79\v{ޤ(ΕCċ]deހT.̬/MJelAbæ%r-p\0jHLHHHz'`] ξk;_` N@-[&zq ϧ ų#[M+RAr$j`w$@$@$@$P|Íyy;hK0|X ?1766jkSE?|G52umvob$WX.71s5j|J6}>籹}8tz)|H7\3?1/UHμq  lCF q|Kkי:.oP~'gwb 8׮3ul lu<0~v]g8_:O:׮3u\\W/%v|΃(Hػ#2Vsw.vU?E{ WU0ÏZ syfԱB )mݺ\%BH:?}hb- D4'   J̦ɓ'5\c\Bs(=N?yꪫd…Xn!Rª!\"!2[NJ8XLQ؟r)tR*~6շT[N`u:!mcQ7YQ"TޔcmG>d]ɵsXq~;Ǻ{N0K _=NR(3:G8?N\ik׹m}S}00O!:ZƆ9=1G8?Nʵ_ +,%sK{繎KulIfu\rg)1qqyfks Q1 c\Ͼ끱!]q}碛F|d]⋩䖪dϸypeSCC`L:z`؞HHH' 7xHߵkW~mhXNQo+駟6`yQGŋ'1ϝ8MjMg9*Hp< &1`a(84Ryyy<_>~   l:8G˅g?Ey駧Kx]lg &q'FDPSʇ>!9cS0!:".gaڍ31/f0Ա&16o:Nv`m\v<{srF+g>ɯwk:NJ Xn+Z8\}3F|m,?u湎sL|z:= X~εsd֟>&;7#`9׮3uU!>&; `nc9;7'\Bix(,Ezm8U?zmy_2 üEQ/Ɛ&Md0!ffib&gZ#"-al_ZXT.lWmү=   "(Qzaɭjb$y;4s(؇x<{ݭ (Vhd.v1.l Ske[|NjOקp qԛ."A:oQԾPB|ž鬳ߍoz9= +9Eh b   d>8 1be^ҨQd…Fė pN8l=*i, t`(&]Ģj q2OT|. OCNU?4i1F|f&;Qwn,ͤc   @.\ӊ|>տ?[?=IHHHHHHH`  "mA~߾se&GEe^\GI{[(L\Q9ѱ}oо\2qdOkHcSA K(.u.)dlzE3qxq%L$@$@$@$?dv8,ùʝmm>m^>ۺ-$@$@$@$@$@$@$@$_>mj,E>?X^oA] PTȡ4rvhH\ހsOl~X_%vGEW$Z,fu\u4:Tp>nyFf,#            PDmrdmA/L;QAHTQMKAbq%(JjW*~)OhIv2sCtnUT DO%hPQEӭ#x}q)ݲu}jv^z)9zO7MdĈ=N;xbyei=Xr̟?_N8y*[~,9gz{UpW_}vmF$M;1|ϖݻwo|C9siu,cHHHHHHHHHHH` @7He)H=ui\/cJPej \.ORc{zJTm<% KEu4I̗Q YLNZaS-8#{u2irxY5j}WmZ_XGJA#Jc@T[WKI~=7fJZյVkJw:65H3jw O*_QE s9O+VȜ9sO}s+[ :O?9pB3f^|:"w!]]]r]wemW>ܷ?Org3[n:QT HHHHHHHHHHH@12B`SЦ4P,1]hud(LPnGӪ5Z}.)W\Mp^L~sq:>irQ4RMQON9IjKkKEݸ=PPu|,^=vyVj^X}ձ"Ww*κ~Ǥ}[KwHgG*}!BB"LKB$e7x_qF5 7 zjJ`{GJ.^,YbLu+Q]tweif?r%dmߟ{N:,;ga!1[u*Yt}Ѧڝw)\ss=r饗ʼyp\x/71oYnv3'SN54ßgJ9s")3 =8&uPX:Eӡ 264⭥Mvhc&JU@!b( }k.]@"cGʈj^raʊUkduBL$I;Uҥp߀OXbz)wSL1'|quւyBF7tSjNe "} /0USN|E~sp I? X!Y[^SkP+K_IaO H}[l!pF|_E.w׮])Qt͚5bbΝ;`t$@$@$@$@$@$@$@$@$@$@$@E!&k%FWq;jZ^{ؐTmx $ꐇp^^>.oڠ+&+-]R 0cnQڢ֞oR^tl/i޹ʱ.rM7%|EcHT׎.۲s CP$f4 "mq j*q̙qmis{_49czs%Ĝ;hak׮MMMM)A2hR0J¬3a}c3Eկ]|o>kcǦ ܜ&\tT:-E: p24!K= @4f?ሪ~XI #(/7d?/4[%-1)ͻU8HMU`Y }@>.tUtH<^\jo#Nuأf*c :)&DWBkL~u*b+]E{vʄ1ls8q|t#P6eʔs8$D@8,4yTT +O+ݸqcήZmfsQ06.4A@|ɜ]H` 13uQȅ:}pfLXn߾]}Qy; yao_/^,_W{&           'nf stpgP(Z.2.JVWUJUEqLy[㋆T0 Oơz88H &پLh>;UjUg¬U߈+i̱3o}+Uԛ02yH~x3As lD_Y'v+{$X??`胏&]jXJV|=czXӢM.|3]X"]DdS?Nk_Dڻwܹ!blW_}|ߦ[] {          J* LfE,aZy=pngmjPA+*xƃcwHq7ǧERjOU5`HjjŧVH U8B*4*.xV3%UTSqAMl1"-^!"J*sX^-w^ᐬ[arn7kakU{… 瞓&N쩧*VXn۶-e[ٳgRXz;N:I>R^&          胀Fi)d(; ܰaa1ypU5^bT*~y?[5$s#->MG+ԮiS*r6ԪGN*IQ,?0P*䃨ً(+_XF}b^JWX{1%m,ώkߗ])!|#Ԕr=qĔ(PHrV馛d˖-rK]]kܿr'?䬳06 @AR.rUh긠Fƥg^>Ȕ*ADȝVH Q%3KcDǨi]C4Q>3J'@?M#j#Xcj9Q4ubҝ 2@>Jƺ}l^u;s8$X&ҡW(u֙)D휖,Y"gqqYl=r.Ɉ w֬Smܓ *CF{2(6-E0bz6#J H!UBnj)ȿʢ'Hu#+_o,D;yB3sXuUղf2 lWK4lcJ]wX*i*`ം,TVD!4ڮPQVPjBU<@ Aܪ"N/+W}CBBN`ĉ2sL3'X5UVOY^Ga7E!":p`m;m4sigO:5[1HHHHHHHHHHHDO#RN*fhäSEQ#AU=ۃqivqMњtxxN=Ιu|x.y[m b"L㑶8jTVVf~X QSTX-TRԭy EV0h"D @Hn X”ϟ?Rjd!:ի`Z__/p<[ݽ{ܹ(ꫯiwqbϖq'\ ~vmj1h$ߟ,HHHHHHHHHHH`AxMOO I}?!'&lv* =Jqۂ&[]7ՆӤ;[3(].iٻW}Win4{n#%>zS!,i fZl:5;a;g; V)Z`G{;v79ĽCzL}'-'י"^… yy           ! CFMIm 'huv{%Pz gӮlڥB\s̝=Gul&ͻZMʊJ귵hR 6neG4QW$TS])[< "i uJlhڳ06#| Vrg!>Opc[sqS>|M nos+_ʠ,ȥ]jƽꪫg1y?kZ|0~XK.&3,R{1o+z/ZȜG} C5\~oR            !"TQc)Faw0 sƌ`RoDvī.pՓZz3ΈXrNUH(&e*ϯ"zj4MN0EuMǢ4Uƺ-ԭ{dž\[E, B|z k˻\ 裏Ev5טk7.s!!}4E]d/ ?n/|3IY.YD1O])W]weZ]2k֬^zߩW_NqƏobZk|Pc顇JgoL$@$@$@$@$@$@$@$@$@$@$@CO"x.V|C?!Au٥>J ewOuDd EUy5i\l".eee$3ʡ3&IR*uk}`#@V-PEz`Ӽu}2PUȅUQW#ʀZp*qӥ$g,AQkuuur ·z˜^xLl3a_y'?9s,3Ax !j#Ẋ+Ȳn0LV*U{L޷I&'?I7D<̴)tM2oHHHHHHHHHHH≪Slh/3D3aS26l0'O\bPFb|b˔5D7nu=**KDêaKxvu#fɢ2WݐLעxLo*ֹtͭy:FZV9&SA[@,4 KLl)mnȸqz$@$@$@$@$@$@$@$@$@$@$@L1EA:SA vCa%VXkrPנnm+e'k%!quK#qԨ:T;JH 4*F4_][-6JE5-21ڬ1^92 ^)k^{@+ڥiNiWW&P(,q|b^ˣZ'fZ9M. ݥgzEOӣ>*,UKpظvER$X>/kϏ~)a;v[z,]XΝ+ ,ȧ: $ %T$5T$l\3/u50R&hj*LGPw?5ADB<_.UE~Io[ڣ#]W?5~ijO+P@uIV,Am\ K4c)\ *Fe4jcũwLj{~uwEo.53gR3MAO!ƺ$@$@$@$@$@$@$@$@$@$@$p`ا%`GKERaMSa4vєStgyV zke]8ImlmjYsVٹsZԚѝ[idG*%?j\)e̴-2V(a_(2ViKQ4sT(E#<_c< 8STCRu2N11eE236&{;%niKŭprK<*Uaϭ[pթLϒ4iD">-ܧn{uEjS.=ʵ]WnH*Եoen>ݠg͚5hc3PYYvaT:8)rnD$@$@$@$@$@$@$@$@$@$@$З76Ћ=-:E7X͘1ðsu)6Xg߃13HtrCUm++%j4.2q7 ޤ(CTATK7 5UD!Ŧj*UNaԐ' @ @uZ9ĥROdOcbN/|t>c:{ gfL&|.Ӌ8sE>%a}Za 0V!вhZC>lޢhL4+"֭[Ǎ'UUU&S+s}Q~Pu`HHHHHHHHHHH,iXϫPmB|v!o-+8~8            )sMLѤh=xx(\aS4ydv.y~07bZ`c 4>D ϶vٴyΉ&qO]Z M-U!mPX$(n_SdI{C1銸%UkP,Xw@]9kRVUoBF #hRDMNG&i۴x@$@$@$@$@$@$@$@$@$@$@$@$0\ @(Cu5DPc-^]jQAѦ@W(xM| ޱ}oо\2qdOkH^a ڥ%e~ݶSMHsT5OY~4HHHHHHHHHHHH!@qUFtA2D<$VTTbzu1 C=~ P!:\Uǣ!qy=ͲKc}L<@٣^7n9~^Z7E&N֭['---fj%Z ,2ZKQMYjƃ=$FLnL)ƇPHlƥ+,fٲmB!dAgE ŅzW˸j! FC#F4M}ţR\#յ C|SAȰ&X:tHK;m8 ZHWD9#0OLJ?"5u=Bfe l +_~Y,XУ w}/|!\}}ܷ?Org3[n:1cFΓ6ywzh\S1C$@$@$@$@$@$@$@$@$@$@$+c8gjMh%v6jEm'0irQH 2꩖)'Imt w2b\ uqԼ N%c_=vUuyIΎvu;U[Că(D:IpO{7W\a>Xp rꩧGydtuɒ%YwJJ8{Ez]vvi?.\rI) xYg9s #S0ҥK裏6Nk{K/T̓S ֶ_~V=4p|r9A(:uYw' }LR3,OHHHHHHHHHHH@;]ҎnZC>2L25nZ$?k+QePFtPDQqXdQ?82  NȾvz{Tߪ:${qW44D}Q3&Q\(U,fj{{{.t7n܈e\ Ѡ5wQ`>_Jx'nK .(ĭP`zw(v\s5rgc!D &?HHHHHHHHHHH*J sFtK\Ib(K=,tvB"G-1n_mwF^YV1WLQ;;M:?|&痴ficnS=_ӍtyAuyֱ}zwpS|nu&IjM qݬAZ: qVtݺu9&bB0f'Gfc, / %pᇛ]===F@.gR{y͛ʕ+/̜j^u袋Vį3(,ro?k    'Ktv-*۶Meټ\OpH\!^.dLnζXZ~0؏C~i0̌1]|mǖDuY*q&nWgfiNq۷:f[ =7!Eyf:,Ǧٛe՘ӳ#Q e2]llZP:2H/R4,>33O0˧]իA|M̗:jQaVoWS4j=u(01b-JqF h7@(+e+שSí"s*+s_KgR7~XSeO8Ӄ%ʢrf5W CCC9\)a/W]*` C@vmC0ljoF駟n͈'tXB`jmXb9\ Sՙg)oܹsUc$@$@$@#7]6bo4o#]pOi6]\Gp|,0\-|קҙʲ$lŶcmCfz؏~i.E7VWgOf?W[*%Q5uTc Dθg>GuhˆV;+b \|GLʖgV[޶.Kڵk͹Μ93\5AThTju/7m!<{WVŶZٹG̘<ij=sd SNU:~SZԅ[6m.ptYҜE8bADETXAttڒx:5k0wDczBBI*"`}G˅^h?3r9礵.>H?;H 1B$@$@$Ph6̓hQrF-k~GQ>rfrQWq|+_+xĮ9qQWnz<*~\Q c?Gr,gN}{Qʜ-vwPCSeD3ƜmV<E,ŃJ)U􃈨s$âhGbfQ1jDŽcZeߏ6UZgMp; Q4ŵa.mA5 Xf\}.EhM {]PH֬YcpצQۦ%K駟n\gߊ9Y/Ԭ<@c]afeiZBn d'O g$IQܴ-q3{9{ L+0ËO(g^9f0܀^EӁ˸>p;;~> XЇ7JLT$~Iۙ.vz>8C؏gUNJrtf`?.ViُgWLNb(q{_f`jT%0uIz8 uճZW$xB G=Bu'7KD2r4?Ѳt[e#1g>U_/j^BN0^.?g'#NxI'Ȃy(ݠc얉dҔ)2:uG ڡA.+pH:g&V;x/:9PAQnU1O;#o|CJ"Tac2o<Ӧ 6e di:W3~_y37qҪli;fϞmZ/6\^|\0mڴԮ0u WH2ՀwnFtvִo$~15Bfz nzKx@bGwO*B1-7, g;xi#7ru' Ϫ~g~S]4t3W8'qXW-Jf?._[:%Q5quR|o= |n x΋*n}ùAs׉_u?oW#y6`Azģr"A}!7,xxo|&nBv{=Tճ'c9v9C8X>p>%˂>XN;X9p:h]0`::L:" :.(Nj5!*==]%|:l*Uɓ'b~!_7;!ǫ#P &ȮZ#0sNپ}*_z%﨣75!#d86顇j6DzџHHHHHHHHHHH,c` {Y eYV}NgV!zT.֗<;1fSʶ]ۧTܽ[te>իZdW.c."_j)KFFDQM'*SC+#\{rҥKk_?>˗ar$HX}٦M?` 943(ijx#\`wΝ;7U(ŋ͛U@{\.ꪫd„ >f3/??ڀ7_u=5 @a' 55&C{V{h_op\X'{[ `魲u$R E#LmCy̼>&? N~?elyf0!~\ݎ/\g=N~\Ja?Eُ+34L"f?W[*%Q/97+~qQ }9/}nCH"5 @456?o+ډ>F}nE.#姺!SOA U]Jw HQCˆݲWuI}] @HDtF_-P)uQJeXT˄)S q1}MA KS3d iF+?x~m5V^zǰꫯ9c袋H9 (Ow]Wb!r-&ח QY ?}ΫZ uYT_XK . U~7]verˤId۶mmX: 9u?Q-Zdn3+62 %ew B*6` "(3&! )St"?jK0~|OXoA8ו!~\Ja?Eُ+34L"f?W[*%Qu4~NP'pR/j^zpg%2+쒁N*3%V}N] ,= KX\t?ITZ ƌ PM5dܐb^O *A u`閘ZwlbAqRWl2#FL\:/EL[̢kED7'-N?.8qr!k:vue9PrBszWoۂeZsd@O}J>c- QZ<7x̟?n{]zWUM:5>`V [GC<+RN;4$@$@|>inl I`Cn/ܶ5RΊx0{OG>FJ&>kc>\2}2Fb=o2 AUW0q~Σ[ř0|t*w,Y6}{QNQfѽmnѭ >;"9Z(}_z eNr7+>Ҋu$ԯ⣚_7cA9` K]C}[Qg霟]]oh^^,׿D5=T\\^R1P|j]ߪG$u1̺P*&JefKK\|F0)bS+S_1ih M\_sK6FZY n@{j kg^q[؏+iqIJN䳼%W43WW-ؒΚ:\3Ke?$Rm,q{_ggT\3yV'],>1܉:q0q NnoIЊm,Ա ٬F΋TMDgԝt[]_"!u]uI@Xrc׀7-7!# 1BEP52 *D;ܐ`v%8 B}ԉOEWtxD*tyNeC\[lsW*ׯM6WAmk<﬙ պ&U%ʼKd׮]Mۥ9Wa\ML曥SkkW8{ݎ>hYfnb'X:Mo[f7"*7 ~VE;߯ AB@$@$@$@$@$@$@$@$@#@ 1Rƫ옕:))@3FU1)-kޭsxWGOzap$>;*L(/*1v:f~!ԲKz:]:d\CP7ux긶sFuNS΍j[J<*M3JiRjZ͙3gVa M,)@Dַ]St)a„ $gZ ;lqpÚ0h2m{f}V;=֝,*"@oo(sRݛd㖭2"۷<>I5!?MD?Rq݃*_X. u^92yarڬ~GJM /P(aY0{lnb\X[MC-K@$@$0:9DB3M&;5 H!AhQz % fmJcĄZf&\P41-{)N[SB:!R {nXmf p Zzje|;_2HħE%^m"!!5_⨶WH[Եo}n>[܈ϟ?buSQMMtA$48)rnVD$@% x,1u_#pq{̭(fF$@$@$@$@$@$@$@$@^mOu"!9*gXT\ntƄ ڳexb]SOU]IqvI(˓Q,7(Zקy8 @ UOZ,D eZ&ӱ%-:/\v5sLilltz|$7bvx,q{V{iSyO] HUH$2c}!E=;d+J`] bqK,֠xRsIv◥UP5 4I5*ein >E@ţK T\sNQ\߲D}c8kQRL(mxŋq'q̮m:t6#qD1_onGb=k,L@1BgXBiitI'7m%7W&OMq4΄HHHHHHHHHHHH`%f) +bx,ZERszeaurL]? 0e*_G&6C?"J@-> DU[$J&sdwsS9wWS;> Q&\%nyKL$@$@$@$@$@$@$@$@$@$@$@$0 DS/&&_T?#ҾiڞBz͕j=} jc\-6kOReFz5*jWT&;[\]zJpKҷe Z6 hy:(\꼡U4ZZ)[#W4L:Dh1Z>uvinzbWָY[gC~[o")7vuuɺuԢ5$MMM2{R֭[eѢE%U͒w۶m&LiӦeIU]`p-ޓ]vWo??~|,zZJ̛"ӧOҦh4*A75GП?% TOkk%j=NVգܲDё3j!z"'n 6!BtƘDpX\CTjzW2?5~eƫlC._Bߗͯ/&]ߊ_*SBEЂWUz$@CH!Hf h'x6,ZZp`@{?/~ ydC ͽ+Ҏ~;9wif_ƍ7HnAδcr<2iҤx7c9F}Yu<7wGG,9wxsEZu^{|[2"iځalsP_W9sdΝiۿx㍩6׿K/4-Mɓ'˛oiDlǹHHHHHHHHHHHS4)"U0Q5%DQٰeL*'OZkB\f>zӥ֛$ E%Vj)HXeˮ>j9dB[iVLNZ&TyDvK׆WdF~XMz[[5bfBm0*'w1ue+H㏛'p@3awŊR[[-{`y'|SN56XuQ)K.1֙/|ߗ~;,b93RAo[nEz{{{9XªHHHHHHHHHHH`0z9:kٞilhը?Ó456dLSn. \R4ݮtG`[?-ukdΌCDQK")˨Qۏ9aRQ7n7.)أjF [T *Wjj*85ͯ;}_z{FFdJkK!tfZj&=7lW]u`M{M7I'~_ە.\.YĔ>_L g?OtSO5e]5);~i93eႅl֩`ײ?q&~䮻/\9n^7X^q"X?6ўoۦMJg͚eNkDꫯ4 gM            0,EǪ\'hչoC[m"?<ҨS%6bI;&lSm:eg\5zF5x4WBAy}Żc X& ӠK'q3a0?G,0[u蜥5MmOL貿.:[ms7i?O|ۥ3R+Rih->fMQX%7͔)\nܸ a:a!ZhnK/Ny'nK .(ąH`zwݰ(v\s5rgcuGmT0fΉAքJ@bU=Be@]Tu@#Օ]eZTY66N>]nV#xM߻N[՚"B{{Y;?`5 8t/6QQ*<1_urf;_kM>6u%p'Gfc, / e6ײ~9cr:-A8~Ml޼Y.]*+W4BŋP _xA| {444Kc$@$0&Gsrc?<P݀'I$@$@$@$@$@$@$@{==.uFc=%VcNR.Ho;LBL( PW+p\B'@FcEC*ŧs~ơz wիA|MثvUm 6W.~#*!9_Om( !aѢE<ֲes{;`:uTw~iϹ8D`]jX V|=#X"\V"{PWXE^;// 3[E 7`M?lɸH$+¢qq3 @ iTԍ.Dq݈QX~eQxb7 ADM{B7*+W;w͎7g@9o<(v$ISkx|ބإS `0$MMj3kgtEuR.ϥXՔH^D=ZbT\lKLʢr\/:R^?KcUL l_5ۖﺡ^_RVN~Zn63OI'$+Vl-Wb&g  NhT% wPX <ΉO$@$@$@$@$@$@$@$Pyis8yA?y-}.,׮]k̙3ZUrXu@3VEk԰pÿ?.UjUlk{C')[5$ X rZw֢.lUܲit鄣fϒfB&J.’fQsXF4 յz }X5gZ2a>[_u!10Wj7_:ZASSS5)lgLOYBG-^xqS39眓V6way晲p´  JV,_?V/f͒ɓ')XE&       AUԚՍ`F]UMG]SݠAȔ*w"u3âm^ťQڣN֤is=O/KPFb(ܱs#muAv>7aWTB}.`,#5VKk֬1͸kSmג%KO7ov.ѫ ۼ\ X& /}{uHGG OųXNnܸFlٳgl˗/70oQGeqM$@c^~ky>B7ߒ~[0s[[D^<0        b.XԤԯYڋJ.K݋ί:'Uu_AU={qiktQkYXS#ՅȂ',="7uZ),jt>nGU-]LԇUN PaPU+OZ+ƭ(UaTE8D^Hէ hɓM1 y;,pUS tmaժUF0#UΝ;e)KlpxkwrEIKKKG$@cQx,زelܸQDߓ]v 4`H=@$@$@$@$@$@$@$@$@ a(9e)rzi.}., 0aA|CdQ25TNm޾sn;zeWW VwXrt:AԪTB.(C!Y:I4g?Yڼcc3Is ӦM nbNg/^<"ngu]m6gu|>\;g7wYg D %9ǻk'̆nO;\;^x$@$@$@$@$@$@$@$@#G@GT´.Z&VF!Fm@OjUR -;5uA .Pa/6w]fz:{J4?B ISJiz]kՁS%urQ4^hAZR5 򫲋&E+?x#z K/5sW_}U9S, 3V\xȓO>)wS([nҗ4"$* 埾{W{5_/R .0G Ǹn]vYJ@j#mEoۦM6K[l[+a{k           AZTbqXƍ՜i -EKNvNϹs #]vW]ઑZxx/׹*N>UƵNRːbRW:"$7SCirP nMf0MDcеcvřNG2`Ĵ,1"lBpoNX[y睦XAN8Q8\֮(+´k_477v}40_l LJ 9?OE,Y"|R;$e陆UzWmԩA֬>`l3fvHH`(-C9X_c% kQSx$@$@$@$@$@$@$@$@%DxbNѱ*:rNQ'dHCZұa)ݽ٭>tCQ@>tH$. ~555$3sgH^'ugS2ԻK4Zeq6eYإZ* X_P Tn$uѬ U,31_l,E_{5SN1i.a-[Y35ϚO~" ,Xfb µ^+ q^ o9AderM7&+\ ^7Ǎc{p!qCybr;HH` Kt|ԳÀ[J;K1J$@$@$@$@$@$@$@$Pe5s<Kb(N 9=׮]kΜ9 e$f5wvzOm'"?\^Vףh@BѩJ4KuO |z|9؃5 MjK c7eC ťPΥk,nC:B6qIa|Fħ+wUf,wm4oҥJX+ެF#SR{͛'K,)IH-˗g?R7 %cb dEQ8ܒRaEVU,HHHHHHHH"@IDAT~񤥨z(KƜ\sŠ'va6gƞ tUtEZ2Q7-[eE%۷o3<>C5g2jN$1(L꼦sdd 2Y(k^x$KQ ,2Za|1[wC=:0/) !N2 bci1[B$@$@$@$@$@$@$@$//Q5SH_WQc/iT(̳~&3^-#] Du0j1۝=}Լ1N].R` .zQf[܈ϟ?buSQMM4<#'E͊HH=M _;Gx,HHHHHHHH`pm) C>8VR(ȃܹsMs)LS,{$7bfU uQ{Zs>?!-sTHӹ4oaԛEZh*"k7 M Wc*-Aզ[=.*~m~kۄ="5$@$@$0F{#$       bjg T0蔋 R(4\_Q Zo"<o,FuZu%Ǽ.iQd"   `]fA        JQ(4KQa }B ܸqɱ~ICC;(βa1VWu 8 Cԋ(MUC?ǠFBd!=1 68}Z;W#!           ط)-_ aGKz<Kfp<6mgcc~            }ҘCp/]Z&ysHDFmmxe,X$XLL$@$@$@{ľhtgy*6-ٸMmi1ĝii1uږgص-æ3Յsk       (P0`Ix\#iGim.Z̜sݺuό3ĝi* Y67".qil> ]J_G$@$@$@U'{!b77^=x 4Meq$('_@p8lځ򠽶. n#       R D%zRXRGEWF1u:u@9=Ts 9 T@= @,pOd1,s[9A[SScɹF>+fKYn4G$@$@$@$@$@$@$@pzJs$fa,,F"[R+ӗ%ܹsͩw9,{ E#LsuVj1s'    8|338,7 MN{̲2mcf>n @ +@UBR1TEQ6dKHejh=o9Kd<UWpٖD{OKD"Q]Ipq%QO4N/.ORDchrw}9sHHHDS{I1ݻeͲsN BF 2a2e444 rp}rl"===,wҤI2yd;ucmcWW{ ӦM'PqlAٶm mmooOn"q6xOf,7sۦ\#-Ǚ 7\el33^4$/-̵͗:?Í8?a엶0Yُ؏ז~lITg~\g6qeyf}k{̮3[Sbg6qNv!j!HƽdGѢ|0ڵkͮ3gJccp#Q%:N,3j8y7mZ{7`ʽxWG5(Qw@jke;#z饗y'mmm=300 WG}._~(^"HAU$@$@$@$@$@$@$@$P&@󉺡9E$;RL(46j~Ǔ8f6Q:S鑸 C7z#ZK^  ΰZ%HKKj>Yi(Ᾱ0qxj^Ng-   KbuaV2,_\ HV6l_~~PZZZɡ `] 1_xy7ĈKQXtB"*N,6ڵKycgϞ-{`ZK.+W Ĕi[yin&RDv[r6ʟuxcQ%>(H _-}:``L ~\ݞ/mE ;mվW:qu)quُ-ُ5TL"f?,Ҝ|5n=ldHYh6v>klssؑ&\~G}N6cYlFgYh]]]jNB3U?gN* ģ0e*_G&6C?"J@ DE{-^%R9D2cҹ);+7IpTW͂Q3znK^   .T;Eȭ[s='k֬#8B>h#`vvvKϗ^zIqlDF9ӟLYp{g1g?3'|Hq mqmmm5(WQ (t믿nQv~DK <>qLoqOvn;:Y/G >sliY 9Y}gZLEVBa+ $`?ƜDSNθ)˺8;*Ҁ.vz~03]sYYLp (X %Cc4Z}R:khC[o"i2`u֙yL;כEV&\!-Ài!xy\d,?>ornpwP9IH`,pkc|9Gdo~ay '*1?{K.$)LXBUA$7e0vm;l$ގuM{_IBZvwws:EQ0rԅ%BbkxTo4#m'\ȫg-]3;c<7 { Yaϼ>K'~{RpŔ%WDu~~Y fS#~i'B]{-1{u2؏{ُז~lITg~\g6qeyf䋸}ΰް/Me3qScPDQQT-1y¬e:$U=dhMG0m@;(+45H`w%t/$^~_ws/i^>v  V _`17ظqq 7ȝwޙvSN|P0xYoQ1ٳ>tց%.opkVo2Q]y商C0h Le-3pHiz`~{铬> ziԩO~҈SLI/`͝;׼\y-%޽[B~m@^/& , gΜi,@,ADZX]ֈ"؊y 'ᾰNz? hV89-gTGLov MQ[ pM"׀xz} _BL G2߬>{|W~LO`?.U9)mĜLc@Y2'^V>۟g(qI~\άǥ*?-q~\ XCޗ B}󪆒9G6'=p!54?ЫC G]2jT p?kV;^ j>˒mѣbA0ݰeL*'OEUW9p8:K;M/,ICt CP}k-d# :?ϰ 얮 Ȍm$DjjTV rTK6%jڵJ"}[Y0VҤ8^X:=N8timX,|;FE}3?/| fs=Wxcy>'>a-QW?X;=X׿qawŊ^'\!`6 azQG&؆5˪U۽f;#k^/5n-&`8[(朳> ؗ w/Ow>[-XFNN 2o^b'Xie#8sڼkA <ԊvnPloۺͤE-Cw̚5K<0Xom3gu* oz AyS-F3u^-<( +XByq.3竟 nEj Ic4: X>~=w?# 1sg|X:t޲<0`%,E=`G؏FUVB㲰dd%e`?. WىُFWTF0 nxw鄛9ϯy>9EшZzU+?OMg$VqThH0qk4'-pL}q`ƲD==g5iBOoYI &eu$x{o0$/c/"Q*ƥcVyOe]3cQ^oF=~477LwQʏvi轺iOW `DQg]^cҳ5yK_o nxkD`! !#e pO{7CW]u@ÀM7$'t[[\Ͷ"%KLَ}/bJ8 Orꩧo.Y^?f^ F` pϛyѐ?\wur]w_.7bCc _dα m[ {β' @/:kf{,ѾXz`y=Ar…rgDIuj}S3o6T@u(ݬa 4؊zNkpE&,GvP_x5A9N~$`-ukѿkWư to\`&0n`uiGЇ>wx `Txۋ=0GW& %~ײܛuBfB\!e\dvbktNaGOֻOHX̋;KZ8t>5?wvZjGӑ<v&ZVujtCצZHؗ%>X LH٠O"uT ȍޞ~ƶ탈 Q_RB9Xlق>G b/ \B,'Z&>cQ~6kQXBMGp0)"Ex饗я݇zH򕯔ַlo޼9%c~3<2R9-#7HH`7=" ♗af+Q~۰moy t7Lۗ }K)6x?ȅ7}^0:x8s<o,Naa'Nf3ǧ} +℉8zl$q_ +PE~&Lx>"-de˴k%4 ܥkJ 1WzorEp:u\~I~7/'NDهsɝ}g? o ,C |'3*S:K}g?f?bs;3~\,؏Qguُ+C2 b(<.͋y:2 8 qx̣/b,k3"ٓiA}nU&wyˌs3̑#Q?.f`,]llZ}s<. #B4n?iXA*y]xTUzr|d uH4d(?cvk\*F|E]pA\_z4s BCXhQdR!"wb 'LC7uTǑD&>8fhoo7sb v[#bM)6;h跾-^sv8 %nZb_ݫQ7D)߹BD5xBSO=%O<1(,D>|Ř2l"%:>Ɗ86{Alh@~ُ{ `/IEl})ij}^ 7m{(d!`)j7k25q<ꥱ#O< `Znva[]>g{GQ!`!4CϦ)Φ8q*7UfL>r|W03qADI~\9 a?Ή"|opb`}NKg{Xs@a>b;>79#]v?4o{m #hZTA'jWYxsyu}n3& `ʶ! }& X ?Uu-m6ʞjq*.n1Шd9V` J {9QA^jbhvpBwݲ}]wݘ.ʖHH o:~`1%'C͗N5P; ](:~C-&#SY VRUt=úS[ihISFp:EZz9;r'{hѬtLh  :Dŀ&ss;yFߺE!hףe^x-BòsozHH`f#Qa4%RW|6x n"-GKwX"6`=\ ɼrwܹr)x8H-w&F4q؄o.5,X m6Zm޼[_׶mk;^(>|"6e=ꢭ&M:ŔicK%[nRm'RYUh#bz&Ȩ~ꈫ:k4^ZpH +фXxEWԪx+;@k0"N믿nbqEg?8kd7o,[̬iCbcC60٘g< hZ 9~:dVZ' @Cc4%%%k;2k/+g!,ZH` Ӗ#.նZ֝8L!d7n헖 `̙up{77/6ͭ_g[ћnɼĽ0 ^L$@$@$@$@$@$@$@5!&rq& <Ô%P(:99>^DUf%PoꞀ?UWh"+)yٶGDû.H{UUTz_V~`_{i~pjo;LƊ)UQ4W-&ZIkC /O_B|P_Q*YS+MN8ᄂ_B_$Z}@f(HHHHHHH!`?}q0ш:i#2%Un NG#noTDx#QD#r!ݛ]_b*h1G9>PZL'SRW{` k"ssspI ͙d#HX[?χzaMZ,N:^<\ 裏 +ѻ۔Z|ZРϻ=S<' E)?&$w8v:6\[gƆy}'       zHTHNKѩ*Qh< 1nsn~W ~5uʣWg~پW1qGM+S+ќA2kzv}X.dI=p*fF:VRGfdǔ8 #z .3|g3c_tEC؎i:_xᅦ#<"([Ck&@"VW^a塇2y?,R .0K/ Nx;K;]sqAq?vL I1G7'/^D{v|+̣E TKnsM`-[dT(5uVv گ*u9zoDVbUOaG If4>{yC6鉩QfZ CM}֝ & o b[[4h:WJ`YإܕmCfksvҔ4k1E]z#F-7HaZV+X[qr g̘!<u]5/ODb/n ~Zn/\+/ڵk(6}k_˗1Gzn lsoFE#sQs^ Spe] }gO,s^[Pԕh5 TKEhM~U݇pZݲy9}PzrH}S*aSWxdF6m.񈊏U$Et$)<HmAgMkxAq7i_@׫( I+4 ZVåbZTTHdtNZVA$"2DLJV]zLdRm c:ovYb2Z^+ GkI +r1Amr&NAL<7S#s# %PZe#)Y̗˶ge+UfKՕ*sL$@$@$@$@$@$@$@%>*g FwҶk%,|}eX"-[L Tԭf߹ZVIy6 i~ EuC D$.m=^D4g$[.gv\13+q2C(Tu[/-EU3nrI/`VULTtXIjRu[t+ _TUAٚ5kd۶msN]]]xY:L³ꪫK/np8,mmm Hk¿nMn5,PH}S&UrՌ>$@$@$@$@$@$@$@$@$@$@$@c#ͨ,Vʔ%]1"˼ٝin޷[zeG|*`[xt*quf,;%!OTNo6lr*D}6XxtK-}HlO1UDLPAwP툃|⒦4qun}{H&)J۴jZM)BxJַi]\ #Pz4Erj4#Ջ`g4 D GhJwLϑE=wn ]]ֶY:.nhOdt5(T@wf{86.ͷ3)9GZ6:#OR^MGf4O]튽y(xشFcQ9kyJۨ]4oZtt=Zֶј5{RИU:X.Ƃ/W?X\j7n(O5ȲedڵRG3 3hȍ7HQtTؘHHHHHHHHHH&f QZB? c=[:s=EQUh=bzb1QwiFs m7\-\pPcu2״n]6ϐg|N;e=|ű#x>:5ePi2UN K.5!mj 9v#7b&DE=2*\ٮmB}~V5WsT78[QobmB*U54[Rl^Zgc0 c 1sήEͮIHHHHHHHHHHHIDMLQLG8EJ# 7Jte:`q!ZM|! 8!W~u9Ok< #           h Ul0޳֢'ԭ򖢹y \abQ4Bsǎfs̑p8l6'ؓm~XDσ$@$@$@$@$@$@$@$@$@$@$@$0 @DJ,E[Rb~U,U9)Vu69SqP($@$@$@$@$@$@$@$@$@$@$@$@R4I=0&O4 EQgpWW(N6ucv=+?'           `% [Q4 NsŢT2H$bBxU rT>`1 &&f)wxΘ2Y6\jfqLϭ[ϟ/&lS4Ϙ/c#d?̒N*ٲe0om5}a"           LFZBBTfcbQԩW\ɜ6׎WY{#rz.F_ fl9DSZ[0G$@$@$@$@$@$@$@$@$@$@$@$0U@/G5+#$+8MHUͲtRQc9lФnA#RkRXŨv9[ Lj(qe=2hᣮX2#oSq)^68G޷=ed&RW ڷIHZ-Իr2ףvI"-3˓Q33iQ@~$@$@$@$@$@$@$@$@$@$@$@$p m.9_ 5U*ԴMfT(`%eS`iiiqV=߈؉ ivgcx[29o#K۸.jtOI(:f:lh~$$xZ"ISj q'%K%rfF hND-5ei^ d!RKQU);S}dGUh'ūK-3!)zTP)OΞm;t/ 3DCo6^t%X"-=qz!t&$>yc>~RK%[D=0$G̬3A3@OeFgX!q:Zj9;1!F@\ceMrpʾ˲I~w&`MJ2+Hry']RmɲS$vԎF֭['Vm~g?قN(x; zs1[o%CW"wqGAի嗿̜9|/xrꩧc=.!͛7_yk[neXh#=}sH~#_F.1?qw#xL$@$@$@$@$@$@$@$@$@$@$@CFf?АiEjڪDquuXL, :eƬUǀ_|2D5K7aIOI\DS-,d[o'(ӻ*ӪtpV dlF/ho$nxkݣuU7a-6;ɌZln]Goe)Vu'ڴtV2cKgx+xַ ŗ,_Ĉb{i&/XԏΏ~믗 ޡ̓>h;3%~;ӟdݗ_~YBXc6#8O<`i3 @ XKQLqVH+jlk˦ҹil(ӳuxr}֝"̝rѥV~K9c@HJo$%Wӌl޶Gdɒ (DR()O-<]K$oRQ7n7l>ak[\-}Ut&evgGaT{nT]uUF5_/gun&okRcTZqڵf1+5k֘wnnJ}Qy{++W4a;4A4CO:98>7֟7mڔD pre9eHHHHHHHHHHHARqEa)ʘak܃wn 䑖Hk0iٱ{Z^ՐhJW[@:mx,*/IؼS, kaRNoX"#`| :Z3iKX;/8R:gK@cZ>GLq׹X]K7HsL[& `ԥV^n+/07!"DQ@#$2H .[…;Ly5 CXENXFM|#AEK.$~|{3?O+\xAU%ns3ֻ (({͹_yndɒ#{./:O?Yo4]'8[sN< ԓOXKQk`3Uz`FzoMaYKMjMwN{}[gFUhRǁ؈YKO-H3i(=֞Sz =sky~}rV׾J2@=()_"r bMXDL$XBX~Ρ j[ZZ qٲeFܰaCAh._.oz%e}h;Z"E7x0#޽[ B"]}2R!¬3a}6Eկ]g}>RڵC|XX`[lθHX?+"          h4X8dDa)16ߧ1Cuh"Ǐ6qrr_T{%pwi6T=I1: {wᐤ.  Js ddJ>MI21.rQMWj~VduYKTBkZ~AuW_cjtU v]Y!+?fyp7 78 pBsv$D4wV*WaxpZ.,% caMj \m ?9#f/ eGGǐfo{L|Bԭ&hې -~f/ض ĥ 1dHHHHHHHHHHHO=T [f4hUh=bzHF̟4F,%[n /psr Sib_4iB|gzVu5*\!=F|\iULI\7 (x"!XKV|=䓇XӢu9\>Rky·rn qE}WڍHHHHHHHHHHH@sULگL!E'⍎fHYaZy=pU8B[6S>*hW_o&*{%Ikgx|ެإZY h4.-S+T©nJ+.U1ӥ\oԪ+JVTS7 fצ;8^%C86յUi d C@vk;4p{EjR;6u_~P-$@$@$@$@$@$@$@$@$@$@$5 jO* &U75d *Q[l1]`AI zIO$VAE_ܳ_6<+!ZCrPnOG5רhq9xPՅw 8h"iˉF„) N&9(9,_X&Vzq{>lre>9S%_l,D;Ygʊe`OnM[hl{eƴ.qc i ;]TZpZA* EQXqBU U =3D0|_w#b[b޼yl2۷ 60wqfgyF_`)(f=guV8vww{ygM?q@ Wٖy           Hx&uG|U&Oa.(z$g_4#]-.9嘅ZC--=<*W,?R=X'mgꉪbL*4iJssX=ZowAͪn:R4cEє *f M֭"`Ak  /`OPAaԪTCZf.:}N;tALJl&)@‚غ֎+Xf5Ν;WVXi|G ƆwmVZ{QؿuZ6ER矟DM NH̓ 8 ;Q܌)ZDE F6J0Ղs_6wHYq1?ݤ}Ut\G EQV"h ի"K@񵊿9B .$|>c jf.ZLX"&%G+^ͳMkr6u$_r<Ϸ~XB`"kԴkex[ZZJ*7믿0q wu@4eaEl\+(n #b= @ bVeE!6ثv߯2u>s˖-tRN *VzsZVIy63BzT ThT+΁$ե뒈Drqӎ+&sfvۭ1F)Ikߨ 1B:q5R8LQ߭/& *oC K&kׯ/Y,\fl۶MviBkmǚ UW]%^ztww5aikkGZvmr 7Hoo>sk 鳯q}%Xپ+5a 8!DMK"9eFW̡2ovlڲ[햁Cj_ XR6!?"NK*quf,;%!OTNo6liWPt,C\eߞ- vJ6m4ko. M)BxJy.HHHHHHHHHHH`(UZݬmreS\(Z#=zoJRK6Km闌ukpQD>,sCM͔.ͷ3)9GZ6:#OR^MGf4O] O#F)PċĆ5 {f׳ڵKfEgKܣ%hmiWZQ5{WDEr.+\;X k~yaՖoܸQx cEZ`rJYjU%نHHHHHHHHHHH`(#؈z2j$7(SM;EGvpiPPKT JߦT4fNPwP%ж$<}Ze׮W/5MmSTD4ceEP#xZQTҬZ(}\I*G“/tx)=OfLQ k߳ؔv?n]S3bf]vTBjEmذA>όxEGEIHHHHHHHHHH`0^Ms3o!PT%#gKU*n~sZhNwei*"Aomn!KD{wʎ{dcG۫0}*¹uj dy$ո Ȭ'_.:oDDX{[nTZX/{Hg`/)Ap=F5GGL$@$@$@$@$@$@$@$@$@$@$@S$(t"5@Ţ(ey󶍭չqz236C}ǭLpr$êT=\-n&V\/XIIh\ҐOԦr|k Q])(1HۛԵos@E7a˗7lJ& r1TҴam pRlnND$@$@$@$@$@$@$@$@$@$@@ƥV&g(w؛P:SŢStҥK 7wpgZCu݈,C]lWM!s>?++9bd&+¨7g16!h*54Rl~31tڂ1Pu] ]  ԍjBi5Ï)*՗Ku[xPaNQÍ9mF=Ax\zyg3_fbo,F5Ni(/7~F_]OHHHHFAz^ٙwamm[6m?'`ˆ/ZU~$֖"P/-[ޖ<<ÍxxHc-ޗvb]׎}lI}\ţr5qmy[{g[gksYn,RԺ5ߟmgM{'[>QiҬ;v0W;*%Q3q:cd'y e6&<܇@q'5eZqy 37HNV(6sSstيEQǓ.Qmz{*?V~O$@$@$@$0~ MG}ʼn2{͍8L+Şk|{2 5cjsg#r7;ѰL≸p눚p >.Q+ٞ8zld A>S[O[y nm"*EmɤD"S j琣OGIHHH/}7TJѨd ) ޴!!^' 1"aq `|pz"3c$<? L_w1~WS/}Í(n?~l|ϣx_o1|>/ng ba> ㈝GD4cWqgQUՐ*lwrE޾k7u['<[J.y;hL1ȾJ }]V7ץB>HR$Hɥe1=njnkj6g؈):膔tR> LA~D>b2 LlxOMx7fވ7cz[M[-D$AH8#75"M_YA7+ iϒzāpKmxWH|lO>U5-/o3]5L>}\?c׎}lI}\vT'_>`ᬨ\_'<[$҄uT5u,N~?olyq-c}\G\/a)jw~FRܮ|sO>W;*%Q3q/זs_ґ"ppBý9$2!Žx֖VenF vTs>׭s#I'rXunpyj ?=b-9bMIGAfѾ/\Gd2k4<-2s~XQ36 Lb USr^ O nC@4 3DPE@ +VڟF& fhȣϞ>W;*CG =f=3u}Qyk[͝wi'7,7!Dj?ۧ$3׬bh̢㙟I0Iq xtg}sq'%6 -#$4|:>` bC]nx"5 i    EϏtx=Y&3^ڳX,."`oh/cfg-cG|ј#¡ y8x8:sֽ|>9zld. x_!OudEԑnm*Qt"Xh-*lHi\Cs!2)K7lZ{]}[t,[Bbt[:I{K~sDw?)L32;RȇDEq!;6)>.'&4C ajjT-s˴2 HHHHHHHHHHHH&шK+ 77EVqB"TMo "hT2nq؜=P5Wwiղ_ZC\Z̨xxJvWPRFSxTt ^_FBr@z?$]Ν[YM裏v7|SM9sȴiӆm_mp-Ӡȹ7ÍN_zp>g^hѐ6Í:   7, msz:^۶宋k+#P,s3BSlUײul{g#,WpgF-,K{.Ru{י>#u2s:Ǖq,jD]qX><ϹwyT;"H㱾<#~eT 5Ӻ3/5xepl2!sU9z!Z[š/HBM{dΝF@1RDB\jf'5̨FGOm0gf,;r4y2cF>s;vK[{%$-m]W^|ɻՁ_55mU_7fu X'x%OJk[ڎe~I1a7[NVZ5g?~u?Qw9cطzK::: |E㎂իW/K9sfAX/^|E9SO{v/sͲkW^.k[n8s>g8,G>mm/Pb 4ҔZԥsi)ZH*QpwKdCrĂN1kD1W9pL>3e֬YN?a_P(T`yg~GqyŃ2SN1LPw饗ƍ婧[oU"vm%UH VCms.A馛cU+ .@~9lsIHHHHHHHHHHL"=.4{tHcC}czcnOT?"ۺS2W]κhץV~K .v{I?U4#푇ٺod!hg6-˔E.m%7b\6G-]x͚W4={_u쫵nWYׯz^^VS;$әٝ=D 9O _NG#VE *"          (᜞󖢪iLsVC=(/!S7+6|䙗u Dҧ8vJQ2vk҃j^<:=1?(gu$Oj ԃR/"Bt>EK!ҖNஶe˖lÆ uF,KwDkЇ>4-b"moQnwޝD!r(a%Yg>?l~_7J}nݵkW^ G|XXڄua^֥L$@$@$@$@$@$@$@$@$@$@$@,MTʸN;y|*OcjDRUQ?5hNBcO.Y3`ORcȞ}nk8$iXj˜@r/xzi:/fqSdb@]"mʉN5+e i!T%_U|iSuh{횕fyp7 Kwlu9ִ.\h_ApΪQ!A\'ZKRͫ,% caMj \m 15q~/ eGGǐvo{LX,-G܆, WHÿկӦMf-N [p8l6&          #HC9)bh*o)զ!؄Es4-;f0XgKYM!%2n J{7qLS5>sdA˧"W(YAx WZjTյBzK:mu7YCt83"#M&K[AL#A;#L˭">ǘ #vM`Q.YObM>mWJkurskª$epK׹$@$@$@$@$@$@$@$@$@$@$p И`):tVyئJ=lăR:$DM{B7%}c;pHFl~kZ}UJK*LTK".4(kV֢*Ƃ4Kkk )+Tf+[4)q|.nTzjMeʣ*{76{AZN;r @Z {檴y_SCn! ;Wϵ J{wbĆ袋J5ɗa4P󅚉D"KIHHHHHHHHHHAJC:,Aړ#h^kRȑoU ܲe }TJRjuTQ4={HkóR9$J~L~تqYq YzZ6w]]تȹ{Nр/(jL*gURz@F/S?J=iXj0Mh@zT=}{56׊ijo"^UW]e\~n:YjU,'          3zGQWey 5|U脺Ê;RE?S*%8t0U2]=£khMt'^LVg0C5VcE3XXFo:DkEss[Thbh4ZYF7edtsFo 6ȁL>9S%_l,D;Ygʊe`OnM[hĘ!?+3uٳ=ӵIaւIϰM5^'ZPFg!3_8w`PۺukAY/͛'˖-3kھ}ؘnMoyA|юgXHguV8-q1m4.3ǣnHHHHHHHHHH&/T5˘h*IuULU8q9rE^DUf.rBy;O3~wrc#w'u(5R+ŘUP^GU-tvv㉂Sb?U-E3c<֦)υ&bP"kC /OZ0DnSXV@-hTNj]zegBP/,CSg]NX-            H#TjAnuk,E2*Qtbj嫆TnW-?}VZpޟKZwHYqgg}V;<袋5uuyF!tMki +0^}C<~_k)zFz饗t킣\:M/#^{[򓟤rYN$@$@$@$@$@$@$@$@$@$@u qPգpibwDQsҥ|S7r]ըG)H4s#眽F::gJOOL45I"'zj*b[[rT'EGli8`_ץͬc)[xq^͌YÍuE׮]+wSnnt׾&˗/nQՍ܊C(I9>t)0}^y}jMM[KXzV`z1'> x 7ҵt#          姵-g)SY$L+⏘[l1 ,rbPQ]c~업GJʓATHK7nu=**j` V 0]8HTN8n}qpd̮}5ƨ3e2iձ!QX3!^ZQv,VMJ/*j[UQ_P3MSЙ*U_0cqm%j ֬Y#۶m;wʺﻰخ &3ugyUWɥ^jb`"Vg86Vt Hk¿Xp kYp\Ts҄xxƻw6{fΜ0WĕHHHHHHH)f@IDATHHHH`Hz0UC>#MUC[U7&IF(y;eӖҽo RDURI v^V [b) y2wz,wgH+Bڤc/76Kk{m"=TGWQ1A f|A粪t|^׃)i>C\eߞ- vJ6m46rxJ1\☨Ȝ9s&n          TJ[†Nu&A ~jJ1= VxNߊ ]]ֶY:.nhOd-[B &>}$ʤIJH[Zdc4sj9+%zE{dFS޾OUٮ:!T(x@q^ysI\-m⛵FiȹGK={WqXV\|ƺlҭ|&X{~,xƍni+u F+WUVU2<ې LbPRДGU-c)Ԧ&WtkW4+ZnҘ~ioš)iߦT4fNPw?5ADB %js]^4qiO_SьAiEQYP,2s}&cQ %^nJzgT4cDѤ?n]Sh#˖-k׎JH6l|3"7x#EQcc          <u$7~R>wR(Z#/uE5aZ\Con6W -mKtWfyV zks Yz$ڻSv#W+}=o^魺Us?sa*(D,&5]Df->Qv٦~g%%677˺uFe)o.3VŋStY Xt N8aTs .) d!%b؉7QaXxdMlBRq޶5FϟXGJ-3kj(zzzP߀{q+*\hV*aY"E/fju H@reO% mHm*wH BյSI]6Ttv^|yd@ sL%M'EΆD$@$@$@$@$@$@$@$@$@$@$0 PYL'/'>x"J7tSJ*E,.]j;c:1C]AT޶)dgPs1Gus @,F&RQ.oPZAc|=cϛBrEů~XJHHHHHHHHHHHH^ z1i7XV2*ɗd l3 bq!ZM|@.8!\rg>HHHHHHHHHHHHa/ԩKpf*E|ɤD"S j琣OGIHHHHHHHHHHH&bOuU5R7TYs֭fKkk;ی0飼c#d?I&c8] Apu+z='t/            )I`#=EQ'J#M;>GbkaJ-a:5[&s5dURnZV ud"-UU H6K%Q-u5L^:êR7]wtGpGpGpGpGpGqO9PsFh9sٲeiH?;uf:1ϪmmbPY3gϹ SM j8yAQni =7Ȫ'n祻7%]UՠG.9 u5 nWy?J]Ӥph3Hl5Jϲ#U,R_Y:pGpGpGpGpGpG((cg™TQ}]TO&3~vpŌi5rh1Xڗ)̚@zRW1tIooLhNH]mz^u'MK44%qrGpGpGpGpGpGpv727.whuuY,FN~pPDz%Q]xtnZ++=6If]q=zDuHs"uzhy/eedÔW5Ut[`u d#;#8#8#8#8#8#<gyWVN1$ꮹү6.V>-:k2keаji-sSlke|C dn۱VW=. uRC&W~z([⊞&բY)TפA6ʦ^K~&M0vJ U5zx_LIhM״H㤹䣘C{{ Mp𭭭|r]+Ǐy:8V\)k׮ JMM\.]*N]$KZ2㢋./qPO|X~(q4>u]WC#8#8#8#8#8#l2n4z5[dٓdڌ=[W?ꪹJDlτ`%i*Q]+d\s)W ~L<9'*]a g=Y'/]U㥷h[S5hfm +G$NډseZRm}N;h7TXUfժZFticU:k6˴oIMGJm 5=D|wSAC詧*7tlܸQ>hy'e0v}g}6:?w̘1Cnvawɒ%А/Xy'x4w+C8∀ g}0".Z(,{,g8{Ygw|!w!sN16%w}7YZpPռS^z!yk^}ߔ/XZ9sC|*ޮ:5\VZ+"o%i^ܹ~8#8#8#8#8#8FelglG&߫[nnkuȾ3fIY@1bQT YfC0WΘ6E&6WK.d\tt˓K e0&]Y$]ns򭯑drUCto@lKQJW]cOjlN*RY/M-y}݃ ^ 0fXY|%9s c=6ljժ K/4Z`!~=3s7UW]%^x|{ ~p! C`bu_Y jQ>O#)ZESo{Lf|bg#8#8#8#8#8#8;qkZIy=~ootmH+49_4R5S O=Tv', =ӉtvK±qsWTx0eVzSUzFf]}x\!==dѫpY%[6ctݖBe{cO:o8~嵯}]o!"G]z6r)Ķ/B!6CY&g X"[1A~Yya6J{t7mt]zuGx9_կ~|GpGpGpGpGpGsT a3CϾ~֒)= 4kBn% Ʒڄln3F;eIT uAFzNC;$ݷT S-r9{BnAc&F֨5UwUCkJk1T떼Skw[Q~z9yFݐYfmSnm+{еOWb\̙?ApÆ Qaݳ+UVyr^dDŽ|mf>c.n"Om\vrf!,'NM0|b-6:/16e/^Uն|GpGpGpGpGpG((Z3=H?3X&%i\1cȁTRzz|^5InbYB:'jHZ(,bFX Vjn0yK46_I+612򗿜{( (X (HB,>lܸQXYַ՜sW3FSd:=5VG8#0(,߲W̏(d8IapGpGpGpGegz+vG^UK茩S䌷EN<(iOgdy-:Y_ ʲ dڔ2}=dBOktXYiYjUYQUXju$1fthkrpN>+/׿VpE)|An~5kV0ڑ' u1Yۑ9䐐OoVY1n}C68∼[V2{ IyAa;X9<;9#82Yl+Ac>}]+cesGpGpGpG`, {8?7U:}LIEqۺuv]ik ׭on[Ț!Z;G l[]XPd'"-!+EՐ٣R-k"V裏LJ lQb[V b~XOqd4hϜ9SZpP`y} b*o1yaA*aÆֹGuToꖕo}+ę7o^QV89#8:ѳ 0x¹#8#8#8#8@eypņدVUUK}]Tg@6YY rh:_˫dc[+qֲ⳧WWf(zX=Z[[Raթ 3r''eH.ypzб|/rKx(o{Bgq6؎JJ~_7 g? un/ /nHqeE帲wcC2WrGdt]G8({mg89C# S2VL!taR[Nj[LJgw"kS("=(3 M 㤶VgW p3Sm-*ՋUjEI牦z%1USR%1ECjxq!/dFV P"+v'-y{+6mz%A/S3PF0G?*]w|_ g`"/n!V5 y;x|r 7ʒȢEQ/o#Uo81s@/zT ]z棦+֠LVr&%G3tI`:k֬LQ0V^6|;`23^CЋ.H0@J|qxF?~<#r%L믿^0&Ro#!;/cx! 8#Ec({q<~Xz`,%ܳ+EXlJ\C8qݳ9 C?[dnBX3cܗŏ0C"nG$zM br[$dkwKDD V@6+37qe>&(Br<`ar 8WK6ϰw>d֎ ,pK6O9\NHYbLe˖ٳgK!cP,ڨ=V"}S|2g2엟UCZmmujSC;;_LV'KWx!R=OΕ;MtJv+oTAzW/1G)$ (,x5z3Nj_*uMy!|ɲrJyW¶2b0sj.?|9eӦM!Ol_R$V^m38/I[[[0bLEQ:Tj}C~!].o8#e8Զ]v׿|0l7YuX=tX[Eyѱ)al򖉰5&,_KB V!9{nbס(&&0{Cؐ5.&ƽQ<4\KéPqt9.\3R_"XZ|p*7qȕ/{Ao\i)&tˮ:j:@PD밡Vf1I_F6_#m[tebRjՀkv+(zj@2sj̛152 5 -2qᲩgTO$KIMzrWd:MF8q}3ELӪ$1i_LdI˔XttBX"V۱/ϋ#8#c.w1c =O]fΜ)zp7-vaz0x;{f;SV`u3[B~ĆtUn;hK+B]TO^OD *rapUyF~oМiWVLcADM~o#אەծ帲ur\Y|˱!Qqepsu9#2.ǣgx{995:Wvg]v$SD73. pOY1/!4Cyը)b5u(;۸,h[Jo d u[Fn3pQġNg !GoРD oni{LqlUit渉{>ǜ'Ik5/vݪW5c Ԍ!44xFzɏ=cWu0Uzi͌eeK}oNnJ҈U:. һ+8,Xw#yh]{9y衇Ҵ`tAɑGYJp8#8c#vxge˖-p`4?-6!QJS>hj= c( FQBM!&m5~g x:KN'C$3$7Z tj> V?{ DLްrZDG;#r\:V4 =3ϥzRWtYh.'9qek帲wcC2WrGdt]G87×6Թt3 qT"cug*J%xƴچzMBB]LIja+a glX!Er2oFLlR2 Y,RQtg*`yyMhC JCUT3:R d#N2Q͞oU 4u_]iT=(W?%SjB*tbb(r-(dXC9޶v=qdTӾjeiCo\VR ;ΊEW^-G?,ZhX$*}cÉ"^zEvGpF}a b]l;o޼pmUNg!%N 15ׇ Y0tvZZϮF0̤E<.L8>$D S/LW}Z?Qu~dQD6i7>UN?7)ULm4CX:.ǥcUNHK[)KIb Jh? !rڝW]+qw96$*su9 q.qDFxts3|qo2txf,y:E}gA˓1nJ˧',b0 Es4kh{uw?0Gqb9p Lw?eE+qpF8|2YRne95J}JۚJ dc?!mȪ5ke U֯_a֨.k}{ӌ X$wGzzT\9ʌyW/˔I-[%JXk( gբk!]Yap}ի^548pGpvu::tWU3{x7=:.#}=C` 09~Lg&?h" ouMarI5k(<^?Cu,cխ /}oDr<$ReW97'ܫ>r?BxXp ;!++qYdFxD xHF o>AIvU#N=ϋMX:Ղ$5,\n ` 5tΖF5&uF:sLcַd({SZoþZM/EMms+Sj8M$J*VľJ-z7W:c=T-kt=&`/PSm5(揫 tkFdf|_)e񭫫 ~B0pB:[GpG`!+[ίJZd`܀~ebX|`@xI\oG@f/U: :/?wè en2|\ "x@]2ib:#8#! !ƦmڊPKm#+HmhMRŭ:8zmBb~9`l82vsPGe2br\\rlHxtss9#Rgj\] ^]+o4WDE*n.Xf=fcNN[-}C$b)~v.%ha'P <|1GXvaŨSڐ:jp{\RurGpGLJmil/J[[[_z饰iSCh\\αt͞( `@c1^Ap9. &4p9+ ]]9 ]y{zms TU[OT75y&c̡d Eٙ˖-amQ޻ZΜ3Zxb踟#8#89}"/?O6~&z^D2GpGpGpG%0nXY/ċe]RԬG75J0aDy"pGpGL0fQI䨣 ._\nvYre裏 nx`CX`o,S2GpGpGpGmꊨNyӭ3&O, sH#8#8KɄq 'Ƚ;{!![x_-'t{!,qo:wGLL>= 0XpGpGpGpG`((jPhg4avRjL沮%s3rGGpGpQG=~5552k֬˗(;`5GpGpGpGpF~ܠ2^F].}5jlފaZIYU+FKH uGpGpQC :徔Aa6FǯŸ'l1>8#8#8#8#0<27.whuuY,X~}89!WDyOJ>ܴVV{2mRzջ$1@{ Dz z{_?+Ɇ),ūGb-o纅4`GpGpGpGpGpGpv}ʳ`H]s_m]ݭ%Nmq9\X;4Zu˴[۸Ziߐ=4.-i:ctz\:WA&RMT30ׯQ=3T MEJSIKlM/(La4Zj.ɔtM4NS{{ Mpح|r]+Ǐy:8V\)k׮ JMM\G?Q0nw~<=!L8#8#8#8#8#8B,vKo@^^E=ICuc]D8OQLVH5O57ʚMz rɓCSjFY߳EZ_~B,Փ z43MMa娚dR;qLUIVRy&ՠ JӬZUh6mTr-f6M2H-(/"|S"`=S妛n7G-O>,X }0GZ a~;Nf̘!~dihh}n<C+m +C8∀ ~g}/?q߮p[Xk}(_#˞/ ~vo9_f,?3 oa;a,G>r0v5(_s4< 8枏J8ū[<5_?v5?Q=r% Oqa~w5xܑ>[:Ɵgs0\ cW WjBጏó'jq-]Ý{7w`}g%O$Up.yq+"M2sj-g= IH*nkgu Hm}LӲtZ׏/ʾ{(Mjfis4+}]Ttܪ*5,ωd$^ NdՍ}՗q6Q])i_y+]]%IǤU1>bGʾ/??XM{% 'Vs^ver뭷cp>aD\hQY,c=3b=묳BW@IDAT;ON>;s9>y,-8(`jmթ`X__/=5 o_,^{{rl\>ZoW_}uXM2Lꍼ^qz{EWKCï~rt ^#8#8"ؖ0?8|Lh36faxg4IF䛏 B-6a 3<3QFay1fdkuW,|aU,P~C囶g/&DŽ%Nj59GY4~?Ѱ)x1O_<&od_Ұ6-=2xƳPX; lV?\w&N7G~嶰o <C#ȱ#2=cdqB-l)rLX6Wn̠\hBv=͋Q<{8loiYX73͗T7~͏@aXkx&֭}g̒:҉QT YfC0WΘ6E&6WK.d\xoO륩0[N;? U,WvΜ9a]jUy}饗䐄b3<3 ox\uUr X -q1T&V^ՠfӟt0Qt8{ d  awVtAce2GpG`L#XN&!@|,Z!w&T(|<=}.q|by԰qw4+G밐{4L:7< oxyÛ_#ō{SQ&g8#3sۉï6iťv"X9치l2< 7ؠ3<G~nG~L@ݬ_$Kl-?XJ˽HyDPbAX8|,G}tp!k_ZaBDZ<ӻm:"ض^(fH5k \pAXK|+E1,+/1F 9{{n氍nԿ[>W9Q3(/_u#8#8 wl\6cvhm(^CiNҸ/5,y&lWivJxRp/^hySe(J WܖƽT9.-ΣP]F O<^ϥ/.7 !@}ZG奰cc*τ+5HJ=Iv;[G~1Vղu>ԥYr{G<؟/!'\05&dsk1)k7()$R%xeVnZ<!龥h՟m_nRj 1i_zF^K|1W )PY[M=jҭt(Zi̜<#nY¶l76W=f(3g ?X5 /Ձ/10ʓJm%clk6sqyjӕN ?iAV|8ĉ W:q'Fr:NmB?2(GUs_O:$g>#\GpG#8I=۵Ul/cv#iii _[ 'f垏rFef2~ Ns-_͛eҥvӦM˕p%/evڐ~h<`wd 9Cθ#Ǔ'Ovi~M&)Ooi=x A*~Yev_#G.L0$/Pț)H( ˗vm7|!ˏ#m>0%<܉r655 Gެ<ϯg{ ~̋{{{C8&Lܕ-5iWx[F,|gB>XO$O#}2\& C8!0oss~z߈ooWG-{.Y Ǖv4Fypo~>I)mX2>>9 #\-@h&,}c grlfv5>OdER?Yd>|?vL|q0aq5 GixtH8bHx#_oJ[?yW0^-,|->N"PQAo7*q{HJ/#VFi 5}iijt0JJ p[WLF7MkC)F!$jt( k ͯXLֈ ŪU͍4o $jf^6)%c㗿ѰQA .9R/~cSlu"dʥ\ ` {̙#]jͮtlj?ig Fg/򠸉AÀ ~]t|k_40~uGpGlih΃c;o0d7)\Qdxpe xs%nL<7/ &_?ƙŘ2eB}ɒ%(g} GqDHŋb#p- G,,2%SO=%/&xGnn\G@|??rva!}0 ψ O%+< ߑG)/|"xY2ww='?)rHn̻7Ieo[{e,FrQG[Vy_>_ ܀|1]r?CRiBd x1gӟd8qc;,ezg+}A|PAwqAvQfBQi ojiνxFFY, I^ ?ς>_ BtA t[" ox(3 \!W*{饗{#c򖷄6@5&,HR~]h ݰ8̟w}9{$.68!cٯkBn̙ϨӨ k}\K&&k›<[5Gvr2֠es\ w3`v4C!`/);pw䖰E^ K;ga,|>܂) ?DvzglHL='pA֨#/7?M6yޞ(G߈2Z9X<ø+FG~߅9ij‘?_cx?0G  d+a.>:o!s9P"`xfB%0y,8HIQg:|(c(5pG1QP09͗#gZB(U'y$9L(h_C>$_(|ɠ!= VNVҎ)/J]b ,G+Q4gP(|Yg`q9A1|R5c8a0lcD=uTlO{6aiT3RF27d.,(1c$B9i'(8!2 L6;-Z.rgrb\GX"`?P(>82{\12Aq?QVSoxd-`؂/yD9M7 81nbRd>]Ãr;ϴ1E~=xH;11 ۴]䍲[:BtCxY,ؐ(J=fSW ٵyX@蟑i҇k2Y\fU{Ε!˿eL{|a"6f]n˟0 7Ǽ,PdҦel2aŹُ\ws`HŘ.1 _Kˮee؆ brcl+{Wu;/z5G 7WQmnl[URrȝRW@N>d]=:.t]*ԓ'j\q8:w\iQ"^-**1df~jIFMH_ү8Q;qib_YFbo4VX#dEb{mmn|饗W0 PbBDJ`E,O4a;d% 2*b3orT(?8GˡxΰJ ?Ju2('!Sʐ>m⚹ G(PV(+YVb! "ܢĪqp2E0m8P0Wpd}+Q:W!Ǭ?N >0\(-&yYŊ7d~p da#CAi3(1DǘI_lӶPT⽄lZ(EQ"E Rl7V>NK4Xo{D}b<*dAVkޓlJ˳ǡK;8#|cAZh]e=Q{g?d l|nTFTW/Kh,\&?&V*]cla!1rLf+0rWc)((bwƏ#x7p A{>v276L[eB ?$2D> W1 /}F3<33yS7C1' ;"DeY`a|@8dx6">:s|%w-qZI| "r >[QxWNеE2sB!2TeEKj*L4B]TicO2hWwq"fH MzZ>2<2bb uB(&ciXBk>,-ٶ'G; ( X 2 du0ֈ/vd10~'_"?? 8#8@X43(<"U#FaJro ;({@L^Qr|'o&(x҄+ q >mX|҆qe2iR|x1Ys=x/P0V،.J0LtO~D"/&1,]v01HkJT9vl'F 7ЃB" ,T(-B~i+H1WK7D?c;s&V  .dB^-V}# y(ͯK5BVD)IS6aE/8DTEyLۯ!@v`M6wce7"d背dǑ7M;}+cdy$uJAJ} 5![;ᙶ|!.͟+2|$|B9 y|?Co;iH,#/陲 жVGѲ` 7o(?-}(D8d+cWCnyJ\ 2M{ی7Cru̱M(ΉC> gr˕>ӇR3#Dq `A|xWQFô5"Fbn~_TGbӈb%꒺bld 1eIY3rK>3mUȐ! yvG䏶`M [ ?!s鋉1Dm 91/۽3FF'Ȅm岹yH?Z-u7H?|y~$hge;2P?92wdoş4ǐFd4%䇱|ǬXm c#6yߙ,Xi7ՠQ~ǡ}Π!oAND{8 nsR3 /ad?mnsC4C-v"PQLзe]9NVK6^Θ:Ex[㎒,~viX!O8N̟'[Ye/kq66eL9O9Gj̬4c^׫o lh/S72kR2 :q3 _3y3Q"=G+yϗRth䉯DgۑkQ&Utq!"讻 ӹG'ܹs:#8#3L &~(Hܡԃ`ĔȌ2OOA3m&(C>fX */˹M|HCLF/gƠ܈?J+{u|hZgj +S `Œ"\LϘcr;r|i]Ա)_PTCac~ ,JmYGPp~+X(DQbDs59~_)dP|EDe+_]0rƜa<*;o'ǤaxfyAID\Vs͙3'gD^(MA Cޑ[ bum8( !A(1F8(I<4% MVr'P rDw( T~ikz3i(^i7 ABrsP"G N-2NM2O@J\a!`a~zECQ3R(e #9V?6!7nB!䇾#Iz%(@iVx52ٶc-JK2; #=trvZ0R (y%,E~tUY{`5@ft_ȟN/zB6V$2K]7j2ϙŶquM~RF?|&rE?Gz'W0 ,dk|&OY_7q`$O!kq;S3%>tY*x1 ;H~(胑w:? ]XrͻŇ?B>9ZX4$…|1$6Ay=F0@eʏ q('=S~m mk40eE ۙ}[3BZUnzwersB8xAzN}:Y>Z]4Vڭ '<65T_M sZEPSƣ J1mP2?5>8#86>H2Cy;FV0 ##KxBwDLQ1Diđ )-?V&i0ed|b%]VRf"WL |AYJLQ06>Ljo֑(yPz_;qR]dxUƲ+{ ѽ򎺡QdlR(j,<1796CvP(jLI?-a>7-]-3BM}DP&%JX#SdM7ݔSĐjþ'br2'/(0P;,Ք﮴ɔu=a3g.A~G3^3Ofe} o#f'r|(}!/jڈr Yo|Q|D8S3PyM*D^/?p`ef,]/ogbLa<w}l0d5z)!AC0bAYuE?#mɌ -+3Cxmr2Mۤ o yXЇ"= ´S ('8  Ɠ#&3gSOc@'ǤA{ R0 !#O7}fܷc c뛭Yje@O1 񴾟r1`|L{b5( d>qc 6 ydlb툼҇/7cE[G |P.#wsw61ZVeep'U:}ZIGJ[wJLMúљj`vk+C;be*I14[Tkjdѣ+EjD94BXuJ޵Bp`Q##:>L/~fQjҠ/nĕΒ/-Z4# *8O&O-8H<$У)/o1Da@`8J=3@apuy2q#8#8I4~L䢬d E3Wr) &yL`M)¶D_ׂ?&LNQayE +y>Cq3gΜ\6]h"I(\XQU(UL2&5BEZ(e0o"CĘFɜJ"p2EX1go!clJ (;Pa(Da _929#+"(Qk N1BqO}*( >sG[GMHu뭷 bBQf`yW7 \~sLM3c?&!g$JiK(li`r ##w~><_(P1/@j Y 3g<#{YfcpVJcDD~H/3 W c+,oC^(?@b7'a05'^ 8^] ?<2Ǩ,qtYDC:A9o(qAO}(ǤBBn0L[!uHS8zO(G{xە43bZiapya ^ChEGrbT|˸xڊˆȮ&obTa'𮥿bG=J9srQdF񧿢M&g> Mch1!a%KΊ=FW| 7(#xg}8~` xF,9 g{` +p7 vLSDaN_}0}kdaDLD5 ?dBn] ѷS{nWY\Q& k3(+CDv <3@"eG^(V~[z Ff3>C .gV3O?W˯ka2fk^mstTW#Uw 6mLʪ9xڑAZpo/q9?J4; N{bԟ6L.1S=NFnyKiS+qQ!12:r-gtgD'ƣp&tEIbB /0t|E،+";/ :%1oo DBD{%z(Bw%Iy @G}WDwwGpvsF)(-0d#Ə|%nJ&Ԭ㙱_x8 4Dt 3IE1ʤ嘔r!VXbGѼorĵI3.v@icJF8 |Z |(;lۆRe&!&(e([P8rܡAgnQ4͍ '\hޘOv@?|G@%1ُr"āF0.EAoFJ>p|1+},ɇ.! H4]El(/gs;~''?E0}j'13g12=R?꘺=Q?8r2O\2޳mx`$->:^ ʲ[y]@;|gh+Cr\גw 2GGLq qVFdxh7V4΢ƒ6L@<hEgQ=?>YDާN1oQG/CYK}lr?\\{ /2fCs҂Boe"mx{=QYCFV"{yoϏ0&Ǵi>& 9F~iP);&:Ύ`$d:}Wƒzn"55!ED_y} jbY@9m|0bDZhv8'Fx,$SNQa`la8YHr@̶%J xNCƉ؃q3iA1 AC3?92" TQTU)ɏyR:O;):x)o -IT065gbp5XjR )]Y O+EVfIdeZyKh# F_^Jgi(; gܼ \Exƌg)i4 /ms|Iկ~ŏ0HӲCu:n EYj+G-JžGzE8}ydGg?s%ןGpG0 ,Ǩ„%Y(Gg&(0;+zg&(DR(zx)BoFz(gtQXbBNc"8f&Bc†/J\ ?Vr"MVa̜ wJyRh!OY5m|0eJ!(+8̟ *bP|cu\@9<w6Η' cW[rg(Q¢EmES4_ Ƞn1g"ga w_6E qKcLm|͝poD6[V]RFBA.32•g _\-PcĐD9)3VS! 䅏h(i0!m;vD96ң[ t/?@C?X(H?}JR&3SP1p%d @MbDxB2tv8kC%2 ?Cc1\؉C&[ Oy1Z)7m<Ϊ?wzI!`B'+, k[@ k˺vY +tu躈(+XZ !N2}|Ν3ɝI2>{Ӟxh뻺܌pEE7A{6"?@F}ТE#3>"@#P ? Sn.BʅQdJ'j( eN:M-c8T QPƴWK;(Cx`x;vw[:b[ IC[`+JQ M܌h'DW7Wu=!0|tR_mm͡sGi45 [6eÖn[>d֖`b$v}ǔgKf@o8P[y.;ޢ)`dZXہ,ޖf+~n5Els4ަEb 6 B<[>h۞a,<#TjWb@&(4 S&΍4cF~qE=;3i rʽF$ܠ(g]{ݵq%p MiHHF49scWj\9CaGpGph=6Mn -̦֘/'} @xJ`{DhdZ~136EB\LӱPƺW1g.k IA y?KB\xr&+ M BЋ\"=VI# h!L~ܮ~T`r扐;se(7xA+Љ[ӤI['A>پ @C<-9NStq uJY%M!L{ E0_~iKŐ;?O6;FYhIgRV|H,vxva4E | '҆q#'~[vģw GC9:? %#hc@ó9A<ć)+7ʄq1? l!*2-l#:Ǵd7<e_(sAФrSѾ|О(>q!0 g4iմ_U yN`:L: Z|/8"N3V #;2W̱Xꚰ^xa,!)?xrN2>Gs:'3LS| =?q'<6>.Ц}h1ch4?4'@9J2m<7aiS:C~~+^VG v,LKÊRxϩH_ 浅lv`ׅn rѷl1X;v~[b X~ }aī΂ẅ]; e(EaJЂ=5;ъgKѮΆ& lU)]Q֎ mLEjY-=3*fMƝ,L5CYsΉ 41fp1MԑO}S}o-Fƽ38#ʊ[uH4pGpZ0x#&,3@ AMdqCP2!N`<0P8"dI5đuVҀLI0ewկ~u1iE;c0 LI'h׽. /OB$XqH3J(\Ӳō4;%?H2Φ@]섦~P𱔦%-~ԉlXOڪ7ؑG줰-y%OǼ#2F|<T('5MD,,a1=m16Uء.P~E0G|&?m4.eR^Ž<M'İˎ'Bur3g2Ajj+ǝ_7Ƀv>ZlA| FQ` o#>,A0JA|t{~(< ϢAXbJRb+m ?"5uCP9?5$ŴkЂ,Hx[{*rPFHtC~Їr~ )=(xP,!CN9)qP( @#+`2]Hbg#?ofdaw>JBʔEp3^oǴă;"m < sp)S*KlZ>W ?CSR>W" m&v^pfD[Ҭik'Jb0EOMqJW|D(t\Ap eɢ#ڒ_sQWNaXlDM$aX4 @e)[=.1gJ~X0S 7?r>E; Foc*p`a0O`bLœ>mw 6XC-͡u)=67d3zB[PV.p##\LBuȰ0p`ѦнVddZVG,=*=mnpЀM~6 k†waKWyA\A銼ESиCݩj:Xm`-8#8#PU| `br &L<#RVj Mx3id;v!%u@ɤ1*"'cDVk ѓwt/J'^v BYDj˓35G(-sVH?!Dh V\q@qC莀 ZD4^>R w a3eKu@IDAT9!PFʍ7K D?3B܈?Bp89"N|L# 'MFF<5#6Z) x=0㉁p>B?E$mv QSN$P<,L} 9F>"~x!(e?މo@; cQɏxfA?n!Tý脟Y ÿ./$%~r"8g~ˎ&C|W$u'&SgF A^dTFsSbo0IL,DGOx5hWis8>ĞDTʍv6r'I[~ 'dR{& <86Q_~/| >묳"?5䍓 `@Zf:yqe-ޓgJ3K&/:=QH]/c/^8$ ^<6V_ l4mܽ ?ewm(,kŒ/;״Š a⠨XCG߸򐅱G`}jXꔰx呡c5ƶnjLh35.yAm:04kN l8KMpyщhy0`Ddˍ#8#8;Xe2ݡrX0^D $۵ÏU2[b"CD h2aDwѳ#|'|3"ݔF&Lb[.C2&ng $]]{x'n7S M:=D8?2>!)zdGyN^F؞e~(GxY_Wt?s(]K#! (ء} zP{|@\Ǎ"Bq _#>?$=4*iG~P k>& ɿrv2CM#ho`;=EAiJyѮ"xGϢdAp=JD{Lʕ# _66~THO4#T8<v"N|̂lLLd =,c|2w!vS!~vo҃z~]%]A9Ov3.RZ3Ct,['<E ("?u;n_)zSeDy!w3h18cxCrބRGF[E23 uE%㌁7C{ (ō:XD#M K:Ex7 aG_|LJ)/=ٗmocb`TcfuL ^V6+¢;~s9s!#hV1t- Mݟv\F Pb\(`(d<1e)4u>GG>' wbݡm|6 {1RSee=6GOa+z +EA:#8t" iXdB?&&&?dDPA؇UL a&+LVУBdѥ3B/O~(0< 0wpyG, /7/ǝqo Z$24dHIV8 Uč<3`L7p#OI; c'>e\U:VC7H~8t|)?,e/`x-}=oax3X'~ҝ?i'qRuPDBc":hw@Jv܈'p{Rgh;?v0whߢw~2 Oz*?#`GK{oYG eA/iܻ"0lѦ2lؔk\sֵˬOlVB|_Bu᫏61ٰAk[͝C]1nURF͆V7` @kuH_n/Xf7|XVF . !;3sZżGJJ8 `-8#X>c?P<>姌Qn{L1ON⾰ [{ؾv°qMP# aPDxOi;?)-g 1P 2id X3ӊ4 |̄ eML ;f|]&@a@U?1 xZ=р}ӄ|# {C҃Nũ4WnnR8>ra'6l8¸ɏ@%~  8rAx2+# _q_|@Uy̷yI]AA艀[qUEjO}#.W w;i/5"1tQ0?ifj? ߤI7R')1~0</)wDReB!2 ^Ia~'A%hiè'w]J//ШqLjzC;?<;t`7<>V :EK 8Z~|a2= i'S; (605 G/GGc /F_¿)|D/J(e-N!F?s,M8`=hL!<a$:EUIl/C8hR:GawW?̉ klKֵu['˄rяrer+Ea$ /&E0~j%\{t?lLfjGZ_T>̇A?&i]VawN,TРUZ,6 mCO7ڣrh:- KNc|C$z8#8!J"^ofoM&U8&>CKw*정0h7Ylg=||6Y>n^I#9@#ʗDwcn痧<񇡌 ׈\i7"Qo>k6z%P=dʇiA{|OQqZjkE-2ciO=O և* ~H3ٴw&zmW:ilYllIcg,"Lzv 4\ҲTOR j꣸j)wUZznDyjrKB#Ei{7z0NfOoulV#a׈nVrxy*=E]n)^!~U:¥~^ =KGʿ?#ʅ' :^(_7‎$N)J'<[ig:vOwc/ _-'|Wg?*#Q8_ ,~1>Ux'vL֟xI{nĤ W:|+oU>d/I'M2"7cd]vUy*e7eJ|*Jg|R|`إ|)cǟx_Y,^—g2cp*_U*/9LvdrYzOqАC}&oGi х{Тp/MG?#[)J1931̙ϘOh4>;F -SM)8#8#+` ز4T1')A V䧚l&r+y+JpG> W SOz,^czP٫̆Ž|;v }8/uO+}wfTVJɾ*?CoV*c˦>Lå*dxˍ7OXC4L˅K.u! ~ri ';'S4z*/<|?h1qWG6]ő6WG@ɳf[6,nRߩ쪹oOvJGV'{뇝˽J'OzWK{6V,.RWK) =o #8#8#0d'xw^(3뿜=vӰ巒JiTW4ݮ2[Τ{9ꙺ^ KOrj Ƒ6@[|Nʡ\RoV=׻S)oSl,|gSή}JR1#(zW[?92Ge.*@Ϧ۶u{'sϽMtTXÃu^Tv?K?zE9ڛB{k!9$흋;twG9)EZv\d8#8#8#8#8#8#YLJ)geqrjkvfl6LG{Kh\tS)GL!mn wY\ACpش?aBhok ߿!> 84t.?,4SJ7#8#8#8#8#8#8lC`RJѹsmiT;Ncn8b d?Z:b;6paX33Ca#Cܧo(p_Ch;D;ko aS,e(\G7!;7s]CS<GpGpGpGpGpGp&s7)JQ$کat;zBOoSr'6j_40G܎p;@cX3ѹׇ޵ ;eRKQ vgl&Ku$ æ;йXciF5c[Chi]-^5jO)(\֭[wm;Z ATr䴻{GZ[GU- ,[,\X#w6md;[¾.]Zd[___ckHgw6o/_>foGpG]~[Z+~׷ѷ8#lT8#8#0;Rt:T"@1 =!PY@(p}]sywpܖtX**k^hn [ 00[wBSF5,A~(I~!St΅g:*FtBT%i . C Epiu{]7>vB|K_ ~ǹ-^8\veя~8@<%*`瞰^{Mx>ύs{+Vُ 'pBꪫd͚5k³po79|(|籎Z׼&P+>??Jm/yK•W^9.g>4q#8# ؍цLF'9#8{$:#8#D& m 8,{kl\+p}nςNoDms 6s{x# ˖, sLx&pвӴ osڶ-,j7-wJ!]Zw.խ ;Y&k6pU:®U nyM=axݖaaŝ m9JpTB|\pAx_"|f袋ƍ.^:í*O?'xb{÷pG-̙S.xvaqUn"clcG S9S󕗿ȷys#0̖vzs:xpم@D@vSMDGWwo w}ma岶Ҏmjjim7 mk-̀u`3:ZLa:ܻ>kpEIry~X0PT;aWQ-}w#4̳wSGx_;\PnSZ}8t?psÎv0x0쳸rѣc(D(8}{^Fw]O9;o~۰\YϊqVח(g_F/{©/zыE ߅V1ھݼ S#?7=}o}[?җ4{, O-O~qwjYvܠh,7:1y1(D:(g#8#A1~fpqTV\&[9 C@[;yf&dyskO9<ݓKiwligK>#f*kf1*v4f!{ڥEiJ0Rk=Օ{/_l%䲧7C ˢA:Ζcx;ZCsrlh8 TS3b~m}[ނ"Ύйrܗ^zix]B;;1VvuuO|k׮L`s-T,__>IO ÛpFɮfE<FGΛzawhnP)DxjwSJzm}Oqp/,wuW)_|q~zxWVX8#8 P2bxM`&S[ion\$%rSOH/{=e _8%mѓ7ZNwk{|fӨг\8=eSartjO_Q_Z.ĭo=A6!Ogϙ|gMC:Z \;}䍽خ lxShk}9&<]j/$Z-LTʷг\8=eSaS\e{jf+ .>kWyT q[O7SGْOן#4 ڱ*)LŝU4ө;$o KS3ڰFD!5Q0e٧?-<MO:vgSg$0ٓ-;6(Djahh'l</X=vD|{SREϵ^8vK ;,u{uE%89?(~wTU*7\{M&wñ~{8Srݺu%%wv_v^M ׿𶷽mR[n) zJpe Gb3SC-jrJԯ;#8Ӂ",ʫ\e9#΁A _Á>0{PӴsD9^v]<4` +|Xk(Rх'/R8>,"E6 ggƽ!jwG`OC@uDMY?{eXs9wtz& 0P s +Z>`g~̆޿gR2/GpݗiIl _6m#&.н7\uoC@!<bN[!l:hw79,4;KgG{qf1802&O;vp`ktŕlhO;1á EeS@َ5hǦ a咽Kqxl*rLGrDrd(Q>CS])Pj9W68 `'E-aN֠DAO:>Uc1`^{MG<"q'JuZOM `|1(w>(co~x(;rwј8#Z$==ۢBdJa8&a66ؒOZ׆v"HvXc~;’%K~ϏqZ}⌏y3BXhQ\p E{eN?D 'e՞KHWdI?t7j Kzo[[,*l9c'[nGu+Kdہx73Sz)ڲeK;#V j)cnP #AB?\9!yQa tNuk ; bK,Q#UƤώ1 PypxYQ\@;6Nߨ'^g'b)3}<pF!0)TYO#} 6Ow ˠu`$t+*:rh9l3Aٶ~SM[^h H|-*dT3 F h+̱_[T4654+(0G}tYo:D[+ KKaA~%.WSq+pw XT2R>򑏜0=7^rˆsT:| dU9NH巿G?QN\q8#.AmjRCgB;JEoq^y[_|Z6asmu^@P?)\}Ձ?$9b;!PV(X>X0#\#ybӑ/)"n0?袎kD;79z9KLͧ%my~wxc)ٰcu5{G(la[hsNԪ"~(V tLRh[Â懹sHLjUZCUx?[ F=/-TAy%NM=T4S8v)"(,Sdc,-5KLO(`ZfM.`Ÿ(.S_di[a7ae|'v'? Nᕯ|e)_8e#u&;FiZj tK|Bj>0ZӥLe`nM:+F7)Tl L?/I+%G//?٥a? KUu(;g )|_7'rK=Y9͛ȣ v- #zOaOOGLJ):wz*)O&8CÔ&Cv_c愍[3~vXN}{tn<.ǎC[Ȏ5%:;mk ;NgTsƲiRR3 jQj136!P2و2b(t*ᩎW݁ 6/8Zql#_ה8"Y;?τO}SAGɓ8#8!V`WhlSeŇw[744`q!(瞨Jg<)(: ơ_,9ɎxӘoS:jh#/?(툋toʼn?'mTΦtiVOJ ч?6+wqG)h\?<-o Gyd'-pe]V3ᔗhiLīWlpJ++'pBۿNmn?¯qHO=?k2`.OO3q?]F~v  ʂQYmgᎢQz~^ġvD?v2gqpf/RLFQMi; Uihkۣ=6 0GS2+@ОoYClhhLJgy^lЇRt$net(8fT(C(D>5i?Gp]evi1Z[r!p/9rdǠoHc&(L's>v|@BR0#~"^ si(Y-MS-mdrotSyRWR_= Q|so??rgoQ*g/R|s6>rŰ3c[o5/ w_կ~q{\򗿌uzTc=6=W*w ˗SOSoT$ҫgWfgT3U6+҈zO~'i7e1,m8l_+JVWbS#rMMX>ۺ;>Ei?6"Cɉ JT`l%OdO⏟e2ie-߻Y & `vE ݹ-A3rD9`Z/OqGK\*`4wzVH:ZOyLn[vV&teKpʉ';D;'Շ6o.Emwg;G{bma%a>E}a 8J N3%:ftƿ ASk5CuJaۉ/5ܓp}x0.6C4fvX !GnLn;c*9Y9]PFrN>gvE-5+W,Y@8# Đ CZ-O3agЗr(FG-3xC:5dm Ii\hQ܁GF_)]3FQ|z/a3UtuuYqUo;=_ww]J=l9ֳw$,_zX%;utvxtX½o [rb_/L9j{Fu//O0`;Tme:䷞WM1jJё8a+*FczzYA.ZwqY).ahEiV]xq8S){&V EyԣUF =?яb/x c߾ʻGpG`wB,}4F%JToS8Q|8DFx@waA)^cQGw"wnݺ5eOxv q  M6b+]!7tSց0(k ÑmقKQ1v;w8AO~xj~@~d,Ȏǫ*v$ Gvq ;7G^>vڈ$>IQ nvfU˿u@2'3b=lKG CP~>~ߌG#Tc,~ĕƧPNoe_QާvZ8Uc(~k6 'P׎:ԧ>5oI>.QfCy*C])W?#b=I,MϙsUKxJ| gߓ? ?0Ps*Ow#ӈ|Wg,ydw&i+yUV~}¨Q()vp,ʕ.wuW h(CxCW@m7y?a1N-Q{?/#(-<"0S*u;rK=kRFY5#O~YgURvgsgrXs'GMRtTp&S >60Kvhxpְ}Gol6Aʺm#aa6*b2 { $~l22&CV41cf!& VKN:x=yO*6<(&Sb|+YzVi ]"yI'BVn-C=/󲂶)zA(!)U2/~xRzo|ѽ+N+#8lBq}=nrK9Ȃ(,8R ء/$£С]:D_?;r-W BX>O( -.{sH?5‡O fGE9b:%/yITL| %O~%?яFD+>'~zTpO )v \ݛZ}ء. ܉iO{ZbQЇtP.T?ׄs%y{ `Hp?{o9b(]}z6:( % _R, 3i QVx3Έ M 37˙Zx+ ;]zW#'gu@IDATuVzŸ%;|g+]{nh_sڟj~~6ympI)EN:[C- qM愇~MOkEL,*8FLccG2qT,&(ָ+ '}=K+۫(7qT~# o{/w^LMozSIըt+Ń/{i 7 s!t*v/<;}?bP5'ޕ0r{GpG`20>DwvDAЋa# Q bbΣC )xR=};P 짏',i0Q(]Kh^ZM܈: K6&>GD!,>4v>Ї2B1DP@{QYD(U9.+J/hCB|d' ďC~Tf|' b>dTЎx:(,v|?P降Oiv:xS_P|f ;0w>8=ؔ1=+VV*l' t'qGϢeZ/Q( ߓ/Ѥ4xH#ΫMVO@RXtwIP# yM>Y`B7pC-JhkhØ|_S4[fJP|$=BJH0mX㎒~ժUv\ik៶M]7('<᠝pR( &~Yg'?~<,P,s/z&Ҏ7άLuQhN;tæ;l&O}SÐM@_h[n;DQvΙZY[idEL@bIQ&&a7)j)V,@[&Ua`ŦJV{4qn I>=/dSAַu BT f+_q?8eA*FM>+8|K_EQ*jej[6L+&e6825yMxSR) wGpfC' g ŽP,*¼-os2`8;&(L,p$]dp(MPC3ό'ҧTAFXG\,BAPS#?>*UI4ٙR%\vekvuuuEwJܣXb"i(@^חMA51 Xp !GQDZ<XR@ .ЇR1(PeQ@9,~\ƍ,XtlQ2S6RB&MG)E)n P2%i|A(gtChGQ0(.¨t\PvgKIق?%)kX_(Ǘ =ʝ>r5b<Hx]Zp,@yG~B'K扴=7RK޸x"- i |R~d6`Na(/ғw׿ulxE,hWr#r.k_Qfs3 j9.w#?L*Cߓͳfғ8#yp#8LB~B((P*"8) sL|Gɉ_`(6ySJQTă1c ر J; . *F8JEGCG|C@aqd%,BѠřC?#}`kUzULj~`N9r=GOӨ\fyͣ/BB~2*S}{fA>WG}@ȝiQDcJȿs5)WxAMeFm5O%vːO ni{ R]P}^l|"x?[ā~c{d9|yX:A v,C0د kTk ]-C̓_n *ܔhǎ0?`:BUNy1]dddZ(C) SɯQ^Z >&h5j;2& a/sC)]Q֎ ]alx2(8AĚ 6VNAɊ0,_׆s9'NtIj&M`D\+'vpy Awg iQSnGpG28>;L@9(7Ea.( (P]A@A8z\!Ec!¥ň hb;1PB/a, b(jK\Ђa'cvh1PJ 3ިDq@tB#XrX Jpc)drGA;O~ Þ cұaj x|`(/3'(Xl#k`PCe"+&?eGQY;]F ?@3O^ė/{.E=I;_=\U?q'-ڮn`Wo>%-JA*(mi ǓJZRW+_jq3hkdPZ`Z(#ʀ6L|Sʋ2^VP{O,a'4l1i^)0P2~>S`zӺ~Xgw~q?,}s~ENt]`WN洷Ynk]شa]رmM0CRAh`_DwFiڼ+}Ҩ>ۈE0%%\J>P:@g̊I_/S?3 ) 6ޗޱ?vD_H|"aLO~P]b';"ZT;[v ‡UK}SsS{xF}V^f o"nr/2m )q1qB+$zk[klYR?bT#|sB=J{{]tQꪫb! ElaZCg(v3ngoz?[,qg8f1Z:B`l&(-ӵtK[X4DdhVQb'ٌ̨ %-T6't.;vZMWۄ{hihcF>[J,*EMi&^æ(pd #)4~ 9"t=9aSm7ƎJd oºz;3L)pxE=DeuER3pP=suh=_GpYC$H?`;j&/,Fc4!ZD[Yb͝kbU])vw*<ɔK¼EáO|MvXn}8|ѡ{V=h*b*G'a_/ ؿ;m5:$}q/.^hN.QbY^dTVN#ЪgȤw+µz@ pGp"I?FtE%SO )qw]jvCv hC<*̝37.#~NC(1Q뿢И;>OG?J ;D…,\#MF磂'=z׻Na9iMRч{#h]ā!,v[1/`Aė?e Bʴwqs?>F$(]wbX΋ iwʅ](0(2| $\"4(5kք}CQD\ |e4<b . \z饱~ ܨ/U(r)(>F;G/u<T`z=J:*V"KkDV? :.N1[ISi.%ٍ;f-2f-(ǖע;]3,zO|3c[@MX 4Wϑ_@+񫽇ō?->G؂=|ݥŧD1YCHOx?[xUi{"kUjgrYCR#Z|R?7+EuBt JQ.}79G K!޺u[]lܼ- *)N  &̰k`#z9wdv6E6n?2;ZX^9vlѐNj*;u 01ZZvUny6 .YԨ cwdPpsw*pG`f rbci94sbM(81Z:ciAX?=B.Pm(2e;͎>˓p"\G(6`GaQUu8Qv.SE8QRlICz`cTjI}! ƢGB ?v /$ű! x!?;beYǗR6SP #>](ĥ4KzOi#F;e-L dV?oҕ .BʋYebcWWW Ϝ#%о4~=D'i|A 7t'ʓY-h7p0w*G0QGdo&>&>& c|Sj,(9Q~ajժ(' Pfp$qgwxq?&y'=E;x Ov(wEs,<2RUZ"N^WbwɦO!5=OOL@ >ЅaKƏ;Z@C>9m8&0(69z0/? ІN:Pċ %I=Q<3p`/KQb:#~e0 &(GڐjEuSUH\ Cxxg t^S/|}Z/[;Ń)wgtTTC}'BotSyMv[? sLY 6 .Q05kĸ*ѝ7_ǴﴣCM;-ǰ啯|e,vְ8dSz3}S? XM'=t?("ڃZc"/G})El {BWT1]:FдQZ 3nιQRUoWE'2HX oyQ,%(^N7D1/x "R\m>묳"+~[:?mh9:v~v_׆ﯿ,[C0o\9Q5.N+ǸܔQa;V'-yRx졏 yx`N <|M -h8O5SsR_=9ω}fRpSJQ?ϤV{+:/ҸgZ[&(B4?߹OGpG@ocL쮪:[&{B "QDAjkEEPi"jֺҺ`ݰVb]ڿU(BDd!dO&}{>y{3o&3IrN{se:Ihe&g D%E<[opU%c jIKL#m*Kn~Fc5HNE ɩf e\sMphkDtsYHN>7;}V})d]-[#/ؒv2ˏ˩U?RL͏6R/kGae|HkV; 'ynORֿLZaV\R_+'1p2]d"GgV{X}N1:v=X;`6xdq91Ҹcrkze`֮gRF|i$>/p=ApR[11u,q=/c ]}%8 r,Tsii"K:HKUR:wTP,xcPi1i.vBtin7KI'%{̆i!zt`i?gqL^rrI ᖇ-B;^pl;Z “uQ6}-i'Z3eRo~q??XXx?O<7l9;xX|պ%DҏvgVZ^'՗mt> .҃bh>&/HѱsDs148!R-:#8#0E 3}.Mv.wS][tҵ X{8k 61>&XV\- 빑A=GAC8p ?<'DIfi,c 4Պ-mrkꥵj7]q^gqlkś>ٷrOgzGgz?c.Bl} s!A?4!A?^bwdGIq:-m[Kk31}loy؎&~AUd}2,DYWz!(nudo$3kf͚Zs=y2|LWR:MⴄYZ t9}>g 6H,y0TYXkpi#iͫ^~rYb,]ԟ5N x>b} fo鲭ginzq:[o;Z>cף?g+ogW?Xxm2}~0Z:mccxҙZ>㍷*Xsƍ+W 39L b݇ke>JEXPa ]d֪.tUc'YC·H<s8ױ~0X9Ҟ'ctϷ#("EY e &>WrR"cT\+JBr0V.wGpGpC /n"q^y T^H[NV]#0~T^^$~|0?6r97/p&E5b7>A}K3qm 4rp3UBg.v"6îten&.u`-TfD knِ&Ju$#8#8"nff&ʰrlkddX#(L$O7HGO?]Ot]FK>9(rn& ]c5gd#I|xz8hd:ke_/#8EaR4.5={j*ioo|:/:QVvinxMv8zEH <@a Psmܨ {d]_P.1Zyk6e9uH*7Wױ|ֆ0Y[!ReZutm"Ցj8#8#p#PCpopG9;({#8#8H(x(4Zi*hܔ,Lg[K7!|[H޳)v'RˏUR(;$MsppH)GHKf2{:۫EFwW8#8#p#`pք䱅Ov˛v^GpG\V &pGpG#0!RhSi]~ssTjb/%m:zvn?,e'@KOVPmW5;LJ⮦loHGrlۢ"pYbIs!,qGpGpGpGpGpG8,wXBQ5=Ez?ieɨb. } =.V=7ڜ9[t PjKw~+d{ J]HjՓ^v궵ԅnPAIQ]}jpgwȎ(-VkU5<Ì'AXfJWrn_Kwww}J_u\+wgglܸQeٲvZm>Zy&i&پ}|ɒ ZCَ;ȢE裏jryғ4G}T:::˖… GM?HΝ;C6&s1bD<iʕ2w܆m )D#8#8#8#8#8#L!"EcM<Cv$e+mMOl8%Bu-q0.,hQM/KJC+ ?e9Қ-B%*kT&/?Qjg\&PKmLJk6/`ЕBUBVIό(A[-.ed˽ߒ[Q,&%PuqR#gĪZdRswۤY7Y/˭*g}~H/|A7?|ߔ~^xa `!͛Jǻ.G]prQG?Ѓ{GN;4yӞ&5@ 6ȋ~Er]>tD8|A+y677WU8X}{~+SH-oGp>-##8#8#8#84"E7P]sӶN9zT,(R~ԔsP!%LC5Muy,Z ZRIQ-uH;eX>]-/FJw)٢igAV>5}ثD쀔-5Eǟߑm^(%-mYԺ-'aY2Ҕۊh D|=|3@# Dʍ7(ws9Gn9$d+^ yBYTBҒ[V~3)K,nIn@{R+ða<Ġ,_=yurqKK/S_.\N?yvJyұ+V8VZUwn|;dݦV-8q[)ZV2V%%]-&)Z;#6Xg(Q[$t,9ubY{ɪs%k^Yw˚-]p<^)%[7͛C=ܰqWhw=]cC"yr-?q'O$wC>b99|'TsZS!O*/v\{!?@@\q}gO?=hx[W5B";뮻.}[S_a?h<8#8#8#8#8#8F`BT?8X]m6iV夽%%J2$mڜ֬kܶgO~yCc[dWKL}nuKA RJWUINuZ򛯮q͕LvJZ-MZnҴ &H~qҴiY^_/%ke`Zlp^J??X"yWcy5T]^ja Y:QbKH,DrM @صzֳ?yR_H㗾Um?X~s Xq4/| ׾jxv b9'> 1c ,u` C%\$eݺu! &i#8#8#8#8#8#83 ϝ5=T_qo۔5S2 7NaΩ+23AM4T\I0ܼGԣmle}I(~M04-s2TmA+ pښ,?-yMOQ׾+{:ea,)aΑ)+GX;"moo%+DH <\ò~zC ײD!fc~_|qlҗ$|;֗KΝ;k)-&/FV|>/fU:Mf[GpC~]uҳ4eHgju[:sEC^SGpGpGpGp1aq  cR:6cUY1v5@*υ? Cʄ}e-g5u?r[w%!EfYE٬x&=;I_Vٵm@.ۥ{kZN:ariH=-j5Պ4Sͼ,X*|+WYJIcIQbŊ/Xū*!B!^zDXMBBڥMPrJq[OpkV+W/aFBNT 8!hd*"/Yvvww+uJ]D5kd@rs1aWjɦMuO9j񴹚wGpGpGpGpGpG!L5=iC6;!(VѤ5_ʨhY-DK28THt$%=%A)u+)I{B{t./RZur4Vj%ejkR2K/Œ-(JYQ mR ׄj\zf=}y'\3 d{Ehno*kB._|JPt H`.5zb+&i3φ u{]"ezci{W 047ͱNwGpGpGpGpGpG`& 0Lhu:@>J)ѕS|%yeJbeIJ4Ԓ84(:WZEh>XxU=Q_}Ξ.Zrv? YUU:XwR9(GVP6jUizZ=2нEZ;Œ))6Fs:^kL bD(r\k>Ϯ {׎JB6h?eMRGpGpGpGpGpGp&DNc;RTf%E԰p]ů~S]E*[ݒi%M{{{o@N>dW-Y0]Ȣl۲E:u=5k%wwIYh)2o}RB4&9G#D+qYKفAٽqtlzD;me5j)$)*F&IYOGYΞ={: esϭfOM2Ыm:Um[C4HMիTU;`csGs;Kas70/#H^?OsΩ!z>>y?V:#8#8#8#8#8#8HXsNy^J࠺-E U/yԧJ6DZ[״CJx \>GztZqמ.Ypt(HkKKhZ{kVښӪKǶi=RTQ3FHfUl*\]4կ[H9z`Ң`x@y31B!ƍGM6@IDAT+:'y,XtSN9%LX_4d_WxEy?FՈiV g>#_~ywGpGpGpGpGpG8\)z4~d;0P%:ӺvhveA{JNҪ#Ϡrʁ*)*JV(̞eNRA%TRrT!YWb} -=. 5I&= J*qJgd,3X*Hڒij_FK^IK;ץB(/D zl7iCxLpb7<ĝ?Z*}[ouݲsHџ':KlщT׷|+CVwwիJ-^xa8#8#8#8#8#8Q[X-S]>hFQNQM}:WSsP4bwglU"t=+{fUAd!D:6ndI$VZ3*RIMo_-S+k+٦)߮=;sNSbtܴ~+_ _oI@bAlb/*_z8Xn~TwGˉ'Tw3B9V7pC;8"QGydTBFTs~ HmhO8#8#8#8#8#8,n)ZQM@47jTIQ +VH>WWiijUmkBQJt(G`پtIgKe!I7|,ecTw FcԠ Y.`Y9EnZPCJN]Uզɳ>{@a/9u];;ʗ\r~nlCy^&]w|ߖ˿wJNgꫯɟɄ VY^{k2/xy-f)җtCzN+. TehhHp5G>j_r!Hc! kZH]GpGpGpGpGpGpu&DNc:cBb՟rjhYIƼmiH sp$[,bV\6/-ҔtJ'$9-Oկn&ĴM=WO5OU׻w;@耔[4|,)sJJo_QOX3Tlhkp>_/^z`xb9S嗿e !wcvRG2|@"k׮\ذկp{E/zQe=y_ǵ"7)ZOnVYf|ӟ&;WK ׊5{#8#8#8#8#8#8SH)njRgdVZ6ee=[ނ !WR֠e,0AUQa|c__R m:kBi%FMJCJeJ.iz|95TfVd8ML!eT?-KZ.:zttM=2_ 7=K]< nt&\.X~"IZ.,3ΝܷB<!ztŊtRMMJZB=?O ',3@A@ҮaޚgϖnMLbs@NT:::ڦvww7~…uӎ=pGpGpGpGpGpHQ|BuqY LE17-/ϖn;iMJ)tRrTX3A˓OY/s4dQ 0닶U]!w,YC MP]JSHtu?&Y!E5J󗮖lsVv #CT:tQM)zmWj29ylڴIlb!`v'1$+]G&jҐ:g͚%s-ۄ Ǫ?q> ]]]Xnw*;9n͵y#8#8#8#8#8#8 S]0iiˊǶImҫrb=-7KIM)fFd,I9U>ɫgK_^.kW#s3e͓WKJa@+ݼY7IGV RؾUJXBZ%#~i%"dayH窻B+Vd٩$b\i]kJ[*Z`P'W܄ I&$pGpGpGpGpGpG`):kz(JVjX.@wԵ?ՍmZP&\buc^TKQRŕnv9zb]_dw~LFԖ kCJJtHuĖj4;gqj=jU9΢-c/y_(s:FZJNP|+`åꕵJq:׾5]/qku O-;#8#8#8#8#8#pX#0!RF$4.%l^槖h`=mqxB]O23%$beTbYR|Vtk&b2DjEH5bt8RIɶ'_&R6uT+Q1-㷼-š㑋.hHѭ[W\1ȺuE/zѸpd~N 1O8#8#8#8#8#8G""EbM?cMXj"J9ʆG7kVYuQLnwsVʒ_#˶ɞGd#:vz9[CRƙV-J+X)[%O(Kϓo[o(TUvZ/~ \òQ!\' %uqGpE 7d裏ʶ%eRe'΀ [Z0T"1ԛ4>RY䵦|NZ j6ge˥4}@AY^%W/⠤51K:9|^#lWZX֬Y#]{2jU/?90ϥɻߍGpGpiDaRĤ"4E۷4݆.juYbM t2 %{{Cw骮[YR4֝iDR[*Meۤyqқ>NVijiBw Dg]AŰjBׯ dUIaBF Z~Tަ&9餓&w2Ap:9U^Gp@fzwWQ7<_ +V鐞Ť$N3)$,oYG>Ŗ7٣}3tba\Чݯ?s`ʟKs~#8#8FaR4&;;BݳIi&a(?X 诬d(&sΑ֖pȀ>KNq}-_mEZtin̟VW+o2B.`LIzd=pGplؽSCl2YhQ*mf) ZЉfRVR3-Uyjtv!iٗ,M<2}{L!z-cxqtl޼Yylm̍ ?ÃDOCŒuLsKM{qGpGp&1jw쏡nэwu23؏:* Utmіa;+TOLrGpG`SzPBonnVg hK{Npյa'N.S^$`sd2}tvY0o̚ժXQ~%cp$bqk"s<t"$FbG#8#8CaR4&Xӓ3gl~$N&Op+EN"*'D#0|pGpzHdɒ7yu[&İʠc -̶+Y^| d:guuVIZf'ɼ>Lבsy?2;ț 7 ,&~XįvuGpGpf* ڡľqnpH,t}#8#,6o"˖.+WW vG` 0d2g =D=qX'b:#8#4L2``ᗔ8M2@cGbwGpG` CZu]PX&5KOT])֣-CtP!i4i6]^Tt 5:e]b,v@E|.[] 6:*!als㣝7+B]^b ߁ݟs`KR~:#8#8 IѸ~CCCbj:?OGpGC~ϟyzWҰhF]luE˚?k)d')Ϋ>͡E%<ːipb45ms(PJ{}wӯ6#8ed^;>:poSo~g}x%׎D\~د_Y߰#8#8#ph#0\sƍ=={v؏L6,nܫy!=ue0#8#8 `V?ɴ6kqlz+j )- FS\~)zLzͯW&!-Cpu#FS0*Cf#%_DZ'G;~@dSJa#8#8@ä(3Mٷm8dkzbݍ[6Rff`W;ՠc P##8#!a?lB! ?c0,I+1=?TWth,`*.!nke 簺6"^BBOJ|XᶟPFke3K{ò9`daK᪴|#8#8HѶ69Bs\OĺI?| s9y(jJbtGh<pGp 60ҍ|4bSu9!"ݗgV&i+XRg"ž}6Xqk[NK_K^3R9SI4vއ4<^@YGpGp@ä(!o4(oi&^ U07m[94Yϥtm.#D{/bgp?W,^ӳ/Blx2$jY]i|rxZ Zk(wGpG@\w4ߛx IwH5l]Bd_+okz(@ W\T厀#8#8# qY AV8zA)XttZ۪tuu+^Qş5?R2Ih>'hq_ҹ6aibalJ7%FՍa Ѧ̐4RߺN֞IJV{PqWL:zh#8#؀둋PD`vESDDM}xVN#8#8H(uM (hܔ,Lg[K7!|[H޳)v'RˏUR(;$MsppH)GHKf2{:]Zhthq:#8#`+[$y`޽Cɖ-[dppPXᨣc=VϟL~Џe֭*˖-ڵKI3vڰEss󤖕T6J5_R۷>;%&ǣVm۶Ƀ>(l-_<\---0pGGGv}ǥXٝ5ɻfͪ{Yƶv̛<qP^Vg}q!O? D ۵2o<;9"_JxPܹsxZU02IzʕN"}\GpGp mkNx=onn_ K&t \T2pֳsleἬ,;^zj@eMzkJͿS> YUm[4\"QC%݇uuGp@nZs u7Q^җI2?O׾j2;3 )ay^ʾ馛v4jtx#8#8#p( 01FPh!\GD$'}}EL+HqsٌNTBOp($y3E} tT-2W*u⣟"UOBzڥV UACA.CoS?Cv+ǎNTG/rD>&NXU#7pC~{;#Qڌe%`}wS熲'^uUDPS>N09G?E.#],ْZ.Tw@9>rl0&KGdoEdI'i%K 50JJ@Q6Z]&ˊ'Ȗ{%{ܣgYMJFJYU4m9s49Iג?? h7pG,xz]U@5V<:E?/_HHQfЎַV /0Xx* " }sYbt\,q{oP׫Ɛ\/L ǕtL}?VO|#~ys'ZMw3} [$_R2EV\ݧ/u`4 e%gKe2;ҖR6@ 嗔-k} h JԳQ6l# =W>Urtߕ^}[i7pGHR*'o =?4b$* > G_Wմ’ ,6ⲧGX 3&$ G____H[PcybkF?aX+X^r%eXwݿۿh21-[E-k*{WyYd','bbs tSWTԛy =gǣ7Tqޟ$ؘր'>!v"Ν;y׉zok2/+hpg}GCm=W"SRjBŵJ/ڇPv\~cQzi|iz[>M֍{\2i2|&]LzܟXw-}/7]Kkksd9Chw{"~WoZk爕I{ >Hջۚ~v%JwGpGpuF 5^}MZc 5j9<0ҩ.sްY6Y(/A5+sqO 1JAXZv$LY۴Ce㣏ȱ+WGR)ZV2V%%]3nlM+RT :hRkL[˿M%./ָ3H=JjYѢGQ!URoKnyB (-+!b]!9>f[38;kXyGpOl5n|V=ď>B2 d$Љ'\믿^֭[YWIaFGS6X_=yOp#qzQG n9'WX}ַ5"n(!F#H5$B^łR?H0vp w]- ѕW^)K. !@!7M;Ӄe".>S&w 1 .~Yq`r oxC:5y[2м% 0%-nB*Q@6ŖB "n"lWxʗ k֬?? @]b?.=Xc}ӗe`5'LAW,`x5șgݏE4h 29 \+1\}_wЗ!D9k3MS6%6l!!m6_eε59(8'BB8#8#L=Kf2ȓ e<3s6vIϠ" rtNkŃ`r4a0 Z6pS/Ѻᎀ#8t ~g*lyi׽nVݘǠ3Ka ;9;XUg`a 8Abe*X^wuriUÖت0’čXČ?wcfŝ(8S X! fu]݊Nùa1a۷oě ~R.Ɔz g6l ~[QoIuwxP<"3bF]C$Brox%3sX:^B|L.40j]in)XѶF:߬78g,t’q8+ 7X=bǘ[~5`m,&>`ej-ʅ2Ky &bi~k[/YylyMF\W ܓ? n-S{ԵַWttk`b ҄٫ ba[lYHgvyRf`fX>X_)ҲfnV\b\{8w ̢G?y(dK|R.q P2ڄ:+bٰaC8fIxwQXM$N><\x<fpGI0]%`1F$&D=óko f6rՈ6`=XF&.]uU5)BKڒ7G}y@pka&杆wAs]yC8d!$%^s㾕XD1qcúvmdaОsԉsF{ϲzyd u`;ֹZ/M{(WU3&:Ἅs;6xl(1B `n*"_~+_ Vg kB56#D?T iVuFV/,G|ɡ7 Ua^֯ ~ӟ4\\OySBuEn喰om;v! ˷8^48"09 w{2+r)T݃WQT,0:2iރ3}Hq3ဴ|?gB./6BƵiM0T2zV jD> 8#8#L`\f|0UR&4ܵˎ䰡*1dtd@ǣJ:22G-2óԳuk{uo]doK~r] W^ˇG# :P-G LidMgrJeδϗBu,\l[^LUa zj 3 r،ap#Rf=3pɌtfC4nݺ5|L 6OZB9ۿ#Plv1 3 +0RO8Q#3Gy`#8#p`~2^g=S%,4J& y7 n_! H~#Nq,`: |K^㊒3H8`n$xGze)u1b;aHɷm;ݚ@FjװV; B? a'~ZB^܁Ho:H_k鱼FGTfD(iŻ-m$֠qV cɍ OLdIL%[9AXұǬopwyqABΛN >mit)z;p^Fmm};nTy`0µj`a .5/(?k:@3Ao~sÄ 5eUͲR}[GpGpGpC`B(/|"DO &4X{e萚ٖ5GKUSӑ9RVRt,.nԀZj=M-Cjwqث_j꫃[#Me8dPQb${s` &t!= V0c}`HCTf2 EzAMv!. Va1^mА $ uf+䠘pG8X@=X%a Zg{gQ=&Ayyz0M2 mO8_Y{c&@h܆l`0r6Nolkpms>?aV0aN K#`g}eQ8MDęW} xc i!jX;_ꐾ)בvA^tC\qluĺT3>t} jɱK/C:x_g===ocAu* ֘F~Z-{3-cz[C`CH;.yt+}ϒ=\;ɻYjw|qj0{5?J#Ť~EzP6u1[$<0F&{ґ(|r8W/^D_0&LހЏ Ij!7$\m^wmrFHllQ l!Qs?G;_̬5Cٷ>] 0І&f[<[f3}ԓY !bDmy,E~[\0:N(g¬bwaAHvGp|Xa0̚|IR5&T$s8JyFXX3F&0a$95XVLXO^>u6,b V+ywʴ_{^9$MeմkozupGpGpGPE`܎1k f?d}aRTJ6A_|eSUbM+iۣr '?'ujmi=`V m:ҩyYF`>i_2K;EZQ>)c!$b'J!UZJq;3(7MHwҹm,F-Eu+HQLPpfn7X0"A f'uTwA2cp[JDd &sZ6bG4iF uO}=#<(ÁØYd嵂fuLN72v5Bƃ|q;#83hg,V? c jhs@>\-؏;$#DY'f);Qe`|`U"zd7oě鈟[}u J?oܭ*_W[䤰(y u + ~L4lW|p& y  @uYnQ6u2>,aR5!s}idkyjFir5v[m\rl-y-?BV°1BLY>~dhBb3 U8>ޏˮ gS#h }vǎURJ;l)exab%yvf{1LG?5XXCp}`̺cEv|bL;b, [+YC-nI@۹"=IǘYsMq{* "&S08[gb HäØLI z=-mi~X>ֹ q2|8#8#T&D kzv¦WdG?"t颅򪗼PyS%Q"T-H#J5>4Wϑݨ@jv QK܁*1f9 (7W**zXW ,'HA׾Yf/j=!뭤tSuFbR2Z6S+}`֊#Љ V=륝p57]#8#0z  шd}4aPORwFNe O ƛx@ E Aw]-q@"pSG{\G\C2RGjzi07 ˃4:GGGGv1uC7kݖg4l켱腀y_C\p8/[K -}\y}Ý4S(zܻ#ǣ+W-^}A%[sb @o&ҿz"t"v.}b߸츞RTqE^򒗄Hm()׃};׆M0@T{q"d{Cr?"xbmO̎@IDAT[TBoE:kς!B&s.Fsm8__5g\>Fab!sBߌϕXy'1s!Ι$C{ߪ35IZdט|%=,L7FczMv?|8#8#4&DδF1P[blF, SrVmTNQ9Pяk/0{W];lTmR³KQ]T?f)e&ɄGUaA? E\*0U6em^|Co_tS׊k>¦B샜K~ЎY~_zu(8gl;T7(bjlGpG`&# `u׾V.ҋ` 0Y0 3 ᇅYrgCpslԁ:u|t馛j7Mtݢg|Tk]W~֞,I ^,Y!>XKQ #B=q_zX=h'$\fMp5/q-I!?By6 to j#̰ JqΗ}G?Q 2Vʠvv}pNo}kVcܐsˮ'p',q}Kip~\|ҏ&@z|oB?_,f\8xe"\̲Ϻ}sGmbK&X?U?,m^y)\sXslg$p|'/˖P$q0Zb |;L9┲oVַ{1] ~k9#ʇ'>5, X3Cx㍁%-5Unɤ/I#d]Dq5B˒~;}F/= OjB6.i_{[EmQ?c POE$V6U+LZJE{kNVkAc*5i%W:gf3D!Ɋa`l ˒o|aұe`3MGaP&^[,H\GpCH4ozӛVuL`bg?xIPu1i q,Xu`"7DyfFP Y/)'ԩ*/x B??Of qc %;<7dX3Rf\Zb};ALrRyz.>묳B/K kI0g47jTݽ*O{o퓎~ݥzCen)(aV ([D2UHӥuҹet)`ᩃcUSFζ.+A[rbE*Iw{*%MuaO^ziP#V !>>5'g,hg=Ya<?šZFp&8#w@Ʉ&{Y~b׼FpumcȲX/ fz GJ @7n y/P.1`Z',Y9 #l-,R/*O HZX|&Xz&Q- 7#,YZ>`P] ag?;N! `fk8G^R'֨Hcȏd1NI>~gI9Ɗ k5i# `ʱsKa -ns/MZ<?? qsBHϤekBL[}\>ׅMD:[2| _k?d"_vk 2|˂%_?[&\>}BSUW]cu;a[LE?o~Ga=|fHk .B؛zW_-˚<2Adq>h~L%%eZq V!( т-㥔\6/-ҔtJl'b1`Oأ逜0õI-Dۤ#~IT׻w*Y,OmoUTYR甈޾n"qQ -_HJdo֡Z/>|LlEd`Y6lPՓZR+,od` +PVy#8#0+㱄wN, ŖZ6؜ԁ ؝l2 bĠ1^G9VAe#x,=[,5ck8r Ǎ~N9h: Lר Z`]aya]WYd.0 ZPtPzذ!>z'W,=<ݝwc3aY{`hh,TkϺ0!~/<_־]+~{~vz6җ4e;ނQ6^&*{Cgl( {r c6 ;bZצ`ߋ hg"1أcmTb/BϟYkPZƧMVݱ+Vǂg}M [jc} e3G@@`&L}Ds&M=ĵfhKw>ʹ.аu?4Sٜf0hR!17Ffi굥si9v 뛅?~)x6nW^zFfvKbK.̓WԀKHcR="M4jBZ:4u2n)I])jT V,J w6!޼ev'3L6+cZy6eTֵ],>!W{S!,j+kuF\mj$o}B2a.[]v'7K6@K 8Zokd7'R.D'zDQK]{]6_^o38de}X>\먉^=s;c&Ot=Z}ëmW6c[=ϭ>x 7#9gK=_筧M5sV˽R@@+(ʘS5&Mg2.ˎ|ē{lLhW D-& YP4 ɰfjuZ׾J,7Ozv/E'~P2{O?z:LmqR5iALK]=-(jqNZcM$]٧j :V kĦ`MՊ}J>]Y,xiXwie+E8Z3X׾Hjm*aicKm˖-m;ւ ~uZƫM⏹fn  p Au_ t T>՞ZqmuSY<"Sʶki:3SY;@@Mh!MݩZqYG6m){vP> F%ZlB^Z9;uYu=" 75Es;dcK~h;:g$=+$v쐞 7-%KR0-SԂE:WeѺՠd-J=wh[薶Y "_˪j]U;DV;~Yj]vD7V 2  GfrH^:ъu_v]eXvTlK;\_展Sq/uɒm^llc+'g<]6   0 1=M#x--]>_ER¦dE{7::dт^_Ժbu-H$%ְx:6//;FJES4 FfmҬU$H?cueֱoyHktka6e˖ɚƟoWru׹,Xj[Yd #ntmx܏Pq׊2t#^11d9]<_S#?d95ex&&Kv@@$G k I,N:c}dзko*P/.eKX\/9 x?Sr٘4c*h׼v~Pǒ̓], z1'm{=%ip7,}ZkU)`o@|rYg1-Xaĺ  p k kG86 ;?v\3Zylz}Wwde5 y6hX t7[?\BXn}_;2pk{^/_-CC{njg 1rFi)uTڝn^]Us[e~,i'"׾Vĺݸq P[Ժ]nMPjo6J>CZУݐNdNWT3_$d4a{DBN;475s P{hבO[g@W6b۱~5sS/?/v'sTϳc[[~eSf<] s7  %dRV\ZFGi4pf8 fA(H݃^j`VMm דNJOW(XZ1ou-JL!# Ktly"6^ A?GܭsiϦ{V홵 NOTAà˓%Шlv@@hhD-c, :>!]!yw\54Ţc$9&YeR~kݨ LX``z%(:Ӿׯz~oo}d9 `8iz&i@@  lL;vKXp`]_`x2$,XiD'ǕV@xXBeN|>YxtuuiJ?EA%. m6!.*WV:)z&h@@ OI%ɸ7lݖh_Q`xF9  0{@ 1̓וY}]IAH=xdEH2}{]uW}s  '~t3 vf(*" G> 65{~ .)zEoZ^X(1E[]h41 v\#ބOo簁45@qO@@3E[{ϥݦHE‘ 69f?&tO-$ L@.o$n듗w,*8H}Yl@@@@@@^uE_U:h4ָ'E ǤCa INνgi.aIg< J= ,H2E-(xH):]w7ԧZ:hmh<"\~dI6q[ZW)6! R@@@@F SλhְҾqrTw,&ɖhS:ڕT*2C]PtgZY|4J&4)w2eɥ=UOɖdҸ}Ъ`lDqi}+w    4 ߏ OըK+(ڨ+XFPEv@v%KDtyX~Yۣ]ߊD"-it=Wթ|Əݰg8BeR,Zx9WKkktvZFim%J{ݻPNVf:>456U=;Y]@+?XDbBQ;_~}kXC@@@@`9{RޔGeU{V*uE7d}OqGÒڻݽQ6(&bl jALNW}˶mr͟_+?[ɤ\{X\ͺu'˚봞x똡.h&.H2?({:Y! ]VL:-^w#Yc"߾n^}{k7^~ǯ,Zmk?! F>+uΔ?'uYyu{o{<-[JnXr[po@*o?Ť}6@!ٺnC! FB3    rãyVQw{S*{O˒JcisNY+(:N[lnțq,aZ@Ԋ~(;>ʌ]C>*Ȑ3m_C-7mupO) BM:i7oᱟu K>emIx_El&kx}Uz%7O Z Q0;7dծ] ?&1sQ[~K~m6LBڒ\n~Ydy}w|'X  66%;˾t Kk,$owU. T     u hs<#; =+{{ZmmTWPQcJY?ۤ}2=F#_g G284 YrUWi]2E[tј jtkWvD) us"!F͟eV+ =og6>!.2DgϞNmXKL9 z-ߐ7_w{/v>ʽ?.3\ ۶2CwLyqVE׮~{ާEmサ|\fnJ3@GqdfZ[V/X,lg\_-GZr  V/mٺ od=y;yt@{nJ>_Lh e@@@@jF񚑠h"Mt۟рhJ^ܵO,xN)ѩZji1='𭚔@wmH4KP˰1j%9gmݘL;CE Mv.W f//I~y}ʯ~voo]v} ?"+V/m+ܧ~}OLG-%K7-_}Ε_w\wknt_u): U@?]lH/ ʀo'bVZWP{qJA@@@@^0h>ul b(2D,.:Ziٲ5RWPt"'. X֧,yٶQ}OugmىtcH?5`]'Z!2SG޼u񮨶Q봲B hZ׾r __nQN,%{rG6n;.\ZZ[(wnɊe)tu; ,Q4K-=      we$ad{N,dkE,?|kl{V9qjX,Q#-}If}(V[bV~[sŲ7n宮.>7}I%{nI[]Wnf;fm.-eG_J bzt:/^̝Wsο]o*uM[vuoMf_+y{=٧6&)@JE^H,+6{QY꽡~CV||s|[%oǗ>ǧ_.V||s|[%oǗ>ǧ_Q>~lr~5c15cSeh۬Eo=0YU"XH~Ϭh{- joڃmյn:nzY{ n=oXx |FR5)5i/Wpi's:_^{]P>wd.sscrgܾ2io/ӟPs9n =K鯓Oxݵa @b); [vmR?(PZg@߄' }2]e|<<<`c 7J~ungџ $8g}¦ZJ]AZ(h0P-=hshc9ि^M9ZW7iu]{u$m.:٬zmo?<@7l2?/ (j3m~^xJ?+Ay%l<<<@+lX޻vpm15|c'N,&ѹ\1.um[:F #dxxXҹnYd|ⓟ\O3Btw>7@n/]pN.ߕkd_n{C>kǏ<,/n*K]c7rYYr^so:vMyG3@וX.Cww|YϺsdˎ^Qu@07^ںFm__l{p}q/S4L`V~?!  ?`4u~?!\<<</* lH`+˲u m>RM "OCpeFd2~nے 9Shͺ͵>C`cm 3>s]T/'Z7z2iHJYX,a`+ڒƶ$s{wxTſ}v[xG?grcl{8M>=~ksz7'w{rnkR_d *@l0e uյփ۬l)>yy_aS~Co''y%^/߇>~Ћ> zzQ.oYiih n9ٳHb-n;u~ ݌o e]MM{d3eʺˬKAydӦM m6뛹y66٥}Я&NPXSC25:Yg+/Gv.w{cWe0A248`,K[[R::ƫz-@wO GkC xp@'/_|ܚ.ྦྷz5է    4FhJciNB(x\:4 ZцIIN9Kѓ\$Ѽ|lssuٟ jDtD:*;[5XCbgSlWyNũ8      0c 6}5-"hX==sU5+4)Q xWOCrE"@7\1'i/,L^̛@Bq4ZԈeTZ5@@@@@@ŢK6Yn\gI1;}?G;\Zhen|DChF4TH5(+EE9&EX־!zq`2{.WNjё!     4@AQԂXV찤[z%RS4Xl`xRԔء@@@@@@,PsPTv,:$ u[{uM n7 Άۿ0@@@@@@+Cԛ׫um,:{=vS?p@@@@@@=S4@yR)UѺ 8f?Ĵ_{G      0jfǔ[:%KHgg[i4Zf<='d[|QBḤ]4W^_yOyTbUD7MBr(TX$lu@@@@@@,PsPW~eٯۨy- iyvmײ7SsN-mOTT%/h=s]}6NWcun>#'eEπ\rF,;|[e7      +(:[8/+݋z5QdHDHgC`P jRi.-k}Ӝձu]44J!/v9      0uEcFu5qX?,0 ZC:WK[ k: f#Yn .c껀εm 潨 D^ܫmo.$t      4G4E<@XMģ2oUuP~y hjaFmwk]ɺ:6.uw斂[}HDbѰ['ΨD4:e``@o>ꨣ&t      pS6f?0Sq!)ÒLΡAO2~JN#x[\pfݜk -Hef5 jZ=x445PXh;;}O?]|Ahp      0}uEr9(%JGԓNJ=& \ ݐj! J-(/%YnE0¢fjPt`Nv)nNG 'w\D=eNEvF=       0 uE6d7篓a5?Ttt}2EmdKqEV4)ivJJ*v.]tgZYu0YɄc3;x\}$YG>)ohhH3a-g      0uE9!`> FN&dQZ(Khްb׷GlA㭍?)!UƏݰg0Be.)zRDQs/Y;::uLScڊbXJ6{jy.I&5FW+ckߊ]է      +(ڸ1%^Lk~Z&e@nݺD,Y"0m̃eG fu x]m?V~Q'tєk/K6ՐȺeu[uPO]^f &jPTwO44J}ܵkW_-7xؘ6g?rU f>#2gy׻%<@y{^p@@矗[oUr] \vmwmͲb vE]Tg v0&8'W^y͘gup r'       uE{׸H?Qs`m`eF xC i0&+y qZ$lFcUuABM:]|nMCօn.,!w('bL|=HPtZ7v_#w\pSyoo]W1Dl5]^zvK>򑏸} ڱʙg)7n;?iC?=kիWO1W֫}XT1-Ov[]v٘,g?a7=x<. .,W]/Kyk^S      uE}L~hmL -_EmQ+s \Mj=[;;;˷Pk5*ܴi6nMlVe=9\:7 d8=,'VZ$)Q *SԿ-倨-ZdŲB?1g͚%7,>Gԯ`A /_us{{#S Z{饗dѢEr[V9餓YA@@@@@U4 Xr[$s[EtX<&xT%3p˸̙3g]lrٴ;^׹M7ov﷬Z}n^{mw!_|,]e~[n"       @ ԕ)ifQqK8[uk٢}eZVv[V.i݊~M/iAQ *Z>מ5k۲DwޭBVWGnntr9;[4Hw`skRK?ZY 88w+#_~暚9x ,#     3M`#3MIܳ+1F"Eo,y7\6#O; ^-Wbl*cEr]Ghж]v{lvllQ Z8޸qbhs4ZxܹnzG??q]r-bR@@@@@@f+(:cj6wЉ\iXH6j|3?D-0gp[N[^֞p;]K>O,3__T{rvmN O}gQO-cM$"b5KTR,?ݥP;~/~Pre\ַn*K.=em uY+38m֕z饗P6X0ުو     4@9l-6P|ZfڈF#.#2ܶ%r)Мuku}\|Y{md(&IP=E\Bʤe =(ʒ$8/tsqKmoX2/X8Xҏ [>'|%!zw駟^\䦛nr٤~r嗗֦Kz7tz6׵np;      @3 4PTST˪ٲes,h`xF{N߱sunM'}~AM    ?Ҙi  EIheWD+jz4ӹ 4 נbe`܅C]\҂Ch2g"ٝҡS"R ,Zk&ou֬Ybӡe8 pCUwM@@@@@@ ZfʕՃe?T@@@@@hJ`g2:hN5m79&?jr,#kJN3]Ӻ-;2uk/ELޒm`J-;4ք!s,k      +( h3vFDygGMfkg^ e~R@@@@@@h@Aтvgjc,g5iP7դ\X'[n]9ci:i.BRgo]w ?rWȏ~r][d޼yc      puEJ3D-gX@J06Ŏk2tСTA4i8C-v1*g!&y`Upς!B7|;l1P $(ڻ袋ܾ{rc=&v?׼5n|wsOۿ/6m ekŗ^={mγqFF}       @ 6jLz_[K&b bl8fϒ,J˵LQ˸鸠hrԨgvzy[ Z]Q=GXCb)Z>b淿r뭷Jgg׿?_{9'8 p9|y衇KH˕W^)o~eawFˮX 뮻|˾}%Zq"     !PWPp+\2Ӳ<:{XYVuu;%nfR)Dj@0ɸh$vN}d8I-K4*Jcz!IkU;WXmnDEC p~k_G-('xVێ;8mj ~f=}snnV,-,s]֍ev@@@@@@~ og4bA\.&[456 gbQ}ѐ$tn!f4n"}aitu1VYf}饗njժx~3<*=36st.(:w\}?X鷾-[m\Qo\P      =+Y M[\V߶YpHi`3O>%P,T׊{neS)++d5:L7;j?T䴮t[W G}ԭvinw#'|om۶=G?zr绀s-@@@@@@+(:cj6vYGY---d6g?^~ .jQ 6Z@!jeݺurv=zK>˴|C%E[, *2]qy~krU~d7zzzCɖ-[\Ov[u7I[nf?ڷ1X sZDu      Phu7&`AX^Ti\nM Ic R託-HZcE=:׮Ekݺu T k{'r #XAXm׮]reP -oqAϳ>v˧?iٰa[/喛K]Ο?,Ѳ       @cm1=󭯿ܴp2ӹnYd|ⓟ\ϢZvHDb~_\,,]V ݺkrwM_^sn Zge, ,pwu]炥\V>5ĶZlC@@@@@UNJpS.`h4"6fVmK&SN9:\Y{s(fP=E z!Ie2yseEGuR jZqF 7o|+_)m_}eژ\sMm;嬳*o[bg?Z9#7tYfvV@@@@@@_h9#Rf^-b.`P|ٴiS-&V/yRO;A4) pjH3ò渵y ڇ.6ﺬT*%dRS= ox|SrQr?qDk]w%{uUg      @ 64(h$4KWGOeRfN6Ph.Ҁh^Fb]܅C@TDb"tTfwvKNK[7sOs2SXg-6jggj9f֬Yb@@@@@@` N~, ֝m ݰrXz̕]VԬАF5)^QosZE@CrsX֕mףBn;UѸ,!q -Z$6ZV)!     +PsP41W\<4%X֨yf<v*ˢ=fytPnuqw]eKӱ;mS5Pj١1HM&dNOz!     x'Q}IENDB`fastnetmon-1.1.3+dfsg/docs/images/fastnetmon_screen.png000066400000000000000000010346661313534057500232570ustar00rootroot00000000000000PNG  IHDRϤ|c iCCPICC ProfileHXSBJ RBo*w%@PEׂ(@E"*+bʛ$>77rs3wf;P*-ExXGU6'W $?עr@"!ps9ِkp<Ю?'O(᷐UDP Dd)[I}b| @٢4|NCBp;!s\ݐ'egφ@l]ș2NgY_fKvxzREQ>qɜ"aiG@V|ϕK^80v 0@%gz [$h8?/(fSDF\1N\% Td8#12|~\8d:QG>c>"qD䷩"(;/̒ÖjP왗(x cڸ<_?Ďj-fEc(8csb &q;8R{'̋iq |/`1)`6K@XZ"-xOH<;-m|h2n]-@5_ B5pwWOXmp'y,0TїH'@Yo.cMB7.Xb"lVbUX=u>Dp 8?X5x9^7gx>%s !@H#!J { G g##L1Dbq>q q;F"H$:ɜF IyBV~I5RYC!2r)yyXNQPE.B+7Onr}r%1ōCɠ,Q)g)(oȗ #L5PSԵjj. F3yҒhyZi#:nIsF5K9C/  * ())()V(S8PVPVZOse2Wynʽ ap{g}*Dc b**ʪvqsU+Tv313\<ļ4Akބ'\^mOHA'uz&T9g5&LtșX4{f|ݚW4Z[Nk h3=37iau6beXgXb]zzzSS7,03g(gdn{#cxFMFύՌ Lr3#23GʂLڳ 9.9sE!H<չ"6$wϯ0'nJss3z޳_9.Xg]E)/^oIK_fd[VhX 酢+]WXXmzEܢKVťşp\粟G֦X簮r=q` jJJ Jz7mlTY/ڕB"]Zּ`oVxW4lܶz*=+wh(i']Jww~'n_~ݫxjAuwMT͙Z}աuw=\oQP|_o 9~p#ێ25"қkoqm9oխU;A9ɂCm¶Sizg?pƙg:Άp^O^pzKN.;\nbphxӹkJ׉kN]~FЍ7ov݊uwwͺ^K=T|XHQտL}Ƿ{9/>ܷ)i3gmw1폾*#yue0a5oT{>9]E?|txSgs>>}15냑![Ėn0XT^W@K{xe/iAdgF)bMZ v pR !d*K1%7F O1##o E422}d(.m9s']B;oRlJS: pHYs%%IR$iTXtXML:com.adobe.xmp 1800 2880 1 ˔>@IDATx] `EL  \pq% *a8D #,r.] D# rQD "D@9&_=G3stWOZTWWzUׯ^߿ߒǏN:`C!`0 C!`0 C!`0J#FbpBk8;;4֕Չ!`0 C!`0 C!`00pY C!`0 C!`0 C !`U;PYU C!`0 C!`0 C!PΡ w2 C!`0 C!`0 @AL)# ͪ`0 C!`0 C!`0:eJY C!`0 C!`0 C ,HCj2 C!`0 C!`0 !^{?C!`0 C!`0 C!(#0)eY5 C!St4LZ C! L>+#`0 @)FXƪ`膀 O&ǽʓrqn,Lۦ[pBW$tn,*BPQ>3BZzF_x% MjëE;JV6ӦY8BI1߼YjO4kT U`/vt=ЏKcIusUd}zV*h291gtZL2_Ɩ#4K Zyt~M>j;NFcO&If 3LqyM0c]kIm)Sʬ(>K~'!+-|GO§9Es"a]`,,щGF$Hxw *q;;]@ׄ6nʡP?M# iK6z8S"SAA9/>=B;yI [HHt=sV/~ ̰Bp4<]]]1bBIf Iؽn!CYl^ ([0¥0:Ex/ىDHJ!jv4m~Q og4Da1R6&I#w;e)!rOmqӦ QًqʸjcWs8UFۮQ?qf"r>_ۃ1MVx7J~\8mL~'"CrbP\DU)O2TGV0 y,h9grL[u#N0'{k"4HZ;@Ϻ6SFMѢ~pS=qx!0Ye"pOD!\tغIT6ߐEvW0R; R P 1~^O|=(~l%qM[0$7#H iRF_*AY֮YǴ>ajhSUu%u@}~АGzO o&tDaUEw >jD *iC"zTuNY\o+Bh CnDjܟ"UPv|EPT uXFx~.<ە.g|>d@@P%R81.sC|}M~p%0.ЙЂS{;\\:w|Zʁpb.U CEœ+GwrʅʓcrLk.&2%P;@TE/bشE߀uǍn#mb/j/Ztd;P^+ÍGFJUyM,,H=䆴+/y'/YT2s\AQ*Q +zYluhO{:'(*%в#n6!>?(pP*ⱀX&ەAB{kٕrq4c{:=vimg2̑p,ۿ]>&w Hꄆ|t.!l﷈H;nLVI'eSqj,_[ҿÑF"Q̯<(]\]q1 R)jv" }grts?8 K7Gs$e+iUH7omȮ u*oCN]c ^(xZ'{wQbʒ/uE,Í9;xIL[2&ǴrA>SGE7FZE+s,u#M<o((ps+Sŗe*IT6~EsCC=DKsJ]f13!(o咰8p.\Υ%scll$ǁkf7q| Cv {=ˎ˟WtmOs* E?crS! QJ87C;˟\d.}w29ތq|x`ToR@R ۖ A3 O zգaݔ1hϘӧ@Ig0:~#)EV{Ѣ#.Na;j=tغ1|ÁD-򧌷8_}8wu\y'_ -p? ⽠] {z E<ͲK KcgoZT5 4 u2 wٱj~89[PEcpx*GFەs'hmi 1q`$AۢC{  k UTs32Y8z+UҌFOvK2Qld72EH-6XnC!d:$EEl|K~:Md|ÁeEwB@St4իR?m4$~q ^'!ݿfH "JNLfpB^ЬvUbgF_%Gd4tO4:Y?pZ$Isġ kRE#~T[ԮZXF0.N^+Yi ^Iގח́=:63/mEBz^'Os䏀gh1% Ke,:-+،tnL(e]p# kX]D b;ns_ 8XnM}ZՒg[%m%5r}xv16__!*e$_Z#Yk];׿k7b}(6ӇI9 ]ENȮOܿgNJȟeb3wvr8Ŀ#߱U_ZU%+RӁ=MF?FlcwTts@_Dn\zdĵ} 6'Upxʟ“MWylϝ":$"YxCCE- <\I[z89GޚТ/ފMf+1pr$R=~4j*auq,Y?t z$Ww'墜`* #i#ʓoჱh[ Yk^)Yйfկkn.Ew-4hj=-ҖUVPb[wj8:{؉+$$D8K~oIɟ #E"CF ڷ ;2*ςV۶7.ADeN^xǜ{@T<'%E> Ej3UR;ɟ?~̐>{.ȝ"rNa69f%/7("jo{(MYsݡUFqdz^29&]Pn dO#A3L蠾Ή4o:[w@ᅔ,XNNO)e/v}W:<( )B_ނPRpX7 EV8CBm#Ȅַsa"05r!᎒LoZ[g!ͦ"CM-h,{1N~VG3"Xvb8;F5֞U [Z峍J@_/J>'m U& r%7:QȟkLEs~c?%ɇѶvWC\›J9L\'p WGoNzSjvr #mRDLG%c޴3yw8?|k⯿CP2E75?t([ lL޽p7Y7unj?($ѼV{ѢCDs=xiV;VT0 +˃!T~n1VnK[K|fPt Qu[7v+再4!P&nn%^ QSf]* R#Xk?eEdWo>+~xQG8ɻUè\EWACQ%U';YX GT,sԂ2V.LLh`wKr#`zP @5ZcD42?c]V;9aDd0!ǐ62Pt $"6Qlƒ[ء' q"磽8 ?my.^IY3&Zr+W+Z'c9P0F[Gras)KjQ8 mL&j:^9}\ bK4MsNL8#xÚӈD ۉ|Af$>+9tWzPZ"iӺ :C~  Fc%=hb>ǾZw\KВ5~e-DNFX}}@qI[!#T\䏘=||u><dL%>0߰eܨbG:'ع4k`64C\\-9wgr=3GL@x V~'%CU>ӑ4x *Sk=N{DW.5/{3Ic:W˛{&8;:FT0,Fop1Ӱi ~\ S]\ Sn/W V;nrF7q,7(j zf6QGkD]ݗӆqAg:1Vu٪9\q'lWPq>Ne)"ū#MzYW7p,`hOte=~t[ce Z6 ,MUP ؞wP=Ƌ\R29^3ގkL,/"JH'zq V%<TdGZiJB2.ۦK\0-| F+|HYs=kFJ&Z5bTT"^`Jౙ۱jz[yqK ٞ+Ƥ'G6~O0(FH[d!OR,?N@_vMkNǷ Sڪܹ#^""[ v*D~/VMCn h'x1.^Npym1zyw͑o\zWe-7٦!me㮊0G+,ϴ/#[mre(o+)D {eI*?>~BܦAc G͟BϣLX0A|8l.2%]'Y*jzDZr?Πt~HsOMDjèI I y:7ܗd /Xj/ZtlgWqJ˷alg9elض-וܚv$oyGVc2"=FСMN@ @S&WXL" ]9QS,N~2KV ks6g3}\Mvkta'Xng'/xFT]'u1n{xr[EeT4-roIvQ;f~kD!nsڬU.p+"еFnF3@}CD\:1hYCS`톒̹+p`IT3.kz{NpGZ[i;$AièiH!Ju1PCˆmIX| 5cx`{CY9Pe*'5gĚSEA,A5f1gT A kH{yrP[7 9' "[iek2o;r-_Z=/* }+R ꑜ1%8^( WE^)c/ ;FTwՆSB%~e韈Nao@V.o?<-~PSܠiSBB '%i-:b,be=qb4+V.aJ%=3 VM-:ޔ圆 y/o8ZAWxG-+1ͼH q^V8 8'775bL]B":n"~} gaחk0BWڶ/b撦-y&v>?RJܔ&_[bIټԕSIEn-*ՃzVr|6f~;Yex?df'PM/=)qM }[}+KW5=GN݇6wxĕ~O^.oRld>An?\lBPf$o*N?q&V|}ϴH3ظ}u ꣝\X9Yav3gۘ0qWqpcr(8ʑ/wL{ MPk\䏇oq?G(ЭkD_VqW~٣n?Jʣ&/ wt=crLgq jQ=b^iCKG &yi W_)U+k{CnjKrKNl:j5/0to-dZt۸Ũ?l$2,N Vv\f 1[d)\}ɒZ$m ļiiXdUm1Ad[?p?|cejXYx!{|?]AUCR! z4i&)(4 8y%$f*LX2 qrh mJ>Wn#)llT~䙍!d]" 6w=gPXE5""RsS5`GH!t{Hzu]pvuh ߞ%}@$rJVMRP6 Pj!N:?1AiN|_jjkM`ΏM+ɸ&Ws~%C>=y2.c5}Ji1ޓT+Ku}b(";Z|\qGT鏽%f% 9* xgr(.Vg ?LɡEyl/BYqHS5qap>z) rhs?:Q2 )A}V[N*FƵEe~ŧ>L̍U~mJ\N]ov|$B k5 -E;pO_-ĂG[y^p6T'q!U Dui#2Nǩÿ-@c=~}Ҝ}굶(4gZ|IRI섾Gؔ_Wseuk䆼Jʪ"AYwK|ǒdu zm3˻+W֕,|ޓeZ:\4 QĒH`CB5w^rdjKܶtA}z/,X0F݇l}v.NtQU,WY?+q8qY҆^z*"면9^,.E5_3obSjÒwpT,<g!c[IҿȂdz$}ٱ>8r\o^f`u>Wib;œ3 i>۳} 09&˟㗴dF8Vf^ch+q -9Oi-]74NP:Ni/_-@1KR ZtQ9]U 2L % (ߨj/0O|>ҔP~Br ^>}-$罥?˶/d6LJ tR6UtRǠLN85 FݢmP2\p. wB)I^[_1X/ .E\R"L;';n47rO쎞==ҋgcH4|^? uhn'Obox`*I2 }xi֭7Nox̾evbG!\=- m/8ʺ{-]Z5U8:Us%ٌdރy'21qPd5Ϝ&A >c&!uD(fFwiH ㇎Ǐ;@|NP:%K<]3M%b3W6fc;wyo Wm/6Ƌ0`vB47g/;d?܂:?x}f82w:hB8FXT`>9|SUn^\zs8y/duhQ]е ,ůAClcǑ fhq?_S.!EG@-[0]vUá;#3 ~.BhhBshI+_K\IPZ"MmqcF{"TwnJc{ 퍅 c=fDs!m)+\o݈!-+ضEXh/ ܕ#Xݶp=H>p .`>IvEM1mh__@ס x:\h_>%: ߲"v߃qMDֳpm:#Yu o'.R|=ÄoS;:Pq=} ܠh!^#d*ג-`wQ|9. 'c14SlAUahp JOPZ:~?5%czcls7t}Z4(c{Hkwfͬ+eۢb}6yW6pxo7d=Q=A0 R!exS_I=Dcr.KMv_&Ǥ0SdUߥN܊!p^x=8s"""ˆl-b(+"^ 9Oe|>P:%K<\*@9ŧ:9J h~ɕof`"}1P{-:rQ{|[j*"'\Y\"`]kq"V(7b|1b ֦5CĘwJF߽⩌";nHeˇ%qOH" ]ÖS=^5X]u0BD+k%")+Edd"~r0bƽ:'Whkcȟ YC$R]ĭDY/K㹻Yǣ][\&cbojk&6ir\3(n-tNa3hǤlآH<:s7b 2{Mˌ! ş/D]/~o-o!/%9l W E'n Ž_ܐVM)|_~PF=u,][Eeo胔 xy{NϕPs_"vf}: xV;f@rWH,p4kO\sE]sOm>H)crLPJCI1psb׽1,h ]pkɼϕH 74 sYܠōnRn.Ky^-:t$]qjf S{I,cR6dE@+ N)ĵɸ_LJWc 3ƛ YB=b ! S@6#'(\>^<[|X9pǮuޏ=E@P%a̜#} }"*qVWї ߞ=DA;T'ܨd¯y⡊WQOk{3._Ҫ m &$ʏV9OjGZZVA*US$4%5TyC}0hq5ǡGQj LvwA;,Ѣ#Ph{nPQ lG;Z 5uRb@ݻ-8~8>+%ihٽ1d:v_t/Q2kBNh45B-aȹx/ћ:[rAE9*yTł-\vĕK7o^G3eGwR^{]?Xh:Փ y% *{X8g&&򮎈UeA|d>Ul?7UvuD P-*T@k,A6t-A|.S Ũ\*L| FF0GB39[`ɇڍIAgc{򛘜O]SLol''~ ?%Ol"x@*.FQ9lx?fg`E Fk[e!΀!'2J] C3si,>+v}!WF`f%y9& ZƏ96~`Ò(o]s97oÃmiQT嶄2(w{\r#[c؜n^%:X:P_zwBz :9 4K]6f yÌBܾmӫ_A(?+W< %u@Gdź7؉bX>`a@EbcUU$hT j ߧ4K[>"֩L|xg#J\]+'q&q;lWᵳ>;hQ0`bs,a@> hGՑ:SRax_8MpQp,F:8*Ug||m7j`!@xR+^faQs"v$[#q "lwT^VG+xA%/XJw@\shP#ta&^J./K& 8doG{Ocq}й066}%mEt,`0:"@OEzkX2<8ߤLez!.}@DžgG?\ۇrdOwx姤w?y0a2{4%6|^}2@^^Ȓ1tB`[+\>-x7[գaݔ1h.=#5^pc\ [Ӊp:^[}.}B!2EOт}-[yInK[{ q-:>$g7 C!K'b *>2e@%\8-^tHZ^]TW^xUL")I|:pUxq2bG\aL!'ALrdp5<[b1xHƌQ?"B3x"sP~l+Ta%ǎѶICT@)ogObCf^s>aЪeU>&'e =] s<ֳ.wV>rr׃ YɄի dvx5? j$m?3e8ԋ&?4BNmQjy\p)iۼK9eGI_HKޗG St4})Bovp[[3gOޫ{|q&{CDv:\Kc;0˱}X̱4 vb Ջf{ԙk>7O\Ώ8$=ɝ*c{odb;0|\m090q%  z@<4? bYFQ9D52Qs 닫dnMk JIČMDZ.\jTn{_/SZFhZSh_ޣݽ/,itUӮܩDn`[5ݤ9xK+sڬ\sOMyjcc,3ڣ ?͜?{2LNP4btV q$@+s"7;^vâe6'F!Th1!8Fpyd´䙈œ\'$áM+̵nHЮV~V#У2a x` 7njYY|*88tMXvaxzso:4tHZC[ʡx#>rL1u] EewkF=ArqqXYtnQMBtv-\pWƜ95MApb%c1&E)JUgPjrV}Ios 2|;!ӼWaxcJ4l]gUOn>CݽS?%鵗zr<>A.lѥƏIA(CSA5wǩ~7>sOԎ6z̯4o9O 榾njIR<7?~.F:2noYI?q{uV 8]̏ s>#52`cR_Vsz,uchos>&sڋeA%;l)MxA=1I7åݩdH֋lq.ȸ ~5~€Xի.߶yNTl36@h*Z~9nJ*Q0S> z$Ww'GpU Ap0\QV4flHsSO#ʋr;'-ߎak8Gx?}-"vߝbC-7G]4EktUSc\a>Bcf`Xԕu;9ىaFA0t'k.S Ŀ;+'PGT(T$gl'tp[.u>{)T~*׬A>njUq7gHѻ)WsB@4rGQ ItT2NH|*_AQ~tw8,?}(8 }p- /mFvz|!1uM*n' ^<9qlM>xjrpR\&˷RH?PJX pSn%(J)OYyHt6a$c|Ԏ;hDžAY/Dn"T#~;"T#,|8dN#[@\OhoXҟfkWoxfyj$]ֺϽqs\ƹ# @UTt;>eDn oq@os#G;{oSZeGNJ|\=;~!4oW CZPVj'vźujQWq<3݇BɩޛXY[Whߑ@UlA5Q[NqB;F@ݣx*z>TK:w4?DC؊~n1VnLߋߒ`ky }T)ĸ>=8L߬DdO'Ang|%~)QcK(_*y/bAT$SwB{%Ssq<\̏ yCxm\EGbLwc;mM3,w8rGky޼Z)/輧|Ǚl 2N==} WN!}ȅ*OtW@8k,@~ =~n 0*2Ea#ʬ=3CpC)|DF-Ʋ V c"(ĕ6ɸ)rtJ 3auhkؖg8IDZc5#O VnùǶ"~taH,7G{{bV5-|H-&[5 #*OFoF4l5ǹ(&YdaG9%v` ^XoGCn!I=N{Dr2lӆ`q2"2Vxb%_ۥzxI @Za͔c Sx_w\Bݍ,Y FnXֆ{'g {@gxdqW ?l]A#?hSR+Lt!7gaI'!WEA8Ӓy _|9a({#*f(Dw򎾛T7OWisJ>+Od 80iy{~̕k—;si<9-H5$5oW?T{ʷ:~\騈aGkwE&ZĞ~}=W"bY\ ͅ;a¤K0m= ه%2 ja|t8(gl@H1툦JB,ۦK\YY)aZ:$nf-SX&n-ZppΧ"? e‚ r9, jەb!6_p/Vs˼xSgׄ9A7>ԋ?V?Q_m!Jq0V8}6^lkS׮ׂM%7?fk] V]- kؖABwԤQHCxoy/辜6֟"ubÕO IꞅD#7+})%Qno,o#".93sz$PGH$u7 \o0 'DYOte֨$9ڶ-Od qqCyg")C:?jJ.WՔmA|u9sqF~1﵎7N@߷y+St<i )n͑Ī;ICH:hY邥ScxFy]?9|v$ȥkdC~^4z!QҒ4K/i"WiDCzPd'xxDI[Ѻ3cBF_uVC{GJ|jH[/XD@W Ez|_༟Lg5rPxoF= *rrE2MPf1T>>`䳝[׈E}GvoYc?Ӓ4{J"zNEbaӭP% %d-Fa|{~y]\\kRLҜ׉WEIhN1¥DyKICP6~eլ Sc-r5+9cE IN)hϕ11oZqɩCJܮ ×ɩk劢רV=5xQ4Ys<}?wp,6AIߢ`9wXȒ٤Q$MQhJdoBBw6 Hz>JuT| x8)ܔIq?`ņ[Gu?^ɂkG=>1[(t/65GEBⱟ}-jR"{ce YF|oR^mMGlw;Zӟ)C OGCU\SA0HL91||AE CX~g"}QMdx$ԅN Jܪ(X@`m/* zBG=Փ]/%֫r aƁd/_v-#pm|qQў/>.6/"fuHQeތ:SIV~eWmZIt;2rrr]¹S+-~hX9  E6W&$B?&?=6кݣ|+eK?>'ΛrUʹk'meFHni1%{)z`ҟpA.oճuZ(B@N4D|晾圖{L,Iݻ,!+I.Deޏ5DYX0q FMzMWw5/!}<aBfvB9]~ k>p@sIE+:?%67,_[G=>iq&"J ұjYae8'}_^;~@+!X&Fm}`TiKL#Jc_G{`9vMl,r/SOC1/Ϯ R-Hʍ32Q}uwaHݩGv֗4;%W|nS'* O~] ַ?/_4D@¯{t'D,b:XJSJ晔Q"`hK{*aʍ;HYv추Mُ(J+,ƍh h?\8X枅/-;v7CՃ-vN_"*A$|McߡȤ'|7հ|P˱QJ#I\)ihx\PlD_`-.zjCКIug{E~zl+& < *mi5isLᣌM}mG-Ouj9PΧe}=KG|:r4>)l) H-)uf`i6./сК }1cO._ĝBJǙ˂c^<"96 GQPcI,ԭ^8h'N[C--|ܮ0rʓB{q%$(B[59!{!S fcڨ^>祙A>7pNv72"L~f/nwYC$^5XkEYb\^ ?)HT?峍3@دg_h =?]k*D&iC KH!urr]޳)*fcՉWb皅wnQ]m(OIcprWe[4|^I#-a%f̙k"rp3X ^4Ku +!9!||8h9رw+iqt5# AmK=h7:"6{)l}?h+\lRBoߏ?nzc&w3M5O'Lxa< Ʀ1hjwIýb 'V.؂Jm`תQ0:|խ y_>qJ)/rBB9%N8%uMg+Yq0EG}߹=sCSzPgaCo;JjTC%ޟImTCj `Eچߐ@@ @E5A H,DE.Q܈\jPa * KDL DŽsuLd2S4m ]UWOuUW}]TR_JU֍42N~fnOpWmF̊`|Unx=׿(ٵ;Ľ*s~y~&vfYhV^9_#YY_]Z9i[ U鵏=Ib2lFg5Fi$n+)n*TCCO+6߻X}fĽѭoø0 [ᚎIhr_)da].RLGL)ټߟ.BF7{b:BeDॣaஇwI|/>_ 1(oݢ&- ĺ푖sQ'\#)õ }d)>5G^D'DBYd>dbE0<ۥSqOiF+j]UB bIeE |ܼ#:&:>JUo?;6܂c$Nyz峤!p_huJJ3n5?ۺ!g3G v֯1V 쵎Or=O v뎏W6y~6܍r;9㬟Z܏&|o+C-^M,횽) hi_H]n.WR^%߇t_ؽ=F-:hlkd$* Ns~a٘yhlh[E'|-0_işn 2B1 1ş;2p_21`(ԍ^)?Nim j a{Btܽ ۆhݵ;MSCzgH뢖3sdZvnň"5^ol)ftLD;[-f}6#z hB+ F4vڒUۡWJ;M 3ހ􎙯b@b^j+vᜃvޏϱbCU& x^)p.J#?]xYϳC%TQ׫rpHp2~dIR'xWϽg]}~v } vfusmW}Wj_zr/˺X6G3оS|;ʩs}UmhlvN}_^VOaeL%&rZl7\,`.TE#LddO's1`Ix6蝫l(xdzC2ϺX;[-$xd}y*Z랠o[-}3YlL|)fwFtlIWfW鄷:-?Y=4OxK0 RI(vQ߬gجOp8,A/vR],g̈́Av&M(y#4rq~=xfOƉKԂ!|wS,jz <[G}(;ʂ3a)-m([TRC 4ˁOm\? Xxk:oIŎ~~K H 2Sb+Az a,KdlgJ_ʩռ̢! ;~Y.V)zz.dUB/$//G}dI/73Zhب AB\ Ξ<]A+Hkޅ"|U~7]Q 5:!27صG^,ą qlXUiS>fBM&$7iu!22pp*dA&hH-ޘ,S*^}ǿƜ.#5J>1iX$&',9Gaҽ)GsزbI)9ۢATNDXn~/`ٚh|; \ҩP%Gk[>x#_Du]ؐ>OglwIKx~isd>3rCU=F'1cD7Hp: vxP;_3] _ચkCЮ EIy~mcm.)0]Nf_"%7_FUIL }ᥜB,x,ی\|:r`$廼r<} č: "SlgyWm8p0,I &`´5F %=̙~ Wn9\iWJ˵J/;EeLSNvF/Ɵ[&h z0h]:yq; .cTE`5\滱'0{$@&W?02^WeLJeM يH(oT0(R;:PD$FoVc!tc" g 7厝nľD4e#HH*)O•4L \yLh 1¾_ϢfkС}j'Jw% T^4W޼eʮhM`pSxK͈u#ĂϤ5PTI R6} ߚ kZx[Tt+>tK>B`P\”eP(kzOM8-GCٸ/hyuPsi~GlYgri1JE \jr`W|K zTF@hKn-:YI.'|!!(96+EsAup4J:+s}hƕ]w*{3}$ CKԍ Ս 8raѴX왛ٓɺzБHt"f\^D3D | nGn$P J} Z4@uN܃9*Aʘ (ևe$@$@r t];Սu=dd~.7"J#   >ab           ˝@=ԟHHHHHHHHHH|!@/HHHHHHHHHH'@eL /hÐ \h쳐           Pb          ˞ }2$`JJ)P6 @+~ ޜ?_ vSEϪaU‰kfؽAx962^nA$@$@$oP*Czfݽ3Ζto^:Z fEQmqV\W7@&GLLţ}x흆32FJ+}zLziUcmɱ_X^['4tT~F+_% cHb֝1Gckm}v] U_9WEK+xvL1|2N>%HHD@{GAHUnY #[f2:zXa~](IHH &`ARv@(g,\Ce|={ vB;OJjZ=.<36e*l52Eg Px,9n'ڵ u C@t`2Pvm>?_ S|$N9"S䀹L,+Bc@ǩGJeD/wZto?qm=1a^BuwFa&ǡX0OLlW[rd ))Y^US?wB稸PiT6|7` H4Rb%KRO(LC,qrZZi:RwG<1l;JZ='ݦW/=2vfk476UԹm{씦{͚ )>|FEG   5'cq֮8k2sK2IgpS"\R=Kq-,(|:;ՌK01}*ohhToxcvbeη\yi8SXG_>_vD7O`ٟeB<-y;DDM˷t>Ijv U =5cdR1>(#;ߟ\X3۾` >NX&ddߖqd/_Y%e=F"Qӹ(>-2]D=cۖozK,򁭜MVoj#6**l_|9̞8Tޟ>uAYr].=6 s rb]TT$a~}Evn_\؉I=>c;KվCʦo̅]XX5U_3/Y觶iK0khv1xX9w [? o9E0uhzjj̟sq"G,56z;ca&%!F%i`49N"NTK>ժb]3h:eK1&V&XWG1)!dɱ&\+fwj5Ô?o=4l"NϨ7j#LLcMJy]W}t?!C̅m;khK>[V稆2*n 7^NՐQ?5k<1[ci) R'*}˻ /,5.HwjCԲ#[a²6-%mÃf%_e1TH_=0}t̀;:ru*M8 gw6vz!LNuģ VT_Xd'1 g -/ޚZ% ]5 Tf LmIh[<q³'pqc>.aW[NRFC?NXZnX!m@e,mb=ÉjMرcZc!ummb ?NRԮ[ ƣNL~@zrWeQr V,pg!yqDT)Z~$`ޤ$xXA`xL|>tFߕʗ,}vl%Ej[/}n3B6qs2rknbQAXhm6l9F휝lW!-."E~t%WE݀1[cvo/>:mvn~9*Tڣ>k}'ëEf-xe6ZޙCسu \%H [Vkqvm<1h,1 7X_jŋPz K@U018ØZ ־1eXGU 7 W$F4Y sJgػn1Mz[xrtO3^DX^RNR bꥃ`kx\s}AGbSMnI)<׭eV~< ۫pctl(^y1zq* Qh 伤<<5c70ВOvxg,\%!͙#sS|2G788.\W'ό^'Ƀ1|J΢s&'o"bM~H~7FXqirgVg9~ qrqɌҰZ , m FK aH2״ȸ.jhh*ƶ0݇yS*1&y"VO{QtƄ6od JRI~'cId2F|`#Zp):Yr}}>2|ҧ |fzz辽3%"RvmS;=Ď0ngDl2ʩ 9F+>g-`hQv>BWXY;|YU~cK_8(LB YzʀbKbN^['4tK\ըe@[AXHj4'B2딻)F   c/keJ݆86o3D䊇ƂbQLu_ݼjʍގv/X/y`*Vw+)͔+nKyu~Hq>z7{%24iP[4F\|>D1#}@KKt^QK]qio9+gUvziɈ45&Qv_fe*<9uē#^Wջ"Tz-ZOb[EQhW3h?:b ck%a$w~$OS`^UFxOdMǸVޕ<װ}9*rS.哊aw+pkR|KGq@hJ;R?k?ʨr^zf%K__pUOmH 1%Yqhw&YmC`rrH!8= M,80sOL3 j4QҲ!=(K+mnrQcd,*D Gϳ|G/oB|V3EH[$ʌb1c窤$o[_<.a|Վ^eSCHHH0qkV(.p{6i v׺+_í 5KޅQ?p)R6NgAܺ>ߎM;Ÿ3k%FN]bU'm) 0+*G`\~;f=#4J?`%}Y顲뺞1<0 /kaJƒz[u/"960nM{W{elx27`Y;^m2\G [W5ZE =eKqK1V zij)b\E2]ʗ,}bьlfbNPlzm}_]zooʒc򞓝~TM"JHYqEkOίv}Qt^N֫KIYVG7[UG#W1:vӭ}LS*2JamE]AVڛ: &fΎա^bnC<&|i`g$@$@$@vEW@%}nd!}L̟?_7] =s|Whu X~=ֻ}~Ͳ 0%pw6Ƶ$1ĺfXrUM/>YrзDGFVH,hz/ݯ>ًcěIg6M&aIH.sR}n Vˤb"l>#&樑85>8uKRC (?HTHhr~bys%Qᧂ{ rT.ސ`O23w89~VhK>< ? tG_K==r Lʻ&3dg7WЎ*>B`cT_ \|W!TP6!ڊo:'Jls/EjG+ AB[b#uǤ[ͧI]hW+e+3 Ppt[GoH#*!9/09bTѰޣZ'a{kdabaB%ILnr}mD 98R#HKtCfnpp!.6l=d2’OBcX:Ɨ`1LHF[ˌkR8-# g!2ևF+_Q,Ep9EÔ҈ovTp1i[0UehxD^UUV֙fc',;j~sT7ö9nsiCY":ƨWCy 0O,ibXWN_HHH@j,o'PB~a&jPgzGqoGȪ8zyGdܐ2^3/b>SWoٺv"ȉbI?f#mQA-'(ۛƢq䉸1^;հzS:,,?MCK'EMFFc ) dq!Lj墸%] H\)C/Y<%HC?#GeSEwrX˒/-vV?YVWˢ/ehwŇ-ܞs¹qx'@&!<IqG;u&2mʝO:dHwhB^d( $@$@$p0AܟΟ۳W5l`X۸[n^vMtoSs0{T|=_])L[ycź̅o{OhRƓ;p6\3 4˽ZN;څ,9n2CYvʣw,1wVhK>%:JEJc{ARGo"JWub[φȜ:DYQ4+P{jpZ+qFy\=i-힖*ETTPk81lKG=Mp4ʗ,}ddr𷜺믜*ۯ͑1Z?]QeG(izLay:C~z˚0Ǔ|FW6Ѷ!ǰOdVfZHa|{mPʌkmx Lq?w:KYY>nLުN+oIkd6)Ԇ,*Fdk y}o)rr]#BN:$#}ãh. _U1qhңÆhCWZ޾cw?o\[vs\[;4gi^;>jo}ʉ8)'Boi%,_SJGXp<\L!nxX1Y.r|GF#*3*9Ѱ ֧1ypԏ|U}4ԨiyWk4mܥ 's, qO{Zsɪ9dh9 ;ݘ,o%EqEcBYLxtRlcNt5d>۵ AFZТ.[)k;a@"w#PD|QӐ:ˠ| ~v[8<0g@L[f#1"L167<=]bk<3N|Oo|&Lijʒ}}KT/n:˓ݍw\g0,^^b벷p:/UO;Q2J.$wvr-_㲹H6b3~`25ftIy%PΗ bpҗl|wP!҄<鸱S+X-%?6_.ac͚jz'F+_qN*$P](4(N;qW iǸ J;o}ʫ+CGh3i|h-*){hU`P[E10o`&'Vܿ[?lpte01?iA3IHHux q8,> n{W֮a| v؅}NjmfMk J:^lΩ[߆q va#8<+5.^m9}-䞄da].*}|Oqt5ݻ#"Ց.͸pw'UFIhUNpk-w=6%ޖkp|5LjӺ MhyR1ʢkZTh~T% }]|=%tӌU"u}WaS]]i.:&}xibw;wa`N.{CXlķ.}-ѤڶAGȁ8G굹_4kZEW~SB;gq/ږ(o[>Dwtobtl*|yѶ{Ce#:{ NrxA$@$@$Pߚ3_*46 Yź}K1pcΣ"b+@8c%t]!ڴ뱟$ ڌp]}NGڅcR'i@l},CK4Gf/ƍe\Lg؆(.Qv7˯|w{^F|7vmƾGZ{+d"NӖ*rSo[,轢@j,j; l4 b hr CL|s/ r-k]C;ѧ%<)'(:mo6FMZQgՇqbmq*D[.Q7޾ک7Z|Ǟ`j=NDMd߅S|wa,q1a=vrZUۡWJ;M0=xSi܁`Gf=HuNR0T7Hϳ 食aΔޱ8UdO+㕔fQ1nFZ=9D@,F򀖎UbZ6Ӹ=oT.fHH.'yU"Pz;ӵ( &Lnoy˚R`/"m{ɢEdd#}",8 Ϻʰ O6Fdl=$!ſ;1 ː\]E\aysu]x7f_v`V].FdY}ܥ=JZęi$=AԮ}{q~|rVL yrwi䖜Y%lxޞ@3~x`49A<-udzCⓜ`4,6]3~߻#:Bz1ur /~6Z}h./ tQ1ZO<-T+BSBrGUUnOԛ6?;%-Y<6~Ն9ruHf?B;*}c~h׹HR^ 1/,vog=XTp;SyXdw^6N3=%Ţ󜇁 f>g}_B= >!yɬx7ӓHH !_~eI^^ߏ>Ȓ]W%5){^/ DLp4܃MtF> 0ojgnQ%8!صaݼ$F]h(G]ce>Iʦ){D>QiChK>>l3ܤ-֭HRw;=;AtxGk[>Q~czT@F~ww/*)0n{UMSBGS}m0w)$|1Kg ɝ".*f"Pٷ= }9$@$@$@AtF A$@![Yc;TɒSJ4&0wdU     (RL) J$mH+Kϊ3 @`y0HHHHH ^ @A @P DI.K$u(H XރIHHHH*@XIH-̇YH )A wdYr'I`yIHHHH Jj4c \\2E.OJ#         0( 1THHHHHHHHHH@.4          AܠCHHHHHHHHHHA\.OJ#         0( 1THHHHHHHHHH@.4          AܠCHHHHHHHHHHA\.OJ#         0( 1THHHHHHHHHH@.4          AܠCHHHHHHHHHHA\.OJ#         0( 1THHHHHHHHHH@.4          AܠCHHHHHHHHHHA\.OJ#         0( 1THHHHHHHHHH@.4          AܠCHHHHHHHHHHA\.OJ#         0( 1THHHHHHHHHH@.4          AܠCHHHHHHHHHHA\.OJ#         0( 1THHHHHHHHHH@.4          AܠCHHHHHHHHHHA\.OJ#         0( 1THHHHHHHHHH@.4          AܠCHHHHHHHHHHA\.OJ#         0( 1THHHHHHHHHH@.4          AܠCHHHHHHHHHHA\.OJ#         0( 1THHHHHHHHHH@.4          AܠCHHHHHHHHHHA\.OJ#         0( 1THHHHHHHHHH@.4          AܠCHHHHHHHHHHA\.OJ#         0(0EJ!`JJSJ8zWf&$%ն$i_|1m$@$@$@$@%;NGVEUR_!8K<=dm|r-3}x2 l_Jr9'b#X|;.(xI$@$@e"F j!rvsȫ^.6i6crS4:ܚ ^sP^Z6noPƢN ]ocrjpr0 ;( ą2q{|i3Q6 u^G£ÃZqwcce,˻O}F.ڀMUE;쬇ΕNfHy4>t ;MqZ"sgƚyEyxc#1Knsu`3 Y._&'ѶVs~ wZTmѰm;4h5ڊ3oUT<*Y?HN*y Uw+xNpF[i wPvxBV߮MN,pf ' `:5Q5O kTEw xj (2)'M] 4/p3_<N],AXt Tq}Udsϖ'Ĵ24\y8m Ds{pn^:~c䒭$F.p9u^@IkZy: DjbQ5 >B#ѐ)#'&hٺ0{JL3"Vљ^ZɓnF4"Fn<{llT j[9VB Տ^Eg|#i"'QF9~ާ˻sfm7PnC$@$@WOoV\8oFqL!.]).@ͯJe"` 79Ħ{ +6-  b<ZTqJi"%Z WR?3 ,IHZ I$a%ydR1{tkaV,;̴PV=wO=_T!Ǥlp\ZMѹu]z\Wy0IJ,M"9YB(ΞO~ƯGl$tr-#Fw'c1>'Č~F뺡{6"w^{fSaUcE[ [0jX/7Ť"I.=? *ԭ|6LjysQCvVlӊNb(1ÃHHH_e饺đΖ&) pd-x`"~¸g~ƴjĪ\F{oԌ^=5np%K=keɁ09Κ&0赵xKCvcϚgnHlw_нZ;LmRh"Z>geJ7J! 㕔V6բ;UakW+G8+҃?̓!YHwػ{9gnw-]\' K|Em-JNu1vcxek̅,FʮxAՊx >~0n0,/IB@V,CΰE#Č}?fnT m~-{Y?$>HTbyפB֩hԵ/:2 @%AܝЅH:N~&Yr|PAH7nd SftF~br>sk2y@}q߷nF_$1L'7;vu6n8 0_0Kƹdc{X~_('9xmG5l<1_Nr S"5YC4#a I˚ݓqhT\CTTxFdKv.^b W^^M @Y9ZvO\ bԳnVlIi$@$@${8-㫇Dԫs?ca*_oԄSeٳ8rp]ip=w(ڵ e*τxֶ*׫)2|UOUFs.M&Ԯ]OFNNE٩o*:4/FcZ2S'xNch1v)"BDg]wsѩCТxk F?؜~Kqߌ˵IAsEFE{YchpsgqA O5@#;#kִ.˦x c>#>~z:I[vU-~Xj$˅\r*OU?(v#K֚?g Yt=~Z? X0maEoSu.|+:7dv~nh*O9u$'$@$@%*Hs2*NL&F%cq֮8v} S0gҺ Ӌ.o?`阱{o0ގh6 L93,_HYCX=6ynwӘ%PT{Tcr1uGQ`N.K=c  L?)2x?}[jnǑ}wheY'$ҖG栙{uۗT*ma2ZY9 S| ):!<'R۫z|Q MV[L5C/|M!˽=E׽ So2?_{irOd_?7K6ǟVcp&I;PSY‹ǫ]i!Ix]: Eo鞧ݴ{ HHL 5jg9Yf<+~a<֦8<צoݏ?gwVNӇwrLʰWClީA|-.7"z MGQ uOXD'rHA*o5í"Z&C0K|b#QLEWO phJk[q:ߗ^X4k\[ #vՆ<7G„eKqr:ECl5zQ15":u0ye&hL7YXg'U-7Ȳ{+k)zhD8=)@΁lGu ˖(vTӦ;{)uX$I@(q<%2؞5 uDpʀ G6#`}##]Rgxq)"UtT5OHNA8yf+StۻYV&ڊg#6Ht?.ŴB[ۦcr5ThSjҴ,[ .Ðbzjy2e}={mvqsDu"[@ի~{(5w^/6P}KJkLG9j֪*. Uf@9|: @P[2{,KB[ y`: vGQA`x~h&m>8$8~.~ߨ!F%[1\;;Οt:d#KVO:>Cd:P w0/cE=x׾ o-c KG9sLH [[=ص Ġ(gż刏bv‘s@0g9FϢ|aROs ZqxH :6Q V|2 }*AEOAe==~>LhC,fmot6WuēFNt   r$ZV1R5Т_x.:b}st,8 M Cn]嫵r㇕0`KͿq|Wq< ۫pELje4LCSWh}B0g:K\)W}{gdKBjAzjg1&[C83};ϱȰ@? %}D1<׽֨$66˱nƣOC bwE]\],3 ˽1#N,s5Ղj7(s[ ŇCϡrP:Ė1:ӳ1\,ڀg5GD дyU2ʅ"J><;ڍ3êYfIhs<4?]{l;6izT֯Q)_=d}pss9{ցeUkg JZ\#A6-\Q_y,N8xA$@$P cn$d5?ݍ qmf$ :gaTV:G]7 Z1:ߢ1RIpјΌ:iZ[V~!1= #Nb٤4zMI3RE沆" ROlo{';f=LvSPJ M,s\`yȞFntA+dF5 ,ZG,CԮICϚuGNjeaۯ𲎄.5ӇX}A~V%(Fb˯TTgfab,,txx+8:h'wBt0kZ}rjŨj={%24ʌ+JBSk!X)l6̜- *o0˲~JikѴTTx&*\(da Ô ޏ[lK$ޝT5fm-vu+JSyݻ^Jes1IP*}!Z}oC3͌]%+{-A֪4jXCC V۷Q YN-` @0AܚvExn5 wޓqM EPk'?Nu1.7m)|Xcrt v|{UG@i~8bZK*g'?(6/ػʱdm,!Nf '//\7 5]1GEOq)s^G kE18v_CiOw7l'vĉ8B6j2J£ءlO.3zj"|A/gc]EQI.d@."@#mi@MbOf|Y+նGl+_]sZ aܗ:tmcwP1":x=<(Lq_]Reyv'!:V 6K1s6.\'VL%J ,ٍf {#wu=u7A ObzGlU\1ivn! m,:"7eųC^>Ϙ$kd|% 0EpE UXpn\Xv~lS-26C";_7c݊jjɓL^0iTL>cRPR' gΆ07Slwv|}PQ+aNI}~Bxܪ ?|PgL_^HEN/L4`aEG7B[bվ27m{ʣ]Vy?87oQ~tݨn\k^jt"FwڰPCmV@ ULjݟj` g5tԡGFa;B:.0X,߳;6ڝȫ)={;\Ȣ 6ͤr%Leeb7fbҥoսMF; S4]ޒn~ʑG=QfD ix0BRʨ;7汝eW#--N)63ف'D*4dcʠIfCq|>9+ V;Y]^vMXÖ'Oujd: Q ؁S+@hd~Y"1<6q+Fy{W3zE!xy.*5K,]XnUم!nOW_X:gPC99o#%a00 u+Bn=)3<#926{c";VR yE0#cF,!Xut"[EӦhjfՆ&w[46GFFΖru#B)xȡv,v6ӘAp_BIhW'+h+Jx*燥3\JOtf{uFjK+(-RUNu!+ Ѧٳ Z뿸T;.(Wo@j\M/zZ/3_$>wGqZ/lQU.8s]0& ih.sf}ذYa^3GK( DxI@ {)^*;˘xv]aPij^q򢴌Zý # hB$kIpU Qddl|-"zvۊSlMU]hUhcX||XxןCԇ!D#ɈM;wZ[soaӂ4Fx@.şl{]Zܻ,"e;mӖqΆJKm2٘9Qo_T6cچ$ 4QU~PA5ruDş6s"̞'d.mMw_/̳./9~2!5C^+2&U5;aVõ~8qWqh >G\~ʁR= DO pU,(>iv}\²e"UrQ `-2s%I)AL?doH?Z m+1`b6L^ K~yKL%b3cx`( |k_.A.dK~Ǡ]L{tzi6?7c-BwbXFclF3o!Ά/q sQ#H[%ꕜA>Mk &|1OS8z \D``1UU 8ہU H@S&w;Vޕm~Ѽbԋ@6S=JuGƙ٢fq8%Q٭63SԵ֔G7fꅌWrjq~fyə0=Ú_=c{{C?O$T^=Uea 0ьy+b̐61nܜ_qJZֆ5~o7bnwmUi xp(DkwE}4j`֢ 7  U_cs;p@qȈiݤ)ggv뤆=ch40+G*q%ڞ9fŹQ^{@؎f,Qaw,F_>a^CkD0gߢ6dJG#cpJ,15)eQVtt t47 څ~s0[[]3趏CI MnTAyOrP D=DHԶ,(R@AW_{ATڻR;|7|/XG,Y`PyG|۲v8*Vג+34L. רAXdKg_jᎥ3YXn?ꅜUAaQhQsZkpܭ h] C=v9|i}p ^xwԛ kDV4Jwe$qQNStdrW2T0D 3/9B"!k42<}'rʟ=OSDomEM$vؼ$R5B;f]$qn>&}D3[VmڐQa+glCtcD UN@9ot%jޞ\TO K#]%YBǫ/yaJgsN =./tn<\xX;섗t,Vᦉ!Ym&?RX$LD]2%w9Fnݯگm83~\s.I͡ô sc̤-"zz%99X#b?lr^&Giu{frgOſNZJ9#]ź2'L]#jbDza?%9~qy?3iU,eu}k;~=o7oS&coHvkJKF{75IsR__}sV%]gg\-rd&D$ %%%8x +c.3J^ p{h.֠20myLd{NE!p#a_]WhS.).A`Rnxllo  =b St7?@]CmQ/gN#w.wHiIЕج+زfAե!U)zZ@ e)u-*})Og ǡms6+?<!Nfyx(vmZEkW ОSױ-ՄuKF-0$p4w-y^(b@jWa 5 Qa\pm2\'EޑmӠ`h$<DZ/~_$wi zhߞa}ěb{J'7oF WH?:jPV P#S?1^{o?s"jB?\i˩c;venc1)ˆkݢ"@\-l]5d5/9tc%@˖ "@ D'Ֆ)C3?翮ÇSq?sByJYP Df0>x@j_R D"/\=g_Kf2QHLFܩ#8t !wNDUuKV2"@ D"@ D"@)ī |MxpJ:;ȕEزp,f"@@i|#/9sSW|> #޸| Lk8KL@-$Kokf罨)=-avM~./9^3/9[~+ii/&)7hBs D NТ %,xNwG.ƦcB)YD2[v:J^D"@}cB"Lalu"8vDjv=ӱnFD[mV|aΓӇgLɘ2yt4Z`xU0YGUK(KCsjsS;/W@X s1펱:xYN@ "K +˶bHú[Bv~zyEç\riRZw%|Ē@=.OgrZ6gA!/9Β社m/ǥhG㠜3aϥ"@pu9T̝z7yjYW&NNVtv:کXe ~<[CY#J\%^X0?zU?UCaqAG~ =#'NU@Bl$ș.5yc>6߾+/9NSps8r(>3aDtv$g d^rBB@&KNϩ_ؗ@id@T߉xo:L\kOe1dwg^rx*EK% W Jq qd/92\)`/\X6j5m"RG܆\ cΠ&E(b1,]o t"@ pG m#ynMl2&WwϡEj'^m6ȑ_@kUc썲)X-ćΤ<>{, _#/>)U?_swbǯ!f;}4ߏ͡e[&oKR'طg5ffn`G4\&ٲΪ)/9VY + /9x_K23[XӌY9uKim'+}Uq]Vd,~NK/\^"9D 9˱bU)Yʯ^E")W.`|KvMl5u(Å$r:<\ JPʂ8.[ ; D"`E+xf+%=Ѭr`e|b'fhZ/4C]ym2+eQ߅Mp,pJJ|ŇS7{йt|?/ncZ)` /9γo>T(ov1?ӈ?1xQN6"@j3^ >r&=! lᖻƓ3sGB~vْ <.*WMG"*~L]y%bxycԫB^r_aV۲sXSVvAvsrJ D!Ai(EkX$1< kٮΦK;_w(%?ֳ|"yhŇ~w3Fv['Y]7zNCJ_S=*xɑeDrhi]ʼn?iYd>Jոls/ 4l;[C"F.nó}^A64}ƌzfLm^r잟Eo(Õc?a) \hyv /9VyOB2b% Y |3?NaD_i91[0]K~YH/ Vcxl(iﲷm'Ӊ'I8UH+ ϫ7i1^mڝ11lYd7啂e'*Nb٨2JLǿ-~a_NjoB&*26Hȁ"@|L+E v'Pn1W>P b3#ڢK >ƶљ]='_丟ON_g:+^tkd'DpIN|pjU+}iɑva e2ljLayioA;vY -`u#5ڄu{K%]'aqXÛGYtY<[;%InyUe~Bˡ& >~7)-740ǿ&S* 0x"կo~١CN`i)echEr)b0WUA,yuUL_\q/;Yy/,{V,N>b dž:=GeטŊpAu@ch͙Y!'im)ޣ%<*JyQ;A6"@&jҫ4F`V7K(|G܂OG3MKXhfn)|xdcP97TLl^̰,^r*K_gGcw=wUCw'<7D04at%ǁxr&DBrY Y;~Tpp@6Nxy;;0".ʥC:qlkCCe3Xv}KiR_ܨF5,7LܥR,&/g"^rBT~rTh", {m`qlعYvxN~ueE^`d4=%b%ǍC6/p҉N!ⲭL.ߢrX hA'brEtKúOTɋ75~6 =Vi~BM-ьwt Xe^yRz" d!D*$w q݆ݞ`JG1fzLH wEl@.iPйfSnwNUQ±)bTF+qlA?فCݖK cGdW [ ڝ%MB(Y9x5kay;f`9A};u,LAr[IVk+^ޏ la$&E.+&YnX2(=^gS)B7x[ShDe"Y"\Əl_?i]]x7ܖK D=A}Ư_6uh^r咝K{s9[6=<>Zt8TM3,̏e5Sv e?xP2\(DLL␥80%#9auDR5/=-,Ȩ?"󒣔ö-Ǭ* Ygt`4}pO7Q/Ibj uz. 4{Vm<8{-?> 1}p孜@#txsx(; /y%=597]/;l4SlZ4w9mV/ 7[r %V%1r Mo,V}0=ђѶ0R6[0!~?s+C xh!e,EPx;LwigQZAHA/gc]EQS- D Bܦ̬s!$c暷+FEU?ah?LYBԾ- qbۭD 2L `*غ e{ V$ xͼ\’ډ %ǒs`[*&N* N }&9yI(& 5rK#l+O0f"Kh-s,qnعpqW6`hKz'1(tB!1e ~4N[y{39y<+)񒣔Ϳxy,uł?+rѥrwB_ɚgbQFl`+OßAcY_]3O=honT@ ([ʍôc3nscHJ D + ?۬YTORt"@@umUG.)FyhgۉX?u :O^ p]'/EI>|=EvRBTH{`XH/LN=U#f ׼xEiވms&]H[]J"n-PJ,8in*#51mSUaLX榫a;fsڸm'MQ=_"@bvv/<*W# MNG(/C %#.5/.+4S!!S 7F؇ %G̈O 9H[c<ǔ {ck"3W.ݕ?TO¦ h)mg&'3%!5Yq8Xq8/ΦX3=a5=.Ѫޏ+_S: Ng7FNV}~uY+O^_V7ϭIӟEqƶYjyq5=wQ2d vi#E eC{ښDŽzU?[M Zsq[!Bx~G܈ DwjBMB;^eޢػ8M865;ؕϠX|ڳ=D' 丘`9b\KRPDV"RV^ގ7ƯQ"L| CuەmB .OL7&,8N|<ҙx.':Mƚ]g񠖱4KsϳΜxq;~w(Zr~Y?{MLiJR$z`%DžjFvXEq6ٱ| }M)X@J㪊1Cݶ`fy)=ds>K |/n-+qز^fw(lx4+2]!sĔfs\>m-g .F(.].rGx@K_t:, #DvިLΖ=gEpU/ CO57iYfI>mӖ) <ۮ[٘9Qo T6cچ]*{]eoZ~x ^rlG˖?$#6I;wأ`&A~8yQ.L0h6N%G\,484n؂LY.[c{~rB] SXȑ"@ױr_H,Yo9Q4Oqj r?Zzr:6m>BĜ/m oىA099gǁ4r<*W[!-/| 8c'Ov[X]:y:[R/r+ /9vENDR|l#-]jEX+Fw0T>,RvYʝg&UVQ'PJWr}n.6T'J6 $ZV2Gy%oˏyU/dJ/ɚexg K[VK`=OBH8_ŋ+R(Bq\ʴ`i[E`cܝ8k{V]2f oZ%iC. F2_9cR2"@@PNr3y}QˬQ9qJ+m3Cg;޳9H#͢'Q;_Cyw K]*^"#2=>L8/9|zXW+ĸfC9GM/bagj= ݞyI05=Ƌ虶+a.IҬ)(W9J,7>m(R wʏUHnAկj 0@"&E!p#a+OIj!/"@Ql$"@ FrEt/&t^Y cO6կ "@ DjxeJdR!D"@?!`%"PP}JD D!@ q"@ D"@ D"@)R/ 'D"@@(L);$*Tj"@ Doh&'"@ D@qy)LBkp^r|TLKj4_5Q D"PcС5VQF D"@ D"@ De7(. D"@ D"@ D12J D"@ @IDAT D"@  7(. D"@ D"@ D12J D"@ D"@  7(. D"@ D"@ D12J D"@ D"@  7(. D"@ D"@ DS( D"@ DZC@"//֔ B D Z6y$HEt:N`QMLFݱh$ kϟdŚضp[01*] Y\ãL$Afxo<*ztizkPxrF׌sYuvQw69m{T,*Tş1YAٷ- 4o| #B **XxY`M>k%t ~Y]sX39, gED"@Pφ6%FkxaX/tBqtOC ^r-}VFq/pəZH ^9-Cc-GS'z!f&o;+}G,}}n`y`I]=3G]_rLsF3/+/A❬@{fbl|?&+YD2*u" DiƄE6 "dQ"o7Dp(`8`O혳͇tQNu_"d@|h#6-^.灗Lɘ2yt4Z`xUvXSi6\ Ɣ\ucl)/9.f"P !Y_crr3v+ÅTZpnMgcⷘ2Lދ}c`}ogN׏9Ó^:W^riRZw%Ց J䩌m/ǥhGXX](猳za|hخgs"D"@%eu]6s1qe^hƕBv*VY)ïֽPx:GcנnÏU1‡%[Kgpa9qb#YGtԭa%3xq3FpQ8| g.\EV.[蟔$g`w/9̑ZM{W{IΠbrگgvmƆBT -cФa 5!Z/R302^R ޮ[mMƢ)2\gǹ=?a/HcRHG<&%;N"LLzmjD !s 7,B[-."@ D=hm$ܭBmQv5x94hT+=F9r h-vؿu Q;70S`p/92^B+>~ 6ۡج D~7n-KQ9!/z;܌}{Vcfv`G4\&ٲΪ)/9VY + ÀB~M7ڮ2`g*{ tS2IDp>rINaex),g4'VFMXQ%.2 .ze>KQT|ՠ,Ჵ"@ VR7kvB^r'|9*V'vbmVX/4C]ym2+eQ߅MC`eW q^|x1s0 N#6H;[<+ɯ: ׃CL#."D%IVȋZF{W{KO'2Ǿ2\.Zv8nW/5*qUu_ڵQW-*ͩ~ԞӜuJȗwd.6PIZ-)؉JneP"@ >&Bܴ ; R6~odlĄ5.\ BZ|V_>^d?tl^eS).ۏ둫^r<*Gꙷ1Fz=6'$䭢>R)+W业#Dp G*v}?wG˖wƷ}XgRNԢ;S_+qQde q^Hۦ-Cjlѡ喆(")s29TQfIa{Ƨlt=:ontbCK$yk|(\0[ۖŚ8GsܨߜKA3PG  #^Mk>Fѵ\,ZsN$ܯjI*[Ny(!kC+/>xX %1R2?꺉StrV2RQK,3d$Dp!{ ,KT]''IB!63ϧSX?9o`p N]oɲ__k񅎭JU/6˘v^lJ͓pgp[asF ćPb:=7nae 8C9}0߄.M},j-$"@W 홋/oa[~+I:S+;"-Z5/> xcl[@sK)ۋx#=a/J'^,yɱKv"@j/w r!ޔ{sg)DnƠYаnLKjYO6.5|7 ٝzakFΛ9+-{B>⡱5uqߠ!S.l!^'>V.f&N.T h *Ln* 0xK׷T!%P^1+@ǪVD V X^v&4_Y\WY|;i u(;(zz߷@1ց,-3/Bnށՙ=Xhfnl0,۹1tm#;Kì 7@XñRPH'fBe[2\Ei)bױ4ރNlŔ薆uSϡߏo@)6 =yJv]7D3ѵ7`Q'GyI p*p"@)ĭvv{)Ř1!mb`* na<,Ӡnsa5֢7T!$mƱd/wS K cGdW [ ڝ%MB(YxR h,|H4 ;a|W5]ph;?~/>5LZ~mC[IVk^ޏ l&[$ I]XWLfܞ eY!gBLI|&)ѲG&ԋx}p+?Yb"TkbZa{ B THS- VپӐpH9ضB]1P_ځ?wCkVlɾ1Q/a TO"@Row$0|.e!1s[#͢0 Sal*3 3}}[VҳKPwZAN|(me8/9^E]Qm8~:EXL3<;u66d)x_"@@M!P[z L= }&nH^&=9W!iiY$;s7\f5jDwr>YҊB!1e ~4N[y۝3xyQ/`R(Rx|.䇭 E d,LRS<//%[a񘙕?Vhj,k9T*Sۻ&/4oi=2:MU_bٍ6"0]SѶ=UƄLan*h6QpbnK UL@Uq%WV劃ehv=Eyb̠^/!qHeqq]ѽ0meM'?Oy( N R3;r ݃X-Α TsAx4E!Dil䝓~[pTOhKаM+udX6مڷ0z-T'e~[?IϝܫȖhѴ) Lvvmm􏌌--s9bHLGo*ݘn w~D'cy/^9QdAB9TԬޖ"I~\u4Cv+>k"3W.-,SXȑ"@|L(=,!/È$QZn CfknBx(0u,[;/9}bIC D{6X%W$DЗY+eu~b+ LgHMVV\8߼g3=a5=.j)L@1ck+ ^9x'Z#g=+oifl/ AxAQ9-`^_ܽ/loW2nq4É2zh{O[PC aKA{4n"D(8 Dl!MB;^eʚػ7Z/;?jg+A3hgms/9N۝Z世"D6I6]nh%~dUU~7hp)MLg,YlsSٯr~b E@#-Q"\(M4|=YRSJI{<ͥ"L4}E.˹x)3sf~39gߙelp*SS#> oo%~]o\2 -'3 haX/;RN,9Wm<uϖw'd\-uU/S5^]qavX,JkX 6>ӯ^ ȹƶ Js,p{O`dT{} GrqeʩC DIWgă"{Y='+=HsPʰ)1utoCToNK5}#-g "pr.+9/+rLnj~WEm^%ǡ 5@u7, Eu Qh0NW]n趉+%@m$sᙘ=Y$1 T5c.[)_-)MåJ_<菿aNC뫄pi$u2az:*r1gF˜I p[ 9* +1J=mNK~+x2O,7:g\|lҕ-TN-b!G"@ /F4 hV濧DžU[(*Kӣ 3zL㒱m!w/u0yh_Cޘ])zq4+R <.aj++), Ѹ>ʕ#Jǐ-ޯV| Kl 5 9 rb) *`h>)7ЯR</LǗcK >ėꖜd| `Cـ|+pؤ1ޫi.rr!g$K )GhvEVe-9TD$kу 泌Aic_YHxӠS>1ZA2Uz'4yP$i9u~oy4m"@xq]Aye!!aaD @֞au"Ƨ q5@Ё):Np>ČBcў`%DM pȯؗ+^YZ18.R4ܱͱhV9[Hld Iљd$%G<+F1=ZXD>6+-I8j>,ÇЯ8Sz;`"N=?3S%G7j/3oV^:39N)+v n84?an+'m2hG[ƕlnxkK۱J-KiqKN}zOal/sp%]&mpt,„\vY,!bc̍83 ^٭h1lnSٻC!T+G\Vy$vU.DݒCKD=6j!x6[tեR~1~X8E-x>B;H܃1zY=X.u*A辺wjܷM_)_TNd"D*' ]:Y34>s(h?,0Q_'QsZ!ZGHw.D7,~E\rxDZ|}&IolZ֭W-\rD"y3R"(,]qx RD>y{F\rOzCB.9JI|KEa8..cVfx&%Z?˒ @_n)Gܟ4qtmo渻_.#H>ښOOp<3v]5|Ir];KۖpqXߦr!Ir|IH^'w*W>!,XHa81VCa[_c<G֢ռ]0&sN;f#_8A\syz HD UN@:5>d-7F% Ck y#1 0ʒDߊؗ:]eeqL<,`.,b9 S K$.櫤ĕ%H`3#gq1_ۏc_X{()!DrrraΖjKM[;'{V?x[ %[y1x%!9c,/WY<09,mg-P5w\BQrn޴Uz'd09'\4)y&:IBAY&^ȇJ*LUzbt2u\2wpH/spc ع\ߘɶFRJuI };aŞ\KQy 3mWЕy3?++' :./焹,*Lȅ"@b׮]ڲ2P!qxu+Adg’韛/P% S٬P.ʗCTa` toϖ7hhJsa9+CvlJ< D"@ :T<*J( D"@ D"@ DCLq%D"@ D"@ DjCQQB D"@ D"@ Dw%D"@ D"@ DjCQQB D"@ D"@ Dw%D"@ D"@ DjCQQB D"@ D"@ Dw%D"@ D"@ DjC@YmRJ %D"@ D"Pc@BԘQF Dx3WōQh\W hro"ddy~iS!(4m5pWsO)NK{J@Tָq0@EE];Y%G< "@oz']ZsԵ7oid%2m"U.聇D>_¤Qsx3i"D@z$LXtF@J^Dƻ0жa뢒+_& M}˩!+ DvҖɓX~w^^P`բѪZ@<$.w /hx7k2wkӬtlҴE7/—ޝN|2"39}>" (V%rɱg0}bU"Wk5شh*>pŠXODHrՇzԪY׍V!vBQÚ?G/?"8Kip(Or=COӻ钡ftz w;Q$ o *|/ ؑ&fL8-̕j+׆PE D?H7$.Ì]* vfX'!^$w|[s1+%YnCYi4X5G<_mw3/Lq:xدn0krɱ =4G2v|[bhX;J1K&oDՇ/وff]yg#t?.,1H {f5^3 W.eC˩7<maRoyC( 轔L~* niWݔgo5ڰ]E D8KZU| ="p˼:5'LR*,tD~\6=rйG4ZVxml۲axcQ8yV%=s s/z-DLT/ozo4G.9&bS|ϝSgqZ En؆=qi0qC\r$BB@!^}(G{G=d$_]Kvbk)# 6hJ Zv!B\ld"˅7+vhmLLRj. 1QMň~\ ~e&ic0>dX žw\-Ε."@ D9h$Xovk"Qeft(9Fp {cR_C50|6Qxz&)|4%/#Q.>rɑ'w%zW'#l;fbme' #qsX~rɑ'W@.l߉cGbv>)>CAT)%$ d%DL}j{ʁoK팾xoʘ.Fせ{͟O*^F# r%%}l_}+ᨴ&c! OSNrLG@zS,cv.&3e8sV 91W2eSd'D"@Lo~25qt'I LŹ!m+ۧt@~:摕&p5c=K ux q%Gkw¤OGˑڐ0 [%vRI7w14ӈ?pc#)tF3K/ K{ee8}߆03 ma >z Mj*ʅO̝r*Cf"@82٥JJnK Dsn)I9 0 󻍢ASϊ,ǘmhB;lDG.9xµak8b$rq1z`@j_0U.9G ]jO9PwItx׭CrfnʡœGl_1/zU^Qmٹ_)//Ʃ߲D9TNeJ D*;}X0ڼ1<1Oخ˷<{&qI%>⦳|Mh\r\̆>B!ح l`u$csܠ֢j\8R QbH@M'R}|{azs؊Ê3e-FFiM^C +1x[xQųsP@b졘m- q:g5" Vֿ?7!S% JZiS.(}VS l1+1ot[ݻ x᧞X8 - g5z9cp?DSCD#|M#eݽ;LTEn^ⵁѸ߬y3be&Ix9@Qy+>Pl2[ aybk / t_oțx]=+VL|>~H_݉n.,#י<;en *?%z߷KAu@hV-BgߎhE(Kl4Mo{BXV+V@I/jf lvzT|3]f2EYCN"5Vlt\TcT  |"kb ++3d -7&>Ar Nr| JJfv%mGq0 8M`"e R^&t׍9f#)ۙ 08: !`<j!?(aof-xgJp"@~ XFh)*JY$f~*SY8R[m{7؀~ZPTpEՔlXa_!L2t|n)Kݔ1f/i%; +)tLI&p %AQ[ꗺ1ߘ)1,R?e.pi%8len8 WWs2gcW+ѸI.\Ta9h4de~ЧQF u\xirꙇPƯ";, _H\ktO9'I9Tτ7%!)) ۋ[s %g $PKoa?pZ6xo˼mtfsۚ6=H_4mR߰J?os*d!D*$ UiTEǎѩS'_Ga$u. } $vbG7'㗝"#c3=цmZso".{#,a{uz\r\0k~ز;[{pnhKۈwb73\r  @LMε:c9a~(z良j؂U5]Ƌ[I7iZe*1(>!'n Aab5]ʹWވGndp\xgPr*M,vL!䩅E6`as-Fʡe$Y7啓Lv.V BQsDMlݶ0WnL̴_ਨKg !C3@ͦб]=VgM*:D D^7707e^[/cF` ]EZ"fo97\JM PXzg3?CY&œlv2S[:dɖP؊ñ{YX4} L3?|=b H $  YkSysق8' ŽAqX;UXE!J"!}48dnf7ja#"|j'EҵA?܉oVwnZ*~{1o% yuW.ܓCK Y[MUK4X,&-ME&\= W3lnS71:J&W wF۰ Jnߊ<5S9q"@w*%21u(1yGNĦϣsr9[jy20A~o.)AR\SOz:rC[?[W,X6sKQW"@ևdlKίxIZkbM;G?eN9c<* f`Uᤨg 357[L-SXȑ"@֔*yavZ;NW0mL+˓l) qJK_.9Y(A1e:dh5rifO6ZkɯE5+g:\B.9Tyz5&-\;c c, 5Dr ۏCƄqOM%Z=n[.? 03Q;h DB<2k8Iϴ7rFD$;f=!!o߃4ǂbؔ`٘:!7󥚊–3{Rlr8C9KȜΗ9cF?+"6S/Pd&Py遅?(Pn <%._|? 8D' +2CT\e&aeĕ~X ܶhKL,/*Z}H*,?_NU.cΫ/s 5å ofmu48]X荩*M;z : ۧI?^*^š N,[c{t1>TN;D".3^ mj [l(%Vm! ,BSYiչ㒱mȧ$}2|&=`s+U[msirq)_~05~qKh\WA`ʏ`U&c![D@M% W}([{O _Vw#s > ̗APEV|UR<^/Ƕ6}/-9>;; ;@Ϸ"MijMr!l{[B-PEF9jM\;ⳐB14%Acb}b4[)6֨{>~Haσ"Iq$Ϩ'|+iETČD"P.=`+z/;A؁xjnL-yfSԦ~Ү |mWn놷Fxݲ>}`&. =wg_BޥkG"*L(eǙՎKrpyUPro[B)P=F9C-Ϧ|η$kxۯ K2(%>Gsw'8C{pu>"ؘY/U'KޅBE?Ww'cr1i8sӫE/*S2"@@pK!ڬ yQX=MZp"<$܄Q9ip| L`-N [N{DB$;_Zs?"CG.9SjXJ פ 6-VիoQ.9"љ|)a{Ga<=M.9FyK@!z}([{3<8Lel"ۂZ ]!]3)֊YN@kC/rCNtp8ZRS67WpHd?zSPr-.-ᢶMB'7Pw /UAO} ҫhvji֬!hb"؝&ؚ~u͍Kr* % DT9t('ה 9tk(P-۬zW ,y򭸊}Un[vQ"|pHbJJnI8f@:/]E8{tL˴}l|c X8ujE2y* L R =;{ȎGIJp,$X5s8U>NY>ڈR6J|zo،pCOcتB. ʱHcRaPBWDFiQP@xh0O!{"09]@2e{5oF ʗpI@,CvQF_vakcfF[(0 D8MkN"@ DAbER}{%rɱ$܈@m'@嫶"@ DT L$R,D"@Vw~JHy|ռgJ9"D"@ < J D"@ D"@ Dx)="@ H HD%G"PPQ2C D!PB D"!0يJ%7rP6I,|UG'D"@@!@jVGE %D"@ D"@ DwЖ)УD"@ D"@ D"PmB<*J( D"@ D"@ DCУD"@ D"@ D"PmB<*J( D"@ D"@ DCУD"@ D"@ D"PmB<*J( D"@ D"@ DCУD"@ D"@ D"Pm(MJ)D"@ D"@j bcrSXX3 Do&Pc⪸13 *QQQMQ.m[,o~/m8>MЀnj.~))rɑ`A j5?(Õ8'˓Tȑ'A@!PC>V-Ӳqic 2rTm-ĦJ^ FA5JSM";Xt›7Qx4wil8`B|3B< WTV4^Dƻ0ېs1}]Tr\b982d%D"`b׮]ڲ2:֯е7~ώORz̋e+ȹ(.K ^EIl(9L{܊ytST3: /M+% aMʋh pMVYˏB숅F*?>φRHz Z! na P3]y6-"@ D@D@AZ1&qfPQ{D6:! Xov٬of D fݦxcΓyG\r23a̿aJ>WFX%n2#cp n梨ax+RT.9&"Px>SY7ּ c=i^;~3+[ 6׏y/ }1w7n)n Z|+n$⬾7uay:l5C٠xaF$5c7+9 RR3e(^}39lXk\ Dg XkYJGD}]nWgq>IJv֘(oƞgQ:FcW -ZU1#{ .S8{Kh$bXGNi}`{ 8|w_;:+8} N:KJаu,w{\L?ĥfq֖\r$#  6!ce8^,O=ry(^Տò3eq.WM?EG{g\5O)1?w$;lژ馴E%΍R`3߂Mj\:[N?4-UP@~PVe򫚊19y~e&ic0>d2cYB.w\-Ε."@ D9h$Xovk"Qeft(9Fp {cR_C50|6Qxz'7 0C.>rpXW q|2̶C YO[lj0h*t\rȊ$h.l߉cGbv> cxgP?"2EU} $D`٠Yk0Ǽ]o[S xW?QãFeGVM !٤!NV}5=|y@*^F#%%}lV\QAm22m0_ehI-$3ߏ#+!aK8~7ޱ= o.bxAit%FR 5 '@NWoه'C=HQl_xhF 2ye>GI~M,Я-j;@yv9H;ÁDa b&I}rOYVs۰`H^tf+`XT H DB-~q; ۾^Ra: W$[ϗY<+%ф>w&3 ,vŕ+丒3l ]r ]I0 5 /w*G#DևmW"!*@u0SymlMP%];=qV`ţg7t-3UYD}/AF&iM쐷sۻQӐEQYd1" xÎ* RdsUwhD5W2rq1"`.e~q\rѦqZqK\rD!# 5_~{;|Uc.w󇷅\ߋWU<; th *fbr?Y矈B*Ǎ?"iTR, x[?Σmt}WS6m9g}{T.f{fl趺wWO=pZ5jr<'b≦>?7lʗe.)k1RԆ]!L$ ~"bk`*LIkqY&f0Mv^Xs  a+c8Mw =,~'iϾ_!^݁.'I2fHȁ"@ޟmKca{O|̖B03ڳ+(m|x13K\"P 8_n\ius#7m 6F > )O99;аnL2wV.9ؒ3ahF]eiL'5N.}ăuu~*S.lǬL't951 _[ϿQ o7L!a!.jʗ:l1TR~l0PͯK}u^,x]=+VLj^rD?Nd7 wWɑoNz7FY~ۥT?u}4 EA!޳oG4N%EZថJ(|PҋA)"@@4UXt9݌v}7\,c UffnâC" $gD a0s%Yag/؋ Lk.c5;^(SXxcOK&"@jgCnU:2`)C6ilc.+n 4clk2f/o- $\rԴM2\SS7 5po?2@jN v`u"#{o7qJj{YdJgR.~ßBCh{_+<NǫWg*_FX&Qaӯ F,(SՓ 7]_wǍ#"eVeE8g&M(G6Ǹ=*RA Q`fB{2\EyDzqlCbxvK7ӯ`?@Ș^<:]jj }3Q! +OׯIQ;(A"@B^7ͻ}hm0S?7abl#(RwXַ3f8\?\LPy'' L` T-I)\r 7cGEqKQ假Qbɑ%  8L͢.u>v$U"S(W|+løf 7($+"reԋ^@9i!xNh\xnUjI#wPlx 3zm'9 HKު!AOs_WdMapeE?C`?t&ܟ A%Ö\pC,09}hM6| M}@cn^|YXowkbvx_/Ǝ0CI)Lٮ$6V{pt7 pߊs-R ElOwݸsm6`8B\=M` cKP &\"("f{Vmm3(<{FCT,D"PЫ0BKQ/U~?W @e!+GutwBzO M ^.@',+>sZ\r-e~i}R1%4 #Sd'Dp>ihyP |ݨǼ]enn WWsϜ_! `~7rBj1ʻ Yi*^C`%8:y k/E!ǽ+8k)G$!ʗTcǰHG^+Oˠ/4QkP^V.Z' h/u[pJ㒐q EaԹqHR8}_vӥpOΊ0u͟QwqYI݀'龪kB)/9o ăe،{`avܛ F,|2cް\r-?-[ػ\7P6Ƹx'Vp33%L09"PRd3i*lqvGR\V=2OR}yʓs7T ]9WJyٜt(ލ 59/}]VեA;E9<6 -ra*_F>[lO}6u+ 8a\Έ Ea}CE#x/U\Lv.V B%:7O4YCۦ\u11ӲsQQpoxC w1t@ͦб]=Vgh@ UL+fyfkеc?uK~L5HK-g ö[iѢ  KcyH" ^;NfpK,*[q8v/ O1stco/Gl!^>|\rxd Dp>)yB'3VhEē^=V] ,F"$T|j'EA?܉oVwC mysqgc+vČj<3KOG !PHV? (:+4ĭ4˜>~aBQQ |lHl3s*+0Į4BhLt4L({??;uCPgrq[=|A}_[&*<.2"@@NB&#F6hމ4ytNZ=qB|_c8uvRT1(4ү 2%3?H qIO':=3\.9|B<`aK4y "SqE&b%DTև߉g W=1Xi6R"[~EW:Sykٛe.L^`^l&va p,$f=q!J5!Dk YjZeӦhjVKKf&-ZVbu#Ǽ]1mXr>D|Pg)BT)e*h!5.d<%L+;}SbۏEu7zk5?ՠKr|Y{Z ~Ρá3sE 8[l  ف K G8y]>pᅧY?~Pt3a-rj 9"@ T8 iA_=Zt8 M 7cCY}XMCc.S. o=TA gH%S$DT5[VsθK˶|ڄ8`836rB?~zu)K!G1RO`Vd~)};Vsh({rV$bN@2D6g:\B.9Tyzwoϙa~ƶY3DiM?vZM>Fw5疰Kcg ~aÕEAnqzhX[ØP?r3)#w%Z=n[.? 03Q;h DD=#cVL{)̓|Ќob;h||=_؊ąt>bKc/"@!#;٪r.oey[b߿*-K1wvyR|4[f.c^;~]ÊOM&|ſUn;XK*ݯB L=(* [ly_ypڈV+Ta2=4AʡeJ]j'JkޓFbݨmOQT^^rپa;08E0n{ މk@DӰ2We!v jװ1[٨qS 2"@T*sSvor 9ARMϟ{;zxs:_–3{Rlr8C9KȜΗ9cF?wbB6S/Pd& K}VDeWEqm?ndpͭ2o8as]v" ݄mWJ s?cpۢI.931{Hc< kk5I.w; )y1Sr 3e\q^}ʥ)=m$Ÿp^tY9S2_|?{6.n zeDoLG;z : ϔ坖8q]]C'|Hٍ3LY.qmP*# DB@vxkB#ct4Uu Eӿª-dcnQqʶf}ϗYj%BȉJ+d<./qEybf~af:^OG&V&lH_Jrȷ"Mi(֏$g$K\)GhvU.DsK/ej</.r2KpgEp,?LwHsԹ_fug!bhJ*MNhcpicnWW<(G:8S32"@@Uvv:7`X:(4U"$L=ړ? 7NcT!h:0EW'©8~._WqG؁Qh#+%NDM pȯؗ+^YZ18.R4ܱͱhV9[Hlvg,‡'3^1b9%q|_ oHY\WoifA>~řR FH=jYYY.9f""@j,Շ.Hb4㣭_ 6_75ƒX^?%8얜"ĥ_|.KȻt~MXD JEQ.;Ό.g xS?j̍83 ^٭h1lnSٻC!T+G\V9Jn QܒCKDzyS'N[{~;A؁xjn/BIy{3ٔoVt-oaIfEZ"g}.G#ywsNVNг[3eq c޻oA\>ܩqr:v_-37}](n~Q9"@v!f ThECzgߛކ;wPGqDavIkf`kqjrhK'"1^޹JܜR.>rɱReUOښOOp<3v]5|Ir];KۖpqXߦr!Ir|IHV'Su>Y8=>oɇ 9q]0\/-4-^eb* '> )T4 G;f4}֐qhu14LbkGãBptZ <dtniR;f#_8aBsyz HD UN@:O/tk(P-p3d,*K}+b_d{wۖݲ~2q׌ytTrLmjvҹ5vK.9B,bJJ\Yt6s_99[NzX-=,rvn,Llk$Th,ݷVe_Zp8#FͱY8{E]?sq@r,rj/9"@@UPڵK[VV'Ob8Lw|RTqmY!!TjШq mg?8$|nА ( ߀]X2s/|c X8ujE2y* L R =;{ȎGIJ22cj]k7l |NcتB. `ڽ#HA ]EA(?mty#Q&q]!4]]2ޘkibg5oF ʗ}^ÃbV݇lq+_Gwm{)=ou)Юi ]T{eu̙͡vPf$"@tm"@ DL@duKP%P@"P PF D/"֖)^J  D"@"Wc3Ij)*_S D"PH!^) "@ D"@ D"@H!~"@bA2'Cb@"@F=N  D"@kRB !D"@VCd+*Ж܄LJ%C$DZU%"@ ՆYm%"@ D"@ D"@swl0?W+b&*dOXV$R?>||HهTR-Jdg lcs?ݼ5uyys^e'(. D"@ D"@ DT42J D"@ D"@  '(. D"@ D"@ DT42J D"@ D"@  '(. D"@ D"@ DT42J D"@ D"@  '(. D"@ D"@ DTJS( D"@ D*C@Z!;;ʔ B D TY.quEdĮMˏMcQ^j1 \8Hs5/9<v듌Mqgd@b9#29I%GH 7?ݒǠsƨPիWwo#dRZy~`S"0Xaܐp H"zCÈ`~ee=`mڏ* )ҝ1cC]t0(޴_ + >?j5mމ}A!Eb]mcNxKp"@p+ZhɃl+)TeM`' [ϣENE%#Lqhd<3k,vl} qa&>3!O- ȋ/9|JW p\<6ۡLDyE6%KR'gV)f XCtmA^$Ϊ)/9VY + U!{xM ?kmWƴ3w]Iʗdx=НL[|VjT%xК/9rݵn+[M6pArҗYlV1JXekd'D"@xoР 5-@IDATB^|{ ::*Vւ{6}k9VH3PB^ݿJ.f`ea/ Vv5xQ!΋/9R{o=c!vhO<ܴR /9o6L x^Gf{o:teqG!:/9B^DT1އ?o?E6.pAZ ֟=+`A'&܉/Տ- [&r{pk<n9o-d-"@pG q6'$2 cq7/x@!5FU|xzR${1M]-W/>U"U7oc\xܒ6'$䭢>ri+g8#D*>tûΙ QmɱAH>-Q5qA,KεYB{(}dn܀rIW p1ddĂؽ|^tB>v}JW䡈 |L: 1ʡ%y[AR"-ٸF-#ucb[sXsz,7 Q= "@]k˚ȿ:;7̙\?'!tiJO'y*I$uѱL!n=*V^|xqGK|]chNe~y/9Χ0>Me%8}ȭRYfH@U'}K!aw˖r; S>*לuFYA!rEsix ٺ$vSK513lxɑgř+XDYr{L?I }ۏ"}=x.Ș7g  ? _x`NYoC1u@0^rԥZVGI׭U#zǡM Oa즼|Vz}2JH7GMVƀx :\Å-h8bvBD"PQtӢn3`[:Qƚs>IpEu٫Q 켋93{ɔaqVcI-5c/zf[wwxFl.]~ԭuAw2ckcRi2&CSg<%e+_*t&9$Z9+U&,<"Īmɚ| gq_8y>av +;01߶LCQKCAkh& Y!ޥGD e1'=SZ =9n bE-FXT  DT I\1镛JC1%,(8coGށՙE7ڐl0l<}iH͘`*wrLVaW^䔗ϏHg®Zwimw'δДŢGYB0DmEgmpi&蕿~7A۸q++@8c=FZyO}eNgBcX=nH2b6lkӌuzq14xXv}KfZ)Kw/q27CPNԭ;q_x.tUP/ǟA`fx<)^&zTxYjG.pg</9jrK|b0+Í#v.tVaq}'kdJ/Eـ~rd i1X.`/?2yq9a1e܇/0u^r咝ۅCO?гvr5~j2\fR_ 璏_K<}U[čv"]Ô wA/v~Ƥ Ȝ)KJzevCNxX5Q_Y CV}Zdgb]ށVoI17]X5%_'M!y A9TLϤx\P^rpJK#'bĉ<_L':Մ1!-MQ*Å8^Ť f`hs]͌fCGLpf_yU 4^Բ2B^n?l6,$E}i+LVvw4K uXQ<0<p-3(aSMhӥ1^+\cGwC O(m: KѡD|{"HAd5C/=3:&6BJFP+XZzcV=CY&̍lvg.¾_SyT\/dsF@V[r^uœr~|:A%q*>۰¦aP@)ز~)sU hijOb}¼vKds넛>߼c;Nzj!Kw"@`vv-lz8&iRG(/s!F&9McNł1{CS,V!!C4S\>tCK%޽g!eIbkF1n$KIS"@|@ż{d"rS;VU qoVo)0zqw:ݑd4V.BQM? bv!oH8ʖӫ$\$MI3US#KB_ǝy iC 9va gvS&mVEH9CRlL !"@*ze-aX+ŏ{kaI6>h4N!_]5mrj ~%G]\啦Hu:si\j_&PCGw͛92Z8Wv[+?ߪS3^ˬs6LXVqIqjsgSuWwf@>i($-Mb],S8/wuʡeMryyɩTaJF]lVY퍃,J@ʹbpbw8"a+j1BwVFڑ0mVt S0Q "@|L(oF|} џYz;)e\<3}d,rtУLގ%H:ũRc'1eG :Sv^rJ">.|y=~ :coQ6eBb-8 rچ"} mdtsYy vHY'N[ᙘ1<-cuwY@_+W[[Y-7|^ī^c[._L|݅x}y2%ZέĩK7qO}Sε10I73:Ԥ s6E2{;w!٥ K-iql}h"[c{Jb DN p2})\~nYvV}U[+ԦGg JLFG虋o/m oكzs,lK{Qp~.#+/>ryQIƝr/ɦɉ*RvcXTXa%G5H@$}ۏE֥a1`n1ldlq2=rԴUhx+E?aXRZ=oÖUs,̦s5e['xkJsUc'9CWU^x$ꗌd%0S IYԣdsd?YHI&ዔ8cR*}Em |d 2_?$-NԟДOS32"@@Ehk1E5\/+3[(i0bz!sc0L&BQKr6A˘{К){0ǡGq"[ČElowADA /_+8}l_bs^vfRf6'βYBjm:%=%[ʊąK"{nkc0܃ mE\|97/92 7U?.ZEUY݇{"To{CM{Jc6^rlEDTYއ*8'Nة9{ ޸FIOr/\{e Q#9Td$+xJLb85]36v@O5p8/=_%CSaqH غJQ1y;VaɔN5Y%8k =IɶzjMD"PΕt"@2 [ȖHJj=A>yJ?<"p}w3Zὤ";3QKs0iY ;&-¢?^N)=S\E?< tmc%ݏK&yNm!i^( x ꗂde<ظJ>QaRjG%ښ$l\͓"q⓸_.]G!WG08]?|{e>?믜S ) D (CMc׭Q9yKc=d7pVEHSzGWye}Ca v`L}σK"nBs=~|ؤf6:汗s`vv䔟 A@U!}yړqYo߱4'ްZ-ɭT\](j?`\9/kaYF-&Izol~LV.NrR?Jʕ"E G"Cm+F" rՐ'S&Sr~قe^A^rĊ[fdG#)mIU( he;N//-#T,?/.\U8|Y#>,ES D m۶P\\#G/0 'zgMgLmТ!u`~\:w {âτ.)½M "<Z&C8~`>tR+x^9{07eTeuZj!(0vsCvX9V#+ D\x$i Nw'jw,Łm_⍴/͑:emX:r4i()c"+LI><'Ԁ~CAQ4LoN/(OY3}D:LSU|.AF?)ze`\ D"@DF L_ Wx$TjT*"@ DC5+ͣ"@ D"@ D"@'hOQ\"@ D"@ D"@4H!^ie"@ D"@ D"@7tcS^rl;KUL4jۏSzwKÔ C něDZ~$0ODTI^{z^8x!Tދڒs\ٍg•??F-qCE!n^" ~B-t{F+S>[JJx:1k1ać06dr^kd 7İ D" # -svSG,jXTM;ZxmA(|4ђ|OLź2pW~A}F%'|%G.5s"&M{-xaj`ּ䔛 '4Dw2er5F7vH'1%lS0"@=m/^[،4@#>.uw^Sֻ-y mq{3w N2nHZ47QI.= xTO}x<(,Q^r\9]>ǎgpF|lș.1 s(i q<>q ً4:'~o$yB2/9 d!D!C{ufe{fǖ=ױx 8wƭUH( 4 ႇQ k8 tGב[gp01RySZ&aPip) >?j5mމ}A!Eb]mcNxKp"@p+ZhɃl+)TeM`' [ϣENE%#Lqhd<3k,vl} qa&>3!O- ȋ/9|JW p\<6ۡ̌DyE6dgçTIټ黬1{lkf#@jKUJ@&}ȧHƋ0ޖ>3^5+?JSV㽤fgRSg 1N<$|A6C݉MOp;y:JDQO}q<(VSc2\eX91Wb0Wekd'D"@xoРB^|{ ::*Vւ{6}k9VH3PB^ݿJ.f`ea/ǂ W.Ic*fa8osM+ Q*[8+fcDGʜ,|uZ?4.Bj8s I@e'}ȣOݚ&*-7o:=S[:0}$wj g}r< ~j20d' dVD xPڷ+Z{0Y}@ DTXʹ$2 cqS)6P0LBkՇ,?,^<+N~}h7t={\>~fVHY mcK;Fd@\pҿ2C9$uL,qNCF&iM\y a=2哴):<8]i-y(b#k =ӅγB́r~$}a=;@6>_);8zT a#ѸK S`Ҕ]N\xq=eOM[! tV9b g}%Z.ىz^$W\[1HIR}30e o)ۆ4W)S(Vl7/mxMd]̙ vIͰf?s jt9,Kj{K.X6bvrDNI g,6=̔[}_O˔&M!)3TRQPVBx0 V` (eU+I +yuULOZٖ<͗pWxq:WVnǀPwm4tD.0ƏfhҐy]zAPcqb36A V2iPʋA%"@@4^83;Z8v|[YtCo:h ڬ Ik0/i1ҝԌih* r.dv%KNyވ~& uZ=p?zwF a E/`*/9*ɉۊ@ oZ"ؽYz{t8u@.,& 46O}eNgBcB-~74b6lklxq14xXv}K)R _d.nV:[' w⦿T 2U{y[[~4o4'eWvBgVlɾ1YQ/a~ Cc-D.nF { Ø~DeG> ~@kRkeOa킘(')} -?Ϙٟ\f<䄇Uyh1deЧE}^/$~N"!MW.VMIagI{tSHBC/3m`V*^9'N4f?ӽxP(B^Ťy0ha؂A?ω:w>C)ЭR^ yh_0P=U  DT !+/4;Eqo:e!>,N{|"W0f 6T%PΆln;dxiy?Syr)DRd~՝P7P4;uŠa42nbR?#ӿDH6O6B!P"p ^+;Zmo]،2iF9p|;ƭRdboQاU[rou#c /O\)>BJ7G330rrɵJ zd3_d"iv؞|Z0wsOEr~YH.wfge,>+,V}Ѣ0R6k316S=ݳgWV !hr<&!Xhv6o<^ףD`A^m~6=V[!h@ L@L] ;Lu6#&C5E0j_z f|uL)϶[iԨ XmP$@ <5s#^c3-v>}X@^rT{Q^iT98/9ΥFe>>̞_sB(ӶcRVk@3q8p.q)A8i+ObUfYl=ЙmWqIqüa]9-l>%WO65wq gL|~ɘwu9ܕC˚i-ǃKrZf8X-mZ!fpZ1;}2~0Vaܯ|0Ƕ2Ԏi=S?8Na@D"!J2C\Wo&+j>~ݾ^fw(,GRgC;\T 0g L#MBҖ3y; pJK\.9Æ>,Όg%^ayq"PIx}*ωQLe~ _{LP]ub+L0cH_&sY‰^4HݤDZ,W '~\[KL_4`چ$[G; >[G٤R.~I,tx2uvx`8uI.L LߠmbCk✿a_2}햯%}8m;}`kl/RVUP; D  RY.? ]7 K׬[#<(B}X6=Xᯭ,E.8qhT&ֳ{m4j CωZb P] j-^rgkw\xZHה^fu*&GqB@IDAT^rԒp\ZF5G@5aM>yxQNDTey@a^⥡M؟4X%c$w-¢?^7{*\=S\E?E2K>6̱Up^Žxs ~^YҜڶ@Bri^(_ ¯VxRe* ?NDI̪A#Qg־&D'q]4])Z)%C򯲏aqvUb[6CnhXF+gmCTBJ&"@pP.&.Clۨ%6\ _gfm8P+͢R )]Kƣ+Լݲǡb;{@qzD6 %GO7UXxM!9a?}>rlw\d˿fb;C^r D>^d/aQGj=.֣(W7%w9Y_%tM#NJŲ,[$.]H.W/D pr㖱ףX!ò bBֈPVaZdsw^IJupJ/[p>«ze<ȉv)ڒb-/@kbَlWve}z&y)vYre& $',Lȅ"@*߶m 8r c99;kz??$to ٬pk ӗҹcػeU~&tHm2 _Ŷ_˗ ])s`nʌJ4n\VhtZj!(0vsCc="@n]0 Nz͢jg msvtdd߂ d)^R9X:r4i()cւZK2!$w@P.!:" Go|09]H~~/2pNluTC0/_}JU7_VIb H-Oro#j=G De>w9"@ N+%0rQKlr#;_/O D!і)EJ"@ y.sB@#@=S* D"@|)}IP> D"@ D"@ DH!U$"@E2K"PPR C D!PF D"%E7Jg`( w%K$DRUe"@ Yie"@ D"@ D"@ɈmwF! +-s8{++T^rxd"p~Yڅ×itz=H*q%~JP0Xaܐj}+HM[tnU~hlk< ~FC> ++cy<`9ogV!(?5^RpVdJ D1m۶q|999c ÊϡIM0R>ZtdD`ܼ#&h'cо~gqoMM>6%Fsđ1;ZDV8NJ)a&/9sSV.@]4ovcG<9"p{=i;+/mT+<LU_r$ xT<͋ꗧY˴3(ɯLm,28%  ]tLO&@ yZw2>cY-Xs89c/5Uİ D" # -svSG,jXTM;Zxmdxhɜm>'bވv W~A}FmZ\<#/9r1iڋ7h V%l8!6)?1zC:t)/9Nf"Pxr~<}5:xR5b:csT1mxE\%Qr51jwCԢ)X O0*ꓨٴ6k/5k ,,9l^\ DW kYKƜi#91ˠR׸>BUuK^ x\Wf[J_x.2EqVW ݵvxCҬumL+0&D"@lx7msN¾M/i0veMZN9hǭV}HEiebӉ'CIb }>Uq:DCkD ĕ ,;PN>hݸ;S:K:n\)ǐIZ%"/dGSgU2>bsU[PFą?Az g녘PIVRB7meO^3n ٸFd*nl|l vkv_/_B穕ꩧ)> D8K@!}mYWaZ]?'!8֔O޻U锇Eޏ2,_HnZy%b-t];͖IVM8Ð#4!J^rd!# n}F gJAlk-eHNC(˖*}.L xk%sP^FZ/`a-KpqJ}(vֿ9:OR((2l˾d8[e9fmal4 2 D#;C%~roNr0vz }w_K^.;9-[R\Z&߶*{MXPצyS c0MvS^IXu_), -áT|3qde (;; B9~;50\؂O},j-$N E#Eܗݍ0|wpu@~vRWvD;7k^|xZAxl[#[SVԴUҊ͞0_A`/pw^r咝M|E.2?ǁI޼xh&= _kϖ4Ӕe  n^h/3Ly"  gnAg,/9R3lF]~7|7lt'T̞֯,KÌ3 &GMfpKq hAHepSM!)3T~}EK9CzѢn(>`[}uQCfPʪVD@&$9'VlNdK8pJ8m+ +c@l]فaueZR ZG4iȼ .= R(8iqfx+j4 (Eو DbHITju-fFy};>Z,a{TU6$5daَQ6}6TLn]*3Kұѽ)LP.z~4eЛ~7ZhJbˣ,?c8xɱL.DkiyDz2 I\U- 2Τ ۞rM>{κnHb6lkKlxq14xXv}K)R _d.nV:[' w޹T{̓AFx)2FTQwD.;)B]%P"qD*tkP y3 caΘs<5k[{o*#W`r̃0\i9$yp4] b6^AD+Qڲ|9`p=ipmxOhXúygbU1m4.tlTn-Czhx+,96cz|dycČ)!_?~'s*u)>d#x-٣J04U+N#T, 4c; +SNEeTNoaK`8!TzmG6Oׯ鶐F IrXl|HȃEU4||1"f] *hj-Nб;v,& WGwpvO{r" }el->5Ka2ǸeHN!1xN)~sLv׺-L{Lۖ&RN5|_rjpHH (r)&ԛ}Ѻukyֿ֢iIWuuxKԽx/ pgb 9q~^VXo7n "q$~?k<L$vbPKX/7_~)/~s&F*Gm04s"$@$GAcx{M{1~[>UB~,x.T{ lߤ""P9'reْe?:*0ʗQ,H{XdnEV2ҎbA5'hpH T˗d95\[gH?u9nh4?Q7D܆WR*1HiHKKb#'_IrOS 6X4Utd=FI÷uv;F7ɡˑ?*VzcbH];km[cE#g8˩  R&vK9aɉG_*S(jq1/LQ2&cJO1{#yJb eYF?vZtGFPֿԎ/gcQr̎]1zd_rziXm>6x(H `|=h{,׍˕)ل;jGBS.GSl͚'qNehxV^$uZ(OPeCT~ Q}ٍ;S#]bmX`BI(09,_ 2\XDObҦ؏p]5m3j/zQ* v=_1̷ R,8a nW7Y?ZKf'Ɩj]RJ&ڴ"Y:UC8]nn'?ATTYb9hx \eb眧a\>[vmD(ڎ] {q@}=yY;gNsQMWju*t{&7-nYpdñRd 1 Č[ M$p(zЗ؈%Mb 7e'~ߪDJV wT˗.)`9b:}q@5"]J((1- H?q/T,/#s~lZUR \ؓ X˗o7 \ؿcc7&6yԟ!SئEGZPJ"uFcզ;8t;/FɛHFuXc%UrIĺyJ058&Qr|KI9%XܭtY%#sa2&SO G6n>xktW,7W璦QrPf .[c8HRݻ1yKSVpŕ/y/U.+a+K;ݿsy;OyrX7QHeQyCd`%hvG3{Ђ=?I^`4f*R=;~XQ @(7qͭkb-S$9AdMbt YƧ/!_0J$JcqIxp>5Jw1 Yzz`*<- (r#]Fщm<4q mv믜oNS]жX,zT5;mԂ} ;U^sopίrXIsOMz1S1qi *+f@tV4Ghe5}]'qT*״%}ڸKRs>!M˩‚.  #PN f<`3u*չwNiyGbfŝ]T̘1e|ǡ[Seə 1isrn7/7rGn<ɄǵA:7JW1 R:C=V egIWJ\,yPt࿘\u%Ux y68Vc CZ]C(퍫EƨraW,_LʾOqΖY?*:<\B{B˙ۻi8tJA{?ƹS4h0ZE(ei?4:c%عJY}X1N1ψ!HH%`A@5dQ sy0,_<ܥB ,ovSKi'L̇2+yo;4/߂tCN]i!)u_N|FrWa )_VFb?j5,Z&<Y?X~-=/ ) }R#9j8|FXCx?~RIZmY_Tp?9_,*ft 6e)}UƥB{`TVF#t⮥5lZX4Spx~ܿNPmbCZ[,R5sGFUDOc׶_)l?|biwF4x79 tR*R޺.ي*V!|$h3_]ylTJ[ F6>M(9co'!E4Qrk]mQi/ϊDj5K*ODN_h]+K:U$^s%!$@qy#@c IW٧|*E6CFAGǖ(c:`DF7r6/D7 $9E-?}eVp5p MqK }MU7-_!rQ<9,_$zG4}\o%64&VOXB]R Y\_Wё1h.NFO==Al!q{#]..l]-G\#mtiZ߭@vÄQW}XNt :p(wɻxQ9xWt|>}y9ғTp濄#]K;v"esw!>QrT|?Tv ~I:3_!ƱXcضnC C T$F獨|8^#f8S>|5}Q'^vƳ<7 ^F+gv<]~#;eEL% WϓJ܂<2.i$/xbȽ,oqxzU.\bzx%\1ޑG0zY/:;$u+nZ ̯@U \ν0aHԓ s*B;pAa*̚zVʚ%9!]ZY}1zudǩc/0o0' ݸq#DU@J`0w{_ +=SUܳ`f t [5j ,*=YO{=qR$@$PAc}G #*"EYKY׻BoKǟX0t4j?}+i_h0WR{4Dt 1Q8kgCyE~/_e ~sԪU,_e֔meJ\c~hcD#;WDJeGh1Ѽn8NG-ϟ񱨓 ֐@)HHe ^L$@$@$@~PG,F;#HW !-RAUHHHH+EM 1Qr<&‹$p`Foį=$@$@$@L  c:CW eF*B$@$@$@%D r>,BЄF)lR, k,_Qy   (7fUTHHHHHHHHHH \2%zK$@$@$@$@$@$@$@$@$@$Pn ^nn%          c\          rCrs( @ h 4[EEIHHHHHHHHH!@x HHHHHHHHHHA**J$@$@$@$@$@$@$@$@$@$Ǹ$@$@$@$@$@$@$@$@$@$@ VQQ          @ =%         (7h/7 B@1. @!@xUTHHHHHHHHHH 4BqIHHHHHHHHH ͭ$@$@$@$@$@$@$@$@$@$@A<zK$@$@$@$@$@$@$@$@$@$Pn ^nn%          c\          rCrs( @ h 4[EEIHHHHHHHHH!@x HHHHHHHHHHA**J$@$@$@$@$@$@$@$@$@$Ǹ$@$@$@$@$@$@$@$@$@$@ VQQ          @ =%         (7h/7 B@1. @!@xUTHHHHHHHHHH 4BqIHHHHHHHHH ͭ$@$@$@$@$@$@$@$@$@$@A<zK$@$@$@$@$@$@$@$@$@$Pn ^nn%          c\          rCrs( @ h 4[EEIHHHHHHHHH!@x HHHHHHHHHHA**J$@$@$@$@$@$@$@$@$@$Ǹ$@$@$@$@$@$@$@$@$@$@ VQQ          @ =%         (7h/7 B@1. @!\n4x =_\[̈arNN23}z[    # 7R "  kq3 -G!" pxzLZcZM¢u/eN.-~g}[iR|Y"c, x-GC]M((8#{aJz&a0' ['5H2Ξ8L /ʒ#Fq++5_e/(]xs e3ilʺMc[i/n'Z N堍}Lje?)Ha܆ ТYcP;ROTYǰ{:Q}P0u)B|9!X>),YrҌ$@$@$P ~ᇢ<޽}U۬,~zGfD ?Pm],y>Rtܖ|NGE&`+> B4S\bY_P?o>JwBf>;t߻{&*-f_2Q:rv?VWrxWlc \6Eۿ}1mR"  rB@=/_Xk. ߈o_CJH:7b#Ɩt]cjkVү]D;.O"W*Ў׿'Fsϖ@MzpH[Ԝe@=Qrk",7#ͅM:.y&i(Nui\)Z * brOx0U-7-~ ec^P]m.Fn< ^=4/;W>}9nZl'Lz&Ktk4Ci}4nz0{+E6DWfZ}+-R~u-b^   _ Yegvc#x>lNêNNqOHt~+K!y15kH=6lUdUʵ#sï஘*?oFL|0C8zxNV4dxzc>F=S\<}şyq-&Q_wn`r$c/Ur{9 3&=NլJ)Y݊cV4˗e>xR $a1 lؾBnBNmPq(=ߺ1ع{s#m;6tFk`yS,;"ʎ\@yjVTl0;ƚ[p|X~Ծ:wɤ{֘8<ڻGOG6Ch9HH}QNrg;|zX-Bz6(SQqIHHZ%KۉQH9Fbܧ>d`<<4sLLgW]h)~FHEaT'gkPfS=睪Qg.ݕt.|wus5eqDڂ%dT)D1Rm0E ;aņ%Gx{qtX䵗g7vY;јp1n˗1QϠl`粗oZD_XfGt vv =>H.&v޵{)Ly 0\ }1,GPG?Nu_ѹmuPIlw#|yTֺEj ƕӃ:1nrT8ǜCM[~ "Ѫ-Ld*5/r㡙w 8 71UJ{S1SY='nn6LE*H]+ejpn(9>$ɠ$P cz+mO믢:K%Gك[2[gݨ1݅pn\Y?_+~~Rj=fJH.l[d |W`˷wz@S:wN u)1 v!XAѸx ;E raŕiŏ+{/c9&B5Ξ=+3CZ$'']*)Ys'v`a7Bk=ӰXK^4sgq>H+{Mɕu-g{>k<,oZg/)!3Uuݻ\$ˌF6S۽w' 95/`S=xO4 \SqWBZcĒqK͐- |%uٖ. ϸ\<,gQ=Z``9uw M:;-?8#J& 0Ԣ8Nח~'yQO~a~Dn`35C+rf w}^Q,~h]6"V/e>4Zhk, ёPGXK)aj1L6e^(v9_<F C{w)=a"U" nrVڅ& ~0m(tjQK0y9dNB#B>zr pel_7 Iم.w@팗@IDAT=L/y2zna,[PP tcs }Sh*1sд#ؔ9kLԕ%'*\<,++޻vt-g.d`MOH/Ujxu{ҩkiN?54aC²)}0En[W'_¹clqm$ ,]Tp?yh/i7z'eGL-<+R8?4u&:ۗaP_rnwWY%/AͬOF;cŬh`_S}IQOgc֪u+G;Gǯ ˗g>~^ \xlܘ1E|]{B.X˚F1H/EI)XR8].ŸrH^Uqx_JN'uHXn n$>~¢!M'?uR owz^cߡã]d=肄$@$@%L?xz <'ξ!p(Wv&fUTjP2@WA&E3^?-]۹cfXyګ% .] y#2lrxMMCLY}ZhU]Fː7ut6_}iMǖwmPMpLȹ.$C0B&gCçN:|aHMܖw|xd7t `wGG^Fj)kcŠTP}*g%1ett,Қҟճ6M{<)Ewy5U/+mk&i3*||R l)fڏ۱Ç2G8 jn@U*4& TZ KE=i͐*?3p]0u))H=XjyHHt(=_;Y5Z QYRDZ,B]).?&e0mgE UֆZ6C#xᬊzM[hg؋D6HVm:ˑ {WNٰFyva WO}G֎ lݟ:;JHsQlOMh ߹ݫfyW?1Cp3xD3Cv0Jm4GH (_mY$%>z؞0,yHGՎʱ#ccA6gߥ%SfuDOԶpqZq;הR9FP^{naĉQp#tN]*uKއnx-OԄc=  R$XV}Hm\ըN!QEA7QY"1*4h^}D-|=*]z 6gtmà bکjyl(ףA>oN%݉M{\уJ1jJGBב}N(9R¡7YDŽ! ۼ }VFOV(`Iƞ`݉'miYqb㈱p "c6ѩWn.q T_I=ϊOĠFUt"m8# fEAoFo,pYxl߬9i93iƕtkTmzYGZVlo{_۽WK#g輩=Gʼnebkތݗ7Onr/Q.$=%xidG3X5bm1ٿŨ`bFc6ZcQTӄ2E`oגIL:'ގѶdXuLDRGz8@Db>ٿMS  " E)R</\æaU833Vj]iƌqz.6Oɩfa"+CV;sx1\ p8E}DiݽszkÖr\\q ާɷyCnëj vC~'>5劑]T,5ۥejr~zGU^H$k1ƒa=4B՝m>h}6;'T񞛳:DgB챢[Uܵ{ǚ6_O0ōѶ۽eހ j2LjYqQٛ㓭m,5sF IrX$qc>CߦJ6Rks$T9' w ATIMrjOΊnUQs,puiFOsL<#1Vj$Rj>-WVPN5‘1fW0rjHH Yź&| Gך /bOzh{)ꕌM.nPY4+b3yV:6O{{*#ͯotg⪶Ulpm?wᎀjCs'>5꾻jb[}Up[`ﺆxܘ4}L5]ҝW˹bMaaQr59L@u([lapxN%I53*gNC笓r7Kq=pkZ*)%^I:FW*p>n{J}d bHi|Y@U(q_ H?AH ovr'8tP53l|?|Shbo ݾR\Y-H{X[ExiJyKֶCq XUR?R iTw̷ R_/4l;4Mok.Q,{!>~k_ﻞ_)SaܹM#$7;r`E>D jQ_#Y%!5xAu/ cfq0zFɱ>Jg&}zEMcYUb'ĴU[wsO8j0Vuv$$~"!^_OŒbR;U:!`JMƔ/iZ2D ԑ lfo5vؗ(}kj[ND 8Zt-=FL.Ie+J2aRH2clD'{=C*׍,#y&|[S kuhFSt?cƏǤI1<9 !iKY㧡S7s?k;Tnݦmi)΍Yf[!.f~daS$@$@L/8#+*c!6<}ٴ2,$kٷ +y:ΜTJS<6 0;eHz]6L… JA hn`Vѥ V$OsTVijw^rǼCqc Ѳ1|}>]Ȋhi{0W[NE~k&'%/Qp7>YVOK~&~A4"<1jؘKs|uF`Gvl؛rYt6ኁdQ>h۽"jgr}ޖ VVJ;Bv)6ujزz+əq\.2sw:OzF)+R83bhc_HnO<,1/:kבjAdLY1q j9K :*]x Hp"y?M uVaH4'Ymtf/F-ΐ 5cC^r*˵}rZ2H5bvb2:7rm(8,cd.l) 3rᐌOKa\ >t-kʦSX葤?"+@0Url>V,kEI)xKl81(9=3² hp4hC?A/QLPPݞJ7BiHo؋@#q ~^5Kwd[xAK@I1wW$ZnM"8|Tm}`8զxMZ{K[ ?3Έr!5B˗ts'פj\gHʗ x!I9 Ǖ)C0hfQ ҺG TWg:秫til^k}t'cQ}/1v@ #Bǩm9m9wJ)˩.z 0 bb0u[){P϶Pn{8gIJ9\J,#eN#NRxrG,'}avߍQ܉n=1_ϳ[Bf.]Q^;5c=L+/٢P|1*NX']|ΌS|JQWŇ4D+PM,JJl~ޖQrM!f .[c8 Ѫ=*ս23)GѠh._vӴ{E`&R3ޗ׶ɋGheTny3iT0J˗V,~sPz1d|0e)̅ɘN=)MxArIQk&15(p ZqN f`cyhvG3{[ʂ=?I^GFwl]VEg=%   X|9f)#bon/E4SM;{T){DFL1yՔvt}-ᑸυႯmt*y(k-[9J'ۺjŢ]:ʄ|JA@{ )wRlSx:ݳk'E 8cNKCG6>}~{oӸ!4)W6D6[b)x* ڊo=16jQp[ʳ 2zvB2ֆ݄WF.&)6voFԞfyצ\g gLhwK]38w|P8LҞTx[rS UO˩:ה@H̓VR߽rM@!j.zHvy>AS01 @˔o;&(hFHڠ%pC΢H$,uh)[3\xLDٴ,)kF#rORcޯ#ǦF 1\qӣ~ݨ9B P wDg.ֹuTP,ta-ղ0[2*5_=+AB<ݚ* 1!ݏUf<`U3u*չR͗z(Jo]2Zv|o |{`dGĠi4.КP|eVFx;A=R-"}?cд/5C._f!MeTX2KxB^}?>Z+HaM ANP-T(Sj2L6+bW mT{:wzeDqΆ^nZ]%(*VկY[xfUR|6<&NQel^{7ހQRwv ` y7# J0ϡz>sŝx+u-Ƕc/YBQFjpn9+RW @tkS`"Tq0nFAX4Os( L6_ 5X3wXu' }U%Qgڎ`TV՝=1.(( w-Eae }d tXok>sNWiHJ?K> 9Bf6qa;tYGO!N&-Mg r 0yPOc׶_)l?|f/w +m}wjbO"u1;{ Õvn^Vos@BTeTpllg'ږp\+Ic_dcA9x%~{J[>ht9i#(uEehL݂d1ظE֭гchT+U>6,yWPWh{zHOwK 럦am 1kZ{rKz=?۽79ZUrZ6jE$@$&NԾ^ ڬD*:b4hܭ¶%g϶>zhOW.Qp^yjDT^c(ԶSE-T!$cܙX+uRED#F1|h6xɽ֚9QR'cw-h&QcvjTywZW J̵@p /ʅ ^V܁O 哮xzDxP7r>eY)dK}u5է"پ@e[L$P)q[aLocYbH91p;_1rr>7  2K@;g53Bgnδ7Y' -8; ::0,SDq]TMw*re?,wW(˷m1UK*l~i2Al:q-8K<#ez㑵n xȣ=E ]FɑUX*9T VMR;\cس/q_,wQrp$N; 9-:>>tF9[.]q8䴌a1 ६<7 i^I+gv\?KRoxY5]؊oTp濄#]V5˙{Yy8ێ` y*mċUs/6\xTeTpvkr6i?hmC)o>O1@QNݹC*dom=֧K=apaHԒ s#x4+¬컀uZy&2ufTr$@$@M?aϬgee9a0$ލ&U[U;|i$unJ9TGƚ7!z }mjHL9z>@ZqzRvsѧlѾ$1,F)ŋ5˻ypl [ጉ.K5ICgHFؗq[Thoڣi'HMX;ې6k̜wƍU=lҥ3ؿm1{uAfuBwtBU2Yw?Ю9"BaXDY lܔ>ðrjF^R,U*H*էYOX7,OYʷ  R!`AT-D0g14O68 Ebf_i(]iO)fR$@$@FPg 6JQHX*d^HHHHpɔ15{yʻ/nz1\J'o~uj#Ii AȺ X&@WŻ XV-q=.[.((о`?'y'XPMhbb:rdy " ~=        hw֍lqEv[J01 )s/.)7 xC ›@^1JI1 \sX[ @)NO6.2$@$pȽva 6fFɹsL`*C Njz`hO@-U*lg^‘`a7b];ڹ) T4W< KKHHHHHHHHHH*+]dHHHHHHHHHH%@xHHHHHHHHHH"A"EHHHHHHHHHHX4HHHHHHHHHH*+]dHHHHHHHHHH%\l       052srri|#  #Pa xs ԮXĦ"]%`O@-PN4je=qku{%EƸv$BSAgDo%G$@@I7Oz5DUZ^"EhT}j0wz,e9,kϜ&?&=p*o1y\kSe{$1 lQPwM>#{a0u)B|9!X>),St 5K?aϬ bųD*ox yn_'aȷX +'ė40oճhW GCNO>.1J`< CLh?w\^_1~t3G!,m+gcqx}.7aF5JNȄ ,e9,kxq7 b j *": eKџtg T_τ~W~=18r8|k1bC1? $̘ :VƖyYJz)/4lrOĨF^< RMh۩ jEAnF{PK%W Q߷ƔMNRy<}j>D4ǽ @jd'xJ$P (^Ï᫉Jשz M6p5>5JFS.sXF&f<HHcx>-5 NZHbg1ND`1\ԅr #>Wz~yɑ$# 5O+ƶp+O հq[~jCũQ~vDe\l|̼ YpQ{Ç;Vovc\tI4 D"`OmNIؗrx%}3&~ɷO]LEtz4l>O<q'^ Sz* Q:msL.GR$xM  .坶wKQ8"@?Ivս`|p,x8,GO=[ޥ= "τ{)do(eB=} Y5Q! {-?Px"7"ﳹp(5m1M fo^CK$闆z[hS X)=3ULKe":lX\,;5;8K6}688TO+ED D[-E}Xv or1<{6Y1Jb-m:!ob +/>xQ&1R2?꺉Str2NT%G2"P x^lUUoV,Z\՞r]xX0,pqrI-'0n3X;gm#sWcqp٥|;L<]˸͕ {.?)+1T?k Fc',WXË5//9rN@%{#B`~QS^rq0ԹB[q 6l?s>"H\ksAl:/92lf][,rJR'س ꌥo&3ep,Ơ/v'dp21$1xsp̀PVBBg>qCڸok;rKٖ`惠eY:Õ'a!^ejEbb!/֬NdK8hj<;sO~ކ!qe5G1obStL.v-L"Т({FBYLʼnCߔXdmzhPʋA%"@@4U^: sZõs86 nn U|Mٹ<0MvI، e;ԅG6 EH˜N`*oJPaWY TOOFc®=wew'q뛶Ҕ$l߀ q՞cA/4פƐ;1ev5Y"@*)_'(iƦFn@?<\ ⶆ%,{3^ {-?g904o'-xT̓ս<Kd'B۳u//+,u@6RmW+kþ]Q(| ݢc|m7l%yeOZ(MB#:NmM'(=3N!L.ߢԀ܃x A{bzEtM_T©סBA@DMLw X meYybxAA"@@;mكnElǘ۵R(B]?GlV2Pma cum-*g1vIt& lAa 8zM\eK &u\:UgDCP"P xH퍄y}q _^Yph? ~~/3\ZBjOyɱ<rL!X+(t,<fl&[t|>qDY/ea]0ri!g|L)|<@\$jQ\y#Vc^ {-?gDbo =vvAxPW3ve]i[[oL(BC/Tfx̌f6J#>BŏyVQ1 LX_3pt8CIpm |0m-eL?j4\d7oY"IhXxH И;Af{w ~KS"@@0O%Ւ ` j*gŸѦ^"6K0" L10kShjDe"Y"L![zXSL^rNX ,et|Uh 0!K\"Ps x(o֫lF6T"4\ij^)/9bL-|ẼlafeDLYb:4jZf<䄇j 8f݋Ut3=?VV{Q/9< c;|'<+kRS8xQ?G$C|Oۿ罃LZ{0~< oix~4L6 c3[erc6U_s8loRp!bΊOQEO ooe2W |-am i!M׵2CߊS"@@oMBS0|+lP,1F4#专]Xnhk(fS6AV16mb]IG acL>ٌ ,^rBB@&baYK{!uadql?`,. R޳՞;.^/Rydz}t*b,0a孜@t]pAPV&ZU#fgT9{kxg;\9ჷš+oPNjx՚=ۜ__7G"gaw,qu}a]/XrE],D)نImk׼p`|j>$DJuF!!": {0"> B "DA@L}#Rw&7}&.aѫuc ]MF |yL1-mXT Bڃy( BNapC %YecQTLMMŬY_^N,Y {-?֧*pμ-Əӭ\^Hx>RM@IDAT'DzlQ +4b|Wޠfn]h{^ u٪mf%E9S[' HMۑd s[nIcL.a Ew۰PJnmY.vL&z*" DT1T+ daN>bdMo p~4Wgg'e`1QZ C#p2|C)n %G̈ 9Y 1x,LYdF$/9J\}oH@X wi=3 aY62^)/9*IS~e_+0-3?É2X6 QqIK؁r_R8jsoݵYupYf .Er~uut v0_d?ӆe{M{q[:bNf; VBZ+Su EJՖ+NYڬHtIHFl#s{)8I m0!onn_̵'R=_"@b& MwqBފ7ݔJe8u5Z0\SM|,>/"tZa /9+'t͗bJZAV+yGq'x8ɪ+^Δ|ɉs%GDv٥N{*&c)JGzOC1o~Eż`U/xɡMAzGmľGmi"ʗ UGaG 'ed 6uQzCdu:'2&4=,vn;Dz ؙCBD"SF!:l.bܻe0$rBo`׌DSydvVۉ'IK8_R/9K7h%~Ԕ!7D՞r+ 898m$]dNS9߬gz^vʺ+A-*7Pog"sXC _$îΚ\SؽB}?3q<${xi"۵U~*RڈiXpSa+Z jY̧m9E, >JZNTO홐  D'PMzػev)jFa{GcCۑPms0}*r駣Ok>pB/C(.%Abnˁ[ѣH@am且"DF콱pJVҦ]sx8pP9(ћT.LWl7)FlJE?ďX(yxɱ9cAvellV7Ke6Vm!>R wx%a#Cښ3_/]0u1eHt _ӟ{evR,r"DxێJmuƢ# m-N ū=%6^5Fx|?7lB)^a{nڷ[,D%w,=mY.~iw996~1sIt|^ Y!CKFbXfj3 WḾ7bDI7ٲ#0$}l&Lǧ)e"cUm|d L{dcu_-;=S32"@@UK{14J-uf#?6jzx [&,mbڢ=S4xfN=CǏE ޅH7Xlnmd%Qfe`xK8gN_@,ӀQx,lf/ܵ olVZxnl{lŅK3yXr!1w7vͪ@@o+:q}yQIxX&Ѯ]$eF }رg)F1c:\WNK,!2"P |o__܋5l[sWlA^(<8p0nwHT:i_vW{K5_~!w⍍e6_;%”Xn*-S7N1}Wx'OƱS#l wvL@\BSpv`UcuuY/|zOG7ګz!+Wr~HYXDe&?~>Z^d7$ #p\Ԭ9 #_ޫ& 6߃qCٍS4)2iŘ^눴]_8_ZMA1ll%߾Kf鲾г1€#;1i|헓$}ETbJ&"@rRƒ#6EQ #(WdS->|D dqvɈCkgbčl9q'06NH%Pv㘼8Ve m!P*X!м(Z,KW& H쓧uJ@M$ὑ73_IE@1inh gŜ'aj8)|L:E`w@S\E?T.bQΡ^v۟B?jS کm["HvMBe/PRr oi)f5 zޢ`p29{CٸJ>aR5 ІYV1كǢx2<N|wKEӕբ]2 f oP%l܇QzCTBJ&"@rPn&o,!s5j6*'/gG`Ly 4Jn5kc);qթpD>,`Q>8Xđ.|p^r\EEʥ -F(`3ȱKr}{ ^e;uuSyN( 5Ʒ/˙8U\ ˓y䨕nY_yLy+.a/Vdf㯼bu~f Gc \CtXÃ6 C)+ܳ8k++T^rxd"P={z'e&K/~2AB8葺z1#pzqlX4? /9ē3 5XMMPs D )C. ]VCq~r*6b~HS4cf'gyT)$bTQ4Nxdܲ*"OF72??6blv?&egdcp"DV"@ DiA\sOYc:#B<,X+C`A>ڠh\ʉi|D9Qhn/!U`>#9g=;+Nl?pȢĭжs^/~ͅKNeMcG3XAt:rB #_wT2/9NSpz9u=/Atzu#,7yQ%  5T@AN{-tIFz!rcQdbg りt()Å>NM\t 7.Ŀ YMQbl1|" bR7q%E DG-ዿ]+TVe]`[[ϡUpz"rJqh Y; ˶af1A15% ȋ/9|JWqn^:vۡLD~7j=K\9ُ>Nb[qpGFh:'`k 6x0ȒuV͑xɱY|=UXրAҺVTFhU[v m~ST/9TBV|)$a 1[7mN` rA|UpVى D^)ě6M /9~]fkXk%V?dB+W(Lq႕]-{$>TKt.$+ FS+*#U%yV\i/; {^ly@!t$+E@ #G Y }LNXC}Azo!r :3Nݸ`)h"(5*ŵsĤuy1C}@ DT$K9 *ߺ wE&K󐝆'#lBDK:_ֱlc]tZlJʭSqGpq + »PB~72[݆{"עt7Y)٨CBD"c^)wf,OUP;ڵq.NT a9#ҼK ض4elaω/9Yk0=a/'^lyɱKv"@j.Ojyɱ3HK°vx9ԹBl K^my ]JzX+  jwr;Vkji{?Z[,ڥJza/Vs(\˝C[su72ck1h˨] 2,ecHb L@9~}IKNsqW֍/u4Z/W20oV2V"Z1f11}fu'v&\Y\EWY[|;u6 u(;(9zyڤ׸epAkAgh͘E!ޣO4b*N"Cl[^x0+j94ZavPɃlD"P5$MrդWi*ܮ`p /yVg_3Z#{(6[C:6I#"2gS4ۺ3TfU/9c+hwh@ ύ-?%/`*/9*ɉJ{ÓT ,/9JL v,كiVl\CĔ$ҁ%%Іfa =/9BnbSc 7 dC q[HCR,&BIC)}_KJřI,G!R/xT̓ս<cgB۳u//+,a jә .$s⦀-K2$&"gnS?r;S˷(5 >=^{О^]Sy9puM_gu|Ovҷ6F3ޥ7`U'EeE8j8B UHe ]cJG0nLJ wCˬж|:[Qxl?-Hq& lAفgBܔ"G G`̈:r):`ì rL!X+(t,<flvzt|>qDi͊.fe$x-x &P?$! ZWȤō| 1 򷿅; {R<@+Kχ4--7&qQ/aEʖ2MfW MC2S3,l~$m,C$h̝`Lm{"-q?TO8B UH`&iT@,gŸѦ^r/P/,3F ㊌3߲}`w{1+n#K_ڻ0q2{mc+D\޿7nO-pyQWS?УDj]q2eA#>̬ "gѡQ2d!'<(PSn1sjOj}P+~ߔK_ڎVC$Y$7_yX< _{Rd\0{jSXx4CX6O6B./9 d!DF=5%~Uz 3Lj_aV^!GG"2 w/o- JW}21vJit3x}Q.`D6;M&g/r O}҃1)lL*[9T$'l&s&R׾a*FW7gUE,V}D6Fzpf&ly$$k{80`ie`X>e*EPx-XF ::c0ct0܊LyfSV"@nPەu+ >5fQՍ1Gցd`NOڷV_0m_-ӂ+]^x|Ff[/aIRVc͇Xj'}4өTX^r6QEbvN{bp|mqS8 j-ftŻ^sȻ\= ;0c-+ƏMrѥrwB_fh\0SFE-=%Cn\# y uFeBa\B'QV" i6EagC;M[\n8L윥k]7cD ++.v 24t"@ ! ۺ!ɻhaYNĆAwrl?erJrϳAy <2WN ~0-1R 21#>0%{BʔE&`D{^L{9/&>PLa34GQk+qx--ONiiThv|K' Sx.sv=-,wڬ_g8,H^X{-ꗣ[ &$~\ R/n؀ /00:y _df7nF |5ki=0O]RDga -WY偑蒐FTSpVc sUޠ Ʋ9 mZ/ DT1IW \^ZOS&MFG(/] wDd$[x4ˊٛI28%P i#a3y3SC6Re1'ÞØxR%ǃ)  ՔSRISѢDaGo!g,2iI)Zle?>r4_GRgQlS`pOTme*!nb'w=or,UcOQ/|C'?TD@&MawGhddha],nt. $!WP(uh6!DY`ۦt;-Jd#D!PMpCů.A_z5<m CYK~U݇6{ɾrѰ}=iO9wKz^^iU/9F x7xܶ_g pm1R&'u&$z;mu5+f*ӗCRb%Ei;XjzQCfZt<eT/[cv\0|!^&aTZ?ڲGF<z)$*u)ؕ{QvXnd0O@Ha2S+ΟV\`[DBJV9_SȶCՎؠH>K"@ q=^JVmQXl︫#õ:`L1G0˥>-g/C(.].rlGz@6qKKQ "@j> >) .'"Va`mCYl7)ؔ~BʼnPV5c/< sN St1c[]Q>Ԭ 7ƨ)7"&gc) |&ꕜ4lڷ[,D%w=mY.~iw996nBD"9 YU+9Td$8Ňオ/}UhDIҜ_YH IQӔ*g1ƪ6>넸i9S| ?=M.\J0`ڲSc~Q=1## DT5jLx=Biy%aG>Zm0^Ii5AhG8{QFw1HwTVLd_$jҬ ov ? (bu0 %r69 t*R Ѝ-Y~n\xɑH1/swc׬ 8C>GEj1E]nY݇{Ʊb?.e}M3Kd%G.DlS^rdMgG|nr' ]f9c׎j0e;vK-^ɩd\>+<'NةE6n;;& .2XLZSpgvs\ 7d;WBVP8gOp S~*x;0 f7Ndˤ}cH5mݎefYBk9o. =kMgpd:/ϷrD17ASpѾz*1% DT9M~FQ8kȦz[|ߍ%$Qq_'Y]p2ڙQFZvi Qn}}}ps(;XqL^|xqSk岆6(r4*XN]/9 ;!45 cns*R,%G]:"P x՞cKfBT|ulԳkLBSꅜ"*(l')Y {,@ 5z_/`|ވt|Զ-RsMBQ /PR7_ |Idq|v+"ä5 ІY6(=x,n\,Gq]4]z[-%C. aq |}xoP="@r:K}\ZjTq.*]qO|60pNEH[v;MA߉Լݲá`LmʃK"Bk#l:G]C7!+kR܈++%,#WQVaZkg4ݯK.Kr~ك q6e.W5׊->S$;I %65Z"/O//M#@6bn.W樬z*! DT=_QRR#GO?5 7vgMgL^ی:G-cgBΖ-  F@i.2]TvvX2z?0{n,Lú5rD= Dvl|@N 9=]>v9dc="P {opiOi^rLJd/,gZ@ plz*xQ&.hZK%DE0(m^7I/~'=GyIl B/oiR|"C Hm ^٘٫R;>L n9D"@p\3.#*= DM4/9jɍ~O"@ UC-S&  D"@0<^r I 5կwODD"@;A D"@ D"@ D Op"@ D r/9Cb@"@FN*  D"@orB!D"@_3dk5(O%G$DZUoe"@ ՆYmne"@ D"@ D"@!@[xC"@ D"@ D"@@!@ js(D"@ D"@ D" R{C"@ D"@ D"@@!@ js(D"@ D"@ D" R{C"@ D"@ D"@@!@ js(D"@ D"@ D" R{C"@ D"@ D"@@!69"@ D"@1HHo*M~~>rrrjLɨ D"@@UGqhT[2 NbEQOHDqh8k ϟo?ŚؖQ@MY ő][Y\ãL$Cw;p޹90v>m>X5)WѶ^='~,Yw9xf#Yaq*sQQ) oq~6F"B kY{bm1w r~\sX7, gCD"@|%%%8r>SS\GcբТ\5HyQh}xR (92t!~a+Kᠣ%G&UE\9Osc: +/9v]s's{]L8Sv7J} :P8葺z1#pzqlX4? /9ē3 5!vP<SEwcUHݽ۾ۉ'M_{ʷ~qsK>NBn_=fmLiNO"ӿH8_~[x##:)Ɩic܋Ǣf։P"D"@d!2ƢmDNHn'KʱVx",,ҿ`7n6jBM:՟`^Mܼ#/9r1}ֳx ι2\Ng5;!hds"D"@%lmݍ-'c=)̫B5!;Y)Sh'czb8qohhLTh[E m2wUwan+ 8~(:y4"a8^mL*3yɑt׬ nr:q РeuK|~6 HxNu/9"P3x>^%uMՀnBTs=bиA u>heY ME'D,>"ԈsÆѣkik" 55ʪ} Ax_~\1<޼_pKS۬M@[O4d"EbQ:,ŠIJ D"wv//vmPe[v]t(ڻom=VJ<v˞C+nġ30lfY$,: 6xԇ ^|xɑXqn^:vۡجD~7jfݤڎ/z݊?œ  xѵJYh<d:HdD`|އ[J_F']W~3eQn"׳%]ڣQ \>+2Lr:LFMR~)].ǐEZ¯=uZص^(U9V"U|)Yؙ>*]'B\z8res_"I2lX~5E~qfbٹ_)--_F\ 8"@pH+uKQpewen! ,ѻN 3xҾˢ{msKj.ڰ}/SKI\ksAlo:/92lF][,Lgďbg}{eи!q]m75vtlx3۲|P7?ͭ]phcK`uAd h 2j}B 7 oBq뛶Ҕ$l߀ ָt?%%Іfa =/9B)QO~2k~Æa$n ܌oSWHY^&pvtEI>nt*{H7k*Awƣm-p_";GWڞկ{~YaѯS=^{О^]Sy9pu#u:';Os&hۻ 񄶈<1\GY D BܶAa"cLIڀI)sS!`Hsa`0O\g+ -'چr_KR'7 L` T 8zM\"$u\RtiOΈ&G!,Dl\}kp{#y~q _^}ph? ~~/3\Z6mR]=wl%VCVPXx+[L|㈲($º`6:%CNhS0y& IբG&y*@\_*QvҬLV B#THm+ՅB]=)m{  ]CYxqWŖz!!x[7-KYthf4q>ڲ`)[ʎ~"6i\-6? L!n$/E ,ж1w}3,hA8 d!D*$`V %U~?+M  ML>_‡^`]#BS#*gҿһ½evba)B{&nXvm^rl咝[Cn` ^e 7ԥqM_kC%#9aDrYb]-LUiOݪ_>by;8 odjyH~K1vjhD>+<+kRS8xQ?G$C|O_n헲~Qi6m&Wg./&,G+cWdMR.$be06&ZV_ǡoY D Hr&jԛCСCtׁul=`.oxKӟ;1s/. ;֯ݱ,*νzL`Mx>^Z^vcP^rI.,wp/dΗض+'. v^NL &"@n}fj /]EBܛ<?懖ޅWҟ5޷v"8tx׋vTG̳߄Ă魜ŢlAnHэ S/hڱ8^_7$_$*ӈlvIiicA*4:rU#WNvعX7HrM&k-Mga0}[5=niqcG(á~+]0)vt1yf.B1飻I C 7_(: M&Kit"Hn3CW9_h!Bn%:1mRWX{0?GAi;aI W;dəd^r_6NT̚)Hz(>ysyDxCԦ 35s1s$qN&-$qTg+n">7˲Y 8I;9ڊ<ЦHHlɝe7o{iK?˶N0mg{N|uDя-o3.^YxXUgnJ^Hy'DM|/_j+tj>pӷElL|LmeYFh.~9t-7_k.3v3%l󢨆w۰PJޙDU;0,)nQ"Z\L4_,rz3ԿJ=|ϥBs4%)Mh Y2sg s9s=oE>vZh@ L; Ya:H#<<7~1;hq#9e`A/aֿ %ǜSӳeUFĹ$/9.$MQZއnY;/)Xv|(4Ł`wr}w^7m) ɶ('ayѬ}qCGrzƒy2SX%^x{ʭ~K#>}~1kb۝6#5X=kxMV|]WQ/|CRw/V&/_^Jn*$!J.E]->pov+>s0L3uW.IT 9"@ No53%^𓎙b糫Ns,U^rKB"}XU;?Fo.~TC[+ jt'$Ʋuhvm8몜o ЙfY8u4W UYTpHgASy {oݳ7^ !5j,r~YYگ^MvX%q6֡h&mpZ1;wWJ^z&)d!ja߯QS? gf'MɋĂLD"95D!WQ(/OFHæ;XT ̙3G>`rEMFvҖ3;ґjq8C%%FbNˆKQ#?@~ae8"D x}(/#y?,RnO<@xRBٿ<~sȧ}w,-e(iLOПs%oU/9³0cHa> kkY]lO=\o}6OĺeS/9̨~Y3nG/y jsS0;:+ .I+cr$]?r:Iۧ+?Th(_š-KLm1,\ܕ꩜ D/ lN6 VFaɚxǎ+?CҪ-\rYhQ6Zu!Tlb|H NjmJ=8/z/¶XcC89.02V7|f#U8󒣒+-{IvaTTzXn1b !vpՉ+`l(NR?n~yr̚J-<3m%>AOX{V䰽nsdGݮ4KϷ"-ej[{ʳ~Yuf*LI+Fl/ѭz!+[r~H#K^Y%4u(9ig!bHZ:-Oɱr1ڬ6>x~Hi2IKp ߼؟8S32"@@uKk14++M f#b646]JEG܋L<8}8rŹK4&=&ش 66720e>3ve/*,x:lf/;L3kc3C٬r!l'֧dKY1axQdrs Dϙ{{>>hQuc*x(`q\bСC:dA2_vJ #x]7)Î)?,y^rEDZ<߇s7EĽC{ mǣwD؂)D}W vߦ m篰pmlOnꖜ {`lif =]L63ۂ̺m׾K1i֚K+ܽ}-AݐsU?8m: 1"lcnOݩ_UQuiCڈ|ۢ # e{UNawԶ-ҰMBy7PRWͨ_n6}DX$U(Uj=}4޴&U5 O)hv`5z]!%C>qp e`˦ ?뮜S ) D (C9\ڇѨۨeU_fdzvfkYTj}+.`WD{c[- XGd{|p^rt\WdWl>92 ]!6KWpkR:|yɩ:'Bg;ݴ'1sqYo_$RPͱ}WKnX_%J^x䰏K3GAV xSm8[j;gR u!<LLg+|"--cjH`e,%7m|*F |?~VCj~WU7Jq̍)퍤e5Z.6턥;N//MELE3 K+g}.WJ,m*R=U  DT/Ͷm8r C;kz>SQ lyHzhtetm:Q H0k!A2)/۰p_AM‡o @xp߃3X7\T.Ƀн[:߰!|j/<'{s|XdDxǫӞElԝfin[U#pi߫L|f ˷,3gZF@]Ʊ3eQ"X~\Axh0 "g|0y=Џ:HAtuկ]Y5󾳽ZGHf[k_~q7H24~ ٻǁkN؀ADI D'5 q$_"@ DN@duKPzI(&~{K%#D"@7pko*"@ s$0O|@$@nw*5 D"@)ī2A D"@ D"@ )o- "@ M Sp!կZu;0D"@Z e"@ "Pr :&[S5z|8/9*&%5կ}(D"@1Ps(D"@ D"@ D"2z"@ D"@ D"@j Rט[E%D"@ D"@ DwBz"@ D"@ D"@j Rט[E%D"@ D"@ DwBz"@ D"@ D"@j Rט[E%D"@ D"@ DwBz"@ D"@ D"@j m)e"@ D"@@!"//֔ B D ZxT(<3MkQQQ]I<|;j^ޢ@45 CC 'iN%%!: l &PSQpdVV&;( D>|E59U/)*L.6L1<'Z3 ?[~Ґuaxh\SY3|cf3Wbh;5_[\l"@l۶M_ZZ#G/0d8??ߵGA:}qmJYc7㹨tUjqVv ]Wu>|:a4Ǫ X v!*a ^"0v˿b` /9sOXQیx8ϟwəZIè阞l-GyN0MǛ* >cm۾L:zt+~Yt{ŸZI[RxS܇[Z+ÍXخbX;Bɋ"@9;oK^ T58vͶ^H#xО9[ RЬ,/ÞA;|xɑtΜ*>`0LmKNp0@ iw'S/Op+ |;wǫjS^r6#D>|f:$4gll+p ?ۂ:*\'z7h־Pwp?l wׇs5OѺz!Mj񭸉Rfu~No3_j\"+ep!s[0>5lױt"@ ղ:.'* s˼5JҙTtB~Dv0֑3^z߶8,b"/9Nf_~<8q ًWѨM#1l1R-)4գI뭔B6Ԕ]'y1WR1?;e1& Dun)ě7K /==~xk=X/ۏq@qځep5߼[ @`eWc,fcBG J7#/+!a*[c?+aAvg㕅?b]L|Du|i] y"P{r VW w] [1 p VX[.ﶄ{ ^xe0i:pC_&8 B_9*zdn]/g8G#2y ݹ:-QMnǕ 4[d)ˠ$tl՜R/Ǎ+8w2Hkbح^?U}܊w  ,QSa(®O\Uz△|-"hŇ|+Bv4['Y7xvCF_ӹ. 2CF"@j;އO[vX]Q:~1иYI$%||;[9F|.}_C^T";eth( VVbsי+r{ PPeIs[UݫI1\V.{UEr?}=2l'2 EKә;#F p3S*TwԔSe:EB ƆYF+&!ct a 3oɩR+*+麵 XeDZ}bL`ݔW"m{|؊SX6),2S>ѯ0>>LފGG_/lAbA+$@ &B|Wb[;Z{Kp>[8򁺁T a9!#ѪYs:/>8]"$)ٶ4fSV̴с͞0]b/pX%; cȻBE~\zGL֌qL|.s}̏;w4Bonַ܎l93>"Ho[Ш~Lsj0@6.ճ|hy+&4ʳe녷dJC凖ٴvT'|~Sol_K<۟epcjmF"Sg$s A~mB\!T9s]LEkPua[>VO9USHĬCXbpVB[ؕF rE'v&4]Y\W_}iv1$:ȦlV5}|> Ǧ+ׯY(Z`^&xϾ1h"PhKDjCXQ+V@)/j6ѭBZB8$XqV(R}Jt\,b1ۍeӕݶ4}4780qz ۚD:ذAA!ӫd8!7Pܬa#7}K#ء{~_^ g.2_Y o'e<+ZvBJ"#_~aѯx< ]\A\/?rvvC/󇳏:YNdՑ {t;&e!n(EK;2/--Jː(iԃVLnry 8u&„ .;OA˽ ww"ܴEE8j8B HeݞfJ'0vzO wpECZƳsck,E3 V) &ҭƱd X"$ u\RLqϫΈ&G!,D5qwÝq}q [ukj, =??&-wV7eTγiYdd+|}Zbp-0_N&IEpWYn#̃zYxR&4P\Dl]Ru#V֋?rlRAO{;>D]]=>m{  ˻DzTD[oLƣ^xȡ%ޖc׬}S#V[ $~-r9g.ܫ%(cv%FG 1 8c"eK9MnW <$7n۔KWXR+v42A}33"h)2(<~CT,D"PSɪ1A|،5dŸq  >!=^ÿ Pz)Q@RZzWL^rNX ,e}}1!K\"PW8>,o3,:Gh-SÀ;}hjSHw}.r}7g/Sv 3eGdzFeF 7YjZ4iNf<j*p̼j2dg~Чnw^Ԡaܤ/0Q$ܢ.s\ށ1cW@sLġƐ< _{Rj0oDsAC um ;YL[wb|@T,D"PVL񈉉AN 1AZ=rU]^(>N{Nyx5},l ILO[Bf|FfνL`؂oR·)1YM\!"wpoƍوp0P7Řux=^r2c% ~fX .[11=f3ҼΊV =L>ͤ9zϰU"wr2O !Gf I1bSr.䗈aє55~t4_Š)j!bPC~Z0 U~VSϚ,]9T$k/(,1175{BVV;+eRr&d9g.ͤ>gc|zg}w=V(~\ 0<_26%8n b{0U> TO "@B!nUnaoT84]cR ddxLl>l-a`R:d}"8L,Di;aID W;dɞP^r_6OIS`ڴ)x59 !)@zB9[zRo6Z/,;Wdl%qEݫRbɡ%9&og&\gvዬ#qbeLݚ.ךW`TSQ"X6&>r2Y8pFh-~<=Cp3F`ٹ(=6,4ȸ۷";-4TOE\d Dj& q,L< Gmc݉X?)tNYG pB_CX7%vRBT)htʯԢ$7?HI %;=31#0䱥ˣz]R%ǹT)4 5;Ci0+_.Zyx#c5?jz=']'!EؐiߴK]Vg8(f3Q SlF7яAp)Cl.xS/>?9}<*)g"ÝgVgeU$]/Lɹ-s7΋C;]K;5\x%}lE1^\rYkz^a0iKT.^-PJͶ8ej*5> M(/敍c C8|q"S3Y%Dj& i9akrp4I=EyY0vpo<$$wł1{ c,S!!]4|1_>sAK9e֧g:9s!I^r\H"{f!D yjW .O{Z v[>5Z5kf?aUb z4m{Z鶤ݝh4VH[X B=Y ~:Ʌw7q-WI*.<8 ?wf2ڜܫlze0Ih)fRSI·׊«0*? z!d_U@9>}O~1kb[;m4F.-˳eQ8<٢ IH4Ţ\Ci?7qPt9a+TsMT 9"@ wNWY2ѭ*8oOɧOJPVRb#/9Y(sF1p rNH{aL_9l# UP^?9y'I &fzu&/9T{sϾ1UN:|:jkɘU8mcmLnUtKuQzCdy|04&᷽,znj{ ۪)+eDr D{h:n or\>L\ 3O-x/+GW>Ugk{ixO^iJT9*/9Ff<އC۵uGMM9䚶D=x86'd1fZGoŸ2aZ]ҕWmBb,[faf~]ONئbſua;XTUQ/-ܭv=mٯXԱm|H5j,r~Yyv{g `rEMFvҖ3;ґjq8C%%FbNˆKQ#H@~ae8"D pzNJڼ6-)YG1[Ym\]%(-%J1MNp}w89Y@iLOПs%oU/9³0cHa> kkY+]8 Gܚa"4Y_ŪA-]E1YؖeN @IDAT!+>*yQIƕrEz˖$C!aTTzXn1b "l?*W &P8&8*;Eh*uȗ;03VgJiuKN|*sdGݮ4~vGZdV//Jw)ċjs^(%ꗌ=ng3SU ?i/+f8Prұ_YHD˨Er9d6>!CRi_}L7ߊSk~Q=1## DT73`-FCye)!aaD @֦a~x%Mj#S4|aR"cU.r D>=w_4@C58l_yҖnAAp<:h0pR:)D;Wk0]}xo8̀co`e;vK-nɹAG1~QqY䟽mq_l<ä8q?4Ai%pasu^J_2on:ִ7bK]C1TkY!x"+t7dG~{1vh8E9x*˜H5o݁ÅAFYB+\7{/NWC>,Ȯ5_o$6 gnF֚KS) D Hm CZD4kp&m"NAb+PP %9lgu6U 骉_jl"85S/ܽ}PUthASϓM׆9 [k?4yI_m["d6 n EݐCKAX?/Ag6b}ۢ # e{UNr 3W>w",XʙQ83Ǡx4ү HxHEӵk .Qt}D6Ӆ÷g([6堠_^wu~JHD N@9u>dF-FE\.:X7{8_+͢R[q'ռݲGn|X=8XD="SÄȧzՕ%:(`3QIXryU_XsKN9D߇M{373q_K-b*Uakrlm'ק<k^ bif6((WJ<?omg+R wY/h5dסT!ݜ7k|ĭ4_%7_f'_U,kP.<ϛg=?lkJ# .vE8pYdH7ҖZhlַ86T4?3FTl?/Ů\+qtTVP= D:hmۦ/--ő#G_ϷYYJHDD`CBC+åsǰo˗XvWՙJ@q_ Oy܆S>2]Td>|k0{MrY( L V 9?|sȎ#"{d%D8E N{Qw6.Ӳ8mJ[$wsiɀ)n)_mK֭(0eeq,g-yU^( hR CQ\p9rݡ|\g{Bhܸ~9ϯ:HAtvksweVYR<7 3eh,V364.&j`$!D"0Q;c H D' žwix'e"D>/95NRNy<:zti,~k(<r~YWtE%}EÿR\چ #fQ:B6axh\SYIg 0\R_QbF"Wo3X?2]{F^rl'g"@j%^C^)S+o:Jx~czr_֗Za_ǣ=m~aQajC0ɨܩAρ{ŸZI[R,c73po U kG(y"@ 22gqɋ1utʢ.6 i v+g.VRЬ,/Þ"+dZ#/9rΙ0y_q_e݇ QaKNp0@ iw'S/Op+ |;wǫjS^r6#D>Ӟ/9S\ yf:$4gy 2@}R 2ʭUEhnHZ|+nԡXXpi年EV/u= nCpp}(猳:a|bkخcs"D"@%`eu\NTL{="n3y}} fk\8!3YPh'a28x~wsX41)qJ?>,Q^r_>ǎ⏓gp-cA\t/o[xkU1ӊ*kfyq=Qxtv-O$d \^rBB@!G{D^~$YHT )=!+}[)(C* Qm#д2!_zz;SPd"SuN]taq ~ai4NdžxPftJ D"gV-؀W5Up:\ݷn=FfJ;_D[C7Ͳ؉HbJO]xoR[^r*.\(K{Ȱefob'L#I DĕyS*$|ƌ]BӘ= 6n3+ KY5F%" d%D^rj񭧢!)ԾxkJ.Fk;}/׹_@xD=uQ"kq;VSa2\q's5,E bgl)D"@͛ߥP?$Le^rgq<,HyNlG}o1;Y!/"@jϼ]jO dy凗Zv82Q VW ۵v),?;ayWlS@"Q)j*m)Wr{&IJ D Bܸ ; r6om5{l$嘳߅}6?|X=Tl>_Cv}JI#v\rfs_"I3xztl\r 8[bNyy1l'7\Q= "@MBk˚Qt-K1 xzs︡RMӭU.vL->c즼l$M),Cz^Wyv 1r<>f>:Ba I/ DT2s_V|Q~ } G>P7𗊡a#ѪXK gT\='_8ZgĈl |+|x1%R.ى\{Ҟ:FѵX%ZK;!7`yiCnG{vŜdR)]l,3G` 4mDĶ`aHCšX"R3S4*غ o̰* ^rJ? Ϗ&c.wÃM#w'<7ZVǚB0D@"ЕR6/9rm57$]}8=mMKK$)?:K֎SyL2 GNf`4lw5c<\p78hnƙW~x/Faݪak6Me%JN7ke!+t=/SU~+J2__-|~ŹX:*>\f%LƗӟAQQ^)l^&^q-eOI5O%U=2i󓈻Jc؂ x[S+<A~d0iAh=wuE8PW3|/+SeRlɾ1Px A9TꫧY$1q<\fG42ELĽMwof?`czA"@F)3՘ZR>LA-\WS4n0V3QW@a}'kP/ЛBBS#*Ҿp;}-etba)K{?XQVe^r,咝BGM/9jqenø x֨ "g~ѢIv2d!'$(PSYc碣ِAf׹:3_f ס\19BjPՕ߀;0f hrNZxƍWVNIfgI[tcHBC/=SONNKcR1Ici-2 ~1;xPo(ႜbEz߶`hsm fGLpۺx|ҬmWe-;1`zA"@FR\Z&xĠSNa }-h. }='wbgc0}[=h}B#H8P.M3u(cS9/Ƹ 3_E1#IԀ!D[@+VfMŋC5&r[0LHƌZö[iٲ)mRWXu0?:Gl+i;aID W;dɞd^r_6OIS`ڴ)x59 !)@zBt=+?X5{V]٬#$វOoEL>9[zRo6 }Daq~L<6z1$/҇שY~;1{"ɇϽz!%_ɚc@=z^k^ *2ElL|Lme,8#{4MF)@^16xݬۘ16x&L@{mXhq%oE>vZh@ L; Yt=5#^rrWSy3ko_rrpB6#]HLGٹ2fꂇ;Ϗ"+ޅSV`{ʼnrGlUs7[#d^[v^un Srnˡ܍:B,|nz^aӰ .^-PJͶ8ej*5> M(/敍c C8|q"S3Y%Dj& i9akrAp4I=EyY0vpo<$$wł1{ c,S!!]4|2_>sAK9Frzbkq.$K IS"@@oOy凗7ηqA'^ෟl!G]$ veU &NOթf=?Y;ԎTBzӢ"ǜ~]UqeABCկWw=u <Ǣ IH-Ţ\Ci?7qPt9a+TKT 9"@ N<ůt>XK'uhR.ƙ4yq&M K7cJ?^%ウ'%(+)Ye,9cWLZ9'=Z͉4{WN4[ȯϏi ؖklQgJǫ^C˙wkVw=u5,<ɫloU1nu4B `iLo{Y=E1l"\[+VHȁ"@t=^rMH5&zfv)yW|t^H 5V,qpۇ5V!'TZ/,*%IWw=u2s˽$:t#<mälN+z. [kTd>l9_<,5jbz)yQ=X"@*yQIƕrEz˖$;Pq0*V*_,KJȉJW{+?xk*uȷ̴ttE?aYRZݒorre\$Qrk(~vGZd,:LEp̟3\ CIklJÆ(ْ]n YfݒCKF&9S^`M@&QŮB Őt$Zƈ/cŏJfU1'*Mc IZf[q {-/2fd$D& Mv! bhd=WVbk&Fdm). +1hlZy qq?s4hy?;3Md{~M*ilbnd%Qe`X8|gO_UYZ#tB;^vff6ǿYBj!1ْOOɖb0q$Ȟ'3`4=||l[ѢU%GQ&bơC0t؃d6 b3啠ALG 4oS+>,cU.r D>Ԟ/9ަq v,?Z>ܒssvb->'Nة?{ ~MxDIJZMIvYݸx>?n<"8l_yҖnAAp<:h0pVR)D #:LG,nɡ%#Y3\)vf32O}=oء)VMF26&cܛH5o݁ÅAFYB+\7{/NWC.Ñ]k4>I l܌M%ĔLD"PR6oRgh@{<y;:I0+'rWMńԱč&O&DaΩDZrC&)L6=V5MKKLht\ZC~PQ w",XʣQ83*Ǡx4ү HxHEӵk .Qt}D65=Cܲ)+gCTBJ&"@vPN&o}"x-Q9y˪;ggm_+͢R [q'ռݲGn|X=8XD="SߗK cnŇ%GzBc~|Xb=W} bMJg!/9UB"P[p{rjOy凗X_%J^VaʟޙFQoI29 `.Q" XFad݈*4"dYda7 5"CpAX7 2!_=W23fꭷ~]vٹPI*kĈ.l8W<\O`ښ4gxEi=ˁT^7$ >iשTsYx]iQd̺ B"兂P´e35׭z1-9_ Ds==+T%jzzR"*voś/Ag+;Ms8|JI]?}rc9}~yM9@rࠇHHr mܸ'|b=?~eM+ImѲuC8{v WmX~6kHp_{6b_+ ħ7z!0oP"ƣwztj-ת` #݇b.}ةG/ A@ZG'~&FYMvDUͫTzɱ)}:ESOd!.&  bvsHƈ"1xQT=?zKUmGyF1'a+ԩS _|mA7$#}l3'QG{|4kw}fwuAXH$@$3~cY )HHH'6d`T ^rf Xn;'   !iɔQ ~LiP T=_UD$@$@$@$/h+A=HHHHHHHHHH|Jqp   $RzI!*EJ]NHHHo4"$@$@$@$#׋aJPv >\/9>*&Œ@@` GIHHH `pS̀TTHHHHHHHHHH@ .Ӓ  RQQ          -hBiIHHHHHHHHH s( 4kǴ$@$@$@$@$@$@$@$@$@$@CTTHHHHHHHHHH@ ĵcZ          !`M( THNe*瑗WeJƂ ?!xKV7 Geld' -ᎺQJp*$l ~#U/9z2H?=5gj'^9Vo^=34979nO{SK@Xipv#FR S=uFnaLRL:`Y4pro]R;Br X gG^  (@ƍˊp|'ru6~=T7,$Wves]ŖÇ-\Ql4aCnـS'hhaо~u"؁G9/8D/9 OtEq:Mɹt1ER/95L<&.1)sX={<^E` *I,X;w,1/}!~7|sð"G#8<: W^ħMh]mR${=` h47`Jבv3zĚ *&{" ӛorw6 u<%b1R'dS"n9,   r ?HI1ih{Ĩ"FަvVHyw1]HXc} |671hV t7!ZKZgZZa9+ $zɩP 7#4Dwcx jo.D0"YT/9nh$@UC%ndVQ ok9hXwFUvqm Фpx9D2y<19Cn ׋}q) =p mX ~/h wfΘL<5wU9|*2%|%s7x˜` z4]-FMDq1(O\uۍb~.   O z/'> O~4yJӸ"qRLOT)qN ;c#;ya\ vND], ^=_WFC>^r*zW>t=(7HJ 9QyKD a߸x 93QY2:uO &XwI R^t:jK/96C$pyX!1\YgodœǯG+[z5Ym~.Ư;6`mA!q<7Eڡ7o0>?)9F V{u.r1<&ڳ$ e L$MЭi 1 ɼ^tBcͦMF;S55dF߁!r\no<;}*u*nx QfxGt0aC/>zQ༌ӧmX` CQOkljX"GAzPG ػ#LbsdYgtmA^ 9ƪ9^rTH 0^~HtP.fR,[Q:a_<.Ő3cw8 [~{x~_L7V9jg0o(1ĠY,yG];=%,p ⾅GU^ԿMW+w;l X` "km:s*BRNw^$@$@$@v44^t:&vދGaUvxz#ԥWv/3K:> ђWͺ>4G/9Rk7{>R |9~   d7O;aú-0juw¼XV}(.guGzʛckb G9WqW>zɩXcb԰,cw y)X/9^fo,L ۝w׫wc< @a%HK7K\.eR">\֧̾+:/whXUE)(}4i@rMȿ.CΡ9pYENs(Af` >6:=xYȲΠAߧvr2fx~EÁx[xrjYsa=?Jl-帹^cމ &]yPr=ne -ݏ: P /gMtNGm ߃H@$@$c [>4+,S,Kc]zqx~%󜝥xzr j-FOXs{/xzɱK? @U&Tj_R' D .N| w)/v>2tVڈ J^*cFTswhuc-#{zF0[տeB膍n΀a"~v^3F:nݲ9ž]޲xdB?geu<-x~X1G|8å3NXn0[obch#l%D}_D mUY 4,.ۢTSq#%A.Zi-HH*bI*̥Vm:_OfϞy(] #OMТ7`V}OYۜ xb̔biekEEToGƢU#mzɑi;cx1;ZգPNY'GF tzaG6_GLFBZ%W g.Şͫ1rJN jM{bwQ*|3{0:G꧞^,G(7k.aeᥡMPءI$_~YaBYO+T)SB c)e Z$(" /-,%Jw'v.BF̘1SC@IDAT%b@G~JvoMfC7jOq'g{=$@$@H e-;>%1*}MTݸЯi̲&xXJ{Qxj-ʛ)t H1ڠjC_K=%㌥)1toU W I; j<m9KѥcOQ}Փ_`#MP*f" gO(lӈ$K:`6کWeG$z;$9VQINE*+}6N=s>ǫ9 l끿,SU#0?lVJ#"44wDwn0w o]t@$TW#Bm+[3o1l1P0=~ɗ[O5\b!aUr_/a4eV!%GbY\EN~ڠ׳Hqhy8{I+Z#<H=gblz/73Sdﶤ1ࠇHH Tbβ #sTJΏKꋏx˱i~LtWlb,:@:fl @&/d~-֓Uޅ^܁1=X^r^rO$py8eP,cXsߤ s'X3@Ǖc4"X2 ?t~l LŨR0n*CNtT5Y`Pi1YbC-l7~Eg=?=nĵky@YdC!ǚW~|oLjŮnuL6ceyX7X>1]_G]1$=~T 4lƍai m(1INєey<+b'&åyG`e1>C tRv׹]4|(19c~ԯW2?ZN/9XOmpC$@$Pr%fjջѶm[kV4[ 1}UW7<ϩ;#nVōݩ~_-ƽz&0`Ηx)I^,ovZA(ك5_5kĽ9irTaYd% x<"Fpu,qx;Rt%5~SIJ>zT9(j(ރIpD1GEUBYdtxsӧ7qF-oꅍc)܎A$wJƃi3 esI}x#{`w vصbeeF=_L&/V&^ۻ S-#Hm?8MkJ*J2~jg/V.+˦0O5X[{6Rв2V=&ʱ {N+7K>Gİ8Hݡ3](Chۥ)][+}))&DHH& CE៙bth;_jfk/h)AXnQzL=+ؼ+:na=nxzZ1JYNR;FÝmTP䔗{r1{X8'OĈ4>q}$&g+ș%GH -Bax'v`9UKAFkɩ'3g?|GTJzhĶ\jnMˑM_n@JzYd$7e൴ӫzahj#J#sᱡS!yYs֟S>Ժ/هSpH2~Vo؂OrشcKkŤ }5wg0BgY-zdMB2p\TO}K`|d J'>_ۑr<#O(wcǍѨ) =%XursiR,2.:HH*m `|!Fcކm'>-Z>_tKb(s2~OQ!X9d X##+GX`HrQ2%(-dT "pkbK券- ^Ֆ?iTʈO4} ggί]fvmx2]<tggV8ܗ(Tze~ˌ4S&}#Rfe!QK 5_lϥ oFYPO,#/nk>r-gm tG<|;4h䲷> ߈z?WPx15;é=On]G$P|X^,'’f9_]M᛬ց2WZ4LYY\%P]8fygƢCrZ5O.Ĩ;eyiV&Ɛ׉6lq6߱zj%_  J&X*9cϲë+vʝikFl]PR"ڦ!MmY[D4oh~)Kil7Q.L'6:E\UGU܇MR/9^d$$@~K@aYl4_-l(j^FqڜFߧ45ObNU)Qqj*V?}Uo#5N$;@X|zVvM V/rwJ1K_6ȺL~L|6}q^g+KKP琽^rPvhsWbeVkƔc[W{0^zC-{ltoa9,ݓ8S6FFy eFG!@WK|iooAiQer+7SHuQyBy5򁖖>?m> ǃM۴sO\|: a O O4[$m?[k.ȓ<|H7s1G,$Vжט:sֳ%<|qN]i s/W丗c 3}sigK)}z5Lk_goސUJ{[bb>2>tNMЬIl[z_o|x*?Gjb=$4J,*RokR hVZJOb@]WT *=m.ԚGj2p] {+˞-XFkǶn#2px2J ٘gؔ>n۟wh i)jg}n?&6]ײtU1~=[b=u # h$ix3sݴ iOPo3ܔЮn]r3G\ѽ a9[K<. 9CF,죸K[1 y(fDU,.4  .i,*[P)hz!6^rڟ T>_SkĦPv?vKL]sş:0. m k*֎0Dzl- l.o)!zy5) LJIn-퍲]ɺ'zqDu/G 752wo6KwnC1>~N6i[6xDR@Q/6V+P2EX37Dub}N2la='B? ~t_2D|)AF0ko,Zo ~!eRgv,#jGJ֊e>Q_.o܆1stl|e܊NxDž@=xU.(lx'zS}’%"IOT};VS9GJ-}?oWIZVRr _*ft T6M#ćY-ZiEoch 9kța3|RΆMѪ=h# ͽ_c8y6u['6L@]yR)Y!}]tR/YEԨY[rgpY,֮` 8}pC1^_8uIP_=lc9LsMrj>0hFۉ8tWzd$*Fڠ Gvfơg1s-K^;HlZ~72GA8<ҧ/o} VZR>N^7)zY6v57—ƃgŽR&FiӽTwj*4aR ^'&Fe~GkvC?%(+kRwɦ݈JD?.>2g_8^b^[VXJ|w8f~]8b='K? @%PZ^dӠj(/>G5P'|ulY}zɱ/ CC(%D6Ε"GᛲlDA8Cү*H" táKNŚ0 @U!y6pY^Th{iKr FG)\+/%Gg◂By&L#`\% pד5iz /TloHMc~_KBTAEŌx )4(IfWl FB1 z^X~ݪܒ.B>7N0$ד2hurjvXXTz3q4çԥsܗ+w9SqyNf`  qƲ"8p|)|5}J|J*&Eˆbyt4" e2Cر3_b%Sa۬ b#a2B|و8~X*腸 3ӧf@8X.}Щ:_ÃQf0 L`wc$@$@^xtgb4}[:~aD&s8srgW/9yģOZh*,ù DbA\7nC71Y$߁o7 gx#Srѡ䟐Ҿb"`4M\ے3}%'a+ԩS _rULirMFhU?gNH<{x*߃:8  A=yHHH&6dKk20Ыe(uA0֯*|qY4   #LrP   p@i0I/9f“$p`E/o5$@$@$@L R:CUW, hBEHHHHG (dx}^r|TL%&Зʓ @চs( \2E =%         4̥$@$@$@$@$@$@$@$@$@$@Z Ӓ  RQQ          -hBiIHHHHHHHHH s( 4kǴ$@$@$@$@$@$@$@$@$@$@C0RQ      2⑜\T#//ʔ!  &Pe )CTԭn@II b˺x' ԯZ5pOŏfzZ/9z|OZ6u&TIغAF^r(e zɑٿS~si'O.^O{SK@Xipv#F>|UYJ(P0ۃhuj[WGTP)ّHH'qƲ"8p|)v~~~\&Ս( ]Ym\Wa pr+g6 .i%_ݩ+'vCoNK>K`S]ѺijENSrG,{sB/95L<&.1)sX={<^E` *I@^rd}` h.n#Gr~W $~W(hk1G)eP0sBT/E k&;ow!֏{,/ ;T#GcY@A[P"  PPV{LJIC#F4*6ϵZtmGλێBvϦF\9!b 1/BQ렗L)?7d sV9a p3BC4kq0_BԮ #5LڌF$Pz^%aY6ǔJXD'9^fo,L ۝w׫wc< @@;rn$JP> NO9 TmP0\:.rIC Ğ*DpE<~9D/gnmKgһ}[2?cUH{N^h%١^L_pSya)PUx;qKv]ENۻl]b=u'HHt& `>֯\ Wvb*%&)z Or=[vnJr':~LG%ҫxY ϓG*ɮ.丟c1i:_*RN N@硛r nH_6YĒXCZKuIK1'nOG.϶D`wybѩi-O`bj?LuX KL O>ߜr q}(2[ogω[{5I g ni}<OٳeϝAzoOdZp`KNoɉˇU>*rVk=]IwtȑHo1J]9nA؅xw"tx"IcG1Z v=RdX,FÒOb;DCKN%'NƟ6!(;{X|ZIHHh2oɚ=MRãKFzB |CW^۶!޸XG/9>+,S,Kc5\zqx~%󜝥xzr j-FOXs{/xzɱK? @U&99ž]޲xdB?geu<-x~Xbd(Nx3oz^Fd)(V2D߆Vb:7]3FR0%Xsށ^l󰞳 6d@5i@ XkFֳ QCX{,x7Mí711|5Tn.y* N@_/F_kXЩzAZ멯+:N@ƙ̞_TL_O+DT&aBy?"4ډ5I!uy2ag ] "]jG/[e6ªW,MSxmQW*8 Y-ipQRH3lmyG$@$P9KrWa.:ƴhq)q>\,n^TJUHl(MSGu ddOFH3Ua &8aWQzɩ(x~doԕnqֺ jK;1 <99D4Hn)z==N jM{bwQ*pe=P7J̛:"q/Li+&CL/*BHdjx4~;b6iKMËqܨZu:=* 7o(C*`oUY4PoOy6Cbsl'm6~=er ^$OfϞyBQj⃓)SB c)Hq4,A%1\Di1ĎEڈS#c:>x d>;{RhOA=LwuX 2x|ࠇHH AܾaQweǧwu5FOJ~56mYϾ^_i/ OeSRyS4)FRm| t}}Rx e ᪖bw!#y#1uc# [^CoI-IZ)ĈZ4wFdUl wvy;K5T$ 1 Wf bR}c G!K,~п^ Ä~2K^nb\b!aUr^i oCJo#ŲHBg?Lj)Ag& Op/VLGxtm/{I^ofݖ4S T"bcVg}9rcEeYtWlb^rO$pyEκsGŜcbbj  S[CϚԜO*ԭBWzȉ& *-!W8sS΁f~^ ?Æ/CΣ8/ +'볱v9B/5uSVU>^Gtcøq0,m8t 90oX)eo/荡˳ v j1\xƯYN*#2dO'ewe}Acˇ2)@Z1?C׫i}U}-68! D[3h۶-ڵkgk+ -\ΘcA 7F | dgۍvw/R2.X<4"%Ǔ<uFW _Y#5ش5GIzD %+a.8f @@69Cx@6ْU s}h匴Nx&7)hYis:P-)M$ѡ'vmjDM&oITke]Z/ra_E1[}ey+1-r3 ''G싕ʼ ˦0O5X[gSв2V=&o;9N+7KS ,:Rwg{L2#ŐxsvkJּƊp_CYOet T20;Y49 /OA6Qj& xAY阺V5ZԳ͋bf66ūbԎpg,Y8K3ٞbı;q"&OiH}, i HL ;V3K, [^CmrRpH2cV7Nl'9l޳b҆ixz~/W'6ww\RӠi9hץP^ɩ lx-,B^ f8Q,ꅒ69_ IwR/cl8,ӷ~Qz6*Sf AO|#mJ']}rqz4j gbyO.i vEչ4)wvS$@$@L? 6r0|1Woöăa\-Q/FQ^|:%S\F9mR'訐@`\JO2~,zɑ#/g&t-*Sxz KF% z=m$F)c7⹞/O㕅;T3!*S3 & *=\+.ei#ӧLGT#ҥB<7jt 2Kxbs?W5?WOn]Gjܽ_’f9_]0?^z-nmIdjI4Uf>S<;t~0{pٰ^ g.%P]8fygƢCrZ5.Ĩ;eyiV&Ɛ׉6Ϻq6߱zj%_  J&X*9cϲë+vFEQ+5ey90oW& qw\6TWҫ{2+:],-fsBy\XH{?/c#L?)Xu@ene/ګU|^B/9__/=[DH4)vX[9ߩq\i˽bX>H{{5yc;n۟wh+AV ^ȵCw10|h0 ^_@S01 F2B<tk*wj=4 qWmF{IS1~nh+~x# uQ%ǭH wSb*ݔKl eGi.8 ԡCfe@ih^cLG/Zګ},^rKĿC*bfpyvdܶf KBCHsK;,fh8ƛ8.<m"}Dzg,ڟ \}b}3m::@  ]nݽ[5̕ChRUY[?K |wUJ֊e>Q_NrΝӱ1(n=Br*Wa )V>؅%I6ޔedɻHs0yp ITJzq7} Kb1Hy lvM3v 4Q/.j=en+Y/Ԉ|;)OO^i'~X^Mr3ΝX|&1re['R]v[O[G7 WxS4 ƚ~HVӫzZﮂGݓ>꽐b/s!R&'鉪'5j6G>i0|K韷O]eFSHH1lI$@$Pԫ6x9пe5\+-5 :V=fHH8_>*y5lVMAah(?xɳA>!fxo}*FM€ؒ?bv㩔b3͜ElN~UʥܢvSKɥ !I/96yYG#e6l\`V (ûKM$媟֭c:k(];v?P^!jm޽BE{yٟM#K:%G-n M@^r)zY6v57—ƃgQ=.=M҄0͘`N]#!.=l[t$fs`Լ~89C~EgZ9MLFB2F?Q٭s~e{C~7ѯ^$KEҷN=ꩤaU ]xj)2=secMN<Ԭ͍Ddl7zn،KPұ3eIo^yʿqE=aEq"Vpz0HH (Y/ib[&8jD .AkJc5KAF[1 W(FGM*!6ΓfǸl<6qR/>zq{6 ^=efUNO);=)-С_`7nY'է< :zz+g2?w5G5GMQ;8H]nb?o]>w~&T˶¢ЬgshPErN}?vQYՊ5vCݴsl2 ꅍB~ِGz*i—=-UN|pR$ՎC+u@ɔPg24.Oy+eוdDž+#r.)˶~N76^QS)]$@$@Nv8)됹Ѷgpe~Ƨ3Yhae*XvƖcrXoH-)~c+c|nJPW#쮺,qƂ=X9A^r*ք1H zɑ4,f}9Tt_} $k]+/%'7sKAZLU (3&݇Gˏݻ;% @?!}+Dh4w׋ oKo 5^a.Gxtgx/U-`ѰqIԎ@q9ڹ b? f>iТ' 犂sl9hmr7#ւNi uTCMuU~y9>ZՏŨ#=>v7^:AR @ ^c43s{wc"?C6h=m,,2tC<0+H PBV@G@<q͛ ~F~ԙ2_JG ~6̸u2h0sevr%C=e-s ?uW"<:m3ycOX"ɩ+/1RY$呭:j737\PE%PߎryP,q(@ P'-WIϝ-GEl.\?3lքJ*JQנAJKeVhvm'ڭ&k ۫S ,> E(?W.ED 6^y[f`dpI\H,\IEƘ؝c[O~[r듸61K~X)gQ;N"݀-?Ы@\59I<u&p^ NzY) P(@PE[$G>OhvDuPBVR1=mڬ%"rHilp K3:[Nq|\LMWȉE,C;;[ƶgG4,H0 /،U<.6f )@ P]޴۠Rv]:0 \l}z~/4\P"2NtQ=5K;ټp9OEۦ(@ P@ h!^ue'-u-Dx9{]+3Ơs g_*Q 3,=[KPXx ^jej%5Rgyȯk;Hջ833)|/ p>SRq<ȭ#΢j[`P*Mփx}s֤gS8_!C7P;t|̉^Y`ՙ{C[qQ*/;ScG!'W}kV`ER!Džd$<Yi# 'ssq¨󸐷|x|ɒ(@ P@MKEE*3Eg ixnT HIa?sκ@ ŇU#3 ΈUrV>.s_!򎏲OwS+.4(þ?`\1~*/ nL' uO5#J|}\3 wQ5-\EJJ,Ͷyo2@,rx~=%^D6KGfᎪ㫦w(O P(@cbDz<LDqu*q")ܟZ0k3sVBhX< Y+м̨<LZAH)T[ie/ă[ 6&fS1;g5v$C|YbڅS#DyRDlSZu;1SÀ݀%uNFQVnUW'k*]n GqE )%eG>V]s̍צ€' N@ y,ѽ;wd%j9.DD"y'F7CI%n ofcP:RR_ |smsv8/nڮ=D ;.Z;  -[F6t\)@8[vnzQ"}6\/q#:/4D[MjcFTT呗s](KaR57>+MVo u#ժ[[.9|ǢY|[$z* PX'1]"U췯Tߺ6!FGl~#b;\GF#9 sNU/|X=?@/?w&{VU| PnbD7K_c1.bevŁ>Lm)9O5 y"(~ʹ@iF [ﯩY=/T'V{ѹmmFTP'զ|]T1kf{ ;nn\1m |XU#auU@w>yL%͸UwUbI`|\۹g%?Sluuf< P(pQ 4K _9ϋѶKP }~<鞙˔.S7jOE/IhWZ!6p>L|NꅀDgQxw4M5$%gPB0Y}}Ei/}܍'lHO<lҧ Ka6'@V~ΈBڪF+(@ Ԣ{WmUt{9 3}~j^dǜ{:ŻQ>gNZF Pu5YϨv ] v? :p׸NDU;W.@ xyQhg"Ń,ă,+qzr٩(e ;w_ߧij]TT F#=bѤX$4x zhLuQhЦ#r{-g@P~駬QDžQs1(@ PxMG299+::u*aJnV:m[՘j0m!ƧFUbs!*S>val3@ Þ]_R]Rp\O>V[z .rq́uڭ u2fo]9xy4ϲA}t7l%;$&qaoDž͇Ǘ$)@ P)DjGj`޲ܒ yK>-8$w/' )gq*22k 4:RtlwjWUs$=힇ū4uWN9@ m{ɕߎv[f=Hw;JM֖6=l̕r\b%G4y`E s[\*r 0Ԩ¨|݄S(@ P#`h@H:Zu UG߸'1ei=Z(('_,,`UؼCy!j [\> ~E\ ʁ:Rtm*+Gr(z}ŢPd@=85Oy A}|y.:RbظrX7YuJqOڳPjs7xχ] (q j~%vTW>زfϸ͔{Lw  -`>.u\ +_*IR(@ (͠\T9Ekg&@EE-uD6> 릗>A2L8gTe,÷'D{X7Ȃ}ce R qRlj׸bB4./>;wƷǭKLBn~[uyO#n݋{61;7ñh ނjE,dC>F67.zGnD kوGS ~i8D4%_y^e|qXAߤ]ػ}ı5K@|˖hk5꼍rzP! o S]qU;aƶ!yX;wS|s(w0o+ZDM#/dz 'uFR1೮|CK]vcRQ( ]pyT$'6 NƷBR ^2wz^U梢x7gxPػ{OyXw9~!fr2UxH$~pWubMPV]]YҥOu)GZb^ qtOt| hU^r :#gQ+Go}}SĮ7w u^R n6ɛ5TMfKқ(@ P!#j̦bA)@ P(@ P(@ P@Lѣe)@ P(@ P(@ P dMłR(@ P(@ PqY P(@ P(@ PCfS(@ P(@ P(Gq=z\(@ P(@ PBFT,((@ P(@ P(@ `@\(@ P(@ P(@`@KCWxxx$.?3fɘ7c˹c wa< \R:A (Ο l=gaȗwl(@ P t٣(呭:j737e"V=o=qd[yJDR@ ^=Wsݗ-~ :_6IջR|Q1FM0 hgM|y>Wp&"Z:_ +S.xۄE(@ P.PuT4}G0ܙ?ņrsny v|۵AR\6j[yzgg L./eS1%vp5(췰'qmRckR΢(,Rp:V>T+ϻ⣼HHBR ,]:u }Up!+~\GzkuYNwGX P(@ +/dŠkv4َ$_"[0ihܐO/ҾkOrZ5smW#A8 npifVgˉ<"WωF쮵xa/n*9]|ڹ*-`1|x>s3 +<8&^˚/]^2(@ Py)DU +pG7N v'jM)O Ӏ~5|]@OObɯeF@p  V0h^"l[9.߱.;a'fpJ6o:rw)w P(@ #<׮vn1Y|Fy\=q㲡hj(*8Nh=uZM"Pvz]98c :hbOؒN @V҃@KPXx ^z"B 0f:_gDS_F},ab)C[v, P#2WWI;uV[=#iNE rtfVMŀx4G¿3F?ޙ˻3 ΈU˜=Ws49/~ZqAeW~Hp|>>Ot z̹x~$1J**`ZEW:A85cb`K{PEh-JtcENa!P\#<]j|;['{yG]T[3?|gx>Rg О?Wރ.21Ʃ>ϟ]w4%s%f7w} r܍fGXEk(q,1sUù(@ Pu&X@<ԇs{@ǃ/DD"FNeϠGXſ [;Z#`U8>`Fg"hXy29Oꕹ@9=F *Ekχ;B^N]ik=Z; dxD|87`20MA{Tpj:݉t AY !/;O P(t1;xL4NKYj]&uR:mmCElFۮIN.=KI*MX5H0+mt AxbXgp+m)".ݲlÞ/!WgP+}xkwW)7g!}:g*ѼMǪF^PFɵD0\[8k)"7AӦMȬvI%w`-tMV2εDD.q9+NH4-Η[3w\Äw?0l-۴F|U8@@E}zmxgׄ b!w￱צ #oCrf(Oƾ/7`sk+0py+E$bw)5ō,Ŋu*--`x ~> c,><6B?I|vnTgQ}; wO1nD[fGrMCΞm Xu}U;4q.:G4P)<ۆc(@ Pw2q~^#:VwyۯC٧J:ÝK?.*܂Ϊ.\ۘPvf 7'NxS4}J*n0\QS}~8K^){7c0.]["mQ(?sVu+"~BFZZY׹psb}is.<5s0[N P PN-g`_\5^sWNyJ}z [|VԮ9nՒ% &#lQWɬvylϧ(هY`c[OC'ǹ@X7""A \sVzZSG<.sC~XRˑe)S./w/_y 2.|=.֛/$)@ P\|Ѻvue%i[zkT.Is!DCxl?K]4yf UK ^e&ӝOl=~44")?2vo9B3#)=uI_3Swpsi[󱝛<O=Ʌ0p;_'Q(@ HĥNr4Z_7gxI!yj3͔ m _qGMd#{֫q&Ե-C4l|0(@ǣ얃RU\tȗ{ I }{T <әq6̉S^oU4^տQzA\2ٻ$]trnU5BGľgtytV|.rK~/wB_ izHQrEc130ѣ}3UтM?]G:L<gQp0JO(} t5 [QDžQ2l2# P(Pok!?{+:]T$S-A 9ï1#cqk56Y sn[qe\Wʭ@|۶kS>O=Se<csuY7 P t *]I좵x1|[P^o]0h>eXgMsl%:d&?KgRWNj!4En={$=.\kh><\%9N P.N%Vo{L^M*˺ %&ǣ.56Cv6p`\Ājmݵ:w>TZ_t[&br95Q1_埑+Xi߯ s@ XM]TE8=6ƁGTSޮsOKK[CGAOae흔*WQq_M._*T-ܳP?ز4cW]p9j u\;/wN(@ P@+7A {b/1YL47|=*'ȩoܓ2`A5Bj>ڣX;||z͐fΝC֬^Ehgz: ߅9=Խ䄗ìk0d+i8 rԑZ`U[5km 8s\ 97ϹY "3$t% VlN(-+>o?=|w-\f {Q]0u~36λ=U  GlbG9ٸٯ|ΖiV+.4U#qo=Z!kH7%+T?;4]}i=ϡ9: iq#_"(@ P͡W1%<['v; LX1p/Goۭ29L.bU!^2Erg㖌eRrߏ?n۳;ZWQ :r,`g5[<ݽ<(kF!G-S7$kߔ)]()2*im>n7Ot| hU^r :#gQC8URuSヅps|&.hGUyȸOz {_9;0(%]X,_Ex˹8=Xj9)\bMB (R">qG=ʇǗLB P.^/o8~ܵgͪ,ic1*$6FIQ/ǧ"'@ {uCa"vo} Wu-3?r+XQ+Ν+C%{]hUbyf`{Xl[Ml-i!;ZETqC4(o,6Cϛ+ѭ][4k⻸;][u;YT<2^DLx8߅x2jUf-Փ +&.O;".2%%ph:q~aT>X0,t$U)>Ð$9 OMK!|S~C;~x:X֮;!Wo(@ P]{֜B PC@Ȗ*B Q KA\ۃ(@ PU@g)(@ P T>Ѩ|R [(@ Pu/xo(@ P(@ PjAZ@*(@ P.(cT>P^ W(@ PA#4%aA(@ P@ \VʳPLJ fT>5TMfKқ(@ P!#j̦bA)@ P(@ P(@ P@Lѣe)@ P(@ P(@ P dMłR(@ P(@ PqY P(@ P(@ PCfS(@ P(@ P(Gq=z\(@ P(@ PBFT,((@ P(@ P(@ `@\(@ P(@ P(@`@F_{4FEE[:s/X;wkj>Qbտa㣱Vﵳ-'shT.B+ѽ۰xgz^ Θ}: Zj9?r870<4naN y5a(@ PbPuY`QZ*254ȻOސ Zl븱ce+M&Qsn=i)J/e]*2ƴ\9Ag->k#gl^8 TXh ~RdON\WPKl -5Rz5jtp:?whBqMx $8Z񂤌R , tߎ%uW&ݡ%u2# c{)pCLJk+>;$$uҥZǙ0C˰L= C6(@ P(@@3'bo;AF{Ѿq v4 +%%^X2嶫UNc[0\ZkVg^Q:~Uin>9D/Rp?h%AJˊ`,f\xok}<`x LU]+>ˡ\D̬j37IM Kmw2 z=4Y$=wSg}..N(@ P+P@\j~W;z) ϐ>=_'i5ʧdR P 2NtQ~rk w&߼E@%YpSC;YP{o۶-jx-zyꏸ,R ɭY.aBp)벱czs] #.+/ϊ(@ P(@ #O:KgXzj)~|ݽy hIM _̎۱3Ik/ p>{M"VZ*| qnVG՟e\ylꗻ%WUk<8c :hؒ.gZ,4k Ϗԭmm#֧싒¨>¹p>OfXi/d=[+,X8g[ڧSܤKښ}uh+eR?CɭUY eߩ tSl< S~ [Jd$KŷIZje嫙`mA{i15&ߟ-'&-izMiL@W~^ZZ{DÓQ>ʁl@'q(@ P OwS+.4(þ?`\1~*/ nVL' u)^9$FiZID[Vqsxc͘;G*=HO~8X(E%jh: `v#"t,^0vmqVu< N|M+a:'11jW{J,\h kr?ӹ]q}堜C RU#˂YsvUgĺeN+95mt{=:%"Ju1̾+u|6.RW_̴ixnT H>vo0ZW]{Q1p>n_GHQ3+j p{=#y>]t=?-ř>3(qE0|_lY Nۍynk]}xfXwWbW4p{N뚗2]Riʧ]ڟSEc>c7w} r܍f4zP~^OŒ /_QYCZ>ݶ@`l=_:m9Z>g w_OU˧8O0n{Ugp>(@ P7 6&fTFh91TV  T[7t> ˙@s4\MhĀ& .F#ď79QZ#0Yuh6;wƻ '&qR^jh]xO|AS̍צRqcʣw;knܻzE7YrQMT}ZZ&"rPJ] s0z tgk> é"]'uwn\q/PS{IK`3=f|!"8qv.{L+Bv&ؒw\ b;VɫډnsOgnKp/tQڙxfDg9A7ph,y% wɔUan;1D8yOߟ;_jF${֎AǧݳpäZhn4<8F P(P=k7,s > 9*ѼMgҶTxr2 N(>"q4mJo|Үu]p[ 蚬v%"ti]iT>ũ=u4-Η[3w\Äw?0l-۴F|˨Uo\6liL)ΠRP[h횬 t^%H"Jş7Mpq^r {4j^>5Kܻs emp ϝ9#Qs#^wεQǻj4ucf{Y/(u Ǖ#'=A{}е.U…ׂ?[ۦvnd6w(mp^$Ft_hƍt|%^?OBX4GD4wRyz?}_n:Y&!Rّ>r:}8gwWMC^ۅצ\>?kꄕ_`xcA ߍ]?*|%`[ ~nc m/ծA P(@j@>Tș.zZz^k~'D-F9P"S9TqnDeT>1uTO20kto!\Rrb$,i񇌫q|wSC3^ɀd-2iS>D3h~H$9QaK1!E >@|mq‰m_(j;l 0pGl|9ǀxQeu]BѨ<(\1F<׽K΀vwX;gX7J_X5w9TYSîg7~;Bw#cj$'_i8|/d}lf"Lgf.ً0 Ln\T+ LgU@PZ)ܔ H s}=륌HHHHK)ĥH% y1FU-Jc, Ll?R}Na:k) #O߅2D.M$~tRu9̙TFQk8p&aR;2\E-7˕lL{ ~!c/9 &zo B3'icÿM}[=+9 ]VK2eJoev?S&⹓9oS]5WՔUXS]jm@FrJ'4Կ,LG}3e:<>5VNœ7'7JaDE2zc &?F.:1Ɗ#iFa/\u7_>E8:mr3aJq̨9cℕc$zIX>,"N\Ms#7(>/FTP{y=O*m#}uvZsK[Udzvb15R"XV͈>{ԣCsw(#_*% Y&"!t R7žm0H,0q^0 @y T1 ׽Rۺ'r!93uកH$_a\EFDST8 7JXjеdSI211W~Y}O jç ~nV(́mIP׵p}ݪ@WnɲKҪc^:I BB97RlUce>E7],^9==)%+;i0>i 0꾀 _#(1coaa~^UJJ;)EVb\ػ3fٞEX,;Aɶ sx)e,◔,єW.zK.szg[,߭O.<)<;V'FgnU?M jw`j^O9KpAeeRַdQI|A#'q>1D$@$@$@$ ~xSC9߀\0!eV"]N? \wxoN7*PgVQr_yBA:@,Loўʸe*k9,mGqPsD43qڏ/?_Twu_HoXT=|O(< 9}O0K"&(J=+Ļ5;mP\QJdbҠ}u5z'%`%nBB4J`Y៳ t7ÿW(*!TQWMrwb>rŐ-`ix~7s`>j}=$@$@$@$@'P%WDƜ}oDSP)ِTnHqqn"XLsPit#A[ )CDs?%VH5%N?_T@U)hs7i`,ժ1[|P{l}ҭrb)c~BKb`4kIϜ8}u_&|M#O6HD>sx>⃺ͤDGq]/Y'~ L޷{Kym櫓p둅)g:[U,mSm&e&z,RJ`YLV5?~?e sh~lI|鼘 |al}=7cL޼ `2@   VW]l<+**ɀGNSm/jUƷj\wUժ\8תcq-7j) 1$%JIGIO˒ݰnI TÚiu6qI!&r>y j@`c3 ~@XF@e?\R)aѪiS4lIciEI-š}RW]sr纊_>O <9bxtK9gJ9b]i*TA Vxjid{hKhy4{K{#   (/E/P^ UܶQT.fNde)Cۊ>TT@{u~t%>صsEp.Ǫ3b;ל^"{W\f8珘4nP+W>9ӫbqYѤZfY%KKdƶQh&**@E+3 ' |! [S)_bfshaE%?ʟfmP\U5nWuNN)z'=Y-M̔s]kL{u7_KVYgUu&[}2WR)؎"V9XO: 1kDD9$H$5 EBRHn/^Vdk E~JuŲY];OڽȞFVoJܸl 񱈍9+͈Y?`v_.-Pܮ):N8w_XL 4_9iUhy*g(M('c 8WI׫Z I~m0lzꏡW)c+ qVͳ2;YF]MXNJToq\ =9璛*7Um`ٞUi7 㣥Ua ^ _RK.򏇚G"s8S-BEmf3||[U^:.]PU_{3biu07|NGq`W/d:3QyH(74`@{*} 70|rÀ~/?mwfY/OQpX9xޯ=6Œ_lPDM#ĿiNC]<ݑ6'C}'(+'l}\qX?|v̭`񜰹5B:N UGhW}m|XR&֊hZdf%1B? ;>AX~=F: pe?YbA^:K,6T"zb}s '± ߤ nar ֑i}aksL}M{{=B̳~zFdxN_aLX\*K,7/y3-l]#e 5 ;V݆eqf3i(ul$ː:tRۓƖrxf* gxlهm wAb0(}'~hFwwpQy\ܹ .Sna|b}O0z5!   ?4f`eQ6 {1bj/ ~)}yqOo m{yneԬj4*RV_HSnKz)[]٨[?uF{bVx:Tu׸7ЏʇOH31w~ Bއ1^PC=j=~fbG1e~(ŪRGK/SuBaT4{о wE?mF{LqKLsޜlmJ{VZ\zT?.?B0ǽ*UQXa&ٽZ7W9?wB/a|7rz~k~9C BKzB0yTk +u9,HF"\Q3uʝYBj dC5udTƳQ3H-z}QMWKM9t<l~[<l:E tJ~` LXz&~nG3=hCp` @2%7+SSK2ؼJIM5.MxQ 7E'dрy%g!H)\ 5)exMS&ι08Tu]+/Z7[DG1UeܧȝXz/U!^>|է֠c;@ix v2sI '>_<S̥kǂJI^9y`pM(j,Ъ/*~UF(m\WK(u.Kw8{o9Fo;YxnH?Zҝnٽ@' u߹<+[4uƲ\ƀ!CkeQXF7*e}OK7)X|{,ϐ]8eVCZUc<]U8qV *{cJeFg *n]XGjOy|.Q<f+Pn|ě!^0 @ |W8r>SlO2˼-a7 %eXiLykk Lѱ q!?%Gq(̫TML_\ owTnCFo_ s.r8~',\fE9vۗwkɯh #!0/jp4 ELxhVKރYJ-cbE:VBKOĖ_;L{7"[#$X<}LsGo²ksL%& Fabt15Bm??kp⥔Vzx{\aQbЮi Ο.@b͂U=FxXѰ _[ͳ$@$@$@$@:nNNGD$@$@$@$@$@$@$@$@$@$&`˔?6JHHHHHHHHHH*B*_HHHHHHHHHH0THHHHHHHHHH2*īaHHHHHHHHHH #@a()HHHHHHHHHH*B*_HHHHHHHHHH0THHHHHHHHHH2*īaHHHHHHHHHH #@a()HHHHHHHHHH*B*_HHHHHHHHHH0THHHHHHHHHH2*īaHHHHHHHHHH #@a()*)* 5 MI앗6z!^|UVa^Jdq@,ׄ<XX[U|f;D*\6&`FG&aWSz-^EkĢ}[Ѩa0">|}G2Ӆ^?w+$UԁHN³ƨrPl6ٟ?SV&d'h,eHHHHHHHF x|6uK r˱rGXMٺ YXl@IDATuUn*!6>ڮ :\ zD7B6nFB>ZHy 㩑1sȰ8L=Vxt&X6B[/dT^-Lkׄ`gy3cM2{r䱸+,]0@륣9t Ҝ:oc~8~t<mjxN,?NaL  ڨװ6綍]jGHV}0ƌ؂1C~,WM>W@H}' +0htu4VP| N]R$/lj¼-xdr!`N$k Ӣ)ׄDlGsN s W>ĭ +Ŷ[,VIk_R*;h:b2!l6e7eb\($~zu-&*Jʅ_pxlZ +VWQjko<$U&1Xx1>I?f_%X;oV}f|jW;Rp(#u:rޑhl;D_iQû z`+vWs&ޛ\7=ь|ӆɅ.q(O9!      7M1`vLדƇ{4@@( Ҙ"1PacI ٳo{f+'bfw]аuC[ ٖC`Vn~x,ASh\nX,r(/a0q͚)gj̍m!M_?٩N;V6&wԯDLW9ܽkVqZFZpjdhPG8Ujn.B{<-m}qP{T$     0L!ժdH>D0i!S?   J#,DlYy q (exW-kԩЈosHigYȸmIXBҶމyB>Inj8E K:PۦW*IΉ=Xє2|GrĒ2uƊG4WQ{ YHHHHHHnj9#ejP/DNzST$3Un+(Ďuç9998ul2-P[ֱezgl_窇 mH,:j.R(9u \% "kyAN޷^683~kO9Vd #e2P^=~/c㻉_HM>xU@-_˓8\VŽΝrҧ[K9|LY+m%rԾ5y ç#b$aX~KW.]+>ikhk'r8 ~W 5aeBNoLt/n 4GN6[ %qF?]1H^,Gq4|Kgaɞ&cuʣhpj'~xg.k )IV KdxUSdRcxu.6AXxe!5Z8ψK7/:YD* lh=Q X??M販w^^g"3ݸaP-yQ&ªֺ2[Ӌ~9Ik;d:ExX0Gk>;v>Kჹ=uq( xH W^9UCnU;awxDTNG8;nWԧv'|yNI'U).>_9 # Qa*}%Kg|<9m F3\ G$$@$@$@$@$@$@hn!.m$z6[+(Z;k^b,gI p+KFƿYqPWW-ǿBvDnmʮOpr[jSO1OW>횈V5Wž pG4|%6b^]::־Z@O6j%َgwO;ɲE`E/SohYB=1Fp@Kk~($K-}~V7B?^vmWs>Vm4,iZSG4L I5'9uB_ '/ފǺRFYnb{5t?nh?cx:Ǘ=2x4iPˀ y"+u%C)1a$GZ@< >f#~&Wq\71Ef{ߓJw|pM5kqݧG|VG%a)V`\A$@$@$@$@$@$@z\umzJŭa" ]=Kk el:_5nݺX)2mq]4JD!Ǭq5lFMAj)VD1 ɤ̿ պ?1[d \3aZ"*/m1N G[m_L!GwD4>W#RiR:) pOsV34@a\]Xl'0du$b&8[17  ۡݽTj1='BEc%CEjB{Lؘ)y݀̀~{,eP+Zb'H DU+-p٠4!3>e9dxE?]Xb]@.b;Sx9.dQ_NjH{~ۡPr.uVBZ(#;|VD37@iGiIB-pRU$ۙnPW68hSIBm<ڴ#g| RLXk :UlgcƏ!JƐ+K&#_G+|x,` '6g~r TÐx:R /Mcz;z!65OHu %=UG~Kܖ r8TVT;9OYQ2Qu2?Y{M|ĩ^X5S5yq %=Ca      c_$0y;xLSn{E1%mMSt +' FrCRt 8L*ŝBpQ>+m&9)>c"mjقVs1]_] W#id6^ J 17%+ Gvf V\\n6 DZ&-\H  VG )s:,c4 %_ْ{H>ퟳS$;sށ)-FqX4k HL~Tz+ؑӴw"[缃GywJ-5D]_Jjbɲ)VIPGO.A)^~>J\C>_DvZ)'3@$@$@$@$@$@$@)w~gG:hܬ\j<vQFN{D>L]eպh D) $E$^+>ƊηPR<ܪRqKߥے%k-z92%C %83L'U?g/iElDߴu\@|e52?YC("2~k+|^OB|;zX`S@7c(k,(P梸#_0_Cqjs+{?U4 )x@y|{fB5yWӽF۽" U u&h&-.Slej6Dx2ǃ ljʫ|oZ[7pXa1| w[Ðwպ$QhiŸzCq{D{~FL)G?I #C:FL0}^HHHHHHH@V''I5ZF"ZCUHb6m&X:uHHOiawQo?Y竑[le$߀\6xKCʬ-D]NVFr6bcg/]qd 1 W{dm<|L3_C{wP)Kv0@׋76oR|/d)íZnSdRfEM_l];r$L,9!h&$/P=aHHHHHHJ+B5)S 崫2\Qݮ5g*©ɦ6R;SG~Wk9 #ex)dEUZk*4_sN Rܚ g$"bOx-Užh־po#9o?_q:~ iC BNۡ{vTS٠cX{tI/1sJd=(hwp'.A Lutj5_mk!$ 9 J rڇ&pK KXwS- Qъk=[T1[|P\>_9[eZ q 50ik(}c*8oMt`ֺD$Z'%=yhYw{K^Iozm,R썠Wi$@$@$@$@$@$@t)s9(FN۷ʮ"GK<PͱX=>icC*X1 w㉔-k] m~8}IR}ֿ1$ 3Ƹ3fM*C^>,Cn[V83~kOQP`>|/vmdi 톅C1qu{U-ӕcTZ>0gh*3< PbIwx /@?-#Ewbk.<5Iͳ~v6){TO*E%~b+lppfJF`}/ )ͧexj}HqϏ,.bu9,Υ,r)       (rNAm+l/uKZD'[V6 Ukm?CۊL޵Gȇˡ>Hif4faaN/_ ֏b;Vל^n}y) UfOD 6/:RMU/ UFH1Aǰw7 Q;)?\1R\B8W1i :*' e$zpeMϱ^K٬8+"{D*RjL( V9s@U?[O],3o$GѦPd/"[( w.ݬmnD>N%z'=NY-MhTQ      dcXJ5oм* M-E6T:U;A`SDaC`,+G]3u3Ųww\%+F`t0y/e06v{=Jn/T8ƌc0d|u6Y$I &W2'vߟ~qwOǬ8 (os' ֦{\\,PNglE+MIpxiZ%ⶑ֬Q-):=jA|TIl2.IǑ٭CS9ů{qG9$@$@$@$@$@$@$D@ ;h..R7M6zd{ ɺ,.m'?-ǽ ,UG Ub`qLV8W[ jC唣[, "qg|ȿ9U2`gcƏq)!OygeRśc"Ÿ,:Ootdu4_|eٖNb>9jD#e8 wri0~DXs15b3bK뭚 v8#mKؾ0eJx/ir΁bAe=mTY/a[KJTF4ף %C$@$@$@$@$@$@$uVgcj S?'[\lX9ђ$%ey@T ^NzeP9I|~ZS.)ݟXb5ؼwGОjd9 0nJl۵ %'4.k޸0!~$WDlٷW%zל*PIٛq?Ƶ= N?SXDuBK*١x4ydxd1!6Kveq =xaCRƪbTZԇxv??vDV^MʃصxI1Rpzn\U5L_sDj9cESM.QL[u/ȓ,]09aӆFQL̜~s1L$@$@$@$@$@$@ k7̒xKlVHdngEhfA^l6_Χk!+' scZQ Y⌥:BꅢAFhlp?R3[ʂ,4Yl}OVY"!}1sT[7=vv_ȵ<ݑ k[ކVSPy_Bs&~#c _g|3WC6BlbOI%S+'.ڇ 9p);  M.Qh\:z?q6h;"Zj8[m ljeF(UtXV:\eDVN؄~h8[aaF5B:QohܿHevGL -q/=L75>*@IŨVO9aƒxwu{?|wveVNxp#;BXs{Pi-\} &]#a'h+ϢLj8<|_'adgdeO6Yͺ`аt U^~:Z]xtKSG;74l{:wnoy/0>Ѩ~3 g_ŞRl5ecнR!jo WN]7ګ{zi]ږRB bf ŭ]ğKnaqnV9s%k}`ޖd<bF%ށM73]OGˬnLV]g'/k~1U%Z;ܫ ᜌ o',H]ޜnyH?bBvAao9^ ◹íl=lJosPksSXa`Kq9LyU n#d<b4س I#-"ޟK귖mMԯE8ĩ/Sy.q' {s8x\}d U=E5Yc`́ 4[Ă6᩻GY7;7w>OԶd-gs'T kZ@/#QVr2sPU{0-.BqeMLd`ڪ5xO' uF Ǿ4HIJu߹$K?,bl)<%͂{OߓbG?X=PWJV|'s(2"wh{ W_}U#GO?=;;0{9,acBy>St,GCB\@EQJk7‘Q-طpy /̱ؾl}, ]ڢƾ{/ލ *\|L)QۺBo~cr6d؞*ua)/|*oz7LxhVKރYN+6=6!`ƒCpO[Ѭ^=TC[ [qdn]ޚĻpT̽hV&G>q[ĺ2O ChPuX,v]?ûU#ޟѶEsԫ[~]#1ݾ~ϧԆ3 @j)Kh(O 7rM,K$@$@$@$@$@$@$@$@$@$@I ʤͺHHHHHHHHHH*įzVL$@$@$@$@$@$@$@$@$@$PLڬHHHHHHHHHHBg$@$@$@$@$@$@$@$@$@$@I ʤͺHHHHHHHHHH*įzVL$@$@$@$@$@$@$@$@$@$PLڬHHHHHHHHHHBg$@$@$@$@$@$@$@$@$@$@I ʤͺHHHHHHHHHH*įzVL$@$@$@$@$@$@$@$@$@$PLڬHHHHHHHHHHBg$@$@$@$@$@$@$@$@$@$@I2+Q6z!^|XW(M ٫f"ѷ߰'(&M%^R:bŸuEp$~Β  (,l."^EG>cl,skgFDgpxzxr mHhPX37&1ﶍgaYN-0j#ǡ׫/ԴsozHn}{k>㶀 |G-t#$@$@$@$@$@$@$@7 WKL_P_+-NܩA▰ P"r$N`GNX W=u5rgu,^#OdNyY-88](R}VY%J?ɤե}QRҧA\Ǹ+_.6A,x!ӑ0/Âk+JG1J:nYu͸V*.3R~~NƋijJxx.hY3vyϧlU B]'-mQZ5TmL1 ba䨚&C!ɻms˿ăagk#GdK~:8;Cp[! WsѩY @@s6@A5:Qq¬?#AY,~ҟ5!=ݳH⺡F6_!M;w6eWG%2ܮܖTxSUG&P610G\Bѱ5}U+q(> 0I-n/Az}PEJ۴sxK^Sdg|t6V=u0HHHHHHHF!`qw]ŭa" ]=Kk(Ëqq9wW̊ &VZ[o2GinlߙCGN|VJ 1ܬRK9?Qӯ(u]nWpwnǶ?!OUM.l8tlhSKɁ㨅"-zN:k ST'8>$.ţt܇GOk|,e2"~TyxVYUm?ӧ/)ʡ7~^I^Cia`E=l /|P /%oB!0dz;ud:bN;ȹ  ^U݃y8M#sQ=럳W,V-$@$@$@$@$@$@$@7" Ye >3>sX&/P)*%׋C-u6p,^}"w( 㺅= ٻt.xLr?BU޹{ EE~ CQxdbPqʑhf4{k.DN>\$ۙnu:2L*OG8} 񁪚h5dݠdoҶPmZVy&,ݵɉkR}$vG34z!}JMnART__?Jz?Ʋ-=Ot6|T1=-n^#5^iXqB_=gΌqtl#ν7uLZ8ۚoD=cocCDҜK2N$@$@$@$@$@$@$p0L!nSMµ;xpa?$kw\3P҆b>Er`"Q^Rrt 8L*ŝBZV"L*R8v.Ҧp帘./Q4j$Rlp)oV2n莹1,]P(0kjEF!H9!6ۤWi>n+h| p̍d2"81a3=E_T@*}J$@$@$@$@$@$@$P S$fuиY+4kbU*?V(}Θik^4j:1`ak~G5~Օ1iF~^2q+5KA U :c8]FIb [<9 95I4w6q4pN @P^VsbRj .6cz\dFjbII zINёC}pq0j)8xH(#le|YH+\gwH?0PNA}ѱe=^\~t_'%Wι#&A';O I5f;,W[kK9Z5VV\׸3/ɭMql4Zٙ &ɩqx{H~Es9|٥%µZfڄIv s9IHHHHHHnDBlQ^\U 7WTA,rU;A`JDy!i0T.ю\%+7w]K`0:^ιfalLt<k[u{EgQqwmcֈr`^V uTBQIA⳧5|L_zpuxJ%5 E54䟳/MY-.62n(/]af3F$@$@$@$@$@$@$PQ rEP\\](nAi<&ZI Gd]OƓ˟#*1O0ٍRȂ8&[qW[ jC唣[, "qg|ȿ9,1)[ǡPT|e1^p7Ž_x?*Y~JWzmr<(%J'r}O ϟJ!$=Blw%l_iI>m\       uVgcj S?';VEݵ(I9tF(jfDZrtY&^FloVe!V#YОjdIO^m6!Ed-S&Oj-aQO~5g;oC'O^Rţs ģ'c w.bTU Q$#RND%.*2gQӧȂlGVqUٛo8Fc8=xaCRj0Fظ|]k qkuo`DecMr n.ő HfgZ~Ou5;D{qZ q6 b>rzܘV:Ł]u(X#^(4jF6 #5c@^,B'+k-m{Ź,_7=vvص<ݑ k[ކVS y_BP۴_lPDM#ĿiiB&jVDOL]rNS8vd]>,@.s1c+'l}]lqX?|v̭`qmn |͹PKmD|R)fI 5މJeEŝP%vzYE_٭hN+KtV®W襲|Yr?q)Q惘lwU} L?#}ur.I$@$@$@$@$@$@$pJ!"rнRi 7x }Z7ݥmI!%t +ZCqk[XzuN$e avf<%O0YQw`Zp+-t$CV랴&NV]g'/k~1U$j;ܫ<@`(iUK8:T)bARP"VRMRAH !?M6!I϶q̜9M^mD cdS u !anWDt.ű;0?ifsȔs]>~nS15y•bN&+NJKz'馡O@D;~||=@@@@ ZŎΑmR'AFY7 1nyKZr*sIDAT_u''0L}ߪ#i o߇|˫)د;l'Oe77Q:M6Ю(eg閌~&ݣ#[z9ۮ`԰okOo:{CQ~?qw`Kspʅ1}/,yfdĎqbzM͸Ei; QhZQ?x.zJ+4wmBEԧKIU\hS\RC7W9R3\cR@R[}V8QAm &@@@;>$??_۶mosnn陏AiCL*/2IУZdk8D99-CR.VBos)Bvh5]e3o_fMs\ ieʩ9V2ݲV֭8Cs@@@@WY@@@@@2     @ W~v     PW$J     ԫ zg     u%@B     @ W~v     PW$J     ԫ zg     u%SW;:sL=yW'ooZ{Ƽ|&5[TMZ4Nߍ/6Zo[oUw8^ߪ_әd s۾qC<ݴF[O֞1$kĤkۥMU>v@@@I TӳJizR(Scr&j.WHH^8;&&zؠ8)_ж&jFZ sμC<4m~pMKBݪ5kBRd'MUtxEնyqS@Z[ﶨfZ+@@@ $ěY&jd%^g'm:^5d;2pˌ: S,{!yVQ̉O4<悋wG' Ѐ pN'H񼵺٦9zZ+gܧܟjّў!ZQuLڎϕrdцYR5XD@@@ j47GEEE:a~trpO//y^Aڨmjs1|cثO }ǽO$%{~5{m;X}o{OI5Xk}>   @] 8xyMEv"y짖X&^]vi:QdǰdoUdx jڲm%\H[5oZ{~_z~(23`6]6یij_Mz恣ta.JY;wUr{k.0:{Y?IJN@5{Lh:cz֬ߨ;v#>S 0⽳+ /8/qߛ{Py۫/5!}Dh2۷0~;[[*^-[۲Wo=q $ C%q392W0bbIYHM ;iBbNxw6ȇKHjOߴwNQ z;.Uύ;jqltq7nl?>%    Pk>B|z_R3}ǽ;;:WPS4|f$_^1rgmҌ\+*3uwHӆ3P폟T2`vZN0nj?>I?Wfp[ʸg87#ϿOm#.ɅyX e ]^q{igR58)OKkX2`8ouϸ;vM%fJӿ:SWӖ>odY;n8iSuOv#ʹ;9cԯ:8J1~y QQh+q#ig5=OX-q/JJc23Kq:?^Vn-?_^^ig{L1ݔыuS@{%ѭ]f!XYY%RY[w7-5^   "ZBs?lVxAv 3jFv/IfhȭV6ܗĎ*ޫ#$"O8H^;$?mkYFIƳ~Iۯ/+BJ}YWl4ܙ n{֒<-/MŞ:[GJhh5pH3#yMڕs6M*.rQRu$s?ɚu/yZ?!e.((a?kD˖A@@@m^CMtJ8GvP6̓pub<9N {2$&M"M7_yK^٠ ?},ƩB3ڌ^xQ>g. |d-ȑ2}7o$}#9ۼ~X ڞ,@Wfdٚ?>~^骵\2 SW9Zwb&ë+.}d/JLpc9ԥ`=^Œ_&]Xnld˴?G%΀pjǭqtg~x!P.z0x7STN)4Vg{^ȼQ+ӆ(O'~CޫmZd)Zp=ӏ8Ne~+   ef`֩Ԛڧ(9#S֨nĜh]{՗ .L^\j!8|0o lԮL0JtL Xpk׷iM֧&}V+GS{vrJ%n9¬~?8ʽ$g'zW5}A̅ƪ}.q\0ٯקq#7Fɮ庣̼ٚ5J|:g&%8dIEڴϣK:B|᧗3c˞A5Ӫ ՏyX+lY@- _ ?fƄgAv6KHMB~Ui 4k,{29Kؤ:/!-'wcUw%@@@"ZBwmQ(ݮGKc@@@z5lʑi܊8~uJ1Hj[sUxʌPR]k+N?[*+h^q6tf]fjv7o1ξDmāNM꿶jڈf0xzuk̑$F=liOUPvC1I,>f.VX1sVGZSה7imJ21J?/qǝ&Nvz>^S@~|,}W\IFyg߬~L匙N];sKHk[ɣ UbfFSyG bLLRMB<1X   .`ʫrc{oq%G<ӍݩC'w!-6hk+ex5ՏP5 מcvn@I95KYޝ;QՕђ _0w~mc)Akc1n&x%v(ydY[j]Iz9?Sa0GUTZ`D}k_P>[b|[<ȸI^G}HkO}³u-NEϺszJU   ԫAf7/>0)õd |C:r֣$pްrM /V:´^רrf(8|jMN6'Dz5qZZ{w_p1h5cf|{ ~=lڞ$ a _2،Xw$s?pFwɜ;:Gd_5( ; 2q:kx36m4lFk6}Ѫen2ZǝIGUT\mKX7^rifa2d%P]W/%;   @ sB;qj%P*kV|bOiy>=*lM)YMֽ'{#45C .مdeYvdwjٸQMϝ٣ri-g7v̓ؾ?}QFiiJi^]aM\ sh=Y^I72{8.Jׂ&IT+أR۾?~~7cti/K ]Yw<^?_򑞸fOf= . 9oeߊ袯~=jܻ_jP!DZ˕xHnulO艻:?'A_-;wI=|O`vʧ@@@:UzL2>_yv-̇|#K0\8sWt6뭭[vh7k睧|~9oW\تnM0Y뢱g`}Q k\n^}֯ߨr<.u0w[=tn< 8xrV$/9kɖ|wOG5-ԪPsyȧi:=F8Ww=_*˯{~']#U]y[b',nat{$ޠk.՞cg)11g4Et9J`m7Z Sooԝ}N%P?kE:ӫSEh'RrQ^ נr\ti7Z;^TohyKeW :/>qW`t-\F*k^W/ktft}sʌ /YS[wX=;HlrX{X*-5M.=n]>|wZ!  D@XwJDAۣbW;GȘzYuoXlW1OZ+7j2{>ĵE=OE~l*GO6#Ǫ"I7 }"6Trsݵ[S1e/REoN u u9Uh th.Uk5%ʄi*RvnxQw l=:EoϞǝ v)I 7JCw9Y4G .\^GO-!’iF玩J'vԌ[4ьGyy_TяoBs&+TTA}4KTʅXq0|*q/u9zPi0&*%|߇i {%QSAog7ikS__?3nK =᥹XF@@8>$??_۶mom`nn陏i}ګ($Aj͒ʮ KX u(mXh١-8vU̼^$AQf kZ5wYFb0XƱ%&^OWJրԹcǗ(5m:r`6)7WrSqym\+}8~.n zc-cwr3eO[5g4K2qq*(8)[qtZQ㕜꯶IizRwZ+pM ?uJ<;-W@7אp>ڞƝf!udoc}WQ @@@7J;Fg"ۚqswCuڻǝ^@@@@o@Lv jGĥvzIT@@@@gα     5 !^<>OW݊SQ @@@@o@̷Wt z;Y wTqJWuOVzb`    DՌB@@@@@`E     $#(@@@@@H-J<@@@@@ !F!    -@BmQ!    D <,4 @@@@@mn@@@@@ "HGaQ     n w[x     )@B<" B@@@@p[ۢC@@@@HyXh     $%     @D B@@@@@ !(@@@@@"RxD     qE     $#(@@@@@H-J<@@@@@ !F!    -@BmQ!    D <,4 @@@@@mn@@@@@ "HGaQ     n w[x     )@B<" B@@@@p[ۢC@@@@HyXh     $%     @D B@@@@@ !(@@@@@"RxD     qE     ,s+6IENDB`fastnetmon-1.1.3+dfsg/docs/images/fastnetmon_stats.png000066400000000000000000001252151313534057500231240ustar00rootroot00000000000000PNG  IHDR,7$iCCPICC ProfileX Yy8U;pkky3.bnDB~[/$ KĢh/Xƀh5LQq1r̈P?(4ҡhTX0VhMFPOy]3%E{0u`͈`җy)L!}XDB9S?#!f9XQh . uƶ'3߁# R A잼8 4 ([>8ȓwro(X *u#[X0gs)5pz8?$X, k?@Z" Bc?o'Y-?_^ xpPkm9u㄁2N Zb3iC1N??|L7]]OO$\ɉm.`Mvu®2om6DQUP믭4Hajдd">!L5p(.DPS93yv_|' 'Z;09)u< e1bC40  Z N Tpp{<C07F{09AAqDQD4=Bw B‘8$D9EN"KMy 'd]ʢj:h&h -C+hz>B 061=s0mR;Xa$G8e8/ۈہ;5p}nOx;/>ŗO3F  r]m ;uׄ"(DT$]4b21xxK%~gacg`fgcRrK/+'  kk:k!1s$.ɈI"eHgIIHlllllnll:F~ d r y|QR%a蕇V(  (:**fkGTUU\T*꯺GCuQM[-Fڐ:/ tMkM͚Bk=vަݪ93+[;ǫ窷CSoId O[تFF4FT #&&4JWR!'LVE6hf0o40`q̷:`Z:ܺzFf [AvtvS^9*82[P'{NϜe/;.]\]/\uWwt8ܳsK+ū՛{IYK"a_U "'gV[.Y=F{M;kƬH  :4OsUf˃}!!!FEcaFaEaF{'"L"J#&#-"D~v^񉩋e /0A1!7a8 $q8$$5'sW|֔uz!+-.+]!=/},:zzL̬̑ fnD6ol$)gf5Y[Զmݒ"gs6[kss Ulmޝ?o1?$nZAi;w\Pn{Lqe봷Z_$NVi>Ҿ}ee? Eg=lzlŊG"<>jsRP1cs#[[noskpݺVYN+w \w={M]]7vt7=}ܣpk&7,=r~pk!ǟ/}!ʗu:WG,G^yzMMқќcc'5ƯLXO[n}\?ldkwj3җB_θμmn6z?:~K'Η-\hYtX|@cЖlѰ0T@knȳc@"hkR12.o@ :(pfsL Y`Ҩ"uRղd@+*4תVmSW7=%5yMƍMLcJ̯YLXXmw5?vsusps}6> x}}y%VˮQ PYLSrAQ/c=OM`QʔcǿTKָrvYϺf/(64nj*Xuuj쯇ȾYz@t;wj˝nGiw_lGE7>uY{/yjěW-oƼNԾy~bGO*>|~%ukׁșoMw13}8o^nyaax1ee){i kB%w/\DE=/v F~b/7v"{&';eh>*K–"+u0́0}rV6(*+U5yus /DU:WuM Wl2kvܼUuQ|{KG'gːk[{GgWwj5|lKkGoӍCDB5G2}7ߕp+%brsʹuGR #3;fm(It3oi e.{:grnȻfG]W ~gj/XDq_tYs8~UTU䱆 V5'q:zsc.nhj|v߬~)e+`V9غ&vۤ=#VܷNxpT3ԓЧCC>?&^!.͌xɦ'>}ʛJ4U㙝̿}YCG9"KGa 3D}.B@ ;F't >Ded-' ){g9#x,xf JE*E W$yPy$H-,!%'7-?YD1YGY_OeVOzFy{\/P_?yjј^QffO7YH[tZYZXlXmQs)N;]X\nntq^) e^>>}+Xݶ&?2`~mS`bR;<}"646L5KxcDFA|Ե1&1 W2g3LIRBiRjӳ2|kdR2'6tl<)w3=rL6!{2gpܺmGdڙ+p={=R\UrԾ e+9xSű#GDYO=QS=xrʪv3? 7~[륺\W2N\_sVm睴;m={R3\%~ëȘD忢r+^ɟXx[i UeRImz6IgHezV275jg^`۲تv;~w\8\Ed5=<\}}|cRsV)]{9~K:[LEXzQ*щ1bś$lKINI9KJͫ.glb۪vJ-ݳy/GqE%{o>qtk5V|*oΩ^i|2r5-v@Ճmo=a{s{Ga-o%b8<_JW&> koCBE'#~a1c2ª͚:B2$ՓyisMm.O¹s4:eKdFDDɢ7WlsDm(\'.!%3%'$_2CaҢ Vգ0қ425kjfegYuبƸ٤7 uMm] i';cg>ݾ/>\X+H )uB C햣<S;xl3-)p <]7n"S6L kpe(,m-S_|`zTi3*E^nXjzزʆk7ZW;w7tt?j|\4qD693_/s/%p'IE":p G6!ey y8j Ov6I &ya.w ^ 1ř<V$:͖Y|Nidb8%5]ɓk'>m"7DWTmOK$iR)2ٲer;W*%e-<M~a=TUF1ƷM̲-:ll[8;9uutpifU@ݠ`-PȻN1lW1ꓥRJR6Y|{YN[zr>?ĊQ;pF|766656\io$:*j{y{] ݋=K{_0(f>ə;w3ߒff?|?'gÜ|Ղ3Ia dsxbiiZ_giirii &3Ltó.?LXc pHYs  iTXtXML:com.adobe.xmp 1280 800 r@IDATx fWUc% R*LC[ +WJVVzrPT+%seLaL&3~sgsw} ҧ YY]Azt\mE<ZH륒[./Y 60Aȡ];ܲT-}_VWze{L6< >E5Vt)hYZ+vX\Aw>MRzqM-=Йʸ*;;; 5UYIWVm/iJ\T*@8 eJhG*#}rysZO0K圼?vkflXl\[zuUayY}j}mMnnF_g\t\/_1S<qSrwJZ[Gfa,~ uBB}{`=]eĀ6g9ծݞsrv!5u/mi*n{/~5Mw͔m 54~3 b ʚjy@66Oܻ 1;:嶻c>''p Oyy K]qP=Jw6XJM::RAɜ]*|\t7,vwYn7B R h4Me]:iUͭֈ}ڢMk\/ר;Q3Ν4+뻚{!eMVV6:0oT/y+$_Xnu7|ߋMBb|OryWȭ]ʞigΞG*7չ"|w&;3,R41x0o]VNjԶrx3i¦UZYoJْsgNə3~ ȇ_&*/n])[m)5?"1w8cP?Sd5zW[?;g(ƻ:A "ˋڲ"u$ibK|/霻5]Uq[ͮDŽ$ԕMvqcGo)ܯQ݊A r2iQؚkG3@fv)oQ\m '<Gs XE~Z9Fb3݌l 7)=2h՞Кx J8@'c1Xv= $$Rk\`fkkK6הv.`&[Qh˩r]BGj I\5\w-!!C~9´/kOPORIy߼ӹs:y5ހe~[/ڋ|eQm=XxS𐮨;'dz.AVKj_1aLQӲX;sS`( >iIA3G{gO6vL mZ v"N,7hCE7QT%e87ϴDP 'qV K^6la=?ZZ MtQ-]d4#?fSe10{U^6F G!6 b(9zb .26yuѪ*C=!m\ sϑU詘 b7b2߅#ǎ L;Z+Zck;Bk8^l_YmՒ߮؎LڿjS2`^{iu+$j=#1b/q] Y"0yyl?珫R^"|-TpJb8LPT) TI@.º[@a`J+.($?hE0 BmPSXUȃy5ԊI~Y=@+\ٖ elHd?ǣ́'Ȱe*-3;rxoc(Ґ&HǼ~}P0~| 0VE=D5:c< DALG*]z1 Ueíz8co1R|ߖv VP`vhQK *CB`r`Ȣ+P 1 (M00(qA2`UIzPܳQ\?)`Y d]ɑ#C喣ܗ{LAN,&zi4zAdX7@p ^6e'^Ƨ25_-[OE)ܣ=\eusF(؍rnhCt4#vਮP.j?KxJS,o!WdE( Ыw͍?&rdpTۆmb=FU >Nh/mbZOvzw(i~w#ciomM?oTۄM%RJX'|/nq N^GohXMyV~&r;ȁ_+׉}afozShԽ3 VVz%rWc7>9éRLFloUVq,XY:d.+N=%Ȱ4 ӭQDN^|րM=M̹JIl!®3V * uu; *鬜=R5un9z|>rJ>c,t"rt`.׫oV~TΎ(]|Mb,"ș3g9-u_@inwv:q-¦ll\8R 9_K1ŷ.skҝ){y/sSR4514V7]o` BϣeuBaM܆aOФ3x@J9=c⇛W;r ĒwOʉ!ѳ3 nS-,sNb(۟p̊,3tCYlCnghgt!*^G9UnPv% Ud=ͱ{05f TiG5vP,wKHYg{r0neZz𽘫Rx{uEëjgjii6m/n+i<OGL/c(7#<׮='%z:bĖ)E:'DLdwPz秶suBL7Cr 9q1"cm/5TэcgHK U:^#sz2&<;~ /?.2e?ǫ}Q<sXh}}]k'5p୭ɘSb [~Q`D˩VJ }\]BGR%oW8Ux&5 "1=o7Dޣa~zO7REWCR.x(<%xg^ԒT0^GG>7 ^w᪼+rdbxi"篆Gu|x9ĞKCHpC_\Ľ^ λ G,޶,{G 5H^^#?cѷ6< _-&K{Õ7͘4x}ؽ>O*}k発kkJc@ۧZhȢ'|Z1mE]~MOiNaFLOytx$ iψLIip[]pX~[mˡvQ3ِvUTOYGTuBpnÑɌZ@>^:0拪hzan$%? JJ*D)9d |[GI4aǑտ(r"}LaۥK`5L0J0q@\tXLś67HYjI}$bwRҍc,^[ 0p\ I-?VqUJ p2Ӌ1|tx;>P~j8jN f3'u &00!yV3*g^þHoo}ܘteqH}ͤ*zj~N'f!`P$,/~p#=݆&gLd7UΪH;.Vy[/5pE4 ȜN<\sM#HA -UZꊧl xX)$t饗}W`Օy٧;D~:0#m-^+vdhN Km䶡\yK|4*?JptCݾJ 'USaJ`:J}eJ-.{E>rYpI %:ŊMܫ>A{XaBC启~ J0̥O~92[n1ߙ@v{`&R[Pbܰ~aKkh6%k]5q4+Sm0K~bln)ZȲ܃gNf==Xv7Aš -,;t!c̦V_wp Ꮥ)$dWf7za#`zX"FPc57ƗʑQyv'[l#LM {y޳li˘q#(uO3KOzpH n3vйsCh8 C'3;%vvͺ}H \۱aAzy*o^· o?$ʂ3ˁ&em5O]#@S`K. #'SNɱp[ +8!PMD4{4./ ӆkT%pE :8*@:(,NJ "•3ќ:V2zǼʞzX;׌1g0*Ezm/?Vi>5O7>z5lj֙S̨ul4(ֵ57@>strz5 ! m|:mƾrCJ?*j1/ Hш/# !X_/[C|Ee1a@n1͎ew(H~+8"U@ǹVw5Tҍ,їqR-y`g>= pxJ3yN9Y9C땉>k>n֍x3=Y83x$,1ݺn>W*ؖ.ِA0LWF4`%zK 6.!s?:lnfp(U겙i7mf_yu7oOq|ir``=1fj7rQ]^Xz=7)mxE8"=MO\TAʴ &fl0(0ɈxDL'1 nQِ~;9.Ǐw_5vc WEt4;uӖJ>ò/i"Piz 3IۆcnqXF/d@Y(NG.Czz" >=0sM\аPOF]Cr_cBNiؿ1NE~UyOִ>>+ʡ])]ozߤݔ J&kNUyW7Nfȟn8oiхz5AF8EKLU&r\UǨٓ-#X[ñy0u 1ā±O>/{ pc2eQ }]`Je?tߔ t\&ct跃Ir{f(7ND95"u;yKw4yet|v'n~<+%̄czgƆLL7ǹ :Qy=XXZ/_??rkw̛}V*X-y_b@X,N`sg1G/TM>)X2[o35*с9. r;in}}kv,5"?}<_^q||7;]WȑXff~@@[#uIQ5}|< *8z"؉xpꭣ Bn-B-|آmH驺qGiL v|GtSWڱ&; oWv)GUx U`AE)SnQ>!ENFwJ;p4bX-31P֭6Z,,–y!L]$G6P,HNW9 !Ϝp=NTq~|i=XRM'/o@&R:"Qm9JM'L|FoLx&e-̆W+R,V,QB'GJ~ dY*`Нҹw˂3?̵5?q2q|[ [~ū|lk!?}(Y ?y ծDȃ>4A_W|F*Y=tceGy ǂ;YX;Tzʽ&qwX/䰬{f^( Up6=H)_zlӿ'C[}u8Z";\I{ANdPD@)ki+D[vYu0 I )8we,wO~ytDp%eGr摳qP<& 1Ys\ 4 ҁMxI'F3?ݼB˸bz7Se3#xI| xt ҡh9db3&p VΛkG4ֱ8/f2G_a':i8>ZoeƸYL)Arͨ[mCW qWeˏ>OL bE}7£pV왉i샖gȿ6 {ҏ},ߤo>ioqxdc7>I5vHRڸ퐿k=`.SL5 eq~W6ۜ_+TF~dH@ xxʺAܞN9:`=p˟-uK[-_ɑ#"ܢ'&aB>MKh+1dNDz80B5f G%(D1ZkynϜ9HY ˱ νagHM}g#q,7q)xOT٪ u옝R$32?6F~nȽ!WYK5zh܋ꊾּP 6Vº]s{n| @"Q~݃%LiXя WY(ZbW`!S]΁lR:A[XoDm,m')ʄ}@"*A^OehQx[.(4#yI\F#FL pj^ƦILNYeu qa7V^k}JԽ5Jӗf`Xت%e2Ñ\47ڔl~ ,;1Ü2X{U,eLa=5:*vQg@ӕ\'(\d;jt3|38XXMmP++%mQw?+xwQ}h0iFiҩ?Rgx,\m ;xgeL7&uoE-ur6Pa@fʇ6up|̉B|)QϹ7eRN=᫓Yc@ѶM$nZ,N6?! Áu Y/ ̺os,-יؖNF UVr}_Ue`;ehH  .5yX{FR70[?xUqY[pܵ_֛ʧOmwIʘpT؅"O%LYg 1X9i]nZF)-:wOmL:-~7M=!{}2A=>}ٿGaBw 3uJ:w4N0 ]g=}x*zFG%\J?^{+r:.z]Krxn^ J= 86n蘮-SC3iq噇OnJrZή~A6_u}JѥoU`0znr@ &2Аg)pLVS0l&' A;Wˤzs#gQ^%Թ]'He)Eqڝ@n 8[X&?wÅؔ$e8?J7q#:0g0wXEf^ʁ,!7.N+W<:54r"c]Ӫ8MвF]UG5Y%wd&"`!= ٸ@`KC:-?IݻfX`Х6LuBע3Tc /0lv8w?Vqu}$NYj#AWV.)QJr |jHbW4(D i,nZe.rbt6-^C 1i9EF*2rQɒk+MNO#h:,Yxȃ:<\:\L^M_1Rs &)x?0)u_p/6~ 6|+s@QFbk /5#LXXbo]7_#eX2_ ,~ZUn%%Wwۮ֮z="ZD8i)*e!b".xk6"s{Nn"}W^@;l寎~=g uе[]q9qRz{ ?o@q=<Z"5ۨoܲpU pW+A)[t}mlo&C]kkEE@x=^ qӢ{mbz"ȞUr@,a~ǒ,7Rly+5O"?U [@Vͳ'0{֐Y\y9ΜdwjjyDSu2tc݋8A8;h[Rp?Yxº3?ݞ9gڿwͭ:s0oq_lmˈg[ 6j \,Ja%e i=,//}廏Dw*u54BDJNOx.,ta|$Dr&1'2Z^f E)pwf1iW"5Liu~Z#_t[[z=y;a]frnkXniY|Y?s|hYe F`biם#G^%W]B)׮|[E^$ ]sʻo| w$3V8s{2!]Mpטz6bQ=p *䰎8l~N-Cߝ/ٶeq4ZVbFaTeHT?&8OuO+SBXSWXjdnW9aw4uSke0({>xA &%e+ [rV1-ʼn=ofgrW7MzhE륉<>q"Stӳd4ɰhlm#O86t7rR1rf}T.pS;:uh Sre MV ̥*p(>sKb XfA&o=^DPq$ @ca,3O>Cz5dIp>TY/YgXPhn ){Kp>T%:rD!A6 ʛ1}oIʾ/' MgGZOM>}z(wg v 뤛4\?2I= 8bl{iz ήoYۛpMS.dsBanyvSv6J|ݭ %K&<"*HUYayCx7 BaM)jhi:w lV`y[!Q,+6H4p%e M1f.frh{s_勲UKS'b8,O]k`)cpdq|?[B[*cy=~;cO]wDzNN/LwAUaJW"EZigC7v q´BcRt,7"iK[Z@~eҵMV{WRshf1`L,3'|rbTf7nxHߏ:kDo;r'升 {XP ?*(3C|7|+Wח.zr?$WJO|]ZƱ&_w_ƱO'BU[FMr $KbanKKP# a9a 1O{&Dqڪt.#@ 0L]$SIY '%k-njhGx4g  P=:S%k-P&P[+= ~H.GmbZkAr u`q TXd/9ͯ sh-G|E#LXW$ZhC``jjpN0V{SSзiQMm|&xoQgUb6 YtB n%VG;+@L+e0G='WAR 𶜢OMO2/ʁ Mï0̈6Y9ufd#rJ:2?nQvC|0I@xCq|pԁ Tr`(7)d` 4#9`4ry&nlʚn]_m+Rp[ïi|$d:;韹u9~, d]$勞2ۅ 1xiVxgب xTĕ6j ?.{FJ49 V5"{/ t.DX6 螢p*E˓*2\rD2/h-IK^hӖ3p=+-ֻwk݋^0>BiP7Xjإze+(|ZIC^FóTUM܋ `L˕[YaX1sI؉tLn`1}?V6pg iP Y_A -8 ( "ZP5LFq^e_ %2pUŹZFS ZC=o{8m:_ 7I,_/Mm<0IZ& pls{XLoa;}L6Ҩ ?ݴ|Wnp*~'<%G4SYm](UeL P-[mnG|mTåxzHwD:B4oEe5EnlxF,el[%ir u[&ܵy\@IDAT' rU0%EӞWE&qy4z_,>!ᓞnCejX&cͳj̨ϗPF6p+:4ܞu < >b4{ο- #@jر!{,g{Ê06t|nrꌶ#Eu/77#oExb:@(alʊ Qۘ.{hH'ۿ{[-]OI+^;6d(7,5xU"}k1ŝ+_)~2kL6@A^ɑ#COiK!nJ%i'g//^p frY2i5'2EԢ;Wts>H&8,-Yf ڏ? Rnn^94bum ^g/_qvc88>v2%WDX,YRD i]s{n|yQyo+QA=:HL I_xCxJ&|CU皴X,XIbqs(bc5Em>zSTs?塿rSbʯ5.|j]+JREI0>i>c*_9f"7V^W  h ѧq=0Jɨ)8 R _H W=XvF4%8/!`4nxlglyu0iP;w;f4gZLGoo{G}_Ng̃Rto[U\$팁aƪPG %MN ;-z p-MX]EA7cy[wC:3(Fc&|ba$Aѡ ooPd:((NXHvRoVW[wF%1#~nA2+(W0RpͿ '~0FҔ'MMӧEND% 6" 9 eL}ޝEYN]REA0^:|]*fF03 ks2r.܅K49dy4}ZT`𶾚bvjx=M"%^"K*:%l 3 b){RO˝S]`@_O^olMhNC0xy؃3&81nW#h4.65cu9BGI/s ij^ʍe~ïʟsLKNr}LMS_1jYpׄN^RO'\8h>GXlISp+z7[٭J_*]#΋1'w@?~8M\hJ~kJ@K}2h'y#>h+8$ Xyq !W꣠ t_,X6},)'ܼ}:lu_: @}bRbKpX$WyzLԕt8| ۱vuS:rβJ} KgcVo@ar\ҨPHׇ~"x/ 'iJ~0}RE{_=ȩ/^/kufTs6׵Ֆ[.8cd,m\mB ZQo>9{--mhVg&C8u§D7ĸwEp+"㴛٘ EWV w-NՙIBDk#={#C sGC3Mv__Fýa,#gy&лҞ :9 0 ӄ,+l!тv{{KN>&Qv͗{M}f/ܳ ax;5)'5s oqYy{G|+D~5"?{ -) ^uJqX(q-me.]sN ]@& m[@{K"Et̻eY~  ,PA mI ^}Yi~%p˵mE&CSO߲F393u0CE$"eO  BXRe:Gsk G{=`U qYD[x=Jj]*ERbuc+-"[z 6z,̗\ tTV4}``}J3+Wj]$gi^RBu[XZBb1R6X:۩50[1: GO@_QNxM^T 2Zs1c}+/(U}I]:~B|E }ȿ8phFus `# ˃;Ab<QEuqbif䘹)7, ŭv$l MuGj!Օ}u9uzY~deOg4Wsmoc{}jx^v#%׾%(",#N:.;ox|cr]r-D yBDc |y7֤ZFS=Umm*7+4y]j^Φ~+_R߅5gkgꃕV{FH;?s`XN孚嶻gıOT\eOJ#aS7}=V~ٜ+ʡ]!gO#)zm?'[/yiBu/#QB\b0 0Py]j0VQ&4μR.44S,M4aͩvOǴ$:b㳠%:7mtJж=:X6 _76/wR|!P%_H"kP`WJy X}}+v2 O~7ZG9Ux}*?4S Cb}n᣷P5F M_/[ݰ ve *HQe(l c=*5\ d'lGpW-ZRMH'Þ(苒(}1XCw9,%|USTE;B@kZ.Skȸ&y3n῔xb[s+,y0E| -Pw z{gﰴqY'*>$en|x ΄ø[vM41X{XTp[Fݣqa=fi280˨h <Xf9걖'?G˂"jKYs?Q/մ(;J[x4X{1O4E V@(~Θ}T*K 蛊x`d,#<wSG ;#YԬm#-Wt*RX0 agڄ Q~GQVVYӀJe* f\D|}^ a+;bO^6FRv$/3iKh>9p=QO?|SP/H)Tj'[iyR轰 4./`,>^c=Ui  !6>Y@D,M|ߺRE}Que4숑̝6,qjN|1s^vG6EPYE.+mS/&|o癰_8!9-nw罺Rx傳(U=ml[BT"^2!Wޖ ^n5g廾U]++x~#&8AP+ۣ,9i 1Uut1 ~o`/*4*B9Fd;^蟹;y,YOfM@ܦ[_ni@D>(;n[1Ry\87b_cN)Xp:bik3#`~5߾*ٞcQ.έ"yaLK۷3}xJm &/OkL%[7:# VO  [AHW4ޱfvEсœI뮰2|'s#/ S2gf=`ia:uvvuj>m)*CmW\VM\@&hS7 r+剗p'&]Ld 57ƗZզ GTXPʼHO_,Ya,Ff%$8j ?+z;?" bHoa{"M]{gDžu@C/ΰ3WɵZc<Ɩ[ۏp _{dΔ"HhZ2MW#@> FtWP2-/*)Ptc0;opS1S0(g,n憴mJjD=ٔ<84ca,w_6hS0\ #,R\iCSǿH8-Y%|f}|hTB.Goi!9>! m"KH]{1m$;8R𐮯wc'M_XY>Z[r&+Lq[#äih/EQ9V#ujNe^wŌlj~QuFBK:T'&ğa6i IsTUo%1%/:a2gCc1q}>{ⳞOVW`DYg,)| >5zG:_?YuRy F8r,__/uü-]Ojoel5(ȦSSuU\ܳtCB1[eچ"gϜwlвW.ٍ gj jSr{*,[X>n۝J`,77t 99v/[ =AΔn FBwHb'",3y0wY)Bf F\/OFCilo@3Ԡ$yH B <( +>/QW-9fY9qFB2f4/}Ge,_?EDzzIEW\)w\vP;<P^/l)㯻pU}a s@bzv%[gw&̥vU)O5ҟήrłCw7TqjGDX'9-.S?K Ven%fh}\?G[Q@v/@ЀOÀ-ͶAqs\QS8ZC'~]à`NBUw$ 𡮽88Vs?>2' ws_m6 k)O^% U&]MP\P!R<Ƕ>ᠴ8IlGT3]#5=J3z|9!Tx]ڐ~U}ΑI3Q:ⱐ?6]Uoqc薽%mt++ڂ゠boHv֖ͧnQӺw=2KW\d=, $͍wW #BCbuKsoxq&|'p pt7oV: V ;F5z6Uh'ky2n>7VElnm69fz)C::_-U/>B[#WX"5ιwDտsR+.M5C&a}9af_kS?ƕٙkzg*A8bn' #nUx^=:sX*Ac'Ғt-7}ZHnJ>F·Z'<.̃#cˣ,~KyQJ^E65բ)LjϤV&n`O ΋'Gp-[`i6G(3o:ySo)^퓰oçxJ?&f3,6o;c}KO˭-j?,ç ~\? 9[;^KR:ͳi >D|Skil0#z{?Mr/Ie棅${ӛ=gaQ'Wm>X<[$iiʐۂbs] _:z"}+_'&ݗw[~#wYI$G [wl`bWedCN|$_`=>9SBAHF>jU۶,Wݸ{1t!B71 ,fKyf{@c"]qj?KxɆڍwIzȋf/|@v_e;mmmp?"j^L})+<[ʘj@SCT, i$O <ȞbUq#~5R#yJ>z oKE[oL˗qPh9@29>1 hڻ {sFC,N"TO? F.@^1}moxC$mXZ[A1Fha,RIvXu5gԂw㣊~Xy(j%Ƃ.u"Y\xfY8XoS:Y9_8k,g1_hn ,Srgty oCغAiΎBX]_8y۽68FՀ>brC9`}g!e%&Sq=@lo<% ߵKOy*]%rMWɥI~e+%[B>[_ܜj>{Jc!sr_]\].ϖG x{9F=@w=n+<^K/ V;r:Zz)7bgOpl+H,k eA JHr5E1}vn9)'>qysF X&3$VTLg+"; ,r.zrC)yȃpԎ_AA-{䩖ϔ&[[4h^bZ 栳u48GrbϪ>d|eA<7J3\Oc?]!ȣ~=M-z_eߗkh $brOYki <|<X?ϔ:s—1ǹ"gw4Xz-y}PQAcn]}B?8 +wx qފ)<abvm|\9kyċƿ$/)Wf53ʖ%^$\zbO5 H :#>3>|{D},~șDc)g,o| >D>W[=%9J U8.C|}?-y/ڶn@c!đbY^Cٟap5b ߛhK5?&΍`1 3=:˻ߪͦBφuzcMibgco0ыny_FGtofsgo#?rZ{G9|㱰JWJtՋ,֏Օ"Qg@20!pCA3W$(Rb{h 'oZ#%i a֫)_AeggC㗭oSR-aE٦nq)Oy05LT>``%"RW4B Lt,  h^\.re01ƃίx+GSK9!HCN3gӯkYHX ߯gWWE^|#72¿uC\2|eە#ؘ .0W*Jװi:h#-`! `&5 e_ֹ_bڋrsJ9nͭVI,# %B:::ҌfOޯ.@o.+,1ߘ$pUKe~+'N%"6<~wy)ivE!cMъnCcr7yuP8[2fi)lm]o_"ȴx-^++\:_I\^B[:^_z~ { пa*L\_m(j`c :Gq­sx^EX:$8H!ʽ壌4~)Gu٥! %?zxq)3Wt%`s@e ? {S>sB;&G2&E,`!me.1q'ӽ_jLs-]qT5S7,ò/%kyAe>u¿l=l+ąۭD ~,Q^ o6vr0!?ᅮT<~G?lWr =JR|%C *X]Hx#β|ysliK1,!]+z&^%ßL?.rA>92+3rXMyg)1r , [lUԋ@AA?O )RR;q5 >|nuCW: qu6^xg7|ŵV>p&"M%$DXeQ}ppgؐ m x-H3ܛHيx`Pd̖௺CNl}odM+@Vu9I3KEdK<9rD[h_¾( 1v ָc='hPr!/-¾[vm }ǽ テ2}}@W$0@S"p4^[`os oxifܡ ^ BLkϮ;k. U ',_o'^Gex(y)`ö6aH5%c nx.+&7>g|jϋ|u2x{V|rCfwwywXע <X&|sapUK{t/re뛸P™|Fz<YWVհ׀q7ä4d`uEv_nIkzFgbɰy5tN 1+d(h5pЗUy׿V~?~v?Sچ,^:}vױycJfMÅUc> F.{`e ¾Іob䲗:AZBʳM;Z=K_:g(uՍ]DgB4+ޑ2e~ `b8Cu䟺ؙBфZ=aRY3\GoorJ4V @οBG4O_};&7@64ҝr=.{"T o^ H2~㄀`٫;LhSC:I{ }+={<g),g_3CȴwTyR 2rp{⮊tsܯP R>ZJ޻[k~{֞={}3fugϞc-cTЎl'sNރ3.wl$/sU6dg_n?ұ #Jc1$ۄ;kׂ:6oiȟ`)Qֵy#wx6& "PKD8^خDZ?Jt8ճy&҈F c_!^3WRh0$P_T7¢ &Sl}|s;غ 9juoL9(f, *9ṁg}]dtdZ8|Z0U?pv*u暾:-Y' (O1!`]e(;(XRVQv0gO>Tx{<,.!u߮ !G=38EW4 l3"l$ix|vL4m_/4RߵR;B<]XAUɇ8 '%+`&tS-LN qJ1J Uy*{_[f56I=6eVv96ީM'Sm^~co/? M*0.682>*#;iN_78M㗌g?E};;\~͕pqh5xh|r yi]W/WOG&#8M6^/lцqalFTmg'EE::~z/Ro7{QӻGяbY9wr#>nR͌#\q]CZ{>;H>UWrNj֩՟tsKJG^iE(ܯe3:`9i¸s\O"S`ȜF?e<ʐlapC㭺S}G@?$9k4*Fqh^ h0[ p?8χp|`wS=.o ;n ;0:L_,.nv_it2%uE-vxI?f"'uG%mZ/:~\{W$ߕKDڐa" }NB4܂c3߫J z/ ze_Qed'`KŌ݆@"E.NSr!;@ ;, %1o1) V lע_AhƄqĬ:-!Q2~/[%I9T>"ooU'ڵ?/Y %9#t[?}e_rq|:#S!!p_~GdWAm_-DN=IXp.G/x1~U, kfoNؒYDEN6 TTHߘy@}Pݭhls;ߑ3)%L .!3>&A*EK_{+`Lȫ=CkoV6&Id|yzcڏu=$Ja|ɬH"uO=%o*O[o(S]_w |,~3+Ftt:+?FvNb79_щȴFV8t~z'E[3!;~v NOeFx*}]vmŷX@ g KBL5Hh\T51cE1!$裌6p^0+u)6E̗ThdaH[RQXo!ف잮'n'(C Q0AE=Km9K~0z׭!\{u' +|$ d0NfZ.l'.Iɟ%j}9SVߌ[&k@sQ:{u ?l)x*_3t4~|-w ok|ZȴC؟G:|q gu'RⱷklonbxzHZTQ4 2yo:7G8"ZIXK 35̜؟m9 .X}UI HWYQ/A`QiSJmkXz_%W)h`$ jϰ@ O{¯/nz}=.u1dE |_ }wk|$Ɍw  $VIDATU- e$ 88gd| /8}аp&ØV?}$>8ym1e(~$?wÞ}xVVi;P-hyvz>/z pw1*+lPBۤ6'x]V|\4_l]X~ +~?BB%BIq`'ў5X ڰ?v:$W֔EZ3$,'e$<% G5ya#a)āG"UjYv♩Ӧk[>?۬6a}!Iz4;c7i /3bbk/|L]S㸔%m.J׸y#Pkql8D3 ||KQ}Z!@4H_' &le'i*roz#p eӺ> -u#5ޒI&KߧLn ̦r; x#CadƗaF{]7I~{^')Н1WyȚ9nY(@ Aa"uXBߚ=ms}o19*\e'IN1⹸,䦉4ĶyIN{1pU9'vYI.լ~]l7uQ5L P>k묇GBNɑ5!ɳ* LgYhM$)բ.u#t|CJ8 vf'1Zlu.cX!mmACֽL*',/|K`9o~{2rϭu,]ƒ_ L{%Ay6'3/ɓB?‡#e"zu~<|8ug": gI}IMKRpZ7#O: PADOx%:񝸻q*1ÌcQ;nĮ<`q<.Z.t&4 6'?1<M|+KesYbSHҞ$AsuCQc^d(pেmt/AEF%-&=SЪOUΉ֘ЅޘA"b: v^0A6HL}0:[B\DB"ITr6#QLg̻7iBZ,ҩBxR\'O}"~CwEH>ٓ;7½-O߸&]Va*LjM O952Iz]79+MyIXE܂|'oT[xZ%|THuP1ѵVFgE1}9b >Pwlm ^)(5(75V1EF^W^}=̠UlS?cTG/3~™{9j{&K‡\Su?$)Aߐ5/ѣ& Zx?.ϯYcYC-EyK:o0C}(P0q]V,aRvKHO$6`"a4'`rtxR.;%WiKUU<4Pû"t.|6#x"Ǔh:^I:uiO۳IZLoY[BNJWehxrp؋x5'v}4Sr^]b~LX۱F\cMΗG:deh Ljq+a .oS i׽{*As^cVU2myw(mH??`{@\@{^K%p3,VT‘!v4=Hz#AԨJX;RGY ̮`\v{x$mHX6'||{ak'ֵ"/vJpKL25i !޿ơf*`T.Ic3;<:f2R7YJ{ITM"NM|O:I]u8[^yqE闧Eq҈~p24DԐD[\M`G>}o3-41~chEdcb&3FjE?=HOԠt֍zw9q1i2/ r]@YǍ~{;#pQm(H+@*-k莬-qH}f .(]!3}Aݻ*ta_v=şC.XhlżW{%4Z'?nI X Vysa*Il&ƞ[Ě=饈5I/ppek.y7^5 Sc4Y]`!;,Q(qֶBN҇!^\W6u6[B7I!Й pӃ7;NIE2MU %5`\O!D#5_xe[TL<є뤫8 !F4Il-zSt&m=|^E}bJg˛Yݪ 2Ƌ?/.s,%{m:"77sL0+r!#e=^$4v2>B3T\xE]BD[ OGS$N$qr4sVqm+VO2K{E.]KU 1Lmڔ"5eB a x{{;9g_v_79o"6Ԑl^uĖ*ML9mM^lxZ9!5f;J$mE6%<E^wXSz;,^b 08J q^Vb/qg ?pnz5 .|uBjb|\>xt9TpJ B5yZLʐ/o(4u)BOt0ʮQJs\Eg VA+aLW; oUfLj!GHo-1҇c>,V 1)%z QѿC+~\>o{U?ŏcN㕷)SzBnxƷǑv~B`-ِ@rlIx?<3y|B!BJskZtX  rȯrRY>mg"B!ګfn6` $U_ƘR +6TFU_B!֎C5k-Ed/"` ERPRJic(7r3iv)].B]Hth aAć)2w-1X |ZJ)_!B/MSSӵ/-nW.k3sHߝϦ=ٴ79)(nW+E`D#>>X$v1&H)RjRgB!BfkNPJ] Dyߝכv|.ۚ͆yy ֚~ KȞ H'O)R2IB!nNk PP\j?Nۡndh6Dp=:dCf%Eqڀ.LԍIڈ@`PJ=kQ !B֨^1&ZGkt1Ekڷ|v+K Mb\$FDW陌zfrJW.я))p4RY,Bql3`6Ƅ0r-]^X<$ Cm/Q\iP \7f 3"Bc V*VJy!B֯րsu"O~VO-YÁ&{{t"$5Zc&<^iu t3sq̚t Q'Uk}zUB!mN1fkZk+߬g߰WGaIlܓGiy˯ t6g_\rݤÃy\9\k=CkbB!mBZYEP *$yWM`ˍ!=;\ҲrIi@st̜n8V\ysKW!A O)uot$B!Zj1fR=;s3&ӯStttH#zvjwXrGޙ94bVmͮW[97i”fڡB!ʀ"gyyIG~](C3 D4".9Lz}>eghXo^#6a{fq-D%wc\ԉZ[c!B489w c#hXoOIy*Mĭ5b6'3N9@WFBdzGp =ؼw;&=nVi^zwZXB!y7yOAzإbChN1O&.,rN&=u‚ܼzlinPZ~B!3Rl)u&c,{ [kU;Lh$ЯS )qzlRRA+EJRڔҲrH9jZ)zG2K,fdtܰ#0_ g !BVCX{h=Y5O(wN\WRaenܐɒ EHJXwcp8t#%) 7sbBߗN.fFJ磁B!hkxQY9 cSHc4#{&pC95qO>r뷺B!rcrֱJ᳜@‚܌zJ%'r3wG1ۻC_;m}c `!Bfxg0k ?_Vy/~Bo,B!\jjmuiG U؞s\9|hH=gcݮ\~ޱwqwI}6Tm_Ckiʰ#fzg%7B!%ƘZ~}DAqYڀ.Iټw?=#ZgG-W||n{}?g/q|FH=#xډ3u!5/l.{t$+){zF`yq6ox^e!BF0\l]c&z /֛oBxPoߥ7z%[O,­5\v W?"Xu_-]۫7u6vNʩ0%JqѰ<})DqՕ>Kå/|ʻ?eX!n^f /y"f-XQYY); Ot uSXĂ2x,Mjќ!Bc6zL~Q)>Qwk wdFN7+W|MYZRvG|qt[LaA 4MP_TJ23I{[ AB!D 3 ~)Ӳc'f>- '[@C!B4vRʯcyǖG,B!Qc"֯3B!Zhsj˟Ѭ B!uf!BSc&1oOZ!%2}VcJdiQ?.gZ۲ !Bbc&qϙ|VY)st_Tb{?!BZYԸf_ܾaZ)֔˄фth¥u+Bq,>i_ `]:1k=Vq)ĆcYr=!B֬>i!&XkW]{9VmXrG}ƘrB!DkdC&s;=5P%`Zd-4(E7ax-ۋMSlۥw(B!ZPZ?iM wgk/Q VoZKNóaA3r=1 !BVMAn-PCf}Ƙma߸t+7W]P Q ijOkZ;Z{kDP]=wD疥疤%٢c#L=Ɔ+sZcJRO!5k5sk!c ƘY1A34nՕ`VpOxPg %{3nhx<1h1 !BWg쥵ZWk5 {=N@IdhV>Ymg996)]|\7N߅@0lzA)a !B?W쥔>f`{Ğ{|6g,ټod.+2w rIUFJG|ccVJ^B!5(`RJY`9Z{=0s60w;Dq{eWY9l؝dBv?X=&!2.1I(tbPX%DֺƘ5b"rT-B!h_~KjT\R,1&N)53j.qX),boA1WTBAq%ey()1c-n p9_nMdp Q!WtH Qć:NccZ.K.km< bH RJ[k JbTR{+!Z' 0oLmepZ=@_km_EAPm)vۀMJMJuT&IfIQJ^[gIzhoQJ ֦^@1&I.B)u1Ǔɡt`R*H`Z{] <=al$IQѮR7RԽVYkcD WTNW|qf'r]Z뜊t!ZcPk{ZKk䍻1/;c1& c 7 Z{۪AnǐwE+,JxRŢP]@@aDHlX!t q tcL V)XjE6 `\3]kQݻw߻e\soY ncYkkCXCX eVkr,ZkONNWJCqq{He<6'cos SPJ!"1􈏠O(%D30!1!<:#J%R'_jG^i[.ڲe6` Y K]BwWQO)gZ 7r 0K+fMvzs̨H 8W) eiSh=l؝GiwZv/b"Vmͮv̭5܁=; =OyGkosIYo+p׀ZB4cL1E%O‡r´=6# z7XtkR3sX1LƘtcuZdR8u;lu;صG .FR3sHaފ$Eqڀ.LԍIFF^\eby^)5W)OOC'%NˑYf`I־>^Ek_=Sqϙ0ֲ8u/McG<7DC*-Zw^q#\JUm-`Y4ZEZk};ߨ5;Eze~l ɌS#*$`էYwB6D7ĸGm`{ݕJk [%cp! MΌڣUus!N>E^-(]kDŽca i-ZkN[l8b)ątt k&0x̑){ x'pwh&oe3痦q$D6NmQ!ڊ̋qY^Iy86]Y=9X$l68ף=3<'DZ}c&1oOڊ ʑ lf$^,rWAFSU0 gA m6;5%2C+k\\;z ڌK)g1wYdp;N􈏨{Y-^ OK_^Ug} IDATgJ.[ϨK~ qM^I)pX~E^j髮~;Vu!کw0NL[KK*fbHxrpqs6#9x SP Efc+@V .9?nWy5;xډoFWӰZBcNsnU{z0|E BT}xhnH e΢UIAk9hI*gUfM:IKzS{ rxq\rSphݮ\~3o~y[N!S1%%kjrb՛OkBcVs-/YYFk`;k8߽[[sI{6=ٲ7b%exː5˭ֺ_㟭W*><7QPHOgȟ<\.xn1v痦U0ozCCNQѫ-^3Y V-ǩfԓ*Z5 !!` ڏ,[X-]Ƈ[ޗ_Tʼ+*9z#u_ς妐`*xZE*7I5/Z``;bE*UK Lұ]V,+Tq,k>}7q```uZ!`39L3Fc$`dbBjW3s?? =VfnXHG0oʍTc#*ϽO#ǘy ͒Yg_pN?±s/5WN?h8ߥ r[G>Y V43RqϙX+wak=w]b(nR Uۦ(\+n?GL'BT7oRubr0{{>Vfў= Z|[}Usˁn͛MFۧ~$}n8~?ln jڷLeyIyU/s@> \Ibl>K [T2SZz/YgWq8(2lQJ-PXw?ȔALTVǚNL`Y){8Ncn$T{+xŸg>]g ey>gG,+o ވjJo !j${4F];^47j<.1Z+R i_fq4_m}ε*OII)IMMy|?@{{lR<㯮_R{pN/,u;{ņ}A􌯹3YO}kջٳg3{R`·TQny1\3j109;{\<}3O"\=Z;KkԆF,y+Ϣ\>\v ACt?~=>Nd٦,~ܾ2+8kFN`|L9XZΌ/e^7sSX))hi&r^ L\EZVnswffA%{cjC7yt 綉CA5>lszyؙB!=Rg+E0W-єn|Ih`@Kut gэS ZBn}c9I⢓zq = ;2_),bFg5뮅BߨZEK1RJMZ6%`Mbl$^Y-o rLVmͮwO;ZZZj 4.1cZ_޶>Ve ;rNfDV^"%>1a$EDTH ..MP_TJ23I{[ Am[ARWZ _) 0S m3/cx5>+FcX_y)8;3'301cL\Tn'Wq{ZR/Z1#X hgo<5N;nNi'~景2}@^6H=+F#4͟Z)丈ەTLV̻jc$b٩:C)%IBh0nh%` }D^"yќ?Y[llK6dr~sW JZL߅qfR6'ƘB!? ĕ#P ņ'sW),(iC{x,)*/J?Xʼ%]xq(nZND5Z񃒨K;e-B90 q"춳ٺ/7doΡ5\EŮs1|Z1I7uq)]ý8BŖ}H{AwX㬅>򖝼mƒꓞo} ]ٺ3=#Xt㙄`yUkG5.f)-;5Rԟc dt&95up>r)x0 QyE%8\:G.Ukewq߶=S1 Cz`>i*"C0|F)%Q[ Zx<BG]7f BW͌SSd(Ƙ.5-p;&J\hd <8eџ]Pt eSal$x g IG7#ťܹ+O|WBݼwtZ)5M)UNQ)n3x?g%iN_n1Z[=!ڈÙ]&@*\)  88H qUX |[qi@}Zq^^wqA=@~=@>\w~HvA:< p:u%J?/Ù1SJMRJYRcbogk/Q VoZ boڈ@er?U7q@GNzG$N{gr0 '3}&bE;V.y6P| bq-::8!8HشΌ=p@/{6j$ dC&s:vqRMV[0g*,\)5ee3%~睈&ZRJ`+"(_?_j;$w.1ale '<(NPmԟK8/:o*/H`5 8 wQL+NuƘQю8xH>0ozn0Z.gDoɺduZc^4̊ gz礡|vkrvl.aAn$}9#Z)<soZ=mG8 pz^Osqϭ ,ĩrxA} H©ѐK7q?V]wYz}T1Z{/0{"g.lo:{+Su]lXP0lVJ=zA)qFH,ۍ. '{qfv8Ppf i YZ`~Eֺ8 q8S8/X0Γv)'q G~Wqq4ހ`"aByJ?-yjc̥4(lclͷ[YK{5b޽#'މYy(RJZ* 07~>"/ ֦q>=$IZkRcJq>QlҕR @Rj 0wgӱֆk4mcLZT=^Z]yfawo?s ٵ  V$D56ENQ$?!W?Ƙ5b"rH4PƘ$`v+C5k-Ed/"` ERPRJic(7rv)].B]Hth aAć)2w-1'R-Kv\oٲţ)f[kk1SZƘ*C5˞"@1yE%QXRFqrcK+\Zv"<(bB CD"Ѫ?KƘ8堾Z/.vOχ|BZkVP_xiurYC|6Ϧl)dOA1wZ):D5&Eh&2 1W1GJdr+qu(V[k{)Z2th}V RR묵c5~>MtR2\R!ߝכv|.ۚ͆y"έ5܁=; ѕ=ORm+Jeܮɛ/;&7F*rc8T6jpŗ YS\UAAN.TAUZC ~!1ťjq6>JNf\lRvIQa6 Suc6"8л:C)RjR*eF-Z83y+_o8>!oBFךF^? iIZ;r{eњmz Mb\$FDW陌~)!n&t Kx*Z? T3 d[%oBFךF^?: i `rg?h[N,yI-@>\I^Ӡ"Cn@fh;D*jTJ=jHmѺ)Sэsx7q51 !y_ky|su"O~VO-YÁ&{{t"$5Zc&<^iu t3sq̚t Q'Uk}ZM/ˁC,O#Zχj}!17[kZ^f==Ehrd? KHemB՘78k) (O9H4/V JoY@TH ɕCzv>ie咖t0ޙ9:q>75K߃g#CpRfH_']_e@`95téR ,/qB$_CGƘYJ_B4oϘLNG4R!AّR׫ayg0r3Y5^m5VJXtM SڛO6kdg_?Xd&s1@{ˊޭx]8fq`NɻY5{ G`2&pp0gqN/V? p;moV'U>'o@tg @!Ͼa:-psEV#mij/dErNQtgUnz}".9Lz}>eghXo^#6a{fq-D%wc\ԉZ[cK|3: 'wo98YM8<@4 'RqozU܆6No Oĩq?VOr(%c'NS8;b*ߎdNh} Zq{N{6QnMs}Iχ*֪e}Uɱ\47[!b6'3N9@WFBdzGp =ؼw;&=nVi^zwZXǢ5˯X`NS.N@898nLg&#Яoqq89Ok'':g|$,v^o}8y/{uV ˷>.-7-$!2k8ؔT 883h-W|Ǿ|gF QU lodž8cEyXp"]֗ۖ84S g; '܏s&!4MQY9 ۻsB%TAA@(EaUE) 6VY XnU溺ղmuuuER4)s;$&z^w#wkBGt- _1~r bϼt~]|g~sBO~z;MK`>O<>uH#8OC% L5r+n_DDsU!e55TzF%zZR|J.7&hQ/ vK:dƛ!d{ܾ湎5>Wt=&-[Q!y>#'>딲- ފm 2hu|h? )HAY Zb/""a~su"MI >ȨCಓ[w?w/]oxLY-}6snY:4E w9)bY $;$]d{{90,K95WȹI4O<ɝnOӐd$#|;~q: {)yZqڷ:R/ /_w.h/mf),Z]7{>>7ԮA#ZTs o&|ķ?LQr%>84PWT9*+kS?K-9!? )notݜy|:H}xP5J tm7yyߗӔ Sb͏;~nzflOAw\:dBJRZl òHyTO<\~R 69pd;hy֢to07ŕ`jH& ;:2Î= ;KNfG٪I!'g~+z\{.{_DZGFˣwEz-""kť/ax-O^}{w<ş䦇cz˻+ =-%p?dD y/__09tͪ<7 4[4ONubYDDDD**z=};w{.M, ;v{LIoQ]t*#gwexN?)]1b_M: ,(KOn1ˇ_XЇ9s<)5 .ٴ}7;zwdx _𧩟u_OŸ.<ocة|*54WdKykO'Hǣ^4y) e#SBH +B4g?$g)s jme?18~K,G'R+dpgHj0Aߺ{}^li#czdu?d/RKWԜ.F*Ewؗ?Fbȑ#Zʅwjsj,D#I\ ޴ϒZ:U֪-V9;~M+7zFO e-<|b6éаVR1ugpyVtf;=&$׉};hi (>N o0O;Vkm[_fةl۽ԮvF,۴5nŪ8o%c.ɨx][4DZGѳMٹw?3f/.ZDDD$_Cv\9. qsr3o%Z=HO'[}@:ֳ7.91Wp-I=O5?~iI%x}+es ֚\m/}P vәmշpaRs[y;[}5[6䒓spXKr]{ו_ȡf=iӦVXQ6Ksp1S 0ι1wc';R$u^g/<\΢5h Ych^&գh~xM]SZVrZi}YYYlٵwaqK6la-|j7mS()%ιG+V@9.  *9Xk KJn\sZRu悀a9wucOC)X8}ι7MrUu=ogk_,/WMsלEH#"""RAs1Y/X4׋͞Z;; j;^v۲k/LJf ҵő """4ιݟō/ǿ>:n ڼHи^/IU4\;`^ V̼!֝~h|EDD$)ιAjmnNGݭ) dʂUIGcI0C#0A1іi 7mKi """R Vxzʺ-;#NiMj`OZ>tiۄ>m+Yߺ+y/HX= u'e AƘt.I @[""R3k1>{ޒƹ5=FNiMxL7LjZ0+F1J Gծ^hΌ=IsusG]Xl1xt=Vo^m"+ߖP6_',%װsWX>t=h*WdayUW,|/ʾs]X|miEqZJul#ee$#Q*Is rb[5{)ik֢Z6%mUv`.ZNqs(U. ;xY {iii_FTp kmϾąN)TO&k߄Q%= ?:%:=)}/Vo**(rjxzE59e'0cns}]{3Xѝxt=9peYko?"""u΍2e?,bsk_re GԬʣ?~ԭe^zk~) =h(*pZs26AiR9]EѾaLOPr ƘQIDD8 {Z5P ⢱8mc~8z=k [v噏]{R/V} CaoLL#wEY M+g9WAwκ`賳̄ϖEѷ2iP<~yF97:--$""Rιι׬7åO筯W1qJ&[I:<>]I)$ ɔi.ܚ+=4s*="~GlV;Z˻#jV寃N岓[?_"""ښ Na٦)i{aۊN,H ˩RRww !nAmQVkq)w_# ?dVU?l续;r+J5Ѹn&ԫIhyD-6˱ ꐞf897b1זsUs[kjoe9|_gs1 Xm)ť,)r`̙ׯsApй57:zFtn ظ}q=ؽ/=rY. Ҭ!RUHFtTˠn*ԯQ5`ɳιι' --MuDiӦqFWcǎl޼˗ӹsgƎˉ'aO6uT6lHv>m4zz!훨<5oޜٳgSnBs64k}_3ػb~,$0;~ 1 AZ9wF jVKoPZJ`5-U KUfQլY7xK/%KvZV I]v]+B.] 3fk-(,x?R,]Tιӝs㭵mcpmrpƭd27Ҹnuzf²l1Ykc^ޛv1挴cUA ;pAC 1ۃ 3A 3~cx܀ Nϯg<--̴{.VX K/| 0 βe2d<[f=_lgq͛7gС,^{エoߞ2|zy5j^vӧO?cǎeE}GӦM8p }Yz7s=G۶miٲ%SN=Aecʕ 80K/塇Clْ7| og'<}μ"8n-km_'tX7qJڏ~M՛J+ɰ\5D%!$"ꎔ&))ݻ_۷K/}ݗkvYv-ƍJ*=y/~ MwK/tq.\Hϙ>}:z+3fH:0}ttBnݲٳ']v-Zw?-tl—_~ɸq[ywǢE{իm/]={{aɒ% ߏŲe`Μ9_{O>]xt9Sn^zFq-0{lN9b(sιc/Տ ]ng7kR˲$r uSEn`r[5L eMY$ݻ7o;wZKfG>x`vĉp :!Cp}qaDڂHkgg㹽gnC]*rʕx86<p8%|S-ҽehzW¹yGXr%]tQիW/?#{!xu~sN֮][r+N?pnݚx_8͛<}<ϫVÐv5ϫs=_O>ɔ)S8q"wqo'|r~ RT4ˋ??ύ崑#}j_lڴ+V0kq4ɕ( Ry\}|7L8B_~}>hΝI'oM,ܙ3grW0{l8 V_SNeȐ!̞=MҢEm=;X|9m۶="#/ 2^zѷo_=\j֬ɀ8q"}A̘1CY{!NG ٙn{4紹kûZk^ָۀ+V1kHQ`)4ܹsi֬YvMmaGp ̟?'u30Wb7I8vcε*veϽ[iW:FGcF&~~? O %h>6/ @2ϖ!% 336m*uQd۪^z!jذA3hV2}||XQ\UT""Qvee9P<>|Lb8.NHO&ޡ@ .Ҋ/> cU7"RI:5D%!wx>nJd 0 8+أ`>lK<~i ~ޯa/m<H ˁ9'u!3 ###nz?D׾ O9 ~a}6.I I4-ܟ/Y Xtz'[Y@{ s5o{s5mtbs~kt|~ ؙuHCڨQF1bd;Qw@D-ZC58NKK{0>II5̀@sk`˱ԳYBn. DO6&/<?\Ei.`~uhCJn`Q>zS%=peQIJV&T^ŗiF_°;| lb@U]ް8 禲Y |s_jVsk--7Z%<|/uH z5>Dk:~8|lsv5:fvc^*ڈu;MIDATHըNurkT97Z3cL D\_M|= Wĝ-~yPgyqW4xum%pFxp85p2QǤuHU/ ou3q}`dut$ Ok.Ǘa< |~Ə}_/|3~x#=7^7|9k*(ܯCDDD*UҙxSƍ}c iiicvKNWlInI IP;~~ T6q+}[奨m4{(Gc{"""RTIOc°spN뮥#Fj)6:ֺYUcr`X.$V*IzIHԱ9 >k0c̸$eFEDD׳Mc,_wƘ"U3 }Һ4;"=+$%G; ؓy*CěPr%sOYkmѢEԽUCu7tRLO\ OEDDVZιW),O ޲a8m1^df㜛aZ-EDD*}^Y{Z;?꾔we֢_\9ѿK:+{Jn^1-L """@LxǚBsccvGݏ`֢8ԯ߿lupZ c+l """cA`I#s>p1'h^c`ywG ZǢ(0Tڍ=Q1HUwsƘaNjf:'""""Ip^jym*0HѿC3-Ƙ1廉MfIpés;cL/k""e(ι;29,կO#""""R\7ɩQAD0HQ݂-ꎔ@q$oS=*yyB6Hp9 pY)߄?/""1(HQF,< L:: tkt`rxq@SW 3BSU9p3XOVGg^G:<ɇAHQ -pnܾ:K.ŇѺ/fxazQ1 9%u@a5k@ RDb9#τ[Y@yU鼶Ň˦qm/H7kl1i㣀 ^ a9~C EN/A؂K8''= ?Zh{ ^prF!k|%gWI=^cf]} #o{ 1E7,.fZRgN;"R4 "=prm/. ^L= ~no ŏÏ ]LI=^cZo|=|> ؄1#/_ז{Z90Qer+90b#QL4z9 8"HQ`ZǶӁ×A,[~/7h z$>> 0>k7"L2yfp`9 |}9=IYK&?8#cg&{+k HYDJBea!g#ù5%EA^cIN)F~Kσ(~6DŽ$%uu Wg59/UqfiYEz1?`L `+ֱH,""P2/0' %V?kI͸g{{/!fiZuӟd'~t7D7FKanŏ>om̘{e8gOș ͧ~?:MYDDDD g};n fBYϦ6K#,""""=p97M΍+;&~yrX]q>@70?,DĮ[{gKG_$"jEDJ3ǁWGxC rnoYK. ,Flis8pElbCҲWnr&7AD?fy="POV'}M{YKlf%+,-"rgpLec,X4""X~KDD,";7ziyZD)H>%=oTP9ȡMYD$ ?펈DLYD$2_!""6f<<~uGDD$r )LD0ܑ5Ƙd~۴iM+V LQAkݘ6EDDDD9*++kyPdeeMBs iY ru Mkm}?OJ kUgpT-RH,R8zA0Z׫4v*v+ݦEʕXi+ԫvCu@DDQw@Dr8. `6sܜ/Ԃ7nCfF:,ًK"""eH᜻9筵˟ԮәmyIOĮT'"" A`swc40OwʧUQuJ"""eH cꂀ'Ԯ_Z܍kNkC)E"✫{Z;`,?1׾X^jׯ^9EDD fAP9ۖ]{0f2-^Wjׯ]5Wo8-$니' "9gV̼!֝~h|_)OEJYc" /|ӶH"""RiZ9lQX)FEJv#Is=qN_a~ML#""""" hY$b[5{)ik֢Z6%m,2o59 """),RTTn9H`2e/EDDJHL/DDDJH4kZF0D^_^amm;ȡ٨; abn=i')3DHaު![5LI[e֢)iEDDD$jRh(0H5zRjSSAYDDDDJDMɔi.(+YYDDDDJ\2ee"/ """"Rf-ZhgŰ "@QwADD*0EQ)0HZQ[z =wGDDʡ eP`LV ޯ|3wRLO\#""XY E,-ҽej&uO4buH97sv[#z´5fZM-W/ԂAz9v1fo)vODD*\fZ6{nҔ_`̶QW?0ƸRH,r3Fי;zw 4wMDDLP`9UwsƘaǣHYbDk{k@eICIE)ma9 `1|c̻QGDDʴO )SCY$"ea^JwƘ^yQEDDʼ)b)SKYAD1ZkG ) %uv ~iӦVXQ&LMfXcңs>p²Hvf {/pkm+V| (kSH)n7F|})~#0XCAYDDDDq/ \ mwJdt~4 ˠfIΩQw *aI@YDDDD$fHVw펎Ch>M?s3נ"'+jq3{4l7E뮈HP`^`?~cA(;U߄?)B&"'wjws3XEDDJT$̀s+Ձ'ULsܹѸ}O%FW YR!1p3~@p_ϰބ|4/at`2> _o^}sx\#&&rLհ3wyFܾ{)W.""RL R <Go焏S3+/ܒmcE\.w:gǵ:| _|@x;w?3/Kb_=7뀎G +>Z&yaUx?v~M^m T#E`>A;રOr`H(0KE>ŏwAr.> _-LjysvFOG[tnLn@:p8ܯ7|ɫ/|>x%^`3da' |T4G1hq΍{"pP%lg|ڈ—jM$^ת;5b_RJ&hl>Z|!ܗ{#sn2MnA2~yRܾfekܶe,fm.d7p<4|,|9~|1Aq F@W(B|H2\% _;|eҦ? _V28A{wmKͫG7}0f!ْ[Gc󞈈$M#R|,V ak u>m-(—cP6~/+w39>ԍOć+ćZ,E*HF&>d~ Eu$\b+L6LUdޛ@s|B'""(ѓ0zR19zVG~޷(cdW T4>\g8RF2R="',Áޛ )*I@YDDDDff9$:h@>=jEDDD$%j~FNjR7f/;x,sEDDD$%lH Anӈ"3# c̅mɞ,"""")1kZf-Z[ %Z=HO8^1j^fIYF2s~3rE]1s ۾T(:sGApƘ()0HQZ܍kNkscYk/V꜈H^';49Z{1ⶩ,""""Fc7Ƽ6EDDD$%VQZsczYkEswesY_FEDDD cLzw.5Ɯj]EDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDʾ`9pv1W6R!0 mwDDDDXr`;ШNێ\10mwDDDDY \a@ ~QӁF`P G-qmMVCw*`&9׀ @ŀ ux"""""y (La9|8>&|p>Pҩo@`Axj}|~o BW~\ @N] | ^/Ja[DDDD$i~8s:!|\2XIFK +3 &|> 2Mx_:>0Mk9#9DDDDD?[?G|x̀qۖ=__? >$ùŗllGĵ17E JBEDDD 9xF Y`w6Y? P?|P'l(+[>hwLy'{4nv=e䌺o[n[pܹ57^$8 onovbn=[ [W qm~/Xz#Kx97 y{ۯŗB . ,qm&>3~V  lܷ saRMy ?$<|I(8?Z^S[S)0&z@{rn+Ȼ"""""""""""""""""""""""""""""""""""""""""""""""""""""""r)dU}DIENDB`fastnetmon-1.1.3+dfsg/docs/sFLOW.md000066400000000000000000000050721313534057500170270ustar00rootroot00000000000000### This article will describe everything about sFLOW from hardware side ### Extreme Please be aware! Extreme XOS operating system before 15.4 release lack of support for ingress sflow, only for egress ([source](http://extrcdn.extremenetworks.com/wp-content/uploads/2014/01/EXOS_Command_Reference_Guide_15_4.pdf)). Please use 15.4 version or more recent. Example of sFLOW configuration for Extreme XOS: ```bash configure sflow collector 10.0.2.33 port 6343 vr "VR-Default" # add collector configure sflow agent 10.0.2.15 # agent address configure sflow poll-interval 1 # send data to collector once per second configure sflow sample-rate 1024 # sampling rate enable sflow ports 1:39,1:40,2:39 both # add ports to sFLOW monitoring for egress and ingress traffic. enable sflow # enable sflow globally ``` Check configuration for correctness: ```bash show sflow SFLOW Global Configuration Global Status: enabled Polling interval: 1 Sampling rate: 2048 Maximum cpu sample limit: 2000 SFLOW Configured Agent IP: 10.0.2.15 Operational Agent IP: 10.0.2.15 Collectors Collector IP 10.0.2.33, Port 6343, VR "VR-Default" SFLOW Port Configuration Port Status Sample-rate Subsampling Config / Actual factor 1:39 enabled 1024 / 1024 1 1:40 enabled 1024 / 1024 1 2:39 enabled 1024 / 1024 1 ``` ### Juniper EX Juniper EX sFLOW configuration: [link](http://kb.juniper.net/InfoCenter/index?page=content&id=KB14855). ### sFLOW configuration on Linux We recommend this [project](http://host-sflow.sourceforge.net/). You could use this reference for configurarion on Debian 8 Jessie BOX: ```bash cd /usr/src wget 'http://downloads.sourceforge.net/project/host-sflow/Latest/hsflowd_1.27.3-1_amd64.deb?r=&ts=1435142676&use_mirror=netcologne' -Ohsflowd_1.27.3-1_amd64.deb dpkg -i hsflowd_1.27.3-1_amd64.deb ``` Configure iptables: ```bash MOD_STATISTIC="-m statistic --mode random --probability 0.0025" ULOG_CONFIG="--ulog-nlgroup 1 --ulog-prefix SFLOW --ulog-qthreshold 1" iptables -I INPUT -j ULOG $MOD_STATISTIC $ULOG_CONFIG iptables -I OUTPUT -j ULOG $MOD_STATISTIC $ULOG_CONFIG ``` Configure daemon ```vim /etc/hsflowd.conf```: ```bash DNSSD=off ulogGroup = 1 ulogProbability = 0.0025 collector { ip = 127.0.0.1 udpport = 6343 } ``` Let's start: ```bash systemctl restart hsflowd ``` ### Qtech switches Qtech QSW-8200-52T and Qtech QSW-2850-28T has some bugs in sFLOW implementation. Sflowtool could not parse they too. Waiting answer from developers. fastnetmon-1.1.3+dfsg/packages/000077500000000000000000000000001313534057500163755ustar00rootroot00000000000000fastnetmon-1.1.3+dfsg/packages/CentOS6/000077500000000000000000000000001313534057500176165ustar00rootroot00000000000000fastnetmon-1.1.3+dfsg/packages/CentOS6/fastnetmon-1.1.1-1.x86_64.rpm000066400000000000000000006526221313534057500242600ustar00rootroot00000000000000fastnetmon-1.1.1-1T>D ,0@c6e6172b319e73ad53bd20f6e054e28b411beea0Tz-K]om {ӑ >= ? d   ,0@D Je=( 2 < P   "@T/L/ J/( 8 9 : = > ? @ G H $I 8X @Y P\ l] ^ b d e f l t ,u @v Tw x (y <(Cfastnetmon1.1.11A high performance DoS/DDoS load analyzer built on top of multiple packet capture engines (NetFlow, IPFIX, sFLOW, netmap, PF_RING, PCAP).A high performance DoS/DDoS load analyzer built on top of multiple packet capture engines (NetFlow, IPFIX, sFLOW, netmap, PF_RING, PCAP).UOevo1000.fv.ee GPLv2System Environment/Daemonshttps://github.com/FastVPSEestiOu/fastnetmonlinuxx86_64 exit 0if [ $1 -eq 1 ]; then # It's install /sbin/chkconfig --add fastnetmon /sbin/chkconfig fastnetmon on /sbin/service fastnetmon start # Fix pfring issue with library path echo "/usr/local/lib" > /etc/ld.so.conf.d/pfring.conf /sbin/ldconfig fi if [ $1 -eq 2 ]; then # upgrade #/sbin/service fastnetmon restart >/dev/null 2>&1 chmod 700 /var/log/fastnetmon_attacks fi # Pre remove if [ $1 -eq 0 ]; then # Uninstall # Stops fastnetmon and disable it loading at startup /sbin/service fastnetmon stop >/dev/null 2>&1 /sbin/chkconfig --del fastnetmon fi# Post removeQAUXAUXAUOUOUO422c29f4a070bd9b07721c6f8e2a948ad6a5fee844bb175765df0e202e64fac21035a069f6ccab2a4b0625360dbbfd0f0fb0674732b3fa67fd4367280770c109rootrootrootrootrootrootrootrootrootrootfastnetmon-1.1.1-1.src.rpmconfig(fastnetmon)fastnetmonfastnetmonfastnetmon(x86-64)@ @@@@@@@@@@@@@@@@@@@@@@@@@@@   @/bin/bash/bin/sh/bin/sh/bin/sh/bin/shboost-regexboost-threadchkconfigchkconfigconfig(fastnetmon)daemonizeinitscriptsinitscriptslibboost_regex-mt.so.5()(64bit)libboost_system-mt.so.5()(64bit)libboost_thread-mt.so.5()(64bit)libc.so.6()(64bit)libc.so.6(GLIBC_2.2.5)(64bit)libc.so.6(GLIBC_2.3)(64bit)libform.so.5()(64bit)libgcc_s.so.1()(64bit)libgcc_s.so.1(GCC_3.0)(64bit)liblog4cpp.so.4()(64bit)libm.so.6()(64bit)libm.so.6(GLIBC_2.2.5)(64bit)libncurses.so.5()(64bit)libnuma.so.1()(64bit)libnuma.so.1(libnuma_1.2)(64bit)libpcaplibpcap.so.1()(64bit)libpfring.so()(64bit)libpthread.so.0()(64bit)libpthread.so.0(GLIBC_2.2.5)(64bit)libpthread.so.0(GLIBC_2.3.2)(64bit)libpthread.so.0(GLIBC_2.3.4)(64bit)libstdc++.so.6()(64bit)libstdc++.so.6(CXXABI_1.3)(64bit)libstdc++.so.6(GLIBCXX_3.4)(64bit)libstdc++.so.6(GLIBCXX_3.4.11)(64bit)libstdc++.so.6(GLIBCXX_3.4.9)(64bit)libtinfo.so.5()(64bit)log4cpppfringrpmlib(CompressedFileNames)rpmlib(PayloadFilesHavePrefix)rtld(GNU_HASH)shadow-utils1.1.1-16.0.3-91543.0.4-14.0-14.8.0U@Pavel Odintsov - 1.1.1-1- First RPM package release/bin/sh/bin/sh/bin/sh/bin/sh1.1.1-11.1.1-11.1.1-1fastnetmon.conffastnetmonfastnetmon_clientfastnetmonfastnetmon_attacks/etc//etc/rc.d/init.d//usr/bin//usr/sbin//var/log/-O2 -gcpiogzip9x86_64-redhat-linuxASCII textBourne-Again shell script text executableELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.18, not strippeddirectoryRRRRRRRRR#R$R%R&R'R(R-R RRRRRRRRRRRRRRRR R!R"R#R$R%R&R'R-?0}y\&܈X;QPD+ $&Aoģj=Wmz^xjk[ZP/o}gv5wٙgyy挢B 3DOTS)A!$}@B[qL뭺IZ5]oM3I>]$htW(DxѬ1, 0=G)|pRq2)՚b[R;%`%J1V!բ1uFsE3f[QݬO4X4&[lC-?.E1c"jAy4&Yd4m`h TkjHXtf bk@,`dYNAY/ص~յSh\hunݣ۱zЂ{'ULlѡ|ʁ>1"Ĥ&VݳwhT;kTᑱ$]1WQӦ7XF #,yET>B/O˰3EmQ[rјʶ_c(WZ[.(o.ak/_JkOYi͌r_B%5+D^$l& Lo9?ˊfv(ԗkQ[Yr=+Ni"l%bۗd:u?\e%C;9R^MoY_?s>Beic\]q_*c6FIC X3 BQ}Qd֧||bY u܋"nTsAZ^E}\y^1xZF@< z<m$#f=T괨||>rn/VTWMD?M\֚ai\V}g8 HSN! @(X{0}J8O=5axOô2 +yIv$ȉO O+B-_-Z/xCpNzs՚ߧ5&iU'm@A(#t6GNG!=#YfHp@?-uG%( W۫o>9I>̩G)ϕI%|0y.:%.ܹx_,) >(@!ڠE#9u}%o$jm߆Y+{=%W~~ZYCwOGO_w"YTߘul] U]ZMËFLnooY&?|6/G06|H"gCbCx(R~"xVN~rD"R6vE/D.B"SM1"ruCJk"!PQ"EtA"Eo\w摟+25F>o<؉lHW@ s/'9a]8,ԒWgxTBOCA4"  U-DTVPoD;Mn ʥ29S wt"x|exGBzCws &p-vgB  cC 6AFL_ȹhMX}aZ JRZ3c2ҭ#t#vY<̌Yg*1~<'Q tMh0c³D2={Y! ZA(Sa_6-RJ]֌gP:uF,FS+MFR[괴خwT]Oʥj2& VLѢAe#JpW $[etFѳ7HUӴAeC) R*mx&D'n-F47X4o_@I銿D"tG<I~ $V vIX@1԰HYt,e@tӌ0$5lNHM9E(]YaP*PD4F2eBfK|@ u0tB3j2yoX"%((LzO- ;w6`Ѱ>\XiꏲhP5ФQFv"PZϢ7[m-/HSSQkbƵФ M-L"ot%޲Oe+2NCa˅%C,.8kΘaeZ&h#>#[jb"5qA"*bu-NڑiDͣdj#S2^# H-izQGUzBʤt,GA_p*IiZC:e5'fV-J`TH,t0 qx%'CgA)4=5H dFh͉{Z4Vm2'& "\,-,aa sTd0M}[1-cF߂AFo"Ď}ڣ7)Gci ͩ,.A%eI`NDG2ǾIL,D:1輈Q)Ӗ}ga<=KH>͉N}1Ϭ")[s ďq=0&/rs9#OP9)uԂ\ފ's\z8'x nǁ8;s&܅]9l>ws9q.mܝ;.=8|\Ɓ=sm8x!Łp8x1^/9Gxu|(q59p^ksˁqqǁp>\s?ĕW9\q?ޘKŧP-N@ݱx'q>2RM9ף x RR5pqK ٸqW/)`Oj8%lcUYqNJre]$qLnqKw8xI ?k8toqܕKl|.\"cqw/q5KJ_<gqܓ?8^?7[6>ǫg 8^?Xfƻxul#`[xMl<kgxmlg5p.[8^ lٸ+cƥ8֟?;8֟ 8ސ??8WȜG\giLȣx%A8硊@bَ[DL.J_s5'nWX."AپCш׼qS$Z|yl"[/5V1qhK.#x9>C{13n{ԤʗQP(n`3|hYKDZ[lY鲈xG,Fv9=}e(> % eYqyrǤV?G =:ɷ!?cRq lǔx ɒJ/Su㈜IvAsb:ɘ_лs yAESɓYcc܀.1f-%%bq3fd#brRW#')=p! 'Q%`TPY,B6QU\U*$ns"q4W;TDyRb+13/'>X;\f;=3pV ~flC2RV۟4J3,caT_H*>uGz6AL13,!HA‡γ#ȵOȹXbʶŗ1kG>g緬 w$Q P\!Q$;i92IJⱗ;P|8Vʷ>DD\I>rnn5*oVO:c|fOQė(Ia+bc(?W}(ʔ}R%ʾOreߢMe[ }d^gJaUҒBʕʬ¿S]~ d.Nl(ft͊yGVr94$vcK6*|A8PkeU4W xûkmM>iip Ek!ON#+Yĩŭl-vMZJo`\a֮{啪+iL{[[b<&hp;wPŘpI(pr)C<^\֊Fv])iK"%s1n !]b%'|ҟ|N+ TI 6gn]դJ_6{dw%W^\xU+~yJZ1nۨ6b-u}zmi$i_KQZ[dsCJ@ߢT∜Q9Q( c"Z=܇U֤Ȝ{a9$2盈Y)VQh612B#rFuۙ &G Ğx v£n6k+ABYn9zhym3O@Rx-Td]/(s"0F==f:LqԸ&:͑ζe/N_ڝHxaIU_$ 2%2˫1R op ϕ*3:Htw榣* G*\zUav:9\Ezp s #ⰷ s3ú̾ ]n*1z2+\W>ʝߗ/97@M>II4RNMVfuVقH !:3K6͠CVR`Atƴ4rSsGB=H zaxu=A}]~| 4?Շt,_4B@_йnPPwg :J*п.|}+ƿh~|`Rs(Nse鷳Qe:__ G?IUep&?Q;jP9ސJm{lY%ƒ_&( ?Ϗ`?z6kAssL^WNA_$/@ɓ~C'!ٞ_Xoސ[a vza*oqznttO^_>} _?=[n/a\*! 'A B{$!+ppO~B$׿>Ų}Y3:@Pο|?BWE|'ry?,ܫL_3^L_k?1lLj^ pʇ4c:erpO#~"t7G[T@iݻпk,܃5 q`"Ƭ|W9IU62?+p2w(gp2?w9C&ܥ_]>VfWϷ .+|g+1>%W*.&?^X9v"AWHl+>X*}N9`s)I}"🥶)V9/ng\lFp;Pw?-i/rW$ZgϹlon;>N伵A>L>I>]IAWl+^!r;Z ? t}="ps+Dd=I9pg:su\D]'?w.ϑ*mxGxW>ϰGˏj3MXȸض&Ɖ$5ɩmh321͔[b!1ł GKHKˢ娣Y\bCb^^MC4Hr@ h Fv~ީSgh(5bԤhSbM5YppÔ*޿9+ @*< e diiY!=oe|* cZ[߽jMů ৥X'&_zl>Ǟ~)T*Ρ`wFr.+%b^rS߯%nxEƽ,†]Aw7 ,#"Lioq X_j]nn.(x~ݍp%p [5}Kѻn*XĻO2 I@.Ȼn]{$f[ݵ|!{$8#*< jg齉)/ӧ udAYq)H{.gT=pqY#|t L e=@QIb# c-!:1Lt=Y=rFv(lz%&"ad^4<\H'uɑvr8J.1>2i㶏zr7R<"Nn )*F۔fӤ^L7Uuwqc|i̞(fګI+0 2/«,cBynLoFqYhC}$>:Kdx g *e|c&>E.].ugK];:r=!Oޝ]?N\'P2B?P=ܛNg&$;WJ%*12[6bšr*Sq#D'tVy\$P9Fg\r eU*Ęl;1[ʄ^`TLO!o/qE~JbURχȨPQCɊ3!*3U& n \y ˍDHey+5 8˗Ye*n uuQ2-}WI %R& }\^(;3L&:;xU<4aO"[w(\  ]&1}iK6I*iן'He b^Ȥ&F2M6*>bܵBn */{O!?wcA6jx{{ϭzB) J YD% Qh+)_ X, Ep|z 슞PI г PA!DN=''!r/d: F(X[&zfqpDPXơ0(0(Ba x<棰(,|%(,Ca9 PXZ֣ (|@a+ Q؁W({Q؇~ #p=1QQ8)NpS~G" WP F8i(B> ^J74IPۗ M(8 =wVB (@ u(۞ BWm!%kN-dY;B!]Gƀ0B!.(tPEGZ@K%SBf CPB (M]0(X }.z: =G0zz"^(Lx't>SJ𜏞 PXb-R\>F_|&u Q -* P؎Nȳ =Fa Q8aC;H=z;(|P8(Orz~X z5D(E C38 'QA* 2|+ 5&zB6 r|* $~#&( [B (ieGinB{T(FU1](tG!>(Ga Vz?CP 4L30xZӊBY#hN| z8&r(KPH3=B(,Gc 5(|FH9z#[9@ <>|1](wM*{t%[v\vnu FV9u:+?9i/]MN;M/3-]zL].ۦp_]>ڣ+ӿ7ҧr: -0YgR^}.w\>iqG7Owcykvq?-:mS//8=0S>Ǜd~~Jle|oqCecs]+T[H5xy/N;zճJmX]E/5Zݶ4j6c-&t0Uk_Kd3"<_3}ԭEz)S;Q>87]SRvVuؖr6}fC]&5{)c\^TðW'qdn;^x繸wd[91pn6Y};yуl< +fwTʀj~*V\ݽdGGo޼' w>)-=͘{5 ;%"ȋw;B_/T=3qLu}6nσ +ؼpÇ%ҿ4qXvv HFKM/Ir孶m o}IoMerO慕,YVT3직*\=xדG*YL7v|u}_ō6hTlne c&#m_?S|zFvu Y>B%gsjĞE}M3ӸuOw?yFS[ٟ%u}n֦5OGmD_~ sFСź\.o8^\WXmum|7.D:jG?Ғ{ݖd{/|_>K쿵j~%sJ=jLtN/ts<KS.8<ųc4`mT6{jLOu7'Wiga3P\e%Mxgʇ)54MQԪs'|}z5W {sT\1a7gwMn E6 < /Z{o} oQ^5nCہK }dyд:WS[(Zd Y[^pNKFt4Ʃ?rŕgk5l謡)gK>RcG/HcY~_ÿͪU 36j:Ee<  LU}^&_$X7}!۰/rc}gS{O'gN􍕱~<5;v̨i57Ĝ Zq1;-vQrD};ƜF\ rIZ-Ѻmݛ1Q(}hȯ+Lc4"ӾqY4&Ȧ|_wCγd;XlÑS fhPsx=U}t3z9ɅLh՚YyesjOr\m.kS;7uzdeiwm^*V+.{~f=߹i5&׿Y͵wΙ5v^>aOkB-x,9o>n;2hĖnͮN4ySYbAyj]vR̐8|:ّK_<\ty%Ki|[W3IE7oT57eLݛ{s~1ss~RDŽⶱ7\^+YoQ>ɼ~c%P5ˆcrI =qp" s~}GsϾSkY=ڇkl7tqҥvE[lEgOk,}ǿog"7>v\BG;up]aMY!꓿=}ȡ [[30ѕkjyy!ղKWw. X<%xĦ~$wt$vͬ5yy>Zέ~yC]68_wt˷fm?iotR̿l_u7n)_o=EV۷+i1)Ջզ{gS8|}uKk~lk tTϐqKX[rgZ_-<.UTv瞱yo{|!i͜]oez~i}5ϟ04,ڻ0 tyޮGU$֚7tQI'|Gur-EtdvIu;!\ptn+j䃱/dU٦Sƿ'ijHM47-jt㗃A#K;K-ne֏[[_o<ѥ^Fŵy飤2r#Y=rZfπ&G}|=cSb-k5"46\66JCzSozl%چ$؆?Hz-+/umw"ЉkmO yWwrᝒlÇ4)W^GEEG"|Y#R_;8xxH?6|H."EqH?r>wILߋ1"."'D&"E:!"|k!DMOH;VRr%Ҏ"ȧH5 3>|26|rS}%BI"T,I V"|iw~)OWYY:">CL9Q< yD%ϊ"t΋ )Eh*ن/ XO{rDTim"0QD?|%rDM"~ceR_ݫ,"I"Z,Bg""<<6/}?_]D'0E׉_ΈA E"F_D/WH?z,"?D"R"scE䤝MI$}hgX'-@aal<Y~^B>O3Qd?:=w{2[𘤧yn$}6&'O6?# ?Ch[3N?Ѐ$V!x?IIA )w$>$pk?ϳ0=zB𛪑NDOxD豃}UҨ>#xr9x#h_zOy~]|$}焞8skh Nzx3k# z>`~G$+myh~2 Lr_E9ç%B"߂9ϛ֑LJ:"1^?k:t6@(Nux/1+~ /?>67&"A'SƅR.eT2}K/=Es{<&="l RA*+ zx4УAދϟ^rKf[ aCLRsq0fMlXi |+&yhC>Boߑ?AXG/x Izq C5G7>F=|,]wmr~ zs_}xo@'?Bo%IʼnxxIAAe}yѓzs?O2r0.&Anz'/#G z`s .?C VT>I7r+t'=`>gt:$%АkOmP?A;\) oa?mBK.Wb#v9'ւzP=ygsrt)dw_$>;>@>!xT|$Fr>Ы/kpq/|<"W}ش v牙BA;O0ֶPqܓԝK6@Gzi'.S7OX@. %\< shdž KMfղ?]Own_xǡ`;q ػ|W <Lϟ|9m߻$|o+ (؅U@'<ѶOڶ};YHD_lY5Q2C[@}-t=.ܶ2^~h"v=O]r>.I\Kj^?Z NIz-~~w{mۣѾKY^?=\:#ø]m۝d>xg׎o3C;Th/At|#CGgP.ߛk  |,&|w?;^'?O+ h97S'xd;*Cw3i!:yQ?,"xX.gQ<P{'7EVw|nƭ;P{\(κTg?`~3@'$ 2j]NKv ] _*A2;cK?<󊟀^Í?p @$6^Ad&p%/C?v$>DƧ>IF`O}mRTO.voR.[B-A%]}Ђ[.荢<~&?U0DW /N% -T#g5;vIП;K!W:7;9}Tֳ6΂8_ȹl-)yʝƶ}]F4ߧR l/-Dl$>e/;]0oI/aH~/O3+a~;|ScmƢrA3Wuxy"̧ރr99މ%oصGA%tNz dgS۶@TߪǑ?G #/<Jolj`}3]0ov+JG +m}}vݼ)S ř@N[S_-]/7 WMz͆h !ϰ^L쯈أ 3M@+KQojcg5'a?7o}_n'E&/ o)9_em:oֿa^w ;S` ֋OZ&6ZaEQ{QLϷ{~y ^mV׋O~.ǧG{$_/5uj)ҕ7~h~#ߞ{gX~&_W:2[N4vaܡZA/_{u'q`7cjB?@v>Ϭ./灕$!>8J}VS d 9tl~z.Gă]v"0~I↓ԋ#'ly;&joϬ. 1^طR<::8}8:kaQCR,vm2*ʛ0˃/#a^7O;,͟W7|zЫ0J]=9DmXڑpPŰ?#^us`b U!ߎL-y|ec.pm֑Fꯒ5t|`Wl~!اem ̃#௟:Ӷضо$/d8[LPJwxĿعد,<7ע|klmW7:i.?},zu {*K8̟"WaX-ˆ0x*a>sxY722o 7yBW'e@]_}4$:o%/#sy* cq /꥖Pne~zY;߯N}<C[(]ûdzzH}aORG/OЯ "g `ԓ_@¼}re ߏuP6`艃lsf^Mз[/ր!TW[ 3toeڶ#a`Ϧ W WSt?X)g]`'/` y=E㠿-] z xt%Hz{nA>akɶ It;yga~>} %Ȑ/l+`/X_ ~l$_nkٶ;aZ}~E|m7iQ/(~ }c1f_Qa a//λ?Ga򠾇fm3g| _:+˿'տ"/a^.-=7 C~s10U!`OX/柫ڝp7By\|zT@ItC`~jހثzw _hY|e ?>"ޠO2`aAH8)1:kC}=߻xL2< -=>#c`7I<z bKa\Vڗ>艇uռ̋fz}:oVd۾ 2hw(ЯGsJ ̏n:{з o3c)(MB3A?xn$}spL~_?My[ɟs6ق}z_D0_=6m;r"&7R¾Rp=ZXG|O!`n|]M'P88'"yK: @P棲z(c>טz BL ; 毒BAM/@3r7Ã->A`VZ?3yۻa}?i?E/| L9FsW?Aqԯ]·ﺯ`ٕwĀަ뉍}`{1n׷wmGqkMt'~{4m:ީHl o 㭣͆ y |~Nᤚ~ AV/_v|loq: Vd&@G c}tcka?j!Q= '@~|y؁I@^7_Ka]k y/h_]qy08qKlGn vt0}jMn4$%t 2Hy"7`|q})ܮI0NHŰ|~W_~ V ~i9|?o^χYe~g q7yHΣr z>K ^0 _oH>}~ YG IO27^$OC W#X$Xga[&y|ӟ7 W#ԆzEmg9,F86ڶcEΏl{TGx~q'^෫Vg~e{}; ~7~w,}S*8}l qwmOD&#+O۶˭x_0^΂upqwO.  |ea)wI!|~ o<0~|,\Xi F=0`̰bޙz.]?iܫB A:o2Ԛ-=^[~.h!FC: m y9~7uxUioFnYV:7O[trYJ;F/jT;U[b -/džFx"7(1*z3=[5L)t߇N)aNݎT}8P.`xmKXXKW; ;@-Z,gv"1Q#4PtM A{,zN, AӓATޤ2+&Ő;Vk)b!x_dX8"Ek}=({-_;i i,)Z3rYm|]1߽eb5ޓE~- _YdIg"7h?I&Ne福yܱ7ׁ{)} x xO߼Ge >T65܇QGz?}kIi+ߓXE'DR[׿kuև@oX'vr<ѓ3o$ 1ghgS'^хt֪^>!rm`$<*qqbή]u>>^~ Qޤ͕uvd Aow%lk*J,ߙ?Ί*CT@Uc5k VKNE"{jz*4=ul׸`M\d V60RKh#c,(wE95q-4qXXMl*GGI⬁&ZԈ U*X#Q. L[,Ɖ M7XI4D=-Ƣ ġn3tVMrzFlAiT0a0@ ,f"h5'Xɤ&A -P_DM7"=RڌL Ec1g--dɲXieM@Ʉへ ZqgbQPtՍ~Vt2CM&}zdALB qȐ$.2n*'Lƙl{|eQ_}Sy1uGq/.,MMhu'~UCڐաC.d4 lr_' 䕕:KA)ѝWr]rwԊ]pLEt@-!W0@Qf lcibeSզFW)zklpM֌Q!G#ԝ /J0"fndew;KpҋmU ,>ۮa "j8ܔ= sbyZ4fg)}a>ߡ6vZg#tЩB%,΂}R@ʪ~ eG}JUbwY) *X}*3 !cZ@#ʐGީ됙CZR%wܡI -4[L ng1R:D>TySӜ0!SEZfuv''v JcޠQ0t+\<:DUcwh`n, #{D^Y Q6$e:]ạq}^aeQIŻ*@t{*r;Ej 1D\(jm!OafF>Yɶ;J\ )m4wq"RrS04u0ڙƆ2ЇI}:^()3u,L@SV>zJ9@a\ O0 dC}Q^ Kaʀ %SSꁎz1?2 )4A*Sv @{] ԑv&p5BMf]5f8cf z \ƢLe*?V`PL_ vfK69awf,u Pr1 ؋r$ӧ 8\d޺=k }Pڴ#MM( f:#,yqʊݙNU7 j,gKh]T)֍ RP3%& aٯ{ eH.>^np>OY>= `syrŗ 5*hO.–Lc%@ h0rS Yh%%9.C ܆<| ˉ:w{UN?C.Pa(q䕀5XV\,_n( M\ |GF+!vQ|>Zβ<4 ~y* U`>e9 4+1A4)\{*0,f 9yyrAWٱC䱑'T-q;3 dЫ(giC)Sy ZDŽ#dG8@Y,;&ha)HdTj]0t,,K( 7 aYV:op~ނ"EKa㜉zEEjsBWh~}2l0 l(/C2򠕻 @ (]Ha8u c'`Ѹ8]q SʑWUZ(.C4e)~'X *J^23{kH>7*RapdA y; LJ&ss褵6&w Ԓ\ J9:*"">]Q`v8ATidGRaF^vil q7ۍm3S'w Tx()I`) ꥛qZ|):7B^+Q(~ߠ'!$mm3Ǭ6ܳqÓXĶUA1"xX`*)fKWCmDi ZypC"XRVQc`|f1WLdz%t zA+[ȏe&/0P0lUVr*sʝlFla(dRz#ft`"AȎQmUd:&!׳\p-蠋h6 aBv}=8H&"i2аMcPgy.mTҜ7ʫhE.5畕;~_aGOHg[c&oHϺ>SՀVer{iAyncPC_a.kJh+3O{l+sM%r쎁:y5&H%d:(L&X1'Z0.hFwkf:gm# "VgI]7l:>; өLT7h0 flxpŠ .X61U0a̡-̆'vR1;yHU@_+ux0m LIj'u4`~NZm?Q[ioK ԶT ~+p=xeIn;".:pw FHkm;h~.7YaI8"-iKgNr@Mpw[adf do a8١y w\;KRyhٲr;ʓŽ5 L:uE1 k{)Yc'X0axVh-]t(b+h'%T=풌O+pvf 0`{_m# $,P$ x\ o3XSӥK:S*sࠍIqhcZ$]tM/=޳-(]rgYsۋlyܬ/&&;Kth[W۱|NRB6 rId}NׄӔ&򢢞=ۼGƔLOPXINiqB32X~+Q$*r(5t7\ Dw25h>/61ʉ^NbM}mcMirBK&EEKzr3G/ctuOw] Ni3!U="<}ηWisz] P2(p G P҄V¹dk`gpx fa™}  *5nVO^ 5G7Am9ٙxōPWO7#th{w_C<\ 8sAb\Sގ)NAzj08wLsE{6e'KݢL,.3(pNz V; 6hadu hC,ɶ¢FN@ dz`yWSѥ.Po]YFPF0级帘n7jw . yL'pIp6ceDZ<@n-ltW;熲wc&ݴ@T:גSkH귦1qHMv :tvt֓3 ?ݤL7;!']jW2Y:|Q9E88=o{42;.Ԫnݱ2 JK#ѯpa09 ,no1u3KٮMTqQ,RSD>Mԩe@Q^E:\| UT[r܅Ómc+%mThJ:]¥OġPHb \ 7SaV ܖhH+wa tc z_=MSY9^=ֶWKKX*!lST.5J \sm'~sd'&$ڬ 7~#8IR2p=0wuӇh󔃀:rvsҎf[TX%=0KZ7xlq[uQGԮFSgPJ_ZcM"hVjtLe75+;7Q GNДމQ6zӝ]RܱkxLb; ǧWdl@0p J{9SKa2k|Q6)tt8Nm@& ss}6' .uԔF UvjvD?.f[`%.XDžsƛ=esCI)!U8Xr:&':.̺!&`\*sgtdN@|vM[ՖW:<ӎ>QJMah-K[pi8z/Wv#{.p@p^+6ڞ84wP|tҕ{v6J\ota,(xhH Ew&hԜm9n^j[v֞;.nS*(\)}w6mYnKժn^fL voS2V8G}'//`P4]?ooLP^7ȑwh1P\OeQ<GRO]8i wFd i ׆szsR([ ی fh+ PnGę)MY(o'6xnm"s ŵmޗf;mts,ad<#/xG# HByc.Ĕ7;J^ya6:v荳18qіو` JyTqh ūnS"pQ%J218m54t+ǁeA2n+\"^·ToGĶ?ħ ~d,;2ݍnGl(97t<`#k:s b GpLM1㻮Q$tUճZ1e&C@T(ܔ'M+ږ8>Ma0/s\=?hW%zAzM|Uol&SNl&CFh؀PK.D B61j3&AAe`Ԛl[(C~v/shEgD* wyV·&oտ 1z, O;3SU ]_zXA%ms*- XXC'0u <]t]}GBt3?:p 8zSf{L;n{q{`?l9&ݑܣntI;::/#=R < 9[Sr 'T[ jF.a!PtaDkXg#NCA(>e -vɐF<j?𛇚t|vDž}SA%q[~̙+)fJi=`[Q)? bJܹ38PGg΄-zFqm8,l' cz%>ڣtwmBx({rNWgġIff%;STLM歷.i ӧa- s~مiTje9<}椬ffL&L*Nrݲ:6G)s:u(~4^#WLus@Nb^6*X".ґ_pMlff%XQ07A1-2Em409SpF~ZI*sFh=F]>/(Qo)4l!Zҥôk9%[$vg*QB''.[JVcLm vxS ] 0A8Awm6'_)* o2* @i|E\k_۝:35K;q w-(s3l:bG%Uݿ5AYa` ^|T-DFxKPaU?7B 멉/t ځ5J`̣ATw} ޴U3%L]߁F_QpUF=2, 2e0Q,='B)IL`GYD" ĩOz2h_W3P&0"w?ZAM!a;o@p!J=!,lOֶO0 w =IH(!J_kIHBvd d`-eV_,WoI" !F7oz>=]^Dvs?om >ٍ?^^O~<q~*_B7b9nI^YW ue?FelMV}I79 :;8Q^V^?Ļ4s6? >pW@~, ='#@ OuQ$j %}=͋µYޒQSo04?; c#ًu|>1~:~71#:>m~(/vfOz2ßYt|#1E 5:5Vo51{_ʟg,Vde/ _eY\@2>f~\?+ ?0C ?7d:>OdXq '0 od 0|G3cx3 ?1|!ß.?Lf~62|ß ~( _02?W0y sg8. ??sga =Oqg3%\R. . #7rgx7sgD. ᓹ3p. ??çpgT. 9?_Grg?qg?sg4. Gqg . ??grg1\3uU\\j. ??gqgq\. -qg3x. 'pg3 \F. ??opg[3$. o3|y\>?;3|1:~ /N. _rgb. _K3|wqg۸3|9wsgx\~gpg۹3L. ?᫹3| ?rg:. ??\>. ?k sgz. ???3\. ??rg3\ffG3. W. ??3|%0\ . $_ߢOsgg3`%\\Y. _b3 \E. ?ÿ_pgWqg׹3r. ?ÿWrgU\~5?7rg5|g\r6^0:. ˿Uo3&. pgl0[\m. ?ÿgZ!ÿ<::SjCdaaIoi0igc _0rDP00 G">5>0 ލ06w"< .'8D?7#C|5B<>?8'B/$ o~ O<aD?'ZO|_Dዉ~?Am_J ^eD?+#+?Fg6?'x!‰D?N" dN<D?!JD?7#|Ox$ D?O4N' QD?C hAgG"<'pWDBa+ON& D?" ~ qD?'x%?uD?O <'x!D?@|ߍ D?3'D?EDD?7#B|5·BxOۉL|19D?"K<p!OnD?;." D?.& ~ uD?D'Yo# ~r~!!  $ 4r~N 'x<3~FQI|9w?Q#\M|15D?"|7OPk~ \G<YD?C|{~"<'x7^G >mOD?Cn ~D?O <'x!~! O}?D|7@r!W\ߌD?~'jF< _?^D|1_D?C~'x‹~!Op$O~g~"'x7$ މD? O6D?~'x%/S#'Y_" ~ᗉ~" O<_% ׈~Fu/' .GxOpo"'fWGx5OI< F^CGpO% uD?C^O< D?H&ën& >fo! މ[D? 6O6! ~ w~!OJ'R#'Y? ~mD? ONڍ#W `!E Eliݱ%y ~vwcwU%u=Kt!K(K:hI==,F?شB^ MhYWа-z{V[RZ@h,1{PVmI}o٩(-;wB [wXYY|P=#pU7-;+W"Ygpn0êzPu#GǼיG ^iWVN_E\ݶs,[,_~|l_n@oGJa1~%vsWRGD[S߉~e>˻_9 _(GSRVՒy JEN} 9 tUm~S?>P<:>2ΐiګ a0ElEF ʑhOFc!ʡ/"H20iZ^- R$6{z<2+2"R|4e}d8񦦖fu\Pڄs]5X!Q~94FG@ӏ{FN!G5`BӚb?)2f`]|~zvK f,X&gnOot7Lv^c<%ۥ$knSR7̥6*ERX~ڌ@H#f#I Zm #+F80ťPIAٟ7"SVFhǴURe^ICC҂쓉-_4.(Į|b+gihDpM΃+эhE6Hwj:,BdS7t0]n* q>sKEOH7_q3ǟշCwCi% tEeA: +YtB:%zH|R $W3թ$iy&jy&˲5䓱쓵tt)jKk QTӨ׍'ʾﱤɿRIh%=g<.%U>*5;AL׸zˆWjoo d|{vj|Rv!^)TxA{x{`G=@YRF#9G/QyZ+i#JJY~8-ZI;XI3lzq#ڐ(* JlJg1z*i|T6%DIZIWSQR|TJjvSIz b2-c''NǡW!v=̪ah֓-{[Ze{u tg[j,ahVYup̵#qxVa"X|i01[={KͺʹϊەA(Y^Ь6e}J ilwʷ{ ?WR|A9){׫E0M=OxGBFE].OGQ^c 7JtaOHt>M}ol\F@B5I[:.~,%JWT1L&FYQ/b?;dC2*͢5yTx%=ЪcQzt0JbaIR"oL:1JLK45GĊY_= .7'Wo}&X]`cP*ېcÝCƟo ӌ8eH 'C4P~tnM'"Lal׵ʭVIVZ"f@l]u@dѻVs YEQP8HF;zVXѵ$ZjI[t+Enms_+{_qEz*=ZTKzؑR\ .$&ލѳj+ـrk-0#+"h: 3B="r`Miϐ)cG3 ۍ_ ֍FE7_33> x¹62[o.ѦM_h\4*Pa~zvD-@W~]QS9V4~):?>,\n+#Ꙅ@rz׷4вH$&ZGkc2W[.eabPcBuBv/eG/w;؈U|H[1[|iP%s&NS;zf++ql'ȍYMuvR(ٻ oM+KuGkk$@|~(48o J*R c7kgRcLr+Slmj~<ߴ+w2o9Gkesү7M,jjBK5(lR>UU=/jLxNJȐ+3ى;v--!M.Lp0&؀:@}OIqa7_/U(PFG/97-`tSY;`G//ެQ|ŋ GG_a( :1 ??:$]7C L H%8nH>*EJPǂ_e*^3 綀*GLҎSeyn@Qc[vd>.kqx7+Ma3{* ͷEXS7Dߛz0,kEG`ԟi>Fe 8!Z[Ǵ*+B;YN6kRWH*aTӤR&4I%$MG;"zV'~/ C7b֏uD#[NvL  -M+J*[ VQ.΄Lw`A`IPMէ~llV?SECu4aDx3&B&cb*.,xN[Cx3 \0^擷 ړ̢= מ%y|KsWа躇wagk'&{J拁:l䵘G׶%|nNZM Z;vߘ;a=3sOf!kgkv~za^ډC$+Ht)Zt@+h`|1/'7:gJQ:ٞvFEiz8Ri#Эbc^bBWT1|;Sµz6j^e;΂v6vJGݚ/f._R-÷0\;T3)sjKٓ@14b;~kt`KLMtSG~AS?,y!ӁtiRFFӁ>f[U}(rlf҇6YS?o|zRJ(CKAXdY> և6m9I.Y2," W-՜H愅`"{ā˄ e!w=E0lmUvm8(ʎeɲXy)٨,;A*Ѥr= M1 n"ST J8eZ߾_Mj@{xD$N>&* Ht%O4_+)3 Y<#ZDdڈ @oK>Fh;$*kDf崏pOTSaW~@Lb2G;Ge-,A▿ ]"B{u\ϟqn ciF leZo$Plzb(_ sϿߑ~7:T ?"ըlOCff,R[v#,p2gCVbQQ[5+^ H}Lfƾ%We]m& {}$y1ɧʆm Yv!F \@s([f]m7r[K|釄/}wFC4A}?tkv:Уs {UǡGYHƁzjr_$ֻF#%rZҖÀBkp|V uk42? 5yZ8ʼK1lfwQP2 cq61˫+R8WbR՗]{KI- R{0Y\-͛e?kU} 6WZ>Ͳ 10qd(?̠`{OǥDyـoK޲`uUp:'0br ?q~ |GT `/5@ѰsWw䌽ݠ}JM@Obwܝv޷z|N۪%l˶jrlZ]M[V|?W1-|,al6>$#Lݠ:s^<4W1V dCj/+Iܩбs%ӔM[Yyέ0R濰5/ヌhMiZQ+\q:Рܴ(Km{fQ]pOBǍj3)-셣Yht= ,c&{@J-d'ϥrmP.bQ>ʧC#r_B-GoSUC G>QERP~KLJm؝r-bq63Wכ(` 1nGB< Z6[I']*oݹAXk+"Q˾-><w;DYD*ύ}iPƮQ E׽H3ȕoR܄q ѳj"?X`] ًxl!~FKYAaϭj25aDl$2znZ/ޅ,f-IbMk/d8eiB0 e8AXQ-N;m#OTL0Ƃ$12WBn$@=-8lY;V2'+ +'J!mr̿(d]ب|owUEt$2J75^H7[gjH7gnnT&Sc ҥ Jt3[8EuykM,"xZ(:qLA~tUn;ZkpLxz=9_px:ʃku Ϫl@]8\s' UƼa8}1h)F: l^+|ZJ>o/-\Xo>ߵ=>?|suBK$y%%G߬f|>&ܤϟ/]k7~Frxrk{$u>>>wy{ *o5qx%Gu_#8ܠs3_λ8\(8\(8p5r{𥫈ÎYpWsxkqPaeNRf]ËJ6 .\8|8:ͯ }ArLo SpIar?@oAW'9yö>-b-<+>+V)F'T9cŊsU=YQxb* z\ilhuB+&,X񀪁!+EV<;#ݩ_XV\L{8U:G/0V(Xᓬ +THV4X?+~=ItBwgx "+V#+f=KhHV|28`#V4gONJ]+ U.eK3USdȊ$V▬~rbGpgʼncŶ+:Q'Ty9Ɗ*ɊYVȊ%M\,  f<WE &>x M|+mPqfEn `oZH0=J0(N!&ya2ZcJu{˂LCȬb!#R|dG<Cy1V<+` x2YjXR%%-Ѣ E{DRRe %bfpoeV#jYm,+{ڈYkWS iY+X(FEH!F5̳Uƈ|F- O.\rr},=R{(2W`eA<+`4@S܍ w]G=II?xP- O4.Y˥lFNmăyN$t}*@-2BxkDk- ޲M|ӻ_Kޏ :+ECʀ/cZzLUǎ~(dzhғj<5bmm]zpgt_ORW'75էY?WO` 79#bӽ / :>]t^*j=>د>O'"):7u01@Qt*4`Q0CjӦgvn~ԅ<ː?.`a⣀ -ZJ~*0݈%")e]QEjX5A} G> ,_T!I؟+N.Ifoצyx[ZH?WO~  ?+;ao"In ?.T^ݨzhm!2 ܉9 c3"wPZVܝ*}/VϗQ6G-#c|7P>yH[yOZJ25\gbZ&2_E>HύAޟA z?:A߂ҟA}%AWAA ^[t/]}l.{hfŽ͐put/cB/͞Do1(Nc#Yb녩xSa>#OeeVZ&5Kw Ζw OVi}wO%QLV2"nǛr|c9ScM#m_NQIGf:LRޓOk7p%XCUԻ>MDnn`BX2% yd&cx ^|{:^'r.!g p' HN*[Ej:XY>7LkSΈ|F2Z^n$oӑӋ%h=PMDdzzƷ?bd z`O4}!^C2sXϒ5bh96Ϫ|](7 տd.L,aCWjdd+簡z-Ov!&sdgaC5W =]&WφjOa:, srfp("JT+>ˣ-s0^;W 7_x|ʏ q~A-W`Ff 02{ʑ Ԅ#,M8Ix1?z1`Wcc+-ƣ_\K񭱸kƨ7h- =ܴ wNVtw݋mԬYxվ8K= /yD+19z+ aI*!s :g/ztx[D=/o&naT\ڱu^.Fq r]D>!U34/Lca N n#ɆN6۾~g9q%8]wz@rYVjEwyhOwu+񳶖7jٲ7<#)emA`ι|OU(o܏hT򱶫njb~4[{p8_vw:# ѩLѭެEc[>ߐ9k[t~ /pDt/$>-6~ji:IZ ]6 L(ݻVks,tWVT^@>YbZ CV)~rbO%3Yj/"H@c !4`]"xK?q;;9}$, '"zwD98Pg~;+GҸYJ?H 564C^:52AW׸= x}B2 o5#f·fV2 c/br0nI ^g࿯@"Q5cH[k,-YޝR>Ky;b@O#ljqYr)FGjtj3)zŭ'sd`|'w?TK%LIuv2t>bl[kX 3(÷Ɗ^s ]?-l5Ҟbh7wɛfeje,gU?-DE؉1huJ~k,,Q 1ޖG77PY=YF TI.%tawSڍM9we(xI?3izU ]"HN8B}z:B4/ȊWLX fQ\.2BFG/_JY @OO{Ʀ{F/=z||㉒@۝Bk`.bwb(57g<ڳ8d{={Aj4 r㰰W`EmsCqbܙ CqJU0b10k o^yFM% B߯b~_U2ыWmW )oP!FTVL[&} 5zy}~e YK #WjS[Z8=B2fpYw M#$I^)H^Hлm7Jيjj=TʹX^ځ+eΤj/P?<"ko3ר&,(̤A nC3 .Qdقh4uFA#㠢DEĐ%eSm a aI&y^Gҷ:uTթSN}Li6PKe\bV6BH=&#/`~w Bֿ\u&[#ҢJϛD!D+RB&{XF,{d\7^Cd? W`ڏm?W ۾Co!='L%S3('߀|fT$varoRܹG(r.߀=725ae^2w>AK<7ZǠM+ZMe|= ^B1[_ﭭgp?>/ip/1]{!EBۆal#_3ȯHx|}~PtrQ_vXxwÉ5xzb5IC2[DJ~֩&guIxROSӼi+E<2UB u ]Jr^z0*>^/Nu sfB5Xo7'2vw_\1';bV T\BJU>Jtyq{blߠZKK`%avgO8E B~S%zU@l[~ռލZc/:hŗc/D;%(>h|TyGMG2sT~,Pʶ _ujREgiGʊgvBQm})@Zx?5Y]|T_M:V~:kr* ὜j(f?4*GdwVzvR*>V2t e?Е.p9?\GM譤:Wt/tb#V+6s<,.Xl6ۣ~d4?)%>ϔMVׄ(YjF\CÀǑw~|̹\RAPgfL< zs eQ݃|s 9Lֽ ;-oixLE=5z쉛 LIһ齎^Z"/Ox7ݫ{Sy}=4 Ŷ-5bHoog^p-4R43vS@_0;MZ^0Hh9:wZ0վ0! (- yX[7:Xb`/TMg<;T65PUtP[I^_6~؟ S*[ 7(5wOg`Ji0I y-e/9++\2z;rMw4.zDQ\A Z|-C`q?F𭭆ѩ$!8K^ n /T85}&Ӊyiǃ Z{ߘ۬,M+g+"Rsgw|PW{̇87Ǻ_BYvtm,;6]vyxwsɖ W8[bO2ڷz3Ow[-EMݤYbo(~G^u6K˰n%a"O]nfѝWs/x^ˁe֑aY@pzt3~+AT4ʱK(ZZ3F0zZX|asE :|)zt?OMa&jo{\nLb]F&Ȼ2Iƈ}䲰&&o&M1!1v_DwȴK50IKJrR輰<";}*wCNwWEXEn2wfZD$R7 [*jɛ̝h&3'X-JqyW=c(K u L@i̪*꽲(VNަ%Vzd\j «[=R-w3C!~ oJÓHP&Ϟj1 I$һyx qB~g;}a/TJE:F^M_bXY1,a+ Bp7 3/E<%apK% 6Gхnwfu#3\`*^Gʣnin(iH(UƬaBQOŨX&Z]L GCizC%H*"$#K@1L2=Gz\`t(_MO1*֝t02kn]'*OXXSZקp"Lc{AÎ軦`Zc-xA _ZwLUzWƹc-L6-c$R%z+d[~rS$ƱdwmG h[̫Nd:"dP0QJBJ޴y E F,C3ID_rBvGPˈpB+֖ ^iXfR7ɼdZL [n{r[B~s dE(\ Cb>^y$"l[R'N7 R7?Sx!n8G?7([[Tϑ̓i_woʆ)f7ѤG92%٨D( 'FTV.4}~1s,"JLŨ°_[Lokߙo?<\`R7ѧ#"2䢉n2j[ZC ȭed(|0z"ǣ#.-qޥ=g(hO냺aCʺzS0 Ԧ_d p?}qu'~Ͻsn]ɡޑIqϦځpFBlwWoO؊y7~,GR|7$=t(1 2PTaT]݂qM*v2m~Ȏ.YJV^8{-0py >]nĽgnM')?vpckɎ[ݽ/e-^mXDSF ->dV-b#wJj^o#yoW;F8 ثgWy;^Ր';(WM,5Zz` +OYFDi43R8E'( +,lF ߐW)ߊ3ݷ2[:PjLawk _يz3YvO}B;O }aYi,""Z?80^"&dGްV*+T&(]G׮)#h>;xF^"Oh>?e?pPA@( F[Lfi@¡Ɵ]}m ?Ow+ L~sHq \GPp7p/7$o 88 g ܦV,x~CK_LmL$//&ЗY<1YIn?[,DDbM4,4)&yD~t{uy)Pi$t`IѲWPY7#[D`M~BuRǠF$]<.!Uu&iG>`³xIY)z0(f* v)pz}E:;W&,YA#z[sfW2?́ѡ|kEFTV|y10"S-H[C{d70/DEa*% yrrmB@zMwD9(*g7;4.@  tC^D!uO a!%F/c!痟y&T~א2_FgCQ᳡mm*?1{E!BtKrah kmj-ъ|,HG֯+QH$B{~|]3||v g/CC_y]a sJObNcAi(0ڃ?&=%gw_mJ'Mi+ǂۃ/ *p @si`H'f+T[|1X|t>~^aHC zZq PuzR?[!^&Ѣ@u֨JUʒZedv}LY4 S,6.HS :u.84(O9Զ8ק`3A0z!vȱtt؊iRIցq^eۭ{JmSڎgIxms٣W鹴 dɖLfsb ?T# h]8Yqf>mM{)EH_mW{1x$OrQ_jeGPx*3C;=q+tgoH`)#ZU ڳ`H_[٦šՎ]xULKD_n<8_Rt<Ġ/NX?Zv WXVƠt_pd<Qp JhEOe;BȎe`8z/R΋n":-lnZFpIV;:?c{u*ɿcxv9~3D{A[D+^OyףzYvW(z} %Kiߌsyhy8Ę05zЧ,0ָھZkXadL3 :0Z֤stE`}t?;G[yZgz\ʂro6=LϋMϥsgz5=瘞'MϪ9*4%p9^C}eോ LJ&YyflSf%ed,˃]G~TtQ)И!CS}&%ʍ)K8ts|30\Hcf7`MS-okuX*rafiG4]%4ƑRJdd(;ɔ AZV+gcUFM80HI>jbg33lDSc+HZB?'՟hJ+i OeE{nxH$Q D:^_,Һ+6DEzH׉9"m E:AIda{?FRK: |*&ωP6|&MD"ಪ8w.Pik.gկ֑կU^>{.'*Mu9Ҩ<4_W} K^f(vlm2 _.#OU]^LbLx\X=SaL$ i/^܅5ω!WIFqհFngٟbB##\E3 ^Ik:-ޫwǙN_Ct XqRX)^1X)V32<{0,~f>NDV_7d Zue„X:{%jܛdx3tM]~u#E>sKSiU\9yE,% ]}Yw#pO#T1jUL:P=j>,u##?F ;ȕv?rq%FIRSӏOgyx 7/W.%])(9"A"%2鸞eafx?#J [/7c*|ql?K, ǺZ B(la\<{ilq@-_bP ]??J ՟mcb,'~ us?u76FLi4UaψWO4I7Pv}UіcJ7 '@/'ѕE=il8a6>!zE#+.MWe ^U۹j>zq^`8ҝ(7/v&'SήR~t@qE&24-b$$+E_,V oLB6-8;ŵSY76@/ߤ,B&U-1.͎݇OQXe6t)|7R\pz3]퀋kO@dKl"JRȖJEImȖC—Ȗٖly\d+l/]"Qi- v|!:ǐ 4wV_G{Y gf wu.\Z-–!%ŘvO72a}٭,M2e5=ĄLSᢅbLwxzȝBKN"a^G0WL߉m3׵`YSG/{D=)QyIΫrܓ=7)wnEވJ7|=<J0 ҨY*B.dũb'$!F<0z5"q)hp͂&#VK"]+8ЍVt@ h oчm,HةBjd&lYJ5ّZ3Α٣F˨S\Y8 ¹S ¹T8(Yz*m.\ Q. " } TPiZU., _d.\" R>FR?v1.KAtK# u6^, RᣝeőCͅdTxQ,p%G'Sep,, ǚ ˨%F#pnjY ϋWGgm0!rjD+%chI,0XRrx1vw">Ao.G藇C9l#!6;-CВ$vNeh$yہ?wЀ]T_8" bmv@|&>MvRr x (ۃAIA9jTV)q2evm`v+>TP[5R):(֧!q%ÀATLO&ю][]N􊰟6A\%0q5zk8{Ȏ~V GXX[fem EՓ zqˢMDŒ vq4)R:aVr(W"r\_+clӨ7;\ivѺPj,7xt{5?mng԰RQnFv:>@OV6R8>L&p1ISMpSDKMB~Ƞ}~gUQګTdPi5e'2g`cDWX42ʫʫ#Q>څ_.d8 :ۆpv/Il\2YF/G{$|'~_6Onb ~M ۆx8Wڃ_*nDu m]%>m~_6' P§}* mÿ4~mY{ $|dj? ?1ۅ'Xki`y~^5DvJ=sܶgD/=9>M ?Gi#%|Rlh;Eox) >jX\ _eV jpkۻ_RhrX6\pi_ݕ9Pkk)~O8{+eK`eiuDm6@ ANwaN 3!me6/ J!s7P}wND#u|8hbK9w U)fUCw|RIJy<vqG#N pC/>.-7gf{3ArjX]0Km֫T-Ŏe Fhm;P;\_9v%ֆLw9Rb`>BY ] 2?T 17D>z+UoSR7+[0̂ÛUH 3)'JᓰROɶͭtA7Opb(^DGWK}g,uM%::iYcE?Y'^`g4 ҆[[E<>jN[ďԔE`@ ԊZ9YkFʛC'ՌhoYww[ul BZ{ j ~퍝Bl(>n&sL%9[C36\tCf @ FWo)}#ٸqu|jؤ{̯w Us/Erlۅ!@ }־`aOr5~ףòDxv \y4^pp'TZ6iǩb1V)W6pJ׎6CSKǮwh%8nvk]k~ɧFWH#xum¿.^Qe[]Nt)_J ZwѫwvNk+ L*剱f͆G{үJG(Qi6;V'(+c(Vdgvb-tvoZ3W>:VE};qhE{Lx)~CU3_elSh[3zk@8o`WV3ag4Z:j8QD}bgڸK+AcH͡F]>IlQ ߘ0" EŽOL(9PlrCvv+xJ8 Pd.v麶)/0J}KR쓮Ѧy]BS Nj j]B$oBR ,|M~駸ctD7V9fؤ4apt¿'cE##7 [)g;ːNay5ay#@K%KȭF,MI;BT׻u+0#N典4/sf%|ǰӅ{ #u0֞x,E6,1U˶;_y3˔D(`xKgM~\/ _czldUFn<$7mrrsl1`"q&.38;񺼛.'cX(~1ZvݫM]7 _"?ƑFC8tHCgB3 v3M6:dOQ~VI~r_g$Zf߼aƁl/xm°a܍9&DpL c(B=_K䴘 1=ڎ;XthӲ _8Z8yQ)o;0ې/VOmr`-1pSISiO_QF$Fmf,vߏMHf'|-A$?|'~9mG5zf$]W]iDWZk g?>u =UDI_ji Mb(>ݟC |+[m)GzsZ;Eox?Og'"g^0{aib|-^GIF2jɓ7&'h8̢wxpOT|m3~/ *,C3Ϸ^ҧObk! Jw}[6 ٍcsF0QJ%EB+>A '2W"]̮fZDXqzЧဳjz_w՜8LW$nD-%GIS*l$} Xx.bŦQ&.SF1QYRP@u䇔gѰ ޳LXFkχNC8;">G٣</ȭF{^T֟EB}}b0G*l4=o_Һ=z|Χqj^7nOٔi>Udz>g6%q9d M\K+q s2Rg[,ܭs7 ۔^fq'~_C Y>lz_Ll|oRt 2(qV!},0f7A2pm(ڙM?v2+@h[Pg`.U(SpMiiRX6#Bo pΔnU_G՗fkVzTk;|UߕK=h Jp鉁-loîz7o4W[wzo5k~řXZ~[;7Y`{0ǭ 7cV*|S(1L>AI9iź[lSB$@ZB33~iڷsN 9rE>vO`'Zw`0O{ϑ>%HDŽX_ ]V[凙05rF/gQ̏<~um{,i6i-,Ukj4lP$-0T ߑ2}Z8^՚ ?O$}S|uwk?[`߆c1ހ{Ԯ{s B jʯ~///?/;oN/￞%YvߴQCiO(Bވ"" 9gfPXp\dĭ*mj ŭKH -|^K7y^q;>skctAzO45igGQمQf@9< @΅nun=ÑᆖVZ\?^:yrMFC*qPT mNN|;($c47$U'Aoļ3%jjiYoWUN܂gJF{IVK% 2dq9JD1p@N-ƥ~/[:N4S7h_4r-MD^߈hˋvGDa2BDQhP,9eϊ*-ʺc" M:OND;Ҁl:kuGx`{OuY Z8Yr;Oz.M.[^ukߨfFW6j}*iގ[ :ȮeZnAn̤]!>_phag`?4|i?>h<||npksM.rΆ: ō.KQp˹>^yu8^ |&33 O&D<0CRvrg4,!Myfs.2x|qz+<Nr}_5%x?7_1{e{W g.Kápׁƃ7†`LJdxonш~Y#R4fFa7,AuZ5?O-.8o`eUj n.jnwRZu:Fjqi5ne .gi MڐL[ Z\&7sT3BؑUmʵh..V?Z-]VU  g8\ op'}zhpE_:A-3|҅Ns7^X;?dEudA'ڦèʬʏ?/:W)3SPeRD,s  a6>cdrcػ'|NucOnM $j6ZM(9A%f4d,AH2A1 unQzF$8fg\:(EE8R_vBwނxR-!XX/J]>ր+Me^!BIwAMML>(1~)=[Ar tGdC΋x&ji(1}L /`d\4IwvL2I"gL3'[N]i'4ÛĦGSmi:@ fBД۠]25VŻ]=}  GqB9Q C|ȑC|PC|H!Q>$ȇx`w_Gu2Oe [:,_ڝ20k|h~ v2z;bWh쮭41*W)0?t%UcZ} 9[/^#:X%QXgB[#=l ɤظs+C}ej\1.4שgx,JpE-y3SZ(CYb~2ԟ֙seqcOb7otdJ+>ŦŮŒ+t<\W~`nî1n] eXm:t| M;EկA}Sݾ[ݸSF3U}0F:'t f|IV J UFlTFlTFlTFlTFM %cݨOӵ1eԤ9 s;)OT=rR܁Tm{plqwN:=CU_aa030@"Z2,\ÄlО ڳa= rk[;:OC_CT_'(hi4*i4*i4*i4*i4*iz7@oU>I[TJpjwggTfÒ?Tog 10YDw5}Tm:tT_r[\U;r[W]MʓN2ke[Z[#k΃F=Ra[djrښ m`GkQ~voB2_埐J}m^mm#k@7eεs`ZQ\+#ÈVjTgìKUQ| ?Z 2b8٩vYc|P%s4k?Y#yd~h Gp^x+m$T 'Sm !N: :J1Anϑ x^|xA7]YJ\`51ࢵi3-VaXU߂s?5+ =}ܢ+}uG16$g*1d[3!(i{[+ϳ+O~kXRvȃ\ oٍA|(P#'݌MXF=tf8q;Z(1kv<{&Y0yxX,3QJw1Z6g;0Ƕq?뻎Wj[bXUcwq=C"$N}.ԭeBOpK;S-) mMynC{+C޺}0ؒEq/dY U/($Uf}IGU Z 5t~^6!o= ݽfVuaJwی;iJ K|?.;J%=?ﺭ;z6˗ա8T^An"͏MנfyډFW9;ԝhpΊn-, [@*f .N,÷dN/Ɍ~&VN(t٢jsɪS"kMb1u6 ˕ \#oXnRg%؅JaD%aNm~ ^6]FlJ kaVFѝ Ȃb.s6zXm)p Qibŷһ[Û'd#J`+q;0sH,%x?3}ȷroSG/4Mh쨨t F^`hK-`eJrEdXMN~DF8@uq›< EMC\< -ĶhFyVlSh !Y%@e^2F!G%V=Qo^cGN cLUpUjnd0ñS8e3"fpEEeOU|ȏj*=AV8Rfޅg5 >WF?FŢ_\ڗ&VyUY!^i__(uYho,vb/>vĘ%1mTe;\rHXtYm~QB&B8u."(O>ah-aoRVUۄ NLȵV@,c!fW&i3z)4p*UnfqUӃ+)l\PA݉"J*Ŕf7uw%Hza0M^g IF  "fnDUX|efbf/Q|cKtbfQ%fFR)&ރβW!W}*d纮Ƭ e%`t4!ͧv,}A$F9\: Br:A"["&"`pQIօpX'ph8!Z)&'QVtzdh\Kہ%O0T0*ed{v,-n1ZK ߬j-1:/FV'K2ɡwm4(ͲqU'֦J&d]!//B gٗ%&>UI@܁<4yDZ>Zj݀| ?av+Tٯ. >un$002 ҈.ec({8gEh<Μc뎒ϡ`(J}bSZ&a˅4O e[XQ~i.1d3;/4"1_Hי&ZS4(ZLF9Q0v5=D1I+H?^pr~XQIQq~'Mo(ږo@b|$^&n7bXL퇂ӌSIˇ,٫ۈ w*+esƚQ+A(*z9MщՂ!Tr/Is>Ifo8g@>!L@CV&_hWjno%&2d\<Z0F8Kzb&R:3Pca(]'RBD2/l~SKqMar>)EJ2g}1c"=k)`3tcs[kن@cIZe@Y`軈E۔fV,FH &%o_S~JyrTsn.{#&~]K,~ρ; yRh:)%P:0 .2rDI)GC 8y _d慚ٷ-yHk-XZ ^+Ee&''J۰U!&Oq:g.;O3`qqE}i93b0XAW87(,PO,gqIl뗈[i7qDBX(s\L@"r4]m xyw]ʳk 7마fqwR2$ I`ofJ#=޴s yq/OHxdk[+C} H~_-c~}Tb?+pM7ELϻLϵ,=#WF#,ysx+g>7`B-t>Kt_}C7g. 0<=Ed:Fc?ؖ=zk3c&oI_[Vg_#S#;⃶k|[j'=ݾh6rxM o;?tflc~~ʯ_/a"/e50WSߣ+~5[_b$~;f^k_ߪTF;_{~~~᧯o W/Z.߻[0W~ʻGCM&ra7{<N7Qj~=H~+v[! 6z"Ck7U"NM:>:UX婮Lw}Z{Ʈҥm}$4߷KӳjFUKqiuk:.r貺ĂĹ瞸=0T-UY5Ӫj_=.w\s%*xߘ>zN1&?.$c(s.w}?ݏz4{8y!?׃ ];zf~ODZLTQMR`2{OG+EդC?mU>| n0dPVTm'9p{G]M»{U@Hhi1Ct?Ǫ&}VOl@e$k+TV%_I{a!3qTϨ. LB6!R&cG1p˴ap@cEgimGm վ3̿ x>5&gAEwUi)|1B_9%́q@xJj=7旅O?'QM#$c 7jP5x,><'oǫ˙17=k/5dix=7^E.,hИ>,5-jRć1j6utK˙)bC5\찆 'TʊOWΪCܛ7UI[ 7(j]VmF\RH@U*cEEH"EZ@1ZgQ\FQ]p-2),mg9M {ly9}!ҞRCeT*;T?O$LFiɷo)^g %B{ tY- !Gja/!986p݆r$܈8Q(KZ!mŨ8 G3.k;K8*EC]O?Yғ]I/1J[DUPߩ׹/79^~y^N^ׯ^c_>֩|cq勎u7[Mɒd%r]D -'S5w[!k;Vup+%<*^_~u~}Shf$J'0=uťgX|z*_c:='FOz,<.=-2/o'>omO'@HU|z 0 ųvVM|DE8$*A15ɓvz@Lu)T/9gSOypC>)%}& S6[yE>)[/'źسބYEedZ}6 < MjbNF38jI kL8>Z~1]}|l1GTRF=ޑmQDeTҧ/|;Z[-/ 7ۍkW-VaqA ŋwKWHz;yAcG@\2 Dq^G?c㯱[{4C}q? 3db'w?_f)~a F0Ly o&fޠlퟣ=}CN/{ȁLG<1dѳ!~=.^eKE0ދY~k+{N>ots'tC珃ձL:7t;ͣ-M .sp9Ho%ttז\䄍&`ła\LËxf6mS@Ѿ3/鸕IVfUe@h[m5J5j[XlLF8&z0&\DŽ\^]0zu\(n&=W$xU17S~bO&]YzUWk>SMFQ*eǀXB2C{D!tƎ~:f>JhKZ]wL2<SWS!?~W?sxƀU7ɸܓ%!6ZBjk?v en:]ӟ1h>^PupJks"~ jOcYU\rw25};pIO"cy?oń]Y؉O߅qt~\sr!6=YkX\\WVjS[n'cgW&ȡ+Ӆ\q/G )%/@yzV=MY*+ 8 Jͣ?p%SnilyJʈ3Q(HSE+- .tLn e>u =%:.,]+ *T.o6ptnjKϖNQ )T7_f(=f=t F鱘ӓlL(=g0dTN#,t!o#zғ =!3ʃb*B()ð K (.=:ԐBzkzT+bSQjETT+⶧VO Tx>Y6Fw*#.pqdB~S}::G?vzsL̳)&*yE3UHc6>X[)V#/@vG'XCK=P?8y6^WH,5ASuAέ].)ŤM}(a5I&/(|^HجYxK{ >*>#s}[=wcA~:fI/Q#`8,x,*0w4%DC\5ghe8)?p٥xL.K]G+,j\˷!ZblAMs6A;Q vQ$ *\ĮSyGDŽkF$ 6U _2G}~7P8Gg2yʃ-z~QˑԿsC|t}8x^r]eq/h\0yda=~A-?OLaby_k>.w(&~1a; x<9VDzkLeyz?/"oG::;lOW_?]wϋۮ*=/kv;OgM65 (R'Eq<@52W/}XJ%54SG(ωꓳ  2‡> )A"j|h#gG*j|=4(~bk{5Q?҉XsCDihC.ԳuD?~^X膸'o#7"KL>H,t^2G^i-l/ !z' WgKVJ S2x?q=ⳑo3)u&yB;Ki)PS033߈27k޽ gR 4uF48 }7ߵ1P e;ڤt=mJ++?,%)jkƁeέhk=geAݷmAnm'{Hz/mWlzf';HUæ͎R #+})Ӧy.mj1Nf3WWb<W;MO7'4Oߙi?ť_Zib>8َ~y@Ÿ/y@BK'LJTy/ĥ_~]y,K7H>=eS2]4IAޡJu16*%9{pzE6@w GEm>`5A?A<썲4Ao,:`VgRuscG $)F[ ?GUhvAޫq F6|޽#j޽ĻUV~(U\NM8 FG|(?s)_@?4U8 J FN;, 2nJvNs39&j#_.5jl04ZbI՛O :̟{y#Xq:?Bp D RbrW:trtj}}}mt[-75wN;g3.SWN zƷh߅s\ljS /XwM `l40R~p/{/u/IO6g 7ۍecdNse6}d-$b4 fk:EmԄCMHS<)USH(P# ݅d+^k أNce> G_IKhYuޝ)V&AiEFH꼨 amhHa4Nd\0cYxg!x(T8DoJsqb.cNTz)Ǒ,{wafX"|ǧeOA'uu騀cml[ڮn|Q6;9OOH4eU#'z{j(?Aߦ}|/aŲ(#bD :_XbF$t$<W/E|G' AQ佺R.0aϨ%-M`4L<\B=)ii!Ȋ?&xhbPwQŋ hH6` <@4Mnȕ UΤB9/ ̺qH_BqxpK!_p*(]ZMSE@/%_E]Hh9x- %Q,&.D/ WB"0=#WȚA^΍VY!\qV ̓?UGFߩ$_@\$ջrЅAl144eO'gu-G]`yr1.pݮQꎴܝRHn1lr5n"/j=N6ߌ:-:Az?uRZ`#ЍCTS;K;g3O3Z~#Ѻ⪌^#^Nqdrth|w X=,vPAtm? nE%}UThL]I9 24}H߲»&jҌV%D[FV+h߷!hh힌^_c`jAT,mK" x1AO_{&yC) qΛb60݂SVuT>{mjnS^J~<[X$v8?T@x ~^90y৪4Cxh Kbd4Ոe*C5&"Tש$>h$Sd @%yBA*Xq:;0+6B+Ϧ=ʅ~~źg_b uC2ݚ"}v.l䤠.D|6܂ $'a$)z(pjPN:5(AӠvt/Ąʇy<ʗ**1yJtO%ЌaާX 2JQ{!;j9@O;{!8U1 y/yl;QmH/jZDC޷2ԧpAUx_!j}¢}S tbODD";a%]t״H;*8't0]YLt7vAw'vGt#TnjG7[ ݷQ%[ds,ݴAR֬ۉnaNId9[gc# 1Нn";%T @nGw&0ҝtwAw'vLd$d.hGw$;jN]t{TbDwQtwbDN";ȁ޼{Nsw Ip$H'w͵)VhO_XG%fpOcC3qSBߏ` cLsc=rKg^%#(*D [[VPUo<ܸvo+p;Tq\κLYceiX{[~'مo(wvubDlʟ&6pl9ʱs1LsqkW̯GY+U<"DU{V-3 c1Iոt,}]g-#@'goFsQO,)Dg;,3k 0"+.!eGkFK%Xrcw+q'vŏ ̏Ho7mNJefŲ3\}7KzŻ^ނv zo z۱`Yl`}(%z-ޮaF\Ip#7?eV;`gCO8/`5ٷcg{F@V+5S;ȗSӆD&6%H%QZ͢ DM59aD3X/JS7 [kE]w Y+VkhH$U ^Qu2YwwcH,cHŠ7lSkq`B[D-x1lBj5F y5h ?4Gȁ8iizfj+ǥl՜쭲rl,HeM=>]foOgR?Eh?\@w=ʗo63ώOϨ&QdOhELm*yMaF+6"wr`JVh\LT9h%? {p"kߧ%m^*[=LjhX?7s%MdG|ZxSKPD./b@F5"CFhF"Fh.ft3j2EFQ9[aȴ&iez;dȴ3"2],2]lͨPd2*Ìn- EEZpHHHy8*]B^s3AMQ_&|%2mEgWQY 156v03 XT*-x +{//LdnULC#ũv'a"o\ \˜wr+RZ}G88COrȯ1_<ӣ6=tw&3{nSaSu)Eڒ@ A`xĄѬƨ;;v@yI7-#}pݶCwUg&/ rUħʧ-SD*wxuX&:ڍ6X!)"\bo>9EX9235z޸M 7EzRJ|0haY: 2ac=TkB&M( ioQ6G*oBsrߋ;n swFh/1i6zR7V a64 d4:g8Y9}xgHj8MUԉ;tq;̀E.~iЩr_ڸb/ʎɐl1&k8 ?j\[+Ϳ1ko1S8!+4Ch`M^-n*N^4v/c"M^ZrnH鐛 _kD|d-KkD|5tCr4-ƍ$  eߑw༱ Haq$Gf#1^5j7-B\w>tDBRhaJ2Ah WzRC"^v_Ƿ9T=E`{񂍡_Eԟ{dёHx(F%ݪLOIfw˓٤tIJa+>/;s(9|}^?Mjb"FBP"TaՆuC=Fˆ0nK)t~qwM;Ճb&$p毝 Y3uȈ1FN9ě\%PvN C<}8Pʧ.L:t źؠ#lD&DxܒU3W_W N,UjCV_ğOT>^YoDQ^OtTkL8o[*vX6ydL oCw$Z#N)3 Âfq%$t2 X]x~BOg K^8mc{ZLe$v )"|;{-6S'Ty :_Å͈֭NJYxM\P!}.)ؼճQ&qߓ.Ǒ@m4d 6rJC9Lz׷܇nPkg n`Ɋ_ÀD]Uaг"<׃pzL0P/u)pH]?k)פޜ.?B=,U<#&^]za]jR/Dg Ҷ?2%\C_x}JJ&boW.1)rE"ծ~Go5oW-bThm7c}.e:@X#vjPGÄK9Lέ~FlW'-è`A#0Ea`J`e 2P}SSvu Bl3IlblUV?((Ys8͢ /jgLЗ`4 D93A7 DOc,yGqv’PCx3ɼ]a}"Ai"m>iHОv)MII HQ]}$=Vd{VKf+ k6o`YDB(!"cLn v^,!rE3cIy# 98mܿpX~ 0&펣ËXo=aHzL* :EqbnŬ8Nw7'#Y,uʿgK#iY~1w𬋹3.gT5s ײmޟ5}h,'8ɻN¢&b4ᗝ3Wۀ%:nҟO+L3.h ejqA}(NEf gעꋐyH6P3 ;7Kc,k>S_xKg qZwJ3.y*z ڴE*mؘN]򁷆L4YIDliCiqa:3vbO>7#@}vY~*1꾄K^݃hs sǡ4g&c?lGǻ~ʚCh2A7RfZ3u>h0⹭k#X#tl+#w_.q41_=N̯j1:N̷'CXin]E܇E܇YhnP*p:y9k;y. POzyj1Mן ښbr}@zж'͇ |sxv2mڪns`F9hq ^< jlATZWL i/ks3~ iދU؇f÷M65Y/eɲo]8ICe誂MMk5G4s%B"dv<zb)r=Oт1 ujf&ZSX/1?y>A$*F0ڸJ{ޕZv;vkGz=Jz〘lf %{, 3˽._J'mxsRMU8iTAu#7A|~駬I>7ޢJGj/p S?}jRA?Oxs9Ͽ9 ڌ6y7„jQS_Gު"yA =~"X؄>z"6w7s q,=0J`{`WYbb|dEN䯊tpMc$O׌molYUD~۵:Y/l\ WT ]N %x{ SVZ K+&'ȁiBp8Ēip^-&I fiA~_RyF.NDO (P!KZP챹շ=H_|n_7{s}:"8NUxoU[2,gKnƆ9J`(k7LU&Ƒ> >3_"9}mqƹY=k-2duqiO'(h[Q=kFֆ9Iluf4Bg!49O=7pftO',iENJ,Jy}Y#'+tye.ůK@=8(+?k-shEu,{?DO%|cGɮv>ړ{T96(PDt0_d2-sTQHGQŇBlQH T$/J1&Rj`8MIP?onsD7I's@l|hC Cp#mR]YZTMn[ou X+UTC7-ɡUcҜ\K?lcic)z,"U!>.imLfbߜC0rMj1]+M]1s _F  u*=B{rYz$cSh ݴJ=ƍJ+^=<ѰeALvnLEXz^T-pBhƀz ozzpLj'ɥ1cQ5KB#[E)Ufkk8sMޖɧЛ'f\F@?ʁ\g\6xr$HauI(lAQ1P^dQ1)5'\ьىʈW~'1x @R/3H\15{wH)5*Iw_ YoeU:5ԷkzpLRj墐ynVܓP\Zfy-̞qswEBPɪI3LZ'=q@xTXx^p` ݈W E"1T\{},:gDa\OlEM#E I%dN6H<Hq@,*Us-Y9R[gbMY4/H9YfgЬ=',Dw,:sU'PnUYVQHY24$n<34H,|{ϟjY`5~>D~As6lca@TSÒ!Tjh^"?mYU\Kg Gq]#KarIy&gzϧ+%=e&5GI` tǰ"t]}@J#ͧo] ]a3ZFC7|RvasoVVpKc(Hm@[L"]/ntrt4S0zǯU08o#)zx L7s亱N٬+AmDb`qU6,eS9TZ?%x9WQ3}efyS7U:[Zd?#m̨yNz _Ō-QPR{UtӨ6.'ir9؊ℜwXlk<(czBG~%6ӹ3^#< ֟Z%Y땆,mX6ݒ"W-$bٵ Y548|*3+%N%gǣ} [U0 =s{%o dgF<@((85v+lpZrzQ}iQx1=sI $zu)-x@(|~@@Ҡrgln}S=j]-v,ֱ}2׎aGyҊZ9-'ұ>7ǴZ>3ǴditJ0*=M:_[_k}]?w_"C/LTwc}Z߸?9W>x^ ᔖ6ۜuSݯmDJTV#g1~֥=")zWȜ]Hp|ϩ9 m9s<[ų-lYUFB ?-1HmqFfΕZuvf3-RWˇ~GNr}KkQ/\I69j\( J1|Hk+KNG,]H2a.H,OEs4YɃl.= ~D2j{ -f1҅%kߛW&J{k{448 QAiJ0Mlc7r,TX^Xlo!2?$֏UA^I{^F9zL Nn>#u4L }PB$@r{]8WeO+كj:$:|o! ,&2ʗ2 9M: HjǫJSjFm@"Tʋթ%LX+Kv۠Uocf4lz̕5 1e{ZCD `=&WWu2pqDe=G{ˠl_GBަIi7(7-gh8`_+a#B>i7ފHZVv=9Ra? ?WyCx0O€[]C_ڍsz 0=7uFo,+Mh.8ƿg֩x7E\vW֚o#žBaZ֓2qoVF|mR0.֩,<_dWXpKx?((g> BfK&LI爧 dMbA hv-[(5]%}=W'#<]\0:#.ƻ Lm[tX^a(7~|t>"-|$g( WYED'~CHYg'jOkcwoi[& vuZw=S3^EՁHH+]:Īt,с%(\ hB%mȪǩ›bo/zMyᏅ#?vg㺰f\vR^gq4.DJI1Ay!IBwU">X% &I`B|9u SYz<ץ=.텱:x,.}Y}Ч~U FbA~շVVLw! ΊێqAU $LNYDA. AA֣\2Q^,d(d[kB sBއ/ BBf-VH(. $BE!?c!B-D(BCzH$QRh0n p?NC1nWoRÕNzSN%f ~hߍ%zT$3H˻)L6"M<nӞT cԷω>ciEHhDE҂NsD!. --:,:LbڤLBU[(7閠qz%I4r̲#Q\P%EH #bEu߸LˠXBqՋi~m%WEIv%Ft ]٧n C.{Αn#Mh#kFɶq Zt +"qNns˸6F oqMD~5rȃ"7+_ƚNxP[Z5'+cr:(g\BC$Y(l;Ѷ5.OsufDwGcfΈ rJl.)]/C ӿ]4h!݂?ϡ9dr8~Aš=bf_, \O@v(@kF2}-n&7m4z#Ebn:]FT;Jۄx/6 WRF0uK>k"8rfuDMg? L]'8=gx.th,_RG^ w8s?j1^ym Փ9A'Ǭzc3"er~7jU/65Ə_ˌo%ࣶRV5ڰz>Oo ?໺bkDGp1cxG$c&mZ:xh}|~-("L0 2\ngհjO0YMܴlnņ#9s}Fqk>ԑh]r7y_ ⣈1)1񇷋G}1l ݓq:qqק~3]Lt֩~a#I _hRa1.ʜނ*90񥎏9]+hޅEʵ+ύ_O1yBܨ{q- y`2'!QG ) R%)-?iqB*oi&IlhYe5T$ zrI=wvFyĘ3D]y[p@fe4ܼBv*-\Q%4dhuJخ*GE$2y@^R!؄#M_A|՟uq?KW/qŽYeHOR)A8e1}y<۬<J "eGzeC[A-ǹ|"-wDqE/ޒQraّ^ˎqrQsٳٳ57 n\)-BzTB]SV>3:ޞMP&m+Jp- ]f;8_=& ؙP*j(yEY6rZkų1o޼TsX!Rݹ]$8ҾVzl[CC?)'~!凣oraF~Sip s~o;64:*/ S`\IMƒ*`kNOF{|e3-$5: 葖ϥiAZa֙<6C-ZZzdL%Qe@+"-wuV0aa3\6Pv2^w7ޝ-rb}+0䠧g4t"ˊŔsX7f\`ܕJRh)k}5|4wӍtHA:ݬJFhu|27c_nFs>azAKͼ2Thp/u)Mǩ*2QTd"W*׳Ew{Z u)F;t0[#r87E9Uf6gЬY10J:G M~I;(C7ɸB@۝*1ƷAh+Y|+J~=wM<'x?U=w*@> AFPqil]' hz x+*jiurD9070H ሬ0?;[oAϔ,zwc<2 )2jhoӫ D 0҂oFDM0B-Qlq{PJ;:N{P>^=+6/Hr:5N{ 5%,iG* aH |/oH E>YzL-8#d.Vchee.F mZNvM*4"{-|x u';56bX.^AMPN/ "*H:*ܑsOULguq,/1 vG* ph yO6=N2?%DflQObGo |2 F|x^K% ,t5W`rzwqTqC߰1,dܷ#sH?6mQ#:Wm2zIP Wp/o|0pyT]/d}Xm1p;tC8 &GNT?XٳĀGPs#sv]Qn豹o ᾸkZK[[H.% mc@A89P A2!5J:/s٩fGf-xPe$ 2y7E+V1zKtz@ϤNAGHDOFmFs7®n]fh)* -h;)W hIcR0t9mǰ w͌=FۣJ3m3x,1_ZC Ā;륇i?&-u<4i#(r0pO!W4Ym`{w'l)Yiۻոq*C;K "m0@^2{Vw.ҦC>2ÁaPjt_HkM.{6v:ELX+e+TWoRXGc5sU}1Շr2A`sfL4AH͊GPhrFKG+s`L)X+:ٷd]n}zdO]ڭJ~92FB9>c5zɇ!B~bfR&O"y[Ӌ&yk56{b-C_j/[y /__m"OJ.B@0'/[%HWj[Pu9⡎\u LoW[]o3ŶmKx{_Xosxۆ6 OWyTD_4?[͌|M+^HmEw'<ˍw,m[KV eAj;~N extC`貯^-ܠ`d i8s53^/(Sml!C "J+I]xjGּYN9I Eߘ,S"l:M oYO7Ľ[3OBhbZ_nV.Mf&)|˔X6^.򛑋[I~i9_ z `ģ9pKfprFnO 4.<k|ʋR+ oc*Zw“wf$%ʨoVN:@GP(2K1i 62'%rjerO 5ʈ@ OZiGrWs1@TZ&v蜋 /H`[\qM0Pظx,耫hh^}'[Rvdf]&?Ynoi%| DNGEPZٖ uC*Q.[I9>~5zw/污l)f@gR Y8 ;A;q碧i.kc|\R8k9qO?TťSͫJW_UѥKwͲCј(E*’ʍ~U[qIO-◢hT4f+<ր4.!y= ͱ(Alsrj3+dŧ&C,sO 'Qpz10n<?F~잴Q#e>N[X ^ 1?U@&+g~P^h>2ˢ?G C10JcHhZ|0 no}*W/ލmX:_g]U00^. 'Pk,{,ng)ď[l@dʙ(oFNh? ^LĹ3XBz8L[tZQn$]3VOVΠzu 1PL ԇfj`%'١x,N=63Ae4WGq%H ]Ioz#ehiVnlC3Љwp+1 FѬ2]SqV֢֣ Ck%A[R5r`RjX8 ҷRu 4ڇ7*QFV Ox<ócrgNsrZ=9Ϋdv{^Wo%֋p.‡^)*@)&?ѻ Zhr Kdg3z](lrTGoԆ LGSwy9<]v~` K ǿ06n*^^RE0~a"KH.1y998<ǛE)ij*=dذ,PTxϦxXA qdxr4?}h ,%vι]`Ab4G;84xL KBhG+S!S5?9'Z#G}@%:=)SbF}&r\nm| (cpjkyA{ cT=^ٶw{^RQ0dG@{tBFs?cdE^OWq:85DnYBO6Qa Oʶ)||Zox2wϳn?JÅ"'}|V_]ێвO+nhONӞ%)rM!B>ep'ق T#UJw)TF<s M [Q$y6cSdlI+vNvPen2DZc"ȕǰIg 7@=ن3 ~:CkVGa&օ┷s?˔: ҼZp/¤{W0[1P@ɤr;ߎgJOm$;c~3`kLрWʁ@r z\iaa\؏APdSo0uEg(A]ρ'.b+K{Fuq=/`\Oر٠܍a8`rQ$/] $~.(loK;HF_ =9NVG`951G?rRD?;V!Pq -,sk;# ?>*OI$U9u2u1.^=@BUs׿aҪ D; a^unbkڙE}7L[K}WeLB 81Q.iefL6w^,/)@ZBkq`YiZcz (jG||F[ckN8n;rњ 0Yh)Rku+o=Wz3f v y/%&%s7]֤# rQ+pZ}D!Eqs@'^7mw+_rеO΀-d쵟~f%dM002ЈÄ7 ^2"I=ڵ\_)\m8HJbD9rx@\4aM9xV_;q i'k r@3?6.71Mt%_(U>9'KG+\f//?2Ȋ/R7q a`”g͘SDbq?0>\dodmvAϊU#LX)D޴4&_xo|.]ͪ(MҬEIw rO9B\BZ79݆6A4vϮ =OioHJ%YHOe$tEP2RzQ-SkNc(攚^pάOJ2(7ř#\+i`1f7jfB/m q[R!qC?%Y >GFam]tRum̔R~v.=czI+& krBfh4l)hGA T&C8/3"ʨ7&lnZg ̩EHz)\a/P<oMJ0d } $WipzE̡:QTғ^3r+Ֆ$saX?jFL vF`]:Oo )네LWVBKa1x02Nyφk#Q_x?xL^8 Q|3F)oKa*?@VU.w  ai[ʟ-5ʦꉃprÒBC=92osR\^/ #rz# {7Nb2 O~vPT1pMrchhA;\Aϣqr_wΌr1Vw>xY &0tډ4NKyOކilTVS&<Cζڕ$Q$#B;&$)\_ /[Eѯ!eeb<)ݢHE'I a4/iA6X3JƋ'/.%G ]B+3&"YLI}ABz+\F +F ȀC֎>QN Ww#_dY+R B'Ki>K <<@ kd;dGW NT!gĻjdF2 =6"T;tx8 Y#'xRM՘tt 8m-4x˖sZj$Y2dh3գ/ĞHH.y7^] k!N镭VxsBN{,\"v.kS8$R!P.3Ɂ~ЉpϗɺX:Jd9m JPUѵN.7|"%a`X 'ۓj :.T :^j 'Fצϗ(RJ1n`Y0΄.MiHH'jhz  Uf҂MA' k`;yȦQ#e[*X+H:N}pqhaS*-ޤ.|veziE(q؛6 a;RU BLWq BTb_ixB/D)K%֢C))3FpTLߞ`?8hxpdkS2J9tR?5KEww1jzxR"tpƻ gVY9ƉQT3^J UO#f9TS ' Y]IKLPi{RKM'5M?6@{-KnX2X*Gã%8{+,ms% ܊x~*OlG@_'ԼF8x1D^`\l>1J;QIE_*;ΝeovN;Ŷ~=I*yڎx%(rƃGhBS)r0~yO] RGbcLYg٩zqwrqBqSfyI91\ci#p-Zr۠ݔ,*j%a:I-vx2{Y /8ݐRvO,J/8M[4tt{R{((4\NUTہ^1nO`3b9$rqn|ʊs 2OSfЇ\mZ]'t~L:!uzu|*ލ dg:~>Vz([X/^#q0_oHWf#ދMؤ6Q3my҈PP iBY$7Mh7xvdPo,]Cx.ss9cR*(jV})Lӑhۭ߿yN"݂NXR"QD3eqi3F#.]H=)qrM4'592D~ >5boau?Qtn 2dP>7d^[_z"N2~Suz΂]g4TtvGazNyt&d9Qjn6įO-Ѷ//Q,\Hlg\<Sr5QKDiOׇS*B/3I{4JU1ZG5U2v^D^NNڪh7TL.GS^QrA3R˞ CM:s_t{@"wWqʮFXV"x+jrnj(w9Sqqe%n̩2CW'09DMxH0ۏ(߿˸Dy߾83λ wH? &nλ Ǡ'CYc8sM|h 霘b.}6;bY΂zA Za*JD(@"o:h%vاxq;+'FnZ-+>d+^K%+ɧ50 h'L&܊'HȨ_Z:yehȯCvJ% lR*dֿ$2]r;rrѕp1D<2=M SZuz4tYu)шBOHE3t0s:@y3ul,UgGOf <xqXd c߻g{錹OMj u(]eGY7WAOʳcrZʋ̜R€S `fe;\!R vgK XJFLh9~z"dt@2]]W5ǂPoG.#t9-#{jjp\D,iZ=W}D7licb3ϸu>o.S]#ʗqϕhf?^z9)x޿X`8MٟC܈c/jTy.D?AqYxPjvGr) G7C]^ۋso+}>+l!^1^ WҸ*7Ϛo|/8mpEyRy++4'7.b h:ހ:v8& we)oy>܀} R)'[L|rb>e}gmm=M ʁOWC3otQʛ[F*AӍƜ1ʝFҰZ9m%ҢtII} 7c15s%YŏT-hsccpfUG8DҰ1X'ŔrΆP5C)p,uX('eL-  2pz]@xD<|fDQp`4ƻoe+tfh46Ңj;UX!ZwxYOiя =,Ȓsʨ qHFCS"\hQNm?uź.@P*>l'; {2idg;#mNYhgN5Fy* V̡:IR̫o#؞9E%vhҵ9b/y6Xb7"2|QBٍ< e_ _!\:{kY'/:yN:'2ZQ |q.ϸhtwZNDR1O})e+,_v=Xlke>/a|Ұ-\*v 5cYmem(]|Qޢd&`VCC*քiBPWxT`rzYgZ/KѬ"LQ%. ޡpN1+w /r4,RYaҪIEz:2зRM,2-9+h̥ɡѸqY7s1RAsb`@`QGRn~kFi43Ҥ[Mz%_hF&+̎Y 5vX`+ ] }HQY {! =)igWB$08G Gz dt,&ʦ?ʘ!_BPee*U3myqyj̼mpPyd{QkbGEGaѡQ3Cy+g󐴘C+OGE{DxsXV dp8?ZVA8> wӏoiC?[Hx5mbRHm367ecS*sVS?d+,A{8Ip%"_CX(;m =l *5l6T`_-CԷ\{R xC)ȋ@\0ų3CF戌>iPaUWq PeŎAFMuyZyf(f tz@!TuTkX HCP+ԯ"%.׈ IngC@=93ͩ&\*UcspjJv6 jWPmRkv r> " |Qh^]_sud(ܠ= =kn{ } Xwjj$QЗFWÜs#)izl*kG6Va/2n cLlU4~[ ȃF_Პ5TV9 ΢k%ApZZ`5 ހ '+A1C/3AߍЭRS tq)_cAc)NL CSvKopgPq - -ыn.I*f I3|>ݧ~|7}Phcw NZ%1Y&}x&2?ux>?tv>_ϫObS*x$˶> }I|y?)/Z'?1:3Ґ˃ubKǦ㳽];S VU2`=% [O(NJ8)E?7cv)[{ LD`V׿Fa`kD^혫f=^Z[z쵾vj\0Bׇkk=],vlu\OS|d7 fa2u+:ܭHz\k,1cY, WME3" uCZx5PP3$?W}]nBt}>\ ͏G)F#i;SqX/d!2%`>hXr,Ɍ%bIN|T忥@h>2PGhOrke8d@}FŅ ZT8\tr;tBQՌWħ}G@WO9MtkZH/δE:Չ#7Ϥ]A<0ھ~`"MMI%h^{*Ax]qe xv -NP~yO'8XW]{J6ay az`>ZʫR2F0 wpя*>b&)˻\sf|bdVDq/ӳe'Dx /C-8]d]Q oV%n(a|>RFOO!EбPQM>a慞+t<:~ Lh#Mjv񰒔Υv/--a1L{{h-Z T'[Zb2WPN4vyљJC齆jV\bw˟ſMo'@SC {رX69eO4#'{h_1JTA<aֻ0Yf$VU1VyRAKXߋc?̫gAAtUX|x`UqE]L7OY+܃gkn rDcFqct(Sht^J .v51: VߪW2lsWU_)<-~ڙ;B$BvG~6XXb|φ5Z'PWzn;aF'ru$k'XMq-7{$F[DNŲDY>kڐWri3k[l6=g)Pԙe1 % j#ĭfnߩ;p4mpJWjl{k;ҠüXlGi@,@blpt$aW~ B@6f@oGn7WvC R$ͲfKʰsfF TP@NkE&Yl)DXt6Y\vFXkxEH!^@!\7~38WyItS%YS}˫k'?SZs.,*١9h"wyfފҡ0$'ީ[h?8w SJS}9T}fW7-S;ZOU` rjTz'!_~GI!pur}> |3c/9#qȆ,tצq_38 }#Å~ 3󓡯"x9(b`̜wdLCo.1Q( ^'Y&Ru^1x)4ӁV' *otV3|8G.&H|Li詜JJ<t u Ϝc@N܁88HgFૡEC>\} xǰoӒc94Q\C`|%wɕӪ v獤7S}IIf?F٫>+տ?5lI-ltP&F/oAO!Qinwɔ߾ ov`vziu+c9l~7G}oI1$RDRa|4FOt1J6Qn1JMf2RIBXznGwjXS"?6؇60U=sS=9]HZV@*qoJxB  ش,zW<"Ѩz–<,ZV.^6>lA!b[ Ƈ?px˦Ňpå>\C 8c<*gT+'& (xBqte*FmUO@m=w"e =Ӑ' U ? yf}'MRf-aZsP,]қwp/\AO`Ϣ"~{#lؙR)Y͕MwlQ2,hZZؤ~L_QoFcϯjܘi6>#ԓsq&tFgw0`YN$g)C\I jwi(?a $YIBz\U'd7J|W{BrBtsw͢lve +$c-GsjYx]ئ`Դu 9ʀX>ZieBmKpW97<ٴ2d6 e1? =38{Ycs}2O:p X\nKZլTˉ2N^rju~F ĘSmMC7˰^b@)(n﷦;YzLX}.^xR9%vr LtD.bg?at6@}j/iv1 #rs9nbC 8Xv7RIebrRxZ0?#s'GRS-=O{ը@=1Ap pdjC,5^}O7B'qh~WXKXB! GEw >jr!y֡72m䛃{y #WƀU9,ɊD!hp"q yϑnRC*0>#eE}vc*16` tg]缇=paT"mGfКejX`Xm:r;b L*fp*xxwZ;7J b^HE`? ]I5u"ckUU](@lW&<ʊOIf&Dw$XIa(̀9ZA>b6ʢf m}U Wdr/AK0JzG1PCsm9h%z ?`fƟv\GOŠ5}.%bfW6M5-[aaLKsE-}Շ"*+eSYGk{t"Cn@.BU*Q=Pc0n̗íтYZo,Ȉ҂Dk>O*bӈ.s`[#9?e|?^s^v44Eg9L+l)w2I+4,CqX@~y J2K~J04z3fɧhih~֨x4ƥUc{K`*qVbq-wsjٚWMC9 3rz ]27QGvfŇ>} /j =G*wۆVgK9JY`KA x%AOV(pb (Dl4(/X_ cU5>ʰTMG*)ye,2ٗ,Nzo w n<$(DqHӉw٢2`` hc؁E^k~8o:C *0]xAzm$ )˳$_<lPDfU]y;uC{6sxιPN+?۸iMǠ?o{x`1t_D/ߩk[ Y?k츔&^ 51Na#rL9dG8AT fnMΠٳ'\OQ.`ުL`u*nn+,ѽ-\g-9.A͂;ilg+.Zm+5n5X5+[צ C&Pڂqɬ -߂5E62Bh%GZ"-`>׸cGo9HWXm%-ޮw,ʴk/يg' 8gR;V\ÛQؙ[д*vTW^ b7+*Qy2LsdA;xxO_м|L$ރw{3@hd̉v\ur4r妄{?4Ƽx{]xn8C7^[iYޞi^{Z̞+RH% ^ءuJc,ɦm_ҿMo*rEtf4̣YrJ4ٲyBG {N?^eux͕L;-G] zL4p4Z{rGT^gqҪ=4KT( l/Ɛ|”nL!if2!ˏ6UfdIh<0q1YVT9Z?a Р4U}4e$~ދR FDƋVv-[7|]`CJe~_aa_ٿ6'kdrM \p:+;WWǮ~6g&|e6bj胸vHw~gMJLQ)Wӄ?L)$7.Ҹq١uz8g_nja 故 eO@evkqX|)WoKOvԞJj>,@4?ħ(Cَ/ÁJa_EAhJ4>#3 峂i'H^a7)g=Fe+w:aRCem%\\UtU7{IHm*lu%XH9~j<^q)vMmٶiaӜ:W-4t1/8LB9xNI/INEVdwbSpڵ}* õJ*g+jO|HM9f;i^;Y~{.d2Nu05+AyIޝe?!3 XicF{8s zgJF}͒V,U+C錡\RcLS6Ո|VN$re t)E;[iU7"0k>2_f IQiҼ{ k(ٙHLbΊmК#ƴPAAwVi^/5;RNe8)7|$+͐TPdo5 _C\43.gDi!x YetPJV٥U7SKXGN%B|*/9%:evnܐs`'0kVUdЗuo/UJ%CN󂾬Hۻg"}){:}y(:~?L,a&.V[a҃ѝO%+1Y)2@g$'&n(e[a~RntZQ]{/7.+4fٔ1`||+ bԈ0ҮOkP|}/Oh73]+j-@/@ot3S#u 떊1GLA8Ju0lU4 $dQ3RZ'KC {gcJ^h c+9HF.DVmh@Өiibyyk+:j'qSY66QXNa:s9@$0iRedW2:t6XU` VESSrɈAoPbv% fW_׎ڤ>WoɼsTa]?!Y ܈7Dؿv2qfJr\k =B8G>իDW~2/"{2s&Ӣ|"ZqeꏑzBƩ?ukĦSKt' udܸ[=ƃCݲVŽXY Lϊx qwX备as+-j빕Ҽ$Z/*7t go '&Z9-+^*y80߻R®rO A>p=zjW*ـ1W7(R7].#/Oe=zУvvykjp첟L;7`a+[lb[b.[ bWq;knc&,_+aåuaOOEu\*R[AKN&4Z-+c`3\jޓ{bљpqn~T} `Z}O-_ C!x&oM1-*6HҼQx4KJrɪ=l>w`X3 +~ָǗ!]sytsK`~*nSv8AsDr:Y~d`gyulWMF@f*ڲiW.K`4Z/Ah/>{7_7YL>}$E3#Ld;)eHr3.8Zܼdu*MkuzV-RRaʎ౨\a.P;kxא:s,ag`PCgДkD7orȊ !-rH-{S,+1.W 82Qw ["wK ' ݓEx<5FR$-t1Ѷ_"[ȑ=l @NH ڲu#1ȿ kⅵNww0Ym%㇡wCR%h/'͛ګ;ߢfYk9ѡVo"oZHj]$Ue9 JBL^7Y}op{w<LAoM6]ԩ %hogyє<o0̡w33Ӧoщ1k[8W*;̘=;Dϋ)fDHzqwL-5O{.ȥkt8(_oϒijBN7X IvaAM~nSێٍ7$Ώv; n+x_o[iޏvQaw@1KpطϪ Ѧ{DԿAB*;UhT<@l1.t@n}a=D{Ҷ|QD}=[7޾tFoZ9t=9Jt}e-zVz]Q.z&4]nKag+F!XckA/B/G%V,tmfZt&55;[BwZX"+mF .2oxŻM :APk$E"}"W:LV ^;tHP"xNěHu(1_x)[=Ř[6u-U=xϵB-x1ן r|~=Ә[ZώwBKo%E|0Y%QV^:ډԉ'a_ SVGؓjakwiԄ?:XqSB~gHVLR'Mrfkb0;F/ZL=JPZ2-(L{?>l?OCߨbؙoj+.2u(}+Zs'0 HOqdO}MOӾOq<29 Fwd}Oik7hYeŇf\nMc=BiK7XJc`^ͫs ]}hK\g):PLRqd37IL& p:U۬$L*wans" c^yN|=v<׹>}|rwYy]e짾_7 o]:߽Y0e&RO_W8uiȇ~gECH{?}K&ۏw`l Þ,mǗ'?j1|˛eMniI?C>k^>RbN)p;xvUA8XiҲk7V~2kd ݺ bs!0>soQٟWK*\0{a<O҇(h:h)絟6kYJJzbvNvޭjm>TJuUtCտC7‚ظ ?HVAtXUBy)ŸơOX댵[9'Fa[%Y)l6Sצk)6Oj:7]uuca=JͯoSoxzy7>x~qK ԳK^<S?pQQX__=c] W:7n6oͤGSS/[}CuJߏުnn'O'zz̬Zq,$f/%^ONpiy.W)$:S¢Zb2-Zp!iW~Hyio܅r|p~06b?IԤ$+WWWQoh21ΥWEdOLF0{zTXM/c:2|YRWmQb_)E!7QcQ4LqfM`PlU=P۰w[VTG6)#zH/CF<oD>6wj4TZMc:S3?<҂~ԿV CŽL Ĩ|S\nN5r検l|-Y_ l~o' ecs|zzUW4F[]QYp@XQl4 Ұ42n9kݜ۵~0`Sjn8Om._%&k' q7%q1K>Aa|A˚no2 ]On'R% o QTl_?LU͆},JĉIdwD[|L_"Ґ:9k5K俪b濊T%&sV8U`pU'U9!7ny@@N*_W[m3B]6e^(,߅|[;z_oqYKs|[pf|iJx4bՐV{kEmt mh{%ڞ;VGC;(ZzSzߋCTuƝvظ@;!uNH I ZRq}>o85;;ĨTj,RhD6()mY6%tROU ׳6ω4na WJ'3F׭Tz +ԟw3;kpgrS p|LA@BzFb瘯&yyYMõ%!=siӊ|3c3'4v:syN`5~D&*)ę:q~wPڂംF e4Ш\nN((\-m䣴{H=JNXY'ƹwV5jyͿn*J}-ꔈGQ;- Tjx߫>?O+F-?-?-2pjе-eS7"pK۬RJw?w+QlGU_-.UU"ŘCW~eUZz1norɧ}x Z5x7_)\LӏYmm[EE>MkjouУN[*oWȇpe䀼$)*S]ִ9VWGGLJ`ɲz#?Ҩ g>>1(᳁Ng\ 2))BWGYLLwFL-oUYGJ ɜtęބ| /l584HƏ\P#6زf_V?~*CZs7^O4л.8LN9;)=c C|bS};I]NޙN&y$:9ӇCs~9f\TFX`_c䁐fuvX1f\hJOU,Us;wZ5sPXíGKSt5,B6!,Yڕw'Vp"*i>kvYggq)2TY J"']cSV$x"}.z;_Υk()Lw: 3Os)W|:^$}%Ό=́Q b \R˲ߒ%'MwdRdzyidY٩"0 m=H2\S'&V\>eDJJaeKYX^)he~UKNm|BIo[;(Oh.mYEs:А[ܞ4[mO atݤ9O/!KYf5:席}AߧGGiHǐ6ŐˤHŹʎYˮkW˗v;K}tT6˰k&#/3ʰU 2%;t󋽬Ջ4:<83= u^/e4{~iiCλ*WӶr_%ȝ<}]azy>'gFc#h YP$!wNzr./KH:=@~4 ҽbDžGfO6W?62BkA!.7qT]k0HnVEl` S1|D)ry:w5JH5&uD*^/F(ޕ(_kȺw]7]tWЁ ?7n؈DODuySyGU 2g:c8*J9\)}^EoU,yjqyb]E5?/R<1T/WޟG{bõ>X%Otύ֟K9PaD()UN^6CH36E-צh] VWͫMWPW10,# oz1vǠiFW.[yAc|Jf 833[ݕƏ>zU쎠5,46*Qn.ﬤgit^HV*멋N{mج3 Mym\L}[N*}m/+2UOt9uziX7wQEKǛW^r *AʤTv=NѪH*͞~~c#=8v AU*I:3^sкۏvտُ$WOZ1+ُi#HȜM{)58扡vמxKc;GU7wk1 6˕M7.ƹegg;B#FwH?ұL)Z;BiJl7襪"*,~B(C-]kMEj =V2ݙ٤LG'OdzNEqKN[ E1dd.Mg3'>mUꎃ`ыtS9֊ _۝Ip*6AZ }Ue|4L_=#K َqz)s|uK]sԫk׍~?>TJ=3C!׸i)~/8ERˍՍ$ɑ܆z Utnb.m;GS_{(~rL}NJ@܄_*[i6qٔqX# $kN۴0č40I/j񡜞4)nmJWwKi5]!-J͛Ɩ>zѮ5mk)rJC+j# :9tOB 3PCH-^ܲ2EѡnOeQSq٢)l*p=CT권T Pe C(Ek˦uf-;ZbKs5rr^6ZglT.m}P j2C˄wxZ`+oqXGE΅sZV3K:,t_/=#TҖe[lY˲7Mcg97яR7m['+̍ks)Du+Q[6%2J0{޳il]3?Jw]?isr=cUW*vmGn~N<:;+Ky1[ =7[x>^MA?FAoR9FcN"k>>^Jo!ueTrnoRZ8dź$KKgYoZoe뷨b[|= z; l?EY^K!2iuWa:Oa/g1r͌cU4g J Ccl[HSk𣨵i{"9X.tȃOHm5?Lj:eR+yގ[oyWp/^`Ž|A:gkCJ܏#Rc=jM=*YPwCBZSs:y!,|}ɝ.Ȼ>pcQ-hCy?Ϸ&)ȸ vyk:yxP9Uę/^MzjiҦ_. oM{}}j_a㪙We*d"ƜUՄ ؂fџ%ӈsQ&FzjmMGFnu;獶 W!; _U߭KO/ [U՗FzrgB"3SC_~n?EeR{2'׽Q lYCoK|B){> #mShB1rJ:<bWqe~䛑b_e5`R.jQ6ylOAo[ ᖭ6ZsWۘ ubv1r>IQ/'x[g_9W;,GN~3cyEwNKzgRO'[y|3r>rrPʨBQMCr~{8}v_yN6>u`[誜5o0@UGea#rs;מ uh73H.{kͺcFےͼr4OIGWJKQY*N[[&UHiC KO{[KqvHnTWK>xVC_`_z4[+2-t5_=A~:F _KNʭ?hxQ/V}Y=Nףs>0s'-&tO|u#asҫfb w^8T/{G ک&ͪzW77 ߿{Cn5:Mձ{y|g! NMKl*9~ppu,+ 2Ϲޱz1|[3X[|h*eaaQ#N'Ŵ]\xT_)rs̥ i'j";Fl*ӱ=t̏X'T :=$>Vw03.}w=:WDtaptoй&pZY5)~In>Ygw'JzKye0+R4/E'w)Hy1{Tu"UwVcV3ac+YoUߓXkӕBDQӳj6FH=1 Cy)JJ=l> ӑ΀QMRUn~u_A hۖbNÏPe (pݾɩ"wi܍_ktᄨ Ό\n5go/+z%퉋\Sx՝ֻ:3A-|}-Ķ'o$H^W`OuNTOQRj`V[T}`7UwF}0؀?^(5D/?[j=e[gF=v/Tp% Ȯng=F3T;誹}^׾>o,wYoOJc@Q;ݍwVQ Y-v3$ln^7SHq>ɨq HŒ'p-NM{\,1, :3ٹ;V*gZi++sOxm+Ϙ#CTO/}rIs3KÌ($0>2YL1UoE=Vniq[n-BWpQJT umlyP&n˃RAI{c"u9[~m?)r?/}c߇yU']t\n J \nU-TbUOYAbg'RWoх.u2k:={(қwCmշ8.)/ؚpxw*!8Eiz#hw5 V}tl` xRy6 hzz9#^+,56fXreeDO莥·ќ{dhTwΧ<sGKnz9&5hAϹES _6I5Z7K=[~J`gWuƔ)b4aI՟LU8Q/TCb)<īxr7NZxl::iZDq4OewQyFYdL|0̗gY]VGu dP*_ EU96DTjLFR9򍟦K9^tYE쉫j;Uu"4-xu3"!p~2n~[<-ԵjbWsf߲L88 |=B5<;``BН՘;4 anfVgSBԖ`n@b6OӘ|`. `~P7 0%/14&Ki GS0Eta)LS`r$80/㒘\1`ds0oeNB#L(0_%1ИT`& ѓDT`R%Y 9, L{[c0ˀs9tIc$&I,avp(ME`&(1oiL40gcwJ<LOטH` LSM&`n&RbV‹Á)"L`/0N`_ԘpI1.`N0J $@`N!0-%f`㐘 ܮ1' ,0!s 0onj`N0߾'Me`S 1'!SH *$/1@8a{KUv`r%%_0>,ػs+0?\@M60aR&[bx1Ą Lx ߹Ms̕7 ` 0e |LKG7Ƥ̝ &Ib%s04 LTxanS zFbnAWnS,{ L|/0opj&\b=7"p` Bz80qIo9HL`~/ԽlFb&Lc~!?^| +L̀170 40S=`f90eNj,><030ɕ"#a3$S|LĴx~L<̝dKLE/ 0s;fqq*0{y\Fc>Zd*1oMAfa<Ls`%Q7&STyGCx#1E 0,&QbnOAtf< @`%8y &^qA`"%7o'L0oH@fw,~[&$J_`'l`^!1&#ܦ1 zax6G0yHR 1 7|GLǀI&_bxVS(`r%woBy3o$7_60 SL8Q'L&07'jhN`Fdl~MĨF}}`JiHF`1V'$"ĴH4si$`$XD`i`zIE/#0h]DKPo$0k 3F&"E &03^b0s 5i1Fo40㒘{ C;^<"c41aF2`Wtfo3LFS0>Me V2Zf`@2bJxbĂQ|e.+$(dāq8Bd$7ĘKb47ǿ`HF+`, F?Ȗ9TXJ`DX 7%̠78FAɈ{dTCMkbch `$KƁ(`|EJhFG$b(Qb$ Fdg0YMFk0V$zA$y;i0#R2Cp[6`<FdEs(c1dT6_C0rGg df,0[gx 67 `, 0Xqk` v`C2Gsƀ*큙LļP<<L;`r$$<:z$:S l989Y90RttDbWR0w3q(M'`F*1%30zy:dɨAd FbH?CDXMfaz-D 0f 0$-1O E1g`{ˬfFd}E#LF;"c&1>cH2?0\ Z,FOa7 ؊h b|rD0N`XaaVt|/ È. |`ot0%j?D0,y`+1cVcn.s_`"1c_Atf2l&0/l* ÿaf?YQB a[xHem$1I2νi2^2+$0&Jb`$IF$&O`/XFdDD=ɨe1^1-sᰑ`+G0‰dÆ%[`L#\2\`Ln#V`$D;0Kc9!aʚ){_K*WKxG2޵RU>^hm1R|H@ےQb /" #XFdl"q1b 1-˳+-P0#K2Gcd\|dƱ?$˫`dHM0f,_FC1b I<4xb 7׿Ho;#~1Z2|M0@ ~ (ɘm1}0"%Si8b ᒑ6c$1ڡ{S %1Z#a^fofO"}o-30LVX%3iS*R0)0mfO Jt`Np"1$!&abdɕ1 <3HbƘɑC#30g-1clS0Cxkb5Eby Gx?J &Ct?$Kz$`R0Ękه#1sD6l(s0S 0`F!LĜG3Pbƛ`"%f8d¼̟3Fb4] e%1?x̫Jk!1bfy0󀩖,=s0017o y0K? =6`'F.hɗTMķi=@$`+1Qp`^`L9}`w#H9hdя'X0%c1fY`@`e1&2Yq F_}^3S`JF-t`5/1sL)~I`f|s`:q b̼Lt늸LG1-1bLs5%1D`[w H8J=a3Ib>7,11ciGρyEIKi%Ӗ0)/1 #HDD`卉`%exq$07`ސ/Li80<]QYKA̺\}41ffv y}O%fCb1-rjLk| V I ̭ ` $)&V }Ű+`VlC\`Z0$k`mɵ/*@ύʧ|l(ݤ|*RԌimPfAق?B"/3?BK?L9)gB-P΂28eEiW_(_2 X砬-+uL)AyoeV{A+lJcP.U+2CVQP((á&eZ+ke (]P69)7BynVVSBʕP꫕E7iecR> 'rS,)@U~z([@|(-եryPB$)4z Fs{V> e\^"or ̆)3LP~*;9PC;|)I||s+P:|C$|h e=(*iBy9mԲ^h ٚ_P2ʇx eF1 e2̅Ae+{/P> exʛ{Kʒ ZYl(kr >(ASO4N(P.\o?̆2y eoe_(AP.'(@Y$(#AyPZy& #kٜC h(6 | H( <em(c 2DT2(]PV#Pn*hPûN|_?ʢZy7o4T6+PEʑPV*|船eu(󡼓؂qPfJ;'w.x|.(2yws( ͠|*/P> e/(tJJ#W+PBy+@(nhPV%I(=<e @eR^TB$BeeȰ jg30?,[xd;/y֠ ͼ!O; :eP΁2JEP$?A .(2R*zP:Ay(ob4ȠV*ϘB(+2RyּePHwtwO̅RN&砼J+o2 gr :qBJ PN`,,s˄b/P#P ZE6PmZP~NT%_'L(&_?'>A D(CuE'%f[PFC˖PH2H(xey=JPCz(R2#'w>(ߗJ>n?(?UR@٩ (c e )A (7]/JՀeCfE r :eByKe WBvd bB*Y?keE(@Phk~`(d $dIVMd G%CvdwBӒY^, ?IVۊw#U|D(ϒ>EϚX˴|{Cΐ,喬ds [Y$d7d-M%C6pȊHd,(ȞiYK,; s@vd!Sv3d-ղ#* C%-$KVn ,B%=Y.dI_Rhe(s2 ʼD/mJPf&ʔP2 ,(5m2 ʧ|T('Pޞ(ڷCYʻL()߂Pʭ_kgӡFНPfA9D(3(1/]ul dѐ&ٗ=F컡PFB+)7Byiʣ_ig, PJe5(CJH'c6d8d)$ %Ɛ(HV0KVrYd?~mml-;ՅsKdr ˅'@6ȒՀ'do@ُ${Fe( P >/,YP#e2&K "{I9 |wLr)GB.A Lr7)C*,wC.R~ eA+ԆZh@k%>a3CH(w;(H#Pv5(á[Xp(@r) R(#=G+Fk{R^rmKu\(|RVIʱR(A6Pn  L2HRWiPBm1^(CaGw=>l!LW`ޓǁL3lL7 sFd`# 7!1g`6f0n{0#U2NcR2yp$/,bW9#I2^h-`'>0la2qx!CHƑ:b#b4#0%Dzj-Dx DJǰ\T4L<!Sln~h0-N^ ;!X0Z0ƁᐌyءTTO3Y 67?=`|K`b1@2m `$F&YOك|~_b+1-F9#W2A,c9Oh'.os`#1>X24V`Zss{]YJef0Y '|CK x`*1#ADkTĴ4$K㈳I|Ep`4Vf?qEbN"L`(1n3%aZӾ=/7Ͳ(OS2Eb~=##c1?.ɨo0 `4π C2vb'RQY<}Oc=}o +`, 0VĈ1m[`fS 1(N AVQڙ0%#^ (h`J}Z$f\60sy?$0ɖH8J0sSnѢr;&Kb*#aZskt&TB 0y M30+f#HLz-|Jakb^.`FdqY<#btQ0%l c1 S"Hm$sOƷOY݃9 nF6&\bʠ!Icԕ$`%1܆G<xc ̞Y-8+* ̒^V\0yLW L ` $f.rL#L"0U%9&_bzDf*<Oc$lf a4c%9&Gbv=dL&{/`dKtY`|H\0JZ㟏]$6pt^A,k(ˋT)Ftfa(1/`%fUD`&B)0JLsLļIU"00GO6b8#0>o-pH00:>)HLasU`D$4Gx]Q(\x !1bZy&Q̃c y/0 +*yyZK" ˅mC%l>B%$Ccc1^10r$*<3qYn ss|8 'f(0%1c0cyJL4dT ay0`m[1tf0;#H̗1!`f>.|ICMtf&Zb"#30,[gˆ4aTfk} Kpa0# <٦*0.i/v/Ө|2\!1 |c|` "J$ o`4`S 1ѡ30ټhFd|a\.oG0` Fd¥ bP=_ǘ H̰iJG_'& liw'" 0Cs0H8`%1'+#:aAb"f?FbV݄xL_^fL&T9*Nyn `%N( 0};06*&ĸ en&Qb"6"L~e`%f*&y'6G`#R2VHŘ|m1CIW\`%K F6.ɸF0܀Pۜ)}b< 1s 001iec*QC7eE|0cK~\\`:󎫗/1&WbEӉ_47Ig\Be`{{P>o&VSYt `R%f.0ɒF`y!%1&UbqHqsss;K ր$~2⁑$`>3HϽA|`,;LY [xTD"9|5s<^"P>EȑEL ܟ?{4)kb"4i؜Og~ɨ+r@ Ł/{OWGkJTM}lGc%{Xl sk{kL# yn㲶ې}ҾE@ɾ?k䊶A#]WZ3~ هH^7<u^ڷI۷{}lc 志u>VS}}1i,\~|Aڗ?0W۟$d_uc<5e?hPzpV=d?=oOk+?ٗ$(oP$eM~ii߭UҀ Olᾀ;$;?d_i_)gjsdiAob.GOGrl~jl_(/vۯDisNGl~am,/ķEھ?/(ʿm7|bx&47o@w-~ϻfd?Vd?-KPid?]>e~[|mpR7d' '"߲Td?~;#Z?\@}~<~ğG~-<P`o8֟|*O}N (?NOPgkd_emB7gS7F{}]??}}_ڏ (Sk+Id}LD$??e믷Lp'i6 d_hdv5'gTqoJ8[\1Ǵ}4?٧S_}O__ߺ}OI}wϰOMۻ*Ic R1m+T@lb>OKp@Gc~/.m?ھ?nk~8B[П_\_O>΀2 /*ǎC@y?;Peeuۏd>?1cg?߆P@O#Rm~,?ٿVvyd6nP!S}?1EگocyݶP!{}[{m~?p}ؠg'loi+}ZHiJ)I5*Z޺ @J M 0R:f&df:sBjlJ qj\Zר"Ж -iQSE bH`sss3ԭg99[S](/~.JCJ]?o8ӄJv/1Ho߳&߄Uʑq,>WzG W>o'UUMK}Vo?W~OIWO O>1CBR]+UǸ'oMMBh_+x~x\K}{M {;ϧ߿UIow JO/'(w?xtoUW9'M5B9?r UG~BGXz?*OQKx}^oTߐ~o?W)OV8_@=?\??(1^BSGE?qg_ O'١y}tFɿZY=ϝ^{Xzw1ɿ.nC_ BSߣss#~*O^eG4=hZoxPI[2cf{TIC?9KBA)8"_[/UU9ݷ /Uy~(?429%^^{Õ?OJ{PY/W9?'-r??OIC?aO?ϻCI'jekU?nH<?~e~3')Ϗ{^Bޫ:?EIC?4B~G? yGV_9ɿDsGh>:oG/ ]9 ~BGJow 3J6QnO'e+w见GɿVoy.ɿxB_<}JDŽ[U{x~8C߮Y eS?~Gz<+_ێ ?CB??q4~G_TOժYBAѿߡ\+Q_g {Q'=JRX~_~'=lQY#B_x'_t觍 T~~>o'|@'g9A2ߛryhO'O:S BTepC?Z*|~ 7;su-O*wMn%zyѾ^Kss?߭?oo'q賀GB?UYQz>?XY/q7B俴IImB~GקBEe}>GAwezh:SOJsQ??_p/D;CU5ӿ@"Jz.eB`~G~w'_{Q|o?Us?iCϿcQ򟭬(w;T(`+O;[:ɿFyQq#OV;ioJlߏ'/p=>R֧79\BW)y}˽B)e%3M9B?QIC?o'R7-q<_>o}B!y T[1-i="BYe~} ?O;|W2_סb?_-O+wg]/WO~/p?Bv:am3)uu#[Gq|?R+O_3iO 7b\ZMwޘ{. O<*zȃD'D#e&4O06|:֚N[Ru,(yZ#˘J^Kˈd#y!gL~L$>'8|̷KO!Au0wI$^ʼXr9 $/%|t!~ɏJCÒ.e]!yog!Nۉ1 b^&yb慒0)y1qGK>N]a$?J|~eC];JK<<(yZ#˘J^Kˈd#y!qLM'>'8|̷KO!Au0wI$^ʼXr9 $/%|/RO/y#K~.ɏɼW}ķ3|'qyDJnw1/x1B] ϔWe>,q?%y䇉2!.[%%ax-seK%%^|eg2ϑWXHf~Dϒ|x? v27$A<ȼNV.ɃKK!>ys,Oˬsa~XHwI~N潒#y;#o'^ǼVryuċJ"^|_b$O(aGIH~.&|仈w0oxyP=G1/x!9Sbc1GK>?%y䇉2!.[%%ax-seK%%^|eg2ϑX/Hf~Dϐ|x? v27$A<ȼNV.ɃKK!>ys?/Osa~X#$?B|'^μC ɷc^+AżL: %w/`>Sbϱ'~&3? Q⻘K~x/"U^A;2H!^ƼTZH^F|& ,YGK>>Lg~@w10^;$Ey#̃weɇ3);ox+sCă$o%aɇ3);ox+sCă$o%ayO[̏H~'$&|`]~ w2K RŒ{a^ y)㿑'~7sa~XGHwI~N潒#y;#o'^ǼVryuċJ"^|ǟa?#O(aGb/awHxV{G% ^#yxRk2#ys$/$~r?"O|O>EgOawJ~%'ܐ| :[{$/e^,a>k'~<%?$.ɏɼW}ķ3|'qyDJnw1/x1B] ϔX'~C$?J|~eC];JK<<(yZ#˘J^Kˈd#y!dR0#1>ɇ3);ox+sCă$o%aC?%y䇉2!.[%%ax-seK%%^|eg2ϑɟ.O|O&3'0~;%?@|oenHxy=]2/C|K0O'/y#K~$?B|'^μC ɷc^+AżL: %w/`>SbO'~'3? Q⻘K~x/"U^A;2H!^Ƽ6+b/d>GJ*RK%1%t)ۣr)R%+Gwm*ĹQ?-]I[ۏ+Us~f<۾m?J=JTeEUU%@ыR+w<IRR{ۯwRRJyWǔۣߏ>ߦESO߯=/֟*Wg;}}}.WϔQDSJ_0_٥.Vt^S?Jy~K)/*YG/+;E?To+~JGlUI1oQZW(O(&M<֟K]]??sM>oQVgO)]J(J:cJ:J:YQ򜧔%Jy^gZ^TgG%R4L5J?N)hҎ)L!=(/(z7(U4 Egw((#Ҏ֟2r]q1eF}2I+RKSJ2E9;})^L!%)/(ޕ&nV[UtVzRoS󂳔s^~IE/]\+cە1vE%>WGה}Ni݊^>VE/5j)5QE[M\֟*Vis6_SJ2Zں N VVw\_J1FAeһ w矫;V˦&]-˞F?&?nou/~~|.\EVynYswS̆.m"͸uޅ&0y:O}"̻T#? ݝ/~| 2Sw5FGݝOPOFN3g̲h:DU6z0SMtjNu:twqt=*WYKҬdmEڸô߼= yy8)Oۏ ;uOzaU~]ꦭ0mU=NL[۽vUMVEO[eP)a/>oP~ꮧ:v>7EVjaJgvw 4ݽviw|IU`U6:ڶ"=}$OW@.Msy{~FJaGtDhp($>(!mtЍ~2I~t[Еpky/Ɗ*S4ZgM?@-OY[X<3c㰷qOK8\݉buxЃz*ކyw=C?*uwWޕ'2W8M2׼::"F<d&WvQFO })'z"qn?$Z*Ŝױ[4I3uu9huZ>rW572#S[]}l9LrP5"0fPy%bWWW="|>/睮\k]]]slx?L9qVQ] c0D}O"9d踙j30xü''"ǾrzeAG6s!h*(?iz~w#OuqA7g%Kn;IF(]\.RaL2 Cul-bM%ǰ헾%*gxf4Vz>DaS+(Cm}C_rj:>N7f-9@~ޞno{$~.8?o.AL>i6=huzޞcOSxo_ÅT3;`UL-+λ뗔+Vw?׳ywR:ͭ\]6c/X11Ioқw'Pmߤkuyg}=c>}45\>tO|mS] s] c3Li01ULC*gطz3C"Q%~4屲k <[$Ǟš?aEI ]p:S_nތ.a1v#дB\_6=1⼎M`"4Jp;i^mҾiN]{,庺OTNwwkR\wco+Ãa:*%q.Ѹ:c_bfچu}h~ޓ߬JRq8P?ʻR!ug~C;lSnۮ_3e7Yy/Ԇi" w<,jo%3_0kp٘-:$oվp?Q$%wwUy{2k\gTY&^x*nOc(׃wwA❕+Ц+aѠ+1WӷU"O #ȕϷuhP7Or$Tbjܫ -Is;^~qrr g6'|~*Fx+[n^5WL_Mݛ*ZwM|̥CXu&J;LiIy/pZG =Knl=6J/%ΝEysړ۰PnHHuHu_r(ZJh6SЧwЧڄyҧD5oϋ /=q~]_bG_CόV^~Bӑ{D$tmK\9U5(^ Y|phZ.C:*q;9]vl7Ns̶XPŚolqeVfFx/^΂!ъWEqu7"(GW23DQ7)"ۛk=Sp)x۔=E1WޞTvM]oA5P/'v{G|GaZ85#^-2c!5B?ŌPBKE7Ec[B9n87j}K8otMOD07z2p07BͧUz, H$PŦƄAE-XkdˆX(F? Y<"Z( Q}FQa@!P7qni͡NHemz dS,IQF!ےX$ !,%>) @U a!Tq-?fH6%42LC?IPBMx(d rCCoZ_$4h7l%ppjS'=JPUq2:"֓!j434oȿXa J$B@,R3p t. >o# J*3vF\8[p:%BB9:QIM nLJxTD5Ncf7%pfVrXj $BCSqp G_pF $bXcc{G  J `?H3A~k P7+CQץT?Bs+YgQ a`ڻ\3ܺrN)%"Zx|ފjkuT T2=o$ -ʅfzo-hџ3 Lbа%hE%h{ р6 PM" 7 yoĘ&a؏Ehv'ş0ˈk+g9jL.J$P9z"nUSWv]5^BTF&iL$/Z`b戀~~3Ak%\tYD T([KY `X9L"Y5jI SN_W3KPO&u$?E4(3Z;қ0IbP7$hM7wfxnB3@@M9sSx#$,Ed鱨[װя IO ݧ=|< ي<(L>usXE5P*ZXDhKfҒWEK Ut2 eI8 cfLaF)}k׮Z[f߾k*׮\ʅľ@c\& xT8\b}e[ǂ dB](roAhX[ RhERFwӯ.|#G2VsITtSKϳ/ܸ`͒1imCL35EAjTDEȽUDFVe_3;0jyzB8Mc 33`q\a2_<"\攢*~ޚ+dhw28m~Rj T'er_~ m \t9g +dr [4oG8O.EMyۘG·b) 1QZ+uZ9v#Y@7V+C_q|є/Ђ|XYnd8C8>ҍ:Xzl0ٟUp;U{,mY~c7hθ>ƾgc o>j+ %?V>:pxMw{>OL[k?c&7HgR?j Vijz_iJr^}Y[&i_~y/oV; 77sx/`7[zrxzXc]9߫fLJT}@2aJߠ#}Vy-kOkkqswp?osᄄ W?Яu÷qx/G;>|nIpu@Lo~$\^:>,mϛs❮?<7;?A}CV8Qcwe 2k7oX+2Y7f"z5ߙa?Ox>;֧哬O8f}:Y|/o{ν~wrGw]_?͛ε~wQC>+NOd [nђ$%=3TM|]/g˿Q}'wV_5k}Z>kY_#40.1bBQ=a %V|JI?J`i ?ؿ~Wz^L-"gg_m~-#Y'oYe-?8z99\>oIh =portsk\fu}SnYurxϹ#q?/>,p<]4.6|Qz\xzF/n IZF"EDY< x"nEBQ &qM]F~nKM0(4R^&ZӯC$Jށ⨀鍟Elhd\ |nC"::V XQxtży=|0[";82LњT$2/[X_87h?q}n>*ADJe̥9tKce"ئT\'. 46b[p@oG6_oe;"y]"\!*Yd ^̅o/n9kѼlzؖ-e[v|,~s@$x(Qf^n|x%\b]/o L8CDta0j&k&TҊp0l=-+ Oz#%BDCm)%F3 eH@&? CιSؿ}^6ei3bZ+މ/ %!oD(mF_ߨmMx?{d*Ir&z̗Nی2MoX=M?1>HX-HQ^o>)l1MEн$zGNo7;o.d}#Tch /O }M־"-[>Oc" n;/DB3 ߟAfܭsW/[ͽis$sKFZ<|bLoA$4)w0t(QećhX/#S@g!)Jȡ} dwC8mZ* 0d kW@Bpݳ}ZC8%F5LjxIդZ1}} EC }h6kE3Xt2oȞ9@QfZ`iINAY#o ·پWȠ -ҭL4ӏgךk|"^tY{‰^मf͇4m8IwcEcH^q]_ E&+]%>sM7;}h4:ݲ lJmwk?$Q?ՎvugH=Pc{aXz98nz=?yq<<</8Ͽz,ze.2mMГx67BHN6J'}?o*AI(Jt(K>Eq|BLCgno=zs Lt  tv=,]*GEzrg퍙rJ.нQ_~++nUjm`s|'/)D,ߓMy 1'*k9}O SDhffuI] RV@yhs8m"I8н+}h_9z?,$H/_DY$h25\  ^G>)5I;ÈV^n `$kf;HE}4fu/j\M#@3lXQ+Us$Xķ >5Օ~J!hCPO[c)Эp`N [}#T[u$6=L̢D"xw!b#3t1/" H%hkoaثboA0 !pJ}cR~[*^3UzC'^̴hlѷιE! FAsl7z̊ FԞ.X27_AXuWKo "UdASh,QY:CUF FGqP2+]S[3XoGoar5Jd)O8oFUhcL[rFY9s3 yھb_˄͌ļV"'͍fX4huKE ܹ3u3{Y^ tI0uwH%B}g30wgRK5mX!6 o‹P $QEl<2h_:U)Vze,ZSaӪ93]W?7qc{[:f;6wz޲:+~>g{Z8ae|]>67~SQ03wi Jw4k7[LODKouDU|ԷyQ/:%iv&kVTmjG󢚪Z_ }!:yTl檇 dIo^vğ5&MבzgeOvru9z횚@DL2H;mI 3V!drēD2QgiARo#F?s.ٌEcPqQSoDCbADnSfKnZ>0A4f-,ցWaf`%4ȰFaԤ34s=dd4x!ob?&t1Kryj,v)5gxf,k}fNެ(/.j3& G{*Ҋ^^Zas80'c Y.τɇkWv%hHql ƐmEД\VR̼ 74(7w\a<iwha2]A4xOrD"ndNLcB4A굕VU4'$dbb/Kmnɒ =g Ҟn7cULv.Lor@i38bڧ\?Mj@_aGb5>%ZDڍ~ j)k+EgpP3"1l`2:&FaV-gs[r.~`U\G%ˡvͅEym~Q]T s^%bH{i[œz p395|ХtE:'W&48rԟnU}y],ZUAv!*=P ˛muI6[0n~3^>f߸wV7_m>N/|䳵\L VM: ?/^%m|o“wW~=ho9?}lW?I~ۮ~۞כּ-9_+-0B+1wV̹ #mWu05˜'pvrmC F ^Xd}ZI^Pj I̯+LtuQ8**QNu`%#p|Mp66kD*kĀJ%c,ՕԼΒ͜YZTE5[㴭b%v#6*h9\790SK UT)Mrro Ex1sJL~p ̗PN QUESARfyU#c\2 ̏A-b>e&rmW!KA& frc2SmEV^νea9<~f)knvb#ۨ)&6esݚ kH((^'n*GT11,0GRkr'2oW29ā^6fZ pz@(HȌ(+i-3jR͘sܚ{T VfJ4QPk@W( X)FDFr5r9]#,?͠>A`QLpڡEyu5m2\iرF FmjXvuICd <@b׾jf‚O/̳^fkF 33H3L[ErKz7F ѳhPڗUY^nV=DV8[ͣT҈5ve<1&bU*>E<Jc7"4fx~#V6Vy&=MZ'bq`BؑLŵI0 #7S-U&eίv]YI%S'LrDtNbq?bRig\PSؖ3Xxl'1eJym(3<⧔Bc8BqDEC[*3۟pn„=sÍh@n׫5S3kXB&6eS:zҴ:-YUTȞ.3 [H64SXe~SZⷷ mEV܄&Gxo8 z6ItYNՄQst;=TQ5N)?)Tt|֘:rHx&jB|ۏwl~SH{ )a!\(.D!3M0bU+oi%i`9bĕ]yVn0ToUmuNJRqf9( R^ASAM?`U(ZJ1+}]2y:3 dUOxr'NUξTgRU76+t9OjZ#E)VH܂7 K1Zik}du>sdϊg ^\YS)?ke0G ;j"lIixdWz<'>g}datע0IJ-^Rzҫ^VUR[j^rW[JynR>7yL)H܂uM8,ĉ7G["u^FeBkƐ9p+vbenI ͵U.pZ7r-xK7o6q_2t/j&n}O}TXV|!)3NYD9*Y6i״jZ~*bMkn-ze%6:v vVS郰Sдyڻ4m6Tzv1.Z|*;T>ء/=.Y ?7qzߩt8T#ңCoy?a?Î}\t|T_O`\p_D7O.cv8} !; ׹>_8>u+Oc<e9;;; ;/OϦ-3~V --uzaaa[``{`{`}vv v 6͸iV ;;4W6aa`a`{`/C8X/lV{ `KBu`5n-mJzB`D8|ئr>1\t|.vv q~E͟?AW`{@[|Öaa[r-Yzm[q)ʕD/aaa-m=%Wv6 ;;;r N/OomzmO]֯Oa*? AX |)[[mnvvvi1ؒHND軹`{`[``z\2ķStmD:ozaA#A؞[Ҁ#[o^`P{aav 6?aK6!}Cmc\یa׹V;@a듨:6nėB{-;݌Î^V\ v.B}݁6|=0!6XmiCaaݰAxX vvD?yێzE~ׁt[a`F ې?ؒNv}G9a\u棼> ?X_.:ށxSr[?>Xvv`͂ރ.=}P"XC:(l~ VB ;;CaG`Ev6E6 [%}vv.Fwm`{`%_aa`o"<(ÿ!\Vuƿr ~;5+E~6mVq|/ ot ;rx}#ء<0{1#H7AWEi}` .GxثUN"{attT W18l~/(:O#_+7Q#tv V;taaNϿϺzsN`G;Ž6OGxWIiw:M3.FdtJv/lυ*-_Ec7[-önsoj!}Ոv t}5t6 oN\O[j`t:=TC"| [jiGzޅt!^~⅍C= [ ll~= Xnm ;;?tzj+iF\H7aG;t'm 4oqͿaG```Gw݈.F5#=='l釭@9VCz6PB7\6[ә IŽ5R{AzMlIF ;LgMkHݰ%t= l lly“H:GHCF:}쀹-&-wyiSZO+Xvs{2S+3m߇!^F\9p(l`̚YSۧ5Θ{f㿫ixߍ;npa&Tzo8)f>V8` Ge?؊YZ5kv9f}{VIiL1bbl8[qo9z_p"5"aJ~ggyL_<5kIIO⿓G==B AO]ȚD8ޟ4YgƬYU>_GףTͱ}s_{Q|;۵Zժbc&6E `;M !Ht{1.z];Ю9{WZ$d_~?{Fs=9+sXV9=^|)D"C9Ӕ8[l[y<'Y=O{>oZX?n^~y\~^~\Xx,os&yI{Z=EzN z 'Do/^?k$ :=(eגz|4v_ZzhkiY/?>_5dϚyhn*R ?Lӥ`[L9CeŻnj㋦TRp=C'Uʸ&\j&'ǶrR2] ':c3_Hؾ,ˏyqȦKhm)hKt\]gIl#Fdђm׾"[%iےq_UiL]״&U?Р[Y.r ͫxLל٫AtnWt.At=\;TbGJHiv4׎6e}A뇸qeD[ ,eY1#2}?$h`Icnu/߃ņ=? Kr>aDIH^I/ߒg5sX 8)">.5ճ,'B"6xz8%xJô>.O#fِrZdzmï&x Ulx[ sif)8ZYf[3nl%ίˏ/7k*ӣ EI2ti~u"M1QlwҺxMfM+ϝߊ~zub̚Z>'uKx//)uV P)zm7,]0%"7K:mzq9=gqs=,[<˖,Y&ں˃Y,,'Wu;djUjk;/͂ןdQJڀYߏ&jԣxkmDu=gVu ĵ:M;]>+?Rs9[~9ׅ{zT>jDtDwT)[$@]$4"9=Еs?~5~:\!^~_(_ xKV1 /oPXWP~=4oa\"ɟز}Rȿv=/ %g76ibb(aŸ#-]3Mtag]l+htD7KwURA峵FrD34XxVNוkKt+cyѵns/>NsFetKL!0isRK fyq&#-=26OA}$-|\eH,c7x}D*ӣ&ڹTN)yXr`Iҙ27_k3vԖƕ}7 pEaϓ܃uo|x;|bϖڋ^e\ӡlދ{ZȵAoBֿ{:Gk!>?/߯x!mMm%XuMŬxv{^O3g|xQDų|$TR|R|DTٻoEul^z@?i }t??>њ<'|sׁ?Og?@?$L|~^ߴ+?8~"Kn.u?+ߕ82-goM[֭8Pݴu*zDY[I}Η?.8k8_ O||ݿI}K)?7)͉)q&m5&OKۜ/}>,}>bG-~k2'T蛖u ;5|9;L{>3V<@ K~IQ5/ny͎EeR69,JzNaII{u0F{֥ncy|9?KJK,IK]櫻P]W֣/C?_/|}å.]`9ק>%~abg t8@m_t}۬B,D>w"yӅRyK+kGt+jQ> Ox|(~DE'Կύz:/|6]2 -.&xrKesKqi7kvR>ߔ]kuPku+7'|^JkB|-ߏ籀qy&R8LxoY^KH&3~ͣgcob=m\镏xO% q]mkI;M.߿6`|^D[W Yͪq"첯~"PD~Bدϓ5\:?xW7^=* Zo5/]^S w7}]o(]_1_ޏYވ9t.s)R䴴UZN4Eƥ6*+|Jb]֥ưwz *IR&w~K'4_;kSG~NJuN*ǗDZk\j#/9y2MD'ngy˥6?sv7%Υ..S%RLѳ>IKW~B8O|Rg[ݛT[SIӵOL}f?z^yedw=Sb?'֜m{gy˃IB3~uK}=핊I}wŨoikq3F­UXѬlU(:FkI*]>T'|BVS?~7U|srW0T8sW#SY.5J2@D{+,+#*Q<4+R]W \%[yu_9T |OcQUz W<]oD>=gzϵ..ߧ?^ O#E-yW*IudQ,yCi2j9 |ߒi"'k_d?'LNOFy`ow9=WXʼnTzze(ϼ .?+KFsDǕwt+[]o_r 4ϥKv$#giCpk-ԯjyi&|sƝ]8orp\o?JbEM Yԁ {Xȷ^jy]Q~W7z?E|dv>޳%>ɒThyo|]Y"o]<^%C?~'xK"|.5w'潛]J/ƻ!D-߯noo$~d'|}?|w= _?}O-ߓ+"|+.3?Jz.yW_XR)_VTSB0_%ky6OJϔD My?K uX_R&o&?2]b^35Iߑ@!R"ɶi҇fMڷJ<E;ڙk[?r;qNF D:󒇮l!@p&{MDRc+ii}mvoiVH R^o/_Y+?M) ~v6*yt}4)N(S;l@WםR;!/!/E ͽ3@.T7oPƞ:8/x?8ScO~Cɜȱ p{.RI9lhwPm-JBwStG.ujqd --LwStץ'G7Q6cX]b9vK]+t56}g'[RR=.N'-eW\]ue9'Wo{?nDtIDCq9d|ǟ+]r6EIwt ۥw}.Mԑ1O^{俭wGYV~<Qv?I>/rs{fB㼫wPsdʾS,sp!fEGjp{S~כſ|<W Kq@sǼ=' ܲI,n};JtMV/߀/zaխ|>>⼜nf-+'7Dw{IDaV)x$ȭNlh3QڕFK`|)ϻZ[}[~^z7UYWܡX2OSO[ F]|'Pe^m<$OpsdWq8#"kq=E$ ?ݧ[o$g؂Kiwi.ǚ쑧s 5^ݯ[ JLQx_Ǻ%וHN|$j[_F}mfoՌy=obRbyʑ*8oWmpGR9E<đB:fKzwm+$bW---ү՘2Y[Ƕesv<~vG߰N\*o2ShGSoɄJI뻤RB#F_ \'y*;Y^lVy' O,t}=ͬz yOHݞ>%VPKnu,}O%\HiV_#xou9%lr6n?&z7$_7~W`9wZSw8~7~hk&YD?}VGrV_qjsqtiDhZ3zŨ韤}LL1d-FP p-o%3}>D5/oRfn?Fϴ {MPDXo{%)q߼#>I-!9P\KjV_RwVwK/#:_n6ܦ+"֭%}r}ʖsq}%%&U:چ˥o$g-h։WCbٵjWdq/](Q,7<q`Ay헔.k,2Ki#o5"yHϭ~aƊqAs68.[EprFx܇i[/n}eN<^I-Y?kw(<ǭ:ݖr/WOr^V~vo9Dnn5_ӯ_ x SՒ\&>0.UsoJEE?^N8of"sqga?D/ޯgMOh[fY=羿s_>%5$ydy_*EVcx{?[@>xeJu?Ǘ%pF~_hkgG`-U-ћ+)K^5lbRs_ulUeH=õq,y`ߕV86G%񐬤LkV/rSFщԕ)n9dOQpY0δvSgz^2%Gxj{ǧA?k5Wӳ'6'ဏuE۱ڭ>`Ր`Toz.Ws /yN FSO~ޣ!<'=ИugkNTg=Vjq\w yHmɟ, nOUx_r#_oUƥ7ދ=vg0n+V3E]#ʺI$M5yЭ~yNzcޓK1(:?Aiן 6z?$s}ĒrwJC U.#2Tr>S\8{?#ϊ_oWh<}hxг>xܥ5}@#kZ`<9/* ౥7Ê!o`nWmIsqdžh]FoIQ<}K |o*VO@f.MIz m!3|}]O͵/b;q]<>^WI|Ӑ\y[<Tu\I]b:X"T:(;+P +s`'( KGrVT0SǼ{V 4"ZJ+)*opnae($QGr6T^ K(_C.$5O2$Q4%%Ce).* =/WC\2':h!UUi LTjf3S^xkxFT!+]HI\*׼UY/O ,T"7EEs$'N*zJzVW%T<0 ±8w %`y˃B`Aw X3jFv0J}2w[&9b$-^"ppXcF6#D4#GFvTaEȓd5mϙౌMP,)o\6$Qc#tmO)0;a-"P3\x7ޥǓzXϞAvfp|4#ĪԻ#n29 ťF.`)Ḍ' t > +stk!?&va gHm=>6s8bp\UKRDzvc: m0v(@R;+;z\7?V9aB; PR7q.Rlx8Pɲ  Trߎ.2<7zi+Lɞ'?8i4!={!Ҁ.P+@#8#:(%n'> ':](uQ}<+ rݯ+܎PSW_e -]aC, ;ctk_@&V.DjH:4AW w CV=L{@7)F+_)vܦsRF{ õf8=o?uMo"%78#=6K`N0z)һ80dr;A^ ;Ӵ@v!-t-qi;2Q7V Fr[nU௶a+n05fuɭ٭5[;}wZcnc8 'b?? i #Ïmw-n{N }D;Z҉s39uN,NI X/Q`Qo+dIx %,^i C|a"zk8)J0»p !v(|1wXTK*r@:`ta-K?2CP&t0\耋 G86/}#eKnۧcrOiS^o ۛ:*1:'Uͯ+^Uzh 9q(ة Svýr|)H TX0<3DR.^wު-2$r?;AN&PskM<)4,U%6S7GHA |\#A% 9%{+F;a~5ohٌ8Lplb3BVr~$!ݍp67qY8;Cz 0*x1V x[(> 33ppgdHfCܗBdTv)jCdR 3)xѨ0ƈ덐cFn8$g؈7)9&2+LxWfkesT)pGCx5螠 )_Ý`_Q,}im($xܺp]AGȋ;:|pB- g>A_ LZ KKkOPNE3 5ˆk#c_m8kүm!Q6o-2xMp'ՂV|ZXRMd}ss-Ѽ:l ID}8eÅ-ha]P<)4YA3Fg_!x`0lÂ0>6!}a IІtP0jW~/@/av``|8zf7"T8.}c 0ЉsĬ ljWÏN<v9kk@ohagGFPߝ||H? !+QxlBun%`w^n^žh'~*Յ 1HYa=u`g g|3ׇ՘fh5|TfTUZR jJGzpSQ$0C" B6x( I\ߓ9F!^'52ù2/H4Od Oi)]Bf=ILXlI0E/KC ]󚬏 gtCl38k5|` o62%q%Y8b m1!$!0/'hE<6< l O[(MxT`(Ci2 0:.P!' <)fz%H}W#q[̜qRvo{V|Cbԯy׏D%=|C?$~a 04A ]+؊h @53Zp:ie6pO4hXh6%ֆ qh4LMP,ӡ1+0\R fjP+" 0 ӫ:O R ;B t`vup[c8WUxl =@8ng묈!ΰQ0$8ωkFM'y19"0 NE "(oo5x4> + teXYtZ)Ӄuxdh cg ).3|ċv]Judq3`avYv|&nGx SJ3 ~Tp9 g(a0 Haa.}89f8 #8cV3)dzD'Ŀ.k8 tPϝ.q824`v澁k1xm5<(֘n!' Ԉg-0;(lK+UQ%+wP;n&E?'|E iҬƖ/1rq?X9>SD_]9=\ٓ':/JHs ܑj9{tViexi u8 s4J*[أ ug {LaR#pMNa<\g | H8CrqJp!(Ň3Pw}yHz#\d92얐0']2ls'd%]3Sa0KT䧞SzZ ң6ATrGl8d+< 5gpC&\Mx" `/38٪`axQ >= 6Ȱ3̲2V<~@Htlx}pXzX:욧gS22QXz&:]5 }NG|F񑅤w43 ?Hte7+&&j )<Ζbyz<{d;%CΒXm؅HϘ $ܣG2R $ '#Ne$)|dOu0NoW#/xOoSX+|H>PY`SM1#c[$hY1\9 nو?j x;5IOal?gBW엝pՂǜR-wˊ0l()pDM z` *?RℌP$xׅ -,27/F*`7EH$v9% l$`FKp[(|B 1eH"_K98)p_4Po/lcj84WRնI9TyR׀$R+H)ʔZEG~` FǛy#c>2 QyM6"1oyV&ln+|fx{Fwh nBfde=Cf3sN 3 ã,x %m ;H b&˿\ )H(/@1-݋c` .nM1_)9oz 4毞EyK{NFCOjH)v\A[y;q I`~4l Aϡ \)VEP< +GC~(<E&017 oh {# Dã8! x((w,;b5x@Ԁpѐ+}x%#a8o!N;I4,N] 8iZ80R"nD^SL;U OīH$yFA0çEq|( DF\t7F{(=]LΙ 8 0J ֱ>Jo"=L ~ !!5HO%AX$PCb!L>V2q J h6)ydyR^|3u9|(-\4@0@Xh@Bo0@8#[LL#Vk?pKdlIwZ;vݍL0EVAXq xc8heVL`5?/c6pU \06\a%ц[!\@HJs/г(MZW>P$ xHsDhI/ׁ"1aLl6 'ۀ'Bs`4O>ߌ[p\SKlyۭ wYOV<!0/sCg^Q3;wM%8c|M0``9z"rmڱ2u:RHq%W§W A5r ^֊w O{φJNSp h'!Uj|X}o)d^ڄpO[#<qRձ%w5JX` 9W &f 앑Y8_cGzool5 2 \S^0#wiF=Q5/@mϚM׌g\zˋ&ح{FCw #_!e&h@^ Oi&01{gO#s8NqEZBrKdZ- Rɕf8'\\D3ɗ3qq rϜ29x@d;Lp8UL.pYFg19f:?xӴ znKHf&+x)lgO!Ie:𓎯2:sGQ)ĽXP@IdvvH3SrJn\|@3N)><5H 1 oC\d;>DkG!W=qO7!EFfk3P{vieAOk"W@Az՜+ "Ra#.'3U+(Fj8|8#iw5:MW"(69aāY `V=Գ%$ ZI)Q h؎8Z&Im )2cx'eJbS!EAo$(>;E:FqQh@HF' qw(cV(pdEvg8/SD&ĊBaiH8#с+$9N4a0< !x6c@ҝHc |щc#]Zi /KE#ZpH "M$na<Ѹ(W>`^.a3a,\G3JfL>){ 3^db =x̄ /Q3|v9z8-,-83LR tz|h,Ӎ8 kWL5aܬl7X<~@V),&Y0AI&P0O-s8a R@eA]`E'k'!v'#(qJdO\Ý ?W72r~Ht_F[CqE{<|QvڑnΎ[# _r@fh'l' ~ $0!45w82<+DpL Gp^)a);pl8䂠!7;= 8MfPQCMP~(#+B~p(_>O2`*]5& Fg&n oSJĽpтpƊɓ2}JZ <@k!';u8^AZ!J=*7]22 dsļfH;ff+&BfX,4B#0k01T9!}\濿 \tKbK,żG#g}:X@##2 4U+MF+5!]kv̰pqwOڠugSw~^Et_Az 2+^yL0Ji{b9YyJ$39sa%od2pXa(4PdeAKA4X~a] 0NWl aB-/?Er{ _!A dSM#@'x㧤 brq&M .S.0HWonZ0MatP0a4 uP<qP5@N0*oDr1a*G|wp m7A ECׅċ%-x}.2F3L ^#Y7,=]N+2hY(f ')pB4l#9?ZfI,55V;4C!Mfo $̥渄k%a65Se7Y2ޣX@M'& ؤj*G T`) a?j(*$.QPC=BE@p=,,{.!Gb5B1S800A0DMAω1Q~\t`! Pc8^n8fY$؉tmkRs2sɈljGxjE=ҵH@̈́(5!5_侀yܨwegqeҝdbL"ztwinD@$a+qA .hQJP(TS]F~w^tݺuk9uΩSN8M#pV_GiZ-EZ,Z7MiDtm9çkK ^:]k tW%xXk!xXk"+Rn(њ ^.њ>(іԗj˩JF7KOJ:36 3& Z-v*emVS|OLLm-Kgj?5;K[JYZ4l)5{qJDƧz/lv!Xrv%gk?FKZ;f8Gk%xsFbs =W#h=W߮ԵfE kv'v+A$m9IZ#IZIZ.C.+FLDPLʴ˴nA}ۭ}A^~rkOhMTzأ5G'xC=ZLֱjZVJWk!n)ך ,ז<.RRʵm"5B'XR,+Wh>+6+TsBRk"x_~Ԗi }OZ;~jmx֘JpLk!xJދi1m9AH-֖U5< Rkz Z-"uE\Fy\DpH=ZZ69̛50-'xyHHm5\^\SSwh5Z;ͿXmԏ5Z+SyjTm-CSf'DꝩZS߉iZ9Ӵz"4iZw$$ԬvJtFpH=}JZ "|m9A<m) 5"Z=%jW\Sw^ulxYjPk#vg"`EZ#"Er. 5k _-R-k_oK6Qr!a+" \Ka3;HGD8d"z'Žoi`^"ҷSa6§~7E.%@@%W̠zBXKx'v|C#pa?1; oL!l!|M.'|&}'53En' W!a-[\.7n}#Z̈́_!},MK @iF· ?@z;auԎu"a;#l#|[ θB W.'|w ~I@WR=o'%Ž&>A8p-H?LBBD箢$uHA##| ks_BzTJAJx/%g\C l \J؈uW͡xu"1 p)n.ՃnZ^K%+M_!}<"µK~-&N  9:—=GNkEp l%| ~FLkP=o'\Jx7-?w#'܈Q=v@߇~#l%pB^NLp9+HN'DVz‡ k B=Žo;^p=Ճ.µ ^%@D%W@ N%l l%#lCgZy7wvÍ"&JLJ&[^~}'aa[+ N~'}.a3᳄MK CbM6[DGxZ·[76H#p᷄o%:~IJ a ۨܟi6n%l"vaa/NS'MA66@:a=eބvP/O'upޝDn"|_6.r W~JX'zRv gڑQ¥6Etz.'|p)g 7Kr%\Kp+a8a3WM.r"\~ɮ 6/>CFxp-S MZMN]՛uv秄m?Ht[ʡ M/D8gW܇ %#l%|?&%|k#D8%6w[ &l!l|ʧqS:6 ;z6.'p)ǩ %\NygFg}!|~!r#D:a37.'+7%f /[I6]}f9͔¥o{ lOS= l%@F@g}̈́M.',}/ᣄ >GR=&l%a 5蹆]m##~y_ ۩?&Dpjᣄ̈́_6ֶ>@X?KXKaw Ta3a3.'"=Gxa᫄轄 ^EAp a/S[?'\N þy:6«_ \IB&ak~Ҽ'\N R]K"7a-{wn"5*a3gM.%\:=GVS{MWAAap-oR6@FuT/^:7 ۩^6EEBFg[ o#l"?'1a;Z~n#\Kx;}O6Fpƻ^f .%ۮ; )p焭~pۄm_66OOx/a#; _N_FHaa;aDCXOkhFzkfsGivT/w 9 tO \d7IM85=5\M725=j6Cz%ʋE;+99no]FsH.{!DR:~e+8;u?0y~Q 0H;vDA{/{%5#+L /+ۀ <8 ,NUumx?p0X,Ɓ3 +ۀ/À9`90 \\ \\\|)'ˁqLb *:F6x?p0X,Ɓ3 +ۀ/À9`90 \\ \\\< 8p&pp1pppp#ppx?p0X,Ɓ3 +ۀga`p..nnÀ9`90 \\ \\\|'ˁqLb *:F6+~0`8XgWW7À9`90 \\ \\\7[]']lMcT𑃰VoMBƧA,H/-pe1kwZlqR~_gqh#:2쎌L{?{=COvx@UFcH6<y]x|UTEzxY9T+Zi"◸7JB"^eĿ{)A<6[G\^$STۭ{kpLJ⎅"Q*wW)w(#&J.R6w(c}!2q!BI8B>q ?PC i $ay&o#O>/I l(= ϏLA-:\?m xPRw{*7[߹Yr2ubɐCBqgԛF_d| O]<}A#w)zqK7/%~cx^?|(4؞ e:0waUINA*k_gNo¿xƓ V^J~;09O;N?(1~pb\˿ CEH3v׉A(Ge;> }D]D9Aȿ-A/:H!_z_IMe(k_-֣ͮ$}7RMc )$+D 唁o|Їa>=*繆>*=O?F}`я~}M?3dw" Gv():&[`: +PU(g%ʹoeG `Thh ! {(gNmP>I?Lo{~SXxI~}Ϡ#oE9a-_ٷp(gAl]S,,ޛÐ_9XП ?ן"CD~/NE< 5(zA!⻦"+!x_Vp!7}3t!]P 5?p.AgG&kR\&޻,= ߻߯{mhwدEe? A_7dG+z^?F oZWQ_5T|3Y/37* L|O#> shEhiGX]}Gw#1?)޻,SA/jAH>'~ɑ]WZ~ >J"C~[QWc+Őaރ'gv2n;Q~fӒ~h/hz*3fQ<:Լ>#׏Q$G mϷž+NI1GcѣE=>&ȿlAʇO?m"h=?N띡{/fWA5_~ϖq;UGc\fw(c/Ew-_[fNoApz: |z0 u_GuWKEJ}XU~rñb=ZcX>Vuc1~Q䊌?`|NpLe{]dkk/o?EX%?#9{ɼkA'̻x8eJIu>gD;\3(/(E7ߡ#3|OC9h@t|O~<:?9Tx/)sQrV3E |"K('AUr? {4b^U8\,ag)!PxJezry7d}ٻ@/g= hm?YlweݮTVØrofF6/d]g9j.OU}Xu?| w>|=:WVAD#<֎0ov ? D9eRe휴sCx@rk{\0}2ڕT࿢R/3Q_Wz>n]#C-7s^(W;A_Ʃ'@ιI@/rH,2(g:%9g7UO~Y2oG~8]Pβzu:dy9[GYKGCn9PCȿlc0$#\oc;c'K Uw,{b^kA;^|u&Am?q5i^y?theq*:Icqc{u|_5'A_DK8-=Iy$"N6d'#i[mÓ{5c?I }D!C+C`E~j{PڵDSއ~b^E`?FCO֜>+c,D}֏iZWc!7"H{8s<? С?O5oː5䭧{UxGݻ9T{qr§bii30FEXAv+I_g鰟Cs鐓p*|Ee.Fw}c2/[~ȿ-A{%X vgJK,R>A_Z'Wr;z1!?g>WrΒ3monT ý<-e88Rܧe./ Q*̻QΐT=hܙ> v?~`Z Z YM;O1wg֫u>_hNYo?%e"ިY/OS秠OHo? (腳y9I9_] +/Fˎ? v=q.֩rQx.3}s\r@%{B7N#VkaGoC9D9O~sh:G_%Ge6fʹN]/*' U('lX&aOۆBq_rE7,_͏@=/*`7ufWKg7^Ou#-r4Q>ߺя=s e1/U Ev<W?Ae^ۋU{d^~y(gTڢ>W[y/߉J,T~^MKo,g(nyn9wBF9RbQϧ1/,}`jVrE|_2Qlw]~m@ |cy=Z#}͆&G~ȟؗab\sнo,ٳ DxBG>0߯vek)Cz2YX'VGQ8ўȏqc=mFK1U~;E9?0?@#aG o`_ڷgKzjo_XWQKGQA^d fGl2{cHo M7Dm5ȓaQ/Wؼ-7Y c~c'vD?,q/'Y8E?||?ZU׻?G>3t_x-.³q'Q% @OB ^|#],"ma<"'\=V/`4]Vv}r,^^{iv ̷J9}z:ގ*ڭ dMՠMQf7p)YI?L3/iϰ>-&(pmZi{k rf>y*Ԟq2Q3?|.z_oX7/8/.0'=B[QsQi\~ߣY7aa ""WC?K-g+Sט=1N>Ƽxr ~՛q?'.ܿ@D(zn4ȁk-N=@Ly)E=>oσ6%_YOϝ'^zp r 0>!?H>Ї_}v<>ǼU#zje>ۛq>@_y-+?k1ND{/?1^|{'3nB=u/^'v8,F:^O2ۯ?6n6t!$[߿{|`D/6s?^yi竮V]|=΁J , [߀s+1Q?70Doڿ[ł^r#P,<ȿ+݄ˠGSzwM~wd?s>Rm?f -o6̋-iK(x|Vgނuxj'AsjYd_ oF'tþ3g"14,2oYmOȿUh5"%-oB5ש [Q[ѿSKV'g7-y>4 &Zx۰.c/c3n7oo=AX7y chcϽ{cK_{|\G?O^v( vw[܉r ie}< _An<}(vUKxxɰnļ[-{.}o/r\dSK-m_Ƌzn'Ї|89'|_~ >geo%aw[Awc( >wa./}L߃ި/^v/a rlB__7}$^&>++_|/$ܬΗ1.^ L>=i/SUy]!ݴ #ˬlA7o^Cvm?^ *lk۲BՏhDdR/ ,w̗vЇ}T}-A~8lB!Y AK_߭Kk`G>}觭@} 熾}"~= OZп3C9 WZs{WvGQ⣚,j|bY )=IQ|̣?1K1;WfӮ̼K1o>>wBz:u8l8E3qsGw8kU =YN w{$~(cIOa*'RƑP V_ =ȿrexfnwwU[Яj6?ݐ(_'?󪧪>rj4Z{<#C#r^Eg`ggWgW!=uA=|?9cvsȥǬOQ7@. @*x~u/MU=ʼ?YO}^ЗyC?C;\F0#yWv]-챷=8ҾԂ~ -^[~A/n3_L_zD܊oQU.~mߴE}!g>ڐgf7OpnˢƗvۢ_l!WbN+h+/U^5vU<}\a c[?`]nx:k8 ,E-?5B/ v}`Ǔ9PJ5g.tyuW}C{Gߨ~WaoazoBqurVY΋7-?ɫM'{ CיOծ}t;A9A匼Co0oklgeqD}1~<moa5ߊEUVz-BbǾgړ1$3ް;ADCG|y4.Z/7/ys`j8y_>TkZo% zum? 燠χmr㙪T9_V׋>84=;c;'D=o򌦞cs{3wy2n q6}%F F?8JϘ '4ܺ7k 91K?><*VßaxWvyԼ?r†uM8?b#7Y݄b8/zmʗO:O ,3y?F+v?8sna;O8LGsW ?( D#OSu~1 $+rcg@n/c}d^t뗢1{4?:},CWGTyj@|*qWXNjT;?q:@+S|m~NL,q vG6Wm_lwqf@>n}/!~?W?yywق;uj͕>=o?چN~}'BmSCqxg@b^oV}'VKA ']{m\gCn6e+Y B[F j g[&i{p+(m^"砨| {~6}ݿWwI߰wCyߛON@I?7'_}ւb߷X6rymEa\K)ҁuߠ~Ձz:3}y:NN݆so.wΏq~k1[܏(1N .4%c+"~vCoc?qnv3M6+"1痞V+,)uJQR$vhܓ;:eW/捸XKE\ oaX3X?|ҸF녉 }zKbruǗyeϭ"` c/-t;K\gI?eꅡ~Z <+/˼Yyy%dXXp,kDfڍ/}leW]uc˔2l. '{.jǙ~7*f;GS"0Uq:=N,@EFkVJovg{gWHABN쨋*nƗdv;Ń2K~ !1x1pE&wG GBnZꠛߘ Қ]|SF?nocFKN.,('w#GH&2q܈`@ѣEc@XFxn=]dqE'9FV˙u^D;6gaJB8<+7# B.G.gMCR$c G 4$hHt=~j1guja>Qٜ]kNʙJ_pZAAT/5zkk.E(Ur0\sP 2?Ybtߞief#OW8:3qM,-kF jeͅBTyaWrG=am\J^1r2[(e]>p_9qQNP= SjwB|=M F;f Krt"eie=G?1/ Ay^gX%!%pu6UϨg"j.63Y*&f9JXn>`t:2D%\QeԒo9"fwclDq[И PX(Ft֨ rH82y#!_T9ݡj^DYa}G<["T/x̸!?|*P(LC:0tplV0qOb e6AUgs2ڳ5$2i%LbVX.%([ũ0ul㹙}9mޗD3>II~u )|8RfINVnW]?ä‚H"Cr ( WG+ Zco٤N)Jjr +6O-͌Bϸ_zl.W | )Yh3D9kBKmlώ>U%je/uEuW ѴťzI^\uӑ M  Hgқ؞K]"-[4IT)U%L`UUd@gJA 4 hᨬ >xl)lݤ V&uJ,`d٫r2_e&rn.#Sr$ښ\2ݜ ")QDs^XJ`ɗ&lif4rN]33´ =jǚ:@hxB00< <֤9ȸpܤP?&@ bL'(,鮪" ߮s%6=O{e.?[3U_w+D>m!~}]JuEe&V3̍a8]w:̐Ϥ);x:tVXlQ.gBښ2ޥ3pRql(I<%L|ċ #tn-3)192BK5{si^./0C;))i*mgTf xZ߾Ѣ M됫'+NPVmGSEzX2q# lU,BhG}Q&=RjNRV7z1zůizYy*TW9ՖEM5TgrSqC> |\ʢENzG,O~T&>*{;#ltf G4Ĉ4vA% B_tQo_ړYz7٠MeV6&XG\t ȑ.e@@OQ!@\++#DO8&* oaǐ$67q|A)JT:cҭeM,RZ]20)uЏl^.k dC g]y")|?G|ؤX7B* Z13Tzykqg2{jwzHܓr[k'v'cqDÄ[U2VW%B2.f&ʞyUT71Q*6fQ 6"`"j!LJIe%KbEk 3dC}v~fA? S-fF?P+bPކMa10,*d`O@o@G5˶}AxNĆX2',riMDi/姏T M'' ye^khUn>ɓS\D E<{J;X#~:t}G""!&p_]^J3ĄBFL4`LTeEQ  Oj{m&ͦCqIN ژ+Ff*b{CʸfLQS#/aH#^b@~P\%-V<8QZh;ůS$#푦ahZ䜐RAyaXj=wΛrV353w:iH`gX|]Z#`dvQv7AŞE%wtU8@yl1/8b qrQb TQ^R rt2FfUQcE&VTl Net-v/oʙ!msMFRz]T<#J2yT[ #SWza`}vT4*mHFiDRefQ]r:v61ŁVI]vbG"06WE} 5Zii.B nf2'Y{g)5#HY~әW׋/Nd% amǗXÆAgʉG:]%1Łr?J/6a!D.emWgJI)p肍fR+cƢr"w,3[q|k28ᰰfb!dY՝eA>=Ǚ;58!u৑n.hvީ0t63g;-/Jji]a 2TO'c:SW3oiݲҧWYse-; z0k-x8iK 趙e8:\=c^ CxGn8#q*3,Zu#Cġt_ 8 ?(!o1?(dq~gx 1_y9՚2*0ҽ58~r/Ҙծ-==+;)Z}2tNN:Ŋw P̅]N2Zw>wreE](ߣߔfsj[9A*R&1\O!aͷ]ɪdYD:Tz;puԨ"SL$ĸh6%rQ/Uc2Cw:w/ g4)h"=9)БIIװ:f߀*oMEGREW˪ȗ08pXwvסw/td"nk5Dvv\"?[q?Vf ^!zߖ$6*A2DYt+zq'_ӊ,u ܖ! Mܤ(8{[b,$GV2zpէ\ėDi z>D4D1/qB*0`C=\xώn)zbty57!;ǒb`Za_zqv7tEiF|3XPO-7=9*]s{E2n4c\uG'7FBHs\e=:5VR/HSҏAxi^- G9X]DG*M KDIg$(>j|`O(ʳBO%_j-)rT@U Ӏ=['soT!U_OJO>>Ru'egBTU3Kmݷ?ag&j|]hO蹽7G'BBTg62-L+ʲ8VF尊FbWK WRNSeϡ'd l|q;un6>OӰ|P--Vte\ÞLJ8DQEPY>|G M GHئV鉤+Jn̑Pw M9s顮H{=H:r+Z5hl;oa]:Q&=qUJtߋ"DWC̡3ߑc\l4撰U9+*\L8&\8 F[MF[|5NvO8;5$|¾̧.x\ a,8;&`sm˝"'s_J`S>ξ#OFq1@q!2AJ3$F,ٗ-E<=iݎy0f^֥vTPz}{4B'cl}\EhӧX n^!pZ&:=_ e<@s*e `m;(vPT+_f{\ KDz@N* ǜ+C)H01WRi\tR]J)}iuS& oƴfc7>4IYiD$΍vԓ'uJ 3?b\1Gt>4pJ$5.7$~v@i$6>mv rX8cΰdĕdD0|n'W$'^T>W|eä#H㊥FxP k rhA4:ˑ4;ߏ+narL=)U*BV9R}DM#8wowXڋ":vkM݄WA3dȁ Hwr҅a'GMS㧺,dMH #M8Er]ObJ a;Nr* -@oF}wBi3YYI'F<;V _pqZbܦ4\3lۗ /w9%z,ݧuC̩E?+wBt+г\o.TI&o-Nʉ!YR9ss10y[`.)i82'ZdWBʜ 4&JY$ՆNO?E<6_gHXJiݎ]M|#tSO#`}쾛;kNŭoān%WN$Ty5diW;]i`/}Q<0d!\SiM<XWC@g c}n7ƻ6tBvkJ9YP5DcްPM!bvؤ{gMDЫ40NѺ:vw zq+ۊ/q乺ލBit70Zw|`wM ^ ҔD;@s>θguc)᡻8kFwR a.5dFTVDPwLWNcd8c<9'0'RuWT;j_P\>8 :LQj:g;c# Pр)"![E}Y$\S6m3Iwuʮ.PbEE[skŵk#&NܠEXݤa=W̜uןn`mI6_\DxB*Qp˭:C6%i̮$u %ս9iA׷88r1ԟ,f;҂WYVO1cg;aCֆH4>Hx$ĝEV0x;1O5H>u2ЋKlO{[Ğv^&дͬթp׋>vWG6y 1x mqIlQra\,.',Sk#'B01ZCnp]ّ8N]_zv¡;/98w&]MikԻH춲we)|]Vܬ8Ĥޠ3jgzH,o0nURrHn)l=3q;-^qUY0r@WF1˖4Bn3a\ !k(^}lKxȅ qe\ܿ]ĤsE'i;rn,92Aٙ="i.lqˑ= ;ku2hi4PC! _$gl8\҈'xXHpv8Ї뺽w'z \d8""s&17if3p/*|v8HHB:;7~ġ{;]#@!y꠷&Le{=x"WÞ!* udúqe-j-U4ANrfF2Y5MUUQQ\^a,,e,/5[g FSlG@.ƞNfuPwn@錄04é.QZ1N)bg%a)NS*uc\j齟ILEIa [F>JTXI,8SrmꝾ5q5!,ܘi$3յ(ӲܭPYB^dˑJ2R~*LS;u㱜>ɎT=**(8vnhQ g-"W,!f%oG5D0ucƕDH3g33O,J4ŻۣLMMS_؅T"kJ+f[;@]1#&#dt 7z3Yj︗66~ƴՍ`;Z8# !)>fwxwzƌ.רl8v.<1fǝi( 3gz]B>bd0?LYc!/BN1֮71ݤ#$d3N|{r,ef+.~Vn0C*Н:dz+J{.}tT}l8 \vQq *y}!+v.; f)mqx<gn{f+dO k*0|QQb݉jZy>[riDpJM|?<9%x>bwlίM^ATpnN 6N945<?qn25:|ԽVn 0[#&Zfzd.QXr }TYb֧zY4P0j4L{ eWytieHn^HggӪl7NYl̤QWN{')ЬBG-2rYi{0>\bUQB}EcEN \5‘$,Fa*Ҵ{^T1-t]${ DGMtdF}"uwZtH 7ѣ8)5+α;3]6,a@G´6?!$BeD_SNEFr ^U@B;tOd\D|9;8?#0nNptN􋫲G*K,3}DZ؇xot%*LMW, /|.!L&:,Cd}IqLK\Jxec1}!8kziq%R !w^85 c z0+DӧA; N ؗ-#i^vN6ؗ:2)v\)=`l/^#]OuҸi^\` :ZdELLVVI& 2ڣ3)e;[1g`; LpK/]<1i>l'1(,^|b.и0>갲j$"{nն(^b[ȳU BLܢ:ervZ,WȐ` PQSÎDCA.lq>>?lmz3MN%$t0Khs/ GR+ÓdvqA<5(rۄ}^U~zZ7%ݦoP%&g@9ċ/0ZI/eeW9ઑs ҧ}qÐ?FoFD|N=E|ݤ>~W?7HW0ܧ9cvUIY?uyp_gӤH5VtVF4"LL|+Z/ 9|{ L+`;< nc;9%I^P N;o@4Au- N_׾j.e^=D}u[''Ы1 _M^J\ۥޞ,XYݺ+%ؕa~͸cn=W:gRTi]|$BboIH{m5#av]8 iVVxꄪc)ː#;[a"Ž\ej~>MӌMhU58vܢH֟\QEɶQQh]\%| Lg4'X^c<_TDžTJ Шm̘tfg;ex򾸶[KdZ'7˙^XTQdVcHJ#bLQr٘2>+ \2q.LSBy%oz_wO8 7YgSú-┕#μ=@?¾h?v`ڝf pեƘْ 4eRQoL a2[pi.MBQmt'kW~̝&Q=W[tKm]%8c.o$kWw p݆޾ÛIg5uFtfiqNgKj Ťs#43s=ECJWT')6!9MF"{M4]ƨiįzq_wS| yйיvgvHG8lX}% a'k/ Tu,/\i=zbw7XFX"\)Im)2qNL~"q |("o""pil/L+_.7 BAK: 4+HT릗}{fޅ1އ7{_-!oM/Ks}d#4p[).Ej,L27N$Lyxhd GmOHfn6RbQOgAi1H5&RFI #:ӐJӴu1/L .^1f'_c?R9jrO@J{u)9z!.d =FQҮ)BS%΃p$k. &NSـu3--k-ƾ`˗4r^K *&D'W6}^.}9|ST+Ls*7eBړ@))vj_x75 Ye *e4% G'$SZXgBu>ё^PkwabKSbd ͸}}_$_?{fɲ3,v{Vy@{俆%ῤX5ԥ3*,>ꨣX;? fastnetmon-1.1.3+dfsg/packages/CentOS7/000077500000000000000000000000001313534057500176175ustar00rootroot00000000000000fastnetmon-1.1.3+dfsg/packages/CentOS7/fastnetmon-1.1.1-1.el7.centos.x86_64.rpm000066400000000000000000004641241313534057500262370ustar00rootroot00000000000000fastnetmon-1.1.1-1.el7.centosT>D ,0@ba4b213ae9b5b2cf87913f58fdb7001a61bd6739g< t6PX>>?td   48TX ^y      & ,Db00 0( p8 x9 |: = > ? @ G HIX$Y4\p]^b*defltuvwxy*pCfastnetmon1.1.11.el7.centosA high performance DoS/DDoS load analyzer built on top of multiple packet capture engines (NetFlow, IPFIX, sFLOW, netmap, PF_RING, PCAP).A high performance DoS/DDoS load analyzer built on top of multiple packet capture engines (NetFlow, IPFIX, sFLOW, netmap, PF_RING, PCAP).Umyprivatevps.odintsov.comGPLv2System Environment/Daemonshttps://github.com/FastVPSEestiOu/fastnetmonlinuxx86_64 exit 0 if [ $1 -eq 1 ] ; then # Initial installation /usr/bin/systemctl preset fastnetmon.service >/dev/null 2>&1 || : fi if [ $1 -eq 1 ]; then # It's install # Enable autostart /usr/bin/systemctl enable fastnetmon.service /usr/bin/systemctl start fastnetmon.service # Fix pfring issue with library path echo "/usr/local/lib" > /etc/ld.so.conf.d/pfring.conf /sbin/ldconfig fi if [ $1 -eq 2 ]; then # upgrade #/sbin/service fastnetmon restart >/dev/null 2>&1 chmod 700 /var/log/fastnetmon_attacks fi if [ $1 -eq 0 ] ; then # Package removal, not upgrade /usr/bin/systemctl --no-reload disable fastnetmon.service > /dev/null 2>&1 || : /usr/bin/systemctl stop fastnetmon.service > /dev/null 2>&1 || : fi # Pre remove #if [ $1 -eq 0 ]; then # Uninstall #fi /usr/bin/systemctl daemon-reload >/dev/null 2>&1 || : if [ $1 -ge 1 ] ; then # Package upgrade, not uninstall /usr/bin/systemctl try-restart fastnetmon.service >/dev/null 2>&1 || : fiX=YAAUxUUxUUU4c4a8e3eee0923f53669e57375bf91fd5d60a08d4f204fb44048182996ebe36cdb8d69dfbe4f98c6e225ae6924f5f7f2e83647c9457ce72975e90a40f26752892a5e26c6b822aa5ed8f5e359ac19ce5224af6fa90cf3c58494b4a7ea0b6e3a660081836e3eb3b36ad3fae1c54dd2a6f60764aea7e4f23b6621cabf596e0f691brootrootrootrootrootrootrootrootrootrootrootrootfastnetmon-1.1.1-1.el7.centos.src.rpmconfig(fastnetmon)fastnetmonfastnetmonfastnetmon(x86-64) @@@@@@@@@@@@@@@@@@@@@@@@@@@@@    @ /bin/sh/bin/sh/bin/sh/bin/shboost-regexboost-threadconfig(fastnetmon)libboost_regex-mt.so.1.53.0()(64bit)libboost_system-mt.so.1.53.0()(64bit)libboost_thread-mt.so.1.53.0()(64bit)libc.so.6()(64bit)libc.so.6(GLIBC_2.14)(64bit)libc.so.6(GLIBC_2.2.5)(64bit)libc.so.6(GLIBC_2.3)(64bit)libform.so.5()(64bit)libgcc_s.so.1()(64bit)libgcc_s.so.1(GCC_3.0)(64bit)liblog4cpp.so.5()(64bit)libm.so.6()(64bit)libm.so.6(GLIBC_2.2.5)(64bit)libncurses.so.5()(64bit)libnuma.so.1()(64bit)libnuma.so.1(libnuma_1.2)(64bit)libpcaplibpcap.so.1()(64bit)libpfring.so()(64bit)libpthread.so.0()(64bit)libpthread.so.0(GLIBC_2.2.5)(64bit)libpthread.so.0(GLIBC_2.3.2)(64bit)libpthread.so.0(GLIBC_2.3.4)(64bit)libstdc++.so.6()(64bit)libstdc++.so.6(CXXABI_1.3)(64bit)libstdc++.so.6(GLIBCXX_3.4)(64bit)libstdc++.so.6(GLIBCXX_3.4.11)(64bit)libstdc++.so.6(GLIBCXX_3.4.15)(64bit)libstdc++.so.6(GLIBCXX_3.4.9)(64bit)libtinfo.so.5()(64bit)log4cpppfringrpmlib(CompressedFileNames)rpmlib(FileDigests)rpmlib(PayloadFilesHavePrefix)rtld(GNU_HASH)shadow-utilssystemdsystemdsystemdrpmlib(PayloadIsXz)1.1.1-1.el7.centos6.0.3-91543.0.4-14.6.0-14.0-15.2-14.11.1U@Pavel Odintsov - 1.1.1-1- First RPM package release/bin/sh/bin/sh/bin/sh/bin/sh1.1.1-1.el7.centos1.1.1-1.el7.centos1.1.1-1.el7.centosfastnetmon.confsystemfastnetmon.servicefastnetmon_clientfastnetmonfastnetmon_attacks/etc//etc/systemd//etc/systemd/system//usr/bin//usr/sbin//var/log/-O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector-strong --param=ssp-buffer-size=4 -grecord-gcc-switches -m64 -mtune=genericcpioxz2x86_64-redhat-linux-gnuASCII textdirectoryELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=0x4d60fa0ca896c7c53b3a70f8e2dc5694ac854085, strippedELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=0x9965bd51e43df2a4bcc1b38386c44d1481282d1d, strippedRR R RR#R!R RR$RRRRR R*RRRR R R RRRR"R!R#RR R RRRRRRRRRRR R*? 7zXZ !#,^] b2u y-iSq4>H]f%qϙs"xVl\i^&kW#Q`Vn'{ؔN ex'CbV5gU ^eUDKN͙\!~kϋ`|w{zS>[7;UdG9 f,`H4z%+9突;.Z_t/I[APpY9«kM3SE6bԩ7SgS(5"-f_R^E&TTEDAY`oe-~ +To&v<5[$=04^];ioɬE}n,|+^ˠOِXh5wd׃r5!Q)aE4١q_R0g<ͥt>>+'hKv %+(f V}l#wAL. j$  +$A:A/ $B=o{^waWV2tmaK/8 4^9̵y?$VrقGu]IFB~tP%;5R4jc`U2_Z-JK @Yٵ *!T4h\?n¶PrD`W^ Ƿ*1L36L[y0R-G:m ĸ؍{[-ggd$qPWбc0EQT ?җ8ɋ^mUlc@Rɓ- #ܖT,UvJw2XlE ?:tF|GOvVz_H6Iokqxn; q@U~oxBQjV=[!*9sfCrQFˏx~LsbD{#7W+$S'Z֫.8#tN#QA kA~Ew$}./R/]3X< J|0YK`IF0 Llx° }94?ULLܦ!U_O7IFiґ.|N̼[:E s19Z>X`/W)JT%n,΁ڷ\诗)= D8XXjc wp4u{@ 0߫(%J6aB?9ȧa&I $V xdo0}Dru6Z׫Nb]%Y0v4[Va'ZQED^זּ4_أ#WHIm-qߺ làLQ4/^|6uQY.sr3d].N7Q! 6>oL%zvᾓSK)O6G Qg jScꋷLE =TfUWCWU,g!)|_pm P[t[yNvKEZ8t׏5x|"\ \OxvΟ^F#~ ]BBJYLuyܣˏ+9gZ}ҵ7[B/F/5VwB'd<( q~@g+#Zýo澌7;N |[i]é%Z=-7q(l@\>Y;1₲"^U(Rc@Y3 EvsO\9'5:|=E(K|.T\g~CdssW6ԿʖƃUcL HZ, fMGziACc[qn+r2t,齙jF@vcJ;$!@.hh&j|Qݔ~j)vB\H5I;pt_`KU; CL*Ι*㗢-{dOJ}H[ٯ˜Q2#㙕ʺd&[Fy^w6g >4@s- ia~zi}.8y z4>)%*s11+,=VCWmi*j጑BPkIh0x(/uP0l>֗ Vvob4BKL?dVM RkZb0I)Ki/n㕀0:ݝ>'ԳJ6xQJ!S& /}˜9s[ Oi,?jb:eJ ЧsR:/V\vMN)q1#L o CNU} w1ܳ[a%oIlkʭm.͕^fyFٲ4_:(B8>aB }ݕY yc,T3w5DA yGAq>zꒊbR} ֫Q1,҄^j`}bBfuNS+r-g<'=;;{3I|x F;Gί# ndu} XQoM"cuڮq ?ٕJUrNƯ9kZ|2QTz,-8Qr.,Qn [ȎTQ)0ZWaᦇ-+g3oi@qw+E#Es qЯ~6$gtCF ؗ?sb_*$gG\8zIHjJņ̦$B`-{Ty]n,.j)8P~|]C3BCD$\^͌<]gif1jvN*uDImfLG|#[ K1ؑ}׏0Sq9'&)?4.[>PV%ؠGY#\a/zѻ 2"i=U:A4>՜;vJ|lpGMyCfwfq H^#үgDw.Mc_θ5Fm#%q,'eݸq&K#Ij<}P([ tG@+Ŗ~"ԖI/øD3/B68FOHEJ+S>|\ N`uߨiD ')|MF5ѯV c@gueL7}>W82VW~8C/mA)O%%A|_WZdp/HMyE[0;~Bs}B&<A]:7$-L pϺ{Mg<,$wDtѕ šOAVezG/=,mA-wI2uwOLR]`R*Z]d29N!Yuf`w?ܚմŵ<#JaI_HŅbT u/JY~cNӉQ7r~A~S{lee/?'pRoO`/. <3#n 3 } UL$!@ϮwVl5=Bj4T:2\vG* {S=AG;p-BWL. C.H<ɧZ`_ |ҭ<.q@Vu4}ag%Ϛx¡ 2bE<vFj9c=}Kr/׏S$S:b93e#ˊʭEXpӈhL}_POm0in;oԚz{5l^} bp y_b zȕ:n_/DZ[\^R#4=&b!DgڑHto cֿ|@Κ._wOډ3ii3QB r9Fd_/y8#p27 а3&^.UULFh)iŖlzzQ륐:C{yC#$_s!qn\ `aW,yI&u"ig&~3f[έ/4 |iKfQ0Ԡ㗯/gU;0EL*t}EF$Q\7blQyҵe[j[Qе$`=4X\^8CHvo{A5$g m\$h2-k.[^ߡ?"[f/rF #^Q@@xeQdUdΧ,sPoVP1nT$=`ɶG#=1 śmFR 5i ;u2hz3jɀVt%6 ]\yfҪJ]E!B-'b-(NvOeAՄjpu?eD|0fRZ(>\z@:ԛ P g:n}xzZ ի0̣%˳zrD8v<^6?~C(_ 飃= \bɝ 0H@zyl <1GwH2z@G~K>a,f<O"TNV ԔZ*.4>!a@Y3:bh즷x5} VVHLpǎCI#_}WKf45Hӝ<J~! m3 W ne4$L$ %gI ;m|@=T;A5N(0g/f[Y/phuvҼ; 1Bj~֦nrZ_@WRAJcb㤘(33 =?hNiU4Y ?JJW]:*ze׶ڋDiGx!1~܆f)פdz ȸb ͫh.n]H4=KJa.q.Ƨw:Rƌ7רp{Q?aX )-A5Z"A&+stFג)n#t;H<6U6?\p`x0/:Hc11p2acmV+Ty1)EJ?jq9QkyovƬ~kXO!!H1gQ9J¬\L_ t63-cum .K@.oL(Q!F\44jzx !u׊HOiql-YJ§&86C{DUxtjL@-kFp2=+6?3 mV9guuX{=d v#mtZ&A5ƦBeu 9-.uq?;)gk/mFZ/EО#K)&7|'%LƘ8JtZ) &VoNRTӼ'^GK$ ! $?3.@b[O+0=ԦAi.=B/)Z4T᳘wr6)e ̸-5yf0KFo 4{S#,!x5X`S"'Ý IjMzĪ4\ٱޭtpB֗n^]2s;b]"Ҟ[*gW2x!^"sv!Aɦ /<{f+0m~Ih %+~ĹA 6 xxՕ]Ꜣ'0"FktQ>X :$5uwϝt3_i ,9riU(P)X}cr4WS}I W\P@rX O2i|ׂC>e8WCjKrY\d-h$ewb77(PV.ƿ X=ؐ<#/i![ ML% +fq°Nf֬N8![m2,\o126 !6&Q,dK. yz9kCt8sY8Z$t v*D[>f1`ߣ̕ZOPhGGP[X܇<pБH=Wu 9||܍0)k qM{񄊄pp'V#O6&'tqRNS۶ IDfYhȬh?xM%*Z}bC_Jܒ Q.`X'cSTҲ£qK#Zli*0!RD* &xJ7rJ?)[3/ brr[E55zPK[oXΑVVVd[QCC{ZC@2dy>jOuf{j%+rdfDLV^FcFts7=] ^!$,F'q}59#/y97/}r\~73B!}!B&REz['MF;Z*,DP5B=#vPxD-:muшUBH̗ӊU"^3orcgO_v<͏mE&uLxEV!ni s.7C ,ܳ*n3j_Ok?W01 m4P(;#A61HR@+s.Oѽ٤@ܾI;r6݅1hz6(4.lm{j[4qRE|sխ&2`V'j[o_RfaJrӭhnݐ'bZ o© ɋ[O&% U^0 1B4lq[Qz`_a(awZfF aWuOJ\g$)tM/V*0 .}\E՗^3)Ɏn` H [Xú Cj;ݣ-v&Xzc8Old-c23ͰԳA *e`='JKt/kͥbёc,~hC_^ad(IeZTfj[C5ii00 椩W*f詌r/Jnٻaꪕ%s7QLM Em~$r97>Kv70|*!a/6Ht%[ e k{ߪ@?z%( 6sc7i, ڏ!gUXKw)wg1;F)i83HCc@<jЀe+,/.Ew=95Sċm4QvgDgqos~?щX|e N@sJtIZr R_4ɔКoq\:ũ4g1^NX.v!XHHb顸Ѳ?p^> I/Nl'iAt56MW t5 /TX: O5ą_Rf#JX9Z%wd'"J_uIqddbq50hFeNUS5r$\o8rQZ1}m% : "WZj (w*hglp1YU)3wJ6Z?5o@}l/G!DRc/|%r+9uQɦz^Qu$; rXJ23#/Bx! Gltd| 2;R?кt>ܞeڍWDՔ)v,F j4׬i&SSpu7LNz%c@S=;6vCs}L _D[mXBJ)I#{Ь; &TRdrٮT17LeN> ?A0ϵMދQۤ (]4\ϙk-c_ @="%s_vf,ZB&4hIMU\V{6sKbwMe'ݵ:MXw YChq}$-MEoF˕Hjs~ Z>'%>~4S^y@YY{*Δɍ)"[ #ĠŽ3t`.j, 1}y4fS˒_duտ)|sޛ쏱q<6P}:l-Rŵ VVѯFTGZO?VZmF* ΂8`w5EY ڌÑ뇎(ܰ r=S@Yu<$} {<;+A]EQ~{cRŧ9\~VUD\jd/ړ(f%sk7&>~`G+llH)HRjxMƢp`L0 ȓ9kfR5viÓWp9E% T!˄vВ zpFFM8Xp~mAJhx-gȧ n~.`(9̸\xh(I9nR~ aI<$뢨hͅڇΕ&xЈLAx\[ݧE#ce_n>jF  /*$#?9|KD>cq&'"l5BjBP+C7H _ěQ)Wb]Bb@؟tÕU e@֒)}c5}7 ($7, f] w~ pl4ӵg#; t ۪0P9/?Ly^dQ?\X?\HףfӺ~.#3=/x1ݵ,0/b aNĦVv XMT=ŋO{:Mg w(|gy~INz5SܽY-(o(Zѿ{i2ut X]XW ),mfYrVmO`v:̓qQ;>rMybF$ 0sw;zI0[DJzGobMdOg,/ve Y:QfaLIujJCw;+ZE?}@ b@--}D ! u1x"Fūձ lJaE}#27m]qs6daY90 Gii;Je{ENع_g]^Rc|ŊD%[ DIjX_!l |` 㓠_}s(U9 F Ш%ja=d0z('o+UgJ l2߹va qIXߙi2h'#1{]/Ex6p#psE^[\yADδ+8Fq[}V灳L/<,M.# YnVϵ ;{vhH̘+X+trI> \?IǭJJNfܭ f_s}A Hc|BM ]zІ7E?J4׿"^SpeZOЄea7uQj|wR8IVA,~QQ8~(H?qvi֫?&|k(".3/ǞЊfH cLDVr60ȯkX.z-IlR.9LAIq2kH B"Psv0ۙ&)\b!gV9$o)'/9V7mE=x{W bW\L qv~C#C OAOzPᥛhvx6EE\mӭ#0vYSq<'}' ,l2e`nyrp]%?]X4.4u5_XgNkfh,2:"{yur~lV)< "UA逖c֟>΀Z5 :~=;ݱYIϢ?(e'kcYZix2" JVB.u@ V'^յ,s.^Tμ;'Uh 3a42inb 5Jկja&]:s9|XB#O0J:-ΙE7fi8Ṣ]8DO >͕'TV%2"-"_jƅ-Q*vy+J?e 'Q܂v+y+> "q'|sP:LvUn̋Hz%B߰偎G_&\2toAJI8~:)A_*`^%d{<*lRd##tu_ri>B–m#RA9ܡZ$!PJNEfH9[O ,ˊA* I7Bbj@"%w`eMJ'@@+9X9s+P[+ Ʀ<wp;=0ukf߭g5TO#>9M4asğ`7-3׃<"gRC=,k9/,c+ o9YqUeeAѵ5F߶o9)\<3<˂:Z>-k"bE!EHjg0 Zm 0΀hbFJto?)V7znP[njX&{ˀc@\UEb0%WyO?k#|дmCGcGX bf"F0bwHUuM0#!k K_/mDh4Ծcg[>36 vͯٸd<׈1x' 4㛈ׇ8<2,v2\9ō8ǤJlJto'9jC3XVsiM(ZwPf\Y3y InmL 蚺;&+oini dA䆧\QoAbRJ6Vsi*qɔ4SАj%V؍Zmo8(ԗSeL' 6Y [jd-r+:TV6z ES 1|.ӥ!zd:{kj^.%yא絊]sH dz$l Z"xnQI^#~@O (0v@9|+\$CMu7J0{6JV>'WijK8AIb[c~Ɩ`ёWlR,^=I*" a?XQKU+Ϫfp"Uܵu##)A:P>Dw)>FƏ][cIXg P O)+Ǽ>j𒍖-mL dtP*'mActE&MYhtR&ީNt5oLx#A 7 >6nL 6p` (X {9^O NүSjkS"_rnVlz =a/0*|o⛄KzgRaJнI3ioOlPwI8>#~lGq<_ nƥ8LaoEʞ +lt<]WN#4W+=xN ~_O(u,n]a;5nl$nK6ƚi6sY!z!l *h ӴDTN I5uudnU$ĕ5DSNKp6S}[aqsgNf)em1 ZNO7[?pji'bW׍Kpw\7xm`#:g="t+|icByBsKX'@EQ>cJI$d$8ƝùEX':q@we%v(/]-^`gZ/sva2 +6CσXP0PxYq@O* t0`¢;b 7S_#.yiԬfcP܉F7Ǟ~,<ոqa^gk/(FD#n7:] XqQun-(Y+#sW_#F*םaEwY=om*CI~bꚳ|C]]i# 4/)i 𥘆9НLij,DgĬ!P3qqb|D/AJe!~}|7!^5\BI": `֓C|u E7إ9Aq&7*^+Y}qw&ln,,k\g9g2ݺ'T((!.D~ږ|ʆZYzVLJ/@#:SqZ82 $=$#J~\"*~9q؇o6cWEb+z5p#_$ןϐx BEP_q !١liZ2 L4G./J <a {ϕ* mrǻdu*2ajAI' \W|x ۫ﮱX#sFMe.݈Qr.1·^]ݑo0I-w{ +y{^M`Q;>M=UBd7TstfUbwrs/ "ȁOc{ ɋ`WGH/`G2ޡ)l; >yE˳cz,~=Lys2#)0y+OY%+ :wA hG׋aT*;e(,[0?OL-d~4FU 1G[ɛaDQ ;~sW-VCK0J(Ԓŕ_n HvkA}},C.,VIF qi-.MW|Y6 ܣkr|= 5Z|Y0.]T[V-T+)R@Ǽe "rΈ'"TZVK,omMVE;j)?3!7;!3$72qK744lv>1{#]2?z}"UѨL61A:Ña+冕ڞF΋Y7 dThe$uk}ZmG,kFZًsM_#ˌs`|kb5]i+. LMEVud Oz/hF=,t^H+ą'٦ _|C`e[A ^m ta~ЙS5UX6/7%cBΘ}u/M9k$+]IeEeю u[Fe󄮤>EM DObk_΀KMiUo視mϋXDV"kT+t[+,25bFטƑRfuf&vpv8*cm8/eN7E+Së#>0N|c(>} ZWwsx5ߥNdw`Q9fWFA7ҹi'UpN{5W|J/gX|j+_$#:jA{or Dc5uw︢<1KVR>ωj*%"`IUW2¨Gmj%tE@%F[q5nt _WF jOr3g4/+F"diwpY}ۢ0~YXr8x$PY~^My߮W_y K`$3 kAOc֦YW7T}:) BFq1!ƻ 4Rf[:;Q{;8bw `;V cV͸ .ʾ}:HDcVZJ,וluwh=*lrD1tFf 9>[WlԈ:.>UT@B0,O3Pk9ॆJTx$MkՍbyDO)6O/I "!pNd[>Ù#(2^f*9<&w;N_{H樷 ^U{2 `7:5ń|o[Ëk{ KoI96&‹&q?rI6Tz CoLhi#(th|.8(N>Ӱ˩!1UNjH]<;6nG]&vo[XY#ABʎ5D{.UGL7?luaZRqwY@a؅'{[;\346T_-Xpgp"0^- +k#fݿ5?NZ\ {ͩOm̊Pᤄ>9NI+>0?Ipò~cD*Ҝ:מrЪA!zH~T{\%uh2k/ ;>q^ ˼C(: .fY4X4 ^r<7Llh-}h0]h Vu 9X H<屜`t6kBQ-{EH jEo?8W"3a&MTh sKM4BW͗e ^^Ao1bbǽ8L%B~7Z"+i3+IX {/.u)d)w̔͡ M9L;_!6cB8a骚av}y3ྖ E@3;7L18~wH=q[3dS_cOd6]]3*MZQV7m =zNJ]X"8u$oSRkR["y pDd@ f.8>4OJ`ǫaHk)*9.(qN8LʄL5L? w'QdBAsJ4jChx9Kp&aH($V$:Pԯt:vS3z57:E gk\}z^𑏫W e֨hЬs O6*BI$JT|5(Zͅ?F.vNKGZ7] ڨf'vфƷpIgȄ%ET> 8. 0kȞKXeAjp?-.p$lk~"鑏NI8n= ҃ Fb6g牁6\9Eo+L mx ^{u-h Itn>~n6U# VPB% 4Ŀc} T"M},CNm`AVMiBgwLf_2~<)ӆe%pz*l}{ S:J Ic)~*f4FUñ/nb[SYhdQ`2۾ʼnZ-C'0@3vA͙6>[A{v#եI}xF*`vF&lwxL\:PA#[9ɛt0$ "<~ӵIY`xbW B^]y.QxfKN#_5LӁ}%ő==l{E^iqAnqÖˋ#]S' X=%u놦L/՚#PZg<9p=VZGiЌ)lY7'ºt[V1;Jԥ;>)\xA4GZo.\Bv]cB,K+_ Hd;zܠB,Ԗ7@wWԹeNRxtnvjʃ x[G)$oJf"rW,I쭜.A1^[H( S0Ƽ)ՙi(`4))L!^@(ѓJ##1YZ2ߧKZ@G"y$,8^3wvEbq /5؄{y]`h %v< YP HpY1A-֩Y(V_IIX"}T5# AT26{~":-\UiGzĒͱK줐R>w9 es'7yU_a$-Ww D?R6B7ˮvq_w !M;+ բ9Dovg E_c`JGGMBoal 'GӉh^3Ĥzݶ"8LX-q)oZ22W(۵ADA2ڮPEˇ񻼶x8W+L Ůae%vT\fDI<ȸim+3)^-~*/[#uun [-5b7B"80ڦ4QY^˰X|z ڶeSѨz;rhzvuO^YPO=& ,/fVMr~ ele6வK݈湖Ȃǁ9\> du3/B}2-=%,.<%4!EYN0b'&@?Ꙋnz!w 2LUXy\- a{]9DhFEu$U!5mц;XE"ŽU 8ucLzתG-zi X_/hQ)Kaef "qӜ c6į &rsĖ4T U*hec@TeJw Hsw v3zliJB)J28Gl"2 sfRze!`<^QcC M+oFEUǯԪR7$rz'&V/.|^I*ZPZHow8xڶ{:f.$=R/@u-LPqs𮧀 $5-5" V|8`fۚ zn:<]EWnȏe^/!mnDac%:y{Kq"Ea)aqcfCDrED 2 LOAu]$E$bwyɰ ~W &` ц q첓ZddWA/`u u(=5F7h` kS"[-uVЇƙoL^($^ݥtLjLʾ$ͨ8*e_IqPcr*o-2AVlNY7߹D٧C̦.r.aR߈s/KhE/e'$WٝEżЪj4=V.W~K7s)jV4eHh^GU>R. ?Mӆr020wI( f}ۯi_D%lTse:]ä"eqX7NW4vT0^z)!eՆUμ ]<]8wLT`vQL*@[p/B&=}u_b(e4{Tr%B? K Fdѳ5ݸܒV$*uU0~/.Ùw#w~$ky0Χ;UvO#t94{NGO`7,l÷`-uks==͉ 3BK+bz$倄tȺ/楗2 RV@"0S ƞfI*&qiC5).xM'zhOpgטp#ɪ|W5~/KWvdu.^Ձ7Cn]h.VY|,:;_97?QЊ|ɡGMf5s!h'Q~U4LVbS{SgZ@{cb rmB":~:By9?v0,#0ɵiig}3dO9PxJǘe8ɍJ/M5n!d{GeWσ?#ئrێB(]wh Jb716pYx򌷁0 6%3EḳQt+o[E +:.ʯ%KƔiGfZ-DžῙ'GLN7oY x%WTz:gX)wcBL:9M"Hk ;2Y,JyNI7=q!ȺJGĮJ†KtjTzJ ,/WQMmqk FL}I[/wR6|ݷE&[@ o࿿_$aͩM1lg-SC "$k7Lo 'y7v|Ӯ\/1.*]?TQDGiVrl4R7r=QXmG/[a*F9Rǻ~-I bL6i\}v=du/!2""f|~zw-Q׉HjWH6d?=߆n~OavvaGLRuJ*봫RDD^>OQM.W>2ׁ:vG3JePL^6^mVѡ{ovgXvyi֩\’SS/U4&w2;B~e {/ "<P^Rj"K4=. X_mv'!yГl-cW]Ex]'iRGndEl.{+ $a}T-sҗ.LD;F 5R ou}{GL>fqy%VR@lSbMm(o;JhNJbա tuN^֥uά`^Y$Z!L/sH0~n 6tC][%G|f7}>.D*dvqҍmL & 7O';ZH<%L\] !Qʞ V_8^$bORʙ*?7˴QFCĤ;M8%&Z ־rF ehESFLYyPx"s4YRg&Ġ3߬`_]@K9<@Z!YrS2:F\ *$R/e,*~ǡJD"X*)]V3G~Ѿ1&n`gL0Gpcb>Bğ*z/ 2-(k7p8ʸY%Bq͋&mx@Qc_'=CbVǮkz̺S^%:5WwCIcELiv@5Ҭ3 l]_gSRS࿹jqPs~!*Յzk9ݒzWTi$aECbBs9[wn&BNweP8V"h`a Meiw* u 4NO䕝KNOvI3/ !G grέ"aY3B`AYoF qEyYt.Pm b<⑓߳\ HOV0(P&IYTF&&Ⱦji ũ)9ydޚk\lE}Kz~:4M,a䶼[|^tuM/ζ6U#>#4&b> CJQtknPHK>Mk&5NCPn+-/JGڧqXnj% wNrTYfI(^&0 k=Y/5D6C^yf\ʀINSҘx4;4#;\֋K:NOb j)(ʖ\) yD K!dѨ%a__V6fA+!]T!>zj4JքD@'no m4АŔ3i\}o Kw҆rDyX6Jyϛ8MJ1%O":,FąWcz.dF?q#l[vye3B$b^%1W|xcPBY~=u BfI/jp=rX&d|p7Xÿ~H-,ljӈ_lI9WtpgȦ{!0Ч%zjlq?eEce9d/#l Y`m=ppW'7N6X8 0֘|0 ءFQc=Uϙ?è!225<,k'ڇ±<a,5LG!'.ܗ?#]DWnK%ֵp# &$He?;ೳߘK D/\SeLӂr;NVQA.=hk%oѦֈ@h1 }}ۉ"Ѕ䰄()A֌ &{ֶmT%m"g,:$q~6v؉F6 stUUgtZNVNתČpY)# 3^g& ǢM  rS_ʫgOE/J<Ӑ=8mtÌ ә8ŀZC\$쪙=rYJ9 gH]*g'F~ȕyCmH_aٳ~*-G!u G\كγ1!X 4#RֵJj%Q}QAG7Qiʋ`>1K%;\ wNHu8- cT)Ӹ%^UZf,eڪ 4\`Kqs@9zb2oXÀ]+Md?c(LFB6$|^b} 5ۥ%$ 5:]Jhs6e)ѳ`(*QN=z{F|I0_);[x>H |du%fSlڱ[mNK̑OO@ej5a`:J:WͭYT N $6rn Ο.b530RWe;`Ao֮@]wCT>#͝ܘӂ2(.&ӿ9)ڽ L|[T`L-3.m/ejAhpob6pS[]H;"{81Ģ"G΢ _,j>!I{˘:Ӆ Ɓ.>/]"?骹/փt~6Zɯ.}xbs(Ӡ!:~Og}P~Vm]@b?"[@Xkt<+ ED;X&O |n[F<#0vk B %Ə`7Ȑ۠JjN;Bcro˓w<"KxdttG3fUXS& GnY{Ư(اŦ84{ڦxHY+D ~0UTs EU YU AێvP>e&~r;DaV5+b\TADBC V>?ᐇΟάwj9@HqґPJ e+fNg.KPQ'>L+GDkuAT769糼&H~]=Vͣgo=#I>qwhJ)AC@UY)S3 jW"Q8dF FgDϥ0 KY"#`epRa VU8in\S~zǿ_F'=ZfUJ@ XPQWաO?sa0 @D`ۗ7IYpbF^JV@P),Hddl3/|<@cݢse6w4j 6CL0ɃCO%1!IOX΋2 G_]Wz g7BjfJ"\*:UpXqw+#KK 4Tc@ɤi1YǤ vb bpBcjp fd[h3:LpZf:y9R>p&oH%E|gr5VdAdLDOGMcB-.KX#7s>+1$!S:T1-t*$k^-# Ey[em$EvOtN4õ0)8kEnnI osGEgy!j;tG78׃Wv$kǻy`qs 2{KB@!iC#Qot*ʹ$PGTvqqs`=&}dƳ/RKC0ZջL)o̫GfRLN%#m(k6d{M2u4m-o^b]ɑy'@(O:r-]_1j.)s "_L;Ei S^WYMuϊy`ݝ*'FTu[X <#O8SFHdzFa#MO )5$3+hn _?8Lt`M{vMNuj%e!yUrUA2!f]}*Z;cT>.#cmp!w*POSKS GHCwӍ6Y1>ϻKX\{`xn@;Ts ™| [+OKu HдyG"*V‘rxq;yalvQ_ԝi5:RH|=6bP_U͕~kdp(u}&ɗbuB9Æs"d[S?\.}<ΡIRCtD_kI EM!.HbՆ5"<2Q; #ߖ]a8w)Q eוr >y o3gq‡zG>!'lEj#ѥD ylk&ݠc|5|Jk>|7LV37tϒt2(<[v(0bIUnjțlT^G_'QjG-Vn׈^r? 50n]:qQjm_qu,[.+. BDSƥ$ǪkuJ3d7ί]E!jX@uQD0ml2ea&,icWJT mڟ /O}׏KjP3`㤌焋 ٕ}a6@`z\WGZ"G@U4OW-^bQwVk8DZ2l4 EJQ7'bc^?$X-)x;8 0]5KLWؐ@}~(IIМ} Q = ]W1^Q":yzO{bÒF4Vرv*J=# w!Im퓌B m*C(u5p[8s-ǯ(,]U'>Ɨ`؎ZrJ+lYf#!24mzBI;_&@ %t  eadpڲ= }kSUo_^mpO(CK?}SԓR<0}HUhi/?jlnPUzVcS#A>)m'=DLD)6oTDgQy#xG.ugE7j Ae| Q>ce[M)v_ԵwoJ1-$Z|rSNVT]]H: h: N_ k߷kN%p!<:kG *x>(dT? J}A *v'RFc85N1_ί0&` V[|!s푰*Y7~k~@OX7x1hM.k \xц,;orL 3]Z@i + ;9II:r!WAox3JщD+<^KY'^5c}Sj2سș# ٪<4C&z&OL̮0[Y>*  I.n]UGRmxVt-JjLv>`պk5CE7A$C!jZB 3a xtnMk:{7wl?PNs 6}HM6x8ynir<@V/vWJ9fTDzlgo_1b8jj $|=u>^$yZd}ieZVmIO]ߟJ1f#ޗEL:Wk J=> } G­^z [<ΚzNɟVe`h"\gka vpmGsQpo× WחSeJRYطzRwѮ,~{P9=b`e8Sz5bZ):mo&x.ۏÓ%MqZ㦈O6. aDBł k7<d\.Mz@t'nw?a̖)cRUd0n vQqiMfH 9T%Z%ĬT$d% }G.Ph$,Q>s~qc (*axKd}B/wSa.B ݽS9#=1FP3lYޓwۊ,: Ē0SaK Nkj3!)3Grq>rBӛSRfI KWFPG! fO~MU7MxF*->)Xv>r /Aa BTˈ-;ˎTnU=1PHV'SPW|_J̖p8dhE©tߧ~>Io!*OAr-A]6Vr7IT/W*R>t PơΎX&6L|JKeEI|6(ѶidR D+>pڑ'Q uhoth '#ce:^z%n9'/AF] @c[lhaZ ُ Gg3᱂(?}{X9G&@+Pj2r?S~0fVˢ߽EӲKihˍsq|*@TFcw8DG`Na; M*܊DdaFbWJjO2aKdWSρRO̜1Kզ-b XI+wf̌{Cx̷У%5e.$fW@)-ZgaHPUL{y"t4)!dS Z vX]nLw#y8̼@OXgBԃ( |(, {w?u<Ӳ7l3F{ B5a,ڷkE Ŋ"m/c͝1} 2>R@Np?d>; t]Z)P=mHF_WM/{0}Nh[4")3ʭ~Cib!R*7} KOQ,ۈPM? vDLCCe*k#0o SjZ8 9 ȣ/|b.17R= tY`lQ('؝6H%C4 ,[}riDSauf NZRM p#^^ U(w+[L(Y;B*e@029t@' ({J.a' Z?X* ͥL=ۢT3*,B̻+6|nRpJR,#3B⣌5*ٵXdP)^98nc;눹;ikC}Nuce5\4m}GVWB3N%[dب' As|=|嵄 6+@rYExbnQ c{Ts8I))d\Dٷ6B[h|ɆckD73VI4Y7 N'߽R=ˢLuU<_/PJN!xt_#&$fK|EhݜTAU{pY:Nh|eCH;@//bXv;ԧA^b1M-n,4i-t HrџV B@nwMߦ F\`Z]xž=D$~5{ RDGV4K0p#o@isaNRn3S*6t ؀t^|TɅd B$^-d@OfssR?1ta}+韢z &bS=MT0QX0|qMCY{̪6!/ 771wa.z|ū:qn}@Dɛ6y,K]GTeU2ļK!:43i1%$~o&d3PCô}—MO9F"<"H~gC= R%|#qtbo#Ʋnyt9+qcXmB޸i|YBNJ/K;U#϶X֥,! u4+Sl0ŝ@Rr9l3@cNvнiDo鸑֊M.c ȃ!oZ{S%U%2֌a =0,]?]!``lrH06og29ʯqġF`Jp<){55<ܿEh>'aKp+yJ 'a+~;oL_uD֡XxSu@R\1 =-~m^ͥ osԱI# :}.$) i->O!c# G5&bPr}3 eVt'9j(z bf,ye+#.)JCvzӀXщ80 ݻE(LqՏL>S'tє2Y]g߫ iWX@u)d_gwv|B Ni1\Z'+IDYj{m-_9V&paXXY K6 &3zO?h`N_J|:F՟ lhDi62cAܔ *3eo8 ޑΆ׊m!tDGputOk1N)"/oi"hĚנ&fv򡶔0DdrPfխ_ >AHvTYhE~B&l)+cΘ"ovrR8*#fTL7j>Qx yNbdcE`h9mCNOi8g+#qsq!#8g7lbFos ca-kUc,^D;ΗȗZxs3S4ҰU> Y?x梹 jLf8 ˈ>v8o.7߂պ 0bd',ug[{;E\e S4(@7J5{r<`ILƈqO嶕 n>;"[Nz& aa*'uY!.+jAgݳ5}4=El䑝ֶ mQcc%.n0(_1$/ywGgӯXuNP4e >YR>St܈2$Yxh#+oM>4R!9ovmeDH~x8hX2u%60r$!S7Hӌ p~?KȚ4O׮o"_vXuqVFF`Qqβ)!;bOS Ċ.p*:}2vJlT) *NUv Jk2FkcAfʄ8$$-<:OkRe)ؿ8|၅NC%~M}Iʬ)O:zyhf 'R|Qo%QZ;#@1eq<ըgҸla\Q@9aCw8~(\؞i(TL^FL2~ =KFdZa5Hmﲩ}[L^4ҫӤ㉗꧔1 cHzg_>_M+@vʯN-r95@3=ص8jmP "v5(~Ht1JX0j,$W720I+] 8#Z- â,odBСR~d75C=J\r<D=IdωgKr4oERKBo^4CjIݐD>F,{&s y̶HT羹鈴T &xjU,P=ʗ s`+Iζ#57DW#D,(K(SL3aw},ihq#ּ)1#ɇKrLD Ǟt ޤwR[ Yw[Uy/(ęm\ڞٌD>$T@SiU$nD.Z04RNzU|ז)n\3cd_lّ 4GXXߗI|T%.o=jTB/Ï@Ն ĨζEO Y v tx6qފZS5 o%{'ӧgօ?RV@>J?O( -wMj(.+򌛊^^;S=#wH`ly6,X Iʦsw_O`>ː4,8C AE~z!aPsx] y".bᛌӦJ"i7 ?B|C\ĭ&tYPW!YONl(O3%&RvmWt20]&tI3Y ]ʠH"B_?mf"To"3Z_K74R}A ~`TXb+M^hCbRC613 sTo-Q`uVY8|#el*!£hcXr]$|r1G=}Mjj{n:vߺH\p)>nzQɄa jf+K%Ӭ%(V5ܱw>كZiwA@/me@'EOQRխXl嶦e1 ÄhT N4 Z0}nH!j¼-MQ K^b]t]e+Ec#򗆫#'iZ6XO0ĔN[00ө^N9NFRm^V, O f㿏@mw:{ޭ 8&TGdz/Eþ"MIQ#u^QfΒLB!o\%[&7G?yE{dl/)_вxFnRZ2fXw.NCiy,ӻU5?&EDZ л8l G+r7%e9&'3 tN\fB9Vc$={.TiIh AՁ:\{;3ﱳ}!1?/S/+M X7D_IZ?OJX{ Շ;1R:*љʟehfeF :OLնB$-6xt :S܅7 3%迆vS" 5cP [m oɧf1, EzzWS!Y1Mc*\ɀaWҠsYÔP[`XP M-Jʃv :@չ1)b٧ >Dz(B,F>nt$-Zr6VoIibwY͇ԩoΚhp D>C-W̮÷oZzn;R "3V%IpgF&.&x*8W%џu$B oٽ  -^ _PRR:n-0^$+A$MԒ|*9GrvjFJow10{:XT2|+ܤ_a[3iFjj_3$\v#G*ĩTN 7F#>SH%S.tpeyj`lLY9s>b5`kۚ[LQcǞ'2MIbWQ׶ }4Чs*K~aPǔz]OFLKDQ睋A,u4̓D@t꣉w3s̢r,0;%VM Άf)otv1p &V+5A= RWaĂa%xhϓ2X?RѢ3$dy]h(~`5 BBS n-ўaXӰbN=40E7fd't¶߆jx|f> }f4 rl{ZynY:!DkL\Z.csjt49Yr= r衾b$KveS^ࣽcXhilJE`qӬǦīƟ]kZn5S4 vcSkڳ322cM(:%{|nӑ/K[yfL#[.5 g}C~"+MEw)Dz?հ&H]Swn˵=6-Ԑ0YX +ta%`>oa`w_8'w IiG6BLps,PJ%/jcz6<sp{ K◑;ad۽}+3~1V'K)k_#EQ:28782B~(LL^SKݾFpƗe+v$0;y@6e<ܞ2c$WԫY6CɆ/. R)0(0a[sz9yxg3'B+ܧ#(l>ŻCOzqק:#c* (> jϗZ^"֬ ӔT8yV08ުyv)aFO X -;cˋjj`Tw`'xډ F$a%H 9l*/p+|NZװpA`>WDҽU׏"@>Abg]֤`8Έr1؄kH1 Mu_vӼ ѻvJ (w՞Sb)]]gԄS\/N_ ~V>cEt: mp*?y_^ba~kCN<0U#G4 e)Maoƶ"}w gV1qr=uFߥSg yLWIu¬y%"{M(HzMaƜQ>40:#mK+k3A P _U5 Z.Gp'Ed9^lM,b};{mK]oaznjNh^׸n+`)M?x=ӕ L=bbf-aK50ZPoN Qd$)$ YKTBa{&^Pbڦ)pfHM1哠10Ge%ݖ|Qh&c$}.O֣+!O8 /a"BX.y>SwqP+DzY:+[SV.p3jԧ.cNOQd ;8'oӖVU I|1ц0hC;8Qo?NYe;Eg]Lg̅0-p bBֳktI*Ϭ;[͙,ƒx pziRM>Zԋ rM20޳9sl)b>H>Bo|oVZFy(*Q^.o>z+O G:ʦifk_<<򻛠1roEn3a@b"Th2ߛJpp;E~ O[%KPzk[<_:W_AuwK7V&N*~AnŦ F>S[Rr]&-m8_4*8h^cCILEg,ʭ`wgT(l 2/&|! my^H*ބFG虇o=!HԞ&h| !Z^ze\f^6(C`T'MD'Oho #UCpC?R$WLN'KRŴJö(Ď OJ9Dɴ•:nS,M#`S+7oĸV_ ƞѹ#c!$i}`W`XG~χ^G5( e?~~xͷd{+Ƙ)_0zr0mi*nۂ\l@B+[#,UD4y iMyzABκHQ j( N6ފ2x@)SJ\ {nG7DŴLvd'J_7.h݆_G|*)4XL!R4vtZzپ8X&瞮BjJP69j#UQ@ȗ\^zDT]ҊL>9r.E,1, r7Wߥsܛ+sp9Gf3%3HKD<{fHU?;BPaEwc79aJkw(<d?̧ ei3ܱXQ d>/-c.F5q3nj)BOKC FBf 2ճaꑗea2Qiqrd= ibw 20B% 3̹h gp)hjp6(G+t,7sso1#,Znz}7g }#Yv,< ޏ84Fb@bm,0qWޢdZC8ҖN=+M1w(PU^ޭh|U{"0qLfC492s>aT@,ƥ uJb?e^n*wK=m7 p9; \<Ԭ20]{RA0e Ꝩ&DK/9l{zie6T 7ʻ5ZЂ`s%BwNXBr@.l;>08(P%Ǭq<@IͻTl%>fӋ=3bIQHG w1حWԛ cǁ Sв'ymXfC=OgKL\Jc1١DDKBn|N!,vLø}k~/X2]>jq=4i1=/ZH7³mƆ)|נ1GA~Ƿ_؇mL π%;FZ%gҳ5 ܇s.6njɉC SGn17%6i Kkش"{fmft3_ u fh gV ! -tW?F_`Ѫ:FlC"ЕE6IQ[ K#o+{= n՛.B&p<<Nc h}9` ͅ|蘋j.Uz]2-pQ2$KjAZނKxOer4򝓍b# 6X8+ȫvaLUE3IvԹ6#rcG9UoX%I;NfFDT0> CAfa6@LmTvD$i UH辭(q Sh&Ԛ= 蓾f +"\fgPFmإכkmj`LAJ?άeB@ɚ))1źa 4C"O%jvm+^*M\) Au?C9X0#CsPfA@/O3j_ȗ?ubzW>r8 ]͘n W5G}~yg˱a\{`- PIfsY ҮRxJ uB$RQ,H&lڶ+3{zU"Z)qXy%p{cy(nP2[ `'eopX *¶oYO, RI#O/Kyug?-HRK' D^g)o߳,ȞP*LQx\i~RHs3m>uXIˠWz)|jH RN_,5M;~R_ej=`g_N'Ԉ# jչ]#:@Z$qs?)k8,@ٖ1&p2X; ?]\2IZWfұyMp?D[<=z~͌\ toȃQ>{lz֧#?OݹV|+urQ/W*B&=UEVfeub`LN6B_9о?8idZ- 1]I H4-.HjD#%KYiVP ȇ:0z](p#37mh3JO0>4Bފ|ڜ2R~wyj~ E֤+]؏D*~Hv{VIrُDLtF IÛq^?Z\Bu(J CU"X$GbSy[ܿuUlԼʗG>7mpQ{e2Xi@xBHgLlLUAlmCBL"+,N'21J3W3:bEbT̬M@,>3{rS-x鼨qJT4-n@D4J:fHR1țT2M~[ھ6@pp / c \Qan$Cvip\Ξ@fWO.o(N~mLIRԑQ0ץ;=2x{ΐ&!̫.ֶR΅z M5Y8gm4P(ů$JHBh$6T 3BS>\3a6b}.G(,T @{ؚ/mڕIP;m zݒx(WGxY}|GaGyu+"tfK{祵BtYQf1ꆽ(.h.4 l (7>  g`J+=FJ~J#)g(풚 2?O7 x1T\.IK_ >Px$ O!҂b"[S+TSg8Bh ;>2x@%9 k3.# H7Uq{- ;[> jO(PЂ% c28"xXlM OߊO8x>ͬ-!L0V9kNYbvlu.Q"hDCj1Kww;؆C1hG̮^ٛy; i2jzƿֺrAe(ՌAQN(si%n{75pt`FJ*F0ŕ *&7j5&## - oQU_SeJ5߃) )_m(_&j$̆\$c.M>ztVOY.K}H:v1k3X|#m1wdmdfF'j=f{Xn>fLC3&ם)?u^GU҄-_ɡ`95VJ 70YkW_@9!xQ?crʟu.FHPMMT`Yžl"A=͏J;4>vգ$-~ZU9Ⱦ)qx'M>^6PMƄ- 6BM(+ ׯp.͎n}oR?#;9e < b`=EIfyo"MUjO@}2ʿ?x$B/wĎr;'EaV9w׺Ӣ$_wR&OH|g=c'悲;߄,vPpߡ^/Y!F/p:vы('vW:Yabi?",%! T'fvwOdaS WQG !Y)GuOBw4/ a'ff3anTd_ aH8x>u^O|Dʹ4!`96k/~/;U ٨e C pW_66 )| ohL^NfP={WʴCЗ|(Ք9au *y]5Fb<nC/q]4ETR054ˋɞV/Fy:Ћx̽689o0#勵%F+>M}5rD ( `H2JW _RDݲqa#Zi #"]{,*zx&hqS\1X5!%n,R3n@FV 8 M$];3߁lFY'4IzfR;&8q[2 9n5,Ά1aMݣ9ޗI"U^z By4-6z!oފiPÊe?!vy(L nf;Q/J u~Hӷ';(97ސa:G}@@8"woitT$H]ߊ@gEW6긳j7uia1*8]_Hۅ|:]|{ռS2Zg4Oc )"8"+4Bb4n+@oT{!R U+I,H2b3l2:׆2޹VPlz4oB{/qZ'PLu8Vj !3Xw 8*OUQ:. 0}'h0{kv J%Ry =CxwÂaem ~rHu 4gTM7^+\i1 rXKMڞ{8pW 6+Rۤ[~m$%vVl a!2sL7Q\n}KlH_aiVZtʔVyWh݁Z@ ڛ{M' VNZB|B\y"1^M K#Ymڃw3'(d<DsD4q[^k5b*i: H4ɩ~Ӡ8 aarM[6yƏ"@<W[~K(tYaauYW\LlpĚ/ˊzw ԕF〇8T3Y9ǖN1r桲s*Z06 t&ɂJ@odS'!F5_5j( sF=T/ SS0oVwho/Y$|m*g;kI[svj=:}-%4H ѥNiYoXAV+ 7(Sz?#lLO8Xf1W%_6?` LX`㜾sorسKwS[rs"cu>ij[zaO-CX#_liJReV r?=Qx}$xS[1 V@ꞁ=Pq0]l6{ΒEǗ L[M1VgrM2j <<|/} |5ץ0=]!Gacv%`,y$šь kŴԼE~)%9 Lw+m`䫋zL afa }TP2Ӱ~1*"˔SnʳŇbKF6OH}n3g:T$x<,gl7/d'8Xb~dX4T ~+~rc[kQeL%Im@x3BF|.9҃K4؂K5[uc_O"乁%%3rV,.oQTV!|OOC7A_bRz]3Qz}1wts任h:|ky 32^ lt,jgN2Y rm-rT21#X5vvcd|ãϓ_YwZJ$9KɡccO%\s\xEӎT`jd"4ݷ89+zBy~&I!G8ٷ[?!D ],vFPPx<#_O$SCTIu7}7fcݙo󿆩 kR3w/Ccs6wPw*UMj;+S3z(Ÿ7V% ҄tM1Ɖ4ldjrTT /_y#|u쉛>[:.)F۱~f4N.Jkz#@o,ږ[EHUD6HHd NŠ vc7L p|CEhD$M%%k=3-`3*NruJW‰%|<ˑe _t}sm{8k%\޵nU1({?]T! {Q|mjmiUj7hoo&> 7[m@Dpxu'3255@w3q*BI:fp.ҎRS `Dz,ȵm%g4vD0C&#-ی&b? =ytx4F|^#>|q¥Vjta f} ɇ[Ɵ!;nǻ՛p̟! B N*([ w42d G MBCIN]V-Iv 793pJUB5pCZ{eΙM>^™M֦*"7;(X{tZE޾ɢ/IGiB";oebCfdzPg?AlT0J^]P`:3rbIPT$ͭH|UþX\GYd& qf~0 j e.%vM) z.Pc@˝Ze(FC/O t=y,܈\6j6B̷G,6(d~g c^XT z5*/ ϔ' [i)a-x]b$ػ`v#yD]|%"1To RҸ<~P|n[̴ɰC$2XTԜ "\٦~hȼR&0nKjgKEw{~h^1PJ;Tk$/ٌghM_J4#$>ē5@B6tJ?W ai mvk9V{=ް\|HPm]\|؍ 2utԧ5 w^2ڴ"uF9īz>e U#w^v#rv$\ 3$5"M7YZ4YCdj0F sUf-Ysu'AKyvs4b{~{(+~@ҥBY>doʡGa}fUs XT 3T?^ ;+eΤt SaxMT VD&󑬢b>.^7_U[j1(sQsD ٞwJc'-NS1FuPgO#`ƹ6כ ,̈Gi/9>(ȵlW ~z9>CMEp (K2Em>m Beg,}=m=d? {"l~Qd PIRyc*RI6>KR3#ڃftMcTZ2Aae^sø4UEfPmVg""ˣm $:,aW|ɉz\{s51 @i}bH75/G~)K.?vgr<׭$SY ^St/|%ŀt<̈́Gkn/xLk4"@A>2l:TMƼW?w2O4vK\Vg_ Nl718. %aۈ';- ':ԯ_i?Ș'azTbVB;]>{LvNe5\aƷ@zeIMse8\d9``p[GI3j7|Qh} s,1GGHl–Γ0[HQcQ*{U2P >h:vurU!Dv/;8LN[f;oKԷB.$sdnk6T`Pra2HR"$s^Kc#jׂ:(9xd| fm1Tjys$TܤbpHn8U -O[yQ*H\ R Q *Yj+)T6tų$BA6Rel'Z9d2PJZsw>$E*-cb|M_XL)\*51!C?uKA2t+鬂~;-3N2=C!osy{0KDL#h-0~ H|y ˄|laC.P XA=;Ыp)dd@x1GہDrpeCe=UUh$H2BXvZmX_3M_ޅ eŬ6 jRچ~_޶2b'̴ўSӾ [C[vՅ.y`d1 U婩9whqPMG50b-@.^!ك̰]Q M8ʑzk@ R})  zty^s<^*)'j 3+.o5F4Ÿ5S/e.FNET=tz~l'CKjy#tXei/Ok`*Fn(39EBs/ k+OZ3zH`tظ[Շ_zaZ4Fd VD1 4ZŢ dmHavXYGPxάucKLVۊϽY|=8XS譿?-K%6?_~ӌݦ?kR֥aT5"ypu\oF ;ӘV+Wxڊ~Wv&Qڨ]H8Wk7RN|+ff| `Y7Vd{ c(ac- USY%Ȳzgz:f*6puCK<|/69v &-{k?٣zW&k>jP|$Xhw U]*ȰQ(=B/I ]բ0i| YpDuЇ*7F`)⩡pTg1D̈mۄC;'0'6NOY7UR&6[r׈xz%+SVގH\61jRhsҞ; hώB!E a\c롚wbpooQ liOu? 0M3yʼeDɻ-HY=oV3¢`Mb]2"g\r,H[_8L NAeI`CcH?Md)ޥ{hLsRWa b;K97W'R/uKTT~Bkva*w?r6P%NE}SVtFm7Vܼ\ھN>\Tv"%t¢G2B}q~0\ݯaOڞ8<+|fN~gAY嚗i{Fm IJqzZ3:ܷpn*~+,Gc94M6bvhv'&[neT^k]P[uҢ+Eq&:[.c^@_+$tAat_`..)4 3ȁ;dɲOTݲطlϬ2zTut %dz CҲxe.֍mr?=Λ΢M!LֻK\օh 2=rQ(-qqHc#jI)-}8:0\Q΍*b vUvi)f`qsReæ jfB)X-ؤ{h<̄$i30a@eH {| L F*3~ZO@~^@k7eM@׺qn[ZCԧSu~5܆`ΥқI`&Rx) R=(.';Bp-!wҬzMC.s|QuWpUܵjlaCGTWZjB)s׼ KDQzLiFFlX-)[L~/X OJ8XWq"H*萙TmyNGLMgcf&X|j-y8:\g=~ʒQg$N2l=C92lYѢ8dIpN$  :Rq9 &gXO-P]L25rˮ5Md!Ke0+}kYpδ-m v5vASP[+Osӆ#EQ# mbV]\SkQo9R?҃Hay[NJ>#豽RPB;"wxk ~+i k, \oPUm2RDL5pQ^Qb)q I'YZϟV2Y3C"%Pٰ1I08ʨ>(Xf4(-;Y)km[ ,֕Iϑ~'6qy[~4dj1H9]|A] Hæ2Rp c"k|oYL7e|{ݱFos6=X*yM˕]9 bD(ƹ7vЃpPDq=_(b𣽾nW )t99<I+h7Z4Q2,YR"͓` @YuCyECd{[TE S~iO bph(Pfe2k13>[oJ絰>`?t܆pzܦ0Vp/TNbU[#ϋwIe>AkO'wVḂD=!.Z^U}Y~PaZTۈM~Υ1EA.ʰwǭd]543% 4]ow8mCvH0 VDlX^)OF8Yo<^|*%Q褶2{b5,a@n Y@-b]mMl1x BZD(sϰ-dfnN0ijMن[qic4V H;PE9uc&kp|u-{#ezfA(z5DN7)9#$16^\5k\q=wi&;=wJWGzty(qۘ[gnmܶ*-TLzXƟ=o?0Jl0vJ)Cq T. [/dZ (w;cE= j|Hm~#(3$+%8j}>"Qs(=CW*$;c 26@^؄4!JǕ{' i$s 2]1ue!|U2Ex0$b9CogcRmSɛFv[_Z=WYl o~a_E ce9j? h!J,-D2Ro2׾%(rmu~'#S> Z ,&͕5p3#_|a  `nS#AVEMk5ƱJ<Ղ0Ij,|Š asO֪>w@4D!cڈ{E)Y>[ރhI~HquX䂙yt_QY9\W"n惆dYJLQ0b(l0V' a' qٺ897W*>!#mrA1OODKbhf@%)8$/VPX*̝?HPACTlBŠcE"ʅ͖mBْiS dd(Ǔ2b0@afSj}2!tax!,Ū*oVUeO<7KI S \WiE ,3%lf$3 %hnú&ωqCغq+o6se dY$N5K{scF@;K-TanVJ ` $Pnj 8bSrސvų@13Je/=W'nO'#z9*yvpBNO& [G}։!Qy퀂5-[s8&Prb ěv^ұAt*-$I or9ze۟X1Wzj56o1@F9fުeSFB EkK t4|&ʴ2] x#l޲wbuy'e*ޝVD TD,IcL[n_Kp A ;w=+D~bf2*;3 K=I:0K}gڐO|'ᇋR}.Qvrq]^ T Xf A=G{o z4|=BZ=Q߬ǁD)=zuG~hTpv<׭_%Wia:Qak\a8.+cYSp7wģCKp (cC iY+L( ?J w_PeϠM-(4܋& ׻qstg8712^l5|u bH/F=eu_u ])e'D=ȭSk+ɳ HVB+/E5UGY~yk-y؈Vx lO=ty|ڱ|٘87NKFB}K `9Eh _y/U #YRm#H!oc#]xebnA̍5# "w& &4+Q}{ #9Jmg(6dM<6F:\;(Z- JH`]УXQ}u f:'9땋_V $semJĶ|9ʺs1i.^ɃSen_[U/=]g (3џvܧL } fl*0fͦH՛͘{)3

+K0?&OwA !|a5?:f"0 9c+jJLg<4EkYW85 Pg~L, l w噙p0< 6?tkn$t{>xkom3%R첪Ueֶ HW/ڑ*pdAf:hR"8sQè>f]P8$VEOҒ>d_5(WwT~2+W.7fؐoΥ>*0|K@ v>u@R! \2]|bP .Ҟ$0NI]f*CmC5b@^؆jY@JlόCqFHoSE)vDKa3]bvނXf(Z,:60E,+eYsLۭ]Z;F6V0mgR+ťWd./2SjE*moV#vz3CA"d7s9-eoF17PyJ4,bަ ER4;HI8I7 /WcTE9u E<ǝL{>[_L}dj3ǹ|0դa`e2iCƜ*@9UޖfDIۥg[C&2^;k,k f;9Fe4J`&R9/lnFrVԈup_'NHV{ D.FQC"MJ7:YYv`/ bsYAԧű&#F`ם7ԺwgFN cd-ߧUR/rQ9q:Y0.YgwřhIV! o}㐼dLAlFiPn9zZ:1ZY<_ԽKgLTVJ^jS/ YHmɇ[W@eT\Ft=lOw9) в~6&wG7Ɵ; ,~p߳yUe%PmJs7]Ѫݒ`ʯ,#ow S>Phk_v׫kw{ܯnv"E?q 2QVHe_RltO3V/@zFLA1V}5qt?ɼZY_zݺHW!0h<))\BwGz(23|ĺ&+.7KBhSA#0"Bt[ 3!UqA V:9g%iԅIBdiJunĹr>L<Ψe͠80)NT>w˦Hj@Wx!I\TE+Uь5Eռ(S}ŐeVPJ$ x{DRpQK~ƈzB93AX\7;jeCʙ)Sd-~?qόU81h]H" ,sKX"8ܨg[ &>Gsl|hӻ ҏnCx㬝#g> wd޺:ZΟjJtđ'V4BzFOayZ Uz}UfxOm8y3ӁL!zЭpuBQ:l{.gH9ȿ0exgMӪeaő]ܵ^wg,t<ڞ~MӺw0dtIKowc?YҢ_[{%/H<:9'K71}^0lV"t*yTDr86o̎eTLܑF~#$?k=vL6٣䭽tW0UP$Sմ 0@՞@ΜR(_ RZ_|]K%вv=dJľ/X@nTДw{iJ1*X:`bÀma 2Y{GM|{Co_V]3,sTב{ i 1 &(eGr֓ο^`2ٺ´ϗ'FB٩ߒؗ*#YD~v;6tNj,W;"7$fԸRmʉzsGW[J>i]rmmQgo^Oi0 ~ 9t2v`yEҗ^J\/kT7JR%u9R1=;G, :.I$~g%"!`(qLlTrdtF[?"d옭d #yݜHjhQg]ɻ[]. LXTg*iJ/Č:G+HώԤqbϫ[>Q0vy15#|F_CV 㮮g'\(8@`668$/{L".=HE;3Pxmɑf=LMJгVcU%8-&3O  r-z]=ZG߭:q 7[zjFM`O?(۪-(S!ŠR]Ȇ8sekc%Q]G؜DK\⢊] &q Z5S1?,- /$8vɨ4, (d-׋g|2 fY-u7A5LX\] q|ciaCATubL2 6'>`̗;By${- '\"MV?&)LTM_Gp = (mesYFͼ_yÉq9!*K2Ki7L73f(69d5"0W+A(pL*mJ}V\{1UK\7HX<^8>SVLd)zWsCWK `PfܟYDžf㲨%z !N-,)&TJ%[hey?W <#x]C_ Pj Oe] al%̿kKlQye'\c֣ltc.![owR:G~Z!r$dM̖p{eM#Y+*üj sxOt,E\^Ix1pu&VfKn.%iP E 8e [V9}͹eLКB,B-_ caB͌/sQ؟.d5ŤJo{g!_X9i|ZvX`͇e8a#5^hzb<9Iu r.TVV?Z ;40K=b<*]e@/ll Yn&ܺgmK (W!::N}K@F:!#J1٭c:5ej\I.jtMAs+1(xrghZءxȩoF3O30o@fa^M x㡓U؇[>g_ٻ° uE-1ĭ֠TFu\)ezzYyw9ϥ_kb"%$~(BtdI@{ʦ+չVMa̮u䷓ jRsA,< !N64%Xms5)^״$mY'OFIPC,e7/|w G`:] IrDMhˣF2pRHmV_ P.I25/2esP!V)%4cJ#ݣ*@h=0iWy@M DǨN@?HS"ƻ̎`,h?P#=p|*ZE׬ m./*h#]6 (ҋ[Klpm,Jd42;&\û-$OKDx|Uȓ݅1вd2k^{ϫXSiG[VtjN/=B~a9ci&PAuVb{Z.L:ó5s8NV7ƈEo@hHSlO<b477 [W 'RW-D+9AR]mu.ߚ}o)#PN 6vbX5B BԀ&R"Ğd8&)ݗFGz끰+G}+&3_ߩ(WQ Se2}'@őSlNM?@|vK75y(מ-\S,Ԃy)ԗluF(W7#<ۏ6HKtj9%p6B2.7ϗp95R7+xֳ:%7ӁQWSjөWd%,Qwa"ymWOrH\umnK"--K<hm"H/ iğCe*q8ؾpw;Mr@،Ŵ\Ld_Lnև^I 5esfoQ·ebU^I烙߉57k_gA=rJQbh0ےX和̂gmEyg?LEz'Ǚ eT9<FP.*k0;+=/Oi ܅*RKN@C;F apu*1~khqpRZ ϬSɮk9" ^$pY(ٻs7#ӪRD7oFBa҉՛1Âr ̏gȩR1-KuK3c+ *oIm-D׷mQ'(@p\7f<.*=Ral9*&kHc1ɻ S(٣}N*Xb~:T䆀}ɟPyg֫+쏔ݏ9A(YTU5`ֆʥf}S`M2Iqs24w$w[齊Zh{lw!='xJ&0 UeyNp1F, ]:D9[b>I}^<҇[Luu%jro|5֐)IJKaħ̐X*9UY:be$I=Y5;/. x*Xl#b[#6kH8qz{8![Dp)~DC5l(JC'J)ЌܖFm_4Xt깖 3--#XjfIڪVZ5;IkTh:Fl(_ M; Ʌ,>Q,X\~nPgcT #\ʎt=d]tACEg%V8EڐbB\EZ^f$9͸g%:@*膕m/AǖnUD G{x]Ca8fZZ$:-z;0{AkU{Ճ8sk&~iEI^5Qs%-|\4OԆ3\ƵJ~'͸;-)ّZaBrZ1l\ YGa_Ġ70mFsP ~9\($ARWs^Yg'ҷE5`~טּg m`b&;:ܡ8`cEBgi(y;)xη(J`u~ v #D|gWDNZyg. C2W۵QW۟n('/k 0_p|U$4/[,_@Z ț r+,a$Ai$Y=@=Vw40:tBvW@)mWs=#P,єAڑ+3o,caTl䇡OC vԆ*$.٘NV /93ʅnu#钀[t7U0Dו8) s?q`Rot@f*N'Jf2nb@*0tN4Zs 1SZu5ԅ<+['&ȿLܕtL}}]ϼǘ6)Z)tSRO_`&f<#+8 d: n2pNVW|ѼDgfNFϞḮӟ:%璕-ͨ'*B_&źԇ[D:Q& f}-UXPJJ SKXVi,4vF=%Ukų4p 5&g,e@nhpb<`>͆튔4Eɏ[;S,M5avaڣV#ji16/;N0NnddW\Uxoa&_GwP{b"_+TSfDuM`"C:A{EtAjnt ?NҪJA{/[tUZ(NךNv "%q)ZhgmጃLH]Is+(wB98ۃ7kJ*oY0`N61ȯVz@p4$&2\b?yՁ'NGO)9u0XK8OF(!~Z&d&< ¯d5* Kɒ*nOߺjua6B6f^a0ѐr6Js*@GeHG`ۆYzG=:Rzs[rH" 1`Q2g 6+Jijzt/p]":d\B;}MڋNb=ΥcTKr ܰ!a#)w4C!"ҩ2Xta=hbT |mC0E{S=Q^!sr# Q<aXKNX+*@WxrFbvY˾Ч?H" b}0ࢷ3jEᘍvT# Cx]ctY*kRcgsmǽv\Ou$1ȃ,%O˘w U˲Nv|O흝_PR c?? Bw}{`stTS\% -L~tTuB ye`w"BСJ.5ugE7}zlT;:ܼDU#hGen<4pw՗]teYL.Ϳck`NLt T[hrӲƭ.ڨRF" !B*bm XvO)]=)8yaTe_Ϭ! /?Y_7QfrЇZh?i ;mpB- ."%FCsy._GPr)|8q57L'ICIDz)Է'<u *pXic+`ތ$H"7uOo3mILv!ulk(N &^|VWpIrqK-'Lwl{ޟQ6qO|PkW1a&,TIs#!EC!鎶͔Rz6T2Ae)cL͟ {"_ědS[W#+Cð('[ DWQhޑ  ubҞw/$h;.훵9`^w毭hbB<ݙ`]_0 t|S]ڭxC4_9?ɭ_-Lk%}u!3 F&fq!c^pG&<2GZ03Hg%cKG!W kIYѨ4|{[C]%{;| X=U_Gjn> Qih"Պ٭*o$L/ʢõ\(,=JnFw3Ky25'bN0+JoqÉa3BnFvіWͿTo@)NM!GFkJB@]iri5d&PDLC[[ikZ{ , %Eg*DԠ5hs]`<)<iO0bnxMj)>c)bJb$U~ZOD٩QD {Lq}$}uw# LD<*_0ސWUыO ͌ɹ9%5 rM^(܌_!q =ɋzp+oW+q8­B3$ȩ!Ge9Q;ő}K/N'4[Mh`OmS 4i͍֩^knlYm P=!`4w̓VѱD7#Қ#Hݑf5[g Qy>JpkkIvYAqmIʉ>u,؂b;c:Yep"o-ok <k籵-1S3.'\yw5<~1 ken٫Q4[h|xc$yS""nmEuPQP5ΘG󤯍GE$ws1emǽ{}2gR)+`#Si͇ctSNFJg=sld5&)0gYi6~*3[%MH+/+61AkAQ8;]jMJͦVIhw&˻k69|YG*Dw60(րf)KڲINďjϴkyq,{\W|/qݸ/϶WnTxzR+ hJ~ṯ^CqC38w9 x i䅡 j" Ƌj,(= q&)dCRςDۧMt*Bm"0̋_nDV QSe<:;qB=a%t 4C]|ej:ih96!QpxqGWȓ-?rEh#)H& m%HR3@M(IZ0arY4Xm_}B!k@ca N']X(ԱKP$ϛ,I!;+%$9G+ܷIVfU`!錩vnTTS]T@ 6E5uhraZ$=`ҫVܕXv,jW=3z,$ b8ڌg4 up,' b6U*>qaNe78ua 5Y\i;Ѹ2w#ٺKե>z\BE !CxkYdWNҨ`8# xJhM*ϱ10#d;E-QzZ5n_u?:y!^<5n\r+c).7Sq։KT%jS?'Xd<9Еlj4QGJS+>KBp\#Ki/^Q/S{2V);[2xb^;tZ gj'WQVSF[ƺSq־`bAMlҟ7kl= u2^>f@d?">TIjPdo@b )SiˬhK 3ZDؖ;* Hl89:輎B8$k ԋ1ciq)U!ܪ܆M&bpVxrƔm[D5-N֫" +x#qy J_ؚgGىZ|2n.g+aޝ?+A"z:hlB-f9&5wB]T*y^NmXE_)gRyW#]Mh%4GMͰXz 4 J#>!VUc.Yχ¨k6Q˰-h2;\͒[4i&%%?N\sU2c&aEgP;3H*w.[AgjEHgδBmuW6-}HOO*Sgq?{'ߨ4K̛_Q*#I(]^r„m%݋dJX'*8 ]`~Y!vj=FkũW,wdЀv ebŞH 2>Xv RAṴ$rP${ zZY<\> dFJ~U\@Sg{uh# [U8Ñ}W EU pKk6FYyn{I'֮9EO*:C=1rIg-\M%*`jl]hfئ]]eKf7_PpؕTddm=!|Ž[$83_F,_~,ɖQpE_ԞS%Ċ|b\<*UT˝ÅkqE<7ĎuU:-e/MOA4@ϔţm×ޙ%i J7>xsJhfrL --(_zJyK5Zx6W4!`Eq_R1o^dhrEP¡makphQ;eYpR[~Oo,3mQ  c ٧ 2y#aPs ];yntFӇ+I`ugG|B $/zv#;5|4E Oc[!3(7ȭ~ [BRG33Fy.^p;'_do3'%z5^ V(~ 'O\`4^q_$g/ s2$pL;]35cB9[D A 9^7šEܖtgQ)I!qy nY8eO?jאETkCc[̑$/R2Wm+7 x)3 jgx-+\1R\*E' >mlO7qZ@57%yUz9.T7%hAPHx:~fӲRN JFk)\ T$ @0z-E#LUt,6=JVg{v21P3/a{ԾJHzm8Jgy{:rH+H,OӤ`Ütdɋ-7\%d綳Φ~^ՏAH.XT /~ 879D2_Oع> ӌ#ntm اz~MXgۄ%n ț;>jJ]m T-vQW?GY00n"[pgvI_) `YT/߹%, cM NQȠ XjK>;I{WՔsXe7&An2&zW7aSs{gJW/P+m=_#m4LJMiDBNgěsN[8/ol\> I8 Uf==*5\Ǥ[ъF88DS5#pEܓ' G.[ťPh#pa[sn5D閑:Rz`{<И2V- D O<@nR <ë-}Bx WືًzڛNiu,nEݜo"Oss1Ӟ[ɯYB1Um _$[/:mw+ၬ! NI {o'o #rx} ]U: 8K->{:q?Ʈ psOXj.r/ 3Uݞk:d N ෽(4zR67̦9;>´WV?$mSh,=,𞣣U .@<+">K=v9Dޏ[t]Uo\HҲWR0ħ =S >"O l RΠoX;f-cdC&mx) Xwi:cͷ)ڤa,5\P9a]"JtAmTqTL~ZI(BHcj"^Z/6όx.yg$DB 6>ۀ(JgnH g4Ѣ`BcJ905S(?j'h mjKv GsS/70Ɣp3}jQXEDp(tU[ Ud +Oъ xvpB+ O#v4$0dj8>nykʾ*Oq,pp)x7ɝ>g?ɳo}t(]xGuǣ!9fi!A5TށR@T6l]Nn *CƗB ^w JAϏ~Q_eJ,=q -v8}%ʰ:M3Nd)W:`[-V*?j{hj3R]U\̴N30{̞s!QSTA pZV(q! utO*m(`WҀ^k*d|HIӛ_NJw۷Z4Cl; <<f$ Qtc}]Sxғ57(RCAݩ֞(F7SJO(O~Y+Z0$chItp֜8aN+iZQ=zz-wh kȀDc6&L_tV,@ePJFÝ*;۵SoYf^7\doVǫmx߯-4 7v@.`RU&Z&p '4 u:詇j>mxV#k. ;QU)P# 4AB4뛽ZFBa[kPk@Wn@ETQN/~c놷r "4NUU>sA`y5) qO0NQGOIu>:/Q{(3Fq"U5'j ɼY]nfmƻ$*(8e [dӿ';U&n+tuӏ[ ϼzP?B3~gp+U/56ˤ$.Wܲ3 }λ!~x&!uZ1'}}VfW'B_&EbiNU靱JsfN~IA4&FP|HgmJju^Kuɦo=G>ZΣ5qyl,=(&%8el$X?ؠl0H[t Pw'9_j$¼h `IVkE=^( $$V)|_;7菧,.'H8C]\#u `2L0jGz[A22(CF& Yq9JNSZ~;)q+#ֺ&kEa{V:q88A /s@?$ ԃLQ`5ƔPbB:v%n$2(QggMsX A̘WHq3K$0C׃[XΣѴ g7 #}&:ZÔXWl2]FWԨCᑯ 6=o{iq:L]vfUN/mXm0Gۊ_q>lɖ7v5ːxiQEcBagG (مqr8.ڿ"v"QZ4$9o+SЙeCq#*tb]`P II`rjYEtZl0Ru, *]x蒲7MtฮgKQ2bݤULC T0T`X+݉1oHLuF6Ln$BӶ9?D1-is`3y)l[@~˩ˤfbnȈ3)x68Kl_ʮ.>`зZo."_6jT%D@8h;xX'dIwz?ɢq"[ض +6f*CL0;CBdwX 0(V@NUQҧmU#Sϟ9|:fqJtP …C $8&'NCZ]UTikF%Q_Ў,vM`(/?-Xz#ff/J`"aa#,VA;k*J8Q&o_+Kc8Y[:T]b3`nL F)0(zдFNd`U ƨ)>QƏSb7=w9R(~E&eV"p}&jFST X^7?ڼc(K>;rCٗq55((ds\pK/sե /MYq^'w2H*uFwNU쪋z"Vή>Dg2E3KeG!C[Ѣ/10PA9c_G: EB7 tO=YإZ${5!)"r%ykZR:>(0,UuzxeAbײNS.&)TE>PūޮXbzhCq Rg't6mz@y^~;UϝHѹ vŤ70D@b,ZK@(B:?UƶFU}mG_Y%(wx<SvWt^);p;cErы:9GY(B&dqjb|HvBK̔RmHXVf EQG}TJKυU&suZ4n1'a +敷L<@!@8c#>Λt=7y٧c$Ӓ6|<YR5iS֛)}ze>+"Lf!8zծZ:ߓ`yf \uTc|VW{6=i[5P[>i~ž0+ʜy~aOh2Q2~ETC8{z'L\us{KȖ$;źǍ\a)SP-B~ p 2)G33ks;߶C<;f$wfm3A(7\\<0jms[eڣT(+tq4:6}' I$VG!/ CeDF[TgpӺ;JqV;JӘF23jjyZg3q8hJQbƴ4ع1Uv_( 츋`?dhބRXP}2bauMpR.jNx_>My κ3\14|j|;%>- ɿ {|S!BX4ltVA;h!sg ٣UDMxCڠŷW/F1\dBbM?Qay%19nݯw+2s٨Zn򾓺jzxޥ7Ȁ/U$ |e9W5 { 7k7lΟ^?Ao Uƙ;(DJ#=]^J3L[VpXCSiȃ([,Zd/Eл l"7;YO9ߠW6,nVc-iT<- ݋77{h\6 ~6v#lfP-WVZsuPCoh~bA'2^pO}4mJ[ >nyr}Pk>-8CMm,Hjez \Dx дe%,/oZÕkq_ y/) qr$ƯlUT%!|dv.?\_W)Bg1$UPiNu9Ah4@|EH[10 ˹ :5;~<UA>s)=S&ici'[.}9ITX }mn=X0wg8=3dg$p }x]@U"G#&gQOwlʇbe"%|2{TRfjHJ!_Jy)jsVmfuA(ҕDaF9Ni̔5T>o`D+V`_ުp)sSd+;#:+sɡ2/񠌝H䝪F#V9\Ɣ2|x[Yp-_2MK0Bನ:hi%x.4jgksT*E-~Yb B#>$V9[(?ם 9WRe3O_u!h27IY;Ŕ[ 4:ks4͑ucg\훮| PJ58U9~6AgK*\/#*l{z6?4!nvE([و! ;WGʜkN)@Yx b}ۭ?|)8)Xt*X=/y|} CB#s?3I&+iӚQORa J gŽM`2Q# BK2=)]lnwR2ua Iteahl&{=+B58?|R>U#R2o_d$ Fƿ.D/YՋu%itr`9\DudݲOw+e~ڵw,s'[a.|t gSX E?'@T{PC$?)?_ߡc;7D%>A\w>YPOttNةUWw|;X@=bjxn~;͡C<|6%:t!-LqgCh|{'S<-h t~||+"L|ٽe Է(n5A D,42IkC&BXr#`߫H{v5MTF} ;e}Z0rw`4 3Ђz;ed +UJ0dm`JbYK8^ٯF35N9;P(]L(tQfyYɫ~_˅-" 8i%"=rS" 2;Zn왢~H}N9uhRn/2shX=%K#NHc)[ܺ n}P ~=pzC@2WGw=|ޕkj)< \Fh$kHndtqpXKf?Xi.[r&'<$C:$6b Df8ylջR{; L 3vu{r`uV9uc$k,G8jod?9"mc 4?-y՞6Qݶ]iв#Nڮ $6@^.f˽ 'f'+/x7r;+"5UaDOgN9l{eeW(dJS4iN KhlpWZu;px m<;2,p:5+[?e%0+sC`SQ |^l1@>6ο?aGPg;{F$SQk4hi;.GHSӨ =,*d"u:.M.TI?ڌ4P=fB/8{ Okԏ09̱|W6|I&gap>oRq6~g\wWÂ4}cJ/ʏ Rc~F ឦ]7,ԱFz)>#~sQMĽP/HşX1F]4K;V1'naڊ97\ߗ.x3<lt(lUM/!/&`u)FU-إi* z*\L,\ +ҔD|B4#i 6uFMp¯Q)͚-6ߍʌͳ$ELJlB:Yq R(v:s9µs<& X\hZيh*GIc&&ڌn9UrƫˠvV8(.LK^F\[#2 )#4@Be4`jExϜWӔ|Ɣ 6`^q;ig2hɞn+pk eGn}k󗈛 Y.F\NHCe$bIIۭܱM FBz&(uXMR%2O>zÁI' sPIh6YwL  -Je}<%nd)7ȗh's<>wʲZ3 ݤ1hrxq 99pmO$ tr\*^܁(c) vO•I"pDm9-x啣-a(q֨Tun.B$f^c4N6DfYRVol4HSsOmc0~pEX:, ~lcc% ],;fv\< pdξĥNÁOmUŐ:^0 7wGR\[3kJg,qlh mFJ>Z@wPoPme{ &2i8BefJV +@t[=1JMk ^A? _Pv{uBimYX_gB9ɰsHSEd^~k6?.3:{{ ܧoۨ4m9Hl=0ne,;זLUȆs׾cKąXz{[ln'f{YN`EIW}hN^zgRqpߚIuq4c'\pYڈEܐԑ _cߙʶ\ +IZ:~/WS].6.߹R5=']&HFC$QT(ط?P|TgJcF/`0\*qE@vR c6{64lB G\r?(mR:%p}Q=xdiTi<ٌeƃs`TcsJAm@:;d< To &{n1^>bCcFVVZ?ohʿw VzO׉;D/Yfn}e/zބݳ9$b֤6{NNV^L9B`8I?]`bKW߄w= ho fase8)ӥSw8LWWce.<J9[P6LE)gj Aw n =3 UP\X1@^p2iv.OŌǬ~ O&ñ d)a\.e) i_u6-*ghv%,a7x5[&`9$u?95/:E8&,50ggr?7T0ɓn ϵQ=ԢKo&)e:IE եFs @1wXHrM#_>:Ki  7|>6a ujGbdhM.tO Yj| w'˧pFz2+MCJM'Qr\!<EOב /Fօ=$M0}~\ѥݣ4ԍWԎOYwgPә_''VtGfg"Y ݰƘܵpPVˠr bp/:}{c MM"t$)YY gW3Lu\[FzT+,Yӽ%1 Rӌ>tuy~w2`$.hӅ$YF]6 _2.ľ{&8L% A+d"c^CL0hZZar}*j4c6j(y:&Ayf_/tB^8rP]4MtOk tWt{dqhםatXX Lv,mkx8 h*YCir$u[6 Bq< '!0l%K@\x[ǀq<7ɒJ4 fCigޛzOǀ0,L0vr MdRuyd $EHҷ0Φm|uGY"#܄VlarxT>:8AjkmkSG dgP-{QOiBDe[RuXlYթ| D%W ݼ?1hn1DoM=*9aa5>N mw?) RZ/Y=Qѩ.nԸN9D6qh l!A$;bC*gɅ i֜6cρ\DXKnC:WH "K,NȲ1f䍢"[?āXwXd |5pݏv02ٴrʣ~[mҙsSWlC6ڹ@KQH07D3*Cszf{h0PGq X.Y؁M&nk]3$gHSi"% $-leu^}ܐJ8ig?r]_4j 3=Ո֟g>XVr0'4Cɨԓ]|V̿`@ޯ[N[-)U ؒdc-ʜ?c|jQ&NqxE<mգSaPkĺul[|iH^G ZxY6AYb_n哐=O%C;w{!r %d?S~7F ɓNM>,y\}2ݘ>j:_^QVp} .%aHedcbBk<JA-D,Ĭܷ,~yޯTdf=F֐.R*`O6R7M8(bwPq-VC #iU9!*V/11\ٞ={Ɣ 7E*Wx x8F:AlMwuvYjHS)Wjۓ!BtG a/;l5'RQ-h {a2R)PbNw;[mjZ^ӯ!Юm?6^BNrAS+/hC8GMr=Bw/H>cb|pDD.א8k̆"l yMCRN0QUin#j,[%3l #Krz} ~nͬ:܋YվY+կWO- Q73:Puo=k"Ye $%pPIL)kɓk//G8L6p5yCg[hkC>m(YE*tuKpo! >~~_z n{:[󄊤v1 Ӆxrh,0 ;+[jCV m߰4!O2ղuM{ V_R d-1띇خRPi~`Ƅcqx/ 2C䖞7.}YE+ǘU x!{B%ZE CۤN6RfyBnڏk9g]:ȶʕXSBCdinmR"%+ bz Z, 9XJɩvw bЪ9=Erꤤ&"E-,~Vwy̠TŔe16>\[v.kAe>D*s˺g|=K-~Xolwϭlloʕ0^ W1(cꍪr"oQN=oU$"0ȞN?saAM} +p.E΀:$wMX1uk\eNoA 3돻_#J64F&Lmt1j*5=CFVYe~"o9vup& ّ" 7 !*^AM6^lȡWw>Y`X\ [f^yni_ܨNu(HmF֩\"X;40WJ רҜIp GSϩ inݐC(Ԛ7c;jz׈@I52R0p~zhC-44QʓԎi7F+~|iJW搠2ݾm:SCP0aWؼQ+-!UlUM~)Ǭ|b]*"5qB &e,y (i=yyz#Lgd78DbGǏAQA/o"h5#NBR/᭙T􉍹Kj$\Z =M(0ӹVZ |UC25rL"/qjSˈ:.oh'ZJ߻F;-Ѿ=:Ye>5)f.1d~e 3Ġ8!^}{$㳈gUd!vI ` A0]"n HQ0vdrB ;(rϊ& V~ԖFBhM8\ihʌOcz5jh[.!\S)201pkW!Sc1̯oKMm€CQ+NgQQqzSߓ<'_+?|ބDtêo >æqUXj0k} ;"wi!>3,6ukfcDjfn315qu*jjbhl pn.N^4cu= 2xt+ !묿1=[ߒN8)L,7q|wgj6\.wЈ$OK=ۮf#5!B/쥆:rKö.?tG9pA oiFnj8L.3#S@2t3}K[ũм_Q]ҟ8h >tE=:!tzF9,46QUщv|ڜ&[DZln`yiW-N:1~G3~8F rk̠;^E?)3*mŮ* =aEuqr8SD@#,pJweu 1_Wڂމ\UDgOa Yh)kF#@wNrLN:#K^5Fd^qKroQjWoV͡ǷZҙ,`DdWn9]CR\[1]ѯ\6&`Uw,Fhkݫx5Y˶9*JCDwE;\IjN2fYޝ*Rݫ]h7b5Y )]g4&(/1%|cbwsĦ=g~> Q{fQv3^2A765W/KN7Lف@8P.-AzuΛ J<"tCoZ$%AEiA(+c[Xxcq?k~/B_GvH2 lr+k#:_Z9|EVLN¦UJI4iQpa*9Ɉ4ߴ1Ó/N:3/=7_V%:դ/RO$rQ'!T>q]ѯ`r? o>p+Iw4,Aa|m@.5gft99.l^6O}/Kn^O,gohh٬i1u A79+g`KO3eD#i^SS@'y =_Cԥ%-I 4[,otmHa f˪M J)w \1FqgA0V*Y,'igzV o?4B>31W b. Ic5ٞfXiivoqa~ aw:>x3^ΛouЫ[{jESUD#ٙb5Ff->M)KFkBo/q% /O>8&/Tޜ}V},*$dzexd/ed9"(|1*:{$,x:NR[J{+fjt-_Vjx !gV\\d2Y\=;Q$ƥ(~MD7D-\$Ħ\nМ,n#}A0x6g|\{JYHKj28%[A {H`rTk[>lxGZY" /)U3ܫ)Vki&=C`44]qY<ʚM=>ya*iG]բ'y- ̭x'ơ6(h"_=ØQc@Z7.ًpJjU91ora>t-~u#r9_F6) u DZhA!)݊ރp{RyКUDv4t%nU晟:Г6qh3wSrJ K YC7~OdV( XWDsW'_y/]ow 1/RB-+hepDJ d<&EjGY 1{#4#G\{W7i'Q=:O(o263Y7EFG"B k ݫxjưz:J*j EڏQA왯yL3"F?bϕL($l@kOܧr;RF*5P7S7`Hfƭ|2{U )4#Kppŋ 1=".xj}Zs?u-*6a4m糵7doKx! F:C K{J|n0!5їA*}'ɪ?+3|WzJj ^ [In܀6$e2Mh8}z#&eW7.1 l'nOȨ6 5ө[w%W9 L 3lB*@\O̶6_'n Ejr6(uv">QzMR%6X>}*keuFPgm#w4HU[!pj'Zz>WA-C4| 2: دy8] $!Ҟi࿐7qvfbYUU Y)2+;!bx݀AHm yyWGԽ͏qh`cV_"[?'}R¿2P{˓mPK-d õIoƝ$2 e&<5y5⁵Mad^h+ςK@aKW*XK[gI@c|6^c<'ϓ`c#$V/5Ie]/]JZI 456`wzKNɹqo&"7bb"5 vls+Mvw&=V0ٜ/GZѠhA^dln}9V-U,j[;ܮQ<S '\RN27AcX@COcXcoѸ";k$G+￿b5붩 9ܫ9~oQQҊs]* mv){S5?lxL Q, iI G.YI ?CBa•ܜ;^ ~ -c4]M]sD&o&ԻXp]@S;I|`ο83)AI%Ųd:b6 `@[I5&bD%h֜8團*#ޣoߢK."2jKct"цt&A>w7Y`C#-8a0 1DƇLF)I9gVB}rUQ仫+*V3~3ŞxKoPmu€w#oVA\Fg*jta̜:NZ-|6\1ac=rOq6<SsUfy>oy(}vAa:CskX(% R؁.znxigjote02Ӓ2Fʷ,DKG@aT\<-km~ 09*^ =U?VY'cq-rv@o zo^eO2F*>Çrtʹx \{8EB.*[w j) A,ь̈́¦e=Tp?(`qyܛ0(*+ ٹ!Qߴy5k UHe)ϯ<`pe]tt0ͥYdE -] u2PvYg[L=oqurPa :5G JG>9I**Yb.&nWѮ-\t^X !aotqAQ\gMb\!;~a!1ۚyxxϑm"@T1A\և6a-˼OM^4vV@"ToT0b{ڑ-_^>ĂP7B!{D̫? >s=z'pꡑ))zɘqӼ`Ag>YBΩM/Y(Ћ[*,GKgfBwS_|>1(T$NMLUoqŧ"ۙdw@3.ru[ᳩrbdg%b;m ^o`4 1tFY"#sU I+cm O1k@2 o_̿9Rʁn) 2ևaTƗj'zS$a75dWP_Y(~&>n@u7Ѝ`"G-/kз>9bZe6]@~L]e{ˀk,Wdxm@uv/p[$S:W3나zi" K`4'~]@C_[՚PSf6s!$h;a8tֻ"4zĆ*%wOzd6[KgUx")w,x50ǹD܊ Hlgov(:$19*I;?Q($Zܽ5i1 '/t9}sc \tIrV!nbXqS+4"C$q~1E =}=8mA=x@j˰!HA9}a՟ y/œI(~Ľ3[k-zFaƁ?#]Qr&{ԞTiTLgo&Kɉ PVFr&S6A\+}'|"vt㫃6o$k+ihX2߀.ȑc> *?b+i(XL-_WIpìjSC9c*_J2獔DqW+ ЭCؾ>< 5+ptV s\!@w^NVձG#.-0|x;S/P\`Q0zS;| zX:cu/rژSmX'.?nd{{TW-Yrp᳠]lkjc4YiôԜ K@x x7l4~z$\GK_ rW˶^~ZcEAsj5>S ٯ2L+ r#xtESr~Sv6,O]ە?N em9^_`c?߆+܀;ђdS Ap ̉ra"PsܐP7+Gj$5dp.vn;N㹤 ݽ:zG B0kPC_&D$u/SM\f~aIĝ0rQV5Anjvf˴rԖSʗluSӑ{SNgqyL&lfύ݉/QBL j#]Ū|!Zi<e טI'-5HI(2p aa]RQ~璮4`16:NMN[!5dCBk@[ii>=趭uX(;39͈UFe||@`9dwJj /:͉ܨ]yz܈ CQe ,Sylvb&.ܾ|Cs^[bd~*k~#L buó&vdji}}$8:T 5Tv os0nwGӫ̸2ؿEJr0[9l4ɿ>Nf/p:X)24*_VJKSCXw_Tc*3j۶#Nr =/".E?Nz, :o^\72_5a=#,ReV嵍G&84X6KRyĢD{kP]OG'{.`5J_Ldz2["P(~1 %(sDXZ7`\vOӌGPb r bciA6/X?ܓ%OyyP Pc4H XQ߬ɶg:'qCol7L[4`?rfy' 4"Ak%dhf-3zwnqaP :Ќw[20|,!}&«}%0V]SNrs?gqv tA JckFBg i"*k2|5׹v?]aӂIFQNg& ʮcopõ ~cܾNo!Vz=Rz»'Tېn\L\kc7Ǹ=lbQ: zr0Yx6Ggi1nZT?#ࠧ( ֩AfGa/,D0f^ Nt6xHvTB>b%hd|Gи|ƷCTfTucU`ck/F1= ke|;U^`Yxk=֒WA('0>7JE Z57VvڸEeC`PEWU#D}1t`ursa;ס즆H:ƅ$9-`r0TyfX[\{I{ BR~}>Mu gnب8eg=WlM0*)w|sWf;9Q2̲ʗEɹs?1EOI5nDF3FҤd@˛P!{qQڿk!Gmo25-Q ";Зk輛!Hmh3}DJQyhmkKӷyK$@FUAG (u9`(v1TJq_?7lu4 [ѭ.C6Pq^Z6ş94EK<}M. =MpFn/@r˯ilf0OuiP҅ t+snYlKB Gf'.H d,s'1&z][vQ8fL_HsnDǡ20( ,f(0QbQRLzWZ;)1/4B~re9ukJ5`!|nʟ󏨓- eg`BT-/ F9ChdfB*g;Mj9;5jd c yՓYsC$i _~>n =@jX9*FKe=69?oJ'~Kx"jhXAyoe>j ˣS/A}fF MSE!IP! TНXXS|cskuٽ3[=+mx+N e ˤ7ד]~d @6M8sn»#1V#성;{l Ǯp]AzF J" 0gsZ#_{aG:#:$f{݉KLF$O EݒKV_mfyPC@}pM$4d (fU}"r?V={Jvrff~e$ b|I0穦K$Г7k!%R7s; li_\#ú3V vڢVY|y4}yHRS+,"1`<&cfpvp ԘOJsl8M3z-I НYZf=22ʖw lMmf6=A.ʤ9‘%6F(sWS2vVj~Tc jea'^W0\{!g)EcU uPAZazњK8oq.ݭ K8k%eL]hb!W.~Bsra g9k. 29[KiE΃ek/aU+3ɻ$ wFq#y qӉúg ٲ˽dA1izQq4V!MW5pNu!clc W8SLMiy@9Z*`ϣ-k/@"bid sPVЪӘ J:֓?Eoֹ]4;,Qg _:;{G 4Zb'm i3E}4hWFD mxTnۇ?18d4*& C߃sg@~=_7bÌ)&dE)&甔g؟Q'h,4{z@ԢT2ʡyx[X-2it8`A= CK?P$aN?8 Ms>AOdո!m64تP‰Ց$j]P3yutYk Eg)tZ4[6daS_>[ࡩ%[zz(/ ? {1:<.TEbW ¦?rd w .96c⌀llV?񔔲!qi:Wi01˟xӖ,r`JOQ1ʕ4tؤmk{gHǡ&IɼOgi#_ġm ̝XݵG_3EbSUKAgG"YVU׮0`Ky8vK%@7>k˳K1gU'RWAS%P\H\fwj`ZK3&-)F@ 07Q26;ޯ5THUBM3fʨ%Vj-;M0{ mLOrdbEJb 5@(PM/?w%!PC] kNL5<ukC׿CSQ0nJZ`[r~pbtqO 6ҡc Cxw27ֶ-^` ͮUUx֤'Fof~qr7lHAC@ED䳲։|jm NT*_'?)$qLb*㈿\0ESX!ĤoV0o6٩ x~?RօcH.'|O=`:q'@>:  y#[4֛h(s.{sF,sֿ$ɕ~ocq&0ʬ]^v`FBV[,Զ$6-j{9#8x2{ ~Jv}rTcdzΌbn>PF'|y"sEc})kt,.,v!|1k CIhdn`uAaXjq_91a*Շdⷞjg0sAh_T}a,rj{{&x4JBe/zvgx9Wke/(<. xu*1mCK:Gt|U/a[kdqU&$T@^쭜]؂gI:Q%ڭ^<#kc>o0_.Zy8!M`6^ˈա^;F;:~'§G d^ J̾{j6w>FpgF9uB0Hu>mc(]]!Bm/-wkզ4]s@l!EB8*k Չd=`e\2p$)G= ^RNy WTŸ-tΧ z/D1l9pLEh&8 f.t7f664 f7~dU@SJa*NjQE=dܤ5b>{hWwWL2ޒ E@q3ɞ_te'mK{CB8q]rJH 䴉YWhInZ~a"$ M:̢>NgB{ϣ̲alnɥ g;!Ö(ޚKaLYKc=ձ9kңR<׬K#zEȈM|E4[988ઓDkTXX?SIKp|n_P)D {kx p[sWΏ3D}FW?}+au;xisjW+08{g+%f&HY%[l8$]'>210pSPWH[a-H.>Z>=_cC$C]RB Κyt4Ś%q$BknLo?4w? t`3b'qT8 20{&4!M)!vB.Wty<u$8B.hKb\\FrK(qчW9cr*r<%4X4PsB{vŸ yǦ;wih6S]x\m>jE Bĸ2⋼zh׃h֝p*P5WbG`H` @"/f$j9ɐ_ۀRȹu:!'1+ c(n#l$$v[; U\ލ/W}3ֈd߅Hި(6 n\p!;KSP"*V&q]x>Pz? g>lC^\hq^KY , 9&$EsF~eZjb>p]ń@*Z"p&Yp^gS$ҭ& h8 7h1 ۠g(uve &;NDSEhiyRV0WF&S^3Ȕ^E;(B8 :0/Ip5ߓH[aѹ)J#)0t4fMGl {7ISPV7p0lv9A PZ6 }"#1T]ڐ7˓- '2kF^r9Xb1ip_7@ VFshM(OuzUBo@9Rim|-1AvTkR`тwL Btg'tNѳv/`,BN.He-t< wy+j7oe*`bc*Y㌜Pdm}LGwO&ugW-o=.QgNf]edCٍJfoeⰋ/I}4{b<*:o0rc<~l6~d bJX:*$z҉>Uov:<8-r 聿v|(. [u}de _fgIrQf^7!W"p-KV IwUIZv<(3;Ssu@nd kS$Ӟ=X$*$O@PҢV ,oT޿T?CdNAa=S, V/rլm l^In(c_:ۄ9hv]S{o\0fdlX $ ڝ {r#;*k;';}LI'·~~%mf+z5cSh`\6m~~H*U -%]TSWb_kt)3G&ƞHlR),`HuV?+MO7t+e4 wm'@f٨?ST}v1['IBp} P׫ڔe< ]x>48yܤ4L%"[ w>OQ(LBׯi+@,|O5n/M8i tk E- 9\LX=/7HY`ZЉᑸ+"VaC#54e21Z{.C߹ei9ph/-eD\+M(80%XgtSOl}U<}\Ҫbgћ$2~kLj1(̯q0I%`L(uĖ$P2uJM}fS%KJ΀}ZtcHИ{&PF/OLKZPze͉SHuWn4-:v;;!Q@p}J?ؤr:KOk[0xPX]n,`j@Oպ,cȨdكAd#/#\ ;%:h" X\XȉM+JjE=u.p7˦9A06A+g:UP|^(ex]tLC. edN0Gq1\`?~E+dxT>|[ rTtM+ֵ$>lhi&Z^fy fq0@%0.,rւhIg&FXU.@7pIRu2^%I%̄~|"ޜ 8؆V}j 3 6iiɖlҩNԋ9CxZ$/§ czKt 6^IZ[0(>g35[ab5TEx=b&/̃< J3:ҭG"{zWg3, H)ǧV+(0?][ӓ4NathA3@:xl HR) D|z Xz -BrQ:ّ$&2%_ ŵ&߯LW7Yi5,m|NQZURR|Nc7a-qt)}Nfew#6!̛6xH%n}Wbd+ 7 ad%zAE#>L,i9.X\a "m,b+R:a# uOGJh^/9'b%XK0vjfb!@㥍UN::8JaQ_=6*F,七bMT5@qB2!]Bw2- m1L \"SOxD 4]'IBl`1jHXu|(6g"ưz+J.*8xD +JmBƝz C]NY͡FRnEin`;ƿ<\7jvNАu'd8_Z$\_ٺweR~6kCLoΤ:?iߝێX UBvW Wwi޷fZWjHdv@$x6 T,0e@<-rK)-f;nPrm^č5#u[I`aS.0_'rkfPO]:<ҧ*u9(][F? EQM2E<ki\ƛwE m!OhN6/9^H !G.ugBK/r-toMzujūdf2]Oi+~61-wm'E"~Е$e]V3k/S_^p]*_=)9Lp׷&=\-#Yo+19qwߖ%(>\<&rGjr$Y񘮡7i~= p)CURy~3r_%'EokɡkWgUѦޱΦ (/Cmf)*[ Zvf!7b'Xsg6rOޜ!r~aa]Q˙)H|kδ:QȄw<-սMuE 9ƶ6zڲJ^r@ r;5 !i~mIb3Ou{V"Vs IЁREIW#0!/,it|R6i%DT~Lޱ5YSحBI9Yp W`c*9;%[,Bl n jnE~ o8e[Jۂ^Pg59 '~ѺD_P 4z5z;O2\GKatvZe B}Y|?L;ڼ OS.cܭ;*ݝH*la9Q~ {=k^{kH+Y -"|Į*cnS>z#k}rS~) M6[yNNE>S)U!M:v4(4-ـCZזG0/dѷwcOɕQ1a]3qp'{MF͐RjA °yݠn |6w&e}c:ګO=dM䋞5;eGVG~x% z`{?8e54п_4F́CT'{Td۫I."p6D[V98K]+IcŲ#6d턛$aQ6bG`'iGKv;Ah`<:ߝFب=/b Z_ҿ1}q"Y !2&8*^ߚg ƿrqir]7 rgv<.\ms.XZB_pɾ]n8k:-"\>}98!U96-85jI% 52α*KyGxAs/.s~̋/ϴ|3~ԊIz5.k_y8Ƃ&*-rdaX3}ver?EluX6VzlR {~cN0z$G%KㆷY"+`DoeNmW#XO03e+X3?H+0Z{!T.uC&MA0-0es:" P_IG ӾR^fg,ۅلJa.1rEa :Dkވ"pp1Z5< D/~-E%;FM/ZᖠNᵤ*EX5/uT"2ӓ& WLƣ~DLCnjvCPl%f)!8n'pLrY@^뚻k:bz{w{A-ch۝(}G#J&7Opˆqn8;qO|FH>˜ȷ{HrqIhEj%RZeE%-+*Qݟi 2>W5:-R_N1Cqc#{@/ ;`N}ci~}RC <(4E2;v؊0m%-7HJl\z*=Ԃ eʆB!F%n:[}Pz!QP.U-e8k2mk]LU}5HM_<<@DYo^ÉĮ\׋ZQ8.IxI6Yp6iUOBϘ6R1?i2rEX؟IddZC2叧QRTq/^ܲ#ZI(|eBu,. ko(~wۗjJ2eNJ*eJ1)×#3[+2 MoretLDم($ OvBeƌA(iX󇀭w{prMj&o .EТ;;+6F!&#QDSj8J4\Rcx 724<@@&ӁIe;K(Fz(iU64Ex9^'5z4+c9N ivJpelЂXP%/ EGF$" 3ӵΜؤeV'\Je}~+4.n<|J`|?RBl(SNA|?8}a_yP;` S)K:Qbb=o_Uշi'eȏI!OXïf-GB OqG×p}TݐSq'۰ggmc865j'G7~X^.RArEEۃ D҇{՚zq'5nl\ %lu`?iąk2 (~vNCF\e[ ֢^1[1gwi5OQao!u Mb$04cf)dcO-L}DkNX`\o, XU6РBykVg"I." P&uhV/)q.4 |Tw 1)G3X“eUj]yK,͝!8"pup 1)ӡ*G>)G+Ӱ`mi_gC#J&b<KbNV5q0+­W )$>|U $K]>3z[\۩1~Td$z-s/e\ >W)Oys`4-;b%0ơy7EOnjlgl'L\he[g%[#KLTnҮlRQG< Ӿ&h4J0t29Hj2G~k0@wZi - (4[.7P 3ҁ'aA]p'jĭmL]yr̼_p zF(r/B@& ':BtM;=XǢ.Gp^91C/PͿ엌~j(0{_$p>W{iy ̉Ҡ;CGm” HcyEWٷ uWg;rV5gvK#_.Y= in֘8ŋW#KE^aȦd;u{,ZtۙEpu_ bOc[|s5 /M6XmiBu:glfSMυ&+km$Y嫎N01Xei $O_ is?(#Ϥ񛆃7*{ vuԥ52]:J:x\TM)d aLd|  A%"bv~a9b<Ľ =߫?<$bN Mf!q{{kpYS^ĜoSw$]r^2Ҭ_x6Ȕfxz~kTor}rI11(mC-LA2!J.;*f0WPh1NЇ_xeCYq[ 5OUʂXY7J%,#DOE,㗮ֲg"2+s=kvA 0%MMqYTIIKdUj%!{3U+p !)oD#uOhET"ocf_CaFe,o%Σ#Wu ms}QW=7ekm&kaCK eZRf]fϏթ`;]rw/k$d{CHwc{Wugk宏jI5"Z48ދ8[3lE{b܁0Tj뜇!GLc0z>awˆYV[Zp{^QbjCO 5bTX:ߒn#IW&Mvێ]T?q79U'& =BĊSLil$Q5|Qkl!kv߾r,Cn4tOBmyOy_ 0/bfMdFH@RYZk> v8dP镇-\M1 4hv6s-4sbc9;ɹ喑q@#4l6qbȿD]]Lɰr$u>.X!gĨS& %?'p=/~1wBEM"V,Z J+4y. irP;eZT/3IP TǤ9[ZxՎ/}otdAL;]z+xF-#x3O™) hݓE3W}gAeM?d-YYIeFcyEE#YPe"MR:4+S?&Fmg-6)?z}]q7]HDwJ<+.f,c |%Xᇈk.@/;SL0FR-E [^B8~ &j;x#&7\آ<"& ܀0jS IW#xĶ{xםSR7]֪1|OgϘn S$WN3ȯK%)]6Vߏ l,7.w(:,%@{kN+ǯEHH):`7LaR̬AT2H{?gAu8`ગ4M=mS%ZP\B_^HN0ʀJBJn]U2ۭ l;AJ2gu =/꿃NjjeKY= g($n{w.%F-yӲ<έvt| ¡%q "/\-KB}M\9f(xD_B&B{ng+IEpֵPW.$;8jyfgG,JHnCf%,K Q˱w8+2D,gaSƯ"@R)1a5Q=1{;rQ+`S"$[FRW"A~L3VE>I [>+( U󉱙$=@WxSm}e 7X5'c?~ Q_5 =>zNj#ZzmB.0RgqkvD1wJyOjN%&R@Bg%0]\^mHBՋ1 `0jq!Tt։b yVbЪ ǔ ^d=Biid05cݣr}q<30TY[t{ mIUtK|Ǭ.[" 4/kIPeMo׾5|79#Jp5i#!! }Amu"QV/[Ry,&"6WqM.H Ȧup>HQd׫nꪛQE𹥖*) HZP:ЫĦ`hgȹ mT8 EY=iwPg+ ;lin'{M{D)7EgSzմ_yD]ϩ5jWfjDbdTpR+%+fˈH8d5@7$r:=Ƚ`ӆ:&:y[6R+ S)߇x;Ёj #pa@Ex# REP$9 D8[TbU)Fͦ/[S0_QL` _@P%_eBU_4**Ik}JE-H2X{/[k$ `ྞ*ߦ/S4'J0i7-5)]Cwj$ܭqsԔńdI5KYȻ T=։ R932Kh0? ZE& Ռ1s]_\q7L8Tszud#` s%a$`:do9XHɭ'k Ж>]|; >  fZ5HwSEvM9䔎XgǮh>|`c(f'-Xv?>ѵ9܇0&Uω}Di6QAojwnȤ7v·?B|! a uBGy5+'S:C_Ph'Q[hzv2$A6K%iڮ^)=l s3QB4mAP|OM~w\}8?cig me0BuźeӾLف/SfGctr{Gʉ|f YbKJP[re>Zr<6度Ѹ)UKg^6ؿKw b6kuv{ST; kݘαp.Rӿ4.,4Y@'C:\)h˄*y/)yц-l x'4{3Ipfkw #o޵l] 9.WfjSf9͑?{{Z6kRS+ $S?4>m#a};biyy)]UFD ڥdhC_ \BcZۦ /&6bq*l0ݔ^1C xhdI 0lvQ!y)֚+>P>Β3t~TH#&Y7QakfWb0P `6It{M>> GZǣ}oDAV&e{̡5/Ǜt`砠=ʿ 97.2A䦋 Ұ Ұה*IC-6A05_1Q2IӅ:?V|K88 7 Ӻ@}A[u,58\,p^5s>ƭ8+1 I3\%/`hyMb!O{G:bc(6)#={F/ !-*ăF3^uEV0k ȴrD:>V^+?S.I]qXfR(#ɾ5T 8L!((diqX^oK撱,{JiMh2]T݆]=Q l0ĥj&B.<,k[\)Gdv>xĭ`A%2 )$A( PpYE~oCOjM;̵@F!&s e v#&3"#1uN4 Lը1¹0[>9ek%+@!lSá\$Ej]%ck |ʳ5 tQ6pUf^"'Z3;죑 W$p-&iJKi2/AuŨȩ/3*<ѐo/P߄ *:j_rmTup!g?&4T2%T.9Uu*|r*KT8j/gZy~>Z(Db$KBseȲmaҁY̹OZԴQcX ,03*.<%%k@'5zH\DjNג,4Q3~CdJuon@6[1U~i9PYTHS#{I?"%;dٮV`@弳x@p34?I2U8;3D@0/t3m잫2L.d~ʯ<oyg~ű{O4PHW΍s/x9aL2; {a63/ߴx;D#c.ڇ֎ő!c+IBd3.Ip_mU8b*'ݠOʩV.9 NA)C 0\[ NAUo/w Q"sFZ$#+V*3l ;tW^8BaD̓a}2W@GZbOm}O|O[=n!۠0!uEe cA^kLNaϦ+.4k4QDϤE@ czeS5ڬs]3mO&M>&WNFhSEKt8,N; C'2/Q2)OOXǔ¸`$Yļ<Ѧ.k=-b(j{nbc.R;zIl;OLv 2PeVǼg!vo9UH$5-DNb;MHXZDAr"L3f[LQ J'I5{jio$!Zpek-L懙;@\dtNwWhKv)&zY|9B8C:nn4O'w}9cKT7U%3Tk[=&xS5º}ra{YV3F`P}M˝3B>Yu1Ln*d1sBQ䚼,bjW҉Ga){/GT?$~R_skCm9U3]A򫆖V:[skaoYH ݸ4Pț6:X$ƶXȑ0$SqSJ6;Tߎ9$8!xH&3$wB_AG} `l O $tBnMmȠHm -<_Yg(D{ Hio-0 .O&.ܛr}ҿ i;Ţf{l%Y]#4;]S+Vgx?ߛ{X&K쨃톡m"|S>BRYxxp(bҋ&b =51<`咋8;3[hh/ha&HN?z =4- X Qel ׯU^ ,%\:a(g N8o:AګۏZo3#sT'D _ 7LਏEELP[ֆ:T@H$4}WZUր=k4S?(!gG< |>h/Z9yC">!6N}`2d]".ON(.bG|lH^حۺx'f}Ƅ<9aUoavW`̝ 1~FWGܬ,l>z!Vvk$]qV/a"r 5A(W?X gKAwCЍ*8HTĎ p( ll'Bp/cL-Wڭ:];Yfz_ۍ?͋M` jëd+UZe^ZoF=5 )Odlp]%r*m9#@ۉ* , NzL|m't U̦說&`kۤ`d0zN] dlD շ'-|!c&ɊەGhKocp-}cB̡q3Z!Sŭ8*]5C%m4fˈm? v>X|0T IC_# -b33Z+)n r2%IXE,z "'}  Z<1zbe-s_?JF,bBB_nq +$&p% RS[ &4d28gV"oh9]PXǒAqs ^-N{fs/>( %3*`pW'o`jVm\etQƚ (W,EǑ+.kvX^ K1Šx>U 2G9j&.8rnF2deiI1hMsjkrU&zLoLB"=h g~@j h=m0BO A,uղ3ȣ?X%q, =[9gg6qc./V72ZfiÓwn4+7ְ j͉ u {2r!M8zFdBR>BGm| y$S'H𴗽 mGDf6bS: bG}=_IY6!m@ $ IБ[)9Ch!kc{3'ٺ~]gW곥sNg 3:M8H.Ck܋(;)yB*& [:; "|T=ߒ@q*Eb,3̈=yyӉe,jD=Pow׽}ϚfwL t.xaEAhI#gW y9u\tyd=Ҿ`(Gn݃'4NYMœZKz=1oKSX˜%bp 0r"a9ͺe$@|uгd֢He kYp$ث PӃCU]&(FzT*,̥: KnňLm"bk6soz] !*n۾u d' wY85kpSI-RCn-˚G4!UX q(CԪ~A}a|hm69韤8QI1P##+m^y&P- sC633 cWKm z5j}ErHO6xb=3MTNG`x~(` iWC 9#K1ÜID܅O!JV{q,|$Kkc9xJf [6M>d`dD#H2g`ũor1!(ŮùiRpZ/|5/, ~j*p$Lmy> rx CncȭGD<55 ґp7vrMPښE jK[M~XEZ<6DyFj(O 2|˱!n!]W#+ײcc5cq/ !Rq~`n"VYazc4f˙vCeKR%P]g$mG`hC)&jBkRrtaCZI&i A"IBoG╲_gHџ:}^KO~y~"T, xK.;d+grU4mۓYSEM<WtY { h(R'xЁW fdBIN츪5WLkP^Zl _1H^EAXbۼ->z!8ߤ!3}kp][ kCMZN{[^4STqH\5P=l{IgVSY-"AM1am]|`f kuu0#i8Ɠ<<"<z \1Ls3~^"\cIK\y~? mR ;R s2fo%%?SY?|_yƭo6rQu/{jBU`143ϟb'痟Wh= >^qRd1zy0 yIWL0ą[M3$bhUV4RIo9^-i!>/+O]0 k>Dzˏd4` > 1ΒM=r U;'M?bGǒW%NO4z "]؈[H}Z^N"DsRU mESeliDRQOS#f-W:1Cty +pTѹU7Xf]$j tZpuxgs}9е !5RG~¸30CCzkݴk]CЬEMX4vIRRoZ7R;k$&%Vo;1(<5kɠ)W۲RSF;U!~З &Y3>ف,Ok]_2mrtK=vlyNݟԤkza\z2r `?7j``]̚zVMB-Ynu'k3- ߻ʉZ2w(} et?Jk/Eqs1qmݞD$I P@ߦh\6Uϧ*zf\Pv-6:Ed[znOIkc@yҥREķfЌ{F4ja7x ]˔\H?5L,:%RXY!Չw>hfE>+.wmȊHW`I"P'=3M,<,&߇ϊOT"T(\R̘ח2/2h<kE4:P.h᭨Zs7*y?Wʁ6o61e#1[C#KFnF{(:P.\Y g7$AĀhA()wox?Tt j#4}'BfzCpE$b7u=!e:v#yjFoU~>2ϻr9gg ^䤺K7)O0m,06a-8d@n162T4k'3G傈TRBwiL2k/% פcf(Ŝs{:2Av؏欯Qn 3j726x(g5$%Q~ I[mj.ULG{N6#ٿU 2VLFGSQlW|&Uz|`h<:ܿVj8knUHo ЕT]j9Qgͭ2oה[z$;RN${~8u 8x>mޒr7 O ’(x?L_?CR< Um|4bEYɇ#$Owkٜ={fPjy/7ɶ;˙m)IY`ӷ}69n%]._G !.=߄\!uc%؆EJޣYJw`U4<S}~J`r2J5h󀬌גaQ!bq@>XV[}xqJ S84YjFz 2fW˩Xk@ꑝg!5r1{g Tƪ^D@WD|py4hD65^vq/e]0ozo7adJ`:-u/`nKP;`A 4f~a"E KzƼAR'IJ\NtWq^^A}N v"JHxN\IцU9Bw^)KX.EP?/ NˬOAZNǵu9 5-|%M0_oP"͙?Ew`Cmx|6=N?K+K0Ith;}m$;>kmq)os_iᲊ*Z/uXK]{fhn oa9ph nL7Y:.B2[{ -U4(ߩ.*_Mt%Jr_%iDM =/p@P7C<6/ckQ /kvd3*^WSjđ[$bv-L=XP J`G`6dnb;/N֖p@P-5Rx@\>CN?R8$AN .SSKDiv~kd8-5 v ڮ=:'g,fZ%!)~Z H)|_T0&fFPV̓i , m׎?'@pSA'nQ_ h Ev}!Ga53{ [nsdX`K=^Uڊϔ/e(;3 @ !H&SڝT?Lm_ʰ FL#UnZ05w@U3#@GSPtQ|m =")2wY.V k\I-^]f4;*E*pZ0U-_J6boa]y'd89Wb 0ќˌi G1dNI&lD|r]DB{[<4Mc@B >-[VvoSL);Pu֟u6gdķ GpRly`{TjGD\WI#12Ѩ1 GP-*4e A:MY*[_R;f _`r~wTZ;ڮ&NpǨPy SMJ_ A lD4GBG[r}M=8"Kɝ/h<# ?BK!.-$rD `U&=s`aZob@?PIBY60&&sX%7¨Hb?e̬Us>c¾I7 bMQjHTX?t:dvsv70`ןm,"~*{@8!1k/AT_;(`Mu#R:`U%КaKz}? Kۿ6ǽfT^|&Qǜ@1h+^"SwV+~bϏs1꡴,fTux4 be#\]H,V4ҥ8pP 7A"#hk %XR(W Vڇ=ƻ M{ZImIUg!+,Q н/zW YZfastnetmon-1.1.3+dfsg/packages/CentOS7/log4cpp-1.1.1-1.el7.x86_64.rpm000066400000000000000000003433441313534057500241370ustar00rootroot00000000000000log4cpp-1.1.1-1.el7T>D ,0@b3b8fbfde4c09e84a024973b517b1e9347a6519eHN3.r|>J;,$>;?d   *$(7FN ]s     ,@tY(89: >@GHIXY\]^Xbd)e.f1l3tLu`vtw<xPydClog4cpp1.1.11.el7C++ logging libraryA library of C++ classes for flexible logging to files, syslog, IDSA and other destinations. It is modeled after the Log for Java library (http://www.log4j.org), staying as close to their API as is reasonable.U#buildvm-18.phx2.fedoraproject.org(Fedora ProjectFedora ProjectLGPLv2+Fedora ProjectDevelopment/Librarieshttp://sourceforge.net/projects/log4cpp/linuxx86_64`g>A큤U#U#U#:U#0b73e2fcdc68895508035e2d102b442fdf20e0f52e7da8d55ca5166fb166afeaa190dc9c8043755d90f8b0a75fa66b9e42d4af4c980bf5ddc633f0124db3cee7872975623d192679b7e36c092da679a3ffd720db468fc6ee88937d8b3a486c66liblog4cpp.so.5.0.6rootrootrootrootrootrootrootrootrootrootlog4cpp-1.1.1-1.el7.src.rpmliblog4cpp.so.5()(64bit)log4cpplog4cpp(x86-64)@@@@@@@@@@@@@@@@@   @ /sbin/ldconfig/sbin/ldconfiglibc.so.6()(64bit)libc.so.6(GLIBC_2.14)(64bit)libc.so.6(GLIBC_2.2.5)(64bit)libc.so.6(GLIBC_2.3.4)(64bit)libc.so.6(GLIBC_2.4)(64bit)libgcc_s.so.1()(64bit)libgcc_s.so.1(GCC_3.0)(64bit)libm.so.6()(64bit)libm.so.6(GLIBC_2.2.5)(64bit)libnsl.so.1()(64bit)libstdc++.so.6()(64bit)libstdc++.so.6(CXXABI_1.3)(64bit)libstdc++.so.6(CXXABI_1.3.1)(64bit)libstdc++.so.6(GLIBCXX_3.4)(64bit)libstdc++.so.6(GLIBCXX_3.4.11)(64bit)libstdc++.so.6(GLIBCXX_3.4.15)(64bit)libstdc++.so.6(GLIBCXX_3.4.9)(64bit)rpmlib(CompressedFileNames)rpmlib(FileDigests)rpmlib(PayloadFilesHavePrefix)rtld(GNU_HASH)rpmlib(PayloadIsXz)3.0.4-14.6.0-14.0-15.2-14.11.1U@S@SR:@QQPO.@OLOM@MQ0@JjI2I@IFFSteve Traylen - 1.1.1-1Fedora Release Engineering - 1.1-3Fedora Release Engineering - 1.1-2Steve Traylen - 1.1-1Fedora Release Engineering - 1.0-12Fedora Release Engineering - 1.0-11Fedora Release Engineering - 1.0-10Peter Robinson - 1.0-9Fedora Release Engineering - 1.0-8Fedora Release Engineering - 1.0-7Steve Traylen - 1.0-6Fedora Release Engineering - 1.0-5Fedora Release Engineering - 1.0-4Fedora Release Engineering - 1.0-3Tom "spot" Callaway - 1.0-2Jon McCann - 1.0-1- New upstream 1.1.1- Rebuilt for https://fedoraproject.org/wiki/Fedora_21_22_Mass_Rebuild- Rebuilt for https://fedoraproject.org/wiki/Fedora_21_Mass_Rebuild- New upstream 1.1- Rebuilt for https://fedoraproject.org/wiki/Fedora_20_Mass_Rebuild- Rebuilt for https://fedoraproject.org/wiki/Fedora_19_Mass_Rebuild- Rebuilt for https://fedoraproject.org/wiki/Fedora_18_Mass_Rebuild- Fix FTBFS- Rebuilt for c++ ABI breakage- Rebuilt for https://fedoraproject.org/wiki/Fedora_17_Mass_Rebuild- Remove useless AUTHORS INSTALL NEWS README THANKS TODO - Move API man pages to devel package. - Move API html pages to a seperate -docs package. - Explicit pkgconfig requires needed on el5 only. - Remove .la and .a files in install rather than files section. - Use buildroot rather than RPM_BUILD_ROOT everywhere. - Add _isa tags to requires. - Convert ChangeLog to utf8. - Remove api/installdox for installing documentaion.- Rebuilt for https://fedoraproject.org/wiki/Fedora_15_Mass_Rebuild- Rebuilt for https://fedoraproject.org/wiki/Fedora_12_Mass_Rebuild- Rebuilt for https://fedoraproject.org/wiki/Fedora_11_Mass_Rebuild- Delete non-free (but freely distributable) snprintf.c under Artistic 1.0 just to be sure we're not using it.- Initial package/sbin/ldconfig/sbin/ldconfig1.1.1-1.el71.1.1-1.el7liblog4cpp.so.5liblog4cpp.so.5.0.6log4cpp-1.1.1COPYINGChangeLog/usr/lib64//usr/share/doc//usr/share/doc/log4cpp-1.1.1/-O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector-strong --param=ssp-buffer-size=4 -grecord-gcc-switches -m64 -mtune=genericcpioxz2x86_64-redhat-linux-gnuELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, BuildID[sha1]=0x0e6078bc2b04fc68c4e28e02163d6dd56b9d9c7b, strippeddirectoryPascal source, ASCII textUTF-8 Unicode textPRRRRRRRR RRRR R R R RRR?P7zXZ !#,] b2u B0Rœ]ƎDVSG`39EoVׁ7+4>>2$elN.v2 >Nc~g>'5?ۭ?5!͜vc=G\MDcFX]5%؋(K F3<^툎JoY@Q)I78vDV )H[b}ZR'P:_<2X#%"Ξ@Κ9BHbz?(8x)'#nnxCx jpt)X s{z6]7퍼̼J\ZUn;> s9K8&o砿B:q>|TV_w״!4,,%eߓtklU$GK7F>ek/^mӟNѻ@V qh/LL9)+ WQF @L[+i/ۃ77A_HL~ !zt=I[p}2tOq zN'<]|\\$n_Rdyۅ [;>RfR[?:cOqDfecҨHbҪ;lr=LLQ\hP'?p1u ƜXpy+m4`3GZ9 p9+dאIrpng c.Hf))Y1ۄkuLdhK,8]_wwɔ ":~:B<Ɯ]/'-f7h|[W\.<S"4|1NɄW alP Y5lOc[)/)`Xc&:ڂ3jرB$>0 iNR 0Q`ƓK|pdIx]G*NN-.&58Vf.WVrBT:G]"> {o9]lo>-Pv.u,oسz~v8=fû >l~EB'8xcS`BE}CMdM h^$gcZ" %oE;}$Gz(W:v#%.~[uUylp~o&jyx|am'Cxf]vB꽥y褳pŮrh&VR wL*"i~ D{BXȴGj}La1TU3`S"()i2gZ8@pc;n-=;kyfxVc(` Y)Niy|Lp ]񶼌A8̸$p_˞MKPZ½~,F:kr'G Z14_W%N'sQٚ}!Rnj7V?Z}9ٳ-#dY)Ҫ$O_4W#3 I]s['`(SpE{ӁQ iQZB2w睖RaDz P*UiKtCaLXu0X! !A7H =E[ 9H̿:)NFf:xB[,5NT%Ūh7| Rô@7ѳe !OPh*\3/{j nbF-13Ď nXKgk1qIko5֮|XC~>Z*ɑsǏMTa PTz*֭Ǐ-!eMR:چ (^A7-ŋnK e<)ITj!FZ.~Rr1J7  Y\$~lSUnyeʗ?Vp 1, #1O{( ǭ>ra⟿_!BPۀ2"ߩ:kq?It;ťcJ ME G_-(Nz[S` %U"2n+^:(~5xO <'ػgG9ǿ~iȩ:8e}[d4rvTMJGY6ýli(\fݿ軷zQ/ tW~6X.82>oz+@u \- ]0"O=MhQ12KSYmdCjچ8/aI1e O8F/m{N5^a>c^I RW XLR(C_ۅc8GݘT0F ދr~4~T?}_}ل1U( b9vc-F{lFbF "dZQ+ mͿ"%6cD]7QEK lsN$˩EGR 1v`$6CxjXu䱇jփ`[ĪMF=Sv TmG]) !`lEk\!b]x,tXoUF?>R5$ 8]:.A|Cdw:W~ZvI](>38"H:~v-CIѿ.7L],{`%½BwWaUϲ7.YzCÜTr)}"=]RI X"{# %j0|46VOpdk$IgeDeCB.)4ĿtuY>KqHӓ+ h{huٓ" g/QKʱ` D|[p {ceH @&G{qșػh5~T խ.Zȹ//~\rxv6GC #(_u~˦I]+c oǒ۳ӂZVQ75u uo)8:R۷eU0JFÂSBɚC{gO#9J85:‚Az5<Vn#첫D}PE:t+M&  u +D+cյV Ά6M%'8oM1JV/zc]HƿvزrﻊM`>8%iYM γAs#± eiau0HZKPK +VH/!f"װN^ԧفJtBZh$ZXtKF/#d-$r j䤰5Fo̢p5uPck,kglVj|߉8VREjCD[4zrM~db9 v[dPGXuU/FIr燤xa WkƤx<*z>^M;kv5 rz׆1ctn-aǭ]aRyhaDn6, r1l6vގ3(&$0Q[Ҡ n7^, -=9]-ә?l\MfU&"@Z%'LIb ϲK4{R%[ O{KV>x̑b MͽzG!ώD]\p]M/z>-3T<@MACCvݓW7􏗷K] ׫RM>5($vgP&H6Ts)ZJ%z|B. QIE y%\!XP/,D`/#ǹNF֩jSB#6ޭG4neu=r6I0=`[<8&Ix&Rŀz<˼Eڳx5LL2(&m6z"p][=md~/>i==W[삖FWC3ыp7_.~!f5-W3F/ }36^ƒ1n\e!sKo$H{)|*=v/ <sE, >& 4pAQE2>RED&W1#tP>鰻JJ$rmi2pb#,uu)m\Iy+֏ 4 +=GzOPp?ϲŎ2sÅք~.nU'S&+\G@_TO:lSR~zdƕ@Ӟ3B(pGڒs?H4)-"S@̤'r.,gYq2z1](v:tr8تTYכ][Vxmk`NlS?[=r.v0H Pכ#ITd-YWQo)gv"! @J[Lv)ɼ5<L|1?B\CK]_XC^ظIqsYчtig,:c !#:Ym+]qM^*L{!fU"wMd|[j{-܍1\1@ݽye&B$;<έh}rWp~MD#ehn =u=ejw!ҒåB9I l_#uXyMHV8x(rD#NlQ'BvESGr#RMB5&'0= r 88>>=|IwmVNѯkqKq5Ep~(fLEO/(ʛ &Cql,3^F#lf ՞l~rNcqAEPoqkŪk%qEǡ0F&&+(-iM+s/t#OMM`봕\G<6-xk!+PXB&=B?oaDgA _jnE&\rA`vŻ C &E驓YfmNcڛh&nQgԽ[ւƾ` <7ps;Ym]z YyLCڶ(.OQ'c얻6}'Yv@+#&ܑ~QLMfA~C)9/ѳK>&H#BB T_JU N"(N*J _Mlc̢͸uXj``CXG9̇Y5[^8AP@yhR=ߛxS_ؗ%{8~pYxTNU#>;Q&\>V5zXG+'ʒPDQ80rX17U wzuPm Fw ")WMzA7ޱx R1]g sFp4NpGFX)ƴjJm˽ӑE| G(Js@\xIt (,óĐ\xnd0D sA!Zb8={-'pHūЏ*]&i!TcA}`Kԧ6w3D CC{vU]NWIiJrЂ G"{SA +'`1ID뇐]~vS G[1M(Ń<2ech]:pk;w Af6RY  V7z$r%-hYM/}Ɔb Ib.AAi4Cq?8NQ=Lݮ%)Þ "`pwznEkyq !v5Oct.-9 M8t8K_Iÿ u| g\H=G;ls}>#J$놤2bút\W.J B6^YGv@%+yj,c$i`Tۡu%WvG<22Rܒ|]2~v`I͏_'#eDZut$>\{JnH7\n7lgzim86ZWKȴ0Xkc+p7GJ wTʥު~^&MwT@u4q1`u9\GO"v>Mxd(`}60B 'au4;+͒FdM)_5֣JT6 + e@alZ-GƘ}zL9`>=kHڞS- cC tĈV7aGi`i 14?ҿKx+m̸QQ$Tqe7-5{up}ptlwe7*XDRgsGc1;Wx.NDsΝWϟK]׭G;cMR? Q>| ڽEp"Bs+6G0%% lJF[إvomRiaT.La=ϡRٽ<Vl;9B5nHO `L.L;ȠrH֩εDݩg֜{lr 8no| Ag8ۏ 9A]4 5ι+*Bz6.QbG}W9U϶u n=0i &φ&K[ Az `Ŕ:=2nUAMcXW{"h5~{a ޠG1=z]:$lL$i?i?/IHiAuRBMqe*P#qs#_ C"rs_zA81"R[R{:Ӯl{$0a >"߉ 6l#\:@4.x% W2~g[OثӾ]:ߎRc1LSj'dMkeΉͻd?y;ITBI7B!K6 ]Ldƈu %B3]L{X\z2d D?EϫVv+=Y]b|W\>Nқ'W-=" 1V.)fNStF4}mRu\' @ӮdrE(; i횾OZ1[!ho5=95-#yWJD_l~Im(/Ɛܴiyjnyhܸ0xb=|V8w%P?4-+)1pJ/ri{Q1TahO&ޞ6{#T?xESjQPv"O`ņ/6ʳT= ~[nZ }K@ACH@CفNo u 05Yœ-=:И4L+3:Dj2E}]>&ajQKkh澼@fz &! S#O;.3bO֙(d)}G+i߀/ %7M![ruD|j#*A"Aē5MPt+pD츖|5lcb@1cX/Et(UlIqk*ؿwxNT_Iq(ka:hCLby7x' lP4"z+f{摾tU y0Ąwa"88EuTj0P]$fXw2@D:ۙj4>-ePU4| )nK;naNUH>7!]𸔘.c)J㘮qf.Q0XegJf}l m6"Dؠ _"{8>׌k+b \AɐO*yG37c\<UA%1XԈ5/XYi,664EN4-Ӑyw򥸄Tumtr_<`< PP6`f 7+z ^TGFLѓ]wS+K!j &U^ sRM~J}$-6U$O̚L:+F5?c?UX'vctSl1xk}[6Y@dM92'v4)@!9qff2|9[ ; EFYF^n%8`=MfHu%r$/eB HRG n2%/wwf"=-$$aoƖdj Mbn޺K!4i{G-hi`V2pM`XN^Eq1dh{"xLC _r"VPo(-a߻.{6^fTad baLc_In|mVq-R!'2+Spk{FZ -t1,󞎅֤F߀؍0pzB0-,({pqe&X՟+3Xx15!ڞ܂˙S ނ.+km[Qt 4~ X<7im>p lp(G~cT1H5=|Y#Zv5/ݰu8R/y nqQlbDN>"!Ld׀Kg<8QJq&mn0uq0g>?|VneWa! ʭkS!HHqnxQxE(b;D12쁟bo<~Ye-;O~c}6zaHq_rn}zֆwEWۘ;W; -G/}DmLN=l <9,uS^T^t\%Vs僎nNsSKu}dHrB$p,-M4YpTąڳb/,dK.O{Y(. n ,LM"<0 _J,C]Ҵ hp5@UO0F[MaPUgB S6_uUbS59%Zߚ灙jΏ!qzfЯz|T5HB(@N{%j$[Gq1h63DM t<wYE{e LgeA|gS9>̤'td6Y6XƸW&-|=5G /F>3W,ҍxRz=f~gX=R/䷊O }kd'q@TCv3eU40OF(TӳY Y'9x}9Z޹B;*pRR(R)Z}V$zdB2VYIP 38TS{J[-C?ChLB@oe9kvccН4y $X]po_Χ7՞sx/- %Uތ_UUGb MJK) 1 bA]bA? (Hh[OD),&A:H՘?*\lD5|)x'^>_q#1,=[LۘBG~m\8pya7|a)!ްcc>YnL0z 8~!}Bb@o"PO%y2iWDPx+?RoН6jU֞QŘI Þ˜ﯴM]`uc* dZO )cŅ,~K{.rR]y`KTZa/v+_b9ѺG`Pcіϛ$;Jh`,jRPp1 )e(ο)f V0ˌ4~O:cy*yAS 'xf Rd:J7on/኱/N%a]HyLi0 0)J88"TIOS* Oe bEV<[˅_X;hRe/)).HQ0Ql]!c5+@&.ȿ`0HQtloV=]nf<20=|՞jG 1Gan4[A.6Uq[e1_z W-t_h *Un* Vϗ6Q-p4,g :BZIUF,"!WtF%rPoad>4~xK䕇x~Vs;X|<#)0&[ZMh4VH|ef7}j7/@xTtvt"]=;Y@S(jf$`And'#W⡔~h? ,IԸ,6'fdO$/cTn1_I`*dFE:; ?I/(aKxX5RJX4KG-/X՜;tˈG‚1驹̗2 [Z䋇Yjy Nr͛̆:">)6_q5n!08tK[xe^V&iĶPl85 M.͗FZٚc˟vMPʆ8ϣ; LIF6]crn[#BM–TS]hot˚厭q7weR/qQ;\I_aq!_2 ĎMhc-d깧.[2/;Hr-3(s6tRw\L 6"Q=IwM&ZSP'dҵ{PkUbxSRj f?C/>-SGTʏOjH? 0W?ϛTb@0NmGj2]lF۔V>22_" # 4!y4a7+Ha ׯ{٣irX)h~94ws{|-T>g yxLN hoMU{v5nj`Kהw^DpXْb'3uO?d{YNHo@u$g4y 0\g $n4-~Z,긳ܙv7OcC1v яYPIOO{OD0!Qd>dZ''F}v-v??i`ƅl$x\WZ$Ls]τ5~aZC˘LĀ|TgIʆJ`A]+L2Ï9孽ҽS u%d4=a}e aYD*7mg={ " Hء@TBWayvFZĔIdbL;Moضdu#9$@_& Q1CM!&V71 #N]ݻ'EJ#+ۮ@%&26ce2ub2-'NT̈́)kDŽdL,yG*D-T}(x69zaک=w+lj'L % 7 10I>07K"R5UmzE _f୶twM>f9wZ6b,Q" Q8-E0rW*.2oyc5!@vt6"NP|9_0yUpcc2 "\'|`.a@^\J>V+%pxLlhT3~l` /_D(/^MA Ѧ{C4*狶v6 7  MsVŬ/6F*_8(UB*þ͊l\jͬƅfe#bpuAT("?y=E|8pL}ϼ>_Q@Gh;t3(r!u x-oҏ¥KMK FbRXU 4'YYR}#)"^KJA/ib:8׺"*z#wg^i[Ab=q7dꚜَyHf{sx=՚s&"DYmgY)K Ňl;_5r~ο~l oؒq/΍Bfd`5)x~~')A7{_ƉpK=Q EYU40dFEՊc0> td5qstGOMNs 1keK*mY6,q&=jc–Rw"ј26זKFDC5uH_Zi !Vٙ"@=xjam*Q^)))h3B'DLVLo@{t:7p'Hmgi&famH"S$-f:FR0Gj \wCZ:Ku8) Ӿ>QCu?ŽM7JNq.L唗ǚS#Q̺ܵm<8a~ 2CFŹ#sͣ*kܑrCW?<&{d)碦`݊01GO a| ~|pI%ڼ˷8Pw/) n"]K9AwZ&DNd `+sH7Y޿% (w0C[F5t!Y91]p5J:F@SьIe.7$2/xԧ-۪拤^lXЙMq:,F쭍eY}2V.=PYk2A.i\-sJcA/|73q;GM a@::L3͡PyCV+<߮h w`7F +K])LbYX6RS,ylg pC {z4zhRN%M#[`}74 ;Gc*:![.KToRߗ K/7T 42[;R-s_!'x$omdA p15+ 3!Y?2w4YbF :=̷qު0~SQ v&H"%"ʍ 2(ZOG9D/i˨4d8 dy* zHaA-ωݔ2h[r*kd}I>Ed_u܈L10OEAKRrVS>)=Ҩmy$x-S 0G9.>Ѧ`7X]B eM6"YNܩ%Sp 5qoX %Cq?{p1PkNeQ;54!-Cb87A0!~eQq~g'z2p) /Kۣ۸fS Yː3[TcQuFn6Vq%m^!2&fV>+^0mdi Qh ,Sl8tz.Lfԗk$m>ج@c]N!~URe"eg %0bG~}5qE;%Vce(jkdHHTl;+*T≸S`˘Qv 9ay{bV3x]hWS>nE<~hQтdpGÚCOH]`O!uUzծJnxg0 й]S~d;%|.>Ʒ)YBb8Ս"9v>(%]DQ-)X,W/1 m M k3 f:)aNDlѸ#K5n"'P\mƖ"d&Vr?7n 6^WRܛUcI\Œ#`by8a&'y4 r*ad?3|z#^\p7mFY7,ӓ6CzudyӇ#=$Ps^+.Qb1f =]7ܦ]hiek(`Wp sH, (sXtjӌ,خ8]yq1H&UE׼0t8X7:Wj)t5I3_ӡzy"{Kj[NxJ0*axX}[}7N*xig Ci0g7R3; Q%{Oκ{}yę88HGA¤pڪ+ϻ]gSȰ}4ly .+ )20,O^% sDO wTZjM6tq G>6\f,9G󚲁=ڕ>\=g39 ͘ghnOғ;s\Uѿ;+F0䂃H?C$;Q ab g莓٭0so3לGTbzsJƙ{Ckܔ!?W疩%DeLr1P/ZDžlk}r+rF~˂m7W`=2yH5g 3Rjbtw§Yrpp}&nZkfk[p皥X{/'NG>SDA7xA@l.Cmh1&ׄ!GX1o4=yC!TSHTfS vH"i9;(*l 0>7YD QE@9] 'Y. OugDӸWAO&!gPX{+~,R qhs]SLQx]mQ N.R?NS$Wޥ & /Wn`teJ8NUws :;Jq扑ZL)D)#8<#ks ;9.Z ò^X5 rTiuɀF?OVҩ`K ?W\w!H~!?VĄJSCt)gyv@?x`L`Ekn-KBO&mnRɢH Uszq6UD{3r1(t|e/v+طsIo CJ$D$hXW(R}9H,?%[}f0~n$-=LA} Eu5HD-h@|;*@Hd;Qjb@6n:jL9 ҁxUӵSw$CgjWNjr57`[CԾu |QL {cbKMlA7D.xZt9xU5=LEx\ +a6Ro@j4] Y\kG`$t=j%@ lcs_ZJyM~ݏ`=9XɈ8*hcV;KL ^cCMο| #lnuǷ{|k8b֨sT`4# ۺeyP9ǮC%p(ݷe#ű[%,54z%pF҅c@8],CȗFK^ QgZ7i2W6+͂XH"Ma<,2-`p}#Bʃ;/33Zi3s)kw2/! P= ~ }&!>_ uWԂd^Q.o}|~kʰ;TDKEmsp~1oABn<b`9 i7.im#+* t5($ƪdEplY5<-8me-%9ٹ͙$ vWsLtϨ퇗A}ݴ01>gŴkP^{a4&< //N{R ]CDbF/qWXqCX/8]*Զw,ŹS d>/4zfqXıMwשcaߛ0xOJ,{9(adx&yOQ60cOˊUЍWѓ+c˰tk ѐX^nzM[+h`ĂHBu|-Sja{־2jpژc$CFW;*0Ѳ긂O~CE0ьJPkhqS(zl\rܥxKHZ g)x14j1çOѼ %aQ$ dقF^8bNԔߺX(CP66]5 ?űutSa= Y+W(F/Ѵƿu=TWuW|lHۃ/h"pΒ# q&{S Z5kfcxSTpǸoA?>!m[֧0T[Tq88R"N1,Dh#%;@Zhm:|i\ǰd>Qު'Wأ[yT dMS`aK42J˿ѶPI*tP13B]ġ13GJV6 ;{ 6oY;[6zZx٬ W}Hi tWc.Y4!V<}mJ}{O@$f8<jt[s+nAuR(KU㡡S5 x8^Ngº+=}_В%ۓ1L+4'6ziu~'2q(y0[$"BH8 O9%ևρSΡo.7͡x34OK!C|;r.suAMι74˴X*?K~`ZX 4;.| o1,ypI_M3wePWqZ< k/ mAD5> XQwZsKgiNO,tRh::ǵz"/&]qmk@>ҷBU\WtD^uJG a\a3ߨ,_t )c5`DEZ#tCt G:S_Pf<44l)DngG8 qiDfToXĒSz0]{ 9[> !(Ԑ,ˁ:>N?usEBmis7 ; DH!7n YׂR%c=֝KKj2C˗?2mӣj4x).^[hQn<)l(qjQ%:]Cnhne\A;}r۶\C[5h;{CKy&$\BV;Oj1uLPfMb/ J)5t$cGӔs!Ν*Tq&u0q5(L2/ZI}Wy4KR-tDbN|B4|6/WNgm> sիր?Hs|Z?x=".mX1 ưL%2K\$ / ƻI'0)_q _e$qH .NZ{@>dI bŐ[nBQ%}wB3#tk"u͛Z1CC54d{%M h9,}O}{&aOlTBd1&enA v9Y"RWD^/i^veruxt&t  Õ6O[_`S\ rq&)9ъFxf×؛K@jMqTeG\%h}ԟ=_6q>5(h#˨?w8x+m|VG~=A<+63I]2E<_?*Ֆ(W*5O־0Sin#ab Zuh|rD|5^ɽ>@!AT7%%Xl9qwVc[p.NʢUt9 ,iWPbNClǯ9/qG|[Z؎DaO<ҼА~#?gDI˲_KMB# |$(ҜSeJb?fܬz 'o>MilXXdz]GX\U %J\?SӟrI0!9EDRÞ^D\㿶~O؎tرee]3A%mz?nj .>{XaBlڛsJ#Iy$0<*%1ОI4/Z%K(C+8Hx30U96 Vdi&7YòD_~ |NyZ=Zebfp|,y#0?=x57& $Z._qk_ʞ%3BBf*ϕ@'Еor* 8:s҂d)wb$9tߠ }h䏨ؼ}i\o`+V5×6ѰVуR^mwayփN Q/鐼᯳CSH+Ħ2ݠ`Di@P"f61#I4s8 w#! 8ds*U @)>=掑t\(g3!I@Kw#b/v:BEPDZE~E~[f"vKcF Q`gqO{ g\\Lzmu7'ҸoLSY@)qBhW/^75('u612>stvv F'eʁ*^(& EzaR$1ʪ0/E?Zk^y~ wW2(uWiԾ# (̞ȐQazj d7OʋV[oY!Ay+0Qkxs٫Q+K#44g061F\mhpM H?(1|ӖX .77)o!rC–QR /,#g ǁ&fma*ַ?Hy8N`O Ei cϪrc([IP xɖKҲ=OBV6 P%L/"qpYee|;F%#zswJ|318$lMVyA~!oQ';S7Ozcfi!/R_pk4`!K~y M~]j^*orl}H/*V[e')9a ];,mm/fyߍl?飠εN8Z9(z<GNq)\VUr]]OBj6RB15'ϭ/gH6QQ\<&ȣ1tV;o~q?žQL¼ Fq.b >w]')Qi' Z`~n;ړy1:dt}zq>ʇ{SXjTf;?hCh_! G]҆f؏m`EYw TO vC4.&xu֠TVG+o ɆN r{(^$)"gZgıP84'XӔݽG-[̒\, v9C΄R""/ٰ~E<@^[`p!er@?ދA>@0j%: ;*(32#va̘Ƿ/ޞFhS%B7w*K:s:[j(Mb8&i!bFaJKw)*Cl7-lo\Bg4eu2<ɶN,1{;P;HD|SHʽ߇܆Յm=0hb.C )c01TfƘt‹)lؐFײ%9:LmeeJhz$O*UvV365W,9 S" -kLjA;pߝ\~r%^lU!hǂ|aX<%X>'O#cߑ Fj:V9)!?A ~jK|@Y ;?ZEơ6ۓΈdaQDtEKKڴt/:}4ݦ4yլWTtq~!4δT\ ]"GDujIS%6JBFA" S`-ؔ|(J̞XH#3k886ρ8Ȧ[|cwVfuq'wb#<4\Bnplbn8ipYD?P{G)a܀,yЃlYQȰtO OE/a,q1\yq$. %a52ts18 TWJ5doR6[6]gA.f%j(s)7L^1SM3=RlmCod2E='ZQXBuuAZ'X%z؃ 0¥AȟI3U*=AݗkBlPO˿s9"D3t poeVU7;<Zّ391uĞoדyq0aM968f;a H1(QW?cf.L3$BteWv b '|ӻVZ+傼٨LaJ(]9%]碼ϵRCP\NWO N. 5i(ܻZ@w/waI1޸qE]S I}^ǗAP.:@ n?.*Uۡ]H/@-3¹b<9XVT7;ˇrU>>} lY'耭m)?_ܵ@`UP9DC* gPӗn=?,XpѶPpv9c* 1KGn&֔NS+ }cƇjs&[ͧhz rL8O1Lnsn b)t֚Brm3ÏT8yL3R, bg E~ M>y4to};xŸ78MYb}w"0nDgR9ht̠_/bcNPյy )LL8ל'hf7 wƮ؁OiF/'_q) (yCȬV;` PjnP'5?xlƍ9NK Z+IR]ˣQU,{fqBy:z{ h7hvA#U1<$VnjI;Z&͗NV FA-LS^,ZnϣSd rSn fS98YLjth1Χ1P̢u@xU XkhG#YwUDv( x@0SᵔNɲ)8+,xxYaõ1IO;69OcǯP]]{tɩbCzګxz0S59}jNv+g 6&S'Ė(5$& GBjecQR@=uͺ}+ QsCQ{"w1J!~1[CKme_P9Nܝ ?{裑]QPl5;~"n(# <6vV!˺ArIALaব兴_ +mUCR {h?T(~|clsԎfHHUÛx,ZhDE N>$-C&͚0U6qPL9@1YH|-eϏ? Ԯj`wӾ4MWQh?)7y?.د},<'bx/uZ:' Z{(Dٹ|J*=K0p%("{b* ,cO _l~u @;vkLw_rV@A! T#/EǿWX%R` _G.S0UPNn2D{9wDZ9Brd(]mៅ8M=-/ HpI8Vd}-:z['rQL+`g X f?ӂbl3~"l!2 ޥSPi/sxY//=c!88wWmYEn?[Bo_̹/ /tjFQKp-cZ0t3 2(<9fAe`+^QIQd$s }e"Z@{E/b zf͓C@* Ux`6ߪnJ׎]bC28cc Ko"#cLu gG6GsDBDgHrT<FdU@P# =Ƙ1.~ LPgus8@MWa k{D҅6g_ ḪAјD&HbR,YCb{7*8S`k%ى?me.”3u{:urF"]Ɖf;yALSF#Qg*XIm+w ijfaVˆ0qds1`r Ʒްau9O< h8E D{ f [75ܘ4) LV1Rw n0YIXj#b@fT*ュ$qKEYM3ƺ {= %g]9n/j`&NFצY8cůf}o3U xUT˶>Rțx;lgh>R2iBOgS9_FvN6/ȋOX `vr*uyݩ$i.5j"_{6Q7w̓o67 $;ުFILAϿ!tVn8$4eZZL>e&1/_D+]|s@ڗEɊI>|/-RIdIڛ^X{15ыASa)=c_P;I"{Rd6,p<9߫@S2z)lM5mHbW(2'Ć+ #OGt]@,/cH=LQr`n~W/4n$sM ^zu0x{Fr ɾt\V"L0%qrV|T>ʚ~M2L K .L"57_+eFg1m6Qj{&%AʼD׹g\e ?^lO>Lk̅'5 ź{mݯis Ng9$_4g{VxAKS#i8}^g^3*U8r>nxAQx-4gq Dv yZ-zGosA] Q_4ce5L{?ߢXVMѧ乽'Ό9^_ul4\MX &Ư^pԷ9 `55Z{M[+fu]ba*vuz-e%ݬ A)8 ={e %*M_JM%W4UM}8ӡl]K( :'ضRqeýZ8E(IH1 M34ߗ; ́aŬ|8rd2n?/vJ+Fa$q>s:^zoB [K}5HVKt!{y.:c/e,qoZ<.nqQ!tX!1 ne~ 9>̓k/Rdp/_H?ddL_-hhwٞQ\E|8RN2L.f(lj@.03l 6$:n/GXct} t;@e*5/֤TT)pBГzhݭ⏴c;-e ƽ^]m]&ZXxM'CH" h-ip~8^k*g3gýȷR868&Rڦr}gľ:Fj3MB|PIբeGʨjvkFӜH^giO,t=Ul?X E5a 6yn靑GRFj|qUle2 $Ķ6@<( RfbHa0m22si &tW-Ղ>dGG O͘]4 ~=Z>% R6IĿÖ[qb>v,iCܢO0OF#:Ԃ!>RP]?o,|˖XE+GX7Q3ga'O^ԕ6gEHvXk9a GZPE,PuR5K8ڒ?ŇdBJ!\0';ϣ P$ɄXϥ?^߄R4vR (O@w>;X[@^s l\'kk-nH27B\Kkɿb7Қq DLb֙1}*9nqi`nU1Kc@h!T&'1|5!dF G]6 a/8ڒ7Ȋ:+ѩhVh·jo!FbKg 9!&uos}aտFx6'̖sK0jR{zck̵ _@l%N4)OȤ{pkN|-d9Sm\ft:bF9?1 rÁ̠87vlnC,uq~m\m׋3M*>:,٬z2|;iXehclVޯA-AA,V o3{QٽWG0AoO.8L)tBT٩/{%Wynd_8?bPFQY!W롍@Q_>kUT:XW0$HU͸~~c8~$6k`7;`ZI+X ~x擇Pg"iXQ:' e(, #E %DU)/i'Tqo9aEHV_t񬁗jy̫;|_*y awjͨ-UC6>uA]3f"L'G&y*hY,h)Pd&֏5u II|W_\m鱧4dC۪P^uI[{"#B<6!Xl^ނO"irAbT\x4@=!<>WeR+KW_ޕu%57$V{dQ&zJg:~3VpkaEEbt7/N!D}S+_bи10पL|Z6è;{O]HiU$/[&ATP#Ptx{>Q+5y9hfk蟆Sd{S1[w>$^JPDTH〴A`ET*)9~;$KM;emxk "\$hpy%DŷyxXDL5n9mV ͰT`I;PG+HWq;Cdnoj-"2>H" G," n-?:Qܮ85΅U=;o١8]&fϞ;{G("C͊OB#ESTv0L#IG\˲|NxOnCH.ã{gnu9G|7 p,NO^l!)9= e#Ḇ߳9ywhKze؈N.w4ʥU-z aSzY&?fZ:obJi/_t<Ks+ nQhEI˅OSw՜+q&rJ*A^dr'.<=.ߌdVC .4opxֺ \W07A4la"a$mp$E * pr23,~ٹ)h9AT5$\w`~9'݀sTa6ڄpRd1!ㄮ1RKӾlU9u*v-=I=xy"EgZ-h>Ov9ٔE&;X=$H,| Fdξݲ Q<"1=@ZZ4~b;>~uhbE\Ф}8b[fx5 W2;|kק9En:zL{j gp0atP[<\tsQ枽\ : aaɪv( Žm'St*~0 ׯ,ث 9 {TqHhȬKcYinއ Ԋ2%OD^p"1,d)FVmwS3euj@pO#%rzϢt`uoSE9>6'=eO^:}9JNT fh4[:P}.d;m#2="ӉxdB/ފqNOMKhXXEzJKo)H:WX"F9K"F߰x¡2}5`fej7,:b).k[Q6m52 mZuљ@Q t=~%ygs?֒FLn :y҃ٹ<CU)T$nJP?6Nm9ʕLbb!!`*̰a {֏3hyB_ZŽ /NӬ^C-B[wN2e=$FOѳҞ/d? _IϏW% @AaQtCXvoE)I$< PZnv-¶_R 9CkCYg^Fk lGe'=T_YlOX9CJf_R wqM%ᘥk.bz+x*}o,m|.og9㉮_`+y::27rdRۻKڢ"}\]ΫÜET]R](QʒՄȪ,z'hB j `a>ӽhF*`R;r)m~ɛUڟ|0Ɠo嗱dp7D ͣeSŻ<ٶx۾,тU`}v>>#K} 7ޝDCwM6u_WC1@ !sG } 9jlE ^U{ 3 v.޽bsF̬33N%:YY98ќw , 0& 1o:FmSX*]8s)X/NKdYBJ@'ЃI݀!. =S^HWIfcF_<吚EEӥ'J-J ĄkSd?>%/Kr$pj@_YWaʑy+>:mb u7~n;L]KߜO2*Gn,~Js(}"cT$'5,>̖ISa|$쵦C4 ,uM.ͧa@b?I8cF|+dp NWݖ@K-".^E3n-"*9Ų(׺5EV22ESٰ[A AH5h֌ /Ytz> Ч)+QjQgcql+.ad _ѡYOBՠ~;y6BUpn|*Ev`*%\!{LV9<k퐘w?)&D/ۅ>Ӧ…6d4ET՛A0frD,;6 lp\j.K(Z%e48=J4 90ieŕAޙ𶉓W<+Ēn2Q"&iO#z}9WdIOi]\P) D@*{1e>w :=f:o6iyQh ̪5qAw7xyqŀ&M*3Ztm>G {7 XN,d٥&ZRR\UX  r# ܚO03:D{vrqFImp(>Ӄ b1cޞ(pޗ֨CNpiȕ4VEgb7gWy t*ul兝}0p F\{X1~F^#yջf&_cKW 8+[bm1Sd-F1cg?9d>'!FHK za#m@tYj _tK@_I O'kuI谺1>T/%N5vSіAUD8%F}?>51)-tNja?xLJT }DbǦ+4WM (uJ$yLHEdEvr 7^րѭA&|jaZxECԄmQLG !}~>XV1~ci4 0 RꨀdgX;{{GҼQrey^Vo%#7!n@=&7 c#y2 'j1Iaeܛ[}_휻.yr)vj k4ZowVլ/ә<;yBSqhNFPx| eŷ`B E[g3fE`+%2 P0!gY 7kd kH|" ' fVnݵB4bɍ.^1RMNeTu1P0_TLU1ٳ1<^#lw;eJ4?zH?H8cJ*E~`ekmX7nXȔQV;~ \VZ̷urbOvyFRaFWE2B,Kw),Պ4AO=Wr)?ш ¢Rm\2Oΐ. 6&ЕMAlHpcjI}q(wv眼iPt 8|1(GTi{D1%cJ̑*C(?1\skTA!` 1V{8Z2&욭K9ϧR ͯu\Q8Mue : >h^ݧ/~ u*Wt;? *lgc$Axx_'C0SS /Y~m^q %K&\,:F_i7Pu5N]~+ ٗunCR*F'4i0Yom\ҹD1?TWts64gP;\x_RNJfλ伊aw*I m!qTB%b TSJ.29a $V1m;T' #YOmU3}VTXyc Ft:+RDA8D?d۫:f}׵pރ)5^)l㋈ũԡH,o bp"\,N֠|4m8k Bj,>zS:c(Kc1p~| E~a]8_H~ZkM4VxJȪuL›2X̶b8x"eʗ$*&]8r+}x(K8й-4ZJYF9|_$ML:C.?7>@S/*=$)CpɤJWg<۸>$?$x TqEQٷhysbM2CgѠr'w_f< qfpFCƐxšܩeHKzvaC `h#76py@,&"= WITS~X+//єb.l ݯyזhV^FjrQYۆE %K~|ad!IusCY;a4&{&?nBK}[Hʘy3 5%P F%ד~jC?"#҂0dè$E "zy,R@jnז(c$Q%9 I%:e#a %ȣy\ aZ}nqlԥVT[I),޵Wĺ|)%OVci•V,MRㆯG]5qPh錏Otj_<ݤ~ڕ_zܺފWq4miS1p4&H}2+8$C}+Y9Ir0t, @ {iIؒ׋ n #fh')7'g Yw-~\->v{l y`nroxz(ݰk yuW%]81<\d]_ڱß@JB2&F6m@SI %WNfg4?Ĉ}bTP )|A'1bK}p/ CD[jQ̉a/ ʌg~%)u8bHr ҡ5(JNIe c~ͮZbL -L[q0ksߍ}c-Ŵ??OosfSL?s[%K*e~'`&DZW 㗨]W8P_s=,4'ց2Jr:~>#ڎ`dY_IeD&Z:Y ˶Wr)mK50v8\zTxsřӮ3O:JGI1k荿t95=_7h ){{ y'^&QaZg|BMq9f?&Y9=nS֜Y*v/&ZZԳ.c t8=vֹiJ oT" :_QVo' e!< Ԭ q'{J-LY 4؄FzBO!c>UAgrqQ)(}Rh)ӤPiocKPx$8(AdvčZ%*4)z::w"~K|Y'!vX']$&3hkݲ> Y?vₜ%+t=0d~t@ʓRJPhc3kۂM:sF`m]:NUQ~ ɾm.;VtݝtQo:'5 C79dJ)v3y$.kcIکg8)wn 46@mW69m\^GAe&~II5n^'Ok⒔TFƾh'1:.ㄤ,Y v X%`/&ǒnZnɁĹW4pJoSȨ{G&Ψ$k~:b w6x:qD;  ^oY >E@ |;> G DC:Vt!ܘV;6N'a;N.^W)X J?-=vv,2QC/`g@A=QZ;y %_ǡ/˪qm*|@%zѱwd^$Ǔ(hGckqտqi]qfH /8'2pofib X Gh.#!t;n1cgpB%*Nu԰k_=mm3mҗ)D^!2x\>$IaÓ&l$?v}b$HŢkQ e"㈠Sj0/?%5U**B_NU2wIpq={ >/6%@z2*ܓ?|bݕLjKn+^-|#=;@#oǢ0om 7xL4r?_yҥZ4.!bJs8 @"rKXGU aGn Ev󡹂O{x?YYuPv,uv0NۄB* m%<߫LaH܂ň{Y;sB_*o펫lh?&%r;P1U<9_ϙKlh7}%ݣE_++YsYFJ؜sPWC=Ar;\#4g4{pZ'7n@y?k JF<5F篼?fpW_ىLJۘnIG2ٶ=!#06tu ʲ/S|$Nl>Zq=TIS.&X{q!Z ^\4|$2sjX u~@ S?]q)O5[RKVq$Nq^L~܅ꐑM:(-z!Mv L5 0|eyQ1U}MJM.4o ax> Eg$i9Jќ^A&/ #^qqNix[\f.Ob!^[ȇ[q-d_eq5)Ҁe5>AڈdA|1# ]8ͯ {lIҎpPh"gٗDG> `X&ht&Jr(EetPקfDQ5HHЁaM6't6cHCzǽޘ@YŸˋ%GC'LG:lxLjA:8 ĞҦ~v't>HINiL/8];c PiNAVdebN ΢fb.F7kp:\PZ0Hĉ.uy7ĽTkNUpt,[Yqݜ؃rÚ$PV:wJ0vK3]^X: Tb^:vɒL35\TňizpQ 2k]'@K+wZFpǭ313v9L**28ui^i2\4d;2[ɕd #y"!ag0bzq,TSC_k޾Onwou2E0j.!8閭h4qE>8 6C6L:Obs[/Zm9e~VOYq `V?+=SVBu8: c2߭Gʛ-}{Q@s &pJH/0 CEC 9drx1J_B5=Dcƽ'3EI:3=k My`&[^}܆[X ?믥J݈L(E)1@t8a(3IvFiZX,i M loWP&鲱 fI6ьB}c&@Qa_J3%Mr@M'tG L; &Դ 2FV>Q$>'8v` W#H<;f~yq&>oº"x騐VV/H*ۊl#dCfCj\\m!n?8eoW[k-dC1]*BT9ʮ3%`ȴ/u `뭸Ta)qV$/xÀ#U6KsؠS˷C9 Q7܇n: fjA$$4W%%ڀ\fJhP͘ -v6 m:gqw~LyZ8kE).KZS%[2umesL<+i/#aqK,snQD[MJ`2Ve\Vz{cߠāICcV 8M2&`_i2@]-5r8V2خU (Ho\f2J7 [e;>6ӤqT-iR`U)KVJ u^$:<]5[i1ש8GR_ -pRkymJjt!K문t 8k<:u-'aΑ) uAż~…3v_+f!2OERqRh+.e{TAjGgx(BO?TZr,D9GMsKu^j)\A(<(T}yK[wHZTwH7P٫,:OLh+o0g"[6>"&eʤo*=Ƌ"^+ =_-=|3Eek8#Z.'BJThx9au˹3إ3pe'ői@LK)а"^qraI 4X~v֊Z26W> /)~1bRMl<,tq!t#{~IFKdc%N򥚛 b '~hu/,4FRQ.Et>оMPUl|Mių}k3~CNa3I 7 ke.iû@|HEQUڝBvD 3\i 5%6UqLq,2d @_4ǂ@rPj6U]Neɻn4SiȈj E?_)5f]Tn*yx+;+`Zk> \h;([,f7bmC E? .ʲf5Ԓx܅BI%k0!ltHVeMgt6 >n$6pn-L,nj(/,!Bk߆6=:]+yHF:PFb}ti]7^~b4H'熪9d1Vh,(WMů045#mCiuB]&yi+n^Jވ ,hou\DʫjFл(b 5j }CBo1*-B ׋>Q[W3D.*g^7A\J}(=)ןK?Fӌ\vR \3h$^=l&19?ͼ2ۻ=E/I6[ kZ&mH6q(B|G@ nmT~"v(Q=an'1U~D곢g]ݵmUw WdZpa;3BMY@^5C}e\FSqʐiá5SGhhmmin8R)/UQspmrV5vYw*8MZ}{4>N\12mLn31.M|ٽ/Yǀ81twJ^䇵=|9T "}UP!}pV d[!4@ *jiW9NLz\?ҿuhPFY`LlA!gR;C:he!_/@쳾u\u= 9ub CF*9jؤҏGKkP,aW,vʫƌs̯1sp 3IX@:yyBP!9j Gf{O{@QPC< IMb5:o0pǾr"Wo>៘_2%4j~@~ _>J7>j(_[_ׂk |>k*3&{AL_"{`x+SCmuPT+ -=4GE%Wk(* X=w=NkۡJPV:|" l!!! ,'-9(s44PUiX2=+BkFmnz&y)X%! ,=&q+,Idi۟NT;zo*RbF3 QuWӗǥ_)L܆XPWU~03R1V's)ծ jLC;X S*=̭@lX-ѼQ=`2Ҵw魀C[w37ז4?9FVGjLVX.VD\Zn a5y{i8Vړ!5#7w9_OCSgNuhe'1У:<ͥs ɹ,h.1H"ZqTcunRo=6x2ъDI>.~~aWZy Le"A^E,7z_sI)rVgG]\u㏫~~*rU 0ƺܞaUƏUCF9Zd$\&kf/Q7L{YfPJm0mnwQ#FgIbƒx~Ŗkں` WVS)\ɛf^%eq֋dFGK} Y5 m:8TP,7ldME#N==t`)b  Wƺ|[yV (z`,w>!7K/DVtJx'] IOQ6s_tR?9o[BHؙпGF`o.ju?V#u!_&6CO[ػ'ldlX8P&V`-髏lƵZs>đJܟ>z]2Sq1`xKhY%_Xl&>CSHrLDZ`mA?+v &KsbPZG}A~srMb8!5/f6w|C)#LN6IuTdP\f`^W*0'C>ųNX_Dr%uQ󬈲^*z]DJ(Oч;RE()}3af?zBe yEqXf h49P%T8Fz寢L%抍zp2wfqv=KG4?o}ʏuv;qk,}0,Ls3MM%uGcbdtZ?dZ,\Lgq*\?1A N5CmyKbBic袻З{y.PfK–<mq=_OrUkDRa aùeE,5ȔmbsPM򚘔!b pLfɤ Xܙ3"̴&n~cuPeص@p@ۤrv$jsKo$+1U48ķ&^N׮_&qV,%`Tb5?83ʝvܟ@}{oG[qz46843zDLwfvd_mk,!J_V8o/&3}˾DŽv55}bֲmoO4at0]D='Gm(^'Fi~[PsDnAR2@? wE(@W0%Y`w+ )vocC㨾9AP^ GŨVjEyDF|a6ze# GOI'o" G{vȠlj3X<5TlS wɖewPT%GFJkf[v-WN3dN@".(uV!H86{fY̭;;w/<'͌ȰU>@Wj2S)y:^QyA8:rjU%KGh"(qCT:6|:.Gs^9=*A^Ͱ!߻|s/ώHqK]q}xNɭ͂oc3ޔ+ɟݣqeD{ڸs''0tF-fU)a$Z\ϐ+ył6}p2Ax*=WG1 2NH|{}K;ﲸti\I2T>KD^Cs2}Tw ?;`? aV%nͲQ-<+!F .ܩ4>nƾ/SY?|ĠľL>:ՆɣO%.Spi/`gC5^ F%]*z6IĔ!#&WEye3(wJR؄ErV8H+ƪ?z6 >&f0XɣvzWѩ0极խ *7B>?Нlw L/Z(N!P1)zn7& '84fY rεE^q\ZIs`z9d!LvRu/54W"\84>';.d!QJ|r&pxa|GQOcS|tDD9HJkV.p;cR1)HɲȚp"*#3&lۆVƗxB.]9Sᾁg)}Jy/,Xu^KF'UoKeNM Z&U?+Af~R)\U8g0m1#ٴ7Ej RRλs! a^h vhrA+2 QTB%Oͳ[[۞Z1+V1EIA涹.!3˿T s3CD-d XG`]$1W/lAPiI/DEE=5$s)0hk 46[OЃ3RR߳Ykz*)3Kc%~?Ziæx6Xu^*?C CilZ# yoBSsZ;_hr,1 ^-FW@cwQe#|8j6K-qjvGOۏ_koؿS5,8Q@ Fy?t݃5*~~ӹK>3[oH bi (<𨅸 Vi"DRR g,p'RrcJxVޘ_{gwoiE)7o*k_7Zݚ5r,pm+GljIRPc]JMH R+9Q7ޚZ6#Jz/+k rT'skYo կh=`[98C`Qlu@W\ݎҜʛ9 s5ًX4,es0(Oƃ-.Fwk'cA"D W;7thv `lC0 'p^L߬-Ź #O~,U8djD+%nqu},."&ՙ (k{Z̜{/e%.HTFFk (@̱|iЕipʅ@z,㋺W+?eo Y3G7fqֿ΁!_-NxÞO@Αi,9NӵHIH_.U$VJ Q56E: [{{Dە <0E$F.K\n& .EFSŸa,7" gŹVTͧ$hq fCȹ쏬Y,>qjTDn5⤷n3fqu'7Whf?%C̼rU T~ևK5J{8sL1dBU$t8O!*WwLXŊ%eb8XH+ [t~Ot/bhn}Kr tUsIg^+2(=D"Xﵺ)|s&Zl5gͦᔐGޠM}kX]Im=vBppenf}k_()3Ҿ>Q\&;Qj(E."<,0Lmj,zNFh A1o-ሺkRp7.(r!N%m^P5b?A?/J`Wv^ S}hL!0Ui?(.u#Ŧ/jɆn=+TW%7tȎkԏPAg4T؝Nl4)="q0MBFF[Nj 65K#*& KI,Ac_31;ćAėRNB<Jo,mqWxdjG"<%F#\Bp#~G$r6v)olUD*B4' _0;ETR5)-rk$zTeV^8NbcE#EB:Ƙw[# `9Z8<yܦ݉Zɬrdċd0̡ Y$H$-*23tё%2yf2\ 6  Ed-O͟17g5_<>kdpЏ );L4׹W̧H?bs"V,[8h6ʳzGFF<3 e/݅AfXqSS~AN*AuT+>ZgY>YWE?^v%y'AA/v$ nGmR4)Hp$Btdf ,2"|~J]SLCt*Vy2P.meHws,;bW^R~ V6!8Km ]p ?YGuh)0WKk_ 4g omlѾJ3NigY[Zic?!P+F.[*8P~bS\lrIa_7-odgH&N*?P%`f>*Dφ z!0Mܸe-Wb,<9AF=9ODO WEicCA Vp`i$&?D`_hu>ͦnn$:zz5w>$"ļ".FPj)8h_@(>hfIdFhT 1,z:UJ9lGivO^75?lRm$Ê_Rn9Yi $+ֲ XJ`-\&؃qo|$Xxr;Mi>p3V|矑:>s9D}Ԋo!;QnE$ $UXHG.[g `ړZRawMy7>)+2>ECH*\:x&SԪ)|e~}%Klnk*0OO__Ú)kF zp68@{ƛKq {vjݶبSLN!T&zJu{ ~@ydo/=DL9X%E}_gL{c:D* .Gݾv~q" 2DřY?`si|,Il1&;{0HMaLL^t H1$i6؞c*2T ݧ87aK~+%^ی:XBe@4jMDD\&z LTW{!EpMe[DzgGOg=R@_JS47:~^5P?3gd捙-hH?&t0cN;rLR Xy+V%]hU&LeºtaK_;~5}ĦeQ9bs,t@L#cgۓ‘v[q3Eŭ3pb:It}*R3T OA Sn 0X w'xj,TzZzbCM=MfBd=& &K) ŗ*ؠyKJ@Qb Ghڮ9PHybQ^׻y։h;"l l5CMRh"S)W!Ir gL4jHB\8g ύXi'`xޛD3砿:X"hNt vbSuYI`vВ0w8 ~!EE2X9?<iuwpBڸ0V_jjh[W2գpՂ/ˆjr46`T!ܞmWxSZ,Y"]Nrhsܥr>4+B}Ѩ !2׉9$|2aQ̙_8'$oq !^t@R=v6`O4Lwr\&PUav<5C_3\yx/ \ok*G|#^5'懌/2KKS%P~N1R{`K1B.CfU7"}b(@~;X$Zg)LV=YmBzW?Q>'wʙJfKu*~l0vFp|q?G-r@p7֟o0ۣaB\$ NfJ ZO5@..W#WpI{gHRQ`=/˽W%1j:9r9(Roʚmʀl{7,Esf]\hPfvM;6:eSZߢ mcf`EoKp0g{e`9MǯoBNh+B>a1F; ],(; R$$nꟷIHFZf012 O8ɩϜZR~yKȎ"W<8tX0rQL^ "gІjc5P#q=7G g,D";<{qػx8 Fh*r`qН`S(А bVio ⁔y"h0U`l,Rz x}!DTՈIM7TYGoc` ,O_b zm|Fe+.]4/Ǽ`:"&blWZ4udJzXPOcY^ay1qx%l$i3˵;j)_ijubn;ɋoW|o>M஦(F\H6`(];0/_F %W&,FVel@QՖx$ޞ!"-vߙ8 uK#h$Wޱ"Ȫ$ K[ X.2",-eP]FE{c2}qʩR1uHFAHo6da=n'jcԗxjRsxlCBLiL HlKL|Td6y2*Svl@ (Wq'O7{?p9jԗZlfWʌUnf7qD n4{RD<Ӵijq|gc,g!Gh@~uQ,3!(8/ r;'j̮ vnEU#mn\On,S~/,s[D=Ii~!ae|3;ѽ^ ǢrCE'T}u"93(Wg(qؤ7u=ha2w7tm=q mah=Qo(RjwlzT : =|3k|ARiz{Lu4mm'k1$TX?u`lB+ vs̛ + I3 gG=G׾*4cn7DUciZO[kߍ'AFE41~2r%4nAds=fҁ"3FinI\2xp hʢzi(ILđubw)WaݨJDZG"l5~W)Z De3-%HumBXп ~DW{'֐b @H*"4"{5#Gmz,+{/oz 60 Lo5͕< _diW9 #/{#&m0k8)Tv:5yWHV"8q:anC&pDu.Z{Ep9~q`IM(?ԝL/ `p$c|N2k0!Q\cΕᖨ~HH>iD3-W*e0+l1*XRt]*2̆hE9 c}=RmdR\:ˮB%ڊ<{ٝFCA;D[ hQ*JWPK &*=l˫[q f`Iiew:s?[tnUOĆ\6CbJ=ٷ0WfJV,K84@.ȂhήW&>2itٕVnF[b&cɪ\s4RE"歰Si \P]6H3/q_B=%ITav0iMl4[T19Fd÷ټO6Wp /kL9"Z@B c[y9pWO>/JNً5GlvDްPN*ǭ4j޻@YSŵc V8/}qFC(&lw"w[sMl+[ *$8WuzYpV.:!g?' ‡\V UBq l0o yޕJ,gI 3P:L{G_C1] }qi -v.<7.Z:mYn)Fxg+jZR_ JdEnZ/ g vr):o˰Qt1Xa,@"3cvz[D Oeb+;5MWX(o3Vh >[ĜGJ&ޝO<-Y]`]sGMb\}=˛[:"6M䇞AT7{P4uw9Xt7A?:*քgGR=(gw@Z/&R6F2d0Ol7;$OM#_~lOyɤy3)9}1eA)sA݈!5=5n//KQլO13)U`m `Ɠ\0g0OFVm߀1;,L?(R74Jr_hƻX}~kEd3Qr)[Bk{`AUa @8-[;6uyU+?i-߮n{(( ث)Inwzu^M 4r4d.)_ O}TfE);/f8P(IzE+dAW6׋q'iU9~UV/ 2Geˆ{?z!物zh^}Y^0~G@]󒻹6AҗK\]GPJ@gu @T{4*0}H9N2^]:j7 Fes Z7;,&M.ǀyXfmA)^AH!l!6ÅF1h=1_JQbi~[ 'ON4*Pv"Rx אau rm;Ԫ}I\އ_)jRY)L#xQiD{P˖Zb}i'VY^0WO25.rn{ʰ'> `r/Ή1X :}\8c1qr9"5<zؗ?2b$>|a_9إOUCKč&~($ z7ro "&h:[ꡗpkERL 7VorMA\췻n尮FȆx9 #/NG_>ha66n> _f{L feUok\@rہaR,y vt AkSi]2 3)dTu+.z'e`3jlhEIک5u\iP@ۗYwR"Q'6Vy:!xsIؤ{$`ɯҨ(0-țPa`<ivNTePYn>ez4vKj0AJ+z\{ͱrbaei2bsEbln#XjR"G"Am{CetL$.9;M x+IpsX,msTLtG]E;&s;}Q>e_Hx4IvҩT`:=5PA N3Cq6m.\NJl}w*!yRV j%=qI@*+xH?w5o<5/[&ض|։Ridq7 hRϕ/QBĝv0Ǻ ፱!DBtM`_` X'#0r1S3Y- 6\(:|N?ݹ䀛O?qjn@z}g0no,t٨U+vĢ_[~ɧx,.{Ut! Z I'5>JFK_20 O7pbMiC\@ׇ uuT&щ:Am'yRu^Hv㍝ ,78#V6THN7)EsN3Og[] pgQSSo|pwo qomq`/^2ra |9_AS2LݞOʉn[>Wd litJb?O Gj)%,k۸v'%M .,Դ]L`*h]9 zNT$K(j֔|/#EQZi vϜc-pF H:zpH6AoԥƳKz3NBpV[~nbn"ZUeb4kӓP P,HRwGXuGt jmٴ#} ,5Ѷ*{\Dc0hJg/O;Xi'GlxҒVKw|sABTd̐äzQ</-۲$`)"hM1 7*mQ2sܓ'̣krTnBR_ώETOgB,(G/uZ.6RI '(! PE;V'pSWgAQL airUy:QYJ'rcQ2l,8bCrfsަ%$p='th4+ˬ',B d}'}EϞڅx(2J'evIY{\y@ .H\0pԷg$WQ`Ա֌⾔Hdk-Pk耶ǽɴTwjk8U4[T~kyON/*ISkYwI?'я2>M-c[c`5U>nUTOnyx/ pNōJ)l=lOl):z`` V̼cHe_q|QULVSzw52['}?q *jCŁ`Ҕ{^Hjѕ"%͡6 ` JrN[C†S'(9 &ee& )6I 8{ cnt쁦y?4"͊M&)=y~rZ}KïVG 1@]Ӂ$Kǹ9e_,37D꾹ݖC>OF9K~hҭ<@IESl+\Hlw0[bJuDX^ v5 L>Bo)= _NT.6`y [-pnd0a X:6z ߾MM`8VP 1Bo+yPHS%Wv2Т"bH-ը \xfpb"A%f*l4??! TҡzٲhfIӾ{1 dRh a!9E%vp ɠ8B$͐&*?ֶedAԪPd Jg柸 i#5d}=4sa G˄QK2wnL(Y2*1|Q帮[F_>pXVó, p -y\z U3EZZ 0G$B/4;e5Un+:oLsmMvgW*'>ioҹ @NO|j\)LǕvmB™d/J,6Vl.7Csפoj?h`|mxNe-[VVO}kw",ߖ9]b7k7 $2b$ '*$vr5]i;$dd~(X`tsV01歑4Y\%p'#D h,b?32xD +mJumms\3\WCG(wq/z?7wSdfڐų1KD3ɘv;Jm\4͐]K:n)䓯Ƭ'w7 ,ra [BHM^÷ebt,0 ] 1H&kZU&LG 9{LPv zWFl0T|1TDj-`" 2Y]l5j>w7qp4o$7ӇD>@ f$D2&Jj2_e0s}&Vz!GgKWEqxAaLD~&4Pu-AZG.ΗC'5HMr՞XDtH7$&UFqMy?@f)Y=  }B O! X%r A .^?ˑmc1VXq`<7D'7 k>`e'ErgJ3G*WF3oQǨv&4`B7-*J^]>>"Q))pFC8z H z@j# (q0i*Sd')rY˞t)9g~s)ŏJςfޔ(\I-&unr4X5'uJQ^-43ىӕ*^@/OMNVgBuj @wJ2!)c;p9㲺&]zVWYDKb;}FQ1 F7L甚ٍs=Wǖ4 M$֖N!רmeo?#<Ŭ.ܚ([#xO&{F,ht8=#,P3ݸjYc떐ݯ;nޗFY``.-A>e﹨r) }zumY kSŋiTNGlN!n&5T@03R~))OڱsVB$^COT#BC3޿'`}q >)S L?6 ADJ(d[%{2KwYR4|8,)ZváJIīqüצS5l6 ր9I \YV2ӳ Lh vרiwS==CLz8H䱗 ?7R3x,pP{ ,S CyvEAF*:c0:ZfGw8ħT=~4` TdqKu o#!dx0x{$e;yS*v]EX1( ;} *p^<ƀ\ȀsOAJ y0wp^C ,Kc͏צ#FRP3a^N:R~U#:.2쥝Yf" щ'| WL!˃tiXvexԷcwISׄl?2erR:VǼW/ As$v+hsٚ zM%yEgL\TX,cgh[7҈VX3wrzlg7.3iXYœ$,,1BXc~ t:b[,M>G.['+PMzYښcH>--J- [bpGv8KFҩΦH}B&iR_ )8J?=7'Z_#Է٧%NjnϗV:-7Qg:@ 3NB[)INE{nƎZaFR7D[db8yb4YBA恵 o2B3tp9kO+Ʊ'Wd3#0FH<@=X┍C}a'Hz ł҈h !Z8}EAkjr}-& "d○ #kwۉMX]GU%D?RnF,o9{u-ᨹ+ꅎn /: lza*86b B1H?.8}) hnI)VItrnXɒY6MπA gV5g?O [#?zstB*xjuJFu JDs$cgdu#?~UiWZ!f ҫҚ-sjTgxĜ_çο`2^ln命#_=ƶ9Fԧ[z$iM4~0k,do/0% czF0B9:,s=noJ+eK^<@ =gȊ(UX*M" =ˆ'6npG;(V|`jpbh\0]_y77X#2.Ո B=O&${?p~yGD2y[ A‰ǴdIf`ۮY)G}0qLVeҍB8BzO_ K =CWN%(NᕩvWhkM>ח zObJ@sS*1,qs-Qԝ8s,o9pr:k|d}+ B51VvJV+t vcd0WlsUXUxX2.M&Иᑃ7i.CEr#}u8-kjpLvV[wtx~"'}:쯴Jʁ0u9#z&v$  TpĝF'!2rpC]Tôr>.Mes1h? 8$v<^{4> !A%ƈ7-=.h,8`|m)O@`[qmCԨ6c00@n4oB7T$aP/Mt*cEg v8=ȋ=4JF>y]00 /X,fQصތ| NO*3܊ЭH:L|Kex̘ 62+o8/[ s$D:75TQcki/G߻lPrX#Gll)50 8b98%`FyjcUZ7|dԠ[O s޽Cy$d޴Pؠw4(6ߥR0U):"R~6y &#w8'k; ]%?d 1v0wXFs jRbCWm!~eL1TRWQxX(;zґO`$&,Qo+c@MmH7tVߴ ߇_&C'Q tY0شș~ &቞ !~D(0Qm= `HQH4i[0@V[Fou>J6fX19[oZ.228n?n8ZVSaa f !Gᛸ0܆,E2w3dK|ǜW=U#* PϞ9C3mjk3GpSOyMf^},DBc\qg!eg[dl<d1Wl}O,: 4^5UЌK$=0 (bz!M Zfz8KbշЏP,@wrA|@SZc[_Gx2\D4&mpRN6߃d IIXɍv5[EHJnK ]6FCCWXq$) R43^iԸ\+Mn հ1h]$47rYłx5y?@;iŠ7^Ӽ@*l[2Ȗ.)Z{ʨ ( JMfO^MW,DCwN)n2to ^=l[Lꁷ[€ Qf-n_r&)z6C8E4>[c_ }eؽjAh2!丬 ls<Iz"h}j&lMjZL5zZŞ matтSؓO3*qߓ**H5vLz!;4 1]lBq0f)XstΈzLșP=ƈZO>u y cEcG$ת"I«)rֵ=OCpqH-zaJ4My1C>QRxVio$׺KTgUV{%T)} G4p|5s iKFVQJ*#bU1D;oa;i鈑k 8ft.f0{/dR6;#?_qj4HHR(lWoZJ`(T,AT.+B)?ݨ9+ݔ|Y9TC%kPN(TFtléW wnG ^ (`̾ELNPYAVD<,0nE,7H h%ㄋRmʻ{?XBRhbWe-H'Q]!1-`B&RX8*.bK;U*+!ʒYPʤN9\;^a[e`ӇR1fgkBJX B*nӦ^^:G\xvz~`7g)NNf$+c~@?Xܜ&QS@ezc{q "8Q`#%_9m<!AitTlyf3B);_oqگs^ e8=- ۝n;(s&9lԔ p *2]d y=Ҫ#4xKJ\!KF=k}e@ sp ̟S#Q%D1t,ҢʆN]S}?,68m_Ư3$#^qAJ%]+Ν(O&R7k+ `<R: \]l3O`nDf`׆U6*0!8Н+9XāӜ(_t{VJe*2 y@9(|yF?V J 2fշ'av5^ysnf?v樥٧QLQYGn,*%QQn!{{ʕf])-٩.M(hkZ!msa7גO胒xmu[6tP@S3v-jGK]>9_3#gG&0펅v23>5\&""Ka(09$dbP>m aA2E\J7U1ooA8d>Tʀ YMa eEr ^u㴇Q{iNYo*9/52,hB}2.)SlxWm S~,s)/x`D,$_ 0,c;L(K8._B~7ldtxɏQz%axFł%M9O,C%hF+5?njx$]qYt=[ZF -~7Vde?v{KB"v_uLtB8PsJ/E,WDpkŻ!EH*TR񚨘kic.24 gzGV9JY;*ɄǦ~7 BL3%Ԇ`|e4n.S~cwA@Z@ ʑрf#}[Z%4T$~A$Q,I2,cSv9 =ԍkC!p:_/u(z 'ue1 mbRCD2i*uKULD'ciQk}AWY_l`!k)/D<0顋u@P^8Q|n&O|St,#9P zG( :۬yd\[ubk*40Xfbjm>=6gI0[hڔ,rhYg}$Ԯ^^<3uAyKPT` aI:c,E]u;GqsMΤEN@n] BwNs El>Y-|t0ecm'f|>c`, ̙';x{SՆ;O.nF2aD=.ۄV2 `4X UT%ɜluQx?8\;q*`C ˬUփUD5G<K:NHrAOcJW%xi+,忏o.:R]*%Y/=X=a*X1[¿ n#66,]gbf38BKf )T8 )Nْgԑ@(B qo/L XORڛ|Mھ#s K]PFIlh3[#ً,5FG+CѠ>SJmEyxwM]ٖM"z0+?wuDfqZ@I:s8zPON ]i> @xރ72%? ,޿a@1w2Jj oaVJ`LVdU/do wAFLq=y*?0W;!kܘ<N]o漈Z }| EG3,"Y fevD 5R.OAs|j\aξHfDF6G|/QSF?vx4ikb_Uʀ5wNjZ7l3n!<@w++@qN :Pc%ï{VwKevdJ>Zi=0ؼ#ĵN~_6 h3]DuQPj:j[Z*`Nzh&-=ds56&OӉt Q#QTn;w %feyWJ$XDsޚYPw=VoLO:@˜M^.x !T4z`-$ra5n F%\$J3mjp{(g6 Qy]I70*d(ȡZ>.Z~U#Y .Ȑ랰aic餠$@U뾾cpjݿC"eU|6v^&݆FM^A!X}m-HE]+oPDSeַcd#ZKxM(uf6VIۘ72%pKYff[x(pv_q"bR۲Z'AkcU!$b#@E9 8lpȽpF .B%5Wu+ y JFkD4cVEl(b.*ZӖ3+JҼ*"Nhuur[ ]sއe9['w$tS> SS&f R-Q؊|TOhEnSn]59I@f"7Haxfm:l^&`E"˝5?ɉ1T勖O!j2x*,&|=c`b9l4a+9?,K0=!x$c)kM0~ Bw.կpbÄKX)眤LWz*{1ڭq\ӤF= ?}ش_6D`5/5zSɨƍep7lǃ&[G C{$xZ a`tPǾ;bz͟,+Zŭ欢Q!#L_QڨL.cTh&I˂ ޾E-z{t]M.AEW"._Z0@܌YsT҅we% lx,[F(JMo&3e [#!^ZCrUit/>)"Z(|]@iӚcʂLk& JNvr3սϷQzq:1xE{>e]Z quTKif*C1#s\E 0Q[;@ ,$$'>Q iTStXf^bn`)}a|!{"xW U= CJ֥zۇ$5E6RtvJ8'KC*dl-%(50G&,m$lw7yiv0+0};_hIڠ,dOo?%J=*X#qϥױxXb܉w6@?`GBXLءxumtVD(w?oo9\#(vAUХZ|T[s!2L O~GDR .W/a͘rlOc]ǯ֝"]D^ε/ hGfcmޫJ$Xfy5`2kC0xR@ѝp\=NwÊj4 <\T@u06͓S!(I<>} )T HqXO › = h UF5x%dzE]ys7؂1(xkE}D'b.iࣖQ56}IN{DK\ !W4|=B~-Yf+CF%w&Y Zll&3?ZFO/_:bEc]nmۓ^nAo3[*1D"M:k`_X/eDlvFN ƐljzOE pvzӫ7u2x"& 3?a0SZ-rY}py,н#[4cvFtM)H:e#R\**O4 ':NJRlJM5*7-zygD\.&Yea2a%It `ITTRFr{;FՁ;eMr8Ϧg\- c7䴅 hn ]L U|@]R[%vq6K3޴/+#Pڞ 7lIXMpE:w:<**>2w=\"y 5#5?g^T)aI+ Xy#uuƒXe(<o A5MSF%.u>Tb$-y|L(h<0KBMd&7 g|R$ p/]8u Z R}\<<.hsd4=Ҁ4? sط҅|c1i~8mbfnXyچ& zw>Kp&zx#jP9;Ŕ!6U 5KGO#~b a9ċԕ1l *nFⷪ 2Pd86NTȔGݤ?u:*<< ?$ kt'JI3Q΁Nw>/m}U}+U%"Sf ĚZhA8+8MU*}6U 4S;L^!IwDg`/RyRPezBJƀJVd(>,ж̓Ul N]CP@ T1+T!)kg~eA?pJ /̾<46a-*<,>[>T?4 -[,X1u$zyBXu!B1IrVIL`4E!pmf=-"C ,U :DQ:@H_,]ޥCk+7|PZ|pD_Eh,Mߨ[L]Dj3zK#4 +eZOp{(H#H:VcƜE\6<_p,Zﭑk4$Ui-.:pQ1C+ /D9^|M<أhYT>=1Ӣm)io޽F!4p#2Tjnn 27p@@;tsU)uoPݐН cbFwT3a0*9;s”Z`$iGp7<a{5hc/F^fh+fC5Uo$%keML`{?cs =|mMR282nSaexYL1/ڥϦ>ucL5{9 #5l+,D>8` |[O'ydTQE_6hdtyܢ"OE.->{?M;:2A?R'7n9-XF#x E.n7yU҉=QO^bIt|tB 5 ,8E,ajoll.z8._\ O ]m~3vA'h 'Ec*j*Ck(@mZ$R1 8pTJ8 ƗF+/by.bvD}2HDc؟Oz[0]ɿa2 }{fKgE&w+ܥ#͠K{7G׽niw,B\)u)Uc¿.jKhǷ`ߕ m5sb»^88iHn4< 2kׅ@cMNg-&ji6* epm'(v!ʕFN<[^=ruRQ ~:*_(Е}ۃd`-$-YK`iPb)}q?yPycCSN˻r'+9s}*BfUW(Yf@ۈ=@:~x[ƆUfċHoҳM-$GcH~A^ȏ=۲C]IJ~..E`(wy$/FDyn=;95a4A7mqܠ} f3QZ֋OAP'ə?E^cQ{ɭ_88}]>oאEnJMR?\91 `CN+lrʏ 6jXj6uFo嶈{aoDH3 $~QeP.Ѿ(x]/rĴLmV Ruwb CjNqQ˦aFx+uPTL NGe\23Fm~&P|{ ڍ<8wҁ (.K)z'$#I;q_@5aR&'?*qa {Q*xłjTG<̎1L<ė?h3I+ \ 0"2as/+3.: <%ʭ3[/9Ys UU^_rkg;)찰TnB ՄCr/7MUy}ZFNSX™hJΔ2y]J4o@OcBqjY"djf(X03҉Y,.?*GP;7O /#vhfV}Xܭ褯/^+zhVWt&#hn`,mdeĥSqS}(d6׌FTPWI3ϴi{$7{"Br&DW>)͟ lU E$9fb1!WÈQ\ڴ5Ĥ|U8ھeĔ.H7Dc|^[ o7%BX"p` R ȍt-bZA}Ew 4*ITQ>}1Y򵥙{<(.{y<[s2+n&ZnmݾyNBl0 ,3[贼;BSb]w^f;D<8I>ȶ@|C HD.% =u6R7U[e,x_!V NYC-#i#\ui*LqQu)t?`+!sE ժVů>e"bb#7mPM W- {~NR|Ck#\ 5e g5Z Y,^o)(zzcVZ˸G84g݂v 5utvq,-YyJqpG%dzV 9jGsEORQWtx>@1V%fc`rdm!009Qn݋-B |]IQ|Z@K1y_D !d9+N9# y9<V*ݭWk LеOԝ/N)0uS+j]'<^Iw2|$1/ C*a[ *a%6i[O@_WM; 댒+# u5b?,:d}.ùex9m*%~UV%&?ok JjD[܇C;ed`,3٭N[͖ts *)C'2EhR}}_sg[+shNP?b+d3I-,hm!)mvsGgzO:.19ܾzMcqi[x]b}n.QǾ!9((Fufz `}.pwe!HM:8cGn3@&#"HոƷ7,Gyc3)#\3!0]@0֚jhp:㠁N:j'YlA J5k#aڼ4PxiȤH$ȅǟd"‚p|C-pyFW5xSNʿ8. \p5;!GHgDia,%,FjEDr4jT^B>^BF"Ո >N"#Mz1Ne'5s78N^WN&b\CSU֧l gdSK\ 㰡$SNvA8J]jsu[#Q)}WOk ߪerssIPM݄( !mɚoWQgًM~M;FqNyy'߼ DhfJZ,+{꩖WNXa<%tA\{ŇKi&dI`n܏!qvhB<2rtQQp8g7<(,O*I]{O^4o/j9mS+5F%| PKэ/QfUҴv-!8kMF@tEv e\!YfBZO-7G)(<<*oF!KBKWmjSVmC pOj\N]SN'm^PcZq=}iپ=dL>첝]..ÊׇɽEb J{b1JVigr:a1fB #[~qyrjHbFQnoUHw:sN?{ߢ4g~$hQu<~GWcN| GPap?M>tS{[Bo3#HKImH%$Uj7ys&!tw(]-8 2AwP\0 :W+m5>-(F q Z0'es3t-ESC CM2snmD Y0ܟ8c2]9/(V@NzIi^c~k?^'Uۜ~, ]`z^5m_64遱Q#o 5s ۰ǩP0,'ڍɯ&+yǗlБYzG /Z8nd/SwS97TG& >unjXcSBa0Y[XK5F]6EZox4(zhBP?;ɬ}t,@TDB{^}gOue]qqhk|_Rh ikT7Lu&HHcGAxmԭR[-Ƽ[rT9Ck̭"cHD \./o1LCx>ޟ!t|b'}[%W.R'J!=;lݎ-.!nzFtΛSvu ɶt yPYܕ4Vssؠ`4ak1\Nfq!o4,mYp{Hy1dY6w(\h3; ٘S['8im]7t3F Dtf}@]fHvuNS慁P;57zW3.>fp;~n9LLZFtBrwOPWdcoF̅4,[;Y4:7i8}%/ ~\p䤎Dtjm G ̫6m&޿]Lr'fqYQxO8FB.{{&#Tk !ߒ|CW)aFiFkk92/"%M9hB:? a r%#-T힠)ގz`اˎ?)xjoyo1:)q!{=El+f5na--^-PȊ2q۟:(9I[H#grb#|E&g×Yz<3Yey1AfP}>S>rfhbQ"*LV9n(^:n*k]o״B2.([gs н?0̬eqP(dp:ʋ.MmlHrw{ ?!ę2p\u?B&/Ydz}?xג]=_$wrbIBQl2$JMAv6$vr 5kF PϷZz:fqZ>ģ(Jβ%MXe'*A͎4rT.y]f~ǩ=h45mkDm{w\@2i{@[¡( 6Xe静6_]ac\4)9?ci1K>ؕb%,ظZ_T߹b!֢Eʲ#p_d)o^CeoM( sW?۰iض 2muU;F3ߺC_ ւ49Y&IX ^\޶\ V+m>q5hzIBVPF.vqqz#Eѷj&uC_o85^pص2@}G2+`C̙|#?I܏C ƅ7g1A1Y _o R6x9Zz\^{P H*0VU⢀Ǝ/^D.|Pr:I_Uİ戰hg4MPn=S#~L4[>AjĕdXSTrba3_@UBɍh#7#o5@j?1Ƽs5ޥqKK8K`s8((SO75w_N؍V1q[ B>P&05 QO(gN1u{V;ݽPDx PCTFP_JI8P۾.̈dllӦ lrMyr0Y x`^c/[3vS$%=໛9WxG mYر(Ku;BS=Ϗ.oFgQ,}5jq.\,'KVUW˫'˿l//VE,duސkĪ9l# BZLحǬD+RYrPԶLI0vTt\}-6IHl5a.%Tݩ/Ai>ȦCz2Q&4߀z-7 RMF2]]MYg'CD JZ܍""&p̠d>Tt !GJO8zMll𪙱eP~gsn<]_̸ͫds mzNz`MT>L a؞b $:̎k cw ϲ䊡ŜtY=ּ&zix{Wgf* T zC`/*o"8XE hҵzXv[*Mv{b9J(AGytkd ,s™JSz2ܽ~?a8z?E/7ɯ>UnbOzʼnb䒽nG.TA>D.Y  2} _ ]xSM[!3INOg-LDsTGHTb h,$!/upRd 4vH+\MӛvG w2{-Z*<ۊ; I+uX*(/1k?O=G6QNRtI[C,I&TzY `RHזҸO }EBqu\<_~pg/+6p;\}0{=~fӀ0ZoKQ?.4Siٽ}%ϯs=4[[a'USæ) UxH !vl͛xb&m">)Bν KszIOߐWĩ\'5E&,2G|V|ԢַyD0@%zj"=їN,ZR3 PXLif%&KM BLRqLy04q@ƻv>~[wS[RO t+CK}*U0w^+أYw\Mt]ې#ڭQHY7\gn]YR5 'ccKj%UQa= z1ԗ&sLo*{,eXef &[viATu/*?)^dg:~ÝE曄p %f[|¢9q:ѶZQڗPS0q2(P_n ꥖"ilL)0#Ocj[}ق341ㅔW jVV+xuf& 9K} ODc`4ڨ֛YM SѾGr L5 *7H H/Ϋ%E;<ʞdl+D(UF1ʦxHCAO` rY\ye/lu*dZIJSAG)&C1O ;ϯv,0ʼn&"9pʹ/z[=IzD("{QdѨ#d6\{g?k/[D%gn7Xw4Y]BchT00a<\$ѹS:~'vU; q֡J>r(T08QOBl^vުOwDf? ÷FlY1{A&>8$ ?jӇaK= -чI̫ ;$̄>=.2b̴ཹ}/C{չHr&+Xtk2WPy\ iSn~@&cC`u/|﹖dm|[ a-TlPy"xlLtKx,{ Rm*XvViAl"YR㢿v bQ͈`5vvU۪łca bC_ 6 o l߮d!A})Žew<D-Zc2lX4Cs~q֐z EcDHv^Lt Nd4N (릿4dfEv]d_(k`a%DEMPCezݺҺ`7F,pv7>f&J1T:QfiJh#ft8dF{x3!.ck!`oU㣌0clU"pW.Č r1ׯk^KWewH)%DGM/<(f= e[vrꊂIק#&jp gшd=f\<5 _U6O鏯׎dfjq*Nq# J/;Qb W$ iU->'FB)pQM>H?ex$xFX`g1obFFSBw`h[۟-ޠSke=VinU%#ij5W#;z1auR)lytxA zБ8v 1-H ?9q.mx0GÊ!yy%͢,C˕wY9%6X)X1&,iܬBqt"-yiII#(cd? fwq:'L?_x ݽ}qs^âFȼ;~c4Q/<@o)czu!K-ѯ`AØF7 <ЫUadMS65|xU2qi!Z+z4$;ֿ6`|tzGX*:8 C$xs]~'vs orE4 jۺjCu IY:z_:o\J5 k0coފIVyaHvu;sdlS\ҍ]S\w9׆0p=\axޣٺU @JRȸ݃2 m! |;qv0[$щ]TM#~ U}A2 0M:N30kb,],k }ciB:Ⳁjs2b JI!C(Ώ *F%JtF#'M]s8塂FO0v Ňn -C OZ>20y6F< ̵s`cIK= Ke4-gBif ۮۼj;$2<£ y=Ɛ nu|A6 (hpӜS" I* XaY<_K U bU?3 jsBF"MY@#^\P.u0N I!e5>'qίi]e a!,13b'pN)^Q,EJT \X f ~WCq5!18_~!'SJQ.ʏ8V5t|d K I@9|RA!EeY楁OS&H~}6jfLS[- CӨd&ܟ=΁ѐԋ&z\FNPUSߓ(Yav3"y> vvZfi9Vh_cK_챒 =[$-Gh&Vw(m_zECP Yl<cs6)g Ƭ#+'r^Sv0#q\Ugt@Vetf^5(AyZ={5&|"'$a6 sp7~8ߚ>Krס.@km~@~Yl~ɝ$ 59s,?z|-70Qj.nylEH:BVHf%?Z;Jw|~ȉhHʄG]Z 1n: ²ғYEdZw۷Hi#73G"?qEʓi&[7hz-U }E.jʔP&M%nCYn.z:m}:s԰t 8=ԌT&"㕊dy.nq<dzxӸ7\t9ӭ:3U4҈J-X3e"IEq1aIA g+C1=!_^z?v)k:]w|0_ʠ$t\XLx 6h=9's# r#^);ے$ҫԤsn^1ƣUv;r}(Yj"l3h`Hi=j_k7d_eAvЦ'@8fk%lS)y S-9ձpy3~D7C_ ̿V=4t%%Eyh.DnyQ*&0wvX6ʕ!aTb'h#o5K SRUvgڡQ| r~_"q-eU=qALɂCfʦb2%^IfBn*.RS?UH|\O |kD|>`%86$Lc޻+[*Ǔ daeI1÷C [xz.wfAq3!?UIi"TY Z;dGZtv OQ/hgA~:4iԧ['.-_ S&v!H8Ȋsۀgkf1W%tbeQ{kmmD huA֚TzX߮#G]{$ 8`AGQ92;Qd-Ċ(x`Tv,RԯV^BJJz$e8O1fUF!]b䍘#~q6%4DBٱ9obJ}d? (1?7"8mCǼdڦM |g-[`n 5ve΃v;ȱxy MՆ{ Ҩ>x-N2C Εxm[t#g{*1.:L qo/:LOd2>0%oƑz'Wdg7Dt[NNP#UlaϯT4'4ے]{ :T[Cʠљ[ Dnz@u3u9n10VD&Cj)H`v]gq)z9fߐ 橡xk^^WtIOޖ%/#s`̜*m]q(62Bݱ+Td,AܤGup e9Z cqTq}?8Od!}Pׅefj"Iй*=@Tv\CF܎߾`<͙j9>TW`4[ZH>lee=AO6-ND?ҜQkg"^Wh/v$ hx&L+s,,n6Fߏs<`0BAD1<:F2&J!-CJaw=gf8#IͶD.h(7G '0ux[I;[ns‹8.h*欈 Gۡ=sK23 G`܀y" xI ˫wאG{T.W#^m8r1-P<=x4i&]N|CM 䗧ApBwxqu}Xq"gRl7kaE>Zi 545t4 :4N-E3Uφqcz4[0lG~y w\- y7o ΀|l ]On8cU6 -5qm{\ xXËH $Q4&[@JQRUqQkyy a1P/n?0_ q, `o7jzgl+uz82N.hG7z ጌWk*lkElj3ҲmN ϕǵǧqȏBa50h3}nIpzϱ*[)CwL֤˼ht2 ghiHomm7^:~E)f?o,7N>CQ|H'sIqP}vo[]b" 2cc(l l^_͝0(NaEDIe17x⟜4dI41(݆\M*|%ё5z?2t7DtC"}zX)Å>xML ̧'GϨ٪KlDMsb!)0/dbuxj^A}X;Ii m:(?h7e1|v>U^n&D {d:*Bd=?fEaeW}) XۓIGXJ;} \Zoeݢ| . ˡ*ÊP?FnZ錄V<AJȕTk v4cl2Tnl,܁mCFVA jhm:V̔|H+@wURdysL /;1%C=C}[msKLrثhN+Ԥ5y)5ц^_XcbꘉS]7(rlZtEtp~1&.6`%ݜ\۩LJas &=L?13nZEkGV4oNI ѷz;DvdgŮYD vY7a9,4/1xK' 7>72%`j1O7K?_Emg67yE鍳 kf;^vL~c-hESվ{&HxQxFFS-k0+ ԓgs(|8LuDCTuʠ<}7jxa12l$nc.q?_Ewmqp[COF_V#ﻊS ڙ PK} ϵ_7חHc3Ma? {A^C_m,&7xXqoN b? Kha̻] YNb/)%.5Hm+ ,`#L )AC8cy$BꓬG#2F6ylF R1cٓNqV^4:P吤w(>G-aWuR=-ÑiL0BT2=%n"\qy &>UևG Ky5s)6(7pDf');^yn-XU(O{^%0i ej"%Uי௡3~ ܇`KGS R]ߋJ_hm)^:& Xa0iE8ayUK\J.!9*sȗ W&brGSdp>?+Rt)t'yY*c'9FVZT P%䮇څ&.#WՁ8r*K8ǵG,'r ƔaYKC0J& `BH!mRK2- 4 9kiJzoX/d/bG@PR8dC*>eP.jLgD{daΣփylT|`B=|RS iEj 27'$tԄ "|axpHwȮ\Z^EږQџwY2vsˀmSz<1o&P!_1架m~Jkd7_Ì^%ۇvWΛ^kiK0~N^_t=Zٜp8hJA<)Vqz0Qt>J D$G.d ys ghAsGxIrRjB/lYUB`' W(!œM (XW@WԖzˬ޺85OZ@"K?5{r,ߔɨ89g EX# zɋ>Iu/#zpJ8dҬr=SV[\V+Ȳƈs+Sa*cXt\6uQ?D'fDy·Z4E'=GBV{[ao{؟߳ߘ˲QR].ϑ@:Rp)3@.8G@-Q  l'Ψ Ӵ K aɓ 'MCX+--b B\UhnPu %p(h!@ıU9nBjŰ5N2IJٴHG>I~P3,O{Ek V}_N:_zA+yjٿ̈Fp'*A{Y` C2$V.>q~f1wM|ȭvv(PK+2A.;yyqˤ rq:9@OhǨ1|Zx }n-;6I`ThvgsC%`*1 <[ +x8[мofhfrU$?CկS=Wվa#ϋi0Ұ޿K _CX|puԘTOP|7{FZӁ4B$S2T?N,/ii*NYNW€~={a߫:$tnsw5"{{acۥ>&yV]ǃOͩ6@S'[hH-x&#<o]QnCb4i쵱!o$B ۧ7MLːXUSECeE?]7  !@HzW&7Dl΍0," D*B|7waj(&;_8ز)Ӫ -{S;#LU$Ty'bXWQ0*+`JL{""fAq5mOd؝Xnj}}y=ݘ`:g-'x\8gSXV0y}b(!>R̸-FV:R*ډHgJ8 ZLx1O?hBf/ a9Y9wݎy ޑiHW՝i9켂 .u¿i~8@c:e'H  =U  =jkN,ӝftγ_=} _:ʄw{N)<[2ϷcF }Rh`[Q1!?Qw/a8 ?bV&y ]w=6L3v^JYڄO`!FA$1mVڶtkq0䱔lHh= }aP\x #u zH}OPZ~ҰQ@𽔉EM4/wW" "=3,օE+4n ;nn4|@Iy(_(eT4 )u,\iY0>fZ,T#)Cw :uH:oNU#Ӄ?wbRqki/JV݆vL\)R5}rYQp+(!=;3Ն$v/'llWP*CL`w9(v$ j3.|5\c]z766eC&=k# F_YpJxnK ٵԠQ́z؇+d۹K j;B"1`|aؼˈhN3 M<,_q)}j~U RU}bn1?I#=348YsK6[%{Յ^'N'nL EMTj#pJXv)~WuڠOj/ F sPF Um[Oץ £vX^.TohS }wE?4 =W`RMp#$ ~g=T T0 Z*Axv٫Pb?!eS4Ƌy16EYBsj1koh'%\xYlXPfFt]i1 K~DH9ѫW'< Bn[g5WA[,OHdC;ƷN[5, 8> {Fri-p$ nE!qhIɆ%c(b?K UζRzMkKyؠeҠ {b $(&,*a|!(`l3v G?Y6d`b.lQEw3#n^ `?mfN,Eqf_j Q } !}،Duܖ'BG"Zj,ikn=|{H@0D|KEC/@̺K6ges*y`m*&_ٿ4ؚ/):~Wn҅fyp q:` \}՟ōP۰+!n㘌Ә4%zaً`,N]?,hӷ+i 5]C#ىLB͟Fh1+h8Jw@-NE w5Y7)6Dȥulq bDYa'bS[^ `qu %NpY|S '22|B~v{M7h?ǚ*q K3*$} 'uy馦69ys7bgRvm*uP{p*/1AưNވq9f L/)f6)ҕLZC\$W;ë,~ZI>iqh4*\lM폽/]CҟH>v?{!ku gL4a.~K z<\^|s|!$)sQ-zJkMk IqO2jXy0Noj<ɉvgKw/ /)׿\AatԳ Sa;=_ذϙk9*Ty5ocN̝x<ЏD3nⰏ/Xu 2X'[f[7a+33uG*%v@7Ik@S[3TZ~"ݬGPqY$ߜ? o‰ !ՆI c90Mm>g#`;m͟0.m_#L GD]?Fge&^р{UKe/LC"/l: 10/HE XNhBxD5\'_wX y# )Z;( ar:禭} x: ce w"JB׳TT7Xg(\dǫ/#,z*ʥ?s|TKi^(ǙAkqLڊ ejayf-.w9GrQ3ɾGZ⎪P98!or bFJd/毓>!<ŋ(΀0g(ULx;D]ޞ{w ̢$ΐ)FâUo˥\r 0R^oY˯p#zE*< wY?znªD ,0@61ff5591c6b2a59d7ecd2857a51119e6f51a5252閻/n R^ >9X@?X0d  W8<KZb q~~ ~ ~ ~ &~ 'x~)p~+~.\.x~0p001p(18191:5G:~H<~I>~X?Y?\?<~]A4~^MkbNdNeNfNlNtN~uP~vR wT,~xV$~yXX,Clog4cpp-devel1.1.11.el7Header files, libraries and development man pages log4cppThis package contains the header files, static libraries and development man pages for log4cpp. If you like to develop programs using log4cpp, you will need to install log4cpp-devel.U#buildvm-18.phx2.fedoraproject.org7,Fedora ProjectFedora ProjectLGPLv2+Fedora ProjectDevelopment/Librarieshttp://sourceforge.net/projects/log4cpp/linuxx86_64H TIYP:h  @m h >JthQy$ F m; b/ Y6, *m|YhG vG`GDhr-yA큤A큤U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#U#1c381f799622f923429cade05ea51697bbe7b746c436545e2fa773ab4dcaabba4b9f8585c251ffec6d2a025b3208cc558f3e3227212de587de80a7039f217c37943d026473eca3763ec173769b6a1095d07422e6ee752949169d528229504d7f8b95b3879583f981fa50bb7f955c87c245ecf40384e08cc2a86ca02390b99a6b3000620faea35c9dbe0ead02ea31f4d1e78dec38cf4b4002e7f3cdb82aa391abd877f8791a1cce6df651e4df210a5cad43b0f4539d8ba2d353ee586621205aa6867f8e95bc7513f047c3f701d9b5869ac8faf2ab38100c2fa96f8afd6bf55bcbe722a449cefd765ccc12c8dcb3b0f3b4398d2087c90f5f245400a2bd6d977afd8ca066a3a365f859e2baca590ad3a77701f5ba7ddc01bd1b99b3bb7fc0b4a5745340a477e6e551ce4d0b679cc95c4c7e90dfaa977df818d49afb7ced08cba7deada6d253cc057471ac9663f043970fb3eee7792aa1596c095416695fb0c7723312b5752d1a89ac5f9e3e1e4392bfbcb23e2ae3da5ac6d7d1df97c5d94bfa6c25c3e050ecd6a560281f24d075f548ecb68e3d3aa457ba0583d23cbc96ba42ed907aaa6c25ee709573d1aefe714e965e4bc97b7e5ce94fdd3dd730ce2944b24504a2fbed78645eb473731e152d2a054360c257f2529b67ebb4014a7db285f0e6904f02bf0efbdd1318f4e5bcf7c6dd09f36c70b72a36d89e3c4336cc2fbcc5e0d5e3ecc98b96c46f2f49820da2ac1673c92977da8ba5bfc3a803b014ee406ccdb84027f059ad92a9da1d219389fca1d1b8fddfc381e81e9288cadf5f3d8ed2cbb857428ff4535c3ee20f0d0db8248b3673a971a1c4fa05ce1c291a8b62681b674d6881cc185dcb42434ec618d9323c4891700c63bf8b15d257e7f09b6f142242d6c198ddb3a8f8ebf66775940a3f96334d59d4e85c4e279a028d9a31876372ed83d41fd6c0ba9ffbbfbfcf1674df28e90fff3fd8ea0050d1833d22f851d8b76ddbec80293404611950b8fe9957c38a08203c4708038608bc8892f5d5222f62c88bdffb19723b5632db2edeb91e0b53a12ba2b51fa87d67271b1d6462a0f1daa5e25b9d420386753717efab89ee075b05561a5eeaa4dbb8e55c1ab621a877d2d9f932c9da0a3bd743e3a36ae67872d6335b0443307f833a74770b94cd0adf1a447230411a0df68938aa708f3a0b1a4ac6ee6c1f8a9781cfc294268842a531b36e13f82367b3db26f3063bc36e6cd2423caebbad77bedb230769c7f4a603eed824d00ab3a1fd24ee11e6808643341d88133ca9566aa10d431f0c73a98a65073f37cef6bbbe422bcbd33de63485d944f396e4053dc084f04a73fb1d89bb15cfc4610e479ef21dae8acd6c146637e5bbef4b0301712fa847ee34e164d9b595080426ee324a40e2e588df47ca624ec712e0aaee7e68aa7d86a1715ea1e43d62e082eb26056a26a2ce1281966c0389e65e2f85d1676fbbf58c23fe6fc9724cd54733257e82c8d5612f1b11fa8a68da0e645b5a159792f4a6c8ce80e41f9095a42c3bf87de55b43963edea5e5a6da38860319d1abdf5f0f44bff3b3f9a00185e3a428c5bec91e6e3761d472c7665f734ee25c668b3c777030764a6af74b7a9bbecabae0cf757f1e561346dd39224e618456034eb3b97819e63b9d608c4b4283194abbccdabc72fe2bed5356d448e6180ae6ba9d149e6b76898cd13e0950fa8f474dabf703c4bd4e64d38eb100380078a155f06770333176bf7024e86777035f5d84c53bd4078032d09a9f4e02efe23f87fcc6c2f6f591562cc717d92af6146e89e34b199374b89f6ee12c88c35f9d1b477d0a235f0de9156f28f7611f3002ab76567b4850831f043f5a93936c0a7bb1d1494208ee9218259187a88c0cd627f220ef4bd1574084fb7b93f56cd419f2c7add4909eb52785192b0ec513f685199a453e04e63c51fc2d2f906b81cef0c41430ea72d38b4f32d179f1be13ed9ad1695360880deb6910466bb944cf9a30f909495b7f9f353fd37d90329af95027baf205f577b0273277b354a823c4f9cb21997adc1f15672647800dfe8b5cf567f1835e03ae3206a3b239f8512b3d6ecc2a04c83134ba31a5ae4b439d97180de88bd61e879b80b5d62a79bde873dbb0a5354cac0ddb35358eb170e94ea9f8e3aa0e7988f805a15b92cbd981538f7d6d58d31671161573c4166be268494443960446d772713ba5b2220fa5d2c3e834fa20186fb9b037271272af4315f7bbad3bb03b405940f90a7a64b5374a2b1d5aedb7a0c9e55ae5afb999b8f8d93233c5341cd1def00dbb977cd7603c78a094bde97f39e0c8fc807a79d71a00a273f5cc7d9a4da49c2d44ccd180156dbd8da1e4bc32d320a16fa342cdf236e22f14170d9bc6f79dc1c7c8d043d0a51b07eb2a60c86702e12d09f75a3def6048021bb4a8b59e7fddae806916af743e7a22e638cdde574e531f4391e2b15cbd27cc71aa40ad792262c2ac083e17eead452d963d2a18bf23e054f362e7183ca778e76690b84273134164ab97b28155fc638b481374f2445835a4099981b778612f5a8b402ba2ed17061508a5807ce4c97a08b6d3fff318fd1683fca0ad7629382e1b0c548350ef2952ffe8a16c3065cdfbf061cf07aba6a0465664423be066163c2cb5ca8dc4353171dd914b41cc4a1eb4e4ae707ceab9c52a103d230c453ada73eb3e5f53ce5853d0a5682ed1169c69fc2890bc3e02638ebffa172ecd19b803cba34c02a2183511c679670983ebf0244ffd861bdb7471883e740b38bacd3005a39bf8d3a38d09486801475a6f2d4fc931f27032a0b7ccaeb8342640ce824eef18ecd8976f253b20a951414ebee5d4716e1ad48b615e6aaa194b093ee29a419dd0cb0d5e67ee1d68858fd833e3da7b089053820156c564a13c73c95bf90cb5e05115a77773e914d50f1111d2e57296e45ddcd3d2fce6f1b06d0536b8a7825e18c70d4d62854552f789590fec10d115962a7b6e27799e080f14a502b14996f81f61d8ec67f99eb90ad27b34f97bce812b5be7003b1bdb4ebd9abb11c9636126cae771cf55db7ec9862e2d1a329affdd3b67274c83479b8c0aee57cfdfd32e0d5dd078d498dbe4823c9d8e9b0deb1ed8147d2b717a463388ed3f7cb99345bb77d30d32831067dc20dc31c8d7976530922ff519f86ea91e3ef13784fc0e12c896d85f284e99f5840cc87916ad6c4b55daabc2d0c6c7fc2f4cadf9ddcfab70e10e58a119bfb3bd7f6ed258045a12f1cc1a8423c3a7ee3b8acc9ce43303ce03e16d91a39ca475c5abfd81af70b8f45c083239190110169a10b331b8d17bd1e139c01c44e69789b005ed1bd761252b8480c50e3f4057ef5b3debcddee0492d8f688ed4dac8e5dcbfd1777c636255ba748bb6385d4ef5e111bed06d07121382635e223fc8870cb1a13883ca269a9cc535b2353ec13964f915c12f24144228be89a4cfea82d75b39a487d096b79fb8b40e0c63f0b4fe13e4207dbf33193d995d58a41c6c773bea8c389a478bb79d6b0f0e6b64fad1be682400d4357efa063434c8ddd87032b9b26b0358f2dabefd2caad456389fdd89cc514b2f2d686c0f803c79339eef4884fca3acaddd838cdbeea175fe18cead7977dedce9a15263610a18f85f3f9fd6d4affbaf20f991e264ac5daace3a00641bf270551689e9960b7b5575299e48d69f482d021cae704e56e82c58f7da61cbeec833be9d2caf1a28b82d81e3e615f6f124ff663eba69217f96c0e09f5dd0da87419c330f8010887772f797d73f22806438c858b2fd691ec7ae89f09957b28de5b1ddce64c3af1eebbc9b6ccaa32fbc170c66ffaea74d0b8b3769c63508e91e722f07a201d5206c73d19741f27038f0910e03d1ec9cf3a16740122f0973dce44f7ec1b71bfe539246dbde93969fe6df66c2bca13058524ccc68fc3d9712ecb2eeae9e45ea853b8b16a980e28c22f0ef543acd298acd58216be80a28df730931a3ebf70d833cbd6f5eb0a780059029434158adf70d6bdc97cc8bfe3288b03f30f13e12f4714a047e69ee6b3cd1b700caeadedfb7846596038c21b805ff3f9076c553e54c51fe1c13bbcf5df663b859615c36030b0698f9dbaa2f51567e46d1ba1743df1403d42b1ec11045d866f54f66e0f912bd222f1f44d1e5bb1ab5e41875b724b6a7a7321b412cbea60e4401cbeb39084b93404114d5b14efc46e49791307aa95167b1b40a489635b8e2e433f481a73fc64b0178ac885928b680f91745d92ca5ac6c2e350ce1adc8d215d219b259f17fde309bd6ea2a260f3d8df6609fe077d9991b12b3f9c6b40127e521e470d86c71a5b93d916fd6a2ffbb6866e8bc9e3fcf861627689b6a09b065a5a96bd82a646c159049b96677f0bf0f401d7ceec8025b5c0e54f75b0866894e52ff5c5a1663ad722159587cfe7aa30f10d354d51cf2347c21cdaab6488f1d4a211be04ba473486383475f0e66b73f1ec685685b7f8729bdf14d22defaf64c12e4dbe9da0dbc68f332ab5767629aff0786f8530f0138c3ed4b8b8d81ef7abad07c575f1aac7999bbcdc4b27a71e8f3ddf69e2a6b989144463711689d99538e8ffd01faceb3a95d66a0dfe9d5c7d0502343d12efa088f32fdda9f86f6c31eca6ed758662dec2ccd166abc4ddedcd57734e3a01c139a4898942d8c07e01f1457fa599fa1aa7b48fbc680b9d5b28a8900dfe26ba4c9ad9ccaff277d2cfaa97986b6731892b96ca01653f1271743e000ba0ac0e772002d278c213c8ba3280374bc8c657f67489d31273773cc9bf4228ef5be3817438eabeb1972ecacba055f48e6b0c9e90cc3aa5180616f0a1990b75ab40adbc9ea1bfb0750375d112a0ddd044a09f4e07b4bcfee29f8ca61c487006093be18480a333617f09fbd7bc4e5e75666aff6f605aa4dda3dc79714129cace4cd0c945c3d6b65c8de3514ecaf5db13ec78d1171fa7ff13c90c6ca0f9c863b28975e3db83051a260e6b8c6137accc275710fbba1d7a31b5a115192454da3cb8f6b076ea25cb493cd04966ba1a786bc0082b5ff26ffa9d1834ec6366093df61751a4f762e60bb68b358724917e1a38d53a8fe1413c8c403f41a2798faaa33169ae00b025f188f9a06620a09d2f4029cf4489eef04e3a32cc89da37abff1be94de3bba0716d582418e286d578698e0113e2a7c4f30fa14102d9b0351c51ac81d5e77483dae3e45623e2b52c1b18841bf57bb9f5369843c5f1da658537247151272ca6e4ea4f0c458f17cb3e786d58d2fb4f713c3864aac5ed5531c35bcc9d025b76bdcce29e0be5b1ff6eff0a4bde8d005cc8e2be48fe29d1197d46f6a19aa5e33168b8b9059f7e6705e5dd1f6190f308710fe370d0b635c19d49b5817245fad2e3e4535a0591b48a395c6e38ace31c4d7136c40b33911ccf2c71a3bba06e7e09e0e911e44a907095b11d67d3450509585d13f36217f5a61ced233e5e3e5c9217e0554fd73031e0c27943213f8cfe00195289edc34b4473eeb36728c2d94b2b3371b94b4f378ad0ae0923645615bf10eda479fcf3fb396aaef10738c50c0b11760c6a08608liblog4cpp.so.5.0.6rootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootrootlog4cpp-1.1.1-1.el7.src.rpmlog4cpp-devellog4cpp-devel(x86-64)pkgconfig(log4cpp)@@@    /bin/sh/usr/bin/pkg-configliblog4cpp.so.5()(64bit)log4cpp(x86-64)rpmlib(CompressedFileNames)rpmlib(FileDigests)rpmlib(PayloadFilesHavePrefix)rpmlib(PayloadIsXz)1.1.1-1.el73.0.4-14.6.0-14.0-15.2-14.11.1U@S@SR:@QQPO.@OLOM@MQ0@JjI2I@IFFSteve Traylen - 1.1.1-1Fedora Release Engineering - 1.1-3Fedora Release Engineering - 1.1-2Steve Traylen - 1.1-1Fedora Release Engineering - 1.0-12Fedora Release Engineering - 1.0-11Fedora Release Engineering - 1.0-10Peter Robinson - 1.0-9Fedora Release Engineering - 1.0-8Fedora Release Engineering - 1.0-7Steve Traylen - 1.0-6Fedora Release Engineering - 1.0-5Fedora Release Engineering - 1.0-4Fedora Release Engineering - 1.0-3Tom "spot" Callaway - 1.0-2Jon McCann - 1.0-1- New upstream 1.1.1- Rebuilt for https://fedoraproject.org/wiki/Fedora_21_22_Mass_Rebuild- Rebuilt for https://fedoraproject.org/wiki/Fedora_21_Mass_Rebuild- New upstream 1.1- Rebuilt for https://fedoraproject.org/wiki/Fedora_20_Mass_Rebuild- Rebuilt for https://fedoraproject.org/wiki/Fedora_19_Mass_Rebuild- Rebuilt for https://fedoraproject.org/wiki/Fedora_18_Mass_Rebuild- Fix FTBFS- Rebuilt for c++ ABI breakage- Rebuilt for https://fedoraproject.org/wiki/Fedora_17_Mass_Rebuild- Remove useless AUTHORS INSTALL NEWS README THANKS TODO - Move API man pages to devel package. - Move API html pages to a seperate -docs package. - Explicit pkgconfig requires needed on el5 only. - Remove .la and .a files in install rather than files section. - Use buildroot rather than RPM_BUILD_ROOT everywhere. - Add _isa tags to requires. - Convert ChangeLog to utf8. - Remove api/installdox for installing documentaion.- Rebuilt for https://fedoraproject.org/wiki/Fedora_15_Mass_Rebuild- Rebuilt for https://fedoraproject.org/wiki/Fedora_12_Mass_Rebuild- Rebuilt for https://fedoraproject.org/wiki/Fedora_11_Mass_Rebuild- Delete non-free (but freely distributable) snprintf.c under Artistic 1.0 just to be sure we're not using it.- Initial package  !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~1.1.1-1.el71.1.1-1.el71.1.1log4cpp-configlog4cppAbortAppender.hhAppender.hhAppenderSkeleton.hhAppendersFactory.hhBasicConfigurator.hhBasicLayout.hhBufferingAppender.hhCategory.hhCategoryStream.hhConfigurator.hhExport.hhFactoryParams.hhFileAppender.hhFilter.hhFixedContextCategory.hhHierarchyMaintainer.hhIdsaAppender.hhLayout.hhLayoutAppender.hhLayoutsFactory.hhLevelEvaluator.hhLoggingEvent.hhManipulator.hhNDC.hhNTEventLogAppender.hhOstreamAppender.hhPassThroughLayout.hhPatternLayout.hhPortability.hhPriority.hhPropertyConfigurator.hhRemoteSyslogAppender.hhRollingFileAppender.hhSimpleConfigurator.hhSimpleLayout.hhStringQueueAppender.hhSyslogAppender.hhTimeStamp.hhTriggeringEventEvaluator.hhTriggeringEventEvaluatorFactory.hhWin32DebugAppender.hhconfig-MinGW32.hconfig-openvms.hconfig-win32.hconfig.hconvenience.hthreadingBoostThreads.hhDummyThreads.hhMSThreads.hhOmniThreads.hhPThreads.hhThreading.hhliblog4cpp.solog4cpp.pclog4cpp.m4log4cpp.3.gzlog4cpp::AbortAppender.3.gzlog4cpp::Appender.3.gzlog4cpp::AppenderSkeleton.3.gzlog4cpp::AppendersFactory.3.gzlog4cpp::BasicConfigurator.3.gzlog4cpp::BasicLayout.3.gzlog4cpp::BufferingAppender.3.gzlog4cpp::Category.3.gzlog4cpp::CategoryNameComponent.3.gzlog4cpp::CategoryStream.3.gzlog4cpp::ConfigureFailure.3.gzlog4cpp::FactoryParams.3.gzlog4cpp::FileAppender.3.gzlog4cpp::Filter.3.gzlog4cpp::FixedContextCategory.3.gzlog4cpp::FormatModifierComponent.3.gzlog4cpp::HierarchyMaintainer.3.gzlog4cpp::IdsaAppender.3.gzlog4cpp::Layout.3.gzlog4cpp::LayoutAppender.3.gzlog4cpp::LayoutsFactory.3.gzlog4cpp::LevelEvaluator.3.gzlog4cpp::LoggingEvent.3.gzlog4cpp::MessageComponent.3.gzlog4cpp::MillisSinceEpochComponent.3.gzlog4cpp::NDC.3.gzlog4cpp::NDC::DiagnosticContext.3.gzlog4cpp::NDCComponent.3.gzlog4cpp::NTEventLogAppender.3.gzlog4cpp::OstreamAppender.3.gzlog4cpp::PassThroughLayout.3.gzlog4cpp::PatternLayout.3.gzlog4cpp::PatternLayout::PatternComponent.3.gzlog4cpp::Priority.3.gzlog4cpp::PriorityComponent.3.gzlog4cpp::ProcessorTimeComponent.3.gzlog4cpp::Properties.3.gzlog4cpp::PropertyConfigurator.3.gzlog4cpp::PropertyConfiguratorImpl.3.gzlog4cpp::RemoteSyslogAppender.3.gzlog4cpp::RollingFileAppender.3.gzlog4cpp::SecondsSinceEpochComponent.3.gzlog4cpp::SimpleConfigurator.3.gzlog4cpp::SimpleLayout.3.gzlog4cpp::StringLiteralComponent.3.gzlog4cpp::StringQueueAppender.3.gzlog4cpp::StringUtil.3.gzlog4cpp::SyslogAppender.3.gzlog4cpp::ThreadNameComponent.3.gzlog4cpp::TimeStamp.3.gzlog4cpp::TimeStampComponent.3.gzlog4cpp::TriggeringEventEvaluator.3.gzlog4cpp::TriggeringEventEvaluatorFactory.3.gzlog4cpp::Win32DebugAppender.3.gzlog4cpp::details.3.gzlog4cpp::details::base::validator::data.3.gzlog4cpp::details::optional::params::validator.3.gzlog4cpp::details::parameter::validator.3.gzlog4cpp::details::required::params::validator.3.gzlog4cpp::tab.3.gzlog4cpp::threading.3.gzlog4cpp::threading::MSMutex.3.gzlog4cpp::threading::MSScopedLock.3.gzlog4cpp::threading::Mutex.3.gzlog4cpp::threading::ScopedLock.3.gzlog4cpp::threading::ThreadLocalDataHolder.3.gzlog4cpp::width.3.gz/usr/bin//usr/include//usr/include/log4cpp//usr/include/log4cpp/threading//usr/lib64//usr/lib64/pkgconfig//usr/share/aclocal//usr/share/man/man3/-O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector-strong --param=ssp-buffer-size=4 -grecord-gcc-switches -m64 -mtune=genericcpioxz2x86_64-redhat-linux-gnu POSIX shell script, ASCII text executabledirectoryC source, ASCII textASCII textC++ source, ASCII textISO-8859 textpkgconfig fileM4 macro processor script, ASCII texttroff or preprocessor input, ASCII text (gzip compressed data, from Unix, max compression)troff or preprocessor input, ASCII text, with very long lines (gzip compressed data, from Unix, max compression)RRPR?p7zXZ !#,] b2u y-iSq `) [ehhnPM1-dD05Qw ٪BU5 IϞfXٛl]ԫdo[ eHd:!nqyk`2Ol^j{if-R+\OYZ)rE0nR2@oQŘGG-!{;:cQ>+joHMu8sFc\(Z=:9Bpa,}H6$ac;ecc.I7~Y4ɈЪыy0p4>+mۼpʄ` q3(4')npJW?φ ٚQc[qS$ shQ'2.*]|cG2xgvę!esI곳^Y> Ax*5c?v_Qq놘;ݧ%]ҝ_QI=XvbED0<)2P#~"-NxўAUd3sCi sM+3Jf. ·~L1Ŀb͐7) i=oCQ(156=7;5';/~x( I馉DNTb g]+#k{͠f u.w>PiЙE<E wj!Y~BêtvĆߑл;,M9i/P!S#͇'fg 8KL}Vs=wv̡_@k5n/㢻]r3  ᖗ~e!MSmRҸɔC3>Hk1C%Z^EtV;OVĀYQE)+%ycaBĪS˶W8:` (4 5APJV[eeaqҿߥ#LӗRkE l`XWTXlJ(nvgA;ʒrPޤnP!*rGpOiB!em "еFXF>|!x}/[(D*68`s芎\Vlm`%YjBFcNU[ -i`

}}R#u89{=PL/%:ެH@CKmo82 &s@!'T, /sHVBg'r*Z#a7T#ΜIe6%7F}Gӯ34qs2{u8o<`_L1.lx2 / 7iKо I X~g=Eo'PVRW= yo#NϞQ!ؐ_B'=4N%E>!qS*'s#93ꅫ Ņ/&naS)U, hycgn^$#4LX{((R8rc-Ǿ}փBQP,mB`GCLOu2 ԚS? cyI-&ܳ49^Fps;Ps6,ђi[g'%7٣29I4BI^9CgwEuQ`$ +~sNs%o'k=!!O+%~UQ.P9:QsPI (4Na*z'Գvh vA~ ۽/ (^P v$T KHNѨW?zCV~??e1:~fjlKPesJ׍<҉rQ]أlf,!X 8dm9k'Ӓs?\r:CV<X5|X[{!AeD{ OL)PbDKkIFpl%ʴ(pN+,ȡpO% $phk1bևڡ>g SzpiU$ŅDƥ@FFbb>i!?#_lgzsvrX/5^c^hT4 }hNLۥ*,0}su^Sꫠ[MSXֿj.)9bB=8DΖ "5zF06T`ba`wqґI9tuis7$gEP^ǬUv 7y%!ӒrM 4hEYY{P>"]p VWXa(q*0k*q .j3hj|/| GaY-7.(D-O֬29!H9q6KtZR5@¸l}c2'@`&.Dgiust9s_}8Q{>h[/ g;s3>fR=EF\bϯε0ULONVD18]5)ыzG9>s@pcr-[C@ %b8ApF[d ^@[FTQ6b!)8cOܷa ܳ]Z|R[bx?:2Y 4>޿w ՚Tc2δ N^ZdiM0%P?r_~ Rȋ5=Th70:&caq.O [ /nc*1濝S)ibo!nM͗A,u+,i ) S"=FUpKA J}3])ir% ;-^zg79ێ˒=,_KWԅ%H%.B<19Jfeg)bD \C`7%U:(pW| G}B}ҹhR~a%Df@*9#R#d% k@^$N4PՏJ&ˡD-PD#K]bd~yqZ8 6T4z'M*gdȇvvm\KUECE$# rE%dS>P񈗳oxF1Bߢ(Y`] _Eddη|nV;}۵`3HNPTگ&)^nqi(](yU\i?-4}#T@ux"JdڣYX,IFnQZ{VYa4dZz {ڣƊ4c CqqKp7ѳ 拹 :] Hc5u{ZQK Ǔ-LH >*ɣ k Eêk@Teĩ!ൿ^APa+ywਚiXB8?/WԸ-ou]L!l8p$`pJ>i8:AC&#Y@< X0 1 ʲnTΞ;ѓ3&탦J- DP2r#@w7&rxzión)}\qT]2v.K?ގ>{.?T$EcDԕzqߓ6`bb)>5: 7A)`=.!B&ʬ@ !=sBi^ vJe+XxK{`3&G&VDK,5;.++6}Gk> v\J׍&^sO꼝sE{Jg $~x$f:7^p [c9(d'O~.ajR-`ʑ`m*M[T&xi]2AaKYM`HY܋>[Jҥ~[w/0|W!^d"@r:fv2ewBǻ;9~4˥tmwf9u#b&LV? ND zBp̈́I-L-[2JOaS$9>PN Yֱ6%ijVA"E 573Msc}c-2q1ov lMx+F1pZH>5Sy# 2Q.@4 EoڼP3EĜ'\e3~=߽o`+!ӃWA=Kí^YC;bF/pZ3Se4XDHz[}?2bV< t= `Yz;лA Lmؠ S O ǚ|YH*6'C5+ai]!%<,CˊO҅[TRD dP't1|µ\V O/ T8-bj[^7I('vfB*y_M{V:7XvFC@$/UA 6nؾ=pNj!,I`T6+&음n-1ZE;mH 0 FC#KPa{g"uz˖og_5S99I=1ɑץ[O"hN?p+Y(oJǝ}aQ U(1ਸ਼k8bM}={(U} e=*1AP . ~/kLPNdQ5G73ikz7hHP̽Uʄ]-(s\ !x{9I vr1o#Ye里a`&e88Kf&BbxV Qp8Hl&z)KƩo*Z!@8%fk!$hQǕE5Ě;T*N-O.19+juH@%MxH6Rdt|\FLnd_1tKW9&ί]Ӷz뻺5#%\ z)l*6rFI: @K֭1FXL7X13_'KGt41{oUfˡ3)̻ ]+IV-͡5# EUe-.=ӻ#e,ʕ,}5Fwq!k&*gҭnܷOdTEX8+}[ X4ƔpypbsH0y`69nͦmp4@MrVH (`ybh'f ]Y׳Ai{ iO4t:>f2vhO"iTCj^vgCy~%'Ǘq!LEkAhIYNgA& ^4 ZncvbҸɫJ} ^ZIy*!QCǷ gN %R*hsvk c[5I݌VݣRl[]0ADw^acʱ˨T⣇ɭEAae6Gxtz}!JYsRp)YT Uo-bh8Hzj,5@ 9T MP Ӆ(5!/)ewD :S3_0@anӂVn>zK :qi, Dca3V{ᧁw/Vߖp@ak,|!̷إP֊|pEYsw`YLƼ4Ŋ*quz{,kK~R:sw~DjJZ (xgIV6QTu;X8by_n[d>Fw]PI[ Dza?2xc7}p mv2^=k$]*[ |ݡֿ Fi=JD킧CkRXofBS޻nԔemti(Lz2=63g_ny}zhqE R}h5"֯m^jlϹ]׆Pjـ{ޢ3سa6Boxk(^od&nrU/XNt* P# m;rkdW}t9`~=K)P$bՕ-}U?5[@S97yiV(J} Tz;(PL#Ru(/$E8AVS )2B7ҋ)x5Ez8 :^]jf/r4cV;R(_ĵG$4 k uБ i#r}!?1Nթ.~o.8be+b'1Yv@27ڠ襤ֿHϊ%3^JSs2S;eJ6u+~5|bj"Ero8k;3S1PxLSI>WdyDƾ*57CaTw a/-ꙃF'7XYx~MDeVmF1?~X gg=! lTLhgLz X5#7jk4`y56^Q(U,lS!Z=\"m "(Vٌ&4Ġ>揑3`m)b#E{YX䶅D$4Ed_8MC+,JӣKZ8>AW!*2^8GT=oAu>=Y='Z#Bu_TS2ak$3Z/Q1mylI.;_C{qS@vt*ES!ebri4]/< 2W$̛$Gj{#pK*zܐyl0^lP4wšY*c8$/-i{ ;(5 7'ʀx&91AS&zi[iʀ91RbdLLȮ*9WnIU>J:>b]6]uO }{'\7=n Wz% G?? fgi7Qu;BKpg܎U@L < #ݦt[9VX\ zZ ^j𞳏$tWij`(6Wr^gĮìHE |G/]gA΁p1\ѩ-8w Á-3M=uKлVINj7b0 =bj䯬|]~(k2Vͼuڜ ڼ1\FʡR:f3с~ܗzbZjq/;E:mxv#D!k~*;9|}?5K5iF=C]淈k6EjCz $W,PdzH9PSdSHtI&][żD?!o݂XxU%70ӾE:Ɩǒs {B^@+2ٞ[ f,a{:}sb\wXB`F <Po?a8;c ?C*7ivlgv2 [94gOhnK:ǟHwc_fwjcX,C 5WmSL?5FQO,:'#w)\Su'# ED2MCI?]ѽKhR9]#mTdxC+֕f2>v~֞K mUL<›.o=颱 Fg. PHhdMf7!@7ie-ZFUM&ŀ"woq.ģpZ.(ng[ː0oE'APlV"yŬW 1]_wl_0W?SuH_.]B0(>^h}v5HxyjS ]c7)AI챩Y#uG^<2]s |P1|n!ZKCZÒ92촔h-|s{s7rRg~HRͭw #m6#@Kt;u4@mE9n钋 MxxJ`f"Һ0۷Zw@ϱ"ɎjBSɲ=֞GbFX*dW퍳,PݎxѬ̠62O΃_CZVamX?p6M?t>6[>NnP:< }'SFk)jxVׅD65:Ga-zdz 7~K;-$ @Z6H.8##Bu/v|m_uNCXYYDfҌe4!,үu4ZYP_͏-BэӕRdX套c#5T]A8QRk췕}a/?q4N=t=|s`u%zd|\FCuyN.@g$.A>m`xxo9\?WN>R %֪-ds`#]_4¬O]U٦'ѴӿaaqR{P0"b6 v*t0۟WI-=ti?]k@(_ ])9s5 @AJ+&FcA}Ҍ2SmE+w@)$ǖ{T#4{[nq_܆Զz6etj°YzVQx^!SPH֬+Gmtj>rk!mRBoA b}]{#Sg:lz O2P_ `U \IzE")d/4vg\牯Sh&Xw6ӳms$<~Rk1Kxr:x'_jH8\ݎ :ˢBAKerIsb'_5\41ZO$ EqU9|sAG(0kF(UJ H8[ ĹF?#aP '-)dT._ғ;MJW*^]{0CnB'6kjioB{UrJ6 zWG9?.~ϑɹMwoo_8h ]#뗙5Ii副 } =v)5sEZ%=},sFqpMC%as7EYeVEOLΫX>zR֦ꏆuyԭY1lQ+0 slo V"f)8Q (K:eAT`&+EF- ;PTÿ@?h U:b'^[@r}a37.*/%'l`eS 1~S)fyfJeIa2Ve7yCLH%J%|M]A}ؗ(00" pL`cZ-KE\ZYe~=5]ÅP@/%ԇvl`!/)5bZp< OoR,Ѯ۲_! AL.%EV@ɔۀ4W*K_ѕG+keV]!niew?Ldsa4vv5`jq&S| fmpC疳Cp{Ocg{L♽p*MW־'ιT[\m;N|DC5gp̊i*ptp{Y7ط5Y4[I͏"{^_%;f(r'XO]c(F/z5(<4\|r>4p w-e*b>Eb#& 7}kaQʎU8nFnz1k5Yj'@a^LVESxߤZIE!3)&m˝roo/J+]oL9|PjxYWޥSZ^4GkA#XU?+Y^hKƇNj!B{$6&t(#(L6 U`)eW-5T6.xdK0IҮLWm_SMx/ʷ+<б&B1Ђ絿skR=_Hifɧ^͠d8\!BsJN{M6vtʹʁ'q z8j>[{F ῎ݤF_Tb5'u`GMKf)M"IzTAWۆI_^0w=1 qSy/iSnyp,bL!ՁG(m<(ܞ?x(5A6|B>n1goB{t}b&גe,.(`b7h~q[M5 lBńLqEZ9'?0h29kz=J7Zi.;!>ϊrF7ݐ{«?>0 8@'n?2٬t -YC>\U^JW:?JKo"]lx+1  h9kCj}! r\pդ|Lxr(&ijyԻ>T `#(~f̐lZ?e5t!n}-8vng/vrN|bNǗ09إp#D]ˉ@|iFaT ,YCm:-HTDD+ hPbD-tR?HG=W \)#Wu'<]j Ln>]3Px[ s` uNh`_(t!RWDF'&e@Z.@CX?jOmI3A T̬p}0#o_ym>sfy^XzrmЖ#y#+c) 7z÷ȍMş6j%l- u^ 6$b3'5=ۀn+"2IUEbp jx%wZ1dq&:F^YQf =+$\.dӪL[iZ ]Ru[f[%2q"Nl=SM# B2N]`[;̒">) uni-ƬZ+N0_Ѩ ^y|Ƌ)]N9斱Cbq΀iR(.8v DTYkt36L[ {+rZ \}ڂj%D;7q)s3zȳ?o|iXa>ϻfOd.7HҸw9}n`Sݕm9k2濕s41XPwp_#xT1Nc4 p AN7,~h쒽|⯡c:γIm n4H?A ?w{ jMjW:P[c-ntmON7Vm/؇8B `T/% TvUZdx LrxBI,)!ئ|*C9J 1nE/ȤT걩j?$KwO4yS0ov4>pހmotUqT .s'2촡zZ fnMr+1}ҝA2%NBy #6BAOVR!!LY- Hi"t$ju>Q9aabAB;OeL';"5܌ Fv$Տ+5,#).WWخ|)<#Ƿ \WJ2e[:'щkKl/2,hRPy)hR,.rxI}{ՕcˆqB?ĸCSq28Z^/f 82?_`΀o& kdhZi1d7VAӾ[BlIC6 )]#"[ztdZKÙW> i(1C4@kp໛PL|ySIB~;͜>UƮeyfzU#FR ]vvϞNֳxfvOv#hvK }ZV*)q 8sB3S7M-VGf9o#HIAAztBwg9[E`pGӡSjAg0՝\YL לA}" ?D6좥^>iɅ-v.Ǐp)=Br dupc3@RsL!Wod7yUoUcjsBg^XaaҁuR0&O@.ۘ(+X<]leR^i MAϏ p8z[ 9615  pkO/;5e_nA_k^\~` A=>kUSm4`xdq_2TbqaSg#qݒV+2ʁMLu\[(z[=l"v[M$X(۟mRSNW[?U7ȎPC&qOSosXo(: pVo=-55=''Enߔq3w[ٿOi,f@h+ګb\J'b\OgcdW(ƏQz,-u3=?BNF0.4_ Ax`(eBG[j-_QA]Z -i ECEgB(_w(FڦO}9_@'"ml&)闃',L ^hBèE| 0 ~`?D(Fm.{Lue^^Fbw[%\tU74gu]4뫠|tLBj4ђ$S'O\F akFj`wS8JpZFu_bQY@c"GDžr5n('B-1&uonoϢY[ dZ9Ni3S)U|o&}C)1DQdhBT92ԟKG¤ɍoxr)8\k1|)d.Yɉ&&x gvjjN;9m8dnagsGU0|2*ݱ@=U{F݁]-M^"0"[ǨtdWY}\ɭѧ$7-tn'hʄ Ǹ`_rM٪5^r!3;ZxPC؋^;OR|A+kEa,qx4J"4fbc9\C1]TV cqTj DNpUq|W/6b'蕙d^Du± ;2@N"or.384ra,1?T4T"i3rq( 1ERVcSW1uFe\5ԛ hHA2y ڴ3loBj(MU_{@d~N^k*,jgޯ>"󪐞 DTPqhIGy$x{v ]$;y+ ^r},1*i m ],t.HV j?[ul\9uj;1Ctlq۵-ּ=)|_5 )|FL"~"zY &KQS?ߨ_-d < E̯(UϾ1rNBAV%O4*e_ d%jKEu}?y>bB md B8t jR+j+P1ae,D'%3Bq )JSts%h>pXW?"Z @ɟ)k N !Gl;s^tf+&2 ,i"P*谟w]Nems/+>rJ/.N@cW`hԚ_,̡܆=j}!)^$1~XXsHcGNiU `K W iDem|n8l@;ąULUa–38\ S{n O6̀ >EL{ھڮȮzzsABeGln)4nP*2_"Xdz!υ [(erJԔ LO4,*j0>h>p@G0.r"dlł+|;ػ~=jr wW~z3'T 0ŏxHu&Kpk ŤvWL\*X/t7Yf0]vUz4)6iԔ +~6(oX_3PA TXڿG^/4ˠ#7xm!sAv.ԥMǏ7(dSIvl([lD{Yh @.ٕ:A9|]s=5LYyݕ/Bud)bq9 % QXe(Djygf>^I_P0,}R[k~;S^fmr_ u dUMħQRHX;n72ءYM⺋,+޶9 t.O%o"^y| S0?\}vQgH͆"DCْ7ji|;|)r&ђQ=۴<;sirw{&JwNfś,_ 77!zj f:Gr ;j!rPےNoR79[J~%Jz1ۣ&oi\'G p0]E>‘i'CRLh5^Ln<F~t{;vO=I @ㄞݣ[Ԍ{v9bV/97b] ҉уy&;ޖ;U9 Ӏ}bI-ΟR6&i,"_ھ5_W]JsuFK%yR}+v%voI )!&z+k8Y;klRs_TNҟ@?ͩJE\H?3=*+b?@ 6jPH H) ,@w6N#ytIuAY?WۨޭI73'bl RMcY)aJbJÿN`=iY8.%e%%6&,s|ڌ&y[@Z֋Vg~16V q;$ 7]R).HI|d%DpH61eS^Etq*-S 5 OA;\&NTdPlށM'h3 RGuIbcaСF Ҡ"al5|ELk%(kfeΨk̋8T'>VSiKe]\tĝ(XfeX Mka -!9yOR+f3hrudw/н4Lk p; &Hqs>+bv^t_ʼn]/,i1Po6ιa.4I*4>f"&ʞ?! H̓92űfs\tyj?AG)qqLɥ|$_^!T=QTwAYTAIJ΄㹶:=}>ǞgXBDῚ] \.Zж/ 4ٮpNn`'`۵G$'fVOAf'1"dNji ~lqxWYYБSUFs`u&N 0sϊ$̄B}I@T0xZ<6ԀNYЊ4ΆYPԩWc9_mx ^%Dd.倹;mG4jOTӼ]o`,9;GC~xnn"XJVXm&qbڳ_Z833HD:"4Ūb_z$r?9ҡMn"\G'>י hr;J1X'yk[yez ۹c~,b>8`xI4ۡtECCj I?3]U:!B3/bٓ0A/=;UTҮL:L.n,LԌF-Uoը'3e4#5冶$nj0z6q 3kc>p.4s>mPЀm" fM!Ę7S Mk"0j+~:l-mBO{<TX;YDU4B1ɷnZTTPyJo2"PG< HipԍHB3*^Umid;~>O r=Oh7pm%\o5/_:^ڞ#e1sZG?LZn cg#& s*28wg^]ڟ"A 7. %וZ|D[5%xVf΃k!z6䃁_-ϋiFmq[(6P?Hk+ *P/WBG6zxui 0Νds4`]6T&%w Z3oӜ*Kϥd0'@nY\|*V_%j%g!9o Lؔ1-Zı*N6hKihצod) C!#*ž_ق7yUp .4:#y|>b];̀8lL<ےAF@X?]BeHP7_q"1dSSã&t;*Վ"f:.\sCSf$kb-^kєfj/H#nT}{ٌ "Et -Y>Ο6!9;5il {Oeᱛǘؤ6K)YXeA;N=0G ɖ63fܾ:Fž+D;79"8xjmN^ A"<ݟ=!ޟ4{E8bw Ӑ(PP ֕]e㜳z6֫DJAjd9xPd @)8nϻ 6_'<4 }؁wp*5;}:.#"K u61,R5qߣC9뽿-Dlc.ӣ%]NH12B@ r%[nCl3V%-M˦fQ3< 81XA%`mPܲܡ;z ta34jzI \g׳:j؂!o`1]~i|ec U<{DFvI?!~u }5 5ڣNlȄdwq1z}~-nA*a1CtLo> - _G':cm觼Y1LV@K@l44$V{ O:+HsWp7qDu\vU^k˾!Y$T(ir#~K<Ļ["+[slwyM\i&t uLlpu,5^OcXOל *wc:"g=;fW7yǻ>O[LNBGY%BOw'pZ{%Z+>vp >QI&=>`k(eLtIStwZ C)n5}Ew~GlY"\*̝e ~i ]ⴐCTSw'MrJ2tt/Ui_nw+DnODơcY˱[2S -*xիUB{;GRɹMձ)(Ų`u^exr!"Y;{3܊ aTvH+q&s̓rXX)<A͑W.0JfbGgzML8|Z w 캈b]U%@o]ߵ6`WP^J>RpSaMW"9Xk_+&z \O-<_lKvGRMX2R5˃ aVڕzd-rZiwy_vQK+Q@oMtm@֯؇/Q.5p0!:UgGb Dr2@cgZJ<eo*v`88Vؾ{t!eP|" w 5Dz,üG!^)SZuXድԦ3 bȼ@ KyHs5^0lB̘gi#Akˣ# =,=kq0oqF7 淖^Y^n̩켾UP}@t)A&egDb h|@v0쎇5nvc^Ix:6tBbF.@?p/;TMSWrZX5 |׶\hYS1{Kxz.kS/ZJR=Z)IX(OۧG£B M;*CҾXE]AIr5NuI4C|R`;f^BTia%I cR pL(iQT=Ʈ' 9Pu4N0ɦXh+b³QpyĈoӶ8(OiC@v"|'$NM9훉"??e|wU5 .g-[YrO d_G=HF"h(@!>=vYzrFqJFSE8 BNY('sBr_sr0EKBmz6WlRϖQ֘C~]h≊ӧ5ۻqœ=xy߉8OYOjRk؊֨e13O܁#%Z2DRnO?[IXݐf/kz՜-$+Z5rvR $9|C ơOwԻ4Mieu+Rvx0ygs'9\IJ>O{R1(viC榐𱬯Xjgw>Uf&ά {p9)lmC@imrB2SM1vXB 8 т-UTRx$ToԵEfq Me=r@AM{D~'%F5qL!jO&.1 6r@4gXO*N>{v /o!k10ܞO,D$rwTw] E.G2o@Q$Cاr†#Pb+o@Z BK#S8<+U#W .0k-?Ԃ!P`bP&Ph#HmFv\@j:iIЂv%v6`eQ&JN2KH QziDTt֏$l!@)܄Ðm1o+L ӢWS'ɖ+Gl(9ڕlP Os<.Y;M(%FR8 #.; >LIV_\\W[M܌voXEtל_mbóG`^d~˯uj^Z铻Dn-#=qjYȚ@eʭӓcvSN'0'ea2~(hH_/>akS|X`ŘEÉL>tCd6#UậEҿ#V{DT^4kK'(ÿ? Qe$FY.z*_ J9ROvM=T+OvLp:%ؖX>NBIsc|S?øUsXe̾ˬDMw89"ri>^ʶqHdV BZNYˁ FYLgW.}L[nR}r*ç*~0GO gfwK*g}dY8c{Jw1'u1VK[$b5uhY7\y zyƫ"jD5 7Dw3rX +Y E{V KŹVhPɆ] S 5E=*SR^z;9{iښtQejyM\ ˑ3nmc_ ꠞBG[֑ @lH^^#s~0Yᒄd;aeδgqn~voycQ&!lMtGoPR62:9@G{6(1G-]G[` llaX|Nh3JmND.I$ 8(W/@~2de59(z<.XLt^T+'* g2se*&ux\s)-FD6R:->mye+0v5qL$eSb" _VaHdI/Z^{_B'_'KYR")z͢ktz_z v(7S,@~>Dha BxklMIf2a[:A &ψ4/ʞm0(Zm",A1J ꑼՇ`}}!!dj1b@AqdӮ({OJ>+&֋ZfRϴO }ߤ/\ufDpPXZƠ}LaAy+(f[0g9AZKDLLr1yBxGҁ&W".vn^{O5goMP|P5_ֲ$ފIi!?ٍH̐~n{IY"M_X[RCaqC¿v®zL78Eɮom5#=pNPf9[M ɑbR}U\0JE^wF>hyѷfw}҅{|MrP4Fx 'i%to TMd!Ҩ<ь~}'Pw٧xFq(uY@L=H):``8z*Cx}ȪEfQtJQ\OxIJ^f [f1so=Ec z&n/HoB{($ EhUlC5erZ٥x$h >o,` ł3t Q сa퍬$o2pn ** 0UL- )8(%R̭m1&9MNTS0z[+شW#3k   [h{RKh^/񖻜V1-1s:0!?`yPaO׎%<l5.=û v%(m xZt7sѩ[Z,$hdޚD~)-LfQ u=u$ڶgT e ذϨ_)q ^ƒQ[H0Ar*h1\H &h<=!B[LZ@䉲B욮rl O A2x.f@"l9A|[0_g?ށ]W}9j eGw%0H:&E_5"7N(׍U1Y+\wkF)({%srkʕg+'L昗 K-#8֢#u6k E\?{&&9.ފ+gy0#(Uԕ<0->$nKlTڵl| "(*0ƦKrl+" Ǩ*ʁ@L_gUwj QϤvR{/BI<-av72^:#ϾzؠC/ K FR;(pnx_e8ZN1)`~^ϥ9ΰ! Xģ})Ap>" k$]K/hFǗ;3 z[M_+^o%|ru;>Em>wUߪ v˛,rH"Ms\Y䭤ߜc9bq^]Lo}zq/ lp7K,8l`<)e~|q#ua tXaaG#{qc8A\tcRsx;[i.XrO[ofC -] Q6>2)DO$ծS_u􀛞JSƅŚxhv YX𭟵e ֥"Wv"7&dtw4[h7aQ Nr^׮gT3Zӝ,te"jcϘecT2g2RTQ7F|۝dٸ2*_x+L*k|{1oj*,W`FDYDdޓ$U@}e>a1HY9lqOWBs*(Ǫ559n߰1 ^dL! e>ه;3\GG$7Ed5_[n;'ؾc#ŌK[f9]m:x8y۴͖f(OhZ܉S0\1nd85{xJsy)0ў6n^A1_?7Y_V،UN'=lJByk~5:Նt4n#j~k:CSmO*C~0y?]l7ocy*05`hrɳ26=\ZGMb[/<2gᬐ)L]{&FwndH,Bӫa9h,wp=پ]gYq6$aWN^<ѮZ!0{/dСyXХ'L8!Q.-o@q X׈(h@h{ yz7T5]ؙw?mL1wDe2\P@Q.'E6^)߯.8G= $MdIRt hƚevG/'ɉ:!mI9`rva3dC6زGsl{]T5nq '`ߊYt_9 )Bj5?qQ7H_2a>GhY1LX"gKMWrV%+Xdd`f{"8S"["d9e>D«lj961 i(ϝWM-S7C1~P7|[h ԯdmXJ}_wJl}{? wՆH vWf/IEpn]~GO)L)ف*[dd .->7YfCt`͵Cl; R}mۯ.ϔW>>KMg?Ҫ2ɖh+m\<`NnRU֚=mW6 8"rB@ժO3QG1IIzz&H[ʎά(qXlC{{%L/cS3{khiɡlcK^ 1@ .eiϝa:OVzvaaV r1Un^H; +oه6;JFNLBkM0wn NQ &KrN{ʔTqhCJgy'/;KI)&QdmTdD7:q!,oj+W؄D(eѮ.]h仢Q9@ <,HdRڮ!WQ@/W#j9f:Xђ~G&2Vl7ωA@i2Ya;7ߦDqp\4XWՔ";؆J*@p-.e}cL㺾QD ;3% 4004ۗr^jts5}>gǹ9fVs/q(GZ֣=hQ+n{l:ב#G/ C i фGM+R+9jU S#Rj)6=f2AIV$=t;"mލ쳓jMa0RƟOo2ؙ2lJ,0PP_]s10T5* uV)'LWRgἂHm.|{-Pe:V=9פVpUzHχN5 =/ecZ&yje/I#_2)6 #YgD O9^_P>3a(1{ OӮvA5q"ʠX>(d)~qϫ$N"/!ق o['DV9-} z* q:P `w  !&+:~~Е?jH{޿WC t%-;,hxw#!Bئ ?Ssg>(j}+6H9#edʆMYQe| p}`xe>EX5.>E+q7-됻'_vF0o36 Ԋ3bjDNP{zEVmXXޓPǬd'/w>Iy?/2Fd19Jl23k'ulayGnn^Ojqo-Qr+P :=e_Yw%t^߶i)+uQ3 +$|BrռX){`I~W{80حUB:F;gE{,\O7SwWw#3-Y7 b3uaźh핋thivҋ΀6Hja@Rmd}x | Y,JK8{='?C;'w:X ~"wB|u.#Q"Ebo(Sm 8!OP p0#e_V%ya;GTtԟaS}r _\Ą,E;&?^]ٜK<84ҁe V f*Nt٥QMz |g2>[V>fLz]<gaj.u; *x`%%͋A6}.X+V؟Ost3=0L ae;b:{}5f䦎%#5!-ٱQhѐ9˦d[·+wZ /1!op$ jXdQ) 7 4r7{g a.S<,4`U3^_^cr='aM;꠳3eJvrl~dgnRos{v[3Ѵ`m<" {G..Fp+$eMa3$k*P]>:TKLJt0l΀fح_]&T4þyTF@buR/|V{\{!,rE*/ڴ- &_v2 X, ~15pa6~ 0Ȓa0 CD&]GO%:fU(*;=I>`p5X))Ҳ S"l lU.58-xR$ͪ~ [Q.ixc<ԽvC=6bhJ΃o>dENentԼ^yK쳥aIŌ;DB}$Sd_1)]~D6n{ۜyt3JT;B1nw"ћJ2`N>F83jJ6( 2BP"n̓PZ}>9p)S'KĘ x 1Z.e 2!iGT9fhd"^Wb >91V%RAgkʗ{9ۭd1*$$!s?d ս\&s~`Ð 犟Pq7? 3D'f\H;::&z4 W0#'RD$̾94A- `=q{K!0jt*eN&A*^/NW~Dɒ%X,;6X\t0`b]uV=o:hkFVΦ!,V]ռԧhA: \SO=l"|R2BRSN7ð3a:EVǏcC,mmHN#Wo,pQ?C 6C%lRo3-X3VLSl,|S8*?6d;TƂw-yG6EF7 L`7DogvO#^{ $"w ܩySWD70bqӳ2ޮ ڣ<*`ms sX{>='ȷW%-sHP$ߌ 0!u NKkVr_;>-I&;P P>瓝xړ4y xeӬSz YxU͘u})HP@; Ce+BźXTƩ_+}·|{e/&7gf-rߗ1VN>M;̖UQND]/7 [Sˈ"(!L}w,>VsX&P.@2)3i#ń`tK$$jnʻFcl.Aa#`u&sGB%:u{wˊ*pV8_P`ų)|H>M̄lӐTlT%ō܏25;7s*<4$L|Ǎ&|8m2l[oaVNGjˇh|bd%\T,e&?p4wɔFkHK|E*`A0yS/wy1_ǞQ- "SrH]03 {ض0?)"CK v&v Q.\t`?#M,y0zjZ-;f@~dh? : Ni ?Jk3cRñ 6Pku&%ijm,7sRPHƘ|SFB6*lEvҁ\)$!mIK׆7ld!3} Jx X4.)XMtDC,vKĆ"/}Х6!C&В(mz&RRgR ?sչ`t٘5î!0+uΉYzVrъ a}tERj7͝AD42t,aD|1EvAk @apS->>I!ع|#ˎϖkiΞLWP b͆v@׆fرAtźN(7*xi+w9i&ZQGǼ5YZ?(b4fe<қ':>5E[FQtzdFoKu'B-O;@iF=_y'qm}W0 hdX"gSq0p)WyHV\qiz i:Q.whRo}Md&!!?訓Yv TЀY`!I;D}%9[H9ta{:fchyqjpJw#ŖCnGtЏ!3Nncx8! rɇ;UC]Y/&kYy7wE.(REiɏY殆 s_rҙҮgX^n!LGp;@B&5-SV@|/Dfȅz@V]Ƭ&/;7OIgB)5:0Zե mt)KvpdL߉h2έN q+_zF]¯\40~|*'[6ldD`v2n529&4W,K嗙Ȉ(gkc;M68mfjSvU2KG떓7ÔaDU?Y2+L.s\8}VIRz˴qwmDO<)h<-̀r! !cT c;FwzR `3 誰Ya| Dq-Ϳb"$w;@m'fʹ(oZm 8Px_az0],W$E{`!cYEDn{~-ٸ_($ɣ3IA #ZvwpCfMMbܬZ;z~,MT>x< Jb(G1rWst?c߫\yK" i:e-W$k^@Z8n¿Uiƌ \)qPL|ZYHrPֆ]:U.xXl޵Hx4w$I Kٵ2 [^K'sbO%ty+t̤|:٩~ 6N1'[њ3EF\s<F.Pw(2͘p1C~X k{9L²Tt' l>pP( GKjAam<.(u)_ ] l<\- )tǨd`!N$.y~ي.X'Jbz9bZ 5MK*!K#q:,s*'0$4n 2ðmqwGVjPRxѡLNf TvI#^fAE Y [h՚SA (AЭqy cm:w3:+>V}^y흒C?vcyP0KO>+OU (BylD7S`5 X`K1p'(!w+yw<'kn,]'ˍ>}y2Ȋ;n]4.[Q/& X>Y8]muy7s 3 a߹n2Cj5v$ڱ e^*pcH5rJMƵ +aC47٣?>5Qh7SZ1!*HL1Y0=AeBtYu"IXh8qPp T㴵;pl'n6zS]긻:FIA將fat^B; ;jWbMFyO Ϲ` #T"%St?T? {LQw.B.x1u'ȯD-bgl M/4.qPˋAusԿP]JðaG tCa`g^+`P" շzF 4(##G'붱YMnio)ytf zg h"m?CΦ3hRfb^+ K2EUB`&JC;8:EKA9kcqU%.ƒ#R/i_4 ]?Oi:V䁘6lUS"W uh'ZkR!-ޑ䃠8&2_rJh;M t4I9GgSuʷoO͇n+Xmڒ^cL3ǜi|O꼭F%G݅ϙ#;S!'(`̩82QG.غQ Q 3%oi%HkRS]dk@Mn_-G޴(NqΚnJ `ME=o!&NC;3g޾܇! }|)x@6LЃ௼I0tVr(Z0DzU;jd-z+\765O% c6 ך) 6e}r%T+w9WOy_]](,B4USi=dPOKvi2RӪC32UL>OnLA P06uWcJyȭ\cg@,+\ ]Y:xlFEǖ\(`0 &L6WU,ViPayA"2WsTc]ZD .lj__ @6؄u++@qf-E:$#XIP'DnifeLm5F GuYM@[(֋vGvbZ8V+[0 ?P<8x@dgC"#eD4F+j ::XV;שuSXg4?ua'Z" ] $W.6~1!0؁'zsBD}LQ`[0 'vNT ;kt+k;m4-)<5ѼhMgRk^zk56[3jXI 4{0T)Rȥ!IH탑_WAn//s]nB; 6L(ȹƂl,hZac{'AnP77c Yf_cN~JA7j:' \ZvOEu&uN 5(3p.*ؖMY/7бba ?CN<쵪p[!CB\2Y!˥|a~V%0Hh;2?d\մ:k5w_JSb΂>x U _A\f "fM\,ƪ=ϭmZ2ziylOQ*d9M N><ư)JO^2;,f 9mRV4W%[wTCރlKFlmYt ` )i|z9-#k0brE͚˲;gϏ4GR CatV! pg&nέgi qwÑeԚPO)T 1`A٣_=dPwRPW![<2]7YrxڻATKpl?ZThA),-hjKeXeȌ.x(GJ X-n4#Ej @#P@Q_,l3carIѱk#p[F;g j ~ɂˆ6GpUAA"eY_qU>Tȷ:?,\譣vRYOg+O1%A܀qSJlqy?qXg2n=qT|V q"(闂3*8Vh&7,M_QNNNuapT\>3Q Ց6XVz__r"XoQ9‹VۧBc$LhQ`r@Z:7SWQnya6#M .ۍAW|OQ,Gtv{c |!3mo^oٚ-c@P&U,&墳0SRD'qU5 HOj.,u[BZƿ̩? 5=}[I 2C\Ye)aKuM,RUR'`y3{i,Wҩog y `d"o:L ^M|nꧻAi$;W87P$(kl쓉5 ]\1dUpTviu:Y֛n_\ X=vgm <)B5 mӂ;VCLwi5w~qJAZډwpk[e^0$**EczGOOY4u \Nĥ];g)hRM2|; 90Q +a2 睌[^BpϢ!l|#EFFf@Kvg@inA%jԚMo[bAҤ9b@?:y>.@4JZhz;q){%i42yjQB) ;;Mx^@irU2eEjRٳidr+wZKЍ+e}!I>}jP3ӌm/:jO:㝕!TKvlvo0~6>JibJ#mB #t>S-$cjˉe$/t#):-}Zpf'$MPӪӧ`@z*&}+3 6SxCN+95@ fV٠ahKLnn jj⦶WTo` D"w;ku&w,Q)l-Eo(XAQx_aӗ(6>]_51:?s#x䘠tKEhߍ2s,_wJ9v)+77W= Nz,1JfDԖTg"BGUk:>)ގd幅kw6ݱƽY\@sr.FY+o{92ŜG`/9*A22D܎C_H"DH|FN@^6vήl1k š;Q?ޤA]{MF2eJ1){/1ĥc ѽ;|'kA>X,(Yv M!\ȃJBTkj=ӖbX M /+mD{fWA& '&Ն!BIĔb !aP5QSA{j7o2 Ps _} ܭ*ޭ_'4ٟh,n /ܡd3$eIh޶ʿYmt;~r2iSa9l#]7VmBfPx>n_&=Pp=xB{6(AXyX!B8Ɯ1'%Fa+bD!Eo[ꬿ!aBݒo6ͰB=z (2[#ǁ[TyWrcw4=:܇a?%X!OY׮8LCK*n OD04hZ< AY;ԘrH;|tRBBF\~ӈ15c-Kylx m"ɅN'.j$pK6\/+'MvF0۟i(ƛ>(i]'‹d+J<e1aq/A܌;Ԃ3pn+yqbThkIOyDUA*sw0''cYC'"^fbpY`'Fim")K8a; Jdm 6A"^n#H3j qsݯYӣN@Yx\[s_nRcX,&!nK)N}V1t0 0V)I%LqD]mXJoLoMQ9b3u{gt@(I$5y">Ԯc!&F-0 ?לӟU>_"{t=l p,`DV$-1J?yd}6繊t*:wIjU(HdieK@jnHID˶ ]X. *N4ƿwj灓0?a(͊z,1EuǡW]m q!Cn&qeC%BNxA RK^YH¸E _ 0 ='$ ?/m{rPtH\=l.Q3] c\lYk)r.> 8v2HZ~g/h0OXm;ןyLòbP@vդy2De{qт cr|SuwֽuB q9Msn*pc&MyOy,Uʕ$\AGFh61Te&Gg <Z/[֩#b|;F;h!LPi n _*"" s T2 GNIjj450 _!{[jܪPyPZluYqzY VM#a9Y "аbFlo=^0S봴4V"p'bKT[ㅔEI6U#Bs)E(1M:CLhHiw}Q͒uG 92gUx+90s̳1;3n+?Knȋ@l )bRcVOn|QuZ ):6L {I, Y>Hx ķnE|^,2yvvw }b8^8/[h3BCX&4]>N)}Pl-yZ?W 2]Rm@PHFǹ>:~`>F_vH]7  o$k(tI>^PCĎTx"Kbcd %=nY! l\DlBf|U)/*ڳhʹ'SjXg?Fi蟇:ߖ\+>|P RDJX%{43 {xȷ֠kKo;?fcL11Gw3Z%E\Z)=^b"#g\|!0e#8ch`8`#))v\nX#6VO09\ho@x z.9rc4VNIsm ,286+AYo+[~_*ΐ ?%—wo X%q f|cmnnIhO/9A/MRkV+9SIPC9ک( i-cЍ'k:#bs5oÛ=j 6-p K8YTxfA[5b 9v*Gtr4Jj;g3/`{CY=wbDj_^&#|{A,x◅!sQnYf [i;A_1 QXW r^,tJrޗnG,dĴP{I (+ J0o\0c4fUVJ:$}=w=zQ۩$Od{_BX#d#wP{|mR)H~tpj:(4 :'}i\鄀L] 6fŌ^A;2-n(hmKr B@N͕Ř,G!d^€"] x8נNcPHV{޺3xJ lȥPQ05™lg=ݢx ĕ2At;No{ɰA>=RUll4>Ko)_A1c˞"r.Gĺl0+dQY2:U{!FއU;;ADI8(yjچ#fk3" hFRHGk0#ىY׷!Is^fs#\J?Ϛ Bf9yE^P1G LKL!8l-QKR$F(\Ӂגv,s];*Jls#/*xÚ,O7gZT|3?N1hjz;ͱ\î9K@_~$1>?\[rv*uRaTxLN>JZSh|2٧4aCe- H~7C1m~ot*&~PR4H.7wK 8g_q45b%mENCh$pٯV(:<=Uv;/=-NA1p=bE(Fx x0xSJxm}FA._-L<Np@gyťaof(/|Z HO?q. Cj{Z*Vݲw?DaN?7b2Lp#[ZA{OUR[QE6q=] N[/oG֊:q{(稰"wsjYQW9lKy}yf:iAaY oo2p^5x^K.^qfrk dEӤIJ7tPx=F`*Ubyq:r㈳cUMh E;4j6Xڭar ϐCmP1 CF->Lurtuħ>_)DO%Cp_$-B׆NybjFD2G"Ëײ(cy#`E)?7FYG @2ӣ[:I9u[@.I]SU$IK Xܥ]IIuo?Ð.ّCއd"BCzhֈ{ LJVa*Wڵt}zK;}RM4ڹY`Se.DzUsĿ()V;u5glڢ_]l|EY6ZsGqp~=>}QBh*4u!VڠώlT6.0gloހτX)j4= oXI wjX8θ#\Jݔ.#I"qJ$*HH#Mr>Vnć[oE i3 `+~H!?-{=|5[ލe;Qb6Kዷ:98īc -,mv(c)d($:"/m.Aj? Pm'DN*R62sN.v S?`Fc+a^VgO#4cdTh:SA}F[#_FþF4~r)֙L8fi $w$2"x7zއz%ZUAfAtq)5{JY析<#þTwg@}o~odB¾Q]삒jz\eA\ѐ>OAh̓  0"˟PMUP<2ԛZ6fQꆜ+toW. ] e^ TAl G**RxR1L!twHκ|@[iٙ!xTS`l9 as[ _;YT0<:Us_ڷDD4L?OOGD0FTpB.P2CnSL2K|1 Z1t6fjЅji8 %FZ#6ÁTwNDL7gzHdAIU3 N[6!L^#{5$jM>E~?9s:6#9sۼuE摑gqŝkϚLC(1" Y·1Tpc5"`=]m`.]ͣ7cl10Q mo9_Ģ};Ta=UwSS,5BIo[P)}7iom/D-ޱp@R-t` . jF_B;3N4tʋeV567R9| B6^bpʟWf$s(:. Q2; kShxIWt`,e-c]pR1Rs'hZLnAҼvNҍ}/ B8R|4FǙ87U>=K)8oD^m7Z4uG)Fu5<%>!#ˋGreoNމz{y.b4 Vh7ހ3U6:IXKWs5u"BCJ~א.u.˰ľSc"@z&H7Z\k N8x^"3y2Ν<--ҳ % $ aQocۯ[Ы,7|#9-(2|F.i1Ԁ3JQma<lY+}BH+u~nc"%Bg[ρu9.ϴQ{Wׂ\eε^R>Td#y."K yߐW-$G妋 k ӗCĶ5[7F׾?c{6>FCjUS1Ce>A$ v`>QQ, !}6MBS*_˖L2!A7uf\K:6{N&OŨ2?h#E>o4C1™F=X݉X/(ND7;jםANB<(VS!'`Ȃ^n^a뛰׶/eLKq OA{EAۮ+,=ZC<#fD i`U󀷩a-)J1|2ace:$J s lC։\Lm)zj/-[$@ejǪߧźRC0@Q5db)bv$D[#4zqg|Fhȓl ID.@:C3;)r3Muh ٹ6RAlAإD'M\41mNylhX'yvSnE/+ˍK(Yw`VLv(BU s*Yhް,H[[V<:NCzܤ0GҠ؎ PLPEϬGQ!=8ІZ7܌[e/>[yϳRh)Bz}{PwmX'= [/uƐ'󘤂SK<6 30Potǣ;Vd9FtғG򂳞[B/[k] '-9Ipr t.yfF?`^ cD94d p Zn&̋U)Z/5H A+6͕`bB f(?}o1TؼcUV>=1As8TGQ ĥKPeMf#7<7z׭3@5@W[ &G`_G3JM9¯m]B*kYEkKB):'f4 +~hS2RBy#UHS,@0ӿlĩZYUVt.؊b??>%v'S v9&F̐͡DG~5Wa U^XnXOG:)m)8-bpzԝ !lq4W:W-D0qҵ:'Tt 譗mf"E[;gd$(hFģq)N"F#G~ :M\ 5.;)˜:]Ksx@+|y1GjNY_ shǒ5߀2T_+v+AћC z1F6kt-jEI>?IPk*wƪ<38wF cn^C2(ꡅ̀`-=c=xft]bAB5FopT<\HuҷC#9>WCNӚvy{*:peYBe:(KMtVV׏X|],{}F`3j\yrP9M#\I~kJ*O&`uJ_-?85h}'oK g'1D?k rM?=?֊Kq/l^ PÄtb T~LOBK܂?K/āp:|UL,7U>Nz\­7[ux[=y꧜ biSmAki>0ۮca:TQ.[`esQ@F$w7ԿK[g/7*ڊ{[ G?VWk7;#[VA`X߃n֌S%oi[7쎝XQC[3&C r^J @y ~cp6?vt ,NDr&7z:˫a.XA>QrGu b p=Cp$6ᡲgfz1Xt"$OgnقsHPcų (e܄׋(IH`Qs!mn8,,2ζU];5FKJ`-B@ ! vj1o u P3Sl n9 Ç7#B!S:5?ȎPE37>kD{֗->0ʕ8JENh~*t\@gEcAOsʟTZSf f٠I6E!$QjsY!-AqʥTAG"\FO!SC'M,>G}|1i= :Q$1akQn.`~x)Eՠl*omsM$9da9Ҿy.-f[\ny: j9_YS'!f{}ഈթa (Y*\J_Jx:hIޡ!+n{tWQ(,4O 4[¥wm.DZDM_k&T'b{hـ}DℑAUOkM(7(cT^m? ǔ::ֵ:>Df4FE)Κ Y4a0ޓݻZV`k>ٚ4B@ЍK/Dt!ٸ']CpeSa$,Ԙ9-Lܓ6?IQ3ᘌCXA::P6FwtBB+ e?(ѻB`q_#_2'1hcijAJW&*r?{Vk p|M=hA!8;[_ @ek[h ^0O~?ttgr|N#4DHu1Xc-_66P_;7A|ؗjY7(ePRo vQFqsy \CA9faG_\LK8hM !Xdģ#12RKUH7iR8sG6˩x/M _ c;흂 >J0׫l,̓}\;1X6IDYZ-Mz?@{*|0\zIlIT@r* n*A6fiҴrT]00r{ 4d 5D=SH`pҀsAp1- ,n$V?pql"{VOmbSuJjD'ݨ9ݍ͂ 3R^Ԕ:|2N} ۠а) }uS\~WZQܞư8@qΊ% \ruF %k11H^%Ɖk).#TW˅9"9&F8IAݻh!I\5~|Xܾ}fZFˍ͌My /f6m*Q!̏3=^uVCGC7>jPb5z`.M=^d6'u*T13 YX rT9uBA]/lvvzo^N, CK뽘mpvѲηvqGbOPEDv+E0~y}΅-^ܷ?qcڕ%x,xNE)E7m#9iF<\4Gh5ZIyX{'W<Ņ)| AvQ[MfUɆ/Е㲢2.V7롕62*ݣvWY7,rrb#c㿴ٱR=KF˜vU␄]li0(^\JX|Ǜ|n(?QWg:1wN2nj?{yL}kv_q{!^֥p@+."4fhغ8Aj9kXٚϯJ~hk *H ehxN|,EG>4r_1yQ?ApE%WtJ@}:dG57Nԫi\VJotyì4btHn6qU ݋ -sr~,P|&qMm|t\< >?܃Jk@f>]^Xa[}CMa9uAC:pQ>2nwE N=Y+^'JI',> OIw1@<Ӻqm/(9>4O@ޫB8wY9EzqX ch ú39iÆg{F{DEL.G*o g=Knp]zנu޺xh!!r@ݧg$ W.䅴[HZPu-jbu/j/->7UhD+h,uKϾ̱ibL҆gˤ]x6[ΰ?qm*O ra'!7㌠qbTҾ?#SS4_I!'*?ْRQ c-WF858q@eګwbwI}4&jr|=?7,ؕAWNvVC `# $XbC\R5X8Q!4y<,\-`R3s2w>Cr9c<\6e֊hzq\)5t 虳2x$(6|q68m@t$$&_!f;|rqUc9aXLDz8 eKM_~&tOzmr ]4y[qU`E4*P/O 4s AIRk^ 1i(Gq9vXr>켲 c9f Cɸ"p, g>c7\(\ko6JFG5\CinzA؍RD+?}0 $BFB_YI_N&?s=Yl5TEw6}N&^OͮmKJ5q\&gu2`o!s'O$qE[`3`+Qzbmz`=(iqU}T H#z.dzK"ĻUK>lHDMɒATA Dz bk4TIH-vʡ\[J=rӧ/#/YJ! Zjuzy8lsԍ#`M) `OӔeduJ ecO! v"L% 6#gǜV O-`/XӴ'Ǝ݄29#IL%,B_1\{`[[Idm&5t7/ݣSCYYA eWj=9PWn@^T)/q;Cpm^3z=0H%&5rC= 41+jb 3ᵶc\LumF7JF1QΕ8갖 =UL U{MsP#vgޖ ٴ3mLإ"hp<ﺈ‡aa"hj+)[ qɬmpg|F+-&ܖG,$"S渿;)6 OAtӻ?tfiCnr&,φ#+`2 +Q̈́{hT$N`=@e{Y1x(5@*Y߸ &AB "IF,ί.DL|1Xrˆ,\d))_\P{/%:DTHP@PM}FjΑlhT g1_̄zD\nl~)Mh%P^YI&MnT|z!Bm(@Oj ѣ[A?T{c[`)QJ.@UNHOA$¬MZ"]˹%Z82M痒$Dp󔯋CRH:Xu&NJ%p9nOEk Bb mr2mz.0%UG:k=p:Oso QΥTeHAĨ1q(s|.wٹF:MHqڅr(oH #1M@1r,/bbhMzx3eJTZܔdw3nm5rOLeUeR)`+a 4~5P8va#Nf_+ݸSO,3uPٳDMà0WG@"g#fd1+Ɠ(]Ka PIWQL 8OSO; GSj)1LrC*h ww/Ľ _5-B #X6B\QCj B- 1󯄬=UL9|%0!h ΃K[Hl*m9=PS:.sҾ|nQvNEҞe.Ht\M67*4whB-۽LWKL+lKE.KЂ+Ux\+܇zX6bjGET (H4`<^wo/qN^+d땡]o ƽB͓кdR+s|v>"̎^s4 ٶ1ST5asBlڏ'uz=,utmvIq\L_.V6G;u H=+:Jզv r#BɹBs/wl1n`S4tY^A3eGtRe˹}黗K|OA@Z7j?vOœudFjNZ… M0-OO-81ãd V":0kÎN݉!V \Ѿ,.- 0pwn%,@M(##r- i:.Qeh5زI.nFL 8 _Eڊ?ңRF!ɇDY*55W\w7NcP,9δaC< $DO.N7Uxfݦ3)W\Ɠ-2YiWb 飥/!7 dsVp9]L0$% 9:g05g1EB68)tqyaMC 0Aܠ(( +Ejyёԟ_a#ߜ2e@Sb`ƌoY}2쿇HʐL5_ۂ A?&ZM1عͬ}M|EO^w̫%K1Ԍ$~ڃN+v$!xRSkWqFJ8nǷ=w"1~O L/M[ ݏY$W6WtO =2!"i:Z&E\%SV lA|yQe|#+mx8꧓cHC ›~ N_aaxW]?KU0 <)@xdѦX Ɖƴ!;-0總=R_ԕbj{*#}ƛ \WΈlmoŠ7wi0aqAdb@bˈZox!?x68٢]č$bA!FZJlw\.YqdNk 1S W'dףa90Of$azDeu겛)qYRZth2Ȣ!=GaAq[qoy6B/qX0ks",yU+7xJz2xJy+Mu"th+ ϥj|=rY4[RT\ PY V/Ҭ s\#ƀxw .\޹}FpĀ\LU}$V7ys> -M {TiT 0h+nrW3 ޶z1اvWMd+8S5YQό=,~K‡۹=t<(qҬ%6ʜjZ5W uLU"} `xJԶޞmfے ?/>JѶw.Eق_%]-A7y9DK) \!HZeV& ͑wPՍvx(]>0p ,~FUՊMov4E^ĩY!5}t!R*|)(M^J{`[hАGN֒_BjhtǭM3:!=;Efjk( 9?y7!/4d(㑼ᡸFA77`eDP|d&myMT{ןFK j  !zUFVu4:'.ȹp;z@ (Pyzqcij?*Ѝv?7́ԫ{ A"׫)g! _r,cE :KLLa,@{q]b&3Wz uybqQ x(dNϴbγ."8없|ܧ RnKW5㻭0K󟟷vQ՗)Hg@k#7eder}½]2۶ l|)V;\s dNu k'ыy4A8"A<FD2#ʈSJ'¾ôb#Iu2`ӣ;`̃2Ti.8Tp*mîlo0' ƨ\)Cw/ZaT,i8q%q9hP]5KLa1#MGorcaGMi~_T{K%&0}J\=*Gkbx E${} 7);ɭ'wv(x7Hԃs~BEQ SBk(k*i˺JCUcgXɘʓ @QEPiʃYN?{ s1u!40׾D9jfz5@ n";яϻE)u!9aHBۄ]1ƾ"hl2_HrJ7m'.@~3/4eI5rsOtAߔp憟{uv]QEݱ嫨&)<,dnjrle筗 Rjb@/>Z &5~PPJ49bstŧK}s٢' :s*:. V3y$m|)\y7G {igXtM=La?ݰvW/?f[<G ߣˈk^&i&t9~W檵}C. <-%\HK3/F;~37A φMWpe pE&,~(yOcImd37&r*as”-u)2)9p[ B:"qԓˎٺG6`ҟ`+/3z_7m%@'a/dM Ўȡԛ6꜇[|jIs@+I5Cj"{´:| Py?vD>ק3PE~J:>jDRW$ʪ69&DIbT{T E-mY8CU@+IsxK)ش~ E0mG=Mm4>lUg*C z2lbe(ΦCK51?cq?8%bt7N"gY^3#L#m)Iš{O Kx^4V+x4IyV6ԄޚqA4 Vj{Dgp'w׃p麒6$G;1s8fELMwMά~lns* ?iPk+gst~v U 4kŒ؊R*m#9Vr%`6+zs>*thtuӈXf P/=Evsi@4+`rkicJrHO'?Ts^;瘓3s[8b,/*ks^K^4th1 B=ԚaZ!0*B6Ȳ|M =N$=) ZM7`#Ϙ+%3G60wLjON ^\4Y{J-]0`q|-uݶ!A0a FeUnX4u%;6lxFdЉ~fQOc t8ٛ0u^j3:rztvy4 k*5ֲ%que=QۙKD ]s!8NJz{E7nd~Tg*0$3# ۫q/2Ýk;zR9-7нlX_V;0ޯ+2w\G ' HE=7ڑ'XNsrfrNT6~pb.M\ /1z4I:`Hڳe:W(13;yE|1 h6weq7ih"/y%Y-VΠ^>lv&򗑜%o{r?>0w-"rk |lSuLi7{<Ya/Csγ&X u7XY@N)h+좙i ZV}ٔ}byK\8tV*%R _]G}X,4ȗշN'!քXرLHbR̉z S2cs!O"vnjiqUPN!XYKMcn/0 8D]]0R1+(209TuC#'cmTQ<&=GC?)gp\91}?SAHp|Xi΁.ZćI:ᅹ̻KrlfQ]P/QXME:{6A~ [Tawr6 > ɮW WaE6 xCjg'PMr3}-4MŮ ~ \9VÓi \Q(oZ-Kc5/Sب|.XN֢9^.v@8vv%9#SXGk7 (B 3mq-|(RMOP(?:RՃ9=Q)n+`OGg3)^Po3d0|nDAʕb?vϮW?s0~ qHZ(*TcZ3x/wvbg }7'ʹ(k}R% |9@QsG^ie_RʓM&ee(\a\G$~!fs-]r$SzYD6+M1'[Gk+qRʁFm.tuT'pЬ4B| CІֲ7GG aV!~m߱OlXuo,5H7r|=G4F-\m?K{{|*Cov*C7igo&.gY%gqOܹ-pH*&>Џ*]E<:[Mp9cUt~XY&fܢ1ps7Σ[g?ػ#[Y縷W,bSg+-:լf1 c#us'3t5Źb3kiXnYk'o!P-|{ y[=4` b7 GRtVMUju%0iql{zI侢g$ 4= nmY, /UZa yLHGV$3d@HBaH4^&Ê=uNY1Za[*smy+7߯XIUrnImG4 JN@=vA~d׾Сk+!ILFײ:Bkb.|5mQ1tpj.lCdRu<,w2%3A:90^i}Fų>ZOd'h/¦bij 1 &廎6 f/lƮvc,$A)PAՠJ Q dߘ푡5: lR(_`CCu>r+SŒfA;;ZAL2\"O? HF{Ņղ2pzR0Ϟ?ԧѵ%@_k /dl :|Q[}}NJl@)'/Kh#^hvi(2D ᓳ@t攏E)Jo֡$cѤ vB `8yB+!H nq7:(^Reko vж>5-RXy|,CSfzF21\o%X+K8k@&aj&oyw[ zlrbB#YNf+_U!'Xte4q6G7ue|TR`,/U@UQ[Cm^=恫eR"VnUwpfKO$p@8bpIv'){ u-WCyübTP]e||{u-A_ޚ 8v~5zoDq(~Պ4@LOwZ֕ G=x3y9G)(L:5f|wD;I .#V.jJq;d:&qJFðmLfpa~FjS6aD: 賜 1H~؎=5 626X̑΍Hr>rۂ.̈́'N.Ⱦs͍ꙊW]`8TV7U (eȩx]/%\,FwOJMUF$_5BUҴDM I/æH ]lD\TjK7{Sc,^Eůsyu\(Hp"H%B|Q [YީS[\7o(Qz>grqLLM_EhR8,#i֊8JqSt/83[j$ bHbv6%tGV<]f0S5.෩8A 9=>0ys絔k&>)ӮC%3nU ]_V522GeT$HX'_84Շ]1>ƢWvN%yec4HD~s:sl 釹 Ŏ#[*뫪(CB/ԤX3281ª9(".o`v 'JS$udBܠ獷7Rx>iLj"wnİ,lsOh epe֢&֎$nSw4ۮ_̔H=2%aH^yp#y"yHGdXb= 0CI7_OG W_t]i|lSrHViЗ I`f%U}V'JutSnH]b<= r5Lq?LXl%J\|}]C 7Y8,~K{CNIW֋B⟨ uO}!LSuT \ &fq e> pg eT;-:p釅(p(d5UGq8% 0ʀw٬lt7 obbc7`D%j9V DXm(ՊQU)4i;shHVVPZ<'y/WQ91t͙`C0m>DSf}foT,3FEUHn{ s LB`C:. wuRX/>f tԕ\g}Nf%H:fgcpőZׇ )aI|^SJWp Zܰ]N< {c"sۇKwR;N߭U=Ӿnhy q@4ԂsbMJl$N2gR ._ $ܘ ò&)瀤2ZЗO l`cvSI!2oZ9:D<,M _(hPtYUM[:Xs ۬F rwNbٚ'\{4&!tInLeӉqRN4hIbE;to yK{wI]s?ȷ.ffΤA3j"ZN.czZϵC-΍U]I#jL¦9ޙ13>tvv]:I6iAF_f8#{zv'ݡ'?p3V8h(mi&G O-4nćzI嗃^9X]c?]I uvgO!NVK3h%,u1.K>=|0̧f 1_UgH/BԺhc.v1IrR{^u%1AH䗘Iܵ:Gl^7jm N: <\v[ʃt]Ə#mo&)sxE`Ҙ3Er e^vH eiXb$J=tDL2Q27>쭤AG-/̰i0cn}l_Z[nٔy (:of+%kW5?LEώ.Hj7bG>@$C󍝬EM؄dۼot\܄dD1lFgÐ~e9f{VஏHU-D#cڠj4\Fnk}:>E}4L[ei^١Z vn .qq@/ d L>2wQ2A`}Te;OX)Nd$Zێ[CRF6Q{u+x|(5ɋQz$Y/;UcJ^5e1I"΁4Sxi7c2`o%h)rksMc)/l75ՙ6OH }'۴h@E KZ'4rҟ#wl]b[~Zor1th*-ǡ+tn ]n/DELp6ed跻-~.=m.3 Y yX=Gz8ճ~Ѳ[ף[ я^J;Ej(f>wb3>շ yp$3UcnM\2er7#gzOL wG_-,FA -zH,ZjM)4՚zc1];!1|_yɉ:nRE(+7x鏀$ޛ| ';eh]إѐmY檃gb6H^\'q[pVјhf)9br{㘠"xF4ݔ^O4`s}$;o6CU<}]-Y(֎0=N$ѲN+J\tF$$ ߍ+5o'Vv8i%>$z'zQ93E- e:QBuŏRZ3:1=%U @: ̐gI]Z:4I%| 'H%Rf7jr*f37LvŠ:lg [c9t|3T)O~؃]Ϡg?M`h)uT㜮]~,UqTh.0rQ  Vų>Z{sqZDp  zZo'[hNs!O;,RYlk5- ҥfG33(xWrNd1*/ںdˑzYĴ_xP_C"  )/iL3!c_u*M8V5GKu64i L.͸qMLؚ;9S%E\uB;ϓQV!KeT-!!YSZ(48+DkizuC"nadt[i8&;l怑/4\wV6AaTh*c7wM*;1 û vnWӾi!9/Гܼì.U.b)# 6Z#0 S&FHbn_U>fMQl*PW:>z )sDrelC2.`#%s9-/;I.qM' ۧlAt Jhc JWŖf-AIq,W)ÙP Ly7%.Bvd2e٢]0G+?ㄼm 9E4"'Ean*:t9iBB,scȒxrk䗨%c3a-2oG1 =ƽPIp1~/o,]PFr1XA2/n~̃`=v}0m̟M6U{m>(4'usAHcװE M棜v-\T~r8[/<>Ws5m%Qʣj5(lq!{ ++^sL8|D0(*ʒ3FW㠩VK9FO:&6lEѢX ?Cp< fW(X3L,=<<~|a Ȫ_Iʽ+OX1LF X-d=657ktR6 u\^1;]6Dh$k>\o73ؼR^jw3cKgBF&A4Ԅ:%NHB6mǏ3>8_ͯ*%/ϩmP>Ƒ0iLJC+uKbY!Maja0Y,ڪ)Ŝ_;y'f¼3pmYņ'HҔݑuEOg 'X`bsq5F2s~;R(G[=li)c6Toh Y'ڄD5sוߜ%/^P }6O_.e4ь 5' 1.sVap^X+u3fh!+^DY/U4&ϴ8ǩTy6^=@UL`{Ϯ;~h\8R)7P(Cõ>t BܹQ >݉ d(|mr#S%P%: T8\)๯Ppv"@hg4Q9+nOo&RrF&5o@kg)O,cAT+#AfIر;B")XfB7(S6g}X䏦p&9,/P) =48!U@zcJE wW Cߨ/'53k"r/:ồoȞ\zGP|#-_]ox#lr,]fF_zJ]A<5j;0U X'&d3]/,֛tqmPc-834d=:K> W Z]HM }Ma70bR>= 5E3d8GSn@C9mTd lLy/U{F1"XtPq0Ce2 RgzաS\w6җk'< Qpn> l1`Nۻ3eU..n([ۗcyd\rD,Lic(8b-:#H_># -' }`M+ۖyl+e]dΩl&(5&jNHn|Pw6(J}]J+:]l0ʘGmQn ^`dͶ VkvK6Kb">uیI|ϛHNy…d$HiDc+d d&f*LX#$W2?dt-;%3|EWQ%2;dDLc;W& {n٭:&C7M$S%DǁobR\l2E%8*tfYvpĪiz?QuNփ̢꽊cu!Aծ3Ě iLW< QdJ\ztwDޓl}P:cjlSL-BY%zDhA+QG?l0Y+Ԛ~I{q;SsWPX%89!9O.wlΟfzIR|9MN'%r"(rQQlOA\ܰCԼ!dUM[" FWb.u<}k'@.ecͧ8 k'ߖZH, 嬒#o%+☗3{@-sކh Z>e}* t ?;`FHR#aIث)'Y >!ٱnPɩ;TkKgɾdz~[,~-}첕u*9)U|~8!Σ0 -CW<>Fnը}*'v<w^C{ Kml{Şb cwlBf%z hfvvKUid퓷 /FdR MB ױxz{jx99.c^#M[/Z=6!t9]>>R9?Dt4\늅 WFp9ѣoZCk?>kj8Es9 }&G s(?w^֞m"EfhK5@EТiI+: N=%ON0.u10Ku~tbFy?w1z!fIcvsU9 M&&PFs4le|BߙmCDUm쌞t-^ކ c"cm}SЫgFP5#2e4K$OjW6VwUa͞ 4q}AOC~5zty[S1BP0 ֕9[?!}?U*I%ܕGk4vٺ%wd|qA'TPQ̋gTXc\4vёbrzy27s<{Sz{WY"ֻ FHak{Ң׍tng~FNIua_v!>)@ᆭJ.s:Pi?M$;unNAn,<k*Hlf3!R*:l25yc<I^6N$od3D97}Mtښ5_!qK :zXIό鶹?[t,+2! D[S i [ZGmvNg >{[y,z':g>UW}l^m ߶dhBct?\P9ܒR{4u`eٍπBn;/u{7YRd?ܕCb%e<̆zL26YVTk<0/@[}Ҋv\wj%džj Ku>@e%F pϢ6ѱ4-Pөǃ@|[ -(WffE ~^oʽ孱Āyv)d -7դ!r^'1+O$y?2獦c_#uHvXTm$w.匘z'Np/t4y\:?mڒu[i٨hzճ5L}VvzE0daՏw^ؗ;):+_(9$ͪy,D"q"4N> Iņ ܒHPT XC$Zb nQ>A l5_3\`'!4ڠ4u@<~Bsbj_9Ai4a._) 7J=vCŸ ]¦CݴO D}li"%G˸8QsB[lg0p*5nnA- YZfastnetmon-1.1.3+dfsg/packages/Fedora21/000077500000000000000000000000001313534057500177405ustar00rootroot00000000000000fastnetmon-1.1.3+dfsg/packages/Fedora21/fastnetmon-1.1.1-1.fc21.x86_64.rpm000066400000000000000000004652241313534057500251340ustar00rootroot00000000000000fastnetmon-1.1.1-1.fc21T>D ,0@Ddd6ee18c6d13bb126a67d88e89f64f74987e0f09YlťR.)R4>@?td   04@D JeyD\ h t    $`+ + +( *8 49 8: l= > ? @ F G H I X Y \ 0] H^ b d e f l t u v wxy&z`pCfastnetmon1.1.11.fc21A high performance DoS/DDoS load analyzer built on top of multiple packet capture engines (NetFlow, IPFIX, sFLOW, netmap, PF_RING, PCAP).A high performance DoS/DDoS load analyzer built on top of multiple packet capture engines (NetFlow, IPFIX, sFLOW, netmap, PF_RING, PCAP).UlocalhostGPLv2System Environment/Daemonshttps://github.com/FastVPSEestiOu/fastnetmonlinuxx86_64 exit 0 if [ $1 -eq 1 ] ; then # Initial installation systemctl preset fastnetmon.service >/dev/null 2>&1 || : fi if [ $1 -eq 2 ]; then # upgrade chmod 700 /var/log/fastnetmon_attacks fi if [ $1 -eq 0 ] ; then # Package removal, not upgrade systemctl --no-reload disable fastnetmon.service > /dev/null 2>&1 || : systemctl stop fastnetmon.service > /dev/null 2>&1 || : fi # Pre remove #if [ $1 -eq 0 ]; then # Uninstall #fi systemctl daemon-reload >/dev/null 2>&1 || : if [ $1 -ge 1 ] ; then # Package upgrade, not uninstall systemctl try-restart fastnetmon.service >/dev/null 2>&1 || : fiX=8AAUUUUUU4c4a8e3eee0923f53669e57375bf91fd5d60a08d4f204fb44048182996ebe36cdb8d69dfbe4f98c6e225ae6924f5f7f2e83647c9457ce72975e90a40f26752896794ac9e10e4b3acb45c9edd1d66aad49dba978ecd7faf8ba163dad1eda9e3ceb6f701343d128cce473e4032b187db6eb8eabc740762074c777ff694a6d3ca45rootrootrootrootrootrootrootrootrootrootrootrootfastnetmon-1.1.1-1.fc21.src.rpmconfig(fastnetmon)fastnetmonfastnetmonfastnetmon(x86-64) @@@@@@@@@@@@@@@@@@@@@@@@@    @/bin/sh/bin/sh/bin/sh/bin/shboost-regexboost-threadconfig(fastnetmon)libboost_regex.so.1.55.0()(64bit)libboost_system.so.1.55.0()(64bit)libboost_thread.so.1.55.0()(64bit)libc.so.6()(64bit)libc.so.6(GLIBC_2.14)(64bit)libc.so.6(GLIBC_2.2.5)(64bit)libc.so.6(GLIBC_2.3)(64bit)libform.so.5()(64bit)libgcc_s.so.1()(64bit)libgcc_s.so.1(GCC_3.0)(64bit)liblog4cpp.so.5()(64bit)libm.so.6()(64bit)libm.so.6(GLIBC_2.2.5)(64bit)libncurses.so.5()(64bit)libpcaplibpcap.so.1()(64bit)libpthread.so.0()(64bit)libpthread.so.0(GLIBC_2.2.5)(64bit)libpthread.so.0(GLIBC_2.3.2)(64bit)libstdc++.so.6()(64bit)libstdc++.so.6(CXXABI_1.3)(64bit)libstdc++.so.6(GLIBCXX_3.4)(64bit)libstdc++.so.6(GLIBCXX_3.4.11)(64bit)libstdc++.so.6(GLIBCXX_3.4.15)(64bit)libstdc++.so.6(GLIBCXX_3.4.9)(64bit)libtinfo.so.5()(64bit)log4cpprpmlib(CompressedFileNames)rpmlib(FileDigests)rpmlib(PayloadFilesHavePrefix)rpmlib(PayloadIsXz)rtld(GNU_HASH)shadow-utilssystemdsystemdsystemd1.1.1-1.fc213.0.4-14.6.0-14.0-15.2-14.12.0.1U@Pavel Odintsov - 1.1.1-1- First RPM package release/bin/sh/bin/sh/bin/sh/bin/shlocalhost 14272863001.1.1-1.fc211.1.1-1.fc211.1.1-1.fc21fastnetmon.confsystemfastnetmon.servicefastnetmon_clientfastnetmonfastnetmon_attacks/etc//etc/systemd//etc/systemd/system//usr/bin//usr/sbin//var/log/-O2 -g -pipe -Wall -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector-strong --param=ssp-buffer-size=4 -grecord-gcc-switches -m64 -mtune=genericcpioxz2x86_64-redhat-linux-gnuASCII textdirectoryELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=f6b90e92780cb42428201d3f31d4a064b544264c, strippedELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=0077015d23f6ddf0eb7275d03394c7ae2b0edac9, strippedRR R RRRRRR RRRRR R%RRR R R RRRRRRRR RRRRRRRRR R%z=qn\",rz.uqA50@E[QE`k; %74{kze S_c:1$ԋ1%,Uԁ`.1brybukH>/r yB^01csC.pEijz-(d2IOHsK]Hq|&EE-ETX(Nח~o Ь__@=OLߜ/+=}5_<=hʷ"ㇾmv&LއKd({ƆZёǹpա_}М=Bӯ_RY#C+X\PB=;@G/r3ˎYB~w'^az.Fm, 45rb VJ45l2ل>Ͳ54k-%)aogDUH9<V\&+.P콵KK%vyo X2jQ }gcgȨ:2 (L< TQN$Nſ2۴K'{b>g/ Z#nq'D>du7 Oa.ں}p%U*u"uú-r">ӱU_ `$qiuPI@,?H}.de_އ5R&eE.FM5bZyȡ:}3`F])nVB ďL#f(V> sn]1rw|::9R\Ol6VX@` aWơΉZ>ar1KAP% ,1l̐#| ps<ÔR_4s2?;8 6Z<]פmX3s +W&~iɖ$a6; 2JD7 6KbdU@GnӅ6TQcP\:-@OxZ㎋vuhn~* 3@<>-dKI<98Yvy 4G* h$v>!;EVY1  5?#z5u\¶7B$hR2A@mZ; H&caZ30i^%& z.~Ër tO&AԚ-g41.+1 ЦmKZq_n\ +f1ǝEfFIį٬ >[/Dq!^Wߐ9MY&0Z}&'8O ?~}oKAlGYF \1cYGa. I ۣ$K8"UsI^x Y\ Ł)RPG<)jW? u&j=!Pet8P[yb?eNpы!c)dd8O;A5K>ӌ OF,ӹӥpF?;Oϸ$.@~6PoRx[b%ͦ/9B 8!3"1޹NV$ĕo^4jôasPF ՠOy3N9n׽L2D=G;^cxU{͗ ~tDK&#hy>ԗ}5;֣pM;[_Ɔ抽xnz \z 8-XP,{Tr8@7_])_.Ql uO<Pc. `F])6G Y惎0e%k,H6DK'W4\dlyչ3 `_7*Q"ҶFMXlOXmg+g%z;gӼB.^:#ZmBM%^IPty}('ǹ%: :-Z8v7 qJ3H[-u^Y0~ŐxɠWX=6Å B1{*f *ѕOBkyn"8 1ͳ~tz|0K3dT<$$4ohPst_.Ck&|%{<Φ>NI)PlZ@^>F[RghUn{Ȍf`@4eeD3D ;+յp8yQtV6' A(VZng*,y Bv,X 8Id˜aЋS~Q0 df{- ?=_pB*w~nnMn8++<,æBG5ӳ/$ W\T%w)Ǯ>0x66%Ub\ S07iS=YNthlr]nr4s =g ӓ:: 2)B.kUiʛn `cZ^l:|Z45k+1#g1wan~[s02a+_<8闉 ث8ɼz[)r1(wRN9@fp8՚ s1`Z*w@n 5"~)n18%ޠy1S9"T k硫xݰ$&emVG㚱J0wle߱h2d4up\7 nכ>%ɞiGE4=Yiw 8~7.IQL(  <Ӄ^Aծvܔ !X! 1E6 u!|8CkzL]J[oq;Ml v`3h샙(%lIlu%S mO%^;i (O5ÓpGt6m {x奒`@ OA dhčS(r5 TYYO7E8R,ֳYANۆԄjRӶ$q Mg40{jO{Wvs(@2Tʂ5y #$hgL{]q,9_YxVysYOH=#@Z[N!Njh*Ò\ER5އ15䬅hgʪ&R **{ .] R6=jMu扉~2> es%BSVtNm()s)d]ﷂ^R8v!)a?%_+ )5u""tNq mGLq/Qj" ĺ ,EsK6=][ =ar0lu7 kFC}٘#YƦ-)2'D)RE%Z/H7{6Ќr$`y9UJ>HB=P&R!^ #w"Ed,K J%mCYKMh-+)|Ɯ=a,Ix#[X[U{!d@dmBE$fk%~=F>]!Qv݌=a*v(\Fҕ]'vsn%" /HE|yw oIUOM ~,EmւM.`: ۿԪFfPbj΢zL|*y*2`|Ye\<Ԛe31/nvx7@K85Pg &NX8%v]fD%.*bukX>=~|0:PwhN#"](vW>gEͼf=k[T;#w>+qz$Z6"|Y *M')+@' 1 }a&I~JӧH!պbb sX,]#,e@.G(J&{l4$ 0n*٠`Ų/s5/^3Re!6G`|=dmwY8+aΖ6t՘,u1‘?`^'|EGp 早l5u+'c[M %n;08ً8R*9V7V0Hq佯ȝ^Th? t&CCټT5NFAUûHw,s5]U# ߾ThZ}<`G?i.U azrRd$oe JJGM䮧`tjQRاS. hejPNp峈 abf;(νmps* >RҢ[L(Ik؁JuxVs'hEw9 +䈩 8GUfew9Yk[kq%(% '}.Yzv:^dST P^Z?zp]Y"30B$F^#Wɲ`O1v/EQle*P(dN!#;qb^$(p5a!_P{T6Z%8XAg,tr=t5 l"j/fQQ},WW J1lV*9@чpoZ"iDL!U EYxO[ˆDh Q;} Q29xvp$85fJ['2_2+D6p)wGV*;a hXziyOTR3'gdg$ƻR2\GW=f=ZoJק1RͨEc #^ *t3m]Z6s v=Wԫ҅ޒXa:keߜ"E wD(01 uUgRA,gc_S}@]GPQұ]϶!5I܃VoA%9H{X|͈Si{Q<Ŭ"NOU5` Mt7 4-ȡa[{(nC9(* ,FZ/4=> cc:en C?9֤r qj$UZAXE6U h[4j*f32(xEL_ 7<'qZz$u 0DYUN fZemsйɲ}5a=ڞfCw+($+JU5Zh\l9jZjϙwOqYUBl#˪D] eB(aSۖxPVg@Vj+08y8 U"1TT'r2]v  Jo5|T't墳ǘvlHPU?”q3 g!*RƘ\3ݳASc}= )*pȼMm"+|@n1D^E+X8k&Y%65:og*vC9l9 L{vk]EvA$8\TS&.ڬ]4ۅ1RT+ꌻL~"-_) KǮWi;3YE|:x8ǰpmiwXJX촪ߡؖD1ʨE/5%?rƽ/@zF:DۭLSL +U4qcyWe09VnM%5Ў:j| B~7F>3nm;{grQ#R˚\la DCM`NWwYLxOV^munwqv @/pIDw!8WFܑiXUo!Xw bcb{wiH}pȻַH>elﱋ;7r6{k*HZt=N zlƻP.2vTy_ߜݽ [ }V{ Bpr xW%&IdFwr5f#H}S_y^7-/b!Ť| `x#GUoUgt64|O-mv$׺~ )%1 S~@uzAem vn qyHdګn:q^(ǓΗRrh5W"k*OD1dͳřGXx?ZCwh@; ٯ1hա핈2nDߓ*O/KP<pNbuQ|I >M`iYu26Wis"{"=Gݐ?ظl>Z+69uyC$LB;y3ʾgB FtRP{RYtjJPA1g q@D/Ԣtf˧О9vj: SlrDs2P $W"̮0| Agt#0Kh)"^{kdU3HOq++-  ĉ!j@sI*kf./7-w`x"(yjFȴaB+D^QT)^Zx H˄3Mf}EW=@7)t=tu fIi> Bcq~dץoe>"+qחo+4x 9`R*MaGv%jk#+ҡ ۞;i:ֵW~~T3e-wn!pmT #n:hnOtOq>?'*Hy̥}8M ?lT^z4W8hlfYު.,$<픀5*確Y_]x* ,`ȥ/vʋ͵ ;6eaRvΉrDpe|"t!=\RI VKWLteB89 ṡf^"67*Ȏ2ퟷ$0M]wdS,g&i1B|We;ꭺe-%ыybOw)3mΧsU޳U ;0\W[_goCM`fCrIЌ쪖׶ځkY1 Iw ȻG,Rk44$;DÞ s80cY)i!*͈xSBGU#ZgMS̓IY p3){2sXv749(󚒬;&{3f ӕRq6r+x򣛂?CGkQ4{EKr;uUR,;(2!6V1RtMT\M@x[H|ydXh6b-=sD (=&$3Co|54%2g⿣:מW/Y) 6㌯Uz=ekľp&9&1|&)R$ f|XhIHv` 0,rS&[&sM^TSʹ2d1EsC_d .xGj;7*jթ QYKmȊ~V9n&LL2+8ŗe;~R?d55p%d7!ekKaPOk$)df=x(xcY.r. "xF>} 4V/ey.zn7KBתv,{k~yxw˽jٰrHuT &DLeƽI(x&䵐 ryiz9"+R+y}hfmrZ07gvER׌mD\( $)bTOߘel%%[ 6 B¾>:M;% 6z[aa~C#Ec5)# 192?J:?+;vw^Vtd&HUmIk+ckeuU zg{}&a]m;F9sAGPAǬB6j#ˢ1} xk6j-x5X؜>9{hQ\"j:f{W鴡 L ʉ6rs wJո9^>V0uBpݗ队IڈfG7a6jS,VR 8%[\%U@D"B?_{G}̜of8C')G~nLQثC#И^=戹)q4ۭ"<$RԞD A?\j%Ti瓃IT˽\ogU/Vl[!X'%ĞZdXI,:?,KN2'q'FK E )ּ֜nnQSYؑKWBL$uĔ[Ee&`~\PGKT@;VevU߆b7x"gUE dq1aM `' _Pz0 l{*3d|G.h3 $k( hMCMq){~?a5՚LkfIb;05@˹jbCQlHn%V@#LPfVRy93S'`p^tm/NAeY"". ydϚi0$PӧaB"+@DE_Xt|2~ڀϼg&~AI6~ůbDP-uJ%5ڢTkncG&cǹE3Ulhyf ޠ:!~Ht&03_;uT%<,aivTgpr GE H2ĽY"U`e,3k{:VcoQ|A".CS4LΉչ%&U8㌋GrBl+iJ5y>l3̐Xy!9$z,5~ n֞;j"cGS¤WڍёlC5B$՝*|aip2aU.G:>l} vݱ;;$jI|%1Owhӷ?d<݊pƷqLoKQD73#Hvq .v?iƯOfnސ.NE{Y:-CpT۪āe!)ߋPqfYHכ2AN1(gJ e' SBMfhe3WW$#Y:nQ9(O6s=.>yTaHˁZm@İ65Yiv^Fv Y #'bQ&24>cU30L4~>M\o"Lo] ="B\=6߂+E-3_lPE[Xj gha㢉W+N"'ҡC<)~|Ņ/h»!f3-иO^;l[)jh/6WLQ־`(3GoKA"DԑGR#ҔxbwJgvf~k5nhӱ] y2U,ml `p+z-v=`uߊ}j6| URBE* veukTFK/-7oTh&fWXcÛ\DhE\XAhk< Ips̟WDǷ,.hE/yV"_@z l-lbh"q$D01 &"53nRZ)Hq*Z3we.L@ްP;? uF5= d9 }Jp:DUv.0/t8e?Mn,.\k(+,h^]ps.;@JCPxLs?C,gϸc nuQ&,(ZGjg&$qň MoY" FSmUU5Vk&'ogt'C#\ϭn6U3+ +l²7y;0H?f)jmu$+ AQfh!@WWN^(1s[ ֙ ūDZPWkWމX>^V}*5#GƁ\$$4čm`9annBp$Ng6kH[-'rȪ-3GU?˴۹:u,"aHvL//M—yf:Ģއojx'Q6Ј:Njr)-8~z^:Kwn5A z!N#EyJ)aNY|7-ne{Aa:9)$pJ̵ T0rkvl pW|6AX( #.n9DkU=.x&\+CmrŻJ l p߂`I%QƹQf&~brHfFI6 a37( $tem—\+9'=7Ƙ} [ЅzӇ [Qj޶8M.غ.U- OcfgwD!Ev́s?֚QvX5 =tEOˮ9Cw%p!*7ڟwVZKq$bpp2Sh$Yq_^\֜G1H*T/\F꼦tTl~Fr OZr4CЙ)/R&j9N)uX~͆cO*Za ,ȦO 7cΠrI|k^MYi 9糰DwϬql৶ $b(/ 5Nmd&\_8&` U\u T $QP9{o9FFk\c Jl~;ż,:Ion Ц"lZ35Un5OÕ0D CK {+ׇM =LֺUڟ19I'%q35#xmsЧY8r9wqffxJ*/AwIshte(*1}a:[k3μt+x!SlxԺӢWg>+T3Ұ0f(lJ&(>2+F)r-:ޝ|Nֹ!nՍPhcU"J1(˥q+f,j,P'j.K q9|Wʎ 3Hb|@mnFj﬍6rgg6nB \lG*$eߘA;|oDi1 <HRP,k EEH< r}> "K6 {8Sr\TQ7?T T2h`^-M\"h|[}oӧ4:`^H"Q-*# t tַWIJ0_jEquAQup¡αJ {dtN}1s"^ 6Ei t7!l\pt&A +GhJ>z } 8ɫ A_⫨PwrxXNp=B[t|Y&, 5=xuEoJsy8z =$-(VX$^ZmZ|EV!m]SJYj V1X\M7ڝha ^c\ی`[is:37 t F2h5D§ HI5cxFw ry)Zb*Ip20Nm8#_8]/7#a1зL3n 0? d<- T @`]q/4OGf^ep[wGک15 uJd:Hɣ"ԘMð"IlEZq~3y$ & =A+V9Jg3 >RǙ^bJC<N' ޾: LE>MLF2ETLE?R!K~%"]i5F7r$V}x%9!a,昭\: _u1 e{"T_鴱M\]1D^.$S.!s8 f!U~vhziYql]tr\,:)]]|!\̎So.W 'h]yJVD1"2۱Aơz}v֩TIjŠx9 q̩ Y `Ě__~jEG('ى3m) qŐW&Y3ƭמVRLːSX_&VׁD+ ʗH=:&@ulNkݩw|1RtnDprh0ZvEPuuu@8yDQ Xy@kwU nyqCUǘ{u `C4u%~Ë1 `T<ֵJ. ,q *"K闛!Y2bA\i鴅xSF6 톤#3Uv4SG ֈtDc&iޖNa!Uv{ s(X/sGBf3EK%Y˵'-Sb~kU|Qe?(8|=-񴶭n8K3DUGj.s5|吏JԺnIڊZ}vxC^wyNe ]ub8B2'/: n7VTG{Ȫ.Wyl_6HـjIb2~n,NO LXE`> }z4y[0;VJ"|A?Fk̃_p)abEID| mL5)udѼډ!k|?t䮱V+y֚Y5w" P{##p[P!opqs2;W2W>Spė&#y.ز;ykQ_8(z9'[iQ.b&0rC["QH,ȳ$XjP)8v_~p~"P2) F5oo#}$ 0Yo,'xnFhb!uR圢ez4n\o?NFu_ `4}2ZI"<5cm\gN׊kv; '(3`L &l"j,߇AefV,'#ΤW)BLC` -\?M`fTQp>=v29Bo$T܇)^g,sӍf |Dxq w?؝ o%Pw&?9v^6R@̺o#1(["bϧrdعIP# S/zO5][ki[-l$2Ov]~rՏBb5zE~@PQ)u>+P=a?m{p !{)ɫ +~}]*nSvfۈ-(w|ډf hEپlAR73YXdORԂV)h$ @Q xIݷ,,Va?NLVV3= *B*lwig H|T8[adﴨ;Rʕl6\F2Eٲvltjkd20th _)!c?\ԇEfs13"š&~K}b1;6/ /9<s2OT8S:7?,^8~-4\]d'LQsFQ:eg4S('1`.{& ᗮZAjXI &ad A6?~('m G[ʼnQbIs!j: cz:4,b٤?Ljqt6A Gp|g2u3ʛcYN_\&܏*z^3ˑa},'p2jZTcz4A<:0CO HL`#M<V\+{\kfa7tKNC]"˥ct SryȘ #,VI~pƣ-1bv^)n ` Ż@۞ C_i@ R'.!UIERSdu Xt_Px<:{yO¢^K@Ԍ 'UW A?JCV3rE.p`jpiέb$M"3GR"SW3͔ yeI04(GA"r\80c)4(f )~Lctw; ־uZ @wZz4 9kxb893ލ|2)A:[;Yj5 cX9!*c$Q2.ZДXS#OFT-r9fNtK]Ze^>q+yFPU݂+ ޛob/mY r =QLJWgRvO [ѲJ,u6wHA:_os" 3N4z2nkf8ڂGK˦vcFRjL_ 80pTxEcn P$5Dv!Ҫn?Uާ|oT HBY}2b{:{fP &B-]eN/|VlXɳv;1XWRiS$6jmR4KtANg{ZBmᎆPErG m2Y)Z0r<^TT(\~􁏹J}M>ECEZʶ_f/s&kɏ]ICqg.^: FqpBE_Xo~8u m 9f(mMY|:dV4G^IH<^q핧c$9u{otQYM'T9\ZK#C ,5ze; P:L^b CTpq^B$U WoфG(,>LX-0} `o>VA6,ɜVㆉe-^%s|½=1O崷7ԴUz_"8sKRģtm7,hS/b#~*Ts}J,NƓCyeO)XYAHu%j`}3*2kZ0Fj:,)%J1>ǜԇ_p|{Tꖼq'* @'+I7ΑWf2^I5MhY!*Ihzb)<O>ԇtv 453ii"|Kץ^zL/8u$R>GMeE0MU&KN@.wc+Nٙ4;JʩYᚼ ϲ5 'KJ)G8 p\XKP;uIbqpuR:.(OjXVhR7 b %(p l[%#.+$ "E8\ 8'udlIbH }@G_4Y$;0ǏP\nokn_^SO/4V٬6$ΠP /+J{LDՓyF &˭rGV e9TQx^ z-'o c8f#q4W{zl. -O54[Mdi)f-H kHO2u-dE^|ubUor\z(ĺbnF]xB`atj9p$'b); {3dyR$N#vS>ef@T UwqyTX g2/pںpPx~tVz;r2  ?δ%C$G< ԲnO i,{zXR{,z(TIhd] T!v ;S:;?jwoQ٠61l B@?Gk ƚ) )?҇L1,_6FP\x '"^ u€C_|zAZtoz (b9uլDwVwu'hfVD$,YoԺ4[ǧ ۃ@YWB8+49d:P58uipu/ys(k'EhG$[=W&!fi+|d /TZ-.eyG|P7 V^(+kt g״m8 ڗ83ĨP Y| a|W % ^}Eژ3 %݉$JosWstWЃ'+Qyu\Ct=k( gIRUcH`KH\lp1i[V>T:Sўv!'Ԉp͡{q:HsYkmWǣ/(aL vʪĩe1Y,uf.F|xLȥ|ǖ;NCe - W84y[eW;npjOi#=<{_C R 1 (|4C8M 7 uz׿٠w,"-OKy0o*Fi1mΗ;*`˘%\x#K:z#jî d, E74dv;`b/>zxHl] g?SMtA.tVѧB݅7]7Ra4?s+AFm;Rv_5ʇw }+/+\.+:&gK$#ơ$Hժ8^(B,47[S3O^ԈB7g-qg6U( xee9\>@xx)#4]=uWLfxp5hetZbr# ZCy9Y5;;d w]R$]q}2Nv5G`$qf[k>љn[Rgj^`R3=Vłޥ=<q zF z+IO@s1%er{O8{pHY\iч~RmtcAe%]\͵ R26ĺ\6%q:  R <@L |ok׫4Fl^kW!Dxʄ숂60\h7^Ɗ/ ŧQmC !g'GfZ`wt|e )Fx^Nʼn3[R1y;,Qv77mo9B Y]azeYW]?`z%賱Ub9OJKf:ۯ:, d'$*m6h![I!*z E71 G"{606t8ˏZ@ѾIe} 7~HQ_Bn@4ٜ` m^Ȫ9;mcatoZoÚH͎vmI:AaUH40HTeنXtXO_!sמ#>xrD|AC O|/BVRp'781 XOjIvT0Qc2ȮS֞={'OM>6rO% {TӲmET1sKoBkFIdvt8Qg΅ے@,:V0R.XerEA%Iqt1ɸLIC63Mͩ$THgy%9=Z#Fض@m^n<*Ci͖I+iQa-_}7 J8t:0T_@W76ֽi }.R'՘(lX.ԮBۈC緘zf9](f &uXdw&B<[޳}2:Ez#IUC螆rse+Jښl{Fw2UKVoЮP?:ЇH"2 v8R gՏW'\3F'XXr0 őLmĘF XZFk5u@FFVxզ6X^9!XֹmH2 nv);8Vp߷ W]Jf61v_J3\֯o8z5M1:C1SՒ,\R3$a6A<_or@^kzuԤ\(K_TeHavs{6+E8F-&~3؃1]'p=dA;qDʼnYeCzeS`p3$gC`OGVʏzćm+fkj=*`w)_c!a0!@ 'ͦcǞ"̺ J?RnYCC&vdzݾ _P"Lќֿv4Mu4ez Wl8)-BA@QXD+ |llOcMh};$<7JMӹo wjF*>y*N.yHf4Cg+s0 MgTUse$8#D!+\I{a42aOގ֌%:VϊaC~t,K&˾ɰaK :u#ˍ0x͕MG]-dD&infDդ'2i?WRBXJ5SVp&vƵE ڮГ^~15c'+"r| 4|-H4%ч goNs~QC` 2@{=gq^Ii1dCOK9|xCNVȱEa{ϭH) hqh1zfB8‡/=BqhOc?ÏvTO<{nB@9`+:2!a]$,uDrQlAb'H!zbT(c9Sl!Jk?[8"iQ]h x?"/L:ep 581Xh6XVP{j")ù\0܏[ϋ]{LŠB7}~S\aK,f0tsm|(ZSW:N*([rӹ9EH[ k]" /+g7lLMIeΆ.?h" }A3٣0 ;Ǔ" mGß`Tc (ʮx)MzųqPa;¨MjMwǚ \uCOD-$nlvN& f'˖tj^  T1}>5L95kDE,qO iwBa'+.&`._֔ =aY] {Ci0G7^HY$Ln|+-D 0l%ZW9v9H{!?&.2^ڻc/t%eu43uj"55G̱Q>ZK3V~V\ H)D ~SoR'|}aI&a8)FxC* `>Ơirm|chtLTL 2 d\.UBMzRa*M8T܏e0vTX,=VZ+"]lQ :fU i؂8mGNZ:`_zw`@f-?4:|Qe{"cB<ڳ# 8mn ~}Y x(Lbv݁JH4b֜= T#O)SUu$QHSDF>X Zk-MC$%/)6U Nbffm{T>b{n?7۪@ exm љ,Oy0E$- e\ Cvr!&*M\94y{Lba~A5gK[#4ƛ>m`,W:H>Hѥ~hVBvb(\VCh3/})\@+ oM_h0Rf:KBEt5JvbN{9kDwUXu$};Bs,XѷZ<ͬs0ž쫱<_XavEJ&?.rUPOy*`@TDKͬB밃G3X4*mV!I66ɻ,~VHn3?RHy?6D}[єgg!vI|obRaj#v&lBq) e6 -E)Phb7fa&E?0 ̻#hg h>$ה+!<,S׀xC˛{se hqa,đX &3jy5IJo{!]U.{,]^tg'A/_D%=DsTKj\ }4զ1qgVT"~?w҂3hq|C'PQDGG]BEvs@9L/ƇC6ZT ބq-7He_V5reGːϊy#D1oxIyȂ]pF8M=,fs#Z]54 000W[=!P7i9UbpY=L=ͣ^f0HZJr 3it4mz?84umhF/;#W8`IomOeUfa8֗b4@~_;5m洏Y|ɢdx1uhߝ+>yxȶ 爸wo2qkA.G; p\a >Y.4}&d u* j4_jmf该dlΰi a8]uܤM8бVdgҪa@(/jd\[S[ ׄg,\M;&天,} Bh(*続%q\Ě-"Ϲ*nK ^E':szīX.+b b\AFۥ7k MQ4@~uPY9 ŒJ۲VrG}~)Qtpk lɧWɣvt.ɟTxp02^ބtJK@h#RN*xn uDۗY%|i=j.¥auʼ 6[ؗ ˼i;O:eNI^zoԼ{zAE'*ߟݱͭmcHZ q|RF|Q_Dso%j5\vfcy\N'N(](݀I;. joC/@ kRR7A(&G^H%eė9 9 uq`=g4I aCH]C 3O?(5WfvtZKʾY5b]{ S Q֚|vO4M9"֣cmCu!ىr6D%VыkA/*~Da-fڳ(K̚-L^~t<0ngw:6t_=ͩx EC}VY]dJR_puxz2 Yug\7sZNe]|Naͳ\j'Djt|'~A<~UɈm d=#gy!+辌Cb˪CN-jQ~ bC ,)KG6'cYa( I mW<}ڜK׬$|DƿIzwy߉ i7/G +5 t1uẽy.(DW 8H~ 2@wlMsX<G&|*WZQ6RW~e] R+<K=L;F}&cDc{7Ɖy,giK+FW죝eZzxsQyǔ@KTZZy[G&χo۽b|:PgHdLQP"^ nSkϺh׬w3z(٫uM4O͞V#u˻~5jD80$DE%bJ2N[IY(v[9y}ec&XdMlc-\RɅ(Pm Q#W+B6./d<Qt%v\%D47!<@ҞA X24"Z,MbmlqrkwP=`kX7E1` ̖bpBv4`@DRsIA!ŠyG>,L@VJ`ՔnhTIxHܜbկ*z}\S YFK{Ho%QU,APg3[?&AKBN;fF˖Gn6b>VgZZXHcBVl%&s )" RV"ZZ4RF65Vٽ5 =&[=BcVԽBƞ/`DBȵo|^@oM&Se>7K(_;2P}oAMtUVo"-͒1_sj?K#˙P53 02[ZbU1sM1zj|мH;o; p/4@ZK~a(;Os]z+~b.=T𶖉]LsU7֭6 &f*ibg!FDzddiٝϣL|vXR\@-N&SlZSſ%S"d[!蝎Bۢ,R 2p[keضJHq =O҂T U^e1 -NA\812%C].h|s`>ۧ>EHTm('eA=p~fĩo<_EY]pqҋx'FnzðY 4i$Iga MQ/a]Cc($Cۘ,0H~,%2+޹@Y’3$ 1;CA$21߼DR ۖG'6iyH띚cR9;vnN7k=p lJ]J$6fF>U_IvQ{ fVL-\oD _U-&IwV _1*43bBBQ)U8˿vvhOIݛ|"˶lEݒ+&Gztyל1%ƽL5ԋ lk`mW| /d}]Z@IG@S=;jiPJdd҄U Nw>`%T&ˍG\E[:FnpmK7֚B- л.sVs a4bF__Aʄ>3i)?2yO:Ԗ`"P2ִs ldtxu :R&#.HbCR%T \ xd܈;Z#zU_U_3۟d!#c",T|T2KB!pbXK`tVƫOFyvn2qdI'̻AOLxsXdLj̇0BϚy*}L2t8S_O *dl 4 נ&{݀dȷc{H`6|hr٫^vȩbrm|0`F0|p?uxA.a9yvlZs t`#/|)a tRm(>d7{,XIYGH^WNh9ipozFe-"ͣFPlC:=i6S5=5ھ^(8kSn""R5_]֦b8ۺRcC"8h얂eI~GV],m*gwK2̇Z,Z+(iF=8;m p! @qr!f/]~8yµ{#Z{wIA=d *4nuF?5 $f1pb@źo:](oZ)I^^js\+9۱v=D v{C^{C6gsUWVư7bgn?]'Kr Kcp .C92g,|! RȾhC7/1yZV:xN׼JL_d!ORe)Rƥ\oso˱4*h8`4rZCuNnX-vugԅbѲJIYELQGc$Ύa^6 &aBq, 椶0"=S}l4q}^*G¯OÄ.b&ζӿ|jD)lԝfk30R \0&m|ʴ9ų 'ၭ%<-h=Ъl֬#!QVl+Pm(ZL @TP"RdD)#֤#`cm?UmU2N}Oa8N_J#^~Sf 4 F NgXd`LA79uQOD5gE7 ϐyMp5XQzb!VX%^J^]< ??S PHRz}&N"ke{C)p6IkZ]n.  :'Z@Rxog rP[59ҕʭm5a-Qu**-8C *YlZ4Ї6#u2F32BlVюYF->?Bf,),KygY2+aoG/DؒH|!#tkmCqኼ*NKtOk]pPkS:{n' cuH˃9GQVݎαSB :+0{ -{g23ϫ"8D\G;b!O 9R> ^s5 ]Yڳ6LynE]ͶEqViL 1'NRN3j+,%PӋs@)[+w,(8u FPڣUAT 7ۛuK|GWߘfЯYނ8h;(@ i"e5ˈ^=2PhjsÃDJ]Gmt'!dˢHi{Bݘ0J}rcK$\LD,O!6<FwOnf] y0(~}]w1逜.їf UNʊ'!5AU[`5s >EGzaQp~G>wK.x}} dN:Y^Q=YM yhBCK< JkM;jVÅKLnr5Zu'' `|a*mHiW3y5D-]ZTx KfRʦ}?*TN䔞([ExP9Oq9BVy$/9buZ;vl\{Br6m_;%[I Ge=n!c s @կd-yWx_B^\W"yaOHhPy KY>rT4Rtt5 8"'ԟ,b?ɵ{,7f]SǠK=CFrnv"1OE #8"@ 0Jn v 8'V[C#Wo1@K>Ҁ O]C6X#l/Lvb 2?s: r1,9HpxR\uYQqKИ7)@.>}{nOz MBoog70eǤR^gXt9ē" ~5&<8r_>|@YTg/TaϠ`dd %b)HDo6"bxWN1mn u$}."f<䘆}B$;jn Dl_03G1֒4?z>)~tiG! B'z4Hr'仰fߪD׿.@"[0us(0%. U;w2mA ߈qv3Sd Vz9+ (r٩b 8-rmֲqa^&] @XYq6p5*U. e+,7C܌ӛP9 j=[C*s0c(+a:T?l谋A?nW3i[Eڗ +}(X}af]hmnE{!s i31KW8n2yG7i3_Wʹ @!Ն4/: *Vk AOZ7(F;3G o!C}夌gMەzLdfz~K_yQ-Ȓ<Qvk.eqM>gFv%ϝZ=CQH 5Iyf}2g}=0gNIaX,ꩧ?pm|GFY,h:(.iI-(éw'yOJ t($f+i e䠢m)68'^`t{\De6XQ绂j^R?]fsv0tҰZ&C;\WN̂ih*ݬr'Fz~2IzШ_/n(~Wau!{\˔R!}j$[W<.Aw|]'qFA;>wh*H0m3u@Z C:yx-joI4o{xI!Σeo=97 $[Ԁ% B+Æc SB V$Rq)@n}E k4!PYnfŅEhg_.}] ۘDyhLgLRm,z!¦nUmFaH Ț,it0jz%2!dZȰ.u:ażDucdDc%DyQ}& ґ9՗ Iho_]Kb7v] 2SA]W< 2{F8Bl꜌mSD*7%40oBM`[kTqָ,],(@YC+v\ GVbgbrP>E#Fly ]P>H.1Ogfk=$ +*hS*IKs+m`JG:@ B34,߬v.Ϲ/1;%HKM[挅1i6i&|X(U̞-fu!IhI|L.ю45<=A>=JbxEUI"B 4dmwO&緧7qւ 0vgp{OLj>vڣa ztmc`N< חJR F s=צm]>7FTҬY%<Sq̿wEz[= oJ0q(%K3 [Dl֬\6e '%IQnCw\M+7}8X_UJ'6vaތeE>2b1u2GxF_qW2Ho!-Rт 7RoXh6C=΁ iմ= 1QR)ôu_ʯt0xYo:g4SD@gh/:YR=HEJDoLI}dHm𷟥q rn?)L Ah֛W/r6l@فn x/l[S[o..oe`>($r 4!W'oD F!5^UN NÉCrlQ:kc>mKK,~;:l-ڂ>母2P ;)<,A{.0;A kD/[Il'3+r>@Pϱew8awhCfbZK<DJe܋ߪ[Us-PWa6pP{8}֝Wު>Hm4M)5UllBa3V(Dl`-I*9u'XH8{IX`r5̼Z' "YJ*yTꦮkkv}]˕:e HIc*~EPJV=7߇ъSَ]Sl9,)0{9bG$}ƃ̵@re ֽy '/j]csH DawE43)|N;WiM^;LfP>gY FQ,c6`yJ;ʼ \y”fUM-z'W2frQ,eq8Pc;ߗE"!%pR$keuSQ֩,,mQfʐ$vɳZ)fdM뾸Ŧ|Ttݺt1gV(Me>u]V) PB "5_pWᄈ̏ t=x̞%ncJ])7k Hq5/0:2n͡_8K%`F6 iH/O t= byƍq<3Bm*D)[-#C8_/EP[%9kK?sISfhHuӫ2S`O7 tMcg)^= rWE(_;Ԟ.!,pBɴ aJJ4Otʞ=Dl&LͰO]Z*ʀD1'yiaU;9cwi].0e`~ciDsAK6\.P E}͂b`5F!LpN7>=hCX|=yeq8*Yٔ(hK}U@ [y$b}/$|L &?Vy1q.S4u-v+]Z>{}}O Ώ"Uw>Rdawn!-hvxHW(Ȥ秐|m (y .C4+JC>Vjs})O$?Фޥ~.'4QFEV,(5.4G5Xs'LiU桡:!oL&&o&S?]MfS4p֔[?1M(=dh}n{ vY=r'mD='o,w2L"^cbχHvY;W*G G8܃9ӈKD8Vqz|J @·,L}=jiFo\-o5Jhǵ7;3Cp:&ƏW}8CRW<⪦#^-jo6N~mֲg߇=TVnLsdabɕc9Ms1iulRIu 9n纐: ?, azB8,o-#ݰssa{\pĄTY}|oNC$Rh9Qxx@ ayLK!(dC6/rEW?h'e(z{tNB61]UkWd#%%SյȤ}=/JfG&[L߁5~:瘫vPsŖ>ai1';4`O Tˤ$YKƒiH^2&ZnvbP^3^k$ PWBPw }lT70L`p'N1vd]u3"߱֙H}] g}HBc$Ma}67QJ+ltDxKu~_ciK<8qF8UQxoUzVcX٣ilĸ#? ѭMGqQ[ WwԤX!)Ս0 DO-] 1&Ԅe8h&bW8'kE@JŸ|nM4<}hQar9Kv,t r_ [=;oW.nBvr㐳bu P_ptx gn5NԧG?b49u S ͛|JėŘ[k͵=LU#+>ТJAg$ 1ehYo/X 8(L#Sz)tPRWjgjxA;戵ދˠHO0r?,"&r4LѹК@D7BxzuU}}ӢPN[ BJZz5N@XyEh@,-[Rll>Оa~>:Iݓ= )ת՞ci0O3oOh;JL|& u0:=M$՟5*O0ɿ*@ `Lj~OUڪ0lL?G0[H UJf^xg$ n8_Y Z}!!:Bק?^OATsA~zGP;d5yGb}BjˌL^3^W պXa$/Łzh_mH)Ele|yÎ}+ԵHvPNBR/ÞR\2gxЊSԲɄ"^Sl 1Vt-fJР' D; B㶵]uaD^+;uϿЖl`-g)~VK QdesbF/ j`&-v {,MCuE!xF=_ Z[m#teί}uZkWV/NID1elDT"_uZ[S])?>4&L3 6#9>>,%]b/-VJ/>4"lT":ҁW]%S]LO^hҡF^[(Dh;AqzC7K` |*C/v@5 at<=V dMn/{qDq@$ $YcT"sa6P2gT%1ѻw=FNu^!&;;gV3HsjTwvLYyHMZIt|]0Eg(s=Ub!i1 w d@o_'gʼn :c"pnʥ1)7 iȋv֑|2(*~\h DYn D> 2}1ΨesI?>|;B =WEj&f 2֕e4ح f_-a $AX-ǣ#bѣ),)p ̕ BTib6buBgad @'z z@[s}U4St{\#R21G<ӏ<@&K)sRWzV+*+gW }ɠ\PIⷜ8 'DVʈK>6 ŻeS ?nc :۽ 6wM+޼MC)Ï]S o C`0dT mz!|_N5/ L$IYl*T-Qp5ŸD x]J8hjb` ǦY6.sקЮ̆3')#fY]6'B~*&Hǝgdt;$E,-pt ,ȿ2'% cYHh [>DTFda>8iZj1SUA OZՋ O"P =XWJS.w(y`¹)^s 0 vunώ;BJc760Bt:E I jz, |xe_>ElZ7e"y^F.Y08 nFs$<}_4,aB4B30/Dk]lٹU(uԑ4K+E9-`,h-}˞bN`d((!I2%#|PyqWh=-.Rሽ!bsi. 'NL|Q=P 3{ā^Jk3ok潘h Ωi:A P> ~Q6ϳ*hb愾K,Z68\%pu.ϸRO46]ȏONsTMb}$Aza=~ mdٽ҆3}oOhWTX*YӁL=%XtxeJ}ŀ33ҙCh'FmX 幏4pWoRu?ᦸc:$C|.o&_fv>2~Շ%yP=`ܷի=Ky0(v,D2^jilBb;h(8`]~TyjZ+ N[: ē{$yrVWc2=ǤGL(X{õ_XQ16ؠ3b!QԲGT\EXtҗPhy u˒nQ٘ m<`V΋7Im$x5A>qZ0|/`n@e~o j,|ْPpoBfy^LDu3<`7&(|=!N'[S% P#Go\Rv @ZQ6RӊVE;s3(u$Fb~ bqCra- 1lob'hP +sǽ,*݄E!y5U<j!pA~bQbǏ:Oz+tK˷@m{ɫ.C~tj'bdBz6PpNH%vh6 9vB s샊σg@ 3,šTjGm*̄ ?#?g-Djz ,8gɛS1vKxX<ϕׄF-6=lN s[7~Qzeڬ)#V:z & KBjoxo͊ckzLB? /3J[I$d 5`bԉ_/ ;IWZ 56rY~*,  [W3U ֬~D働︖4 ]H]w[65Y ki#6}=Τ'pU jAѭJMHJs۰2-0ZCk^Qs =H۔m68OäH^%ߕz~vxp~3B/c0g@‹ j\Vӱi(.{5~7Ww^9QuS8xp.=FW *=Hq1Ȣ ޷'VdX&Y<5 (. Ȳp<?#H,Ұۡ#s12RS\V&NLď1~,aZKtoQj}+IO+K q(½/z{|Y6U CIyE3rrZ% (U/N}$ ?3D_`e/gsD&r1Y,4G7[gqrsC;7M{^=WvuC~iw!q [ɣ؆ lW G ICb^dbȼZ9# %N-$;W}N)'*~;gUx|::3 rce[ko/ZZ88su 拹#:\O<>+$m"3T F3e\YXH|ۣwVT ǼXж `BZ33>.@y˝ɘMoY KZw1vt^S*חXO-9މ_xzĪ4gDZ oaHم"z- uŰ n~ v:rtfWXV۹CͅÕ8~QX0%(]gAygJ^`9-<": ZG&ϸSؠիhMDI`K*n.d&]Avz[܃-84CR J749jgi&efXk 9Y#֍l D~N^7;̏ %*v+}:`34G(7ݞ*U[XQIy5bޛ4zIMU:j 89Mv׳{7ͬz][!<jhn9pz ݱ 5->{`6E` +[F/6!F١2KTןW˛ H 5 %N~ߗ|\F$נ$7A#=42P}g-S Aҗ20"gh=x6oHz\s{][%V!؀oApdkwX&GI"D5vP dY2ϚJ@d9bӱ>(hAץswJ fΙ ؏S:T=(m a oJ ۰:ݔu+ VGomŜ0@Ez!B2Q )wKa}|O%iY) CcXAGso]&@Bv -%msrXw0(B~0RCې:(VN[u95pBR]4R]F!Vխ? 7o|L:#m3ui"O? n 6 "}'=Ԅ#H:}ԖhX=_`0^uYr-n\AM"@&㬛k8UY"pr+9E[/Pr)Nξ>F@Iz[E?ˢ{[ڊrj!a ;0-pA%؊0^#MYݱ(dW[*}iJ#ɴ }S@LieH&8XhiKq<nST^mi~#h$6ԚA,xֻߧ(vxPs߀n {c 0Q.dkDq2) hc-Lmg,XK ]>t\ d&|0$$znISJt&x?zQ9I {~z7o}KVбH$>jƐhP5< 1tدBݭ@\wУv>ə]D)v5@[UZ/Z-irQ3$C%ujfjs~`M(?'?KACC!HyojRwU50~"Aznl[y(M ? R/>GVK3TfZjԪ&0'UtH+eP+y, 89k|a UoA~kKi/CaUP| .QeuU#2k?2FOpcBhrTgʪ));ky؅YaHVlJLpQ@R[-hۨTHJnOub,t%ᳪꟸ@(x;@? 73YS~`$v@xn'5"q(CFN[Ub|#)7Y/@Tob%@۝BC)x),ś/:3gpj8Ϭ@8@A=;^ş<盲0e5dw"`邸| .IՇN̪۲:B/\^%2&Pհ.|O7hGHWHJf_C@g n){"Ƿg= D![kaᬃ]y5/ &1?hadcKDJ {;JᨂEZroz1#FΛ[ʗmYdBQ!P~`s@̬]>ϏE_ 0~^aK4sa`_sw5mO{z@q|aABɎ$$ՐZf̹7wUdXç,&$YCA]USXQJ+%{WpӳOxNRLTN}SA|vRTzH [qG Oߕq4;imQv ƜY!(7 0ջzVXoZw[GUxH iq볡eF@qїzj SU'0*V8ƍgA34̈́m26j(&qyE4G Xī ^i\3e%+bm3ÂԍZ$D~֐}j)߀Q?PLʑu- h 8S%>tsKYB¼xB786 gk?c1_R۶|ȩ*5ϕA5':rFi.8k%+ Ej;._}q=`xHR"يGc w_j=Oޛ0"f_©>]=Cf?4u D͂ũGWU d孈Z?0m̙zālLf)c0=щW@i!QWRYؿUi,A8Y>f>T&}^^D6ham2Ht=9p*ϐ(d+Mq 7]>l=Ghw$r hq)1]{HLW-b|٠»KR@VڀOؓ.y5?s}ΎmnQCvydFM.]ʮX=HSmSCF?{;7r 8%;2\Ro*`pmmE@*?_yRߒwd\QQ'j|NѭE"iӋ`@[db}.77G]{r-ӕ yRB,3V'Ͻ%\W]wX V!Ħ_x#y(rZ 2!a[AW>Q3 X1/E`^86h 1DR&d[{0g ŗ8ѓFݠPF6xS }WDOF*D\wH8g.Oo;<`-fڧr;=n M5|{tj1VhBoO^/[,cPe8r7+iUr+hI:DUR:ܰí B(Cj wu7ʤ&vUK^@2 Iz\,&:<=pчYY{ϫ␕DTbe)cbvA.MB {C\{O"dv$Edg',`Y. T(7Ε@ $bMK,ionv~~ar4FM[/ UE&==P VGH+{&mL>Ndʚ : 1Eq'<>fAV⏛̆ +Q; o8CI5Ah@-JED8}JABM=rz|F(4YG+t ނ쮔3 ![sy:ݗc긡h&hTL>;O]xc>,$ȉhݪf=?7=>#jAU5X3A.[Y%hEY+^?& ª/:?9|z kcMP Szfj[IiƉK$sp@ȹ;K  п9.:e$LfQm E6GT#ZUr.xx=7 J4OFq ďTшE:MZmJRq!@8lXVOcHjIƾ`L;|liؠoz.'o kSj @sqZbn܂!h {X&JVIZ:a#!TVn7:>+U5Zm1V_FK%w @UF}kRBjnvsk q0x(INor_{;E /3_Lxի$Lw;ሊ޵~{6jD<t3bthQiPvg(:834WX&w'}[OeTbTdO[b}1hC#aAy@*LuTf|f$́ioPZ*y%B],VW:L+L%Yz'I5֒E=!!n }8xA\rNwk3kN)HzMxk 5<$AGj'V5E΅nH,iqdȴ*JcKP]$Q/Z$]j'q4 q8'1CX]_a9:*k'Ɣ&eE xw^=qEo8TN7SA6W5\|l t8VrA)D^u)GbJZzq#4fȦ + :/<6\Zķw\vk>bfu75zna#m99WBH489.5@O4RgWNtǝ9@k%Y<7\Bi֬;Xs0R}-V΁ \D#4+$U-ϳ]&Khd7ܯ&S \3;F(R65u]ko&3V]*xhUoGy2N>0m? } q/$7:ZMe9o3΢<(Mtۘ-1!wu\܄pD|56C~PQ9嵟W^n#g1gPEfn:Z S>?6`4ןŁh1Vk7]rAf<ޟ,&@";[Ƅ_Y3UCsl_,[EbA=z㉸=TVLBw} vd I6Arڃ,kU8%pƯ>NV)]h;jj(`{ Fucևƨk%]Rglq`&"Jǩ*!(5sMX隂IU: <.}u3YGf|:^!z@:89JL;nZ{I!BMW ^3_ vy^`wAat7VkH>C'_SdY|(b:ʤ!cd,1ԣƫөוe x2dbf%%vNj}cqAp*{&Gk,yFvv`99= #1-4*j9ֺ8hD1\ ;'Ȥ6L&Qi">c|Zxq$V\D?D C{2}>&!=ї 8fSړgt7hK=[wiϭzy+_틿IX`B]#;zW/oרBLGmuܸQ' \ t7% -4b1P;E`==MG> Gjx^.d\nU3-jXbtxMJ7Aɽͪze;2/MS3O q3bt@[F ChaHI>Gi*_>TYʃ$@dz7yR۟I*WَjRiۓ"3-M oD*祢prulïئD㖗9RH2n G]t Uo⌞LD7x8_K)i7H`AP]Ԙ;M%5ٯM*S 64=:#٧uA(TV#/qS4#Fq_Us q |]{ZqáZ6GaEDVLT%mS 3Ro|0 K]Cc3Iiɼsa*jp-EcDq3-Bv?Nv=>Vzt'A5UvN@P0"<.h#TG K US1` , l4355qS#l 勉M'!fhkсo(&]3 )$ʶqR֟uUahkH*FjEV-]G$j3/ HqPz5uUyJ 2}I} ӈ([J [r˜`6 &Eg5,  Ȝ̎O$&2EʼBC-7iR(u~@}>R X{1;/ۆ$)ۯ R Gsr.m-x3pyi+aWʜ@v*m0< e=yR}Jo$Ud͘?d̑Wb Jq3O^aD-bmM#q0C 4irMZdKX ߬7-ʿwinPbDd‹s,VŁ4V}䱬|f^P3bSZ}s+ ħ/vx((,tM7{u+D˦0G܎j.NA1}̬SܪNM$tYEt iOX5=r1lɷXndoZ$) 2gvx RY?]^-N!N>GW ޥ%TAlt ΒZJ٧?_t݉ ;"Wdz1eS4&2ƌ1 C*ـ3;A{>GSӭ Z?- d jfŰ|= \UO4&!eTM1p1'Hw9@v fcWQMfCŤv8zI@r{y 9".9J¥$}p먩KLFqdz:(qľ"_.$}@k%B`0/u"'ʍ5t UUJH4;Xxc/K= dy< D5YAJop:A؂M̪i6D;4YT⿫AgZ0}~'q)(Pl`0QCĖcWQ q]+zEU%'_hˣ b?wm!B΃7LSJ} _@e/Eq ˓(<[WDaV8c x6{jLK;9 j(He6Vǂ/}Q!5o'FV!/?&!܃ @{V׻d-]&m~(e)O mE ˹83R^(ɋ w76bsFI)ffk#!TOT=w*44̬\CC{@QBs7H0Du+0} T2\k9B$)5\*Dz){zEx^0 gV;Z]n\ c?Vն9IK^R\p.:@(n|\Tdu3,"͙@ 2 4$9m'3aKucdYeIYs8NW *>Ja #!bwAY]F=?}:kWb"}9zAQ ˮH|5GHJ9e- `j#Ziq8a$Ҽ mrf|֡\Uu@ޏNrb'[1Y%y;Iiwlc9ySpFTcKP~ I67 yݠu 0gc<0]+LUj Cg1@!~~ =8.wL%حUfryJ|+]g+NhR5V7^}g{t4*G}6g f2]bޒ(e} >멽pd0JݜkhbH~{Hċ *wZ*D9:ܲBe?LOz\`+rp4Z(Ζ+u"ɍ9`{Uk)}C '}"$#d%R$ĥVfy+GZdQcxfl\3aq&+zn5oJU\g^HhX7bx< QZm7AދY(N&)W38–ЬcEXQw蹦 ^!PQ wcR[(a]LعS#vCt$bëXxfKCgᢴpw@5sIU#V(h:Z5? p1Q:M5&(0a̾1xV"Ddէ1dZ셺;Kv׺zP97ےĻďBUt+%9—?\B+I! I߃ ,wxLU6;ޱ6P6䟳Ox6<9 Wz/#;T&dB<9상 *_/R1-TZ%hw,=qgfsT>S9^9wO&-q\P)ϰF ^0VylC!PZc.XO=FrX# Y"VHPj Oԟd %u4N(:5eژU#/uYP Je֙l' MLRFqd5|m0!u׫f׶[}%`:K݅ h[1owN͋ܥ RT}Obf,!_@Y*$u?\wEV-Θ?Ǖv kuȈdp+襸+ m>"¸TF% g-lBj  j\ ۯAٞgQ=*F 5A+ )ՉwaY6pKx5EAN 0g0K@=ueQ1L-]LϞaso~VG/>:m5AFЦ]BI=On霈b0[l$^hϙU]A{zyS{ L*j|r;] BY\(,|dRLEhX6 oڵT \N|H [8OmEJvwtKAx|0]ϭ@6uKQ!]X0kdXuYI21h1cvDUSsLT;M|,Gxe%_\ 2 zz9:Pb= V75KNP^v6T\Wh&ZRm{W82MüJ^ C^5⛒4@f Oa)<ӟ$e*G}I{؜X uY4-|:Tn]ԑ{;\3򝉤]R< {T4(aZ$4hM"d\n7wȸSat+4X$_C [ 9'd;O#e߄aJw.3O7L@ߦ% 5KU K)qBC&/ E{_{66=qa#tkl1l' fƮx ۈ|K#fM`_hG)_#%)YH0P"RRG!k@BuM{`1&DT]׭[݄Ii"ǁBMPBDm·?\F'()oʄ4Ů; L,KVWLz7>"9g IBM `#לvT.x:*?N:ڶu+ /RZMzlT2˂}QKfc=Q2Yn/T57ť0_Uv?Sfg%z{ %F+WooҼLK],!59_/IZTA/F"xvW⠉uFzC4JeTAWL@BPE6UIG/8bh3gFc$J)s[-& Ь"ORyUNio.Wwd'ڿ9O;ii!73z.;_oJhJq ׄyG<`i3Lv|' 89Fȓ}oL4ev2aB'?J .*7a9m>WZAck!ӉVwzܳjT*NiX%v¸ya<ɑC dUmB#j#+1}> ̩j9T#+ ղ;k YڋiAy-D|eӀwjɾ]:ĕvQ6ˢPG;v'qPQM Je*"Ԇ)I3C K jy3t0&Z\#")X@ݽq$i5Ce_avg E).fd-SFgog!1E`8ࢱώL5TxG#KE1"%S[rcPr S[֔)޿C.׿2i9N?7ګLJǪa>K)uQzs^'zp Idlz7̪uvp`N/=fꍫSP;.gW3M/4(%lص݁ +tcc\~N:OT*DZۛqjYi3>@7G\K$|v,躯\ޟʍАSgx<+qQ`W79x_ü&OQq>u_U!l @9x|U8?I숮ɓ;S|@Bбjd]bJHGdAS agg xOTV%)}Iu٫83? rKU{Aiǹ $L Bַ;?{3A(y!FI^;5cC{3J#+.'sJM/qKT4CL5¼߁K _~X$E:u-}N'H j R"*_B?Uۃa$'rdԩYn  ~Vω݁q7h-5-vO "0xXk^fy 5ˠ{/M X~/9I">bފN-_-šAGx )Tٞjׇʷ4ͤ;5#\M2*8>ҼޑEfU @r ;Fۼ[ TëtQ2 HZw@u 敠&G>d͗$ )~gjZ6AZf_[lkId)/OFo}h!80yB U(}FC1s3^C,1/XY]#F@SˆejosdQ0{] )rMf6qBՂ ^U@:u *lzU\{_q杇t\\GkB\wZ RbSRw\޽̸$rwN9֏"oD23.dHPfzIa7c$7*@3Lk:vR@nsٔvg}pL5,>[K26ɧ$:;P ,woByA+{L,:\zrgePiymv# 0hwxڶ&Yt;9qg⋳leS @O3r %{vJ |TT]~ޙqAat܇[ tx>ԏ£t;KQOKs1dD=1TS|ὲqJѝhbQ4/=L_BHGw+Ek@dy.#L:U~Z֝W+$J:tFTn3ت}.3];xoZS`4"C=xwݡK}ỰG:ep#O92sfڨWa(j#g/5HPk֞8/R.Sx!ymݕͭ  ^#|*׋&,tnxGW[ Y(t\ʝ:V!tٗWgjX rje $!5A Ybw0=@Q$1E+pqq>Zlr[l_=zEhtz·ܥњYdwbTHE R֝>D\`a?PpT*[wSTkI_M:6lj".g@(:aJC@ K*r{LGcUH4pdt?1D*\y[jWzLpk 6xՇ[iS fR RS({{u47upLy1ҹ3֠ kM_9Bʏ\{*i&W:;蕫!fS2 36Ǝ5O͏Aj-. H"LZ~kx֢ʃֲ\/\BWq2X>[)"Ev\鈟8)0.kgv&%(RO 2kP69N*x K+l7nDIʂx "G4Oh@hoif<byöh+۶Yׄ $aQ%1{0[. B&ճqê]3v^ɽW-y x32TT=l0J`d+!gi'n#]]:tOYoCEz`=uXmoez ?QB}rE9v}ke`!mlOcلUKѱE)<]'#s_m-Q㿬 qny"$3h]$*]D\[nj'c'F{"P qMPޡ*سw;+!F+"iC$4*E|NmF9 ѡ@d5Cϖ籥 ȈKy|KT Amن^ϞCwDo1#FE?@" qq#p[zxuъ]8A].xNQ čWFpRXL*{W?8}ܟfA9xʗl0pvަ{|*Gw(vp]$obR_VU #0]2eabjs'θF_O=]uxFAg4.-ߧSnx惆4&?vmQV4˜ *2ggx]*zS+SW,wj'>IfFԝ69*DfMH >GD[~FPnu}.me%^tm~&t|kͅSyWXPqQ.K).Rʯ)cDq5q/yӍăM&i#GE^7&k+f#3=/g_J~-Ɋ!ܗ@/_Tsq {iJu3"l+8ӶlR9nC/DRk?s'7?{(PVlߘEi9hjnZ(&:Wi I :ԔG< b,ju"6`iTd<7dwwCyV "vz[jӬo,5mx.?PM5ނF?HT!=yHb|sXT +y4#-k/ضcJ&?u>lq(3U +ң#} f:Jdm[m) :}@ EeC#M<,i2v]oKxX8U\RNs+.|ZCsZy=nAx… Poѵ2|2Fzٞ/l$_$Y&!x/6*$-!JH+nq]{oNTT֔ne7i?C὇^Uq0("}|X48 +@8tξwҾ: ޞh ,W 'QDt-T7 u̙dhǎ xn ]i?v. 'RJgn,.,#4Ѐ VDҮIwm jv+fzȸ̞.#%%tC 2 u9ۖLYߖKnI{^Up %u0UP𞣤Ri4uήcJAwW~WI-q+?+\mL6]&wI<$\OTgPKg}a9a72ׄ࠺d8*xeJtp 7cHCsȑDнfeA#VcX6wo]qv .i=b%Dˠ=74"y)jةpn=,dGg(x#i=]㫎uG%A]CFD:txG> '=\<DHV?59~-R| R]1ZB yrCSdv%MV=1(1cN}oBbd}-͘z6?łV{Cr2 Q2fr@xͮȩ.l 7+uKO@mhomh}wf?3Gn69(fp1)D J#ɳ)̼}!fQYEN!!nļUIXذgkB[WI;Pl8Y(X [(8VHTY Z ͵w2IB[cl,>\ ZHG_£q*\y}*"O珜1p rXܓB%{.Iz0;9:N>Hˌu-ԿLf(MOV9,+Dg4iҭ7x ė bOs)J W~WӇyA#6DC;DUNJEQ"fd'*r9Hsa DZa[q61NLiA"8idÈmWMùQ!es黬pn f0D3w`#[6O͒m V!#H)JB\#kξ'Ӡ1E!&h,!}<6Q<E,/-iY_.wd5C 9R_9n(71ρCcUos8Dձ0^6,?r?vQŠJd# _ -L"-n!8v8bc?+؎ ȸWs+w\팚3aHuZ/]|[Ե ;ƸBI:CpqO촟B>6qW'OO6hk,nO[CmMYmYtpEqjq<o{ C>k:R%|V,*Dn4r*x"? 99tŀQw!qy15;^󉖼8#Q/w*Ml0Fg\|[!+`Z䀱"Ғ)1UZuftՀ:j9EV,7Gĩt ۉ@/o~ ''d8GuҾu 9{\_}fR9eGwD70W mo6t:nANՔ}6a|Q6Vٺz>2lcNf'`o])[(mf7s3a 5bq?ֹ?V2J26)7nHeYH/ [eڎNJm$K /Ѳ991`#JG+%"Ģ;-TU|T!Q`K̓[h #cБXkrm+De<\xe%}^uC ^l MM4ԝXkFh9M:2 Y} yƖP^P맙3LDMSFVidۮsKu.~SFOBAk]BwнKaYp T)^C߂ϛKV)U4G"m-JIűcj#]g=4BHS߯`/k>۶0Q7Cd|qvk,HqƐUrRɧԌ8e{F?gI+Gs aQ2p`$)pVFػؓ$֩bQswdYY+Wgk"݀q\ tY>3qUm6x;Uނ})/B?*aqDࢺSC+,>kP?*|ϡтJ9U5~2|)|vv]~$~, .@ouSQ-=).ԓK믌D %oe86z4i>T!X?nvJ,M['NX9$JI9.xE[62Քt.v Wf6nN VK^꒑!w;:k@]KK h."I{¹'W`0Yc`+E0͒iJSupN)u{R02v~1Wͅ0-SAL s&̶۫Ez" uC~4A>T@]B*a$Й,_f1AOvJؒs7(\G[͞x xk+]JТyTN\*$װmlRƽ~4wIЏ^+s5 PtޙYl C\a y!)16Mqscļ;Vi”#|arl\  Tjl3 %۴ pPejf%95|kKqDO}U3I |(7"n?y!6 OcB ^Æg6x hƷҾIsx`Dmߦ/,B, ~&I0-phUL(p7ϐ =zlQnc f=vU1":{J9~ lBKB 8wŋQ`l$$a\鹊z6R1sVq\YPH-lWg^8 \:AeMQ$ɀ1_;*m $FA-/l1aI'gnF;8Z3KBI<w>.vge<c <|NٖM,b숞 :KuѐƉ͈eډJϼj T|v?&c'N[k soַck!cy}(`l#8!:~wM3{icJxCּTkJ<( ۻ3&CP]-2^ fɻj_Nb}R3JJ -DD&dxʖzp]/6RӃ]q?*zbY L]ϲ_~L:M.nsipy|EVv)78=&Ț$"OH QЏ'%= Ťc?y"紭@Qyv- *ِ!}& "^>C?{>d*PJ$ Ei+ww| 댈v3Ow/ 쏖F $!/(vFߢo |w͹Ir#tZ_.MvEv[k^z,u(Cٲ6_GY :-p> ?'OdͶS@B[0shնG1<#MugtMNQv _T:U8)=Ac VsI5r\h[Tzrj6/|DSyiꖕ-uȲͤFr+:5[7'-cBt@~XC0,=7z}@_W-'.F*n..ws!iO >x?lAS_ERx.M%`<4`tWKQSfCl4J)eŷ l7BnU2;]@e̙ ̀x`z$ץݟ$+ϵ7iRpQ邃/F{9)̙3&x4A*e2!L0ĺ&OŠBCòNFW|Tm" ˜_Mǘx`6Rz)boI֣w0Xn舥qtugXCu:68{k7ca*d+SF+~ĆQeKɈ')E~oE \S)-|h#4^῞B{G6bnV]3TEĎx*w9IF[?O4:e Ə)]jO BDZ^SIT吢'/''ii fi.% "܁Tdܡɧ"8P-*Wh|dI€PN8dr!.?\%^yn%E lGKUR{MW{jxtԑ- !pl̹۸)IJI{p]z$/,ZwuWc̩ɱS [RB3|\Mėy0}~1~m7 s2~(VÞ'Y'lR>k%ڑp3"M ի yn`x:9]d׭)C" ӁEm*M㹇 3Yw^{k{֏A\{ya}yDEp3{t*lh2(⭓KqɅɝfMU3ǣj%=./LI Qx"ͧ]QdPġCF)6TkC%{(Q#"M>8\r%)+CXI7'-9Me|izxdK! }R]) ߳>-c|lۺ**Dlik_}an5^x:G~woB/_x>mYj~K|R~DpEF/(j d@,ȫ KuߠFRN#3rL [d{Oၚ.k+V5Ōq⽍71|KZP,ֿSoBMpdRgswؼk bb>B04 hQ+wBQl6rl]_.Ph%/Nɡ&zU1Ljh980+h}N3>Q$3V%/u y@jY*ppZtPht9T;ɇ#~j޳ZYڰA4r|\HL9V0Ꞟ,*3 ]%Ӏļڋ%q d14_YnwG*|OyN6(rhӴA ][} ,_aSdz~O#B)n1'!+s'sva2-K{8D$mk*MiBzQgWGkw)޴k4oK[/msALwjlP$5&SSn׹:T: ڞI^*)|5֨^~J aaSBk,=ZeD $ Y3=PgL73q[MJDy;Ū_%|SM~I -C ظzٻ6w.uߣf X /IlUhJN8k<#h'qӰ~1nRm*6CgnJU!.ENJS \mfu/R '^Ƣ(Z<9P 1a?K=.dcvXEX"PjfY,| y|Wr'ĭg9o1"ߙ;qV1!=]x׽-w4۷\ʽIl8UbDJbqnf~\7B~ꠇ^Yg]UE횥fn)U2=NOE1`+:c48{_;y''(@ jnXamӞGRrC*㮦 g48(O/1)dMg4{}ϓSFT-To5gᅾs|T搊ffج~ '#;7`C%O&L#%tQh {zMW-sVdgynW+_v(&Lw!mo醹t{/p)p-{xƍ:=K{9IvyB2m9O45uyʾ")5}nw("B5HӴrSo*xhS+ɳ߿&?)CX.iy䀊8r9 %=`b+F)c|o3|LP<~)zeTWhmɒ rIˆ옵|2BuB8v~WώXmo{ͻZ=9vo+~,hfMMƁwsD'1X]bvcnŵЏFr4~j-Ƹ*5o7 OP^mkۍVG5 љ8+iԧYTXO4O'5B55 !tQGJIb\6Z'! هj<6k kUM QӖza :iV]{DC$'Dql_.Np H6_ΛW$MPH}'ZlnqlⓨF_$e/LDd`a|\:W%+plVVKXanJ) g8[BxLY|5dxC )}$s_HTPYisXcT ,St+be@7"n)lpi {VwjމxܚFFߪ¶QkFy)e/0ȟSRTOAvZK|Z Lľˈ]#FlOPґ+V5N8"6ӅBq1F9T#38Gs耖7Mf?}mC'ØD Am-+qP8ߍƍigQ)}c9d`wćoftɰ2hޫch!3ͦ~OД'?>&YTԽ@鳈";>W5R7%!lƷխ,#Je0ۨF vLc*L< Af kȳi kbN["2icG%t؊vCjC4o!NccZ>1bӣT`EY~: ko]\7E@Mm*3@dS+NIٍ#U},Fjx%lkob;Y~ށf5r` Ɨ;٭mQfqN{Hsy) Rc8^MkC> ғ`Koe>T\|8mhúر.XiSݱh Њv~]Tϑ7 !0 re҄V Ba2wJKqjp-@~)7~njn  n-p} Uv}[b*vd'Lv97nhk Mh!4`wLhf'pKDDϩug)ڹ0zGx05yyJ2XɄΌP6%hkɫt!| N޸Tsen|8&+\sW񨼜nA"q6$&H;B, ׆F"<5 6%=CEIݦM٧>8i>##j 3'|6]ܺ|$";͊wvEXv$p"†ǔ3aPk; ; |fX"-a7B %ou# Yж=m d'"hMբ}8w[\՟OψA;8ݤ!H MU&b'rO;u+ f2=Фw~ϴKY^#$4HNz-X%u}L2roeB4GOTA]9oli)CJ,E3ij=j֪:pB1uxumGb^(L Π!O06Sڏf$ݦ3+*8;y[]>+$}׹/8{KK{X7R5^_wiI]"jt(׹UBՂۉ SSkVs@ϖɑ( ɐ=J >S$$rY;Bi wx bˆUa .s`QbA)Q>T` |}5&/5@ ]&2ff:wS4lJV]Sf- ˅~2\.p7Nr[lNT">hoԅyC>d5x.,aURҽ]L'}:@F8m(p{L.K޶*x8hUQ%4bW۔8vgbmRզJ2МL[9r/Iy&2;) sBaBI}oq'<=^ƀl0'WqC7؟GYč$X$#Oa؝{ԭ+iҡ6k TFLV$ P_7XZiiZM5td w`I½lQteJdKf rchښ歖MҖYe]״r=_"J@k ubOVg  5ITo]נ E2D u!A|ZN؜# K&ܷe!>Lvn[tÃ|dbԖEJYUvR-ZډGJٿB_2?߷NýV'h*^V; j4[v=vL!.H.W8h=fq5}JEEjmPZUSt6H.̻Ch8rO>7եf <Fס WK^MudL_?vj"Xu_RʻE9Y]4M9h^;Ż%wo=&ȊhQI&pguX*el0(dߌh%)ɪ J'`wHK)u[ NG:l.׫H.խ#:XQ?$Z'r GWpI8M]QfUT6g`47QBSCf {fQ20H ֞Tv|w23 I݃RphPR}FP~WjP5LL4i|ی˛ dDOu))]ύAb|ґgj#:./'&V'~;7ƇH=) U IDxEJ:cs"16ͷ ٶli+oHlEځIR˛b#NwPlp8S̖]@Ub<q3n©(LW~0McqPrMs[+44X/Bg5Y~. (jVpatn2݌kQ[L֙A9^-;Bb^ Af0aO=@;:Êk ClVnèW>U GN6Bu~ <[IPdb &l'D:!o &AxQVqvt ÑE)Ah^9dq jk5/پTg l-.=tw"p+Ӻr_兲z7n,I4 . zl{2#jJ;VeNB Erj1#BC[L)#PA<bH\:O̍ (ToDw\gR!xJzb~X{Xᢉ5p_ض1 WXݶ1~˥~fg2?|n@̶{eD+&xgzG^5tg(%:uޯBERz@zոӅ.R$:}?*) %8t#vS Ҹ~mԤK⥉~R&k r'~Jmww,%GU.6k{ECoh(g^6.C <$+E>lG)5kGWVD5iR^NO.X^-]7#]Tܛ!cTJJSx!%#2Smci"Xb,{ Ж+M3"| ;m䒵&.0jIUUe2Xm.x+‹[ >^AI'|SBm0q3bzf\{N )IeCդH g>m*b zGXO P,_ h9 -hFuM (\Fٶ.%ӧ[@{d GL|H x$ls?A`MŬPK f^/yV8>yS%@`8cVP?=SԼB&Wrp24(uaڭ%bGPwTÑ+Y&/G cS2t| nin6U#8`=J(.qRWPbɺn:yK@x8%}||=B|L=5 A $]B:>2O`]/]~║c,~;g3Ͻ>"&wnnjT#+X鞬XRdVWɭtS΍B hF0!h)JЅH*@kӷGuΒ=t"Ž>6=#Btz[z>&#D6XS\1 V+*kE+u( y٥QhlŮAqzW[{OP%fI.bX5u1'4nnN!zV)\Xotf Ppq1 5WR'gZ-H ֫D.<ڪeK\i~Qx OĮ E [Z4oO"] [\iF|fѫZpaӍ7{l8EKe nH WaL-pyDj矷=Q)qQ{p{ @h\1>Pʮ/G.|љoG1L{)o>R{5L:[z]̥+hr%חgltJ,~Ƶ N0v{m~psc{MHФNG˒7[ȦTl̨Z-q!.׭iq;ۺ n\--tf:m9@?TF\ыgV։j=`nl.N"JTU2cڨ5Ec.Y#C;FDVh ,3?JX}=FeX'<|讘I!(r`l!s7h $,Sl2 }lUct/_~龥*v%_ 4"$]bls)m0ׯ m%Ny]C$ӵM:PPBIk]Y?]\*9 Q|݌R; {yqM`uWb'4ʺj@#1>?oy|$W48č͜  0r)HK[ٻ6JKNճ(Xd]ˀygB{\c zM)Wt+m 1`.XM#Q&h!b-dТKQLwhSK_"i:౾KKO@{9pizOv/з}$Uk=ذX8#B o6r%1׵ VhZh `ףZ.f5@ϺHaTr xqz=KdDڋO6Dr,v;V5YL:`dK rBVQX\I !(<8)V.~̓f (Q,),xF<٦FIhx Ql?6KbG9쫗g?tdK]]49*3w|@L^ l9'>&]ZEa;Kb,ܝ#mόup_OPVpXpBs-"HoubG|THL] ZJ[q $ܑBLMzyr)|BL:\;*@sHt،UչLTOT`&<܀C-7 m6;Im;ndp*&*B<NF ]/|]٥\cCƘ8<%H6q*"`8!}~?Ȗ,LUTfOZ_sv3Xylϡ}Qn P@גĝ_v9xc% #dJZ]jݱ3(2p̤D knf.:܉Ņz'e\7NpQIw?03>wjh>ɕ!#M Rl֒jƗzb6 ϲzA/RnZ~!yT@*cNVH5?$xƫ^ɷ$k<@JnOX֖?$dA%fCz+[K"N?i-%loebe3*Zh5_m%W}mXޕօx8&䔿`G1pb6XJvBWdSY>@'U=s )00栜w!lWVBe[$R dCeh ŝ,< g>d2U n8=D¤.P=^r12Ax@s)O@B&z+ӮCC/4LZmyNax_^xVNL+&07zI Ѣ"w?#g7]"-ԓ]d FmrIȪ_bޮ@;͟ .٦ ;3jR>R݆CYAB;)JɣZ{c"$AeɲQCoi &c _ך= >nɁP쩸4L!R~#ݵIE+)>o"uuʴ 6O5rU]~Se];%2g / meݪ\[TϛYXehn"F@>qw AMWfocY#G8vY5O1d=[J9& z&/7} Sd6,-ﱈ%ض1smѥYl ~ +b_5K98P]}Z,ÔeE=xVW%RqOn۩BMp:ad |o)&? zr'3RF;TZ>2S6S/ɹ<$Z TD2[:Vn:BÓ CO9d "7(,}tUuǴak>׿\:\wU[}ŊB>?h'8-;NZx;O{\E)08xZ".3C2h06䌊/:^I$2cGI/m,Vv ʪGO%h4Ǽxsn̢yAt]. Ξ+7Sxuq Ͱ1p>4'׻T7Ry %SWnצq@}к䢸]̬kz>џ6b4f{R 7IyCfP@LD-_k9QY C:oT틝7RGbUڷ2t'jiD ad^Kz49}0+m$J+i[D`"UCE%Hi0G@ǎF{>b`1~j|m 7FI֝RodAr"Bw#"Ѥ%X"1M#쫋7܅g`S 6߂ߴls3e3/ju12pޘ[&]YzlFpVXvAFS8hHC g J d 2$k7WbVK2>7)YXw4L8J۹|Q>aIMϵNJ@c~g6f!I]ww/KF9De'bI@`z(٣&*cEO[d_jI|pv'X9{F烛 Q?ޏ6 rt[ar7yCuT0 6xA,J!:j/}Xu=)gEFmIΛ⎇]q`+CAhsGTΥ,_HUJq,O5X>rKn1#FuԮ1݄V*V-@26624, IrdJൠ) y32qڼ%CRc%vj;F\2* Ǫ<\EM &矿+R:߮9}_̘YY*تɌ=Ը0yaSinDr$JRF"$] ~oCU_9^V1_aihd[Uy\y {YvV8j:1\`>Y ajx9+jl4S0#(.7pRnz{<0Zm˴/p#.M5jI{|Jի)mGtP"d/ZٮbNqJXxVd> lDxcf>D&0&C3jS5@b^˚ޭR|c^.W{^Pާu:ѲZw~ L؃2!Vs+Z9|mg-5JcFC*lux@VIRkٝrfmriw=OMr9Ĕ22̮q%RlP}JW|T$NϨ)SS(܆[^֊^pNg4^&tl6Aտ~8q6(%S7dKҭ8ĻDcu$l(iwu3H ظ>nXEw ^14a%6VK,׀0QJapL}I:KmLwCIZ:LxPS#5if@ɇ4^đ'XLcpðM4)ݕ0wKY0kČƟFf7!$W?PO˳9m]YyYrʷUfycCJh Z[Σ`9 비 Mom#IҐ{9R,r1'DqP[ihRHi:$;ti0;o7w EI2> f`dpUKoib9b:N*2.UA (ۨ(β$EGzl^c(XŧA;2&}0$8O8 4dZhT TV6|[+x΋J̫+}WߧtƃY 8L4Lb$MGX&n}.uex)-RfjHY*v,fdCT>Ishgy1hX$9gV=79vn:Om_'"28Z^Fo+=4[dY $MM[qd{iLT~dd-J+S<ˢ2&ػI`4~$1fxvL< բ_eLTs~H>P@M3J럱*q['>7뾸/ FO1=>krߕ5x 2E8|D1Wr@WX ~"Y?v)zOR{lؑ͞/Weوh&CW扨Uwnj=ڜDOjS6Buduk!0}"?tȢs~uch-3B^QbY5j)5*vVk;ޮ'bm_ Dq;gӼ;'uirp.ٸW>K|G#NM*e?Lg2=aD#"+S"mvnEڃPN統,lVЍ\TXz h|E1em .Q/#Ѹ9zJ)k%p#*8>tc~>8eA[فZn 8SNkɖkrD(}"(&Ь"Xgv.e_{Rajw4L{V ; gH2(f `Pב_˦.r`J5G\-&Ub[!nJ"꜈ 9O^|gsxqPjKUvj*Kj`s?駦9r^A3@\ֱP2V"sM J֪"yJ"io 5/LMεƙ.* TASct:s0779/0sD5$}xdđh@'UvPFEm IWsV$W5`bj1)hCM﨏WhB1n,P{ k$NWqNIe80w"LTjgtmps>4ͭ P.; 84H~N* HA u*0afCh}'fCOBX 3I JKºjRbU(v?C_T xu=3( bs;*a D槨GrيtvEPZ34:sB2+8 8,E8X#f`YEo5OwޞMyŤj cK{F uDT8qq)e&-#rfPĶ fSV! jRk}x;bB2f;ē'":y-YlQxVoz@Z(Be ^m JBb +HI=NP X[C]5j*A|Ii4X J<+o^k `dxZxE'"v#.̷2st p|R%, {hѫ3o~%DE/Wfg;b:Vl\߂jŤD,dI+v/[JŶ5`,g]<} wS <'udZ2K<5u*>h3"}c;;W{6Hqh86:$P3`DiYDro|ef3*`dr[L37 v]ytm_͍N *tp?d44 j/Qg `7q;a";6z!~ѫ<})cɶЈ _8H|9McoCxq"kg`h[pHW66+΁NN(GWK­)¾ljti F{װ6^qZɴ~}3KQFނjmBڜL~G*7Q,l`RF<I9OzJ U˅Cݟv_XBF@Qdj ~ ԦB.!4C Z9m#ڐ3w\偁zP`,P"v"nQs`ӜHqzcaf(*č< w[v#QH!h/yH߸KCMtGRMrҬ[c.zZ1㲘!VM\D)4:F\ u8%2}3͚7 il5W K39GVִCR+<<0JrUJ#!Ƴl\Z<\Gb 艹!3k[߅U]cэ;n}7Xx~a۞8jmwi$D9az]mƓH0UYsM|t@/v )k9Axq`pQ' * yO߼B*ZIVVRR-FQRk}o^W0fO}r..J-@2q]1yWu#ӭxt U`t}i@PpEQU!gF#QnN~ OW&KB=XL5$Dޞvpm&ϭ>bm,(~i:e3!`m;U/F.8ʿ&\è*c懀ULi^N뱤*QqHwjz?b'+#5>ڙmq?A,p~Uey4NbYc%]s8[ JZYB/]=b +3#FS%Y2EfG5M Hȋ% KI+3bʞV<1`6dJ{ NLv7[1Ў('1Os+Q> _yDR愦sʇ(J^ڋ+oc$&/jz0ؒTC @z>2A!c?(]8RW#_v |Bjt7 *AshFe~E^L'W_^ԭa22c[~Z6ө(j1ymy1/ YC섻gX  :)#((;zļG\9kN.uIgV)똩hߵ1*uB!QY~MTcCI#޳xXiݨqYCKNiq32<FӽMIؿ5Z޿1XAF W:q|0YrccdƠmP)/ܒioCYg\a'Lmf. or55+# n_ Z?Udyy1,|%Abh ꤠ~U[Ee|\ox".[j+FA N:cYe%{0g}zo^s˯?稡@Ax\C l!r~|1b'U7y/ '4/5S|wٚ~,_}|, e= 4ᓊ`,gsIyƫR϶`͝ eƍ0;;Dо˧r͹4Gnc<sȯB)Ws)jwHqXUOvV>[QN' j,(d⃛;#]ɦA-@j:-¹t0XipA2,HX/.ܛ?C)BG5dKb/eXBPjajɈe1&&J#>jC5X0a5!!zK~WqGY)Z# Mж#+"=OccDR֨ f  eFz3 lK.on@ >-S's kAG8C8\"+2HV UYBj}Fpn> xa!؟c5T  ?R>‚!,3JSBWʜxNt&Wy٭xOCOtƠ^ns{+)U? ^f[$]Y, 4Ip:@w9F.i" vM=Qŷ \,M!uS%lȽ7+V;7x;zcbmpPct`m=QV wax2[(NoWq{CQ"`t/DER~TlƐ<'c n}}J.x%axQD8RhUa7w(f> =NsmM1UvBp߶J H[5ܐԔ]72>hܩ+t%:Og0MTJU"N^N"\w#aeᱡna^S[IAթ%UV>Pڭs@4TYhVe Cco4VNEZT y+R MO$  m}WTU)b, z{14){"4ΠE,%`xܮ1_2[ЯݾB{;'ZML-yu{4tGgJղqK !//+tjy =i4H8RoP6 -2 2n8hhT h7+")fOL)N62߮,-S}[Y B#cՖD,CR wfru/?LXOObqf:[k) [A7}\mh|2Z;[ҩĝ Û_ȡe"cδFRjqNrdO@1H3(;*^DmMd.ms?J ǠYN62m\n}ͨ6 :/ wӥmeC?~raA _@M`L6 9G8,s?deάV :`-fd͗1) b8#fn` ֲzC𨬍M09_R)D"HJs lMaG?isF6̏.yN'c Jےsǃ8S2mqCb2Z04@U SJuǪ4]Gk !'ݨI. ,*lǒɍQ[9*w'"L ^A*<3VTL1!j+'Q//0׍+u-] k S7x~ <.-6^=^ nU^x>:_@5HF!ӋRZc0TI٨~X U! i皾řUhWJ< sc`(FU>{x`%,4hx-{yд[vn`o"$^ {7DFٍII~am&ˆX#I! ^/W {i.oN5_68[:WICI1늢bJ]ӁңP=V@v*wv6N.r_lPX@NgnfEB: ps*τć-e:HVHF Y鹏Cn>#5FR*#x-$osbnsg))[7襸~}lnhRRDB9[*UeG5M2y]\'DMAvVKRw+;2ӂsF庲t{\1BO;oESf")u<I4:"  * m|Z '[l{QlW%}e'EI o$gsUzqi"ykP`B"Q T(-'2q{ ]˹ )oĬx[''3 \E)/,;׶\W$ȇPY6YuռuQ߶D+"EIWJ̝)I۬ךUvETko"ɜГpE|a^Qeh܃ɞ>{ cG|G0No38W'W2o&am:ZQy3JfÐC7wrDXp@L*hWK @LzR?9r ʟ}WN:ĔWv|T/E{[eSYb孞q`-}>tF#HLL'ka1ꇱ; ]ie6ڧ^iJ LuS`#hmҸNV!nV,8 Y/kN [|rnڗJB۽l䔬u<P *g7I5yLu|g굩쟳XHD [. QOT]pSJ;p1Brӥ~wG wb'qI݂ "ʾFG#MVB%"*: ^jčG9’m5ۦA1}et,~hDF@YN* <Ժ8*(8ˠ /K$iejᯢy˅#cgmNL׉Ba N|sH*G}J2 xکuf/W|T1}Qވҍz/FG})w~^5'pwO[y/[y"k aXܬXZ4[3)2͜gr>Q.I~8>#MKs7Rs}-ػY-Q겕k~53GbvrA1;%_D4Y ɻ(7̍iQuLX%˦'}'PV)Bj8>RcIZ]RocVqV홉@Ɩ&Y7?z!зr'䔩?+PTմ=:WybP|ա}@R(UmO*pXd8ٴ˖9/~=YSFdW1ʆG#ەȃ#>s.W?衙M_{4qr6s%J$<~pj(fgN}͂$,rp \uބÒ$t8ɘAm<= q(=d#UK d?g.P,C4D!UfQ[}Hm{e` ‚)_ޔ22'? Ð{!pʰW.kClgVDAᄲ@EzS`!s-f,OͮF 4o;nap*$>:!>@|Ie4uBP[sOe,EttG~r?m1z4h&'n^:GGBWYZ,uVd޼jFи/VAUa7u } ]B#blYDPd n][4WJ|@TaL$) I 3 D`c]<ʵBG59fd]r,y/bǑh2fe`se*OAPpvf&,R27uB`6zWAFa5mM-\DO){< /8-fɮ̧yc $'S\"23A~>QK, 6*7`G/FfO.gѯe8;yQ m p*6q\q,mXa\mqt vk* E(e逜8P1VPg>(;2rգҝ=,6x1 hZ[`&|pdGZg *MbD'o3cb&ڈQ.DzIn=bΒȝ w鹫Rpw<[lDr*ZZqU~KFCN!ElGwOqRvt_ȧ% E<͇}#([ FAS͹ˌg&@ٺ<_hSe=mq>Ql`<]5QD@A>$n`vN&0~24&rjDHD.yU|)@5mF#TȖ9Ud.ܐ摝-3 Ӱkɡp6 8Gn- 3e*sf#ʊ*B;k;Tƕ#J)c:ϥFkd7&h@ re;d\ 4ٙ{CA+FrG/=_kj6LW 5Vܽhfɤ\N飒 5|Ң zX8̃ <{hx1YGf̓h Sd9N+ok2 Izy7?<`{^J+WI /* O?!Vݡ6=d5z }?YyL;`D>_@1xDu7xoĿY:qv#tU}+ig }gÍ!iDUr,)l& k SuW +ۋ'`Y)D7]np%&Ofv$o./h92qCsr& 蒷4o>GuR@_G7̹^JU#gw yOR%(347@-Z#m1%#@voC2Q퇂l[QϬ1޳8"}2 w )F,#0=<-9Nqԩ@ s-}8@ t~Q,O:GR߬%ixu3\!t~3IC;q9e6ĴQoPSR*|SW.bzؗ"bx逽bPi%t^9Hf#,!VFs5=%mlH:]1n6'(hitϿhT+"W."g﨔)?髑ZUw܀AwNmp}QI@)RlHL1Q6ZEI\=6`+ ij" c ÷Z5=y~Oh\~bQTҋ_+fBthU޷3zoD쾍н?婯'F5O*:˦ ܫMa Z5ʥ* F07W,f1 4P&?X@)q )KԿZtO"ص \* d e)|LF;e?vG8z(A;{L͒Z [rRݗ)Qb;Mȧ)'$p฻2Z!Fk#WPZePfc?tx͸M697\FD@ȕKhf4 {O`zgTƧ!dZ)Hj>gt?V~Ef*'v 7APس2Dod@=]JE;ٖ7|Y^7-/ U6q͡6/nsCVwClc9uaV.y gk1e-w#id`TF`ȔT'I(I/N9pϢ ӛBZܵOk,Iz1DdEl50S [|\3^121fL>RsŠ0Lp8'BtSY@08a^9+icOf]|>Ƀ=zZ U=Ɂ%`ke>k*iCpu`VY8. R29鈷?]ϟa=>)U{rF1,Nf{DzX K@%ɝ&%q23kYmz =5:ri/䅝 ÆZq`_iWm`8&%V'J\1iaiGK5Neo!TJFv8^N;W_ a.3=*PHv ۵lE @ [fyc''Y-ǭ=pӝ.M6+53IV]ĴR(A f_Zc80#Xql@Jޚsz rfO !@69[y,uJkQds0/kX2'` k=/t!}m$`TQ8|xbcl?t +d) |@0>1}\JҰFl,l KM]tߕɸf^y kjߘO%B-*̕g3mWzM|Қq=W1ƙĿi ߷}oݹQm[sd_LH|P}qdߑv{VfxwiD;Y-w2%5p[e1B]%7|mgp @S# n6.,Q٪4\Un|%0\?;JoV F-w5Żh> ۵[It @浾ռ=&2::Lؤެ  @qBRw%˰堁9]k䟗IQ]sN\d$&>cY7">O |9+ʵIoPh; {У&l) %QƉ|Fl$g xɅ)$3k3W-cr:{5 jHC% 2D / #tdoԳ9 i-'HFEĬ"M@lz!vDEHc{pr,MNjX:hE]IA z,|^u!uH%2#w/7  lUZuGrlj&)e_̲gjhN xSGR|QiV$!' 6h6- e##Xazϛ-N y=x]W2Jh:Dq9Kg'g-ÁoN|Ap Yb޵*rjK?mx]/ė",Zs'IVPR+Ufo'y6_Wv@}N]X߿ ty]=j NMM#|^-`qzI2gR#*|P;br=Xtзkٺ0 _H> v7i(3!Av$!C7LPd}ɐjCmGEikW6qdqF%ʍx뛳af#:}S 4{NYj!8% l. fEn ?VYx|ШLJ79n"ʁ?uKW -%n9 1GyWص[yy%j3_(xBR&M"-! ` j^}x//QR!E#.BH@Qe} ؤ8]ZFQWZڹa#ѶSC;lL\sz3M`(Khw7zF4$ vɒS!&ΪJ;]\9auUܓCv2uSO5sqF')Π `0d6XӍto1mNkeeUi|E -:sِTSQ@$oyj[7"[mT`|,Sz@]RiuG  B?+eyYof]1ܫH5w p^`c5@ԧ H;/C$m4~ZR`ɉ0ύmOKI ;f"WOOTLqf~HG*s?%DQ "9# 7#"AqMpu;MM~#k_^!otb!q7iS:¿ik7?"Y՞j bQ*wI%&}e7LdZz_& duҨ7kKUX N>):Ȅ9EDthRvϞF+9=4%aTg1?AֻNN2&Odr{yPbAͪɡZ?O'5F8SzAY o_'ʋ\ge&eLU#Msլ2  x/VP.2Xk:H66( Nq[-T&7Xzz'X,WNDPt;~;"Cp1>tCHi$~{|ZM$^D[ y97r g劌{ZREED=v33Ā~7ۄLhJ,#'!FK?.5 /Yl@dQh(dkȞ1Dz]o6SrVR ZNj.q^GU v ڞ؋sU&ؔmXgv =Dza:g=D3J֑C0Ff,˰s>R6]dymH}pE-ȡijSWcT凰8PZ׿(B8cעh.~5n^QscpCWaϗ%&8%װ{g "u Y}I?Y¸$7v̖\,VO>ErZ&ե]4]k@NsG_`=cZ;D $N(mJǦXR') U(ޟWu,h5n5LDx_\Oɿ 85xt7_Ѧ#pNwqOroh/ς cZd:|@B~+fPBJMSF"6{jH h|B$/߬f1Qf`{u7@}]sAI1`ݍi#>G(,$~KcUԆt@gf>d.*'!$ʮaI!!S"้GE҅e8lɌG@oS· Q%,uّHF=oD _'עN2):y85ω @t>fgjwp+@0oz-`ÀY .xC?ԪS#!Vn6M*o~ZY 7^{/qn> -yNGm=ۯ)6<%R?PxÍJp3 *ąT%.qM6bBH3M@Kh3=< a2hCHНׄޘBsH= MF?p K=@@ >v, b ?=kN< hm^]=Qup {#{'cR08+/ ]7"Ed%as ,7S9C  x33W9 wp43 /EL꯸I y%Ja廲 1~aPoSB<艼Q;pښ$~}# >H08'x:s_7͒!a1rQ>BTݝ:S GHw8NY0ŒZG[䛙Rا^Xsnel۸^-xЛQO Ƨn|xI7j>dѧیCjOA4Dlnq ()E3՝ϖؐG=<}bӂ$@^CZ0}@IY?y|+}80~A4q]0|R#ӌA5%\蟓!yH_2L.{ǨLLO_%1tST^[8r-~6lwa`ML0{՟s^Y~f` ⨔X&ÿ斩Lt S5b$AD5/O|@0N L oAhh0Gu ZBN|C^$]O ~A9Wij@ 'Ȫ8vsh$+r99G{CGRt?/تG V J.i=:| )bc.,? ](ʁKJƴ)7ĬP ynqʷzN6lt/i(9EЍ$1d^6F&>MBtPoZzMm:CFu%cϱ*)7o #Y0P0M4 c9Cb 7rvsjH@F A6 q w1"dnYBqHIqji0c2.rŘ)2Q>3]]rӂ-Ǻ"qaw7zZMYe3# 7wZmQÓ qпbve4?UUuuYMxB:ej'JSg/$tjϥsibԪkdp7s"B|NW&㿢\ gdz?>݃ThqHV׈H8=;ZZ;JWg_W&'.WDM0ЖU @Я]<'7%t`*'HyFfSGH#0W۫K2g;mE;H 0d٘LjS;Vabր)D K `M@ށ7YRa"$>rdch%J:h Gb&sH$)1=4d#ztdêՓ~P#3#j+/_^Q%mb 07x(S $B` ^gʻMNSO_E-uħ5NWWk (%F}7+TMD_`^#e̴J#Y)"E-7YyX.Sҭ76c-[oΩZAd N~\ʴVUʣ P7tD x6~/n1RR4x9La.EB:^p᾵m%,xmr7\Hc\@qRݩ%5%:_d3m!%½%Е>=pF#!~axRRk ygVdǦKaV49U#0Q'͕(gS dݐ]miҮ)Y$fx {EZr.p3{;/aw'UvOzO3%kƈb3 {/wz$xvPO98sǤZ!`nj AՒ"^dcR#W,%gf?Gi0"?~@D ā;;|kn^E.]GJnV{Ò@U~Z :RMbϐfvUddJ~Or#`8\qSAPRGl` $<.!Qoazu!r4}`͍yL 6xOY'9acK?N6D2 އ͙PK\1Q3([.>HBTM OkW%MR~^ڄ;I35[vА3K{U+<!W#!~FJIpNɉ\=~ȫmPJ힦jϲ\v"Y[P_cE}qhflqΔOYZxK@ĥDL+ ^aQƋ?1w$Bp:jđKWę>sˏGt9o fm}Խfؗ:KmX ޾i.yOEuQ^۪X#595VCO>ۋxhmFZ@s3sځ7̊u+* f_roZ[ԖGH8 Οk &]H>ȔVIFBBW 60NJ:Ui5~rOm}#pg*>N J ;.O+'Uov6#RD"+u,DPv7rX(֕aɳ*' *|- A7t d>%˃@!p?;xBt6<'dDQZBo `k>PöP9E>;Vp$LHrH0SZl)v;!ڳiog pfCdnhb03h+|);+FZqT+>B= ˠ 3۹(?Q~jF\ŧ(6*#*]*2e"eQ4OƙULCA=A.JZ-As"V<0FXknx@sOpe+OE2PV}[XdHUٳωnSyL<-TmVx$U" mS:OaUu51 w!tX< dŎ V;n<}#O)aSL.՘ICcƿ5EAJ(549fA)x[0H Py=I㌞%*v}-w^>6,S4oeIG1dEÈŚ%rY2h?+GOe6a$3rb#Vy@hf6옛&변#veIVd. ` r]<[)=X'rG1S26:#V8 FY~@J.A{O{RJ )3p"^ G6^3|`K Awʪ'zaE?j8uz-5A.9Mx54G'tsKU}ggUѥzھeHf(@ !U1itR-:cGjOǡ0ƱF@ޤDHr벇pm`At`B X?9Q+.J2w/$m#^k@J1 #.y?1n}EʼCB)߱> ~! 1PdrgCR1GME )McӰ匐l~腵IdV؝)Ty\<˫n{9!$^ XF"B?#Lѕ0MT$]ACuդykեjat3svĆ |,3%Wȓ,J}lVA n!# ْY\2S`.`n7;A}u%)WJ/d派KºNhOx#rPfJݤD k&Z!+ho:6;% CÈr!]ep+P mLo-%:yU1xބ`%6Kƅ`e=Tz$b˘rK? MҾχ$~Tm'6)gNYmޓdؘ%Y"?|KN+tvB86ҔB?Bis ^֐t ;54Wn3EZvxTJNCcI /'%EH^c3:V]9a =}cr&OQJu3xI)r*46I]1e(yyQuWBIJvƜm_+up \1S=b(AAZ }u+jiMznLLBx >@=%kqJOe ܄/ {0{~W$/oops4sch !JtvFY7c"2O;¶w#@R.3HɣP©v[LzOkQӔH]q*$d ) j?Ms Nv c o<[?=~ %W% _V_ pN䄘jJvzSi`um1uīŽXķ0"a4ePAQ,cؽwx%V^]f_*Kĭ;qs5x8?&<4˃njGbbDe2jgl֨@N_It:ձhsG暑+>k,us\&,`QBbezG#M&=rf&)Z4j€G/I\Kkϻ7VU-%Z̍ -Ύ2a$' J\>zgYo/K@ޚcڈ,2ṘeUeڌB]=Vdb75Լ2 ]1~p]Vio/1d+_+Ƈc6!U vHm:sxYd2~U. 6b]ӽ_88.YZi)S{T;g۴pX߳K{r=|\ɅYі]oNVR$3E(٬ WiQ҅0EQ> i$ʼs2bӡ1v4j1pA" Y/m4pZq3>P^.7?~4&95TuXte($wPKNe@sAX*bM6nҍ5<l('g \RТs~j SiATێ9@"S2Wipbzж<\Mt&ظ17*+8nNe2w^zX0tb䍽e>my>k=>r@A.쒹xJe͑36FlFDA㏟|_r O&wu}Nݺ~LJQĿ )TizxpK,R,pFaiRۑoZëmjI߻F5#n~Q|)d7qJio4J}@@Saƫx׈"IqYB ߾xJF"k5K/tbhqBa=pP')_3%{=-~n\6gSp*`ؠSuH-K╔wM3ב2W%_J]bTTQ6gy@h0ԡ`{?N%낅8Sn g+=rԬGe2ZbDœhԻ ,Np3pK2^ ޱLbn_.]gh$OUSaܞhfE@ɰ;Eb1&@E9a_U 3R pzV(@Ֆ$C\#$)mZ>s8xjtSY'/9!"֜4Mhr!;[O p>%Iƒ73D -o`JvE61~@/Ejp+⼰1["]B'JmVј&3'H|ֳәQ1t|^K hx(垐h*NسIc.H.Qc+M3gԤ/ 5W(Yq_ -Е'EMN MΏe&|ܘ` s0ft1{ߍr``qlYR IZ}V8Kد0qV j3p'(/q_mW<^|72r)_T TVh?t4Z 5]$z3<-gaߖs/)jYťIU4Wf,1ͷwt? s28oHxU|3Gth%z)ztAW,WWȈZ*4Rse_c++ Jnq~J?RֹR5N מưa#Z;U`m &,u/#>0u%uNzmKǻQ*z*Бfi/8 KxOw-(ʶU.xsi1G d%7G=dIQIeײ|o d&(`i $jT|J3U$.LIK(O׊+[kYi,?ɓqpq ڬpX-,]x6Bbĺ0'BsLuT%8ph O|$_tW~X|xfpa`=pKx"܅t4r˪ҠC{ٿp՝m iџϯ2}*/p(`Sn6\ N)!Tw9p"(Ozm7r6K=>蠈FfN4֞&!oF**&EV¼H4m6}]0 :W镼GlBE[*>v H7' ]ypx$iVRZCR,7B=D8s20aR ZךoqԏԼ\ A.'5P^? Wi~'Iv}],R{ >(l:MD G}@&&R%.nEI//]maf@=p){aXͥi +)dӞ@t|: G8(ܢ'S $*d: o1s& u2L Stp"bQvïDL֟iׅ8hp6eQ3ӌǫ0sMʳ"Tr'_'q1Oq&9`yd@Lg?K&"^+5 ЪTрEmbШG=Ĥk jEa|31-ez)Y]ݚ-M1^qiFr~'=N1/1XQ?\|Z<6)ew(sfFc)Mw`9'ɴHE qI9$[XG\-= }PwhFLI ݃VC S0gPdjI EC|XP#øD /(\J;X~ (1ї5f9 : ghɽCbtkZU=K$zbi]k=C7aBi"JĞNZ_N)nҼoC[Y*ooPWbLQ38YeM G=O t5_ʄgYg0i^f_\#7Ϋ7S{i0;8LzH/>cAuJt$E)y*+@G쯐کL$? 8!W_rΰmM@&Uj>vw t(҅wtDHܽ%>:,J d^& !E5 JAI d+^׭kQAw}./khUT>)P,Uaā _s X27"̤o&ҮZ:BT:cIB׮mf|l{S*AH ?4PM?sx_QfSC5$kW}KO6L|GN2[g sp ,6+OȦ00aĹk^Ae8`+%L2v:ޤ^ #`nUaX w8W>E;4?[4%*`&R݃!q..|?o)ү)}\$c~pJ*.V@Z*\ђ~_K y}XJc%%+Cܪ n5J Vk-n꾕A źS2-Rh7i_/ɂît2lT [lAc֡&!m2ʗ pUѩss52KƊ=o;8+# Rή0[ϔ6w;^u"Ѥ9ŋtl5D,vtwZiq!elQRj7ʍ?YKX {]cU^yI榫q9h$'i {i31`=B=Tf{s6@;%VÊ^S~f8\wxVotAǃe+b~Kn)eIs_Ԯ))Pm6i,V[K5|ﳀ}6H`u_eIpP3H 5y\8dÕO-6%|9@ +;/%J@M-VJeWxyz> n!m f)\91p2 "ѴV7Q˛^)1o}ӎK:]ܨ|Nc%X_)GU25j1.DD,#P]C GJCHk86)1RIipohh~;8u$=R9c 'MTn/ŝrGk_r'Nξ]A.~64=G8vd0qDac͜(OPu&/1| ΣXaw(6bh^'ADp6cZ~44m4޾eF$wDMg1ZN4 l9WC̬W-RWW4t qjP1"U\;Yt€2ei-~4ʿFW5DN bu1vcPXģ]pZ'V_DZ @#Xbxw>'u2DH,_ jL+4ɉ`EڧLtmǟW@*ea$88 ١A֜VEVRge2]dZ3l_h ^QHO/|ҥ[N'쁦SR3C/foI&ra$ZP_zIả` t=fU}]ʙeu;uhIwڂNP>:A$*,9shŅ '.[2ڃdUk_gM;c&^u-NϽzd5KĽ}vs'i>t?XAaزB%96&e tõLTNE]n\ $K'7>ҝBĞiO̲*\} 0CZpL..{b$w"VV#Ahp^ \;.\*K3!/Hb"I^+R4"`j`'i f 2YvkWh"-3t/@@,{,##h +MS%)`oњYJ=}aͶ~9-$Ce/g_anߝJkXPrdpޅm=HLM#5Od$UtD=1̧77ݘ/$[m[;{Z$.祸2Gv⪃ e9:;Dʹpd36G5) עZ;Ca`^S!CZZ/{qrmķM%ٶ/p26PDѻGl6}glWPJGoή.%wZ{^п;uDK,_tc˵<$+ܘf^dLFly\=&(KN-Usic`MO##pQ>#CA. /8{nf+;Eu:n?9 ]CrnHedfx}7Ŕ:B@}$aQ+@H1;ѧ<{G^Jٶd vV Dmzl(OU.a OeWz!#HćfR1j}%Em%:@L֐aocDN$2ٽlW}xdfD PG58^%]6e<^Ib͕49umDC^PW<F2VN\4FKrúl~9^U4?OC*3}h$#Dрc˶@ >sitd ~e{4 @sl2)12c5*z@޿gN0w 4Tu!-_cs;LCe_z'R<]Mm 9]$-Ct tՍi5Qr#NjUE(3vhޢd[P-6GtyeRe4>6mZZfOh`WPi Ṩ|}i> d}Pڒ.q:;.=RJd= 24 A2BFO h޲UV5 6eLA&&=V_ry=aqX:Cw̩ n "9fpS*2 Q0W*cɏ-&Kj>pq +~Bf>5-|e|sg\)eUT٤k4#zN'vLee_2`Vwp_6Kb[V:V͑$:k7h~OFnģS qG[v<$*Y vpT(m[S dB2LR`:%uLV?Z*#=n1{ :nʶ DQY0=l!RUx/JFT'Or9|=eDzZK>n9dYNA&M$4|L[_0K\w>r8?խ}xjS꩝U_5b!$xa51+޹z?cMO%=6KxH$.)LÊ1^x{6b=Ly~m0A9 @liD3HUL7v\7֏TƯ1"rx2\l-4cd{=}sh-pDK A>E"+9 RJh{x&u}6.ϫՀѦ̐Đ4f1$/'Y%_Y#*"I1`$Fn :`'U1oM|XK{`l zD'[yU\ yu 5oI-`3uqkxA ;|.þzMŁW]C\KGӺBf§\]L0wql\qxQ9T,O:aOw\v|Y({J1뵐N2"f>=8oC1Bv$T}V<͇Ex&8z#&FĊa\mJήGw%P&| [g!D ټi_lDEjK;%P| +O#df~,yj_cOn,JY^NlB[0n,)OP>lQ}zٍn~CV4g>?\ͭ=}I_L!& ێ߰>.*A7a ( \47
*;*1O1`{u&DAaS۱%wJPZ5Oqš& tv- ]nZo-ux:/A&}8eZ17X!Dťgl3MӃn>!Ne]~/p Ӣ[n. dSN'7%0;u4E=;ЋeBœх̳FEXWTyK"y)vB&z+@0)JdVJ-;D?0o1`'}G>vR`͜P ?yRAj=Y?}j F1[/@gՠG-#!ĕwِTu:e۟͜xI S to bE+b=We6jjk_M BalTmV * ֛xa?5H <}~k6:5경/M/MXژ/"YQ  5#+5Rfhl(l~:UUA!6d xR ӨBG8Q$ט-WdeK4k`02" 1jS~?MƩ7P$JWSzV \ xy [tm6`\m5[1&cm?'ک^Qǯb:S>R6 3S{˫4Q{Xn4FSG/f)KQAZGWmeٵw49iX$Bxc&j?/p׾; -dZ =!mI{aB.ؼĞ?2uqsF)BUP*̚k*_9_PοMtĮ{✗t4qM4b!`>3+zf:QWA(zi&u,mFbzlmL6!3Kh&:2gi<كǺ6,X,#%{9WN-b(N 2$+7tC2%uwׄx ū!"2I_¾ank-ԽȤ4 "NLs'!t[!س=rp6R XyҼ4r|HY"&B%gV:4N{68H:Ck ^1ov |L}wB4G ++MjA1tCfsq gPn3;~ޠ[i1eٔ0X% rtef59ުo0M X~bɅ^ܫ< GՕ|!l6$EoiJ&Q_E@3 foSN0ay۷ZW~Iޗ pI~r`RKh@n}Bѣb ! sAHx͚+GpYkx& DgM9,ۈ'kE!!!^ݔ)879@-dE"2XR jኪ=sC]uE d}2+ogCyhNRɟ'vjC-Ird=sk/0!Udo-L5%xqQ1'fS~WMJߏ 5 P0VdzgAr2JےM x{H AG,dF+*;-*yD@_$*Pe/~hO0'9Smٚ#tp)5:}{K"' a)M\;XI/, _w҄v}U-AO:N1XZL3~we*9z ~ŦP]D ?䝧;@\+?e~jr#1a0qQOU_z& vYmDTx(k=B7.L{G_Af2, Kcj/RD&A`yX=aApC(P}gj$W{zE7ZB/H4=%WW7g0}J pф/AտH D/3:)NY*/G?J8|J[*]Pȇ5jQbM'Bkwr*҃`fδg~nJeP&֞_U~&MeOsf!>^+ˣrdBşs6l2I燘qPi,fI/|qpiSzL!c/UdCu  GP6=͵ٺIێ^$ο]_5Pe ojI] dP.ѽgk- .`! 5NkHxfo~QzE?Eö?cg#۹tqO&̳ s3k< bDTq'uNT%@&s֓/) n&0_w w }+ݟ{ 41_!i=U:$!| bXuJ)|쥙'JPF]݇Xpn[Ը1k j~jV i ^fY~S\C=ەӜߤ^BcJ0Ԯ7e}[])"+I˾^'av3~<jۿh?S"={yy݁gsvA!nkI6"y4)amg G⹐D 6eWdH)%p,#]#4riW|iNf+LK׳bz9]}r|G%uBy>ҪR%oDf5'"Tݔe8O;_nD,{EĦ΀g2hSE؈mկ;w60 &qQ}4OFSmqFs I>߫u4 q"f9ɝ]:Rs ;Rr?chZWeayC2w|*^WQ?x:^׸&JQSj2{k47Y5(w$u%#q̓Ҵ$1Qɗir݊ykc8yzw804l W`i4'M<" *av\R$s:\*opNerO"~#:&bLR^ͿW6`3ᷭRbt=nP*-f#,"8+zD7'!yq:¶;"o1HfIDĝ-᪝4%d̳y$hkB+Q]A;(JuIћ=STmu7o p)I4/[F{|'. NY:\C\0i3IͺdíM>D~02F-ɫnopkݓYf}(70G (ܸ3C[8} 0听YhT E녚ܳy^MpywI9b"Ƈs];ǂ]Tg'yGjJ\=[z"8U9Ujܿ+ W[b ѹ!eD]e~QU~u?~{43|ZLXoC~%R,{ :m*|4̘-UVMLF,:Q-> 6tR /hm-iBEPwe7XJSL'?tʽ/k1m(*ϐBT/[ݟha0j6d ]tt}PiVK, DS7"!Q(І~pT7}DC˟ HغbD6d;vci;]B}N-d['\im`}e;,0,).#nyQdZb`DuևָMݲǣ/.Os|(F0r/g=zxquՠMde Φ?RG۹6TH^xVuܗ XMjLy;,hHĐW3,h(2tY=u5擸H_$tVh)dH,Y?6u?S{A2ln7l rg֎ ݡ+v/"'CMhjjVeSH[lj)UŶU|˲;C;{O.CչRƝT";E<2Ʈs a梵8[8Ū}]rnn>G%ՠ8lH}mk>\,jߋ^$ 52u;F,&-[6Gm߅L=G+Нq h#k٦""V(q@_#Sݬ9smj1d/`;2É}Pyxcpp~z 6T\W^P狧=/>SkOZIkɓ0!GWob4"b4Pos=\:}0Mkq5Mt+WpBE?_]#:CzaCڴ^HE#u'֚4?YA~&/2Gt'TbZ=8ָ9o'>Sb2 ̽4NJض4zQYDso4^حz;'ͬcbhvu6{٩Z`Y|NF;AAD<7&-rWL>3.8׌U?3[7ʱ0OD&f}S﫵?"7|kk^M:fY%$ѭ|c2mv$al9˻1i7/O14ٓhղ2bc<*䊗T*+B G:jFVAV=LkKClWѲ!f:'`˷T K6xs9O-9jj+4!ц9NK k/G 4m~*vXR/O[ W=>K*P˭r&ur杖m\D/PD譇 yC[HjַIn]B?cM'PKqT,>9xPN~ 1MB&#k8M|rėsh<[yx8%LBQ^68 W;.M1[!Bcal3UQG"UL 8ѣnyY4TCLA/%1ףpIsw'){#^Oj._D)ZWW pػ1GP&eV֔ y 0 5𺹼.:khײa+x 1*_ rty FR a%zҠ"44sPaDv' ]6)(>dp8I;5cVxW7p gٽ b̀y6Vڙ>K(C`&"q>s~I  6̲X5F׫ے\v0J\_}2e\3'_T}C@g,n 9Z7!;)S }m`4rw CC ^mtr]bJNugmE]ubĎ3i%R5#وbz.K痕ҩ#*]Uf},G'W}Vx6zbS~EMݴvʹ-A9p4TxW%CTc5{ܣT-)Ѥ b"lb7D|ɴHSp%bAwq7лnƬxɟLA`5ٝHAS,ixwNnsEVI\?)۴Z %(ӝGxh&8B[74;17E/ԍȴps14[YR!GXx/\RN؂!"s` "F]5v,hf)\`iȆ uT(n\5}R h b& k;7&{@7 v;;Q94?%qJzk4Bˁ({xFer&x`u;^"j|B!Gbzcg%۹)Ғ;]@&:pqxh"I,âԀ7S:tp}a$rB_#\1aP8F3l6bB<ܛK_R]MmPӢeFmiT2HEk1r}5D{;ǫ2`FⓣLf~vq3G΍5xH38{c֘v$+QsT0GfaB& 5N]FŤBuoJ\tF;Ħ!2,J|k^'2'ˤg%HLMlFV4d+DI|!wֱ`t+v GT0SD@&K8֢)Z͕T{KҜ>dwml>ٛR0309́Cd 2t:1EU)ͼѽhEȜp4\^YB(x9"̅ @g< =iLf AYO8V[]>z|KXӛwEЌ|&mh_1wpN{TY[?^,< z--2ZW|WN^/]&+K> lkI8)2O1$! оr<.dyEQ=c^`S+l6] V<.rsplHo2xMÓ Lt.EAV Zl YKc~y (Ԩ ?qE 9ݗL"zٵoڻ7'bn%b91oedp<{"Zae2 oaoQsk!b 7ದiĴ}YdTZFhJ2E^ج]Bo@\@LgP3*[vĪO;]6mhXu"fᷙBE^(Tn-9`N:گ`iAp-*ήa Wx"EDFJ.J G߿N e<N`Q<'N"Xjh7CWpL<~FlM lbߑ];Aäor}zKb[KHZ̴ ͙#ThP9R7{=xf@ S綽Cyv_L\_ꆡ9w":v8!/AX+ 7sLXCE먏X'lJ؝Iu.nJB^DVv_8xSǬWòxutYX%0㟽HW>~^qGaT'@ zYa9sLKaKj&y3)IOr$t'No" x+7cdrWqӤ\ɱ-8W-_i*V G|&O T;!ؠ^ӢXx~;ejm\*\Tڎ>TC>„Y*8ߜhF"˅f(/ NLBB<0Yk>5-/8|#.58nQG2JtׅC0>Dfÿ7T@F5&~V'v A潆4#Ewhۜa\zdKI ]]+3yKDAorV^XHRQ:N-{|4%tP:b A\4#2-Z8v ?x.~p D+obc>!)Aݏr X9$y*,|v:<@w>?Hvs0!<a=J1ts k)b0&,h \hV}ˈ¤?2*S~_ӂyH u̼_׋u^8VXFFSfSEhR.e^{7x X ȗ_ YZfastnetmon-1.1.3+dfsg/packages/docker/000077500000000000000000000000001313534057500176445ustar00rootroot00000000000000fastnetmon-1.1.3+dfsg/packages/docker/Dockerfile000066400000000000000000000015241313534057500216400ustar00rootroot00000000000000FROM ubuntu MAINTAINER robertoberto RUN apt-get update && apt-get install -y \ bison \ build-essential \ cmake \ flex \ g++ \ gcc \ git \ libboost-all-dev \ libgeoip-dev \ libgpm-dev \ libhiredis-dev \ liblog4cpp5-dev \ libncurses5-dev \ libnuma-dev \ libpcap-dev \ linux-headers-$(uname -r) \ make \ mongodb-dev \ python-pip \ socat \ vim RUN pip install exabgp RUN cd /usr/src; git clone https://github.com/FastVPSEestiOu/fastnetmon.git #COPY exabgp_blackhole.conf /etc/exabgp_blackhole.conf RUN cd /usr/src/fastnetmon/src; mkdir build; cd build; cmake .. -DDISABLE_PF_RING_SUPPORT=ON; make RUN cp /usr/src/fastnetmon/src/fastnetmon.conf /etc/ RUN cp /usr/src/fastnetmon/src/build/fastnetmon /usr/bin/ RUN cp /usr/src/fastnetmon/src/build/fastnetmon_client /usr/bin/ RUN touch /etc/networks_list fastnetmon-1.1.3+dfsg/src/000077500000000000000000000000001313534057500154065ustar00rootroot00000000000000fastnetmon-1.1.3+dfsg/src/.clang-format000066400000000000000000000027271313534057500177710ustar00rootroot00000000000000--- BreakBeforeBraces: Attach AccessModifierOffset: 0 AlignEscapedNewlinesLeft: true AlignTrailingComments: false AllowAllParametersOfDeclarationOnNextLine: false AllowShortFunctionsOnASingleLine: false AllowShortIfStatementsOnASingleLine: true AllowShortLoopsOnASingleLine: false AlwaysBreakBeforeMultilineStrings: false AlwaysBreakTemplateDeclarations: false BinPackParameters: false BreakBeforeBinaryOperators: false BreakBeforeTernaryOperators: false BreakConstructorInitializersBeforeComma: false ColumnLimit: 100 CommentPragmas: '' ConstructorInitializerAllOnOneLineOrOnePerLine: false ConstructorInitializerIndentWidth: 0 ContinuationIndentWidth: 0 Cpp11BracedListStyle: false DerivePointerBinding: false PointerAlignment: Left IndentCaseLabels: false IndentFunctionDeclarationAfterType: false IndentWidth: 4 Language: Cpp MaxEmptyLinesToKeep: 2 NamespaceIndentation: None ObjCSpaceAfterProperty: true ObjCSpaceBeforeProtocolList: true PenaltyBreakBeforeFirstCallParameter: 100 PenaltyBreakComment: 100 PenaltyBreakFirstLessLess: 0 PenaltyBreakString: 100 PenaltyExcessCharacter: 1 PenaltyReturnTypeOnItsOwnLine: 20 SpaceBeforeAssignmentOperators: true SpaceBeforeParens: ControlStatements SpaceInEmptyParentheses: false SpacesBeforeTrailingComments: 1 SpacesInAngles: false SpacesInCStyleCastParentheses: false SpacesInContainerLiterals: false SpacesInParentheses: false Standard: Cpp03 TabWidth: 4 UseTab: Never # Sopported only from clang 3.7 # AlignConsecutiveAssignments: false ... fastnetmon-1.1.3+dfsg/src/CMakeLists.txt000066400000000000000000000613541313534057500201570ustar00rootroot00000000000000cmake_minimum_required (VERSION 2.8) # cmake versions: # Debian 6 - 2.8.2 # Debian 7 - 2.8.9 # CentOS 6 - 2.8.12 # set(ENABLE_GOBGP_SUPPORT "yes") # We should set compiler before project() call if (ENABLE_BUILD_IN_CPP_11_CUSTOM_ENVIRONMENT) # We use custom compiler too set(CMAKE_C_COMPILER "/opt/gcc520/bin/gcc") set(CMAKE_CXX_COMPILER "/opt/gcc520/bin/g++") endif() project(FastNetMon) # Unfortunately, Debian Squeeze haven't support for this feature # It added in 2.8.5 release: http://www.cmake.org/cmake/help/v2.8.5/cmake.html # Get convinient paths for all system folders: http://www.cmake.org/gitweb?p=cmake.git;a=commitdiff;h=a262fe09 # include(GNUInstallDirs) # Enable it and fix all warnigns! # add_definitions ("-Wall") set (FASTNETMON_VERSION_MAJOR 1) set (FASTNETMON_VERSION_MINOR 1) if (ENABLE_GOBGP_SUPPORT) # We could not compile gRPC without C++ 11 set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -std=c++11") set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -std=c++11") endif() # cmake -DENABLE_BUILD_IN_CPP_11_CUSTOM_ENVIRONMENT=ON .. if (ENABLE_BUILD_IN_CPP_11_CUSTOM_ENVIRONMENT) set(BOOST_INCLUDEDIR "/opt/boost_1_58_0") set(BOOST_LIBRARYDIR "/opt/boost_1_58_0/stage/lib/") # It's really nice part of this custom build process :) set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -std=c++11") set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -std=c++11") # Disable warning from Boost when compiling with gcc 5.2 set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -Wno-deprecated-declarations") set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_RPATH};/opt/gcc520/lib64;/opt/boost_1_58_0/stage/lib") endif() # Specify full RPATH for build tree SET(CMAKE_SKIP_BUILD_RPATH FALSE) # Create builds in current folder with install RPATH SET(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE) # /opt/libgobgp_1_0_0/lib should be mentioned here explicitly!!!! We link it in runtime SET(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_RPATH};/opt/libhiredis_0_13/lib;/opt/log4cpp1.1.1/lib;/opt/luajit_2.0.4/lib;/opt/ndpi/lib;/opt/pf_ring_6.0.3/lib;/opt/json-c-0.12/lib;/opt/mongo_c_driver_1_1_9/lib;/opt/libgobgp_1_0_0/lib;/opt/grpc_0_11_1_7a94236d698477636dd06282f12f706cad527029/lib;/opt/protobuf_3.0.0_alpha4/lib") message(STATUS "C++ compilation flags: ${CMAKE_CXX_FLAGS_RELEASE}") set(HIREDIS_CUSTOM_INSTALL_PATH "/opt/libhiredis_0_13") set(LOG4CPP_CUSTOM_INSTALL_PATH "/opt/log4cpp1.1.1") set(JSONC_CUSTOM_INSTALL_PATH "/opt/json-c-0.12") set(PFRING_CUSTOM_INSTALL_PATH "/opt/pf_ring_6.0.3") set(LIBPCAP_CUSTOM_INSTALL_PATH "/opt/libpcap_1.7.4") set(MONGO_C_CUSTOM_INSTALL_PATH "/opt/mongo_c_driver_1_1_9") set(FASTNETMON_PROFILER OFF) set(FASTNETMON_PROFILE_FLAGS "-g -pg") if (NOT CMAKE_BUILD_TYPE) message(STATUS "Setting build type to Release as none was specified.") set(CMAKE_BUILD_TYPE Release) endif() if (FASTNETMON_PROFILER) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${FASTNETMON_PROFILE_FLAGS}") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${FASTNETMON_PROFILE_FLAGS}") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${FASTNETMON_PROFILE_FLAGS}") endif() ### Executables definition # Main tool add_executable(fastnetmon fastnetmon.cpp) # Get last commit hash execute_process(COMMAND git rev-list HEAD COMMAND head -n 1 OUTPUT_VARIABLE GIT_LAST_COMMIT_HASH OUTPUT_STRIP_TRAILING_WHITESPACE) set(FASTNETMON_APPLICATION_VERSION "1.1.3 master git-${GIT_LAST_COMMIT_HASH}") configure_file(fast_platform.h.template "${PROJECT_SOURCE_DIR}/fast_platform.h") # With this flag we can diable PF_RING build via console: cmake .. -DDISABLE_PF_RING_SUPPORT=ON if (NOT DISABLE_PF_RING_SUPPORT) if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") message(STATUS "You are running Linux and we can build PF_RING support") set (ENABLE_PFRING_SUPPORT ON) else() message(WARNING "You are running not an Linux and we can't build PF_RING support") endif() endif() if (ENABLE_PFRING_SUPPORT) # Set path to manually compiled PF_RING set(PFRING_INCLUDE_DIRS "${PFRING_CUSTOM_INSTALL_PATH}/include") find_library(PFRING_LIBRARIES NAMES pfring PATHS "${PFRING_CUSTOM_INSTALL_PATH}/lib" NO_DEFAULT_PATH) if (NOT PFRING_LIBRARIES) message(FATAL_ERROR "Could not find PF_RING") endif() link_directories("${PFRING_CUSTOM_INSTALL_PATH}/lib") add_definitions(-DPF_RING) if (EXISTS "${PFRING_CUSTOM_INSTALL_PATH}/include/pfring_zc.h" OR EXISTS "/usr/local/include/pfring_zc.h") message(STATUS "We found PF_RING ZC headers and will build PF_RING ZC support") # Enable ZC support add_definitions(-DPF_RING_ZC) else() message(WARNING "We can't find PF_RING ZC header pfring_zc.h in folder /opt/pf_ring/include. Will not compile ZC support") endif() include_directories(${PFRING_INCLUDE_DIRS}) message(STATUS "We have enabled PF_RING's hardware filtering option") endif() # Our LPM library add_library(patricia STATIC libpatricia/patricia.c) add_library(fastnetmon_pcap_format STATIC fastnetmon_pcap_format.cpp) # Our tools library add_library(fast_library STATIC fast_library.cpp) # Our ipfix database library add_library(ipfix_rfc STATIC ipfix_rfc.cpp) # Our packet parser add_library(fastnetmon_packet_parser STATIC fastnetmon_packet_parser.c) # -DENABLE_SNABBSWITCH_SUPPORT=ON .. # Please also comment out line: set(ENABLE_LUA_SUPPORT yes) if you want SnabbSwitch support if (ENABLE_SNABBSWITCH_SUPPORT) add_definitions(-DSNABB_SWITCH) add_library(snabbswitch_plugin STATIC snabbswitch_plugin/snabbswitch_collector.cpp) link_directories(/usr/src/snabbswitch/src) target_link_libraries(snabbswitch_plugin snabb) endif() if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") execute_process(COMMAND uname -r OUTPUT_VARIABLE LINUX_KERNEL_VERSION OUTPUT_STRIP_TRAILING_WHITESPACE) # Extract major version number from Linux Kernel Version string(REGEX MATCH "^[0-9]+\\.[0-9]+" KERNEL_VERSION_MATCHES ${LINUX_KERNEL_VERSION}) # For tests # set(KERNEL_VERSION_MATCHES "2.6.32") # We need 3.7 or more recent kernel here because older kernels are buggy # VERSION_GREATER comparator available from cmake 2.6.2 if (${KERNEL_VERSION_MATCHES} VERSION_GREATER "3.7") message(STATUS "You are running Linux with enough recent kernel and we can build AF_PACKET support") set (ENABLE_AFPACKET_SUPPORT ON) else() message(STATUS "You are running old Linux kernel and we can't build AF_PACKET support") endif() endif() # -DENABLE_AFPACKET_SUPPORT=ON .. if (ENABLE_AFPACKET_SUPPORT) add_definitions(-DFASTNETMON_ENABLE_AFPACKET) add_library(afpacket_plugin STATIC afpacket_plugin/afpacket_collector.cpp) endif() # sFLOW plugin add_library(sflow_plugin STATIC sflow_plugin/sflow_collector.cpp) # netflow plugin add_library(netflow_plugin STATIC netflow_plugin/netflow_collector.cpp) target_link_libraries(netflow_plugin ipfix_rfc) set(ENABLE_DPI_SUPPORT YES) if (ENABLE_DPI_SUPPORT) message(STATUS "We will enable nDPI support") add_library(fast_dpi STATIC fast_dpi.cpp) set(NDPI_INCLUDE_DIRS "/opt/ndpi/include/libndpi-1.7.1") find_library(NDPI_LIBRARIES NAMES ndpi PATHS "/opt/ndpi/lib" NO_DEFAULT_PATH) if (NOT NDPI_LIBRARIES) message(FATAL_ERROR "Could not find nDPI library") endif() link_directories("/opt/ndpi/lib") include_directories(${NDPI_INCLUDE_DIRS}) add_definitions(-DENABLE_DPI) target_link_libraries(fast_dpi ${NDPI_LIBRARIES}) endif() # We do not enable it by default, it's testing feature # If you want it please build with: # cmake -DENABLE_LUA_SUPPORT=ON .. set(ENABLE_LUA_SUPPORT yes) if (ENABLE_LUA_SUPPORT) message(STATUS "We will enable LuaJIT support") add_definitions(-DENABLE_LUA_HOOKS) set(LUAJIT_CUSTOM_INSTALL_PATH "/opt/luajit_2.0.4") link_directories("${LUAJIT_CUSTOM_INSTALL_PATH}/lib") include_directories("${LUAJIT_CUSTOM_INSTALL_PATH}/include") find_library(LUAJIT_LIBRARY_PATH NAMES luajit-5.1 PATHS "${LUAJIT_CUSTOM_INSTALL_PATH}/lib" NO_DEFAULT_PATH) if (NOT LUAJIT_LIBRARY_PATH) message(FATAL_ERROR "Could not find luajit library") endif() target_link_libraries(netflow_plugin ${LUAJIT_LIBRARY_PATH}) target_link_libraries(sflow_plugin ${LUAJIT_LIBRARY_PATH}) target_link_libraries(fast_library ${LUAJIT_LIBRARY_PATH}) endif() # pcap plugin add_library(pcap_plugin STATIC pcap_plugin/pcap_collector.cpp) target_link_libraries(pcap_plugin pcap) find_package(Threads) if (ENABLE_PFRING_SUPPORT) add_library(pfring_plugin STATIC pfring_plugin/pfring_collector.cpp) target_link_libraries(pfring_plugin ${PFRING_LIBRARIES}) target_link_libraries(pfring_plugin numa) target_link_libraries(pfring_plugin ${CMAKE_THREAD_LIBS_INIT}) # Add action for hardware filetring add_library(pfring_hardware_filter_action STATIC actions/pfring_hardware_filter_action.cpp) endif() if (ENABLE_GOBGP_SUPPORT) set(GOBGP_CUSTOM_INSTALL_PATH "/opt/libgobgp_1_0_0") set(GRPC_CUSTOM_INSTALL_PATH "/opt/grpc_0_11_1_7a94236d698477636dd06282f12f706cad527029") set(PROTOCOL_BUFFERS_CUSTOM_INSTALL_PATH "/opt/protobuf_3.0.0_alpha4") add_definitions(-DENABLE_GOBGP) add_library(gobgp_action STATIC actions/gobgp_action.cpp) find_path(GOBGP_INCLUDES_FOLDER NAMES libgobgp.h PATHS "${GOBGP_CUSTOM_INSTALL_PATH}/include" NO_DEFAULT_PATH) find_library(GOBGP_LIBRARY_PATH NAMES gobgp PATHS "${GOBGP_CUSTOM_INSTALL_PATH}/lib" NO_DEFAULT_PATH) if (GOBGP_INCLUDES_FOLDER AND GOBGP_LIBRARY_PATH) message(STATUS "We found libgobgp and will link it: ${GOBGP_INCLUDES_FOLDER} ${GOBGP_LIBRARY_PATH}") ### We do not link with it in compilation time because it broke daemonization code and pope ### So we use runtime dynamic linking ### target_link_libraries(gobgp_action ${GOBGP_LIBRARY_PATH}) target_link_libraries(gobgp_action dl) include_directories(${GOBGP_INCLUDES_FOLDER}) else() message(FATAL_ERROR "Could not find libgobgp") endif() find_path(GRPC_INCLUDES_FOLDER NAMES grpc/grpc.h PATHS "${GRPC_CUSTOM_INSTALL_PATH}/include" NO_DEFAULT_PATH) find_library(GRPC_LIBRARY_GRPC_PATH NAMES grpc PATHS "${GRPC_CUSTOM_INSTALL_PATH}/lib" NO_DEFAULT_PATH) find_library(GRPC_LIBRARY_GPR_PATH NAMES gpr PATHS "${GRPC_CUSTOM_INSTALL_PATH}/lib" NO_DEFAULT_PATH) find_library(GRPC_LIBRARY_GRPC_CPP_UNSECURE_PATH NAMES grpc++_unsecure PATHS "${GRPC_CUSTOM_INSTALL_PATH}/lib" NO_DEFAULT_PATH) if (GRPC_INCLUDES_FOLDER AND GRPC_LIBRARY_GRPC_PATH AND GRPC_LIBRARY_GPR_PATH AND GRPC_LIBRARY_GRPC_CPP_UNSECURE_PATH) include_directories(${GRPC_INCLUDES_FOLDER}) target_link_libraries(gobgp_action ${GRPC_LIBRARY_GRPC_PATH}) target_link_libraries(gobgp_action ${GRPC_LIBRARY_GPR_PATH}) target_link_libraries(gobgp_action ${GRPC_LIBRARY_GRPC_CPP_UNSECURE_PATH}) else() message(FATAL_ERROR "Could not find gRPC library") endif() find_path(PROTOCOL_BUFFERS_INCLUDE_FOLDER NAMES "google/protobuf/stubs/common.h" PATHS "${PROTOCOL_BUFFERS_CUSTOM_INSTALL_PATH}/include") find_library(PROTOCOL_BUFFERS_LIBRARY_PATH NAMES protobuf PATHS "${PROTOCOL_BUFFERS_CUSTOM_INSTALL_PATH}/lib") if (PROTOCOL_BUFFERS_INCLUDE_FOLDER AND PROTOCOL_BUFFERS_LIBRARY_PATH) include_directories(${PROTOCOL_BUFFERS_INCLUDE_FOLDER}) target_link_libraries(gobgp_action ${PROTOCOL_BUFFERS_LIBRARY_PATH}) else() message(FATAL_ERROR "Could not find protocol buffers") endif() # message(STATUS "grpc: ${GRPC_INCLUDES_FOLDER} ${GRPC_LIBRARY_GRPC_PATH} ${GRPC_LIBRARY_GPR_PATH}") # message(STATUS ${PROJECT_BINARY_DIR}) find_program(PROTOC_BINARY protoc PATHS "${PROTOCOL_BUFFERS_CUSTOM_INSTALL_PATH}/bin" NO_DEFAULT_PATH) if (PROTOC_BINARY) message(STATUS "Found protoc protobuf compiler: ${PROTOC_BINARY}") else() message(FATAL_ERROR "Can't find protoc compiler") endif() set(GRPC_CPP_PLUGIN "${GRPC_CUSTOM_INSTALL_PATH}/bin/grpc_cpp_plugin") execute_process(COMMAND ${PROTOC_BINARY} -I ${PROJECT_BINARY_DIR}/../actions --grpc_out=${PROJECT_BINARY_DIR}/../actions --plugin=protoc-gen-grpc=${GRPC_CPP_PLUGIN} ${PROJECT_BINARY_DIR}/../actions/gobgp_api_client.proto ERROR_VARIABLE PROTOC_STDERR RESULT_VARIABLE PROTOC_RETURN_CODE OUTPUT_STRIP_TRAILING_WHITESPACE) message(STATUS "Protoc return code: ${PROTOC_RETURN_CODE} std err: ${PROTOC_STDERR}") execute_process(COMMAND ${PROTOC_BINARY} -I ${PROJECT_BINARY_DIR}/../actions --cpp_out=${PROJECT_BINARY_DIR}/../actions ${PROJECT_BINARY_DIR}/../actions/gobgp_api_client.proto ERROR_VARIABLE PROTOC_STDERR RESULT_VARIABLE PROTOC_RETURN_CODE OUTPUT_STRIP_TRAILING_WHITESPACE) message(STATUS "Protoc return code: ${PROTOC_RETURN_CODE} std err: ${PROTOC_STDERR}") # Build gRPC and protocol bufffers libraries and link they to gobgp_action add_library(gobgp_api_client_pb_cc STATIC actions/gobgp_api_client.pb.cc) add_library(gobgp_api_client_grpc_pb_cc STATIC actions/gobgp_api_client.grpc.pb.cc) target_link_libraries(gobgp_action gobgp_api_client_pb_cc) target_link_libraries(gobgp_action gobgp_api_client_grpc_pb_cc) # FastNetMon API add_definitions(-DFASTNETMON_API) execute_process(COMMAND ${PROTOC_BINARY} -I ${PROJECT_BINARY_DIR}/.. --grpc_out=${PROJECT_BINARY_DIR}/.. --plugin=protoc-gen-grpc=${GRPC_CPP_PLUGIN} ${PROJECT_BINARY_DIR}/../fastnetmon.proto ERROR_VARIABLE PROTOC_STDERR RESULT_VARIABLE PROTOC_RETURN_CODE OUTPUT_STRIP_TRAILING_WHITESPACE) message(STATUS "Protoc return code: ${PROTOC_RETURN_CODE} std err: ${PROTOC_STDERR}") execute_process(COMMAND ${PROTOC_BINARY} -I ${PROJECT_BINARY_DIR}/.. --cpp_out=${PROJECT_BINARY_DIR}/.. ${PROJECT_BINARY_DIR}/../fastnetmon.proto ERROR_VARIABLE PROTOC_STDERR RESULT_VARIABLE PROTOC_RETURN_CODE OUTPUT_STRIP_TRAILING_WHITESPACE) message(STATUS "Protoc return code: ${PROTOC_RETURN_CODE} std err: ${PROTOC_STDERR}") add_library(fastnetmon_grpc_pb_cc STATIC fastnetmon.grpc.pb.cc) add_library(fastnetmon_pb_cc STATIC fastnetmon.pb.cc) add_executable(fastnetmon_api_client fastnetmon_api_client.cpp) target_link_libraries(fastnetmon_api_client ${GRPC_LIBRARY_GPR_PATH}) target_link_libraries(fastnetmon_api_client ${GRPC_LIBRARY_GRPC_CPP_UNSECURE_PATH}) target_link_libraries(fastnetmon_api_client ${GRPC_LIBRARY_GRPC_PATH}) target_link_libraries(fastnetmon_api_client fastnetmon_grpc_pb_cc) target_link_libraries(fastnetmon_api_client fastnetmon_pb_cc) target_link_libraries(fastnetmon_api_client ${PROTOCOL_BUFFERS_LIBRARY_PATH}) target_link_libraries(fastnetmon ${GRPC_LIBRARY_GPR_PATH}) target_link_libraries(fastnetmon ${GRPC_LIBRARY_GRPC_CPP_UNSECURE_PATH}) target_link_libraries(fastnetmon ${GRPC_LIBRARY_GRPC_PATH}) target_link_libraries(fastnetmon fastnetmon_grpc_pb_cc) target_link_libraries(fastnetmon fastnetmon_pb_cc) target_link_libraries(fastnetmon ${PROTOCOL_BUFFERS_LIBRARY_PATH}) endif() # example plugin add_library(example_plugin STATIC example_plugin/example_collector.cpp) # Netmap plugin set(NETMAP_INCLUDE_DIRS "netmap_plugin/netmap_includes") include_directories(${NETMAP_INCLUDE_DIRS}) add_library(netmap_plugin STATIC netmap_plugin/netmap_collector.cpp) target_link_libraries(netmap_plugin fastnetmon_packet_parser) # Client tool add_executable(fastnetmon_client fastnetmon_client.cpp) # Find boost: http://www.cmake.org/cmake/help/v3.0/module/FindBoost.html # Enable detailed errors set(Boost_DETAILED_FAILURE_MSG ON) find_package(Boost COMPONENTS thread regex program_options system REQUIRED) if(Boost_FOUND) include_directories(${Boost_INCLUDE_DIRS}) target_link_libraries(fastnetmon ${Boost_LIBRARIES}) target_link_libraries(fast_library ${Boost_LIBRARIES}) endif() target_link_libraries(fast_library patricia) target_link_libraries(fast_library fastnetmon_pcap_format) # Try to find ncurses librreary find_package(Curses REQUIRED) if(CURSES_FOUND) include_directories(${CURSES_INCLUDE_DIRS}) target_link_libraries(fastnetmon_client ${CURSES_LIBRARIES}) endif() ### Move this code to cmake module # Try to find hiredis in a specific folder find_path(HIREDIS_INCLUDES_FOLDER NAMES hiredis/hiredis.h PATHS "${HIREDIS_CUSTOM_INSTALL_PATH}/include" NO_DEFAULT_PATH) # Try to find hiredis library path find_library(HIREDIS_LIBRARY_PATH NAMES hiredis PATHS "${HIREDIS_CUSTOM_INSTALL_PATH}/lib" NO_DEFAULT_PATH) if (HIREDIS_INCLUDES_FOLDER AND HIREDIS_LIBRARY_PATH) message(STATUS "We found hiredis library and will build Redis support ${HIREDIS_INCLUDES_FOLDER} ${HIREDIS_LIBRARY_PATH}") add_definitions(-DREDIS) include_directories(${HIREDIS_INCLUDES_FOLDER}) target_link_libraries (fastnetmon ${HIREDIS_LIBRARY_PATH}) else() message(STATUS "We can't find hiredis library and will disable Redis support") endif() ### Find mongo-c find_path(MONGOC_INCLUDES_FOLDER NAMES libmongoc-1.0/mongoc.h PATHS "${MONGO_C_CUSTOM_INSTALL_PATH}/include" NO_DEFAULT_PATH) find_library(MONGOC_LIBRARY_PATH NAMES mongoc-1.0 PATHS "${MONGO_C_CUSTOM_INSTALL_PATH}/lib" NO_DEFAULT_PATH) ### find bson find_path(BSON_INCLUDES_FOLDER NAMES libbson-1.0/bson.h PATHS "${MONGO_C_CUSTOM_INSTALL_PATH}/include" NO_DEFAULT_PATH) find_library(BSON_LIBRARY_PATH NAMES bson-1.0 PATHS "${MONGO_C_CUSTOM_INSTALL_PATH}/lib" NO_DEFAULT_PATH) if (MONGOC_INCLUDES_FOLDER AND MONGOC_LIBRARY_PATH AND BSON_INCLUDES_FOLDER AND BSON_LIBRARY_PATH) message(STATUS "We found mongo-c library ${MONGOC_INCLUDES_FOLDER} ${MONGOC_LIBRARY_PATH} ${BSON_INCLUDES_FOLDER} ${BSON_LIBRARY_PATH}") add_definitions(-DMONGO) # We add suffix name because cmake could not detect it correctly... include_directories("${MONGOC_INCLUDES_FOLDER}/libmongoc-1.0") include_directories("${BSON_INCLUDES_FOLDER}/libbson-1.0") target_link_libraries(fastnetmon ${MONGOC_LIBRARY_PATH} ${BSON_LIBRARY_PATH}) else() message(WARNING "We can't find Mongo C library") endif() ### Look for libpcap #find_path(LIBPCAP_INCLUDES_FOLDER NAMES pcap.h PATHS "${LIBPCAP_CUSTOM_INSTALL_PATH}/include" NO_DEFAULT_PATH) #find_library(LIBPCAP_LIBRARY_PATH NAMES pcap PATHS "${LIBPCAP_CUSTOM_INSTALL_PATH}/lib" NO_DEFAULT_PATH) #if (LIBPCAP_INCLUDES_FOLDER AND LIBPCAP_LIBRARY_PATH) # message(STATUS "We found pcap library ${LIBPCAP_LIBRARY_PATH}") # include_directories(${LIBPCAP_INCLUDES_FOLDER}) #else() # message(FATAL_ERROR "We can't find pcap library") #endif() ### Look for log4cpp # Try to find log4cpp includes path find_path(LOG4CPP_INCLUDES_FOLDER NAMES log4cpp/Appender.hh PATHS "${LOG4CPP_CUSTOM_INSTALL_PATH}/include" NO_DEFAULT_PATH) # Try to find log4cpp library path find_library(LOG4CPP_LIBRARY_PATH NAMES log4cpp PATHS "${LOG4CPP_CUSTOM_INSTALL_PATH}/lib" NO_DEFAULT_PATH) if (LOG4CPP_INCLUDES_FOLDER AND LOG4CPP_LIBRARY_PATH) include_directories(${LOG4CPP_INCLUDES_FOLDER}) message(STATUS "We have found log4cpp and will build project") else() message(FATAL_ERROR "We can't find log4cpp. We can't build project") endif() ### Look for jsonc find_path(JSONC_INCLUDES_FOLDER NAMES json-c/json.h PATHS "${JSONC_CUSTOM_INSTALL_PATH}/include" NO_DEFAULT_PATH) find_library(JSONC_LIBRARY_PATH NAMES json-c PATHS "${JSONC_CUSTOM_INSTALL_PATH}/lib" NO_DEFAULT_PATH) if (JSONC_INCLUDES_FOLDER AND JSONC_LIBRARY_PATH) include_directories(${JSONC_INCLUDES_FOLDER}) message(STATUS "We have found json-c library correctly: ${JSONC_LIBRARY_PATH}") else() message(FATAL_ERROR "We can't find json-c library! Can't build project") endif() target_link_libraries(fast_library ${JSONC_LIBRARY_PATH}) if (ENABLE_DPI_SUPPORT) target_link_libraries(fastnetmon fast_dpi) endif() target_link_libraries(fastnetmon ${LOG4CPP_LIBRARY_PATH}) target_link_libraries(fastnetmon ${CMAKE_THREAD_LIBS_INIT}) if (ENABLE_LUA_SUPPORT) target_link_libraries(fast_library ${LUAJIT_LIBRARY_PATH}) target_link_libraries(fastnetmon ${LUAJIT_LIBRARY_PATH}) endif() # Our libs target_link_libraries(fastnetmon patricia) target_link_libraries(fastnetmon fastnetmon_pcap_format) target_link_libraries(fastnetmon ipfix_rfc) # Link to our functions target_link_libraries(fastnetmon fast_library) if (ENABLE_PFRING_SUPPORT) target_link_libraries(fastnetmon pfring_plugin) # Link hardware filter too target_link_libraries(fastnetmon pfring_hardware_filter_action) endif() if (ENABLE_GOBGP_SUPPORT) target_link_libraries(fastnetmon gobgp_action) endif() if (ENABLE_SNABBSWITCH_SUPPORT) target_link_libraries(fastnetmon snabbswitch_plugin) endif() if (ENABLE_AFPACKET_SUPPORT) target_link_libraries(fastnetmon afpacket_plugin) endif() target_link_libraries(fastnetmon sflow_plugin netflow_plugin pcap_plugin example_plugin netmap_plugin) # cmake .. -DBUILD_PLUGIN_RUNNER=ON if (BUILD_PLUGIN_RUNNER) add_executable(fastnetmon_plugin_runner plugin_runner.cpp) if (ENABLE_SNABBSWITCH_SUPPORT) target_link_libraries(fastnetmon_plugin_runner snabbswitch_plugin) endif() if (ENABLE_AFPACKET_SUPPORT) target_link_libraries(fastnetmon_plugin_runner afpacket_plugin) endif() target_link_libraries(fastnetmon_plugin_runner ${CMAKE_THREAD_LIBS_INIT}) target_link_libraries(fastnetmon_plugin_runner patricia) target_link_libraries(fastnetmon_plugin_runner fastnetmon_pcap_format) target_link_libraries(fastnetmon_plugin_runner ${LOG4CPP_LIBRARY_PATH}) target_link_libraries(fastnetmon_plugin_runner fast_library) # Add all plugins target_link_libraries(fastnetmon_plugin_runner sflow_plugin netflow_plugin pcap_plugin example_plugin netmap_plugin) if (ENABLE_PFRING_SUPPORT) target_link_libraries(fastnetmon_plugin_runner ${PFRING_LIBRARIES}) target_link_libraries(fastnetmon_plugin_runner pfring_plugin) endif() endif() # cmake .. -DBUILD_PCAP_READER=ON if (BUILD_PCAP_READER) add_executable(fastnetmon_pcap_reader pcap_reader.cpp) target_link_libraries(fastnetmon_pcap_reader fastnetmon_packet_parser) target_link_libraries(fastnetmon_pcap_reader patricia) target_link_libraries(fastnetmon_pcap_reader fastnetmon_pcap_format) target_link_libraries(fastnetmon_pcap_reader fast_library) target_link_libraries(fastnetmon_pcap_reader ${LOG4CPP_LIBRARY_PATH}) target_link_libraries(fastnetmon_pcap_reader netflow_plugin) target_link_libraries(fastnetmon_pcap_reader sflow_plugin) target_link_libraries(fastnetmon_pcap_reader netmap_plugin) if (ENABLE_DPI_SUPPORT) target_link_libraries(fastnetmon_pcap_reader fast_dpi) endif() endif() # cmake -DBUILD_TESTS=ON .. if (BUILD_TESTS) add_executable(fastnetmon_tests fastnetmon_tests.cpp) target_link_libraries(fastnetmon_tests fast_library) target_link_libraries(fastnetmon_tests ${CMAKE_THREAD_LIBS_INIT}) target_link_libraries(fastnetmon_tests ${Boost_LIBRARIES}) target_link_libraries(fastnetmon_tests ${LOG4CPP_LIBRARY_PATH}) set(GOOGLE_TEST_INCLUDE_DIRS /opt/gtest/include) set(GOOGLE_TEST_LIBRARIES /opt/gtest/lib/libgtest.a /opt/gtest/lib/libgtest_main.a) # Compiled Google Library include_directories(${GOOGLE_TEST_INCLUDE_DIRS}) target_link_libraries(fastnetmon_tests ${GOOGLE_TEST_LIBRARIES}) endif() if (${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD" OR ${CMAKE_SYSTEM_NAME} STREQUAL "DragonFly") install(TARGETS fastnetmon DESTINATION bin) install(TARGETS fastnetmon_client DESTINATION bin) install(FILES fastnetmon.conf DESTINATION etc) # Install blank files for networks list and whitelist install(FILES networks_list DESTINATION etc) install(FILES networks_whitelist DESTINATION etc) else() # Linux install(TARGETS fastnetmon DESTINATION /usr/sbin) install(TARGETS fastnetmon_client DESTINATION /usr/bin) install(FILES fastnetmon.conf DESTINATION /etc) # Install blank files for networks list and whitelist install(FILES networks_list DESTINATION /etc) install(FILES networks_whitelist DESTINATION /etc) endif() # man pages install(FILES man/fastnetmon.1 DESTINATION /usr/share/man/man1) install(FILES man/fastnetmon_client.1 DESTINATION /usr/share/man/man1) # Configure cpack package builder # Run it with: cd build; cpack -G DEB .. set(CPACK_PACKAGE_NAME "fastnetmon") set(CPACK_PACKAGE_VENDOR "vps2fast.com") set(CPACK_PACKAGE_CONTACT "pavel.odintsov@gmail.com") set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "FastNetMon - very fast DDoS analyzer with sflow/netflow/mirror support") set(CPACK_PACKAGE_VERSION "1.1.2") set(CPACK_PACKAGE_VERSION_MAJOR "1") set(CPACK_PACKAGE_VERSION_MINOR "1") set(CPACK_PACKAGE_VERSION_PATCH "2") set(CPACK_DEBIAN_PACKAGE_DEPENDS "") # set(CPACK_PACKAGE_INSTALL_DIRECTORY "CPack Component Example") # Specify config for deb package # http://www.cmake.org/Wiki/CMake:CPackPackageGenerators#DEB_.28UNIX_only.29 set(CPACK_DEBIAN_PACKAGE_DEPENDS "libboost-thread-dev, libboost-system-dev, libboost-regex-dev, libpcap-dev, libnuma-dev, liblog4cpp5-dev") # This must always be last! include(CPack) fastnetmon-1.1.3+dfsg/src/FreeBSD_port/000077500000000000000000000000001313534057500176645ustar00rootroot00000000000000fastnetmon-1.1.3+dfsg/src/FreeBSD_port/Makefile000066400000000000000000000023141313534057500213240ustar00rootroot00000000000000# $FreeBSD$ PORTNAME= fastnetmon PORTVERSION= 1.1.2 DISTVERSIONPREFIX= v CATEGORIES= net-mgmt MAINTAINER= pavel.odintsov@gmail.com COMMENT= Very fast DDoS analyzer with sFlow/NetFLow/IPFIX/SPAN/mirror support LICENSE= GPLv2 LICENSE_FILE= ${WRKSRC}/LICENSE LIB_DEPENDS= liblog4cpp.so:${PORTSDIR}/devel/log4cpp \ libboost_regex.so:${PORTSDIR}/devel/boost-libs USE_GITHUB= yes GH_ACCOUNT= FastVPSEestiOu # TODO: enable this after updating /usr/ports/{UIDs,GIDs} #USERS= fastnetmon #GROUPS= fastnetmon USES= cmake CMAKE_SOURCE_PATH=${WRKSRC}/src USE_RC_SUBR= fastnetmon PORTDOCS= * OPTIONS_DEFINE= DOCS post-patch: @${REINPLACE_CMD} -e 's|/usr/local|${LOCALBASE}|' ${WRKSRC}/src/CMakeLists.txt @${REINPLACE_CMD} -e 's|/usr/local|${PREFIX}|; \ s|/var/run|&/fastnetmon|g; s|/var/log|&/fastnetmon|g; \ s|"/etc/|"${PREFIX}/etc/|g; s|/root/fastnetmon|${DATADIR}|g' \ ${WRKSRC}/src/fastnetmon.conf ${WRKSRC}/src/fastnetmon.cpp post-install: ${MV} ${STAGEDIR}${PREFIX}/etc/${PORTNAME}.conf \ ${STAGEDIR}${PREFIX}/etc/${PORTNAME}.conf.sample cd ${WRKSRC} && ${COPYTREE_SHARE} "README.md docs" ${STAGEDIR}${DOCSDIR} ${MKDIR} ${STAGEDIR}/var/run/fastnetmon ${STAGEDIR}/var/log/fastnetmon .include fastnetmon-1.1.3+dfsg/src/FreeBSD_port/distinfo000066400000000000000000000002651313534057500214310ustar00rootroot00000000000000SHA256 (FastVPSEestiOu-fastnetmon-v1.1.2_GH0.tar.gz) = f2c554aa402e608b9837132b17da79b49f1b998c17934344779ddc9a397261b4 SIZE (FastVPSEestiOu-fastnetmon-v1.1.2_GH0.tar.gz) = 6072730 fastnetmon-1.1.3+dfsg/src/FreeBSD_port/files/000077500000000000000000000000001313534057500207665ustar00rootroot00000000000000fastnetmon-1.1.3+dfsg/src/FreeBSD_port/files/fastnetmon.in000066400000000000000000000010451313534057500234740ustar00rootroot00000000000000#!/bin/sh # PROVIDE: fastnetmon # REQUIRE: NETWORKING SERVERS LOGIN # BEFORE: securelevel # KEYWORD: shutdown # Add the following line to /etc/rc.conf to enable `fastnetmon': # # fastnetmon_enable="YES" # . /etc/rc.subr name="fastnetmon" rcvar="${name}_enable" command="%%PREFIX%%/bin/fastnetmon" pidfile="/var/run/fastnetmon/$name.pid" load_rc_config "$name" : ${fastnetmon_enable:="NO"} # TODO: enable this after updating /usr/ports/{UIDs,GIDs} #: ${fastnetmon_user:="fastnetmon"} : ${fastnetmon_flags:="--daemonize"} run_rc_command "$1" fastnetmon-1.1.3+dfsg/src/FreeBSD_port/files/patch-src_CMakeLists.txt000066400000000000000000000030011313534057500254640ustar00rootroot00000000000000--- src/CMakeLists.txt.orig 2015-06-02 16:43:16 UTC +++ src/CMakeLists.txt @@ -14,8 +14,8 @@ set (Tutorial_VERSION_MAJOR 1) set (Tutorial_VERSION_MINOR 1) # It's pretty safe and provide big speedup for our packet processor and patricia code -set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O2 ") -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O2") +set(CMAKE_C_FLAGS_RELEASE "-O2") +set(CMAKE_CXX_FLAGS_RELEASE "-O2") set(FASTNETMON_PROFILER OFF) @@ -91,11 +91,13 @@ target_link_libraries(netflow_plugin ipf add_library(pcap_plugin STATIC pcap_plugin/pcap_collector.cpp) target_link_libraries(pcap_plugin pcap) +find_package(Threads) + if (ENABLE_PFRING_SUPPORT) add_library(pfring_plugin STATIC pfring_plugin/pfring_collector.cpp) target_link_libraries(pfring_plugin ${PFRING_LIBRARIES}) target_link_libraries(pfring_plugin numa) - target_link_libraries(pfring_plugin pthread) + target_link_libraries(pfring_plugin ${CMAKE_THREAD_LIBS_INIT}) endif() # example plugin @@ -169,7 +171,7 @@ endif() target_link_libraries(fastnetmon ${LOG4CPP_LIBRARY_PATH}) -target_link_libraries(fastnetmon pthread) +target_link_libraries(fastnetmon ${CMAKE_THREAD_LIBS_INIT}) # Our libs target_link_libraries(fastnetmon patricia) @@ -217,6 +219,8 @@ endif() install(TARGETS fastnetmon DESTINATION bin) install(TARGETS fastnetmon_client DESTINATION bin) +install(FILES fastnetmon.conf DESTINATION etc) + # Configure cpack package builder # Run it with: cd build; cpack -G DEB .. set(CPACK_PACKAGE_NAME "fastnetmon") fastnetmon-1.1.3+dfsg/src/FreeBSD_port/pkg-descr000066400000000000000000000003121313534057500214620ustar00rootroot00000000000000FastNetMon - A high performance DoS/DDoS load analyzer built on top of multiple packet capture engines (NetFlow, IPFIX, sFLOW, netmap, PF_RING, PCAP). WWW: https://github.com/FastVPSEestiOu/fastnetmon fastnetmon-1.1.3+dfsg/src/FreeBSD_port/pkg-plist000066400000000000000000000001721313534057500215210ustar00rootroot00000000000000bin/fastnetmon bin/fastnetmon_client @sample etc/fastnetmon.conf.sample @dir /var/run/fastnetmon @dir /var/log/fastnetmon fastnetmon-1.1.3+dfsg/src/actions/000077500000000000000000000000001313534057500170465ustar00rootroot00000000000000fastnetmon-1.1.3+dfsg/src/actions/gobgp_action.cpp000066400000000000000000000326071313534057500222150ustar00rootroot00000000000000#include "gobgp_action.h" #include "../fastnetmon_actions.h" #include "../fastnetmon_types.h" #include extern "C" { // Gobgp library #include "libgobgp.h" } #include #include #include #include #include #include "gobgp_api_client.grpc.pb.h" using grpc::Channel; using grpc::ClientContext; using grpc::Status; using gobgpapi::GobgpApi; // Create function pointers typedef path* (*serialize_path_dynamic_t)(int p0, char* p1); typedef char* (*decode_path_dynamic_t)(path* p0); serialize_path_dynamic_t serialize_path_dynamic = NULL; decode_path_dynamic_t decode_path_dynamic = NULL; class GrpcClient { public: GrpcClient(std::shared_ptr channel) : stub_(GobgpApi::NewStub(channel)) {} void GetAllActiveAnnounces(unsigned int route_family) { ClientContext context; gobgpapi::Table table; table.set_family(route_family); // We could specify certain neighbor here table.set_name(""); table.set_type(gobgpapi::Resource::GLOBAL); gobgpapi::Table response_table; auto status = stub_->GetRib(&context, table, &response_table); if (!status.ok()) { // error_message logger << log4cpp::Priority::INFO << "Problem with RPC: " << status.error_code() << " message " << status.error_message(); //std::cout << "Problem with RPC: " << status.error_code() << " message " << status.error_message() << std::endl; return; } else { // std::cout << "RPC working well" << std::endl; } std::cout << "List of announced prefixes for route family: " << route_family << std::endl << std::endl; for (auto current_destination : response_table.destinations()) { logger << log4cpp::Priority::INFO << "Prefix: " << current_destination.prefix(); //std::cout << "Prefix: " << current_destination.prefix() << std::endl; //std::cout << "Paths size: " << current_destination.paths_size() << std::endl; gobgpapi::Path my_path = current_destination.paths(0); // std::cout << "Pattrs size: " << my_path.pattrs_size() << std::endl; buf my_nlri; my_nlri.value = (char*)my_path.nlri().c_str(); my_nlri.len = my_path.nlri().size(); path_t gobgp_lib_path; gobgp_lib_path.nlri = my_nlri; // Not used in library code! gobgp_lib_path.path_attributes_cap = 0; gobgp_lib_path.path_attributes_len = my_path.pattrs_size(); buf* my_path_attributes[ my_path.pattrs_size() ]; for (int i = 0; i < my_path.pattrs_size(); i++) { my_path_attributes[i] = (buf*)malloc(sizeof(buf)); my_path_attributes[i]->len = my_path.pattrs(i).size(); my_path_attributes[i]->value = (char*)my_path.pattrs(i).c_str(); } gobgp_lib_path.path_attributes = my_path_attributes; logger << log4cpp::Priority::INFO << "NLRI: " << decode_path_dynamic(&gobgp_lib_path); //std::cout << "NLRI: " << decode_path(&gobgp_lib_path) << std::endl; } } void AnnounceFlowSpecPrefix(bool withdraw) { const gobgpapi::ModPathArguments current_mod_path_arguments; unsigned int AFI_IP = 1; unsigned int SAFI_FLOW_SPEC_UNICAST = 133; unsigned int ipv4_flow_spec_route_family = AFI_IP<<16 | SAFI_FLOW_SPEC_UNICAST; gobgpapi::Path* current_path = new gobgpapi::Path; current_path->set_is_withdraw(withdraw); /* buf: char *value; int len; path: buf nlri; buf** path_attributes; int path_attributes_len; int path_attributes_cap; */ path* path_c_struct = serialize_path_dynamic(ipv4_flow_spec_route_family, (char*)"match destination 10.0.0.0/24 protocol tcp source 20.0.0.0/24 then redirect 10:10"); // printf("Decoded NLRI output: %s, length %d raw string length: %d\n", decode_path(path_c_struct), path_c_struct->nlri.len, strlen(path_c_struct->nlri.value)); for (int path_attribute_number = 0; path_attribute_number < path_c_struct->path_attributes_len; path_attribute_number++) { current_path->add_pattrs(path_c_struct->path_attributes[path_attribute_number]->value, path_c_struct->path_attributes[path_attribute_number]->len); } current_path->set_nlri(path_c_struct->nlri.value, path_c_struct->nlri.len); gobgpapi::ModPathsArguments request; request.set_resource(gobgpapi::Resource::GLOBAL); google::protobuf::RepeatedPtrField< ::gobgpapi::Path >* current_path_list = request.mutable_paths(); current_path_list->AddAllocated(current_path); request.set_name(""); ClientContext context; gobgpapi::Error return_error; // result is a std::unique_ptr > auto send_stream = stub_->ModPaths(&context, &return_error); bool write_result = send_stream->Write(request); if (!write_result) { logger << log4cpp::Priority::INFO << "Write to API failed\n"; //std::cout << "Write to API failed\n"; } // Finish all writes send_stream->WritesDone(); auto status = send_stream->Finish(); if (status.ok()) { //std::cout << "modpath executed correctly" << std::cout; } else { logger << log4cpp::Priority::INFO << "modpath failed with code: " << status.error_code() << " message " << status.error_message(); //std::cout << "modpath failed with code: " << status.error_code() // << " message " << status.error_message() << std::endl; } } void AnnounceUnicastPrefix(std::string announced_prefix, std::string announced_prefix_nexthop, bool is_withdrawal) { const gobgpapi::ModPathArguments current_mod_path_arguments; unsigned int AFI_IP = 1; unsigned int SAFI_UNICAST = 1; unsigned int ipv4_unicast_route_family = AFI_IP<<16 | SAFI_UNICAST; gobgpapi::Path* current_path = new gobgpapi::Path; // current_path->set_is_withdraw(withdraw); if (is_withdrawal) { current_path->set_is_withdraw(true); } /* buf: char *value; int len; path: buf nlri; buf** path_attributes; int path_attributes_len; int path_attributes_cap; */ std::string announce_line = announced_prefix + " nexthop " + announced_prefix_nexthop; // 10.10.20.33/22 nexthop 10.10.1.99/32 path* path_c_struct = serialize_path_dynamic(ipv4_unicast_route_family, (char*)announce_line.c_str()); if (path_c_struct == NULL) { logger << log4cpp::Priority::ERROR << "Could not generate path\n"; return; } // printf("Decoded NLRI output: %s, length %d raw string length: %d\n", decode_path(path_c_struct), path_c_struct->nlri.len, strlen(path_c_struct->nlri.value)); for (int path_attribute_number = 0; path_attribute_number < path_c_struct->path_attributes_len; path_attribute_number++) { current_path->add_pattrs(path_c_struct->path_attributes[path_attribute_number]->value, path_c_struct->path_attributes[path_attribute_number]->len); } current_path->set_nlri(path_c_struct->nlri.value, path_c_struct->nlri.len); gobgpapi::ModPathsArguments request; request.set_resource(gobgpapi::Resource::GLOBAL); google::protobuf::RepeatedPtrField< ::gobgpapi::Path >* current_path_list = request.mutable_paths(); current_path_list->AddAllocated(current_path); request.set_name(""); ClientContext context; gobgpapi::Error return_error; // result is a std::unique_ptr > auto send_stream = stub_->ModPaths(&context, &return_error); bool write_result = send_stream->Write(request); if (!write_result) { //std::cout << "Write to API failed\n"; logger << log4cpp::Priority::ERROR << "Write to API failed\n"; return; } // Finish all writes send_stream->WritesDone(); auto status = send_stream->Finish(); if (status.ok()) { //std::cout << "modpath executed correctly" << std::cout; } else { //std::cout << "modpath failed with code: " << status.error_code() // << " message " << status.error_message() << std::endl; logger << log4cpp::Priority::ERROR << "modpath failed with code: " << status.error_code() << " message " << status.error_message(); return; } } std::string GetNeighbor(std::string neighbor_ip) { gobgpapi::Arguments request; request.set_family(4); request.set_name(neighbor_ip); ClientContext context; gobgpapi::Peer peer; grpc::Status status = stub_->GetNeighbor(&context, request, &peer); if (status.ok()) { gobgpapi::PeerConf peer_conf = peer.conf(); gobgpapi::PeerState peer_info = peer.info(); std::stringstream buffer; buffer << "Peer AS: " << peer_conf.peer_as() << "\n" << "Peer router id: " << peer_conf.id() << "\n" << "Peer flops: " << peer_info.flops() << "\n" << "BGP state: " << peer_info.bgp_state(); return buffer.str(); } else { return "Something wrong"; } } private: std::unique_ptr stub_; }; GrpcClient* gobgp_client = NULL; std::string gobgp_nexthop = "0.0.0.0"; bool gobgp_announce_whole_subnet = false; bool gobgp_announce_host = false; void gobgp_action_init() { logger << log4cpp::Priority::INFO << "GoBGP action module loaded"; gobgp_client = new GrpcClient(grpc::CreateChannel("localhost:50051", grpc::InsecureCredentials())); // GrpcClient gobgp_client(grpc::CreateChannel("localhost:50051", grpc::InsecureCredentials())); if (configuration_map.count("gobgp_next_hop")) { gobgp_nexthop = configuration_map["gobgp_next_hop"]; } if (configuration_map.count("gobgp_announce_host")) { gobgp_announce_host = configuration_map["gobgp_announce_host"] == "on"; } if (configuration_map.count("gobgp_announce_whole_subnet")) { gobgp_announce_whole_subnet = configuration_map["gobgp_announce_whole_subnet"] == "on"; } // According to this bug report: https://github.com/golang/go/issues/12873 // We have significant issues with popen, daemonization and Go's runtime // We use non absoulte path here and linker will find it fir us void* gobgdp_library_handle = dlopen("libgobgp.so", RTLD_NOW); if (gobgdp_library_handle == NULL) { logger << log4cpp::Priority::ERROR << "Could not load gobgp binary library: " << dlerror(); exit(1); } dlerror(); /* Clear any existing error */ /* According to the ISO C standard, casting between function pointers and 'void *', as done above, produces undefined results. POSIX.1-2003 and POSIX.1-2008 accepted this state of affairs and proposed the following workaround: */ serialize_path_dynamic = (serialize_path_dynamic_t)dlsym(gobgdp_library_handle, "serialize_path"); if (serialize_path_dynamic == NULL) { logger << log4cpp::Priority::ERROR << "Could not load function serialize_path from the dynamic library"; exit(1); } decode_path_dynamic = (decode_path_dynamic_t)dlsym(gobgdp_library_handle, "decode_path"); if (decode_path_dynamic == NULL) { logger << log4cpp::Priority::ERROR << "Could not load function decode_path from the dynamic library"; exit(1); } } void gobgp_action_shutdown() { delete gobgp_client; } void gobgp_ban_manage(std::string action, std::string ip_as_string, attack_details current_attack) { bool is_withdrawal = false; if (action == "ban") { is_withdrawal = false; } else { is_withdrawal = true; } if (gobgp_announce_whole_subnet) { std::string subnet_as_string_with_mask = convert_subnet_to_string(current_attack.customer_network); gobgp_client->AnnounceUnicastPrefix(subnet_as_string_with_mask, gobgp_nexthop, is_withdrawal); } if (gobgp_announce_host) { std::string ip_as_string_with_mask = ip_as_string + "/32"; gobgp_client->AnnounceUnicastPrefix(ip_as_string_with_mask, gobgp_nexthop, is_withdrawal); } } fastnetmon-1.1.3+dfsg/src/actions/gobgp_action.h000066400000000000000000000004071313534057500216530ustar00rootroot00000000000000#ifndef GOBGP_ACTION_H #define GOBGP_ACTION_H #include #include "../fastnetmon_types.h" void gobgp_action_init(); void gobgp_action_shutdown(); void gobgp_ban_manage(std::string action, std::string ip_as_string, attack_details current_attack); #endif fastnetmon-1.1.3+dfsg/src/actions/gobgp_api_client.proto000066400000000000000000000300171313534057500234210ustar00rootroot00000000000000// Copyright (C) 2015 Nippon Telegraph and Telephone Corporation. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or // implied. // See the License for the specific language governing permissions and // limitations under the License. syntax = "proto3"; package gobgpapi; // Interface exported by the server. service GobgpApi { rpc GetGlobalConfig(Arguments) returns (Global) {} rpc ModGlobalConfig(ModGlobalConfigArguments) returns (Error) {} rpc GetNeighbors(Arguments) returns (stream Peer) {} rpc GetNeighbor(Arguments) returns (Peer) {} rpc ModNeighbor(ModNeighborArguments) returns(Error) {} rpc GetRib(Table) returns (Table) {} rpc Reset(Arguments) returns (Error) {} rpc SoftReset(Arguments) returns (Error) {} rpc SoftResetIn(Arguments) returns (Error) {} rpc SoftResetOut(Arguments) returns (Error) {} rpc Shutdown(Arguments) returns (Error) {} rpc Enable(Arguments) returns (Error) {} rpc Disable(Arguments) returns (Error) {} rpc ModPath(ModPathArguments) returns (ModPathResponse) {} rpc ModPaths(stream ModPathsArguments) returns (Error) {} rpc MonitorRib(Table) returns (stream Destination) {} rpc MonitorBestChanged(Arguments) returns (stream Destination) {} rpc MonitorPeerState(Arguments) returns (stream Peer) {} rpc MonitorROAValidation(Arguments) returns (stream ROAResult) {} rpc GetMrt(MrtArguments) returns (stream MrtMessage) {} rpc ModMrt(ModMrtArguments) returns (Error) {} rpc ModBmp(ModBmpArguments) returns (Error) {} rpc GetRPKI(Arguments) returns (stream RPKI) {} rpc ModRPKI(ModRpkiArguments) returns (Error) {} rpc GetROA(Arguments) returns (stream ROA) {} rpc GetVrfs(Arguments) returns (stream Vrf) {} rpc ModVrf(ModVrfArguments) returns (Error) {} rpc GetDefinedSet(DefinedSet) returns (DefinedSet) {} rpc GetDefinedSets(DefinedSet) returns (stream DefinedSet) {} rpc ModDefinedSet(ModDefinedSetArguments) returns (Error) {} rpc GetStatement(Statement) returns (Statement) {} rpc GetStatements(Statement) returns (stream Statement) {} rpc ModStatement(ModStatementArguments) returns (Error) {} rpc GetPolicy(Policy) returns (Policy) {} rpc GetPolicies(Policy) returns (stream Policy) {} rpc ModPolicy(ModPolicyArguments) returns (Error) {} rpc GetPolicyAssignment(PolicyAssignment) returns (PolicyAssignment) {} rpc ModPolicyAssignment(ModPolicyAssignmentArguments) returns (Error) {} } message Error { enum ErrorCode { SUCCESS = 0; FAIL = 1; } ErrorCode code = 1; string msg = 2; } message Arguments { Resource resource = 1; uint32 family = 2; string name = 3; } message ModPathArguments { Operation operation = 1; Resource resource = 2; string name = 3; Path path = 4; // uuid field can be only used when operation is DEL bytes uuid = 5; // family field is only used when operation is DEL_ALL uint32 family = 6; } message ModPathResponse { bytes uuid = 1; } message ModPathsArguments { Resource resource = 1; string name = 2; repeated Path paths = 3; } message ModNeighborArguments { Operation operation = 1; Peer peer = 2; } message MrtArguments { Resource resource = 1; uint32 family = 2; uint64 interval = 3; string neighbor_address = 4; } message ModMrtArguments { Operation operation = 1; int32 dump_type = 2; string filename = 3; uint64 interval = 4; } message ModBmpArguments { Operation operation = 1; string address = 2; uint32 port = 3; enum MonitoringPolicy { PRE = 0; POST = 1; BOTH = 2; } MonitoringPolicy type = 4; } message ModRpkiArguments { Operation operation = 1; string address = 2; uint32 port = 3; } message ModVrfArguments { Operation operation = 1; Vrf vrf = 2; } message ModDefinedSetArguments { Operation operation = 1; DefinedSet set = 2; } message ModStatementArguments { Operation operation = 1; Statement statement = 2; } message ModPolicyArguments { Operation operation = 1; Policy policy = 2; // if this flag is set, gobgpd won't define new statements // but refer existing statements using statement's names in this arguments. // this flag only works with Operation_ADD bool refer_existing_statements = 3; // if this flag is set, gobgpd won't delete any statements // even if some statements get not used by any policy by this operation. // this flag means nothing if it is used with Operation_ADD bool preserve_statements = 4; } message ModPolicyAssignmentArguments { Operation operation = 1; PolicyAssignment assignment = 2; } message ModGlobalConfigArguments { Operation operation = 1; Global global = 2; } enum Resource { GLOBAL = 0; LOCAL = 1; ADJ_IN = 2; ADJ_OUT = 3; VRF = 4; } enum Operation { ADD = 0; DEL = 1; DEL_ALL = 2; REPLACE = 3; ENABLE = 4; DISABLE = 5; RESET = 6; SOFTRESET = 7; } message Path { bytes nlri = 1; repeated bytes pattrs = 2; int64 age = 3; bool best = 4; bool is_withdraw = 5; int32 validation = 6; bool no_implicit_withdraw = 7; uint32 family = 8; uint32 source_asn = 9; string source_id = 10; bool filtered = 11; bool stale = 12; bool is_from_external = 13; string neighbor_ip = 14; } message Destination { string prefix = 1; repeated Path paths = 2; bool longer_prefixes = 3; } message Table { Resource type = 1; string name = 2; uint32 family = 3; repeated Destination destinations = 4; bool post_policy = 5; } message Peer { repeated uint32 families = 2; ApplyPolicy apply_policy = 3; PeerConf conf = 5; EbgpMultihop ebgp_multihop = 6; RouteReflector route_reflector = 10; PeerState info = 11; Timers timers = 12; Transport transport = 13; RouteServer route_server = 15; } message ApplyPolicy { PolicyAssignment in_policy = 1; PolicyAssignment export_policy = 2; PolicyAssignment import_policy = 3; } message PeerConf { string auth_password = 1; string description = 2; uint32 local_as = 3; string neighbor_address = 4; uint32 peer_as = 5; string peer_group = 6; uint32 peer_type = 7; uint32 remove_private_as = 8; bool route_flap_damping = 9; uint32 send_community = 10; repeated bytes remote_cap = 11; repeated bytes local_cap = 12; string id = 13; } message EbgpMultihop { bool enabled = 1; uint32 multihop_ttl = 2; } message RouteReflector { bool route_reflector_client = 1; uint32 route_reflector_cluster_id = 2; } message PeerState { string auth_password = 1; string description = 2; uint32 local_as = 3; Messages messages = 4; string neighbor_address = 5; uint32 peer_as = 6; string peer_group = 7; uint32 peer_type = 8; Queues queues = 9; uint32 remove_private_as = 10; bool route_flap_damping = 11; uint32 send_community = 12; uint32 session_state = 13; repeated string supported_capabilities = 14; string bgp_state = 15; string admin_state = 16; uint32 received = 17; uint32 accepted = 18; uint32 advertised = 19; uint32 out_q = 20; uint32 flops = 21; } message Messages { Message received = 1; Message sent = 2; } message Message { uint64 NOTIFICATION = 1; uint64 UPDATE = 2; uint64 OPEN = 3; uint64 KEEPALIVE = 4; uint64 REFRESH = 5; uint64 DISCARDED = 6; uint64 TOTAL = 7; } message Queues { uint32 input = 1; uint32 output = 2; } message Timers { TimersConfig config =1; TimersState state = 2; } message TimersConfig{ uint64 connect_retry = 1; uint64 hold_time = 2; uint64 keepalive_interval = 3; uint64 minimum_advertisement_interval = 4; } message TimersState{ uint64 connect_retry = 1; uint64 hold_time = 2; uint64 keepalive_interval = 3; uint64 minimum_advertisement_interval = 4; uint64 negotiated_hold_time = 5; uint64 uptime = 6; uint64 downtime = 7; } message Transport { string local_address = 1; uint32 local_port = 2; bool mtu_discovery = 3; bool passive_mode = 4; string remote_address = 5; uint32 remote_port = 6; uint32 tcp_mss = 7; } message RouteServer { bool route_server_client = 1; } message Prefix { string ip_prefix = 1; uint32 mask_length_min = 2; uint32 mask_length_max = 3; } enum DefinedType { PREFIX = 0; NEIGHBOR = 1; TAG = 2; AS_PATH = 3; COMMUNITY = 4; EXT_COMMUNITY = 5; } message DefinedSet { DefinedType type = 1; string name = 2; repeated string list = 3; repeated Prefix prefixes = 4; } enum MatchType { ANY = 0; ALL = 1; INVERT = 2; } message MatchSet { MatchType type = 1; string name = 2; } enum AsPathLengthType { EQ = 0; GE = 1; LE = 2; } message AsPathLength { AsPathLengthType type = 1; uint32 length = 2; } message Conditions { MatchSet prefix_set = 1; MatchSet neighbor_set = 2; AsPathLength as_path_length = 3; MatchSet as_path_set = 4; MatchSet community_set = 5; MatchSet ext_community_set = 6; int32 rpki_result = 7; } enum RouteAction { NONE = 0; ACCEPT = 1; REJECT = 2; } enum CommunityActionType { COMMUNITY_ADD = 0; COMMUNITY_REMOVE = 1; COMMUNITY_REPLACE = 2; } message CommunityAction { CommunityActionType type = 1; repeated string communities = 2; } enum MedActionType { MED_MOD = 0; MED_REPLACE = 1; } message MedAction { MedActionType type = 1; int64 value = 2; } message AsPrependAction { uint32 asn = 1; uint32 repeat = 2; bool use_left_most = 3; } message Actions { RouteAction route_action = 1; CommunityAction community = 2; MedAction med = 3; AsPrependAction as_prepend = 4; CommunityAction ext_community = 5; } message Statement { string name = 1; Conditions conditions = 2; Actions actions = 3; } message Policy { string name = 1; repeated Statement statements = 2; } enum PolicyType { IN = 0; IMPORT = 1; EXPORT = 2; } message PolicyAssignment { PolicyType type = 1; Resource resource = 2; string name = 3; repeated Policy policies = 4; RouteAction default = 5; } message MrtMessage { bytes data = 1; } message RPKIConf { string address = 1; string remote_port = 2; } message RPKIState { int64 uptime = 1; int64 downtime = 2; bool up = 3; uint32 record_ipv4 = 4; uint32 record_ipv6 = 5; uint32 prefix_ipv4 = 6; uint32 prefix_ipv6 = 7; uint32 serial = 8; int64 received_ipv4 = 9; int64 received_ipv6 = 10; int64 serial_notify = 11; int64 cache_reset = 12; int64 cache_response = 13; int64 end_of_data = 14; int64 error = 15; int64 serial_query = 16; int64 reset_query = 17; } message RPKI { RPKIConf conf = 1; RPKIState state = 2; } message ROA { uint32 as = 1; uint32 prefixlen = 2; uint32 maxlen = 3; string prefix = 4; RPKIConf conf = 5; } message ROAResult { enum ValidationReason { UPDATE = 0; WITHDRAW = 1; PEER_DOWN = 2; REVALIDATE = 3; } ValidationReason reason = 1; string address = 2; int64 timestamp = 3; bytes aspath_attr = 4; uint32 origin_as = 5; string prefix = 6; enum ValidationResult { NONE = 0; NOT_FOUND = 1; VALID = 2; INVALID = 3; } ValidationResult old_result = 7; ValidationResult new_result = 8; repeated ROA roas = 9; } message Vrf { string name = 1; bytes rd = 2; repeated bytes import_rt = 3; repeated bytes export_rt = 4; } message Global { uint32 as = 1; string router_id = 2; int32 listen_port = 3; repeated string listen_addresses = 4; repeated uint32 families = 5; uint32 mpls_label_min = 6; uint32 mpls_label_max = 7; bool collector = 8; } fastnetmon-1.1.3+dfsg/src/actions/pfring_hardware_filter_action.cpp000066400000000000000000000041531313534057500256210ustar00rootroot00000000000000#include "pfring.h" #include #include #include #include "../fastnetmon_actions.h" // Got it from global namespace extern pfring* pf_ring_descr; void pfring_hardware_filter_action_block(std::string client_ip_as_string) { /* 6 - tcp, 17 - udp, 0 - other (non tcp and non udp) */ std::vector banned_protocols; banned_protocols.push_back(17); banned_protocols.push_back(6); banned_protocols.push_back(0); int rule_number = 10; // Iterate over incoming and outgoing direction for (int rule_direction = 0; rule_direction < 2; rule_direction++) { for (std::vector::iterator banned_protocol = banned_protocols.begin(); banned_protocol != banned_protocols.end(); ++banned_protocol) { /* On 82599 NIC we can ban traffic using hardware filtering rules */ // Difference between fie tuple and perfect filters: // http://www.ntop.org/products/pf_ring/hardware-packet-filtering/ hw_filtering_rule rule; intel_82599_five_tuple_filter_hw_rule* ft_rule; ft_rule = &rule.rule_family.five_tuple_rule; memset(&rule, 0, sizeof(rule)); rule.rule_family_type = intel_82599_five_tuple_rule; rule.rule_id = rule_number++; ft_rule->queue_id = -1; // drop traffic ft_rule->proto = *banned_protocol; std::string hw_filter_rule_direction = ""; if (rule_direction == 0) { hw_filter_rule_direction = "outgoing"; ft_rule->s_addr = ntohl(inet_addr(client_ip_as_string.c_str())); } else { hw_filter_rule_direction = "incoming"; ft_rule->d_addr = ntohl(inet_addr(client_ip_as_string.c_str())); } if (pfring_add_hw_rule(pf_ring_descr, &rule) != 0) { logger << log4cpp::Priority::ERROR << "Can't add hardware filtering rule for protocol: " << *banned_protocol << " in direction: " << hw_filter_rule_direction; } rule_number++; } } } fastnetmon-1.1.3+dfsg/src/actions/pfring_hardware_filter_action.h000066400000000000000000000002671313534057500252700ustar00rootroot00000000000000#ifndef PFRING_HARDWARE_FILTER_ACTION_H #define PFRING_HARDWARE_FILTER_ACTION_H #include void pfring_hardware_filter_action_block(std::string client_ip_as_string); #endif fastnetmon-1.1.3+dfsg/src/afpacket_plugin/000077500000000000000000000000001313534057500205425ustar00rootroot00000000000000fastnetmon-1.1.3+dfsg/src/afpacket_plugin/afpacket_collector.cpp000066400000000000000000000263701313534057500251020ustar00rootroot00000000000000// log4cpp logging facility #include "log4cpp/Category.hh" #include "log4cpp/Appender.hh" #include "log4cpp/FileAppender.hh" #include "log4cpp/OstreamAppender.hh" #include "log4cpp/Layout.hh" #include "log4cpp/BasicLayout.hh" #include "log4cpp/PatternLayout.hh" #include "log4cpp/Priority.hh" #include #include #include "../fast_library.h" // For support uint32_t, uint16_t #include // For config map operations #include #include #include #include #include #include "../fastnetmon_packet_parser.h" // For support: IPPROTO_TCP, IPPROTO_ICMP, IPPROTO_UDP #include #include #include #include "afpacket_collector.h" #include #include #include #include #include #include #include #include #include /* the L2 protocols */ // Get log4cpp logger from main programm extern log4cpp::Category& logger; // Pass unparsed packets number to main programm extern uint64_t total_unparsed_packets; // Global configuration map extern std::map configuration_map; // This variable name should be uniq for every plugin! process_packet_pointer afpacket_process_func_ptr = NULL; // 4194304 bytes unsigned int blocksiz = 1 << 22; // 2048 bytes unsigned int framesiz = 1 << 11; unsigned int blocknum = 64; struct block_desc { uint32_t version; uint32_t offset_to_priv; struct tpacket_hdr_v1 h1; }; // We will use this code from Global Symbols table (originally it's defined in netmap collector.cpp) bool parse_raw_packet_to_simple_packet(u_char* buffer, int len, simple_packet& packet); // Get interface number by name int get_interface_number_by_device_name(int socket_fd, std::string interface_name) { struct ifreq ifr; memset(&ifr, 0, sizeof(ifr)); if (interface_name.size() > IFNAMSIZ) { return -1; } strncpy(ifr.ifr_name, interface_name.c_str(), sizeof(ifr.ifr_name)); if (ioctl(socket_fd, SIOCGIFINDEX, &ifr) == -1) { return -1; } return ifr.ifr_ifindex; } void flush_block(struct block_desc *pbd) { pbd->h1.block_status = TP_STATUS_KERNEL; } void walk_block(struct block_desc *pbd, const int block_num) { int num_pkts = pbd->h1.num_pkts, i; unsigned long bytes = 0; struct tpacket3_hdr *ppd; ppd = (struct tpacket3_hdr *) ((uint8_t *) pbd + pbd->h1.offset_to_first_pkt); for (i = 0; i < num_pkts; ++i) { bytes += ppd->tp_snaplen; // struct ethhdr *eth = (struct ethhdr *) ((uint8_t *) ppd + ppd->tp_mac); // Print packets struct pfring_pkthdr packet_header; memset(&packet_header, 0, sizeof(packet_header)); packet_header.len = ppd->tp_snaplen; packet_header.caplen = ppd->tp_snaplen; u_int8_t timestamp = 0; u_int8_t add_hash = 0; u_char* data_pointer = (u_char*)((uint8_t *) ppd + ppd->tp_mac); simple_packet packet; int parser_result = parse_raw_packet_to_simple_packet((u_char*)data_pointer, ppd->tp_snaplen, packet); //char print_buffer[512]; //fastnetmon_print_parsed_pkt(print_buffer, 512, data_pointer, &packet_header); //printf("%s\n", print_buffer); ppd = (struct tpacket3_hdr *) ((uint8_t *) ppd + ppd->tp_next_offset); if (parser_result) { afpacket_process_func_ptr(packet); } else { total_unparsed_packets++; } } } int setup_socket(std::string interface_name, int fanout_group_id) { // More details here: http://man7.org/linux/man-pages/man7/packet.7.html // We could use SOCK_RAW or SOCK_DGRAM for second argument // SOCK_RAW - raw packets pass from the kernel // SOCK_DGRAM - some amount of processing // Third argument manage ether type of captured packets int packet_socket = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL)); if (packet_socket == -1) { logger << log4cpp::Priority::ERROR << "Can't create AF_PACKET socket"; return -1; } // We whould use V3 bcause it could read/pool in per block basis instead per packet int version = TPACKET_V3; int setsockopt_packet_version = setsockopt(packet_socket, SOL_PACKET, PACKET_VERSION, &version, sizeof(version)); if (setsockopt_packet_version < 0) { logger << log4cpp::Priority::ERROR << "Can't set packet v3 version"; return -1; } int interface_number = get_interface_number_by_device_name(packet_socket, interface_name); if (interface_number == -1) { logger << log4cpp::Priority::ERROR << "Can't get interface number by interface name for " << interface_name; return -1; } // Switch to PROMISC mode struct packet_mreq sock_params; memset(&sock_params, 0, sizeof(sock_params)); sock_params.mr_type = PACKET_MR_PROMISC; sock_params.mr_ifindex = interface_number; int set_promisc = setsockopt(packet_socket, SOL_PACKET, PACKET_ADD_MEMBERSHIP, (void *)&sock_params, sizeof(sock_params)); if (set_promisc == -1) { logger << log4cpp::Priority::ERROR << "Can't enable promisc mode"; return -1; } struct sockaddr_ll bind_address; memset(&bind_address, 0, sizeof(bind_address)); bind_address.sll_family = AF_PACKET; bind_address.sll_protocol = htons(ETH_P_ALL); bind_address.sll_ifindex = interface_number; // We will follow http://yusufonlinux.blogspot.ru/2010/11/data-link-access-and-zero-copy.html // And this: https://www.kernel.org/doc/Documentation/networking/packet_mmap.txt struct tpacket_req3 req; memset(&req, 0, sizeof(req)); req.tp_block_size = blocksiz; req.tp_frame_size = framesiz; req.tp_block_nr = blocknum; req.tp_frame_nr = (blocksiz * blocknum) / framesiz; req.tp_retire_blk_tov = 60; // Timeout in msec req.tp_feature_req_word = TP_FT_REQ_FILL_RXHASH; int setsockopt_rx_ring = setsockopt(packet_socket, SOL_PACKET , PACKET_RX_RING , (void*)&req , sizeof(req)); if (setsockopt_rx_ring == -1) { logger << log4cpp::Priority::ERROR << "Can't enable RX_RING for AF_PACKET socket"; return -1; } // We use per thread structures uint8_t* mapped_buffer = NULL; struct iovec* rd = NULL; mapped_buffer = (uint8_t*)mmap(NULL, req.tp_block_size * req.tp_block_nr, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_LOCKED, packet_socket, 0); if (mapped_buffer == MAP_FAILED) { logger << log4cpp::Priority::ERROR << "MMAP failed"; return -1; } // Allocate iov structure for each block rd = (struct iovec*)malloc(req.tp_block_nr * sizeof(struct iovec)); // Initilize iov structures for (int i = 0; i < req.tp_block_nr; ++i) { rd[i].iov_base = mapped_buffer + (i * req.tp_block_size); rd[i].iov_len = req.tp_block_size; } int bind_result = bind(packet_socket, (struct sockaddr *)&bind_address, sizeof(bind_address)); if (bind_result == -1) { logger << log4cpp::Priority::ERROR << "Can't bind to AF_PACKET socket"; return -1; } if (fanout_group_id) { // PACKET_FANOUT_LB - round robin // PACKET_FANOUT_CPU - send packets to CPU where packet arrived int fanout_type = PACKET_FANOUT_CPU; int fanout_arg = (fanout_group_id | (fanout_type << 16)); int setsockopt_fanout = setsockopt(packet_socket, SOL_PACKET, PACKET_FANOUT, &fanout_arg, sizeof(fanout_arg)); if (setsockopt_fanout < 0) { logger << log4cpp::Priority::ERROR << "Can't configure fanout error number: "<< errno << " error: " << strerror(errno); return -1; } } unsigned int current_block_num = 0; struct pollfd pfd; memset(&pfd, 0, sizeof(pfd)); pfd.fd = packet_socket; pfd.events = POLLIN | POLLERR; pfd.revents = 0; while (true) { struct block_desc *pbd = (struct block_desc *) rd[current_block_num].iov_base; if ((pbd->h1.block_status & TP_STATUS_USER) == 0) { poll(&pfd, 1, -1); continue; } walk_block(pbd, current_block_num); flush_block(pbd); current_block_num = (current_block_num + 1) % blocknum; } return packet_socket; } void start_af_packet_capture(std::string interface_name, int fanout_group_id) { setup_socket(interface_name, fanout_group_id); } void get_af_packet_stats() { // getsockopt PACKET_STATISTICS } // Could get some speed up on NUMA servers bool afpacket_execute_strict_cpu_affinity = true; void start_afpacket_collection(process_packet_pointer func_ptr) { logger << log4cpp::Priority::INFO << "AF_PACKET plugin started"; afpacket_process_func_ptr = func_ptr; std::string interfaces_list = ""; if (configuration_map.count("interfaces") != 0) { interfaces_list = configuration_map["interfaces"]; } std::vector interfaces_for_listen; boost::split(interfaces_for_listen, interfaces_list, boost::is_any_of(","), boost::token_compress_on); logger << log4cpp::Priority::INFO << "AF_PACKET will listen on " << interfaces_for_listen.size() << " interfaces"; if (interfaces_for_listen.size() == 0) { logger << log4cpp::Priority::ERROR << "Please specify intreface for AF_PACKET"; return; } if (interfaces_for_listen.size() > 1) { logger << log4cpp::Priority::WARN << "We support only single interface for AF_PACKET, sorry!"; } std::string capture_interface = interfaces_for_listen[0]; int fanout_group_id = getpid() & 0xffff; unsigned int num_cpus = sysconf(_SC_NPROCESSORS_ONLN);; logger.info("We have %d cpus for AF_PACKET", num_cpus); if (num_cpus > 1) { boost::thread_group packet_receiver_thread_group; for (int cpu = 0; cpu < num_cpus; cpu++) { // Well, we have thread attributes from Boost 1.50 #if defined(BOOST_THREAD_PLATFORM_PTHREAD) && BOOST_VERSION / 100 % 1000 >= 50 boost::thread::attributes thread_attrs; if (afpacket_execute_strict_cpu_affinity) { cpu_set_t current_cpu_set; int cpu_to_bind = cpu % num_cpus; CPU_ZERO(¤t_cpu_set); // We count cpus from zero CPU_SET(cpu_to_bind, ¤t_cpu_set); int set_affinity_result = pthread_attr_setaffinity_np(thread_attrs.native_handle(), sizeof(cpu_set_t), ¤t_cpu_set); if (set_affinity_result != 0) { logger << log4cpp::Priority::ERROR << "Can't set CPU affinity for thread"; } } packet_receiver_thread_group.add_thread( new boost::thread(thread_attrs, boost::bind(start_af_packet_capture, capture_interface, fanout_group_id)) ); #else logger.error("Sorry but CPU affinity did not supported for your platform"); packet_receiver_thread_group.add_thread( new boost::thread(start_af_packet_capture, capture_interface, fanout_group_id) ); #endif } // Wait all processes for finish packet_receiver_thread_group.join_all(); } else { start_af_packet_capture(capture_interface, 0); } } fastnetmon-1.1.3+dfsg/src/afpacket_plugin/afpacket_collector.h000066400000000000000000000002401313534057500245330ustar00rootroot00000000000000#ifndef AFPACKET_PLUGIN_H #define AFPACKET_PLUGIN_H #include "../fastnetmon_types.h" void start_afpacket_collection(process_packet_pointer func_ptr); #endif fastnetmon-1.1.3+dfsg/src/asn_geoip_update.sh000077500000000000000000000022201313534057500212470ustar00rootroot00000000000000#!/usr/bin/env bash # Current dir pushd "$(dirname $0)" >/dev/null SCRIPT_DIR="$(pwd -P)" popd >/dev/null OS_TYPE=$(uname) echo "Getting GeoIPASNum.dat GeoIPASNumv6.dat..." # ASN (+IPv6) Database if [ "$OS_TYPE" = "Linux" ];then wget -qO - "http://geolite.maxmind.com/download/geoip/database/asnum/GeoIPASNum.dat.gz" | gunzip -cv > "${SCRIPT_DIR}/GeoIPASNum.dat" wget -qO - "http://download.maxmind.com/download/geoip/database/asnum/GeoIPASNumv6.dat.gz" | gunzip -cv > "${SCRIPT_DIR}/GeoIPASNumv6.dat" elif [ "$OS_TYPE" = "Darwin" ];then curl -sq "http://geolite.maxmind.com/download/geoip/database/asnum/GeoIPASNum.dat.gz" | gunzip -v -c > "${SCRIPT_DIR}/GeoIPASNum.dat" curl -sq "http://download.maxmind.com/download/geoip/database/asnum/GeoIPASNumv6.dat.gz" | gunzip -v -c > "${SCRIPT_DIR}/GeoIPASNumv6.dat" elif [ "$OS_TYPE" = "FreeBSD" ];then fetch -qo - "http://geolite.maxmind.com/download/geoip/database/asnum/GeoIPASNum.dat.gz" | gunzip -cv > "${SCRIPT_DIR}/GeoIPASNum.dat" fetch -qo - "http://download.maxmind.com/download/geoip/database/asnum/GeoIPASNumv6.dat.gz" | gunzip -cv > "${SCRIPT_DIR}/GeoIPASNumv6.dat" fi echo "Done." fastnetmon-1.1.3+dfsg/src/bgp_flow_spec.cpp000066400000000000000000000004461313534057500207270ustar00rootroot00000000000000#include "bgp_flow_spec.h" #include "log4cpp/Category.hh" #include "log4cpp/Appender.hh" #include "log4cpp/FileAppender.hh" #include "log4cpp/OstreamAppender.hh" #include "log4cpp/Layout.hh" #include "log4cpp/BasicLayout.hh" #include "log4cpp/PatternLayout.hh" #include "log4cpp/Priority.hh" fastnetmon-1.1.3+dfsg/src/bgp_flow_spec.h000066400000000000000000000404341313534057500203750ustar00rootroot00000000000000#ifndef BGP_FLOW_SPEC_H #define BGP_FLOW_SPEC_H #include #include #include #include "fastnetmon_types.h" #include "fast_library.h" // Helper for serialization by comma template std::string serialize_vector_by_string(const std::vector vect, std::string delimiter) { std::ostringstream output_buffer; std::copy(vect.begin(), vect.end() - 1, std::ostream_iterator(output_buffer, delimiter.c_str())); output_buffer << vect.back(); return output_buffer.str(); } template std::string serialize_vector_by_string_with_prefix(const std::vector vect, std::string delimiter, std::string prefix) { std::vector elements_with_prefix; for (typename std::vector::const_iterator itr = vect.begin(); itr != vect.end(); ++itr) { elements_with_prefix.push_back(prefix + convert_int_to_string(*itr)); } return serialize_vector_by_string(elements_with_prefix, delimiter); } // All possible values for BGP Flow Spec fragmentation field enum flow_spec_fragmentation_types_t { FLOW_SPEC_DONT_FRAGMENT, FLOW_SPEC_IS_A_FRAGMENT, FLOW_SPEC_FIRST_FRAGMENT, FLOW_SPEC_LAST_FRAGMENT, FLOW_NOT_A_FRAGMENT, }; // TCP flags for Flow Spec enum flow_spec_tcp_flags_t { FLOW_TCP_FLAG_SYN, FLOW_TCP_FLAG_FIN, FLOW_TCP_FLAG_URG, FLOW_TCP_FLAG_ACK, FLOW_TCP_FLAG_PSH, FLOW_TCP_FLAG_RST, }; // Flow spec actions enum bgp_flow_spec_action_types_t { FLOW_SPEC_ACTION_DISCARD, FLOW_SPEC_ACTION_ACCEPT, FLOW_SPEC_ACTION_RATE_LIMIT, // TBD }; enum bgp_flow_spec_protocol_t { FLOW_SPEC_PROTOCOL_UDP, FLOW_SPEC_PROTOCOL_TCP, FLOW_SPEC_PROTOCOL_ICMP, }; // Enable custom casts from our own types std::ostream &operator<<(std::ostream &os, bgp_flow_spec_protocol_t const &protocol) { if (protocol == FLOW_SPEC_PROTOCOL_UDP) { return os << "udp"; } else if (protocol == FLOW_SPEC_PROTOCOL_TCP) { return os << "tcp"; } else if (protocol == FLOW_SPEC_PROTOCOL_ICMP) { return os << "icmp"; } else { return os; } } std::ostream &operator<<(std::ostream &os, flow_spec_fragmentation_types_t const &fragment_flag) { // Nice docs here: https://github.com/Exa-Networks/exabgp/blob/71157d560096ec20084cf96cfe0f60203721e93b/lib/exabgp/protocol/ip/fragment.py if (fragment_flag == FLOW_SPEC_DONT_FRAGMENT) { return os << "dont-fragment"; } else if (fragment_flag == FLOW_SPEC_IS_A_FRAGMENT) { return os << "is-fragment"; } else if (fragment_flag == FLOW_SPEC_FIRST_FRAGMENT) { return os << "first-fragment"; } else if (fragment_flag == FLOW_SPEC_LAST_FRAGMENT) { return os << "last-fragment"; } else if (fragment_flag == FLOW_NOT_A_FRAGMENT) { return os << "not-a-fragment"; } else { return os; } } std::ostream &operator<<(std::ostream &os, flow_spec_tcp_flags_t const &tcp_flag) { if (tcp_flag == FLOW_TCP_FLAG_SYN) { return os << "syn"; } else if (tcp_flag == FLOW_TCP_FLAG_ACK) { return os << "ack"; } else if (tcp_flag == FLOW_TCP_FLAG_FIN) { return os << "fin"; } else if (tcp_flag == FLOW_TCP_FLAG_URG) { return os << "urgent"; } else if (tcp_flag == FLOW_TCP_FLAG_PSH) { return os << "push"; } else if(tcp_flag == FLOW_TCP_FLAG_RST) { return os << "rst"; } else { return os; } } class bgp_flow_spec_action_t { public: bgp_flow_spec_action_t() { this->action_type = FLOW_SPEC_ACTION_ACCEPT; this->rate_limit = 9600; sentence_separator = ";"; } void set_type(bgp_flow_spec_action_types_t action_type) { this->action_type = action_type; } void set_rate_limit(unsigned int rate_limit) { this->rate_limit = rate_limit; } void set_sentence_separator(std::string sentence_separator) { this->sentence_separator = sentence_separator; } std::string serialize() { if (this->action_type == FLOW_SPEC_ACTION_ACCEPT) { return "accept" + sentence_separator; } else if (this->action_type == FLOW_SPEC_ACTION_DISCARD) { return "discard" + sentence_separator; } else if (this->action_type == FLOW_SPEC_ACTION_RATE_LIMIT) { return "rate-limit " + convert_int_to_string(this->rate_limit) + sentence_separator; } } private: bgp_flow_spec_action_types_t action_type; unsigned int rate_limit; std::string sentence_separator; // TBD // Community, rate-limit value }; // We do not use < and > operators at all, sorry class flow_spec_rule_t { public: flow_spec_rule_t() { // We should explidictly initialize it! source_subnet_used = false; destination_subnet_used = false; } bool announce_is_correct() { if (source_subnet_used || destination_subnet_used) { return true; } else { return false; } } void set_source_subnet(subnet_t source_subnet) { this->source_subnet = source_subnet; this->source_subnet_used = true; } void set_destination_subnet(subnet_t destination_subnet) { this->destination_subnet = destination_subnet; this->destination_subnet_used = true; } void add_source_port(uint16_t source_port) { this->source_ports.push_back(source_port); } void add_destination_port(uint16_t destination_port) { this->destination_ports.push_back(destination_port); } void add_packet_length(uint16_t packet_length) { this->packet_lengths.push_back(packet_length); } void add_protocol(bgp_flow_spec_protocol_t protocol) { this->protocols.push_back(protocol); } /* std::string icmp_flags; bool icmp_flags_used; std::string icmp_type; bool icmp_type_used; std::string dscp; bool dscp_used; */ void add_fragmentation_flag(flow_spec_fragmentation_types_t flag) { this->fragmentation_flags.push_back(flag); } void add_tcp_flag(flow_spec_tcp_flags_t flag) { this->tcp_flags.push_back(flag); } void set_action(bgp_flow_spec_action_t action) { this->action = action; } protected: // Only IPv4 supported subnet_t source_subnet; bool source_subnet_used; subnet_t destination_subnet; bool destination_subnet_used; std::vector source_ports; std::vector destination_ports; std::vector packet_lengths; std::vector protocols; std::vector fragmentation_flags; std::vector tcp_flags; bgp_flow_spec_action_t action; }; class exabgp_flow_spec_rule_t : public flow_spec_rule_t { public: exabgp_flow_spec_rule_t() { four_spaces = " "; sentence_separator = ";"; this->enabled_indents = true; this->enble_block_headers = true; } void disable_indents() { enabled_indents = false; } std::string serialize_source_ports() { std::ostringstream output_buffer; output_buffer << "source-port [ " << serialize_vector_by_string_with_prefix(this->source_ports, " ", "=") << " ]" << sentence_separator; return output_buffer.str(); } std::string serialize_destination_ports() { std::ostringstream output_buffer; output_buffer << "destination-port [ " << serialize_vector_by_string_with_prefix(this->destination_ports, " ", "=") << " ]" << sentence_separator; return output_buffer.str(); } std::string serialize_packet_lengths() { std::ostringstream output_buffer; output_buffer << "packet-length [ " << serialize_vector_by_string_with_prefix(this->packet_lengths, " ", "=") << " ]" << sentence_separator; return output_buffer.str(); } std::string serialize_protocols() { std::ostringstream output_buffer; output_buffer << "protocol [ " << serialize_vector_by_string(this->protocols, " ") << " ]" << sentence_separator; return output_buffer.str(); } std::string serialize_fragmentation_flags() { std::ostringstream output_buffer; output_buffer << "fragment [ " << serialize_vector_by_string(this->fragmentation_flags, " ") << " ]" << sentence_separator; return output_buffer.str(); } std::string serialize_tcp_flags() { std::ostringstream output_buffer; output_buffer << "tcp-flags [ " << serialize_vector_by_string(this->tcp_flags, " ") << " ]" << sentence_separator; return output_buffer.str(); } std::string serialize_source_subnet() { return "source " + convert_subnet_to_string(this->source_subnet) + sentence_separator; } std::string serialize_destination_subnet() { return "destination " + convert_subnet_to_string(this->destination_subnet) + sentence_separator; } // More details regarding format: https://github.com/Exa-Networks/exabgp/blob/master/qa/conf/api-flow.run // https://plus.google.com/+ThomasMangin/posts/bL6w16BXcJ4 // This format is INCOMPATIBLE with ExaBGP v3, please be careful! std::string serialize_single_line_exabgp_v4_configuration() { this->enabled_indents = false; this->enble_block_headers = false; sentence_separator = " "; return "flow route " + this->serialize_match() + this->serialize_then(); sentence_separator = ";"; this->enabled_indents = true; this->enble_block_headers = true; } std::string serialize_complete_exabgp_configuration() { std::ostringstream buffer; buffer << "neighbor 127.0.0.1 {" << "\n" << four_spaces << "router-id 1.2.3.4;" << "\n" << four_spaces << "local-address 127.0.0.1;" << "\n" << four_spaces << "local-as 1;" << "\n" << four_spaces << "peer-as 1;" << "\n" << four_spaces << "group-updates false;" << "\n\n"; buffer << four_spaces << "family {" << "\n" << four_spaces << four_spaces << "ipv4 flow;" << "\n" << four_spaces << four_spaces << "ipv6 flow;" << "\n" << four_spaces << "}" << "\n"; buffer << "flow {" << "\n"; buffer << this->serialize(); buffer << "}" << "\n"; buffer << "}" << "\n"; return buffer.str(); } std::string serialize() { std::ostringstream buffer; buffer << "route {"; if (enabled_indents) { buffer << "\n"; } buffer << this->serialize_match(); buffer << this->serialize_then(); if (enabled_indents) { buffer << "\n"; } buffer << "}"; if (enabled_indents) { buffer << "\n"; } return buffer.str(); } std::string serialize_match() { std::ostringstream buffer; if (enabled_indents) { buffer << four_spaces; } if (enble_block_headers) { buffer << "match {"; } if (enabled_indents) { buffer << "\n"; } // Match block if (this->source_subnet_used) { if (enabled_indents) { buffer << four_spaces << four_spaces; } buffer << serialize_source_subnet(); if (enabled_indents) { buffer << "\n"; } } if (this->destination_subnet_used) { if (enabled_indents) { buffer << four_spaces << four_spaces; } buffer << serialize_destination_subnet(); if (enabled_indents) { buffer << "\n"; } } if (!this->protocols.empty()) { if (enabled_indents) { buffer << four_spaces << four_spaces; } buffer << this->serialize_protocols(); if (enabled_indents) { buffer << "\n"; } } // If we have TCP in protocols list explicitly, we add flags if (find(this->protocols.begin(), this->protocols.end(), FLOW_SPEC_PROTOCOL_TCP) != this->protocols.end() ) { if (!this->tcp_flags.empty()) { if (enabled_indents) { buffer << four_spaces << four_spaces; } buffer << this->serialize_tcp_flags(); if (enabled_indents) { buffer << "\n"; } } } if (!this->source_ports.empty()) { if (enabled_indents) { buffer << four_spaces << four_spaces; } buffer << this->serialize_source_ports(); if (enabled_indents) { buffer << "\n"; } } if (!this->destination_ports.empty()) { if (enabled_indents) { buffer << four_spaces << four_spaces; } buffer << this->serialize_destination_ports(); if (enabled_indents) { buffer << "\n"; } } if (!this->packet_lengths.empty()) { if (enabled_indents) { buffer << four_spaces << four_spaces; } buffer << this->serialize_packet_lengths(); if (enabled_indents) { buffer << "\n"; } } if (!this->fragmentation_flags.empty()) { if (enabled_indents) { buffer << four_spaces << four_spaces; } buffer << this->serialize_fragmentation_flags(); if (enabled_indents) { buffer << "\n"; } } // Match block end if (enabled_indents) { buffer << four_spaces; } if (enble_block_headers) { buffer << "}"; } return buffer.str(); } std::string serialize_then() { std::ostringstream buffer; if (enabled_indents) { buffer << "\n" << four_spaces; } if (enble_block_headers) { buffer << "then {"; } if (enabled_indents) { buffer << "\n"; buffer << four_spaces << four_spaces; } // Set same sentence separator as in main class this->action.set_sentence_separator(this->sentence_separator); buffer << this->action.serialize(); if (enabled_indents) { buffer << "\n"; buffer << four_spaces; } if (enble_block_headers) { buffer << "}"; } return buffer.str(); } private: std::string four_spaces; bool enabled_indents; bool enble_block_headers; std::string sentence_separator; }; void exabgp_flow_spec_rule_ban_manage(std::string action, flow_spec_rule_t flow_spec_rule) { // "announce flow route {\\n match {\\n source 10.0.0.1/32;\\nsource-port =" + str(i) + // ";\\n destination 1.2.3.4/32;\\n }\\n then {\\n discard;\\n }\\n }\\n\n") } #endif fastnetmon-1.1.3+dfsg/src/ci/000077500000000000000000000000001313534057500160015ustar00rootroot00000000000000fastnetmon-1.1.3+dfsg/src/ci/parallel_builder.go000066400000000000000000000153071313534057500216400ustar00rootroot00000000000000package main import "fmt" import "sync" import "os" import "bytes" import "os/exec" import "regexp" import "log" import "strconv" import "io/ioutil" // In this folder we will store all results of our work var target_directory = "" var public_key_path = "/root/.ssh/id_rsa.pub" var container_private_path = "/vz_zram/private" /* Download all images vztmpl-dl --update centos-6-x86_64 centos-6-x86 centos-7-x86_64 debian-7.0-x86 debian-7.0-x86_64 debian-8.0-x86_64 ubuntu-12.04-x86 ubuntu-12.04-x86_64 ubuntu-14.04-x86 ubuntu-14.04-x86_64 debian-6.0-x86_64 Please configure NAT before: Fix /etc/modprobe.d/openvz.conf to following content: options nf_conntrack ip_conntrack_disable_ve0=0 vim /etc/sysct.conf Uncomment: net.ipv4.ip_forward=1 sysctl -p iptables -t nat -A POSTROUTING -s 10.10.10.1/24 -o eth0 -j SNAT --to 192.168.0.241 Save iptables config /etc/init.d/iptables save Enable iptables: chkconfig iptables on Generate ssh key: ssh-keygen -t rsa -q -f /root/.ssh/id_rsa -P "" Disable quotas: vim /etc/vz/vz.conf # Disable quota DISK_QUOTA=no Create zram disk for build speedup: modprobe zram num_devices=1 echo $((20*1024*1024*1024)) > /sys/block/zram0/disksize mkdir /vz_zram mkfs.ext4 /dev/zram0 mount /dev/zram0 /vz_zram mkdir /vz_zram/private */ /* For renaming of result packages you could use: find -type f| perl -e 'do{ chomp; my @m=split "/", $_; my @n = split /\./, $_; rename($_, "fastnetmon-git-447aa5b86bb5a248e310c15a4d5945e72594d6cf-$m[1]_x86_64.$n[-1]"); } for <>' */ var distros_x86_64 = []string{ "centos-6-x86_64", "centos-7-x86_64", "debian-6.0-x86_64", "debian-7.0-x86_64", "debian-8.0-x86_64", "ubuntu-12.04-x86_64", "ubuntu-14.04-x86_64" } var distros_x86 = []string{ "centos-6-x86", "debian-6.0-x86", "debian-7.0-x86", "ubuntu-12.04-x86", "ubuntu-14.04-x86" } var start_ctid_number = 1000 func main() { target_directory, err := ioutil.TempDir("/root", "builded_packages") if err != nil { log.Fatal("Can't create temp folder", err) } fmt.Println("We will store result data to folder", target_directory) _ = distros_x86 var wg sync.WaitGroup if _, err := os.Stat(public_key_path); os.IsNotExist(err) { log.Fatal("Please generate ssh keys for root here") } for element_number, distro := range distros_x86_64 { // Increment the WaitGroup counter. wg.Add(1) go func(position int, distribution_name string) { // Decrement the counter when the goroutine completes. defer wg.Done() ip_address := fmt.Sprintf("10.10.10.%d", position) ctid := start_ctid_number + position ctid_as_string := strconv.Itoa(ctid) vzctl_create_as_string := fmt.Sprintf("create %d --ostemplate %s --config vswap-4g --layout simfs --ipadd %s --diskspace 20G --hostname ct%d.test.com --private %s/%d", ctid, distribution_name, ip_address, ctid, container_private_path, ctid) r := regexp.MustCompile("[^\\s]+") vzctl_create_as_list := r.FindAllString(vzctl_create_as_string, -1) fmt.Println("Create container ", ctid_as_string) create_cmd := exec.Command("/usr/sbin/vzctl", vzctl_create_as_list...) //cmd.Stdout = os.Stdout //cmd.Stderr = os.Stderr err := create_cmd.Run() if err != nil { log.Println("create failed") log.Fatal(err) } // Run it fmt.Println("Start container ", ctid_as_string) // We whould wait here for full CT startup start_cmd := exec.Command("/usr/sbin/vzctl", "start", ctid_as_string, "--wait"); // start_cmd.Stdout = os.Stdout // start_cmd.Stderr = os.Stderr err = start_cmd.Run() if err != nil { log.Println("start failed") log.Fatal(err) } vzroot_path := fmt.Sprintf("/vz/root/%d", ctid) auth_keys_path := vzroot_path + "/root/.ssh/authorized_keys" os.Mkdir(vzroot_path + "/root/.ssh", 0600) copy_key_command := exec.Command("cp", public_key_path, auth_keys_path) copy_key_command.Run() if err != nil { log.Println("Can't copy ssh keys to container") log.Fatal(err) } os.Chmod(auth_keys_path, 0400) wget_installer_cmd := exec.Command("wget", "--no-check-certificate", "https://raw.githubusercontent.com/FastVPSEestiOu/fastnetmon/master/src/fastnetmon_install.pl", "-O" + vzroot_path + "/root/fastnetmon_install.pl") wget_installer_cmd.Run() if err != nil { log.Println("Can't download FastNetMon installer to container") log.Fatal(err) } // Remove ssh known hosst file because in other case ssh will fail os.Remove("/root/.ssh/known_hosts") // perl /root/fastnetmon_install.pl --use-git-master --create-binary-bundle --build-binary-environment" // install_cmd := exec.Command("ssh", "-lroot", ip_address, "perl", "/root/fastnetmon_install.pl") install_cmd := exec.Command("ssh", "-o", "UserKnownHostsFile=/dev/null", "-o", "StrictHostKeyChecking=no", "-lroot", ip_address, "perl", "/root/fastnetmon_install.pl", "--use-git-master", "--create-binary-bundle", "--build-binary-environment") var stdout_output bytes.Buffer var stderr_output bytes.Buffer install_cmd.Stdout = &stdout_output install_cmd.Stderr = &stderr_output install_cmd.Run() fmt.Println("Command call on " + distribution_name + " finished") fmt.Println("stdout") fmt.Println(stdout_output.String()) fmt.Println("stderr") fmt.Println(stderr_output.String()) fmt.Println("Get produced data from container to host system") copy_cmd := exec.Command("cp", "-rf", "/vz/root/" + ctid_as_string + "/tmp/result_data", target_directory + "/" + distribution_name) copy_cmd.Run() // Stop it fmt.Println("Stop container ", ctid_as_string) stop_cmd := exec.Command("/usr/sbin/vzctl", "stop", ctid_as_string); err = stop_cmd.Run() if err != nil { log.Println("stop failed") log.Fatal(err) } fmt.Println("Destroy container ", ctid_as_string) destroy_cmd := exec.Command("/usr/sbin/vzctl", "destroy", ctid_as_string); err = destroy_cmd.Run() if err != nil { log.Println("destroy failed") log.Fatal(err) } } (element_number, distro) } wg.Wait() } fastnetmon-1.1.3+dfsg/src/concurrentqueue.h000066400000000000000000004252031313534057500210140ustar00rootroot00000000000000// Provides a C++11 implementation of a multi-producer, multi-consumer lock-free queue. // An overview, including benchmark results, is provided here: // http://moodycamel.com/blog/2014/a-fast-general-purpose-lock-free-queue-for-c++ // The full design is also described in excruciating detail at: // http://moodycamel.com/blog/2014/detailed-design-of-a-lock-free-queue // Simplified BSD license: // Copyright (c) 2013-2015, Cameron Desrochers. // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: // // - Redistributions of source code must retain the above copyright notice, this list of // conditions and the following disclaimer. // - Redistributions in binary form must reproduce the above copyright notice, this list of // conditions and the following disclaimer in the documentation and/or other materials // provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 COPYRIGHT HOLDER 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. #pragma once #if defined(__GNUC__) // Disable -Wconversion warnings (spuriously triggered when Traits::size_t and // Traits::index_t are set to < 32 bits, causing integer promotion, causing warnings // upon assigning any computed values) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wconversion" #ifdef MCDBGQ_USE_RELACY #pragma GCC diagnostic ignored "-Wint-to-pointer-cast" #endif #endif #ifdef MCDBGQ_USE_RELACY #include "relacy/relacy_std.hpp" #include "relacy_shims.h" // We only use malloc/free anyway, and the delete macro messes up `= delete` method declarations. // We'll override the default trait malloc ourselves without a macro. #undef new #undef delete #undef malloc #undef free #else #include // Requires C++11. Sorry VS2010. #include #endif #include #include #include #include #include #include #include // for CHAR_BIT #include #include // for __WINPTHREADS_VERSION if on MinGW-w64 w/ POSIX threading // Platform-specific definitions of a numeric thread ID type and an invalid value #if defined(MCDBGQ_USE_RELACY) namespace moodycamel { namespace details { typedef std::uint32_t thread_id_t; static const thread_id_t invalid_thread_id = 0xFFFFFFFFU; static const thread_id_t invalid_thread_id2 = 0xFFFFFFFEU; static inline thread_id_t thread_id() { return rl::thread_index(); } } } #elif defined(_WIN32) || defined(__WINDOWS__) || defined(__WIN32__) // No sense pulling in windows.h in a header, we'll manually declare the function // we use and rely on backwards-compatibility for this not to break extern "C" __declspec(dllimport) unsigned long __stdcall GetCurrentThreadId(void); namespace moodycamel { namespace details { static_assert(sizeof(unsigned long) == sizeof(std::uint32_t), "Expected size of unsigned long to be 32 bits on Windows"); typedef std::uint32_t thread_id_t; static const thread_id_t invalid_thread_id = 0; // See http://blogs.msdn.com/b/oldnewthing/archive/2004/02/23/78395.aspx static const thread_id_t invalid_thread_id2 = 0xFFFFFFFFU; // Not technically guaranteed to be invalid, but is never used in practice. Note that all Win32 thread IDs are presently multiples of 4. static inline thread_id_t thread_id() { return static_cast(::GetCurrentThreadId()); } } } #else // Use a nice trick from this answer: http://stackoverflow.com/a/8438730/21475 // In order to get a numeric thread ID in a platform-independent way, we use a thread-local // static variable's address as a thread identifier :-) #if defined(__GNUC__) || defined(__INTEL_COMPILER) #define MOODYCAMEL_THREADLOCAL __thread #elif defined(_MSC_VER) #define MOODYCAMEL_THREADLOCAL __declspec(thread) #else // Assume C++11 compliant compiler #define MOODYCAMEL_THREADLOCAL thread_local #endif namespace moodycamel { namespace details { typedef std::uintptr_t thread_id_t; static const thread_id_t invalid_thread_id = 0; // Address can't be nullptr static const thread_id_t invalid_thread_id2 = 1; // Member accesses off a null pointer are also generally invalid. Plus it's not aligned. static inline thread_id_t thread_id() { static MOODYCAMEL_THREADLOCAL int x; return reinterpret_cast(&x); } } } #endif // Exceptions #ifndef MOODYCAMEL_EXCEPTIONS_ENABLED #if (defined(_MSC_VER) && defined(_CPPUNWIND)) || (defined(__GNUC__) && defined(__EXCEPTIONS)) || (!defined(_MSC_VER) && !defined(__GNUC__)) #define MOODYCAMEL_EXCEPTIONS_ENABLED #define MOODYCAMEL_TRY try #define MOODYCAMEL_CATCH(...) catch(__VA_ARGS__) #define MOODYCAMEL_RETHROW throw #define MOODYCAMEL_THROW(expr) throw (expr) #else #define MOODYCAMEL_TRY if (true) #define MOODYCAMEL_CATCH(...) else if (false) #define MOODYCAMEL_RETHROW #define MOODYCAMEL_THROW(expr) #endif #endif #ifndef MOODYCAMEL_NOEXCEPT #if !defined(MOODYCAMEL_EXCEPTIONS_ENABLED) #define MOODYCAMEL_NOEXCEPT #define MOODYCAMEL_NOEXCEPT_CTOR(type, valueType, expr) true #define MOODYCAMEL_NOEXCEPT_ASSIGN(type, valueType, expr) true #elif defined(_MSC_VER) && defined(_NOEXCEPT) && _MSC_VER < 1800 // VS2012's std::is_nothrow_[move_]constructible is broken and returns true when it shouldn't :-( // We have to assume *all* non-trivial constructors may throw on VS2012! #define MOODYCAMEL_NOEXCEPT _NOEXCEPT #define MOODYCAMEL_NOEXCEPT_CTOR(type, valueType, expr) (std::is_rvalue_reference::value && std::is_move_constructible::value ? std::is_trivially_move_constructible::value : std::is_trivially_copy_constructible::value) #define MOODYCAMEL_NOEXCEPT_ASSIGN(type, valueType, expr) ((std::is_rvalue_reference::value && std::is_move_assignable::value ? std::is_trivially_move_assignable::value || std::is_nothrow_move_assignable::value : std::is_trivially_copy_assignable::value || std::is_nothrow_copy_assignable::value) && MOODYCAMEL_NOEXCEPT_CTOR(type, valueType, expr)) #elif defined(_MSC_VER) && defined(_NOEXCEPT) && _MSC_VER < 1900 #define MOODYCAMEL_NOEXCEPT _NOEXCEPT #define MOODYCAMEL_NOEXCEPT_CTOR(type, valueType, expr) (std::is_rvalue_reference::value && std::is_move_constructible::value ? std::is_trivially_move_constructible::value || std::is_nothrow_move_constructible::value : std::is_trivially_copy_constructible::value || std::is_nothrow_copy_constructible::value) #define MOODYCAMEL_NOEXCEPT_ASSIGN(type, valueType, expr) ((std::is_rvalue_reference::value && std::is_move_assignable::value ? std::is_trivially_move_assignable::value || std::is_nothrow_move_assignable::value : std::is_trivially_copy_assignable::value || std::is_nothrow_copy_assignable::value) && MOODYCAMEL_NOEXCEPT_CTOR(type, valueType, expr)) #else #define MOODYCAMEL_NOEXCEPT noexcept #define MOODYCAMEL_NOEXCEPT_CTOR(type, valueType, expr) noexcept(expr) #define MOODYCAMEL_NOEXCEPT_ASSIGN(type, valueType, expr) noexcept(expr) #endif #endif #ifndef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED #ifdef MCDBGQ_USE_RELACY #define MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED #else //// VS2013 doesn't support `thread_local`, and MinGW-w64 w/ POSIX threading has a crippling bug: http://sourceforge.net/p/mingw-w64/bugs/445 //// g++ <=4.7 doesn't support thread_local either //#if (!defined(_MSC_VER) || _MSC_VER >= 1900) && (!defined(__MINGW32__) && !defined(__MINGW64__) || !defined(__WINPTHREADS_VERSION)) && (!defined(__GNUC__) || __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8)) //// Assume `thread_local` is fully supported in all other C++11 compilers/runtimes //#define MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED //#endif #endif #endif // VS2012 doesn't support deleted functions. // In this case, we declare the function normally but don't define it. A link error will be generated if the function is called. #ifndef MOODYCAMEL_DELETE_FUNCTION #if defined(_MSC_VER) && _MSC_VER < 1800 #define MOODYCAMEL_DELETE_FUNCTION #else #define MOODYCAMEL_DELETE_FUNCTION = delete #endif #endif // Compiler-specific likely/unlikely hints namespace moodycamel { namespace details { #if defined(__GNUC__) inline bool likely(bool x) { return __builtin_expect((x), true); } inline bool unlikely(bool x) { return __builtin_expect((x), false); } #else inline bool likely(bool x) { return x; } inline bool unlikely(bool x) { return x; } #endif } } #ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG #include "internal/concurrentqueue_internal_debug.h" #endif namespace moodycamel { namespace details { template struct const_numeric_max { static_assert(std::is_integral::value, "const_numeric_max can only be used with integers"); static const T value = std::numeric_limits::is_signed ? (static_cast(1) << (sizeof(T) * CHAR_BIT - 1)) - static_cast(1) : static_cast(-1); }; } // Default traits for the ConcurrentQueue. To change some of the // traits without re-implementing all of them, inherit from this // struct and shadow the declarations you wish to be different; // since the traits are used as a template type parameter, the // shadowed declarations will be used where defined, and the defaults // otherwise. struct ConcurrentQueueDefaultTraits { // General-purpose size type. std::size_t is strongly recommended. typedef std::size_t size_t; // The type used for the enqueue and dequeue indices. Must be at least as // large as size_t. Should be significantly larger than the number of elements // you expect to hold at once, especially if you have a high turnover rate; // for example, on 32-bit x86, if you expect to have over a hundred million // elements or pump several million elements through your queue in a very // short space of time, using a 32-bit type *may* trigger a race condition. // A 64-bit int type is recommended in that case, and in practice will // prevent a race condition no matter the usage of the queue. Note that // whether the queue is lock-free with a 64-int type depends on the whether // std::atomic is lock-free, which is platform-specific. typedef std::size_t index_t; // Internally, all elements are enqueued and dequeued from multi-element // blocks; this is the smallest controllable unit. If you expect few elements // but many producers, a smaller block size should be favoured. For few producers // and/or many elements, a larger block size is preferred. A sane default // is provided. Must be a power of 2. static const size_t BLOCK_SIZE = 32; // For explicit producers (i.e. when using a producer token), the block is // checked for being empty by iterating through a list of flags, one per element. // For large block sizes, this is too inefficient, and switching to an atomic // counter-based approach is faster. The switch is made for block sizes strictly // larger than this threshold. static const size_t EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD = 32; // How many full blocks can be expected for a single explicit producer? This should // reflect that number's maximum for optimal performance. Must be a power of 2. static const size_t EXPLICIT_INITIAL_INDEX_SIZE = 32; // How many full blocks can be expected for a single implicit producer? This should // reflect that number's maximum for optimal performance. Must be a power of 2. static const size_t IMPLICIT_INITIAL_INDEX_SIZE = 32; // The initial size of the hash table mapping thread IDs to implicit producers. // Note that the hash is resized every time it becomes half full. // Must be a power of two, and either 0 or at least 1. If 0, implicit production // (using the enqueue methods without an explicit producer token) is disabled. static const size_t INITIAL_IMPLICIT_PRODUCER_HASH_SIZE = 32; // Controls the number of items that an explicit consumer (i.e. one with a token) // must consume before it causes all consumers to rotate and move on to the next // internal queue. static const std::uint32_t EXPLICIT_CONSUMER_CONSUMPTION_QUOTA_BEFORE_ROTATE = 256; // The maximum number of elements (inclusive) that can be enqueued to a sub-queue. // Enqueue operations that would cause this limit to be surpassed will fail. Note // that this limit is enforced at the block level (for performance reasons), i.e. // it's rounded up to the nearest block size. static const size_t MAX_SUBQUEUE_SIZE = details::const_numeric_max::value; #ifndef MCDBGQ_USE_RELACY // Memory allocation can be customized if needed. // malloc should return nullptr on failure, and handle alignment like std::malloc. static inline void* malloc(size_t size) { return std::malloc(size); } static inline void free(void* ptr) { return std::free(ptr); } #else // Debug versions when running under the Relacy race detector (ignore // these in user code) static inline void* malloc(size_t size) { return rl::rl_malloc(size, $); } static inline void free(void* ptr) { return rl::rl_free(ptr, $); } #endif }; // When producing or consuming many elements, the most efficient way is to: // 1) Use one of the bulk-operation methods of the queue with a token // 2) Failing that, use the bulk-operation methods without a token // 3) Failing that, create a token and use that with the single-item methods // 4) Failing that, use the single-parameter methods of the queue // Having said that, don't create tokens willy-nilly -- ideally there should be // a maximum of one token per thread (of each kind). struct ProducerToken; struct ConsumerToken; template class ConcurrentQueue; template class BlockingConcurrentQueue; class ConcurrentQueueTests; namespace details { struct ConcurrentQueueProducerTypelessBase { ConcurrentQueueProducerTypelessBase* next; std::atomic inactive; ProducerToken* token; ConcurrentQueueProducerTypelessBase() : inactive(false), token(nullptr) { } }; template struct _hash_32_or_64 { static inline std::uint32_t hash(std::uint32_t h) { // MurmurHash3 finalizer -- see https://code.google.com/p/smhasher/source/browse/trunk/MurmurHash3.cpp // Since the thread ID is already unique, all we really want to do is propagate that // uniqueness evenly across all the bits, so that we can use a subset of the bits while // reducing collisions significantly h ^= h >> 16; h *= 0x85ebca6b; h ^= h >> 13; h *= 0xc2b2ae35; return h ^ (h >> 16); } }; template<> struct _hash_32_or_64<1> { static inline std::uint64_t hash(std::uint64_t h) { h ^= h >> 33; h *= 0xff51afd7ed558ccd; h ^= h >> 33; h *= 0xc4ceb9fe1a85ec53; return h ^ (h >> 33); } }; template struct hash_32_or_64 : public _hash_32_or_64<(size > 4)> { }; static inline size_t hash_thread_id(thread_id_t id) { static_assert(sizeof(thread_id_t) <= 8, "Expected a platform where thread IDs are at most 64-bit values"); return static_cast(hash_32_or_64::hash(id)); } template static inline bool circular_less_than(T a, T b) { #ifdef _MSC_VER #pragma warning(push) #pragma warning(disable: 4554) #endif static_assert(std::is_integral::value && !std::numeric_limits::is_signed, "circular_less_than is intended to be used only with unsigned integer types"); return static_cast(a - b) > static_cast(static_cast(1) << static_cast(sizeof(T) * CHAR_BIT - 1)); #ifdef _MSC_VER #pragma warning(pop) #endif } template static inline char* align_for(char* ptr) { const std::size_t alignment = std::alignment_of::value; return ptr + (alignment - (reinterpret_cast(ptr) % alignment)) % alignment; } template static inline T ceil_to_pow_2(T x) { static_assert(std::is_integral::value && !std::numeric_limits::is_signed, "ceil_to_pow_2 is intended to be used only with unsigned integer types"); // Adapted from http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2 --x; x |= x >> 1; x |= x >> 2; x |= x >> 4; for (std::size_t i = 1; i < sizeof(T); i <<= 1) { x |= x >> (i << 3); } ++x; return x; } template static inline void swap_relaxed(std::atomic& left, std::atomic& right) { T temp = std::move(left.load(std::memory_order_relaxed)); left.store(std::move(right.load(std::memory_order_relaxed)), std::memory_order_relaxed); right.store(std::move(temp), std::memory_order_relaxed); } template static inline T const& nomove(T const& x) { return x; } template struct nomove_if { template static inline T const& eval(T const& x) { return x; } }; template<> struct nomove_if { template static inline auto eval(U&& x) -> decltype(std::forward(x)) { return std::forward(x); } }; template static inline auto deref_noexcept(It& it) MOODYCAMEL_NOEXCEPT -> decltype(*it) { return *it; } #if defined(__APPLE__) || defined(__clang__) || !defined(__GNUC__) || __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8) template struct is_trivially_destructible : std::is_trivially_destructible { }; #else template struct is_trivially_destructible : std::has_trivial_destructor { }; #endif #ifdef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED #ifdef MCDBGQ_USE_RELACY typedef RelacyThreadExitListener ThreadExitListener; typedef RelacyThreadExitNotifier ThreadExitNotifier; #else struct ThreadExitListener { typedef void (*callback_t)(void*); callback_t callback; void* userData; ThreadExitListener* next; // reserved for use by the ThreadExitNotifier }; class ThreadExitNotifier { public: static void subscribe(ThreadExitListener* listener) { auto& tlsInst = instance(); listener->next = tlsInst.tail; tlsInst.tail = listener; } static void unsubscribe(ThreadExitListener* listener) { auto& tlsInst = instance(); ThreadExitListener** prev = &tlsInst.tail; for (auto ptr = tlsInst.tail; ptr != nullptr; ptr = ptr->next) { if (ptr == listener) { *prev = ptr->next; break; } prev = &ptr->next; } } private: ThreadExitNotifier() : tail(nullptr) { } ThreadExitNotifier(ThreadExitNotifier const&) MOODYCAMEL_DELETE_FUNCTION; ThreadExitNotifier& operator=(ThreadExitNotifier const&) MOODYCAMEL_DELETE_FUNCTION; ~ThreadExitNotifier() { // This thread is about to exit, let everyone know! assert(this == &instance() && "If this assert fails, you likely have a buggy compiler! Change the preprocessor conditions such that MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED is no longer defined."); for (auto ptr = tail; ptr != nullptr; ptr = ptr->next) { ptr->callback(ptr->userData); } } // Thread-local static inline ThreadExitNotifier& instance() { static thread_local ThreadExitNotifier notifier; return notifier; } private: ThreadExitListener* tail; }; #endif #endif template struct static_is_lock_free_num { enum { value = 0 }; }; template<> struct static_is_lock_free_num { enum { value = ATOMIC_CHAR_LOCK_FREE }; }; template<> struct static_is_lock_free_num { enum { value = ATOMIC_SHORT_LOCK_FREE }; }; template<> struct static_is_lock_free_num { enum { value = ATOMIC_INT_LOCK_FREE }; }; template<> struct static_is_lock_free_num { enum { value = ATOMIC_LONG_LOCK_FREE }; }; template<> struct static_is_lock_free_num { enum { value = ATOMIC_LLONG_LOCK_FREE }; }; template struct static_is_lock_free : static_is_lock_free_num::type> { }; template<> struct static_is_lock_free { enum { value = ATOMIC_BOOL_LOCK_FREE }; }; template struct static_is_lock_free { enum { value = ATOMIC_POINTER_LOCK_FREE }; }; } struct ProducerToken { template explicit ProducerToken(ConcurrentQueue& queue); template explicit ProducerToken(BlockingConcurrentQueue& queue); explicit ProducerToken(ProducerToken&& other) MOODYCAMEL_NOEXCEPT : producer(other.producer) { other.producer = nullptr; if (producer != nullptr) { producer->token = this; } } inline ProducerToken& operator=(ProducerToken&& other) MOODYCAMEL_NOEXCEPT { swap(other); return *this; } void swap(ProducerToken& other) MOODYCAMEL_NOEXCEPT { std::swap(producer, other.producer); if (producer != nullptr) { producer->token = this; } if (other.producer != nullptr) { other.producer->token = &other; } } // A token is always valid unless: // 1) Memory allocation failed during construction // 2) It was moved via the move constructor // (Note: assignment does a swap, leaving both potentially valid) // 3) The associated queue was destroyed // Note that if valid() returns true, that only indicates // that the token is valid for use with a specific queue, // but not which one; that's up to the user to track. inline bool valid() const { return producer != nullptr; } ~ProducerToken() { if (producer != nullptr) { producer->token = nullptr; producer->inactive.store(true, std::memory_order_release); } } // Disable copying and assignment ProducerToken(ProducerToken const&) MOODYCAMEL_DELETE_FUNCTION; ProducerToken& operator=(ProducerToken const&) MOODYCAMEL_DELETE_FUNCTION; private: template friend class ConcurrentQueue; friend class ConcurrentQueueTests; protected: details::ConcurrentQueueProducerTypelessBase* producer; }; struct ConsumerToken { template explicit ConsumerToken(ConcurrentQueue& q); template explicit ConsumerToken(BlockingConcurrentQueue& q); explicit ConsumerToken(ConsumerToken&& other) MOODYCAMEL_NOEXCEPT : initialOffset(other.initialOffset), lastKnownGlobalOffset(other.lastKnownGlobalOffset), itemsConsumedFromCurrent(other.itemsConsumedFromCurrent), currentProducer(other.currentProducer), desiredProducer(other.desiredProducer) { } inline ConsumerToken& operator=(ConsumerToken&& other) MOODYCAMEL_NOEXCEPT { swap(other); return *this; } void swap(ConsumerToken& other) MOODYCAMEL_NOEXCEPT { std::swap(initialOffset, other.initialOffset); std::swap(lastKnownGlobalOffset, other.lastKnownGlobalOffset); std::swap(itemsConsumedFromCurrent, other.itemsConsumedFromCurrent); std::swap(currentProducer, other.currentProducer); std::swap(desiredProducer, other.desiredProducer); } // Disable copying and assignment ConsumerToken(ConsumerToken const&) MOODYCAMEL_DELETE_FUNCTION; ConsumerToken& operator=(ConsumerToken const&) MOODYCAMEL_DELETE_FUNCTION; private: template friend class ConcurrentQueue; friend class ConcurrentQueueTests; private: // but shared with ConcurrentQueue std::uint32_t initialOffset; std::uint32_t lastKnownGlobalOffset; std::uint32_t itemsConsumedFromCurrent; details::ConcurrentQueueProducerTypelessBase* currentProducer; details::ConcurrentQueueProducerTypelessBase* desiredProducer; }; // Need to forward-declare this swap because it's in a namespace. // See http://stackoverflow.com/questions/4492062/why-does-a-c-friend-class-need-a-forward-declaration-only-in-other-namespaces template inline void swap(typename ConcurrentQueue::ImplicitProducerKVP& a, typename ConcurrentQueue::ImplicitProducerKVP& b) MOODYCAMEL_NOEXCEPT; template class ConcurrentQueue { public: typedef ::moodycamel::ProducerToken producer_token_t; typedef ::moodycamel::ConsumerToken consumer_token_t; typedef typename Traits::index_t index_t; typedef typename Traits::size_t size_t; static const size_t BLOCK_SIZE = static_cast(Traits::BLOCK_SIZE); static const size_t EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD = static_cast(Traits::EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD); static const size_t EXPLICIT_INITIAL_INDEX_SIZE = static_cast(Traits::EXPLICIT_INITIAL_INDEX_SIZE); static const size_t IMPLICIT_INITIAL_INDEX_SIZE = static_cast(Traits::IMPLICIT_INITIAL_INDEX_SIZE); static const size_t INITIAL_IMPLICIT_PRODUCER_HASH_SIZE = static_cast(Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE); static const std::uint32_t EXPLICIT_CONSUMER_CONSUMPTION_QUOTA_BEFORE_ROTATE = static_cast(Traits::EXPLICIT_CONSUMER_CONSUMPTION_QUOTA_BEFORE_ROTATE); #ifdef _MSC_VER #pragma warning(push) #pragma warning(disable: 4307) // + integral constant overflow (that's what the ternary expression is for!) #pragma warning(disable: 4309) // static_cast: Truncation of constant value #endif static const size_t MAX_SUBQUEUE_SIZE = (details::const_numeric_max::value - static_cast(Traits::MAX_SUBQUEUE_SIZE) < BLOCK_SIZE) ? details::const_numeric_max::value : ((static_cast(Traits::MAX_SUBQUEUE_SIZE) + (BLOCK_SIZE - 1)) / BLOCK_SIZE * BLOCK_SIZE); #ifdef _MSC_VER #pragma warning(pop) #endif static_assert(!std::numeric_limits::is_signed && std::is_integral::value, "Traits::size_t must be an unsigned integral type"); static_assert(!std::numeric_limits::is_signed && std::is_integral::value, "Traits::index_t must be an unsigned integral type"); static_assert(sizeof(index_t) >= sizeof(size_t), "Traits::index_t must be at least as wide as Traits::size_t"); static_assert((BLOCK_SIZE > 1) && !(BLOCK_SIZE & (BLOCK_SIZE - 1)), "Traits::BLOCK_SIZE must be a power of 2 (and at least 2)"); static_assert((EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD > 1) && !(EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD & (EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD - 1)), "Traits::EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD must be a power of 2 (and greater than 1)"); static_assert((EXPLICIT_INITIAL_INDEX_SIZE > 1) && !(EXPLICIT_INITIAL_INDEX_SIZE & (EXPLICIT_INITIAL_INDEX_SIZE - 1)), "Traits::EXPLICIT_INITIAL_INDEX_SIZE must be a power of 2 (and greater than 1)"); static_assert((IMPLICIT_INITIAL_INDEX_SIZE > 1) && !(IMPLICIT_INITIAL_INDEX_SIZE & (IMPLICIT_INITIAL_INDEX_SIZE - 1)), "Traits::IMPLICIT_INITIAL_INDEX_SIZE must be a power of 2 (and greater than 1)"); static_assert((INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) || !(INITIAL_IMPLICIT_PRODUCER_HASH_SIZE & (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE - 1)), "Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE must be a power of 2"); static_assert(INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0 || INITIAL_IMPLICIT_PRODUCER_HASH_SIZE >= 1, "Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE must be at least 1 (or 0 to disable implicit enqueueing)"); public: // Creates a queue with at least `capacity` element slots; note that the // actual number of elements that can be inserted without additional memory // allocation depends on the number of producers and the block size (e.g. if // the block size is equal to `capacity`, only a single block will be allocated // up-front, which means only a single producer will be able to enqueue elements // without an extra allocation -- blocks aren't shared between producers). // This method is not thread safe -- it is up to the user to ensure that the // queue is fully constructed before it starts being used by other threads (this // includes making the memory effects of construction visible, possibly with a // memory barrier). explicit ConcurrentQueue(size_t capacity = 6 * BLOCK_SIZE) : producerListTail(nullptr), producerCount(0), initialBlockPoolIndex(0), nextExplicitConsumerId(0), globalExplicitConsumerOffset(0) { implicitProducerHashResizeInProgress.clear(std::memory_order_relaxed); populate_initial_implicit_producer_hash(); populate_initial_block_list(capacity / BLOCK_SIZE + ((capacity & (BLOCK_SIZE - 1)) == 0 ? 0 : 1)); #ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG // Track all the producers using a fully-resolved typed list for // each kind; this makes it possible to debug them starting from // the root queue object (otherwise wacky casts are needed that // don't compile in the debugger's expression evaluator). explicitProducers.store(nullptr, std::memory_order_relaxed); implicitProducers.store(nullptr, std::memory_order_relaxed); #endif } // Computes the correct amount of pre-allocated blocks for you based // on the minimum number of elements you want available at any given // time, and the maximum concurrent number of each type of producer. ConcurrentQueue(size_t minCapacity, size_t maxExplicitProducers, size_t maxImplicitProducers) : producerListTail(nullptr), producerCount(0), initialBlockPoolIndex(0), nextExplicitConsumerId(0), globalExplicitConsumerOffset(0) { implicitProducerHashResizeInProgress.clear(std::memory_order_relaxed); populate_initial_implicit_producer_hash(); size_t blocks = ((((minCapacity + BLOCK_SIZE - 1) / BLOCK_SIZE) - 1) * (maxExplicitProducers + 1) + 2 * (maxExplicitProducers + maxImplicitProducers)) * BLOCK_SIZE; populate_initial_block_list(blocks); #ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG explicitProducers.store(nullptr, std::memory_order_relaxed); implicitProducers.store(nullptr, std::memory_order_relaxed); #endif } // Note: The queue should not be accessed concurrently while it's // being deleted. It's up to the user to synchronize this. // This method is not thread safe. ~ConcurrentQueue() { // Destroy producers auto ptr = producerListTail.load(std::memory_order_relaxed); while (ptr != nullptr) { auto next = ptr->next_prod(); if (ptr->token != nullptr) { ptr->token->producer = nullptr; } destroy(ptr); ptr = next; } // Destroy implicit producer hash tables if (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE != 0) { auto hash = implicitProducerHash.load(std::memory_order_relaxed); while (hash != nullptr) { auto prev = hash->prev; if (prev != nullptr) { // The last hash is part of this object and was not allocated dynamically for (size_t i = 0; i != hash->capacity; ++i) { hash->entries[i].~ImplicitProducerKVP(); } hash->~ImplicitProducerHash(); Traits::free(hash); } hash = prev; } } // Destroy global free list auto block = freeList.head_unsafe(); while (block != nullptr) { auto next = block->freeListNext.load(std::memory_order_relaxed); if (block->dynamicallyAllocated) { destroy(block); } block = next; } // Destroy initial free list destroy_array(initialBlockPool, initialBlockPoolSize); } // Disable copying and copy assignment ConcurrentQueue(ConcurrentQueue const&) MOODYCAMEL_DELETE_FUNCTION; ConcurrentQueue& operator=(ConcurrentQueue const&) MOODYCAMEL_DELETE_FUNCTION; // Moving is supported, but note that it is *not* a thread-safe operation. // Nobody can use the queue while it's being moved, and the memory effects // of that move must be propagated to other threads before they can use it. // Note: When a queue is moved, its tokens are still valid but can only be // used with the destination queue (i.e. semantically they are moved along // with the queue itself). ConcurrentQueue(ConcurrentQueue&& other) MOODYCAMEL_NOEXCEPT : producerListTail(other.producerListTail.load(std::memory_order_relaxed)), producerCount(other.producerCount.load(std::memory_order_relaxed)), initialBlockPoolIndex(other.initialBlockPoolIndex.load(std::memory_order_relaxed)), initialBlockPool(other.initialBlockPool), initialBlockPoolSize(other.initialBlockPoolSize), freeList(std::move(other.freeList)), nextExplicitConsumerId(other.nextExplicitConsumerId.load(std::memory_order_relaxed)), globalExplicitConsumerOffset(other.globalExplicitConsumerOffset.load(std::memory_order_relaxed)) { // Move the other one into this, and leave the other one as an empty queue implicitProducerHashResizeInProgress.clear(std::memory_order_relaxed); populate_initial_implicit_producer_hash(); swap_implicit_producer_hashes(other); other.producerListTail.store(nullptr, std::memory_order_relaxed); other.producerCount.store(0, std::memory_order_relaxed); other.nextExplicitConsumerId.store(0, std::memory_order_relaxed); other.globalExplicitConsumerOffset.store(0, std::memory_order_relaxed); #ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG explicitProducers.store(other.explicitProducers.load(std::memory_order_relaxed), std::memory_order_relaxed); other.explicitProducers.store(nullptr, std::memory_order_relaxed); implicitProducers.store(other.implicitProducers.load(std::memory_order_relaxed), std::memory_order_relaxed); other.implicitProducers.store(nullptr, std::memory_order_relaxed); #endif other.initialBlockPoolIndex.store(0, std::memory_order_relaxed); other.initialBlockPoolSize = 0; other.initialBlockPool = nullptr; reown_producers(); } inline ConcurrentQueue& operator=(ConcurrentQueue&& other) MOODYCAMEL_NOEXCEPT { return swap_internal(other); } // Swaps this queue's state with the other's. Not thread-safe. // Swapping two queues does not invalidate their tokens, however // the tokens that were created for one queue must be used with // only the swapped queue (i.e. the tokens are tied to the // queue's movable state, not the object itself). inline void swap(ConcurrentQueue& other) MOODYCAMEL_NOEXCEPT { swap_internal(other); } private: ConcurrentQueue& swap_internal(ConcurrentQueue& other) { if (this == &other) { return *this; } details::swap_relaxed(producerListTail, other.producerListTail); details::swap_relaxed(producerCount, other.producerCount); details::swap_relaxed(initialBlockPoolIndex, other.initialBlockPoolIndex); std::swap(initialBlockPool, other.initialBlockPool); std::swap(initialBlockPoolSize, other.initialBlockPoolSize); freeList.swap(other.freeList); details::swap_relaxed(nextExplicitConsumerId, other.nextExplicitConsumerId); details::swap_relaxed(globalExplicitConsumerOffset, other.globalExplicitConsumerOffset); swap_implicit_producer_hashes(other); reown_producers(); other.reown_producers(); #ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG details::swap_relaxed(explicitProducers, other.explicitProducers); details::swap_relaxed(implicitProducers, other.implicitProducers); #endif return *this; } public: // Enqueues a single item (by copying it). // Allocates memory if required. Only fails if memory allocation fails (or implicit // production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0, // or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). // Thread-safe. inline bool enqueue(T const& item) { if (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) return false; return inner_enqueue(item); } // Enqueues a single item (by moving it, if possible). // Allocates memory if required. Only fails if memory allocation fails (or implicit // production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0, // or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). // Thread-safe. inline bool enqueue(T&& item) { if (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) return false; return inner_enqueue(std::move(item)); } // Enqueues a single item (by copying it) using an explicit producer token. // Allocates memory if required. Only fails if memory allocation fails (or // Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). // Thread-safe. inline bool enqueue(producer_token_t const& token, T const& item) { return inner_enqueue(token, item); } // Enqueues a single item (by moving it, if possible) using an explicit producer token. // Allocates memory if required. Only fails if memory allocation fails (or // Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). // Thread-safe. inline bool enqueue(producer_token_t const& token, T&& item) { return inner_enqueue(token, std::move(item)); } // Enqueues several items. // Allocates memory if required. Only fails if memory allocation fails (or // implicit production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE // is 0, or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). // Note: Use std::make_move_iterator if the elements should be moved instead of copied. // Thread-safe. template bool enqueue_bulk(It itemFirst, size_t count) { if (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) return false; return inner_enqueue_bulk(std::forward(itemFirst), count); } // Enqueues several items using an explicit producer token. // Allocates memory if required. Only fails if memory allocation fails // (or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). // Note: Use std::make_move_iterator if the elements should be moved // instead of copied. // Thread-safe. template bool enqueue_bulk(producer_token_t const& token, It itemFirst, size_t count) { return inner_enqueue_bulk(token, std::forward(itemFirst), count); } // Enqueues a single item (by copying it). // Does not allocate memory. Fails if not enough room to enqueue (or implicit // production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE // is 0). // Thread-safe. inline bool try_enqueue(T const& item) { if (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) return false; return inner_enqueue(item); } // Enqueues a single item (by moving it, if possible). // Does not allocate memory (except for one-time implicit producer). // Fails if not enough room to enqueue (or implicit production is // disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0). // Thread-safe. inline bool try_enqueue(T&& item) { if (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) return false; return inner_enqueue(std::move(item)); } // Enqueues a single item (by copying it) using an explicit producer token. // Does not allocate memory. Fails if not enough room to enqueue. // Thread-safe. inline bool try_enqueue(producer_token_t const& token, T const& item) { return inner_enqueue(token, item); } // Enqueues a single item (by moving it, if possible) using an explicit producer token. // Does not allocate memory. Fails if not enough room to enqueue. // Thread-safe. inline bool try_enqueue(producer_token_t const& token, T&& item) { return inner_enqueue(token, std::move(item)); } // Enqueues several items. // Does not allocate memory (except for one-time implicit producer). // Fails if not enough room to enqueue (or implicit production is // disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0). // Note: Use std::make_move_iterator if the elements should be moved // instead of copied. // Thread-safe. template bool try_enqueue_bulk(It itemFirst, size_t count) { if (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) return false; return inner_enqueue_bulk(std::forward(itemFirst), count); } // Enqueues several items using an explicit producer token. // Does not allocate memory. Fails if not enough room to enqueue. // Note: Use std::make_move_iterator if the elements should be moved // instead of copied. // Thread-safe. template bool try_enqueue_bulk(producer_token_t const& token, It itemFirst, size_t count) { return inner_enqueue_bulk(token, std::forward(itemFirst), count); } // Attempts to dequeue from the queue. // Returns false if all producer streams appeared empty at the time they // were checked (so, the queue is likely but not guaranteed to be empty). // Never allocates. Thread-safe. template bool try_dequeue(U& item) { // Instead of simply trying each producer in turn (which could cause needless contention on the first // producer), we score them heuristically. size_t nonEmptyCount = 0; ProducerBase* best = nullptr; size_t bestSize = 0; for (auto ptr = producerListTail.load(std::memory_order_acquire); nonEmptyCount < 3 && ptr != nullptr; ptr = ptr->next_prod()) { auto size = ptr->size_approx(); if (size > 0) { if (size > bestSize) { bestSize = size; best = ptr; } ++nonEmptyCount; } } // If there was at least one non-empty queue but it appears empty at the time // we try to dequeue from it, we need to make sure every queue's been tried if (nonEmptyCount > 0) { if (details::likely(best->dequeue(item))) { return true; } for (auto ptr = producerListTail.load(std::memory_order_acquire); ptr != nullptr; ptr = ptr->next_prod()) { if (ptr != best && ptr->dequeue(item)) { return true; } } } return false; } // Attempts to dequeue from the queue. // Returns false if all producer streams appeared empty at the time they // were checked (so, the queue is likely but not guaranteed to be empty). // This differs from the try_dequeue(item) method in that this one does // not attempt to reduce contention by interleaving the order that producer // streams are dequeued from. So, using this method can reduce overall throughput // under contention, but will give more predictable results in single-threaded // consumer scenarios. This is mostly only useful for internal unit tests. // Never allocates. Thread-safe. template bool try_dequeue_non_interleaved(U& item) { for (auto ptr = producerListTail.load(std::memory_order_acquire); ptr != nullptr; ptr = ptr->next_prod()) { if (ptr->dequeue(item)) { return true; } } return false; } // Attempts to dequeue from the queue using an explicit consumer token. // Returns false if all producer streams appeared empty at the time they // were checked (so, the queue is likely but not guaranteed to be empty). // Never allocates. Thread-safe. template bool try_dequeue(consumer_token_t& token, U& item) { // The idea is roughly as follows: // Every 256 items from one producer, make everyone rotate (increase the global offset) -> this means the highest efficiency consumer dictates the rotation speed of everyone else, more or less // If you see that the global offset has changed, you must reset your consumption counter and move to your designated place // If there's no items where you're supposed to be, keep moving until you find a producer with some items // If the global offset has not changed but you've run out of items to consume, move over from your current position until you find an producer with something in it if (token.desiredProducer == nullptr || token.lastKnownGlobalOffset != globalExplicitConsumerOffset.load(std::memory_order_relaxed)) { if (!update_current_producer_after_rotation(token)) { return false; } } // If there was at least one non-empty queue but it appears empty at the time // we try to dequeue from it, we need to make sure every queue's been tried if (static_cast(token.currentProducer)->dequeue(item)) { if (++token.itemsConsumedFromCurrent == EXPLICIT_CONSUMER_CONSUMPTION_QUOTA_BEFORE_ROTATE) { globalExplicitConsumerOffset.fetch_add(1, std::memory_order_relaxed); } return true; } auto tail = producerListTail.load(std::memory_order_acquire); auto ptr = static_cast(token.currentProducer)->next_prod(); if (ptr == nullptr) { ptr = tail; } while (ptr != static_cast(token.currentProducer)) { if (ptr->dequeue(item)) { token.currentProducer = ptr; token.itemsConsumedFromCurrent = 1; return true; } ptr = ptr->next_prod(); if (ptr == nullptr) { ptr = tail; } } return false; } // Attempts to dequeue several elements from the queue. // Returns the number of items actually dequeued. // Returns 0 if all producer streams appeared empty at the time they // were checked (so, the queue is likely but not guaranteed to be empty). // Never allocates. Thread-safe. template size_t try_dequeue_bulk(It itemFirst, size_t max) { size_t count = 0; for (auto ptr = producerListTail.load(std::memory_order_acquire); ptr != nullptr; ptr = ptr->next_prod()) { count += ptr->dequeue_bulk(itemFirst, max - count); if (count == max) { break; } } return count; } // Attempts to dequeue several elements from the queue using an explicit consumer token. // Returns the number of items actually dequeued. // Returns 0 if all producer streams appeared empty at the time they // were checked (so, the queue is likely but not guaranteed to be empty). // Never allocates. Thread-safe. template size_t try_dequeue_bulk(consumer_token_t& token, It itemFirst, size_t max) { if (token.desiredProducer == nullptr || token.lastKnownGlobalOffset != globalExplicitConsumerOffset.load(std::memory_order_relaxed)) { if (!update_current_producer_after_rotation(token)) { return false; } } size_t count = static_cast(token.currentProducer)->dequeue_bulk(itemFirst, max); if (count == max) { if ((token.itemsConsumedFromCurrent += static_cast(max)) >= EXPLICIT_CONSUMER_CONSUMPTION_QUOTA_BEFORE_ROTATE) { globalExplicitConsumerOffset.fetch_add(1, std::memory_order_relaxed); } return max; } token.itemsConsumedFromCurrent += static_cast(count); max -= count; auto tail = producerListTail.load(std::memory_order_acquire); auto ptr = static_cast(token.currentProducer)->next_prod(); if (ptr == nullptr) { ptr = tail; } while (ptr != static_cast(token.currentProducer)) { auto dequeued = ptr->dequeue_bulk(itemFirst, max); count += dequeued; if (dequeued != 0) { token.currentProducer = ptr; token.itemsConsumedFromCurrent = static_cast(dequeued); } if (dequeued == max) { break; } max -= dequeued; ptr = ptr->next_prod(); if (ptr == nullptr) { ptr = tail; } } return count; } // Attempts to dequeue from a specific producer's inner queue. // If you happen to know which producer you want to dequeue from, this // is significantly faster than using the general-case try_dequeue methods. // Returns false if the producer's queue appeared empty at the time it // was checked (so, the queue is likely but not guaranteed to be empty). // Never allocates. Thread-safe. template inline bool try_dequeue_from_producer(producer_token_t const& producer, U& item) { return static_cast(producer.producer)->dequeue(item); } // Attempts to dequeue several elements from a specific producer's inner queue. // Returns the number of items actually dequeued. // If you happen to know which producer you want to dequeue from, this // is significantly faster than using the general-case try_dequeue methods. // Returns 0 if the producer's queue appeared empty at the time it // was checked (so, the queue is likely but not guaranteed to be empty). // Never allocates. Thread-safe. template inline size_t try_dequeue_bulk_from_producer(producer_token_t const& producer, It itemFirst, size_t max) { return static_cast(producer.producer)->dequeue_bulk(itemFirst, max); } // Returns an estimate of the total number of elements currently in the queue. This // estimate is only accurate if the queue has completely stabilized before it is called // (i.e. all enqueue and dequeue operations have completed and their memory effects are // visible on the calling thread, and no further operations start while this method is // being called). // Thread-safe. size_t size_approx() const { size_t size = 0; for (auto ptr = producerListTail.load(std::memory_order_acquire); ptr != nullptr; ptr = ptr->next_prod()) { size += ptr->size_approx(); } return size; } // Returns true if the underlying atomic variables used by // the queue are lock-free (they should be on most platforms). // Thread-safe. static bool is_lock_free() { return details::static_is_lock_free::value == 2 && details::static_is_lock_free::value == 2 && details::static_is_lock_free::value == 2 && details::static_is_lock_free::value == 2 && details::static_is_lock_free::value == 2 && details::static_is_lock_free::value == 2; } private: friend struct ProducerToken; friend struct ConsumerToken; friend struct ExplicitProducer; friend class ConcurrentQueueTests; enum AllocationMode { CanAlloc, CannotAlloc }; /////////////////////////////// // Queue methods /////////////////////////////// template inline bool inner_enqueue(producer_token_t const& token, U&& element) { return static_cast(token.producer)->ConcurrentQueue::ExplicitProducer::template enqueue(std::forward(element)); } template inline bool inner_enqueue(U&& element) { auto producer = get_or_add_implicit_producer(); return producer == nullptr ? false : producer->ConcurrentQueue::ImplicitProducer::template enqueue(std::forward(element)); } template inline bool inner_enqueue_bulk(producer_token_t const& token, It itemFirst, size_t count) { return static_cast(token.producer)->ConcurrentQueue::ExplicitProducer::template enqueue_bulk(std::forward(itemFirst), count); } template inline bool inner_enqueue_bulk(It itemFirst, size_t count) { auto producer = get_or_add_implicit_producer(); return producer == nullptr ? false : producer->ConcurrentQueue::ImplicitProducer::template enqueue_bulk(std::forward(itemFirst), count); } inline bool update_current_producer_after_rotation(consumer_token_t& token) { // Ah, there's been a rotation, figure out where we should be! auto tail = producerListTail.load(std::memory_order_acquire); if (token.desiredProducer == nullptr && tail == nullptr) { return false; } auto prodCount = producerCount.load(std::memory_order_relaxed); auto globalOffset = globalExplicitConsumerOffset.load(std::memory_order_relaxed); if (details::unlikely(token.desiredProducer == nullptr)) { // Aha, first time we're dequeueing anything. // Figure out our local position // Note: offset is from start, not end, but we're traversing from end -- subtract from count first std::uint32_t offset = prodCount - 1 - (token.initialOffset % prodCount); token.desiredProducer = tail; for (std::uint32_t i = 0; i != offset; ++i) { token.desiredProducer = static_cast(token.desiredProducer)->next_prod(); if (token.desiredProducer == nullptr) { token.desiredProducer = tail; } } } std::uint32_t delta = globalOffset - token.lastKnownGlobalOffset; if (delta >= prodCount) { delta = delta % prodCount; } for (std::uint32_t i = 0; i != delta; ++i) { token.desiredProducer = static_cast(token.desiredProducer)->next_prod(); if (token.desiredProducer == nullptr) { token.desiredProducer = tail; } } token.lastKnownGlobalOffset = globalOffset; token.currentProducer = token.desiredProducer; token.itemsConsumedFromCurrent = 0; return true; } /////////////////////////// // Free list /////////////////////////// template struct FreeListNode { FreeListNode() : freeListRefs(0), freeListNext(nullptr) { } std::atomic freeListRefs; std::atomic freeListNext; }; // A simple CAS-based lock-free free list. Not the fastest thing in the world under heavy contention, but // simple and correct (assuming nodes are never freed until after the free list is destroyed), and fairly // speedy under low contention. template // N must inherit FreeListNode or have the same fields (and initialization of them) struct FreeList { FreeList() : freeListHead(nullptr) { } FreeList(FreeList&& other) : freeListHead(other.freeListHead.load(std::memory_order_relaxed)) { other.freeListHead.store(nullptr, std::memory_order_relaxed); } void swap(FreeList& other) { details::swap_relaxed(freeListHead, other.freeListHead); } FreeList(FreeList const&) MOODYCAMEL_DELETE_FUNCTION; FreeList& operator=(FreeList const&) MOODYCAMEL_DELETE_FUNCTION; inline void add(N* node) { #if MCDBGQ_NOLOCKFREE_FREELIST debug::DebugLock lock(mutex); #endif // We know that the should-be-on-freelist bit is 0 at this point, so it's safe to // set it using a fetch_add if (node->freeListRefs.fetch_add(SHOULD_BE_ON_FREELIST, std::memory_order_acq_rel) == 0) { // Oh look! We were the last ones referencing this node, and we know // we want to add it to the free list, so let's do it! add_knowing_refcount_is_zero(node); } } inline N* try_get() { #if MCDBGQ_NOLOCKFREE_FREELIST debug::DebugLock lock(mutex); #endif auto head = freeListHead.load(std::memory_order_acquire); while (head != nullptr) { auto prevHead = head; auto refs = head->freeListRefs.load(std::memory_order_relaxed); if ((refs & REFS_MASK) == 0 || !head->freeListRefs.compare_exchange_strong(refs, refs + 1, std::memory_order_acquire, std::memory_order_relaxed)) { head = freeListHead.load(std::memory_order_acquire); continue; } // Good, reference count has been incremented (it wasn't at zero), which means we can read the // next and not worry about it changing between now and the time we do the CAS auto next = head->freeListNext.load(std::memory_order_relaxed); if (freeListHead.compare_exchange_strong(head, next, std::memory_order_acquire, std::memory_order_relaxed)) { // Yay, got the node. This means it was on the list, which means shouldBeOnFreeList must be false no // matter the refcount (because nobody else knows it's been taken off yet, it can't have been put back on). assert((head->freeListRefs.load(std::memory_order_relaxed) & SHOULD_BE_ON_FREELIST) == 0); // Decrease refcount twice, once for our ref, and once for the list's ref head->freeListRefs.fetch_add(-2, std::memory_order_release); return head; } // OK, the head must have changed on us, but we still need to decrease the refcount we increased. // Note that we don't need to release any memory effects, but we do need to ensure that the reference // count decrement happens-after the CAS on the head. refs = prevHead->freeListRefs.fetch_add(-1, std::memory_order_acq_rel); if (refs == SHOULD_BE_ON_FREELIST + 1) { add_knowing_refcount_is_zero(prevHead); } } return nullptr; } // Useful for traversing the list when there's no contention (e.g. to destroy remaining nodes) N* head_unsafe() const { return freeListHead.load(std::memory_order_relaxed); } private: inline void add_knowing_refcount_is_zero(N* node) { // Since the refcount is zero, and nobody can increase it once it's zero (except us, and we run // only one copy of this method per node at a time, i.e. the single thread case), then we know // we can safely change the next pointer of the node; however, once the refcount is back above // zero, then other threads could increase it (happens under heavy contention, when the refcount // goes to zero in between a load and a refcount increment of a node in try_get, then back up to // something non-zero, then the refcount increment is done by the other thread) -- so, if the CAS // to add the node to the actual list fails, decrease the refcount and leave the add operation to // the next thread who puts the refcount back at zero (which could be us, hence the loop). auto head = freeListHead.load(std::memory_order_relaxed); while (true) { node->freeListNext.store(head, std::memory_order_relaxed); node->freeListRefs.store(1, std::memory_order_release); if (!freeListHead.compare_exchange_strong(head, node, std::memory_order_release, std::memory_order_relaxed)) { // Hmm, the add failed, but we can only try again when the refcount goes back to zero if (node->freeListRefs.fetch_add(SHOULD_BE_ON_FREELIST - 1, std::memory_order_release) == 1) { continue; } } return; } } private: // Implemented like a stack, but where node order doesn't matter (nodes are inserted out of order under contention) std::atomic freeListHead; static const std::uint32_t REFS_MASK = 0x7FFFFFFF; static const std::uint32_t SHOULD_BE_ON_FREELIST = 0x80000000; #if MCDBGQ_NOLOCKFREE_FREELIST debug::DebugMutex mutex; #endif }; /////////////////////////// // Block /////////////////////////// enum InnerQueueContext { implicit_context = 0, explicit_context = 1 }; struct Block { Block() : elementsCompletelyDequeued(0), freeListRefs(0), freeListNext(nullptr), shouldBeOnFreeList(false), dynamicallyAllocated(true) { #if MCDBGQ_TRACKMEM owner = nullptr; #endif } template inline bool is_empty() const { if (context == explicit_context && BLOCK_SIZE <= EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD) { // Check flags for (size_t i = 0; i < BLOCK_SIZE; ++i) { if (!emptyFlags[i].load(std::memory_order_relaxed)) { return false; } } // Aha, empty; make sure we have all other memory effects that happened before the empty flags were set std::atomic_thread_fence(std::memory_order_acquire); return true; } else { // Check counter if (elementsCompletelyDequeued.load(std::memory_order_relaxed) == BLOCK_SIZE) { std::atomic_thread_fence(std::memory_order_acquire); return true; } assert(elementsCompletelyDequeued.load(std::memory_order_relaxed) <= BLOCK_SIZE); return false; } } // Returns true if the block is now empty (does not apply in explicit context) template inline bool set_empty(index_t i) { if (context == explicit_context && BLOCK_SIZE <= EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD) { // Set flag assert(!emptyFlags[BLOCK_SIZE - 1 - static_cast(i & static_cast(BLOCK_SIZE - 1))].load(std::memory_order_relaxed)); emptyFlags[BLOCK_SIZE - 1 - static_cast(i & static_cast(BLOCK_SIZE - 1))].store(true, std::memory_order_release); return false; } else { // Increment counter auto prevVal = elementsCompletelyDequeued.fetch_add(1, std::memory_order_release); assert(prevVal < BLOCK_SIZE); return prevVal == BLOCK_SIZE - 1; } } // Sets multiple contiguous item statuses to 'empty' (assumes no wrapping and count > 0). // Returns true if the block is now empty (does not apply in explicit context). template inline bool set_many_empty(index_t i, size_t count) { if (context == explicit_context && BLOCK_SIZE <= EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD) { // Set flags std::atomic_thread_fence(std::memory_order_release); i = BLOCK_SIZE - 1 - static_cast(i & static_cast(BLOCK_SIZE - 1)) - count + 1; for (size_t j = 0; j != count; ++j) { assert(!emptyFlags[i + j].load(std::memory_order_relaxed)); emptyFlags[i + j].store(true, std::memory_order_relaxed); } return false; } else { // Increment counter auto prevVal = elementsCompletelyDequeued.fetch_add(count, std::memory_order_release); assert(prevVal + count <= BLOCK_SIZE); return prevVal + count == BLOCK_SIZE; } } template inline void set_all_empty() { if (context == explicit_context && BLOCK_SIZE <= EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD) { // Set all flags for (size_t i = 0; i != BLOCK_SIZE; ++i) { emptyFlags[i].store(true, std::memory_order_relaxed); } } else { // Reset counter elementsCompletelyDequeued.store(BLOCK_SIZE, std::memory_order_relaxed); } } template inline void reset_empty() { if (context == explicit_context && BLOCK_SIZE <= EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD) { // Reset flags for (size_t i = 0; i != BLOCK_SIZE; ++i) { emptyFlags[i].store(false, std::memory_order_relaxed); } } else { // Reset counter elementsCompletelyDequeued.store(0, std::memory_order_relaxed); } } inline T* operator[](index_t idx) MOODYCAMEL_NOEXCEPT { return reinterpret_cast(elements) + static_cast(idx & static_cast(BLOCK_SIZE - 1)); } inline T const* operator[](index_t idx) const MOODYCAMEL_NOEXCEPT { return reinterpret_cast(elements) + static_cast(idx & static_cast(BLOCK_SIZE - 1)); } public: Block* next; std::atomic elementsCompletelyDequeued; std::atomic emptyFlags[BLOCK_SIZE <= EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD ? BLOCK_SIZE : 1]; private: char elements[sizeof(T) * BLOCK_SIZE]; public: std::atomic freeListRefs; std::atomic freeListNext; std::atomic shouldBeOnFreeList; bool dynamicallyAllocated; // Perhaps a better name for this would be 'isNotPartOfInitialBlockPool' #if MCDBGQ_TRACKMEM void* owner; #endif }; #if MCDBGQ_TRACKMEM public: struct MemStats; private: #endif /////////////////////////// // Producer base /////////////////////////// struct ProducerBase : public details::ConcurrentQueueProducerTypelessBase { ProducerBase(ConcurrentQueue* parent, bool isExplicit) : tailIndex(0), headIndex(0), dequeueOptimisticCount(0), dequeueOvercommit(0), tailBlock(nullptr), isExplicit(isExplicit), parent(parent) { } virtual ~ProducerBase() { }; template inline bool dequeue(U& element) { if (isExplicit) { return static_cast(this)->dequeue(element); } else { return static_cast(this)->dequeue(element); } } template inline size_t dequeue_bulk(It& itemFirst, size_t max) { if (isExplicit) { return static_cast(this)->dequeue_bulk(itemFirst, max); } else { return static_cast(this)->dequeue_bulk(itemFirst, max); } } inline ProducerBase* next_prod() const { return static_cast(next); } inline size_t size_approx() const { auto tail = tailIndex.load(std::memory_order_relaxed); auto head = headIndex.load(std::memory_order_relaxed); return details::circular_less_than(head, tail) ? static_cast(tail - head) : 0; } inline index_t getTail() const { return tailIndex.load(std::memory_order_relaxed); } protected: std::atomic tailIndex; // Where to enqueue to next std::atomic headIndex; // Where to dequeue from next std::atomic dequeueOptimisticCount; std::atomic dequeueOvercommit; Block* tailBlock; public: bool isExplicit; ConcurrentQueue* parent; protected: #if MCDBGQ_TRACKMEM friend struct MemStats; #endif }; /////////////////////////// // Explicit queue /////////////////////////// struct ExplicitProducer : public ProducerBase { explicit ExplicitProducer(ConcurrentQueue* parent) : ProducerBase(parent, true), blockIndex(nullptr), pr_blockIndexSlotsUsed(0), pr_blockIndexSize(EXPLICIT_INITIAL_INDEX_SIZE >> 1), pr_blockIndexFront(0), pr_blockIndexEntries(nullptr), pr_blockIndexRaw(nullptr) { size_t poolBasedIndexSize = details::ceil_to_pow_2(parent->initialBlockPoolSize) >> 1; if (poolBasedIndexSize > pr_blockIndexSize) { pr_blockIndexSize = poolBasedIndexSize; } new_block_index(0); // This creates an index with double the number of current entries, i.e. EXPLICIT_INITIAL_INDEX_SIZE } ~ExplicitProducer() { // Destruct any elements not yet dequeued. // Since we're in the destructor, we can assume all elements // are either completely dequeued or completely not (no halfways). if (this->tailBlock != nullptr) { // Note this means there must be a block index too // First find the block that's partially dequeued, if any Block* halfDequeuedBlock = nullptr; if ((this->headIndex.load(std::memory_order_relaxed) & static_cast(BLOCK_SIZE - 1)) != 0) { // The head's not on a block boundary, meaning a block somewhere is partially dequeued // (or the head block is the tail block and was fully dequeued, but the head/tail are still not on a boundary) size_t i = (pr_blockIndexFront - pr_blockIndexSlotsUsed) & (pr_blockIndexSize - 1); while (details::circular_less_than(pr_blockIndexEntries[i].base + BLOCK_SIZE, this->headIndex.load(std::memory_order_relaxed))) { i = (i + 1) & (pr_blockIndexSize - 1); } assert(details::circular_less_than(pr_blockIndexEntries[i].base, this->headIndex.load(std::memory_order_relaxed))); halfDequeuedBlock = pr_blockIndexEntries[i].block; } // Start at the head block (note the first line in the loop gives us the head from the tail on the first iteration) auto block = this->tailBlock; do { block = block->next; if (block->ConcurrentQueue::Block::template is_empty()) { continue; } size_t i = 0; // Offset into block if (block == halfDequeuedBlock) { i = static_cast(this->headIndex.load(std::memory_order_relaxed) & static_cast(BLOCK_SIZE - 1)); } // Walk through all the items in the block; if this is the tail block, we need to stop when we reach the tail index auto lastValidIndex = (this->tailIndex.load(std::memory_order_relaxed) & static_cast(BLOCK_SIZE - 1)) == 0 ? BLOCK_SIZE : static_cast(this->tailIndex.load(std::memory_order_relaxed) & static_cast(BLOCK_SIZE - 1)); while (i != BLOCK_SIZE && (block != this->tailBlock || i != lastValidIndex)) { (*block)[i++]->~T(); } } while (block != this->tailBlock); } // Destroy all blocks that we own if (this->tailBlock != nullptr) { auto block = this->tailBlock; do { auto next = block->next; if (block->dynamicallyAllocated) { destroy(block); } block = next; } while (block != this->tailBlock); } // Destroy the block indices auto header = static_cast(pr_blockIndexRaw); while (header != nullptr) { auto prev = static_cast(header->prev); header->~BlockIndexHeader(); Traits::free(header); header = prev; } } template inline bool enqueue(U&& element) { index_t currentTailIndex = this->tailIndex.load(std::memory_order_relaxed); index_t newTailIndex = 1 + currentTailIndex; if ((currentTailIndex & static_cast(BLOCK_SIZE - 1)) == 0) { // We reached the end of a block, start a new one auto startBlock = this->tailBlock; auto originalBlockIndexSlotsUsed = pr_blockIndexSlotsUsed; if (this->tailBlock != nullptr && this->tailBlock->next->ConcurrentQueue::Block::template is_empty()) { // We can re-use the block ahead of us, it's empty! this->tailBlock = this->tailBlock->next; this->tailBlock->ConcurrentQueue::Block::template reset_empty(); // We'll put the block on the block index (guaranteed to be room since we're conceptually removing the // last block from it first -- except instead of removing then adding, we can just overwrite). // Note that there must be a valid block index here, since even if allocation failed in the ctor, // it would have been re-attempted when adding the first block to the queue; since there is such // a block, a block index must have been successfully allocated. } else { // Whatever head value we see here is >= the last value we saw here (relatively), // and <= its current value. Since we have the most recent tail, the head must be // <= to it. auto head = this->headIndex.load(std::memory_order_relaxed); assert(!details::circular_less_than(currentTailIndex, head)); if (!details::circular_less_than(head, currentTailIndex + BLOCK_SIZE) || (MAX_SUBQUEUE_SIZE != details::const_numeric_max::value && (MAX_SUBQUEUE_SIZE == 0 || MAX_SUBQUEUE_SIZE - BLOCK_SIZE < currentTailIndex - head))) { // We can't enqueue in another block because there's not enough leeway -- the // tail could surpass the head by the time the block fills up! (Or we'll exceed // the size limit, if the second part of the condition was true.) return false; } // We're going to need a new block; check that the block index has room if (pr_blockIndexRaw == nullptr || pr_blockIndexSlotsUsed == pr_blockIndexSize) { // Hmm, the circular block index is already full -- we'll need // to allocate a new index. Note pr_blockIndexRaw can only be nullptr if // the initial allocation failed in the constructor. if (allocMode == CannotAlloc || !new_block_index(pr_blockIndexSlotsUsed)) { return false; } } // Insert a new block in the circular linked list auto newBlock = this->parent->ConcurrentQueue::template requisition_block(); if (newBlock == nullptr) { return false; } #if MCDBGQ_TRACKMEM newBlock->owner = this; #endif newBlock->ConcurrentQueue::Block::template reset_empty(); if (this->tailBlock == nullptr) { newBlock->next = newBlock; } else { newBlock->next = this->tailBlock->next; this->tailBlock->next = newBlock; } this->tailBlock = newBlock; ++pr_blockIndexSlotsUsed; } if (!MOODYCAMEL_NOEXCEPT_CTOR(T, U, new (nullptr) T(std::forward(element)))) { // The constructor may throw. We want the element not to appear in the queue in // that case (without corrupting the queue): MOODYCAMEL_TRY { new ((*this->tailBlock)[currentTailIndex]) T(std::forward(element)); } MOODYCAMEL_CATCH (...) { // Revert change to the current block, but leave the new block available // for next time pr_blockIndexSlotsUsed = originalBlockIndexSlotsUsed; this->tailBlock = startBlock == nullptr ? this->tailBlock : startBlock; MOODYCAMEL_RETHROW; } } else { (void)startBlock; (void)originalBlockIndexSlotsUsed; } // Add block to block index auto& entry = blockIndex.load(std::memory_order_relaxed)->entries[pr_blockIndexFront]; entry.base = currentTailIndex; entry.block = this->tailBlock; blockIndex.load(std::memory_order_relaxed)->front.store(pr_blockIndexFront, std::memory_order_release); pr_blockIndexFront = (pr_blockIndexFront + 1) & (pr_blockIndexSize - 1); if (!MOODYCAMEL_NOEXCEPT_CTOR(T, U, new (nullptr) T(std::forward(element)))) { this->tailIndex.store(newTailIndex, std::memory_order_release); return true; } } // Enqueue new ((*this->tailBlock)[currentTailIndex]) T(std::forward(element)); this->tailIndex.store(newTailIndex, std::memory_order_release); return true; } template bool dequeue(U& element) { auto tail = this->tailIndex.load(std::memory_order_relaxed); auto overcommit = this->dequeueOvercommit.load(std::memory_order_relaxed); if (details::circular_less_than(this->dequeueOptimisticCount.load(std::memory_order_relaxed) - overcommit, tail)) { // Might be something to dequeue, let's give it a try // Note that this if is purely for performance purposes in the common case when the queue is // empty and the values are eventually consistent -- we may enter here spuriously. // Note that whatever the values of overcommit and tail are, they are not going to change (unless we // change them) and must be the same value at this point (inside the if) as when the if condition was // evaluated. // We insert an acquire fence here to synchronize-with the release upon incrementing dequeueOvercommit below. // This ensures that whatever the value we got loaded into overcommit, the load of dequeueOptisticCount in // the fetch_add below will result in a value at least as recent as that (and therefore at least as large). // Note that I believe a compiler (signal) fence here would be sufficient due to the nature of fetch_add (all // read-modify-write operations are guaranteed to work on the latest value in the modification order), but // unfortunately that can't be shown to be correct using only the C++11 standard. // See http://stackoverflow.com/questions/18223161/what-are-the-c11-memory-ordering-guarantees-in-this-corner-case std::atomic_thread_fence(std::memory_order_acquire); // Increment optimistic counter, then check if it went over the boundary auto myDequeueCount = this->dequeueOptimisticCount.fetch_add(1, std::memory_order_relaxed); // Note that since dequeueOvercommit must be <= dequeueOptimisticCount (because dequeueOvercommit is only ever // incremented after dequeueOptimisticCount -- this is enforced in the `else` block below), and since we now // have a version of dequeueOptimisticCount that is at least as recent as overcommit (due to the release upon // incrementing dequeueOvercommit and the acquire above that synchronizes with it), overcommit <= myDequeueCount. assert(overcommit <= myDequeueCount); // Note that we reload tail here in case it changed; it will be the same value as before or greater, since // this load is sequenced after (happens after) the earlier load above. This is supported by read-read // coherency (as defined in the standard), explained here: http://en.cppreference.com/w/cpp/atomic/memory_order tail = this->tailIndex.load(std::memory_order_acquire); if (details::likely(details::circular_less_than(myDequeueCount - overcommit, tail))) { // Guaranteed to be at least one element to dequeue! // Get the index. Note that since there's guaranteed to be at least one element, this // will never exceed tail. We need to do an acquire-release fence here since it's possible // that whatever condition got us to this point was for an earlier enqueued element (that // we already see the memory effects for), but that by the time we increment somebody else // has incremented it, and we need to see the memory effects for *that* element, which is // in such a case is necessarily visible on the thread that incremented it in the first // place with the more current condition (they must have acquired a tail that is at least // as recent). auto index = this->headIndex.fetch_add(1, std::memory_order_acq_rel); // Determine which block the element is in auto localBlockIndex = blockIndex.load(std::memory_order_acquire); auto localBlockIndexHead = localBlockIndex->front.load(std::memory_order_acquire); // We need to be careful here about subtracting and dividing because of index wrap-around. // When an index wraps, we need to preserve the sign of the offset when dividing it by the // block size (in order to get a correct signed block count offset in all cases): auto headBase = localBlockIndex->entries[localBlockIndexHead].base; auto blockBaseIndex = index & ~static_cast(BLOCK_SIZE - 1); auto offset = static_cast(static_cast::type>(blockBaseIndex - headBase) / BLOCK_SIZE); auto block = localBlockIndex->entries[(localBlockIndexHead + offset) & (localBlockIndex->size - 1)].block; // Dequeue auto& el = *((*block)[index]); if (!MOODYCAMEL_NOEXCEPT_ASSIGN(T, T&&, element = std::move(el))) { // Make sure the element is still fully dequeued and destroyed even if the assignment // throws struct Guard { Block* block; index_t index; ~Guard() { (*block)[index]->~T(); block->ConcurrentQueue::Block::template set_empty(index); } } guard = { block, index }; element = std::move(el); } else { element = std::move(el); el.~T(); block->ConcurrentQueue::Block::template set_empty(index); } return true; } else { // Wasn't anything to dequeue after all; make the effective dequeue count eventually consistent this->dequeueOvercommit.fetch_add(1, std::memory_order_release); // Release so that the fetch_add on dequeueOptimisticCount is guaranteed to happen before this write } } return false; } template bool enqueue_bulk(It itemFirst, size_t count) { // First, we need to make sure we have enough room to enqueue all of the elements; // this means pre-allocating blocks and putting them in the block index (but only if // all the allocations succeeded). index_t startTailIndex = this->tailIndex.load(std::memory_order_relaxed); auto startBlock = this->tailBlock; auto originalBlockIndexFront = pr_blockIndexFront; auto originalBlockIndexSlotsUsed = pr_blockIndexSlotsUsed; Block* firstAllocatedBlock = nullptr; // Figure out how many blocks we'll need to allocate, and do so size_t blockBaseDiff = ((startTailIndex + count - 1) & ~static_cast(BLOCK_SIZE - 1)) - ((startTailIndex - 1) & ~static_cast(BLOCK_SIZE - 1)); index_t currentTailIndex = (startTailIndex - 1) & ~static_cast(BLOCK_SIZE - 1); if (blockBaseDiff > 0) { // Allocate as many blocks as possible from ahead while (blockBaseDiff > 0 && this->tailBlock != nullptr && this->tailBlock->next != firstAllocatedBlock && this->tailBlock->next->ConcurrentQueue::Block::template is_empty()) { blockBaseDiff -= static_cast(BLOCK_SIZE); currentTailIndex += static_cast(BLOCK_SIZE); this->tailBlock = this->tailBlock->next; firstAllocatedBlock = firstAllocatedBlock == nullptr ? this->tailBlock : firstAllocatedBlock; auto& entry = blockIndex.load(std::memory_order_relaxed)->entries[pr_blockIndexFront]; entry.base = currentTailIndex; entry.block = this->tailBlock; pr_blockIndexFront = (pr_blockIndexFront + 1) & (pr_blockIndexSize - 1); } // Now allocate as many blocks as necessary from the block pool while (blockBaseDiff > 0) { blockBaseDiff -= static_cast(BLOCK_SIZE); currentTailIndex += static_cast(BLOCK_SIZE); auto head = this->headIndex.load(std::memory_order_relaxed); assert(!details::circular_less_than(currentTailIndex, head)); bool full = !details::circular_less_than(head, currentTailIndex + BLOCK_SIZE) || (MAX_SUBQUEUE_SIZE != details::const_numeric_max::value && (MAX_SUBQUEUE_SIZE == 0 || MAX_SUBQUEUE_SIZE - BLOCK_SIZE < currentTailIndex - head)); if (pr_blockIndexRaw == nullptr || pr_blockIndexSlotsUsed == pr_blockIndexSize || full) { if (allocMode == CannotAlloc || full || !new_block_index(originalBlockIndexSlotsUsed)) { // Failed to allocate, undo changes (but keep injected blocks) pr_blockIndexFront = originalBlockIndexFront; pr_blockIndexSlotsUsed = originalBlockIndexSlotsUsed; this->tailBlock = startBlock == nullptr ? firstAllocatedBlock : startBlock; return false; } // pr_blockIndexFront is updated inside new_block_index, so we need to // update our fallback value too (since we keep the new index even if we // later fail) originalBlockIndexFront = originalBlockIndexSlotsUsed; } // Insert a new block in the circular linked list auto newBlock = this->parent->ConcurrentQueue::template requisition_block(); if (newBlock == nullptr) { pr_blockIndexFront = originalBlockIndexFront; pr_blockIndexSlotsUsed = originalBlockIndexSlotsUsed; this->tailBlock = startBlock == nullptr ? firstAllocatedBlock : startBlock; return false; } #if MCDBGQ_TRACKMEM newBlock->owner = this; #endif newBlock->ConcurrentQueue::Block::template set_all_empty(); if (this->tailBlock == nullptr) { newBlock->next = newBlock; } else { newBlock->next = this->tailBlock->next; this->tailBlock->next = newBlock; } this->tailBlock = newBlock; firstAllocatedBlock = firstAllocatedBlock == nullptr ? this->tailBlock : firstAllocatedBlock; ++pr_blockIndexSlotsUsed; auto& entry = blockIndex.load(std::memory_order_relaxed)->entries[pr_blockIndexFront]; entry.base = currentTailIndex; entry.block = this->tailBlock; pr_blockIndexFront = (pr_blockIndexFront + 1) & (pr_blockIndexSize - 1); } // Excellent, all allocations succeeded. Reset each block's emptiness before we fill them up, and // publish the new block index front auto block = firstAllocatedBlock; while (true) { block->ConcurrentQueue::Block::template reset_empty(); if (block == this->tailBlock) { break; } block = block->next; } if (MOODYCAMEL_NOEXCEPT_CTOR(T, decltype(*itemFirst), new (nullptr) T(details::deref_noexcept(itemFirst)))) { blockIndex.load(std::memory_order_relaxed)->front.store((pr_blockIndexFront - 1) & (pr_blockIndexSize - 1), std::memory_order_release); } } // Enqueue, one block at a time index_t newTailIndex = startTailIndex + static_cast(count); currentTailIndex = startTailIndex; auto endBlock = this->tailBlock; this->tailBlock = startBlock; assert((startTailIndex & static_cast(BLOCK_SIZE - 1)) != 0 || firstAllocatedBlock != nullptr || count == 0); if ((startTailIndex & static_cast(BLOCK_SIZE - 1)) == 0 && firstAllocatedBlock != nullptr) { this->tailBlock = firstAllocatedBlock; } while (true) { auto stopIndex = (currentTailIndex & ~static_cast(BLOCK_SIZE - 1)) + static_cast(BLOCK_SIZE); if (details::circular_less_than(newTailIndex, stopIndex)) { stopIndex = newTailIndex; } if (MOODYCAMEL_NOEXCEPT_CTOR(T, decltype(*itemFirst), new (nullptr) T(details::deref_noexcept(itemFirst)))) { while (currentTailIndex != stopIndex) { new ((*this->tailBlock)[currentTailIndex++]) T(*itemFirst++); } } else { MOODYCAMEL_TRY { while (currentTailIndex != stopIndex) { // Must use copy constructor even if move constructor is available // because we may have to revert if there's an exception. // Sorry about the horrible templated next line, but it was the only way // to disable moving *at compile time*, which is important because a type // may only define a (noexcept) move constructor, and so calls to the // cctor will not compile, even if they are in an if branch that will never // be executed new ((*this->tailBlock)[currentTailIndex]) T(details::nomove_if<(bool)!MOODYCAMEL_NOEXCEPT_CTOR(T, decltype(*itemFirst), new (nullptr) T(details::deref_noexcept(itemFirst)))>::eval(*itemFirst)); ++currentTailIndex; ++itemFirst; } } MOODYCAMEL_CATCH (...) { // Oh dear, an exception's been thrown -- destroy the elements that // were enqueued so far and revert the entire bulk operation (we'll keep // any allocated blocks in our linked list for later, though). auto constructedStopIndex = currentTailIndex; auto lastBlockEnqueued = this->tailBlock; pr_blockIndexFront = originalBlockIndexFront; pr_blockIndexSlotsUsed = originalBlockIndexSlotsUsed; this->tailBlock = startBlock == nullptr ? firstAllocatedBlock : startBlock; if (!details::is_trivially_destructible::value) { auto block = startBlock; if ((startTailIndex & static_cast(BLOCK_SIZE - 1)) == 0) { block = firstAllocatedBlock; } currentTailIndex = startTailIndex; while (true) { auto stopIndex = (currentTailIndex & ~static_cast(BLOCK_SIZE - 1)) + static_cast(BLOCK_SIZE); if (details::circular_less_than(constructedStopIndex, stopIndex)) { stopIndex = constructedStopIndex; } while (currentTailIndex != stopIndex) { (*block)[currentTailIndex++]->~T(); } if (block == lastBlockEnqueued) { break; } block = block->next; } } MOODYCAMEL_RETHROW; } } if (this->tailBlock == endBlock) { assert(currentTailIndex == newTailIndex); break; } this->tailBlock = this->tailBlock->next; } if (!MOODYCAMEL_NOEXCEPT_CTOR(T, decltype(*itemFirst), new (nullptr) T(details::deref_noexcept(itemFirst))) && firstAllocatedBlock != nullptr) { blockIndex.load(std::memory_order_relaxed)->front.store((pr_blockIndexFront - 1) & (pr_blockIndexSize - 1), std::memory_order_release); } this->tailIndex.store(newTailIndex, std::memory_order_release); return true; } template size_t dequeue_bulk(It& itemFirst, size_t max) { auto tail = this->tailIndex.load(std::memory_order_relaxed); auto overcommit = this->dequeueOvercommit.load(std::memory_order_relaxed); auto desiredCount = static_cast(tail - (this->dequeueOptimisticCount.load(std::memory_order_relaxed) - overcommit)); if (details::circular_less_than(0, desiredCount)) { desiredCount = desiredCount < max ? desiredCount : max; std::atomic_thread_fence(std::memory_order_acquire); auto myDequeueCount = this->dequeueOptimisticCount.fetch_add(desiredCount, std::memory_order_relaxed); assert(overcommit <= myDequeueCount); tail = this->tailIndex.load(std::memory_order_acquire); auto actualCount = static_cast(tail - (myDequeueCount - overcommit)); if (details::circular_less_than(0, actualCount)) { actualCount = desiredCount < actualCount ? desiredCount : actualCount; if (actualCount < desiredCount) { this->dequeueOvercommit.fetch_add(desiredCount - actualCount, std::memory_order_release); } // Get the first index. Note that since there's guaranteed to be at least actualCount elements, this // will never exceed tail. auto firstIndex = this->headIndex.fetch_add(actualCount, std::memory_order_acq_rel); // Determine which block the first element is in auto localBlockIndex = blockIndex.load(std::memory_order_acquire); auto localBlockIndexHead = localBlockIndex->front.load(std::memory_order_acquire); auto headBase = localBlockIndex->entries[localBlockIndexHead].base; auto firstBlockBaseIndex = firstIndex & ~static_cast(BLOCK_SIZE - 1); auto offset = static_cast(static_cast::type>(firstBlockBaseIndex - headBase) / BLOCK_SIZE); auto indexIndex = (localBlockIndexHead + offset) & (localBlockIndex->size - 1); // Iterate the blocks and dequeue auto index = firstIndex; do { auto firstIndexInBlock = index; auto endIndex = (index & ~static_cast(BLOCK_SIZE - 1)) + static_cast(BLOCK_SIZE); endIndex = details::circular_less_than(firstIndex + static_cast(actualCount), endIndex) ? firstIndex + static_cast(actualCount) : endIndex; auto block = localBlockIndex->entries[indexIndex].block; if (MOODYCAMEL_NOEXCEPT_ASSIGN(T, T&&, details::deref_noexcept(itemFirst) = std::move((*(*block)[index])))) { while (index != endIndex) { auto& el = *((*block)[index]); *itemFirst++ = std::move(el); el.~T(); ++index; } } else { MOODYCAMEL_TRY { while (index != endIndex) { auto& el = *((*block)[index]); *itemFirst = std::move(el); ++itemFirst; el.~T(); ++index; } } MOODYCAMEL_CATCH (...) { // It's too late to revert the dequeue, but we can make sure that all // the dequeued objects are properly destroyed and the block index // (and empty count) are properly updated before we propagate the exception do { block = localBlockIndex->entries[indexIndex].block; while (index != endIndex) { (*block)[index++]->~T(); } block->ConcurrentQueue::Block::template set_many_empty(firstIndexInBlock, static_cast(endIndex - firstIndexInBlock)); indexIndex = (indexIndex + 1) & (localBlockIndex->size - 1); firstIndexInBlock = index; endIndex = (index & ~static_cast(BLOCK_SIZE - 1)) + static_cast(BLOCK_SIZE); endIndex = details::circular_less_than(firstIndex + static_cast(actualCount), endIndex) ? firstIndex + static_cast(actualCount) : endIndex; } while (index != firstIndex + actualCount); MOODYCAMEL_RETHROW; } } block->ConcurrentQueue::Block::template set_many_empty(firstIndexInBlock, static_cast(endIndex - firstIndexInBlock)); indexIndex = (indexIndex + 1) & (localBlockIndex->size - 1); } while (index != firstIndex + actualCount); return actualCount; } else { // Wasn't anything to dequeue after all; make the effective dequeue count eventually consistent this->dequeueOvercommit.fetch_add(desiredCount, std::memory_order_release); } } return 0; } private: struct BlockIndexEntry { index_t base; Block* block; }; struct BlockIndexHeader { size_t size; std::atomic front; // Current slot (not next, like pr_blockIndexFront) BlockIndexEntry* entries; void* prev; }; bool new_block_index(size_t numberOfFilledSlotsToExpose) { auto prevBlockSizeMask = pr_blockIndexSize - 1; // Create the new block pr_blockIndexSize <<= 1; auto newRawPtr = static_cast(Traits::malloc(sizeof(BlockIndexHeader) + std::alignment_of::value - 1 + sizeof(BlockIndexEntry) * pr_blockIndexSize)); if (newRawPtr == nullptr) { pr_blockIndexSize >>= 1; // Reset to allow graceful retry return false; } auto newBlockIndexEntries = reinterpret_cast(details::align_for(newRawPtr + sizeof(BlockIndexHeader))); // Copy in all the old indices, if any size_t j = 0; if (pr_blockIndexSlotsUsed != 0) { auto i = (pr_blockIndexFront - pr_blockIndexSlotsUsed) & prevBlockSizeMask; do { newBlockIndexEntries[j++] = pr_blockIndexEntries[i]; i = (i + 1) & prevBlockSizeMask; } while (i != pr_blockIndexFront); } // Update everything auto header = new (newRawPtr) BlockIndexHeader; header->size = pr_blockIndexSize; header->front.store(numberOfFilledSlotsToExpose - 1, std::memory_order_relaxed); header->entries = newBlockIndexEntries; header->prev = pr_blockIndexRaw; // we link the new block to the old one so we can free it later pr_blockIndexFront = j; pr_blockIndexEntries = newBlockIndexEntries; pr_blockIndexRaw = newRawPtr; blockIndex.store(header, std::memory_order_release); return true; } private: std::atomic blockIndex; // To be used by producer only -- consumer must use the ones in referenced by blockIndex size_t pr_blockIndexSlotsUsed; size_t pr_blockIndexSize; size_t pr_blockIndexFront; // Next slot (not current) BlockIndexEntry* pr_blockIndexEntries; void* pr_blockIndexRaw; #ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG public: ExplicitProducer* nextExplicitProducer; private: #endif #if MCDBGQ_TRACKMEM friend struct MemStats; #endif }; ////////////////////////////////// // Implicit queue ////////////////////////////////// struct ImplicitProducer : public ProducerBase { ImplicitProducer(ConcurrentQueue* parent) : ProducerBase(parent, false), nextBlockIndexCapacity(IMPLICIT_INITIAL_INDEX_SIZE), blockIndex(nullptr) { new_block_index(); } ~ImplicitProducer() { // Note that since we're in the destructor we can assume that all enqueue/dequeue operations // completed already; this means that all undequeued elements are placed contiguously across // contiguous blocks, and that only the first and last remaining blocks can be only partially // empty (all other remaining blocks must be completely full). #ifdef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED // Unregister ourselves for thread termination notification if (!this->inactive.load(std::memory_order_relaxed)) { details::ThreadExitNotifier::unsubscribe(&threadExitListener); } #endif // Destroy all remaining elements! auto tail = this->tailIndex.load(std::memory_order_relaxed); auto index = this->headIndex.load(std::memory_order_relaxed); Block* block = nullptr; assert(index == tail || details::circular_less_than(index, tail)); bool forceFreeLastBlock = index != tail; // If we enter the loop, then the last (tail) block will not be freed while (index != tail) { if ((index & static_cast(BLOCK_SIZE - 1)) == 0 || block == nullptr) { if (block != nullptr && block->dynamicallyAllocated) { // Free the old block this->parent->destroy(block); } block = get_block_index_entry_for_index(index)->value.load(std::memory_order_relaxed); } ((*block)[index])->~T(); ++index; } // Even if the queue is empty, there's still one block that's not on the free list // (unless the head index reached the end of it, in which case the tail will be poised // to create a new block). if (this->tailBlock != nullptr && (forceFreeLastBlock || (tail & static_cast(BLOCK_SIZE - 1)) != 0) && this->tailBlock->dynamicallyAllocated) { this->parent->destroy(this->tailBlock); } // Destroy block index auto localBlockIndex = blockIndex.load(std::memory_order_relaxed); if (localBlockIndex != nullptr) { for (size_t i = 0; i != localBlockIndex->capacity; ++i) { localBlockIndex->index[i]->~BlockIndexEntry(); } do { auto prev = localBlockIndex->prev; localBlockIndex->~BlockIndexHeader(); Traits::free(localBlockIndex); localBlockIndex = prev; } while (localBlockIndex != nullptr); } } template inline bool enqueue(U&& element) { index_t currentTailIndex = this->tailIndex.load(std::memory_order_relaxed); index_t newTailIndex = 1 + currentTailIndex; if ((currentTailIndex & static_cast(BLOCK_SIZE - 1)) == 0) { // We reached the end of a block, start a new one auto head = this->headIndex.load(std::memory_order_relaxed); assert(!details::circular_less_than(currentTailIndex, head)); if (!details::circular_less_than(head, currentTailIndex + BLOCK_SIZE) || (MAX_SUBQUEUE_SIZE != details::const_numeric_max::value && (MAX_SUBQUEUE_SIZE == 0 || MAX_SUBQUEUE_SIZE - BLOCK_SIZE < currentTailIndex - head))) { return false; } #if MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX debug::DebugLock lock(mutex); #endif // Find out where we'll be inserting this block in the block index BlockIndexEntry* idxEntry; if (!insert_block_index_entry(idxEntry, currentTailIndex)) { return false; } // Get ahold of a new block auto newBlock = this->parent->ConcurrentQueue::template requisition_block(); if (newBlock == nullptr) { rewind_block_index_tail(); idxEntry->value.store(nullptr, std::memory_order_relaxed); return false; } #if MCDBGQ_TRACKMEM newBlock->owner = this; #endif newBlock->ConcurrentQueue::Block::template reset_empty(); if (!MOODYCAMEL_NOEXCEPT_CTOR(T, U, new (nullptr) T(std::forward(element)))) { // May throw, try to insert now before we publish the fact that we have this new block MOODYCAMEL_TRY { new ((*newBlock)[currentTailIndex]) T(std::forward(element)); } MOODYCAMEL_CATCH (...) { rewind_block_index_tail(); idxEntry->value.store(nullptr, std::memory_order_relaxed); this->parent->add_block_to_free_list(newBlock); MOODYCAMEL_RETHROW; } } // Insert the new block into the index idxEntry->value.store(newBlock, std::memory_order_relaxed); this->tailBlock = newBlock; if (!MOODYCAMEL_NOEXCEPT_CTOR(T, U, new (nullptr) T(std::forward(element)))) { this->tailIndex.store(newTailIndex, std::memory_order_release); return true; } } // Enqueue new ((*this->tailBlock)[currentTailIndex]) T(std::forward(element)); this->tailIndex.store(newTailIndex, std::memory_order_release); return true; } template bool dequeue(U& element) { // See ExplicitProducer::dequeue for rationale and explanation index_t tail = this->tailIndex.load(std::memory_order_relaxed); index_t overcommit = this->dequeueOvercommit.load(std::memory_order_relaxed); if (details::circular_less_than(this->dequeueOptimisticCount.load(std::memory_order_relaxed) - overcommit, tail)) { std::atomic_thread_fence(std::memory_order_acquire); index_t myDequeueCount = this->dequeueOptimisticCount.fetch_add(1, std::memory_order_relaxed); assert(overcommit <= myDequeueCount); tail = this->tailIndex.load(std::memory_order_acquire); if (details::likely(details::circular_less_than(myDequeueCount - overcommit, tail))) { index_t index = this->headIndex.fetch_add(1, std::memory_order_acq_rel); // Determine which block the element is in auto entry = get_block_index_entry_for_index(index); // Dequeue auto block = entry->value.load(std::memory_order_relaxed); auto& el = *((*block)[index]); if (!MOODYCAMEL_NOEXCEPT_ASSIGN(T, T&&, element = std::move(el))) { #if MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX // Note: Acquiring the mutex with every dequeue instead of only when a block // is released is very sub-optimal, but it is, after all, purely debug code. debug::DebugLock lock(producer->mutex); #endif struct Guard { Block* block; index_t index; BlockIndexEntry* entry; ConcurrentQueue* parent; ~Guard() { (*block)[index]->~T(); if (block->ConcurrentQueue::Block::template set_empty(index)) { entry->value.store(nullptr, std::memory_order_relaxed); parent->add_block_to_free_list(block); } } } guard = { block, index, entry, this->parent }; element = std::move(el); } else { element = std::move(el); el.~T(); if (block->ConcurrentQueue::Block::template set_empty(index)) { { #if MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX debug::DebugLock lock(mutex); #endif // Add the block back into the global free pool (and remove from block index) entry->value.store(nullptr, std::memory_order_relaxed); } this->parent->add_block_to_free_list(block); // releases the above store } } return true; } else { this->dequeueOvercommit.fetch_add(1, std::memory_order_release); } } return false; } template bool enqueue_bulk(It itemFirst, size_t count) { // First, we need to make sure we have enough room to enqueue all of the elements; // this means pre-allocating blocks and putting them in the block index (but only if // all the allocations succeeded). // Note that the tailBlock we start off with may not be owned by us any more; // this happens if it was filled up exactly to the top (setting tailIndex to // the first index of the next block which is not yet allocated), then dequeued // completely (putting it on the free list) before we enqueue again. index_t startTailIndex = this->tailIndex.load(std::memory_order_relaxed); auto startBlock = this->tailBlock; Block* firstAllocatedBlock = nullptr; auto endBlock = this->tailBlock; // Figure out how many blocks we'll need to allocate, and do so size_t blockBaseDiff = ((startTailIndex + count - 1) & ~static_cast(BLOCK_SIZE - 1)) - ((startTailIndex - 1) & ~static_cast(BLOCK_SIZE - 1)); index_t currentTailIndex = (startTailIndex - 1) & ~static_cast(BLOCK_SIZE - 1); if (blockBaseDiff > 0) { #if MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX debug::DebugLock lock(mutex); #endif do { blockBaseDiff -= static_cast(BLOCK_SIZE); currentTailIndex += static_cast(BLOCK_SIZE); // Find out where we'll be inserting this block in the block index BlockIndexEntry* idxEntry; Block* newBlock; bool indexInserted = false; auto head = this->headIndex.load(std::memory_order_relaxed); assert(!details::circular_less_than(currentTailIndex, head)); bool full = !details::circular_less_than(head, currentTailIndex + BLOCK_SIZE) || (MAX_SUBQUEUE_SIZE != details::const_numeric_max::value && (MAX_SUBQUEUE_SIZE == 0 || MAX_SUBQUEUE_SIZE - BLOCK_SIZE < currentTailIndex - head)); if (full || !(indexInserted = insert_block_index_entry(idxEntry, currentTailIndex)) || (newBlock = this->parent->ConcurrentQueue::template requisition_block()) == nullptr) { // Index allocation or block allocation failed; revert any other allocations // and index insertions done so far for this operation if (indexInserted) { rewind_block_index_tail(); idxEntry->value.store(nullptr, std::memory_order_relaxed); } currentTailIndex = (startTailIndex - 1) & ~static_cast(BLOCK_SIZE - 1); for (auto block = firstAllocatedBlock; block != nullptr; block = block->next) { currentTailIndex += static_cast(BLOCK_SIZE); idxEntry = get_block_index_entry_for_index(currentTailIndex); idxEntry->value.store(nullptr, std::memory_order_relaxed); rewind_block_index_tail(); } this->parent->add_blocks_to_free_list(firstAllocatedBlock); this->tailBlock = startBlock; return false; } #if MCDBGQ_TRACKMEM newBlock->owner = this; #endif newBlock->ConcurrentQueue::Block::template reset_empty(); newBlock->next = nullptr; // Insert the new block into the index idxEntry->value.store(newBlock, std::memory_order_relaxed); // Store the chain of blocks so that we can undo if later allocations fail, // and so that we can find the blocks when we do the actual enqueueing if ((startTailIndex & static_cast(BLOCK_SIZE - 1)) != 0 || firstAllocatedBlock != nullptr) { assert(this->tailBlock != nullptr); this->tailBlock->next = newBlock; } this->tailBlock = newBlock; endBlock = newBlock; firstAllocatedBlock = firstAllocatedBlock == nullptr ? newBlock : firstAllocatedBlock; } while (blockBaseDiff > 0); } // Enqueue, one block at a time index_t newTailIndex = startTailIndex + static_cast(count); currentTailIndex = startTailIndex; this->tailBlock = startBlock; assert((startTailIndex & static_cast(BLOCK_SIZE - 1)) != 0 || firstAllocatedBlock != nullptr || count == 0); if ((startTailIndex & static_cast(BLOCK_SIZE - 1)) == 0 && firstAllocatedBlock != nullptr) { this->tailBlock = firstAllocatedBlock; } while (true) { auto stopIndex = (currentTailIndex & ~static_cast(BLOCK_SIZE - 1)) + static_cast(BLOCK_SIZE); if (details::circular_less_than(newTailIndex, stopIndex)) { stopIndex = newTailIndex; } if (MOODYCAMEL_NOEXCEPT_CTOR(T, decltype(*itemFirst), new (nullptr) T(details::deref_noexcept(itemFirst)))) { while (currentTailIndex != stopIndex) { new ((*this->tailBlock)[currentTailIndex++]) T(*itemFirst++); } } else { MOODYCAMEL_TRY { while (currentTailIndex != stopIndex) { new ((*this->tailBlock)[currentTailIndex]) T(details::nomove_if<(bool)!MOODYCAMEL_NOEXCEPT_CTOR(T, decltype(*itemFirst), new (nullptr) T(details::deref_noexcept(itemFirst)))>::eval(*itemFirst)); ++currentTailIndex; ++itemFirst; } } MOODYCAMEL_CATCH (...) { auto constructedStopIndex = currentTailIndex; auto lastBlockEnqueued = this->tailBlock; if (!details::is_trivially_destructible::value) { auto block = startBlock; if ((startTailIndex & static_cast(BLOCK_SIZE - 1)) == 0) { block = firstAllocatedBlock; } currentTailIndex = startTailIndex; while (true) { auto stopIndex = (currentTailIndex & ~static_cast(BLOCK_SIZE - 1)) + static_cast(BLOCK_SIZE); if (details::circular_less_than(constructedStopIndex, stopIndex)) { stopIndex = constructedStopIndex; } while (currentTailIndex != stopIndex) { (*block)[currentTailIndex++]->~T(); } if (block == lastBlockEnqueued) { break; } block = block->next; } } currentTailIndex = (startTailIndex - 1) & ~static_cast(BLOCK_SIZE - 1); for (auto block = firstAllocatedBlock; block != nullptr; block = block->next) { currentTailIndex += static_cast(BLOCK_SIZE); auto idxEntry = get_block_index_entry_for_index(currentTailIndex); idxEntry->value.store(nullptr, std::memory_order_relaxed); rewind_block_index_tail(); } this->parent->add_blocks_to_free_list(firstAllocatedBlock); this->tailBlock = startBlock; MOODYCAMEL_RETHROW; } } if (this->tailBlock == endBlock) { assert(currentTailIndex == newTailIndex); break; } this->tailBlock = this->tailBlock->next; } this->tailIndex.store(newTailIndex, std::memory_order_release); return true; } template size_t dequeue_bulk(It& itemFirst, size_t max) { auto tail = this->tailIndex.load(std::memory_order_relaxed); auto overcommit = this->dequeueOvercommit.load(std::memory_order_relaxed); auto desiredCount = static_cast(tail - (this->dequeueOptimisticCount.load(std::memory_order_relaxed) - overcommit)); if (details::circular_less_than(0, desiredCount)) { desiredCount = desiredCount < max ? desiredCount : max; std::atomic_thread_fence(std::memory_order_acquire); auto myDequeueCount = this->dequeueOptimisticCount.fetch_add(desiredCount, std::memory_order_relaxed); assert(overcommit <= myDequeueCount); tail = this->tailIndex.load(std::memory_order_acquire); auto actualCount = static_cast(tail - (myDequeueCount - overcommit)); if (details::circular_less_than(0, actualCount)) { actualCount = desiredCount < actualCount ? desiredCount : actualCount; if (actualCount < desiredCount) { this->dequeueOvercommit.fetch_add(desiredCount - actualCount, std::memory_order_release); } // Get the first index. Note that since there's guaranteed to be at least actualCount elements, this // will never exceed tail. auto firstIndex = this->headIndex.fetch_add(actualCount, std::memory_order_acq_rel); // Iterate the blocks and dequeue auto index = firstIndex; BlockIndexHeader* localBlockIndex; auto indexIndex = get_block_index_index_for_index(index, localBlockIndex); do { auto blockStartIndex = index; auto endIndex = (index & ~static_cast(BLOCK_SIZE - 1)) + static_cast(BLOCK_SIZE); endIndex = details::circular_less_than(firstIndex + static_cast(actualCount), endIndex) ? firstIndex + static_cast(actualCount) : endIndex; auto entry = localBlockIndex->index[indexIndex]; auto block = entry->value.load(std::memory_order_relaxed); if (MOODYCAMEL_NOEXCEPT_ASSIGN(T, T&&, details::deref_noexcept(itemFirst) = std::move((*(*block)[index])))) { while (index != endIndex) { auto& el = *((*block)[index]); *itemFirst++ = std::move(el); el.~T(); ++index; } } else { MOODYCAMEL_TRY { while (index != endIndex) { auto& el = *((*block)[index]); *itemFirst = std::move(el); ++itemFirst; el.~T(); ++index; } } MOODYCAMEL_CATCH (...) { do { entry = localBlockIndex->index[indexIndex]; block = entry->value.load(std::memory_order_relaxed); while (index != endIndex) { (*block)[index++]->~T(); } if (block->ConcurrentQueue::Block::template set_many_empty(blockStartIndex, static_cast(endIndex - blockStartIndex))) { #if MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX debug::DebugLock lock(mutex); #endif entry->value.store(nullptr, std::memory_order_relaxed); this->parent->add_block_to_free_list(block); } indexIndex = (indexIndex + 1) & (localBlockIndex->capacity - 1); blockStartIndex = index; endIndex = (index & ~static_cast(BLOCK_SIZE - 1)) + static_cast(BLOCK_SIZE); endIndex = details::circular_less_than(firstIndex + static_cast(actualCount), endIndex) ? firstIndex + static_cast(actualCount) : endIndex; } while (index != firstIndex + actualCount); MOODYCAMEL_RETHROW; } } if (block->ConcurrentQueue::Block::template set_many_empty(blockStartIndex, static_cast(endIndex - blockStartIndex))) { { #if MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX debug::DebugLock lock(mutex); #endif // Note that the set_many_empty above did a release, meaning that anybody who acquires the block // we're about to free can use it safely since our writes (and reads!) will have happened-before then. entry->value.store(nullptr, std::memory_order_relaxed); } this->parent->add_block_to_free_list(block); // releases the above store } indexIndex = (indexIndex + 1) & (localBlockIndex->capacity - 1); } while (index != firstIndex + actualCount); return actualCount; } else { this->dequeueOvercommit.fetch_add(desiredCount, std::memory_order_release); } } return 0; } private: // The block size must be > 1, so any number with the low bit set is an invalid block base index static const index_t INVALID_BLOCK_BASE = 1; struct BlockIndexEntry { std::atomic key; std::atomic value; }; struct BlockIndexHeader { size_t capacity; std::atomic tail; BlockIndexEntry* entries; BlockIndexEntry** index; BlockIndexHeader* prev; }; template inline bool insert_block_index_entry(BlockIndexEntry*& idxEntry, index_t blockStartIndex) { auto localBlockIndex = blockIndex.load(std::memory_order_relaxed); // We're the only writer thread, relaxed is OK auto newTail = (localBlockIndex->tail.load(std::memory_order_relaxed) + 1) & (localBlockIndex->capacity - 1); idxEntry = localBlockIndex->index[newTail]; if (idxEntry->key.load(std::memory_order_relaxed) == INVALID_BLOCK_BASE || idxEntry->value.load(std::memory_order_relaxed) == nullptr) { idxEntry->key.store(blockStartIndex, std::memory_order_relaxed); localBlockIndex->tail.store(newTail, std::memory_order_release); return true; } // No room in the old block index, try to allocate another one! if (allocMode == CannotAlloc || !new_block_index()) { return false; } localBlockIndex = blockIndex.load(std::memory_order_relaxed); newTail = (localBlockIndex->tail.load(std::memory_order_relaxed) + 1) & (localBlockIndex->capacity - 1); idxEntry = localBlockIndex->index[newTail]; assert(idxEntry->key.load(std::memory_order_relaxed) == INVALID_BLOCK_BASE); idxEntry->key.store(blockStartIndex, std::memory_order_relaxed); localBlockIndex->tail.store(newTail, std::memory_order_release); return true; } inline void rewind_block_index_tail() { auto localBlockIndex = blockIndex.load(std::memory_order_relaxed); localBlockIndex->tail.store((localBlockIndex->tail.load(std::memory_order_relaxed) - 1) & (localBlockIndex->capacity - 1), std::memory_order_relaxed); } inline BlockIndexEntry* get_block_index_entry_for_index(index_t index) const { BlockIndexHeader* localBlockIndex; auto idx = get_block_index_index_for_index(index, localBlockIndex); return localBlockIndex->index[idx]; } inline size_t get_block_index_index_for_index(index_t index, BlockIndexHeader*& localBlockIndex) const { #if MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX debug::DebugLock lock(mutex); #endif index &= ~static_cast(BLOCK_SIZE - 1); localBlockIndex = blockIndex.load(std::memory_order_acquire); auto tail = localBlockIndex->tail.load(std::memory_order_acquire); auto tailBase = localBlockIndex->index[tail]->key.load(std::memory_order_relaxed); assert(tailBase != INVALID_BLOCK_BASE); // Note: Must use division instead of shift because the index may wrap around, causing a negative // offset, whose negativity we want to preserve auto offset = static_cast(static_cast::type>(index - tailBase) / BLOCK_SIZE); size_t idx = (tail + offset) & (localBlockIndex->capacity - 1); assert(localBlockIndex->index[idx]->key.load(std::memory_order_relaxed) == index && localBlockIndex->index[idx]->value.load(std::memory_order_relaxed) != nullptr); return idx; } bool new_block_index() { auto prev = blockIndex.load(std::memory_order_relaxed); size_t prevCapacity = prev == nullptr ? 0 : prev->capacity; auto entryCount = prev == nullptr ? nextBlockIndexCapacity : prevCapacity; auto raw = static_cast(Traits::malloc( sizeof(BlockIndexHeader) + std::alignment_of::value - 1 + sizeof(BlockIndexEntry) * entryCount + std::alignment_of::value - 1 + sizeof(BlockIndexEntry*) * nextBlockIndexCapacity)); if (raw == nullptr) { return false; } auto header = new (raw) BlockIndexHeader; auto entries = reinterpret_cast(details::align_for(raw + sizeof(BlockIndexHeader))); auto index = reinterpret_cast(details::align_for(reinterpret_cast(entries) + sizeof(BlockIndexEntry) * entryCount)); if (prev != nullptr) { auto prevTail = prev->tail.load(std::memory_order_relaxed); auto prevPos = prevTail; size_t i = 0; do { prevPos = (prevPos + 1) & (prev->capacity - 1); index[i++] = prev->index[prevPos]; } while (prevPos != prevTail); assert(i == prevCapacity); } for (size_t i = 0; i != entryCount; ++i) { new (entries + i) BlockIndexEntry; entries[i].key.store(INVALID_BLOCK_BASE, std::memory_order_relaxed); index[prevCapacity + i] = entries + i; } header->prev = prev; header->entries = entries; header->index = index; header->capacity = nextBlockIndexCapacity; header->tail.store((prevCapacity - 1) & (nextBlockIndexCapacity - 1), std::memory_order_relaxed); blockIndex.store(header, std::memory_order_release); nextBlockIndexCapacity <<= 1; return true; } private: size_t nextBlockIndexCapacity; std::atomic blockIndex; #ifdef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED public: details::ThreadExitListener threadExitListener; private: #endif #ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG public: ImplicitProducer* nextImplicitProducer; private: #endif #if MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX mutable debug::DebugMutex mutex; #endif #if MCDBGQ_TRACKMEM friend struct MemStats; #endif }; ////////////////////////////////// // Block pool manipulation ////////////////////////////////// void populate_initial_block_list(size_t blockCount) { initialBlockPoolSize = blockCount; if (initialBlockPoolSize == 0) { initialBlockPool = nullptr; return; } initialBlockPool = create_array(blockCount); if (initialBlockPool == nullptr) { initialBlockPoolSize = 0; } for (size_t i = 0; i < initialBlockPoolSize; ++i) { initialBlockPool[i].dynamicallyAllocated = false; } } inline Block* try_get_block_from_initial_pool() { if (initialBlockPoolIndex.load(std::memory_order_relaxed) >= initialBlockPoolSize) { return nullptr; } auto index = initialBlockPoolIndex.fetch_add(1, std::memory_order_relaxed); return index < initialBlockPoolSize ? (initialBlockPool + index) : nullptr; } inline void add_block_to_free_list(Block* block) { #if MCDBGQ_TRACKMEM block->owner = nullptr; #endif freeList.add(block); } inline void add_blocks_to_free_list(Block* block) { while (block != nullptr) { auto next = block->next; add_block_to_free_list(block); block = next; } } inline Block* try_get_block_from_free_list() { return freeList.try_get(); } // Gets a free block from one of the memory pools, or allocates a new one (if applicable) template Block* requisition_block() { auto block = try_get_block_from_initial_pool(); if (block != nullptr) { return block; } block = try_get_block_from_free_list(); if (block != nullptr) { return block; } if (canAlloc == CanAlloc) { return create(); } return nullptr; } #if MCDBGQ_TRACKMEM public: struct MemStats { size_t allocatedBlocks; size_t usedBlocks; size_t freeBlocks; size_t ownedBlocksExplicit; size_t ownedBlocksImplicit; size_t implicitProducers; size_t explicitProducers; size_t elementsEnqueued; size_t blockClassBytes; size_t queueClassBytes; size_t implicitBlockIndexBytes; size_t explicitBlockIndexBytes; friend class ConcurrentQueue; private: static MemStats getFor(ConcurrentQueue* q) { MemStats stats = { 0 }; stats.elementsEnqueued = q->size_approx(); auto block = q->freeList.head_unsafe(); while (block != nullptr) { ++stats.allocatedBlocks; ++stats.freeBlocks; block = block->freeListNext.load(std::memory_order_relaxed); } for (auto ptr = q->producerListTail.load(std::memory_order_acquire); ptr != nullptr; ptr = ptr->next_prod()) { bool implicit = dynamic_cast(ptr) != nullptr; stats.implicitProducers += implicit ? 1 : 0; stats.explicitProducers += implicit ? 0 : 1; if (implicit) { auto prod = static_cast(ptr); stats.queueClassBytes += sizeof(ImplicitProducer); auto head = prod->headIndex.load(std::memory_order_relaxed); auto tail = prod->tailIndex.load(std::memory_order_relaxed); auto hash = prod->blockIndex.load(std::memory_order_relaxed); if (hash != nullptr) { for (size_t i = 0; i != hash->capacity; ++i) { if (hash->index[i]->key.load(std::memory_order_relaxed) != ImplicitProducer::INVALID_BLOCK_BASE && hash->index[i]->value.load(std::memory_order_relaxed) != nullptr) { ++stats.allocatedBlocks; ++stats.ownedBlocksImplicit; } } stats.implicitBlockIndexBytes += hash->capacity * sizeof(typename ImplicitProducer::BlockIndexEntry); for (; hash != nullptr; hash = hash->prev) { stats.implicitBlockIndexBytes += sizeof(typename ImplicitProducer::BlockIndexHeader) + hash->capacity * sizeof(typename ImplicitProducer::BlockIndexEntry*); } } for (; details::circular_less_than(head, tail); head += BLOCK_SIZE) { //auto block = prod->get_block_index_entry_for_index(head); ++stats.usedBlocks; } } else { auto prod = static_cast(ptr); stats.queueClassBytes += sizeof(ExplicitProducer); auto tailBlock = prod->tailBlock; bool wasNonEmpty = false; if (tailBlock != nullptr) { auto block = tailBlock; do { ++stats.allocatedBlocks; if (!block->ConcurrentQueue::Block::template is_empty() || wasNonEmpty) { ++stats.usedBlocks; wasNonEmpty = wasNonEmpty || block != tailBlock; } ++stats.ownedBlocksExplicit; block = block->next; } while (block != tailBlock); } auto index = prod->blockIndex.load(std::memory_order_relaxed); while (index != nullptr) { stats.explicitBlockIndexBytes += sizeof(typename ExplicitProducer::BlockIndexHeader) + index->size * sizeof(typename ExplicitProducer::BlockIndexEntry); index = static_cast(index->prev); } } } auto freeOnInitialPool = q->initialBlockPoolIndex.load(std::memory_order_relaxed) >= q->initialBlockPoolSize ? 0 : q->initialBlockPoolSize - q->initialBlockPoolIndex.load(std::memory_order_relaxed); stats.allocatedBlocks += freeOnInitialPool; stats.freeBlocks += freeOnInitialPool; stats.blockClassBytes = sizeof(Block) * stats.allocatedBlocks; stats.queueClassBytes += sizeof(ConcurrentQueue); return stats; } }; // For debugging only. Not thread-safe. MemStats getMemStats() { return MemStats::getFor(this); } private: friend struct MemStats; #endif ////////////////////////////////// // Producer list manipulation ////////////////////////////////// ProducerBase* recycle_or_create_producer(bool isExplicit) { bool recycled; return recycle_or_create_producer(isExplicit, recycled); } ProducerBase* recycle_or_create_producer(bool isExplicit, bool& recycled) { #if MCDBGQ_NOLOCKFREE_IMPLICITPRODHASH debug::DebugLock lock(implicitProdMutex); #endif // Try to re-use one first for (auto ptr = producerListTail.load(std::memory_order_acquire); ptr != nullptr; ptr = ptr->next_prod()) { if (ptr->inactive.load(std::memory_order_relaxed) && ptr->isExplicit == isExplicit) { bool expected = true; if (ptr->inactive.compare_exchange_strong(expected, /* desired */ false, std::memory_order_acquire, std::memory_order_relaxed)) { // We caught one! It's been marked as activated, the caller can have it recycled = true; return ptr; } } } recycled = false; return add_producer(isExplicit ? static_cast(create(this)) : create(this)); } ProducerBase* add_producer(ProducerBase* producer) { // Handle failed memory allocation if (producer == nullptr) { return nullptr; } producerCount.fetch_add(1, std::memory_order_relaxed); // Add it to the lock-free list auto prevTail = producerListTail.load(std::memory_order_relaxed); do { producer->next = prevTail; } while (!producerListTail.compare_exchange_weak(prevTail, producer, std::memory_order_release, std::memory_order_relaxed)); #ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG if (producer->isExplicit) { auto prevTailExplicit = explicitProducers.load(std::memory_order_relaxed); do { static_cast(producer)->nextExplicitProducer = prevTailExplicit; } while (!explicitProducers.compare_exchange_weak(prevTailExplicit, static_cast(producer), std::memory_order_release, std::memory_order_relaxed)); } else { auto prevTailImplicit = implicitProducers.load(std::memory_order_relaxed); do { static_cast(producer)->nextImplicitProducer = prevTailImplicit; } while (!implicitProducers.compare_exchange_weak(prevTailImplicit, static_cast(producer), std::memory_order_release, std::memory_order_relaxed)); } #endif return producer; } void reown_producers() { // After another instance is moved-into/swapped-with this one, all the // producers we stole still think their parents are the other queue. // So fix them up! for (auto ptr = producerListTail.load(std::memory_order_relaxed); ptr != nullptr; ptr = ptr->next_prod()) { ptr->parent = this; } } ////////////////////////////////// // Implicit producer hash ////////////////////////////////// struct ImplicitProducerKVP { std::atomic key; ImplicitProducer* value; // No need for atomicity since it's only read by the thread that sets it in the first place ImplicitProducerKVP() { } ImplicitProducerKVP(ImplicitProducerKVP&& other) MOODYCAMEL_NOEXCEPT { key.store(other.key.load(std::memory_order_relaxed), std::memory_order_relaxed); value = other.value; } inline ImplicitProducerKVP& operator=(ImplicitProducerKVP&& other) MOODYCAMEL_NOEXCEPT { swap(other); return *this; } inline void swap(ImplicitProducerKVP& other) MOODYCAMEL_NOEXCEPT { if (this != &other) { details::swap_relaxed(key, other.key); std::swap(value, other.value); } } }; template friend void moodycamel::swap(typename ConcurrentQueue::ImplicitProducerKVP&, typename ConcurrentQueue::ImplicitProducerKVP&) MOODYCAMEL_NOEXCEPT; struct ImplicitProducerHash { size_t capacity; ImplicitProducerKVP* entries; ImplicitProducerHash* prev; }; inline void populate_initial_implicit_producer_hash() { if (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) return; implicitProducerHashCount.store(0, std::memory_order_relaxed); auto hash = &initialImplicitProducerHash; hash->capacity = INITIAL_IMPLICIT_PRODUCER_HASH_SIZE; hash->entries = &initialImplicitProducerHashEntries[0]; for (size_t i = 0; i != INITIAL_IMPLICIT_PRODUCER_HASH_SIZE; ++i) { initialImplicitProducerHashEntries[i].key.store(details::invalid_thread_id, std::memory_order_relaxed); } hash->prev = nullptr; implicitProducerHash.store(hash, std::memory_order_relaxed); } void swap_implicit_producer_hashes(ConcurrentQueue& other) { if (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) return; // Swap (assumes our implicit producer hash is initialized) initialImplicitProducerHashEntries.swap(other.initialImplicitProducerHashEntries); initialImplicitProducerHash.entries = &initialImplicitProducerHashEntries[0]; other.initialImplicitProducerHash.entries = &other.initialImplicitProducerHashEntries[0]; details::swap_relaxed(implicitProducerHashCount, other.implicitProducerHashCount); details::swap_relaxed(implicitProducerHash, other.implicitProducerHash); if (implicitProducerHash.load(std::memory_order_relaxed) == &other.initialImplicitProducerHash) { implicitProducerHash.store(&initialImplicitProducerHash, std::memory_order_relaxed); } else { ImplicitProducerHash* hash; for (hash = implicitProducerHash.load(std::memory_order_relaxed); hash->prev != &other.initialImplicitProducerHash; hash = hash->prev) { continue; } hash->prev = &initialImplicitProducerHash; } if (other.implicitProducerHash.load(std::memory_order_relaxed) == &initialImplicitProducerHash) { other.implicitProducerHash.store(&other.initialImplicitProducerHash, std::memory_order_relaxed); } else { ImplicitProducerHash* hash; for (hash = other.implicitProducerHash.load(std::memory_order_relaxed); hash->prev != &initialImplicitProducerHash; hash = hash->prev) { continue; } hash->prev = &other.initialImplicitProducerHash; } } // Only fails (returns nullptr) if memory allocation fails ImplicitProducer* get_or_add_implicit_producer() { // Note that since the data is essentially thread-local (key is thread ID), // there's a reduced need for fences (memory ordering is already consistent // for any individual thread), except for the current table itself. // Start by looking for the thread ID in the current and all previous hash tables. // If it's not found, it must not be in there yet, since this same thread would // have added it previously to one of the tables that we traversed. // Code and algorithm adapted from http://preshing.com/20130605/the-worlds-simplest-lock-free-hash-table #if MCDBGQ_NOLOCKFREE_IMPLICITPRODHASH debug::DebugLock lock(implicitProdMutex); #endif auto id = details::thread_id(); auto hashedId = details::hash_thread_id(id); auto mainHash = implicitProducerHash.load(std::memory_order_acquire); for (auto hash = mainHash; hash != nullptr; hash = hash->prev) { // Look for the id in this hash auto index = hashedId; while (true) { // Not an infinite loop because at least one slot is free in the hash table index &= hash->capacity - 1; auto probedKey = hash->entries[index].key.load(std::memory_order_relaxed); if (probedKey == id) { // Found it! If we had to search several hashes deep, though, we should lazily add it // to the current main hash table to avoid the extended search next time. // Note there's guaranteed to be room in the current hash table since every subsequent // table implicitly reserves space for all previous tables (there's only one // implicitProducerHashCount). auto value = hash->entries[index].value; if (hash != mainHash) { index = hashedId; while (true) { index &= mainHash->capacity - 1; probedKey = mainHash->entries[index].key.load(std::memory_order_relaxed); auto empty = details::invalid_thread_id; #ifdef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED auto reusable = details::invalid_thread_id2; if ((probedKey == empty && mainHash->entries[index].key.compare_exchange_strong(empty, id, std::memory_order_relaxed)) || (probedKey == reusable && mainHash->entries[index].key.compare_exchange_strong(reusable, id, std::memory_order_acquire))) { #else if ((probedKey == empty && mainHash->entries[index].key.compare_exchange_strong(empty, id, std::memory_order_relaxed))) { #endif mainHash->entries[index].value = value; break; } ++index; } } return value; } if (probedKey == details::invalid_thread_id) { break; // Not in this hash table } ++index; } } // Insert! auto newCount = 1 + implicitProducerHashCount.fetch_add(1, std::memory_order_relaxed); while (true) { if (newCount >= (mainHash->capacity >> 1) && !implicitProducerHashResizeInProgress.test_and_set(std::memory_order_acquire)) { // We've acquired the resize lock, try to allocate a bigger hash table. // Note the acquire fence synchronizes with the release fence at the end of this block, and hence when // we reload implicitProducerHash it must be the most recent version (it only gets changed within this // locked block). mainHash = implicitProducerHash.load(std::memory_order_acquire); if (newCount >= (mainHash->capacity >> 1)) { auto newCapacity = mainHash->capacity << 1; while (newCount >= (newCapacity >> 1)) { newCapacity <<= 1; } auto raw = static_cast(Traits::malloc(sizeof(ImplicitProducerHash) + std::alignment_of::value - 1 + sizeof(ImplicitProducerKVP) * newCapacity)); if (raw == nullptr) { // Allocation failed implicitProducerHashCount.fetch_add(-1, std::memory_order_relaxed); implicitProducerHashResizeInProgress.clear(std::memory_order_relaxed); return nullptr; } auto newHash = new (raw) ImplicitProducerHash; newHash->capacity = newCapacity; newHash->entries = reinterpret_cast(details::align_for(raw + sizeof(ImplicitProducerHash))); for (size_t i = 0; i != newCapacity; ++i) { new (newHash->entries + i) ImplicitProducerKVP; newHash->entries[i].key.store(details::invalid_thread_id, std::memory_order_relaxed); } newHash->prev = mainHash; implicitProducerHash.store(newHash, std::memory_order_release); implicitProducerHashResizeInProgress.clear(std::memory_order_release); mainHash = newHash; } else { implicitProducerHashResizeInProgress.clear(std::memory_order_release); } } // If it's < three-quarters full, add to the old one anyway so that we don't have to wait for the next table // to finish being allocated by another thread (and if we just finished allocating above, the condition will // always be true) if (newCount < (mainHash->capacity >> 1) + (mainHash->capacity >> 2)) { bool recycled; auto producer = static_cast(recycle_or_create_producer(false, recycled)); if (producer == nullptr) { implicitProducerHashCount.fetch_add(-1, std::memory_order_relaxed); return nullptr; } if (recycled) { implicitProducerHashCount.fetch_add(-1, std::memory_order_relaxed); } #ifdef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED producer->threadExitListener.callback = &ConcurrentQueue::implicit_producer_thread_exited_callback; producer->threadExitListener.userData = producer; details::ThreadExitNotifier::subscribe(&producer->threadExitListener); #endif auto index = hashedId; while (true) { index &= mainHash->capacity - 1; auto probedKey = mainHash->entries[index].key.load(std::memory_order_relaxed); auto empty = details::invalid_thread_id; #ifdef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED auto reusable = details::invalid_thread_id2; if ((probedKey == empty && mainHash->entries[index].key.compare_exchange_strong(empty, id, std::memory_order_relaxed)) || (probedKey == reusable && mainHash->entries[index].key.compare_exchange_strong(reusable, id, std::memory_order_acquire))) { #else if ((probedKey == empty && mainHash->entries[index].key.compare_exchange_strong(empty, id, std::memory_order_relaxed))) { #endif mainHash->entries[index].value = producer; break; } ++index; } return producer; } // Hmm, the old hash is quite full and somebody else is busy allocating a new one. // We need to wait for the allocating thread to finish (if it succeeds, we add, if not, // we try to allocate ourselves). mainHash = implicitProducerHash.load(std::memory_order_acquire); } } #ifdef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED void implicit_producer_thread_exited(ImplicitProducer* producer) { // Remove from thread exit listeners details::ThreadExitNotifier::unsubscribe(&producer->threadExitListener); // Remove from hash #if MCDBGQ_NOLOCKFREE_IMPLICITPRODHASH debug::DebugLock lock(implicitProdMutex); #endif auto hash = implicitProducerHash.load(std::memory_order_acquire); assert(hash != nullptr); // The thread exit listener is only registered if we were added to a hash in the first place auto id = details::thread_id(); auto hashedId = details::hash_thread_id(id); details::thread_id_t probedKey; // We need to traverse all the hashes just in case other threads aren't on the current one yet and are // trying to add an entry thinking there's a free slot (because they reused a producer) for (; hash != nullptr; hash = hash->prev) { auto index = hashedId; do { index &= hash->capacity - 1; probedKey = hash->entries[index].key.load(std::memory_order_relaxed); if (probedKey == id) { hash->entries[index].key.store(details::invalid_thread_id2, std::memory_order_release); break; } ++index; } while (probedKey != details::invalid_thread_id); // Can happen if the hash has changed but we weren't put back in it yet, or if we weren't added to this hash in the first place } // Mark the queue as being recyclable producer->inactive.store(true, std::memory_order_release); } static void implicit_producer_thread_exited_callback(void* userData) { auto producer = static_cast(userData); auto queue = producer->parent; queue->implicit_producer_thread_exited(producer); } #endif ////////////////////////////////// // Utility functions ////////////////////////////////// template static inline U* create_array(size_t count) { assert(count > 0); auto p = static_cast(Traits::malloc(sizeof(U) * count)); if (p == nullptr) { return nullptr; } for (size_t i = 0; i != count; ++i) { new (p + i) U(); } return p; } template static inline void destroy_array(U* p, size_t count) { if (p != nullptr) { assert(count > 0); for (size_t i = count; i != 0; ) { (p + --i)->~U(); } Traits::free(p); } } template static inline U* create() { auto p = Traits::malloc(sizeof(U)); return p != nullptr ? new (p) U : nullptr; } template static inline U* create(A1&& a1) { auto p = Traits::malloc(sizeof(U)); return p != nullptr ? new (p) U(std::forward(a1)) : nullptr; } template static inline void destroy(U* p) { if (p != nullptr) { p->~U(); } Traits::free(p); } private: std::atomic producerListTail; std::atomic producerCount; std::atomic initialBlockPoolIndex; Block* initialBlockPool; size_t initialBlockPoolSize; #if !MCDBGQ_USEDEBUGFREELIST FreeList freeList; #else debug::DebugFreeList freeList; #endif std::atomic implicitProducerHash; std::atomic implicitProducerHashCount; // Number of slots logically used ImplicitProducerHash initialImplicitProducerHash; std::array initialImplicitProducerHashEntries; std::atomic_flag implicitProducerHashResizeInProgress; std::atomic nextExplicitConsumerId; std::atomic globalExplicitConsumerOffset; #if MCDBGQ_NOLOCKFREE_IMPLICITPRODHASH debug::DebugMutex implicitProdMutex; #endif #ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG std::atomic explicitProducers; std::atomic implicitProducers; #endif }; template ProducerToken::ProducerToken(ConcurrentQueue& queue) : producer(queue.recycle_or_create_producer(true)) { if (producer != nullptr) { producer->token = this; } } template ProducerToken::ProducerToken(BlockingConcurrentQueue& queue) : producer(reinterpret_cast*>(&queue)->recycle_or_create_producer(true)) { if (producer != nullptr) { producer->token = this; } } template ConsumerToken::ConsumerToken(ConcurrentQueue& queue) : itemsConsumedFromCurrent(0), currentProducer(nullptr), desiredProducer(nullptr) { initialOffset = queue.nextExplicitConsumerId.fetch_add(1, std::memory_order_release); lastKnownGlobalOffset = -1; } template ConsumerToken::ConsumerToken(BlockingConcurrentQueue& queue) : itemsConsumedFromCurrent(0), currentProducer(nullptr), desiredProducer(nullptr) { initialOffset = reinterpret_cast*>(&queue)->nextExplicitConsumerId.fetch_add(1, std::memory_order_release); lastKnownGlobalOffset = -1; } template inline void swap(ConcurrentQueue& a, ConcurrentQueue& b) MOODYCAMEL_NOEXCEPT { a.swap(b); } inline void swap(ProducerToken& a, ProducerToken& b) MOODYCAMEL_NOEXCEPT { a.swap(b); } inline void swap(ConsumerToken& a, ConsumerToken& b) MOODYCAMEL_NOEXCEPT { a.swap(b); } template inline void swap(typename ConcurrentQueue::ImplicitProducerKVP& a, typename ConcurrentQueue::ImplicitProducerKVP& b) MOODYCAMEL_NOEXCEPT { a.swap(b); } } #if defined(__GNUC__) #pragma GCC diagnostic pop #endif fastnetmon-1.1.3+dfsg/src/example_plugin/000077500000000000000000000000001313534057500204175ustar00rootroot00000000000000fastnetmon-1.1.3+dfsg/src/example_plugin/example_collector.cpp000066400000000000000000000042411313534057500246250ustar00rootroot00000000000000// log4cpp logging facility #include "log4cpp/Category.hh" #include "log4cpp/Appender.hh" #include "log4cpp/FileAppender.hh" #include "log4cpp/OstreamAppender.hh" #include "log4cpp/Layout.hh" #include "log4cpp/BasicLayout.hh" #include "log4cpp/PatternLayout.hh" #include "log4cpp/Priority.hh" // For support uint32_t, uint16_t #include // For config map operations #include #include // For support: IPPROTO_TCP, IPPROTO_ICMP, IPPROTO_UDP #include #include #include #include "example_collector.h" // Get log4cpp logger from main programm extern log4cpp::Category& logger; // Global configuration map extern std::map configuration_map; // This variable name should be uniq for every plugin! process_packet_pointer example_process_func_ptr = NULL; void start_example_collection(process_packet_pointer func_ptr) { logger << log4cpp::Priority::INFO << "Example plugin started"; example_process_func_ptr = func_ptr; std::string example_plugin_config_param = ""; if (configuration_map.count("some_plugin_param_from_global_config") != 0) { example_plugin_config_param = configuration_map["some_plugin_param_from_global_config"]; } // We should fill this structure for passing to FastNetMon simple_packet current_packet; current_packet.src_ip = 0; current_packet.dst_ip = 0; current_packet.ts.tv_sec = 0; current_packet.ts.tv_usec = 0; current_packet.flags = 0; // There we store packet length or total length of aggregated stream current_packet.length = 128; // Number of received packets, it's not equal to 1 only for aggregated data like netflow current_packet.number_of_packets = 1; // If your data sampled current_packet.sample_ratio = 1; /* ICMP */ current_packet.protocol = IPPROTO_ICMP; /* TCP */ current_packet.protocol = IPPROTO_TCP; current_packet.source_port = 0; current_packet.destination_port = 0; /* UDP */ current_packet.protocol = IPPROTO_UDP; current_packet.source_port = 0; current_packet.destination_port = 0; example_process_func_ptr(current_packet); } fastnetmon-1.1.3+dfsg/src/example_plugin/example_collector.h000066400000000000000000000003161313534057500242710ustar00rootroot00000000000000#ifndef EXAMPLE_PLUGIN_H #define EXAMPLE_PLUGIN_H #include "../fastnetmon_types.h" // This function should be implemented in plugin void start_example_collection(process_packet_pointer func_ptr); #endif fastnetmon-1.1.3+dfsg/src/fast_dpi.cpp000066400000000000000000000027721313534057500177130ustar00rootroot00000000000000#include "fast_dpi.h" void debug_printf(u_int32_t protocol, void *id_struct, ndpi_log_level_t log_level, const char *format, ...) { va_list va_ap; struct tm result; char buf[8192], out_buf[8192]; char theDate[32]; const char *extra_msg = ""; time_t theTime = time(NULL); va_start (va_ap, format); /* if(log_level == NDPI_LOG_ERROR) extra_msg = "ERROR: "; else if(log_level == NDPI_LOG_TRACE) extra_msg = "TRACE: "; else extra_msg = "DEBUG: "; */ memset(buf, 0, sizeof(buf)); strftime(theDate, 32, "%d/%b/%Y %H:%M:%S", localtime_r(&theTime, &result) ); vsnprintf(buf, sizeof(buf)-1, format, va_ap); snprintf(out_buf, sizeof(out_buf), "%s %s%s", theDate, extra_msg, buf); printf("%s", out_buf); fflush(stdout); va_end(va_ap); } struct ndpi_detection_module_struct* init_ndpi() { u_int32_t detection_tick_resolution = 1000; struct ndpi_detection_module_struct* my_ndpi_struct = ndpi_init_detection_module(detection_tick_resolution, malloc, free, debug_printf); if (my_ndpi_struct == NULL) { // printf("Can't init nDPI"); return NULL; } NDPI_PROTOCOL_BITMASK all; // enable all protocols NDPI_BITMASK_SET_ALL(all); ndpi_set_protocol_detection_bitmask2(my_ndpi_struct, &all); // Load custom protocols // ndpi_load_protocols_file(ndpi_thread_info[thread_id].ndpi_struct, _protoFilePath); //printf("nDPI started correctly\n"); return my_ndpi_struct; } fastnetmon-1.1.3+dfsg/src/fast_dpi.h000066400000000000000000000002241313534057500173460ustar00rootroot00000000000000#ifndef FAST_DPI_H #define FAST_DPI_H #include #include "libndpi/ndpi_api.h" struct ndpi_detection_module_struct* init_ndpi(); #endif fastnetmon-1.1.3+dfsg/src/fast_library.cpp000066400000000000000000001245041313534057500206010ustar00rootroot00000000000000#include #include #include "fast_library.h" #include #include // atoi #include #include #include #include #include #include #include #include #include #include #include "log4cpp/Category.hh" #include "log4cpp/Appender.hh" #include "log4cpp/FileAppender.hh" #include "log4cpp/OstreamAppender.hh" #include "log4cpp/Layout.hh" #include "log4cpp/BasicLayout.hh" #include "log4cpp/PatternLayout.hh" #include "log4cpp/Priority.hh" #include #if defined(__APPLE__) #include // Source: https://gist.github.com/pavel-odintsov/d13684600423d1c5e64e #define be64toh(x) OSSwapBigToHostInt64(x) #define htobe64(x) OSSwapHostToBigInt64(x) #endif // For be64toh and htobe64 #if defined(__FreeBSD__) || defined(__DragonFly__) #include #endif boost::regex regular_expression_cidr_pattern("^\\d+\\.\\d+\\.\\d+\\.\\d+\\/\\d+$"); boost::regex regular_expression_host_pattern("^\\d+\\.\\d+\\.\\d+\\.\\d+$"); // convert string to integer int convert_string_to_integer(std::string line) { return atoi(line.c_str()); } // Type safe versions of ntohl, ntohs with type control uint16_t fast_ntoh(uint16_t value) { return ntohs(value); } uint32_t fast_ntoh(uint32_t value) { return ntohl(value); } // network (big endian) byte order to host byte order uint64_t fast_ntoh(uint64_t value) { return be64toh(value); } // Type safe version of htonl, htons uint16_t fast_hton(uint16_t value) { return htons(value); } uint32_t fast_hton(uint32_t value) { return htonl(value); } uint64_t fast_hton(uint64_t value) { // host to big endian (network byte order) return htobe64(value); } uint32_t convert_ip_as_string_to_uint(std::string ip) { struct in_addr ip_addr; inet_aton(ip.c_str(), &ip_addr); // in network byte order return ip_addr.s_addr; } std::string convert_ip_as_uint_to_string(uint32_t ip_as_integer) { struct in_addr ip_addr; ip_addr.s_addr = ip_as_integer; return (std::string)inet_ntoa(ip_addr); } // convert integer to string std::string convert_int_to_string(int value) { std::stringstream out; out << value; return out.str(); } // BE AWARE! WE USE NON STANDARD SUBNET_T HERE! WE USE NON CIDR MASK HERE! subnet_t convert_subnet_from_string_to_binary(std::string subnet_cidr) { std::vector subnet_as_string; split(subnet_as_string, subnet_cidr, boost::is_any_of("/"), boost::token_compress_on); unsigned int cidr = convert_string_to_integer(subnet_as_string[1]); uint32_t subnet_as_int = convert_ip_as_string_to_uint(subnet_as_string[0]); uint32_t netmask_as_int = convert_cidr_to_binary_netmask(cidr); return std::make_pair(subnet_as_int, netmask_as_int); } subnet_t convert_subnet_from_string_to_binary_with_cidr_format(std::string subnet_cidr) { std::vector subnet_as_string; split(subnet_as_string, subnet_cidr, boost::is_any_of("/"), boost::token_compress_on); unsigned int cidr = convert_string_to_integer(subnet_as_string[1]); uint32_t subnet_as_int = convert_ip_as_string_to_uint(subnet_as_string[0]); return std::make_pair(subnet_as_int, cidr); } void copy_networks_from_string_form_to_binary(std::vector networks_list_as_string, std::vector& our_networks) { for (std::vector::iterator ii = networks_list_as_string.begin(); ii != networks_list_as_string.end(); ++ii) { subnet_t current_subnet = convert_subnet_from_string_to_binary(*ii); our_networks.push_back(current_subnet); } } std::string convert_subnet_to_string(subnet_t my_subnet) { std::stringstream buffer; buffer< subnet_as_string; split(subnet_as_string, network_cidr_format, boost::is_any_of("/"), boost::token_compress_on); if (subnet_as_string.size() != 2) { return 0; } return convert_string_to_integer(subnet_as_string[1]); } std::string print_time_t_in_fastnetmon_format(time_t current_time) { struct tm* timeinfo; char buffer[80]; timeinfo = localtime(¤t_time); strftime(buffer, sizeof(buffer), "%d_%m_%y_%H:%M:%S", timeinfo); return std::string(buffer); } // extract 192.168.1.1 from 192.168.1.1/24 std::string get_net_address_from_network_as_string(std::string network_cidr_format) { std::vector subnet_as_string; split(subnet_as_string, network_cidr_format, boost::is_any_of("/"), boost::token_compress_on); if (subnet_as_string.size() != 2) { return 0; } return subnet_as_string[0]; } std::string get_printable_protocol_name(unsigned int protocol) { std::string proto_name; switch (protocol) { case IPPROTO_TCP: proto_name = "tcp"; break; case IPPROTO_UDP: proto_name = "udp"; break; case IPPROTO_ICMP: proto_name = "icmp"; break; default: proto_name = "unknown"; break; } return proto_name; } uint32_t convert_cidr_to_binary_netmask(unsigned int cidr) { uint32_t binary_netmask = 0xFFFFFFFF; binary_netmask = binary_netmask << (32 - cidr); // htonl from host byte order to network // ntohl from network byte order to host // We need network byte order at output return htonl(binary_netmask); } bool is_cidr_subnet(const char* subnet) { boost::cmatch what; if (regex_match(subnet, what, regular_expression_cidr_pattern)) { return true; } else { return false; } } bool is_v4_host(std::string host) { boost::cmatch what; return regex_match(host.c_str(), what, regular_expression_host_pattern); } // check file existence bool file_exists(std::string path) { FILE* check_file = fopen(path.c_str(), "r"); if (check_file) { fclose(check_file); return true; } else { return false; } } bool folder_exists(std::string path) { if (access(path.c_str(), 0) == 0) { struct stat status; stat(path.c_str(), &status); if (status.st_mode & S_IFDIR) { return true; } } return false; } #define BIG_CONSTANT(x) (x##LLU) /* // calculate hash unsigned int seed = 11; uint64_t hash = MurmurHash64A(¤t_packet, sizeof(current_packet), seed); */ // https://code.google.com/p/smhasher/source/browse/trunk/MurmurHash2.cpp // 64-bit hash for 64-bit platforms uint64_t MurmurHash64A(const void* key, int len, uint64_t seed) { const uint64_t m = BIG_CONSTANT(0xc6a4a7935bd1e995); const int r = 47; uint64_t h = seed ^ (len * m); const uint64_t* data = (const uint64_t*)key; const uint64_t* end = data + (len / 8); while (data != end) { uint64_t k = *data++; k *= m; k ^= k >> r; k *= m; h ^= k; h *= m; } const unsigned char* data2 = (const unsigned char*)data; switch (len & 7) { case 7: h ^= uint64_t(data2[6]) << 48; case 6: h ^= uint64_t(data2[5]) << 40; case 5: h ^= uint64_t(data2[4]) << 32; case 4: h ^= uint64_t(data2[3]) << 24; case 3: h ^= uint64_t(data2[2]) << 16; case 2: h ^= uint64_t(data2[1]) << 8; case 1: h ^= uint64_t(data2[0]); h *= m; }; h ^= h >> r; h *= m; h ^= h >> r; return h; } // http://www.gnu.org/software/libc/manual/html_node/Elapsed-Time.html int timeval_subtract(struct timeval* result, struct timeval* x, struct timeval* y) { /* Perform the carry for the later subtraction by updating y. */ if (x->tv_usec < y->tv_usec) { int nsec = (y->tv_usec - x->tv_usec) / 1000000 + 1; y->tv_usec -= 1000000 * nsec; y->tv_sec += nsec; } if (x->tv_usec - y->tv_usec > 1000000) { int nsec = (x->tv_usec - y->tv_usec) / 1000000; y->tv_usec += 1000000 * nsec; y->tv_sec -= nsec; } /* Compute the time remaining to wait. tv_usec is certainly positive. */ result->tv_sec = x->tv_sec - y->tv_sec; result->tv_usec = x->tv_usec - y->tv_usec; /* Return 1 if result is negative. */ return x->tv_sec < y->tv_sec; } std::string print_tcp_flags(uint8_t flag_value) { if (flag_value == 0) { return "-"; } // cod from pfring.h // (tcp->fin * TH_FIN_MULTIPLIER) + (tcp->syn * TH_SYN_MULTIPLIER) + // (tcp->rst * TH_RST_MULTIPLIER) + (tcp->psh * TH_PUSH_MULTIPLIER) + // (tcp->ack * TH_ACK_MULTIPLIER) + (tcp->urg * TH_URG_MULTIPLIER); /* // Required for decoding tcp flags #define TH_FIN_MULTIPLIER 0x01 #define TH_SYN_MULTIPLIER 0x02 #define TH_RST_MULTIPLIER 0x04 #define TH_PUSH_MULTIPLIER 0x08 #define TH_ACK_MULTIPLIER 0x10 #define TH_URG_MULTIPLIER 0x20 */ std::vector all_flags; if (extract_bit_value(flag_value, TCP_FIN_FLAG_SHIFT)) { all_flags.push_back("fin"); } if (extract_bit_value(flag_value, TCP_SYN_FLAG_SHIFT)) { all_flags.push_back("syn"); } if (extract_bit_value(flag_value, TCP_RST_FLAG_SHIFT)) { all_flags.push_back("rst"); } if (extract_bit_value(flag_value, TCP_PSH_FLAG_SHIFT)) { all_flags.push_back("psh"); } if (extract_bit_value(flag_value, TCP_ACK_FLAG_SHIFT)) { all_flags.push_back("ack"); } if (extract_bit_value(flag_value, TCP_URG_FLAG_SHIFT)) { all_flags.push_back("urg"); } std::ostringstream flags_as_string; if (all_flags.empty()) { return "-"; } // concatenate all vector elements with comma std::copy(all_flags.begin(), all_flags.end() - 1, std::ostream_iterator(flags_as_string, ",")); // add last element flags_as_string << all_flags.back(); return flags_as_string.str(); } std::vector split_strings_to_vector_by_comma(std::string raw_string) { std::vector splitted_strings; boost::split(splitted_strings, raw_string, boost::is_any_of(","), boost::token_compress_on); return splitted_strings; } // http://stackoverflow.com/questions/14528233/bit-masking-in-c-how-to-get-first-bit-of-a-byte int extract_bit_value(uint8_t num, int bit) { if (bit > 0 && bit <= 8) { return ((num >> (bit - 1)) & 1); } else { return 0; } } // Overloaded version with 16 bit integer support int extract_bit_value(uint16_t num, int bit) { if (bit > 0 && bit <= 16) { return ((num >> (bit - 1)) & 1); } else { return 0; } } int set_bit_value(uint8_t& num, int bit) { if (bit > 0 && bit <= 8) { num = num | 1 << (bit - 1); return 1; } else { return 0; } } int set_bit_value(uint16_t& num, int bit) { if (bit > 0 && bit <= 16) { num = num | 1 << (bit - 1); return 1; } else { return 0; } } int clear_bit_value(uint8_t& num, int bit) { if (bit > 0 && bit <= 8) { num = num & ~(1 << (bit - 1) ); return 1; } else { return 0; } } // http://stackoverflow.com/questions/47981/how-do-you-set-clear-and-toggle-a-single-bit-in-c-c int clear_bit_value(uint16_t& num, int bit) { if (bit > 0 && bit <= 16) { num = num & ~(1 << (bit - 1) ); return 1; } else { return 0; } } std::string print_simple_packet(simple_packet packet) { std::stringstream buffer; if (packet.ts.tv_sec == 0) { // PF_RING and netmap do not generate timestamp for all packets because it's very CPU // intensive operation // But we want pretty attack report and fill it there gettimeofday(&packet.ts, NULL); } buffer << convert_timeval_to_date(packet.ts) << " "; std::string source_ip_as_string = ""; std::string destination_ip_as_string = ""; if (packet.ip_protocol_version == 4) { source_ip_as_string = convert_ip_as_uint_to_string(packet.src_ip); destination_ip_as_string = convert_ip_as_uint_to_string(packet.dst_ip); } else if (packet.ip_protocol_version == 6) { source_ip_as_string = print_ipv6_address(packet.src_ipv6); destination_ip_as_string = print_ipv6_address(packet.dst_ipv6); } else { // WTF? } buffer << source_ip_as_string << ":" << packet.source_port << " > " << destination_ip_as_string << ":" << packet.destination_port << " protocol: " << get_printable_protocol_name(packet.protocol); // Print flags only for TCP if (packet.protocol == IPPROTO_TCP) { buffer << " flags: " << print_tcp_flags(packet.flags); } buffer << " frag: " << packet.ip_fragmented << " "; buffer << " "; buffer << "packets: " << packet.number_of_packets << " "; buffer << "size: " << packet.length << " bytes "; // We should cast it to integer because otherwise it will be interpreted as char buffer << "ttl: " << unsigned(packet.ttl) << " "; buffer << "sample ratio: " << packet.sample_ratio << " "; buffer << " \n"; return buffer.str(); } std::string convert_timeval_to_date(struct timeval tv) { time_t nowtime = tv.tv_sec; struct tm* nowtm = localtime(&nowtime); char tmbuf[64]; char buf[64]; strftime(tmbuf, sizeof(tmbuf), "%Y-%m-%d %H:%M:%S", nowtm); #if defined(__APPLE__) snprintf(buf, sizeof(buf), "%s.%06d", tmbuf, tv.tv_usec); #else snprintf(buf, sizeof(buf), "%s.%06ld", tmbuf, tv.tv_usec); #endif return std::string(buf); } uint64_t convert_speed_to_mbps(uint64_t speed_in_bps) { return uint64_t((double)speed_in_bps / 1024 / 1024 * 8); } std::string get_protocol_name_by_number(unsigned int proto_number) { struct protoent* proto_ent = getprotobynumber(proto_number); std::string proto_name = proto_ent->p_name; return proto_name; } // exec command in shell std::vector exec(std::string cmd) { std::vector output_list; FILE* pipe = popen(cmd.c_str(), "r"); if (!pipe) return output_list; char buffer[256]; while (!feof(pipe)) { if (fgets(buffer, 256, pipe) != NULL) { size_t newbuflen = strlen(buffer); // remove newline at the end if (buffer[newbuflen - 1] == '\n') { buffer[newbuflen - 1] = '\0'; } output_list.push_back(buffer); } } pclose(pipe); return output_list; } bool print_pid_to_file(pid_t pid, std::string pid_path) { std::ofstream pid_file; pid_file.open(pid_path.c_str(), std::ios::trunc); if (pid_file.is_open()) { pid_file << pid << "\n"; pid_file.close(); return true; } else { return false; } } bool read_pid_from_file(pid_t& pid, std::string pid_path) { std::fstream pid_file(pid_path.c_str(), std::ios_base::in); if (pid_file.is_open()) { pid_file >> pid; pid_file.close(); return true; } else { return false; } } bool store_data_to_graphite(unsigned short int graphite_port, std::string graphite_host, graphite_data_t graphite_data) { int client_sockfd = socket(AF_INET, SOCK_STREAM, 0); if (client_sockfd < 0) { return false; } struct sockaddr_in serv_addr; memset(&serv_addr, 0, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_port = htons(graphite_port); int pton_result = inet_pton(AF_INET, graphite_host.c_str(), &serv_addr.sin_addr); if (pton_result <= 0) { close(client_sockfd); return false; } int connect_result = connect(client_sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)); if (connect_result < 0) { close(client_sockfd); return false; } std::stringstream buffer; time_t current_time = time(NULL); for (graphite_data_t::iterator itr = graphite_data.begin(); itr != graphite_data.end(); ++itr) { buffer << itr->first << " " << itr->second << " " << current_time << "\n"; } std::string buffer_as_string = buffer.str(); int write_result = write(client_sockfd, buffer_as_string.c_str(), buffer_as_string.size()); close(client_sockfd); if (write_result > 0) { return true; } else { return false; } } // Get list of all available interfaces on the server interfaces_list_t get_interfaces_list() { interfaces_list_t interfaces_list; // Format: 1: eth0: < .... boost::regex interface_name_pattern("^\\d+:\\s+(\\w+):.*?$"); std::vector output_list = exec("ip -o link show"); if (output_list.empty()) { return interfaces_list; } for (std::vector::iterator iter = output_list.begin(); iter != output_list.end(); ++iter) { boost::match_results regex_results; if (boost::regex_match(*iter, regex_results, interface_name_pattern)) { // std::cout<<"Interface: "< output_list = exec("ip address show dev " + interface); if (output_list.empty()) { return ip_list; } boost::regex interface_alias_pattern("^\\s+inet\\s+(\\d+\\.\\d+\\.\\d+\\.\\d+).*?$"); // inet 188.40.35.142 for (std::vector::iterator iter = output_list.begin(); iter != output_list.end(); ++iter) { boost::match_results regex_results; if (boost::regex_match(*iter, regex_results, interface_alias_pattern)) { ip_list.push_back(regex_results[1]); // std::cout<<"IP: "< list_of_ignored_interfaces; list_of_ignored_interfaces.push_back("lo"); list_of_ignored_interfaces.push_back("venet0"); interfaces_list_t interfaces_list = get_interfaces_list(); if (interfaces_list.empty()) { return ip_list; } for (interfaces_list_t::iterator iter = interfaces_list.begin(); iter != interfaces_list.end(); ++iter) { std::vector::iterator iter_exclude_list = std::find(list_of_ignored_interfaces.begin(), list_of_ignored_interfaces.end(), *iter); // Skip ignored interface if (iter_exclude_list != list_of_ignored_interfaces.end()) { continue; } // std::cout<<*iter<add.sin.s_addr); return address + "/" + convert_int_to_string(prefix->bitlen); } std::string find_subnet_by_ip_in_string_format(patricia_tree_t* patricia_tree, std::string ip) { patricia_node_t* found_patrica_node = NULL; // Convert IP to integer uint32_t client_ip = convert_ip_as_string_to_uint(ip); prefix_t prefix_for_check_adreess; prefix_for_check_adreess.add.sin.s_addr = client_ip; prefix_for_check_adreess.family = AF_INET; prefix_for_check_adreess.bitlen = 32; found_patrica_node = patricia_search_best2(patricia_tree, &prefix_for_check_adreess, 1); if (found_patrica_node != NULL) { return convert_prefix_to_string_representation(found_patrica_node->prefix); } else { return ""; } } // It could not be on start or end of the line boost::regex ipv6_address_compression_algorithm("(0000:){2,}"); std::string print_ipv6_address(struct in6_addr& ipv6_address) { char buffer[128]; // For short print uint8_t* b = ipv6_address.s6_addr; sprintf(buffer, "%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x", b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7], b[8], b[9], b[10], b[11], b[12], b[13], b[14], b[15]); std::string buffer_string(buffer); // Compress IPv6 address std::string result = boost::regex_replace(buffer_string, ipv6_address_compression_algorithm, ":", boost::format_first_only); return result; } direction get_packet_direction_ipv6(patricia_tree_t* lookup_tree, struct in6_addr src_ipv6, struct in6_addr dst_ipv6) { direction packet_direction; bool our_ip_is_destination = false; bool our_ip_is_source = false; prefix_t prefix_for_check_address; prefix_for_check_address.family = AF_INET6; prefix_for_check_address.bitlen = 128; patricia_node_t* found_patrica_node = NULL; prefix_for_check_address.add.sin6 = dst_ipv6; found_patrica_node = patricia_search_best2(lookup_tree, &prefix_for_check_address, 1); if (found_patrica_node) { our_ip_is_destination = true; } found_patrica_node = NULL; prefix_for_check_address.add.sin6 = src_ipv6; if (found_patrica_node) { our_ip_is_source = true; } if (our_ip_is_source && our_ip_is_destination) { packet_direction = INTERNAL; } else if (our_ip_is_source) { packet_direction = OUTGOING; } else if (our_ip_is_destination) { packet_direction = INCOMING; } else { packet_direction = OTHER; } return packet_direction; } /* Get traffic type: check it belongs to our IPs */ direction get_packet_direction(patricia_tree_t* lookup_tree, uint32_t src_ip, uint32_t dst_ip, unsigned long& subnet, unsigned int& subnet_cidr_mask) { direction packet_direction; bool our_ip_is_destination = false; bool our_ip_is_source = false; prefix_t prefix_for_check_adreess; prefix_for_check_adreess.family = AF_INET; prefix_for_check_adreess.bitlen = 32; patricia_node_t* found_patrica_node = NULL; prefix_for_check_adreess.add.sin.s_addr = dst_ip; unsigned long destination_subnet = 0; unsigned int destination_subnet_cidr_mask = 0; found_patrica_node = patricia_search_best2(lookup_tree, &prefix_for_check_adreess, 1); if (found_patrica_node) { our_ip_is_destination = true; destination_subnet = found_patrica_node->prefix->add.sin.s_addr; destination_subnet_cidr_mask = found_patrica_node->prefix->bitlen; } found_patrica_node = NULL; prefix_for_check_adreess.add.sin.s_addr = src_ip; unsigned long source_subnet = 0; unsigned int source_subnet_cidr_mask = 0; found_patrica_node = patricia_search_best2(lookup_tree, &prefix_for_check_adreess, 1); if (found_patrica_node) { our_ip_is_source = true; source_subnet = found_patrica_node->prefix->add.sin.s_addr; source_subnet_cidr_mask = found_patrica_node->prefix->bitlen; } subnet = 0; if (our_ip_is_source && our_ip_is_destination) { packet_direction = INTERNAL; } else if (our_ip_is_source) { subnet = source_subnet; subnet_cidr_mask = source_subnet_cidr_mask; packet_direction = OUTGOING; } else if (our_ip_is_destination) { subnet = destination_subnet; subnet_cidr_mask = destination_subnet_cidr_mask; packet_direction = INCOMING; } else { packet_direction = OTHER; } return packet_direction; } std::string get_direction_name(direction direction_value) { std::string direction_name; switch (direction_value) { case INCOMING: direction_name = "incoming"; break; case OUTGOING: direction_name = "outgoing"; break; case INTERNAL: direction_name = "internal"; break; case OTHER: direction_name = "other"; break; default: direction_name = "unknown"; break; } return direction_name; } // We haven't this code for FreeBSD yet #ifdef __linux__ bool manage_interface_promisc_mode(std::string interface_name, bool switch_on) { extern log4cpp::Category& logger; // We need really any socket for ioctl int fd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); if (!fd) { logger << log4cpp::Priority::ERROR << "Can't create socket for promisc mode manager"; return false; } struct ifreq ethreq; memset(ðreq, 0, sizeof(ethreq)); strncpy(ethreq.ifr_name, interface_name.c_str(), IFNAMSIZ); int ioctl_res = ioctl(fd, SIOCGIFFLAGS, ðreq); if (ioctl_res == -1) { logger << log4cpp::Priority::ERROR << "Can't get interface flags"; return false; } bool promisc_enabled_on_device = ethreq.ifr_flags & IFF_PROMISC; if (switch_on) { if (promisc_enabled_on_device) { logger << log4cpp::Priority::INFO << "Interface " << interface_name << " in promisc mode already"; return true; } else { logger << log4cpp::Priority::INFO << "Interface in non promisc mode now, switch it on"; ethreq.ifr_flags |= IFF_PROMISC; int ioctl_res_set = ioctl(fd, SIOCSIFFLAGS, ðreq); if (ioctl_res_set == -1) { logger << log4cpp::Priority::ERROR << "Can't set interface flags"; return false; } return true; } } else { if (!promisc_enabled_on_device) { logger << log4cpp::Priority::INFO << "Interface " << interface_name << " in normal mode already"; return true; } else { logger << log4cpp::Priority::INFO << "Interface in promisc mode now, switch it off"; ethreq.ifr_flags &= ~IFF_PROMISC; int ioctl_res_set = ioctl(fd, SIOCSIFFLAGS, ðreq); if (ioctl_res_set == -1) { logger << log4cpp::Priority::ERROR << "Can't set interface flags"; return false; } return true; } } } #endif #ifdef ENABLE_LUA_HOOKS lua_State* init_lua_jit(std::string lua_hooks_path) { extern log4cpp::Category& logger; lua_State* lua_state = luaL_newstate(); if (lua_state == NULL) { logger << log4cpp::Priority::ERROR << "Can't create LUA session"; return NULL; } // load libraries luaL_openlibs(lua_state); int lua_load_file_result = luaL_dofile(lua_state, lua_hooks_path.c_str()); if (lua_load_file_result != 0) { logger << log4cpp::Priority::ERROR << "LuaJIT can't load file correctly from path: " << lua_hooks_path << " disable LUA support"; return NULL; } return lua_state; } bool call_lua_function(std::string function_name, lua_State* lua_state_param, std::string client_addres_in_string_format, void* ptr) { extern log4cpp::Category& logger; /* Function name */ lua_getfield(lua_state_param, LUA_GLOBALSINDEX, function_name.c_str()); /* Function params */ lua_pushstring(lua_state_param, client_addres_in_string_format.c_str()); lua_pushlightuserdata(lua_state_param, ptr); // Call with 1 argumnents and 1 result lua_call(lua_state_param, 2, 1); if (lua_gettop(lua_state_param) == 1) { bool result = lua_toboolean(lua_state_param, -1) == 1 ? true : false; // pop returned value lua_pop(lua_state_param, 1); return result; } else { logger << log4cpp::Priority::ERROR << "We got " << lua_gettop(lua_state_param) << " return values from the LUA, it's error, please check your LUA code"; return false; } return false; } #endif inline uint64_t read_tsc_cpu_register() { union { uint64_t tsc_64; struct { uint32_t lo_32; uint32_t hi_32; }; } tsc; asm volatile("rdtsc" : "=a" (tsc.lo_32), "=d" (tsc.hi_32)); return tsc.tsc_64; } uint64_t get_tsc_freq_with_sleep() { uint64_t start = read_tsc_cpu_register(); sleep(1); return read_tsc_cpu_register() - start; } json_object* serialize_attack_description_to_json(attack_details& current_attack) { json_object* jobj = json_object_new_object(); attack_type_t attack_type = detect_attack_type(current_attack); std::string printable_attack_type = get_printable_attack_name(attack_type); json_object_object_add(jobj, "attack_type", json_object_new_string(printable_attack_type.c_str())); json_object_object_add(jobj, "initial_attack_power", json_object_new_int(current_attack.attack_power)); json_object_object_add(jobj, "peak_attack_power", json_object_new_int(current_attack.max_attack_power)); json_object_object_add(jobj, "attack_direction", json_object_new_string(get_direction_name(current_attack.attack_direction).c_str())); json_object_object_add(jobj, "attack_protocol", json_object_new_string(get_printable_protocol_name(current_attack.attack_protocol).c_str())); json_object_object_add(jobj, "total_incoming_traffic", json_object_new_int(current_attack.in_bytes)); json_object_object_add(jobj, "total_outgoing_traffic", json_object_new_int(current_attack.out_bytes)); json_object_object_add(jobj, "total_incoming_pps", json_object_new_int(current_attack.in_packets)); json_object_object_add(jobj, "total_outgoing_pps", json_object_new_int(current_attack.out_packets)); json_object_object_add(jobj, "total_incoming_flows", json_object_new_int(current_attack.in_flows)); json_object_object_add(jobj, "total_outgoing_flows", json_object_new_int(current_attack.out_flows)); json_object_object_add(jobj, "average_incoming_traffic", json_object_new_int(current_attack.average_in_bytes)); json_object_object_add(jobj, "average_outgoing_traffic", json_object_new_int(current_attack.average_out_bytes)); json_object_object_add(jobj, "average_incoming_pps", json_object_new_int(current_attack.average_in_packets)); json_object_object_add(jobj, "average_outgoing_pps", json_object_new_int(current_attack.average_out_packets)); json_object_object_add(jobj, "average_incoming_flows", json_object_new_int(current_attack.average_in_flows)); json_object_object_add(jobj, "average_outgoing_flows", json_object_new_int(current_attack.average_out_flows)); json_object_object_add(jobj, "incoming_ip_fragmented_traffic", json_object_new_int( current_attack.fragmented_in_bytes )); json_object_object_add(jobj, "outgoing_ip_fragmented_traffic", json_object_new_int( current_attack.fragmented_out_bytes )); json_object_object_add(jobj, "incoming_ip_fragmented_pps", json_object_new_int( current_attack.fragmented_in_packets )); json_object_object_add(jobj, "outgoing_ip_fragmented_pps", json_object_new_int( current_attack.fragmented_out_packets )); json_object_object_add(jobj, "incoming_tcp_traffic", json_object_new_int( current_attack.tcp_in_bytes )); json_object_object_add(jobj, "outgoing_tcp_traffic", json_object_new_int( current_attack.tcp_out_bytes )); json_object_object_add(jobj, "incoming_tcp_pps", json_object_new_int( current_attack.tcp_in_packets )); json_object_object_add(jobj, "outgoing_tcp_pps", json_object_new_int(current_attack.tcp_out_packets )); json_object_object_add(jobj, "incoming_syn_tcp_traffic", json_object_new_int( current_attack.tcp_syn_in_bytes )); json_object_object_add(jobj, "outgoing_syn_tcp_traffic", json_object_new_int( current_attack.tcp_syn_out_bytes )); json_object_object_add(jobj, "incoming_syn_tcp_pps", json_object_new_int( current_attack.tcp_syn_in_packets )); json_object_object_add(jobj, "outgoing_syn_tcp_pps", json_object_new_int( current_attack.tcp_syn_out_packets )); json_object_object_add(jobj, "incoming_udp_traffic", json_object_new_int( current_attack.udp_in_bytes )); json_object_object_add(jobj, "outgoing_udp_traffic", json_object_new_int( current_attack.udp_out_bytes )); json_object_object_add(jobj, "incoming_udp_pps", json_object_new_int( current_attack.udp_in_packets )); json_object_object_add(jobj, "outgoing_udp_pps", json_object_new_int( current_attack.udp_out_packets )); json_object_object_add(jobj, "incoming_icmp_traffic", json_object_new_int( current_attack.icmp_in_bytes )); json_object_object_add(jobj, "outgoing_icmp_traffic", json_object_new_int( current_attack.icmp_out_bytes )); json_object_object_add(jobj, "incoming_icmp_pps", json_object_new_int( current_attack.icmp_in_packets )); json_object_object_add(jobj, "outgoing_icmp_pps", json_object_new_int( current_attack.icmp_out_packets )); return jobj; } std::string serialize_attack_description(attack_details& current_attack) { std::stringstream attack_description; attack_type_t attack_type = detect_attack_type(current_attack); std::string printable_attack_type = get_printable_attack_name(attack_type); attack_description << "Attack type: " << printable_attack_type << "\n" << "Initial attack power: " << current_attack.attack_power << " packets per second\n" << "Peak attack power: " << current_attack.max_attack_power << " packets per second\n" << "Attack direction: " << get_direction_name(current_attack.attack_direction) << "\n" << "Attack protocol: " << get_printable_protocol_name(current_attack.attack_protocol) << "\n"; attack_description << "Total incoming traffic: " << convert_speed_to_mbps(current_attack.in_bytes) << " mbps\n" << "Total outgoing traffic: " << convert_speed_to_mbps(current_attack.out_bytes) << " mbps\n" << "Total incoming pps: " << current_attack.in_packets << " packets per second\n" << "Total outgoing pps: " << current_attack.out_packets << " packets per second\n" << "Total incoming flows: " << current_attack.in_flows << " flows per second\n" << "Total outgoing flows: " << current_attack.out_flows << " flows per second\n"; // Add average counters attack_description << "Average incoming traffic: " << convert_speed_to_mbps(current_attack.average_in_bytes) << " mbps\n" << "Average outgoing traffic: " << convert_speed_to_mbps(current_attack.average_out_bytes) << " mbps\n" << "Average incoming pps: " << current_attack.average_in_packets << " packets per second\n" << "Average outgoing pps: " << current_attack.average_out_packets << " packets per second\n" << "Average incoming flows: " << current_attack.average_in_flows << " flows per second\n" << "Average outgoing flows: " << current_attack.average_out_flows << " flows per second\n"; attack_description << "Incoming ip fragmented traffic: " << convert_speed_to_mbps(current_attack.fragmented_in_bytes) << " mbps\n" << "Outgoing ip fragmented traffic: " << convert_speed_to_mbps(current_attack.fragmented_out_bytes) << " mbps\n" << "Incoming ip fragmented pps: " << current_attack.fragmented_in_packets << " packets per second\n" << "Outgoing ip fragmented pps: " << current_attack.fragmented_out_packets << " packets per second\n" << "Incoming tcp traffic: " << convert_speed_to_mbps(current_attack.tcp_in_bytes) << " mbps\n" << "Outgoing tcp traffic: " << convert_speed_to_mbps(current_attack.tcp_out_bytes) << " mbps\n" << "Incoming tcp pps: " << current_attack.tcp_in_packets << " packets per second\n" << "Outgoing tcp pps: " << current_attack.tcp_out_packets << " packets per second\n" << "Incoming syn tcp traffic: " << convert_speed_to_mbps(current_attack.tcp_syn_in_bytes) << " mbps\n" << "Outgoing syn tcp traffic: " << convert_speed_to_mbps(current_attack.tcp_syn_out_bytes) << " mbps\n" << "Incoming syn tcp pps: " << current_attack.tcp_syn_in_packets << " packets per second\n" << "Outgoing syn tcp pps: " << current_attack.tcp_syn_out_packets << " packets per second\n" << "Incoming udp traffic: " << convert_speed_to_mbps(current_attack.udp_in_bytes) << " mbps\n" << "Outgoing udp traffic: " << convert_speed_to_mbps(current_attack.udp_out_bytes) << " mbps\n" << "Incoming udp pps: " << current_attack.udp_in_packets << " packets per second\n" << "Outgoing udp pps: " << current_attack.udp_out_packets << " packets per second\n" << "Incoming icmp traffic: " << convert_speed_to_mbps(current_attack.icmp_in_bytes) << " mbps\n" << "Outgoing icmp traffic: " << convert_speed_to_mbps(current_attack.icmp_out_bytes) << " mbps\n" << "Incoming icmp pps: " << current_attack.icmp_in_packets << " packets per second\n" << "Outgoing icmp pps: " << current_attack.icmp_out_packets << " packets per second\n"; return attack_description.str(); } attack_type_t detect_attack_type(attack_details& current_attack) { double threshold_value = 0.9; if (current_attack.attack_direction == INCOMING) { if (current_attack.tcp_syn_in_packets > threshold_value * current_attack.in_packets) { return ATTACK_SYN_FLOOD; } else if (current_attack.icmp_in_packets > threshold_value * current_attack.in_packets) { return ATTACK_ICMP_FLOOD; } else if (current_attack.fragmented_in_packets > threshold_value * current_attack.in_packets) { return ATTACK_IP_FRAGMENTATION_FLOOD; } else if (current_attack.udp_in_packets > threshold_value * current_attack.in_packets) { return ATTACK_UDP_FLOOD; } } else if (current_attack.attack_direction == OUTGOING) { if (current_attack.tcp_syn_out_packets > threshold_value * current_attack.out_packets) { return ATTACK_SYN_FLOOD; } else if (current_attack.icmp_out_packets > threshold_value * current_attack.out_packets) { return ATTACK_ICMP_FLOOD; } else if (current_attack.fragmented_out_packets > threshold_value * current_attack.out_packets) { return ATTACK_IP_FRAGMENTATION_FLOOD; } else if (current_attack.udp_out_packets > threshold_value * current_attack.out_packets) { return ATTACK_UDP_FLOOD; } } return ATTACK_UNKNOWN; } std::string get_printable_attack_name(attack_type_t attack) { if (attack == ATTACK_SYN_FLOOD) { return "syn_flood"; } else if (attack == ATTACK_ICMP_FLOOD) { return "icmp_flood"; } else if (attack == ATTACK_UDP_FLOOD) { return "udp_flood"; } else if (attack == ATTACK_IP_FRAGMENTATION_FLOOD) { return "ip_fragmentation"; } else if (attack == ATTACK_UNKNOWN) { return "unknown"; } else { return "unknown"; } } std::string serialize_network_load_to_text(map_element& network_speed_meter, bool average) { std::stringstream buffer; std::string prefix = "Network"; if (average) { prefix = "Average network"; } buffer << prefix << " incoming traffic: "<< convert_speed_to_mbps(network_speed_meter.in_bytes) << " mbps\n" << prefix << " outgoing traffic: "<< convert_speed_to_mbps(network_speed_meter.out_bytes) << " mbps\n" << prefix << " incoming pps: "<< network_speed_meter.in_packets << " packets per second\n" << prefix << " outgoing pps: "<< network_speed_meter.out_packets << " packets per second\n"; return buffer.str(); } json_object* serialize_network_load_to_json(map_element& network_speed_meter) { json_object* jobj = json_object_new_object(); json_object_object_add(jobj, "incoming traffic", json_object_new_int(network_speed_meter.in_bytes)); json_object_object_add(jobj, "outgoing traffic", json_object_new_int(network_speed_meter.out_bytes)); json_object_object_add(jobj, "incoming pps", json_object_new_int(network_speed_meter.in_packets)); json_object_object_add(jobj, "outgoing pps", json_object_new_int(network_speed_meter.out_packets)); return jobj; } std::string serialize_statistic_counters_about_attack(attack_details& current_attack) { std::stringstream attack_description; double average_packet_size_for_incoming_traffic = 0; double average_packet_size_for_outgoing_traffic = 0; if (current_attack.average_in_packets > 0) { average_packet_size_for_incoming_traffic = (double)current_attack.average_in_bytes / (double)current_attack.average_in_packets; } if (current_attack.average_out_packets > 0) { average_packet_size_for_outgoing_traffic = (double)current_attack.average_out_bytes / (double)current_attack.average_out_packets; } // We do not need very accurate size attack_description.precision(1); attack_description << "Average packet size for incoming traffic: " << std::fixed << average_packet_size_for_incoming_traffic << " bytes \n" << "Average packet size for outgoing traffic: " << std::fixed << average_packet_size_for_outgoing_traffic << " bytes \n"; return attack_description.str(); } std::string dns_lookup(std::string domain_name) { try { boost::asio::io_service io_service; boost::asio::ip::tcp::resolver resolver(io_service); boost::asio::ip::tcp::resolver::query query(domain_name, ""); for (boost::asio::ip::tcp::resolver::iterator i = resolver.resolve(query); i != boost::asio::ip::tcp::resolver::iterator(); ++i) { boost::asio::ip::tcp::endpoint end = *i; return end.address().to_string(); } } catch (std::exception& e) { return ""; } return ""; } bool store_data_to_stats_server(unsigned short int graphite_port, std::string graphite_host, std::string buffer_as_string) { int client_sockfd = socket(AF_INET, SOCK_STREAM, 0); if (client_sockfd < 0) { return false; } struct sockaddr_in serv_addr; memset(&serv_addr, 0, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_port = htons(graphite_port); int pton_result = inet_pton(AF_INET, graphite_host.c_str(), &serv_addr.sin_addr); if (pton_result <= 0) { close(client_sockfd); return false; } int connect_result = connect(client_sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)); if (connect_result < 0) { close(client_sockfd); return false; } int write_result = write(client_sockfd, buffer_as_string.c_str(), buffer_as_string.size()); close(client_sockfd); if (write_result > 0) { return true; } else { return false; } } fastnetmon-1.1.3+dfsg/src/fast_library.h000066400000000000000000000121321313534057500202370ustar00rootroot00000000000000#ifndef FAST_LIBRARY_H #define FAST_LIBRARY_H #include "fastnetmon_types.h" #include #include #include #include #include #include #include #include #include #include #include #include #include // Boost libs #include #include "libpatricia/patricia.h" #ifdef ENABLE_LUA_HOOKS #include #endif #define TCP_FIN_FLAG_SHIFT 1 #define TCP_SYN_FLAG_SHIFT 2 #define TCP_RST_FLAG_SHIFT 3 #define TCP_PSH_FLAG_SHIFT 4 #define TCP_ACK_FLAG_SHIFT 5 #define TCP_URG_FLAG_SHIFT 6 typedef std::map graphite_data_t; typedef std::vector interfaces_list_t; typedef std::vector ip_addresses_list_t; ip_addresses_list_t get_local_ip_v4_addresses_list(); ip_addresses_list_t get_ip_list_for_interface(std::string interface); interfaces_list_t get_interfaces_list(); bool store_data_to_graphite(unsigned short int graphite_port, std::string graphite_host, graphite_data_t graphite_data); std::string get_protocol_name_by_number(unsigned int proto_number); uint64_t convert_speed_to_mbps(uint64_t speed_in_bps); std::vector exec(std::string cmd); uint32_t convert_ip_as_string_to_uint(std::string ip); std::string convert_ip_as_uint_to_string(uint32_t ip_as_integer); std::string convert_int_to_string(int value); std::string print_ipv6_address(struct in6_addr& ipv6_address); std::string print_simple_packet(simple_packet packet); std::string convert_timeval_to_date(struct timeval tv); int extract_bit_value(uint8_t num, int bit); int extract_bit_value(uint16_t num, int bit); int clear_bit_value(uint8_t& num, int bit); int clear_bit_value(uint16_t& num, int bit); int set_bit_value(uint8_t& num, int bit); int set_bit_value(uint16_t& num, int bit); std::string print_tcp_flags(uint8_t flag_value); uint64_t MurmurHash64A(const void* key, int len, uint64_t seed); std::string print_tcp_flags(uint8_t flag_value); int timeval_subtract(struct timeval* result, struct timeval* x, struct timeval* y); bool folder_exists(std::string path); bool is_cidr_subnet(const char* subnet); bool is_v4_host(std::string host); bool file_exists(std::string path); uint32_t convert_cidr_to_binary_netmask(unsigned int cidr); std::string get_printable_protocol_name(unsigned int protocol); std::string get_net_address_from_network_as_string(std::string network_cidr_format); std::string print_time_t_in_fastnetmon_format(time_t current_time); unsigned int get_cidr_mask_from_network_as_string(std::string network_cidr_format); void copy_networks_from_string_form_to_binary(std::vector networks_list_as_string, std::vector& our_networks); int convert_string_to_integer(std::string line); // Byte order type safe converters uint16_t fast_ntoh(uint16_t value); uint32_t fast_ntoh(uint32_t value); uint64_t fast_ntoh(uint64_t value); uint16_t fast_hton(uint16_t value); uint32_t fast_hton(uint32_t value); uint64_t fast_hton(uint64_t value); bool print_pid_to_file(pid_t pid, std::string pid_path); bool read_pid_from_file(pid_t& pid, std::string pid_path); direction get_packet_direction(patricia_tree_t* lookup_tree, uint32_t src_ip, uint32_t dst_ip, unsigned long& subnet, unsigned int& subnet_cidr_mask); direction get_packet_direction_ipv6(patricia_tree_t* lookup_tree, struct in6_addr src_ipv6, struct in6_addr dst_ipv6); std::string convert_prefix_to_string_representation(prefix_t* prefix); std::string find_subnet_by_ip_in_string_format(patricia_tree_t* patricia_tree, std::string ip); std::string convert_subnet_to_string(subnet_t my_subnet); std::string get_direction_name(direction direction_value); subnet_t convert_subnet_from_string_to_binary(std::string subnet_cidr); std::vector split_strings_to_vector_by_comma(std::string raw_string); subnet_t convert_subnet_from_string_to_binary_with_cidr_format(std::string subnet_cidr); inline uint64_t read_tsc_cpu_register(); uint64_t get_tsc_freq_with_sleep(); #ifdef __linux__ bool manage_interface_promisc_mode(std::string interface_name, bool switch_on); #endif #ifdef ENABLE_LUA_HOOKS lua_State* init_lua_jit(std::string lua_hooks_path); bool call_lua_function(std::string function_name, lua_State* lua_state_param, std::string client_addres_in_string_format, void* ptr); #endif std::string serialize_attack_description(attack_details& current_attack); attack_type_t detect_attack_type(attack_details& current_attack); std::string get_printable_attack_name(attack_type_t attack); std::string serialize_network_load_to_text(map_element& network_speed_meter, bool average); json_object* serialize_attack_description_to_json(attack_details& current_attack); json_object* serialize_network_load_to_json(map_element& network_speed_meter); std::string serialize_statistic_counters_about_attack(attack_details& current_attack); std::string dns_lookup(std::string domain_name); bool store_data_to_stats_server(unsigned short int graphite_port, std::string graphite_host, std::string buffer_as_string); #endif fastnetmon-1.1.3+dfsg/src/fast_platform.h.template000066400000000000000000000015151313534057500222340ustar00rootroot00000000000000#ifndef FAST_PLATFORM_H #define FAST_PLATFORM_H // This file automatically generated for your platform (Linux, FreeBSD and others) with cmake /* Platform specific paths */ std::string fastnetmon_version = "${FASTNETMON_APPLICATION_VERSION}"; std::string pid_path = "/var/run/fastnetmon.pid"; std::string global_config_path = "/etc/fastnetmon.conf"; std::string log_file_path = "/var/log/fastnetmon.log"; std::string attack_details_folder = "/var/log/fastnetmon_attacks"; // Default path to notify script std::string notify_script_path = "/usr/local/bin/notify_about_attack.sh"; // Default path to file with networks for whitelising std::string white_list_path = "/etc/networks_whitelist"; // Default path to file with all networks listing std::string networks_list_path = "/etc/networks_list"; /* Platform specific paths end */ #endif fastnetmon-1.1.3+dfsg/src/fast_priority_queue.cpp000066400000000000000000000044561313534057500222250ustar00rootroot00000000000000bool compare_min(unsigned int a, unsigned int b) { return a > b; } bool compare_max(unsigned int a, unsigned int b) { return a < b; } template fast_priority_queue::fast_priority_queue(unsigned int queue_size) { this->queue_size = queue_size; internal_list.reserve(queue_size); } template void fast_priority_queue::insert(order_by_template_type main_value, int data) { // Because it's ehap we can remove // Append new element to the end of list internal_list.push_back(main_value); // Convert list to the complete heap // Up to logarithmic in the distance between first and last: Compares elements and potentially // swaps (or moves) them until rearranged as a longer heap. std::push_heap(internal_list.begin(), internal_list.end(), compare_min); if (this->internal_list.size() >= queue_size) { // And now we should remove minimal element from the internal_list // Prepare heap to remove min element std::pop_heap(internal_list.begin(), internal_list.end(), compare_min); // Remove element from the head internal_list.pop_back(); } } template order_by_template_type fast_priority_queue::get_min_element() { // We will return head of list because it's consists minimum element return internal_list.front(); } template void fast_priority_queue::print_internal_list() { for (unsigned int i = 0; i < internal_list.size(); i++) { std::cout << internal_list[i] << std::endl; } } template void fast_priority_queue::print() { // Create new list for sort because we can't do it in place std::vector sorted_list; // Allocate enough space sorted_list.reserve(internal_list.size()); // Copy to new vector with copy constructor sorted_list = internal_list; // Execute heap sort because array paritally sorted already std::sort_heap(sorted_list.begin(), sorted_list.end(), compare_min); for (unsigned int i = 0; i < sorted_list.size(); i++) { std::cout << sorted_list[i] << std::endl; } } fastnetmon-1.1.3+dfsg/src/fast_priority_queue.h000066400000000000000000000014421313534057500216620ustar00rootroot00000000000000#ifndef fast_priority_queue_h #define fast_priority_queue_h #include #include #include #include #include #include template class fast_priority_queue { public: fast_priority_queue(unsigned int queue_size); void insert(order_by_template_type main_value, int data); order_by_template_type get_min_element(); void print_internal_list(); void print(); private: order_by_template_type max_number; order_by_template_type min_number; unsigned int queue_size; // We can't use list here! std::vector internal_list; // std::priority_queue, std::less > class_priority_queue; }; #include "fast_priority_queue.cpp" #endif fastnetmon-1.1.3+dfsg/src/fastnetmon.conf000066400000000000000000000216631313534057500204430ustar00rootroot00000000000000### ### Main configuration params ### ### Logging configuration # enable this option if you want to send logs to local syslog facility logging:local_syslog_logging = off # enable this option if you want to send logs to a remote syslog server via UDP logging:remote_syslog_logging = off # specify a custom server and port for remote logging logging:remote_syslog_server = 10.10.10.10 logging:remote_syslog_port = 514 # Enable/Disable any actions in case of attack enable_ban = on # disable processing for certain direction of traffic process_incoming_traffic = on process_outgoing_traffic = on # How many packets will be collected from attack traffic ban_details_records_count = 500 # How long (in seconds) we should keep an IP in blocked state # If you set 0 here it completely disables unban capability ban_time = 1900 # Check if the attack is still active, before triggering an unban callback with this option # If the attack is still active, check each run of the unban watchdog unban_only_if_attack_finished = on # enable per subnet speed meters # For each subnet, list track speed in bps and pps for both directions enable_subnet_counters = off # list of all your networks in CIDR format networks_list_path = /etc/networks_list # list networks in CIDR format which will be not monitored for attacks white_list_path = /etc/networks_whitelist # redraw period for client's screen check_period = 1 # Connection tracking is very useful for attack detection because it provides huge amounts of information, # but it's very CPU intensive and not recommended in big networks enable_connection_tracking = off # Different approaches to attack detection ban_for_pps = on ban_for_bandwidth = on ban_for_flows = off # Limits for Dos/DDoS attacks threshold_pps = 20000 threshold_mbps = 1000 threshold_flows = 3500 # Per protocol attack thresholds # We don't implement per protocol flow limits, sorry :( # These limits should be smaller than global pps/mbps limits threshold_tcp_mbps = 100000 threshold_udp_mbps = 100000 threshold_icmp_mbps = 100000 threshold_tcp_pps = 100000 threshold_udp_pps = 100000 threshold_icmp_pps = 100000 ban_for_tcp_bandwidth = off ban_for_udp_bandwidth = off ban_for_icmp_bandwidth = off ban_for_tcp_pps = off ban_for_udp_pps = off ban_for_icmp_pps = off ### ### Traffic capture methods ### # PF_RING traffic capture, fast enough but the wirespeed version needs a paid license mirror = off # Port mirroring sample rate pfring_sampling_ratio = 1 # Netmap traffic capture (very fast but needs patched drivers) mirror_netmap = off # SnabbSwitch traffic capture mirror_snabbswitch = off # AF_PACKET capture engine # Please use it only with modern Linux kernels (3.6 and more) # And please install birq for irq ditribution over cores mirror_afpacket = off # use PCI-e addresses here instead of OS device names. You can find them in "lspci" output interfaces_snabbswitch = 0000:04:00.0,0000:04:00.1,0000:03:00.0,0000:03:00.1 # Port mirroring sampling ratio netmap_sampling_ratio = 1 # This option should be enabled if you are using Juniper with mirroring of the first X bytes of packet: maximum-packet-length 110; netmap_read_packet_length_from_ip_header = off # Pcap mode, very slow and thus not suitable for production pcap = off # Netflow capture method with v5, v9 and IPFIX support netflow = on # sFLOW capture suitable for switches sflow = on # PF_RING configuration # If you have a license for PF_RING ZC, enable this mode and it might achieve wire speed for 10GE enable_pf_ring_zc_mode = off # Configuration for netmap, mirror, pcap modes # For pcap and PF_RING we could specify "any" # For netmap and PF_RING we could specify multiple interfaces separated by comma interfaces = eth3,eth4 # We use average values for traffic speed to certain IP and we calculate average over this time slice average_calculation_time = 5 # We use average values for traffic speed for subnet and we calculate average over this time slice average_calculation_time_for_subnets = 20 # Netflow configuration # it's possible to specify multiple ports here, using commas as delimiter netflow_port = 2055 netflow_host = 0.0.0.0 # To bind to all interfaces for all protocols: not possible yet # To bind to all interfaces for a specific protocol: :: or 0.0.0.0 # To bind to localhost for a specific protocol: ::1 or 127.0.0.1 # Netflow v9 and IPFIX agents use different and very complex approaches for notifying about sample ratio # Here you could specify a sampling ratio for all this agents # For NetFLOW v5 we extract sampling ratio from packets directely and this option not used netflow_sampling_ratio = 1 # In some cases with NetFlow we could get huge bursts related to aggregated data nature # We could try to get smoother data with this option, i.e. we will divide counters on collection interval time netflow_divide_counters_on_interval_length = off # Process each netflow packet with LUA # This option is not default and you need build it additionally # netflow_lua_hooks_path = /usr/src/fastnetmon/src/netflow_hooks.lua # sFLOW configuration # It's possible to specify multiple ports here, using commas as delimiter sflow_port = 6343 # sflow_port = 6343,6344 sflow_host = 0.0.0.0 # process each sFLOW packet with LUA # This option is not default and you need build it additionally # sflow_lua_hooks_path = /usr/src/fastnetmon/src/sflow_hooks.lua ### ### Actions when attack detected ### # This script executed for ban, unban and attack detail collection notify_script_path = /usr/local/bin/notify_about_attack.sh # pass attack details to notify_script via stdin # Pass details only in case of "ban" call # No details will be passed for "unban" call notify_script_pass_details = on # collect a full dump of the attack with full payload in pcap compatible format collect_attack_pcap_dumps = off # Execute Deep Packet Inspection on captured PCAP packets process_pcap_attack_dumps_with_dpi = off # Save attack details to Redis redis_enabled = off # Redis configuration redis_port = 6379 redis_host = 127.0.0.1 # specify a custom prefix here redis_prefix = mydc1 # We could store attack information to MongoDB mongodb_enabled = off mongodb_host = localhost mongodb_port = 27017 mongodb_database_name = fastnetmon # If you are using PF_RING non ZC version you could block traffic on host with hardware filters # Please be aware! We can not remove blocks with this action plugin pfring_hardware_filters_enabled = off # announce blocked IPs with BGP protocol with ExaBGP exabgp = off exabgp_command_pipe = /var/run/exabgp.cmd exabgp_community = 65001:666 # specify multiple communities with this syntax: # exabgp_community = [65001:666 65001:777] # specify different communities for host and subnet announces # exabgp_community_subnet = 65001:667 # exabgp_community_host = 65001:668 exabgp_next_hop = 10.0.3.114 # In complex cases you could have both options enabled and announce host and subnet simultaneously # Announce /32 host itself with BGP exabgp_announce_host = on # Announce origin subnet of IP address instead IP itself exabgp_announce_whole_subnet = off # Announce Flow Spec rules when we could detect certain attack type # Please we aware! Flow Spec announce triggered when we collect some details about attack, # i.e. when we call attack_details script # Please disable exabgp_announce_host and exabgp_announce_whole_subnet if you want to use this feature # Please use ExaBGP v4 only (Git version), for more details: https://github.com/FastVPSEestiOu/fastnetmon/blob/master/docs/BGP_FLOW_SPEC.md exabgp_flow_spec_announces = off # GoBGP intergation gobgp = off gobgp_next_hop = 0.0.0.0 gobgp_announce_host = on gobgp_announce_whole_subnet = off # Graphite monitoring # InfluxDB is also supported, please check our reference: # https://github.com/FastVPSEestiOu/fastnetmon/blob/master/docs/INFLUXDB_INTEGRATION.md graphite = off graphite_host = 127.0.0.1 graphite_port = 2003 # Default namespace for Graphite data graphite_prefix = fastnetmon # Add local IP addresses and aliases to monitoring list # Works only for Linux monitor_local_ip_addresses = on # Create group of hosts with non-standard thresholds # You should create this group before (in configuration file) specifying any limits hostgroup = my_hosts:10.10.10.221/32,10.10.10.222/32 # Configure this group my_hosts_enable_ban = off my_hosts_ban_for_pps = off my_hosts_ban_for_bandwidth = off my_hosts_ban_for_flows = off my_hosts_threshold_pps = 20000 my_hosts_threshold_mbps = 1000 my_hosts_threshold_flows = 3500 # Path to pid file for checking "if another copy of tool is running", it's useful when you run multiple instances of tool pid_path = /var/run/fastnetmon.pid # Path to file where we store information for fastnetmon_client cli_stats_file_path = /tmp/fastnetmon.dat # Enable gRPC api (required for fastnetmon_api_client tool) enable_api = off ### ### Client configuration ### # Field used for sorting in client, valid values are: packets, bytes or flows sort_parameter = packets # How much IPs will be listed for incoming and outgoing channel eaters max_ips_in_list = 7 fastnetmon-1.1.3+dfsg/src/fastnetmon.cpp000066400000000000000000005312771313534057500203070ustar00rootroot00000000000000/* Author: pavel.odintsov@gmail.com */ /* License: GPLv2 */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "libpatricia/patricia.h" #include "fastnetmon_types.h" #include "fastnetmon_packet_parser.h" #include "fast_library.h" #include "packet_storage.h" #include "bgp_flow_spec.h" // Here we store variables which differs for different paltforms #include "fast_platform.h" #ifdef ENABLE_DPI #include "fast_dpi.h" #endif #ifdef FASTNETMON_API #include #include "fastnetmon.grpc.pb.h" #endif // Plugins #include "sflow_plugin/sflow_collector.h" #include "netflow_plugin/netflow_collector.h" #include "pcap_plugin/pcap_collector.h" #include "netmap_plugin/netmap_collector.h" #ifdef PF_RING #include "pfring_plugin/pfring_collector.h" #endif #ifdef SNABB_SWITCH #include "snabbswitch_plugin/snabbswitch_collector.h" #endif #ifdef FASTNETMON_ENABLE_AFPACKET #include "afpacket_plugin/afpacket_collector.h" #endif #ifdef PF_RING #include "actions/pfring_hardware_filter_action.h" #endif #ifdef ENABLE_GOBGP #include "actions/gobgp_action.h" #endif // Yes, maybe it's not an good idea but with this we can guarantee working code in example plugin #include "example_plugin/example_collector.h" #include #include #include #include #include #include #include #include #include #include #include // log4cpp logging facility #include "log4cpp/RemoteSyslogAppender.hh" #include "log4cpp/SyslogAppender.hh" #include "log4cpp/Category.hh" #include "log4cpp/Appender.hh" #include "log4cpp/FileAppender.hh" #include "log4cpp/OstreamAppender.hh" #include "log4cpp/Layout.hh" #include "log4cpp/BasicLayout.hh" #include "log4cpp/PatternLayout.hh" #include "log4cpp/Priority.hh" // Boost libs #include #ifdef GEOIP #include "GeoIP.h" #endif #ifdef REDIS #include #endif #ifdef MONGO #include #include #endif // #define IPV6_HASH_COUNTERS #ifdef IPV6_HASH_COUNTERS #include "concurrentqueue.h" #endif #ifdef FASTNETMON_API using grpc::Server; using grpc::ServerBuilder; using grpc::ServerContext; using grpc::Status; using fastmitigation::BanListRequest; using fastmitigation::BanListReply; using fastmitigation::Fastnetmon; std::unique_ptr api_server; bool enable_api = false; #endif time_t last_call_of_traffic_recalculation; std::string cli_stats_file_path = "/tmp/fastnetmon.dat"; std::string reporting_server = "heartbeat.stableit.ru"; unsigned int stats_thread_sleep_time = 3600; unsigned int stats_thread_initial_call_delay = 30; unsigned int recalculate_speed_timeout = 1; // Send or not any details about attack for ban script call over stdin bool notify_script_pass_details = true; bool pfring_hardware_filters_enabled = false; bool notify_script_enabled = true; // We could collect attack dumps in pcap format bool collect_attack_pcap_dumps = false; // We could process this dumps with DPI bool process_pcap_attack_dumps_with_dpi = false; bool unban_only_if_attack_finished = true; logging_configuration_t logging_configuration; // Variable with all data from main screen std::string screen_data_stats = ""; // Global map with parsed config file typedef std::map configuration_map_t; configuration_map_t configuration_map; // Every X seconds we will run ban list cleaner thread // If customer uses ban_time smaller than this value we will use ban_time/2 as unban_iteration_sleep_time int unban_iteration_sleep_time = 60; bool unban_enabled = true; #ifdef ENABLE_DPI struct ndpi_detection_module_struct* my_ndpi_struct = NULL; u_int32_t ndpi_size_flow_struct = 0; u_int32_t ndpi_size_id_struct = 0; #endif #ifdef ENABLE_GOBGP bool gobgp_enabled = false; #endif #ifdef MONGO std::string mongodb_host = "localhost"; unsigned int mongodb_port = 27017; bool mongodb_enabled = false; std::string mongodb_database_name = "fastnetmon"; #endif /* Configuration block, we must move it to configuration file */ #ifdef REDIS unsigned int redis_port = 6379; std::string redis_host = "127.0.0.1"; // redis key prefix std::string redis_prefix = ""; // because it's additional and very specific feature we should disable it by default bool redis_enabled = false; #endif bool monitor_local_ip_addresses = true; // This flag could enable print of ban actions and thresholds on the client's screen bool print_configuration_params_on_the_screen = false; // Trigger for enable or disable traffic counting for whole subnets bool enable_subnet_counters = false; // We will announce whole subnet instead single IP with BGP if this flag enabled bool exabgp_announce_whole_subnet = false; // We will announce only /32 host bool exabgp_announce_host = false; // With this flag we will announce more specfic then whole block Flow Spec announces bool exabgp_flow_spec_announces = false; ban_settings_t global_ban_settings; void init_global_ban_settings() { // ban Configuration params global_ban_settings.enable_ban_for_pps = false; global_ban_settings.enable_ban_for_bandwidth = false; global_ban_settings.enable_ban_for_flows_per_second = false; // We must ban IP if it exceeed this limit in PPS global_ban_settings.ban_threshold_pps = 20000; // We must ban IP of it exceed this limit for number of flows in any direction global_ban_settings.ban_threshold_flows = 3500; // We must ban client if it exceed 1GBps global_ban_settings.ban_threshold_mbps = 1000; // Disable per protocol thresholds too global_ban_settings.enable_ban_for_tcp_pps = false; global_ban_settings.enable_ban_for_tcp_bandwidth = false; global_ban_settings.enable_ban_for_udp_pps = false; global_ban_settings.enable_ban_for_udp_bandwidth = false; global_ban_settings.enable_ban_for_icmp_pps = false; global_ban_settings.enable_ban_for_icmp_bandwidth = false; // Ban enable/disable flag global_ban_settings.enable_ban = true; } bool enable_conection_tracking = true; bool enable_snabbswitch_collection = false; bool enable_afpacket_collection = false; bool enable_data_collection_from_mirror = true; bool enable_netmap_collection = false; bool enable_sflow_collection = false; bool enable_netflow_collection = false; bool enable_pcap_collection = false; // Time consumed by reaclculation for all IPs struct timeval speed_calculation_time; // Time consumed by drawing stats for all IPs struct timeval drawing_thread_execution_time; // Global thread group for packet capture threads boost::thread_group packet_capture_plugin_thread_group; // Global thread group for service processes (speed recalculation, // screen updater and ban list cleaner) boost::thread_group service_thread_group; // Total number of hosts in our networks // We need this as global variable because it's very important value for configuring data structures unsigned int total_number_of_hosts_in_our_networks = 0; #ifdef GEOIP GeoIP* geo_ip = NULL; #endif // IPv4 lookup trees patricia_tree_t* lookup_tree_ipv4, *whitelist_tree_ipv4; // IPv6 lookup trees patricia_tree_t* lookup_tree_ipv6, *whitelist_tree_ipv6; bool DEBUG = 0; // flag about dumping all packets to log bool DEBUG_DUMP_ALL_PACKETS = false; // dump "other" packets bool DEBUG_DUMP_OTHER_PACKETS = false; // Period for update screen for console version of tool unsigned int check_period = 3; // Standard ban time in seconds for all attacks but you can tune this value int global_ban_time = 1800; // We calc average pps/bps for this time double average_calculation_amount = 15; // We calc average pps/bps for subnets with this time, we use longer value for calculation average network traffic double average_calculation_amount_for_subnets = 30; // Show average or absolute value of speed bool print_average_traffic_counts = true; // Key used for sorting clients in output. Allowed sort params: packets/bytes/flows std::string sort_parameter = "packets"; // Number of lines in programm output unsigned int max_ips_in_list = 7; // Number of lines for sending ben attack details to email unsigned int ban_details_records_count = 500; // We haven't option for configure it with configuration file unsigned int number_of_packets_for_pcap_attack_dump = 500; // log file log4cpp::Category& logger = log4cpp::Category::getRoot(); // We storae all active BGP Flow Spec announces here typedef std::map active_flow_spec_announces_t; active_flow_spec_announces_t active_flow_spec_announces; /* Configuration block ends */ // We count total number of incoming/outgoing/internal and other traffic type packets/bytes // And initilize by 0 all fields total_counter_element total_counters[4]; total_counter_element total_speed_counters[4]; total_counter_element total_speed_average_counters[4]; // Total amount of non parsed packets uint64_t total_unparsed_packets = 0; uint64_t total_unparsed_packets_speed = 0; // Total amount of IPv6 packets uint64_t total_ipv6_packets = 0; // IPv6 traffic which belongs to our own networks uint64_t our_ipv6_packets = 0; uint64_t incoming_total_flows_speed = 0; uint64_t outgoing_total_flows_speed = 0; map_of_vector_counters SubnetVectorMap; // Here we store taffic per subnet map_for_subnet_counters PerSubnetCountersMap; // Here we store traffic speed per subnet map_for_subnet_counters PerSubnetSpeedMap; // Here we store average speed per subnet map_for_subnet_counters PerSubnetAverageSpeedMap; // Flow tracking structures map_of_vector_counters_for_flow SubnetVectorMapFlow; /* End of our data structs */ boost::mutex ban_list_details_mutex; boost::mutex ban_list_mutex; boost::mutex flow_counter; // map for flows std::map FlowCounter; // Struct for string speed per IP map_of_vector_counters SubnetVectorMapSpeed; // Struct for storing average speed per IP for specified interval map_of_vector_counters SubnetVectorMapSpeedAverage; #ifdef GEOIP map_for_counters GeoIpCounter; #endif // In ddos info we store attack power and direction std::map ban_list; std::map > ban_list_details; host_group_map_t host_groups; // Here we store assignment from subnet to certain host group for fast lookup subnet_to_host_group_map_t subnet_to_host_groups; host_group_ban_settings_map_t host_group_ban_settings_map; std::vector our_networks; std::vector whitelist_networks; // ExaBGP support flag bool exabgp_enabled = false; std::string exabgp_community = ""; // We could use separate communities for subnet and host announces std::string exabgp_community_subnet = ""; std::string exabgp_community_host = ""; std::string exabgp_command_pipe = "/var/run/exabgp.cmd"; std::string exabgp_next_hop = ""; // Graphite monitoring bool graphite_enabled = false; std::string graphite_host = "127.0.0.1"; unsigned short int graphite_port = 2003; // Default graphite namespace std::string graphite_prefix = "fastnetmon"; bool process_incoming_traffic = true; bool process_outgoing_traffic = true; // Prototypes #ifdef ENABLE_DPI void init_current_instance_of_ndpi(); #endif inline void build_average_speed_counters_from_speed_counters( map_element* current_average_speed_element, map_element& new_speed_element, double exp_value, double exp_power); inline void build_speed_counters_from_packet_counters(map_element& new_speed_element, map_element* vector_itr, double speed_calc_period); void execute_ip_ban(uint32_t client_ip, map_element average_speed_element, std::string flow_attack_details, subnet_t customer_subnet); void collect_stats(); std::string get_attack_description_in_json(uint32_t client_ip, attack_details& current_attack); logging_configuration_t read_logging_settings(configuration_map_t configuration_map); std::string get_amplification_attack_type(amplification_attack_type_t attack_type); std::string generate_flow_spec_for_amplification_attack(amplification_attack_type_t amplification_attack_type, std::string destination_ip); bool exabgp_flow_spec_ban_manage(std::string action, std::string flow_spec_rule_as_text); void call_attack_details_handlers(uint32_t client_ip, attack_details& current_attack, std::string attack_fingerprint); void call_ban_handlers(uint32_t client_ip, attack_details& current_attack, std::string flow_attack_details); void call_unban_handlers(uint32_t client_ip, attack_details& current_attack); ban_settings_t read_ban_settings(configuration_map_t configuration_map, std::string host_group_name = ""); void exabgp_prefix_ban_manage(std::string action, std::string prefix_as_string_with_mask, std::string exabgp_next_hop, std::string exabgp_community); std::string print_subnet_load(); bool we_should_ban_this_ip(map_element* current_average_speed_element, ban_settings_t current_ban_settings); unsigned int get_max_used_protocol(uint64_t tcp, uint64_t udp, uint64_t icmp); void print_attack_details_to_file(std::string details, std::string client_ip_as_string, attack_details current_attack); std::string print_ban_thresholds(ban_settings_t current_ban_settings); bool load_configuration_file(); std::string print_flow_tracking_for_ip(conntrack_main_struct& conntrack_element, std::string client_ip); void convert_integer_to_conntrack_hash_struct(packed_session* packed_connection_data, packed_conntrack_hash* unpacked_data); uint64_t convert_conntrack_hash_struct_to_integer(packed_conntrack_hash* struct_value); void cleanup_ban_list(); std::string get_attack_description(uint32_t client_ip, attack_details& current_attack); void send_attack_details(uint32_t client_ip, attack_details current_attack_details); void free_up_all_resources(); std::string print_ddos_attack_details(); void recalculate_speed(); std::string print_channel_speed(std::string traffic_type, direction packet_direction); void process_packet(simple_packet& current_packet); void traffic_draw_programm(); void interruption_signal_handler(int signal_number); #ifdef FASTNETMON_API void silent_logging_function(gpr_log_func_args *args) { // We do not want any logging here } // Logic and data behind the server's behavior. class FastnetmonApiServiceImpl final : public Fastnetmon::Service { Status GetBanlist(::grpc::ServerContext* context, const ::fastmitigation::BanListRequest* request, ::grpc::ServerWriter< ::fastmitigation::BanListReply>* writer) override { logger << log4cpp::Priority::INFO << "API we asked for banlist"; for (std::map::iterator itr = ban_list.begin(); itr != ban_list.end(); ++itr) { std::string client_ip_as_string = convert_ip_as_uint_to_string(itr->first); BanListReply reply; reply.set_ip_address( client_ip_as_string + "/32" ); writer->Write(reply); } return Status::OK; } Status ExecuteBan(ServerContext* context, const fastmitigation::ExecuteBanRequest* request, fastmitigation::ExecuteBanReply* reply) override { logger << log4cpp::Priority::INFO << "API we asked for ban for IP: " << request->ip_address(); if (!is_v4_host(request->ip_address())) { logger << log4cpp::Priority::ERROR << "IP bad format"; return Status::CANCELLED; } uint32_t client_ip = convert_ip_as_string_to_uint(request->ip_address()); struct attack_details current_attack; ban_list_mutex.lock(); ban_list[client_ip] = current_attack; ban_list_mutex.unlock(); ban_list_details_mutex.lock(); ban_list_details[client_ip] = std::vector(); ban_list_details_mutex.unlock(); logger << log4cpp::Priority::INFO << "API call ban handlers manually"; std::string flow_attack_details = "manually triggered attack"; call_ban_handlers(client_ip, current_attack, flow_attack_details); return Status::OK; } Status ExecuteUnBan(ServerContext* context, const fastmitigation::ExecuteBanRequest* request, fastmitigation::ExecuteBanReply* reply) override { logger << log4cpp::Priority::INFO << "API: We asked for unban for IP: " << request->ip_address(); if (!is_v4_host(request->ip_address())) { logger << log4cpp::Priority::ERROR << "IP bad format"; return Status::CANCELLED; } uint32_t banned_ip = convert_ip_as_string_to_uint(request->ip_address()); if (ban_list.count(banned_ip) == 0) { logger << log4cpp::Priority::ERROR << "API: Could not find IP in ban list"; return Status::CANCELLED; } banlist_item ban_details = ban_list[banned_ip]; logger << log4cpp::Priority::INFO << "API: call unban handlers"; call_unban_handlers(banned_ip, ban_details); logger << log4cpp::Priority::INFO << "API: remove IP from ban list"; ban_list_mutex.lock(); ban_list.erase(banned_ip); ban_list_mutex.unlock(); return Status::OK; } }; // We could not define this variable in top of the file because we should define class before FastnetmonApiServiceImpl api_service; std::unique_ptr StartupApiServer() { std::string server_address("127.0.0.1:50052"); ServerBuilder builder; // Listen on the given address without any authentication mechanism. builder.AddListeningPort(server_address, grpc::InsecureServerCredentials()); // Register "service" as the instance through which we'll communicate with // clients. In this case it corresponds to an *synchronous* service. builder.RegisterService(&api_service); // Finally assemble the server. std::unique_ptr current_api_server(builder.BuildAndStart()); logger << log4cpp::Priority::INFO << "API server listening on " << server_address; return current_api_server; } void RunApiServer() { api_server = StartupApiServer(); // Wait for the server to shutdown. Note that some other thread must be // responsible for shutting down the server for this call to ever return. api_server->Wait(); logger << log4cpp::Priority::INFO << "API server got shutdown signal"; } #endif /* Class for custom comparison fields by different fields */ template class TrafficComparatorClass { private: sort_type sort_field; direction sort_direction; public: TrafficComparatorClass(direction sort_direction, sort_type sort_field) { this->sort_field = sort_field; this->sort_direction = sort_direction; } bool operator()(T a, T b) { if (sort_field == FLOWS) { if (sort_direction == INCOMING) { return a.second.in_flows > b.second.in_flows; } else if (sort_direction == OUTGOING) { return a.second.out_flows > b.second.out_flows; } else { return false; } } else if (sort_field == PACKETS) { if (sort_direction == INCOMING) { return a.second.in_packets > b.second.in_packets; } else if (sort_direction == OUTGOING) { return a.second.out_packets > b.second.out_packets; } else { return false; } } else if (sort_field == BYTES) { if (sort_direction == INCOMING) { return a.second.in_bytes > b.second.in_bytes; } else if (sort_direction == OUTGOING) { return a.second.out_bytes > b.second.out_bytes; } else { return false; } } else { return false; } } }; void sigpipe_handler_for_popen(int signo) { logger << log4cpp::Priority::ERROR << "Sorry but we experienced error with popen. " << "Please check your scripts. They should receive data on stdin! Optionally you could disable passing any details with configuration param: notify_script_pass_details = no"; // Well, we do not need exit here because we have another options to notifying about atatck // exit(1); } // exec command and pass data to it stdin bool exec_with_stdin_params(std::string cmd, std::string params) { FILE* pipe = popen(cmd.c_str(), "w"); if (!pipe) { logger << log4cpp::Priority::ERROR << "Can't execute programm " << cmd << " error code: " << errno << " error text: " << strerror(errno); return false; } int fputs_ret = fputs(params.c_str(), pipe); if (fputs_ret) { pclose(pipe); return true; } else { logger << log4cpp::Priority::ERROR << "Can't pass data to stdin of programm " << cmd; pclose(pipe); return false; } } #ifdef GEOIP bool geoip_init() { // load GeoIP ASN database to memory geo_ip = GeoIP_open("/root/fastnetmon/GeoIPASNum.dat", GEOIP_MEMORY_CACHE); if (geo_ip == NULL) { return false; } else { return true; } } #endif #ifdef REDIS redisContext* redis_init_connection() { struct timeval timeout = { 1, 500000 }; // 1.5 seconds redisContext* redis_context = redisConnectWithTimeout(redis_host.c_str(), redis_port, timeout); if (redis_context->err) { logger << log4cpp::Priority::ERROR << "Redis connection error:" << redis_context->errstr; return NULL; } // We should check connection with ping because redis do not check connection redisReply* reply = (redisReply*)redisCommand(redis_context, "PING"); if (reply) { freeReplyObject(reply); } else { return NULL; } return redis_context; } #endif #ifdef MONGO void store_data_in_mongo(std::string key_name, std::string attack_details_json) { mongoc_client_t *client; mongoc_collection_t *collection; mongoc_cursor_t *cursor; bson_error_t error; bson_oid_t oid; bson_t *doc; mongoc_init (); std::string collection_name = "attacks"; std::string connection_string = "mongodb://" + mongodb_host + ":" + convert_int_to_string(mongodb_port) + "/"; client = mongoc_client_new (connection_string.c_str()); if (!client) { logger << log4cpp::Priority::ERROR << "Can't connect to MongoDB database"; return; } bson_error_t bson_from_json_error; bson_t* bson_data = bson_new_from_json((const uint8_t *)attack_details_json.c_str(), attack_details_json.size(), &bson_from_json_error); if (!bson_data) { logger << log4cpp::Priority::ERROR << "Could not convert JSON to BSON"; return; } // logger << log4cpp::Priority::INFO << bson_as_json(bson_data, NULL); collection = mongoc_client_get_collection (client, mongodb_database_name.c_str(), collection_name.c_str()); doc = bson_new (); bson_oid_init (&oid, NULL); BSON_APPEND_OID (doc, "_id", &oid); bson_append_document(doc, key_name.c_str(), key_name.size(), bson_data); // logger << log4cpp::Priority::INFO << bson_as_json(doc, NULL); if (!mongoc_collection_insert (collection, MONGOC_INSERT_NONE, doc, NULL, &error)) { logger << log4cpp::Priority::ERROR << "Could not store data to MongoDB: " << error.message; } // TODO: destroy bson_data too! bson_destroy (doc); mongoc_collection_destroy (collection); mongoc_client_destroy (client); } #endif #ifdef REDIS void store_data_in_redis(std::string key_name, std::string attack_details) { redisReply* reply = NULL; redisContext* redis_context = redis_init_connection(); if (!redis_context) { logger << log4cpp::Priority::ERROR << "Could not initiate connection to Redis"; return; } reply = (redisReply*)redisCommand(redis_context, "SET %s %s", key_name.c_str(), attack_details.c_str()); // If we store data correctly ... if (!reply) { logger << log4cpp::Priority::ERROR << "Can't increment traffic in redis error_code: " << redis_context->err << " error_string: " << redis_context->errstr; // Handle redis server restart corectly if (redis_context->err == 1 or redis_context->err == 3) { // Connection refused logger << log4cpp::Priority::ERROR << "Unfortunately we can't store data in Redis because server reject connection"; } } else { freeReplyObject(reply); } redisFree(redis_context); } #endif std::string draw_table(direction data_direction, bool do_redis_update, sort_type sort_item) { std::vector vector_for_sort; std::stringstream output_buffer; // Preallocate memory for sort vector // We use total networks size for this vector vector_for_sort.reserve(total_number_of_hosts_in_our_networks); // Switch to Average speed there!!! map_of_vector_counters* current_speed_map = NULL; if (print_average_traffic_counts) { current_speed_map = &SubnetVectorMapSpeedAverage; } else { current_speed_map = &SubnetVectorMapSpeed; } map_element zero_map_element; memset(&zero_map_element, 0, sizeof(zero_map_element)); unsigned int count_of_zero_speed_packets = 0; for (map_of_vector_counters::iterator itr = current_speed_map->begin(); itr != current_speed_map->end(); ++itr) { for (vector_of_counters::iterator vector_itr = itr->second.begin(); vector_itr != itr->second.end(); ++vector_itr) { int current_index = vector_itr - itr->second.begin(); // convert to host order for math operations uint32_t subnet_ip = ntohl(itr->first.first); uint32_t client_ip_in_host_bytes_order = subnet_ip + current_index; // covnert to our standard network byte order uint32_t client_ip = htonl(client_ip_in_host_bytes_order); // Do not add zero speed packets to sort list if (memcmp((void*)&zero_map_element, &*vector_itr, sizeof(map_element)) != 0) { vector_for_sort.push_back(std::make_pair(client_ip, *vector_itr)); } else { count_of_zero_speed_packets++; } } } // Sort only first X elements in this vector unsigned int shift_for_sort = max_ips_in_list; if (data_direction == INCOMING or data_direction == OUTGOING) { // Because in another case we will got segmentation fault unsigned int vector_size = vector_for_sort.size(); if (vector_size < shift_for_sort) { shift_for_sort = vector_size; } std::partial_sort(vector_for_sort.begin(), vector_for_sort.begin() + shift_for_sort, vector_for_sort.end(), TrafficComparatorClass(data_direction, sort_item)); } else { logger << log4cpp::Priority::ERROR << "Unexpected bahaviour on sort function"; return "Internal error"; } unsigned int element_number = 0; // In this loop we print only top X talkers in our subnet to screen buffer for (std::vector::iterator ii = vector_for_sort.begin(); ii != vector_for_sort.end(); ++ii) { // Print first max_ips_in_list elements in list, we will show top X "huge" channel loaders if (element_number >= max_ips_in_list) { break; } uint32_t client_ip = (*ii).first; std::string client_ip_as_string = convert_ip_as_uint_to_string((*ii).first); uint64_t pps = 0; uint64_t bps = 0; uint64_t flows = 0; uint64_t pps_average = 0; uint64_t bps_average = 0; uint64_t flows_average = 0; // Here we could have average or instantaneous speed map_element* current_speed_element = &ii->second; // Create polymorphic pps, byte and flow counters if (data_direction == INCOMING) { pps = current_speed_element->in_packets; bps = current_speed_element->in_bytes; flows = current_speed_element->in_flows; } else if (data_direction == OUTGOING) { pps = current_speed_element->out_packets; bps = current_speed_element->out_bytes; flows = current_speed_element->out_flows; } uint64_t mbps = convert_speed_to_mbps(bps); uint64_t mbps_average = convert_speed_to_mbps(bps_average); std::string is_banned = ban_list.count(client_ip) > 0 ? " *banned* " : ""; // We use setw for alignment output_buffer << client_ip_as_string << "\t\t"; output_buffer << std::setw(6) << pps << " pps "; output_buffer << std::setw(6) << mbps << " mbps "; output_buffer << std::setw(6) << flows << " flows "; output_buffer << is_banned << std::endl; element_number++; } graphite_data_t graphite_data; // TODO: add graphite operations time to the config file if (graphite_enabled) { for (std::vector::iterator ii = vector_for_sort.begin(); ii != vector_for_sort.end(); ++ii) { uint32_t client_ip = (*ii).first; std::string client_ip_as_string = convert_ip_as_uint_to_string((*ii).first); uint64_t pps = 0; uint64_t bps = 0; uint64_t flows = 0; // Here we could have average or instantaneous speed map_element* current_speed_element = &ii->second; // Create polymorphic pps, byte and flow counters if (data_direction == INCOMING) { pps = current_speed_element->in_packets; bps = current_speed_element->in_bytes; flows = current_speed_element->in_flows; } else if (data_direction == OUTGOING) { pps = current_speed_element->out_packets; bps = current_speed_element->out_bytes; flows = current_speed_element->out_flows; } std::string direction_as_string; if (data_direction == INCOMING) { direction_as_string = "incoming"; } else if (data_direction == OUTGOING) { direction_as_string = "outgoing"; } std::string ip_as_string_with_dash_delimiters = client_ip_as_string; // Replace dots by dashes std::replace(ip_as_string_with_dash_delimiters.begin(), ip_as_string_with_dash_delimiters.end(), '.', '_'); std::string graphite_current_prefix = graphite_prefix + ".hosts." + ip_as_string_with_dash_delimiters + "." + direction_as_string; if (print_average_traffic_counts) { graphite_current_prefix = graphite_current_prefix + ".average"; } // We do not store zero data to Graphite if (pps != 0) { graphite_data[ graphite_current_prefix + ".pps" ] = pps; } if (bps != 0) { graphite_data[ graphite_current_prefix + ".bps" ] = bps * 8; } if (flows != 0) { graphite_data[ graphite_current_prefix + ".flows" ] = flows; } } } // TODO: we should switch to piclke format instead text // TODO: we should check packet size for Graphite // logger << log4cpp::Priority::INFO << "We will write " << graphite_data.size() << " records to Graphite"; if (graphite_enabled) { bool graphite_put_result = store_data_to_graphite(graphite_port, graphite_host, graphite_data); if (!graphite_put_result) { logger << log4cpp::Priority::ERROR << "Can't store data to Graphite"; } } return output_buffer.str(); } // TODO: move to lirbary // read whole file to vector std::vector read_file_to_vector(std::string file_name) { std::vector data; std::string line; std::ifstream reading_file; reading_file.open(file_name.c_str(), std::ifstream::in); if (reading_file.is_open()) { while (getline(reading_file, line)) { data.push_back(line); } } else { logger << log4cpp::Priority::ERROR << "Can't open file: " << file_name; } return data; } void parse_hostgroups(std::string name, std::string value) { // We are creating new host group of subnets if (name != "hostgroup") { return; } std::vector splitted_new_host_group; // We have new host groups in form: // hostgroup = new_host_group_name:11.22.33.44/32,.... split(splitted_new_host_group, value, boost::is_any_of(":"), boost::token_compress_on); if (splitted_new_host_group.size() != 2) { logger << log4cpp::Priority::ERROR << "We can't parse new host group"; } std::string host_group_name = splitted_new_host_group[0]; if (host_groups.count(host_group_name) > 0) { logger << log4cpp::Priority::WARN << "We already have this host group (" << host_group_name << "). Please check!"; return; } // Split networks std::vector hostgroup_subnets = split_strings_to_vector_by_comma(splitted_new_host_group[1]); for (std::vector::iterator itr = hostgroup_subnets.begin(); itr != hostgroup_subnets.end(); ++itr) { subnet_t subnet = convert_subnet_from_string_to_binary_with_cidr_format(*itr); host_groups[ host_group_name ].push_back( subnet ); logger << log4cpp::Priority::WARN << "We add subnet " << convert_subnet_to_string( subnet ) << " to host group " << host_group_name; // And add to subnet to host group lookup hash if (subnet_to_host_groups.count(subnet) > 0) { // Huston, we have problem! Subnet to host group mapping should map single subnet to single group! logger << log4cpp::Priority::WARN << "Seems you have specified single subnet " << *itr << " to multiple host groups, please fix it, it's prohibited"; } else { subnet_to_host_groups[ subnet ] = host_group_name; } } logger << log4cpp::Priority::INFO << "We have created host group " << host_group_name << " with " << host_groups[ host_group_name ].size() << " subnets"; } // Load configuration bool load_configuration_file() { std::ifstream config_file(global_config_path.c_str()); std::string line; if (!config_file.is_open()) { logger << log4cpp::Priority::ERROR << "Can't open config file"; return false; } while (getline(config_file, line)) { std::vector parsed_config; if (line.find("#") == 0 or line.empty()) { // Ignore comments line continue; } boost::split(parsed_config, line, boost::is_any_of(" ="), boost::token_compress_on); if (parsed_config.size() == 2) { configuration_map[parsed_config[0]] = parsed_config[1]; // Well, we parse host groups here parse_hostgroups(parsed_config[0], parsed_config[1]); } else { logger << log4cpp::Priority::ERROR << "Can't parse config line: '" << line << "'"; } } if (configuration_map.count("enable_connection_tracking")) { if (configuration_map["enable_connection_tracking"] == "on") { enable_conection_tracking = true; } else { enable_conection_tracking = false; } } if (configuration_map.count("ban_time") != 0) { global_ban_time = convert_string_to_integer(configuration_map["ban_time"]); // Completely disable unban option if (global_ban_time == 0) { unban_enabled = false; } } if (configuration_map.count("pid_path") != 0) { pid_path = configuration_map["pid_path"]; } if (configuration_map.count("cli_stats_file_path") != 0) { cli_stats_file_path = configuration_map["cli_stats_file_path"]; } if (configuration_map.count("unban_only_if_attack_finished") != 0) { if (configuration_map["unban_only_if_attack_finished"] == "on") { unban_only_if_attack_finished = true; } else { unban_only_if_attack_finished = false; } } if(configuration_map.count("graphite_prefix") != 0) { graphite_prefix = configuration_map["graphite_prefix"]; } if (configuration_map.count("average_calculation_time") != 0) { average_calculation_amount = convert_string_to_integer(configuration_map["average_calculation_time"]); } if (configuration_map.count("average_calculation_time_for_subnets") != 0) { average_calculation_amount_for_subnets = convert_string_to_integer(configuration_map["average_calculation_time_for_subnets"]); } if (configuration_map.count("monitor_local_ip_addresses") != 0) { monitor_local_ip_addresses = configuration_map["monitor_local_ip_addresses"] == "on" ? true : false; } #ifdef FASTNETMON_API if (configuration_map.count("enable_api") != 0) { enable_api = configuration_map["enable_api"] == "on"; } #endif #ifdef ENABLE_GOBGP // GoBGP configuration if (configuration_map.count("gobgp") != 0) { gobgp_enabled = configuration_map["gobgp"] == "on"; } #endif // ExaBGP configuration if (configuration_map.count("exabgp") != 0) { if (configuration_map["exabgp"] == "on") { exabgp_enabled = true; } else { exabgp_enabled = false; } } if (exabgp_enabled) { // TODO: add community format validation if (configuration_map.count("exabgp_community")) { exabgp_community = configuration_map["exabgp_community"]; } if (configuration_map.count("exabgp_community_subnet")) { exabgp_community_subnet = configuration_map["exabgp_community_subnet"]; } else { exabgp_community_subnet = exabgp_community; } if (configuration_map.count("exabgp_community_host")) { exabgp_community_host = configuration_map["exabgp_community_host"]; } else { exabgp_community_host = exabgp_community; } if (exabgp_enabled && exabgp_announce_whole_subnet && exabgp_community_subnet.empty()) { logger << log4cpp::Priority::ERROR << "You enabled exabgp for subnet but not specified community, we disable exabgp support"; exabgp_enabled = false; } if (exabgp_enabled && exabgp_announce_host && exabgp_community_host.empty()) { logger << log4cpp::Priority::ERROR << "You enabled exabgp for host but not specified community, we disable exabgp support"; exabgp_enabled = false; } } if (exabgp_enabled) { exabgp_command_pipe = configuration_map["exabgp_command_pipe"]; if (exabgp_command_pipe.empty()) { logger << log4cpp::Priority::ERROR << "You enabled exabgp but not specified " "exabgp_command_pipe, so we disable exabgp " "support"; exabgp_enabled = false; } } if (exabgp_enabled) { exabgp_next_hop = configuration_map["exabgp_next_hop"]; if (exabgp_next_hop.empty()) { logger << log4cpp::Priority::ERROR << "You enabled exabgp but not specified exabgp_next_hop, so we disable exabgp support"; exabgp_enabled = false; } if (configuration_map.count("exabgp_flow_spec_announces") != 0) { exabgp_flow_spec_announces = configuration_map["exabgp_flow_spec_announces"] == "on"; } if (exabgp_enabled) { logger << log4cpp::Priority::INFO << "ExaBGP support initialized correctly"; } } if (configuration_map.count("sflow") != 0) { if (configuration_map["sflow"] == "on") { enable_sflow_collection = true; } else { enable_sflow_collection = false; } } if (configuration_map.count("pfring_hardware_filters_enabled") != 0) { pfring_hardware_filters_enabled = configuration_map["pfring_hardware_filters_enabled"] == "on"; } if (configuration_map.count("netflow") != 0) { if (configuration_map["netflow"] == "on") { enable_netflow_collection = true; } else { enable_netflow_collection = false; } } if (configuration_map.count("exabgp_announce_whole_subnet") != 0) { exabgp_announce_whole_subnet = configuration_map["exabgp_announce_whole_subnet"] == "on" ? true : false; } if (configuration_map.count("exabgp_announce_host") != 0) { exabgp_announce_host = configuration_map["exabgp_announce_host"] == "on" ? true : false; } if (configuration_map.count("enable_subnet_counters") != 0) { enable_subnet_counters = configuration_map["enable_subnet_counters"] == "on" ? true : false; } // Graphite if (configuration_map.count("graphite") != 0) { graphite_enabled = configuration_map["graphite"] == "on" ? true : false; } if (configuration_map.count("graphite_host") != 0) { graphite_host = configuration_map["graphite_host"]; } if (configuration_map.count("graphite_port") != 0) { graphite_port = convert_string_to_integer(configuration_map["graphite_port"]); } if (configuration_map.count("graphite_number_of_ips") != 0) { logger << log4cpp::Priority::ERROR << "Sorry, you have used deprecated function graphite_number_of_ips"; } if (configuration_map.count("process_incoming_traffic") != 0) { process_incoming_traffic = configuration_map["process_incoming_traffic"] == "on" ? true : false; } if (configuration_map.count("process_outgoing_traffic") != 0) { process_outgoing_traffic = configuration_map["process_outgoing_traffic"] == "on" ? true : false; } if (configuration_map.count("mirror") != 0) { if (configuration_map["mirror"] == "on") { enable_data_collection_from_mirror = true; } else { enable_data_collection_from_mirror = false; } } if (configuration_map.count("mirror_netmap") != 0) { if (configuration_map["mirror_netmap"] == "on") { enable_netmap_collection = true; } else { enable_netmap_collection = false; } } if (configuration_map.count("mirror_snabbswitch") != 0) { enable_snabbswitch_collection = configuration_map["mirror_snabbswitch"] == "on"; } if (configuration_map.count("mirror_afpacket") != 0) { enable_afpacket_collection = configuration_map["mirror_afpacket"] == "on"; } if (enable_netmap_collection && enable_data_collection_from_mirror) { logger << log4cpp::Priority::ERROR << "You have enabled pfring and netmap data collection " "from mirror which strictly prohibited, please " "select one"; exit(1); } if (configuration_map.count("pcap") != 0) { if (configuration_map["pcap"] == "on") { enable_pcap_collection = true; } else { enable_pcap_collection = false; } } // Read global ban configuration global_ban_settings = read_ban_settings(configuration_map, ""); logging_configuration = read_logging_settings(configuration_map); // logger << log4cpp::Priority::INFO << "We read global ban settings: " << print_ban_thresholds(global_ban_settings); // Read host group ban settings for (host_group_map_t::iterator hostgroup_itr = host_groups.begin(); hostgroup_itr != host_groups.end(); ++hostgroup_itr) { std::string host_group_name = hostgroup_itr->first; logger << log4cpp::Priority::INFO << "We will read ban settings for " << host_group_name; host_group_ban_settings_map[ host_group_name ] = read_ban_settings(configuration_map, host_group_name); //logger << log4cpp::Priority::INFO << "We read " << host_group_name << " ban settings " // << print_ban_thresholds(host_group_ban_settings_map[ host_group_name ]); } if (configuration_map.count("white_list_path") != 0) { white_list_path = configuration_map["white_list_path"]; } if (configuration_map.count("networks_list_path") != 0) { networks_list_path = configuration_map["networks_list_path"]; } #ifdef REDIS if (configuration_map.count("redis_port") != 0) { redis_port = convert_string_to_integer(configuration_map["redis_port"]); } if (configuration_map.count("redis_host") != 0) { redis_host = configuration_map["redis_host"]; } if (configuration_map.count("redis_prefix") != 0) { redis_prefix = configuration_map["redis_prefix"]; } if (configuration_map.count("redis_enabled") != 0) { // We use yes and on because it's stupid typo :( if (configuration_map["redis_enabled"] == "on" or configuration_map["redis_enabled"] == "yes") { redis_enabled = true; } else { redis_enabled = false; } } #endif #ifdef MONGO if (configuration_map.count("mongodb_enabled") != 0) { if (configuration_map["mongodb_enabled"] == "on") { mongodb_enabled = true; } } if (configuration_map.count("mongodb_host") != 0) { mongodb_host = configuration_map["mongodb_host"]; } if (configuration_map.count("mongodb_port") != 0) { mongodb_port = convert_string_to_integer(configuration_map["mongodb_port"]); } if (configuration_map.count("mongodb_database_name") != 0) { mongodb_database_name = configuration_map["mongodb_database_name"]; } #endif if (configuration_map.count("ban_details_records_count") != 0) { ban_details_records_count = convert_string_to_integer(configuration_map["ban_details_records_count"]); } if (configuration_map.count("check_period") != 0) { check_period = convert_string_to_integer(configuration_map["check_period"]); } if (configuration_map.count("sort_parameter") != 0) { sort_parameter = configuration_map["sort_parameter"]; } if (configuration_map.count("max_ips_in_list") != 0) { max_ips_in_list = convert_string_to_integer(configuration_map["max_ips_in_list"]); } if (configuration_map.count("notify_script_path") != 0) { notify_script_path = configuration_map["notify_script_path"]; } if (configuration_map.count("notify_script_pass_details") != 0) { notify_script_pass_details = configuration_map["notify_script_pass_details"] == "on" ? true : false; } if (file_exists(notify_script_path)) { notify_script_enabled = true; } else { logger << log4cpp::Priority::ERROR << "We can't find notify script " << notify_script_path; notify_script_enabled = false; } if (configuration_map.count("collect_attack_pcap_dumps") != 0) { collect_attack_pcap_dumps = configuration_map["collect_attack_pcap_dumps"] == "on" ? true : false; } if (configuration_map.count("process_pcap_attack_dumps_with_dpi") != 0) { if (collect_attack_pcap_dumps) { process_pcap_attack_dumps_with_dpi = configuration_map["process_pcap_attack_dumps_with_dpi"] == "on" ? true : false; } } return true; } /* Enable core dumps for simplify debug tasks */ void enable_core_dumps() { struct rlimit rlim; int result = getrlimit(RLIMIT_CORE, &rlim); if (result) { logger << log4cpp::Priority::ERROR << "Can't get current rlimit for RLIMIT_CORE"; return; } else { rlim.rlim_cur = rlim.rlim_max; setrlimit(RLIMIT_CORE, &rlim); } } void subnet_vectors_allocator(prefix_t* prefix, void* data) { // Network byte order uint32_t subnet_as_integer = prefix->add.sin.s_addr; u_short bitlen = prefix->bitlen; double base = 2; int network_size_in_ips = pow(base, 32 - bitlen); // logger<< log4cpp::Priority::INFO<<"Subnet: "<add.sin.s_addr<<" network size: // "<first; std::fill(itr->second.begin(), itr->second.end(), zero_map_element); } } void zeroify_all_flow_counters() { // On creating it initilizes by zeros conntrack_main_struct zero_conntrack_main_struct; // Iterate over map for (map_of_vector_counters_for_flow::iterator itr = SubnetVectorMapFlow.begin(); itr != SubnetVectorMapFlow.end(); ++itr) { // Iterate over vector for (vector_of_flow_counters::iterator vector_iterator = itr->second.begin(); vector_iterator != itr->second.end(); ++vector_iterator) { // TODO: rewrite this monkey code vector_iterator->in_tcp.clear(); vector_iterator->in_udp.clear(); vector_iterator->in_icmp.clear(); vector_iterator->in_other.clear(); vector_iterator->out_tcp.clear(); vector_iterator->out_udp.clear(); vector_iterator->out_icmp.clear(); vector_iterator->out_other.clear(); } } } bool load_our_networks_list() { if (file_exists(white_list_path)) { std::vector network_list_from_config = read_file_to_vector(white_list_path); for (std::vector::iterator ii = network_list_from_config.begin(); ii != network_list_from_config.end(); ++ii) { if (ii->length() > 0 && is_cidr_subnet(ii->c_str())) { make_and_lookup(whitelist_tree_ipv4, const_cast(ii->c_str())); } else { logger << log4cpp::Priority::ERROR << "Can't parse line from whitelist: " << *ii; } } logger << log4cpp::Priority::INFO << "We loaded " << network_list_from_config.size() << " networks from whitelist file"; } std::vector networks_list_ipv4_as_string; std::vector networks_list_ipv6_as_string; // We can bould "our subnets" automatically here if (file_exists("/proc/vz/version")) { logger << log4cpp::Priority::INFO << "We found OpenVZ"; // Add /32 CIDR mask for every IP here std::vector openvz_ips = read_file_to_vector("/proc/vz/veip"); for (std::vector::iterator ii = openvz_ips.begin(); ii != openvz_ips.end(); ++ii) { // skip header if (strstr(ii->c_str(), "Version") != NULL) { continue; } /* Example data for this lines: 2a03:f480:1:17:0:0:0:19 0 185.4.72.40 0 */ if (strstr(ii->c_str(), ":") == NULL) { // IPv4 std::vector subnet_as_string; split(subnet_as_string, *ii, boost::is_any_of(" "), boost::token_compress_on); std::string openvz_subnet = subnet_as_string[1] + "/32"; networks_list_ipv4_as_string.push_back(openvz_subnet); } else { // IPv6 std::vector subnet_as_string; split(subnet_as_string, *ii, boost::is_any_of(" "), boost::token_compress_on); std::string openvz_subnet = subnet_as_string[1] + "/128"; networks_list_ipv6_as_string.push_back(openvz_subnet); } } logger << log4cpp::Priority::INFO << "We loaded " << networks_list_ipv4_as_string.size() << " IPv4 networks from /proc/vz/veip"; logger << log4cpp::Priority::INFO << "We loaded " << networks_list_ipv6_as_string.size() << " IPv6 networks from /proc/vz/veip"; } if (monitor_local_ip_addresses && file_exists("/sbin/ip")) { logger << log4cpp::Priority::INFO << "We are working on Linux and could use ip tool for detecting local IP's"; ip_addresses_list_t ip_list = get_local_ip_v4_addresses_list(); logger << log4cpp::Priority::INFO << "We found " << ip_list.size() << " local IP addresses and will monitor they"; for (ip_addresses_list_t::iterator iter = ip_list.begin(); iter != ip_list.end(); ++iter) { // TODO: add IPv6 here networks_list_ipv4_as_string.push_back(*iter + "/32"); } } if (file_exists(networks_list_path)) { std::vector network_list_from_config = read_file_to_vector(networks_list_path); for (std::vector::iterator line_itr = network_list_from_config.begin(); line_itr != network_list_from_config.end(); ++line_itr) { if (line_itr->length() == 0) { // Skip blank lines in subnet list file silently continue; } if (strstr(line_itr->c_str(), ":") == NULL) { networks_list_ipv4_as_string.push_back(*line_itr); } else { networks_list_ipv6_as_string.push_back(*line_itr); } } logger << log4cpp::Priority::INFO << "We loaded " << network_list_from_config.size() << " networks from networks file"; } // Some consistency checks assert(convert_ip_as_string_to_uint("255.255.255.0") == convert_cidr_to_binary_netmask(24)); assert(convert_ip_as_string_to_uint("255.255.255.255") == convert_cidr_to_binary_netmask(32)); logger << log4cpp::Priority::INFO << "Totally we have " << networks_list_ipv4_as_string.size() << " IPv4 subnets"; logger << log4cpp::Priority::INFO << "Totally we have " << networks_list_ipv6_as_string.size() << " IPv6 subnets"; for (std::vector::iterator ii = networks_list_ipv4_as_string.begin(); ii != networks_list_ipv4_as_string.end(); ++ii) { if (!is_cidr_subnet(ii->c_str())) { logger << log4cpp::Priority::ERROR << "Can't parse line from subnet list: '" << *ii << "'"; continue; } std::string network_address_in_cidr_form = *ii; unsigned int cidr_mask = get_cidr_mask_from_network_as_string(network_address_in_cidr_form); std::string network_address = get_net_address_from_network_as_string(network_address_in_cidr_form); double base = 2; total_number_of_hosts_in_our_networks += pow(base, 32 - cidr_mask); // Make sure it's "subnet address" and not an host address uint32_t subnet_address_as_uint = convert_ip_as_string_to_uint(network_address); uint32_t subnet_address_netmask_binary = convert_cidr_to_binary_netmask(cidr_mask); uint32_t generated_subnet_address = subnet_address_as_uint & subnet_address_netmask_binary; if (subnet_address_as_uint != generated_subnet_address) { std::string new_network_address_as_string = convert_ip_as_uint_to_string(generated_subnet_address) + "/" + convert_int_to_string(cidr_mask); logger << log4cpp::Priority::WARN << "We will use " << new_network_address_as_string << " instead of " << network_address_in_cidr_form << " because it's host address"; network_address_in_cidr_form = new_network_address_as_string; } make_and_lookup(lookup_tree_ipv4, const_cast(network_address_in_cidr_form.c_str())); } for (std::vector::iterator ii = networks_list_ipv6_as_string.begin(); ii != networks_list_ipv6_as_string.end(); ++ii) { // TODO: add IPv6 subnet format validation make_and_lookup_ipv6(lookup_tree_ipv6, (char*)ii->c_str()); } logger << log4cpp::Priority::INFO << "Total number of monitored hosts (total size of all networks): " << total_number_of_hosts_in_our_networks; // 3 - speed counter, average speed counter and data counter uint64_t memory_requirements = 3 * sizeof(map_element) * total_number_of_hosts_in_our_networks / 1024 / 1024; logger << log4cpp::Priority::INFO << "We need " << memory_requirements << " MB of memory for storing counters for your networks"; /* Preallocate data structures */ patricia_process(lookup_tree_ipv4, (void_fn_t)subnet_vectors_allocator); logger << log4cpp::Priority::INFO << "We start total zerofication of counters"; zeroify_all_counters(); logger << log4cpp::Priority::INFO << "We finished zerofication"; logger << log4cpp::Priority::INFO << "We loaded " << networks_list_ipv4_as_string.size() << " IPv4 subnets to our in-memory list of networks"; return true; } #ifdef IPV6_HASH_COUNTERS moodycamel::ConcurrentQueue multi_process_queue_for_ipv6_counters; void ipv6_traffic_processor() { simple_packet packets_from_queue[32]; while (true) { std::size_t count = 0; while ((count = multi_process_queue_for_ipv6_counters.try_dequeue_bulk(packets_from_queue, 32)) != 0) { for (std::size_t i = 0; i != count; ++i) { __sync_fetch_and_add(&total_ipv6_packets, 1); direction packet_direction = packets_from_queue[i].packet_direction; uint64_t sampled_number_of_packets = packets_from_queue[i].number_of_packets * packets_from_queue[i].sample_ratio; uint64_t sampled_number_of_bytes = packets_from_queue[i].length * packets_from_queue[i].sample_ratio; __sync_fetch_and_add(&total_counters[packet_direction].packets, sampled_number_of_packets); __sync_fetch_and_add(&total_counters[packet_direction].bytes, sampled_number_of_bytes); if (packet_direction != OTHER) { __sync_fetch_and_add(&our_ipv6_packets, 1); } } } } } #endif /* Process simple unified packet */ void process_packet(simple_packet& current_packet) { // Packets dump is very useful for bug hunting if (DEBUG_DUMP_ALL_PACKETS) { logger << log4cpp::Priority::INFO << "Dump: " << print_simple_packet(current_packet); } if (current_packet.ip_protocol_version == 6) { #ifdef IPV6_HASH_COUNTERS current_packet.packet_direction = get_packet_direction_ipv6(lookup_tree_ipv6, current_packet.src_ipv6, current_packet.dst_ipv6); // TODO: move to bulk operations here! multi_process_queue_for_ipv6_counters.enqueue(current_packet); #else __sync_fetch_and_add(&total_ipv6_packets, 1); #endif return; } // We do not process IPv6 at all on this mement if (current_packet.ip_protocol_version != 4) { return; } // Subnet for found IPs unsigned long subnet = 0; unsigned int subnet_cidr_mask = 0; direction packet_direction = get_packet_direction(lookup_tree_ipv4, current_packet.src_ip, current_packet.dst_ip, subnet, subnet_cidr_mask); // It's useful in case when we can't find what packets do not processed correctly if (DEBUG_DUMP_OTHER_PACKETS && packet_direction == OTHER) { logger << log4cpp::Priority::INFO << "Dump other: " << print_simple_packet(current_packet); } // Skip processing of specific traffic direction if ((packet_direction == INCOMING && !process_incoming_traffic) or (packet_direction == OUTGOING && !process_outgoing_traffic)) { return; } subnet_t current_subnet = std::make_pair(subnet, subnet_cidr_mask); uint32_t subnet_in_host_byte_order = 0; // We operate in host bytes order and need to convert subnet if (subnet != 0) { subnet_in_host_byte_order = ntohl(current_subnet.first); } // Try to find map key for this subnet map_of_vector_counters::iterator itr; // Iterator for subnet counter subnet_counter_t* subnet_counter = NULL; if (packet_direction == OUTGOING or packet_direction == INCOMING) { // Find element in map of vectors itr = SubnetVectorMap.find(current_subnet); if (itr == SubnetVectorMap.end()) { logger << log4cpp::Priority::ERROR << "Can't find vector address in subnet map"; return; } if (enable_subnet_counters) { map_for_subnet_counters::iterator subnet_iterator; // Find element in map of subnet counters subnet_iterator = PerSubnetCountersMap.find(current_subnet); if (subnet_iterator == PerSubnetCountersMap.end()) { logger << log4cpp::Priority::ERROR << "Can't find counter structure for subnet"; return; } subnet_counter = &subnet_iterator->second; } } map_of_vector_counters_for_flow::iterator itr_flow; if (enable_conection_tracking) { if (packet_direction == OUTGOING or packet_direction == INCOMING) { itr_flow = SubnetVectorMapFlow.find(current_subnet); if (itr_flow == SubnetVectorMapFlow.end()) { logger << log4cpp::Priority::ERROR << "Can't find vector address in subnet flow map"; return; } } } /* Because we support mirroring, sflow and netflow we should support different cases: - One packet passed for processing (mirror) - Multiple packets ("flows") passed for processing (netflow) - One sampled packed passed for processing (netflow) - Another combinations of this three options */ uint64_t sampled_number_of_packets = current_packet.number_of_packets * current_packet.sample_ratio; uint64_t sampled_number_of_bytes = current_packet.length * current_packet.sample_ratio; __sync_fetch_and_add(&total_counters[packet_direction].packets, sampled_number_of_packets); __sync_fetch_and_add(&total_counters[packet_direction].bytes, sampled_number_of_bytes); // Incerementi main and per protocol packet counters if (packet_direction == OUTGOING) { int64_t shift_in_vector = (int64_t)ntohl(current_packet.src_ip) - (int64_t)subnet_in_host_byte_order; if (shift_in_vector < 0 or shift_in_vector >= itr->second.size()) { logger << log4cpp::Priority::ERROR << "We tried to access to element with index " << shift_in_vector << " which located outside allocated vector with size " << itr->second.size(); logger << log4cpp::Priority::ERROR << "We expect issues with this packet in OUTGOING direction: " << print_simple_packet(current_packet); return; } map_element* current_element = &itr->second[shift_in_vector]; // Main packet/bytes counter __sync_fetch_and_add(¤t_element->out_packets, sampled_number_of_packets); __sync_fetch_and_add(¤t_element->out_bytes, sampled_number_of_bytes); // Fragmented IP packets if (current_packet.ip_fragmented) { __sync_fetch_and_add(¤t_element->fragmented_out_packets, sampled_number_of_packets); __sync_fetch_and_add(¤t_element->fragmented_out_bytes, sampled_number_of_bytes); } // TODO: add another counters if (enable_subnet_counters) { __sync_fetch_and_add(&subnet_counter->out_packets, sampled_number_of_packets); __sync_fetch_and_add(&subnet_counter->out_bytes, sampled_number_of_bytes); } conntrack_main_struct* current_element_flow = NULL; if (enable_conection_tracking) { current_element_flow = &itr_flow->second[shift_in_vector]; } // Collect data when ban client if (!ban_list_details.empty() && ban_list_details.count(current_packet.src_ip) > 0 && ban_list_details[current_packet.src_ip].size() < ban_details_records_count) { ban_list_details_mutex.lock(); if (collect_attack_pcap_dumps) { // this code SHOULD NOT be called without mutex! if (current_packet.packet_payload_length > 0 && current_packet.packet_payload_pointer != NULL) { ban_list[current_packet.src_ip].pcap_attack_dump.write_packet(current_packet.packet_payload_pointer, current_packet.packet_payload_length); } } ban_list_details[current_packet.src_ip].push_back(current_packet); ban_list_details_mutex.unlock(); } uint64_t connection_tracking_hash = 0; if (enable_conection_tracking) { packed_conntrack_hash flow_tracking_structure; flow_tracking_structure.opposite_ip = current_packet.dst_ip; flow_tracking_structure.src_port = current_packet.source_port; flow_tracking_structure.dst_port = current_packet.destination_port; // convert this struct to 64 bit integer connection_tracking_hash = convert_conntrack_hash_struct_to_integer(&flow_tracking_structure); } if (current_packet.protocol == IPPROTO_TCP) { __sync_fetch_and_add(¤t_element->tcp_out_packets, sampled_number_of_packets); __sync_fetch_and_add(¤t_element->tcp_out_bytes, sampled_number_of_bytes); if (extract_bit_value(current_packet.flags, TCP_SYN_FLAG_SHIFT)) { __sync_fetch_and_add(¤t_element->tcp_syn_out_packets, sampled_number_of_packets); __sync_fetch_and_add(¤t_element->tcp_syn_out_bytes, sampled_number_of_bytes); } if (enable_conection_tracking) { flow_counter.lock(); conntrack_key_struct* conntrack_key_struct_ptr = ¤t_element_flow->out_tcp[connection_tracking_hash]; conntrack_key_struct_ptr->packets += sampled_number_of_packets; conntrack_key_struct_ptr->bytes += sampled_number_of_bytes; flow_counter.unlock(); } } else if (current_packet.protocol == IPPROTO_UDP) { __sync_fetch_and_add(¤t_element->udp_out_packets, sampled_number_of_packets); __sync_fetch_and_add(¤t_element->udp_out_bytes, sampled_number_of_bytes); if (enable_conection_tracking) { flow_counter.lock(); conntrack_key_struct* conntrack_key_struct_ptr = ¤t_element_flow->out_udp[connection_tracking_hash]; conntrack_key_struct_ptr->packets += sampled_number_of_packets; conntrack_key_struct_ptr->bytes += sampled_number_of_bytes; flow_counter.unlock(); } } else if (current_packet.protocol == IPPROTO_ICMP) { __sync_fetch_and_add(¤t_element->icmp_out_packets, sampled_number_of_packets); __sync_fetch_and_add(¤t_element->icmp_out_bytes, sampled_number_of_bytes); // no flow tracking for icmp } else { } } else if (packet_direction == INCOMING) { int64_t shift_in_vector = (int64_t)ntohl(current_packet.dst_ip) - (int64_t)subnet_in_host_byte_order; if (shift_in_vector < 0 or shift_in_vector >= itr->second.size()) { logger << log4cpp::Priority::ERROR << "We tried to access to element with index " << shift_in_vector << " which located outside allocated vector with size " << itr->second.size(); logger << log4cpp::Priority::ERROR << "We expect issues with this packet in INCOMING direction: " << print_simple_packet(current_packet); return; } map_element* current_element = &itr->second[shift_in_vector]; // Main packet/bytes counter __sync_fetch_and_add(¤t_element->in_packets, sampled_number_of_packets); __sync_fetch_and_add(¤t_element->in_bytes, sampled_number_of_bytes); if (enable_subnet_counters) { __sync_fetch_and_add(&subnet_counter->in_packets, sampled_number_of_packets); __sync_fetch_and_add(&subnet_counter->in_bytes, sampled_number_of_bytes); } // Count fragmented IP packets if (current_packet.ip_fragmented) { __sync_fetch_and_add(¤t_element->fragmented_in_packets, sampled_number_of_packets); __sync_fetch_and_add(¤t_element->fragmented_in_bytes, sampled_number_of_bytes); } conntrack_main_struct* current_element_flow = NULL; if (enable_conection_tracking) { current_element_flow = &itr_flow->second[shift_in_vector]; } uint64_t connection_tracking_hash = 0; if (enable_conection_tracking) { packed_conntrack_hash flow_tracking_structure; flow_tracking_structure.opposite_ip = current_packet.src_ip; flow_tracking_structure.src_port = current_packet.source_port; flow_tracking_structure.dst_port = current_packet.destination_port; // convert this struct to 64 bit integer connection_tracking_hash = convert_conntrack_hash_struct_to_integer(&flow_tracking_structure); } // Collect attack details if (!ban_list_details.empty() && ban_list_details.count(current_packet.dst_ip) > 0 && ban_list_details[current_packet.dst_ip].size() < ban_details_records_count) { ban_list_details_mutex.lock(); if (collect_attack_pcap_dumps) { // this code SHOULD NOT be called without mutex! if (current_packet.packet_payload_length > 0 && current_packet.packet_payload_pointer != NULL) { ban_list[current_packet.dst_ip].pcap_attack_dump.write_packet(current_packet.packet_payload_pointer, current_packet.packet_payload_length); } } ban_list_details[current_packet.dst_ip].push_back(current_packet); ban_list_details_mutex.unlock(); } if (current_packet.protocol == IPPROTO_TCP) { __sync_fetch_and_add(¤t_element->tcp_in_packets, sampled_number_of_packets); __sync_fetch_and_add(¤t_element->tcp_in_bytes, sampled_number_of_bytes); if (extract_bit_value(current_packet.flags, TCP_SYN_FLAG_SHIFT)) { __sync_fetch_and_add(¤t_element->tcp_syn_in_packets, sampled_number_of_packets); __sync_fetch_and_add(¤t_element->tcp_syn_in_bytes, sampled_number_of_bytes); } if (enable_conection_tracking) { flow_counter.lock(); conntrack_key_struct* conntrack_key_struct_ptr = ¤t_element_flow->in_tcp[connection_tracking_hash]; conntrack_key_struct_ptr->packets += sampled_number_of_packets; conntrack_key_struct_ptr->bytes += sampled_number_of_bytes; flow_counter.unlock(); } } else if (current_packet.protocol == IPPROTO_UDP) { __sync_fetch_and_add(¤t_element->udp_in_packets, sampled_number_of_packets); __sync_fetch_and_add(¤t_element->udp_in_bytes, sampled_number_of_bytes); if (enable_conection_tracking) { flow_counter.lock(); conntrack_key_struct* conntrack_key_struct_ptr = ¤t_element_flow->in_udp[connection_tracking_hash]; conntrack_key_struct_ptr->packets += sampled_number_of_packets; conntrack_key_struct_ptr->bytes += sampled_number_of_bytes; flow_counter.unlock(); } } else if (current_packet.protocol == IPPROTO_ICMP) { __sync_fetch_and_add(¤t_element->icmp_in_packets, sampled_number_of_packets); __sync_fetch_and_add(¤t_element->icmp_in_bytes, sampled_number_of_bytes); // no flow tracking for icmp } else { // TBD } } else if (packet_direction == INTERNAL) { } } #ifdef GEOIP unsigned int get_asn_for_ip(uint32_t ip) { char* asn_raw = GeoIP_org_by_name(geo_ip, convert_ip_as_uint_to_string(remote_ip).c_str()); uint32_t asn_number = 0; if (asn_raw == NULL) { asn_number = 0; } else { // split string: AS1299 TeliaSonera International Carrier std::vector asn_as_string; split(asn_as_string, asn_raw, boost::is_any_of(" "), boost::token_compress_on); // free up original string free(asn_raw); // extract raw number asn_number = convert_string_to_integer(asn_as_string[0].substr(2)); } return asn_number; } #endif // It's vizualization thread :) void screen_draw_thread() { // we need wait one second for calculating speed by recalculate_speed //#include // prctl(PR_SET_NAME , "fastnetmon calc thread", 0, 0, 0); // Sleep for a half second for shift against calculatiuon thread boost::this_thread::sleep(boost::posix_time::milliseconds(500)); while (true) { // Available only from boost 1.54: boost::this_thread::sleep_for( // boost::chrono::seconds(check_period) ); boost::this_thread::sleep(boost::posix_time::seconds(check_period)); traffic_draw_programm(); } } void recalculate_speed_thread_handler() { while (true) { // recalculate data every one second // Available only from boost 1.54: boost::this_thread::sleep_for( boost::chrono::seconds(1) // ); boost::this_thread::sleep(boost::posix_time::seconds(recalculate_speed_timeout)); recalculate_speed(); } } // Get ban settings for this subnet or return global ban settings ban_settings_t get_ban_settings_for_this_subnet(subnet_t subnet, std::string& host_group_name) { // Try to find host group for this subnet subnet_to_host_group_map_t::iterator host_group_itr = subnet_to_host_groups.find( subnet ); if (host_group_itr == subnet_to_host_groups.end()) { // We haven't host groups for all subnets, it's OK // logger << log4cpp::Priority::INFO << "We haven't custom host groups for this network. We will use global ban settings"; host_group_name = "global"; return global_ban_settings; } host_group_name = host_group_itr->second; // We found host group for this subnet host_group_ban_settings_map_t::iterator hostgroup_settings_itr = host_group_ban_settings_map.find(host_group_itr->second); if (hostgroup_settings_itr == host_group_ban_settings_map.end()) { logger << log4cpp::Priority::ERROR << "We can't find ban settings for host group " << host_group_itr->second; return global_ban_settings; } // We found ban settings for this host group and use they instead global return hostgroup_settings_itr->second; } /* Calculate speed for all connnections */ void recalculate_speed() { // logger<< log4cpp::Priority::INFO<<"We run recalculate_speed"; struct timeval start_calc_time; gettimeofday(&start_calc_time, NULL); double speed_calc_period = recalculate_speed_timeout; time_t start_time; time(&start_time); // If we got 1+ seconds lag we should use new "delta" or skip this step double time_difference = difftime(start_time, last_call_of_traffic_recalculation); if (time_difference < 1) { // It could occur on programm start logger << log4cpp::Priority::INFO << "We skip one iteration of speed_calc because it runs so early!"; return; } else if (int(time_difference) == int(speed_calc_period)) { // All fine, we run on time } else { logger << log4cpp::Priority::INFO << "Time from last run of speed_recalc is soooo big, we got ugly lags: " << time_difference; speed_calc_period = time_difference; } map_element zero_map_element; memset(&zero_map_element, 0, sizeof(zero_map_element)); uint64_t incoming_total_flows = 0; uint64_t outgoing_total_flows = 0; if (enable_subnet_counters) { for (map_for_subnet_counters::iterator itr = PerSubnetSpeedMap.begin(); itr != PerSubnetSpeedMap.end(); ++itr) { subnet_t current_subnet = itr->first; map_for_subnet_counters::iterator iter_subnet = PerSubnetCountersMap.find(current_subnet); if (iter_subnet == PerSubnetCountersMap.end()) { logger << log4cpp::Priority::INFO<<"Can't find traffic counters for subnet"; break; } subnet_counter_t* subnet_traffic = &iter_subnet->second; subnet_counter_t new_speed_element; new_speed_element.in_packets = uint64_t((double)subnet_traffic->in_packets / speed_calc_period); new_speed_element.in_bytes = uint64_t((double)subnet_traffic->in_bytes / speed_calc_period); new_speed_element.out_packets = uint64_t((double)subnet_traffic->out_packets / speed_calc_period); new_speed_element.out_bytes = uint64_t((double)subnet_traffic->out_bytes / speed_calc_period); /* Moving average recalculation for subnets */ /* http://en.wikipedia.org/wiki/Moving_average#Application_to_measuring_computer_performance */ double exp_power_subnet = -speed_calc_period / average_calculation_amount_for_subnets; double exp_value_subnet = exp(exp_power_subnet); map_element* current_average_speed_element = &PerSubnetAverageSpeedMap[current_subnet]; current_average_speed_element->in_bytes = uint64_t(new_speed_element.in_bytes + exp_value_subnet * ((double)current_average_speed_element->in_bytes - (double)new_speed_element.in_bytes)); current_average_speed_element->out_bytes = uint64_t(new_speed_element.out_bytes + exp_value_subnet * ((double)current_average_speed_element->out_bytes - (double)new_speed_element.out_bytes)); current_average_speed_element->in_packets = uint64_t(new_speed_element.in_packets + exp_value_subnet * ((double)current_average_speed_element->in_packets - (double)new_speed_element.in_packets)); current_average_speed_element->out_packets = uint64_t(new_speed_element.out_packets + exp_value_subnet * ((double)current_average_speed_element->out_packets - (double)new_speed_element.out_packets)); // Update speed calculation structure PerSubnetSpeedMap[current_subnet] = new_speed_element; *subnet_traffic = zero_map_element; //logger << log4cpp::Priority::INFO<second.begin(); vector_itr != itr->second.end(); ++vector_itr) { int current_index = vector_itr - itr->second.begin(); // New element map_element new_speed_element; // convert to host order for math operations uint32_t subnet_ip = ntohl(itr->first.first); uint32_t client_ip_in_host_bytes_order = subnet_ip + current_index; // covnert to our standard network byte order uint32_t client_ip = htonl(client_ip_in_host_bytes_order); // Calculate speed for IP or whole subnet build_speed_counters_from_packet_counters(new_speed_element, & *vector_itr, speed_calc_period); conntrack_main_struct* flow_counter_ptr = &SubnetVectorMapFlow[itr->first][current_index]; if (enable_conection_tracking) { // todo: optimize this operations! // it's really bad and SLOW CODE uint64_t total_out_flows = (uint64_t)flow_counter_ptr->out_tcp.size() + (uint64_t)flow_counter_ptr->out_udp.size() + (uint64_t)flow_counter_ptr->out_icmp.size() + (uint64_t)flow_counter_ptr->out_other.size(); uint64_t total_in_flows = (uint64_t)flow_counter_ptr->in_tcp.size() + (uint64_t)flow_counter_ptr->in_udp.size() + (uint64_t)flow_counter_ptr->in_icmp.size() + (uint64_t)flow_counter_ptr->in_other.size(); new_speed_element.out_flows = uint64_t((double)total_out_flows / speed_calc_period); new_speed_element.in_flows = uint64_t((double)total_in_flows / speed_calc_period); // Increment global counter outgoing_total_flows += new_speed_element.out_flows; incoming_total_flows += new_speed_element.in_flows; } else { new_speed_element.out_flows = 0; new_speed_element.in_flows = 0; } /* Moving average recalculation */ // http://en.wikipedia.org/wiki/Moving_average#Application_to_measuring_computer_performance // double speed_calc_period = 1; double exp_power = -speed_calc_period / average_calculation_amount; double exp_value = exp(exp_power); map_element* current_average_speed_element = &SubnetVectorMapSpeedAverage[itr->first][current_index]; // Calculate average speed from per-second speed build_average_speed_counters_from_speed_counters(current_average_speed_element, new_speed_element, exp_value, exp_power); if (enable_conection_tracking) { current_average_speed_element->out_flows = uint64_t( new_speed_element.out_flows + exp_value * ((double)current_average_speed_element->out_flows - (double)new_speed_element.out_flows)); current_average_speed_element->in_flows = uint64_t( new_speed_element.in_flows + exp_value * ((double)current_average_speed_element->in_flows - (double)new_speed_element.in_flows)); } /* Moving average recalculation end */ std::string host_group_name; ban_settings_t current_ban_settings = get_ban_settings_for_this_subnet(itr->first, host_group_name); if (we_should_ban_this_ip(current_average_speed_element, current_ban_settings)) { logger << log4cpp::Priority::INFO << "We have found host group for this host as: " << host_group_name; std::string flow_attack_details = ""; if (enable_conection_tracking) { flow_attack_details = print_flow_tracking_for_ip(*flow_counter_ptr, convert_ip_as_uint_to_string(client_ip)); } // TODO: we should pass type of ddos ban source (pps, flowd, bandwidth)! execute_ip_ban(client_ip, *current_average_speed_element, flow_attack_details, itr->first); } SubnetVectorMapSpeed[itr->first][current_index] = new_speed_element; *vector_itr = zero_map_element; } } // Calculate global flow speed incoming_total_flows_speed = uint64_t((double)incoming_total_flows / (double)speed_calc_period); outgoing_total_flows_speed = uint64_t((double)outgoing_total_flows / (double)speed_calc_period); if (enable_conection_tracking) { // Clean Flow Counter flow_counter.lock(); zeroify_all_flow_counters(); flow_counter.unlock(); } total_unparsed_packets_speed = uint64_t((double)total_unparsed_packets / (double)speed_calc_period); total_unparsed_packets = 0; for (unsigned int index = 0; index < 4; index++) { total_speed_counters[index].bytes = uint64_t((double)total_counters[index].bytes / (double)speed_calc_period); total_speed_counters[index].packets = uint64_t((double)total_counters[index].packets / (double)speed_calc_period); double exp_power = -speed_calc_period / average_calculation_amount; double exp_value = exp(exp_power); total_speed_average_counters[index].bytes = uint64_t(total_speed_counters[index].bytes + exp_value * ((double) total_speed_average_counters[index].bytes - (double) total_speed_counters[index].bytes)); total_speed_average_counters[index].packets = uint64_t(total_speed_counters[index].packets + exp_value * ((double) total_speed_average_counters[index].packets - (double) total_speed_counters[index].packets)); // nullify data counters after speed calculation total_counters[index].bytes = 0; total_counters[index].packets = 0; } // Set time of previous startup time(&last_call_of_traffic_recalculation); struct timeval finish_calc_time; gettimeofday(&finish_calc_time, NULL); timeval_subtract(&speed_calculation_time, &finish_calc_time, &start_calc_time); } void print_screen_contents_into_file(std::string screen_data_stats_param) { std::ofstream screen_data_file; screen_data_file.open(cli_stats_file_path.c_str(), std::ios::trunc); if (screen_data_file.is_open()) { screen_data_file << screen_data_stats_param; screen_data_file.close(); } else { logger << log4cpp::Priority::ERROR << "Can't print programm screen into file"; } } void traffic_draw_programm() { std::stringstream output_buffer; // logger< 0) { output_buffer << "ALERT! Toolkit working incorrectly! We should calculate speed in ~1 second\n"; } #ifdef IPV6_HASH_COUNTERS output_buffer << "Total amount of IPv6 packets: " << total_ipv6_packets << "\n"; #endif output_buffer << "Total amount of IPv6 packets related to our own network: " << our_ipv6_packets << "\n"; output_buffer << "Not processed packets: " << total_unparsed_packets_speed << " pps\n"; // Print backend stats if (enable_pcap_collection) { output_buffer << get_pcap_stats() << "\n"; } #ifdef PF_RING if (enable_data_collection_from_mirror) { output_buffer << get_pf_ring_stats(); } #endif // Print thresholds if (print_configuration_params_on_the_screen) { output_buffer << "\n" << print_ban_thresholds(global_ban_settings); } if (!ban_list.empty()) { output_buffer << std::endl << "Ban list:" << std::endl; output_buffer << print_ddos_attack_details(); } if (enable_subnet_counters) { output_buffer << std::endl << "Subnet load:" << std::endl; output_buffer << print_subnet_load() << "\n"; } screen_data_stats = output_buffer.str(); // Print screen contents into file print_screen_contents_into_file(screen_data_stats); struct timeval end_calc_time; gettimeofday(&end_calc_time, NULL); timeval_subtract(&drawing_thread_execution_time, &end_calc_time, &start_calc_time); } // pretty print channel speed in pps and MBit std::string print_channel_speed(std::string traffic_type, direction packet_direction) { uint64_t speed_in_pps = total_speed_average_counters[packet_direction].packets; uint64_t speed_in_bps = total_speed_average_counters[packet_direction].bytes; unsigned int number_of_tabs = 1; // We need this for correct alignment of blocks if (traffic_type == "Other traffic") { number_of_tabs = 2; } std::stringstream stream; stream << traffic_type; for (unsigned int i = 0; i < number_of_tabs; i++) { stream << "\t"; } uint64_t speed_in_mbps = convert_speed_to_mbps(speed_in_bps); stream << std::setw(6) << speed_in_pps << " pps " << std::setw(6) << speed_in_mbps << " mbps"; if (traffic_type == "Incoming traffic" or traffic_type == "Outgoing traffic") { if (packet_direction == INCOMING) { stream << " " << std::setw(6) << incoming_total_flows_speed << " flows"; } else if (packet_direction == OUTGOING) { stream << " " << std::setw(6) << outgoing_total_flows_speed << " flows"; } if (graphite_enabled) { graphite_data_t graphite_data; std::string direction_as_string; if (packet_direction == INCOMING) { direction_as_string = "incoming"; graphite_data[graphite_prefix + ".total." + direction_as_string + ".flows"] = incoming_total_flows_speed; } else if (packet_direction == OUTGOING) { direction_as_string = "outgoing"; graphite_data[graphite_prefix + ".total." + direction_as_string + ".flows"] = outgoing_total_flows_speed; } graphite_data[graphite_prefix + ".total." + direction_as_string + ".pps"] = speed_in_pps; graphite_data[graphite_prefix + ".total." + direction_as_string + ".bps"] = speed_in_bps * 8; bool graphite_put_result = store_data_to_graphite(graphite_port, graphite_host, graphite_data); if (!graphite_put_result) { logger << log4cpp::Priority::ERROR << "Can't store data to Graphite"; } } } return stream.str(); } bool file_is_appendable(std::string path) { std::ofstream check_appendable_file; check_appendable_file.open(path.c_str(), std::ios::app); if (check_appendable_file.is_open()) { // all fine, just close file check_appendable_file.close(); return true; } else { return false; } } void init_logging() { // So log4cpp will never notify you if it could not write to log file due to permissions issues // We will check it manually if (!file_is_appendable(log_file_path)) { std::cerr << "Can't open log file " << log_file_path << " for writing! Please check file and folder permissions" << std::endl; exit(EXIT_FAILURE); } log4cpp::PatternLayout* layout = new log4cpp::PatternLayout(); layout->setConversionPattern("%d [%p] %m%n"); log4cpp::Appender* appender = new log4cpp::FileAppender("default", log_file_path); appender->setLayout(layout); logger.setPriority(log4cpp::Priority::INFO); logger.addAppender(appender); logger << log4cpp::Priority::INFO << "Logger initialized!"; } void reconfigure_logging() { if (logging_configuration.local_syslog_logging) { log4cpp::Appender* local_syslog_appender = new log4cpp::SyslogAppender("fastnetmon", "fastnetmon", LOG_USER); logger.addAppender(local_syslog_appender); logger << log4cpp::Priority::INFO << "We start local syslog logging corectly"; } if (logging_configuration.remote_syslog_logging) { log4cpp::Appender* remote_syslog_appender = new log4cpp::RemoteSyslogAppender( "fastnetmon", "fastnetmon", logging_configuration.remote_syslog_server, LOG_USER, logging_configuration.remote_syslog_port); logger.addAppender(remote_syslog_appender); logger << log4cpp::Priority::INFO << "We start remote syslog logging corectly"; } } // Call fork function int do_fork() { int status = 0; switch (fork()) { case 0: // It's child break; case -1: /* fork failed */ status = -1; break; default: // We should close master process with _exit(0) // We should not call exit() because it will destroy all global variables for programm _exit(0); } return status; } void redirect_fds() { // Close stdin, stdout and stderr close(0); close(1); close(2); if (open("/dev/null", O_RDWR) != 0) { // We can't notify anybody now exit(1); } // Create copy of zero decriptor for 1 and 2 fd's // We do not need return codes here but we need do it for suppressing complaints from compiler int first_dup_result = dup(0); int second_dup_result = dup(0); } int main(int argc, char** argv) { bool daemonize = false; namespace po = boost::program_options; try { po::options_description desc("Allowed options"); desc.add_options() ("help", "produce help message") ("version", "show version") ("daemonize", "detach from the terminal") ("configuration_file", po::value(), "set path to custom configuration file") ; po::variables_map vm; po::store(po::parse_command_line(argc, argv, desc), vm); po::notify(vm); if (vm.count("help")) { std::cout << desc << std::endl; exit(EXIT_SUCCESS); } if (vm.count("version")) { std::cout << "Version: " << fastnetmon_version << std::endl; exit(EXIT_SUCCESS); } if (vm.count("daemonize")) { daemonize = true; } if (vm.count("configuration_file")) { global_config_path = vm["configuration_file"].as(); std::cout << "We will use custom path to configuration file: " << global_config_path << std::endl; } } catch (po::error& e) { std::cerr << "ERROR: " << e.what() << std::endl << std::endl; exit(EXIT_FAILURE); } // We use ideas from here https://github.com/bmc/daemonize/blob/master/daemon.c if (daemonize) { int status = 0; printf("We will run in daemonized mode\n"); if ((status = do_fork()) < 0) { // fork failed status = -1; } else if (setsid() < 0) { // Create new session status = -1; } else if ((status = do_fork()) < 0) { status = -1; } else { // Clear inherited umask umask(0); // Chdir to root int chdir_result = chdir("/"); // close all descriptors because we are daemon! redirect_fds(); } } // enable core dumps enable_core_dumps(); init_logging(); #ifdef FASTNETMON_API gpr_set_log_function(silent_logging_function); #endif // Set default ban configuration init_global_ban_settings(); // We should read configurartion file _after_ logging initialization bool load_config_result = load_configuration_file(); if (!load_config_result) { std::cerr << "Can't open config file " << global_config_path << " please create it!" << std::endl; exit(1); } if (file_exists(pid_path)) { pid_t pid_from_file = 0; if (read_pid_from_file(pid_from_file, pid_path)) { // We could read pid if (pid_from_file > 0) { // We use signal zero for check process existence int kill_result = kill(pid_from_file, 0); if (kill_result == 0) { logger << log4cpp::Priority::ERROR << "FastNetMon is already running with pid: " << pid_from_file; exit(1); } else { // Yes, we have pid with pid but it's zero } } else { // pid from file is broken, we assume tool is not running } } else { // We can't open file, let's assume it's broken and tool is not running } } else { // no pid file } // If we not failed in check steps we could run toolkit bool print_pid_to_file_result = print_pid_to_file(getpid(), pid_path); if (!print_pid_to_file_result) { logger << log4cpp::Priority::ERROR << "Could not create pid file, please check permissions: " << pid_path; exit(EXIT_FAILURE); } #ifdef ENABLE_DPI init_current_instance_of_ndpi(); #endif lookup_tree_ipv4 = New_Patricia(32); whitelist_tree_ipv4 = New_Patricia(32); lookup_tree_ipv6 = New_Patricia(128); whitelist_tree_ipv6 = New_Patricia(128); // nullify total counters for (int index = 0; index < 4; index++) { total_counters[index].bytes = 0; total_counters[index].packets = 0; total_speed_counters[index].bytes = 0; total_speed_counters[index].packets = 0; total_speed_average_counters[index].bytes = 0; total_speed_average_counters[index].packets = 0; } /* Create folder for attack details */ if (!folder_exists(attack_details_folder)) { int mkdir_result = mkdir(attack_details_folder.c_str(), S_IRWXU); if (mkdir_result != 0) { logger << log4cpp::Priority::ERROR << "Can't create folder for attack details: " << attack_details_folder; exit(1); } } if (getenv("DUMP_ALL_PACKETS") != NULL) { DEBUG_DUMP_ALL_PACKETS = true; } if (getenv("DUMP_OTHER_PACKETS") != NULL) { DEBUG_DUMP_OTHER_PACKETS = true; } if (sizeof(packed_conntrack_hash) != sizeof(uint64_t) or sizeof(packed_conntrack_hash) != 8) { logger << log4cpp::Priority::INFO << "Assertion about size of packed_conntrack_hash, it's " << sizeof(packed_conntrack_hash) << " instead 8"; exit(1); } logger << log4cpp::Priority::INFO << "Read configuration file"; // Reconfigure logging. We will enable specific logging methods here reconfigure_logging(); load_our_networks_list(); // Setup CTRL+C handler if (signal(SIGINT, interruption_signal_handler) == SIG_ERR) { logger << log4cpp::Priority::ERROR << "Can't setup SIGINT handler"; exit(1); } /* Without this SIGPIPE error could shutdown toolkit on call of exec_with_stdin_params */ if (signal(SIGPIPE, sigpipe_handler_for_popen) == SIG_ERR) { logger << log4cpp::Priority::ERROR << "Can't setup SIGPIPE handler"; exit(1); } #ifdef GEOIP // Init GeoIP if (!geoip_init()) { logger << log4cpp::Priority::ERROR << "Can't load geoip tables"; exit(1); } #endif // Init previous run date time(&last_call_of_traffic_recalculation); // We call init for each action #ifdef ENABLE_GOBGP if (gobgp_enabled) { gobgp_action_init(); } #endif #ifdef IPV6_HASH_COUNTERS service_thread_group.add_thread(new boost::thread(ipv6_traffic_processor)); #endif #ifdef FASTNETMON_API if (enable_api) { service_thread_group.add_thread(new boost::thread(RunApiServer)); } #endif // Run screen draw thread service_thread_group.add_thread(new boost::thread(screen_draw_thread)); // start thread for recalculating speed in realtime service_thread_group.add_thread(new boost::thread(recalculate_speed_thread_handler)); // Run banlist cleaner thread if (unban_enabled) { service_thread_group.add_thread(new boost::thread(cleanup_ban_list)); } // Run stats thread service_thread_group.add_thread(new boost::thread(collect_stats)); #ifdef PF_RING if (enable_data_collection_from_mirror) { packet_capture_plugin_thread_group.add_thread(new boost::thread(start_pfring_collection, process_packet)); } #endif // netmap processing if (enable_netmap_collection) { packet_capture_plugin_thread_group.add_thread(new boost::thread(start_netmap_collection, process_packet)); } #ifdef SNABB_SWITCH if (enable_snabbswitch_collection) { packet_capture_plugin_thread_group.add_thread(new boost::thread(start_snabbswitch_collection, process_packet)); } #endif #ifdef FASTNETMON_ENABLE_AFPACKET if (enable_afpacket_collection) { packet_capture_plugin_thread_group.add_thread(new boost::thread(start_afpacket_collection, process_packet)); } #endif if (enable_sflow_collection) { packet_capture_plugin_thread_group.add_thread(new boost::thread(start_sflow_collection, process_packet)); } if (enable_netflow_collection) { packet_capture_plugin_thread_group.add_thread(new boost::thread(start_netflow_collection, process_packet)); } if (enable_pcap_collection) { packet_capture_plugin_thread_group.add_thread(new boost::thread(start_pcap_collection, process_packet)); } // Wait for all threads in capture thread group packet_capture_plugin_thread_group.join_all(); // Wait for all service threads service_thread_group.join_all(); free_up_all_resources(); return 0; } void free_up_all_resources() { #ifdef GEOIP // Free up geoip handle GeoIP_delete(geo_ip); #endif Destroy_Patricia(lookup_tree_ipv4, (void_fn_t)0); Destroy_Patricia(whitelist_tree_ipv4, (void_fn_t)0); Destroy_Patricia(lookup_tree_ipv6, (void_fn_t)0); Destroy_Patricia(whitelist_tree_ipv6, (void_fn_t)0); } // For correct programm shutdown by CTRL+C void interruption_signal_handler(int signal_number) { logger << log4cpp::Priority::INFO << "SIGNAL captured, prepare toolkit shutdown"; #ifdef FASTNETMON_API logger << log4cpp::Priority::INFO << "Send shutdown command to API server"; api_server->Shutdown(); #endif logger << log4cpp::Priority::INFO << "Interrupt service threads"; service_thread_group.interrupt_all(); logger << log4cpp::Priority::INFO << "Wait while they finished"; service_thread_group.join_all(); logger << log4cpp::Priority::INFO << "Interrupt packet capture treads"; packet_capture_plugin_thread_group.interrupt_all(); logger << log4cpp::Priority::INFO << "Wait while they finished"; packet_capture_plugin_thread_group.join_all(); logger << log4cpp::Priority::INFO << "Shutdown main process"; // TODO: we should REMOVE this exit command and wait for correct toolkit shutdown exit(1); } unsigned int detect_attack_protocol(map_element& speed_element, direction attack_direction) { if (attack_direction == INCOMING) { return get_max_used_protocol(speed_element.tcp_in_packets, speed_element.udp_in_packets, speed_element.icmp_in_packets); } else { // OUTGOING return get_max_used_protocol(speed_element.tcp_out_packets, speed_element.udp_out_packets, speed_element.icmp_out_packets); } } #define my_max_on_defines(a, b) (a > b ? a : b) unsigned int get_max_used_protocol(uint64_t tcp, uint64_t udp, uint64_t icmp) { unsigned int max = my_max_on_defines(my_max_on_defines(udp, tcp), icmp); if (max == tcp) { return IPPROTO_TCP; } else if (max == udp) { return IPPROTO_UDP; } else if (max == icmp) { return IPPROTO_ICMP; } return 0; } void exabgp_ban_manage(std::string action, std::string ip_as_string, attack_details current_attack) { // We will announce whole subent here if (exabgp_announce_whole_subnet) { std::string subnet_as_string_with_mask = convert_subnet_to_string(current_attack.customer_network); exabgp_prefix_ban_manage(action, subnet_as_string_with_mask, exabgp_next_hop, exabgp_community_subnet); } // And we could announce single host here (/32) if (exabgp_announce_host) { std::string ip_as_string_with_mask = ip_as_string + "/32"; exabgp_prefix_ban_manage(action, ip_as_string_with_mask, exabgp_next_hop, exabgp_community_host); } } // Low level ExaBGP ban management void exabgp_prefix_ban_manage(std::string action, std::string prefix_as_string_with_mask, std::string exabgp_next_hop, std::string exabgp_community) { /* Buffer for BGP message */ char bgp_message[256]; if (action == "ban") { sprintf(bgp_message, "announce route %s next-hop %s community %s\n", prefix_as_string_with_mask.c_str(), exabgp_next_hop.c_str(), exabgp_community.c_str()); } else { sprintf(bgp_message, "withdraw route %s next-hop %s\n", prefix_as_string_with_mask.c_str(), exabgp_next_hop.c_str()); } logger << log4cpp::Priority::INFO << "ExaBGP announce message: " << bgp_message; int exabgp_pipe = open(exabgp_command_pipe.c_str(), O_WRONLY); if (exabgp_pipe <= 0) { logger << log4cpp::Priority::ERROR << "Can't open ExaBGP pipe " << exabgp_command_pipe << " Ban is not executed"; return; } int wrote_bytes = write(exabgp_pipe, bgp_message, strlen(bgp_message)); if (wrote_bytes != strlen(bgp_message)) { logger << log4cpp::Priority::ERROR << "Can't write message to ExaBGP pipe"; } close(exabgp_pipe); } bool exabgp_flow_spec_ban_manage(std::string action, std::string flow_spec_rule_as_text) { std::string announce_action; if (action == "ban") { announce_action = "announce"; } else { announce_action = "withdraw"; } // Trailing \n is very important! std::string bgp_message = announce_action + " " + flow_spec_rule_as_text + "\n"; int exabgp_pipe = open(exabgp_command_pipe.c_str(), O_WRONLY); if (exabgp_pipe <= 0) { logger << log4cpp::Priority::ERROR << "Can't open ExaBGP pipe for flow spec announce " << exabgp_command_pipe; return false; } int wrote_bytes = write(exabgp_pipe, bgp_message.c_str(), bgp_message.size()); if (wrote_bytes != bgp_message.size()) { logger << log4cpp::Priority::ERROR << "Can't write message to ExaBGP pipe"; return false; } close(exabgp_pipe); return true; } void execute_ip_ban(uint32_t client_ip, map_element average_speed_element, std::string flow_attack_details, subnet_t customer_subnet) { struct attack_details current_attack; uint64_t pps = 0; uint64_t in_pps = average_speed_element.in_packets; uint64_t out_pps = average_speed_element.out_packets; uint64_t in_bps = average_speed_element.in_bytes; uint64_t out_bps = average_speed_element.out_bytes; uint64_t in_flows = average_speed_element.in_flows; uint64_t out_flows = average_speed_element.out_flows; direction data_direction; if (!global_ban_settings.enable_ban) { logger << log4cpp::Priority::INFO << "We do not ban: " << convert_ip_as_uint_to_string(client_ip) << " because ban disabled completely"; return; } // Detect attack direction with simple heuristic if (abs(int((int)in_pps - (int)out_pps)) < 1000) { // If difference between pps speed is so small we should do additional investigation using // bandwidth speed if (in_bps > out_bps) { data_direction = INCOMING; pps = in_pps; } else { data_direction = OUTGOING; pps = out_pps; } } else { if (in_pps > out_pps) { data_direction = INCOMING; pps = in_pps; } else { data_direction = OUTGOING; pps = out_pps; } } current_attack.attack_protocol = detect_attack_protocol(average_speed_element, data_direction); if (ban_list.count(client_ip) > 0) { if (ban_list[client_ip].attack_direction != data_direction) { logger << log4cpp::Priority::INFO << "We expected very strange situation: attack direction for " << convert_ip_as_uint_to_string(client_ip) << " was changed"; return; } // update attack power if (pps > ban_list[client_ip].max_attack_power) { ban_list[client_ip].max_attack_power = pps; } return; } prefix_t prefix_for_check_adreess; prefix_for_check_adreess.add.sin.s_addr = client_ip; prefix_for_check_adreess.family = AF_INET; prefix_for_check_adreess.bitlen = 32; bool in_white_list = (patricia_search_best2(whitelist_tree_ipv4, &prefix_for_check_adreess, 1) != NULL); if (in_white_list) { return; } std::string data_direction_as_string = get_direction_name(data_direction); logger << log4cpp::Priority::INFO << "We run execute_ip_ban code with following params " << " in_pps: " << in_pps << " out_pps: " << out_pps << " in_bps: " << in_bps << " out_bps: " << out_bps << " and we decide it's " << data_direction_as_string << " attack"; std::string client_ip_as_string = convert_ip_as_uint_to_string(client_ip); std::string pps_as_string = convert_int_to_string(pps); // Store information about subnet current_attack.customer_network = customer_subnet; // Store ban time time(¤t_attack.ban_timestamp); // set ban time in seconds current_attack.ban_time = global_ban_time; current_attack.unban_enabled = unban_enabled; // Pass main information about attack current_attack.attack_direction = data_direction; current_attack.attack_power = pps; current_attack.max_attack_power = pps; current_attack.in_packets = in_pps; current_attack.out_packets = out_pps; current_attack.in_bytes = in_bps; current_attack.out_bytes = out_bps; // pass flow information current_attack.in_flows = in_flows; current_attack.out_flows = out_flows; current_attack.fragmented_in_packets = average_speed_element.fragmented_in_packets; current_attack.tcp_in_packets = average_speed_element.tcp_in_packets; current_attack.tcp_syn_in_packets = average_speed_element.tcp_syn_in_packets; current_attack.udp_in_packets = average_speed_element.udp_in_packets; current_attack.icmp_in_packets = average_speed_element.icmp_in_packets; current_attack.fragmented_out_packets = average_speed_element.fragmented_out_packets; current_attack.tcp_out_packets = average_speed_element.tcp_out_packets; current_attack.tcp_syn_out_packets = average_speed_element.tcp_syn_out_packets; current_attack.udp_out_packets = average_speed_element.udp_out_packets; current_attack.icmp_out_packets = average_speed_element.icmp_out_packets; current_attack.fragmented_out_bytes = average_speed_element.fragmented_out_bytes; current_attack.tcp_out_bytes = average_speed_element.tcp_out_bytes; current_attack.tcp_syn_out_bytes = average_speed_element.tcp_syn_out_bytes; current_attack.udp_out_bytes = average_speed_element.udp_out_bytes; current_attack.icmp_out_bytes = average_speed_element.icmp_out_bytes; current_attack.fragmented_in_bytes = average_speed_element.fragmented_in_bytes; current_attack.tcp_in_bytes = average_speed_element.tcp_in_bytes; current_attack.tcp_syn_in_bytes = average_speed_element.tcp_syn_in_bytes; current_attack.udp_in_bytes = average_speed_element.udp_in_bytes; current_attack.icmp_in_bytes = average_speed_element.icmp_in_bytes; current_attack.average_in_packets = average_speed_element.in_packets; current_attack.average_in_bytes = average_speed_element.in_bytes; current_attack.average_in_flows = average_speed_element.in_flows; current_attack.average_out_packets = average_speed_element.out_packets; current_attack.average_out_bytes = average_speed_element.out_bytes; current_attack.average_out_flows = average_speed_element.out_flows; if (collect_attack_pcap_dumps) { bool buffer_allocation_result = current_attack.pcap_attack_dump.allocate_buffer( number_of_packets_for_pcap_attack_dump ); if (!buffer_allocation_result) { logger << log4cpp::Priority::ERROR << "Can't allocate buffer for attack, switch off this option completely "; collect_attack_pcap_dumps = false; } } ban_list_mutex.lock(); ban_list[client_ip] = current_attack; ban_list_mutex.unlock(); ban_list_details_mutex.lock(); ban_list_details[client_ip] = std::vector(); ban_list_details_mutex.unlock(); logger << log4cpp::Priority::INFO << "Attack with direction: " << data_direction_as_string << " IP: " << client_ip_as_string << " Power: " << pps_as_string; call_ban_handlers(client_ip, ban_list[client_ip], flow_attack_details); } void call_ban_handlers(uint32_t client_ip, attack_details& current_attack, std::string flow_attack_details) { std::string client_ip_as_string = convert_ip_as_uint_to_string(client_ip); std::string pps_as_string = convert_int_to_string(current_attack.attack_power); std::string data_direction_as_string = get_direction_name(current_attack.attack_direction); bool store_attack_details_to_file = true; std::string basic_attack_information = get_attack_description(client_ip, current_attack); std::string basic_attack_information_in_json = get_attack_description_in_json(client_ip, current_attack); std::string full_attack_description = basic_attack_information + flow_attack_details; if (store_attack_details_to_file) { print_attack_details_to_file(full_attack_description, client_ip_as_string, current_attack); } if (pfring_hardware_filters_enabled) { #ifdef PF_RING logger << log4cpp::Priority::INFO << "We will block traffic to/from this IP with hardware filters"; pfring_hardware_filter_action_block(client_ip_as_string); #else logger << log4cpp::Priority::ERROR << "You haven't compiled PF_RING hardware filters support"; #endif } if (notify_script_enabled) { std::string script_call_params = notify_script_path + " " + client_ip_as_string + " " + data_direction_as_string + " " + pps_as_string + " " + "ban"; logger << log4cpp::Priority::INFO << "Call script for ban client: " << client_ip_as_string; // We should execute external script in separate thread because any lag in this code will be // very distructive if (notify_script_pass_details) { // We will pass attack details over stdin boost::thread exec_thread(exec_with_stdin_params, script_call_params, full_attack_description); exec_thread.detach(); } else { // Do not pass anything to script boost::thread exec_thread(exec, script_call_params); exec_thread.detach(); } logger << log4cpp::Priority::INFO << "Script for ban client is finished: " << client_ip_as_string; } if (exabgp_enabled) { logger << log4cpp::Priority::INFO << "Call ExaBGP for ban client started: " << client_ip_as_string; boost::thread exabgp_thread(exabgp_ban_manage, "ban", client_ip_as_string, current_attack); exabgp_thread.detach(); logger << log4cpp::Priority::INFO << "Call to ExaBGP for ban client is finished: " << client_ip_as_string; } #ifdef ENABLE_GOBGP if (gobgp_enabled) { logger << log4cpp::Priority::INFO << "Call GoBGP for ban client started: " << client_ip_as_string; boost::thread gobgp_thread(gobgp_ban_manage, "ban", client_ip_as_string, current_attack); gobgp_thread.detach(); logger << log4cpp::Priority::INFO << "Call to GoBGP for ban client is finished: " << client_ip_as_string; } #endif #ifdef REDIS if (redis_enabled) { std::string redis_key_name = client_ip_as_string + "_information"; if (!redis_prefix.empty()) { redis_key_name = redis_prefix + "_" + client_ip_as_string + "_information"; } logger << log4cpp::Priority::INFO << "Start data save in Redis in key: " << redis_key_name; boost::thread redis_store_thread(store_data_in_redis, redis_key_name, basic_attack_information_in_json); redis_store_thread.detach(); logger << log4cpp::Priority::INFO << "Finish data save in Redis in key: " << redis_key_name; } #ifdef MONGO if (mongodb_enabled) { std::string mongo_key_name = client_ip_as_string + "_information_" + print_time_t_in_fastnetmon_format(current_attack.ban_timestamp); // We could not use dot in key names: http://docs.mongodb.org/manual/core/document/#dot-notation std::replace(mongo_key_name.begin(), mongo_key_name.end(), '.', '_'); logger << log4cpp::Priority::INFO << "Start data save in Mongo in key: " << mongo_key_name; boost::thread mongo_store_thread(store_data_in_mongo, mongo_key_name, basic_attack_information_in_json); mongo_store_thread.detach(); logger << log4cpp::Priority::INFO << "Finish data save in Mongo in key: " << mongo_key_name; } #endif // If we have flow dump put in redis too if (redis_enabled && !flow_attack_details.empty()) { std::string redis_key_name = client_ip_as_string + "_flow_dump"; if (!redis_prefix.empty()) { redis_key_name = redis_prefix + "_" + client_ip_as_string + "_flow_dump"; } logger << log4cpp::Priority::INFO << "Start data save in redis in key: " << redis_key_name; boost::thread redis_store_thread(store_data_in_redis, redis_key_name, flow_attack_details); redis_store_thread.detach(); logger << log4cpp::Priority::INFO << "Finish data save in redis in key: " << redis_key_name; } #endif } void send_usage_data_to_reporting_server() { std::stringstream request_stream; request_stream << "GET " << "/heartbeat/stats?incoming_traffic_speed=" << total_speed_average_counters[INCOMING].bytes; request_stream << "&outgoing_traffic_speed=" << total_speed_average_counters[OUTGOING].bytes; request_stream << " HTTP/1.0\r\n"; request_stream << "Host: " << reporting_server << "\r\n"; request_stream << "Accept: */*\r\n"; request_stream << "Connection: close\r\n\r\n"; std::string reporting_server_ip_address = dns_lookup(reporting_server); if (reporting_server_ip_address.empty()) { logger << log4cpp::Priority::ERROR << "Stats server resolver failed, please check your DNS"; return; } bool result = store_data_to_stats_server(80, reporting_server_ip_address, request_stream.str()); if (!result) { logger << log4cpp::Priority::ERROR << "Can't collect stats data"; } } void collect_stats() { boost::this_thread::sleep(boost::posix_time::seconds(stats_thread_initial_call_delay)); while (true) { send_usage_data_to_reporting_server(); boost::this_thread::sleep(boost::posix_time::seconds(stats_thread_sleep_time)); } } /* Thread for cleaning up ban list */ void cleanup_ban_list() { // If we use very small ban time we should call ban_cleanup thread more often if (unban_iteration_sleep_time > global_ban_time) { unban_iteration_sleep_time = int(global_ban_time / 2); logger << log4cpp::Priority::INFO << "You are using enough small ban time " << global_ban_time << " we need reduce unban_iteration_sleep_time twices to " << unban_iteration_sleep_time << " seconds"; } logger << log4cpp::Priority::INFO << "Run banlist cleanup thread, we will awake every " << unban_iteration_sleep_time << " seconds"; while (true) { boost::this_thread::sleep(boost::posix_time::seconds(unban_iteration_sleep_time)); time_t current_time; time(¤t_time); std::vector ban_list_items_for_erase; for (std::map::iterator itr = ban_list.begin(); itr != ban_list.end(); ++itr) { uint32_t client_ip = itr->first; // This IP should be banned permanentely and we skip any processing if (!itr->second.unban_enabled) { continue; } double time_difference = difftime(current_time, itr->second.ban_timestamp); int ban_time = itr->second.ban_time; // Yes, we reached end of ban time for this customer bool we_could_unban_this_ip = time_difference > ban_time; // We haven't reached time for unban yet if (!we_could_unban_this_ip) { continue; } // Check about ongoing attack if (unban_only_if_attack_finished) { std::string client_ip_as_string = convert_ip_as_uint_to_string(client_ip); uint32_t subnet_in_host_byte_order = ntohl(itr->second.customer_network.first); int64_t shift_in_vector = (int64_t)ntohl(client_ip) - (int64_t)subnet_in_host_byte_order; // Try to find average speed element map_of_vector_counters::iterator itr_average_speed = SubnetVectorMapSpeedAverage.find(itr->second.customer_network); if (itr_average_speed == SubnetVectorMapSpeedAverage.end()) { logger << log4cpp::Priority::ERROR << "Can't find vector address in subnet map for unban function"; continue; } if (shift_in_vector < 0 or shift_in_vector >= itr_average_speed->second.size()) { logger << log4cpp::Priority::ERROR << "We tried to access to element with index " << shift_in_vector << " which located outside allocated vector with size " << itr_average_speed->second.size(); continue; } map_element* average_speed_element = &itr_average_speed->second[shift_in_vector]; // We get ban settings from host subnet std::string host_group_name; ban_settings_t current_ban_settings = get_ban_settings_for_this_subnet(itr->second.customer_network, host_group_name); if (we_should_ban_this_ip(average_speed_element, current_ban_settings)) { logger << log4cpp::Priority::ERROR << "Attack to IP " << client_ip_as_string << " still going! We should not unblock this host"; // Well, we still saw attack, skip to next iteration continue; } } // Add this IP to remove list // We will remove keyas really after this loop ban_list_items_for_erase.push_back(itr->first); // Call all hooks for unban call_unban_handlers(itr->first, itr->second); } // Remove all unbanned hosts from the ban list for (std::vector::iterator itr = ban_list_items_for_erase.begin(); itr != ban_list_items_for_erase.end(); ++itr) { ban_list_mutex.lock(); ban_list.erase(*itr); ban_list_mutex.unlock(); } } } void call_unban_handlers(uint32_t client_ip, attack_details& current_attack) { std::string client_ip_as_string = convert_ip_as_uint_to_string(client_ip); logger << log4cpp::Priority::INFO << "We will unban banned IP: " << client_ip_as_string << " because it ban time " << current_attack.ban_time << " seconds is ended"; if (notify_script_enabled) { std::string data_direction_as_string = get_direction_name(current_attack.attack_direction); std::string pps_as_string = convert_int_to_string(current_attack.attack_power); std::string script_call_params = notify_script_path + " " + client_ip_as_string + " " + data_direction_as_string + " " + pps_as_string + " unban"; logger << log4cpp::Priority::INFO << "Call script for unban client: " << client_ip_as_string; // We should execute external script in separate thread because any lag in this // code will be very distructive boost::thread exec_thread(exec, script_call_params); exec_thread.detach(); logger << log4cpp::Priority::INFO << "Script for unban client is finished: " << client_ip_as_string; } if (exabgp_enabled) { logger << log4cpp::Priority::INFO << "Call ExaBGP for unban client started: " << client_ip_as_string; boost::thread exabgp_thread(exabgp_ban_manage, "unban", client_ip_as_string, current_attack); exabgp_thread.detach(); logger << log4cpp::Priority::INFO << "Call to ExaBGP for unban client is finished: " << client_ip_as_string; } #ifdef ENABLE_GOBGP if (gobgp_enabled) { logger << log4cpp::Priority::INFO << "Call GoBGP for unban client started: " << client_ip_as_string; boost::thread gobgp_thread(gobgp_ban_manage, "unban", client_ip_as_string, current_attack); gobgp_thread.detach(); logger << log4cpp::Priority::INFO << "Call to GoBGP for unban client is finished: " << client_ip_as_string; } #endif } std::string print_ddos_attack_details() { std::stringstream output_buffer; for (std::map::iterator ii = ban_list.begin(); ii != ban_list.end(); ++ii) { uint32_t client_ip = (*ii).first; std::string client_ip_as_string = convert_ip_as_uint_to_string(client_ip); std::string max_pps_as_string = convert_int_to_string(((*ii).second).max_attack_power); std::string attack_direction = get_direction_name(((*ii).second).attack_direction); output_buffer << client_ip_as_string << "/" << max_pps_as_string << " pps " << attack_direction << " at " << print_time_t_in_fastnetmon_format(ii->second.ban_timestamp) << std::endl; send_attack_details(client_ip, (*ii).second); } return output_buffer.str(); } std::string get_attack_description(uint32_t client_ip, attack_details& current_attack) { std::stringstream attack_description; attack_description << "IP: " << convert_ip_as_uint_to_string(client_ip) << "\n"; attack_description << serialize_attack_description(current_attack) << "\n"; if (enable_subnet_counters) { // Got subnet tracking structure // TODO: we suppose case "no key exists" is not possible map_element network_speed_meter = PerSubnetSpeedMap[ current_attack.customer_network ]; map_element average_network_speed_meter = PerSubnetAverageSpeedMap[ current_attack.customer_network ]; attack_description <<"Network: " << convert_subnet_to_string(current_attack.customer_network) << "\n"; attack_description << serialize_network_load_to_text(network_speed_meter, false); attack_description << serialize_network_load_to_text(average_network_speed_meter, true); } attack_description << serialize_statistic_counters_about_attack(current_attack); return attack_description.str(); } std::string get_attack_description_in_json(uint32_t client_ip, attack_details& current_attack) { json_object* jobj = json_object_new_object(); json_object_object_add(jobj, "ip", json_object_new_string(convert_ip_as_uint_to_string(client_ip).c_str())); json_object_object_add(jobj, "attack_details", serialize_attack_description_to_json(current_attack) ); if (enable_subnet_counters) { map_element network_speed_meter = PerSubnetSpeedMap[ current_attack.customer_network ]; map_element average_network_speed_meter = PerSubnetAverageSpeedMap[ current_attack.customer_network ]; json_object_object_add(jobj, "network_load", serialize_network_load_to_json(network_speed_meter)); json_object_object_add(jobj, "network_average_load", serialize_network_load_to_json(average_network_speed_meter)); } // So we haven't statistic_counters here but from my point of view they are useless std::string json_as_text = json_object_to_json_string(jobj); // Free memory json_object_put(jobj); return json_as_text; } std::string generate_simple_packets_dump(std::vector& ban_list_details) { std::stringstream attack_details; std::map protocol_counter; for (std::vector::iterator iii = ban_list_details.begin(); iii != ban_list_details.end(); ++iii) { attack_details << print_simple_packet(*iii); protocol_counter[iii->protocol]++; } std::map::iterator max_proto = std::max_element(protocol_counter.begin(), protocol_counter.end(), protocol_counter.value_comp()); /* attack_details << "\n" << "We got more packets (" << max_proto->second << " from " << ban_details_records_count << ") for protocol: " << get_protocol_name_by_number(max_proto->first) << "\n"; */ return attack_details.str(); } void send_attack_details(uint32_t client_ip, attack_details current_attack_details) { std::string pps_as_string = convert_int_to_string(current_attack_details.attack_power); std::string attack_direction = get_direction_name(current_attack_details.attack_direction); std::string client_ip_as_string = convert_ip_as_uint_to_string(client_ip); // Very strange code but it work in 95% cases if (ban_list_details.count(client_ip) > 0 && ban_list_details[client_ip].size() >= ban_details_records_count) { std::stringstream attack_details; attack_details << get_attack_description(client_ip, current_attack_details) << "\n\n"; attack_details << generate_simple_packets_dump(ban_list_details[client_ip]); logger << log4cpp::Priority::INFO << "Attack with direction: " << attack_direction << " IP: " << client_ip_as_string << " Power: " << pps_as_string << " traffic samples collected"; call_attack_details_handlers(client_ip, current_attack_details, attack_details.str()); // TODO: here we have definitely RACE CONDITION!!! FIX IT // Remove key and prevent collection new data about this attack ban_list_details_mutex.lock(); ban_list_details.erase(client_ip); ban_list_details_mutex.unlock(); } } #ifdef ENABLE_DPI // Parse raw binary stand-alone packet with nDPI ndpi_protocol dpi_parse_packet(char* buffer, uint32_t len, uint32_t snap_len, struct ndpi_id_struct *src, struct ndpi_id_struct *dst, struct ndpi_flow_struct *flow, std::string& parsed_packet_as_string) { struct pfring_pkthdr packet_header; memset(&packet_header, 0, sizeof(packet_header)); packet_header.len = len; packet_header.caplen = snap_len; fastnetmon_parse_pkt((u_char*)buffer, &packet_header, 4, 1, 0); uint32_t current_tickt = 0; uint8_t* iph = (uint8_t*)(&buffer[packet_header.extended_hdr.parsed_pkt.offset.l3_offset]); unsigned int ipsize = packet_header.len; ndpi_protocol detected_protocol = ndpi_detection_process_packet(my_ndpi_struct, flow, iph, ipsize, current_tickt, src, dst); // So bad approach :( char print_buffer[512]; fastnetmon_print_parsed_pkt(print_buffer, 512, (u_char*)buffer, &packet_header); parsed_packet_as_string = std::string(print_buffer); return detected_protocol; } #endif #ifdef ENABLE_DPI void init_current_instance_of_ndpi() { my_ndpi_struct = init_ndpi(); if (my_ndpi_struct == NULL) { logger << log4cpp::Priority::ERROR << "Can't load nDPI, disable it!"; process_pcap_attack_dumps_with_dpi = false; return; } // Load sizes of main parsing structures ndpi_size_id_struct = ndpi_detection_get_sizeof_ndpi_id_struct(); ndpi_size_flow_struct = ndpi_detection_get_sizeof_ndpi_flow_struct(); } // Not so pretty copy and paste from pcap_reader() // TODO: rewrite to memory parser void produce_dpi_dump_for_pcap_dump(std::string pcap_file_path, std::stringstream& ss, std::string client_ip_as_string) { int filedesc = open(pcap_file_path.c_str(), O_RDONLY); if (filedesc <= 0) { logger << log4cpp::Priority::ERROR << "Can't open file for DPI"; return; } struct fastnetmon_pcap_file_header pcap_header; ssize_t file_header_readed_bytes = read(filedesc, &pcap_header, sizeof(struct fastnetmon_pcap_file_header)); if (file_header_readed_bytes != sizeof(struct fastnetmon_pcap_file_header)) { logger << log4cpp::Priority::ERROR << "Can't read pcap file header"; return; } // http://www.tcpdump.org/manpages/pcap-savefile.5.html if (pcap_header.magic == 0xa1b2c3d4 or pcap_header.magic == 0xd4c3b2a1) { // printf("Magic readed correctly\n"); } else { logger << log4cpp::Priority::ERROR << "Magic in file header broken"; return; } // Buffer for packets char packet_buffer[pcap_header.snaplen]; unsigned int total_packets_number = 0; uint64_t dns_amplification_packets = 0; uint64_t ntp_amplification_packets = 0; uint64_t ssdp_amplification_packets = 0; uint64_t snmp_amplification_packets = 0; while (1) { struct fastnetmon_pcap_pkthdr pcap_packet_header; ssize_t packet_header_readed_bytes = read(filedesc, &pcap_packet_header, sizeof(struct fastnetmon_pcap_pkthdr)); if (packet_header_readed_bytes != sizeof(struct fastnetmon_pcap_pkthdr)) { // We haven't any packets break; } if (pcap_packet_header.incl_len > pcap_header.snaplen) { logger << log4cpp::Priority::ERROR << "Please enlarge packet buffer for DPI"; return; } ssize_t packet_payload_readed_bytes = read(filedesc, packet_buffer, pcap_packet_header.incl_len); if (pcap_packet_header.incl_len != packet_payload_readed_bytes) { logger << log4cpp::Priority::ERROR << "I read packet header but can't read packet payload"; return; } struct ndpi_id_struct *src = NULL; struct ndpi_id_struct *dst = NULL; struct ndpi_flow_struct *flow = NULL; src = (struct ndpi_id_struct*)malloc(ndpi_size_id_struct); memset(src, 0, ndpi_size_id_struct); dst = (struct ndpi_id_struct*)malloc(ndpi_size_id_struct); memset(dst, 0, ndpi_size_id_struct); flow = (struct ndpi_flow_struct *)malloc(ndpi_size_flow_struct); memset(flow, 0, ndpi_size_flow_struct); std::string parsed_packet_as_string; ndpi_protocol detected_protocol = dpi_parse_packet(packet_buffer, pcap_packet_header.orig_len, pcap_packet_header.incl_len, src, dst, flow, parsed_packet_as_string); char* protocol_name = ndpi_get_proto_name(my_ndpi_struct, detected_protocol.protocol); char* master_protocol_name = ndpi_get_proto_name(my_ndpi_struct, detected_protocol.master_protocol); if (detected_protocol.protocol == NDPI_PROTOCOL_DNS) { // It's answer for ANY request with so much if (flow->protos.dns.query_type == 255 && flow->protos.dns.num_queries < flow->protos.dns.num_answers) { dns_amplification_packets++; } } else if (detected_protocol.protocol == NDPI_PROTOCOL_NTP) { // Detect packets with type MON_GETLIST_1 if (flow->protos.ntp.version == 2 && flow->protos.ntp.request_code == 42) { ntp_amplification_packets++; } } else if (detected_protocol.protocol == NDPI_PROTOCOL_SSDP) { // So, this protocol completely unexpected in WAN networks ssdp_amplification_packets++; } else if (detected_protocol.protocol == NDPI_PROTOCOL_SNMP) { // TODO: we need detailed tests for SNMP! snmp_amplification_packets++; } ss << parsed_packet_as_string << " protocol: " << protocol_name << " master_protocol: " << master_protocol_name << "\n"; // Free up all memory ndpi_free_flow(flow); free(dst); free(src); close(filedesc); total_packets_number++; } amplification_attack_type_t attack_type; // Attack type in unknown by default attack_type = AMPLIFICATION_ATTACK_UNKNOWN; // Detect amplification attack type if ( (double)dns_amplification_packets / (double)total_packets_number > 0.5) { attack_type = AMPLIFICATION_ATTACK_DNS; } else if ( (double)ntp_amplification_packets / (double)total_packets_number > 0.5) { attack_type = AMPLIFICATION_ATTACK_NTP; } else if ( (double)ssdp_amplification_packets / (double)total_packets_number > 0.5) { attack_type = AMPLIFICATION_ATTACK_SSDP; } else if ( (double)snmp_amplification_packets / (double)total_packets_number > 0.5) { attack_type = AMPLIFICATION_ATTACK_SNMP; } if (attack_type == AMPLIFICATION_ATTACK_UNKNOWN) { logger << log4cpp::Priority::ERROR << "We can't detect attack type with DPI it's not so criticial, only for your information"; } else { logger << log4cpp::Priority::INFO << "We detected this attack as: " << get_amplification_attack_type(attack_type); std::string flow_spec_rule_text = generate_flow_spec_for_amplification_attack(attack_type, client_ip_as_string); logger << log4cpp::Priority::INFO << "We have generated BGP Flow Spec rule for this attack: " << flow_spec_rule_text; if (exabgp_flow_spec_announces) { active_flow_spec_announces_t::iterator itr = active_flow_spec_announces.find(flow_spec_rule_text); if (itr == active_flow_spec_announces.end()) { // We havent this flow spec rule active yet logger << log4cpp::Priority::INFO << "We will publish flow spec announce about this attack"; bool exabgp_publish_result = exabgp_flow_spec_ban_manage("ban", flow_spec_rule_text); if (exabgp_publish_result) { active_flow_spec_announces[ flow_spec_rule_text ] = 1; } } else { // We have already blocked this attack } } } } #endif void call_attack_details_handlers(uint32_t client_ip, attack_details& current_attack, std::string attack_fingerprint) { std::string client_ip_as_string = convert_ip_as_uint_to_string(client_ip); std::string attack_direction = get_direction_name(current_attack.attack_direction); std::string pps_as_string = convert_int_to_string(current_attack.attack_power); // We place this variables here because we need this paths from DPI parser code std::string ban_timestamp_as_string = print_time_t_in_fastnetmon_format(current_attack.ban_timestamp); std::string attack_pcap_dump_path = attack_details_folder + "/" + client_ip_as_string + "_" + ban_timestamp_as_string + ".pcap"; if (collect_attack_pcap_dumps) { int pcap_fump_filedesc = open(attack_pcap_dump_path.c_str(), O_WRONLY|O_CREAT, S_IRUSR|S_IWUSR); if (pcap_fump_filedesc <= 0) { logger << log4cpp::Priority::ERROR << "Can't open file for storing pcap dump: " << attack_pcap_dump_path; } else { ssize_t wrote_bytes = write(pcap_fump_filedesc, (void*)current_attack.pcap_attack_dump.get_buffer_pointer(), current_attack.pcap_attack_dump.get_used_memory()); if (wrote_bytes != current_attack.pcap_attack_dump.get_used_memory()) { logger << log4cpp::Priority::ERROR << "Can't wrote all attack details to the disk correctly"; } close (pcap_fump_filedesc); // Freeup memory current_attack.pcap_attack_dump.deallocate_buffer(); } } #ifdef ENABLE_DPI // Yes, will be fine to read packets from the memory but we haven't this code yet // Thus we could read from file with not good performance because it's simpler if (collect_attack_pcap_dumps && process_pcap_attack_dumps_with_dpi) { std::stringstream string_buffer_for_dpi_data; string_buffer_for_dpi_data << "\n\nDPI\n\n"; produce_dpi_dump_for_pcap_dump(attack_pcap_dump_path, string_buffer_for_dpi_data, client_ip_as_string); attack_fingerprint = attack_fingerprint + string_buffer_for_dpi_data.str(); } #endif print_attack_details_to_file(attack_fingerprint, client_ip_as_string, current_attack); // Pass attack details to script if (notify_script_enabled) { logger << log4cpp::Priority::INFO << "Call script for notify about attack details for: " << client_ip_as_string; std::string script_params = notify_script_path + " " + client_ip_as_string + " " + attack_direction + " " + pps_as_string + " attack_details"; // We should execute external script in separate thread because any lag in this code // will be very distructive boost::thread exec_with_params_thread(exec_with_stdin_params, script_params, attack_fingerprint); exec_with_params_thread.detach(); logger << log4cpp::Priority::INFO << "Script for notify about attack details is finished: " << client_ip_as_string; } #ifdef REDIS if (redis_enabled) { std::string redis_key_name = client_ip_as_string + "_packets_dump"; if (!redis_prefix.empty()) { redis_key_name = redis_prefix + "_" + client_ip_as_string + "_packets_dump"; } logger << log4cpp::Priority::INFO << "Start data save in redis for key: " << redis_key_name; boost::thread redis_store_thread(store_data_in_redis, redis_key_name, attack_fingerprint); redis_store_thread.detach(); logger << log4cpp::Priority::INFO << "Finish data save in redis for key: " << redis_key_name; } #endif } uint64_t convert_conntrack_hash_struct_to_integer(packed_conntrack_hash* struct_value) { uint64_t unpacked_data = 0; memcpy(&unpacked_data, struct_value, sizeof(uint64_t)); return unpacked_data; } void convert_integer_to_conntrack_hash_struct(packed_session* packed_connection_data, packed_conntrack_hash* unpacked_data) { memcpy(unpacked_data, packed_connection_data, sizeof(uint64_t)); } std::string print_flow_tracking_for_specified_protocol(contrack_map_type& protocol_map, std::string client_ip, direction flow_direction) { std::stringstream buffer; // We shoud iterate over all fields int printed_records = 0; for (contrack_map_type::iterator itr = protocol_map.begin(); itr != protocol_map.end(); ++itr) { // We should limit number of records in flow dump because syn flood attacks produce // thounsands of lines if (printed_records > ban_details_records_count) { buffer << "Flows have cropped due to very long list.\n"; break; } uint64_t packed_connection_data = itr->first; packed_conntrack_hash unpacked_key_struct; convert_integer_to_conntrack_hash_struct(&packed_connection_data, &unpacked_key_struct); std::string opposite_ip_as_string = convert_ip_as_uint_to_string(unpacked_key_struct.opposite_ip); if (flow_direction == INCOMING) { buffer << client_ip << ":" << unpacked_key_struct.dst_port << " < " << opposite_ip_as_string << ":" << unpacked_key_struct.src_port << " "; } else if (flow_direction == OUTGOING) { buffer << client_ip << ":" << unpacked_key_struct.src_port << " > " << opposite_ip_as_string << ":" << unpacked_key_struct.dst_port << " "; } buffer << itr->second.bytes << " bytes " << itr->second.packets << " packets"; buffer << "\n"; printed_records++; } return buffer.str(); } /* Attack types: - syn flood: one local port, multiple remote hosts (and maybe multiple remote ports) and small packet size */ /* Iterate over all flow tracking table */ bool process_flow_tracking_table(conntrack_main_struct& conntrack_element, std::string client_ip) { std::map uniq_remote_hosts_which_generate_requests_to_us; std::map uniq_local_ports_which_target_of_connectiuons_from_inside; /* Process incoming TCP connections */ for (contrack_map_type::iterator itr = conntrack_element.in_tcp.begin(); itr != conntrack_element.in_tcp.end(); ++itr) { uint64_t packed_connection_data = itr->first; packed_conntrack_hash unpacked_key_struct; convert_integer_to_conntrack_hash_struct(&packed_connection_data, &unpacked_key_struct); uniq_remote_hosts_which_generate_requests_to_us[unpacked_key_struct.opposite_ip]++; uniq_local_ports_which_target_of_connectiuons_from_inside[unpacked_key_struct.dst_port]++; // we can calc average packet size // string opposite_ip_as_string = // convert_ip_as_uint_to_string(unpacked_key_struct.opposite_ip); // unpacked_key_struct.src_port // unpacked_key_struct.dst_port // itr->second.packets // itr->second.bytes } return true; } std::string print_flow_tracking_for_ip(conntrack_main_struct& conntrack_element, std::string client_ip) { std::stringstream buffer; std::string in_tcp = print_flow_tracking_for_specified_protocol(conntrack_element.in_tcp, client_ip, INCOMING); std::string in_udp = print_flow_tracking_for_specified_protocol(conntrack_element.in_udp, client_ip, INCOMING); unsigned long long total_number_of_incoming_tcp_flows = conntrack_element.in_tcp.size(); unsigned long long total_number_of_incoming_udp_flows = conntrack_element.in_udp.size(); unsigned long long total_number_of_outgoing_tcp_flows = conntrack_element.out_tcp.size(); unsigned long long total_number_of_outgoing_udp_flows = conntrack_element.out_udp.size(); bool we_have_incoming_flows = in_tcp.length() > 0 or in_udp.length() > 0; if (we_have_incoming_flows) { buffer << "Incoming\n\n"; if (in_tcp.length() > 0) { buffer << "TCP flows: " << total_number_of_incoming_tcp_flows << "\n"; buffer << in_tcp << "\n"; } if (in_udp.length() > 0) { buffer << "UDP flows: " << total_number_of_incoming_udp_flows << "\n"; buffer << in_udp << "\n"; } } std::string out_tcp = print_flow_tracking_for_specified_protocol(conntrack_element.out_tcp, client_ip, OUTGOING); std::string out_udp = print_flow_tracking_for_specified_protocol(conntrack_element.out_udp, client_ip, OUTGOING); bool we_have_outgoing_flows = out_tcp.length() > 0 or out_udp.length() > 0; // print delimiter if we have flows in both directions if (we_have_incoming_flows && we_have_outgoing_flows) { buffer << "\n"; } if (we_have_outgoing_flows) { buffer << "Outgoing\n\n"; if (out_tcp.length() > 0) { buffer << "TCP flows: " << total_number_of_outgoing_tcp_flows << "\n"; buffer << out_tcp << "\n"; } if (out_udp.length() > 0) { buffer << "UDP flows: " << total_number_of_outgoing_udp_flows << "\n"; buffer << out_udp << "\n"; } } return buffer.str(); } std::string print_subnet_load() { std::stringstream buffer; sort_type sorter; if (sort_parameter == "packets") { sorter = PACKETS; } else if (sort_parameter == "bytes") { sorter = BYTES; } else if (sort_parameter == "flows") { sorter = FLOWS; } else { logger << log4cpp::Priority::INFO << "Unexpected sorter type: " << sort_parameter; sorter = PACKETS; } std::vector vector_for_sort; vector_for_sort.reserve(PerSubnetSpeedMap.size()); for (map_for_subnet_counters::iterator itr = PerSubnetSpeedMap.begin(); itr != PerSubnetSpeedMap.end(); ++itr) { vector_for_sort.push_back(std::make_pair(itr->first, itr->second)); } std::sort(vector_for_sort.begin(), vector_for_sort.end(), TrafficComparatorClass(INCOMING, sorter)); graphite_data_t graphite_data; for (std::vector::iterator itr = vector_for_sort.begin(); itr != vector_for_sort.end(); ++itr) { map_element* speed = &itr->second; std::string subnet_as_string = convert_subnet_to_string(itr->first); buffer << std::setw(18) << std::left << subnet_as_string; if (graphite_enabled) { std::string subnet_as_string_as_dash_delimiters = subnet_as_string; // Replace dots by dashes std::replace(subnet_as_string_as_dash_delimiters.begin(), subnet_as_string_as_dash_delimiters.end(), '.', '_'); // Replace / by dashes too std::replace(subnet_as_string_as_dash_delimiters.begin(), subnet_as_string_as_dash_delimiters.end(), '/', '_'); graphite_data[ graphite_prefix + ".networks." + subnet_as_string_as_dash_delimiters + ".incoming.pps" ] = speed->in_packets; graphite_data[ graphite_prefix + ".networks." + subnet_as_string_as_dash_delimiters + ".outgoing.pps" ] = speed->out_packets; graphite_data[ graphite_prefix + ".networks." + subnet_as_string_as_dash_delimiters + ".incoming.bps" ] = speed->in_bytes * 8; graphite_data[ graphite_prefix + ".networks." + subnet_as_string_as_dash_delimiters + ".outgoing.bps" ] = speed->out_bytes * 8; } buffer << " " << "pps in: " << std::setw(8) << speed->in_packets << " out: " << std::setw(8) << speed->out_packets << " mbps in: " << std::setw(5) << convert_speed_to_mbps(speed->in_bytes) << " out: " << std::setw(5) << convert_speed_to_mbps(speed->out_bytes) << "\n"; } if (graphite_enabled) { bool graphite_put_result = store_data_to_graphite(graphite_port, graphite_host, graphite_data); if (!graphite_put_result) { logger << log4cpp::Priority::ERROR << "Can't store network load data to Graphite"; } } return buffer.str(); } std::string print_ban_thresholds(ban_settings_t current_ban_settings) { std::stringstream output_buffer; output_buffer << "Configuration params:\n"; if (current_ban_settings.enable_ban) { output_buffer << "We call ban script: yes\n"; } else { output_buffer << "We call ban script: no\n"; } output_buffer << "Packets per second: "; if (current_ban_settings.enable_ban_for_pps) { output_buffer << current_ban_settings.ban_threshold_pps; } else { output_buffer << "disabled"; } output_buffer << "\n"; output_buffer << "Mbps per second: "; if (current_ban_settings.enable_ban_for_bandwidth) { output_buffer << current_ban_settings.ban_threshold_mbps; } else { output_buffer << "disabled"; } output_buffer << "\n"; output_buffer << "Flows per second: "; if (current_ban_settings.enable_ban_for_flows_per_second) { output_buffer << current_ban_settings.ban_threshold_flows; } else { output_buffer << "disabled"; } output_buffer << "\n"; return output_buffer.str(); } void print_attack_details_to_file(std::string details, std::string client_ip_as_string, attack_details current_attack) { std::ofstream my_attack_details_file; std::string ban_timestamp_as_string = print_time_t_in_fastnetmon_format(current_attack.ban_timestamp); std::string attack_dump_path = attack_details_folder + "/" + client_ip_as_string + "_" + ban_timestamp_as_string; my_attack_details_file.open(attack_dump_path.c_str(), std::ios::app); if (my_attack_details_file.is_open()) { my_attack_details_file << details << "\n\n"; my_attack_details_file.close(); } else { logger << log4cpp::Priority::ERROR << "Can't print attack details to file"; } } logging_configuration_t read_logging_settings(configuration_map_t configuration_map) { logging_configuration_t logging_configuration_temp; if (configuration_map.count("logging:local_syslog_logging") != 0) { logging_configuration_temp.local_syslog_logging = configuration_map["logging:local_syslog_logging"] == "on"; } if (configuration_map.count("logging:remote_syslog_logging") != 0) { logging_configuration_temp.remote_syslog_logging = configuration_map["logging:remote_syslog_logging"] == "on"; } if (configuration_map.count("logging:remote_syslog_server") != 0) { logging_configuration_temp.remote_syslog_server = configuration_map["logging:remote_syslog_server"]; } if (configuration_map.count("logging:remote_syslog_port") != 0) { logging_configuration_temp.remote_syslog_port = convert_string_to_integer(configuration_map["logging:remote_syslog_port"]); } if (logging_configuration_temp.remote_syslog_logging) { if (logging_configuration_temp.remote_syslog_port > 0 && !logging_configuration_temp.remote_syslog_server.empty()) { logger << log4cpp::Priority::INFO << "We have configured remote syslog logging corectly"; } else { logger << log4cpp::Priority::ERROR << "You have enabled remote logging but haven't specified port or host"; logging_configuration_temp.remote_syslog_logging = false; } } if (logging_configuration_temp.local_syslog_logging) { logger << log4cpp::Priority::INFO << "We have configured local syslog logging corectly"; } return logging_configuration_temp; } ban_settings_t read_ban_settings(configuration_map_t configuration_map, std::string host_group_name) { ban_settings_t ban_settings; std::string prefix = ""; if (host_group_name != "") { prefix = host_group_name + "_"; } if (configuration_map.count(prefix + "enable_ban") != 0) { ban_settings.enable_ban = configuration_map[prefix + "enable_ban"] == "on"; } if (configuration_map.count(prefix + "ban_for_pps") != 0) { ban_settings.enable_ban_for_pps = configuration_map[prefix + "ban_for_pps"] == "on"; } if (configuration_map.count(prefix + "ban_for_bandwidth") != 0) { ban_settings.enable_ban_for_bandwidth = configuration_map[prefix + "ban_for_bandwidth"] == "on"; } if (configuration_map.count(prefix + "ban_for_flows") != 0) { ban_settings.enable_ban_for_flows_per_second = configuration_map[prefix + "ban_for_flows"] == "on"; } // Per protocol bandwidth triggers if (configuration_map.count(prefix + "ban_for_tcp_bandwidth") != 0) { ban_settings.enable_ban_for_tcp_bandwidth = configuration_map[prefix + "ban_for_tcp_bandwidth"] == "on"; } if (configuration_map.count(prefix + "ban_for_udp_bandwidth") != 0) { ban_settings.enable_ban_for_udp_bandwidth = configuration_map[prefix + "ban_for_udp_bandwidth"] == "on"; } if (configuration_map.count(prefix + "ban_for_icmp_bandwidth") != 0) { ban_settings.enable_ban_for_icmp_bandwidth = configuration_map[prefix + "ban_for_icmp_bandwidth"] == "on"; } // Per protocol pps ban triggers if (configuration_map.count(prefix + "ban_for_tcp_pps") != 0) { ban_settings.enable_ban_for_tcp_pps = configuration_map[prefix + "ban_for_tcp_pps"] == "on"; } if (configuration_map.count(prefix + "ban_for_udp_pps") != 0) { ban_settings.enable_ban_for_udp_pps = configuration_map[prefix + "ban_for_udp_pps"] == "on"; } if (configuration_map.count(prefix + "ban_for_icmp_pps") != 0) { ban_settings.enable_ban_for_icmp_pps = configuration_map[prefix + "ban_for_icmp_pps"] == "on"; } // Pps per protocol thresholds if (configuration_map.count(prefix + "threshold_tcp_pps") != 0) { ban_settings.ban_threshold_tcp_pps = convert_string_to_integer(configuration_map[prefix + "threshold_tcp_pps"]); } if (configuration_map.count(prefix + "threshold_udp_pps") != 0) { ban_settings.ban_threshold_udp_pps = convert_string_to_integer(configuration_map[prefix + "threshold_udp_pps"]); } if (configuration_map.count(prefix + "threshold_icmp_pps") != 0) { ban_settings.ban_threshold_icmp_pps = convert_string_to_integer(configuration_map[prefix + "threshold_icmp_pps"]); } // Bandwidth per protocol thresholds if (configuration_map.count(prefix + "threshold_tcp_mbps") != 0) { ban_settings.ban_threshold_tcp_mbps = convert_string_to_integer(configuration_map[prefix + "threshold_tcp_mbps"]); } if (configuration_map.count(prefix + "threshold_udp_mbps") != 0) { ban_settings.ban_threshold_udp_mbps = convert_string_to_integer(configuration_map[prefix + "threshold_udp_mbps"]); } if (configuration_map.count(prefix + "threshold_icmp_mbps") != 0) { ban_settings.ban_threshold_icmp_mbps = convert_string_to_integer(configuration_map[prefix + "threshold_icmp_mbps"]); } if (configuration_map.count(prefix + "threshold_pps") != 0) { ban_settings.ban_threshold_pps = convert_string_to_integer(configuration_map[prefix + "threshold_pps"]); } if (configuration_map.count(prefix + "threshold_mbps") != 0) { ban_settings.ban_threshold_mbps = convert_string_to_integer(configuration_map[prefix + "threshold_mbps"]); } if (configuration_map.count(prefix + "threshold_flows") != 0) { ban_settings.ban_threshold_flows = convert_string_to_integer(configuration_map[prefix + "threshold_flows"]); } return ban_settings; } bool exceed_pps_speed(uint64_t in_counter, uint64_t out_counter, unsigned int threshold) { if (in_counter > threshold or out_counter > threshold) { return true; } else { return false; } } bool exceed_flow_speed(uint64_t in_counter, uint64_t out_counter, unsigned int threshold) { if (in_counter > threshold or out_counter > threshold) { return true; } else { return false; } } bool exceed_mbps_speed(uint64_t in_counter, uint64_t out_counter, unsigned int threshold_mbps) { if (convert_speed_to_mbps(in_counter) > threshold_mbps or convert_speed_to_mbps(out_counter) > threshold_mbps) { return true; } else { return false; } } // Return true when we should ban this IP bool we_should_ban_this_ip(map_element* average_speed_element, ban_settings_t current_ban_settings) { // we detect overspeed by packets bool attack_detected_by_pps = false; bool attack_detected_by_bandwidth = false; bool attack_detected_by_flow = false; if (current_ban_settings.enable_ban_for_pps && exceed_pps_speed(average_speed_element->in_packets, average_speed_element->out_packets, current_ban_settings.ban_threshold_pps)) { logger << log4cpp::Priority::INFO << "We detected this attack by pps limit"; return true; } if (current_ban_settings.enable_ban_for_bandwidth && exceed_mbps_speed(average_speed_element->in_bytes, average_speed_element->out_bytes, current_ban_settings.ban_threshold_mbps)) { logger << log4cpp::Priority::INFO << "We detected this attack by mbps limit"; return true; } if (current_ban_settings.enable_ban_for_flows_per_second && exceed_flow_speed(average_speed_element->in_flows, average_speed_element->out_flows, current_ban_settings.ban_threshold_flows)) { logger << log4cpp::Priority::INFO << "We detected this attack by flow limit"; return true; } // We could try per protocol thresholds here // Per protocol pps thresholds if (current_ban_settings.enable_ban_for_tcp_pps && exceed_pps_speed(average_speed_element->tcp_in_packets, average_speed_element->tcp_out_packets, current_ban_settings.ban_threshold_tcp_pps)) { logger << log4cpp::Priority::INFO << "We detected this attack by tcp pps limit"; return true; } if (current_ban_settings.enable_ban_for_udp_pps && exceed_pps_speed(average_speed_element->udp_in_packets, average_speed_element->udp_out_packets, current_ban_settings.ban_threshold_udp_pps)) { logger << log4cpp::Priority::INFO << "We detected this attack by udp pps limit"; return true; } if (current_ban_settings.enable_ban_for_icmp_pps && exceed_pps_speed(average_speed_element->icmp_in_packets, average_speed_element->icmp_out_packets, current_ban_settings.ban_threshold_icmp_pps)) { logger << log4cpp::Priority::INFO << "We detected this attack by icmp pps limit"; return true; } // Per protocol bandwidth thresholds if (current_ban_settings.enable_ban_for_tcp_bandwidth && exceed_mbps_speed(average_speed_element->tcp_in_bytes, average_speed_element->tcp_out_bytes, current_ban_settings.ban_threshold_tcp_mbps)) { logger << log4cpp::Priority::INFO << "We detected this attack by tcp mbps limit"; return true; } if (current_ban_settings.enable_ban_for_udp_bandwidth && exceed_mbps_speed(average_speed_element->udp_in_bytes, average_speed_element->udp_out_bytes, current_ban_settings.ban_threshold_udp_mbps)) { logger << log4cpp::Priority::INFO << "We detected this attack by udp mbps limit"; return true; } if (current_ban_settings.enable_ban_for_icmp_bandwidth && exceed_mbps_speed(average_speed_element->icmp_in_bytes, average_speed_element->icmp_out_bytes, current_ban_settings.ban_threshold_icmp_mbps)) { logger << log4cpp::Priority::INFO << "We detected this attack by icmp mbps limit"; return true; } return false; } std::string generate_flow_spec_for_amplification_attack(amplification_attack_type_t amplification_attack_type, std::string destination_ip) { exabgp_flow_spec_rule_t exabgp_rule; bgp_flow_spec_action_t my_action; // We drop all traffic by default my_action.set_type(FLOW_SPEC_ACTION_DISCARD); // Assign action to the rule exabgp_rule.set_action( my_action ); // TODO: rewrite! exabgp_rule.set_destination_subnet( convert_subnet_from_string_to_binary_with_cidr_format( destination_ip + "/32") ); // We use only UDP here exabgp_rule.add_protocol(FLOW_SPEC_PROTOCOL_UDP); if (amplification_attack_type == AMPLIFICATION_ATTACK_DNS) { exabgp_rule.add_source_port(53); } else if (amplification_attack_type == AMPLIFICATION_ATTACK_NTP) { exabgp_rule.add_source_port(123); } else if (amplification_attack_type == AMPLIFICATION_ATTACK_SSDP) { exabgp_rule.add_source_port(1900); } else if (amplification_attack_type == AMPLIFICATION_ATTACK_SNMP) { exabgp_rule.add_source_port(161); } else if (amplification_attack_type == AMPLIFICATION_ATTACK_CHARGEN) { exabgp_rule.add_source_port(19); } return exabgp_rule.serialize_single_line_exabgp_v4_configuration(); } std::string get_amplification_attack_type(amplification_attack_type_t attack_type) { if (attack_type == AMPLIFICATION_ATTACK_UNKNOWN) { return "unknown"; } else if (attack_type == AMPLIFICATION_ATTACK_DNS) { return "dns_amplification"; } else if (attack_type == AMPLIFICATION_ATTACK_NTP) { return "ntp_amplification"; } else if (attack_type == AMPLIFICATION_ATTACK_SSDP) { return "ssdp_amplification"; } else if (attack_type == AMPLIFICATION_ATTACK_SNMP) { return "snmp_amplification"; } else if (attack_type == AMPLIFICATION_ATTACK_CHARGEN) { return "chargen_amplification"; } else { return "unexpected"; } } // We calculate speed from packet counters here inline void build_speed_counters_from_packet_counters(map_element& new_speed_element, map_element* vector_itr, double speed_calc_period) { // calculate_speed(new_speed_element speed_element, vector_itr* ); new_speed_element.in_packets = uint64_t((double)vector_itr->in_packets / speed_calc_period); new_speed_element.out_packets = uint64_t((double)vector_itr->out_packets / speed_calc_period); new_speed_element.in_bytes = uint64_t((double)vector_itr->in_bytes / speed_calc_period); new_speed_element.out_bytes = uint64_t((double)vector_itr->out_bytes / speed_calc_period); // Fragmented new_speed_element.fragmented_in_packets = uint64_t((double)vector_itr->fragmented_in_packets / speed_calc_period); new_speed_element.fragmented_out_packets = uint64_t((double)vector_itr->fragmented_out_packets / speed_calc_period); new_speed_element.fragmented_in_bytes = uint64_t((double)vector_itr->fragmented_in_bytes / speed_calc_period); new_speed_element.fragmented_out_bytes = uint64_t((double)vector_itr->fragmented_out_bytes / speed_calc_period); // By protocol counters // TCP new_speed_element.tcp_in_packets = uint64_t((double)vector_itr->tcp_in_packets / speed_calc_period); new_speed_element.tcp_out_packets = uint64_t((double)vector_itr->tcp_out_packets / speed_calc_period); new_speed_element.tcp_in_bytes = uint64_t((double)vector_itr->tcp_in_bytes / speed_calc_period); new_speed_element.tcp_out_bytes = uint64_t((double)vector_itr->tcp_out_bytes / speed_calc_period); // TCP syn new_speed_element.tcp_syn_in_packets = uint64_t((double)vector_itr->tcp_syn_in_packets / speed_calc_period); new_speed_element.tcp_syn_out_packets = uint64_t((double)vector_itr->tcp_syn_out_packets / speed_calc_period); new_speed_element.tcp_syn_in_bytes = uint64_t((double)vector_itr->tcp_syn_in_bytes / speed_calc_period); new_speed_element.tcp_syn_out_bytes = uint64_t((double)vector_itr->tcp_syn_out_bytes / speed_calc_period); // UDP new_speed_element.udp_in_packets = uint64_t((double)vector_itr->udp_in_packets / speed_calc_period); new_speed_element.udp_out_packets = uint64_t((double)vector_itr->udp_out_packets / speed_calc_period); new_speed_element.udp_in_bytes = uint64_t((double)vector_itr->udp_in_bytes / speed_calc_period); new_speed_element.udp_out_bytes = uint64_t((double)vector_itr->udp_out_bytes / speed_calc_period); // ICMP new_speed_element.icmp_in_packets = uint64_t((double)vector_itr->icmp_in_packets / speed_calc_period); new_speed_element.icmp_out_packets = uint64_t((double)vector_itr->icmp_out_packets / speed_calc_period); new_speed_element.icmp_in_bytes = uint64_t((double)vector_itr->icmp_in_bytes / speed_calc_period); new_speed_element.icmp_out_bytes = uint64_t((double)vector_itr->icmp_out_bytes / speed_calc_period); } inline void build_average_speed_counters_from_speed_counters( map_element* current_average_speed_element, map_element& new_speed_element, double exp_value, double exp_power) { // Global bytes counters current_average_speed_element->in_bytes = uint64_t( new_speed_element.in_bytes + exp_value * ((double)current_average_speed_element->in_bytes - (double)new_speed_element.in_bytes)); current_average_speed_element->out_bytes = uint64_t( new_speed_element.out_bytes + exp_value * ((double)current_average_speed_element->out_bytes - (double)new_speed_element.out_bytes)); // Global packet counters current_average_speed_element->in_packets = uint64_t( new_speed_element.in_packets + exp_value * ((double)current_average_speed_element->in_packets - (double)new_speed_element.in_packets)); current_average_speed_element->out_packets = uint64_t( new_speed_element.out_packets + exp_value * ((double)current_average_speed_element->out_packets - (double)new_speed_element.out_packets)); // Per packet type packet counters for in traffic current_average_speed_element->fragmented_in_packets = uint64_t( new_speed_element.fragmented_in_packets + exp_value * ((double)current_average_speed_element->fragmented_in_packets - (double)new_speed_element.fragmented_in_packets)); current_average_speed_element->tcp_in_packets = uint64_t( new_speed_element.tcp_in_packets + exp_value * ((double)current_average_speed_element->tcp_in_packets - (double)new_speed_element.tcp_in_packets)); current_average_speed_element->tcp_syn_in_packets = uint64_t( new_speed_element.tcp_syn_in_packets + exp_value * ((double)current_average_speed_element->tcp_syn_in_packets - (double)new_speed_element.tcp_syn_in_packets)); current_average_speed_element->udp_in_packets = uint64_t( new_speed_element.udp_in_packets + exp_value * ((double)current_average_speed_element->udp_in_packets - (double)new_speed_element.udp_in_packets)); current_average_speed_element->icmp_in_packets = uint64_t( new_speed_element.icmp_in_packets + exp_value * ((double)current_average_speed_element->icmp_in_packets - (double)new_speed_element.icmp_in_packets)); // Per packet type packets counters for out current_average_speed_element->fragmented_out_packets = uint64_t( new_speed_element.fragmented_out_packets + exp_value * ((double)current_average_speed_element->fragmented_out_packets - (double)new_speed_element.fragmented_out_packets)); current_average_speed_element->tcp_out_packets = uint64_t( new_speed_element.tcp_out_packets + exp_value * ((double)current_average_speed_element->tcp_out_packets - (double)new_speed_element.tcp_out_packets)); current_average_speed_element->tcp_syn_out_packets = uint64_t( new_speed_element.tcp_syn_out_packets + exp_value * ((double)current_average_speed_element->tcp_syn_out_packets - (double)new_speed_element.tcp_syn_out_packets)); current_average_speed_element->udp_out_packets = uint64_t( new_speed_element.udp_out_packets + exp_value * ((double)current_average_speed_element->udp_out_packets - (double)new_speed_element.udp_out_packets)); current_average_speed_element->icmp_out_packets = uint64_t( new_speed_element.icmp_out_packets + exp_value * ((double)current_average_speed_element->icmp_out_packets - (double)new_speed_element.icmp_out_packets)); // Per packet type bytes counter for out current_average_speed_element->fragmented_out_bytes = uint64_t( new_speed_element.fragmented_out_bytes + exp_value * ((double)current_average_speed_element->fragmented_out_bytes - (double)new_speed_element.fragmented_out_bytes)); current_average_speed_element->tcp_out_bytes = uint64_t( new_speed_element.tcp_out_bytes + exp_value * ((double)current_average_speed_element->tcp_out_bytes - (double)new_speed_element.tcp_out_bytes)); current_average_speed_element->tcp_syn_out_bytes = uint64_t( new_speed_element.tcp_syn_out_bytes + exp_value * ((double)current_average_speed_element->tcp_syn_out_bytes - (double)new_speed_element.tcp_syn_out_bytes)); current_average_speed_element->udp_out_bytes = uint64_t( new_speed_element.udp_out_bytes + exp_value * ((double)current_average_speed_element->udp_out_bytes - (double)new_speed_element.udp_out_bytes)); current_average_speed_element->icmp_out_bytes = uint64_t( new_speed_element.icmp_out_bytes + exp_value * ((double)current_average_speed_element->icmp_out_bytes - (double)new_speed_element.icmp_out_bytes)); // Per packet type bytes counter for in current_average_speed_element->fragmented_in_bytes = uint64_t( new_speed_element.fragmented_in_bytes + exp_value * ((double)current_average_speed_element->fragmented_in_bytes - (double)new_speed_element.fragmented_in_bytes)); current_average_speed_element->tcp_in_bytes = uint64_t( new_speed_element.tcp_in_bytes + exp_value * ((double)current_average_speed_element->tcp_in_bytes - (double)new_speed_element.tcp_in_bytes)); current_average_speed_element->tcp_syn_in_bytes = uint64_t( new_speed_element.tcp_syn_in_bytes + exp_value * ((double)current_average_speed_element->tcp_syn_in_bytes - (double)new_speed_element.tcp_syn_in_bytes)); current_average_speed_element->udp_in_bytes = uint64_t( new_speed_element.udp_in_bytes + exp_value * ((double)current_average_speed_element->udp_in_bytes - (double)new_speed_element.udp_in_bytes)); current_average_speed_element->icmp_in_bytes = uint64_t( new_speed_element.icmp_in_bytes + exp_value * ((double)current_average_speed_element->icmp_in_bytes - (double)new_speed_element.icmp_in_bytes)); } fastnetmon-1.1.3+dfsg/src/fastnetmon.proto000066400000000000000000000007771313534057500206640ustar00rootroot00000000000000syntax = "proto3"; package fastmitigation; service Fastnetmon { rpc GetBanlist(BanListRequest) returns (stream BanListReply) {} rpc ExecuteBan(ExecuteBanRequest) returns (ExecuteBanReply) {} rpc ExecuteUnBan(ExecuteBanRequest) returns (ExecuteBanReply) {} } // We could not create RPC method without params message BanListRequest { } message BanListReply { string ip_address = 1; } message ExecuteBanRequest { string ip_address = 1; } message ExecuteBanReply { bool result = 1; } fastnetmon-1.1.3+dfsg/src/fastnetmon.service000066400000000000000000000005411313534057500211460ustar00rootroot00000000000000[Unit] Description=FastNetMon - DoS/DDoS analyzer with sflow/netflow/mirror support After=syslog.target network.target remote-fs.target [Service] Type=forking ExecStart=/opt/fastnetmon/fastnetmon --daemonize PIDFile=/run/fastnetmon.pid #ExecReload=/bin/kill -s HUP $MAINPID #ExecStop=/bin/kill -s QUIT $MAINPID [Install] WantedBy=multi-user.target fastnetmon-1.1.3+dfsg/src/fastnetmon_actions.h000066400000000000000000000007121313534057500214550ustar00rootroot00000000000000#include "log4cpp/Category.hh" #include "log4cpp/Appender.hh" #include "log4cpp/FileAppender.hh" #include "log4cpp/OstreamAppender.hh" #include "log4cpp/Layout.hh" #include "log4cpp/BasicLayout.hh" #include "log4cpp/PatternLayout.hh" #include "log4cpp/Priority.hh" #include "fast_library.h" // Get log4cpp logger from main programm extern log4cpp::Category& logger; // Global configuration map extern std::map configuration_map; fastnetmon-1.1.3+dfsg/src/fastnetmon_api_client.cpp000066400000000000000000000101121313534057500224520ustar00rootroot00000000000000#include #include #include #include #include "fastnetmon.grpc.pb.h" using grpc::Channel; using grpc::ClientContext; using grpc::Status; using fastmitigation::BanListRequest; using fastmitigation::BanListReply; using fastmitigation::Fastnetmon; unsigned int client_connection_timeout = 5; class FastnetmonClient { public: FastnetmonClient(std::shared_ptr channel) : stub_(Fastnetmon::NewStub(channel)) {} void ExecuteBan(std::string host, bool is_ban) { ClientContext context; fastmitigation::ExecuteBanRequest request; fastmitigation::ExecuteBanReply reply; request.set_ip_address(host); Status status; if (is_ban) { status = stub_->ExecuteBan(&context, request, &reply); } else { status = stub_->ExecuteUnBan(&context, request, &reply); } if (status.ok()) { } else { if (status.error_code() == grpc::DEADLINE_EXCEEDED) { std::cerr << "Could not connect to API server. Timeout exceed" << std::endl; return; } else { std::cerr << "RPC failed " + status.error_message() << std::endl; return; } } } void GetBanList() { // This request haven't any useful data BanListRequest request; // Container for the data we expect from the server. BanListReply reply; // Context for the client. It could be used to convey extra information to // the server and/or tweak certain RPC behaviors. ClientContext context; // Set timeout for API std::chrono::system_clock::time_point deadline = std::chrono::system_clock::now() + std::chrono::seconds(client_connection_timeout); context.set_deadline(deadline); // The actual RPC. auto announces_list = stub_->GetBanlist(&context, request); while (announces_list->Read(&reply)) { std::cout << reply.ip_address() << std::endl; } // Get status and handle errors auto status = announces_list->Finish(); if (!status.ok()) { if (status.error_code() == grpc::DEADLINE_EXCEEDED) { std::cerr << "Could not connect to API server. Timeout exceed" << std::endl; return; } else { std::cerr << "RPC failed " + status.error_message(); return; } } } private: std::unique_ptr stub_; }; void silent_logging_function(gpr_log_func_args *args) { // We do not want any logging here } int main(int argc, char** argv) { if (argc <= 1) { std::cerr << "Please provide request" << std::endl; return 1; } gpr_set_log_function(silent_logging_function); // Instantiate the client. It requires a channel, out of which the actual RPCs // are created. This channel models a connection to an endpoint (in this case, // localhost at port 50051). We indicate that the channel isn't authenticated // (use of InsecureCredentials()). FastnetmonClient fastnetmon( grpc::CreateChannel("localhost:50052", grpc::InsecureCredentials())); std::string request_command = argv[1]; if (request_command == "get_banlist") { fastnetmon.GetBanList(); } else if (request_command == "ban" or request_command == "unban") { if (argc < 2) { std::cerr << "Please provide IP for action" << std::endl; return(1); } std::string ip_for_ban = argv[2]; if (request_command == "ban") { fastnetmon.ExecuteBan(ip_for_ban, true); } else { fastnetmon.ExecuteBan(ip_for_ban, false); } } else { std::cerr << "Unknown command" << std::endl; } return 0; } fastnetmon-1.1.3+dfsg/src/fastnetmon_centos6.spec000066400000000000000000000072701313534057500221070ustar00rootroot00000000000000# # Pre/post params: https://fedoraproject.org/wiki/Packaging:ScriptletSnippets # %global fastnetmon_attackdir %{_localstatedir}/log/fastnetmon_attacks %global fastnetmon_user root %global fastnetmon_group %{fastnetmon_user} %global fastnetmon_config_path %{_sysconfdir}/fastnetmon.conf Name: fastnetmon Version: 1.1.1 Release: 1%{?dist} Summary: A high performance DoS/DDoS load analyzer built on top of multiple packet capture engines (NetFlow, IPFIX, sFLOW, netmap, PF_RING, PCAP). Group: System Environment/Daemons License: GPLv2 URL: https://github.com/FastVPSEestiOu/fastnetmon # Top level fodler inside archive should be named as "fastnetmon-1.1.1" #Source0: https://github.com/FastVPSEestiOu/fastnetmon/archive/v%{version}.tar.gz Source0: https://github.com/FastVPSEestiOu/fastnetmon/archive/fastnetmon-%{version}.tar.gz # Yes, it's bad idea to specify fixed version of PF_RING but they have strange issue when we use another library version BuildRequires: git, make, gcc, gcc-c++, boost-devel, GeoIP-devel, log4cpp-devel BuildRequires: ncurses-devel, boost-thread, boost-regex, libpcap-devel, gpm-devel, clang, cmake BuildRequires: pfring >= 6.0.3-9154 Requires: pfring >= 6.0.3-9154 Requires: log4cpp, daemonize, libpcap, boost-thread, boost-thread, boost-regex Requires(pre): shadow-utils Requires(post): chkconfig Requires(preun): chkconfig, initscripts Requires(postun): initscripts Provides: fastnetmon %description A high performance DoS/DDoS load analyzer built on top of multiple packet capture engines (NetFlow, IPFIX, sFLOW, netmap, PF_RING, PCAP). %prep # For production # %setup -n fastnetmon-%{version} # Testing # Specify name of folder inside rpm package %setup -n fastnetmon-master %build cd src mkdir build cd build # You could disable PF_RING support with param: -DDISABLE_PF_RING_SUPPORT=ON # WE have broken cmake library with Boost on CentOS 6 and should use library from cmake # http://public.kitware.com/Bug/view.php?id=15270 cmake .. -DWE_USE_PFRING_FROM_NTOP=ON -DBoost_NO_BOOST_CMAKE=BOOL:ON make %install # install init script install -p -D -m 0755 src/fastnetmon_init_script_centos6 %{buildroot}%{_initrddir}/fastnetmon # install daemon binary file install -p -D -m 0755 src/build/fastnetmon %{buildroot}%{_sbindir}/fastnetmon # install client binary file install -p -D -m 0755 src/build/fastnetmon_client %{buildroot}%{_bindir}/fastnetmon_client # install config install -p -D -m 0755 src/fastnetmon.conf %{buildroot}%{fastnetmon_config_path} # Create log folder install -p -d -m 0700 %{buildroot}%{fastnetmon_attackdir} %pre exit 0 %post if [ $1 -eq 1 ]; then # It's install /sbin/chkconfig --add %{name} /sbin/chkconfig %{name} on /sbin/service %{name} start # Fix pfring issue with library path echo "/usr/local/lib" > /etc/ld.so.conf.d/pfring.conf /sbin/ldconfig fi if [ $1 -eq 2 ]; then # upgrade #/sbin/service %{name} restart >/dev/null 2>&1 chmod 700 %{fastnetmon_attackdir} fi %preun # Pre remove if [ $1 -eq 0 ]; then # Uninstall # Stops fastnetmon and disable it loading at startup /sbin/service %{name} stop >/dev/null 2>&1 /sbin/chkconfig --del %{name} fi %postun # Post remove %files #%doc LICENSE CHANGES README # init.d script %{_initrddir}/fastnetmon # binary daemon %{_sbindir}/fastnetmon %{_bindir}/fastnetmon_client %config(noreplace) %{_sysconfdir}/fastnetmon.conf %attr(700,%{fastnetmon_user},%{fastnetmon_group}) %dir %{fastnetmon_attackdir} %changelog * Mon Mar 23 2015 Pavel Odintsov - 1.1.1-1 - First RPM package release fastnetmon-1.1.3+dfsg/src/fastnetmon_centos7.spec000066400000000000000000000067361313534057500221160ustar00rootroot00000000000000# # Pre/post params: https://fedoraproject.org/wiki/Packaging:ScriptletSnippets # %global fastnetmon_attackdir %{_localstatedir}/log/fastnetmon_attacks %global fastnetmon_user root %global fastnetmon_group %{fastnetmon_user} %global fastnetmon_config_path %{_sysconfdir}/fastnetmon.conf Name: fastnetmon Version: 1.1.1 Release: 1%{?dist} Summary: A high performance DoS/DDoS load analyzer built on top of multiple packet capture engines (NetFlow, IPFIX, sFLOW, netmap, PF_RING, PCAP). Group: System Environment/Daemons License: GPLv2 URL: https://github.com/FastVPSEestiOu/fastnetmon # Top level fodler inside archive should be named as "fastnetmon-1.1.1" #Source0: https://github.com/FastVPSEestiOu/fastnetmon/archive/v%{version}.tar.gz Source0: https://github.com/FastVPSEestiOu/fastnetmon/archive/fastnetmon-%{version}.tar.gz # Yes, it's bad idea to specify fixed version of PF_RING but they have strange issue when we use another library version BuildRequires: git, make, gcc, gcc-c++, boost-devel, GeoIP-devel, log4cpp-devel BuildRequires: ncurses-devel, boost-thread, boost-regex, libpcap-devel, gpm-devel, clang, cmake BuildRequires: pfring >= 6.0.3-9154 BuildRequires: systemd Requires: pfring >= 6.0.3-9154 Requires: log4cpp, libpcap, boost-thread, boost-thread, boost-regex Requires(pre): shadow-utils Requires(post): systemd Requires(preun): systemd Requires(postun): systemd Provides: fastnetmon %description A high performance DoS/DDoS load analyzer built on top of multiple packet capture engines (NetFlow, IPFIX, sFLOW, netmap, PF_RING, PCAP). %prep # For production # %setup -n fastnetmon-%{version} # Testing # Specify name of folder inside rpm package %setup -n fastnetmon-master %build cd src mkdir build cd build # You could disable PF_RING support with param: -DDISABLE_PF_RING_SUPPORT=ON cmake .. -DWE_USE_PFRING_FROM_NTOP=ON make %install # install init script install -p -D -m 0755 src/fastnetmon.service %{buildroot}%{_sysconfdir}/systemd/system/fastnetmon.service # install daemon binary file install -p -D -m 0755 src/build/fastnetmon %{buildroot}%{_sbindir}/fastnetmon # install client binary file install -p -D -m 0755 src/build/fastnetmon_client %{buildroot}%{_bindir}/fastnetmon_client # install config install -p -D -m 0755 src/fastnetmon.conf %{buildroot}%{fastnetmon_config_path} # Create log folder install -p -d -m 0700 %{buildroot}%{fastnetmon_attackdir} %pre exit 0 %post %systemd_post fastnetmon.service if [ $1 -eq 1 ]; then # It's install # Enable autostart /usr/bin/systemctl enable fastnetmon.service /usr/bin/systemctl start fastnetmon.service # Fix pfring issue with library path echo "/usr/local/lib" > /etc/ld.so.conf.d/pfring.conf /sbin/ldconfig fi if [ $1 -eq 2 ]; then # upgrade chmod 700 %{fastnetmon_attackdir} fi %preun %systemd_preun fastnetmon.service # Pre remove #if [ $1 -eq 0 ]; then # Uninstall #fi %postun %systemd_postun_with_restart fastnetmon.service %files #%doc LICENSE CHANGES README # init.d script %{_sysconfdir}/systemd/system # binary daemon %{_sbindir}/fastnetmon %{_bindir}/fastnetmon_client %config(noreplace) %{_sysconfdir}/fastnetmon.conf %attr(700,%{fastnetmon_user},%{fastnetmon_group}) %dir %{fastnetmon_attackdir} %changelog * Mon Mar 23 2015 Pavel Odintsov - 1.1.1-1 - First RPM package release fastnetmon-1.1.3+dfsg/src/fastnetmon_client.cpp000066400000000000000000000025541313534057500216340ustar00rootroot00000000000000#include #include #include #include #include #include #include #include std::string cli_stats_file_path = "/tmp/fastnetmon.dat"; int main() { // Init ncurses screen initscr(); // disable any character output noecho(); // hide cursor curs_set(0); // Do not wait for getch timeout(0); while (true) { sleep(1); // clean up screen clear(); int c = getch(); if (c == 'q') { endwin(); exit(0); } char* cli_stats_file_path_env = getenv("cli_stats_file_path"); if (cli_stats_file_path_env != NULL) { cli_stats_file_path = std::string(cli_stats_file_path_env); } std::ifstream reading_file; reading_file.open(cli_stats_file_path.c_str(), std::ifstream::in); if (!reading_file.is_open()) { std::cout << "Can't open fastnetmon stats file: " << cli_stats_file_path; } std::string line = ""; std::stringstream screen_buffer; while (getline(reading_file, line)) { screen_buffer << line << "\n"; } reading_file.close(); printw(screen_buffer.str().c_str()); // update screen refresh(); } /* End ncurses mode */ endwin(); } fastnetmon-1.1.3+dfsg/src/fastnetmon_fedora12.spec000066400000000000000000000055521313534057500221320ustar00rootroot00000000000000%global fastnetmon_attackdir %{_localstatedir}/log/fastnetmon_attacks %global fastnetmon_user root %global fastnetmon_group %{fastnetmon_user} %global fastnetmon_config_path %{_sysconfdir}/fastnetmon.conf %global fastnetmon_commit 86b951b6dffae0fc1e6cbf66fe5f0f4aa61aaa5a %global fastnetmon_project_name fastnetmon %global fastnetmon_company FastVPSEestiOu Name: fastnetmon Version: 1.1.1 Release: 1%{?dist} Summary: A high performance DoS/DDoS load analyzer built on top of multiple packet capture engines (NetFlow, IPFIX, sFLOW, netmap, PF_RING, PCAP). Group: System Environment/Daemons License: GPLv2 URL: https://github.com/FastVPSEestiOu/fastnetmon Source0: https://github.com/%{fastnetmon_company}/%{name}/archive/%{fastnetmon_commit}/%{name}-%{fastnetmon_commit}.tar.gz BuildRequires: git, make, gcc, gcc-c++, boost-devel, GeoIP-devel, log4cpp-devel BuildRequires: ncurses-devel, boost-thread, boost-regex, libpcap-devel, gpm-devel, clang, cmake BuildRequires: systemd Requires: log4cpp, libpcap, boost-thread, boost-thread, boost-regex Requires(pre): shadow-utils Requires(post): systemd Requires(preun): systemd Requires(postun): systemd Provides: fastnetmon %description A high performance DoS/DDoS load analyzer built on top of multiple packet capture engines (NetFlow, IPFIX, sFLOW, netmap, PF_RING, PCAP). %prep %setup -n %{name}-%{fastnetmon_commit} %build cd src mkdir build cd build # We should disable PF_RING plugon support because we did not have it in repository cmake .. -DDISABLE_PF_RING_SUPPORT=ON make %install # install init script install -p -D -m 0755 src/fastnetmon.service %{buildroot}%{_sysconfdir}/systemd/system/fastnetmon.service # install daemon binary file install -p -D -m 0755 src/build/fastnetmon %{buildroot}%{_sbindir}/fastnetmon # install client binary file install -p -D -m 0755 src/build/fastnetmon_client %{buildroot}%{_bindir}/fastnetmon_client # install config install -p -D -m 0755 src/fastnetmon.conf %{buildroot}%{fastnetmon_config_path} # Create log folder install -p -d -m 0700 %{buildroot}%{fastnetmon_attackdir} %pre exit 0 %post %systemd_post fastnetmon.service if [ $1 -eq 2 ]; then # upgrade chmod 700 %{fastnetmon_attackdir} fi %preun %systemd_preun fastnetmon.service # Pre remove #if [ $1 -eq 0 ]; then # Uninstall #fi %postun %systemd_postun_with_restart fastnetmon.service %files #%doc LICENSE CHANGES README # init script %{_sysconfdir}/systemd/system # binary daemon %{_sbindir}/fastnetmon %{_bindir}/fastnetmon_client %config(noreplace) %{_sysconfdir}/fastnetmon.conf %attr(700,%{fastnetmon_user},%{fastnetmon_group}) %dir %{fastnetmon_attackdir} %changelog * Mon Mar 23 2015 Pavel Odintsov - 1.1.1-1 - First RPM package release fastnetmon-1.1.3+dfsg/src/fastnetmon_init_script_centos6000077500000000000000000000032151313534057500235630ustar00rootroot00000000000000#!/bin/bash # # fastnetmon Startup script for FastNetMon # # chkconfig: - 85 15 # description: FastNetMon - high performance DoS/DDoS analyzer with sflow/netflow/mirror support # processname: fastnemon # config: /etc/fastnetmon.conf # pidfile: /var/run/fastnetmon.pid # ### BEGIN INIT INFO # Provides: fastnetmon # Required-Start: $local_fs $remote_fs $network # Required-Stop: $local_fs $remote_fs $network # Should-Start: # Short-Description: start and stop FastNetMon # Description: high performance DoS/DDoS analyzer with sflow/netflow/mirror support ### END INIT INFO # Source function library. . /etc/rc.d/init.d/functions # We do not use this configs #if [ -f /etc/sysconfig/fastnetmon ]; then # . /etc/sysconfig/fastnetmon #fi FASTNETMON=/usr/sbin/fastnetmon PROGNAME="fastnetmon" PIDFILE=/var/run/fastnetmon.pid RETVAL=0 ARGS="--daemonize" start() { echo -n $"Starting $PROGNAME: " $FASTNETMON $ARGS > /dev/null 2>&1 && echo_success || echo_failure RETVAL=$? echo "" return $RETVAL } stop() { echo -n $"Stopping $PROGNAME: " killproc -p $PIDFILE $FASTNETMON RETVAL=$? echo "" rm -f $PIDFILE } reload() { echo "Reloading is not supported now, sorry" #echo -n $"Reloading $PROGNAME: " #kill -HUP `cat $PIDFILE` } # See how we were called. case "$1" in start) start ;; stop) stop ;; status) status -p ${PIDFILE} $PROGNAME RETVAL=$? ;; restart) stop sleep 1 start ;; reload) reload ;; *) echo $"Usage: $prog {start|stop|restart|reload|status}" RETVAL=2 esac exit $RETVAL fastnetmon-1.1.3+dfsg/src/fastnetmon_init_script_debian_6_7000077500000000000000000000024631313534057500241030ustar00rootroot00000000000000#!/bin/sh ### BEGIN INIT INFO # Provides: fastnetmon # Required-Start: $local_fs $remote_fs $network $syslog # Required-Stop: $local_fs $remote_fs $network $syslog # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Short-Description: Fast DDoS detection toolkit. # Description: Fast DDoS detection toolkit with sFLOW/Netflow/netmap/pf_ring support. ### END INIT INFO # test -r /etc/default/fastnetmon && . /etc/default/fastnetmon NAME="fastnetmon" . /lib/lsb/init-functions PIDFILE="/var/run/${NAME}.pid" DAEMON="/usr/sbin/fastnetmon" DAEMON_OPTS="--daemonize" START_OPTS="--start --background --exec ${DAEMON} -- ${DAEMON_OPTS}" STOP_OPTS="--stop --pidfile ${PIDFILE}" STATUS_OPTS="--status --pidfile ${PIDFILE}" case "$1" in start) echo -n "Starting $NAME: " start-stop-daemon $START_OPTS echo "$NAME." ;; stop) echo -n "Stopping $NAME: " start-stop-daemon $STOP_OPTS rm -f $PIDFILE echo "$NAME." ;; restart) $0 stop sleep 2 $0 start ;; force-reload) $0 restart ;; # no support of status on Debian squeeze # status) # start-stop-daemon $STATUS_OPTS # ;; *) N=/etc/init.d/$NAME echo "Usage: $N {start|stop|restart}" >&2 exit 1 ;; esac exit 0 fastnetmon-1.1.3+dfsg/src/fastnetmon_init_script_gentoo000077500000000000000000000012571313534057500235010ustar00rootroot00000000000000#!/sbin/runscript FASTNETMON="/opt/fastnetmon/fastnetmon" PIDFILE="/var/run/fastnetmon.pid" ARGS="--daemonize" depend() { need net } start() { ebegin "Starting fastnetmon" if [ -f "${PIDFILE}" ]; then einfo " Removing stale pidfile ${PIDFILE}" rm -f "${PIDFILE}" 1>/dev/null fi $FASTNETMON $ARGS eend $? } stop() { ebegin "Stopping fastnetmon" if [ -f $PIDFILE ]; then kill -9 $(cat $PIDFILE) 2>/dev/null RETVAL=$? fi if [ -n $RETVAL ] && [ "$RETVAL" -ne "0" ]; then ACTUAL_PID=$(pidof fastnetmon) [ -n $ACTUAL_PID ] && kill -9 $ACTUAL_PID 2>/dev/null RETVAL=$? fi rm -f "${PIDFILE}" eend $RETVAL }fastnetmon-1.1.3+dfsg/src/fastnetmon_install.pl000077500000000000000000001454311313534057500216620ustar00rootroot00000000000000#!/usr/bin/perl use strict; use warnings; use Getopt::Long; my $pf_ring_version = '6.0.3'; my $pf_ring_url = "https://github.com/ntop/PF_RING/archive/v$pf_ring_version.tar.gz"; my $fastnetmon_git_path = 'https://github.com/pavel-odintsov/fastnetmon.git'; my $temp_folder_for_building_project = `mktemp -d /tmp/fastnetmon.build.dir.XXXXXXXXXX`; chomp $temp_folder_for_building_project; unless ($temp_folder_for_building_project && -e $temp_folder_for_building_project) { die "Can't create temp folder in /tmp for building project: $temp_folder_for_building_project\n"; } my $start_time = time(); my $fastnetmon_code_dir = "$temp_folder_for_building_project/fastnetmon/src"; my $install_log_path = '/tmp/fastnetmon_install.log'; # Official mirror: https://github.com/ntop/nDPI.git # But we have some patches for NTP and DNS protocols here my $ndpi_repository = 'https://github.com/pavel-odintsov/nDPI.git'; my $stable_branch_name = 'v1.1.2'; my $we_use_code_from_master = ''; my $os_type = ''; my $distro_type = ''; my $distro_version = ''; my $distro_architecture = ''; my $user_email = ''; # Used for VyOS and different appliances based on rpm/deb my $appliance_name = ''; # So, you could disable this option but without this feature we could not improve FastNetMon for your distribution my $do_not_track_me = ''; my $cpus_number = 1; # We could pass options to make with this variable my $make_options = ''; # We could pass options to configure with this variable my $configure_options = ''; welcome_message(); # We will build gcc, stdc++ and boost for this distribution from sources my $build_binary_environment = ''; # With this option we could build full binary package my $create_binary_bundle = ''; # Get options from command line GetOptions( 'use-git-master' => \$we_use_code_from_master, 'do-not-track-me' => \$do_not_track_me, 'build-binary-environment' => \$build_binary_environment, 'create-binary-bundle' => \$create_binary_bundle, ); my $we_have_ndpi_support = ''; my $we_have_luajit_support = ''; my $we_have_hiredis_support = ''; my $we_have_log4cpp_support = ''; my $we_have_pfring_support = ''; my $we_have_mongo_support = ''; my $we_have_protobuf_support = ''; my $we_have_grpc_support = ''; my $we_have_golang_support = ''; my $we_have_gobgp_support = ''; if ($we_use_code_from_master) { $we_have_ndpi_support = 1; $we_have_luajit_support = 1; $we_have_hiredis_support = 1; $we_have_log4cpp_support = 1; $we_have_mongo_support = 1; } my $enable_gobgp_backend = ''; if ($enable_gobgp_backend) { $we_have_protobuf_support = 1; $we_have_grpc_support = 1; $we_have_golang_support = 1; $we_have_gobgp_support = 1; } main(); sub welcome_message { print "Hello, my dear Customer!\n\n"; print "We need about ten minutes of your time for installing FastNetMon toolkit\n"; print "You could make coffee/tee or you will help project and fill this short survey: http://bit.ly/fastnetmon_survey\n"; print "I would be very glad if you spent this time and shared your DDoS experience :)\n\n"; } sub get_logical_cpus_number { if ($os_type eq 'linux') { my @cpuinfo = `cat /proc/cpuinfo`; chomp @cpuinfo; my $cpus_number = scalar grep {/processor/} @cpuinfo; return $cpus_number; } elsif ($os_type eq 'macosx' or $os_type eq 'freebsd') { my $cpus_number = `sysctl -n hw.ncpu`; chomp $cpus_number; } } sub install_additional_repositories { if ($distro_type eq 'centos') { if ($distro_version == 6) { print "Install EPEL repository for your system\n"; yum('https://dl.fedoraproject.org/pub/epel/epel-release-latest-6.noarch.rpm'); } if ($distro_version == 7) { print "Install EPEL repository for your system\n"; yum('https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm'); } } } sub get_user_email { # http://docs.travis-ci.com/user/environment-variables/#Default-Environment-Variables if (defined($ENV{'TRAVIS'}) && $ENV{'TRAVIS'}) { return; } my $user_entered_valid_email = 0; do { print "\nPlease provide your email address at company domain for free tool activation.\nWe will not share your email with any third party companies.\nEmail: "; my $raw_email = ; chomp $raw_email; if ($raw_email =~ /\@/ && length $raw_email > 3) { $user_entered_valid_email = 1; $user_email = $raw_email; } else { print "Sorry you have entered invalid email, please try again!\n"; } } while !$user_entered_valid_email; print "\nThank you so much!\n\n"; } ### Functions start here sub main { detect_distribution(); get_user_email(); $cpus_number = get_logical_cpus_number(); # We could get huge speed benefits with this option if ($cpus_number > 1) { print "You have really nice server with $cpus_number CPU's and we will use they all for build process :)\n"; $make_options = "-j $cpus_number"; } # We have PF_RING only for Linux if ($os_type eq 'linux') { $we_have_pfring_support = 1; } if ($os_type eq 'macosx') { # Really strange issue https://github.com/FastVPSEestiOu/fastnetmon/issues/415 $we_have_hiredis_support = 0; } # CentOS base repository is very very poor and we need EPEL for some dependencies install_additional_repositories(); # Refresh information about packages init_package_manager(); if ($os_type eq 'freebsd') { exec_command("pkg install -y wget"); } send_tracking_information('started'); if ($build_binary_environment) { install_gcc(); install_boost_builder(); install_boost(); } if ($we_have_pfring_support) { install_pf_ring(); } if ($we_use_code_from_master) { install_json_c(); } if ($we_have_ndpi_support) { install_ndpi(); } if ($we_have_luajit_support) { install_luajit(); install_luajit_libs(); } if ($we_have_hiredis_support) { install_hiredis(); } if ($we_have_mongo_support) { install_mongo_client(); } if ($we_have_protobuf_support) { install_protobuf(); } if ($we_have_grpc_support) { install_grpc(); } if ($we_have_golang_support) { install_golang(); } if ($we_have_gobgp_support) { install_gobgp(); } if ($we_have_log4cpp_support) { install_log4cpp(); } install_fastnetmon(); send_tracking_information('finished'); if ($create_binary_bundle) { create_binary_bundle(); } my $install_time = time() - $start_time; my $pretty_install_time_in_minutes = sprintf("%.2f", $install_time / 60); print "We have built project in $pretty_install_time_in_minutes minutes\n"; } sub create_binary_bundle { chdir $temp_folder_for_building_project; chdir "fastnetmon"; my $bundle_version = ''; if ($we_use_code_from_master) { my $git_last_commit_id = `git log --format="%H" -n 1`; chomp $git_last_commit_id; $bundle_version = "git-$git_last_commit_id"; } else { $bundle_version = $stable_branch_name; } my $bundle_file_name = "fastnetmon-binary-$bundle_version-$distro_type-$distro_version-$distro_architecture.tar.gz"; my $full_bundle_path = "/tmp/$bundle_file_name"; print "We will create bundle with name $bundle_file_name\n"; exec_command("$temp_folder_for_building_project/fastnetmon/src/scripts/build_libary_bundle.pl $full_bundle_path"); print "You could download bundle here $full_bundle_path\n"; if ($distro_type eq 'ubuntu' or $distro_type eq 'debian') { print "We could build .deb packages for this OS\n"; exec_command("perl $temp_folder_for_building_project/fastnetmon/src/scripts/build_any_package.pl deb $full_bundle_path"); } if ($distro_type eq 'centos') { print "We could build .rpm packages for this OS\n"; exec_command("perl $temp_folder_for_building_project/fastnetmon/src/scripts/build_any_package.pl rpm $full_bundle_path"); } } sub send_tracking_information { my $step = shift; unless ($do_not_track_me) { my $stats_url = "http://178.62.227.110/new_fastnetmon_installation"; my $post_data = "distro_type=$distro_type&os_type=$os_type&distro_version=$distro_version&distro_architecture=$distro_architecture&step=$step&we_use_code_from_master=$we_use_code_from_master&user_email=$user_email"; my $user_agent = 'FastNetMon install tracker v1'; `wget --post-data="$post_data" --user-agent="$user_agent" -q '$stats_url'`; } } sub exec_command { my $command = shift; open my $fl, ">>", $install_log_path; print {$fl} "We are calling command: $command\n\n"; my $output = `$command 2>&1 >> $install_log_path`; print {$fl} "Command finished with code $?\n\n"; if ($? == 0) { return 1; } else { return ''; } } sub get_sha1_sum { my $path = shift; if ($os_type eq 'freebsd') { # # We should not use 'use' here because we haven't this package on non FreeBSD systems by default require Digest::SHA; # SHA1 my $sha = Digest::SHA->new(1); $sha->addfile($path); return $sha->hexdigest; } my $hasher_name = ''; if ($os_type eq 'macosx') { $hasher_name = 'shasum'; } elsif ($os_type eq 'freebsd') { $hasher_name = 'sha1'; } else { # Linux $hasher_name = 'sha1sum'; } my $output = `$hasher_name $path`; chomp $output; my ($sha1) = ($output =~ m/^(\w+)\s+/); return $sha1; } sub download_file { my ($url, $path, $expected_sha1_checksumm) = @_; `wget --no-check-certificate --quiet '$url' -O$path`; if ($? != 0) { print "We can't download archive $url correctly\n"; return ''; } if ($expected_sha1_checksumm) { my $calculated_checksumm = get_sha1_sum($path); if ($calculated_checksumm eq $expected_sha1_checksumm) { return 1; } else { print "Downloaded archive has incorrect sha1: $calculated_checksumm expected: $expected_sha1_checksumm\n"; return ''; } } else { return 1; } } sub install_binary_gcc { my $binary_repository_path = 'http://213.133.111.200/fastnetmon_gcc_toolchain'; my $package_distro_version = ''; if ($distro_type eq 'debian') { # Debian 6: 6.0.10 # Debian 7: 7.8 # Debian 8: 8.1 if ($distro_version =~ m/^(6)/) { $package_distro_version = $1; } else { $package_distro_version = int($distro_version); } } elsif ($distro_type eq 'ubuntu') { $package_distro_version = $distro_version; } elsif ($distro_type eq 'centos') { $package_distro_version = $distro_version; } chdir $temp_folder_for_building_project; my $distribution_file_name = "gcc-5.2.0-$distro_type-$package_distro_version-$distro_architecture.tar.gz"; my $full_path = "$binary_repository_path/$distribution_file_name"; print "We will try to download prebuilded binary gcc package for your distribution\n"; print "We will download from $full_path\n"; my $gcc_binary_download_result = download_file($full_path, $distribution_file_name); unless ($gcc_binary_download_result) { print "Download failed, skip to source compilation\n"; return ''; } print "Unpack gcc binary package\n"; # Unpack file to opt exec_command("tar -xf $distribution_file_name -C /opt"); # Remove archive unlink($distribution_file_name); return 1; } sub install_gcc { my $result = install_binary_gcc(); # Add new compiler to configure options # It's mandatory for log4cpp $configure_options = "CC=/opt/gcc520/bin/gcc CXX=/opt/gcc520/bin/g++"; # More detailes about jam lookup: http://www.boost.org/build/doc/html/bbv2/overview/configuration.html # We use non standard gcc compiler for Boost builder and Boost and specify it this way open my $fl, ">", "/root/user-config.jam" or die "Can't open $! file for writing manifest\n"; print {$fl} "using gcc : 5.2 : /opt/gcc520/bin/g++ ;\n"; close $fl; # When we run it with vzctl exec we ahve broken env and should put config in /etc too open my $etcfl, ">", "/etc/user-config.jam" or die "Can't open $! file for writing manifest\n"; print {$etcfl} "using gcc : 5.2 : /opt/gcc520/bin/g++ ;\n"; close $etcfl; # Install gcc from sources if ($distro_type eq 'debian') { my @dependency_list = ('libmpfr-dev', 'libmpc-dev'); if ($distro_version <= 7) { # We have another name for Debian 6 Squeeze push @dependency_list, 'libgmp3-dev'; } else { push @dependency_list, 'libgmp-dev'; } apt_get(@dependency_list); } elsif ($distro_type eq 'ubuntu') { my @dependency_list = ('libmpfr-dev', 'libmpc-dev', 'libgmp-dev'); apt_get(@dependency_list); } elsif ($distro_type eq 'centos') { if ($distro_version == 6) { # We haven't libmpc in base repository here and should enable EPEL yum('https://dl.fedoraproject.org/pub/epel/epel-release-latest-6.noarch.rpm'); } my @dependency_list = ('gmp-devel', 'mpfr-devel', 'libmpc-devel'); yum(@dependency_list); } # Please be careful! This libs required for binary version of gcc! We should install they! # Do not call source compilation in this case if ($result) { return; } print "Download gcc archive\n"; chdir $temp_folder_for_building_project; my $archive_file_name = 'gcc-5.2.0.tar.gz'; my $gcc_download_result = download_file("ftp://ftp.mpi-sb.mpg.de/pub/gnu/mirror/gcc.gnu.org/pub/gcc/releases/gcc-5.2.0/$archive_file_name", $archive_file_name, '713211883406b3839bdba4a22e7111a0cff5d09b'); unless ($gcc_download_result) { die "Can't download gcc sources\n"; } print "Unpack archive\n"; exec_command("tar -xf $archive_file_name"); exec_command("mkdir $temp_folder_for_building_project/gcc-5.2.0-objdir"); chdir "$temp_folder_for_building_project/gcc-5.2.0-objdir"; print "Configure build system\n"; exec_command("$temp_folder_for_building_project/gcc-5.2.0/configure --prefix=/opt/gcc520 --enable-languages=c,c++ --disable-multilib"); print "Build gcc\n"; exec_command("make $make_options"); exec_command("make $make_options install"); # We do not add it to ld.so.conf.d path because it could broke system } sub install_boost { chdir '/opt'; my $archive_file_name = 'boost_1_58_0.tar.gz'; print "Install Boost dependencies\n"; # libicu dependencies if ($distro_type eq 'ubuntu') { if ($distro_version eq '14.04') { apt_get('libicu52'); } if ($distro_version eq '12.04') { apt_get('libicu48'); } } print "Download Boost source code\n"; my $boost_download_result = download_file("http://downloads.sourceforge.net/project/boost/boost/1.58.0/boost_1_58_0.tar.gz?r=http%3A%2F%2Fwww.boost.org%2Fusers%2Fhistory%2Fversion_1_58_0.html&ts=1439207367&use_mirror=cznic", $archive_file_name, 'a27b010b9d5de0c07df9dddc9c336767725b1e6b'); unless ($boost_download_result) { die "Can't download Boost source code\n"; } print "Unpack Boost source code\n"; exec_command("tar -xf $archive_file_name"); # Remove archive unlink "$archive_file_name"; chdir "boost_1_58_0"; print "Build Boost\n"; # We have troubles when run this code with vzctl exec so we should add custom compiler in path # So without HOME=/root nothing worked correctly due to another "openvz" feature my $b2_build_result = exec_command("HOME=/root PATH=\$PATH:/opt/gcc520/bin /opt/boost_build1.5.8/bin/b2 -j$cpus_number --build-dir=/tmp/boost_build_temp_directory_1_5_8 toolset=gcc-5.2 link=shared --without-test --without-python --without-wave --without-graph --without-coroutine --without-math --without-log --without-graph_parallel --without-mpi"); # We should not do this check because b2 build return bad return code even in success case... when it can't build few non important targets unless ($b2_build_result) { ### die "Can't execute b2 build correctly\n"; } } sub install_boost_builder { chdir $temp_folder_for_building_project; # We need libc headers for compilation of this code if ($distro_type eq 'centos') { yum('glibc-devel'); } # We use another name because it uses same name as boost distribution my $archive_file_name = 'boost-builder-1.58.0.tar.gz'; print "Download boost builder\n"; my $boost_build_result = download_file("https://github.com/boostorg/build/archive/boost-1.58.0.tar.gz", $archive_file_name, 'e86375ed83ed07a79a33c76e80e8648d969b3218'); unless ($boost_build_result) { die "Can't download boost builder\n"; } print "Unpack boost builder\n"; exec_command("tar -xf $archive_file_name"); chdir "build-boost-1.58.0"; print "Build Boost builder\n"; # We haven't system compiler here and we will use custom gcc for compilation here my $bootstrap_result = exec_command("CC=/opt/gcc520/bin/gcc CXX=/opt/gcc520/bin/g++ ./bootstrap.sh --with-toolset=cc"); unless ($bootstrap_result) { die "bootstrap of Boost Builder failed, please check logs\n"; } # We should specify toolset here if we want to do build with custom compiler # We have troubles when run this code with vzctl exec so we should add custom compiler in path my $b2_install_result = exec_command("PATH=\$PATH:/opt/gcc520/bin ./b2 install --prefix=/opt/boost_build1.5.8 toolset=gcc"); unless ($b2_install_result) { die "Can't execute b2 install\n"; } } sub install_luajit { chdir $temp_folder_for_building_project; my $archive_file_name = "LuaJIT-2.0.4.tar.gz"; print "Download Luajit\n"; my $luajit_download_result = download_file( "http://luajit.org/download/$archive_file_name", $archive_file_name, '6e533675180300e85d12c4bbeea2d0e41ad21172' ); unless ($luajit_download_result) { die "Can't download luajit\n"; } print "Unpack Luajit\n"; exec_command("tar -xf LuaJIT-2.0.4.tar.gz"); chdir "LuaJIT-2.0.4"; if ($os_type eq 'macosx' or $os_type eq 'freebsd') { # FreeBSD's sed has slightly different syntax exec_command("sed -i -e 's#export PREFIX= /usr/local#export PREFIX= /opt/luajit_2.0.4#' Makefile"); } else { # Standard Linux sed exec_command("sed -i 's#export PREFIX= /usr/local#export PREFIX= /opt/luajit_2.0.4#' Makefile"); } print "Build and install Luajit\n"; if ($os_type eq 'freebsd') { exec_command("pkg install -y gcc gmake"); exec_command('gmake CC=gcc48 CXX=g++48 CPP="gcc48 -E" install') } else { exec_command("make $make_options install"); } put_library_path_to_ld_so("/etc/ld.so.conf.d/luajit.conf", "/opt/luajit_2.0.4/lib"); } sub install_luajit_libs { install_lua_lpeg(); install_lua_json(); } sub install_lua_lpeg { print "Install LUA lpeg module\n"; print "Download archive\n"; chdir $temp_folder_for_building_project; my $archive_file_name = 'lpeg-0.12.2.tar.gz'; my $lpeg_download_result = download_file("http://www.inf.puc-rio.br/~roberto/lpeg/$archive_file_name", $archive_file_name, '69eda40623cb479b4a30fb3720302d3a75f45577'); unless ($lpeg_download_result) { die "Can't download lpeg\n"; } exec_command("tar -xf lpeg-0.12.2.tar.gz"); chdir "lpeg-0.12.2"; # Set path print "Install lpeg library\n"; if ($os_type eq 'macosx' or $os_type eq 'freebsd') { exec_command("sed -i -e 's#LUADIR = ../lua/#LUADIR = /opt/luajit_2.0.4/include/luajit-2.0#' makefile"); } else { exec_command("sed -i 's#LUADIR = ../lua/#LUADIR = /opt/luajit_2.0.4/include/luajit-2.0#' makefile"); } exec_command("make $make_options"); exec_command("cp lpeg.so /opt/luajit_2.0.4/lib/lua/5.1"); } sub install_json_c { my $archive_name = 'json-c-0.12-20140410.tar.gz'; my $install_path = '/opt/json-c-0.12'; print "Install json library\n"; chdir $temp_folder_for_building_project; print "Download archive\n"; my $json_c_download_result = download_file("https://github.com/json-c/json-c/archive/$archive_name", $archive_name, 'b33872f8b2837c7909e9bd8734855669c57a67ce'); unless ($json_c_download_result) { die "Can't download json-c sources\n"; } print "Uncompress it\n"; exec_command("tar -xf $archive_name"); chdir "json-c-json-c-0.12-20140410"; # Fix bugs (assigned but not used variable) which prevent code compilation if ($os_type eq 'macosx' or $os_type eq 'freebsd') { exec_command("sed -i -e '355 s#^#//#' json_tokener.c"); exec_command("sed -i -e '360 s#^#//#' json_tokener.c"); } else { exec_command("sed -i '355 s#^#//#' json_tokener.c"); exec_command("sed -i '360 s#^#//#' json_tokener.c"); } print "Build it\n"; exec_command("./configure --prefix=$install_path"); print "Install it\n"; exec_command("make $make_options install"); put_library_path_to_ld_so("/etc/ld.so.conf.d/json-c.conf", "$install_path/lib"); } sub install_lua_json { print "Install LUA json module\n"; chdir $temp_folder_for_building_project; print "Download archive\n"; my $archive_file_name = '1.3.3.tar.gz'; my $lua_json_download_result = download_file("https://github.com/harningt/luajson/archive/$archive_file_name", $archive_file_name, '53455f697c3f1d7cc955202062e97bbafbea0779'); unless ($lua_json_download_result) { die "Can't download lua json\n"; } exec_command("tar -xf $archive_file_name"); chdir "luajson-1.3.3"; print "Install it\n"; exec_command("PREFIX=/opt/luajit_2.0.4 make $make_options install"); } sub install_init_scripts { # Init file for any systemd aware distro if ( ($distro_type eq 'debian' && $distro_version > 7) or ($distro_type eq 'centos' && $distro_version >= 7) ) { my $systemd_service_path = "/etc/systemd/system/fastnetmon.service"; exec_command("cp $fastnetmon_code_dir/fastnetmon.service $systemd_service_path"); exec_command("sed -i 's#/usr/sbin/fastnetmon#/opt/fastnetmon/fastnetmon#' $systemd_service_path"); print "We found systemd enabled distro and created service: fastnetmon.service\n"; print "You could run it with command: systemctl start fastnetmon.service\n"; return 1; } # Init file for CentOS 6 if ($distro_type eq 'centos' && $distro_version == 6) { my $system_init_path = '/etc/init.d/fastnetmon'; exec_command("cp $fastnetmon_code_dir/fastnetmon_init_script_centos6 $system_init_path"); exec_command("sed -i 's#/usr/sbin/fastnetmon#/opt/fastnetmon/fastnetmon#' $system_init_path"); print "We created service fastnetmon for you\n"; print "You could run it with command: /etc/init.d/fastnetmon start\n"; return 1; } # For Gentoo if ( $distro_type eq 'gentoo' ) { my $init_path_in_src = "$fastnetmon_code_dir/fastnetmon_init_script_gentoo"; my $system_init_path = '/etc/init.d/fastnetmon'; # Checker for source code version, will work only for 1.1.3+ versions if (-e $init_path_in_src) { exec_command("cp $init_path_in_src $system_init_path"); print "We created service fastnetmon for you\n"; print "You could run it with command: /etc/init.d/fastnetmon start\n"; return 1; } } # For Debian Squeeze and Wheezy # And any stable Ubuntu version if ( ($distro_type eq 'debian' && ($distro_version == 6 or $distro_version == 7)) or $distro_type eq 'ubuntu') { my $init_path_in_src = "$fastnetmon_code_dir/fastnetmon_init_script_debian_6_7"; my $system_init_path = '/etc/init.d/fastnetmon'; # Checker for source code version, will work only for 1.1.3+ versions if (-e $init_path_in_src) { exec_command("cp $init_path_in_src $system_init_path"); exec_command("sed -i 's#/usr/sbin/fastnetmon#/opt/fastnetmon/fastnetmon#' $system_init_path"); print "We created service fastnetmon for you\n"; print "You could run it with command: /etc/init.d/fastnetmon start\n"; return 1; } } } sub install_log4cpp { my $distro_file_name = 'log4cpp-1.1.1.tar.gz'; my $log4cpp_url = 'https://sourceforge.net/projects/log4cpp/files/log4cpp-1.1.x%20%28new%29/log4cpp-1.1/log4cpp-1.1.1.tar.gz/download'; my $log4cpp_install_path = '/opt/log4cpp1.1.1'; chdir $temp_folder_for_building_project; print "Download log4cpp sources\n"; my $log4cpp_download_result = download_file($log4cpp_url, $distro_file_name, '23aa5bd7d6f79992c92bad3e1c6d64a34f8fcf68'); unless ($log4cpp_download_result) { die "Can't download log4cpp\n"; } print "Unpack log4cpp sources\n"; exec_command("tar -xf $distro_file_name"); chdir "$temp_folder_for_building_project/log4cpp"; print "Build log4cpp\n"; # TODO: we need some more reliable way to specify options here if ($configure_options) { exec_command("$configure_options ./configure --prefix=$log4cpp_install_path"); } else { exec_command("./configure --prefix=$log4cpp_install_path"); } exec_command("make $make_options install"); print "Add log4cpp to ld.so.conf\n"; put_library_path_to_ld_so("/etc/ld.so.conf.d/log4cpp.conf", "$log4cpp_install_path/lib"); } sub install_grpc { # We use this commit because 0.11.1 is broken and do not build on CentOS 6 correctly my $grpc_git_commit = "7a94236d698477636dd06282f12f706cad527029"; my $grpc_install_path = "/opt/grpc_0_11_1_$grpc_git_commit"; if ($distro_type eq 'debian' or $distro_type eq 'ubuntu') { apt_get('gcc', 'make', 'autoconf', 'automake', 'git', 'libtool', 'g++', 'python-all-dev', 'python-virtualenv'); } # TODO: add deps for CentOS chdir $temp_folder_for_building_project; print "Clone gRPC repository\n"; exec_command("git clone https://github.com/grpc/grpc.git"); chdir "grpc"; # For back compatibility with old git exec_command("git checkout $grpc_git_commit"); exec_command("git submodule update --init"); print "Build gRPC\n"; exec_command("make $make_options"); print "Install gRPC\n"; exec_command("make install prefix=$grpc_install_path"); } sub install_gobgp { chdir $temp_folder_for_building_project; my $distro_file_name = 'v1.0.tar.gz'; my $gobgp_download_result = download_file("https://github.com/osrg/gobgp/archive/$distro_file_name", $distro_file_name, 'daafc31b06d95611ca76f45630e5db140ba5d4c9'); unless ($gobgp_download_result) { die "Can't download gobgp sources\n"; } exec_command("tar -xf $distro_file_name"); chdir "gobgp-1.0"; chdir "gobgp/lib"; my $go_binary = '/usr/local/go/bin/go'; print "Build gobgp\n"; exec_command("GOPATH=\"$temp_folder_for_building_project/gofolder\" $go_binary get github.com/osrg/gobgp/gobgpd"); exec_command("GOPATH=\"$temp_folder_for_building_project/gofolder\" $go_binary get github.com/osrg/gobgp/gobgp"); print "Build gobgp library\n"; exec_command("GOPATH=\"$temp_folder_for_building_project/gofolder\" $go_binary build -buildmode=c-shared -o libgobgp.so *.go"); my $libgobgp_install_path = '/opt/libgobgp_1_0_0'; print "Install gobgp library\n"; mkdir "$libgobgp_install_path"; mkdir "$libgobgp_install_path/include"; mkdir "$libgobgp_install_path/lib"; exec_command("cp libgobgp.h $libgobgp_install_path/include"); exec_command("cp libgobgp.so $libgobgp_install_path/lib"); print "Install gobgp daemon files\n"; my $gobgp_install_path = '/opt/gobgp_1_0_0'; mkdir $gobgp_install_path; exec_command("cp $temp_folder_for_building_project/gofolder/bin/gobgp $gobgp_install_path"); exec_command("cp $temp_folder_for_building_project/gofolder/bin/gobgpd $gobgp_install_path"); } sub install_golang { chdir $temp_folder_for_building_project; my $distro_file_name = ''; my $distro_file_hash = ''; if ($distro_architecture eq 'x86_64') { $distro_file_name = "go1.5.1.linux-amd64.tar.gz"; $distro_file_hash = '46eecd290d8803887dec718c691cc243f2175fe0'; } elsif ($distro_architecture eq 'i686') { $distro_file_name = 'go1.5.1.linux-386.tar.gz'; $distro_file_hash = '6ce7328f84a863f341876658538dfdf10aff86ee'; } else { die "We haven't golang for your platform sorry :(\n"; } my $golang_download_result = download_file("https://storage.googleapis.com/golang/$distro_file_name", $distro_file_name, $distro_file_hash); unless ($golang_download_result) { die "Can't download golanguage\n"; } exec_command("tar -C /usr/local -xzf $distro_file_name"); } sub install_protobuf { if ($distro_type eq 'debian' or $distro_type eq 'ubuntu') { apt_get('gcc', 'make', 'autoconf', 'automake', 'git', 'libtool', 'g++', 'curl'); } # TODO: add deps for CentOS my $protobuf_install_path = '/opt/protobuf_3.0.0_alpha4'; my $distro_file_name = 'v3.0.0-alpha-4.tar.gz'; chdir $temp_folder_for_building_project; print "Download protocol buffers\n"; my $protobuf_download_result = download_file("https://github.com/google/protobuf/archive/$distro_file_name", $distro_file_name, 'd23048ba3218af21ba65fa39bfb6326f5bf9f7a4'); unless ($protobuf_download_result) { die "Can't download protobuf\n"; } print "Unpack protocol buffers\n"; exec_command("tar -xf $distro_file_name"); chdir "protobuf-3.0.0-alpha-4"; print "Configure protobuf\n"; exec_command("./autogen.sh"); exec_command("./configure --prefix=$protobuf_install_path"); print "Build protobuf\n"; exec_command("make $make_options install"); } sub install_mongo_client { my $distro_file_name = 'mongo-c-driver-1.1.9.tar.gz'; my $mongo_install_path = '/opt/mongo_c_driver_1_1_9'; chdir $temp_folder_for_building_project; print "Download mongo\n"; my $mongo_download_result = download_file("https://github.com/mongodb/mongo-c-driver/releases/download/1.1.9/$distro_file_name", $distro_file_name, '32452481be64a297e981846e433b2b492c302b34'); unless ($mongo_download_result) { die "Can't download mongo\n"; } exec_command("tar -xf $distro_file_name"); print "Build mongo client\n"; chdir "mongo-c-driver-1.1.9"; exec_command("./configure --prefix=$mongo_install_path"); exec_command("make $make_options install"); } sub install_hiredis { my $disto_file_name = 'v0.13.1.tar.gz'; my $hiredis_install_path = '/opt/libhiredis_0_13'; chdir $temp_folder_for_building_project; print "Download hiredis\n"; my $hiredis_download_result = download_file("https://github.com/redis/hiredis/archive/$disto_file_name", $disto_file_name, '737c4ed101096c5ec47fcaeba847664352d16204'); unless ($hiredis_download_result) { die "Can't download hiredis\n"; } exec_command("tar -xf $disto_file_name"); print "Build hiredis\n"; chdir "hiredis-0.13.1"; exec_command("PREFIX=$hiredis_install_path make $make_options install"); print "Add hiredis to ld.so.conf\n"; put_library_path_to_ld_so("/etc/ld.so.conf.d/hiredis.conf", "$hiredis_install_path/lib"); } # We use global variable $ndpi_repository here sub install_ndpi { if ($distro_type eq 'debian' or $distro_type eq 'ubuntu') { apt_get('git', 'autoconf', 'libtool', 'automake', 'libpcap-dev'); } elsif ($distro_type eq 'centos') { # We have json-c-devel for CentOS 6 and 7 and will use it for nDPI build system yum('git', 'autoconf', 'automake', 'libtool', 'libpcap-devel', 'json-c-devel'); } elsif ($os_type eq 'freebsd') { exec_command("pkg install -y git autoconf automake libtool"); } print "Download nDPI\n"; if (-e "$temp_folder_for_building_project/nDPI") { # Get new code from the repository chdir "$temp_folder_for_building_project/nDPI"; exec_command("git pull"); } else { chdir $temp_folder_for_building_project; exec_command("git clone $ndpi_repository"); chdir "$temp_folder_for_building_project/nDPI"; } print "Configure nDPI\n"; exec_command("./autogen.sh"); # We have specified direct path to json-c here because it required for example app compilation exec_command("PKG_CONFIG_PATH=/opt/json-c-0.12/lib/pkgconfig ./configure --prefix=/opt/ndpi"); if ($? != 0) { print "Configure failed\n"; return; } print "Build and install nDPI\n"; exec_command("make $make_options install"); print "Add ndpi to ld.so.conf\n"; put_library_path_to_ld_so("/etc/ld.so.conf.d/ndpi.conf", "/opt/ndpi/lib"); } sub init_package_manager { print "Update package manager cache\n"; if ($distro_type eq 'debian' or $distro_type eq 'ubuntu') { exec_command("apt-get update"); } if ($os_type eq 'freebsd') { exec_command("pkg update"); } } sub put_library_path_to_ld_so { my ($ld_so_file_path, $library_path) = @_; if ($os_type eq 'macosx' or $os_type eq 'freebsd') { return; } open my $ld_so_conf_handle, ">", $ld_so_file_path or die "Can't open file $ld_so_file_path $! for writing\n"; print {$ld_so_conf_handle} $library_path; close $ld_so_conf_handle; exec_command("ldconfig"); } sub read_file { my $file_name = shift; my $res = open my $fl, "<", $file_name; unless ($res) { return ""; } my $content = join '', <$fl>; chomp $content; return $content; } # Detect operating system of this machine sub detect_distribution { # We use following global variables here: # $os_type, $distro_type, $distro_version, $appliance_name my $uname_s_output = `uname -s`; chomp $uname_s_output; # uname -a output examples: # FreeBSD 10.1-STABLE FreeBSD 10.1-STABLE #0 r278618: Thu Feb 12 13:55:09 UTC 2015 root@:/usr/obj/usr/src/sys/KERNELWITHNETMAP amd64 # Darwin MacBook-Pro-Pavel.local 14.5.0 Darwin Kernel Version 14.5.0: Wed Jul 29 02:26:53 PDT 2015; root:xnu-2782.40.9~1/RELEASE_X86_64 x86_64 # Linux ubuntu 3.16.0-30-generic #40~14.04.1-Ubuntu SMP Thu Jan 15 17:43:14 UTC 2015 x86_64 x86_64 x86_64 GNU/Linux if ($uname_s_output =~ /FreeBSD/) { $os_type = 'freebsd'; } elsif ($uname_s_output =~ /Darwin/) { $os_type = 'macosx'; } elsif ($uname_s_output =~ /Linux/) { $os_type = 'linux'; } else { warn "Can't detect platform operating system\n"; } if ($os_type eq 'linux') { # x86_64 or i686 $distro_architecture = `uname -m`; chomp $distro_architecture; if (-e "/etc/debian_version") { # Well, on this step it could be Ubuntu or Debian # We need check issue for more details my @issue = `cat /etc/issue`; chomp @issue; my $issue_first_line = $issue[0]; # Possible /etc/issue contents: # Debian GNU/Linux 8 \n \l # Ubuntu 14.04.2 LTS \n \l # Welcome to VyOS - \n \l my $is_proxmox = ''; # Really hard to detect https://github.com/proxmox/pve-manager/blob/master/bin/pvebanner for my $issue_line (@issue) { if ($issue_line =~ m/Welcome to the Proxmox Virtual Environment/) { $is_proxmox = 1; $appliance_name = 'proxmox'; last; } } if ($issue_first_line =~ m/Debian/ or $is_proxmox) { $distro_type = 'debian'; $distro_version = `cat /etc/debian_version`; chomp $distro_version; # Debian 6 example: 6.0.10 # We will try transform it to decimal number if ($distro_version =~ /^(\d+\.\d+)\.\d+$/) { $distro_version = $1; } } elsif ($issue_first_line =~ m/Ubuntu (\d+(?:\.\d+)?)/) { $distro_type = 'ubuntu'; $distro_version = $1; } elsif ($issue_first_line =~ m/VyOS/) { # Yes, VyOS is a Debian $distro_type = 'debian'; $appliance_name = 'vyos'; my $vyos_distro_version = `cat /etc/debian_version`; chomp $vyos_distro_version; # VyOS have strange version and we should fix it if ($vyos_distro_version =~ /^(\d+)\.\d+\.\d+$/) { $distro_version = $1; } } } if (-e "/etc/redhat-release") { $distro_type = 'centos'; my $distro_version_raw = `cat /etc/redhat-release`; chomp $distro_version_raw; # CentOS 6: # CentOS release 6.6 (Final) # CentOS 7: # CentOS Linux release 7.0.1406 (Core) # Fedora release 21 (Twenty One) if ($distro_version_raw =~ /(\d+)/) { $distro_version = $1; } } if (-e "/etc/gentoo-release") { $distro_type = 'gentoo'; my $distro_version_raw = `cat /etc/gentoo-release`; chomp $distro_version_raw; } unless ($distro_type) { die "This distro is unsupported, please do manual install"; } print "We detected your OS as $distro_type Linux $distro_version\n"; } elsif ($os_type eq 'macosx') { my $mac_os_versions_raw = `sw_vers -productVersion`; chomp $mac_os_versions_raw; if ($mac_os_versions_raw =~ /(\d+\.\d+)/) { $distro_version = $1; } print "We detected your OS as Mac OS X $distro_version\n"; } elsif ($os_type eq 'freebsd') { my $freebsd_os_version_raw = `uname -r`; chomp $freebsd_os_version_raw; if ($freebsd_os_version_raw =~ /^(\d+)\.?/) { $distro_version = $1; } print "We detected your OS as FreeBSD $distro_version\n"; } } sub install_pf_ring { my $pf_ring_archive_path = "$temp_folder_for_building_project/PF_RING-$pf_ring_version.tar.gz"; my $pf_ring_sources_path = "$temp_folder_for_building_project/PF_RING-$pf_ring_version"; my $kernel_version = `uname -r`; chomp $kernel_version; print "Install PF_RING dependencies with package manager\n"; if ($distro_type eq 'debian' or $distro_type eq 'ubuntu') { my @debian_packages_for_pfring = ('build-essential', 'bison', 'flex', 'subversion', 'libnuma-dev', 'wget', 'tar', 'make', 'dpkg-dev', 'dkms', 'debhelper'); # Install kernel headers only when we could compile kernel modules there my $kernel_headers_package_name = "linux-headers-$kernel_version"; if ($appliance_name eq 'vyos') { # VyOS uses another name for package for building kernel modules $kernel_headers_package_name = 'linux-vyatta-kbuild'; } elsif ($appliance_name eq 'proxmox') { $kernel_headers_package_name = "pve-headers-$kernel_version"; } push @debian_packages_for_pfring, $kernel_headers_package_name; apt_get(@debian_packages_for_pfring); if ($appliance_name eq 'vyos') { # By default we waven't this symlink and should add it manually if ($distro_architecture eq 'x86_64') { exec_command("ln -s /usr/src/linux-image/debian/build/build-amd64-none-amd64-vyos/ /lib/modules/$kernel_version/build"); } else { # i686 exec_command("ln -s /usr/src/linux-image/debian/build/build-i386-none-586-vyos/ /lib/modules/$kernel_version/build"); } } } elsif ($distro_type eq 'centos') { my $kernel_package_name = 'kernel-devel'; # Fix deplist for OpenVZ if ($kernel_version =~ /stab/) { $kernel_package_name = "vzkernel-devel-$kernel_version"; } yum('make', 'bison', 'flex', $kernel_package_name, 'gcc', 'gcc-c++', 'dkms', 'numactl-devel', 'subversion'); } elsif ($distro_type eq 'gentoo') { my @gentoo_packages_for_pfring = ('subversion', 'sys-process/numactl', 'wget', 'tar'); my $gentoo_packages_for_pfring_as_string = join " ", @gentoo_packages_for_pfring; exec_command("emerge -vu $gentoo_packages_for_pfring_as_string"); if ($? != 0) { print "Emerge fail with code $?\n"; } } # Sometimes we do not want to build kernel module (Docker, KVM and other cases) my $we_could_install_kernel_modules = 1; if ($we_could_install_kernel_modules) { print "Download PF_RING $pf_ring_version sources\n"; my $pfring_download_result = download_file($pf_ring_url, $pf_ring_archive_path, '9fb8080defd1a079ad5f0097e8a8adb5bc264d00'); unless ($pfring_download_result) { die "Can't download PF_RING sources\n"; } my $archive_file_name = $pf_ring_archive_path; if ($? == 0) { print "Unpack PF_RING\n"; mkdir $pf_ring_sources_path; exec_command("tar -xf $pf_ring_archive_path -C $temp_folder_for_building_project"); print "Build PF_RING kernel module\n"; exec_command("make $make_options -C $pf_ring_sources_path/kernel clean"); exec_command("make $make_options -C $pf_ring_sources_path/kernel"); exec_command("make $make_options -C $pf_ring_sources_path/kernel install"); print "Unload PF_RING if it was installed earlier\n"; exec_command("rmmod pf_ring 2>/dev/null"); print "Load PF_RING module into kernel\n"; exec_command("modprobe pf_ring"); my @dmesg = `dmesg`; chomp @dmesg; if (scalar grep (/\[PF_RING\] Initialized correctly/, @dmesg) > 0) { print "PF_RING loaded correctly\n"; } else { warn "PF_RING load error! Please fix this issue manually\n"; # We need this headers for building userspace libs exec_command("cp $pf_ring_sources_path/kernel/linux/pf_ring.h /usr/include/linux"); } } else { warn "Can't download PF_RING source code. Disable support of PF_RING\n"; } } print "Build PF_RING lib\n"; # Because we can't run configure from another folder because it can't find ZC dependency :( chdir "$pf_ring_sources_path/userland/lib"; exec_command("./configure --prefix=/opt/pf_ring_$pf_ring_version"); exec_command("make $make_options"); exec_command("make $make_options install"); # We need do this for backward compatibility with old code (v1.1.2) exec_command("ln -s /opt/pf_ring_$pf_ring_version /opt/pf_ring"); print "Create library symlink\n"; print "Add pf_ring to ld.so.conf\n"; put_library_path_to_ld_so("/etc/ld.so.conf.d/pf_ring.conf", "/opt/pf_ring_$pf_ring_version/lib"); } sub apt_get { my @packages_list = @_; # We install one package per apt-get call because installing multiple packages in one time could fail of one package is broken for my $package (@packages_list) { exec_command("apt-get install -y --force-yes $package"); if ($? != 0) { print "Package '$package' install failed with code $?\n" } } } sub yum { my @packages_list = @_; for my $package (@packages_list) { exec_command("yum install -y $package"); if ($? != 0) { print "Package '$package' install failed with code $?\n"; } } } sub install_fastnetmon { print "Install FastNetMon dependency list\n"; if ($distro_type eq 'debian' or $distro_type eq 'ubuntu') { my @fastnetmon_deps = ("git", "g++", "gcc", "libgpm-dev", "libncurses5-dev", "liblog4cpp5-dev", "libnuma-dev", "libgeoip-dev","libpcap-dev", "cmake", "pkg-config", "libhiredis-dev", ); # Do not install Boost when we build it manually unless ($build_binary_environment) { # We add this dependencies because package libboost-all-dev is broken on VyOS if ($appliance_name eq 'vyos') { push @fastnetmon_deps, ('libboost-regex-dev', 'libboost-system-dev', 'libboost-thread-dev'); } else { push @fastnetmon_deps, "libboost-all-dev"; } } apt_get(@fastnetmon_deps); } elsif ($distro_type eq 'centos') { my @fastnetmon_deps = ('git', 'make', 'gcc', 'gcc-c++', 'GeoIP-devel', 'ncurses-devel', 'glibc-static', 'ncurses-static', 'libpcap-devel', 'gpm-static', 'gpm-devel', 'cmake', 'pkgconfig', 'hiredis-devel', ); # Do not install Boost when we build it manually unless ($build_binary_environment) { @fastnetmon_deps = (@fastnetmon_deps, 'boost-devel', 'boost-thread') } if ($distro_version == 7) { print "Your distro haven't log4cpp in stable EPEL packages and we install log4cpp from testing of EPEL\n"; # We should install log4cpp packages only in this order! yum('https://kojipkgs.fedoraproject.org//packages/log4cpp/1.1.1/1.el7/x86_64/log4cpp-1.1.1-1.el7.x86_64.rpm', 'https://kojipkgs.fedoraproject.org//packages/log4cpp/1.1.1/1.el7/x86_64/log4cpp-devel-1.1.1-1.el7.x86_64.rpm'), } else { push @fastnetmon_deps, 'log4cpp-devel'; } yum(@fastnetmon_deps); } elsif ($distro_type eq 'gentoo') { my @fastnetmon_deps = ("dev-vcs/git", "gcc", "sys-libs/gpm", "sys-libs/ncurses", "dev-libs/log4cpp", "dev-libs/geoip", "net-libs/libpcap", "dev-util/cmake", "pkg-config", "dev-libs/hiredis", "dev-libs/boost" ); my $fastnetmon_deps_as_string = join " ", @fastnetmon_deps; exec_command("emerge -vu $fastnetmon_deps_as_string"); if ($? != 0) { print "Emerge fail with code $?\n"; } } elsif ($os_type eq 'freebsd') { exec_command("pkg install -y cmake git ncurses boost-all log4cpp"); } print "Clone FastNetMon repo\n"; chdir $temp_folder_for_building_project; if (-e $fastnetmon_code_dir) { # Code already downloaded chdir $fastnetmon_code_dir; # Switch to master if we on stable branch if ($we_use_code_from_master) { exec_command("git checkout master"); printf("\n"); } exec_command("git pull"); } else { # Pull new code if ($we_use_code_from_master) { exec_command("git clone $fastnetmon_git_path --quiet 2>/dev/null"); } else { exec_command("git clone $fastnetmon_git_path --quiet 2>/dev/null"); } if ($? != 0) { die "Can't clone source code\n"; } } if ($we_use_code_from_master) { } else { # We use this approach because older git versions do not support git clone -b ... correctly # warning: Remote branch v1.1.2 not found in upstream origin, using HEAD instead chdir "fastnetmon"; exec_command("git checkout $stable_branch_name"); } exec_command("mkdir -p $fastnetmon_code_dir/build"); chdir "$fastnetmon_code_dir/build"; my $cmake_params = ""; unless ($we_have_pfring_support) { $cmake_params .= " -DDISABLE_PF_RING_SUPPORT=ON"; } if ($distro_type eq 'centos' && $distro_version == 6 && !$build_binary_environment) { # Disable cmake script from Boost package because it's broken: # http://public.kitware.com/Bug/view.php?id=15270 $cmake_params .= " -DBoost_NO_BOOST_CMAKE=BOOL:ON"; } if ($enable_gobgp_backend) { $cmake_params .= " -DENABLE_GOBGP_SUPPORT=ON"; } # We should specify this option if we want to build with custom gcc compiler if ($build_binary_environment) { $cmake_params .= " -DENABLE_BUILD_IN_CPP_11_CUSTOM_ENVIRONMENT=ON "; # We should specify compilir this way $cmake_params .= " -DCMAKE_C_COMPILER=/opt/gcc520/bin/gcc -DCMAKE_CXX_COMPILER=/opt/gcc520/bin/g++ "; } if (defined($ENV{'TRAVIS'}) && $ENV{'TRAVIS'}) { system("cmake .. $cmake_params"); system("make $make_options"); } else { system("cmake .. $cmake_params"); system("make $make_options"); } my $fastnetmon_dir = "/opt/fastnetmon"; my $fastnetmon_build_binary_path = "$fastnetmon_code_dir/build/fastnetmon"; unless (-e $fastnetmon_build_binary_path) { die "Can't build fastnetmon!"; } mkdir $fastnetmon_dir; print "Install fastnetmon to dir $fastnetmon_dir\n"; exec_command("cp $fastnetmon_build_binary_path $fastnetmon_dir/fastnetmon"); exec_command("cp $fastnetmon_code_dir/build/fastnetmon_client $fastnetmon_dir/fastnetmon_client"); if (-e "$fastnetmon_code_dir/build/fastnetmon_api_client") { exec_command("cp $fastnetmon_code_dir/build/fastnetmon_api_client $fastnetmon_dir/fastnetmon_api_client"); } my $fastnetmon_config_path = "/etc/fastnetmon.conf"; unless (-e $fastnetmon_config_path) { print "Create stub configuration file\n"; exec_command("cp $fastnetmon_code_dir/fastnetmon.conf $fastnetmon_config_path"); # netmap will detach interface completely and will broke network # So we do not configure it automatically if ($os_type ne 'freebsd') { my @interfaces = get_active_network_interfaces(); my $interfaces_as_list = join ',', @interfaces; print "Select $interfaces_as_list as active interfaces\n"; print "Tune config\n"; if ($os_type eq 'macosx' or $os_type eq 'freebsd') { exec_command("sed -i -e 's/interfaces.*/interfaces = $interfaces_as_list/' $fastnetmon_config_path"); } else { exec_command("sed -i 's/interfaces.*/interfaces = $interfaces_as_list/' $fastnetmon_config_path"); } } } print "If you have any issues, please check /var/log/fastnetmon.log file contents\n"; print "Please add your subnets in /etc/networks_list in CIDR format one subnet per line\n"; my $init_script_result = install_init_scripts(); # Print unified run message unless ($init_script_result) { print "You can run fastnetmon with command: $fastnetmon_dir/fastnetmon\n"; } } sub get_active_network_interfaces { my @interfaces = `LANG=C netstat -i|egrep -v 'lo|Iface|Kernel'|awk '{print \$1}'`; chomp @interfaces; my @clean_interfaces = (); for my $iface (@interfaces) { # skip aliases if ($iface =~ /:/) { next; } push @clean_interfaces, $iface; } return @clean_interfaces; } fastnetmon-1.1.3+dfsg/src/fastnetmon_packet_parser.c000066400000000000000000001024171313534057500226400ustar00rootroot00000000000000#include "fastnetmon_packet_parser.h" /* This code is copy & paste from PF_RING user space library licensed under LGPL terms */ #include // For support uint32_t, uint16_t #include // gettimeofday #include #include #include // in6_addr #include #include // memcpy #include #include // inet_ntop #if defined(__FreeBSD__) || defined(__APPLE__) || defined(__DragonFly__) #include // AF_INET6 #endif // Fake fields #define ipv4_tos ip_tos #define ipv6_tos ip_tos #define ipv4_src ip_src.v4 #define ipv4_dst ip_dst.v4 #define ipv6_src ip_src.v6 #define ipv6_dst ip_dst.v6 #define host4_low host_low.v4 #define host4_high host_high.v4 #define host6_low host_low.v6 #define host6_high host_high.v6 #define host4_peer_a host_peer_a.v4 #define host4_peer_b host_peer_b.v4 #define host6_peer_a host_peer_a.v6 #define host6_peer_b host_peer_b.v6 // GRE tunnels #define GRE_HEADER_CHECKSUM 0x8000 #define GRE_HEADER_ROUTING 0x4000 #define GRE_HEADER_KEY 0x2000 #define GRE_HEADER_SEQ_NUM 0x1000 #define GRE_HEADER_VERSION 0x0007 struct gre_header { u_int16_t flags_and_version; u_int16_t proto; /* Optional fields */ }; // GTP tunnels #define GTP_SIGNALING_PORT 2123 #define GTP_U_DATA_PORT 2152 #define GTP_VERSION_1 0x1 #define GTP_VERSION_2 0x2 #define GTP_PROTOCOL_TYPE 0x1 #define GTP_VERSION_1 0x1 #define GTP_VERSION_2 0x2 #define GTP_PROTOCOL_TYPE 0x1 struct gtp_v1_hdr { #define GTP_FLAGS_VERSION 0xE0 #define GTP_FLAGS_VERSION_SHIFT 5 #define GTP_FLAGS_PROTOCOL_TYPE 0x10 #define GTP_FLAGS_RESERVED 0x08 #define GTP_FLAGS_EXTENSION 0x04 #define GTP_FLAGS_SEQ_NUM 0x02 #define GTP_FLAGS_NPDU_NUM 0x01 u_int8_t flags; u_int8_t message_type; u_int16_t payload_len; u_int32_t teid; } __attribute__((__packed__)); /* Optional: GTP_FLAGS_EXTENSION | GTP_FLAGS_SEQ_NUM | GTP_FLAGS_NPDU_NUM */ struct gtp_v1_opt_hdr { u_int16_t seq_num; u_int8_t npdu_num; u_int8_t next_ext_hdr; } __attribute__((__packed__)); /* Optional: GTP_FLAGS_EXTENSION && next_ext_hdr != 0 */ struct gtp_v1_ext_hdr { #define GTP_EXT_HDR_LEN_UNIT_BYTES 4 u_int8_t len; /* 4-byte unit */ /* * u_char contents[len*4-2]; * u_int8_t next_ext_hdr; */ } __attribute__((__packed__)); #define NO_TUNNEL_ID 0xFFFFFFFF #define NEXTHDR_HOP 0 #define NEXTHDR_TCP 6 #define NEXTHDR_UDP 17 #define NEXTHDR_IPV6 41 #define NEXTHDR_ROUTING 43 #define NEXTHDR_FRAGMENT 44 #define NEXTHDR_ESP 50 #define NEXTHDR_AUTH 51 #define NEXTHDR_ICMP 58 #define NEXTHDR_NONE 59 #define NEXTHDR_DEST 60 #define NEXTHDR_MOBILITY 135 // TCP flags #define TH_FIN_MULTIPLIER 0x01 #define TH_SYN_MULTIPLIER 0x02 #define TH_RST_MULTIPLIER 0x04 #define TH_PUSH_MULTIPLIER 0x08 #define TH_ACK_MULTIPLIER 0x10 #define TH_URG_MULTIPLIER 0x20 #define __LITTLE_ENDIAN_BITFIELD /* FIX */ struct tcphdr { u_int16_t source; u_int16_t dest; u_int32_t seq; u_int32_t ack_seq; #if defined(__LITTLE_ENDIAN_BITFIELD) u_int16_t res1 : 4, doff : 4, fin : 1, syn : 1, rst : 1, psh : 1, ack : 1, urg : 1, ece : 1, cwr : 1; #elif defined(__BIG_ENDIAN_BITFIELD) u_int16_t doff : 4, res1 : 4, cwr : 1, ece : 1, urg : 1, ack : 1, psh : 1, rst : 1, syn : 1, fin : 1; #else #error "Adjust your defines" #endif u_int16_t window; u_int16_t check; u_int16_t urg_ptr; }; struct udphdr { u_int16_t source; u_int16_t dest; u_int16_t len; u_int16_t check; }; struct eth_vlan_hdr { u_int16_t h_vlan_id; /* Tag Control Information (QoS, VLAN ID) */ u_int16_t h_proto; /* packet type ID field */ }; struct kcompact_ipv6_hdr { u_int8_t priority : 4, version : 4; u_int8_t flow_lbl[3]; u_int16_t payload_len; u_int8_t nexthdr; u_int8_t hop_limit; struct in6_addr saddr; struct in6_addr daddr; }; struct kcompact_ipv6_opt_hdr { u_int8_t nexthdr; u_int8_t hdrlen; u_int8_t padding[6]; } __attribute__((packed)); #define __LITTLE_ENDIAN_BITFIELD /* FIX */ struct iphdr { #if defined(__LITTLE_ENDIAN_BITFIELD) u_int8_t ihl : 4, version : 4; #elif defined(__BIG_ENDIAN_BITFIELD) u_int8_t version : 4, ihl : 4; #else #error "Please fix " #endif u_int8_t tos; u_int16_t tot_len; u_int16_t id; #define IP_CE 0x8000 #define IP_DF 0x4000 #define IP_MF 0x2000 #define IP_OFFSET 0x1FFF u_int16_t frag_off; u_int8_t ttl; u_int8_t protocol; u_int16_t check; u_int32_t saddr; u_int32_t daddr; /*The options start here. */ }; // Prototypes char* etheraddr2string(const u_char* ep, char* buf); char* intoa(unsigned int addr); char* _intoa(unsigned int addr, char* buf, u_short bufLen); static char* in6toa(struct in6_addr addr6); char* proto2str(u_short proto); #if defined(__FreeBSD__) || defined(__APPLE__) || defined(__DragonFly__) /* This code from /usr/includes/linux/if_ether.h Linus file */ #define ETH_ALEN 6 /* Octets in one ethernet addr */ #define ETH_P_IP 0x0800 /* Internet Protocol packet */ #define ETH_P_IPV6 0x86DD /* IPv6 over bluebook */ /* * This is an Ethernet frame header. */ struct ethhdr { unsigned char h_dest[ETH_ALEN]; /* destination eth addr */ unsigned char h_source[ETH_ALEN]; /* source ether addr */ u_int16_t h_proto; /* packet type ID field */ } __attribute__((packed)); #endif #if defined(__FreeBSD__) || defined(__APPLE__) u_int32_t pfring_hash_pkt(struct pfring_pkthdr* hdr) { if (hdr->extended_hdr.parsed_pkt.tunnel.tunnel_id == NO_TUNNEL_ID) { return hdr->extended_hdr.parsed_pkt.vlan_id + hdr->extended_hdr.parsed_pkt.l3_proto + hdr->extended_hdr.parsed_pkt.ip_src.v6.__u6_addr.__u6_addr32[0] + hdr->extended_hdr.parsed_pkt.ip_src.v6.__u6_addr.__u6_addr32[1] + hdr->extended_hdr.parsed_pkt.ip_src.v6.__u6_addr.__u6_addr32[2] + hdr->extended_hdr.parsed_pkt.ip_src.v6.__u6_addr.__u6_addr32[3] + hdr->extended_hdr.parsed_pkt.ip_dst.v6.__u6_addr.__u6_addr32[0] + hdr->extended_hdr.parsed_pkt.ip_dst.v6.__u6_addr.__u6_addr32[1] + hdr->extended_hdr.parsed_pkt.ip_dst.v6.__u6_addr.__u6_addr32[2] + hdr->extended_hdr.parsed_pkt.ip_dst.v6.__u6_addr.__u6_addr32[3] + hdr->extended_hdr.parsed_pkt.l4_src_port + hdr->extended_hdr.parsed_pkt.l4_dst_port; } else { return hdr->extended_hdr.parsed_pkt.vlan_id + hdr->extended_hdr.parsed_pkt.tunnel.tunneled_proto + hdr->extended_hdr.parsed_pkt.tunnel.tunneled_ip_src.v6.__u6_addr.__u6_addr32[1] + hdr->extended_hdr.parsed_pkt.tunnel.tunneled_ip_src.v6.__u6_addr.__u6_addr32[2] + hdr->extended_hdr.parsed_pkt.tunnel.tunneled_ip_src.v6.__u6_addr.__u6_addr32[3] + hdr->extended_hdr.parsed_pkt.tunnel.tunneled_ip_dst.v6.__u6_addr.__u6_addr32[0] + hdr->extended_hdr.parsed_pkt.tunnel.tunneled_ip_dst.v6.__u6_addr.__u6_addr32[1] + hdr->extended_hdr.parsed_pkt.tunnel.tunneled_ip_dst.v6.__u6_addr.__u6_addr32[2] + hdr->extended_hdr.parsed_pkt.tunnel.tunneled_ip_dst.v6.__u6_addr.__u6_addr32[3] + hdr->extended_hdr.parsed_pkt.tunnel.tunneled_l4_src_port + hdr->extended_hdr.parsed_pkt.tunnel.tunneled_l4_dst_port; } } #else u_int32_t pfring_hash_pkt(struct pfring_pkthdr* hdr) { if (hdr->extended_hdr.parsed_pkt.tunnel.tunnel_id == NO_TUNNEL_ID) { return hdr->extended_hdr.parsed_pkt.vlan_id + hdr->extended_hdr.parsed_pkt.l3_proto + hdr->extended_hdr.parsed_pkt.ip_src.v6.s6_addr32[0] + hdr->extended_hdr.parsed_pkt.ip_src.v6.s6_addr32[1] + hdr->extended_hdr.parsed_pkt.ip_src.v6.s6_addr32[2] + hdr->extended_hdr.parsed_pkt.ip_src.v6.s6_addr32[3] + hdr->extended_hdr.parsed_pkt.ip_dst.v6.s6_addr32[0] + hdr->extended_hdr.parsed_pkt.ip_dst.v6.s6_addr32[1] + hdr->extended_hdr.parsed_pkt.ip_dst.v6.s6_addr32[2] + hdr->extended_hdr.parsed_pkt.ip_dst.v6.s6_addr32[3] + hdr->extended_hdr.parsed_pkt.l4_src_port + hdr->extended_hdr.parsed_pkt.l4_dst_port; } else { return hdr->extended_hdr.parsed_pkt.vlan_id + hdr->extended_hdr.parsed_pkt.tunnel.tunneled_proto + hdr->extended_hdr.parsed_pkt.tunnel.tunneled_ip_src.v6.s6_addr32[1] + hdr->extended_hdr.parsed_pkt.tunnel.tunneled_ip_src.v6.s6_addr32[2] + hdr->extended_hdr.parsed_pkt.tunnel.tunneled_ip_src.v6.s6_addr32[3] + hdr->extended_hdr.parsed_pkt.tunnel.tunneled_ip_dst.v6.s6_addr32[0] + hdr->extended_hdr.parsed_pkt.tunnel.tunneled_ip_dst.v6.s6_addr32[1] + hdr->extended_hdr.parsed_pkt.tunnel.tunneled_ip_dst.v6.s6_addr32[2] + hdr->extended_hdr.parsed_pkt.tunnel.tunneled_ip_dst.v6.s6_addr32[3] + hdr->extended_hdr.parsed_pkt.tunnel.tunneled_l4_src_port + hdr->extended_hdr.parsed_pkt.tunnel.tunneled_l4_dst_port; } } #endif static int __pfring_parse_tunneled_pkt(u_char* pkt, struct pfring_pkthdr* hdr, u_int16_t ip_version, u_int16_t tunnel_offset) { u_int32_t ip_len = 0; u_int16_t fragment_offset = 0; if (ip_version == 4 /* IPv4 */) { struct iphdr* tunneled_ip; if (hdr->caplen < (tunnel_offset + sizeof(struct iphdr))) return 0; tunneled_ip = (struct iphdr*)(&pkt[tunnel_offset]); hdr->extended_hdr.parsed_pkt.tunnel.tunneled_proto = tunneled_ip->protocol; hdr->extended_hdr.parsed_pkt.tunnel.tunneled_ip_src.v4 = ntohl(tunneled_ip->saddr); hdr->extended_hdr.parsed_pkt.tunnel.tunneled_ip_dst.v4 = ntohl(tunneled_ip->daddr); fragment_offset = tunneled_ip->frag_off & htons(IP_OFFSET); /* fragment, but not the first */ ip_len = tunneled_ip->ihl * 4; tunnel_offset += ip_len; } else if (ip_version == 6 /* IPv6 */) { struct kcompact_ipv6_hdr* tunneled_ipv6; if (hdr->caplen < (tunnel_offset + sizeof(struct kcompact_ipv6_hdr))) return 0; tunneled_ipv6 = (struct kcompact_ipv6_hdr*)(&pkt[tunnel_offset]); hdr->extended_hdr.parsed_pkt.tunnel.tunneled_proto = tunneled_ipv6->nexthdr; /* Values of IPv6 addresses are stored as network byte order */ memcpy(&hdr->extended_hdr.parsed_pkt.tunnel.tunneled_ip_src.v6, &tunneled_ipv6->saddr, sizeof(tunneled_ipv6->saddr)); memcpy(&hdr->extended_hdr.parsed_pkt.tunnel.tunneled_ip_dst.v6, &tunneled_ipv6->daddr, sizeof(tunneled_ipv6->daddr)); ip_len = sizeof(struct kcompact_ipv6_hdr); /* Note: NEXTHDR_AUTH, NEXTHDR_ESP, NEXTHDR_IPV6, NEXTHDR_MOBILITY are not handled */ while (hdr->extended_hdr.parsed_pkt.tunnel.tunneled_proto == NEXTHDR_HOP || hdr->extended_hdr.parsed_pkt.tunnel.tunneled_proto == NEXTHDR_DEST || hdr->extended_hdr.parsed_pkt.tunnel.tunneled_proto == NEXTHDR_ROUTING || hdr->extended_hdr.parsed_pkt.tunnel.tunneled_proto == NEXTHDR_FRAGMENT) { struct kcompact_ipv6_opt_hdr* ipv6_opt; if (hdr->caplen < tunnel_offset + ip_len + sizeof(struct kcompact_ipv6_opt_hdr)) return 1; ipv6_opt = (struct kcompact_ipv6_opt_hdr*)(&pkt[tunnel_offset + ip_len]); ip_len += sizeof(struct kcompact_ipv6_opt_hdr); fragment_offset = 0; if (hdr->extended_hdr.parsed_pkt.tunnel.tunneled_proto == NEXTHDR_HOP || hdr->extended_hdr.parsed_pkt.tunnel.tunneled_proto == NEXTHDR_DEST || hdr->extended_hdr.parsed_pkt.tunnel.tunneled_proto == NEXTHDR_ROUTING) ip_len += ipv6_opt->hdrlen * 8; hdr->extended_hdr.parsed_pkt.tunnel.tunneled_proto = ipv6_opt->nexthdr; } if (hdr->extended_hdr.parsed_pkt.tunnel.tunneled_proto == NEXTHDR_NONE) hdr->extended_hdr.parsed_pkt.tunnel.tunneled_proto = 0; tunnel_offset += ip_len; } else return 0; if (fragment_offset) return 1; if (hdr->extended_hdr.parsed_pkt.tunnel.tunneled_proto == IPPROTO_TCP) { struct tcphdr* tcp; if (hdr->caplen < tunnel_offset + sizeof(struct tcphdr)) return 1; tcp = (struct tcphdr*)(&pkt[tunnel_offset]); hdr->extended_hdr.parsed_pkt.tunnel.tunneled_l4_src_port = ntohs(tcp->source), hdr->extended_hdr.parsed_pkt.tunnel.tunneled_l4_dst_port = ntohs(tcp->dest); } else if (hdr->extended_hdr.parsed_pkt.tunnel.tunneled_proto == IPPROTO_UDP) { struct udphdr* udp; if (hdr->caplen < tunnel_offset + sizeof(struct udphdr)) return 1; udp = (struct udphdr*)(&pkt[tunnel_offset]); hdr->extended_hdr.parsed_pkt.tunnel.tunneled_l4_src_port = ntohs(udp->source), hdr->extended_hdr.parsed_pkt.tunnel.tunneled_l4_dst_port = ntohs(udp->dest); } return 2; } int fastnetmon_parse_pkt(unsigned char* pkt, struct pfring_pkthdr* hdr, u_int8_t level /* L2..L4, 5 (tunnel) */, u_int8_t add_timestamp /* 0,1 */, u_int8_t add_hash /* 0,1 */) { struct ethhdr* eh = (struct ethhdr*)pkt; u_int32_t displ = 0, ip_len; u_int16_t analyzed = 0, fragment_offset = 0; hdr->extended_hdr.parsed_pkt.tunnel.tunnel_id = NO_TUNNEL_ID; /* Note: in order to optimize the computation, this function expects a zero-ed * or partially parsed pkthdr */ // memset(&hdr->extended_hdr.parsed_pkt, 0, sizeof(struct pkt_parsing_info)); // hdr->extended_hdr.parsed_header_len = 0; if (hdr->extended_hdr.parsed_pkt.offset.l3_offset != 0) goto L3; memcpy(&hdr->extended_hdr.parsed_pkt.dmac, eh->h_dest, sizeof(eh->h_dest)); memcpy(&hdr->extended_hdr.parsed_pkt.smac, eh->h_source, sizeof(eh->h_source)); hdr->extended_hdr.parsed_pkt.eth_type = ntohs(eh->h_proto); hdr->extended_hdr.parsed_pkt.offset.eth_offset = 0; hdr->extended_hdr.parsed_pkt.offset.vlan_offset = 0; hdr->extended_hdr.parsed_pkt.vlan_id = 0; /* Any VLAN */ if (hdr->extended_hdr.parsed_pkt.eth_type == 0x8100 /* 802.1q (VLAN) */) { struct eth_vlan_hdr* vh; hdr->extended_hdr.parsed_pkt.offset.vlan_offset = sizeof(struct ethhdr) - sizeof(struct eth_vlan_hdr); while (hdr->extended_hdr.parsed_pkt.eth_type == 0x8100 /* 802.1q (VLAN) */) { hdr->extended_hdr.parsed_pkt.offset.vlan_offset += sizeof(struct eth_vlan_hdr); vh = (struct eth_vlan_hdr*)&pkt[hdr->extended_hdr.parsed_pkt.offset.vlan_offset]; hdr->extended_hdr.parsed_pkt.vlan_id = ntohs(vh->h_vlan_id) & 0x0fff; hdr->extended_hdr.parsed_pkt.eth_type = ntohs(vh->h_proto); displ += sizeof(struct eth_vlan_hdr); } } hdr->extended_hdr.parsed_pkt.offset.l3_offset = hdr->extended_hdr.parsed_pkt.offset.eth_offset + displ + sizeof(struct ethhdr); L3: analyzed = 2; if (level < 3) goto TIMESTAMP; if (hdr->extended_hdr.parsed_pkt.offset.l4_offset != 0) goto L4; if (hdr->extended_hdr.parsed_pkt.eth_type == 0x0800 /* IPv4 */) { struct iphdr* ip; hdr->extended_hdr.parsed_pkt.ip_version = 4; if (hdr->caplen < hdr->extended_hdr.parsed_pkt.offset.l3_offset + sizeof(struct iphdr)) goto TIMESTAMP; ip = (struct iphdr*)(&pkt[hdr->extended_hdr.parsed_pkt.offset.l3_offset]); hdr->extended_hdr.parsed_pkt.ipv4_src = ntohl(ip->saddr); hdr->extended_hdr.parsed_pkt.ipv4_dst = ntohl(ip->daddr); hdr->extended_hdr.parsed_pkt.l3_proto = ip->protocol; hdr->extended_hdr.parsed_pkt.ipv4_tos = ip->tos; fragment_offset = ip->frag_off & htons(IP_OFFSET); /* fragment, but not the first */ ip_len = ip->ihl * 4; hdr->extended_hdr.parsed_pkt.ip_total_size = ntohs(ip->tot_len); // Parse fragmentation info: // Very good examples about IPv4 flags: http://lwn.net/Articles/136319/ hdr->extended_hdr.parsed_pkt.ip_fragmented = 0; hdr->extended_hdr.parsed_pkt.ip_ttl = ip->ttl; int fast_frag_off = ntohs(ip->frag_off); int fast_offset = (fast_frag_off & IP_OFFSET); if (fast_frag_off & IP_MF) { // printf("Packet with MF flag\n"); hdr->extended_hdr.parsed_pkt.ip_fragmented = 1; } if (fast_offset != 0) { // printf("Packet with non zero offset\n"); hdr->extended_hdr.parsed_pkt.ip_fragmented = 1; } } else if (hdr->extended_hdr.parsed_pkt.eth_type == 0x86DD /* IPv6 */) { struct kcompact_ipv6_hdr* ipv6; hdr->extended_hdr.parsed_pkt.ip_version = 6; if (hdr->caplen < hdr->extended_hdr.parsed_pkt.offset.l3_offset + sizeof(struct kcompact_ipv6_hdr)) goto TIMESTAMP; ipv6 = (struct kcompact_ipv6_hdr*)(&pkt[hdr->extended_hdr.parsed_pkt.offset.l3_offset]); ip_len = sizeof(struct kcompact_ipv6_hdr); /* Values of IPv6 addresses are stored as network byte order */ memcpy(&hdr->extended_hdr.parsed_pkt.ipv6_src, &ipv6->saddr, sizeof(ipv6->saddr)); memcpy(&hdr->extended_hdr.parsed_pkt.ipv6_dst, &ipv6->daddr, sizeof(ipv6->daddr)); hdr->extended_hdr.parsed_pkt.l3_proto = ipv6->nexthdr; hdr->extended_hdr.parsed_pkt.ipv6_tos = ipv6->priority; /* IPv6 class of service */ /* Note: NEXTHDR_AUTH, NEXTHDR_ESP, NEXTHDR_IPV6, NEXTHDR_MOBILITY are not handled */ while (hdr->extended_hdr.parsed_pkt.l3_proto == NEXTHDR_HOP || hdr->extended_hdr.parsed_pkt.l3_proto == NEXTHDR_DEST || hdr->extended_hdr.parsed_pkt.l3_proto == NEXTHDR_ROUTING || hdr->extended_hdr.parsed_pkt.l3_proto == NEXTHDR_FRAGMENT) { struct kcompact_ipv6_opt_hdr* ipv6_opt; if (hdr->caplen < hdr->extended_hdr.parsed_pkt.offset.l3_offset + ip_len + sizeof(struct kcompact_ipv6_opt_hdr)) goto TIMESTAMP; ipv6_opt = (struct kcompact_ipv6_opt_hdr*)(&pkt[hdr->extended_hdr.parsed_pkt.offset.l3_offset + ip_len]); ip_len += sizeof(struct kcompact_ipv6_opt_hdr); if (hdr->extended_hdr.parsed_pkt.l3_proto == NEXTHDR_HOP || hdr->extended_hdr.parsed_pkt.l3_proto == NEXTHDR_DEST || hdr->extended_hdr.parsed_pkt.l3_proto == NEXTHDR_ROUTING) ip_len += ipv6_opt->hdrlen * 8; hdr->extended_hdr.parsed_pkt.l3_proto = ipv6_opt->nexthdr; } if (hdr->extended_hdr.parsed_pkt.l3_proto == NEXTHDR_NONE) hdr->extended_hdr.parsed_pkt.l3_proto = 0; } else { hdr->extended_hdr.parsed_pkt.l3_proto = 0; goto TIMESTAMP; } hdr->extended_hdr.parsed_pkt.offset.l4_offset = hdr->extended_hdr.parsed_pkt.offset.l3_offset + ip_len; L4: analyzed = 3; if (level < 4 || fragment_offset) goto TIMESTAMP; if (hdr->extended_hdr.parsed_pkt.l3_proto == IPPROTO_TCP) { struct tcphdr* tcp; if (hdr->caplen < hdr->extended_hdr.parsed_pkt.offset.l4_offset + sizeof(struct tcphdr)) goto TIMESTAMP; tcp = (struct tcphdr*)(&pkt[hdr->extended_hdr.parsed_pkt.offset.l4_offset]); hdr->extended_hdr.parsed_pkt.l4_src_port = ntohs(tcp->source); hdr->extended_hdr.parsed_pkt.l4_dst_port = ntohs(tcp->dest); hdr->extended_hdr.parsed_pkt.offset.payload_offset = hdr->extended_hdr.parsed_pkt.offset.l4_offset + (tcp->doff * 4); hdr->extended_hdr.parsed_pkt.tcp.seq_num = ntohl(tcp->seq); hdr->extended_hdr.parsed_pkt.tcp.ack_num = ntohl(tcp->ack_seq); hdr->extended_hdr.parsed_pkt.tcp.flags = (tcp->fin * TH_FIN_MULTIPLIER) + (tcp->syn * TH_SYN_MULTIPLIER) + (tcp->rst * TH_RST_MULTIPLIER) + (tcp->psh * TH_PUSH_MULTIPLIER) + (tcp->ack * TH_ACK_MULTIPLIER) + (tcp->urg * TH_URG_MULTIPLIER); analyzed = 4; } else if (hdr->extended_hdr.parsed_pkt.l3_proto == IPPROTO_UDP) { struct udphdr* udp; if (hdr->caplen < hdr->extended_hdr.parsed_pkt.offset.l4_offset + sizeof(struct udphdr)) goto TIMESTAMP; udp = (struct udphdr*)(&pkt[hdr->extended_hdr.parsed_pkt.offset.l4_offset]); hdr->extended_hdr.parsed_pkt.l4_src_port = ntohs(udp->source), hdr->extended_hdr.parsed_pkt.l4_dst_port = ntohs(udp->dest); hdr->extended_hdr.parsed_pkt.offset.payload_offset = hdr->extended_hdr.parsed_pkt.offset.l4_offset + sizeof(struct udphdr); analyzed = 4; if (level < 5) goto TIMESTAMP; /* GTPv1 */ if ((hdr->extended_hdr.parsed_pkt.l4_src_port == GTP_SIGNALING_PORT) || (hdr->extended_hdr.parsed_pkt.l4_dst_port == GTP_SIGNALING_PORT) || (hdr->extended_hdr.parsed_pkt.l4_src_port == GTP_U_DATA_PORT) || (hdr->extended_hdr.parsed_pkt.l4_dst_port == GTP_U_DATA_PORT)) { struct gtp_v1_hdr* gtp; u_int16_t gtp_len; if (hdr->caplen < (hdr->extended_hdr.parsed_pkt.offset.payload_offset + sizeof(struct gtp_v1_hdr))) goto TIMESTAMP; gtp = (struct gtp_v1_hdr*)(&pkt[hdr->extended_hdr.parsed_pkt.offset.payload_offset]); gtp_len = sizeof(struct gtp_v1_hdr); if (((gtp->flags & GTP_FLAGS_VERSION) >> GTP_FLAGS_VERSION_SHIFT) == GTP_VERSION_1) { struct iphdr* tunneled_ip; hdr->extended_hdr.parsed_pkt.tunnel.tunnel_id = ntohl(gtp->teid); if ((hdr->extended_hdr.parsed_pkt.l4_src_port == GTP_U_DATA_PORT) || (hdr->extended_hdr.parsed_pkt.l4_dst_port == GTP_U_DATA_PORT)) { if (gtp->flags & (GTP_FLAGS_EXTENSION | GTP_FLAGS_SEQ_NUM | GTP_FLAGS_NPDU_NUM)) { struct gtp_v1_opt_hdr* gtpopt; if (hdr->caplen < (hdr->extended_hdr.parsed_pkt.offset.payload_offset + gtp_len + sizeof(struct gtp_v1_opt_hdr))) goto TIMESTAMP; gtpopt = (struct gtp_v1_opt_hdr*)(&pkt[hdr->extended_hdr.parsed_pkt.offset.payload_offset + gtp_len]); gtp_len += sizeof(struct gtp_v1_opt_hdr); if ((gtp->flags & GTP_FLAGS_EXTENSION) && gtpopt->next_ext_hdr) { struct gtp_v1_ext_hdr* gtpext; u_int8_t* next_ext_hdr; do { if (hdr->caplen < (hdr->extended_hdr.parsed_pkt.offset.payload_offset + gtp_len + 1 /* 8bit len field */)) goto TIMESTAMP; gtpext = (struct gtp_v1_ext_hdr*)(&pkt[hdr->extended_hdr.parsed_pkt.offset.payload_offset + gtp_len]); gtp_len += (gtpext->len * GTP_EXT_HDR_LEN_UNIT_BYTES); if (gtpext->len == 0 || hdr->caplen < (hdr->extended_hdr.parsed_pkt.offset.payload_offset + gtp_len)) goto TIMESTAMP; next_ext_hdr = (u_int8_t*)(&pkt[hdr->extended_hdr.parsed_pkt.offset.payload_offset + gtp_len - 1 /* 8bit next_ext_hdr field*/]); } while (*next_ext_hdr); } } if (hdr->caplen < (hdr->extended_hdr.parsed_pkt.offset.payload_offset + gtp_len + sizeof(struct iphdr))) goto TIMESTAMP; tunneled_ip = (struct iphdr*)(&pkt[hdr->extended_hdr.parsed_pkt.offset.payload_offset + gtp_len]); analyzed += __pfring_parse_tunneled_pkt(pkt, hdr, tunneled_ip->version, hdr->extended_hdr.parsed_pkt.offset.payload_offset + gtp_len); } } } } else if (hdr->extended_hdr.parsed_pkt.l3_proto == IPPROTO_GRE /* 0x47 */) { struct gre_header* gre = (struct gre_header*)(&pkt[hdr->extended_hdr.parsed_pkt.offset.l4_offset]); int gre_offset; gre->flags_and_version = ntohs(gre->flags_and_version); gre->proto = ntohs(gre->proto); gre_offset = sizeof(struct gre_header); if ((gre->flags_and_version & GRE_HEADER_VERSION) == 0) { if (gre->flags_and_version & (GRE_HEADER_CHECKSUM | GRE_HEADER_ROUTING)) gre_offset += 4; if (gre->flags_and_version & GRE_HEADER_KEY) { u_int32_t* tunnel_id = (u_int32_t*)(&pkt[hdr->extended_hdr.parsed_pkt.offset.l4_offset + gre_offset]); gre_offset += 4; hdr->extended_hdr.parsed_pkt.tunnel.tunnel_id = ntohl(*tunnel_id); } if (gre->flags_and_version & GRE_HEADER_SEQ_NUM) gre_offset += 4; hdr->extended_hdr.parsed_pkt.offset.payload_offset = hdr->extended_hdr.parsed_pkt.offset.l4_offset + gre_offset; analyzed = 4; if (level < 5) goto TIMESTAMP; if (gre->proto == ETH_P_IP /* IPv4 */ || gre->proto == ETH_P_IPV6 /* IPv6 */) analyzed += __pfring_parse_tunneled_pkt(pkt, hdr, gre->proto == ETH_P_IP ? 4 : 6, hdr->extended_hdr.parsed_pkt.offset.payload_offset); } else { /* TODO handle other GRE versions */ hdr->extended_hdr.parsed_pkt.offset.payload_offset = hdr->extended_hdr.parsed_pkt.offset.l4_offset; } } else { hdr->extended_hdr.parsed_pkt.offset.payload_offset = hdr->extended_hdr.parsed_pkt.offset.l4_offset; hdr->extended_hdr.parsed_pkt.l4_src_port = hdr->extended_hdr.parsed_pkt.l4_dst_port = 0; } TIMESTAMP: if (add_timestamp && hdr->ts.tv_sec == 0) gettimeofday(&hdr->ts, NULL); /* TODO What about using clock_gettime(CLOCK_REALTIME, ts) ? */ if (add_hash && hdr->extended_hdr.pkt_hash == 0) hdr->extended_hdr.pkt_hash = pfring_hash_pkt(hdr); return analyzed; } int fastnetmon_print_parsed_pkt(char* buff, u_int buff_len, const u_char* p, const struct pfring_pkthdr* h) { char buf1[32], buf2[32]; int buff_used = 0; buff_used += snprintf(&buff[buff_used], buff_len - buff_used, "[%s -> %s] ", etheraddr2string(h->extended_hdr.parsed_pkt.smac, buf1), etheraddr2string(h->extended_hdr.parsed_pkt.dmac, buf2)); if (h->extended_hdr.parsed_pkt.offset.vlan_offset) buff_used += snprintf(&buff[buff_used], buff_len - buff_used, "[vlan %u] ", h->extended_hdr.parsed_pkt.vlan_id); if (h->extended_hdr.parsed_pkt.eth_type == 0x0800 /* IPv4*/ || h->extended_hdr.parsed_pkt.eth_type == 0x86DD /* IPv6*/) { if (h->extended_hdr.parsed_pkt.eth_type == 0x0800 /* IPv4*/) { buff_used += snprintf(&buff[buff_used], buff_len - buff_used, "[IPv4][%s:%d ", intoa(h->extended_hdr.parsed_pkt.ipv4_src), h->extended_hdr.parsed_pkt.l4_src_port); buff_used += snprintf(&buff[buff_used], buff_len - buff_used, "-> %s:%d] ", intoa(h->extended_hdr.parsed_pkt.ipv4_dst), h->extended_hdr.parsed_pkt.l4_dst_port); } else { buff_used += snprintf(&buff[buff_used], buff_len - buff_used, "[IPv6][%s:%d ", in6toa(h->extended_hdr.parsed_pkt.ipv6_src), h->extended_hdr.parsed_pkt.l4_src_port); buff_used += snprintf(&buff[buff_used], buff_len - buff_used, "-> %s:%d] ", in6toa(h->extended_hdr.parsed_pkt.ipv6_dst), h->extended_hdr.parsed_pkt.l4_dst_port); } buff_used += snprintf(&buff[buff_used], buff_len - buff_used, "[l3_proto=%s]", proto2str(h->extended_hdr.parsed_pkt.l3_proto)); if (h->extended_hdr.parsed_pkt.tunnel.tunnel_id != NO_TUNNEL_ID) { buff_used += snprintf(&buff[buff_used], buff_len - buff_used, "[TEID=0x%08X][tunneled_proto=%s]", h->extended_hdr.parsed_pkt.tunnel.tunnel_id, proto2str(h->extended_hdr.parsed_pkt.tunnel.tunneled_proto)); if (h->extended_hdr.parsed_pkt.eth_type == 0x0800 /* IPv4*/) { buff_used += snprintf(&buff[buff_used], buff_len - buff_used, "[IPv4][%s:%d ", intoa(h->extended_hdr.parsed_pkt.tunnel.tunneled_ip_src.v4), h->extended_hdr.parsed_pkt.tunnel.tunneled_l4_src_port); buff_used += snprintf(&buff[buff_used], buff_len - buff_used, "-> %s:%d] ", intoa(h->extended_hdr.parsed_pkt.tunnel.tunneled_ip_dst.v4), h->extended_hdr.parsed_pkt.tunnel.tunneled_l4_dst_port); } else { buff_used += snprintf(&buff[buff_used], buff_len - buff_used, "[IPv6][%s:%d ", in6toa(h->extended_hdr.parsed_pkt.tunnel.tunneled_ip_src.v6), h->extended_hdr.parsed_pkt.tunnel.tunneled_l4_src_port); buff_used += snprintf(&buff[buff_used], buff_len - buff_used, "-> %s:%d] ", in6toa(h->extended_hdr.parsed_pkt.tunnel.tunneled_ip_dst.v6), h->extended_hdr.parsed_pkt.tunnel.tunneled_l4_dst_port); } } buff_used += snprintf(&buff[buff_used], buff_len - buff_used, "[ip_fragmented: %d]", h->extended_hdr.parsed_pkt.ip_fragmented); buff_used += snprintf(&buff[buff_used], buff_len - buff_used, "[hash=%u][tos=%d][tcp_seq_num=%u]", h->extended_hdr.pkt_hash, h->extended_hdr.parsed_pkt.ipv4_tos, h->extended_hdr.parsed_pkt.tcp.seq_num); } else if (h->extended_hdr.parsed_pkt.eth_type == 0x0806 /* ARP */) { buff_used += snprintf(&buff[buff_used], buff_len - buff_used, "[ARP]"); if (buff_len >= h->extended_hdr.parsed_pkt.offset.l3_offset + 30) { buff_used += snprintf(&buff[buff_used], buff_len - buff_used, "[Sender=%s/%s]", etheraddr2string(&p[h->extended_hdr.parsed_pkt.offset.l3_offset + 8], buf1), intoa(ntohl(*((u_int32_t*)&p[h->extended_hdr.parsed_pkt.offset.l3_offset + 14])))); buff_used += snprintf(&buff[buff_used], buff_len - buff_used, "[Target=%s/%s]", etheraddr2string(&p[h->extended_hdr.parsed_pkt.offset.l3_offset + 18], buf2), intoa(ntohl(*((u_int32_t*)&p[h->extended_hdr.parsed_pkt.offset.l3_offset + 24])))); } } else { buff_used += snprintf(&buff[buff_used], buff_len - buff_used, "[eth_type=0x%04X]", h->extended_hdr.parsed_pkt.eth_type); } buff_used += snprintf(&buff[buff_used], buff_len - buff_used, " [caplen=%d][len=%d][parsed_header_len=%d][" "eth_offset=%d][l3_offset=%d][l4_offset=%d][" "payload_offset=%d]\n", h->caplen, h->len, h->extended_hdr.parsed_header_len, h->extended_hdr.parsed_pkt.offset.eth_offset, h->extended_hdr.parsed_pkt.offset.l3_offset, h->extended_hdr.parsed_pkt.offset.l4_offset, h->extended_hdr.parsed_pkt.offset.payload_offset); return buff_used; } char* etheraddr2string(const u_char* ep, char* buf) { char* hex = "0123456789ABCDEF"; u_int i, j; char* cp; cp = buf; if ((j = *ep >> 4) != 0) *cp++ = hex[j]; else *cp++ = '0'; *cp++ = hex[*ep++ & 0xf]; for (i = 5; (int)--i >= 0;) { *cp++ = ':'; if ((j = *ep >> 4) != 0) *cp++ = hex[j]; else *cp++ = '0'; *cp++ = hex[*ep++ & 0xf]; } *cp = '\0'; return (buf); } char* intoa(unsigned int addr) { static char buf[sizeof "ff:ff:ff:ff:ff:ff:255.255.255.255"]; return (_intoa(addr, buf, sizeof(buf))); } char* _intoa(unsigned int addr, char* buf, u_short bufLen) { char* cp, *retStr; u_int byte; int n; cp = &buf[bufLen]; *--cp = '\0'; n = 4; do { byte = addr & 0xff; *--cp = byte % 10 + '0'; byte /= 10; if (byte > 0) { *--cp = byte % 10 + '0'; byte /= 10; if (byte > 0) *--cp = byte + '0'; } *--cp = '.'; addr >>= 8; } while (--n > 0); retStr = (char*)(cp + 1); return (retStr); } static char* in6toa(struct in6_addr addr6) { static char buf[sizeof "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"]; char* ret = (char*)inet_ntop(AF_INET6, &addr6, buf, sizeof(buf)); if (ret == NULL) { // printf("Internal error (&buff[buff_used]r too short)"); buf[0] = '\0'; } return (ret); } char* proto2str(u_short proto) { static char protoName[8]; switch (proto) { case IPPROTO_TCP: return ("TCP"); case IPPROTO_UDP: return ("UDP"); case IPPROTO_ICMP: return ("ICMP"); case IPPROTO_GRE: return ("GRE"); default: snprintf(protoName, sizeof(protoName), "%d", proto); return (protoName); } } fastnetmon-1.1.3+dfsg/src/fastnetmon_packet_parser.h000066400000000000000000000107311313534057500226420ustar00rootroot00000000000000#ifndef PFRING_PACKET_PARSER_H #define PFRING_PACKET_PARSER_H #include #include // in6_addr #if defined(__APPLE__) // For Mac OS X here we can find definition of "struct timeval" #include #endif #define ETH_ALEN 6 /* Note that as offsets *can* be negative, please do not change them to unsigned */ struct pkt_offset { int16_t eth_offset; /* This offset *must* be added to all offsets below ONLY if you are inside the kernel (e.g. when you code a pf_ring plugin). Ignore it in user-space. */ int16_t vlan_offset; int16_t l3_offset; int16_t l4_offset; int16_t payload_offset; }; typedef union { struct in6_addr v6; /* IPv6 src/dst IP addresses (Network byte order) */ u_int32_t v4; /* IPv4 src/dst IP addresses */ } ip_addr; /* GPRS Tunneling Protocol */ typedef struct { u_int32_t tunnel_id; /* GTP/GRE tunnelId or NO_TUNNEL_ID for no filtering */ u_int8_t tunneled_proto; ip_addr tunneled_ip_src, tunneled_ip_dst; u_int16_t tunneled_l4_src_port, tunneled_l4_dst_port; } tunnel_info; struct pkt_parsing_info { /* Core fields (also used by NetFlow) */ u_int8_t dmac[ETH_ALEN], smac[ETH_ALEN]; /* MAC src/dst addresses */ u_int16_t eth_type; /* Ethernet type */ u_int16_t vlan_id; /* VLAN Id or NO_VLAN */ u_int8_t ip_version; u_int8_t l3_proto, ip_tos; /* Layer 3 protocol/TOS */ u_int8_t ip_fragmented; /* Layer 3 fragmentation flag */ u_int16_t ip_total_size; /* Total size of IP packet */ u_int8_t ip_ttl; /* TTL flag */ ip_addr ip_src, ip_dst; /* IPv4 src/dst IP addresses */ u_int16_t l4_src_port, l4_dst_port; /* Layer 4 src/dst ports */ struct { u_int8_t flags; /* TCP flags (0 if not available) */ u_int32_t seq_num, ack_num; /* TCP sequence number */ } tcp; tunnel_info tunnel; u_int16_t last_matched_plugin_id; /* If > 0 identifies a plugin to that matched the packet */ u_int16_t last_matched_rule_id; /* If > 0 identifies a rule that matched the packet */ struct pkt_offset offset; /* Offsets of L3/L4/payload elements */ }; struct pfring_extended_pkthdr { u_int64_t timestamp_ns; /* Packet timestamp at ns precision. Note that if your NIC supports hardware timestamp, this is the place to read timestamp from */ #define PKT_FLAGS_CHECKSUM_OFFLOAD 1 << 0 /* IP/TCP checksum offload enabled */ #define PKT_FLAGS_CHECKSUM_OK 1 << 1 /* Valid checksum (with IP/TCP checksum offload enabled) */ #define PKT_FLAGS_IP_MORE_FRAG 1 << 2 /* IP More fragments flag set */ #define PKT_FLAGS_IP_FRAG_OFFSET 1 << 3 /* IP fragment offset set (not 0) */ #define PKT_FLAGS_VLAN_HWACCEL 1 << 4 /* VLAN stripped by hw */ u_int32_t flags; /* --- short header ends here --- */ u_int8_t rx_direction; /* 1=RX: packet received by the NIC, 0=TX: packet transmitted by the NIC */ int32_t if_index; /* index of the interface on which the packet has been received. It can be also used to report other information */ u_int32_t pkt_hash; /* Hash based on the packet header */ struct { int bounce_interface; /* Interface Id where this packet will bounce after processing if its values is other than UNKNOWN_INTERFACE */ struct sk_buff* reserved; /* Kernel only pointer */ } tx; u_int16_t parsed_header_len; /* Extra parsing data before packet */ /* NOTE: leave it as last field of the memset on parse_pkt() will fail */ struct pkt_parsing_info parsed_pkt; /* packet parsing info */ }; /* NOTE: Keep 'struct pfring_pkthdr' in sync with 'struct pcap_pkthdr' */ struct pfring_pkthdr { /* pcap header */ struct timeval ts; /* time stamp */ u_int32_t caplen; /* length of portion present */ u_int32_t len; /* length of whole packet (off wire) */ struct pfring_extended_pkthdr extended_hdr; /* PF_RING extended header */ }; #ifdef __cplusplus extern "C" { #endif // Prototypes int fastnetmon_print_parsed_pkt(char* buff, u_int buff_len, const u_char* p, const struct pfring_pkthdr* h); int fastnetmon_parse_pkt(unsigned char* pkt, struct pfring_pkthdr* hdr, u_int8_t level /* L2..L4, 5 (tunnel) */, u_int8_t add_timestamp /* 0,1 */, u_int8_t add_hash /* 0,1 */); #ifdef __cplusplus } #endif #endif fastnetmon-1.1.3+dfsg/src/fastnetmon_pcap_format.cpp000066400000000000000000000054251313534057500226510ustar00rootroot00000000000000#include "fastnetmon_pcap_format.h" #include #include int pcap_reader(const char* pcap_file_path, pcap_packet_parser_callback pcap_parse_packet_function_ptr) { int filedesc = open(pcap_file_path, O_RDONLY); if (filedesc <= 0) { printf("Can't open dump file, error: %s\n", strerror(errno)); return -1; } struct fastnetmon_pcap_file_header pcap_header; ssize_t file_header_readed_bytes = read(filedesc, &pcap_header, sizeof(struct fastnetmon_pcap_file_header)); if (file_header_readed_bytes != sizeof(struct fastnetmon_pcap_file_header)) { printf("Can't read pcap file header"); } // http://www.tcpdump.org/manpages/pcap-savefile.5.html if (pcap_header.magic == 0xa1b2c3d4 or pcap_header.magic == 0xd4c3b2a1) { // printf("Magic readed correctly\n"); } else { printf("Magic in file header broken\n"); return -2; } // Buffer for packets char packet_buffer[pcap_header.snaplen]; unsigned int read_packets = 0; while (1) { // printf("Start packet %d processing\n", read_packets); struct fastnetmon_pcap_pkthdr pcap_packet_header; ssize_t packet_header_readed_bytes = read(filedesc, &pcap_packet_header, sizeof(struct fastnetmon_pcap_pkthdr)); if (packet_header_readed_bytes != sizeof(struct fastnetmon_pcap_pkthdr)) { // We haven't any packets break; } if (pcap_packet_header.incl_len > pcap_header.snaplen) { printf("Please enlarge packet buffer! We got packet with size: %d but our buffer is %d " "bytes\n", pcap_packet_header.incl_len, pcap_header.snaplen); return -4; } ssize_t packet_payload_readed_bytes = read(filedesc, packet_buffer, pcap_packet_header.incl_len); if (pcap_packet_header.incl_len != packet_payload_readed_bytes) { printf("I read packet header but can't read packet payload\n"); return -3; } // printf("packet payload read\n"); pcap_parse_packet_function_ptr(packet_buffer, pcap_packet_header.orig_len, pcap_packet_header.incl_len); // printf("Process packet %d\n", read_packets); read_packets++; } printf("I correctly read %d packets from this dump\n", read_packets); return 0; } bool fill_pcap_header(struct fastnetmon_pcap_file_header* pcap_header, bpf_u_int32 snap_length) { pcap_header->magic = 0xa1b2c3d4; pcap_header->version_major = 2; pcap_header->version_minor = 4; pcap_header->thiszone = 0; pcap_header->sigfigs = 0; // TODO: fix this!!! pcap_header->snaplen = snap_length; // http://www.tcpdump.org/linktypes.html // DLT_EN10MB = 1 pcap_header->linktype = 1; return true; } fastnetmon-1.1.3+dfsg/src/fastnetmon_pcap_format.h000066400000000000000000000036471313534057500223220ustar00rootroot00000000000000#ifndef FASTNETMON_PCAP_FORMAT_H #define FASTNETMON_PCAP_FORMAT_H #include #include #include #include #include #include #include /* pcap dump format: global header: struct pcap_file_header packet header: struct fastnetmon_pcap_pkthdr */ /* * Compatibility for systems that have a bpf.h that * predates the bpf typedefs for 64-bit support. */ #if BPF_RELEASE - 0 < 199406 typedef int bpf_int32; typedef u_int bpf_u_int32; #endif // We use copy and paste from pcap.h here because we do not want to link with pcap here struct fastnetmon_pcap_file_header { bpf_u_int32 magic; u_short version_major; u_short version_minor; bpf_int32 thiszone; /* gmt to local correction */ bpf_u_int32 sigfigs; /* accuracy of timestamps */ bpf_u_int32 snaplen; /* max length saved portion of each pkt */ bpf_u_int32 linktype; /* data link type (LINKTYPE_*) */ }; /* TODO: move to this code, get rid any bpf* custom types struct fastnetmon_pcap_file_header { uint32_t magic; uint16_t version_major; uint16_t version_minor; int32_t thiszone; uint32_t sigfigs; uint32_t snaplen; uint32_t linktype; }; */ // We can't use pcap_pkthdr from upstream because it uses 16 bytes timeval instead of 8 byte and // broke everything struct fastnetmon_pcap_pkthdr { uint32_t ts_sec; /* timestamp seconds */ uint32_t ts_usec; /* timestamp microseconds */ uint32_t incl_len; /* number of octets of packet saved in file */ uint32_t orig_len; /* actual length of packet */ }; typedef void (*pcap_packet_parser_callback)(char* buffer, uint32_t len, uint32_t snaplen); int pcap_reader(const char* pcap_file_path, pcap_packet_parser_callback pcap_parse_packet_function_ptr); bool fill_pcap_header(struct fastnetmon_pcap_file_header* pcap_header, bpf_u_int32 snap_length); #endif fastnetmon-1.1.3+dfsg/src/fastnetmon_rc_freebsd000066400000000000000000000012661313534057500216720ustar00rootroot00000000000000#!/bin/sh # PROVIDE: fastnetmon # REQUIRE: NETWORKING SERVERS LOGIN # BEFORE: securelevel # KEYWORD: shutdown # Add the following line to /etc/rc.conf to enable `fastnetmon': # #fastnetmon_enable="YES" # . /etc/rc.subr name="fastnetmon" rcvar="${name}_enable" command="/usr/local/bin/fastnetmon" start_precmd="start_precmd" pidfile="/var/run/$name.pid" # read configuration and set defaults load_rc_config "$name" : ${fastnetmon_enable="NO"} : ${fastnetmon_user="fastnetmon"} #: ${fastnetmon_config="/usr/local/etc/$name.conf"} start_precmd() { echo -n "Starting fastnetmon deamon:" && \ su -m $fastnetmon_user -c "$command"; && \ echo . return 0 } run_rc_command "$1" fastnetmon-1.1.3+dfsg/src/fastnetmon_tests.cpp000066400000000000000000000422221313534057500215140ustar00rootroot00000000000000#include #include #include "fast_library.h" #include "bgp_flow_spec.h" #include #include "log4cpp/Category.hh" #include "log4cpp/Appender.hh" #include "log4cpp/FileAppender.hh" #include "log4cpp/OstreamAppender.hh" #include "log4cpp/Layout.hh" #include "log4cpp/BasicLayout.hh" #include "log4cpp/PatternLayout.hh" #include "log4cpp/Priority.hh" #include log4cpp::Category& logger = log4cpp::Category::getRoot(); TEST(BgpFlowSpec, protocol_check_udp) { exabgp_flow_spec_rule_t exabgp_rule; exabgp_rule.add_protocol(FLOW_SPEC_PROTOCOL_UDP); EXPECT_EQ(exabgp_rule.serialize_protocols(), "protocol [ udp ];"); } TEST(BgpFlowSpec, protocol_check_tcp) { exabgp_flow_spec_rule_t exabgp_rule; exabgp_rule.add_protocol(FLOW_SPEC_PROTOCOL_TCP); EXPECT_EQ(exabgp_rule.serialize_protocols(), "protocol [ tcp ];"); } TEST(BgpFlowSpec, protocol_check_icmp) { exabgp_flow_spec_rule_t exabgp_rule; exabgp_rule.add_protocol(FLOW_SPEC_PROTOCOL_ICMP); EXPECT_EQ(exabgp_rule.serialize_protocols(), "protocol [ icmp ];"); } TEST(BgpFlowSpec, protocol_check_mix) { exabgp_flow_spec_rule_t exabgp_rule; exabgp_rule.add_protocol(FLOW_SPEC_PROTOCOL_UDP); exabgp_rule.add_protocol(FLOW_SPEC_PROTOCOL_TCP); EXPECT_EQ(exabgp_rule.serialize_protocols(), "protocol [ udp tcp ];"); } TEST(BgpFlowSpec, packet_length) { exabgp_flow_spec_rule_t exabgp_rule; exabgp_rule.add_packet_length(777); exabgp_rule.add_packet_length(1122); EXPECT_EQ(exabgp_rule.serialize_packet_lengths(), "packet-length [ =777 =1122 ];"); } TEST(BgpFlowSpec, source_subnet) { exabgp_flow_spec_rule_t exabgp_rule; exabgp_rule.set_source_subnet( convert_subnet_from_string_to_binary_with_cidr_format("4.0.0.0/24") ); EXPECT_EQ(exabgp_rule.serialize_source_subnet(), "source 4.0.0.0/24;"); } TEST(BgpFlowSpec, destination_subnet) { exabgp_flow_spec_rule_t exabgp_rule; exabgp_rule.set_destination_subnet( convert_subnet_from_string_to_binary_with_cidr_format("77.0.0.0/24") ); EXPECT_EQ(exabgp_rule.serialize_destination_subnet(), "destination 77.0.0.0/24;"); } TEST(BgpFlowSpec, source_port) { exabgp_flow_spec_rule_t exabgp_rule; exabgp_rule.add_source_port(53); EXPECT_EQ(exabgp_rule.serialize_source_ports(), "source-port [ =53 ];"); } TEST(BgpFlowSpec, destaination_port) { exabgp_flow_spec_rule_t exabgp_rule; exabgp_rule.add_destination_port(53); EXPECT_EQ(exabgp_rule.serialize_destination_ports(), "destination-port [ =53 ];"); } TEST(BgpFlowSpec, source_ports) { exabgp_flow_spec_rule_t exabgp_rule; exabgp_rule.add_source_port(53); exabgp_rule.add_source_port(7777); EXPECT_EQ(exabgp_rule.serialize_source_ports(), "source-port [ =53 =7777 ];"); } TEST(BgpFlowSpec, destaination_ports) { exabgp_flow_spec_rule_t exabgp_rule; exabgp_rule.add_destination_port(53); exabgp_rule.add_destination_port(1900); EXPECT_EQ(exabgp_rule.serialize_destination_ports(), "destination-port [ =53 =1900 ];"); } TEST(BgpFlowSpec, fragmentation_is_fragment) { exabgp_flow_spec_rule_t exabgp_rule; exabgp_rule.add_fragmentation_flag(FLOW_SPEC_IS_A_FRAGMENT); EXPECT_EQ(exabgp_rule.serialize_fragmentation_flags(), "fragment [ is-fragment ];"); } TEST(BgpFlowSpec, fragmentation_first_fragment) { exabgp_flow_spec_rule_t exabgp_rule; exabgp_rule.add_fragmentation_flag(FLOW_SPEC_FIRST_FRAGMENT); EXPECT_EQ(exabgp_rule.serialize_fragmentation_flags(), "fragment [ first-fragment ];"); } TEST(BgpFlowSpec, fragmentation_dont_fragment) { exabgp_flow_spec_rule_t exabgp_rule; exabgp_rule.add_fragmentation_flag(FLOW_SPEC_DONT_FRAGMENT); EXPECT_EQ(exabgp_rule.serialize_fragmentation_flags(), "fragment [ dont-fragment ];"); } TEST(BgpFlowSpec, fragmentation_last_fragment) { exabgp_flow_spec_rule_t exabgp_rule; exabgp_rule.add_fragmentation_flag(FLOW_SPEC_LAST_FRAGMENT); EXPECT_EQ(exabgp_rule.serialize_fragmentation_flags(), "fragment [ last-fragment ];"); } TEST(BgpFlowSpec, fragmentation_not_a_fragment) { exabgp_flow_spec_rule_t exabgp_rule; exabgp_rule.add_fragmentation_flag(FLOW_NOT_A_FRAGMENT); EXPECT_EQ(exabgp_rule.serialize_fragmentation_flags(), "fragment [ not-a-fragment ];"); } TEST(BgpFlowSpec, fragmentation_fragments) { exabgp_flow_spec_rule_t exabgp_rule; exabgp_rule.add_fragmentation_flag(FLOW_NOT_A_FRAGMENT); EXPECT_EQ(exabgp_rule.serialize_fragmentation_flags(), "fragment [ not-a-fragment ];"); } // tcp flags tests TEST(BgpFlowSpec, syn) { exabgp_flow_spec_rule_t exabgp_rule; exabgp_rule.add_tcp_flag(FLOW_TCP_FLAG_SYN); EXPECT_EQ(exabgp_rule.serialize_tcp_flags(), "tcp-flags [ syn ];" ); } TEST(BgpFlowSpec, rst) { exabgp_flow_spec_rule_t exabgp_rule; exabgp_rule.add_tcp_flag(FLOW_TCP_FLAG_RST); EXPECT_EQ(exabgp_rule.serialize_tcp_flags(), "tcp-flags [ rst ];" ); } TEST(BgpFlowSpec, ack) { exabgp_flow_spec_rule_t exabgp_rule; exabgp_rule.add_tcp_flag(FLOW_TCP_FLAG_ACK); EXPECT_EQ(exabgp_rule.serialize_tcp_flags(), "tcp-flags [ ack ];" ); } TEST(BgpFlowSpec, fin) { exabgp_flow_spec_rule_t exabgp_rule; exabgp_rule.add_tcp_flag(FLOW_TCP_FLAG_FIN); EXPECT_EQ(exabgp_rule.serialize_tcp_flags(), "tcp-flags [ fin ];" ); } TEST(BgpFlowSpec, psh) { exabgp_flow_spec_rule_t exabgp_rule; exabgp_rule.add_tcp_flag(FLOW_TCP_FLAG_PSH); EXPECT_EQ(exabgp_rule.serialize_tcp_flags(), "tcp-flags [ push ];" ); } TEST(BgpFlowSpec, urg) { exabgp_flow_spec_rule_t exabgp_rule; exabgp_rule.add_tcp_flag(FLOW_TCP_FLAG_URG); EXPECT_EQ(exabgp_rule.serialize_tcp_flags(), "tcp-flags [ urgent ];" ); } TEST(BgpFlowSpec, serialize_match_first) { exabgp_flow_spec_rule_t exabgp_rule; exabgp_rule.add_protocol(FLOW_SPEC_PROTOCOL_UDP); exabgp_rule.add_source_port(53); exabgp_rule.add_destination_port(80); exabgp_rule.add_packet_length(777); exabgp_rule.add_packet_length(1122); exabgp_rule.add_fragmentation_flag(FLOW_SPEC_IS_A_FRAGMENT); exabgp_rule.add_fragmentation_flag(FLOW_SPEC_DONT_FRAGMENT); exabgp_rule.set_destination_subnet( convert_subnet_from_string_to_binary_with_cidr_format("127.0.0.0/24") ); exabgp_rule.set_source_subnet( convert_subnet_from_string_to_binary_with_cidr_format("4.0.0.0/24") ); // Disable indentation exabgp_rule.disable_indents(); EXPECT_EQ( exabgp_rule.serialize_match(), "match {source 4.0.0.0/24;destination 127.0.0.0/24;protocol [ udp ];source-port [ =53 ];destination-port [ =80 ];packet-length [ =777 =1122 ];fragment [ is-fragment dont-fragment ];}"); } TEST(BgpFlowSpec, serialize_then_first) { exabgp_flow_spec_rule_t exabgp_rule; bgp_flow_spec_action_t my_action; //my_action.set_type(FLOW_SPEC_ACTION_ACCEPT); my_action.set_type(FLOW_SPEC_ACTION_RATE_LIMIT); my_action.set_rate_limit(1024); exabgp_rule.set_action( my_action ); exabgp_rule.disable_indents(); EXPECT_EQ( exabgp_rule.serialize_then(), "then {rate-limit 1024;}"); } TEST(BgpFlowSpec, serialize_signle_line) { bgp_flow_spec_action_t my_action; //my_action.set_type(FLOW_SPEC_ACTION_ACCEPT); my_action.set_type(FLOW_SPEC_ACTION_RATE_LIMIT); my_action.set_rate_limit(1024); exabgp_flow_spec_rule_t exabgp_rule; exabgp_rule.add_protocol(FLOW_SPEC_PROTOCOL_UDP); exabgp_rule.add_source_port(53); exabgp_rule.add_destination_port(80); exabgp_rule.add_packet_length(777); exabgp_rule.add_packet_length(1122); exabgp_rule.add_fragmentation_flag(FLOW_SPEC_IS_A_FRAGMENT); exabgp_rule.add_fragmentation_flag(FLOW_SPEC_DONT_FRAGMENT); exabgp_rule.set_destination_subnet( convert_subnet_from_string_to_binary_with_cidr_format("127.0.0.0/24") ); exabgp_rule.set_source_subnet( convert_subnet_from_string_to_binary_with_cidr_format("4.0.0.0/24") ); exabgp_rule.set_action( my_action ); EXPECT_EQ( exabgp_rule.serialize_single_line_exabgp_v4_configuration(), "flow route source 4.0.0.0/24 destination 127.0.0.0/24 protocol [ udp ] source-port [ =53 ] destination-port [ =80 ] packet-length [ =777 =1122 ] fragment [ is-fragment dont-fragment ] rate-limit 1024 "); } TEST(BgpFlowSpec, serialize_whole_single_line_form) { bgp_flow_spec_action_t my_action; //my_action.set_type(FLOW_SPEC_ACTION_ACCEPT); my_action.set_type(FLOW_SPEC_ACTION_RATE_LIMIT); my_action.set_rate_limit(1024); exabgp_flow_spec_rule_t exabgp_rule; exabgp_rule.add_protocol(FLOW_SPEC_PROTOCOL_UDP); exabgp_rule.add_source_port(53); exabgp_rule.add_destination_port(80); exabgp_rule.add_packet_length(777); exabgp_rule.add_packet_length(1122); exabgp_rule.add_fragmentation_flag(FLOW_SPEC_IS_A_FRAGMENT); exabgp_rule.add_fragmentation_flag(FLOW_SPEC_DONT_FRAGMENT); exabgp_rule.set_destination_subnet( convert_subnet_from_string_to_binary_with_cidr_format("127.0.0.0/24") ); exabgp_rule.set_source_subnet( convert_subnet_from_string_to_binary_with_cidr_format("4.0.0.0/24") ); exabgp_rule.set_action( my_action ); // TBD } TEST(BgpFlowSpec, serialize_with_real_exabgp) { bgp_flow_spec_action_t my_action; //my_action.set_type(FLOW_SPEC_ACTION_ACCEPT); my_action.set_type(FLOW_SPEC_ACTION_RATE_LIMIT); my_action.set_rate_limit(1024); exabgp_flow_spec_rule_t exabgp_rule; exabgp_rule.add_protocol(FLOW_SPEC_PROTOCOL_UDP); exabgp_rule.add_source_port(53); exabgp_rule.add_destination_port(80); exabgp_rule.add_packet_length(777); exabgp_rule.add_packet_length(1122); exabgp_rule.add_fragmentation_flag(FLOW_SPEC_IS_A_FRAGMENT); exabgp_rule.add_fragmentation_flag(FLOW_SPEC_DONT_FRAGMENT); exabgp_rule.set_destination_subnet( convert_subnet_from_string_to_binary_with_cidr_format("127.0.0.0/24") ); exabgp_rule.set_source_subnet( convert_subnet_from_string_to_binary_with_cidr_format("4.0.0.0/24") ); exabgp_rule.set_action( my_action ); //exabgp_rule.disable_indents(); std::string exabgp_configuration = exabgp_rule.serialize_complete_exabgp_configuration(); std::ofstream config_file; config_file.open("/tmp/exabgp_test_config.conf", std::ios::trunc); if (config_file.is_open()) { config_file << exabgp_configuration; config_file.close(); } int system_ret_code = system("/usr/src/exabgp/sbin/exabgp --test /tmp/exabgp_test_config.conf 2>/dev/null"); EXPECT_EQ( system_ret_code, 0 ); } // Flow Spec actions tests TEST(BgpFlowSpecAction, rate_limit) { bgp_flow_spec_action_t my_action; my_action.set_type(FLOW_SPEC_ACTION_RATE_LIMIT); my_action.set_rate_limit(1024); EXPECT_EQ( my_action.serialize(), "rate-limit 1024;"); } TEST(BgpFlowSpecAction, discard) { bgp_flow_spec_action_t my_action; my_action.set_type(FLOW_SPEC_ACTION_DISCARD); EXPECT_EQ( my_action.serialize(), "discard;"); } TEST(BgpFlowSpecAction, accept) { bgp_flow_spec_action_t my_action; my_action.set_type(FLOW_SPEC_ACTION_ACCEPT); EXPECT_EQ( my_action.serialize(), "accept;"); } TEST(BgpFlowSpecAction, default_constructor) { bgp_flow_spec_action_t my_action; EXPECT_EQ( my_action.serialize(), "accept;"); } // Serializers tests TEST(serialize_vector_by_string, single_element) { std::vector vect; vect.push_back("123"); EXPECT_EQ( serialize_vector_by_string(vect, ","), "123"); } TEST(serialize_vector_by_string, few_elements) { std::vector vect; vect.push_back("123"); vect.push_back("456"); EXPECT_EQ( serialize_vector_by_string(vect, ","), "123,456"); } TEST(serialize_vector_by_string_with_prefix, single_element) { std::vector vect; vect.push_back(123); EXPECT_EQ( serialize_vector_by_string_with_prefix(vect, ",", "^"), "^123"); } TEST(serialize_vector_by_string_with_prefix, few_elements) { std::vector vect; vect.push_back(123); vect.push_back(456); EXPECT_EQ( serialize_vector_by_string_with_prefix(vect, ",", "^"), "^123,^456"); } /* Patricia tests */ TEST (patricia, negative_lookup_ipv6_prefix) { patricia_tree_t* lookup_ipv6_tree; lookup_ipv6_tree = New_Patricia(128); make_and_lookup_ipv6(lookup_ipv6_tree, (char*)"2a03:f480::/32"); //Destroy_Patricia(lookup_ipv6_tree, (void_fn_t)0); prefix_t prefix_for_check_address; // Convert fb.com frontend address to internal structure inet_pton(AF_INET6, "2a03:2880:2130:cf05:face:b00c::1", (void*)&prefix_for_check_address.add.sin6); prefix_for_check_address.family = AF_INET6; prefix_for_check_address.bitlen = 128; bool found = patricia_search_best2(lookup_ipv6_tree, &prefix_for_check_address, 1) != NULL; EXPECT_EQ( found, false ); } TEST (patricia, positive_lookup_ipv6_prefix) { patricia_tree_t* lookup_ipv6_tree; lookup_ipv6_tree = New_Patricia(128); make_and_lookup_ipv6(lookup_ipv6_tree, (char*)"2a03:f480::/32"); //Destroy_Patricia(lookup_ipv6_tree, (void_fn_t)0); prefix_t prefix_for_check_address; inet_pton(AF_INET6, "2a03:f480:2130:cf05:face:b00c::1", (void*)&prefix_for_check_address.add.sin6); prefix_for_check_address.family = AF_INET6; prefix_for_check_address.bitlen = 128; bool found = patricia_search_best2(lookup_ipv6_tree, &prefix_for_check_address, 1) != NULL; EXPECT_EQ( found, true ); } TEST (serialize_attack_description, blank_attack) { attack_details current_attack; std::string result = serialize_attack_description(current_attack); EXPECT_EQ( result, "Attack type: unknown\nInitial attack power: 0 packets per second\nPeak attack power: 0 packets per second\nAttack direction: other\nAttack protocol: unknown\nTotal incoming traffic: 0 mbps\nTotal outgoing traffic: 0 mbps\nTotal incoming pps: 0 packets per second\nTotal outgoing pps: 0 packets per second\nTotal incoming flows: 0 flows per second\nTotal outgoing flows: 0 flows per second\nAverage incoming traffic: 0 mbps\nAverage outgoing traffic: 0 mbps\nAverage incoming pps: 0 packets per second\nAverage outgoing pps: 0 packets per second\nAverage incoming flows: 0 flows per second\nAverage outgoing flows: 0 flows per second\nIncoming ip fragmented traffic: 0 mbps\nOutgoing ip fragmented traffic: 0 mbps\nIncoming ip fragmented pps: 0 packets per second\nOutgoing ip fragmented pps: 0 packets per second\nIncoming tcp traffic: 0 mbps\nOutgoing tcp traffic: 0 mbps\nIncoming tcp pps: 0 packets per second\nOutgoing tcp pps: 0 packets per second\nIncoming syn tcp traffic: 0 mbps\nOutgoing syn tcp traffic: 0 mbps\nIncoming syn tcp pps: 0 packets per second\nOutgoing syn tcp pps: 0 packets per second\nIncoming udp traffic: 0 mbps\nOutgoing udp traffic: 0 mbps\nIncoming udp pps: 0 packets per second\nOutgoing udp pps: 0 packets per second\nIncoming icmp traffic: 0 mbps\nOutgoing icmp traffic: 0 mbps\nIncoming icmp pps: 0 packets per second\nOutgoing icmp pps: 0 packets per second\n"); } TEST (serialize_attack_description_to_json, blank_attack) { attack_details current_attack; json_object * jobj = serialize_attack_description_to_json(current_attack); EXPECT_EQ( std::string(json_object_to_json_string(jobj)), "{ \"Attack type\": \"unknown\", \"Initial attack power\": 0, \"Peak attack power\": 0, \"Attack direction\": \"other\", \"Attack protocol\": \"unknown\", \"Total incoming traffic\": 0, \"Total outgoing traffic\": 0, \"Total incoming pps\": 0, \"Total outgoing pps\": 0, \"Total incoming flows\": 0, \"Total outgoing flows\": 0, \"Average incoming traffic\": 0, \"Average outgoing traffic\": 0, \"Average incoming pps\": 0, \"Average outgoing pps\": 0, \"Average incoming flows\": 0, \"Average outgoing flows\": 0, \"Incoming ip fragmented traffic\": 0, \"Outgoing ip fragmented traffic\": 0, \"Incoming ip fragmented pps\": 0, \"Outgoing ip fragmented pps\": 0, \"Incoming tcp traffic\": 0, \"Outgoing tcp traffic\": 0, \"Incoming tcp pps\": 0, \"Outgoing tcp pps\": 0, \"Incoming syn tcp traffic\": 0, \"Outgoing syn tcp traffic\": 0, \"Incoming syn tcp pps\": 0, \"Outgoing syn tcp pps\": 0, \"Incoming udp traffic\": 0, \"Outgoing udp traffic\": 0, \"Incoming udp pps\": 0, \"Outgoing udp pps\": 0, \"Incoming icmp traffic\": 0, \"Outgoing icmp traffic\": 0, \"Incoming icmp pps\": 0, \"Outgoing icmp pps\": 0 }"); } TEST (serialize_network_load_to_text, blank_attck_average) { map_element network_speed_meter; EXPECT_EQ( serialize_network_load_to_text(network_speed_meter, true), "Average network incoming traffic: 0 mbps\nAverage network outgoing traffic: 0 mbps\nAverage network incoming pps: 0 packets per second\nAverage network outgoing pps: 0 packets per second\n"); } TEST (serialize_network_load_to_text, blank_attck_absolute) { map_element network_speed_meter; EXPECT_EQ( serialize_network_load_to_text(network_speed_meter, false), "Network incoming traffic: 0 mbps\nNetwork outgoing traffic: 0 mbps\nNetwork incoming pps: 0 packets per second\nNetwork outgoing pps: 0 packets per second\n"); } TEST (serialize_network_load_to_json, blank_attack_average) { map_element network_speed_meter; json_object * jobj = serialize_network_load_to_json(network_speed_meter); EXPECT_EQ( std::string(json_object_to_json_string(jobj)), "{ \"incoming traffic\": 0, \"outgoing traffic\": 0, \"incoming pps\": 0, \"outgoing pps\": 0 }"); } fastnetmon-1.1.3+dfsg/src/fastnetmon_types.h000066400000000000000000000213721313534057500211660ustar00rootroot00000000000000#ifndef FASTNETMON_TYPES_H #define FASTNETMON_TYPES_H #include // std::pair #include // uint32_t #include // struct timeval #include // struct in6_addr #include #include #include #include "packet_storage.h" enum direction { INCOMING = 0, OUTGOING, INTERNAL, OTHER }; // simplified packet struct for lightweight save into memory class simple_packet { public: simple_packet() : sample_ratio(1), src_ip(0), dst_ip(0), source_port(0), destination_port(0), protocol(0), length(0), flags(0), number_of_packets(1), ip_fragmented(false), ip_protocol_version(4), ttl(0), packet_payload_pointer(NULL), packet_payload_length(0), packet_direction(OTHER) { ts.tv_usec = 0; ts.tv_sec = 0; } uint32_t sample_ratio; /* IPv4 */ uint32_t src_ip; uint32_t dst_ip; /* IPv6 */ struct in6_addr src_ipv6; struct in6_addr dst_ipv6; uint8_t ip_protocol_version; /* IPv4 or IPv6 */ uint8_t ttl; uint16_t source_port; uint16_t destination_port; unsigned int protocol; uint64_t length; uint64_t number_of_packets; /* for netflow */ uint8_t flags; /* tcp flags */ bool ip_fragmented; /* If IP packet fragmented */ struct timeval ts; void* packet_payload_pointer; int packet_payload_length; // We store packet direction here because direction calculation is very difficult task for cpu direction packet_direction; }; class logging_configuration_t { public: logging_configuration_t() : filesystem_logging(true), local_syslog_logging(false), remote_syslog_logging(false), remote_syslog_port(0) {} bool filesystem_logging; std::string filesystem_logging_path; bool local_syslog_logging; bool remote_syslog_logging; std::string remote_syslog_server; unsigned int remote_syslog_port; }; typedef std::pair subnet_t; typedef std::vector subnet_vector_t; typedef std::map subnet_to_host_group_map_t; typedef std::map host_group_map_t; typedef void (*process_packet_pointer)(simple_packet&); // Enum with available sort by field enum sort_type { PACKETS, BYTES, FLOWS }; // Attack types enum attack_type_t { ATTACK_UNKNOWN = 1, ATTACK_SYN_FLOOD = 2, ATTACK_ICMP_FLOOD = 3, ATTACK_UDP_FLOOD = 4, ATTACK_IP_FRAGMENTATION_FLOOD = 5, }; // Amplification types enum amplification_attack_type_t { AMPLIFICATION_ATTACK_UNKNOWN = 1, AMPLIFICATION_ATTACK_DNS = 2, AMPLIFICATION_ATTACK_NTP = 3, AMPLIFICATION_ATTACK_SSDP = 4, AMPLIFICATION_ATTACK_SNMP = 5, AMPLIFICATION_ATTACK_CHARGEN = 6, }; typedef struct { uint64_t bytes; uint64_t packets; uint64_t flows; } total_counter_element; // main data structure for storing traffic and speed data for all our IPs class map_element { public: map_element() : in_bytes(0), out_bytes(0), in_packets(0), out_packets(0), tcp_in_packets(0), tcp_out_packets(0), tcp_in_bytes(0), tcp_out_bytes(0), tcp_syn_in_packets(0), tcp_syn_out_packets(0), tcp_syn_in_bytes(0), tcp_syn_out_bytes(0), udp_in_packets(0), udp_out_packets(0), udp_in_bytes(0), udp_out_bytes(0), in_flows(0), out_flows(0), fragmented_in_packets(0), fragmented_out_packets(0), fragmented_in_bytes(0), fragmented_out_bytes(0), icmp_in_packets(0), icmp_out_packets(0), icmp_in_bytes(0), icmp_out_bytes(0) { } uint64_t in_bytes; uint64_t out_bytes; uint64_t in_packets; uint64_t out_packets; // Fragmented traffic is so recently used for attacks uint64_t fragmented_in_packets; uint64_t fragmented_out_packets; uint64_t fragmented_in_bytes; uint64_t fragmented_out_bytes; // Additional data for correct attack protocol detection uint64_t tcp_in_packets; uint64_t tcp_out_packets; uint64_t tcp_in_bytes; uint64_t tcp_out_bytes; // Additional details about one of most popular atatck type uint64_t tcp_syn_in_packets; uint64_t tcp_syn_out_packets; uint64_t tcp_syn_in_bytes; uint64_t tcp_syn_out_bytes; uint64_t udp_in_packets; uint64_t udp_out_packets; uint64_t udp_in_bytes; uint64_t udp_out_bytes; uint64_t icmp_in_packets; uint64_t icmp_out_packets; uint64_t icmp_in_bytes; uint64_t icmp_out_bytes; uint64_t in_flows; uint64_t out_flows; }; // structure with attack details class attack_details : public map_element { public: attack_details() : attack_protocol(0), attack_power(0), max_attack_power(0), average_in_bytes(0), average_out_bytes(0), average_in_packets(0), average_out_packets(0), average_in_flows(0), average_out_flows(0), ban_time(0), attack_direction(OTHER), unban_enabled(true) { customer_network.first = 0; customer_network.second = 0; } direction attack_direction; // first attackpower detected uint64_t attack_power; // max attack power uint64_t max_attack_power; unsigned int attack_protocol; // Average counters uint64_t average_in_bytes; uint64_t average_out_bytes; uint64_t average_in_packets; uint64_t average_out_packets; uint64_t average_in_flows; uint64_t average_out_flows; // time when we but this user time_t ban_timestamp; bool unban_enabled; int ban_time; // seconds of the ban subnet_t customer_network; packet_storage_t pcap_attack_dump; }; typedef attack_details banlist_item; // struct for save per direction and per protocol details for flow typedef struct { uint64_t bytes; uint64_t packets; // will be used for Garbage Collection time_t last_update_time; } conntrack_key_struct; typedef uint64_t packed_session; // Main mega structure for storing conntracks // We should use class instead struct for correct std::map allocation typedef std::map contrack_map_type; class conntrack_main_struct { public: contrack_map_type in_tcp; contrack_map_type in_udp; contrack_map_type in_icmp; contrack_map_type in_other; contrack_map_type out_tcp; contrack_map_type out_udp; contrack_map_type out_icmp; contrack_map_type out_other; }; typedef std::map map_for_counters; typedef std::vector vector_of_counters; typedef std::map map_of_vector_counters; // Flow tracking structures typedef std::vector vector_of_flow_counters; typedef std::map map_of_vector_counters_for_flow; typedef map_element subnet_counter_t; typedef std::pair pair_of_map_for_subnet_counters_elements_t; typedef std::map map_for_subnet_counters; class packed_conntrack_hash { public: packed_conntrack_hash() : opposite_ip(0), src_port(0), dst_port(0) { } // src or dst IP uint32_t opposite_ip; uint16_t src_port; uint16_t dst_port; }; // This class consists of all configuration of global or per subnet ban thresholds class ban_settings_t { public: ban_settings_t() : enable_ban(false), enable_ban_for_pps(false), enable_ban_for_bandwidth(false), enable_ban_for_flows_per_second(false), enable_ban_for_tcp_pps(false), enable_ban_for_tcp_bandwidth(false), enable_ban_for_udp_pps(false), enable_ban_for_udp_bandwidth(false), enable_ban_for_icmp_pps(false), enable_ban_for_icmp_bandwidth(false), ban_threshold_tcp_mbps(0), ban_threshold_tcp_pps(0), ban_threshold_udp_mbps(0), ban_threshold_udp_pps(0), ban_threshold_icmp_mbps(0), ban_threshold_icmp_pps(0), ban_threshold_mbps(0), ban_threshold_flows(0), ban_threshold_pps(0) { } bool enable_ban; bool enable_ban_for_pps; bool enable_ban_for_bandwidth; bool enable_ban_for_flows_per_second; bool enable_ban_for_tcp_pps; bool enable_ban_for_tcp_bandwidth; bool enable_ban_for_udp_pps; bool enable_ban_for_udp_bandwidth; bool enable_ban_for_icmp_pps; bool enable_ban_for_icmp_bandwidth; unsigned int ban_threshold_tcp_mbps; unsigned int ban_threshold_tcp_pps; unsigned int ban_threshold_udp_mbps; unsigned int ban_threshold_udp_pps; unsigned int ban_threshold_icmp_mbps; unsigned int ban_threshold_icmp_pps; unsigned int ban_threshold_mbps; unsigned int ban_threshold_flows; unsigned int ban_threshold_pps; }; typedef std::map host_group_ban_settings_map_t; // data structure for storing data in Vector typedef std::pair pair_of_map_elements; #endif fastnetmon-1.1.3+dfsg/src/ipfix_csv_processor.pl000077500000000000000000000124041313534057500220400ustar00rootroot00000000000000#!/usr/bin/perl use strict; use warnings; # This script can convert data from http://www.iana.org/assignments/ipfix/ipfix.xhtml ipfix standard # represented in CSV form into form suitable for us # http://www.iana.org/assignments/ipfix/ipfix-information-elements.csv # to our C/C++ frndly storage format open my $fl, "<", "ipfix_fields.csv" or die "Can't open input file"; open my $target_h_file, ">", "ipfix_rfc.h" or die "Can't open file"; open my $target_cpp_file, ">", "ipfix_rfc.cpp" or die "Can't open file"; my $type_length_hash = { unsigned8 => 1, unsigned16 => 2, unsigned32 => 4, unsigned64 => 8, ipv4Address => 4, ipv6Address => 16, # types with unknown or variable length basicList => 0, boolean => 0, dateTimeMicroseconds => 0, dateTimeMilliseconds => 0, dateTimeNanoseconds => 0, dateTimeSeconds => 0, float64 => 0, macAddress => 0, octetArray => 0, string => 0, subTemplateList => 0, subTemplateMultiList => 0, }; my $field_id = 0; my @intervals = (); my $fields = {}; while (<$fl>) { chomp; if (/^(\d+\-\d+)/) { push @intervals, $1; } my $we_will_process_this_line = /^(\d+),/; # Skip descriptions and other crap unless ($we_will_process_this_line) { next; } # Numbers should growth monotonous if ($1 < $field_id) { next; } $field_id++; my @keys = ("id", "name", "data_type", "data_type_semantics", "status", "description", "units", "range", "reference", "requester", "revision", "date"); my %data = (); @data{ @keys } = split /,/, $_; #print "$data{id} $data{name} $data{data_type}\n"; $fields->{$data{"id"}} = \%data; } for my $interval (@intervals) { my ($interval_start, $interval_end) = $interval =~ /^(\d+)\-(\d+)$/; # Skip reserved interval up to 32767 because it's so long next if $interval_start == 433; for my $field_id ($interval_start .. $interval_end) { $fields->{$field_id} = { id => $field_id, name => 'Reserved' }; } # print "Interval start $interval_start end $interval_end\n"; } for my $field_id (sort { $a <=> $b } keys %$fields) { my $data = $fields->{$field_id}; if ($data->{name} eq 'Reserved') { #print "$data->{id} $data->{name}\n"; } else { #print "$data->{id} $data->{name} $data->{data_type}\n"; } } print {$target_h_file} < #include class ipfix_information_element_t { public: ipfix_information_element_t(std::string name, unsigned int length); ipfix_information_element_t(); std::string get_name(); unsigned int get_length(); std::string name; unsigned int length; }; typedef std::map ipfix_database_t; class ipfix_information_database { public: ipfix_information_database(); bool add_element(unsigned int field_id, std::string name, unsigned int length); std::string get_name_by_id(unsigned int field_id); unsigned int get_length_by_id(unsigned int field_id); private: ipfix_database_t database; }; #endif DOC print {$target_cpp_file} < #include #include "ipfix_rfc.h" /* This file is autogenerated with script ipfix_csv_processor.pl */ /* Please do not edit it directly */ std::string ipfix_information_element_t::get_name() { return this->name; } unsigned int ipfix_information_element_t::get_length() { return this->length; } ipfix_information_element_t::ipfix_information_element_t(std::string name, unsigned int length) { this->name = name; this->length = length; } ipfix_information_element_t::ipfix_information_element_t() { this->name = std::string(""); this->length = 0; } std::string ipfix_information_database::get_name_by_id(unsigned int field_id) { ipfix_database_t::iterator itr = database.find(field_id); if (itr == database.end()) { return std::string(""); } return itr->second.get_name(); } unsigned int ipfix_information_database::get_length_by_id(unsigned int field_id) { ipfix_database_t::iterator itr = database.find(field_id); if (itr == database.end()) { return 0; } return itr->second.get_length(); } bool ipfix_information_database::add_element(unsigned int field_id, std::string name, unsigned int length) { ipfix_database_t::iterator itr = database.find(field_id); // Duplicate ID's strictly prohibited if (itr != database.end()) { return false; } database[field_id] = ipfix_information_element_t(name, length); return true; } ipfix_information_database::ipfix_information_database() { DOC for my $field_id (sort { $a <=> $b } keys %$fields) { my $data = $fields->{$field_id}; my $field_length = 0; my $field_name = '"reserved"'; if ($data->{"data_type"}) { $field_length = $type_length_hash->{ $data->{"data_type"} }; } if (defined($data->{"name"}) && $data->{"name"}) { $field_name = "\"$data->{'name'}\""; } print {$target_cpp_file} " this->add_element($field_id, $field_name, $field_length);\n"; } print {$target_cpp_file} "}\n"; fastnetmon-1.1.3+dfsg/src/ipfix_fields.csv000066400000000000000000005520661313534057500206060ustar00rootroot00000000000000ElementID,Name,Data Type,Data Type Semantics,Status,Description,Units,Range,References,Requester,Revision,Date 0,Reserved,,,,,,,,[RFC5102],,2013-02-18 1,octetDeltaCount,unsigned64,deltaCounter,current,"The number of octets since the previous report (if any) in incoming packets for this Flow at the Observation Point. The number of octets includes IP header(s) and IP payload.",octets,,,[RFC5102],0,2013-02-18 2,packetDeltaCount,unsigned64,deltaCounter,current,"The number of incoming packets since the previous report (if any) for this Flow at the Observation Point.",packets,,,[RFC5102],0,2013-02-18 3,deltaFlowCount,unsigned64,deltaCounter,current,"The conservative count of Original Flows contributing to this Aggregated Flow; may be distributed via any of the methods expressed by the valueDistributionMethod Information Element.",flows,,,[RFC7015],1,2013-06-25 4,protocolIdentifier,unsigned8,identifier,current,"The value of the protocol number in the IP packet header. The protocol number identifies the IP packet payload type. Protocol numbers are defined in the IANA Protocol Numbers registry. In Internet Protocol version 4 (IPv4), this is carried in the Protocol field. In Internet Protocol version 6 (IPv6), this is carried in the Next Header field in the last extension header of the packet.",,,"See [RFC791] for the specification of the IPv4 protocol field. See [RFC2460] for the specification of the IPv6 protocol field. See the list of protocol numbers assigned by IANA at [IANA registry protocol-numbers].",[RFC5102],0,2013-02-18 5,ipClassOfService,unsigned8,identifier,current,"For IPv4 packets, this is the value of the TOS field in the IPv4 packet header. For IPv6 packets, this is the value of the Traffic Class field in the IPv6 packet header.",,,"See [RFC1812] (Section 5.3.2) and [RFC791] for the definition of the IPv4 TOS field. See [RFC2460] for the definition of the IPv6 Traffic Class field.",[RFC5102],0,2013-02-18 6,tcpControlBits,unsigned16,flags,current,"TCP control bits observed for the packets of this Flow. This information is encoded as a bit field; for each TCP control bit, there is a bit in this set. The bit is set to 1 if any observed packet of this Flow has the corresponding TCP control bit set to 1. The bit is cleared to 0 otherwise. The values of each bit are shown below, per the definition of the bits in the TCP header [RFC793][RFC3168][RFC3540]: MSb LSb 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | | | N | C | E | U | A | P | R | S | F | | Zero | Future | S | W | C | R | C | S | S | Y | I | | (Data Offset) | Use | | R | E | G | K | H | T | N | N | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ bit flag value name description ------+-----+------------------------------------- 0x8000 Zero (see tcpHeaderLength) 0x4000 Zero (see tcpHeaderLength) 0x2000 Zero (see tcpHeaderLength) 0x1000 Zero (see tcpHeaderLength) 0x0800 Future Use 0x0400 Future Use 0x0200 Future Use 0x0100 NS ECN Nonce Sum 0x0080 CWR Congestion Window Reduced 0x0040 ECE ECN Echo 0x0020 URG Urgent Pointer field significant 0x0010 ACK Acknowledgment field significant 0x0008 PSH Push Function 0x0004 RST Reset the connection 0x0002 SYN Synchronize sequence numbers 0x0001 FIN No more data from sender As the most significant 4 bits of octets 12 and 13 (counting from zero) of the TCP header [RFC793] are used to encode the TCP data offset (header length), the corresponding bits in this Information Element MUST be exported as zero and MUST be ignored by the collector. Use the tcpHeaderLength Information Element to encode this value. Each of the 3 bits (0x800, 0x400, and 0x200), which are reserved for future use in [RFC793], SHOULD be exported as observed in the TCP headers of the packets of this Flow. If exported as a single octet with reduced-size encoding, this Information Element covers the low-order octet of this field (i.e, bits 0x80 to 0x01), omitting the ECN Nonce Sum and the three Future Use bits. A collector receiving this Information Element with reduced-size encoding must not assume anything about the content of these four bits. Exporting Processes exporting this Information Element on behalf of a Metering Process that is not capable of observing any of the ECN Nonce Sum or Future Use bits SHOULD use reduced-size encoding, and only export the least significant 8 bits of this Information Element. Note that previous revisions of this Information Element's definition specified that the CWR and ECE bits must be exported as zero, even if observed. Collectors should therefore not assume that a value of zero for these bits in this Information Element indicates the bits were never set in the observed traffic, especially if these bits are zero in every Flow Record sent by a given exporter.",,,[RFC793][RFC3168][RFC3540],[RFC7125],1,2014-01-03 7,sourceTransportPort,unsigned16,identifier,current,"The source port identifier in the transport header. For the transport protocols UDP, TCP, and SCTP, this is the source port number given in the respective header. This field MAY also be used for future transport protocols that have 16-bit source port identifiers.",,,"See [RFC768] for the definition of the UDP source port field. See [RFC793] for the definition of the TCP source port field. See [RFC4960] for the definition of SCTP. Additional information on defined UDP and TCP port numbers can be found at [IANA registry service-names-port-numbers].",[RFC5102],0,2013-02-18 8,sourceIPv4Address,ipv4Address,default,current,The IPv4 source address in the IP packet header.,,,"See [RFC791] for the definition of the IPv4 source address field.",[RFC5102],1,2014-02-03 9,sourceIPv4PrefixLength,unsigned8,,current,"The number of contiguous bits that are relevant in the sourceIPv4Prefix Information Element.",bits,0-32,,[RFC5102],0,2013-02-18 10,ingressInterface,unsigned32,identifier,current,"The index of the IP interface where packets of this Flow are being received. The value matches the value of managed object 'ifIndex' as defined in [RFC2863]. Note that ifIndex values are not assigned statically to an interface and that the interfaces may be renumbered every time the device's management system is re-initialized, as specified in [RFC2863].",,,"See [RFC2863] for the definition of the ifIndex object.",[RFC5102],0,2013-02-18 11,destinationTransportPort,unsigned16,identifier,current,"The destination port identifier in the transport header. For the transport protocols UDP, TCP, and SCTP, this is the destination port number given in the respective header. This field MAY also be used for future transport protocols that have 16-bit destination port identifiers.",,,"See [RFC768] for the definition of the UDP destination port field. See [RFC793] for the definition of the TCP destination port field. See [RFC4960] for the definition of SCTP. Additional information on defined UDP and TCP port numbers can be found at [IANA registry service-names-port-numbers].",[RFC5102],0,2013-02-18 12,destinationIPv4Address,ipv4Address,default,current,The IPv4 destination address in the IP packet header.,,,"See [RFC791] for the definition of the IPv4 destination address field.",[RFC5102],1,2014-02-03 13,destinationIPv4PrefixLength,unsigned8,,current,"The number of contiguous bits that are relevant in the destinationIPv4Prefix Information Element.",bits,0-32,,[RFC5102],0,2013-02-18 14,egressInterface,unsigned32,identifier,current,"The index of the IP interface where packets of this Flow are being sent. The value matches the value of managed object 'ifIndex' as defined in [RFC2863]. Note that ifIndex values are not assigned statically to an interface and that the interfaces may be renumbered every time the device's management system is re-initialized, as specified in [RFC2863].",,,"See [RFC2863] for the definition of the ifIndex object.",[RFC5102],0,2013-02-18 15,ipNextHopIPv4Address,ipv4Address,default,current,The IPv4 address of the next IPv4 hop.,,,,[RFC5102],1,2014-02-03 16,bgpSourceAsNumber,unsigned32,identifier,current,"The autonomous system (AS) number of the source IP address. If AS path information for this Flow is only available as an unordered AS set (and not as an ordered AS sequence), then the value of this Information Element is 0.",,,"See [RFC4271] for a description of BGP-4, and see [RFC1930] for the definition of the AS number.",[RFC5102],0,2013-02-18 17,bgpDestinationAsNumber,unsigned32,identifier,current,"The autonomous system (AS) number of the destination IP address. If AS path information for this Flow is only available as an unordered AS set (and not as an ordered AS sequence), then the value of this Information Element is 0.",,,"See [RFC4271] for a description of BGP-4, and see [RFC1930] for the definition of the AS number.",[RFC5102],0,2013-02-18 18,bgpNextHopIPv4Address,ipv4Address,default,current,The IPv4 address of the next (adjacent) BGP hop.,,,See [RFC4271] for a description of BGP-4.,[RFC5102],1,2014-02-03 19,postMCastPacketDeltaCount,unsigned64,deltaCounter,current,"The number of outgoing multicast packets since the previous report (if any) sent for packets of this Flow by a multicast daemon within the Observation Domain. This property cannot necessarily be observed at the Observation Point, but may be retrieved by other means.",packets,,,[RFC5102],0,2013-02-18 20,postMCastOctetDeltaCount,unsigned64,deltaCounter,current,"The number of octets since the previous report (if any) in outgoing multicast packets sent for packets of this Flow by a multicast daemon within the Observation Domain. This property cannot necessarily be observed at the Observation Point, but may be retrieved by other means. The number of octets includes IP header(s) and IP payload.",octets,,,[RFC5102],0,2013-02-18 21,flowEndSysUpTime,unsigned32,,current,"The relative timestamp of the last packet of this Flow. It indicates the number of milliseconds since the last (re-)initialization of the IPFIX Device (sysUpTime). sysUpTime can be calculated from systemInitTimeMilliseconds.",milliseconds,,,[RFC5102],1,2014-01-11 22,flowStartSysUpTime,unsigned32,,current,"The relative timestamp of the first packet of this Flow. It indicates the number of milliseconds since the last (re-)initialization of the IPFIX Device (sysUpTime). sysUpTime can be calculated from systemInitTimeMilliseconds.",milliseconds,,,[RFC5102],1,2014-01-11 23,postOctetDeltaCount,unsigned64,deltaCounter,current,"The definition of this Information Element is identical to the definition of Information Element 'octetDeltaCount', except that it reports a potentially modified value caused by a middlebox function after the packet passed the Observation Point.",octets,,,[RFC5102],0,2013-02-18 24,postPacketDeltaCount,unsigned64,deltaCounter,current,"The definition of this Information Element is identical to the definition of Information Element 'packetDeltaCount', except that it reports a potentially modified value caused by a middlebox function after the packet passed the Observation Point.",packets,,,[RFC5102],0,2013-02-18 25,minimumIpTotalLength,unsigned64,,current,"Length of the smallest packet observed for this Flow. The packet length includes the IP header(s) length and the IP payload length.",octets,,"See [RFC791] for the specification of the IPv4 total length. See [RFC2460] for the specification of the IPv6 payload length. See [RFC2675] for the specification of the IPv6 jumbo payload length.",[RFC5102],0,2013-02-18 26,maximumIpTotalLength,unsigned64,,current,"Length of the largest packet observed for this Flow. The packet length includes the IP header(s) length and the IP payload length.",octets,,"See [RFC791] for the specification of the IPv4 total length. See [RFC2460] for the specification of the IPv6 payload length. See [RFC2675] for the specification of the IPv6 jumbo payload length.",[RFC5102],0,2013-02-18 27,sourceIPv6Address,ipv6Address,default,current,The IPv6 source address in the IP packet header.,,,"See [RFC2460] for the definition of the Source Address field in the IPv6 header.",[RFC5102],1,2014-02-03 28,destinationIPv6Address,ipv6Address,default,current,The IPv6 destination address in the IP packet header.,,,"See [RFC2460] for the definition of the Destination Address field in the IPv6 header.",[RFC5102],1,2014-02-03 29,sourceIPv6PrefixLength,unsigned8,,current,"The number of contiguous bits that are relevant in the sourceIPv6Prefix Information Element.",bits,0-128,,[RFC5102],0,2013-02-18 30,destinationIPv6PrefixLength,unsigned8,,current,"The number of contiguous bits that are relevant in the destinationIPv6Prefix Information Element.",bits,0-128,,[RFC5102],0,2013-02-18 31,flowLabelIPv6,unsigned32,identifier,current,The value of the IPv6 Flow Label field in the IP packet header.,,0-0xFFFFF,"See [RFC2460] for the definition of the Flow Label field in the IPv6 packet header.",[RFC5102],1,2014-08-13 32,icmpTypeCodeIPv4,unsigned16,identifier,current,"Type and Code of the IPv4 ICMP message. The combination of both values is reported as (ICMP type * 256) + ICMP code.",,,"See [RFC792] for the definition of the IPv4 ICMP type and code fields.",[RFC5102],0,2013-02-18 33,igmpType,unsigned8,identifier,current,The type field of the IGMP message.,,,"See [RFC3376] for the definition of the IGMP type field.",[RFC5102],0,2013-02-18 34,samplingInterval,unsigned32,quantity,deprecated,"Deprecated in favor of 305 samplingPacketInterval. When using sampled NetFlow, the rate at which packets are sampled -- e.g., a value of 100 indicates that one of every 100 packets is sampled.",packets,,,[RFC7270],0,2014-04-04 35,samplingAlgorithm,unsigned8,identifier,deprecated,"Deprecated in favor of 304 selectorAlgorithm. The type of algorithm used for sampled NetFlow: 1 - Deterministic Sampling, 2 - Random Sampling. The values are not compatible with the selectorAlgorithm IE, where ""Deterministic"" has been replaced by ""Systematic count-based"" (1) or ""Systematic time-based"" (2), and ""Random"" is (3). Conversion is required; see [Packet Sampling (PSAMP) Parameters.]",,,,[RFC7270],0,2014-04-04 36,flowActiveTimeout,unsigned16,,current,"The number of seconds after which an active Flow is timed out anyway, even if there is still a continuous flow of packets.",seconds,,,[RFC5102],0,2013-02-18 37,flowIdleTimeout,unsigned16,,current,"A Flow is considered to be timed out if no packets belonging to the Flow have been observed for the number of seconds specified by this field.",seconds,,,[RFC5102],0,2013-02-18 38,engineType,unsigned8,identifier,deprecated,"Type of flow switching engine in a router/switch: RP = 0, VIP/Line card = 1, PFC/DFC = 2. Reserved for internal use on the Collector.",,,,[RFC7270],0,2014-04-04 39,engineId,unsigned8,identifier,deprecated,"Versatile Interface Processor (VIP) or line card slot number of the flow switching engine in a router/switch. Reserved for internal use on the Collector.",,,,[RFC7270],0,2014-04-04 40,exportedOctetTotalCount,unsigned64,totalCounter,current,"The total number of octets that the Exporting Process has sent since the Exporting Process (re-)initialization to a particular Collecting Process. The value of this Information Element is calculated by summing up the IPFIX Message Header length values of all IPFIX Messages that were successfully sent to the Collecting Process. The reported number excludes octets in the IPFIX Message that carries the counter value. If this Information Element is sent to a particular Collecting Process, then by default it specifies the number of octets sent to this Collecting Process.",octets,,,[RFC5102],0,2013-02-18 41,exportedMessageTotalCount,unsigned64,totalCounter,current,"The total number of IPFIX Messages that the Exporting Process has sent since the Exporting Process (re-)initialization to a particular Collecting Process. The reported number excludes the IPFIX Message that carries the counter value. If this Information Element is sent to a particular Collecting Process, then by default it specifies the number of IPFIX Messages sent to this Collecting Process.",messages,,,[RFC5102],0,2013-02-18 42,exportedFlowRecordTotalCount,unsigned64,totalCounter,current,"The total number of Flow Records that the Exporting Process has sent as Data Records since the Exporting Process (re-)initialization to a particular Collecting Process. The reported number excludes Flow Records in the IPFIX Message that carries the counter value. If this Information Element is sent to a particular Collecting Process, then by default it specifies the number of Flow Records sent to this process.",flows,,,[RFC5102],0,2013-02-18 43,ipv4RouterSc,ipv4Address,default,deprecated,"This is a platform-specific field for the Catalyst 5000/Catalyst 6000 family. It is used to store the address of a router that is being shortcut when performing MultiLayer Switching.",,,[CCO-MLS] describes MultiLayer Switching.,[RFC7270],0,2014-04-04 44,sourceIPv4Prefix,ipv4Address,default,current,IPv4 source address prefix.,,,,[RFC5102],0,2013-02-18 45,destinationIPv4Prefix,ipv4Address,default,current,IPv4 destination address prefix.,,,,[RFC5102],0,2013-02-18 46,mplsTopLabelType,unsigned8,identifier,current,"This field identifies the control protocol that allocated the top-of-stack label. Values for this field are listed in the MPLS label type registry. See [http://www.iana.org/assignments/ipfix/ipfix.xml#ipfix-mpls-label-type]",,,"See [RFC3031] for the MPLS label structure. See [RFC4364] for the association of MPLS labels with Virtual Private Networks (VPNs). See [RFC4271] for BGP and BGP routing. See [RFC5036] for Label Distribution Protocol (LDP). See the list of MPLS label types assigned by IANA at [IANA registry mpls-label-values].",[RFC5102],0,2013-02-18 47,mplsTopLabelIPv4Address,ipv4Address,default,current,"The IPv4 address of the system that the MPLS top label will cause this Flow to be forwarded to.",,,"See [RFC3031] for the association between MPLS labels and IP addresses.",[RFC5102],1,2014-02-03 48,samplerId,unsigned8,identifier,deprecated,"Deprecated in favor of 302 selectorId. The unique identifier associated with samplerName.",,,,[RFC7270],0,2014-04-04 49,samplerMode,unsigned8,identifier,deprecated,"Deprecated in favor of 304 selectorAlgorithm. The values are not compatible: selectorAlgorithm=3 is random sampling. The type of algorithm used for sampling data: 1 - Deterministic, 2 - Random Sampling. Use with samplerRandomInterval.",,,,[RFC7270],0,2014-04-04 50,samplerRandomInterval,unsigned32,quantity,deprecated,"Deprecated in favor of 305 samplingPacketInterval. Packet interval at which to sample -- in case of random sampling. Used in connection with the samplerMode 0x02 (random sampling) value.",,,,[RFC7270],0,2014-04-04 51,classId,unsigned8,identifier,deprecated,"Deprecated in favor of 302 selectorId. Characterizes the traffic class, i.e., QoS treatment.",,,,[RFC7270],0,2014-04-04 52,minimumTTL,unsigned8,,current,Minimum TTL value observed for any packet in this Flow.,hops,,"See [RFC791] for the definition of the IPv4 Time to Live field. See [RFC2460] for the definition of the IPv6 Hop Limit field.",[RFC5102],0,2013-02-18 53,maximumTTL,unsigned8,,current,Maximum TTL value observed for any packet in this Flow.,hops,,"See [RFC791] for the definition of the IPv4 Time to Live field. See [RFC2460] for the definition of the IPv6 Hop Limit field.",[RFC5102],0,2013-02-18 54,fragmentIdentification,unsigned32,identifier,current,"The value of the Identification field in the IPv4 packet header or in the IPv6 Fragment header, respectively. The value is 0 for IPv6 if there is no fragment header.",,,"See [RFC791] for the definition of the IPv4 Identification field. See [RFC2460] for the definition of the Identification field in the IPv6 Fragment header.",[RFC5102],0,2013-02-18 55,postIpClassOfService,unsigned8,identifier,current,"The definition of this Information Element is identical to the definition of Information Element 'ipClassOfService', except that it reports a potentially modified value caused by a middlebox function after the packet passed the Observation Point.",,,"See [RFC791] for the definition of the IPv4 TOS field. See [RFC2460] for the definition of the IPv6 Traffic Class field. See [RFC3234] for the definition of middleboxes.",[RFC5102],0,2013-02-18 56,sourceMacAddress,macAddress,default,current,The IEEE 802 source MAC address field.,,,See IEEE.802-3.2002.,[RFC5102],1,2014-02-03 57,postDestinationMacAddress,macAddress,default,current,"The definition of this Information Element is identical to the definition of Information Element 'destinationMacAddress', except that it reports a potentially modified value caused by a middlebox function after the packet passed the Observation Point.",,,See IEEE.802-3.2002.,[RFC5102],1,2014-02-03 58,vlanId,unsigned16,identifier,current,"Virtual LAN identifier associated with ingress interface. For dot1q vlans, see 243 dot1qVlanId.",,,See IEEE.802-1Q.2003.,[RFC5102],0,2013-02-18 59,postVlanId,unsigned16,identifier,current,"Virtual LAN identifier associated with egress interface. For postdot1q vlans, see 254, postDot1qVlanId.",,,See IEEE.802-1Q.2003.,[RFC5102],0,2013-02-18 60,ipVersion,unsigned8,identifier,current,The IP version field in the IP packet header.,,,"See [RFC791] for the definition of the version field in the IPv4 packet header. See [RFC2460] for the definition of the version field in the IPv6 packet header. Additional information on defined version numbers can be found at [IANA registry version-numbers].",[RFC5102],0,2013-02-18 61,flowDirection,unsigned8,identifier,current,"The direction of the Flow observed at the Observation Point. There are only two values defined. 0x00: ingress flow 0x01: egress flow",,,,[RFC5102],0,2013-02-18 62,ipNextHopIPv6Address,ipv6Address,default,current,The IPv6 address of the next IPv6 hop.,,,,[RFC5102],1,2014-02-03 63,bgpNextHopIPv6Address,ipv6Address,default,current,The IPv6 address of the next (adjacent) BGP hop.,,,See [RFC4271] for a description of BGP-4.,[RFC5102],1,2014-02-03 64,ipv6ExtensionHeaders,unsigned32,flags,current,"IPv6 extension headers observed in packets of this Flow. The information is encoded in a set of bit fields. For each IPv6 option header, there is a bit in this set. The bit is set to 1 if any observed packet of this Flow contains the corresponding IPv6 extension header. Otherwise, if no observed packet of this Flow contained the respective IPv6 extension header, the value of the corresponding bit is 0. 0 1 2 3 4 5 6 7 +-----+-----+-----+-----+-----+-----+-----+-----+ | DST | HOP | Res | UNK |FRA0 | RH |FRA1 | Res | ... +-----+-----+-----+-----+-----+-----+-----+-----+ 8 9 10 11 12 13 14 15 +-----+-----+-----+-----+-----+-----+-----+-----+ ... | Reserved | MOB | ESP | AH | PAY | ... +-----+-----+-----+-----+-----+-----+-----+-----+ 16 17 18 19 20 21 22 23 +-----+-----+-----+-----+-----+-----+-----+-----+ ... | Reserved | ... +-----+-----+-----+-----+-----+-----+-----+-----+ 24 25 26 27 28 29 30 31 +-----+-----+-----+-----+-----+-----+-----+-----+ ... | Reserved | +-----+-----+-----+-----+-----+-----+-----+-----+ Bit IPv6 Option Description 0, DST 60 Destination option header 1, HOP 0 Hop-by-hop option header 2, Res Reserved 3, UNK Unknown Layer 4 header (compressed, encrypted, not supported) 4, FRA0 44 Fragment header - first fragment 5, RH 43 Routing header 6, FRA1 44 Fragmentation header - not first fragment 7, Res Reserved 8 to 11 Reserved 12, MOB 135 IPv6 mobility [RFC3775] 13, ESP 50 Encrypted security payload 14, AH 51 Authentication Header 15, PAY 108 Payload compression header 16 to 31 Reserved",,,"See [RFC2460] for the general definition of IPv6 extension headers and for the specification of the hop-by-hop options header, the routing header, the fragment header, and the destination options header. See [RFC4302] for the specification of the authentication header. See [RFC4303] for the specification of the encapsulating security payload. The diagram provided in [RFC5102] is incorrect. The diagram in this registry is taken from Errata 1738. See [RFC Errata 1738]",[RFC5102],0,2013-02-18 65-69,Assigned for NetFlow v9 compatibility,,,,,,,[RFC3954],[RFC5102],0,2013-02-18 70,mplsTopLabelStackSection,octetArray,default,current,"The Label, Exp, and S fields from the top MPLS label stack entry, i.e., from the last label that was pushed. The size of this Information Element is 3 octets. 0 1 2 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Label | Exp |S| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Label: Label Value, 20 bits Exp: Experimental Use, 3 bits S: Bottom of Stack, 1 bit",,,See [RFC3032].,[RFC5102],1,2014-02-03 71,mplsLabelStackSection2,octetArray,default,current,"The Label, Exp, and S fields from the label stack entry that was pushed immediately before the label stack entry that would be reported by mplsTopLabelStackSection. See the definition of mplsTopLabelStackSection for further details. The size of this Information Element is 3 octets.",,,See [RFC3032].,[RFC5102],1,2014-02-03 72,mplsLabelStackSection3,octetArray,default,current,"The Label, Exp, and S fields from the label stack entry that was pushed immediately before the label stack entry that would be reported by mplsLabelStackSection2. See the definition of mplsTopLabelStackSection for further details. The size of this Information Element is 3 octets.",,,See [RFC3032].,[RFC5102],1,2014-02-03 73,mplsLabelStackSection4,octetArray,default,current,"The Label, Exp, and S fields from the label stack entry that was pushed immediately before the label stack entry that would be reported by mplsLabelStackSection3. See the definition of mplsTopLabelStackSection for further details. The size of this Information Element is 3 octets.",,,See [RFC3032].,[RFC5102],1,2014-02-03 74,mplsLabelStackSection5,octetArray,default,current,"The Label, Exp, and S fields from the label stack entry that was pushed immediately before the label stack entry that would be reported by mplsLabelStackSection4. See the definition of mplsTopLabelStackSection for further details. The size of this Information Element is 3 octets.",,,See [RFC3032].,[RFC5102],1,2014-02-03 75,mplsLabelStackSection6,octetArray,default,current,"The Label, Exp, and S fields from the label stack entry that was pushed immediately before the label stack entry that would be reported by mplsLabelStackSection5. See the definition of mplsTopLabelStackSection for further details. The size of this Information Element is 3 octets.",,,See [RFC3032].,[RFC5102],1,2014-02-03 76,mplsLabelStackSection7,octetArray,default,current,"The Label, Exp, and S fields from the label stack entry that was pushed immediately before the label stack entry that would be reported by mplsLabelStackSection6. See the definition of mplsTopLabelStackSection for further details. The size of this Information Element is 3 octets.",,,See [RFC3032].,[RFC5102],1,2014-02-03 77,mplsLabelStackSection8,octetArray,default,current,"The Label, Exp, and S fields from the label stack entry that was pushed immediately before the label stack entry that would be reported by mplsLabelStackSection7. See the definition of mplsTopLabelStackSection for further details. The size of this Information Element is 3 octets.",,,See [RFC3032].,[RFC5102],1,2014-02-03 78,mplsLabelStackSection9,octetArray,default,current,"The Label, Exp, and S fields from the label stack entry that was pushed immediately before the label stack entry that would be reported by mplsLabelStackSection8. See the definition of mplsTopLabelStackSection for further details. The size of this Information Element is 3 octets.",,,See [RFC3032].,[RFC5102],1,2014-02-03 79,mplsLabelStackSection10,octetArray,default,current,"The Label, Exp, and S fields from the label stack entry that was pushed immediately before the label stack entry that would be reported by mplsLabelStackSection9. See the definition of mplsTopLabelStackSection for further details. The size of this Information Element is 3 octets.",,,See [RFC3032].,[RFC5102],1,2014-02-03 80,destinationMacAddress,macAddress,default,current,The IEEE 802 destination MAC address field.,,,See IEEE.802-3.2002.,[RFC5102],1,2014-02-03 81,postSourceMacAddress,macAddress,default,current,"The definition of this Information Element is identical to the definition of Information Element 'sourceMacAddress', except that it reports a potentially modified value caused by a middlebox function after the packet passed the Observation Point.",,,See IEEE.802-3.2002.,[RFC5102],1,2014-02-03 82,interfaceName,string,default,current,"A short name uniquely describing an interface, eg ""Eth1/0"".",,,See [RFC2863] for the definition of the ifName object.,[ipfix-iana_at_cisco.com],0,2013-02-18 83,interfaceDescription,string,default,current,"The description of an interface, eg ""FastEthernet 1/0"" or ""ISP connection"".",,,See [RFC2863] for the definition of the ifDescr object.,[ipfix-iana_at_cisco.com],0,2013-02-18 84,samplerName,string,,deprecated,"Deprecated in favor of 335 selectorName. Name of the flow sampler.",,,,[RFC7270],0,2014-04-04 85,octetTotalCount,unsigned64,totalCounter,current,"The total number of octets in incoming packets for this Flow at the Observation Point since the Metering Process (re-)initialization for this Observation Point. The number of octets includes IP header(s) and IP payload.",octets,,,[RFC5102],0,2013-02-18 86,packetTotalCount,unsigned64,totalCounter,current,"The total number of incoming packets for this Flow at the Observation Point since the Metering Process (re-)initialization for this Observation Point.",packets,,,[RFC5102],0,2013-02-18 87,flagsAndSamplerId,unsigned32,identifier,deprecated,"Flow flags and the value of the sampler ID (samplerId) combined in one bitmapped field. Reserved for internal use on the Collector.",,,,[RFC7270],0,2014-04-04 88,fragmentOffset,unsigned16,quantity,current,"The value of the IP fragment offset field in the IPv4 packet header or the IPv6 Fragment header, respectively. The value is 0 for IPv6 if there is no fragment header.",,0-0x1FFF,"See [RFC791] for the specification of the fragment offset in the IPv4 header. See [RFC2460] for the specification of the fragment offset in the IPv6 Fragment header.",[RFC5102],1,2014-08-13 89,forwardingStatus,unsigned32,identifier,current,"This Information Element describes the forwarding status of the flow and any attached reasons. The reduced-size encoding rules as per [RFC7011] apply. The basic encoding is 8 bits. The future extensions could add one or three bytes. The layout of the basic encoding is as follows: MSB - 0 1 2 3 4 5 6 7 - LSB +---+---+---+---+---+---+---+---+ | Status| Reason code or flags | +---+---+---+---+---+---+---+---+ Status: 00b = Unknown 01b = Forwarded 10b = Dropped 11b = Consumed Reason Code (status = 01b, Forwarded) 01 000000b = 64 = Unknown 01 000001b = 65 = Fragmented 01 000010b = 66 = Not Fragmented Reason Code (status = 10b, Dropped) 10 000000b = 128 = Unknown 10 000001b = 129 = ACL deny 10 000010b = 130 = ACL drop 10 000011b = 131 = Unroutable 10 000100b = 132 = Adjacency 10 000101b = 133 = Fragmentation and DF set 10 000110b = 134 = Bad header checksum 10 000111b = 135 = Bad total Length 10 001000b = 136 = Bad header length 10 001001b = 137 = bad TTL 10 001010b = 138 = Policer 10 001011b = 139 = WRED 10 001100b = 140 = RPF 10 001101b = 141 = For us 10 001110b = 142 = Bad output interface 10 001111b = 143 = Hardware Reason Code (status = 11b, Consumed) 11 000000b = 192 = Unknown 11 000001b = 193 = Punt Adjacency 11 000010b = 194 = Incomplete Adjacency 11 000011b = 195 = For us Examples: value : 0x40 = 64 binary: 01000000 decode: 01 -> Forward 000000 -> No further information value : 0x89 = 137 binary: 10001001 decode: 10 -> Drop 001001 -> Fragmentation and DF set",,,"See ""NetFlow Version 9 Flow-Record Format"" [CCO-NF9FMT].",[RFC7270],0,2014-04-04 90,mplsVpnRouteDistinguisher,octetArray,default,current,"The value of the VPN route distinguisher of a corresponding entry in a VPN routing and forwarding table. Route distinguisher ensures that the same address can be used in several different MPLS VPNs and that it is possible for BGP to carry several completely different routes to that address, one for each VPN. According to [RFC4364], the size of mplsVpnRouteDistinguisher is 8 octets. However, in [RFC4382] an octet string with flexible length was chosen for representing a VPN route distinguisher by object MplsL3VpnRouteDistinguisher. This choice was made in order to be open to future changes of the size. This idea was adopted when choosing octetArray as abstract data type for this Information Element. The maximum length of this Information Element is 256 octets.",,,"See [RFC4364] for the specification of the route distinguisher. See [RFC4382] for the specification of the MPLS/BGP Layer 3 Virtual Private Network (VPN) Management Information Base.",[RFC5102],1,2014-02-03 91,mplsTopLabelPrefixLength,unsigned8,quantity,current,"The prefix length of the subnet of the mplsTopLabelIPv4Address that the MPLS top label will cause the Flow to be forwarded to.",bits,0-32,"See [RFC3031] for the association between MPLS labels and prefix lengths.",[ipfix-iana_at_cisco.com],1,2014-08-13 92,srcTrafficIndex,unsigned32,identifier,current,BGP Policy Accounting Source Traffic Index.,,,BGP policy accounting as described in [CCO-BGPPOL].,[RFC7270],0,2014-04-04 93,dstTrafficIndex,unsigned32,identifier,current,BGP Policy Accounting Destination Traffic Index.,,,BGP policy accounting as described in [CCO-BGPPOL].,[RFC7270],0,2014-04-04 94,applicationDescription,string,default,current,Specifies the description of an application.,,,,[RFC6759],1,2014-02-03 95,applicationId,octetArray,default,current,Specifies an Application ID per [RFC6759].,,,See section 4 of [RFC6759] for the applicationId Information Element Specification.,[RFC6759],1,2014-02-03 96,applicationName,string,default,current,Specifies the name of an application.,,,,[RFC6759],0,2013-02-18 97,Assigned for NetFlow v9 compatibility,,,,,,,[RFC3954],[RFC5102],0,2013-02-18 98,postIpDiffServCodePoint,unsigned8,identifier,current,"The definition of this Information Element is identical to the definition of Information Element 'ipDiffServCodePoint', except that it reports a potentially modified value caused by a middlebox function after the packet passed the Observation Point.",,0-63,"See [RFC3260] for the definition of the Differentiated Services Field. See section 5.3.2 of [RFC1812] and [RFC791] for the definition of the IPv4 TOS field. See [RFC2460] for the definition of the IPv6 Traffic Class field. See the IPFIX Information Model [RFC5102] for the 'ipDiffServCodePoint' specification.",[ipfix-iana_at_cisco.com],0,2013-02-18 99,multicastReplicationFactor,unsigned32,quantity,current,"The amount of multicast replication that's applied to a traffic stream.",,,"See [RFC1112] for the specification of reserved IPv4 multicast addresses. See [RFC4291] for the specification of reserved IPv6 multicast addresses.",[ipfix-iana_at_cisco.com],0,2013-02-18 100,className,string,,deprecated,"Deprecated in favor of 335 selectorName. Traffic Class Name, associated with the classId Information Element.",,,,[RFC7270],0,2014-04-04 101,classificationEngineId,unsigned8,identifier,current,"A unique identifier for the engine that determined the Selector ID. Thus, the Classification Engine ID defines the context for the Selector ID. The Classification Engine can be considered a specific registry for application assignments. Values for this field are listed in the Classification Engine IDs registry. See [http://www.iana.org/assignments/ipfix/ipfix.xml#classification-engine-ids]",,,,[RFC6759],0,2013-02-18 102,layer2packetSectionOffset,unsigned16,quantity,deprecated,"Deprecated in favor of 409 sectionOffset. Layer 2 packet section offset. Potentially a generic packet section offset.",,,,[RFC7270],0,2014-04-04 103,layer2packetSectionSize,unsigned16,quantity,deprecated,"Deprecated in favor of 312 dataLinkFrameSize. Layer 2 packet section size. Potentially a generic packet section size.",,,,[RFC7270],0,2014-04-04 104,layer2packetSectionData,octetArray,,deprecated,"Deprecated in favor of 315 dataLinkFrameSection. Layer 2 packet section data.",,,,[RFC7270],0,2014-04-04 105-127,Assigned for NetFlow v9 compatibility,,,,,,,[RFC3954],[RFC5102],0,2013-02-18 128,bgpNextAdjacentAsNumber,unsigned32,identifier,current,"The autonomous system (AS) number of the first AS in the AS path to the destination IP address. The path is deduced by looking up the destination IP address of the Flow in the BGP routing information base. If AS path information for this Flow is only available as an unordered AS set (and not as an ordered AS sequence), then the value of this Information Element is 0.",,,"See [RFC4271] for a description of BGP-4, and see [RFC1930] for the definition of the AS number.",[RFC5102],0,2013-02-18 129,bgpPrevAdjacentAsNumber,unsigned32,identifier,current,"The autonomous system (AS) number of the last AS in the AS path from the source IP address. The path is deduced by looking up the source IP address of the Flow in the BGP routing information base. If AS path information for this Flow is only available as an unordered AS set (and not as an ordered AS sequence), then the value of this Information Element is 0. In case of BGP asymmetry, the bgpPrevAdjacentAsNumber might not be able to report the correct value.",,,"See [RFC4271] for a description of BGP-4, and see [RFC1930] for the definition of the AS number.",[RFC5102],0,2013-02-18 130,exporterIPv4Address,ipv4Address,default,current,"The IPv4 address used by the Exporting Process. This is used by the Collector to identify the Exporter in cases where the identity of the Exporter may have been obscured by the use of a proxy.",,,,[RFC5102],1,2014-02-03 131,exporterIPv6Address,ipv6Address,default,current,"The IPv6 address used by the Exporting Process. This is used by the Collector to identify the Exporter in cases where the identity of the Exporter may have been obscured by the use of a proxy.",,,,[RFC5102],1,2014-02-03 132,droppedOctetDeltaCount,unsigned64,deltaCounter,current,"The number of octets since the previous report (if any) in packets of this Flow dropped by packet treatment. The number of octets includes IP header(s) and IP payload.",octets,,,[RFC5102],0,2013-02-18 133,droppedPacketDeltaCount,unsigned64,deltaCounter,current,"The number of packets since the previous report (if any) of this Flow dropped by packet treatment.",packets,,,[RFC5102],0,2013-02-18 134,droppedOctetTotalCount,unsigned64,totalCounter,current,"The total number of octets in packets of this Flow dropped by packet treatment since the Metering Process (re-)initialization for this Observation Point. The number of octets includes IP header(s) and IP payload.",octets,,,[RFC5102],0,2013-02-18 135,droppedPacketTotalCount,unsigned64,totalCounter,current,"The number of packets of this Flow dropped by packet treatment since the Metering Process (re-)initialization for this Observation Point.",packets,,,[RFC5102],0,2013-02-18 136,flowEndReason,unsigned8,identifier,current,"The reason for Flow termination. The range of values includes the following: 0x01: idle timeout The Flow was terminated because it was considered to be idle. 0x02: active timeout The Flow was terminated for reporting purposes while it was still active, for example, after the maximum lifetime of unreported Flows was reached. 0x03: end of Flow detected The Flow was terminated because the Metering Process detected signals indicating the end of the Flow, for example, the TCP FIN flag. 0x04: forced end The Flow was terminated because of some external event, for example, a shutdown of the Metering Process initiated by a network management application. 0x05: lack of resources The Flow was terminated because of lack of resources available to the Metering Process and/or the Exporting Process.",,,,[RFC5102],0,2013-02-18 137,commonPropertiesId,unsigned64,identifier,current,"An identifier of a set of common properties that is unique per Observation Domain and Transport Session. Typically, this Information Element is used to link to information reported in separate Data Records.",,,,[RFC5102],0,2013-02-18 138,observationPointId,unsigned64,identifier,current,"An identifier of an Observation Point that is unique per Observation Domain. It is RECOMMENDED that this identifier is also unique per IPFIX Device. Typically, this Information Element is used for limiting the scope of other Information Elements.",,,,[RFC5102][ipfix-iana_at_cisco.com],1,2013-04-11 139,icmpTypeCodeIPv6,unsigned16,identifier,current,"Type and Code of the IPv6 ICMP message. The combination of both values is reported as (ICMP type * 256) + ICMP code.",,,"See [RFC4443] for the definition of the IPv6 ICMP type and code fields.",[RFC5102],0,2013-02-18 140,mplsTopLabelIPv6Address,ipv6Address,default,current,"The IPv6 address of the system that the MPLS top label will cause this Flow to be forwarded to.",,,"See [RFC3031] for the association between MPLS labels and IP addresses.",[RFC5102],1,2014-02-03 141,lineCardId,unsigned32,identifier,current,"An identifier of a line card that is unique per IPFIX Device hosting an Observation Point. Typically, this Information Element is used for limiting the scope of other Information Elements.",,,,[RFC5102],0,2013-02-18 142,portId,unsigned32,identifier,current,"An identifier of a line port that is unique per IPFIX Device hosting an Observation Point. Typically, this Information Element is used for limiting the scope of other Information Elements.",,,,[RFC5102],0,2013-02-18 143,meteringProcessId,unsigned32,identifier,current,"An identifier of a Metering Process that is unique per IPFIX Device. Typically, this Information Element is used for limiting the scope of other Information Elements. Note that process identifiers are typically assigned dynamically. The Metering Process may be re-started with a different ID.",,,,[RFC5102],0,2013-02-18 144,exportingProcessId,unsigned32,identifier,current,"An identifier of an Exporting Process that is unique per IPFIX Device. Typically, this Information Element is used for limiting the scope of other Information Elements. Note that process identifiers are typically assigned dynamically. The Exporting Process may be re-started with a different ID.",,,,[RFC5102],0,2013-02-18 145,templateId,unsigned16,identifier,current,"An identifier of a Template that is locally unique within a combination of a Transport session and an Observation Domain. Template IDs 0-255 are reserved for Template Sets, Options Template Sets, and other reserved Sets yet to be created. Template IDs of Data Sets are numbered from 256 to 65535. Typically, this Information Element is used for limiting the scope of other Information Elements. Note that after a re-start of the Exporting Process Template identifiers may be re-assigned.",,,,[RFC5102],0,2013-02-18 146,wlanChannelId,unsigned8,identifier,current,The identifier of the 802.11 (Wi-Fi) channel used.,,,See IEEE.802-11.1999.,[RFC5102],0,2013-02-18 147,wlanSSID,string,default,current,"The Service Set IDentifier (SSID) identifying an 802.11 (Wi-Fi) network used. According to IEEE.802-11.1999, the SSID is encoded into a string of up to 32 characters.",,,See IEEE.802-11.1999.,[RFC5102],0,2013-02-18 148,flowId,unsigned64,identifier,current,"An identifier of a Flow that is unique within an Observation Domain. This Information Element can be used to distinguish between different Flows if Flow Keys such as IP addresses and port numbers are not reported or are reported in separate records.",,,,[RFC5102],0,2013-02-18 149,observationDomainId,unsigned32,identifier,current,"An identifier of an Observation Domain that is locally unique to an Exporting Process. The Exporting Process uses the Observation Domain ID to uniquely identify to the Collecting Process the Observation Domain where Flows were metered. It is RECOMMENDED that this identifier is also unique per IPFIX Device. A value of 0 indicates that no specific Observation Domain is identified by this Information Element. Typically, this Information Element is used for limiting the scope of other Information Elements.",,,,[RFC5102],0,2013-02-18 150,flowStartSeconds,dateTimeSeconds,default,current,The absolute timestamp of the first packet of this Flow.,seconds,,,[RFC5102],0,2013-02-18 151,flowEndSeconds,dateTimeSeconds,default,current,The absolute timestamp of the last packet of this Flow.,seconds,,,[RFC5102],0,2013-02-18 152,flowStartMilliseconds,dateTimeMilliseconds,default,current,The absolute timestamp of the first packet of this Flow.,milliseconds,,,[RFC5102],0,2013-02-18 153,flowEndMilliseconds,dateTimeMilliseconds,default,current,The absolute timestamp of the last packet of this Flow.,milliseconds,,,[RFC5102],0,2013-02-18 154,flowStartMicroseconds,dateTimeMicroseconds,default,current,The absolute timestamp of the first packet of this Flow.,microseconds,,,[RFC5102],0,2013-02-18 155,flowEndMicroseconds,dateTimeMicroseconds,default,current,The absolute timestamp of the last packet of this Flow.,microseconds,,,[RFC5102],0,2013-02-18 156,flowStartNanoseconds,dateTimeNanoseconds,default,current,The absolute timestamp of the first packet of this Flow.,nanoseconds,,,[RFC5102],0,2013-02-18 157,flowEndNanoseconds,dateTimeNanoseconds,default,current,The absolute timestamp of the last packet of this Flow.,nanoseconds,,,[RFC5102],0,2013-02-18 158,flowStartDeltaMicroseconds,unsigned32,,current,"This is a relative timestamp only valid within the scope of a single IPFIX Message. It contains the negative time offset of the first observed packet of this Flow relative to the export time specified in the IPFIX Message Header.",microseconds,,"See the [IPFIX protocol specification] for the definition of the IPFIX Message Header.",[RFC5102],0,2013-02-18 159,flowEndDeltaMicroseconds,unsigned32,,current,"This is a relative timestamp only valid within the scope of a single IPFIX Message. It contains the negative time offset of the last observed packet of this Flow relative to the export time specified in the IPFIX Message Header.",microseconds,,"See the [IPFIX protocol specification] for the definition of the IPFIX Message Header.",[RFC5102],0,2013-02-18 160,systemInitTimeMilliseconds,dateTimeMilliseconds,default,current,"The absolute timestamp of the last (re-)initialization of the IPFIX Device.",milliseconds,,,[RFC5102],0,2013-02-18 161,flowDurationMilliseconds,unsigned32,,current,"The difference in time between the first observed packet of this Flow and the last observed packet of this Flow.",milliseconds,,,[RFC5102],0,2013-02-18 162,flowDurationMicroseconds,unsigned32,,current,"The difference in time between the first observed packet of this Flow and the last observed packet of this Flow.",microseconds,,,[RFC5102],0,2013-02-18 163,observedFlowTotalCount,unsigned64,totalCounter,current,"The total number of Flows observed in the Observation Domain since the Metering Process (re-)initialization for this Observation Point.",flows,,,[RFC5102],0,2013-02-18 164,ignoredPacketTotalCount,unsigned64,totalCounter,current,"The total number of observed IP packets that the Metering Process did not process since the (re-)initialization of the Metering Process.",packets,,,[RFC5102],0,2013-02-18 165,ignoredOctetTotalCount,unsigned64,totalCounter,current,"The total number of octets in observed IP packets (including the IP header) that the Metering Process did not process since the (re-)initialization of the Metering Process.",octets,,,[RFC5102],0,2013-02-18 166,notSentFlowTotalCount,unsigned64,totalCounter,current,"The total number of Flow Records that were generated by the Metering Process and dropped by the Metering Process or by the Exporting Process instead of being sent to the Collecting Process. There are several potential reasons for this including resource shortage and special Flow export policies.",flows,,,[RFC5102],0,2013-02-18 167,notSentPacketTotalCount,unsigned64,totalCounter,current,"The total number of packets in Flow Records that were generated by the Metering Process and dropped by the Metering Process or by the Exporting Process instead of being sent to the Collecting Process. There are several potential reasons for this including resource shortage and special Flow export policies.",packets,,,[RFC5102],0,2013-02-18 168,notSentOctetTotalCount,unsigned64,totalCounter,current,"The total number of octets in packets in Flow Records that were generated by the Metering Process and dropped by the Metering Process or by the Exporting Process instead of being sent to the Collecting Process. There are several potential reasons for this including resource shortage and special Flow export policies.",octets,,,[RFC5102],0,2013-02-18 169,destinationIPv6Prefix,ipv6Address,default,current,IPv6 destination address prefix.,,,,[RFC5102],0,2013-02-18 170,sourceIPv6Prefix,ipv6Address,default,current,IPv6 source address prefix.,,,,[RFC5102],0,2013-02-18 171,postOctetTotalCount,unsigned64,totalCounter,current,"The definition of this Information Element is identical to the definition of Information Element 'octetTotalCount', except that it reports a potentially modified value caused by a middlebox function after the packet passed the Observation Point.",octets,,,[RFC5102],0,2013-02-18 172,postPacketTotalCount,unsigned64,totalCounter,current,"The definition of this Information Element is identical to the definition of Information Element 'packetTotalCount', except that it reports a potentially modified value caused by a middlebox function after the packet passed the Observation Point.",packets,,,[RFC5102],0,2013-02-18 173,flowKeyIndicator,unsigned64,flags,current,"This set of bit fields is used for marking the Information Elements of a Data Record that serve as Flow Key. Each bit represents an Information Element in the Data Record with the n-th bit representing the n-th Information Element. A bit set to value 1 indicates that the corresponding Information Element is a Flow Key of the reported Flow. A bit set to value 0 indicates that this is not the case. If the Data Record contains more than 64 Information Elements, the corresponding Template SHOULD be designed such that all Flow Keys are among the first 64 Information Elements, because the flowKeyIndicator only contains 64 bits. If the Data Record contains less than 64 Information Elements, then the bits in the flowKeyIndicator for which no corresponding Information Element exists MUST have the value 0.",,,,[RFC5102],0,2013-02-18 174,postMCastPacketTotalCount,unsigned64,totalCounter,current,"The total number of outgoing multicast packets sent for packets of this Flow by a multicast daemon within the Observation Domain since the Metering Process (re-)initialization. This property cannot necessarily be observed at the Observation Point, but may be retrieved by other means.",packets,,,[RFC5102],0,2013-02-18 175,postMCastOctetTotalCount,unsigned64,totalCounter,current,"The total number of octets in outgoing multicast packets sent for packets of this Flow by a multicast daemon in the Observation Domain since the Metering Process (re-)initialization. This property cannot necessarily be observed at the Observation Point, but may be retrieved by other means. The number of octets includes IP header(s) and IP payload.",octets,,,[RFC5102],0,2013-02-18 176,icmpTypeIPv4,unsigned8,identifier,current,Type of the IPv4 ICMP message.,,,"See [RFC792] for the definition of the IPv4 ICMP type field.",[RFC5102],0,2013-02-18 177,icmpCodeIPv4,unsigned8,identifier,current,Code of the IPv4 ICMP message.,,,"See [RFC792] for the definition of the IPv4 ICMP code field.",[RFC5102],0,2013-02-18 178,icmpTypeIPv6,unsigned8,identifier,current,Type of the IPv6 ICMP message.,,,"See [RFC4443] for the definition of the IPv6 ICMP type field.",[RFC5102],0,2013-02-18 179,icmpCodeIPv6,unsigned8,identifier,current,Code of the IPv6 ICMP message.,,,"See [RFC4443] for the definition of the IPv6 ICMP code field.",[RFC5102],0,2013-02-18 180,udpSourcePort,unsigned16,identifier,current,The source port identifier in the UDP header.,,,"See [RFC768] for the definition of the UDP source port field. Additional information on defined UDP port numbers can be found at [IANA registry service-names-port-numbers].",[RFC5102],0,2013-02-18 181,udpDestinationPort,unsigned16,identifier,current,The destination port identifier in the UDP header.,,,"See [RFC768] for the definition of the UDP destination port field. Additional information on defined UDP port numbers can be found at [IANA registry service-names-port-numbers].",[RFC5102],0,2013-02-18 182,tcpSourcePort,unsigned16,identifier,current,The source port identifier in the TCP header.,,,"See [RFC793] for the definition of the TCP source port field. Additional information on defined TCP port numbers can be found at [IANA registry service-names-port-numbers].",[RFC5102],0,2013-02-18 183,tcpDestinationPort,unsigned16,identifier,current,The destination port identifier in the TCP header.,,,"See [RFC793] for the definition of the TCP destination port field. Additional information on defined TCP port numbers can be found at [IANA registry service-names-port-numbers].",[RFC5102],0,2013-02-18 184,tcpSequenceNumber,unsigned32,,current,The sequence number in the TCP header.,,,"See [RFC793] for the definition of the TCP sequence number.",[RFC5102],0,2013-02-18 185,tcpAcknowledgementNumber,unsigned32,,current,The acknowledgement number in the TCP header.,,,"See [RFC793] for the definition of the TCP acknowledgement number.",[RFC5102],0,2013-02-18 186,tcpWindowSize,unsigned16,,current,"The window field in the TCP header. If the TCP window scale is supported, then TCP window scale must be known to fully interpret the value of this information.",,,"See [RFC793] for the definition of the TCP window field. See [RFC1323] for the definition of the TCP window scale.",[RFC5102],0,2013-02-18 187,tcpUrgentPointer,unsigned16,,current,The urgent pointer in the TCP header.,,,"See [RFC793] for the definition of the TCP urgent pointer.",[RFC5102],0,2013-02-18 188,tcpHeaderLength,unsigned8,,current,"The length of the TCP header. Note that the value of this Information Element is different from the value of the Data Offset field in the TCP header. The Data Offset field indicates the length of the TCP header in units of 4 octets. This Information Elements specifies the length of the TCP header in units of octets.",octets,,"See [RFC793] for the definition of the TCP header.",[RFC5102],0,2013-02-18 189,ipHeaderLength,unsigned8,,current,"The length of the IP header. For IPv6, the value of this Information Element is 40.",octets,,"See [RFC791] for the definition of the IPv4 header. See [RFC2460] for the definition of the IPv6 header.",[RFC5102],0,2013-02-18 190,totalLengthIPv4,unsigned16,,current,The total length of the IPv4 packet.,octets,,"See [RFC791] for the specification of the IPv4 total length.",[RFC5102],0,2013-02-18 191,payloadLengthIPv6,unsigned16,,current,"This Information Element reports the value of the Payload Length field in the IPv6 header. Note that IPv6 extension headers belong to the payload. Also note that in case of a jumbo payload option the value of the Payload Length field in the IPv6 header is zero and so will be the value reported by this Information Element.",octets,,"See [RFC2460] for the specification of the IPv6 payload length. See [RFC2675] for the specification of the IPv6 jumbo payload option.",[RFC5102],0,2013-02-18 192,ipTTL,unsigned8,,current,"For IPv4, the value of the Information Element matches the value of the Time to Live (TTL) field in the IPv4 packet header. For IPv6, the value of the Information Element matches the value of the Hop Limit field in the IPv6 packet header.",hops,,"See [RFC791] for the definition of the IPv4 Time to Live field. See [RFC2675] for the definition of the IPv6 Hop Limit field.",[RFC5102],0,2013-02-18 193,nextHeaderIPv6,unsigned8,,current,"The value of the Next Header field of the IPv6 header. The value identifies the type of the following IPv6 extension header or of the following IP payload. Valid values are defined in the IANA Protocol Numbers registry.",,,"See [RFC2460] for the definition of the IPv6 Next Header field. See the list of protocol numbers assigned by IANA at [IANA registry protocol-numbers].",[RFC5102],0,2013-02-18 194,mplsPayloadLength,unsigned32,,current,The size of the MPLS packet without the label stack.,octets,,"See [RFC3031] for the specification of MPLS packets. See [RFC3032] for the specification of the MPLS label stack.",[RFC5102],0,2013-02-18 195,ipDiffServCodePoint,unsigned8,identifier,current,"The value of a Differentiated Services Code Point (DSCP) encoded in the Differentiated Services field. The Differentiated Services field spans the most significant 6 bits of the IPv4 TOS field or the IPv6 Traffic Class field, respectively. This Information Element encodes only the 6 bits of the Differentiated Services field. Therefore, its value may range from 0 to 63.",,0-63,"See [RFC3260] for the definition of the Differentiated Services field. See [RFC1812] (Section 5.3.2) and [RFC791] for the definition of the IPv4 TOS field. See [RFC2460] for the definition of the IPv6 Traffic Class field.",[RFC5102],0,2013-02-18 196,ipPrecedence,unsigned8,identifier,current,"The value of the IP Precedence. The IP Precedence value is encoded in the first 3 bits of the IPv4 TOS field or the IPv6 Traffic Class field, respectively. This Information Element encodes only these 3 bits. Therefore, its value may range from 0 to 7.",,0-7,"See [RFC1812] (Section 5.3.3) and [RFC791] for the definition of the IP Precedence. See [RFC1812] (Section 5.3.2) and [RFC791] for the definition of the IPv4 TOS field. See [RFC2460] for the definition of the IPv6 Traffic Class field.",[RFC5102],0,2013-02-18 197,fragmentFlags,unsigned8,flags,current,"Fragmentation properties indicated by flags in the IPv4 packet header or the IPv6 Fragment header, respectively. Bit 0: (RS) Reserved. The value of this bit MUST be 0 until specified otherwise. Bit 1: (DF) 0 = May Fragment, 1 = Don't Fragment. Corresponds to the value of the DF flag in the IPv4 header. Will always be 0 for IPv6 unless a ""don't fragment"" feature is introduced to IPv6. Bit 2: (MF) 0 = Last Fragment, 1 = More Fragments. Corresponds to the MF flag in the IPv4 header or to the M flag in the IPv6 Fragment header, respectively. The value is 0 for IPv6 if there is no fragment header. Bits 3-7: (DC) Don't Care. The values of these bits are irrelevant. 0 1 2 3 4 5 6 7 +---+---+---+---+---+---+---+---+ | R | D | M | D | D | D | D | D | | S | F | F | C | C | C | C | C | +---+---+---+---+---+---+---+---+",,,"See [RFC791] for the specification of the IPv4 fragment flags. See [RFC2460] for the specification of the IPv6 Fragment header.",[RFC5102],0,2013-02-18 198,octetDeltaSumOfSquares,unsigned64,,current,"The sum of the squared numbers of octets per incoming packet since the previous report (if any) for this Flow at the Observation Point. The number of octets includes IP header(s) and IP payload.",,,,[RFC5102],0,2013-02-18 199,octetTotalSumOfSquares,unsigned64,,current,"The total sum of the squared numbers of octets in incoming packets for this Flow at the Observation Point since the Metering Process (re-)initialization for this Observation Point. The number of octets includes IP header(s) and IP payload.",octets,,,[RFC5102],0,2013-02-18 200,mplsTopLabelTTL,unsigned8,,current,"The TTL field from the top MPLS label stack entry, i.e., the last label that was pushed.",hops,,"See [RFC3032] for the specification of the TTL field.",[RFC5102],0,2013-02-18 201,mplsLabelStackLength,unsigned32,,current,The length of the MPLS label stack in units of octets.,octets,,"See [RFC3032] for the specification of the MPLS label stack.",[RFC5102],0,2013-02-18 202,mplsLabelStackDepth,unsigned32,,current,The number of labels in the MPLS label stack.,label stack entries,,"See [RFC3032] for the specification of the MPLS label stack.",[RFC5102],0,2013-02-18 203,mplsTopLabelExp,unsigned8,flags,current,"The Exp field from the top MPLS label stack entry, i.e., the last label that was pushed. Bits 0-4: Don't Care, value is irrelevant. Bits 5-7: MPLS Exp field. 0 1 2 3 4 5 6 7 +---+---+---+---+---+---+---+---+ | don't care | Exp | +---+---+---+---+---+---+---+---+",,,"See [RFC3032] for the specification of the Exp field. See [RFC3270] for usage of the Exp field.",[RFC5102],0,2013-02-18 204,ipPayloadLength,unsigned32,,current,"The effective length of the IP payload. For IPv4 packets, the value of this Information Element is the difference between the total length of the IPv4 packet (as reported by Information Element totalLengthIPv4) and the length of the IPv4 header (as reported by Information Element headerLengthIPv4). For IPv6, the value of the Payload Length field in the IPv6 header is reported except in the case that the value of this field is zero and that there is a valid jumbo payload option. In this case, the value of the Jumbo Payload Length field in the jumbo payload option is reported.",octets,,"See [RFC791] for the specification of IPv4 packets. See [RFC2460] for the specification of the IPv6 payload length. See [RFC2675] for the specification of the IPv6 jumbo payload length.",[RFC5102],0,2013-02-18 205,udpMessageLength,unsigned16,,current,The value of the Length field in the UDP header.,octets,,"See [RFC768] for the specification of the UDP header.",[RFC5102],0,2013-02-18 206,isMulticast,unsigned8,flags,current,"If the IP destination address is not a reserved multicast address, then the value of all bits of the octet (including the reserved ones) is zero. The first bit of this octet is set to 1 if the Version field of the IP header has the value 4 and if the Destination Address field contains a reserved multicast address in the range from 224.0.0.0 to 239.255.255.255. Otherwise, this bit is set to 0. The second and third bits of this octet are reserved for future use. The remaining bits of the octet are only set to values other than zero if the IP Destination Address is a reserved IPv6 multicast address. Then the fourth bit of the octet is set to the value of the T flag in the IPv6 multicast address and the remaining four bits are set to the value of the scope field in the IPv6 multicast address. 0 1 2 3 4 5 6 7 +------+------+------+------+------+------+------+------+ | IPv6 multicast scope | T | RES. | RES. | MCv4 | +------+------+------+------+------+------+------+------+ Bits 0-3: set to value of multicast scope if IPv6 multicast Bit 4: set to value of T flag, if IPv6 multicast Bits 5-6: reserved for future use Bit 7: set to 1 if IPv4 multicast",,,"See [RFC1112] for the specification of reserved IPv4 multicast addresses. See [RFC4291] for the specification of reserved IPv6 multicast addresses and the definition of the T flag and the IPv6 multicast scope. The diagram provided in [RFC5102] is incorrect. The diagram in this registry is taken from Errata 1736. See [RFC Errata 1736]",[RFC5102],0,2013-02-18 207,ipv4IHL,unsigned8,,current,"The value of the Internet Header Length (IHL) field in the IPv4 header. It specifies the length of the header in units of 4 octets. Please note that its unit is different from most of the other Information Elements reporting length values.",4 octets,,"See [RFC791] for the specification of the IPv4 header.",[RFC5102],0,2013-02-18 208,ipv4Options,unsigned32,flags,current,"IPv4 options in packets of this Flow. The information is encoded in a set of bit fields. For each valid IPv4 option type, there is a bit in this set. The bit is set to 1 if any observed packet of this Flow contains the corresponding IPv4 option type. Otherwise, if no observed packet of this Flow contained the respective IPv4 option type, the value of the corresponding bit is 0. The list of valid IPv4 options is maintained by IANA. Note that for identifying an option not just the 5-bit Option Number, but all 8 bits of the Option Type need to match one of the IPv4 options specified at http://www.iana.org/assignments/ip-parameters. Options are mapped to bits according to their option numbers. Option number X is mapped to bit X. The mapping is illustrated by the figure below. 0 1 2 3 4 5 6 7 +------+------+------+------+------+------+------+------+ ... | RR |CIPSO |E-SEC | TS | LSR | SEC | NOP | EOOL | +------+------+------+------+------+------+------+------+ 8 9 10 11 12 13 14 15 +------+------+------+------+------+------+------+------+ ... |ENCODE| VISA | FINN | MTUR | MTUP | ZSU | SSR | SID | ... +------+------+------+------+------+------+------+------+ 16 17 18 19 20 21 22 23 +------+------+------+------+------+------+------+------+ ... | DPS |NSAPA | SDB |RTRALT|ADDEXT| TR | EIP |IMITD | ... +------+------+------+------+------+------+------+------+ 24 25 26 27 28 29 30 31 +------+------+------+------+------+------+------+------+ | | EXP | to be assigned by IANA | QS | UMP | ... +------+------+------+------+------+------+------+------+ Type Option Bit Value Name Reference ---+-----+-------+------------------------------------ 0 7 RR Record Route, RFC 791 1 134 CIPSO Commercial Security 2 133 E-SEC Extended Security, RFC 1108 3 68 TS Time Stamp, RFC 791 4 131 LSR Loose Source Route, RFC791 5 130 SEC Security, RFC 1108 6 1 NOP No Operation, RFC 791 7 0 EOOL End of Options List, RFC 791 8 15 ENCODE 9 142 VISA Experimental Access Control 10 205 FINN Experimental Flow Control 11 12 MTUR (obsoleted) MTU Reply, RFC 1191 12 11 MTUP (obsoleted) MTU Probe, RFC 1191 13 10 ZSU Experimental Measurement 14 137 SSR Strict Source Route, RFC 791 15 136 SID Stream ID, RFC 791 16 151 DPS Dynamic Packet State 17 150 NSAPA NSAP Address 18 149 SDB Selective Directed Broadcast 19 147 ADDEXT Address Extension 20 148 RTRALT Router Alert, RFC 2113 21 82 TR Traceroute, RFC 3193 22 145 EIP Extended Internet Protocol, RFC 1385 23 144 IMITD IMI Traffic Descriptor 25 30 EXP RFC3692-style Experiment 25 94 EXP RFC3692-style Experiment 25 158 EXP RFC3692-style Experiment 25 222 EXP RFC3692-style Experiment 30 25 QS Quick-Start 31 152 UMP Upstream Multicast Pkt. ... ... ... Further options numbers may be assigned by IANA",,,"See [RFC791] for the definition of IPv4 options. See the list of IPv4 option numbers assigned by IANA at [IANA registry ip-parameters]. The diagram provided in [RFC5102] is incorrect. The diagram in this registry is taken from Errata 1737. See [RFC Errata 1737]",[RFC5102],0,2013-02-18 209,tcpOptions,unsigned64,flags,current,"TCP options in packets of this Flow. The information is encoded in a set of bit fields. For each TCP option, there is a bit in this set. The bit is set to 1 if any observed packet of this Flow contains the corresponding TCP option. Otherwise, if no observed packet of this Flow contained the respective TCP option, the value of the corresponding bit is 0. Options are mapped to bits according to their option numbers. Option number X is mapped to bit X. TCP option numbers are maintained by IANA. 0 1 2 3 4 5 6 7 +-----+-----+-----+-----+-----+-----+-----+-----+ | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | ... +-----+-----+-----+-----+-----+-----+-----+-----+ 8 9 10 11 12 13 14 15 +-----+-----+-----+-----+-----+-----+-----+-----+ ... | 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 |... +-----+-----+-----+-----+-----+-----+-----+-----+ 16 17 18 19 20 21 22 23 +-----+-----+-----+-----+-----+-----+-----+-----+ ... | 23 | 22 | 21 | 20 | 19 | 18 | 17 | 16 |... +-----+-----+-----+-----+-----+-----+-----+-----+ . . . 56 57 58 59 60 61 62 63 +-----+-----+-----+-----+-----+-----+-----+-----+ ... | 63 | 62 | 61 | 60 | 59 | 58 | 57 | 56 | +-----+-----+-----+-----+-----+-----+-----+-----+",,,"See [RFC793] for the definition of TCP options. See the list of TCP option numbers assigned by IANA at [IANA registry tcp-parameters]. The diagram provided in [RFC5102] is incorrect. The diagram in this registry is taken from Errata 1739. See [RFC Errata 1739]",[RFC5102],0,2013-02-18 210,paddingOctets,octetArray,default,current,"The value of this Information Element is always a sequence of 0x00 values.",,,,[RFC5102],0,2013-02-18 211,collectorIPv4Address,ipv4Address,default,current,"An IPv4 address to which the Exporting Process sends Flow information.",,,,[RFC5102],1,2014-02-03 212,collectorIPv6Address,ipv6Address,default,current,"An IPv6 address to which the Exporting Process sends Flow information.",,,,[RFC5102],1,2014-02-03 213,exportInterface,unsigned32,identifier,current,"The index of the interface from which IPFIX Messages sent by the Exporting Process to a Collector leave the IPFIX Device. The value matches the value of managed object 'ifIndex' as defined in [RFC2863]. Note that ifIndex values are not assigned statically to an interface and that the interfaces may be renumbered every time the device's management system is re-initialized, as specified in [RFC2863].",,,"See [RFC2863] for the definition of the ifIndex object.",[RFC5102],0,2013-02-18 214,exportProtocolVersion,unsigned8,identifier,current,"The protocol version used by the Exporting Process for sending Flow information. The protocol version is given by the value of the Version Number field in the Message Header. The protocol version is 10 for IPFIX and 9 for NetFlow version 9. A value of 0 indicates that no export protocol is in use.",,,"See the [IPFIX protocol specification] for the definition of the IPFIX Message Header. See [RFC3954] for the definition of the NetFlow version 9 message header.",[RFC5102],0,2013-02-18 215,exportTransportProtocol,unsigned8,identifier,current,"The value of the protocol number used by the Exporting Process for sending Flow information. The protocol number identifies the IP packet payload type. Protocol numbers are defined in the IANA Protocol Numbers registry. In Internet Protocol version 4 (IPv4), this is carried in the Protocol field. In Internet Protocol version 6 (IPv6), this is carried in the Next Header field in the last extension header of the packet.",,,"See [RFC791] for the specification of the IPv4 protocol field. See [RFC2460] for the specification of the IPv6 protocol field. See the list of protocol numbers assigned by IANA at [IANA registry protocol-numbers].",[RFC5102],0,2013-02-18 216,collectorTransportPort,unsigned16,identifier,current,"The destination port identifier to which the Exporting Process sends Flow information. For the transport protocols UDP, TCP, and SCTP, this is the destination port number. This field MAY also be used for future transport protocols that have 16-bit source port identifiers.",,,"See [RFC768] for the definition of the UDP destination port field. See [RFC793] for the definition of the TCP destination port field. See [RFC4960] for the definition of SCTP. Additional information on defined UDP and TCP port numbers can be found at [IANA registry service-names-port-numbers].",[RFC5102],0,2013-02-18 217,exporterTransportPort,unsigned16,identifier,current,"The source port identifier from which the Exporting Process sends Flow information. For the transport protocols UDP, TCP, and SCTP, this is the source port number. This field MAY also be used for future transport protocols that have 16-bit source port identifiers. This field may be useful for distinguishing multiple Exporting Processes that use the same IP address.",,,"See [RFC768] for the definition of the UDP source port field. See [RFC793] for the definition of the TCP source port field. See [RFC4960] for the definition of SCTP. Additional information on defined UDP and TCP port numbers can be found at [IANA registry service-names-port-numbers].",[RFC5102],0,2013-02-18 218,tcpSynTotalCount,unsigned64,totalCounter,current,"The total number of packets of this Flow with TCP ""Synchronize sequence numbers"" (SYN) flag set.",packets,,"See [RFC793] for the definition of the TCP SYN flag.",[RFC5102],0,2013-02-18 219,tcpFinTotalCount,unsigned64,totalCounter,current,"The total number of packets of this Flow with TCP ""No more data from sender"" (FIN) flag set.",packets,,"See [RFC793] for the definition of the TCP FIN flag.",[RFC5102],0,2013-02-18 220,tcpRstTotalCount,unsigned64,totalCounter,current,"The total number of packets of this Flow with TCP ""Reset the connection"" (RST) flag set.",packets,,"See [RFC793] for the definition of the TCP RST flag.",[RFC5102],0,2013-02-18 221,tcpPshTotalCount,unsigned64,totalCounter,current,"The total number of packets of this Flow with TCP ""Push Function"" (PSH) flag set.",packets,,"See [RFC793] for the definition of the TCP PSH flag.",[RFC5102],0,2013-02-18 222,tcpAckTotalCount,unsigned64,totalCounter,current,"The total number of packets of this Flow with TCP ""Acknowledgment field significant"" (ACK) flag set.",packets,,"See [RFC793] for the definition of the TCP ACK flag.",[RFC5102],0,2013-02-18 223,tcpUrgTotalCount,unsigned64,totalCounter,current,"The total number of packets of this Flow with TCP ""Urgent Pointer field significant"" (URG) flag set.",packets,,"See [RFC793] for the definition of the TCP URG flag.",[RFC5102],0,2013-02-18 224,ipTotalLength,unsigned64,,current,The total length of the IP packet.,octets,,"See [RFC791] for the specification of the IPv4 total length. See [RFC2460] for the specification of the IPv6 payload length. See [RFC2675] for the specification of the IPv6 jumbo payload length.",[RFC5102],0,2013-02-18 225,postNATSourceIPv4Address,ipv4Address,default,current,"The definition of this Information Element is identical to the definition of Information Element 'sourceIPv4Address', except that it reports a modified value caused by a NAT middlebox function after the packet passed the Observation Point.",,,"See [RFC791] for the definition of the IPv4 source address field. See [RFC3022] for the definition of NAT. See [RFC3234] for the definition of middleboxes.",[ipfix-iana_at_cisco.com],1,2014-02-03 226,postNATDestinationIPv4Address,ipv4Address,default,current,"The definition of this Information Element is identical to the definition of Information Element 'destinationIPv4Address', except that it reports a modified value caused by a NAT middlebox function after the packet passed the Observation Point.",,,"See [RFC791] for the definition of the IPv4 destination address field. See [RFC3022] for the definition of NAT. See [RFC3234] for the definition of middleboxes.",[ipfix-iana_at_cisco.com],1,2014-02-03 227,postNAPTSourceTransportPort,unsigned16,identifier,current,"The definition of this Information Element is identical to the definition of Information Element 'sourceTransportPort', except that it reports a modified value caused by a Network Address Port Translation (NAPT) middlebox function after the packet passed the Observation Point.",,,"See [RFC768] for the definition of the UDP source port field. See [RFC793] for the definition of the TCP source port field. See [RFC4960] for the definition of SCTP. See [RFC3022] for the definition of NAPT. See [RFC3234] for the definition of middleboxes. Additional information on defined UDP and TCP port numbers can be found at http://www.iana.org/assignments/port-numbers.",[ipfix-iana_at_cisco.com],0,2013-02-18 228,postNAPTDestinationTransportPort,unsigned16,identifier,current,"The definition of this Information Element is identical to the definition of Information Element 'destinationTransportPort', except that it reports a modified value caused by a Network Address Port Translation (NAPT) middlebox function after the packet passed the Observation Point.",,,"See [RFC768] for the definition of the UDP source port field. See [RFC793] for the definition of the TCP source port field. See [RFC4960] for the definition of SCTP. See [RFC3022] for the definition of NAPT. See [RFC3234] for the definition of middleboxes. Additional information on defined UDP and TCP port numbers can be found at [IANA registry service-names-port-numbers].",[ipfix-iana_at_cisco.com],0,2013-02-18 229,natOriginatingAddressRealm,unsigned8,identifier,current,"Indicates whether the session was created because traffic originated in the private or public address realm. postNATSourceIPv4Address, postNATDestinationIPv4Address, postNAPTSourceTransportPort, and postNAPTDestinationTransportPort are qualified with the address realm in perspective. The allowed values are: Private: 1 Public: 2",,1-2,See [RFC3022] for the definition of NAT.,[ipfix-iana_at_cisco.com],1,2014-08-13 230,natEvent,unsigned8,identifier,current,"Indicates a NAT event. The allowed values are: 1 - Create event. 2 - Delete event. 3 - Pool exhausted. A Create event is generated when a NAT translation is created, whether dynamically or statically. A Delete event is generated when a NAT translation is deleted.",,1-2,See [RFC3022] for the definition of NAT.,[ipfix-iana_at_cisco.com],1,2014-08-13 231,initiatorOctets,unsigned64,deltaCounter,current,"The total number of layer 4 payload bytes in a flow from the initiator. The initiator is the device which triggered the session creation, and remains the same for the life of the session.",octets,,"See #298, initiatorPackets.",[ipfix-iana_at_cisco.com],1,2014-08-13 232,responderOctets,unsigned64,deltaCounter,current,"The total number of layer 4 payload bytes in a flow from the responder. The responder is the device which replies to the initiator, and remains the same for the life of the session.",octets,,"See #299, responderPackets.",[ipfix-iana_at_cisco.com],1,2014-08-13 233,firewallEvent,unsigned8,,current,"Indicates a firewall event. The allowed values are: 0 - Ignore (invalid) 1 - Flow Created 2 - Flow Deleted 3 - Flow Denied 4 - Flow Alert 5 - Flow Update",,,,[ipfix-iana_at_cisco.com],0,2013-02-18 234,ingressVRFID,unsigned32,,current,"An unique identifier of the VRFname where the packets of this flow are being received. This identifier is unique per Metering Process",,,,[ipfix-iana_at_cisco.com],0,2013-02-18 235,egressVRFID,unsigned32,,current,"An unique identifier of the VRFname where the packets of this flow are being sent. This identifier is unique per Metering Process",,,,[ipfix-iana_at_cisco.com],0,2013-02-18 236,VRFname,string,default,current,The name of a VPN Routing and Forwarding table (VRF).,,,See [RFC4364] for the definition of VRF.,[ipfix-iana_at_cisco.com],0,2013-02-18 237,postMplsTopLabelExp,unsigned8,flags,current,"The definition of this Information Element is identical to the definition of Information Element 'mplsTopLabelExp', except that it reports a potentially modified value caused by a middlebox function after the packet passed the Observation Point.",,,"See [RFC3032] for the specification of the Exp field. See [RFC3270] for usage of the Exp field.",[RFC5102],0,2013-02-18 238,tcpWindowScale,unsigned16,,current,The scale of the window field in the TCP header.,,,"See [RFC1323] for the definition of the TCP window scale.",[RFC5102],0,2013-02-18 239,biflowDirection,unsigned8,identifier,current,"A description of the direction assignment method used to assign the Biflow Source and Destination. This Information Element MAY be present in a Flow Data Record, or applied to all flows exported from an Exporting Process or Observation Domain using IPFIX Options. If this Information Element is not present in a Flow Record or associated with a Biflow via scope, it is assumed that the configuration of the direction assignment method is done out-of-band. Note that when using IPFIX Options to apply this Information Element to all flows within an Observation Domain or from an Exporting Process, the Option SHOULD be sent reliably. If reliable transport is not available (i.e., when using UDP), this Information Element SHOULD appear in each Flow Record. This field may take the following values: +-------+------------------+----------------------------------------+ | Value | Name | Description | +-------+------------------+----------------------------------------+ | 0x00 | arbitrary | Direction was assigned arbitrarily. | | 0x01 | initiator | The Biflow Source is the flow | | | | initiator, as determined by the | | | | Metering Process' best effort to | | | | detect the initiator. | | 0x02 | reverseInitiator | The Biflow Destination is the flow | | | | initiator, as determined by the | | | | Metering Process' best effort to | | | | detect the initiator. This value is | | | | provided for the convenience of | | | | Exporting Processes to revise an | | | | initiator estimate without re-encoding | | | | the Biflow Record. | | 0x03 | perimeter | The Biflow Source is the endpoint | | | | outside of a defined perimeter. The | | | | perimeter's definition is implicit in | | | | the set of Biflow Source and Biflow | | | | Destination addresses exported in the | | | | Biflow Records. | +-------+------------------+----------------------------------------+",,,,[RFC5103],0,2013-02-18 240,ethernetHeaderLength,unsigned8,quantity,current,"The difference between the length of an Ethernet frame (minus the FCS) and the length of its MAC Client Data section (including any padding) as defined in section 3.1 of [IEEE.802-3.2005]. It does not include the Preamble, SFD and Extension field lengths.",octets,,[IEEE.802-3.2005],[ipfix-iana_at_cisco.com],1,2014-08-13 241,ethernetPayloadLength,unsigned16,quantity,current,"The length of the MAC Client Data section (including any padding) of a frame as defined in section 3.1 of [IEEE.802-3.2005].",octets,,[IEEE.802-3.2005],[ipfix-iana_at_cisco.com],1,2014-08-13 242,ethernetTotalLength,unsigned16,quantity,current,"The total length of the Ethernet frame (excluding the Preamble, SFD, Extension and FCS fields) as described in section 3.1 of [IEEE.802-3.2005].",octets,,[IEEE.802-3.2005],[ipfix-iana_at_cisco.com],1,2014-08-13 243,dot1qVlanId,unsigned16,identifier,current,"The value of the 12-bit VLAN Identifier portion of the Tag Control Information field of an Ethernet frame. The structure and semantics within the Tag Control Information field are defined in [IEEE802.1Q]. In Provider Bridged Networks, it represents the Service VLAN identifier in the Service VLAN Tag (S-TAG) Tag Control Information (TCI) field or the Customer VLAN identifier in the Customer VLAN Tag (C-TAG) Tag Control Information (TCI) field as described in [IEEE802.1Q]. In Provider Backbone Bridged Networks, it represents the Backbone VLAN identifier in the Backbone VLAN Tag (B-TAG) Tag Control Information (TCI) field as described in [IEEE802.1Q]. In a virtual link between a host system and EVB bridge, it represents the Service VLAN identifier indicating S-channel as described in [IEEE802.1Qbg]. In the case of a multi-tagged frame, it represents the outer tag's VLAN identifier, except for I-TAG.",,,[IEEE802.1Q][IEEE802.1Qbg],[ipfix-iana_at_cisco.com][RFC7133],2,2014-01-11 244,dot1qPriority,unsigned8,identifier,current,"The value of the 3-bit User Priority portion of the Tag Control Information field of an Ethernet frame. The structure and semantics within the Tag Control Information field are defined in [IEEE802.1Q]. In the case of multi-tagged frame, it represents the 3-bit Priority Code Point (PCP) portion of the outer tag's Tag Control Information (TCI) field as described in [IEEE802.1Q], except for I-TAG.",,,[IEEE802.1Q],[ipfix-iana_at_cisco.com][RFC7133],1,2014-01-11 245,dot1qCustomerVlanId,unsigned16,identifier,current,"The value represents the Customer VLAN identifier in the Customer VLAN Tag (C-TAG) Tag Control Information (TCI) field as described in [IEEE802.1Q].",,,[IEEE802.1Q],[ipfix-iana_at_cisco.com][RFC7133],1,2014-01-11 246,dot1qCustomerPriority,unsigned8,identifier,current,"The value represents the 3-bit Priority Code Point (PCP) portion of the Customer VLAN Tag (C-TAG) Tag Control Information (TCI) field as described in [IEEE802.1Q].",,,[IEEE802.1Q],[ipfix-iana_at_cisco.com][RFC7133],1,2014-01-11 247,metroEvcId,string,default,current,"The EVC Service Attribute which uniquely identifies the Ethernet Virtual Connection (EVC) within a Metro Ethernet Network, as defined in section 6.2 of MEF 10.1. The MetroEVCID is encoded in a string of up to 100 characters.",,,"MEF 10.1 (Ethernet Services Attributes Phase 2) MEF16 (Ethernet Local Management Interface)",[ipfix-iana_at_cisco.com],1,2014-02-03 248,metroEvcType,unsigned8,identifier,current,"The 3-bit EVC Service Attribute which identifies the type of service provided by an EVC.",,,"MEF 10.1 (Ethernet Services Attributes Phase 2) MEF16 (Ethernet Local Management Interface)",[ipfix-iana_at_cisco.com],0,2013-02-18 249,pseudoWireId,unsigned32,identifier,current,"A 32-bit non-zero connection identifier, which together with the pseudoWireType, identifies the Pseudo Wire (PW) as defined in [RFC4447].",,,See [RFC4447] for pseudowire definitions.,[ipfix-iana_at_cisco.com],0,2013-02-18 250,pseudoWireType,unsigned16,identifier,current,"The value of this information element identifies the type of MPLS Pseudo Wire (PW) as defined in [RFC4446].",,,"See [RFC4446] for the pseudowire type definition, and http://www.iana.org/assignments/pwe3-parameters for the IANA Pseudowire Types Registry.",[ipfix-iana_at_cisco.com],0,2013-02-18 251,pseudoWireControlWord,unsigned32,identifier,current,"The 32-bit Preferred Pseudo Wire (PW) MPLS Control Word as defined in Section 3 of [RFC4385].",,,"See [RFC4385] for the Pseudo Wire Control Word definition.",[ipfix-iana_at_cisco.com],0,2013-02-18 252,ingressPhysicalInterface,unsigned32,identifier,current,"The index of a networking device's physical interface (example, a switch port) where packets of this flow are being received.",,,See [RFC2863] for the definition of the ifIndex object.,[ipfix-iana_at_cisco.com],0,2013-02-18 253,egressPhysicalInterface,unsigned32,identifier,current,"The index of a networking device's physical interface (example, a switch port) where packets of this flow are being sent.",,,See [RFC2863] for the definition of the ifIndex object.,[ipfix-iana_at_cisco.com],0,2013-02-18 254,postDot1qVlanId,unsigned16,identifier,current,"The definition of this Information Element is identical to the definition of Information Element 'dot1qVlanId', except that it reports a potentially modified value caused by a middlebox function after the packet passed the Observation Point.",,,"[IEEE.802-3.2005] [IEEE.802-1ad.2005]",[ipfix-iana_at_cisco.com],0,2013-02-18 255,postDot1qCustomerVlanId,unsigned16,identifier,current,"The definition of this Information Element is identical to the definition of Information Element 'dot1qCustomerVlanId', except that it reports a potentially modified value caused by a middlebox function after the packet passed the Observation Point.",,,"[IEEE.802-1ad.2005] [IEEE.802-1Q.2003]",[ipfix-iana_at_cisco.com],0,2013-02-18 256,ethernetType,unsigned16,identifier,current,"The Ethernet type field of an Ethernet frame that identifies the MAC client protocol carried in the payload as defined in paragraph 1.4.349 of [IEEE.802-3.2005].",,,"[IEEE.802-3.2005] Ethertype registry available at [http://standards.ieee.org/regauth/ethertype/eth.txt]",[ipfix-iana_at_cisco.com],0,2013-02-18 257,postIpPrecedence,unsigned8,identifier,current,"The definition of this Information Element is identical to the definition of Information Element 'ipPrecedence', except that it reports a potentially modified value caused by a middlebox function after the packet passed the Observation Point.",,0-7,"See [RFC1812] (Section 5.3.3) and [RFC791] for the definition of the IP Precedence. See [RFC1812] (Section 5.3.2) and [RFC791] for the definition of the IPv4 TOS field. See [RFC2460] for the definition of the IPv6 Traffic Class field.",[ipfix-iana_at_cisco.com],0,2013-02-18 258,collectionTimeMilliseconds,dateTimeMilliseconds,default,current,"The absolute timestamp at which the data within the scope containing this Information Element was received by a Collecting Process. This Information Element SHOULD be bound to its containing IPFIX Message via IPFIX Options and the messageScope Information Element, as defined below.",milliseconds,,,"[RFC5655][RFC Errata 3559]",1,2013-03-26 259,exportSctpStreamId,unsigned16,identifier,current,"The value of the SCTP Stream Identifier used by the Exporting Process for exporting IPFIX Message data. This is carried in the Stream Identifier field of the header of the SCTP DATA chunk containing the IPFIX Message(s).",,,,[RFC5655],0,2013-02-18 260,maxExportSeconds,dateTimeSeconds,default,current,"The absolute Export Time of the latest IPFIX Message within the scope containing this Information Element. This Information Element SHOULD be bound to its containing IPFIX Transport Session via IPFIX Options and the sessionScope Information Element.",seconds,,,[RFC5655],0,2013-02-18 261,maxFlowEndSeconds,dateTimeSeconds,default,current,"The latest absolute timestamp of the last packet within any Flow within the scope containing this Information Element, rounded up to the second if necessary. This Information Element SHOULD be bound to its containing IPFIX Transport Session via IPFIX Options and the sessionScope Information Element.",seconds,,,[RFC5655],0,2013-02-18 262,messageMD5Checksum,octetArray,default,current,"The MD5 checksum of the IPFIX Message containing this record. This Information Element SHOULD be bound to its containing IPFIX Message via an options record and the messageScope Information Element, as defined below, and SHOULD appear only once in a given IPFIX Message. To calculate the value of this Information Element, first buffer the containing IPFIX Message, setting the value of this Information Element to all zeroes. Then calculate the MD5 checksum of the resulting buffer as defined in [RFC1321], place the resulting value in this Information Element, and export the buffered message. This Information Element is intended as a simple checksum only; therefore collision resistance and algorithm agility are not required, and MD5 is an appropriate message digest. This Information Element has a fixed length of 16 octets.",,,,[RFC5655][RFC1321],0,2013-02-18 263,messageScope,unsigned8,,current,"The presence of this Information Element as scope in an Options Template signifies that the options described by the Template apply to the IPFIX Message that contains them. It is defined for general purpose message scoping of options, and proposed specifically to allow the attachment a checksum to a message via IPFIX Options. The value of this Information Element MUST be written as 0 by the File Writer or Exporting Process. The value of this Information Element MUST be ignored by the File Reader or the Collecting Process.",,0-0,,[RFC5655],0,2013-02-18 264,minExportSeconds,dateTimeSeconds,default,current,"The absolute Export Time of the earliest IPFIX Message within the scope containing this Information Element. This Information Element SHOULD be bound to its containing IPFIX Transport Session via an options record and the sessionScope Information Element.",seconds,,,[RFC5655],0,2013-02-18 265,minFlowStartSeconds,dateTimeSeconds,default,current,"The earliest absolute timestamp of the first packet within any Flow within the scope containing this Information Element, rounded down to the second if necessary. This Information Element SHOULD be bound to its containing IPFIX Transport Session via an options record and the sessionScope Information Element.",seconds,,,[RFC5655],0,2013-02-18 266,opaqueOctets,octetArray,default,current,"This Information Element is used to encapsulate non- IPFIX data into an IPFIX Message stream, for the purpose of allowing a non-IPFIX data processor to store a data stream inline within an IPFIX File. A Collecting Process or File Writer MUST NOT try to interpret this binary data. This Information Element differs from paddingOctets as its contents are meaningful in some non-IPFIX context, while the contents of paddingOctets MUST be 0x00 and are intended only for Information Element alignment.",,,,[RFC5655],0,2013-02-18 267,sessionScope,unsigned8,,current,"The presence of this Information Element as scope in an Options Template signifies that the options described by the Template apply to the IPFIX Transport Session that contains them. Note that as all options are implicitly scoped to Transport Session and Observation Domain, this Information Element is equivalent to a ""null"" scope. It is defined for general purpose session scoping of options, and proposed specifically to allow the attachment of time window to an IPFIX File via IPFIX Options. The value of this Information Element MUST be written as 0 by the File Writer or Exporting Process. The value of this Information Element MUST be ignored by the File Reader or the Collecting Process.",,0-0,,[RFC5655],0,2013-02-18 268,maxFlowEndMicroseconds,dateTimeMicroseconds,default,current,"The latest absolute timestamp of the last packet within any Flow within the scope containing this Information Element, rounded up to the microsecond if necessary. This Information Element SHOULD be bound to its containing IPFIX Transport Session via IPFIX Options and the sessionScope Information Element. This Information Element SHOULD be used only in Transport Sessions containing Flow Records with microsecond- precision (or better) timestamp Information Elements.",microseconds,,,[RFC5655],0,2013-02-18 269,maxFlowEndMilliseconds,dateTimeMilliseconds,default,current,"The latest absolute timestamp of the last packet within any Flow within the scope containing this Information Element, rounded up to the millisecond if necessary. This Information Element SHOULD be bound to its containing IPFIX Transport Session via IPFIX Options and the sessionScope Information Element. This Information Element SHOULD be used only in Transport Sessions containing Flow Records with millisecond- precision (or better) timestamp Information Elements.",milliseconds,,,[RFC5655],0,2013-02-18 270,maxFlowEndNanoseconds,dateTimeNanoseconds,default,current,"The latest absolute timestamp of the last packet within any Flow within the scope containing this Information Element. This Information Element SHOULD be bound to its containing IPFIX Transport Session via IPFIX Options and the sessionScope Information Element. This Information Element SHOULD be used only in Transport Sessions containing Flow Records with nanosecond-precision timestamp Information Elements.",nanoseconds,,,[RFC5655],0,2013-02-18 271,minFlowStartMicroseconds,dateTimeMicroseconds,default,current,"The earliest absolute timestamp of the first packet within any Flow within the scope containing this Information Element, rounded down to the microsecond if necessary. This Information Element SHOULD be bound to its containing IPFIX Transport Session via an options record and the sessionScope Information Element. This Information Element SHOULD be used only in Transport Sessions containing Flow Records with microsecond- precision (or better) timestamp Information Elements.",microseconds,,,[RFC5655],0,2013-02-18 272,minFlowStartMilliseconds,dateTimeMilliseconds,default,current,"The earliest absolute timestamp of the first packet within any Flow within the scope containing this Information Element, rounded down to the millisecond if necessary. This Information Element SHOULD be bound to its containing IPFIX Transport Session via an options record and the sessionScope Information Element. This Information Element SHOULD be used only in Transport Sessions containing Flow Records with millisecond- precision (or better) timestamp Information Elements.",milliseconds,,,[RFC5655],0,2013-02-18 273,minFlowStartNanoseconds,dateTimeNanoseconds,default,current,"The earliest absolute timestamp of the first packet within any Flow within the scope containing this Information Element. This Information Element SHOULD be bound to its containing IPFIX Transport Session via an options record and the sessionScope Information Element. This Information Element SHOULD be used only in Transport Sessions containing Flow Records with nanosecond-precision timestamp Information Elements.",nanoseconds,,,[RFC5655],0,2013-02-18 274,collectorCertificate,octetArray,default,current,"The full X.509 certificate, encoded in ASN.1 DER format, used by the Collector when IPFIX Messages were transmitted using TLS or DTLS. This Information Element SHOULD be bound to its containing IPFIX Transport Session via an options record and the sessionScope Information Element, or to its containing IPFIX Message via an options record and the messageScope Information Element.",,,,[RFC5655],0,2013-02-18 275,exporterCertificate,octetArray,default,current,"The full X.509 certificate, encoded in ASN.1 DER format, used by the Collector when IPFIX Messages were transmitted using TLS or DTLS. This Information Element SHOULD be bound to its containing IPFIX Transport Session via an options record and the sessionScope Information Element, or to its containing IPFIX Message via an options record and the messageScope Information Element.",,,,[RFC5655],0,2013-02-18 276,dataRecordsReliability,boolean,default,current,"The export reliability of Data Records, within this SCTP stream, for the element(s) in the Options Template scope. A typical example of an element for which the export reliability will be reported is the templateID, as specified in the Data Records Reliability Options Template. A value of 'True' means that the Exporting Process MUST send any Data Records associated with the element(s) reliably within this SCTP stream. A value of 'False' means that the Exporting Process MAY send any Data Records associated with the element(s) unreliably within this SCTP stream.",,,,[RFC6526],1,2014-02-03 277,observationPointType,unsigned8,identifier,current,"Type of observation point. Values assigned to date are: 1. Physical port 2. Port channel 3. Vlan.",,,,[ipfix-iana_at_cisco.com],0,2013-02-18 278,newConnectionDeltaCount,unsigned32,deltaCounter,current,"This information element counts the number of TCP or UDP connections which were opened during the observation period. The observation period may be specified by the flow start and end timestamps.",,,,[ipfix-iana_at_cisco.com],1,2014-08-13 279,connectionSumDurationSeconds,unsigned64,,current,"This information element aggregates the total time in seconds for all of the TCP or UDP connections which were in use during the observation period. For example if there are 5 concurrent connections each for 10 seconds, the value would be 50 s.",seconds,,,[ipfix-iana_at_cisco.com],1,2013-06-25 280,connectionTransactionId,unsigned64,identifier,current,"This information element identifies a transaction within a connection. A transaction is a meaningful exchange of application data between two network devices or a client and server. A transactionId is assigned the first time a flow is reported, so that later reports for the same flow will have the same transactionId. A different transactionId is used for each transaction within a TCP or UDP connection. The identifiers need not be sequential.",,,,[ipfix-iana_at_cisco.com],0,2013-02-18 281,postNATSourceIPv6Address,ipv6Address,default,current,"The definition of this Information Element is identical to the definition of Information Element 'sourceIPv6Address', except that it reports a modified value caused by a NAT64 middlebox function after the packet passed the Observation Point. See [RFC2460] for the definition of the Source Address field in the IPv6 header. See [RFC3234] for the definition of middleboxes. See [RFC6146] for nat64 specification.",,,,[ipfix-iana_at_cisco.com],0,2013-02-18 282,postNATDestinationIPv6Address,ipv6Address,default,current,"The definition of this Information Element is identical to the definition of Information Element 'destinationIPv6Address', except that it reports a modified value caused by a NAT64 middlebox function after the packet passed the Observation Point. See [RFC2460] for the definition of the Destination Address field in the IPv6 header. See [RFC3234] for the definition of middleboxes. See [RFC6146] for nat64 specification.",,,,[ipfix-iana_at_cisco.com],0,2013-02-18 283,natPoolId,unsigned32,identifier,current,Locally unique identifier of a NAT pool.,,,,[ipfix-iana_at_cisco.com],0,2013-02-18 284,natPoolName,string,default,current,The name of a NAT pool identified by a natPoolID.,,,,[ipfix-iana_at_cisco.com],0,2013-02-18 285,anonymizationFlags,unsigned16,flags,current,"A flag word describing specialized modifications to the anonymization policy in effect for the anonymization technique applied to a referenced Information Element within a referenced Template. When flags are clear (0), the normal policy (as described by anonymizationTechnique) applies without modification. MSB 14 13 12 11 10 9 8 7 6 5 4 3 2 1 LSB +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | Reserved |LOR|PmA| SC | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ anonymizationFlags IE +--------+----------+-----------------------------------------------+ | bit(s) | name | description | | (LSB = | | | | 0) | | | +--------+----------+-----------------------------------------------+ | 0-1 | SC | Stability Class: see the Stability Class | | | | table below, and section Section 5.1. | | 2 | PmA | Perimeter Anonymization: when set (1), | | | | source- Information Elements as described in | | | | [RFC5103] are interpreted as external | | | | addresses, and destination- Information | | | | Elements as described in [RFC5103] are | | | | interpreted as internal addresses, for the | | | | purposes of associating | | | | anonymizationTechnique to Information | | | | Elements only; see Section 7.2.2 for details. | | | | This bit MUST NOT be set when associated with | | | | a non-endpoint (i.e., source- or | | | | destination-) Information Element. SHOULD be | | | | consistent within a record (i.e., if a | | | | source- Information Element has this flag | | | | set, the corresponding destination- element | | | | SHOULD have this flag set, and vice-versa.) | | 3 | LOR | Low-Order Unchanged: when set (1), the | | | | low-order bits of the anonymized Information | | | | Element contain real data. This modification | | | | is intended for the anonymization of | | | | network-level addresses while leaving | | | | host-level addresses intact in order to | | | | preserve host level-structure, which could | | | | otherwise be used to reverse anonymization. | | | | MUST NOT be set when associated with a | | | | truncation-based anonymizationTechnique. | | 4-15 | Reserved | Reserved for future use: SHOULD be cleared | | | | (0) by the Exporting Process and MUST be | | | | ignored by the Collecting Process. | +--------+----------+-----------------------------------------------+ The Stability Class portion of this flags word describes the stability class of the anonymization technique applied to a referenced Information Element within a referenced Template. Stability classes refer to the stability of the parameters of the anonymization technique, and therefore the comparability of the mapping between the real and anonymized values over time. This determines which anonymized datasets may be compared with each other. Values are as follows: +-----+-----+-------------------------------------------------------+ | Bit | Bit | Description | | 1 | 0 | | +-----+-----+-------------------------------------------------------+ | 0 | 0 | Undefined: the Exporting Process makes no | | | | representation as to how stable the mapping is, or | | | | over what time period values of this field will | | | | remain comparable; while the Collecting Process MAY | | | | assume Session level stability, Session level | | | | stability is not guaranteed. Processes SHOULD assume | | | | this is the case in the absence of stability class | | | | information; this is the default stability class. | | 0 | 1 | Session: the Exporting Process will ensure that the | | | | parameters of the anonymization technique are stable | | | | during the Transport Session. All the values of the | | | | described Information Element for each Record | | | | described by the referenced Template within the | | | | Transport Session are comparable. The Exporting | | | | Process SHOULD endeavour to ensure at least this | | | | stability class. | | 1 | 0 | Exporter-Collector Pair: the Exporting Process will | | | | ensure that the parameters of the anonymization | | | | technique are stable across Transport Sessions over | | | | time with the given Collecting Process, but may use | | | | different parameters for different Collecting | | | | Processes. Data exported to different Collecting | | | | Processes are not comparable. | | 1 | 1 | Stable: the Exporting Process will ensure that the | | | | parameters of the anonymization technique are stable | | | | across Transport Sessions over time, regardless of | | | | the Collecting Process to which it is sent. | +-----+-----+-------------------------------------------------------+",,,,[RFC6235],0,2013-02-18 286,anonymizationTechnique,unsigned16,identifier,current,"A description of the anonymization technique applied to a referenced Information Element within a referenced Template. Each technique may be applicable only to certain Information Elements and recommended only for certain Infomation Elements; these restrictions are noted in the table below. +-------+---------------------------+-----------------+-------------+ | Value | Description | Applicable to | Recommended | | | | | for | +-------+---------------------------+-----------------+-------------+ | 0 | Undefined: the Exporting | all | all | | | Process makes no | | | | | representation as to | | | | | whether the defined field | | | | | is anonymized or not. | | | | | While the Collecting | | | | | Process MAY assume that | | | | | the field is not | | | | | anonymized, it is not | | | | | guaranteed not to be. | | | | | This is the default | | | | | anonymization technique. | | | | 1 | None: the values exported | all | all | | | are real. | | | | 2 | Precision | all | all | | | Degradation/Truncation: | | | | | the values exported are | | | | | anonymized using simple | | | | | precision degradation or | | | | | truncation. The new | | | | | precision or number of | | | | | truncated bits is | | | | | implicit in the exported | | | | | data, and can be deduced | | | | | by the Collecting | | | | | Process. | | | | 3 | Binning: the values | all | all | | | exported are anonymized | | | | | into bins. | | | | 4 | Enumeration: the values | all | timestamps | | | exported are anonymized | | | | | by enumeration. | | | | 5 | Permutation: the values | all | identifiers | | | exported are anonymized | | | | | by permutation. | | | | 6 | Structured Permutation: | addresses | | | | the values exported are | | | | | anonymized by | | | | | permutation, preserving | | | | | bit-level structure as | | | | | appropriate; this | | | | | represents | | | | | prefix-preserving IP | | | | | address anonymization or | | | | | structured MAC address | | | | | anonymization. | | | | 7 | Reverse Truncation: the | addresses | | | | values exported are | | | | | anonymized using reverse | | | | | truncation. The number | | | | | of truncated bits is | | | | | implicit in the exported | | | | | data, and can be deduced | | | | | by the Collecting | | | | | Process. | | | | 8 | Noise: the values | non-identifiers | counters | | | exported are anonymized | | | | | by adding random noise to | | | | | each value. | | | | 9 | Offset: the values | all | timestamps | | | exported are anonymized | | | | | by adding a single offset | | | | | to all values. | | | +-------+---------------------------+-----------------+-------------+",,,,[RFC6235],0,2013-02-18 287,informationElementIndex,unsigned16,identifier,current,"A zero-based index of an Information Element referenced by informationElementId within a Template referenced by templateId; used to disambiguate scope for templates containing multiple identical Information Elements.",,,,[RFC6235],0,2013-02-18 288,p2pTechnology,string,default,current,"Specifies if the Application ID is based on peer-to-peer technology. Possible values are: { ""yes"", ""y"", 1 }, { ""no"", ""n"", 2 } and { ""unassigned"", ""u"", 0 }.",,,,[RFC6759],0,2013-02-18 289,tunnelTechnology,string,default,current,"Specifies if the Application ID is used as a tunnel technology. Possible values are: { ""yes"", ""y"", 1 }, { ""no"", ""n"", 2 } and { ""unassigned"", ""u"", 0 }.",,,,[RFC6759],0,2013-02-18 290,encryptedTechnology,string,default,current,"Specifies if the Application ID is an encrypted networking protocol. Possible values are: { ""yes"", ""y"", 1 }, { ""no"", ""n"", 2 } and { ""unassigned"", ""u"", 0 }.",,,,[RFC6759],0,2013-02-18 291,basicList,basicList,list,current,"Specifies a generic Information Element with a basicList abstract data type. For example, a list of port numbers, a list of interface indexes, etc.",,,,[RFC6313],0,2013-02-18 292,subTemplateList,subTemplateList,list,current,"Specifies a generic Information Element with a subTemplateList abstract data type.",,,,[RFC6313],0,2013-02-18 293,subTemplateMultiList,subTemplateMultiList,list,current,"Specifies a generic Information Element with a subTemplateMultiList abstract data type.",,,,[RFC6313],0,2013-02-18 294,bgpValidityState,unsigned8,identifier,current,"This element describes the ""validity state"" of the BGP route correspondent source or destination IP address. If the ""validity state"" for this Flow is only available, then the value of this Information Element is 255.",,,"See [RFC4271] for a description of BGP-4, [RFC6811] for the definition of ""validity states"" and [draft-ietf-sidr-origin-validation-signaling] for the encoding of those ""validity states"".",[ipfix-iana_at_cisco.com],0,2013-02-18 295,IPSecSPI,unsigned32,identifier,current,IPSec Security Parameters Index (SPI).,,,See [RFC2401] for the definition of SPI.,[ipfix-iana_at_cisco.com],0,2013-02-18 296,greKey,unsigned32,identifier,current,"GRE key, which is used for identifying an individual traffic flow within a tunnel.",,,See [RFC1701] for the definition of GRE and the GRE Key.,[ipfix-iana_at_cisco.com],0,2013-02-18 297,natType,unsigned8,identifier,current,"The type of NAT treatment: 0 unknown 1 NAT44 translated 2 NAT64 translated 3 NAT46 translated 4 IPv4-->IPv4 (no NAT) 5 NAT66 translated 6 IPv6-->IPv6 (no NAT)",,,"See [RFC3022] for the definition of NAT. See [RFC1631] for the definition of NAT44. See [RFC6144] for the definition of NAT64. See [RFC6146] for the definition of NAT46. See [RFC6296] for the definition of NAT66. See [RFC791] for the definition of IPv4. See [RFC2460] for the definition of IPv6.",[ipfix-iana_at_cisco.com],0,2013-02-18 298,initiatorPackets,unsigned64,deltaCounter,current,"The total number of layer 4 packets in a flow from the initiator. The initiator is the device which triggered the session creation, and remains the same for the life of the session.",packets,,"See #231, initiatorOctets.",[ipfix-iana_at_cisco.com],1,2014-08-13 299,responderPackets,unsigned64,deltaCounter,current,"The total number of layer 4 packets in a flow from the responder. The responder is the device which replies to the initiator, and remains the same for the life of the session.",packets,,"See #232, responderOctets.",[ipfix-iana_at_cisco.com],1,2014-08-13 300,observationDomainName,string,default,current,"The name of an observation domain identified by an observationDomainId.",,,"See #149, observationDomainId.",[ipfix-iana_at_cisco.com],0,2013-02-18 301,selectionSequenceId,unsigned64,identifier,current,"From all the packets observed at an Observation Point, a subset of the packets is selected by a sequence of one or more Selectors. The selectionSequenceId is a unique value per Observation Domain, specifying the Observation Point and the sequence of Selectors through which the packets are selected.",,,,[RFC5477],0,2013-02-18 302,selectorId,unsigned64,identifier,current,"The Selector ID is the unique ID identifying a Primitive Selector. Each Primitive Selector must have a unique ID in the Observation Domain.",,,,"[RFC5477][RFC Errata 2052]",0,2013-02-18 303,informationElementId,unsigned16,identifier,current,"This Information Element contains the ID of another Information Element.",,,,[RFC5477],0,2013-02-18 304,selectorAlgorithm,unsigned16,identifier,current,"This Information Element identifies the packet selection methods (e.g., Filtering, Sampling) that are applied by the Selection Process. Most of these methods have parameters. Further Information Elements are needed to fully specify packet selection with these methods and all their parameters. The methods listed below are defined in [RFC5475]. For their parameters, Information Elements are defined in the information model document. The names of these Information Elements are listed for each method identifier. Further method identifiers may be added to the list below. It might be necessary to define new Information Elements to specify their parameters. The selectorAlgorithm registry is maintained by IANA. New assignments for the registry will be administered by IANA, and are subject to Expert Review [RFC5226]. The registry can be updated when specifications of the new method(s) and any new Information Elements are provided. The group of experts must double check the selectorAlgorithm definitions and Information Elements with already defined selectorAlgorithms and Information Elements for completeness, accuracy, and redundancy. Those experts will initially be drawn from the Working Group Chairs and document editors of the IPFIX and PSAMP Working Groups. The following packet selection methods identifiers are defined here: [IANA registry psamp-parameters] There is a broad variety of possible parameters that could be used for Property match Filtering (5) but currently there are no agreed parameters specified.",,,,[RFC5477],0,2013-02-18 305,samplingPacketInterval,unsigned32,quantity,current,"This Information Element specifies the number of packets that are consecutively sampled. A value of 100 means that 100 consecutive packets are sampled. For example, this Information Element may be used to describe the configuration of a systematic count-based Sampling Selector.",packets,,,[RFC5477],0,2013-02-18 306,samplingPacketSpace,unsigned32,quantity,current,"This Information Element specifies the number of packets between two ""samplingPacketInterval""s. A value of 100 means that the next interval starts 100 packets (which are not sampled) after the current ""samplingPacketInterval"" is over. For example, this Information Element may be used to describe the configuration of a systematic count-based Sampling Selector.",packets,,,[RFC5477],0,2013-02-18 307,samplingTimeInterval,unsigned32,quantity,current,"This Information Element specifies the time interval in microseconds during which all arriving packets are sampled. For example, this Information Element may be used to describe the configuration of a systematic time-based Sampling Selector.",microseconds,,,[RFC5477],0,2013-02-18 308,samplingTimeSpace,unsigned32,quantity,current,"This Information Element specifies the time interval in microseconds between two ""samplingTimeInterval""s. A value of 100 means that the next interval starts 100 microseconds (during which no packets are sampled) after the current ""samplingTimeInterval"" is over. For example, this Information Element may used to describe the configuration of a systematic time-based Sampling Selector.",microseconds,,,[RFC5477],0,2013-02-18 309,samplingSize,unsigned32,quantity,current,"This Information Element specifies the number of elements taken from the parent Population for random Sampling methods. For example, this Information Element may be used to describe the configuration of a random n-out-of-N Sampling Selector.",packets,,,[RFC5477],0,2013-02-18 310,samplingPopulation,unsigned32,quantity,current,"This Information Element specifies the number of elements in the parent Population for random Sampling methods. For example, this Information Element may be used to describe the configuration of a random n-out-of-N Sampling Selector.",packets,,,[RFC5477],0,2013-02-18 311,samplingProbability,float64,quantity,current,"This Information Element specifies the probability that a packet is sampled, expressed as a value between 0 and 1. The probability is equal for every packet. A value of 0 means no packet was sampled since the probability is 0. For example, this Information Element may be used to describe the configuration of a uniform probabilistic Sampling Selector.",,,,[RFC5477],0,2013-02-18 312,dataLinkFrameSize,unsigned16,quantity,current,"This Information Element specifies the length of the selected data link frame. The data link layer is defined in [ISO/IEC.7498-1:1994].",,,[ISO/IEC.7498-1:1994],[RFC7133],1,2014-01-11 313,ipHeaderPacketSection,octetArray,default,current,"This Information Element carries a series of n octets from the IP header of a sampled packet, starting sectionOffset octets into the IP header. However, if no sectionOffset field corresponding to this Information Element is present, then a sectionOffset of zero applies, and the octets MUST be from the start of the IP header. With sufficient length, this element also reports octets from the IP payload. However, full packet capture of arbitrary packet streams is explicitly out of scope per the Security Considerations sections of [RFC5477] and [RFC2804]. The sectionExportedOctets expresses how much data was exported, while the remainder is padding. When the sectionExportedOctets field corresponding to this Information Element exists, this Information Element MAY have a fixed length and MAY be padded, or it MAY have a variable length. When the sectionExportedOctets field corresponding to this Information Element does not exist, this Information Element SHOULD have a variable length and MUST NOT be padded. In this case, the size of the exported section may be constrained due to limitations in the IPFIX protocol.",,,"[RFC2804] [RFC5477]",[RFC5477][RFC7133],1,2014-01-11 314,ipPayloadPacketSection,octetArray,default,current,"This Information Element carries a series of n octets from the IP payload of a sampled packet, starting sectionOffset octets into the IP payload. However, if no sectionOffset field corresponding to this Information Element is present, then a sectionOffset of zero applies, and the octets MUST be from the start of the IP payload. The IPv4 payload is that part of the packet that follows the IPv4 header and any options, which [RFC791] refers to as ""data"" or ""data octets"". For example, see the examples in [RFC791], Appendix A. The IPv6 payload is the rest of the packet following the 40-octet IPv6 header. Note that any extension headers present are considered part of the payload. See [RFC2460] for the IPv6 specification. The sectionExportedOctets expresses how much data was observed, while the remainder is padding. When the sectionExportedOctets field corresponding to this Information Element exists, this Information Element MAY have a fixed length and MAY be padded, or MAY have a variable length. When the sectionExportedOctets field corresponding to this Information Element does not exist, this Information Element SHOULD have a variable length and MUST NOT be padded. In this case, the size of the exported section may be constrained due to limitations in the IPFIX protocol.",,,"[RFC791] [RFC2460]",[RFC5477][RFC7133],1,2014-01-11 315,dataLinkFrameSection,octetArray,default,current,"This Information Element carries n octets from the data link frame of a selected frame, starting sectionOffset octets into the frame. However, if no sectionOffset field corresponding to this Information Element is present, then a sectionOffset of zero applies, and the octets MUST be from the start of the data link frame. The sectionExportedOctets expresses how much data was observed, while the remainder is padding. When the sectionExportedOctets field corresponding to this Information Element exists, this Information Element MAY have a fixed length and MAY be padded, or MAY have a variable length. When the sectionExportedOctets field corresponding to this Information Element does not exist, this Information Element SHOULD have a variable length and MUST NOT be padded. In this case, the size of the exported section may be constrained due to limitations in the IPFIX protocol. Further Information Elements, i.e., dataLinkFrameType and dataLinkFrameSize, are needed to specify the data link type and the size of the data link frame of this Information Element. A set of these Information Elements MAY be contained in a structured data type, as expressed in [RFC6313]. Or a set of these Information Elements MAY be contained in one Flow Record as shown in Appendix B of [RFC7133]. The data link layer is defined in [ISO/IEC.7498-1:1994].",,,"[RFC6313] [RFC7133] [ISO/IEC.7498-1:1994]",[RFC7133],1,2014-01-11 316,mplsLabelStackSection,octetArray,default,current,"This Information Element carries a series of n octets from the MPLS label stack of a sampled packet, starting sectionOffset octets into the MPLS label stack. However, if no sectionOffset field corresponding to this Information Element is present, then a sectionOffset of zero applies, and the octets MUST be from the head of the MPLS label stack. With sufficient length, this element also reports octets from the MPLS payload. However, full packet capture of arbitrary packet streams is explicitly out of scope per the Security Considerations sections of [RFC5477] and [RFC2804]. See [RFC3031] for the specification of MPLS packets. See [RFC3032] for the specification of the MPLS label stack. The sectionExportedOctets expresses how much data was observed, while the remainder is padding. When the sectionExportedOctets field corresponding to this Information Element exists, this Information Element MAY have a fixed length and MAY be padded, or MAY have a variable length. When the sectionExportedOctets field corresponding to this Information Element does not exist, this Information Element SHOULD have a variable length and MUST NOT be padded. In this case, the size of the exported section may be constrained due to limitations in the IPFIX protocol.",,,"[RFC2804] [RFC3031] [RFC3032] [RFC5477]",[RFC5477][RFC7133],1,2014-01-11 317,mplsPayloadPacketSection,octetArray,default,current,"The mplsPayloadPacketSection carries a series of n octets from the MPLS payload of a sampled packet, starting sectionOffset octets into the MPLS payload, as it is data that follows immediately after the MPLS label stack. However, if no sectionOffset field corresponding to this Information Element is present, then a sectionOffset of zero applies, and the octets MUST be from the start of the MPLS payload. See [RFC3031] for the specification of MPLS packets. See [RFC3032] for the specification of the MPLS label stack. The sectionExportedOctets expresses how much data was observed, while the remainder is padding. When the sectionExportedOctets field corresponding to this Information Element exists, this Information Element MAY have a fixed length and MAY be padded, or it MAY have a variable length. When the sectionExportedOctets field corresponding to this Information Element does not exist, this Information Element SHOULD have a variable length and MUST NOT be padded. In this case, the size of the exported section may be constrained due to limitations in the IPFIX protocol.",,,"[RFC3031] [RFC3032]",[RFC5477][RFC7133],1,2014-01-11 318,selectorIdTotalPktsObserved,unsigned64,totalCounter,current,"This Information Element specifies the total number of packets observed by a Selector, for a specific value of SelectorId. This Information Element should be used in an Options Template scoped to the observation to which it refers. See Section 3.4.2.1 of the IPFIX protocol document [RFC7011].",packets,,,[RFC5477],0,2013-02-18 319,selectorIdTotalPktsSelected,unsigned64,totalCounter,current,"This Information Element specifies the total number of packets selected by a Selector, for a specific value of SelectorId. This Information Element should be used in an Options Template scoped to the observation to which it refers. See Section 3.4.2.1 of the IPFIX protocol document [RFC7011].",packets,,,[RFC5477],0,2013-02-18 320,absoluteError,float64,quantity,current,"This Information Element specifies the maximum possible measurement error of the reported value for a given Information Element. The absoluteError has the same unit as the Information Element with which it is associated. The real value of the metric can differ by absoluteError (positive or negative) from the measured value. This Information Element provides only the error for measured values. If an Information Element contains an estimated value (from Sampling), the confidence boundaries and confidence level have to be provided instead, using the upperCILimit, lowerCILimit, and confidenceLevel Information Elements. This Information Element should be used in an Options Template scoped to the observation to which it refers. See Section 3.4.2.1 of the IPFIX protocol document [RFC7011].",The units of the Information Element for which the error is specified.,,,[RFC5477],0,2013-02-18 321,relativeError,float64,quantity,current,"This Information Element specifies the maximum possible positive or negative error ratio for the reported value for a given Information Element as percentage of the measured value. The real value of the metric can differ by relativeError percent (positive or negative) from the measured value. This Information Element provides only the error for measured values. If an Information Element contains an estimated value (from Sampling), the confidence boundaries and confidence level have to be provided instead, using the upperCILimit, lowerCILimit, and confidenceLevel Information Elements. This Information Element should be used in an Options Template scoped to the observation to which it refers. See Section 3.4.2.1 of the IPFIX protocol document [RFC7011].",,,,[RFC5477],0,2013-02-18 322,observationTimeSeconds,dateTimeSeconds,default,current,"This Information Element specifies the absolute time in seconds of an observation.",seconds,,,[RFC5477],1,2014-02-03 323,observationTimeMilliseconds,dateTimeMilliseconds,default,current,"This Information Element specifies the absolute time in milliseconds of an observation.",milliseconds,,,[RFC5477],1,2014-02-03 324,observationTimeMicroseconds,dateTimeMicroseconds,default,current,"This Information Element specifies the absolute time in microseconds of an observation.",microseconds,,,[RFC5477],1,2014-02-03 325,observationTimeNanoseconds,dateTimeNanoseconds,default,current,"This Information Element specifies the absolute time in nanoseconds of an observation.",nanoseconds,,,[RFC5477],1,2014-02-03 326,digestHashValue,unsigned64,quantity,current,"This Information Element specifies the value from the digest hash function. See also Sections 6.2, 3.8 and 7.1 of [RFC5475].",,,,[RFC5477],0,2013-02-18 327,hashIPPayloadOffset,unsigned64,quantity,current,"This Information Element specifies the IP payload offset used by a Hash-based Selection Selector. See also Sections 6.2, 3.8 and 7.1 of [RFC5475].",,,,[RFC5477],0,2013-02-18 328,hashIPPayloadSize,unsigned64,quantity,current,"This Information Element specifies the IP payload size used by a Hash-based Selection Selector. See also Sections 6.2, 3.8 and 7.1 of [RFC5475].",,,,[RFC5477],0,2013-02-18 329,hashOutputRangeMin,unsigned64,quantity,current,"This Information Element specifies the value for the beginning of a hash function's potential output range. See also Sections 6.2, 3.8 and 7.1 of [RFC5475].",,,,[RFC5477],0,2013-02-18 330,hashOutputRangeMax,unsigned64,quantity,current,"This Information Element specifies the value for the end of a hash function's potential output range. See also Sections 6.2, 3.8 and 7.1 of [RFC5475].",,,,[RFC5477],0,2013-02-18 331,hashSelectedRangeMin,unsigned64,quantity,current,"This Information Element specifies the value for the beginning of a hash function's selected range. See also Sections 6.2, 3.8 and 7.1 of [RFC5475].",,,,[RFC5477],0,2013-02-18 332,hashSelectedRangeMax,unsigned64,quantity,current,"This Information Element specifies the value for the end of a hash function's selected range. See also Sections 6.2, 3.8 and 7.1 of [RFC5475].",,,,[RFC5477],0,2013-02-18 333,hashDigestOutput,boolean,default,current,"This Information Element contains a boolean value that is TRUE if the output from this hash Selector has been configured to be included in the packet report as a packet digest, else FALSE. See also Sections 6.2, 3.8 and 7.1 of [RFC5475].",,,,[RFC5477],1,2014-02-03 334,hashInitialiserValue,unsigned64,quantity,current,"This Information Element specifies the initialiser value to the hash function. See also Sections 6.2, 3.8 and 7.1 of [RFC5475].",,,,[RFC5477],0,2013-02-18 335,selectorName,string,default,current,"The name of a selector identified by a selectorID. Globally unique per Metering Process.",,,,[ipfix-iana_at_cisco.com],0,2013-02-18 336,upperCILimit,float64,quantity,current,"This Information Element specifies the upper limit of a confidence interval. It is used to provide an accuracy statement for an estimated value. The confidence limits define the range in which the real value is assumed to be with a certain probability p. Confidence limits always need to be associated with a confidence level that defines this probability p. Please note that a confidence interval only provides a probability that the real value lies within the limits. That means the real value can lie outside the confidence limits. The upperCILimit, lowerCILimit, and confidenceLevel Information Elements should all be used in an Options Template scoped to the observation to which they refer. See Section 3.4.2.1 of the IPFIX protocol document [RFC7011]. Note that the upperCILimit, lowerCILimit, and confidenceLevel are all required to specify confidence, and should be disregarded unless all three are specified together.",,,,[RFC5477],0,2013-02-18 337,lowerCILimit,float64,quantity,current,"This Information Element specifies the lower limit of a confidence interval. For further information, see the description of upperCILimit. The upperCILimit, lowerCILimit, and confidenceLevel Information Elements should all be used in an Options Template scoped to the observation to which they refer. See Section 3.4.2.1 of the IPFIX protocol document [RFC7011]. Note that the upperCILimit, lowerCILimit, and confidenceLevel are all required to specify confidence, and should be disregarded unless all three are specified together.",,,,[RFC5477],0,2013-02-18 338,confidenceLevel,float64,quantity,current,"This Information Element specifies the confidence level. It is used to provide an accuracy statement for estimated values. The confidence level provides the probability p with which the real value lies within a given range. A confidence level always needs to be associated with confidence limits that define the range in which the real value is assumed to be. The upperCILimit, lowerCILimit, and confidenceLevel Information Elements should all be used in an Options Template scoped to the observation to which they refer. See Section 3.4.2.1 of the IPFIX protocol document [RFC7011]. Note that the upperCILimit, lowerCILimit, and confidenceLevel are all required to specify confidence, and should be disregarded unless all three are specified together.",,,,[RFC5477],0,2013-02-18 339,informationElementDataType,unsigned8,,current,"A description of the abstract data type of an IPFIX information element.These are taken from the abstract data types defined in section 3.1 of the IPFIX Information Model [RFC5102]; see that section for more information on the types described in the informationElementDataType sub-registry. These types are registered in the IANA IPFIX Information Element Data Type subregistry. This subregistry is intended to assign numbers for type names, not to provide a mechanism for adding data types to the IPFIX Protocol, and as such requires a Standards Action [RFC5226] to modify.",,,,[RFC5610],0,2013-02-18 340,informationElementDescription,string,default,current,"A UTF-8 [RFC3629] encoded Unicode string containing a human-readable description of an Information Element. The content of the informationElementDescription MAY be annotated with one or more language tags [RFC4646], encoded in-line [RFC2482] within the UTF-8 string, in order to specify the language in which the description is written. Description text in multiple languages MAY tag each section with its own language tag; in this case, the description information in each language SHOULD have equivalent meaning. In the absence of any language tag, the ""i-default"" [RFC2277] language SHOULD be assumed. See the Security Considerations section for notes on string handling for Information Element type records.",,,,[RFC5610],0,2013-02-18 341,informationElementName,string,default,current,"A UTF-8 [RFC3629] encoded Unicode string containing the name of an Information Element, intended as a simple identifier. See the Security Considerations section for notes on string handling for Information Element type records",,,,[RFC5610],0,2013-02-18 342,informationElementRangeBegin,unsigned64,quantity,current,"Contains the inclusive low end of the range of acceptable values for an Information Element.",,,,[RFC5610],0,2013-02-18 343,informationElementRangeEnd,unsigned64,quantity,current,"Contains the inclusive high end of the range of acceptable values for an Information Element.",,,,[RFC5610],0,2013-02-18 344,informationElementSemantics,unsigned8,,current,"A description of the semantics of an IPFIX Information Element. These are taken from the data type semantics defined in section 3.2 of the IPFIX Information Model [RFC5102]; see that section for more information on the types defined in the informationElementSemantics sub-registry. This field may take the values in Table ; the special value 0x00 (default) is used to note that no semantics apply to the field; it cannot be manipulated by a Collecting Process or File Reader that does not understand it a priori. These semantics are registered in the IANA IPFIX Information Element Semantics subregistry. This subregistry is intended to assign numbers for semantics names, not to provide a mechanism for adding semantics to the IPFIX Protocol, and as such requires a Standards Action [RFC5226] to modify.",,,,[RFC5610],0,2013-02-18 345,informationElementUnits,unsigned16,,current,"A description of the units of an IPFIX Information Element. These correspond to the units implicitly defined in the Information Element definitions in section 5 of the IPFIX Information Model [RFC5102]; see that section for more information on the types described in the informationElementsUnits sub-registry. This field may take the values in Table 3 below; the special value 0x00 (none) is used to note that the field is unitless. These types are registered in the IANA IPFIX Information Element Units subregistry; new types may be added on a First Come First Served [RFC5226] basis.",,,,[RFC5610],0,2013-02-18 346,privateEnterpriseNumber,unsigned32,identifier,current,"A private enterprise number, as assigned by IANA. Within the context of an Information Element Type record, this element can be used along with the informationElementId element to scope properties to a specific Information Element. To export type information about an IANA-assigned Information Element, set the privateEnterpriseNumber to 0, or do not export the privateEnterpriseNumber in the type record. To export type information about an enterprise-specific Information Element, export the enterprise number in privateEnterpriseNumber, and export the Information Element number with the Enterprise bit cleared in informationElementId. The Enterprise bit in the associated informationElementId Information Element MUST be ignored by the Collecting Process.",,,,[RFC5610],0,2013-02-18 347,virtualStationInterfaceId,octetArray,default,current,"Instance Identifier of the interface to a Virtual Station. A Virtual Station is an end station instance: it can be a virtual machine or a physical host.",,,See IEEE 802.1Qbg for the definition of Virtual Station Interface ID.,[ipfix-iana_at_cisco.com],1,2014-02-03 348,virtualStationInterfaceName,string,default,current,"Name of the interface to a Virtual Station. A Virtual Station is an end station instance: it can be a virtual machine or a physical host.",,,See IEEE 802.1Qbg for the definition of Virtual Station Interface.,[ipfix-iana_at_cisco.com],1,2014-02-03 349,virtualStationUUID,octetArray,default,current,"Unique Identifier of a Virtual Station. A Virtual Station is an end station instance: it can be a virtual machine or a physical host.",,,See IEEE 802.1Qbg for the definition of Virtual Station.,[ipfix-iana_at_cisco.com],1,2014-02-03 350,virtualStationName,string,default,current,"Name of a Virtual Station. A Virtual Station is an end station instance: it can be a virtual machine or a physical host.",,,See IEEE 802.1Qbg for the definition of Virtual Station.,[ipfix-iana_at_cisco.com],0,2013-02-18 351,layer2SegmentId,unsigned64,identifier,current,"Identifier of a layer 2 network segment in an overlay network. The most significant byte identifies the layer 2 network overlay network encapsulation type: 0x00 reserved 0x01 VxLAN 0x02 NVGRE The three lowest significant bytes hold the value of the layer 2 overlay network segment identifier. For example: - a 24 bit segment ID VXLAN Network Identifier (VNI) - a 24 bit Tenant Network Identifier (TNI) for NVGRE",,,"See VxLAN RFC at [draft-mahalingam-dutt-dcops-vxlan] See NVGRE RFC at [draft-sridharan-virtualization-nvgre]",[ipfix-iana_at_cisco.com],0,2013-02-18 352,layer2OctetDeltaCount,unsigned64,deltaCounter,current,"The number of layer 2 octets since the previous report (if any) in incoming packets for this Flow at the Observation Point. The number of octets includes layer 2 header(s) and layer 2 payload. # memo: layer 2 version of octetDeltaCount (field #1)",octets,,,[ipfix-iana_at_cisco.com],1,2014-05-02 353,layer2OctetTotalCount,unsigned64,totalCounter,current,"The total number of layer 2 octets in incoming packets for this Flow at the Observation Point since the Metering Process (re-)initialization for this Observation Point. The number of octets includes layer 2 header(s) and layer 2 payload. # memo: layer 2 version of octetTotalCount (field #85)",octets,,,[ipfix-iana_at_cisco.com],1,2014-05-02 354,ingressUnicastPacketTotalCount,unsigned64,totalCounter,current,"The total number of incoming unicast packets metered at the Observation Point since the Metering Process (re-)initialization for this Observation Point.",packets,,,[ipfix-iana_at_cisco.com],0,2013-02-18 355,ingressMulticastPacketTotalCount,unsigned64,totalCounter,current,"The total number of incoming multicast packets metered at the Observation Point since the Metering Process (re-)initialization for this Observation Point.",packets,,,[ipfix-iana_at_cisco.com],0,2013-02-18 356,ingressBroadcastPacketTotalCount,unsigned64,totalCounter,current,"The total number of incoming broadcast packets metered at the Observation Point since the Metering Process (re-)initialization for this Observation Point.",packets,,,[ipfix-iana_at_cisco.com],0,2013-02-18 357,egressUnicastPacketTotalCount,unsigned64,totalCounter,current,"The total number of incoming unicast packets metered at the Observation Point since the Metering Process (re-)initialization for this Observation Point.",packets,,,[ipfix-iana_at_cisco.com],0,2013-02-18 358,egressBroadcastPacketTotalCount,unsigned64,totalCounter,current,"The total number of incoming broadcast packets metered at the Observation Point since the Metering Process (re-)initialization for this Observation Point.",packets,,,[ipfix-iana_at_cisco.com],0,2013-02-18 359,monitoringIntervalStartMilliSeconds,dateTimeMilliseconds,default,current,"The absolute timestamp at which the monitoring interval started. A Monitoring interval is the period of time during which the Metering Process is running.",milliseconds,,,[ipfix-iana_at_cisco.com],0,2013-02-18 360,monitoringIntervalEndMilliSeconds,dateTimeMilliseconds,default,current,"The absolute timestamp at which the monitoring interval ended. A Monitoring interval is the period of time during which the Metering Process is running.",milliseconds,,,[ipfix-iana_at_cisco.com],0,2013-02-18 361,portRangeStart,unsigned16,identifier,current,"The port number identifying the start of a range of ports. A value of zero indicates that the range start is not specified, ie the range is defined in some other way. Additional information on defined TCP port numbers can be found at [IANA registry service-names-port-numbers].",,,,[ipfix-iana_at_cisco.com],0,2013-02-18 362,portRangeEnd,unsigned16,identifier,current,"The port number identifying the end of a range of ports. A value of zero indicates that the range end is not specified, ie the range is defined in some other way. Additional information on defined TCP port numbers can be found at [IANA registry service-names-port-numbers].",,,,[ipfix-iana_at_cisco.com],0,2013-02-18 363,portRangeStepSize,unsigned16,identifier,current,"The step size in a port range. The default step size is 1, which indicates contiguous ports. A value of zero indicates that the step size is not specified, ie the range is defined in some other way.",,,,[ipfix-iana_at_cisco.com],0,2013-02-18 364,portRangeNumPorts,unsigned16,identifier,current,"The number of ports in a port range. A value of zero indicates that the number of ports is not specified, ie the range is defined in some other way.",,,,[ipfix-iana_at_cisco.com],0,2013-02-18 365,staMacAddress,macAddress,default,current,The IEEE 802 MAC address of a wireless station (STA).,,,See section 1.4 of [RFC5415] for the definition of STA.,[ipfix-iana_at_cisco.com],1,2014-02-03 366,staIPv4Address,ipv4Address,default,current,The IPv4 address of a wireless station (STA).,,,See section 1.4 of [RFC5415] for the definition of STA.,[ipfix-iana_at_cisco.com],1,2014-02-03 367,wtpMacAddress,macAddress,default,current,The IEEE 802 MAC address of a wireless access point (WTP).,,,See section 1.4 of [RFC5415] for the definition of WTP.,[ipfix-iana_at_cisco.com],1,2014-02-03 368,ingressInterfaceType,unsigned32,identifier,current,"The type of interface where packets of this Flow are being received. The value matches the value of managed object 'ifType' as defined in [IANA registry ianaiftype-mib].",,,[IANA registry ianaiftype-mib],[ipfix-iana_at_cisco.com],0,2013-02-18 369,egressInterfaceType,unsigned32,identifier,current,"The type of interface where packets of this Flow are being sent. The value matches the value of managed object 'ifType' as defined in [IANA registry ianaiftype-mib].",,,[IANA registry ianaiftype-mib],[ipfix-iana_at_cisco.com],0,2013-02-18 370,rtpSequenceNumber,unsigned16,,current,The RTP sequence number per [RFC3550].,,,[RFC3550],[ipfix-iana_at_cisco.com],0,2013-02-18 371,userName,string,default,current,User name associated with the flow.,,,,[ipfix-iana_at_cisco.com],0,2013-02-18 372,applicationCategoryName,string,default,current,"An attribute that provides a first level categorization for each Application ID.",,,,[RFC6759],0,2013-02-18 373,applicationSubCategoryName,string,default,current,"An attribute that provides a second level categorization for each Application ID.",,,,[RFC6759],0,2013-02-18 374,applicationGroupName,string,default,current,"An attribute that groups multiple Application IDs that belong to the same networking application.",,,,[RFC6759],0,2013-02-18 375,originalFlowsPresent,unsigned64,deltaCounter,current,"The non-conservative count of Original Flows contributing to this Aggregated Flow. Non-conservative counts need not sum to the original count on re-aggregation.",flows,,,[RFC7015],1,2013-06-25 376,originalFlowsInitiated,unsigned64,deltaCounter,current,"The conservative count of Original Flows whose first packet is represented within this Aggregated Flow. Conservative counts must sum to the original count on re-aggregation.",flows,,,[RFC7015],1,2013-06-25 377,originalFlowsCompleted,unsigned64,deltaCounter,current,"The conservative count of Original Flows whose last packet is represented within this Aggregated Flow. Conservative counts must sum to the original count on re-aggregation.",flows,,,[RFC7015],1,2013-06-25 378,distinctCountOfSourceIPAddress,unsigned64,totalCounter,current,"The count of distinct source IP address values for Original Flows contributing to this Aggregated Flow, without regard to IP version. This Information Element is preferred to the IP-version-specific counters, unless it is important to separate the counts by version.",,,,[RFC7015],0,2013-02-18 379,distinctCountOfDestinationIPAddress,unsigned64,totalCounter,current,"The count of distinct destination IP address values for Original Flows contributing to this Aggregated Flow, without regard to IP version. This Information Element is preferred to the version-specific counters below, unless it is important to separate the counts by version.",,,,[RFC7015],0,2013-02-18 380,distinctCountOfSourceIPv4Address,unsigned32,totalCounter,current,"The count of distinct source IPv4 address values for Original Flows contributing to this Aggregated Flow.",,,,[RFC7015],0,2013-02-18 381,distinctCountOfDestinationIPv4Address,unsigned32,totalCounter,current,"The count of distinct destination IPv4 address values for Original Flows contributing to this Aggregated Flow.",,,,[RFC7015],0,2013-02-18 382,distinctCountOfSourceIPv6Address,unsigned64,totalCounter,current,"The count of distinct source IPv6 address values for Original Flows contributing to this Aggregated Flow.",,,,[RFC7015],0,2013-02-18 383,distinctCountOfDestinationIPv6Address,unsigned64,totalCounter,current,"The count of distinct destination IPv6 address values for Original Flows contributing to this Aggregated Flow.",,,,[RFC7015],0,2013-02-18 384,valueDistributionMethod,unsigned8,,current,"A description of the method used to distribute the counters from Contributing Flows into the Aggregated Flow records described by an associated scope, generally a Template. The method is deemed to apply to all the non-key Information Elements in the referenced scope for which value distribution is a valid operation; if the originalFlowsInitiated and/or originalFlowsCompleted Information Elements appear in the Template, they are not subject to this distribution method, as they each infer their own distribution method. This is intended to be a complete set of possible value distribution methods; it is encoded as follows: +-------+-----------------------------------------------------------+ | Value | Description | +-------+-----------------------------------------------------------+ | 0 | Unspecified: The counters for an Original Flow are | | | explicitly not distributed according to any other method | | | defined for this Information Element; use for arbitrary | | | distribution, or distribution algorithms not described by | | | any other codepoint. | | | --------------------------------------------------------- | | | | | 1 | Start Interval: The counters for an Original Flow are | | | added to the counters of the appropriate Aggregated Flow | | | containing the start time of the Original Flow. This | | | should be assumed the default if value distribution | | | information is not available at a Collecting Process for | | | an Aggregated Flow. | | | --------------------------------------------------------- | | | | | 2 | End Interval: The counters for an Original Flow are added | | | to the counters of the appropriate Aggregated Flow | | | containing the end time of the Original Flow. | | | --------------------------------------------------------- | | | | | 3 | Mid Interval: The counters for an Original Flow are added | | | to the counters of a single appropriate Aggregated Flow | | | containing some timestamp between start and end time of | | | the Original Flow. | | | --------------------------------------------------------- | | | | | 4 | Simple Uniform Distribution: Each counter for an Original | | | Flow is divided by the number of time intervals the | | | Original Flow covers (i.e., of appropriate Aggregated | | | Flows sharing the same Flow Key), and this number is | | | added to each corresponding counter in each Aggregated | | | Flow. | | | --------------------------------------------------------- | | | | | 5 | Proportional Uniform Distribution: Each counter for an | | | Original Flow is divided by the number of time units the | | | Original Flow covers, to derive a mean count rate. This | | | mean count rate is then multiplied by the number of time | | | units in the intersection of the duration of the Original | | | Flow and the time interval of each Aggregated Flow. This | | | is like simple uniform distribution, but accounts for the | | | fractional portions of a time interval covered by an | | | Original Flow in the first and last time interval. | | | --------------------------------------------------------- | | | | | 6 | Simulated Process: Each counter of the Original Flow is | | | distributed among the intervals of the Aggregated Flows | | | according to some function the Intermediate Aggregation | | | Process uses based upon properties of Flows presumed to | | | be like the Original Flow. This is essentially an | | | assertion that the Intermediate Aggregation Process has | | | no direct packet timing information but is nevertheless | | | not using one of the other simpler distribution methods. | | | The Intermediate Aggregation Process specifically makes | | | no assertion as to the correctness of the simulation. | | | --------------------------------------------------------- | | | | | 7 | Direct: The Intermediate Aggregation Process has access | | | to the original packet timings from the packets making up | | | the Original Flow, and uses these to distribute or | | | recalculate the counters. | +-------+-----------------------------------------------------------+",,,,[RFC7015],0,2013-02-18 385,rfc3550JitterMilliseconds,unsigned32,quantity,current,"Interarrival jitter as defined in section 6.4.1 of [RFC3550], measured in milliseconds.",milliseconds,,[RFC3550],[ipfix-iana_at_cisco.com],0,2013-02-18 386,rfc3550JitterMicroseconds,unsigned32,quantity,current,"Interarrival jitter as defined in section 6.4.1 of [RFC3550], measured in microseconds.",microseconds,,[RFC3550],[ipfix-iana_at_cisco.com],0,2013-02-18 387,rfc3550JitterNanoseconds,unsigned32,quantity,current,"Interarrival jitter as defined in section 6.4.1 of [RFC3550], measured in nanoseconds.",nanoseconds,,[RFC3550],[ipfix-iana_at_cisco.com],0,2013-02-18 388,dot1qDEI,boolean,default,current,"The value of the 1-bit Drop Eligible Indicator (DEI) field of the VLAN tag as described in 802.1Q-2011 subclause 9.6. In case of a QinQ frame, it represents the outer tag's DEI field and in case of an IEEE 802.1ad frame it represents the DEI field of the S-TAG. Note: in earlier versions of 802.1Q the same bit field in the incoming packet is occupied by the Canonical Format Indicator (CFI) field, except for S-TAGs.",,,[802.1Q-2011 subclause 9.6],[Yaakov_J_Stein],1,2014-02-03 389,dot1qCustomerDEI,boolean,default,current,"In case of a QinQ frame, it represents the inner tag's Drop Eligible Indicator (DEI) field and in case of an IEEE 802.1ad frame it represents the DEI field of the C-TAG.",,,[802.1Q-2011 subclause 9.6],[Yaakov_J_Stein],1,2014-02-03 390,flowSelectorAlgorithm,unsigned16,identifier,current,"This Information Element identifies the Intermediate Flow Selection Process technique (e.g., Filtering, Sampling) that is applied by the Intermediate Flow Selection Process. Most of these techniques have parameters. Its configuration parameter(s) MUST be clearly specified. Further Information Elements are needed to fully specify packet selection with these methods and all their parameters. Further method identifiers may be added to the flowSelectorAlgorithm registry. It might be necessary to define new Information Elements to specify their parameters. The flowSelectorAlgorithm registry is maintained by IANA. New assignments for the registry will be administered by IANA, on a First Come First Served basis [RFC5226], subject to Expert Review [RFC5226]. Please note that the purpose of the flow selection techniques described in this document is the improvement of measurement functions as defined in the Scope (Section 1). Before adding new flow selector algorithms it should be checked what is their intended purpose and especially if those contradict with policies defined in [RFC2804]. The designated expert(s) should consult with the community if a request is received that runs counter to [RFC2804]. The registry can be updated when specifications of the new method(s) and any new Information Elements are provided. The group of experts must double check the flowSelectorAlgorithm definitions and Information Elements with already defined flowSelectorAlgorithm and Information Elements for completeness, accuracy, and redundancy. Those experts will initially be drawn from the Working Group Chairs and document editors of the IPFIX and PSAMP Working Groups. The Intermediate Flow Selection Process Techniques identifiers are defined at [http://www.iana.org/assignments/ipfix/ipfix.xml#ipfix-flowselectoralgorithm].",,,,[RFC7014],0,2013-06-07 391,flowSelectedOctetDeltaCount,unsigned64,deltaCounter,current,"This Information Element specifies the volume in octets of all Flows that are selected in the Intermediate Flow Selection Process since the previous report.",octets,,,[RFC7014],1,2014-08-13 392,flowSelectedPacketDeltaCount,unsigned64,deltaCounter,current,"This Information Element specifies the volume in packets of all Flows that were selected in the Intermediate Flow Selection Process since the previous report.",packets,,,[RFC7014],1,2014-08-13 393,flowSelectedFlowDeltaCount,unsigned64,deltaCounter,current,"This Information Element specifies the number of Flows that were selected in the Intermediate Flow Selection Process since the last report.",flows,,,[RFC7014],1,2014-08-13 394,selectorIDTotalFlowsObserved,unsigned64,,current,"This Information Element specifies the total number of Flows observed by a Selector, for a specific value of SelectorId. This Information Element should be used in an Options Template scoped to the observation to which it refers. See Section 3.4.2.1 of the IPFIX protocol document [RFC7011].",flows,,,[RFC7014],0,2013-06-07 395,selectorIDTotalFlowsSelected,unsigned64,,current,"This Information Element specifies the total number of Flows selected by a Selector, for a specific value of SelectorId. This Information Element should be used in an Options Template scoped to the observation to which it refers. See Section 3.4.2.1 of the IPFIX protocol document [RFC7011].",flows,,,[RFC7014],0,2013-06-07 396,samplingFlowInterval,unsigned64,,current,"This Information Element specifies the number of Flows that are consecutively sampled. A value of 100 means that 100 consecutive Flows are sampled. For example, this Information Element may be used to describe the configuration of a systematic count-based Sampling Selector.",flows,,,[RFC7014],0,2013-06-07 397,samplingFlowSpacing,unsigned64,,current,"This Information Element specifies the number of Flows between two ""samplingFlowInterval""s. A value of 100 means that the next interval starts 100 Flows (which are not sampled) after the current ""samplingFlowInterval"" is over. For example, this Information Element may be used to describe the configuration of a systematic count-based Sampling Selector.",flows,,,[RFC7014],0,2013-06-07 398,flowSamplingTimeInterval,unsigned64,,current,"This Information Element specifies the time interval in microseconds during which all arriving Flows are sampled. For example, this Information Element may be used to describe the configuration of a systematic time-based Sampling Selector.",microseconds,,,[RFC7014],0,2013-06-07 399,flowSamplingTimeSpacing,unsigned64,,current,"This Information Element specifies the time interval in microseconds between two ""flowSamplingTimeInterval""s. A value of 100 means that the next interval starts 100 microseconds (during which no Flows are sampled) after the current ""flowsamplingTimeInterval"" is over. For example, this Information Element may used to describe the configuration of a systematic time-based Sampling Selector.",microseconds,,,[RFC7014],0,2013-06-07 400,hashFlowDomain,unsigned16,identifier,current,"This Information Element specifies the Information Elements that are used by the Hash-based Flow Selector as the Hash Domain.",,,,[RFC7014],0,2013-06-07 401,transportOctetDeltaCount,unsigned64,deltaCounter,current,"The number of octets, excluding IP header(s) and Layer 4 transport protocol header(s), observed for this Flow at the Observation Point since the previous report (if any).",octets,,,[Brian_Trammell],0,2013-08-01 402,transportPacketDeltaCount,unsigned64,deltaCounter,current,"The number of packets containing at least one octet beyond the IP header(s) and Layer 4 transport protocol header(s), observed for this Flow at the Observation Point since the previous report (if any).",packets,,,[Brian_Trammell],0,2013-08-01 403,originalExporterIPv4Address,ipv4Address,,current,"The IPv4 address used by the Exporting Process on an Original Exporter, as seen by the Collecting Process on an IPFIX Mediator. Used to provide information about the Original Observation Points to a downstream Collector.",,,,[RFC7119],0,2013-12-24 404,originalExporterIPv6Address,ipv6Address,,current,"The IPv6 address used by the Exporting Process on an Original Exporter, as seen by the Collecting Process on an IPFIX Mediator. Used to provide information about the Original Observation Points to a downstream Collector.",,,,[RFC7119],0,2013-12-24 405,originalObservationDomainId,unsigned32,identifier,current,"The Observation Domain ID reported by the Exporting Process on an Original Exporter, as seen by the Collecting Process on an IPFIX Mediator. Used to provide information about the Original Observation Domain to a downstream Collector. When cascading through multiple Mediators, this identifies the initial Observation Domain in the cascade.",,,,[RFC7119],0,2013-12-24 406,intermediateProcessId,unsigned32,identifier,current,"Description: An identifier of an Intermediate Process that is unique per IPFIX Device. Typically, this Information Element is used for limiting the scope of other Information Elements. Note that process identifiers may be assigned dynamically; that is, an Intermediate Process may be restarted with a different ID.",,,,[RFC7119],0,2013-12-24 407,ignoredDataRecordTotalCount,unsigned64,totalCounter,current,"Description: The total number of received Data Records that the Intermediate Process did not process since the (re-)initialization of the Intermediate Process; includes only Data Records not examined or otherwise handled by the Intermediate Process due to resource constraints, not Data Records that were examined or otherwise handled by the Intermediate Process but those that merely do not contribute to any exported Data Record due to the operations performed by the Intermediate Process.",,,,[RFC7119],0,2013-12-24 408,dataLinkFrameType,unsigned16,flags,current,"This Information Element specifies the type of the selected data link frame. The following data link types are defined here: - 0x01 IEEE802.3 ETHERNET [IEEE802.3] - 0x02 IEEE802.11 MAC Frame format [IEEE802.11] Further values may be assigned by IANA. Note that the assigned values are bits so that multiple observations can be OR'd together. The data link layer is defined in [ISO/IEC.7498-1:1994].",,,[IEEE802.3][IEEE802.11][ISO/IEC.7498-1:1994],[RFC7133],0,2014-01-11 409,sectionOffset,unsigned16,quantity,current,"This Information Element specifies the offset of the packet section (e.g., dataLinkFrameSection, ipHeaderPacketSection, ipPayloadPacketSection, mplsLabelStackSection, and mplsPayloadPacketSection). If this Information Element is omitted, it defaults to zero (i.e., no offset). If multiple sectionOffset Information Elements are specified within a single Template, then they apply to the packet section Information Elements in order: the first sectionOffset applies to the first packet section, the second to the second, and so on. Note that the ""closest"" sectionOffset and packet section Information Elements within a given Template are not necessarily related. If there are fewer sectionOffset Information Elements than packet section Information Elements, then subsequent packet section Information Elements have no offset, i.e., a sectionOffset of zero applies to those packet section Information Elements. If there are more sectionOffset Information Elements than the number of packet section Information Elements, then the additional sectionOffset Information Elements are meaningless.",,,,[RFC7133],0,2014-01-11 410,sectionExportedOctets,unsigned16,quantity,current,"This Information Element specifies the observed length of the packet section (e.g., dataLinkFrameSection, ipHeaderPacketSection, ipPayloadPacketSection, mplsLabelStackSection, and mplsPayloadPacketSection) when padding is used. The packet section may be of a fixed size larger than the sectionExportedOctets. In this case, octets in the packet section beyond the sectionExportedOctets MUST follow the [RFC7011] rules for padding (i.e., be composed of zero (0) valued octets).",,,[RFC7011],[RFC7133],0,2014-01-11 411,dot1qServiceInstanceTag,octetArray,default,current,"This Information Element, which is 16 octets long, represents the Backbone Service Instance Tag (I-TAG) Tag Control Information (TCI) field of an Ethernet frame as described in [IEEE802.1Q]. It encodes the Backbone Service Instance Priority Code Point (I-PCP), Backbone Service Instance Drop Eligible Indicator (I-DEI), Use Customer Addresses (UCAs), Backbone Service Instance Identifier (I-SID), Encapsulated Customer Destination Address (C-DA), Encapsulated Customer Source Address (C-SA), and reserved fields. The structure and semantics within the Tag Control Information field are defined in [IEEE802.1Q].",,,[IEEE802.1Q],[RFC7133],1,2014-05-02 412,dot1qServiceInstanceId,unsigned32,identifier,current,"The value of the 24-bit Backbone Service Instance Identifier (I-SID) portion of the Backbone Service Instance Tag (I-TAG) Tag Control Information (TCI) field of an Ethernet frame as described in [IEEE802.1Q].",,"The valid range is 0 - 16777215 (i.e., 24 bits).",[IEEE802.1Q],[RFC7133],1,2014-05-02 413,dot1qServiceInstancePriority,unsigned8,identifier,current,"The value of the 3-bit Backbone Service Instance Priority Code Point (I-PCP) portion of the Backbone Service Instance Tag (I-TAG) Tag Control Information (TCI) field of an Ethernet frame as described in [IEEE802.1Q].",,The valid range is 0-7.,[IEEE802.1Q],[RFC7133],1,2014-05-02 414,dot1qCustomerSourceMacAddress,macAddress,default,current,"The value of the Encapsulated Customer Source Address (C-SA) portion of the Backbone Service Instance Tag (I-TAG) Tag Control Information (TCI) field of an Ethernet frame as described in [IEEE802.1Q].",,,[IEEE802.1Q],[RFC7133],1,2014-05-02 415,dot1qCustomerDestinationMacAddress,macAddress,default,current,"The value of the Encapsulated Customer Destination Address (C-DA) portion of the Backbone Service Instance Tag (I-TAG) Tag Control Information (TCI) field of an Ethernet frame as described in [IEEE802.1Q].",,,[IEEE802.1Q],[RFC7133],1,2014-05-02 416,,,,deprecated,"Duplicate of Information Element ID 352, layer2OctetDeltaCount.",,,[RFC5477],,2,2014-05-13 417,postLayer2OctetDeltaCount,unsigned64,deltaCounter,current,"The definition of this Information Element is identical to the definition of the layer2OctetDeltaCount Information Element, except that it reports a potentially modified value caused by a middlebox function after the packet passed the Observation Point. This Information Element is the layer 2 version of postOctetDeltaCount (ElementId #23).",octets,,[RFC5477],[RFC7133],1,2014-05-02 418,postMCastLayer2OctetDeltaCount,unsigned64,deltaCounter,current,"The number of layer 2 octets since the previous report (if any) in outgoing multicast packets sent for packets of this Flow by a multicast daemon within the Observation Domain. This property cannot necessarily be observed at the Observation Point but may be retrieved by other means. The number of octets includes layer 2 header(s) and layer 2 payload. This Information Element is the layer 2 version of postMCastOctetDeltaCount (ElementId #20).",octets,,[RFC5477],[RFC7133],1,2014-05-02 419,,,,deprecated,"Duplicate of Information Element ID 353, layer2OctetTotalCount.",,,[RFC5477],,2,2014-05-13 420,postLayer2OctetTotalCount,unsigned64,totalCounter,current,"The definition of this Information Element is identical to the definition of the layer2OctetTotalCount Information Element, except that it reports a potentially modified value caused by a middlebox function after the packet passed the Observation Point. This Information Element is the layer 2 version of postOctetTotalCount (ElementId #171).",octets,,[RFC5477],[RFC7133],1,2014-05-02 421,postMCastLayer2OctetTotalCount,unsigned64,totalCounter,current,"The total number of layer 2 octets in outgoing multicast packets sent for packets of this Flow by a multicast daemon in the Observation Domain since the Metering Process (re-)initialization. This property cannot necessarily be observed at the Observation Point but may be retrieved by other means. The number of octets includes layer 2 header(s) and layer 2 payload. This Information Element is the layer 2 version of postMCastOctetTotalCount (ElementId #175).",octets,,[RFC5477],[RFC7133],1,2014-05-02 422,minimumLayer2TotalLength,unsigned64,,current,"Layer 2 length of the smallest packet observed for this Flow. The packet length includes the length of the layer 2 header(s) and the length of the layer 2 payload. This Information Element is the layer 2 version of minimumIpTotalLength (ElementId #25).",octets,,[RFC5477],[RFC7133],1,2014-05-02 423,maximumLayer2TotalLength,unsigned64,,current,"Layer 2 length of the largest packet observed for this Flow. The packet length includes the length of the layer 2 header(s) and the length of the layer 2 payload. This Information Element is the layer 2 version of maximumIpTotalLength (ElementId #26).",octets,,[RFC5477],[RFC7133],1,2014-05-02 424,droppedLayer2OctetDeltaCount,unsigned64,deltaCounter,current,"The number of layer 2 octets since the previous report (if any) in packets of this Flow dropped by packet treatment. The number of octets includes layer 2 header(s) and layer 2 payload. This Information Element is the layer 2 version of droppedOctetDeltaCount (ElementId #132).",octets,,[RFC5477],[RFC7133],1,2014-05-02 425,droppedLayer2OctetTotalCount,unsigned64,totalCounter,current,"The total number of octets in observed layer 2 packets (including the layer 2 header) that were dropped by packet treatment since the (re-)initialization of the Metering Process. This Information Element is the layer 2 version of droppedOctetTotalCount (ElementId #134).",octets,,[RFC5477],[RFC7133],1,2014-05-02 426,ignoredLayer2OctetTotalCount,unsigned64,totalCounter,current,"The total number of octets in observed layer 2 packets (including the layer 2 header) that the Metering Process did not process since the (re-)initialization of the Metering Process. This Information Element is the layer 2 version of ignoredOctetTotalCount (ElementId #165).",octets,,[RFC5477],[RFC7133],1,2014-05-02 427,notSentLayer2OctetTotalCount,unsigned64,totalCounter,current,"The total number of octets in observed layer 2 packets (including the layer 2 header) that the Metering Process did not process since the (re-)initialization of the Metering Process. This Information Element is the layer 2 version of notSentOctetTotalCount (ElementId #168).",octets,,[RFC5477],[RFC7133],1,2014-05-02 428,layer2OctetDeltaSumOfSquares,unsigned64,deltaCounter,current,"The sum of the squared numbers of layer 2 octets per incoming packet since the previous report (if any) for this Flow at the Observation Point. The number of octets includes layer 2 header(s) and layer 2 payload. This Information Element is the layer 2 version of octetDeltaSumOfSquares (ElementId #198).",octets,,[RFC5477],[RFC7133],1,2014-05-02 429,layer2OctetTotalSumOfSquares,unsigned64,totalCounter,current,"The total sum of the squared numbers of layer 2 octets in incoming packets for this Flow at the Observation Point since the Metering Process (re-)initialization for this Observation Point. The number of octets includes layer 2 header(s) and layer 2 payload. This Information Element is the layer 2 version of octetTotalSumOfSquares (ElementId #199).",octets,,[RFC5477],[RFC7133],1,2014-05-02 430,layer2FrameDeltaCount,unsigned64,deltaCounter,current,"The number of incoming layer 2 frames since the previous report (if any) for this Flow at the Observation Point.",frames,,,[ipfix-iana_at_cisco.com],0,2014-05-02 431,layer2FrameTotalCount,unsigned64,totalCounter,current,"The total number of incoming layer 2 frames for this Flow at the Observation Point since the Metering Process (re-)initialization for this Observation Point.",frames,,,[ipfix-iana_at_cisco.com],0,2014-05-02 432,pseudoWireDestinationIPv4Address,ipv4Address,default,current,The destination IPv4 address of the PSN tunnel carrying the pseudowire.,,,[RFC3985],[ipfix-iana_at_cisco.com],0,2014-05-28 433,ignoredLayer2FrameTotalCount,unsigned64,totalCounter,current,"The total number of observed layer 2 frames that the Metering Process did not process since the (re-)initialization of the Metering Process. This Information Element is the layer 2 version of ignoredPacketTotalCount (ElementId #164).",frames,,,[ipfix-iana_at_cisco.com],0,2014-06-27 433-32767,Unassigned,,,,,,,,,, fastnetmon-1.1.3+dfsg/src/ipfix_rfc.cpp000066400000000000000000000601411313534057500200650ustar00rootroot00000000000000#include #include #include "ipfix_rfc.h" /* This file is autogenerated with script ipfix_csv_processor.pl */ /* Please do not edit it directly */ std::string ipfix_information_element_t::get_name() { return this->name; } unsigned int ipfix_information_element_t::get_length() { return this->length; } ipfix_information_element_t::ipfix_information_element_t(std::string name, unsigned int length) { this->name = name; this->length = length; } ipfix_information_element_t::ipfix_information_element_t() { this->name = std::string(""); this->length = 0; } std::string ipfix_information_database::get_name_by_id(unsigned int field_id) { ipfix_database_t::iterator itr = database.find(field_id); if (itr == database.end()) { return std::string(""); } return itr->second.get_name(); } unsigned int ipfix_information_database::get_length_by_id(unsigned int field_id) { ipfix_database_t::iterator itr = database.find(field_id); if (itr == database.end()) { return 0; } return itr->second.get_length(); } bool ipfix_information_database::add_element(unsigned int field_id, std::string name, unsigned int length) { ipfix_database_t::iterator itr = database.find(field_id); // Duplicate ID's strictly prohibited if (itr != database.end()) { return false; } database[field_id] = ipfix_information_element_t(name, length); return true; } ipfix_information_database::ipfix_information_database() { this->add_element(0, "Reserved", 0); this->add_element(1, "octetDeltaCount", 8); this->add_element(2, "packetDeltaCount", 8); this->add_element(3, "deltaFlowCount", 8); this->add_element(4, "protocolIdentifier", 1); this->add_element(5, "ipClassOfService", 1); this->add_element(6, "tcpControlBits", 2); this->add_element(7, "sourceTransportPort", 2); this->add_element(8, "sourceIPv4Address", 4); this->add_element(9, "sourceIPv4PrefixLength", 1); this->add_element(10, "ingressInterface", 4); this->add_element(11, "destinationTransportPort", 2); this->add_element(12, "destinationIPv4Address", 4); this->add_element(13, "destinationIPv4PrefixLength", 1); this->add_element(14, "egressInterface", 4); this->add_element(15, "ipNextHopIPv4Address", 4); this->add_element(16, "bgpSourceAsNumber", 4); this->add_element(17, "bgpDestinationAsNumber", 4); this->add_element(18, "bgpNextHopIPv4Address", 4); this->add_element(19, "postMCastPacketDeltaCount", 8); this->add_element(20, "postMCastOctetDeltaCount", 8); this->add_element(21, "flowEndSysUpTime", 4); this->add_element(22, "flowStartSysUpTime", 4); this->add_element(23, "postOctetDeltaCount", 8); this->add_element(24, "postPacketDeltaCount", 8); this->add_element(25, "minimumIpTotalLength", 8); this->add_element(26, "maximumIpTotalLength", 8); this->add_element(27, "sourceIPv6Address", 16); this->add_element(28, "destinationIPv6Address", 16); this->add_element(29, "sourceIPv6PrefixLength", 1); this->add_element(30, "destinationIPv6PrefixLength", 1); this->add_element(31, "flowLabelIPv6", 4); this->add_element(32, "icmpTypeCodeIPv4", 2); this->add_element(33, "igmpType", 1); this->add_element(34, "samplingInterval", 4); this->add_element(35, "samplingAlgorithm", 1); this->add_element(36, "flowActiveTimeout", 2); this->add_element(37, "flowIdleTimeout", 2); this->add_element(38, "engineType", 1); this->add_element(39, "engineId", 1); this->add_element(40, "exportedOctetTotalCount", 8); this->add_element(41, "exportedMessageTotalCount", 8); this->add_element(42, "exportedFlowRecordTotalCount", 8); this->add_element(43, "ipv4RouterSc", 4); this->add_element(44, "sourceIPv4Prefix", 4); this->add_element(45, "destinationIPv4Prefix", 4); this->add_element(46, "mplsTopLabelType", 1); this->add_element(47, "mplsTopLabelIPv4Address", 4); this->add_element(48, "samplerId", 1); this->add_element(49, "samplerMode", 1); this->add_element(50, "samplerRandomInterval", 4); this->add_element(51, "classId", 1); this->add_element(52, "minimumTTL", 1); this->add_element(53, "maximumTTL", 1); this->add_element(54, "fragmentIdentification", 4); this->add_element(55, "postIpClassOfService", 1); this->add_element(56, "sourceMacAddress", 0); this->add_element(57, "postDestinationMacAddress", 0); this->add_element(58, "vlanId", 2); this->add_element(59, "postVlanId", 2); this->add_element(60, "ipVersion", 1); this->add_element(61, "flowDirection", 1); this->add_element(62, "ipNextHopIPv6Address", 16); this->add_element(63, "bgpNextHopIPv6Address", 16); this->add_element(64, "ipv6ExtensionHeaders", 4); this->add_element(65, "Reserved", 0); this->add_element(66, "Reserved", 0); this->add_element(67, "Reserved", 0); this->add_element(68, "Reserved", 0); this->add_element(69, "Reserved", 0); this->add_element(70, "mplsTopLabelStackSection", 0); this->add_element(71, "mplsLabelStackSection2", 0); this->add_element(72, "mplsLabelStackSection3", 0); this->add_element(73, "mplsLabelStackSection4", 0); this->add_element(74, "mplsLabelStackSection5", 0); this->add_element(75, "mplsLabelStackSection6", 0); this->add_element(76, "mplsLabelStackSection7", 0); this->add_element(77, "mplsLabelStackSection8", 0); this->add_element(78, "mplsLabelStackSection9", 0); this->add_element(79, "mplsLabelStackSection10", 0); this->add_element(80, "destinationMacAddress", 0); this->add_element(81, "postSourceMacAddress", 0); this->add_element(82, "interfaceName", 0); this->add_element(83, "interfaceDescription", 0); this->add_element(84, "samplerName", 0); this->add_element(85, "octetTotalCount", 8); this->add_element(86, "packetTotalCount", 8); this->add_element(87, "flagsAndSamplerId", 4); this->add_element(88, "fragmentOffset", 2); this->add_element(89, "forwardingStatus", 4); this->add_element(90, "mplsVpnRouteDistinguisher", 0); this->add_element(91, "mplsTopLabelPrefixLength", 1); this->add_element(92, "srcTrafficIndex", 4); this->add_element(93, "dstTrafficIndex", 4); this->add_element(94, "applicationDescription", 0); this->add_element(95, "applicationId", 0); this->add_element(96, "applicationName", 0); this->add_element(97, "Assigned for NetFlow v9 compatibility", 0); this->add_element(98, "postIpDiffServCodePoint", 1); this->add_element(99, "multicastReplicationFactor", 4); this->add_element(100, "className", 0); this->add_element(101, "classificationEngineId", 1); this->add_element(102, "layer2packetSectionOffset", 2); this->add_element(103, "layer2packetSectionSize", 2); this->add_element(104, "layer2packetSectionData", 0); this->add_element(105, "Reserved", 0); this->add_element(106, "Reserved", 0); this->add_element(107, "Reserved", 0); this->add_element(108, "Reserved", 0); this->add_element(109, "Reserved", 0); this->add_element(110, "Reserved", 0); this->add_element(111, "Reserved", 0); this->add_element(112, "Reserved", 0); this->add_element(113, "Reserved", 0); this->add_element(114, "Reserved", 0); this->add_element(115, "Reserved", 0); this->add_element(116, "Reserved", 0); this->add_element(117, "Reserved", 0); this->add_element(118, "Reserved", 0); this->add_element(119, "Reserved", 0); this->add_element(120, "Reserved", 0); this->add_element(121, "Reserved", 0); this->add_element(122, "Reserved", 0); this->add_element(123, "Reserved", 0); this->add_element(124, "Reserved", 0); this->add_element(125, "Reserved", 0); this->add_element(126, "Reserved", 0); this->add_element(127, "Reserved", 0); this->add_element(128, "bgpNextAdjacentAsNumber", 4); this->add_element(129, "bgpPrevAdjacentAsNumber", 4); this->add_element(130, "exporterIPv4Address", 4); this->add_element(131, "exporterIPv6Address", 16); this->add_element(132, "droppedOctetDeltaCount", 8); this->add_element(133, "droppedPacketDeltaCount", 8); this->add_element(134, "droppedOctetTotalCount", 8); this->add_element(135, "droppedPacketTotalCount", 8); this->add_element(136, "flowEndReason", 1); this->add_element(137, "commonPropertiesId", 8); this->add_element(138, "observationPointId", 8); this->add_element(139, "icmpTypeCodeIPv6", 2); this->add_element(140, "mplsTopLabelIPv6Address", 16); this->add_element(141, "lineCardId", 4); this->add_element(142, "portId", 4); this->add_element(143, "meteringProcessId", 4); this->add_element(144, "exportingProcessId", 4); this->add_element(145, "templateId", 2); this->add_element(146, "wlanChannelId", 1); this->add_element(147, "wlanSSID", 0); this->add_element(148, "flowId", 8); this->add_element(149, "observationDomainId", 4); this->add_element(150, "flowStartSeconds", 0); this->add_element(151, "flowEndSeconds", 0); this->add_element(152, "flowStartMilliseconds", 0); this->add_element(153, "flowEndMilliseconds", 0); this->add_element(154, "flowStartMicroseconds", 0); this->add_element(155, "flowEndMicroseconds", 0); this->add_element(156, "flowStartNanoseconds", 0); this->add_element(157, "flowEndNanoseconds", 0); this->add_element(158, "flowStartDeltaMicroseconds", 4); this->add_element(159, "flowEndDeltaMicroseconds", 4); this->add_element(160, "systemInitTimeMilliseconds", 0); this->add_element(161, "flowDurationMilliseconds", 4); this->add_element(162, "flowDurationMicroseconds", 4); this->add_element(163, "observedFlowTotalCount", 8); this->add_element(164, "ignoredPacketTotalCount", 8); this->add_element(165, "ignoredOctetTotalCount", 8); this->add_element(166, "notSentFlowTotalCount", 8); this->add_element(167, "notSentPacketTotalCount", 8); this->add_element(168, "notSentOctetTotalCount", 8); this->add_element(169, "destinationIPv6Prefix", 16); this->add_element(170, "sourceIPv6Prefix", 16); this->add_element(171, "postOctetTotalCount", 8); this->add_element(172, "postPacketTotalCount", 8); this->add_element(173, "flowKeyIndicator", 8); this->add_element(174, "postMCastPacketTotalCount", 8); this->add_element(175, "postMCastOctetTotalCount", 8); this->add_element(176, "icmpTypeIPv4", 1); this->add_element(177, "icmpCodeIPv4", 1); this->add_element(178, "icmpTypeIPv6", 1); this->add_element(179, "icmpCodeIPv6", 1); this->add_element(180, "udpSourcePort", 2); this->add_element(181, "udpDestinationPort", 2); this->add_element(182, "tcpSourcePort", 2); this->add_element(183, "tcpDestinationPort", 2); this->add_element(184, "tcpSequenceNumber", 4); this->add_element(185, "tcpAcknowledgementNumber", 4); this->add_element(186, "tcpWindowSize", 2); this->add_element(187, "tcpUrgentPointer", 2); this->add_element(188, "tcpHeaderLength", 1); this->add_element(189, "ipHeaderLength", 1); this->add_element(190, "totalLengthIPv4", 2); this->add_element(191, "payloadLengthIPv6", 2); this->add_element(192, "ipTTL", 1); this->add_element(193, "nextHeaderIPv6", 1); this->add_element(194, "mplsPayloadLength", 4); this->add_element(195, "ipDiffServCodePoint", 1); this->add_element(196, "ipPrecedence", 1); this->add_element(197, "fragmentFlags", 1); this->add_element(198, "octetDeltaSumOfSquares", 8); this->add_element(199, "octetTotalSumOfSquares", 8); this->add_element(200, "mplsTopLabelTTL", 1); this->add_element(201, "mplsLabelStackLength", 4); this->add_element(202, "mplsLabelStackDepth", 4); this->add_element(203, "mplsTopLabelExp", 1); this->add_element(204, "ipPayloadLength", 4); this->add_element(205, "udpMessageLength", 2); this->add_element(206, "isMulticast", 1); this->add_element(207, "ipv4IHL", 1); this->add_element(208, "ipv4Options", 4); this->add_element(209, "tcpOptions", 8); this->add_element(210, "paddingOctets", 0); this->add_element(211, "collectorIPv4Address", 4); this->add_element(212, "collectorIPv6Address", 16); this->add_element(213, "exportInterface", 4); this->add_element(214, "exportProtocolVersion", 1); this->add_element(215, "exportTransportProtocol", 1); this->add_element(216, "collectorTransportPort", 2); this->add_element(217, "exporterTransportPort", 2); this->add_element(218, "tcpSynTotalCount", 8); this->add_element(219, "tcpFinTotalCount", 8); this->add_element(220, "tcpRstTotalCount", 8); this->add_element(221, "tcpPshTotalCount", 8); this->add_element(222, "tcpAckTotalCount", 8); this->add_element(223, "tcpUrgTotalCount", 8); this->add_element(224, "ipTotalLength", 8); this->add_element(225, "postNATSourceIPv4Address", 4); this->add_element(226, "postNATDestinationIPv4Address", 4); this->add_element(227, "postNAPTSourceTransportPort", 2); this->add_element(228, "postNAPTDestinationTransportPort", 2); this->add_element(229, "natOriginatingAddressRealm", 1); this->add_element(230, "natEvent", 1); this->add_element(231, "initiatorOctets", 8); this->add_element(232, "responderOctets", 8); this->add_element(233, "firewallEvent", 1); this->add_element(234, "ingressVRFID", 4); this->add_element(235, "egressVRFID", 4); this->add_element(236, "VRFname", 0); this->add_element(237, "postMplsTopLabelExp", 1); this->add_element(238, "tcpWindowScale", 2); this->add_element(239, "biflowDirection", 1); this->add_element(240, "ethernetHeaderLength", 1); this->add_element(241, "ethernetPayloadLength", 2); this->add_element(242, "ethernetTotalLength", 2); this->add_element(243, "dot1qVlanId", 2); this->add_element(244, "dot1qPriority", 1); this->add_element(245, "dot1qCustomerVlanId", 2); this->add_element(246, "dot1qCustomerPriority", 1); this->add_element(247, "metroEvcId", 0); this->add_element(248, "metroEvcType", 1); this->add_element(249, "pseudoWireId", 4); this->add_element(250, "pseudoWireType", 2); this->add_element(251, "pseudoWireControlWord", 4); this->add_element(252, "ingressPhysicalInterface", 4); this->add_element(253, "egressPhysicalInterface", 4); this->add_element(254, "postDot1qVlanId", 2); this->add_element(255, "postDot1qCustomerVlanId", 2); this->add_element(256, "ethernetType", 2); this->add_element(257, "postIpPrecedence", 1); this->add_element(258, "collectionTimeMilliseconds", 0); this->add_element(259, "exportSctpStreamId", 2); this->add_element(260, "maxExportSeconds", 0); this->add_element(261, "maxFlowEndSeconds", 0); this->add_element(262, "messageMD5Checksum", 0); this->add_element(263, "messageScope", 1); this->add_element(264, "minExportSeconds", 0); this->add_element(265, "minFlowStartSeconds", 0); this->add_element(266, "opaqueOctets", 0); this->add_element(267, "sessionScope", 1); this->add_element(268, "maxFlowEndMicroseconds", 0); this->add_element(269, "maxFlowEndMilliseconds", 0); this->add_element(270, "maxFlowEndNanoseconds", 0); this->add_element(271, "minFlowStartMicroseconds", 0); this->add_element(272, "minFlowStartMilliseconds", 0); this->add_element(273, "minFlowStartNanoseconds", 0); this->add_element(274, "collectorCertificate", 0); this->add_element(275, "exporterCertificate", 0); this->add_element(276, "dataRecordsReliability", 0); this->add_element(277, "observationPointType", 1); this->add_element(278, "newConnectionDeltaCount", 4); this->add_element(279, "connectionSumDurationSeconds", 8); this->add_element(280, "connectionTransactionId", 8); this->add_element(281, "postNATSourceIPv6Address", 16); this->add_element(282, "postNATDestinationIPv6Address", 16); this->add_element(283, "natPoolId", 4); this->add_element(284, "natPoolName", 0); this->add_element(285, "anonymizationFlags", 2); this->add_element(286, "anonymizationTechnique", 2); this->add_element(287, "informationElementIndex", 2); this->add_element(288, "p2pTechnology", 0); this->add_element(289, "tunnelTechnology", 0); this->add_element(290, "encryptedTechnology", 0); this->add_element(291, "basicList", 0); this->add_element(292, "subTemplateList", 0); this->add_element(293, "subTemplateMultiList", 0); this->add_element(294, "bgpValidityState", 1); this->add_element(295, "IPSecSPI", 4); this->add_element(296, "greKey", 4); this->add_element(297, "natType", 1); this->add_element(298, "initiatorPackets", 8); this->add_element(299, "responderPackets", 8); this->add_element(300, "observationDomainName", 0); this->add_element(301, "selectionSequenceId", 8); this->add_element(302, "selectorId", 8); this->add_element(303, "informationElementId", 2); this->add_element(304, "selectorAlgorithm", 2); this->add_element(305, "samplingPacketInterval", 4); this->add_element(306, "samplingPacketSpace", 4); this->add_element(307, "samplingTimeInterval", 4); this->add_element(308, "samplingTimeSpace", 4); this->add_element(309, "samplingSize", 4); this->add_element(310, "samplingPopulation", 4); this->add_element(311, "samplingProbability", 0); this->add_element(312, "dataLinkFrameSize", 2); this->add_element(313, "ipHeaderPacketSection", 0); this->add_element(314, "ipPayloadPacketSection", 0); this->add_element(315, "dataLinkFrameSection", 0); this->add_element(316, "mplsLabelStackSection", 0); this->add_element(317, "mplsPayloadPacketSection", 0); this->add_element(318, "selectorIdTotalPktsObserved", 8); this->add_element(319, "selectorIdTotalPktsSelected", 8); this->add_element(320, "absoluteError", 0); this->add_element(321, "relativeError", 0); this->add_element(322, "observationTimeSeconds", 0); this->add_element(323, "observationTimeMilliseconds", 0); this->add_element(324, "observationTimeMicroseconds", 0); this->add_element(325, "observationTimeNanoseconds", 0); this->add_element(326, "digestHashValue", 8); this->add_element(327, "hashIPPayloadOffset", 8); this->add_element(328, "hashIPPayloadSize", 8); this->add_element(329, "hashOutputRangeMin", 8); this->add_element(330, "hashOutputRangeMax", 8); this->add_element(331, "hashSelectedRangeMin", 8); this->add_element(332, "hashSelectedRangeMax", 8); this->add_element(333, "hashDigestOutput", 0); this->add_element(334, "hashInitialiserValue", 8); this->add_element(335, "selectorName", 0); this->add_element(336, "upperCILimit", 0); this->add_element(337, "lowerCILimit", 0); this->add_element(338, "confidenceLevel", 0); this->add_element(339, "informationElementDataType", 1); this->add_element(340, "informationElementDescription", 0); this->add_element(341, "informationElementName", 0); this->add_element(342, "informationElementRangeBegin", 8); this->add_element(343, "informationElementRangeEnd", 8); this->add_element(344, "informationElementSemantics", 1); this->add_element(345, "informationElementUnits", 2); this->add_element(346, "privateEnterpriseNumber", 4); this->add_element(347, "virtualStationInterfaceId", 0); this->add_element(348, "virtualStationInterfaceName", 0); this->add_element(349, "virtualStationUUID", 0); this->add_element(350, "virtualStationName", 0); this->add_element(351, "layer2SegmentId", 8); this->add_element(352, "layer2OctetDeltaCount", 8); this->add_element(353, "layer2OctetTotalCount", 8); this->add_element(354, "ingressUnicastPacketTotalCount", 8); this->add_element(355, "ingressMulticastPacketTotalCount", 8); this->add_element(356, "ingressBroadcastPacketTotalCount", 8); this->add_element(357, "egressUnicastPacketTotalCount", 8); this->add_element(358, "egressBroadcastPacketTotalCount", 8); this->add_element(359, "monitoringIntervalStartMilliSeconds", 0); this->add_element(360, "monitoringIntervalEndMilliSeconds", 0); this->add_element(361, "portRangeStart", 2); this->add_element(362, "portRangeEnd", 2); this->add_element(363, "portRangeStepSize", 2); this->add_element(364, "portRangeNumPorts", 2); this->add_element(365, "staMacAddress", 0); this->add_element(366, "staIPv4Address", 4); this->add_element(367, "wtpMacAddress", 0); this->add_element(368, "ingressInterfaceType", 4); this->add_element(369, "egressInterfaceType", 4); this->add_element(370, "rtpSequenceNumber", 2); this->add_element(371, "userName", 0); this->add_element(372, "applicationCategoryName", 0); this->add_element(373, "applicationSubCategoryName", 0); this->add_element(374, "applicationGroupName", 0); this->add_element(375, "originalFlowsPresent", 8); this->add_element(376, "originalFlowsInitiated", 8); this->add_element(377, "originalFlowsCompleted", 8); this->add_element(378, "distinctCountOfSourceIPAddress", 8); this->add_element(379, "distinctCountOfDestinationIPAddress", 8); this->add_element(380, "distinctCountOfSourceIPv4Address", 4); this->add_element(381, "distinctCountOfDestinationIPv4Address", 4); this->add_element(382, "distinctCountOfSourceIPv6Address", 8); this->add_element(383, "distinctCountOfDestinationIPv6Address", 8); this->add_element(384, "valueDistributionMethod", 1); this->add_element(385, "rfc3550JitterMilliseconds", 4); this->add_element(386, "rfc3550JitterMicroseconds", 4); this->add_element(387, "rfc3550JitterNanoseconds", 4); this->add_element(388, "dot1qDEI", 0); this->add_element(389, "dot1qCustomerDEI", 0); this->add_element(390, "flowSelectorAlgorithm", 2); this->add_element(391, "flowSelectedOctetDeltaCount", 8); this->add_element(392, "flowSelectedPacketDeltaCount", 8); this->add_element(393, "flowSelectedFlowDeltaCount", 8); this->add_element(394, "selectorIDTotalFlowsObserved", 8); this->add_element(395, "selectorIDTotalFlowsSelected", 8); this->add_element(396, "samplingFlowInterval", 8); this->add_element(397, "samplingFlowSpacing", 8); this->add_element(398, "flowSamplingTimeInterval", 8); this->add_element(399, "flowSamplingTimeSpacing", 8); this->add_element(400, "hashFlowDomain", 2); this->add_element(401, "transportOctetDeltaCount", 8); this->add_element(402, "transportPacketDeltaCount", 8); this->add_element(403, "originalExporterIPv4Address", 4); this->add_element(404, "originalExporterIPv6Address", 16); this->add_element(405, "originalObservationDomainId", 4); this->add_element(406, "intermediateProcessId", 4); this->add_element(407, "ignoredDataRecordTotalCount", 8); this->add_element(408, "dataLinkFrameType", 2); this->add_element(409, "sectionOffset", 2); this->add_element(410, "sectionExportedOctets", 2); this->add_element(411, "dot1qServiceInstanceTag", 0); this->add_element(412, "dot1qServiceInstanceId", 4); this->add_element(413, "dot1qServiceInstancePriority", 1); this->add_element(414, "dot1qCustomerSourceMacAddress", 0); this->add_element(415, "dot1qCustomerDestinationMacAddress", 0); this->add_element(416, "reserved", 0); this->add_element(417, "postLayer2OctetDeltaCount", 8); this->add_element(418, "postMCastLayer2OctetDeltaCount", 8); this->add_element(419, "reserved", 0); this->add_element(420, "postLayer2OctetTotalCount", 8); this->add_element(421, "postMCastLayer2OctetTotalCount", 8); this->add_element(422, "minimumLayer2TotalLength", 8); this->add_element(423, "maximumLayer2TotalLength", 8); this->add_element(424, "droppedLayer2OctetDeltaCount", 8); this->add_element(425, "droppedLayer2OctetTotalCount", 8); this->add_element(426, "ignoredLayer2OctetTotalCount", 8); this->add_element(427, "notSentLayer2OctetTotalCount", 8); this->add_element(428, "layer2OctetDeltaSumOfSquares", 8); this->add_element(429, "layer2OctetTotalSumOfSquares", 8); this->add_element(430, "layer2FrameDeltaCount", 8); this->add_element(431, "layer2FrameTotalCount", 8); this->add_element(432, "pseudoWireDestinationIPv4Address", 4); this->add_element(433, "ignoredLayer2FrameTotalCount", 8); } fastnetmon-1.1.3+dfsg/src/ipfix_rfc.h000066400000000000000000000015341313534057500175330ustar00rootroot00000000000000#ifndef IPFIX_RFC_H #define IPFIX_RFC_H /* This file is autogenerated with script ipfix_csv_processor.pl */ /* Please do not edit it directly */ #include #include class ipfix_information_element_t { public: ipfix_information_element_t(std::string name, unsigned int length); ipfix_information_element_t(); std::string get_name(); unsigned int get_length(); std::string name; unsigned int length; }; typedef std::map ipfix_database_t; class ipfix_information_database { public: ipfix_information_database(); bool add_element(unsigned int field_id, std::string name, unsigned int length); std::string get_name_by_id(unsigned int field_id); unsigned int get_length_by_id(unsigned int field_id); private: ipfix_database_t database; }; #endif fastnetmon-1.1.3+dfsg/src/irq_balance_manually.sh000077500000000000000000000010641313534057500221100ustar00rootroot00000000000000#!/bin/bash # from http://habrahabr.ru/post/108240/ ncpus=`grep -ciw ^processor /proc/cpuinfo` test "$ncpus" -gt 1 || exit 1 n=0 for irq in `cat /proc/interrupts | grep eth | awk '{print $1}' | sed s/\://g` do f="/proc/irq/$irq/smp_affinity" test -r "$f" || continue cpu=$[$ncpus - ($n % $ncpus) - 1] if [ $cpu -ge 0 ] then mask=`printf %x $[2 ** $cpu]` echo "Assign SMP affinity: eth queue $n, irq $irq, cpu $cpu, mask 0x$mask" echo "$mask" > "$f" let n+=1 fi done fastnetmon-1.1.3+dfsg/src/libpatricia/000077500000000000000000000000001313534057500176715ustar00rootroot00000000000000fastnetmon-1.1.3+dfsg/src/libpatricia/copyright000066400000000000000000000023451313534057500216300ustar00rootroot00000000000000$Id: COPYRIGHT,v 1.1.1.1 2013/08/15 18:46:09 labovit Exp $ Copyright (c) 1999-2013 The Regents of the University of Michigan ("The Regents") and Merit Network, Inc. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 COPYRIGHT HOLDER 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.fastnetmon-1.1.3+dfsg/src/libpatricia/credits.txt000066400000000000000000000054351313534057500220760ustar00rootroot00000000000000 [newtool.gif] MRT Credits The Multi-Threaded Routing Toolkit _________________________________________________________________ MRT was developed by [1]Merit Network, Inc., under National Science Foundation grant NCR-9318902, "Experimentation with Routing Technology to be Used for Inter-Domain Routing in the Internet." Current MRT Staff * [2]Craig Labovitz * [3]Makaki Hirabaru * [4]Farnam Jahanian * Susan Hares * Susan R. Harris * Nathan Binkert * Gerald Winters Project Alumni * [5]Marc Unangst * John Scudder The BGP4+ extension was originally written by Francis Dupont . The public domain Struct C-library of linked list, hash table and memory allocation routines was developed by Jonathan Dekock . Susan Rebecca Harris provided help with the documentation. David Ward provided bug fixes and helpful suggestions. Some sections of code and architecture ideas were taken from the GateD routing daemon. The first port to Linux with IPv6 was done by Pedro Roque . Some interface routines to the Linux kernel were originally written by him. Alexey Kuznetsov made enhancements to 1.4.3a and fixed the Linux kernel intarface. Linux's netlink interface was written, referring to his code "iproute2". We would also like to thank our other colleagues in Japan, Portugal, the Netherlands, the UK, and the US for their many contributions to the MRT development effort. _________________________________________________________________ Cisco is a registered trademark of Cisco Systems Inc. _________________________________________________________________ Merit Network 4251 Plymouth Road Suite C Ann Arbor, MI 48105-2785 734-764-9430 info@merit.edu _________________________________________________________________ 1999 Merit Network, Inc. [6]www@merit.edu References 1. http://www.merit.edu/ 2. http://www.merit.edu/~labovit 3. http://www.merit.edu/~masaki 4. http://www.eecs.umich.edu/~farnam 5. http://www.contrib.andrew.cmu.edu/~mju/ 6. mailto:www@merit.edu fastnetmon-1.1.3+dfsg/src/libpatricia/patricia.c000066400000000000000000000611351313534057500216370ustar00rootroot00000000000000/* * $Id: patricia.c,v 1.7 2005/12/07 20:46:41 dplonka Exp $ * Dave Plonka * * This product includes software developed by the University of Michigan, * Merit Network, Inc., and their contributors. * * This file had been called "radix.c" in the MRT sources. * * I renamed it to "patricia.c" since it's not an implementation of a general * radix trie. Also I pulled in various requirements from "prefix.c" and * "demo.c" so that it could be used as a standalone API. */ static char copyright[] = "This product includes software developed by the University of Michigan, Merit" "Network, Inc., and their contributors."; #include /* assert */ #include /* isdigit */ #include /* errno */ #include /* sin */ #include /* NULL */ #include /* sprintf, fprintf, stderr */ #include /* free, atol, calloc */ #include /* memcpy, strchr, strlen */ #include /* BSD: for inet_addr */ #include /* BSD, Linux: for inet_addr */ #include /* BSD, Linux: for inet_addr */ #include /* BSD, Linux, Solaris: for inet_addr */ #include "patricia.h" #define Delete free /* { from prefix.c */ /* prefix_tochar * convert prefix information to bytes */ u_char * prefix_tochar (prefix_t * prefix) { if (prefix == NULL) return (NULL); return ((u_char *) & prefix->add.sin); } int comp_with_mask (void *addr, void *dest, u_int mask) { if ( /* mask/8 == 0 || */ memcmp (addr, dest, mask / 8) == 0) { int n = mask / 8; int m = ((-1) << (8 - (mask % 8))); if (mask % 8 == 0 || (((u_char *)addr)[n] & m) == (((u_char *)dest)[n] & m)) return (1); } return (0); } /* this allows imcomplete prefix */ int my_inet_pton (int af, const char *src, void *dst) { if (af == AF_INET) { int i, c, val; u_char xp[sizeof(struct in_addr)] = {0, 0, 0, 0}; for (i = 0; ; i++) { c = *src++; if (!isdigit (c)) return (-1); val = 0; do { val = val * 10 + c - '0'; if (val > 255) return (0); c = *src++; } while (c && isdigit (c)); xp[i] = val; if (c == '\0') break; if (c != '.') return (0); if (i >= 3) return (0); } memcpy (dst, xp, sizeof(struct in_addr)); return (1); #ifdef HAVE_IPV6 } else if (af == AF_INET6) { return (inet_pton (af, src, dst)); #endif /* HAVE_IPV6 */ } else { #ifndef NT errno = EAFNOSUPPORT; #endif /* NT */ return -1; } } #define PATRICIA_MAX_THREADS 16 /* * convert prefix information to ascii string with length * thread safe and (almost) re-entrant implementation */ char * prefix_toa2x (prefix_t *prefix, char *buff, int with_len) { if (prefix == NULL) return ("(Null)"); assert (prefix->ref_count >= 0); if (buff == NULL) { struct buffer { char buffs[PATRICIA_MAX_THREADS][48+5]; u_int i; } *buffp; # if 0 THREAD_SPECIFIC_DATA (struct buffer, buffp, 1); # else { /* for scope only */ static struct buffer local_buff; buffp = &local_buff; } # endif if (buffp == NULL) { /* XXX should we report an error? */ return (NULL); } buff = buffp->buffs[buffp->i++%PATRICIA_MAX_THREADS]; } if (prefix->family == AF_INET) { u_char *a; assert (prefix->bitlen <= sizeof(struct in_addr) * 8); a = prefix_touchar (prefix); if (with_len) { sprintf (buff, "%d.%d.%d.%d/%d", a[0], a[1], a[2], a[3], prefix->bitlen); } else { sprintf (buff, "%d.%d.%d.%d", a[0], a[1], a[2], a[3]); } return (buff); } #ifdef HAVE_IPV6 else if (prefix->family == AF_INET6) { char *r; r = (char *) inet_ntop (AF_INET6, &prefix->add.sin6, buff, 48 /* a guess value */ ); if (r && with_len) { assert (prefix->bitlen <= sizeof(struct in6_addr) * 8); sprintf (buff + strlen (buff), "/%d", prefix->bitlen); } return (buff); } #endif /* HAVE_IPV6 */ else return (NULL); } /* prefix_toa2 * convert prefix information to ascii string */ char * prefix_toa2 (prefix_t *prefix, char *buff) { return (prefix_toa2x (prefix, buff, 0)); } /* prefix_toa */ char * prefix_toa (prefix_t * prefix) { return (prefix_toa2 (prefix, (char *) NULL)); } prefix_t * New_Prefix2 (int family, void *dest, int bitlen, prefix_t *prefix) { int dynamic_allocated = 0; int default_bitlen = sizeof(struct in_addr) * 8; #ifdef HAVE_IPV6 if (family == AF_INET6) { default_bitlen = sizeof(struct in6_addr) * 8; if (prefix == NULL) { prefix = calloc(1, sizeof (prefix_t)); dynamic_allocated++; } memcpy (&prefix->add.sin6, dest, sizeof(struct in6_addr)); } else #endif /* HAVE_IPV6 */ if (family == AF_INET) { if (prefix == NULL) { #ifndef NT prefix = calloc(1, sizeof (prefix4_t)); #else //for some reason, compiler is getting //prefix4_t size incorrect on NT prefix = calloc(1, sizeof (prefix_t)); #endif /* NT */ dynamic_allocated++; } memcpy (&prefix->add.sin, dest, sizeof(struct in_addr)); } else { return (NULL); } prefix->bitlen = (bitlen >= 0)? bitlen: default_bitlen; prefix->family = family; prefix->ref_count = 0; if (dynamic_allocated) { prefix->ref_count++; } /* fprintf(stderr, "[C %s, %d]\n", prefix_toa (prefix), prefix->ref_count); */ return (prefix); } prefix_t * New_Prefix (int family, void *dest, int bitlen) { return (New_Prefix2 (family, dest, bitlen, NULL)); } /* ascii2prefix */ prefix_t * ascii2prefix (int family, char *string) { u_long bitlen, maxbitlen = 0; char *cp; struct in_addr sin; #ifdef HAVE_IPV6 struct in6_addr sin6; #endif /* HAVE_IPV6 */ int result; char save[MAXLINE]; if (string == NULL) return (NULL); /* easy way to handle both families */ if (family == 0) { family = AF_INET; #ifdef HAVE_IPV6 if (strchr (string, ':')) family = AF_INET6; #endif /* HAVE_IPV6 */ } if (family == AF_INET) { maxbitlen = sizeof(struct in_addr) * 8; } #ifdef HAVE_IPV6 else if (family == AF_INET6) { maxbitlen = sizeof(struct in6_addr) * 8; } #endif /* HAVE_IPV6 */ if ((cp = strchr (string, '/')) != NULL) { bitlen = atol (cp + 1); /* *cp = '\0'; */ /* copy the string to save. Avoid destroying the string */ assert (cp - string < MAXLINE); memcpy (save, string, cp - string); save[cp - string] = '\0'; string = save; if (bitlen < 0 || bitlen > maxbitlen) bitlen = maxbitlen; } else { bitlen = maxbitlen; } if (family == AF_INET) { if ((result = my_inet_pton (AF_INET, string, &sin)) <= 0) return (NULL); return (New_Prefix (AF_INET, &sin, bitlen)); } #ifdef HAVE_IPV6 else if (family == AF_INET6) { // Get rid of this with next IPv6 upgrade #if defined(NT) && !defined(HAVE_INET_NTOP) inet6_addr(string, &sin6); return (New_Prefix (AF_INET6, &sin6, bitlen)); #else if ((result = inet_pton (AF_INET6, string, &sin6)) <= 0) return (NULL); #endif /* NT */ return (New_Prefix (AF_INET6, &sin6, bitlen)); } #endif /* HAVE_IPV6 */ else return (NULL); } prefix_t * Ref_Prefix (prefix_t * prefix) { if (prefix == NULL) return (NULL); if (prefix->ref_count == 0) { /* make a copy in case of a static prefix */ return (New_Prefix2 (prefix->family, &prefix->add, prefix->bitlen, NULL)); } prefix->ref_count++; /* fprintf(stderr, "[A %s, %d]\n", prefix_toa (prefix), prefix->ref_count); */ return (prefix); } void Deref_Prefix (prefix_t * prefix) { if (prefix == NULL) return; /* for secure programming, raise an assert. no static prefix can call this */ assert (prefix->ref_count > 0); prefix->ref_count--; assert (prefix->ref_count >= 0); if (prefix->ref_count <= 0) { Delete (prefix); return; } } /* } */ /* #define PATRICIA_DEBUG 1 */ static int num_active_patricia = 0; /* these routines support continuous mask only */ patricia_tree_t * New_Patricia (int maxbits) { patricia_tree_t *patricia = calloc(1, sizeof *patricia); patricia->maxbits = maxbits; patricia->head = NULL; patricia->num_active_node = 0; assert (maxbits <= PATRICIA_MAXBITS); /* XXX */ num_active_patricia++; return (patricia); } /* * if func is supplied, it will be called as func(node->data) * before deleting the node */ void Clear_Patricia (patricia_tree_t *patricia, void_fn_t func) { assert (patricia); if (patricia->head) { patricia_node_t *Xstack[PATRICIA_MAXBITS+1]; patricia_node_t **Xsp = Xstack; patricia_node_t *Xrn = patricia->head; while (Xrn) { patricia_node_t *l = Xrn->l; patricia_node_t *r = Xrn->r; if (Xrn->prefix) { Deref_Prefix (Xrn->prefix); if (Xrn->data && func) func (Xrn->data); } else { assert (Xrn->data == NULL); } Delete (Xrn); patricia->num_active_node--; if (l) { if (r) { *Xsp++ = r; } Xrn = l; } else if (r) { Xrn = r; } else if (Xsp != Xstack) { Xrn = *(--Xsp); } else { Xrn = NULL; } } } assert (patricia->num_active_node == 0); /* Delete (patricia); */ } void Destroy_Patricia (patricia_tree_t *patricia, void_fn_t func) { Clear_Patricia (patricia, func); Delete (patricia); num_active_patricia--; } /* * if func is supplied, it will be called as func(node->prefix, node->data) */ void patricia_process (patricia_tree_t *patricia, void_fn_t func) { patricia_node_t *node; assert (func); PATRICIA_WALK (patricia->head, node) { func (node->prefix, node->data); } PATRICIA_WALK_END; } size_t patricia_walk_inorder(patricia_node_t *node, void_fn_t func) { size_t n = 0; assert(func); if (node->l) { n += patricia_walk_inorder(node->l, func); } if (node->prefix) { func(node->prefix, node->data); n++; } if (node->r) { n += patricia_walk_inorder(node->r, func); } return n; } patricia_node_t * patricia_search_exact (patricia_tree_t *patricia, prefix_t *prefix) { patricia_node_t *node; u_char *addr; u_int bitlen; assert (patricia); assert (prefix); assert (prefix->bitlen <= patricia->maxbits); if (patricia->head == NULL) return (NULL); node = patricia->head; addr = prefix_touchar (prefix); bitlen = prefix->bitlen; while (node->bit < bitlen) { if (BIT_TEST (addr[node->bit >> 3], 0x80 >> (node->bit & 0x07))) { #ifdef PATRICIA_DEBUG if (node->prefix) fprintf (stderr, "patricia_search_exact: take right %s/%d\n", prefix_toa (node->prefix), node->prefix->bitlen); else fprintf (stderr, "patricia_search_exact: take right at %u\n", node->bit); #endif /* PATRICIA_DEBUG */ node = node->r; } else { #ifdef PATRICIA_DEBUG if (node->prefix) fprintf (stderr, "patricia_search_exact: take left %s/%d\n", prefix_toa (node->prefix), node->prefix->bitlen); else fprintf (stderr, "patricia_search_exact: take left at %u\n", node->bit); #endif /* PATRICIA_DEBUG */ node = node->l; } if (node == NULL) return (NULL); } #ifdef PATRICIA_DEBUG if (node->prefix) fprintf (stderr, "patricia_search_exact: stop at %s/%d\n", prefix_toa (node->prefix), node->prefix->bitlen); else fprintf (stderr, "patricia_search_exact: stop at %u\n", node->bit); #endif /* PATRICIA_DEBUG */ if (node->bit > bitlen || node->prefix == NULL) return (NULL); assert (node->bit == bitlen); assert (node->bit == node->prefix->bitlen); if (comp_with_mask (prefix_tochar (node->prefix), prefix_tochar (prefix), bitlen)) { #ifdef PATRICIA_DEBUG fprintf (stderr, "patricia_search_exact: found %s/%d\n", prefix_toa (node->prefix), node->prefix->bitlen); #endif /* PATRICIA_DEBUG */ return (node); } return (NULL); } /* if inclusive != 0, "best" may be the given prefix itself */ patricia_node_t * patricia_search_best2 (patricia_tree_t *patricia, prefix_t *prefix, int inclusive) { patricia_node_t *node; patricia_node_t *stack[PATRICIA_MAXBITS + 1]; u_char *addr; u_int bitlen; int cnt = 0; assert (patricia); assert (prefix); assert (prefix->bitlen <= patricia->maxbits); if (patricia->head == NULL) return (NULL); node = patricia->head; addr = prefix_touchar (prefix); bitlen = prefix->bitlen; while (node->bit < bitlen) { if (node->prefix) { #ifdef PATRICIA_DEBUG fprintf (stderr, "patricia_search_best: push %s/%d\n", prefix_toa (node->prefix), node->prefix->bitlen); #endif /* PATRICIA_DEBUG */ stack[cnt++] = node; } if (BIT_TEST (addr[node->bit >> 3], 0x80 >> (node->bit & 0x07))) { #ifdef PATRICIA_DEBUG if (node->prefix) fprintf (stderr, "patricia_search_best: take right %s/%d\n", prefix_toa (node->prefix), node->prefix->bitlen); else fprintf (stderr, "patricia_search_best: take right at %u\n", node->bit); #endif /* PATRICIA_DEBUG */ node = node->r; } else { #ifdef PATRICIA_DEBUG if (node->prefix) fprintf (stderr, "patricia_search_best: take left %s/%d\n", prefix_toa (node->prefix), node->prefix->bitlen); else fprintf (stderr, "patricia_search_best: take left at %u\n", node->bit); #endif /* PATRICIA_DEBUG */ node = node->l; } if (node == NULL) break; } if (inclusive && node && node->prefix) stack[cnt++] = node; #ifdef PATRICIA_DEBUG if (node == NULL) fprintf (stderr, "patricia_search_best: stop at null\n"); else if (node->prefix) fprintf (stderr, "patricia_search_best: stop at %s/%d\n", prefix_toa (node->prefix), node->prefix->bitlen); else fprintf (stderr, "patricia_search_best: stop at %u\n", node->bit); #endif /* PATRICIA_DEBUG */ if (cnt <= 0) return (NULL); while (--cnt >= 0) { node = stack[cnt]; #ifdef PATRICIA_DEBUG fprintf (stderr, "patricia_search_best: pop %s/%d\n", prefix_toa (node->prefix), node->prefix->bitlen); #endif /* PATRICIA_DEBUG */ if (comp_with_mask (prefix_tochar (node->prefix), prefix_tochar (prefix), node->prefix->bitlen) && node->prefix->bitlen <= bitlen) { #ifdef PATRICIA_DEBUG fprintf (stderr, "patricia_search_best: found %s/%d\n", prefix_toa (node->prefix), node->prefix->bitlen); #endif /* PATRICIA_DEBUG */ return (node); } } return (NULL); } patricia_node_t * patricia_search_best (patricia_tree_t *patricia, prefix_t *prefix) { return (patricia_search_best2 (patricia, prefix, 1)); } patricia_node_t * patricia_lookup (patricia_tree_t *patricia, prefix_t *prefix) { patricia_node_t *node, *new_node, *parent, *glue; u_char *addr, *test_addr; u_int bitlen, check_bit, differ_bit; int i, j, r; assert (patricia); assert (prefix); assert (prefix->bitlen <= patricia->maxbits); if (patricia->head == NULL) { node = calloc(1, sizeof *node); node->bit = prefix->bitlen; node->prefix = Ref_Prefix (prefix); node->parent = NULL; node->l = node->r = NULL; node->data = NULL; patricia->head = node; #ifdef PATRICIA_DEBUG fprintf (stderr, "patricia_lookup: new_node #0 %s/%d (head)\n", prefix_toa (prefix), prefix->bitlen); #endif /* PATRICIA_DEBUG */ patricia->num_active_node++; return (node); } addr = prefix_touchar (prefix); bitlen = prefix->bitlen; node = patricia->head; while (node->bit < bitlen || node->prefix == NULL) { if (node->bit < patricia->maxbits && BIT_TEST (addr[node->bit >> 3], 0x80 >> (node->bit & 0x07))) { if (node->r == NULL) break; #ifdef PATRICIA_DEBUG if (node->prefix) fprintf (stderr, "patricia_lookup: take right %s/%d\n", prefix_toa (node->prefix), node->prefix->bitlen); else fprintf (stderr, "patricia_lookup: take right at %u\n", node->bit); #endif /* PATRICIA_DEBUG */ node = node->r; } else { if (node->l == NULL) break; #ifdef PATRICIA_DEBUG if (node->prefix) fprintf (stderr, "patricia_lookup: take left %s/%d\n", prefix_toa (node->prefix), node->prefix->bitlen); else fprintf (stderr, "patricia_lookup: take left at %u\n", node->bit); #endif /* PATRICIA_DEBUG */ node = node->l; } assert (node); } assert (node->prefix); #ifdef PATRICIA_DEBUG fprintf (stderr, "patricia_lookup: stop at %s/%d\n", prefix_toa (node->prefix), node->prefix->bitlen); #endif /* PATRICIA_DEBUG */ test_addr = prefix_touchar (node->prefix); /* find the first bit different */ check_bit = (node->bit < bitlen)? node->bit: bitlen; differ_bit = 0; for (i = 0; i*8 < check_bit; i++) { if ((r = (addr[i] ^ test_addr[i])) == 0) { differ_bit = (i + 1) * 8; continue; } /* I know the better way, but for now */ for (j = 0; j < 8; j++) { if (BIT_TEST (r, (0x80 >> j))) break; } /* must be found */ assert (j < 8); differ_bit = i * 8 + j; break; } if (differ_bit > check_bit) differ_bit = check_bit; #ifdef PATRICIA_DEBUG fprintf (stderr, "patricia_lookup: differ_bit %d\n", differ_bit); #endif /* PATRICIA_DEBUG */ parent = node->parent; while (parent && parent->bit >= differ_bit) { node = parent; parent = node->parent; #ifdef PATRICIA_DEBUG if (node->prefix) fprintf (stderr, "patricia_lookup: up to %s/%d\n", prefix_toa (node->prefix), node->prefix->bitlen); else fprintf (stderr, "patricia_lookup: up to %u\n", node->bit); #endif /* PATRICIA_DEBUG */ } if (differ_bit == bitlen && node->bit == bitlen) { if (node->prefix) { #ifdef PATRICIA_DEBUG fprintf (stderr, "patricia_lookup: found %s/%d\n", prefix_toa (node->prefix), node->prefix->bitlen); #endif /* PATRICIA_DEBUG */ return (node); } node->prefix = Ref_Prefix (prefix); #ifdef PATRICIA_DEBUG fprintf (stderr, "patricia_lookup: new node #1 %s/%d (glue mod)\n", prefix_toa (prefix), prefix->bitlen); #endif /* PATRICIA_DEBUG */ assert (node->data == NULL); return (node); } new_node = calloc(1, sizeof *new_node); new_node->bit = prefix->bitlen; new_node->prefix = Ref_Prefix (prefix); new_node->parent = NULL; new_node->l = new_node->r = NULL; new_node->data = NULL; patricia->num_active_node++; if (node->bit == differ_bit) { new_node->parent = node; if (node->bit < patricia->maxbits && BIT_TEST (addr[node->bit >> 3], 0x80 >> (node->bit & 0x07))) { assert (node->r == NULL); node->r = new_node; } else { assert (node->l == NULL); node->l = new_node; } #ifdef PATRICIA_DEBUG fprintf (stderr, "patricia_lookup: new_node #2 %s/%d (child)\n", prefix_toa (prefix), prefix->bitlen); #endif /* PATRICIA_DEBUG */ return (new_node); } if (bitlen == differ_bit) { if (bitlen < patricia->maxbits && BIT_TEST (test_addr[bitlen >> 3], 0x80 >> (bitlen & 0x07))) { new_node->r = node; } else { new_node->l = node; } new_node->parent = node->parent; if (node->parent == NULL) { assert (patricia->head == node); patricia->head = new_node; } else if (node->parent->r == node) { node->parent->r = new_node; } else { node->parent->l = new_node; } node->parent = new_node; #ifdef PATRICIA_DEBUG fprintf (stderr, "patricia_lookup: new_node #3 %s/%d (parent)\n", prefix_toa (prefix), prefix->bitlen); #endif /* PATRICIA_DEBUG */ } else { glue = calloc(1, sizeof *glue); glue->bit = differ_bit; glue->prefix = NULL; glue->parent = node->parent; glue->data = NULL; patricia->num_active_node++; if (differ_bit < patricia->maxbits && BIT_TEST (addr[differ_bit >> 3], 0x80 >> (differ_bit & 0x07))) { glue->r = new_node; glue->l = node; } else { glue->r = node; glue->l = new_node; } new_node->parent = glue; if (node->parent == NULL) { assert (patricia->head == node); patricia->head = glue; } else if (node->parent->r == node) { node->parent->r = glue; } else { node->parent->l = glue; } node->parent = glue; #ifdef PATRICIA_DEBUG fprintf (stderr, "patricia_lookup: new_node #4 %s/%d (glue+node)\n", prefix_toa (prefix), prefix->bitlen); #endif /* PATRICIA_DEBUG */ } return (new_node); } void patricia_remove (patricia_tree_t *patricia, patricia_node_t *node) { patricia_node_t *parent, *child; assert (patricia); assert (node); if (node->r && node->l) { #ifdef PATRICIA_DEBUG fprintf (stderr, "patricia_remove: #0 %s/%d (r & l)\n", prefix_toa (node->prefix), node->prefix->bitlen); #endif /* PATRICIA_DEBUG */ /* this might be a placeholder node -- have to check and make sure * there is a prefix aossciated with it ! */ if (node->prefix != NULL) Deref_Prefix (node->prefix); node->prefix = NULL; /* Also I needed to clear data pointer -- masaki */ node->data = NULL; return; } if (node->r == NULL && node->l == NULL) { #ifdef PATRICIA_DEBUG fprintf (stderr, "patricia_remove: #1 %s/%d (!r & !l)\n", prefix_toa (node->prefix), node->prefix->bitlen); #endif /* PATRICIA_DEBUG */ parent = node->parent; Deref_Prefix (node->prefix); Delete (node); patricia->num_active_node--; if (parent == NULL) { assert (patricia->head == node); patricia->head = NULL; return; } if (parent->r == node) { parent->r = NULL; child = parent->l; } else { assert (parent->l == node); parent->l = NULL; child = parent->r; } if (parent->prefix) return; /* we need to remove parent too */ if (parent->parent == NULL) { assert (patricia->head == parent); patricia->head = child; } else if (parent->parent->r == parent) { parent->parent->r = child; } else { assert (parent->parent->l == parent); parent->parent->l = child; } child->parent = parent->parent; Delete (parent); patricia->num_active_node--; return; } #ifdef PATRICIA_DEBUG fprintf (stderr, "patricia_remove: #2 %s/%d (r ^ l)\n", prefix_toa (node->prefix), node->prefix->bitlen); #endif /* PATRICIA_DEBUG */ if (node->r) { child = node->r; } else { assert (node->l); child = node->l; } parent = node->parent; child->parent = parent; Deref_Prefix (node->prefix); Delete (node); patricia->num_active_node--; if (parent == NULL) { assert (patricia->head == node); patricia->head = child; return; } if (parent->r == node) { parent->r = child; } else { assert (parent->l == node); parent->l = child; } } /* { from demo.c */ patricia_node_t * make_and_lookup (patricia_tree_t *tree, char *string) { prefix_t *prefix; patricia_node_t *node; prefix = ascii2prefix (AF_INET, string); // printf ("make_and_lookup: %s/%d\n", prefix_toa (prefix), prefix->bitlen); node = patricia_lookup (tree, prefix); Deref_Prefix (prefix); return (node); } patricia_node_t * make_and_lookup_ipv6 (patricia_tree_t *tree, char *string) { prefix_t *prefix; patricia_node_t *node; prefix = ascii2prefix (AF_INET6, string); // printf ("make_and_lookup: %s/%d\n", prefix_toa (prefix), prefix->bitlen); node = patricia_lookup (tree, prefix); Deref_Prefix (prefix); return (node); } patricia_node_t * try_search_exact (patricia_tree_t *tree, char *string) { prefix_t *prefix; patricia_node_t *node; prefix = ascii2prefix (AF_INET, string); //printf ("try_search_exact: %s/%d\n", prefix_toa (prefix), prefix->bitlen); if ((node = patricia_search_exact (tree, prefix)) == NULL) { //printf ("try_search_exact: not found\n"); } else { //printf ("try_search_exact: %s/%d found\n", //prefix_toa (node->prefix), node->prefix->bitlen); } Deref_Prefix (prefix); return (node); } void lookup_then_remove (patricia_tree_t *tree, char *string) { patricia_node_t *node; if ((node = try_search_exact (tree, string))) patricia_remove (tree, node); } patricia_node_t * try_search_best (patricia_tree_t *tree, char *string) { prefix_t *prefix; patricia_node_t *node; prefix = ascii2prefix (AF_INET, string); //printf ("try_search_best: %s/%d\n", prefix_toa (prefix), prefix->bitlen); if ((node = patricia_search_best (tree, prefix)) == NULL) { //printf ("try_search_best: not found\n"); } else { //printf ("try_search_best: %s/%d found\n", //prefix_toa (node->prefix), node->prefix->bitlen); } Deref_Prefix (prefix); return (node); } /* } */ fastnetmon-1.1.3+dfsg/src/libpatricia/patricia.h000066400000000000000000000107531313534057500216440ustar00rootroot00000000000000#ifdef __cplusplus extern "C" { #endif /* * $Id: patricia.h,v 1.6 2005/12/07 20:53:01 dplonka Exp $ * Dave Plonka * * This product includes software developed by the University of Michigan, * Merit Network, Inc., and their contributors. * * This file had been called "radix.h" in the MRT sources. * * I renamed it to "patricia.h" since it's not an implementation of a general * radix trie. Also, pulled in various requirements from "mrt.h" and added * some other things it could be used as a standalone API. */ #ifndef PATRICIA_H #define PATRICIA_H #define HAVE_IPV6 /* typedef unsigned int u_int; */ typedef void (*void_fn_t)(); /* { from defs.h */ #define prefix_touchar(prefix) ((u_char *)&(prefix)->add.sin) #define MAXLINE 1024 #define BIT_TEST(f, b) ((f) & (b)) /* } */ #define addroute make_and_lookup #include /* for u_* definitions (on FreeBSD 5) */ #include /* for EAFNOSUPPORT */ #ifndef EAFNOSUPPORT # defined EAFNOSUPPORT WSAEAFNOSUPPORT # include #else # include /* for struct in_addr */ #endif #include /* for AF_INET */ /* { from mrt.h */ typedef struct _prefix4_t { u_short family; /* AF_INET | AF_INET6 */ u_short bitlen; /* same as mask? */ int ref_count; /* reference count */ struct in_addr sin; } prefix4_t; typedef struct _prefix_t { u_short family; /* AF_INET | AF_INET6 */ u_short bitlen; /* same as mask? */ int ref_count; /* reference count */ union { struct in_addr sin; #ifdef HAVE_IPV6 struct in6_addr sin6; #endif /* IPV6 */ } add; } prefix_t; /* } */ typedef struct _patricia_node_t { u_int bit; /* flag if this node used */ prefix_t *prefix; /* who we are in patricia tree */ struct _patricia_node_t *l, *r; /* left and right children */ struct _patricia_node_t *parent;/* may be used */ void *data; /* pointer to data */ void *user1; /* pointer to usr data (ex. route flap info) */ } patricia_node_t; typedef struct _patricia_tree_t { patricia_node_t *head; u_int maxbits; /* for IP, 32 bit addresses */ int num_active_node; /* for debug purpose */ } patricia_tree_t; patricia_node_t *patricia_search_exact (patricia_tree_t *patricia, prefix_t *prefix); patricia_node_t *patricia_search_best (patricia_tree_t *patricia, prefix_t *prefix); patricia_node_t * patricia_search_best2 (patricia_tree_t *patricia, prefix_t *prefix, int inclusive); patricia_node_t *patricia_lookup (patricia_tree_t *patricia, prefix_t *prefix); void patricia_remove (patricia_tree_t *patricia, patricia_node_t *node); patricia_tree_t *New_Patricia (int maxbits); void Clear_Patricia (patricia_tree_t *patricia, void_fn_t func); void Destroy_Patricia (patricia_tree_t *patricia, void_fn_t func); void patricia_process (patricia_tree_t *patricia, void_fn_t func); char *prefix_toa (prefix_t * prefix); /* { from demo.c */ prefix_t * ascii2prefix (int family, char *string); patricia_node_t * make_and_lookup (patricia_tree_t *tree, char *string); patricia_node_t * make_and_lookup_ipv6 (patricia_tree_t *tree, char *string); /* } */ #define PATRICIA_MAXBITS (sizeof(struct in6_addr) * 8) #define PATRICIA_NBIT(x) (0x80 >> ((x) & 0x7f)) #define PATRICIA_NBYTE(x) ((x) >> 3) #define PATRICIA_DATA_GET(node, type) (type *)((node)->data) #define PATRICIA_DATA_SET(node, value) ((node)->data = (void *)(value)) #define PATRICIA_WALK(Xhead, Xnode) \ do { \ patricia_node_t *Xstack[PATRICIA_MAXBITS+1]; \ patricia_node_t **Xsp = Xstack; \ patricia_node_t *Xrn = (Xhead); \ while ((Xnode = Xrn)) { \ if (Xnode->prefix) #define PATRICIA_WALK_ALL(Xhead, Xnode) \ do { \ patricia_node_t *Xstack[PATRICIA_MAXBITS+1]; \ patricia_node_t **Xsp = Xstack; \ patricia_node_t *Xrn = (Xhead); \ while ((Xnode = Xrn)) { \ if (1) #define PATRICIA_WALK_BREAK { \ if (Xsp != Xstack) { \ Xrn = *(--Xsp); \ } else { \ Xrn = (patricia_node_t *) 0; \ } \ continue; } #define PATRICIA_WALK_END \ if (Xrn->l) { \ if (Xrn->r) { \ *Xsp++ = Xrn->r; \ } \ Xrn = Xrn->l; \ } else if (Xrn->r) { \ Xrn = Xrn->r; \ } else if (Xsp != Xstack) { \ Xrn = *(--Xsp); \ } else { \ Xrn = (patricia_node_t *) 0; \ } \ } \ } while (0) #endif /* PATRICIA_H */ #ifdef __cplusplus } #endif fastnetmon-1.1.3+dfsg/src/log4cpp.spec000066400000000000000000000057441313534057500176440ustar00rootroot00000000000000%define RELEASE 4 %define rel %{?CUSTOM_RELEASE} %{!?CUSTOM_RELEASE:%RELEASE} %define lib_name log4cpp %define manualdir /var/www/html/manual/%{name} Name: log4cpp # Fixed by Pavel Odintsov Version: 1.1.1 Release: %rel Summary: Log for C++ License: LGPL Group: Development/Libraries Vendor: Bastiaan Bakker Packager: Cedric Le Goater Url: http://log4cpp.sourceforge.net/ Source: ftp://download.sourceforge.net/pub/sourceforge/log4cpp/%name-%version.tar.gz Prefix: %_prefix BuildRoot: %_tmppath/%name-%version-root %description Log for C++ is a library of classes for flexible logging to files, syslog, and other destinations. It is modeled after the Log for Java library and stays as close to its API as is reasonable. %package devel Summary: development tools for Log for C++ Group: Development/Libraries Requires: %name = %version %description devel The %name-devel package contains the static libraries and header files needed for development with %name. %package doc Summary: HTML formatted API documention for Log for C++ Group: Development/Libraries #can't set doc package to noarch without setting the others as well. #BuildArch: noarch %{!?_without_doxygenrpm:BuildRequires: doxygen} %description doc The %name-doc package contains HTML formatted API documention generated by the popular doxygen documentation generation tool. %prep %{__rm} -rf $RPM_BUILD_ROOT # Fixed by Pavel Odintsov %setup -n log4cpp # Doxygen disabled temporarly by Pavel Odintsov CC=%{__cc} CXX=%{__cxx} ./configure --prefix=%{prefix} --enable-doxygen %build %{__make} %install %{__rm} -rf $RPM_BUILD_ROOT # Space before docdir added by Pavel Odintsov %{__make} prefix=$RPM_BUILD_ROOT%{prefix} docdir=$RPM_BUILD_ROOT/%{manualdir} install %clean %{__rm} -rf $RPM_BUILD_ROOT %ifnos solaris2.8 solaris2.9 solaris2.10 %post -p /sbin/ldconfig %endif %post devel if test "x$RPM_INSTALL_PREFIX0" != "x" ; then %{__perl} -pi -e"s|^libdir='[^\']*'|libdir='$RPM_INSTALL_PREFIX0/lib'|" $RPM_INSTALL_PREFIX0/lib/liblog4cpp.la %{__perl} -pi -e"s|^prefix=\"[^\"]*\"|prefix=\"$RPM_INSTALL_PREFIX0\"|" $RPM_INSTALL_PREFIX0/bin/log4cpp-config fi %ifnos solaris2.8 solaris2.9 solaris2.10 %postun -p /sbin/ldconfig %endif %files %defattr(-,root,root,755) %attr(755,root,root) %prefix/lib/lib*.so.* %doc AUTHORS COPYING INSTALL NEWS README THANKS ChangeLog # Enable filtering of symlinks #%filter_requires_in %prefix/lib/liblog4cpp.so #%filter_setup %files devel %defattr(-,root,root,755) %prefix/include/* %prefix/share/man/* %attr(755,root,root) %prefix/bin/log4cpp-config # All * patterns replaced by Pavel Odintsov # It's symlink #%attr(755,root,root) %prefix/lib/liblog4cpp.so %attr(755,root,root) %prefix/lib/liblog4cpp.so.5.0.6 %attr(644,root,root) %prefix/lib/liblog4cpp.a %attr(644,root,root) %prefix/lib/liblog4cpp.la %attr(644,root,root) %prefix/lib/pkgconfig/log4cpp.pc %attr(644,root,root) %prefix/share/aclocal/*.m4 %files doc %defattr(-,root,root) %doc %{manualdir} fastnetmon-1.1.3+dfsg/src/man/000077500000000000000000000000001313534057500161615ustar00rootroot00000000000000fastnetmon-1.1.3+dfsg/src/man/fastnetmon.1000066400000000000000000000015361313534057500204260ustar00rootroot00000000000000.\" Manpage for fastnetmon. .\" Contact pavel.odintsov@gmail.com to correct errors or typos. .TH man 1 "04 Jun 2015" "1.1.2" "fastnetmon man page" .SH NAME FastNetMon \- a high performance DoS/DDoS load analyzer built on top of multiple packet capture .SH SYNOPSIS fastnetmon [--daemonize] .SH DESCRIPTION FastNetMon - a high performance DoS/DDoS load analyzer built on top of multiple packet capture engines (NetFlow, IPFIX, sFLOW, netmap, PF_RING, PCAP). For more information about configuration please look at comments in /etc/fastnetmon.conf and check GitHub page: https://github.com/FastVPSEestiOu/fastnetmon. .SH OPTIONS The fastnetmon has only single command line option --daemonize which used for forking and detaching it from the terminal. .SH SEE ALSO fastnetmon_client(1) .SH BUGS No known bugs. .SH AUTHOR Pavel Odintsov (pavel.odintsov@gmail.com) fastnetmon-1.1.3+dfsg/src/man/fastnetmon_client.1000066400000000000000000000007731313534057500217660ustar00rootroot00000000000000.\" Manpage for fastnetmon_client. .\" Contact pavel.odintsov@gmail.com to correct errors or typos. .TH man 1 "04 Jun 2015" "1.1.2" "fastnetmon_client man page" .SH NAME fastnetmon_client \- show information about top talkers and detected DDoS attacks .SH SYNOPSIS fastnetmon_client .SH DESCRIPTION It's client interface for monitoring fastnetmon(1) DDoS detection toolkit. .SH OPTIONS No options here .SH SEE ALSO fastnetmon(1) .SH BUGS No known bugs. .SH AUTHOR Pavel Odintsov (pavel.odintsov@gmail.com) fastnetmon-1.1.3+dfsg/src/netflow_hooks.lua000066400000000000000000000037741313534057500210050ustar00rootroot00000000000000package.path = package.path .. ";/usr/share/lua/5.1/?.lua" local json = require("json") -- We have this library bundled only in luajit: -- g++ lua_integration.cpp -lluajit-5.1 -- Before production use, please call your code with luajit CLI local ffi = require("ffi") -- Load declaration from the inside separate header file -- This code should be in sync with https://github.com/FastVPSEestiOu/fastnetmon/blob/master/src/netflow_plugin/netflow.h -- And we use uintXX_t instead u_intXX_t here ffi.cdef([[typedef struct __attribute__((packed)) NF5_FLOW { uint32_t src_ip, dest_ip, nexthop_ip; uint16_t if_index_in, if_index_out; uint32_t flow_packets, flow_octets; uint32_t flow_start, flow_finish; uint16_t src_port, dest_port; uint8_t pad1; uint8_t tcp_flags, protocol, tos; uint16_t src_as, dest_as; uint8_t src_mask, dst_mask; uint16_t pad2; } NF5_FLOW_t;]]) -- Load json file once local json_file = io.open("/usr/src/fastnetmon/src/tests/netflow_exclude.json", "r") local decoded = json.decode(json_file:read("*all")) --for k, v in pairs(decoded) do -- for kk, vv in pairs(v) do -- print(k, kk, vv) -- end --end function process_netflow(flow_agent_ip, flow) local netlflow5_t = ffi.typeof('NF5_FLOW_t*') local lua_flow = ffi.cast(netlflow5_t, flow) --print ("We got this packets from: ", flow_agent_ip) -- TODO: PLEASE BE AWARE! Thid code will read json file for every netflow packet --print ("Flow packets and bytes: ", lua_flow.flow_packets, lua_flow.flow_octets) --print ("In interface :", lua_flow.if_index_in, " out interface: ", lua_flow.if_index_out) for agent_ip, ports_table in pairs(decoded) do if agent_ip == flow_agent_ip then for port_number, port_description in pairs(ports_table) do if lua_flow.if_index_in == port_number then -- We found this port in ignore list return false end end end end return true end fastnetmon-1.1.3+dfsg/src/netflow_plugin/000077500000000000000000000000001313534057500204425ustar00rootroot00000000000000fastnetmon-1.1.3+dfsg/src/netflow_plugin/netflow.h000066400000000000000000000176361313534057500223060ustar00rootroot00000000000000/* $Id$ */ /* * Copyright (c) 2004,2005 Damien Miller * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* NetFlow packet definitions */ #ifndef NETFLOW_H #define NETFLOW_H #include /* A record in a NetFlow v.9 template record */ struct peer_nf9_record { u_int type; u_int len; }; typedef std::vector netflow9_template_records_map; /* A NetFlow v.9 template record */ struct peer_nf9_template { u_int16_t template_id; u_int num_records; u_int total_len; netflow9_template_records_map records; }; // TODO: clean up!!! #if defined(__GNUC__) #ifndef __dead #define __dead __attribute__((__noreturn__)) #endif #ifndef __packed #define __packed __attribute__((__packed__)) #endif #endif /* * These are Cisco Netflow(tm) packet formats * Based on: * http://www.cisco.com/univercd/cc/td/doc/product/rtrmgmt/nfc/nfc_3_0/nfc_ug/nfcform.htm */ /* Common header fields */ struct NF_HEADER_COMMON { u_int16_t version, flows; } __packed; /* Netflow v.1 */ struct NF1_HEADER { struct NF_HEADER_COMMON c; u_int32_t uptime_ms, time_sec, time_nanosec; } __packed; struct NF1_FLOW { u_int32_t src_ip, dest_ip, nexthop_ip; u_int16_t if_index_in, if_index_out; u_int32_t flow_packets, flow_octets; u_int32_t flow_start, flow_finish; u_int16_t src_port, dest_port; u_int16_t pad1; u_int8_t protocol, tos, tcp_flags; u_int8_t pad2, pad3, pad4; u_int32_t reserved1; #if 0 u_int8_t reserved2; /* XXX: no longer used */ #endif } __packed; /* Maximum of 30 flows per packet */ #define NF1_MAXFLOWS 24 #define NF1_PACKET_SIZE(nflows) (sizeof(struct NF1_HEADER) + ((nflows) * sizeof(struct NF1_FLOW))) #define NF1_MAXPACKET_SIZE (NF1_PACKET_SIZE(NF1_MAXFLOWS)) /* Netflow v.5 */ struct NF5_HEADER { struct NF_HEADER_COMMON c; u_int32_t uptime_ms, time_sec, time_nanosec, flow_sequence; u_int8_t engine_type, engine_id; // "First two bits hold the sampling mode; remaining 14 bits hold value of sampling interval" // accoring to https://www.plixer.com/support/netflow_v5.html // http://www.cisco.com/c/en/us/td/docs/net_mgmt/netflow_collection_engine/3-6/user/guide/format.html u_int16_t sampling_rate; } __packed; struct NF5_FLOW { u_int32_t src_ip, dest_ip, nexthop_ip; u_int16_t if_index_in, if_index_out; u_int32_t flow_packets, flow_octets; u_int32_t flow_start, flow_finish; u_int16_t src_port, dest_port; u_int8_t pad1; u_int8_t tcp_flags, protocol, tos; u_int16_t src_as, dest_as; u_int8_t src_mask, dst_mask; u_int16_t pad2; } __packed; /* Maximum of 24 flows per packet */ #define NF5_MAXFLOWS 30 #define NF5_PACKET_SIZE(nflows) (sizeof(struct NF5_HEADER) + ((nflows) * sizeof(struct NF5_FLOW))) #define NF5_MAXPACKET_SIZE (NF5_PACKET_SIZE(NF5_MAXFLOWS)) /* Netflow v.7 */ struct NF7_HEADER { struct NF_HEADER_COMMON c; u_int32_t uptime_ms, time_sec, time_nanosec, flow_sequence; u_int32_t reserved1; } __packed; struct NF7_FLOW { u_int32_t src_ip, dest_ip, nexthop_ip; u_int16_t if_index_in, if_index_out; u_int32_t flow_packets, flow_octets; u_int32_t flow_start, flow_finish; u_int16_t src_port, dest_port; u_int8_t flags1; u_int8_t tcp_flags, protocol, tos; u_int16_t src_as, dest_as; u_int8_t src_mask, dst_mask; u_int16_t flags2; u_int32_t router_sc; } __packed; /* Maximum of 24 flows per packet */ #define NF7_MAXFLOWS 30 #define NF7_PACKET_SIZE(nflows) (sizeof(struct NF7_HEADER) + ((nflows) * sizeof(struct NF7_FLOW))) #define NF7_MAXPACKET_SIZE (NF7_PACKET_SIZE(NF7_MAXFLOWS)) /* Netflow v.9 */ struct NF9_HEADER { struct NF_HEADER_COMMON c; u_int32_t uptime_ms, time_sec; u_int32_t package_sequence, source_id; } __packed; struct NF9_FLOWSET_HEADER_COMMON { u_int16_t flowset_id, length; } __packed; struct NF9_TEMPLATE_FLOWSET_HEADER { u_int16_t template_id, count; } __packed; struct NF9_TEMPLATE_FLOWSET_RECORD { u_int16_t type, length; } __packed; struct NF9_DATA_FLOWSET_HEADER { struct NF9_FLOWSET_HEADER_COMMON c; } __packed; #define NF9_TEMPLATE_FLOWSET_ID 0 #define NF9_OPTIONS_FLOWSET_ID 1 #define NF9_MIN_RECORD_FLOWSET_ID 256 /* Flowset record types the we care about */ #define NF9_IN_BYTES 1 #define NF9_IN_PACKETS 2 /* ... */ #define NF9_IN_PROTOCOL 4 #define NF9_SRC_TOS 5 #define NF9_TCP_FLAGS 6 #define NF9_L4_SRC_PORT 7 #define NF9_IPV4_SRC_ADDR 8 #define NF9_SRC_MASK 9 #define NF9_INPUT_SNMP 10 #define NF9_L4_DST_PORT 11 #define NF9_IPV4_DST_ADDR 12 #define NF9_DST_MASK 13 #define NF9_OUTPUT_SNMP 14 #define NF9_IPV4_NEXT_HOP 15 #define NF9_SRC_AS 16 #define NF9_DST_AS 17 /* ... */ #define NF9_LAST_SWITCHED 21 #define NF9_FIRST_SWITCHED 22 /* ... */ #define NF9_IPV6_SRC_ADDR 27 #define NF9_IPV6_DST_ADDR 28 #define NF9_IPV6_SRC_MASK 29 #define NF9_IPV6_DST_MASK 30 /* Added by Odintsov Pavel */ #define NF9_SAMPLING_INTERVAL 34 /* ... */ #define NF9_ENGINE_TYPE 38 #define NF9_ENGINE_ID 39 /* ... */ #define NF9_IPV6_NEXT_HOP 62 /* Netflow v.10 */ struct NF10_HEADER { struct NF_HEADER_COMMON c; u_int32_t time_sec; u_int32_t package_sequence, source_id; } __packed; struct NF10_FLOWSET_HEADER_COMMON { u_int16_t flowset_id, length; } __packed; struct NF10_TEMPLATE_FLOWSET_HEADER { u_int16_t template_id, count; } __packed; struct NF10_TEMPLATE_FLOWSET_RECORD { u_int16_t type, length; } __packed; struct NF10_DATA_FLOWSET_HEADER { struct NF10_FLOWSET_HEADER_COMMON c; } __packed; #define NF10_TEMPLATE_FLOWSET_ID 2 #define NF10_OPTIONS_FLOWSET_ID 3 #define NF10_MIN_RECORD_FLOWSET_ID 256 #define NF10_ENTERPRISE (1 << 15) /* Flowset record types the we care about */ #define NF10_IN_BYTES 1 #define NF10_IN_PACKETS 2 /* ... */ #define NF10_IN_PROTOCOL 4 #define NF10_SRC_TOS 5 #define NF10_TCP_FLAGS 6 #define NF10_L4_SRC_PORT 7 #define NF10_IPV4_SRC_ADDR 8 #define NF10_SRC_MASK 9 #define NF10_INPUT_SNMP 10 #define NF10_L4_DST_PORT 11 #define NF10_IPV4_DST_ADDR 12 #define NF10_DST_MASK 13 #define NF10_OUTPUT_SNMP 14 #define NF10_IPV4_NEXT_HOP 15 #define NF10_SRC_AS 16 #define NF10_DST_AS 17 /* ... */ #define NF10_LAST_SWITCHED 21 #define NF10_FIRST_SWITCHED 22 /* ... */ #define NF10_IPV6_SRC_ADDR 27 #define NF10_IPV6_DST_ADDR 28 #define NF10_IPV6_SRC_MASK 29 #define NF10_IPV6_DST_MASK 30 /* ... */ #define NF10_ENGINE_TYPE 38 #define NF10_ENGINE_ID 39 /* ... */ #define NF10_IPV6_NEXT_HOP 62 // copy & paste from store.h /* * Optional flow fields, specify what is stored for the flow * NB - the flow records appear in this order on disk */ #define STORE_FIELD_TAG (1U) #define STORE_FIELD_RECV_TIME (1U << 1) #define STORE_FIELD_PROTO_FLAGS_TOS (1U << 2) #define STORE_FIELD_AGENT_ADDR4 (1U << 3) #define STORE_FIELD_AGENT_ADDR6 (1U << 4) #define STORE_FIELD_SRC_ADDR4 (1U << 5) #define STORE_FIELD_SRC_ADDR6 (1U << 6) #define STORE_FIELD_DST_ADDR4 (1U << 7) #define STORE_FIELD_DST_ADDR6 (1U << 8) #define STORE_FIELD_GATEWAY_ADDR4 (1U << 9) #define STORE_FIELD_GATEWAY_ADDR6 (1U << 10) #define STORE_FIELD_SRCDST_PORT (1U << 11) #define STORE_FIELD_PACKETS (1U << 12) #define STORE_FIELD_OCTETS (1U << 13) #define STORE_FIELD_IF_INDICES (1U << 14) #define STORE_FIELD_AGENT_INFO (1U << 15) #define STORE_FIELD_FLOW_TIMES (1U << 16) #define STORE_FIELD_AS_INFO (1U << 17) #define STORE_FIELD_FLOW_ENGINE_INFO (1U << 18) #endif /* _NETFLOW_H */ fastnetmon-1.1.3+dfsg/src/netflow_plugin/netflow_collector.cpp000066400000000000000000001311631313534057500246770ustar00rootroot00000000000000/* netflow plugin body */ #include #include #include #include #include #include #include #include #include #include #include #include #include "../fast_library.h" #include "../ipfix_rfc.h" // log4cpp logging facility #include "log4cpp/Category.hh" #include "log4cpp/Appender.hh" #include "log4cpp/FileAppender.hh" #include "log4cpp/OstreamAppender.hh" #include "log4cpp/Layout.hh" #include "log4cpp/BasicLayout.hh" #include "log4cpp/PatternLayout.hh" #include "log4cpp/Priority.hh" #ifdef ENABLE_LUA_HOOKS #include #endif #ifdef ENABLE_LUA_HOOKS lua_State* netflow_lua_state = NULL; bool lua_hooks_enabled = false; std::string lua_hooks_path = "/usr/src/fastnetmon/src/netflow_hooks.lua"; #endif // Get it from main programm extern log4cpp::Category& logger; // Global configuration map extern std::map configuration_map; // Sampling rate for all netflow agents unsigned int sampling_rate = 1; std::string netflow_plugin_name = "netflow"; std::string netflow_plugin_log_prefix = netflow_plugin_name + ": "; // Divide packets and bytes counters from the flow by interval length for more 'smoother' data bool netflow_divide_counters_on_interval_length = false; ipfix_information_database ipfix_db_instance; #include "netflow_collector.h" #include "netflow.h" // If we wan't listen on IPv4 and IPv6 i nsame time we need listen multiple sockets. Not good, // right. // TODO: add per source uniq templates support process_packet_pointer netflow_process_func_ptr = NULL; typedef std::map template_storage_t; typedef std::map global_template_storage_t; global_template_storage_t global_netflow9_templates; global_template_storage_t global_netflow10_templates; /* Prototypes */ void add_peer_template(global_template_storage_t& table_for_add, u_int32_t source_id, u_int template_id, std::string client_addres_in_string_format, struct peer_nf9_template& field_template); int nf9_rec_to_flow(u_int record_type, u_int record_length, u_int8_t* data, simple_packet& packet, netflow9_template_records_map& template_records); struct peer_nf9_template* peer_find_template(global_template_storage_t& table_for_lookup, u_int32_t source_id, u_int template_id, std::string client_addres_in_string_format) { // We use source_id for distinguish multiple netflow agents with same IP std::string key = client_addres_in_string_format + "_" + convert_int_to_string(source_id); global_template_storage_t::iterator itr = table_for_lookup.find(key); if (itr == table_for_lookup.end()) { return NULL; } // Well, we find it! if (itr->second.count(template_id) > 0) { return &itr->second[template_id]; } else { return NULL; } } // Wrapper functions struct peer_nf9_template* peer_nf9_find_template(u_int32_t source_id, u_int template_id, std::string client_addres_in_string_format) { return peer_find_template(global_netflow9_templates, source_id, template_id, client_addres_in_string_format); } struct peer_nf9_template* peer_nf10_find_template(u_int32_t source_id, u_int template_id, std::string client_addres_in_string_format) { return peer_find_template(global_netflow10_templates, source_id, template_id, client_addres_in_string_format); } std::string print_peer_nf9_template(struct peer_nf9_template& field_template) { std::stringstream buffer; buffer << "template_id: " << field_template.template_id << "\n" << "num records: " << field_template.num_records << "\n" << "total len: " << field_template.total_len << "\n"; for (netflow9_template_records_map::iterator itr = field_template.records.begin(); itr != field_template.records.end(); ++itr) { buffer << "Records\n"; unsigned int length_from_database = ipfix_db_instance.get_length_by_id(itr->type); buffer << "type: " << itr->type << "\n"; buffer << "len: " << itr->len << "\n"; buffer << "name from database: " << ipfix_db_instance.get_name_by_id(itr->type) << "\n"; buffer << "length from database: " << length_from_database << "\n"; if (length_from_database != itr->len) { buffer << "ATTENTION!!!! Length from database is not equal to length from received " "from the device\n"; } buffer << "\n"; } return buffer.str(); } struct NF10_OPTIONS_HEADER_COMMON { u_int16_t flowset_id; u_int16_t length; }; struct NF10_OPTIONS_HEADER { u_int16_t template_id; u_int16_t field_count; u_int16_t scope_field_count; }; // https://tools.ietf.org/html/rfc5101#page-18 int process_netflow_v10_options_template(u_int8_t* pkt, size_t len, u_int32_t source_id) { struct NF10_OPTIONS_HEADER_COMMON* options_template_header = (struct NF10_OPTIONS_HEADER_COMMON*)pkt; if (len < sizeof(*options_template_header)) { logger << log4cpp::Priority::ERROR << "Short netflow ipfix options template header"; return 1; } if (ntohs(options_template_header->flowset_id) != NF10_OPTIONS_FLOWSET_ID) { logger << log4cpp::Priority::ERROR << "Function process_netflow_v10_options_template " "expects only NF10_OPTIONS_FLOWSET_ID but got " "another id: " << ntohs(options_template_header->flowset_id); return 1; } struct NF10_OPTIONS_HEADER* options_nested_header = (struct NF10_OPTIONS_HEADER*)(pkt + sizeof(struct NF10_OPTIONS_HEADER_COMMON*)); // Yes, I should convert it to host byter order but it broke it! // WTF?? u_int16_t template_id = options_nested_header->template_id; if (template_id <= 255) { logger << log4cpp::Priority::ERROR << "Template ID for options template should be bigger than 255"; return 1; } u_int16_t field_count = ntohs(options_nested_header->field_count); u_int16_t scope_field_count = ntohs(options_nested_header->scope_field_count); logger << log4cpp::Priority::INFO << "Options template id: " << template_id << " field_count: " << field_count << " scope_field_count: " << scope_field_count; return 0; } int process_netflow_v10_template(u_int8_t* pkt, size_t len, u_int32_t source_id, std::string client_addres_in_string_format) { struct NF10_FLOWSET_HEADER_COMMON* template_header = (struct NF10_FLOWSET_HEADER_COMMON*)pkt; // We use same struct as netflow v9 because netflow v9 and v10 (ipfix) is compatible struct peer_nf9_template field_template; if (len < sizeof(*template_header)) { logger << log4cpp::Priority::ERROR << "Short netflow ipfix flowset template header"; return 1; } if (ntohs(template_header->flowset_id) != NF10_TEMPLATE_FLOWSET_ID) { logger << log4cpp::Priority::ERROR << "Function process_netflow_v10_template expects only NF10_TEMPLATE_FLOWSET_ID but " "got another id: " << ntohs(template_header->flowset_id); return 1; } for (u_int offset = sizeof(*template_header); offset < len;) { struct NF10_TEMPLATE_FLOWSET_HEADER* tmplh = (struct NF10_TEMPLATE_FLOWSET_HEADER*)(pkt + offset); u_int template_id = ntohs(tmplh->template_id); u_int count = ntohs(tmplh->count); offset += sizeof(*tmplh); netflow9_template_records_map template_records_map; u_int total_size = 0; for (u_int i = 0; i < count; i++) { if (offset >= len) { logger << log4cpp::Priority::ERROR << "short netflow v.10 flowset template"; return 1; } struct NF10_TEMPLATE_FLOWSET_RECORD* tmplr = (struct NF10_TEMPLATE_FLOWSET_RECORD*)(pkt + offset); u_int record_type = ntohs(tmplr->type); u_int record_length = ntohs(tmplr->length); struct peer_nf9_record current_record; current_record.type = record_type; current_record.len = record_length; template_records_map.push_back(current_record); offset += sizeof(*tmplr); if (record_type & NF10_ENTERPRISE) { offset += sizeof(u_int32_t); /* XXX -- ? */ } total_size += record_length; // add check: if (total_size > peers->max_template_len) } field_template.num_records = count; field_template.total_len = total_size; field_template.records = template_records_map; add_peer_template(global_netflow10_templates, source_id, template_id, client_addres_in_string_format, field_template); } return 0; } int process_netflow_v9_template(u_int8_t* pkt, size_t len, u_int32_t source_id, std::string client_addres_in_string_format) { struct NF9_FLOWSET_HEADER_COMMON* template_header = (struct NF9_FLOWSET_HEADER_COMMON*)pkt; struct peer_nf9_template field_template; if (len < sizeof(*template_header)) { logger << log4cpp::Priority::ERROR << "Short netflow v9 flowset template header"; return 1; } if (ntohs(template_header->flowset_id) != NF9_TEMPLATE_FLOWSET_ID) { logger << log4cpp::Priority::ERROR << "Function process_netflow_v9_template expects only NF9_TEMPLATE_FLOWSET_ID but " "got another id: " << ntohs(template_header->flowset_id); return 1; } for (u_int offset = sizeof(*template_header); offset < len;) { struct NF9_TEMPLATE_FLOWSET_HEADER* tmplh = (struct NF9_TEMPLATE_FLOWSET_HEADER*)(pkt + offset); u_int template_id = ntohs(tmplh->template_id); u_int count = ntohs(tmplh->count); offset += sizeof(*tmplh); // logger<< log4cpp::Priority::INFO<<"Template template_id is:"<= len) { logger << log4cpp::Priority::ERROR << "short netflow v.9 flowset template"; return 1; } struct NF9_TEMPLATE_FLOWSET_RECORD* tmplr = (struct NF9_TEMPLATE_FLOWSET_RECORD*)(pkt + offset); u_int record_type = ntohs(tmplr->type); u_int record_length = ntohs(tmplr->length); struct peer_nf9_record current_record; current_record.type = record_type; current_record.len = record_length; template_records_map.push_back(current_record); // logger<< log4cpp::Priority::INFO<<"Learn new template type: "<type)<<" // length:"<length); offset += sizeof(*tmplr); total_size += record_length; // TODO: introduce nf9_check_rec_len } field_template.num_records = count; field_template.total_len = total_size; field_template.records = template_records_map; // Add/update template add_peer_template(global_netflow9_templates, source_id, template_id, client_addres_in_string_format, field_template); } return 0; } void add_peer_template(global_template_storage_t& table_for_add, u_int32_t source_id, u_int template_id, std::string client_addres_in_string_format, struct peer_nf9_template& field_template) { std::string key = client_addres_in_string_format + "_" + convert_int_to_string(source_id); // logger<< log4cpp::Priority::INFO<<"It's new option template "<second.count(template_id) > 0) { // logger<< log4cpp::Priority::INFO<<"We already have information about this template // with id:" // <second[template_id] = field_template; } else { // logger<< log4cpp::Priority::INFO<<"It's new option template "<second[template_id] = field_template; } } else { template_storage_t temp_template_storage; temp_template_storage[template_id] = field_template; table_for_add[key] = temp_template_storage; } return; } /* Copy an int (possibly shorter than the target) keeping their LSBs aligned */ #define BE_COPY(a) memcpy((u_char*)&a + (sizeof(a) - record_length), data, record_length); #define V9_FIELD(v9_field, store_field, flow_field) \ case v9_field: \ BE_COPY(packet.flow_field); \ break #define V9_FIELD_ADDR(v9_field, store_field, flow_field) \ case v9_field: \ memcpy(&packet.flow_field, data, record_length); \ break int nf9_rec_to_flow(u_int record_type, u_int record_length, u_int8_t* data, simple_packet& packet) { /* XXX: use a table-based interpreter */ switch (record_type) { V9_FIELD(NF9_IN_BYTES, OCTETS, length); V9_FIELD(NF9_IN_PACKETS, PACKETS, number_of_packets); V9_FIELD(NF9_IN_PROTOCOL, PROTO_FLAGS_TOS, protocol); V9_FIELD(NF9_TCP_FLAGS, PROTO_FLAGS_TOS, flags); V9_FIELD(NF9_L4_SRC_PORT, SRCDST_PORT, source_port); V9_FIELD(NF9_L4_DST_PORT, SRCDST_PORT, destination_port); case NF9_IPV4_SRC_ADDR: memcpy(&packet.src_ip, data, record_length); break; case NF9_IPV4_DST_ADDR: memcpy(&packet.dst_ip, data, record_length); break; case NF9_INPUT_SNMP: { // TODO: port number could be 4 byte (Juniper MX) and we should rewrite BE_COPY for correct handling uint16_t input_port = 0; if (record_length > sizeof(input_port)) { //logger << log4cpp::Priority::ERROR << "Received very big packet for NF9_INPUT_SNMP!"; //return 0; } else { BE_COPY(input_port); input_port = fast_ntoh(input_port); // logger << log4cpp::Priority::INFO << "NF9_INPUT_SNMP is: " << input_port; } } break; case NF9_OUTPUT_SNMP: { uint16_t output_port = 0; if (record_length > sizeof(output_port)) { //logger << log4cpp::Priority::ERROR << "Received very big packet for NF9_OUTPUT_SNMP!"; //return 0; } else { BE_COPY(output_port); output_port = fast_ntoh(output_port); // logger << log4cpp::Priority::INFO << "NF9_OUTPUT_SNMP is: " << output_port; } } break; //V9_FIELD_ADDR(NF9_IPV4_SRC_ADDR, SRC_ADDR4, src_ip); //V9_FIELD_ADDR(NF9_IPV4_DST_ADDR, DST_ADDR4, dst_ip); // Sampling rate // We use NULL as second argument because it's suelles for us // It did not help us because looks like sampling rate implemented with OPTIONS flowset // V9_FIELD(NF9_SAMPLING_INTERVAL, NULL, sample_ratio); // V9_FIELD(NF9_SRC_TOS, PROTO_FLAGS_TOS, pft.tos); // V9_FIELD(NF9_SRC_MASK, AS_INFO, asinf.src_mask); // V9_FIELD(NF9_INPUT_SNMP, IF_INDICES, ifndx.if_index_in); // V9_FIELD(NF9_DST_MASK, AS_INFO, asinf.dst_mask); // V9_FIELD(NF9_OUTPUT_SNMP, IF_INDICES, ifndx.if_index_out); // V9_FIELD(NF9_SRC_AS, AS_INFO, asinf.src_as); // V9_FIELD(NF9_DST_AS, AS_INFO, asinf.dst_as); // V9_FIELD(NF9_LAST_SWITCHED, FLOW_TIMES, ftimes.flow_finish); // V9_FIELD(NF9_FIRST_SWITCHED, FLOW_TIMES, ftimes.flow_start); // V9_FIELD(NF9_IPV6_SRC_MASK, AS_INFO, asinf.src_mask); // V9_FIELD(NF9_IPV6_DST_MASK, AS_INFO, asinf.dst_mask); // V9_FIELD(NF9_ENGINE_TYPE, FLOW_ENGINE_INFO, finf.engine_type); // V9_FIELD(NF9_ENGINE_ID, FLOW_ENGINE_INFO, finf.engine_id); // V9_FIELD_ADDR(NF9_IPV4_NEXT_HOP, GATEWAY_ADDR4, gateway_addr, 4, INET); // V9_FIELD_ADDR(NF9_IPV6_SRC_ADDR, SRC_ADDR6, src_addr, 6, INET6); // V9_FIELD_ADDR(NF9_IPV6_DST_ADDR, DST_ADDR6, dst_addr, 6, INET6); // V9_FIELD_ADDR(NF9_IPV6_NEXT_HOP, GATEWAY_ADDR6, gateway_addr, 6, INET6); //#undef V9_FIELD //#undef V9_FIELD_ADDR //#undef BE_COPY } return 0; } int nf10_rec_to_flow(u_int record_type, u_int record_length, u_int8_t* data, simple_packet& packet) { /* XXX: use a table-based interpreter */ switch (record_type) { V9_FIELD(NF10_IN_BYTES, OCTETS, length); V9_FIELD(NF10_IN_PACKETS, PACKETS, number_of_packets); V9_FIELD(NF10_IN_PROTOCOL, PROTO_FLAGS_TOS, protocol); V9_FIELD(NF10_TCP_FLAGS, PROTO_FLAGS_TOS, flags); V9_FIELD(NF10_L4_SRC_PORT, SRCDST_PORT, source_port); V9_FIELD(NF10_L4_DST_PORT, SRCDST_PORT, destination_port); V9_FIELD_ADDR(NF10_IPV4_SRC_ADDR, SRC_ADDR4, src_ip); V9_FIELD_ADDR(NF10_IPV4_DST_ADDR, DST_ADDR4, dst_ip); } return 0; } // We use maximum possible variable langth // But devices can send shoretr data to us typedef struct netflow_ipfix_struct { uint32_t sourceIPv4Address; uint32_t destinationIPv4Address; uint16_t sourceTransportPort; uint16_t destinationTransportPort; uint16_t tcpControlBits; uint8_t protocolIdentifier; uint64_t octetDeltaCount; uint64_t packetDeltaCount; } netflow_ipfix_struct; // We should rewrite nf9_flowset_to_store accroding to fixes here void nf10_flowset_to_store(u_int8_t* pkt, size_t len, struct NF10_HEADER* nf10_hdr, struct peer_nf9_template* field_template) { u_int offset = 0; if (len < field_template->total_len) { logger << log4cpp::Priority::ERROR << "Total len from template bigger than packet len"; return; } simple_packet packet; // We use shifted values and should process only zeroed values // because we are working with little and big endian data in same time packet.number_of_packets = 0; packet.ts.tv_sec = ntohl(nf10_hdr->time_sec); packet.sample_ratio = sampling_rate; netflow_ipfix_struct data_in_ipfix_format; memset(&data_in_ipfix_format, 0, sizeof(netflow_ipfix_struct)); for (netflow9_template_records_map::iterator iter = field_template->records.begin(); iter != field_template->records.end(); iter++) { u_int record_type = iter->type; u_int record_length = iter->len; nf10_rec_to_flow(record_type, record_length, pkt + offset, packet); // New code /* unsigned int field_id = record_type; std::string field_name = ipfix_db_instance.get_name_by_id(field_id); if (field_name == "octetDeltaCount") { unsigned int reference_field_length = sizeof(data_in_ipfix_format.octetDeltaCount); if (reference_field_length == record_length) { // We use standard copy memcpy(&data_in_ipfix_format.octetDeltaCount, pkt + offset, record_length); // Convert to host byte order data_in_ipfix_format.octetDeltaCount = fast_ntoh(data_in_ipfix_format.octetDeltaCount); } else if (record_length < reference_field_length) { logger<< log4cpp::Priority::ERROR<<"We can't copy data because magic memcpy is not implemented yet"; // We use copy memcpy for netfowrk byte order } else { // Holy cow! It's impossible! logger<< log4cpp::Priority::ERROR<<"We can't copy data because receiver data is bigger than our storage."; return; } logger<< log4cpp::Priority::INFO<<"We received packet size with new parser: "<total_len > len) // return 1; u_int offset = 0; simple_packet packet; // We use shifted values and should process only zeroed values // because we are working with little and big endian data in same time packet.number_of_packets = 0; packet.ts.tv_sec = ntohl(nf9_hdr->time_sec); packet.sample_ratio = sampling_rate; // We should iterate over all available template fields for (netflow9_template_records_map::iterator iter = template_records.begin(); iter != template_records.end(); iter++) { u_int record_type = iter->type; u_int record_length = iter->len; int nf9_rec_to_flow_result = nf9_rec_to_flow(record_type, record_length, pkt + offset, packet); // logger<< log4cpp::Priority::INFO<<"Read data with type: "<c.flowset_id); struct peer_nf9_template* flowset_template = peer_nf10_find_template(source_id, flowset_id, client_addres_in_string_format); if (flowset_template == NULL) { logger << log4cpp::Priority::INFO << "We don't have a template for flowset_id: " << flowset_id << " but it's not an error if this message disappears in 5-10 seconds. We need some " "time to learn it!"; return 1; } if (flowset_template->records.empty()) { logger << log4cpp::Priority::ERROR << "Blank records in template"; return 1; } u_int offset = sizeof(*dath); u_int num_flowsets = (len - offset) / flowset_template->total_len; if (num_flowsets == 0 || num_flowsets > 0x4000) { logger << log4cpp::Priority::ERROR << "Invalid number of data flowset, strange number of flows: " << num_flowsets; return 1; } for (u_int i = 0; i < num_flowsets; i++) { // process whole flowset nf10_flowset_to_store(pkt + offset, flowset_template->total_len, nf10_hdr, flowset_template); offset += flowset_template->total_len; } return 0; } int process_netflow_v9_data(u_int8_t* pkt, size_t len, struct NF9_HEADER* nf9_hdr, u_int32_t source_id, std::string client_addres_in_string_format) { struct NF9_DATA_FLOWSET_HEADER* dath = (struct NF9_DATA_FLOWSET_HEADER*)pkt; if (len < sizeof(*dath)) { logger << log4cpp::Priority::INFO << "Short netflow v9 data flowset header"; return 1; } u_int flowset_id = ntohs(dath->c.flowset_id); // logger<< log4cpp::Priority::INFO<<"We have data with flowset_id: "<records.empty()) { logger << log4cpp::Priority::ERROR << "Blank records in template"; return 1; } u_int offset = sizeof(*dath); u_int num_flowsets = (len - offset) / flowset_template->total_len; if (num_flowsets == 0 || num_flowsets > 0x4000) { logger << log4cpp::Priority::ERROR << "Invalid number of data flowsets, strange number of flows: " << num_flowsets; return 1; } for (u_int i = 0; i < num_flowsets; i++) { // process whole flowset nf9_flowset_to_store(pkt + offset, flowset_template->total_len, nf9_hdr, flowset_template->records); offset += flowset_template->total_len; } return 0; } void process_netflow_packet_v10(u_int8_t* packet, u_int len, std::string client_addres_in_string_format) { struct NF10_HEADER* nf10_hdr = (struct NF10_HEADER*)packet; struct NF10_FLOWSET_HEADER_COMMON* flowset; u_int32_t i, pktlen, flowset_id, flowset_len, flowset_flows; u_int32_t offset, source_id, total_flows; if (len < sizeof(*nf10_hdr)) { logger << log4cpp::Priority::ERROR << "Short netflow v10 header"; return; } /* v10 uses pkt length, not # of flows */ pktlen = ntohs(nf10_hdr->c.flows); source_id = ntohl(nf10_hdr->source_id); offset = sizeof(*nf10_hdr); total_flows = 0; for (i = 0;; i++) { if (offset >= len) { logger << log4cpp::Priority::ERROR << "We tried to read from address outside netflow packet"; return; } flowset = (struct NF10_FLOWSET_HEADER_COMMON*)(packet + offset); flowset_id = ntohs(flowset->flowset_id); flowset_len = ntohs(flowset->length); /* * Yes, this is a near duplicate of the short packet check * above, but this one validates the flowset length from in * the packet before we pass it to the flowset-specific * handlers below. */ if (offset + flowset_len > len) { logger << log4cpp::Priority::ERROR << "We tried to read from address outside netflow's packet flowset"; return; } switch (flowset_id) { case NF10_TEMPLATE_FLOWSET_ID: if (process_netflow_v10_template(packet + offset, flowset_len, source_id, client_addres_in_string_format) != 0) { logger << log4cpp::Priority::ERROR << "Function process_netflow_v10_template executed with errors"; break; } break; case NF10_OPTIONS_FLOWSET_ID: // process_netflow_v10_options_template(packet + offset, flowset_len, source_id); logger << log4cpp::Priority::INFO << "Received ipfix options flowset id, which is not supported"; /* Not implemented yet */ break; default: if (flowset_id < NF10_MIN_RECORD_FLOWSET_ID) { logger << log4cpp::Priority::ERROR << "Received unknown netflow v10 reserved flowset type " << flowset_id; break; } if (process_netflow_v10_data(packet + offset, flowset_len, nf10_hdr, source_id, client_addres_in_string_format) != 0) { // logger<< log4cpp::Priority::ERROR<<"Can't process function // process_netflow_v10_data correctly"; return; } break; } offset += flowset_len; if (offset == len) { break; } } } void process_netflow_packet_v9(u_int8_t* packet, u_int len, std::string client_addres_in_string_format) { // logger<< log4cpp::Priority::INFO<<"We get v9 netflow packet!"; struct NF9_HEADER* nf9_hdr = (struct NF9_HEADER*)packet; struct NF9_FLOWSET_HEADER_COMMON* flowset; u_int32_t count, flowset_id, flowset_len, flowset_flows; u_int32_t offset, source_id, total_flows; if (len < sizeof(*nf9_hdr)) { logger << log4cpp::Priority::ERROR << "Short netflow v9 header"; return; } count = ntohs(nf9_hdr->c.flows); source_id = ntohl(nf9_hdr->source_id); // logger<< log4cpp::Priority::INFO<<"Template source id: "<= len) { logger << log4cpp::Priority::ERROR << "We tried to read from address outside netflow packet"; return; } flowset = (struct NF9_FLOWSET_HEADER_COMMON*)(packet + offset); flowset_id = ntohs(flowset->flowset_id); flowset_len = ntohs(flowset->length); /* * Yes, this is a near duplicate of the short packet check * above, but this one validates the flowset length from in * the packet before we pass it to the flowset-specific * handlers below. */ if (offset + flowset_len > len) { logger << log4cpp::Priority::ERROR << "We tried to read from address outside netflow's packet flowset"; return; } switch (flowset_id) { case NF9_TEMPLATE_FLOWSET_ID: // logger<< log4cpp::Priority::INFO<<"We read template"; if (process_netflow_v9_template(packet + offset, flowset_len, source_id, client_addres_in_string_format) != 0) { logger << log4cpp::Priority::ERROR << "Function process_netflow_v9_template executed with errors"; break; } break; case NF9_OPTIONS_FLOWSET_ID: logger << log4cpp::Priority::INFO << "I received netflow v9 options flowset id but I haven't support for it"; /* Not implemented yet */ break; default: if (flowset_id < NF9_MIN_RECORD_FLOWSET_ID) { logger << log4cpp::Priority::ERROR << "Received unknown netflow v9 reserved flowset type " << flowset_id; break; } // logger<< log4cpp::Priority::INFO<<"We read data"; if (process_netflow_v9_data(packet + offset, flowset_len, nf9_hdr, source_id, client_addres_in_string_format) != 0) { // logger<< log4cpp::Priority::ERROR<<"Can't process function // process_netflow_v9_data correctly"; return; } break; } offset += flowset_len; if (offset == len) { break; } } } void process_netflow_packet_v5(u_int8_t* packet, u_int len, std::string client_addres_in_string_format) { // logger<< log4cpp::Priority::INFO<<"We get v5 netflow packet!"; struct NF5_HEADER* nf5_hdr = (struct NF5_HEADER*)packet; if (len < sizeof(*nf5_hdr)) { logger << log4cpp::Priority::ERROR << "Short netflow v5 packet " << len; return; } u_int nflows = ntohs(nf5_hdr->c.flows); if (nflows == 0 || nflows > NF5_MAXFLOWS) { logger << log4cpp::Priority::ERROR << "Invalid number of flows in netflow " << nflows; return; } uint16_t netflow5_sampling_ratio = fast_ntoh(nf5_hdr->sampling_rate); // In first two bits we store sampling type. // We are not interested in it and should zeroify it for getting correct value of sampling rate clear_bit_value(netflow5_sampling_ratio, 15); clear_bit_value(netflow5_sampling_ratio, 16); // Sampling not enabled on device if (netflow5_sampling_ratio == 0) { netflow5_sampling_ratio = 1; } for (u_int i = 0; i < nflows; i++) { size_t offset = NF5_PACKET_SIZE(i); struct NF5_FLOW* nf5_flow = (struct NF5_FLOW*)(packet + offset); /* Check packet bounds */ if (offset + sizeof(struct NF5_FLOW) > len) { logger << log4cpp::Priority::ERROR << "Error! You will try to read outside the packet"; } /* Decode to host encoding */ // TODO: move to separate function nf5_flow->flow_octets = fast_ntoh(nf5_flow->flow_octets); nf5_flow->flow_packets = fast_ntoh(nf5_flow->flow_packets); nf5_flow->if_index_in = fast_ntoh(nf5_flow->if_index_in); nf5_flow->if_index_out = fast_ntoh(nf5_flow->if_index_out); // convert netflow to simple packet form simple_packet current_packet; current_packet.src_ip = nf5_flow->src_ip; current_packet.dst_ip = nf5_flow->dest_ip; current_packet.ts.tv_sec = ntohl(nf5_hdr->time_sec); current_packet.ts.tv_usec = ntohl(nf5_hdr->time_nanosec); current_packet.flags = 0; current_packet.source_port = 0; current_packet.destination_port = 0; // TODO: we should pass data about "flow" structure of this data current_packet.length = nf5_flow->flow_octets; current_packet.number_of_packets = nf5_flow->flow_packets; if (netflow_divide_counters_on_interval_length) { // This interval in milliseconds, convert it to seconds int64_t interval_length = (fast_ntoh(nf5_flow->flow_finish) - fast_ntoh(nf5_flow->flow_start)) / 1000; /* if (interval_length > 0) { logger << log4cpp::Priority::INFO << "NetFlow v5 from: " << client_addres_in_string_format << " start: " << fast_ntoh(nf5_flow->flow_start) << " finish: " << fast_ntoh(nf5_flow->flow_finish) << " interval length:" << interval_length << "\n"; } */ if (interval_length == 0) { // it's OK } else if (interval_length < 0) { // it's internal error logger << log4cpp::Priority::ERROR << "We got negative interval length from netflow agent, something goes wrong!"; } else { // OK, let's divide // We will get integer result for this operation current_packet.length = current_packet.length / interval_length; current_packet.number_of_packets = current_packet.number_of_packets / interval_length; } } // TODO: use sampling data from packet, disable customization here // Wireshark dump approves this idea current_packet.sample_ratio = netflow5_sampling_ratio; current_packet.source_port = fast_ntoh(nf5_flow->src_port); current_packet.destination_port = fast_ntoh(nf5_flow->dest_port); // We do not support IPv6 in NetFlow v5 at all current_packet.ip_protocol_version = 4; switch (nf5_flow->protocol) { case 1: { // ICMP current_packet.protocol = IPPROTO_ICMP; } break; case 6: { // TCP current_packet.protocol = IPPROTO_TCP; // TODO: flags can be in another format! current_packet.flags = nf5_flow->tcp_flags; } break; case 17: { // UDP current_packet.protocol = IPPROTO_UDP; } break; } #ifdef ENABLE_LUA_HOOKS if (lua_hooks_enabled) { // This code could be used only for tests with pcap_reader //if (lua_state == NULL) { // init_lua_jit(); //} if (call_lua_function("process_netflow", netflow_lua_state, client_addres_in_string_format, (void*)nf5_flow)) { // We will process this packet } else { logger << log4cpp::Priority::INFO << "We will drop this packets because LUA script decided to do it"; return; } } #endif // Call processing function for every flow in packet netflow_process_func_ptr(current_packet); } } void process_netflow_packet(u_int8_t* packet, u_int len, std::string client_addres_in_string_format) { struct NF_HEADER_COMMON* hdr = (struct NF_HEADER_COMMON*)packet; switch (ntohs(hdr->version)) { case 5: process_netflow_packet_v5(packet, len, client_addres_in_string_format); break; case 9: process_netflow_packet_v9(packet, len, client_addres_in_string_format); break; case 10: process_netflow_packet_v10(packet, len, client_addres_in_string_format); break; default: logger << log4cpp::Priority::ERROR << "We do not support this version of netflow " << ntohs(hdr->version); break; } } void start_netflow_collector(std::string netflow_host, unsigned int netflow_port); // #include void start_netflow_collection(process_packet_pointer func_ptr) { logger << log4cpp::Priority::INFO << "netflow plugin started"; #ifdef ENABLE_LUA_HOOKS if (lua_hooks_enabled) { netflow_lua_state = init_lua_jit(lua_hooks_path); if (netflow_lua_state == NULL) { lua_hooks_enabled = false; } } #endif // prctl(PR_SET_NAME,"fastnetmon_netflow", 0, 0, 0); netflow_process_func_ptr = func_ptr; // By default we listen on IPv4 std::string netflow_host = "0.0.0.0"; std::string netflow_ports = ""; if (configuration_map.count("netflow_port") != 0) { netflow_ports = configuration_map["netflow_port"]; } if (configuration_map.count("netflow_host") != 0) { netflow_host = configuration_map["netflow_host"]; } if (configuration_map.count("netflow_sampling_ratio") != 0) { sampling_rate = convert_string_to_integer(configuration_map["netflow_sampling_ratio"]); logger << log4cpp::Priority::INFO << "Using custom sampling ratio for netflow: " << sampling_rate; } if (configuration_map.count("netflow_divide_counters_on_interval_length") != 0) { netflow_divide_counters_on_interval_length = configuration_map["netflow_divide_counters_on_interval_length"] == "on" ? true : false; } #ifdef ENABLE_LUA_HOOKS if (configuration_map.count("netflow_lua_hooks_path") != 0) { lua_hooks_path = configuration_map["netflow_lua_hooks_path"]; lua_hooks_enabled = true; } #endif boost::thread_group netflow_collector_threads; std::vector ports_for_listen; boost::split(ports_for_listen, netflow_ports, boost::is_any_of(","), boost::token_compress_on); logger << log4cpp::Priority::INFO << netflow_plugin_log_prefix << "We will listen on " << ports_for_listen.size() << " ports"; for (std::vector::iterator port = ports_for_listen.begin(); port != ports_for_listen.end(); ++port) { unsigned int netflow_port = convert_string_to_integer(*port); if (netflow_port == 0) { netflow_port = 2055; } netflow_collector_threads.add_thread( new boost::thread(start_netflow_collector, netflow_host, convert_string_to_integer(*port) )); } netflow_collector_threads.join_all(); } void start_netflow_collector(std::string netflow_host, unsigned int netflow_port) { logger << log4cpp::Priority::INFO << "netflow plugin will listen on " << netflow_host << ":" << netflow_port << " udp port"; unsigned int udp_buffer_size = 65536; char udp_buffer[udp_buffer_size]; struct addrinfo hints; memset(&hints, 0, sizeof hints); // Could be AF_INET6 or AF_INET hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_DGRAM; // This flag will generate wildcard IP address if we not specified certain IP address for // binding hints.ai_flags = AI_PASSIVE | AI_NUMERICHOST; struct addrinfo* servinfo = NULL; const char* address_for_binding = NULL; if (!netflow_host.empty()) { address_for_binding = netflow_host.c_str(); } char port_as_string[16]; sprintf(port_as_string, "%d", netflow_port); int getaddrinfo_result = getaddrinfo(address_for_binding, port_as_string, &hints, &servinfo); if (getaddrinfo_result != 0) { logger << log4cpp::Priority::ERROR << "Netflow getaddrinfo function failed with code: " << getaddrinfo_result << " please check netflow_host"; exit(1); } int sockfd = socket(servinfo->ai_family, servinfo->ai_socktype, servinfo->ai_protocol); int bind_result = bind(sockfd, servinfo->ai_addr, servinfo->ai_addrlen); if (bind_result) { logger << log4cpp::Priority::ERROR << "Can't listen on port: " << netflow_port << " on host " << netflow_host << " errno:" << errno << " error: " << strerror(errno); return; } struct sockaddr_in6 peer; memset(&peer, 0, sizeof(peer)); /* We should specify timeout there for correct toolkit shutdown */ /* Because otherwise recvfrom will stay in blocked mode forever */ struct timeval tv; tv.tv_sec = 5; /* X Secs Timeout */ tv.tv_usec = 0; // Not init'ing this can cause strange errors setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof(struct timeval)); while (true) { // This approach provide ability to store both IPv4 and IPv6 client's addresses struct sockaddr_storage client_address; // It's MUST memset(&client_address, 0, sizeof(struct sockaddr_storage)); socklen_t address_len = sizeof(struct sockaddr_storage); int received_bytes = recvfrom(sockfd, udp_buffer, udp_buffer_size, 0, (struct sockaddr*)&client_address, &address_len); if (received_bytes > 0) { // Pass host and port as numbers without any conversion int getnameinfo_flags = NI_NUMERICSERV | NI_NUMERICHOST; char host[NI_MAXHOST]; char service[NI_MAXSERV]; int result = getnameinfo((struct sockaddr*)&client_address, address_len, host, NI_MAXHOST, service, NI_MAXSERV, getnameinfo_flags); // We sill store client's IP address as string for allowing IPv4 and IPv6 processing in // same time std::string client_addres_in_string_format = std::string(host); // logger<< log4cpp::Priority::INFO<<"We receive packet from IP: // "< #include #include "../fast_library.h" // For support uint32_t, uint16_t #include // For config map operations #include #include #include #include #include #define NETMAP_WITH_LIBS // Disable debug messages from Netmap #define NETMAP_NO_DEBUG #include #include #if defined(__FreeBSD__) // On FreeBSD function pthread_attr_setaffinity_np declared here #include // Also we have different type name for cpu set's store typedef cpuset_t cpu_set_t; #endif #include "../fastnetmon_packet_parser.h" // For pooling operations #include // For support: IPPROTO_TCP, IPPROTO_ICMP, IPPROTO_UDP #include #include #include #include "netmap_collector.h" // By default we read packet size from link layer // But in case of Juniper we could crop first X bytes from packet: // maximum-packet-length 110; // And this option become mandatory if we want correct bps speed in toolkit bool netmap_read_packet_length_from_ip_header = false; uint32_t netmap_sampling_ratio = 1; /* prototypes */ void netmap_thread(struct nm_desc* netmap_descriptor, int netmap_thread); void consume_pkt(u_char* buffer, int len, int thread_number); // Get log4cpp logger from main programm extern log4cpp::Category& logger; // Pass unparsed packets number to main programm extern uint64_t total_unparsed_packets; // Global configuration map extern std::map configuration_map; u_int num_cpus = 0; // This variable name should be uniq for every plugin! process_packet_pointer netmap_process_func_ptr = NULL; bool execute_strict_cpu_affinity = true; int receive_packets(struct netmap_ring* ring, int thread_number) { u_int cur, rx, n; cur = ring->cur; n = nm_ring_space(ring); for (rx = 0; rx < n; rx++) { struct netmap_slot* slot = &ring->slot[cur]; char* p = NETMAP_BUF(ring, slot->buf_idx); // process data consume_pkt((u_char*)p, slot->len, thread_number); cur = nm_ring_next(ring, cur); } ring->head = ring->cur = cur; return (rx); } bool parse_raw_packet_to_simple_packet(u_char* buffer, int len, simple_packet& packet) { struct pfring_pkthdr packet_header; memset(&packet_header, 0, sizeof(packet_header)); packet_header.len = len; packet_header.caplen = len; // We do not calculate timestamps because timestamping is very CPU intensive operation: // https://github.com/ntop/PF_RING/issues/9 u_int8_t timestamp = 0; u_int8_t add_hash = 0; fastnetmon_parse_pkt((u_char*)buffer, &packet_header, 4, timestamp, add_hash); // char print_buffer[512]; // fastnetmon_print_parsed_pkt(print_buffer, 512, (u_char*)buffer, &packet_header); // logger.info("%s", print_buffer); if (packet_header.extended_hdr.parsed_pkt.ip_version != 4 && packet_header.extended_hdr.parsed_pkt.ip_version != 6) { return false; } // We need this for deep packet inspection packet.packet_payload_length = len; packet.packet_payload_pointer = (void*)buffer; packet.ip_protocol_version = packet_header.extended_hdr.parsed_pkt.ip_version; if (packet.ip_protocol_version == 4) { // IPv4 /* PF_RING stores data in host byte order but we use network byte order */ packet.src_ip = htonl(packet_header.extended_hdr.parsed_pkt.ip_src.v4); packet.dst_ip = htonl(packet_header.extended_hdr.parsed_pkt.ip_dst.v4); } else { // IPv6 memcpy(packet.src_ipv6.s6_addr, packet_header.extended_hdr.parsed_pkt.ip_src.v6.s6_addr, 16); memcpy(packet.dst_ipv6.s6_addr, packet_header.extended_hdr.parsed_pkt.ip_dst.v6.s6_addr, 16); } packet.source_port = packet_header.extended_hdr.parsed_pkt.l4_src_port; packet.destination_port = packet_header.extended_hdr.parsed_pkt.l4_dst_port; if (netmap_read_packet_length_from_ip_header) { packet.length = packet_header.extended_hdr.parsed_pkt.ip_total_size; } else { packet.length = packet_header.len; } packet.protocol = packet_header.extended_hdr.parsed_pkt.l3_proto; packet.ts = packet_header.ts; packet.ip_fragmented = packet_header.extended_hdr.parsed_pkt.ip_fragmented; packet.ttl = packet_header.extended_hdr.parsed_pkt.ip_ttl; // Copy flags from PF_RING header to our pseudo header if (packet.protocol == IPPROTO_TCP) { packet.flags = packet_header.extended_hdr.parsed_pkt.tcp.flags; } else { packet.flags = 0; } return true; } void consume_pkt(u_char* buffer, int len, int thread_number) { // We should fill this structure for passing to FastNetMon simple_packet packet; packet.sample_ratio = netmap_sampling_ratio; if (!parse_raw_packet_to_simple_packet(buffer, len, packet)) { total_unparsed_packets++; return; } netmap_process_func_ptr(packet); } void receiver(std::string interface_for_listening) { struct nm_desc* netmap_descriptor; struct nmreq base_nmd; bzero(&base_nmd, sizeof(base_nmd)); // Magic from pkt-gen.c base_nmd.nr_tx_rings = base_nmd.nr_rx_rings = 0; base_nmd.nr_tx_slots = base_nmd.nr_rx_slots = 0; std::string interface = ""; std::string system_interface_name = ""; // If we haven't netmap: prefix in interface name we will append it if (interface_for_listening.find("netmap:") == std::string::npos) { system_interface_name = interface_for_listening; interface = "netmap:" + interface_for_listening; } else { // We should skip netmap prefix system_interface_name = boost::replace_all_copy(interface_for_listening, "netmap:", ""); interface = interface_for_listening; } #ifdef __linux__ manage_interface_promisc_mode(system_interface_name, true); logger.warn("Please disable all types of offload for this NIC manually: ethtool -K %s gro off gso off tso off lro off", system_interface_name.c_str()); #endif netmap_descriptor = nm_open(interface.c_str(), &base_nmd, 0, NULL); if (netmap_descriptor == NULL) { logger.error("Can't open netmap device %s", interface.c_str()); exit(1); return; } logger.info("Mapped %dKB memory at %p", netmap_descriptor->req.nr_memsize >> 10, netmap_descriptor->mem); logger.info("We have %d tx and %d rx rings", netmap_descriptor->req.nr_tx_rings, netmap_descriptor->req.nr_rx_rings); if (num_cpus > netmap_descriptor->req.nr_rx_rings) { num_cpus = netmap_descriptor->req.nr_rx_rings; logger.info("We have number of CPUs bigger than number of NIC RX queues. Set number of " "CPU's to number of threads"); } /* protocol stack and may cause a reset of the card, which in turn may take some time for the PHY to reconfigure. We do the open here to have time to reset. */ int wait_link = 2; logger.info("Wait %d seconds for NIC reset", wait_link); sleep(wait_link); boost::thread_group packet_receiver_thread_group; for (int i = 0; i < num_cpus; i++) { struct nm_desc nmd = *netmap_descriptor; // This operation is VERY important! nmd.self = &nmd; uint64_t nmd_flags = 0; if (nmd.req.nr_flags != NR_REG_ALL_NIC) { logger.error("Ooops, main descriptor should be with NR_REG_ALL_NIC flag"); } nmd.req.nr_flags = NR_REG_ONE_NIC; nmd.req.nr_ringid = i; /* Only touch one of the rings (rx is already ok) */ nmd_flags |= NETMAP_NO_TX_POLL; struct nm_desc* new_nmd = nm_open(interface.c_str(), NULL, nmd_flags | NM_OPEN_IFNAME | NM_OPEN_NO_MMAP, &nmd); if (new_nmd == NULL) { logger.error("Can't open netmap descriptor for netmap per hardware queue thread"); exit(1); } logger.info("My first ring is %d and last ring id is %d I'm thread %d", new_nmd->first_rx_ring, new_nmd->last_rx_ring, i); /* logger<< log4cpp::Priority::INFO<< "We are using Boost " << BOOST_VERSION / 100000 << "." // major version << BOOST_VERSION / 100 % 1000 << "." // minior version << BOOST_VERSION % 100; */ logger.info("Start new netmap thread %d", i); // Well, we have thread attributes from Boost 1.50 #if defined(BOOST_THREAD_PLATFORM_PTHREAD) && BOOST_VERSION / 100 % 1000 >= 50 && !defined(__APPLE__) /* Bind to certain core */ boost::thread::attributes thread_attrs; if (execute_strict_cpu_affinity) { cpu_set_t current_cpu_set; int cpu_to_bind = i % num_cpus; CPU_ZERO(¤t_cpu_set); // We count cpus from zero CPU_SET(cpu_to_bind, ¤t_cpu_set); logger.info("I will bind this thread to logical CPU: %d", cpu_to_bind); int set_affinity_result = pthread_attr_setaffinity_np(thread_attrs.native_handle(), sizeof(cpu_set_t), ¤t_cpu_set); if (set_affinity_result != 0) { logger.error("Can't specify CPU affinity for netmap thread"); } } // Start thread and pass netmap descriptor to it packet_receiver_thread_group.add_thread( new boost::thread(thread_attrs, boost::bind(netmap_thread, new_nmd, i))); #else logger.error("Sorry but CPU affinity did not supported for your platform"); packet_receiver_thread_group.add_thread(new boost::thread(netmap_thread, new_nmd, i)); #endif } // Wait all threads for completion packet_receiver_thread_group.join_all(); } void netmap_thread(struct nm_desc* netmap_descriptor, int thread_number) { struct nm_pkthdr h; u_char* buf; struct pollfd fds; fds.fd = netmap_descriptor->fd; // NETMAP_FD(netmap_descriptor); fds.events = POLLIN; struct netmap_ring* rxring = NULL; struct netmap_if* nifp = netmap_descriptor->nifp; // printf("Reading from fd %d thread id: %d", netmap_descriptor->fd, thread_number); for (;;) { // We will wait 1000 microseconds for retry, for infinite timeout please use -1 int poll_result = poll(&fds, 1, 1000); if (poll_result == 0) { // printf("poll return 0 return code"); continue; } if (poll_result == -1) { logger.error("Netmap plugin: poll failed with return code -1"); } for (int i = netmap_descriptor->first_rx_ring; i <= netmap_descriptor->last_rx_ring; i++) { // printf("Check ring %d from thread %d", i, thread_number); rxring = NETMAP_RXRING(nifp, i); if (nm_ring_empty(rxring)) { continue; } receive_packets(rxring, thread_number); } // TODO: this code could add performance degradation // Add interruption point for correct toolkit shutdown // boost::this_thread::interruption_point(); } // nm_close(netmap_descriptor); } void start_netmap_collection(process_packet_pointer func_ptr) { logger << log4cpp::Priority::INFO << "Netmap plugin started"; netmap_process_func_ptr = func_ptr; num_cpus = sysconf(_SC_NPROCESSORS_ONLN); logger.info("We have %d cpus", num_cpus); std::string interfaces_list = ""; if (configuration_map.count("interfaces") != 0) { interfaces_list = configuration_map["interfaces"]; } if (configuration_map.count("netmap_sampling_ratio") != 0) { netmap_sampling_ratio = convert_string_to_integer(configuration_map["netmap_sampling_ratio"]); } if (configuration_map.count("netmap_read_packet_length_from_ip_header") != 0) { netmap_read_packet_length_from_ip_header = configuration_map["netmap_read_packet_length_from_ip_header"] == "on"; } std::vector interfaces_for_listen; boost::split(interfaces_for_listen, interfaces_list, boost::is_any_of(","), boost::token_compress_on); logger << log4cpp::Priority::INFO << "netmap will listen on " << interfaces_for_listen.size() << " interfaces"; // Thread group for all "master" processes boost::thread_group netmap_main_threads; for (std::vector::iterator interface = interfaces_for_listen.begin(); interface != interfaces_for_listen.end(); ++interface) { logger << log4cpp::Priority::INFO << "netmap will sniff interface: " << *interface; netmap_main_threads.add_thread( new boost::thread(receiver, *interface) ); } netmap_main_threads.join_all(); } fastnetmon-1.1.3+dfsg/src/netmap_plugin/netmap_collector.h000066400000000000000000000002321313534057500237500ustar00rootroot00000000000000#ifndef NETMAP_PLUGIN_H #define NETMAP_PLUGIN_H #include "../fastnetmon_types.h" void start_netmap_collection(process_packet_pointer func_ptr); #endif fastnetmon-1.1.3+dfsg/src/netmap_plugin/netmap_includes/000077500000000000000000000000001313534057500234225ustar00rootroot00000000000000fastnetmon-1.1.3+dfsg/src/netmap_plugin/netmap_includes/net/000077500000000000000000000000001313534057500242105ustar00rootroot00000000000000fastnetmon-1.1.3+dfsg/src/netmap_plugin/netmap_includes/net/netmap.h000066400000000000000000000500671313534057500256550ustar00rootroot00000000000000/* * Copyright (C) 2011-2014 Matteo Landi, Luigi Rizzo. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``S IS''AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * $FreeBSD: head/sys/net/netmap.h 251139 2013-05-30 14:07:14Z luigi $ * * Definitions of constants and the structures used by the netmap * framework, for the part visible to both kernel and userspace. * Detailed info on netmap is available with "man netmap" or at * * http://info.iet.unipi.it/~luigi/netmap/ * * This API is also used to communicate with the VALE software switch */ #ifndef _NET_NETMAP_H_ #define _NET_NETMAP_H_ #define NETMAP_API 11 /* current API version */ #define NETMAP_MIN_API 11 /* min and max versions accepted */ #define NETMAP_MAX_API 15 /* * Some fields should be cache-aligned to reduce contention. * The alignment is architecture and OS dependent, but rather than * digging into OS headers to find the exact value we use an estimate * that should cover most architectures. */ #define NM_CACHE_ALIGN 128 /* * --- Netmap data structures --- * * The userspace data structures used by netmap are shown below. * They are allocated by the kernel and mmap()ed by userspace threads. * Pointers are implemented as memory offsets or indexes, * so that they can be easily dereferenced in kernel and userspace. KERNEL (opaque, obviously) ==================================================================== | USERSPACE | struct netmap_ring +---->+---------------+ / | head,cur,tail | struct netmap_if (nifp, 1 per fd) / | buf_ofs | +---------------+ / | other fields | | ni_tx_rings | / +===============+ | ni_rx_rings | / | buf_idx, len | slot[0] | | / | flags, ptr | | | / +---------------+ +===============+ / | buf_idx, len | slot[1] | txring_ofs[0] | (rel.to nifp)--' | flags, ptr | | txring_ofs[1] | +---------------+ (tx+1 entries) (num_slots entries) | txring_ofs[t] | | buf_idx, len | slot[n-1] +---------------+ | flags, ptr | | rxring_ofs[0] | +---------------+ | rxring_ofs[1] | (rx+1 entries) | rxring_ofs[r] | +---------------+ * For each "interface" (NIC, host stack, PIPE, VALE switch port) bound to * a file descriptor, the mmap()ed region contains a (logically readonly) * struct netmap_if pointing to struct netmap_ring's. * * There is one netmap_ring per physical NIC ring, plus one tx/rx ring * pair attached to the host stack (this pair is unused for non-NIC ports). * * All physical/host stack ports share the same memory region, * so that zero-copy can be implemented between them. * VALE switch ports instead have separate memory regions. * * The netmap_ring is the userspace-visible replica of the NIC ring. * Each slot has the index of a buffer (MTU-sized and residing in the * mmapped region), its length and some flags. An extra 64-bit pointer * is provided for user-supplied buffers in the tx path. * * In user space, the buffer address is computed as * (char *)ring + buf_ofs + index * NETMAP_BUF_SIZE * * Added in NETMAP_API 11: * * + NIOCREGIF can request the allocation of extra spare buffers from * the same memory pool. The desired number of buffers must be in * nr_arg3. The ioctl may return fewer buffers, depending on memory * availability. nr_arg3 will return the actual value, and, once * mapped, nifp->ni_bufs_head will be the index of the first buffer. * * The buffers are linked to each other using the first uint32_t * as the index. On close, ni_bufs_head must point to the list of * buffers to be released. * * + NIOCREGIF can request space for extra rings (and buffers) * allocated in the same memory space. The number of extra rings * is in nr_arg1, and is advisory. This is a no-op on NICs where * the size of the memory space is fixed. * * + NIOCREGIF can attach to PIPE rings sharing the same memory * space with a parent device. The ifname indicates the parent device, * which must already exist. Flags in nr_flags indicate if we want to * bind the master or slave side, the index (from nr_ringid) * is just a cookie and does not need to be sequential. * * + NIOCREGIF can also attach to 'monitor' rings that replicate * the content of specific rings, also from the same memory space. * * Extra flags in nr_flags support the above functions. * Application libraries may use the following naming scheme: * netmap:foo all NIC ring pairs * netmap:foo^ only host ring pair * netmap:foo+ all NIC ring + host ring pairs * netmap:foo-k the k-th NIC ring pair * netmap:foo{k PIPE ring pair k, master side * netmap:foo}k PIPE ring pair k, slave side */ /* * struct netmap_slot is a buffer descriptor */ struct netmap_slot { uint32_t buf_idx; /* buffer index */ uint16_t len; /* length for this slot */ uint16_t flags; /* buf changed, etc. */ uint64_t ptr; /* pointer for indirect buffers */ }; /* * The following flags control how the slot is used */ #define NS_BUF_CHANGED 0x0001 /* buf_idx changed */ /* * must be set whenever buf_idx is changed (as it might be * necessary to recompute the physical address and mapping) * * It is also set by the kernel whenever the buf_idx is * changed internally (e.g., by pipes). Applications may * use this information to know when they can reuse the * contents of previously prepared buffers. */ #define NS_REPORT 0x0002 /* ask the hardware to report results */ /* * Request notification when slot is used by the hardware. * Normally transmit completions are handled lazily and * may be unreported. This flag lets us know when a slot * has been sent (e.g. to terminate the sender). */ #define NS_FORWARD 0x0004 /* pass packet 'forward' */ /* * (Only for physical ports, rx rings with NR_FORWARD set). * Slot released to the kernel (i.e. before ring->head) with * this flag set are passed to the peer ring (host/NIC), * thus restoring the host-NIC connection for these slots. * This supports efficient traffic monitoring or firewalling. */ #define NS_NO_LEARN 0x0008 /* disable bridge learning */ /* * On a VALE switch, do not 'learn' the source port for * this buffer. */ #define NS_INDIRECT 0x0010 /* userspace buffer */ /* * (VALE tx rings only) data is in a userspace buffer, * whose address is in the 'ptr' field in the slot. */ #define NS_MOREFRAG 0x0020 /* packet has more fragments */ /* * (VALE ports only) * Set on all but the last slot of a multi-segment packet. * The 'len' field refers to the individual fragment. */ #define NS_PORT_SHIFT 8 #define NS_PORT_MASK (0xff << NS_PORT_SHIFT) /* * The high 8 bits of the flag, if not zero, indicate the * destination port for the VALE switch, overriding * the lookup table. */ #define NS_RFRAGS(_slot) ( ((_slot)->flags >> 8) & 0xff) /* * (VALE rx rings only) the high 8 bits * are the number of fragments. */ /* * struct netmap_ring * * Netmap representation of a TX or RX ring (also known as "queue"). * This is a queue implemented as a fixed-size circular array. * At the software level the important fields are: head, cur, tail. * * In TX rings: * * head first slot available for transmission. * cur wakeup point. select() and poll() will unblock * when 'tail' moves past 'cur' * tail (readonly) first slot reserved to the kernel * * [head .. tail-1] can be used for new packets to send; * 'head' and 'cur' must be incremented as slots are filled * with new packets to be sent; * 'cur' can be moved further ahead if we need more space * for new transmissions. XXX todo (2014-03-12) * * In RX rings: * * head first valid received packet * cur wakeup point. select() and poll() will unblock * when 'tail' moves past 'cur' * tail (readonly) first slot reserved to the kernel * * [head .. tail-1] contain received packets; * 'head' and 'cur' must be incremented as slots are consumed * and can be returned to the kernel; * 'cur' can be moved further ahead if we want to wait for * new packets without returning the previous ones. * * DATA OWNERSHIP/LOCKING: * The netmap_ring, and all slots and buffers in the range * [head .. tail-1] are owned by the user program; * the kernel only accesses them during a netmap system call * and in the user thread context. * * Other slots and buffers are reserved for use by the kernel */ struct netmap_ring { /* * buf_ofs is meant to be used through macros. * It contains the offset of the buffer region from this * descriptor. */ const int64_t buf_ofs; const uint32_t num_slots; /* number of slots in the ring. */ const uint32_t nr_buf_size; const uint16_t ringid; const uint16_t dir; /* 0: tx, 1: rx */ uint32_t head; /* (u) first user slot */ uint32_t cur; /* (u) wakeup point */ uint32_t tail; /* (k) first kernel slot */ uint32_t flags; struct timeval ts; /* (k) time of last *sync() */ /* opaque room for a mutex or similar object */ uint8_t sem[128] __attribute__((__aligned__(NM_CACHE_ALIGN))); /* the slots follow. This struct has variable size */ struct netmap_slot slot[0]; /* array of slots. */ }; /* * RING FLAGS */ #define NR_TIMESTAMP 0x0002 /* set timestamp on *sync() */ /* * updates the 'ts' field on each netmap syscall. This saves * saves a separate gettimeofday(), and is not much worse than * software timestamps generated in the interrupt handler. */ #define NR_FORWARD 0x0004 /* enable NS_FORWARD for ring */ /* * Enables the NS_FORWARD slot flag for the ring. */ /* * Netmap representation of an interface and its queue(s). * This is initialized by the kernel when binding a file * descriptor to a port, and should be considered as readonly * by user programs. The kernel never uses it. * * There is one netmap_if for each file descriptor on which we want * to select/poll. * select/poll operates on one or all pairs depending on the value of * nmr_queueid passed on the ioctl. */ struct netmap_if { char ni_name[IFNAMSIZ]; /* name of the interface. */ const uint32_t ni_version; /* API version, currently unused */ const uint32_t ni_flags; /* properties */ #define NI_PRIV_MEM 0x1 /* private memory region */ /* * The number of packet rings available in netmap mode. * Physical NICs can have different numbers of tx and rx rings. * Physical NICs also have a 'host' ring pair. * Additionally, clients can request additional ring pairs to * be used for internal communication. */ const uint32_t ni_tx_rings; /* number of HW tx rings */ const uint32_t ni_rx_rings; /* number of HW rx rings */ uint32_t ni_bufs_head; /* head index for extra bufs */ uint32_t ni_spare1[5]; /* * The following array contains the offset of each netmap ring * from this structure, in the following order: * NIC tx rings (ni_tx_rings); host tx ring (1); extra tx rings; * NIC rx rings (ni_rx_rings); host tx ring (1); extra rx rings. * * The area is filled up by the kernel on NIOCREGIF, * and then only read by userspace code. */ const ssize_t ring_ofs[0]; }; #ifndef NIOCREGIF /* * ioctl names and related fields * * NIOCTXSYNC, NIOCRXSYNC synchronize tx or rx queues, * whose identity is set in NIOCREGIF through nr_ringid. * These are non blocking and take no argument. * * NIOCGINFO takes a struct ifreq, the interface name is the input, * the outputs are number of queues and number of descriptor * for each queue (useful to set number of threads etc.). * The info returned is only advisory and may change before * the interface is bound to a file descriptor. * * NIOCREGIF takes an interface name within a struct nmre, * and activates netmap mode on the interface (if possible). * * The argument to NIOCGINFO/NIOCREGIF overlays struct ifreq so we * can pass it down to other NIC-related ioctls. * * The actual argument (struct nmreq) has a number of options to request * different functions. * The following are used in NIOCREGIF when nr_cmd == 0: * * nr_name (in) * The name of the port (em0, valeXXX:YYY, etc.) * limited to IFNAMSIZ for backward compatibility. * * nr_version (in/out) * Must match NETMAP_API as used in the kernel, error otherwise. * Always returns the desired value on output. * * nr_tx_slots, nr_tx_slots, nr_tx_rings, nr_rx_rings (in/out) * On input, non-zero values may be used to reconfigure the port * according to the requested values, but this is not guaranteed. * On output the actual values in use are reported. * * nr_ringid (in) * Indicates how rings should be bound to the file descriptors. * If nr_flags != 0, then the low bits (in NETMAP_RING_MASK) * are used to indicate the ring number, and nr_flags specifies * the actual rings to bind. NETMAP_NO_TX_POLL is unaffected. * * NOTE: THE FOLLOWING (nr_flags == 0) IS DEPRECATED: * If nr_flags == 0, NETMAP_HW_RING and NETMAP_SW_RING control * the binding as follows: * 0 (default) binds all physical rings * NETMAP_HW_RING | ring number binds a single ring pair * NETMAP_SW_RING binds only the host tx/rx rings * * NETMAP_NO_TX_POLL can be OR-ed to make select()/poll() push * packets on tx rings only if POLLOUT is set. * The default is to push any pending packet. * * NETMAP_DO_RX_POLL can be OR-ed to make select()/poll() release * packets on rx rings also when POLLIN is NOT set. * The default is to touch the rx ring only with POLLIN. * Note that this is the opposite of TX because it * reflects the common usage. * * NOTE: NETMAP_PRIV_MEM IS DEPRECATED, use nr_arg2 instead. * NETMAP_PRIV_MEM is set on return for ports that do not use * the global memory allocator. * This information is not significant and applications * should look at the region id in nr_arg2 * * nr_flags is the recommended mode to indicate which rings should * be bound to a file descriptor. Values are NR_REG_* * * nr_arg1 (in) The number of extra rings to be reserved. * Especially when allocating a VALE port the system only * allocates the amount of memory needed for the port. * If more shared memory rings are desired (e.g. for pipes), * the first invocation for the same basename/allocator * should specify a suitable number. Memory cannot be * extended after the first allocation without closing * all ports on the same region. * * nr_arg2 (in/out) The identity of the memory region used. * On input, 0 means the system decides autonomously, * other values may try to select a specific region. * On return the actual value is reported. * Region '1' is the global allocator, normally shared * by all interfaces. Other values are private regions. * If two ports the same region zero-copy is possible. * * nr_arg3 (in/out) number of extra buffers to be allocated. * * * * nr_cmd (in) if non-zero indicates a special command: * NETMAP_BDG_ATTACH and nr_name = vale*:ifname * attaches the NIC to the switch; nr_ringid specifies * which rings to use. Used by vale-ctl -a ... * nr_arg1 = NETMAP_BDG_HOST also attaches the host port * as in vale-ctl -h ... * * NETMAP_BDG_DETACH and nr_name = vale*:ifname * disconnects a previously attached NIC. * Used by vale-ctl -d ... * * NETMAP_BDG_LIST * list the configuration of VALE switches. * * NETMAP_BDG_VNET_HDR * Set the virtio-net header length used by the client * of a VALE switch port. * * NETMAP_BDG_NEWIF * create a persistent VALE port with name nr_name. * Used by vale-ctl -n ... * * NETMAP_BDG_DELIF * delete a persistent VALE port. Used by vale-ctl -d ... * * nr_arg1, nr_arg2, nr_arg3 (in/out) command specific * * * */ /* * struct nmreq overlays a struct ifreq (just the name) */ struct nmreq { char nr_name[IFNAMSIZ]; uint32_t nr_version; /* API version */ uint32_t nr_offset; /* nifp offset in the shared region */ uint32_t nr_memsize; /* size of the shared region */ uint32_t nr_tx_slots; /* slots in tx rings */ uint32_t nr_rx_slots; /* slots in rx rings */ uint16_t nr_tx_rings; /* number of tx rings */ uint16_t nr_rx_rings; /* number of rx rings */ uint16_t nr_ringid; /* ring(s) we care about */ #define NETMAP_HW_RING 0x4000 /* single NIC ring pair */ #define NETMAP_SW_RING 0x2000 /* only host ring pair */ #define NETMAP_RING_MASK 0x0fff /* the ring number */ #define NETMAP_NO_TX_POLL 0x1000 /* no automatic txsync on poll */ #define NETMAP_DO_RX_POLL 0x8000 /* DO automatic rxsync on poll */ uint16_t nr_cmd; #define NETMAP_BDG_ATTACH 1 /* attach the NIC */ #define NETMAP_BDG_DETACH 2 /* detach the NIC */ #define NETMAP_BDG_REGOPS 3 /* register bridge callbacks */ #define NETMAP_BDG_LIST 4 /* get bridge's info */ #define NETMAP_BDG_VNET_HDR 5 /* set the port virtio-net-hdr length */ #define NETMAP_BDG_OFFSET NETMAP_BDG_VNET_HDR /* deprecated alias */ #define NETMAP_BDG_NEWIF 6 /* create a virtual port */ #define NETMAP_BDG_DELIF 7 /* destroy a virtual port */ uint16_t nr_arg1; /* reserve extra rings in NIOCREGIF */ #define NETMAP_BDG_HOST 1 /* attach the host stack on ATTACH */ uint16_t nr_arg2; uint32_t nr_arg3; /* req. extra buffers in NIOCREGIF */ uint32_t nr_flags; /* various modes, extends nr_ringid */ uint32_t spare2[1]; }; #define NR_REG_MASK 0xf /* values for nr_flags */ enum { NR_REG_DEFAULT = 0, /* backward compat, should not be used. */ NR_REG_ALL_NIC = 1, NR_REG_SW = 2, NR_REG_NIC_SW = 3, NR_REG_ONE_NIC = 4, NR_REG_PIPE_MASTER = 5, NR_REG_PIPE_SLAVE = 6, }; /* monitor uses the NR_REG to select the rings to monitor */ #define NR_MONITOR_TX 0x100 #define NR_MONITOR_RX 0x200 #define NR_ZCOPY_MON 0x400 /* request exclusive access to the selected rings */ #define NR_EXCLUSIVE 0x800 /* * FreeBSD uses the size value embedded in the _IOWR to determine * how much to copy in/out. So we need it to match the actual * data structure we pass. We put some spares in the structure * to ease compatibility with other versions */ #define NIOCGINFO _IOWR('i', 145, struct nmreq) /* return IF info */ #define NIOCREGIF _IOWR('i', 146, struct nmreq) /* interface register */ #define NIOCTXSYNC _IO('i', 148) /* sync tx queues */ #define NIOCRXSYNC _IO('i', 149) /* sync rx queues */ #define NIOCCONFIG _IOWR('i',150, struct nm_ifreq) /* for ext. modules */ #endif /* !NIOCREGIF */ /* * Helper functions for kernel and userspace */ /* * check if space is available in the ring. */ static inline int nm_ring_empty(struct netmap_ring *ring) { return (ring->cur == ring->tail); } /* * Opaque structure that is passed to an external kernel * module via ioctl(fd, NIOCCONFIG, req) for a user-owned * bridge port (at this point ephemeral VALE interface). */ #define NM_IFRDATA_LEN 256 struct nm_ifreq { char nifr_name[IFNAMSIZ]; char data[NM_IFRDATA_LEN]; }; #endif /* _NET_NETMAP_H_ */ fastnetmon-1.1.3+dfsg/src/netmap_plugin/netmap_includes/net/netmap_user.h000066400000000000000000000524641313534057500267160ustar00rootroot00000000000000/* * Copyright (C) 2011-2014 Universita` di Pisa. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * $FreeBSD$ * * Functions and macros to manipulate netmap structures and packets * in userspace. See netmap(4) for more information. * * The address of the struct netmap_if, say nifp, is computed from the * value returned from ioctl(.., NIOCREG, ...) and the mmap region: * ioctl(fd, NIOCREG, &req); * mem = mmap(0, ... ); * nifp = NETMAP_IF(mem, req.nr_nifp); * (so simple, we could just do it manually) * * From there: * struct netmap_ring *NETMAP_TXRING(nifp, index) * struct netmap_ring *NETMAP_RXRING(nifp, index) * we can access ring->cur, ring->head, ring->tail, etc. * * ring->slot[i] gives us the i-th slot (we can access * directly len, flags, buf_idx) * * char *buf = NETMAP_BUF(ring, x) returns a pointer to * the buffer numbered x * * All ring indexes (head, cur, tail) should always move forward. * To compute the next index in a circular ring you can use * i = nm_ring_next(ring, i); * * To ease porting apps from pcap to netmap we supply a few fuctions * that can be called to open, close, read and write on netmap in a way * similar to libpcap. Note that the read/write function depend on * an ioctl()/select()/poll() being issued to refill rings or push * packets out. * * In order to use these, include #define NETMAP_WITH_LIBS * in the source file that invokes these functions. */ #ifndef _NET_NETMAP_USER_H_ #define _NET_NETMAP_USER_H_ #include #include /* apple needs sockaddr */ #include /* IFNAMSIZ */ #ifndef likely #define likely(x) __builtin_expect(!!(x), 1) #define unlikely(x) __builtin_expect(!!(x), 0) #endif /* likely and unlikely */ #include /* helper macro */ #define _NETMAP_OFFSET(type, ptr, offset) \ ((type)(void *)((char *)(ptr) + (offset))) #define NETMAP_IF(_base, _ofs) _NETMAP_OFFSET(struct netmap_if *, _base, _ofs) #define NETMAP_TXRING(nifp, index) _NETMAP_OFFSET(struct netmap_ring *, \ nifp, (nifp)->ring_ofs[index] ) #define NETMAP_RXRING(nifp, index) _NETMAP_OFFSET(struct netmap_ring *, \ nifp, (nifp)->ring_ofs[index + (nifp)->ni_tx_rings + 1] ) #define NETMAP_BUF(ring, index) \ ((char *)(ring) + (ring)->buf_ofs + ((index)*(ring)->nr_buf_size)) #define NETMAP_BUF_IDX(ring, buf) \ ( ((char *)(buf) - ((char *)(ring) + (ring)->buf_ofs) ) / \ (ring)->nr_buf_size ) static inline uint32_t nm_ring_next(struct netmap_ring *r, uint32_t i) { return ( unlikely(i + 1 == r->num_slots) ? 0 : i + 1); } /* * Return 1 if we have pending transmissions in the tx ring. * When everything is complete ring->head = ring->tail + 1 (modulo ring size) */ static inline int nm_tx_pending(struct netmap_ring *r) { return nm_ring_next(r, r->tail) != r->head; } static inline uint32_t nm_ring_space(struct netmap_ring *ring) { int ret = ring->tail - ring->cur; if (ret < 0) ret += ring->num_slots; return ret; } #ifdef NETMAP_WITH_LIBS /* * Support for simple I/O libraries. * Include other system headers required for compiling this. */ #ifndef HAVE_NETMAP_WITH_LIBS #define HAVE_NETMAP_WITH_LIBS #include #include #include #include /* memset */ #include #include /* EINVAL */ #include /* O_RDWR */ #include /* close() */ #include #include #ifdef NETMAP_NO_DEBUG #define ND(_fmt, ...) do {} while(0) #define D(_fmt, ...) do {} while(0) #define RD(lps, format, ...) do {} while(0) #else #ifndef ND /* debug macros */ /* debug support */ #define ND(_fmt, ...) do {} while(0) #define D(_fmt, ...) \ do { \ struct timeval _t0; \ gettimeofday(&_t0, NULL); \ fprintf(stderr, "%03d.%06d %s [%d] " _fmt "\n", \ (int)(_t0.tv_sec % 1000), (int)_t0.tv_usec, \ __FUNCTION__, __LINE__, ##__VA_ARGS__); \ } while (0) /* Rate limited version of "D", lps indicates how many per second */ #define RD(lps, format, ...) \ do { \ static int __t0, __cnt; \ struct timeval __xxts; \ gettimeofday(&__xxts, NULL); \ if (__t0 != __xxts.tv_sec) { \ __t0 = __xxts.tv_sec; \ __cnt = 0; \ } \ if (__cnt++ < lps) { \ D(format, ##__VA_ARGS__); \ } \ } while (0) #endif #endif struct nm_pkthdr { /* same as pcap_pkthdr */ struct timeval ts; uint32_t caplen; uint32_t len; }; struct nm_stat { /* same as pcap_stat */ u_int ps_recv; u_int ps_drop; u_int ps_ifdrop; #ifdef WIN32 u_int bs_capt; #endif /* WIN32 */ }; #define NM_ERRBUF_SIZE 512 struct nm_desc { struct nm_desc *self; /* point to self if netmap. */ int fd; void *mem; uint32_t memsize; int done_mmap; /* set if mem is the result of mmap */ struct netmap_if * const nifp; uint16_t first_tx_ring, last_tx_ring, cur_tx_ring; uint16_t first_rx_ring, last_rx_ring, cur_rx_ring; struct nmreq req; /* also contains the nr_name = ifname */ struct nm_pkthdr hdr; /* * The memory contains netmap_if, rings and then buffers. * Given a pointer (e.g. to nm_inject) we can compare with * mem/buf_start/buf_end to tell if it is a buffer or * some other descriptor in our region. * We also store a pointer to some ring as it helps in the * translation from buffer indexes to addresses. */ struct netmap_ring * const some_ring; void * const buf_start; void * const buf_end; /* parameters from pcap_open_live */ int snaplen; int promisc; int to_ms; char *errbuf; /* save flags so we can restore them on close */ uint32_t if_flags; uint32_t if_reqcap; uint32_t if_curcap; struct nm_stat st; char msg[NM_ERRBUF_SIZE]; }; /* * when the descriptor is open correctly, d->self == d * Eventually we should also use some magic number. */ #define P2NMD(p) ((struct nm_desc *)(p)) #define IS_NETMAP_DESC(d) ((d) && P2NMD(d)->self == P2NMD(d)) #define NETMAP_FD(d) (P2NMD(d)->fd) /* * this is a slightly optimized copy routine which rounds * to multiple of 64 bytes and is often faster than dealing * with other odd sizes. We assume there is enough room * in the source and destination buffers. * * XXX only for multiples of 64 bytes, non overlapped. */ static inline void nm_pkt_copy(const void *_src, void *_dst, int l) { const uint64_t *src = (const uint64_t *)_src; uint64_t *dst = (uint64_t *)_dst; if (unlikely(l >= 1024)) { memcpy(dst, src, l); return; } for (; likely(l > 0); l-=64) { *dst++ = *src++; *dst++ = *src++; *dst++ = *src++; *dst++ = *src++; *dst++ = *src++; *dst++ = *src++; *dst++ = *src++; *dst++ = *src++; } } /* * The callback, invoked on each received packet. Same as libpcap */ typedef void (*nm_cb_t)(u_char *, const struct nm_pkthdr *, const u_char *d); /* *--- the pcap-like API --- * * nm_open() opens a file descriptor, binds to a port and maps memory. * * ifname (netmap:foo or vale:foo) is the port name * a suffix can indicate the follwing: * ^ bind the host (sw) ring pair * * bind host and NIC ring pairs (transparent) * -NN bind individual NIC ring pair * {NN bind master side of pipe NN * }NN bind slave side of pipe NN * a suffix starting with + and the following flags, * in any order: * x exclusive access * z zero copy monitor * t monitor tx side * r monitor rx side * * req provides the initial values of nmreq before parsing ifname. * Remember that the ifname parsing will override the ring * number in nm_ringid, and part of nm_flags; * flags special functions, normally 0 * indicates which fields of *arg are significant * arg special functions, normally NULL * if passed a netmap_desc with mem != NULL, * use that memory instead of mmap. */ static struct nm_desc *nm_open(const char *ifname, const struct nmreq *req, uint64_t flags, const struct nm_desc *arg); /* * nm_open can import some fields from the parent descriptor. * These flags control which ones. * Also in flags you can specify NETMAP_NO_TX_POLL and NETMAP_DO_RX_POLL, * which set the initial value for these flags. * Note that the 16 low bits of the flags are reserved for data * that may go into the nmreq. */ enum { NM_OPEN_NO_MMAP = 0x040000, /* reuse mmap from parent */ NM_OPEN_IFNAME = 0x080000, /* nr_name, nr_ringid, nr_flags */ NM_OPEN_ARG1 = 0x100000, NM_OPEN_ARG2 = 0x200000, NM_OPEN_ARG3 = 0x400000, NM_OPEN_RING_CFG = 0x800000, /* tx|rx rings|slots */ }; /* * nm_close() closes and restores the port to its previous state */ static int nm_close(struct nm_desc *); /* * nm_inject() is the same as pcap_inject() * nm_dispatch() is the same as pcap_dispatch() * nm_nextpkt() is the same as pcap_next() */ static int nm_inject(struct nm_desc *, const void *, size_t); static int nm_dispatch(struct nm_desc *, int, nm_cb_t, u_char *); static u_char *nm_nextpkt(struct nm_desc *, struct nm_pkthdr *); /* * Try to open, return descriptor if successful, NULL otherwise. * An invalid netmap name will return errno = 0; * You can pass a pointer to a pre-filled nm_desc to add special * parameters. Flags is used as follows * NM_OPEN_NO_MMAP use the memory from arg, only * if the nr_arg2 (memory block) matches. * NM_OPEN_ARG1 use req.nr_arg1 from arg * NM_OPEN_ARG2 use req.nr_arg2 from arg * NM_OPEN_RING_CFG user ring config from arg */ static struct nm_desc * nm_open(const char *ifname, const struct nmreq *req, uint64_t new_flags, const struct nm_desc *arg) { struct nm_desc *d = NULL; const struct nm_desc *parent = arg; u_int namelen; uint32_t nr_ringid = 0, nr_flags, nr_reg; const char *port = NULL; #define MAXERRMSG 80 char errmsg[MAXERRMSG] = ""; enum { P_START, P_RNGSFXOK, P_GETNUM, P_FLAGS, P_FLAGSOK } p_state; long num; if (strncmp(ifname, "netmap:", 7) && strncmp(ifname, "vale", 4)) { errno = 0; /* name not recognised, not an error */ return NULL; } if (ifname[0] == 'n') ifname += 7; /* scan for a separator */ for (port = ifname; *port && !index("-*^{}/", *port); port++) ; namelen = port - ifname; if (namelen >= sizeof(d->req.nr_name)) { snprintf(errmsg, MAXERRMSG, "name too long"); goto fail; } p_state = P_START; nr_flags = NR_REG_ALL_NIC; /* default for no suffix */ while (*port) { switch (p_state) { case P_START: switch (*port) { case '^': /* only SW ring */ nr_flags = NR_REG_SW; p_state = P_RNGSFXOK; break; case '*': /* NIC and SW */ nr_flags = NR_REG_NIC_SW; p_state = P_RNGSFXOK; break; case '-': /* one NIC ring pair */ nr_flags = NR_REG_ONE_NIC; p_state = P_GETNUM; break; case '{': /* pipe (master endpoint) */ nr_flags = NR_REG_PIPE_MASTER; p_state = P_GETNUM; break; case '}': /* pipe (slave endoint) */ nr_flags = NR_REG_PIPE_SLAVE; p_state = P_GETNUM; break; case '/': /* start of flags */ p_state = P_FLAGS; break; default: snprintf(errmsg, MAXERRMSG, "unknown modifier: '%c'", *port); goto fail; } port++; break; case P_RNGSFXOK: switch (*port) { case '/': p_state = P_FLAGS; break; default: snprintf(errmsg, MAXERRMSG, "unexpected character: '%c'", *port); goto fail; } port++; break; case P_GETNUM: num = strtol(port, (char **)&port, 10); if (num < 0 || num >= NETMAP_RING_MASK) { snprintf(errmsg, MAXERRMSG, "'%ld' out of range [0, %d)", num, NETMAP_RING_MASK); goto fail; } nr_ringid = num & NETMAP_RING_MASK; p_state = P_RNGSFXOK; break; case P_FLAGS: case P_FLAGSOK: switch (*port) { case 'x': nr_flags |= NR_EXCLUSIVE; break; case 'z': nr_flags |= NR_ZCOPY_MON; break; case 't': nr_flags |= NR_MONITOR_TX; break; case 'r': nr_flags |= NR_MONITOR_RX; break; default: snprintf(errmsg, MAXERRMSG, "unrecognized flag: '%c'", *port); goto fail; } port++; p_state = P_FLAGSOK; break; } } if (p_state != P_START && p_state != P_RNGSFXOK && p_state != P_FLAGSOK) { snprintf(errmsg, MAXERRMSG, "unexpected end of port name"); goto fail; } ND("flags: %s %s %s %s", (nr_flags & NR_EXCLUSIVE) ? "EXCLUSIVE" : "", (nr_flags & NR_ZCOPY_MON) ? "ZCOPY_MON" : "", (nr_flags & NR_MONITOR_TX) ? "MONITOR_TX" : "", (nr_flags & NR_MONITOR_RX) ? "MONITOR_RX" : ""); d = (struct nm_desc *)calloc(1, sizeof(*d)); if (d == NULL) { snprintf(errmsg, MAXERRMSG, "nm_desc alloc failure"); errno = ENOMEM; return NULL; } d->self = d; /* set this early so nm_close() works */ d->fd = open("/dev/netmap", O_RDWR); if (d->fd < 0) { snprintf(errmsg, MAXERRMSG, "cannot open /dev/netmap: %s", strerror(errno)); goto fail; } if (req) d->req = *req; d->req.nr_version = NETMAP_API; d->req.nr_ringid &= ~NETMAP_RING_MASK; /* these fields are overridden by ifname and flags processing */ d->req.nr_ringid |= nr_ringid; d->req.nr_flags = nr_flags; memcpy(d->req.nr_name, ifname, namelen); d->req.nr_name[namelen] = '\0'; /* optionally import info from parent */ if (IS_NETMAP_DESC(parent) && new_flags) { if (new_flags & NM_OPEN_ARG1) D("overriding ARG1 %d", parent->req.nr_arg1); d->req.nr_arg1 = new_flags & NM_OPEN_ARG1 ? parent->req.nr_arg1 : 4; if (new_flags & NM_OPEN_ARG2) D("overriding ARG2 %d", parent->req.nr_arg2); d->req.nr_arg2 = new_flags & NM_OPEN_ARG2 ? parent->req.nr_arg2 : 0; if (new_flags & NM_OPEN_ARG3) D("overriding ARG3 %d", parent->req.nr_arg3); d->req.nr_arg3 = new_flags & NM_OPEN_ARG3 ? parent->req.nr_arg3 : 0; if (new_flags & NM_OPEN_RING_CFG) { D("overriding RING_CFG"); d->req.nr_tx_slots = parent->req.nr_tx_slots; d->req.nr_rx_slots = parent->req.nr_rx_slots; d->req.nr_tx_rings = parent->req.nr_tx_rings; d->req.nr_rx_rings = parent->req.nr_rx_rings; } if (new_flags & NM_OPEN_IFNAME) { D("overriding ifname %s ringid 0x%x flags 0x%x", parent->req.nr_name, parent->req.nr_ringid, parent->req.nr_flags); memcpy(d->req.nr_name, parent->req.nr_name, sizeof(d->req.nr_name)); d->req.nr_ringid = parent->req.nr_ringid; d->req.nr_flags = parent->req.nr_flags; } } /* add the *XPOLL flags */ d->req.nr_ringid |= new_flags & (NETMAP_NO_TX_POLL | NETMAP_DO_RX_POLL); if (ioctl(d->fd, NIOCREGIF, &d->req)) { snprintf(errmsg, MAXERRMSG, "NIOCREGIF failed: %s", strerror(errno)); goto fail; } if (IS_NETMAP_DESC(parent) && parent->mem && parent->req.nr_arg2 == d->req.nr_arg2) { /* do not mmap, inherit from parent */ d->memsize = parent->memsize; d->mem = parent->mem; } else { /* XXX TODO: check if memsize is too large (or there is overflow) */ d->memsize = d->req.nr_memsize; d->mem = mmap(0, d->memsize, PROT_WRITE | PROT_READ, MAP_SHARED, d->fd, 0); if (d->mem == MAP_FAILED) { snprintf(errmsg, MAXERRMSG, "mmap failed: %s", strerror(errno)); goto fail; } d->done_mmap = 1; } { struct netmap_if *nifp = NETMAP_IF(d->mem, d->req.nr_offset); struct netmap_ring *r = NETMAP_RXRING(nifp, ); *(struct netmap_if **)(uintptr_t)&(d->nifp) = nifp; *(struct netmap_ring **)(uintptr_t)&d->some_ring = r; *(void **)(uintptr_t)&d->buf_start = NETMAP_BUF(r, 0); *(void **)(uintptr_t)&d->buf_end = (char *)d->mem + d->memsize; } nr_reg = d->req.nr_flags & NR_REG_MASK; if (nr_reg == NR_REG_SW) { /* host stack */ d->first_tx_ring = d->last_tx_ring = d->req.nr_tx_rings; d->first_rx_ring = d->last_rx_ring = d->req.nr_rx_rings; } else if (nr_reg == NR_REG_ALL_NIC) { /* only nic */ d->first_tx_ring = 0; d->first_rx_ring = 0; d->last_tx_ring = d->req.nr_tx_rings - 1; d->last_rx_ring = d->req.nr_rx_rings - 1; } else if (nr_reg == NR_REG_NIC_SW) { d->first_tx_ring = 0; d->first_rx_ring = 0; d->last_tx_ring = d->req.nr_tx_rings; d->last_rx_ring = d->req.nr_rx_rings; } else if (nr_reg == NR_REG_ONE_NIC) { /* XXX check validity */ d->first_tx_ring = d->last_tx_ring = d->first_rx_ring = d->last_rx_ring = d->req.nr_ringid & NETMAP_RING_MASK; } else { /* pipes */ d->first_tx_ring = d->last_tx_ring = 0; d->first_rx_ring = d->last_rx_ring = 0; } #ifdef DEBUG_NETMAP_USER { /* debugging code */ int i; D("%s tx %d .. %d %d rx %d .. %d %d", ifname, d->first_tx_ring, d->last_tx_ring, d->req.nr_tx_rings, d->first_rx_ring, d->last_rx_ring, d->req.nr_rx_rings); for (i = 0; i <= d->req.nr_tx_rings; i++) { struct netmap_ring *r = NETMAP_TXRING(d->nifp, i); D("TX%d %p h %d c %d t %d", i, r, r->head, r->cur, r->tail); } for (i = 0; i <= d->req.nr_rx_rings; i++) { struct netmap_ring *r = NETMAP_RXRING(d->nifp, i); D("RX%d %p h %d c %d t %d", i, r, r->head, r->cur, r->tail); } } #endif /* debugging */ d->cur_tx_ring = d->first_tx_ring; d->cur_rx_ring = d->first_rx_ring; return d; fail: nm_close(d); if (errmsg[0]) D("%s %s", errmsg, ifname); if (errno == 0) errno = EINVAL; return NULL; } static int nm_close(struct nm_desc *d) { /* * ugly trick to avoid unused warnings */ static void *__xxzt[] __attribute__ ((unused)) = { (void *)nm_open, (void *)nm_inject, (void *)nm_dispatch, (void *)nm_nextpkt } ; if (d == NULL || d->self != d) return EINVAL; if (d->done_mmap && d->mem) munmap(d->mem, d->memsize); if (d->fd != -1) close(d->fd); bzero(d, sizeof(*d)); free(d); return 0; } /* * Same prototype as pcap_inject(), only need to cast. */ static int nm_inject(struct nm_desc *d, const void *buf, size_t size) { u_int c, n = d->last_tx_ring - d->first_tx_ring + 1; for (c = 0; c < n ; c++) { /* compute current ring to use */ struct netmap_ring *ring; uint32_t i, idx; uint32_t ri = d->cur_tx_ring + c; if (ri > d->last_tx_ring) ri = d->first_tx_ring; ring = NETMAP_TXRING(d->nifp, ri); if (nm_ring_empty(ring)) { continue; } i = ring->cur; idx = ring->slot[i].buf_idx; ring->slot[i].len = size; nm_pkt_copy(buf, NETMAP_BUF(ring, idx), size); d->cur_tx_ring = ri; ring->head = ring->cur = nm_ring_next(ring, i); return size; } return 0; /* fail */ } /* * Same prototype as pcap_dispatch(), only need to cast. */ static int nm_dispatch(struct nm_desc *d, int cnt, nm_cb_t cb, u_char *arg) { int n = d->last_rx_ring - d->first_rx_ring + 1; int c, got = 0, ri = d->cur_rx_ring; if (cnt == 0) cnt = -1; /* cnt == -1 means infinite, but rings have a finite amount * of buffers and the int is large enough that we never wrap, * so we can omit checking for -1 */ for (c=0; c < n && cnt != got; c++) { /* compute current ring to use */ struct netmap_ring *ring; ri = d->cur_rx_ring + c; if (ri > d->last_rx_ring) ri = d->first_rx_ring; ring = NETMAP_RXRING(d->nifp, ri); for ( ; !nm_ring_empty(ring) && cnt != got; got++) { u_int i = ring->cur; u_int idx = ring->slot[i].buf_idx; u_char *buf = (u_char *)NETMAP_BUF(ring, idx); // __builtin_prefetch(buf); d->hdr.len = d->hdr.caplen = ring->slot[i].len; d->hdr.ts = ring->ts; cb(arg, &d->hdr, buf); ring->head = ring->cur = nm_ring_next(ring, i); } } d->cur_rx_ring = ri; return got; } static u_char * nm_nextpkt(struct nm_desc *d, struct nm_pkthdr *hdr) { int ri = d->cur_rx_ring; do { /* compute current ring to use */ struct netmap_ring *ring = NETMAP_RXRING(d->nifp, ri); if (!nm_ring_empty(ring)) { u_int i = ring->cur; u_int idx = ring->slot[i].buf_idx; u_char *buf = (u_char *)NETMAP_BUF(ring, idx); // __builtin_prefetch(buf); hdr->ts = ring->ts; hdr->len = hdr->caplen = ring->slot[i].len; ring->cur = nm_ring_next(ring, i); /* we could postpone advancing head if we want * to hold the buffer. This can be supported in * the future. */ ring->head = ring->cur; d->cur_rx_ring = ri; return buf; } ri++; if (ri > d->last_rx_ring) ri = d->first_rx_ring; } while (ri != d->cur_rx_ring); return NULL; /* nothing found */ } #endif /* !HAVE_NETMAP_WITH_LIBS */ #endif /* NETMAP_WITH_LIBS */ #endif /* _NET_NETMAP_USER_H_ */ fastnetmon-1.1.3+dfsg/src/networks_list000066400000000000000000000000001313534057500202260ustar00rootroot00000000000000fastnetmon-1.1.3+dfsg/src/networks_whitelist000066400000000000000000000000001313534057500212670ustar00rootroot00000000000000fastnetmon-1.1.3+dfsg/src/notify_about_attack.sh000077500000000000000000000023411313534057500217760ustar00rootroot00000000000000#!/usr/bin/env bash # # Hello, lovely FastNetMon customer! I'm really happy to see you here! # Pavel Odintsov, author # # This script will get following params: # $1 client_ip_as_string # $2 data_direction # $3 pps_as_string # $4 action (ban or unban) email_notify="root,please_fix_this_email@domain.ru" # # Please be carefult! You should not remove cat > # if [ "$4" = "unban" ]; then # No details arrived to stdin here # Unban actions if used exit 0 fi # # For ban and attack_details actions we will receive attack details to stdin # if option notify_script_pass_details enabled in FastNetMon's configuration file # # If you do not need this details, please set option notify_script_pass_details to "no". # # Please do not remove "cat" command if you have notify_script_pass_details enabled, because # FastNetMon will crash in this case (it expect read of data from script side). # if [ "$4" = "ban" ]; then cat | mail -s "FastNetMon Guard: IP $1 blocked because $2 attack with power $3 pps" $email_notify; # You can add ban code here! exit 0 fi if [ "$4" == "attack_details" ]; then cat | mail -s "FastNetMon Guard: IP $1 blocked because $2 attack with power $3 pps" $email_notify; exit 0 fi fastnetmon-1.1.3+dfsg/src/packet_storage.h000066400000000000000000000077321313534057500205630ustar00rootroot00000000000000#ifndef PACKET_STORAGE_H #define PACKET_STORAGE_H #include #include #include "fastnetmon_pcap_format.h" class packet_storage_t { public: packet_storage_t() { memory_pointer = NULL; memory_pos = NULL; buffer_size = 0; // TODO: fix hardcoded mtu size this!!! max_packet_size = 1500; } bool allocate_buffer(unsigned int buffer_size_in_packets) { unsigned int memory_size_in_bytes = buffer_size_in_packets * (max_packet_size + sizeof(fastnetmon_pcap_pkthdr)) + sizeof(fastnetmon_pcap_file_header); // std::cout << "We will allocate " << memory_size_in_bytes << std::endl; memory_pointer = (unsigned char*)malloc( memory_size_in_bytes ); if (memory_pointer != NULL) { this->buffer_size = memory_size_in_bytes; memory_pos = memory_pointer; // Add header to newely allocated memory block return this->write_header(); } else { return false; } } bool write_binary_data(void* data_pointer, unsigned int length) { if (we_have_free_space_for_x_bytes(length)) { memcpy(memory_pos, data_pointer, length); memory_pos += length; return true; } else { return false; } } bool write_packet(void* payload_pointer, unsigned int length) { // TODO: performance killer! Check it! bool we_do_timestamps = true; struct timeval current_time; current_time.tv_sec = 0; current_time.tv_usec = 0; if (we_do_timestamps) { gettimeofday(¤t_time, NULL); } struct fastnetmon_pcap_pkthdr pcap_packet_header; pcap_packet_header.ts_sec = current_time.tv_sec; pcap_packet_header.ts_usec = current_time.tv_usec; // Store full length of packet pcap_packet_header.orig_len = length; if (length > max_packet_size) { // We whould crop packet because it's too big pcap_packet_header.incl_len = max_packet_size; } else { pcap_packet_header.incl_len = length; } if (!this->write_binary_data(&pcap_packet_header, sizeof(pcap_packet_header))) { return false; } return (this->write_binary_data(payload_pointer, pcap_packet_header.incl_len)); } bool we_have_free_space_for_x_bytes(unsigned int length) { if (this->get_used_memory() + length <= this->buffer_size) { return true; } else { return false; } } bool write_header() { struct fastnetmon_pcap_file_header pcap_header; fill_pcap_header(&pcap_header, max_packet_size); return this->write_binary_data(&pcap_header, sizeof(pcap_header)); } int64_t get_used_memory() { return memory_pos - memory_pointer; } bool deallocate_buffer() { if (memory_pointer == NULL or buffer_size == 0) { return true; } free(this->memory_pointer); this->memory_pointer = NULL; this->memory_pos = NULL; this->buffer_size = 0; return true; } void* get_buffer_pointer() { return memory_pointer; } unsigned int get_max_packet_size() { return this->max_packet_size; } void set_max_packet_size(unsigned int new_max_packet_size) { this->max_packet_size = new_max_packet_size; } private: unsigned char* memory_pointer; unsigned char* memory_pos; unsigned int buffer_size; unsigned int max_packet_size; }; #endif fastnetmon-1.1.3+dfsg/src/patches/000077500000000000000000000000001313534057500170355ustar00rootroot00000000000000fastnetmon-1.1.3+dfsg/src/patches/0001-Fix-netmap-code-for-compatibility-with-C-boost.patch000066400000000000000000000074661313534057500313010ustar00rootroot00000000000000From 463481e630a1f9717bdb6a1eb8c993464393d7e7 Mon Sep 17 00:00:00 2001 From: Pavel Odintsov Date: Mon, 2 Mar 2015 15:53:11 +0300 Subject: [PATCH] Fix netmap code for compatibility with C++ boost --- tests/netmap_includes/net/netmap_user.h | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/tests/netmap_includes/net/netmap_user.h b/tests/netmap_includes/net/netmap_user.h index aab6c35..17c2308 100644 --- a/tests/netmap_includes/net/netmap_user.h +++ b/tests/netmap_includes/net/netmap_user.h @@ -147,7 +147,7 @@ nm_ring_space(struct netmap_ring *ring) #ifndef ND /* debug macros */ /* debug support */ #define ND(_fmt, ...) do {} while(0) -#define D(_fmt, ...) \ +#define NETMAP_DEBUG(_fmt, ...) \ do { \ struct timeval _t0; \ gettimeofday(&_t0, NULL); \ @@ -167,7 +167,7 @@ nm_ring_space(struct netmap_ring *ring) __cnt = 0; \ } \ if (__cnt++ < lps) { \ - D(format, ##__VA_ARGS__); \ + NETMAP_DEBUG(format, ##__VA_ARGS__); \ } \ } while (0) #endif @@ -432,26 +432,26 @@ nm_open(const char *ifname, const struct nmreq *req, /* optionally import info from parent */ if (IS_NETMAP_DESC(parent) && new_flags) { if (new_flags & NM_OPEN_ARG1) - D("overriding ARG1 %d", parent->req.nr_arg1); + NETMAP_DEBUG("overriding ARG1 %d", parent->req.nr_arg1); d->req.nr_arg1 = new_flags & NM_OPEN_ARG1 ? parent->req.nr_arg1 : 4; if (new_flags & NM_OPEN_ARG2) - D("overriding ARG2 %d", parent->req.nr_arg2); + NETMAP_DEBUG("overriding ARG2 %d", parent->req.nr_arg2); d->req.nr_arg2 = new_flags & NM_OPEN_ARG2 ? parent->req.nr_arg2 : 0; if (new_flags & NM_OPEN_ARG3) - D("overriding ARG3 %d", parent->req.nr_arg3); + NETMAP_DEBUG("overriding ARG3 %d", parent->req.nr_arg3); d->req.nr_arg3 = new_flags & NM_OPEN_ARG3 ? parent->req.nr_arg3 : 0; if (new_flags & NM_OPEN_RING_CFG) { - D("overriding RING_CFG"); + NETMAP_DEBUG("overriding RING_CFG"); d->req.nr_tx_slots = parent->req.nr_tx_slots; d->req.nr_rx_slots = parent->req.nr_rx_slots; d->req.nr_tx_rings = parent->req.nr_tx_rings; d->req.nr_rx_rings = parent->req.nr_rx_rings; } if (new_flags & NM_OPEN_IFNAME) { - D("overriding ifname %s ringid 0x%x flags 0x%x", + NETMAP_DEBUG("overriding ifname %s ringid 0x%x flags 0x%x", parent->req.nr_name, parent->req.nr_ringid, parent->req.nr_flags); memcpy(d->req.nr_name, parent->req.nr_name, @@ -521,16 +521,16 @@ nm_open(const char *ifname, const struct nmreq *req, { /* debugging code */ int i; - D("%s tx %d .. %d %d rx %d .. %d %d", ifname, + NETMAP_DEBUG("%s tx %d .. %d %d rx %d .. %d %d", ifname, d->first_tx_ring, d->last_tx_ring, d->req.nr_tx_rings, d->first_rx_ring, d->last_rx_ring, d->req.nr_rx_rings); for (i = 0; i <= d->req.nr_tx_rings; i++) { struct netmap_ring *r = NETMAP_TXRING(d->nifp, i); - D("TX%d %p h %d c %d t %d", i, r, r->head, r->cur, r->tail); + NETMAP_DEBUG("TX%d %p h %d c %d t %d", i, r, r->head, r->cur, r->tail); } for (i = 0; i <= d->req.nr_rx_rings; i++) { struct netmap_ring *r = NETMAP_RXRING(d->nifp, i); - D("RX%d %p h %d c %d t %d", i, r, r->head, r->cur, r->tail); + NETMAP_DEBUG("RX%d %p h %d c %d t %d", i, r, r->head, r->cur, r->tail); } } #endif /* debugging */ @@ -542,7 +542,7 @@ nm_open(const char *ifname, const struct nmreq *req, fail: nm_close(d); if (errmsg) - D("%s %s", errmsg, ifname); + NETMAP_DEBUG("%s %s", errmsg, ifname); if (errno == 0) errno = EINVAL; return NULL; -- 1.7.10.4 fastnetmon-1.1.3+dfsg/src/pcap_plugin/000077500000000000000000000000001313534057500177075ustar00rootroot00000000000000fastnetmon-1.1.3+dfsg/src/pcap_plugin/pcap_collector.cpp000066400000000000000000000172371313534057500234160ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // log4cpp logging facility #include "log4cpp/Category.hh" #include "log4cpp/Appender.hh" #include "log4cpp/FileAppender.hh" #include "log4cpp/OstreamAppender.hh" #include "log4cpp/Layout.hh" #include "log4cpp/BasicLayout.hh" #include "log4cpp/PatternLayout.hh" #include "log4cpp/Priority.hh" #include "pcap_collector.h" // Standard shift for type DLT_EN10MB, Ethernet unsigned int DATA_SHIFT_VALUE = 14; /* Complete list of ethertypes: http://en.wikipedia.org/wiki/EtherType */ /* This is the decimal equivalent of the VLAN tag's ether frame type */ #define VLAN_ETHERTYPE 0x8100 #define IP_ETHERTYPE 0x0800 #define IP6_ETHERTYPE 0x86dd #define ARP_ETHERTYPE 0x0806 /* 802.1Q VLAN tags are 4 bytes long. */ #define VLAN_HDRLEN 4 extern log4cpp::Category& logger; extern std::map configuration_map; // This variable name should be uniq for every plugin! process_packet_pointer pcap_process_func_ptr = NULL; // Enlarge receive buffer for PCAP for minimize packet drops unsigned int pcap_buffer_size_mbytes = 10; // pcap handler, we want it as global variable beacuse it used in singnal handler pcap_t* descr = NULL; char errbuf[PCAP_ERRBUF_SIZE]; struct pcap_pkthdr hdr; // Prototypes void parse_packet(u_char* user, struct pcap_pkthdr* packethdr, const u_char* packetptr); void pcap_main_loop(const char* dev); void start_pcap_collection(process_packet_pointer func_ptr) { logger << log4cpp::Priority::INFO << "Pcap plugin started"; pcap_process_func_ptr = func_ptr; std::string interface_for_listening = ""; if (configuration_map.count("interfaces") != 0) { interface_for_listening = configuration_map["interfaces"]; } logger << log4cpp::Priority::INFO << "Pcap will sniff interface: " << interface_for_listening; pcap_main_loop(interface_for_listening.c_str()); } void stop_pcap_collection() { // stop pcap loop pcap_breakloop(descr); } // We do not use this function now! It's buggy! void parse_packet(u_char* user, struct pcap_pkthdr* packethdr, const u_char* packetptr) { struct ip* iphdr; struct tcphdr* tcphdr; struct udphdr* udphdr; struct ether_header* eptr; /* net/ethernet.h */ eptr = (struct ether_header*)packetptr; if (ntohs(eptr->ether_type) == VLAN_ETHERTYPE) { // It's tagged traffic we should sjoft for 4 bytes for getting the data packetptr += DATA_SHIFT_VALUE + VLAN_HDRLEN; } else if (ntohs(eptr->ether_type) == IP_ETHERTYPE) { // Skip the datalink layer header and get the IP header fields. packetptr += DATA_SHIFT_VALUE; } else if (ntohs(eptr->ether_type) == IP6_ETHERTYPE or ntohs(eptr->ether_type) == ARP_ETHERTYPE) { // we know about it but does't not care now } else { // printf("Packet with non standard ethertype found: 0x%x\n", ntohs(eptr->ether_type)); } iphdr = (struct ip*)packetptr; // src/dst UO is an in_addr, http://man7.org/linux/man-pages/man7/ip.7.html uint32_t src_ip = iphdr->ip_src.s_addr; uint32_t dst_ip = iphdr->ip_dst.s_addr; // The ntohs() function converts the unsigned short integer netshort from network byte order to // host byte order unsigned int packet_length = ntohs(iphdr->ip_len); simple_packet current_packet; // Advance to the transport layer header then parse and display // the fields based on the type of hearder: tcp, udp or icmp packetptr += 4 * iphdr->ip_hl; switch (iphdr->ip_p) { case IPPROTO_TCP: tcphdr = (struct tcphdr*)packetptr; #if defined(__FreeBSD__) || defined(__APPLE__) || defined(__DragonFly__) current_packet.source_port = ntohs(tcphdr->th_sport); #else current_packet.source_port = ntohs(tcphdr->source); #endif #if defined(__FreeBSD__) || defined(__APPLE__) || defined(__DragonFly__) current_packet.destination_port = ntohs(tcphdr->th_dport); #else current_packet.destination_port = ntohs(tcphdr->dest); #endif break; case IPPROTO_UDP: udphdr = (struct udphdr*)packetptr; #if defined(__FreeBSD__) || defined(__APPLE__) || defined(__DragonFly__) current_packet.source_port = ntohs(udphdr->uh_sport); #else current_packet.source_port = ntohs(udphdr->source); #endif #if defined(__FreeBSD__) || defined(__APPLE__) || defined(__DragonFly__) current_packet.destination_port = ntohs(udphdr->uh_dport); #else current_packet.destination_port = ntohs(udphdr->dest); #endif break; case IPPROTO_ICMP: // there are no port for ICMP current_packet.source_port = 0; current_packet.destination_port = 0; break; } current_packet.protocol = iphdr->ip_p; current_packet.src_ip = src_ip; current_packet.dst_ip = dst_ip; current_packet.length = packet_length; // Do packet processing pcap_process_func_ptr(current_packet); } void pcap_main_loop(const char* dev) { char errbuf[PCAP_ERRBUF_SIZE]; /* open device for reading in promiscuous mode */ int promisc = 1; bpf_u_int32 maskp; /* subnet mask */ bpf_u_int32 netp; /* ip */ logger << log4cpp::Priority::INFO << "Start listening on " << dev; /* Get the network address and mask */ pcap_lookupnet(dev, &netp, &maskp, errbuf); descr = pcap_create(dev, errbuf); if (descr == NULL) { logger << log4cpp::Priority::ERROR << "pcap_create was failed with error: " << errbuf; exit(0); } // Setting up 1MB buffer int set_buffer_size_res = pcap_set_buffer_size(descr, pcap_buffer_size_mbytes * 1024 * 1024); if (set_buffer_size_res != 0) { if (set_buffer_size_res == PCAP_ERROR_ACTIVATED) { logger << log4cpp::Priority::ERROR << "Can't set buffer size because pcap already activated\n"; exit(1); } else { logger << log4cpp::Priority::ERROR << "Can't set buffer size due to error: " << set_buffer_size_res; exit(1); } } if (pcap_set_promisc(descr, promisc) != 0) { logger << log4cpp::Priority::ERROR << "Can't activate promisc mode for interface: " << dev; exit(1); } if (pcap_activate(descr) != 0) { logger << log4cpp::Priority::ERROR << "Call pcap_activate was failed: " << pcap_geterr(descr); exit(1); } // man pcap-linktype int link_layer_header_type = pcap_datalink(descr); if (link_layer_header_type == DLT_EN10MB) { DATA_SHIFT_VALUE = 14; } else if (link_layer_header_type == DLT_LINUX_SLL) { DATA_SHIFT_VALUE = 16; } else { logger << log4cpp::Priority::INFO << "We did not support link type:" << link_layer_header_type; exit(0); } pcap_loop(descr, -1, (pcap_handler)parse_packet, NULL); } std::string get_pcap_stats() { std::stringstream output_buffer; struct pcap_stat current_pcap_stats; if (pcap_stats(descr, ¤t_pcap_stats) == 0) { output_buffer << "PCAP statistics" << "\n" << "Received packets: " << current_pcap_stats.ps_recv << "\n" << "Dropped packets: " << current_pcap_stats.ps_drop << " (" << int((double)current_pcap_stats.ps_drop / current_pcap_stats.ps_recv * 100) << "%)" << "\n" << "Dropped by driver or interface: " << current_pcap_stats.ps_ifdrop << "\n"; } return output_buffer.str(); } fastnetmon-1.1.3+dfsg/src/pcap_plugin/pcap_collector.h000066400000000000000000000003431313534057500230510ustar00rootroot00000000000000#ifndef PCAP_PLUGIN_H #define PCAP_PLUGIN_H #include "../fastnetmon_types.h" #include void start_pcap_collection(process_packet_pointer func_ptr); void stop_pcap_collection(); std::string get_pcap_stats(); #endif fastnetmon-1.1.3+dfsg/src/pcap_reader.cpp000066400000000000000000000224311313534057500203610ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #include "fastnetmon_pcap_format.h" #ifdef ENABLE_DPI #include "fast_dpi.h" #endif #include #include #include #include "netflow_plugin/netflow_collector.h" #include "sflow_plugin/sflow_collector.h" #include "sflow_plugin/sflow_data.h" #include "sflow_plugin/sflow.h" #include "fastnetmon_packet_parser.h" #include "fastnetmon_types.h" #include "fast_library.h" #include "log4cpp/Category.hh" #include "log4cpp/Appender.hh" #include "log4cpp/FileAppender.hh" #include "log4cpp/OstreamAppender.hh" #include "log4cpp/Layout.hh" #include "log4cpp/BasicLayout.hh" #include "log4cpp/PatternLayout.hh" #include "log4cpp/Priority.hh" // We will use this code from Global Symbols table (originally it's defined in netmap collector.cpp) bool parse_raw_packet_to_simple_packet(u_char* buffer, int len, simple_packet& packet); // Fake config std::map configuration_map; std::string log_file_path = "/tmp/fastnetmon_pcap_reader.log"; log4cpp::Category& logger = log4cpp::Category::getRoot(); uint64_t total_unparsed_packets = 0; uint64_t dns_amplification_packets = 0; uint64_t ntp_amplification_packets = 0; uint64_t ssdp_amplification_packets = 0; uint64_t raw_parsed_packets = 0; uint64_t raw_unparsed_packets = 0; /* It's prototype for moc testing of FastNetMon, it's very useful for netflow or direct packet * parsers debug */ void init_logging() { log4cpp::PatternLayout* layout = new log4cpp::PatternLayout(); layout->setConversionPattern("%d [%p] %m%n"); log4cpp::Appender* appender = new log4cpp::FileAppender("default", log_file_path); appender->setLayout(layout); logger.setPriority(log4cpp::Priority::INFO); logger.addAppender(appender); logger.info("Logger initialized!"); } void pcap_parse_packet(const char* flow_type, char* buffer, uint32_t len); void my_fastnetmon_packet_handler(simple_packet& current_packet) { std::cout << print_simple_packet(current_packet); } extern process_packet_pointer netflow_process_func_ptr; extern process_packet_pointer sflow_process_func_ptr; char* flow_type = NULL; #ifdef ENABLE_DPI struct ndpi_detection_module_struct* my_ndpi_struct = NULL; u_int32_t ndpi_size_flow_struct = 0; u_int32_t ndpi_size_id_struct = 0; #endif void pcap_parse_packet(char* buffer, uint32_t len, uint32_t snap_len) { struct pfring_pkthdr packet_header; memset(&packet_header, 0, sizeof(packet_header)); packet_header.len = len; packet_header.caplen = snap_len; fastnetmon_parse_pkt((u_char*)buffer, &packet_header, 4, 1, 0); // char print_buffer[512]; // fastnetmon_print_parsed_pkt(print_buffer, 512, (u_char*)buffer, &packet_header); // logger.info("%s", print_buffer); char* payload_ptr = packet_header.extended_hdr.parsed_pkt.offset.payload_offset + buffer; if (packet_header.len < packet_header.extended_hdr.parsed_pkt.offset.payload_offset) { printf("Something goes wrong! Offset %u is bigger than total packet length %u\n", packet_header.extended_hdr.parsed_pkt.offset.payload_offset, packet_header.len); return; } unsigned int payload_length = packet_header.len - packet_header.extended_hdr.parsed_pkt.offset.payload_offset; if (strcmp(flow_type, "netflow") == 0) { netflow_process_func_ptr = my_fastnetmon_packet_handler; std::string fake_peer_ip = "10.0.1.2"; process_netflow_packet((u_int8_t*)payload_ptr, payload_length, fake_peer_ip); } else if (strcmp(flow_type, "sflow") == 0) { sflow_process_func_ptr = my_fastnetmon_packet_handler; SFSample sample; memset(&sample, 0, sizeof(sample)); sample.rawSample = (uint8_t*)payload_ptr; sample.rawSampleLen = payload_length; sample.sourceIP.type = SFLADDRESSTYPE_IP_V4; read_sflow_datagram(&sample); } else if (strcmp(flow_type, "raw") == 0) { // We do not need parsed data here struct pfring_pkthdr raw_packet_header; memset(&raw_packet_header, 0, sizeof(raw_packet_header)); raw_packet_header.len = len; raw_packet_header.caplen = snap_len; int parser_return_code = fastnetmon_parse_pkt((u_char*)buffer, &raw_packet_header, 4, 1, 0); // We are not interested so much in l2 data and we interested only in l3 data here and more if (parser_return_code < 3) { printf("Parser failed for with code %d following packet with number %llu\n", parser_return_code, raw_unparsed_packets + raw_parsed_packets); raw_unparsed_packets++; } else { raw_parsed_packets++; } char print_buffer[512]; fastnetmon_print_parsed_pkt(print_buffer, 512, (u_char*)buffer, &raw_packet_header); printf("Raw parser: %s", print_buffer); simple_packet packet; // TODO: add support for caplen here! if (parse_raw_packet_to_simple_packet((u_char*)buffer, len, packet)) { std::cout << "High level parser: " << print_simple_packet(packet) << std::endl; } else { printf("High level parser failed\n"); } } else if (strcmp(flow_type, "dpi") == 0) { #ifdef ENABLE_DPI struct ndpi_id_struct *src = NULL; struct ndpi_id_struct *dst = NULL; struct ndpi_flow_struct *flow = NULL; src = (struct ndpi_id_struct*)malloc(ndpi_size_id_struct); memset(src, 0, ndpi_size_id_struct); dst = (struct ndpi_id_struct*)malloc(ndpi_size_id_struct); memset(dst, 0, ndpi_size_id_struct); flow = (struct ndpi_flow_struct *)malloc(ndpi_size_flow_struct); memset(flow, 0, ndpi_size_flow_struct); uint32_t current_tickt = 0; uint8_t* iph = (uint8_t*)(&buffer[packet_header.extended_hdr.parsed_pkt.offset.l3_offset]); unsigned int ipsize = packet_header.len; ndpi_protocol detected_protocol = ndpi_detection_process_packet(my_ndpi_struct, flow, iph, ipsize, current_tickt, src, dst); char* protocol_name = ndpi_get_proto_name(my_ndpi_struct, detected_protocol.protocol); char* master_protocol_name = ndpi_get_proto_name(my_ndpi_struct, detected_protocol.master_protocol); printf("Protocol: %s master protocol: %s\n", protocol_name, master_protocol_name); if (detected_protocol.protocol == NDPI_PROTOCOL_DNS) { // It's answer for ANY request with so much if (flow->protos.dns.query_type == 255 && flow->protos.dns.num_queries < flow->protos.dns.num_answers) { dns_amplification_packets++; } printf("It's DNS, we could check packet type. query_type: %d query_class: %d rsp_code: %d num answers: %d, num queries: %d\n", flow->protos.dns.query_type, flow->protos.dns.query_class, flow->protos.dns.rsp_type, flow->protos.dns.num_answers, flow->protos.dns.num_queries ); /* struct { u_int8_t num_queries, num_answers, ret_code; u_int8_t bad_packet // the received packet looks bad u_int16_t query_type, query_class, rsp_type; } dns; */ } else if (detected_protocol.protocol == NDPI_PROTOCOL_NTP) { printf("Request type field: %d version: %d\n", flow->protos.ntp.request_code, flow->protos.ntp.version); // Detect packets with type MON_GETLIST_1 if (flow->protos.ntp.version == 2 && flow->protos.ntp.request_code == 42) { ntp_amplification_packets++; } } else if (detected_protocol.protocol == NDPI_PROTOCOL_SSDP) { ssdp_amplification_packets++; } ndpi_free_flow(flow); free(dst); free(src); #endif } else { printf("We do not support this flow type: %s\n", flow_type); } } int main(int argc, char** argv) { init_logging(); if (argc != 3) { printf("Please provide flow type: sflow, netflow, raw or dpi and path to pcap dump\n"); exit(1); } flow_type = argv[1]; printf("We will process file: %s as %s dump\n", argv[2], argv[1]); #ifdef ENABLE_DPI if (strcmp(flow_type, "dpi") == 0) { my_ndpi_struct = init_ndpi(); if (my_ndpi_struct == NULL) { printf("Can't load nDPI\n"); exit(0); } ndpi_size_id_struct = ndpi_detection_get_sizeof_ndpi_id_struct(); ndpi_size_flow_struct = ndpi_detection_get_sizeof_ndpi_flow_struct(); } #endif pcap_reader(argv[2], pcap_parse_packet); if (strcmp(flow_type, "raw") == 0) { printf("Parsed packets: %llu\n", raw_parsed_packets); printf("Unparsed packets: %llu\n", raw_unparsed_packets); printf("Total packets: %llu\n", raw_parsed_packets + raw_unparsed_packets); } #ifdef ENABLE_DPI if (strcmp(flow_type, "dpi") == 0) { printf("DNS amplification packets: %lld\n", dns_amplification_packets); printf("NTP amplification packets: %lld\n", ntp_amplification_packets); printf("SSDP amplification packets: %lld\n", ssdp_amplification_packets); } #endif } fastnetmon-1.1.3+dfsg/src/pfring_plugin/000077500000000000000000000000001313534057500202515ustar00rootroot00000000000000fastnetmon-1.1.3+dfsg/src/pfring_plugin/pfring_collector.cpp000066400000000000000000000614141313534057500243160ustar00rootroot00000000000000// log4cpp logging facility #include "log4cpp/Category.hh" #include "log4cpp/Appender.hh" #include "log4cpp/FileAppender.hh" #include "log4cpp/OstreamAppender.hh" #include "log4cpp/Layout.hh" #include "log4cpp/BasicLayout.hh" #include "log4cpp/PatternLayout.hh" #include "log4cpp/Priority.hh" #include "../fast_library.h" // For support uint32_t, uint16_t #include #include #include // For config map operations #include #include // For support: IPPROTO_TCP, IPPROTO_ICMP, IPPROTO_UDP #include #include #include #include "pfring_collector.h" #include "pfring.h" #ifdef PF_RING_ZC #include "pfring_zc.h" #endif #include uint32_t pfring_sampling_ratio = 1; // Get log4cpp logger from main programm extern log4cpp::Category& logger; extern uint64_t total_unparsed_packets; // Global configuration map extern std::map configuration_map; // Interface name or interface list (delimitered by comma) std::string work_on_interfaces = ""; // This variable name should be uniq for every plugin! process_packet_pointer pfring_process_func_ptr = NULL; // We can look inside L2TP packets with IP encapsulation // And do it by default bool do_unpack_l2tp_over_ip = true; // Variable from PF_RING multi channel mode int num_pfring_channels = 0; // We can use software or hardware (in kernel module) packet parser bool we_use_pf_ring_in_kernel_parser = true; // By default we pool PF_RING on one thread bool enable_pfring_multi_channel_mode = false; struct thread_stats { u_int64_t __padding_0[8]; u_int64_t numPkts; u_int64_t numBytes; pfring* ring; pthread_t pd_thread; int core_affinity; volatile u_int64_t do_shutdown; u_int64_t __padding_1[3]; }; struct thread_stats* threads; pfring* pf_ring_descr = NULL; // We can use ZC api bool pf_ring_zc_api_mode = false; #ifdef PF_RING_ZC u_int32_t zc_num_threads = 0; pthread_t* zc_threads; pfring_zc_cluster* zc; pfring_zc_worker* zw; pfring_zc_queue** inzq; pfring_zc_queue** outzq; pfring_zc_multi_queue* outzmq; /* fanout */ pfring_zc_buffer_pool* wsp; pfring_zc_pkt_buff** buffers; #endif // Prototypes #ifdef PF_RING_ZC bool zc_main_loop(const char* device); #endif bool pf_ring_main_loop(const char* dev); bool pf_ring_main_loop_multi_channel(const char* dev); void* pf_ring_packet_consumer_thread(void* _id); void pfring_main_packet_process_task(); void start_pfring_collection(process_packet_pointer func_ptr) { logger << log4cpp::Priority::INFO << "PF_RING plugin started"; pfring_process_func_ptr = func_ptr; #ifdef PF_RING_ZC if (configuration_map.count("enable_pf_ring_zc_mode")) { if (configuration_map["enable_pf_ring_zc_mode"] == "on") { pf_ring_zc_api_mode = true; } else { pf_ring_zc_api_mode = false; } } #endif if (configuration_map.count("interfaces") != 0) { work_on_interfaces = configuration_map["interfaces"]; // We should check all interfaces and check zc flag for all if (work_on_interfaces.find("zc:") != std::string::npos) { we_use_pf_ring_in_kernel_parser = false; logger << log4cpp::Priority::INFO << "We detect run in PF_RING Zero Copy or DNA mode and we enable packet parser!"; } logger << log4cpp::Priority::INFO << "We selected interface:" << work_on_interfaces; } if (configuration_map.count("pfring_sampling_ratio") != 0) { pfring_sampling_ratio = convert_string_to_integer(configuration_map["pfring_sampling_ratio"]); } if (work_on_interfaces == "") { logger << log4cpp::Priority::ERROR << "Please specify interface"; exit(1); } pfring_main_packet_process_task(); } void stop_pfring_collection() { pfring_breakloop(pf_ring_descr); } void parse_packet_pf_ring(const struct pfring_pkthdr* h, const u_char* p, const u_char* user_bytes) { // Description of all fields: http://www.ntop.org/pfring_api/structpkt__parsing__info.html simple_packet packet; // We pass only one packet to processing packet.number_of_packets = 1; // Now we support only non sampled input from PF_RING packet.sample_ratio = pfring_sampling_ratio; if (!pf_ring_zc_api_mode) { if (!we_use_pf_ring_in_kernel_parser) { // In ZC (zc:eth0) mode you should manually add packet parsing here // Because it disabled by default: "parsing already disabled in zero-copy" // http://www.ntop.org/pfring_api/pfring_8h.html // Parse up to L3, no timestamp, no hashing // 1 - add timestamp, 0 - disable hash // We should zeroify packet header because PFRING ZC did not do this! memset((void*)&h->extended_hdr.parsed_pkt, 0, sizeof(h->extended_hdr.parsed_pkt)); // We do not calculate timestamps here because it's useless and consumes so much cpu // https://github.com/ntop/PF_RING/issues/9 u_int8_t timestamp = 0; u_int8_t add_hash = 0; pfring_parse_pkt((u_char*)p, (struct pfring_pkthdr*)h, 4, timestamp, add_hash); } } if (do_unpack_l2tp_over_ip) { // 2014-12-08 13:36:53,537 [INFO] [00:1F:12:84:E2:E7 -> 90:E2:BA:49:85:C8] // [IPv4][5.254.105.102:0 -> 159.253.17.251:0] // [l3_proto=115][hash=2784721876][tos=32][tcp_seq_num=0] // [caplen=128][len=873][parsed_header_len=0][eth_offset=-14][l3_offset=14][l4_offset=34][payload_offset=0] // L2TP has an proto number 115 if (h->extended_hdr.parsed_pkt.l3_proto == 115) { // pfring_parse_pkt expects that the hdr memory is either zeroed or contains valid // values // for the current packet, in order to avoid parsing twice the same packet headers. struct pfring_pkthdr l2tp_header; memset(&l2tp_header, 0, sizeof(l2tp_header)); int16_t l4_offset = h->extended_hdr.parsed_pkt.offset.l4_offset; // L2TP has two headers: L2TP and default L2-Specific Sublayer: every header for 4bytes int16_t l2tp_header_size = 8; l2tp_header.len = h->len - (l4_offset + l2tp_header_size); l2tp_header.caplen = h->caplen - (l4_offset + l2tp_header_size); const u_char* l2tp_tunnel_payload = p + l4_offset + l2tp_header_size; // 1 - add timestamp, 0 - disable hash pfring_parse_pkt((u_char*)l2tp_tunnel_payload, &l2tp_header, 4, 1, 0); // Copy data back // TODO: it's not fine solution and I should redesign this code memcpy((struct pfring_pkthdr*)h, &l2tp_header, sizeof(l2tp_header)); // TODO: Global pfring_print_parsed_pkt can fail because we did not shift 'p' pointer // Uncomment this line for deep inspection of all packets /* char buffer[512]; pfring_print_parsed_pkt(buffer, 512, l2tp_tunnel_payload, h); logger<extended_hdr.parsed_pkt.ip_version != 4 && h->extended_hdr.parsed_pkt.ip_version != 6) { total_unparsed_packets++; return; } packet.ip_protocol_version = h->extended_hdr.parsed_pkt.ip_version; if (packet.ip_protocol_version == 4) { // IPv4 /* PF_RING stores data in host byte order but we use network byte order */ packet.src_ip = htonl(h->extended_hdr.parsed_pkt.ip_src.v4); packet.dst_ip = htonl(h->extended_hdr.parsed_pkt.ip_dst.v4); } else { // IPv6 memcpy(packet.src_ipv6.s6_addr, h->extended_hdr.parsed_pkt.ip_src.v6.s6_addr, 16); memcpy(packet.dst_ipv6.s6_addr, h->extended_hdr.parsed_pkt.ip_dst.v6.s6_addr, 16); } packet.source_port = h->extended_hdr.parsed_pkt.l4_src_port; packet.destination_port = h->extended_hdr.parsed_pkt.l4_dst_port; // We need this for deep packet inspection packet.packet_payload_length = h->len; packet.packet_payload_pointer = (void*)p; packet.length = h->len; packet.protocol = h->extended_hdr.parsed_pkt.l3_proto; packet.ts = h->ts; // Copy flags from PF_RING header to our pseudo header if (packet.protocol == IPPROTO_TCP) { packet.flags = h->extended_hdr.parsed_pkt.tcp.flags; } else { packet.flags = 0; } pfring_process_func_ptr(packet); } // Main worker thread for packet handling void pfring_main_packet_process_task() { const char* device_name = work_on_interfaces.c_str(); bool pf_ring_init_result = false; if (pf_ring_zc_api_mode) { #ifdef PF_RING_ZC pf_ring_init_result = zc_main_loop((char*)device_name); #else logger << log4cpp::Priority::ERROR << "PF_RING library hasn't ZC support, please try SVN version"; #endif } else { if (enable_pfring_multi_channel_mode) { pf_ring_init_result = pf_ring_main_loop_multi_channel(device_name); } else { pf_ring_init_result = pf_ring_main_loop(device_name); } } if (!pf_ring_init_result) { // Internal error in PF_RING logger << log4cpp::Priority::ERROR << "PF_RING initilization failed, exit from programm"; exit(1); } } std::string get_pf_ring_stats() { std::stringstream output_buffer; if (pf_ring_zc_api_mode) { #ifdef PF_RING_ZC pfring_zc_stat stats; // We have elements in insq for every hardware device! We shoulw add ability to configure ot int stats_res = pfring_zc_stats(inzq[0], &stats); if (stats_res) { logger << log4cpp::Priority::ERROR << "Can't get PF_RING ZC stats for in queue"; } else { double dropped_percent = 0; if (stats.recv + stats.sent > 0) { dropped_percent = (double)stats.drop / ((double)stats.recv + (double)stats.sent) * 100; } output_buffer << "\n"; output_buffer << "PF_RING ZC in queue statistics\n"; output_buffer << "Received:\t" << stats.recv << "\n"; output_buffer << "Sent:\t\t" << stats.sent << "\n"; output_buffer << "Dropped:\t" << stats.drop << "\n"; output_buffer << "Dropped:\t" << std::fixed << std::setprecision(2) << dropped_percent << " %\n"; } output_buffer << "\n"; output_buffer << "PF_RING ZC out queue statistics\n"; u_int64_t total_recv = 0; u_int64_t total_sent = 0; u_int64_t total_drop = 0; for (int i = 0; i < zc_num_threads; i++) { pfring_zc_stat outq_stats; int outq_stats_res = pfring_zc_stats(outzq[0], &outq_stats); if (stats_res) { logger << log4cpp::Priority::ERROR << "Can't get PF_RING ZC stats for out queue"; } else { total_recv += outq_stats.recv; total_sent += outq_stats.sent; total_drop += outq_stats.drop; } } double total_drop_percent = 0; if (total_recv + total_sent > 0) { total_drop_percent = (double)total_drop / ((double)total_recv + (double)total_sent) * 100; } output_buffer << "Received:\t" << total_recv << "\n"; output_buffer << "Sent:\t\t" << total_sent << "\n"; output_buffer << "Dropped:\t" << total_drop << "\n"; output_buffer << "Dropped:\t" << std::fixed << std::setprecision(2) << total_drop_percent << " %\n"; #endif } // Getting stats for multi channel mode is so complex task if (!enable_pfring_multi_channel_mode && !pf_ring_zc_api_mode) { pfring_stat pfring_status_data; if (pfring_stats(pf_ring_descr, &pfring_status_data) >= 0) { char stats_buffer[256]; double packets_dropped_percent = 0; if (pfring_status_data.recv > 0) { packets_dropped_percent = (double)pfring_status_data.drop / pfring_status_data.recv * 100; } sprintf(stats_buffer, "Packets received:\t%lu\n" "Packets dropped:\t%lu\n" "Packets dropped:\t%.1f %%\n", (long unsigned int)pfring_status_data.recv, (long unsigned int)pfring_status_data.drop, packets_dropped_percent); output_buffer << stats_buffer; } else { logger << log4cpp::Priority::ERROR << "Can't get PF_RING stats"; } } return output_buffer.str(); } bool pf_ring_main_loop_multi_channel(const char* dev) { int MAX_NUM_THREADS = 64; if ((threads = (struct thread_stats*)calloc(MAX_NUM_THREADS, sizeof(struct thread_stats))) == NULL) { logger << log4cpp::Priority::ERROR << "Can't allocate memory for threads structure"; return false; } u_int32_t flags = 0; flags |= PF_RING_PROMISC; /* hardcode: promisc=1 */ flags |= PF_RING_DNA_SYMMETRIC_RSS; /* Note that symmetric RSS is ignored by non-DNA drivers */ flags |= PF_RING_LONG_HEADER; packet_direction direction = rx_only_direction; pfring* ring_array[MAX_NUM_RX_CHANNELS]; unsigned int snaplen = 128; num_pfring_channels = pfring_open_multichannel(dev, snaplen, flags, ring_array); if (num_pfring_channels <= 0) { logger << log4cpp::Priority::INFO << "pfring_open_multichannel returned: " << num_pfring_channels << " and error:" << strerror(errno); return false; } u_int num_cpus = sysconf(_SC_NPROCESSORS_ONLN); logger << log4cpp::Priority::INFO << "We have: " << num_cpus << " logical cpus in this server"; logger << log4cpp::Priority::INFO << "We have: " << num_pfring_channels << " channels from pf_ring NIC"; // We should not start more processes then we have kernel cores // if (num_pfring_channels > num_cpus) { // num_pfring_channels = num_cpus; //} for (int i = 0; i < num_pfring_channels; i++) { // char buf[32]; threads[i].ring = ring_array[i]; // threads[i].core_affinity = threads_core_affinity[i]; int rc = 0; if ((rc = pfring_set_direction(threads[i].ring, direction)) != 0) { logger << log4cpp::Priority::INFO << "pfring_set_direction returned: " << rc; } if ((rc = pfring_set_socket_mode(threads[i].ring, recv_only_mode)) != 0) { logger << log4cpp::Priority::INFO << "pfring_set_socket_mode returned: " << rc; } int rehash_rss = 0; if (rehash_rss) pfring_enable_rss_rehash(threads[i].ring); int poll_duration = 0; if (poll_duration > 0) pfring_set_poll_duration(threads[i].ring, poll_duration); pfring_enable_ring(threads[i].ring); unsigned long thread_id = i; pthread_create(&threads[i].pd_thread, NULL, pf_ring_packet_consumer_thread, (void*)thread_id); } for (int i = 0; i < num_pfring_channels; i++) { pthread_join(threads[i].pd_thread, NULL); pfring_close(threads[i].ring); } return true; } void* pf_ring_packet_consumer_thread(void* _id) { long thread_id = (long)_id; int wait_for_packet = 1; // TODO: fix it bool do_shutdown = false; while (!do_shutdown) { u_char* buffer = NULL; struct pfring_pkthdr hdr; if (pfring_recv(threads[thread_id].ring, &buffer, 0, &hdr, wait_for_packet) > 0) { // TODO: pass (u_char*)thread_id) parse_packet_pf_ring(&hdr, buffer, 0); } else { if (wait_for_packet == 0) { usleep(1); // sched_yield(); } } } return NULL; } #ifdef PF_RING_ZC int rr = -1; int32_t rr_distribution_func(pfring_zc_pkt_buff* pkt_handle, pfring_zc_queue* in_queue, void* user) { long num_out_queues = (long)user; if (++rr == num_out_queues) { rr = 0; } return rr; } #endif #ifdef PF_RING_ZC int bind2core(int core_id) { cpu_set_t cpuset; int s; if (core_id < 0) return -1; CPU_ZERO(&cpuset); CPU_SET(core_id, &cpuset); if ((s = pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpuset)) != 0) { logger << log4cpp::Priority::INFO << "Error while binding to core:" << core_id; return -1; } else { return 0; } } #endif #ifdef PF_RING_ZC void* zc_packet_consumer_thread(void* _id) { long id = (long)_id; pfring_zc_pkt_buff* b = buffers[id]; // Bind to core with thread number bind2core(id); u_int8_t wait_for_packet = 1; struct pfring_pkthdr zc_header; memset(&zc_header, 0, sizeof(zc_header)); while (true) { if (pfring_zc_recv_pkt(outzq[id], &b, wait_for_packet) > 0) { u_char* pkt_data = pfring_zc_pkt_buff_data(b, outzq[id]); memset(&zc_header, 0, sizeof(zc_header)); zc_header.len = b->len; zc_header.caplen = b->len; pfring_parse_pkt(pkt_data, (struct pfring_pkthdr*)&zc_header, 4, 1, 0); parse_packet_pf_ring(&zc_header, pkt_data, 0); } } pfring_zc_sync_queue(outzq[id], rx_only); return NULL; } #endif int max_packet_len(const char* device) { int max_len = 0; pfring* ring = pfring_open(device, 1536, PF_RING_PROMISC); if (ring == NULL) return 1536; // pfring_get_card_settings have added in 6.0.3 // We should not use 6.0.3 API for PF_RING library from ntop because it announces "6.0.3" but lack // of many 6.0.3 features #if RING_VERSION_NUM >= 0x060003 and !defined(WE_USE_PFRING_FROM_NTOP) pfring_card_settings settings; pfring_get_card_settings(ring, &settings); max_len = settings.max_packet_size; #else if (ring->dna.dna_mapped_device) { max_len = ring->dna.dna_dev.mem_info.rx.packet_memory_slot_len; } else { max_len = pfring_get_mtu_size(ring); if (max_len == 0) max_len = 9000 /* Jumbo */; max_len += 14 /* Eth */ + 4 /* VLAN */; } #endif pfring_close(ring); return max_len; } #define MAX_CARD_SLOTS 32768 #define PREFETCH_BUFFERS 8 #define QUEUE_LEN 8192 #ifdef PF_RING_ZC bool zc_main_loop(const char* device) { u_int32_t cluster_id = 0; int bind_core = -1; u_int num_cpus = sysconf(_SC_NPROCESSORS_ONLN); logger << log4cpp::Priority::INFO << "We have: " << num_cpus << " logical cpus in this server"; // TODO: add support for multiple devices! u_int32_t num_devices = 1; zc_num_threads = num_cpus - 1; logger << log4cpp::Priority::INFO << "We will start " << zc_num_threads << " worker threads"; u_int32_t tot_num_buffers = (num_devices * MAX_CARD_SLOTS) + (zc_num_threads * QUEUE_LEN) + zc_num_threads + PREFETCH_BUFFERS; u_int32_t buffer_len = max_packet_len(device); logger << log4cpp::Priority::INFO << "We got max packet len from device: " << buffer_len; logger << log4cpp::Priority::INFO << "We will use total number of ZC buffers: " << tot_num_buffers; zc = pfring_zc_create_cluster(cluster_id, buffer_len, 0, tot_num_buffers, numa_node_of_cpu(bind_core), NULL /* auto hugetlb mountpoint */ ); if (zc == NULL) { logger << log4cpp::Priority::INFO << "pfring_zc_create_cluster error: " << strerror(errno) << " Please check that pf_ring.ko is loaded and hugetlb fs is mounted"; return false; } zc_threads = (pthread_t*)calloc(zc_num_threads, sizeof(pthread_t)); buffers = (pfring_zc_pkt_buff**)calloc(zc_num_threads, sizeof(pfring_zc_pkt_buff*)); inzq = (pfring_zc_queue**)calloc(num_devices, sizeof(pfring_zc_queue*)); outzq = (pfring_zc_queue**)calloc(zc_num_threads, sizeof(pfring_zc_queue*)); for (int i = 0; i < zc_num_threads; i++) { buffers[i] = pfring_zc_get_packet_handle(zc); if (buffers[i] == NULL) { logger << log4cpp::Priority::ERROR << "pfring_zc_get_packet_handle failed"; return false; } } for (int i = 0; i < num_devices; i++) { u_int32_t zc_flags = 0; inzq[i] = pfring_zc_open_device(zc, device, rx_only, zc_flags); if (inzq[i] == NULL) { logger << log4cpp::Priority::ERROR << "pfring_zc_open_device error " << strerror(errno) << " Please check that device is up and not already used"; return false; } #if RING_VERSION_NUM >= 0x060003 int pf_ring_license_state = pfring_zc_check_license(); if (!pf_ring_license_state) { logger << log4cpp::Priority::WARN << "PF_RING ZC haven't license for device" << device << " and running in trial mode and will work only 5 minutes! Please buy license " "or switch to vanilla PF_RING"; } #endif } for (int i = 0; i < zc_num_threads; i++) { outzq[i] = pfring_zc_create_queue(zc, QUEUE_LEN); if (outzq[i] == NULL) { logger << log4cpp::Priority::ERROR << "pfring_zc_create_queue error: " << strerror(errno); return false; } } wsp = pfring_zc_create_buffer_pool(zc, PREFETCH_BUFFERS); if (wsp == NULL) { logger << log4cpp::Priority::ERROR << "pfring_zc_create_buffer_pool error"; return false; } logger << log4cpp::Priority::INFO << "We are starting balancer with: " << zc_num_threads << " threads"; pfring_zc_distribution_func func = rr_distribution_func; u_int8_t wait_for_packet = 1; // We run balancer at last thread int32_t bind_worker_core = zc_num_threads; logger << log4cpp::Priority::INFO << "We will run balancer on core: " << bind_worker_core; zw = pfring_zc_run_balancer(inzq, outzq, num_devices, zc_num_threads, wsp, round_robin_bursts_policy, NULL /* idle callback */, func, (void*)((long)zc_num_threads), !wait_for_packet, bind_worker_core); if (zw == NULL) { logger << log4cpp::Priority::ERROR << "pfring_zc_run_balancer error:" << strerror(errno); return false; } for (int i = 0; i < zc_num_threads; i++) { pthread_create(&zc_threads[i], NULL, zc_packet_consumer_thread, (void*)(long)i); } for (int i = 0; i < zc_num_threads; i++) { pthread_join(zc_threads[i], NULL); } pfring_zc_kill_worker(zw); pfring_zc_destroy_cluster(zc); return true; } #endif bool pf_ring_main_loop(const char* dev) { // We could pool device in multiple threads unsigned int num_threads = 1; bool promisc = true; /* This flag manages packet parser for extended_hdr */ bool use_extended_pkt_header = true; bool enable_hw_timestamp = false; bool dont_strip_timestamps = false; u_int32_t flags = 0; if (num_threads > 1) flags |= PF_RING_REENTRANT; if (use_extended_pkt_header) flags |= PF_RING_LONG_HEADER; if (promisc) flags |= PF_RING_PROMISC; if (enable_hw_timestamp) flags |= PF_RING_HW_TIMESTAMP; if (!dont_strip_timestamps) flags |= PF_RING_STRIP_HW_TIMESTAMP; if (!we_use_pf_ring_in_kernel_parser) { flags |= PF_RING_DO_NOT_PARSE; } flags |= PF_RING_DNA_SYMMETRIC_RSS; /* Note that symmetric RSS is ignored by non-DNA drivers */ // use default value from pfcount.c unsigned int snaplen = 128; pf_ring_descr = pfring_open(dev, snaplen, flags); if (pf_ring_descr == NULL) { logger << log4cpp::Priority::INFO << "pfring_open error: " << strerror(errno) << " (pf_ring not loaded or perhaps you use quick mode and have already a socket bound to: " << dev << ")"; return false; } logger << log4cpp::Priority::INFO << "Successully binded to: " << dev; // We need cast to int because in other way it will be interpreted as char :( logger << log4cpp::Priority::INFO << "Device RX channels number: " << int(pfring_get_num_rx_channels(pf_ring_descr)); u_int32_t version; // Set spplication name in /proc int pfring_set_application_name_result = pfring_set_application_name(pf_ring_descr, (char*)"fastnetmon"); if (pfring_set_application_name_result != 0) { logger << log4cpp::Priority::ERROR << "Can't set programm name for PF_RING: pfring_set_application_name"; } pfring_version(pf_ring_descr, &version); logger.info("Using PF_RING v.%d.%d.%d", (version & 0xFFFF0000) >> 16, (version & 0x0000FF00) >> 8, version & 0x000000FF); int pfring_set_socket_mode_result = pfring_set_socket_mode(pf_ring_descr, recv_only_mode); if (pfring_set_socket_mode_result != 0) { logger.info("pfring_set_socket_mode returned [rc=%d]\n", pfring_set_socket_mode_result); } // enable ring if (pfring_enable_ring(pf_ring_descr) != 0) { logger << log4cpp::Priority::INFO << "Unable to enable ring :-("; pfring_close(pf_ring_descr); return false; } // Active wait wor packets. But I did not know what is mean.. u_int8_t wait_for_packet = 1; pfring_loop(pf_ring_descr, parse_packet_pf_ring, (u_char*)NULL, wait_for_packet); return true; } fastnetmon-1.1.3+dfsg/src/pfring_plugin/pfring_collector.h000066400000000000000000000004121313534057500237520ustar00rootroot00000000000000#ifndef PFRING_PLUGIN_H #define PFRING_PLUGIN_H #include "../fastnetmon_types.h" // This function should be implemented in plugin void start_pfring_collection(process_packet_pointer func_ptr); void stop_pfring_collection(); std::string get_pf_ring_stats(); #endif fastnetmon-1.1.3+dfsg/src/plugin_runner.cpp000066400000000000000000000120511313534057500210000ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #include "libpatricia/patricia.h" #include "fastnetmon_types.h" #include "fast_library.h" #include "netflow_plugin/netflow_collector.h" #include "sflow_plugin/sflow_collector.h" #include "pcap_plugin/pcap_collector.h" #ifdef PF_RING #include "pfring_plugin/pfring_collector.h" #endif #ifdef FASTNETMON_ENABLE_AFPACKET #include "afpacket_plugin/afpacket_collector.h" #endif #ifdef SNABB_SWITCH #include "snabbswitch_plugin/snabbswitch_collector.h" #endif #include "netmap_plugin/netmap_collector.h" // log4cpp logging facility #include "log4cpp/Category.hh" #include "log4cpp/Appender.hh" #include "log4cpp/FileAppender.hh" #include "log4cpp/OstreamAppender.hh" #include "log4cpp/Layout.hh" #include "log4cpp/BasicLayout.hh" #include "log4cpp/PatternLayout.hh" #include "log4cpp/Priority.hh" #include using namespace std; uint64_t total_unparsed_packets = 0; std::string log_file_path = "/tmp/fastnetmon_plugin_tester.log"; log4cpp::Category& logger = log4cpp::Category::getRoot(); // #define DO_SUBNET_LOOKUP #ifdef DO_SUBNET_LOOKUP patricia_tree_t* lookup_tree; #endif // Global map with parsed config file std::map configuration_map; void init_logging() { log4cpp::PatternLayout* layout = new log4cpp::PatternLayout(); layout->setConversionPattern("%d [%p] %m%n"); log4cpp::Appender* appender = new log4cpp::FileAppender("default", log_file_path); appender->setLayout(layout); logger.setPriority(log4cpp::Priority::INFO); logger.addAppender(appender); logger.info("Logger initialized!"); } void process_packet(simple_packet& current_packet) { std::cout << print_simple_packet(current_packet); #ifdef DO_SUBNET_LOOKUP unsigned long subnet = 0; unsigned int subnet_cidr_mask = 0; direction packet_direction = get_packet_direction(lookup_tree, current_packet.src_ip, current_packet.dst_ip, subnet, subnet_cidr_mask); std::cout << "direction: " << get_direction_name(packet_direction) << std::endl; #endif } // Copy & paste from fastnetmon.cpp std::vector read_file_to_vector(std::string file_name) { std::vector data; std::string line; std::ifstream reading_file; reading_file.open(file_name.c_str(), std::ifstream::in); if (reading_file.is_open()) { while (getline(reading_file, line)) { data.push_back(line); } } else { logger << log4cpp::Priority::ERROR << "Can't open file: " << file_name; } return data; } int main(int argc, char* argv[]) { init_logging(); if (argc < 2) { std::cout << "Please specify sflow, netflow, raw, dpi, snabbswitch, afpacket as param" << std::endl; return 1; } #ifdef DO_SUBNET_LOOKUP lookup_tree = New_Patricia(32); std::vector network_list_from_config = read_file_to_vector("/etc/networks_list"); for (std::vector::iterator ii = network_list_from_config.begin(); ii != network_list_from_config.end(); ++ii) { std::string network_address_in_cidr_form = *ii; make_and_lookup(lookup_tree, const_cast(network_address_in_cidr_form.c_str())); } #endif // Required by Netmap and PF_RING plugins // We use fake interface name here because netmap could make server unreachable :) configuration_map["interfaces"] = "ethXXX"; configuration_map["sflow_lua_hooks_path"] = "/usr/src/fastnetmon_lua/src/sflow_hooks.lua"; if (strstr(argv[1], "sflow") != NULL) { std::cout << "Starting sflow" << std::endl; start_sflow_collection(process_packet); } else if (strstr(argv[1], "netflow") != NULL) { std::cout << "Starting netflow" << std::endl; start_netflow_collection(process_packet); } else if (strstr(argv[1], "pcap") != NULL) { std::cout << "Starting pcap" << std::endl; start_pcap_collection(process_packet); } else if (strstr(argv[1], "snabbswitch") != NULL) { std::cout << "Starting snabbswitch" << std::endl; #ifdef SNABB_SWITCH start_snabbswitch_collection(process_packet); #else printf("SnabbSwitch support is not compiled here\n"); #endif } else if (strstr(argv[1], "pfring") != NULL) { #ifdef PF_RING std::cout << "Starting pf_ring" << std::endl; start_pfring_collection(process_packet); #else std::cout << "PF_RING support disabled here" << std::endl; #endif } else if (strstr(argv[1], "afpacket") != NULL) { #ifdef FASTNETMON_ENABLE_AFPACKET std::cout << "Starting afpacket" << std::endl; start_afpacket_collection(process_packet); #else printf("AF_PACKET is not supported here"); #endif } else if (strstr(argv[1], "netmap") != NULL) { std::cout << "Starting netmap" << std::endl; start_netmap_collection(process_packet); } else { std::cout << "Bad plugin name!" << std::endl; } } fastnetmon-1.1.3+dfsg/src/scripts/000077500000000000000000000000001313534057500170755ustar00rootroot00000000000000fastnetmon-1.1.3+dfsg/src/scripts/README.md000066400000000000000000000024071313534057500203570ustar00rootroot00000000000000### Here you could find nice scripts for subnet's collection from the BGP router server - Clone ExaBGP master's repository: ```bash # yum/apt-get install -y python-pip pip install exabgp ``` - Download configs and scripts: ```bash wget https://raw.githubusercontent.com/FastVPSEestiOu/fastnetmon/master/src/scripts/exabgp_network_collector.conf -O/etc/exabgp_network_collector.conf wget https://raw.githubusercontent.com/FastVPSEestiOu/fastnetmon/master/src/scripts/bgp_network_retriever.py -O/usr/local/bin/bgp_network_retriever.py wget https://raw.githubusercontent.com/FastVPSEestiOu/fastnetmon/master/src/scripts/bgp_network_collector.py -O/usr/local/bin/bgp_network_collector.py chmod +x /usr/local/bin/bgp_network_retriever.py /usr/local/bin/bgp_network_collector.py ``` - Run ExaBGP: ```bash cd /usr/src/exabgp env exabgp.log.level=DEBUG exabgp.daemon.user=root exabgp.tcp.bind="0.0.0.0" exabgp.tcp.port=179 exabgp.daemon.daemonize=false exabgp.daemon.pid=/var/run/exabgp.pid exabgp.log.destination=/var/log/exabgp.log exabgp /etc/exabgp_network_collector.conf ``` - Wait few minutes while all announces received (depends on router server size) - Retrieve learned networks from database (/var/lib/bgp_network_collector.db): ```python /usr/local/bin/bgp_network_retriever.py``` fastnetmon-1.1.3+dfsg/src/scripts/bgp_network_collector.py000077500000000000000000000037041313534057500240450ustar00rootroot00000000000000#!/usr/bin/env python import os import sys import time import json from StringIO import StringIO import pprint import shelve import shelve # # Add withdrawal option # database = shelve.open("/var/lib/bgp_network_collector.db") while True: try: line = sys.stdin.readline().strip() # print >> sys.stderr, "GOT A LINE: ", line sys.stdout.flush() io = StringIO(line) decoded_update = json.load(io) pp = pprint.PrettyPrinter(indent=4, stream=sys.stderr) #pp.pprint(decoded_update) try: current_announce = decoded_update["neighbor"]["message"]["update"]["announce"]["ipv4 unicast"] #pp.pprint(current_announce) for next_hop in current_announce: current_announce_for_certain_next_hop = current_announce[next_hop] for prefix_announce in current_announce_for_certain_next_hop: #pp.pprint(current_announce_for_certain_next_hop[prefix_announce]) # drop default gateway if prefix_announce == "0.0.0.0/0": continue print >> sys.stderr, "We learned prefix:", prefix_announce if type(prefix_announce) != str: prefix_announce = prefix_announce.encode('utf8') if not database.has_key(prefix_announce): #print >> sys.stderr, "New data" database[prefix_announce] = 1; else: pass #print >> sys.stderr, "I already have this subnet" # call sync for each data portion database.sync() except KeyError: pass except KeyboardInterrupt: database.close() sys.exit(0) except IOError: # most likely a signal during readline database.close() sys.exit(0) fastnetmon-1.1.3+dfsg/src/scripts/bgp_network_retriever.py000077500000000000000000000002141313534057500240570ustar00rootroot00000000000000#!/usr/bin/env python import shelve database = shelve.open("/var/lib/bgp_network_collector.db") for key in sorted(database): print key fastnetmon-1.1.3+dfsg/src/scripts/build_any_package.pl000077500000000000000000000337431313534057500230700ustar00rootroot00000000000000#!/usr/bin/perl use strict; use warnings; unless (scalar @ARGV == 2) { die "Please specify type and original binary file name: rpm fastnetmon-binary-git-0cfdfd5e2062ad94de24f2f383576ea48e6f3a07-debian-6.0.10-x86_64"; } my $package_type = $ARGV[0]; my $archive_name = $ARGV[1]; if ($package_type eq 'rpm') { build_rpm_package(); } elsif ($package_type eq 'deb') { build_deb_package(); } sub build_rpm_package { print "Install packages for crafting rpm packages\n"; `yum install -y rpmdevtools yum-utils`; mkdir '/root/rpmbuild'; mkdir '/root/rpmbuild/SOURCES'; my $system_v_init_script = <<'DOC'; #!/bin/bash # # fastnetmon Startup script for FastNetMon # # chkconfig: - 85 15 # description: FastNetMon - high performance DoS/DDoS analyzer with sflow/netflow/mirror support # processname: fastnemon # config: /etc/fastnetmon.conf # pidfile: /var/run/fastnetmon.pid # ### BEGIN INIT INFO # Provides: fastnetmon # Required-Start: $local_fs $remote_fs $network # Required-Stop: $local_fs $remote_fs $network # Should-Start: # Short-Description: start and stop FastNetMon # Description: high performance DoS/DDoS analyzer with sflow/netflow/mirror support ### END INIT INFO # Source function library. . /etc/rc.d/init.d/functions # We do not use this configs #if [ -f /etc/sysconfig/fastnetmon ]; then # . /etc/sysconfig/fastnetmon #fi FASTNETMON=/opt/fastnetmon/fastnetmon PROGNAME="fastnetmon" PIDFILE=/var/run/fastnetmon.pid RETVAL=0 ARGS="--daemonize" start() { echo -n $"Starting $PROGNAME: " $FASTNETMON $ARGS > /dev/null 2>&1 && echo_success || echo_failure RETVAL=$? echo "" return $RETVAL } stop() { echo -n $"Stopping $PROGNAME: " killproc -p $PIDFILE $FASTNETMON RETVAL=$? echo "" rm -f $PIDFILE } reload() { echo "Reloading is not supported now, sorry" #echo -n $"Reloading $PROGNAME: " #kill -HUP `cat $PIDFILE` } # See how we were called. case "$1" in start) start ;; stop) stop ;; status) status -p ${PIDFILE} $PROGNAME RETVAL=$? ;; restart) stop sleep 1 start ;; reload) reload ;; *) echo $"Usage: $prog {start|stop|restart|reload|status}" RETVAL=2 esac exit $RETVAL DOC my $systemd_init_script = <<'DOC'; [Unit] Description=FastNetMon - DoS/DDoS analyzer with sflow/netflow/mirror support After=syslog.target network.target remote-fs.target [Service] Type=forking ExecStart=/opt/fastnetmon/fastnetmon --daemonize PIDFile=/run/fastnetmon.pid #ExecReload=/bin/kill -s HUP $MAINPID #ExecStop=/bin/kill -s QUIT $MAINPID [Install] WantedBy=multi-user.target DOC my $rpm_sources_path = '/root/rpmbuild/SOURCES'; # Copy bundle to build tree `cp $archive_name $rpm_sources_path/archive.tar.gz`; `wget --no-check-certificate https://raw.githubusercontent.com/FastVPSEestiOu/fastnetmon/master/src/fastnetmon.conf -O$rpm_sources_path/fastnetmon.conf`; open my $system_v_init_fl, ">", "$rpm_sources_path/system_v_init"; print {$system_v_init_fl} $system_v_init_script; close $system_v_init_fl; open my $systemd_init_fl, ">", "$rpm_sources_path/systemd_init"; print {$systemd_init_fl} $systemd_init_script; close $systemd_init_fl; # Create files list from archive # ./luajit_2.0.4/ my @files_list = `tar -tf /root/rpmbuild/SOURCES/archive.tar.gz`; chomp @files_list; # Replace path @files_list = map { s#^\.#/opt#; $_ } @files_list; # Filter out folders @files_list = grep { ! m#/$# } @files_list; my $systemd_spec_file = <<'DOC'; # # Pre/post params: https://fedoraproject.org/wiki/Packaging:ScriptletSnippets # %global fastnetmon_attackdir %{_localstatedir}/log/fastnetmon_attacks %global fastnetmon_user root %global fastnetmon_group %{fastnetmon_user} %global fastnetmon_config_path %{_sysconfdir}/fastnetmon.conf Name: fastnetmon Version: 1.1.3 Release: 1%{?dist} Summary: A high performance DoS/DDoS load analyzer built on top of multiple packet capture engines (NetFlow, IPFIX, sFLOW, netmap, PF_RING, PCAP). Group: System Environment/Daemons License: GPLv2 URL: https://github.com/FastVPSEestiOu/fastnetmon # Top level fodler inside archive should be named as "fastnetmon-1.1.1" Source0: http://178.62.227.110/fastnetmon_binary_repository/test_binary_builds/this_fake_path_do_not_check_it/archive.tar.gz # Disable any sort of dynamic dependency detection for our own custom bunch of binaries AutoReq: no AutoProv: no Requires: libpcap, numactl, libicu Requires(pre): shadow-utils Requires(post): systemd Requires(preun): systemd Requires(postun): systemd Provides: fastnetmon %description A high performance DoS/DDoS load analyzer built on top of multiple packet capture engines (NetFlow, IPFIX, sFLOW, netmap, PF_RING, PCAP). %prep rm -rf fastnetmon-tree mkdir fastnetmon-tree mkdir fastnetmon-tree/opt tar -xvvf /root/rpmbuild/SOURCES/archive.tar.gz -C fastnetmon-tree/opt # Copy service scripts mkdir fastnetmon-tree/etc cp /root/rpmbuild/SOURCES/systemd_init fastnetmon-tree/etc cp /root/rpmbuild/SOURCES/fastnetmon.conf fastnetmon-tree/etc %build # We do not build anything exit 0 %install mkdir %{buildroot}/opt cp -R fastnetmon-tree/opt/* %{buildroot}/opt chmod 755 %{buildroot}/opt/fastnetmon/fastnetmon chmod 755 %{buildroot}/opt/fastnetmon/fastnetmon_client # install init script install -p -D -m 0755 fastnetmon-tree/etc/systemd_init %{buildroot}%{_sysconfdir}/systemd/system/fastnetmon.service # install config install -p -D -m 0644 fastnetmon-tree/etc/fastnetmon.conf %{buildroot}%{fastnetmon_config_path} # Create log folder install -p -d -m 0700 %{buildroot}%{fastnetmon_attackdir} exit 0 %pre exit 0 %post %systemd_post fastnetmon.service if [ $1 -eq 1 ]; then # It's install # Enable autostart /usr/bin/systemctl enable fastnetmon.service /usr/bin/systemctl start fastnetmon.service fi #if [ $1 -eq 2 ]; then # upgrade #/sbin/service %{name} restart >/dev/null 2>&1 #fi %preun %systemd_preun fastnetmon.service %postun %systemd_postun_with_restart fastnetmon.service %files #%doc LICENSE CHANGES README {files_list} %{_sysconfdir}/systemd/system %config(noreplace) %{_sysconfdir}/fastnetmon.conf %attr(700,%{fastnetmon_user},%{fastnetmon_group}) %dir %{fastnetmon_attackdir} %changelog * Mon Mar 23 2015 Pavel Odintsov - 1.1.1-1 - First RPM package release DOC my $spec_file = <<'DOC'; # # Pre/post params: https://fedoraproject.org/wiki/Packaging:ScriptletSnippets # %global fastnetmon_attackdir %{_localstatedir}/log/fastnetmon_attacks %global fastnetmon_user root %global fastnetmon_group %{fastnetmon_user} %global fastnetmon_config_path %{_sysconfdir}/fastnetmon.conf Name: fastnetmon Version: 1.1.3 Release: 1%{?dist} Summary: A high performance DoS/DDoS load analyzer built on top of multiple packet capture engines (NetFlow, IPFIX, sFLOW, netmap, PF_RING, PCAP). Group: System Environment/Daemons License: GPLv2 URL: https://github.com/FastVPSEestiOu/fastnetmon # Top level fodler inside archive should be named as "fastnetmon-1.1.1" Source0: http://178.62.227.110/fastnetmon_binary_repository/test_binary_builds/this_fake_path_do_not_check_it/archive.tar.gz # Disable any sort of dynamic dependency detection for our own custom bunch of binaries AutoReq: no AutoProv: no Requires: libpcap, numactl, libicu Requires(pre): shadow-utils Requires(post): chkconfig Requires(preun): chkconfig, initscripts Requires(postun): initscripts Provides: fastnetmon %description A high performance DoS/DDoS load analyzer built on top of multiple packet capture engines (NetFlow, IPFIX, sFLOW, netmap, PF_RING, PCAP). %prep rm -rf fastnetmon-tree mkdir fastnetmon-tree mkdir fastnetmon-tree/opt tar -xvvf /root/rpmbuild/SOURCES/archive.tar.gz -C fastnetmon-tree/opt # Copy service scripts mkdir fastnetmon-tree/etc cp /root/rpmbuild/SOURCES/system_v_init fastnetmon-tree/etc cp /root/rpmbuild/SOURCES/fastnetmon.conf fastnetmon-tree/etc %build # We do not build anything exit 0 %install mkdir %{buildroot}/opt cp -R fastnetmon-tree/opt/* %{buildroot}/opt chmod 755 %{buildroot}/opt/fastnetmon/fastnetmon chmod 755 %{buildroot}/opt/fastnetmon/fastnetmon_client # install init script install -p -D -m 0755 fastnetmon-tree/etc/system_v_init %{buildroot}%{_initrddir}/fastnetmon # install config install -p -D -m 0644 fastnetmon-tree/etc/fastnetmon.conf %{buildroot}%{fastnetmon_config_path} # Create log folder install -p -d -m 0700 %{buildroot}%{fastnetmon_attackdir} exit 0 %pre exit 0 %post if [ $1 -eq 1 ]; then # It's install /sbin/chkconfig --add %{name} /sbin/chkconfig %{name} on /sbin/service %{name} start fi #if [ $1 -eq 2 ]; then # upgrade #/sbin/service %{name} restart >/dev/null 2>&1 #fi %preun # Pre remove if [ $1 -eq 0 ]; then # Uninstall # Stops fastnetmon and disable it loading at startup /sbin/service %{name} stop >/dev/null 2>&1 /sbin/chkconfig --del %{name} fi %postun # Post remove %files #%doc LICENSE CHANGES README {files_list} %{_initrddir}/fastnetmon %config(noreplace) %{_sysconfdir}/fastnetmon.conf %attr(700,%{fastnetmon_user},%{fastnetmon_group}) %dir %{fastnetmon_attackdir} %changelog * Mon Mar 23 2015 Pavel Odintsov - 1.1.1-1 - First RPM package release DOC my $selected_spec_file = $spec_file; # For CentOS we use systemd if ($archive_name =~ m/centos-7/) { $selected_spec_file = $systemd_spec_file; } my $joined_file_list = join "\n", @files_list; $selected_spec_file =~ s/\{files_list\}/$joined_file_list/; open my $fl, ">", "generated_spec_file.spec" or die "Can't create spec file\n"; print {$fl} $selected_spec_file; system("rpmbuild -bb generated_spec_file.spec"); mkdir "/tmp/result_data"; `cp /root/rpmbuild/RPMS/x86_64/* /tmp/result_data`; } sub build_deb_package { print "We will build deb from $archive_name\n"; my $fastnetmon_systemd_unit = <<'DOC'; [Unit] Description=FastNetMon - DoS/DDoS analyzer with sflow/netflow/mirror support After=network.target remote-fs.target [Service] Type=forking ExecStart=/opt/fastnetmon/fastnetmon --daemonize PIDFile=/run/fastnetmon.pid #ExecReload=/bin/kill -s HUP $MAINPID #ExecStop=/bin/kill -s QUIT $MAINPID [Install] WantedBy=multi-user.target DOC my $fastnetmon_systemv_init = <<'DOC'; #!/bin/sh ### BEGIN INIT INFO # Provides: fastnetmon # Required-Start: $local_fs $remote_fs $network $syslog # Required-Stop: $local_fs $remote_fs $network $syslog # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Short-Description: Fast DDoS detection toolkit. # Description: Fast DDoS detection toolkit with sFLOW/Netflow/netmap/pf_ring support. ### END INIT INFO # test -r /etc/default/fastnetmon && . /etc/default/fastnetmon NAME="fastnetmon" . /lib/lsb/init-functions PIDFILE="/var/run/${NAME}.pid" DAEMON="/opt/fastnetmon/fastnetmon" DAEMON_OPTS="--daemonize" START_OPTS="--start --background --exec ${DAEMON} -- ${DAEMON_OPTS}" STOP_OPTS="--stop --pidfile ${PIDFILE}" STATUS_OPTS="--status --pidfile ${PIDFILE}" case "$1" in start) echo -n "Starting $NAME: " start-stop-daemon $START_OPTS echo "$NAME." ;; stop) echo -n "Stopping $NAME: " start-stop-daemon $STOP_OPTS rm -f $PIDFILE echo "$NAME." ;; restart) $0 stop sleep 2 $0 start ;; force-reload) $0 restart ;; # no support of status on Debian squeeze # status) # start-stop-daemon $STATUS_OPTS # ;; *) N=/etc/init.d/$NAME echo "Usage: $N {start|stop|restart}" >&2 exit 1 ;; esac exit 0 DOC # dpkg-deb: warning: '/tmp/tmp.gbd1VXGPQB/DEBIAN/control' contains user-defined field '#Standards-Version' my $fastnetmon_control_file = <<'DOC'; Package: fastnetmon Maintainer: Pavel Odintsov Section: misc Priority: optional Architecture: amd64 Version: 1.1.3 Depends: libpcap0.8, libnuma1 Description: Very fast DDoS analyzer with sflow/netflow/mirror support FastNetMon - A high performance DoS/DDoS attack sensor. DOC my $folder_for_build = `mktemp -d`; chomp $folder_for_build; unless (-e $folder_for_build) { die "Can't create temp folder\n"; } chdir $folder_for_build; mkdir "$folder_for_build/DEBIAN"; put_text_to_file("$folder_for_build/DEBIAN/control", $fastnetmon_control_file); # Create init files for different versions of Debian like OS mkdir "$folder_for_build/etc"; mkdir "$folder_for_build/etc/init.d"; put_text_to_file("$folder_for_build/etc/init.d/fastnetmon", $fastnetmon_systemv_init); chmod 0755, "$folder_for_build/etc/init.d/fastnetmon"; # systemd mkdir "$folder_for_build/lib"; mkdir "$folder_for_build/lib/systemd"; mkdir "$folder_for_build/lib/systemd/system"; put_text_to_file("$folder_for_build/lib/systemd/system/fastnetmon.service", $fastnetmon_systemd_unit); # Configuration file put_text_to_file("$folder_for_build/DEBIAN/conffiles", "etc/fastnetmon.conf\n"); # Create folder for config mkdir("$folder_for_build/etc"); print `wget --no-check-certificate https://raw.githubusercontent.com/FastVPSEestiOu/fastnetmon/master/src/fastnetmon.conf -O$folder_for_build/etc/fastnetmon.conf`; `cp $archive_name $folder_for_build/archive.tar.gz`; mkdir "$folder_for_build/opt"; print `tar -xf $folder_for_build/archive.tar.gz -C $folder_for_build/opt`; unlink("$folder_for_build/archive.tar.gz"); mkdir "/tmp/result_data"; system("dpkg-deb --build $folder_for_build /tmp/result_data/fastnetmon_package.deb"); } sub put_text_to_file { my ($path, $text) = @_; open my $fl, ">", $path or die "Can't open $! for writing\n"; print {$fl} $text; close $fl; } fastnetmon-1.1.3+dfsg/src/scripts/build_libary_bundle.pl000077500000000000000000000052671313534057500234410ustar00rootroot00000000000000#!/usr/bin/perl use strict; use warnings; use File::Copy; use File::Basename; use File::Path qw(make_path); # # This script will produce binary archive withh all libraries which required for FastNetMon # my $archive_bundle_name = ''; if (scalar @ARGV == 1 && $ARGV[0]) { $archive_bundle_name = $ARGV[0]; } else { $archive_bundle_name = '/tmp/fastnetmon_bundle.tar.gz'; } if (-e $archive_bundle_name) { print "Bundle file is already exists, remove it\n"; unlink $archive_bundle_name; } my $global_path = '/opt'; my $target_path = `mktemp -d`; chomp $target_path; unless (-e $target_path && -d $target_path) { die "Can't create target path\n"; } my @our_libraries = ( 'boost_1_58_0', 'gcc520', 'json-c-0.12', 'libhiredis_0_13', 'log4cpp1.1.1', 'luajit_2.0.4', 'ndpi', 'pf_ring_6.0.3' ); for my $library (@our_libraries) { my $library_path = "$global_path/$library"; unless (-e $library_path) { die "Can't find library $library please check\n"; } print "Library: $library\n"; my @files = `find $library_path`; for my $file_full_path (@files) { chomp $file_full_path; if ($file_full_path =~ /\.so[\.\d]*$/) { my $dir_name = dirname($file_full_path); my $file_name = basename($file_full_path); print "$dir_name $file_name\n"; my $target_full_path = $file_full_path; $target_full_path =~ s/^$global_path/$target_path/; # Create target folder my $target_full_folder_path = $dir_name; $target_full_folder_path =~ s/^$global_path/$target_path/; unless (-e $target_full_folder_path) { print "Create folder $target_full_folder_path\n"; make_path( $target_full_folder_path ); } if (-l $file_full_path) { my $symlink_target_name = readlink($file_full_path); print "We have symlink which aims to $symlink_target_name\n"; # This way we copy symlinks symlink($symlink_target_name, $target_full_path); } else { copy($file_full_path, $target_full_folder_path); } } } } # manually handle toolkit itself mkdir "$target_path/fastnetmon"; copy("$global_path/fastnetmon/fastnetmon", "$target_path/fastnetmon/fastnetmon"); copy("$global_path/fastnetmon/fastnetmon_client", "$target_path/fastnetmon/fastnetmon_client"); # Set exec flag chmod 0755, "$target_path/fastnetmon/fastnetmon"; chmod 0755, "$target_path/fastnetmon/fastnetmon_client"; `tar -cpzf $archive_bundle_name -C $target_path ./`; print "We have created bundle $archive_bundle_name\n"; fastnetmon-1.1.3+dfsg/src/scripts/enable_passthrough_for_pcie_nic_to_kvm_vm.pl000066400000000000000000000063201313534057500300700ustar00rootroot00000000000000#!/usr/bin/perl use strict; use warnings; use Data::Dumper; my $vm_name = "fastnetmonvm.fastvps.ru"; execute_precheck(); execute_detach(); sub execute_precheck { my $cmdline = `cat /proc/cmdline`; chomp $cmdline; unless ($cmdline =~ /intel_iommu=on/) { print "Please add intel_iommu=on to kernel params\n"; print "You could do it in file /etc/default/grub\n"; print 'With param: GRUB_CMDLINE_LINUX_DEFAULT="intel_iommu=on"', "\n"; print "update-grub\n"; print "reboot\n"; exit(1); } my $conf_path = '/etc/modprobe.d/vfio_iommu_type1.conf'; # Debian Jessie, 3.16, Intel(R) Core(TM) i7-3820 CPU @ 3.60GHz desktop # Could be fixed in runtime: # echo 1 > /sys/module/vfio_iommu_type1/parameters/allow_unsafe_interrupts unless (-e $conf_path) { print "Please apply work around for error\n"; print "vfio_iommu_type1_attach_group: No interrupt remapping support.\n\n"; print "echo \"options vfio_iommu_type1 allow_unsafe_interrupts=1\" > /etc/modprobe.d/vfio_iommu_type1.conf"; print "\n"; print "And reboot server \n"; exit(0); } } sub execute_detach { # 03:00.0 Ethernet controller: Intel Corporation 82599ES 10-Gigabit SFI/SFP+ Network Connection (rev 01) my @lspci = `lspci`; chomp @lspci; # We process only Ethernet devices @lspci = grep {/Ethernet/} @lspci; @lspci = grep {/82599/} @lspci; my @nic_addresses = (); for my $nic (@lspci) { if ($nic =~ /(\d+\:\d+\.\d+)/) { push @nic_addresses, $1; } } my @virsh_full_addresses = (); for my $nic_address (@nic_addresses) { my $nic_address_in_virsh_format = $nic_address; $nic_address_in_virsh_format =~ s/[\:\.]/_/; my $virsh_address_full_format = `virsh nodedev-list | grep '$nic_address_in_virsh_format'`; chomp $virsh_address_full_format; push @virsh_full_addresses, $virsh_address_full_format; } # We use hash because multy port NICs could produce multiple equal address groups my $xml_blocks = {}; print "Detach NICs from the system\n"; for my $virsh_address (@virsh_full_addresses) { my $output = `virsh nodedev-dettach $virsh_address 2>&1`; chomp $output; if ($? != 0) { die "virsh nodedev-dettach failed with output: $output\n"; } #

my @xml_address_data = `virsh nodedev-dumpxml $virsh_address | grep address`; chomp @xml_address_data; for my $xml_line (@xml_address_data) { # cleanup $xml_line =~ s/^\s+//g; $xml_line =~ s/\s+$//g; $xml_blocks->{ $xml_line } = 1; } } my $target_xml = ''; for my $address_block (keys %$xml_blocks) { $target_xml .= "$address_block\n"; } print "Please run virsh edit $vm_name and insert this xml to devices block\n\n"; print $target_xml, "\n"; print "After this please execute virsh destroy $vm_name and virsh start $vm_name for applying changes\n"; } fastnetmon-1.1.3+dfsg/src/scripts/exabgp_network_collector.conf000066400000000000000000000010751313534057500250340ustar00rootroot00000000000000group Core_v4 { hold-time 180; local-as 65000; peer-as 65000; router-id 10.0.129.2; #graceful-restart 1200; neighbor 10.0.131.2 { local-address 10.0.129.2; description "ExaBGP route server client"; # Do not establish outgoing connection # passive; process stdout { neighbor-changes; receive { parsed; update; } encoder json; run /usr/local/bin/bgp_network_collector.py; } } } fastnetmon-1.1.3+dfsg/src/scripts/fastnetmon_notify.py000077500000000000000000000044621313534057500232260ustar00rootroot00000000000000#!/usr/bin/python import smtplib import sys from sys import stdin import optparse import sys import logging LOG_FILE = "/var/log/fastnetmon-notify.log" MAIL_HOSTNAME="localhost" MAIL_FROM="infra@example.com" MAIL_TO="infra@example.com" logger = logging.getLogger("DaemonLog") logger.setLevel(logging.INFO) formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") handler = logging.FileHandler(LOG_FILE) handler.setFormatter(formatter) logger.addHandler(handler) client_ip_as_string=sys.argv[1] data_direction=sys.argv[2] pps_as_string=int(sys.argv[3]) action=sys.argv[4] logger.info(" - " . join(sys.argv)) def mail(subject, body): fromaddr = MAIL_FROM toaddrs = [MAIL_TO] # Add the From: and To: headers at the start! headers = ("From: %s\r\nTo: %s\r\nSubject: %s\r\n\r\n" % ( fromaddr, ", ".join(toaddrs), subject ) ) msg = headers + body server = smtplib.SMTP(MAIL_HOSTNAME) #server.set_debuglevel(1) server.sendmail(fromaddr, toaddrs, msg) server.quit() if action == "unban": subject = "Fastnetmon Guard: IP %(client_ip_as_string)s unblocked because %(data_direction)s attack with power %(pps_as_string)d pps" % { 'client_ip_as_string': client_ip_as_string, 'data_direction': data_direction, 'pps_as_string' : pps_as_string, 'action' : action } mail(subject, "unban") sys.exit(0) elif action == "ban": subject = "Fastnetmon Guard: IP %(client_ip_as_string)s blocked because %(data_direction)s attack with power %(pps_as_string)d pps" % { 'client_ip_as_string': client_ip_as_string, 'data_direction': data_direction, 'pps_as_string' : pps_as_string, 'action' : action } body = "".join(sys.stdin.readlines()) mail(subject, body) sys.exit(0) elif action == "attack_details": subject = "Fastnetmon Guard: IP %(client_ip_as_string)s blocked because %(data_direction)s attack with power %(pps_as_string)d pps" % { 'client_ip_as_string': client_ip_as_string, 'data_direction': data_direction, 'pps_as_string' : pps_as_string, 'action' : action } body = "".join(sys.stdin.readlines()) mail(subject, body) sys.exit(0) else: sys.exit(0) fastnetmon-1.1.3+dfsg/src/scripts/install_binary.pl000077500000000000000000000203201313534057500224440ustar00rootroot00000000000000#!/usr/bin/perl use strict; use warnings; use Getopt::Long; my $start_time = time(); my $install_log_path = '/tmp/fastnetmon_install.log'; my $distro_type = ''; my $distro_version = ''; my $distro_architecture = ''; # Used for VyOS and different appliances based on rpm/deb my $appliance_name = ''; # So, you could disable this option but without this feature we could not improve FastNetMon for your distribution my $do_not_track_me = ''; my $cpus_number = get_logical_cpus_number(); main(); sub get_logical_cpus_number { my @cpuinfo = `cat /proc/cpuinfo`; chomp @cpuinfo; my $cpus_number = scalar grep {/processor/} @cpuinfo; return $cpus_number; } ### Functions start here sub main { detect_distribution(); # Refresh information about packages init_package_manager(); send_tracking_information('started'); install_fastnetmon(); my $install_time = time() - $start_time; my $pretty_install_time_in_minutes = sprintf("%.2f", $install_time / 60); print "We have installed project in $pretty_install_time_in_minutes minutes\n"; } sub send_tracking_information { my $step = shift; unless ($do_not_track_me) { my $stats_url = "http://178.62.227.110/new_fastnetmon_installation"; my $post_data = "distro_type=$distro_type&distro_version=$distro_version&distro_architecture=$distro_architecture&step=$step"; my $user_agent = 'FastNetMon install tracker v1'; `wget --post-data="$post_data" --user-agent="$user_agent" -q '$stats_url'`; } } sub exec_command { my $command = shift; open my $fl, ">>", $install_log_path; print {$fl} "We are calling command: $command\n\n"; my $output = `$command 2>&1 >> $install_log_path`; print {$fl} "Command finished with code $?\n\n"; if ($? == 0) { return 1; } else { return ''; } } sub get_sha1_sum { my $path = shift; my $output = `sha1sum $path`; chomp $output; my ($sha1) = ($output =~ m/^(\w+)\s+/); return $sha1; } sub download_file { my ($url, $path, $expected_sha1_checksumm) = @_; `wget --quiet '$url' -O$path`; if ($? != 0) { print "We can't download archive $url correctly\n"; return ''; } if ($expected_sha1_checksumm) { if (get_sha1_sum($path) eq $expected_sha1_checksumm) { return 1; } else { print "Downloaded archive has incorrect sha1\n"; return ''; } } else { return 1; } } sub init_package_manager { print "Update package manager cache\n"; if ($distro_type eq 'debian' or $distro_type eq 'ubuntu') { exec_command("apt-get update"); } } sub read_file { my $file_name = shift; my $res = open my $fl, "<", $file_name; unless ($res) { return ""; } my $content = join '', <$fl>; chomp $content; return $content; } # Detect operating system of this machine sub detect_distribution { # We use following global variables here: # $distro_type, $distro_version, $appliance_name # x86_64 or i686 $distro_architecture = `uname -m`; chomp $distro_architecture; if (-e "/etc/debian_version") { # Well, on this step it could be Ubuntu or Debian # We need check issue for more details my @issue = `cat /etc/issue`; chomp @issue; my $issue_first_line = $issue[0]; # Possible /etc/issue contents: # Debian GNU/Linux 8 \n \l # Ubuntu 14.04.2 LTS \n \l # Welcome to VyOS - \n \l if ($issue_first_line =~ m/Debian/) { $distro_type = 'debian'; $distro_version = `cat /etc/debian_version`; chomp $distro_version; # Debian 6 example: 6.0.10 # We will try transform it to decimal number if ($distro_version =~ /^(\d+\.\d+)\.\d+$/) { $distro_version = $1; } } elsif ($issue_first_line =~ m/Ubuntu (\d+(?:\.\d+)?)/) { $distro_type = 'ubuntu'; $distro_version = $1; } elsif ($issue_first_line =~ m/VyOS/) { # Yes, VyOS is a Debian $distro_type = 'debian'; $appliance_name = 'vyos'; my $vyos_distro_version = `cat /etc/debian_version`; chomp $vyos_distro_version; # VyOS have strange version and we should fix it if ($vyos_distro_version =~ /^(\d+)\.\d+\.\d+$/) { $distro_version = $1; } } } if (-e "/etc/redhat-release") { $distro_type = 'centos'; my $distro_version_raw = `cat /etc/redhat-release`; chomp $distro_version_raw; # CentOS 6: # CentOS release 6.6 (Final) # CentOS 7: # CentOS Linux release 7.0.1406 (Core) # Fedora release 21 (Twenty One) if ($distro_version_raw =~ /(\d+)/) { $distro_version = $1; } } if (-e "/etc/gentoo-release") { $distro_type = 'gentoo'; my $distro_version_raw = `cat /etc/gentoo-release`; chomp $distro_version_raw; } unless ($distro_type) { die "This distro is unsupported, please do manual install"; } } sub apt_get { my @packages_list = @_; # We install one package per apt-get call because installing multiple packages in one time could fail of one package is broken for my $package (@packages_list) { exec_command("apt-get install -y --force-yes $package"); if ($? != 0) { print "Package '$package' install failed with code $?\n" } } } sub yum { my @packages_list = @_; for my $package (@packages_list) { exec_command("yum install -y $package"); if ($? != 0) { print "Package '$package' install failed with code $?\n"; } } } sub install_fastnetmon { print "Install FastNetMon dependency list\n"; my $repository_address = 'http://178.62.227.110/fastnetmon_binary_repository/test_package_build'; my $file_name = ''; if ($distro_type eq 'ubuntu') { $file_name = "ubuntu-$distro_version-x86_64.deb"; } elsif ($distro_type eq 'debian') { my $our_own_debian_version = $distro_version; # Convert 6.x to 6.0 $our_own_debian_version =~ s/\.\d+/.0/; $file_name = "debian-$our_own_debian_version-x86_64.deb"; } elsif ($distro_type eq 'centos') { my $our_own_centos_version = int($distro_version); $file_name = "centos-$our_own_centos_version-x86_64.rpm"; } else { die "Sorry, we haven't binary packages for your distribution\n"; } # http://178.62.227.110/fastnetmon_binary_repository/test_package_build/fastnetmon-git-447aa5b86bb5a248e310c15a4d5945e72594d6cf-centos-6-x86_64_x86_64.rpm my $git_version = '447aa5b86bb5a248e310c15a4d5945e72594d6cf'; my $bundle_file_name = "fastnetmon-git-$git_version-$file_name"; my $full_url = "$repository_address/$bundle_file_name"; print "I will try to download file from $full_url\n"; my $fastnetmon_download_result = download_file($full_url, "/tmp/$bundle_file_name"); unless ($fastnetmon_download_result) { die "Can't download FastNetMon distribution\n"; } if ($distro_type eq 'debian') { if (int($distro_version) == 6) { apt_get('libpcap0.8', 'libnuma1', 'libicu44'); } if (int($distro_version) == 7) { apt_get('libpcap0.8', 'libnuma1', 'libicu48'); } if (int($distro_version) == 8) { apt_get('libpcap0.8', 'libnuma1', 'libicu52'); } } if ($distro_type eq 'ubuntu') { if ($distro_version eq '14.04') { apt_get('libicu52', 'libpcap0.8', 'libnuma1'); } } if ($distro_type eq 'centos') { # For CentOS 6 and 7 we are installind all deps with yum } if ($distro_type eq 'centos') { yum("/tmp/$bundle_file_name"); } elsif ($distro_type eq 'debian' or $distro_type eq 'ubuntu') { exec_command("dpkg -i /tmp/$bundle_file_name"); } print "If you have any issues, please check /var/log/fastnetmon.log file contents\n"; print "Please add your subnets in /etc/networks_list in CIDR format one subnet per line\n"; } fastnetmon-1.1.3+dfsg/src/scripts/reformat_code_with_clang_format.sh000077500000000000000000000005511313534057500260150ustar00rootroot00000000000000#!env bash # Exclude: # netmap_plugin/netmap_includes/net/netmap.h # netmap_plugin/netmap_includes/net/netmap_user.h # libpatricia/patricia.c # libpatricia/patricia.h # for i in `find . |egrep "\.cpp$"`; do clang-format -i $i ;done # for i in `find . |egrep "\.c$"`; do clang-format -i $i ;done # for i in `find . |egrep "\.h$"`; do clang-format -i $i ;done fastnetmon-1.1.3+dfsg/src/scripts/reset_redis_stats000077500000000000000000000000611313534057500225460ustar00rootroot00000000000000#!/usr/bin/env bash /usr/bin/redis-cli FLUSHALL fastnetmon-1.1.3+dfsg/src/sflow_hooks.lua000066400000000000000000000132111313534057500204440ustar00rootroot00000000000000local json = require("json") -- We have this library bundled only in luajit: -- g++ lua_integration.cpp -lluajit-5.1 -- Before production use, please call your code with luajit CLI local ffi = require("ffi") -- Load declaration from the inside separate header file -- This code should be in sync with https://github.com/FastVPSEestiOu/fastnetmon/blob/master/src/sflow_plugin/sflow_data.h -- We have changed all defines to actual values ffi.cdef([[ typedef unsigned char u_char; typedef long time_t; typedef struct _SFLIf_counters { uint32_t ifIndex; uint32_t ifType; uint64_t ifSpeed; uint32_t ifDirection; /* Derived from MAU MIB (RFC 2668) 0 = unknown, 1 = full-duplex, 2 = half-duplex, 3 = in, 4 = out */ uint32_t ifStatus; /* bit field with the following bits assigned: bit 0 = ifAdminStatus (0 = down, 1 = up) bit 1 = ifOperStatus (0 = down, 1 = up) */ uint64_t ifInOctets; uint32_t ifInUcastPkts; uint32_t ifInMulticastPkts; uint32_t ifInBroadcastPkts; uint32_t ifInDiscards; uint32_t ifInErrors; uint32_t ifInUnknownProtos; uint64_t ifOutOctets; uint32_t ifOutUcastPkts; uint32_t ifOutMulticastPkts; uint32_t ifOutBroadcastPkts; uint32_t ifOutDiscards; uint32_t ifOutErrors; uint32_t ifPromiscuousMode; } SFLIf_counters; typedef struct { uint32_t addr; } SFLIPv4; typedef struct { u_char addr[16]; } SFLIPv6; typedef union _SFLAddress_value { SFLIPv4 ip_v4; SFLIPv6 ip_v6; } SFLAddress_value; typedef struct _SFLAddress { uint32_t type; /* enum SFLAddress_type */ SFLAddress_value address; } SFLAddress; typedef struct _SFSample { SFLAddress sourceIP; SFLAddress agent_addr; uint32_t agentSubId; /* the raw pdu */ uint8_t* rawSample; uint32_t rawSampleLen; uint8_t* endp; time_t pcapTimestamp; /* decode cursor */ uint32_t* datap; uint32_t datagramVersion; uint32_t sampleType; uint32_t elementType; uint32_t ds_class; uint32_t ds_index; /* generic interface counter sample */ SFLIf_counters ifCounters; /* sample stream info */ uint32_t sysUpTime; uint32_t sequenceNo; uint32_t sampledPacketSize; uint32_t samplesGenerated; uint32_t meanSkipCount; uint32_t samplePool; uint32_t dropEvents; /* the sampled header */ uint32_t packet_data_tag; uint32_t headerProtocol; uint8_t* header; int headerLen; uint32_t stripped; /* header decode */ int gotIPV4; int gotIPV4Struct; int offsetToIPV4; int gotIPV6; int gotIPV6Struct; int offsetToIPV6; int offsetToPayload; SFLAddress ipsrc; SFLAddress ipdst; uint32_t dcd_ipProtocol; uint32_t dcd_ipTos; uint32_t dcd_ipTTL; uint32_t dcd_sport; uint32_t dcd_dport; uint32_t dcd_tcpFlags; uint32_t ip_fragmentOffset; uint32_t udp_pduLen; /* ports */ uint32_t inputPortFormat; uint32_t outputPortFormat; uint32_t inputPort; uint32_t outputPort; /* ethernet */ uint32_t eth_type; uint32_t eth_len; uint8_t eth_src[8]; uint8_t eth_dst[8]; /* vlan */ uint32_t in_vlan; uint32_t in_priority; uint32_t internalPriority; uint32_t out_vlan; uint32_t out_priority; int vlanFilterReject; /* extended data fields */ uint32_t num_extended; uint32_t extended_data_tag; /* IP forwarding info */ SFLAddress nextHop; uint32_t srcMask; uint32_t dstMask; /* BGP info */ SFLAddress bgp_nextHop; uint32_t my_as; uint32_t src_as; uint32_t src_peer_as; uint32_t dst_as_path_len; uint32_t* dst_as_path; /* note: version 4 dst as path segments just get printed, not stored here, however * the dst_peer and dst_as are filled in, since those are used for netflow encoding */ uint32_t dst_peer_as; uint32_t dst_as; uint32_t communities_len; uint32_t* communities; uint32_t localpref; /* user id */ uint32_t src_user_charset; uint32_t src_user_len; char src_user[200 + 1]; uint32_t dst_user_charset; uint32_t dst_user_len; char dst_user[200 + 1]; /* url */ uint32_t url_direction; uint32_t url_len; char url[200 + 1]; uint32_t host_len; char host[200 + 1]; /* mpls */ SFLAddress mpls_nextHop; /* nat */ SFLAddress nat_src; SFLAddress nat_dst; /* counter blocks */ uint32_t statsSamplingInterval; uint32_t counterBlockVersion; /* exception handler context */ //jmp_buf env; } SFSample; ]]) -- Load json file once local json_file = io.open("/usr/src/fastnetmon/src/tests/netflow_exclude.json", "r") local decoded = json.decode(json_file:read("*all")) function process_sflow(flow_agent_ip, flow) local sflow_t = ffi.typeof('SFSample*') local lua_sflow = ffi.cast(sflow_t, flow) --print ("We got this packets from: ", flow_agent_ip) -- TODO: PLEASE BE AWARE! Thid code will read json file for every packet --print ("Flow packets and bytes: ", lua_flow.flow_packets, lua_flow.flow_octets) print ("Agent IP", flow_agent_ip," in interface :", lua_sflow.inputPort, " out interface: ", lua_sflow.outputPort) for agent_ip, ports_table in pairs(decoded) do if agent_ip == flow_agent_ip then for port_number, port_description in pairs(ports_table) do if lua_sflow.outputPort == port_number then -- We found this port in ignore list return false end end end end --for k,v in pairs(decoded) do -- for kk, vv in pairs(v) do -- --print(k, kk, vv) -- end --end return true end fastnetmon-1.1.3+dfsg/src/sflow_plugin/000077500000000000000000000000001313534057500201165ustar00rootroot00000000000000fastnetmon-1.1.3+dfsg/src/sflow_plugin/sflow.h000066400000000000000000001373501313534057500214320ustar00rootroot00000000000000/* Copyright (c) 2002-2011 InMon Corp. Licensed under the terms of the InMon sFlow licence: */ /* http://www.inmon.com/technology/sflowlicense.txt */ /* ///////////////////////////////////////////////////////////////////////////////// /////////////////////// sFlow Sampling Packet Data Types //////////////////////// ///////////////////////////////////////////////////////////////////////////////// */ #ifndef SFLOW_H #define SFLOW_H 1 #if defined(__cplusplus) extern "C" { #endif typedef struct { uint32_t addr; } SFLIPv4; typedef struct { u_char addr[16]; } SFLIPv6; typedef union _SFLAddress_value { SFLIPv4 ip_v4; SFLIPv6 ip_v6; } SFLAddress_value; enum SFLAddress_type { SFLADDRESSTYPE_UNDEFINED = 0, SFLADDRESSTYPE_IP_V4 = 1, SFLADDRESSTYPE_IP_V6 = 2 }; typedef struct _SFLAddress { uint32_t type; /* enum SFLAddress_type */ SFLAddress_value address; } SFLAddress; /* Packet header data */ #define SFL_DEFAULT_HEADER_SIZE 128 #define SFL_DEFAULT_COLLECTOR_PORT 6343 #define SFL_DEFAULT_SAMPLING_RATE 400 /* The header protocol describes the format of the sampled header */ enum SFLHeader_protocol { SFLHEADER_ETHERNET_ISO8023 = 1, SFLHEADER_ISO88024_TOKENBUS = 2, SFLHEADER_ISO88025_TOKENRING = 3, SFLHEADER_FDDI = 4, SFLHEADER_FRAME_RELAY = 5, SFLHEADER_X25 = 6, SFLHEADER_PPP = 7, SFLHEADER_SMDS = 8, SFLHEADER_AAL5 = 9, SFLHEADER_AAL5_IP = 10, /* e.g. Cisco AAL5 mux */ SFLHEADER_IPv4 = 11, SFLHEADER_IPv6 = 12, SFLHEADER_MPLS = 13, SFLHEADER_POS = 14, SFLHEADER_IEEE80211MAC = 15, SFLHEADER_IEEE80211_AMPDU = 16, SFLHEADER_IEEE80211_AMSDU_SUBFRAME = 17 }; /* raw sampled header */ typedef struct _SFLSampled_header { uint32_t header_protocol; /* (enum SFLHeader_protocol) */ uint32_t frame_length; /* Original length of packet before sampling */ uint32_t stripped; /* header/trailer bytes stripped by sender */ uint32_t header_length; /* length of sampled header bytes to follow */ uint8_t* header_bytes; /* Header bytes */ } SFLSampled_header; /* decoded ethernet header */ typedef struct _SFLSampled_ethernet { uint32_t eth_len; /* The length of the MAC packet excluding lower layer encapsulations */ uint8_t src_mac[8]; /* 6 bytes + 2 pad */ uint8_t dst_mac[8]; uint32_t eth_type; } SFLSampled_ethernet; /* decoded IP version 4 header */ typedef struct _SFLSampled_ipv4 { uint32_t length; /* The length of the IP packet excluding lower layer encapsulations */ uint32_t protocol; /* IP Protocol type (for example, TCP = 6, UDP = 17) */ SFLIPv4 src_ip; /* Source IP Address */ SFLIPv4 dst_ip; /* Destination IP Address */ uint32_t src_port; /* TCP/UDP source port number or equivalent */ uint32_t dst_port; /* TCP/UDP destination port number or equivalent */ uint32_t tcp_flags; /* TCP flags */ uint32_t tos; /* IP type of service */ } SFLSampled_ipv4; /* decoded IP version 6 data */ typedef struct _SFLSampled_ipv6 { uint32_t length; /* The length of the IP packet excluding lower layer encapsulations */ uint32_t protocol; /* IP Protocol type (for example, TCP = 6, UDP = 17) */ SFLIPv6 src_ip; /* Source IP Address */ SFLIPv6 dst_ip; /* Destination IP Address */ uint32_t src_port; /* TCP/UDP source port number or equivalent */ uint32_t dst_port; /* TCP/UDP destination port number or equivalent */ uint32_t tcp_flags; /* TCP flags */ uint32_t priority; /* IP priority */ } SFLSampled_ipv6; /* Extended data types */ /* Extended switch data */ typedef struct _SFLExtended_switch { uint32_t src_vlan; /* The 802.1Q VLAN id of incomming frame */ uint32_t src_priority; /* The 802.1p priority */ uint32_t dst_vlan; /* The 802.1Q VLAN id of outgoing frame */ uint32_t dst_priority; /* The 802.1p priority */ } SFLExtended_switch; /* Extended router data */ typedef struct _SFLExtended_router { SFLAddress nexthop; /* IP address of next hop router */ uint32_t src_mask; /* Source address prefix mask bits */ uint32_t dst_mask; /* Destination address prefix mask bits */ } SFLExtended_router; /* Extended gateway data */ enum SFLExtended_as_path_segment_type { SFLEXTENDED_AS_SET = 1, /* Unordered set of ASs */ SFLEXTENDED_AS_SEQUENCE = 2 /* Ordered sequence of ASs */ }; typedef struct _SFLExtended_as_path_segment { uint32_t type; /* enum SFLExtended_as_path_segment_type */ uint32_t length; /* number of AS numbers in set/sequence */ union { uint32_t* set; uint32_t* seq; } as; } SFLExtended_as_path_segment; typedef struct _SFLExtended_gateway { SFLAddress nexthop; /* Address of the border router that should be used for the destination network */ uint32_t as; /* AS number for this gateway */ uint32_t src_as; /* AS number of source (origin) */ uint32_t src_peer_as; /* AS number of source peer */ uint32_t dst_as_path_segments; /* number of segments in path */ SFLExtended_as_path_segment* dst_as_path; /* list of seqs or sets */ uint32_t communities_length; /* number of communities */ uint32_t* communities; /* set of communities */ uint32_t localpref; /* LocalPref associated with this route */ } SFLExtended_gateway; typedef struct _SFLString { uint32_t len; char* str; } SFLString; /* Extended user data */ typedef struct _SFLExtended_user { uint32_t src_charset; /* MIBEnum value of character set used to encode a string - See RFC 2978 Where possible UTF-8 encoding (MIBEnum=106) should be used. A value of zero indicates an unknown encoding. */ SFLString src_user; uint32_t dst_charset; SFLString dst_user; } SFLExtended_user; /* Extended URL data */ enum SFLExtended_url_direction { SFLEXTENDED_URL_SRC = 1, /* URL is associated with source address */ SFLEXTENDED_URL_DST = 2 /* URL is associated with destination address */ }; typedef struct _SFLExtended_url { uint32_t direction; /* enum SFLExtended_url_direction */ SFLString url; /* URL associated with the packet flow. Must be URL encoded */ SFLString host; /* The host field from the HTTP header */ } SFLExtended_url; /* Extended MPLS data */ typedef struct _SFLLabelStack { uint32_t depth; uint32_t* stack; /* first entry is top of stack - see RFC 3032 for encoding */ } SFLLabelStack; typedef struct _SFLExtended_mpls { SFLAddress nextHop; /* Address of the next hop */ SFLLabelStack in_stack; SFLLabelStack out_stack; } SFLExtended_mpls; /* Extended NAT data Packet header records report addresses as seen at the sFlowDataSource. The extended_nat structure reports on translated source and/or destination addesses for this packet. If an address was not translated it should be equal to that reported for the header. */ typedef struct _SFLExtended_nat { SFLAddress src; /* Source address */ SFLAddress dst; /* Destination address */ } SFLExtended_nat; typedef struct _SFLExtended_nat_port { uint32_t src_port; uint32_t dst_port; } SFLExtended_nat_port; /* additional Extended MPLS stucts */ typedef struct _SFLExtended_mpls_tunnel { SFLString tunnel_lsp_name; /* Tunnel name */ uint32_t tunnel_id; /* Tunnel ID */ uint32_t tunnel_cos; /* Tunnel COS value */ } SFLExtended_mpls_tunnel; typedef struct _SFLExtended_mpls_vc { SFLString vc_instance_name; /* VC instance name */ uint32_t vll_vc_id; /* VLL/VC instance ID */ uint32_t vc_label_cos; /* VC Label COS value */ } SFLExtended_mpls_vc; /* Extended MPLS FEC - Definitions from MPLS-FTN-STD-MIB mplsFTNTable */ typedef struct _SFLExtended_mpls_FTN { SFLString mplsFTNDescr; uint32_t mplsFTNMask; } SFLExtended_mpls_FTN; /* Extended MPLS LVP FEC - Definition from MPLS-LDP-STD-MIB mplsFecTable Note: mplsFecAddrType, mplsFecAddr information available from packet header */ typedef struct _SFLExtended_mpls_LDP_FEC { uint32_t mplsFecAddrPrefixLength; } SFLExtended_mpls_LDP_FEC; /* Extended VLAN tunnel information Record outer VLAN encapsulations that have been stripped. extended_vlantunnel information should only be reported if all the following conditions are satisfied: 1. The packet has nested vlan tags, AND 2. The reporting device is VLAN aware, AND 3. One or more VLAN tags have been stripped, either because they represent proprietary encapsulations, or because switch hardware automatically strips the outer VLAN encapsulation. Reporting extended_vlantunnel information is not a substitute for reporting extended_switch information. extended_switch data must always be reported to describe the ingress/egress VLAN information for the packet. The extended_vlantunnel information only applies to nested VLAN tags, and then only when one or more tags has been stripped. */ typedef SFLLabelStack SFLVlanStack; typedef struct _SFLExtended_vlan_tunnel { SFLVlanStack stack; /* List of stripped 802.1Q TPID/TCI layers. Each TPID,TCI pair is represented as a single 32 bit integer. Layers listed from outermost to innermost. */ } SFLExtended_vlan_tunnel; /* ////////////////// IEEE 802.11 Extension structs //////////////////// The 4-byte cipher_suite identifier follows the format of the cipher suite selector value from the 802.11i (TKIP/CCMP amendment to 802.11i) The most significant three bytes contain the OUI and the least significant byte contains the Suite Type. The currently assigned values are: OUI |Suite type |Meaning ---------------------------------------------------- 00-0F-AC | 0 | Use group cipher suite 00-0F-AC | 1 | WEP-40 00-0F-AC | 2 | TKIP 00-0F-AC | 3 | Reserved 00-0F-AC | 4 | CCMP 00-0F-AC | 5 | WEP-104 00-0F-AC | 6-255 | Reserved Vendor OUI | Other | Vendor specific Other | Any | Reserved ---------------------------------------------------- */ typedef uint32_t SFLCipherSuite; /* Extended wifi Payload Used to provide unencrypted version of 802.11 MAC data. If the MAC data is not encrypted then the agent must not include an extended_wifi_payload structure. If 802.11 MAC data is encrypted then the sampled_header structure should only contain the MAC header (since encrypted data cannot be decoded by the sFlow receiver). If the sFlow agent has access to the unencrypted payload, it should add an extended_wifi_payload structure containing the unencrypted data bytes from the sampled packet header, starting at the beginning of the 802.2 LLC and not including any trailing encryption footers. */ /* opaque = flow_data; enterprise = 0; format = 1013 */ typedef struct _SFLExtended_wifi_payload { SFLCipherSuite cipherSuite; SFLSampled_header header; } SFLExtended_wifi_payload; typedef enum { IEEE80211_A = 1, IEEE80211_B = 2, IEEE80211_G = 3, IEEE80211_N = 4, } SFL_IEEE80211_version; /* opaque = flow_data; enterprise = 0; format = 1014 */ #define SFL_MAX_SSID_LEN 256 typedef struct _SFLExtended_wifi_rx { uint32_t ssid_len; char* ssid; char bssid[6]; /* BSSID */ SFL_IEEE80211_version version; /* version */ uint32_t channel; /* channel number */ uint64_t speed; uint32_t rsni; /* received signal to noise ratio, see dot11FrameRprtRSNI */ uint32_t rcpi; /* received channel power, see dot11FrameRprtLastRCPI */ uint32_t packet_duration_us; /* amount of time that the successfully received pkt occupied RF medium.*/ } SFLExtended_wifi_rx; /* opaque = flow_data; enterprise = 0; format = 1015 */ typedef struct _SFLExtended_wifi_tx { uint32_t ssid_len; char* ssid; /* SSID string */ char bssid[6]; /* BSSID */ SFL_IEEE80211_version version; /* version */ uint32_t transmissions; /* number of transmissions for sampled packet. 0 = unkown 1 = packet was successfully transmitted on first attempt n > 1 = n - 1 retransmissions */ uint32_t packet_duration_us; /* amount of time that the successfully transmitted packet occupied the RF medium */ uint32_t retrans_duration_us; /* amount of time that failed transmission attempts occupied the RF medium */ uint32_t channel; /* channel number */ uint64_t speed; uint32_t power_mw; /* transmit power in mW. */ } SFLExtended_wifi_tx; /* Extended 802.11 Aggregation Data */ /* A flow_sample of an aggregated frame would consist of a packet header for the whole frame + any other extended structures that apply (e.g. 80211_tx/rx etc.) + an extended_wifi_aggregation structure which would contain an array of pdu structures (one for each PDU in the aggregate). A pdu is simply an array of flow records, in the simplest case a packet header for each PDU, but extended structures could be included as well. */ /* opaque = flow_data; enterprise = 0; format = 1016 */ struct _SFLFlow_Pdu; /* forward decl */ typedef struct _SFLExtended_aggregation { uint32_t num_pdus; struct _SFFlow_Pdu* pdus; } SFLExtended_aggregation; /* Extended socket information, Must be filled in for all application transactions associated with a network socket Omit if transaction associated with non-network IPC */ /* IPv4 Socket */ /* opaque = flow_data; enterprise = 0; format = 2100 */ typedef struct _SFLExtended_socket_ipv4 { uint32_t protocol; /* IP Protocol (e.g. TCP = 6, UDP = 17) */ SFLIPv4 local_ip; /* local IP address */ SFLIPv4 remote_ip; /* remote IP address */ uint32_t local_port; /* TCP/UDP local port number or equivalent */ uint32_t remote_port; /* TCP/UDP remote port number of equivalent */ } SFLExtended_socket_ipv4; #define XDRSIZ_SFLEXTENDED_SOCKET4 20 /* IPv6 Socket */ /* opaque = flow_data; enterprise = 0; format = 2101 */ typedef struct _SFLExtended_socket_ipv6 { uint32_t protocol; /* IP Protocol (e.g. TCP = 6, UDP = 17) */ SFLIPv6 local_ip; /* local IP address */ SFLIPv6 remote_ip; /* remote IP address */ uint32_t local_port; /* TCP/UDP local port number or equivalent */ uint32_t remote_port; /* TCP/UDP remote port number of equivalent */ } SFLExtended_socket_ipv6; #define XDRSIZ_SFLEXTENDED_SOCKET6 44 typedef enum { MEMCACHE_PROT_OTHER = 0, MEMCACHE_PROT_ASCII = 1, MEMCACHE_PROT_BINARY = 2, } SFLMemcache_prot; typedef enum { MEMCACHE_CMD_OTHER = 0, MEMCACHE_CMD_SET = 1, MEMCACHE_CMD_ADD = 2, MEMCACHE_CMD_REPLACE = 3, MEMCACHE_CMD_APPEND = 4, MEMCACHE_CMD_PREPEND = 5, MEMCACHE_CMD_CAS = 6, MEMCACHE_CMD_GET = 7, MEMCACHE_CMD_GETS = 8, MEMCACHE_CMD_INCR = 9, MEMCACHE_CMD_DECR = 10, MEMCACHE_CMD_DELETE = 11, MEMCACHE_CMD_STATS = 12, MEMCACHE_CMD_FLUSH = 13, MEMCACHE_CMD_VERSION = 14, MEMCACHE_CMD_QUIT = 15, MEMCACHE_CMD_TOUCH = 16, } SFLMemcache_cmd; enum SFLMemcache_operation_status { MEMCACHE_OP_UNKNOWN = 0, MEMCACHE_OP_OK = 1, MEMCACHE_OP_ERROR = 2, MEMCACHE_OP_CLIENT_ERROR = 3, MEMCACHE_OP_SERVER_ERROR = 4, MEMCACHE_OP_STORED = 5, MEMCACHE_OP_NOT_STORED = 6, MEMCACHE_OP_EXISTS = 7, MEMCACHE_OP_NOT_FOUND = 8, MEMCACHE_OP_DELETED = 9, }; #define SFL_MAX_MEMCACHE_KEY 255 typedef struct _SFLSampled_memcache { uint32_t protocol; /* SFLMemcache_prot */ uint32_t command; /* SFLMemcache_cmd */ SFLString key; /* up to 255 chars */ uint32_t nkeys; uint32_t value_bytes; uint32_t duration_uS; uint32_t status; /* SFLMemcache_operation_status */ } SFLSampled_memcache; typedef enum { SFHTTP_OTHER = 0, SFHTTP_OPTIONS = 1, SFHTTP_GET = 2, SFHTTP_HEAD = 3, SFHTTP_POST = 4, SFHTTP_PUT = 5, SFHTTP_DELETE = 6, SFHTTP_TRACE = 7, SFHTTP_CONNECT = 8, } SFLHTTP_method; #define SFL_MAX_HTTP_URI 255 #define SFL_MAX_HTTP_HOST 64 #define SFL_MAX_HTTP_REFERRER 255 #define SFL_MAX_HTTP_USERAGENT 128 #define SFL_MAX_HTTP_XFF 64 #define SFL_MAX_HTTP_AUTHUSER 32 #define SFL_MAX_HTTP_MIMETYPE 64 typedef struct _SFLSampled_http { SFLHTTP_method method; uint32_t protocol; /* 1.1=1001 */ SFLString uri; /* URI exactly as it came from the client (up to 255 bytes) */ SFLString host; /* Host value from request header (<= 64 bytes) */ SFLString referrer; /* Referer value from request header (<=255 bytes) */ SFLString useragent; /* User-Agent value from request header (<= 128 bytes)*/ SFLString xff; /* X-Forwarded-For value from request header (<= 64 bytes)*/ SFLString authuser; /* RFC 1413 identity of user (<=32 bytes)*/ SFLString mimetype; /* Mime-Type (<=64 bytes) */ uint64_t req_bytes; /* Content-Length of request */ uint64_t resp_bytes; /* Content-Length of response */ uint32_t uS; /* duration of the operation (microseconds) */ uint32_t status; /* HTTP status code */ } SFLSampled_http; typedef enum { SFLAPP_SUCCESS = 0, SFLAPP_OTHER = 1, SFLAPP_TIMEOUT = 2, SFLAPP_INTERNAL_ERROR = 3, SFLAPP_BAD_REQUEST = 4, SFLAPP_FORBIDDEN = 5, SFLAPP_TOO_LARGE = 6, SFLAPP_NOT_IMPLEMENTED = 7, SFLAPP_NOT_FOUND = 8, SFLAPP_UNAVAILABLE = 9, SFLAPP_UNAUTHORIZED = 10, } EnumSFLAPPStatus; static const char* SFL_APP_STATUS_names[] = { "SUCCESS", "OTHER", "TIMEOUT", "INTERNAL_ERROR", "BAD_REQUEST", "FORBIDDEN", "TOO_LARGE", "NOT_IMPLEMENTED", "NOT_FOUND", "UNAVAILABLE", "UNATHORIZED" }; /* Operation context */ typedef struct { SFLString application; SFLString operation; /* type of operation (e.g. authorization, payment) */ SFLString attributes; /* specific attributes associated operation */ } SFLSampled_APP_CTXT; #define SFLAPP_MAX_APPLICATION_LEN 32 #define SFLAPP_MAX_OPERATION_LEN 32 #define SFLAPP_MAX_ATTRIBUTES_LEN 255 /* Sampled Enterprise Operation */ /* opaque = flow_data; enterprise = 0; format = 2202 */ typedef struct { SFLSampled_APP_CTXT context; /* attributes describing the operation */ SFLString status_descr; /* additional text describing status (e.g. "unknown client") */ uint64_t req_bytes; /* size of request body (exclude headers) */ uint64_t resp_bytes; /* size of response body (exclude headers) */ uint32_t duration_uS; /* duration of the operation (microseconds) */ EnumSFLAPPStatus status; /* status code */ } SFLSampled_APP; #define SFLAPP_MAX_STATUS_LEN 32 typedef struct { SFLString actor; } SFLSampled_APP_ACTOR; #define SFLAPP_MAX_ACTOR_LEN 64 typedef struct _SFLExtended_vni { uint32_t vni; /* virtual network identifier */ } SFLExtended_vni; typedef struct _SFLExtended_decap { uint32_t innerHeaderOffset; } SFLExtended_decap; enum SFLFlow_type_tag { /* enterprise = 0, format = ... */ SFLFLOW_HEADER = 1, /* Packet headers are sampled */ SFLFLOW_ETHERNET = 2, /* MAC layer information */ SFLFLOW_IPV4 = 3, /* IP version 4 data */ SFLFLOW_IPV6 = 4, /* IP version 6 data */ SFLFLOW_EX_SWITCH = 1001, /* Extended switch information */ SFLFLOW_EX_ROUTER = 1002, /* Extended router information */ SFLFLOW_EX_GATEWAY = 1003, /* Extended gateway router information */ SFLFLOW_EX_USER = 1004, /* Extended TACAS/RADIUS user information */ SFLFLOW_EX_URL = 1005, /* Extended URL information */ SFLFLOW_EX_MPLS = 1006, /* Extended MPLS information */ SFLFLOW_EX_NAT = 1007, /* Extended NAT information */ SFLFLOW_EX_MPLS_TUNNEL = 1008, /* additional MPLS information */ SFLFLOW_EX_MPLS_VC = 1009, SFLFLOW_EX_MPLS_FTN = 1010, SFLFLOW_EX_MPLS_LDP_FEC = 1011, SFLFLOW_EX_VLAN_TUNNEL = 1012, /* VLAN stack */ SFLFLOW_EX_80211_PAYLOAD = 1013, SFLFLOW_EX_80211_RX = 1014, SFLFLOW_EX_80211_TX = 1015, SFLFLOW_EX_AGGREGATION = 1016, SFLFLOW_EX_NAT_PORT = 1020, /* Extended NAT port information */ SFLFLOW_EX_L2_TUNNEL_OUT = 1021, /* http://sflow.org/sflow_tunnels.txt */ SFLFLOW_EX_L2_TUNNEL_IN = 1022, SFLFLOW_EX_IPV4_TUNNEL_OUT = 1023, SFLFLOW_EX_IPV4_TUNNEL_IN = 1024, SFLFLOW_EX_IPV6_TUNNEL_OUT = 1025, SFLFLOW_EX_IPV6_TUNNEL_IN = 1026, SFLFLOW_EX_DECAP_OUT = 1027, SFLFLOW_EX_DECAP_IN = 1028, SFLFLOW_EX_VNI_OUT = 1029, SFLFLOW_EX_VNI_IN = 1030, SFLFLOW_EX_SOCKET4 = 2100, SFLFLOW_EX_SOCKET6 = 2101, SFLFLOW_EX_PROXYSOCKET4 = 2102, SFLFLOW_EX_PROXYSOCKET6 = 2103, SFLFLOW_MEMCACHE = 2200, SFLFLOW_HTTP = 2201, SFLFLOW_APP = 2202, /* transaction sample */ SFLFLOW_APP_CTXT = 2203, /* enclosing server context */ SFLFLOW_APP_ACTOR_INIT = 2204, /* initiator */ SFLFLOW_APP_ACTOR_TGT = 2205, /* target */ SFLFLOW_HTTP2 = 2206, }; typedef union _SFLFlow_type { SFLSampled_header header; SFLSampled_ethernet ethernet; SFLSampled_ipv4 ipv4; SFLSampled_ipv6 ipv6; SFLSampled_memcache memcache; SFLSampled_http http; SFLSampled_APP app; SFLSampled_APP_CTXT appCtxt; SFLSampled_APP_ACTOR appActor; SFLExtended_switch sw; SFLExtended_router router; SFLExtended_gateway gateway; SFLExtended_user user; SFLExtended_url url; SFLExtended_mpls mpls; SFLExtended_nat nat; SFLExtended_nat_port nat_port; SFLExtended_mpls_tunnel mpls_tunnel; SFLExtended_mpls_vc mpls_vc; SFLExtended_mpls_FTN mpls_ftn; SFLExtended_mpls_LDP_FEC mpls_ldp_fec; SFLExtended_vlan_tunnel vlan_tunnel; SFLExtended_wifi_payload wifi_payload; SFLExtended_wifi_rx wifi_rx; SFLExtended_wifi_tx wifi_tx; SFLExtended_aggregation aggregation; SFLExtended_socket_ipv4 socket4; SFLExtended_socket_ipv6 socket6; SFLExtended_vni tunnel_vni; SFLExtended_decap tunnel_decap; } SFLFlow_type; typedef struct _SFLFlow_sample_element { struct _SFLFlow_sample_element* nxt; uint32_t tag; /* SFLFlow_type_tag */ uint32_t length; SFLFlow_type flowType; } SFLFlow_sample_element; enum SFL_sample_tag { SFLFLOW_SAMPLE = 1, /* enterprise = 0 : format = 1 */ SFLCOUNTERS_SAMPLE = 2, /* enterprise = 0 : format = 2 */ SFLFLOW_SAMPLE_EXPANDED = 3, /* enterprise = 0 : format = 3 */ SFLCOUNTERS_SAMPLE_EXPANDED = 4 /* enterprise = 0 : format = 4 */ }; typedef struct _SFLFlow_Pdu { struct _SFLFlow_Pdu* nxt; uint32_t num_elements; SFLFlow_sample_element* elements; } SFLFlow_Pdu; /* Format of a single flow sample */ typedef struct _SFLFlow_sample { /* uint32_t tag; */ /* SFL_sample_tag -- enterprise = 0 : format = 1 */ /* uint32_t length; */ uint32_t sequence_number; /* Incremented with each flow sample generated */ uint32_t source_id; /* fsSourceId */ uint32_t sampling_rate; /* fsPacketSamplingRate */ uint32_t sample_pool; /* Total number of packets that could have been sampled (i.e. packets skipped by sampling process + total number of samples) */ uint32_t drops; /* Number of times a packet was dropped due to lack of resources */ uint32_t input; /* SNMP ifIndex of input interface. 0 if interface is not known. */ uint32_t output; /* SNMP ifIndex of output interface, 0 if interface is not known. Set most significant bit to indicate multiple destination interfaces (i.e. in case of broadcast or multicast) and set lower order bits to indicate number of destination interfaces. Examples: 0x00000002 indicates ifIndex = 2 0x00000000 ifIndex unknown. 0x80000007 indicates a packet sent to 7 interfaces. 0x80000000 indicates a packet sent to an unknown number of interfaces greater than 1.*/ uint32_t num_elements; SFLFlow_sample_element* elements; } SFLFlow_sample; /* same thing, but the expanded version (for full 32-bit ifIndex numbers) */ typedef struct _SFLFlow_sample_expanded { /* uint32_t tag; */ /* SFL_sample_tag -- enterprise = 0 : format = 1 */ /* uint32_t length; */ uint32_t sequence_number; /* Incremented with each flow sample generated */ uint32_t ds_class; /* EXPANDED */ uint32_t ds_index; /* EXPANDED */ uint32_t sampling_rate; /* fsPacketSamplingRate */ uint32_t sample_pool; /* Total number of packets that could have been sampled (i.e. packets skipped by sampling process + total number of samples) */ uint32_t drops; /* Number of times a packet was dropped due to lack of resources */ uint32_t inputFormat; /* EXPANDED */ uint32_t input; /* SNMP ifIndex of input interface. 0 if interface is not known. */ uint32_t outputFormat; /* EXPANDED */ uint32_t output; /* SNMP ifIndex of output interface, 0 if interface is not known. */ uint32_t num_elements; SFLFlow_sample_element* elements; } SFLFlow_sample_expanded; /* Counter types */ /* Generic interface counters - see RFC 1573, 2233 */ typedef struct _SFLIf_counters { uint32_t ifIndex; uint32_t ifType; uint64_t ifSpeed; uint32_t ifDirection; /* Derived from MAU MIB (RFC 2668) 0 = unknown, 1 = full-duplex, 2 = half-duplex, 3 = in, 4 = out */ uint32_t ifStatus; /* bit field with the following bits assigned: bit 0 = ifAdminStatus (0 = down, 1 = up) bit 1 = ifOperStatus (0 = down, 1 = up) */ uint64_t ifInOctets; uint32_t ifInUcastPkts; uint32_t ifInMulticastPkts; uint32_t ifInBroadcastPkts; uint32_t ifInDiscards; uint32_t ifInErrors; uint32_t ifInUnknownProtos; uint64_t ifOutOctets; uint32_t ifOutUcastPkts; uint32_t ifOutMulticastPkts; uint32_t ifOutBroadcastPkts; uint32_t ifOutDiscards; uint32_t ifOutErrors; uint32_t ifPromiscuousMode; } SFLIf_counters; /* Ethernet interface counters - see RFC 2358 */ typedef struct _SFLEthernet_counters { uint32_t dot3StatsAlignmentErrors; uint32_t dot3StatsFCSErrors; uint32_t dot3StatsSingleCollisionFrames; uint32_t dot3StatsMultipleCollisionFrames; uint32_t dot3StatsSQETestErrors; uint32_t dot3StatsDeferredTransmissions; uint32_t dot3StatsLateCollisions; uint32_t dot3StatsExcessiveCollisions; uint32_t dot3StatsInternalMacTransmitErrors; uint32_t dot3StatsCarrierSenseErrors; uint32_t dot3StatsFrameTooLongs; uint32_t dot3StatsInternalMacReceiveErrors; uint32_t dot3StatsSymbolErrors; } SFLEthernet_counters; /* Token ring counters - see RFC 1748 */ typedef struct _SFLTokenring_counters { uint32_t dot5StatsLineErrors; uint32_t dot5StatsBurstErrors; uint32_t dot5StatsACErrors; uint32_t dot5StatsAbortTransErrors; uint32_t dot5StatsInternalErrors; uint32_t dot5StatsLostFrameErrors; uint32_t dot5StatsReceiveCongestions; uint32_t dot5StatsFrameCopiedErrors; uint32_t dot5StatsTokenErrors; uint32_t dot5StatsSoftErrors; uint32_t dot5StatsHardErrors; uint32_t dot5StatsSignalLoss; uint32_t dot5StatsTransmitBeacons; uint32_t dot5StatsRecoverys; uint32_t dot5StatsLobeWires; uint32_t dot5StatsRemoves; uint32_t dot5StatsSingles; uint32_t dot5StatsFreqErrors; } SFLTokenring_counters; /* 100 BaseVG interface counters - see RFC 2020 */ typedef struct _SFLVg_counters { uint32_t dot12InHighPriorityFrames; uint64_t dot12InHighPriorityOctets; uint32_t dot12InNormPriorityFrames; uint64_t dot12InNormPriorityOctets; uint32_t dot12InIPMErrors; uint32_t dot12InOversizeFrameErrors; uint32_t dot12InDataErrors; uint32_t dot12InNullAddressedFrames; uint32_t dot12OutHighPriorityFrames; uint64_t dot12OutHighPriorityOctets; uint32_t dot12TransitionIntoTrainings; uint64_t dot12HCInHighPriorityOctets; uint64_t dot12HCInNormPriorityOctets; uint64_t dot12HCOutHighPriorityOctets; } SFLVg_counters; typedef struct _SFLVlan_counters { uint32_t vlan_id; uint64_t octets; uint32_t ucastPkts; uint32_t multicastPkts; uint32_t broadcastPkts; uint32_t discards; } SFLVlan_counters; typedef struct _SFLWifi_counters { uint32_t dot11TransmittedFragmentCount; uint32_t dot11MulticastTransmittedFrameCount; uint32_t dot11FailedCount; uint32_t dot11RetryCount; uint32_t dot11MultipleRetryCount; uint32_t dot11FrameDuplicateCount; uint32_t dot11RTSSuccessCount; uint32_t dot11RTSFailureCount; uint32_t dot11ACKFailureCount; uint32_t dot11ReceivedFragmentCount; uint32_t dot11MulticastReceivedFrameCount; uint32_t dot11FCSErrorCount; uint32_t dot11TransmittedFrameCount; uint32_t dot11WEPUndecryptableCount; uint32_t dot11QoSDiscardedFragmentCount; uint32_t dot11AssociatedStationCount; uint32_t dot11QoSCFPollsReceivedCount; uint32_t dot11QoSCFPollsUnusedCount; uint32_t dot11QoSCFPollsUnusableCount; uint32_t dot11QoSCFPollsLostCount; } SFLWifi_counters; /* Processor Information */ /* opaque = counter_data; enterprise = 0; format = 1001 */ typedef struct _SFLProcessor_counters { uint32_t five_sec_cpu; /* 5 second average CPU utilization */ uint32_t one_min_cpu; /* 1 minute average CPU utilization */ uint32_t five_min_cpu; /* 5 minute average CPU utilization */ uint64_t total_memory; /* total memory (in bytes) */ uint64_t free_memory; /* free memory (in bytes) */ } SFLProcessor_counters; typedef struct _SFLRadio_counters { uint32_t elapsed_time; /* elapsed time in ms */ uint32_t on_channel_time; /* time in ms spent on channel */ uint32_t on_channel_busy_time; /* time in ms spent on channel and busy */ } SFLRadio_counters; /* host sflow */ enum SFLMachine_type { SFLMT_unknown = 0, SFLMT_other = 1, SFLMT_x86 = 2, SFLMT_x86_64 = 3, SFLMT_ia64 = 4, SFLMT_sparc = 5, SFLMT_alpha = 6, SFLMT_powerpc = 7, SFLMT_m68k = 8, SFLMT_mips = 9, SFLMT_arm = 10, SFLMT_hppa = 11, SFLMT_s390 = 12 }; enum SFLOS_name { SFLOS_unknown = 0, SFLOS_other = 1, SFLOS_linux = 2, SFLOS_windows = 3, SFLOS_darwin = 4, SFLOS_hpux = 5, SFLOS_aix = 6, SFLOS_dragonfly = 7, SFLOS_freebsd = 8, SFLOS_netbsd = 9, SFLOS_openbsd = 10, SFLOS_osf = 11, SFLOS_solaris = 12 }; typedef struct _SFLMacAddress { uint8_t mac[8]; } SFLMacAddress; typedef struct _SFLAdaptor { uint32_t ifIndex; uint32_t num_macs; SFLMacAddress macs[1]; } SFLAdaptor; typedef struct _SFLAdaptorList { uint32_t capacity; uint32_t num_adaptors; SFLAdaptor** adaptors; } SFLAdaptorList; typedef struct _SFLHost_parent { uint32_t dsClass; /* sFlowDataSource class */ uint32_t dsIndex; /* sFlowDataSource index */ } SFLHost_parent; #define SFL_MAX_HOSTNAME_LEN 64 #define SFL_MAX_OSRELEASE_LEN 32 typedef struct _SFLHostId { SFLString hostname; u_char uuid[16]; uint32_t machine_type; /* enum SFLMachine_type */ uint32_t os_name; /* enum SFLOS_name */ SFLString os_release; /* max len 32 bytes */ } SFLHostId; typedef struct _SFLHost_nio_counters { uint64_t bytes_in; uint32_t pkts_in; uint32_t errs_in; uint32_t drops_in; uint64_t bytes_out; uint32_t pkts_out; uint32_t errs_out; uint32_t drops_out; } SFLHost_nio_counters; typedef struct _SFLHost_cpu_counters { float load_one; /* 1 minute load avg. */ float load_five; /* 5 minute load avg. */ float load_fifteen; /* 15 minute load avg. */ uint32_t proc_run; /* running threads */ uint32_t proc_total; /* total threads */ uint32_t cpu_num; /* # CPU cores */ uint32_t cpu_speed; /* speed in MHz of CPU */ uint32_t uptime; /* seconds since last reboot */ uint32_t cpu_user; /* time executing in user mode processes (ms) */ uint32_t cpu_nice; /* time executing niced processs (ms) */ uint32_t cpu_system; /* time executing kernel mode processes (ms) */ uint32_t cpu_idle; /* idle time (ms) */ uint32_t cpu_wio; /* time waiting for I/O to complete (ms) */ uint32_t cpu_intr; /* time servicing interrupts (ms) */ uint32_t cpu_sintr; /* time servicing softirqs (ms) */ uint32_t interrupts; /* interrupt count */ uint32_t contexts; /* context switch count */ } SFLHost_cpu_counters; typedef struct _SFLHost_mem_counters { uint64_t mem_total; /* total bytes */ uint64_t mem_free; /* free bytes */ uint64_t mem_shared; /* shared bytes */ uint64_t mem_buffers; /* buffers bytes */ uint64_t mem_cached; /* cached bytes */ uint64_t swap_total; /* swap total bytes */ uint64_t swap_free; /* swap free bytes */ uint32_t page_in; /* page in count */ uint32_t page_out; /* page out count */ uint32_t swap_in; /* swap in count */ uint32_t swap_out; /* swap out count */ } SFLHost_mem_counters; typedef struct _SFLHost_dsk_counters { uint64_t disk_total; uint64_t disk_free; uint32_t part_max_used; /* as percent * 100, so 100==1% */ uint32_t reads; /* reads issued */ uint64_t bytes_read; /* bytes read */ uint32_t read_time; /* read time (ms) */ uint32_t writes; /* writes completed */ uint64_t bytes_written; /* bytes written */ uint32_t write_time; /* write time (ms) */ } SFLHost_dsk_counters; /* Virtual Node Statistics */ /* opaque = counter_data; enterprise = 0; format = 2100 */ typedef struct _SFLHost_vrt_node_counters { uint32_t mhz; /* expected CPU frequency */ uint32_t cpus; /* the number of active CPUs */ uint64_t memory; /* memory size in bytes */ uint64_t memory_free; /* unassigned memory in bytes */ uint32_t num_domains; /* number of active domains */ } SFLHost_vrt_node_counters; /* Virtual Domain Statistics */ /* opaque = counter_data; enterprise = 0; format = 2101 */ /* virDomainState imported from libvirt.h */ enum SFLVirDomainState { SFL_VIR_DOMAIN_NOSTATE = 0, /* no state */ SFL_VIR_DOMAIN_RUNNING = 1, /* the domain is running */ SFL_VIR_DOMAIN_BLOCKED = 2, /* the domain is blocked on resource */ SFL_VIR_DOMAIN_PAUSED = 3, /* the domain is paused by user */ SFL_VIR_DOMAIN_SHUTDOWN = 4, /* the domain is being shut down */ SFL_VIR_DOMAIN_SHUTOFF = 5, /* the domain is shut off */ SFL_VIR_DOMAIN_CRASHED = 6 /* the domain is crashed */ }; typedef struct _SFLHost_vrt_cpu_counters { uint32_t state; /* virtDomainState */ uint32_t cpuTime; /* the CPU time used in mS */ uint32_t cpuCount; /* number of virtual CPUs for the domain */ } SFLHost_vrt_cpu_counters; /* Virtual Domain Memory statistics */ /* opaque = counter_data; enterprise = 0; format = 2102 */ typedef struct _SFLHost_vrt_mem_counters { uint64_t memory; /* memory in bytes used by domain */ uint64_t maxMemory; /* memory in bytes allowed */ } SFLHost_vrt_mem_counters; /* Virtual Domain Disk statistics */ /* opaque = counter_data; enterprise = 0; format = 2103 */ typedef struct _SFLHost_vrt_dsk_counters { uint64_t capacity; /* logical size in bytes */ uint64_t allocation; /* current allocation in bytes */ uint64_t available; /* remaining free bytes */ uint32_t rd_req; /* number of read requests */ uint64_t rd_bytes; /* number of read bytes */ uint32_t wr_req; /* number of write requests */ uint64_t wr_bytes; /* number of written bytes */ uint32_t errs; /* read/write errors */ } SFLHost_vrt_dsk_counters; /* Virtual Domain Network statistics */ /* opaque = counter_data; enterprise = 0; format = 2104 */ typedef struct _SFLHost_vrt_nio_counters { uint64_t bytes_in; uint32_t pkts_in; uint32_t errs_in; uint32_t drops_in; uint64_t bytes_out; uint32_t pkts_out; uint32_t errs_out; uint32_t drops_out; } SFLHost_vrt_nio_counters; /* NVML statistics */ /* opaque = counter_data; enterprise = 5703, format=1 */ typedef struct _SFLHost_gpu_nvml { uint32_t device_count; /* see nvmlGetDeviceCount */ uint32_t processes; /* see nvmlDeviceGetComputeRunningProcesses */ uint32_t gpu_time; /* total milliseconds in which one or more kernels was executing on GPU */ uint32_t mem_time; /* total milliseconds during which global device memory was being read/written */ uint64_t mem_total; /* bytes. see nvmlDeviceGetMemoryInfo */ uint64_t mem_free; /* bytes. see nvmlDeviceGetMemoryInfo */ uint32_t ecc_errors; /* see nvmlDeviceGetTotalEccErrors */ uint32_t energy; /* mJ. see nvmlDeviceGetPowerUsage */ uint32_t temperature; /* C. maximum across devices - see nvmlDeviceGetTemperature */ uint32_t fan_speed; /* %. maximum across devices - see nvmlDeviceGetFanSpeed */ } SFLHost_gpu_nvml; typedef struct _SFLMemcache_counters { uint32_t uptime; /* not in 2204 */ uint32_t rusage_user; /* not in 2204 */ uint32_t rusage_system; /* not in 2204 */ uint32_t cmd_get; /* not in 2204 */ uint32_t accepting_conns; /* not in 2204 */ uint32_t cmd_set; uint32_t cmd_touch; /* added for 2204 */ uint32_t cmd_flush; uint32_t get_hits; uint32_t get_misses; uint32_t delete_hits; uint32_t delete_misses; uint32_t incr_hits; uint32_t incr_misses; uint32_t decr_hits; uint32_t decr_misses; uint32_t cas_hits; uint32_t cas_misses; uint32_t cas_badval; uint32_t auth_cmds; uint32_t auth_errors; uint32_t threads; uint32_t conn_yields; uint32_t listen_disabled_num; uint32_t curr_connections; uint32_t rejected_connections; /* added for 2204 */ uint32_t total_connections; uint32_t connection_structures; uint32_t evictions; uint32_t reclaimed; /* added for 2204 */ uint32_t curr_items; uint32_t total_items; uint64_t bytes_read; uint64_t bytes_written; uint64_t bytes; uint64_t limit_maxbytes; /* converted to 64-bit for structure 2204 */ } SFLMemcache_counters; typedef struct _SFLHTTP_counters { uint32_t method_option_count; uint32_t method_get_count; uint32_t method_head_count; uint32_t method_post_count; uint32_t method_put_count; uint32_t method_delete_count; uint32_t method_trace_count; uint32_t methd_connect_count; uint32_t method_other_count; uint32_t status_1XX_count; uint32_t status_2XX_count; uint32_t status_3XX_count; uint32_t status_4XX_count; uint32_t status_5XX_count; uint32_t status_other_count; } SFLHTTP_counters; /* Enterprise counters */ /* opaque = counter_data; enterprise = 0; format = 2202 */ typedef struct _SFLAPP_counters { SFLString application; uint32_t status_OK; uint32_t errors_OTHER; uint32_t errors_TIMEOUT; uint32_t errors_INTERNAL_ERROR; uint32_t errors_BAD_REQUEST; uint32_t errors_FORBIDDEN; uint32_t errors_TOO_LARGE; uint32_t errors_NOT_IMPLEMENTED; uint32_t errors_NOT_FOUND; uint32_t errors_UNAVAILABLE; uint32_t errors_UNAUTHORIZED; } SFLAPP_counters; /* Enterprise resource counters */ /* opaque = counter_data; enterprise = 0; format = 2203 */ typedef struct { uint32_t user_time; /* in milliseconds */ uint32_t system_time; /* in milliseconds */ uint64_t mem_used; uint64_t mem_max; uint32_t fd_open; uint32_t fd_max; uint32_t conn_open; uint32_t conn_max; } SFLAPP_resources; /* Enterprise application workers */ /* opaque = counter_data; enterprise = 0; format = 2206 */ typedef struct { uint32_t workers_active; uint32_t workers_idle; uint32_t workers_max; uint32_t req_delayed; uint32_t req_dropped; } SFLAPP_workers; typedef struct _SFLJVM_ID { SFLString vm_name; SFLString vm_vendor; SFLString vm_version; } SFLJVM_ID; #define SFLJVM_MAX_VMNAME_LEN 64 #define SFLJVM_MAX_VENDOR_LEN 32 #define SFLJVM_MAX_VERSION_LEN 32 typedef struct _SFLJMX_counters { uint64_t hmem_initial; uint64_t hmem_used; uint64_t hmem_committed; uint64_t hmem_max; uint64_t nhmem_initial; uint64_t nhmem_used; uint64_t nhmem_committed; uint64_t nhmem_max; uint32_t gc_count; uint32_t gc_ms; uint32_t cls_loaded; uint32_t cls_total; uint32_t cls_unloaded; uint32_t comp_ms; uint32_t thread_live; uint32_t thread_daemon; uint32_t thread_started; uint32_t fds_open; uint32_t fds_max; } SFLJMX_counters; #define XDRSIZ_JMX_COUNTERS 108 typedef struct _SFLVdi_counters { uint32_t sessions_current; /* number of current sessions */ uint32_t sessions_total; /* total sessions started */ uint32_t sessions_duration; /* cumulative session time (in seconds) across all sessions, such that average session duration = sessions_duration / sessions_total */ uint32_t rx_bytes; /* total bytes received */ uint32_t tx_bytes; /* total bytes sent */ uint32_t rx_packets; /* total packet received */ uint32_t tx_packets; /* total packets sent */ uint32_t rx_packets_lost; /* total received packets lost */ uint32_t tx_packets_lost; /* total sent packets lost */ uint32_t rtt_min_ms; /* minimum round trip latency with client across all current sessions measured in milliseconds */ uint32_t rtt_max_ms; /* maximum round trip latency with client across all current sessions measured in millisecond */ uint32_t rtt_avg_ms; /* average round trip latency with client across all current sessions measured in milliseconds */ uint32_t audio_rx_bytes; /* total bytes of audio data received */ uint32_t audio_tx_bytes; /* total bytes of audio data sent */ uint32_t audio_tx_limit; /* administrative limit on audio transmission bandwidth (in bits per second) */ uint32_t img_rx_bytes; /* total bytes of imaging data recieved */ uint32_t img_tx_bytes; /* total bytes of imaging data sent */ uint32_t img_frames; /* total image frames encoded */ uint32_t img_qual_min; /* minimum image encoding quality across current sessions, on a scale of 0 to 100 */ uint32_t img_qual_max; /* best image encoding quality across current sessions, on a scale of 0 to 100 */ uint32_t img_qual_avg; /* average image encoding quality across current sessions, on a scale of 0 to 100 */ uint32_t usb_rx_bytes; /* total bytes of usb data received */ uint32_t usb_tx_bytes; /* total bytes of usb data sent */ } SFLVdi_counters; /* LAG Port Statistics - see IEEE8023-LAG-MIB */ /* opaque = counter_data; enterprise = 0; format = 7 */ typedef union _SFLLACP_portState { uint32_t all; struct { uint8_t actorAdmin; uint8_t actorOper; uint8_t partnerAdmin; uint8_t partnerOper; } v; } SFLLACP_portState; typedef struct _SFLLACP_counters { uint8_t actorSystemID[8]; /* 6 bytes + 2 pad */ uint8_t partnerSystemID[8]; /* 6 bytes + 2 pad */ uint32_t attachedAggID; SFLLACP_portState portState; uint32_t LACPDUsRx; uint32_t markerPDUsRx; uint32_t markerResponsePDUsRx; uint32_t unknownRx; uint32_t illegalRx; uint32_t LACPDUsTx; uint32_t markerPDUsTx; uint32_t markerResponsePDUsTx; } SFLLACP_counters; #define XDRSIZ_LACP_COUNTERS 56 /* port name */ /* opaque = counter_data; enterprise = 0; format = 1005 */ typedef struct { SFLString portName; } SFLPortName; #define SFL_MAX_PORTNAME_LEN 255 /* Counters data */ enum SFLCounters_type_tag { /* enterprise = 0, format = ... */ SFLCOUNTERS_GENERIC = 1, SFLCOUNTERS_ETHERNET = 2, SFLCOUNTERS_TOKENRING = 3, SFLCOUNTERS_VG = 4, SFLCOUNTERS_VLAN = 5, SFLCOUNTERS_80211 = 6, SFLCOUNTERS_LACP = 7, SFLCOUNTERS_PROCESSOR = 1001, SFLCOUNTERS_RADIO = 1002, SFLCOUNTERS_PORTNAME = 1005, SFLCOUNTERS_HOST_HID = 2000, /* host id */ SFLCOUNTERS_ADAPTORS = 2001, /* host adaptors */ SFLCOUNTERS_HOST_PAR = 2002, /* host parent */ SFLCOUNTERS_HOST_CPU = 2003, /* host cpu */ SFLCOUNTERS_HOST_MEM = 2004, /* host memory */ SFLCOUNTERS_HOST_DSK = 2005, /* host storage I/O */ SFLCOUNTERS_HOST_NIO = 2006, /* host network I/O */ SFLCOUNTERS_HOST_VRT_NODE = 2100, /* host virt node */ SFLCOUNTERS_HOST_VRT_CPU = 2101, /* host virt cpu */ SFLCOUNTERS_HOST_VRT_MEM = 2102, /* host virt mem */ SFLCOUNTERS_HOST_VRT_DSK = 2103, /* host virt storage */ SFLCOUNTERS_HOST_VRT_NIO = 2104, /* host virt network I/O */ SFLCOUNTERS_JVM = 2105, /* java runtime */ SFLCOUNTERS_JMX = 2106, /* java JMX stats */ SFLCOUNTERS_MEMCACHE = 2200, /* memcached (deprecated) */ SFLCOUNTERS_HTTP = 2201, /* http */ SFLCOUNTERS_APP = 2202, SFLCOUNTERS_APP_RESOURCE = 2203, SFLCOUNTERS_MEMCACHE2 = 2204, /* memcached */ SFLCOUNTERS_VDI = 2205, SFLCOUNTERS_APP_WORKERS = 2206, SFLCOUNTERS_HOST_GPU_NVML = (5703 << 12) + 1, /* = 23359489 */ }; typedef union _SFLCounters_type { SFLIf_counters generic; SFLEthernet_counters ethernet; SFLTokenring_counters tokenring; SFLVg_counters vg; SFLVlan_counters vlan; SFLWifi_counters wifi; SFLProcessor_counters processor; SFLRadio_counters radio; SFLHostId hostId; SFLAdaptorList* adaptors; SFLHost_parent host_par; SFLHost_cpu_counters host_cpu; SFLHost_mem_counters host_mem; SFLHost_dsk_counters host_dsk; SFLHost_nio_counters host_nio; SFLHost_vrt_node_counters host_vrt_node; SFLHost_vrt_cpu_counters host_vrt_cpu; SFLHost_vrt_mem_counters host_vrt_mem; SFLHost_vrt_dsk_counters host_vrt_dsk; SFLHost_vrt_nio_counters host_vrt_nio; SFLHost_gpu_nvml host_gpu_nvml; SFLMemcache_counters memcache; SFLHTTP_counters http; SFLJVM_ID jvm; SFLJMX_counters jmx; SFLAPP_counters app; SFLAPP_resources appResources; SFLAPP_workers appWorkers; SFLVdi_counters vdi; SFLLACP_counters lacp; SFLPortName portName; } SFLCounters_type; typedef struct _SFLCounters_sample_element { struct _SFLCounters_sample_element* nxt; /* linked list */ uint32_t tag; /* SFLCounters_type_tag */ uint32_t length; SFLCounters_type counterBlock; } SFLCounters_sample_element; typedef struct _SFLCounters_sample { /* uint32_t tag; */ /* SFL_sample_tag -- enterprise = 0 : format = 2 */ /* uint32_t length; */ uint32_t sequence_number; /* Incremented with each counters sample generated by this source_id */ uint32_t source_id; /* fsSourceId */ uint32_t num_elements; SFLCounters_sample_element* elements; } SFLCounters_sample; /* same thing, but the expanded version, so ds_index can be a full 32 bits */ typedef struct _SFLCounters_sample_expanded { /* uint32_t tag; */ /* SFL_sample_tag -- enterprise = 0 : format = 2 */ /* uint32_t length; */ uint32_t sequence_number; /* Incremented with each counters sample generated by this source_id */ uint32_t ds_class; /* EXPANDED */ uint32_t ds_index; /* EXPANDED */ uint32_t num_elements; SFLCounters_sample_element* elements; } SFLCounters_sample_expanded; #define SFLADD_ELEMENT(_sm, _el) \ do { \ (_el)->nxt = (_sm)->elements; \ (_sm)->elements = (_el); \ } while (0) /* Format of a sample datagram */ enum SFLDatagram_version { SFLDATAGRAM_VERSION2 = 2, SFLDATAGRAM_VERSION4 = 4, SFLDATAGRAM_VERSION5 = 5 }; typedef struct _SFLSample_datagram_hdr { uint32_t datagram_version; /* (enum SFLDatagram_version) = VERSION5 = 5 */ SFLAddress agent_address; /* IP address of sampling agent */ uint32_t sub_agent_id; /* Used to distinguishing between datagram streams from separate agent sub entities within an device. */ uint32_t sequence_number; /* Incremented with each sample datagram generated */ uint32_t uptime; /* Current time (in milliseconds since device last booted). Should be set as close to datagram transmission time as possible.*/ uint32_t num_records; /* Number of tag-len-val flow/counter records to follow */ } SFLSample_datagram_hdr; #define SFL_MAX_DATAGRAM_SIZE 1500 #define SFL_MIN_DATAGRAM_SIZE 200 #define SFL_DEFAULT_DATAGRAM_SIZE 1400 #define SFL_DATA_PAD 400 #if defined(__cplusplus) } /* extern "C" */ #endif #endif /* SFLOW_H */ fastnetmon-1.1.3+dfsg/src/sflow_plugin/sflow_collector.cpp000066400000000000000000000724541313534057500240360ustar00rootroot00000000000000#include #include #include #include "sflow_collector.h" // sflowtool-3.32 #include "sflow.h" // custom sFLOW data structures #include "sflow_data.h" #include "../fast_library.h" #include #include #include #include // UDP server #include #include #include #include #include #include // log4cpp logging facility #include "log4cpp/Category.hh" #include "log4cpp/Appender.hh" #include "log4cpp/FileAppender.hh" #include "log4cpp/OstreamAppender.hh" #include "log4cpp/Layout.hh" #include "log4cpp/BasicLayout.hh" #include "log4cpp/PatternLayout.hh" #include "log4cpp/Priority.hh" #ifdef ENABLE_LUA_HOOKS lua_State* sflow_lua_state = NULL; bool sflow_lua_hooks_enabled = false; std::string sflow_lua_hooks_path = "/usr/src/fastnetmon/src/sflow_hooks.lua"; #endif // sFLOW v4 specification: http://www.sflow.org/rfc3176.txt std::string plugin_name = "sflow"; std::string plugin_log_prefix = plugin_name + ": "; // Get logger from main programm extern log4cpp::Category& logger; // Global configuration map extern std::map configuration_map; // Enable debug messages in log bool debug_sflow_parser = false; uint32_t getData32(SFSample* sample); bool skipTLVRecord(SFSample* sample, uint32_t tag, uint32_t len); bool readFlowSample(SFSample* sample, int expanded); void readFlowSample_header(SFSample* sample); void decode_ipv4_protocol(SFSample* sample); void decode_ipv6_protocol(SFSample* sample); void print_simple_packet(struct simple_packet& packet); process_packet_pointer sflow_process_func_ptr = NULL; // #include void start_sflow_collector(std::string interface_for_binding, unsigned int sflow_port); void start_sflow_collection(process_packet_pointer func_ptr) { std::string interface_for_binding = "0.0.0.0"; std::string sflow_ports = ""; logger << log4cpp::Priority::INFO << plugin_log_prefix << "plugin started"; // prctl(PR_SET_NAME,"fastnetmon_sflow", 0, 0, 0); sflow_process_func_ptr = func_ptr; if (configuration_map.count("sflow_port") != 0) { sflow_ports = configuration_map["sflow_port"]; } if (configuration_map.count("sflow_host") != 0) { interface_for_binding = configuration_map["sflow_host"]; } #ifdef ENABLE_LUA_HOOKS if (configuration_map.count("sflow_lua_hooks_path") != 0) { sflow_lua_hooks_path = configuration_map["sflow_lua_hooks_path"]; sflow_lua_hooks_enabled = true; } #endif #ifdef ENABLE_LUA_HOOKS if (sflow_lua_hooks_enabled) { sflow_lua_state = init_lua_jit(sflow_lua_hooks_path); if (sflow_lua_state == NULL) { sflow_lua_hooks_enabled = false; } } #endif boost::thread_group sflow_collector_threads; std::vector ports_for_listen; boost::split(ports_for_listen, sflow_ports, boost::is_any_of(","), boost::token_compress_on); logger << log4cpp::Priority::INFO << plugin_log_prefix << "We will listen on " << ports_for_listen.size() << " ports"; for (std::vector::iterator port = ports_for_listen.begin(); port != ports_for_listen.end(); ++port) { unsigned int sflow_port = convert_string_to_integer(*port); if (sflow_port == 0) { sflow_port = 6343; } sflow_collector_threads.add_thread( new boost::thread(start_sflow_collector, interface_for_binding, sflow_port )); } sflow_collector_threads.join_all(); } void start_sflow_collector(std::string interface_for_binding, unsigned int sflow_port) { logger << log4cpp::Priority::INFO << plugin_log_prefix << "plugin will listen on " << interface_for_binding << ":" << sflow_port << " udp port"; unsigned int udp_buffer_size = 65536; char udp_buffer[udp_buffer_size]; int sockfd = socket(AF_INET, SOCK_DGRAM, 0); struct sockaddr_in servaddr; memset(&servaddr, 0, sizeof(servaddr)); servaddr.sin_family = AF_INET; if (interface_for_binding == "0.0.0.0") { servaddr.sin_addr.s_addr = htonl(INADDR_ANY); } else { servaddr.sin_addr.s_addr = inet_addr(interface_for_binding.c_str()); } servaddr.sin_port = htons(sflow_port); int bind_result = bind(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr)); if (bind_result) { logger << log4cpp::Priority::ERROR << plugin_log_prefix << "can't listen port: " << sflow_port; return; } struct sockaddr_in6 peer; memset(&peer, 0, sizeof(peer)); /* We should specify timeout there for correct toolkit shutdown */ /* Because otherwise recvfrom will stay in blocked mode forever */ struct timeval tv; tv.tv_sec = 5; /* X Secs Timeout */ tv.tv_usec = 0; // Not init'ing this can cause strange errors setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof(struct timeval)); while (true) { struct sockaddr_in cliaddr; socklen_t address_len = sizeof(cliaddr); int received_bytes = recvfrom(sockfd, udp_buffer, udp_buffer_size, 0, (struct sockaddr*)&cliaddr, &address_len); if (received_bytes > 0) { // printf("We receive %d\n", received_bytes); SFSample sample; memset(&sample, 0, sizeof(sample)); sample.rawSample = (uint8_t*)udp_buffer; sample.rawSampleLen = received_bytes; if (address_len == sizeof(struct sockaddr_in)) { struct sockaddr_in* peer4 = (struct sockaddr_in*)&cliaddr; sample.sourceIP.type = SFLADDRESSTYPE_IP_V4; memcpy(&sample.sourceIP.address.ip_v4, &peer4->sin_addr, 4); read_sflow_datagram(&sample); } else { // We do not support an IPv6 } } else { if (received_bytes == -1) { if (errno == EAGAIN) { // We got timeout, it's OK! } else { logger << log4cpp::Priority::ERROR << plugin_log_prefix << "data receive failed"; } } } // Add interruption point for correct application shutdown boost::this_thread::interruption_point(); } } uint32_t getData32_nobswap(SFSample* sample) { uint32_t ans = *(sample->datap)++; // make sure we didn't run off the end of the datagram. Thanks to // Sven Eschenberg for spotting a bug/overrun-vulnerabilty that was here before. if ((uint8_t*)sample->datap > sample->endp) { // SFABORT(sample, SF_ABORT_EOS); // Error!!! logger << log4cpp::Priority::ERROR << plugin_log_prefix << "we tried to read data in bad place! Fault!"; return 0; } return ans; } bool skipBytes(SFSample* sample, uint32_t skip) { int quads = (skip + 3) / 4; sample->datap += quads; if (skip > sample->rawSampleLen || (uint8_t*)sample->datap > sample->endp) { // SFABORT(sample, SF_ABORT_EOS); logger << log4cpp::Priority::ERROR << plugin_log_prefix << "very dangerous error from skipBytes function! We try to read from restricted memory region"; return false; } return true; } uint32_t getAddress(SFSample* sample, SFLAddress* address) { address->type = getData32(sample); if (address->type == SFLADDRESSTYPE_IP_V4) { address->address.ip_v4.addr = getData32_nobswap(sample); } else { memcpy(&address->address.ip_v6.addr, sample->datap, 16); skipBytes(sample, 16); } return address->type; } uint32_t getData32(SFSample* sample) { return ntohl(getData32_nobswap(sample)); } bool readFlowSample_v2v4(SFSample *sample) { sample->samplesGenerated = getData32(sample); uint32_t samplerId = getData32(sample); sample->ds_class = samplerId >> 24; sample->ds_index = samplerId & 0x00ffffff; sample->meanSkipCount = getData32(sample); sample->samplePool = getData32(sample); sample->dropEvents = getData32(sample); sample->inputPort = getData32(sample); sample->outputPort = getData32(sample); sample->packet_data_tag = getData32(sample); switch(sample->packet_data_tag) { case INMPACKETTYPE_HEADER: readFlowSample_header(sample); break; case INMPACKETTYPE_IPV4: logger << log4cpp::Priority::ERROR << plugin_log_prefix << "hit INMPACKETTYPE_IPV4, very strange"; return false; break; case INMPACKETTYPE_IPV6: logger << log4cpp::Priority::ERROR << plugin_log_prefix << "hit INMPACKETTYPE_IPV6, very strange"; return false; break; default: logger << log4cpp::Priority::ERROR << plugin_log_prefix << "unexpected packet_data_tag"; return false; break; } sample->extended_data_tag = 0; // We should read this data sample->num_extended = getData32(sample); if (sample->num_extended > 0) { logger << log4cpp::Priority::ERROR << plugin_log_prefix << "we have " << sample->num_extended << " extended fields"; logger << log4cpp::Priority::ERROR << plugin_log_prefix << "and sorry we haven't support for it :("; return false; } return true; } void read_sflow_datagram(SFSample* sample) { sample->datap = (uint32_t*)sample->rawSample; sample->endp = (uint8_t*)sample->rawSample + sample->rawSampleLen; sample->datagramVersion = getData32(sample); // printf("sFLOW version %d\n", sample->datagramVersion); if (sample->datagramVersion != 5 && sample->datagramVersion != 4) { logger << log4cpp::Priority::ERROR << plugin_log_prefix << "we do not support sFLOW v<< "<< sample->datagramVersion << " because it's too old. Please change version to sFLOW 4 or 5"; return; } /* get the agent address */ getAddress(sample, &sample->agent_addr); /* version 5 has an agent sub-id as well */ if (sample->datagramVersion >= 5) { sample->agentSubId = getData32(sample); // sf_log(sample,"agentSubId %u\n", sample->agentSubId); } else { sample->agentSubId = 0; } sample->sequenceNo = getData32(sample); /* this is the packet sequence number */ sample->sysUpTime = getData32(sample); uint32_t samplesInPacket = getData32(sample); // printf("We have %d samples in packet\n", samplesInPacket); uint32_t samp = 0; for (; samp < samplesInPacket; samp++) { if ((uint8_t*)sample->datap >= sample->endp) { logger << log4cpp::Priority::INFO << plugin_log_prefix << "we tried to read data outside packet! It's very dangerous, we stop all operations"; return; } // printf("Sample #%d\n", samp); /* just read the tag, then call the approriate decode fn */ sample->sampleType = getData32(sample); if (sample->datagramVersion >= 5) { switch (sample->sampleType) { case SFLFLOW_SAMPLE: // skipBytes(sample, getData32(sample)); if (!readFlowSample(sample, 0)) { logger << log4cpp::Priority::ERROR << plugin_log_prefix << "we failed in SFLFLOW_SAMPLE handler"; return; } break; case SFLCOUNTERS_SAMPLE: // We do not need counters for our task, skip it if (!skipBytes(sample, getData32(sample))) { logger << log4cpp::Priority::ERROR << plugin_log_prefix << "we failed in SFLCOUNTERS_SAMPLE handler"; return; } break; case SFLFLOW_SAMPLE_EXPANDED: // skipBytes(sample, getData32(sample)); if (!readFlowSample(sample, 1)) { logger << log4cpp::Priority::ERROR << plugin_log_prefix << "we failed in SFLFLOW_SAMPLE_EXPANDED handler"; return; } break; case SFLCOUNTERS_SAMPLE_EXPANDED: // We do not need counters for our task, skip it if (!skipBytes(sample, getData32(sample))) { logger << log4cpp::Priority::ERROR << plugin_log_prefix << "we failed in SFLCOUNTERS_SAMPLE_EXPANDED handler"; return; } break; default: if (!skipTLVRecord(sample, sample->sampleType, getData32(sample))) { logger << log4cpp::Priority::ERROR << plugin_log_prefix << "we failed in default handler in skipTLVRecord"; return; } break; } } else { // sFLOW v2 or v4 here switch(sample->sampleType) { case FLOWSAMPLE: if (!readFlowSample_v2v4(sample)) { // We have some troubles with old sFLOW parser return; } break; case COUNTERSSAMPLE: logger << log4cpp::Priority::ERROR << plugin_log_prefix << "we haven't support for COUNTERSSAMPLE for " << "sFLOW v4 and ignore it completely"; return; break; default: logger << log4cpp::Priority::ERROR << plugin_log_prefix << "unexpected sample type: " << sample->sampleType; return; break; } } } } bool skipTLVRecord(SFSample* sample, uint32_t tag, uint32_t len) { return skipBytes(sample, len); } bool length_check(SFSample *sample, const char *description, uint8_t *start, int len) { uint32_t actualLen = (uint8_t *)sample->datap - start; uint32_t adjustedLen = ((len + 3) >> 2) << 2; if (actualLen != adjustedLen) { logger << log4cpp::Priority::ERROR << plugin_log_prefix << description << " length error: expected " << len << " found " << actualLen; return false; } return true; } bool readFlowSample(SFSample* sample, int expanded) { uint32_t num_elements, sampleLength; uint8_t* sampleStart; sampleLength = getData32(sample); sampleStart = (uint8_t*)sample->datap; sample->samplesGenerated = getData32(sample); if (expanded) { sample->ds_class = getData32(sample); sample->ds_index = getData32(sample); } else { uint32_t samplerId = getData32(sample); sample->ds_class = samplerId >> 24; sample->ds_index = samplerId & 0x00ffffff; } sample->meanSkipCount = getData32(sample); // printf("Sample ratio: %d\n", sample->meanSkipCount); sample->samplePool = getData32(sample); sample->dropEvents = getData32(sample); if (expanded) { sample->inputPortFormat = getData32(sample); sample->inputPort = getData32(sample); sample->outputPortFormat = getData32(sample); sample->outputPort = getData32(sample); } else { uint32_t inp, outp; inp = getData32(sample); outp = getData32(sample); sample->inputPortFormat = inp >> 30; sample->outputPortFormat = outp >> 30; sample->inputPort = inp & 0x3fffffff; sample->outputPort = outp & 0x3fffffff; } num_elements = getData32(sample); uint32_t el; for (el = 0; el < num_elements; el++) { uint32_t tag, length; uint8_t* start; char buf[51]; tag = sample->elementType = getData32(sample); length = getData32(sample); start = (uint8_t*)sample->datap; // tag analyze if (tag == SFLFLOW_HEADER) { // process data readFlowSample_header(sample); } else { if (!skipTLVRecord(sample, tag, length)) { return false; } } if (!length_check(sample, "flow_sample_element", start, length)) { return false; } } if (!length_check(sample, "flow_sample", sampleStart, sampleLength)) { return false; } return true; } #define NFT_ETHHDR_SIZ 14 #define NFT_8022_SIZ 3 #define NFT_MAX_8023_LEN 1500 #define NFT_MIN_SIZ (NFT_ETHHDR_SIZ + sizeof(struct myiphdr)) void decode_link_layer(SFSample* sample) { uint8_t* start = (uint8_t*)sample->header; uint8_t* end = start + sample->headerLen; uint8_t* ptr = start; uint16_t type_len; /* assume not found */ sample->gotIPV4 = 0; sample->gotIPV6 = 0; if (sample->headerLen < NFT_ETHHDR_SIZ) { /* not enough for an Ethernet header */ return; } // sf_log(sample,"dstMAC %02x%02x%02x%02x%02x%02x\n", ptr[0], ptr[1], ptr[2], ptr[3], ptr[4], // ptr[5]); memcpy(sample->eth_dst, ptr, 6); ptr += 6; // sf_log(sample,"srcMAC %02x%02x%02x%02x%02x%02x\n", ptr[0], ptr[1], ptr[2], ptr[3], ptr[4], // ptr[5]); memcpy(sample->eth_src, ptr, 6); ptr += 6; type_len = (ptr[0] << 8) + ptr[1]; ptr += 2; if (type_len == 0x8100) { /* VLAN - next two bytes */ uint32_t vlanData = (ptr[0] << 8) + ptr[1]; uint32_t vlan = vlanData & 0x0fff; uint32_t priority = vlanData >> 13; ptr += 2; /* _____________________________________ */ /* | pri | c | vlan-id | */ /* ------------------------------------- */ /* [priority = 3bits] [Canonical Format Flag = 1bit] [vlan-id = 12 bits] */ // sf_log(sample,"decodedVLAN %u\n", vlan); // sf_log(sample,"decodedPriority %u\n", priority); sample->in_vlan = vlan; /* now get the type_len again (next two bytes) */ type_len = (ptr[0] << 8) + ptr[1]; ptr += 2; } /* assume type_len is an ethernet-type now */ sample->eth_type = type_len; if (type_len == 0x0800) { /* IPV4 */ if ((end - ptr) < sizeof(struct myiphdr)) { return; } /* look at first byte of header.... */ /* ___________________________ */ /* | version | hdrlen | */ /* --------------------------- */ if ((*ptr >> 4) != 4) return; /* not version 4 */ if ((*ptr & 15) < 5) return; /* not IP (hdr len must be 5 quads or more) */ /* survived all the tests - store the offset to the start of the ip header */ sample->gotIPV4 = 1; sample->offsetToIPV4 = (ptr - start); } if (type_len == 0x86DD) { /* IPV6 */ /* look at first byte of header.... */ if ((*ptr >> 4) != 6) return; /* not version 6 */ /* survived all the tests - store the offset to the start of the ip6 header */ sample->gotIPV6 = 1; sample->offsetToIPV6 = (ptr - start); printf("IPv6\n"); } // printf("vlan: %d\n",sample->in_vlan); } void readFlowSample_header(SFSample* sample) { sample->headerProtocol = getData32(sample); sample->sampledPacketSize = getData32(sample); if (sample->datagramVersion > 4) { /* stripped count introduced in sFlow version 5 */ sample->stripped = getData32(sample); } sample->headerLen = getData32(sample); sample->header = (uint8_t*)sample->datap; /* just point at the header */ skipBytes(sample, sample->headerLen); if (sample->headerProtocol == SFLHEADER_ETHERNET_ISO8023) { // Detect IPv4 or IPv6 here decode_link_layer(sample); // Process IP packets next if (sample->gotIPV4) { decode_ipv4_protocol(sample); } if (sample->gotIPV6) { decode_ipv6_protocol(sample); } } else { logger << log4cpp::Priority::ERROR << plugin_log_prefix << "not supported protocol: " << sample->headerProtocol; return; } } char* IP_to_a(uint32_t ipaddr, char* buf) { uint8_t* ip = (uint8_t*)&ipaddr; /* should really be: snprintf(buf, buflen,...) but snprintf() is not always available */ sprintf(buf, "%u.%u.%u.%u", ip[0], ip[1], ip[2], ip[3]); return buf; } char* printAddress(SFLAddress* address, char* buf) { switch (address->type) { case SFLADDRESSTYPE_IP_V4: IP_to_a(address->address.ip_v4.addr, buf); break; case SFLADDRESSTYPE_IP_V6: { uint8_t* b = address->address.ip_v6.addr; /* should really be: snprintf(buf, buflen,...) but snprintf() is not always available */ sprintf(buf, "%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x", b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7], b[8], b[9], b[10], b[11], b[12], b[13], b[14], b[15]); } break; default: sprintf(buf, "-"); } return buf; } void decodeIPLayer4(SFSample* sample, uint8_t* ptr) { uint8_t* end = sample->header + sample->headerLen; if (ptr > (end - 8)) { /* not enough header bytes left */ return; } simple_packet current_packet; if (sample->gotIPV6) { current_packet.ip_protocol_version = 6; memcpy(current_packet.src_ipv6.s6_addr, sample->ipsrc.address.ip_v6.addr, 16); memcpy(current_packet.dst_ipv6.s6_addr, sample->ipdst.address.ip_v6.addr, 16); } else { current_packet.ip_protocol_version = 4; current_packet.src_ip = sample->ipsrc.address.ip_v4.addr; current_packet.dst_ip = sample->ipdst.address.ip_v4.addr; } // Because sFLOW data is near real time we could get current time gettimeofday(¤t_packet.ts, NULL); current_packet.flags = 0; current_packet.number_of_packets = 1; current_packet.length = sample->sampledPacketSize; current_packet.sample_ratio = sample->meanSkipCount; switch (sample->dcd_ipProtocol) { case 1: { // ICMP current_packet.protocol = IPPROTO_ICMP; struct myicmphdr icmp; memcpy(&icmp, ptr, sizeof(icmp)); // printf("ICMPType %u\n", icmp.type); // printf("ICMPCode %u\n", icmp.code); sample->dcd_sport = icmp.type; sample->dcd_dport = icmp.code; sample->offsetToPayload = ptr + sizeof(icmp) - sample->header; } break; case 6: { // TCP current_packet.protocol = IPPROTO_TCP; struct mytcphdr tcp; int headerBytes; memcpy(&tcp, ptr, sizeof(tcp)); sample->dcd_sport = ntohs(tcp.th_sport); sample->dcd_dport = ntohs(tcp.th_dport); current_packet.source_port = sample->dcd_sport; current_packet.destination_port = sample->dcd_dport; // TODO: flags could be broken because our flags parser implemented with PF_RING style flags // PF_RING current_packet.flags = tcp.th_flags; sample->dcd_tcpFlags = tcp.th_flags; // printf("TCPSrcPort %u\n", sample->dcd_sport); // printf("TCPDstPort %u\n",sample->dcd_dport); // printf("TCPFlags %u\n", sample->dcd_tcpFlags); headerBytes = (tcp.th_off_and_unused >> 4) * 4; ptr += headerBytes; sample->offsetToPayload = ptr - sample->header; } break; case 17: { // UDP current_packet.protocol = IPPROTO_UDP; struct myudphdr udp; memcpy(&udp, ptr, sizeof(udp)); sample->dcd_sport = ntohs(udp.uh_sport); sample->dcd_dport = ntohs(udp.uh_dport); current_packet.source_port = sample->dcd_sport; current_packet.destination_port = sample->dcd_dport; sample->udp_pduLen = ntohs(udp.uh_ulen); // printf("UDPSrcPort %u\n", sample->dcd_sport); // printf("UDPDstPort %u\n", sample->dcd_dport); // printf("UDPBytes %u\n", sample->udp_pduLen); sample->offsetToPayload = ptr + sizeof(udp) - sample->header; } break; default: /* some other protcol */ sample->offsetToPayload = ptr - sample->header; break; } #ifdef ENABLE_LUA_HOOKS //sample->inputPort = fast_ntoh(sample->inputPort); //sample->outputPort = fast_ntoh(sample->outputPort); if (sflow_lua_hooks_enabled) { // This code could be used only for tests with pcap_reader if (sflow_lua_state == NULL) { sflow_lua_state = init_lua_jit(sflow_lua_hooks_path); } if (call_lua_function("process_sflow", sflow_lua_state, convert_ip_as_uint_to_string(sample->sourceIP.address.ip_v4.addr), (void*)sample)) { // We will process this packet } else { logger << log4cpp::Priority::DEBUG << "We will drop this packets because LUA script decided to do it"; return; } } #endif // Call external handler function sflow_process_func_ptr(current_packet); } void decode_ipv6_protocol(SFSample* sample) { uint8_t *ptr = sample->header + sample->offsetToIPV6; uint8_t *end = sample->header + sample->headerLen; int ipVersion = (*ptr >> 4); if (ipVersion != 6) { logger << log4cpp::Priority::ERROR << plugin_log_prefix << "sFLOW header decode error: unexpected IP version: " << ipVersion; return; } /* get the tos (priority) */ sample->dcd_ipTos = *ptr++ & 15; if (debug_sflow_parser) { logger << log4cpp::Priority::INFO << plugin_log_prefix << "IPTOS: " << sample->dcd_ipTos; } /* 24-bit label */ uint32_t label = *ptr++; label <<= 8; label += *ptr++; label <<= 8; label += *ptr++; if (debug_sflow_parser) { logger << log4cpp::Priority::INFO << plugin_log_prefix << "IP6_label: " << label; } /* payload */ uint16_t payloadLen = (ptr[0] << 8) + ptr[1]; ptr += 2; /* if payload is zero, that implies a jumbo payload */ if (debug_sflow_parser) { if (payloadLen == 0) { logger << log4cpp::Priority::INFO << plugin_log_prefix << "IPV6_payloadLen "; } else { logger << log4cpp::Priority::INFO << plugin_log_prefix << "IPV6_payloadLen " << payloadLen; } } /* next header */ uint32_t nextHeader = *ptr++; /* TTL */ sample->dcd_ipTTL = *ptr++; //sf_log(sample,"IPTTL %u\n", sample->dcd_ipTTL); /* src and dst address */ // char buf[101]; sample->ipsrc.type = SFLADDRESSTYPE_IP_V6; memcpy(&sample->ipsrc.address, ptr, 16); ptr +=16; if (debug_sflow_parser) { char buf[101]; logger << log4cpp::Priority::INFO << plugin_log_prefix << "srcIP6: " << printAddress(&sample->ipsrc, buf); } sample->ipdst.type = SFLADDRESSTYPE_IP_V6; memcpy(&sample->ipdst.address, ptr, 16); ptr +=16; if (debug_sflow_parser) { char buf[101]; logger << log4cpp::Priority::INFO << plugin_log_prefix << "dstIP6: " << printAddress(&sample->ipdst, buf); } /* skip over some common header extensions... http://searchnetworking.techtarget.com/originalContent/0,289142,sid7_gci870277,00.html */ while(nextHeader == 0 || /* hop */ nextHeader == 43 || /* routing */ nextHeader == 44 || /* fragment */ /* nextHeader == 50 => encryption - don't bother coz we'll not be able to read any further */ nextHeader == 51 || /* auth */ nextHeader == 60) { /* destination options */ uint32_t optionLen, skip; if (debug_sflow_parser) { logger << log4cpp::Priority::INFO << plugin_log_prefix << "IP6HeaderExtension: " << nextHeader; } nextHeader = ptr[0]; optionLen = 8 * (ptr[1] + 1); /* second byte gives option len in 8-byte chunks, not counting first 8 */ skip = optionLen - 2; ptr += skip; if (ptr > end) return; /* ran off the end of the header */ } /* now that we have eliminated the extension headers, nextHeader should have what we want to remember as the ip protocol... */ sample->dcd_ipProtocol = nextHeader; if (debug_sflow_parser) { logger << log4cpp::Priority::INFO << plugin_log_prefix << "IPProtocol: " << sample->dcd_ipProtocol; } decodeIPLayer4(sample, ptr); } void decode_ipv4_protocol(SFSample* sample) { char buf[51]; uint8_t* ptr = sample->header + sample->offsetToIPV4; /* Create a local copy of the IP header (cannot overlay structure in case it is not quad-aligned...some platforms would core-dump if we tried that). It's OK coz this probably performs just as well anyway. */ struct myiphdr ip; memcpy(&ip, ptr, sizeof(ip)); /* Value copy all ip elements into sample */ sample->ipsrc.type = SFLADDRESSTYPE_IP_V4; sample->ipsrc.address.ip_v4.addr = ip.saddr; sample->ipdst.type = SFLADDRESSTYPE_IP_V4; sample->ipdst.address.ip_v4.addr = ip.daddr; sample->dcd_ipProtocol = ip.protocol; sample->dcd_ipTos = ip.tos; sample->dcd_ipTTL = ip.ttl; // printf("ip.tot_len %d\n", ntohs(ip.tot_len)); /* Log out the decoded IP fields */ // printf("srcIP %s\n", printAddress(&sample->ipsrc, buf)); // printf("dstIP %s\n", printAddress(&sample->ipdst, buf)); // printf("IPProtocol %u\n", sample->dcd_ipProtocol); // printf("IPTOS %u\n", sample->dcd_ipTos); // printf("IPTTL %u\n", sample->dcd_ipTTL); /* check for fragments */ sample->ip_fragmentOffset = ntohs(ip.frag_off) & 0x1FFF; if (sample->ip_fragmentOffset > 0) { // printf("IPFragmentOffset %u\n", sample->ip_fragmentOffset); } else { /* advance the pointer to the next protocol layer */ /* ip headerLen is expressed as a number of quads */ ptr += (ip.version_and_headerLen & 0x0f) * 4; decodeIPLayer4(sample, ptr); } } fastnetmon-1.1.3+dfsg/src/sflow_plugin/sflow_collector.h000066400000000000000000000003511313534057500234660ustar00rootroot00000000000000#ifndef SFLOW_PLUGIN_H #define SFLOW_PLUGIN_H #include "../fastnetmon_types.h" #include "sflow_data.h" void start_sflow_collection(process_packet_pointer func_ptr); // For tests void read_sflow_datagram(SFSample* sample); #endif fastnetmon-1.1.3+dfsg/src/sflow_plugin/sflow_data.h000066400000000000000000000122641313534057500224170ustar00rootroot00000000000000#ifndef SFLOW_DATA_H #define SFLOW_DATA_H #include "sflow.h" #include // Packet headers for sFLOW v4 enum INMPacket_information_type { INMPACKETTYPE_HEADER = 1, /* Packet headers are sampled */ INMPACKETTYPE_IPV4 = 2, /* IP version 4 data */ INMPACKETTYPE_IPV6 = 3 /* IP version 4 data */ }; /* when I turn on optimisation with the Microsoft compiler it seems to change the values of these enumerated types and break the program - not sure why */ enum INMSample_types { FLOWSAMPLE = 1, COUNTERSSAMPLE = 2 }; /* same for tcp */ struct mytcphdr { uint16_t th_sport; /* source port */ uint16_t th_dport; /* destination port */ uint32_t th_seq; /* sequence number */ uint32_t th_ack; /* acknowledgement number */ uint8_t th_off_and_unused; uint8_t th_flags; uint16_t th_win; /* window */ uint16_t th_sum; /* checksum */ uint16_t th_urp; /* urgent pointer */ }; /* and UDP */ struct myudphdr { uint16_t uh_sport; /* source port */ uint16_t uh_dport; /* destination port */ uint16_t uh_ulen; /* udp length */ uint16_t uh_sum; /* udp checksum */ }; /* and ICMP */ struct myicmphdr { uint8_t type; /* message type */ uint8_t code; /* type sub-code */ /* ignore the rest */ }; /* define my own IP header struct - to ease portability */ struct myiphdr { uint8_t version_and_headerLen; uint8_t tos; uint16_t tot_len; uint16_t id; uint16_t frag_off; uint8_t ttl; uint8_t protocol; uint16_t check; uint32_t saddr; uint32_t daddr; }; #define SASAMPLE_EXTENDED_DATA_SWITCH 1 #define SASAMPLE_EXTENDED_DATA_ROUTER 4 #define SASAMPLE_EXTENDED_DATA_GATEWAY 8 #define SASAMPLE_EXTENDED_DATA_USER 16 #define SASAMPLE_EXTENDED_DATA_URL 32 #define SASAMPLE_EXTENDED_DATA_MPLS 64 #define SASAMPLE_EXTENDED_DATA_NAT 128 #define SASAMPLE_EXTENDED_DATA_MPLS_TUNNEL 256 #define SASAMPLE_EXTENDED_DATA_MPLS_VC 512 #define SASAMPLE_EXTENDED_DATA_MPLS_FTN 1024 #define SASAMPLE_EXTENDED_DATA_MPLS_LDP_FEC 2048 #define SASAMPLE_EXTENDED_DATA_VLAN_TUNNEL 4096 #define SASAMPLE_EXTENDED_DATA_NAT_PORT 8192 #define SA_MAX_EXTENDED_USER_LEN 200 #define SA_MAX_EXTENDED_URL_LEN 200 #define SA_MAX_EXTENDED_HOST_LEN 200 typedef struct _SFSample { SFLAddress sourceIP; SFLAddress agent_addr; uint32_t agentSubId; /* the raw pdu */ uint8_t* rawSample; uint32_t rawSampleLen; uint8_t* endp; time_t pcapTimestamp; /* decode cursor */ uint32_t* datap; uint32_t datagramVersion; uint32_t sampleType; uint32_t elementType; uint32_t ds_class; uint32_t ds_index; /* generic interface counter sample */ SFLIf_counters ifCounters; /* sample stream info */ uint32_t sysUpTime; uint32_t sequenceNo; uint32_t sampledPacketSize; uint32_t samplesGenerated; uint32_t meanSkipCount; uint32_t samplePool; uint32_t dropEvents; /* the sampled header */ uint32_t packet_data_tag; uint32_t headerProtocol; uint8_t* header; int headerLen; uint32_t stripped; /* header decode */ int gotIPV4; int gotIPV4Struct; int offsetToIPV4; int gotIPV6; int gotIPV6Struct; int offsetToIPV6; int offsetToPayload; SFLAddress ipsrc; SFLAddress ipdst; uint32_t dcd_ipProtocol; uint32_t dcd_ipTos; uint32_t dcd_ipTTL; uint32_t dcd_sport; uint32_t dcd_dport; uint32_t dcd_tcpFlags; uint32_t ip_fragmentOffset; uint32_t udp_pduLen; /* ports */ uint32_t inputPortFormat; uint32_t outputPortFormat; uint32_t inputPort; uint32_t outputPort; /* ethernet */ uint32_t eth_type; uint32_t eth_len; uint8_t eth_src[8]; uint8_t eth_dst[8]; /* vlan */ uint32_t in_vlan; uint32_t in_priority; uint32_t internalPriority; uint32_t out_vlan; uint32_t out_priority; int vlanFilterReject; /* extended data fields */ uint32_t num_extended; uint32_t extended_data_tag; /* IP forwarding info */ SFLAddress nextHop; uint32_t srcMask; uint32_t dstMask; /* BGP info */ SFLAddress bgp_nextHop; uint32_t my_as; uint32_t src_as; uint32_t src_peer_as; uint32_t dst_as_path_len; uint32_t* dst_as_path; /* note: version 4 dst as path segments just get printed, not stored here, however * the dst_peer and dst_as are filled in, since those are used for netflow encoding */ uint32_t dst_peer_as; uint32_t dst_as; uint32_t communities_len; uint32_t* communities; uint32_t localpref; /* user id */ uint32_t src_user_charset; uint32_t src_user_len; char src_user[SA_MAX_EXTENDED_USER_LEN + 1]; uint32_t dst_user_charset; uint32_t dst_user_len; char dst_user[SA_MAX_EXTENDED_USER_LEN + 1]; /* url */ uint32_t url_direction; uint32_t url_len; char url[SA_MAX_EXTENDED_URL_LEN + 1]; uint32_t host_len; char host[SA_MAX_EXTENDED_HOST_LEN + 1]; /* mpls */ SFLAddress mpls_nextHop; /* nat */ SFLAddress nat_src; SFLAddress nat_dst; /* counter blocks */ uint32_t statsSamplingInterval; uint32_t counterBlockVersion; /* exception handler context */ //jmp_buf env; } SFSample; #endif // SFLOW_DATA_H fastnetmon-1.1.3+dfsg/src/snabbswitch_plugin/000077500000000000000000000000001313534057500212735ustar00rootroot00000000000000fastnetmon-1.1.3+dfsg/src/snabbswitch_plugin/snabbswitch_collector.cpp000066400000000000000000000112451313534057500263570ustar00rootroot00000000000000// log4cpp logging facility #include "log4cpp/Category.hh" #include "log4cpp/Appender.hh" #include "log4cpp/FileAppender.hh" #include "log4cpp/OstreamAppender.hh" #include "log4cpp/Layout.hh" #include "log4cpp/BasicLayout.hh" #include "log4cpp/PatternLayout.hh" #include "log4cpp/Priority.hh" #include #include #include "../fast_library.h" // For support uint32_t, uint16_t #include // For config map operations #include #include #include #include #include #include "../fastnetmon_packet_parser.h" // For support: IPPROTO_TCP, IPPROTO_ICMP, IPPROTO_UDP #include #include #include #include "snabbswitch_collector.h" #ifdef __cplusplus extern "C" { #endif // This code defined in SnabbSwitch int start_snabb_switch(int snabb_argc, const char **snabb_argv); #ifdef __cplusplus } #endif // Get log4cpp logger from main programm extern log4cpp::Category& logger; // Pass unparsed packets number to main programm extern uint64_t total_unparsed_packets; // Global configuration map extern std::map configuration_map; // This variable name should be uniq for every plugin! process_packet_pointer snabbswitch_process_func_ptr = NULL; inline void firehose_packet(const char *pciaddr, char *data, int length); /* Intel 82599 "Legacy" receive descriptor format. * See Intel 82599 data sheet section 7.1.5. * http://www.intel.com/content/dam/www/public/us/en/documents/datasheets/82599-10-gbe-controller-datasheet.pdf */ struct firehose_rdesc { uint64_t address; uint16_t length; uint16_t cksum; uint8_t status; uint8_t errors; uint16_t vlan; } __attribute__((packed)); // We will use this code from Global Symbols table (originally it's defined in netmap collector.cpp) bool parse_raw_packet_to_simple_packet(u_char* buffer, int len, simple_packet& packet); void firehose_packet(const char *pciaddr, char *data, int length) { simple_packet packet; if (!parse_raw_packet_to_simple_packet((u_char*)data, length, packet)) { total_unparsed_packets++; return; } snabbswitch_process_func_ptr(packet); } #ifdef __cplusplus extern "C" { #endif int firehose_callback_v1(const char *pciaddr, char **packets, struct firehose_rdesc *rxring, int ring_size, int index); #ifdef __cplusplus } #endif int firehose_callback_v1(const char *pciaddr, char **packets, struct firehose_rdesc *rxring, int ring_size, int index) { while (rxring[index].status & 1) { int next_index = (index + 1) & (ring_size-1); __builtin_prefetch(packets[next_index]); firehose_packet(pciaddr, packets[index], rxring[index].length); rxring[index].status = 0; /* reset descriptor for reuse */ index = next_index; } return index; } void start_snabbswitch_collection(process_packet_pointer func_ptr) { logger << log4cpp::Priority::INFO << "SnabbSwitch plugin started"; snabbswitch_process_func_ptr = func_ptr; std::string interfaces_list = ""; if (configuration_map.count("interfaces_snabbswitch") != 0) { interfaces_list = configuration_map["interfaces_snabbswitch"]; } std::vector interfaces_for_capture; boost::split(interfaces_for_capture, interfaces_list, boost::is_any_of(","), boost::token_compress_on); if (interfaces_for_capture.size() == 0) { logger << log4cpp::Priority::ERROR << "Please specify list of PCI-e addresses for SnabbSwitch capture"; } logger << log4cpp::Priority::INFO << "SnabbSwitch will listen on " << interfaces_for_capture.size() << " interfaces"; boost::thread_group snabbswitch_main_threads; for (std::vector::iterator interface = interfaces_for_capture.begin(); interface != interfaces_for_capture.end(); ++interface) { // We could specify multiple NIC's for single thread with multiple --input const char* cli_arguments[5]; cli_arguments[0] = "snabb"; // emulate call of standard application cli_arguments[1] = "firehose"; cli_arguments[2] = "--input"; cli_arguments[3] = interface->c_str(); cli_arguments[4] ="weird_data"; int cli_number_of_arguments = sizeof(cli_arguments) / sizeof(char*); logger << log4cpp::Priority::INFO << "We are starting SnabbSwitch instance for PCIe interface " << *interface; snabbswitch_main_threads.add_thread( new boost::thread(start_snabb_switch, cli_number_of_arguments, cli_arguments) ); // We should sleep here because init code of SnabbSwitch is not thread safe sleep(10); } snabbswitch_main_threads.join_all(); } fastnetmon-1.1.3+dfsg/src/snabbswitch_plugin/snabbswitch_collector.h000066400000000000000000000002511313534057500260170ustar00rootroot00000000000000#ifndef SNABBSWITCH_PLUGIN_H #define SNABBSWITCH_PLUGIN_H #include "../fastnetmon_types.h" void start_snabbswitch_collection(process_packet_pointer func_ptr); #endif fastnetmon-1.1.3+dfsg/src/tests/000077500000000000000000000000001313534057500165505ustar00rootroot00000000000000fastnetmon-1.1.3+dfsg/src/tests/af_packet.cpp000066400000000000000000000153661313534057500212040ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #include /* the L2 protocols */ #include "../fastnetmon_packet_parser.h" /* Build it: g++ ../fastnetmon_packet_parser.c -ofastnetmon_packet_parser.o -c g++ af_packet.cpp fastnetmon_packet_parser.o -lboost_thread -lboost_system -lpthread */ // Copy and paste from netmap code void consume_pkt(u_char* buffer, int len) { /* struct pfring_pkthdr packet_header; memset(&packet_header, 0, sizeof(packet_header)); packet_header.len = len; packet_header.caplen = len; // We do not calculate timestamps because timestamping is very CPU intensive operation: // https://github.com/ntop/PF_RING/issues/9 u_int8_t timestamp = 0; u_int8_t add_hash = 0; fastnetmon_parse_pkt((u_char*)buffer, &packet_header, 4, timestamp, add_hash); */ //char print_buffer[512]; //fastnetmon_print_parsed_pkt(print_buffer, 512, (u_char*)buffer, &packet_header); //printf("%s\n", print_buffer); // logger.info("%s", print_buffer); } // Get interface number by name int get_interface_number_by_device_name(int socket_fd, std::string interface_name) { struct ifreq ifr; memset(&ifr, 0, sizeof(ifr)); if (interface_name.size() > IFNAMSIZ) { return -1; } strncpy(ifr.ifr_name, interface_name.c_str(), sizeof(ifr.ifr_name)); if (ioctl(socket_fd, SIOCGIFINDEX, &ifr) == -1) { return -1; } return ifr.ifr_ifindex; } unsigned int af_packet_threads = 1; uint64_t received_packets = 0; void speed_printer() { while (true) { uint64_t packets_before = received_packets; boost::this_thread::sleep(boost::posix_time::seconds(1)); uint64_t packets_after = received_packets; uint64_t pps = packets_after - packets_before; printf("We process: %llu pps\n", pps); } } int setup_socket(std::string interface_name, int fanout_group_id) { // More details here: http://man7.org/linux/man-pages/man7/packet.7.html // We could use SOCK_RAW or SOCK_DGRAM for second argument // SOCK_RAW - raw packets pass from the kernel // SOCK_DGRAM - some amount of processing // Third argument manage ether type of captured packets int packet_socket = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL)); if (packet_socket == -1) { printf("Can't create AF_PACKET socket\n"); return -1; } int interface_number = get_interface_number_by_device_name(packet_socket, interface_name); if (interface_number == -1) { printf("Can't get interface number by interface name\n"); return -1; } // Switch to PROMISC mode struct packet_mreq sock_params; memset(&sock_params, 0, sizeof(sock_params)); sock_params.mr_type = PACKET_MR_PROMISC; sock_params.mr_ifindex = interface_number; int set_promisc = setsockopt(packet_socket, SOL_PACKET, PACKET_ADD_MEMBERSHIP, (void *)&sock_params, sizeof(sock_params)); if (set_promisc == -1) { printf("Can't enable promisc mode\n"); return -1; } struct sockaddr_ll bind_address; memset(&bind_address, 0, sizeof(bind_address)); bind_address.sll_family = AF_PACKET; bind_address.sll_protocol = htons(ETH_P_ALL); bind_address.sll_ifindex = interface_number; // We will follow http://yusufonlinux.blogspot.ru/2010/11/data-link-access-and-zero-copy.html // And this: https://www.kernel.org/doc/Documentation/networking/packet_mmap.txt /* struct tpacket_req req; memset(&req, 0, sizeof(req); setsockopt(packet_socket, SOL_PACKET , PACKET_RX_RING , (void*)&req , sizeof(req)); setsockopt(packet_socket, SOL_PACKET , PACKET_TX_RING , (void*)&req , sizeof(req)); */ int bind_result = bind(packet_socket, (struct sockaddr *)&bind_address, sizeof(bind_address)); if (bind_result == -1) { printf("Can't bind to AF_PACKET socket\n"); return -1; } if (fanout_group_id) { // PACKET_FANOUT_LB - round robin // PACKET_FANOUT_CPU - send packets to CPU where packet arrived int fanout_type = PACKET_FANOUT_CPU; int fanout_arg = (fanout_group_id | (fanout_type << 16)); int setsockopt_fanout = setsockopt(packet_socket, SOL_PACKET, PACKET_FANOUT, &fanout_arg, sizeof(fanout_arg)); if (setsockopt_fanout < 0) { printf("Can't configure fanout\n"); return -1; } } // Most challenging option: PACKET_TX_RING return packet_socket; } void start_af_packet_capture(std::string interface_name, int fanout_group_id) { int packet_socket = setup_socket(interface_name, fanout_group_id); if (packet_socket == -1) { printf("Can't create socket\n"); return; } unsigned int capture_length = 1500; char buffer[capture_length]; while (true) { received_packets++; int readed_bytes = read(packet_socket, buffer, capture_length); // printf("Got %d bytes from interface\n", readed_bytes); consume_pkt((u_char*)buffer, readed_bytes); if (readed_bytes < 0) { break; } } } void get_af_packet_stats() { // getsockopt PACKET_STATISTICS } bool use_multiple_fanout_processes = true; // Could get some speed up on NUMA servers bool execute_strict_cpu_affinity = false; int main() { boost::thread speed_printer_thread( speed_printer ); int fanout_group_id = getpid() & 0xffff; if (use_multiple_fanout_processes) { boost::thread_group packet_receiver_thread_group; unsigned int num_cpus = 8; for (int cpu = 0; cpu < num_cpus; cpu++) { boost::thread::attributes thread_attrs; if (execute_strict_cpu_affinity) { cpu_set_t current_cpu_set; int cpu_to_bind = cpu % num_cpus; CPU_ZERO(¤t_cpu_set); // We count cpus from zero CPU_SET(cpu_to_bind, ¤t_cpu_set); int set_affinity_result = pthread_attr_setaffinity_np(thread_attrs.native_handle(), sizeof(cpu_set_t), ¤t_cpu_set); if (set_affinity_result != 0) { printf("Can't set CPU affinity for thread\n"); } } packet_receiver_thread_group.add_thread( new boost::thread(thread_attrs, boost::bind(start_af_packet_capture, "eth6", fanout_group_id)) ); } // Wait all processes for finish packet_receiver_thread_group.join_all(); } else { start_af_packet_capture("eth6", 0); } speed_printer_thread.join(); } fastnetmon-1.1.3+dfsg/src/tests/af_packet_ring.cpp000066400000000000000000000217261313534057500222200ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #include #include #include /* the L2 protocols */ #include "../fastnetmon_packet_parser.h" // 4194304 bytes unsigned int blocksiz = 1 << 22; // 2048 bytes unsigned int framesiz = 1 << 11; unsigned int blocknum = 64; struct block_desc { uint32_t version; uint32_t offset_to_priv; struct tpacket_hdr_v1 h1; }; /* Build it: g++ ../fastnetmon_packet_parser.c -ofastnetmon_packet_parser.o -c g++ af_packet.cpp fastnetmon_packet_parser.o -lboost_thread -lboost_system -lpthread */ // Get interface number by name int get_interface_number_by_device_name(int socket_fd, std::string interface_name) { struct ifreq ifr; memset(&ifr, 0, sizeof(ifr)); if (interface_name.size() > IFNAMSIZ) { return -1; } strncpy(ifr.ifr_name, interface_name.c_str(), sizeof(ifr.ifr_name)); if (ioctl(socket_fd, SIOCGIFINDEX, &ifr) == -1) { return -1; } return ifr.ifr_ifindex; } unsigned int af_packet_threads = 1; uint64_t received_packets = 0; uint64_t received_bytes = 0; void speed_printer() { while (true) { uint64_t packets_before = received_packets; boost::this_thread::sleep(boost::posix_time::seconds(1)); uint64_t packets_after = received_packets; uint64_t pps = packets_after - packets_before; printf("We process: %llu pps\n", pps); } } void flush_block(struct block_desc *pbd) { pbd->h1.block_status = TP_STATUS_KERNEL; } void walk_block(struct block_desc *pbd, const int block_num) { int num_pkts = pbd->h1.num_pkts, i; unsigned long bytes = 0; struct tpacket3_hdr *ppd; ppd = (struct tpacket3_hdr *) ((uint8_t *) pbd + pbd->h1.offset_to_first_pkt); for (i = 0; i < num_pkts; ++i) { bytes += ppd->tp_snaplen; // struct ethhdr *eth = (struct ethhdr *) ((uint8_t *) ppd + ppd->tp_mac); // Print packets // #define PRINT_PACKETS #ifdef PRINT_PACKETS struct pfring_pkthdr packet_header; memset(&packet_header, 0, sizeof(packet_header)); packet_header.len = ppd->tp_snaplen; packet_header.caplen = ppd->tp_snaplen; u_int8_t timestamp = 0; u_int8_t add_hash = 0; u_char* data_pointer = (u_char*)((uint8_t *) ppd + ppd->tp_mac); fastnetmon_parse_pkt(data_pointer, &packet_header, 4, timestamp, add_hash); char print_buffer[512]; fastnetmon_print_parsed_pkt(print_buffer, 512, data_pointer, &packet_header); printf("%s\n", print_buffer); #endif ppd = (struct tpacket3_hdr *) ((uint8_t *) ppd + ppd->tp_next_offset); } received_packets += num_pkts; received_bytes += bytes; } int setup_socket(std::string interface_name, int fanout_group_id) { // More details here: http://man7.org/linux/man-pages/man7/packet.7.html // We could use SOCK_RAW or SOCK_DGRAM for second argument // SOCK_RAW - raw packets pass from the kernel // SOCK_DGRAM - some amount of processing // Third argument manage ether type of captured packets int packet_socket = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL)); if (packet_socket == -1) { printf("Can't create AF_PACKET socket\n"); return -1; } // We whould use V3 bcause it could read/pool in per block basis instead per packet int version = TPACKET_V3; int setsockopt_packet_version = setsockopt(packet_socket, SOL_PACKET, PACKET_VERSION, &version, sizeof(version)); if (setsockopt_packet_version < 0) { printf("Can't set packet v3 version\n"); return -1; } int interface_number = get_interface_number_by_device_name(packet_socket, interface_name); if (interface_number == -1) { printf("Can't get interface number by interface name\n"); return -1; } // Switch to PROMISC mode struct packet_mreq sock_params; memset(&sock_params, 0, sizeof(sock_params)); sock_params.mr_type = PACKET_MR_PROMISC; sock_params.mr_ifindex = interface_number; int set_promisc = setsockopt(packet_socket, SOL_PACKET, PACKET_ADD_MEMBERSHIP, (void *)&sock_params, sizeof(sock_params)); if (set_promisc == -1) { printf("Can't enable promisc mode\n"); return -1; } struct sockaddr_ll bind_address; memset(&bind_address, 0, sizeof(bind_address)); bind_address.sll_family = AF_PACKET; bind_address.sll_protocol = htons(ETH_P_ALL); bind_address.sll_ifindex = interface_number; // We will follow http://yusufonlinux.blogspot.ru/2010/11/data-link-access-and-zero-copy.html // And this: https://www.kernel.org/doc/Documentation/networking/packet_mmap.txt struct tpacket_req3 req; memset(&req, 0, sizeof(req)); req.tp_block_size = blocksiz; req.tp_frame_size = framesiz; req.tp_block_nr = blocknum; req.tp_frame_nr = (blocksiz * blocknum) / framesiz; req.tp_retire_blk_tov = 60; // Timeout in msec req.tp_feature_req_word = TP_FT_REQ_FILL_RXHASH; int setsockopt_rx_ring = setsockopt(packet_socket, SOL_PACKET , PACKET_RX_RING , (void*)&req , sizeof(req)); if (setsockopt_rx_ring == -1) { printf("Can't enable RX_RING for AF_PACKET socket\n"); return -1; } // We use per thread structures uint8_t* mapped_buffer = NULL; struct iovec* rd = NULL; mapped_buffer = (uint8_t*)mmap(NULL, req.tp_block_size * req.tp_block_nr, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_LOCKED, packet_socket, 0); if (mapped_buffer == MAP_FAILED) { printf("mmap failed!\n"); return -1; } // Allocate iov structure for each block rd = (struct iovec*)malloc(req.tp_block_nr * sizeof(struct iovec)); // Initilize iov structures for (int i = 0; i < req.tp_block_nr; ++i) { rd[i].iov_base = mapped_buffer + (i * req.tp_block_size); rd[i].iov_len = req.tp_block_size; } int bind_result = bind(packet_socket, (struct sockaddr *)&bind_address, sizeof(bind_address)); if (bind_result == -1) { printf("Can't bind to AF_PACKET socket\n"); return -1; } if (fanout_group_id) { // PACKET_FANOUT_LB - round robin // PACKET_FANOUT_CPU - send packets to CPU where packet arrived int fanout_type = PACKET_FANOUT_CPU; int fanout_arg = (fanout_group_id | (fanout_type << 16)); int setsockopt_fanout = setsockopt(packet_socket, SOL_PACKET, PACKET_FANOUT, &fanout_arg, sizeof(fanout_arg)); if (setsockopt_fanout < 0) { printf("Can't configure fanout\n"); return -1; } } unsigned int current_block_num = 0; struct pollfd pfd; memset(&pfd, 0, sizeof(pfd)); pfd.fd = packet_socket; pfd.events = POLLIN | POLLERR; pfd.revents = 0; while (true) { struct block_desc *pbd = (struct block_desc *) rd[current_block_num].iov_base; if ((pbd->h1.block_status & TP_STATUS_USER) == 0) { poll(&pfd, 1, -1); continue; } walk_block(pbd, current_block_num); flush_block(pbd); current_block_num = (current_block_num + 1) % blocknum; } return packet_socket; } void start_af_packet_capture(std::string interface_name, int fanout_group_id) { setup_socket(interface_name, fanout_group_id); } void get_af_packet_stats() { // getsockopt PACKET_STATISTICS } // Could get some speed up on NUMA servers bool execute_strict_cpu_affinity = false; bool use_multiple_fanout_processes = true; int main() { int fanout_group_id = getpid() & 0xffff; boost::thread speed_printer_thread( speed_printer ); if (use_multiple_fanout_processes) { boost::thread_group packet_receiver_thread_group; unsigned int num_cpus = 8; for (int cpu = 0; cpu < num_cpus; cpu++) { boost::thread::attributes thread_attrs; if (execute_strict_cpu_affinity) { cpu_set_t current_cpu_set; int cpu_to_bind = cpu % num_cpus; CPU_ZERO(¤t_cpu_set); // We count cpus from zero CPU_SET(cpu_to_bind, ¤t_cpu_set); int set_affinity_result = pthread_attr_setaffinity_np(thread_attrs.native_handle(), sizeof(cpu_set_t), ¤t_cpu_set); if (set_affinity_result != 0) { printf("Can't set CPU affinity for thread\n"); } } packet_receiver_thread_group.add_thread( new boost::thread(thread_attrs, boost::bind(start_af_packet_capture, "eth6", fanout_group_id)) ); } // Wait all processes for finish packet_receiver_thread_group.join_all(); } else { start_af_packet_capture("eth6", 0); } speed_printer_thread.join(); } fastnetmon-1.1.3+dfsg/src/tests/build_lpm_test.bash000077500000000000000000000006741313534057500224270ustar00rootroot00000000000000#!/usr/bin/env bash COMPILER=clang CPP_COMPILER=clang++ gcc -g -pg -O2 ../libpatricia/patricia.c -c -o patricia.o g++ -g -pg -O2 lpm_performance_tests.cpp patricia.o -olpm_performance_tests -lrt #$COMPILER -O4 ../libpatricia/patricia.c -c -o patricia.o #ar q patricia.a patricia.o #$CPP_COMPILER lpm_performance_tests.cpp -olpm_performance_tests.o -c #$CPP_COMPILER -v -O4 lpm_performance_tests.o patricia.a -olpm_performance_tests -lrt fastnetmon-1.1.3+dfsg/src/tests/build_netmap.bash000077500000000000000000000003711313534057500220560ustar00rootroot00000000000000#!/usr/bin/env bash clang++ fastnetmon_packet_parser.cpp -c -ofastnetmon_packet_parser.o clang++ netmap.cpp -I/usr/local/include -L/usr/local/lib -I/usr/src/fastnetmon/tests/netmap_includes -lboost_thread -lboost_system fastnetmon_packet_parser.o fastnetmon-1.1.3+dfsg/src/tests/conntrack_prototype.cpp000066400000000000000000000120661313534057500233700ustar00rootroot00000000000000#include #include #include #include #include #include #include "../fastnetmon_types.h" // It's very raw API implementation for connection tracking code. Due to HUGE amount of collisions it's very slow: ~1Mpps // For performance it's very close to std::map but much times more buggy :) // https://code.google.com/p/smhasher/source/browse/trunk/MurmurHash2.cpp // 64-bit hash for 64-bit platforms #define BIG_CONSTANT(x) (x##LLU) uint64_t MurmurHash64A(const void* key, int len, uint64_t seed) { const uint64_t m = BIG_CONSTANT(0xc6a4a7935bd1e995); const int r = 47; uint64_t h = seed ^ (len * m); const uint64_t* data = (const uint64_t*)key; const uint64_t* end = data + (len / 8); while (data != end) { uint64_t k = *data++; k *= m; k ^= k >> r; k *= m; h ^= k; h *= m; } const unsigned char* data2 = (const unsigned char*)data; switch (len & 7) { case 7: h ^= uint64_t(data2[6]) << 48; case 6: h ^= uint64_t(data2[5]) << 40; case 5: h ^= uint64_t(data2[4]) << 32; case 4: h ^= uint64_t(data2[3]) << 24; case 3: h ^= uint64_t(data2[2]) << 16; case 2: h ^= uint64_t(data2[1]) << 8; case 1: h ^= uint64_t(data2[0]); h *= m; }; h ^= h >> r; h *= m; h ^= h >> r; return h; } class conntrack_hash_struct_for_simple_packet_t { public: uint32_t src_ip; uint32_t dst_ip; uint16_t source_port; uint16_t destination_port; unsigned int protocol; bool operator==(const conntrack_hash_struct_for_simple_packet_t& rhs) { // TODO: not so smart, we should fix this! return memcmp(this, &rhs, sizeof(conntrack_hash_struct_for_simple_packet_t)) == 0; } }; // Extract only important for us fields from main simple_packet structure bool convert_simple_packet_toconntrack_hash_struct(simple_packet& packet, conntrack_hash_struct_for_simple_packet_t& conntrack_struct) { conntrack_struct.src_ip = packet.src_ip; conntrack_struct.dst_ip = packet.dst_ip; conntrack_struct.protocol = packet.protocol; conntrack_struct.source_port = packet.source_port; conntrack_struct.destination_port = packet.destination_port; } // Class prototype for connection tracking typedef std::vector< conntrack_hash_struct_for_simple_packet_t > vector_of_connetrack_structs_t; class connection_tracking_fast_storage_t { public: connection_tracking_fast_storage_t(unsigned int structure_size) { murmur_seed = 13; max_vector_size = 0; number_of_buckets = structure_size; buckets_storage.reserve(structure_size); } uint64_t get_bucket_number(conntrack_hash_struct_for_simple_packet_t& element) { uint64_t conntrack_hash = MurmurHash64A(&element, sizeof(conntrack_hash_struct_for_simple_packet_t), murmur_seed); return conntrack_hash % number_of_buckets; } bool lookup(conntrack_hash_struct_for_simple_packet_t* element) { uint64_t bucket_number = get_bucket_number(*element); vector_of_connetrack_structs_t* vector_pointer = &buckets_storage[bucket_number]; unsigned int vector_size = vector_pointer->size(); if (vector_size > max_vector_size) { max_vector_size = vector_size; if (max_vector_size > 100) { printf("We got %u collisions for key %llu\n", max_vector_size, bucket_number); } } if (vector_size == 0) { return false; } vector_of_connetrack_structs_t::iterator itr = std::find(vector_pointer->begin(), vector_pointer->end(), *element); if (itr == vector_pointer->end()) { return false; } return true; } bool insert(conntrack_hash_struct_for_simple_packet_t element) { uint64_t bucket_number = get_bucket_number(element); buckets_storage[bucket_number].push_back(element); } public: unsigned int number_of_buckets; std::vector buckets_storage; unsigned int murmur_seed; // conntrack_hash_struct_for_simple_packet_t conntrack_structure; unsigned int max_vector_size; }; connection_tracking_fast_storage_t my_conntrack_storage(32000); int main() { // fake data char data[1500]; simple_packet current_packet; // parse_raw_packet_to_simple_packet((u_char*)data, length, current_packet); conntrack_hash_struct_for_simple_packet_t conntrack_structure; convert_simple_packet_toconntrack_hash_struct(current_packet, conntrack_structure); if (my_conntrack_storage.lookup(&conntrack_structure)) { //printf("Already exists\n"); // found it } else { //printf("New\n"); my_conntrack_storage.insert(conntrack_structure); } } fastnetmon-1.1.3+dfsg/src/tests/exabgp_pipe.c000066400000000000000000000024061313534057500212010ustar00rootroot00000000000000#include #include #include #include #include #include int ban_ip() { int exabgp_pipe = open("/var/run/exabgp.cmd", O_WRONLY); if (exabgp_pipe <= 0) { printf("Can't open exabgp PIPE"); exit(1); } char bgp_message[256]; char* ip_cidr_form = "10.10.10.123/32"; char* next_hop = "10.0.3.114"; char* exabgp_community = "65001:666"; // withdraw char* action = "announce"; sprintf(bgp_message, "%s route %s next-hop %s community %s\n", action, ip_cidr_form, next_hop, exabgp_community); int wrote_bytes = write(exabgp_pipe, bgp_message, strlen(bgp_message)); printf("We wrote %d bytes\n", wrote_bytes); close(exabgp_pipe); } int unban_ip() { char bgp_message[256]; char* ip_cidr_form = "10.10.10.123/32"; int exabgp_pipe = open("/var/run/exabgp.cmd", O_WRONLY); if (exabgp_pipe <= 0) { printf("Can't open exabgp PIPE"); exit(1); } char* action = "withdraw"; sprintf(bgp_message, "%s route %s\n", action, ip_cidr_form); int wrote_bytes = write(exabgp_pipe, bgp_message, strlen(bgp_message)); printf("We wrote %d bytes\n", wrote_bytes); close(exabgp_pipe); } int main() { unban_ip(); } fastnetmon-1.1.3+dfsg/src/tests/ip_lookup.cpp000066400000000000000000000212511313534057500212560ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace std; vector exec(string cmd) { vector output_list; FILE* pipe = popen(cmd.c_str(), "r"); if (!pipe) return output_list; char buffer[256]; std::string result = ""; while (!feof(pipe)) { if (fgets(buffer, 256, pipe) != NULL) { size_t newbuflen = strlen(buffer); // remove newline at the end if (buffer[newbuflen - 1] == '\n') { buffer[newbuflen - 1] = '\0'; } output_list.push_back(buffer); } } pclose(pipe); return output_list; } typedef pair subnet; bool belongs_to_networks(vector& networks_list, uint32_t ip) { for (vector::iterator ii = networks_list.begin(); ii != networks_list.end(); ++ii) { if ((ip & (*ii).second) == ((*ii).first & (*ii).second)) { return true; } } return false; } uint32_t convert_ip_as_string_to_uint(string ip) { struct in_addr ip_addr; inet_aton(ip.c_str(), &ip_addr); // in network byte order return ip_addr.s_addr; } uint32_t convert_cidr_to_binary_netmask(int cidr) { uint32_t binary_netmask = 0xFFFFFFFF; binary_netmask = binary_netmask << (32 - cidr); // htonl from host byte order to network // ntohl from network byte order to host // I suppose we need network byte order here return htonl(binary_netmask); } int get_bit(uint32_t number, uint32_t ip) { return 1; } typedef struct leaf { bool bit; struct leaf* right, *left; } tree_leaf; #include void insert_prefix_bitwise_tree(tree_leaf* root, string subnet, int cidr_mask) { uint32_t netmask_as_int = convert_ip_as_string_to_uint(subnet); // std::cout<(netmask_as_int)<= 32 - cidr_mask; i--) { uint32_t result_bit = netmask_as_int & (1 << i); bool bit = result_bit == 0 ? false : true; // cout<<"Insert: "<right != NULL) { // Elemelnt already there, just switch pointer temp_root = temp_root->right; } else { // No element here, we should create it tree_leaf* new_leaf = new tree_leaf; new_leaf->right = new_leaf->left = NULL; new_leaf->bit = bit; temp_root->right = new_leaf; temp_root = new_leaf; } } else { // check left subtree if (temp_root->left != NULL) { // Elemelnt already there, just switch pointer temp_root = temp_root->left; } else { // No element here, we should create it tree_leaf* new_leaf = new tree_leaf; new_leaf->right = new_leaf->left = NULL; new_leaf->bit = bit; temp_root->left = new_leaf; temp_root = new_leaf; } } } // #include // std::cout<(netmask_as_int)<left == NULL && temp_root->right == NULL)) { return false; } // convert to host byte order ip = ntohl(ip); int bits_matched = 0; for (int i = 31; i >= 0; i--) { // cout<<"bit"<left == NULL && temp_root->right == NULL)) { if (bits_matched > 0) { // if we havent child elemets (leaf is terinal) and we have match for single bit at lease // thus, we found mask! // std::cout<<"Bits matched: "<right != NULL) { temp_root = temp_root->right; bits_matched++; } else { if (temp_root->left != NULL) { return false; } else { // already checked above } } } else { if (temp_root->left != NULL) { temp_root = temp_root->left; bits_matched++; } else { if (temp_root->right != NULL) { return false; } else { // already checked above } } } } // We will repeat same checks as in begin of function. But we need it because // we could pass cycle and do not hit any terminals - both childs become zeroes if ((temp_root->left == NULL && temp_root->right == NULL)) { if (bits_matched > 0) { // if we havent child elemets (leaf is terinal) and we have match for single bit at lease // thus, we found mask! // std::cout<<"Bits matched: "<left = root->right = NULL; // uint32_t ip_127 = convert_ip_as_string_to_uint("127.0.0.3"); // uint32_t ip_159 = convert_ip_as_string_to_uint("159.253.17.1"); // uint32_t ip_8 = convert_ip_as_string_to_uint("255.8.8.8"); // insert_prefix_bitwise_tree(root, "159.253.17.0", 24); // insert_prefix_bitwise_tree(root, "159.253.16.0", 24); // insert_prefix_bitwise_tree(root, "127.0.0.1", 24); // insert_prefix_bitwise_tree(root, "255.8.8.8", 32); // std::cout< networks_list_as_string; vector our_networks; vector network_list_from_config = exec("cat /etc/networks_list"); networks_list_as_string.insert(networks_list_as_string.end(), network_list_from_config.begin(), network_list_from_config.end()); for (vector::iterator ii = networks_list_as_string.begin(); ii != networks_list_as_string.end(); ++ii) { vector subnet_as_string; split(subnet_as_string, *ii, boost::is_any_of("/"), boost::token_compress_on); int cidr = atoi(subnet_as_string[1].c_str()); uint32_t subnet_as_int = convert_ip_as_string_to_uint(subnet_as_string[0]); uint32_t netmask_as_int = convert_cidr_to_binary_netmask(cidr); insert_prefix_bitwise_tree(root, subnet_as_string[0], cidr); subnet current_subnet = std::make_pair(subnet_as_int, netmask_as_int); our_networks.push_back(current_subnet); } uint32_t my_ip = convert_ip_as_string_to_uint("192.0.0.192"); // my_ip = ntohl(my_ip); // std::bitset<32> x(my_ip); // std::cout< #include int main() { for (int i = 0; i < 10000000; i++) { json_object * jobj = json_object_new_object(); /*Creating a json array*/ json_object *jarray = json_object_new_array(); /*Creating json strings*/ json_object *jstring1 = json_object_new_string("c"); json_object *jstring2 = json_object_new_string("c++"); json_object *jstring3 = json_object_new_string("php"); /*Adding the above created json strings to the array*/ json_object_array_add(jarray,jstring1); json_object_array_add(jarray,jstring2); json_object_array_add(jarray,jstring3); json_object_object_add(jobj, "languages", jarray); // After _array_add and _object_add operations all ownership moves to obj and will be freed up with jobj /*Now printing the json object*/ //printf ("The json object created: %sn", json_object_to_json_string(jobj)); // Free up memory json_object_put(jobj); } } fastnetmon-1.1.3+dfsg/src/tests/lpm_performance_tests.cpp000066400000000000000000000150021313534057500236450ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #include #include #include "../libpatricia/patricia.h" using namespace std; // main data structure for storing traffic and speed data for all our IPs class map_element { public: map_element() : in_bytes(0), out_bytes(0), in_packets(0), out_packets(0), tcp_in_packets(0), tcp_out_packets(0), tcp_in_bytes(0), tcp_out_bytes(0), udp_in_packets(0), udp_out_packets(0), udp_in_bytes(0), udp_out_bytes(0), in_flows(0), out_flows(0), icmp_in_packets(0), icmp_out_packets(0), icmp_in_bytes(0), icmp_out_bytes(0) { } unsigned int in_bytes; unsigned int out_bytes; unsigned int in_packets; unsigned int out_packets; // Additional data for correct attack protocol detection unsigned int tcp_in_packets; unsigned int tcp_out_packets; unsigned int tcp_in_bytes; unsigned int tcp_out_bytes; unsigned int udp_in_packets; unsigned int udp_out_packets; unsigned int udp_in_bytes; unsigned int udp_out_bytes; unsigned int icmp_in_packets; unsigned int icmp_out_packets; unsigned int icmp_in_bytes; unsigned int icmp_out_bytes; unsigned int in_flows; unsigned int out_flows; }; typedef vector vector_of_counters; typedef std::map map_of_vector_counters; typedef std::pair pair_of_subnets_with_key; typedef vector vector_of_vector_counters; map_of_vector_counters SubnetVectorMap; vector_of_vector_counters SubnetVectorVector; #include void subnet_vectors_allocator(prefix_t* prefix, void* data) { uint32_t subnet_as_integer = prefix->add.sin.s_addr; u_short bitlen = prefix->bitlen; int network_size_in_ips = pow(2, 32 - bitlen); network_size_in_ips = 1; SubnetVectorMap[subnet_as_integer] = new vector_of_counters(network_size_in_ips); pair_of_subnets_with_key my_pair; my_pair.first = subnet_as_integer; my_pair.second = new vector_of_counters(network_size_in_ips); SubnetVectorVector.push_back(my_pair); } void suxx_func(unsigned long suxx) { } uint32_t convert_ip_as_string_to_uint(string ip) { struct in_addr ip_addr; inet_aton(ip.c_str(), &ip_addr); // in network byte order return ip_addr.s_addr; } bool mysortfunction(pair_of_subnets_with_key i, pair_of_subnets_with_key j) { return (i.first < j.first); } int main() { patricia_tree_t* lookup_tree; lookup_tree = New_Patricia(32); make_and_lookup(lookup_tree, "46.36.216.0/21"); make_and_lookup(lookup_tree, "159.253.16.0/21"); make_and_lookup(lookup_tree, "5.45.112.0/21"); make_and_lookup(lookup_tree, "5.45.120.0/21"); make_and_lookup(lookup_tree, "5.101.112.0/21"); make_and_lookup(lookup_tree, "5.101.120.0/21"); make_and_lookup(lookup_tree, "185.4.72.0/22"); make_and_lookup(lookup_tree, "181.114.240.0/20"); make_and_lookup(lookup_tree, "193.42.142.0/24"); // patricia_process (lookup_tree, (void_fn_t)subnet_vectors_allocator); // std::sort(SubnetVectorVector.begin(), SubnetVectorVector.end(), mysortfunction); prefix_t prefix_for_check_adreess; prefix_for_check_adreess.family = AF_INET; prefix_for_check_adreess.bitlen = 32; patricia_node_t* found_patrica_node = NULL; // prefix_for_check_adreess.add.sin.s_addr = 123123123; // std::map lpm_cache; // Without cache: 16.7 million of operations int i_iter = 100; // Million operations int j_iter = 1000000; // printf("Preallocate table\n"); // Iterate over all our IP addresses // for (int j = 0; j < j_iter; j++) { // for (int i = 0; i < i_iter; i++) { // lpm_cache[i*j] = true; // } //} printf("Start tests\n"); timespec start_time; clock_gettime(CLOCK_REALTIME, &start_time); prefix_for_check_adreess.add.sin.s_addr = convert_ip_as_string_to_uint("159.253.17.1"); for (int j = 0; j < j_iter; j++) { for (int i = 0; i < i_iter; i++) { // Random Pseudo IP // prefix_for_check_adreess.add.sin.s_addr = i*j; patricia_node_t* found_patrica_node = patricia_search_best2(lookup_tree, &prefix_for_check_adreess, 1); unsigned long destination_subnet = 0; suxx_func(found_patrica_node != NULL); if (found_patrica_node != NULL) { destination_subnet = found_patrica_node->prefix->add.sin.s_addr; suxx_func(destination_subnet); // std::cout<<"*"; /* for (vector_of_vector_counters::iterator it = SubnetVectorVector.begin() ; it != SubnetVectorVector.end(); ++it) { std::cout<first<<","; if (it->first == destination_subnet) { suxx_func(destination_subnet); } } std::cout<<"\n"; */ /* map_of_vector_counters::iterator itr; itr = SubnetVectorMap.find(destination_subnet); if (itr == SubnetVectorMap.end()) { } else { suxx_func(destination_subnet); } */ } // prefix_for_check_adreess.add.sin.s_addr = i*j + 1; // patricia_node_t* found_second_patrica_node = patricia_search_best(lookup_tree, // &prefix_for_check_adreess); // std::map ::iterator itr = lpm_cache.find(i*j); // if (itr != lpm_cache.end()) { // found it! //} else { // cache miss // bool result = patricia_search_best(lookup_tree, &prefix_for_check_adreess) != NULL; // lpm_cache[i*j] = result; // not found! //} } } timespec finish_time; clock_gettime(CLOCK_REALTIME, &finish_time); unsigned long used_seconds = finish_time.tv_sec - start_time.tv_sec; unsigned long total_ops = i_iter * j_iter; float megaops_per_second = (float)total_ops / (float)used_seconds / 1000000; printf("Total time is %d seconds total ops: %d\nMillion of ops per second: %.1f\n", used_seconds, total_ops, megaops_per_second); Destroy_Patricia(lookup_tree, (void_fn_t)0); } fastnetmon-1.1.3+dfsg/src/tests/lru_cache/000077500000000000000000000000001313534057500204755ustar00rootroot00000000000000fastnetmon-1.1.3+dfsg/src/tests/lru_cache/README000066400000000000000000000000601313534057500213510ustar00rootroot00000000000000https://patrickaudley.com/code/project/lrucache fastnetmon-1.1.3+dfsg/src/tests/lru_cache/lru_cache.cpp000066400000000000000000000152311313534057500231300ustar00rootroot00000000000000/*************************************************************************** * Copyright (C) 2004-2011 by Patrick Audley * * paudley@blackcat.ca * * http://patrickaudley.com * * * ***************************************************************************/ /** * @file lru_cache.cpp Template cache with an LRU removal policy (unit tests) * @author Patrick Audley */ #include "lru_cache.h" #ifdef UNITTEST #include "unit_test.h" #include #include /// LRUCache type for use in the unit tests typedef LRUCache unit_lru_type; /// LRUCache POD type for use in the unit tests typedef LRUCache unit_lru_type2; /// Data class for testing the scoping issues with const refs class test_big_data { public: char buffer[1000]; }; /// LRUCache with large data for use in the unit tests typedef LRUCache unit_lru_type3; /// Dumps the cache for debugging. std::string dump(unit_lru_type* L) { unit_lru_type::Key_List _list(L->get_all_keys()); std::string ret(""); for (unit_lru_type::Key_List_Iter liter = _list.begin(); liter != _list.end(); liter++) { ret.append(*liter); ret.append(":"); ret.append(L->fetch(*liter, false)); ret.append("\n"); } // std::cout << "Dump--" << std::endl << ret << "----" << std::endl; return ret; } /// Scoping test object unit_lru_type3* L3; UNIT_TEST_DEFINES /** @test Basic creation and desctruction test */ DEFINE_TEST(lru_cache_1cycle) { const std::string unit_data_1cycle_a("foo:4\n"); const std::string unit_data_1cycle_b("bar:flower\nfoo:4\n"); const std::string unit_data_1cycle_c("foo:4\nbar:flower\n"); const std::string unit_data_1cycle_d("foo:moose\nbaz:Stalin\nbar:flower\n"); const std::string unit_data_1cycle_e("foo:moose\nbar:flower\n"); const std::string unit_data_1cycle_f("quz:xyzzy\nbaz:monkey\nfoo:moose\n"); const std::string unit_data_1cycle_g("coat:mouse\npants:cat\nsocks:bear\n"); unit_lru_type* L = new unit_lru_type(3); unit_assert("size==0", (L->size() == 0)); unit_assert("maxsize==3", (L->max_size() == 3)); // Checking a bogus key shouldn't alter the cache. L->exists("foo"); unit_assert("exists() doesn't increase size", (L->size() == 0)); // Check insert() and exists() L->insert("foo", "4"); unit_assert("size==1 after insert(foo,4)", (L->size() == 1)); unit_assert("check exists(foo)", L->exists("foo")); unit_assert("contents check a)", unit_data_1cycle_a.compare(dump(L)) == 0); // Check second insert and ordering L->insert("bar", "flower"); unit_assert("size==2 after insert(bar,flower)", (L->size() == 2)); unit_assert("contents check b)", unit_data_1cycle_b.compare(dump(L)) == 0); // Check touching L->touch("foo"); unit_assert("contents check c)", unit_data_1cycle_c.compare(dump(L)) == 0); // Insert of an existing element should result in only a touch L->insert("bar", "flower"); unit_assert("verify insert touches", unit_data_1cycle_b.compare(dump(L)) == 0); // Verify that fetch works unit_assert("verify fetch(bar)", (std::string("flower").compare(L->fetch("bar")) == 0)); // Insert of an existing element with new data should replace and touch L->insert("baz", "Stalin"); L->insert("foo", "moose"); unit_assert("verify insert replaces", unit_data_1cycle_d.compare(dump(L)) == 0); // Test removal of an existing member. L->remove("baz"); unit_assert("verify remove works", unit_data_1cycle_e.compare(dump(L)) == 0); // Test LRU removal as we add more members than max_size() L->insert("baz", "monkey"); L->insert("quz", "xyzzy"); unit_assert("verify LRU semantics", unit_data_1cycle_f.compare(dump(L)) == 0); // Stress test the implementation a little.. const char* names[10] = { "moose", "dog", "bear", "cat", "mouse", "hat", "mittens", "socks", "pants", "coat" }; for (int i = 0; i < 50; i++) { L->insert(names[i % 10], names[i % 9]); } unit_assert("stress test a little", unit_data_1cycle_g.compare(dump(L)) == 0); // Setup a little for the third test which verifies that scoped references inserted into the // cache don't disappear. L3 = new unit_lru_type3(2); for (int i = 0; i < 10; i++) { test_big_data B; snprintf(B.buffer, 1000, "%d\n", i); L3->insert(i, B); } // Check that clear fully clears. // Bug discovered by: 月迷津渡 gdcex@qq.com unit_assert("very size before clear.", (L->size() > 0)); L->clear(); unit_assert("very size after clear.", (L->size() == 0)); unit_pass(); } #define TRANSACTIONS 50000 /** @test Insert lots of objects and benchmark the rate. */ DEFINE_TEST(lru_cache_stress) { // Stress test the implementation a little more using no objects unit_lru_type2* L2 = new unit_lru_type2(5); double t0 = cputime(); for (int i = 0; i < TRANSACTIONS; i++) { L2->insert(i, i - 1); } double t1 = cputime(); delete L2; print_cputime("(int,int) inserts", t1 - t0, TRANSACTIONS); unit_pass(); } /** @test Check that objects inserted in a different scope are still there. */ DEFINE_TEST(lru_cache_scope_check) { test_big_data* B = L3->fetch_ptr(9); unit_assert("scope check element L3[1]", (strncmp(B->buffer, "9\n", 1000) == 0)); B = L3->fetch_ptr(8); unit_assert("scope check element L3[2]", (strncmp(B->buffer, "8\n", 1000) == 0)); delete L3; unit_pass(); } #ifdef _REENTRANT #include #define THREAD_TRANS 20000 #define THREAD_COUNT 10 unit_lru_type2* L4; void insert_junk() { for (int i = 0; i < THREAD_TRANS; i++) { L4->insert(i, i + 1); L4->remove(i - 5); L4->fetch(i - 3); L4->touch(i - 10); } } /** @test Check for badness with multithreaded access, this is more of a stress test than an * empirical test. */ DEFINE_TEST(lru_cache_threads) { L4 = new unit_lru_type2(20); boost::thread_group thrds; double t0 = cputime(); for (int i = 0; i < THREAD_COUNT; ++i) thrds.create_thread(&insert_junk); thrds.join_all(); double t1 = cputime(); print_cputime("(int,int) multithreaded inserts", t1 - t0, THREAD_TRANS * THREAD_COUNT * 4); delete L4; unit_pass(); } #endif UNIT_TEST_RUN("LRU Cache"); ADD_TEST(lru_cache_1cycle); ADD_TEST(lru_cache_stress); ADD_TEST(lru_cache_scope_check); #ifdef _REENTRANT ADD_TEST(lru_cache_threads); #endif UNIT_TEST_END; #endif fastnetmon-1.1.3+dfsg/src/tests/lru_cache/lru_cache.h000066400000000000000000000217751313534057500226070ustar00rootroot00000000000000/*************************************************************************** * Copyright (C) 2004-2011 by Patrick Audley * * paudley@blackcat.ca * * http://patrickaudley.com * * * ***************************************************************************/ /** * @file lru_cache.h Template cache with an LRU removal policy * @author Patrick Audley * @version 1.4 * @date June 2012 * @par * This cache is thread safe if compiled with _REENTRANT defined. It * uses the BOOST scientific computing library to provide the thread safety * mutexes. * * @par * Thanks to graydon@pobox.com for the size counting functor. * Thanks to 月迷津渡 gdcex@qq.com for fixes and tweaks. * */ /** * @mainpage LRU Cache * * @section intro_section Introduction * * Fast, thread safe C++ template with Least Recently Used (LRU) * removal semantics. Complete with a comprehensive unit test * suite. Threading features require the BOOST scientific library to be * installed. * * @section usage_section Usage * * An LRU cache is a fixed size cache that discards the oldest (least * recently accessed) elements after it fills up. It's ideally * suited to be used in situations where you need to speed up access to * slower data sources (databases, synthetic structures, etc.). Below is * a simple example of using it to cache strings using integer keys. * * @section also_section See Also * * See: LRU Cache * * @example lru_example.cpp */ #include #include #include #ifdef _REENTRANT #include /// If we are reentrant then use a BOOST scoped mutex where neccessary. #define SCOPED_MUTEX boost::mutex::scoped_lock lock(this->_mutex); #else /// If we aren't reentrant then don't do anything. #define SCOPED_MUTEX #endif template struct Countfn { unsigned long operator()(const T& x) { return 1; } }; /** * @brief Template cache with an LRU removal policy. * @class LRUCache * * @par * This template creats a simple collection of key-value pairs that grows * until the size specified at construction is reached and then begins * discard the Least Recently Used element on each insertion. * */ template > class LRUCache { public: typedef std::list > List; ///< Main cache storage typedef typedef typename List::iterator List_Iter; ///< Main cache iterator typedef typename List::const_iterator List_cIter; ///< Main cache iterator (const) typedef std::vector Key_List; ///< List of keys typedef typename Key_List::iterator Key_List_Iter; ///< Main cache iterator typedef typename Key_List::const_iterator Key_List_cIter; ///< Main cache iterator (const) typedef std::map Map; ///< Index typedef typedef std::pair Pair; ///< Pair of Map elements typedef typename Map::iterator Map_Iter; ///< Index iterator typedef typename Map::const_iterator Map_cIter; ///< Index iterator (const) private: List _list; ///< Main cache storage Map _index; ///< Cache storage index unsigned long _max_size; ///< Maximum abstract size of the cache unsigned long _curr_size; ///< Current abstract size of the cache #ifdef _REENTRANT boost::mutex _mutex; #endif public: /** @brief Creates a cache that holds at most Size worth of elements. * @param Size maximum size of cache */ LRUCache(const unsigned long Size) : _max_size(Size), _curr_size(0) { } /// Destructor - cleans up both index and storage ~LRUCache() { clear(); } /** @brief Gets the current abstract size of the cache. * @return current size */ inline const unsigned long size(void) const { return _curr_size; } /** @brief Gets the maximum sbstract size of the cache. * @return maximum size */ inline const unsigned long max_size(void) const { return _max_size; } /// Clears all storage and indices. void clear(void) { SCOPED_MUTEX; _list.clear(); _index.clear(); _curr_size = 0; }; /** @brief Checks for the existance of a key in the cache. * @param key to check for * @return bool indicating whether or not the key was found. */ #ifdef _REENTRANT inline bool exists(const Key& key) { SCOPED_MUTEX; #else inline bool exists(const Key& key) const { #endif return _index.find(key) != _index.end(); } /** @brief Removes a key-data pair from the cache. * @param key to be removed */ inline void remove(const Key& key) { #ifdef _REENTRANT SCOPED_MUTEX; #endif Map_Iter miter = _index.find(key); if (miter == _index.end()) return; _remove(miter); } /** @brief Touches a key in the Cache and makes it the most recently used. * @param key to be touched */ inline void touch(const Key& key) { SCOPED_MUTEX; _touch(key); } /** @brief Fetches a pointer to cache data. * @param key to fetch data for * @param touch whether or not to touch the data * @return pointer to data or NULL on error */ inline Data* fetch_ptr(const Key& key, bool touch = true) { SCOPED_MUTEX; Map_Iter miter = _index.find(key); if (miter == _index.end()) return NULL; _touch(key); return &(miter->second->second); } /** @brief Fetches a copy of cached data. * @param key to fetch data for * @param touch_data whether or not to touch the data * @return copy of the data or an empty Data object if not found */ inline Data fetch(const Key& key, bool touch_data = true) { SCOPED_MUTEX; Map_Iter miter = _index.find(key); if (miter == _index.end()) return Data(); Data tmp = miter->second->second; if (touch_data) _touch(key); return tmp; } /** @brief Fetches a pointer to cache data. * @param key to fetch data for * @param data to fetch data into * @param touch_data whether or not to touch the data * @return whether or not data was filled in */ inline bool fetch(const Key& key, Data& data, bool touch_data = true) { SCOPED_MUTEX; Map_Iter miter = _index.find(key); if (miter == _index.end()) return false; if (touch_data) _list.splice(_list.begin(), _list, miter->second); // Do a touch inline. data = miter->second->second; return true; } /** @brief Inserts a key-data pair into the cache and removes entries if neccessary. * @param key object key for insertion * @param data object data for insertion * @note This function checks key existance and touches the key if it already exists. */ inline void insert(const Key& key, const Data& data) { SCOPED_MUTEX; // Touch the key, if it exists, then replace the content. Map_Iter miter = _touch(key); if (miter != _index.end()) _remove(miter); // Ok, do the actual insert at the head of the list _list.push_front(std::make_pair(key, data)); List_Iter liter = _list.begin(); // Store the index _index.insert(std::make_pair(key, liter)); _curr_size += Sizefn()(data); // Check to see if we need to remove an element due to exceeding max_size while (_curr_size > _max_size) { // Remove the last element. liter = _list.end(); --liter; _remove(liter->first); } } /** @brief Get a list of keys. @return list of the current keys. */ inline const Key_List get_all_keys(void) { SCOPED_MUTEX; Key_List ret; for (List_cIter liter = _list.begin(); liter != _list.end(); liter++) ret.push_back(liter->first); return ret; } private: /** @brief Internal touch function. * @param key to be touched * @return a Map_Iter pointing to the key that was touched. */ inline Map_Iter _touch(const Key& key) { Map_Iter miter = _index.find(key); if (miter == _index.end()) return miter; // Move the found node to the head of the list. _list.splice(_list.begin(), _list, miter->second); return miter; } /** @brief Interal remove function * @param miter Map_Iter that points to the key to remove * @warning miter is now longer usable after being passed to this function. */ inline void _remove(const Map_Iter& miter) { _curr_size -= Sizefn()(miter->second->second); _list.erase(miter->second); _index.erase(miter); } /** @brief Interal remove function * @param key to remove */ inline void _remove(const Key& key) { Map_Iter miter = _index.find(key); _remove(miter); } }; fastnetmon-1.1.3+dfsg/src/tests/lru_cache/lru_cache.h.gch000066400000000000000000000003621313534057500233340ustar00rootroot00000000000000gpcWrite©p^r| Tx86-64generic64fastnetmon-1.1.3+dfsg/src/tests/lua_integration.cpp000066400000000000000000000026561313534057500224510ustar00rootroot00000000000000#include // Heh, we have luajit only for Debian Jessie and should think about custom compilation // https://packages.debian.org/search?keywords=luajit // This code will NOT work with lua 5.2 because 5.1 and 5.2 really incompatible: // http://lists.opensuse.org/opensuse-factory/2012-01/msg00265.html // Ubuntu 14.04 also has it: http://packages.ubuntu.com/trusty/luajit // apt-get install -y lua5.1 lua-json liblua5.1-dev // g++ lua_integration.cpp -lluajit-5.1 // Unfortunately, we haven't support for FFI in standard lua and should switch to luajit: // Info about bundled modules to luajit: http://luajit.org/extensions.html // apt-get install -y libluajit-5.1-dev int main() { typedef struct netflow_struct { int packets; int bytes; } netflow_t; netflow_t flow; flow.packets = 55; flow.bytes = 77; lua_State* L = luaL_newstate(); // load libraries luaL_openlibs(L); luaL_dofile(L, "json_parser.lua"); //luaL_dostring(L, "a = 10 + 5"); //lua_getglobal(L, "a"); //int i = lua_tointeger(L, -1); //printf("%d\n", i); lua_getfield(L, LUA_GLOBALSINDEX, "process_netflow"); //lua_pushstring(L, "first_arg"); lua_pushlightuserdata(L, (void*)&flow); // Call with 1 argumnents and 1 result lua_call(L, 1, 1); printf( "Lua gettop: %d\n", lua_gettop(L) ); printf( "Boolean result: %d\n", lua_toboolean(L, -1) ); lua_close(L); return 0; } fastnetmon-1.1.3+dfsg/src/tests/mongodb_client.cpp000066400000000000000000000016461313534057500222460ustar00rootroot00000000000000#include #include #include // g++ mongodb_client.cpp $(PKG_CONFIG_PATH=/opt/mongo_c_driver/lib/pkgconfig pkg-config --cflags --libs libmongoc-1.0) int main (int argc, char *argv[]) { mongoc_client_t *client; mongoc_collection_t *collection; mongoc_cursor_t *cursor; bson_error_t error; bson_oid_t oid; bson_t *doc; mongoc_init (); client = mongoc_client_new ("mongodb://localhost:27017/"); collection = mongoc_client_get_collection (client, "test", "test"); doc = bson_new (); bson_oid_init (&oid, NULL); BSON_APPEND_OID (doc, "_id", &oid); BSON_APPEND_UTF8 (doc, "hello", "world"); if (!mongoc_collection_insert (collection, MONGOC_INSERT_NONE, doc, NULL, &error)) { printf ("Error: %s\n", error.message); } bson_destroy (doc); mongoc_collection_destroy (collection); mongoc_client_destroy (client); return 0; } fastnetmon-1.1.3+dfsg/src/tests/netflow_exclude.json000066400000000000000000000004011313534057500226250ustar00rootroot00000000000000{"22.11.22.33" : { 33 : "BB", 4 : "BB", 3 : "BB" },"88.99.11.22" : { 559 : "BB", 572 : "BB", 613 : "BB", 542 : "BB", 565 : "BB", 558 : "BB", 561 : "BB", 543 : "BB", 555 : "BB", 545 : "BB", 568 : "BB", 551 : "BB", 574 : "BB" }, "10.0.1.2" : { 916 : "BB" } } fastnetmon-1.1.3+dfsg/src/tests/netmap.cpp000066400000000000000000000124501313534057500205420ustar00rootroot00000000000000#include #include #include #define NETMAP_WITH_LIBS #include #include // For pooling operations #include #include "fastnetmon_packet_parser.h" int number_of_packets = 0; /* prototypes */ void netmap_thread(struct nm_desc* netmap_descriptor, int netmap_thread); void consume_pkt(u_char* buffer, int len); int receive_packets(struct netmap_ring* ring) { u_int cur, rx, n; cur = ring->cur; n = nm_ring_space(ring); for (rx = 0; rx < n; rx++) { struct netmap_slot* slot = &ring->slot[cur]; char* p = NETMAP_BUF(ring, slot->buf_idx); // process data consume_pkt((u_char*)p, slot->len); cur = nm_ring_next(ring, cur); } ring->head = ring->cur = cur; return (rx); } void consume_pkt(u_char* buffer, int len) { // static char packet_data[2000]; // printf("Got packet with length: %d\n", len); // memcpy(packet_data, buffer, len); struct pfring_pkthdr l2tp_header; memset(&l2tp_header, 0, sizeof(l2tp_header)); l2tp_header.len = len; l2tp_header.caplen = len; fastnetmon_parse_pkt((u_char*)buffer, &l2tp_header, 4, 1, 0); // char print_buffer[512]; // fastnetmon_print_parsed_pkt(print_buffer, 512, (u_char*)buffer, &l2tp_header); // printf("%s\n", print_buffer); __sync_fetch_and_add(&number_of_packets, 1); } void receiver(void) { struct nm_desc* netmap_descriptor; u_int num_cpus = sysconf(_SC_NPROCESSORS_ONLN); printf("We have %d cpus\n", num_cpus); struct nmreq base_nmd; bzero(&base_nmd, sizeof(base_nmd)); // Magic from pkt-gen.c base_nmd.nr_tx_rings = base_nmd.nr_rx_rings = 0; base_nmd.nr_tx_slots = base_nmd.nr_rx_slots = 0; std::string interface = "netmap:eth4"; netmap_descriptor = nm_open(interface.c_str(), &base_nmd, 0, NULL); if (netmap_descriptor == NULL) { printf("Can't open netmap device %s\n", interface.c_str()); exit(1); return; } printf("Mapped %dKB memory at %p\n", netmap_descriptor->req.nr_memsize >> 10, netmap_descriptor->mem); printf("We have %d tx and %d rx rings\n", netmap_descriptor->req.nr_tx_rings, netmap_descriptor->req.nr_rx_rings); /* protocol stack and may cause a reset of the card, which in turn may take some time for the PHY to reconfigure. We do the open here to have time to reset. */ int wait_link = 2; printf("Wait %d seconds for NIC reset\n", wait_link); sleep(wait_link); boost::thread* boost_threads_array[num_cpus]; for (int i = 0; i < num_cpus; i++) { struct nm_desc nmd = *netmap_descriptor; // This operation is VERY important! nmd.self = &nmd; uint64_t nmd_flags = 0; if (nmd.req.nr_flags != NR_REG_ALL_NIC) { printf("SHIT SHIT SHIT HAPPINED\n"); } nmd.req.nr_flags = NR_REG_ONE_NIC; nmd.req.nr_ringid = i; /* Only touch one of the rings (rx is already ok) */ nmd_flags |= NETMAP_NO_TX_POLL; struct nm_desc* new_nmd = nm_open(interface.c_str(), NULL, nmd_flags | NM_OPEN_IFNAME | NM_OPEN_NO_MMAP, &nmd); if (new_nmd == NULL) { printf("Can't open netmap descripto for netmap\n"); exit(1); } printf("My first ring is %d and last ring id is %d I'm thread %d\n", new_nmd->first_rx_ring, new_nmd->last_rx_ring, i); printf("Start new thread %d\n", i); // Start thread and pass netmap descriptor to it boost_threads_array[i] = new boost::thread(netmap_thread, new_nmd, i); } printf("Wait for thread finish\n"); // Wait all threads for completion for (int i = 0; i < num_cpus; i++) { boost_threads_array[i]->join(); } } void netmap_thread(struct nm_desc* netmap_descriptor, int thread_number) { struct nm_pkthdr h; u_char* buf; struct pollfd fds; fds.fd = netmap_descriptor->fd; // NETMAP_FD(netmap_descriptor); fds.events = POLLIN; struct netmap_ring* rxring = NULL; struct netmap_if* nifp = netmap_descriptor->nifp; printf("Reading from fd %d thread id: %d\n", netmap_descriptor->fd, thread_number); for (;;) { // We will wait 1000 microseconds for retry, for infinite timeout please use -1 int poll_result = poll(&fds, 1, 1000); if (poll_result == 0) { // printf("poll return 0 return code\n"); continue; } if (poll_result == -1) { printf("poll failed with return code -1\n"); } for (int i = netmap_descriptor->first_rx_ring; i <= netmap_descriptor->last_rx_ring; i++) { // printf("Check ring %d from thread %d\n", i, thread_number); rxring = NETMAP_RXRING(nifp, i); if (nm_ring_empty(rxring)) { continue; } int m = receive_packets(rxring); } // while ( (buf = nm_nextpkt(netmap_descriptor, &h)) ) { // consume_pkt(buf, h.len); //} } // nm_close(netmap_descriptor); } int main() { // receiver(); boost::thread netmap_thread(receiver); for (;;) { sleep(1); printf("We received %d packets in 1 second\n", number_of_packets); number_of_packets = 0; } netmap_thread.join(); } fastnetmon-1.1.3+dfsg/src/tests/parser_performance_tests.cpp000066400000000000000000000077061313534057500243650ustar00rootroot00000000000000#include #include #include #include #include #include "../fastnetmon_packet_parser.h" #include #include using namespace Tins; /* gcc ../fastnetmon_packet_parser.c -o fastnetmon_packet_parser.o -c g++ parser_performance_tests.cpp fastnetmon_packet_parser.o -lpthread -ltins -std=c++11 */ /* Tins: C++ 98 We process: 3 557 647 pps We process: 3 554 012 pps Tins: C++11 We process: 3 529 692 pps We process: 3 529 249 pps PF_RING packet parser without hashing and timestamps: We process: 18 145 597 pps We process: 20 395 563 pps We process: 18 145 597 pps We process: 20 395 563 pps */ void call_fastnetmon_parser(void* ptr, int length); void call_tins_parser(void* ptr, int length); uint64_t received_packets = 0; void* speed_printer(void* ptr) { while (1) { uint64_t packets_before = received_packets; sleep(1); uint64_t packets_after = received_packets; uint64_t pps = packets_after - packets_before; printf("We process: %llu pps\n", pps); } } // We could print any payload with this function and use it for tests void print_packet_payload_in_c_form(unsigned char* data, int length) { int i = 0; printf("unsigned char payload[] = { "); for (i = 0; i < length; i++) { printf("0x%02X", (unsigned char)data[i]); if (i != length -1) { printf(","); } } printf(" }\n"); } int main() { pthread_t thread; pthread_create(&thread, NULL, speed_printer, NULL); pthread_detach(thread); unsigned char payload1[] = { 0x90,0xE2,0xBA,0x83,0x3F,0x25,0x90,0xE2,0xBA,0x2C,0xCB,0x02,0x08,0x00,0x45,0x00,0x00,0x2E,0x00,0x00,0x00,0x00,0x40,0x06,0x69,0xDC,0x0A,0x84,0xF1,0x83,0x0A,0x0A,0x0A,0xDD,0x04,0x01,0x00,0x50,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x50,0x02,0x00,0x0A,0x9A,0x92,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 }; unsigned char payload2[] = { 0x90,0xE2,0xBA,0x83,0x3F,0x25,0x90,0xE2,0xBA,0x2C,0xCB,0x02,0x08,0x00,0x45,0x00,0x00,0x2E,0x00,0x00,0x00,0x00,0x40,0x06,0x69,0xDB,0x0A,0x84,0xF1,0x84,0x0A,0x0A,0x0A,0xDD,0x04,0x01,0x00,0x50,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x50,0x02,0x00,0x0A,0x9A,0x91,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 }; unsigned char byte_value = 0; //int counter = 512; //while (counter > 0) { while(1) { // We use overflow here! byte_value++; // payload1[26] = byte_value; // first octet payload1[29] = byte_value; // last octet call_fastnetmon_parser((void*)payload1, sizeof(payload1)); //call_tins_parser((void*)payload1, sizeof(payload1)); } } void call_tins_parser(void* ptr, int length) { __sync_fetch_and_add(&received_packets, 1); EthernetII pdu((const uint8_t*)ptr, length); const IP &ip = pdu.rfind_pdu(); // Find the IP layer if (ip.protocol() == Tins::Constants::IP::PROTO_TCP) { const TCP &tcp = pdu.rfind_pdu(); // Find the TCP layer //std::cout << ip.src_addr() << ':' << tcp.sport() << " -> " // << ip.dst_addr() << ':' << tcp.dport() << std::endl; } else if (ip.protocol() == Tins::Constants::IP::PROTO_UDP) { const UDP &udp = pdu.rfind_pdu(); // Find the UDP layer } else if (ip.protocol() == Tins::Constants::IP::PROTO_ICMP) { const ICMP &icmp = pdu.rfind_pdu(); // Find the ICMP layer } } void call_fastnetmon_parser(void* ptr, int length) { __sync_fetch_and_add(&received_packets, 1); struct pfring_pkthdr packet_header; memset(&packet_header, 0, sizeof(struct pfring_pkthdr)); packet_header.len = length; packet_header.caplen = length; u_int8_t timestamp = 0; u_int8_t add_hash = 0; fastnetmon_parse_pkt((u_char*)ptr, &packet_header, 4, 0, 0); /* char print_buffer[512]; fastnetmon_print_parsed_pkt(print_buffer, 512, (u_char*)ptr, &packet_header); printf("packet: %s\n", print_buffer); */ } fastnetmon-1.1.3+dfsg/src/tests/patch_for_custom_libc.patch000066400000000000000000000171211313534057500241230ustar00rootroot00000000000000diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a65734b..8a52b0b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -5,6 +5,13 @@ cmake_minimum_required (VERSION 2.8) # Debian 7 - 2.8.9 # CentOS 6 - 2.8.12 +# We should set compiler berfor project() call +if (ENABLE_BUILD_IN_CPP_11_CUSTOM_ENVIRONMENT) + # We use custom compiler too + set(CMAKE_C_COMPILER "/opt/gcc520/bin/gcc") + set(CMAKE_CXX_COMPILER "/opt/gcc520/bin/g++") +endif() + project(FastNetMon) # Unfortunately, Debian Squeeze haven't support for this feature @@ -20,11 +27,42 @@ set (FASTNETMON_VERSION_MINOR 1) # cmake -DENABLE_BUILD_IN_CPP_11_CUSTOM_ENVIRONMENT=ON .. if (ENABLE_BUILD_IN_CPP_11_CUSTOM_ENVIRONMENT) - # We use custom compiler too - set(CMAKE_C_COMPILER "/opt/gcc520/bin/gcc") - set(CMAKE_CXX_COMPILER "/opt/gcc520/bin/g++") + # Set blank sysroot + #set(CMAKE_SYSROOT "/opt/glibc_2.22") + + set(MY_LINK_DIRECTORIES "/opt/glibc_2.22/lib;/opt/gcc520/lib64;/opt/boost_1_58_0/stage/lib;/opt/libhiredis_0_13/lib;/opt/log4cpp1.1.1/lib;/opt/luajit_2.0.4/lib;/opt/ndpi/lib;/opt/pf_ring/lib;/opt/json-c-0.12/lib") + set(MY_INCLUDE_DIRECTORIES "/opt/glibc_2.22/include") + + # TODO: onlt temp code + include_directories("/usr/include/x86_64-linux-gnu") + include_directories("/usr/include") + #include_directories("/usr/src/linux-headers-3.16.0-4-common/include/uapi") + + # Remove all standard path's for C and C++ compilers + set(CMAKE_CXX_IMPLICIT_LINK_DIRECTORIES "") + set(CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES "") + set(CMAKE_CXX_IMPLICIT_LINK_LIBRARIES "") + + set(CMAKE_C_IMPLICIT_LINK_DIRECTORIES "") + set(CMAKE_C_IMPLICIT_INCLUDE_DIRECTORIES "") + set(CMAKE_C_IMPLICIT_LINK_LIBRARIES "") + + # Remove all default paths for platform + set(CMAKE_PLATFORM_IMPLICIT_LINK_DIRECTORIES "") + set(CMAKE_SYSTEM_INCLUDE_PATH "${MY_INCLUDE_DIRECTORIES}") + + set(CMAKE_C_IMPLICIT_LINK_DIRECTORIES "${MY_LINK_DIRECTORIES}") + set(CMAKE_C_IMPLICIT_INCLUDE_DIRECTORIES "${MY_LINK_DIRECTORIES}") + set(CMAKE_C_IMPLICIT_LINK_LIBRARIES "c") + + # Specify path's to custom compiled gcc and glibc + set(CMAKE_CXX_IMPLICIT_LINK_LIBRARIES "stdc++;gcc;gcc_s;m;c") + set(CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES "${MY_LINK_DIRECTORIES}") + set(CMAKE_CXX_IMPLICIT_LINK_DIRECTORIES "${MY_LINK_DIRECTORIES}") set(BOOST_INCLUDEDIR "/opt/boost_1_58_0") + include_directories("${BOOST_INCLUDEDIR}") + set(BOOST_LIBRARYDIR "/opt/boost_1_58_0/stage/lib/") # It's really nice part of this custom build process :) @@ -32,6 +70,9 @@ if (ENABLE_BUILD_IN_CPP_11_CUSTOM_ENVIRONMENT) # Disable warning from Boost when compiling with gcc 5.2 set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -Wno-deprecated-declarations") + + # Specify custom ld-linux dynamic linker path + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -Wl,--dynamic-linker=/opt/glibc_2.22/lib/ld-linux-x86-64.so.2") # Specify full RPATH for build tree SET(CMAKE_SKIP_BUILD_RPATH FALSE) @@ -39,7 +80,7 @@ if (ENABLE_BUILD_IN_CPP_11_CUSTOM_ENVIRONMENT) # Create builds in current folder with install RPATH SET(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE) - SET(CMAKE_INSTALL_RPATH "/opt/gcc520/lib64;/opt/boost_1_58_0/stage/lib;/opt/libhiredis_0_13/lib;/opt/log4cpp1.1.1/lib;/opt/luajit_2.0.4/lib;/opt/ndpi/lib;/opt/pf_ring/lib;/opt/json-c-0.12/lib") + SET(CMAKE_INSTALL_RPATH "${MY_LINK_DIRECTORIES}") endif() # It's pretty safe and provide big speedup for our packet processor and patricia code @@ -169,7 +210,15 @@ endif() add_library(pcap_plugin STATIC pcap_plugin/pcap_collector.cpp) target_link_libraries(pcap_plugin pcap) -find_package(Threads) +#find_package(Threads) + +#if (Threads_FOUND) +# message(STATUS "We found threads library") +#else() +# message(FATAL_ERROR "We can't find threads library") +#endif() +# TODO: fix this hack +set(CMAKE_THREAD_LIBS_INIT "-lpthread") if (ENABLE_PFRING_SUPPORT) add_library(pfring_plugin STATIC pfring_plugin/pfring_collector.cpp) @@ -247,7 +296,7 @@ if (LOG4CPP_INCLUDES_FOLDER AND LOG4CPP_LIBRARY_PATH) include_directories(${LOG4CPP_INCLUDES_FOLDER}) message(STATUS "We have found log4cpp and will build project") else() - message(STATUS "We can't find log4cpp. We can't build project") + message(FATAL_ERROR "We can't find log4cpp. We can't build project") endif() ### Look for jsonc @@ -259,7 +308,7 @@ if (JSONC_INCLUDES_FOLDER AND JSONC_LIBRARY_PATH) include_directories(${JSONC_INCLUDES_FOLDER}) message(STATUS "We have found json-c library correctly: ${JSONC_LIBRARY_PATH}") else() - message(STATUS "We can't find json-c library! Can't build project") + message(FATAL_ERROR "We can't find json-c library! Can't build project") endif() target_link_libraries(fast_library ${JSONC_LIBRARY_PATH}) diff --git a/src/tests/patch_for_custom_libc.patch b/src/tests/patch_for_custom_libc.patch index 6426400..c7ea4ff 100644 --- a/src/tests/patch_for_custom_libc.patch +++ b/src/tests/patch_for_custom_libc.patch @@ -1,46 +0,0 @@ -diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt -index a65734b..406a5b9 100644 ---- a/src/CMakeLists.txt -+++ b/src/CMakeLists.txt -@@ -27,11 +27,32 @@ if (ENABLE_BUILD_IN_CPP_11_CUSTOM_ENVIRONMENT) - set(BOOST_INCLUDEDIR "/opt/boost_1_58_0") - set(BOOST_LIBRARYDIR "/opt/boost_1_58_0/stage/lib/") - -+ # Remove all system directories with default libraries -+ message(STATUS "CMAKE_CXX_IMPLICIT_LINK_DIRECTORIES=${CMAKE_CXX_IMPLICIT_LINK_DIRECTORIES}") -+ set(CMAKE_CXX_IMPLICIT_LINK_DIRECTORIES "") -+ -+ message(STATUS "CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES = ${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES}") -+ set(CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES "/opt/glibc_2.22/include") -+ -+ set(CMAKE_CXX_IMPLICIT_LINK_DIRECTORIES "/opt/glibc_2.22/lib;/opt/gcc520/lib64;/opt/glibc_2.22/lib;/opt/gcc520/lib/gcc/x86_64-unknown-linux-gnu/5.2.0") -+ include_directories("/opt/glibc_2.22/include") -+ -+ message(STATUS "CMAKE_CXX_IMPLICIT_LINK_LIBRARIES=${CMAKE_CXX_IMPLICIT_LINK_LIBRARIES}") -+ set(CMAKE_CXX_IMPLICIT_LINK_LIBRARIES "") -+ set(CMAKE_CXX_IMPLICIT_LINK_LIBRARIES "stdc++;gcc;gcc_s;m;c") -+ -+ message(STATUS "CMAKE_CXX_IMPLICIT_LINK_LIBRARIES=${CMAKE_CXX_IMPLICIT_LINK_LIBRARIES}") -+ - # It's really nice part of this custom build process :) - set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -std=c++11") - - # Disable warning from Boost when compiling with gcc 5.2 - set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -Wno-deprecated-declarations") -+ -+ # Pass custom ld-linux for our own binary -+ set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -Wl,--dynamic-linker=/opt/glibc_2.22/lib/ld-linux-x86-64.so.2") -+ -+ set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -nodefaultlibs -nostdinc -nostdinc++") - - # Specify full RPATH for build tree - SET(CMAKE_SKIP_BUILD_RPATH FALSE) -@@ -39,7 +60,7 @@ if (ENABLE_BUILD_IN_CPP_11_CUSTOM_ENVIRONMENT) - # Create builds in current folder with install RPATH - SET(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE) - -- SET(CMAKE_INSTALL_RPATH "/opt/gcc520/lib64;/opt/boost_1_58_0/stage/lib;/opt/libhiredis_0_13/lib;/opt/log4cpp1.1.1/lib;/opt/luajit_2.0.4/lib;/opt/ndpi/lib;/opt/pf_ring/lib;/opt/json-c-0.12/lib") -+ SET(CMAKE_INSTALL_RPATH "/opt/glibc_2.22/lib;/opt/gcc520/lib64;/opt/boost_1_58_0/stage/lib;/opt/libhiredis_0_13/lib;/opt/log4cpp1.1.1/lib;/opt/luajit_2.0.4/lib;/opt/ndpi/lib;/opt/pf_ring/lib;/opt/json-c-0.12/lib") - endif() - - # It's pretty safe and provide big speedup for our packet processor and patricia code fastnetmon-1.1.3+dfsg/src/tests/patch_for_libcuckoo_as_flow_tracking_structure.patch000066400000000000000000000054231313534057500313100ustar00rootroot00000000000000diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index dfbe8c1..a84d696 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -10,6 +10,10 @@ project(FastNetMon) # Enable it and fix all warnigns! # add_definitions ("-Wall") +include_directories("/opt/libcuckoo/include/") +# We need C++11 support for libcuckoo +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") + set (Tutorial_VERSION_MAJOR 1) set (Tutorial_VERSION_MINOR 1) @@ -189,5 +193,7 @@ target_link_libraries(fastnetmon pcap_plugin) target_link_libraries(fastnetmon example_plugin) target_link_libraries(fastnetmon netmap_plugin) +target_link_libraries(fastnetmon /opt/libcuckoo/lib/libcityhash.so) + install(TARGETS fastnetmon DESTINATION bin) install(TARGETS fastnetmon_client DESTINATION bin) diff --git a/src/fastnetmon.cpp b/src/fastnetmon.cpp index 5308244..cd9e9dc 100644 --- a/src/fastnetmon.cpp +++ b/src/fastnetmon.cpp @@ -268,6 +268,8 @@ void process_packet(simple_packet& current_packet); void traffic_draw_programm(); void interruption_signal_handler(int signal_number); +cuckoohash_map > flow_tracking_table_new_generation; + /* Class for custom comparison fields by different fields */ class TrafficComparatorClass { private: @@ -1029,6 +1031,24 @@ void process_packet(simple_packet& current_packet) { } } + + if (packet_direction == OUTGOING or packet_direction == INCOMING) { + std::string connection_tracking_hash_string = convert_ip_as_uint_to_string(current_packet.dst_ip) + "_" + convert_ip_as_uint_to_string(current_packet.src_ip) + "_" + + convert_ip_as_uint_to_string(current_packet.source_port) + "_" + convert_ip_as_uint_to_string(current_packet.destination_port) + "_" + convert_ip_as_uint_to_string(current_packet.protocol) + "_"; + get_direction_name(packet_direction); + + map_element temp_element; + + if (flow_tracking_table_new_generation.find(connection_tracking_hash_string, temp_element)) { + // found! + + } else { + // not found, create it + flow_tracking_table_new_generation.insert(connection_tracking_hash_string, temp_element); + } + } + + /* Because we support mirroring, sflow and netflow we should support different cases: - One packet passed for processing (mirror) - Multiple packets ("flows") passed for processing (netflow) diff --git a/src/fastnetmon_types.h b/src/fastnetmon_types.h index 8263645..6e098ca 100644 --- a/src/fastnetmon_types.h +++ b/src/fastnetmon_types.h @@ -5,6 +5,9 @@ #include // uint32_t #include // struct timeval +#include +#include + #include #include fastnetmon-1.1.3+dfsg/src/tests/patch_for_switching_from_std_map_to_sorted_vector.patch000066400000000000000000000152431313534057500320200ustar00rootroot00000000000000diff --git a/src/fastnetmon.cpp b/src/fastnetmon.cpp index 5308244..2a67506 100644 --- a/src/fastnetmon.cpp +++ b/src/fastnetmon.cpp @@ -191,6 +191,12 @@ map_of_vector_counters SubnetVectorMap; // Flow tracking structures map_of_vector_counters_for_flow SubnetVectorMapFlow; +typedef std::vector lookup_vector_indexes_t; +typedef std::vector lookup_vector_data_t; + +lookup_vector_indexes_t lookup_vector_indexes; +lookup_vector_data_t lookup_vector_data; + /* End of our data structs */ boost::mutex data_counters_mutex; @@ -969,12 +975,22 @@ bool load_our_networks_list() { /* Preallocate data structures */ - patricia_process (lookup_tree, (void_fn_t)subnet_vectors_allocator); - logger<first); + lookup_vector_data.push_back(ii->second); + } + logger<= itr->second.size()) { + if (shift_in_vector < 0 or shift_in_vector >= itr->size()) { logger<< log4cpp::Priority::ERROR<<"We tried to access to element with index "<second.size(); + <<" which located outside allocated vector with size "<size(); logger<< log4cpp::Priority::ERROR<<"We expect issues with this packet in OUTGOING direction: "<second[shift_in_vector]; + map_element* current_element = &(*itr)[shift_in_vector]; // Main packet/bytes counter __sync_fetch_and_add(¤t_element->out_packets, sampled_number_of_packets); @@ -1135,16 +1154,16 @@ void process_packet(simple_packet& current_packet) { } else if (packet_direction == INCOMING) { int64_t shift_in_vector = (int64_t)ntohl(current_packet.dst_ip) - (int64_t)subnet_in_host_byte_order; - if (shift_in_vector < 0 or shift_in_vector >= itr->second.size()) { + if (shift_in_vector < 0 or shift_in_vector >= itr->size()) { logger<< log4cpp::Priority::ERROR<<"We tried to access to element with index "<second.size(); + <<" which located outside allocated vector with size "<size(); logger<< log4cpp::Priority::INFO<<"We expect issues with this packet in INCOMING direction: "<second[shift_in_vector]; + map_element* current_element = &(*itr)[shift_in_vector]; // Main packet/bytes counter __sync_fetch_and_add(¤t_element->in_packets, sampled_number_of_packets); @@ -1307,15 +1326,17 @@ void recalculate_speed() { uint64_t incoming_total_flows = 0; uint64_t outgoing_total_flows = 0; - for (map_of_vector_counters::iterator itr = SubnetVectorMap.begin(); itr != SubnetVectorMap.end(); ++itr) { - for (vector_of_counters::iterator vector_itr = itr->second.begin(); vector_itr != itr->second.end(); ++vector_itr) { - int current_index = vector_itr - itr->second.begin(); + for (lookup_vector_indexes_t::iterator itr = lookup_vector_indexes.begin(); itr != lookup_vector_indexes.end(); ++itr) { + vector_of_counters* vector_pointer = &lookup_vector_data[ itr - lookup_vector_indexes.begin() ]; + + for (vector_of_counters::iterator vector_itr = vector_pointer->begin(); vector_itr != vector_pointer->end(); ++vector_itr) { + int current_index = vector_itr - vector_pointer->begin(); // New element map_element new_speed_element; // convert to host order for math operations - uint32_t subnet_ip = ntohl(itr->first); + uint32_t subnet_ip = ntohl(*itr); uint32_t client_ip_in_host_bytes_order = subnet_ip + current_index; // covnert to our standard network byte order @@ -1364,7 +1385,7 @@ void recalculate_speed() { new_speed_element.icmp_in_bytes = uint64_t((double)vector_itr->icmp_in_bytes / speed_calc_period); new_speed_element.icmp_out_bytes = uint64_t((double)vector_itr->icmp_out_bytes / speed_calc_period); - conntrack_main_struct* flow_counter_ptr = &SubnetVectorMapFlow[itr->first][current_index]; + conntrack_main_struct* flow_counter_ptr = &SubnetVectorMapFlow[*itr][current_index]; // todo: optimize this operations! uint64_t total_out_flows = fastnetmon-1.1.3+dfsg/src/tests/patricia_performance_tests.c000066400000000000000000000051111313534057500243110ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include "../libpatricia/patricia.h" /* How to compile: gcc ../libpatricia/patricia.c -c -opatricia.o gcc patricia_performance_tests.c patricia.o -o patricia_performance_test */ int main() { patricia_tree_t* lookup_tree; lookup_tree = New_Patricia(32); make_and_lookup(lookup_tree, "46.36.216.0/21"); make_and_lookup(lookup_tree, "159.253.16.0/21"); make_and_lookup(lookup_tree, "5.45.112.0/21"); make_and_lookup(lookup_tree, "5.45.120.0/21"); make_and_lookup(lookup_tree, "5.101.112.0/21"); make_and_lookup(lookup_tree, "5.101.120.0/21"); make_and_lookup(lookup_tree, "185.4.72.0/22"); make_and_lookup(lookup_tree, "181.114.240.0/20"); make_and_lookup(lookup_tree, "193.42.142.0/24"); prefix_t prefix_for_check_adreess; prefix_for_check_adreess.family = AF_INET; prefix_for_check_adreess.bitlen = 32; patricia_node_t* found_patrica_node = NULL; // prefix_for_check_adreess.add.sin.s_addr = 123123123; // std::map lpm_cache; // Without cache: 16.7 million of operations int i_iter = 100; // Million operations int j_iter = 1000000; // printf("Preallocate table\n"); // Iterate over all our IP addresses // for (int j = 0; j < j_iter; j++) { // for (int i = 0; i < i_iter; i++) { // lpm_cache[i*j] = true; // } //} printf("Start tests\n"); struct timespec start_time; clock_gettime(CLOCK_REALTIME, &start_time); int i, j; for (j = 0; j < j_iter; j++) { for (i = 0; i < i_iter; i++) { // Random Pseudo IP prefix_for_check_adreess.add.sin.s_addr = i*j; patricia_node_t* found_patrica_node = patricia_search_best2(lookup_tree, &prefix_for_check_adreess, 1); unsigned long destination_subnet = 0; if (found_patrica_node != NULL) { // printf("Found\n"); } } } struct timespec finish_time; clock_gettime(CLOCK_REALTIME, &finish_time); unsigned long used_seconds = finish_time.tv_sec - start_time.tv_sec; unsigned long total_ops = i_iter * j_iter; float megaops_per_second = (float)total_ops / (float)used_seconds / 1000000; printf("Total time is %d seconds total ops: %d\nMillion of ops per second: %.1f\n", used_seconds, total_ops, megaops_per_second); Destroy_Patricia(lookup_tree, (void_fn_t)0); } fastnetmon-1.1.3+dfsg/src/tests/pcap_writer.cpp000066400000000000000000000030331313534057500215720ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include "../fastnetmon_pcap_format.h" #include "../packet_storage.h" int main() { packet_storage_t packet_storage; // We specify in in packets if (!packet_storage.allocate_buffer(500)) { printf("Can't allocate buffer"); return -1; } unsigned char payload1[] = { 0x90,0xE2,0xBA,0x83,0x3F,0x25,0x90,0xE2,0xBA,0x2C,0xCB,0x02,0x08,0x00,0x45,0x00,0x00,0x2E,0x00,0x00,0x00,0x00,0x40,0x06,0x69,0xDC,0x0A,0x84,0xF1,0x83,0x0A,0x0A,0x0A,0xDD,0x04,0x01,0x00,0x50,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x50,0x02,0x00,0x0A,0x9A,0x92,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 }; if (!packet_storage.write_packet(payload1, sizeof(payload1))) { printf("Can't write packet to the storage\n"); return -1; } // Dump buffer to memory std::string pcap_file_path = "/tmp/fastnetmon_example.pcap"; int filedesc = open(pcap_file_path.c_str(), O_WRONLY|O_CREAT); if (filedesc <= 0) { printf("Can't open dump file for writing"); return -1; } std::cout << "Used size: " << packet_storage.get_used_memory() << std::endl; ssize_t wrote_bytes = write(filedesc, (void*)packet_storage.get_buffer_pointer(), packet_storage.get_used_memory()); if (wrote_bytes != packet_storage.get_used_memory()) { printf("Can't write data to the file\n"); return -1; } close(filedesc); return(0); } fastnetmon-1.1.3+dfsg/src/tests/pfring_parser_zc_issue.c000066400000000000000000000047421313534057500234700ustar00rootroot00000000000000#include "pfring.h" #include /* How to compile me: g++ pfring_parser_zc_issue.c -I/opt/pf_ring/include -L/opt/pf_ring/lib/ -lpfring -lnuma */ void parse_packet_pf_ring(const struct pfring_pkthdr* h, const u_char* p, const u_char* user_bytes) { memset((void*)&h->extended_hdr.parsed_pkt, 0, sizeof(h->extended_hdr.parsed_pkt)); pfring_parse_pkt((u_char*)p, (struct pfring_pkthdr*)h, 4, 1, 0); char buffer[512]; pfring_print_parsed_pkt(buffer, 512, p, h); std::cout << buffer; } int main() { char* dev = "zc:eth3"; // We could pool device in multiple threads unsigned int num_threads = 1; bool promisc = true; /* This flag manages packet parser for extended_hdr */ bool use_extended_pkt_header = true; bool enable_hw_timestamp = false; bool dont_strip_timestamps = false; u_int32_t flags = 0; if (num_threads > 1) flags |= PF_RING_REENTRANT; if (use_extended_pkt_header) flags |= PF_RING_LONG_HEADER; if (promisc) flags |= PF_RING_PROMISC; if (enable_hw_timestamp) flags |= PF_RING_HW_TIMESTAMP; if (!dont_strip_timestamps) flags |= PF_RING_STRIP_HW_TIMESTAMP; // if (!we_use_pf_ring_in_kernel_parser) { // flags != PF_RING_DO_NOT_PARSE; //} // flags |= PF_RING_DNA_SYMMETRIC_RSS; /* Note that symmetric RSS is ignored by non-DNA drivers // */ // use default value from pfcount.c unsigned int snaplen = 128; pfring* pf_ring_descr = pfring_open(dev, snaplen, flags); if (pf_ring_descr == NULL) { std::cout << "pfring_open error: " << strerror(errno) << " (pf_ring not loaded or perhaps you use quick mode and have already a socket bound to: " << dev << ")"; return false; } u_int32_t version; // Set spplication name in /proc int pfring_set_application_name_result = pfring_set_application_name(pf_ring_descr, (char*)"fastnetmon"); if (pfring_set_application_name_result != 0) { std::cout << "Can't set programm name for PF_RING: pfring_set_application_name"; } pfring_version(pf_ring_descr, &version); int pfring_set_socket_mode_result = pfring_set_socket_mode(pf_ring_descr, recv_only_mode); // enable ring if (pfring_enable_ring(pf_ring_descr) != 0) { std::cout << "Unable to enable ring :-("; pfring_close(pf_ring_descr); return false; } u_int8_t wait_for_packet = 1; pfring_loop(pf_ring_descr, parse_packet_pf_ring, (u_char*)NULL, wait_for_packet); } fastnetmon-1.1.3+dfsg/src/tests/promisc.cpp000066400000000000000000000033451313534057500207350ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include // This code compiles on FreeBSD and Linux but did not work on FreeBSD /* Promisc management on FreeBSD is real nighmare, really. Issues with: ifr_flagshigh and IFF_PPROMISC vs IFF_PROMISC */ /* Good code examples here: https://github.com/fichtner/netmap/blob/master/extra/libpcap-netmap.diff */ int main() { // We need really any socket for ioctl int fd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); if (!fd) { printf("Can't create socket\n"); exit(1); } struct ifreq ethreq; memset(ðreq, 0, sizeof(ethreq)); strncpy(ethreq.ifr_name, "eth6", IFNAMSIZ); int ioctl_res = ioctl(fd, SIOCGIFFLAGS, ðreq); if (ioctl_res == -1) { printf("Can't get interface flags"); exit(1); } if (ethreq.ifr_flags & IFF_PROMISC) { printf("Interface in promisc mode already\n"); printf("Switch it off\n"); ethreq.ifr_flags &= ~IFF_PROMISC; int ioctl_res_set = ioctl(fd, SIOCSIFFLAGS, ðreq); if (ioctl_res_set == -1) { printf("Can't set interface flags"); exit(1); } printf("promisc mode disabled correctly\n"); } else { printf("Interface in non promisc mode now, switch it on\n"); ethreq.ifr_flags |= IFF_PROMISC; int ioctl_res_set = ioctl(fd, SIOCSIFFLAGS, ðreq); if (ioctl_res_set == -1) { printf("Can't set interface flags"); exit(1); } printf("promisc mode enabled\n"); } close(fd); } fastnetmon-1.1.3+dfsg/src/tests/py_counters_performance_test.py000077500000000000000000000010071313534057500251150ustar00rootroot00000000000000#!/usr/bin/python import time my_dict = {} # Intel(R) Core(TM) i7-3635QM CPU @ 2.40GHz: 4.8 MOPS # Intel(R) Core(TM) i7-3820 CPU @ 3.60GHz: python 2.7 7.0 MOPS # Intel(R) Core(TM) i7-3820 CPU @ 3.60GHz: python 2.7/pypy: 8.6 MOPS iterations = 10**6*14 start = time.time() # Emulate 14.6 mpps for index in range(0, iterations): my_dict[index] = { "udp": 0 } stop = time.time() interval = stop - start print iterations / interval / 10**6, "millions of iterations per second" fastnetmon-1.1.3+dfsg/src/tests/snabb/000077500000000000000000000000001313534057500176355ustar00rootroot00000000000000fastnetmon-1.1.3+dfsg/src/tests/snabb/README.md000066400000000000000000000052751313534057500211250ustar00rootroot00000000000000### Here we store all code related with Snabb switch intergration :) We like it because it's awesome! First of all, please compile Snabb Switch from next branch: ```bash cd /usr/src/ git clone https://github.com/SnabbCo/snabbswitch.git -b next cd snabbswitch make ``` Then compile our .so library: ``` g++ -O3 ../../fastnetmon_packet_parser.c -c -o fastnetmon_packet_parser.o -fPIC g++ -O3 -shared -o capturecallback.so -fPIC capturecallback.cpp fastnetmon_packet_parser.o ``` Polly enabled clang compilation (offer significant speedup): ```bash alias pollycc="/usr/src/polly/llvm_build/bin/clang -Xclang -load -Xclang /usr/src/polly/llvm_build/lib/LLVMPolly.so" pollycc -O3 ../../fastnetmon_packet_parser.c -c -o fastnetmon_packet_parser.o -fPIC pollycc -O3 -shared -o capturecallback.so -fPIC capturecallback.c fastnetmon_packet_parser.o ``` Get NIC's PCI address: ```bash lspci -m|grep 82599 00:05.0 "Ethernet controller" "Intel Corporation" "82599ES 10-Gigabit SFI/SFP+ Network Connection" -r01 "Intel Corporation" "Ethernet Server Adapter X520-2" 00:06.0 "Ethernet controller" "Intel Corporation" "82599ES 10-Gigabit SFI/SFP+ Network Connection" -r01 "Intel Corporation" "Ethernet Server Adapter X520-2" ``` For example, we will use 00:06.0, we need convert this value to Snabb's format with adding 4 leading zeroes: 0000:00:06.0 Run capture: ```bash /usr/src/snabbswitch/src/snabb firehose --input 0000:03:00.0 --input 0000:03:00.1 /usr/src/fastnetmon/src/tests/snabb/capturecallback.so ``` I have got really amazing results: ```bash Loading shared object: ./capturecallback.so Initializing NIC: 0000:00:06.0 Run speed printer from C code Processing traffic... We process: 14566196 pps We process: 14820487 pps We process: 14856881 pps We process: 14863727 pps ``` We achieved this with only single logical core of i7 3820 CPU: ```bash 1 [ 0.0%] 5 [ 0.0%] 2 [ 0.0%] 6 [ 0.0%] 3 [ 0.0%] 7 [|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||100.0%] 4 [ 0.0%] 8 [ 0.0%] ``` Really awesome! Huge thanks to Luke Gorrie for great help with it! Once the testing is done we can rebind the NIC to the kernel `ixgbe` driver. This can be done either be reloading `ixgbe` or, less disruptively, like this: ```bash echo 0000:00:06.0 | sudo tee /sys/bus/pci/drivers/ixgbe/bind ``` fastnetmon-1.1.3+dfsg/src/tests/snabb/build_ndpi.sh000077500000000000000000000011341313534057500223040ustar00rootroot00000000000000#!/bin/bash # apt-get install -y libhiredis-dev redis-server g++ -O3 ../../fastnetmon_packet_parser.c -c -o fastnetmon_packet_parser.o -fPIC g++ -O3 ../../fastnetmon_pcap_format.cpp -c -o fastnetmon_pcap_format.o -fPIC g++ -O3 ../../fast_dpi.cpp -c -o fast_dpi.o `PKG_CONFIG_PATH=/opt/ndpi/lib/pkgconfig pkg-config pkg-config --cflags --libs libndpi` -fPIC g++ -O3 -shared -o ndpicallback.so -fPIC ndpicallback.cpp fastnetmon_pcap_format.o fast_dpi.o fastnetmon_packet_parser.o `PKG_CONFIG_PATH=/opt/ndpi/lib/pkgconfig pkg-config pkg-config --cflags --libs libndpi` -std=c++11 -fPIC -lhiredis fastnetmon-1.1.3+dfsg/src/tests/snabb/capturecallback.cpp000066400000000000000000000132751313534057500234710ustar00rootroot00000000000000// compile with: gcc -shared -o capturecallback.so -fPIC capturecallback.c #include #include #include #include #include #include #include #include #include "../../fastnetmon_packet_parser.h" double system_tsc_resolution_hz = 0; #ifdef __cplusplus extern "C" { #endif inline uint64_t rte_rdtsc(void) { union { uint64_t tsc_64; struct { uint32_t lo_32; uint32_t hi_32; }; } tsc; asm volatile("rdtsc" : "=a" (tsc.lo_32), "=d" (tsc.hi_32)); return tsc.tsc_64; } void set_tsc_freq_fallback() { uint64_t start = rte_rdtsc(); sleep(1); system_tsc_resolution_hz = (double)rte_rdtsc() - start; } #ifdef __cplusplus } #endif // C++ rewrite of Python code: https://gist.github.com/pavel-odintsov/652904287ca0ca6816f6 class token_bucket_t { public: token_bucket_t() { this->tokens = 0; this->rate = 0; this->burst = 0; this->last_timestamp = 0; } int64_t get_rate() { return this->rate; } int64_t get_burst() { return this->burst; } int64_t get_tokens() { return this->tokens; } bool set_rate(int64_t rate, int64_t burst) { this->rate = rate; this->tokens = burst; this->burst = burst; // Start counter! this->last_timestamp = (double)rte_rdtsc() / system_tsc_resolution_hz; } int64_t consume(int64_t consumed_tokens) { double current_time = (double)rte_rdtsc() / system_tsc_resolution_hz; double interval = (current_time - this->last_timestamp); if (interval < 0) { printf("Your TSC is buggy, we have last %llu and current time: %llu\n", this->last_timestamp, current_time); } this->last_timestamp = current_time; this->tokens = std::max( (double)0, std::min( double(this->tokens + this->rate * interval), double(this->burst) ) ) - 1; return this->tokens; } private: int64_t rate; int64_t tokens; int64_t burst; double last_timestamp; }; token_bucket_t global_token_bucket_counter; #ifdef __cplusplus extern "C" { #endif /* Called once before processing packets. */ void firehose_start(); /* optional */ /* Called once after processing packets. */ void firehose_stop(); /* optional */ void firehose_stop() { } /* * Process a packet received from a NIC. * * pciaddr: name of PCI device packet is received from * data: packet payload (ethernet frame) * length: payload length in bytes */ inline void firehose_packet(const char *pciaddr, char *data, int length); /* Intel 82599 "Legacy" receive descriptor format. * See Intel 82599 data sheet section 7.1.5. * http://www.intel.com/content/dam/www/public/us/en/documents/datasheets/82599-10-gbe-controller-datasheet.pdf */ struct firehose_rdesc { uint64_t address; uint16_t length; uint16_t cksum; uint8_t status; uint8_t errors; uint16_t vlan; } __attribute__((packed)); /* Traverse the hardware receive descriptor ring. * Process each packet that is ready. * Return the updated ring index. */ int firehose_callback_v1(const char *pciaddr, char **packets, struct firehose_rdesc *rxring, int ring_size, int index) { while (rxring[index].status & 1) { int next_index = (index + 1) & (ring_size-1); __builtin_prefetch(packets[next_index]); firehose_packet(pciaddr, packets[index], rxring[index].length); rxring[index].status = 0; /* reset descriptor for reuse */ index = next_index; } return index; } uint64_t received_packets = 0; void* speed_printer(void* ptr) { while (1) { uint64_t packets_before = received_packets; sleep(1); uint64_t packets_after = received_packets; uint64_t pps = packets_after - packets_before; printf("We process: %llu pps tokens %lld rate %lld burst %lld \n", (long long)pps, global_token_bucket_counter.get_tokens(), global_token_bucket_counter.get_rate(), global_token_bucket_counter.get_burst() ); } } void sigproc(int sig) { firehose_stop(); printf("We caught SINGINT and will finish application\n"); exit(0); } // We will start speed printer void firehose_start() { signal(SIGINT, sigproc); set_tsc_freq_fallback(); global_token_bucket_counter.set_rate(10000000, 15000000); //printf("tsq hz is: %f\n", system_tsc_resolution_hz); pthread_t thread; pthread_create(&thread, NULL, speed_printer, NULL); pthread_detach(thread); } void firehose_packet(const char *pciaddr, char *data, int length) { // Put packet to the cache /* struct pfring_pkthdr packet_header; memset(&packet_header, 0, sizeof(packet_header)); packet_header.len = length; packet_header.caplen = length; fastnetmon_parse_pkt((u_char*)data, &packet_header, 3, 0, 0); */ /* char print_buffer[512]; fastnetmon_print_parsed_pkt(print_buffer, 512, (u_char*)data, &packet_header); printf("packet: %s\n", print_buffer); */ int64_t consume_result = global_token_bucket_counter.consume(1); if (consume_result < 0) { printf("Overflow!\n"); } __sync_fetch_and_add(&received_packets, 1); //printf("Got packet with %d bytes.\n", length); } #ifdef __cplusplus } #endif fastnetmon-1.1.3+dfsg/src/tests/snabb/capturetodisk.cpp000066400000000000000000000161511313534057500232260ustar00rootroot00000000000000// Author: Pavel.Odintsov@gmail.com // License GPLv2 #include #include #include #include #include #include #include #include #include #include #include #include #include "../../fastnetmon_pcap_format.h" /* Compile it: g++ -O3 -fPIC -std=c++11 ../../fastnetmon_pcap_format.cpp -c -o fastnetmon_pcap_format.o g++ -O3 -shared -o capturetodisk.so -fPIC capturetodisk.cpp fastnetmon_pcap_format.o -std=c++11 -lboost_system Run it: /usr/src/snabbswitch/src/snabb firehose --input 0000:02:00.0 --input 0000:02:00.1 /usr/src/fastnetmon/src/tests/snabb/capturetodisk.so Please use ext4 with writeback feature: mount -odata=writeback /dev/sdb /mnt */ class packet_buffer_t { public: unsigned char buffer[1600]; unsigned int length; }; typedef std::shared_ptr packet_buffer_shared_pointer_t; constexpr auto size_spsc_queue = 1048576; // We use persistent preallocation for ext4 and allocate 20 GB for storing data before any operations uint64_t preallocate_packet_dump_file_size = 1073741824ul * 20ul; typedef boost::lockfree::spsc_queue< packet_buffer_shared_pointer_t, boost::lockfree::capacity > my_spsc_queue_t; int pcap_file = 0; my_spsc_queue_t my_spsc_queue; #ifdef __cplusplus extern "C" { #endif /* Called once before processing packets. */ void firehose_start(); /* optional */ #ifdef __cplusplus } #endif /* Called once after processing packets. */ void firehose_stop(); /* optional */ void firehose_stop() { // Close file and flush data close(pcap_file); } /* * Process a packet received from a NIC. * * pciaddr: name of PCI device packet is received from * data: packet payload (ethernet frame) * length: payload length in bytes */ inline void firehose_packet(const char *pciaddr, char *data, int length); /* Intel 82599 "Legacy" receive descriptor format. * See Intel 82599 data sheet section 7.1.5. * http://www.intel.com/content/dam/www/public/us/en/documents/datasheets/82599-10-gbe-controller-datasheet.pdf */ struct firehose_rdesc { uint64_t address; uint16_t length; uint16_t cksum; uint8_t status; uint8_t errors; uint16_t vlan; } __attribute__((packed)); /* Traverse the hardware receive descriptor ring. * Process each packet that is ready. * Return the updated ring index. */ #ifdef __cplusplus extern "C" { #endif int firehose_callback_v1(const char *pciaddr, char **packets, struct firehose_rdesc *rxring, int ring_size, int index) { while (rxring[index].status & 1) { int next_index = (index + 1) & (ring_size-1); __builtin_prefetch(packets[next_index]); firehose_packet(pciaddr, packets[index], rxring[index].length); rxring[index].status = 0; /* reset descriptor for reuse */ index = next_index; } return index; } #ifdef __cplusplus } #endif uint64_t received_packets = 0; uint64_t received_bytes = 0; void* speed_printer(void* ptr) { while (1) { uint64_t packets_before = received_packets; uint64_t bytes_before = received_bytes; sleep(1); uint64_t packets_after = received_packets; uint64_t bytes_after = received_bytes; uint64_t pps = packets_after - packets_before; uint64_t bps = bytes_after - bytes_before; float gbps_speed = (float)bps/1024/1024/1024 * 8; float gb_total = (float)received_bytes/1024/1024/1024; printf("We process: %llu pps %.2f Gbps. We will store %.2f megabytes per second. We have stored %.2f GB of data\n", (long long)pps, gbps_speed, gbps_speed / 8 * 1024, gb_total); } } void write_packet_to_file(packet_buffer_shared_pointer_t packet) { struct timeval current_time; current_time.tv_sec = 0; current_time.tv_usec = 0; // It's performance killer! bool we_do_timestamps = false; if (we_do_timestamps) { gettimeofday(¤t_time, NULL); } struct fastnetmon_pcap_pkthdr pcap_packet_header; pcap_packet_header.ts_sec = current_time.tv_sec; pcap_packet_header.ts_usec = current_time.tv_usec; pcap_packet_header.incl_len = packet->length; pcap_packet_header.orig_len = packet->length; unsigned int packet_header_written_bytes = write(pcap_file, &pcap_packet_header, sizeof(pcap_packet_header)); if (packet_header_written_bytes != sizeof(pcap_packet_header)) { printf("Can't write pcap pcaket header\n"); } unsigned int packet_written_bytes = write(pcap_file, packet->buffer, packet->length); if (packet_written_bytes != packet->length) { printf("Can't write data to file\n"); } } void* packets_consumer(void* ptr) { printf("Start consumer thread\n"); packet_buffer_shared_pointer_t packet; while (true) { while (my_spsc_queue.pop(packet)) { write_packet_to_file(packet); __sync_fetch_and_add(&received_packets, 1); __sync_fetch_and_add(&received_bytes, packet->length); } } } void sigproc(int sig) { firehose_stop(); printf("We caught SINGINT and will finish application\n"); exit(0); } #ifdef __cplusplus extern "C" { #endif // We will start speed printer void firehose_start() { signal(SIGINT, sigproc); pcap_file = open("/mnt/traffic_capture.pcap", O_TRUNC|O_WRONLY|O_CREAT); if (pcap_file < 0) { printf("Can't open file for capture\n"); exit(-1); } printf("Preaallocate %llu bytes on file system for storing traffic\n", preallocate_packet_dump_file_size); int fallocate_result = posix_fallocate(pcap_file, 0, preallocate_packet_dump_file_size); if (fallocate_result != 0) { printf("fallocate failed! Please check disk space and Linux Kernel Code\n"); } /* Caching is useless for our case because we have average linear traffic in most cases // We enable full buffering: _IOFBF int setvbuf_result = setvbuf(pcap_file, NULL, _IONBF, 1024 * 1024 * 4); if (setvbuf_result != 0) { printf("Can't set buffer for file operation\n"); } */ struct fastnetmon_pcap_file_header pcap_header; fill_pcap_header(&pcap_header, 1600); unsigned int written_bytes = write(pcap_file, &pcap_header, sizeof(pcap_header)); if (written_bytes != sizeof(pcap_header)) { printf("Can't write pcap header\n"); } pthread_t thread; pthread_create(&thread, NULL, speed_printer, NULL); pthread_t consumer_thread; pthread_create(&consumer_thread, NULL, packets_consumer, NULL); pthread_detach(thread); pthread_detach(consumer_thread); } #ifdef __cplusplus } #endif void firehose_packet(const char *pciaddr, char *data, int length) { std::shared_ptr packet_pointer( new packet_buffer_t ); packet_pointer->length = length; if (length < 1600) { memcpy(packet_pointer->buffer, data, length); } else { printf("So big packet: %d\n", length); } // Put pointer to the tube! while (!my_spsc_queue.push(packet_pointer)); } fastnetmon-1.1.3+dfsg/src/tests/snabb/ndpicallback.cpp000066400000000000000000000457021313534057500227600ustar00rootroot00000000000000// compile with: gcc -shared -o capturecallback.so -fPIC capturecallback.c #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../../fastnetmon_pcap_format.h" #include "../../fastnetmon_types.h" #include "../../fastnetmon_packet_parser.h" #include "../../fast_dpi.h" unsigned int redis_port = 6379; std::string redis_host = "127.0.0.1"; u_int32_t size_flow_struct = 0; u_int32_t size_id_struct = 0; double last_timestamp = 0; double system_tsc_resolution_hz = 0; redisContext* redis_context = NULL; #ifdef __cplusplus extern "C" { redisContext* redis_init_connection(); void store_data_in_redis(std::string key_name, std::string value) { redisReply* reply = NULL; //redisContext* redis_context = redis_init_connection(); if (!redis_context) { printf("Could not initiate connection to Redis\n"); return; } reply = (redisReply*)redisCommand(redis_context, "SET %s %s", key_name.c_str(), value.c_str()); // If we store data correctly ... if (!reply) { std::cout << "Can't increment traffic in redis error_code: " << redis_context->err << " error_string: " << redis_context->errstr; // Handle redis server restart corectly if (redis_context->err == 1 or redis_context->err == 3) { // Connection refused printf("Unfortunately we can't store data in Redis because server reject connection\n"); } } else { freeReplyObject(reply); } //redisFree(redis_context); } redisContext* redis_init_connection() { struct timeval timeout = { 1, 500000 }; // 1.5 seconds redisContext* redis_context = redisConnectWithTimeout(redis_host.c_str(), redis_port, timeout); if (redis_context->err) { std::cout << "Connection error:" << redis_context->errstr; return NULL; } // We should check connection with ping because redis do not check connection redisReply* reply = (redisReply*)redisCommand(redis_context, "PING"); if (reply) { freeReplyObject(reply); } else { return NULL; } return redis_context; } #endif inline uint64_t rte_rdtsc(void) { union { uint64_t tsc_64; struct { uint32_t lo_32; uint32_t hi_32; }; } tsc; asm volatile("rdtsc" : "=a" (tsc.lo_32), "=d" (tsc.hi_32)); return tsc.tsc_64; } void set_tsc_freq_fallback() { uint64_t start = rte_rdtsc(); sleep(1); system_tsc_resolution_hz = (double)rte_rdtsc() - start; } #ifdef __cplusplus } #endif class conntrack_hash_struct_for_simple_packet_t { public: uint32_t upper_ip; uint32_t lower_ip; uint16_t upper_port; uint16_t lower_port; unsigned int protocol; bool operator==(const conntrack_hash_struct_for_simple_packet_t& rhs) const { return memcmp(this, &rhs, sizeof(conntrack_hash_struct_for_simple_packet_t)) == 0; } }; namespace std { template<> struct hash { size_t operator()(const conntrack_hash_struct_for_simple_packet_t& x) const { std::size_t seed = 0; boost::hash_combine(seed, x.upper_ip); boost::hash_combine(seed, x.lower_ip); boost::hash_combine(seed, x.upper_port); boost::hash_combine(seed, x.lower_port); boost::hash_combine(seed, x.protocol); return seed; } }; } class ndpi_tracking_flow_t { public: ndpi_tracking_flow_t() { src = (struct ndpi_id_struct*)malloc(size_id_struct); memset(src, 0, size_id_struct); dst = (struct ndpi_id_struct*)malloc(size_id_struct); memset(dst, 0, size_id_struct); flow = (struct ndpi_flow_struct *)malloc(size_flow_struct); memset(flow, 0, size_flow_struct); update_timestamp(); } void update_timestamp() { this->last_timestamp = (double)rte_rdtsc() / system_tsc_resolution_hz; } ~ndpi_tracking_flow_t() { // We need use custom function because standard free could not free all memory here ndpi_free_flow(flow); free(dst); free(src); flow = NULL; dst = NULL; src = NULL; } ndpi_protocol detected_protocol; struct ndpi_id_struct *src = NULL; struct ndpi_id_struct *dst = NULL; struct ndpi_flow_struct *flow = NULL; bool protocol_detected = false; double last_timestamp; }; typedef std::unordered_map my_connection_tracking_storage_t; my_connection_tracking_storage_t my_connection_tracking_storage; typedef std::unordered_map known_http_hosts_t; known_http_hosts_t known_http_hosts; // For correct compilation with g++ #ifdef __cplusplus extern "C" { #endif void pcap_parse_packet(char* buffer, uint32_t len); void debug_printf(u_int32_t protocol, void *id_struct, ndpi_log_level_t log_level, const char *format, ...) { va_list va_ap; struct tm result; char buf[8192], out_buf[8192]; char theDate[32]; const char *extra_msg = ""; time_t theTime = time(NULL); va_start (va_ap, format); /* if(log_level == NDPI_LOG_ERROR) extra_msg = "ERROR: "; else if(log_level == NDPI_LOG_TRACE) extra_msg = "TRACE: "; else extra_msg = "DEBUG: "; */ memset(buf, 0, sizeof(buf)); strftime(theDate, 32, "%d/%b/%Y %H:%M:%S", localtime_r(&theTime, &result) ); vsnprintf(buf, sizeof(buf)-1, format, va_ap); snprintf(out_buf, sizeof(out_buf), "%s %s%s", theDate, extra_msg, buf); printf("%s", out_buf); fflush(stdout); va_end(va_ap); } struct ndpi_detection_module_struct* my_ndpi_struct = NULL; /* Called once before processing packets. */ void firehose_start(); /* optional */ /* Called once after processing packets. */ void firehose_stop(); /* optional */ /* * Process a packet received from a NIC. * * pciaddr: name of PCI device packet is received from * data: packet payload (ethernet frame) * length: payload length in bytes */ inline void firehose_packet(const char *pciaddr, char *data, int length); /* Intel 82599 "Legacy" receive descriptor format. * See Intel 82599 data sheet section 7.1.5. * http://www.intel.com/content/dam/www/public/us/en/documents/datasheets/82599-10-gbe-controller-datasheet.pdf */ struct firehose_rdesc { uint64_t address; uint16_t length; uint16_t cksum; uint8_t status; uint8_t errors; uint16_t vlan; } __attribute__((packed)); /* Traverse the hardware receive descriptor ring. * Process each packet that is ready. * Return the updated ring index. */ int firehose_callback_v1(const char *pciaddr, char **packets, struct firehose_rdesc *rxring, int ring_size, int index) { while (rxring[index].status & 1) { int next_index = (index + 1) & (ring_size-1); __builtin_prefetch(packets[next_index]); firehose_packet(pciaddr, packets[index], rxring[index].length); rxring[index].status = 0; /* reset descriptor for reuse */ index = next_index; } return index; } uint64_t received_packets = 0; uint64_t received_bytes = 0; void* speed_printer(void* ptr) { while (1) { uint64_t packets_before = received_packets; uint64_t bytes_before = received_bytes; sleep(1); uint64_t packets_after = received_packets; uint64_t bytes_after = received_bytes; uint64_t pps = packets_after - packets_before; uint64_t bps = bytes_after - bytes_before; printf("We process: %llu pps %.2f Gbps\n", (long long)pps, (float)bps/1024/1024/1024 * 8); // std::cout << "Hash size: " << my_connection_tracking_storage.size() << std::endl; std::cout << "Uniq hosts: " << known_http_hosts.size() << std::endl; } } // We will start speed printer void firehose_start() { my_ndpi_struct = init_ndpi(); // Connect to the Redis redis_context = redis_init_connection(); if (!redis_context) { printf("Can't connect to the Redis\n"); } // Tune timer set_tsc_freq_fallback(); // Set call time last_timestamp = (double)rte_rdtsc() / system_tsc_resolution_hz; size_id_struct = ndpi_detection_get_sizeof_ndpi_id_struct(); size_flow_struct = ndpi_detection_get_sizeof_ndpi_flow_struct(); pthread_t thread; pthread_create(&thread, NULL, speed_printer, NULL); pthread_detach(thread); } // https://code.google.com/p/smhasher/source/browse/trunk/MurmurHash2.cpp // 64-bit hash for 64-bit platforms #define BIG_CONSTANT(x) (x##LLU) uint64_t MurmurHash64A(const void* key, int len, uint64_t seed) { const uint64_t m = BIG_CONSTANT(0xc6a4a7935bd1e995); const int r = 47; uint64_t h = seed ^ (len * m); const uint64_t* data = (const uint64_t*)key; const uint64_t* end = data + (len / 8); while (data != end) { uint64_t k = *data++; k *= m; k ^= k >> r; k *= m; h ^= k; h *= m; } const unsigned char* data2 = (const unsigned char*)data; switch (len & 7) { case 7: h ^= uint64_t(data2[6]) << 48; case 6: h ^= uint64_t(data2[5]) << 40; case 5: h ^= uint64_t(data2[4]) << 32; case 4: h ^= uint64_t(data2[3]) << 24; case 3: h ^= uint64_t(data2[2]) << 16; case 2: h ^= uint64_t(data2[1]) << 8; case 1: h ^= uint64_t(data2[0]); h *= m; }; h ^= h >> r; h *= m; h ^= h >> r; return h; } // Copy and paste from netmap module inline bool parse_raw_packet_to_simple_packet(u_char* buffer, int len, simple_packet& packet) { struct pfring_pkthdr packet_header; memset(&packet_header, 0, sizeof(packet_header)); packet_header.len = len; packet_header.caplen = len; // We do not calculate timestamps because timestamping is very CPU intensive operation: // https://github.com/ntop/PF_RING/issues/9 u_int8_t timestamp = 0; u_int8_t add_hash = 0; fastnetmon_parse_pkt((u_char*)buffer, &packet_header, 4, timestamp, add_hash); // char print_buffer[512]; // fastnetmon_print_parsed_pkt(print_buffer, 512, (u_char*)buffer, &packet_header); // logger.info("%s", print_buffer); if (packet_header.extended_hdr.parsed_pkt.ip_version != 4 && packet_header.extended_hdr.parsed_pkt.ip_version != 6) { return false; } // We need this for deep packet inspection packet.packet_payload_length = len; packet.packet_payload_pointer = (void*)buffer; packet.ip_protocol_version = packet_header.extended_hdr.parsed_pkt.ip_version; if (packet.ip_protocol_version == 4) { // IPv4 /* PF_RING stores data in host byte order but we use network byte order */ packet.src_ip = htonl(packet_header.extended_hdr.parsed_pkt.ip_src.v4); packet.dst_ip = htonl(packet_header.extended_hdr.parsed_pkt.ip_dst.v4); } else { // IPv6 memcpy(packet.src_ipv6.s6_addr, packet_header.extended_hdr.parsed_pkt.ip_src.v6.s6_addr, 16); memcpy(packet.dst_ipv6.s6_addr, packet_header.extended_hdr.parsed_pkt.ip_dst.v6.s6_addr, 16); } packet.source_port = packet_header.extended_hdr.parsed_pkt.l4_src_port; packet.destination_port = packet_header.extended_hdr.parsed_pkt.l4_dst_port; packet.length = packet_header.len; packet.protocol = packet_header.extended_hdr.parsed_pkt.l3_proto; packet.ts = packet_header.ts; packet.ip_fragmented = packet_header.extended_hdr.parsed_pkt.ip_fragmented; packet.ttl = packet_header.extended_hdr.parsed_pkt.ip_ttl; // Copy flags from PF_RING header to our pseudo header if (packet.protocol == IPPROTO_TCP) { packet.flags = packet_header.extended_hdr.parsed_pkt.tcp.flags; } else { packet.flags = 0; } return true; } bool convert_simple_packet_toconntrack_hash_struct(simple_packet& packet, conntrack_hash_struct_for_simple_packet_t& conntrack_struct) { conntrack_struct.protocol = packet.protocol; // Build hash for lookup this connection uint32_t ip_src = packet.src_ip; uint32_t ip_dst = packet.dst_ip; uint16_t src_port = packet.source_port; uint16_t dst_port = packet.destination_port; // Build universal lookup structure which describes single connection if (ip_src < ip_dst) { conntrack_struct.lower_ip = ip_src; conntrack_struct.upper_ip = ip_dst; conntrack_struct.lower_port = src_port; conntrack_struct.upper_port = dst_port; } else { conntrack_struct.lower_ip = ip_dst; conntrack_struct.upper_ip = ip_src; conntrack_struct.lower_port = dst_port; conntrack_struct.upper_port = src_port; } return true; } unsigned int gc_call_timeout = 20; unsigned int gc_clean_how_old_records = 20; void firehose_packet(const char *pciaddr, char *data, int length) { // Garbadge collection code double current_timestamp = (double)rte_rdtsc() / system_tsc_resolution_hz; if (current_timestamp - last_timestamp > gc_call_timeout) { std::vector keys_to_remove; for (auto& itr : my_connection_tracking_storage) { // Remove all records who older than X seconds if (current_timestamp - itr.second.last_timestamp > gc_clean_how_old_records) { keys_to_remove.push_back(itr.first); } } //if (!keys_to_remove.empty()) { // std::cout << "We will remove " << keys_to_remove.size() << " keys" << std::endl; //} for (auto key_to_remove : keys_to_remove) { my_connection_tracking_storage.erase(key_to_remove); } last_timestamp = current_timestamp; } // GC code ends __sync_fetch_and_add(&received_packets, 1); __sync_fetch_and_add(&received_bytes, length); struct pfring_pkthdr packet_header; memset(&packet_header, 0, sizeof(packet_header)); packet_header.len = length; packet_header.caplen = length; // We do not calculate timestamps because timestamping is very CPU intensive operation: // https://github.com/ntop/PF_RING/issues/9 u_int8_t timestamp = 0; u_int8_t add_hash = 0; fastnetmon_parse_pkt((u_char*)data, &packet_header, 4, timestamp, add_hash); simple_packet current_packet; parse_raw_packet_to_simple_packet((u_char*)data, length, current_packet); conntrack_hash_struct_for_simple_packet_t conntrack_structure; convert_simple_packet_toconntrack_hash_struct(current_packet, conntrack_structure); ndpi_tracking_flow_t& dpi_tracking_structure = my_connection_tracking_storage[ conntrack_structure ]; // Protocol already detected /* if (dpi_tracking_structure.protocol_detected && dpi_tracking_structure.detected_protocol.protocol == NDPI_PROTOCOL_IRC) { char print_buffer[512]; fastnetmon_print_parsed_pkt(print_buffer, 512, (u_char*)data, &packet_header); printf("packet: %s\n", print_buffer); for (unsigned int index = packet_header.extended_hdr.parsed_pkt.offset.payload_offset; index < packet_header.len; index++) { printf("%c", data[index]); } printf("\n"); return; } */ dpi_tracking_structure.update_timestamp(); uint32_t current_tickt = 0 ; uint8_t* iph = (uint8_t*)(&data[packet_header.extended_hdr.parsed_pkt.offset.l3_offset]); // printf("vlan: %d\n", packet_header.extended_hdr.parsed_pkt.vlan_id); struct ndpi_iphdr* ndpi_ip_header = (struct ndpi_iphdr*)iph; unsigned int ipsize = packet_header.len; ndpi_protocol detected_protocol = ndpi_detection_process_packet(my_ndpi_struct, dpi_tracking_structure.flow, iph, ipsize, current_tickt, dpi_tracking_structure.src, dpi_tracking_structure.dst); if (detected_protocol.protocol == NDPI_PROTOCOL_UNKNOWN && detected_protocol.master_protocol == NDPI_PROTOCOL_UNKNOWN) { // printf("Can't detect protocol\n"); } else { dpi_tracking_structure.detected_protocol = detected_protocol; dpi_tracking_structure.protocol_detected = true; //printf("Master protocol: %d protocol: %d\n", detected_protocol.master_protocol, detected_protocol.protocol); char* protocol_name = ndpi_get_proto_name(my_ndpi_struct, detected_protocol.protocol); char* master_protocol_name = ndpi_get_proto_name(my_ndpi_struct, detected_protocol.master_protocol); if (detected_protocol.protocol == NDPI_PROTOCOL_HTTP) { std::string host_name = std::string((const char*)dpi_tracking_structure.flow->host_server_name); //printf("server name: %s\n", dpi_tracking_structure.flow->host_server_name); if (redis_context != NULL) { known_http_hosts_t::iterator itr = known_http_hosts.find(host_name); if (itr == known_http_hosts.end()) { // Not defined in internal cache // Add in local cache: known_http_hosts[ host_name ] = 1; // Add to Redis store_data_in_redis(host_name, "1"); } else { // Already stored } } } //printf("Protocol: %s master protocol: %s\n", protocol_name, master_protocol_name); bool its_bad_protocol = false; //if(ndpi_is_proto(detected_protocol, NDPI_PROTOCOL_TOR)) { // its_bad_protocol = true; //} if (detected_protocol.protocol == NDPI_PROTOCOL_IRC or detected_protocol.master_protocol == NDPI_PROTOCOL_IRC) { its_bad_protocol = true; } if (its_bad_protocol) { printf("Bad protocol %s master protocol %s found\n", protocol_name, master_protocol_name); char print_buffer[512]; fastnetmon_print_parsed_pkt(print_buffer, 512, (u_char*)data, &packet_header); printf("packet: %s\n", print_buffer); for (unsigned int index = packet_header.extended_hdr.parsed_pkt.offset.payload_offset; index < packet_header.len; index++) { printf("%c", data[index]); } printf("\n"); } } } #ifdef __cplusplus } #endif int main(int argc, char** argv) { my_ndpi_struct = init_ndpi(); size_id_struct = ndpi_detection_get_sizeof_ndpi_id_struct(); size_flow_struct = ndpi_detection_get_sizeof_ndpi_flow_struct(); if (argc != 2) { printf("Please specify path to dump file\n"); exit(-1); } const char* path = argv[1]; //pcap_reader(path, pcap_parse_packet); } fastnetmon-1.1.3+dfsg/src/tests/sort_struct.cpp000066400000000000000000000061471313534057500216570ustar00rootroot00000000000000#include #include #include #include #include #include bool compare_min(unsigned int a, unsigned int b) { return a > b; } bool compare_max(unsigned int a, unsigned int b) { return a < b; } template class fast_priority_queue { public: fast_priority_queue(unsigned int queue_size) { this->queue_size = queue_size; internal_list.reserve(queue_size); } void insert(order_by_template_type main_value, int data) { // Because it's ehap we can remove // Append new element to the end of list internal_list.push_back(main_value); // Convert list to the complete heap // Up to logarithmic in the distance between first and last: Compares elements and // potentially swaps (or moves) them until rearranged as a longer heap. std::push_heap(internal_list.begin(), internal_list.end(), compare_min); if (this->internal_list.size() >= queue_size) { // And now we should remove minimal element from the internal_list // Prepare heap to remove min element std::pop_heap(internal_list.begin(), internal_list.end(), compare_min); // Remove element from the head internal_list.pop_back(); } } order_by_template_type get_min_element() { // We will return head of list because it's consists minimum element return internal_list.front(); } void print_internal_list() { for (unsigned int i = 0; i < internal_list.size(); i++) { std::cout << internal_list[i] << std::endl; } } void print() { // Create new list for sort because we can't do it in place std::vector sorted_list; // Allocate enough space sorted_list.reserve(internal_list.size()); // Copy to new vector with copy constructor sorted_list = internal_list; // Execute heap sort because array paritally sorted already std::sort_heap(sorted_list.begin(), sorted_list.end(), compare_min); for (unsigned int i = 0; i < sorted_list.size(); i++) { std::cout << sorted_list[i] << std::endl; } } private: order_by_template_type max_number; order_by_template_type min_number; unsigned int queue_size; // We can't use list here! std::vector internal_list; // std::priority_queue, std::less > class_priority_queue; }; int main() { fast_priority_queue my_priority_queue(10); for (int i = 0; i < 100; i++) { int current_value = rand() % 100; // std::cout< my_priority_queue.get_min_element()) { // Check for existence of this element in struct already my_priority_queue.insert(current_value, 0); } } std::cout << "Print internal list" << std::endl; my_priority_queue.print_internal_list(); std::cout << "Print sorted list" << std::endl; my_priority_queue.print(); } fastnetmon-1.1.3+dfsg/src/tests/spsc_prototype.cpp000066400000000000000000000073651313534057500223640ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #include "../libpatricia/patricia.h" #include "../fastnetmon_types.h" #include "../fast_library.h" #include "../netflow_plugin/netflow_collector.h" #include "../sflow_plugin/sflow_collector.h" #include "../pcap_plugin/pcap_collector.h" #ifdef PF_RING #include "../pfring_plugin/pfring_collector.h" #endif #include "../netmap_plugin/netmap_collector.h" #include #include #include // log4cpp logging facility #include "log4cpp/Category.hh" #include "log4cpp/Appender.hh" #include "log4cpp/FileAppender.hh" #include "log4cpp/OstreamAppender.hh" #include "log4cpp/Layout.hh" #include "log4cpp/BasicLayout.hh" #include "log4cpp/PatternLayout.hh" #include "log4cpp/Priority.hh" #include #include #include #include #include using namespace std; typedef simple_packet* simple_packet_shared_ptr_t; typedef boost::lockfree::spsc_queue< simple_packet_shared_ptr_t, boost::lockfree::capacity<1048576> > my_spsc_queue_t; uint64_t total_unparsed_packets = 0; my_spsc_queue_t my_spsc_queue[8]; std::string log_file_path = "/tmp/fastnetmon_plugin_tester.log"; log4cpp::Category& logger = log4cpp::Category::getRoot(); #include extern boost::pool_allocator alloc[8]; // #define DO_SUBNET_LOOKUP #ifdef DO_SUBNET_LOOKUP patricia_tree_t* lookup_tree; #endif // Global map with parsed config file std::map configuration_map; void init_logging() { log4cpp::PatternLayout* layout = new log4cpp::PatternLayout(); layout->setConversionPattern("%d [%p] %m%n"); log4cpp::Appender* appender = new log4cpp::FileAppender("default", log_file_path); appender->setLayout(layout); logger.setPriority(log4cpp::Priority::INFO); logger.addAppender(appender); logger.info("Logger initialized!"); } uint64_t received_packets = 0; void process_packet(simple_packet& current_packet) { //__sync_fetch_and_add(&received_packets, 1); //std::cout << print_simple_packet(current_packet); } std::unordered_map map_counter; void traffic_processor() { simple_packet_shared_ptr_t packet; //map_counter.reserve(16000000); while (1) { for (int i = 0; i < 8; i ++) { // while (!my_spsc_queue[thread_number].push(packet)); while (my_spsc_queue[i].pop(packet)) { //std::cout << print_simple_packet(packet); //map_counter[packet.src_ip]++; __sync_fetch_and_add(&received_packets, 1); delete packet; //alloc[i].deallocate(packet, 1); } } } } void speed_printer() { while (true) { uint64_t packets_before = received_packets; boost::this_thread::sleep(boost::posix_time::seconds(1)); uint64_t packets_after = received_packets; uint64_t pps = packets_after - packets_before; printf("We process: %llu pps\n", pps); } } int main(int argc, char* argv[]) { boost::thread speed_printer_thread( speed_printer ); boost::thread traffic_processor_thread(traffic_processor); init_logging(); // Required by Netmap and PF_RING plugins // We use fake interface name here because netmap could make server unreachable :) configuration_map["interfaces"] = "eth5"; start_netmap_collection(process_packet); traffic_processor_thread.join(); speed_printer_thread.join(); } fastnetmon-1.1.3+dfsg/src/tests/store_data_to_graphite.cpp000066400000000000000000000021371313534057500237710ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #include int main() { int sockfd = 0; if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { printf("\n Error : Could not create socket \n"); return 1; } struct sockaddr_in serv_addr; memset(&serv_addr, 0, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_port = htons(2003); if (inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr) <= 0) { printf("\n inet_pton error occured\n"); return 1; } if (connect(sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0) { printf("\n Error : Connect Failed \n"); return 1; } unsigned long long pps = 10000778; char buffer[256]; sprintf(buffer, "client.ip.in.udp %ld %ld\n", pps, time(NULL)); int write_result = write(sockfd, buffer, strlen(buffer)); printf("We store %d bytes\n", write_result); } fastnetmon-1.1.3+dfsg/src/tests/test_cidr.cpp000066400000000000000000000027141313534057500212400ustar00rootroot00000000000000#include #include #include #include #include #include #include #include uint32_t convert_cidr_to_binary_netmask(unsigned int cidr) { uint32_t binary_netmask = 0xFFFFFFFF; binary_netmask = binary_netmask << (32 - cidr); // htonl from host byte order to network // ntohl from network byte order to host // We need network byte order at output return htonl(binary_netmask); } uint32_t convert_ip_as_string_to_uint(std::string ip) { struct in_addr ip_addr; inet_aton(ip.c_str(), &ip_addr); // in network byte order return ip_addr.s_addr; } int main() { uint32_t network_zero = convert_ip_as_string_to_uint("10.10.10.0"); uint32_t network_200 = convert_ip_as_string_to_uint("10.10.10.200"); uint32_t binary_netmask = convert_cidr_to_binary_netmask(24); uint32_t generated_subnet_address = network_200 & binary_netmask; std::cout << "network byte order" << std::endl; std::cout << "10.10.10.200/24\tnetwork byte order:" << network_200 << " host byte order:" << ntohl(network_200) << std::endl; std::cout << "10.10.10.0/24\tnetwork byte order:" << network_zero << " host byte order:" << ntohl(network_zero) << std::endl; std::cout << "generated \tnetwork byte order:" << generated_subnet_address << " host byte order:" << ntohl(generated_subnet_address) << std::endl; } fastnetmon-1.1.3+dfsg/src/tests/tins_parser.cpp000066400000000000000000000007201313534057500216040ustar00rootroot00000000000000#include #include // g++ tins_parser.cpp -ltins using namespace Tins; bool callback(const PDU &pdu) { const IP &ip = pdu.rfind_pdu(); // Find the IP layer const TCP &tcp = pdu.rfind_pdu(); // Find the TCP layer std::cout << ip.src_addr() << ':' << tcp.sport() << " -> " << ip.dst_addr() << ':' << tcp.dport() << std::endl; return true; } int main() { Sniffer("eth0").sniff_loop(callback); } fastnetmon-1.1.3+dfsg/src/tests/traffic_structures_performance_tests.cpp000066400000000000000000000237551313534057500270140ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../fastnetmon_types.h" // apt-get install -y libtbb-dev libsparsehash-dev // g++ -O3 traffic_structures_performance_tests.cpp -std=c++11 -lboost_system -lboost_thread -ltbb -I/opt/local/include -L/opt/local/lib // clang traffic_structures_performance_tests.cpp -std=c++11 -lboost_system -lboost_thread -ltbb -I/opt/local/include -L/opt/local/lib -lstdc++ -lm // Mac OS: // g++ traffic_structures_performance_tests.cpp -std=c++11 -lboost_system_mt -lboost_thread_mt -ltbb -I/opt/local/include -L/opt/local/lib -g -pg #ifndef __APPLE__ #include "tbb/concurrent_unordered_map.h" #endif #include // No speed benefits // std::map, boost::fast_pool_allocator> std::map DataCounter; boost::mutex data_counter_mutex; std::unordered_map DataCounterUnordered; std::unordered_map DataCounterUnorderedPreallocated; boost::unordered_map DataCounterBoostUnordered; boost::container::flat_map DataCounterBoostFlatMap; struct eqint { bool operator()(uint32_t a, uint32_t b) const { return a == b; } }; google::dense_hash_map, eqint> DataCounterGoogleDensehashMapPreallocated; google::dense_hash_map, eqint> DataCounterGoogleDensehashMap; #ifndef __APPLE__ tbb::concurrent_unordered_map DataCounterUnorderedConcurrent; #endif std::vector DataCounterVector; using namespace std; int number_of_ips = 10 * 1000 * 1000; int number_of_retries = 1; // #define enable_mutexex_in_test unsigned int number_of_threads = 1; // 83 seconds // without mutexes segmentation fault void packet_collector_thread_std_map() { for (int iteration = 0; iteration < number_of_retries; iteration++) { for (uint32_t i = 0; i < number_of_ips; i++) { #ifdef enable_mutexex_in_test data_counter_mutex.lock(); #endif DataCounter[i].udp_in_bytes++; #ifdef enable_mutexex_in_test data_counter_mutex.unlock(); #endif } } } void packet_collector_thread_boost_unordered_map() { for (int iteration = 0; iteration < number_of_retries; iteration++) { for (uint32_t i = 0; i < number_of_ips; i++) { #ifdef enable_mutexex_in_test data_counter_mutex.lock(); #endif DataCounterBoostUnordered[i].udp_in_bytes++; #ifdef enable_mutexex_in_test data_counter_mutex.unlock(); #endif } } } void packet_collector_thread_unordered_map() { for (int iteration = 0; iteration < number_of_retries; iteration++) { for (uint32_t i = 0; i < number_of_ips; i++) { #ifdef enable_mutexex_in_test data_counter_mutex.lock(); #endif DataCounterUnordered[i].udp_in_bytes++; #ifdef enable_mutexex_in_test data_counter_mutex.unlock(); #endif } } } void packet_collector_thread_google_dense_hash_map_preallocated() { for (int iteration = 0; iteration < number_of_retries; iteration++) { for (uint32_t i = 0; i < number_of_ips; i++) { #ifdef enable_mutexex_in_test data_counter_mutex.lock(); #endif DataCounterGoogleDensehashMapPreallocated[i].udp_in_bytes++; #ifdef enable_mutexex_in_test data_counter_mutex.unlock(); #endif } } } void packet_collector_thread_google_dense_hash_map() { for (int iteration = 0; iteration < number_of_retries; iteration++) { for (uint32_t i = 0; i < number_of_ips; i++) { #ifdef enable_mutexex_in_test data_counter_mutex.lock(); #endif DataCounterGoogleDensehashMap[i].udp_in_bytes++; #ifdef enable_mutexex_in_test data_counter_mutex.unlock(); #endif } } } void packet_collector_thread_unordered_map_preallocated() { for (int iteration = 0; iteration < number_of_retries; iteration++) { for (uint32_t i = 0; i < number_of_ips; i++) { #ifdef enable_mutexex_in_test data_counter_mutex.lock(); #endif DataCounterUnordered[i].udp_in_bytes++; #ifdef enable_mutexex_in_test data_counter_mutex.unlock(); #endif } } } void packet_collector_thread_flat_map_preallocated() { for (int iteration = 0; iteration < number_of_retries; iteration++) { for (uint32_t i = 0; i < number_of_ips; i++) { #ifdef enable_mutexex_in_test data_counter_mutex.lock(); #endif DataCounterBoostFlatMap[i].udp_in_bytes++; #ifdef enable_mutexex_in_test data_counter_mutex.unlock(); #endif } } } void packet_collector_thread_vector() { for (int iteration = 0; iteration < number_of_retries; iteration++) { for (uint32_t i = 0; i < number_of_ips; i++) { DataCounterVector[i].udp_in_bytes++; } } } #ifndef __APPLE__ void packet_collector_thread_unordered_concurrent_map() { for (int iteration = 0; iteration < number_of_retries; iteration++) { for (uint32_t i = 0; i < number_of_ips; i++) { DataCounterUnorderedConcurrent[i].udp_in_bytes++; } } } #endif // http://www.gnu.org/software/libc/manual/html_node/Elapsed-Time.html int timeval_subtract(struct timeval* result, struct timeval* x, struct timeval* y) { /* Perform the carry for the later subtraction by updating y. */ if (x->tv_usec < y->tv_usec) { int nsec = (y->tv_usec - x->tv_usec) / 1000000 + 1; y->tv_usec -= 1000000 * nsec; y->tv_sec += nsec; } if (x->tv_usec - y->tv_usec > 1000000) { int nsec = (x->tv_usec - y->tv_usec) / 1000000; y->tv_usec += 1000000 * nsec; y->tv_sec -= nsec; } /* Compute the time remaining to wait. tv_usec is certainly positive. */ result->tv_sec = x->tv_sec - y->tv_sec; result->tv_usec = x->tv_usec - y->tv_usec; /* Return 1 if result is negative. */ return x->tv_sec < y->tv_sec; } int run_tests(void (*tested_function)(void)) { timeval start_time; gettimeofday(&start_time, NULL); // std::cout << "Run "<< number_of_threads <<" threads" << endl; boost::thread* threads[number_of_threads]; for (int i = 0; i < number_of_threads; i++) { threads[i] = new boost::thread(tested_function); } // std::cout << "All threads started" << endl; // std::cout << "Wait for finishing" << endl; for (int i = 0; i < number_of_threads; i++) { threads[i]->join(); } //cout << "All threads finished" << endl; timeval finish_time; gettimeofday(&finish_time, NULL); double total_operations = number_of_ips * number_of_retries * number_of_threads; // We use ' for pretty print of long numbers // http://stackoverflow.com/questions/1499156/convert-astronomically-large-numbers-into-human-readable-form-in-c-c setlocale(LC_NUMERIC, "en_US.utf-8"); /* important */ timeval interval; timeval_subtract(&interval, &finish_time, &start_time); // Build time with float part double used_time = (double)interval.tv_sec + (double)interval.tv_usec / 1000000; // printf("We spent %f seconds\n", used_time); double ops_per_second = total_operations / used_time;; double mega_ops_per_second = ops_per_second / 1000 / 1000; printf("%'.1f mega ops per second\n", mega_ops_per_second); return 0; } bool enabled_slow_data_structures_test = true; int main() { std::cout << "Element size: " << sizeof(map_element) << std::endl; if (enabled_slow_data_structures_test) { std::cout << "std::map: "; run_tests(packet_collector_thread_std_map); DataCounter.clear(); #ifndef __APPLE__ std::cout << "tbb::concurrent_unordered_map: "; run_tests(packet_collector_thread_unordered_concurrent_map); DataCounterUnorderedConcurrent.clear(); #endif // Boost unordered map std::cout << "boost::unordered_map: "; run_tests(packet_collector_thread_boost_unordered_map); DataCounterBoostUnordered.clear(); // Boost flat_map DataCounterBoostFlatMap.reserve( number_of_ips ); std::cout << "boost::container::flat_map with preallocated elements: "; run_tests(packet_collector_thread_flat_map_preallocated); DataCounterBoostFlatMap.clear(); } std::cout << "std::unordered_map C++11: "; run_tests(packet_collector_thread_unordered_map); DataCounterUnordered.clear(); // Preallocate hash buckets DataCounterUnorderedPreallocated.reserve( number_of_ips ); std::cout << "std::unordered_map C++11 preallocated buckets: "; run_tests(packet_collector_thread_unordered_map_preallocated); DataCounterUnorderedPreallocated.clear(); std::cout << "google:dense_hashmap without preallocation: "; DataCounterGoogleDensehashMap.set_empty_key(UINT32_MAX); // We will got assert without it! run_tests(packet_collector_thread_google_dense_hash_map); DataCounterGoogleDensehashMap.clear(); std::cout << "google:dense_hashmap preallocated buckets: "; // We use UINT32_MAX as "empty" here, not a good idea but OK for tests DataCounterGoogleDensehashMapPreallocated.set_empty_key(UINT32_MAX); // We will got assert without it! DataCounterGoogleDensehashMapPreallocated.resize( number_of_ips ); run_tests(packet_collector_thread_google_dense_hash_map_preallocated); DataCounterGoogleDensehashMapPreallocated.clear(); // Preallocate vector DataCounterVector.reserve( number_of_ips ); std::cout << "std::vector preallocated: "; run_tests(packet_collector_thread_vector); DataCounterVector.clear(); } fastnetmon-1.1.3+dfsg/src/tests/tsc_timers.cpp000066400000000000000000000034631313534057500214360ustar00rootroot00000000000000#include #include #include #include #include #include inline uint64_t read_tsc_cpu_register(void) { union { uint64_t tsc_64; struct { uint32_t lo_32; uint32_t hi_32; }; } tsc; asm volatile("rdtsc" : "=a" (tsc.lo_32), "=d" (tsc.hi_32)); return tsc.tsc_64; } uint64_t get_tsc_freq_with_sleep() { uint64_t start = read_tsc_cpu_register(); sleep(1); return read_tsc_cpu_register() - start; } uint64_t get_tsc_freq_from_clock(void) { //#ifdef CLOCK_MONOTONIC_RAW #define NS_PER_SEC 1E9 struct timespec sleeptime; sleeptime.tv_sec = 1; sleeptime.tv_nsec = 5E8; /* 1/2 second */ struct timespec t_start, t_end; if (clock_gettime(CLOCK_MONOTONIC_RAW, &t_start) == 0) { uint64_t ns, end, start; start = read_tsc_cpu_register(); nanosleep(&sleeptime,NULL); clock_gettime(CLOCK_MONOTONIC_RAW, &t_end); end = read_tsc_cpu_register(); ns = ((t_end.tv_sec - t_start.tv_sec) * NS_PER_SEC); ns += (t_end.tv_nsec - t_start.tv_nsec); double secs = (double)ns/NS_PER_SEC; return (uint64_t)((end - start)/secs); } //#endif } int main() { /* The frequency of the RDTSC timer resolution */ uint64_t fastnetmon_tsc_resolution_hz = 0; printf("Determine TSC freq with sleep\n"); fastnetmon_tsc_resolution_hz = get_tsc_freq_with_sleep(); printf("TSC freq is %llu\n", fastnetmon_tsc_resolution_hz); printf("Determing TSC freq with CLOCK_MONOTONIC_RAW\n"); fastnetmon_tsc_resolution_hz = get_tsc_freq_from_clock(); printf("TSC freq is %llu\n", fastnetmon_tsc_resolution_hz); printf("Current TSC value %llu\n", read_tsc_cpu_register()); }